[PATCH] SUNIX SDC CAN controller driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



From: Morris Ku <saumah@xxxxxxxxx>

Add support for SUNIX SDC CAN controller

Cc: Jason Lee <jason_lee@xxxxxxxxx>
Cc: Taian Chen <taian.chen@xxxxxxxxx>
Cc: Morris Ku <morris_ku@xxxxxxxxx>
Cc: Edward Lee <Edward.lee@xxxxxxxxx>
Signed-off-by: Morris Ku <saumah@xxxxxxxxx>
---
 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@xxxxxxxxx>");
+MODULE_DESCRIPTION("SUNIX SDC CAN controller driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" DRIVER_NAME);
-- 
2.20.1




[Index of Archives]     [Automotive Discussions]     [Linux ARM Kernel]     [Linux ARM]     [Linux Omap]     [Fedora ARM]     [IETF Annouce]     [Security]     [Bugtraq]     [Linux]     [Linux OMAP]     [Linux MIPS]     [eCos]     [Asterisk Internet PBX]     [Linux API]     [CAN Bus]

  Powered by Linux