Hello all, I'm working on a driver for the Janz ICAN3 module. Now that I'm getting good review feedback, I've found some errors in the driver. One of them was that I was missing candev_open() and candev_close() in the netdevice open() and close() routines.
So I added them, but I cannot bring up the the CAN devices anymore. I get this message in the kernel log: janz-ican3 janz-ican3.0: bit-timing not yet defined Great, so I need to set the bit timing. Let's try that: $ ip link set can0 up type can bitrate 1000000 RTNETLINK answers: Numerical argument out of domain That doesn't work either! What, why? The kernel logs show: janz-ican3 janz-ican3.0: bitrate error 100.0% too high My driver is setup exactly the same as the SJA1000 driver. The bittiming_const and do_set_bittiming() functions are copied almost exactly from the sja1000.c driver. Does anyone have any insight to offer about what I did wrong? For completeness, my janz-ican3 driver is included below, just in case anyone needs to look at the code. It has changed slightly, as I am incorporating comments from the last RFCv2 posting. Also, is there some way to set the default bitrate of a device, so a user doesn't have to specify the bitrate to bring the device up? This card comes out of reset with a bitrate of 1000000, and is ready to run. Thanks, Ira /* * Janz MODULbus VMOD-ICAN3 CAN Interface Driver * * Copyright (c) 2010 Ira W. Snyder <[email protected]> * * This file is licensed under the terms of the GNU General Public License * version 2. This program is licensed "as is" without any warranty of any * kind, whether express or implied. */ #include <linux/kernel.h> #include <linux/module.h> #include <linux/init.h> #include <linux/interrupt.h> #include <linux/delay.h> #include <linux/platform_device.h> #include <linux/netdevice.h> #include <linux/can.h> #include <linux/can/dev.h> #include <linux/can/error.h> #include "janz.h" #include "sja1000/sja1000.h" /* the DPM has 64k of memory, organized into 256x 256 byte pages */ #define DPM_NUM_PAGES 256 #define DPM_PAGE_SIZE 256 #define DPM_PAGE_ADDR(p) ((p) * DPM_PAGE_SIZE) /* Janz ICAN3 DPM control registers */ #define DPM_REG_ADDR 0x100 #define DPM_REG_INT 0x102 #define DPM_REG_HWRESET 0x104 #define DPM_REG_TPUINT 0x106 /* Janz "old-style" host interface control registers */ #define MSYNC_PEER 0x00 /* ICAN only */ #define MSYNC_LOCL 0x01 /* host only */ #define TARGET_RUNNING 0x02 /* Janz "new-style" host interface queue page numbers */ #define QUEUE_TOHOST 5 #define QUEUE_FROMHOST_MID 6 #define QUEUE_FROMHOST_HIGH 7 #define QUEUE_FROMHOST_LOW 8 /* The first free page in the DPM is #9 */ #define DPM_FREE_START 9 /* Janz "new-style" and "fast" host interface descriptor flags */ #define DESC_VALID 0x80 #define DESC_WRAP 0x40 #define DESC_INTERRUPT 0x20 #define DESC_IVALID 0x10 #define DESC_LEN(len) (len) /* Janz Firmware Messages */ #define MSG_CONNECTI 0x02 #define MSG_DISCONNECT 0x03 #define MSG_IDVERS 0x04 #define MSG_MSGLOST 0x05 #define MSG_NEWHOSTIF 0x08 #define MSG_SETAFILMASK 0x10 #define MSG_INITFDPMQUEUE 0x11 #define MSG_HWCONF 0x12 #define MSG_FMSGLOST 0x15 #define MSG_CEVTIND 0x37 #define MSG_CBTRREQ 0x41 #define MSG_COFFREQ 0x42 #define MSG_CONREQ 0x43 #define MSG_CCONFREQ 0x47 /* Janz CAN Event Indication Message Types */ #define CEVTIND_EI 0x01 #define CEVTIND_DOI 0x02 #define CEVTIND_LOST 0x04 #define CEVTIND_FULL 0x08 #define CEVTIND_BEI 0x10 /* Number of buffers for use in the "new-style" host interface */ #define JANZ_NEW_BUFFERS 16 /* Number of buffers for use in the "fast" host interface */ #define JANZ_FAST_BUFFERS 256 /* Driver Name */ #define DRV_NAME "janz-ican3" struct janz_ican3 { /* must be the first member */ struct can_priv can; /* CAN network device */ struct net_device *ndev; /* Device for printing */ struct device *dev; /* module number */ unsigned int num; /* base address of registers and IRQ */ void __iomem *ctrl; void __iomem *regs; int irq; /* old and new style host interface */ unsigned int iftype; spinlock_t lock; /* new host interface */ unsigned int rx_int; unsigned int rx_num; unsigned int tx_num; /* fast host interface */ unsigned int fastrx_start; unsigned int fastrx_int; unsigned int fastrx_num; unsigned int fasttx_start; unsigned int fasttx_num; /* first free DPM page */ unsigned int free_page; /* interrupt handling */ struct work_struct work; }; struct janz_msg { u8 control; u8 spec; __le16 len; u8 data[252]; }; struct janz_new_desc { u8 control; u8 pointer; }; struct janz_fast_desc { u8 control; u8 command; u8 data[14]; }; /*----------------------------------------------------------------------------*/ /* DPM Register Access */ /*----------------------------------------------------------------------------*/ /* write to the window basic address register */ static inline void janz_set_page(struct janz_ican3 *mod, unsigned int page) { BUG_ON(page >= DPM_NUM_PAGES); iowrite8(page, mod->regs + DPM_REG_ADDR); } /* clear a MODULbus interrupt */ static inline void janz_clr_int(struct janz_ican3 *mod) { ioread8(mod->regs + DPM_REG_INT); } /* generate a MODULbus interrupt */ static inline void janz_set_int(struct janz_ican3 *mod) { iowrite8(0x01, mod->regs + DPM_REG_INT); } /*----------------------------------------------------------------------------*/ /* Onboard Registers */ /*----------------------------------------------------------------------------*/ static inline void janz_disable_interrupts(struct janz_ican3 *mod) { iowrite8(1 << mod->num, mod->ctrl + JANZ_OB_INT_DISABLE); } static inline void janz_enable_interrupts(struct janz_ican3 *mod) { iowrite8(1 << mod->num, mod->ctrl + JANZ_OB_INT_ENABLE); } /*----------------------------------------------------------------------------*/ /* Janz "old-style" host interface */ /*----------------------------------------------------------------------------*/ /* Get the MSYNC bits from the "old-style" interface control registers */ static void janz_get_msync(struct janz_ican3 *mod, u8 *locl, u8 *peer) { janz_set_page(mod, 0); *peer = ioread8(mod->regs + MSYNC_PEER); *locl = ioread8(mod->regs + MSYNC_LOCL); } /* * Recieve a message from the Janz "old-style" firmware interface * * returns 0 on success, -ENOMEM when no message exists */ static int janz_old_recv_msg(struct janz_ican3 *mod, struct janz_msg *msg) { u8 locl, peer, xord; unsigned int mbox; /* get the MSYNC registers */ janz_get_msync(mod, &locl, &peer); xord = locl ^ peer; if ((xord & 0x03) == 0x00) { dev_dbg(mod->dev, "no mbox for reading\n"); return -ENOMEM; } /* find the first free mbox to read */ if ((xord & 0x03) == 0x03) mbox = (xord & 0x04) ? 0 : 1; else mbox = (xord & 0x01) ? 0 : 1; /* copy the message */ janz_set_page(mod, mbox + 1); memcpy_fromio(msg, mod->regs, sizeof(*msg)); /* * notify the firmware that the read buffer is available * for it to fill again */ locl ^= (1 << mbox); janz_set_page(mod, 0); iowrite8(locl, mod->regs + MSYNC_LOCL); return 0; } /* * Send a message through the "old-style" firmware interface * * returns 0 on success, -ENOMEM when no free space exists */ static int janz_old_send_msg(struct janz_ican3 *mod, struct janz_msg *msg) { u8 locl, peer, xord; unsigned int mbox; /* get the MSYNC registers */ janz_get_msync(mod, &locl, &peer); xord = locl ^ peer; if ((xord & 0x30) == 0x30) { dev_err(mod->dev, "no mbox for writing\n"); return -ENOMEM; } /* calculate a free mbox to use */ mbox = (xord & 0x10) ? 1 : 0; /* copy the message to the DPM */ janz_set_page(mod, mbox + 3); memcpy_toio(mod->regs, msg, sizeof(*msg)); locl ^= (mbox == 0) ? 0x10 : 0x20; locl |= (mbox == 0) ? 0x00 : 0x40; janz_set_page(mod, 0); iowrite8(locl, mod->regs + MSYNC_LOCL); return 0; } /*----------------------------------------------------------------------------*/ /* Janz "new-style" Host Interface Setup */ /*----------------------------------------------------------------------------*/ static void janz_init_new_host_interface(struct janz_ican3 *mod) { struct janz_new_desc desc; unsigned long flags; void __iomem *dst; int i; spin_lock_irqsave(&mod->lock, flags); dev_dbg(mod->dev, "%s: starting at page %d\n", __func__, mod->free_page); /* setup the internal datastructures for RX */ mod->rx_num = 0; mod->rx_int = 0; /* tohost queue descriptors are in page 5 */ janz_set_page(mod, 5); dst = mod->regs; /* initialize the tohost (rx) queue descriptors: pages 9-24 */ for (i = 0; i < JANZ_NEW_BUFFERS; i++) { desc.control = DESC_INTERRUPT | DESC_LEN(1); /* I L=1 */ desc.pointer = mod->free_page; /* set wrap flag on last buffer */ if (i == JANZ_NEW_BUFFERS - 1) desc.control |= DESC_WRAP; memcpy_toio(dst, &desc, sizeof(desc)); dst += sizeof(desc); mod->free_page++; } /* fromhost (tx) mid queue descriptors are in page 6 */ janz_set_page(mod, 6); dst = mod->regs; /* setup the internal datastructures for TX */ mod->tx_num = 0; /* initialize the fromhost mid queue descriptors: pages 25-40 */ for (i = 0; i < JANZ_NEW_BUFFERS; i++) { desc.control = DESC_VALID | DESC_LEN(1); /* V L=1 */ desc.pointer = mod->free_page; /* set wrap flag on last buffer */ if (i == JANZ_NEW_BUFFERS - 1) desc.control |= DESC_WRAP; memcpy_toio(dst, &desc, sizeof(desc)); dst += sizeof(desc); mod->free_page++; } /* fromhost hi queue descriptors are in page 7 */ janz_set_page(mod, 7); dst = mod->regs; /* initialize only a single buffer in the fromhost hi queue (unused) */ desc.control = DESC_VALID | DESC_WRAP | DESC_LEN(1); /* VW L=1 */ desc.pointer = mod->free_page; memcpy_toio(dst, &desc, sizeof(desc)); mod->free_page++; /* fromhost low queue descriptors are in page 8 */ janz_set_page(mod, 8); dst = mod->regs; /* initialize only a single buffer in the fromhost low queue (unused) */ desc.control = DESC_VALID | DESC_WRAP | DESC_LEN(1); /* VW L=1 */ desc.pointer = mod->free_page; memcpy_toio(dst, &desc, sizeof(desc)); mod->free_page++; dev_dbg(mod->dev, "%s: next free page %d\n", __func__, mod->free_page); spin_unlock_irqrestore(&mod->lock, flags); } /*----------------------------------------------------------------------------*/ /* Janz Fast Host Interface Setup */ /*----------------------------------------------------------------------------*/ static void janz_init_fast_host_interface(struct janz_ican3 *mod) { struct janz_fast_desc desc; unsigned long flags; unsigned int addr; void __iomem *dst; int i; spin_lock_irqsave(&mod->lock, flags); dev_dbg(mod->dev, "%s: starting at page %d\n", __func__, mod->free_page); /* save the start recv page */ mod->fastrx_start = mod->free_page; mod->fastrx_num = 0; mod->fastrx_int = 0; /* build a single fast tohost queue descriptor */ memset(&desc, 0, sizeof(desc)); desc.control = 0x00; desc.command = 1; /* build the tohost queue descriptor ring in memory */ addr = 0; for (i = 0; i < JANZ_FAST_BUFFERS; i++) { /* set the wrap bit on the last buffer */ if (i == JANZ_FAST_BUFFERS - 1) desc.control |= DESC_WRAP; /* switch to the correct page */ janz_set_page(mod, mod->free_page); /* copy the descriptor to the DPM */ dst = mod->regs + addr; memcpy_toio(dst, &desc, sizeof(desc)); addr += sizeof(desc); /* move to the next page if necessary */ if (addr >= DPM_PAGE_SIZE) { addr = 0; mod->free_page++; } } /* make sure we page-align the next queue */ if (addr != 0) mod->free_page++; /* save the start xmit page */ mod->fasttx_start = mod->free_page; mod->fasttx_num = 0; /* build a single fast fromhost queue descriptor */ memset(&desc, 0, sizeof(desc)); desc.control = DESC_VALID; desc.command = 1; /* build the fromhost queue descriptor ring in memory */ addr = 0; for (i = 0; i < JANZ_FAST_BUFFERS; i++) { /* set the wrap bit on the last buffer */ if (i == JANZ_FAST_BUFFERS - 1) desc.control |= DESC_WRAP; /* switch to the correct page */ janz_set_page(mod, mod->free_page); /* copy the descriptor to the DPM */ dst = mod->regs + addr; memcpy_toio(dst, &desc, sizeof(desc)); addr += sizeof(desc); /* move to the next page if necessary */ if (addr >= DPM_PAGE_SIZE) { addr = 0; mod->free_page++; } } dev_dbg(mod->dev, "%s: next free page %d\n", __func__, mod->free_page); spin_unlock_irqrestore(&mod->lock, flags); } /*----------------------------------------------------------------------------*/ /* Janz "new-style" Host Interface Message Helpers */ /*----------------------------------------------------------------------------*/ /* * LOCKING: must hold mod->lock */ static int janz_new_send_msg(struct janz_ican3 *mod, struct janz_msg *msg) { struct janz_new_desc desc; void __iomem *desc_addr = mod->regs + (mod->tx_num * sizeof(desc)); /* switch to the fromhost mid queue, and read the buffer descriptor */ janz_set_page(mod, 6); memcpy_fromio(&desc, desc_addr, sizeof(desc)); if (!(desc.control & DESC_VALID)) { dev_dbg(mod->dev, "%s: no free buffers\n", __func__); return -ENOMEM; } /* switch to the data page, copy the data */ janz_set_page(mod, desc.pointer); memcpy_toio(mod->regs, msg, sizeof(*msg)); /* switch back to the descriptor, set the valid bit, write it back */ janz_set_page(mod, 6); desc.control ^= DESC_VALID; memcpy_toio(desc_addr, &desc, sizeof(desc)); /* update the tx number */ mod->tx_num = (desc.control & DESC_WRAP) ? 0 : (mod->tx_num + 1); dev_dbg(mod->dev, "%s: update TX num -> %d\n", __func__, mod->tx_num); return 0; } /* * LOCKING: must hold mod->lock */ static int janz_new_recv_msg(struct janz_ican3 *mod, struct janz_msg *msg) { struct janz_new_desc desc; void __iomem *desc_addr = mod->regs + (mod->rx_num * sizeof(desc)); /* switch to the tohost queue, and read the buffer descriptor */ janz_set_page(mod, 5); memcpy_fromio(&desc, desc_addr, sizeof(desc)); if (!(desc.control & DESC_VALID)) { dev_dbg(mod->dev, "%s: no buffers to recv\n", __func__); return -ENOMEM; } /* switch to the data page, copy the data */ janz_set_page(mod, desc.pointer); memcpy_fromio(msg, mod->regs, sizeof(*msg)); /* switch back to the descriptor, toggle the valid bit, write it back */ janz_set_page(mod, 5); desc.control ^= DESC_VALID; memcpy_toio(desc_addr, &desc, sizeof(desc)); /* update the rx number */ mod->rx_num = (desc.control & DESC_WRAP) ? 0 : (mod->rx_num + 1); dev_dbg(mod->dev, "%s: update RX num -> %d\n", __func__, mod->rx_num); return 0; } /*----------------------------------------------------------------------------*/ /* Message Send / Recv Helpers */ /*----------------------------------------------------------------------------*/ static int janz_send_msg(struct janz_ican3 *mod, struct janz_msg *msg) { unsigned long flags; int ret; spin_lock_irqsave(&mod->lock, flags); if (mod->iftype == 0) ret = janz_old_send_msg(mod, msg); else ret = janz_new_send_msg(mod, msg); spin_unlock_irqrestore(&mod->lock, flags); return ret; } static int janz_recv_msg(struct janz_ican3 *mod, struct janz_msg *msg) { unsigned long flags; int ret; spin_lock_irqsave(&mod->lock, flags); if (mod->iftype == 0) ret = janz_old_recv_msg(mod, msg); else ret = janz_new_recv_msg(mod, msg); spin_unlock_irqrestore(&mod->lock, flags); return ret; } /*----------------------------------------------------------------------------*/ /* Quick Pre-constructed Messages */ /*----------------------------------------------------------------------------*/ static int janz_msg_connect(struct janz_ican3 *mod) { struct janz_msg msg; int ret; memset(&msg, 0, sizeof(msg)); msg.control = 0x00; msg.spec = MSG_CONNECTI; msg.len = cpu_to_le16(0); ret = janz_send_msg(mod, &msg); if (ret) { dev_dbg(mod->dev, "unable to send CONNECT message\n"); return ret; } return 0; } static int janz_msg_disconnect(struct janz_ican3 *mod) { struct janz_msg msg; int ret; memset(&msg, 0, sizeof(msg)); msg.control = 0x00; msg.spec = MSG_DISCONNECT; msg.len = cpu_to_le16(0); ret = janz_send_msg(mod, &msg); if (ret) { dev_dbg(mod->dev, "unable to send DISCONNECT message\n"); return ret; } return 0; } static int janz_msg_newhostif(struct janz_ican3 *mod) { struct janz_msg msg; int ret; memset(&msg, 0, sizeof(msg)); msg.control = 0x00; msg.spec = MSG_NEWHOSTIF; msg.len = cpu_to_le16(0); /* If we're not using the old interface, switching seems bogus */ WARN_ON(mod->iftype != 0); ret = janz_send_msg(mod, &msg); if (ret) { dev_dbg(mod->dev, "unable to send NEWHOSTIF message\n"); return ret; } /* mark the module as using the new host interface */ mod->iftype = 1; return 0; } static int janz_msg_fasthostif(struct janz_ican3 *mod) { struct janz_msg msg; unsigned int addr; int ret; memset(&msg, 0, sizeof(msg)); msg.control = 0x00; msg.spec = MSG_INITFDPMQUEUE; msg.len = cpu_to_le16(8); /* write the tohost queue start address */ addr = DPM_PAGE_ADDR(mod->fastrx_start); msg.data[0] = addr & 0xff; msg.data[1] = (addr >> 8) & 0xff; msg.data[2] = (addr >> 16) & 0xff; msg.data[3] = (addr >> 24) & 0xff; /* write the fromhost queue start address */ addr = DPM_PAGE_ADDR(mod->fasttx_start); msg.data[4] = addr & 0xff; msg.data[5] = (addr >> 8) & 0xff; msg.data[6] = (addr >> 16) & 0xff; msg.data[7] = (addr >> 24) & 0xff; /* If we're not using the new interface yet, we cannot do this */ WARN_ON(mod->iftype != 1); ret = janz_send_msg(mod, &msg); if (ret) { dev_dbg(mod->dev, "unable to send FASTHOSTIF message\n"); return ret; } return 0; } /* * Setup the CAN filter to either accept or reject all * messages from the CAN bus. */ static int janz_set_id_filter(struct janz_ican3 *mod, bool accept) { struct janz_msg msg; int ret; /* Standard Frame Format */ memset(&msg, 0, sizeof(msg)); msg.control = 0x00; msg.spec = MSG_SETAFILMASK; msg.len = cpu_to_le16(5); msg.data[0] = 0x00; /* IDLo LSB */ msg.data[1] = 0x00; /* IDLo MSB */ msg.data[2] = 0xff; /* IDHi LSB */ msg.data[3] = 0x07; /* IDHi MSB */ /* accept all frames for fast host if, or reject all frames */ msg.data[4] = accept ? 0x02 : 0x00; ret = janz_send_msg(mod, &msg); if (ret) { dev_dbg(mod->dev, "unable to send SETAFILMASK message\n"); return ret; } /* Extended Frame Format */ memset(&msg, 0, sizeof(msg)); msg.control = 0x00; msg.spec = MSG_SETAFILMASK; msg.len = cpu_to_le16(13); msg.data[0] = 0; /* MUX = 0 */ msg.data[1] = 0x00; /* IDLo LSB */ msg.data[2] = 0x00; msg.data[3] = 0x00; msg.data[4] = 0x20; /* IDLo MSB */ msg.data[5] = 0xff; /* IDHi LSB */ msg.data[6] = 0xff; msg.data[7] = 0xff; msg.data[8] = 0x3f; /* IDHi MSB */ /* accept all frames for fast host if, or reject all frames */ msg.data[9] = accept ? 0x02 : 0x00; ret = janz_send_msg(mod, &msg); if (ret) { dev_dbg(mod->dev, "unable to send SETAFILMASK message\n"); return ret; } return 0; } /* * Bring the CAN bus online or offline */ static int janz_set_bus_state(struct janz_ican3 *mod, bool on) { struct janz_msg msg; int ret; memset(&msg, 0, sizeof(msg)); msg.control = 0x00; msg.spec = on ? MSG_CONREQ : MSG_COFFREQ; msg.len = cpu_to_le16(0); dev_dbg(mod->dev, "%s: %s request: spec %.2x\n", __func__, on ? "on" : "off", msg.spec); ret = janz_send_msg(mod, &msg); if (ret) { dev_dbg(mod->dev, "unable to send CONREQ/COFFREQ message\n"); return ret; } return 0; } static int janz_set_termination(struct janz_ican3 *mod, bool on) { struct janz_msg msg; int ret; memset(&msg, 0, sizeof(msg)); msg.control = 0x00; msg.spec = MSG_HWCONF; msg.len = cpu_to_le16(2); msg.data[0] = 0x00; msg.data[1] = on ? 0x01 : 0x00; ret = janz_send_msg(mod, &msg); if (ret) { dev_dbg(mod->dev, "unable to send HWCONF message\n"); return ret; } return 0; } static int janz_set_buserror(struct janz_ican3 *mod, u8 quota) { struct janz_msg msg; int ret; memset(&msg, 0, sizeof(msg)); msg.spec = MSG_CCONFREQ; msg.len = cpu_to_le16(2); msg.data[0] = 0x00; msg.data[1] = quota; ret = janz_send_msg(mod, &msg); if (ret) { dev_dbg(mod->dev, "unable to send CCONFREQ message\n"); return ret; } return 0; } /*----------------------------------------------------------------------------*/ /* Janz to CAN Frame Conversion */ /*----------------------------------------------------------------------------*/ static void janz_to_can(struct janz_ican3 *mod, struct janz_fast_desc *desc, struct can_frame *cf) { if ((desc->command & 0x0f) == 0) { dev_dbg(mod->dev, "%s: old frame format\n", __func__); if (desc->data[1] & 0x10) cf->can_id |= CAN_RTR_FLAG; cf->can_id |= desc->data[0] << 3; cf->can_id |= (desc->data[1] & 0xe0) >> 5; cf->can_dlc = desc->data[1] & 0x0f; memcpy(cf->data, &desc->data[2], sizeof(cf->data)); } else { dev_dbg(mod->dev, "%s: new frame format\n", __func__); cf->can_dlc = desc->data[0] & 0x0f; if (desc->data[0] & 0x40) cf->can_id |= CAN_RTR_FLAG; if (desc->data[0] & 0x80) { cf->can_id |= CAN_EFF_FLAG; cf->can_id |= desc->data[2] << 21; /* 28-21 */ cf->can_id |= desc->data[3] << 13; /* 20-13 */ cf->can_id |= desc->data[4] << 5; /* 12-5 */ cf->can_id |= (desc->data[5] & 0xf8) >> 3; } else { cf->can_id |= desc->data[2] << 3; /* 10-3 */ cf->can_id |= desc->data[3] >> 5; /* 2-0 */ } memcpy(cf->data, &desc->data[6], sizeof(cf->data)); } } static void can_to_janz(struct janz_ican3 *mod, struct can_frame *cf, struct janz_fast_desc *desc) { /* clear out any stale data in the descriptor */ memset(desc->data, 0, sizeof(desc->data)); /* we always use the extended format, with the ECHO flag set */ desc->command = 1; desc->data[0] |= cf->can_dlc; desc->data[1] |= 0x10; /* echo */ if (cf->can_id & CAN_RTR_FLAG) desc->data[0] |= 0x40; /* pack the id into the correct places */ if (cf->can_id & CAN_EFF_FLAG) { dev_dbg(mod->dev, "%s: extended frame\n", __func__); desc->data[0] |= 0x80; /* extended id frame */ desc->data[2] = (cf->can_id & 0x1fe00000) >> 21; /* 28-21 */ desc->data[3] = (cf->can_id & 0x001fe000) >> 13; /* 20-13 */ desc->data[4] = (cf->can_id & 0x00001fe0) >> 5; /* 12-5 */ desc->data[5] = (cf->can_id & 0x0000001f) << 3; /* 4-0 */ } else { dev_dbg(mod->dev, "%s: standard frame\n", __func__); desc->data[2] = (cf->can_id & 0x7F8) >> 3; /* bits 10-3 */ desc->data[3] = (cf->can_id & 0x007) << 5; /* bits 2-0 */ } /* copy the data bits into the descriptor */ memcpy(&desc->data[6], cf->data, sizeof(cf->data)); } /*----------------------------------------------------------------------------*/ /* Interrupt Handling */ /*----------------------------------------------------------------------------*/ static void janz_handle_idvers(struct janz_ican3 *mod, struct janz_msg *msg) { dev_dbg(mod->dev, "%s: %s\n", __func__, msg->data); } static void janz_handle_msglost(struct janz_ican3 *mod, struct janz_msg *msg) { char *queue; if (msg->spec == MSG_MSGLOST) queue = "new"; else queue = "fast"; dev_dbg(mod->dev, "%s: %s hostif: %d messages lost\n", __func__, queue, msg->data[0]); } /* * Handle CAN Event Indication Messages from the firmware * * The Janz firmware provides the values of some SJA1000 registers when it * generates this message. The code below is largely copied from the * drivers/net/can/sja1000/sja1000.c file, and adapted as necessary */ static int janz_handle_cevtind(struct janz_ican3 *mod, struct janz_msg *msg) { struct net_device *dev = mod->ndev; struct net_device_stats *stats = &dev->stats; enum can_state state = mod->can.state; struct can_frame *cf; struct sk_buff *skb; u8 status, isrc; /* we can only handle the SJA1000 part */ if (msg->data[1] != 0x02) { dev_err(mod->dev, "unable to handle errors on non-SJA1000\n"); return -ENODEV; } /* check the message length for sanity */ if (msg->len < 6) { dev_dbg(mod->dev, "unable to handle short error message\n"); return -EINVAL; } skb = alloc_can_err_skb(dev, &cf); if (skb == NULL) return -ENOMEM; isrc = msg->data[0]; status = msg->data[3]; /* data overrun interrupt */ if (isrc == CEVTIND_DOI || isrc == CEVTIND_LOST) { cf->can_id |= CAN_ERR_CRTL; cf->data[1] = CAN_ERR_CRTL_RX_OVERFLOW; stats->rx_over_errors++; stats->rx_errors++; } /* error warning interrupt */ if (isrc == CEVTIND_EI) { if (status & SR_BS) { state = CAN_STATE_BUS_OFF; cf->can_id |= CAN_ERR_BUSOFF; can_bus_off(dev); } else if (status & SR_ES) { state = CAN_STATE_ERROR_WARNING; } else { state = CAN_STATE_ERROR_ACTIVE; } } /* bus error interrupt */ if (isrc == CEVTIND_BEI) { u8 ecc = msg->data[2]; mod->can.can_stats.bus_error++; stats->rx_errors++; cf->can_id |= CAN_ERR_PROT | CAN_ERR_BUSERROR; switch (ecc & ECC_MASK) { case ECC_BIT: cf->data[2] |= CAN_ERR_PROT_BIT; break; case ECC_FORM: cf->data[2] |= CAN_ERR_PROT_FORM; break; case ECC_STUFF: cf->data[2] |= CAN_ERR_PROT_STUFF; break; default: cf->data[2] |= CAN_ERR_PROT_UNSPEC; cf->data[3] = ecc & ECC_SEG; break; } if ((ecc & ECC_DIR) == 0) cf->data[2] |= CAN_ERR_PROT_TX; } if (state != mod->can.state && (state == CAN_STATE_ERROR_WARNING || state == CAN_STATE_ERROR_PASSIVE)) { u8 rxerr = msg->data[4]; u8 txerr = msg->data[5]; cf->can_id |= CAN_ERR_CRTL; if (state == CAN_STATE_ERROR_WARNING) { mod->can.can_stats.error_warning++; cf->data[1] = (txerr > rxerr) ? CAN_ERR_CRTL_TX_WARNING : CAN_ERR_CRTL_RX_WARNING; } else { mod->can.can_stats.error_passive++; cf->data[1] = (txerr > rxerr) ? CAN_ERR_CRTL_TX_PASSIVE : CAN_ERR_CRTL_RX_PASSIVE; } } mod->can.state = state; stats->rx_packets++; stats->rx_bytes += cf->can_dlc; netif_rx(skb); return 0; } static void janz_handle_unknown(struct janz_ican3 *mod, struct janz_msg *msg) { u16 len; int i; len = le16_to_cpu(msg->len); dev_dbg(mod->dev, "%s: modno %d UNKNOWN spec 0x%.2x len %d\n", __func__, mod->num, msg->spec, len); for (i = 0; i < len; i++) dev_dbg(mod->dev, "msg->data[%.2d] -> 0x%.2x\n", i, msg->data[i]); } /* * Handle a control message from the firmware */ static void janz_handle_message(struct janz_ican3 *mod, struct janz_msg *msg) { dev_dbg(mod->dev, "%s: modno %d spec 0x%.2x len %d bytes\n", __func__, mod->num, msg->spec, le16_to_cpu(msg->len)); switch (msg->spec) { case MSG_IDVERS: janz_handle_idvers(mod, msg); break; case MSG_MSGLOST: case MSG_FMSGLOST: janz_handle_msglost(mod, msg); break; case MSG_CEVTIND: janz_handle_cevtind(mod, msg); break; default: janz_handle_unknown(mod, msg); break; } } /* * Recieve one CAN frame from the hardware * * This works like the core of a NAPI function, but is intended to be called * from workqueue context instead. This driver already needs a workqueue to * process control messages, so we use the workqueue instead of using NAPI. * This was done to simplify locking. * * CONTEXT: must be called from user context */ static int janz_recv_skb(struct janz_ican3 *mod) { struct net_device *ndev = mod->ndev; struct net_device_stats *stats = &ndev->stats; struct janz_fast_desc desc; void __iomem *desc_addr; struct can_frame *cf; struct sk_buff *skb; unsigned long flags; int ret; dev_dbg(mod->dev, "%s: modno %d called\n", __func__, mod->num); spin_lock_irqsave(&mod->lock, flags); /* copy the whole descriptor */ janz_set_page(mod, mod->fastrx_start + (mod->fastrx_num / 16)); desc_addr = mod->regs + ((mod->fastrx_num % 16) * sizeof(desc)); memcpy_fromio(&desc, desc_addr, sizeof(desc)); /* check that we actually have a CAN frame */ if (!(desc.control & DESC_VALID)) { dev_dbg(mod->dev, "%s: no more frames\n", __func__); ret = -ENOBUFS; goto out_unlock; } /* allocate an skb */ skb = alloc_can_skb(ndev, &cf); if (unlikely(skb == NULL)) { dev_dbg(mod->dev, "%s: no more skbs\n", __func__); stats->rx_dropped++; goto err_noalloc; } /* convert the Janz frame into CAN format */ janz_to_can(mod, &desc, cf); /* receive the skb, update statistics */ netif_rx(skb); stats->rx_packets++; stats->rx_bytes += cf->can_dlc; err_noalloc: /* toggle the valid bit and return the descriptor to the ring */ desc.control ^= DESC_VALID; memcpy_toio(desc_addr, &desc, 1); /* update the next buffer pointer */ mod->fastrx_num = (desc.control & DESC_WRAP) ? 0 : (mod->fastrx_num + 1); dev_dbg(mod->dev, "%s: update fast RX num -> %d\n", __func__, mod->fastrx_num); /* there are still more buffers to process */ ret = 0; out_unlock: spin_unlock_irqrestore(&mod->lock, flags); return ret; } static void janz_work(struct work_struct *work) { struct janz_ican3 *mod = container_of(work, struct janz_ican3, work); unsigned int handled = 0; struct janz_msg msg; int ret; dev_dbg(mod->dev, "%s: module number %d\n", __func__, mod->num); /* process all communication messages */ while (true) { ret = janz_recv_msg(mod, &msg); if (ret) { dev_dbg(mod->dev, "%s: no more messages\n", __func__); break; } janz_handle_message(mod, &msg); handled++; } /* process all CAN frames from the fast interface */ while (true) { ret = janz_recv_skb(mod); if (ret) { dev_dbg(mod->dev, "%s: no more CAN frames\n", __func__); break; } handled++; } dev_dbg(mod->dev, "%s: handled %d messages\n", __func__, handled); } /* * Handle a MODULbus interrupt * * Due to the way the firmware works, we must first go through all of the * buffers and unset their IVALID flag, then notify our work function to * process the message. The IVALID flag must be unset before clearing the * interrupt. * * Only after the message has been processed can the VALID flag be unset. */ static void janz_handle_interrupt(struct janz_ican3 *mod) { unsigned long flags; void __iomem *addr; u8 control; spin_lock_irqsave(&mod->lock, flags); /* * If we're using the old-style host interface, we only need to * start the work function, since the fast host interface (and * therefore CAN frame reception) cannot be working yet */ if (mod->iftype == 0) { dev_dbg(mod->dev, "%s: old style host interface\n", __func__); schedule_work(&mod->work); goto out_unlock; } /* * Ok, at least the new-style host interface must be running, so we * need to go through it's buffers and unset all of their DESC_IVALID * bits before clearing the interrupt */ while (true) { dev_dbg(mod->dev, "%s: modno %d new style host interface\n", __func__, mod->num); /* check the new host interface tohost queue */ janz_set_page(mod, 5); addr = mod->regs + (mod->rx_int * sizeof(struct janz_new_desc)); control = ioread8(addr); /* check if we're finished with buffers */ if (!(control & DESC_IVALID)) break; /* write the control bits back with IVALID unset */ control &= ~DESC_IVALID; iowrite8(control, addr); /* * update the interrupt handler's position and schedule * the work function to run at some point in the future */ mod->rx_int = (control & DESC_WRAP) ? 0 : (mod->rx_int + 1); schedule_work(&mod->work); } /* Check the fast host interface for interrupts */ while (true) { dev_dbg(mod->dev, "%s: modno %d fast interface\n", __func__, mod->num); /* check the fast host interface */ janz_set_page(mod, mod->fastrx_start + (mod->fastrx_int / 16)); addr = mod->regs + ((mod->fastrx_int % 16) * sizeof(struct janz_fast_desc)); control = ioread8(addr); /* check if we're finished with buffers */ if (!(control & DESC_IVALID)) break; /* write back the control bits with IVALID unset */ control &= ~DESC_IVALID; iowrite8(control, addr); /* * update the interrupt handler's position and schedule * the work function to run at some point in the future */ mod->fastrx_int = (control & DESC_WRAP) ? 0 : (mod->fastrx_int + 1); schedule_work(&mod->work); } out_unlock: janz_clr_int(mod); spin_unlock_irqrestore(&mod->lock, flags); } static irqreturn_t janz_irq(int irq, void *dev_id) { struct janz_ican3 *mod = dev_id; u8 stat; /* * The interrupt status register on this device reports interrupts * as zeroes instead of using ones like most other devices */ stat = ioread8(mod->ctrl + JANZ_OB_INT_STAT) & (1 << mod->num); dev_dbg(mod->dev, "IRQ: enter stat 0x%.2x\n", stat); if (stat == (1 << mod->num)) { dev_dbg(mod->dev, "IRQ: none pending\n"); return IRQ_NONE; } dev_dbg(mod->dev, "IRQ: module %d\n", mod->num); janz_handle_interrupt(mod); stat = ioread8(mod->ctrl + JANZ_OB_INT_STAT) & (1 << mod->num); dev_dbg(mod->dev, "IRQ: exit stat 0x%.2x\n", stat); return IRQ_HANDLED; } /*----------------------------------------------------------------------------*/ /* TEST CODE */ /*----------------------------------------------------------------------------*/ /* * Reset an ICAN module to its power-on state * * CONTEXT: no network device registered * LOCKING: work function disabled */ static int janz_reset_module(struct janz_ican3 *mod) { u8 val = 1 << mod->num; unsigned long start; u8 runold, runnew; /* disable interrupts so no more work is scheduled */ janz_disable_interrupts(mod); /* flush any pending work */ flush_scheduled_work(); /* the first unallocated page in the DPM is #9 */ mod->free_page = DPM_FREE_START; janz_set_page(mod, 0); runold = ioread8(mod->regs + TARGET_RUNNING); /* reset the module */ iowrite8(val, mod->ctrl + JANZ_OB_RESET_ASSERT); iowrite8(val, mod->ctrl + JANZ_OB_RESET_DEASSERT); /* wait until the module has finished resetting and is running */ start = jiffies; do { janz_set_page(mod, 0); runnew = ioread8(mod->regs + TARGET_RUNNING); if (runnew == (runold ^ 0xff)) { dev_dbg(mod->dev, "%s: success\n", __func__); return 0; } dev_dbg(mod->dev, "%s: msleep(10)\n", __func__); msleep(10); } while (time_before(jiffies, start + HZ / 4)); dev_dbg(mod->dev, "%s: timed out\n", __func__); return -ETIMEDOUT; } static void janz_shutdown_module(struct janz_ican3 *mod) { int ret; dev_dbg(mod->dev, "%s: disconnect and reset module\n", __func__); janz_msg_disconnect(mod); ret = janz_reset_module(mod); if (ret) dev_err(mod->dev, "unable to reset module\n"); } /* * Startup an ICAN module, bringing it into fast mode */ static int janz_startup_module(struct janz_ican3 *mod) { int ret; dev_dbg(mod->dev, "%s: reset module\n", __func__); ret = janz_reset_module(mod); if (ret) { dev_err(mod->dev, "unable to reset module\n"); return ret; } /* re-enable interrupts so we can send messages */ janz_enable_interrupts(mod); dev_dbg(mod->dev, "%s: connect and switch to new if\n", __func__); ret = janz_msg_connect(mod); if (ret) { dev_err(mod->dev, "unable to connect to module\n"); return ret; } janz_init_new_host_interface(mod); ret = janz_msg_newhostif(mod); if (ret) { dev_err(mod->dev, "unable to switch to new-style interface\n"); return ret; } dev_dbg(mod->dev, "%s: enable termination\n", __func__); ret = janz_set_termination(mod, true); if (ret) { dev_err(mod->dev, "unable to enable termination\n"); return ret; } dev_dbg(mod->dev, "%s: enable bus-error\n", __func__); ret = janz_set_buserror(mod, 0xff); if (ret) { dev_err(mod->dev, "unable to set bus-error\n"); return ret; } dev_dbg(mod->dev, "%s: start fast host if\n", __func__); janz_init_fast_host_interface(mod); ret = janz_msg_fasthostif(mod); if (ret) { dev_err(mod->dev, "unable to switch to fast host interface\n"); return ret; } dev_dbg(mod->dev, "%s: set filter to accept everything\n", __func__); ret = janz_set_id_filter(mod, true); if (ret) { dev_err(mod->dev, "unable to set acceptance filter\n"); return ret; } return 0; } /*----------------------------------------------------------------------------*/ /* CAN Network Device */ /*----------------------------------------------------------------------------*/ static int janz_open(struct net_device *ndev) { struct janz_ican3 *mod = netdev_priv(ndev); int ret; /* open the CAN layer */ ret = open_candev(ndev); if (ret) { dev_err(mod->dev, "unable to start CAN layer\n"); return ret; } /* bring the bus online */ ret = janz_set_bus_state(mod, true); if (ret) { dev_err(mod->dev, "unable to set bus-on\n"); close_candev(ndev); return ret; } /* start up the network device */ mod->can.state = CAN_STATE_ERROR_ACTIVE; netif_start_queue(ndev); return 0; } static int janz_stop(struct net_device *ndev) { struct janz_ican3 *mod = netdev_priv(ndev); int ret; dev_dbg(mod->dev, "%s: called\n", __func__); /* stop the network device xmit routine */ netif_stop_queue(ndev); mod->can.state = CAN_STATE_STOPPED; /* bring the bus offline, stop receiving packets */ ret = janz_set_bus_state(mod, false); if (ret) { dev_err(mod->dev, "unable to set bus-off\n"); return ret; } /* close the CAN layer */ close_candev(ndev); return 0; } static int janz_xmit(struct sk_buff *skb, struct net_device *ndev) { struct janz_ican3 *mod = netdev_priv(ndev); struct net_device_stats *stats = &ndev->stats; struct can_frame *cf = (struct can_frame *)skb->data; struct janz_fast_desc desc; void __iomem *desc_addr; unsigned long flags; spin_lock_irqsave(&mod->lock, flags); /* copy the control bits of the descriptor */ janz_set_page(mod, mod->fasttx_start + (mod->fasttx_num / 16)); desc_addr = mod->regs + ((mod->fasttx_num % 16) * sizeof(desc)); memset(&desc, 0, sizeof(desc)); memcpy_fromio(&desc, desc_addr, 1); /* check that we can actually transmit */ if (!(desc.control & DESC_VALID)) { dev_err(mod->dev, "%s: no buffers\n", __func__); stats->tx_dropped++; kfree_skb(skb); goto out_unlock; } /* convert the CAN frame into Janz format */ can_to_janz(mod, cf, &desc); /* * the programming manual says that you must set the IVALID bit, then * interrupt, then set the valid bit. Quite weird, but it seems to be * required for this to work */ desc.control |= DESC_IVALID; memcpy_toio(desc_addr, &desc, sizeof(desc)); janz_set_int(mod); desc.control ^= DESC_VALID; memcpy_toio(desc_addr, &desc, sizeof(desc)); /* update the next buffer pointer */ mod->fasttx_num = (desc.control & DESC_WRAP) ? 0 : (mod->fasttx_num + 1); dev_dbg(mod->dev, "%s: update fast TX num -> %d\n", __func__, mod->fasttx_num); /* update statistics */ stats->tx_packets++; stats->tx_bytes += cf->can_dlc; kfree_skb(skb); out_unlock: spin_unlock_irqrestore(&mod->lock, flags); return NETDEV_TX_OK; } static const struct net_device_ops janz_netdev_ops = { .ndo_open = janz_open, .ndo_stop = janz_stop, .ndo_start_xmit = janz_xmit, }; /*----------------------------------------------------------------------------*/ /* Low-level CAN Device */ /*----------------------------------------------------------------------------*/ /* This structure was stolen from drivers/net/can/sja1000/sja1000.c */ static struct can_bittiming_const janz_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, }; /* * This routine was stolen from drivers/net/can/sja1000/sja1000.c * * The bittiming register command for the ICAN3 just sets the bit timing * registers on the SJA1000 chip directly */ static int janz_set_bittiming(struct net_device *ndev) { struct janz_ican3 *mod = netdev_priv(ndev); struct can_bittiming *bt = &mod->can.bittiming; struct janz_msg msg; u8 btr0, btr1; int ret; btr0 = ((bt->brp - 1) & 0x3f) | (((bt->sjw - 1) & 0x3) << 6); btr1 = ((bt->prop_seg + bt->phase_seg1 - 1) & 0xf) | (((bt->phase_seg2 - 1) & 0x7) << 4); if (mod->can.ctrlmode & CAN_CTRLMODE_3_SAMPLES) btr1 |= 0x80; dev_info(mod->dev, "setting BTR0=0x%02x BTR1=0x%02x\n", btr0, btr1); memset(&msg, 0, sizeof(msg)); msg.spec = MSG_CBTRREQ; msg.len = cpu_to_le16(4); msg.data[0] = 0x00; msg.data[1] = 0x00; msg.data[2] = btr0; msg.data[3] = btr1; ret = janz_send_msg(mod, &msg); if (ret) { dev_dbg(mod->dev, "unable to send CBTRREQ message\n"); return ret; } return 0; } static int janz_set_mode(struct net_device *ndev, enum can_mode mode) { struct janz_ican3 *mod = netdev_priv(ndev); int ret; if (mode != CAN_MODE_START) return -ENOTSUPP; /* bring the bus online */ ret = janz_set_bus_state(mod, true); if (ret) { dev_err(mod->dev, "unable to set bus-on\n"); return ret; } /* start up the network device */ mod->can.state = CAN_STATE_ERROR_ACTIVE; if (netif_queue_stopped(ndev)) netif_wake_queue(ndev); return 0; } /*----------------------------------------------------------------------------*/ /* PCI Subsystem */ /*----------------------------------------------------------------------------*/ static int __devinit ican3_probe(struct platform_device *pdev) { struct janz_platform_data *pdata; struct net_device *ndev; struct janz_ican3 *mod; struct resource *res; struct device *dev; int ret; pdata = pdev->dev.platform_data; if (!pdata) return -ENXIO; dev_dbg(&pdev->dev, "probe: module number %d\n", pdata->modno); /* save the struct device for printing */ dev = &pdev->dev; /* allocate the CAN device and private data */ ndev = alloc_candev(sizeof(*mod), 0); if (!ndev) { dev_err(dev, "unable to allocate CANdev\n"); ret = -ENOMEM; goto out_return; } platform_set_drvdata(pdev, ndev); mod = netdev_priv(ndev); mod->ndev = ndev; mod->dev = &pdev->dev; mod->num = pdata->modno; INIT_WORK(&mod->work, janz_work); spin_lock_init(&mod->lock); /* initialize the software state */ /* the first unallocated page in the DPM is 9 */ mod->free_page = 9; ndev->netdev_ops = &janz_netdev_ops; ndev->flags |= IFF_ECHO; mod->can.bittiming_const = &janz_bittiming_const; mod->can.do_set_bittiming = janz_set_bittiming; mod->can.do_set_mode = janz_set_mode; SET_NETDEV_DEV(ndev, &pdev->dev); /* find our IRQ number */ mod->irq = platform_get_irq(pdev, 0); if (mod->irq < 0) { dev_err(dev, "IRQ line not found\n"); ret = -ENODEV; goto out_free_ndev; } ndev->irq = mod->irq; /* get access to the MODULbus registers for this module */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { dev_err(dev, "MODULbus registers not found\n"); ret = -ENODEV; goto out_free_ndev; } mod->regs = ioremap(res->start, resource_size(res)); if (!mod->regs) { dev_err(dev, "MODULbus registers not ioremap\n"); ret = -ENOMEM; goto out_free_ndev; } /* get access to the control registers for this module */ res = platform_get_resource(pdev, IORESOURCE_MEM, 1); if (!res) { dev_err(dev, "CONTROL registers not found\n"); ret = -ENODEV; goto out_iounmap_regs; } mod->ctrl = ioremap(res->start, resource_size(res)); if (!mod->ctrl) { dev_err(dev, "CONTROL registers not ioremap\n"); ret = -ENOMEM; goto out_iounmap_regs; } /* disable our IRQ, then hookup the IRQ handler */ janz_disable_interrupts(mod); ret = request_irq(mod->irq, janz_irq, IRQF_SHARED, DRV_NAME, mod); if (ret) { dev_err(dev, "unable to request IRQ\n"); goto out_iounmap_ctrl; } /* reset and initialize the CAN controller into fast mode */ ret = janz_startup_module(mod); if (ret) { dev_err(dev, "%s: unable to start CANdev\n", __func__); goto out_free_irq; } /* register with the Linux CAN layer */ ret = register_candev(ndev); if (ret) { dev_err(dev, "%s: unable to register CANdev\n", __func__); goto out_free_irq; } dev_info(dev, "module %d: registered CAN device\n", pdata->modno); return 0; out_free_irq: janz_disable_interrupts(mod); free_irq(mod->irq, mod); out_iounmap_ctrl: iounmap(mod->ctrl); out_iounmap_regs: iounmap(mod->regs); out_free_ndev: free_candev(ndev); out_return: return ret; } static int __devexit ican3_remove(struct platform_device *pdev) { struct net_device *ndev = platform_get_drvdata(pdev); struct janz_ican3 *mod = netdev_priv(ndev); /* unregister the netdevice, stop interrupts */ unregister_netdev(ndev); janz_disable_interrupts(mod); free_irq(mod->irq, mod); /* put the module into reset */ janz_shutdown_module(mod); /* unmap all registers */ iounmap(mod->ctrl); iounmap(mod->regs); free_candev(ndev); return 0; } static struct platform_driver ican3_driver = { .driver = { .name = DRV_NAME, .owner = THIS_MODULE, }, .probe = ican3_probe, .remove = ican3_remove, }; static int __init ican3_init(void) { return platform_driver_register(&ican3_driver); } static void __exit ican3_exit(void) { platform_driver_unregister(&ican3_driver); } MODULE_AUTHOR("Ira W. Snyder <[email protected]>"); MODULE_DESCRIPTION("Janz MODULbus VMOD-ICAN3 Driver"); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:janz-ican3"); module_init(ican3_init); module_exit(ican3_exit); _______________________________________________ Socketcan-core mailing list [email protected] https://lists.berlios.de/mailman/listinfo/socketcan-core
