| // SPDX-License-Identifier: GPL-2.0 |
| // |
| // Copyright (c) 2023, 2024 Pengutronix, |
| // Marc Kleine-Budde <kernel@pengutronix.de> |
| // |
| |
| #include <net/netdev_queues.h> |
| |
| #include "rockchip_canfd.h" |
| |
| static bool rkcanfd_tx_tail_is_eff(const struct rkcanfd_priv *priv) |
| { |
| const struct canfd_frame *cfd; |
| const struct sk_buff *skb; |
| unsigned int tx_tail; |
| |
| if (!rkcanfd_get_tx_pending(priv)) |
| return false; |
| |
| tx_tail = rkcanfd_get_tx_tail(priv); |
| skb = priv->can.echo_skb[tx_tail]; |
| if (!skb) { |
| netdev_err(priv->ndev, |
| "%s: echo_skb[%u]=NULL tx_head=0x%08x tx_tail=0x%08x\n", |
| __func__, tx_tail, |
| priv->tx_head, priv->tx_tail); |
| |
| return false; |
| } |
| |
| cfd = (struct canfd_frame *)skb->data; |
| |
| return cfd->can_id & CAN_EFF_FLAG; |
| } |
| |
| unsigned int rkcanfd_get_effective_tx_free(const struct rkcanfd_priv *priv) |
| { |
| if (priv->devtype_data.quirks & RKCANFD_QUIRK_RK3568_ERRATUM_6 && |
| rkcanfd_tx_tail_is_eff(priv)) |
| return 0; |
| |
| return rkcanfd_get_tx_free(priv); |
| } |
| |
| static void rkcanfd_start_xmit_write_cmd(const struct rkcanfd_priv *priv, |
| const u32 reg_cmd) |
| { |
| if (priv->devtype_data.quirks & RKCANFD_QUIRK_RK3568_ERRATUM_12) |
| rkcanfd_write(priv, RKCANFD_REG_MODE, priv->reg_mode_default | |
| RKCANFD_REG_MODE_SPACE_RX_MODE); |
| |
| rkcanfd_write(priv, RKCANFD_REG_CMD, reg_cmd); |
| |
| if (priv->devtype_data.quirks & RKCANFD_QUIRK_RK3568_ERRATUM_12) |
| rkcanfd_write(priv, RKCANFD_REG_MODE, priv->reg_mode_default); |
| } |
| |
| void rkcanfd_xmit_retry(struct rkcanfd_priv *priv) |
| { |
| const unsigned int tx_head = rkcanfd_get_tx_head(priv); |
| const u32 reg_cmd = RKCANFD_REG_CMD_TX_REQ(tx_head); |
| |
| rkcanfd_start_xmit_write_cmd(priv, reg_cmd); |
| } |
| |
| netdev_tx_t rkcanfd_start_xmit(struct sk_buff *skb, struct net_device *ndev) |
| { |
| struct rkcanfd_priv *priv = netdev_priv(ndev); |
| u32 reg_frameinfo, reg_id, reg_cmd; |
| unsigned int tx_head, frame_len; |
| const struct canfd_frame *cfd; |
| int err; |
| u8 i; |
| |
| if (can_dropped_invalid_skb(ndev, skb)) |
| return NETDEV_TX_OK; |
| |
| if (!netif_subqueue_maybe_stop(priv->ndev, 0, |
| rkcanfd_get_effective_tx_free(priv), |
| RKCANFD_TX_STOP_THRESHOLD, |
| RKCANFD_TX_START_THRESHOLD)) { |
| if (net_ratelimit()) |
| netdev_info(priv->ndev, |
| "Stopping tx-queue (tx_head=0x%08x, tx_tail=0x%08x, tx_pending=%d)\n", |
| priv->tx_head, priv->tx_tail, |
| rkcanfd_get_tx_pending(priv)); |
| |
| return NETDEV_TX_BUSY; |
| } |
| |
| cfd = (struct canfd_frame *)skb->data; |
| |
| if (cfd->can_id & CAN_EFF_FLAG) { |
| reg_frameinfo = RKCANFD_REG_FD_FRAMEINFO_FRAME_FORMAT; |
| reg_id = FIELD_PREP(RKCANFD_REG_FD_ID_EFF, cfd->can_id); |
| } else { |
| reg_frameinfo = 0; |
| reg_id = FIELD_PREP(RKCANFD_REG_FD_ID_SFF, cfd->can_id); |
| } |
| |
| if (cfd->can_id & CAN_RTR_FLAG) |
| reg_frameinfo |= RKCANFD_REG_FD_FRAMEINFO_RTR; |
| |
| if (can_is_canfd_skb(skb)) { |
| reg_frameinfo |= RKCANFD_REG_FD_FRAMEINFO_FDF; |
| |
| if (cfd->flags & CANFD_BRS) |
| reg_frameinfo |= RKCANFD_REG_FD_FRAMEINFO_BRS; |
| |
| reg_frameinfo |= FIELD_PREP(RKCANFD_REG_FD_FRAMEINFO_DATA_LENGTH, |
| can_fd_len2dlc(cfd->len)); |
| } else { |
| reg_frameinfo |= FIELD_PREP(RKCANFD_REG_FD_FRAMEINFO_DATA_LENGTH, |
| cfd->len); |
| } |
| |
| tx_head = rkcanfd_get_tx_head(priv); |
| reg_cmd = RKCANFD_REG_CMD_TX_REQ(tx_head); |
| |
| rkcanfd_write(priv, RKCANFD_REG_FD_TXFRAMEINFO, reg_frameinfo); |
| rkcanfd_write(priv, RKCANFD_REG_FD_TXID, reg_id); |
| for (i = 0; i < cfd->len; i += 4) |
| rkcanfd_write(priv, RKCANFD_REG_FD_TXDATA0 + i, |
| *(u32 *)(cfd->data + i)); |
| |
| 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); |
| |
| WRITE_ONCE(priv->tx_head, priv->tx_head + 1); |
| |
| rkcanfd_start_xmit_write_cmd(priv, reg_cmd); |
| |
| netif_subqueue_maybe_stop(priv->ndev, 0, |
| rkcanfd_get_effective_tx_free(priv), |
| RKCANFD_TX_STOP_THRESHOLD, |
| RKCANFD_TX_START_THRESHOLD); |
| |
| return NETDEV_TX_OK; |
| } |
| |
| void rkcanfd_handle_tx_done_one(struct rkcanfd_priv *priv, const u32 ts, |
| unsigned int *frame_len_p) |
| { |
| struct net_device_stats *stats = &priv->ndev->stats; |
| unsigned int tx_tail; |
| struct sk_buff *skb; |
| |
| tx_tail = rkcanfd_get_tx_tail(priv); |
| skb = priv->can.echo_skb[tx_tail]; |
| |
| /* Manual handling of CAN Bus Error counters. See |
| * rkcanfd_get_corrected_berr_counter() for detailed |
| * explanation. |
| */ |
| if (priv->bec.txerr) |
| priv->bec.txerr--; |
| |
| if (skb) |
| rkcanfd_skb_set_timestamp(priv, skb, ts); |
| stats->tx_bytes += |
| can_rx_offload_get_echo_skb_queue_timestamp(&priv->offload, |
| tx_tail, ts, |
| frame_len_p); |
| stats->tx_packets++; |
| } |