* [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®