Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
Marc Kleine-Budde | 911 | 82.59% | 7 | 77.78% |
Vitor Soares | 191 | 17.32% | 1 | 11.11% |
Oliver Hartkopp | 1 | 0.09% | 1 | 11.11% |
Total | 1103 | 9 |
// 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 <asm/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; }
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1