Commit 9d56d4aa authored by Marc Kleine-Budde's avatar Marc Kleine-Budde

Merge patch series "can: rockchip_canfd: add support for CAN-FD IP core found on Rockchip RK3568"

Marc Kleine-Budde <mkl@pengutronix.de> says:

This series adds support for the CAN-FD IP core found on the Rockchip
RK3568.

The IP core is a bit complicated and has several documented errata.
The driver is added in several stages, first the base driver including
the RX-path. Then several workarounds for errata and the TX-path, and
finally features like hardware time stamping, loop-back mode and
bus error reporting.

Link: https://patch.msgid.link/20240904-rockchip-canfd-v5-0-8ae22bcb27cc@pengutronix.de
[mkl: removed DTS patches]
Signed-off-by: default avatarMarc Kleine-Budde <mkl@pengutronix.de>
parents 3d4d0fa4 e3b5fa0f
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/net/can/rockchip,rk3568v2-canfd.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title:
Rockchip CAN-FD controller
maintainers:
- Marc Kleine-Budde <mkl@pengutronix.de>
allOf:
- $ref: can-controller.yaml#
properties:
compatible:
oneOf:
- const: rockchip,rk3568v2-canfd
- items:
- const: rockchip,rk3568v3-canfd
- const: rockchip,rk3568v2-canfd
reg:
maxItems: 1
interrupts:
maxItems: 1
clocks:
maxItems: 2
clock-names:
items:
- const: baud
- const: pclk
resets:
maxItems: 2
reset-names:
items:
- const: core
- const: apb
required:
- compatible
- reg
- interrupts
- clocks
- resets
additionalProperties: false
examples:
- |
#include <dt-bindings/clock/rk3568-cru.h>
#include <dt-bindings/interrupt-controller/arm-gic.h>
#include <dt-bindings/interrupt-controller/irq.h>
soc {
#address-cells = <2>;
#size-cells = <2>;
can@fe570000 {
compatible = "rockchip,rk3568v2-canfd";
reg = <0x0 0xfe570000 0x0 0x1000>;
interrupts = <GIC_SPI 1 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cru CLK_CAN0>, <&cru PCLK_CAN0>;
clock-names = "baud", "pclk";
resets = <&cru SRST_CAN0>, <&cru SRST_P_CAN0>;
reset-names = "core", "apb";
};
};
......@@ -19730,6 +19730,14 @@ F: Documentation/ABI/*/sysfs-driver-hid-roccat*
F: drivers/hid/hid-roccat*
F: include/linux/hid-roccat*
ROCKCHIP CAN-FD DRIVER
M: Marc Kleine-Budde <mkl@pengutronix.de>
R: kernel@pengutronix.de
L: linux-can@vger.kernel.org
S: Maintained
F: Documentation/devicetree/bindings/net/can/rockchip,rk3568v2-canfd.yaml
F: drivers/net/can/rockchip/
ROCKCHIP CRYPTO DRIVERS
M: Corentin Labbe <clabbe@baylibre.com>
L: linux-crypto@vger.kernel.org
......
......@@ -225,6 +225,7 @@ source "drivers/net/can/m_can/Kconfig"
source "drivers/net/can/mscan/Kconfig"
source "drivers/net/can/peak_canfd/Kconfig"
source "drivers/net/can/rcar/Kconfig"
source "drivers/net/can/rockchip/Kconfig"
source "drivers/net/can/sja1000/Kconfig"
source "drivers/net/can/softing/Kconfig"
source "drivers/net/can/spi/Kconfig"
......
......@@ -10,6 +10,7 @@ obj-$(CONFIG_CAN_SLCAN) += slcan/
obj-y += dev/
obj-y += esd/
obj-y += rcar/
obj-y += rockchip/
obj-y += spi/
obj-y += usb/
obj-y += softing/
......
# SPDX-License-Identifier: GPL-2.0
config CAN_ROCKCHIP_CANFD
tristate "Rockchip CAN-FD controller"
depends on OF || COMPILE_TEST
select CAN_RX_OFFLOAD
help
Say Y here if you want to use CAN-FD controller found on
Rockchip SoCs.
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_CAN_ROCKCHIP_CANFD) += rockchip_canfd.o
rockchip_canfd-objs :=
rockchip_canfd-objs += rockchip_canfd-core.o
rockchip_canfd-objs += rockchip_canfd-ethtool.o
rockchip_canfd-objs += rockchip_canfd-rx.o
rockchip_canfd-objs += rockchip_canfd-timestamp.o
rockchip_canfd-objs += rockchip_canfd-tx.o
This diff is collapsed.
// SPDX-License-Identifier: GPL-2.0
//
// Copyright (c) 2023, 2024 Pengutronix,
// Marc Kleine-Budde <kernel@pengutronix.de>
//
#include <linux/ethtool.h>
#include "rockchip_canfd.h"
enum rkcanfd_stats_type {
RKCANFD_STATS_TYPE_RX_FIFO_EMPTY_ERRORS,
RKCANFD_STATS_TYPE_TX_EXTENDED_AS_STANDARD_ERRORS,
};
static const char rkcanfd_stats_strings[][ETH_GSTRING_LEN] = {
[RKCANFD_STATS_TYPE_RX_FIFO_EMPTY_ERRORS] = "rx_fifo_empty_errors",
[RKCANFD_STATS_TYPE_TX_EXTENDED_AS_STANDARD_ERRORS] = "tx_extended_as_standard_errors",
};
static void
rkcanfd_ethtool_get_strings(struct net_device *ndev, u32 stringset, u8 *buf)
{
switch (stringset) {
case ETH_SS_STATS:
memcpy(buf, rkcanfd_stats_strings,
sizeof(rkcanfd_stats_strings));
}
}
static int rkcanfd_ethtool_get_sset_count(struct net_device *netdev, int sset)
{
switch (sset) {
case ETH_SS_STATS:
return ARRAY_SIZE(rkcanfd_stats_strings);
default:
return -EOPNOTSUPP;
}
}
static void
rkcanfd_ethtool_get_ethtool_stats(struct net_device *ndev,
struct ethtool_stats *stats, u64 *data)
{
struct rkcanfd_priv *priv = netdev_priv(ndev);
struct rkcanfd_stats *rkcanfd_stats;
unsigned int start;
rkcanfd_stats = &priv->stats;
do {
start = u64_stats_fetch_begin(&rkcanfd_stats->syncp);
data[RKCANFD_STATS_TYPE_RX_FIFO_EMPTY_ERRORS] =
u64_stats_read(&rkcanfd_stats->rx_fifo_empty_errors);
data[RKCANFD_STATS_TYPE_TX_EXTENDED_AS_STANDARD_ERRORS] =
u64_stats_read(&rkcanfd_stats->tx_extended_as_standard_errors);
} while (u64_stats_fetch_retry(&rkcanfd_stats->syncp, start));
}
static const struct ethtool_ops rkcanfd_ethtool_ops = {
.get_ts_info = can_ethtool_op_get_ts_info_hwts,
.get_strings = rkcanfd_ethtool_get_strings,
.get_sset_count = rkcanfd_ethtool_get_sset_count,
.get_ethtool_stats = rkcanfd_ethtool_get_ethtool_stats,
};
void rkcanfd_ethtool_init(struct rkcanfd_priv *priv)
{
priv->ndev->ethtool_ops = &rkcanfd_ethtool_ops;
u64_stats_init(&priv->stats.syncp);
}
// 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_can_frame_header_equal(const struct canfd_frame *const cfd1,
const struct canfd_frame *const cfd2,
const bool is_canfd)
{
const u8 mask_flags = CANFD_BRS | CANFD_ESI | CANFD_FDF;
canid_t mask = CAN_EFF_FLAG;
if (canfd_sanitize_len(cfd1->len) != canfd_sanitize_len(cfd2->len))
return false;
if (!is_canfd)
mask |= CAN_RTR_FLAG;
if (cfd1->can_id & CAN_EFF_FLAG)
mask |= CAN_EFF_MASK;
else
mask |= CAN_SFF_MASK;
if ((cfd1->can_id & mask) != (cfd2->can_id & mask))
return false;
if (is_canfd &&
(cfd1->flags & mask_flags) != (cfd2->flags & mask_flags))
return false;
return true;
}
static bool rkcanfd_can_frame_data_equal(const struct canfd_frame *cfd1,
const struct canfd_frame *cfd2,
const bool is_canfd)
{
u8 len;
if (!is_canfd && (cfd1->can_id & CAN_RTR_FLAG))
return true;
len = canfd_sanitize_len(cfd1->len);
return !memcmp(cfd1->data, cfd2->data, len);
}
static unsigned int
rkcanfd_fifo_header_to_cfd_header(const struct rkcanfd_priv *priv,
const struct rkcanfd_fifo_header *header,
struct canfd_frame *cfd)
{
unsigned int len = sizeof(*cfd) - sizeof(cfd->data);
u8 dlc;
if (header->frameinfo & RKCANFD_REG_FD_FRAMEINFO_FRAME_FORMAT)
cfd->can_id = FIELD_GET(RKCANFD_REG_FD_ID_EFF, header->id) |
CAN_EFF_FLAG;
else
cfd->can_id = FIELD_GET(RKCANFD_REG_FD_ID_SFF, header->id);
dlc = FIELD_GET(RKCANFD_REG_FD_FRAMEINFO_DATA_LENGTH,
header->frameinfo);
/* CAN-FD */
if (header->frameinfo & RKCANFD_REG_FD_FRAMEINFO_FDF) {
cfd->len = can_fd_dlc2len(dlc);
/* The cfd is not allocated by alloc_canfd_skb(), so
* set CANFD_FDF here.
*/
cfd->flags |= CANFD_FDF;
if (header->frameinfo & RKCANFD_REG_FD_FRAMEINFO_BRS)
cfd->flags |= CANFD_BRS;
} else {
cfd->len = can_cc_dlc2len(dlc);
if (header->frameinfo & RKCANFD_REG_FD_FRAMEINFO_RTR) {
cfd->can_id |= CAN_RTR_FLAG;
return len;
}
}
return len + cfd->len;
}
static int rkcanfd_rxstx_filter(struct rkcanfd_priv *priv,
const struct canfd_frame *cfd_rx, const u32 ts,
bool *tx_done)
{
struct net_device_stats *stats = &priv->ndev->stats;
struct rkcanfd_stats *rkcanfd_stats = &priv->stats;
const struct canfd_frame *cfd_nominal;
const struct sk_buff *skb;
unsigned int tx_tail;
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 -ENOMSG;
}
cfd_nominal = (struct canfd_frame *)skb->data;
/* We RX'ed a frame identical to our pending TX frame. */
if (rkcanfd_can_frame_header_equal(cfd_rx, cfd_nominal,
cfd_rx->flags & CANFD_FDF) &&
rkcanfd_can_frame_data_equal(cfd_rx, cfd_nominal,
cfd_rx->flags & CANFD_FDF)) {
unsigned int frame_len;
rkcanfd_handle_tx_done_one(priv, ts, &frame_len);
WRITE_ONCE(priv->tx_tail, priv->tx_tail + 1);
netif_subqueue_completed_wake(priv->ndev, 0, 1, frame_len,
rkcanfd_get_effective_tx_free(priv),
RKCANFD_TX_START_THRESHOLD);
*tx_done = true;
return 0;
}
if (!(priv->devtype_data.quirks & RKCANFD_QUIRK_RK3568_ERRATUM_6))
return 0;
/* Erratum 6: Extended frames may be send as standard frames.
*
* Not affected if:
* - TX'ed a standard frame -or-
* - RX'ed an extended frame
*/
if (!(cfd_nominal->can_id & CAN_EFF_FLAG) ||
(cfd_rx->can_id & CAN_EFF_FLAG))
return 0;
/* Not affected if:
* - standard part and RTR flag of the TX'ed frame
* is not equal the CAN-ID and RTR flag of the RX'ed frame.
*/
if ((cfd_nominal->can_id & (CAN_RTR_FLAG | CAN_SFF_MASK)) !=
(cfd_rx->can_id & (CAN_RTR_FLAG | CAN_SFF_MASK)))
return 0;
/* Not affected if:
* - length is not the same
*/
if (cfd_nominal->len != cfd_rx->len)
return 0;
/* Not affected if:
* - the data of non RTR frames is different
*/
if (!(cfd_nominal->can_id & CAN_RTR_FLAG) &&
memcmp(cfd_nominal->data, cfd_rx->data, cfd_nominal->len))
return 0;
/* Affected by Erratum 6 */
u64_stats_update_begin(&rkcanfd_stats->syncp);
u64_stats_inc(&rkcanfd_stats->tx_extended_as_standard_errors);
u64_stats_update_end(&rkcanfd_stats->syncp);
/* Manual handling of CAN Bus Error counters. See
* rkcanfd_get_corrected_berr_counter() for detailed
* explanation.
*/
if (priv->bec.txerr)
priv->bec.txerr--;
*tx_done = true;
stats->tx_packets++;
stats->tx_errors++;
rkcanfd_xmit_retry(priv);
return 0;
}
static inline bool
rkcanfd_fifo_header_empty(const struct rkcanfd_fifo_header *header)
{
/* Erratum 5: If the FIFO is empty, we read the same value for
* all elements.
*/
return header->frameinfo == header->id &&
header->frameinfo == header->ts;
}
static int rkcanfd_handle_rx_int_one(struct rkcanfd_priv *priv)
{
struct net_device_stats *stats = &priv->ndev->stats;
struct canfd_frame cfd[1] = { }, *skb_cfd;
struct rkcanfd_fifo_header header[1] = { };
struct sk_buff *skb;
unsigned int len;
int err;
/* read header into separate struct and convert it later */
rkcanfd_read_rep(priv, RKCANFD_REG_RX_FIFO_RDATA,
header, sizeof(*header));
/* read data directly into cfd */
rkcanfd_read_rep(priv, RKCANFD_REG_RX_FIFO_RDATA,
cfd->data, sizeof(cfd->data));
/* Erratum 5: Counters for TXEFIFO and RXFIFO may be wrong */
if (rkcanfd_fifo_header_empty(header)) {
struct rkcanfd_stats *rkcanfd_stats = &priv->stats;
u64_stats_update_begin(&rkcanfd_stats->syncp);
u64_stats_inc(&rkcanfd_stats->rx_fifo_empty_errors);
u64_stats_update_end(&rkcanfd_stats->syncp);
return 0;
}
len = rkcanfd_fifo_header_to_cfd_header(priv, header, cfd);
/* Drop any received CAN-FD frames if CAN-FD mode is not
* requested.
*/
if (header->frameinfo & RKCANFD_REG_FD_FRAMEINFO_FDF &&
!(priv->can.ctrlmode & CAN_CTRLMODE_FD)) {
stats->rx_dropped++;
return 0;
}
if (rkcanfd_get_tx_pending(priv)) {
bool tx_done = false;
err = rkcanfd_rxstx_filter(priv, cfd, header->ts, &tx_done);
if (err)
return err;
if (tx_done && !(priv->can.ctrlmode & CAN_CTRLMODE_LOOPBACK))
return 0;
}
/* Manual handling of CAN Bus Error counters. See
* rkcanfd_get_corrected_berr_counter() for detailed
* explanation.
*/
if (priv->bec.rxerr)
priv->bec.rxerr = min(CAN_ERROR_PASSIVE_THRESHOLD,
priv->bec.rxerr) - 1;
if (header->frameinfo & RKCANFD_REG_FD_FRAMEINFO_FDF)
skb = alloc_canfd_skb(priv->ndev, &skb_cfd);
else
skb = alloc_can_skb(priv->ndev, (struct can_frame **)&skb_cfd);
if (!skb) {
stats->rx_dropped++;
return 0;
}
memcpy(skb_cfd, cfd, len);
rkcanfd_skb_set_timestamp(priv, skb, header->ts);
err = can_rx_offload_queue_timestamp(&priv->offload, skb, header->ts);
if (err)
stats->rx_fifo_errors++;
return 0;
}
static inline unsigned int
rkcanfd_rx_fifo_get_len(const struct rkcanfd_priv *priv)
{
const u32 reg = rkcanfd_read(priv, RKCANFD_REG_RX_FIFO_CTRL);
return FIELD_GET(RKCANFD_REG_RX_FIFO_CTRL_RX_FIFO_CNT, reg);
}
int rkcanfd_handle_rx_int(struct rkcanfd_priv *priv)
{
unsigned int len;
int err;
while ((len = rkcanfd_rx_fifo_get_len(priv))) {
err = rkcanfd_handle_rx_int_one(priv);
if (err)
return err;
}
return 0;
}
// SPDX-License-Identifier: GPL-2.0
//
// Copyright (c) 2023, 2024 Pengutronix,
// Marc Kleine-Budde <kernel@pengutronix.de>
//
#include <linux/clocksource.h>
#include "rockchip_canfd.h"
static u64 rkcanfd_timestamp_read(const struct cyclecounter *cc)
{
const struct rkcanfd_priv *priv = container_of(cc, struct rkcanfd_priv, cc);
return rkcanfd_get_timestamp(priv);
}
void rkcanfd_skb_set_timestamp(const struct rkcanfd_priv *priv,
struct sk_buff *skb, const u32 timestamp)
{
struct skb_shared_hwtstamps *hwtstamps = skb_hwtstamps(skb);
u64 ns;
ns = timecounter_cyc2time(&priv->tc, timestamp);
hwtstamps->hwtstamp = ns_to_ktime(ns);
}
static void rkcanfd_timestamp_work(struct work_struct *work)
{
const struct delayed_work *delayed_work = to_delayed_work(work);
struct rkcanfd_priv *priv;
priv = container_of(delayed_work, struct rkcanfd_priv, timestamp);
timecounter_read(&priv->tc);
schedule_delayed_work(&priv->timestamp, priv->work_delay_jiffies);
}
void rkcanfd_timestamp_init(struct rkcanfd_priv *priv)
{
const struct can_bittiming *dbt = &priv->can.data_bittiming;
const struct can_bittiming *bt = &priv->can.bittiming;
struct cyclecounter *cc = &priv->cc;
u32 bitrate, div, reg, rate;
u64 work_delay_ns;
u64 max_cycles;
/* At the standard clock rate of 300Mhz on the rk3658, the 32
* bit timer overflows every 14s. This means that we have to
* poll it quite often to avoid missing a wrap around.
*
* Divide it down to a reasonable rate, at least twice the bit
* rate.
*/
bitrate = max(bt->bitrate, dbt->bitrate);
div = min(DIV_ROUND_UP(priv->can.clock.freq, bitrate * 2),
FIELD_MAX(RKCANFD_REG_TIMESTAMP_CTRL_TIME_BASE_COUNTER_PRESCALE) + 1);
reg = FIELD_PREP(RKCANFD_REG_TIMESTAMP_CTRL_TIME_BASE_COUNTER_PRESCALE,
div - 1) |
RKCANFD_REG_TIMESTAMP_CTRL_TIME_BASE_COUNTER_ENABLE;
rkcanfd_write(priv, RKCANFD_REG_TIMESTAMP_CTRL, reg);
cc->read = rkcanfd_timestamp_read;
cc->mask = CYCLECOUNTER_MASK(32);
rate = priv->can.clock.freq / div;
clocks_calc_mult_shift(&cc->mult, &cc->shift, rate, NSEC_PER_SEC,
RKCANFD_TIMESTAMP_WORK_MAX_DELAY_SEC);
max_cycles = div_u64(ULLONG_MAX, cc->mult);
max_cycles = min(max_cycles, cc->mask);
work_delay_ns = clocksource_cyc2ns(max_cycles, cc->mult, cc->shift) / 3;
priv->work_delay_jiffies = nsecs_to_jiffies(work_delay_ns);
INIT_DELAYED_WORK(&priv->timestamp, rkcanfd_timestamp_work);
netdev_dbg(priv->ndev, "clock=%lu.%02luMHz bitrate=%lu.%02luMBit/s div=%u rate=%lu.%02luMHz mult=%u shift=%u delay=%lus\n",
priv->can.clock.freq / MEGA,
priv->can.clock.freq % MEGA / KILO / 10,
bitrate / MEGA,
bitrate % MEGA / KILO / 100,
div,
rate / MEGA,
rate % MEGA / KILO / 10,
cc->mult, cc->shift,
priv->work_delay_jiffies / HZ);
}
void rkcanfd_timestamp_start(struct rkcanfd_priv *priv)
{
timecounter_init(&priv->tc, &priv->cc, ktime_get_real_ns());
schedule_delayed_work(&priv->timestamp, priv->work_delay_jiffies);
}
void rkcanfd_timestamp_stop(struct rkcanfd_priv *priv)
{
cancel_delayed_work(&priv->timestamp);
}
void rkcanfd_timestamp_stop_sync(struct rkcanfd_priv *priv)
{
cancel_delayed_work_sync(&priv->timestamp);
}
// 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);
}
int 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++;
}
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment