[PATCH v6 1/2] can: Add Loongson CAN-FD controller driver
Binbin Zhou <[email protected]> Tue, 21 Jul 2026 19:44:16 +0800
| Newsgroups | dev.linux.lists.loongarch,org.kernel.vger.linux-can |
|---|---|
| Message-ID | <5896e9ebf39e4796a6e1d9f587f876e106094969.1784597142.git.zhoubinbin@loongson.cn> |
Add a new driver for the Loongson CAN-FD controller, which is integrated into Loongson-2K series SoCs. The controller supports both CAN 2.0 and CAN-FD protocols, with up to 8 TX buffers and a flexible RX buffer. The driver implements the core CAN device operations: - NAPI-based receive path with interrupt masking to reduce overhead; - TX echo support for accurate loopback and echo skb handling; - Error reporting (warning, passive, bus-off) with detailed status; - Support for common CAN controller modes: loopback, listen-only, one-shot, FD, and non-ISO FD; - Auto TDC (Transmitter Delay Compensation) for FD data phase. Co-developed-by: Bingxiong Li <[email protected]> Signed-off-by: Bingxiong Li <[email protected]> Signed-off-by: Binbin Zhou <[email protected]> --- MAINTAINERS | 7 + drivers/net/can/Kconfig | 11 + drivers/net/can/Makefile | 1 + drivers/net/can/loongson_canfd.c | 1687 ++++++++++++++++++++++++++++++ 4 files changed, 1706 insertions(+) create mode 100644 drivers/net/can/loongson_canfd.c diff --git a/MAINTAINERS b/MAINTAINERS index f37a81950e25..6b39c880a05a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15193,6 +15193,13 @@ F: Documentation/devicetree/bindings/sound/loongson,ls2k1000-i2s.yaml F: sound/soc/loongson/loongson_*.c F: sound/soc/loongson/loongson_*.h +LOONGSON CAN FD DRIVER +M: Bingxiong Li <[email protected]> +M: Binbin Zhou <[email protected]> +L: [email protected] +S: Maintained +F: drivers/net/can/loongson_canfd/ + LOONGSON GPIO DRIVER M: Yinbo Zhu <[email protected]> L: [email protected] diff --git a/drivers/net/can/Kconfig b/drivers/net/can/Kconfig index a8fad6fe5302..95a0f898959c 100644 --- a/drivers/net/can/Kconfig +++ b/drivers/net/can/Kconfig @@ -186,6 +186,17 @@ config CAN_KVASER_PCIEFD Kvaser M.2 PCIe 4xCAN Kvaser PCIe 8xCAN +config CAN_LOONGSON_CANFD + tristate "Loongson CAN-FD controller" + depends on HAS_IOMEM + select REGMAP_MMIO + help + This is a canfd driver switch for the Loongson platform, + integrated with the Loongson-2K series SoCs. + + To compile as a module, choose M here: the module will be + called loongson_canfd. + config CAN_SLCAN tristate "Serial / USB serial CAN Adaptors (slcan)" depends on TTY diff --git a/drivers/net/can/Makefile b/drivers/net/can/Makefile index 4010d17f8583..c4eae4595ce3 100644 --- a/drivers/net/can/Makefile +++ b/drivers/net/can/Makefile @@ -27,6 +27,7 @@ obj-$(CONFIG_CAN_GRCAN) += grcan.o obj-$(CONFIG_CAN_IFI_CANFD) += ifi_canfd/ obj-$(CONFIG_CAN_JANZ_ICAN3) += janz-ican3.o obj-$(CONFIG_CAN_KVASER_PCIEFD) += kvaser_pciefd/ +obj-$(CONFIG_CAN_LOONGSON_CANFD) += loongson_canfd.o obj-$(CONFIG_CAN_MSCAN) += mscan/ obj-$(CONFIG_CAN_M_CAN) += m_can/ obj-$(CONFIG_CAN_PEAK_PCIEFD) += peak_canfd/ diff --git a/drivers/net/can/loongson_canfd.c b/drivers/net/can/loongson_canfd.c new file mode 100644 index 000000000000..211133258b08 --- /dev/null +++ b/drivers/net/can/loongson_canfd.c @@ -0,0 +1,1687 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * LOONGSON CANFD controller + * + * Copyright (C) 2024-2026 Loongson Technology Corporation Limited + */ + +#include <linux/acpi.h> +#include <linux/bitfield.h> +#include <linux/bits.h> +#include <linux/can/dev.h> +#include <linux/can/error.h> +#include <linux/ethtool.h> +#include <linux/io.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/netdevice.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/skbuff.h> +#include <linux/string.h> +#include <linux/types.h> + +#define LOONGSON_CANFD_DEVICE_ID 0x0 /* CANFD controller ID Register */ +#define LOONGSON_CANFD_MODE 0x4 /* Mode Configuration Register */ +#define LOONGSON_CANFD_CONF 0x8 /* Configure Register */ +#define LOONGSON_CANFD_STAT 0xc /* Status Register */ +#define LOONGSON_CANFD_CMD 0x10 /* Command Register */ +#define LOONGSON_CANFD_INT_STAT 0x14 /* Interrupt Status Register */ +#define LOONGSON_CANFD_INT_ENA 0x18 /* Interrupt Enable Register */ +#define LOONGSON_CANFD_INT_MASK 0x1c /* Interrupt Mask Register */ +#define LOONGSON_CANFD_BTR_NORM 0x20 /* Normal Rate Configuration Register */ +#define LOONGSON_CANFD_BTR_FD 0x24 /* FD Data Rate Configuration Register */ +#define LOONGSON_CANFD_ERL 0x28 /* Error Threshold Configuration Register */ +#define LOONGSON_CANFD_FSTAT 0x2c /* Error Status Register */ +#define LOONGSON_CANFD_ERC 0x30 /* Error Count Register */ +#define LOONGSON_CANFD_BRE 0x34 /* Rate Error Count Register */ +#define LOONGSON_CANFD_CTR_PRES 0x38 /* Error Count Debug Register */ +#define LOONGSON_CANFD_ERR_CAPT 0x3c /* Error Capture Status Register */ +#define LOONGSON_CANFD_RETX_CNT 0x40 /* Retransmission Count Register */ +#define LOONGSON_CANFD_ALC 0x44 /* Lost Arbitration Capture Register */ +#define LOONGSON_CANFD_TRV_DLY 0x48 /* Transmission Delay Measurement Register */ +#define LOONGSON_CANFD_SSP_CFG 0x4c /* Second Sampling Point Configuration Register */ +#define LOONGSON_CANFD_RX_FR_CNT 0x50 /* Receive Message Count Register */ +#define LOONGSON_CANFD_TX_FR_CNT 0x54 /* Transmit Message Count Register */ +#define LOONGSON_CANFD_DEBUG 0x58 /* Debug Register */ +#define LOONGSON_CANFD_TS 0x5c /* Timestamp Register */ +#define LOONGSON_CANFD_TX_FRM_TST 0x60 /* Transmit Message Debug Register */ +#define LOONGSON_CANFD_FRC_DIV 0x64 /* Fractional Divider Ratio Register */ +#define LOONGSON_CANFD_FLT_A_MASK 0x68 /* Filter A Mask Register */ +#define LOONGSON_CANFD_FLT_A_VAL 0x6c /* Filter A value Register */ +#define LOONGSON_CANFD_FLT_B_MASK 0x70 /* Filter B Mask Register */ +#define LOONGSON_CANFD_FLT_B_VAL 0x74 /* Filter B value Register */ +#define LOONGSON_CANFD_FLT_C_MASK 0x78 /* Filter C Mask Register */ +#define LOONGSON_CANFD_FLT_C_VAL 0x7c /* Filter C value Register */ +#define LOONGSON_CANFD_FLT_R_LOW 0x80 /* Range Filter Low Threshold Register */ +#define LOONGSON_CANFD_FLT_R_HI 0x84 /* Range Filter High Threshold Register */ +#define LOONGSON_CANFD_FLT_CTRL 0x88 /* Filter Control Register */ +#define LOONGSON_CANFD_RX_MEM_INFO 0x8c /* Receive Buffer Information Register */ +#define LOONGSON_CANFD_RX_PRT 0x90 /* Receive Buffer Pointer Register */ +#define LOONGSON_CANFD_RX_STAT 0x94 /* Receive Buffer Status Register */ +#define LOONGSON_CANFD_RX_DATA 0x98 /* Receive Data Register */ +#define LOONGSON_CANFD_TX_STAT 0x9c /* Transmit Buffer Status Register */ +#define LOONGSON_CANFD_TX_CMD 0xa0 /* Transmit Command Register */ +#define LOONGSON_CANFD_TX_SEL 0xa4 /* Transmit Buffer Selection Register */ +#define LOONGSON_CANFD_TX_DATA_1 0xb0 +#define LOONGSON_CANFD_TX_DATA_18 0xf4 + +/* Bitfields of CANFD controller ID register */ +#define REG_ID_MASK GENMASK(15, 0) +#define REG_ID_VER_MIN GENMASK(23, 16) +#define REG_ID_VER_MAJ GENMASK(31, 24) + +/* Bitfields of Mode Configuration register */ +#define REG_MODE_RST BIT(0) /* Soft Reset */ +#define REG_MODE_BMM BIT(1) /* Bus Monitoring Mode */ +#define REG_MODE_STM BIT(2) /* Self-Test Mode */ +#define REG_MODE_AFM BIT(3) /* Receive Filter Mode */ +#define REG_MODE_FDE BIT(4) /* CANFD Enabled */ +#define REG_MODE_TTTM BIT(5) /* Timed Transmission Mode */ +#define REG_MODE_ROM BIT(6) /* Restricted Operation Mode */ +#define REG_MODE_ACF BIT(7) /* No ACK Mode*/ +#define REG_MODE_TSTM BIT(8) /* Test Mode */ +#define REG_MODE_RXBAM BIT(9) /* Receive Buffer Auto Mode */ +#define REG_MODE_ITSM BIT(10) /* Internal Timestamp Mode */ +#define REG_MODE_RTSOP BIT(12) /* Receive Buffer Timestamp Addition Mode */ +#define REG_MODE_BUFM BIT(13) /* Transmit Buffer Mode */ + +/* Bitfields of Configure register */ +#define REG_CONF_RTRLE BIT(0) /* Retransmission Threshold Enable */ +#define REG_CONF_RTRTH GENMASK(4, 1) /* Retransmission Threshold */ +#define REG_CONF_ILBP BIT(5) /* Internal Loopback Mode */ +#define REG_CONF_ENA BIT(6) /* Controller Enable */ +#define REG_CONF_NISOFD BIT(7) /* NON-ISO CANFD Protocol Enable */ +#define REG_CONF_PEX BIT(8) /* Protocol Exception Handling */ +#define REG_CONF_FDRF BIT(10) /* Ignore Remote Frame Enable */ + +/* Bitfields of Status register */ +#define REG_STAT_RXNE BIT(0) /* Receive Buffer Not Empty Status */ +#define REG_STAT_DOR BIT(1) /* Receive Data Overflow Status */ +#define REG_STAT_EFT BIT(3) /* Error Frame Flag */ +#define REG_STAT_RXS BIT(4) /* Receive Status Flag */ +#define REG_STAT_TXS BIT(5) /* Transmission Status Flag */ +#define REG_STAT_EWL BIT(6) /* Transmission Error Threshold Flag */ +#define REG_STAT_IDLE BIT(7) /* Bus Idle Flag */ +#define REG_STAT_PEXS BIT(8) /* Protocol Exception Status */ +#define REG_STAT_STCNT BIT(16) /* Transmission Count Support */ + +/* Bitfields of Command register */ +#define REG_CMD_RXRPMV BIT(1) /* Receive Buffer Read-to-Move */ +#define REG_CMD_RRB BIT(2) /* Receive Buffer Release */ +#define REG_CMD_CDO BIT(3) /* Received Data Overflow Status Clear */ +#define REG_CMD_ERCRST BIT(4) /* Error Count Clear */ +#define REG_CMD_RXFCRST BIT(5) /* Receive Frame Count Clear */ +#define REG_CMD_TXFCRST BIT(6) /* Transmit Frame Count Clear */ +#define REG_CMD_CPEXS BIT(7) /* Exception Status Clear */ + +/* Bitfields of Interrupt Status register */ +#define REG_INT_STAT_RXI BIT(0) /* Message Reception Interrupt */ +#define REG_INT_STAT_TXI BIT(1) /* Message Transmission Interrupt */ +#define REG_INT_STAT_EWLI BIT(2) /* Error Warning Threshold Interrupt */ +#define REG_INT_STAT_DOI BIT(3) /* Receive Data Overflow Interrupt */ +#define REG_INT_STAT_FCSI BIT(4) /* Fault State Change Interrupt */ +#define REG_INT_STAT_ALI BIT(5) /* Arbitration Loss Interrupt */ +#define REG_INT_STAT_BEI BIT(6) /* Bus Error Interrupt */ +#define REG_INT_STAT_RXFI BIT(7) /* Receive Buffer Full Interrupt */ +#define REG_INT_STAT_BSI BIT(8) /* Rate Switching Interrupt */ +#define REG_INT_STAT_RBNEI BIT(9) /* Receive Buffer Not Empty Interrupt */ +#define REG_INT_STAT_TXBHCI BIT(10) /* Transmit Buffer Hardware Command Interrupt */ +#define REG_INT_STAT_OFI BIT(11) /* Overload Frame Transmission Interrupt */ +#define REG_INT_STAT_DMADI BIT(12) /* Message DMA Transfer Complete Interrupt */ + +#define REG_INT_STAT_ERRORI (REG_INT_STAT_EWLI | REG_INT_STAT_FCSI |\ + REG_INT_STAT_ALI | REG_INT_STAT_BEI) + +/* Bitfields of Interrupt Enable register */ +#define REG_INT_ENA_SET GENMASK(12, 0) +#define REG_INT_ENA_CLR GENMASK(28, 16) + +/* Bitfields of Interrupt Mask register */ +#define REG_INT_MASK_SET GENMASK(12, 0) +#define REG_INT_MASK_CLR GENMASK(28, 16) + +/* Bitfields of Normal Rate Configuration register */ +#define REG_BTR_PROP GENMASK(6, 0) +#define REG_BTR_PH1 GENMASK(12, 7) +#define REG_BTR_PH2 GENMASK(18, 13) +#define REG_BTR_BRP GENMASK(22, 19) +#define REG_BTR_SJW GENMASK(31, 27) + +/* Bitfields of FD Data Rate Configuration register */ +#define REG_BTR_FD_PROP GENMASK(6, 0) +#define REG_BTR_FD_PH1 GENMASK(12, 7) +#define REG_BTR_FD_PH2 GENMASK(18, 13) +#define REG_BTR_FD_BRP GENMASK(26, 19) +#define REG_BTR_FD_SJW GENMASK(31, 27) + +/* Bitfields of Error Threshold Configuration register */ +#define REG_ERL_ERP GENMASK(7, 0) +#define REG_ERL_EW GENMASK(23, 16) + +/* Bitfields of Error Status register */ +#define REG_FSTAT_ERA BIT(0) +#define REG_FSTAT_ERP BIT(1) +#define REG_FSTAT_BOF BIT(2) + +#define REG_FSTAT_MASK GENMASK(2, 0) + +/* Bitfields of Error Count register */ +#define REG_ERC_TEC GENMASK(8, 0) +#define REG_ERC_REC GENMASK(24, 16) + +/* Bitfields of Rate Error Count register */ +#define REG_BRE_NORM GENMASK(15, 0) +#define REG_BRE_FD_DATA GENMASK(31, 16) + +/* Bitfields of Error Count Debug register */ +#define REG_CTR_PRES_CTPV GENMASK(8, 0) +#define REG_CTR_PRES_PTX BIT(9) +#define REG_CTR_PRES_PRX BIT(10) + +/* Bitfields of Error Capture Status register */ +#define REG_ERR_CAPT_POS GENMASK(4, 0) +#define REG_ERR_CAPT_TYPE GENMASK(7, 5) + +/* Bitfields of Retransmission Count register */ +#define REG_RETX_CNT_VAL GENMASK(3, 0) + +/* Bitfields of Lost Arbitration Capture register */ +#define REG_ALC_BIT_POS GENMASK(4, 0) +#define REG_ALC_ID_FIELD GENMASK(7, 5) + +/* Bitfields of Transmission Delay Measurement register */ +#define REG_TRV_DLY_VAL GENMASK(6, 0) + +/* Bitfields of Second Sampling Point Configuration register */ +#define REG_SSP_CFG_OFF GENMASK(7, 0) +#define REG_SSP_CFG_SRC GENMASK(9, 8) +#define REG_SSP_CFG_SAT BIT(10) + +/* Bitfields of Receive Message Count register */ +#define REG_RX_FR_CNT_VAL GENMASK(31, 0) + +/* Bitfields of Transmit Message Count register */ +#define REG_TX_FR_CNT_VAL GENMASK(31, 0) + +/* Bitfields of Debug register */ +#define REG_DEBUG_STF_CNT GENMASK(2, 0) +#define REG_DEBUG_DSTF_CNT GENMASK(5, 3) +#define REG_DEBUG_PC_ARB BIT(6) +#define REG_DEBUG_PC_CON BIT(7) +#define REG_DEBUG_PC_DAT BIT(8) +#define REG_DEBUG_PC_STC BIT(9) +#define REG_DEBUG_PC_CRC BIT(10) +#define REG_DEBUG_PC_CRCD BIT(11) +#define REG_DEBUG_PC_ACK BIT(12) +#define REG_DEBUG_PC_ACKD BIT(13) +#define REG_DEBUG_PC_EOF BIT(14) +#define REG_DEBUG_PC_INT BIT(15) +#define REG_DEBUG_PC_SUSP BIT(16) +#define REG_DEBUG_PC_OVR BIT(17) +#define REG_DEBUG_PC_SOF BIT(18) + +/* Bitfields of Timestamp register */ +#define REG_TS_TIMESTAMP GENMASK(15, 0) +#define REG_TS_PSC GENMASK(24, 16) + +/* Bitfields of Fractional Divider Ratio register */ +#define REG_FRC_FRC_DBT GENMASK(15, 8) +#define REG_FRC_FRC_NBT GENMASK(7, 0) + +/* Bitfields of Filter A Mask register */ +#define REG_FIL_A_MASK GENMASK(28, 0) + +/* Bitfields of Filter A value register */ +#define REG_FIL_A_VAL GENMASK(28, 0) + +/* Bitfields of Filter B Mask register */ +#define REG_FIL_B_MASK GENMASK(28, 0) + +/* Bitfields of Filter B value register */ +#define REG_FIL_B_VAL GENMASK(28, 0) + +/* Bitfields of Filter C Mask register */ +#define REG_FIL_C_MASK GENMASK(28, 0) + +/* Bitfields of Filter C value register */ +#define REG_FIL_C_VAL GENMASK(28, 0) + +/* Bitfields of Range Filter Low Threshold register */ +#define REG_FIL_R_LOW_VAL GENMASK(28, 0) + +/* Bitfields of Range Filter High Threshold register */ +#define REG_FIL_R_HI_VAL GENMASK(28, 0) + +/* Bitfields of Filter Control register */ +#define REG_FIL_CTRL_FANB BIT(0) +#define REG_FIL_CTRL_FANE BIT(1) +#define REG_FIL_CTRL_FAFB BIT(2) +#define REG_FIL_CTRL_FAFE BIT(3) +#define REG_FIL_CTRL_FBNB BIT(4) +#define REG_FIL_CTRL_FBNE BIT(5) +#define REG_FIL_CTRL_FBFB BIT(6) +#define REG_FIL_CTRL_FBFE BIT(7) +#define REG_FIL_CTRL_FCNB BIT(8) +#define REG_FIL_CTRL_FCNE BIT(9) +#define REG_FIL_CTRL_FCFB BIT(10) +#define REG_FIL_CTRL_FCFE BIT(11) +#define REG_FIL_CTRL_FRNB BIT(12) +#define REG_FIL_CTRL_FRNE BIT(13) +#define REG_FIL_CTRL_FRFB BIT(14) +#define REG_FIL_CTRL_FRFE BIT(15) +#define REG_FIL_CTRL_SFA BIT(16) +#define REG_FIL_CTRL_SFB BIT(17) +#define REG_FIL_CTRL_SFC BIT(18) +#define REG_FIL_CTRL_SFR BIT(19) + +/* Bitfields of Receive Buffer Information register */ +#define REG_RX_MEM_INFO_BUFF_SIZE GENMASK(12, 0) +#define REG_RX_MEM_INFO_MEM_FREE GENMASK(28, 16) + +/* Bitfields of Receive Buffer Pointer register */ +#define REG_RX_PTR_WPP GENMASK(11, 0) +#define REG_RX_PTR_RPP GENMASK(27, 16) + +/* Bitfields of Receive Buffer Status register */ +#define REG_RX_STAT_RXE BIT(0) +#define REG_RX_STAT_RXF BIT(1) +#define REG_RX_STAT_RXMOF BIT(2) +#define REG_RX_STAT_RXFRC GENMASK(14, 4) +#define REG_RX_STAT_RTSOP BIT(16) + +/* Bitfields of Receive Data register */ +#define REG_RX_DATA_VAL GENMASK(31, 0) + +/* Bitfields of Transmit Buffer Status register */ +#define REG_TX_STAT_BRP GENMASK(7, 0) +#define REG_TX_STAT_TXS GENMASK(10, 8) +#define REG_TX_STAT_BS GENMASK(31, 16) + +#define REG_TX_STAT_BS_TX_MASK_BASE GENMASK(17, 16) + +/* Bitfields of Transmit Command register */ +#define REG_TX_CMD_BAR GENMASK(7, 0) +#define REG_TX_CMD_BCR GENMASK(15, 8) +#define REG_TX_CMD_BSC GENMASK(23, 16) + +/* Bitfields of Transmit Buffer Selection register */ +#define REG_TX_SEL_BUF_SEL GENMASK(3, 0) +#define REG_TX_SEL_BUF_CNT GENMASK(7, 4) + +/* Loongson CANFD Frame format */ +#define LOONGSON_CANFD_FRAME_META0 0x0 +#define LOONGSON_CANFD_FRAME_META1 0x4 +#define LOONGSON_CANFD_FRAME_DB_1 0x8 + +/* Bitfields of FRAME META0 */ +#define REG_FRAME_META0_ID_EXT GENMASK(17, 0) +#define REG_FRAME_META0_ID_BASE GENMASK(28, 18) +#define REG_FRAME_META0_RTR BIT(29) /* Remote Transmission Request, only in CAN2.0 */ +/* Extended Identifier Type. 1: 29 bits ID, 0: 11 bits ID. */ +#define REG_FRAME_META0_XDT BIT(30) +#define REG_FRAME_META0_ESI BIT(31) /* Error State Indicator, only in CAN-FD */ + +/* Bitfields of FRAME META1 */ +#define REG_FRAME_META1_TIMESTAMP GENMASK(15, 0) +#define REG_FRAME_META1_DLC GENMASK(19, 16) /* Data Length Code */ +#define REG_FRAME_META1_BRS BIT(20) /* Bit Rate Shift, Only in CANFD */ +/* Flexible Data-rate Format. 1: CANFD, 0: CAN2.0 */ +#define REG_FRAME_META1_FDF BIT(21) +#define REG_FRAME_META1_RWCNT GENMASK(28, 24) /* Read Word Counter */ + +/* Bitfields of FRAME TEST */ +#define REG_FRAME_TEST_FSTC BIT(0) +#define REG_FRAME_TEST_FCRC BIT(1) +#define REG_FRAME_TEST_SDLC BIT(2) +#define REG_FRAME_TEST_TPRM GENMASK(12, 8) + +#define DEV_NAME "loongson_canfd" +#define LOONGSON_CANFD_ID 0xBABE +#define LOONGSON_CANFD_DW_BYTE 4 +#define LOONGSON_CANFD_TXBUF_NUM 8 +#define LOONGSON_CANFD_MAX_RTXTH 0xf + +/** + * struct loongson_canfd_priv - This definition define CAN driver instance + * @can: CAN private data structure. + * @napi: NAPI structure + * @regmap: regmap of the CAN device + * @res: Pointer to the CAN device respurce + * @tx_lock: Lock for synchronizing TX interrupt handling + */ +struct loongson_canfd_priv { + struct can_priv can; /* must be first member! */ + struct napi_struct napi; + struct regmap *regmap; + struct resource *res; + spinlock_t tx_lock; /* protect the sending queue */ +}; + +/** + * enum loongson_canfd_txbuf_sts - status of TX buffer + * @TX_BS_IDLE: Status processed or not sent. + * @TX_BS_VALID: Sending successful. + * @TX_BS_FAIL: Sending failed. + * @TX_BS_CANCEL: Sending cancelled. + */ +enum loongson_canfd_txbuf_sts { + TX_BS_IDLE = 0x0, + TX_BS_VALID = 0x1, + TX_BS_FAIL = 0x2, + TX_BS_CANCEL = 0x3 +}; + +/** + * enum loongson_canfd_txbuf_cmd - command of TX buffer + * @TXT_CMD_ADD: Buffer add request. + * @TXT_CMD_CANCEL: Buffer cancel request. + * @TXT_CMD_SR_CLEAN: Buffer send record clear. + * 1: to clear the send completion record (LOONGSON_CANFD_TX_STAT[BS]). + */ +enum loongson_canfd_txbuf_cmd { + TXT_CMD_ADD = 0x01, + TXT_CMD_CANCEL = 0x02, + TXT_CMD_SR_CLEAN = 0x03 +}; + +static const struct can_bittiming_const loongson_canfd_bit_timing = { + .name = DEV_NAME, + .tseg1_min = 2, + .tseg1_max = 63, + .tseg2_min = 2, + .tseg2_max = 63, + .sjw_max = 31, + .brp_min = 1, + .brp_max = 15, + .brp_inc = 1, +}; + +static const struct can_bittiming_const loongson_canfd_bit_timing_data = { + .name = DEV_NAME, + .tseg1_min = 2, + .tseg1_max = 63, + .tseg2_min = 2, + .tseg2_max = 63, + .sjw_max = 31, + .brp_min = 1, + .brp_max = 255, + .brp_inc = 1, +}; + +/* CAN FD Transmission Delay Compensation constants */ +static const struct can_tdc_const loongson_canfd_tdc_data = { + .tdcv_min = 0, + .tdcv_max = 0, + .tdco_min = 1, + .tdco_max = 127, + .tdcf_min = 0, /* Filter window not supported */ + .tdcf_max = 0, +}; + +/** + * loongson_canfd_enabled() - Gets the controller is enabled + * @priv: Pointer to private data + * + * Return: True - the controller is enabled. + * False - the controller is disabled. + */ +static bool loongson_canfd_enabled(struct loongson_canfd_priv *priv) +{ + return !!regmap_test_bits(priv->regmap, LOONGSON_CANFD_CONF, REG_CONF_ENA); +} + +/** + * loongson_canfd_txbuf_freed() - Gets the flag for TX buffer to be sent + * @priv: Pointer to private data + * + * Return: True - TX buffer is empty. + * False - TX buffer is processing + */ +static bool loongson_canfd_txbuf_freed(struct loongson_canfd_priv *priv) +{ + return !regmap_test_bits(priv->regmap, LOONGSON_CANFD_TX_STAT, REG_TX_STAT_BRP); +} + +/** + * loongson_canfd_disable_interrupts() - Disable all interrupts + * @priv: Pointer to private data + */ +static void loongson_canfd_disable_interrupts(struct loongson_canfd_priv *priv) +{ + regmap_write(priv->regmap, LOONGSON_CANFD_INT_ENA, FIELD_MAX(REG_INT_ENA_CLR) << 16); + regmap_write(priv->regmap, LOONGSON_CANFD_INT_MASK, FIELD_MAX(REG_INT_MASK_SET)); +} + +/** + * loongson_canfd_get_txbuf_sts() - Gets status of TX buffer + * @priv: Pointer to private data + * @buf_id: Buffer index (0-based) + * + * Return: Status of TX buffer + */ +static enum loongson_canfd_txbuf_sts +loongson_canfd_get_txbuf_sts(struct loongson_canfd_priv *priv, u8 buf_id) +{ + u32 sts, mask; + + mask = REG_TX_STAT_BS_TX_MASK_BASE << 2 * buf_id; + regmap_read(priv->regmap, LOONGSON_CANFD_TX_STAT, &sts); + + return field_get(mask, sts); +} + +/** + * loongson_canfd_txbuf_is_writable() - Checks if frame can be inserted to TX Buffer + * @priv: Pointer to private data + * @buf_id: Buffer index (0-based) + * + * Return: True - Frame can be inserted to TXT Buffer, + * False - If attempted, frame will not be inserted to TX Buffer + */ +static bool loongson_canfd_txbuf_is_writable(struct loongson_canfd_priv *priv, u8 buf_id) +{ + enum loongson_canfd_txbuf_sts bs; + + bs = loongson_canfd_get_txbuf_sts(priv, buf_id); + if (bs) + return false; + + return !regmap_test_bits(priv->regmap, LOONGSON_CANFD_TX_STAT, BIT(buf_id)); +} + +/** + * loongson_canfd_set_txbuf_cmd() - Applies command on TX buffer + * @ndev: Pointer to net_device structure + * @cmd: Command to set + * @buf_id: Buffer index (0-based) + */ +static void loongson_canfd_set_txbuf_cmd(struct net_device *ndev, + enum loongson_canfd_txbuf_cmd cmd, u8 buf_id) +{ + struct loongson_canfd_priv *priv = netdev_priv(ndev); + u32 mask; + + switch (cmd) { + case TXT_CMD_ADD: + mask = REG_TX_CMD_BAR; + break; + case TXT_CMD_CANCEL: + mask = REG_TX_CMD_BCR; + break; + case TXT_CMD_SR_CLEAN: + mask = REG_TX_CMD_BSC; + break; + default: + netdev_err(ndev, "Unknown command id: %x\n", cmd); + return; + } + + regmap_write(priv->regmap, LOONGSON_CANFD_TX_CMD, field_prep(mask, BIT(buf_id))); +} + +/** + * loongson_canfd_rxbuf_empty() - Gets the RX buffer is empty + * @priv: Pointer to private data + * + * Return: True - RX buffer is empty. + * False - RX buffer is processing + */ +static bool loongson_canfd_rxbuf_empty(struct loongson_canfd_priv *priv) +{ + return !!regmap_test_bits(priv->regmap, LOONGSON_CANFD_RX_STAT, REG_RX_STAT_RXE); +} + +/** + * loongson_canfd_reset() - Issues software reset request to Loongson CANFD + * @ndev: Pointer to net_device structure + */ +static void loongson_canfd_reset(struct net_device *ndev) +{ + struct loongson_canfd_priv *priv = netdev_priv(ndev); + + regmap_write(priv->regmap, LOONGSON_CANFD_MODE, REG_MODE_RST); + regmap_write(priv->regmap, LOONGSON_CANFD_MODE, REG_MODE_RXBAM | REG_MODE_BUFM); +} + +/** + * loongson_canfd_set_btr() - Sets CAN bus bit timing in Loongson CANFD + * @ndev: Pointer to net_device structure + * @bt: Pointer to Bit timing structure + * @nominal: True - Nominal bit timing, False - Data bit timing + */ +static void loongson_canfd_set_btr(struct net_device *ndev, struct can_bittiming *bt, bool nominal) +{ + struct loongson_canfd_priv *priv = netdev_priv(ndev); + u32 ph1_max = FIELD_MAX(REG_BTR_PH1); + u32 btr = 0; + + /* + * The timing calculation functions have only constraints on tseg1, + * which is prop_seg + phase1_seg combined. + * tseg1 is then split in half and stored into prog_seg and phase_seg1. + * In Loongson CAN-FD, PROP is 7 bits wide but PH1 only 6, so we must + * re-distribute the values here. + */ + if (bt->phase_seg1 > ph1_max) { + bt->prop_seg += bt->phase_seg1 - ph1_max; + bt->phase_seg1 = ph1_max; + } + + if (nominal) { + btr = FIELD_PREP(REG_BTR_PROP, bt->prop_seg) | + FIELD_PREP(REG_BTR_PH1, bt->phase_seg1) | + FIELD_PREP(REG_BTR_PH2, bt->phase_seg2) | + FIELD_PREP(REG_BTR_BRP, bt->brp) | + FIELD_PREP(REG_BTR_SJW, bt->sjw); + + regmap_write(priv->regmap, LOONGSON_CANFD_BTR_NORM, btr); + } else { + btr = FIELD_PREP(REG_BTR_FD_PROP, bt->prop_seg) | + FIELD_PREP(REG_BTR_FD_PH1, bt->phase_seg1) | + FIELD_PREP(REG_BTR_FD_PH2, bt->phase_seg2) | + FIELD_PREP(REG_BTR_FD_BRP, bt->brp) | + FIELD_PREP(REG_BTR_FD_SJW, bt->sjw); + + regmap_write(priv->regmap, LOONGSON_CANFD_BTR_FD, btr); + } +} + +/** + * loongson_canfd_set_bittiming() - CAN set nominal bit timing routine + * @ndev: Pointer to net_device structure + * + * Return: 0 on success, -%EPERM on error + */ +static int loongson_canfd_set_bittiming(struct net_device *ndev) +{ + struct loongson_canfd_priv *priv = netdev_priv(ndev); + struct can_bittiming *bt = &priv->can.bittiming; + + /* Note that bt may be modified here */ + loongson_canfd_set_btr(ndev, bt, true); + + return 0; +} + +/** + * loongson_canfd_set_data_bittiming() - CAN set data bit timing routine + * @ndev: Pointer to net_device structure + * + * Return: 0 on success, -%EPERM on error + */ +static int loongson_canfd_set_data_bittiming(struct net_device *ndev) +{ + struct loongson_canfd_priv *priv = netdev_priv(ndev); + struct can_bittiming *dbt = &priv->can.fd.data_bittiming; + + /* Note that dbt may be modified here */ + loongson_canfd_set_btr(ndev, dbt, false); + + return 0; +} + +/** + * loongson_canfd_get_auto_tdcv - Get Transmitter Delay Compensation Value + * @ndev: Pointer to net_device structure + * @tdcv: Pointer to TDCV value + * + * Return: 0 on success + */ +static int loongson_canfd_get_auto_tdcv(const struct net_device *ndev, u32 *tdcv) +{ + struct loongson_canfd_priv *priv = netdev_priv(ndev); + u32 val; + + regmap_read(priv->regmap, LOONGSON_CANFD_TRV_DLY, &val); + *tdcv = FIELD_GET(REG_TRV_DLY_VAL, val); + + return 0; +} + +/** + * loongson_canfd_set_secondary_sample_point() - Set secondary sample point in Loongson CANFD + * @ndev: Pointer to net_device structure + * + * Return: 0 on success, -%EPERM if controller is enabled + */ +static int loongson_canfd_set_secondary_sample_point(struct net_device *ndev) +{ + struct loongson_canfd_priv *priv = netdev_priv(ndev); + u32 ssp_cfg; + + if (loongson_canfd_enabled(priv)) { + netdev_err(ndev, "BUG! Cannot set SSP - CAN is enabled\n"); + return -EPERM; + } + + if (can_fd_tdc_is_enabled(&priv->can)) + ssp_cfg = FIELD_PREP(REG_SSP_CFG_OFF, priv->can.fd.tdc.tdco) | + FIELD_PREP(REG_SSP_CFG_SRC, 0x0); + else + ssp_cfg = FIELD_PREP(REG_SSP_CFG_SRC, 0x1); + + regmap_write(priv->regmap, LOONGSON_CANFD_SSP_CFG, ssp_cfg); + + return 0; +} + +/** + * loongson_canfd_set_conf_mode() - Sets Loongson CANFD mode and configure registers + * @priv: Pointer to private data + */ +static void loongson_canfd_set_conf_mode(struct loongson_canfd_priv *priv) +{ + u32 ctrlmode = priv->can.ctrlmode; + u32 mode, conf; + + regmap_read(priv->regmap, LOONGSON_CANFD_MODE, &mode); + + if (ctrlmode & CAN_CTRLMODE_LISTENONLY) + mode |= REG_MODE_BMM; + else + mode &= ~REG_MODE_BMM; + + if (ctrlmode & CAN_CTRLMODE_FD) + mode |= REG_MODE_FDE; + else + mode &= ~REG_MODE_FDE; + + if (ctrlmode & CAN_CTRLMODE_PRESUME_ACK) + mode |= REG_MODE_ACF; + else + mode &= ~REG_MODE_ACF; + + /* + * Some bits fixed: + * TSTM - Off, User shall not be able to change REC/TEC by hand + * during operation + */ + mode &= ~REG_MODE_TSTM; + regmap_write(priv->regmap, LOONGSON_CANFD_MODE, mode); + + regmap_read(priv->regmap, LOONGSON_CANFD_CONF, &conf); + + if (ctrlmode & CAN_CTRLMODE_LOOPBACK) + conf |= REG_CONF_ILBP; + else + conf &= ~REG_CONF_ILBP; + + if (ctrlmode & CAN_CTRLMODE_FD_NON_ISO) + conf |= REG_CONF_NISOFD; + else + conf &= ~REG_CONF_NISOFD; + + /* One shot mode supported indirectly via Retransmit limit */ + conf &= ~FIELD_PREP(REG_CONF_RTRTH, LOONGSON_CANFD_MAX_RTXTH); + + if (ctrlmode & CAN_CTRLMODE_ONE_SHOT) + conf |= REG_CONF_RTRLE; + else + conf |= REG_CONF_RTRLE | FIELD_PREP(REG_CONF_RTRTH, LOONGSON_CANFD_MAX_RTXTH); + + regmap_write(priv->regmap, LOONGSON_CANFD_CONF, conf); +} + +/** + * loongson_canfd_chip_start() - This routine starts the driver + * @ndev: Pointer to net_device structure + * + * Return: 0 on success and failure value on error + */ +static int loongson_canfd_chip_start(struct net_device *ndev) +{ + struct loongson_canfd_priv *priv = netdev_priv(ndev); + u16 int_ena, int_msk; + int ret; + + /* Configure bit-rates and ssp */ + ret = loongson_canfd_set_bittiming(ndev); + if (ret < 0) + return ret; + + ret = loongson_canfd_set_data_bittiming(ndev); + if (ret < 0) + return ret; + + ret = loongson_canfd_set_secondary_sample_point(ndev); + if (ret < 0) + return ret; + + /* Setting mode and configure registers */ + loongson_canfd_set_conf_mode(priv); + + /* Configure interrupts */ + int_ena = REG_INT_STAT_RBNEI | REG_INT_STAT_TXBHCI | + REG_INT_STAT_EWLI | REG_INT_STAT_FCSI; + + /* Bus error reporting */ + if (priv->can.ctrlmode & CAN_CTRLMODE_BERR_REPORTING) + int_ena |= REG_INT_STAT_ALI | REG_INT_STAT_BEI; + + int_msk = FIELD_GET(REG_INT_MASK_SET, ~int_ena); /* Mask all disabled interrupts */ + + /* It's after reset, so there is no need to clear anything */ + regmap_write(priv->regmap, LOONGSON_CANFD_INT_MASK, int_msk); + regmap_write(priv->regmap, LOONGSON_CANFD_INT_ENA, int_ena); + + /* Controller enters ERROR_ACTIVE on initial FCSI */ + priv->can.state = CAN_STATE_STOPPED; + + /* Enable the controller */ + regmap_update_bits(priv->regmap, LOONGSON_CANFD_CONF, REG_CONF_ENA, REG_CONF_ENA); + + return 0; +} + +/** + * loongson_canfd_do_set_mode() - Sets mode of the driver + * @ndev: Pointer to net_device structure + * @mode: Tells the mode of the driver + * + * This check the drivers state and calls the corresponding modes to set. + * + * Return: 0 on success and failure value on error + */ +static int loongson_canfd_do_set_mode(struct net_device *ndev, enum can_mode mode) +{ + int ret; + + switch (mode) { + case CAN_MODE_START: + loongson_canfd_reset(ndev); + + ret = loongson_canfd_chip_start(ndev); + if (ret < 0) { + netdev_err(ndev, "loongson_canfd_chip_start failed!\n"); + return ret; + } + + netif_wake_queue(ndev); + break; + default: + ret = -EOPNOTSUPP; + break; + } + + return ret; +} + +/** + * loongson_canfd_insert_frame() - Inserts frame to TXT buffer + * @priv: Pointer to private data + * @cf: Pointer to CAN frame to be inserted + * @buf: TXT Buffer index to which frame is inserted (0-based) + * @isfdf: True - CAN FD Frame, False - CAN 2.0 Frame + * + * Return: + * * True - Frame inserted successfully + * * False - Frame was not inserted due to one of: + * 1. TXT Buffer is not writable (it is in wrong state) + * 2. Invalid TXT buffer index + * 3. Invalid frame length + */ +static bool loongson_canfd_insert_frame(struct loongson_canfd_priv *priv, + const struct canfd_frame *cf, u8 buf, bool isfdf) +{ + u32 meta0, meta1 = 0; + + if (buf >= LOONGSON_CANFD_TXBUF_NUM) + return false; + + if (!loongson_canfd_txbuf_is_writable(priv, buf)) + return false; + + /* Prepare identifier */ + if (cf->can_id & CAN_EFF_FLAG) { + meta0 = cf->can_id & CAN_EFF_MASK; + meta0 |= REG_FRAME_META0_XDT; + } else { + meta0 = FIELD_PREP(REG_FRAME_META0_ID_BASE, cf->can_id & CAN_SFF_MASK); + } + + /* Prepare Frame format */ + if (cf->can_id & CAN_RTR_FLAG) + meta0 |= REG_FRAME_META0_RTR; + + if (isfdf) { + meta1 = REG_FRAME_META1_FDF; + + if (cf->flags & CANFD_BRS) + meta1 |= REG_FRAME_META1_BRS; + } + + meta1 |= FIELD_PREP(REG_FRAME_META1_DLC, can_fd_len2dlc(cf->len)); + + /* TXT buffer select */ + regmap_write(priv->regmap, LOONGSON_CANFD_TX_SEL, buf); + + /* Write ID, Frame format */ + regmap_write(priv->regmap, LOONGSON_CANFD_TX_DATA_1 + LOONGSON_CANFD_FRAME_META0, meta0); + regmap_write(priv->regmap, LOONGSON_CANFD_TX_DATA_1 + LOONGSON_CANFD_FRAME_META1, meta1); + + if (cf->can_id & CAN_RTR_FLAG) + return true; + + /* Write Data payload */ + for (unsigned int i = 0; i < cf->len; i += LOONGSON_CANFD_DW_BYTE) { + regmap_write(priv->regmap, + LOONGSON_CANFD_TX_DATA_1 + LOONGSON_CANFD_FRAME_DB_1 + i, + *(u32 *)(cf->data + i)); + } + + return true; +} + +/** + * loongson_canfd_start_xmit() - Starts the transmission + * @skb: sk_buff pointer that contains data to be Txed + * @ndev: Pointer to net_device structure + * + * Invoked from upper layers to initiate transmission. Uses the next available free TX Buffer and + * populates its fields to start the transmission. + * + * Return: %NETDEV_TX_OK on success, + * %NETDEV_TX_BUSY when no free TX buffer is available, negative return values reserved + * for error cases. + */ +static netdev_tx_t loongson_canfd_start_xmit(struct sk_buff *skb, struct net_device *ndev) +{ + struct canfd_frame *cf = (struct canfd_frame *)skb->data; + struct loongson_canfd_priv *priv = netdev_priv(ndev); + u32 buf_id = 0, tx_stat, i = 0; + unsigned long tx_brp; + u16 tx_bs; + + if (can_dropped_invalid_skb(ndev, skb)) + return NETDEV_TX_OK; + + guard(spinlock_irqsave)(&priv->tx_lock); + + if (unlikely(!loongson_canfd_txbuf_freed(priv))) { + netif_stop_queue(ndev); + netdev_err(ndev, "BUG!, no TXB free when queue awake!\n"); + return NETDEV_TX_BUSY; + } + + regmap_read(priv->regmap, LOONGSON_CANFD_TX_STAT, &tx_stat); + tx_brp = FIELD_GET(REG_TX_STAT_BRP, tx_stat); + tx_bs = FIELD_GET(REG_TX_STAT_BS, tx_stat); + + for_each_clear_bit(i, &tx_brp, LOONGSON_CANFD_TXBUF_NUM) { + u32 transmission_complete_mask = 0x3 << (i * 2); + + if (!(tx_bs & transmission_complete_mask)) { + buf_id = i; + break; + } + } + + if (!loongson_canfd_insert_frame(priv, cf, buf_id, can_is_canfd_skb(skb))) { + netdev_err(ndev, "TXNF set but cannot insert frame into TXTB! HW Bug?"); + dev_kfree_skb_any(skb); + ndev->stats.tx_dropped++; + return NETDEV_TX_OK; + } + + can_put_echo_skb(skb, ndev, buf_id, 0); + + loongson_canfd_set_txbuf_cmd(ndev, TXT_CMD_ADD, buf_id); + + /* Check if all TX buffers are full */ + if (!loongson_canfd_txbuf_freed(priv)) + netif_stop_queue(ndev); + + return NETDEV_TX_OK; +} + +/** + * loongson_canfd_rx() - Called from CAN ISR to complete the received frame processing + * @ndev: Pointer to net_device structure + * + * This function is invoked from the CAN isr to process the Rx frames. It does minimal + * processing and invokes "netif_receive_skb" to complete further processing. + * Return: 1 when frame is passed to the network layer or system is out of free SKBs + * temporally and left code to resolve SKB allocation later, -%EAGAIN in a case + * of empty Rx FIFO. + */ +static int loongson_canfd_rx(struct net_device *ndev) +{ + struct loongson_canfd_priv *priv = netdev_priv(ndev); + struct net_device_stats *stats = &ndev->stats; + u32 meta0, meta1, dlc, rwcnt, dbcnt, i, data; + struct canfd_frame *cfd; + struct can_frame *ccf; + struct sk_buff *skb; + bool is_can_fd; + + regmap_read(priv->regmap, LOONGSON_CANFD_RX_DATA, &meta0); + regmap_read(priv->regmap, LOONGSON_CANFD_RX_DATA, &meta1); + + /* Number of characters received */ + rwcnt = FIELD_GET(REG_FRAME_META1_RWCNT, meta1); + if (!rwcnt) + return -EAGAIN; + dbcnt = (rwcnt - 2) * LOONGSON_CANFD_DW_BYTE; + + /* Flexible Data-rate Format */ + is_can_fd = meta1 & REG_FRAME_META1_FDF; + if (is_can_fd) { + skb = alloc_canfd_skb(ndev, &cfd); + } else { + skb = alloc_can_skb(ndev, &ccf); + cfd = (struct canfd_frame *)ccf; + } + + if (unlikely(!skb)) { + for (i = 0; i < dbcnt; i += LOONGSON_CANFD_DW_BYTE) + regmap_read(priv->regmap, LOONGSON_CANFD_RX_DATA, &data); + ndev->stats.rx_dropped++; + return 1; + } + + /* Extended Identifier Type */ + if (meta0 & REG_FRAME_META0_XDT) + cfd->can_id = (meta0 & CAN_EFF_MASK) | CAN_EFF_FLAG; + else + cfd->can_id = FIELD_GET(REG_FRAME_META0_ID_BASE, meta0) & CAN_SFF_MASK; + + /* BRS, ESI, RTR Flags */ + cfd->flags = 0; + + if (is_can_fd) { + if (meta1 & REG_FRAME_META1_BRS) + cfd->flags |= CANFD_BRS; + + if (meta0 & REG_FRAME_META0_ESI) + cfd->flags |= CANFD_ESI; + } else if (meta0 & REG_FRAME_META0_RTR) { + cfd->can_id |= CAN_RTR_FLAG; + } + + /* Data Length Code */ + dlc = FIELD_GET(REG_FRAME_META1_DLC, meta1); + if (is_can_fd) + cfd->len = can_fd_dlc2len(dlc); + else + can_frame_set_cc_len(ccf, dlc, priv->can.ctrlmode); + + /* Copy payload */ + for (i = 0; i < dbcnt; i += LOONGSON_CANFD_DW_BYTE) + regmap_read(priv->regmap, LOONGSON_CANFD_RX_DATA, (u32 *)(cfd->data + i)); + + /* Drain any residual (should not happen) */ + while (unlikely(i < dbcnt)) { + regmap_read(priv->regmap, LOONGSON_CANFD_RX_DATA, &data); + i += LOONGSON_CANFD_DW_BYTE; + } + + if (!(cfd->can_id & CAN_RTR_FLAG)) + stats->rx_bytes += cfd->len; + stats->rx_packets++; + netif_receive_skb(skb); + + return 1; +} + +/** + * loongson_canfd_read_fault_state() - Reads Loongson CANFD fault state. + * @ndev: Pointer to net_device structure + * + * Returns: Fault confinement state of controller + */ +static enum can_state loongson_canfd_read_fault_state(struct net_device *ndev) +{ + struct loongson_canfd_priv *priv = netdev_priv(ndev); + enum can_state sts = CAN_STATE_ERROR_PASSIVE; + u32 fstat, ewl, erl, rec_tec, max_tec; + + regmap_read(priv->regmap, LOONGSON_CANFD_FSTAT, &fstat); + + switch (FIELD_GET(REG_FSTAT_MASK, fstat)) { + case REG_FSTAT_ERA: + + regmap_read(priv->regmap, LOONGSON_CANFD_ERL, &erl); + regmap_read(priv->regmap, LOONGSON_CANFD_ERC, &rec_tec); + + ewl = FIELD_GET(REG_ERL_EW, erl); + max_tec = max(FIELD_GET(REG_ERC_REC, rec_tec), FIELD_GET(REG_ERC_TEC, rec_tec)); + + if (ewl > max_tec) + sts = CAN_STATE_ERROR_ACTIVE; + else + sts = CAN_STATE_ERROR_WARNING; + break; + case REG_FSTAT_ERP: + sts = CAN_STATE_ERROR_PASSIVE; + break; + case REG_FSTAT_BOF: + sts = CAN_STATE_BUS_OFF; + break; + default: + netdev_err(ndev, "Invalid error state.\n"); + break; + } + + return sts; +} + +/** + * loongson_canfd_get_bec() - Reads REC/TEC counter values from controller + * @priv: Pointer to private data + * @bec: Pointer to Error counter structure + */ +static void loongson_canfd_get_bec(struct loongson_canfd_priv *priv, struct can_berr_counter *bec) +{ + u32 erc; + + regmap_read(priv->regmap, LOONGSON_CANFD_ERC, &erc); + bec->rxerr = FIELD_GET(REG_ERC_REC, erc); + bec->txerr = FIELD_GET(REG_ERC_TEC, erc); +} + +/** + * loongson_canfd_get_berr_counter() - error counter routine + * @ndev: Pointer to net_device structure + * @bec: Pointer to can_berr_counter structure + * + * Return: 0 always + */ +static int loongson_canfd_get_berr_counter(const struct net_device *ndev, + struct can_berr_counter *bec) +{ + struct loongson_canfd_priv *priv = netdev_priv(ndev); + + loongson_canfd_get_bec(priv, bec); + return 0; +} + +/** + * loongson_canfd_err_interrupt() - Error frame ISR + * @ndev: net_device pointer + * @isr: interrupt status register value + * + * This is the CAN error interrupt and it will check the type of error and forward the error + * frame to upper layers. + */ +static void loongson_canfd_err_interrupt(struct net_device *ndev, u32 isr) +{ + struct loongson_canfd_priv *priv = netdev_priv(ndev); + struct net_device_stats *stats = &ndev->stats; + struct can_berr_counter bec; + u32 capt, alc, mask = 0; + enum can_state state; + struct can_frame *cf; + struct sk_buff *skb; + + loongson_canfd_get_bec(priv, &bec); + + state = loongson_canfd_read_fault_state(ndev); + regmap_read(priv->regmap, LOONGSON_CANFD_ERR_CAPT, &capt); + regmap_read(priv->regmap, LOONGSON_CANFD_ALC, &alc); + + netdev_dbg(ndev, "%s: ISR 0x%08x, rxerr %d, txerr %d, error type %lu, pos %lu, ALC id_field %lu, bit %lu\n", + __func__, isr, bec.rxerr, bec.txerr, + FIELD_GET(REG_ERR_CAPT_TYPE, capt), FIELD_GET(REG_ERR_CAPT_POS, capt), + FIELD_GET(REG_ALC_ID_FIELD, alc), FIELD_GET(REG_ALC_BIT_POS, alc)); + + skb = alloc_can_err_skb(ndev, &cf); + + /* + * EWLI: error warning limit condition met + * FCSI: fault confinement state changed + * ALI: arbitration lost (just informative) + * BEI: bus error interrupt + */ + if ((isr & REG_INT_STAT_FCSI) || (isr & REG_INT_STAT_EWLI)) { + netdev_info(ndev, "state changes from %s to %s\n", + can_get_state_str(priv->can.state), can_get_state_str(state)); + + if (priv->can.state == state) + netdev_warn(ndev, "cur and pre state is the same!(miss intr?)\n"); + + mask |= REG_INT_STAT_FCSI | REG_INT_STAT_EWLI; + priv->can.state = state; + switch (state) { + case CAN_STATE_BUS_OFF: + priv->can.can_stats.bus_off++; + can_bus_off(ndev); + if (skb) + cf->can_id |= CAN_ERR_BUSOFF; + break; + case CAN_STATE_ERROR_PASSIVE: + priv->can.can_stats.error_passive++; + if (skb) { + cf->can_id |= CAN_ERR_CRTL | CAN_ERR_CNT; + if (bec.rxerr >= CAN_ERROR_PASSIVE_THRESHOLD) + cf->data[1] = CAN_ERR_CRTL_RX_PASSIVE; + else + cf->data[1] = CAN_ERR_CRTL_TX_PASSIVE; + cf->data[6] = bec.txerr; + cf->data[7] = bec.rxerr; + } + break; + case CAN_STATE_ERROR_WARNING: + priv->can.can_stats.error_warning++; + if (skb) { + cf->can_id |= CAN_ERR_CRTL | CAN_ERR_CNT; + if (bec.txerr > bec.rxerr) + cf->data[1] |= CAN_ERR_CRTL_TX_WARNING; + else + cf->data[1] |= CAN_ERR_CRTL_RX_WARNING; + cf->data[6] = bec.txerr; + cf->data[7] = bec.rxerr; + } + break; + case CAN_STATE_ERROR_ACTIVE: + if (skb) { + cf->can_id |= CAN_ERR_CNT; + cf->data[1] = CAN_ERR_CRTL_ACTIVE; + cf->data[6] = bec.txerr; + cf->data[7] = bec.rxerr; + } + break; + default: + netdev_err(ndev, "Unexpected state: %d, %s!\n", state, + can_get_state_str(state)); + break; + } + } + + /* Check for Arbitration Lost interrupt */ + if (isr & REG_INT_STAT_ALI) { + mask |= REG_INT_STAT_ALI; + netdev_err(ndev, "Arbitration Lost interrupt\n"); + priv->can.can_stats.arbitration_lost++; + if (skb) { + cf->can_id |= CAN_ERR_LOSTARB; + cf->data[0] = CAN_ERR_LOSTARB_UNSPEC; + } + } + + /* Check for Bus Error interrupt */ + if (isr & REG_INT_STAT_BEI) { + mask |= REG_INT_STAT_BEI; + netdev_err(ndev, "Bus Error interrupt\n"); + priv->can.can_stats.bus_error++; + stats->rx_errors++; + if (skb) { + cf->can_id |= CAN_ERR_PROT | CAN_ERR_BUSERROR; + cf->data[2] = CAN_ERR_PROT_UNSPEC; + cf->data[3] = CAN_ERR_PROT_LOC_UNSPEC; + } + } + + if (skb) + netif_rx(skb); + + regmap_write(priv->regmap, LOONGSON_CANFD_INT_STAT, mask); + regmap_write(priv->regmap, LOONGSON_CANFD_INT_MASK, mask << 16); +} + +static int loongson_canfd_rx_napi(struct napi_struct *napi, int quota) +{ + struct net_device *ndev = napi->dev; + struct loongson_canfd_priv *priv = netdev_priv(ndev); + int work_done = 0, ret = 1; + bool rxbuf_is_empty; + u32 sts; + + rxbuf_is_empty = loongson_canfd_rxbuf_empty(priv); + + while (!rxbuf_is_empty && work_done < quota && ret != -EAGAIN) { + ret = loongson_canfd_rx(ndev); + work_done++; + rxbuf_is_empty = loongson_canfd_rxbuf_empty(priv); + } + + /* Check for RX FIFO Overflow */ + regmap_read(priv->regmap, LOONGSON_CANFD_STAT, &sts); + if (sts & REG_STAT_DOR) { + struct net_device_stats *stats = &ndev->stats; + struct can_frame *cf; + struct sk_buff *skb; + + netdev_info(ndev, "Loongson canfd RX overflow\n"); + stats->rx_over_errors++; + stats->rx_errors++; + + skb = alloc_can_err_skb(ndev, &cf); + if (skb) { + cf->can_id |= CAN_ERR_CRTL; + cf->data[1] |= CAN_ERR_CRTL_RX_OVERFLOW; + netif_rx(skb); + } + + /* Clear Data Overrun */ + regmap_write(priv->regmap, LOONGSON_CANFD_CMD, REG_CMD_CDO); + } + + if (rxbuf_is_empty) { + if (napi_complete_done(napi, work_done)) { + /* + * Clear and enable RBNEI. It is level-triggered, + * so there is no race condition. + */ + regmap_write(priv->regmap, LOONGSON_CANFD_INT_STAT, REG_INT_STAT_RBNEI); + regmap_write(priv->regmap, LOONGSON_CANFD_INT_MASK, + (REG_INT_STAT_RBNEI << 16)); + } + } + + return work_done; +} + +/** + * loongson_canfd_tx_interrupt() - Tx done Isr + * @ndev: net_device pointer + */ +static void loongson_canfd_tx_interrupt(struct net_device *ndev) +{ + struct loongson_canfd_priv *priv = netdev_priv(ndev); + struct net_device_stats *stats = &ndev->stats; + enum loongson_canfd_txbuf_sts sts; + u8 processed_mask = 0; + u32 buf_id, cnt; + + guard(spinlock_irqsave)(&priv->tx_lock); + + for (buf_id = 0; buf_id < LOONGSON_CANFD_TXBUF_NUM; buf_id++) { + if (processed_mask & BIT(buf_id)) + continue; + + sts = loongson_canfd_get_txbuf_sts(priv, buf_id); + if (sts == TX_BS_IDLE) + continue; + processed_mask |= BIT(buf_id); + + switch (sts) { + case TX_BS_VALID: + stats->tx_bytes += can_get_echo_skb(ndev, buf_id, NULL); + stats->tx_packets++; + break; + case TX_BS_FAIL: + /* + * This indicated that retransmit limit has been reached. + * Obviously we should not echo the frame, but also not indicate any + * kind of error. If desired, it was already reported (possible + * multiple times) on each arbitration lost. + */ + regmap_read(priv->regmap, LOONGSON_CANFD_TX_FR_CNT, &cnt); + netdev_warn(ndev, "TXB in FAIL state, TX frame count: %d\n", cnt); + can_free_echo_skb(ndev, buf_id, NULL); + stats->tx_dropped++; + break; + case TX_BS_CANCEL: + /* + * We *could* re-queue the frame, but multiqueue/abort is + * not supported yet anyway. + */ + netdev_warn(ndev, "TXB in CANCEL state\n"); + can_free_echo_skb(ndev, buf_id, NULL); + stats->tx_dropped++; + break; + default: + netdev_warn(ndev, "TXB unexpected status: %d\n", sts); + break; + } + + loongson_canfd_set_txbuf_cmd(ndev, TXT_CMD_SR_CLEAN, buf_id); + } + + /* + * If no buffers were processed this time, we cannot clear - that would introduce + * a race condition. + */ + if (processed_mask) { + /* + * Clear the interrupt again. We do not want to receive again interrupt + * for the buffer already handled. If it is the last finished one then + * it would cause log of spurious interrupt. + */ + regmap_write(priv->regmap, LOONGSON_CANFD_INT_STAT, REG_INT_STAT_TXBHCI); + } + + /* Check if at least one TX buffer is free */ + if (loongson_canfd_txbuf_freed(priv)) + netif_wake_queue(ndev); +} + +/** + * loongson_canfd_interrupt() - CAN Isr + * @irq: irq number + * @dev_id: device id pointer + * + * This is the Loongson CANFD ISR. It checks for the type of interrupt + * and invokes the corresponding ISR. + * + * Return: + * IRQ_NONE - If CAN device is in sleep mode, IRQ_HANDLED otherwise + */ +static irqreturn_t loongson_canfd_interrupt(int irq, void *dev_id) +{ + struct net_device *ndev = (struct net_device *)dev_id; + struct loongson_canfd_priv *priv = netdev_priv(ndev); + u32 isr, imask; + + for (unsigned int irq_loops = 0; irq_loops < 10000; irq_loops++) { + /* Get the interrupt status */ + regmap_read(priv->regmap, LOONGSON_CANFD_INT_STAT, &isr); + if (!isr) { + if (irq_loops) + return IRQ_HANDLED; + else + return IRQ_NONE; + } + + /* Receive Buffer Not Empty Interrupt */ + if (isr & REG_INT_STAT_RBNEI) { + /* + * Mask RXBNEI the first, then clear interrupt and schedule NAPI. + * Even if another IRQ fires, RBNEI will always be 0 (masked). + */ + regmap_write(priv->regmap, LOONGSON_CANFD_INT_MASK, REG_INT_STAT_RBNEI); + regmap_write(priv->regmap, LOONGSON_CANFD_INT_STAT, REG_INT_STAT_RBNEI); + napi_schedule(&priv->napi); + } + + /* TX Buffer HW Command Interrupt */ + if (isr & REG_INT_STAT_TXBHCI) + loongson_canfd_tx_interrupt(ndev); + + /* Error interrupts */ + imask = isr & REG_INT_STAT_ERRORI; + if (imask) { + regmap_write(priv->regmap, LOONGSON_CANFD_INT_MASK, imask); + regmap_write(priv->regmap, LOONGSON_CANFD_INT_STAT, imask); + loongson_canfd_err_interrupt(ndev, isr); + } + + /* Ignore RI, TI, LFI, RFI, BSI */ + } + + netdev_err(ndev, "Intterupt state: 0x%x.\n", isr); + + if (isr & REG_INT_STAT_TXBHCI) { + for (unsigned int i = 0; i < LOONGSON_CANFD_TXBUF_NUM; i++) { + u32 sts = loongson_canfd_get_txbuf_sts(priv, i); + + netdev_err(ndev, "TX buf %d status: 0x%x.\n", i, sts); + } + } + + loongson_canfd_disable_interrupts(priv); + + return IRQ_HANDLED; +} + +/** + * loongson_canfd_chip_stop() - Driver stop routine + * @ndev: Pointer to net_device structure + * + * This is the drivers stop routine. It will disable the interrupts and + * disable the controller. + */ +static void loongson_canfd_chip_stop(struct net_device *ndev) +{ + struct loongson_canfd_priv *priv = netdev_priv(ndev); + + /* Disable interrupts and disable CAN */ + loongson_canfd_disable_interrupts(priv); + regmap_update_bits(priv->regmap, LOONGSON_CANFD_CONF, REG_CONF_ENA, 0); + + priv->can.state = CAN_STATE_STOPPED; +} + +/** + * loongson_canfd_open() - Driver open routine + * @ndev: Pointer to net_device structure + * + * This is the driver open routine. + * Return: 0 on success and failure value on error + */ +static int loongson_canfd_open(struct net_device *ndev) +{ + struct loongson_canfd_priv *priv = netdev_priv(ndev); + int ret; + + loongson_canfd_reset(ndev); + + /* Common open */ + ret = open_candev(ndev); + if (ret) { + netdev_warn(ndev, "open_candev failed!\n"); + return ret; + } + + napi_enable(&priv->napi); + ret = request_irq(ndev->irq, loongson_canfd_interrupt, IRQF_SHARED, ndev->name, ndev); + if (ret < 0) { + netdev_err(ndev, "irq allocation for CAN failed\n"); + goto exit_napi_disable; + } + + ret = loongson_canfd_chip_start(ndev); + if (ret < 0) { + netdev_err(ndev, "loongson_canfd_chip_start failed!\n"); + goto exit_free_irq; + } + + netdev_info(ndev, "loongson_canfd_device registered\n"); + netif_start_queue(ndev); + + return 0; + +exit_free_irq: + free_irq(ndev->irq, ndev); +exit_napi_disable: + napi_disable(&priv->napi); + close_candev(ndev); + return ret; +} + +/** + * loongson_canfd_close() - Driver close routine + * @ndev: Pointer to net_device structure + * + * Return: 0 always + */ +static int loongson_canfd_close(struct net_device *ndev) +{ + struct loongson_canfd_priv *priv = netdev_priv(ndev); + + netif_stop_queue(ndev); + napi_disable(&priv->napi); + loongson_canfd_chip_stop(ndev); + free_irq(ndev->irq, ndev); + close_candev(ndev); + + return 0; +} + +static const struct net_device_ops loongson_canfd_netdev_ops = { + .ndo_open = loongson_canfd_open, + .ndo_stop = loongson_canfd_close, + .ndo_start_xmit = loongson_canfd_start_xmit, +}; + +static const struct ethtool_ops loongson_canfd_ethtool_ops = { + .get_ts_info = ethtool_op_get_ts_info, +}; + +static const struct regmap_range loongson_canfd_reg_table_wr_range[] = { + regmap_reg_range(LOONGSON_CANFD_DEVICE_ID, LOONGSON_CANFD_CONF), + regmap_reg_range(LOONGSON_CANFD_CMD, LOONGSON_CANFD_CMD), + regmap_reg_range(LOONGSON_CANFD_INT_STAT, LOONGSON_CANFD_ERL), + regmap_reg_range(LOONGSON_CANFD_CTR_PRES, LOONGSON_CANFD_CTR_PRES), + regmap_reg_range(LOONGSON_CANFD_SSP_CFG, LOONGSON_CANFD_SSP_CFG), + regmap_reg_range(LOONGSON_CANFD_TS, LOONGSON_CANFD_FLT_CTRL), + regmap_reg_range(LOONGSON_CANFD_TX_CMD, LOONGSON_CANFD_TX_DATA_18), +}; + +static const struct regmap_range loongson_canfd_reg_table_rd_range[] = { + regmap_reg_range(LOONGSON_CANFD_DEVICE_ID, LOONGSON_CANFD_STAT), + regmap_reg_range(LOONGSON_CANFD_INT_STAT, LOONGSON_CANFD_BRE), + regmap_reg_range(LOONGSON_CANFD_ERR_CAPT, LOONGSON_CANFD_TX_STAT), + regmap_reg_range(LOONGSON_CANFD_TX_SEL, LOONGSON_CANFD_TX_DATA_18), +}; + +static const struct regmap_access_table loongson_canfd_reg_table_wr = { + .yes_ranges = loongson_canfd_reg_table_wr_range, + .n_yes_ranges = ARRAY_SIZE(loongson_canfd_reg_table_wr_range), +}; + +static const struct regmap_access_table loongson_canfd_reg_table_rd = { + .yes_ranges = loongson_canfd_reg_table_rd_range, + .n_yes_ranges = ARRAY_SIZE(loongson_canfd_reg_table_rd_range), +}; + +static bool loongson_canfd_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case LOONGSON_CANFD_MODE: + case LOONGSON_CANFD_CONF: + case LOONGSON_CANFD_STAT: + case LOONGSON_CANFD_INT_STAT: + case LOONGSON_CANFD_INT_ENA: + case LOONGSON_CANFD_INT_MASK: + case LOONGSON_CANFD_ERL: + case LOONGSON_CANFD_FSTAT: + case LOONGSON_CANFD_ERC: + case LOONGSON_CANFD_ERR_CAPT: + case LOONGSON_CANFD_ALC: + case LOONGSON_CANFD_TRV_DLY: + case LOONGSON_CANFD_TX_FR_CNT: + case LOONGSON_CANFD_RX_STAT: + case LOONGSON_CANFD_RX_DATA: + case LOONGSON_CANFD_TX_STAT: + case LOONGSON_CANFD_TX_SEL: + return true; + default: + return false; + }; +} + +static const struct regmap_config loongson_cangfd_regmap = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .wr_table = &loongson_canfd_reg_table_wr, + .rd_table = &loongson_canfd_reg_table_rd, + .volatile_reg = loongson_canfd_volatile_reg, + .max_register = LOONGSON_CANFD_TX_DATA_18, + .cache_type = REGCACHE_MAPLE, +}; + +static int loongson_canfd_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct loongson_canfd_priv *priv; + struct net_device *ndev; + struct regmap *regmap; + struct resource *res; + void __iomem *base; + int ret = 0, irq; + u32 clk_rate; + + base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(base)) + return PTR_ERR(base); + + regmap = devm_regmap_init_mmio(dev, base, &loongson_cangfd_regmap); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + ret = device_property_read_u32(dev, "clock-frequency", &clk_rate); + if (ret) + return dev_err_probe(dev, ret, "Missing clock-frequency property\n"); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + /* Create a CAN device instance */ + ndev = alloc_candev(sizeof(*priv), LOONGSON_CANFD_TXBUF_NUM); + if (!ndev) + return -ENOMEM; + + priv = netdev_priv(ndev); + spin_lock_init(&priv->tx_lock); + priv->regmap = regmap; + priv->res = res; + + priv->can.clock.freq = clk_rate; + priv->can.bittiming_const = &loongson_canfd_bit_timing; + priv->can.fd.data_bittiming_const = &loongson_canfd_bit_timing_data; + priv->can.fd.tdc_const = &loongson_canfd_tdc_data; + + priv->can.fd.do_set_data_bittiming = loongson_canfd_set_data_bittiming; + priv->can.fd.do_get_auto_tdcv = loongson_canfd_get_auto_tdcv; + priv->can.do_set_mode = loongson_canfd_do_set_mode; + priv->can.do_set_bittiming = loongson_canfd_set_bittiming; + priv->can.do_get_berr_counter = loongson_canfd_get_berr_counter; + + priv->can.ctrlmode_supported = CAN_CTRLMODE_LOOPBACK | CAN_CTRLMODE_LISTENONLY | + CAN_CTRLMODE_ONE_SHOT | CAN_CTRLMODE_BERR_REPORTING | + CAN_CTRLMODE_FD | CAN_CTRLMODE_PRESUME_ACK | + CAN_CTRLMODE_FD_NON_ISO | CAN_CTRLMODE_TDC_AUTO | + CAN_CTRLMODE_CC_LEN8_DLC; + + ndev->irq = irq; + ndev->flags |= IFF_ECHO; /* We support local echo */ + platform_set_drvdata(pdev, ndev); + ndev->netdev_ops = &loongson_canfd_netdev_ops; + ndev->ethtool_ops = &loongson_canfd_ethtool_ops; + SET_NETDEV_DEV(ndev, dev); + + netif_napi_add(ndev, &priv->napi, loongson_canfd_rx_napi); + + ret = register_candev(ndev); + if (ret) { + dev_err(dev, "register_candev failed with %d\n", ret); + goto err_candev_free; + } + + return 0; + +err_candev_free: + netif_napi_del(&priv->napi); + free_candev(ndev); + return ret; +} + +/** + * loongson_canfd_remove - Unregister the device after releasing the resources + * @pdev: Handle to the platform device structure + * + * This function frees all the resources allocated to the device. + */ +static void loongson_canfd_remove(struct platform_device *pdev) +{ + struct net_device *ndev = platform_get_drvdata(pdev); + struct loongson_canfd_priv *priv = netdev_priv(ndev); + + unregister_candev(ndev); + netif_napi_del(&priv->napi); + free_candev(ndev); +} + +static const struct acpi_device_id loongson_canfd_acpi_match[] = { + { .id = "LOON0015" }, + { } +}; +MODULE_DEVICE_TABLE(acpi, loongson_canfd_acpi_match); + +static struct platform_driver loongson_canfd_driver = { + .probe = loongson_canfd_probe, + .remove = loongson_canfd_remove, + .driver = { + .name = DEV_NAME, + .acpi_match_table = loongson_canfd_acpi_match, + }, +}; +module_platform_driver(loongson_canfd_driver); + +MODULE_AUTHOR("Loongson Technology Corporation Limited"); +MODULE_DESCRIPTION("Loongson CAN-FD Controller driver"); +MODULE_LICENSE("GPL"); -- 2.52.0