#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static int dev_count=0; #ifdef PCMCIA_DEBUG static int pc_debug = PCMCIA_DEBUG; MODULE_PARM(pc_debug, "i"); #define DEBUG(n, args...) if (pc_debug>(n)) printk(KERN_DEBUG args) static char *version = "ibmir_cs.c 1.144 2001/11/07 04:06:56 (David Hinds)"; #else #define DEBUG(n, args...) #endif /*====================================================================*/ /* Module parameters */ MODULE_AUTHOR("James "); MODULE_DESCRIPTION("IBM Serial IrDA adpator"); MODULE_LICENSE("GPL"); #define INT_MODULE_PARM(n, v) static int n = v; MODULE_PARM(n, "i") /* Bit map of interrupts to choose from */ INT_MODULE_PARM(irq_mask, 0xdeb8); static int irq_list[4] = { -1 }; MODULE_PARM(irq_list, "1-4i"); /*====================================================================*/ static void ibmir_config(dev_link_t *link); static void ibmir_release(u_long arg); static int ibmir_event(event_t event, int priority, event_callback_args_t *args); static int ibmir_open(struct net_device *dev); static int ibmir_stop(struct net_device *dev); static void ibmir_change_speed(void *priv, __u32 speed); static void ibmir_interrupt(int irq, void *dev_id, struct pt_regs *regs) ; static dev_link_t *ibmir_attach(void); static void ibmir_detach(dev_link_t *); static dev_info_t dev_info = "ibmir_cs"; static dev_link_t *dev_list; /*====================================================================*/ #define ASIC_REG00 0x00 #define ASIC_REG01 0x01 #define ASIC_RPTRL 0x02 #define ASIC_RPTRH 0x03 #define ASIC_WPTRL 0x04 #define ASIC_WPTRH 0x05 #define ASIC_REG06 0x06 #define ASIC_REG07 0x07 #define ASIC_CTL 0x08 #define ASIC_PAGE 0x09 #define ASIC_REG0A 0x0a #define ASIC_REG0B 0x0b #define ASIC_REG0C 0x0c #define ASIC_REG0D 0x0d #define ASIC_REG0E 0x0e #define ASIC_REG0F 0x0f #define SDLC_WR0 0x10 #define SDLC_RR0 0x10 #define ASIC_REG11 0x11 #define ASIC_REG12 0x12 #define ASIC_SPEED 0x13 #define ASIC_WLENL 0x14 #define ASIC_WLENH 0x15 #define ASIC_REG16 0x16 #define ASIC_REG17 0x17 /*====================================================================*/ typedef struct ibmir_dev_t { struct irport_cb *irport; dev_link_t link; dev_node_t node; int irq; int asic_base; int uart_base; caddr_t mem_base; int netdevregd; __u32 speed; } ibmir_dev_t; static void ibmir_reset(ibmir_dev_t *info); /*====================================================================== This bit of code is used to avoid unregistering network devices at inappropriate times. 2.2 and later kernels are fairly picky about when this can happen. ======================================================================*/ static void flush_stale_links(void) { dev_link_t *link, *next; for (link = dev_list; link; link = next) { next = link->next; if (link->state & DEV_STALE_LINK) { ibmir_detach(link); } } } /*====================================================================*/ static void cs_error(client_handle_t handle, int func, int ret) { error_info_t err = { func, ret }; CardServices(ReportError, handle, &err); } /*====================================================================== ibmir_attach() creates an "instance" of the driver, allocating local data structures for one device. The device is registered with Card Services. ======================================================================*/ static dev_link_t *ibmir_attach(void) { ibmir_dev_t *info=NULL; dev_link_t *link; client_reg_t client_reg; int i, ret; DEBUG(0, "ibmir_attach()\n"); flush_stale_links(); if(dev_count>3) return NULL; /* Create new ethernet device */ info = kmalloc(sizeof(*info), GFP_KERNEL); if (!info) return NULL; memset(info, 0, sizeof(*info)); link = &info->link; link->priv = info; link->release.function = &ibmir_release; link->release.data = (u_long)link; link->irq.Attributes = IRQ_TYPE_EXCLUSIVE; link->irq.IRQInfo1 = IRQ_INFO2_VALID|IRQ_LEVEL_ID; if (irq_list[0] == -1) link->irq.IRQInfo2 = irq_mask; else for (i = 0; i < 4; i++) link->irq.IRQInfo2 |= 1 << irq_list[i]; link->irq.Handler=NULL; link->conf.Attributes = CONF_ENABLE_IRQ; link->conf.Vcc=50; link->conf.IntType = INT_MEMORY_AND_IO; info->irport=irport_open(dev_count++,0,0); if (!info->irport) { kfree(info); return NULL; } info->irport->netdev->open=&ibmir_open; info->irport->netdev->stop=&ibmir_stop; info->irport->interrupt=&ibmir_interrupt; info->irport->change_speed=&ibmir_change_speed; info->irport->netdev->watchdog_timeo=3*HZ; info->irport->priv=info; init_dev_name(info->irport->netdev, info->node); /* Register with Card Services */ link->next = dev_list; dev_list = link; client_reg.dev_info = &dev_info; client_reg.Attributes = INFO_IO_CLIENT | INFO_CARD_SHARE; client_reg.EventMask = CS_EVENT_CARD_INSERTION | CS_EVENT_CARD_REMOVAL | CS_EVENT_RESET_PHYSICAL | CS_EVENT_CARD_RESET | CS_EVENT_PM_SUSPEND | CS_EVENT_PM_RESUME; client_reg.event_handler = &ibmir_event; client_reg.Version = 0x0210; client_reg.event_callback_args.client_data = link; ret = CardServices(RegisterClient, &link->handle, &client_reg); if (ret != CS_SUCCESS) { cs_error(link->handle, RegisterClient, ret); ibmir_detach(link); return NULL; } return link; } /* ibmir_attach */ /*====================================================================== This deletes a driver "instance". The device is de-registered with Card Services. If it has been released, all local data structures are freed. Otherwise, the structures will be freed when the device is released. ======================================================================*/ static void ibmir_detach(dev_link_t *link) { ibmir_dev_t *info = link->priv; dev_link_t **linkp; int ret; DEBUG(0, "ibmir_detach(0x%p)\n", link); /* Locate device structure */ for (linkp = &dev_list; *linkp; linkp = &(*linkp)->next) if (*linkp == link) break; if (*linkp == NULL) return; del_timer(&link->release); if (link->state & DEV_CONFIG) { ibmir_release((u_long)link); if (link->state & DEV_STALE_CONFIG) { link->state |= DEV_STALE_LINK; return; } } if (link->handle) { /* Disconnect all MTD links */ ret = CardServices(DeregisterClient, link->handle); if (ret != CS_SUCCESS) { cs_error(link->handle, DeregisterClient, ret); return; } } /* Unlink device structure, free bits */ *linkp = link->next; if (info->irport) { if (info->netdevregd) { rtnl_lock(); unregister_netdevice(info->irport->netdev); rtnl_unlock(); info->netdevregd=0; } info->irport->io.sir_base=0; irport_close(info->irport); info->irport=NULL; } kfree(info); } /* ibmir_detach */ /*====================================================================== ibmir_config() is scheduled to run after a CARD_INSERTION event is received, to configure the PCMCIA socket, and to make the ethernet device available to the system. ======================================================================*/ #define CS_CHECK(fn, args...) \ while ((last_ret=CardServices(last_fn=(fn), args))!=0) goto cs_failed static void ibmir_config(dev_link_t *link) { client_handle_t handle = link->handle; ibmir_dev_t *info = link->priv; win_req_t req; memreq_t mem; tuple_t tuple; cisparse_t parse; int last_ret, last_fn; int manfid = 0, prodid = 0; u_short buf[64]; config_info_t conf; DEBUG(0, "ibmir_config(0x%p)\n", link); tuple.Attributes = 0; tuple.TupleData = (cisdata_t *)buf; tuple.TupleDataMax = sizeof(buf); tuple.TupleOffset = 0; tuple.DesiredTuple = CISTPL_CONFIG; CS_CHECK(GetFirstTuple, handle, &tuple); CS_CHECK(GetTupleData, handle, &tuple); CS_CHECK(ParseTuple, handle, &tuple, &parse); link->conf.ConfigBase = parse.config.base; link->conf.Present = parse.config.rmask[0]; /* Configure card */ link->state |= DEV_CONFIG; /* Look up current Vcc */ CS_CHECK(GetConfigurationInfo, handle, &conf); link->conf.Vcc = conf.Vcc; tuple.DesiredTuple = CISTPL_MANFID; tuple.Attributes = TUPLE_RETURN_COMMON; if ((CardServices(GetFirstTuple, handle, &tuple) == CS_SUCCESS) && (CardServices(GetTupleData, handle, &tuple) == CS_SUCCESS)) { manfid = le16_to_cpu(buf[0]); prodid = le16_to_cpu(buf[1]); } //FIXME: check manfid,prodid link->io.Attributes1 = IO_DATA_PATH_WIDTH_8; link->io.Attributes2 = IO_DATA_PATH_WIDTH_8; link->io.BasePort1 =0x00; link->io.NumPorts1 =0x18; link->io.BasePort2 =0x18; link->io.NumPorts2 =0x08; link->io.IOAddrLines = 5; CS_CHECK(RequestIO, handle, &link->io); info->asic_base=link->io.BasePort1; info->uart_base=link->io.BasePort2; //link->irq.Attributes = IRQ_TYPE_DYNAMIC_SHARING|IRQ_FIRST_SHARED; //link->irq.Attributes = IRQ_TYPE_EXCLUSIVE; CS_CHECK(RequestIRQ, handle, &link->irq); info->irq=link->irq.AssignedIRQ; req.Attributes = WIN_MEMORY_TYPE_CM|WIN_ENABLE; req.Attributes |= WIN_USE_WAIT; req.Base = 0; req.Size = 0x2000; req.AccessSpeed = 0; //FIXME: link->win = (window_handle_t)link->handle; last_ret=CardServices(last_fn=RequestWindow, &link->win, &req); if (last_ret) { printk("ibmir: Failed to map memory window - due to a bug in CardServices\n"); printk("ibmir: your system may crash when you try to eject the card or \n"); printk("ibmir: unload this module - this module will now prevent itself \n"); printk("ibmir: from unloading to protect you\n"); /* For those wanting to fix this imagine the following scenario: * Say we give a parameter in req which only the socket services * can complain about [eg AccessSpeed=something bogus] * pcmcia_request_window() sets the CLIENT_WIN_REQ(0) bit * in handle->state before it calls socket services (set_mem_map) * set_mem_map returns an error so link->win is not initialized * we don't have a handle to Release the window in ReleaseWindow * below, so we can't clear the bit which prevents us from * deregistering. Our module is then unloaded while still being * registered. The next call to our now nonexistant * event proc will take the system down - workaround is to * do MOD_INC_USE_COUNT */ MOD_INC_USE_COUNT; goto cs_failed; } mem.CardOffset=0; mem.Page=0; CS_CHECK(MapMemPage, link->win, &mem); info->mem_base = ioremap(req.Base, 0x2000); link->conf.ConfigIndex = 0x20; CS_CHECK(RequestConfiguration, handle, &link->conf); info->irport->io.sir_base=info->uart_base; info->irport->io.irq=info->irq; info->irport->netdev->base_addr=info->asic_base; info->irport->netdev->irq=info->irq; copy_dev_name(info->node,info->irport->netdev); link->dev=&info->node; link->state &= ~DEV_CONFIG_PENDING; info->speed=9600; ibmir_reset(info); rtnl_lock(); if (register_netdevice(info->irport->netdev) != 0) { rtnl_unlock(); printk(KERN_NOTICE "ibmir_cs: register_netdev() failed\n"); goto failed; } rtnl_unlock(); info->netdevregd++; printk(KERN_INFO "%s: IBM Serial Infrared Adapter ", info->irport->netdev->name); printk("io %#3x and %#3x,", info->asic_base,info->uart_base); printk(" irq %d,", info->irq); printk (" mem %#5lx\n", (unsigned long) info->mem_base); return; cs_failed: cs_error(link->handle, last_fn, last_ret); failed: ibmir_release((u_long)link); return; } /* ibmir_config */ /*====================================================================== After a card is removed, ibmir_release() will unregister the net device, and release the PCMCIA configuration. If the device is still open, this will be postponed until it is closed. ======================================================================*/ static void ibmir_release(u_long arg) { dev_link_t *link = (dev_link_t *)arg; ibmir_dev_t *info = link->priv; DEBUG(0, "ibmir_release(0x%p)\n", link); if (link->open) { DEBUG(1, "ibmir_cs: release postponed, '%s' still open\n", info->node.dev_name); link->state |= DEV_STALE_CONFIG; return; } if (info->mem_base){ iounmap(info->mem_base); info->mem_base=0; } CardServices(ReleaseWindow, link->win); CardServices(ReleaseConfiguration, link->handle); CardServices(ReleaseIO, link->handle, &link->io); CardServices(ReleaseIRQ, link->handle, &link->irq); link->state &= ~DEV_CONFIG; } /* ibmir_release */ /*====================================================================== The card status event handler. Mostly, this schedules other stuff to run after an event is received. A CARD_REMOVAL event also sets some flags to discourage the net drivers from trying to talk to the card any more. ======================================================================*/ static int ibmir_event(event_t event, int priority, event_callback_args_t *args) { dev_link_t *link = args->client_data; ibmir_dev_t *info = link->priv; DEBUG(2, "ibmir_event(0x%06x)\n", event); switch (event) { case CS_EVENT_CARD_REMOVAL: link->state &= ~DEV_PRESENT; if (link->state & DEV_CONFIG) { netif_device_detach(info->irport->netdev); mod_timer(&link->release, jiffies + HZ/20); } break; case CS_EVENT_CARD_INSERTION: link->state |= DEV_PRESENT; ibmir_config(link); break; case CS_EVENT_PM_SUSPEND: link->state |= DEV_SUSPEND; /* Fall through... */ case CS_EVENT_RESET_PHYSICAL: if (link->state & DEV_CONFIG) { if (link->open){ netif_device_detach(info->irport->netdev); } CardServices(ReleaseConfiguration, link->handle); } break; case CS_EVENT_PM_RESUME: link->state &= ~DEV_SUSPEND; /* Fall through... */ case CS_EVENT_CARD_RESET: if (link->state & DEV_CONFIG) { CardServices(RequestConfiguration, link->handle, &link->conf); if (link->open) { ibmir_reset(info); netif_device_attach(info->irport->netdev); } } break; } return 0; } /* ibmir_event */ static void ibmir_change_speed(void *priv, __u32 speed) { ibmir_dev_t *info = (ibmir_dev_t *) priv; if (!speed) { printk(KERN_NOTICE "ibmir_cs: attempt to set speed to zero\n"); speed=9600; } info->speed=speed; irport_change_speed(info->irport,speed); } static void ibmir_interrupt(int irq, void *dev_id, struct pt_regs *regs) { // struct net_device *netdev = (struct net_device *) dev_id; // struct irport_cb *irport=(struct irport_cb *) netdev->priv; // ibmir_dev_t *info = (ibmir_dev_t *) irport->priv; irport_interrupt(irq, dev_id, regs); } static int ibmir_open(struct net_device *dev) { struct irport_cb *irport=(struct irport_cb *)dev->priv; ibmir_dev_t *info = (ibmir_dev_t *)irport->priv; dev_link_t *link = &info->link; int ret; DEBUG(2, "ibmir_open('%s')\n", dev->name); if (!DEV_OK(link)) return -ENODEV; ret=irport_net_open(info->irport->netdev); if (ret) return ret; link->open++; MOD_INC_USE_COUNT; return 0; } /* ibmir_open */ /*====================================================================*/ static int ibmir_stop(struct net_device *dev) { struct irport_cb *irport=(struct irport_cb *)dev->priv; ibmir_dev_t *info = (ibmir_dev_t *)irport->priv; dev_link_t *link = &info->link; DEBUG(2, "ibmir_close('%s')\n", dev->name); (void) irport_net_close(dev); link->open--; if (link->state & DEV_STALE_CONFIG) mod_timer(&link->release, jiffies + HZ/20); MOD_DEC_USE_COUNT; return 0; } /* ibmir_close */ /*====================================================================== Hard reset the card. ======================================================================*/ static void ibmir_reset(ibmir_dev_t *info) { outb(0x80, ASIC_CTL +info->asic_base); udelay(100); outb(0x0, ASIC_CTL +info->asic_base); outb(0x1c, ASIC_REG0C+info->asic_base); outb(0x00, ASIC_PAGE +info->asic_base); outb(0x36, ASIC_CTL +info->asic_base); outb(0x0, ASIC_WPTRL+info->asic_base); outb(0x0, ASIC_WPTRH+info->asic_base); outb(0x0, ASIC_RPTRL+info->asic_base); outb(0x0, ASIC_RPTRH+info->asic_base); outb(0x0, ASIC_REG06+info->asic_base); outb(0x2, ASIC_REG07+info->asic_base); outb(0x10, ASIC_REG12+info->asic_base); outb(0x0, ASIC_SPEED+info->asic_base); outb(UART_LCR_WLEN8, UART_LCR+info->uart_base); outb(0, UART_IER+info->uart_base); outb(UART_LCR_WLEN8|UART_LCR_DLAB, UART_LCR+info->uart_base); outb(0xc, UART_DLL+info->uart_base); outb(0x0, UART_DLM+info->uart_base); outb(UART_LCR_WLEN8, UART_LCR+info->uart_base); outb(0x02, UART_FCR+info->uart_base); outb(0x04, UART_FCR+info->uart_base); outb(0x01, UART_FCR+info->uart_base); outb(0x0, UART_MCR+info->uart_base); if (!info->speed) info->speed=9600; ibmir_change_speed(info,info->speed); } /* ibmir_reset */ /*====================================================================*/ static int __init init_ibmir_cs(void) { servinfo_t serv; DEBUG(0, "%s\n", version); CardServices(GetCardServicesInfo, &serv); if (serv.Revision != CS_RELEASE_CODE) { printk(KERN_NOTICE "ibmir_cs: Card Services release " "does not match!\n"); return -1; } register_pccard_driver(&dev_info, &ibmir_attach, &ibmir_detach); return 0; } static void __exit exit_ibmir_cs(void) { DEBUG(0, "ibmir_cs: unloading\n"); unregister_pccard_driver(&dev_info); while (dev_list != NULL) { ibmir_detach(dev_list); } } module_init(init_ibmir_cs); module_exit(exit_ibmir_cs);