URB Queueing for Memory Constrained HCs

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



I am using the PLX Technologies OXU210HP USB Host Controller and would
like guidance on the best approach for working with memory constrained
devices.  The OXU210 is a slave DMA device with 72K of SRAM and I have
modified an existing EHCI-OXU210HP driver to 'work' using
HCD_LOCAL_MEM.

One issue I have not resolved is how to deal with the case where Linux
USB core system expects memory to available when usb_hcd_submit_urb is
called.  I cannot provide a new implementation of ehci_urb_enqueue
because the call to map_urb_for_dma in usb_hcd_submit_urb fails before
ehci_enqueue_urb is called.  To get through the initial board debug, I
have implemented an URB queue that allows the memory allocation to
fail and retry without depending on the higher level device drivers to
handle the failure.  Most of them do not handle this situation
gracefully.  As a result of breaking the USB state machine, I have
(not surprisingly) run into race conditions in how URBs are unlinked
and when devices are removed unexpectedly.

At this point I am looking for recommendations on the best path going
forward.  One option is to move the changes I made in usb/core to
usb/ehci-oxu210hp.c and add new functions to the hc_driver, similar to
ehci_urb_enqueue/dequeue, but this does not resolve the root cause of
needing the queue in the first place.

Thanks,
Greg

diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c
index 81fa850..924f5fd 100644
--- a/drivers/usb/core/hcd.c
+++ b/drivers/usb/core/hcd.c
@@ -45,6 +45,7 @@
 #include "hcd.h"
 #include "hub.h"

+extern int remove_urb_from_queue (struct urb *urb, int status);

 /*-------------------------------------------------------------------------*/

@@ -1359,7 +1364,15 @@ static int unlink1(struct usb_hcd *hcd, struct
urb *urb, int status)
 		 * it has not yet fully queued the urb to begin with.
 		 * Such failures should be harmless. */
 		value = hcd->driver->urb_dequeue(hcd, urb, status);
+
+		/* If the URB is not dequeued by the HCD, check
+		 * the local URB queue.  This should probably be
+		 * moved into the urb_dequeue function
+		 */
+		if (value == -EIDRM)
+			value = remove_urb_from_queue (urb, status);
 	}
+
 	return value;
 }

diff --git a/drivers/usb/core/urb.c b/drivers/usb/core/urb.c
index 3376055..3aca995 100644
--- a/drivers/usb/core/urb.c
+++ b/drivers/usb/core/urb.c
@@ -7,9 +7,17 @@
 #include <linux/usb.h>
 #include <linux/wait.h>
 #include "hcd.h"
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include <linux/workqueue.h>

 #define to_urb(d) container_of(d, struct urb, kref)

+extern int queue_urb(
+	struct urb		*urb,
+	gfp_t			memflags
+);
+

 static void urb_destroy(struct kref *kref)
 {
@@ -453,7 +461,7 @@ int usb_submit_urb(struct urb *urb, gfp_t mem_flags)
 		urb->interval = min(max, 1 << ilog2(urb->interval));
 	}

-	return usb_hcd_submit_urb(urb, mem_flags);
+	return queue_urb(urb, mem_flags);
 }
 EXPORT_SYMBOL_GPL(usb_submit_urb);

@@ -815,3 +823,167 @@ int usb_anchor_empty(struct usb_anchor *anchor)

 EXPORT_SYMBOL_GPL(usb_anchor_empty);

+/* Start URB queuing code */
+
+static DEFINE_SPINLOCK(urblock);
+static LIST_HEAD(urb_queue);
+static struct workqueue_struct *urb_workqueue = NULL;
+
+/*
+ * qd_urb status values
+ */
+#define UQ_QUEUED    ( 1 )
+#define UQ_INPROCESS ( 2 )
+#define UQ_NUKED     ( 3 )
+
+struct qd_urb {
+	struct list_head	list;
+	struct urb		*urb;
+	int                     urb_status;
+	gfp_t			memflags;
+	unsigned		count;
+	volatile unsigned       status;
+};
+
+static void submit_urb(void *ignored)
+{
+	unsigned long irqflags = 0;
+	int status = 0;
+
+	spin_lock_irqsave(&urblock, irqflags);
+	while (!list_empty(&urb_queue)) {
+		struct qd_urb *qu;
+
+		qu = list_entry(urb_queue.next, struct qd_urb, list);
+		qu->status = UQ_INPROCESS;
+		spin_unlock_irqrestore(&urblock, irqflags);
+
+		while ((qu->status == UQ_INPROCESS) &&
+		       ((status = usb_hcd_submit_urb (qu->urb, qu->memflags)) == -ENOMEM)) {
+			qu->count++;
+			msleep (1);
+		}
+
+		if (status == -EPERM) {
+			qu->urb->status = -ENOENT;
+			qu->urb->complete (qu->urb);
+		} else if (qu->status == UQ_NUKED) {
+			qu->urb->status = qu->urb_status;
+			qu->urb->complete (qu->urb);
+		} else if (status != 0) {
+			printk (KERN_ERR "%s:%d usb_hcd_submit_urb failed for %p, status
%d, urb_status = %d\n",
+				__func__, __LINE__, qu->urb, status, qu->urb_status);
+			qu->urb->status = qu->urb_status;
+			qu->urb->complete (qu->urb);
+		}
+		
+		spin_lock_irqsave(&urblock, irqflags);
+		list_del(&qu->list);
+		spin_unlock_irqrestore(&urblock, irqflags);
+
+		atomic_dec(&qu->urb->use_count);
+		if (unlikely(atomic_read(&qu->urb->reject)))
+			wake_up (&usb_kill_urb_queue);
+		usb_put_urb(qu->urb);
+		kfree (qu);
+		spin_lock_irqsave(&urblock, irqflags);
+	}
+	spin_unlock_irqrestore(&urblock, irqflags);
+}
+
+/*
+ * This method returns 1 if the URB is in the queue and 0 if not
+ */
+int usb_check_urb_queued (struct urb *urb) {
+	int found = 0;
+	unsigned long irqflags = 0;
+	struct qd_urb *qu = NULL;
+	struct list_head *tmp = NULL;
+
+	spin_lock_irqsave (&urblock, irqflags);
+	list_for_each (tmp, &urb_queue) {
+		qu = list_entry (urb_queue.next, struct qd_urb, list);
+
+		if (qu->urb == urb) {
+			found = 1;
+			break;
+		}
+	}
+	spin_unlock_irqrestore (&urblock, irqflags);
+	return found;
+}
+EXPORT_SYMBOL_GPL (usb_check_urb_queued);
+
+/*
+ * This method is called to remove an URB from the urb_queue.  If the
+ * URB is in process to be submitted, the status of the qd_urb is set
+ * to UQ_NUKED, which will be detected by the workqueue and not continue
+ * retrying the submission.
+ */
+int remove_urb_from_queue (struct urb *urb, int status) {
+	unsigned long irqflags = 0;
+	struct qd_urb *qu;
+	int retval = -EIDRM;
+
+	spin_lock_irqsave(&urblock, irqflags);
+	while (!list_empty(&urb_queue)) {
+
+		qu = list_entry(urb_queue.next, struct qd_urb, list);
+		if (qu->urb == urb) {
+			if (qu->status == UQ_QUEUED) {
+				qu->urb->status = status;
+				qu->urb->complete (qu->urb);
+				atomic_dec(&qu->urb->use_count);
+				usb_put_urb(qu->urb);
+				list_del(&qu->list);
+				kfree (qu);
+				retval = 0;
+			}
+			else {
+				qu->urb_status = status;
+				qu->status = UQ_NUKED;
+				retval = 0;
+			}
+			break;
+		}
+	}		
+	spin_unlock_irqrestore(&urblock, irqflags);
+
+	return retval;
+}
+EXPORT_SYMBOL_GPL (remove_urb_from_queue);
+
+static DECLARE_WORK(deferred_submit_urb, submit_urb);
+
+int queue_urb(
+	struct urb		*urb,
+	gfp_t			memflags
+)
+{
+	unsigned long irqflags = 0;
+	struct qd_urb *qu = NULL;
+	int status = 0;
+
+	/* Need to bump the ref counter on the urb so that it is not
+	 * freed before handing off to rest of system */
+	usb_get_urb(urb);
+	atomic_inc(&urb->use_count);
+
+	if (unlikely(urb_workqueue == NULL))
+		urb_workqueue = create_workqueue("urb_queue");
+
+	qu = kmalloc (sizeof (struct qd_urb), GFP_KERNEL);
+
+	qu->urb = urb;
+	qu->memflags = memflags;
+	qu->count = 0;
+	qu->status = UQ_QUEUED;
+
+	spin_lock_irqsave(&urblock, irqflags);
+	list_add_tail(&qu->list, &urb_queue);
+	queue_work(urb_workqueue, &deferred_submit_urb);
+	spin_unlock_irqrestore(&urblock, irqflags);
+	return status;
+}
+
+
diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c
index f2618d1..3730562 100644
--- a/drivers/usb/host/ehci-hcd.c
+++ b/drivers/usb/host/ehci-hcd.c
@@ -44,6 +44,10 @@
 #include <asm/system.h>
 #include <asm/unaligned.h>

+
+int usb_check_urb_queued (struct urb *urb);
+
+
 /*-------------------------------------------------------------------------*/

 /*
@@ -65,7 +69,6 @@

 static const char	hcd_name [] = "ehci_hcd";

-
 #undef VERBOSE_DEBUG
 #undef EHCI_URB_TRACE

@@ -622,7 +625,7 @@ static int ehci_run (struct usb_hcd *hcd)
 		ehci_writel(ehci, 0, &ehci->regs->segment);
 #if 0
 // this is deeply broken on almost all architectures
-		if (!dma_set_mask(hcd->self.controller, DMA_64BIT_MASK))
+		if (!dma_set_mask(hcd->self.controller, DMA_BIT_MASK(64)))
 			ehci_info(ehci, "enabled 64bit DMA\n");
 #endif
 	}
@@ -883,7 +886,7 @@ static int ehci_urb_dequeue(struct usb_hcd *hcd,
struct urb *urb, int status)

 	spin_lock_irqsave (&ehci->lock, flags);
 	rc = usb_hcd_check_unlink_urb(hcd, urb, status);
-	if (rc)
+	if (rc && !usb_check_urb_queued (urb))
 		goto done;

 	switch (usb_pipetype (urb->pipe)) {
@@ -903,7 +906,8 @@ static int ehci_urb_dequeue(struct usb_hcd *hcd,
struct urb *urb, int status)
 			/* already started */
 			break;
 		case QH_STATE_IDLE:
-			WARN_ON(1);
+			/* QH might be waiting for a Clear-TT-Buffer */
+			qh_completions(ehci, qh);
 			break;
 		}
 		break;
@@ -1003,6 +1007,8 @@ idle_timeout:
 		schedule_timeout_uninterruptible(1);
 		goto rescan;
 	case QH_STATE_IDLE:		/* fully unlinked */
+		if (qh->clearing_tt)
+			goto idle_timeout;
 		if (list_empty (&qh->qtd_list)) {
 			qh_put (qh);
 			break;
@@ -1042,11 +1048,21 @@ MODULE_LICENSE ("GPL");
 #define	PCI_DRIVER		ehci_pci_driver
 #endif

+#ifdef CONFIG_USB_EHCI_OXU210HP
+#include "ehci-oxu210hp.c"
+#define	PLATFORM_DRIVER		oxu210hp_ehci_driver
+#endif
+
 #ifdef CONFIG_USB_EHCI_FSL
 #include "ehci-fsl.c"
 #define	PLATFORM_DRIVER		ehci_fsl_driver
 #endif

+#ifdef CONFIG_USB_EHCI_OXU210
+#include "ehci-oxu210.c"
+#define	PLATFORM_DRIVER		oxu210_ehci_driver
+#endif
+
 #ifdef CONFIG_SOC_AU1200
 #include "ehci-au1xxx.c"
 #define	PLATFORM_DRIVER		ehci_hcd_au1xxx_driver
diff --git a/drivers/usb/host/ehci-oxu210hp.c b/drivers/usb/host/ehci-oxu210hp.c
new file mode 100644
index 0000000..62c7437
--- /dev/null
+++ b/drivers/usb/host/ehci-oxu210hp.c
@@ -0,0 +1,366 @@
+/*
+ * OXU210HP Host Controller Driver
+ *
+ * Author: Amit Walambe <amit.walambe@xxxxxxxxxxxx>
+ *
+ * Based on drivers/usb/host/oxu210hp-hcd.c and
+ * drivers/usb/host/ehci-*.c
+ *
+ * Copyright (c) 2009 Eurotech Ltd.
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#include <linux/platform_device.h>
+#include <asm/irq.h>
+#include <linux/irq.h>
+#include <asm-generic/dma-coherent.h>
+
+#include "ehci-oxu210hp.h"
+
+
+static int oxu210hp_reset(struct usb_hcd *hcd)
+{
+	struct ehci_hcd *ehci = hcd_to_ehci(hcd);
+	int retval;
+
+	/* FIMXE */
+	hcd->self.controller->dma_mask = 0UL;
+	printk("running reset\n");
+
+	ehci->caps = hcd->regs + 0x100;
+	ehci->regs = hcd->regs + 0x100 + HC_LENGTH(ehci_readl(ehci,
&ehci->caps->hc_capbase));
+
+	ehci->hcs_params = readl(&ehci->caps->hcs_params);
+	ehci->sbrn = 0x20;
+
+	retval = ehci_halt(ehci);
+	if (retval)
+		return retval;
+
+	retval = ehci_init(hcd);
+	if (retval)
+		return retval;
+
+	hcd->has_tt = 1;
+
+	ehci_reset(ehci);
+
+	ehci_port_power(ehci, 0);
+
+	return retval;
+}
+
+static const struct hc_driver oxu210hp_ehci_hc_driver = {
+	.description		= hcd_name,
+	.product_desc		= "OXU210HP EHCI Host Controller",
+	.hcd_priv_size		= sizeof(struct ehci_hcd),
+	.irq			= ehci_irq,
+	.flags			= HCD_MEMORY | HCD_USB2 | HCD_LOCAL_MEM,
+	.reset			= oxu210hp_reset,
+	.start			= ehci_run,
+	.stop			= ehci_stop,
+	.shutdown		= ehci_shutdown,
+	.urb_enqueue		= ehci_urb_enqueue,
+	.urb_dequeue		= ehci_urb_dequeue,
+	.endpoint_disable	= ehci_endpoint_disable,
+	.get_frame_number	= ehci_get_frame,
+	.hub_status_data	= ehci_hub_status_data,
+	.hub_control		= ehci_hub_control,
+#if defined(CONFIG_PM)
+	.bus_suspend		= ehci_bus_suspend,
+	.bus_resume		= ehci_bus_resume,
+#endif
+	.relinquish_port	= ehci_relinquish_port,
+	.port_handed_over	= ehci_port_handed_over,
+
+	.clear_tt_buffer_complete = ehci_clear_tt_buffer_complete,
+};
+
+static struct usb_hcd *oxu210hp_create(struct platform_device *pdev,
+				unsigned long memstart, unsigned long memlen,
+				void *base, int irq, int otg)
+{
+	struct device *dev = &pdev->dev;
+
+	struct usb_hcd *hcd;
+	//struct echi_hcd *ehci;
+	int ret;
+
+	/* Set endian mode and host mode */
+	if (otg)
+		writel(OXU_CM_HOST_ONLY, base + OXU_OTG_CORE_OFFSET + OXU_USBMODE);
+
+	hcd = usb_create_hcd(&oxu210hp_ehci_hc_driver, dev,
+				otg ? "oxu210hp_otg" : "oxu210hp_sph");
+	if (!hcd)
+		return ERR_PTR(-ENOMEM);
+
+	//hcd->rsrc_start = memstart;
+	hcd->rsrc_start = memstart + ( otg ? OXU_OTG_CORE_OFFSET :
OXU_SPH_CORE_OFFSET );
+	//hcd->rsrc_len = memlen;
+	hcd->rsrc_len = SZ_1K;
+	//hcd->regs = base;
+	hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len);
+	hcd->irq = irq;
+	hcd->state = HC_STATE_HALT;
+
+	//ehci = hcd_to_ehci(hcd);
+
+	ret = usb_add_hcd(hcd, irq, IRQF_SHARED);
+	if (ret < 0)
+		return ERR_PTR(ret);
+
+	return hcd;
+}
+
+static void oxu210hp_configuration(struct platform_device *pdev, void *base)
+{
+	u32 tmp;
+
+	/* Initialize top level registers.
+	 * - Expected HostIfConfig bits : BE 0, bit DT 0, bit IP 1, bit BM 1
+	 */
+	writel(0x000003FD, base + OXU_HOSTIFCONFIG);
+	writel(OXU_SRESET, base + OXU_SOFTRESET);
+	writel(0x000003FD, base + OXU_HOSTIFCONFIG);
+
+	//TODO what should this be set to?
+	tmp = readl(base + OXU_PIOBURSTREADCTRL);
+	writel(tmp | 0x0040, base + OXU_PIOBURSTREADCTRL);
+	//writel(0x0, base + OXU_PIOBURSTREADCTRL);
+
+	writel(OXU_SPHPOEN | OXU_OVRCCURPUPDEN | OXU_COMPARATOR | OXU_ASO_OP,
+					base + OXU_ASO);
+
+	tmp = readl(base + OXU_CLKCTRL_SET);
+	writel(tmp | OXU_SYSCLKEN | OXU_USBSPHCLKEN | OXU_USBOTGCLKEN, base
+ OXU_CLKCTRL_SET);
+
+	/* Clear all top interrupt enable */
+	writel(0xFF, base + OXU_CHIPIRQEN_CLR);
+
+	/* Clear all top interrupt status */
+	writel(0xFF, base + OXU_CHIPIRQSTATUS);
+
+	/* Enable all needed top interrupt except OTG SPH core */
+	writel(OXU_USBSPHLPWUI | OXU_USBOTGLPWUI, base + OXU_CHIPIRQEN_SET);
+}
+
+static int oxu210hp_verify_id(struct platform_device *pdev, void *base)
+{
+	u32 id;
+	char *bo[] = {
+		"reserved",
+		"128-pin LQFP",
+		"84-pin TFBGA",
+		"reserved",
+	};
+
+	/* Read controller signature register to find a match */
+	id = readl(base + OXU_DEVICEID);
+	dev_info(&pdev->dev, "device ID %x\n", id);
+	if ((id & OXU_REV_MASK) != (OXU_REV_2100 << OXU_REV_SHIFT))
+		return -1;
+
+	dev_info(&pdev->dev, "found device %x %s (%04x:%04x)\n",
+		id >> OXU_REV_SHIFT,
+		bo[(id & OXU_BO_MASK) >> OXU_BO_SHIFT],
+		(id & OXU_MAJ_REV_MASK) >> OXU_MAJ_REV_SHIFT,
+		(id & OXU_MIN_REV_MASK) >> OXU_MIN_REV_SHIFT);
+
+	return 0;
+}
+
+static int oxu210hp_init(struct platform_device *pdev,
+				unsigned long memstart, unsigned long memlen,
+				void *base, int irq)
+{
+	struct oxu_info *info = platform_get_drvdata(pdev);
+	struct usb_hcd *hcd;
+	int ret;
+
+	/* First time configuration at start up */
+	oxu210hp_configuration(pdev, base);
+
+	ret = oxu210hp_verify_id(pdev, base);
+	if (ret) {
+		dev_err(&pdev->dev, "no devices found!\n");
+		return -ENODEV;
+	}
+
+	/* Create the OTG controller */
+	hcd = oxu210hp_create(pdev, memstart, memlen, base, irq, 1);
+	if (IS_ERR(hcd)) {
+		dev_err(&pdev->dev, "cannot create OTG controller!\n");
+		ret = PTR_ERR(hcd);
+		goto error_create_otg;
+	}
+	info->hcd[0] = hcd;
+
+	/* Create the SPH host controller */
+	hcd = oxu210hp_create(pdev, memstart, memlen, base, irq, 0);
+	if (IS_ERR(hcd)) {
+		dev_err(&pdev->dev, "cannot create SPH controller!\n");
+		ret = PTR_ERR(hcd);
+		goto error_create_sph;
+	}
+	info->hcd[1] = hcd;
+
+	writel(readl(base + OXU_CHIPIRQEN_SET) | 3,
+			base + OXU_CHIPIRQEN_SET);
+
+	return 0;
+
+error_create_sph:
+	usb_remove_hcd(info->hcd[0]);
+	usb_put_hcd(info->hcd[0]);
+
+error_create_otg:
+	return ret;
+}
+
+static int oxu210hp_ehci_probe(struct platform_device *pdev)
+{
+	struct resource *res;
+	void *base;
+	int irq, ret;
+	struct oxu_info *info;
+
+	if (usb_disabled())
+		return -ENODEV;
+
+	/*
+	 * Get the platform resources
+	 */
+	res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+	if (!res) {
+		dev_err(&pdev->dev,
+			"no IRQ! Check %s setup!\n", dev_name(&pdev->dev));
+		return -ENODEV;
+	}
+	irq = res->start;
+	dev_dbg(&pdev->dev, "IRQ resource %d\n", irq);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		dev_err(&pdev->dev, "no registers address! Check %s setup!\n",
+			dev_name(&pdev->dev));
+		return -ENODEV;
+	}
+
+	dev_dbg(&pdev->dev, "MEM resource %x-%x\n", res->start, resource_size(res));
+	if (!request_mem_region(res->start, resource_size(res),
+				oxu210hp_ehci_hc_driver.description)) {
+		dev_dbg(&pdev->dev, "memory area already in use\n");
+		return -EBUSY;
+	}
+
+	ret = set_irq_type(irq, IRQF_TRIGGER_FALLING);
+	if (ret) {
+		dev_err(&pdev->dev, "error setting irq type\n");
+		ret = -EFAULT;
+		goto error_set_irq_type;
+	}
+
+	base = ioremap(res->start, SZ_1K);
+	if (!base) {
+		dev_dbg(&pdev->dev, "error mapping memory\n");
+		ret = -EFAULT;
+		goto error_ioremap;
+	}
+
+	/* Here we try to use dma_declare_coherent_memory() to make sure
+	 * usb allocations with dma_alloc_coherent() allocate from
+	 * OXU210's local memory. The dma_handle returned by
+	 * dma_alloc_coherent() will be an offset starting from 0 for the
+	 * first local memory byte.
+	 *
+	 * Using the corresponding HCD_LOCAL_MEM flag in HCD creation.
+	 */
+	if (!dma_declare_coherent_memory(&pdev->dev, res->start + OXU_SRAM_OFFSET,
+							OXU_SRAM_OFFSET,
+							SZ_64K + SZ_8K,
+							DMA_MEMORY_MAP |
+							DMA_MEMORY_EXCLUSIVE)) {
+			dev_err(&pdev->dev, "cannot declare DMA coherent memory\n");
+			ret = -ENXIO;
+			goto error_alloc;
+	}
+
+	/* Allocate a driver data struct to hold useful info for both
+	 * SPH & OTG devices
+	 */
+	info = kzalloc(sizeof(struct oxu_info), GFP_KERNEL);
+	if (!info) {
+		dev_dbg(&pdev->dev, "error allocating memory\n");
+		ret = -EFAULT;
+		goto error_dma_declare_memory;
+	}
+	platform_set_drvdata(pdev, info);
+
+	ret = oxu210hp_init(pdev, res->start, resource_size(res), base, irq);
+	if (ret < 0) {
+		dev_dbg(&pdev->dev, "cannot init USB devices\n");
+		goto error_init;
+	}
+
+	dev_info(&pdev->dev, "devices enabled and running\n");
+	platform_set_drvdata(pdev, info);
+
+	return 0;
+
+error_init:
+	kfree(info);
+	platform_set_drvdata(pdev, NULL);
+
+error_dma_declare_memory:
+	dma_release_declared_memory(&pdev->dev);
+
+error_alloc:
+	iounmap(base);
+
+error_set_irq_type:
+error_ioremap:
+	release_mem_region(res->start, resource_size(res));
+
+	dev_err(&pdev->dev, "init %s fail, %d\n", dev_name(&pdev->dev), ret);
+	return ret;
+}
+
+static void oxu210hp_remove(struct platform_device *pdev, struct usb_hcd *hcd)
+{
+	usb_remove_hcd(hcd);
+	usb_put_hcd(hcd);
+}
+
+static int oxu210hp_ehci_remove(struct platform_device *pdev)
+{
+	struct oxu_info *info = platform_get_drvdata(pdev);
+	unsigned long memstart = info->hcd[0]->rsrc_start,
+			memlen = info->hcd[0]->rsrc_len;
+	void *base = info->hcd[0]->regs;
+
+	oxu210hp_remove(pdev, info->hcd[0]);
+	oxu210hp_remove(pdev, info->hcd[1]);
+
+	iounmap(base);
+	release_mem_region(memstart, memlen);
+
+	kfree(info);
+	platform_set_drvdata(pdev, NULL);
+
+	return 0;
+}
+
+MODULE_ALIAS("oxu210hp-ehci");
+
+static struct platform_driver oxu210hp_ehci_driver = {
+	.probe = oxu210hp_ehci_probe,
+	.remove = oxu210hp_ehci_remove,
+	.driver = {
+		.name = "oxu210hp-ehci",
+		.bus = &platform_bus_type
+	},
+};
--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux