diff options
Diffstat (limited to 'drivers/net/can/softing')
-rw-r--r-- | drivers/net/can/softing/Makefile | 20 | ||||
-rw-r--r-- | drivers/net/can/softing/softing.h | 268 | ||||
-rw-r--r-- | drivers/net/can/softing/softing_cs.c | 427 | ||||
-rw-r--r-- | drivers/net/can/softing/softing_fw.c | 685 | ||||
-rw-r--r-- | drivers/net/can/softing/softing_main.c | 1058 |
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, ®); +} + +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(®, 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, ®); + 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"); |