diff --git a/drivers/net/can/Kconfig b/drivers/net/can/Kconfig index bb709fd..671be9d 100644 --- a/drivers/net/can/Kconfig +++ b/drivers/net/can/Kconfig @@ -110,6 +110,13 @@ config PCH_CAN is an IOH for x86 embedded processor (Intel Atom E6xx series). This driver can access CAN bus. +config CAN_SUNXI + tristate "Sun7i, Sun4i CAN bus controller" + depends on ARCH_SUN4I || ARCH_SUN7I + default n + ---help--- + This is the Sun7i, Sun4i Allwinner CAN BUS driver. + source "drivers/net/can/mscan/Kconfig" source "drivers/net/can/sja1000/Kconfig" diff --git a/drivers/net/can/Makefile b/drivers/net/can/Makefile index 938be37..02123c4 100644 --- a/drivers/net/can/Makefile +++ b/drivers/net/can/Makefile @@ -22,5 +22,6 @@ obj-$(CONFIG_CAN_BFIN) += bfin_can.o obj-$(CONFIG_CAN_JANZ_ICAN3) += janz-ican3.o obj-$(CONFIG_CAN_FLEXCAN) += flexcan.o obj-$(CONFIG_PCH_CAN) += pch_can.o +obj-$(CONFIG_CAN_SUNXI) += sunxi_can.o ccflags-$(CONFIG_CAN_DEBUG_DEVICES) := -DDEBUG diff --git a/drivers/net/can/sunxi_can.c b/drivers/net/can/sunxi_can.c new file mode 100644 index 0000000..57631fa --- /dev/null +++ b/drivers/net/can/sunxi_can.c @@ -0,0 +1,695 @@ +/* +* sunxi_can.c - CAN bus controller driver for sun7i and sun4i +* +* Copyright (c) 2013 Peter Chen +* - driver for sun7i +* Copyright (c) 2017 Oleg Strelkov +* - modifications for sun4i +* +* Copyright (c) 2013 Inmotion Co,. LTD +* All right reserved. +* +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "sunxi_can.h" + +#define DRV_NAME "sunxi_can" + +MODULE_AUTHOR("Peter Chen "); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_DESCRIPTION(DRV_NAME "CAN netdevice driver"); + +static struct net_device *sunxican_dev; +static struct can_bittiming_const sunxi_can_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 sunxi_can_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, CAN_CMD_ADDR); + spin_unlock_irqrestore(&priv->cmdreg_lock, flags); +} + +static int sunxi_can_is_absent(struct sunxi_can_priv *priv) +{ + return ((readl(CAN_MSEL_ADDR) & 0xFF) == 0xFF); +} + +static int sunxi_can_probe(struct net_device *dev) +{ + struct sunxi_can_priv *priv = netdev_priv(dev); + + if (sunxi_can_is_absent(priv)) { + printk(KERN_INFO "%s: probing @0x%lX failed\n", + DRV_NAME, dev->base_addr); + return 0; + } + return -1; +} + +static void set_reset_mode(struct net_device *dev) +{ + struct sunxi_can_priv *priv = netdev_priv(dev); + uint32_t status = readl(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; + } + + writel(readl(CAN_MSEL_ADDR) | RESET_MODE, CAN_MSEL_ADDR); /* select reset mode */ + //writel(RESET_MODE, CAN_MSEL_ADDR); /* select reset mode */ + udelay(10); + status = readl(CAN_MSEL_ADDR); + } + + netdev_err(dev, "setting SUNXI_CAN into reset mode failed!\n"); +} + +static void set_normal_mode(struct net_device *dev) +{ + struct sunxi_can_priv *priv = netdev_priv(dev); + unsigned char status = readl(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, CAN_INTEN_ADDR); + } else { + writel(0xFFFF & ~BERR_IRQ_EN, CAN_INTEN_ADDR); + } + + if (priv->can.ctrlmode & CAN_CTRLMODE_LOOPBACK) { + /* Put device into loopback mode */ + writel(readl(CAN_MSEL_ADDR) | LOOPBACK_MODE, CAN_MSEL_ADDR); + } else if (priv->can.ctrlmode & CAN_CTRLMODE_LISTENONLY) { + /* Put device into listen-only mode */ + writel(readl(CAN_MSEL_ADDR) | LISTEN_ONLY_MODE, CAN_MSEL_ADDR); + } + return; + } + + /* set chip to normal mode */ + writel(readl(CAN_MSEL_ADDR) & (~RESET_MODE), CAN_MSEL_ADDR); + udelay(10); + status = readl(CAN_MSEL_ADDR); + } + + netdev_err(dev, "setting SUNXI_CAN into normal mode failed!\n"); +} + + +static void sunxi_can_start(struct net_device *dev) +{ + struct sunxi_can_priv *priv = netdev_priv(dev); + + /* leave reset mode */ + if (priv->can.state != CAN_STATE_STOPPED) + set_reset_mode(dev); + + /* Clear error counters and error code capture */ + writel(0x0, CAN_ERRC_ADDR); + + /* leave reset mode */ + set_normal_mode(dev); +} + +static int sunxi_can_set_mode(struct net_device *dev, enum can_mode mode) +{ + struct sunxi_can_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; +} + +static int sunxi_can_set_bittiming(struct net_device *dev) +{ + struct sunxi_can_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); + + set_reset_mode(dev); //CAN_BTIME_ADDR only writable in reset mode + writel(cfg, CAN_BTIME_ADDR); + set_normal_mode(dev); + + return 0; +} + +static int sunxi_can_get_berr_counter(const struct net_device *dev, + struct can_berr_counter *bec) +{ + bec->txerr = readl(CAN_ERRC_ADDR) & 0x000F; + bec->rxerr = (readl(CAN_ERRC_ADDR) & 0x0F00) >> 16; + + return 0; +} + +/* +* initialize sunxi_can: +* - reset chip +* - set output mode +* - set baudrate +* - enable interrupts +* - start operating mode +*/ +static void chipset_init(struct net_device *dev) +{ + u32 temp_irqen; + + /* config pins + * PH20-TX, PH21-RX :4 */ + + if (gpio_request_ex("can_para", "can_tx") == 0 || gpio_request_ex("can_para", "can_rx") == 0 ) { + pr_info("can request gpio fail!\n"); + } + + //enable clock + writel(readl(0xF1C20000 + 0x6C) | (1 << 4), 0xF1C20000 + 0x6C); + + //set can controller in reset mode + set_reset_mode(dev); + + //enable interrupt + temp_irqen = BERR_IRQ_EN | ERR_PASSIVE_IRQ_EN + | OR_IRQ_EN | RX_IRQ_EN; + writel(readl(CAN_INTEN_ADDR) | temp_irqen, CAN_INTEN_ADDR); + + //return to transfer mode + set_normal_mode(dev); +} + +/* +* 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 netdev_tx_t sunxi_can_start_xmit(struct sk_buff *skb, + struct net_device *dev) +{ + struct sunxi_can_priv *priv = netdev_priv(dev); + struct can_frame *cf = (struct can_frame *)skb->data; + uint8_t dlc; + canid_t id; + uint32_t temp = 0; + uint8_t i; + + //wait buff ready + while (!(readl(CAN_STA_ADDR) & TBUF_RDY)); + + set_reset_mode(dev); + + writel(0xffffffff, CAN_ACPM_ADDR); + + //enter transfer mode + set_normal_mode(dev); + + 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, CAN_BUF0_ADDR); + if (id & CAN_EFF_FLAG) {/* extern frame */ + writel(0xFF & (id >> 21), CAN_BUF1_ADDR); //id28~21 + writel(0xFF & (id >> 13), CAN_BUF2_ADDR); //id20~13 + writel(0xFF & (id >> 5), CAN_BUF3_ADDR); //id12~5 + writel((id & 0x1F) << 3, CAN_BUF4_ADDR); //id4~0 + + for (i = 0; i < dlc; i++) { + writel(cf->data[i], CAN_BUF5_ADDR + i * 4); + } + } else { /* standard frame*/ + writel(0xFF & (id >> 3), CAN_BUF1_ADDR); //id28~21 + writel((id & 0x7) << 5, CAN_BUF2_ADDR); //id20~13 + + for (i = 0; i < dlc; i++) { + writel(cf->data[i], CAN_BUF3_ADDR + i * 4); + } + } + + can_put_echo_skb(skb, dev, 0); + + while (!(readl(CAN_STA_ADDR) & TBUF_RDY)); + sunxi_can_write_cmdreg(priv, TRANS_REQ); + + return NETDEV_TX_OK; +} + +static void sunxi_can_rx(struct net_device *dev) +{ + struct sunxi_can_priv *priv = netdev_priv(dev); + struct net_device_stats *stats = &dev->stats; + struct can_frame *cf; + struct sk_buff *skb; + uint8_t fi; + canid_t id; + int i; + + /* create zero'ed CAN frame buffer */ + skb = alloc_can_skb(dev, &cf); + if (skb == NULL) + return; + + fi = readl(CAN_BUF0_ADDR); + cf->can_dlc = get_can_dlc(fi & 0x0F); + if (fi >> 7) { + /* extended frame format (EFF) */ + id = (readl(CAN_BUF1_ADDR) << 21) //id28~21 + | (readl(CAN_BUF2_ADDR) << 13) //id20~13 + | (readl(CAN_BUF3_ADDR) << 5) //id12~5 + | ((readl(CAN_BUF4_ADDR) >> 3) & 0x1f); //id4~0 + id |= CAN_EFF_FLAG; + + if ((fi >> 6) & 0x1) { /* remote transmission request */ + id |= CAN_RTR_FLAG; + } else { + for (i = 0; i < cf->can_dlc; i++) + cf->data[i] = readl(CAN_BUF5_ADDR + i * 4); + } + } else { + /* standard frame format (SFF) */ + id = (readl(CAN_BUF1_ADDR) << 3) //id28~21 + | ((readl(CAN_BUF2_ADDR) >> 5) & 0x7); //id20~18 + + if ((fi >> 6) & 0x1) { /* remote transmission request */ + id |= CAN_RTR_FLAG; + } else { + for (i = 0; i < cf->can_dlc; i++) + cf->data[i] = readl(CAN_BUF3_ADDR + i * 4); + } + } + + cf->can_id = id; + + /* release receive buffer */ + sunxi_can_write_cmdreg(priv, RELEASE_RBUF); + + netif_rx(skb); + + stats->rx_packets++; + stats->rx_bytes += cf->can_dlc; +} + +static int sunxi_can_err(struct net_device *dev, uint8_t isrc, uint8_t status) +{ + struct sunxi_can_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; + uint32_t ecc, alc; + + skb = alloc_can_err_skb(dev, &cf); + if (skb == NULL) + return -ENOMEM; + + if (isrc & DATA_ORUNI) { + /* 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_DOVERRUN); /* 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 */ + priv->can.can_stats.bus_error++; + stats->rx_errors++; + + ecc = readl(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(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)) { + uint8_t rxerr = (readl(CAN_ERRC_ADDR) >> 16) & 0xFF; + uint8_t txerr = readl(CAN_ERRC_ADDR) & 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; + + netif_rx(skb); + + stats->rx_packets++; + stats->rx_bytes += cf->can_dlc; + + return 0; +} + +irqreturn_t sunxi_can_interrupt(int irq, void *dev_id) +{ + struct net_device *dev = (struct net_device *)dev_id; + struct sunxi_can_priv *priv = netdev_priv(dev); + struct net_device_stats *stats = &dev->stats; + uint8_t isrc, status; + int n = 0; + + while ((isrc = readl(CAN_INT_ADDR)) && (n < SUNXI_CAN_MAX_IRQ)) { + n++; + status = readl(CAN_STA_ADDR); + /* check for absent controller due to hw unplug */ + if (sunxi_can_is_absent(priv)) + return IRQ_NONE; + + if (isrc & WAKEUP) + netdev_warn(dev, "wakeup interrupt\n"); + + if (isrc & TBUF_VLD) { + pr_debug("sunxicanirq: Tx irq, reg=0x%X\n", isrc); + /* transmission complete interrupt */ + stats->tx_bytes += readl(CAN_RBUF_RBACK_START_ADDR) & 0xf; + stats->tx_packets++; + can_get_echo_skb(dev, 0); + netif_wake_queue(dev); + } + if (isrc & RBUF_VLD) { + pr_debug("sunxicanirq: Rx irq, reg=0x%X\n", isrc); + /* receive interrupt */ + while (status & RBUF_RDY) { //RX buffer is not empty + sunxi_can_rx(dev); + status = readl(CAN_STA_ADDR); + /* check for absent controller */ + if (sunxi_can_is_absent(priv)) + return IRQ_NONE; + } + } + if (isrc & (DATA_ORUNI | ERR_WRN | BUS_ERR | ERR_PASSIVE | ARB_LOST)) { + pr_debug("sunxicanirq: error, reg=0x%X\n", isrc); + /* error interrupt */ + if (sunxi_can_err(dev, isrc, status)) + break; + } + + //clear the interrupt + writel(isrc, CAN_INT_ADDR); + readl(CAN_INT_ADDR); + } + + 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 sunxi_can_open(struct net_device *dev) +{ + struct sunxi_can_priv *priv = netdev_priv(dev); + int err; + + /* set chip into reset mode */ + set_reset_mode(dev); + + writel(0xffffffff, CAN_ACPM_ADDR); + + /* common open */ + err = open_candev(dev); + if (err) + return err; + + /* register interrupt handler, if not done by the device driver */ + if (!(priv->flags & SUNXI_CAN_CUSTOM_IRQ_HANDLER)) { + err = request_irq(dev->irq, sunxi_can_interrupt, priv->irq_flags, + dev->name, (void *)dev); + if (err) { + close_candev(dev); + pr_info("request_irq err:%d\n", err); + return -EAGAIN; + } + } + + /* init and start chi */ + sunxi_can_start(dev); + priv->open_time = jiffies; + + netif_start_queue(dev); + + return 0; +} + +static int sunxi_can_close(struct net_device *dev) +{ + struct sunxi_can_priv *priv = netdev_priv(dev); + + netif_stop_queue(dev); + set_reset_mode(dev); + + if (!(priv->flags & SUNXI_CAN_CUSTOM_IRQ_HANDLER)) + free_irq(dev->irq, (void *)dev); + + close_candev(dev); + + priv->open_time = 0; + + return 0; +} + +struct net_device *alloc_sunxicandev(int sizeof_priv) +{ + struct net_device *dev; + struct sunxi_can_priv *priv; + + dev = alloc_candev(sizeof(struct sunxi_can_priv) + sizeof_priv, + SUNXI_CAN_ECHO_SKB_MAX); + if (!dev) + return NULL; + + priv = netdev_priv(dev); + + priv->dev = dev; + priv->can.bittiming_const = &sunxi_can_bittiming_const; + priv->can.do_set_bittiming = sunxi_can_set_bittiming; + priv->can.do_set_mode = sunxi_can_set_mode; + priv->can.do_get_berr_counter = sunxi_can_get_berr_counter; + priv->can.ctrlmode_supported = CAN_CTRLMODE_LOOPBACK | + CAN_CTRLMODE_LISTENONLY | + CAN_CTRLMODE_3_SAMPLES | + CAN_CTRLMODE_BERR_REPORTING; + + spin_lock_init(&priv->cmdreg_lock); + + if (sizeof_priv) + priv->priv = (void *)priv + sizeof(struct sunxi_can_priv); + + return dev; +} +EXPORT_SYMBOL_GPL(alloc_sunxicandev); + +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 = sunxi_can_open, + .ndo_stop = sunxi_can_close, + .ndo_start_xmit = sunxi_can_start_xmit, +}; + +int register_sunxicandev(struct net_device *dev) +{ + if (!sunxi_can_probe(dev)) + return -ENODEV; + + dev->flags |= IFF_ECHO; /* support local echo */ + dev->netdev_ops = &sunxican_netdev_ops; + + set_reset_mode(dev); + + return register_candev(dev); +} +EXPORT_SYMBOL_GPL(register_sunxicandev); + +void unregister_sunxicandev(struct net_device *dev) +{ + set_reset_mode(dev); + unregister_candev(dev); +} +EXPORT_SYMBOL_GPL(unregister_sunxicandev); + +static __init int sunxi_can_init(void) +{ + struct sunxi_can_priv *priv; + int err = 0; + int ret = 0; + int used = 0; + + sunxican_dev = alloc_sunxicandev(0); + if(!sunxican_dev) { + pr_info("alloc sunxicandev fail\n"); + } + + ret = script_parser_fetch("can_para", "can_used", &used, sizeof (used)); + if ( ret || used == 0) { + pr_info("[sunxi-can] Cannot setup CANBus driver, maybe not configured in script.bin?"); + goto exit_free; + } + + priv = netdev_priv(sunxican_dev); + sunxican_dev->irq = SW_INT_IRQNO_CAN; + priv->irq_flags = 0; + priv->can.clock.freq = clk_get_rate(clk_get(NULL, "can")); + chipset_init(sunxican_dev); + err = register_sunxicandev(sunxican_dev); + if(err) { + dev_err(&sunxican_dev->dev, "registering %s failed (err=%d)\n", DRV_NAME, err); + goto exit_free; + } + + dev_info(&sunxican_dev->dev, "%s device registered (reg_base=0x%08x, irq=%d)\n", + DRV_NAME, CAN_BASE0, sunxican_dev->irq); + + pr_info("%s CAN netdevice driver\n", DRV_NAME); + + return 0; + +exit_free: + free_sunxicandev(sunxican_dev); + + return err; +} +module_init(sunxi_can_init); + +static __exit void sunxi_can_exit(void) +{ + unregister_sunxicandev(sunxican_dev); + free_sunxicandev(sunxican_dev); + + pr_info("%s: driver removed\n", DRV_NAME); +} +module_exit(sunxi_can_exit); diff --git a/drivers/net/can/sunxi_can.h b/drivers/net/can/sunxi_can.h new file mode 100644 index 0000000..9e84307 --- /dev/null +++ b/drivers/net/can/sunxi_can.h @@ -0,0 +1,178 @@ +/* +* sun7i_can.h - CAN bus controller driver for sun7i +* +* Copyright (c) 2013 Peter Chen +* +* Copyright (c) 2013 Inmotion Co., Ltd. +* All right reserved. +* +*/ + +#ifndef SUNXI_CAN_H +#define SUNXI_CAN_H + +#include +#include + +#define SUNXI_CAN_ECHO_SKB_MAX 1 /* the SUN7I, SUN4I CAN has one TX buffer object */ + +/* Registers' address */ +#define CAN_BASE0 0xF1C2BC00 +#define CAN_MSEL_ADDR (CAN_BASE0 + 0x0000) //Can Mode Select Register +#define CAN_CMD_ADDR (CAN_BASE0 + 0x0004) //Can Command Register +#define CAN_STA_ADDR (CAN_BASE0 + 0x0008) //Can Status Register +#define CAN_INT_ADDR (CAN_BASE0 + 0x000c) //Can Interrupt Flag Register +#define CAN_INTEN_ADDR (CAN_BASE0 + 0x0010) //Can Interrupt Enable Register +#define CAN_BTIME_ADDR (CAN_BASE0 + 0x0014) //Can Bus Timing 0 Register +#define CAN_TEWL_ADDR (CAN_BASE0 + 0x0018) //Can Tx Error Warning Limit Register +#define CAN_ERRC_ADDR (CAN_BASE0 + 0x001c) //Can Error Counter Register +#define CAN_RMCNT_ADDR (CAN_BASE0 + 0x0020) //Can Receive Message Counter Register +#define CAN_RBUFSA_ADDR (CAN_BASE0 + 0x0024) //Can Receive Buffer Start Address Register +#define CAN_BUF0_ADDR (CAN_BASE0 + 0x0040) //Can Tx/Rx Buffer 0 Register +#define CAN_BUF1_ADDR (CAN_BASE0 + 0x0044) //Can Tx/Rx Buffer 1 Register +#define CAN_BUF2_ADDR (CAN_BASE0 + 0x0048) //Can Tx/Rx Buffer 2 Register +#define CAN_BUF3_ADDR (CAN_BASE0 + 0x004c) //Can Tx/Rx Buffer 3 Register +#define CAN_BUF4_ADDR (CAN_BASE0 + 0x0050) //Can Tx/Rx Buffer 4 Register +#define CAN_BUF5_ADDR (CAN_BASE0 + 0x0054) //Can Tx/Rx Buffer 5 Register +#define CAN_BUF6_ADDR (CAN_BASE0 + 0x0058) //Can Tx/Rx Buffer 6 Register +#define CAN_BUF7_ADDR (CAN_BASE0 + 0x005c) //Can Tx/Rx Buffer 7 Register +#define CAN_BUF8_ADDR (CAN_BASE0 + 0x0060) //Can Tx/Rx Buffer 8 Register +#define CAN_BUF9_ADDR (CAN_BASE0 + 0x0064) //Can Tx/Rx Buffer 9 Register +#define CAN_BUF10_ADDR (CAN_BASE0 + 0x0068) //Can Tx/Rx Buffer 10 Register +#define CAN_BUF11_ADDR (CAN_BASE0 + 0x006c) //Can Tx/Rx Buffer 11 Register +#define CAN_BUF12_ADDR (CAN_BASE0 + 0x0070) //Can Tx/Rx Buffer 12 Register +#define CAN_ACPC_ADDR (CAN_BASE0 + 0x0040) //Can Acceptance Code 0 Register +#define CAN_ACPM_ADDR (CAN_BASE0 + 0x0044) //Can Acceptance Mask 0 Register +#define CAN_RBUF_RBACK_START_ADDR (CAN_BASE0 + 0x0180) //CAN transmit buffer for read back register +#define CAN_RBUF_RBACK_END_ADDR (CAN_BASE0 + 0x01b0) //CAN transmit buffer for read back register + +/* Controller Register Description */ + +/* mode select register (r/w) +* offset:0x0000 default:0x0000_0001 */ +#define SLEEP_MODE (1<<4) //This bit can only be written in Reset Mode +#define WAKE_UP (0<<4) //This bit can only be written in Reset Mode +#define SINGLE_FILTER (1<<3) +#define DUAL_FILTERS (0<<3) +#define LOOPBACK_MODE (1<<2) +#define LISTEN_ONLY_MODE (1<<1) +#define RESET_MODE (1<<0) +/* command register (w) +* offset:0x0004 default:0x0000_0000 */ +#define BUS_OFF_REQ (1<<5) +#define SELF_RCV_REQ (1<<4) +#define CLEAR_DOVERRUN (1<<3) +#define RELEASE_RBUF (1<<2) +#define ABORT_REQ (1<<1) +#define TRANS_REQ (1<<0) +/* status register (r) +* offset:0x0008 default:0x0000_003c */ +#define BIT_ERR (0<<22) +#define FORM_ERR (1<<22) +#define STUFF_ERR (2<<22) +#define OTHER_ERR (3<<22) +#define ERR_DIR (1<<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 (1<<7) +#define ERR_STA (1<<6) +#define TRANS_BUSY (1<<5) +#define RCV_BUSY (1<<4) +#define TRANS_OVER (1<<3) +#define TBUF_RDY (1<<2) +#define DATA_ORUN (1<<1) +#define RBUF_RDY (1<<0) +/* interrupt register (r) +* offset:0x000c default:0x0000_0000 */ +#define BUS_ERR (1<<7) //write 1 to clear +#define ARB_LOST (1<<6) //write 1 to clear +#define ERR_PASSIVE (1<<5) //write 1 to clear +#define WAKEUP (1<<4) //write 1 to clear +#define DATA_ORUNI (1<<3) //write 1 to clear +#define ERR_WRN (1<<2) //write 1 to clear +#define TBUF_VLD (1<<1) //write 1 to clear +#define RBUF_VLD (1<<0) //write 1 to clear +/* interrupt enable register (r/w) +* offset:0x0010 default:0x0000_0000 */ +#define BERR_IRQ_EN (1<<7) +#define BERR_IRQ_DIS (0<<7) +#define ARB_LOST_IRQ_EN (1<<6) +#define ARB_LOST_IRQ_DIS (0<<6) +#define ERR_PASSIVE_IRQ_EN (1<<5) +#define ERR_PASSIVE_IRQ_DIS (0<<5) +#define WAKEUP_IRQ_EN (1<<4) +#define WAKEUP_IRQ_DIS (0<<4) +#define OR_IRQ_EN (1<<3) +#define OR_IRQ_DIS (0<<3) +#define ERR_WRN_IRQ_EN (1<<2) +#define ERR_WRN_IRQ_DIS (0<<2) +#define TX_IRQ_EN (1<<1) +#define TX_IRQ_DIS (0<<1) +#define RX_IRQ_EN (1<<0) +#define RX_IRQ_DIS (0<<0) +/* timing register (r/w) +* offset:0x0014 default:0x0000_0000 */ +//... + +/* output control */ +#define NOR_OMODE (2) +#define CLK_OMODE (3) +/* arbitration lost flag*/ + +/* error code */ +#define ERR_INRCV (1<<5) +#define ERR_INTRANS (0<<5) + +/* filter mode */ +#define FILTER_CLOSE 0 +#define SINGLE_FLTER_MODE 1 +#define DUAL_FILTER_MODE 2 + +/* +* Flags for sun7icanpriv.flags +*/ +#define SUNXI_CAN_CUSTOM_IRQ_HANDLER 0x1 + +#define SUNXI_CAN_MAX_IRQ 20 /* max. number of interrupts handled in ISR */ + +/* +* sun7i_can private data structure +*/ +struct sunxi_can_priv { + struct can_priv can; /* must be the first member */ + int open_time; + struct sk_buff *echo_skb; + + void *priv; /* for board-specific data */ + struct net_device *dev; + + unsigned long irq_flags; /* for request_irq() */ + spinlock_t cmdreg_lock; /* lock for concurrent cmd register writes */ + + u16 flags; /* custom mode flags */ +}; + +#endif