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>; }; 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) */ 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 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 + /** * @} */