return 0;
 }
 
-static netdev_tx_t cc770_start_xmit(struct sk_buff *skb, struct net_device *dev)
+static void cc770_tx(struct net_device *dev, int mo)
 {
        struct cc770_priv *priv = netdev_priv(dev);
-       struct net_device_stats *stats = &dev->stats;
-       struct can_frame *cf = (struct can_frame *)skb->data;
-       unsigned int mo = obj2msgobj(CC770_OBJ_TX);
+       struct can_frame *cf = (struct can_frame *)priv->tx_skb->data;
        u8 dlc, rtr;
        u32 id;
        int i;
 
-       if (can_dropped_invalid_skb(dev, skb))
-               return NETDEV_TX_OK;
-
-       if ((cc770_read_reg(priv,
-                           msgobj[mo].ctrl1) & TXRQST_UNC) == TXRQST_SET) {
-               netdev_err(dev, "TX register is still occupied!\n");
-               return NETDEV_TX_BUSY;
-       }
-
-       netif_stop_queue(dev);
-
        dlc = cf->can_dlc;
        id = cf->can_id;
-       if (cf->can_id & CAN_RTR_FLAG)
-               rtr = 0;
-       else
-               rtr = MSGCFG_DIR;
+       rtr = cf->can_id & CAN_RTR_FLAG ? 0 : MSGCFG_DIR;
+
+       cc770_write_reg(priv, msgobj[mo].ctrl0,
+                       MSGVAL_RES | TXIE_RES | RXIE_RES | INTPND_RES);
        cc770_write_reg(priv, msgobj[mo].ctrl1,
                        RMTPND_RES | TXRQST_RES | CPUUPD_SET | NEWDAT_RES);
-       cc770_write_reg(priv, msgobj[mo].ctrl0,
-                       MSGVAL_SET | TXIE_SET | RXIE_RES | INTPND_RES);
+
        if (id & CAN_EFF_FLAG) {
                id &= CAN_EFF_MASK;
                cc770_write_reg(priv, msgobj[mo].config,
        for (i = 0; i < dlc; i++)
                cc770_write_reg(priv, msgobj[mo].data[i], cf->data[i]);
 
-       /* Store echo skb before starting the transfer */
-       can_put_echo_skb(skb, dev, 0);
-
        cc770_write_reg(priv, msgobj[mo].ctrl1,
-                       RMTPND_RES | TXRQST_SET | CPUUPD_RES | NEWDAT_UNC);
+                       RMTPND_UNC | TXRQST_SET | CPUUPD_RES | NEWDAT_UNC);
+       cc770_write_reg(priv, msgobj[mo].ctrl0,
+                       MSGVAL_SET | TXIE_SET | RXIE_SET | INTPND_UNC);
+}
+
+static netdev_tx_t cc770_start_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+       struct cc770_priv *priv = netdev_priv(dev);
+       unsigned int mo = obj2msgobj(CC770_OBJ_TX);
+
+       if (can_dropped_invalid_skb(dev, skb))
+               return NETDEV_TX_OK;
 
-       stats->tx_bytes += dlc;
+       netif_stop_queue(dev);
+
+       if ((cc770_read_reg(priv,
+                           msgobj[mo].ctrl1) & TXRQST_UNC) == TXRQST_SET) {
+               netdev_err(dev, "TX register is still occupied!\n");
+               return NETDEV_TX_BUSY;
+       }
+
+       priv->tx_skb = skb;
+       cc770_tx(dev, mo);
 
        return NETDEV_TX_OK;
 }
        struct cc770_priv *priv = netdev_priv(dev);
        struct net_device_stats *stats = &dev->stats;
        unsigned int mo = obj2msgobj(o);
+       struct can_frame *cf;
+       u8 ctrl1;
+
+       ctrl1 = cc770_read_reg(priv, msgobj[mo].ctrl1);
 
-       /* Nothing more to send, switch off interrupts */
        cc770_write_reg(priv, msgobj[mo].ctrl0,
                        MSGVAL_RES | TXIE_RES | RXIE_RES | INTPND_RES);
+       cc770_write_reg(priv, msgobj[mo].ctrl1,
+                       RMTPND_RES | TXRQST_RES | MSGLST_RES | NEWDAT_RES);
 
-       stats->tx_packets++;
+       if (unlikely(!priv->tx_skb)) {
+               netdev_err(dev, "missing tx skb in tx interrupt\n");
+               return;
+       }
+
+       if (unlikely(ctrl1 & MSGLST_SET)) {
+               stats->rx_over_errors++;
+               stats->rx_errors++;
+       }
+
+       /* When the CC770 is sending an RTR message and it receives a regular
+        * message that matches the id of the RTR message, it will overwrite the
+        * outgoing message in the TX register. When this happens we must
+        * process the received message and try to transmit the outgoing skb
+        * again.
+        */
+       if (unlikely(ctrl1 & NEWDAT_SET)) {
+               cc770_rx(dev, mo, ctrl1);
+               cc770_tx(dev, mo);
+               return;
+       }
+
+       can_put_echo_skb(priv->tx_skb, dev, 0);
        can_get_echo_skb(dev, 0);
+
+       cf = (struct can_frame *)priv->tx_skb->data;
+       stats->tx_bytes += cf->can_dlc;
+       stats->tx_packets++;
+
+       priv->tx_skb = NULL;
+
        netif_wake_queue(dev);
 }
 
        priv->can.do_set_bittiming = cc770_set_bittiming;
        priv->can.do_set_mode = cc770_set_mode;
        priv->can.ctrlmode_supported = CAN_CTRLMODE_3_SAMPLES;
+       priv->tx_skb = NULL;
 
        memcpy(priv->obj_flags, cc770_obj_flags, sizeof(cc770_obj_flags));