From 0b721ac21c7862294fcca619cf75cd0eec441baf Mon Sep 17 00:00:00 2001
From: Peter van der Perk <peter.vanderperk@nxp.com>
Date: Fri, 4 Aug 2023 15:02:39 +0200
Subject: [PATCH] imxrt: flexcan use hpwork for receiving frames

FlexCAN before used interrupts to process incoming frames.
This patch adds a HPWORK workqueue to process incoming frames in the workqueue
context instead. Also renamed mbi to txmb for better readability.
---
 arch/arm/src/imxrt/imxrt_flexcan.c | 90 +++++++++++++++++++++++-------
 1 file changed, 71 insertions(+), 19 deletions(-)

diff --git a/arch/arm/src/imxrt/imxrt_flexcan.c b/arch/arm/src/imxrt/imxrt_flexcan.c
index cabd1161c0..71010c6720 100644
--- a/arch/arm/src/imxrt/imxrt_flexcan.c
+++ b/arch/arm/src/imxrt/imxrt_flexcan.c
@@ -66,7 +66,8 @@
  * is required.
  */
 
-#define CANWORK LPWORK
+#define CANWORK    LPWORK
+#define CANRCVWORK HPWORK
 
 /* CONFIG_IMXRT_FLEXCAN_NETHIFS determines the number of physical
  * interfaces that will be supported.
@@ -268,6 +269,7 @@ struct imxrt_driver_s
 #ifdef TX_TIMEOUT_WQ
   struct wdog_s txtimeout[TXMBCOUNT]; /* TX timeout timer */
 #endif
+  struct work_s rcvwork;            /* For deferring interrupt work to the wq */
   struct work_s irqwork;            /* For deferring interrupt work to the wq */
   struct work_s pollwork;           /* For deferring poll work to the work wq */
   struct canfd_frame *txdesc_fd;    /* A pointer to the list of TX descriptor for FD frames */
@@ -482,6 +484,7 @@ static void imxrt_txdone(struct imxrt_driver_s *priv);
 
 static int  imxrt_flexcan_interrupt(int irq, void *context,
                                       void *arg);
+static void imxrt_flexcan_interrupt_work(void *arg);
 
 /* Watchdog timer expirations */
 #ifdef TX_TIMEOUT_WQ
@@ -578,6 +581,7 @@ static int imxrt_transmit(struct imxrt_driver_s *priv)
 #endif
 #ifdef CONFIG_NET_CAN_RAW_TX_DEADLINE
   int32_t timeout;
+  uint32_t txmb = 0;
 #endif
 
   mbi = RXMBCOUNT + 1;
@@ -596,6 +600,9 @@ static int imxrt_transmit(struct imxrt_driver_s *priv)
 
       mb_bit <<= 1;
       mbi++;
+#ifdef CONFIG_NET_CAN_RAW_TX_DEADLINE
+      txmb++;
+#endif
     }
 
   if (mbi == TOTALMBCOUNT)
@@ -613,7 +620,7 @@ static int imxrt_transmit(struct imxrt_driver_s *priv)
     {
       struct timeval *tv =
              (struct timeval *)(priv->dev.d_buf + priv->dev.d_len);
-      priv->txmb[mbi].deadline = *tv;
+      priv->txmb[txmb].deadline = *tv;
       timeout  = (tv->tv_sec - ts.tv_sec)*CLK_TCK
                  + ((tv->tv_usec - ts.tv_nsec / 1000)*CLK_TCK) / 1000000;
       if (timeout < 0)
@@ -629,15 +636,15 @@ static int imxrt_transmit(struct imxrt_driver_s *priv)
         {
           timeout = ((CONFIG_NET_CAN_RAW_DEFAULT_TX_DEADLINE / 1000000)
               *CLK_TCK);
-          priv->txmb[mbi].deadline.tv_sec = ts.tv_sec +
+          priv->txmb[txmb].deadline.tv_sec = ts.tv_sec +
               CONFIG_NET_CAN_RAW_DEFAULT_TX_DEADLINE / 1000000;
-          priv->txmb[mbi].deadline.tv_usec = (ts.tv_nsec / 1000) +
+          priv->txmb[txmb].deadline.tv_usec = (ts.tv_nsec / 1000) +
               CONFIG_NET_CAN_RAW_DEFAULT_TX_DEADLINE % 1000000;
         }
       else
         {
-          priv->txmb[mbi].deadline.tv_sec = 0;
-          priv->txmb[mbi].deadline.tv_usec = 0;
+          priv->txmb[txmb].deadline.tv_sec = 0;
+          priv->txmb[txmb].deadline.tv_usec = 0;
           timeout = -1;
         }
     }
@@ -726,7 +733,7 @@ static int imxrt_transmit(struct imxrt_driver_s *priv)
 
   if (timeout > 0)
     {
-      wd_start(&priv->txtimeout[mbi - RXMBCOUNT], timeout + 1,
+      wd_start(&priv->txtimeout[txmb], timeout + 1,
                imxrt_txtimeout_expiry, (wdparm_t)priv);
     }
 #endif
@@ -981,6 +988,9 @@ static void imxrt_txdone(struct imxrt_driver_s *priv)
   uint32_t flags;
   uint32_t mbi;
   uint32_t mb_bit;
+#ifdef TX_TIMEOUT_WQ
+  uint32_t txmb;
+#endif
 
   flags  = getreg32(priv->base + IMXRT_CAN_IFLAG1_OFFSET);
   flags &= IFLAG1_TX;
@@ -989,8 +999,13 @@ static void imxrt_txdone(struct imxrt_driver_s *priv)
 
   /* Process TX completions */
 
-  mb_bit = 1 << RXMBCOUNT;
-  for (mbi = 0; flags && mbi < TXMBCOUNT; mbi++)
+  mbi = RXMBCOUNT + 1;
+  mb_bit = 1 << mbi;
+#ifdef TX_TIMEOUT_WQ
+  txmb = 0;
+#endif
+
+  while (mbi < TOTALMBCOUNT)
     {
       if (flags & mb_bit)
         {
@@ -1003,13 +1018,17 @@ static void imxrt_txdone(struct imxrt_driver_s *priv)
            * mailbox be set to inactive
            */
 
-          wd_cancel(&priv->txtimeout[mbi]);
-          struct mb_s *mb = flexcan_get_mb(priv, mbi + RXMBCOUNT);
+          wd_cancel(&priv->txtimeout[txmb]);
+          struct mb_s *mb = flexcan_get_mb(priv, mbi);
           mb->cs.code = CAN_TXMB_INACTIVE;
 #endif
         }
 
       mb_bit <<= 1;
+      mbi++;
+#ifdef TX_TIMEOUT_WQ
+      txmb++;
+#endif
     }
 }
 
@@ -1046,6 +1065,43 @@ static void imxrt_txdone_work(void *arg)
   net_unlock();
 }
 
+/****************************************************************************
+ * Function: imxrt_flexcan_interrupt_work
+ *
+ * Description:
+ *   Three interrupt sources will vector this this function:
+ *   1. CAN MB transmit interrupt handler
+ *   2. CAN MB receive interrupt handler
+ *   3.
+ *
+ * Input Parameters:
+ *   irq     - Number of the IRQ that generated the interrupt
+ *   context - Interrupt register state save info (architecture-specific)
+ *
+ * Returned Value:
+ *   OK on success
+ *
+ * Assumptions:
+ *
+ ****************************************************************************/
+
+static void imxrt_flexcan_interrupt_work(void *arg)
+{
+  struct imxrt_driver_s *priv = (struct imxrt_driver_s *)arg;
+
+  uint32_t flags;
+  flags  = getreg32(priv->base + IMXRT_CAN_IFLAG1_OFFSET);
+  flags &= IFLAG1_RX;
+
+  net_lock();
+  imxrt_receive(priv, flags);
+  net_unlock();
+
+  /* Mask MB again */
+
+  modifyreg32(priv->base + IMXRT_CAN_IMASK1_OFFSET, 0, IFLAG1_RX);
+}
+
 /****************************************************************************
  * Function: imxrt_flexcan_interrupt
  *
@@ -1079,11 +1135,9 @@ static int imxrt_flexcan_interrupt(int irq, void *context,
 
       if (flags)
         {
-          /* Process immediately since scheduling a workqueue is too slow
-           * which causes us to drop CAN frames
-           */
-
-          imxrt_receive(priv, flags);
+          modifyreg32(priv->base + IMXRT_CAN_IMASK1_OFFSET, IFLAG1_RX, 0);
+          work_queue(CANRCVWORK, &priv->rcvwork,
+                     imxrt_flexcan_interrupt_work, priv, 0);
         }
 
       flags  = getreg32(priv->base + IMXRT_CAN_IFLAG1_OFFSET);
@@ -1095,9 +1149,7 @@ static int imxrt_flexcan_interrupt(int irq, void *context,
            * condition here.
            */
 
-          flags  = getreg32(priv->base + IMXRT_CAN_IMASK1_OFFSET);
-          flags &= ~(IFLAG1_TX);
-          putreg32(flags, priv->base + IMXRT_CAN_IMASK1_OFFSET);
+          modifyreg32(priv->base + IMXRT_CAN_IMASK1_OFFSET, IFLAG1_TX, 0);
           work_queue(CANWORK, &priv->irqwork, imxrt_txdone_work, priv, 0);
         }
     }