Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions boards/shields/lcd_par_s035/boards/rd_rw612_bga.overlay
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@
* software
*/
rgb-is-inverted;
/* Enable TE synchronization, using the rising edge */
te-mode = "MIPI_DBI_TE_RISING_EDGE";
};

&lcdic {
Expand All @@ -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>;
};
19 changes: 18 additions & 1 deletion drivers/display/display_st7796s.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};

Expand Down Expand Up @@ -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);
Expand All @@ -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,
Expand Down Expand Up @@ -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, &param, sizeof(param));
if (ret < 0) {
return ret;
}
}

/* Lock display configuration */
param = ST7796S_LOCK_1;
ret = st7796s_send_cmd(dev, ST7796S_CMD_CSCON, &param, sizeof(param));
Expand Down Expand Up @@ -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, \
Expand Down
1 change: 1 addition & 0 deletions drivers/display/display_st7796s.h
Original file line number Diff line number Diff line change
Expand Up @@ -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) */
Expand Down
102 changes: 88 additions & 14 deletions drivers/mipi_dbi/mipi_dbi_nxp_lcdic.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) */
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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};
Expand All @@ -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;
}
Expand All @@ -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);
Expand All @@ -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;
}
Copy link
Contributor

@Finomnis Finomnis Dec 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a little confused here; the way this if statement is written, if every single frame is frame_incomplete = false, then the te_sync will only be active every other write call. Is that intended?

I feel like the else should be removed, and the if (!desc->frame_incomplete) should be an independent separate block that gets always executed regardless of the previous if block.

Feel free to correct my thoughts if I'm mistaken.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I think you are right here. We always want the next call after one with frame_incomplete = false to trigger a TE wait. I'll update this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated


/* State reset is required before transfer */
mipi_dbi_lcdic_reset_state(dev);

Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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;
Expand All @@ -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)) {
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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.
Expand All @@ -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,
};

Expand Down Expand Up @@ -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
Expand Down
18 changes: 18 additions & 0 deletions dts/bindings/mipi-dbi/mipi-dbi-device.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
66 changes: 66 additions & 0 deletions include/zephyr/drivers/mipi_dbi.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand Down Expand Up @@ -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);
};

/**
Expand Down Expand Up @@ -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
Comment on lines +343 to +344
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some controllers also support TE source (DSI link or External pin). How would you propose to configure this ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have an example I could look at? Not sure I follow here. Do you mean that the MIPI controller supports selecting the TE source between an on chip DSI peripheral and external pin?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for pointing this out- one thing to note, this is a DSI controller, not a DBI controller- so it wouldn't fit within the MIPI DBI API itself.

Looking at this more, I do think we'd need something like this within the MIPI DSI API though- it looks like DSI controllers can either signal TE via a dedicated GPIO pin, or via a D-PHY trigger event on the DSI bus itself.

* @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
Expand Down
Loading
Loading