From patchwork Wed Dec 9 10:07:57 2009 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Barry Song <21cnbao@gmail.com> X-Patchwork-Id: 40717 X-Patchwork-Delegate: davem@davemloft.net Return-Path: X-Original-To: patchwork-incoming@ozlabs.org Delivered-To: patchwork-incoming@ozlabs.org Received: from vger.kernel.org (vger.kernel.org [209.132.176.167]) by ozlabs.org (Postfix) with ESMTP id 42F1BB7BC3 for ; Wed, 9 Dec 2009 21:08:17 +1100 (EST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1754247AbZLIKIE (ORCPT ); Wed, 9 Dec 2009 05:08:04 -0500 Received: (majordomo@vger.kernel.org) by vger.kernel.org id S1754070AbZLIKID (ORCPT ); Wed, 9 Dec 2009 05:08:03 -0500 Received: from nwd2mail11.analog.com ([137.71.25.57]:7602 "EHLO nwd2mail11.analog.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1753986AbZLIKIA (ORCPT ); Wed, 9 Dec 2009 05:08:00 -0500 X-IronPort-AV: E=Sophos;i="4.47,367,1257138000"; d="scan'208";a="10114481" Received: from nwd2hubcas2.ad.analog.com ([10.64.73.30]) by nwd2mail11.analog.com with ESMTP; 09 Dec 2009 05:08:05 -0500 Received: from sdc1.sdcdesign.analog.com (10.99.22.250) by NWD2HUBCAS2.ad.analog.com (10.64.73.30) with Microsoft SMTP Server id 8.1.358.0; Wed, 9 Dec 2009 05:08:04 -0500 Received: from localhost.localdomain ([10.99.29.104]) by sdc1.sdcdesign.analog.com (8.11.7p1+Sun/8.11.7) with ESMTP id nB9A3Nn06783; Wed, 9 Dec 2009 18:03:24 +0800 (CST) From: Barry Song <21cnbao@gmail.com> To: wg@grandegger.com, davem@davemloft.net CC: socketcan-core@lists.berlios.de, uclinux-dist-devel@blackfin.uclinux.org, netdev@vger.kernel.org, Barry Song <21cnbao@gmail.com>, "H.J. Oertel" Subject: [PATCH v2] add the driver for Analog Devices Blackfin on-chip CAN controllers Date: Wed, 9 Dec 2009 18:07:57 +0800 Message-ID: <1260353277-30902-1-git-send-email-21cnbao@gmail.com> X-Mailer: git-send-email 1.5.6.3 MIME-Version: 1.0 Sender: netdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org Signed-off-by: Barry Song <21cnbao@gmail.com> Signed-off-by: H.J. Oertel --- -v2: cleanup according to Wolfgang Grandegger's feedback 1.delete some unnecessary debug print 2.delete ndo_tx_timeout entry as it is not needed in can 3.use alloc_can_skb, alloc_can_err_skb instead of netdev_alloc_skb 4.add timeout while polling can status 5.rename BFIN_CAN_READ/WRITE_MSG to bfin_can_read/write_data 6.use kernel BIT instead of bit shift 7.use void __iomem * for CAN memory base memory instead of u32 8.delete "dev->last_rx = jiffies" since it is not needed now 9.delete redundant "echo_skb" member in bfin can private data 10.follow can convention to use "_" instead of "-" for file names drivers/net/can/Kconfig | 9 + drivers/net/can/Makefile | 1 + drivers/net/can/bfin_can.c | 786 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 796 insertions(+), 0 deletions(-) create mode 100644 drivers/net/can/bfin_can.c diff --git a/drivers/net/can/Kconfig b/drivers/net/can/Kconfig index bb803fa..8c485aa 100644 --- a/drivers/net/can/Kconfig +++ b/drivers/net/can/Kconfig @@ -54,6 +54,15 @@ config CAN_MCP251X ---help--- Driver for the Microchip MCP251x SPI CAN controllers. +config CAN_BFIN + depends on CAN_DEV && (BF534 || BF536 || BF537 || BF538 || BF539 || BF54x) + tristate "Analog Devices Blackfin on-chip CAN" + ---help--- + Driver for the Analog Devices Blackfin on-chip CAN controllers + + To compile this driver as a module, choose M here: the + module will be called bfin_can. + 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 56899fe..7a702f2 100644 --- a/drivers/net/can/Makefile +++ b/drivers/net/can/Makefile @@ -14,5 +14,6 @@ obj-$(CONFIG_CAN_MSCAN) += mscan/ obj-$(CONFIG_CAN_AT91) += at91_can.o obj-$(CONFIG_CAN_TI_HECC) += ti_hecc.o obj-$(CONFIG_CAN_MCP251X) += mcp251x.o +obj-$(CONFIG_CAN_BFIN) += bfin_can.o ccflags-$(CONFIG_CAN_DEBUG_DEVICES) := -DDEBUG diff --git a/drivers/net/can/bfin_can.c b/drivers/net/can/bfin_can.c new file mode 100644 index 0000000..36a9f8c --- /dev/null +++ b/drivers/net/can/bfin_can.c @@ -0,0 +1,786 @@ +/* + * Blackfin On-Chip CAN Driver + * + * Copyright 2004-2009 Analog Devices Inc. + * + * Enter bugs at http://blackfin.uclinux.org/ + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#define DRV_NAME "bfin_can" +#define BFIN_CAN_TIMEOUT 100 + +/* + * registers offset + */ +#define OFFSET_MB_MASK 0x100 +#define OFFSET_MASK_AML 0x0 +#define OFFSET_MASK_AMH 0x4 +#define OFFSET_MB_OBJ 0x200 +#define OFFSET_OBJ_DATA 0x0 +#define OFFSET_OBJ_DLC 0x10 +#define OFFSET_OBJ_ID0 0x18 +#define OFFSET_OBJ_ID1 0x1C +#define OFFSET_CLOCK 0x80 +#define OFFSET_TIMING 0x84 +#define OFFSET_STATUS 0x8C +#define OFFSET_CEC 0x90 +#define OFFSET_GIS 0x94 +#define OFFSET_GIM 0x98 +#define OFFSET_CONTROL 0xA0 +#define OFFSET_INTR 0xA4 +#define OFFSET_ESR 0xB4 +#define OFFSET_MBIM1 0x28 +#define OFFSET_MBIM2 0x68 +#define OFFSET_MC1 0x0 +#define OFFSET_MC2 0x40 +#define OFFSET_MD1 0x4 +#define OFFSET_MD2 0x44 +#define OFFSET_TRS2 0x48 +#define OFFSET_MBTIF1 0x20 +#define OFFSET_MBTIF2 0x60 +#define OFFSET_MBRIF1 0x24 +#define OFFSET_MBRIF2 0x64 + +/* + * transmit and receive channels + */ +#define TRANSMIT_CHL 24 +#define RECEIVE_STD_CHL 0 +#define RECEIVE_EXT_CHL 4 +#define RECEIVE_RTR_CHL 8 +#define RECEIVE_EXT_RTR_CHL 12 + +/* + * bfin can private data + */ +struct bfin_can_priv { + struct can_priv can; /* must be the first member */ + struct net_device *dev; + void __iomem *membase; + int rx_irq; + int tx_irq; + int err_irq; + unsigned short *pin_list; +}; + +/* + * read/write CAN registers and messages + */ +#define can_membase(priv) \ + ((priv)->membase) +#define can_channel_membase(priv, channel) \ + ((priv)->membase + OFFSET_MB_OBJ + ((channel) << 5)) +#define can_mask_membase(priv, channel) \ + ((priv)->membase + OFFSET_MB_MASK + ((channel) << 3)) + +#define CAN_WRITE_REG(val, addr) \ + writew((val), (addr)) + +#define CAN_READ_REG(addr) \ + readw((addr)) + +#define CAN_WRITE_CTRL(priv, off, val) \ + CAN_WRITE_REG(val, can_membase((priv)) + (off)) + +#define CAN_READ_CTRL(priv, off) \ + CAN_READ_REG(can_membase((priv)) + (off)) + +#define CAN_WRITE_AML(priv, channel, aml) \ + (CAN_WRITE_REG((aml), can_mask_membase(priv, channel) + OFFSET_MASK_AML)) + +#define CAN_WRITE_AMH(priv, channel, amh) \ + (CAN_WRITE_REG((amh), can_mask_membase(priv, channel) + OFFSET_MASK_AMH)) + +#define CAN_WRITE_DLC(priv, channel, length) \ + (CAN_WRITE_REG((length), can_channel_membase(priv, channel) + OFFSET_OBJ_DLC)) + +#define CAN_READ_DLC(priv, channel) \ + (CAN_READ_REG(can_channel_membase((priv), (channel)) + OFFSET_OBJ_DLC)) + +#define CAN_READ_OID(priv, channel) \ + ((CAN_READ_REG(can_channel_membase((priv), (channel)) + OFFSET_OBJ_ID1) & 0x1ffc) >> 2) + +#define CAN_READ_XOID(priv, channel) \ + (((CAN_READ_REG(can_channel_membase((priv), (channel)) + OFFSET_OBJ_ID1) & 0x1fff) << 16) \ + + ((CAN_READ_REG(can_channel_membase((priv), (channel)) + OFFSET_OBJ_ID0)))) + +#define CAN_READ_ID1(priv, channel) \ + (CAN_READ_REG(can_channel_membase((priv), (channel)) + OFFSET_OBJ_ID1)) + +#define CAN_WRITE_ID0(priv, channel, val) \ + CAN_WRITE_REG((val), can_channel_membase((priv), (channel)) + OFFSET_OBJ_ID0) + +#define CAN_WRITE_OID(priv, channel, id) \ + CAN_WRITE_REG(((id) << 2) | AME, can_channel_membase((priv), (channel)) + OFFSET_OBJ_ID1) + +#define CAN_WRITE_XOID(priv, channel, id) \ + do { \ + CAN_WRITE_REG((id), can_channel_membase((priv), (channel)) + OFFSET_OBJ_ID0); \ + CAN_WRITE_REG((((id) & 0x1FFF0000) >> 16) + IDE + AME, \ + can_channel_membase((priv), (channel)) + OFFSET_OBJ_ID1); \ + } while (0) + +#define CAN_WRITE_OID_RTR(priv, channel, id) \ + CAN_WRITE_REG(((id) << 2) | RTR | AME, can_channel_membase((priv), (channel)) + OFFSET_OBJ_ID1) + +#define CAN_WRITE_XOID_RTR(priv, channel, id) \ + do { \ + CAN_WRITE_REG((id), can_channel_membase((priv), (channel)) + OFFSET_OBJ_ID0); \ + CAN_WRITE_REG((((id) & 0x1FFF0000) >> 16) + IDE + RTR + AME, \ + can_channel_membase((priv), (channel)) + OFFSET_OBJ_ID1); \ + } while (0) + +static struct can_bittiming_const bfin_can_bittiming_const = { + .name = DRV_NAME, + .tseg1_min = 1, + .tseg1_max = 16, + .tseg2_min = 1, + .tseg2_max = 8, + .sjw_max = 4, + /* Although the BRP field can be set to any value, it is recommended + * that the value be greater than or equal to 4, as restrictions + * apply to the bit timing configuration when BRP is less than 4. + */ + .brp_min = 4, + .brp_max = 1024, + .brp_inc = 1, +}; + +static int bfin_can_set_bittiming(struct net_device *dev) +{ + struct bfin_can_priv *priv = netdev_priv(dev); + struct can_bittiming *bt = &priv->can.bittiming; + u16 clk, timing; + + clk = bt->brp - 1; + timing = ((bt->sjw - 1) << 8) | (bt->prop_seg + bt->phase_seg1 - 1) | + ((bt->phase_seg2 - 1) << 4); + + /* + * If the SAM bit is set, the input signal is oversampled three times at the SCLK rate. + */ + if (priv->can.ctrlmode & CAN_CTRLMODE_3_SAMPLES) + timing |= SAM; + + CAN_WRITE_CTRL(priv, OFFSET_CLOCK, clk); + CAN_WRITE_CTRL(priv, OFFSET_TIMING, timing); + + dev_info(dev->dev.parent, "setting CLOCK=0x%04x TIMING=0x%04x\n", clk, timing); + return 0; +} + +static void bfin_can_set_reset_mode(struct net_device *dev) +{ + struct bfin_can_priv *priv = netdev_priv(dev); + int i; + int timeout = BFIN_CAN_TIMEOUT; + + /* disable interrupts */ + CAN_WRITE_CTRL(priv, OFFSET_MBIM1, 0); + CAN_WRITE_CTRL(priv, OFFSET_MBIM2, 0); + CAN_WRITE_CTRL(priv, OFFSET_GIM, 0); + + /* reset can and enter configuration mode */ + CAN_WRITE_CTRL(priv, OFFSET_CONTROL, SRS | CCR); + SSYNC(); + CAN_WRITE_CTRL(priv, OFFSET_CONTROL, CCR); + SSYNC(); + while (!(CAN_READ_CTRL(priv, OFFSET_CONTROL) & CCA)) { + udelay(10); + if (--timeout == 0) { + dev_err(dev->dev.parent, "fail to enter configuration mode\n"); + BUG(); + } + } + + /* + * All mailbox configurations are marked as inactive + * by writing to CAN Mailbox Configuration Registers 1 and 2 + * For all bits: 0 - Mailbox disabled, 1 - Mailbox enabled + */ + CAN_WRITE_CTRL(priv, OFFSET_MC1, 0); + CAN_WRITE_CTRL(priv, OFFSET_MC2, 0); + + /* Set Mailbox Direction */ + CAN_WRITE_CTRL(priv, OFFSET_MD1, 0xFFFF); /* mailbox 1-16 are RX */ + CAN_WRITE_CTRL(priv, OFFSET_MD2, 0); /* mailbox 17-32 are TX */ + + /* RECEIVE_STD_CHL */ + for (i = 0; i < 2; i++) { + CAN_WRITE_OID(priv, RECEIVE_STD_CHL + i, 0); + CAN_WRITE_ID0(priv, RECEIVE_STD_CHL, 0); + CAN_WRITE_DLC(priv, RECEIVE_STD_CHL + i, 0); + CAN_WRITE_AMH(priv, RECEIVE_STD_CHL + i, 0x1FFF); + CAN_WRITE_AML(priv, RECEIVE_STD_CHL + i, 0xFFFF); + } + + /* RECEIVE_EXT_CHL */ + for (i = 0; i < 2; i++) { + CAN_WRITE_XOID(priv, RECEIVE_EXT_CHL + i, 0); + CAN_WRITE_DLC(priv, RECEIVE_EXT_CHL + i, 0); + CAN_WRITE_AMH(priv, RECEIVE_EXT_CHL + i, 0x1FFF); + CAN_WRITE_AML(priv, RECEIVE_EXT_CHL + i, 0xFFFF); + } + + CAN_WRITE_CTRL(priv, OFFSET_MC2, BIT(TRANSMIT_CHL - 16)); + CAN_WRITE_CTRL(priv, OFFSET_MC1, BIT(RECEIVE_STD_CHL) + BIT(RECEIVE_EXT_CHL)); + SSYNC(); + + priv->can.state = CAN_STATE_STOPPED; +} + +static void bfin_can_set_normal_mode(struct net_device *dev) +{ + struct bfin_can_priv *priv = netdev_priv(dev); + int timeout = BFIN_CAN_TIMEOUT; + + /* + * leave configuration mode + */ + CAN_WRITE_CTRL(priv, OFFSET_CONTROL, CAN_READ_CTRL(priv, OFFSET_CONTROL) & ~CCR); + + while (CAN_READ_CTRL(priv, OFFSET_STATUS) & CCA) { + udelay(10); + if (--timeout == 0) { + dev_err(dev->dev.parent, "fail to leave configuration mode\n"); + BUG(); + } + } + + /* + * clear _All_ tx and rx interrupts + */ + CAN_WRITE_CTRL(priv, OFFSET_MBTIF1, 0xFFFF); + CAN_WRITE_CTRL(priv, OFFSET_MBTIF2, 0xFFFF); + CAN_WRITE_CTRL(priv, OFFSET_MBRIF1, 0xFFFF); + CAN_WRITE_CTRL(priv, OFFSET_MBRIF2, 0xFFFF); + + /* + * clear global interrupt status register + */ + CAN_WRITE_CTRL(priv, OFFSET_GIS, 0x7FF); /* overwrites with '1' */ + + /* + * Initialize Interrupts + * - set bits in the mailbox interrupt mask register + * - global interrupt mask + */ + CAN_WRITE_CTRL(priv, OFFSET_MBIM1, BIT(RECEIVE_STD_CHL) + BIT(RECEIVE_EXT_CHL)); + CAN_WRITE_CTRL(priv, OFFSET_MBIM2, BIT(TRANSMIT_CHL - 16)); + + CAN_WRITE_CTRL(priv, OFFSET_GIM, EPIM | BOIM | RMLIM); + SSYNC(); +} + +static inline void bfin_can_write_data(struct bfin_can_priv *priv, int channel, u8 *data, int dlc) +{ + int i; + u16 val; + + for (i = 0; i < 8; i += 2) { + val = ((7 - i) < dlc ? (data[7 - i]) : 0) + + ((6 - i) < dlc ? (data[6 - i] << 8) : 0); + CAN_WRITE_REG(val, can_channel_membase((priv), (channel)) + OFFSET_OBJ_DATA + (i << 1)); + } +} + +static inline void bfin_can_read_data(struct bfin_can_priv *priv, int channel, u8 *data, int dlc) +{ + int i; + u16 val; + + for (i = 0; i < 8; i += 2) { + val = CAN_READ_REG(can_channel_membase((priv), (channel)) + OFFSET_OBJ_DATA + (i << 1)); + data[7 - i] = (7 - i) < dlc ? val : 0; + data[6 - i] = (6 - i) < dlc ? (val >> 8) : 0; + } +} + +static void bfin_can_start(struct net_device *dev) +{ + struct bfin_can_priv *priv = netdev_priv(dev); + + /* leave reset mode */ + if (priv->can.state != CAN_STATE_STOPPED) + bfin_can_set_reset_mode(dev); + + /* leave reset mode */ + bfin_can_set_normal_mode(dev); +} + +static int bfin_can_set_mode(struct net_device *dev, enum can_mode mode) +{ + switch (mode) { + case CAN_MODE_START: + bfin_can_start(dev); + if (netif_queue_stopped(dev)) + netif_wake_queue(dev); + break; + + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static int bfin_can_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct bfin_can_priv *priv = netdev_priv(dev); + struct can_frame *cf = (struct can_frame *)skb->data; + u8 dlc; + canid_t id; + + netif_stop_queue(dev); + + dlc = cf->can_dlc; + id = cf->can_id; + + /* fill id and data length code */ + if (id & CAN_EFF_FLAG) { + if (id & CAN_RTR_FLAG) + CAN_WRITE_XOID_RTR(priv, TRANSMIT_CHL, id); + else + CAN_WRITE_XOID(priv, TRANSMIT_CHL, id); + } else { + if (id & CAN_RTR_FLAG) + CAN_WRITE_OID_RTR(priv, TRANSMIT_CHL, id); + else + CAN_WRITE_OID(priv, TRANSMIT_CHL, id); + } + + bfin_can_write_data(priv, TRANSMIT_CHL, cf->data, dlc); + + CAN_WRITE_DLC(priv, TRANSMIT_CHL, dlc); + + dev->trans_start = jiffies; + + can_put_echo_skb(skb, dev, 0); + + /* set transmit request */ + CAN_WRITE_CTRL(priv, OFFSET_TRS2, BIT(TRANSMIT_CHL - 16)); + return 0; +} + +static void bfin_can_rx(struct net_device *dev, uint16_t isrc) +{ + struct bfin_can_priv *priv = netdev_priv(dev); + struct net_device_stats *stats = &dev->stats; + struct can_frame *cf; + struct sk_buff *skb; + canid_t id; + uint8_t dlc; + int obj; + + skb = alloc_can_skb(dev, &cf); + if (skb == NULL) + return; + + /* get id and data length code */ + if (isrc & BIT(RECEIVE_EXT_CHL)) { + /* extended frame format (EFF) */ + id = CAN_READ_XOID(priv, RECEIVE_EXT_CHL); + id |= CAN_EFF_FLAG; + obj = RECEIVE_EXT_CHL; + } else { + /* standard frame format (SFF) */ + id = CAN_READ_OID(priv, RECEIVE_STD_CHL); + obj = RECEIVE_STD_CHL; + } + if (CAN_READ_ID1(priv, obj) & RTR) + id |= CAN_RTR_FLAG; + dlc = CAN_READ_DLC(priv, obj); + + cf->can_id = id; + cf->can_dlc = dlc; + + bfin_can_read_data(priv, obj, cf->data, dlc); + + netif_rx(skb); + + stats->rx_packets++; + stats->rx_bytes += dlc; +} + +static int bfin_can_err(struct net_device *dev, uint16_t isrc, uint16_t status) +{ + struct bfin_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; + + skb = alloc_can_err_skb(dev, &cf); + if (skb == NULL) + return -ENOMEM; + + if (isrc & RMLIS) { + /* data overrun interrupt */ + dev_dbg(dev->dev.parent, "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++; + } + + if (isrc & BOIS) { + dev_dbg(dev->dev.parent, "bus-off mode interrupt\n"); + + state = CAN_STATE_BUS_OFF; + cf->can_id |= CAN_ERR_BUSOFF; + can_bus_off(dev); + } + + if (isrc & EPIS) { + /* error passive interrupt */ + dev_dbg(dev->dev.parent, "error passive interrupt\n"); + state = CAN_STATE_ERROR_PASSIVE; + } + + if ((isrc & EWTIS) || (isrc & EWRIS)) { + dev_dbg(dev->dev.parent, "Error Warning Transmit/Receive Interrupt\n"); + state = CAN_STATE_ERROR_WARNING; + } + + if (state != priv->can.state && (state == CAN_STATE_ERROR_WARNING || + state == CAN_STATE_ERROR_PASSIVE)) { + uint16_t cec = CAN_READ_CTRL(priv, OFFSET_CEC); + uint8_t rxerr = cec; + uint8_t txerr = cec >> 8; + 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; + } + } + + if (status) { + priv->can.can_stats.bus_error++; + + cf->can_id |= CAN_ERR_PROT | CAN_ERR_BUSERROR; + + if (status & BEF) + cf->data[2] |= CAN_ERR_PROT_BIT; + else if (status & FER) + cf->data[2] |= CAN_ERR_PROT_FORM; + else if (status & SER) + cf->data[2] |= CAN_ERR_PROT_STUFF; + else + cf->data[2] |= CAN_ERR_PROT_UNSPEC; + } + + priv->can.state = state; + + netif_rx(skb); + + stats->rx_packets++; + stats->rx_bytes += cf->can_dlc; + + return 0; +} + +irqreturn_t bfin_can_interrupt(int irq, void *dev_id) +{ + struct net_device *dev = dev_id; + struct bfin_can_priv *priv = netdev_priv(dev); + struct net_device_stats *stats = &dev->stats; + uint16_t status, isrc; + + if ((irq == priv->tx_irq) && CAN_READ_CTRL(priv, OFFSET_MBTIF2)) { + /* transmission complete interrupt */ + CAN_WRITE_CTRL(priv, OFFSET_MBTIF2, 0xFFFF); + stats->tx_packets++; + stats->tx_bytes += CAN_READ_DLC(priv, TRANSMIT_CHL); + can_get_echo_skb(dev, 0); + netif_wake_queue(dev); + } else if ((irq == priv->rx_irq) && CAN_READ_CTRL(priv, OFFSET_MBRIF1)) { + /* receive interrupt */ + isrc = CAN_READ_CTRL(priv, OFFSET_MBRIF1); + CAN_WRITE_CTRL(priv, OFFSET_MBRIF1, 0xFFFF); + bfin_can_rx(dev, isrc); + } else if ((irq == priv->err_irq) && CAN_READ_CTRL(priv, OFFSET_GIS)) { + /* error interrupt */ + isrc = CAN_READ_CTRL(priv, OFFSET_GIS); + status = CAN_READ_CTRL(priv, OFFSET_ESR); + CAN_WRITE_CTRL(priv, OFFSET_GIS, 0x7FF); + bfin_can_err(dev, isrc, status); + } else + return IRQ_NONE; + + return IRQ_HANDLED; +} + +static int bfin_can_open(struct net_device *dev) +{ + int err; + + /* set chip into reset mode */ + bfin_can_set_reset_mode(dev); + + /* common open */ + err = open_candev(dev); + if (err) + return err; + + /* init and start chi */ + bfin_can_start(dev); + + netif_start_queue(dev); + + return 0; +} + +static int bfin_can_close(struct net_device *dev) +{ + netif_stop_queue(dev); + bfin_can_set_reset_mode(dev); + + close_candev(dev); + + return 0; +} + +struct net_device *alloc_bfin_candev(void) +{ + struct net_device *dev; + struct bfin_can_priv *priv; + + dev = alloc_candev(sizeof(*priv)); + if (!dev) + return NULL; + + priv = netdev_priv(dev); + + priv->dev = dev; + priv->can.bittiming_const = &bfin_can_bittiming_const; + priv->can.do_set_bittiming = bfin_can_set_bittiming; + priv->can.do_set_mode = bfin_can_set_mode; + + return dev; +} + +static const struct net_device_ops bfin_can_netdev_ops = { + .ndo_open = bfin_can_open, + .ndo_stop = bfin_can_close, + .ndo_start_xmit = bfin_can_start_xmit, +}; + +static int __devinit bfin_can_probe(struct platform_device *pdev) +{ + int err; + struct net_device *dev; + struct bfin_can_priv *priv; + struct resource *res_mem, *rx_irq, *tx_irq, *err_irq; + unsigned short *pdata; + + pdata = pdev->dev.platform_data; + if (!pdata) { + dev_err(&pdev->dev, "No platform data provided!\n"); + err = -EINVAL; + goto exit; + } + + res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + rx_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + tx_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 1); + err_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 2); + if (!res_mem || !rx_irq || !tx_irq || !err_irq) { + err = -EINVAL; + goto exit; + } + + if (!request_mem_region(res_mem->start, resource_size(res_mem), + dev_name(&pdev->dev))) { + err = -EBUSY; + goto exit; + } + + /* request peripheral pins */ + err = peripheral_request_list(pdata, dev_name(&pdev->dev)); + if (err) + goto exit_mem_release; + + dev = alloc_bfin_candev(); + if (!dev) { + err = -ENOMEM; + goto exit_peri_pin_free; + } + + /* register interrupt handler */ + err = request_irq(rx_irq->start, &bfin_can_interrupt, 0, + "bfin-can-rx", (void *)dev); + if (err) + goto exit_candev_free; + err = request_irq(tx_irq->start, &bfin_can_interrupt, 0, + "bfin-can-tx", (void *)dev); + if (err) + goto exit_rx_irq_free; + err = request_irq(err_irq->start, &bfin_can_interrupt, 0, + "bfin-can-err", (void *)dev); + if (err) + goto exit_tx_irq_free; + + priv = netdev_priv(dev); + priv->membase = (void __iomem *)res_mem->start; + priv->rx_irq = rx_irq->start; + priv->tx_irq = tx_irq->start; + priv->err_irq = err_irq->start; + priv->pin_list = pdata; + priv->can.clock.freq = get_sclk(); + + dev_set_drvdata(&pdev->dev, dev); + SET_NETDEV_DEV(dev, &pdev->dev); + + dev->flags |= IFF_ECHO; /* we support local echo */ + dev->netdev_ops = &bfin_can_netdev_ops; + + bfin_can_set_reset_mode(dev); + + err = register_candev(dev); + if (err) { + dev_err(&pdev->dev, "registering failed (err=%d)\n", err); + goto exit_err_irq_free; + } + + dev_info(&pdev->dev, "%s device registered (reg_base=%p, rx_irq=%d, tx_irq=%d, err_irq=%d, sclk=%d)\n", + DRV_NAME, (void *)priv->membase, priv->rx_irq, priv->tx_irq, priv->err_irq, + priv->can.clock.freq); + return 0; + +exit_err_irq_free: + free_irq(err_irq->start, dev); +exit_tx_irq_free: + free_irq(tx_irq->start, dev); +exit_rx_irq_free: + free_irq(rx_irq->start, dev); +exit_candev_free: + free_candev(dev); +exit_peri_pin_free: + peripheral_free_list(pdata); +exit_mem_release: + release_mem_region(res_mem->start, resource_size(res_mem)); +exit: + return err; +} + +static int __devexit bfin_can_remove(struct platform_device *pdev) +{ + struct net_device *dev = dev_get_drvdata(&pdev->dev); + struct bfin_can_priv *priv = netdev_priv(dev); + struct resource *res; + + bfin_can_set_reset_mode(dev); + + unregister_candev(dev); + + dev_set_drvdata(&pdev->dev, NULL); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + release_mem_region(res->start, resource_size(res)); + + free_irq(priv->rx_irq, dev); + free_irq(priv->tx_irq, dev); + free_irq(priv->err_irq, dev); + peripheral_free_list(priv->pin_list); + + free_candev(dev); + return 0; +} + +#ifdef CONFIG_PM +static int bfin_can_suspend(struct platform_device *pdev, pm_message_t mesg) +{ + struct net_device *dev = dev_get_drvdata(&pdev->dev); + struct bfin_can_priv *priv = netdev_priv(dev); + int timeout = BFIN_CAN_TIMEOUT; + + if (netif_running(dev)) { + /* enter sleep mode */ + CAN_WRITE_CTRL(priv, OFFSET_CONTROL, + CAN_READ_CTRL(priv, OFFSET_CONTROL) | SMR); + SSYNC(); + while (!(CAN_READ_CTRL(priv, OFFSET_INTR) & SMACK)) { + udelay(10); + if (--timeout == 0) { + dev_err(dev->dev.parent, "fail to enter sleep mode\n"); + BUG(); + } + } + } + + return 0; +} + +static int bfin_can_resume(struct platform_device *pdev) +{ + struct net_device *dev = dev_get_drvdata(&pdev->dev); + struct bfin_can_priv *priv = netdev_priv(dev); + + if (netif_running(dev)) { + /* leave sleep mode */ + CAN_WRITE_CTRL(priv, OFFSET_INTR, 0); + SSYNC(); + } + + return 0; +} +#else +#define bfin_can_suspend NULL +#define bfin_can_resume NULL +#endif /* CONFIG_PM */ + +static struct platform_driver bfin_can_driver = { + .probe = bfin_can_probe, + .remove = __devexit_p(bfin_can_remove), + .suspend = bfin_can_suspend, + .resume = bfin_can_resume, + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init bfin_can_init(void) +{ + return platform_driver_register(&bfin_can_driver); +} + +module_init(bfin_can_init); + +static void __exit bfin_can_exit(void) +{ + platform_driver_unregister(&bfin_can_driver); +} + +module_exit(bfin_can_exit); + +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Blackfin on-chip CAN netdevice driver"); +