From 211389e195c6e908f0330f88a150bdc810ead6d2 Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Mon, 11 Nov 2024 20:33:36 +0000 Subject: [PATCH 1/4] drivers: mipi_dbi: add support for mipi_dbi_configure_te Many MIPI DBI displays support a "tearing effect" signal, which can be configured to signal each v-sync or h-sync interval. This signal can be used by the MIPI DBI controller to synchronize writes with the controller, and avoid tearing effects on the screen (which occur when the write pointer from the MCU overlaps with the panel's read pointer in the display controller's graphics RAM). Add the `mipi_dbi_configure_te` API, which allows display controllers to configure MIPI DBI controller to wait for a TE edge before streaming display data. Allow the tearing enable parameters to be configured via devicetree settings, since these will vary based on the MIPI DBI controller and display controller in use. Signed-off-by: Daniel DeGrasse --- dts/bindings/mipi-dbi/mipi-dbi-device.yaml | 18 +++++ include/zephyr/drivers/mipi_dbi.h | 66 +++++++++++++++++++ .../zephyr/dt-bindings/mipi_dbi/mipi_dbi.h | 34 ++++++++++ 3 files changed, 118 insertions(+) diff --git a/dts/bindings/mipi-dbi/mipi-dbi-device.yaml b/dts/bindings/mipi-dbi/mipi-dbi-device.yaml index c7e11745d837f..57e2f42770b42 100644 --- a/dts/bindings/mipi-dbi/mipi-dbi-device.yaml +++ b/dts/bindings/mipi-dbi/mipi-dbi-device.yaml @@ -26,3 +26,21 @@ properties: - "MIPI_DBI_MODE_8080_BUS_16_BIT" - "MIPI_DBI_MODE_8080_BUS_9_BIT" - "MIPI_DBI_MODE_8080_BUS_8_BIT" + + te-mode: + type: string + default: "MIPI_DBI_TE_NO_EDGE" + description: | + MIPI DBI tearing enable signal mode. Defaults to disabled. + enum: + - "MIPI_DBI_TE_NO_EDGE" + - "MIPI_DBI_TE_RISING_EDGE" + - "MIPI_DBI_TE_FALLING_EDGE" + + te-delay: + type: int + default: 0 + description: | + Delay in microseconds to wait before transmitting display data after a + tearing enable synchronization signal is seen. Defaults to 0 since most + controllers will not need a delay. diff --git a/include/zephyr/drivers/mipi_dbi.h b/include/zephyr/drivers/mipi_dbi.h index 79f26d3d71fd8..109741e3d84b4 100644 --- a/include/zephyr/drivers/mipi_dbi.h +++ b/include/zephyr/drivers/mipi_dbi.h @@ -112,6 +112,30 @@ extern "C" { #define MIPI_DBI_CONFIG_DT_INST(inst, operation_, delay_) \ MIPI_DBI_CONFIG_DT(DT_DRV_INST(inst), operation_, delay_) +/** + * @brief Get the MIPI DBI TE mode from devicetree + * + * Gets the MIPI DBI TE mode from a devicetree property. + * @param node_id Devicetree node identifier for the MIPI DBI device with the + * TE mode property + * @param edge_prop Property name for the TE mode that should be read from + * devicetree + */ +#define MIPI_DBI_TE_MODE_DT(node_id, edge_prop) \ + DT_STRING_UPPER_TOKEN(node_id, edge_prop) + +/** + * @brief Get the MIPI DBI TE mode for device instance + * + * Gets the MIPI DBI TE mode from a devicetree property. Equivalent to + * MIPI_DBI_TE_MODE_DT(DT_DRV_INST(inst), edge_mode). + * @param inst Instance of the device to get the TE mode for + * @param edge_prop Property name for the TE mode that should be read from + * devicetree + */ +#define MIPI_DBI_TE_MODE_DT_INST(inst, edge_prop) \ + DT_STRING_UPPER_TOKEN(DT_DRV_INST(inst), edge_prop) + /** * @brief MIPI DBI controller configuration * @@ -141,6 +165,9 @@ __subsystem struct mipi_dbi_driver_api { int (*reset)(const struct device *dev, k_timeout_t delay); int (*release)(const struct device *dev, const struct mipi_dbi_config *config); + int (*configure_te)(const struct device *dev, + uint8_t edge, + k_timeout_t delay); }; /** @@ -293,6 +320,45 @@ static inline int mipi_dbi_release(const struct device *dev, return api->release(dev, config); } +/** + * @brief Configures MIPI DBI tearing effect signal + * + * Many displays provide a tearing effect signal, which can be configured + * to pulse at each vsync interval or each hsync interval. This signal can be + * used by the MCU to determine when to transmit a new frame so that the + * read pointer of the display never overlaps with the write pointer from the + * MCU. This function configures the MIPI DBI controller to delay transmitting + * display frames until the selected tearing effect signal edge occurs. + * + * The delay will occur on the on each call to @ref mipi_dbi_write_display + * where the ``frame_incomplete`` flag was set within the buffer descriptor + * provided with the prior call, as this indicates the buffer being written + * in this call is the first buffer of a new frame. + * + * Note that most display controllers will need to enable the TE signal + * using vendor specific commands before the MIPI DBI controller can react + * to it. + * + * @param dev mipi dbi controller + * @param edge which edge of the TE signal to start transmitting on + * @param delay_us how many microseconds after TE edge to start transmission + * @retval -EIO I/O error + * @retval -ENOSYS not implemented + * @retval -ENOTSUP not supported + */ +static inline int mipi_dbi_configure_te(const struct device *dev, + uint8_t edge, + uint32_t delay_us) +{ + const struct mipi_dbi_driver_api *api = + (const struct mipi_dbi_driver_api *)dev->api; + + if (api->configure_te == NULL) { + return -ENOSYS; + } + return api->configure_te(dev, edge, K_USEC(delay_us)); +} + #ifdef __cplusplus } #endif diff --git a/include/zephyr/dt-bindings/mipi_dbi/mipi_dbi.h b/include/zephyr/dt-bindings/mipi_dbi/mipi_dbi.h index 25886635f5962..00de044dfb5c9 100644 --- a/include/zephyr/dt-bindings/mipi_dbi/mipi_dbi.h +++ b/include/zephyr/dt-bindings/mipi_dbi/mipi_dbi.h @@ -110,6 +110,40 @@ #define MIPI_DBI_MODE_8080_BUS_9_BIT 0x7 #define MIPI_DBI_MODE_8080_BUS_8_BIT 0x8 +/** MIPI DBI tearing enable synchronization is disabled. */ +#define MIPI_DBI_TE_NO_EDGE 0x0 + +/** + * MIPI DBI tearing enable synchronization on rising edge of TE signal. + * The controller will only send display write data on a rising edge of TE. + * This should be used when the controller can send a frame worth of data + * data to the display panel faster than the display panel can read a frame + * from its RAM + * + * .------. .------. + * TE -----' '------------------------' '------------- + * -----. .----------------------. + * CS '--------' '-------------------- + */ +#define MIPI_DBI_TE_RISING_EDGE 0x1 + +/** + * MIPI DBI tearing enable synchronization on falling edge of TE signal. + * The controller will only send display write data on a falling edge of TE. + * This should be used when the controller sends a frame worth of data + * data to the display panel slower than the display panel can read a frame + * from its RAM. TE synchronization in this mode will only work if the + * controller can complete the write before the display panel completes 2 + * read cycles, otherwise the read pointer will "catch up" with the write + * pointer. + * + * .------. .------. + * TE -----' '------------------------' '------------- + * ------------. .----- + * CS '---------------------------------------' + */ +#define MIPI_DBI_TE_FALLING_EDGE 0x2 + /** * @} */ From 4622fb44dc019da0981da636428d78969384b2c4 Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Mon, 11 Nov 2024 20:38:13 +0000 Subject: [PATCH 2/4] drivers: mipi_dbi_nxp_lcdic: add support for mipi_dbi_configure_te Add support for the mipi_dbi_configure_te API within the NXP LCDIC peripheral. Also, remove a redundant code patch in the write_command function that was previously used to determine when the display driver was writing to graphics RAM, as these writes should now be performed using the mipi_dbi_write_display API. Signed-off-by: Daniel DeGrasse --- drivers/mipi_dbi/mipi_dbi_nxp_lcdic.c | 102 ++++++++++++++++++++++---- 1 file changed, 88 insertions(+), 14 deletions(-) diff --git a/drivers/mipi_dbi/mipi_dbi_nxp_lcdic.c b/drivers/mipi_dbi/mipi_dbi_nxp_lcdic.c index 4c252a277feee..733abcba20c09 100644 --- a/drivers/mipi_dbi/mipi_dbi_nxp_lcdic.c +++ b/drivers/mipi_dbi/mipi_dbi_nxp_lcdic.c @@ -34,6 +34,12 @@ enum lcdic_cmd_type { LCDIC_TX = 1, }; +enum lcdic_cmd_te { + LCDIC_TE_NO_SYNC = 0, + LCDIC_TE_RISING_EDGE = 1, + LCDIC_TE_FALLING_EDGE = 2, +}; + /* Limit imposed by size of data length field in LCDIC command */ #define LCDIC_MAX_XFER 0x40000 /* Max reset width (in terms of Timer0_Period, see RST_CTRL register) */ @@ -102,6 +108,10 @@ struct mipi_dbi_lcdic_data { uint32_t unaligned_word __aligned(4); /* Tracks lcdic_data_fmt value we should use for pixel data */ uint8_t pixel_fmt; + /* Tracks TE edge setting we should use for pixel data */ + uint8_t te_edge; + /* Are we starting a new display frame */ + bool new_frame; const struct mipi_dbi_config *active_cfg; struct k_sem xfer_sem; struct k_sem lock; @@ -376,6 +386,7 @@ static void mipi_dbi_lcdic_set_cmd(LCDIC_Type *base, enum lcdic_cmd_type dir, enum lcdic_cmd_dc dc, enum lcdic_data_fmt data_fmt, + enum lcdic_cmd_te te_sync, uint32_t buf_len) { union lcdic_trx_cmd cmd = {0}; @@ -387,6 +398,7 @@ static void mipi_dbi_lcdic_set_cmd(LCDIC_Type *base, cmd.bits.trx = dir; cmd.bits.cmd_done_int = true; cmd.bits.data_format = data_fmt; + cmd.bits.te_sync_mode = te_sync; /* Write command */ base->TFIFO_WDATA = cmd.u32; } @@ -401,6 +413,7 @@ static int mipi_dbi_lcdic_write_display(const struct device *dev, struct mipi_dbi_lcdic_data *dev_data = dev->data; LCDIC_Type *base = config->base; int ret; + enum lcdic_cmd_te te_sync = LCDIC_TE_NO_SYNC; uint32_t interrupts = 0U; ret = k_sem_take(&dev_data->lock, K_FOREVER); @@ -413,6 +426,26 @@ static int mipi_dbi_lcdic_write_display(const struct device *dev, goto out; } + if (dev_data->new_frame) { + switch (dev_data->te_edge) { + case MIPI_DBI_TE_RISING_EDGE: + te_sync = LCDIC_TE_RISING_EDGE; + break; + case MIPI_DBI_TE_FALLING_EDGE: + te_sync = LCDIC_TE_FALLING_EDGE; + break; + default: + te_sync = LCDIC_TE_NO_SYNC; + break; + } + dev_data->new_frame = false; + } + + if (!desc->frame_incomplete) { + /* Next frame will be a new one */ + dev_data->new_frame = true; + } + /* State reset is required before transfer */ mipi_dbi_lcdic_reset_state(dev); @@ -448,6 +481,7 @@ static int mipi_dbi_lcdic_write_display(const struct device *dev, */ mipi_dbi_lcdic_set_cmd(base, LCDIC_TX, LCDIC_DATA, dev_data->pixel_fmt, + te_sync, dev_data->cmd_bytes); #ifdef CONFIG_MIPI_DBI_NXP_LCDIC_DMA /* Enable command complete interrupt */ @@ -506,7 +540,7 @@ static int mipi_dbi_lcdic_write_cmd(const struct device *dev, /* Write command */ mipi_dbi_lcdic_set_cmd(base, LCDIC_TX, LCDIC_COMMAND, - LCDIC_DATA_FMT_BYTE, 1); + LCDIC_DATA_FMT_BYTE, LCDIC_TE_NO_SYNC, 1); /* Use standard byte writes */ dev_data->pixel_fmt = LCDIC_DATA_FMT_BYTE; base->TFIFO_WDATA = cmd; @@ -530,18 +564,10 @@ static int mipi_dbi_lcdic_write_cmd(const struct device *dev, dev_data->xfer_buf, dev_data->cmd_bytes); } - if (cmd == MIPI_DCS_WRITE_MEMORY_START) { - /* Use pixel format data width, so we can byte swap - * if needed - */ - mipi_dbi_lcdic_set_cmd(base, LCDIC_TX, LCDIC_DATA, - dev_data->pixel_fmt, - dev_data->cmd_bytes); - } else { - mipi_dbi_lcdic_set_cmd(base, LCDIC_TX, LCDIC_DATA, - LCDIC_DATA_FMT_BYTE, - dev_data->cmd_bytes); - } + mipi_dbi_lcdic_set_cmd(base, LCDIC_TX, LCDIC_DATA, + LCDIC_DATA_FMT_BYTE, + LCDIC_TE_NO_SYNC, + dev_data->cmd_bytes); #ifdef CONFIG_MIPI_DBI_NXP_LCDIC_DMA if (((((uint32_t)dev_data->xfer_buf) & 0x3) == 0) || (dev_data->cmd_bytes < 4)) { @@ -618,6 +644,50 @@ static int mipi_dbi_lcdic_reset(const struct device *dev, k_timeout_t delay) return 0; } +static int mipi_dbi_lcdic_configure_te(const struct device *dev, + uint8_t edge, + k_timeout_t delay) +{ + const struct mipi_dbi_lcdic_config *config = dev->config; + LCDIC_Type *base = config->base; + struct mipi_dbi_lcdic_data *data = dev->data; + uint32_t lcdic_freq, ttew, reg; + uint32_t delay_us = k_ticks_to_us_ceil32(delay.ticks); + + /* Calculate delay based off timer0 ratio. Formula given + * by RM is as follows: + * TE delay = Timer1_Period * ttew + * Timer1_Period = 2^(TIMER_RATIO1) * Timer0_Period + * Timer0_Period = 2^(TIMER_RATIO0) / LCDIC_Clock_Freq + */ + if (clock_control_get_rate(config->clock_dev, config->clock_subsys, + &lcdic_freq)) { + return -EIO; + } + + /* + * Calculate TTEW. Done in multiple steps to avoid overflowing + * the uint32_t type. Full formula is: + * (lcdic_freq * delay_us) / + * ((2 ^ (TIMER_RATIO1 + TIMER_RATIO0)) * USEC_PER_SEC) + */ + ttew = lcdic_freq / (1 << config->timer0_ratio); + ttew *= delay_us; + ttew /= (1 << config->timer1_ratio); + ttew /= USEC_PER_SEC; + + /* Check to see if the delay is shorter than we can support */ + if ((ttew == 0) && (delay_us != 0)) { + LOG_ERR("Timer ratios too large to support this TE delay"); + return -ENOTSUP; + } + reg = base->TE_CTRL; + reg &= ~LCDIC_TE_CTRL_TTEW_MASK; + reg |= LCDIC_TE_CTRL_TTEW(ttew); + base->TE_CTRL = reg; + data->te_edge = edge; + return 0; +} /* Initializes LCDIC peripheral */ @@ -671,6 +741,8 @@ static int mipi_dbi_lcdic_init(const struct device *dev) base->TIMER_CTRL = LCDIC_TIMER_CTRL_TIMER_RATIO1(config->timer1_ratio) | LCDIC_TIMER_CTRL_TIMER_RATIO0(config->timer0_ratio); + data->te_edge = MIPI_DBI_TE_NO_EDGE; + #ifdef CONFIG_MIPI_DBI_NXP_LCDIC_DMA /* Attach the LCDIC DMA request signal to the DMA channel we will * use with hardware triggering. @@ -687,6 +759,7 @@ static int mipi_dbi_lcdic_init(const struct device *dev) static const struct mipi_dbi_driver_api mipi_dbi_lcdic_driver_api = { .command_write = mipi_dbi_lcdic_write_cmd, .write_display = mipi_dbi_lcdic_write_display, + .configure_te = mipi_dbi_lcdic_configure_te, .reset = mipi_dbi_lcdic_reset, }; @@ -718,7 +791,8 @@ static void mipi_dbi_lcdic_isr(const struct device *dev) /* Command done. Queue next command */ data->cmd_bytes = MIN(data->xfer_bytes, LCDIC_MAX_XFER); mipi_dbi_lcdic_set_cmd(base, LCDIC_TX, LCDIC_DATA, - LCDIC_DATA_FMT_BYTE, + data->pixel_fmt, + LCDIC_TE_NO_SYNC, data->cmd_bytes); if (data->cmd_bytes & 0x3) { /* Save unaligned portion of transfer into From f4d6bc2ded647961a272e94cf6ad4b6e8c3f04ce Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Mon, 11 Nov 2024 20:40:03 +0000 Subject: [PATCH 3/4] drivers: display: st7796s: use mipi_dbi_configure_te API Use the mipi_dbi_configure_te API within the st7796s display driver. If the MIPI DBI controller supports the tearing enable signal, then configure the ST7796S to output the TE line signal. Signed-off-by: Daniel DeGrasse --- drivers/display/display_st7796s.c | 19 ++++++++++++++++++- drivers/display/display_st7796s.h | 1 + 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/drivers/display/display_st7796s.c b/drivers/display/display_st7796s.c index f7c31cde347d5..7bde63871cf3a 100644 --- a/drivers/display/display_st7796s.c +++ b/drivers/display/display_st7796s.c @@ -46,6 +46,8 @@ struct st7796s_config { uint8_t pgc[14]; /* Positive gamma control */ uint8_t ngc[14]; /* Negative gamma control */ uint8_t madctl; /* Memory data access control */ + uint8_t te_mode; /* Tearing enable mode */ + uint32_t te_delay; /* Tearing enable delay */ bool rgb_is_inverted; }; @@ -155,7 +157,7 @@ static int st7796s_write(const struct device *dev, { const struct st7796s_config *config = dev->config; int ret; - struct display_buffer_descriptor mipi_desc; + struct display_buffer_descriptor mipi_desc = {0}; enum display_pixel_format pixfmt; ret = st7796s_set_cursor(dev, x, y, desc->width, desc->height); @@ -164,6 +166,7 @@ static int st7796s_write(const struct device *dev, } mipi_desc.buf_size = desc->width * desc->height * ST7796S_PIXEL_SIZE; + mipi_desc.frame_incomplete = desc->frame_incomplete; ret = mipi_dbi_command_write(config->mipi_dbi, &config->dbi_config, ST7796S_CMD_RAMWR, @@ -282,6 +285,18 @@ static int st7796s_lcd_config(const struct device *dev) return ret; } + /* Attempt to enable TE signal */ + ret = mipi_dbi_configure_te(config->mipi_dbi, config->te_mode, + config->te_delay); + if (ret == 0) { + /* TE was enabled- send TEON, and enable vblank only */ + param = 0x0; /* Set TMEM bit to 0 */ + ret = st7796s_send_cmd(dev, ST7796S_CMD_TEON, ¶m, sizeof(param)); + if (ret < 0) { + return ret; + } + } + /* Lock display configuration */ param = ST7796S_LOCK_1; ret = st7796s_send_cmd(dev, ST7796S_CMD_CSCON, ¶m, sizeof(param)); @@ -385,6 +400,8 @@ static const struct display_driver_api st7796s_api = { .ngc = DT_INST_PROP(n, ngc), \ .madctl = DT_INST_PROP(n, madctl), \ .rgb_is_inverted = DT_INST_PROP(n, rgb_is_inverted), \ + .te_mode = MIPI_DBI_TE_MODE_DT_INST(n, te_mode), \ + .te_delay = DT_INST_PROP(n, te_delay), \ }; \ \ DEVICE_DT_INST_DEFINE(n, st7796s_init, \ diff --git a/drivers/display/display_st7796s.h b/drivers/display/display_st7796s.h index 7b0be000107a2..43e4e2e4b9c61 100644 --- a/drivers/display/display_st7796s.h +++ b/drivers/display/display_st7796s.h @@ -16,6 +16,7 @@ #define ST7796S_CMD_RAMWR 0x2C /* Memory write */ #define ST7796S_CMD_DISPOFF 0x28 /* Display off */ #define ST7796S_CMD_DISPON 0x29 /* Display on */ +#define ST7796S_CMD_TEON 0x35 /* Tearing effect on */ #define ST7796S_CMD_MADCTL 0x36 /* Memory data access control */ #define ST7796S_CMD_COLMOD 0x3A /* Interface pixel format */ #define ST7796S_CMD_FRMCTR1 0xB1 /* Frame rate control 1 (normal mode) */ From 7ab4386573a9b4ff19d19ae592a26d11aead9f6c Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Mon, 11 Nov 2024 20:41:07 +0000 Subject: [PATCH 4/4] boards: shields: lcd_par_s035: configure TE signal for RW612 Configure the TE signal for the rw_rw612_bga board when using the lcd_par_s035 shield. This signal should be handled on the rising edge in the default configuration, since the display writes from the MCU are faster than the panel reads data. Signed-off-by: Daniel DeGrasse --- boards/shields/lcd_par_s035/boards/rd_rw612_bga.overlay | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/boards/shields/lcd_par_s035/boards/rd_rw612_bga.overlay b/boards/shields/lcd_par_s035/boards/rd_rw612_bga.overlay index 3633336931089..d8d217460b421 100644 --- a/boards/shields/lcd_par_s035/boards/rd_rw612_bga.overlay +++ b/boards/shields/lcd_par_s035/boards/rd_rw612_bga.overlay @@ -84,6 +84,8 @@ * software */ rgb-is-inverted; + /* Enable TE synchronization, using the rising edge */ + te-mode = "MIPI_DBI_TE_RISING_EDGE"; }; &lcdic { @@ -94,4 +96,6 @@ nxp,write-inactive-cycles = <1>; /* Raise the timer0 ratio to enable longer reset delay */ nxp,timer0-ratio = <15>; + /* Lower timer1 ratio to enable shorter TE delay */ + nxp,timer1-ratio = <0>; };