All of lore.kernel.org
 help / color / mirror / Atom feed
From: Gerhard Bertelsmann <info@gerhard-bertelsmann.de>
To: linux-can@vger.kernel.org
Cc: Marc Kleine-Budde <mkl@pengutronix.de>
Subject: [PATCH] CAN controller support for Allwinner A10/A20 SoCs
Date: Wed, 02 Sep 2015 20:44:17 +0200	[thread overview]
Message-ID: <55E74381.6050506@gerhard-bertelsmann.de> (raw)

This driver add support for SUNXI CAN controllers found on Allwinner A10/A20 SoCs
V1

Signed-of-by: Gerhard Bertelsmann <info <at> gerhard-bertelsmann>
Tested-by: Gerhard Bertelsmann <info <at> gerhard-bertelsmann>


 drivers/net/can/Kconfig     |  10 +
 drivers/net/can/Makefile    |   1 +
 drivers/net/can/sunxi_can.c | 883 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 894 insertions(+)

diff --git a/drivers/net/can/Kconfig b/drivers/net/can/Kconfig
index e8c96b8..29b62e6 100644
--- a/drivers/net/can/Kconfig
+++ b/drivers/net/can/Kconfig
@@ -129,6 +129,16 @@ config CAN_RCAR
 	  To compile this driver as a module, choose M here: the module will
 	  be called rcar_can.

+config CAN_SUNXI
+	tristate "Allwinner SUNXI CAN controller"
+	depends on MACH_SUN4I || MACH_SUN7I
+	---help---
+	  Say Y here if you want to use CAN controller found on Allwinner
+	  A10/A20 SoCs.
+
+	  To compile this driver as a module, choose M here: the module will
+	  be called sunxi_can.
+
 config CAN_XILINXCAN
 	tristate "Xilinx CAN"
 	depends on ARCH_ZYNQ || ARM64 || MICROBLAZE || COMPILE_TEST
diff --git a/drivers/net/can/Makefile b/drivers/net/can/Makefile
index c533c62..b3e825c 100644
--- a/drivers/net/can/Makefile
+++ b/drivers/net/can/Makefile
@@ -27,6 +27,7 @@ obj-$(CONFIG_CAN_FLEXCAN)	+= flexcan.o
 obj-$(CONFIG_PCH_CAN)		+= pch_can.o
 obj-$(CONFIG_CAN_GRCAN)		+= grcan.o
 obj-$(CONFIG_CAN_RCAR)		+= rcar_can.o
+obj-$(CONFIG_CAN_SUNXI)		+= sunxi_can.o
 obj-$(CONFIG_CAN_XILINXCAN)	+= xilinx_can.o

 subdir-ccflags-y += -D__CHECK_ENDIAN__
diff --git a/drivers/net/can/sunxi_can.c b/drivers/net/can/sunxi_can.c
new file mode 100644
index 0000000..1585be9
--- /dev/null
+++ b/drivers/net/can/sunxi_can.c
@@ -0,0 +1,883 @@
+/*
+ * sunxi_can.c - CAN bus controller driver for Allwinner SUN4I&SUN7I based SoCs
+ *
+ * Copyright (C) 2013 Peter Chen
+ * Copyright (C) 2015 Gerhard Bertelsmann
+ *
+ * Parts of this software are based on (derived from) the SJA1000 code by:
+ *   Copyright (C) 2014 Oliver Hartkopp <oliver.hartkopp@volkswagen.de>
+ *   Copyright (C) 2007 Wolfgang Grandegger <wg@grandegger.com>
+ *   Copyright (C) 2002-2007 Volkswagen Group Electronic Research
+ *   Copyright (C) 2003 Matthias Brukner, Trajet Gmbh, Rebenring 33,
+ *   38106 Braunschweig, GERMANY
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the version 2 of the GNU General Public License
+ * as published by the Free Software Foundation
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * To use the driver CAN must be defined like:
+ *  soc@01c00000 {
+ *    ...
+ *    pio: pinctrl@01c20800 {
+ *      ...
+ *      can0_pins_a: can0@0 {
+ *        allwinner,pins = "PH20","PH21";
+ *        allwinner,function = "can";
+ *        allwinner,drive = <0>;
+ *        allwinner,pull = <0>;
+ *      };
+ *    };
+ *    ...
+ *    can0: can@01c2bc00 {
+ *      compatible = "allwinner,sunxican";
+ *      reg = <0x01c2bc00 0x400>;
+ *      reg-io-width = <4>;
+ *      interrupts = <0 26 4>;
+ *      clocks = <&apb1_gates 4>;
+ *      #address-cells = <1>;
+ *      #size-cells = <0>;
+ *    };
+ *    ...
+ *  };
+ *
+ */
+
+#include <linux/netdevice.h>
+#include <linux/can.h>
+#include <linux/can/dev.h>
+#include <linux/can/error.h>
+#include <linux/can/led.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+
+#define DRV_NAME "sunxi_can"
+#define DRV_MODULE_VERSION "0.93"
+
+/* Registers address */
+#define CAN_BASE0		0x01C2BC00
+#define CAN_MSEL_ADDR		0x0000	/* CAN Mode Select Register */
+#define CAN_CMD_ADDR            0x0004	/* CAN Command Register */
+#define CAN_STA_ADDR		0x0008	/* CAN Status Register */
+#define CAN_INT_ADDR		0x000c	/* CAN Interrupt Flag Register */
+#define CAN_INTEN_ADDR		0x0010	/* CAN Interrupt Enable Register */
+#define CAN_BTIME_ADDR		0x0014	/* CAN Bus Timing 0 Register */
+#define CAN_TEWL_ADDR		0x0018	/* CAN Tx Error Warning Limit Register */
+#define CAN_ERRC_ADDR		0x001c	/* CAN Error Counter Register */
+#define CAN_RMCNT_ADDR		0x0020	/* CAN Receive Message Counter Register */
+#define CAN_RBUFSA_ADDR		0x0024	/* CAN Receive Buffer Start Address Register */
+#define CAN_BUF0_ADDR		0x0040	/* CAN Tx/Rx Buffer 0 Register */
+#define CAN_BUF1_ADDR		0x0044	/* CAN Tx/Rx Buffer 1 Register */
+#define CAN_BUF2_ADDR		0x0048	/* CAN Tx/Rx Buffer 2 Register */
+#define CAN_BUF3_ADDR		0x004c	/* CAN Tx/Rx Buffer 3 Register */
+#define CAN_BUF4_ADDR		0x0050	/* CAN Tx/Rx Buffer 4 Register */
+#define CAN_BUF5_ADDR		0x0054	/* CAN Tx/Rx Buffer 5 Register */
+#define CAN_BUF6_ADDR		0x0058	/* CAN Tx/Rx Buffer 6 Register */
+#define CAN_BUF7_ADDR		0x005c	/* CAN Tx/Rx Buffer 7 Register */
+#define CAN_BUF8_ADDR		0x0060	/* CAN Tx/Rx Buffer 8 Register */
+#define CAN_BUF9_ADDR		0x0064	/* CAN Tx/Rx Buffer 9 Register */
+#define CAN_BUF10_ADDR		0x0068	/* CAN Tx/Rx Buffer 10 Register */
+#define CAN_BUF11_ADDR		0x006c	/* CAN Tx/Rx Buffer 11 Register */
+#define CAN_BUF12_ADDR		0x0070	/* CAN Tx/Rx Buffer 12 Register */
+#define CAN_ACPC_ADDR		0x0040	/* CAN Acceptance Code 0 Register */
+#define CAN_ACPM_ADDR		0x0044	/* CAN Acceptance Mask 0 Register */
+#define CAN_RBUF_RBACK_START_ADDR	+ 0x0180	/* CAN transmit buffer for read back register */
+#define CAN_RBUF_RBACK_END_ADDR		+ 0x01b0	/* CAN transmit buffer for read back register */
+
+/* Controller Register Description */
+
+/* mode select register (r/w)
+ * offset:0x0000 default:0x0000_0001
+ */
+#define SLEEP_MODE		(0x01<<4)	/* This bit can only be written in Reset Mode */
+#define WAKE_UP			(0x00<<4)
+#define SINGLE_FILTER		(0x01<<3)	/* This bit can only be written in Reset Mode */
+#define DUAL_FILTERS		(0x00<<3)
+#define LOOPBACK_MODE		BIT(2)
+#define LISTEN_ONLY_MODE	BIT(1)
+#define RESET_MODE		BIT(0)
+
+/* command register (w)
+ * offset:0x0004 default:0x0000_0000
+ */
+#define BUS_OFF_REQ		BIT(5)
+#define SELF_RCV_REQ		BIT(4)
+#define CLEAR_OR_FLAG		BIT(3)
+#define RELEASE_RBUF		BIT(2)
+#define ABORT_REQ		BIT(1)
+#define TRANS_REQ		BIT(0)
+
+/* status register (r)
+ * offset:0x0008 default:0x0000_003c
+ */
+#define BIT_ERR			(0x00<<22)
+#define FORM_ERR		(0x01<<22)
+#define STUFF_ERR		(0x02<<22)
+#define OTHER_ERR		(0x03<<22)
+#define ERR_DIR			BIT(21)
+#define ERR_SEG_CODE		(0x1f<<16)
+#define START			(0x03<<16)
+#define ID28_21			(0x02<<16)
+#define ID20_18			(0x06<<16)
+#define SRTR			(0x04<<16)
+#define IDE			(0x05<<16)
+#define ID17_13			(0x07<<16)
+#define ID12_5			(0x0f<<16)
+#define ID4_0			(0x0e<<16)
+#define RTR			(0x0c<<16)
+#define RB1			(0x0d<<16)
+#define RB0			(0x09<<16)
+#define DLEN			(0x0b<<16)
+#define DATA_FIELD		(0x0a<<16)
+#define CRC_SEQUENCE		(0x08<<16)
+#define CRC_DELIMITER		(0x18<<16)
+#define ACK			(0x19<<16)
+#define ACK_DELIMITER		(0x1b<<16)
+#define END			(0x1a<<16)
+#define INTERMISSION		(0x12<<16)
+#define ACTIVE_ERROR		(0x11<<16)
+#define PASSIVE_ERROR		(0x16<<16)
+#define TOLERATE_DOMINANT_BITS	(0x13<<16)
+#define ERROR_DELIMITER		(0x17<<16)
+#define OVERLOAD		(0x1c<<16)
+#define BUS_OFF			BIT(7)
+#define ERR_STA			BIT(6)
+#define TRANS_BUSY		BIT(5)
+#define RCV_BUSY		BIT(4)
+#define TRANS_OVER		BIT(3)
+#define TBUF_RDY		BIT(2)
+#define DATA_ORUN		BIT(1)
+#define RBUF_RDY		BIT(0)
+
+/* interrupt register (r)
+ * offset:0x000c default:0x0000_0000
+ */
+#define BUS_ERR			BIT(7)
+#define ARB_LOST		BIT(6)
+#define ERR_PASSIVE		BIT(5)
+#define WAKEUP			BIT(4)
+#define DATA_OR			BIT(3)
+#define ERR_WRN			BIT(2)
+#define TBUF_VLD		BIT(1)
+#define RBUF_VLD		BIT(0)
+
+/* interrupt enable register (r/w)
+ * offset:0x0010 default:0x0000_0000
+ */
+#define BERR_IRQ_EN		BIT(7)
+#define ARB_LOST_IRQ_EN		BIT(6)
+#define ERR_PASSIVE_IRQ_EN	BIT(5)
+#define WAKEUP_IRQ_EN		BIT(4)
+#define OR_IRQ_EN		BIT(3)
+#define ERR_WRN_IRQ_EN		BIT(2)
+#define TX_IRQ_EN		BIT(1)
+#define RX_IRQ_EN		BIT(0)
+
+/* error code */
+#define ERR_INRCV		(0x1<<5)
+#define ERR_INTRANS		(0x0<<5)
+
+/* filter mode */
+#define FILTER_CLOSE		0
+#define SINGLE_FLTER_MODE	1
+#define DUAL_FILTER_MODE	2
+
+#define SUNXI_CAN_MAX_IRQ	20	/* max. number of interrupts handled in ISR */
+
+struct sunxican_priv {
+	struct can_priv can;
+	struct net_device *dev;
+	struct napi_struct napi;
+
+	int open_time;
+	struct sk_buff *echo_skb;
+	void __iomem *base;
+	struct clk *clk;
+
+	void *priv;		/* for board-specific data */
+
+	unsigned long irq_flags;	/* for request_irq() */
+	spinlock_t cmdreg_lock;	/* lock for concurrent cmd register writes */
+
+	u16 flags;		/* custom mode flags */
+};
+
+static const struct can_bittiming_const sunxican_bittiming_const = {
+	.name = DRV_NAME,
+	.tseg1_min = 1,
+	.tseg1_max = 16,
+	.tseg2_min = 1,
+	.tseg2_max = 8,
+	.sjw_max = 4,
+	.brp_min = 1,
+	.brp_max = 64,
+	.brp_inc = 1,
+};
+
+static void sunxi_can_write_cmdreg(struct sunxican_priv *priv, u8 val)
+{
+	unsigned long flags;
+
+	/*
+	 * The command register needs some locking and time to settle
+	 * the write_reg() operation - especially on SMP systems.
+	 */
+	spin_lock_irqsave(&priv->cmdreg_lock, flags);
+	writel(val, priv->base + CAN_CMD_ADDR);
+	readl(priv->base + CAN_STA_ADDR);
+	spin_unlock_irqrestore(&priv->cmdreg_lock, flags);
+}
+
+static void set_normal_mode(struct net_device *dev)
+{
+	struct sunxican_priv *priv = netdev_priv(dev);
+	u8 status = readl(priv->base + CAN_MSEL_ADDR);
+	int i;
+
+	for (i = 0; i < 100; i++) {
+		/* check reset bit */
+		if ((status & RESET_MODE) == 0) {
+			priv->can.state = CAN_STATE_ERROR_ACTIVE;
+			/* enable interrupts */
+			if (priv->can.ctrlmode & CAN_CTRLMODE_BERR_REPORTING) {
+				writel(0xFFFF, priv->base + CAN_INTEN_ADDR);
+			} else {
+				writel(0xFFFF & ~BERR_IRQ_EN,
+				       priv->base + CAN_INTEN_ADDR);
+			}
+
+			if (priv->can.ctrlmode & CAN_CTRLMODE_LOOPBACK) {
+				/* Put device into loopback mode */
+				writel(readl(priv->base + CAN_MSEL_ADDR) |
+				       LOOPBACK_MODE,
+				       priv->base + CAN_MSEL_ADDR);
+			} else if (priv->can.ctrlmode & CAN_CTRLMODE_LISTENONLY) {
+				/* Put device into listen-only mode */
+				writel(readl(priv->base + CAN_MSEL_ADDR) |
+				       LISTEN_ONLY_MODE,
+				       priv->base + CAN_MSEL_ADDR);
+			}
+			return;
+		}
+		/* set chip to normal mode */
+		writel(readl(priv->base + CAN_MSEL_ADDR) & (~RESET_MODE),
+		       priv->base + CAN_MSEL_ADDR);
+		udelay(10);
+		status = readl(priv->base + CAN_MSEL_ADDR);
+	}
+	netdev_err(dev, "setting controller into normal mode failed!\n");
+}
+
+static void set_reset_mode(struct net_device *dev)
+{
+	struct sunxican_priv *priv = netdev_priv(dev);
+	u8 status = readl(priv->base + CAN_MSEL_ADDR);
+	int i;
+
+	for (i = 0; i < 100; i++) {
+		/* check reset bit */
+		if (status & RESET_MODE) {
+			priv->can.state = CAN_STATE_STOPPED;
+			return;
+		}
+		/* select reset mode */
+		writel(readl(priv->base + CAN_MSEL_ADDR) | RESET_MODE, priv->base + CAN_MSEL_ADDR);
+		udelay(10);
+		status = readl(priv->base + CAN_MSEL_ADDR);
+	}
+	netdev_err(dev, "setting controller into reset mode failed!\n");
+}
+
+static int sunxican_set_bittiming(struct net_device *dev)
+{
+	struct sunxican_priv *priv = netdev_priv(dev);
+	struct can_bittiming *bt = &priv->can.bittiming;
+	u32 cfg;
+
+	cfg = ((bt->brp - 1) & 0x3FF) |
+	    (((bt->sjw - 1) & 0x3) << 14) |
+	    (((bt->prop_seg + bt->phase_seg1 - 1) & 0xf) << 16) |
+	    (((bt->phase_seg2 - 1) & 0x7) << 20);
+	if (priv->can.ctrlmode & CAN_CTRLMODE_3_SAMPLES)
+		cfg |= 0x800000;
+
+	netdev_info(dev, "setting BITTIMING=0x%08x\n", cfg);
+
+	/* CAN_BTIME_ADDR only writable in reset mode */
+	set_reset_mode(dev);
+	writel(cfg, priv->base + CAN_BTIME_ADDR);
+	set_normal_mode(dev);
+
+	return 0;
+}
+
+static int sunxican_get_berr_counter(const struct net_device *dev,
+				     struct can_berr_counter *bec)
+{
+	struct sunxican_priv *priv = netdev_priv(dev);
+	u32 errors;
+	errors = readl(priv->base + CAN_ERRC_ADDR);
+	bec->txerr = errors & 0x000F;
+	bec->rxerr = (errors >> 16) & 0x000F;
+	return 0;
+}
+
+static void sunxi_can_start(struct net_device *dev)
+{
+	struct sunxican_priv *priv = netdev_priv(dev);
+
+	/* leave reset mode */
+	if (priv->can.state != CAN_STATE_STOPPED)
+		set_reset_mode(dev);
+
+	/* set filters - we accept all */
+	writel(0x00000000, priv->base + CAN_ACPC_ADDR);
+	writel(0xffffffff, priv->base + CAN_ACPM_ADDR);
+
+	/* Clear error counters and error code capture */
+	writel(0x0, priv->base + CAN_ERRC_ADDR);
+
+	/* leave reset mode */
+	set_normal_mode(dev);
+}
+
+static int sunxican_set_mode(struct net_device *dev, enum can_mode mode)
+{
+	struct sunxican_priv *priv = netdev_priv(dev);
+
+	if (!priv->open_time)
+		return -EINVAL;
+
+	switch (mode) {
+	case CAN_MODE_START:
+		sunxi_can_start(dev);
+		if (netif_queue_stopped(dev))
+			netif_wake_queue(dev);
+		break;
+
+	default:
+		return -EOPNOTSUPP;
+	}
+	return 0;
+}
+
+/*
+ * transmit a CAN message
+ * message layout in the sk_buff should be like this:
+ * xx xx xx xx         ff         ll 00 11 22 33 44 55 66 77
+ * [ can_id ] [flags] [len] [can data (up to 8 bytes]
+ */
+static int sunxican_start_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+	struct sunxican_priv *priv = netdev_priv(dev);
+	struct can_frame *cf = (struct can_frame *)skb->data;
+	u8 dlc;
+	canid_t id;
+	u32 temp = 0;
+	int i;
+
+	/* wait buffer ready */
+	while (!(readl(priv->base + CAN_STA_ADDR) & TBUF_RDY))
+		usleep_range(10, 100);
+
+	if (can_dropped_invalid_skb(dev, skb))
+		return NETDEV_TX_OK;
+
+	netif_stop_queue(dev);
+
+	dlc = cf->can_dlc;
+	id = cf->can_id;
+
+	temp = ((id >> 30) << 6) | dlc;
+	writel(temp, priv->base + CAN_BUF0_ADDR);
+	if (id & CAN_EFF_FLAG) {
+		/* extended frame */
+		writel((id >> 21) & 0xFF, priv->base + CAN_BUF1_ADDR);	/* id28~21 */
+		writel((id >> 13) & 0xFF, priv->base + CAN_BUF2_ADDR);	/* id20~13 */
+		writel((id >> 5)  & 0xFF, priv->base + CAN_BUF3_ADDR);	/* id12~5  */
+		writel((id & 0x1F) << 3,  priv->base + CAN_BUF4_ADDR);	/* id4~0   */
+
+		for (i = 0; i < dlc; i++) {
+			writel(cf->data[i],
+			       priv->base + (CAN_BUF5_ADDR + i * 4));
+		}
+	} else {
+		/* standard frame */
+		writel((id >> 3) & 0xFF, priv->base + CAN_BUF1_ADDR);	/* id28~21 */
+		writel((id & 0x7) << 5, priv->base + CAN_BUF2_ADDR);	/* id20~13 */
+
+		for (i = 0; i < dlc; i++) {
+			writel(cf->data[i], priv->base + CAN_BUF3_ADDR + i * 4);
+		}
+	}
+	can_put_echo_skb(skb, dev, 0);
+	sunxi_can_write_cmdreg(priv, TRANS_REQ);
+
+	return NETDEV_TX_OK;
+}
+
+static void sunxi_can_rx(struct net_device *dev)
+{
+	struct sunxican_priv *priv = netdev_priv(dev);
+	struct net_device_stats *stats = &dev->stats;
+	struct can_frame *cf;
+	struct sk_buff *skb;
+	u8 fi;
+	canid_t id;
+	int i;
+
+	/* create zero'ed CAN frame buffer */
+	skb = alloc_can_skb(dev, &cf);
+	if (skb == NULL)
+		return;
+
+	fi = readl(priv->base + CAN_BUF0_ADDR);
+	cf->can_dlc = get_can_dlc(fi & 0x0F);
+	if (fi >> 7) {
+		/* extended frame format (EFF) */
+		id = (readl(priv->base + CAN_BUF1_ADDR) << 21) |	/* id28~21 */
+		     (readl(priv->base + CAN_BUF2_ADDR) << 13) |	/* id20~13 */
+		     (readl(priv->base + CAN_BUF3_ADDR) << 5)  |	/* id12~5  */
+		    ((readl(priv->base + CAN_BUF4_ADDR) >> 3) & 0x1f);	/* id4~0   */
+		id |= CAN_EFF_FLAG;
+
+		/* remote transmission request ? */
+		if ((fi >> 6) & 0x1)
+			id |= CAN_RTR_FLAG;
+		else {
+			for (i = 0; i < cf->can_dlc; i++)
+				cf->data[i] =
+				    readl(priv->base + CAN_BUF5_ADDR + i * 4);
+		}
+	} else {
+		/* standard frame format (SFF) */
+		id = (readl(priv->base + CAN_BUF1_ADDR) << 3) |		/* id28~21 */
+		    ((readl(priv->base + CAN_BUF2_ADDR) >> 5) & 0x7);	/* id20~18 */
+
+		/* remote transmission request ? */
+		if ((fi >> 6) & 0x1)
+			id |= CAN_RTR_FLAG;
+		else {
+			for (i = 0; i < cf->can_dlc; i++)
+				cf->data[i] =
+				    readl(priv->base + CAN_BUF3_ADDR + i * 4);
+		}
+	}
+	cf->can_id = id;
+
+	/* release receive buffer */
+	sunxi_can_write_cmdreg(priv, RELEASE_RBUF);
+
+	stats->rx_packets++;
+	stats->rx_bytes += cf->can_dlc;
+	netif_rx(skb);
+
+	can_led_event(dev, CAN_LED_EVENT_RX);
+}
+
+static int sunxi_can_err(struct net_device *dev, u8 isrc, u8 status)
+{
+	struct sunxican_priv *priv = netdev_priv(dev);
+	struct net_device_stats *stats = &dev->stats;
+	struct can_frame *cf;
+	struct sk_buff *skb;
+	enum can_state state = priv->can.state;
+	u32 ecc, alc;
+
+	skb = alloc_can_err_skb(dev, &cf);
+	if (skb == NULL)
+		return -ENOMEM;
+
+	if (isrc & DATA_OR) {
+		/* data overrun interrupt */
+		netdev_dbg(dev, "data overrun interrupt\n");
+		cf->can_id |= CAN_ERR_CRTL;
+		cf->data[1] = CAN_ERR_CRTL_RX_OVERFLOW;
+		stats->rx_over_errors++;
+		stats->rx_errors++;
+		sunxi_can_write_cmdreg(priv, CLEAR_OR_FLAG);	/* clear bit */
+	}
+	if (isrc & ERR_WRN) {
+		/* error warning interrupt */
+		netdev_dbg(dev, "error warning interrupt\n");
+
+		if (status & BUS_OFF) {
+			state = CAN_STATE_BUS_OFF;
+			cf->can_id |= CAN_ERR_BUSOFF;
+			can_bus_off(dev);
+		} else if (status & ERR_STA) {
+			state = CAN_STATE_ERROR_WARNING;
+		} else
+			state = CAN_STATE_ERROR_ACTIVE;
+	}
+	if (isrc & BUS_ERR) {
+		/* bus error interrupt */
+		netdev_dbg(dev, "bus error interrupt\n");
+		priv->can.can_stats.bus_error++;
+		stats->rx_errors++;
+
+		ecc = readl(priv->base + CAN_STA_ADDR);
+
+		cf->can_id |= CAN_ERR_PROT | CAN_ERR_BUSERROR;
+
+		if (ecc & BIT_ERR)
+			cf->data[2] |= CAN_ERR_PROT_BIT;
+		else if (ecc & FORM_ERR)
+			cf->data[2] |= CAN_ERR_PROT_FORM;
+		else if (ecc & STUFF_ERR)
+			cf->data[2] |= CAN_ERR_PROT_STUFF;
+		else {
+			cf->data[2] |= CAN_ERR_PROT_UNSPEC;
+			cf->data[3] = (ecc & ERR_SEG_CODE) >> 16;
+		}
+		/* error occurred during transmission? */
+		if ((ecc & ERR_DIR) == 0)
+			cf->data[2] |= CAN_ERR_PROT_TX;
+	}
+	if (isrc & ERR_PASSIVE) {
+		/* error passive interrupt */
+		netdev_dbg(dev, "error passive interrupt\n");
+		if (status & ERR_STA)
+			state = CAN_STATE_ERROR_PASSIVE;
+		else
+			state = CAN_STATE_ERROR_ACTIVE;
+	}
+	if (isrc & ARB_LOST) {
+		/* arbitration lost interrupt */
+		netdev_dbg(dev, "arbitration lost interrupt\n");
+		alc = readl(priv->base + CAN_STA_ADDR);
+		priv->can.can_stats.arbitration_lost++;
+		stats->tx_errors++;
+		cf->can_id |= CAN_ERR_LOSTARB;
+		cf->data[0] = (alc & 0x1f) >> 8;
+	}
+	if (state != priv->can.state && (state == CAN_STATE_ERROR_WARNING ||
+					 state == CAN_STATE_ERROR_PASSIVE)) {
+		u32 errc = readl(priv->base + CAN_ERRC_ADDR);
+		u8 rxerr = (errc >> 16) & 0xFF;
+		u8 txerr = errc & 0xFF;
+		cf->can_id |= CAN_ERR_CRTL;
+		if (state == CAN_STATE_ERROR_WARNING) {
+			priv->can.can_stats.error_warning++;
+			cf->data[1] = (txerr > rxerr) ?
+			    CAN_ERR_CRTL_TX_WARNING : CAN_ERR_CRTL_RX_WARNING;
+		} else {
+			priv->can.can_stats.error_passive++;
+			cf->data[1] = (txerr > rxerr) ?
+			    CAN_ERR_CRTL_TX_PASSIVE : CAN_ERR_CRTL_RX_PASSIVE;
+		}
+		cf->data[6] = txerr;
+		cf->data[7] = rxerr;
+	}
+	priv->can.state = state;
+
+	stats->rx_packets++;
+	stats->rx_bytes += cf->can_dlc;
+	netif_rx(skb);
+
+	return 0;
+}
+
+irqreturn_t sunxi_can_interrupt(int irq, void *dev_id)
+{
+	struct net_device *dev = (struct net_device *)dev_id;
+	struct sunxican_priv *priv = netdev_priv(dev);
+	struct net_device_stats *stats = &dev->stats;
+	u8 isrc, status;
+	int n = 0;
+
+	while ((isrc = readl(priv->base + CAN_INT_ADDR))
+	       && (n < SUNXI_CAN_MAX_IRQ)) {
+		n++;
+		status = readl(priv->base + CAN_STA_ADDR);
+
+		if (isrc & WAKEUP)
+			netdev_warn(dev, "wakeup interrupt\n");
+
+		if (isrc & TBUF_VLD) {
+			/* transmission complete interrupt */
+			stats->tx_bytes +=
+			    readl(priv->base + CAN_RBUF_RBACK_START_ADDR) & 0xf;
+			stats->tx_packets++;
+			can_get_echo_skb(dev, 0);
+			netif_wake_queue(dev);
+			can_led_event(dev, CAN_LED_EVENT_TX);
+		}
+		if (isrc & RBUF_VLD) {
+			/* receive interrupt */
+			while (status & RBUF_RDY) {	/* RX buffer is not empty */
+				sunxi_can_rx(dev);
+				status = readl(priv->base + CAN_STA_ADDR);
+			}
+		}
+		if (isrc &
+		    (DATA_OR | ERR_WRN | BUS_ERR | ERR_PASSIVE | ARB_LOST)) {
+			/* error interrupt */
+			if (sunxi_can_err(dev, isrc, status))
+				break;
+		}
+		/* clear the interrupt */
+		writel(isrc, priv->base + CAN_INT_ADDR);
+		udelay(10);
+	}
+	if (n >= SUNXI_CAN_MAX_IRQ)
+		netdev_dbg(dev, "%d messages handled in ISR", n);
+
+	return (n) ? IRQ_HANDLED : IRQ_NONE;
+}
+
+EXPORT_SYMBOL_GPL(sunxi_can_interrupt);
+
+static int sunxican_open(struct net_device *dev)
+{
+	struct sunxican_priv *priv = netdev_priv(dev);
+	int err;
+
+	/* set chip into reset mode */
+	set_reset_mode(dev);
+
+	/* common open */
+	err = open_candev(dev);
+	if (err)
+		return err;
+
+	/* register interrupt handler */
+	if (request_irq
+	    (dev->irq, sunxi_can_interrupt, priv->irq_flags, dev->name,
+	     (void *)dev)) {
+		netdev_err(dev, "request_irq err: %d\n", err);
+		return -EAGAIN;
+	}
+	sunxican_set_bittiming(dev);
+	sunxi_can_start(dev);
+
+	can_led_event(dev, CAN_LED_EVENT_OPEN);
+
+	netif_start_queue(dev);
+
+	return 0;
+}
+
+static int sunxican_close(struct net_device *dev)
+{
+	netif_stop_queue(dev);
+	set_reset_mode(dev);
+
+	free_irq(dev->irq, (void *)dev);
+
+	close_candev(dev);
+
+	can_led_event(dev, CAN_LED_EVENT_STOP);
+
+	return 0;
+}
+
+void free_sunxicandev(struct net_device *dev)
+{
+	free_candev(dev);
+}
+
+EXPORT_SYMBOL_GPL(free_sunxicandev);
+
+static const struct net_device_ops sunxican_netdev_ops = {
+	.ndo_open = sunxican_open,
+	.ndo_stop = sunxican_close,
+	.ndo_start_xmit = sunxican_start_xmit,
+};
+
+static const struct of_device_id sunxican_of_match[] = {
+	{.compatible = "allwinner,sunxican"},
+	{},
+};
+
+MODULE_DEVICE_TABLE(of, sunxican_of_match);
+
+static const struct platform_device_id sunxican_id_table[] = {
+	{.name = "sunxican"},
+	{},
+};
+
+MODULE_DEVICE_TABLE(platform, sunxican_id_table);
+
+static int sunxican_remove(struct platform_device *pdev)
+{
+	struct net_device *dev = platform_get_drvdata(pdev);
+	struct sunxican_priv *priv = netdev_priv(dev);
+	struct resource *res;
+
+	set_reset_mode(dev);
+	unregister_netdev(dev);
+	iounmap(priv->base);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	release_mem_region(res->start, resource_size(res));
+
+	free_candev(dev);
+
+	return 0;
+}
+
+static int sunxican_probe(struct platform_device *pdev)
+{
+	struct resource *res;
+	struct clk *clk;
+	void __iomem *addr;
+	int err, irq;
+	u32 temp_irqen;
+	struct net_device *dev;
+	struct sunxican_priv *priv;
+
+	clk = clk_get(&pdev->dev, "apb1_can");
+	if (IS_ERR(clk)) {
+		dev_err(&pdev->dev, "no clock defined\n");
+		err = -ENODEV;
+		goto exit;
+	}
+	/* turn on clocking for CAN peripheral block */
+	err = clk_prepare_enable(clk);
+	if (err) {
+		dev_err(&pdev->dev, "could not enable clocking (apb1_can)\n");
+		goto exit;
+	}
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	irq = platform_get_irq(pdev, 0);
+
+	if (!res || irq <= 0) {
+		dev_err(&pdev->dev, "could not get a valid irq\n");
+		err = -ENODEV;
+		goto exit_put;
+	}
+	if (!request_mem_region(res->start, resource_size(res), pdev->name)) {
+		dev_err(&pdev->dev, "could not get io memory resource\n");
+		err = -EBUSY;
+		goto exit_put;
+	}
+	addr = ioremap_nocache(res->start, resource_size(res));
+	if (!addr) {
+		dev_err(&pdev->dev, "could not map io memory\n");
+		err = -ENOMEM;
+		goto exit_release;
+	}
+	dev = alloc_candev(sizeof(struct sunxican_priv), 1);
+
+	if (!dev) {
+		dev_err(&pdev->dev,
+			"could not allocate memory for CAN device\n");
+		err = -ENOMEM;
+		goto exit_iounmap;
+	}
+
+	dev->netdev_ops = &sunxican_netdev_ops;
+	dev->irq = irq;
+	dev->flags |= IFF_ECHO;
+
+	priv = netdev_priv(dev);
+	priv->can.clock.freq = clk_get_rate(clk);
+	priv->can.bittiming_const = &sunxican_bittiming_const;
+	priv->can.do_set_mode = sunxican_set_mode;
+	priv->can.do_get_berr_counter = sunxican_get_berr_counter;
+	priv->can.ctrlmode_supported = CAN_CTRLMODE_BERR_REPORTING |
+	    CAN_CTRLMODE_LISTENONLY | CAN_CTRLMODE_LOOPBACK |
+	    CAN_CTRLMODE_3_SAMPLES;
+	priv->dev = dev;
+	priv->base = addr;
+	priv->clk = clk;
+	spin_lock_init(&priv->cmdreg_lock);
+
+	/* enable CAN specific interrupts */
+	set_reset_mode(dev);
+	temp_irqen = BERR_IRQ_EN | ERR_PASSIVE_IRQ_EN | OR_IRQ_EN | RX_IRQ_EN;
+	writel(readl(priv->base + CAN_INTEN_ADDR) | temp_irqen,
+	       priv->base + CAN_INTEN_ADDR);
+
+	platform_set_drvdata(pdev, dev);
+	SET_NETDEV_DEV(dev, &pdev->dev);
+
+	err = register_candev(dev);
+	if (err) {
+		dev_err(&pdev->dev, "registering %s failed (err=%d)\n",
+			DRV_NAME, err);
+		goto exit_free;
+	}
+	devm_can_led_init(dev);
+
+	dev_info(&pdev->dev, "device registered (base=%p, irq=%d)\n",
+		 priv->base, dev->irq);
+
+	return 0;
+
+exit_free:
+	free_candev(dev);
+exit_iounmap:
+	iounmap(addr);
+exit_release:
+	release_mem_region(res->start, resource_size(res));
+exit_put:
+	clk_put(clk);
+exit:
+	return err;
+}
+
+static int __maybe_unused sunxi_can_suspend(struct device *device)
+{
+	struct net_device *dev = dev_get_drvdata(device);
+	struct sunxican_priv *priv = netdev_priv(dev);
+
+	if (netif_running(dev)) {
+		netif_stop_queue(dev);
+		netif_device_detach(dev);
+	}
+	priv->can.state = CAN_STATE_SLEEPING;
+
+	return 0;
+}
+
+static int __maybe_unused sunxi_can_resume(struct device *device)
+{
+	struct net_device *dev = dev_get_drvdata(device);
+	struct sunxican_priv *priv = netdev_priv(dev);
+
+	priv->can.state = CAN_STATE_ERROR_ACTIVE;
+	if (netif_running(dev)) {
+		netif_device_attach(dev);
+		netif_start_queue(dev);
+	}
+	return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(sunxi_can_pm_ops, sunxi_can_suspend, sunxi_can_resume);
+
+static struct platform_driver sunxi_can_driver = {
+	.driver = {
+		   .name = DRV_NAME,
+		   .owner = THIS_MODULE,
+		   .pm = &sunxi_can_pm_ops,
+		   .of_match_table = sunxican_of_match,
+		   },
+	.probe = sunxican_probe,
+	.remove = sunxican_remove,
+	.id_table = sunxican_id_table,
+};
+
+module_platform_driver(sunxi_can_driver);
+
+MODULE_AUTHOR("Peter Chen <xingkongcp@gmail.com>, "
+	      "Gerhard Bertelsmann <info@gerhard-bertelsmann.de>");
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_DESCRIPTION(DRV_NAME "CAN driver for Allwinner SoCs (A10/A20)");
+MODULE_VERSION(DRV_MODULE_VERSION);

             reply	other threads:[~2015-09-02 18:44 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2015-09-02 18:44 Gerhard Bertelsmann [this message]
2015-09-02 19:22 ` [PATCH] CAN controller support for Allwinner A10/A20 SoCs Marc Kleine-Budde

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=55E74381.6050506@gerhard-bertelsmann.de \
    --to=info@gerhard-bertelsmann.de \
    --cc=linux-can@vger.kernel.org \
    --cc=mkl@pengutronix.de \
    /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.