MC251x CAN controller driver example

From: David Vrabel
Date: Tue Jan 24 2006 - 07:07:36 EST


And here's an example of a CAN controller driver. It for the Microchip
MCP251x (tested with an MCP2515) which uses an SPI interface to the
processor.

David Vrabel
--
David Vrabel, Design Engineer

Arcom, Clifton Road Tel: +44 (0)1223 411200 ext. 3233
Cambridge CB1 7EA, UK Web: http://www.arcom.com/
Index: linux-2.6-working/drivers/can/mcp251x.c
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6-working/drivers/can/mcp251x.c 2006-01-24 11:18:40.000000000 +0000
@@ -0,0 +1,694 @@
+/*
+ * Microchip MCP251x CAN controller driver.
+ *
+ * Copyright (C) 2006 Arcom Control Systems Ltd.
+ *
+ * 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.
+ */
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/netdevice.h>
+#include <linux/interrupt.h>
+#include <linux/spi/spi.h>
+#include <linux/can/can.h>
+#include <linux/can/mcp251x.h>
+
+#include <asm/semaphore.h>
+
+/* SPI interface instruction set */
+#define INSTRUCTION_WRITE 0x02
+#define INSTRUCTION_READ 0x03
+#define INSTRUCTION_BIT_MODIFY 0x05
+#define INSTRUCTION_LOAD_TXB(n) (0x40 + 2 * (n))
+#define INSTRUCTION_READ_RXB(n) (0x90 + 2 * (n))
+#define INSTRUCTION_RESET 0xc0
+
+/* MPC251x registers */
+#define CANCTRL 0x0f
+# define CANCTRL_REQOP_MASK 0xe0
+# define CANCTRL_REQOP_CONF 0x80
+# define CANCTRL_REQOP_LISTEN_ONLY 0x60
+# define CANCTRL_REQOP_LOOPBACK 0x40
+# define CANCTRL_REQOP_SLEEP 0x20
+# define CANCTRL_REQOP_NORMAL 0x00
+# define CANCTRL_OSM 0x08
+#define TEC 0x1c
+#define REC 0x1d
+#define CNF1 0x2a
+#define CNF2 0x29
+# define CNF2_BTLMODE 0x80
+#define CNF3 0x28
+# define CNF3_SOF 0x08
+# define CNF3_WAKFIL 0x04
+# define CNF3_PHSEG2_MASK 0x07
+#define CANINTE 0x2b
+# define CANINTE_MERRE 0x80
+# define CANINTE_WAKIE 0x40
+# define CANINTE_ERRIE 0x20
+# define CANINTE_TX2IE 0x10
+# define CANINTE_TX1IE 0x08
+# define CANINTE_TX0IE 0x04
+# define CANINTE_RX1IE 0x02
+# define CANINTE_RX0IE 0x01
+#define CANINTF 0x2c
+# define CANINTF_MERRF 0x80
+# define CANINTF_WAKIF 0x40
+# define CANINTF_ERRIF 0x20
+# define CANINTF_TX2IF 0x10
+# define CANINTF_TX1IF 0x08
+# define CANINTF_TX0IF 0x04
+# define CANINTF_RX1IF 0x02
+# define CANINTF_RX0IF 0x01
+#define EFLG 0x2d
+# define EFLG_RX1OVR 0x80
+# define EFLG_RX0OVR 0x40
+#define TXBCTRL(n) ((n * 0x10) + 0x30)
+# define TXBCTRL_TXREQ 0x08
+
+/* Buffer size required for the largest SPI transfer (i.e., reading a
+ * frame). */
+#define SPI_TRANSFER_BUF_LEN (2*(6 + CAN_FRAME_MAX_DATA_LEN))
+
+struct mcp251x {
+ struct can_device *can;
+ struct semaphore lock;
+ uint8_t *spi_transfer_buf;
+
+ int bit_rate;
+ int reg;
+
+ struct sk_buff *tx_skb;
+
+ struct work_struct tx_work;
+ struct work_struct irq_work;
+};
+
+static uint8_t mcp251x_read_reg(struct spi_device *spi, uint8_t reg)
+{
+ struct mcp251x *chip = dev_get_drvdata(&spi->dev);
+ uint8_t *tx_buf, *rx_buf;
+ uint8_t val;
+ struct spi_transfer t;
+ struct spi_message m;
+ int ret;
+
+ tx_buf = chip->spi_transfer_buf;
+ rx_buf = chip->spi_transfer_buf + 8;
+
+ down(&chip->lock);
+
+ tx_buf[0] = INSTRUCTION_READ;
+ tx_buf[1] = reg;
+
+ t.tx_buf = tx_buf;
+ t.rx_buf = rx_buf;
+ t.len = 3;
+ t.cs_change = 0;
+
+ spi_message_init(&m);
+ spi_message_add_tail(&t, &m);
+
+ ret = spi_sync(spi, &m);
+ if (ret < 0) {
+ dev_dbg(&spi->dev, "%s: failed: ret = %d\n", __FUNCTION__, ret);
+ val = 0;
+ } else
+ val = rx_buf[2];
+
+ up(&chip->lock);
+
+ return val;
+}
+
+static void mcp251x_write_reg(struct spi_device *spi, uint8_t reg, uint8_t val)
+{
+ struct mcp251x *chip = dev_get_drvdata(&spi->dev);
+ int ret;
+
+ down(&chip->lock);
+
+ chip->spi_transfer_buf[0] = INSTRUCTION_WRITE;
+ chip->spi_transfer_buf[1] = reg;
+ chip->spi_transfer_buf[2] = val;
+
+ ret = spi_write(spi, chip->spi_transfer_buf, 3);
+ if (ret < 0)
+ dev_dbg(&spi->dev, "%s: failed: ret = %d\n", __FUNCTION__, ret);
+
+ up(&chip->lock);
+}
+
+static void mcp251x_write_bits(struct spi_device *spi, uint8_t reg, uint8_t mask, uint8_t val)
+{
+ struct mcp251x *chip = dev_get_drvdata(&spi->dev);
+ int ret;
+
+ down(&chip->lock);
+
+ chip->spi_transfer_buf[0] = INSTRUCTION_BIT_MODIFY;
+ chip->spi_transfer_buf[1] = reg;
+ chip->spi_transfer_buf[2] = mask;
+ chip->spi_transfer_buf[3] = val;
+
+ ret = spi_write(spi, chip->spi_transfer_buf, 4);
+ if (ret < 0)
+ dev_dbg(&spi->dev, "%s: failed: ret = %d\n", __FUNCTION__, ret);
+
+ up(&chip->lock);
+}
+
+static void hw_reset(struct spi_device *spi)
+{
+ struct mcp251x *chip = dev_get_drvdata(&spi->dev);
+ int ret;
+
+ down(&chip->lock);
+
+ chip->spi_transfer_buf[0] = INSTRUCTION_RESET;
+
+ ret = spi_write(spi, chip->spi_transfer_buf, 1);
+ if (ret < 0)
+ dev_dbg(&spi->dev, "%s: failed: ret = %d\n", __FUNCTION__, ret);
+
+ up(&chip->lock);
+}
+
+
+#ifdef DEBUG
+
+static ssize_t mcp251x_reg_addr_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct spi_device *spi = to_spi_device(dev);
+ struct mcp251x *chip = dev_get_drvdata(&spi->dev);
+
+ return sprintf(buf, "0x%02x\n", chip->reg);
+}
+
+static ssize_t mcp251x_reg_addr_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct spi_device *spi = to_spi_device(dev);
+ struct mcp251x *chip = dev_get_drvdata(&spi->dev);
+
+ chip->reg = simple_strtoul(buf, NULL, 0);
+
+ return count;
+}
+
+static DEVICE_ATTR(reg_addr, 0600, mcp251x_reg_addr_show, mcp251x_reg_addr_store);
+
+static ssize_t mcp251x_reg_data_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct spi_device *spi = to_spi_device(dev);
+ struct mcp251x *chip = dev_get_drvdata(&spi->dev);
+
+ return sprintf(buf, "0x%02x\n", mcp251x_read_reg(spi, chip->reg));
+}
+
+static ssize_t mcp251x_reg_data_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct spi_device *spi = to_spi_device(dev);
+ struct mcp251x *chip = dev_get_drvdata(&spi->dev);
+
+ mcp251x_write_reg(spi, chip->reg, simple_strtoul(buf, NULL, 0));
+
+ return count;
+}
+
+static DEVICE_ATTR(reg_data, 0600, mcp251x_reg_data_show, mcp251x_reg_data_store);
+
+#endif /* DEBUG */
+
+
+static void __devinit mcp251x_hw_init(struct spi_device *spi)
+{
+ hw_reset(spi);
+}
+
+static void mcp251x_hw_sleep(struct spi_device *spi)
+{
+ mcp251x_write_reg(spi, CANCTRL, CANCTRL_REQOP_SLEEP);
+}
+
+static void mcp251x_hw_wakeup(struct spi_device *spi)
+{
+ /* Can only wake up by generating a wake-up interrupt. */
+ mcp251x_write_bits(spi, CANINTE, CANINTE_WAKIE, CANINTE_WAKIE);
+ mcp251x_write_bits(spi, CANINTF, CANINTF_WAKIF, CANINTF_WAKIF);
+}
+
+
+static int mcp251x_set_bit_rate(struct can_device *can, int bit_rate)
+{
+ struct spi_device *spi = to_spi_device(can->cdev.dev);
+ struct mcp251x *chip = dev_get_drvdata(&spi->dev);
+ struct mcp251x_platform_data *pdata = spi->dev.platform_data;
+ int tqs; /* tbit/TQ */
+ int brp;
+ int ps1, ps2, propseg, sjw;
+
+ /* Determine the BRP value that gives the requested bit rate. */
+ for(brp = 0; brp < 8; brp++) {
+ tqs = pdata->f_osc / (2 * (brp + 1)) / bit_rate;
+ if (tqs >= 5 && tqs <= 25
+ && (pdata->f_osc / (2 * (brp + 1)) / tqs) == bit_rate)
+ break;
+ }
+ if (brp >= 8)
+ return -EINVAL;
+
+ /* The CAN bus bit time (tbit) is determined by:
+ * tbit = (SyncSeg + PropSeg + PS1 + PS2) * TQ
+ * with:
+ * SyncSeg = 1
+ * sample point (between PS1 and PS2) must be at 60%-70% of the bit time
+ * PropSeg + PS1 >= PS2
+ * PropSeg + PS1 >= Tdelay
+ * PS2 > SJW
+ * 1 <= PropSeg <= 8, 1 <= PS1 <=8, 2 <= PS2 <= 8
+ * SJW = 1 is sufficient in most cases.
+ * Tdelay is usually 1 or 2 TQ.
+ */
+
+ propseg = ps1 = ps2 = (tqs - 1) / 3;
+ if (tqs - (1 + propseg + ps1 + ps2) == 2)
+ ps1++;
+ if (tqs - (1 + propseg + ps1 + ps2) == 1)
+ ps2++;
+ sjw = 1;
+
+ dev_dbg(&spi->dev, "bit rate: BRP = %d, Tbit = %d TQ, PropSeg = %d, PS1 = %d, PS2 = %d, SJW = %d\n",
+ brp, tqs, propseg, ps1, ps2, sjw);
+
+ /* Since we can only change the bit rate when the network device is
+ * down the chip must be in sleep mode. Wake it up and put it into
+ * config mode. */
+ mcp251x_hw_wakeup(spi);
+ mcp251x_write_bits(spi, CANCTRL, CANCTRL_REQOP_MASK, CANCTRL_REQOP_CONF);
+
+ mcp251x_write_reg(spi, CNF1, ((sjw-1) << 6) | brp);
+ mcp251x_write_reg(spi, CNF2, CNF2_BTLMODE | ((ps1-1) << 3) | (propseg-1));
+ mcp251x_write_bits(spi, CNF3, CNF3_PHSEG2_MASK, (ps2-1));
+
+ mcp251x_hw_sleep(spi);
+
+ /* Calculate actual bit rate. */
+ chip->bit_rate = pdata->f_osc / (2 * (brp + 1)) / tqs;
+
+ return 0;
+}
+
+static int mcp251x_get_bit_rate(struct can_device *can)
+{
+ struct spi_device *spi = to_spi_device(can->cdev.dev);
+ struct mcp251x *chip = dev_get_drvdata(&spi->dev);
+
+ return chip->bit_rate;
+}
+
+
+static void mcp251x_hw_tx(struct spi_device *spi, struct can_frame *frame, int tx_buf_idx)
+{
+ struct mcp251x *chip = dev_get_drvdata(&spi->dev);
+ uint8_t *buf = chip->spi_transfer_buf;
+ int ret;
+
+ dev_dbg(&spi->dev, "%s()\n", __FUNCTION__);
+
+ down(&chip->lock);
+
+ buf[0] = INSTRUCTION_LOAD_TXB(tx_buf_idx);
+ buf[1] = frame->header.id >> 3;
+ buf[2] = (frame->header.id << 5) | (frame->header.ide << 3)
+ | frame->header.eid >> 16;
+ buf[3] = frame->header.eid >> 8;
+ buf[4] = frame->header.eid;
+ buf[5] = (frame->header.rtr << 6) | frame->header.dlc;
+ memcpy(buf + 6, frame->data, frame->header.dlc);
+
+ ret = spi_write(spi, buf, 6 + CAN_FRAME_MAX_DATA_LEN);
+ if (ret < 0)
+ dev_dbg(&spi->dev, "%s: failed: ret = %d\n", __FUNCTION__, ret);
+
+ up(&chip->lock);
+
+ mcp251x_write_reg(spi, TXBCTRL(tx_buf_idx), TXBCTRL_TXREQ);
+}
+
+static void mcp251x_hw_rx(struct spi_device *spi, int buf_idx)
+{
+ struct mcp251x *chip = dev_get_drvdata(&spi->dev);
+ uint8_t *buf = chip->spi_transfer_buf;
+ uint8_t *rx_buf;
+ struct spi_transfer t;
+ struct spi_message m;
+ int ret;
+ struct sk_buff *skb;
+ struct can_frame *frame;
+
+ skb = dev_alloc_skb(sizeof(struct can_frame));
+ if (!skb) {
+ dev_dbg(&spi->dev, "%s: out of memory for Rx'd frame\n", __FUNCTION__);
+ chip->can->stats.rx_dropped++;
+ return;
+ }
+ skb->dev = &chip->can->ndev;
+ frame = (struct can_frame *)skb_put(skb, sizeof(struct can_frame));
+
+ down(&chip->lock);
+
+ buf[0] = INSTRUCTION_READ_RXB(buf_idx);
+
+ t.tx_buf = buf;
+ t.rx_buf = rx_buf = buf + (6 + CAN_FRAME_MAX_DATA_LEN);
+ t.len = 14;
+ t.cs_change = 0;
+
+ spi_message_init(&m);
+ spi_message_add_tail(&t, &m);
+
+ ret = spi_sync(spi, &m);
+ if (ret < 0)
+ dev_dbg(&spi->dev, "%s: failed: ret = %d\n", __FUNCTION__, ret);
+
+ frame->header.id = (rx_buf[1] << 3) | (rx_buf[2] >> 5);
+ frame->header.ide = (rx_buf[2] >> 3) & 0x1;
+ frame->header.eid = (rx_buf[2] << 16) | (rx_buf[3] << 8) | rx_buf[4];
+ frame->header.rtr = (rx_buf[5] >> 6) & 0x1;
+ frame->header.dlc = rx_buf[5] & 0x0f;
+ memcpy(frame->data, rx_buf + 6, CAN_FRAME_MAX_DATA_LEN);
+
+ up(&chip->lock);
+
+ chip->can->stats.rx_packets++;
+ chip->can->stats.rx_bytes += frame->header.dlc;
+
+ netif_rx(skb);
+}
+
+
+static void mcp251x_tx_work_handler(void *data)
+{
+ struct spi_device *spi = data;
+ struct mcp251x *chip = dev_get_drvdata(&spi->dev);
+ struct can_frame *frame = (struct can_frame *)chip->tx_skb->data;
+
+ dev_dbg(&spi->dev, "%s()\n", __FUNCTION__);
+
+ /* FIXME: move this somewhere more appropriate? */
+ if (frame->header.dlc > CAN_FRAME_MAX_DATA_LEN)
+ frame->header.dlc = CAN_FRAME_MAX_DATA_LEN;
+
+ /* FIXME: use all 3 Tx buffers */
+ mcp251x_hw_tx(spi, frame, 0);
+
+ dev_kfree_skb(chip->tx_skb);
+}
+
+
+static void mcp251x_irq_work_handler(void *data)
+{
+ struct spi_device *spi = data;
+ struct mcp251x *chip = dev_get_drvdata(&spi->dev);
+ uint8_t intf;
+
+ for (;;) {
+ intf = mcp251x_read_reg(spi, CANINTF);
+ if (intf == 0x00)
+ break;
+
+ dev_dbg(&spi->dev, "interrupt:%s%s%s%s%s%s%s%s\n",
+ (intf & CANINTF_MERRF) ? " MERR":"",
+ (intf & CANINTF_WAKIF) ? " WAK":"",
+ (intf & CANINTF_ERRIF) ? " ERR":"",
+ (intf & CANINTF_TX2IF) ? " TX2":"",
+ (intf & CANINTF_TX1IF) ? " TX1":"",
+ (intf & CANINTF_TX0IF) ? " TX0":"",
+ (intf & CANINTF_RX1IF) ? " RX1":"",
+ (intf & CANINTF_RX0IF) ? " RX0":"");
+
+ if (intf & CANINTF_MERRF) {
+ uint8_t txbnctrl;
+ /* if there are no pending Tx buffers, restart queue */
+ txbnctrl = mcp251x_read_reg(spi, TXBCTRL(0));
+ if (!(txbnctrl & TXBCTRL_TXREQ))
+ netif_wake_queue(&chip->can->ndev);
+ }
+ if (intf & CANINTF_ERRIF) {
+ uint8_t eflg = mcp251x_read_reg(spi, EFLG);
+
+ dev_dbg(&spi->dev, "EFLG = 0x%02x\n", eflg);
+
+ if (eflg & (EFLG_RX0OVR | EFLG_RX1OVR)) {
+ if (eflg & EFLG_RX0OVR)
+ chip->can->stats.rx_over_errors++;
+ if (eflg & EFLG_RX1OVR)
+ chip->can->stats.rx_over_errors++;
+ mcp251x_write_reg(spi, EFLG, 0x00);
+ }
+ }
+ if (intf & (CANINTF_TX2IF | CANINTF_TX1IF | CANINTF_TX0IF)) {
+ chip->can->stats.tx_packets++;
+ chip->can->stats.tx_bytes += ((struct can_frame *)(chip->tx_skb->data))->header.dlc;
+ netif_wake_queue(&chip->can->ndev);
+ }
+ if (intf & CANINTF_RX0IF)
+ mcp251x_hw_rx(spi, 0);
+ if (intf & CANINTF_RX1IF)
+ mcp251x_hw_rx(spi, 1);
+
+ mcp251x_write_bits(spi, CANINTF, intf, 0x00);
+ }
+}
+
+
+static irqreturn_t mcp251x_irq(int irq, void *dev_id, struct pt_regs *regs)
+{
+ struct spi_device *spi = dev_id;
+ struct mcp251x *chip = dev_get_drvdata(&spi->dev);
+
+ /* Can't do anything in interrupt context so fire of the interrupt
+ * handling workqueue. */
+ schedule_work(&chip->irq_work);
+
+ return IRQ_HANDLED;
+}
+
+static int mcp251x_open(struct net_device *netdev)
+{
+ struct can_device *can = netdev->priv;
+ struct spi_device *spi = to_spi_device(can->cdev.dev);
+ struct mcp251x_platform_data *pdata = spi->dev.platform_data;
+
+ if (pdata->transceiver_enable)
+ pdata->transceiver_enable(1);
+
+ mcp251x_hw_wakeup(spi);
+
+ /* Enable interrupts */
+ mcp251x_write_reg(spi, CANINTE,
+ CANINTE_ERRIE
+ | CANINTE_TX2IE | CANINTE_TX1IE | CANINTE_TX0IE
+ | CANINTE_RX1IE | CANINTE_RX0IE);
+
+ /* put device into normal mode */
+ mcp251x_write_reg(spi, CANCTRL, CANCTRL_REQOP_NORMAL);
+
+ return 0;
+}
+
+static int mcp251x_stop(struct net_device *netdev)
+{
+ struct can_device *can = netdev->priv;
+ struct spi_device *spi = to_spi_device(can->cdev.dev);
+ struct mcp251x_platform_data *pdata = spi->dev.platform_data;
+
+ /* disable and clear pending interrupts */
+ mcp251x_write_reg(spi, CANINTE, 0x00);
+ mcp251x_write_reg(spi, CANINTF, 0x00);
+
+ mcp251x_hw_sleep(spi);
+
+ if (pdata->transceiver_enable)
+ pdata->transceiver_enable(0);
+
+ return 0;
+}
+
+static int mcp251x_tx(struct sk_buff *skb, struct net_device *netdev)
+{
+ struct can_device *can = netdev->priv;
+ struct spi_device *spi = to_spi_device(can->cdev.dev);
+ struct mcp251x *chip = dev_get_drvdata(&spi->dev);
+ struct can_frame *frame;
+
+ if (skb->len != sizeof(struct can_frame)) {
+ dev_dbg(&spi->dev, "dropping packet - bad length\n");
+ dev_kfree_skb(skb);
+ chip->can->stats.tx_dropped++;
+ return 0;
+ }
+
+ netif_stop_queue(netdev);
+
+ frame = (struct can_frame *)skb->data;
+
+ chip->tx_skb = skb;
+ schedule_work(&chip->tx_work);
+
+ return 0;
+}
+
+static int mcp251x_remove(struct spi_device *spi)
+{
+ struct mcp251x *chip = dev_get_drvdata(&spi->dev);
+
+ dev_dbg(&spi->dev, "%s: stop\n", __FUNCTION__);
+
+#ifdef DEBUG
+ device_remove_file(&spi->dev, &dev_attr_reg_addr);
+ device_remove_file(&spi->dev, &dev_attr_reg_data);
+#endif
+
+ can_device_unregister(chip->can);
+ free_irq(spi->irq, spi);
+ kfree(chip->spi_transfer_buf);
+
+ return 0;
+}
+
+static int __devinit mcp251x_probe(struct spi_device *spi)
+{
+ struct can_device *can;
+ struct mcp251x *chip;
+ int ret = 0;
+
+ dev_dbg(&spi->dev, "%s: start\n", __FUNCTION__);
+
+ can = can_device_alloc(&spi->dev, sizeof(struct mcp251x));
+ if (!can) {
+ ret = -ENOMEM;
+ goto error_alloc;
+ }
+ chip = can_device_get_devdata(can);
+ dev_set_drvdata(&spi->dev, chip);
+ chip->can = can;
+
+ chip->spi_transfer_buf = kmalloc(SPI_TRANSFER_BUF_LEN, GFP_KERNEL);
+ if (!chip->spi_transfer_buf) {
+ ret = -ENOMEM;
+ goto error_buf;
+ }
+ init_MUTEX(&chip->lock);
+
+ ret = request_irq(spi->irq, mcp251x_irq, SA_SAMPLE_RANDOM, "mcp251x", spi);
+ if (ret < 0) {
+ dev_err(&spi->dev, "request irq %d failed (ret = %d)\n", spi->irq, ret);
+ goto error_irq;
+ }
+
+ can->set_bit_rate = mcp251x_set_bit_rate;
+ can->get_bit_rate = mcp251x_get_bit_rate;
+
+ can->ndev.open = mcp251x_open;
+ can->ndev.stop = mcp251x_stop;
+ can->ndev.hard_start_xmit = mcp251x_tx;
+
+ ret = can_device_register(can);
+ if (ret < 0) {
+ dev_err(&spi->dev, "register can device failed (ret = %d)\n", ret);
+ goto error_register;
+ }
+
+ INIT_WORK(&chip->tx_work, mcp251x_tx_work_handler, spi);
+ INIT_WORK(&chip->irq_work, mcp251x_irq_work_handler, spi);
+
+#ifdef DEBUG
+ device_create_file(&spi->dev, &dev_attr_reg_addr);
+ device_create_file(&spi->dev, &dev_attr_reg_data);
+#endif
+
+ mcp251x_hw_init(spi);
+ mcp251x_set_bit_rate(can, 125000); /* A reasonable default */
+ mcp251x_hw_sleep(spi);
+
+ return 0;
+
+ error_register:
+ free_irq(spi->irq, spi);
+ error_irq:
+ kfree(chip->spi_transfer_buf);
+ error_buf:
+ class_device_put(&can->cdev);
+ error_alloc:
+ return ret;
+}
+
+static int mcp251x_suspend(struct spi_device *spi, pm_message_t mesg)
+{
+ struct mcp251x *chip = dev_get_drvdata(&spi->dev);
+ struct mcp251x_platform_data *pdata = spi->dev.platform_data;
+
+ if (!netif_running(&chip->can->ndev))
+ return 0;
+
+ netif_device_detach(&chip->can->ndev);
+
+ mcp251x_hw_sleep(spi);
+ if (pdata->transceiver_enable)
+ pdata->transceiver_enable(0);
+
+ return 0;
+}
+
+static int mcp251x_resume(struct spi_device *spi)
+{
+ struct mcp251x *chip = dev_get_drvdata(&spi->dev);
+ struct mcp251x_platform_data *pdata = spi->dev.platform_data;
+
+ if (!netif_running(&chip->can->ndev))
+ return 0;
+
+ if (pdata->transceiver_enable)
+ pdata->transceiver_enable(1);
+ mcp251x_hw_wakeup(spi);
+
+ netif_device_attach(&chip->can->ndev);
+
+ return 0;
+}
+
+
+static struct spi_driver mcp251x_driver = {
+ .driver = {
+ .name = "mcp251x",
+ .bus = &spi_bus_type,
+ .owner = THIS_MODULE,
+ },
+ .probe = mcp251x_probe,
+ .remove = __devexit_p(mcp251x_remove),
+#ifdef CONFIG_PM
+ .suspend = mcp251x_suspend,
+ .resume = mcp251x_resume,
+#endif
+};
+
+static int __init mcp251x_init(void)
+{
+ return spi_register_driver(&mcp251x_driver);
+}
+module_init(mcp251x_init);
+
+static void __exit mcp251x_exit(void)
+{
+ spi_unregister_driver(&mcp251x_driver);
+}
+module_exit(mcp251x_exit);
+
+
+MODULE_DESCRIPTION("MCP251x CAN controller driver");
+MODULE_LICENSE("GPL");
Index: linux-2.6-working/include/linux/can/mcp251x.h
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6-working/include/linux/can/mcp251x.h 2006-01-24 11:18:40.000000000 +0000
@@ -0,0 +1,26 @@
+/*
+ * MCP251x CAN controller driver
+ *
+ * Copyright (C) 2006 Arcom Control Systems Ltd.
+ *
+ * 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.
+ */
+#ifndef __LINUX_CAN_MCP251X_H
+#define __LINUX_CAN_MCP251X_H
+
+/**
+ * struct mpc251x - MCP251x CAN controller platform data
+ *
+ * f_osc: input clock frequency in Hz
+ * transceiver_enable: enable/disable CAN bus transceivers. May be NULL if
+ * the transceivers are always enabled.
+ */
+struct mcp251x_platform_data {
+ int f_osc;
+ void (*transceiver_enable)(int enable);
+};
+
+#endif /* !__LINUX_CAN_MCP251X_H */
Index: linux-2.6-working/drivers/can/Kconfig
===================================================================
--- linux-2.6-working.orig/drivers/can/Kconfig 2006-01-24 11:17:13.000000000 +0000
+++ linux-2.6-working/drivers/can/Kconfig 2006-01-24 11:18:35.000000000 +0000
@@ -14,4 +14,14 @@
Say "yes" to enable debug messaging (like dev_dbg and pr_debug),
sysfs, and debugfs support in CAN controller drivers.

+config CAN_MCP251X
+ tristate "MCP251x CAN controller"
+ depends on CAN
+ depends on SPI
+ help
+ Support for Microchip MCP2510 and MCP2515 CAN controllers.
+
+ To compile this driver as a module, choose M here: the
+ module will be called mcp251x.
+
endmenu
Index: linux-2.6-working/drivers/can/Makefile
===================================================================
--- linux-2.6-working.orig/drivers/can/Makefile 2006-01-24 11:17:13.000000000 +0000
+++ linux-2.6-working/drivers/can/Makefile 2006-01-24 11:18:35.000000000 +0000
@@ -3,3 +3,4 @@
endif

obj-$(CONFIG_CAN) += can.o
+obj-$(CONFIG_CAN_MCP251X) += mcp251x.o