Driver for Microchip MCP2515 SPI CAN controller. Uses the spi_async API directly for improved performance.
Signed-off-by: Andre B. Oliveira <[email protected]> --- drivers/net/can/Kconfig | 10 + drivers/net/can/Makefile | 1 + drivers/net/can/mcp2515.c | 803 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 814 insertions(+), 0 deletions(-) create mode 100644 drivers/net/can/mcp2515.c diff --git a/drivers/net/can/Kconfig b/drivers/net/can/Kconfig index 9d9e453..4908bfc 100644 --- a/drivers/net/can/Kconfig +++ b/drivers/net/can/Kconfig @@ -48,6 +48,16 @@ config CAN_TI_HECC Driver for TI HECC (High End CAN Controller) module found on many TI devices. The device specifications are available from www.ti.com +config CAN_MCP2515 + tristate "Microchip MCP2515 SPI CAN controller" + depends on SPI + select CAN_DEV + help + Driver for the Microchip MCP2515 SPI CAN controllers. + This is a new driver that uses the asynchronous SPI API directly, + eliminating the overhead of threads, work queues and synchronous SPI + wrappers, thus reducing SPI transactions latency to a minimum. + config CAN_MCP251X tristate "Microchip MCP251x SPI CAN controllers" depends on CAN_DEV && SPI && HAS_DMA diff --git a/drivers/net/can/Makefile b/drivers/net/can/Makefile index 0057537..679b103 100644 --- a/drivers/net/can/Makefile +++ b/drivers/net/can/Makefile @@ -13,6 +13,7 @@ obj-$(CONFIG_CAN_SJA1000) += sja1000/ obj-$(CONFIG_CAN_MSCAN) += mscan/ obj-$(CONFIG_CAN_AT91) += at91_can.o obj-$(CONFIG_CAN_TI_HECC) += ti_hecc.o +obj-$(CONFIG_CAN_MCP2515) += mcp2515.o obj-$(CONFIG_CAN_MCP251X) += mcp251x.o obj-$(CONFIG_CAN_BFIN) += bfin_can.o obj-$(CONFIG_CAN_JANZ_ICAN3) += janz-ican3.o diff --git a/drivers/net/can/mcp2515.c b/drivers/net/can/mcp2515.c new file mode 100644 index 0000000..746171b --- /dev/null +++ b/drivers/net/can/mcp2515.c @@ -0,0 +1,803 @@ +/* + * mcp2515.c: driver for Microchip MCP2515 SPI CAN controller + * + * Copyright 2010 Andre B. Oliveira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * 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/>. + */ + +/* + * Example of mcp2515 platform spi_board_info definition: + * + * static struct mcp251x_platform_data mcp251x_info = { + * .oscillator_frequency = 8000000, + * .model = CAN_MCP251X_MCP2515, + * .board_specific_setup = mcp251x_setup, + * .power_enable = mcp251x_power_enable, + * .transceiver_enable = NULL, + * }; + * + * static struct spi_board_info spi_board_info[] = { + * { + * .modalias = "mcp2515", + * .bus_num = 2, + * .chip_select = 0, + * .irq = IRQ_GPIO(28), + * .max_speed_hz = 10000000, + * .platform_data = &mcp251x_info, + * }, + * }; + */ + +/* + * References: Microchip MCP2515 data sheet, DS21801E, 2007. + */ + +#include <linux/dma-mapping.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/netdevice.h> +#include <linux/skbuff.h> +#include <linux/spi/spi.h> +#include <linux/spinlock.h> +#include <linux/can.h> +#include <linux/can/dev.h> +#include <linux/can/platform/mcp251x.h> + +MODULE_DESCRIPTION("Driver for Microchip MCP2515 SPI CAN controller"); +MODULE_AUTHOR("Andre B. Oliveira <[email protected]>"); +MODULE_LICENSE("GPL"); + +/* Registers */ +#define CANCTRL 0x0f +#define RXB0CTRL 0x60 +#define RXB1CTRL 0x70 + +/* RXBnCTRL bits */ +#define RXBCTRL_RXM1 0x40 +#define RXBCTRL_RXM0 0x20 +#define RXBCTRL_BUKT 0x04 + +/* RXBnSIDL bits */ +#define RXBSIDL_SRR 0x10 +#define RXBSIDL_IDE 0x08 + +/* RXBnDLC bits */ +#define RXBDLC_RTR 0x40 + +/* CANINTF bits */ +#define CANINTF_ERRIF 0x20 +#define CANINTF_TX0IF 0x04 +#define CANINTF_RX1IF 0x02 +#define CANINTF_RX0IF 0x01 + +/* EFLG bits */ +#define EFLG_RX1OVR 0x80 +#define EFLG_RX0OVR 0x40 + +/* Network device private data */ +struct mcp2515_priv { + struct can_priv can; /* must be first for all CAN network devices */ + struct spi_device *spi; /* SPI device */ + + u8 canintf; /* last read value of CANINTF register */ + u8 eflg; /* last read value of EFLG register */ + + struct sk_buff *skb; /* skb to transmit or currently transmitting */ + + spinlock_t lock; /* Lock for the following flags: */ + unsigned busy:1; /* set when pending async spi transaction */ + unsigned interrupt:1; /* set when pending interrupt handling */ + unsigned transmit:1; /* set when pending transmission */ + + /* Message, transfer and buffers for one async spi transaction */ + struct spi_message message; + struct spi_transfer transfer; + u8 rx_buf[14] __attribute__((aligned(8))); + u8 tx_buf[14] __attribute__((aligned(8))); +}; + +static struct can_bittiming_const mcp2515_bittiming_const = { + .name = "mcp2515", + .tseg1_min = 2, + .tseg1_max = 16, + .tseg2_min = 2, + .tseg2_max = 8, + .sjw_max = 4, + .brp_min = 1, + .brp_max = 64, + .brp_inc = 1, +}; + +/* + * SPI asynchronous completion callback functions. + */ +static void mcp2515_read_flags_complete(void *context); +static void mcp2515_read_rxb0_complete(void *context); +static void mcp2515_read_rxb1_complete(void *context); +static void mcp2515_clear_canintf_complete(void *context); +static void mcp2515_clear_eflg_complete(void *context); +static void mcp2515_load_txb0_complete(void *context); +static void mcp2515_rts_txb0_complete(void *context); + +/* + * Write VALUE to register at address ADDR. + * Synchronous. + */ +static int mcp2515_write(struct spi_device *spi, unsigned addr, unsigned value) +{ + const u8 buf[3] __attribute__((aligned(8))) = { + [0] = 2, /* write instruction */ + [1] = addr, /* address */ + [2] = value, /* data */ + }; + + return spi_write(spi, buf, sizeof(buf)); +} + +/* + * Reset internal registers to default state and enter configuration mode. + * Synchronous. + */ +static int mcp2515_reset(struct spi_device *spi) +{ + const u8 reset = 0xc0; + + return spi_write(spi, &reset, sizeof(reset)); +} + +/* + * Set the bit timing configuration registers, the interrupt enable register + * and the receive buffers control registers. + * Synchronous. + */ +static int mcp2515_config(struct net_device *dev) +{ + struct mcp2515_priv *priv = netdev_priv(dev); + struct spi_device *spi = priv->spi; + struct can_bittiming *bt = &priv->can.bittiming; + u8 buf[6] __attribute__((aligned(8))); + int err; + + buf[0] = 2; /* write instruction */ + buf[1] = 0x28; /* address of CNF3 */ + + /* CNF3 */ + buf[2] = bt->phase_seg2 - 1; + + /* CNF2 */ + buf[3] = (priv->can.ctrlmode & CAN_CTRLMODE_3_SAMPLES ? 0xc0 : 0x80) | + (bt->phase_seg1 - 1) << 3 | (bt->prop_seg - 1); + + /* CNF1 */ + buf[4] = (bt->sjw - 1) << 6 | (bt->brp - 1); + + /* CANINTE */ + buf[5] = ~0; /* enable all interrupts */ + + err = spi_write(spi, buf, sizeof(buf)); + if (err) + return err; + + err = mcp2515_write(spi, RXB0CTRL, + RXBCTRL_RXM1 | RXBCTRL_RXM0 | RXBCTRL_BUKT); + if (err) + return err; + + err = mcp2515_write(spi, RXB1CTRL, RXBCTRL_RXM1 | RXBCTRL_RXM0); + if (err) + return err; + + /* Finally, enter normal operation mode. */ + err = mcp2515_write(spi, CANCTRL, 0); + if (err) + return err; + + netdev_info(dev, "writing CNF: 0x%02x 0x%02x 0x%02x\n", + buf[4], buf[3], buf[2]); + + return 0; +} + +/* + * Start an asynchronous SPI transaction. + */ +static void mcp2515_spi_async(struct net_device *dev) +{ + struct mcp2515_priv *priv = netdev_priv(dev); + int err; + + err = spi_async(priv->spi, &priv->message); + if (err) + netdev_err(dev, "%s failed with err=%d\n", __func__, err); +} + +/* + * Read CANINTF and EFLG registers in one shot. + * Asynchronous. + */ +static void mcp2515_read_flags(struct net_device *dev) +{ + struct mcp2515_priv *priv = netdev_priv(dev); + u8 *buf = (u8 *)priv->transfer.tx_buf; + + buf[0] = 3; /* read instruction */ + buf[1] = 0x2c; /* address of CANINTF */ + buf[2] = 0; /* CANINTF */ + buf[3] = 0; /* EFLG */ + priv->transfer.len = 4; + priv->message.complete = mcp2515_read_flags_complete; + + mcp2515_spi_async(dev); +} + +/* + * Read receive buffer 0 (instruction 0x90) or 1 (instruction 0x94). + * Asynchronous. + */ +static void mcp2515_read_rxb(struct net_device *dev, u8 instruction, + void (*complete)(void *)) +{ + struct mcp2515_priv *priv = netdev_priv(dev); + u8 *buf = (u8 *)priv->transfer.tx_buf; + + memset(buf, 0, 14); + buf[0] = instruction; + priv->transfer.len = 14; /* instruction + id(4) + dlc + data(8) */ + priv->message.complete = complete; + + mcp2515_spi_async(dev); +} + +/* + * Read receive buffer 0. + * Asynchronous. + */ +static void mcp2515_read_rxb0(struct net_device *dev) +{ + mcp2515_read_rxb(dev, 0x90, mcp2515_read_rxb0_complete); +} + +/* + * Read receive buffer 1. + * Asynchronous. + */ +static void mcp2515_read_rxb1(struct net_device *dev) +{ + mcp2515_read_rxb(dev, 0x94, mcp2515_read_rxb1_complete); +} + +/* + * Clear CANINTF bits. + * Asynchronous. + */ +static void mcp2515_clear_canintf(struct net_device *dev) +{ + struct mcp2515_priv *priv = netdev_priv(dev); + u8 *buf = (u8 *)priv->transfer.tx_buf; + + buf[0] = 5; /* bit modify instruction */ + buf[1] = 0x2c; /* address of CANINTF */ + buf[2] = priv->canintf & ~(CANINTF_RX0IF | CANINTF_RX1IF); /* mask */ + buf[3] = 0; /* data */ + priv->transfer.len = 4; + priv->message.complete = mcp2515_clear_canintf_complete; + + mcp2515_spi_async(dev); +} + +/* + * Clear EFLG bits. + * Asynchronous. + */ +static void mcp2515_clear_eflg(struct net_device *dev) +{ + struct mcp2515_priv *priv = netdev_priv(dev); + u8 *buf = (u8 *)priv->transfer.tx_buf; + + buf[0] = 5; /* bit modify instruction */ + buf[1] = 0x2d; /* address of EFLG */ + buf[2] = priv->eflg; /* mask */ + buf[3] = 0; /* data */ + priv->transfer.len = 4; + priv->message.complete = mcp2515_clear_eflg_complete; + + mcp2515_spi_async(dev); +} + +/* + * Set the transmit buffer, starting at TXB0SIDH, for an skb. + */ +static int mcp2515_set_txbuf(u8 *buf, const struct sk_buff *skb) +{ + struct can_frame *frame = (struct can_frame *)skb->data; + + if (frame->can_id & CAN_EFF_FLAG) { + buf[0] = frame->can_id >> 21; + buf[1] = (frame->can_id >> 13 & 0xe0) | 8 | + (frame->can_id >> 16 & 3); + buf[2] = frame->can_id >> 8; + buf[3] = frame->can_id; + } else { + buf[0] = frame->can_id >> 3; + buf[1] = frame->can_id << 5; + buf[2] = 0; + buf[3] = 0; + } + + if (frame->can_id & CAN_RTR_FLAG) + buf[4] = frame->can_dlc | 0x40; + else + buf[4] = frame->can_dlc; + + memcpy(buf + 5, frame->data, frame->can_dlc); + + return 5 + frame->can_dlc; +} + +/* + * Send the "load transmit buffer 0" SPI message. + * Asynchronous. + */ +static void mcp2515_load_txb0(struct sk_buff *skb, struct net_device *dev) +{ + struct mcp2515_priv *priv = netdev_priv(dev); + u8 *buf = (u8 *)priv->transfer.tx_buf; + + buf[0] = 0x40; /* load txb0 instruction */ + priv->transfer.len = mcp2515_set_txbuf(buf + 1, skb) + 1; + priv->message.complete = mcp2515_load_txb0_complete; + + mcp2515_spi_async(dev); +} + +/* + * Send the "request to send transmit buffer 0" SPI message. + * Asynchronous. + */ +static void mcp2515_rts_txb0(struct net_device *dev) +{ + struct mcp2515_priv *priv = netdev_priv(dev); + u8 *buf = (u8 *)priv->transfer.tx_buf; + + buf[0] = 0x81; /* request to send txb0 instruction */ + priv->transfer.len = 1; + priv->message.complete = mcp2515_rts_txb0_complete; + + mcp2515_spi_async(dev); +} + +/* + * Called when the "read CANINTF and EFLG registers" SPI message completes. + */ +static void mcp2515_read_flags_complete(void *context) +{ + struct net_device *dev = context; + struct mcp2515_priv *priv = netdev_priv(dev); + u8 *buf = priv->transfer.rx_buf; + unsigned canintf; + unsigned long flags; + + priv->canintf = canintf = buf[2]; + priv->eflg = buf[3]; + + if (canintf & CANINTF_RX0IF) + mcp2515_read_rxb0(dev); + else if (canintf & CANINTF_RX1IF) + mcp2515_read_rxb1(dev); + else if (canintf) + mcp2515_clear_canintf(dev); + else { + spin_lock_irqsave(&priv->lock, flags); + if (priv->transmit) { + priv->transmit = 0; + spin_unlock_irqrestore(&priv->lock, flags); + mcp2515_load_txb0(priv->skb, dev); + } else if (priv->interrupt) { + priv->interrupt = 0; + spin_unlock_irqrestore(&priv->lock, flags); + mcp2515_read_flags(dev); + } else { + priv->busy = 0; + spin_unlock_irqrestore(&priv->lock, flags); + } + } +} + +/* + * Called when one of the "read receive buffer i" SPI message completes. + */ +static void mcp2515_read_rxb_complete(void *context) +{ + struct net_device *dev = context; + struct mcp2515_priv *priv = netdev_priv(dev); + struct sk_buff *skb; + struct can_frame *frame; + u8 *buf = priv->transfer.rx_buf; + + skb = alloc_can_skb(dev, &frame); + if (!skb) { + dev->stats.rx_dropped++; + return; + } + + if (buf[2] & RXBSIDL_IDE) { + frame->can_id = buf[1] << 21 | (buf[2] & 0xe0) << 13 | + (buf[2] & 3) << 16 | buf[3] << 8 | buf[4] | + CAN_EFF_FLAG; + if (buf[5] & RXBDLC_RTR) + frame->can_id |= CAN_RTR_FLAG; + } else { + frame->can_id = buf[1] << 3 | buf[2] >> 5; + if (buf[2] & RXBSIDL_SRR) + frame->can_id |= CAN_RTR_FLAG; + } + + frame->can_dlc = get_can_dlc(buf[5] & 0xf); + + memcpy(frame->data, buf + 6, frame->can_dlc); + + dev->stats.rx_packets++; + dev->stats.rx_bytes += frame->can_dlc; + + netif_rx(skb); +} + +/* + * Transmit a frame if transmission pending, else read and process flags. + */ +static void mcp2515_transmit_or_read_flags(struct net_device *dev) +{ + struct mcp2515_priv *priv = netdev_priv(dev); + unsigned long flags; + + spin_lock_irqsave(&priv->lock, flags); + if (priv->transmit) { + priv->transmit = 0; + spin_unlock_irqrestore(&priv->lock, flags); + mcp2515_load_txb0(priv->skb, dev); + } else { + spin_unlock_irqrestore(&priv->lock, flags); + mcp2515_read_flags(dev); + } +} + +/* + * Called when the "read receive buffer 0" SPI message completes. + */ +static void mcp2515_read_rxb0_complete(void *context) +{ + struct net_device *dev = context; + struct mcp2515_priv *priv = netdev_priv(dev); + + mcp2515_read_rxb_complete(context); + + if (priv->canintf & CANINTF_RX1IF) + mcp2515_read_rxb1(dev); + else + mcp2515_transmit_or_read_flags(dev); +} + +/* + * Called when the "read receive buffer 1" SPI message completes. + */ +static void mcp2515_read_rxb1_complete(void *context) +{ + struct net_device *dev = context; + + mcp2515_read_rxb_complete(context); + + mcp2515_transmit_or_read_flags(dev); +} + +/* + * Called when the "clear CANINTF bits" SPI message completes. + */ +static void mcp2515_clear_canintf_complete(void *context) +{ + struct net_device *dev = context; + struct mcp2515_priv *priv = netdev_priv(dev); + + if (priv->canintf & CANINTF_TX0IF) { + struct sk_buff *skb = priv->skb; + if (skb) { + struct can_frame *f = (struct can_frame *)skb->data; + dev->stats.tx_bytes += f->can_dlc; + dev->stats.tx_packets++; + can_put_echo_skb(skb, dev, 0); + can_get_echo_skb(dev, 0); + } + priv->skb = NULL; + netif_wake_queue(dev); + } + + if (priv->eflg) + mcp2515_clear_eflg(dev); + else + mcp2515_read_flags(dev); +} + +/* + * Called when the "clear EFLG bits" SPI message completes. + */ +static void mcp2515_clear_eflg_complete(void *context) +{ + struct net_device *dev = context; + struct mcp2515_priv *priv = netdev_priv(dev); + + /* + * The receive flow chart (figure 4-3) of the data sheet (DS21801E) + * says that, if RXB0CTRL.BUKT is set (our case), the overflow + * flag that is set is EFLG.RX1OVR, when in fact it is EFLG.RX0OVR + * that is set. To be safe, we test for any one of them. + */ + if (priv->eflg & (EFLG_RX0OVR | EFLG_RX1OVR)) + dev->stats.rx_over_errors++; + + mcp2515_read_flags(dev); +} + +/* + * Called when the "load transmit buffer 0" SPI message completes. + */ +static void mcp2515_load_txb0_complete(void *context) +{ + struct net_device *dev = context; + + mcp2515_rts_txb0(dev); +} + +/* + * Called when the "request to send transmit buffer 0" SPI message completes. + */ +static void mcp2515_rts_txb0_complete(void *context) +{ + struct net_device *dev = context; + + mcp2515_read_flags(dev); +} + +/* + * Interrupt handler. + */ +static irqreturn_t mcp2515_interrupt(int irq, void *dev_id) +{ + struct net_device *dev = dev_id; + struct mcp2515_priv *priv = netdev_priv(dev); + + spin_lock(&priv->lock); + if (priv->busy) { + priv->interrupt = 1; + spin_unlock(&priv->lock); + return IRQ_HANDLED; + } + priv->busy = 1; + spin_unlock(&priv->lock); + + mcp2515_read_flags(dev); + + return IRQ_HANDLED; +} + +/* + * Transmit a frame. + */ +static netdev_tx_t mcp2515_start_xmit(struct sk_buff *skb, + struct net_device *dev) +{ + struct mcp2515_priv *priv = netdev_priv(dev); + unsigned long flags; + + if (can_dropped_invalid_skb(dev, skb)) + return NETDEV_TX_OK; + + netif_stop_queue(dev); + priv->skb = skb; + + spin_lock_irqsave(&priv->lock, flags); + if (priv->busy) { + priv->transmit = 1; + spin_unlock_irqrestore(&priv->lock, flags); + return NETDEV_TX_OK; + } + priv->busy = 1; + spin_unlock_irqrestore(&priv->lock, flags); + + mcp2515_load_txb0(skb, dev); + + return NETDEV_TX_OK; +} + +/* + * Called when the network device transitions to the up state. + */ +static int mcp2515_open(struct net_device *dev) +{ + struct mcp2515_priv *priv = netdev_priv(dev); + struct spi_device *spi = priv->spi; + int err; + + err = mcp2515_reset(spi); + if (err) + return err; + + err = open_candev(dev); + if (err) + return err; + + err = request_irq(spi->irq, mcp2515_interrupt, + IRQF_TRIGGER_FALLING, dev->name, dev); + if (err) + goto err1; + + err = mcp2515_config(dev); + if (err) + goto err2; + + netif_wake_queue(dev); + + return 0; + +err2: mcp2515_reset(spi); + free_irq(spi->irq, dev); +err1: close_candev(dev); + return err; +} + +/* + * Called when the network device transitions to the down state. + */ +static int mcp2515_stop(struct net_device *dev) +{ + struct mcp2515_priv *priv = netdev_priv(dev); + struct spi_device *spi = priv->spi; + + mcp2515_reset(spi); + close_candev(dev); + free_irq(spi->irq, dev); + + return 0; +} + +/* + * Set up SPI messages. + */ +static void mcp2515_setup_spi_messages(struct net_device *dev) +{ + struct mcp2515_priv *priv = netdev_priv(dev); + struct device *device; + void *buf; + dma_addr_t dma; + + spi_message_init(&priv->message); + priv->message.context = dev; + + /* FIXME */ + device = &priv->spi->dev; + device->coherent_dma_mask = 0xffffffff; + + buf = dma_alloc_coherent(device, 32, &dma, GFP_KERNEL); + if (buf) { + priv->transfer.tx_buf = buf; + priv->transfer.rx_buf = buf + 16; + priv->transfer.tx_dma = dma; + priv->transfer.rx_dma = dma + 16; + priv->message.is_dma_mapped = 1; + } else { + priv->transfer.tx_buf = priv->tx_buf; + priv->transfer.rx_buf = priv->rx_buf; + } + + spi_message_add_tail(&priv->transfer, &priv->message); +} + +static int mcp2515_set_mode(struct net_device *dev, enum can_mode mode) +{ + return 0; +} + +/* + * Network device operations. + */ +static const struct net_device_ops mcp2515_netdev_ops = { + .ndo_open = mcp2515_open, + .ndo_stop = mcp2515_stop, + .ndo_start_xmit = mcp2515_start_xmit, +}; + +/* + * Binds this driver to the spi device. + */ +static int __devinit mcp2515_probe(struct spi_device *spi) +{ + struct net_device *dev; + struct mcp2515_priv *priv; + struct mcp251x_platform_data *pdata = spi->dev.platform_data; + int err; + + if (!pdata) + /* Platform data is required for osc freq */ + return -ENODEV; + + err = mcp2515_reset(spi); + if (err) + return err; + + dev = alloc_candev(sizeof(struct mcp2515_priv), 1); + if (!dev) + return -ENOMEM; + + dev_set_drvdata(&spi->dev, dev); + SET_NETDEV_DEV(dev, &spi->dev); + + dev->netdev_ops = &mcp2515_netdev_ops; + dev->flags |= IFF_ECHO; + + priv = netdev_priv(dev); + priv->can.bittiming_const = &mcp2515_bittiming_const; + priv->can.do_set_mode = mcp2515_set_mode; + priv->can.clock.freq = pdata->oscillator_frequency / 2; + priv->spi = spi; + + spin_lock_init(&priv->lock); + + mcp2515_setup_spi_messages(dev); + + err = register_candev(dev); + if (err) { + free_candev(dev); + return err; + } + + netdev_info(dev, "device registered (cs=%u, irq=%d)\n", + spi->chip_select, spi->irq); + + return 0; +} + +/* + * Unbinds this driver from the spi device. + */ +static int __devexit mcp2515_remove(struct spi_device *spi) +{ + struct net_device *dev = dev_get_drvdata(&spi->dev); + + unregister_candev(dev); + dev_set_drvdata(&spi->dev, NULL); + free_candev(dev); + + return 0; +} + +static struct spi_driver mcp2515_spi_driver = { + .driver = { + .name = "mcp2515", + .owner = THIS_MODULE, + }, + .probe = mcp2515_probe, + .remove = __devexit_p(mcp2515_remove), +}; + +static int __init mcp2515_init(void) +{ + return spi_register_driver(&mcp2515_spi_driver); +} +module_init(mcp2515_init); + +static void __exit mcp2515_exit(void) +{ + spi_unregister_driver(&mcp2515_spi_driver); +} +module_exit(mcp2515_exit); -- 1.7.1 _______________________________________________ Socketcan-core mailing list [email protected] https://lists.berlios.de/mailman/listinfo/socketcan-core
