Replace the abort-only RX initialization with a proper stop sequence
that matches the Linux driver approach. The new
qcom_geni_serial_stop_rx_fifo function sends a CANCEL command instead
of immediately aborting, waits for completion, drains any remaining
FIFO data if S_RX_FIFO_LAST_EN is set, and only sends ABORT as a
fallback if the secondary sequencer remains active after cancellation.

Taken from 679aac5ead2f ("tty: serial: qcom_geni_serial: Fix RX cancel command 
failure")

Signed-off-by: Balaji Selvanathan <[email protected]>
---
Changes in v2:
- Replace the abort-only RX initialization with a proper stop sequence
  that matches the Linux driver approach.
---
 drivers/serial/serial_msm_geni.c | 63 ++++++++++++++++++++++++++++++++++++++--
 1 file changed, 61 insertions(+), 2 deletions(-)

diff --git a/drivers/serial/serial_msm_geni.c b/drivers/serial/serial_msm_geni.c
index 3dca581f68f..a580dee8c8a 100644
--- a/drivers/serial/serial_msm_geni.c
+++ b/drivers/serial/serial_msm_geni.c
@@ -362,11 +362,70 @@ static void qcom_geni_serial_abort_rx(struct udevice *dev)
        writel(FORCE_DEFAULT, priv->base + GENI_FORCE_DEFAULT_REG);
 }
 
+static void qcom_geni_serial_stop_rx_fifo(struct udevice *dev)
+{
+       struct msm_serial_data *priv = dev_get_priv(dev);
+       u32 irq_en;
+       u32 s_irq_status;
+       u32 status;
+       u32 rx_fifo_status;
+
+       /* Disable RX interrupts */
+       irq_en = readl(priv->base + SE_GENI_S_IRQ_EN);
+       irq_en &= ~(S_RX_FIFO_WATERMARK_EN | S_RX_FIFO_LAST_EN);
+       writel(irq_en, priv->base + SE_GENI_S_IRQ_EN);
+
+       irq_en = readl(priv->base + SE_GENI_M_IRQ_EN);
+       irq_en &= ~(M_RX_FIFO_WATERMARK_EN | M_RX_FIFO_LAST_EN);
+       writel(irq_en, priv->base + SE_GENI_M_IRQ_EN);
+
+       /* Check if secondary sequencer is active */
+       status = readl(priv->base + SE_GENI_STATUS);
+       if (!(status & S_GENI_CMD_ACTIVE))
+               return;
+
+       /* Send CANCEL command */
+       writel(S_GENI_CMD_CANCEL, priv->base + SE_GENI_S_CMD_CTRL_REG);
+
+       /* Poll for CANCEL completion */
+       qcom_geni_serial_poll_bit(dev, SE_GENI_S_CMD_CTRL_REG,
+                                 S_GENI_CMD_CANCEL, false);
+
+       /* Read IRQ status and check if S_RX_FIFO_LAST_EN is set */
+       s_irq_status = readl(priv->base + SE_GENI_S_IRQ_STATUS);
+
+       /* Flush/drain the RX FIFO if needed */
+       if (s_irq_status & S_RX_FIFO_LAST_EN) {
+               while ((rx_fifo_status = readl(priv->base + 
SE_GENI_RX_FIFO_STATUS)) &
+                      RX_FIFO_WC_MSK) {
+                       readl(priv->base + SE_GENI_RX_FIFOn);
+               }
+       }
+
+       /* Clear all pending IRQ status bits */
+       writel(s_irq_status, priv->base + SE_GENI_S_IRQ_CLEAR);
+
+       /* Check if still active after CANCEL */
+       status = readl(priv->base + SE_GENI_STATUS);
+       if (status & S_GENI_CMD_ACTIVE) {
+               /* Send ABORT as fallback */
+               writel(S_GENI_CMD_ABORT, priv->base + SE_GENI_S_CMD_CTRL_REG);
+               qcom_geni_serial_poll_bit(dev, SE_GENI_S_CMD_CTRL_REG,
+                                         S_GENI_CMD_ABORT, false);
+
+               /* Clear ABORT IRQ status */
+               s_irq_status = readl(priv->base + SE_GENI_S_IRQ_STATUS);
+               writel(s_irq_status, priv->base + SE_GENI_S_IRQ_CLEAR);
+       }
+
+       writel(FORCE_DEFAULT, priv->base + GENI_FORCE_DEFAULT_REG);
+}
+
 static void msm_geni_serial_setup_rx(struct udevice *dev)
 {
        struct msm_serial_data *priv = dev_get_priv(dev);
 
-       qcom_geni_serial_abort_rx(dev);
+       qcom_geni_serial_stop_rx_fifo(dev);
 
        writel(UART_PACKING_CFG0, priv->base + SE_GENI_RX_PACKING_CFG0);
        writel(UART_PACKING_CFG1, priv->base + SE_GENI_RX_PACKING_CFG1);
@@ -494,7 +553,7 @@ static inline void geni_serial_init(struct udevice *dev)
         * it else we could end up in data loss scenarios.
         */
        qcom_geni_serial_poll_tx_done(dev);
-       qcom_geni_serial_abort_rx(dev);
+       qcom_geni_serial_stop_rx_fifo(dev);
 
        writel(UART_PACKING_CFG0, base_address + SE_GENI_TX_PACKING_CFG0);
        writel(UART_PACKING_CFG1, base_address + SE_GENI_TX_PACKING_CFG1);

-- 
2.34.1

Reply via email to