| // SPDX-License-Identifier: GPL-2.0 |
| // |
| // mcp251xfd - Microchip MCP251xFD Family CAN controller driver |
| // |
| // Copyright (c) 2019, 2020, 2021 Pengutronix, |
| // Marc Kleine-Budde <kernel@pengutronix.de> |
| // |
| // Based on: |
| // |
| // CAN bus driver for Microchip 25XXFD CAN Controller with SPI Interface |
| // |
| // Copyright (c) 2019 Martin Sperl <kernel@martin.sperl.org> |
| // |
| |
| #include <linux/unaligned.h> |
| #include <linux/bitfield.h> |
| |
| #include "mcp251xfd.h" |
| |
| static inline struct |
| mcp251xfd_tx_obj *mcp251xfd_get_tx_obj_next(struct mcp251xfd_tx_ring *tx_ring) |
| { |
| u8 tx_head; |
| |
| tx_head = mcp251xfd_get_tx_head(tx_ring); |
| |
| return &tx_ring->obj[tx_head]; |
| } |
| |
| static void |
| mcp251xfd_tx_obj_from_skb(const struct mcp251xfd_priv *priv, |
| struct mcp251xfd_tx_obj *tx_obj, |
| const struct sk_buff *skb, |
| unsigned int seq) |
| { |
| const struct canfd_frame *cfd = (struct canfd_frame *)skb->data; |
| struct mcp251xfd_hw_tx_obj_raw *hw_tx_obj; |
| union mcp251xfd_tx_obj_load_buf *load_buf; |
| u8 dlc; |
| u32 id, flags; |
| int len_sanitized = 0, len; |
| |
| if (cfd->can_id & CAN_EFF_FLAG) { |
| u32 sid, eid; |
| |
| sid = FIELD_GET(MCP251XFD_REG_FRAME_EFF_SID_MASK, cfd->can_id); |
| eid = FIELD_GET(MCP251XFD_REG_FRAME_EFF_EID_MASK, cfd->can_id); |
| |
| id = FIELD_PREP(MCP251XFD_OBJ_ID_EID_MASK, eid) | |
| FIELD_PREP(MCP251XFD_OBJ_ID_SID_MASK, sid); |
| |
| flags = MCP251XFD_OBJ_FLAGS_IDE; |
| } else { |
| id = FIELD_PREP(MCP251XFD_OBJ_ID_SID_MASK, cfd->can_id); |
| flags = 0; |
| } |
| |
| /* Use the MCP2518FD mask even on the MCP2517FD. It doesn't |
| * harm, only the lower 7 bits will be transferred into the |
| * TEF object. |
| */ |
| flags |= FIELD_PREP(MCP251XFD_OBJ_FLAGS_SEQ_MCP2518FD_MASK, seq); |
| |
| if (cfd->can_id & CAN_RTR_FLAG) |
| flags |= MCP251XFD_OBJ_FLAGS_RTR; |
| else |
| len_sanitized = canfd_sanitize_len(cfd->len); |
| |
| /* CANFD */ |
| if (can_is_canfd_skb(skb)) { |
| if (cfd->flags & CANFD_ESI) |
| flags |= MCP251XFD_OBJ_FLAGS_ESI; |
| |
| flags |= MCP251XFD_OBJ_FLAGS_FDF; |
| |
| if (cfd->flags & CANFD_BRS) |
| flags |= MCP251XFD_OBJ_FLAGS_BRS; |
| |
| dlc = can_fd_len2dlc(cfd->len); |
| } else { |
| dlc = can_get_cc_dlc((struct can_frame *)cfd, |
| priv->can.ctrlmode); |
| } |
| |
| flags |= FIELD_PREP(MCP251XFD_OBJ_FLAGS_DLC_MASK, dlc); |
| |
| load_buf = &tx_obj->buf; |
| if (priv->devtype_data.quirks & MCP251XFD_QUIRK_CRC_TX) |
| hw_tx_obj = &load_buf->crc.hw_tx_obj; |
| else |
| hw_tx_obj = &load_buf->nocrc.hw_tx_obj; |
| |
| put_unaligned_le32(id, &hw_tx_obj->id); |
| put_unaligned_le32(flags, &hw_tx_obj->flags); |
| |
| /* Copy data */ |
| memcpy(hw_tx_obj->data, cfd->data, cfd->len); |
| |
| /* Clear unused data at end of CAN frame */ |
| if (MCP251XFD_SANITIZE_CAN && len_sanitized) { |
| int pad_len; |
| |
| pad_len = len_sanitized - cfd->len; |
| if (pad_len) |
| memset(hw_tx_obj->data + cfd->len, 0x0, pad_len); |
| } |
| |
| /* Number of bytes to be written into the RAM of the controller */ |
| len = sizeof(hw_tx_obj->id) + sizeof(hw_tx_obj->flags); |
| if (MCP251XFD_SANITIZE_CAN) |
| len += round_up(len_sanitized, sizeof(u32)); |
| else |
| len += round_up(cfd->len, sizeof(u32)); |
| |
| if (priv->devtype_data.quirks & MCP251XFD_QUIRK_CRC_TX) { |
| u16 crc; |
| |
| mcp251xfd_spi_cmd_crc_set_len_in_ram(&load_buf->crc.cmd, |
| len); |
| /* CRC */ |
| len += sizeof(load_buf->crc.cmd); |
| crc = mcp251xfd_crc16_compute(&load_buf->crc, len); |
| put_unaligned_be16(crc, (void *)load_buf + len); |
| |
| /* Total length */ |
| len += sizeof(load_buf->crc.crc); |
| } else { |
| len += sizeof(load_buf->nocrc.cmd); |
| } |
| |
| tx_obj->xfer[0].len = len; |
| } |
| |
| static void mcp251xfd_tx_failure_drop(const struct mcp251xfd_priv *priv, |
| struct mcp251xfd_tx_ring *tx_ring, |
| int err) |
| { |
| struct net_device *ndev = priv->ndev; |
| struct net_device_stats *stats = &ndev->stats; |
| unsigned int frame_len = 0; |
| u8 tx_head; |
| |
| tx_ring->head--; |
| stats->tx_dropped++; |
| tx_head = mcp251xfd_get_tx_head(tx_ring); |
| can_free_echo_skb(ndev, tx_head, &frame_len); |
| netdev_completed_queue(ndev, 1, frame_len); |
| netif_wake_queue(ndev); |
| |
| if (net_ratelimit()) |
| netdev_err(priv->ndev, "ERROR in %s: %d\n", __func__, err); |
| } |
| |
| void mcp251xfd_tx_obj_write_sync(struct work_struct *work) |
| { |
| struct mcp251xfd_priv *priv = container_of(work, struct mcp251xfd_priv, |
| tx_work); |
| struct mcp251xfd_tx_obj *tx_obj = priv->tx_work_obj; |
| struct mcp251xfd_tx_ring *tx_ring = priv->tx; |
| int err; |
| |
| err = spi_sync(priv->spi, &tx_obj->msg); |
| if (err) |
| mcp251xfd_tx_failure_drop(priv, tx_ring, err); |
| } |
| |
| static int mcp251xfd_tx_obj_write(const struct mcp251xfd_priv *priv, |
| struct mcp251xfd_tx_obj *tx_obj) |
| { |
| return spi_async(priv->spi, &tx_obj->msg); |
| } |
| |
| static bool mcp251xfd_tx_busy(const struct mcp251xfd_priv *priv, |
| struct mcp251xfd_tx_ring *tx_ring) |
| { |
| if (mcp251xfd_get_tx_free(tx_ring) > 0) |
| return false; |
| |
| netif_stop_queue(priv->ndev); |
| |
| /* Memory barrier before checking tx_free (head and tail) */ |
| smp_mb(); |
| |
| if (mcp251xfd_get_tx_free(tx_ring) == 0) { |
| netdev_dbg(priv->ndev, |
| "Stopping tx-queue (tx_head=0x%08x, tx_tail=0x%08x, len=%d).\n", |
| tx_ring->head, tx_ring->tail, |
| tx_ring->head - tx_ring->tail); |
| |
| return true; |
| } |
| |
| netif_start_queue(priv->ndev); |
| |
| return false; |
| } |
| |
| static bool mcp251xfd_work_busy(struct work_struct *work) |
| { |
| return work_busy(work); |
| } |
| |
| netdev_tx_t mcp251xfd_start_xmit(struct sk_buff *skb, |
| struct net_device *ndev) |
| { |
| struct mcp251xfd_priv *priv = netdev_priv(ndev); |
| struct mcp251xfd_tx_ring *tx_ring = priv->tx; |
| struct mcp251xfd_tx_obj *tx_obj; |
| unsigned int frame_len; |
| u8 tx_head; |
| int err; |
| |
| if (can_dev_dropped_skb(ndev, skb)) |
| return NETDEV_TX_OK; |
| |
| if (mcp251xfd_tx_busy(priv, tx_ring) || |
| mcp251xfd_work_busy(&priv->tx_work)) |
| return NETDEV_TX_BUSY; |
| |
| tx_obj = mcp251xfd_get_tx_obj_next(tx_ring); |
| mcp251xfd_tx_obj_from_skb(priv, tx_obj, skb, tx_ring->head); |
| |
| /* Stop queue if we occupy the complete TX FIFO */ |
| tx_head = mcp251xfd_get_tx_head(tx_ring); |
| tx_ring->head++; |
| if (mcp251xfd_get_tx_free(tx_ring) == 0) |
| netif_stop_queue(ndev); |
| |
| frame_len = can_skb_get_frame_len(skb); |
| err = can_put_echo_skb(skb, ndev, tx_head, frame_len); |
| if (!err) |
| netdev_sent_queue(priv->ndev, frame_len); |
| |
| err = mcp251xfd_tx_obj_write(priv, tx_obj); |
| if (err == -EBUSY) { |
| netif_stop_queue(ndev); |
| priv->tx_work_obj = tx_obj; |
| queue_work(priv->wq, &priv->tx_work); |
| } else if (err) { |
| mcp251xfd_tx_failure_drop(priv, tx_ring, err); |
| } |
| |
| return NETDEV_TX_OK; |
| } |