|
38 | 38 | #include "hal/lcd_types.h" |
39 | 39 | #include "esp_lcd_mipi_dsi.h" |
40 | 40 | #include "esp_ldo_regulator.h" |
| 41 | + #include "hal/cache_hal.h" |
| 42 | + #include "hal/cache_ll.h" |
| 43 | + |
| 44 | + #if SOC_PPA_SUPPORTED |
| 45 | + #include "driver/ppa.h" |
| 46 | + #endif |
41 | 47 |
|
42 | 48 |
|
43 | 49 | // Prefix of ESP-IDF's private esp_lcd_dpi_panel_t (esp_lcd/dsi/esp_lcd_panel_dpi.c), |
|
71 | 77 | } |
72 | 78 |
|
73 | 79 |
|
| 80 | + // frame buffers (and the PPA's output) have to be cache-line aligned |
| 81 | + static uint32_t dsi_cache_line_size(void) |
| 82 | + { |
| 83 | + uint32_t line = cache_hal_get_cache_line_size(CACHE_LL_LEVEL_EXT_MEM, CACHE_TYPE_DATA); |
| 84 | + return line ? line : 64; |
| 85 | + } |
| 86 | + |
| 87 | + |
74 | 88 | // Runs from the DPI panel's end-of-frame interrupt: the DMA has just |
75 | 89 | // restarted its scan-out from fbs[cur_fb_index]. Once that is the buffer |
76 | 90 | // LVGL last flushed, the other buffer is free to be drawn into. |
|
107 | 121 | ARG_vsync_pulse_width, |
108 | 122 | ARG_dpi_clock_freq, |
109 | 123 | ARG_phy_ldo_channel, |
110 | | - ARG_phy_ldo_voltage_mv |
| 124 | + ARG_phy_ldo_voltage_mv, |
| 125 | + ARG_rotation |
111 | 126 | }; |
112 | 127 |
|
113 | 128 | const mp_arg_t make_new_args[] = { |
|
123 | 138 | { MP_QSTR_vsync_pulse_width, MP_ARG_INT | MP_ARG_KW_ONLY, { .u_int = 1 } }, |
124 | 139 | { MP_QSTR_dpi_clock_freq, MP_ARG_INT | MP_ARG_KW_ONLY, { .u_int = 0 } }, // DPI pixel clock, MHz; 0 = same number as freq |
125 | 140 | { MP_QSTR_phy_ldo_channel, MP_ARG_INT | MP_ARG_KW_ONLY, { .u_int = -1 } }, // internal LDO channel powering the DSI PHY (ESP32-P4 boards: 3), -1 = not managed here |
126 | | - { MP_QSTR_phy_ldo_voltage_mv, MP_ARG_INT | MP_ARG_KW_ONLY, { .u_int = 2500 } } |
| 141 | + { MP_QSTR_phy_ldo_voltage_mv, MP_ARG_INT | MP_ARG_KW_ONLY, { .u_int = 2500 } }, |
| 142 | + { MP_QSTR_rotation, MP_ARG_INT | MP_ARG_KW_ONLY, { .u_int = 0 } } // 0/90/180/270 degrees (LVGL's sense); the PPA rotates every frame into the panel |
127 | 143 | }; |
128 | 144 |
|
129 | 145 | mp_arg_val_t args[MP_ARRAY_SIZE(make_new_args)]; |
|
166 | 182 | self->phy_ldo_channel = (int)args[ARG_phy_ldo_channel].u_int; |
167 | 183 | self->phy_ldo_voltage_mv = (int)args[ARG_phy_ldo_voltage_mv].u_int; |
168 | 184 |
|
| 185 | + self->rotation = (int)args[ARG_rotation].u_int; |
| 186 | + if (self->rotation != 0 && self->rotation != 90 && self->rotation != 180 && self->rotation != 270) { |
| 187 | + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("rotation must be 0, 90, 180 or 270 (%d)"), self->rotation); |
| 188 | + return mp_const_none; |
| 189 | + } |
| 190 | + #if !SOC_PPA_SUPPORTED |
| 191 | + if (self->rotation != 0) { |
| 192 | + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("rotation needs a PPA (pixel processing accelerator), this chip has none")); |
| 193 | + return mp_const_none; |
| 194 | + } |
| 195 | + #endif |
| 196 | + |
169 | 197 | LCD_DEBUG_PRINT("bus_id=%d\n", self->bus_config.bus_id) |
170 | 198 | LCD_DEBUG_PRINT("num_data_lanes=%d\n", self->bus_config.num_data_lanes) |
171 | 199 | LCD_DEBUG_PRINT("lane_bit_rate_mbps=%d\n",self->bus_config.lane_bit_rate_mbps) |
|
178 | 206 | LCD_DEBUG_PRINT("vsync_back_porch=%d\n", self->panel_config.video_timing.vsync_back_porch) |
179 | 207 | LCD_DEBUG_PRINT("vsync_pulse_width=%d\n", self->panel_config.video_timing.vsync_pulse_width) |
180 | 208 | LCD_DEBUG_PRINT("phy_ldo_channel=%d\n", self->phy_ldo_channel) |
| 209 | + LCD_DEBUG_PRINT("rotation=%d\n", self->rotation) |
181 | 210 |
|
182 | 211 | self->panel_io_handle.get_lane_count = &dsi_get_lane_count; |
183 | 212 | self->panel_io_handle.del = &dsi_del; |
|
234 | 263 |
|
235 | 264 | // LVGL renders in the frame buffers' own byte order; nothing here to swap. |
236 | 265 | self->rgb565_byte_swap = false; |
| 266 | + self->bpp = bpp; |
| 267 | + |
| 268 | + if (self->rotation == 90 || self->rotation == 270) { |
| 269 | + self->lvgl_width = (uint32_t)height; |
| 270 | + self->lvgl_height = (uint32_t)width; |
| 271 | + } else { |
| 272 | + self->lvgl_width = (uint32_t)width; |
| 273 | + self->lvgl_height = (uint32_t)height; |
| 274 | + } |
| 275 | + |
| 276 | + if (self->rotation != 0) { |
| 277 | + if (bpp == 18) { |
| 278 | + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("rotation works with 16 and 24 bits per pixel, not 18")); |
| 279 | + return LCD_ERR_INVALID_ARG; |
| 280 | + } |
| 281 | + // the rotated frame goes into the panel's back buffer while the |
| 282 | + // front one is scanned out, whatever LVGL's own buffering is |
| 283 | + self->panel_config.num_fbs = 2; |
| 284 | + } |
237 | 285 |
|
238 | 286 | self->panel_config.video_timing.h_size = (uint32_t)width; |
239 | 287 | self->panel_config.video_timing.v_size = (uint32_t)height; |
|
302 | 350 | return ret; |
303 | 351 | } |
304 | 352 |
|
305 | | - // Point the buffers handed out by allocate_framebuffer() at the DPI |
306 | | - // panel's frame buffers and drop the interim allocations. |
307 | 353 | dpi_panel_t *dpi_panel = __containerof((esp_lcd_panel_t *)self->panel_handle, dpi_panel_t, base); |
| 354 | + self->panel_fbs[0] = dpi_panel->fbs[0]; |
| 355 | + self->panel_fbs[1] = self->panel_config.num_fbs == 2 ? dpi_panel->fbs[1] : NULL; |
| 356 | + |
| 357 | + if (self->rotation == 0) { |
| 358 | + // Point the buffers handed out by allocate_framebuffer() at the DPI |
| 359 | + // panel's frame buffers and drop the interim allocations: LVGL |
| 360 | + // renders straight into what the panel scans out. |
| 361 | + heap_caps_free(self->view1->items); |
| 362 | + self->view1->items = (void *)self->panel_fbs[0]; |
| 363 | + self->view1->len = self->buffer_size; |
| 364 | + |
| 365 | + if (self->view2 != NULL) { |
| 366 | + heap_caps_free(self->view2->items); |
| 367 | + self->view2->items = (void *)self->panel_fbs[1]; |
| 368 | + self->view2->len = self->buffer_size; |
| 369 | + } |
| 370 | + } else { |
| 371 | + #if SOC_PPA_SUPPORTED |
| 372 | + // LVGL keeps its own (logical, rotated) buffers; each finished |
| 373 | + // update is rotated into the panel's back buffer by the PPA. |
| 374 | + ppa_client_config_t ppa_cfg = { |
| 375 | + .oper_type = PPA_OPERATION_SRM, |
| 376 | + .max_pending_trans_num = 1, |
| 377 | + }; |
| 378 | + ret = ppa_register_client(&ppa_cfg, &self->ppa_client); |
308 | 379 |
|
309 | | - heap_caps_free(self->view1->items); |
310 | | - self->view1->items = (void *)dpi_panel->fbs[0]; |
311 | | - self->view1->len = self->buffer_size; |
| 380 | + if (ret != 0) { |
| 381 | + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("%d(ppa_register_client)"), ret); |
| 382 | + return ret; |
| 383 | + } |
312 | 384 |
|
313 | | - if (self->view2 != NULL) { |
314 | | - heap_caps_free(self->view2->items); |
315 | | - self->view2->items = (void *)dpi_panel->fbs[1]; |
316 | | - self->view2->len = self->buffer_size; |
| 385 | + uint32_t line = dsi_cache_line_size(); |
| 386 | + self->fb_size_aligned = (full_frame_size + line - 1) / line * line; |
| 387 | + self->back_fb_index = 1; |
| 388 | + #endif |
317 | 389 | } |
318 | 390 |
|
319 | | - LCD_DEBUG_PRINT("fb1=%p fb2=%p\n", self->view1->items, self->view2 != NULL ? self->view2->items : NULL) |
| 391 | + LCD_DEBUG_PRINT("fb1=%p fb2=%p rotation=%d\n", self->view1->items, self->view2 != NULL ? self->view2->items : NULL, self->rotation) |
320 | 392 |
|
321 | 393 | return LCD_OK; |
322 | 394 | } |
|
337 | 409 | } |
338 | 410 | self->panel_handle = NULL; |
339 | 411 | self->panel_started = false; |
| 412 | + self->panel_fbs[0] = NULL; |
| 413 | + self->panel_fbs[1] = NULL; |
340 | 414 |
|
341 | | - // the frame buffers went with the panel |
| 415 | + // unrotated: the frame buffers went with the panel; rotated: they are ours |
342 | 416 | if (self->view1 != NULL) { |
| 417 | + if (self->rotation != 0) { |
| 418 | + heap_caps_free(self->view1->items); |
| 419 | + } |
343 | 420 | self->view1->items = NULL; |
344 | 421 | self->view1->len = 0; |
345 | 422 | self->view1 = NULL; |
346 | 423 | } |
347 | 424 | if (self->view2 != NULL) { |
| 425 | + if (self->rotation != 0) { |
| 426 | + heap_caps_free(self->view2->items); |
| 427 | + } |
348 | 428 | self->view2->items = NULL; |
349 | 429 | self->view2->len = 0; |
350 | 430 | self->view2 = NULL; |
351 | 431 | } |
352 | 432 | } |
353 | 433 |
|
| 434 | + #if SOC_PPA_SUPPORTED |
| 435 | + if (self->ppa_client != NULL) { |
| 436 | + ppa_unregister_client(self->ppa_client); |
| 437 | + self->ppa_client = NULL; |
| 438 | + } |
| 439 | + #endif |
| 440 | + |
354 | 441 | if (self->panel_io_handle.panel_io != NULL) { |
355 | 442 | ret = esp_lcd_panel_io_del(self->panel_io_handle.panel_io); |
356 | 443 | if (ret != 0) { |
|
450 | 537 | // A real buffer for now, so the memoryview is always valid; once the |
451 | 538 | // bus is initialized it is repointed at the frame buffer the DPI panel |
452 | 539 | // driver allocated itself and this one is freed again (dsi_init). |
453 | | - void *buf = heap_caps_calloc(1, size, caps); |
| 540 | + void *buf = heap_caps_aligned_calloc(dsi_cache_line_size(), 1, size, caps); |
454 | 541 |
|
455 | 542 | if (buf == NULL) { |
456 | 543 | mp_raise_msg_varg(&mp_type_MemoryError, MP_ERROR_TEXT("Not enough memory available (%lu)"), size); |
|
502 | 589 | self->panel_started = true; |
503 | 590 | } |
504 | 591 |
|
| 592 | + #if SOC_PPA_SUPPORTED |
| 593 | + if (self->rotation != 0) { |
| 594 | + if (!last_update) { |
| 595 | + // more areas of this update follow; LVGL's buffer is complete |
| 596 | + // only after the last one, nothing to rotate yet |
| 597 | + self->trans_done = true; |
| 598 | + |
| 599 | + if (self->callback != mp_const_none && mp_obj_is_callable(self->callback)) { |
| 600 | + mp_call_function_n_kw(self->callback, 0, 0, NULL); |
| 601 | + } |
| 602 | + return LCD_OK; |
| 603 | + } |
| 604 | + |
| 605 | + // LVGL's buffer (the one this area lives in) holds the whole |
| 606 | + // logical picture: rotate all of it into the panel's back buffer. |
| 607 | + const uint8_t *base = NULL; |
| 608 | + if (self->view1 != NULL && dsi_buf_in_fb(self, (const uint8_t *)self->view1->items, color)) { |
| 609 | + base = (const uint8_t *)self->view1->items; |
| 610 | + } else if (self->view2 != NULL && dsi_buf_in_fb(self, (const uint8_t *)self->view2->items, color)) { |
| 611 | + base = (const uint8_t *)self->view2->items; |
| 612 | + } else { |
| 613 | + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("flushed buffer is not a DSIBus frame buffer")); |
| 614 | + return LCD_ERR_INVALID_ARG; |
| 615 | + } |
| 616 | + |
| 617 | + uint8_t *dst = self->panel_fbs[self->back_fb_index]; |
| 618 | + ppa_srm_color_mode_t cm = self->bpp == 16 ? PPA_SRM_COLOR_MODE_RGB565 : PPA_SRM_COLOR_MODE_RGB888; |
| 619 | + |
| 620 | + ppa_srm_oper_config_t op = { 0 }; |
| 621 | + op.in.buffer = base; |
| 622 | + op.in.pic_w = self->lvgl_width; |
| 623 | + op.in.pic_h = self->lvgl_height; |
| 624 | + op.in.block_w = self->lvgl_width; |
| 625 | + op.in.block_h = self->lvgl_height; |
| 626 | + op.in.block_offset_x = 0; |
| 627 | + op.in.block_offset_y = 0; |
| 628 | + op.in.srm_cm = cm; |
| 629 | + op.out.buffer = dst; |
| 630 | + op.out.buffer_size = self->fb_size_aligned; |
| 631 | + op.out.pic_w = self->panel_config.video_timing.h_size; |
| 632 | + op.out.pic_h = self->panel_config.video_timing.v_size; |
| 633 | + op.out.block_offset_x = 0; |
| 634 | + op.out.block_offset_y = 0; |
| 635 | + op.out.srm_cm = cm; |
| 636 | + // LVGL's rotation and the PPA's are both counter-clockwise |
| 637 | + op.rotation_angle = self->rotation == 90 ? PPA_SRM_ROTATION_ANGLE_90 : |
| 638 | + self->rotation == 180 ? PPA_SRM_ROTATION_ANGLE_180 : |
| 639 | + PPA_SRM_ROTATION_ANGLE_270; |
| 640 | + op.scale_x = 1.0f; |
| 641 | + op.scale_y = 1.0f; |
| 642 | + op.mode = PPA_TRANS_MODE_BLOCKING; |
| 643 | + |
| 644 | + ret = ppa_do_scale_rotate_mirror(self->ppa_client, &op); |
| 645 | + |
| 646 | + if (ret != 0) { |
| 647 | + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("%d(ppa_do_scale_rotate_mirror)"), ret); |
| 648 | + return ret; |
| 649 | + } |
| 650 | + |
| 651 | + self->trans_done = false; |
| 652 | + self->transmitting_buf = NULL; |
| 653 | + |
| 654 | + // whole-buffer "draw": cache write-back plus the swap to this buffer |
| 655 | + // at the next frame boundary; the refresh-done interrupt reports it |
| 656 | + ret = esp_lcd_panel_draw_bitmap( |
| 657 | + self->panel_handle, |
| 658 | + 0, |
| 659 | + 0, |
| 660 | + (int)self->panel_config.video_timing.h_size, |
| 661 | + (int)self->panel_config.video_timing.v_size, |
| 662 | + dst |
| 663 | + ); |
| 664 | + |
| 665 | + if (ret != 0) { |
| 666 | + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("%d(esp_lcd_panel_draw_bitmap)"), ret); |
| 667 | + return ret; |
| 668 | + } |
| 669 | + |
| 670 | + self->transmitting_buf = dst; |
| 671 | + self->back_fb_index ^= 1; |
| 672 | + return LCD_OK; |
| 673 | + } |
| 674 | + #endif |
| 675 | + |
505 | 676 | self->trans_done = false; |
506 | 677 | self->transmitting_buf = NULL; |
507 | 678 |
|
|
0 commit comments