diff options
Diffstat (limited to 'drivers/net/can/softing/softing_cs.c')
-rw-r--r-- | drivers/net/can/softing/softing_cs.c | 427 |
1 files changed, 427 insertions, 0 deletions
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"); + |