1 /* 2 * Access the HDMI I2C peripheral on the MIPS Creator CI20 board. 3 * 4 * Copyright (C) 2020, 2021, 2023, 2024 Paul Boddie <paul@boddie.org.uk> 5 * 6 * This program is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU General Public License as 8 * published by the Free Software Foundation; either version 2 of 9 * the License, or (at your option) any later version. 10 * 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with this program; if not, write to the Free Software 18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, 19 * Boston, MA 02110-1301, USA 20 */ 21 22 #include <l4/devices/cpm-jz4780.h> 23 #include <l4/devices/gpio-jz4780.h> 24 #include <l4/devices/hdmi-jz4780.h> 25 26 #include <l4/devices/lcd-jz4780.h> 27 #include <l4/devices/lcd-jz4740-config.h> 28 #include <l4/devices/lcd-jz4740-panel.h> 29 30 #include <l4/devices/memory.h> 31 32 #include <l4/re/c/dataspace.h> 33 #include <l4/re/c/dma_space.h> 34 #include <l4/re/c/mem_alloc.h> 35 #include <l4/re/c/rm.h> 36 #include <l4/re/c/util/cap_alloc.h> 37 #include <l4/re/env.h> 38 #include <l4/re/protocols.h> 39 40 #include <l4/sys/cache.h> 41 #include <l4/sys/err.h> 42 #include <l4/sys/factory.h> 43 #include <l4/sys/icu.h> 44 #include <l4/sys/ipc.h> 45 #include <l4/sys/irq.h> 46 #include <l4/sys/rcv_endpoint.h> 47 48 #include <l4/io/io.h> 49 #include <l4/libedid/edid.h> 50 #include <l4/vbus/vbus.h> 51 52 #include <stdio.h> 53 #include <unistd.h> 54 #include <stdint.h> 55 #include <string.h> 56 #include <stdlib.h> 57 58 59 60 enum { 61 DDCSCL = 24, /* via PORTF */ 62 DDCSDA = 25, /* via PORTF */ 63 }; 64 65 66 67 /* Test panel. */ 68 69 static struct Jz4740_lcd_panel panel = { 70 .config = ( 71 Jz4740_lcd_mode_tft_generic 72 | Jz4740_lcd_bpp_24 73 | Jz4740_lcd_desc_8_word 74 | Jz4740_lcd_underrun_recover 75 | Jz4740_lcd_ps_disabled 76 | Jz4740_lcd_cls_disabled 77 | Jz4740_lcd_spl_disabled 78 | Jz4740_lcd_rev_disabled 79 | Jz4740_lcd_pclock_negative 80 | Jz4740_lcd_hsync_positive 81 | Jz4740_lcd_vsync_positive 82 | Jz4740_lcd_de_positive), 83 84 .width = 1280, 85 .height = 1024, 86 .bpp = 24, 87 .frame_rate = 60, 88 .hsync = 112, 89 .vsync = 3, 90 .line_start = 248, // back porch (blanking - hsync - offset) 91 .line_end = 48, // front porch (sync offset) 92 .frame_start = 36, // back porch (blanking - vsync - offset) 93 .frame_end = 3, // front porch (sync offset) 94 }; 95 96 97 98 /* Device and resource discovery. */ 99 100 static long item_in_range(long start, long end, long index) 101 { 102 if (start < end) 103 return start + index; 104 else 105 return start - index; 106 } 107 108 static void show_timings(uint8_t *buf) 109 { 110 unsigned int width, height; 111 112 /* Attempt to decode EDID information. */ 113 114 libedid_prefered_resolution(buf, &width, &height); 115 printf("Preferred resolution: %d x %d\n", width, height); 116 117 libedid_dump_standard_timings(buf); 118 } 119 120 int main(void) 121 { 122 long err; 123 124 /* Buffer for EDID data. */ 125 126 uint8_t edid[128]; 127 unsigned int length = 0, i; 128 129 /* Version details. */ 130 131 uint8_t hdmi_major; 132 uint16_t hdmi_minor; 133 const struct Phy_capabilities *phy_def; 134 135 /* Peripheral memory. */ 136 137 l4_addr_t cpm_base = 0, cpm_base_end = 0; 138 l4_addr_t gpio_base = 0, gpio_base_end = 0; 139 l4_addr_t hdmi_base = 0, hdmi_base_end = 0; 140 l4_addr_t lcd_base = 0, lcd_base_end = 0; 141 142 /* Peripheral abstractions. */ 143 144 void *cpm; 145 void *gpio_port_f; 146 void *hdmi; 147 void *lcd; 148 149 /* Allocated memory. */ 150 151 l4_cap_idx_t desc_mem, fb_mem, dma, vbus; 152 l4_size_t desc_size, desc_psize, fb_size, fb_psize; 153 l4_addr_t desc_addr, fb_addr; 154 l4re_dma_space_dma_addr_t desc_paddr, fb_paddr; 155 unsigned char *picture; 156 unsigned char *fb_picture, *fb_picture_row; 157 unsigned int x, y; 158 159 /* Access to IRQs. */ 160 161 l4_uint32_t hdmi_irq_start = 0, hdmi_irq_end = 0; 162 l4_uint32_t lcd_irq_start = 0, lcd_irq_end = 0; 163 l4_cap_idx_t icu, hdmi_irq, lcd_irq; 164 165 /* Capability allocation. */ 166 167 desc_mem = l4re_util_cap_alloc(); 168 fb_mem = l4re_util_cap_alloc(); 169 dma = l4re_util_cap_alloc(); 170 hdmi_irq = l4re_util_cap_alloc(); 171 lcd_irq = l4re_util_cap_alloc(); 172 icu = l4re_env_get_cap("icu"); 173 vbus = l4re_env_get_cap("vbus"); 174 175 if (l4_is_invalid_cap(icu)) 176 { 177 printf("No 'icu' capability available in the virtual bus.\n"); 178 return 1; 179 } 180 181 if (l4_is_invalid_cap(desc_mem) || l4_is_invalid_cap(fb_mem) || 182 l4_is_invalid_cap(hdmi_irq) || l4_is_invalid_cap(lcd_irq)) 183 { 184 printf("Capabilities could not be reserved.\n"); 185 return 1; 186 } 187 188 /* Obtain resource details describing the interrupt for HDMI I2C. */ 189 190 printf("Access IRQ...\n"); 191 192 if (get_irq("jz4780-hdmi", &hdmi_irq_start, &hdmi_irq_end) < 0) 193 return 1; 194 195 printf("HDMI IRQ range at %d...%d.\n", hdmi_irq_start, hdmi_irq_end); 196 197 if (get_irq("jz4780-lcd", &lcd_irq_start, &lcd_irq_end) < 0) 198 return 1; 199 200 printf("LCD IRQ range at %d...%d.\n", lcd_irq_start, lcd_irq_end); 201 202 /* Create interrupt objects. */ 203 204 err = l4_error(l4_factory_create_irq(l4re_global_env->factory, hdmi_irq)) || 205 l4_error(l4_factory_create_irq(l4re_global_env->factory, lcd_irq)); 206 207 if (err) 208 { 209 printf("Could not create IRQ object: %lx\n", err); 210 return 1; 211 } 212 213 /* Bind interrupt objects to IRQ numbers. Here, the first HDMI interrupt is 214 bound, this being the general HDMI interrupt. */ 215 216 err = l4_error(l4_icu_bind(icu, 217 item_in_range(hdmi_irq_start, hdmi_irq_end, 0), 218 hdmi_irq)) || 219 l4_error(l4_icu_bind(icu, 220 item_in_range(lcd_irq_start, lcd_irq_end, 0), 221 lcd_irq)); 222 223 if (err) 224 { 225 printf("Could not bind IRQ to the ICU: %ld\n", err); 226 return 1; 227 } 228 229 /* Attach ourselves to the interrupt handler with some arbitrary labels. */ 230 231 err = l4_error(l4_rcv_ep_bind_thread(hdmi_irq, l4re_env()->main_thread, 0x00)) || 232 l4_error(l4_rcv_ep_bind_thread(lcd_irq, l4re_env()->main_thread, 0x10)); 233 234 if (err) 235 { 236 printf("Could not attach to IRQs: %ld\n", err); 237 return 1; 238 } 239 240 /* Obtain resource details describing I/O memory. */ 241 242 printf("Access CPM...\n"); 243 244 if (get_memory("jz4780-cpm", &cpm_base, &cpm_base_end) < 0) 245 return 1; 246 247 printf("CPM at 0x%lx...0x%lx.\n", cpm_base, cpm_base_end); 248 249 printf("Access GPIO...\n"); 250 251 if (get_memory("jz4780-gpio", &gpio_base, &gpio_base_end) < 0) 252 return 1; 253 254 printf("GPIO at 0x%lx...0x%lx.\n", gpio_base, gpio_base_end); 255 256 printf("Access HDMI...\n"); 257 258 if (get_memory("jz4780-hdmi", &hdmi_base, &hdmi_base_end) < 0) 259 return 1; 260 261 printf("HDMI at 0x%lx...0x%lx.\n", hdmi_base, hdmi_base_end); 262 263 printf("Access LCD...\n"); 264 265 if (get_memory("jz4780-lcd", &lcd_base, &lcd_base_end) < 0) 266 return 1; 267 268 printf("LCD at 0x%lx...0x%lx.\n", lcd_base, lcd_base_end); 269 270 /* Obtain CPM object. */ 271 272 cpm = jz4780_cpm_init(cpm_base); 273 274 printf("VPLL frequency: %lld\n", jz4780_cpm_get_frequency(cpm, Clock_pll_V)); 275 printf("HDMI frequency: %lld\n", jz4780_cpm_get_frequency(cpm, Clock_hdmi)); 276 277 jz4780_cpm_stop_clock(cpm, Clock_hdmi); 278 jz4780_cpm_set_frequency(cpm, Clock_hdmi, 27000000); 279 280 printf("HDMI frequency: %lld\n", jz4780_cpm_get_frequency(cpm, Clock_hdmi)); 281 282 jz4780_cpm_start_clock(cpm, Clock_hdmi); 283 284 /* Configure pins. */ 285 286 printf("PORTF at 0x%lx...0x%lx.\n", gpio_base + 0x500, gpio_base + 0x600); 287 288 gpio_port_f = jz4780_gpio_init(gpio_base, 5); 289 290 printf("Set up GPIO pins...\n"); 291 292 jz4780_gpio_config_pad(gpio_port_f, DDCSCL, Function_alt, 0); 293 jz4780_gpio_config_pad(gpio_port_f, DDCSDA, Function_alt, 0); 294 295 /* Obtain HDMI reference. */ 296 297 printf("Set up HDMI...\n"); 298 299 hdmi = jz4780_hdmi_init(hdmi_base, hdmi_base_end, hdmi_irq, &panel); 300 301 printf("Read version...\n"); 302 303 jz4780_hdmi_get_version(hdmi, &hdmi_major, &hdmi_minor); 304 305 printf("HDMI version is %x.%03x\n", hdmi_major, hdmi_minor); 306 307 jz4780_hdmi_get_phy_capabilities(hdmi, &phy_def); 308 309 printf("PHY type: %s\n", phy_def->name); 310 311 printf("Connected: %s\n", jz4780_hdmi_connected(hdmi) ? "yes" : "no"); 312 313 while (!jz4780_hdmi_connected(hdmi)) 314 jz4780_hdmi_wait_for_connection(hdmi); 315 316 printf("Read EDID...\n"); 317 318 jz4780_hdmi_i2c_set_address(hdmi, 0x50); 319 length = jz4780_hdmi_i2c_read(hdmi, edid, 128); 320 321 if (length) 322 { 323 for (i = 0; i < length; i++) 324 printf("%02x%c", edid[i], ((i % 16) != 15) ? ' ' : '\n'); 325 } 326 327 show_timings(edid); 328 329 /* Obtain LCD reference. */ 330 331 printf("Set up LCD...\n"); 332 333 lcd = jz4780_lcd_init(lcd_base, &panel); 334 335 /* Test initialisation with a frequency appropriate for the test panel. */ 336 337 printf("LCD source: %d\n", jz4780_cpm_get_source(cpm, Clock_lcd)); 338 printf("LCD frequency: %lld\n", jz4780_cpm_get_frequency(cpm, Clock_lcd_pixel0)); 339 printf("Desired frequency: %d\n", jz4740_lcd_get_pixel_clock(lcd)); 340 341 jz4780_cpm_stop_clock(cpm, Clock_lcd); 342 jz4780_cpm_set_frequency(cpm, Clock_lcd, jz4740_lcd_get_pixel_clock(lcd) * 3); 343 jz4780_cpm_set_frequency(cpm, Clock_lcd_pixel0, jz4740_lcd_get_pixel_clock(lcd)); 344 345 printf("LCD source: %d\n", jz4780_cpm_get_source(cpm, Clock_lcd)); 346 printf("LCD frequency: %lld\n", jz4780_cpm_get_frequency(cpm, Clock_lcd_pixel0)); 347 348 /* With the LCD pixel clock set up, bring up the HDMI. */ 349 350 printf("Enable HDMI output...\n"); 351 352 jz4780_hdmi_enable(hdmi, jz4780_cpm_get_frequency(cpm, Clock_lcd_pixel0)); 353 354 /* Create the DMA space. */ 355 356 err = l4_error(l4_factory_create(l4re_env()->mem_alloc, L4RE_PROTO_DMA_SPACE, dma)); 357 358 if (err) 359 { 360 printf("Could not create DMA space: %s\n", l4sys_errtostr(err)); 361 return 1; 362 } 363 364 l4vbus_device_handle_t device = L4VBUS_NULL; 365 l4vbus_resource_t dma_resource; 366 367 if (!find_resource(&device, &dma_resource, L4VBUS_RESOURCE_DMA_DOMAIN)) 368 { 369 printf("Could not find DMA domain.\n"); 370 return 1; 371 } 372 373 err = l4vbus_assign_dma_domain(vbus, dma_resource.start, 374 L4VBUS_DMAD_BIND | L4VBUS_DMAD_L4RE_DMA_SPACE, 375 dma); 376 377 if (err) 378 { 379 printf("Could not assign DMA space: %s\n", l4sys_errtostr(err)); 380 return 1; 381 } 382 383 /* Allocate descriptors and framebuffer at 2**8 == 256 byte == 64 word alignment. */ 384 385 desc_size = jz4740_lcd_get_descriptors_size(lcd); 386 fb_size = jz4740_lcd_get_screen_size(lcd); 387 388 err = l4re_ma_alloc_align(desc_size, desc_mem, L4RE_MA_CONTINUOUS | L4RE_MA_PINNED, 8) || 389 l4re_ma_alloc_align(fb_size, fb_mem, L4RE_MA_CONTINUOUS | L4RE_MA_PINNED, 8); 390 391 if (err) 392 { 393 printf("Could not allocate memory: %s\n", l4sys_errtostr(err)); 394 return 1; 395 } 396 397 err = l4re_rm_attach((void **) &desc_addr, desc_size, 398 L4RE_RM_F_SEARCH_ADDR | L4RE_RM_F_EAGER_MAP | L4RE_RM_F_RW, 399 desc_mem, 0, L4_PAGESHIFT) || 400 l4re_rm_attach((void **) &fb_addr, fb_size, 401 L4RE_RM_F_SEARCH_ADDR | L4RE_RM_F_EAGER_MAP | L4RE_RM_F_RW, 402 fb_mem, 0, L4_PAGESHIFT); 403 404 if (err) 405 { 406 printf("Could not map memory: %s\n", l4sys_errtostr(err)); 407 return 1; 408 } 409 410 err = l4re_dma_space_map(dma, desc_mem | L4_CAP_FPAGE_RW, 0, &desc_psize, 0, 411 L4RE_DMA_SPACE_TO_DEVICE, &desc_paddr) || 412 l4re_dma_space_map(dma, fb_mem | L4_CAP_FPAGE_RW, 0, &fb_psize, 0, 413 L4RE_DMA_SPACE_TO_DEVICE, &fb_paddr); 414 415 if (err) 416 { 417 printf("Could not get physical addresses for memory: %s\n", l4sys_errtostr(err)); 418 return 1; 419 } 420 421 printf("Descriptors at %lx/%llx, size %d/%d.\n", desc_addr, desc_paddr, desc_size, desc_psize); 422 printf("Framebuffer at %lx/%llx, size %d/%d.\n", fb_addr, fb_paddr, fb_size, fb_psize); 423 424 //memset((void *) fb_addr, 0x7f, fb_size); 425 426 picture = (unsigned char *) malloc(gimp_image.width * gimp_image.height * gimp_image.bytes_per_pixel); 427 428 GIMP_IMAGE_RUN_LENGTH_DECODE(picture, 429 gimp_image.rle_pixel_data, 430 gimp_image.width * gimp_image.height, 431 gimp_image.bytes_per_pixel); 432 433 fb_picture_row = (unsigned char *) fb_addr; 434 435 for (y = 0; y < gimp_image.height; y++) 436 { 437 fb_picture = fb_picture_row; 438 439 for (x = 0; x < gimp_image.width; x++) 440 { 441 *(fb_picture + 2) = *picture++; 442 *(fb_picture + 1) = *picture++; 443 *fb_picture = *picture++; 444 *(fb_picture + 3) = 0; 445 fb_picture += 4; 446 } 447 448 fb_picture_row += jz4740_lcd_get_line_size(lcd); 449 } 450 451 l4_cache_clean_data((unsigned long) fb_addr, (unsigned long) fb_addr + fb_size); 452 453 printf("Start LCD clock and initialise LCD...\n"); 454 455 jz4780_cpm_start_clock(cpm, Clock_lcd); 456 l4_sleep(1); // 1ms == 1000us 457 458 jz4740_lcd_disable(lcd); 459 460 jz4740_lcd_set_irq(lcd, lcd_irq, Lcd_irq_frame_end); 461 462 jz4740_lcd_config(lcd, (struct Jz4740_lcd_descriptor *) desc_addr, 463 (struct Jz4740_lcd_descriptor *) (l4_addr_t) desc_paddr, 464 fb_paddr); 465 466 jz4740_lcd_enable(lcd); 467 468 printf("LCD enabled: %s\n", jz4740_lcd_enabled(lcd) ? "yes" : "no"); 469 470 printf("Wait for interrupt conditions...\n"); 471 472 for (i = 0; i < 30000; i++) 473 { 474 if (jz4740_lcd_wait_for_irq(lcd)) 475 continue; 476 477 if (!(i % 60)) 478 printf("IRQ #%d\n", i); 479 } 480 481 /* Detach from the interrupts. */ 482 483 err = l4_error(l4_irq_detach(hdmi_irq)) || 484 l4_error(l4_irq_detach(lcd_irq)); 485 486 if (err) 487 printf("Error detaching from IRQ: %ld\n", err); 488 489 printf("Done.\n"); 490 491 return 0; 492 }