blob: ced8d9c81f8c6b132db1eba5198101d21a44f618 [file] [log] [blame]
// 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/bitfield.h>
#include "mcp251xfd.h"
static inline int
mcp251xfd_rx_head_get_from_chip(const struct mcp251xfd_priv *priv,
const struct mcp251xfd_rx_ring *ring,
u8 *rx_head, bool *fifo_empty)
{
u32 fifo_sta;
int err;
err = regmap_read(priv->map_reg, MCP251XFD_REG_FIFOSTA(ring->fifo_nr),
&fifo_sta);
if (err)
return err;
*rx_head = FIELD_GET(MCP251XFD_REG_FIFOSTA_FIFOCI_MASK, fifo_sta);
*fifo_empty = !(fifo_sta & MCP251XFD_REG_FIFOSTA_TFNRFNIF);
return 0;
}
static inline int
mcp251xfd_rx_tail_get_from_chip(const struct mcp251xfd_priv *priv,
const struct mcp251xfd_rx_ring *ring,
u8 *rx_tail)
{
u32 fifo_ua;
int err;
err = regmap_read(priv->map_reg, MCP251XFD_REG_FIFOUA(ring->fifo_nr),
&fifo_ua);
if (err)
return err;
fifo_ua -= ring->base - MCP251XFD_RAM_START;
*rx_tail = fifo_ua / ring->obj_size;
return 0;
}
static int
mcp251xfd_check_rx_tail(const struct mcp251xfd_priv *priv,
const struct mcp251xfd_rx_ring *ring)
{
u8 rx_tail_chip, rx_tail;
int err;
if (!IS_ENABLED(CONFIG_CAN_MCP251XFD_SANITY))
return 0;
err = mcp251xfd_rx_tail_get_from_chip(priv, ring, &rx_tail_chip);
if (err)
return err;
rx_tail = mcp251xfd_get_rx_tail(ring);
if (rx_tail_chip != rx_tail) {
netdev_err(priv->ndev,
"RX tail of chip (%d) and ours (%d) inconsistent.\n",
rx_tail_chip, rx_tail);
return -EILSEQ;
}
return 0;
}
static int
mcp251xfd_rx_ring_update(const struct mcp251xfd_priv *priv,
struct mcp251xfd_rx_ring *ring)
{
u32 new_head;
u8 chip_rx_head;
bool fifo_empty;
int err;
err = mcp251xfd_rx_head_get_from_chip(priv, ring, &chip_rx_head,
&fifo_empty);
if (err || fifo_empty)
return err;
/* chip_rx_head, is the next RX-Object filled by the HW.
* The new RX head must be >= the old head.
*/
new_head = round_down(ring->head, ring->obj_num) + chip_rx_head;
if (new_head <= ring->head)
new_head += ring->obj_num;
ring->head = new_head;
return mcp251xfd_check_rx_tail(priv, ring);
}
static void
mcp251xfd_hw_rx_obj_to_skb(const struct mcp251xfd_priv *priv,
const struct mcp251xfd_hw_rx_obj_canfd *hw_rx_obj,
struct sk_buff *skb)
{
struct canfd_frame *cfd = (struct canfd_frame *)skb->data;
u8 dlc;
if (hw_rx_obj->flags & MCP251XFD_OBJ_FLAGS_IDE) {
u32 sid, eid;
eid = FIELD_GET(MCP251XFD_OBJ_ID_EID_MASK, hw_rx_obj->id);
sid = FIELD_GET(MCP251XFD_OBJ_ID_SID_MASK, hw_rx_obj->id);
cfd->can_id = CAN_EFF_FLAG |
FIELD_PREP(MCP251XFD_REG_FRAME_EFF_EID_MASK, eid) |
FIELD_PREP(MCP251XFD_REG_FRAME_EFF_SID_MASK, sid);
} else {
cfd->can_id = FIELD_GET(MCP251XFD_OBJ_ID_SID_MASK,
hw_rx_obj->id);
}
dlc = FIELD_GET(MCP251XFD_OBJ_FLAGS_DLC_MASK, hw_rx_obj->flags);
/* CANFD */
if (hw_rx_obj->flags & MCP251XFD_OBJ_FLAGS_FDF) {
if (hw_rx_obj->flags & MCP251XFD_OBJ_FLAGS_ESI)
cfd->flags |= CANFD_ESI;
if (hw_rx_obj->flags & MCP251XFD_OBJ_FLAGS_BRS)
cfd->flags |= CANFD_BRS;
cfd->len = can_fd_dlc2len(dlc);
} else {
if (hw_rx_obj->flags & MCP251XFD_OBJ_FLAGS_RTR)
cfd->can_id |= CAN_RTR_FLAG;
can_frame_set_cc_len((struct can_frame *)cfd, dlc,
priv->can.ctrlmode);
}
if (!(hw_rx_obj->flags & MCP251XFD_OBJ_FLAGS_RTR))
memcpy(cfd->data, hw_rx_obj->data, cfd->len);
mcp251xfd_skb_set_timestamp(priv, skb, hw_rx_obj->ts);
}
static int
mcp251xfd_handle_rxif_one(struct mcp251xfd_priv *priv,
struct mcp251xfd_rx_ring *ring,
const struct mcp251xfd_hw_rx_obj_canfd *hw_rx_obj)
{
struct net_device_stats *stats = &priv->ndev->stats;
struct sk_buff *skb;
struct canfd_frame *cfd;
int err;
if (hw_rx_obj->flags & MCP251XFD_OBJ_FLAGS_FDF)
skb = alloc_canfd_skb(priv->ndev, &cfd);
else
skb = alloc_can_skb(priv->ndev, (struct can_frame **)&cfd);
if (!skb) {
stats->rx_dropped++;
return 0;
}
mcp251xfd_hw_rx_obj_to_skb(priv, hw_rx_obj, skb);
err = can_rx_offload_queue_timestamp(&priv->offload, skb, hw_rx_obj->ts);
if (err)
stats->rx_fifo_errors++;
return 0;
}
static inline int
mcp251xfd_rx_obj_read(const struct mcp251xfd_priv *priv,
const struct mcp251xfd_rx_ring *ring,
struct mcp251xfd_hw_rx_obj_canfd *hw_rx_obj,
const u8 offset, const u8 len)
{
const int val_bytes = regmap_get_val_bytes(priv->map_rx);
int err;
err = regmap_bulk_read(priv->map_rx,
mcp251xfd_get_rx_obj_addr(ring, offset),
hw_rx_obj,
len * ring->obj_size / val_bytes);
return err;
}
static int
mcp251xfd_handle_rxif_ring(struct mcp251xfd_priv *priv,
struct mcp251xfd_rx_ring *ring)
{
struct mcp251xfd_hw_rx_obj_canfd *hw_rx_obj = ring->obj;
u8 rx_tail, len;
int err, i;
err = mcp251xfd_rx_ring_update(priv, ring);
if (err)
return err;
while ((len = mcp251xfd_get_rx_linear_len(ring))) {
int offset;
rx_tail = mcp251xfd_get_rx_tail(ring);
err = mcp251xfd_rx_obj_read(priv, ring, hw_rx_obj,
rx_tail, len);
if (err)
return err;
for (i = 0; i < len; i++) {
err = mcp251xfd_handle_rxif_one(priv, ring,
(void *)hw_rx_obj +
i * ring->obj_size);
if (err)
return err;
}
/* Increment the RX FIFO tail pointer 'len' times in a
* single SPI message.
*
* Note:
* Calculate offset, so that the SPI transfer ends on
* the last message of the uinc_xfer array, which has
* "cs_change == 0", to properly deactivate the chip
* select.
*/
offset = ARRAY_SIZE(ring->uinc_xfer) - len;
err = spi_sync_transfer(priv->spi,
ring->uinc_xfer + offset, len);
if (err)
return err;
ring->tail += len;
}
return 0;
}
int mcp251xfd_handle_rxif(struct mcp251xfd_priv *priv)
{
struct mcp251xfd_rx_ring *ring;
int err, n;
mcp251xfd_for_each_rx_ring(priv, ring, n) {
/* - if RX IRQ coalescing is active always handle ring 0
* - only handle rings if RX IRQ is active
*/
if ((ring->nr > 0 || !priv->rx_obj_num_coalesce_irq) &&
!(priv->regs_status.rxif & BIT(ring->fifo_nr)))
continue;
err = mcp251xfd_handle_rxif_ring(priv, ring);
if (err)
return err;
}
if (priv->rx_coalesce_usecs_irq)
hrtimer_start(&priv->rx_irq_timer,
ns_to_ktime(priv->rx_coalesce_usecs_irq *
NSEC_PER_USEC),
HRTIMER_MODE_REL);
return 0;
}