From: Moriis Ku <saumah@gmail.com>
To: wg@grandegger.com
Cc: mkl@pengutronix.de, linux-can@vger.kernel.org,
jason_lee@sunix.com, taian.chen@sunix.com, morris_ku@sunix.com,
edward.lee@sunix.com, Morris Ku <saumah@gmail.com>,
Edward Lee <Edward.lee@sunix.com>
Subject: [PATCH] SUNIX SDC CAN controller driver
Date: Fri, 25 Jun 2021 17:45:20 +0800 [thread overview]
Message-ID: <20210625094520.13283-1-saumah@gmail.com> (raw)
From: Morris Ku <saumah@gmail.com>
Add support for SUNIX SDC CAN controller
Cc: Jason Lee <jason_lee@sunix.com>
Cc: Taian Chen <taian.chen@sunix.com>
Cc: Morris Ku <morris_ku@sunix.com>
Cc: Edward Lee <Edward.lee@sunix.com>
Signed-off-by: Morris Ku <saumah@gmail.com>
---
sx2010_can.c | 1243 ++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 1243 insertions(+)
create mode 100644 sx2010_can.c
diff --git a/sx2010_can.c b/sx2010_can.c
new file mode 100644
index 0000000..5d9d360
--- /dev/null
+++ b/sx2010_can.c
@@ -0,0 +1,1243 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * SUNIX SDC CAN controller driver.
+ *
+ * Copyright (C) 2021, SUNIX Co., Ltd.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/of_platform.h>
+#include <linux/io.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/netdevice.h>
+#include <linux/can/core.h>
+#include <linux/can/dev.h>
+#include <linux/can/led.h>
+
+#define DRIVER_NAME "sx2010_can"
+
+#define SX2010_REG_CTRL 0
+#define SX2010_CTRL_ENABLE BIT(0)
+#define SX2010_CTRL_TX_IRQ_ENABLE BIT(1)
+#define SX2010_CTRL_RX_IRQ_ENABLE BIT(2)
+#define SX2010_CTRL_DEV_IRQ_ENABLE BIT(3)
+#define SX2010_REG_STATUS 4
+#define SX2010_STATUS_BUSY BIT(0)
+#define SX2010_STATUS_TX_IRQ BIT(1)
+#define SX2010_STATUS_RX_IRQ BIT(2)
+#define SX2010_STATUS_DEV_IRQ BIT(3)
+#define SX2010_REG_TIMING_CTRL 8
+#define SX2010_REG_IRQ_ENABLE 12
+#define SX2010_REG_IRQ_STATUS 16
+#define SX2010_REG_GPIO_OUT_ENABLE 20
+#define SX2010_REG_GPIO_OUT 24
+#define SX2010_REG_TRANS_CTRL_0 28
+#define SX2010_TRANS_ENABLE BIT(0)
+#define SX2010_TRANS_WRITE BIT(1)
+#define SX2010_REG_TRANS_CTRL_1 32
+#define SX2010_REG_RAM 512
+
+#define SX2010_WAIT_MAX_LOOP 2000
+#define SX2010_ISR_MAX_CNT 10
+#define SX2010_ISR_TIME (HZ / 100)
+#define SX2010_CAN_MAX_DATA_LEN 8
+#define SX2010_CAN_TRANS_BUF_LEN (6 + SX2010_CAN_MAX_DATA_LEN)
+#define SX2010_BUF_LEN 512
+#define SX2010_TX_ECHO_SKB_MAX 1
+#define SX2010_OST_DELAY_MS (5)
+#define SX2010_FREQUENCY 20000000
+
+#define SX2010_CMD_WRITE 0x02
+#define SX2010_CMD_READ 0x03
+#define SX2010_CMD_BIT_MODIFY 0x05
+#define SX2010_CMD_WRITE_TXB(n) (0x40 + 2 * (n))
+#define SX2010_CMD_READ_RXB(n) (((n) == 0) ? 0x90 : 0x94)
+#define SX2010_CMD_RESET 0xC0
+#define SX2010_CMD_RTS(n) (0x80 | ((n) & 0x07))
+
+#define REG_STAT 0x0e
+#define REG_CTRL 0x0f
+#define REG_CTRL_MASK GENMASK(7, 5)
+#define REG_CTRL_CONF BIT(7)
+#define REG_CTRL_LISTEN_ONLY GENMASK(6, 5)
+#define REG_CTRL_LOOPBACK BIT(6)
+#define REG_CTRL_SLEEP BIT(5)
+#define REG_CTRL_NORMAL 0
+#define REG_CFG1 0x2a
+#define REG_CFG2 0x29
+#define REG_CFG2_BTLMODE BIT(7)
+#define REG_CFG2_SAM BIT(6)
+#define REG_CFG3 0x28
+#define REG_CFG3_PHSEG2_MASK GENMASK(2, 0)
+#define REG_INTE 0x2b
+#define REG_INTE_ALL GENMASK(5, 0)
+#define REG_INTF 0x2c
+#define REG_INTF_RX1 BIT(1)
+#define REG_INTF_RX0 BIT(0)
+#define REG_INTF_RX GENMASK(1, 0)
+#define REG_INTF_TX GENMASK(4, 2)
+#define REG_INTF_ERR BIT(5)
+#define REG_EFLG 0x2d
+#define REG_EFLG_RXWAR BIT(1)
+#define REG_EFLG_TXWAR BIT(2)
+#define REG_EFLG_RXEP BIT(3)
+#define REG_EFLG_TXEP BIT(4)
+#define REG_EFLG_TXBO BIT(5)
+#define REG_EFLG_RX0OVR BIT(6)
+#define REG_EFLG_RX1OVR BIT(7)
+#define REG_TXBCTRL(n) (((n) * 0x10) + 0x30)
+#define REG_RXBCTRL(n) (((n) * 0x10) + 0x60)
+#define REG_RXBCTRL_BUKT BIT(2)
+#define REG_RXBCTRL_RXM0 BIT(5)
+#define REG_RXBCTRL_RXM1 BIT(6)
+#define REG_RXBSIDL_IDE BIT(3)
+#define REG_RXBSIDL_SRR BIT(4)
+#define REG_RXBSIDL_EID GENMASK(1, 0)
+#define REG_RXBDLC_LEN_MASK GENMASK(3, 0)
+#define REG_RXBDLC_RTR BIT(6)
+
+#define GET_BYTE(val, byte) \
+ (((val) >> ((byte) * 8)) & 0xff)
+#define SET_BYTE(val, byte) \
+ (((val) & 0xff) << ((byte) * 8))
+
+static const struct can_bittiming_const sx2010_bittiming_const = {
+ .name = DRIVER_NAME,
+ .tseg1_min = 3,
+ .tseg1_max = 16,
+ .tseg2_min = 2,
+ .tseg2_max = 8,
+ .sjw_max = 4,
+ .brp_min = 1,
+ .brp_max = 64,
+ .brp_inc = 1,
+};
+
+struct sx2010_can_data {
+ u32 board_id;
+ u8 chl_number;
+ u8 version;
+ u32 significand;
+ u8 exponent;
+ u8 gpio_input;
+ u8 gpio_output;
+};
+
+struct sx2010_data {
+ struct can_priv can;
+ struct net_device *net;
+ struct mutex trans_lock;
+ struct mutex sx_lock;
+ u8 *tx_buf;
+ u8 *rx_buf;
+ struct sk_buff *tx_skb;
+ int tx_len;
+ struct workqueue_struct *wq;
+ struct work_struct tx_work;
+ struct work_struct restart_work;
+
+ int force_quit;
+ int after_suspend;
+#define SX2010_AFTER_SUSPEND_UP 1
+#define SX2010_AFTER_SUSPEND_DOWN 2
+#define SX2010_AFTER_SUSPEND_POWER 4
+#define SX2010_AFTER_SUSPEND_RESTART 8
+ int restart_tx;
+
+ struct sx2010_can_data data;
+ struct device *dev;
+ struct resource *res;
+ u8 chip_select;
+ unsigned long long clk_rate;
+ struct timer_list timer;
+ void __iomem *base;
+};
+
+static inline u32 sx2010_readl(struct sx2010_data *data, u32 reg)
+{
+ return readl(data->base + reg);
+}
+
+static inline void sx2010_writel(struct sx2010_data *data, u32 reg,
+ u32 val)
+{
+ writel(val, data->base + reg);
+}
+
+static inline int sx2010_wait_till_ready(struct sx2010_data *data,
+ bool wait_rx_irq)
+{
+ bool available = false;
+ bool rx_irq = false;
+ u32 status;
+ int i;
+
+ for (i = 0; i < SX2010_WAIT_MAX_LOOP; i++) {
+ status = sx2010_readl(data, SX2010_REG_STATUS);
+ if (!(status & SX2010_STATUS_BUSY))
+ available = true;
+ if (status & SX2010_STATUS_RX_IRQ)
+ rx_irq = true;
+
+ if (wait_rx_irq) {
+ if (available && rx_irq)
+ return 0;
+ } else {
+ if (available)
+ return 0;
+ }
+ cpu_relax();
+ udelay(1);
+ }
+
+ return -ETIMEDOUT;
+}
+
+static int sx2010_write_trans(struct sx2010_data *data, int cmd_len,
+ int data_len)
+{
+ int len_in_dw;
+ int total_len;
+ u32 val;
+ int ret;
+ int i;
+ int j;
+
+ if (cmd_len <= 0)
+ return -EINVAL;
+
+ total_len = cmd_len + data_len;
+ mutex_lock(&data->trans_lock);
+
+ ret = sx2010_wait_till_ready(data, false);
+ if (ret)
+ goto out_unlock;
+
+ /* Set ram */
+ if ((total_len % 4) == 0) {
+ len_in_dw = total_len / 4;
+ } else {
+ if (total_len < 4)
+ len_in_dw = 1;
+ else
+ len_in_dw = (total_len / 4) + 1;
+ }
+
+ j = 0;
+ for (i = 0; i < len_in_dw; i++) {
+ do {
+ val = data->tx_buf[i * 4];
+ if (++j == total_len)
+ break;
+ val |= data->tx_buf[i * 4 + 1] << 8;
+ if (++j == total_len)
+ break;
+ val |= data->tx_buf[i * 4 + 2] << 16;
+ if (++j == total_len)
+ break;
+ val |= data->tx_buf[i * 4 + 3] << 24;
+ if (++j == total_len)
+ break;
+ } while (false);
+
+ sx2010_writel(data, SX2010_REG_RAM + (i * 4), val);
+ }
+
+ /* Set trans reg 1 */
+ val = data_len << 16;
+ sx2010_writel(data, SX2010_REG_TRANS_CTRL_1, val);
+
+ /* Set trans reg 0 */
+ val = SX2010_TRANS_ENABLE | SX2010_TRANS_WRITE;
+ val |= cmd_len << 8;
+ val |= data->chip_select << 16;
+ sx2010_writel(data, SX2010_REG_TRANS_CTRL_0, val);
+
+ ret = sx2010_wait_till_ready(data, false);
+
+out_unlock:
+ mutex_unlock(&data->trans_lock);
+ return ret;
+}
+
+static int sx2010_read_trans(struct sx2010_data *data, int cmd_len,
+ int data_len)
+{
+ int len_in_dw;
+ u32 val;
+ int ret;
+ int i;
+ int j;
+
+ if (cmd_len <= 0)
+ return -EINVAL;
+
+ mutex_lock(&data->trans_lock);
+
+ ret = sx2010_wait_till_ready(data, false);
+ if (ret)
+ goto out_unlock;
+
+ /* Set ram */
+ if ((cmd_len % 4) == 0) {
+ len_in_dw = cmd_len / 4;
+ } else {
+ if (cmd_len < 4)
+ len_in_dw = 1;
+ else
+ len_in_dw = (cmd_len / 4) + 1;
+ }
+
+ j = 0;
+ for (i = 0; i < len_in_dw; i++) {
+ do {
+ val = data->tx_buf[i * 4];
+ if (++j == cmd_len)
+ break;
+ val |= data->tx_buf[i * 4 + 1] << 8;
+ if (++j == cmd_len)
+ break;
+ val |= data->tx_buf[i * 4 + 2] << 16;
+ if (++j == cmd_len)
+ break;
+ val |= data->tx_buf[i * 4 + 3] << 24;
+ if (++j == cmd_len)
+ break;
+ } while (false);
+
+ sx2010_writel(data, SX2010_REG_RAM + (i * 4), val);
+ }
+
+ /* Set trans reg 1 */
+ val = data_len;
+ sx2010_writel(data, SX2010_REG_TRANS_CTRL_1, val);
+
+ /* Set trans reg 0 */
+ val = SX2010_TRANS_ENABLE;
+ val |= cmd_len << 8;
+ val |= data->chip_select << 16;
+ sx2010_writel(data, SX2010_REG_TRANS_CTRL_0, val);
+
+ ret = sx2010_wait_till_ready(data, true);
+ if (ret)
+ goto out_unlock;
+
+ /* Get ram */
+ if ((data_len % 4) == 0) {
+ len_in_dw = data_len / 4;
+ } else {
+ if (data_len < 4)
+ len_in_dw = 1;
+ else
+ len_in_dw = (data_len / 4) + 1;
+ }
+
+ j = 0;
+ for (i = 0; i < len_in_dw; i++) {
+ val = sx2010_readl(data, SX2010_REG_RAM + (i * 4));
+ data->rx_buf[j++] = val & GENMASK(7, 0);
+ if (j == data_len)
+ break;
+ data->rx_buf[j++] = (val & GENMASK(15, 8)) >> 8;
+ if (j == data_len)
+ break;
+ data->rx_buf[j++] = (val & GENMASK(23, 16)) >> 16;
+ if (j == data_len)
+ break;
+ data->rx_buf[j++] = (val & GENMASK(31, 24)) >> 24;
+ if (j == data_len)
+ break;
+ }
+
+out_unlock:
+ mutex_unlock(&data->trans_lock);
+ return ret;
+}
+
+static void sx2010_clean(struct net_device *net)
+{
+ struct sx2010_data *data = netdev_priv(net);
+
+ if (data->tx_skb || data->tx_len)
+ net->stats.tx_errors++;
+ dev_kfree_skb(data->tx_skb);
+ if (data->tx_len)
+ can_free_echo_skb(data->net, 0);
+ data->tx_skb = NULL;
+ data->tx_len = 0;
+}
+
+static u8 sx2010_read_reg(struct sx2010_data *data, u8 reg)
+{
+ int ret;
+ u8 val = 0;
+
+ data->tx_buf[0] = SX2010_CMD_READ;
+ data->tx_buf[1] = reg;
+
+ ret = sx2010_read_trans(data, 2, 1);
+ if (ret == 0)
+ val = data->rx_buf[0];
+
+ return val;
+}
+
+static void sx2010_read_2regs(struct sx2010_data *data, u8 reg, u8 *v1, u8 *v2)
+{
+ int ret;
+
+ data->tx_buf[0] = SX2010_CMD_READ;
+ data->tx_buf[1] = reg;
+
+ ret = sx2010_read_trans(data, 2, 2);
+ if (ret == 0) {
+ *v1 = data->rx_buf[0];
+ *v2 = data->rx_buf[1];
+ }
+}
+
+static void sx2010_write_reg(struct sx2010_data *data, u8 reg, u8 val)
+{
+ data->tx_buf[0] = SX2010_CMD_WRITE;
+ data->tx_buf[1] = reg;
+ data->tx_buf[2] = val;
+
+ sx2010_write_trans(data, 2, 1);
+}
+
+static void sx2010_write_2regs(struct sx2010_data *data, u8 reg, u8 v1, u8 v2)
+{
+ data->tx_buf[0] = SX2010_CMD_WRITE;
+ data->tx_buf[1] = reg;
+ data->tx_buf[2] = v1;
+ data->tx_buf[3] = v2;
+
+ sx2010_write_trans(data, 2, 2);
+}
+
+static void sx2010_write_bits(struct sx2010_data *data, u8 reg, u8 mask, u8 val)
+{
+ data->tx_buf[0] = SX2010_CMD_BIT_MODIFY;
+ data->tx_buf[1] = reg;
+ data->tx_buf[2] = mask;
+ data->tx_buf[3] = val;
+
+ sx2010_write_trans(data, 2, 2);
+}
+
+static void sx2010_hw_tx_frame(struct sx2010_data *data, u8 *buf,
+ int len, int tx_buf_idx)
+{
+ memcpy(data->tx_buf, buf, 6 + len);
+ sx2010_write_trans(data, 1, 6 + len - 1);
+}
+
+static void sx2010_hw_tx(struct sx2010_data *data, struct can_frame *frame,
+ int tx_buf_idx)
+{
+ u32 sid, eid, exide, rtr;
+ u8 buf[SX2010_CAN_TRANS_BUF_LEN];
+
+ exide = (frame->can_id & CAN_EFF_FLAG) ? 1 : 0;
+ if (exide)
+ sid = (frame->can_id & CAN_EFF_MASK) >> 18;
+ else
+ sid = frame->can_id & CAN_SFF_MASK;
+ eid = frame->can_id & CAN_EFF_MASK;
+ rtr = (frame->can_id & CAN_RTR_FLAG) ? 1 : 0;
+
+ buf[0] = SX2010_CMD_WRITE_TXB(tx_buf_idx);
+ buf[1] = sid >> 3;
+ buf[2] = ((sid & 7) << 5) | (exide << 3) | ((eid >> 16) & 3);
+ buf[3] = GET_BYTE(eid, 1);
+ buf[4] = GET_BYTE(eid, 0);
+ buf[5] = (rtr << 6) | frame->len;
+
+ memcpy(buf + 6, frame->data, frame->len);
+ sx2010_hw_tx_frame(data, buf, frame->len, tx_buf_idx);
+
+ data->tx_buf[0] = SX2010_CMD_RTS(1 << tx_buf_idx);
+ sx2010_write_trans(data, 1, 0);
+}
+
+static void sx2010_hw_rx_frame(struct sx2010_data *data, u8 *buf,
+ int buf_idx)
+{
+ memset(data->rx_buf, 0, sizeof(u8) * SX2010_BUF_LEN);
+ data->tx_buf[0] = SX2010_CMD_READ_RXB(buf_idx);
+
+ sx2010_read_trans(data, 1, SX2010_CAN_TRANS_BUF_LEN - 1);
+ memcpy(&buf[1], data->rx_buf, SX2010_CAN_TRANS_BUF_LEN - 1);
+}
+
+static void sx2010_hw_rx(struct sx2010_data *data, int buf_idx)
+{
+ struct can_frame *frame;
+ struct sk_buff *skb;
+ u8 buf[SX2010_CAN_TRANS_BUF_LEN];
+
+ skb = alloc_can_skb(data->net, &frame);
+ if (!skb) {
+ dev_err(data->dev, "cannot allocate RX skb\n");
+ data->net->stats.rx_dropped++;
+ return;
+ }
+
+ sx2010_hw_rx_frame(data, buf, buf_idx);
+ if (buf[2] & REG_RXBSIDL_IDE) {
+ frame->can_id = CAN_EFF_FLAG;
+ frame->can_id |=
+ SET_BYTE(buf[2] & REG_RXBSIDL_EID, 2) |
+ SET_BYTE(buf[3], 1) |
+ SET_BYTE(buf[4], 0) |
+ (((buf[1] << 3) | (buf[1] >> 5)) << 18);
+
+ if (buf[5] & REG_RXBDLC_RTR)
+ frame->can_id |= CAN_RTR_FLAG;
+ } else {
+ frame->can_id = (buf[1] << 3) | (buf[2] >> 5);
+
+ if (buf[2] & REG_RXBSIDL_SRR)
+ frame->can_id |= CAN_RTR_FLAG;
+ }
+
+ frame->len = can_cc_dlc2len(buf[5] & REG_RXBDLC_LEN_MASK);
+ memcpy(frame->data, buf + 6, frame->len);
+
+ data->net->stats.rx_packets++;
+ data->net->stats.rx_bytes += frame->len;
+
+ can_led_event(data->net, CAN_LED_EVENT_RX);
+ netif_rx_ni(skb);
+}
+
+static void sx2010_hw_sleep(struct sx2010_data *data)
+{
+ sx2010_write_reg(data, REG_CTRL, REG_CTRL_SLEEP);
+}
+
+static int sx2010_hw_startup(struct sx2010_data *data)
+{
+ int divisor = 1;
+ u32 val;
+
+ divisor = data->clk_rate / SX2010_FREQUENCY;
+ val = 0x01;
+ val |= ((divisor % 256) << 16);
+ val |= ((divisor / 256) << 24);
+ sx2010_writel(data, SX2010_REG_CTRL, val);
+ sx2010_writel(data, SX2010_REG_IRQ_ENABLE, 0);
+ sx2010_writel(data, SX2010_REG_GPIO_OUT_ENABLE, data->chip_select);
+ sx2010_writel(data, SX2010_REG_GPIO_OUT, data->chip_select);
+ return 0;
+}
+
+static int sx2010_hw_shutdown(struct sx2010_data *data)
+{
+ sx2010_writel(data, SX2010_REG_CTRL, 0);
+ sx2010_writel(data, SX2010_REG_IRQ_ENABLE, 0);
+ sx2010_writel(data, SX2010_REG_GPIO_OUT_ENABLE, data->chip_select);
+ sx2010_writel(data, SX2010_REG_GPIO_OUT, 0);
+ return 0;
+}
+
+static int sx2010_hw_reset(struct sx2010_data *data)
+{
+ unsigned long timeout;
+ int ret;
+
+ mdelay(SX2010_OST_DELAY_MS);
+
+ data->tx_buf[0] = SX2010_CMD_RESET;
+ ret = sx2010_write_trans(data, 1, 0);
+ if (ret)
+ return ret;
+
+ mdelay(SX2010_OST_DELAY_MS);
+
+ timeout = jiffies + HZ;
+ while ((sx2010_read_reg(data, REG_STAT) & REG_CTRL_MASK) !=
+ REG_CTRL_CONF) {
+ usleep_range(SX2010_OST_DELAY_MS * 1000,
+ SX2010_OST_DELAY_MS * 1000 * 2);
+
+ if (time_after(jiffies, timeout)) {
+ dev_err(data->dev, "SX2010 didn't enter in config mode\n");
+ return -EBUSY;
+ }
+ }
+ return 0;
+}
+
+static int sx2010_hw_probe(struct sx2010_data *data)
+{
+ int ret;
+ u8 val;
+
+ ret = sx2010_hw_startup(data);
+ if (ret)
+ return ret;
+
+ ret = sx2010_hw_reset(data);
+ if (ret)
+ return ret;
+
+ val = sx2010_read_reg(data, REG_CTRL);
+ if ((val & 0x17) != 0x07)
+ return -ENODEV;
+ return 0;
+}
+
+static netdev_tx_t sx2010_start_xmit(struct sk_buff *skb,
+ struct net_device *net)
+{
+ struct sx2010_data *data = netdev_priv(net);
+
+ if (data->tx_skb || data->tx_len) {
+ dev_warn(data->dev, "xmit called while tx busy\n");
+ return NETDEV_TX_BUSY;
+ }
+
+ if (can_dropped_invalid_skb(net, skb))
+ return NETDEV_TX_OK;
+
+ netif_stop_queue(net);
+ data->tx_skb = skb;
+ queue_work(data->wq, &data->tx_work);
+
+ return NETDEV_TX_OK;
+}
+
+static int sx2010_do_set_mode(struct net_device *net, enum can_mode mode)
+{
+ struct sx2010_data *data = netdev_priv(net);
+
+ switch (mode) {
+ case CAN_MODE_START:
+ sx2010_clean(net);
+ data->can.state = CAN_STATE_ERROR_ACTIVE;
+ data->restart_tx = 1;
+ if (data->can.restart_ms == 0)
+ data->after_suspend = SX2010_AFTER_SUSPEND_RESTART;
+ queue_work(data->wq, &data->restart_work);
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static int sx2010_set_normal_mode(struct sx2010_data *data)
+{
+ unsigned long timeout;
+
+ sx2010_write_reg(data, REG_INTE, REG_INTE_ALL);
+
+ if (data->can.ctrlmode & CAN_CTRLMODE_LOOPBACK) {
+ sx2010_write_reg(data, REG_CTRL, REG_CTRL_LOOPBACK);
+ } else if (data->can.ctrlmode & CAN_CTRLMODE_LISTENONLY) {
+ sx2010_write_reg(data, REG_CTRL, REG_CTRL_LISTEN_ONLY);
+ } else {
+ sx2010_write_reg(data, REG_CTRL, REG_CTRL_NORMAL);
+
+ timeout = jiffies + HZ;
+ while (sx2010_read_reg(data, REG_STAT) & REG_CTRL_MASK) {
+ schedule();
+ if (time_after(jiffies, timeout)) {
+ dev_err(data->dev, "SX2010 didn't enter in normal mode\n");
+ return -EBUSY;
+ }
+ }
+ }
+ data->can.state = CAN_STATE_ERROR_ACTIVE;
+ return 0;
+}
+
+static int sx2010_do_set_bittiming(struct net_device *net)
+{
+ struct sx2010_data *data = netdev_priv(net);
+ struct can_bittiming *bt = &data->can.bittiming;
+
+ sx2010_write_reg(data, REG_CFG1, ((bt->sjw - 1) << 6) |
+ (bt->brp - 1));
+ sx2010_write_reg(data, REG_CFG2, REG_CFG2_BTLMODE |
+ (data->can.ctrlmode & CAN_CTRLMODE_3_SAMPLES ?
+ REG_CFG2_SAM : 0) |
+ ((bt->phase_seg1 - 1) << 3) |
+ (bt->prop_seg - 1));
+ sx2010_write_bits(data, REG_CFG3, REG_CFG3_PHSEG2_MASK,
+ (bt->phase_seg2 - 1));
+ dev_dbg(data->dev, "CFG: 0x%02x 0x%02x 0x%02x\n",
+ sx2010_read_reg(data, REG_CFG1),
+ sx2010_read_reg(data, REG_CFG2),
+ sx2010_read_reg(data, REG_CFG3));
+
+ return 0;
+}
+
+static int sx2010_setup(struct net_device *net, struct sx2010_data *data)
+{
+ sx2010_do_set_bittiming(net);
+
+ sx2010_write_reg(data, REG_RXBCTRL(0),
+ REG_RXBCTRL_BUKT | REG_RXBCTRL_RXM0 | REG_RXBCTRL_RXM1);
+ sx2010_write_reg(data, REG_RXBCTRL(1),
+ REG_RXBCTRL_RXM0 | REG_RXBCTRL_RXM1);
+ return 0;
+}
+
+static int sx2010_stop(struct net_device *net)
+{
+ struct sx2010_data *data = netdev_priv(net);
+
+ close_candev(net);
+
+ data->force_quit = 1;
+ del_timer(&data->timer);
+
+ mutex_lock(&data->sx_lock);
+
+ sx2010_write_2regs(data, REG_INTE, 0x00, 0x00);
+ sx2010_write_reg(data, REG_TXBCTRL(0), 0);
+ sx2010_clean(net);
+
+ sx2010_hw_sleep(data);
+
+ data->can.state = CAN_STATE_STOPPED;
+
+ mutex_unlock(&data->sx_lock);
+
+ can_led_event(net, CAN_LED_EVENT_STOP);
+ return 0;
+}
+
+static void sx2010_error_skb(struct net_device *net, int can_id, int data1)
+{
+ struct can_frame *frame;
+ struct sk_buff *skb;
+
+ skb = alloc_can_err_skb(net, &frame);
+ if (skb) {
+ frame->can_id |= can_id;
+ frame->data[1] = data1;
+ netif_rx_ni(skb);
+ } else {
+ netdev_err(net, "cannot allocate error skb\n");
+ }
+}
+
+static void sx2010_tx_work_handler(struct work_struct *ws)
+{
+ struct sx2010_data *data = container_of(ws, struct sx2010_data,
+ tx_work);
+ struct net_device *net = data->net;
+ struct can_frame *frame;
+
+ mutex_lock(&data->sx_lock);
+ if (data->tx_skb) {
+ if (data->can.state == CAN_STATE_BUS_OFF) {
+ sx2010_clean(net);
+ } else {
+ frame = (struct can_frame *)data->tx_skb->data;
+
+ if (frame->can_dlc > SX2010_CAN_MAX_DATA_LEN)
+ frame->can_dlc = SX2010_CAN_MAX_DATA_LEN;
+
+ sx2010_hw_tx(data, frame, 0);
+ data->tx_len = 1 + frame->len;
+ can_put_echo_skb(data->tx_skb, net, 0, 0);
+ data->tx_skb = NULL;
+ }
+ }
+ mutex_unlock(&data->sx_lock);
+}
+
+static void sx2010_restart_work_handler(struct work_struct *ws)
+{
+ struct sx2010_data *data = container_of(ws, struct sx2010_data,
+ restart_work);
+ struct net_device *net = data->net;
+
+ mutex_lock(&data->sx_lock);
+ if (data->after_suspend) {
+ sx2010_hw_reset(data);
+ sx2010_setup(net, data);
+ data->force_quit = 0;
+ if (data->after_suspend & SX2010_AFTER_SUSPEND_RESTART) {
+ sx2010_set_normal_mode(data);
+ } else if (data->after_suspend & SX2010_AFTER_SUSPEND_UP) {
+ netif_device_attach(net);
+ sx2010_clean(net);
+ sx2010_set_normal_mode(data);
+ netif_wake_queue(net);
+ } else {
+ sx2010_hw_sleep(data);
+ }
+ data->after_suspend = 0;
+ }
+
+ if (data->restart_tx) {
+ data->restart_tx = 0;
+ sx2010_write_reg(data, REG_TXBCTRL(0), 0);
+ sx2010_clean(net);
+ netif_wake_queue(net);
+ sx2010_error_skb(net, CAN_ERR_RESTARTED, 0);
+ }
+ mutex_unlock(&data->sx_lock);
+}
+
+static void sx2010_can_isr(struct timer_list *t)
+{
+ struct sx2010_data *data = from_timer(data, t, timer);
+ struct net_device *net = data->net;
+ int cnt = SX2010_ISR_MAX_CNT;
+
+ if (!mutex_trylock(&data->sx_lock))
+ goto out_without_unlock;
+
+ while (!data->force_quit) {
+ enum can_state new_state;
+ u8 intf, eflag;
+ u8 clear_intf = 0;
+ int can_id = 0, data1 = 0;
+
+ sx2010_read_2regs(data, REG_INTF, &intf, &eflag);
+
+ intf &= REG_INTF_RX | REG_INTF_TX | REG_INTF_ERR;
+
+ if (intf & REG_INTF_RX0)
+ sx2010_hw_rx(data, 0);
+ if (intf & REG_INTF_RX1)
+ sx2010_hw_rx(data, 1);
+
+ if (intf & (REG_INTF_ERR | REG_INTF_TX))
+ clear_intf |= intf & (REG_INTF_ERR | REG_INTF_TX);
+ if (clear_intf)
+ sx2010_write_bits(data, REG_INTF, clear_intf, 0x00);
+ if (eflag & (REG_EFLG_RX0OVR | REG_EFLG_RX1OVR))
+ sx2010_write_bits(data, REG_EFLG, eflag, 0x00);
+
+ if (eflag & REG_EFLG_TXBO) {
+ new_state = CAN_STATE_BUS_OFF;
+ can_id |= CAN_ERR_BUSOFF;
+ } else if (eflag & REG_EFLG_TXEP) {
+ new_state = CAN_STATE_ERROR_PASSIVE;
+ can_id |= CAN_ERR_CRTL;
+ data1 |= CAN_ERR_CRTL_TX_PASSIVE;
+ } else if (eflag & REG_EFLG_RXEP) {
+ new_state = CAN_STATE_ERROR_PASSIVE;
+ can_id |= CAN_ERR_CRTL;
+ data1 |= CAN_ERR_CRTL_RX_PASSIVE;
+ } else if (eflag & REG_EFLG_TXWAR) {
+ new_state = CAN_STATE_ERROR_WARNING;
+ can_id |= CAN_ERR_CRTL;
+ data1 |= CAN_ERR_CRTL_TX_WARNING;
+ } else if (eflag & REG_EFLG_RXWAR) {
+ new_state = CAN_STATE_ERROR_WARNING;
+ can_id |= CAN_ERR_CRTL;
+ data1 |= CAN_ERR_CRTL_RX_WARNING;
+ } else {
+ new_state = CAN_STATE_ERROR_ACTIVE;
+ }
+
+ switch (data->can.state) {
+ case CAN_STATE_ERROR_ACTIVE:
+ if (new_state >= CAN_STATE_ERROR_WARNING &&
+ new_state <= CAN_STATE_BUS_OFF)
+ data->can.can_stats.error_warning++;
+ fallthrough;
+ case CAN_STATE_ERROR_WARNING:
+ if (new_state >= CAN_STATE_ERROR_PASSIVE &&
+ new_state <= CAN_STATE_BUS_OFF)
+ data->can.can_stats.error_passive++;
+ break;
+ default:
+ break;
+ }
+ data->can.state = new_state;
+
+ if (intf & REG_INTF_ERR) {
+ if (eflag & (REG_EFLG_RX0OVR | REG_EFLG_RX1OVR)) {
+ if (eflag & REG_EFLG_RX0OVR) {
+ net->stats.rx_over_errors++;
+ net->stats.rx_errors++;
+ }
+ if (eflag & REG_EFLG_RX1OVR) {
+ net->stats.rx_over_errors++;
+ net->stats.rx_errors++;
+ }
+ can_id |= CAN_ERR_CRTL;
+ data1 |= CAN_ERR_CRTL_RX_OVERFLOW;
+ }
+ sx2010_error_skb(net, can_id, data1);
+ }
+
+ if (data->can.state == CAN_STATE_BUS_OFF) {
+ if (data->can.restart_ms == 0) {
+ data->force_quit = 1;
+ data->can.can_stats.bus_off++;
+ can_bus_off(net);
+ sx2010_hw_sleep(data);
+ break;
+ }
+ }
+
+ if (intf == 0)
+ break;
+
+ if (intf & REG_INTF_TX) {
+ net->stats.tx_packets++;
+ net->stats.tx_bytes += data->tx_len - 1;
+ can_led_event(net, CAN_LED_EVENT_TX);
+ if (data->tx_len) {
+ can_get_echo_skb(net, 0, NULL);
+ data->tx_len = 0;
+ }
+ netif_wake_queue(net);
+ }
+
+ if (--cnt <= 0)
+ break;
+ }
+ mutex_unlock(&data->sx_lock);
+
+out_without_unlock:
+ mod_timer(&data->timer, jiffies + SX2010_ISR_TIME);
+}
+
+static int sx2010_open(struct net_device *net)
+{
+ struct sx2010_data *data = netdev_priv(net);
+ int ret;
+
+ ret = open_candev(net);
+ if (ret) {
+ dev_err(data->dev, "unable to set initial baudrate!\n");
+ return ret;
+ }
+
+ mutex_lock(&data->sx_lock);
+
+ data->force_quit = 0;
+ data->tx_skb = NULL;
+ data->tx_len = 0;
+
+ ret = sx2010_hw_reset(data);
+ if (ret)
+ goto out_clean;
+
+ ret = sx2010_setup(net, data);
+ if (ret)
+ goto out_clean;
+
+ ret = sx2010_set_normal_mode(data);
+ if (ret)
+ goto out_clean;
+
+ can_led_event(net, CAN_LED_EVENT_OPEN);
+
+ netif_wake_queue(net);
+ mutex_unlock(&data->sx_lock);
+
+ add_timer(&data->timer);
+ mod_timer(&data->timer, jiffies + SX2010_ISR_TIME);
+ return 0;
+
+out_clean:
+ sx2010_hw_sleep(data);
+ close_candev(net);
+ mutex_unlock(&data->sx_lock);
+ return ret;
+}
+
+static const struct net_device_ops sx2010_netdev_ops = {
+ .ndo_open = sx2010_open,
+ .ndo_stop = sx2010_stop,
+ .ndo_start_xmit = sx2010_start_xmit,
+ .ndo_change_mtu = can_change_mtu,
+};
+
+static int sx2010_read_property(struct sx2010_can_data *data,
+ struct device *dev)
+{
+ int ret;
+
+ ret = device_property_read_u32(dev, "board_id", &data->board_id);
+ if (ret < 0)
+ return -EINVAL;
+ ret = device_property_read_u8(dev, "chl_number", &data->chl_number);
+ if (ret < 0)
+ return -EINVAL;
+ ret = device_property_read_u8(dev, "version", &data->version);
+ if (ret < 0)
+ return -EINVAL;
+ ret = device_property_read_u32(dev, "significand", &data->significand);
+ if (ret < 0)
+ return -EINVAL;
+ ret = device_property_read_u8(dev, "exponent", &data->exponent);
+ if (ret < 0)
+ return -EINVAL;
+ ret = device_property_read_u8(dev, "gpio_input", &data->gpio_input);
+ if (ret < 0)
+ return -EINVAL;
+ ret = device_property_read_u8(dev, "gpio_output", &data->gpio_output);
+ if (ret < 0)
+ return -EINVAL;
+
+ return 0;
+}
+
+#ifdef CONFIG_DEBUG_FS
+
+static struct dentry *sx2010_debugfs;
+#define SX2010_DEBUGFS_FORMAT "b%d:can%d"
+
+static void sx2010_debugfs_add(struct sx2010_data *data)
+{
+ struct dentry *dir;
+ char name[32];
+
+ snprintf(name, sizeof(name), SX2010_DEBUGFS_FORMAT,
+ data->data.board_id, data->data.chl_number);
+ dir = debugfs_create_dir(name, sx2010_debugfs);
+ debugfs_create_x8("version", 0444, dir, &data->data.version);
+}
+
+static void sx2010_debugfs_remove(struct sx2010_data *data)
+{
+ struct dentry *dir;
+ char name[32];
+
+ snprintf(name, sizeof(name), SX2010_DEBUGFS_FORMAT,
+ data->data.board_id, data->data.chl_number);
+ dir = debugfs_lookup(name, sx2010_debugfs);
+ debugfs_remove_recursive(dir);
+}
+
+static void sx2010_debugfs_init(void)
+{
+ sx2010_debugfs = debugfs_create_dir(DRIVER_NAME, NULL);
+}
+
+static void sx2010_debugfs_exit(void)
+{
+ debugfs_remove(sx2010_debugfs);
+}
+#else
+static void sx2010_debugfs_add(struct sx2010_data *data) {}
+static void sx2010_debugfs_remove(struct sx2010_data *data) {}
+static void sx2010_debugfs_init(void) {}
+static void sx2010_debugfs_exit(void) {}
+#endif
+
+static int sx2010_probe(struct platform_device *pdev)
+{
+ unsigned long long exponent;
+ struct sx2010_data *data;
+ struct net_device *net;
+ struct resource *res;
+ struct device *dev;
+ int ret;
+ int i;
+
+ dev = &pdev->dev;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(dev, "no resource defined\n");
+ return -EINVAL;
+ }
+
+ net = alloc_candev(sizeof(*data), SX2010_TX_ECHO_SKB_MAX);
+ if (!net)
+ return -ENOMEM;
+
+ data = netdev_priv(net);
+ if (!data)
+ goto err_out_free;
+
+ ret = sx2010_read_property(&data->data, dev);
+ if (ret < 0) {
+ dev_err(dev, "failed to read property\n");
+ goto err_out_free;
+ }
+ data->dev = dev;
+ data->chip_select = data->data.gpio_output;
+ data->res = res;
+
+ exponent = 1;
+ for (i = 0; i < data->data.exponent; i++)
+ exponent *= 10;
+ data->clk_rate = data->data.significand * exponent;
+
+ data->base = devm_ioremap(dev, res->start, resource_size(res));
+ if (!data->base) {
+ ret = -ENOMEM;
+ goto err_out_free;
+ }
+
+ net->netdev_ops = &sx2010_netdev_ops;
+ net->flags |= IFF_ECHO;
+
+ data->can.bittiming_const = &sx2010_bittiming_const;
+ data->can.do_set_mode = sx2010_do_set_mode;
+ data->can.clock.freq = SX2010_FREQUENCY / 2;
+ data->can.ctrlmode_supported = CAN_CTRLMODE_3_SAMPLES |
+ CAN_CTRLMODE_LOOPBACK | CAN_CTRLMODE_LISTENONLY;
+
+ data->net = net;
+ platform_set_drvdata(pdev, data);
+
+ data->wq = alloc_workqueue("sx2010_wq", WQ_FREEZABLE | WQ_MEM_RECLAIM,
+ 0);
+ if (!data->wq) {
+ ret = -ENOMEM;
+ goto err_out_free;
+ }
+ INIT_WORK(&data->tx_work, sx2010_tx_work_handler);
+ INIT_WORK(&data->restart_work, sx2010_restart_work_handler);
+
+ mutex_init(&data->trans_lock);
+ mutex_init(&data->sx_lock);
+
+ data->tx_buf = devm_kzalloc(data->dev, SX2010_BUF_LEN, GFP_KERNEL);
+ if (!data->tx_buf) {
+ ret = -ENOMEM;
+ goto err_out_probe;
+ }
+
+ data->rx_buf = devm_kzalloc(data->dev, SX2010_BUF_LEN, GFP_KERNEL);
+ if (!data->rx_buf) {
+ ret = -ENOMEM;
+ goto err_out_probe;
+ }
+
+ SET_NETDEV_DEV(net, dev);
+
+ ret = sx2010_hw_probe(data);
+ if (ret) {
+ if (ret == -ENODEV)
+ dev_err(data->dev, "cannot initialize SX2010.\n");
+ goto err_out_probe;
+ }
+
+ sx2010_hw_sleep(data);
+
+ ret = register_candev(net);
+ if (ret)
+ goto err_out_probe;
+
+ devm_can_led_init(net);
+
+ sx2010_debugfs_add(data);
+ timer_setup(&data->timer, sx2010_can_isr, 0);
+ netdev_info(net, "SX2010 successfully initialized.\n");
+ return 0;
+
+err_out_probe:
+ destroy_workqueue(data->wq);
+ data->wq = NULL;
+
+err_out_free:
+ free_candev(net);
+
+ dev_err(dev, "failed to probe, err %d\n", ret);
+ return ret;
+}
+
+static int sx2010_remove(struct platform_device *pdev)
+{
+ struct sx2010_data *data = platform_get_drvdata(pdev);
+ struct net_device *net = data->net;
+
+ sx2010_debugfs_remove(data);
+ del_timer(&data->timer);
+ unregister_candev(net);
+
+ destroy_workqueue(data->wq);
+ data->wq = NULL;
+
+ sx2010_hw_shutdown(data);
+ free_candev(net);
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int sx2010_suspend(struct device *dev)
+{
+ struct sx2010_data *data = dev_get_drvdata(dev);
+ struct net_device *net = data->net;
+
+ data->force_quit = 1;
+ del_timer(&data->timer);
+
+ if (netif_running(net)) {
+ netif_device_detach(net);
+
+ sx2010_hw_sleep(data);
+ data->after_suspend = SX2010_AFTER_SUSPEND_UP;
+ } else {
+ data->after_suspend = SX2010_AFTER_SUSPEND_DOWN;
+ }
+
+ sx2010_hw_shutdown(data);
+ return 0;
+}
+
+static int sx2010_resume(struct device *dev)
+{
+ struct sx2010_data *data = dev_get_drvdata(dev);
+
+ sx2010_hw_startup(data);
+
+ if (data->after_suspend & SX2010_AFTER_SUSPEND_UP) {
+ add_timer(&data->timer);
+ mod_timer(&data->timer, jiffies + SX2010_ISR_TIME);
+ queue_work(data->wq, &data->restart_work);
+ } else {
+ data->after_suspend = 0;
+ }
+
+ data->force_quit = 0;
+ return 0;
+}
+#endif
+
+static const struct dev_pm_ops sx2010_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(sx2010_suspend, sx2010_resume)
+};
+
+static struct platform_driver sx2010_platform_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .pm = &sx2010_pm_ops,
+ .suppress_bind_attrs = true,
+ },
+ .probe = sx2010_probe,
+ .remove = sx2010_remove,
+};
+
+static int __init sx2010_init(void)
+{
+ sx2010_debugfs_init();
+ platform_driver_register(&sx2010_platform_driver);
+ return 0;
+}
+module_init(sx2010_init);
+
+static void __exit sx2010_exit(void)
+{
+ platform_driver_unregister(&sx2010_platform_driver);
+ sx2010_debugfs_exit();
+}
+module_exit(sx2010_exit);
+
+MODULE_AUTHOR("Jason Lee <jason_lee@sunix.com>");
+MODULE_DESCRIPTION("SUNIX SDC CAN controller driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" DRIVER_NAME);
--
2.20.1
next reply other threads:[~2021-06-25 9:45 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2021-06-25 9:45 Moriis Ku [this message]
2021-06-25 10:09 ` [PATCH] SUNIX SDC CAN controller driver Marc Kleine-Budde
2021-06-25 10:14 ` Marc Kleine-Budde
-- strict thread matches above, loose matches on Subject: below --
2021-06-25 9:45 Moriis Ku
2021-06-25 9:44 Moriis Ku
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20210625094520.13283-1-saumah@gmail.com \
--to=saumah@gmail.com \
--cc=edward.lee@sunix.com \
--cc=jason_lee@sunix.com \
--cc=linux-can@vger.kernel.org \
--cc=mkl@pengutronix.de \
--cc=morris_ku@sunix.com \
--cc=taian.chen@sunix.com \
--cc=wg@grandegger.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.