mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH 0/4] tty: serial: qcom_geni_serial: Add GPI DMA support
@ 2026-10-09  6:57 Naresh Maramaina
  2026-10-09  6:57 ` [PATCH 1/4] dmaengine: qcom: gpi: Add UART transfer support Naresh Maramaina
                   ` (3 more replies)
  0 siblings, 4 replies; 6+ messages in thread
From: Naresh Maramaina @ 2026-10-09  6:57 UTC (permalink / raw)
  To: konrad.dybcio, Vinod Koul, Frank Li, Bjorn Andersson,
	Konrad Dybcio, Abel Vesa, Greg Kroah-Hartman, Jiri Slaby
  Cc: linux-arm-msm, dmaengine, linux-kernel, linux-serial,
	Mukesh Kumar Savaliya, Aniket Randive, chandana.chiluveru,
	jyothi.seerapu, praveen.talari, Naresh Maramaina

QUP serial engines support FIFO, SE DMA and GPI DMA transfer modes.
The qcom_geni_serial driver currently supports FIFO and SE DMA modes.

Add support for serial engines configured to operate in GPI DMA mode.

The Qualcomm GPI DMA driver is extended with UART transfer support,
UART-specific TRE accounting and handling for transfer-completion
events generated during channel cancellation.

The qcom_geni_serial driver is updated to detect GPI DMA operation,
manage UART transfers through DMAengine and program UART parameters
through GPI peripheral configuration.

This series currently supports 2-wire UART operation (TX/RX) and has
been validated across multiple baud rates and transfer sizes.
4-wire UART operation with CTS/RTS flow control is not included in
this series. GPI DMA support for CTS/RTS will be added separately
after implementation and validation.

The series is organized as follows:

1. Add UART transfer support to the GPI DMA driver
2. Calculate UART TRE count per direction
3. Ignore cancelled transfer-completion events
4. Add GPI DMA support to qcom_geni_serial

Signed-off-by: Naresh Maramaina <naresh.maramaina@oss.qualcomm.com>
---
Naresh Maramaina (4):
      dmaengine: qcom: gpi: Add UART transfer support
      dmaengine: qcom: gpi: Calculate UART TRE count per direction
      dmaengine: qcom: gpi: Ignore cancelled transfer-completion events
      tty: serial: qcom_geni_serial: Add GPI DMA support

 drivers/dma/qcom/gpi.c                | 153 ++++++++-
 drivers/soc/qcom/qcom-geni-se.c       |   4 -
 drivers/tty/serial/qcom_geni_serial.c | 576 ++++++++++++++++++++++++++++++++--
 include/linux/dma/qcom-gpi-dma.h      |  33 ++
 4 files changed, 732 insertions(+), 34 deletions(-)
---
base-commit: aac26bee2287c88af5be5a5ff96d783b19a28790
change-id: 20261008-add-gpi-dma-support-for-uart-53f2500368d6

Best regards,
--  
Naresh Maramaina <naresh.maramaina@oss.qualcomm.com>


^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH 1/4] dmaengine: qcom: gpi: Add UART transfer support
  2026-10-09  6:57 [PATCH 0/4] tty: serial: qcom_geni_serial: Add GPI DMA support Naresh Maramaina
@ 2026-10-09  6:57 ` Naresh Maramaina
  2026-10-09  6:57 ` [PATCH 2/4] dmaengine: qcom: gpi: Calculate UART TRE count per direction Naresh Maramaina
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 6+ messages in thread
From: Naresh Maramaina @ 2026-10-09  6:57 UTC (permalink / raw)
  To: konrad.dybcio, Vinod Koul, Frank Li, Bjorn Andersson,
	Konrad Dybcio, Abel Vesa, Greg Kroah-Hartman, Jiri Slaby
  Cc: linux-arm-msm, dmaengine, linux-kernel, linux-serial,
	Mukesh Kumar Savaliya, Aniket Randive, chandana.chiluveru,
	jyothi.seerapu, praveen.talari, Naresh Maramaina

The GPI DMA driver currently supports TRE construction for SPI and I2C
clients, but not for UART clients. Add the UART CONFIG0 and GO TRE
definitions and expose a UART peripheral configuration structure so
UART clients can describe their transfer parameters.

Build the TRE sequence needed for UART transfers. Emit a CONFIG0 TRE
when a configuration update is requested, followed by a GO TRE and
a DMA TRE for the transfer buffer.

Add the UART case to the TRE construction path in gpi_prep_slave_sg().

Signed-off-by: Naresh Maramaina <naresh.maramaina@oss.qualcomm.com>
---
 drivers/dma/qcom/gpi.c           | 101 +++++++++++++++++++++++++++++++++++++--
 include/linux/dma/qcom-gpi-dma.h |  33 +++++++++++++
 2 files changed, 131 insertions(+), 3 deletions(-)

diff --git a/drivers/dma/qcom/gpi.c b/drivers/dma/qcom/gpi.c
index c41bfac65ddf..fb26a286e5df 100644
--- a/drivers/dma/qcom/gpi.c
+++ b/drivers/dma/qcom/gpi.c
@@ -30,6 +30,21 @@
 #define TRE_FLAGS_LINK		BIT(11)
 #define TRE_FLAGS_TYPE		GENMASK(23, 16)
 
+/* UART CONFIG0 WD0 */
+#define TRE_UART_C0_CHAR_SZ		GENMASK(2, 0)
+#define TRE_UART_C0_STOP_BIT_LEN	GENMASK(4, 3)
+#define TRE_UART_C0_PARITY		GENMASK(7, 5)
+#define TRE_UART_C0_LOOPBACK		BIT(8)
+#define TRE_UART_C0_HW_FLOW_CTRL_EN	BIT(10)
+#define TRE_UART_C0_PACKING		BIT(24)
+
+/* UART CONFIG0 WD1 */
+#define TRE_UART_C0_RX_STALE_TIMEOUT	GENMASK(23, 0)
+#define TRE_UART_C0_RX_RFR_LEVEL	GENMASK(29, 24)
+
+/* UART GO WD0 */
+#define TRE_UART_GO_CMD		GENMASK(4, 0)
+
 /* SPI CONFIG0 WD0 */
 #define TRE_SPI_C0_WORD_SZ	GENMASK(4, 0)
 #define TRE_SPI_C0_LOOPBACK	BIT(8)
@@ -1793,6 +1808,80 @@ static int gpi_create_spi_tre(struct gchan *chan, struct gpi_desc *desc,
 	return tre_idx;
 }
 
+static int
+gpi_create_serial_tre(struct gchan *chan, struct gpi_desc *desc,
+		      struct scatterlist *sgl, enum dma_transfer_direction direction)
+{
+	struct gpi_serial_config *serial = chan->config;
+	struct device *dev = chan->gpii->gpi_dev->dev;
+	struct gpi_tre *tre;
+	dma_addr_t address;
+	unsigned int tre_idx = 0;
+	unsigned int i;
+	bool set_config = (direction == DMA_DEV_TO_MEM) ?
+			   serial->set_rx_config :
+			   serial->set_tx_config;
+
+	/* create config tre if applicable */
+	if (set_config) {
+		tre = &desc->tre[tre_idx];
+		tre_idx++;
+		tre->dword[0] = u32_encode_bits(serial->char_size, TRE_UART_C0_CHAR_SZ);
+		tre->dword[0] |= u32_encode_bits(serial->stop_bit_len, TRE_UART_C0_STOP_BIT_LEN);
+		tre->dword[0] |= u32_encode_bits(serial->parity, TRE_UART_C0_PARITY);
+		tre->dword[0] |= u32_encode_bits(serial->loopback_en, TRE_UART_C0_LOOPBACK);
+		tre->dword[0] |= u32_encode_bits(serial->hw_flow_ctrl_en,
+						 TRE_UART_C0_HW_FLOW_CTRL_EN);
+		tre->dword[0] |= u32_encode_bits(serial->packing, TRE_UART_C0_PACKING);
+		if (direction == DMA_MEM_TO_DEV) {
+			/* No configuration for dword1 of TX channel */
+			tre->dword[1] = 0;
+		} else if (direction == DMA_DEV_TO_MEM) {
+			tre->dword[1] = u32_encode_bits(serial->rx_stale_timeout,
+							TRE_UART_C0_RX_STALE_TIMEOUT);
+			tre->dword[1] |= u32_encode_bits(serial->rfr_level,
+							 TRE_UART_C0_RX_RFR_LEVEL);
+			serial->set_rx_config = false;
+		}
+
+		tre->dword[2] = u32_encode_bits(serial->clk_div, TRE_C0_CLK_DIV);
+		tre->dword[2] |= u32_encode_bits(serial->clk_src, TRE_C0_CLK_SRC);
+
+		tre->dword[3] = u32_encode_bits(TRE_TYPE_CONFIG0, TRE_FLAGS_TYPE);
+		tre->dword[3] |= u32_encode_bits(1, TRE_FLAGS_CHAIN);
+	}
+
+	/* create the GO tre */
+	tre = &desc->tre[tre_idx];
+	tre_idx++;
+
+	tre->dword[0] = u32_encode_bits(serial->opcode, TRE_UART_GO_CMD);
+	tre->dword[1] = 0;
+	tre->dword[2] = 0;
+	tre->dword[3] = u32_encode_bits(TRE_TYPE_GO, TRE_FLAGS_TYPE);
+	tre->dword[3] |= u32_encode_bits(1, TRE_FLAGS_CHAIN);
+
+	/* create the dma tre */
+	tre = &desc->tre[tre_idx];
+	tre_idx++;
+
+	address = sg_dma_address(sgl);
+	tre->dword[0] = lower_32_bits(address);
+	tre->dword[1] = upper_32_bits(address);
+	tre->dword[2] = u32_encode_bits(sg_dma_len(sgl), TRE_DMA_LEN);
+	tre->dword[3] = u32_encode_bits(TRE_TYPE_DMA, TRE_FLAGS_TYPE);
+	if (direction == DMA_MEM_TO_DEV)
+		tre->dword[3] |= u32_encode_bits(1, TRE_FLAGS_IEOT);
+	else if (direction == DMA_DEV_TO_MEM)
+		tre->dword[3] |= u32_encode_bits(1, TRE_FLAGS_IEOB);
+
+	for (i = 0; i < tre_idx; i++)
+		dev_dbg(dev, "TRE:%d %x:%x:%x:%x\n", i, desc->tre[i].dword[0],
+			desc->tre[i].dword[1], desc->tre[i].dword[2], desc->tre[i].dword[3]);
+
+	return tre_idx;
+}
+
 /* copy tre into transfer ring */
 static struct dma_async_tx_descriptor *
 gpi_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl,
@@ -1841,11 +1930,17 @@ gpi_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl,
 		return NULL;
 
 	/* create TREs for xfer */
-	if (gchan->protocol == QCOM_GPI_SPI) {
+	switch (gchan->protocol) {
+	case QCOM_GPI_SPI:
 		i = gpi_create_spi_tre(gchan, gpi_desc, sgl, direction);
-	} else if (gchan->protocol == QCOM_GPI_I2C) {
+		break;
+	case QCOM_GPI_I2C:
 		i = gpi_create_i2c_tre(gchan, gpi_desc, sgl, direction, flags);
-	} else {
+		break;
+	case QCOM_GPI_UART:
+		i = gpi_create_serial_tre(gchan, gpi_desc, sgl, direction);
+		break;
+	default:
 		dev_err(dev, "invalid peripheral: %d\n", gchan->protocol);
 		kfree(gpi_desc);
 		return NULL;
diff --git a/include/linux/dma/qcom-gpi-dma.h b/include/linux/dma/qcom-gpi-dma.h
index 332be28427e4..ba42e8383391 100644
--- a/include/linux/dma/qcom-gpi-dma.h
+++ b/include/linux/dma/qcom-gpi-dma.h
@@ -83,4 +83,37 @@ struct gpi_i2c_config {
 	bool multi_msg;
 };
 
+/**
+ * struct gpi_serial_config - serial config for peripheral
+ *
+ * @rx_stale_timeout: serial rx stale timeout
+ * @clk_div: source clock divider
+ * @clk_src: serial clock
+ * @stop_bit_len: stop bit length
+ * @char_size: UART character size
+ * @rfr_level: auto rfr level for FIFO threshold
+ * @parity: set parity type
+ * @opcode: go TRE opcode for tx and rx
+ * @hw_flow_ctrl_en: hardware flow control enable when set
+ * @set_tx_config: set tx channel peripheral config
+ * @set_rx_config: set rx channel peripheral config
+ * @packing: set packing config
+ * @loopback_en: serial loopback enable when set
+ */
+struct gpi_serial_config {
+	u32 rx_stale_timeout;
+	u32 clk_div;
+	u32 clk_src;
+	u8 stop_bit_len;
+	u8 char_size;
+	u8 rfr_level;
+	u8 parity;
+	u8 opcode;
+	bool hw_flow_ctrl_en;
+	bool set_tx_config;
+	bool set_rx_config;
+	bool packing;
+	bool loopback_en;
+};
+
 #endif /* QCOM_GPI_DMA_H */

-- 
2.34.1


^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH 2/4] dmaengine: qcom: gpi: Calculate UART TRE count per direction
  2026-10-09  6:57 [PATCH 0/4] tty: serial: qcom_geni_serial: Add GPI DMA support Naresh Maramaina
  2026-10-09  6:57 ` [PATCH 1/4] dmaengine: qcom: gpi: Add UART transfer support Naresh Maramaina
@ 2026-10-09  6:57 ` Naresh Maramaina
  2026-10-09  6:57 ` [PATCH 3/4] dmaengine: qcom: gpi: Ignore cancelled transfer-completion events Naresh Maramaina
  2026-10-09  6:57 ` [PATCH 4/4] tty: serial: qcom_geni_serial: Add GPI DMA support Naresh Maramaina
  3 siblings, 0 replies; 6+ messages in thread
From: Naresh Maramaina @ 2026-10-09  6:57 UTC (permalink / raw)
  To: konrad.dybcio, Vinod Koul, Frank Li, Bjorn Andersson,
	Konrad Dybcio, Abel Vesa, Greg Kroah-Hartman, Jiri Slaby
  Cc: linux-arm-msm, dmaengine, linux-kernel, linux-serial,
	Mukesh Kumar Savaliya, Aniket Randive, chandana.chiluveru,
	jyothi.seerapu, praveen.talari, Naresh Maramaina

The common TRE-count logic assumes RX transfers need only one TRE and
uses a single configuration flag. That matches SPI and I2C, but not
UART: UART RX may need an extra CONFIG0 TRE, and UART RX and TX track
configuration updates independently.

Move the TRE-count calculation into a protocol-specific helper. Keep
the existing SPI and I2C behavior, and use the UART RX or TX
configuration flag for UART transfers.

Signed-off-by: Naresh Maramaina <naresh.maramaina@oss.qualcomm.com>
---
 drivers/dma/qcom/gpi.c | 40 +++++++++++++++++++++++++++++++++-------
 1 file changed, 33 insertions(+), 7 deletions(-)

diff --git a/drivers/dma/qcom/gpi.c b/drivers/dma/qcom/gpi.c
index fb26a286e5df..f72b80bf5553 100644
--- a/drivers/dma/qcom/gpi.c
+++ b/drivers/dma/qcom/gpi.c
@@ -1882,6 +1882,35 @@ gpi_create_serial_tre(struct gchan *chan, struct gpi_desc *desc,
 	return tre_idx;
 }
 
+/* Calculate the number of TREs required for this transfer. */
+static u32 gpi_get_nr_tre(struct gchan *gchan,
+			  enum dma_transfer_direction direction)
+{
+	const struct gpi_serial_config *serial;
+	u32 nr_tre = MAX_TRE;
+	u8 set_config = true;
+
+	switch (gchan->protocol) {
+	case QCOM_GPI_SPI:
+	case QCOM_GPI_I2C:
+		set_config = *(u32 *)gchan->config;
+
+		if (direction == DMA_DEV_TO_MEM)
+			return 1;
+		break;
+	case QCOM_GPI_UART:
+		serial = gchan->config;
+		set_config = (direction == DMA_DEV_TO_MEM) ?
+					  serial->set_rx_config : serial->set_tx_config;
+		break;
+	}
+
+	if (!set_config)
+		nr_tre -= 1;
+
+	return nr_tre;
+}
+
 /* copy tre into transfer ring */
 static struct dma_async_tx_descriptor *
 gpi_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl,
@@ -1894,7 +1923,6 @@ gpi_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl,
 	struct gpi_ring *ch_ring = &gchan->ch_ring;
 	struct gpi_desc *gpi_desc;
 	u32 nr, nr_tre = 0;
-	u8 set_config;
 	int i;
 
 	gpii->ieob_set = false;
@@ -1908,12 +1936,10 @@ gpi_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl,
 		return NULL;
 	}
 
-	nr_tre = 3;
-	set_config = *(u32 *)gchan->config;
-	if (!set_config)
-		nr_tre = 2;
-	if (direction == DMA_DEV_TO_MEM) /* rx */
-		nr_tre = 1;
+	if (!gchan->config)
+		return NULL;
+
+	nr_tre = gpi_get_nr_tre(gchan, direction);
 
 	/* calculate # of elements required & available */
 	nr = gpi_ring_num_elements_avail(ch_ring);

-- 
2.34.1


^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH 3/4] dmaengine: qcom: gpi: Ignore cancelled transfer-completion events
  2026-10-09  6:57 [PATCH 0/4] tty: serial: qcom_geni_serial: Add GPI DMA support Naresh Maramaina
  2026-10-09  6:57 ` [PATCH 1/4] dmaengine: qcom: gpi: Add UART transfer support Naresh Maramaina
  2026-10-09  6:57 ` [PATCH 2/4] dmaengine: qcom: gpi: Calculate UART TRE count per direction Naresh Maramaina
@ 2026-10-09  6:57 ` Naresh Maramaina
  2026-10-09  6:57 ` [PATCH 4/4] tty: serial: qcom_geni_serial: Add GPI DMA support Naresh Maramaina
  3 siblings, 0 replies; 6+ messages in thread
From: Naresh Maramaina @ 2026-10-09  6:57 UTC (permalink / raw)
  To: konrad.dybcio, Vinod Koul, Frank Li, Bjorn Andersson,
	Konrad Dybcio, Abel Vesa, Greg Kroah-Hartman, Jiri Slaby
  Cc: linux-arm-msm, dmaengine, linux-kernel, linux-serial,
	Mukesh Kumar Savaliya, Aniket Randive, chandana.chiluveru,
	jyothi.seerapu, praveen.talari, Naresh Maramaina

Channel cancellation can generate a transfer-completion event marked
with the cancellation status bit. If the event is processed after
the channel has left ACTIVE_STATE, it triggers a false error report.

Ignore transfer-completion events marked as cancellation events when
the channel is no longer active. Continue to report all other
unexpected completion events received on inactive channels.

Signed-off-by: Naresh Maramaina <naresh.maramaina@oss.qualcomm.com>
---
 drivers/dma/qcom/gpi.c | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)

diff --git a/drivers/dma/qcom/gpi.c b/drivers/dma/qcom/gpi.c
index f72b80bf5553..7e1fa8d24902 100644
--- a/drivers/dma/qcom/gpi.c
+++ b/drivers/dma/qcom/gpi.c
@@ -22,6 +22,8 @@
 #define TRE_TYPE_GO		0x20
 #define TRE_TYPE_CONFIG0	0x22
 
+#define GPI_COMPL_STATUS_CANCELLED	BIT(14)
+
 /* TRE flags */
 #define TRE_FLAGS_CHAIN		BIT(0)
 #define TRE_FLAGS_IEOB		BIT(8)
@@ -1022,7 +1024,15 @@ static void gpi_process_xfer_compl_event(struct gchan *gchan,
 
 	/* only process events on active channel */
 	if (unlikely(gchan->pm_state != ACTIVE_STATE)) {
-		dev_err(gpii->gpi_dev->dev, "skipping processing event because ch @ %s state\n",
+		/*
+		 * A channel cancel may generate a transfer-completion event after
+		 * the channel has left ACTIVE_STATE. Ignore such events to avoid
+		 * reporting a spurious error.
+		 */
+		if (compl_event->status & GPI_COMPL_STATUS_CANCELLED)
+			return;
+
+		dev_err(gpii->gpi_dev->dev, "skipping processing xfer completion event because ch @ %s state\n",
 			TO_GPI_PM_STR(gchan->pm_state));
 		return;
 	}

-- 
2.34.1


^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH 4/4] tty: serial: qcom_geni_serial: Add GPI DMA support
  2026-10-09  6:57 [PATCH 0/4] tty: serial: qcom_geni_serial: Add GPI DMA support Naresh Maramaina
                   ` (2 preceding siblings ...)
  2026-10-09  6:57 ` [PATCH 3/4] dmaengine: qcom: gpi: Ignore cancelled transfer-completion events Naresh Maramaina
@ 2026-10-09  6:57 ` Naresh Maramaina
  2026-10-10  1:11   ` kernel test robot
  3 siblings, 1 reply; 6+ messages in thread
From: Naresh Maramaina @ 2026-10-09  6:57 UTC (permalink / raw)
  To: konrad.dybcio, Vinod Koul, Frank Li, Bjorn Andersson,
	Konrad Dybcio, Abel Vesa, Greg Kroah-Hartman, Jiri Slaby
  Cc: linux-arm-msm, dmaengine, linux-kernel, linux-serial,
	Mukesh Kumar Savaliya, Aniket Randive, chandana.chiluveru,
	jyothi.seerapu, praveen.talari, Naresh Maramaina

Some QUP serial engines are configured to operate in GPI DMA mode,
while the UART driver currently supports only FIFO and SE DMA
transfers. Add support for UARTs operating through the DMAengine GPI
interface.
 
Allow UART serial engines to remain in GPI DMA mode during firmware
loading instead of forcing FIFO mode. Detect GPI DMA operation during
port setup and initialize the DMA channels, workqueues and RX buffers
required for GPI transfers.
 
Submit TX and RX transfers through DMAengine when operating in GPI
mode, and route transfer cancellation and shutdown through the GPI DMA
channels. Defer these operations to workqueues since the DMAengine path
may sleep while waiting on mutexes or timeouts, whereas UART transfer
operations can be invoked with the port spinlock held.

Pass clock source, clock divider, character format, parity,
flow-control settings, RX stale timeout, FIFO threshold and other
UART configuration parameters to the GPI DMA driver through the
UART peripheral configuration structure. Start RX only after this
configuration is available so the DMA engine can program the
corresponding CONFIG0 TRE.

Support 2-wire UART TX and RX transfers in GPI DMA mode. GPI DMA
support for CTS/RTS flow control is outside the scope of this change.

Keep the existing FIFO and SE DMA paths unchanged.

Signed-off-by: Naresh Maramaina <naresh.maramaina@oss.qualcomm.com>
---
 drivers/soc/qcom/qcom-geni-se.c       |   4 -
 drivers/tty/serial/qcom_geni_serial.c | 576 ++++++++++++++++++++++++++++++++--
 2 files changed, 557 insertions(+), 23 deletions(-)

diff --git a/drivers/soc/qcom/qcom-geni-se.c b/drivers/soc/qcom/qcom-geni-se.c
index 074087c592cc..2e5ba93e1d7e 100644
--- a/drivers/soc/qcom/qcom-geni-se.c
+++ b/drivers/soc/qcom/qcom-geni-se.c
@@ -1582,10 +1582,6 @@ int geni_load_se_firmware(struct geni_se *se, enum geni_se_protocol_type protoco
 	if (of_property_read_bool(se->dev->of_node, "qcom,enable-gsi-dma"))
 		mode = GENI_GPI_DMA;
 
-	/* GSI mode is not supported by the UART driver; therefore, setting FIFO mode */
-	if (protocol == GENI_SE_UART)
-		mode = GENI_SE_FIFO;
-
 	ret = request_firmware(&fw, fw_name, se->dev);
 	if (ret) {
 		if (ret == -ENOENT)
diff --git a/drivers/tty/serial/qcom_geni_serial.c b/drivers/tty/serial/qcom_geni_serial.c
index 4a6f526340b8..eb66194a7f64 100644
--- a/drivers/tty/serial/qcom_geni_serial.c
+++ b/drivers/tty/serial/qcom_geni_serial.c
@@ -12,7 +12,9 @@
 
 #include <linux/clk.h>
 #include <linux/console.h>
+#include <linux/dmaengine.h>
 #include <linux/dma-mapping.h>
+#include <linux/dma/qcom-gpi-dma.h>
 #include <linux/io.h>
 #include <linux/iopoll.h>
 #include <linux/irq.h>
@@ -69,6 +71,16 @@
 #define PAR_ODD				0x01
 #define PAR_SPACE			0x10
 
+/* UART GPI mode parity flags */
+#define GPI_PAR_EVEN		0x0
+#define GPI_PAR_ODD		0x1
+#define GPI_PAR_SPACE		0x2
+#define GPI_PAR_MARK		0x3
+#define GPI_PAR_NONE		0x4
+
+#define GPI_START_TRANSFER	1
+#define GPI_CHAR_SIZE_OFFSET	5
+
 /* SE_UART_MANUAL_RFR register fields */
 #define UART_MANUAL_RFR_EN		BIT(31)
 #define UART_RFR_NOT_READY		BIT(1)
@@ -106,6 +118,8 @@
 #define BYTES_PER_FIFO_WORD		4U
 
 #define DMA_RX_BUF_SIZE		2048
+#define NUM_GPI_RX_BUFFERS	4
+#define INC_WRAP_RX_INDEX(index) (((index) + 1) % NUM_GPI_RX_BUFFERS)
 
 static DEFINE_IDA(port_ida);
 
@@ -129,6 +143,26 @@ struct qcom_geni_private_data {
 	unsigned int write_cached_bytes_cnt;
 };
 
+struct qcom_geni_serial_gpi {
+	struct gpi_serial_config peripheral;
+	struct qcom_geni_serial_port *port;
+	struct work_struct tx_cancel_work;
+	struct work_struct tx_xfer_work;
+	struct work_struct rx_cancel_work;
+	struct work_struct rx_xfer_work;
+	struct workqueue_struct *tx_wq;
+	struct workqueue_struct *rx_wq;
+	struct completion tx_xfer_completion;
+	struct dma_chan *tx_chan;
+	struct dma_chan *rx_chan;
+	dma_addr_t rx_dma_addr[NUM_GPI_RX_BUFFERS];
+	void *rx_buf[NUM_GPI_RX_BUFFERS];
+	u32 xmit_size;
+	u8 rx_done_index;
+	unsigned int baud;
+	bool tx_cancel_pending;
+};
+
 struct qcom_geni_serial_port {
 	struct uart_port uport;
 	struct geni_se se;
@@ -157,6 +191,9 @@ struct qcom_geni_serial_port {
 	struct qcom_geni_private_data private_data;
 	const struct qcom_geni_device_data *dev_data;
 	struct notifier_block panic_nb;
+
+	struct qcom_geni_serial_gpi *gpi;
+	bool gpi_mode;
 };
 
 static const struct uart_ops qcom_geni_console_pops;
@@ -170,6 +207,8 @@ static void qcom_geni_serial_cancel_tx_cmd(struct uart_port *uport);
 static int qcom_geni_serial_port_setup(struct uart_port *uport);
 static void qcom_geni_serial_start_tx_fifo(struct uart_port *uport);
 static void qcom_geni_serial_resume_tx(struct uart_port *uport);
+static void
+qcom_geni_serial_gpi_rx_callback_result(void *cb, const struct dmaengine_result *result);
 
 static inline struct qcom_geni_serial_port *to_dev_port(struct uart_port *uport)
 {
@@ -789,6 +828,11 @@ static void handle_rx_uart(struct uart_port *uport, u32 bytes)
 
 static unsigned int qcom_geni_serial_tx_empty(struct uart_port *uport)
 {
+	struct qcom_geni_serial_port *port = to_dev_port(uport);
+
+	if (port->gpi_mode)
+		return !port->tx_dma_addr ? TIOCSER_TEMT : 0;
+
 	return !readl(uport->membase + SE_GENI_TX_FIFO_STATUS);
 }
 
@@ -805,6 +849,17 @@ static void qcom_geni_serial_stop_tx_dma(struct uart_port *uport)
 {
 	struct qcom_geni_serial_port *port = to_dev_port(uport);
 
+	if (port->gpi_mode && port->tx_dma_addr) {
+		/*
+		 * Allow tx_xfer_work to finish. Buffer cleanup is deferred until
+		 * the DMA channel has been terminated.
+		 */
+		port->gpi->tx_cancel_pending = true;
+		complete(&port->gpi->tx_xfer_completion);
+		queue_work(port->gpi->tx_wq, &port->gpi->tx_cancel_work);
+		return;
+	}
+
 	if (qcom_geni_serial_main_active(uport))
 		__qcom_geni_serial_cancel_tx_cmd(uport);
 
@@ -836,6 +891,11 @@ static void qcom_geni_serial_start_tx_dma(struct uart_port *uport)
 	if (kfifo_is_empty(&tport->xmit_fifo))
 		return;
 
+	if (port->gpi_mode) {
+		queue_work(port->gpi->tx_wq, &port->gpi->tx_xfer_work);
+		return;
+	}
+
 	xmit_size = kfifo_out_linear_ptr(&tport->xmit_fifo, &tail,
 			UART_XMIT_SIZE);
 
@@ -996,6 +1056,11 @@ static void qcom_geni_serial_stop_rx_dma(struct uart_port *uport)
 	if (!qcom_geni_serial_secondary_active(uport))
 		return;
 
+	if (port->gpi_mode) {
+		queue_work(port->gpi->rx_wq, &port->gpi->rx_cancel_work);
+		return;
+	}
+
 	geni_se_cancel_s_cmd(&port->se);
 	done = qcom_geni_serial_poll_bit(uport, SE_DMA_RX_IRQ_STAT,
 			RX_EOT, true);
@@ -1021,6 +1086,11 @@ static void qcom_geni_serial_start_rx_dma(struct uart_port *uport)
 	if (qcom_geni_serial_secondary_active(uport))
 		qcom_geni_serial_stop_rx_dma(uport);
 
+	if (port->gpi_mode) {
+		queue_work(port->gpi->rx_wq, &port->gpi->rx_xfer_work);
+		return;
+	}
+
 	geni_se_setup_s_cmd(&port->se, UART_START_READ, UART_PARAM_RFR_OPEN);
 
 	if (!port->rx_dma_addr) {
@@ -1311,6 +1381,16 @@ static void qcom_geni_serial_shutdown(struct uart_port *uport)
 	/* Atomic console output queues tx_kick without taking the port lock. */
 	WRITE_ONCE(port->tx_kick_enabled, false);
 	irq_work_sync(&port->tx_kick);
+
+	if (port->gpi_mode) {
+		qcom_geni_serial_stop_tx_dma(uport);
+		flush_workqueue(port->gpi->tx_wq);
+		flush_workqueue(port->gpi->rx_wq);
+
+		pm_runtime_put_sync(uport->dev);
+		return;
+	}
+
 	disable_irq(uport->irq);
 
 	uart_port_lock_irq(uport);
@@ -1332,6 +1412,379 @@ static void qcom_geni_serial_flush_buffer_fifo(struct uart_port *uport)
 	qcom_geni_serial_cancel_tx_cmd(uport);
 }
 
+static void qcom_geni_serial_gpi_tx_unmap(struct qcom_geni_serial_port *port)
+{
+	struct qcom_geni_serial_gpi *gpi = port->gpi;
+
+	if (!port->tx_dma_addr)
+		return;
+
+	dma_unmap_single(port->se.dev->parent, port->tx_dma_addr,
+			 gpi->xmit_size, DMA_TO_DEVICE);
+	port->tx_dma_addr = (dma_addr_t)NULL;
+}
+
+static void qcom_geni_serial_gpi_cancel_tx_work(struct work_struct *work)
+{
+	struct qcom_geni_serial_gpi *gpi = container_of(work, struct qcom_geni_serial_gpi,
+							tx_cancel_work);
+	struct qcom_geni_serial_port *port = gpi->port;
+
+	dmaengine_terminate_sync(gpi->tx_chan);
+	qcom_geni_serial_gpi_tx_unmap(port);
+}
+
+static void qcom_geni_serial_gpi_cancel_rx_work(struct work_struct *work)
+{
+	struct qcom_geni_serial_gpi *gpi = container_of(work, struct qcom_geni_serial_gpi,
+							rx_cancel_work);
+	dmaengine_terminate_sync(gpi->rx_chan);
+}
+
+static void qcom_geni_serial_gpi_rx_queue_dma_tre(struct qcom_geni_serial_port *port, u8 idx)
+{
+	struct qcom_geni_serial_gpi *gpi = port->gpi;
+	struct uart_port *uport = &port->uport;
+	struct dma_async_tx_descriptor *desc;
+	unsigned long flags = DMA_PREP_INTERRUPT | DMA_CTRL_ACK;
+	dma_cookie_t rx_cookie;
+
+	desc = dmaengine_prep_slave_single(gpi->rx_chan, gpi->rx_dma_addr[idx],
+					   UART_XMIT_SIZE, DMA_DEV_TO_MEM, flags);
+	if (!desc) {
+		dev_err(uport->dev, "Error setting up rx desc\n");
+		return;
+	}
+
+	desc->callback_result = qcom_geni_serial_gpi_rx_callback_result;
+	desc->callback_param = port;
+	rx_cookie = dmaengine_submit(desc);
+	if (dma_submit_error(rx_cookie)) {
+		dev_err(uport->dev, "%s: dmaengine_submit failed: %d\n", __func__, rx_cookie);
+		dmaengine_terminate_sync(gpi->rx_chan);
+		return;
+	}
+	dma_async_issue_pending(gpi->rx_chan);
+}
+
+static void
+qcom_geni_serial_gpi_rx_callback_result(void *cb, const struct dmaengine_result *result)
+{
+	struct qcom_geni_serial_port *port = cb;
+	struct uart_port *uport = &port->uport;
+	struct tty_port *tport = &uport->state->port;
+	size_t bytes = UART_XMIT_SIZE - result->residue;
+	u8 idx = port->gpi->rx_done_index;
+	int ret;
+
+	if (result->result != DMA_TRANS_NOERROR) {
+		dev_err(uport->dev, "rx dma xfer failed: %d\n", result->result);
+		qcom_geni_serial_stop_rx_dma(uport);
+		qcom_geni_serial_start_rx_dma(uport);
+		return;
+	}
+
+	if (bytes <= 0) {
+		port->gpi->rx_done_index = INC_WRAP_RX_INDEX(idx);
+		qcom_geni_serial_gpi_rx_queue_dma_tre(port, idx);
+		return;
+	}
+
+	dma_sync_single_for_cpu(port->se.dev->parent,
+				port->gpi->rx_dma_addr[idx],
+				UART_XMIT_SIZE, DMA_FROM_DEVICE);
+
+	ret = tty_insert_flip_string(tport, port->gpi->rx_buf[idx], bytes);
+	if (ret != bytes) {
+		dev_err(uport->dev, "%s: Unable to push data %d_bytes %zu\n",
+			__func__, ret, bytes);
+		WARN_ON_ONCE(1);
+	}
+
+	uport->icount.rx += ret;
+	tty_flip_buffer_push(tport);
+	dma_sync_single_for_device(port->se.dev->parent, port->gpi->rx_dma_addr[idx],
+				   UART_XMIT_SIZE, DMA_FROM_DEVICE);
+
+	port->gpi->rx_done_index = INC_WRAP_RX_INDEX(idx);
+	qcom_geni_serial_gpi_rx_queue_dma_tre(port, idx);
+}
+
+static void qcom_geni_serial_gpi_rx_xfer_work(struct work_struct *work)
+{
+	struct qcom_geni_serial_gpi *gpi = container_of(work, struct qcom_geni_serial_gpi,
+							rx_xfer_work);
+	struct qcom_geni_serial_port *port = gpi->port;
+	struct uart_port *uport = &port->uport;
+	struct dma_slave_config config = {};
+	int ret, i;
+
+	config.peripheral_config = &gpi->peripheral;
+	config.peripheral_size = sizeof(gpi->peripheral);
+
+	ret = dmaengine_slave_config(gpi->rx_chan, &config);
+	if (ret) {
+		dev_err(uport->dev, "rx dma config error: %d\n", ret);
+		return;
+	}
+
+	gpi->rx_done_index = 0;
+	for (i = 0; i < NUM_GPI_RX_BUFFERS; i++)
+		qcom_geni_serial_gpi_rx_queue_dma_tre(port, i);
+}
+
+static void
+qcom_geni_serial_gpi_tx_callback_result(void *cb, const struct dmaengine_result *result)
+{
+	struct qcom_geni_serial_port *port = cb;
+	struct uart_port *uport = &port->uport;
+	struct tty_port *tport = &uport->state->port;
+	unsigned long flags;
+
+	if (result->result != DMA_TRANS_NOERROR) {
+		dev_err(uport->dev, "tx dma xfer failed: %d\n", result->result);
+		qcom_geni_serial_stop_tx_dma(uport);
+		return;
+	}
+
+	uart_port_lock_irqsave(uport, &flags);
+	uart_xmit_advance(uport, port->gpi->xmit_size - result->residue);
+	complete(&port->gpi->tx_xfer_completion);
+
+	if (kfifo_len(&tport->xmit_fifo) < WAKEUP_CHARS)
+		uart_write_wakeup(uport);
+	uart_port_unlock_irqrestore(uport, flags);
+}
+
+#define UART_BITS_PER_BYTE		10	/* 8N1 */
+#define UART_TX_SAFETY_COEFF		10
+#define UART_TX_MIN_TIMEOUT_USEC	20000
+
+static unsigned long qcom_geni_serial_gpi_tx_timeout(struct qcom_geni_serial_gpi *gpi)
+{
+	u64 wire_time_usec;
+	u64 timeout_usec;
+
+	if (!gpi->xmit_size || WARN_ON_ONCE(!gpi->baud))
+		return 0;
+
+	wire_time_usec = mul_u64_u32_div((u64)gpi->xmit_size * UART_BITS_PER_BYTE,
+					 USEC_PER_SEC, gpi->baud);
+	timeout_usec = wire_time_usec * UART_TX_SAFETY_COEFF + UART_TX_MIN_TIMEOUT_USEC;
+
+	return usecs_to_jiffies(min_t(u64, timeout_usec, UINT_MAX));
+}
+
+static void qcom_geni_serial_gpi_tx_xfer_work(struct work_struct *work)
+{
+	struct qcom_geni_serial_gpi *gpi = container_of(work, struct qcom_geni_serial_gpi,
+							tx_xfer_work);
+	struct qcom_geni_serial_port *port = gpi->port;
+	struct uart_port *uport = &port->uport;
+	struct tty_port *tport = &uport->state->port;
+	struct dma_slave_config config = {};
+	struct dma_async_tx_descriptor *desc;
+	unsigned long flags = DMA_PREP_INTERRUPT | DMA_CTRL_ACK;
+	unsigned long time_left;
+	unsigned long timeout;
+	dma_cookie_t tx_cookie;
+	u8 *tail = NULL;
+	int ret;
+
+	gpi->xmit_size = kfifo_out_linear_ptr(&tport->xmit_fifo, &tail,
+					      UART_XMIT_SIZE);
+	if (!gpi->xmit_size)
+		return;
+
+	config.peripheral_config = &gpi->peripheral;
+	config.peripheral_size = sizeof(gpi->peripheral);
+
+	ret = dmaengine_slave_config(gpi->tx_chan, &config);
+	if (ret) {
+		dev_err(uport->dev, "tx dma config error: %d\n", ret);
+		return;
+	}
+
+	port->tx_dma_addr = dma_map_single(port->se.dev->parent, tail,
+					   gpi->xmit_size, DMA_TO_DEVICE);
+	ret = dma_mapping_error(port->se.dev->parent, port->tx_dma_addr);
+	if (ret) {
+		dev_err(uport->dev, "DMA mapping failure: %d\n", ret);
+		port->tx_dma_addr = (dma_addr_t)NULL;
+		return;
+	}
+
+	dma_sync_single_for_cpu(port->se.dev->parent, port->tx_dma_addr,
+				gpi->xmit_size, DMA_TO_DEVICE);
+	desc = dmaengine_prep_slave_single(gpi->tx_chan, port->tx_dma_addr,
+					   gpi->xmit_size, DMA_MEM_TO_DEV, flags);
+	if (!desc) {
+		dev_err(uport->dev, "Error setting up tx desc\n");
+		goto err;
+	}
+
+	desc->callback_result = qcom_geni_serial_gpi_tx_callback_result;
+	desc->callback_param = port;
+	tx_cookie = dmaengine_submit(desc);
+	if (dma_submit_error(tx_cookie)) {
+		dev_err(uport->dev, "%s: dmaengine_submit failed: %d\n", __func__, tx_cookie);
+		dmaengine_terminate_sync(gpi->tx_chan);
+		goto err;
+	}
+	reinit_completion(&port->gpi->tx_xfer_completion);
+	dma_async_issue_pending(gpi->tx_chan);
+
+	gpi->peripheral.set_tx_config = false;
+
+	timeout = qcom_geni_serial_gpi_tx_timeout(gpi);
+
+	time_left = wait_for_completion_timeout(&port->gpi->tx_xfer_completion, timeout);
+	if (!time_left) {
+		dev_err(uport->dev, "Error: gpi tx timedout:%ld\n", timeout);
+		dmaengine_terminate_sync(gpi->tx_chan);
+		goto err;
+	}
+
+	/*
+	 * tx_cancel_work will terminate the channel and release the DMA
+	 * mapping. Return early to avoid racing with that cleanup.
+	 */
+	if (gpi->tx_cancel_pending) {
+		gpi->tx_cancel_pending = false;
+		return;
+	}
+
+err:
+	qcom_geni_serial_gpi_tx_unmap(port);
+}
+
+static void qcom_geni_serial_unprepare_gpi_rx_dma(struct qcom_geni_serial_port *port)
+{
+	struct qcom_geni_serial_gpi *gpi = port->gpi;
+	int i;
+
+	for (i = 0; i < NUM_GPI_RX_BUFFERS; i++) {
+		if (gpi->rx_dma_addr[i]) {
+			dma_unmap_single(port->se.dev->parent, gpi->rx_dma_addr[i],
+					 UART_XMIT_SIZE, DMA_FROM_DEVICE);
+			gpi->rx_dma_addr[i] = (dma_addr_t)NULL;
+		}
+	}
+}
+
+static int qcom_geni_serial_prepare_gpi_rx_dma(struct qcom_geni_serial_port *port)
+{
+	struct qcom_geni_serial_gpi *gpi = port->gpi;
+	struct uart_port *uport = &port->uport;
+	int i, ret;
+
+	for (i = 0; i < NUM_GPI_RX_BUFFERS; i++) {
+		gpi->rx_buf[i] = devm_kzalloc(uport->dev, UART_XMIT_SIZE, GFP_KERNEL);
+		if (!gpi->rx_buf[i])
+			return dev_err_probe(uport->dev, -ENOMEM, "RX buffer %d\n", i);
+
+		gpi->rx_dma_addr[i] = dma_map_single(port->se.dev->parent, gpi->rx_buf[i],
+						     UART_XMIT_SIZE, DMA_FROM_DEVICE);
+		ret = dma_mapping_error(port->se.dev->parent, gpi->rx_dma_addr[i]);
+		if (ret) {
+			dev_err(uport->dev, "Failed to map gpi rx buffer[%d]\n", i);
+			return ret;
+		}
+	}
+	return 0;
+}
+
+static void qcom_geni_serial_release_gpi_dma(struct qcom_geni_serial_port *port)
+{
+	if (port->gpi->rx_chan) {
+		dma_release_channel(port->gpi->rx_chan);
+		port->gpi->rx_chan = NULL;
+	}
+
+	if (port->gpi->tx_chan) {
+		dma_release_channel(port->gpi->tx_chan);
+		port->gpi->tx_chan = NULL;
+	}
+}
+
+static int qcom_geni_serial_setup_gpi_dma(struct qcom_geni_serial_port *port)
+{
+	struct qcom_geni_serial_gpi *gpi = port->gpi;
+	int ret = -EINVAL;
+
+	gpi->tx_chan = dma_request_chan(port->se.dev, "tx");
+	if (IS_ERR(gpi->tx_chan)) {
+		dev_err(port->se.dev, "Failed to get tx DMA ch\n");
+		return ret;
+	}
+
+	gpi->rx_chan = dma_request_chan(port->se.dev, "rx");
+	if (IS_ERR(gpi->rx_chan)) {
+		dev_err(port->se.dev, "Failed to get rx DMA ch\n");
+		dma_release_channel(gpi->tx_chan);
+		gpi->tx_chan = NULL;
+		return ret;
+	}
+
+	return 0;
+}
+
+static int qcom_geni_serial_gpi_init(struct qcom_geni_serial_port *port)
+{
+	struct uart_port *uport = &port->uport;
+	int ret;
+
+	port->gpi = devm_kzalloc(uport->dev, sizeof(*port->gpi), GFP_KERNEL);
+	if (!port->gpi)
+		return -ENOMEM;
+
+	ret = qcom_geni_serial_prepare_gpi_rx_dma(port);
+	if (ret)
+		return ret;
+
+	port->gpi->port = port;
+	ret = qcom_geni_serial_setup_gpi_dma(port);
+	if (ret)
+		goto err_unprepare_gpi_rx_dma;
+	/*
+	 * Defer GPI DMA operations to workqueues as the DMAengine path may
+	 * sleep while waiting on mutexes or timeouts, whereas UART transfer
+	 * operations can be invoked with the port spinlock held.
+	 */
+	port->gpi->tx_wq = alloc_ordered_workqueue("%s_tx_wq", WQ_HIGHPRI,
+						   dev_name(uport->dev));
+	if (!port->gpi->tx_wq) {
+		ret = -ENOMEM;
+		goto err_release_gpi_dma;
+	}
+
+	port->gpi->rx_wq = alloc_ordered_workqueue("%s_rx_wq", WQ_HIGHPRI,
+						   dev_name(uport->dev));
+	if (!port->gpi->rx_wq) {
+		ret = -ENOMEM;
+		goto err_destroy_tx_wq;
+	}
+
+	INIT_WORK(&port->gpi->tx_xfer_work, qcom_geni_serial_gpi_tx_xfer_work);
+	INIT_WORK(&port->gpi->tx_cancel_work, qcom_geni_serial_gpi_cancel_tx_work);
+	INIT_WORK(&port->gpi->rx_xfer_work, qcom_geni_serial_gpi_rx_xfer_work);
+	INIT_WORK(&port->gpi->rx_cancel_work, qcom_geni_serial_gpi_cancel_rx_work);
+	init_completion(&port->gpi->tx_xfer_completion);
+
+	/* Prevent spurious interrupts during data transfers for gpi mode */
+	writel(0, uport->membase + SE_GENI_M_IRQ_EN);
+	writel(0, uport->membase + SE_GENI_S_IRQ_EN);
+	return 0;
+
+err_destroy_tx_wq:
+	destroy_workqueue(port->gpi->tx_wq);
+err_release_gpi_dma:
+	qcom_geni_serial_release_gpi_dma(port);
+err_unprepare_gpi_rx_dma:
+	qcom_geni_serial_unprepare_gpi_rx_dma(port);
+	return ret;
+}
+
 static int qcom_geni_serial_port_setup(struct uart_port *uport)
 {
 	struct qcom_geni_serial_port *port = to_dev_port(uport);
@@ -1352,13 +1805,26 @@ static int qcom_geni_serial_port_setup(struct uart_port *uport)
 		return -ENXIO;
 	}
 
+	port->gpi_mode = readl(uport->membase + GENI_IF_DISABLE_RO) & FIFO_IF_DISABLE;
+	if (port->gpi_mode) {
+		geni_se_select_mode(&port->se, GENI_GPI_DMA);
+		ret = qcom_geni_serial_gpi_init(port);
+		if (ret)
+			return ret;
+	}
+
 	qcom_geni_serial_stop_rx(uport);
 
 	ret = setup_fifos(port);
 	if (ret)
 		return ret;
 
-	writel(rxstale, uport->membase + SE_UART_RX_STALE_CNT);
+	if (port->gpi_mode) {
+		port->gpi->peripheral.rx_stale_timeout = rxstale;
+		port->gpi->peripheral.rfr_level = port->rx_fifo_depth - 2;
+	} else {
+		writel(rxstale, uport->membase + SE_UART_RX_STALE_CNT);
+	}
 
 	pin_swap = readl(uport->membase + SE_UART_IO_MACRO_CTRL);
 	if (port->rx_tx_swap) {
@@ -1379,10 +1845,12 @@ static int qcom_geni_serial_port_setup(struct uart_port *uport)
 	 */
 	if (uart_console(uport))
 		qcom_geni_serial_poll_tx_done(uport);
-	geni_se_config_packing(&port->se, BITS_PER_BYTE, BYTES_PER_FIFO_WORD,
-			       false, true, true);
-	geni_se_init(&port->se, UART_RX_WM, port->rx_fifo_depth - 2);
-	geni_se_select_mode(&port->se, port->dev_data->mode);
+	if (!port->gpi_mode) {
+		geni_se_config_packing(&port->se, BITS_PER_BYTE, BYTES_PER_FIFO_WORD,
+				       false, true, true);
+		geni_se_init(&port->se, UART_RX_WM, port->rx_fifo_depth - 2);
+		geni_se_select_mode(&port->se, port->dev_data->mode);
+	}
 	port->setup = true;
 
 	return 0;
@@ -1415,11 +1883,23 @@ static int qcom_geni_serial_startup(struct uart_port *uport)
 	if (uart_console(uport))
 		tport->closing_wait = ASYNC_CLOSING_WAIT_NONE;
 
-	uart_port_lock_irq(uport);
-	qcom_geni_serial_start_rx(uport);
-	uart_port_unlock_irq(uport);
+	/*
+	 * FIFO and SE-DMA modes apply UART configuration directly to
+	 * hardware registers, so configuration updates take effect
+	 * without restarting RX.
+	 *
+	 * GPI mode programs UART configuration through a CFG TRE
+	 * submitted with an RX transfer. Defer RX startup until the
+	 * initial configuration can be queued.
+	 */
+	if (!port->gpi_mode) {
+		uart_port_lock_irq(uport);
+		qcom_geni_serial_start_rx(uport);
+		uart_port_unlock_irq(uport);
+
+		enable_irq(uport->irq);
+	}
 
-	enable_irq(uport->irq);
 	WRITE_ONCE(port->tx_kick_enabled, true);
 
 	return 0;
@@ -1461,6 +1941,10 @@ static int geni_serial_set_rate(struct geni_se *se, unsigned long baud)
 
 	uport->uartclk = clk_rate;
 	port->clk_rate = clk_rate;
+	if (port->gpi_mode) {
+		port->gpi->peripheral.clk_div = clk_div;
+		port->gpi->peripheral.clk_src = clk_idx;
+	}
 	dev_pm_opp_set_rate(uport->dev, clk_rate);
 	ser_clk_cfg = SER_CLK_EN;
 	ser_clk_cfg |= clk_div << CLK_DIV_SHFT;
@@ -1474,6 +1958,9 @@ static int geni_serial_set_rate(struct geni_se *se, unsigned long baud)
 	port->se.icc_paths[CPU_TO_GENI].avg_bw = Bps_to_icc(baud);
 	geni_icc_set_bw(&port->se);
 
+	if (port->gpi_mode)
+		return 0;
+
 	writel(ser_clk_cfg, uport->membase + GENI_SER_M_CLK_CFG);
 	writel(ser_clk_cfg, uport->membase + GENI_SER_S_CLK_CFG);
 	/* Configure clock selection register with the selected clock index */
@@ -1494,8 +1981,22 @@ static void qcom_geni_serial_set_termios(struct uart_port *uport,
 	u32 rx_trans_cfg;
 	u32 rx_parity_cfg;
 	u32 stop_bit_len;
+	u8 gpi_parity;
 	int ret = 0;
 
+	/*
+	 * Drain pending GPI transfers before updating the UART
+	 * configuration. Unlike FIFO and SE-DMA modes, configuration
+	 * changes are not applied directly to hardware registers in
+	 * GPI mode. Stop the active RX transfer so the next RX request
+	 * can submit an updated CFG TRE.
+	 */
+	if (port->gpi_mode) {
+		flush_workqueue(port->gpi->tx_wq);
+		qcom_geni_serial_stop_rx(uport);
+		flush_workqueue(port->gpi->rx_wq);
+	}
+
 	/* baud rate */
 	baud = uart_get_baud_rate(uport, termios, old, 300, 8000000);
 
@@ -1516,18 +2017,22 @@ static void qcom_geni_serial_set_termios(struct uart_port *uport,
 		if (termios->c_cflag & PARODD) {
 			tx_parity_cfg |= PAR_ODD;
 			rx_parity_cfg |= PAR_ODD;
+			gpi_parity = GPI_PAR_ODD;
 		} else if (termios->c_cflag & CMSPAR) {
 			tx_parity_cfg |= PAR_SPACE;
 			rx_parity_cfg |= PAR_SPACE;
+			gpi_parity = GPI_PAR_SPACE;
 		} else {
 			tx_parity_cfg |= PAR_EVEN;
 			rx_parity_cfg |= PAR_EVEN;
+			gpi_parity = GPI_PAR_EVEN;
 		}
 	} else {
 		tx_trans_cfg &= ~UART_TX_PAR_EN;
 		rx_trans_cfg &= ~UART_RX_PAR_EN;
 		tx_parity_cfg &= ~PAR_CALC_EN;
 		rx_parity_cfg &= ~PAR_CALC_EN;
+		gpi_parity = GPI_PAR_NONE;
 	}
 
 	/* bits per char */
@@ -1570,16 +2075,41 @@ static void qcom_geni_serial_set_termios(struct uart_port *uport,
 		WRITE_ONCE(port->poll_timeout_us, timeout);
 	}
 
-	if (!uart_console(uport))
-		writel(port->loopback,
-				uport->membase + SE_UART_LOOPBACK_CFG);
-	writel(tx_trans_cfg, uport->membase + SE_UART_TX_TRANS_CFG);
-	writel(tx_parity_cfg, uport->membase + SE_UART_TX_PARITY_CFG);
-	writel(rx_trans_cfg, uport->membase + SE_UART_RX_TRANS_CFG);
-	writel(rx_parity_cfg, uport->membase + SE_UART_RX_PARITY_CFG);
-	writel(bits_per_char, uport->membase + SE_UART_TX_WORD_LEN);
-	writel(bits_per_char, uport->membase + SE_UART_RX_WORD_LEN);
-	writel(stop_bit_len, uport->membase + SE_UART_TX_STOP_BIT_LEN);
+	if (port->gpi_mode) {
+		struct gpi_serial_config *peripheral = &port->gpi->peripheral;
+
+		port->gpi->baud = baud;
+		peripheral->char_size = bits_per_char - GPI_CHAR_SIZE_OFFSET;
+		peripheral->loopback_en = !!port->loopback;
+		peripheral->packing = true;
+		peripheral->stop_bit_len = stop_bit_len;
+		peripheral->opcode = GPI_START_TRANSFER;
+		peripheral->parity = gpi_parity;
+		peripheral->hw_flow_ctrl_en = !(tx_trans_cfg & UART_CTS_MASK);
+		peripheral->set_rx_config = true;
+		peripheral->set_tx_config = true;
+	} else {
+		if (!uart_console(uport))
+			writel(port->loopback, uport->membase + SE_UART_LOOPBACK_CFG);
+		writel(tx_trans_cfg, uport->membase + SE_UART_TX_TRANS_CFG);
+		writel(tx_parity_cfg, uport->membase + SE_UART_TX_PARITY_CFG);
+		writel(rx_trans_cfg, uport->membase + SE_UART_RX_TRANS_CFG);
+		writel(rx_parity_cfg, uport->membase + SE_UART_RX_PARITY_CFG);
+		writel(bits_per_char, uport->membase + SE_UART_TX_WORD_LEN);
+		writel(bits_per_char, uport->membase + SE_UART_RX_WORD_LEN);
+		writel(stop_bit_len, uport->membase + SE_UART_TX_STOP_BIT_LEN);
+	}
+
+	/*
+	 * Re-arm RX to submit the updated CFG TRE. FIFO and SE-DMA
+	 * modes apply configuration changes immediately through
+	 * register updates, whereas GPI mode requires a new RX
+	 * transfer for the updated settings to reach hardware.
+	 */
+	if (port->gpi_mode) {
+		qcom_geni_serial_start_rx(uport);
+		flush_workqueue(port->gpi->rx_wq);
+	}
 
 	trace_geni_serial_set_termios(uport->dev, baud, bits_per_char,
 				      tx_trans_cfg, tx_parity_cfg, rx_trans_cfg,
@@ -2035,6 +2565,14 @@ static void qcom_geni_serial_remove(struct platform_device *pdev)
 
 	WRITE_ONCE(port->tx_kick_enabled, false);
 	irq_work_sync(&port->tx_kick);
+
+	if (port->gpi_mode && port->setup) {
+		destroy_workqueue(port->gpi->tx_wq);
+		destroy_workqueue(port->gpi->rx_wq);
+		qcom_geni_serial_release_gpi_dma(port);
+		qcom_geni_serial_unprepare_gpi_rx_dma(port);
+	}
+
 	dev_pm_clear_wake_irq(&pdev->dev);
 	device_init_wakeup(&pdev->dev, false);
 	if (!port->dev_data->console)

-- 
2.34.1


^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH 4/4] tty: serial: qcom_geni_serial: Add GPI DMA support
  2026-10-09  6:57 ` [PATCH 4/4] tty: serial: qcom_geni_serial: Add GPI DMA support Naresh Maramaina
@ 2026-10-10  1:11   ` kernel test robot
  0 siblings, 0 replies; 6+ messages in thread
From: kernel test robot @ 2026-10-10  1:11 UTC (permalink / raw)
  To: Naresh Maramaina, konrad.dybcio, Vinod Koul, Frank Li,
	Bjorn Andersson, Konrad Dybcio, Abel Vesa, Greg Kroah-Hartman,
	Jiri Slaby
  Cc: oe-kbuild-all, linux-arm-msm, dmaengine, linux-kernel,
	linux-serial, Mukesh Kumar Savaliya, Aniket Randive,
	chandana.chiluveru, jyothi.seerapu, praveen.talari,
	Naresh Maramaina

Hi Naresh,

kernel test robot noticed the following build warnings:

[auto build test WARNING on aac26bee2287c88af5be5a5ff96d783b19a28790]

url:    https://github.com/intel-lab-lkp/linux/commits/Naresh-Maramaina/dmaengine-qcom-gpi-Add-UART-transfer-support/20261009-122753
base:   aac26bee2287c88af5be5a5ff96d783b19a28790
patch link:    https://lore.kernel.org/r/20261009-add-gpi-dma-support-for-uart-v1-4-c69e728df686%40oss.qualcomm.com
patch subject: [PATCH 4/4] tty: serial: qcom_geni_serial: Add GPI DMA support
config: arc-allmodconfig (https://download.01.org/0day-ci/archive/20261010/202610100800.CCtA9f8L-lkp@intel.com/config)
compiler: arc-linux-gcc (GCC) 16.1.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20261010/202610100800.CCtA9f8L-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202610100800.CCtA9f8L-lkp@intel.com/

All warnings (new ones prefixed by >>):

   drivers/tty/serial/qcom_geni_serial.c: In function 'qcom_geni_serial_gpi_tx_unmap':
>> drivers/tty/serial/qcom_geni_serial.c:1424:29: warning: cast from pointer to integer of different size [-Wpointer-to-int-cast]
    1424 |         port->tx_dma_addr = (dma_addr_t)NULL;
         |                             ^
   drivers/tty/serial/qcom_geni_serial.c: In function 'qcom_geni_serial_gpi_tx_xfer_work':
   drivers/tty/serial/qcom_geni_serial.c:1613:37: warning: cast from pointer to integer of different size [-Wpointer-to-int-cast]
    1613 |                 port->tx_dma_addr = (dma_addr_t)NULL;
         |                                     ^
   drivers/tty/serial/qcom_geni_serial.c: In function 'qcom_geni_serial_unprepare_gpi_rx_dma':
   drivers/tty/serial/qcom_geni_serial.c:1670:47: warning: cast from pointer to integer of different size [-Wpointer-to-int-cast]
    1670 |                         gpi->rx_dma_addr[i] = (dma_addr_t)NULL;
         |                                               ^


vim +1424 drivers/tty/serial/qcom_geni_serial.c

  1414	
  1415	static void qcom_geni_serial_gpi_tx_unmap(struct qcom_geni_serial_port *port)
  1416	{
  1417		struct qcom_geni_serial_gpi *gpi = port->gpi;
  1418	
  1419		if (!port->tx_dma_addr)
  1420			return;
  1421	
  1422		dma_unmap_single(port->se.dev->parent, port->tx_dma_addr,
  1423				 gpi->xmit_size, DMA_TO_DEVICE);
> 1424		port->tx_dma_addr = (dma_addr_t)NULL;
  1425	}
  1426	

--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2026-10-10  1:11 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-09  6:57 [PATCH 0/4] tty: serial: qcom_geni_serial: Add GPI DMA support Naresh Maramaina
2026-10-09  6:57 ` [PATCH 1/4] dmaengine: qcom: gpi: Add UART transfer support Naresh Maramaina
2026-10-09  6:57 ` [PATCH 2/4] dmaengine: qcom: gpi: Calculate UART TRE count per direction Naresh Maramaina
2026-10-09  6:57 ` [PATCH 3/4] dmaengine: qcom: gpi: Ignore cancelled transfer-completion events Naresh Maramaina
2026-10-09  6:57 ` [PATCH 4/4] tty: serial: qcom_geni_serial: Add GPI DMA support Naresh Maramaina
2026-10-10  1:11   ` kernel test robot

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®