summaryrefslogtreecommitdiff
path: root/drivers/net/can/softing
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/can/softing')
-rw-r--r--drivers/net/can/softing/Makefile20
-rw-r--r--drivers/net/can/softing/softing.h268
-rw-r--r--drivers/net/can/softing/softing_cs.c427
-rw-r--r--drivers/net/can/softing/softing_fw.c685
-rw-r--r--drivers/net/can/softing/softing_main.c1058
5 files changed, 2458 insertions, 0 deletions
diff --git a/drivers/net/can/softing/Makefile b/drivers/net/can/softing/Makefile
new file mode 100644
index 000000000000..df3fceca7bd0
--- /dev/null
+++ b/drivers/net/can/softing/Makefile
@@ -0,0 +1,20 @@
+# Makefile for softing CAN driver
+
+ifeq ($(KERNELRELEASE),)
+# necessary when used outside kernel
+KERNELDIR := /lib/modules/$(shell uname -r)/build
+PWD := $(shell pwd)
+TOPDIR := $(PWD)/../../../..
+
+modules modules_install clean:
+ $(MAKE) -C $(KERNELDIR) M=$(PWD) $@ TOPDIR=$(TOPDIR)
+
+else
+
+-include $(TOPDIR)/Makefile.common
+
+softing-y := softing_main.o softing_fw.o
+obj-$(CONFIG_CAN_SOFTING) += softing.o
+obj-$(CONFIG_CAN_SOFTING_CS) += softing_cs.o
+
+endif
diff --git a/drivers/net/can/softing/softing.h b/drivers/net/can/softing/softing.h
new file mode 100644
index 000000000000..e43c7f116799
--- /dev/null
+++ b/drivers/net/can/softing/softing.h
@@ -0,0 +1,268 @@
+/*
+ * softing common interfaces
+ *
+ * by Kurt Van Dijck, 06-2008
+ */
+
+#include <linux/interrupt.h>
+#include <linux/netdevice.h>
+#include <linux/can.h>
+#include <linux/can/dev.h>
+
+struct softing;
+struct sofing_desc;
+
+/* special attribute, so we should not rely on the ->priv pointers
+ * before knowing how to interpret these
+ */
+struct softing_attribute;
+
+struct softing_priv {
+ struct can_priv can; /* must be the first member! */
+ struct net_device *netdev;
+ struct softing *card;
+ struct {
+ int pending;
+ /* variables wich hold the circular buffer */
+ int echo_put;
+ int echo_get;
+ } tx;
+ struct can_bittiming_const btr_const;
+ int index;
+ u8 output;
+ u16 chip;
+ struct attribute_group sysfs;
+};
+#define netdev2softing(netdev) ((struct softing_priv *)netdev_priv(netdev))
+
+/* the 'all cards have the same' fields definition */
+extern const struct can_bittiming_const softing_btr_const;
+
+struct softing_desc {
+ unsigned int manf;
+ unsigned int prod;
+ /* generation
+ * 1st with NEC or SJA1000
+ * 8bit, exclusive interrupt, ...
+ * 2nd only SJA11000
+ * 16bit, shared interrupt
+ */
+ int generation;
+ unsigned int freq; /*crystal in MHz */
+ unsigned int max_brp;
+ unsigned int max_sjw;
+ unsigned long dpram_size;
+ char name[32];
+ struct {
+ unsigned long offs;
+ unsigned long addr;
+ char fw[32];
+ } boot, load, app;
+};
+
+struct softing {
+ int nbus;
+ struct softing_priv *bus[2];
+ spinlock_t spin; /* protect this structure & DPRAM access */
+
+ struct {
+ /* indication of firmware status */
+ int up;
+ /* protection of the 'up' variable */
+ struct mutex lock;
+ } fw;
+ struct {
+ int nr;
+ int requested;
+ struct tasklet_struct bh;
+ int svc_count;
+ } irq;
+ struct {
+ int pending;
+ int last_bus;
+ /* keep the bus that last tx'd a message,
+ * in order to let every netdev queue resume
+ */
+ } tx;
+ struct {
+ unsigned long phys;
+ unsigned long size;
+ unsigned char *virt;
+ unsigned char *end;
+ struct softing_fct *fct;
+ struct softing_info *info;
+ struct softing_rx *rx;
+ struct softing_tx *tx;
+ struct softing_irq *irq;
+ unsigned short *command;
+ unsigned short *receipt;
+ } dpram;
+ struct {
+ unsigned short manf;
+ unsigned short prod;
+ u32 serial, fw, hw, lic;
+ u16 chip [2];
+ u32 freq;
+ const char *name;
+ } id;
+ const struct softing_desc *desc;
+ struct {
+ int (*reset) (struct softing *, int);
+ int (*enable_irq)(struct softing *, int);
+ } fn;
+ struct device *dev;
+ /* sysfs */
+ struct attribute_group sysfs;
+ struct softing_attribute *attr;
+ struct attribute **grp;
+};
+
+extern int mk_softing(struct softing *);
+/* fields that must be set already are :
+ * ncan
+ * id.manf
+ * id.prod
+ * fn.reset
+ * fn.enable_irq
+ */
+extern void rm_softing(struct softing *);
+/* usefull functions during operation */
+
+extern const struct softing_desc *
+ softing_lookup_desc(unsigned int manf, unsigned int prod);
+
+extern int softing_default_output(struct softing *card
+ , struct softing_priv *priv);
+extern u32 softing_time2usec(struct softing *card, u32 raw);
+
+extern int softing_fct_cmd(struct softing *card
+ , int cmd, int vector, const char *msg);
+
+extern int softing_bootloader_command(struct softing *card
+ , int command, const char *msg);
+
+/* Load firmware after reset */
+extern int softing_load_fw(const char *file, struct softing *card,
+ unsigned char *virt, unsigned int size, int offset);
+
+/* Load final application firmware after bootloader */
+extern int softing_load_app_fw(const char *file, struct softing *card);
+
+extern int softing_reset_chip(struct softing *card);
+
+/* enable or disable irq
+ * only called with fw.lock locked
+ */
+extern int softing_card_irq(struct softing *card, int enable);
+
+/* called when tx queue is flushed */
+extern void softing_flush_echo_skb(struct softing_priv *priv);
+
+/* reinitaliase the card, apply -1 for bus[01] for 'no change' */
+extern int softing_reinit(struct softing *card, int bus0, int bus1);
+
+/* SOFTING DPRAM mappings */
+struct softing_rx {
+ u8 fifo[16][32];
+ u8 dummy1;
+ u16 rd;
+ u16 dummy2;
+ u16 wr;
+ u16 dummy3;
+ u16 lost_msg;
+} __attribute__((packed));
+
+#define TXMAX 31
+struct softing_tx {
+ u8 fifo[32][16];
+ u8 dummy1;
+ u16 rd;
+ u16 dummy2;
+ u16 wr;
+ u8 dummy3;
+} __attribute__((packed));
+
+struct softing_irq {
+ u8 to_host;
+ u8 to_card;
+} __attribute__((packed));
+
+struct softing_fct {
+ s16 param[20]; /* 0 is index */
+ s16 returned;
+ u8 dummy;
+ u16 host_access;
+} __attribute__((packed));
+
+struct softing_info {
+ u8 dummy1;
+ u16 bus_state;
+ u16 dummy2;
+ u16 bus_state2;
+ u16 dummy3;
+ u16 error_state;
+ u16 dummy4;
+ u16 error_state2;
+ u16 dummy5;
+ u16 reset;
+ u16 dummy6;
+ u16 clear_rcv_fifo;
+ u16 dummy7;
+ u16 dummyxx;
+ u16 dummy8;
+ u16 time_reset;
+ u8 dummy9;
+ u32 time;
+ u32 time_wrap;
+ u8 wr_start;
+ u8 wr_end;
+ u8 dummy10;
+ u16 dummy12;
+ u16 dummy12x;
+ u16 dummy13;
+ u16 reset_rcv_fifo;
+ u8 dummy14;
+ u8 reset_xmt_fifo;
+ u8 read_fifo_levels;
+ u16 rcv_fifo_level;
+ u16 xmt_fifo_level;
+} __attribute__((packed));
+
+/* DPRAM return codes */
+#define RES_NONE 0
+#define RES_OK 1
+#define RES_NOK 2
+#define RES_UNKNOWN 3
+/* DPRAM flags */
+#define CMD_TX 0x01
+#define CMD_ACK 0x02
+#define CMD_XTD 0x04
+#define CMD_RTR 0x08
+#define CMD_ERR 0x10
+#define CMD_BUS2 0x80
+
+/* debug */
+extern int softing_debug;
+
+#define mod_alert(fmt,arg...) { \
+ if (softing_debug >= 0) \
+ printk(KERN_ALERT "[%s] %s:" fmt "\n" \
+ , THIS_MODULE->name \
+ , __func__ \
+ , ##arg); \
+ }
+#define mod_info(fmt,arg...) { \
+ if (softing_debug >= 1) \
+ printk(KERN_INFO "[%s] %s:" fmt "\n"\
+ , THIS_MODULE->name \
+ , __func__ \
+ , ##arg); \
+ }
+#define mod_trace(fmt,arg...) { \
+ if (softing_debug >= 2) \
+ printk(KERN_DEBUG "[%s] %s:" fmt "\n" \
+ , THIS_MODULE->name \
+ , __func__ \
+ , ##arg); \
+ }
+
diff --git a/drivers/net/can/softing/softing_cs.c b/drivers/net/can/softing/softing_cs.c
new file mode 100644
index 000000000000..e33b614de28f
--- /dev/null
+++ b/drivers/net/can/softing/softing_cs.c
@@ -0,0 +1,427 @@
+/*
+* drivers/net/can/softing/softing_cs.c
+*
+* Copyright (C) 2008
+*
+* - Kurt Van Dijck, EIA Electronics
+*
+* This program is free software; you can redistribute it and/or modify
+* it under the terms of the version 2 of the GNU General Public License
+* as published by the Free Software Foundation
+*
+* 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, write to the Free Software
+* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/ptrace.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/timer.h>
+#include <linux/major.h>
+#include <linux/io.h>
+
+#include <pcmcia/cs_types.h>
+#include <pcmcia/cs.h>
+#include <pcmcia/cistpl.h>
+#include <pcmcia/ciscode.h>
+#include <pcmcia/ds.h>
+#include <pcmcia/cisreg.h>
+
+#include <asm/system.h>
+
+#include "softing.h"
+
+struct softing_cs {
+ struct softing softing;
+ win_req_t win;
+};
+#define softing2cs(x) container_of((x), struct softing_cs, softing)
+
+struct lookup {
+ int i;
+ const char *a;
+};
+
+static const char __devinit *lookup_mask(const struct lookup *lp, int *i)
+{
+ for (; lp->a; ++lp) {
+ if (lp->i & *i) {
+ *i &= ~lp->i;
+ return lp->a;
+ }
+ }
+ return 0;
+}
+
+static int card_reset_via_pcmcia(struct softing *sdev, int v)
+{
+ struct pcmcia_device *pcmcia = to_pcmcia_dev(sdev->dev);
+ conf_reg_t reg;
+ reg.Function = 0; /* socket */
+ reg.Action = CS_WRITE;
+ reg.Offset = 2;
+ reg.Value = v ? 0 : 0x20;
+ return pcmcia_access_configuration_register(pcmcia, &reg);
+}
+
+static int card_reset_via_dpram(struct softing *sdev, int v)
+{
+ if (v) {
+ spin_lock_bh(&sdev->spin);
+ sdev->dpram.virt[0xe00] &= ~1;
+ spin_unlock_bh(&sdev->spin);
+ card_reset_via_pcmcia(sdev, v);
+ } else {
+ card_reset_via_pcmcia(sdev, v);
+ spin_lock_bh(&sdev->spin);
+ sdev->dpram.virt[0xe00] |= 1;
+ spin_unlock_bh(&sdev->spin);
+ }
+ return 0;
+}
+
+static int card_enable_irq_via_pcmcia(struct softing *sdev, int v)
+{
+ int ret;
+ struct pcmcia_device *pcmcia = to_pcmcia_dev(sdev->dev);
+ conf_reg_t reg;
+ memset(&reg, 0, sizeof(reg));
+ reg.Function = 0; /* socket */
+ reg.Action = CS_WRITE;
+ reg.Offset = 0;
+ reg.Value = v ? 0x60 : 0;
+ ret = pcmcia_access_configuration_register(pcmcia, &reg);
+ if (ret)
+ mod_alert("failed %u", ret);
+ return ret;
+}
+
+/* TODO: in 2.6.26, __devinitconst works*/
+static const __devinitdata struct lookup pcmcia_io_attr[] = {
+ { IO_DATA_PATH_WIDTH_AUTO , "[auto]" , },
+ { IO_DATA_PATH_WIDTH_8 , "8bit" , },
+ { IO_DATA_PATH_WIDTH_16 , "16bit" , },
+ { 0, 0, },
+};
+
+static const __devinitdata struct lookup pcmcia_mem_attr[] = {
+ { WIN_ADDR_SPACE_IO , "IO" , },
+ { WIN_MEMORY_TYPE_AM , "typeAM" , },
+ { WIN_ENABLE , "enable" , },
+ { WIN_DATA_WIDTH_8 , "8bit" , },
+ { WIN_DATA_WIDTH_16 , "16bit" , },
+ { WIN_DATA_WIDTH_32 , "32bit" , },
+ { WIN_PAGED , "paged" , },
+ { WIN_SHARED , "shared" , },
+ { WIN_FIRST_SHARED , "first_shared", },
+ { WIN_USE_WAIT , "wait" , },
+ { WIN_STRICT_ALIGN , "strict_align", },
+ { WIN_MAP_BELOW_1MB , "below_1MB" , },
+ { WIN_PREFETCH , "prefetch" , },
+ { WIN_CACHEABLE , "cacheable" , },
+ { 0, 0, },
+};
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 28)
+/* backported */
+struct pcmcia_cfg_mem {
+ tuple_t tuple;
+ cisparse_t parse;
+ u8 buf[256];
+ cistpl_cftable_entry_t dflt;
+};
+static int pcmcia_loop_config(struct pcmcia_device *p_dev,
+ int (*conf_check) (struct pcmcia_device *p_dev,
+ cistpl_cftable_entry_t *cfg,
+ cistpl_cftable_entry_t *dflt,
+ unsigned int vcc,
+ void *priv_data),
+ void *priv_data)
+{
+ struct pcmcia_cfg_mem *cfg_mem;
+
+ tuple_t *tuple;
+ int ret = -ENODEV;
+ unsigned int vcc;
+
+ cfg_mem = kzalloc(sizeof(*cfg_mem), GFP_KERNEL);
+ if (cfg_mem == NULL)
+ return -ENOMEM;
+
+ /* get the current Vcc setting */
+ vcc = p_dev->socket->socket.Vcc;
+
+ tuple = &cfg_mem->tuple;
+ tuple->TupleData = cfg_mem->buf;
+ tuple->TupleDataMax = sizeof(cfg_mem->buf)-1;
+ tuple->TupleOffset = 0;
+ tuple->DesiredTuple = CISTPL_CFTABLE_ENTRY;
+ tuple->Attributes = 0;
+
+ ret = pcmcia_get_first_tuple(p_dev, tuple);
+ while (!ret) {
+ cistpl_cftable_entry_t *cfg = &cfg_mem->parse.cftable_entry;
+
+ if (pcmcia_get_tuple_data(p_dev, tuple))
+ goto next_entry;
+
+ if (pcmcia_parse_tuple(p_dev, tuple, &cfg_mem->parse))
+ goto next_entry;
+
+ /* default values */
+ p_dev->conf.ConfigIndex = cfg->index;
+ if (cfg->flags & CISTPL_CFTABLE_DEFAULT)
+ cfg_mem->dflt = *cfg;
+
+ ret = conf_check(p_dev, cfg, &cfg_mem->dflt, vcc, priv_data);
+ if (!ret)
+ break;
+
+next_entry:
+ ret = pcmcia_get_next_tuple(p_dev, tuple);
+ }
+ kfree(cfg_mem);
+ return ret;
+}
+#endif
+
+static int dev_conf_check(struct pcmcia_device *pdev,
+ cistpl_cftable_entry_t *cf, cistpl_cftable_entry_t *def_cf,
+ unsigned int vcc, void *priv_data)
+{
+ struct softing_cs *csdev = priv_data;
+ struct softing *sdev = &csdev->softing;
+ int ret;
+
+ if (!cf->index)
+ goto do_next;
+ /* power settings (Vcc & Vpp) */
+ if (cf->vcc.present & (1 << CISTPL_POWER_VNOM)) {
+ if (vcc != cf->vcc.param[CISTPL_POWER_VNOM]/10000) {
+ mod_alert("%s: cf->Vcc mismatch\n", __FILE__);
+ goto do_next;
+ }
+ } else if (def_cf->vcc.present & (1 << CISTPL_POWER_VNOM)) {
+ if (vcc != def_cf->vcc.param[CISTPL_POWER_VNOM]/10000) {
+ mod_alert("%s: cf->Vcc mismatch\n", __FILE__);
+ goto do_next;
+ }
+ }
+ if (cf->vpp1.present & (1 << CISTPL_POWER_VNOM))
+ pdev->conf.Vpp
+ = cf->vpp1.param[CISTPL_POWER_VNOM] / 10000;
+
+ else if (def_cf->vpp1.present & (1 << CISTPL_POWER_VNOM))
+ pdev->conf.Vpp
+ = def_cf->vpp1.param[CISTPL_POWER_VNOM] / 10000;
+
+ /* interrupt ? */
+ if (cf->irq.IRQInfo1 || def_cf->irq.IRQInfo1)
+ pdev->conf.Attributes |= CONF_ENABLE_IRQ;
+
+ /* IO window */
+ pdev->io.NumPorts1
+ = pdev->io.NumPorts2
+ = 0;
+ /* Memory window */
+ if ((cf->mem.nwin > 0) || (def_cf->mem.nwin > 0)) {
+ memreq_t map;
+ cistpl_mem_t *mem
+ = (cf->mem.nwin) ? &cf->mem : &def_cf->mem;
+ /* softing specific: choose 8 or 16bit access */
+ csdev->win.Attributes = ((sdev->desc->generation >= 2)
+ ? WIN_DATA_WIDTH_16 : WIN_DATA_WIDTH_8)
+ | WIN_MEMORY_TYPE_CM
+ | WIN_ENABLE;
+ csdev->win.Base = mem->win[0].host_addr;
+ csdev->win.Size = mem->win[0].len;
+ csdev->win.AccessSpeed = 0;
+ ret = pcmcia_request_window(&pdev, &csdev->win, &pdev->win);
+ if (ret) {
+ mod_alert("pcmcia_request_window() mismatch\n");
+ goto do_next;
+ }
+ /* softing specific: choose slower access for old cards */
+ if (sdev->desc->generation < 2) {
+ pdev->win->ctl.flags
+ = MAP_ACTIVE | MAP_USE_WAIT;
+ pdev->win->ctl.speed = 3;
+ }
+ map.Page = 0;
+ map.CardOffset = mem->win[0].card_addr;
+ if (pcmcia_map_mem_page(pdev->win, &map)) {
+ mod_alert("pcmcia_map_mem_page() mismatch\n");
+ goto do_next_win;
+ }
+ } else {
+ mod_info("no memory window in tuple %u", cf->index);
+ goto do_next;
+ }
+ return 0;
+do_next_win:
+do_next:
+ pcmcia_disable_device(pdev);
+ return -ENODEV;
+}
+
+static void driver_remove(struct pcmcia_device *pcmcia)
+{
+ struct softing *card = (struct softing *)pcmcia->priv;
+ struct softing_cs *cs = softing2cs(card);
+ mod_trace("%s,device'%s'", card->id.name, pcmcia->devname);
+ rm_softing(card);
+ /* release pcmcia stuff */
+ pcmcia_disable_device(pcmcia);
+ /* free bits */
+ kfree(cs);
+}
+
+static int __devinit driver_probe(struct pcmcia_device *pcmcia)
+{
+ struct softing_cs *cs;
+ struct softing *card;
+
+ mod_trace("on %s", pcmcia->devname);
+
+ /* Create new softing device */
+ cs = kzalloc(sizeof(*cs), GFP_KERNEL);
+ if (!cs)
+ goto no_mem;
+ /* setup links */
+ card = &cs->softing;
+ pcmcia->priv = card;
+ card->dev = &pcmcia->dev;
+ /* properties */
+ card->id.manf = pcmcia->manf_id;
+ card->id.prod = pcmcia->card_id;
+ card->desc = softing_lookup_desc(card->id.manf, card->id.prod);
+ if (card->desc->generation >= 2) {
+ card->fn.reset = card_reset_via_dpram;
+ } else {
+ card->fn.reset = card_reset_via_pcmcia;
+ card->fn.enable_irq = card_enable_irq_via_pcmcia;
+ }
+
+ card->nbus = 2;
+ /* pcmcia presets */
+ pcmcia->irq.Attributes = IRQ_TYPE_DYNAMIC_SHARING;
+ pcmcia->irq.IRQInfo1 = IRQ_LEVEL_ID;
+ pcmcia->irq.Handler = 0;
+ pcmcia->conf.Attributes = 0;
+ pcmcia->conf.IntType = INT_MEMORY_AND_IO;
+
+ if (pcmcia_loop_config(pcmcia, dev_conf_check, cs))
+ goto config_failed;
+
+ if (pcmcia_request_irq(pcmcia, &pcmcia->irq))
+ goto config_failed;
+
+ if (pcmcia_request_configuration(pcmcia, &pcmcia->conf))
+ goto config_failed;
+
+ card->dpram.phys = cs->win.Base;
+ card->dpram.size = cs->win.Size;
+
+ if (card->dpram.size != 0x1000) {
+ mod_alert("dpram size 0x%lx mismatch\n", card->dpram.size);
+ goto wrong_dpram;
+ }
+
+ /* Finally, report what we've done */
+ printk(KERN_INFO "[%s] %s: index 0x%02x",
+ THIS_MODULE->name,
+ pcmcia->devname,
+ pcmcia->conf.ConfigIndex);
+ if (pcmcia->conf.Vpp)
+ printk(", Vpp %d.%d", pcmcia->conf.Vpp/10, pcmcia->conf.Vpp%10);
+ if (pcmcia->conf.Attributes & CONF_ENABLE_IRQ) {
+ printk(", irq %d", pcmcia->irq.AssignedIRQ);
+ card->irq.nr = pcmcia->irq.AssignedIRQ;
+ }
+ if (pcmcia->win) {
+ int tmp;
+ const char *p;
+ printk(", mem 0x%08lx-0x%08lx"
+ , card->dpram.phys
+ , card->dpram.phys + card->dpram.size-1);
+ tmp = cs->win.Attributes;
+ while (tmp) {
+ p = lookup_mask(pcmcia_mem_attr, &tmp);
+ if (p)
+ printk(" %s", p);
+ }
+ }
+ printk("\n");
+
+ if (mk_softing(card))
+ goto softing_failed;
+ return 0;
+
+softing_failed:
+wrong_dpram:
+config_failed:
+ kfree(cs);
+no_mem:
+ pcmcia_disable_device(pcmcia);
+ return -ENODEV;
+}
+
+static struct pcmcia_device_id driver_ids[] = {
+ /* softing */
+ PCMCIA_DEVICE_MANF_CARD(0x0168, 0x0001),
+ PCMCIA_DEVICE_MANF_CARD(0x0168, 0x0002),
+ PCMCIA_DEVICE_MANF_CARD(0x0168, 0x0004),
+ PCMCIA_DEVICE_MANF_CARD(0x0168, 0x0005),
+ /* vector , manufacturer? */
+ PCMCIA_DEVICE_MANF_CARD(0x0168, 0x0081),
+ PCMCIA_DEVICE_MANF_CARD(0x0168, 0x0084),
+ PCMCIA_DEVICE_MANF_CARD(0x0168, 0x0085),
+ /* EDIC */
+ PCMCIA_DEVICE_MANF_CARD(0x0168, 0x0102),
+ PCMCIA_DEVICE_MANF_CARD(0x0168, 0x0105),
+ PCMCIA_DEVICE_NULL,
+};
+
+MODULE_DEVICE_TABLE(pcmcia, driver_ids);
+
+static struct pcmcia_driver softing_cs_driver = {
+ .owner = THIS_MODULE,
+ .drv = {
+ .name = "softing_cs",
+ },
+ .probe = driver_probe,
+ .remove = driver_remove,
+ .id_table = driver_ids,
+};
+
+static int __init mod_start(void)
+{
+ mod_trace("");
+ return pcmcia_register_driver(&softing_cs_driver);
+}
+
+static void __exit mod_stop(void)
+{
+ mod_trace("");
+ pcmcia_unregister_driver(&softing_cs_driver);
+}
+
+module_init(mod_start);
+module_exit(mod_stop);
+
+MODULE_DESCRIPTION("softing CANcard driver"
+ ", links PCMCIA card to softing driver");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("softing CANcard2");
+
diff --git a/drivers/net/can/softing/softing_fw.c b/drivers/net/can/softing/softing_fw.c
new file mode 100644
index 000000000000..4d20774c9cb9
--- /dev/null
+++ b/drivers/net/can/softing/softing_fw.c
@@ -0,0 +1,685 @@
+/*
+* drivers/net/can/softing/softing_fw.c
+*
+* Copyright (C) 2008
+*
+* - Kurt Van Dijck, EIA Electronics
+*
+* This program is free software; you can redistribute it and/or modify
+* it under the terms of the version 2 of the GNU General Public License
+* as published by the Free Software Foundation
+*
+* 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, write to the Free Software
+* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/firmware.h>
+#include <linux/interrupt.h>
+#include <linux/mutex.h>
+#include <linux/io.h>
+
+#include "softing.h"
+
+#define fw_dir "softing-4.6/"
+
+const struct can_bittiming_const softing_btr_const = {
+ .tseg1_min = 1,
+ .tseg1_max = 16,
+ .tseg2_min = 1,
+ .tseg2_max = 8,
+ .sjw_max = 4, /* overruled */
+ .brp_min = 1,
+ .brp_max = 32, /* overruled */
+ .brp_inc = 1,
+};
+
+static const struct softing_desc carddescs[] = {
+{
+ .name = "CANcard",
+ .manf = 0x0168, .prod = 0x001,
+ .generation = 1,
+ .freq = 16, .max_brp = 32, .max_sjw = 4,
+ .dpram_size = 0x0800,
+ .boot = {0x0000, 0x000000, fw_dir "bcard.bin",},
+ .load = {0x0120, 0x00f600, fw_dir "ldcard.bin",},
+ .app = {0x0010, 0x0d0000, fw_dir "cancard.bin",},
+}, {
+ .name = "CANcard-NEC",
+ .manf = 0x0168, .prod = 0x002,
+ .generation = 1,
+ .freq = 16, .max_brp = 32, .max_sjw = 4,
+ .dpram_size = 0x0800,
+ .boot = {0x0000, 0x000000, fw_dir "bcard.bin",},
+ .load = {0x0120, 0x00f600, fw_dir "ldcard.bin",},
+ .app = {0x0010, 0x0d0000, fw_dir "cancard.bin",},
+}, {
+ .name = "CANcard-SJA",
+ .manf = 0x0168, .prod = 0x004,
+ .generation = 1,
+ .freq = 20, .max_brp = 32, .max_sjw = 4,
+ .dpram_size = 0x0800,
+ .boot = {0x0000, 0x000000, fw_dir "bcard.bin",},
+ .load = {0x0120, 0x00f600, fw_dir "ldcard.bin",},
+ .app = {0x0010, 0x0d0000, fw_dir "cansja.bin",},
+}, {
+ .name = "CANcard-2",
+ .manf = 0x0168, .prod = 0x005,
+ .generation = 2,
+ .freq = 24, .max_brp = 64, .max_sjw = 4,
+ .dpram_size = 0x0800,
+ .boot = {0x0000, 0x000000, fw_dir "bcard2.bin",},
+ .load = {0x0120, 0x00f600, fw_dir "ldcard2.bin",},
+ .app = {0x0010, 0x0d0000, fw_dir "cancrd2.bin",},
+}, {
+ .name = "Vector-CANcard",
+ .manf = 0x0168, .prod = 0x081,
+ .generation = 1,
+ .freq = 16, .max_brp = 64, .max_sjw = 4,
+ .dpram_size = 0x0800,
+ .boot = {0x0000, 0x000000, fw_dir "bcard.bin",},
+ .load = {0x0120, 0x00f600, fw_dir "ldcard.bin",},
+ .app = {0x0010, 0x0d0000, fw_dir "cancard.bin",},
+}, {
+ .name = "Vector-CANcard-SJA",
+ .manf = 0x0168, .prod = 0x084,
+ .generation = 1,
+ .freq = 20, .max_brp = 32, .max_sjw = 4,
+ .dpram_size = 0x0800,
+ .boot = {0x0000, 0x000000, fw_dir "bcard.bin",},
+ .load = {0x0120, 0x00f600, fw_dir "ldcard.bin",},
+ .app = {0x0010, 0x0d0000, fw_dir "cansja.bin",},
+}, {
+ .name = "Vector-CANcard-2",
+ .manf = 0x0168, .prod = 0x085,
+ .generation = 2,
+ .freq = 24, .max_brp = 64, .max_sjw = 4,
+ .dpram_size = 0x0800,
+ .boot = {0x0000, 0x000000, fw_dir "bcard2.bin",},
+ .load = {0x0120, 0x00f600, fw_dir "ldcard2.bin",},
+ .app = {0x0010, 0x0d0000, fw_dir "cancrd2.bin",},
+}, {
+ .name = "EDICcard-NEC",
+ .manf = 0x0168, .prod = 0x102,
+ .generation = 1,
+ .freq = 16, .max_brp = 64, .max_sjw = 4,
+ .dpram_size = 0x0800,
+ .boot = {0x0000, 0x000000, fw_dir "bcard.bin",},
+ .load = {0x0120, 0x00f600, fw_dir "ldcard.bin",},
+ .app = {0x0010, 0x0d0000, fw_dir "cancard.bin",},
+}, {
+ .name = "EDICcard-2",
+ .manf = 0x0168, .prod = 0x105,
+ .generation = 2,
+ .freq = 24, .max_brp = 64, .max_sjw = 4,
+ .dpram_size = 0x0800,
+ .boot = {0x0000, 0x000000, fw_dir "bcard2.bin",},
+ .load = {0x0120, 0x00f600, fw_dir "ldcard2.bin",},
+ .app = {0x0010, 0x0d0000, fw_dir "cancrd2.bin",},
+ },
+
+/* never tested, but taken from original softing */
+{ .name = "CAN-AC2-104",
+ .manf = 0x0000, .prod = 0x009,
+ .generation = 1,
+ .freq = 25, .max_brp = 64, .max_sjw = 4,
+ .dpram_size = 0x1000,
+ .boot = {0x0000, 0x000000, fw_dir "boot104.bin",},
+ .load = {0x0800, 0x035000, fw_dir "ld104.bin",},
+ .app = {0x0010, 0x120000, fw_dir "canpc104.bin",},
+ },
+{0, 0,},
+};
+
+const struct softing_desc *softing_lookup_desc
+ (unsigned int manf, unsigned int prod)
+{
+ const struct softing_desc *lp = carddescs;
+ for (; lp->name; ++lp) {
+ if ((lp->manf == manf) && (lp->prod == prod))
+ return lp;
+ }
+ return 0;
+}
+EXPORT_SYMBOL(softing_lookup_desc);
+
+int softing_fct_cmd(struct softing *card, int cmd, int vector, const char *msg)
+{
+ int ret;
+ unsigned long stamp;
+ if (vector == RES_OK)
+ vector = RES_NONE;
+ card->dpram.fct->param[0] = cmd;
+ card->dpram.fct->host_access = vector;
+ /* be sure to flush this to the card */
+ wmb();
+ stamp = jiffies;
+ /*wait for card */
+ do {
+ ret = card->dpram.fct->host_access;
+ /* don't have any cached variables */
+ rmb();
+ if (ret == RES_OK) {
+ /*don't read return-value now */
+ ret = card->dpram.fct->returned;
+ if (ret)
+ mod_alert("%s returned %u", msg, ret);
+ return 0;
+ }
+ if ((jiffies - stamp) >= 1 * HZ)
+ break;
+ if (in_interrupt())
+ /* go as fast as possible */
+ continue;
+ /* process context => relax */
+ schedule();
+ } while (!signal_pending(current));
+
+ if (ret == RES_NONE) {
+ mod_alert("%s, no response from card on %u/0x%02x"
+ , msg, cmd, vector);
+ return 1;
+ } else {
+ mod_alert("%s, bad response from card on %u/0x%02x, 0x%04x"
+ , msg, cmd, vector, ret);
+ /*make sure to return something not 0 */
+ return ret ? ret : 1;
+ }
+}
+
+int softing_bootloader_command(struct softing *card
+ , int command, const char *msg)
+{
+ int ret;
+ unsigned long stamp;
+ card->dpram.receipt[0] = RES_NONE;
+ card->dpram.command[0] = command;
+ /* be sure to flush this to the card */
+ wmb();
+ stamp = jiffies;
+ /*wait for card */
+ do {
+ ret = card->dpram.receipt[0];
+ /* don't have any cached variables */
+ rmb();
+ if (ret == RES_OK)
+ return 0;
+ if ((jiffies - stamp) >= (3 * HZ))
+ break;
+ schedule();
+ } while (!signal_pending(current));
+
+ switch (ret) {
+ case RES_NONE:
+ mod_alert("%s: no response from card", msg);
+ break;
+ case RES_NOK:
+ mod_alert("%s: response from card nok", msg);
+ break;
+ case RES_UNKNOWN:
+ mod_alert("%s: command 0x%04x unknown", msg, command);
+ break;
+ default:
+ mod_alert("%s: bad response from card (%u)]", msg, ret);
+ break;
+ }
+ return ret ? ret : 1;
+}
+
+struct fw_hdr {
+ u16 type;
+ u32 addr;
+ u16 len;
+ u16 checksum;
+ const unsigned char *base;
+} __attribute__ ((packed));
+
+static int fw_parse(const unsigned char **pmem, struct fw_hdr *hdr)
+{
+ u16 tmp;
+ const unsigned char *mem;
+ const unsigned char *end;
+ mem = *pmem;
+ hdr->type = (mem[0] << 0) | (mem[1] << 8);
+ hdr->addr = (mem[2] << 0) | (mem[3] << 8)
+ | (mem[4] << 16) | (mem[5] << 24);
+ hdr->len = (mem[6] << 0) | (mem[7] << 8);
+ hdr->base = &mem[8];
+ hdr->checksum =
+ (hdr->base[hdr->len] << 0) | (hdr->base[hdr->len + 1] << 8);
+ for (tmp = 0, mem = *pmem, end = &hdr->base[hdr->len]; mem < end; ++mem)
+ tmp += *mem;
+ if (tmp != hdr->checksum)
+ return EINVAL;
+ *pmem += 10 + hdr->len;
+ return 0;
+}
+
+int softing_load_fw(const char *file, struct softing *card,
+ unsigned char *virt, unsigned int size, int offset)
+{
+ const struct firmware *fw;
+ const unsigned char *mem;
+ const unsigned char *end;
+ int ret;
+ u32 start_addr;
+ struct fw_hdr rec;
+ int ok = 0;
+ unsigned char buf[256];
+
+ ret = request_firmware(&fw, file, card->dev);
+ if (ret) {
+ mod_alert("request_firmware(%s) got %i", file, ret);
+ return ret;
+ }
+ mod_trace("%s, firmware(%s) got %u bytes, offset %c0x%04x"
+ , card->id.name, file, (unsigned int)fw->size,
+ (offset >= 0) ? '+' : '-', abs(offset));
+ /* parse the firmware */
+ mem = fw->data;
+ end = &mem[fw->size];
+ /* look for header record */
+ if (fw_parse(&mem, &rec))
+ goto fw_end;
+ if (rec.type != 0xffff) {
+ mod_alert("firware starts with type 0x%04x", rec.type);
+ goto fw_end;
+ }
+ if (strncmp("Structured Binary Format, Softing GmbH"
+ , rec.base, rec.len)) {
+ mod_info("firware string '%.*s'", rec.len, rec.base);
+ goto fw_end;
+ }
+ ok |= 1;
+ /* ok, we had a header */
+ while (mem < end) {
+ if (fw_parse(&mem, &rec))
+ break;
+ if (rec.type == 3) {
+ /*start address */
+ start_addr = rec.addr;
+ ok |= 2;
+ continue;
+ } else if (rec.type == 1) {
+ /*eof */
+ ok |= 4;
+ goto fw_end;
+ } else if (rec.type != 0) {
+ mod_alert("unknown record type 0x%04x", rec.type);
+ break;
+ }
+
+ if ((rec.addr + rec.len + offset) > size) {
+ mod_alert("firmware out of range (0x%08x / 0x%08x)"
+ , (rec.addr + rec.len + offset), size);
+ goto fw_end;
+ }
+ memcpy_toio(&virt[rec.addr + offset],
+ rec.base, rec.len);
+ /* be sure to flush caches from IO space */
+ mb();
+ if (rec.len > sizeof(buf)) {
+ mod_info("record is big (%u bytes), not verifying"
+ , rec.len);
+ continue;
+ }
+ /* verify record data */
+ memcpy_fromio(buf, &virt[rec.addr + offset], rec.len);
+ if (!memcmp(buf, rec.base, rec.len))
+ /* is ok */
+ continue;
+ mod_alert("0x%08x:0x%03x at 0x%p failed", rec.addr, rec.len
+ , &virt[rec.addr + offset]);
+ goto fw_end;
+ }
+fw_end:
+ release_firmware(fw);
+ if (0x5 == (ok & 0x5)) {
+ /*got eof & start */
+ return 0;
+ }
+ mod_alert("failed");
+ return EINVAL;
+}
+
+int softing_load_app_fw(const char *file, struct softing *card)
+{
+ const struct firmware *fw;
+ const unsigned char *mem;
+ const unsigned char *end;
+ int ret;
+ struct fw_hdr rec;
+ int ok = 0;
+ u32 start_addr = 0;
+ u16 rx_sum;
+ unsigned int sum;
+ const unsigned char *mem_lp;
+ const unsigned char *mem_end;
+ struct cpy {
+ u32 src;
+ u32 dst;
+ u16 len;
+ u8 do_cs;
+ } __attribute__((packed)) *pcpy =
+ (struct cpy *)&card->dpram.command[1];
+
+ ret = request_firmware(&fw, file, card->dev);
+ if (ret) {
+ mod_alert("request_firmware(%s) got %i", file, ret);
+ return ret;
+ }
+ mod_trace("%s, firmware(%s) got %lu bytes", card->id.name, file,
+ (unsigned long)fw->size);
+ /* parse the firmware */
+ mem = fw->data;
+ end = &mem[fw->size];
+ /* look for header record */
+ if (fw_parse(&mem, &rec))
+ goto fw_end;
+ if (rec.type != 0xffff) {
+ mod_alert("firware starts with type 0x%04x", rec.type);
+ goto fw_end;
+ }
+ if (strncmp("Structured Binary Format, Softing GmbH"
+ , rec.base, rec.len)) {
+ mod_info("firware string '%.*s'", rec.len, rec.base);
+ goto fw_end;
+ }
+ ok |= 1;
+ /* ok, we had a header */
+ while (mem < end) {
+ if (fw_parse(&mem, &rec))
+ break;
+
+ if (rec.type == 3) {
+ /*start address */
+ start_addr = rec.addr;
+ ok |= 2;
+ continue;
+ } else if (rec.type == 1) {
+ /*eof */
+ ok |= 4;
+ goto fw_end;
+ } else if (rec.type != 0) {
+ mod_alert("unknown record type 0x%04x", rec.type);
+ break;
+ }
+ /* regualar data */
+ for (sum = 0, mem_lp = rec.base, mem_end = &mem_lp[rec.len];
+ mem_lp < mem_end; ++mem_lp)
+ sum += *mem_lp;
+
+ memcpy_toio(&card->dpram. virt[card->desc->app.offs],
+ rec.base, rec.len);
+ pcpy->src = card->desc->app.offs + card->desc->app.addr;
+ pcpy->dst = rec.addr;
+ pcpy->len = rec.len;
+ pcpy->do_cs = 1;
+ if (softing_bootloader_command(card, 1, "loading app."))
+ goto fw_end;
+ /*verify checksum */
+ rx_sum = card->dpram.receipt[1];
+ if (rx_sum != (sum & 0xffff)) {
+ mod_alert("SRAM seems to be damaged"
+ ", wanted 0x%04x, got 0x%04x", sum, rx_sum);
+ goto fw_end;
+ }
+ }
+fw_end:
+ release_firmware(fw);
+ if (ok == 7) {
+ /*got start, start_addr, & eof */
+ struct cmd {
+ u32 start;
+ u8 autorestart;
+ } *pcmd = (struct cmd *)&card->dpram.command[1];
+ pcmd->start = start_addr;
+ pcmd->autorestart = 1;
+ if (!softing_bootloader_command(card, 3, "start app.")) {
+ mod_trace("%s: card app. run at 0x%06x"
+ , card->id.name, start_addr);
+ return 0;
+ }
+ }
+ mod_alert("failed");
+ return EINVAL;
+}
+
+int softing_reset_chip(struct softing *card)
+{
+ mod_trace("%s", card->id.name);
+ do {
+ /*reset chip */
+ card->dpram.info->reset_rcv_fifo = 0;
+ card->dpram.info->reset = 1;
+ if (!softing_fct_cmd(card, 0, 0, "reset_chip"))
+ break;
+ if (signal_pending(current))
+ goto failed;
+ /*sync */
+ if (softing_fct_cmd(card, 99, 0x55, "sync-a"))
+ goto failed;
+ if (softing_fct_cmd(card, 99, 0xaa, "sync-a"))
+ goto failed;
+ } while (1);
+ card->tx.pending = 0;
+ return 0;
+failed:
+ return -EIO;
+}
+
+int softing_reinit(struct softing *card, int bus0, int bus1)
+{
+ int ret;
+ int restarted_bus = -1;
+ mod_trace("%s", card->id.name);
+ if (!card->fw.up)
+ return -EIO;
+ if (bus0 < 0) {
+ bus0 = (card->bus[0]->netdev->flags & IFF_UP) ? 1 : 0;
+ if (bus0)
+ restarted_bus = 0;
+ } else if (bus1 < 0) {
+ bus1 = (card->bus[1]->netdev->flags & IFF_UP) ? 1 : 0;
+ if (bus1)
+ restarted_bus = 1;
+ }
+ /* collect info */
+ if (card->bus[0]) {
+ card->bus[0]->can.state = CAN_STATE_STOPPED;
+ softing_flush_echo_skb(card->bus[0]);
+ }
+ if (card->bus[1]) {
+ card->bus[1]->can.state = CAN_STATE_STOPPED;
+ softing_flush_echo_skb(card->bus[1]);
+ }
+
+ /* start acting */
+ if (!bus0 && !bus1) {
+ softing_card_irq(card, 0);
+ softing_reset_chip(card);
+ if (card->bus[0])
+ netif_carrier_off(card->bus[0]->netdev);
+ if (card->bus[1])
+ netif_carrier_off(card->bus[1]->netdev);
+ return 0;
+ }
+ ret = softing_reset_chip(card);
+ if (ret) {
+ softing_card_irq(card, 0);
+ return ret;
+ }
+ if (bus0) {
+ /*init chip */
+ card->dpram.fct->param[1] = card->bus[0]->can.bittiming.brp;
+ card->dpram.fct->param[2] = card->bus[0]->can.bittiming.sjw;
+ card->dpram.fct->param[3] =
+ card->bus[0]->can.bittiming.phase_seg1 +
+ card->bus[0]->can.bittiming.prop_seg;
+ card->dpram.fct->param[4] =
+ card->bus[0]->can.bittiming.phase_seg2;
+ card->dpram.fct->param[5] = (card->bus[0]->can.ctrlmode &
+ CAN_CTRLMODE_3_SAMPLES)?1:0;
+ if (softing_fct_cmd(card, 1, 0, "initialize_chip[0]"))
+ goto failed;
+ /*set mode */
+ card->dpram.fct->param[1] = 0;
+ card->dpram.fct->param[2] = 0;
+ if (softing_fct_cmd(card, 3, 0, "set_mode[0]"))
+ goto failed;
+ /*set filter */
+ card->dpram.fct->param[1] = 0x0000;/*card->bus[0].s.msg; */
+ card->dpram.fct->param[2] = 0x07ff;/*card->bus[0].s.msk; */
+ card->dpram.fct->param[3] = 0x0000;/*card->bus[0].l.msg; */
+ card->dpram.fct->param[4] = 0xffff;/*card->bus[0].l.msk; */
+ card->dpram.fct->param[5] = 0x0000;/*card->bus[0].l.msg >> 16;*/
+ card->dpram.fct->param[6] = 0x1fff;/*card->bus[0].l.msk >> 16;*/
+ if (softing_fct_cmd(card, 7, 0, "set_filter[0]"))
+ goto failed;
+ /*set output control */
+ card->dpram.fct->param[1] = card->bus[0]->output;
+ if (softing_fct_cmd(card, 5, 0, "set_output[0]"))
+ goto failed;
+ }
+ if (bus1) {
+ /*init chip2 */
+ card->dpram.fct->param[1] = card->bus[1]->can.bittiming.brp;
+ card->dpram.fct->param[2] = card->bus[1]->can.bittiming.sjw;
+ card->dpram.fct->param[3] =
+ card->bus[1]->can.bittiming.phase_seg1 +
+ card->bus[1]->can.bittiming.prop_seg;
+ card->dpram.fct->param[4] =
+ card->bus[1]->can.bittiming.phase_seg2;
+ card->dpram.fct->param[5] = (card->bus[1]->can.ctrlmode &
+ CAN_CTRLMODE_3_SAMPLES)?1:0;
+ if (softing_fct_cmd(card, 2, 0, "initialize_chip[1]"))
+ goto failed;
+ /*set mode2 */
+ card->dpram.fct->param[1] = 0;
+ card->dpram.fct->param[2] = 0;
+ if (softing_fct_cmd(card, 4, 0, "set_mode[1]"))
+ goto failed;
+ /*set filter2 */
+ card->dpram.fct->param[1] = 0x0000;/*card->bus[1].s.msg; */
+ card->dpram.fct->param[2] = 0x07ff;/*card->bus[1].s.msk; */
+ card->dpram.fct->param[3] = 0x0000;/*card->bus[1].l.msg; */
+ card->dpram.fct->param[4] = 0xffff;/*card->bus[1].l.msk; */
+ card->dpram.fct->param[5] = 0x0000;/*card->bus[1].l.msg >> 16;*/
+ card->dpram.fct->param[6] = 0x1fff;/*card->bus[1].l.msk >> 16;*/
+ if (softing_fct_cmd(card, 8, 0, "set_filter[1]"))
+ goto failed;
+ /*set output control2 */
+ card->dpram.fct->param[1] = card->bus[1]->output;
+ if (softing_fct_cmd(card, 6, 0, "set_output[1]"))
+ goto failed;
+ }
+ /*set interrupt */
+ /*enable_error_frame */
+ if (softing_fct_cmd(card, 51, 0, "enable_error_frame"))
+ goto failed;
+ /*initialize interface */
+ card->dpram.fct->param[1] = 1;
+ card->dpram.fct->param[2] = 1;
+ card->dpram.fct->param[3] = 1;
+ card->dpram.fct->param[4] = 1;
+ card->dpram.fct->param[5] = 1;
+ card->dpram.fct->param[6] = 1;
+ card->dpram.fct->param[7] = 1;
+ card->dpram.fct->param[8] = 1;
+ card->dpram.fct->param[9] = 1;
+ card->dpram.fct->param[10] = 1;
+ if (softing_fct_cmd(card, 17, 0, "initialize_interface"))
+ goto failed;
+ /*enable_fifo */
+ if (softing_fct_cmd(card, 36, 0, "enable_fifo"))
+ goto failed;
+ /*enable fifo tx ack */
+ if (softing_fct_cmd(card, 13, 0, "fifo_tx_ack[0]"))
+ goto failed;
+ /*enable fifo tx ack2 */
+ if (softing_fct_cmd(card, 14, 0, "fifo_tx_ack[1]"))
+ goto failed;
+ /*enable timestamps */
+ /*is default, no code found */
+ /*start_chip */
+ if (softing_fct_cmd(card, 11, 0, "start_chip"))
+ goto failed;
+ card->dpram.info->bus_state = 0;
+ card->dpram.info->bus_state2 = 0;
+ mod_info("ok for %s, %s/%s\n", card->bus[0]->netdev->name,
+ card->bus[1]->netdev->name, card->id.name);
+ if (card->desc->generation < 2) {
+ card->dpram.irq->to_host = 0;
+ /* flush the DPRAM caches */
+ wmb();
+ }
+ /*run once */
+ /*the bottom halve will start flushing the tx-queue too */
+ tasklet_schedule(&card->irq.bh);
+
+ ret = softing_card_irq(card, 1);
+ if (ret)
+ goto failed;
+
+ /*TODO: generate RESTARTED messages */
+
+ if (card->bus[0] && bus0) {
+ card->bus[0]->can.state = CAN_STATE_ACTIVE;
+ netif_carrier_on(card->bus[0]->netdev);
+ }
+ if (card->bus[1] && bus1) {
+ card->bus[1]->can.state = CAN_STATE_ACTIVE;
+ netif_carrier_on(card->bus[1]->netdev);
+ }
+ return 0;
+failed:
+ softing_card_irq(card, 0);
+ softing_reset_chip(card);
+ if (card->bus[0])
+ netif_carrier_off(card->bus[0]->netdev);
+ if (card->bus[1])
+ netif_carrier_off(card->bus[1]->netdev);
+ return -EIO;
+}
+
+
+int softing_default_output(struct softing *card, struct softing_priv *priv)
+{
+ switch (priv->chip) {
+ case 1000:
+ if (card->desc->generation < 2)
+ return 0xfb;
+ return 0xfa;
+ case 5:
+ return 0x60;
+ default:
+ return 0x40;
+ }
+}
+
+u32 softing_time2usec(struct softing *card, u32 raw)
+{
+ /*TODO : don't loose higher order bits in computation */
+ switch (card->desc->freq) {
+ case 20:
+ return raw * 4 / 5;
+ case 24:
+ return raw * 2 / 3;
+ case 25:
+ return raw * 16 / 25;
+ case 0:
+ case 16:
+ default:
+ return raw;
+ }
+}
+
+
diff --git a/drivers/net/can/softing/softing_main.c b/drivers/net/can/softing/softing_main.c
new file mode 100644
index 000000000000..955f9c85a3a4
--- /dev/null
+++ b/drivers/net/can/softing/softing_main.c
@@ -0,0 +1,1058 @@
+/*
+* drivers/net/can/softing/softing_main.c
+*
+* Copyright (C) 2008
+*
+* - Kurt Van Dijck, EIA Electronics
+*
+* This program is free software; you can redistribute it and/or modify
+* it under the terms of the version 2 of the GNU General Public License
+* as published by the Free Software Foundation
+*
+* 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, write to the Free Software
+* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/firmware.h>
+#include <linux/interrupt.h>
+#include <linux/mutex.h>
+#include <linux/io.h>
+
+#include "softing.h"
+
+/* this is the worst thing on the softing API
+ * 2 busses are driven together, I don't know how
+ * to recover a single of them.
+ * Therefore, when one bus is modified, the other
+ * is flushed too
+ */
+void softing_flush_echo_skb(struct softing_priv *priv)
+{
+ can_close_cleanup(priv->netdev);
+ priv->tx.pending = 0;
+ priv->tx.echo_put = 0;
+ priv->tx.echo_get = 0;
+}
+
+/*softing_unlocked_tx_run:*/
+/*trigger the tx queue-ing*/
+/*no locks are grabbed, so be sure to have the spin spinlock*/
+static int netdev_start_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ struct softing_priv *priv = (struct softing_priv *)netdev_priv(dev);
+ struct softing *card = priv->card;
+ int ret;
+ int bhlock;
+ u8 *ptr;
+ u8 cmd;
+ unsigned int fifo_wr;
+ struct can_frame msg;
+
+ ret = -ENOTTY;
+ if (in_interrupt()) {
+ bhlock = 0;
+ spin_lock(&card->spin);
+ } else {
+ bhlock = 1;
+ spin_lock_bh(&card->spin);
+ }
+ if (!card->fw.up) {
+ ret = -EIO;
+ goto xmit_done;
+ }
+ if (netif_carrier_ok(priv->netdev) <= 0) {
+ ret = -EBADF;
+ goto xmit_done;
+ }
+ if (card->tx.pending >= TXMAX) {
+ ret = -EBUSY;
+ goto xmit_done;
+ }
+ if (priv->tx.pending >= CAN_ECHO_SKB_MAX) {
+ ret = -EBUSY;
+ goto xmit_done;
+ }
+ fifo_wr = card->dpram.tx->wr;
+ if (fifo_wr == card->dpram.tx->rd) {
+ /*fifo full */
+ ret = -EAGAIN;
+ goto xmit_done;
+ }
+ memcpy(&msg, skb->data, sizeof(msg));
+ ptr = &card->dpram.tx->fifo[fifo_wr][0];
+ cmd = CMD_TX;
+ if (msg.can_id & CAN_RTR_FLAG)
+ cmd |= CMD_RTR;
+ if (msg.can_id & CAN_EFF_FLAG)
+ cmd |= CMD_XTD;
+ if (priv->index)
+ cmd |= CMD_BUS2;
+ *ptr++ = cmd;
+ *ptr++ = msg.can_dlc;
+ *ptr++ = (msg.can_id >> 0);
+ *ptr++ = (msg.can_id >> 8);
+ if (msg.can_id & CAN_EFF_FLAG) {
+ *ptr++ = (msg.can_id >> 16);
+ *ptr++ = (msg.can_id >> 24);
+ } else {
+ /*increment 1, not 2 as you might think */
+ ptr += 1;
+ }
+ if (!(msg.can_id & CAN_RTR_FLAG))
+ memcpy_toio(ptr, &msg.data[0], msg.can_dlc);
+ if (++fifo_wr >=
+ sizeof(card->dpram.tx->fifo) /
+ sizeof(card->dpram.tx->fifo[0]))
+ fifo_wr = 0;
+ card->dpram.tx->wr = fifo_wr;
+ ret = 0;
+ ++card->tx.pending;
+ ++priv->tx.pending;
+ can_put_echo_skb(skb, dev, priv->tx.echo_put);
+ ++priv->tx.echo_put;
+ if (priv->tx.echo_put >= CAN_ECHO_SKB_MAX)
+ priv->tx.echo_put = 0;
+ /* clear pointer, so don't erase later */
+ skb = 0;
+xmit_done:
+ if (bhlock)
+ spin_unlock_bh(&card->spin);
+ else
+ spin_unlock(&card->spin);
+ if (card->tx.pending >= TXMAX) {
+ struct softing_priv *bus;
+ int j;
+ for (j = 0; j < card->nbus; ++j) {
+ bus = card->bus[j];
+ if (!bus)
+ continue;
+ netif_stop_queue(bus->netdev);
+ }
+ }
+
+ /* free skb, if not handled by the driver */
+ if (skb)
+ dev_kfree_skb(skb);
+ return ret;
+}
+
+static int softing_dev_svc_once(struct softing *card)
+{
+ int j;
+ struct softing_priv *bus;
+ struct sk_buff *skb;
+ struct can_frame msg;
+
+ unsigned int fifo_rd;
+ unsigned int cnt = 0;
+ struct net_device_stats *stats;
+
+ memset(&msg, 0, sizeof(msg));
+ if (card->dpram.rx->lost_msg) {
+ /*reset condition */
+ card->dpram.rx->lost_msg = 0;
+ /* prepare msg */
+ msg.can_id = CAN_ERR_FLAG | CAN_ERR_CRTL;
+ msg.can_dlc = CAN_ERR_DLC;
+ msg.data[1] = CAN_ERR_CRTL_RX_OVERFLOW;
+ /*service to both busses, we don't know which one generated */
+ for (j = 0; j < card->nbus; ++j) {
+ bus = card->bus[j];
+ if (!bus)
+ continue;
+ if (!netif_carrier_ok(bus->netdev))
+ continue;
+ ++bus->can.can_stats.data_overrun;
+ skb = dev_alloc_skb(sizeof(msg));
+ if (!skb)
+ return -ENOMEM;
+ skb->dev = bus->netdev;
+ skb->protocol = htons(ETH_P_CAN);
+ skb->ip_summed = CHECKSUM_UNNECESSARY;
+ memcpy(skb_put(skb, sizeof(msg)), &msg, sizeof(msg));
+ if (netif_rx(skb))
+ dev_kfree_skb_irq(skb);
+ }
+ memset(&msg, 0, sizeof(msg));
+ ++cnt;
+ }
+
+ fifo_rd = card->dpram.rx->rd;
+ if (++fifo_rd >=
+ sizeof(card->dpram.rx->fifo) / sizeof(card->dpram.rx->fifo[0]))
+ fifo_rd = 0;
+ if (card->dpram.rx->wr != fifo_rd) {
+ u8 *ptr;
+ u32 tmp;
+ u8 cmd;
+ int do_skb;
+
+ ptr = &card->dpram.rx->fifo[fifo_rd][0];
+
+ cmd = *ptr++;
+ if (cmd == 0xff) {
+ /*not quite usefull, probably the card has got out */
+ mod_alert("got cmd 0x%02x, I suspect the card is lost"
+ , cmd);
+ }
+ /*mod_trace("0x%02x", cmd);*/
+ bus = card->bus[0];
+ if (cmd & CMD_BUS2)
+ bus = card->bus[1];
+
+ stats = &bus->netdev->stats;
+ if (cmd & CMD_ERR) {
+ u8 can_state;
+ u8 state;
+ state = *ptr++;
+
+ msg.can_id = CAN_ERR_FLAG;
+ msg.can_dlc = CAN_ERR_DLC;
+
+ if (state & 0x80) {
+ can_state = CAN_STATE_BUS_OFF;
+ msg.can_id |= CAN_ERR_BUSOFF;
+ state = 2;
+ } else if (state & 0x60) {
+ can_state = CAN_STATE_BUS_PASSIVE;
+ msg.can_id |= CAN_ERR_BUSERROR;
+ state = 1;
+ } else {
+ can_state = CAN_STATE_ACTIVE;
+ state = 0;
+ do_skb = 0;
+ }
+ /*update DPRAM */
+ if (!bus->index)
+ card->dpram.info->bus_state = state;
+ else
+ card->dpram.info->bus_state2 = state;
+ /*timestamp */
+ tmp = (ptr[0] << 0)
+ |(ptr[1] << 8)
+ |(ptr[2] << 16)
+ |(ptr[3] << 24);
+ ptr += 4;
+ /*msg.time = */ softing_time2usec(card, tmp);
+ /*trigger dual port RAM */
+ mb();
+ card->dpram.rx->rd = fifo_rd;
+ /*update internal status */
+ if (can_state != bus->can.state) {
+ bus->can.state = can_state;
+ if (can_state == 1)
+ bus->can.can_stats.error_passive += 1;
+ }
+ bus->can.can_stats.bus_error += 1;
+
+ /*trigger socketcan */
+ if (state == 2) {
+ /* this calls can_close_cleanup() */
+ softing_flush_echo_skb(bus);
+ can_bus_off(bus->netdev);
+ netif_stop_queue(bus->netdev);
+ }
+ if ((state == CAN_STATE_BUS_OFF)
+ || (state == CAN_STATE_BUS_PASSIVE)) {
+ skb = dev_alloc_skb(sizeof(msg));
+ if (!skb)
+ return -ENOMEM;
+ skb->dev = bus->netdev;
+ skb->protocol = htons(ETH_P_CAN);
+ skb->ip_summed = CHECKSUM_UNNECESSARY;
+ memcpy(skb_put(skb, sizeof(msg)), &msg,
+ sizeof(msg));
+ if (netif_rx(skb))
+ dev_kfree_skb_irq(skb);
+ }
+ } else {
+ if (cmd & CMD_RTR)
+ msg.can_id |= CAN_RTR_FLAG;
+ /* acknowledge, was tx msg
+ * no real tx flag to set
+ if (cmd & CMD_ACK) {
+ }
+ */
+ msg.can_dlc = *ptr++;
+ if (msg.can_dlc > 8)
+ msg.can_dlc = 8;
+ if (cmd & CMD_XTD) {
+ msg.can_id |= CAN_EFF_FLAG;
+ msg.can_id |=
+ (ptr[0] << 0)
+ | (ptr[1] << 8)
+ | (ptr[2] << 16)
+ | (ptr[3] << 24);
+ ptr += 4;
+ } else {
+ msg.can_id |= (ptr[0] << 0) | (ptr[1] << 8);
+ ptr += 2;
+ }
+ tmp = (ptr[0] << 0)
+ | (ptr[1] << 8)
+ | (ptr[2] << 16)
+ | (ptr[3] << 24);
+ ptr += 4;
+ /*msg.time = */ softing_time2usec(card, tmp);
+ memcpy_fromio(&msg.data[0], ptr, 8);
+ ptr += 8;
+ /*trigger dual port RAM */
+ mb();
+ card->dpram.rx->rd = fifo_rd;
+ /*update socket */
+ if (cmd & CMD_ACK) {
+ can_get_echo_skb(bus->netdev, bus->tx.echo_get);
+ ++bus->tx.echo_get;
+ if (bus->tx.echo_get >= CAN_ECHO_SKB_MAX)
+ bus->tx.echo_get = 0;
+ if (bus->tx.pending)
+ --bus->tx.pending;
+ if (card->tx.pending)
+ --card->tx.pending;
+ stats->tx_packets += 1;
+ stats->tx_bytes += msg.can_dlc;
+ } else {
+ stats->rx_packets += 1;
+ stats->rx_bytes += msg.can_dlc;
+ bus->netdev->last_rx = jiffies;
+ skb = dev_alloc_skb(sizeof(msg));
+ if (skb) {
+ skb->dev = bus->netdev;
+ skb->protocol = htons(ETH_P_CAN);
+ skb->ip_summed = CHECKSUM_UNNECESSARY;
+ memcpy(skb_put(skb, sizeof(msg)), &msg,
+ sizeof(msg));
+ if (netif_rx(skb))
+ dev_kfree_skb_irq(skb);
+ }
+ }
+ }
+ ++cnt;
+ }
+ return cnt;
+}
+
+static void softing_dev_svc(unsigned long param)
+{
+ struct softing *card = (struct softing *)param;
+ struct softing_priv *bus;
+ int j;
+ int offset;
+
+ spin_lock(&card->spin);
+ while (softing_dev_svc_once(card) > 0)
+ ++card->irq.svc_count;
+ /*resume tx queue's */
+ offset = card->tx.last_bus;
+ for (j = 0; j < card->nbus; ++j) {
+ if (card->tx.pending >= TXMAX)
+ break;
+ bus = card->bus[(j + offset) % card->nbus];
+ if (netif_carrier_ok(bus->netdev))
+ netif_wake_queue(bus->netdev);
+ }
+ spin_unlock(&card->spin);
+}
+
+static void card_seems_down(struct softing *card)
+{
+ /* free interrupt, but probably
+ * in wrong (interrupt) context
+ if (card->irq.requested) {
+ free_irq(card->irq.nr, card);
+ card->irq.requested = 0;
+ card->fw.up = 0;
+ }
+ */
+ mod_alert("I think the card is vanished");
+}
+
+static
+irqreturn_t dev_interrupt_shared(int irq, void *dev_id)
+{
+ struct softing *card = (struct softing *)dev_id;
+ unsigned char ir;
+ ir = card->dpram.virt[0xe02];
+ card->dpram.virt[0xe02] = 0;
+ if (card->dpram.rx->rd == 0xffff) {
+ card_seems_down(card);
+ return IRQ_NONE;
+ }
+ if (ir == 1) {
+ tasklet_schedule(&card->irq.bh);
+ return IRQ_HANDLED;
+ } else if (ir == 0x10) {
+ return IRQ_NONE;
+ } else {
+ return IRQ_NONE;
+ }
+}
+
+static
+irqreturn_t dev_interrupt_nshared(int irq, void *dev_id)
+{
+ struct softing *card = (struct softing *)dev_id;
+ unsigned char irq_host;
+ irq_host = card->dpram.irq->to_host;
+ /* make sure we have a copy, before clearing the variable in DPRAM */
+ rmb();
+ card->dpram.irq->to_host = 0;
+ /* make sure we cleared it */
+ wmb();
+ mod_trace("0x%02x", irq_host);
+ if (card->dpram.rx->rd == 0xffff) {
+ card_seems_down(card);
+ return IRQ_NONE;
+ }
+ tasklet_schedule(&card->irq.bh);
+ return IRQ_HANDLED;
+}
+
+static int netdev_open(struct net_device *ndev)
+{
+ struct softing_priv *priv = netdev_priv(ndev);
+ struct softing *card = priv->card;
+ int fw;
+ int ret;
+
+ mod_trace("%s", ndev->name);
+ /* determine and set bittime */
+ ret = can_set_bittiming(ndev);
+ if (ret)
+ return ret;
+ if (mutex_lock_interruptible(&card->fw.lock))
+ return -ERESTARTSYS;
+ fw = card->fw.up;
+ if (fw)
+ softing_reinit(card
+ , (card->bus[0] == priv) ? 1 : -1
+ , (card->bus[1] == priv) ? 1 : -1);
+ mutex_unlock(&card->fw.lock);
+ if (!fw)
+ return -EIO;
+ netif_start_queue(ndev);
+ return 0;
+}
+
+static int netdev_stop(struct net_device *ndev)
+{
+ struct softing_priv *priv = netdev_priv(ndev);
+ struct softing *card = priv->card;
+ int fw;
+
+ mod_trace("%s", ndev->name);
+ netif_stop_queue(ndev);
+ netif_carrier_off(ndev);
+ softing_flush_echo_skb(priv);
+ can_close_cleanup(ndev);
+ if (mutex_lock_interruptible(&card->fw.lock))
+ return -ERESTARTSYS;
+ fw = card->fw.up;
+ if (fw)
+ softing_reinit(card
+ , (card->bus[0] == priv) ? 0 : -1
+ , (card->bus[1] == priv) ? 0 : -1);
+ mutex_unlock(&card->fw.lock);
+ if (!fw)
+ return -EIO;
+ return 0;
+}
+
+static int candev_get_state(struct net_device *ndev, enum can_state *state)
+{
+ struct softing_priv *priv = netdev_priv(ndev);
+ mod_trace("%s", ndev->name);
+ if (priv->netdev->flags & IFF_UP)
+ *state = CAN_STATE_STOPPED;
+ else if (priv->can.state == CAN_STATE_STOPPED)
+ *state = CAN_STATE_STOPPED;
+ else
+ *state = CAN_STATE_ACTIVE;
+ return 0;
+}
+
+static int candev_set_mode(struct net_device *ndev, enum can_mode mode)
+{
+ struct softing_priv *priv = netdev_priv(ndev);
+ struct softing *card = priv->card;
+ mod_trace("%s %u", ndev->name, mode);
+ switch (mode) {
+ case CAN_MODE_START:
+ /*recovery from busoff? */
+ if (mutex_lock_interruptible(&card->fw.lock))
+ return -ERESTARTSYS;
+ softing_reinit(card, -1, -1);
+ mutex_unlock(&card->fw.lock);
+ break;
+ case CAN_MODE_STOP:
+ case CAN_MODE_SLEEP:
+ return -EOPNOTSUPP;
+ }
+ return 0;
+}
+
+/*assume the card->lock is held*/
+
+int softing_card_irq(struct softing *card, int enable)
+{
+ int ret;
+ if (!enable) {
+ if (card->irq.requested && card->irq.nr) {
+ free_irq(card->irq.nr, card);
+ card->irq.requested = 0;
+ }
+ return 0;
+ }
+ if (!card->irq.requested && (card->irq.nr)) {
+ irqreturn_t(*fn) (int, void *);
+ unsigned int flags;
+ flags = IRQF_DISABLED | IRQF_SHARED;/*| IRQF_TRIGGER_LOW; */
+ fn = dev_interrupt_nshared;
+ if (card->desc->generation >= 2)
+ fn = dev_interrupt_shared;
+ ret = request_irq(card->irq.nr, fn, flags, card->id.name, card);
+ if (ret) {
+ mod_alert("%s, request_irq(%u) failed"
+ , card->id.name, card->irq.nr
+ );
+ return ret;
+ }
+ card->irq.requested = 1;
+ }
+ return 0;
+}
+
+static void shutdown_card(struct softing *card)
+{
+ int fw_up = 0;
+ mod_trace("%s", card->id.name);
+ if (mutex_lock_interruptible(&card->fw.lock))
+ /* return -ERESTARTSYS*/;
+ fw_up = card->fw.up;
+ card->fw.up = 0;
+
+ if (card->irq.requested && card->irq.nr) {
+ free_irq(card->irq.nr, card);
+ card->irq.requested = 0;
+ }
+ if (fw_up) {
+ if (card->fn.enable_irq)
+ card->fn.enable_irq(card, 0);
+ if (card->fn.reset)
+ card->fn.reset(card, 1);
+ }
+ mutex_unlock(&card->fw.lock);
+ tasklet_kill(&card->irq.bh);
+}
+
+static int boot_card(struct softing *card)
+{
+ mod_trace("%s", card->id.name);
+ if (mutex_lock_interruptible(&card->fw.lock))
+ return -ERESTARTSYS;
+ if (card->fw.up) {
+ mutex_unlock(&card->fw.lock);
+ return 0;
+ }
+ /*reset board */
+
+ if (card->fn.enable_irq)
+ card->fn.enable_irq(card, 1);
+ /*boot card */
+ if (card->fn.reset)
+ card->fn.reset(card, 1);
+ /*test dp ram */
+ if (card->dpram.virt) {
+ unsigned char *lp;
+ static const unsigned char stream[]
+ = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, };
+ unsigned char back[sizeof(stream)];
+ for (lp = card->dpram.virt;
+ &lp[sizeof(stream)] <= card->dpram.end;
+ lp += sizeof(stream)) {
+ memcpy_toio(lp, stream, sizeof(stream));
+ /* flush IO cache */
+ mb();
+ memcpy_fromio(back, lp, sizeof(stream));
+
+ if (memcmp(back, stream, sizeof(stream))) {
+ char line[3 * sizeof(stream)
+ / sizeof(stream[0]) + 1];
+ char *pline = line;
+ unsigned char *addr = lp;
+ for (lp = back; lp < &back[sizeof(stream)
+ / sizeof(stream[0])]; ++lp)
+ pline += sprintf(pline, " %02x", *lp);
+
+ mod_alert("write to dpram failed at 0x%p, %s"
+ , addr, line);
+ goto open_failed;
+ }
+ }
+ /*fill dpram with 0x55 */
+ /*for (lp = card->dpram.virt; lp <= card->dpram.end; ++lp) {
+ *lp = 0x55;
+ }*/
+ wmb();
+ } else {
+ goto open_failed;
+ }
+ /*load boot firmware */
+ if (softing_load_fw(card->desc->boot.fw, card, card->dpram.virt,
+ card->dpram.size,
+ card->desc->boot.offs -
+ card->desc->boot.addr))
+ goto open_failed;
+ /*load load firmware */
+ if (softing_load_fw(card->desc->load.fw, card, card->dpram.virt,
+ card->dpram.size,
+ card->desc->load.offs -
+ card->desc->load.addr))
+ goto open_failed;
+
+ if (card->fn.reset)
+ card->fn.reset(card, 0);
+ if (softing_bootloader_command(card, 0, "card boot"))
+ goto open_failed;
+ if (softing_load_app_fw(card->desc->app.fw, card))
+ goto open_failed;
+ /*reset chip */
+ card->dpram.info->reset_rcv_fifo = 0;
+ card->dpram.info->reset = 1;
+ /*sync */
+ if (softing_fct_cmd(card, 99, 0x55, "sync-a"))
+ goto open_failed;
+ if (softing_fct_cmd(card, 99, 0xaa, "sync-a"))
+ goto open_failed;
+ /*reset chip */
+ if (softing_fct_cmd(card, 0, 0, "reset_chip"))
+ goto open_failed;
+ /*get_serial */
+ if (softing_fct_cmd(card, 43, 0, "get_serial_number"))
+ goto open_failed;
+ card->id.serial =
+ (u16) card->dpram.fct->param[1] +
+ (((u16) card->dpram.fct->param[2]) << 16);
+ /*get_version */
+ if (softing_fct_cmd(card, 12, 0, "get_version"))
+ goto open_failed;
+ card->id.fw = (u16) card->dpram.fct->param[1];
+ card->id.hw = (u16) card->dpram.fct->param[2];
+ card->id.lic = (u16) card->dpram.fct->param[3];
+ card->id.chip[0] = (u16) card->dpram.fct->param[4];
+ card->id.chip[1] = (u16) card->dpram.fct->param[5];
+
+ mod_info("%s, card booted, "
+ "serial %u, fw %u, hw %u, lic %u, chip (%u,%u)",
+ card->id.name, card->id.serial, card->id.fw, card->id.hw,
+ card->id.lic, card->id.chip[0], card->id.chip[1]);
+
+ card->fw.up = 1;
+ mutex_unlock(&card->fw.lock);
+ return 0;
+open_failed:
+ card->fw.up = 0;
+ if (card->fn.enable_irq)
+ card->fn.enable_irq(card, 0);
+ if (card->fn.reset)
+ card->fn.reset(card, 1);
+ mutex_unlock(&card->fw.lock);
+ return EINVAL;
+}
+
+/*sysfs stuff*/
+
+/* Because the struct softing may be used by pcmcia devices
+ * as well as pci devices, * we have no clue how to get
+ * from a struct device * towards the struct softing *.
+ * It may go over a pci_device->priv or over a pcmcia_device->priv.
+ * Therefore, provide the struct softing pointer within the attribute.
+ * Then we don't need driver/bus specific things in these attributes
+ */
+struct softing_attribute {
+ struct device_attribute dev;
+ ssize_t (*show) (struct softing *card, char *buf);
+ ssize_t (*store)(struct softing *card, const char *buf, size_t count);
+ struct softing *card;
+};
+
+static ssize_t rd_card_attr(struct device *dev, struct device_attribute *attr
+ , char *buf) {
+ struct softing_attribute *cattr
+ = container_of(attr, struct softing_attribute, dev);
+ return cattr->show ? cattr->show(cattr->card, buf) : 0;
+}
+static ssize_t wr_card_attr(struct device *dev, struct device_attribute *attr
+ , const char *buf, size_t count) {
+ struct softing_attribute *cattr
+ = container_of(attr, struct softing_attribute, dev);
+ return cattr->store ? cattr->store(cattr->card, buf, count) : 0;
+}
+
+#define declare_attr(_name, _mode, _show, _store) { \
+ .dev = { \
+ .attr = { \
+ .name = __stringify(_name), \
+ .mode = _mode, \
+ }, \
+ .show = rd_card_attr, \
+ .store = wr_card_attr, \
+ }, \
+ .show = _show, \
+ .store = _store, \
+}
+
+#define CARD_SHOW(name, member) \
+static ssize_t show_##name(struct softing *card, char *buf) { \
+ return sprintf(buf, "%u\n", card->member); \
+}
+CARD_SHOW(serial , id.serial);
+CARD_SHOW(firmware , id.fw);
+CARD_SHOW(hardware , id.hw);
+CARD_SHOW(license , id.lic);
+CARD_SHOW(freq , id.freq);
+CARD_SHOW(txpending , tx.pending);
+
+static const struct softing_attribute card_attr_proto [] = {
+ declare_attr(serial , 0444, show_serial , 0),
+ declare_attr(firmware , 0444, show_firmware , 0),
+ declare_attr(hardware , 0444, show_hardware , 0),
+ declare_attr(license , 0444, show_license , 0),
+ declare_attr(freq , 0444, show_freq , 0),
+ declare_attr(txpending , 0644, show_txpending , 0),
+};
+
+static int mk_card_sysfs(struct softing *card)
+{
+ int size;
+ int j;
+
+ size = sizeof(card_attr_proto)/sizeof(card_attr_proto[0]);
+ card->attr = kmalloc((size+1)*sizeof(card->attr[0]), GFP_KERNEL);
+ if (!card->attr)
+ goto attr_mem_failed;
+ memcpy(card->attr, card_attr_proto, size * sizeof(card->attr[0]));
+ memset(&card->attr[size], 0, sizeof(card->attr[0]));
+
+ card->grp = kmalloc((size+1)*sizeof(card->grp [0]), GFP_KERNEL);
+ if (!card->grp)
+ goto grp_mem_failed;
+
+ for (j = 0; j < size; ++j) {
+ card->attr[j].card = card;
+ card->grp[j] = &card->attr[j].dev.attr;
+ if (!card->attr[j].show)
+ card->attr[j].dev.attr.mode &= ~(S_IRUGO);
+ if (!card->attr[j].store)
+ card->attr[j].dev.attr.mode &= ~(S_IWUGO);
+ }
+ card->grp[size] = 0;
+ card->sysfs.name = "softing";
+ card->sysfs.attrs = card->grp;
+ if (sysfs_create_group(&card->dev->kobj, &card->sysfs) < 0)
+ goto sysfs_failed;
+
+ return 0;
+
+sysfs_failed:
+ kfree(card->grp);
+grp_mem_failed:
+ kfree(card->attr);
+attr_mem_failed:
+ return -1;
+}
+static void rm_card_sysfs(struct softing *card)
+{
+ sysfs_remove_group(&card->dev->kobj, &card->sysfs);
+ kfree(card->grp);
+ kfree(card->attr);
+}
+
+static ssize_t show_chip(struct device *dev
+ , struct device_attribute *attr, char *buf)
+{
+ struct net_device *ndev = to_net_dev(dev);
+ struct softing_priv *priv = netdev2softing(ndev);
+ return sprintf(buf, "%i\n", priv->chip);
+}
+
+static ssize_t show_output(struct device *dev
+ , struct device_attribute *attr, char *buf)
+{
+ struct net_device *ndev = to_net_dev(dev);
+ struct softing_priv *priv = netdev2softing(ndev);
+ return sprintf(buf, "0x%02x\n", priv->output);
+}
+
+static ssize_t store_output(struct device *dev
+ , struct device_attribute *attr
+ , const char *buf, size_t count)
+{
+ struct net_device *ndev = to_net_dev(dev);
+ struct softing_priv *priv = netdev2softing(ndev);
+ struct softing *card = priv->card;
+
+ u8 v = simple_strtoul(buf, NULL, 10) & 0xFFU;
+
+ if (mutex_lock_interruptible(&card->fw.lock))
+ return -ERESTARTSYS;
+ if (ndev->flags & IFF_UP) {
+ int j;
+ /* we will need a restart */
+ for (j = 0; j < card->nbus; ++j) {
+ if (j == priv->index)
+ /* me, myself & I */
+ continue;
+ if (card->bus[j]->netdev->flags & IFF_UP) {
+ mutex_unlock(&card->fw.lock);
+ return -EBUSY;
+ }
+ }
+ priv->output = v;
+ softing_reinit(card, -1, -1);
+ } else {
+ priv->output = v;
+ }
+ mutex_unlock(&card->fw.lock);
+ return count;
+}
+/* TODO
+ * the latest softing cards support sleep mode too
+ */
+
+static const DEVICE_ATTR(chip, S_IRUGO, show_chip, 0);
+static const DEVICE_ATTR(output, S_IRUGO | S_IWUSR, show_output, store_output);
+
+static const struct attribute *const netdev_sysfs_entries [] = {
+ &dev_attr_chip .attr,
+ &dev_attr_output .attr,
+ 0,
+};
+static const struct attribute_group netdev_sysfs = {
+ .name = 0,
+ .attrs = (struct attribute **)netdev_sysfs_entries,
+};
+
+static int mk_netdev_sysfs(struct softing_priv *priv)
+{
+ if (!priv->netdev->dev.kobj.sd) {
+ mod_alert("sysfs_create_group would fail");
+ return ENODEV;
+ }
+ return sysfs_create_group(&priv->netdev->dev.kobj, &netdev_sysfs);
+}
+static void rm_netdev_sysfs(struct softing_priv *priv)
+{
+ sysfs_remove_group(&priv->netdev->dev.kobj, &netdev_sysfs);
+}
+
+static struct softing_priv *mk_netdev(struct softing *card, u16 chip_id)
+{
+ struct net_device *ndev;
+ struct softing_priv *priv;
+
+ ndev = alloc_candev(sizeof(*priv));
+ if (!ndev) {
+ mod_alert("alloc_candev failed");
+ return 0;
+ }
+ priv = netdev_priv(ndev);
+ priv->netdev = ndev;
+ priv->card = card;
+ memcpy(&priv->btr_const, &softing_btr_const, sizeof(priv->btr_const));
+ priv->btr_const.brp_max = card->desc->max_brp;
+ priv->btr_const.sjw_max = card->desc->max_sjw;
+ priv->can.bittiming_const = &priv->btr_const;
+ priv->can.bittiming.clock = 8000000;
+ priv->chip = chip_id;
+ priv->output = softing_default_output(card, priv);
+ SET_NETDEV_DEV(ndev, card->dev);
+
+ ndev->flags |= IFF_ECHO;
+ ndev->open = netdev_open;
+ ndev->stop = netdev_stop;
+ ndev->hard_start_xmit = netdev_start_xmit;
+ priv->can.do_get_state = candev_get_state;
+ priv->can.do_set_mode = candev_set_mode;
+
+ return priv;
+}
+
+static void rm_netdev(struct softing_priv *priv)
+{
+ free_candev(priv->netdev);
+}
+
+static int reg_netdev(struct softing_priv *priv)
+{
+ int ret;
+ netif_carrier_off(priv->netdev);
+ ret = register_candev(priv->netdev);
+ if (ret) {
+ mod_alert("%s, register failed", priv->card->id.name);
+ goto reg_failed;
+ }
+ ret = mk_netdev_sysfs(priv);
+ if (ret) {
+ mod_alert("%s, sysfs failed", priv->card->id.name);
+ goto sysfs_failed;
+ }
+ return 0;
+sysfs_failed:
+ unregister_candev(priv->netdev);
+reg_failed:
+ return EINVAL;
+}
+
+static void unreg_netdev(struct softing_priv *priv)
+{
+ rm_netdev_sysfs(priv);
+ unregister_candev(priv->netdev);
+}
+
+void rm_softing(struct softing *card)
+{
+ int j;
+
+ /*first, disable card*/
+ shutdown_card(card);
+
+ for (j = 0; j < card->nbus; ++j) {
+ unreg_netdev(card->bus[j]);
+ rm_netdev(card->bus[j]);
+ }
+
+ rm_card_sysfs(card);
+
+ iounmap(card->dpram.virt);
+}
+EXPORT_SYMBOL(rm_softing);
+
+int mk_softing(struct softing *card)
+{
+ int j;
+
+ /* try_module_get(THIS_MODULE); */
+ mutex_init(&card->fw.lock);
+ spin_lock_init(&card->spin);
+ tasklet_init(&card->irq.bh, softing_dev_svc, (unsigned long)card);
+
+ card->desc = softing_lookup_desc(card->id.manf, card->id.prod);
+ if (!card->desc) {
+ mod_alert("0x%04x:0x%04x not supported\n", card->id.manf,
+ card->id.prod);
+ goto lookup_failed;
+ }
+ card->id.name = card->desc->name;
+ mod_trace("can (%s)", card->id.name);
+
+ card->dpram.virt = ioremap(card->dpram.phys, card->dpram.size);
+ if (!card->dpram.virt) {
+ mod_alert("dpram ioremap failed\n");
+ goto ioremap_failed;
+ }
+
+ card->dpram.size = card->desc->dpram_size;
+ card->dpram.end = &card->dpram.virt[card->dpram.size];
+ /*initialize_board */
+ card->dpram.rx = (struct softing_rx *)&card->dpram.virt[0x0000];
+ card->dpram.tx = (struct softing_tx *)&card->dpram.virt[0x0400];
+ card->dpram.fct = (struct softing_fct *)&card->dpram.virt[0x0300];
+ card->dpram.info = (struct softing_info *)&card->dpram.virt[0x0330];
+ card->dpram.command = (unsigned short *)&card->dpram.virt[0x07e0];
+ card->dpram.receipt = (unsigned short *)&card->dpram.virt[0x07f0];
+ card->dpram.irq = (struct softing_irq *)&card->dpram.virt[0x07fe];
+
+ /*reset card */
+ if (card->fn.reset)
+ card->fn.reset(card, 1);
+ if (boot_card(card)) {
+ mod_alert("%s, failed to boot", card->id.name);
+ goto boot_failed;
+ }
+
+ /*only now, the chip's are known */
+ card->id.freq = card->desc->freq * 1000000UL;
+
+ if (mk_card_sysfs(card)) {
+ mod_alert("%s, sysfs failed", card->id.name);
+ goto sysfs_failed;
+ }
+
+ if (card->nbus > (sizeof(card->bus) / sizeof(card->bus[0]))) {
+ card->nbus = sizeof(card->bus) / sizeof(card->bus[0]);
+ mod_alert("%s, going for %u busses", card->id.name, card->nbus);
+ }
+
+ for (j = 0; j < card->nbus; ++j) {
+ card->bus[j] = mk_netdev(card, card->id.chip[j]);
+ if (!card->bus[j]) {
+ mod_alert("%s: failed to make can[%i]", card->id.name,
+ j);
+ goto netdev_failed;
+ }
+ card->bus[j]->index = j;
+ }
+ for (j = 0; j < card->nbus; ++j) {
+ if (reg_netdev(card->bus[j])) {
+ mod_alert("%s: failed to register can[%i]",
+ card->id.name, j);
+ goto reg_failed;
+ }
+ }
+ mod_trace("card initialised");
+ return 0;
+
+reg_failed:
+ for (j = 0; j < card->nbus; ++j)
+ unreg_netdev(card->bus[j]);
+netdev_failed:
+ for (j = 0; j < card->nbus; ++j) {
+ if (card->bus[j])
+ rm_netdev(card->bus[j]);
+ }
+ rm_card_sysfs(card);
+sysfs_failed:
+ shutdown_card(card);
+boot_failed:
+ iounmap(card->dpram.virt);
+ card->dpram.virt = 0;
+ card->dpram.end = 0;
+ioremap_failed:
+lookup_failed:
+ tasklet_kill(&card->irq.bh);
+ return EINVAL;
+}
+EXPORT_SYMBOL(mk_softing);
+
+static int __init mod_start(void)
+{
+ mod_trace("");
+ return 0;
+}
+
+static void __exit mod_stop(void)
+{
+ mod_trace("");
+}
+
+module_init(mod_start);
+module_exit(mod_stop);
+
+MODULE_DESCRIPTION("socketcan softing driver");
+MODULE_AUTHOR("Kurt Van Dijck <kurt.van.dijck@eia.be>");
+MODULE_LICENSE("GPL");
+
+int softing_debug = 1;
+EXPORT_SYMBOL(softing_debug);
+module_param(softing_debug, int , S_IRUGO | S_IWUSR | S_IWGRP);
+MODULE_PARM_DESC(softing_debug, "trace softing functions");