aboutsummaryrefslogtreecommitdiff
path: root/drivers/usb/host
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/host')
-rw-r--r--drivers/usb/host/Kconfig11
-rw-r--r--drivers/usb/host/Makefile1
-rw-r--r--drivers/usb/host/ehci-dbg.c10
-rw-r--r--drivers/usb/host/ehci-hub.c5
-rw-r--r--drivers/usb/host/ohci-dbg.c10
-rw-r--r--drivers/usb/host/sl811-hcd.c146
-rw-r--r--drivers/usb/host/sl811_cs.c442
7 files changed, 549 insertions, 76 deletions
diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig
index 3196c3265ff5..19e598c9641f 100644
--- a/drivers/usb/host/Kconfig
+++ b/drivers/usb/host/Kconfig
@@ -124,3 +124,14 @@ config USB_SL811_HCD
To compile this driver as a module, choose M here: the
module will be called sl811-hcd.
+config USB_SL811_CS
+ tristate "CF/PCMCIA support for SL811HS HCD"
+ depends on USB_SL811_HCD && PCMCIA
+ default N
+ help
+ Wraps a PCMCIA driver around the SL811HS HCD, supporting the RATOC
+ REX-CFU1U CF card (often used with PDAs). If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called "sl811_cs".
+
diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile
index a574ca06cf6b..5dbd3e7a27c7 100644
--- a/drivers/usb/host/Makefile
+++ b/drivers/usb/host/Makefile
@@ -7,4 +7,5 @@ obj-$(CONFIG_USB_EHCI_HCD) += ehci-hcd.o
obj-$(CONFIG_USB_OHCI_HCD) += ohci-hcd.o
obj-$(CONFIG_USB_UHCI_HCD) += uhci-hcd.o
obj-$(CONFIG_USB_SL811_HCD) += sl811-hcd.o
+obj-$(CONFIG_USB_SL811_CS) += sl811_cs.o
obj-$(CONFIG_ETRAX_ARCH_V10) += hc_crisv10.o
diff --git a/drivers/usb/host/ehci-dbg.c b/drivers/usb/host/ehci-dbg.c
index 9b347d765383..2ff11d53567b 100644
--- a/drivers/usb/host/ehci-dbg.c
+++ b/drivers/usb/host/ehci-dbg.c
@@ -450,7 +450,7 @@ show_async (struct class_device *class_dev, char *buf)
*buf = 0;
- bus = to_usb_bus(class_dev);
+ bus = class_get_devdata(class_dev);
hcd = bus->hcpriv;
ehci = hcd_to_ehci (hcd);
next = buf;
@@ -496,7 +496,7 @@ show_periodic (struct class_device *class_dev, char *buf)
return 0;
seen_count = 0;
- bus = to_usb_bus(class_dev);
+ bus = class_get_devdata(class_dev);
hcd = bus->hcpriv;
ehci = hcd_to_ehci (hcd);
next = buf;
@@ -633,7 +633,7 @@ show_registers (struct class_device *class_dev, char *buf)
static char fmt [] = "%*s\n";
static char label [] = "";
- bus = to_usb_bus(class_dev);
+ bus = class_get_devdata(class_dev);
hcd = bus->hcpriv;
ehci = hcd_to_ehci (hcd);
next = buf;
@@ -735,7 +735,7 @@ static CLASS_DEVICE_ATTR (registers, S_IRUGO, show_registers, NULL);
static inline void create_debug_files (struct ehci_hcd *ehci)
{
- struct class_device *cldev = &ehci_to_hcd(ehci)->self.class_dev;
+ struct class_device *cldev = ehci_to_hcd(ehci)->self.class_dev;
class_device_create_file(cldev, &class_device_attr_async);
class_device_create_file(cldev, &class_device_attr_periodic);
@@ -744,7 +744,7 @@ static inline void create_debug_files (struct ehci_hcd *ehci)
static inline void remove_debug_files (struct ehci_hcd *ehci)
{
- struct class_device *cldev = &ehci_to_hcd(ehci)->self.class_dev;
+ struct class_device *cldev = ehci_to_hcd(ehci)->self.class_dev;
class_device_remove_file(cldev, &class_device_attr_async);
class_device_remove_file(cldev, &class_device_attr_periodic);
diff --git a/drivers/usb/host/ehci-hub.c b/drivers/usb/host/ehci-hub.c
index 429330bc38de..d7b4f7939ded 100644
--- a/drivers/usb/host/ehci-hub.c
+++ b/drivers/usb/host/ehci-hub.c
@@ -439,9 +439,12 @@ static int ehci_hub_control (
/* force reset to complete */
writel (temp & ~PORT_RESET,
&ehci->regs->port_status [wIndex]);
+ /* REVISIT: some hardware needs 550+ usec to clear
+ * this bit; seems too long to spin routinely...
+ */
retval = handshake (
&ehci->regs->port_status [wIndex],
- PORT_RESET, 0, 500);
+ PORT_RESET, 0, 750);
if (retval != 0) {
ehci_err (ehci, "port %d reset error %d\n",
wIndex + 1, retval);
diff --git a/drivers/usb/host/ohci-dbg.c b/drivers/usb/host/ohci-dbg.c
index 62f53a213808..c58408c95c3d 100644
--- a/drivers/usb/host/ohci-dbg.c
+++ b/drivers/usb/host/ohci-dbg.c
@@ -481,7 +481,7 @@ show_async (struct class_device *class_dev, char *buf)
size_t temp;
unsigned long flags;
- bus = to_usb_bus(class_dev);
+ bus = class_get_devdata(class_dev);
hcd = bus->hcpriv;
ohci = hcd_to_ohci(hcd);
@@ -514,7 +514,7 @@ show_periodic (struct class_device *class_dev, char *buf)
return 0;
seen_count = 0;
- bus = to_usb_bus(class_dev);
+ bus = class_get_devdata(class_dev);
hcd = bus->hcpriv;
ohci = hcd_to_ohci(hcd);
next = buf;
@@ -611,7 +611,7 @@ show_registers (struct class_device *class_dev, char *buf)
char *next;
u32 rdata;
- bus = to_usb_bus(class_dev);
+ bus = class_get_devdata(class_dev);
hcd = bus->hcpriv;
ohci = hcd_to_ohci(hcd);
regs = ohci->regs;
@@ -684,7 +684,7 @@ static CLASS_DEVICE_ATTR (registers, S_IRUGO, show_registers, NULL);
static inline void create_debug_files (struct ohci_hcd *ohci)
{
- struct class_device *cldev = &ohci_to_hcd(ohci)->self.class_dev;
+ struct class_device *cldev = ohci_to_hcd(ohci)->self.class_dev;
class_device_create_file(cldev, &class_device_attr_async);
class_device_create_file(cldev, &class_device_attr_periodic);
@@ -694,7 +694,7 @@ static inline void create_debug_files (struct ohci_hcd *ohci)
static inline void remove_debug_files (struct ohci_hcd *ohci)
{
- struct class_device *cldev = &ohci_to_hcd(ohci)->self.class_dev;
+ struct class_device *cldev = ohci_to_hcd(ohci)->self.class_dev;
class_device_remove_file(cldev, &class_device_attr_async);
class_device_remove_file(cldev, &class_device_attr_periodic);
diff --git a/drivers/usb/host/sl811-hcd.c b/drivers/usb/host/sl811-hcd.c
index a374b7692073..99d43f758ad0 100644
--- a/drivers/usb/host/sl811-hcd.c
+++ b/drivers/usb/host/sl811-hcd.c
@@ -2,8 +2,8 @@
* SL811HS HCD (Host Controller Driver) for USB.
*
* Copyright (C) 2004 Psion Teklogix (for NetBook PRO)
- * Copyright (C) 2004 David Brownell
- *
+ * Copyright (C) 2004-2005 David Brownell
+ *
* Periodic scheduling is based on Roman's OHCI code
* Copyright (C) 1999 Roman Weissgaerber
*
@@ -15,7 +15,7 @@
* For documentation, see the SL811HS spec and the "SL811HS Embedded Host"
* document (providing significant pieces missing from that spec); plus
* the SL811S spec if you want peripheral side info.
- */
+ */
/*
* Status: Passed basic stress testing, works with hubs, mice, keyboards,
@@ -67,7 +67,7 @@
MODULE_DESCRIPTION("SL811HS USB Host Controller Driver");
MODULE_LICENSE("GPL");
-#define DRIVER_VERSION "15 Dec 2004"
+#define DRIVER_VERSION "19 May 2005"
#ifndef DEBUG
@@ -121,6 +121,10 @@ static void port_power(struct sl811 *sl811, int is_on)
/* reset as thoroughly as we can */
if (sl811->board && sl811->board->reset)
sl811->board->reset(hcd->self.controller);
+ else {
+ sl811_write(sl811, SL11H_CTLREG1, SL11H_CTL1MASK_SE0);
+ mdelay(20);
+ }
sl811_write(sl811, SL11H_IRQ_ENABLE, 0);
sl811_write(sl811, SL11H_CTLREG1, sl811->ctrl1);
@@ -443,6 +447,7 @@ static void finish_request(
spin_lock(&urb->lock);
if (urb->status == -EINPROGRESS)
urb->status = status;
+ urb->hcpriv = NULL;
spin_unlock(&urb->lock);
spin_unlock(&sl811->lock);
@@ -472,7 +477,7 @@ static void finish_request(
if (*prev)
*prev = ep->next;
sl811->load[i] -= ep->load;
- }
+ }
ep->branch = PERIODIC_SIZE;
sl811->periodic_count--;
sl811_to_hcd(sl811)->self.bandwidth_allocated
@@ -661,9 +666,9 @@ retry:
#ifdef QUIRK2
/* this may no longer be necessary ... */
- if (irqstat == 0 && ret == IRQ_NONE) {
+ if (irqstat == 0) {
irqstat = checkdone(sl811);
- if (irqstat /* && irq != ~0 */ )
+ if (irqstat)
sl811->stat_lost++;
}
#endif
@@ -722,7 +727,8 @@ retry:
if (sl811->active_a) {
sl811_write(sl811, SL811_EP_A(SL11H_HOSTCTLREG), 0);
finish_request(sl811, sl811->active_a,
- container_of(sl811->active_a->hep->urb_list.next,
+ container_of(sl811->active_a
+ ->hep->urb_list.next,
struct urb, urb_list),
NULL, -ESHUTDOWN);
sl811->active_a = NULL;
@@ -731,7 +737,8 @@ retry:
if (sl811->active_b) {
sl811_write(sl811, SL811_EP_B(SL11H_HOSTCTLREG), 0);
finish_request(sl811, sl811->active_b,
- container_of(sl811->active_b->hep->urb_list.next,
+ container_of(sl811->active_b
+ ->hep->urb_list.next,
struct urb, urb_list),
NULL, -ESHUTDOWN);
sl811->active_b = NULL;
@@ -761,7 +768,7 @@ retry:
goto retry;
}
- if (sl811->periodic_count == 0 && list_empty(&sl811->async))
+ if (sl811->periodic_count == 0 && list_empty(&sl811->async))
sofirq_off(sl811);
sl811_write(sl811, SL11H_IRQ_ENABLE, sl811->irq_enable);
@@ -796,7 +803,7 @@ static int balance(struct sl811 *sl811, u16 period, u16 load)
}
if (j < PERIODIC_SIZE)
continue;
- branch = i;
+ branch = i;
}
}
return branch;
@@ -890,6 +897,7 @@ static int sl811h_urb_enqueue(
break;
}
+ ep->hep = hep;
hep->hcpriv = ep;
}
@@ -961,15 +969,16 @@ fail:
static int sl811h_urb_dequeue(struct usb_hcd *hcd, struct urb *urb)
{
struct sl811 *sl811 = hcd_to_sl811(hcd);
- struct usb_host_endpoint *hep = urb->hcpriv;
+ struct usb_host_endpoint *hep;
unsigned long flags;
struct sl811h_ep *ep;
int retval = 0;
+ spin_lock_irqsave(&sl811->lock, flags);
+ hep = urb->hcpriv;
if (!hep)
- return -EINVAL;
+ goto fail;
- spin_lock_irqsave(&sl811->lock, flags);
ep = hep->hcpriv;
if (ep) {
/* finish right away if this urb can't be active ...
@@ -1017,6 +1026,7 @@ static int sl811h_urb_dequeue(struct usb_hcd *hcd, struct urb *urb)
VDBG("dequeue, urb %p active %s; wait4irq\n", urb,
(sl811->active_a == ep) ? "A" : "B");
} else
+fail:
retval = -EINVAL;
spin_unlock_irqrestore(&sl811->lock, flags);
return retval;
@@ -1576,6 +1586,9 @@ sl811h_start(struct usb_hcd *hcd)
if (sl811->board && sl811->board->power)
hub_set_power_budget(udev, sl811->board->power * 2);
+ /* enable power and interupts */
+ port_power(sl811, 1);
+
return 0;
}
@@ -1618,7 +1631,7 @@ static struct hc_driver sl811h_hc_driver = {
/*-------------------------------------------------------------------------*/
-static int __init_or_module
+static int __devexit
sl811h_remove(struct device *dev)
{
struct usb_hcd *hcd = dev_get_drvdata(dev);
@@ -1631,21 +1644,20 @@ sl811h_remove(struct device *dev)
remove_debug_file(sl811);
usb_remove_hcd(hcd);
- iounmap(sl811->data_reg);
+ /* some platforms may use IORESOURCE_IO */
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
- release_mem_region(res->start, 1);
+ if (res)
+ iounmap(sl811->data_reg);
- iounmap(sl811->addr_reg);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- release_mem_region(res->start, 1);
+ if (res)
+ iounmap(sl811->addr_reg);
usb_put_hcd(hcd);
return 0;
}
-#define resource_len(r) (((r)->end - (r)->start) + 1)
-
-static int __init
+static int __devinit
sl811h_probe(struct device *dev)
{
struct usb_hcd *hcd;
@@ -1656,7 +1668,7 @@ sl811h_probe(struct device *dev)
void __iomem *addr_reg;
void __iomem *data_reg;
int retval;
- u8 tmp;
+ u8 tmp, ioaddr = 0;
/* basic sanity checks first. board-specific init logic should
* have initialized these three resources and probably board
@@ -1664,13 +1676,8 @@ sl811h_probe(struct device *dev)
* minimal sanity checking.
*/
pdev = container_of(dev, struct platform_device, dev);
- if (pdev->num_resources < 3)
- return -ENODEV;
-
- addr = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- data = platform_get_resource(pdev, IORESOURCE_MEM, 1);
irq = platform_get_irq(pdev, 0);
- if (!addr || !data || irq < 0)
+ if (pdev->num_resources < 3 || irq < 0)
return -ENODEV;
/* refuse to confuse usbcore */
@@ -1679,24 +1686,31 @@ sl811h_probe(struct device *dev)
return -EINVAL;
}
- if (!request_mem_region(addr->start, 1, hcd_name)) {
- retval = -EBUSY;
- goto err1;
- }
- addr_reg = ioremap(addr->start, resource_len(addr));
- if (addr_reg == NULL) {
- retval = -ENOMEM;
- goto err2;
- }
+ /* the chip may be wired for either kind of addressing */
+ addr = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ data = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ retval = -EBUSY;
+ if (!addr || !data) {
+ addr = platform_get_resource(pdev, IORESOURCE_IO, 0);
+ data = platform_get_resource(pdev, IORESOURCE_IO, 1);
+ if (!addr || !data)
+ return -ENODEV;
+ ioaddr = 1;
+
+ addr_reg = (void __iomem *) addr->start;
+ data_reg = (void __iomem *) data->start;
+ } else {
+ addr_reg = ioremap(addr->start, 1);
+ if (addr_reg == NULL) {
+ retval = -ENOMEM;
+ goto err2;
+ }
- if (!request_mem_region(data->start, 1, hcd_name)) {
- retval = -EBUSY;
- goto err3;
- }
- data_reg = ioremap(data->start, resource_len(addr));
- if (data_reg == NULL) {
- retval = -ENOMEM;
- goto err4;
+ data_reg = ioremap(data->start, 1);
+ if (data_reg == NULL) {
+ retval = -ENOMEM;
+ goto err4;
+ }
}
/* allocate and initialize hcd */
@@ -1737,12 +1751,14 @@ sl811h_probe(struct device *dev)
goto err6;
}
- /* sl811s would need a different handler for this irq */
-#ifdef CONFIG_ARM
- /* Cypress docs say the IRQ is IRQT_HIGH ... */
- set_irq_type(irq, IRQT_RISING);
-#endif
- retval = usb_add_hcd(hcd, irq, SA_INTERRUPT);
+ /* The chip's IRQ is level triggered, active high. A requirement
+ * for platform device setup is to cope with things like signal
+ * inverters (e.g. CF is active low) or working only with edge
+ * triggers (e.g. most ARM CPUs). Initial driver stress testing
+ * was on a system with single edge triggering, so most sorts of
+ * triggering arrangement should work.
+ */
+ retval = usb_add_hcd(hcd, irq, SA_INTERRUPT | SA_SHIRQ);
if (retval != 0)
goto err6;
@@ -1752,14 +1768,12 @@ sl811h_probe(struct device *dev)
err6:
usb_put_hcd(hcd);
err5:
- iounmap(data_reg);
+ if (!ioaddr)
+ iounmap(data_reg);
err4:
- release_mem_region(data->start, 1);
- err3:
- iounmap(addr_reg);
+ if (!ioaddr)
+ iounmap(addr_reg);
err2:
- release_mem_region(addr->start, 1);
- err1:
DBG("init error, %d\n", retval);
return retval;
}
@@ -1767,7 +1781,7 @@ sl811h_probe(struct device *dev)
#ifdef CONFIG_PM
/* for this device there's no useful distinction between the controller
- * and its root hub, except that the root hub only gets direct PM calls
+ * and its root hub, except that the root hub only gets direct PM calls
* when CONFIG_USB_SUSPEND is enabled.
*/
@@ -1821,20 +1835,22 @@ sl811h_resume(struct device *dev, u32 phase)
#endif
-static struct device_driver sl811h_driver = {
+/* this driver is exported so sl811_cs can depend on it */
+struct device_driver sl811h_driver = {
.name = (char *) hcd_name,
.bus = &platform_bus_type,
.probe = sl811h_probe,
- .remove = sl811h_remove,
+ .remove = __devexit_p(sl811h_remove),
.suspend = sl811h_suspend,
.resume = sl811h_resume,
};
+EXPORT_SYMBOL(sl811h_driver);
/*-------------------------------------------------------------------------*/
-
-static int __init sl811h_init(void)
+
+static int __init sl811h_init(void)
{
if (usb_disabled())
return -ENODEV;
@@ -1844,8 +1860,8 @@ static int __init sl811h_init(void)
}
module_init(sl811h_init);
-static void __exit sl811h_cleanup(void)
-{
+static void __exit sl811h_cleanup(void)
+{
driver_unregister(&sl811h_driver);
}
module_exit(sl811h_cleanup);
diff --git a/drivers/usb/host/sl811_cs.c b/drivers/usb/host/sl811_cs.c
new file mode 100644
index 000000000000..6e173265095c
--- /dev/null
+++ b/drivers/usb/host/sl811_cs.c
@@ -0,0 +1,442 @@
+/*
+ * PCMCIA driver for SL811HS (as found in REX-CFU1U)
+ * Filename: sl811_cs.c
+ * Author: Yukio Yamamoto
+ *
+ * Port to sl811-hcd and 2.6.x by
+ * Botond Botyanszki <boti@rocketmail.com>
+ * Simon Pickering
+ *
+ * Last update: 2005-05-12
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.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/ioport.h>
+
+#include <pcmcia/version.h>
+#include <pcmcia/cs_types.h>
+#include <pcmcia/cs.h>
+#include <pcmcia/cistpl.h>
+#include <pcmcia/cisreg.h>
+#include <pcmcia/ds.h>
+
+#include <linux/usb_sl811.h>
+
+MODULE_AUTHOR("Botond Botyanszki");
+MODULE_DESCRIPTION("REX-CFU1U PCMCIA driver for 2.6");
+MODULE_LICENSE("GPL");
+
+
+/*====================================================================*/
+/* MACROS */
+/*====================================================================*/
+
+#if defined(DEBUG) || defined(CONFIG_USB_DEBUG) || defined(PCMCIA_DEBUG)
+
+static int pc_debug = 0;
+module_param(pc_debug, int, 0644);
+
+#define DBG(n, args...) if (pc_debug>(n)) printk(KERN_DEBUG "sl811_cs: " args)
+
+#else
+#define DBG(n, args...) do{}while(0)
+#endif /* no debugging */
+
+#define INFO(args...) printk(KERN_INFO "sl811_cs: " args)
+
+#define INT_MODULE_PARM(n, v) static int n = v; module_param(n, int, 0444)
+
+#define CS_CHECK(fn, ret) \
+ do { \
+ last_fn = (fn); \
+ if ((last_ret = (ret)) != 0) \
+ goto cs_failed; \
+ } while (0)
+
+/*====================================================================*/
+/* VARIABLES */
+/*====================================================================*/
+
+static const char driver_name[DEV_NAME_LEN] = "sl811_cs";
+
+static dev_link_t *dev_list = NULL;
+
+static int irq_list[4] = { -1 };
+static int irq_list_count;
+
+module_param_array(irq_list, int, &irq_list_count, 0444);
+
+INT_MODULE_PARM(irq_mask, 0xdeb8);
+
+typedef struct local_info_t {
+ dev_link_t link;
+ dev_node_t node;
+} local_info_t;
+
+/*====================================================================*/
+
+static void release_platform_dev(struct device * dev)
+{
+ DBG(0, "sl811_cs platform_dev release\n");
+ dev->parent = NULL;
+}
+
+static struct sl811_platform_data platform_data = {
+ .potpg = 100,
+ .power = 50, /* == 100mA */
+ // .reset = ... FIXME: invoke CF reset on the card
+};
+
+static struct resource resources[] = {
+ [0] = {
+ .flags = IORESOURCE_IRQ,
+ },
+ [1] = {
+ // .name = "address",
+ .flags = IORESOURCE_IO,
+ },
+ [2] = {
+ // .name = "data",
+ .flags = IORESOURCE_IO,
+ },
+};
+
+extern struct device_driver sl811h_driver;
+
+static struct platform_device platform_dev = {
+ .id = -1,
+ .dev = {
+ .platform_data = &platform_data,
+ .release = release_platform_dev,
+ },
+ .resource = resources,
+ .num_resources = ARRAY_SIZE(resources),
+};
+
+static int sl811_hc_init(struct device *parent, ioaddr_t base_addr, int irq)
+{
+ if (platform_dev.dev.parent)
+ return -EBUSY;
+ platform_dev.dev.parent = parent;
+
+ /* finish seting up the platform device */
+ resources[0].start = irq;
+
+ resources[1].start = base_addr;
+ resources[1].end = base_addr;
+
+ resources[2].start = base_addr + 1;
+ resources[2].end = base_addr + 1;
+
+ /* The driver core will probe for us. We know sl811-hcd has been
+ * initialized already because of the link order dependency.
+ */
+ platform_dev.name = sl811h_driver.name;
+ return platform_device_register(&platform_dev);
+}
+
+/*====================================================================*/
+
+static void sl811_cs_detach(dev_link_t *link)
+{
+ dev_link_t **linkp;
+
+ DBG(0, "sl811_cs_detach(0x%p)\n", link);
+
+ /* Locate device structure */
+ for (linkp = &dev_list; *linkp; linkp = &(*linkp)->next) {
+ if (*linkp == link)
+ break;
+ }
+ if (*linkp == NULL)
+ return;
+
+ /* Break the link with Card Services */
+ if (link->handle)
+ pcmcia_deregister_client(link->handle);
+
+ /* Unlink device structure, and free it */
+ *linkp = link->next;
+ /* This points to the parent local_info_t struct */
+ kfree(link->priv);
+}
+
+static void sl811_cs_release(dev_link_t * link)
+{
+
+ DBG(0, "sl811_cs_release(0x%p)\n", link);
+
+ if (link->open) {
+ DBG(1, "sl811_cs: release postponed, '%s' still open\n",
+ link->dev->dev_name);
+ link->state |= DEV_STALE_CONFIG;
+ return;
+ }
+
+ /* Unlink the device chain */
+ link->dev = NULL;
+
+ platform_device_unregister(&platform_dev);
+ pcmcia_release_configuration(link->handle);
+ if (link->io.NumPorts1)
+ pcmcia_release_io(link->handle, &link->io);
+ if (link->irq.AssignedIRQ)
+ pcmcia_release_irq(link->handle, &link->irq);
+ link->state &= ~DEV_CONFIG;
+
+ if (link->state & DEV_STALE_LINK)
+ sl811_cs_detach(link);
+}
+
+static void sl811_cs_config(dev_link_t *link)
+{
+ client_handle_t handle = link->handle;
+ struct device *parent = &handle_to_dev(handle);
+ local_info_t *dev = link->priv;
+ tuple_t tuple;
+ cisparse_t parse;
+ int last_fn, last_ret;
+ u_char buf[64];
+ config_info_t conf;
+ cistpl_cftable_entry_t dflt = { 0 };
+
+ DBG(0, "sl811_cs_config(0x%p)\n", link);
+
+ tuple.DesiredTuple = CISTPL_CONFIG;
+ tuple.Attributes = 0;
+ tuple.TupleData = buf;
+ tuple.TupleDataMax = sizeof(buf);
+ tuple.TupleOffset = 0;
+ CS_CHECK(GetFirstTuple, pcmcia_get_first_tuple(handle, &tuple));
+ CS_CHECK(GetTupleData, pcmcia_get_tuple_data(handle, &tuple));
+ CS_CHECK(ParseTuple, pcmcia_parse_tuple(handle, &tuple, &parse));
+ link->conf.ConfigBase = parse.config.base;
+ link->conf.Present = parse.config.rmask[0];
+
+ /* Configure card */
+ link->state |= DEV_CONFIG;
+
+ /* Look up the current Vcc */
+ CS_CHECK(GetConfigurationInfo,
+ pcmcia_get_configuration_info(handle, &conf));
+ link->conf.Vcc = conf.Vcc;
+
+ tuple.DesiredTuple = CISTPL_CFTABLE_ENTRY;
+ CS_CHECK(GetFirstTuple, pcmcia_get_first_tuple(handle, &tuple));
+ while (1) {
+ cistpl_cftable_entry_t *cfg = &(parse.cftable_entry);
+
+ if (pcmcia_get_tuple_data(handle, &tuple) != 0
+ || pcmcia_parse_tuple(handle, &tuple, &parse)
+ != 0)
+ goto next_entry;
+
+ if (cfg->flags & CISTPL_CFTABLE_DEFAULT) {
+ dflt = *cfg;
+ }
+
+ if (cfg->index == 0)
+ goto next_entry;
+
+ link->conf.ConfigIndex = cfg->index;
+
+ /* Use power settings for Vcc and Vpp if present */
+ /* Note that the CIS values need to be rescaled */
+ if (cfg->vcc.present & (1<<CISTPL_POWER_VNOM)) {
+ if (cfg->vcc.param[CISTPL_POWER_VNOM]/10000
+ != conf.Vcc)
+ goto next_entry;
+ } else if (dflt.vcc.present & (1<<CISTPL_POWER_VNOM)) {
+ if (dflt.vcc.param[CISTPL_POWER_VNOM]/10000
+ != conf.Vcc)
+ goto next_entry;
+ }
+
+ if (cfg->vpp1.present & (1<<CISTPL_POWER_VNOM))
+ link->conf.Vpp1 = link->conf.Vpp2 =
+ cfg->vpp1.param[CISTPL_POWER_VNOM]/10000;
+ else if (dflt.vpp1.present & (1<<CISTPL_POWER_VNOM))
+ link->conf.Vpp1 = link->conf.Vpp2 =
+ dflt.vpp1.param[CISTPL_POWER_VNOM]/10000;
+
+ /* we need an interrupt */
+ if (cfg->irq.IRQInfo1 || dflt.irq.IRQInfo1)
+ link->conf.Attributes |= CONF_ENABLE_IRQ;
+
+ /* IO window settings */
+ link->io.NumPorts1 = link->io.NumPorts2 = 0;
+ if ((cfg->io.nwin > 0) || (dflt.io.nwin > 0)) {
+ cistpl_io_t *io = (cfg->io.nwin) ? &cfg->io : &dflt.io;
+
+ link->io.Attributes1 = IO_DATA_PATH_WIDTH_8;
+ link->io.IOAddrLines = io->flags & CISTPL_IO_LINES_MASK;
+ link->io.BasePort1 = io->win[0].base;
+ link->io.NumPorts1 = io->win[0].len;
+
+ if (pcmcia_request_io(link->handle, &link->io) != 0)
+ goto next_entry;
+ }
+ break;
+
+next_entry:
+ if (link->io.NumPorts1)
+ pcmcia_release_io(link->handle, &link->io);
+ last_ret = pcmcia_get_next_tuple(handle, &tuple);
+ }
+
+ /* require an IRQ and two registers */
+ if (!link->io.NumPorts1 || link->io.NumPorts1 < 2)
+ goto cs_failed;
+ if (link->conf.Attributes & CONF_ENABLE_IRQ)
+ CS_CHECK(RequestIRQ,
+ pcmcia_request_irq(link->handle, &link->irq));
+ else
+ goto cs_failed;
+
+ CS_CHECK(RequestConfiguration,
+ pcmcia_request_configuration(link->handle, &link->conf));
+
+ sprintf(dev->node.dev_name, driver_name);
+ dev->node.major = dev->node.minor = 0;
+ link->dev = &dev->node;
+
+ printk(KERN_INFO "%s: index 0x%02x: Vcc %d.%d",
+ dev->node.dev_name, link->conf.ConfigIndex,
+ link->conf.Vcc/10, link->conf.Vcc%10);
+ if (link->conf.Vpp1)
+ printk(", Vpp %d.%d", link->conf.Vpp1/10, link->conf.Vpp1%10);
+ printk(", irq %d", link->irq.AssignedIRQ);
+ printk(", io 0x%04x-0x%04x", link->io.BasePort1,
+ link->io.BasePort1+link->io.NumPorts1-1);
+ printk("\n");
+
+ link->state &= ~DEV_CONFIG_PENDING;
+
+ if (sl811_hc_init(parent, link->io.BasePort1, link->irq.AssignedIRQ)
+ < 0) {
+cs_failed:
+ printk("sl811_cs_config failed\n");
+ cs_error(link->handle, last_fn, last_ret);
+ sl811_cs_release(link);
+ link->state &= ~DEV_CONFIG_PENDING;
+ }
+}
+
+static int
+sl811_cs_event(event_t event, int priority, event_callback_args_t *args)
+{
+ dev_link_t *link = args->client_data;
+
+ DBG(1, "sl811_cs_event(0x%06x)\n", event);
+
+ switch (event) {
+ case CS_EVENT_CARD_REMOVAL:
+ link->state &= ~DEV_PRESENT;
+ if (link->state & DEV_CONFIG)
+ sl811_cs_release(link);
+ break;
+
+ case CS_EVENT_CARD_INSERTION:
+ link->state |= DEV_PRESENT | DEV_CONFIG_PENDING;
+ sl811_cs_config(link);
+ break;
+
+ case CS_EVENT_PM_SUSPEND:
+ link->state |= DEV_SUSPEND;
+ /* Fall through... */
+ case CS_EVENT_RESET_PHYSICAL:
+ if (link->state & DEV_CONFIG)
+ pcmcia_release_configuration(link->handle);
+ break;
+
+ case CS_EVENT_PM_RESUME:
+ link->state &= ~DEV_SUSPEND;
+ /* Fall through... */
+ case CS_EVENT_CARD_RESET:
+ if (link->state & DEV_CONFIG)
+ pcmcia_request_configuration(link->handle, &link->conf);
+ DBG(0, "reset sl811-hcd here?\n");
+ break;
+ }
+ return 0;
+}
+
+static dev_link_t *sl811_cs_attach(void)
+{
+ local_info_t *local;
+ dev_link_t *link;
+ client_reg_t client_reg;
+ int ret, i;
+
+ local = kmalloc(sizeof(local_info_t), GFP_KERNEL);
+ if (!local)
+ return NULL;
+ memset(local, 0, sizeof(local_info_t));
+ link = &local->link;
+ link->priv = local;
+
+ /* Initialize */
+ 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 < irq_list_count; i++)
+ link->irq.IRQInfo2 |= 1 << irq_list[i];
+ link->irq.Handler = NULL;
+
+ link->conf.Attributes = 0;
+ link->conf.Vcc = 33;
+ link->conf.IntType = INT_MEMORY_AND_IO;
+
+ /* Register with Card Services */
+ link->next = dev_list;
+ dev_list = link;
+ client_reg.dev_info = (dev_info_t *) &driver_name;
+ 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 = &sl811_cs_event;
+ client_reg.Version = 0x0210;
+ client_reg.event_callback_args.client_data = link;
+ ret = pcmcia_register_client(&link->handle, &client_reg);
+ if (ret != CS_SUCCESS) {
+ cs_error(link->handle, RegisterClient, ret);
+ sl811_cs_detach(link);
+ return NULL;
+ }
+
+ return link;
+}
+
+static struct pcmcia_driver sl811_cs_driver = {
+ .owner = THIS_MODULE,
+ .drv = {
+ .name = (char *)driver_name,
+ },
+ .attach = sl811_cs_attach,
+ .detach = sl811_cs_detach,
+};
+
+/*====================================================================*/
+
+static int __init init_sl811_cs(void)
+{
+ return pcmcia_register_driver(&sl811_cs_driver);
+}
+module_init(init_sl811_cs);
+
+static void __exit exit_sl811_cs(void)
+{
+ pcmcia_unregister_driver(&sl811_cs_driver);
+}
+module_exit(exit_sl811_cs);