[PATCH 4/4] drm/rcar-du: dsi: Implement DSI command support

Tomi Valkeinen tomi.valkeinen+renesas at ideasonboard.com
Tue Aug 12 14:36:36 UTC 2025


Hi,

On 08/06/2025 17:24, Marek Vasut wrote:
> Implement support for DSI command transfer mode. Transmission of both Short

I constantly kept reading "DSI command mode support". So I was quite
confused for a bit =). Maybe avoid the use of "mode" with "DSI command".

> Packet and Long Packet is implemented, so is command transmission to request
> response from peripheral device and transmission of non-read command with BTA.
> 
> The AXI memory access mode is currently not implemented, each transfer is
> performed purely using controller register interface. Short Packet transfer
> can transfer up to 2 Bytes of data, Long Packet transfer can transfer up to
> 16 Bytes of data.
> 
> Signed-off-by: Marek Vasut <marek.vasut+renesas at mailbox.org>
> ---
> Cc: David Airlie <airlied at gmail.com>
> Cc: Geert Uytterhoeven <geert+renesas at glider.be>
> Cc: Kieran Bingham <kieran.bingham+renesas at ideasonboard.com>
> Cc: Laurent Pinchart <laurent.pinchart+renesas at ideasonboard.com>
> Cc: Maarten Lankhorst <maarten.lankhorst at linux.intel.com>
> Cc: Magnus Damm <magnus.damm at gmail.com>
> Cc: Maxime Ripard <mripard at kernel.org>
> Cc: Simona Vetter <simona at ffwll.ch>
> Cc: Thomas Zimmermann <tzimmermann at suse.de>
> Cc: Tomi Valkeinen <tomi.valkeinen+renesas at ideasonboard.com>
> Cc: dri-devel at lists.freedesktop.org
> Cc: linux-renesas-soc at vger.kernel.org
> ---
>  .../gpu/drm/renesas/rcar-du/rcar_mipi_dsi.c   | 215 ++++++++++++++++++
>  .../drm/renesas/rcar-du/rcar_mipi_dsi_regs.h  | 125 ++++++++++
>  2 files changed, 340 insertions(+)
> 
> diff --git a/drivers/gpu/drm/renesas/rcar-du/rcar_mipi_dsi.c b/drivers/gpu/drm/renesas/rcar-du/rcar_mipi_dsi.c
> index c31e0d8f3ff9..bc1151c3ce90 100644
> --- a/drivers/gpu/drm/renesas/rcar-du/rcar_mipi_dsi.c
> +++ b/drivers/gpu/drm/renesas/rcar-du/rcar_mipi_dsi.c
> @@ -938,9 +938,224 @@ static int rcar_mipi_dsi_host_detach(struct mipi_dsi_host *host,
>  	return 0;
>  }
>  
> +static ssize_t rcar_mipi_dsi_host_tx_transfer(struct mipi_dsi_host *host,
> +					      const struct mipi_dsi_msg *msg,
> +					      bool is_rx_xfer)
> +{
> +	const bool is_tx_long = mipi_dsi_packet_format_is_long(msg->type);
> +	struct rcar_mipi_dsi *dsi = host_to_rcar_mipi_dsi(host);
> +	struct mipi_dsi_packet packet;
> +	u8 payload[16] = { 0 };
> +	u32 status;
> +	int ret;
> +
> +	ret = mipi_dsi_create_packet(&packet, msg);
> +	if (ret)
> +		return ret;
> +
> +	/* Configure LP or HS command transfer. */
> +	rcar_mipi_dsi_write(dsi, TXCMSETR, (msg->flags & MIPI_DSI_MSG_USE_LPM) ?
> +					   TXCMSETR_SPDTYP : 0);

There's no runtime PM in the driver, and the clocks are enabled
externally... So I think we just assume that the IP is running here?
It's not a few times (on other platforms) I've hit an issue with
dsi_host tx being called when the video output is not yet enabled, and
thus the whole IP is off. So, maybe this works, but just a note.

> +	/* Register access mode for RX transfer. */
> +	if (is_rx_xfer)
> +		rcar_mipi_dsi_write(dsi, RXPSETR, 0);
> +
> +	/* Do not use IRQ, poll for completion, the completion is quick. */
> +	rcar_mipi_dsi_write(dsi, TXCMIER, 0);
> +
> +	/*
> +	 * Send the header:
> +	 * header[0] = Virtual Channel + Data Type
> +	 * header[1] = Word Count LSB (LP) or first param (SP)
> +	 * header[2] = Word Count MSB (LP) or second param (SP)
> +	 */
> +	rcar_mipi_dsi_write(dsi, TXCMPHDR,
> +			    (is_tx_long ? TXCMPHDR_FMT : 0) |
> +			    TXCMPHDR_VC(msg->channel) |
> +			    TXCMPHDR_DT(msg->type) |
> +			    TXCMPHDR_DATA1(packet.header[2]) |
> +			    TXCMPHDR_DATA0(packet.header[1]));
> +
> +	if (is_tx_long) {
> +		memcpy(payload, packet.payload,
> +		       min(msg->tx_len, sizeof(payload)));
> +
> +		rcar_mipi_dsi_write(dsi, TXCMPPD0R,
> +				    (payload[3] << 24) | (payload[2] << 16) |
> +				    (payload[1] << 8) | payload[0]);
> +		rcar_mipi_dsi_write(dsi, TXCMPPD1R,
> +				    (payload[7] << 24) | (payload[6] << 16) |
> +				    (payload[5] << 8) | payload[4]);
> +		rcar_mipi_dsi_write(dsi, TXCMPPD2R,
> +				    (payload[11] << 24) | (payload[10] << 16) |
> +				    (payload[9] << 8) | payload[8]);
> +		rcar_mipi_dsi_write(dsi, TXCMPPD3R,
> +				    (payload[15] << 24) | (payload[14] << 16) |
> +				    (payload[13] << 8) | payload[12]);
> +	}
> +
> +	/* Start the transfer, RX with BTA, TX without BTA */
> +	if (is_rx_xfer) {
> +		rcar_mipi_dsi_write(dsi, TXCMCR, TXCMCR_BTAREQ);
> +
> +		ret = read_poll_timeout(rcar_mipi_dsi_read, status,
> +					(status & RXPSR_BTAREQEND),
> +					2000, 10000, false, dsi, RXPSR);
> +	} else {
> +		rcar_mipi_dsi_write(dsi, TXCMCR, TXCMCR_TXREQ);
> +
> +		ret = read_poll_timeout(rcar_mipi_dsi_read, status,
> +					(status & TXCMSR_TXREQEND),
> +					2000, 10000, false, dsi, TXCMSR);
> +	}

Did you check the timeout is big enough? With LP and BTA... Well, it's
only 16 bytes at max. Maybe it's fine. Again, just a note. =)

Does this work when the video stream is on? If yes, then it might take
much longer until the command can be transferred. If not maybe the
function should return an error if the video stream is enabled.

What do these read_poll_timeouts wait, exactly? The first one waits
until the data is sent, and BTA has been done? And the latter waits only
for the data to be sent? Hmm, no... The first must wait until the
peripheral has replied, as there's no wait in the
rcar_mipi_dsi_host_rx_transfer()...

It would be nice to have a short comment what the wait is for.

> +
> +	if (ret < 0) {
> +		dev_err(dsi->dev, "Command transfer timeout (0x%08x)\n",
> +			status);
> +		return ret;
> +	}
> +
> +	return packet.size;
> +}
> +
> +static ssize_t rcar_mipi_dsi_host_rx_transfer(struct mipi_dsi_host *host,
> +					      const struct mipi_dsi_msg *msg)
> +{
> +	struct rcar_mipi_dsi *dsi = host_to_rcar_mipi_dsi(host);
> +	u8 *rx_buf = (u8 *)(msg->rx_buf);
> +	u32 reg, data, status, wc;
> +	int i, ret;
> +
> +	/* RX transfer received data validation and parsing starts here. */
> +	reg = rcar_mipi_dsi_read(dsi, TOSR);
> +	if (reg & TOSR_TATO) {	/* Turn-Around TimeOut. */
> +		/* Clear TATO Turn-Around TimeOut bit. */
> +		rcar_mipi_dsi_write(dsi, TOSR, TOSR_TATO);
> +		return -ETIMEDOUT;
> +	}
> +
> +	reg = rcar_mipi_dsi_read(dsi, RXPSR);
> +
> +	if (msg->flags & MIPI_DSI_MSG_REQ_ACK) {
> +		/* Transfer with zero-length RX */
> +		if (!(reg & RXPSR_RCVACK)) {
> +			/* No ACK on RX response received */
> +			return -EINVAL;
> +		}
> +	} else {
> +		/* Transfer with non-zero-length RX */
> +		if (!(reg & RXPSR_RCVRESP)) {
> +			/* No packet header of RX response received */
> +			return -EINVAL;
> +		}
> +
> +		if (reg & (RXPSR_CRCERR | RXPSR_WCERR | RXPSR_AXIERR | RXPSR_OVRERR)) {
> +			/* Incorrect response payload */
> +			return -ENODATA;
> +		}
> +
> +		data = rcar_mipi_dsi_read(dsi, RXPHDR);
> +		if (data & RXPHDR_FMT) {	/* Long Packet Response */
> +			/* Read Long Packet Response length from packet header. */
> +			wc = data & 0xffff;
> +			if (wc > msg->rx_len) {
> +				dev_warn(dsi->dev,
> +					 "Long Packet Response longer than RX buffer (%d), limited to %ld Bytes\n",
> +					 wc, msg->rx_len);
> +				wc = msg->rx_len;
> +			}
> +
> +			if (wc > 16) {
> +				dev_warn(dsi->dev,
> +					 "Long Packet Response too long (%d), limited to 16 Bytes\n",
> +					 wc);
> +				wc = 16;
> +			}
> +
> +			for (i = 0; i < msg->rx_len; i++) {
> +				if (!(i % 4))
> +					data = rcar_mipi_dsi_read(dsi, RXPPD0R + i);
> +
> +				rx_buf[i] = data & 0xff;
> +				data >>= 8;
> +			}
> +		} else {	/* Short Packet Response */
> +			if (msg->rx_len >= 1)
> +				rx_buf[0] = data & 0xff;
> +			if (msg->rx_len >= 2)
> +				rx_buf[1] = (data >> 8) & 0xff;
> +			if (msg->rx_len >= 3) {
> +				dev_warn(dsi->dev,
> +					 "Expected Short Packet Response too long (%ld), limited to 2 Bytes\n",
> +					 msg->rx_len);
> +			}
> +		}
> +	}
> +
> +	if (reg & RXPSR_RCVAKE) {
> +		/* Acknowledge and Error report received */
> +		return -EFAULT;
> +	}
> +
> +	ret = read_poll_timeout(rcar_mipi_dsi_read, status,
> +				!(status & PPIDL0SR_DIR),
> +				2000, 10000, false, dsi, PPIDL0SR);
> +	if (ret < 0) {
> +		dev_err(dsi->dev, "Command RX DIR timeout (0x%08x)\n", status);
> +		return ret;
> +	}
> +
> +	ret = read_poll_timeout(rcar_mipi_dsi_read, status,
> +				status & PPIDL0SR_STPST,
> +				2000, 10000, false, dsi, PPIDL0SR);
> +	if (ret < 0) {
> +		dev_err(dsi->dev, "Command RX STPST timeout (0x%08x)\n", status);
> +		return ret;
> +	}

Same here, it's not very clear what the waits are for. Aren't we done
already after the tx function finished?

> +
> +	return 0;
> +}
> +
> +static ssize_t rcar_mipi_dsi_host_transfer(struct mipi_dsi_host *host,
> +					   const struct mipi_dsi_msg *msg)
> +{
> +	const bool is_rx_xfer = (msg->flags & MIPI_DSI_MSG_REQ_ACK) || msg->rx_len;
> +	struct rcar_mipi_dsi *dsi = host_to_rcar_mipi_dsi(host);
> +	int ret;
> +
> +	if (msg->tx_len > 16 || msg->rx_len > 16) {
> +		/* ToDo: Implement Memory on AXI bus command mode. */
> +		dev_warn(dsi->dev,
> +			 "Register-based command mode supports only up to 16 Bytes long payload\n");
> +		return -EOPNOTSUPP;
> +	}
> +
> +	ret = rcar_mipi_dsi_host_tx_transfer(host, msg, is_rx_xfer);
> +
> +	/* If TX transfer succeeded and this transfer has RX part. */
> +	if (ret >= 0 && is_rx_xfer) {
> +		ret = rcar_mipi_dsi_host_rx_transfer(host, msg);
> +		if (ret)
> +			return ret;
> +
> +		ret = msg->rx_len;
> +	}
> +
> +	/* Wait a bit between commands */
> +	usleep_range(1000, 2000);

Why wait and wait a bit between what?

 Tomi



More information about the dri-devel mailing list