|
| 1 | +/* |
| 2 | + * This file is part of the MicroPython project, http://micropython.org/ |
| 3 | + * |
| 4 | + * The MIT License (MIT) |
| 5 | + * |
| 6 | + * Copyright (c) 2023 Jeff Epler for Adafruit Industries |
| 7 | + * |
| 8 | + * Permission is hereby granted, free of charge, to any person obtaining a copy |
| 9 | + * of this software and associated documentation files (the "Software"), to deal |
| 10 | + * in the Software without restriction, including without limitation the rights |
| 11 | + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| 12 | + * copies of the Software, and to permit persons to whom the Software is |
| 13 | + * furnished to do so, subject to the following conditions: |
| 14 | + * |
| 15 | + * The above copyright notice and this permission notice shall be included in |
| 16 | + * all copies or substantial portions of the Software. |
| 17 | + * |
| 18 | + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 19 | + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 20 | + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| 21 | + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 22 | + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 23 | + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
| 24 | + * THE SOFTWARE. |
| 25 | + */ |
| 26 | + |
| 27 | +#include <stdint.h> |
| 28 | + |
| 29 | +#include "esp_intr_alloc.h" |
| 30 | +#include "esp_lcd_panel_interface.h" |
| 31 | +#include "esp_lcd_panel_rgb.h" |
| 32 | +#include "esp_pm.h" |
| 33 | +#include "esp_private/gdma.h" |
| 34 | +#include "hal/dma_types.h" |
| 35 | +#include "hal/lcd_hal.h" |
| 36 | +#include "hal/lcd_ll.h" |
| 37 | +#include "soc/lcd_periph.h" |
| 38 | + |
| 39 | +// extract from esp-idf esp_lcd_rgb_panel.c |
| 40 | +typedef struct |
| 41 | +{ |
| 42 | + esp_lcd_panel_t base; // Base class of generic lcd panel |
| 43 | + int panel_id; // LCD panel ID |
| 44 | + lcd_hal_context_t hal; // Hal layer object |
| 45 | + size_t data_width; // Number of data lines (e.g. for RGB565, the data width is 16) |
| 46 | + size_t sram_trans_align; // Alignment for framebuffer that allocated in SRAM |
| 47 | + size_t psram_trans_align; // Alignment for framebuffer that allocated in PSRAM |
| 48 | + int disp_gpio_num; // Display control GPIO, which is used to perform action like "disp_off" |
| 49 | + intr_handle_t intr; // LCD peripheral interrupt handle |
| 50 | + esp_pm_lock_handle_t pm_lock; // Power management lock |
| 51 | + size_t num_dma_nodes; // Number of DMA descriptors that used to carry the frame buffer |
| 52 | + uint8_t *fb; // Frame buffer |
| 53 | + size_t fb_size; // Size of frame buffer |
| 54 | + int data_gpio_nums[SOC_LCD_RGB_DATA_WIDTH]; // GPIOs used for data lines, we keep these GPIOs for action like "invert_color" |
| 55 | + size_t resolution_hz; // Peripheral clock resolution |
| 56 | + esp_lcd_rgb_timing_t timings; // RGB timing parameters (e.g. pclk, sync pulse, porch width) |
| 57 | + gdma_channel_handle_t dma_chan; // DMA channel handle |
| 58 | + esp_lcd_rgb_panel_frame_trans_done_cb_t on_frame_trans_done; // Callback, invoked after frame trans done |
| 59 | + void *user_ctx; // Reserved user's data of callback functions |
| 60 | + int x_gap; // Extra gap in x coordinate, it's used when calculate the flush window |
| 61 | + int y_gap; // Extra gap in y coordinate, it's used when calculate the flush window |
| 62 | + struct |
| 63 | + { |
| 64 | + unsigned int disp_en_level : 1; // The level which can turn on the screen by `disp_gpio_num` |
| 65 | + unsigned int stream_mode : 1; // If set, the LCD transfers data continuously, otherwise, it stops refreshing the LCD when transaction done |
| 66 | + unsigned int fb_in_psram : 1; // Whether the frame buffer is in PSRAM |
| 67 | + } flags; |
| 68 | + dma_descriptor_t dma_nodes[]; // DMA descriptor pool of size `num_dma_nodes` |
| 69 | +} esp_rgb_panel_t; |
| 70 | + |
| 71 | + |
| 72 | +#include "esp_log.h" |
| 73 | +#define TAG "LCD" |
| 74 | + |
| 75 | +#include "components/esp_rom/include/esp_rom_sys.h" |
| 76 | + |
| 77 | + |
| 78 | +#include "py/objarray.h" |
| 79 | +#include "shared-bindings/dotclockframebuffer/DotClockFramebuffer.h" |
| 80 | +#include "common-hal/dotclockframebuffer/DotClockFramebuffer.h" |
| 81 | +#include "bindings/espidf/__init__.h" |
| 82 | +#include "shared-bindings/microcontroller/Pin.h" |
| 83 | +#include "py/runtime.h" |
| 84 | +#include "components/driver/include/driver/gpio.h" |
| 85 | +#include "components/driver/include/driver/periph_ctrl.h" |
| 86 | +#include "components/driver/include/esp_private/gdma.h" |
| 87 | +#include "components/esp_rom/include/esp_rom_gpio.h" |
| 88 | +#include "components/hal/esp32s3/include/hal/lcd_ll.h" |
| 89 | +#include "components/hal/include/hal/gpio_hal.h" |
| 90 | +#include "components/soc/esp32s3/include/soc/lcd_cam_struct.h" |
| 91 | +#include "esp_heap_caps.h" |
| 92 | + |
| 93 | +// should be from rom/cache.h but it wasn't working |
| 94 | +int Cache_WriteBack_Addr(uint32_t addr, uint32_t size); |
| 95 | + |
| 96 | +#define LCD_RGB_ISR_IRAM_SAFE (1) |
| 97 | +#define LCD_RGB_INTR_ALLOC_FLAGS (ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_INTRDISABLED) |
| 98 | + |
| 99 | +#define common_hal_mcu_pin_number_maybe(x) ((x) ? common_hal_mcu_pin_number((x)) : -1) |
| 100 | + |
| 101 | +static void claim_and_record(const mcu_pin_obj_t *pin, uint64_t *used_pins_mask) { |
| 102 | + if (pin) { |
| 103 | + int number = common_hal_mcu_pin_number(pin); |
| 104 | + *used_pins_mask |= (UINT64_C(1) << number); |
| 105 | + claim_pin_number(number); |
| 106 | + never_reset_pin_number(number); |
| 107 | + } |
| 108 | +} |
| 109 | + |
| 110 | +static int valid_pin(const mcu_pin_obj_t *pin, qstr name) { |
| 111 | + int result = common_hal_mcu_pin_number(pin); |
| 112 | + if (result == NO_PIN) { |
| 113 | + mp_raise_ValueError_varg(translate("Invalid %q pin"), name); |
| 114 | + } |
| 115 | + return result; |
| 116 | +} |
| 117 | + |
| 118 | +void common_hal_dotclockframebuffer_framebuffer_construct(dotclockframebuffer_framebuffer_obj_t *self, |
| 119 | + const mcu_pin_obj_t *de, |
| 120 | + const mcu_pin_obj_t *vsync, |
| 121 | + const mcu_pin_obj_t *hsync, |
| 122 | + const mcu_pin_obj_t *dclk, |
| 123 | + const mcu_pin_obj_t **red, uint8_t num_red, |
| 124 | + const mcu_pin_obj_t **green, uint8_t num_green, |
| 125 | + const mcu_pin_obj_t **blue, uint8_t num_blue, |
| 126 | + int frequency, int width, int height, |
| 127 | + int hsync_pulse_width, int hsync_back_porch, int hsync_front_porch, bool hsync_idle_low, |
| 128 | + int vsync_pulse_width, int vsync_back_porch, int vsync_front_porch, bool vsync_idle_low, |
| 129 | + bool de_idle_high, bool pclk_active_high, bool pclk_idle_high) { |
| 130 | + |
| 131 | + if (num_red != 5 || num_green != 6 || num_blue != 5) { |
| 132 | + mp_raise_ValueError(translate("Must provide 5/6/5 RGB pins")); |
| 133 | + } |
| 134 | + |
| 135 | + claim_and_record(de, &self->used_pins_mask); |
| 136 | + claim_and_record(vsync, &self->used_pins_mask); |
| 137 | + claim_and_record(hsync, &self->used_pins_mask); |
| 138 | + claim_and_record(dclk, &self->used_pins_mask); |
| 139 | + |
| 140 | + for (size_t i = 0; i < num_red; i++) { |
| 141 | + claim_and_record(red[i], &self->used_pins_mask); |
| 142 | + } |
| 143 | + for (size_t i = 0; i < num_green; i++) { |
| 144 | + claim_and_record(green[i], &self->used_pins_mask); |
| 145 | + } |
| 146 | + for (size_t i = 0; i < num_blue; i++) { |
| 147 | + claim_and_record(blue[i], &self->used_pins_mask); |
| 148 | + } |
| 149 | + |
| 150 | + esp_lcd_rgb_panel_config_t *cfg = &self->panel_config; |
| 151 | + cfg->timings.pclk_hz = frequency; |
| 152 | + cfg->timings.h_res = width; |
| 153 | + cfg->timings.v_res = height; |
| 154 | + cfg->timings.hsync_pulse_width = hsync_pulse_width; |
| 155 | + cfg->timings.hsync_back_porch = hsync_back_porch; |
| 156 | + cfg->timings.hsync_front_porch = hsync_front_porch; |
| 157 | + cfg->timings.vsync_pulse_width = vsync_pulse_width; |
| 158 | + cfg->timings.vsync_back_porch = vsync_back_porch; |
| 159 | + cfg->timings.vsync_front_porch = vsync_front_porch; |
| 160 | + cfg->timings.flags.hsync_idle_low = hsync_idle_low; |
| 161 | + cfg->timings.flags.vsync_idle_low = hsync_idle_low; |
| 162 | + cfg->timings.flags.de_idle_high = de_idle_high; |
| 163 | + cfg->timings.flags.pclk_active_neg = !pclk_active_high; |
| 164 | + cfg->timings.flags.pclk_idle_high = pclk_idle_high; |
| 165 | + |
| 166 | + cfg->data_width = 16; |
| 167 | + cfg->sram_trans_align = 8; |
| 168 | + cfg->psram_trans_align = 64; |
| 169 | + cfg->hsync_gpio_num = valid_pin(hsync, MP_QSTR_hsync); |
| 170 | + cfg->vsync_gpio_num = valid_pin(vsync, MP_QSTR_vsync); |
| 171 | + cfg->de_gpio_num = valid_pin(de, MP_QSTR_de); |
| 172 | + cfg->pclk_gpio_num = valid_pin(dclk, MP_QSTR_dclk); |
| 173 | + |
| 174 | + cfg->data_gpio_nums[0] = valid_pin(blue[0], MP_QSTR_blue); |
| 175 | + cfg->data_gpio_nums[1] = valid_pin(blue[1], MP_QSTR_blue); |
| 176 | + cfg->data_gpio_nums[2] = valid_pin(blue[2], MP_QSTR_blue); |
| 177 | + cfg->data_gpio_nums[3] = valid_pin(blue[3], MP_QSTR_blue); |
| 178 | + cfg->data_gpio_nums[4] = valid_pin(blue[4], MP_QSTR_blue); |
| 179 | + |
| 180 | + cfg->data_gpio_nums[5] = valid_pin(green[0], MP_QSTR_green); |
| 181 | + cfg->data_gpio_nums[6] = valid_pin(green[1], MP_QSTR_green); |
| 182 | + cfg->data_gpio_nums[7] = valid_pin(green[2], MP_QSTR_green); |
| 183 | + cfg->data_gpio_nums[8] = valid_pin(green[3], MP_QSTR_green); |
| 184 | + cfg->data_gpio_nums[9] = valid_pin(green[4], MP_QSTR_green); |
| 185 | + cfg->data_gpio_nums[10] = valid_pin(green[5], MP_QSTR_green); |
| 186 | + |
| 187 | + cfg->data_gpio_nums[11] = valid_pin(red[0], MP_QSTR_red); |
| 188 | + cfg->data_gpio_nums[12] = valid_pin(red[1], MP_QSTR_red); |
| 189 | + cfg->data_gpio_nums[13] = valid_pin(red[2], MP_QSTR_red); |
| 190 | + cfg->data_gpio_nums[14] = valid_pin(red[3], MP_QSTR_red); |
| 191 | + cfg->data_gpio_nums[15] = valid_pin(red[4], MP_QSTR_red); |
| 192 | + |
| 193 | + cfg->disp_gpio_num = GPIO_NUM_NC; |
| 194 | + |
| 195 | + cfg->flags.disp_active_low = 0; |
| 196 | + cfg->flags.relax_on_idle = 0; |
| 197 | + cfg->flags.fb_in_psram = 1; // allocate frame buffer in PSRAM |
| 198 | + |
| 199 | + ESP_ERROR_CHECK(esp_lcd_new_rgb_panel(&self->panel_config, &self->panel_handle)); |
| 200 | + ESP_ERROR_CHECK(esp_lcd_panel_reset(self->panel_handle)); |
| 201 | + ESP_ERROR_CHECK(esp_lcd_panel_init(self->panel_handle)); |
| 202 | + |
| 203 | + uint16_t color = 0; |
| 204 | + ESP_ERROR_CHECK(self->panel_handle->draw_bitmap(self->panel_handle, 0, 0, 1, 1, &color)); |
| 205 | + |
| 206 | + esp_rgb_panel_t *_rgb_panel = __containerof(self->panel_handle, esp_rgb_panel_t, base); |
| 207 | + |
| 208 | + self->frequency = frequency; |
| 209 | + self->refresh_rate = frequency / (width + hsync_front_porch + hsync_back_porch) / (height + vsync_front_porch + vsync_back_porch); |
| 210 | + self->bufinfo.buf = _rgb_panel->fb; |
| 211 | + self->bufinfo.len = 2 * width * height; |
| 212 | + self->bufinfo.typecode = 'H' | MP_OBJ_ARRAY_TYPECODE_FLAG_RW; |
| 213 | + |
| 214 | + memset(self->bufinfo.buf, 0xaa, width * height); |
| 215 | + memset(self->bufinfo.buf + width * height, 0x55, width * height); |
| 216 | + |
| 217 | +// LCD_CAM.lcd_ctrl2.lcd_vsync_idle_pol = _vsync_polarity; |
| 218 | +// LCD_CAM.lcd_ctrl2.lcd_hsync_idle_pol = _hsync_polarity; |
| 219 | + |
| 220 | +} |
| 221 | + |
| 222 | + |
| 223 | +void common_hal_dotclockframebuffer_framebuffer_deinit(dotclockframebuffer_framebuffer_obj_t *self) { |
| 224 | + if (common_hal_dotclockframebuffer_framebuffer_deinitialized(self)) { |
| 225 | + return; |
| 226 | + } |
| 227 | + |
| 228 | + reset_pin_mask(self->used_pins_mask); |
| 229 | + self->used_pins_mask = 0; |
| 230 | + esp_lcd_panel_del(self->panel_handle); |
| 231 | +} |
| 232 | + |
| 233 | +bool common_hal_dotclockframebuffer_framebuffer_deinitialized(dotclockframebuffer_framebuffer_obj_t *self) { |
| 234 | + return self->used_pins_mask == 0; |
| 235 | +} |
| 236 | + |
| 237 | + |
| 238 | +mp_int_t common_hal_dotclockframebuffer_framebuffer_get_width(dotclockframebuffer_framebuffer_obj_t *self) { |
| 239 | + return self->panel_config.timings.h_res; |
| 240 | +} |
| 241 | + |
| 242 | +mp_int_t common_hal_dotclockframebuffer_framebuffer_get_height(dotclockframebuffer_framebuffer_obj_t *self) { |
| 243 | + return self->panel_config.timings.v_res; |
| 244 | +} |
| 245 | + |
| 246 | +mp_int_t common_hal_dotclockframebuffer_framebuffer_get_frequency(dotclockframebuffer_framebuffer_obj_t *self) { |
| 247 | + return self->frequency; |
| 248 | +} |
| 249 | + |
| 250 | +void common_hal_dotclockframebuffer_framebuffer_refresh(dotclockframebuffer_framebuffer_obj_t *self) { |
| 251 | + Cache_WriteBack_Addr((uint32_t)(self->bufinfo.buf), self->bufinfo.len); |
| 252 | +} |
| 253 | + |
| 254 | +mp_int_t common_hal_dotclockframebuffer_framebuffer_get_refresh_rate(dotclockframebuffer_framebuffer_obj_t *self) { |
| 255 | + return self->refresh_rate; |
| 256 | +} |
0 commit comments