paul@0 | 1 | /* |
paul@0 | 2 | * Access the LCD and related GPIOs on the Ben NanoNote. |
paul@0 | 3 | * |
paul@0 | 4 | * (c) 2017, 2018 Paul Boddie <paul@boddie.org.uk> |
paul@0 | 5 | * |
paul@0 | 6 | * This program is free software; you can redistribute it and/or |
paul@0 | 7 | * modify it under the terms of the GNU General Public License as |
paul@0 | 8 | * published by the Free Software Foundation; either version 2 of |
paul@0 | 9 | * the License, or (at your option) any later version. |
paul@0 | 10 | * |
paul@0 | 11 | * This program is distributed in the hope that it will be useful, |
paul@0 | 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
paul@0 | 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
paul@0 | 14 | * GNU General Public License for more details. |
paul@0 | 15 | * |
paul@0 | 16 | * You should have received a copy of the GNU General Public License |
paul@0 | 17 | * along with this program; if not, write to the Free Software |
paul@0 | 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, |
paul@0 | 19 | * Boston, MA 02110-1301, USA |
paul@0 | 20 | */ |
paul@0 | 21 | |
paul@0 | 22 | #include <l4/devices/cpm-jz4740.h> |
paul@0 | 23 | #include <l4/devices/gpio-jz4740.h> |
paul@0 | 24 | #include <l4/io/io.h> |
paul@0 | 25 | #include <l4/re/env.h> |
paul@0 | 26 | #include <l4/re/c/mem_alloc.h> |
paul@0 | 27 | #include <l4/re/c/rm.h> |
paul@0 | 28 | #include <l4/re/c/util/cap_alloc.h> |
paul@0 | 29 | #include <l4/sys/cache.h> |
paul@0 | 30 | #include <l4/util/util.h> |
paul@0 | 31 | #include <l4/vbus/vbus.h> |
paul@0 | 32 | |
paul@0 | 33 | #include <stdio.h> |
paul@0 | 34 | #include <stdint.h> |
paul@0 | 35 | |
paul@0 | 36 | #include "nanonote_gpm940b0.h" |
paul@0 | 37 | #include "jzlcd.h" |
paul@0 | 38 | #include "xburst_types.h" |
paul@0 | 39 | #include "memory.h" |
paul@0 | 40 | |
paul@0 | 41 | enum Jz4740_lcd_gpio |
paul@0 | 42 | { |
paul@0 | 43 | Jz4740_lcd_gpio_spen = 21, /* serial command enable */ |
paul@0 | 44 | Jz4740_lcd_gpio_spda = 22, /* serial command clock */ |
paul@0 | 45 | Jz4740_lcd_gpio_spck = 23, /* serial command data */ |
paul@0 | 46 | }; |
paul@0 | 47 | |
paul@0 | 48 | /* Peripheral memory regions. */ |
paul@0 | 49 | |
paul@0 | 50 | static l4_addr_t cpm_virt_base = 0, cpm_virt_base_end = 0; |
paul@0 | 51 | static l4_addr_t gpio_virt_base = 0, gpio_virt_base_end = 0; |
paul@0 | 52 | static l4_addr_t lcd_virt_base = 0, lcd_virt_base_end = 0; |
paul@0 | 53 | |
paul@0 | 54 | /* Device abstractions. */ |
paul@0 | 55 | |
paul@0 | 56 | static void *gpio_port_c; |
paul@0 | 57 | static void *cpm_device = 0; |
paul@0 | 58 | static vidinfo_t *panel_info = 0; |
paul@0 | 59 | |
paul@0 | 60 | /* Framebuffer and descriptor regions. */ |
paul@0 | 61 | |
paul@0 | 62 | static void *fb_vaddr = 0, *desc_vaddr = 0; |
paul@0 | 63 | static l4_addr_t fb_paddr = 0, desc_paddr = 0; |
paul@0 | 64 | static l4_size_t fb_size, desc_size; |
paul@0 | 65 | |
paul@0 | 66 | |
paul@0 | 67 | |
paul@0 | 68 | // Write SPI values via the LCD GPIO pins. |
paul@0 | 69 | |
paul@0 | 70 | static void spi_write_reg(uint8_t reg, uint8_t val) |
paul@0 | 71 | { |
paul@0 | 72 | uint8_t no; |
paul@0 | 73 | uint16_t value; |
paul@0 | 74 | |
paul@0 | 75 | jz4740_gpio_set(gpio_port_c, Jz4740_lcd_gpio_spen, 1); |
paul@0 | 76 | jz4740_gpio_set(gpio_port_c, Jz4740_lcd_gpio_spck, 1); |
paul@0 | 77 | jz4740_gpio_set(gpio_port_c, Jz4740_lcd_gpio_spda, 0); |
paul@0 | 78 | jz4740_gpio_set(gpio_port_c, Jz4740_lcd_gpio_spen, 0); |
paul@0 | 79 | |
paul@0 | 80 | value = ((reg << 8) | (val & 0xFF)); |
paul@0 | 81 | |
paul@0 | 82 | /* Clock data using the clock and data outputs. */ |
paul@0 | 83 | |
paul@0 | 84 | for (no = 0; no < 16; no++) |
paul@0 | 85 | { |
paul@0 | 86 | jz4740_gpio_set(gpio_port_c, Jz4740_lcd_gpio_spck, 0); |
paul@0 | 87 | jz4740_gpio_set(gpio_port_c, Jz4740_lcd_gpio_spda, value & 0x8000 ? 1 : 0); |
paul@0 | 88 | jz4740_gpio_set(gpio_port_c, Jz4740_lcd_gpio_spck, 1); |
paul@0 | 89 | value = (value << 1); |
paul@0 | 90 | } |
paul@0 | 91 | |
paul@0 | 92 | jz4740_gpio_set(gpio_port_c, Jz4740_lcd_gpio_spen, 1); |
paul@0 | 93 | } |
paul@0 | 94 | |
paul@0 | 95 | // GPIO-based operations. |
paul@0 | 96 | |
paul@0 | 97 | static void lcd_display_pin_init(void) |
paul@0 | 98 | { |
paul@0 | 99 | Pin_slice mask = {.offset=0, .mask=(1 << Jz4740_lcd_gpio_spen) | (1 << Jz4740_lcd_gpio_spck) | (1 << Jz4740_lcd_gpio_spda)}; |
paul@0 | 100 | Pin_slice slcd8_mask = {.offset=0, .mask=0x003c00ff}; |
paul@0 | 101 | |
paul@0 | 102 | /* Configure SPI pins. */ |
paul@0 | 103 | |
paul@0 | 104 | jz4740_gpio_multi_setup(gpio_port_c, &mask, L4VBUS_GPIO_SETUP_OUTPUT, 0); |
paul@0 | 105 | |
paul@0 | 106 | /* Configure SLCD8 pins. */ |
paul@0 | 107 | |
paul@0 | 108 | jz4740_gpio_multi_config_pad(gpio_port_c, &slcd8_mask, Function_alt, 0); |
paul@0 | 109 | } |
paul@0 | 110 | |
paul@0 | 111 | static void lcd_display_on(void) |
paul@0 | 112 | { |
paul@0 | 113 | spi_write_reg(0x05, 0x1e); // GRB=0 (reset); PWM_DUTY=0b011 (70%, default); SHDB2=1, SHDB1=1 (power-related); STB=0 (standby) |
paul@0 | 114 | spi_write_reg(0x05, 0x5e); // GRB=1 (normal operation); ... |
paul@0 | 115 | spi_write_reg(0x07, 0x8d); // HBLK=141 (horizontal blanking period from start of hsync pulse to data start) |
paul@0 | 116 | spi_write_reg(0x13, 0x01); // IN_SEL=1 (alignment mode) |
paul@0 | 117 | spi_write_reg(0x05, 0x5f); // ...; STB=1 (not standby) |
paul@0 | 118 | } |
paul@0 | 119 | |
paul@0 | 120 | /* CPM operations. */ |
paul@0 | 121 | |
paul@0 | 122 | static void lcd_set_timing(vidinfo_t *vid) |
paul@0 | 123 | { |
paul@0 | 124 | uint32_t pclk = jz4740_lcd_get_pixel_clock(vid); |
paul@0 | 125 | |
paul@0 | 126 | jz4740_cpm_stop_lcd(cpm_device); |
paul@0 | 127 | |
paul@0 | 128 | /* |
paul@0 | 129 | Original comment: LCDClock > 2.5*Pixclock |
paul@0 | 130 | However, the documentation indicates that a TFT panel needs a device clock |
paul@0 | 131 | 1.5 times that of the pixel clock, and a STN panel needs a device clock 3 |
paul@0 | 132 | times that of the pixel clock. |
paul@0 | 133 | */ |
paul@0 | 134 | |
paul@0 | 135 | jz4740_cpm_set_lcd_frequencies(cpm_device, pclk, 3); |
paul@0 | 136 | jz4740_cpm_update_output_frequency(cpm_device); |
paul@0 | 137 | jz4740_cpm_start_lcd(cpm_device); |
paul@0 | 138 | |
paul@0 | 139 | l4_sleep(1); // 1ms == 1000us |
paul@0 | 140 | } |
paul@0 | 141 | |
paul@0 | 142 | |
paul@0 | 143 | |
paul@0 | 144 | static int setup_memory(void) |
paul@0 | 145 | { |
paul@0 | 146 | l4re_ds_t fbmem, descmem; |
paul@0 | 147 | l4_size_t fb_size_out, desc_size_out; |
paul@0 | 148 | int result = 0; |
paul@0 | 149 | |
paul@0 | 150 | if (fb_vaddr) |
paul@0 | 151 | return 0; |
paul@0 | 152 | |
paul@0 | 153 | if (!panel_info) |
paul@0 | 154 | return 1; |
paul@0 | 155 | |
paul@0 | 156 | fb_size = jz4740_lcd_get_screen_size(panel_info); |
paul@0 | 157 | desc_size = jz4740_lcd_get_descriptors_size(panel_info); |
paul@0 | 158 | |
paul@0 | 159 | /* Obtain resource details describing the I/O memory. */ |
paul@0 | 160 | |
paul@0 | 161 | if ((result = get_memory("jz4740-cpm", &cpm_virt_base, &cpm_virt_base_end)) < 0) |
paul@0 | 162 | return 1; |
paul@0 | 163 | |
paul@0 | 164 | if ((result = get_memory("jz4740-lcd", &lcd_virt_base, &lcd_virt_base_end)) < 0) |
paul@0 | 165 | return 1; |
paul@0 | 166 | |
paul@0 | 167 | if ((result = get_memory("jz4740-gpio", &gpio_virt_base, &gpio_virt_base_end)) < 0) |
paul@0 | 168 | return 1; |
paul@0 | 169 | |
paul@0 | 170 | /* Set the framebuffer up. */ |
paul@0 | 171 | |
paul@0 | 172 | fbmem = l4re_util_cap_alloc(); |
paul@0 | 173 | descmem = l4re_util_cap_alloc(); |
paul@0 | 174 | |
paul@0 | 175 | if (l4_is_invalid_cap(fbmem) || l4_is_invalid_cap(descmem)) |
paul@0 | 176 | return 1; |
paul@0 | 177 | |
paul@0 | 178 | /* Allocate memory for the framebuffer at 2**6 == 64 byte == 16 word alignment, |
paul@0 | 179 | also for the descriptors. */ |
paul@0 | 180 | |
paul@0 | 181 | if (l4re_ma_alloc_align(fb_size, fbmem, L4RE_MA_CONTINUOUS | L4RE_MA_PINNED, 6) || |
paul@0 | 182 | l4re_ma_alloc_align(desc_size, descmem, L4RE_MA_CONTINUOUS | L4RE_MA_PINNED, 6)) |
paul@0 | 183 | return 1; |
paul@0 | 184 | |
paul@0 | 185 | /* Map the allocated memory, obtaining virtual addresses. */ |
paul@0 | 186 | |
paul@0 | 187 | if (l4re_rm_attach(&fb_vaddr, fb_size, |
paul@0 | 188 | L4RE_RM_SEARCH_ADDR | L4RE_RM_EAGER_MAP, |
paul@0 | 189 | fbmem, 0, L4_PAGESHIFT) || |
paul@0 | 190 | l4re_rm_attach(&desc_vaddr, desc_size, |
paul@0 | 191 | L4RE_RM_SEARCH_ADDR | L4RE_RM_EAGER_MAP, |
paul@0 | 192 | descmem, 0, L4_PAGESHIFT)) |
paul@0 | 193 | return 1; |
paul@0 | 194 | |
paul@0 | 195 | fb_size_out = fb_size; |
paul@0 | 196 | desc_size_out = desc_size; |
paul@0 | 197 | |
paul@0 | 198 | if (l4re_ds_phys(fbmem, 0, &fb_paddr, &fb_size_out) || |
paul@0 | 199 | l4re_ds_phys(descmem, 0, &desc_paddr, &desc_size_out)) |
paul@0 | 200 | return 1; |
paul@0 | 201 | |
paul@0 | 202 | if ((fb_size_out != fb_size) || (desc_size_out != desc_size)) |
paul@0 | 203 | return 1; |
paul@0 | 204 | |
paul@0 | 205 | cpm_device = jz4740_cpm_init(cpm_virt_base); |
paul@0 | 206 | gpio_port_c = jz4740_gpio_init(gpio_virt_base + 0x200, gpio_virt_base + 0x300, 32); |
paul@0 | 207 | |
paul@0 | 208 | return 0; |
paul@0 | 209 | } |
paul@0 | 210 | |
paul@0 | 211 | static void enable(void) |
paul@0 | 212 | { |
paul@0 | 213 | if (setup_memory()) |
paul@0 | 214 | return; |
paul@0 | 215 | |
paul@0 | 216 | jz4740_lcd_set_base(panel_info, (void *) lcd_virt_base); |
paul@0 | 217 | jz4740_lcd_disable(panel_info); |
paul@0 | 218 | |
paul@0 | 219 | // Initialise the LCD controller and structures. |
paul@0 | 220 | |
paul@0 | 221 | jz4740_lcd_ctrl_init( |
paul@0 | 222 | (struct jz_fb_dma_descriptor *) desc_vaddr, |
paul@0 | 223 | (struct jz_fb_dma_descriptor *) desc_paddr, |
paul@0 | 224 | fb_vaddr, |
paul@0 | 225 | (void *) fb_paddr, |
paul@0 | 226 | panel_info); |
paul@0 | 227 | |
paul@0 | 228 | // Initialise the LCD peripheral. |
paul@0 | 229 | |
paul@0 | 230 | jz4740_lcd_hw_init(panel_info); |
paul@0 | 231 | |
paul@0 | 232 | // Initialise the clocks for the LCD controller. |
paul@0 | 233 | |
paul@0 | 234 | lcd_set_timing(panel_info); |
paul@0 | 235 | |
paul@0 | 236 | // Switch the display on using GPIO operations. |
paul@0 | 237 | |
paul@0 | 238 | lcd_display_pin_init(); |
paul@0 | 239 | lcd_display_on(); |
paul@0 | 240 | |
paul@0 | 241 | // Finally, enable the peripheral. |
paul@0 | 242 | |
paul@0 | 243 | jz4740_lcd_enable(panel_info); |
paul@0 | 244 | } |
paul@0 | 245 | |
paul@0 | 246 | |
paul@0 | 247 | |
paul@0 | 248 | int main(void) |
paul@0 | 249 | { |
paul@0 | 250 | l4_size_t *fb; |
paul@0 | 251 | l4_size_t i, mask, value, onpix, offpix; |
paul@0 | 252 | |
paul@0 | 253 | /* Configure the LCD. */ |
paul@0 | 254 | |
paul@0 | 255 | panel_info = &nanonote_panel_info; |
paul@0 | 256 | enable(); |
paul@0 | 257 | |
paul@0 | 258 | fb = (l4_size_t *) fb_vaddr; |
paul@0 | 259 | |
paul@0 | 260 | /* Try and show some values. */ |
paul@0 | 261 | |
paul@0 | 262 | onpix = 0xffaaffaa; offpix = 0x11551155; |
paul@0 | 263 | mask = 0x80000000; value = 0x80000001; |
paul@0 | 264 | |
paul@0 | 265 | i = 0; |
paul@0 | 266 | |
paul@0 | 267 | while (i < fb_size / 4) |
paul@0 | 268 | { |
paul@0 | 269 | fb[i] = value & mask ? onpix : offpix; |
paul@0 | 270 | i++; |
paul@0 | 271 | |
paul@0 | 272 | if ((i % 10) == 0) |
paul@0 | 273 | { |
paul@0 | 274 | if (mask == 1) mask = 0x80000000; |
paul@0 | 275 | else mask >>= 1; |
paul@0 | 276 | |
paul@0 | 277 | onpix = (onpix >> 8) | ((onpix & 0xff) << 24); |
paul@0 | 278 | offpix = (offpix >> 8) | ((offpix & 0xff) << 24); |
paul@0 | 279 | |
paul@0 | 280 | if (i == 3200) value = jz4740_cpm_get_lcd_pixel_divider(cpm_device); |
paul@0 | 281 | else if (i == 6400) value = jz4740_cpm_get_lcd_pixel_frequency(cpm_device); |
paul@0 | 282 | else if (i == 9600) value = (l4_size_t) fb_vaddr; |
paul@0 | 283 | else if (i == 12800) value = fb_paddr; |
paul@0 | 284 | else if (i == 16000) value = ((struct jz_fb_dma_descriptor *) desc_vaddr)[0].fsadr; |
paul@0 | 285 | else if (i == 19200) value = REG32(lcd_virt_base + 0x00); |
paul@0 | 286 | else if (i == 22400) value = REG32(lcd_virt_base + 0x04); |
paul@0 | 287 | else if (i == 25600) value = REG32(lcd_virt_base + 0x30); |
paul@0 | 288 | else if (i == 28800) value = REG32(lcd_virt_base + 0x40); |
paul@0 | 289 | } |
paul@0 | 290 | } |
paul@0 | 291 | |
paul@0 | 292 | l4_cache_clean_data((long unsigned int) fb_vaddr, (long unsigned int) fb_vaddr + fb_size); |
paul@0 | 293 | |
paul@0 | 294 | while (1); |
paul@0 | 295 | |
paul@0 | 296 | return 0; |
paul@0 | 297 | } |