sl811_cs.c revision c9a50cc9318772e62d56f2a9172bdfda72bdacbe
1c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell/* 2c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell * PCMCIA driver for SL811HS (as found in REX-CFU1U) 3c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell * Filename: sl811_cs.c 4c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell * Author: Yukio Yamamoto 5c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell * 6c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell * Port to sl811-hcd and 2.6.x by 7c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell * Botond Botyanszki <boti@rocketmail.com> 8c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell * Simon Pickering 9c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell * 10c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell * Last update: 2005-05-12 11c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell */ 12c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 13c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell#include <linux/kernel.h> 14c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell#include <linux/module.h> 15c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell#include <linux/init.h> 16c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell#include <linux/sched.h> 17c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell#include <linux/ptrace.h> 18c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell#include <linux/slab.h> 19c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell#include <linux/string.h> 20c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell#include <linux/timer.h> 21c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell#include <linux/ioport.h> 22d052d1beff706920e82c5d55006b08e256b5df09Russell King#include <linux/platform_device.h> 23c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 24c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell#include <pcmcia/cs_types.h> 25c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell#include <pcmcia/cs.h> 26c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell#include <pcmcia/cistpl.h> 27c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell#include <pcmcia/cisreg.h> 28c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell#include <pcmcia/ds.h> 29c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 30c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell#include <linux/usb_sl811.h> 31c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 32c6de2b64eb575a3f9326969ec5fcdc6032b38e42David BrownellMODULE_AUTHOR("Botond Botyanszki"); 33c6de2b64eb575a3f9326969ec5fcdc6032b38e42David BrownellMODULE_DESCRIPTION("REX-CFU1U PCMCIA driver for 2.6"); 34c6de2b64eb575a3f9326969ec5fcdc6032b38e42David BrownellMODULE_LICENSE("GPL"); 35c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 36c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 37c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell/*====================================================================*/ 38c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell/* MACROS */ 39c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell/*====================================================================*/ 40c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 41c9a50cc9318772e62d56f2a9172bdfda72bdacbeDavid Brownell#if defined(DEBUG) || defined(PCMCIA_DEBUG) 42c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 43c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownellstatic int pc_debug = 0; 44c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownellmodule_param(pc_debug, int, 0644); 45c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 46c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell#define DBG(n, args...) if (pc_debug>(n)) printk(KERN_DEBUG "sl811_cs: " args) 47c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 48c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell#else 49c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell#define DBG(n, args...) do{}while(0) 50c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell#endif /* no debugging */ 51c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 52c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell#define INFO(args...) printk(KERN_INFO "sl811_cs: " args) 53c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 54c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell#define INT_MODULE_PARM(n, v) static int n = v; module_param(n, int, 0444) 55c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 56c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell#define CS_CHECK(fn, ret) \ 57c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell do { \ 58c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell last_fn = (fn); \ 59c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell if ((last_ret = (ret)) != 0) \ 60c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell goto cs_failed; \ 61c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell } while (0) 62c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 63c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell/*====================================================================*/ 64c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell/* VARIABLES */ 65c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell/*====================================================================*/ 66c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 67c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownellstatic const char driver_name[DEV_NAME_LEN] = "sl811_cs"; 68c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 69c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownellstatic dev_link_t *dev_list = NULL; 70c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 71c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownelltypedef struct local_info_t { 72c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell dev_link_t link; 73c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell dev_node_t node; 74c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell} local_info_t; 75c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 76c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell/*====================================================================*/ 77c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 78c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownellstatic void release_platform_dev(struct device * dev) 79c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell{ 80c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell DBG(0, "sl811_cs platform_dev release\n"); 81c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell dev->parent = NULL; 82c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell} 83c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 84c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownellstatic struct sl811_platform_data platform_data = { 85c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell .potpg = 100, 86c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell .power = 50, /* == 100mA */ 87c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell // .reset = ... FIXME: invoke CF reset on the card 88c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell}; 89c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 90c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownellstatic struct resource resources[] = { 91c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell [0] = { 92c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell .flags = IORESOURCE_IRQ, 93c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell }, 94c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell [1] = { 95c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell // .name = "address", 96c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell .flags = IORESOURCE_IO, 97c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell }, 98c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell [2] = { 99c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell // .name = "data", 100c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell .flags = IORESOURCE_IO, 101c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell }, 102c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell}; 103c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 104c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownellextern struct device_driver sl811h_driver; 105c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 106c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownellstatic struct platform_device platform_dev = { 107c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell .id = -1, 108c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell .dev = { 109c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell .platform_data = &platform_data, 110c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell .release = release_platform_dev, 111c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell }, 112c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell .resource = resources, 113c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell .num_resources = ARRAY_SIZE(resources), 114c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell}; 115c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 116c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownellstatic int sl811_hc_init(struct device *parent, ioaddr_t base_addr, int irq) 117c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell{ 118c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell if (platform_dev.dev.parent) 119c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell return -EBUSY; 120c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell platform_dev.dev.parent = parent; 121c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 122c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell /* finish seting up the platform device */ 123c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell resources[0].start = irq; 124c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 125c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell resources[1].start = base_addr; 126c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell resources[1].end = base_addr; 127c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 128c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell resources[2].start = base_addr + 1; 129c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell resources[2].end = base_addr + 1; 130c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 131c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell /* The driver core will probe for us. We know sl811-hcd has been 132c9a50cc9318772e62d56f2a9172bdfda72bdacbeDavid Brownell * initialized already because of the link order dependency created 133c9a50cc9318772e62d56f2a9172bdfda72bdacbeDavid Brownell * by referencing "sl811h_driver". 134c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell */ 135c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell platform_dev.name = sl811h_driver.name; 136c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell return platform_device_register(&platform_dev); 137c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell} 138c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 139c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell/*====================================================================*/ 140c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 141c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownellstatic void sl811_cs_detach(dev_link_t *link) 142c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell{ 143c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell dev_link_t **linkp; 144c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 145c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell DBG(0, "sl811_cs_detach(0x%p)\n", link); 146c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 147c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell /* Locate device structure */ 148c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell for (linkp = &dev_list; *linkp; linkp = &(*linkp)->next) { 149c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell if (*linkp == link) 150c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell break; 151c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell } 152c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell if (*linkp == NULL) 153c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell return; 154c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 155c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell /* Break the link with Card Services */ 156c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell if (link->handle) 157c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell pcmcia_deregister_client(link->handle); 158c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 159c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell /* Unlink device structure, and free it */ 160c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell *linkp = link->next; 161c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell /* This points to the parent local_info_t struct */ 162c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell kfree(link->priv); 163c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell} 164c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 165c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownellstatic void sl811_cs_release(dev_link_t * link) 166c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell{ 167c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 168c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell DBG(0, "sl811_cs_release(0x%p)\n", link); 169c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 170c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell if (link->open) { 171c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell DBG(1, "sl811_cs: release postponed, '%s' still open\n", 172c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell link->dev->dev_name); 173c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell link->state |= DEV_STALE_CONFIG; 174c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell return; 175c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell } 176c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 177c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell /* Unlink the device chain */ 178c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell link->dev = NULL; 179c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 180c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell platform_device_unregister(&platform_dev); 181c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell pcmcia_release_configuration(link->handle); 182c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell if (link->io.NumPorts1) 183c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell pcmcia_release_io(link->handle, &link->io); 184c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell if (link->irq.AssignedIRQ) 185c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell pcmcia_release_irq(link->handle, &link->irq); 186c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell link->state &= ~DEV_CONFIG; 187c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 188c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell if (link->state & DEV_STALE_LINK) 189c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell sl811_cs_detach(link); 190c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell} 191c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 192c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownellstatic void sl811_cs_config(dev_link_t *link) 193c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell{ 194c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell client_handle_t handle = link->handle; 195c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell struct device *parent = &handle_to_dev(handle); 196c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell local_info_t *dev = link->priv; 197c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell tuple_t tuple; 198c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell cisparse_t parse; 199c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell int last_fn, last_ret; 200c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell u_char buf[64]; 201c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell config_info_t conf; 202c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell cistpl_cftable_entry_t dflt = { 0 }; 203c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 204c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell DBG(0, "sl811_cs_config(0x%p)\n", link); 205c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 206c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell tuple.DesiredTuple = CISTPL_CONFIG; 207c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell tuple.Attributes = 0; 208c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell tuple.TupleData = buf; 209c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell tuple.TupleDataMax = sizeof(buf); 210c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell tuple.TupleOffset = 0; 211c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell CS_CHECK(GetFirstTuple, pcmcia_get_first_tuple(handle, &tuple)); 212c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell CS_CHECK(GetTupleData, pcmcia_get_tuple_data(handle, &tuple)); 213c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell CS_CHECK(ParseTuple, pcmcia_parse_tuple(handle, &tuple, &parse)); 214c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell link->conf.ConfigBase = parse.config.base; 215c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell link->conf.Present = parse.config.rmask[0]; 216c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 217c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell /* Configure card */ 218c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell link->state |= DEV_CONFIG; 219c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 220c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell /* Look up the current Vcc */ 221c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell CS_CHECK(GetConfigurationInfo, 222c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell pcmcia_get_configuration_info(handle, &conf)); 223c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell link->conf.Vcc = conf.Vcc; 224c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 225c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell tuple.DesiredTuple = CISTPL_CFTABLE_ENTRY; 226c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell CS_CHECK(GetFirstTuple, pcmcia_get_first_tuple(handle, &tuple)); 227c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell while (1) { 228c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell cistpl_cftable_entry_t *cfg = &(parse.cftable_entry); 229c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 230c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell if (pcmcia_get_tuple_data(handle, &tuple) != 0 231c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell || pcmcia_parse_tuple(handle, &tuple, &parse) 232c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell != 0) 233c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell goto next_entry; 234c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 235c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell if (cfg->flags & CISTPL_CFTABLE_DEFAULT) { 236c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell dflt = *cfg; 237c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell } 238c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 239c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell if (cfg->index == 0) 240c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell goto next_entry; 241c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 242c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell link->conf.ConfigIndex = cfg->index; 243c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 244c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell /* Use power settings for Vcc and Vpp if present */ 245c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell /* Note that the CIS values need to be rescaled */ 246c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell if (cfg->vcc.present & (1<<CISTPL_POWER_VNOM)) { 247c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell if (cfg->vcc.param[CISTPL_POWER_VNOM]/10000 248c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell != conf.Vcc) 249c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell goto next_entry; 250c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell } else if (dflt.vcc.present & (1<<CISTPL_POWER_VNOM)) { 251c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell if (dflt.vcc.param[CISTPL_POWER_VNOM]/10000 252c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell != conf.Vcc) 253c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell goto next_entry; 254c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell } 255c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 256c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell if (cfg->vpp1.present & (1<<CISTPL_POWER_VNOM)) 257c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell link->conf.Vpp1 = link->conf.Vpp2 = 258c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell cfg->vpp1.param[CISTPL_POWER_VNOM]/10000; 259c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell else if (dflt.vpp1.present & (1<<CISTPL_POWER_VNOM)) 260c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell link->conf.Vpp1 = link->conf.Vpp2 = 261c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell dflt.vpp1.param[CISTPL_POWER_VNOM]/10000; 262c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 263c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell /* we need an interrupt */ 264c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell if (cfg->irq.IRQInfo1 || dflt.irq.IRQInfo1) 265c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell link->conf.Attributes |= CONF_ENABLE_IRQ; 266c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 267c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell /* IO window settings */ 268c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell link->io.NumPorts1 = link->io.NumPorts2 = 0; 269c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell if ((cfg->io.nwin > 0) || (dflt.io.nwin > 0)) { 270c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell cistpl_io_t *io = (cfg->io.nwin) ? &cfg->io : &dflt.io; 271c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 272c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell link->io.Attributes1 = IO_DATA_PATH_WIDTH_8; 273c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell link->io.IOAddrLines = io->flags & CISTPL_IO_LINES_MASK; 274c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell link->io.BasePort1 = io->win[0].base; 275c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell link->io.NumPorts1 = io->win[0].len; 276c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 277c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell if (pcmcia_request_io(link->handle, &link->io) != 0) 278c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell goto next_entry; 279c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell } 280c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell break; 281c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 282c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownellnext_entry: 283c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell if (link->io.NumPorts1) 284c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell pcmcia_release_io(link->handle, &link->io); 285c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell last_ret = pcmcia_get_next_tuple(handle, &tuple); 286c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell } 287c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 288c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell /* require an IRQ and two registers */ 289c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell if (!link->io.NumPorts1 || link->io.NumPorts1 < 2) 290c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell goto cs_failed; 291c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell if (link->conf.Attributes & CONF_ENABLE_IRQ) 292c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell CS_CHECK(RequestIRQ, 293c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell pcmcia_request_irq(link->handle, &link->irq)); 294c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell else 295c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell goto cs_failed; 296c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 297c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell CS_CHECK(RequestConfiguration, 298c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell pcmcia_request_configuration(link->handle, &link->conf)); 299c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 300c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell sprintf(dev->node.dev_name, driver_name); 301c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell dev->node.major = dev->node.minor = 0; 302c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell link->dev = &dev->node; 303c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 304c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell printk(KERN_INFO "%s: index 0x%02x: Vcc %d.%d", 305c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell dev->node.dev_name, link->conf.ConfigIndex, 306c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell link->conf.Vcc/10, link->conf.Vcc%10); 307c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell if (link->conf.Vpp1) 308c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell printk(", Vpp %d.%d", link->conf.Vpp1/10, link->conf.Vpp1%10); 309c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell printk(", irq %d", link->irq.AssignedIRQ); 310c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell printk(", io 0x%04x-0x%04x", link->io.BasePort1, 311c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell link->io.BasePort1+link->io.NumPorts1-1); 312c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell printk("\n"); 313c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 314c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell link->state &= ~DEV_CONFIG_PENDING; 315c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 316c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell if (sl811_hc_init(parent, link->io.BasePort1, link->irq.AssignedIRQ) 317c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell < 0) { 318c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownellcs_failed: 319c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell printk("sl811_cs_config failed\n"); 320c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell cs_error(link->handle, last_fn, last_ret); 321c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell sl811_cs_release(link); 322c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell link->state &= ~DEV_CONFIG_PENDING; 323c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell } 324c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell} 325c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 326c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownellstatic int 327c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownellsl811_cs_event(event_t event, int priority, event_callback_args_t *args) 328c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell{ 329c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell dev_link_t *link = args->client_data; 330c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 331c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell DBG(1, "sl811_cs_event(0x%06x)\n", event); 332c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 333c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell switch (event) { 334c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell case CS_EVENT_CARD_REMOVAL: 335c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell link->state &= ~DEV_PRESENT; 336c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell if (link->state & DEV_CONFIG) 337c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell sl811_cs_release(link); 338c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell break; 339c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 340c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell case CS_EVENT_CARD_INSERTION: 341c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell link->state |= DEV_PRESENT | DEV_CONFIG_PENDING; 342c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell sl811_cs_config(link); 343c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell break; 344c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 345c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell case CS_EVENT_PM_SUSPEND: 346c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell link->state |= DEV_SUSPEND; 347c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell /* Fall through... */ 348c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell case CS_EVENT_RESET_PHYSICAL: 349c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell if (link->state & DEV_CONFIG) 350c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell pcmcia_release_configuration(link->handle); 351c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell break; 352c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 353c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell case CS_EVENT_PM_RESUME: 354c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell link->state &= ~DEV_SUSPEND; 355c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell /* Fall through... */ 356c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell case CS_EVENT_CARD_RESET: 357c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell if (link->state & DEV_CONFIG) 358c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell pcmcia_request_configuration(link->handle, &link->conf); 359c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell DBG(0, "reset sl811-hcd here?\n"); 360c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell break; 361c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell } 362c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell return 0; 363c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell} 364c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 365c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownellstatic dev_link_t *sl811_cs_attach(void) 366c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell{ 367c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell local_info_t *local; 368c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell dev_link_t *link; 369c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell client_reg_t client_reg; 37022f3a8f5fc94be4dd31c4c5ec1d1dc2b9c83a8acDavid Brownell int ret; 371c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 372c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell local = kmalloc(sizeof(local_info_t), GFP_KERNEL); 373c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell if (!local) 374c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell return NULL; 375c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell memset(local, 0, sizeof(local_info_t)); 376c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell link = &local->link; 377c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell link->priv = local; 378c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 379c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell /* Initialize */ 380c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell link->irq.Attributes = IRQ_TYPE_EXCLUSIVE; 381c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell link->irq.IRQInfo1 = IRQ_INFO2_VALID|IRQ_LEVEL_ID; 382c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell link->irq.Handler = NULL; 383c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 384c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell link->conf.Attributes = 0; 385c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell link->conf.Vcc = 33; 386c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell link->conf.IntType = INT_MEMORY_AND_IO; 387c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 388c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell /* Register with Card Services */ 389c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell link->next = dev_list; 390c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell dev_list = link; 391c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell client_reg.dev_info = (dev_info_t *) &driver_name; 392c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell client_reg.Attributes = INFO_IO_CLIENT | INFO_CARD_SHARE; 393c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell client_reg.Version = 0x0210; 394c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell client_reg.event_callback_args.client_data = link; 395c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell ret = pcmcia_register_client(&link->handle, &client_reg); 396c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell if (ret != CS_SUCCESS) { 397c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell cs_error(link->handle, RegisterClient, ret); 398c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell sl811_cs_detach(link); 399c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell return NULL; 400c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell } 401c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 402c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell return link; 403c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell} 404c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 40522f3a8f5fc94be4dd31c4c5ec1d1dc2b9c83a8acDavid Brownellstatic struct pcmcia_device_id sl811_ids[] = { 40622f3a8f5fc94be4dd31c4c5ec1d1dc2b9c83a8acDavid Brownell PCMCIA_DEVICE_MANF_CARD(0xc015, 0x0001), /* RATOC USB HOST CF+ Card */ 40722f3a8f5fc94be4dd31c4c5ec1d1dc2b9c83a8acDavid Brownell PCMCIA_DEVICE_NULL, 40822f3a8f5fc94be4dd31c4c5ec1d1dc2b9c83a8acDavid Brownell}; 40922f3a8f5fc94be4dd31c4c5ec1d1dc2b9c83a8acDavid BrownellMODULE_DEVICE_TABLE(pcmcia, sl811_ids); 41022f3a8f5fc94be4dd31c4c5ec1d1dc2b9c83a8acDavid Brownell 411c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownellstatic struct pcmcia_driver sl811_cs_driver = { 412c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell .owner = THIS_MODULE, 413c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell .drv = { 414c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell .name = (char *)driver_name, 415c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell }, 416c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell .attach = sl811_cs_attach, 4171e212f3645a6b355de8c43a23376bc0e2ac49a63Dominik Brodowski .event = sl811_cs_event, 418c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell .detach = sl811_cs_detach, 41922f3a8f5fc94be4dd31c4c5ec1d1dc2b9c83a8acDavid Brownell .id_table = sl811_ids, 420c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell}; 421c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 422c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell/*====================================================================*/ 423c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 424c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownellstatic int __init init_sl811_cs(void) 425c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell{ 426c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell return pcmcia_register_driver(&sl811_cs_driver); 427c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell} 428c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownellmodule_init(init_sl811_cs); 429c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell 430c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownellstatic void __exit exit_sl811_cs(void) 431c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell{ 432c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell pcmcia_unregister_driver(&sl811_cs_driver); 433c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownell} 434c6de2b64eb575a3f9326969ec5fcdc6032b38e42David Brownellmodule_exit(exit_sl811_cs); 435