]> git.karo-electronics.de Git - karo-tx-linux.git/commitdiff
ENGR00320354-1 can: m_can: add bus error handling
authorDong Aisheng <b29396@freescale.com>
Wed, 25 Jun 2014 12:05:19 +0000 (20:05 +0800)
committerNitin Garg <nitin.garg@freescale.com>
Fri, 16 Jan 2015 03:17:31 +0000 (21:17 -0600)
Add bus error, state change, lost message handling mechanism.

Signed-off-by: Dong Aisheng <b29396@freescale.com>
(cherry picked from commit 537669a10eb16b9f869ac254dc6b6709f8352bc2)

drivers/net/can/m_can.c

index 4397cfe2ec311a43cf02214fd0bca19840bf6115..1a320aff2b2756236e35c4fddfe022d728151dc5 100644 (file)
@@ -83,6 +83,18 @@ enum m_can_reg {
        M_CAN_TXEFA     = 0xf8,
 };
 
+/* m_can lec values */
+enum m_can_lec_type {
+       LEC_NO_ERROR = 0,
+       LEC_STUFF_ERROR,
+       LEC_FORM_ERROR,
+       LEC_ACK_ERROR,
+       LEC_BIT1_ERROR,
+       LEC_BIT0_ERROR,
+       LEC_CRC_ERROR,
+       LEC_UNUSED,
+};
+
 /* CC Control Register(CCCR) */
 #define CCCR_CCE       BIT(1)
 #define CCCR_INIT      BIT(0)
@@ -97,6 +109,19 @@ enum m_can_reg {
 #define BTR_SJW_SHIFT          0
 #define BTR_SJW_MASK           0xf
 
+/* Error Counter Register(ECR) */
+#define ECR_RP                 BIT(15)
+#define ECR_REC_SHIFT          8
+#define ECR_REC_MASK           (0x7f << ECR_REC_SHIFT)
+#define ECR_TEC_SHIFT          0
+#define ECR_TEC_MASK           0xff
+
+/* Protocol Status Register(PSR) */
+#define PSR_BO         BIT(7)
+#define PSR_EW         BIT(6)
+#define PSR_EP         BIT(5)
+#define PSR_LEC_MASK   0x7
+
 /* Interrupt Register(IR) */
 #define IR_ALL_INT     0xffffffff
 #define IR_STE         BIT(31)
@@ -131,10 +156,11 @@ enum m_can_reg {
 #define IR_RF0F                BIT(2)
 #define IR_RF0W                BIT(1)
 #define IR_RF0N                BIT(0)
-#define IR_ERR_ALL     (IR_STE | IR_FOE | IR_ACKE | IR_BE | IR_CRCE | \
-               IR_WDI | IR_BO | IR_EW | IR_EP | IR_ELO | IR_BEU | \
-               IR_BEC | IR_TOO | IR_MRAF | IR_TSW | IR_TEFL | IR_RF1L | \
-               IR_RF0L)
+#define IR_ERR_STATE   (IR_BO | IR_EW | IR_EP)
+#define IR_ERR_BUS     (IR_STE | IR_FOE | IR_ACKE | IR_BE | IR_CRCE | \
+               IR_WDI | IR_ELO | IR_BEU | IR_BEC | IR_TOO | IR_MRAF | \
+               IR_TSW | IR_TEFL | IR_RF1L | IR_RF0L)
+#define IR_ERR_ALL     (IR_ERR_STATE | IR_ERR_BUS)
 
 /* Rx FIFO 0/1 Configuration (RXF0C/RXF1C) */
 #define RXFC_FWM_OFF   24
@@ -320,12 +346,175 @@ static int m_can_do_rx_poll(struct net_device *dev, int quota)
        return num_rx_pkts;
 }
 
+static int m_can_handle_lost_msg(struct net_device *dev)
+{
+       struct net_device_stats *stats = &dev->stats;
+       struct sk_buff *skb;
+       struct can_frame *frame;
+
+       netdev_err(dev, "msg lost in rxf0\n");
+
+       skb = alloc_can_err_skb(dev, &frame);
+       if (unlikely(!skb))
+               return 0;
+
+       frame->can_id |= CAN_ERR_CRTL;
+       frame->data[1] = CAN_ERR_CRTL_RX_OVERFLOW;
+       stats->rx_errors++;
+       stats->rx_over_errors++;
+
+       netif_receive_skb(skb);
+
+       return 1;
+}
+
+static int m_can_handle_bus_err(struct net_device *dev,
+                               enum m_can_lec_type lec_type)
+{
+       struct m_can_priv *priv = netdev_priv(dev);
+       struct net_device_stats *stats = &dev->stats;
+       struct can_frame *cf;
+       struct sk_buff *skb;
+
+       /* early exit if no lec update */
+       if (lec_type == LEC_UNUSED)
+               return 0;
+
+       /* propagate the error condition to the CAN stack */
+       skb = alloc_can_err_skb(dev, &cf);
+       if (unlikely(!skb))
+               return 0;
+
+       /*
+        * check for 'last error code' which tells us the
+        * type of the last error to occur on the CAN bus
+        */
+       priv->can.can_stats.bus_error++;
+       stats->rx_errors++;
+       cf->can_id |= CAN_ERR_PROT | CAN_ERR_BUSERROR;
+       cf->data[2] |= CAN_ERR_PROT_UNSPEC;
+
+       switch (lec_type) {
+       case LEC_STUFF_ERROR:
+               netdev_dbg(dev, "stuff error\n");
+               cf->data[2] |= CAN_ERR_PROT_STUFF;
+               break;
+       case LEC_FORM_ERROR:
+               netdev_dbg(dev, "form error\n");
+               cf->data[2] |= CAN_ERR_PROT_FORM;
+               break;
+       case LEC_ACK_ERROR:
+               netdev_dbg(dev, "ack error\n");
+               cf->data[3] |= (CAN_ERR_PROT_LOC_ACK |
+                               CAN_ERR_PROT_LOC_ACK_DEL);
+               break;
+       case LEC_BIT1_ERROR:
+               netdev_dbg(dev, "bit1 error\n");
+               cf->data[2] |= CAN_ERR_PROT_BIT1;
+               break;
+       case LEC_BIT0_ERROR:
+               netdev_dbg(dev, "bit0 error\n");
+               cf->data[2] |= CAN_ERR_PROT_BIT0;
+               break;
+       case LEC_CRC_ERROR:
+               netdev_dbg(dev, "CRC error\n");
+               cf->data[3] |= (CAN_ERR_PROT_LOC_CRC_SEQ |
+                               CAN_ERR_PROT_LOC_CRC_DEL);
+               break;
+       default:
+               break;
+       }
+
+       netif_receive_skb(skb);
+       stats->rx_packets++;
+       stats->rx_bytes += cf->can_dlc;
+
+       return 1;
+}
+
+static int m_can_get_berr_counter(const struct net_device *dev,
+                                 struct can_berr_counter *bec)
+{
+       struct m_can_priv *priv = netdev_priv(dev);
+       unsigned int ecr;
+
+       ecr = m_can_read(priv, M_CAN_ECR);
+       bec->rxerr = (ecr & ECR_REC_MASK) >> ECR_REC_SHIFT;
+       bec->txerr = ecr & ECR_TEC_MASK;
+
+       return 0;
+}
+
+static int m_can_handle_state_change(struct net_device *dev,
+                               enum can_state new_state)
+{
+       struct m_can_priv *priv = netdev_priv(dev);
+       struct net_device_stats *stats = &dev->stats;
+       struct can_frame *cf;
+       struct sk_buff *skb;
+       struct can_berr_counter bec;
+       unsigned int ecr;
+
+       /* propagate the error condition to the CAN stack */
+       skb = alloc_can_err_skb(dev, &cf);
+       if (unlikely(!skb))
+               return 0;
+
+       m_can_get_berr_counter(dev, &bec);
+
+       switch (new_state) {
+       case CAN_STATE_ERROR_ACTIVE:
+               /* error warning state */
+               priv->can.can_stats.error_warning++;
+               priv->can.state = CAN_STATE_ERROR_WARNING;
+               cf->can_id |= CAN_ERR_CRTL;
+               cf->data[1] = (bec.txerr > bec.rxerr) ?
+                       CAN_ERR_CRTL_TX_WARNING :
+                       CAN_ERR_CRTL_RX_WARNING;
+               cf->data[6] = bec.txerr;
+               cf->data[7] = bec.rxerr;
+               break;
+       case CAN_STATE_ERROR_PASSIVE:
+               /* error passive state */
+               priv->can.can_stats.error_passive++;
+               priv->can.state = CAN_STATE_ERROR_PASSIVE;
+               cf->can_id |= CAN_ERR_CRTL;
+               ecr = m_can_read(priv, M_CAN_ECR);
+               if (ecr & ECR_RP)
+                       cf->data[1] |= CAN_ERR_CRTL_RX_PASSIVE;
+               if (bec.txerr > 127)
+                       cf->data[1] |= CAN_ERR_CRTL_TX_PASSIVE;
+               cf->data[6] = bec.txerr;
+               cf->data[7] = bec.rxerr;
+               break;
+       case CAN_STATE_BUS_OFF:
+               /* bus-off state */
+               priv->can.state = CAN_STATE_BUS_OFF;
+               cf->can_id |= CAN_ERR_BUSOFF;
+               /*
+                * disable all interrupts in bus-off mode to ensure that
+                * the CPU is not hogged down
+                */
+               m_can_enable_all_interrupts(priv, false);
+               can_bus_off(dev);
+               break;
+       default:
+               break;
+       }
+
+       netif_receive_skb(skb);
+       stats->rx_packets++;
+       stats->rx_bytes += cf->can_dlc;
+
+       return 1;
+}
+
 static int m_can_poll(struct napi_struct *napi, int quota)
 {
        struct net_device *dev = napi->dev;
        struct m_can_priv *priv = netdev_priv(dev);
        u32 work_done = 0;
-       u32 irqstatus;
+       u32 irqstatus, psr;
 
        irqstatus = m_can_read(priv, M_CAN_IR);
        if (irqstatus)
@@ -337,6 +526,48 @@ static int m_can_poll(struct napi_struct *napi, int quota)
        if (!irqstatus)
                goto end;
 
+       psr = m_can_read(priv, M_CAN_PSR);
+       if (irqstatus & IR_ERR_STATE) {
+               if ((psr & PSR_EW) &&
+                       (priv->can.state != CAN_STATE_ERROR_WARNING)) {
+                       netdev_dbg(dev, "entered error warning state\n");
+                       work_done += m_can_handle_state_change(dev,
+                                       CAN_STATE_ERROR_WARNING);
+               }
+
+               if ((psr & PSR_EP) &&
+                       (priv->can.state != CAN_STATE_ERROR_PASSIVE)) {
+                       netdev_dbg(dev, "entered error warning state\n");
+                       work_done += m_can_handle_state_change(dev,
+                                       CAN_STATE_ERROR_PASSIVE);
+               }
+
+               if ((psr & PSR_BO) &&
+                       (priv->can.state != CAN_STATE_BUS_OFF)) {
+                       netdev_dbg(dev, "entered error warning state\n");
+                       work_done += m_can_handle_state_change(dev,
+                                       CAN_STATE_BUS_OFF);
+               }
+       }
+
+       if (irqstatus & IR_ERR_BUS) {
+               if (irqstatus & IR_RF0L)
+                       work_done += m_can_handle_lost_msg(dev);
+
+               /* handle lec errors on the bus */
+               if (psr & LEC_UNUSED)
+                       work_done += m_can_handle_bus_err(dev,
+                                       psr & LEC_UNUSED);
+
+               /* other unproccessed error interrupts */
+               if (irqstatus & IR_WDI)
+                       netdev_err(dev, "Message RAM Watchdog event due to missing READY\n");
+               if (irqstatus & IR_TOO)
+                       netdev_err(dev, "Timeout reached\n");
+               if (irqstatus & IR_MRAF)
+                       netdev_err(dev, "Message RAM access failure occurred\n");
+       }
+
        if (irqstatus & IR_RF0N)
                /* handle events corresponding to receive message objects */
                work_done += m_can_do_rx_poll(dev, (quota - work_done));
@@ -369,31 +600,18 @@ static irqreturn_t m_can_isr(int irq, void *dev_id)
        if (ir & IR_ALL_INT)
                m_can_write(priv, M_CAN_IR, ir);
 
-       if (ir & IR_ERR_ALL) {
-               netdev_dbg(dev, "bus error\n");
-               /* TODO: handle bus error */
-       }
-
-       /* save irqstatus for later using */
-       priv->irqstatus = ir;
-
        /*
         * schedule NAPI in case of
         * - rx IRQ
-        * - state change IRQ(TODO)
-        * - bus error IRQ and bus error reporting (TODO)
+        * - state change IRQ
+        * - bus error IRQ and bus error reporting
         */
-       if (ir & IR_RF0N) {
+       if ((ir & IR_RF0N) || (ir & IR_ERR_ALL)) {
+               priv->irqstatus = ir;
                m_can_enable_all_interrupts(priv, false);
                napi_schedule(&priv->napi);
        }
 
-       /* FIFO overflow */
-       if (ir & IR_RF0L) {
-               dev->stats.rx_over_errors++;
-               dev->stats.rx_errors++;
-       }
-
        /* transmission complete interrupt */
        if (ir & IR_TC) {
                netdev_dbg(dev, "tx complete\n");
@@ -446,7 +664,6 @@ static int m_can_set_bittiming(struct net_device *dev)
  * - setup bittiming
  * - TODO:
  *   1) other working modes support like monitor, loopback...
- *   2) lec error status report enable
  */
 static void m_can_chip_config(struct net_device *dev)
 {
@@ -515,14 +732,6 @@ static int m_can_set_mode(struct net_device *dev, enum can_mode mode)
        return 0;
 }
 
-static int m_can_get_berr_counter(const struct net_device *dev,
-                                 struct can_berr_counter *bec)
-{
-       /* TODO */
-
-       return 0;
-}
-
 static void free_m_can_dev(struct net_device *dev)
 {
        free_candev(dev);