From e0d3974dc51951879604eaa3dd61d76ce93379c2 Mon Sep 17 00:00:00 2001 From: Anton Tarasov Date: Fri, 26 Dec 2025 22:40:46 +0300 Subject: [PATCH 01/11] Init CAN driver Holtek ht42b416 for WB8 --- .../bindings/net/can/holtek,ht42b416.yaml | 55 ++ MAINTAINERS | 7 + .../allwinner/sun50i-h616-wirenboard85x.dtsi | 7 +- arch/arm64/configs/wb8.config | 1 + drivers/net/can/Kconfig | 11 + drivers/net/can/Makefile | 1 + drivers/net/can/ht42b416.c | 815 ++++++++++++++++++ 7 files changed, 896 insertions(+), 1 deletion(-) create mode 100644 Documentation/devicetree/bindings/net/can/holtek,ht42b416.yaml create mode 100644 drivers/net/can/ht42b416.c diff --git a/Documentation/devicetree/bindings/net/can/holtek,ht42b416.yaml b/Documentation/devicetree/bindings/net/can/holtek,ht42b416.yaml new file mode 100644 index 0000000000000..f1c759352a977 --- /dev/null +++ b/Documentation/devicetree/bindings/net/can/holtek,ht42b416.yaml @@ -0,0 +1,55 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/net/can/holtek,ht42b416.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Holtek HT42B416 UART to CAN bus bridge + +maintainers: + - Anton Tarasov + +description: + The Holtek HT42B416-x devices expose a CAN 2.0A/B controller via a UART + interface. The controller is configured with ASCII commands terminated by + carriage return characters and is normally attached to a SoC UART using the + serdev framework. + +allOf: + - $ref: /schemas/net/can/can-controller.yaml# + +properties: + compatible: + const: holtek,ht42b416 + + current-speed: + description: UART baud rate used to communicate with the bridge. + default: 115200 + + enable-gpios: + maxItems: 1 + description: + Optional GPIO used to drive the CE pin of the bridge. The line should be + driven high to enable the device and low to reset or power it down. + +required: + - compatible + +unevaluatedProperties: false + +examples: + - | + #include + + &uart4 { + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_mod3_txrx_uart>; + status = "okay"; + + can@0 { + compatible = "holtek,ht42b416"; + current-speed = <115200>; + enable-gpios = <&pio PG 10 GPIO_ACTIVE_HIGH>; + }; + }; +... diff --git a/MAINTAINERS b/MAINTAINERS index 1aabf1c15bb30..48bb0df4c44d5 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9413,6 +9413,13 @@ M: Alexander Shishkin S: Maintained F: drivers/hwtracing/ +HOLTEK HT42B416 CAN DRIVER +M: Anton Tarasov +L: linux-can@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/net/can/holtek,ht42b416.yaml +F: drivers/net/can/ht42b416.c + HARMONY SOUND DRIVER L: linux-parisc@vger.kernel.org S: Maintained diff --git a/arch/arm64/boot/dts/allwinner/sun50i-h616-wirenboard85x.dtsi b/arch/arm64/boot/dts/allwinner/sun50i-h616-wirenboard85x.dtsi index 51983e30bd513..9922bbbe9cf4c 100644 --- a/arch/arm64/boot/dts/allwinner/sun50i-h616-wirenboard85x.dtsi +++ b/arch/arm64/boot/dts/allwinner/sun50i-h616-wirenboard85x.dtsi @@ -1065,7 +1065,12 @@ pinctrl-0 = <&pinctrl_mod3_txrx_uart>; cts-override; - status = "disabled"; + status = "okay"; + + can_mod3: can@0 { + compatible = "holtek,ht42b416"; + current-speed = <115200>; + }; }; &uart5 { /* MOD4 */ diff --git a/arch/arm64/configs/wb8.config b/arch/arm64/configs/wb8.config index 91c34be0a840a..6e6c4b1491433 100644 --- a/arch/arm64/configs/wb8.config +++ b/arch/arm64/configs/wb8.config @@ -161,6 +161,7 @@ CONFIG_INET6_XFRM_MODE_BEET=m # CAN support CONFIG_CAN=y CONFIG_CAN_GS_USB=m +CONFIG_CAN_HT42B416=m # Docker CONFIG_BLK_CGROUP=y diff --git a/drivers/net/can/Kconfig b/drivers/net/can/Kconfig index eb410714afc20..113f2a100ff79 100644 --- a/drivers/net/can/Kconfig +++ b/drivers/net/can/Kconfig @@ -124,6 +124,17 @@ config CAN_CAN327 If this driver is built as a module, it will be called can327. +config CAN_HT42B416 + tristate "Holtek HT42B416 UART-to-CAN bridge" + depends on SERIAL_DEV_BUS && OF + help + Enable support for the Holtek HT42B416 UART to CAN bridge chips. + The driver uses the serdev framework to talk to the bridge via a SoC + UART and exposes a SocketCAN network interface. + + To compile this driver as a module, choose M here: the module will be + called ht42b416. + config CAN_FLEXCAN tristate "Support for Freescale FLEXCAN based chips" depends on OF || COLDFIRE || COMPILE_TEST diff --git a/drivers/net/can/Makefile b/drivers/net/can/Makefile index ff8f76295d13b..bb04f4e9e9b9e 100644 --- a/drivers/net/can/Makefile +++ b/drivers/net/can/Makefile @@ -16,6 +16,7 @@ obj-y += softing/ obj-$(CONFIG_CAN_AT91) += at91_can.o obj-$(CONFIG_CAN_BXCAN) += bxcan.o obj-$(CONFIG_CAN_CAN327) += can327.o +obj-$(CONFIG_CAN_HT42B416) += ht42b416.o obj-$(CONFIG_CAN_CC770) += cc770/ obj-$(CONFIG_CAN_C_CAN) += c_can/ obj-$(CONFIG_CAN_CTUCANFD) += ctucanfd/ diff --git a/drivers/net/can/ht42b416.c b/drivers/net/can/ht42b416.c new file mode 100644 index 0000000000000..c8110a18aed96 --- /dev/null +++ b/drivers/net/can/ht42b416.c @@ -0,0 +1,815 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Holtek HT42B416 UART-to-CAN bridge driver + * + * The controller is configured with ASCII commands terminated by carriage + * return characters. Payload bytes are transferred in binary form. + * + * Copyright (C) 2024 Wiren Board + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define HT42B416_MAX_DATA_LEN 8 +#define HT42B416_MAX_CMD_LEN 16 +#define HT42B416_MAX_FRAME_LEN (1 + 4 + 1 + HT42B416_MAX_DATA_LEN + 1) +#define HT42B416_RX_BUF_LEN 64 + +#define HT42B416_CMD_TIMEOUT_MS 250 +#define HT42B416_POWERUP_DELAY_US 200 +#define HT42B416_TX_TIMEOUT (2 * HZ) + +enum ht42b416_wait_ack { + HT42B416_WAIT_NONE, + HT42B416_WAIT_CR, +}; + +enum ht42b416_tx_expect { + HT42B416_TX_NONE, + HT42B416_TX_EXPECT_Z, + HT42B416_TX_EXPECT_CAP_Z, +}; + +enum ht42b416_status { + HT42B416_STATUS_TX_OK = 6, + HT42B416_STATUS_BUS_OFF = -8, + HT42B416_STATUS_ERR_CRC = -10, + HT42B416_STATUS_ERR_BIT1 = -11, + HT42B416_STATUS_ERR_BIT0 = -12, + HT42B416_STATUS_ERR_ACK = -13, + HT42B416_STATUS_ERR_FORM = -14, + HT42B416_STATUS_ERR_STUFF = -15, + HT42B416_STATUS_ERR_UNKNOWN = -16, +}; + +struct ht42b416_priv { + struct can_priv can; + struct serdev_device *serdev; + struct gpio_desc *enable_gpiod; + + struct work_struct tx_work; + spinlock_t tx_lock; /* protects below fields */ + u8 tx_buf[HT42B416_MAX_FRAME_LEN]; + size_t tx_len; + size_t tx_pos; + enum ht42b416_tx_expect tx_expect; + u8 tx_dlc; + bool tx_busy; + + u8 rx_buf[HT42B416_RX_BUF_LEN]; + size_t rx_len; + + struct completion ack_complete; + enum ht42b416_wait_ack wait_ack; + struct mutex cmd_lock; /* serialises configuration commands */ + + u32 uart_baud; + bool running; +}; + +static const u32 ht42b416_bitrate_const[] = { + 5000, 10000, 20000, 50000, 100000, + 125000, 250000, 500000, 800000, 1000000, +}; + +static const u8 ht42b416_filter_all_std[] = { 'm', 0x00, 0x00 }; +static const u8 ht42b416_code_all_std[] = { 'M', 0x00, 0x00 }; +static const u8 ht42b416_filter_all_ext[] = { 'm', 0x00, 0x00, 0x00, 0x00 }; +static const u8 ht42b416_code_all_ext[] = { 'M', 0x00, 0x00, 0x00, 0x00 }; + +static int ht42b416_write_raw(struct ht42b416_priv *priv, + const u8 *buf, size_t len) +{ + int ret; + + ret = serdev_device_write(priv->serdev, buf, len, + msecs_to_jiffies(HT42B416_CMD_TIMEOUT_MS)); + if (ret < 0) + return ret; + if (ret != len) + return -ETIMEDOUT; + + return 0; +} + +static int ht42b416_send_cmd_locked(struct ht42b416_priv *priv, + const u8 *payload, size_t len, + enum ht42b416_wait_ack wait) +{ + u8 buffer[HT42B416_MAX_CMD_LEN]; + int ret; + + if (!len || len >= HT42B416_MAX_CMD_LEN) + return -EINVAL; + + memcpy(buffer, payload, len); + buffer[len++] = '\r'; + + if (wait != HT42B416_WAIT_NONE) { + reinit_completion(&priv->ack_complete); + WRITE_ONCE(priv->wait_ack, wait); + } + + ret = ht42b416_write_raw(priv, buffer, len); + if (ret || wait == HT42B416_WAIT_NONE) + goto out; + + if (!wait_for_completion_timeout(&priv->ack_complete, + msecs_to_jiffies(HT42B416_CMD_TIMEOUT_MS))) + ret = -ETIMEDOUT; + +out: + if (ret && wait != HT42B416_WAIT_NONE) + WRITE_ONCE(priv->wait_ack, HT42B416_WAIT_NONE); + + return ret; +} + +static int ht42b416_send_cmd(struct ht42b416_priv *priv, + const u8 *payload, size_t len, + enum ht42b416_wait_ack wait) +{ + int ret; + + mutex_lock(&priv->cmd_lock); + ret = ht42b416_send_cmd_locked(priv, payload, len, wait); + mutex_unlock(&priv->cmd_lock); + + return ret; +} + +static int ht42b416_bitrate_to_code(u32 bitrate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ht42b416_bitrate_const); i++) + if (ht42b416_bitrate_const[i] == bitrate) + return i; + + return -EINVAL; +} + +static void ht42b416_abort_tx(struct ht42b416_priv *priv) +{ + struct net_device *ndev = priv->can.dev; + unsigned long flags; + + spin_lock_irqsave(&priv->tx_lock, flags); + priv->tx_len = 0; + priv->tx_pos = 0; + priv->tx_expect = HT42B416_TX_NONE; + priv->tx_busy = false; + spin_unlock_irqrestore(&priv->tx_lock, flags); + + can_free_echo_skb(ndev, 0, NULL); + netif_wake_queue(ndev); +} + +static const struct ethtool_ops ht42b416_ethtool_ops = { + .get_ts_info = ethtool_op_get_ts_info, +}; + +static int ht42b416_hw_stop(struct ht42b416_priv *priv) +{ + static const u8 close_cmd[] = { 'C' }; + + if (!priv->running) + goto disable_gpio; + + netif_stop_queue(priv->can.dev); + ht42b416_abort_tx(priv); + + ht42b416_send_cmd(priv, close_cmd, sizeof(close_cmd), + HT42B416_WAIT_CR); + priv->running = false; + +disable_gpio: + if (priv->enable_gpiod) + gpiod_set_value_cansleep(priv->enable_gpiod, 0); + + priv->can.state = CAN_STATE_STOPPED; + + return 0; +} + +static int ht42b416_hw_start(struct ht42b416_priv *priv) +{ + struct net_device *ndev = priv->can.dev; + u8 cmd[5]; + int ret, code; + u8 open_cmd; + + if (priv->running) + return 0; + + if (priv->enable_gpiod) { + gpiod_set_value_cansleep(priv->enable_gpiod, 1); + usleep_range(HT42B416_POWERUP_DELAY_US, + HT42B416_POWERUP_DELAY_US + 200); + } + + priv->rx_len = 0; + + /* Close device to ensure clean state */ + cmd[0] = 'C'; + ret = ht42b416_send_cmd(priv, cmd, 1, HT42B416_WAIT_CR); + if (ret) + return ret; + + code = ht42b416_bitrate_to_code(priv->can.bittiming.bitrate); + if (code < 0) + return code; + + cmd[0] = 'S'; + cmd[1] = code; + ret = ht42b416_send_cmd(priv, cmd, 2, HT42B416_WAIT_CR); + if (ret) + return ret; + + ret = ht42b416_send_cmd(priv, ht42b416_filter_all_std, + sizeof(ht42b416_filter_all_std), + HT42B416_WAIT_CR); + if (ret) + return ret; + + ret = ht42b416_send_cmd(priv, ht42b416_code_all_std, + sizeof(ht42b416_code_all_std), + HT42B416_WAIT_CR); + if (ret) + return ret; + + ret = ht42b416_send_cmd(priv, ht42b416_filter_all_ext, + sizeof(ht42b416_filter_all_ext), + HT42B416_WAIT_CR); + if (ret) + return ret; + + ret = ht42b416_send_cmd(priv, ht42b416_code_all_ext, + sizeof(ht42b416_code_all_ext), + HT42B416_WAIT_CR); + if (ret) + return ret; + + if (priv->can.ctrlmode & CAN_CTRLMODE_LOOPBACK) + open_cmd = 'l'; + else if (priv->can.ctrlmode & CAN_CTRLMODE_LISTENONLY) + open_cmd = 'L'; + else + open_cmd = 'O'; + + ret = ht42b416_send_cmd(priv, &open_cmd, 1, HT42B416_WAIT_CR); + if (ret) + return ret; + + priv->tx_pos = 0; + priv->tx_len = 0; + priv->tx_expect = HT42B416_TX_NONE; + priv->tx_busy = false; + priv->running = true; + priv->can.state = CAN_STATE_ERROR_ACTIVE; + netif_wake_queue(ndev); + + return 0; +} + +static int ht42b416_tx_kick_locked(struct ht42b416_priv *priv) +{ + while (priv->tx_pos < priv->tx_len) { + int written; + + written = serdev_device_write_buf(priv->serdev, + priv->tx_buf + priv->tx_pos, + priv->tx_len - priv->tx_pos); + if (written < 0) { + netdev_err(priv->can.dev, + "unable to push UART data: %d\n", written); + priv->tx_pos = priv->tx_len; + return written; + } + + if (!written) + break; + + priv->tx_pos += written; + } + + return 0; +} + +static void ht42b416_tx_work(struct work_struct *work) +{ + struct ht42b416_priv *priv = + container_of(work, struct ht42b416_priv, tx_work); + unsigned long flags; + + spin_lock_irqsave(&priv->tx_lock, flags); + if (priv->tx_pos < priv->tx_len) + ht42b416_tx_kick_locked(priv); + spin_unlock_irqrestore(&priv->tx_lock, flags); +} + +static void ht42b416_tx_wakeup(struct serdev_device *serdev) +{ + struct ht42b416_priv *priv = serdev_device_get_drvdata(serdev); + + serdev_device_write_wakeup(serdev); + schedule_work(&priv->tx_work); +} + +static void ht42b416_finish_tx(struct ht42b416_priv *priv, u8 ack_byte) +{ + struct net_device *ndev = priv->can.dev; + unsigned long flags; + unsigned int tx_bytes; + + spin_lock_irqsave(&priv->tx_lock, flags); + if (!priv->tx_busy || priv->tx_pos < priv->tx_len) { + spin_unlock_irqrestore(&priv->tx_lock, flags); + return; + } + + if ((ack_byte != 'z' || priv->tx_expect != HT42B416_TX_EXPECT_Z) && + (ack_byte != 'Z' || priv->tx_expect != HT42B416_TX_EXPECT_CAP_Z)) + netdev_warn(ndev, "unexpected TX ack 0x%02x\n", ack_byte); + + priv->tx_busy = false; + priv->tx_expect = HT42B416_TX_NONE; + priv->tx_len = 0; + priv->tx_pos = 0; + spin_unlock_irqrestore(&priv->tx_lock, flags); + + tx_bytes = can_get_echo_skb(ndev, 0, NULL); + ndev->stats.tx_packets++; + ndev->stats.tx_bytes += tx_bytes; + + netif_wake_queue(ndev); +} + +static void ht42b416_handle_status(struct ht42b416_priv *priv, s8 status) +{ + struct net_device *ndev = priv->can.dev; + struct sk_buff *skb; + struct can_frame *cf; + + switch (status) { + case HT42B416_STATUS_TX_OK: + case HT42B416_STATUS_ERR_UNKNOWN: + return; + case HT42B416_STATUS_BUS_OFF: + if (priv->can.state != CAN_STATE_BUS_OFF) { + priv->can.can_stats.bus_off++; + priv->running = false; + can_bus_off(ndev); + } + return; + default: + break; + } + + skb = alloc_can_err_skb(ndev, &cf); + if (!skb) { + ndev->stats.rx_dropped++; + return; + } + + memset(cf->data, 0, CAN_ERR_DLC); + cf->can_id |= CAN_ERR_PROT | CAN_ERR_BUSERROR; + + switch (status) { + case HT42B416_STATUS_ERR_CRC: + cf->data[2] |= CAN_ERR_PROT_BIT; + cf->data[3] = CAN_ERR_PROT_LOC_CRC_SEQ; + break; + case HT42B416_STATUS_ERR_BIT1: + cf->data[2] |= CAN_ERR_PROT_BIT1; + break; + case HT42B416_STATUS_ERR_BIT0: + cf->data[2] |= CAN_ERR_PROT_BIT0; + break; + case HT42B416_STATUS_ERR_ACK: + cf->can_id |= CAN_ERR_ACK; + cf->data[3] = CAN_ERR_PROT_LOC_ACK; + break; + case HT42B416_STATUS_ERR_FORM: + cf->data[2] |= CAN_ERR_PROT_FORM; + break; + case HT42B416_STATUS_ERR_STUFF: + cf->data[2] |= CAN_ERR_PROT_STUFF; + break; + default: + break; + } + + priv->can.can_stats.bus_error++; + ndev->stats.rx_packets++; + ndev->stats.rx_bytes += cf->len; + netif_rx(skb); +} + +static void ht42b416_signal_ack(struct ht42b416_priv *priv) +{ + enum ht42b416_wait_ack wait; + + wait = READ_ONCE(priv->wait_ack); + if (wait == HT42B416_WAIT_CR) { + WRITE_ONCE(priv->wait_ack, HT42B416_WAIT_NONE); + complete(&priv->ack_complete); + } +} + +static int ht42b416_parse_frame(struct ht42b416_priv *priv, + const u8 *buf, size_t len) +{ + struct net_device *ndev = priv->can.dev; + struct sk_buff *skb; + struct can_frame *cf; + bool is_eff, is_rtr; + u8 dlc; + size_t min_len, idx; + u32 can_id; + + if (!len) + return -EINVAL; + + is_eff = buf[0] == 'T' || buf[0] == 'R'; + is_rtr = buf[0] == 'r' || buf[0] == 'R'; + + if (is_eff) { + min_len = 1 + 4 + 1; + if (len < min_len) + return -EINVAL; + can_id = ((u32)buf[1] << 24) | + ((u32)buf[2] << 16) | + ((u32)buf[3] << 8) | + buf[4]; + can_id &= CAN_EFF_MASK; + dlc = buf[5]; + idx = 6; + } else { + min_len = 1 + 2 + 1; + if (len < min_len) + return -EINVAL; + can_id = ((u32)buf[1] << 8) | buf[2]; + can_id &= CAN_SFF_MASK; + dlc = buf[3]; + idx = 4; + } + + if (dlc > CAN_MAX_DLEN) + return -EINVAL; + + if (!is_rtr && len != idx + dlc) + return -EINVAL; + + skb = alloc_can_skb(ndev, &cf); + if (!skb) { + ndev->stats.rx_dropped++; + return 0; + } + + cf->can_id = can_id; + if (is_eff) + cf->can_id |= CAN_EFF_FLAG; + if (is_rtr) + cf->can_id |= CAN_RTR_FLAG; + + cf->len = dlc; + if (!is_rtr && dlc) + memcpy(cf->data, &buf[idx], dlc); + + ndev->stats.rx_packets++; + ndev->stats.rx_bytes += cf->len; + netif_rx(skb); + + return 0; +} + +static void ht42b416_process_msg(struct ht42b416_priv *priv, + const u8 *buf, size_t len) +{ + struct net_device *ndev = priv->can.dev; + + if (!len) { + ht42b416_signal_ack(priv); + return; + } + + switch (buf[0]) { + case 't': + case 'T': + case 'r': + case 'R': + if (ht42b416_parse_frame(priv, buf, len) && net_ratelimit()) + netdev_warn(ndev, "invalid frame len=%zu\n", len); + break; + case 'z': + case 'Z': + ht42b416_finish_tx(priv, buf[0]); + break; + case 'F': + if (len >= 2) + ht42b416_handle_status(priv, (s8)buf[1]); + break; + default: + netdev_dbg(ndev, "dropping response 0x%02x len=%zu\n", + buf[0], len); + break; + } +} + +static size_t ht42b416_receive(struct serdev_device *serdev, + const u8 *data, size_t count) +{ + struct ht42b416_priv *priv = serdev_device_get_drvdata(serdev); + size_t i; + + for (i = 0; i < count; i++) { + u8 byte = data[i]; + + if (byte == '\r' || byte == '\n') { + if (priv->rx_len < HT42B416_RX_BUF_LEN) + ht42b416_process_msg(priv, + priv->rx_buf, + priv->rx_len); + else + netdev_warn(priv->can.dev, + "RX buffer overflow (%zu bytes)\n", + priv->rx_len); + priv->rx_len = 0; + continue; + } + + if (priv->rx_len >= HT42B416_RX_BUF_LEN) { + priv->rx_len = HT42B416_RX_BUF_LEN; + continue; + } + + priv->rx_buf[priv->rx_len++] = byte; + } + + return count; +} + +static const struct serdev_device_ops ht42b416_serdev_ops = { + .receive_buf = ht42b416_receive, + .write_wakeup = ht42b416_tx_wakeup, +}; + +static netdev_tx_t ht42b416_start_xmit(struct sk_buff *skb, + struct net_device *ndev) +{ + struct ht42b416_priv *priv = netdev_priv(ndev); + struct can_frame *cf = (struct can_frame *)skb->data; + unsigned long flags; + bool eff, rtr; + u8 *pos; + int err; + + if (can_dev_dropped_skb(ndev, skb)) + return NETDEV_TX_OK; + + eff = cf->can_id & CAN_EFF_FLAG; + rtr = cf->can_id & CAN_RTR_FLAG; + + spin_lock_irqsave(&priv->tx_lock, flags); + if (priv->tx_busy) { + netif_stop_queue(ndev); + spin_unlock_irqrestore(&priv->tx_lock, flags); + return NETDEV_TX_BUSY; + } + + pos = priv->tx_buf; + if (eff) { + u32 id = cf->can_id & CAN_EFF_MASK; + + *pos++ = rtr ? 'R' : 'T'; + *pos++ = id >> 24; + *pos++ = id >> 16; + *pos++ = id >> 8; + *pos++ = id; + } else { + u16 id = cf->can_id & CAN_SFF_MASK; + + *pos++ = rtr ? 'r' : 't'; + *pos++ = id >> 8; + *pos++ = id; + } + + *pos++ = cf->len; + + if (!rtr && cf->len) { + memcpy(pos, cf->data, cf->len); + pos += cf->len; + } + + *pos++ = '\r'; + + priv->tx_len = pos - priv->tx_buf; + priv->tx_pos = 0; + priv->tx_busy = true; + priv->tx_expect = eff ? HT42B416_TX_EXPECT_CAP_Z : HT42B416_TX_EXPECT_Z; + priv->tx_dlc = cf->len; + + netif_stop_queue(ndev); + can_put_echo_skb(skb, ndev, 0, 0); + + err = ht42b416_tx_kick_locked(priv); + if (err) { + priv->tx_busy = false; + priv->tx_len = 0; + priv->tx_pos = 0; + priv->tx_expect = HT42B416_TX_NONE; + spin_unlock_irqrestore(&priv->tx_lock, flags); + ndev->stats.tx_errors++; + can_free_echo_skb(ndev, 0, NULL); + netif_wake_queue(ndev); + return NETDEV_TX_OK; + } + + if (priv->tx_pos < priv->tx_len) + schedule_work(&priv->tx_work); + spin_unlock_irqrestore(&priv->tx_lock, flags); + + return NETDEV_TX_OK; +} + +static int ht42b416_open(struct net_device *ndev) +{ + struct ht42b416_priv *priv = netdev_priv(ndev); + int ret; + + ret = open_candev(ndev); + if (ret) + return ret; + + ret = ht42b416_hw_start(priv); + if (ret) { + close_candev(ndev); + return ret; + } + + netif_start_queue(ndev); + return 0; +} + +static int ht42b416_close(struct net_device *ndev) +{ + struct ht42b416_priv *priv = netdev_priv(ndev); + + netif_stop_queue(ndev); + ht42b416_hw_stop(priv); + close_candev(ndev); + + return 0; +} + +static void ht42b416_tx_timeout(struct net_device *ndev, + unsigned int txqueue) +{ + struct ht42b416_priv *priv = netdev_priv(ndev); + + netdev_warn(ndev, "TX timeout\n"); + ndev->stats.tx_errors++; + ht42b416_abort_tx(priv); +} + +static const struct net_device_ops ht42b416_netdev_ops = { + .ndo_open = ht42b416_open, + .ndo_stop = ht42b416_close, + .ndo_start_xmit = ht42b416_start_xmit, + .ndo_tx_timeout = ht42b416_tx_timeout, +}; + +static int ht42b416_set_mode(struct net_device *ndev, enum can_mode mode) +{ + struct ht42b416_priv *priv = netdev_priv(ndev); + + switch (mode) { + case CAN_MODE_START: + return ht42b416_hw_start(priv); + case CAN_MODE_STOP: + return ht42b416_hw_stop(priv); + default: + return -EOPNOTSUPP; + } +} + +static int ht42b416_probe(struct serdev_device *serdev) +{ + struct device *dev = &serdev->dev; + struct net_device *ndev; + struct ht42b416_priv *priv; + u32 baud = 115200; + int ret; + + ndev = alloc_candev(sizeof(*priv), 1); + if (!ndev) + return -ENOMEM; + + ndev->flags |= IFF_ECHO; /* we support local echo */ + + priv = netdev_priv(ndev); + priv->serdev = serdev; + priv->uart_baud = baud; + priv->running = false; + priv->rx_len = 0; + mutex_init(&priv->cmd_lock); + init_completion(&priv->ack_complete); + spin_lock_init(&priv->tx_lock); + INIT_WORK(&priv->tx_work, ht42b416_tx_work); + priv->wait_ack = HT42B416_WAIT_NONE; + + priv->enable_gpiod = devm_gpiod_get_optional(dev, "enable", GPIOD_OUT_LOW); + if (IS_ERR(priv->enable_gpiod)) { + ret = PTR_ERR(priv->enable_gpiod); + goto err_free; + } + + device_property_read_u32(dev, "current-speed", &priv->uart_baud); + + serdev_device_set_drvdata(serdev, priv); + serdev_device_set_client_ops(serdev, &ht42b416_serdev_ops); + + ret = devm_serdev_device_open(dev, serdev); + if (ret) + goto err_free; + + serdev_device_set_baudrate(serdev, priv->uart_baud); + serdev_device_set_flow_control(serdev, false); + serdev_device_set_parity(serdev, SERDEV_PARITY_NONE); + + priv->can.clock.freq = 0; + priv->can.ctrlmode_supported = CAN_CTRLMODE_LOOPBACK | + CAN_CTRLMODE_LISTENONLY; + priv->can.do_set_mode = ht42b416_set_mode; + priv->can.bitrate_const = ht42b416_bitrate_const; + priv->can.bitrate_const_cnt = ARRAY_SIZE(ht42b416_bitrate_const); + + ndev->netdev_ops = &ht42b416_netdev_ops; + ndev->ethtool_ops = &ht42b416_ethtool_ops; + ndev->watchdog_timeo = HT42B416_TX_TIMEOUT; + + SET_NETDEV_DEV(ndev, dev); + + ret = register_candev(ndev); + if (ret) + goto err_free; + + dev_info(dev, "Holtek HT42B416 bridge at %u baud\n", priv->uart_baud); + + return 0; + +err_free: + free_candev(ndev); + return ret; +} + +static void ht42b416_remove(struct serdev_device *serdev) +{ + struct ht42b416_priv *priv = serdev_device_get_drvdata(serdev); + struct net_device *ndev = priv->can.dev; + + cancel_work_sync(&priv->tx_work); + ht42b416_hw_stop(priv); + unregister_candev(ndev); + free_candev(ndev); +} + +static const struct of_device_id ht42b416_of_match[] = { + { .compatible = "holtek,ht42b416" }, + { } +}; +MODULE_DEVICE_TABLE(of, ht42b416_of_match); + +static struct serdev_device_driver ht42b416_driver = { + .driver = { + .name = "ht42b416", + .of_match_table = ht42b416_of_match, + }, + .probe = ht42b416_probe, + .remove = ht42b416_remove, +}; + +module_serdev_device_driver(ht42b416_driver); + +MODULE_AUTHOR("Anton Tarasov "); +MODULE_DESCRIPTION("Holtek HT42B416 UART-to-CAN bridge driver"); +MODULE_LICENSE("GPL"); From c0b4a924b163a85eecbc0395843da9fdc03364eb Mon Sep 17 00:00:00 2001 From: Anton Tarasov Date: Sat, 27 Dec 2025 00:58:16 +0300 Subject: [PATCH 02/11] Change slot for CAN: MOD3 to MOD1 --- .../dts/allwinner/sun50i-h616-wirenboard85x.dtsi | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/arch/arm64/boot/dts/allwinner/sun50i-h616-wirenboard85x.dtsi b/arch/arm64/boot/dts/allwinner/sun50i-h616-wirenboard85x.dtsi index 9922bbbe9cf4c..de1427405d12c 100644 --- a/arch/arm64/boot/dts/allwinner/sun50i-h616-wirenboard85x.dtsi +++ b/arch/arm64/boot/dts/allwinner/sun50i-h616-wirenboard85x.dtsi @@ -831,7 +831,12 @@ compatible = "wirenboard,wbec-uart"; reg = <0>; - status = "disabled"; + status = "okay"; + + can_mod1: can@0 { + compatible = "holtek,ht42b416"; + current-speed = <115200>; + }; }; wbec_uart_mod2: serial@1 { @@ -1065,12 +1070,7 @@ pinctrl-0 = <&pinctrl_mod3_txrx_uart>; cts-override; - status = "okay"; - - can_mod3: can@0 { - compatible = "holtek,ht42b416"; - current-speed = <115200>; - }; + status = "disabled"; }; &uart5 { /* MOD4 */ From 33cbdf682de582ccbc0cb557b43e300b2dd3e1d6 Mon Sep 17 00:00:00 2001 From: Anton Tarasov Date: Sat, 27 Dec 2025 01:32:39 +0300 Subject: [PATCH 03/11] Add debug mode --- drivers/net/can/ht42b416.c | 47 +++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/drivers/net/can/ht42b416.c b/drivers/net/can/ht42b416.c index c8110a18aed96..ecc7474684760 100644 --- a/drivers/net/can/ht42b416.c +++ b/drivers/net/can/ht42b416.c @@ -37,6 +37,10 @@ #define HT42B416_POWERUP_DELAY_US 200 #define HT42B416_TX_TIMEOUT (2 * HZ) +static bool ht42b416_debug; +module_param_named(debug, ht42b416_debug, bool, 0644); +MODULE_PARM_DESC(debug, "Enable verbose UART logging"); + enum ht42b416_wait_ack { HT42B416_WAIT_NONE, HT42B416_WAIT_CR, @@ -95,6 +99,24 @@ static const u8 ht42b416_code_all_std[] = { 'M', 0x00, 0x00 }; static const u8 ht42b416_filter_all_ext[] = { 'm', 0x00, 0x00, 0x00, 0x00 }; static const u8 ht42b416_code_all_ext[] = { 'M', 0x00, 0x00, 0x00, 0x00 }; +static void ht42b416_log_uart(struct device *dev, const char *prefix, + const u8 *buf, size_t len) +{ + char hex[HT42B416_RX_BUF_LEN * 3 + 1]; + size_t i, pos = 0; + + for (i = 0; i < len && pos + 3 < sizeof(hex); i++) + pos += scnprintf(hex + pos, sizeof(hex) - pos, + "%02x ", buf[i]); + + if (pos) + hex[pos - 1] = '\0'; + else + hex[0] = '\0'; + + dev_info(dev, "%s[%zu]: %s\n", prefix, len, hex); +} + static int ht42b416_write_raw(struct ht42b416_priv *priv, const u8 *buf, size_t len) { @@ -128,6 +150,9 @@ static int ht42b416_send_cmd_locked(struct ht42b416_priv *priv, WRITE_ONCE(priv->wait_ack, wait); } + if (ht42b416_debug) + ht42b416_log_uart(&priv->serdev->dev, "UART TX cmd ", buffer, len); + ret = ht42b416_write_raw(priv, buffer, len); if (ret || wait == HT42B416_WAIT_NONE) goto out; @@ -240,6 +265,9 @@ static int ht42b416_hw_start(struct ht42b416_priv *priv) cmd[0] = 'S'; cmd[1] = code; + if (ht42b416_debug) + dev_info(&priv->serdev->dev, "CAN bitrate %u -> S 0x%02x\n", + priv->can.bittiming.bitrate, code); ret = ht42b416_send_cmd(priv, cmd, 2, HT42B416_WAIT_CR); if (ret) return ret; @@ -507,6 +535,14 @@ static void ht42b416_process_msg(struct ht42b416_priv *priv, { struct net_device *ndev = priv->can.dev; + if (ht42b416_debug) { + if (len) + ht42b416_log_uart(&priv->serdev->dev, + "UART RX msg ", buf, len); + else + dev_info(&priv->serdev->dev, "UART RX ack \n"); + } + if (!len) { ht42b416_signal_ack(priv); return; @@ -628,6 +664,10 @@ static netdev_tx_t ht42b416_start_xmit(struct sk_buff *skb, priv->tx_expect = eff ? HT42B416_TX_EXPECT_CAP_Z : HT42B416_TX_EXPECT_Z; priv->tx_dlc = cf->len; + if (ht42b416_debug) + ht42b416_log_uart(&priv->serdev->dev, "UART TX frame ", + priv->tx_buf, priv->tx_len); + netif_stop_queue(ndev); can_put_echo_skb(skb, ndev, 0, 0); @@ -752,7 +792,12 @@ static int ht42b416_probe(struct serdev_device *serdev) if (ret) goto err_free; - serdev_device_set_baudrate(serdev, priv->uart_baud); + ret = serdev_device_set_baudrate(serdev, priv->uart_baud); + if (ret > 0 && ret != priv->uart_baud) + dev_warn(dev, "UART requested %u baud, got %d\n", + priv->uart_baud, ret); + if (ret > 0) + priv->uart_baud = ret; serdev_device_set_flow_control(serdev, false); serdev_device_set_parity(serdev, SERDEV_PARITY_NONE); From 744cfe58ea4687071f5d1b1080bb9b1583d1e916 Mon Sep 17 00:00:00 2001 From: Evgeny Boger Date: Sat, 27 Dec 2025 05:01:05 +0300 Subject: [PATCH 04/11] filters must be set after port is open, otherwise the IC will end up in some buggy state with a broken baudrate --- drivers/net/can/ht42b416.c | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/drivers/net/can/ht42b416.c b/drivers/net/can/ht42b416.c index ecc7474684760..9e85dd146cc23 100644 --- a/drivers/net/can/ht42b416.c +++ b/drivers/net/can/ht42b416.c @@ -253,6 +253,14 @@ static int ht42b416_hw_start(struct ht42b416_priv *priv) priv->rx_len = 0; + /* Reset chip just in case */ + cmd[0] = 'R'; + cmd[1] = 'S'; + cmd[2] = 'T'; + ret = ht42b416_send_cmd(priv, cmd, 3, HT42B416_WAIT_CR); + if (ret) + return ret; + /* Close device to ensure clean state */ cmd[0] = 'C'; ret = ht42b416_send_cmd(priv, cmd, 1, HT42B416_WAIT_CR); @@ -272,6 +280,20 @@ static int ht42b416_hw_start(struct ht42b416_priv *priv) if (ret) return ret; + if (priv->can.ctrlmode & CAN_CTRLMODE_LOOPBACK) + open_cmd = 'l'; + else if (priv->can.ctrlmode & CAN_CTRLMODE_LISTENONLY) + open_cmd = 'L'; + else + open_cmd = 'O'; + + ret = ht42b416_send_cmd(priv, &open_cmd, 1, HT42B416_WAIT_CR); + if (ret) + return ret; + + /* Filters must be set after port is open, otherwise the IC will + * end up in some buggy state with a broken baudrate + */ ret = ht42b416_send_cmd(priv, ht42b416_filter_all_std, sizeof(ht42b416_filter_all_std), HT42B416_WAIT_CR); @@ -296,17 +318,6 @@ static int ht42b416_hw_start(struct ht42b416_priv *priv) if (ret) return ret; - if (priv->can.ctrlmode & CAN_CTRLMODE_LOOPBACK) - open_cmd = 'l'; - else if (priv->can.ctrlmode & CAN_CTRLMODE_LISTENONLY) - open_cmd = 'L'; - else - open_cmd = 'O'; - - ret = ht42b416_send_cmd(priv, &open_cmd, 1, HT42B416_WAIT_CR); - if (ret) - return ret; - priv->tx_pos = 0; priv->tx_len = 0; priv->tx_expect = HT42B416_TX_NONE; From fff670afbec957f0f92c40e11257c14a46876416 Mon Sep 17 00:00:00 2001 From: Anton Tarasov Date: Fri, 16 Jan 2026 02:26:52 +0300 Subject: [PATCH 05/11] Disable CAN driver for overlay --- .../boot/dts/allwinner/sun50i-h616-wirenboard85x.dtsi | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/arch/arm64/boot/dts/allwinner/sun50i-h616-wirenboard85x.dtsi b/arch/arm64/boot/dts/allwinner/sun50i-h616-wirenboard85x.dtsi index de1427405d12c..51983e30bd513 100644 --- a/arch/arm64/boot/dts/allwinner/sun50i-h616-wirenboard85x.dtsi +++ b/arch/arm64/boot/dts/allwinner/sun50i-h616-wirenboard85x.dtsi @@ -831,12 +831,7 @@ compatible = "wirenboard,wbec-uart"; reg = <0>; - status = "okay"; - - can_mod1: can@0 { - compatible = "holtek,ht42b416"; - current-speed = <115200>; - }; + status = "disabled"; }; wbec_uart_mod2: serial@1 { From 3a5b4f2b8ac4527ff53256dee920c5a9822d4766 Mon Sep 17 00:00:00 2001 From: Anton Tarasov Date: Fri, 16 Jan 2026 09:44:40 +0300 Subject: [PATCH 06/11] can: ht42b416: fix receive_buf signature for serdev --- drivers/net/can/ht42b416.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/can/ht42b416.c b/drivers/net/can/ht42b416.c index 9e85dd146cc23..29a4aa737138b 100644 --- a/drivers/net/can/ht42b416.c +++ b/drivers/net/can/ht42b416.c @@ -582,8 +582,8 @@ static void ht42b416_process_msg(struct ht42b416_priv *priv, } } -static size_t ht42b416_receive(struct serdev_device *serdev, - const u8 *data, size_t count) +static ssize_t ht42b416_receive(struct serdev_device *serdev, + const u8 *data, size_t count) { struct ht42b416_priv *priv = serdev_device_get_drvdata(serdev); size_t i; From 7f39bb4d7ebadb973f1ee3e4a3c2670c9bdd2891 Mon Sep 17 00:00:00 2001 From: Anton Tarasov Date: Fri, 16 Jan 2026 11:05:21 +0300 Subject: [PATCH 07/11] Bump version --- debian/changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/debian/changelog b/debian/changelog index 5f2bfc20500be..8e83689f4781d 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +linux-wb (6.8.0-wb149) stable; urgency=medium + + * wbe2-i-can: add support for CAN-module based on Holtek ht42b41 + + -- Anton Tarasov Fri, 16 Jan 2026 11:05:02 +0300 + linux-wb (6.8.0-wb148) stable; urgency=medium * wb8.5 HDMI: reject >30Hz 4K modes on H616 since T507-H can’t drive 4K60 reliably From 903164e4e22a2d9fccaaeb27b3db8f6462059fe5 Mon Sep 17 00:00:00 2001 From: Anton Tarasov Date: Mon, 19 Jan 2026 10:16:45 +0300 Subject: [PATCH 08/11] Correct maintainer email addr --- Documentation/devicetree/bindings/net/can/holtek,ht42b416.yaml | 2 +- debian/changelog | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/net/can/holtek,ht42b416.yaml b/Documentation/devicetree/bindings/net/can/holtek,ht42b416.yaml index f1c759352a977..c4e227fa0d341 100644 --- a/Documentation/devicetree/bindings/net/can/holtek,ht42b416.yaml +++ b/Documentation/devicetree/bindings/net/can/holtek,ht42b416.yaml @@ -7,7 +7,7 @@ $schema: http://devicetree.org/meta-schemas/core.yaml# title: Holtek HT42B416 UART to CAN bus bridge maintainers: - - Anton Tarasov + - Anton Tarasov description: The Holtek HT42B416-x devices expose a CAN 2.0A/B controller via a UART diff --git a/debian/changelog b/debian/changelog index 8e83689f4781d..d4f5d8bcaad32 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,6 +1,6 @@ linux-wb (6.8.0-wb149) stable; urgency=medium - * wbe2-i-can: add support for CAN-module based on Holtek ht42b41 + * wbe2-i-can: add support for CAN-module based on Holtek ht42b41 -- Anton Tarasov Fri, 16 Jan 2026 11:05:02 +0300 From cdaa73a09b1965efe262d4767072cdcc5af0d5f1 Mon Sep 17 00:00:00 2001 From: Anton Tarasov Date: Tue, 27 Jan 2026 12:42:07 +0300 Subject: [PATCH 09/11] can: ht42b416: reassemble UART RX stream Handle fragmented UART chunks by assembling complete frames before parsing. Add documentation comments for new helpers and RX/TX paths. --- drivers/net/can/ht42b416.c | 177 +++++++++++++++++++++++++++++++------ 1 file changed, 148 insertions(+), 29 deletions(-) diff --git a/drivers/net/can/ht42b416.c b/drivers/net/can/ht42b416.c index 29a4aa737138b..a6dec47ddfd96 100644 --- a/drivers/net/can/ht42b416.c +++ b/drivers/net/can/ht42b416.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -90,7 +91,7 @@ struct ht42b416_priv { }; static const u32 ht42b416_bitrate_const[] = { - 5000, 10000, 20000, 50000, 100000, + 5000, 10000, 20000, 50000, 100000, 125000, 250000, 500000, 800000, 1000000, }; @@ -106,8 +107,7 @@ static void ht42b416_log_uart(struct device *dev, const char *prefix, size_t i, pos = 0; for (i = 0; i < len && pos + 3 < sizeof(hex); i++) - pos += scnprintf(hex + pos, sizeof(hex) - pos, - "%02x ", buf[i]); + pos += scnprintf(hex + pos, sizeof(hex) - pos, "%02x ", buf[i]); if (pos) hex[pos - 1] = '\0'; @@ -122,8 +122,7 @@ static int ht42b416_write_raw(struct ht42b416_priv *priv, { int ret; - ret = serdev_device_write(priv->serdev, buf, len, - msecs_to_jiffies(HT42B416_CMD_TIMEOUT_MS)); + ret = serdev_device_write(priv->serdev, buf, len, msecs_to_jiffies(HT42B416_CMD_TIMEOUT_MS)); if (ret < 0) return ret; if (ret != len) @@ -212,6 +211,10 @@ static const struct ethtool_ops ht42b416_ethtool_ops = { .get_ts_info = ethtool_op_get_ts_info, }; +/** + * ht42b416_hw_stop - stop the UART-to-CAN bridge and disable the port + * @priv: device private data + */ static int ht42b416_hw_stop(struct ht42b416_priv *priv) { static const u8 close_cmd[] = { 'C' }; @@ -222,8 +225,7 @@ static int ht42b416_hw_stop(struct ht42b416_priv *priv) netif_stop_queue(priv->can.dev); ht42b416_abort_tx(priv); - ht42b416_send_cmd(priv, close_cmd, sizeof(close_cmd), - HT42B416_WAIT_CR); + ht42b416_send_cmd(priv, close_cmd, sizeof(close_cmd), HT42B416_WAIT_CR); priv->running = false; disable_gpio: @@ -235,6 +237,10 @@ static int ht42b416_hw_stop(struct ht42b416_priv *priv) return 0; } +/** + * ht42b416_hw_start - initialize and open the UART-to-CAN bridge + * @priv: device private data + */ static int ht42b416_hw_start(struct ht42b416_priv *priv) { struct net_device *ndev = priv->can.dev; @@ -402,6 +408,11 @@ static void ht42b416_finish_tx(struct ht42b416_priv *priv, u8 ack_byte) netif_wake_queue(ndev); } +/** + * ht42b416_handle_status - handle status/error notifications from the chip + * @priv: device private data + * @status: status code returned by the bridge + */ static void ht42b416_handle_status(struct ht42b416_priv *priv, s8 status) { struct net_device *ndev = priv->can.dev; @@ -474,6 +485,12 @@ static void ht42b416_signal_ack(struct ht42b416_priv *priv) } } +/** + * ht42b416_parse_frame - decode a complete CAN frame from UART payload + * @priv: device private data + * @buf: message payload (no trailing CR/LF) + * @len: payload length + */ static int ht42b416_parse_frame(struct ht42b416_priv *priv, const u8 *buf, size_t len) { @@ -541,6 +558,15 @@ static int ht42b416_parse_frame(struct ht42b416_priv *priv, return 0; } +/** + * ht42b416_process_msg - handle one complete message from the device + * @priv: device private data + * @buf: message payload (without trailing CR/LF) + * @len: payload length + * + * The function expects a fully assembled message. It does not try to + * reassemble fragmented UART data; that is done by ht42b416_try_parse(). + */ static void ht42b416_process_msg(struct ht42b416_priv *priv, const u8 *buf, size_t len) { @@ -582,6 +608,106 @@ static void ht42b416_process_msg(struct ht42b416_priv *priv, } } +/** + * ht42b416_rx_consume - drop consumed bytes from the RX buffer + * @priv: device private data + * @len: number of bytes to remove from the front + */ +static void ht42b416_rx_consume(struct ht42b416_priv *priv, size_t len) +{ + if (len >= priv->rx_len) { + priv->rx_len = 0; + return; + } + + memmove(priv->rx_buf, priv->rx_buf + len, priv->rx_len - len); + priv->rx_len -= len; +} + +/** + * ht42b416_try_parse - attempt to parse one complete message from RX buffer + * @priv: device private data + * + * Why it is needed: the UART delivers a byte stream in uneven chunks, and + * under load a single CAN response can be split across multiple receive + * callbacks. The old logic treated each chunk as a full message and ended + * up parsing partial frames (leading to "invalid frame len" and data shifts). + * + * What it does: it looks at the first byte, derives the expected message + * length (using DLC for CAN frames), and only passes a message to + * ht42b416_process_msg() once the full payload is present. Otherwise it + * keeps the data in the buffer and waits for more bytes. + * + * Return: true if a message was consumed (success or drop), false if more + * data is needed. + */ +static bool ht42b416_try_parse(struct ht42b416_priv *priv) +{ + struct net_device *ndev = priv->can.dev; + struct device *dev = &priv->serdev->dev; + size_t msg_len = 0; + size_t hdr_len; + bool is_eff, is_rtr; + u8 start; + u8 dlc; + + if (!priv->rx_len) + return false; + + start = priv->rx_buf[0]; + + if (start == '\r' || start == '\n') { + ht42b416_process_msg(priv, priv->rx_buf, 0); + ht42b416_rx_consume(priv, 1); + return true; + } + + switch (start) { + case 'z': + case 'Z': + msg_len = 1; + break; + case 'F': + msg_len = 2; + break; + case 't': + case 'r': + case 'T': + case 'R': + is_eff = start == 'T' || start == 'R'; + is_rtr = start == 'r' || start == 'R'; + hdr_len = is_eff ? (1 + 4 + 1) : (1 + 2 + 1); + if (priv->rx_len < hdr_len) + return false; + dlc = priv->rx_buf[hdr_len - 1]; + if (dlc > CAN_MAX_DLEN) { + if (net_ratelimit()) + netdev_warn(ndev, "invalid DLC %u\n", dlc); + ht42b416_rx_consume(priv, 1); + return true; + } + msg_len = hdr_len + (is_rtr ? 0 : dlc); + break; + default: + if (net_ratelimit()) + netdev_dbg(ndev, "dropping response 0x%02x len=%zu\n", + start, priv->rx_len); + ht42b416_rx_consume(priv, 1); + return true; + } + + if (priv->rx_len < msg_len) { + if (ht42b416_debug && net_ratelimit()) + dev_info(dev, "UART RX partial type %c have %zu need %zu\n", + start, priv->rx_len, msg_len); + return false; + } + + ht42b416_process_msg(priv, priv->rx_buf, msg_len); + ht42b416_rx_consume(priv, msg_len); + return true; +} + static ssize_t ht42b416_receive(struct serdev_device *serdev, const u8 *data, size_t count) { @@ -589,27 +715,17 @@ static ssize_t ht42b416_receive(struct serdev_device *serdev, size_t i; for (i = 0; i < count; i++) { - u8 byte = data[i]; - - if (byte == '\r' || byte == '\n') { - if (priv->rx_len < HT42B416_RX_BUF_LEN) - ht42b416_process_msg(priv, - priv->rx_buf, - priv->rx_len); - else + if (priv->rx_len >= HT42B416_RX_BUF_LEN) { + if (net_ratelimit()) netdev_warn(priv->can.dev, - "RX buffer overflow (%zu bytes)\n", - priv->rx_len); + "RX buffer overflow (%zu bytes)\n", + priv->rx_len); priv->rx_len = 0; - continue; - } - - if (priv->rx_len >= HT42B416_RX_BUF_LEN) { - priv->rx_len = HT42B416_RX_BUF_LEN; - continue; } - priv->rx_buf[priv->rx_len++] = byte; + priv->rx_buf[priv->rx_len++] = data[i]; + while (ht42b416_try_parse(priv)) + ; } return count; @@ -620,6 +736,11 @@ static const struct serdev_device_ops ht42b416_serdev_ops = { .write_wakeup = ht42b416_tx_wakeup, }; +/** + * ht42b416_start_xmit - enqueue a CAN frame for UART transmission + * @skb: CAN skb to transmit + * @ndev: net device + */ static netdev_tx_t ht42b416_start_xmit(struct sk_buff *skb, struct net_device *ndev) { @@ -677,7 +798,7 @@ static netdev_tx_t ht42b416_start_xmit(struct sk_buff *skb, if (ht42b416_debug) ht42b416_log_uart(&priv->serdev->dev, "UART TX frame ", - priv->tx_buf, priv->tx_len); + priv->tx_buf, priv->tx_len); netif_stop_queue(ndev); can_put_echo_skb(skb, ndev, 0, 0); @@ -805,16 +926,14 @@ static int ht42b416_probe(struct serdev_device *serdev) ret = serdev_device_set_baudrate(serdev, priv->uart_baud); if (ret > 0 && ret != priv->uart_baud) - dev_warn(dev, "UART requested %u baud, got %d\n", - priv->uart_baud, ret); + dev_warn(dev, "UART requested %u baud, got %d\n", priv->uart_baud, ret); if (ret > 0) priv->uart_baud = ret; serdev_device_set_flow_control(serdev, false); serdev_device_set_parity(serdev, SERDEV_PARITY_NONE); priv->can.clock.freq = 0; - priv->can.ctrlmode_supported = CAN_CTRLMODE_LOOPBACK | - CAN_CTRLMODE_LISTENONLY; + priv->can.ctrlmode_supported = CAN_CTRLMODE_LOOPBACK | CAN_CTRLMODE_LISTENONLY; priv->can.do_set_mode = ht42b416_set_mode; priv->can.bitrate_const = ht42b416_bitrate_const; priv->can.bitrate_const_cnt = ARRAY_SIZE(ht42b416_bitrate_const); From 096f2aa7ac5b340b12cbceeef3559ebe008fa5bd Mon Sep 17 00:00:00 2001 From: Anton Tarasov Date: Wed, 28 Jan 2026 03:47:26 +0300 Subject: [PATCH 10/11] can: ht42b416: add TX window + pacing for UART bridge Implement a small TX window with per-frame expected ACK and proper echo handling to tolerate uneven UART chunks. Add a delayed TX wake worker to pace frames and avoid WBEC UART overruns. Reset/stop paths now clear the FIFO and free all echo slots. Use a fixed echo size constant. --- drivers/net/can/ht42b416.c | 121 ++++++++++++++++++++++++++++++------- 1 file changed, 98 insertions(+), 23 deletions(-) diff --git a/drivers/net/can/ht42b416.c b/drivers/net/can/ht42b416.c index a6dec47ddfd96..a1be6ae59b636 100644 --- a/drivers/net/can/ht42b416.c +++ b/drivers/net/can/ht42b416.c @@ -33,11 +33,16 @@ #define HT42B416_MAX_CMD_LEN 16 #define HT42B416_MAX_FRAME_LEN (1 + 4 + 1 + HT42B416_MAX_DATA_LEN + 1) #define HT42B416_RX_BUF_LEN 64 +#define HT42B416_ECHO_SKB_MAX 8 +#define HT42B416_ECHO_SIZE 1 #define HT42B416_CMD_TIMEOUT_MS 250 #define HT42B416_POWERUP_DELAY_US 200 #define HT42B416_TX_TIMEOUT (2 * HZ) +/* Fixed TX delay (us) to keep WBEC UART stable under load. */ +#define HT42B416_TX_DELAY_US 5000 + static bool ht42b416_debug; module_param_named(debug, ht42b416_debug, bool, 0644); MODULE_PARM_DESC(debug, "Enable verbose UART logging"); @@ -71,13 +76,17 @@ struct ht42b416_priv { struct gpio_desc *enable_gpiod; struct work_struct tx_work; + struct work_struct tx_wake_work; spinlock_t tx_lock; /* protects below fields */ u8 tx_buf[HT42B416_MAX_FRAME_LEN]; size_t tx_len; size_t tx_pos; - enum ht42b416_tx_expect tx_expect; - u8 tx_dlc; bool tx_busy; + u8 tx_window; + u8 tx_fifo_head; + u8 tx_fifo_tail; + u8 tx_fifo_len; + enum ht42b416_tx_expect tx_expect_fifo[HT42B416_ECHO_SKB_MAX]; u8 rx_buf[HT42B416_RX_BUF_LEN]; size_t rx_len; @@ -195,15 +204,19 @@ static void ht42b416_abort_tx(struct ht42b416_priv *priv) { struct net_device *ndev = priv->can.dev; unsigned long flags; + u8 i; spin_lock_irqsave(&priv->tx_lock, flags); priv->tx_len = 0; priv->tx_pos = 0; - priv->tx_expect = HT42B416_TX_NONE; priv->tx_busy = false; + priv->tx_fifo_head = 0; + priv->tx_fifo_tail = 0; + priv->tx_fifo_len = 0; spin_unlock_irqrestore(&priv->tx_lock, flags); - can_free_echo_skb(ndev, 0, NULL); + for (i = 0; i < priv->tx_window; i++) + can_free_echo_skb(ndev, i, NULL); netif_wake_queue(ndev); } @@ -222,6 +235,7 @@ static int ht42b416_hw_stop(struct ht42b416_priv *priv) if (!priv->running) goto disable_gpio; + cancel_work_sync(&priv->tx_wake_work); netif_stop_queue(priv->can.dev); ht42b416_abort_tx(priv); @@ -326,8 +340,10 @@ static int ht42b416_hw_start(struct ht42b416_priv *priv) priv->tx_pos = 0; priv->tx_len = 0; - priv->tx_expect = HT42B416_TX_NONE; priv->tx_busy = false; + priv->tx_fifo_head = 0; + priv->tx_fifo_tail = 0; + priv->tx_fifo_len = 0; priv->running = true; priv->can.state = CAN_STATE_ERROR_ACTIVE; netif_wake_queue(ndev); @@ -337,6 +353,8 @@ static int ht42b416_hw_start(struct ht42b416_priv *priv) static int ht42b416_tx_kick_locked(struct ht42b416_priv *priv) { + struct net_device *ndev = priv->can.dev; + while (priv->tx_pos < priv->tx_len) { int written; @@ -356,6 +374,12 @@ static int ht42b416_tx_kick_locked(struct ht42b416_priv *priv) priv->tx_pos += written; } + if (priv->tx_pos >= priv->tx_len) { + priv->tx_busy = false; + if (priv->tx_fifo_len < priv->tx_window) + netif_wake_queue(ndev); + } + return 0; } @@ -371,6 +395,33 @@ static void ht42b416_tx_work(struct work_struct *work) spin_unlock_irqrestore(&priv->tx_lock, flags); } +/** + * ht42b416_tx_wake_work - re-enable TX queue after a pacing delay + * @work: work item embedded in the driver private data + * + * This worker enforces a small inter-frame pause, then checks (under + * tx_lock) that the device is still running and the TX window has room, + * and finally wakes the netdev queue outside the lock. + */ +static void ht42b416_tx_wake_work(struct work_struct *work) +{ + struct ht42b416_priv *priv = + container_of(work, struct ht42b416_priv, tx_wake_work); + struct net_device *ndev = priv->can.dev; + unsigned long flags; + bool wake = false; + + usleep_range(HT42B416_TX_DELAY_US, HT42B416_TX_DELAY_US + 50); + + spin_lock_irqsave(&priv->tx_lock, flags); + if (priv->running && priv->tx_fifo_len < priv->tx_window) + wake = true; + spin_unlock_irqrestore(&priv->tx_lock, flags); + + if (wake) + netif_wake_queue(ndev); +} + static void ht42b416_tx_wakeup(struct serdev_device *serdev) { struct ht42b416_priv *priv = serdev_device_get_drvdata(serdev); @@ -384,28 +435,32 @@ static void ht42b416_finish_tx(struct ht42b416_priv *priv, u8 ack_byte) struct net_device *ndev = priv->can.dev; unsigned long flags; unsigned int tx_bytes; + enum ht42b416_tx_expect expect; + u8 slot; spin_lock_irqsave(&priv->tx_lock, flags); - if (!priv->tx_busy || priv->tx_pos < priv->tx_len) { + if (!priv->tx_fifo_len) { spin_unlock_irqrestore(&priv->tx_lock, flags); return; } - if ((ack_byte != 'z' || priv->tx_expect != HT42B416_TX_EXPECT_Z) && - (ack_byte != 'Z' || priv->tx_expect != HT42B416_TX_EXPECT_CAP_Z)) + slot = priv->tx_fifo_tail; + expect = priv->tx_expect_fifo[slot]; + priv->tx_fifo_tail = (priv->tx_fifo_tail + 1) % priv->tx_window; + priv->tx_fifo_len--; + + if ((ack_byte != 'z' || expect != HT42B416_TX_EXPECT_Z) && + (ack_byte != 'Z' || expect != HT42B416_TX_EXPECT_CAP_Z)) netdev_warn(ndev, "unexpected TX ack 0x%02x\n", ack_byte); - priv->tx_busy = false; - priv->tx_expect = HT42B416_TX_NONE; - priv->tx_len = 0; - priv->tx_pos = 0; spin_unlock_irqrestore(&priv->tx_lock, flags); - tx_bytes = can_get_echo_skb(ndev, 0, NULL); + tx_bytes = can_get_echo_skb(ndev, slot, NULL); ndev->stats.tx_packets++; ndev->stats.tx_bytes += tx_bytes; - netif_wake_queue(ndev); + if (priv->tx_fifo_len < priv->tx_window) + schedule_work(&priv->tx_wake_work); } /** @@ -748,6 +803,8 @@ static netdev_tx_t ht42b416_start_xmit(struct sk_buff *skb, struct can_frame *cf = (struct can_frame *)skb->data; unsigned long flags; bool eff, rtr; + enum ht42b416_tx_expect expect; + u8 slot; u8 *pos; int err; @@ -756,14 +813,20 @@ static netdev_tx_t ht42b416_start_xmit(struct sk_buff *skb, eff = cf->can_id & CAN_EFF_FLAG; rtr = cf->can_id & CAN_RTR_FLAG; + expect = eff ? HT42B416_TX_EXPECT_CAP_Z : HT42B416_TX_EXPECT_Z; spin_lock_irqsave(&priv->tx_lock, flags); - if (priv->tx_busy) { + if (priv->tx_busy || priv->tx_fifo_len >= priv->tx_window) { netif_stop_queue(ndev); spin_unlock_irqrestore(&priv->tx_lock, flags); return NETDEV_TX_BUSY; } + slot = priv->tx_fifo_head; + priv->tx_fifo_head = (priv->tx_fifo_head + 1) % priv->tx_window; + priv->tx_fifo_len++; + priv->tx_expect_fifo[slot] = expect; + pos = priv->tx_buf; if (eff) { u32 id = cf->can_id & CAN_EFF_MASK; @@ -793,25 +856,25 @@ static netdev_tx_t ht42b416_start_xmit(struct sk_buff *skb, priv->tx_len = pos - priv->tx_buf; priv->tx_pos = 0; priv->tx_busy = true; - priv->tx_expect = eff ? HT42B416_TX_EXPECT_CAP_Z : HT42B416_TX_EXPECT_Z; - priv->tx_dlc = cf->len; if (ht42b416_debug) ht42b416_log_uart(&priv->serdev->dev, "UART TX frame ", priv->tx_buf, priv->tx_len); - netif_stop_queue(ndev); - can_put_echo_skb(skb, ndev, 0, 0); + if (priv->tx_fifo_len >= priv->tx_window) + netif_stop_queue(ndev); + can_put_echo_skb(skb, ndev, slot, 0); err = ht42b416_tx_kick_locked(priv); if (err) { priv->tx_busy = false; priv->tx_len = 0; priv->tx_pos = 0; - priv->tx_expect = HT42B416_TX_NONE; + priv->tx_fifo_head = slot; + priv->tx_fifo_len--; spin_unlock_irqrestore(&priv->tx_lock, flags); ndev->stats.tx_errors++; - can_free_echo_skb(ndev, 0, NULL); + can_free_echo_skb(ndev, slot, NULL); netif_wake_queue(ndev); return NETDEV_TX_OK; } @@ -892,7 +955,13 @@ static int ht42b416_probe(struct serdev_device *serdev) u32 baud = 115200; int ret; - ndev = alloc_candev(sizeof(*priv), 1); + /* + * WBEC UART can deliver uneven RX chunks under load, so we keep a + * single-frame TX window and reassemble RX data. For simplicity the + * same policy is used for all UARTs; if higher throughput is needed on + * SoC UARTs, consider applying the small TX window only to WBEC. + */ + ndev = alloc_candev(sizeof(*priv), HT42B416_ECHO_SIZE); if (!ndev) return -ENOMEM; @@ -903,10 +972,15 @@ static int ht42b416_probe(struct serdev_device *serdev) priv->uart_baud = baud; priv->running = false; priv->rx_len = 0; + priv->tx_window = HT42B416_ECHO_SIZE; + priv->tx_fifo_head = 0; + priv->tx_fifo_tail = 0; + priv->tx_fifo_len = 0; mutex_init(&priv->cmd_lock); init_completion(&priv->ack_complete); spin_lock_init(&priv->tx_lock); INIT_WORK(&priv->tx_work, ht42b416_tx_work); + INIT_WORK(&priv->tx_wake_work, ht42b416_tx_wake_work); priv->wait_ack = HT42B416_WAIT_NONE; priv->enable_gpiod = devm_gpiod_get_optional(dev, "enable", GPIOD_OUT_LOW); @@ -962,6 +1036,7 @@ static void ht42b416_remove(struct serdev_device *serdev) struct ht42b416_priv *priv = serdev_device_get_drvdata(serdev); struct net_device *ndev = priv->can.dev; + cancel_work_sync(&priv->tx_wake_work); cancel_work_sync(&priv->tx_work); ht42b416_hw_stop(priv); unregister_candev(ndev); @@ -985,6 +1060,6 @@ static struct serdev_device_driver ht42b416_driver = { module_serdev_device_driver(ht42b416_driver); -MODULE_AUTHOR("Anton Tarasov "); +MODULE_AUTHOR("Anton Tarasov "); MODULE_DESCRIPTION("Holtek HT42B416 UART-to-CAN bridge driver"); MODULE_LICENSE("GPL"); From 1a5392c13a92a0ad9f7a7fa4905687c3d5d32498 Mon Sep 17 00:00:00 2001 From: Anton Tarasov Date: Tue, 3 Feb 2026 16:12:12 +0300 Subject: [PATCH 11/11] =?UTF-8?q?can:=20ht42b416:=20Introduce=20a=20strict?= =?UTF-8?q?=20TX=20FSM=20(IDLE=20=E2=86=92=20WAIT=5FZ=20=E2=86=92=20WAIT?= =?UTF-8?q?=5FSTATUS=5FRESP)=20with=20a=20new=20TX=20queue=20and=20seriali?= =?UTF-8?q?zed=20sending;=20the=20next=20frame=20is=20sent=20only=20after?= =?UTF-8?q?=20Z=20and=20status=20F06.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove the artificial inter-frame delay; drop debug messages/parameters/log infrastructure. --- drivers/net/can/ht42b416.c | 694 +++++++++++++++++++++++++------------ 1 file changed, 475 insertions(+), 219 deletions(-) diff --git a/drivers/net/can/ht42b416.c b/drivers/net/can/ht42b416.c index a1be6ae59b636..1dd1e08370690 100644 --- a/drivers/net/can/ht42b416.c +++ b/drivers/net/can/ht42b416.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -33,19 +34,14 @@ #define HT42B416_MAX_CMD_LEN 16 #define HT42B416_MAX_FRAME_LEN (1 + 4 + 1 + HT42B416_MAX_DATA_LEN + 1) #define HT42B416_RX_BUF_LEN 64 -#define HT42B416_ECHO_SKB_MAX 8 #define HT42B416_ECHO_SIZE 1 #define HT42B416_CMD_TIMEOUT_MS 250 #define HT42B416_POWERUP_DELAY_US 200 #define HT42B416_TX_TIMEOUT (2 * HZ) +#define HT42B416_TX_RESP_TIMEOUT_MS 500 -/* Fixed TX delay (us) to keep WBEC UART stable under load. */ -#define HT42B416_TX_DELAY_US 5000 - -static bool ht42b416_debug; -module_param_named(debug, ht42b416_debug, bool, 0644); -MODULE_PARM_DESC(debug, "Enable verbose UART logging"); +#define HT42B416_TX_QUEUE_LEN 64 enum ht42b416_wait_ack { HT42B416_WAIT_NONE, @@ -70,23 +66,45 @@ enum ht42b416_status { HT42B416_STATUS_ERR_UNKNOWN = -16, }; +enum ht42b416_tx_stage { + HT42B416_TX_IDLE, + HT42B416_TX_WAIT_Z, + HT42B416_TX_WAIT_STATUS_RESP, +}; + +enum ht42b416_tx_event { + HT42B416_TX_EVT_KICK, + HT42B416_TX_EVT_Z, + HT42B416_TX_EVT_STATUS_RESP, +}; + +struct ht42b416_tx_actions { + bool kick; + bool arm_timeout; + bool cancel_timeout; +}; + struct ht42b416_priv { struct can_priv can; struct serdev_device *serdev; struct gpio_desc *enable_gpiod; struct work_struct tx_work; - struct work_struct tx_wake_work; + struct delayed_work tx_timeout_work; spinlock_t tx_lock; /* protects below fields */ u8 tx_buf[HT42B416_MAX_FRAME_LEN]; size_t tx_len; size_t tx_pos; bool tx_busy; - u8 tx_window; u8 tx_fifo_head; u8 tx_fifo_tail; u8 tx_fifo_len; - enum ht42b416_tx_expect tx_expect_fifo[HT42B416_ECHO_SKB_MAX]; + enum ht42b416_tx_expect tx_expect_fifo[HT42B416_ECHO_SIZE]; + struct sk_buff *tx_queue[HT42B416_TX_QUEUE_LEN]; + u8 tx_q_head; + u8 tx_q_tail; + u8 tx_q_len; + enum ht42b416_tx_stage tx_stage; u8 rx_buf[HT42B416_RX_BUF_LEN]; size_t rx_len; @@ -97,6 +115,7 @@ struct ht42b416_priv { u32 uart_baud; bool running; + }; static const u32 ht42b416_bitrate_const[] = { @@ -109,37 +128,43 @@ static const u8 ht42b416_code_all_std[] = { 'M', 0x00, 0x00 }; static const u8 ht42b416_filter_all_ext[] = { 'm', 0x00, 0x00, 0x00, 0x00 }; static const u8 ht42b416_code_all_ext[] = { 'M', 0x00, 0x00, 0x00, 0x00 }; -static void ht42b416_log_uart(struct device *dev, const char *prefix, - const u8 *buf, size_t len) -{ - char hex[HT42B416_RX_BUF_LEN * 3 + 1]; - size_t i, pos = 0; +static void ht42b416_kick_queue(struct ht42b416_priv *priv); +static void ht42b416_tx_fsm_locked(struct ht42b416_priv *priv, + enum ht42b416_tx_event ev, + struct ht42b416_tx_actions *act); +static void ht42b416_tx_timeout_arm(struct ht42b416_priv *priv); +static void ht42b416_tx_timeout_cancel(struct ht42b416_priv *priv); - for (i = 0; i < len && pos + 3 < sizeof(hex); i++) - pos += scnprintf(hex + pos, sizeof(hex) - pos, "%02x ", buf[i]); - - if (pos) - hex[pos - 1] = '\0'; - else - hex[0] = '\0'; - - dev_info(dev, "%s[%zu]: %s\n", prefix, len, hex); -} +/** + * ht42b416_write_raw - write a raw UART buffer with timeout handling + */ static int ht42b416_write_raw(struct ht42b416_priv *priv, const u8 *buf, size_t len) { - int ret; + unsigned long deadline = jiffies + msecs_to_jiffies(HT42B416_CMD_TIMEOUT_MS); + size_t pos = 0; - ret = serdev_device_write(priv->serdev, buf, len, msecs_to_jiffies(HT42B416_CMD_TIMEOUT_MS)); - if (ret < 0) - return ret; - if (ret != len) - return -ETIMEDOUT; + while (pos < len) { + int written = serdev_device_write_buf(priv->serdev, buf + pos, len - pos); + + if (written < 0) + return written; + if (!written) { + if (time_after(jiffies, deadline)) + return -ETIMEDOUT; + usleep_range(100, 200); + continue; + } + pos += written; + } return 0; } +/** + * ht42b416_send_cmd_locked - send a command with optional CR ack wait + */ static int ht42b416_send_cmd_locked(struct ht42b416_priv *priv, const u8 *payload, size_t len, enum ht42b416_wait_ack wait) @@ -158,9 +183,6 @@ static int ht42b416_send_cmd_locked(struct ht42b416_priv *priv, WRITE_ONCE(priv->wait_ack, wait); } - if (ht42b416_debug) - ht42b416_log_uart(&priv->serdev->dev, "UART TX cmd ", buffer, len); - ret = ht42b416_write_raw(priv, buffer, len); if (ret || wait == HT42B416_WAIT_NONE) goto out; @@ -176,6 +198,9 @@ static int ht42b416_send_cmd_locked(struct ht42b416_priv *priv, return ret; } +/** + * ht42b416_send_cmd - serialized command sender + */ static int ht42b416_send_cmd(struct ht42b416_priv *priv, const u8 *payload, size_t len, enum ht42b416_wait_ack wait) @@ -189,6 +214,9 @@ static int ht42b416_send_cmd(struct ht42b416_priv *priv, return ret; } +/** + * ht42b416_bitrate_to_code - map bitrate to chip configuration code + */ static int ht42b416_bitrate_to_code(u32 bitrate) { int i; @@ -200,12 +228,16 @@ static int ht42b416_bitrate_to_code(u32 bitrate) return -EINVAL; } +/** + * ht42b416_abort_tx - drop pending TX data and reset TX state + */ static void ht42b416_abort_tx(struct ht42b416_priv *priv) { struct net_device *ndev = priv->can.dev; unsigned long flags; u8 i; + ht42b416_tx_timeout_cancel(priv); spin_lock_irqsave(&priv->tx_lock, flags); priv->tx_len = 0; priv->tx_pos = 0; @@ -213,9 +245,20 @@ static void ht42b416_abort_tx(struct ht42b416_priv *priv) priv->tx_fifo_head = 0; priv->tx_fifo_tail = 0; priv->tx_fifo_len = 0; + priv->tx_stage = HT42B416_TX_IDLE; + while (priv->tx_q_len) { + struct sk_buff *skb = priv->tx_queue[priv->tx_q_tail]; + + priv->tx_queue[priv->tx_q_tail] = NULL; + priv->tx_q_tail = (priv->tx_q_tail + 1) % HT42B416_TX_QUEUE_LEN; + priv->tx_q_len--; + dev_kfree_skb_any(skb); + } + priv->tx_q_head = 0; + priv->tx_q_tail = 0; spin_unlock_irqrestore(&priv->tx_lock, flags); - for (i = 0; i < priv->tx_window; i++) + for (i = 0; i < HT42B416_ECHO_SIZE; i++) can_free_echo_skb(ndev, i, NULL); netif_wake_queue(ndev); } @@ -228,6 +271,9 @@ static const struct ethtool_ops ht42b416_ethtool_ops = { * ht42b416_hw_stop - stop the UART-to-CAN bridge and disable the port * @priv: device private data */ +/** + * ht42b416_hw_stop - stop the bridge and close the CAN channel + */ static int ht42b416_hw_stop(struct ht42b416_priv *priv) { static const u8 close_cmd[] = { 'C' }; @@ -235,7 +281,7 @@ static int ht42b416_hw_stop(struct ht42b416_priv *priv) if (!priv->running) goto disable_gpio; - cancel_work_sync(&priv->tx_wake_work); + cancel_delayed_work_sync(&priv->tx_timeout_work); netif_stop_queue(priv->can.dev); ht42b416_abort_tx(priv); @@ -255,6 +301,9 @@ static int ht42b416_hw_stop(struct ht42b416_priv *priv) * ht42b416_hw_start - initialize and open the UART-to-CAN bridge * @priv: device private data */ +/** + * ht42b416_hw_start - reset and configure the bridge for CAN operation + */ static int ht42b416_hw_start(struct ht42b416_priv *priv) { struct net_device *ndev = priv->can.dev; @@ -293,9 +342,6 @@ static int ht42b416_hw_start(struct ht42b416_priv *priv) cmd[0] = 'S'; cmd[1] = code; - if (ht42b416_debug) - dev_info(&priv->serdev->dev, "CAN bitrate %u -> S 0x%02x\n", - priv->can.bittiming.bitrate, code); ret = ht42b416_send_cmd(priv, cmd, 2, HT42B416_WAIT_CR); if (ret) return ret; @@ -351,16 +397,18 @@ static int ht42b416_hw_start(struct ht42b416_priv *priv) return 0; } +/** + * ht42b416_tx_kick_locked - push pending TX buffer to UART + */ static int ht42b416_tx_kick_locked(struct ht42b416_priv *priv) { - struct net_device *ndev = priv->can.dev; - while (priv->tx_pos < priv->tx_len) { int written; + size_t left = priv->tx_len - priv->tx_pos; written = serdev_device_write_buf(priv->serdev, priv->tx_buf + priv->tx_pos, - priv->tx_len - priv->tx_pos); + left); if (written < 0) { netdev_err(priv->can.dev, "unable to push UART data: %d\n", written); @@ -376,13 +424,14 @@ static int ht42b416_tx_kick_locked(struct ht42b416_priv *priv) if (priv->tx_pos >= priv->tx_len) { priv->tx_busy = false; - if (priv->tx_fifo_len < priv->tx_window) - netif_wake_queue(ndev); } return 0; } +/** + * ht42b416_tx_work - worker that continues partial UART writes + */ static void ht42b416_tx_work(struct work_struct *work) { struct ht42b416_priv *priv = @@ -396,62 +445,75 @@ static void ht42b416_tx_work(struct work_struct *work) } /** - * ht42b416_tx_wake_work - re-enable TX queue after a pacing delay - * @work: work item embedded in the driver private data - * - * This worker enforces a small inter-frame pause, then checks (under - * tx_lock) that the device is still running and the TX window has room, - * and finally wakes the netdev queue outside the lock. + * ht42b416_tx_wakeup - serdev write_wakeup callback */ -static void ht42b416_tx_wake_work(struct work_struct *work) +static void ht42b416_tx_wakeup(struct serdev_device *serdev) +{ + struct ht42b416_priv *priv = serdev_device_get_drvdata(serdev); + serdev_device_write_wakeup(serdev); + schedule_work(&priv->tx_work); +} + +/** + * ht42b416_tx_timeout_work - timeout waiting for Z or F06 response + */ +static void ht42b416_tx_timeout_work(struct work_struct *work) { struct ht42b416_priv *priv = - container_of(work, struct ht42b416_priv, tx_wake_work); + container_of(to_delayed_work(work), + struct ht42b416_priv, tx_timeout_work); struct net_device *ndev = priv->can.dev; unsigned long flags; - bool wake = false; - - usleep_range(HT42B416_TX_DELAY_US, HT42B416_TX_DELAY_US + 50); + enum ht42b416_tx_stage stage; + bool expired = false; spin_lock_irqsave(&priv->tx_lock, flags); - if (priv->running && priv->tx_fifo_len < priv->tx_window) - wake = true; + stage = priv->tx_stage; + if (stage == HT42B416_TX_WAIT_Z || + stage == HT42B416_TX_WAIT_STATUS_RESP) { + priv->tx_stage = HT42B416_TX_IDLE; + expired = true; + } spin_unlock_irqrestore(&priv->tx_lock, flags); - if (wake) - netif_wake_queue(ndev); -} - -static void ht42b416_tx_wakeup(struct serdev_device *serdev) -{ - struct ht42b416_priv *priv = serdev_device_get_drvdata(serdev); + if (!expired) + return; - serdev_device_write_wakeup(serdev); - schedule_work(&priv->tx_work); + netdev_err(ndev, "TX timeout waiting for %s\n", + stage == HT42B416_TX_WAIT_Z ? "Z" : "status 0x06"); + priv->running = false; + ht42b416_abort_tx(priv); + netif_stop_queue(ndev); + can_bus_off(ndev); } -static void ht42b416_finish_tx(struct ht42b416_priv *priv, u8 ack_byte) +/** + * ht42b416_tx_complete - handle Z/Z ack and advance TX FSM + */ +static bool ht42b416_tx_complete(struct ht42b416_priv *priv, + enum ht42b416_tx_expect ack_expect) { struct net_device *ndev = priv->can.dev; unsigned long flags; unsigned int tx_bytes; enum ht42b416_tx_expect expect; + struct ht42b416_tx_actions act = {}; u8 slot; spin_lock_irqsave(&priv->tx_lock, flags); - if (!priv->tx_fifo_len) { + if (priv->tx_stage != HT42B416_TX_WAIT_Z || !priv->tx_fifo_len) { spin_unlock_irqrestore(&priv->tx_lock, flags); - return; + return false; } slot = priv->tx_fifo_tail; expect = priv->tx_expect_fifo[slot]; - priv->tx_fifo_tail = (priv->tx_fifo_tail + 1) % priv->tx_window; + priv->tx_fifo_tail = (priv->tx_fifo_tail + 1) % HT42B416_ECHO_SIZE; priv->tx_fifo_len--; - if ((ack_byte != 'z' || expect != HT42B416_TX_EXPECT_Z) && - (ack_byte != 'Z' || expect != HT42B416_TX_EXPECT_CAP_Z)) - netdev_warn(ndev, "unexpected TX ack 0x%02x\n", ack_byte); + if (expect != ack_expect) + netdev_warn(ndev, "unexpected TX ack (exp=%u got=%u)\n", + expect, ack_expect); spin_unlock_irqrestore(&priv->tx_lock, flags); @@ -459,8 +521,15 @@ static void ht42b416_finish_tx(struct ht42b416_priv *priv, u8 ack_byte) ndev->stats.tx_packets++; ndev->stats.tx_bytes += tx_bytes; - if (priv->tx_fifo_len < priv->tx_window) - schedule_work(&priv->tx_wake_work); + spin_lock_irqsave(&priv->tx_lock, flags); + ht42b416_tx_fsm_locked(priv, HT42B416_TX_EVT_Z, &act); + spin_unlock_irqrestore(&priv->tx_lock, flags); + + if (act.cancel_timeout) + ht42b416_tx_timeout_cancel(priv); + if (act.arm_timeout) + ht42b416_tx_timeout_arm(priv); + return true; } /** @@ -529,6 +598,9 @@ static void ht42b416_handle_status(struct ht42b416_priv *priv, s8 status) netif_rx(skb); } +/** + * ht42b416_signal_ack - wake up a command waiting for CR ack + */ static void ht42b416_signal_ack(struct ht42b416_priv *priv) { enum ht42b416_wait_ack wait; @@ -540,6 +612,31 @@ static void ht42b416_signal_ack(struct ht42b416_priv *priv) } } +/** + * ht42b416_handle_status_response - process Fxx status and unblock on F06 + */ +static void ht42b416_handle_status_response(struct ht42b416_priv *priv, s8 status) +{ + unsigned long flags; + struct ht42b416_tx_actions act = {}; + + if (status != HT42B416_STATUS_TX_OK) { + ht42b416_handle_status(priv, status); + return; + } + + spin_lock_irqsave(&priv->tx_lock, flags); + ht42b416_tx_fsm_locked(priv, HT42B416_TX_EVT_STATUS_RESP, &act); + spin_unlock_irqrestore(&priv->tx_lock, flags); + + if (act.cancel_timeout) + ht42b416_tx_timeout_cancel(priv); + if (act.arm_timeout) + ht42b416_tx_timeout_arm(priv); + if (act.kick) + ht42b416_kick_queue(priv); +} + /** * ht42b416_parse_frame - decode a complete CAN frame from UART payload * @priv: device private data @@ -614,176 +711,191 @@ static int ht42b416_parse_frame(struct ht42b416_priv *priv, } /** - * ht42b416_process_msg - handle one complete message from the device + * ht42b416_rx_consume - drop consumed bytes from the RX buffer * @priv: device private data - * @buf: message payload (without trailing CR/LF) - * @len: payload length - * - * The function expects a fully assembled message. It does not try to - * reassemble fragmented UART data; that is done by ht42b416_try_parse(). + * @len: number of bytes to remove from the front + */ +static void ht42b416_rx_consume(struct ht42b416_priv *priv, size_t len) +{ + if (len >= priv->rx_len) { + priv->rx_len = 0; + return; + } + + memmove(priv->rx_buf, priv->rx_buf + len, priv->rx_len - len); + priv->rx_len -= len; +} + +/** + * ht42b416_rx_handle_ack - handle a lone CR ack + */ +static bool ht42b416_rx_handle_ack(struct ht42b416_priv *priv) +{ + ht42b416_signal_ack(priv); + ht42b416_rx_consume(priv, 1); + return true; +} + +/** + * ht42b416_rx_handle_z - handle Z/Z ack messages */ -static void ht42b416_process_msg(struct ht42b416_priv *priv, - const u8 *buf, size_t len) +static bool ht42b416_rx_handle_z(struct ht42b416_priv *priv, u8 start) { struct net_device *ndev = priv->can.dev; + enum ht42b416_tx_expect ack_expect; - if (ht42b416_debug) { - if (len) - ht42b416_log_uart(&priv->serdev->dev, - "UART RX msg ", buf, len); - else - dev_info(&priv->serdev->dev, "UART RX ack \n"); + if (priv->rx_len < 2) + return false; + if (priv->rx_buf[1] != '\r') { + ht42b416_rx_consume(priv, 1); + return true; } - if (!len) { - ht42b416_signal_ack(priv); - return; - } + ack_expect = start == 'z' ? HT42B416_TX_EXPECT_Z + : HT42B416_TX_EXPECT_CAP_Z; + if (!ht42b416_tx_complete(priv, ack_expect) && net_ratelimit()) + netdev_warn(ndev, "unexpected TX ack (empty fifo)\n"); - switch (buf[0]) { - case 't': - case 'T': - case 'r': - case 'R': - if (ht42b416_parse_frame(priv, buf, len) && net_ratelimit()) - netdev_warn(ndev, "invalid frame len=%zu\n", len); - break; - case 'z': - case 'Z': - ht42b416_finish_tx(priv, buf[0]); - break; - case 'F': - if (len >= 2) - ht42b416_handle_status(priv, (s8)buf[1]); - break; - default: - netdev_dbg(ndev, "dropping response 0x%02x len=%zu\n", - buf[0], len); - break; + ht42b416_rx_consume(priv, 2); + return true; +} + +/** + * ht42b416_rx_handle_status - handle Fxx status response + */ +static bool ht42b416_rx_handle_status(struct ht42b416_priv *priv) +{ + if (priv->rx_len < 3) + return false; + if (priv->rx_buf[2] != '\r') { + ht42b416_rx_consume(priv, 1); + return true; } + + ht42b416_handle_status_response(priv, (s8)priv->rx_buf[1]); + ht42b416_rx_consume(priv, 3); + return true; } /** - * ht42b416_rx_consume - drop consumed bytes from the RX buffer - * @priv: device private data - * @len: number of bytes to remove from the front + * ht42b416_rx_handle_frame - handle received CAN frame payload */ -static void ht42b416_rx_consume(struct ht42b416_priv *priv, size_t len) +static bool ht42b416_rx_handle_frame(struct ht42b416_priv *priv, u8 start) { - if (len >= priv->rx_len) { - priv->rx_len = 0; - return; + struct net_device *ndev = priv->can.dev; + size_t hdr_len; + size_t msg_len; + bool is_eff, is_rtr; + u8 dlc; + + is_eff = start == 'T' || start == 'R'; + is_rtr = start == 'r' || start == 'R'; + hdr_len = is_eff ? (1 + 4 + 1) : (1 + 2 + 1); + if (priv->rx_len < hdr_len) + return false; + + dlc = priv->rx_buf[hdr_len - 1]; + if (dlc > CAN_MAX_DLEN) { + if (net_ratelimit()) + netdev_warn(ndev, "invalid DLC %u\n", dlc); + ht42b416_rx_consume(priv, 1); + return true; } - memmove(priv->rx_buf, priv->rx_buf + len, priv->rx_len - len); - priv->rx_len -= len; + msg_len = hdr_len + (is_rtr ? 0 : dlc); + if (priv->rx_len < msg_len + 1) + return false; + if (priv->rx_buf[msg_len] != '\r') { + ht42b416_rx_consume(priv, 1); + return true; + } + + if (ht42b416_parse_frame(priv, priv->rx_buf, msg_len) && + net_ratelimit()) + netdev_warn(ndev, "invalid frame len=%zu\n", msg_len); + ht42b416_rx_consume(priv, msg_len + 1); + return true; } /** - * ht42b416_try_parse - attempt to parse one complete message from RX buffer + * ht42b416_rx_step - assemble and handle one complete message from RX buffer * @priv: device private data * - * Why it is needed: the UART delivers a byte stream in uneven chunks, and - * under load a single CAN response can be split across multiple receive - * callbacks. The old logic treated each chunk as a full message and ended - * up parsing partial frames (leading to "invalid frame len" and data shifts). - * - * What it does: it looks at the first byte, derives the expected message - * length (using DLC for CAN frames), and only passes a message to - * ht42b416_process_msg() once the full payload is present. Otherwise it - * keeps the data in the buffer and waits for more bytes. + * The UART delivers a byte stream in uneven chunks, so a single message may + * arrive in pieces. This function keeps buffering until it can identify a + * full message (based on type and DLC), then handles it in one place. * * Return: true if a message was consumed (success or drop), false if more * data is needed. */ -static bool ht42b416_try_parse(struct ht42b416_priv *priv) +static bool ht42b416_rx_step(struct ht42b416_priv *priv) { - struct net_device *ndev = priv->can.dev; - struct device *dev = &priv->serdev->dev; - size_t msg_len = 0; - size_t hdr_len; - bool is_eff, is_rtr; u8 start; - u8 dlc; if (!priv->rx_len) return false; start = priv->rx_buf[0]; - if (start == '\r' || start == '\n') { - ht42b416_process_msg(priv, priv->rx_buf, 0); - ht42b416_rx_consume(priv, 1); - return true; - } + if (start == '\r') + return ht42b416_rx_handle_ack(priv); switch (start) { case 'z': case 'Z': - msg_len = 1; - break; + return ht42b416_rx_handle_z(priv, start); case 'F': - msg_len = 2; - break; + return ht42b416_rx_handle_status(priv); case 't': case 'r': case 'T': case 'R': - is_eff = start == 'T' || start == 'R'; - is_rtr = start == 'r' || start == 'R'; - hdr_len = is_eff ? (1 + 4 + 1) : (1 + 2 + 1); - if (priv->rx_len < hdr_len) - return false; - dlc = priv->rx_buf[hdr_len - 1]; - if (dlc > CAN_MAX_DLEN) { - if (net_ratelimit()) - netdev_warn(ndev, "invalid DLC %u\n", dlc); - ht42b416_rx_consume(priv, 1); - return true; - } - msg_len = hdr_len + (is_rtr ? 0 : dlc); - break; + return ht42b416_rx_handle_frame(priv, start); default: - if (net_ratelimit()) - netdev_dbg(ndev, "dropping response 0x%02x len=%zu\n", - start, priv->rx_len); ht42b416_rx_consume(priv, 1); return true; } - - if (priv->rx_len < msg_len) { - if (ht42b416_debug && net_ratelimit()) - dev_info(dev, "UART RX partial type %c have %zu need %zu\n", - start, priv->rx_len, msg_len); - return false; - } - - ht42b416_process_msg(priv, priv->rx_buf, msg_len); - ht42b416_rx_consume(priv, msg_len); - return true; } static ssize_t ht42b416_receive(struct serdev_device *serdev, const u8 *data, size_t count) { struct ht42b416_priv *priv = serdev_device_get_drvdata(serdev); - size_t i; - - for (i = 0; i < count; i++) { - if (priv->rx_len >= HT42B416_RX_BUF_LEN) { - if (net_ratelimit()) - netdev_warn(priv->can.dev, - "RX buffer overflow (%zu bytes)\n", - priv->rx_len); + size_t orig_count = count; + size_t space; + size_t copy; + size_t tail; + + if (count > HT42B416_RX_BUF_LEN) { + data += count - HT42B416_RX_BUF_LEN; + count = HT42B416_RX_BUF_LEN; + } + + if (priv->rx_len + count > HT42B416_RX_BUF_LEN) { + if (net_ratelimit()) + netdev_warn(priv->can.dev, + "RX buffer overflow (%zu bytes)\n", + priv->rx_len + count); + if (priv->rx_len >= HT42B416_RX_BUF_LEN / 2) { + tail = HT42B416_RX_BUF_LEN / 2; + memmove(priv->rx_buf, + priv->rx_buf + priv->rx_len - tail, + tail); + priv->rx_len = tail; + } else { priv->rx_len = 0; } - - priv->rx_buf[priv->rx_len++] = data[i]; - while (ht42b416_try_parse(priv)) - ; } - return count; + space = HT42B416_RX_BUF_LEN - priv->rx_len; + copy = min(count, space); + memcpy(priv->rx_buf + priv->rx_len, data, copy); + priv->rx_len += copy; + + while (ht42b416_rx_step(priv)) + ; + + return orig_count; } static const struct serdev_device_ops ht42b416_serdev_ops = { @@ -792,38 +904,66 @@ static const struct serdev_device_ops ht42b416_serdev_ops = { }; /** - * ht42b416_start_xmit - enqueue a CAN frame for UART transmission - * @skb: CAN skb to transmit - * @ndev: net device + * ht42b416_tx_queue_full - check if software TX queue is full */ -static netdev_tx_t ht42b416_start_xmit(struct sk_buff *skb, - struct net_device *ndev) +static bool ht42b416_tx_queue_full(struct ht42b416_priv *priv) { - struct ht42b416_priv *priv = netdev_priv(ndev); - struct can_frame *cf = (struct can_frame *)skb->data; - unsigned long flags; + return priv->tx_q_len >= HT42B416_TX_QUEUE_LEN; +} + +/** + * ht42b416_can_send_locked - check if a new frame can be sent now + */ +static bool ht42b416_can_send_locked(struct ht42b416_priv *priv) +{ + return priv->running && + !priv->tx_busy && + priv->tx_fifo_len < HT42B416_ECHO_SIZE && + priv->tx_stage == HT42B416_TX_IDLE; +} + +/** + * ht42b416_queue_wake_if_needed - wake netdev queue when space is available + */ +static void ht42b416_queue_wake_if_needed(struct ht42b416_priv *priv) +{ + struct net_device *ndev = priv->can.dev; + + if (priv->running && !ht42b416_tx_queue_full(priv) && + netif_queue_stopped(ndev)) + netif_wake_queue(ndev); +} + +/** + * ht42b416_send_next_locked - pop one skb and start UART transmission + */ +static void ht42b416_send_next_locked(struct ht42b416_priv *priv) +{ + struct net_device *ndev = priv->can.dev; + struct sk_buff *skb; + struct can_frame *cf; bool eff, rtr; enum ht42b416_tx_expect expect; u8 slot; u8 *pos; int err; - if (can_dev_dropped_skb(ndev, skb)) - return NETDEV_TX_OK; + if (!priv->tx_q_len || !ht42b416_can_send_locked(priv)) { + return; + } + + skb = priv->tx_queue[priv->tx_q_tail]; + priv->tx_queue[priv->tx_q_tail] = NULL; + priv->tx_q_tail = (priv->tx_q_tail + 1) % HT42B416_TX_QUEUE_LEN; + priv->tx_q_len--; + cf = (struct can_frame *)skb->data; eff = cf->can_id & CAN_EFF_FLAG; rtr = cf->can_id & CAN_RTR_FLAG; expect = eff ? HT42B416_TX_EXPECT_CAP_Z : HT42B416_TX_EXPECT_Z; - spin_lock_irqsave(&priv->tx_lock, flags); - if (priv->tx_busy || priv->tx_fifo_len >= priv->tx_window) { - netif_stop_queue(ndev); - spin_unlock_irqrestore(&priv->tx_lock, flags); - return NETDEV_TX_BUSY; - } - slot = priv->tx_fifo_head; - priv->tx_fifo_head = (priv->tx_fifo_head + 1) % priv->tx_window; + priv->tx_fifo_head = (priv->tx_fifo_head + 1) % HT42B416_ECHO_SIZE; priv->tx_fifo_len++; priv->tx_expect_fifo[slot] = expect; @@ -856,13 +996,8 @@ static netdev_tx_t ht42b416_start_xmit(struct sk_buff *skb, priv->tx_len = pos - priv->tx_buf; priv->tx_pos = 0; priv->tx_busy = true; + priv->tx_stage = HT42B416_TX_WAIT_Z; - if (ht42b416_debug) - ht42b416_log_uart(&priv->serdev->dev, "UART TX frame ", - priv->tx_buf, priv->tx_len); - - if (priv->tx_fifo_len >= priv->tx_window) - netif_stop_queue(ndev); can_put_echo_skb(skb, ndev, slot, 0); err = ht42b416_tx_kick_locked(priv); @@ -872,17 +1007,120 @@ static netdev_tx_t ht42b416_start_xmit(struct sk_buff *skb, priv->tx_pos = 0; priv->tx_fifo_head = slot; priv->tx_fifo_len--; - spin_unlock_irqrestore(&priv->tx_lock, flags); + priv->tx_stage = HT42B416_TX_IDLE; ndev->stats.tx_errors++; can_free_echo_skb(ndev, slot, NULL); - netif_wake_queue(ndev); - return NETDEV_TX_OK; + netdev_err(ndev, "TX write failed: %d\n", err); + return; } if (priv->tx_pos < priv->tx_len) schedule_work(&priv->tx_work); +} + +/** + * ht42b416_tx_fsm_locked - advance TX FSM for a given event + */ +static void ht42b416_tx_fsm_locked(struct ht42b416_priv *priv, + enum ht42b416_tx_event ev, + struct ht42b416_tx_actions *act) +{ + switch (priv->tx_stage) { + case HT42B416_TX_IDLE: + if (ev == HT42B416_TX_EVT_KICK) { + ht42b416_send_next_locked(priv); + if (priv->tx_stage == HT42B416_TX_WAIT_Z) + act->arm_timeout = true; + } + break; + case HT42B416_TX_WAIT_Z: + if (ev == HT42B416_TX_EVT_Z) { + priv->tx_stage = HT42B416_TX_WAIT_STATUS_RESP; + act->cancel_timeout = true; + act->arm_timeout = true; + } + break; + case HT42B416_TX_WAIT_STATUS_RESP: + if (ev == HT42B416_TX_EVT_STATUS_RESP) { + act->cancel_timeout = true; + priv->tx_stage = HT42B416_TX_IDLE; + act->kick = true; + } + break; + } +} + +/** + * ht42b416_tx_timeout_arm - arm timeout for Z/F06 response + */ +static void ht42b416_tx_timeout_arm(struct ht42b416_priv *priv) +{ + mod_delayed_work(system_wq, &priv->tx_timeout_work, + msecs_to_jiffies(HT42B416_TX_RESP_TIMEOUT_MS)); +} + +/** + * ht42b416_tx_timeout_cancel - cancel outstanding TX response timeout + */ +static void ht42b416_tx_timeout_cancel(struct ht42b416_priv *priv) +{ + cancel_delayed_work(&priv->tx_timeout_work); +} + +/** + * ht42b416_kick_queue - try to send the next queued frame + */ +static void ht42b416_kick_queue(struct ht42b416_priv *priv) +{ + unsigned long flags; + bool wake = false; + struct ht42b416_tx_actions act = {}; + + spin_lock_irqsave(&priv->tx_lock, flags); + ht42b416_tx_fsm_locked(priv, HT42B416_TX_EVT_KICK, &act); + wake = priv->running && !ht42b416_tx_queue_full(priv); + spin_unlock_irqrestore(&priv->tx_lock, flags); + + if (act.cancel_timeout) + ht42b416_tx_timeout_cancel(priv); + if (act.arm_timeout) + ht42b416_tx_timeout_arm(priv); + if (wake) + ht42b416_queue_wake_if_needed(priv); +} + +/** + * ht42b416_start_xmit - enqueue a CAN frame for UART transmission + * @skb: CAN skb to transmit + * @ndev: net device + */ +static netdev_tx_t ht42b416_start_xmit(struct sk_buff *skb, + struct net_device *ndev) +{ + struct ht42b416_priv *priv = netdev_priv(ndev); + unsigned long flags; + bool stop; + + if (can_dev_dropped_skb(ndev, skb)) + return NETDEV_TX_OK; + + spin_lock_irqsave(&priv->tx_lock, flags); + if (ht42b416_tx_queue_full(priv)) { + netif_stop_queue(ndev); + spin_unlock_irqrestore(&priv->tx_lock, flags); + return NETDEV_TX_BUSY; + } + + priv->tx_queue[priv->tx_q_head] = skb; + priv->tx_q_head = (priv->tx_q_head + 1) % HT42B416_TX_QUEUE_LEN; + priv->tx_q_len++; + stop = ht42b416_tx_queue_full(priv); spin_unlock_irqrestore(&priv->tx_lock, flags); + if (stop) + netif_stop_queue(ndev); + + ht42b416_kick_queue(priv); return NETDEV_TX_OK; } @@ -905,6 +1143,9 @@ static int ht42b416_open(struct net_device *ndev) return 0; } +/** + * ht42b416_close - stop the device and close the CAN channel + */ static int ht42b416_close(struct net_device *ndev) { struct ht42b416_priv *priv = netdev_priv(ndev); @@ -916,6 +1157,9 @@ static int ht42b416_close(struct net_device *ndev) return 0; } +/** + * ht42b416_tx_timeout - netdev TX watchdog handler + */ static void ht42b416_tx_timeout(struct net_device *ndev, unsigned int txqueue) { @@ -933,6 +1177,9 @@ static const struct net_device_ops ht42b416_netdev_ops = { .ndo_tx_timeout = ht42b416_tx_timeout, }; +/** + * ht42b416_set_mode - handle CAN mode switches + */ static int ht42b416_set_mode(struct net_device *ndev, enum can_mode mode) { struct ht42b416_priv *priv = netdev_priv(ndev); @@ -947,6 +1194,9 @@ static int ht42b416_set_mode(struct net_device *ndev, enum can_mode mode) } } +/** + * ht42b416_probe - bind and initialize the serdev-backed CAN device + */ static int ht42b416_probe(struct serdev_device *serdev) { struct device *dev = &serdev->dev; @@ -972,15 +1222,18 @@ static int ht42b416_probe(struct serdev_device *serdev) priv->uart_baud = baud; priv->running = false; priv->rx_len = 0; - priv->tx_window = HT42B416_ECHO_SIZE; priv->tx_fifo_head = 0; priv->tx_fifo_tail = 0; priv->tx_fifo_len = 0; + priv->tx_q_head = 0; + priv->tx_q_tail = 0; + priv->tx_q_len = 0; + priv->tx_stage = HT42B416_TX_IDLE; mutex_init(&priv->cmd_lock); init_completion(&priv->ack_complete); spin_lock_init(&priv->tx_lock); INIT_WORK(&priv->tx_work, ht42b416_tx_work); - INIT_WORK(&priv->tx_wake_work, ht42b416_tx_wake_work); + INIT_DELAYED_WORK(&priv->tx_timeout_work, ht42b416_tx_timeout_work); priv->wait_ack = HT42B416_WAIT_NONE; priv->enable_gpiod = devm_gpiod_get_optional(dev, "enable", GPIOD_OUT_LOW); @@ -1031,13 +1284,16 @@ static int ht42b416_probe(struct serdev_device *serdev) return ret; } +/** + * ht42b416_remove - unbind and teardown the CAN device + */ static void ht42b416_remove(struct serdev_device *serdev) { struct ht42b416_priv *priv = serdev_device_get_drvdata(serdev); struct net_device *ndev = priv->can.dev; - cancel_work_sync(&priv->tx_wake_work); cancel_work_sync(&priv->tx_work); + cancel_delayed_work_sync(&priv->tx_timeout_work); ht42b416_hw_stop(priv); unregister_candev(ndev); free_candev(ndev);