Tested on 2 different panels, with an RZ/G2L and RZ/V2L. Thanks Hugo!
Tested-by: Chris Brandt <chris.bra...@renesas.com> -----Original Message----- From: Hugo Villeneuve <h...@hugovil.com> Sent: Wednesday, June 4, 2025 10:53 AM To: Biju Das <biju.das...@bp.renesas.com>; maarten.lankho...@linux.intel.com; mrip...@kernel.org; tzimmerm...@suse.de; airl...@gmail.com; sim...@ffwll.ch Cc: dri-devel@lists.freedesktop.org; linux-renesas-...@vger.kernel.org; linux-ker...@vger.kernel.org; h...@hugovil.com; Chris Brandt <chris.bra...@renesas.com>; Hugo Villeneuve <hvillene...@dimonoff.com> Subject: [PATCH v4 1/1] drm: renesas: rz-du: Implement MIPI DSI host transfers From: Hugo Villeneuve <hvillene...@dimonoff.com> Add support for sending MIPI DSI command packets from the host to a peripheral. This is required for panels that need configuration before they accept video data. Also for long reads to work properly, set DCS maximum return packet size to the value of the DMA buffer size. Based on Renesas Linux kernel v5.10 repos [1]. Link: https://github.com/renesas-rz/rz_linux-cip.git Cc: Biju Das <biju.das...@bp.renesas.com> Signed-off-by: Hugo Villeneuve <hvillene...@dimonoff.com> --- .../gpu/drm/renesas/rz-du/rzg2l_mipi_dsi.c | 186 ++++++++++++++++++ .../drm/renesas/rz-du/rzg2l_mipi_dsi_regs.h | 54 +++++ 2 files changed, 240 insertions(+) diff --git a/drivers/gpu/drm/renesas/rz-du/rzg2l_mipi_dsi.c b/drivers/gpu/drm/renesas/rz-du/rzg2l_mipi_dsi.c index 91e1a9adad7d6..50ec109aa6ed3 100644 --- a/drivers/gpu/drm/renesas/rz-du/rzg2l_mipi_dsi.c +++ b/drivers/gpu/drm/renesas/rz-du/rzg2l_mipi_dsi.c @@ -4,8 +4,11 @@ * * Copyright (C) 2022 Renesas Electronics Corporation */ + +#include <linux/bitfield.h> #include <linux/clk.h> #include <linux/delay.h> +#include <linux/dma-mapping.h> #include <linux/io.h> #include <linux/iopoll.h> #include <linux/module.h> @@ -23,9 +26,12 @@ #include <drm/drm_of.h> #include <drm/drm_panel.h> #include <drm/drm_probe_helper.h> +#include <video/mipi_display.h> #include "rzg2l_mipi_dsi_regs.h" +#define RZG2L_DCS_BUF_SIZE 128 /* Maximum DCS buffer size in external memory. */ + struct rzg2l_mipi_dsi { struct device *dev; void __iomem *mmio; @@ -44,6 +50,10 @@ struct rzg2l_mipi_dsi { unsigned int num_data_lanes; unsigned int lanes; unsigned long mode_flags; + + /* DCS buffer pointers when using external memory. */ + dma_addr_t dcs_buf_phys; + u8 *dcs_buf_virt; }; static inline struct rzg2l_mipi_dsi * @@ -267,6 +277,7 @@ static int rzg2l_mipi_dsi_startup(struct rzg2l_mipi_dsi *dsi, u32 clkbfht; u32 clkstpt; u32 golpbkt; + u32 dsisetr; int ret; /* @@ -328,6 +339,15 @@ static int rzg2l_mipi_dsi_startup(struct rzg2l_mipi_dsi *dsi, lptrnstsetr = LPTRNSTSETR_GOLPBKT(golpbkt); rzg2l_mipi_dsi_link_write(dsi, LPTRNSTSETR, lptrnstsetr); + /* + * Increase MRPSZ as the default value of 1 will result in long read + * commands payload not being saved to memory. + */ + dsisetr = rzg2l_mipi_dsi_link_read(dsi, DSISETR); + dsisetr &= ~DSISETR_MRPSZ; + dsisetr |= FIELD_PREP(DSISETR_MRPSZ, RZG2L_DCS_BUF_SIZE); + rzg2l_mipi_dsi_link_write(dsi, DSISETR, dsisetr); + return 0; err_phy: @@ -659,9 +679,168 @@ static int rzg2l_mipi_dsi_host_detach(struct mipi_dsi_host *host, return 0; } +static ssize_t rzg2l_mipi_dsi_read_response(struct rzg2l_mipi_dsi *dsi, + const struct mipi_dsi_msg *msg) { + u8 *msg_rx = msg->rx_buf; + u8 datatype; + u32 result; + u16 size; + + result = rzg2l_mipi_dsi_link_read(dsi, RXRSS0R); + if (result & RXRSS0R_RXPKTDFAIL) { + dev_err(dsi->dev, "packet rx data did not save correctly\n"); + return -EPROTO; + } + + if (result & RXRSS0R_RXFAIL) { + dev_err(dsi->dev, "packet rx failure\n"); + return -EPROTO; + } + + if (!(result & RXRSS0R_RXSUC)) + return -EPROTO; + + datatype = FIELD_GET(RXRSS0R_DT, result); + + switch (datatype) { + case 0: + dev_dbg(dsi->dev, "ACK\n"); + return 0; + case MIPI_DSI_RX_END_OF_TRANSMISSION: + dev_dbg(dsi->dev, "EoTp\n"); + return 0; + case MIPI_DSI_RX_ACKNOWLEDGE_AND_ERROR_REPORT: + dev_dbg(dsi->dev, "Acknowledge and error report: $%02x%02x\n", + (u8)FIELD_GET(RXRSS0R_DATA1, result), + (u8)FIELD_GET(RXRSS0R_DATA0, result)); + return 0; + case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_1BYTE: + case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_1BYTE: + msg_rx[0] = FIELD_GET(RXRSS0R_DATA0, result); + return 1; + case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_2BYTE: + case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_2BYTE: + msg_rx[0] = FIELD_GET(RXRSS0R_DATA0, result); + msg_rx[1] = FIELD_GET(RXRSS0R_DATA1, result); + return 2; + case MIPI_DSI_RX_GENERIC_LONG_READ_RESPONSE: + case MIPI_DSI_RX_DCS_LONG_READ_RESPONSE: + size = FIELD_GET(RXRSS0R_WC, result); + + if (size > msg->rx_len) { + dev_err(dsi->dev, "rx buffer too small"); + return -ENOSPC; + } + + memcpy(msg_rx, dsi->dcs_buf_virt, size); + return size; + default: + dev_err(dsi->dev, "unhandled response type: %02x\n", datatype); + return -EPROTO; + } +} + +static ssize_t rzg2l_mipi_dsi_host_transfer(struct mipi_dsi_host *host, + const struct mipi_dsi_msg *msg) { + struct rzg2l_mipi_dsi *dsi = host_to_rzg2l_mipi_dsi(host); + struct mipi_dsi_packet packet; + bool need_bta; + u32 value; + int ret; + + ret = mipi_dsi_create_packet(&packet, msg); + if (ret < 0) + return ret; + + /* Terminate operation after this descriptor is finished */ + value = SQCH0DSC0AR_NXACT_TERM; + + if (msg->flags & MIPI_DSI_MSG_REQ_ACK) { + need_bta = true; /* Message with explicitly requested ACK */ + value |= FIELD_PREP(SQCH0DSC0AR_BTA, SQCH0DSC0AR_BTA_NON_READ); + } else if (msg->rx_buf && msg->rx_len > 0) { + need_bta = true; /* Read request */ + value |= FIELD_PREP(SQCH0DSC0AR_BTA, SQCH0DSC0AR_BTA_READ); + } else { + need_bta = false; + value |= FIELD_PREP(SQCH0DSC0AR_BTA, SQCH0DSC0AR_BTA_NONE); + } + + /* Set transmission speed */ + if (msg->flags & MIPI_DSI_MSG_USE_LPM) + value |= SQCH0DSC0AR_SPD_LOW; + else + value |= SQCH0DSC0AR_SPD_HIGH; + + /* Write TX packet header */ + value |= FIELD_PREP(SQCH0DSC0AR_DT, packet.header[0]) | + FIELD_PREP(SQCH0DSC0AR_DATA0, packet.header[1]) | + FIELD_PREP(SQCH0DSC0AR_DATA1, packet.header[2]); + + if (mipi_dsi_packet_format_is_long(msg->type)) { + value |= SQCH0DSC0AR_FMT_LONG; + + if (packet.payload_length > RZG2L_DCS_BUF_SIZE) { + dev_err(dsi->dev, "Packet Tx payload size (%d) too large", + (unsigned int)packet.payload_length); + return -ENOSPC; + } + + /* Copy TX packet payload data to memory space */ + memcpy(dsi->dcs_buf_virt, packet.payload, packet.payload_length); + } else { + value |= SQCH0DSC0AR_FMT_SHORT; + } + + rzg2l_mipi_dsi_link_write(dsi, SQCH0DSC0AR, value); + + /* + * Write: specify payload data source location, only used for + * long packet. + * Read: specify payload data storage location of response + * packet. Note: a read packet is always a short packet. + * If the response packet is a short packet or a long packet + * with WC = 0 (no payload), DTSEL is meaningless. + */ + rzg2l_mipi_dsi_link_write(dsi, SQCH0DSC0BR, +SQCH0DSC0BR_DTSEL_MEM_SPACE); + + /* + * Set SQCHxSR.AACTFIN bit when descriptor actions are finished. + * Read: set Rx result save slot number to 0 (ACTCODE). + */ + rzg2l_mipi_dsi_link_write(dsi, SQCH0DSC0CR, SQCH0DSC0CR_FINACT); + + /* Set rx/tx payload data address, only relevant for long packet. */ + rzg2l_mipi_dsi_link_write(dsi, SQCH0DSC0DR, (u32)dsi->dcs_buf_phys); + + /* Start sequence 0 operation */ + value = rzg2l_mipi_dsi_link_read(dsi, SQCH0SET0R); + value |= SQCH0SET0R_START; + rzg2l_mipi_dsi_link_write(dsi, SQCH0SET0R, value); + + /* Wait for operation to finish */ + ret = read_poll_timeout(rzg2l_mipi_dsi_link_read, + value, value & SQCH0SR_ADESFIN, + 2000, 20000, false, dsi, SQCH0SR); + if (ret == 0) { + /* Success: clear status bit */ + rzg2l_mipi_dsi_link_write(dsi, SQCH0SCR, SQCH0SCR_ADESFIN); + + if (need_bta) + ret = rzg2l_mipi_dsi_read_response(dsi, msg); + else + ret = packet.payload_length; + } + + return ret; +} + static const struct mipi_dsi_host_ops rzg2l_mipi_dsi_host_ops = { .attach = rzg2l_mipi_dsi_host_attach, .detach = rzg2l_mipi_dsi_host_detach, + .transfer = rzg2l_mipi_dsi_host_transfer, }; /* ----------------------------------------------------------------------------- @@ -779,6 +958,11 @@ static int rzg2l_mipi_dsi_probe(struct platform_device *pdev) if (ret < 0) goto err_pm_disable; + dsi->dcs_buf_virt = dma_alloc_coherent(dsi->host.dev, RZG2L_DCS_BUF_SIZE, + &dsi->dcs_buf_phys, GFP_KERNEL); + if (!dsi->dcs_buf_virt) + return -ENOMEM; + return 0; err_phy: @@ -793,6 +977,8 @@ static void rzg2l_mipi_dsi_remove(struct platform_device *pdev) { struct rzg2l_mipi_dsi *dsi = platform_get_drvdata(pdev); + dma_free_coherent(dsi->host.dev, RZG2L_DCS_BUF_SIZE, dsi->dcs_buf_virt, + dsi->dcs_buf_phys); mipi_dsi_host_unregister(&dsi->host); pm_runtime_disable(&pdev->dev); } diff --git a/drivers/gpu/drm/renesas/rz-du/rzg2l_mipi_dsi_regs.h b/drivers/gpu/drm/renesas/rz-du/rzg2l_mipi_dsi_regs.h index 1dbc16ec64a4b..26d8a37ee6351 100644 --- a/drivers/gpu/drm/renesas/rz-du/rzg2l_mipi_dsi_regs.h +++ b/drivers/gpu/drm/renesas/rz-du/rzg2l_mipi_dsi_regs.h @@ -81,6 +81,20 @@ #define RSTSR_SWRSTLP (1 << 1) #define RSTSR_SWRSTHS (1 << 0) +/* DSI Set Register */ +#define DSISETR 0x120 +#define DSISETR_MRPSZ GENMASK(15, 0) + +/* Rx Result Save Slot 0 Register */ +#define RXRSS0R 0x240 +#define RXRSS0R_RXPKTDFAIL BIT(28) +#define RXRSS0R_RXFAIL BIT(27) +#define RXRSS0R_RXSUC BIT(25) +#define RXRSS0R_DT GENMASK(21, 16) +#define RXRSS0R_DATA1 GENMASK(15, 8) +#define RXRSS0R_DATA0 GENMASK(7, 0) +#define RXRSS0R_WC GENMASK(15, 0) /* Word count for long packet. */ + /* Clock Lane Stop Time Set Register */ #define CLSTPTSETR 0x314 #define CLSTPTSETR_CLKKPT(x) ((x) << 24) @@ -148,4 +162,44 @@ #define VICH1HPSETR_HFP(x) (((x) & 0x1fff) << 16) #define VICH1HPSETR_HBP(x) (((x) & 0x1fff) << 0) +/* Sequence Channel 0 Set 0 Register */ +#define SQCH0SET0R 0x5c0 +#define SQCH0SET0R_START BIT(0) + +/* Sequence Channel 0 Status Register */ +#define SQCH0SR 0x5d0 +#define SQCH0SR_ADESFIN BIT(8) + +/* Sequence Channel 0 Status Clear Register */ +#define SQCH0SCR 0x5d4 +#define SQCH0SCR_ADESFIN BIT(8) + +/* Sequence Channel 0 Descriptor 0-A Register */ +#define SQCH0DSC0AR 0x780 +#define SQCH0DSC0AR_NXACT_TERM 0 /* Bit 28 */ +#define SQCH0DSC0AR_BTA GENMASK(27, 26) +#define SQCH0DSC0AR_BTA_NONE 0 +#define SQCH0DSC0AR_BTA_NON_READ 1 +#define SQCH0DSC0AR_BTA_READ 2 +#define SQCH0DSC0AR_BTA_ONLY 3 +#define SQCH0DSC0AR_SPD_HIGH 0 +#define SQCH0DSC0AR_SPD_LOW BIT(25) +#define SQCH0DSC0AR_FMT_SHORT 0 +#define SQCH0DSC0AR_FMT_LONG BIT(24) +#define SQCH0DSC0AR_DT GENMASK(21, 16) +#define SQCH0DSC0AR_DATA1 GENMASK(15, 8) +#define SQCH0DSC0AR_DATA0 GENMASK(7, 0) + +/* Sequence Channel 0 Descriptor 0-B Register */ +#define SQCH0DSC0BR 0x784 +#define SQCH0DSC0BR_DTSEL_MEM_SPACE BIT(24) /* Use external memory */ + +/* Sequence Channel 0 Descriptor 0-C Register */ +#define SQCH0DSC0CR 0x788 +#define SQCH0DSC0CR_FINACT BIT(0) +#define SQCH0DSC0CR_AUXOP BIT(22) + +/* Sequence Channel 0 Descriptor 0-D Register */ +#define SQCH0DSC0DR 0x78c + #endif /* __RZG2L_MIPI_DSI_REGS_H__ */ -- 2.39.5