I've attacehd the latest version of the usb driver for the
S3C2410/2440 onchip usb controller, including an interface
for the board to tell the usb driver when it detects
an over-current event. I would welcome comments on this
driver, as I would like to get it included soon.
I have a couple of questions regrading the handling of
the over-current handling, which seems to ignore the
PORT_OVER_CURRENT field in the wPortStatus field,
and only check the C_PORT_OVERCURRENT_CHANGE?
The second is that once this change has been detected,
it seems that the port is then re-powered pretty
much immediately, causing the system to keep bouncing
the power, possibly a delay could be added so that
the affected port(s) are not thrashed?
My other problem is that the over current sensor on
our board will only assert an over current whilst
power is on, so it is only possible to asser the
wPortStatus PORT_OVER_CURRENT flag wilst power is
supplied to the port. Since the current port checks
do not check this field, it would seem acceptable
at the moment not to try and work around this
problem.
--
Ben ([EMAIL PROTECTED], http://www.fluff.org/)
'a smiley only costs 4 bytes'
| usb3.diff
|
| Files affected:
| drivers/usb/Kconfig | 2 1 + 1 - 0 !
| drivers/usb/host/ohci-hcd.c | 5 5 + 0 - 0 !
| drivers/usb/host/ohci-s3c2410.c | 600 600 + 0 - 0 !
| 3 files changed, 606 insertions(+), 1 deletion(-)
|
| Ben Dooks, Tue, 19 Oct 2004 15:05:21 +0100
diff -urN -X ../dontdiff linux-2.6.9-rc3/drivers/usb/Kconfig
linux-2.6.9-rc3-work2/drivers/usb/Kconfig
--- linux-2.6.9-rc3/drivers/usb/Kconfig 2004-08-14 06:36:17.000000000 +0100
+++ linux-2.6.9-rc3-work2/drivers/usb/Kconfig 2004-10-18 12:06:43.000000000 +0100
@@ -7,7 +7,7 @@
# ARM SA1111 chips have a non-PCI based "OHCI-compatible" USB host interface.
config USB
tristate "Support for Host-side USB"
- depends on PCI || SA1111 || ARCH_OMAP1510 || ARCH_OMAP1610 || ARCH_LH7A404
+ depends on PCI || SA1111 || ARCH_OMAP1510 || ARCH_OMAP1610 || ARCH_LH7A404 ||
ARCH_S3C2410
---help---
Universal Serial Bus (USB) is a specification for a serial bus
subsystem which offers higher speeds and more features than the
diff -urN -X ../dontdiff linux-2.6.9-rc3/drivers/usb/host/ohci-hcd.c
linux-2.6.9-rc3-work2/drivers/usb/host/ohci-hcd.c
--- linux-2.6.9-rc3/drivers/usb/host/ohci-hcd.c 2004-10-04 22:57:08.000000000 +0100
+++ linux-2.6.9-rc3-work2/drivers/usb/host/ohci-hcd.c 2004-10-18 12:06:43.000000000
+0100
@@ -810,10 +810,15 @@
#include "ohci-lh7a404.c"
#endif
+#ifdef CONFIG_ARCH_S3C2410
+#include "ohci-s3c2410.c"
+#endif
+
#if !(defined(CONFIG_PCI) \
|| defined(CONFIG_SA1111) \
|| defined(CONFIG_ARCH_OMAP) \
|| defined (CONFIG_ARCH_LH7A404) \
+ || defined (CONFIG_ARCH_S3C2410) \
)
#error "missing bus glue for ohci-hcd"
#endif
diff -urN -X ../dontdiff linux-2.6.9-rc3/drivers/usb/host/ohci-s3c2410.c
linux-2.6.9-rc3-work2/drivers/usb/host/ohci-s3c2410.c
--- linux-2.6.9-rc3/drivers/usb/host/ohci-s3c2410.c 1970-01-01 01:00:00.000000000
+0100
+++ linux-2.6.9-rc3-work2/drivers/usb/host/ohci-s3c2410.c 2004-10-19
15:02:28.000000000 +0100
@@ -0,0 +1,600 @@
+/*
+ * OHCI HCD (Host Controller Driver) for USB.
+ *
+ * (C) Copyright 1999 Roman Weissgaerber <[EMAIL PROTECTED]>
+ * (C) Copyright 2000-2002 David Brownell <[EMAIL PROTECTED]>
+ * (C) Copyright 2002 Hewlett-Packard Company
+ *
+ * USB Bus Glue for Samsung S3C2410
+ *
+ * Written by Christopher Hoover <[EMAIL PROTECTED]>
+ * Based on fragments of previous driver by Rusell King et al.
+ *
+ * Modified for S3C2410 from ohci-sa1111.c, ohci-omap.c and ohci-lh7a40.c
+ * by Ben Dooks, <[EMAIL PROTECTED]>
+ * Copyright (C) 2004 Simtec Electronics
+ *
+ * This file is licenced under the GPL.
+*/
+
+/* for over-current information, see usb1.1 specification, section
+ * 7.2.1.2.1, 11.13
+*/
+
+#include <asm/hardware.h>
+#include <asm/mach-types.h>
+#include <asm/hardware/clock.h>
+#include <asm/arch/usb-control.h>
+
+#ifdef CONFIG_S3C2410
+#error "This file is S3C2410 bus glue. CONFIG_S3C2410 must be set"
+#endif
+
+#define valid_port(idx) ((idx) == 1 || (idx) == 2)
+
+static struct resource *mem_res;
+static struct clk *clk;
+
+/* conversion functions */
+
+struct s3c2410_hcd_info *
+to_s3c2410_info(struct usb_hcd *hcd)
+{
+ return (struct s3c2410_hcd_info *)hcd->self.controller->platform_data;
+}
+
+static void s3c2410_start_hc(struct platform_device *dev)
+{
+ dev_dbg(&dev->dev, "s3c2410_start_hc:\n");
+ clk_enable(clk);
+}
+
+static void s3c2410_stop_hc(struct platform_device *dev)
+{
+ dev_dbg(&dev->dev, "s3c2410_stop_hc:\n");
+ clk_disable(clk);
+}
+
+/* ohci_s3c2410_hub_status_data
+ *
+ * update the status data from the hub with anything that
+ * has been detected by our system
+*/
+
+static int
+ohci_s3c2410_hub_status_data (struct usb_hcd *hcd, char *buf)
+{
+ struct s3c2410_hcd_info *info = to_s3c2410_info(hcd);
+ struct s3c2410_hcd_port *port;
+ int orig;
+ int portno;
+
+ orig = ohci_hub_status_data (hcd, buf);
+
+ if (info == NULL)
+ return orig;
+
+ port = &info->port[0];
+
+ /* mark any changed port as changed */
+
+ for (portno = 0; portno < 2; port++, portno++) {
+ if (port->oc_changed == 1 &&
+ port->flags & S3C_HCDFLG_USED) {
+ dev_dbg(hcd->self.controller,
+ "oc change on port %d\n", portno);
+
+ if (orig < 1)
+ orig = 1;
+
+ buf[0] |= 1<<(portno+1);
+ }
+ }
+
+ return orig;
+}
+
+/* s3c2410_usb_set_power
+ *
+ * configure the power on a port, by calling the platform device
+ * routine registered with the platfrom device
+*/
+
+static void s3c2410_usb_set_power(struct s3c2410_hcd_info *info,
+ int port, int to)
+{
+ if (info == NULL)
+ return;
+
+ if (info->power_control != NULL) {
+ info->port[port-1].power = to;
+ (info->power_control)(port, to);
+ }
+}
+
+/* ohci_s3c2410_hub_control
+ *
+ * look at control requests to the hub, and see if we need
+ * to take any action or over-ride the results from the
+ * request.
+*/
+
+static int ohci_s3c2410_hub_control (
+ struct usb_hcd *hcd,
+ u16 typeReq,
+ u16 wValue,
+ u16 wIndex,
+ char *buf,
+ u16 wLength)
+{
+ struct s3c2410_hcd_info *info = to_s3c2410_info(hcd);
+ int ret;
+
+ dev_dbg(hcd->self.controller,
+ "s3c2410_hub_control(%p,0x%04x,0x%04x,0x%04x,%p,%04x)\n",
+ hcd, typeReq, wValue, wIndex, buf, wLength);
+
+ if (info != NULL) {
+ if (typeReq == SetPortFeature &&
+ wValue == USB_PORT_FEAT_POWER) {
+ dev_dbg(hcd->self.controller, "SetPortFeat: POWER\n");
+ s3c2410_usb_set_power(info, wIndex, 1);
+ return 0;
+ }
+
+ if (typeReq == ClearPortFeature) {
+ switch (wValue) {
+ case USB_PORT_FEAT_C_OVER_CURRENT:
+ dev_dbg(hcd->self.controller,
+ "ClearPortFeature: C_OVER_CURRENT\n");
+
+ if (valid_port(wIndex)) {
+ info->port[wIndex-1].oc_changed = 0;
+ info->port[wIndex-1].oc_status = 0;
+ }
+ return 0;
+
+ case USB_PORT_FEAT_POWER:
+ dev_dbg(hcd->self.controller,
+ "ClearPortFeature: POWER\n");
+
+ if (valid_port(wIndex)) {
+ s3c2410_usb_set_power(info, wIndex, 0);
+ return 0;
+ }
+ }
+ }
+ }
+
+ ret = ohci_hub_control(hcd, typeReq, wValue, wIndex, buf, wLength);
+
+ if (info == NULL)
+ return ret;
+
+ if (typeReq == GetHubDescriptor) {
+ struct usb_hub_descriptor *desc;
+
+ /* update the hub's descriptor */
+
+ desc = (struct usb_hub_descriptor *)buf;
+
+ if (info->power_control == NULL)
+ return ret;
+
+ dev_dbg(hcd->self.controller, "wHubCharacteristics 0x%04x\n",
+ desc->wHubCharacteristics);
+
+ /* remove the old configurations for power-switching, and
+ * over-current protection, and insert our new configuration
+ */
+
+ desc->wHubCharacteristics &= ~cpu_to_le16(HUB_CHAR_LPSM);
+ desc->wHubCharacteristics |= cpu_to_le16(0x0001);
+
+ if (info->enable_oc) {
+ desc->wHubCharacteristics &= ~cpu_to_le16(HUB_CHAR_OCPM);
+ desc->wHubCharacteristics |= cpu_to_le16(0x0008|0x0001);
+ }
+
+ dev_dbg(hcd->self.controller, "wHubCharacteristics after 0x%04x\n",
+ desc->wHubCharacteristics);
+
+ return ret;
+ }
+
+ if (typeReq == GetPortStatus) {
+ u32 *data = (u32 *)buf;
+ u32 before = *data;
+ /* check port status */
+
+ dev_dbg(hcd->self.controller, "GetPortStatus(%d)\n", wIndex);
+
+ if (valid_port(wIndex)) {
+ dev_dbg(hcd->self.controller,
+ "GetPortStatus(%d) oc=%d, changed=%d\n",
+ wIndex, info->port[wIndex-1].oc_status,
+ info->port[wIndex-1].oc_changed);
+
+ if (info->port[wIndex-1].oc_changed) {
+ *data |= cpu_to_le32(RH_PS_OCIC);
+ }
+
+ if (info->port[wIndex-1].oc_status) {
+ *data |= cpu_to_le32(RH_PS_POCI);
+ }
+
+ dev_dbg(hcd->self.controller, "GetPortStatus(%d) 0x%x =>
0x%x\n",
+ wIndex, before, *data);
+ }
+ }
+
+ return ret;
+}
+
+
+static irqreturn_t s3c2410_hc_irq(int irq, void *param, struct pt_regs * r)
+{
+ struct usb_hcd *hcd = (struct usb_hcd *)param;
+
+ usb_hcd_irq(irq, hcd, r);
+ return IRQ_HANDLED;
+}
+
+/* s3c2410_hcd_oc
+ *
+ * handle an over-current report
+*/
+
+static void s3c2410_hcd_oc(struct s3c2410_hcd_info *info, int port_oc)
+{
+ struct s3c2410_hcd_port *port;
+ struct usb_hcd *hcd;
+ unsigned long flags;
+ int portno;
+
+ if (info == NULL)
+ return;
+
+ port = &info->port[0];
+ hcd = info->hcd;
+
+ local_irq_save(flags);
+
+ for (portno = 0; portno < 2; port++, portno++) {
+ if (!(port->flags & S3C_HCDFLG_USED))
+ continue;
+
+ if (port_oc & (1<<portno)) {
+ port->oc_status = 1;
+
+ /* ok, once over-current is detected,
+ the port needs to be powered down */
+ s3c2410_usb_set_power(info, portno+1, 0);
+ } else {
+ port->oc_status = 0;
+ }
+
+ port->oc_changed = 1;
+
+ }
+
+ local_irq_restore(flags);
+}
+
+/* may be called without controller electrically present */
+/* may be called with controller, bus, and devices active */
+
+/*
+ * usb_hcd_s3c2410_remove - shutdown processing for HCD
+ * @dev: USB Host Controller being removed
+ * Context: !in_interrupt()
+ *
+ * Reverses the effect of usb_hcd_3c2410_probe(), first invoking
+ * the HCD's stop() method. It is always called from a thread
+ * context, normally "rmmod", "apmd", or something similar.
+ *
+*/
+
+void usb_hcd_s3c2410_remove (struct usb_hcd *hcd, struct platform_device *dev)
+{
+ void *base;
+
+ dev_info(&dev->dev, "remove: %s, state %x",
+ hcd->self.bus_name, hcd->state);
+
+ if (in_interrupt ())
+ BUG ();
+
+ hcd->state = USB_STATE_QUIESCING;
+ usb_disconnect (&hcd->self.root_hub);
+
+ hcd->driver->stop (hcd);
+ hcd->state = USB_STATE_HALT;
+
+ free_irq (hcd->irq, hcd);
+ hcd_buffer_destroy (hcd);
+
+ usb_deregister_bus (&hcd->self);
+
+ base = hcd->regs;
+ hcd->driver->hcd_free (hcd);
+
+ s3c2410_stop_hc(dev);
+
+ if (mem_res) {
+ release_resource(mem_res);
+ kfree(mem_res);
+ }
+}
+
+/**
+ * usb_hcd_s3c2410_probe - initialize S3C2410-based HCDs
+ * Context: !in_interrupt()
+ *
+ * Allocates basic resources for this USB host controller, and
+ * then invokes the start() method for the HCD associated with it
+ * through the hotplug entry's driver_data.
+ *
+ */
+int usb_hcd_s3c2410_probe (const struct hc_driver *driver,
+ struct usb_hcd **hcd_out,
+ struct platform_device *dev)
+{
+ struct s3c2410_hcd_info *info;
+ int retval;
+ struct usb_hcd *hcd = 0;
+ unsigned int *addr = NULL;
+ struct resource *res;
+
+ res = platform_get_resource(dev, IORESOURCE_MEM, 0);
+ if (res == NULL) {
+ dev_err(&dev->dev, "no memory region for controller\n");
+ return -ENXIO;
+ }
+
+ mem_res = request_mem_region(res->start, (res->end - res->start)+1,
+ hcd_name);
+
+ if (mem_res == NULL) {
+ dev_err(&dev->dev, "request_mem_region failed");
+ return -EBUSY;
+ }
+
+ clk = clk_get(NULL, "usb-host");
+ clk_use(clk);
+
+ s3c2410_start_hc(dev);
+
+ info = (struct s3c2410_hcd_info *)dev->dev.platform_data;
+
+ addr = ioremap(res->start, (res->end - res->start)+1);
+ if (!addr) {
+ dev_err(&dev->dev, "ioremap failed\n");
+ retval = -ENOMEM;
+ goto err1;
+ }
+
+ hcd = driver->hcd_alloc ();
+ if (hcd == NULL){
+ dev_err(&dev->dev, "hcd_alloc() failed\n");
+ retval = -ENOMEM;
+ goto err1;
+ }
+
+ hcd->irq = platform_get_irq(dev, 0);
+ if (hcd->irq <= 0) {
+ dev_err(&dev->dev, "no IRQ specified\n");
+ retval = -ENOMEM;
+ goto err1;
+ }
+
+ hcd->driver = (struct hc_driver *) driver;
+ hcd->description = driver->description;
+ hcd->irq = dev->resource[1].start;
+ hcd->regs = addr;
+ hcd->self.controller = &dev->dev;
+
+ retval = hcd_buffer_create (hcd);
+ if (retval != 0) {
+ dev_err(&dev->dev, "hcd_buffer_create() failed\n");
+ goto err1;
+ }
+
+ retval = request_irq (hcd->irq, s3c2410_hc_irq, SA_INTERRUPT,
+ hcd->description, hcd);
+ if (retval != 0) {
+ dev_err(&dev->dev, "cannot request irq %d\n", hcd->irq);
+ retval = -EBUSY;
+ goto err2;
+ }
+
+ dev_info (&dev->dev, "%s (S3C2410) at 0x%p, irq %d\n",
+ hcd->description, hcd->regs, hcd->irq);
+
+ usb_bus_init (&hcd->self);
+ hcd->self.op = &usb_hcd_operations;
+ hcd->self.hcpriv = (void *) hcd;
+ hcd->self.bus_name = "s3c2410";
+ hcd->product_desc = "S3C2410 OHCI";
+
+ INIT_LIST_HEAD (&hcd->dev_list);
+
+ usb_register_bus (&hcd->self);
+
+ if (info != NULL) {
+ info->hcd= hcd;
+ info->report_oc = s3c2410_hcd_oc;
+
+ if (info->enable_oc != NULL) {
+ (info->enable_oc)(info, 1);
+ }
+ }
+
+ if ((retval = driver->start (hcd)) < 0)
+ {
+ usb_hcd_s3c2410_remove(hcd, dev);
+ return retval;
+ }
+
+ *hcd_out = hcd;
+ return 0;
+
+ err2:
+ hcd_buffer_destroy (hcd);
+ if (hcd)
+ driver->hcd_free(hcd);
+ err1:
+ if (info != NULL) {
+ info->report_oc = NULL;
+ info->hcd = NULL;
+
+ if (info->enable_oc != NULL) {
+ (info->enable_oc)(info, 0);
+ }
+ }
+
+ s3c2410_stop_hc(dev);
+ if (mem_res) {
+ release_resource(mem_res);
+ kfree(mem_res);
+ }
+
+ return retval;
+}
+
+/*-------------------------------------------------------------------------*/
+
+static int
+ohci_s3c2410_start (struct usb_hcd *hcd)
+{
+ struct s3c2410_hcd_info *info;
+ struct ohci_hcd *ohci = hcd_to_ohci (hcd);
+ int ret;
+
+ info = to_s3c2410_info(hcd);
+
+ ohci->hcca = dma_alloc_coherent (hcd->self.controller,
+ sizeof *ohci->hcca, &ohci->hcca_dma,
+ 0);
+ if (!ohci->hcca)
+ return -ENOMEM;
+
+ memset (ohci->hcca, 0, sizeof (struct ohci_hcca));
+ if ((ret = ohci_mem_init (ohci)) < 0) {
+ ohci_stop (hcd);
+ return ret;
+ }
+ ohci->regs = hcd->regs;
+
+ if (hc_reset (ohci) < 0) {
+ ohci_stop (hcd);
+ return -ENODEV;
+ }
+
+ if (hc_start (ohci) < 0) {
+ err ("can't start %s", ohci->hcd.self.bus_name);
+ ohci_stop (hcd);
+ return -EBUSY;
+ }
+
+ create_debug_files (ohci);
+
+#ifdef DEBUG
+ ohci_dump (ohci, 1);
+#endif
+ return 0;
+}
+
+
+static const struct hc_driver ohci_s3c2410_hc_driver = {
+ .description = hcd_name,
+
+ /*
+ * generic hardware linkage
+ */
+ .irq = ohci_irq,
+ .flags = HCD_USB11,
+
+ /*
+ * basic lifecycle operations
+ */
+ .start = ohci_s3c2410_start,
+ .stop = ohci_stop,
+
+ /*
+ * memory lifecycle (except per-request)
+ */
+ .hcd_alloc = ohci_hcd_alloc,
+ .hcd_free = ohci_hcd_free,
+
+ /*
+ * managing i/o requests and associated device resources
+ */
+ .urb_enqueue = ohci_urb_enqueue,
+ .urb_dequeue = ohci_urb_dequeue,
+ .endpoint_disable = ohci_endpoint_disable,
+
+ /*
+ * scheduling support
+ */
+ .get_frame_number = ohci_get_frame,
+
+ /*
+ * root hub support
+ */
+ .hub_status_data = ohci_s3c2410_hub_status_data,
+ .hub_control = ohci_s3c2410_hub_control,
+#if defined(CONFIG_USB_SUSPEND) && 0
+ .hub_suspend = ohci_hub_suspend,
+ .hub_resume = ohci_hub_resume,
+#endif
+ .start_port_reset = ohci_start_port_reset,
+};
+
+/* device driver */
+
+static int ohci_hcd_s3c2410_drv_probe(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct usb_hcd *hcd = NULL;
+ int ret;
+
+ ret = usb_hcd_s3c2410_probe(&ohci_s3c2410_hc_driver, &hcd, pdev);
+ if (ret == 0)
+ dev_set_drvdata(dev, hcd);
+
+ return ret;
+}
+
+
+static int ohci_hcd_s3c2410_drv_remove(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct usb_hcd *hcd = dev_get_drvdata(dev);
+
+ usb_hcd_s3c2410_remove(hcd, pdev);
+ dev_set_drvdata(dev, NULL);
+ return 0;
+}
+
+static struct device_driver ohci_hcd_s3c2410_driver = {
+ .name = "s3c2410-ohci",
+ .bus = &platform_bus_type,
+ .probe = ohci_hcd_s3c2410_drv_probe,
+ .remove = ohci_hcd_s3c2410_drv_remove,
+ /*.suspend = ohci_hcd_s3c2410_drv_suspend, */
+ /*.resume = ohci_hcd_s3c2410_drv_resume, */
+};
+
+static int __init ohci_hcd_s3c2410_init (void)
+{
+ return driver_register(&ohci_hcd_s3c2410_driver);
+}
+
+static void __exit ohci_hcd_s3c2410_cleanup (void)
+{
+ driver_unregister(&ohci_hcd_s3c2410_driver);
+}
+
+module_init (ohci_hcd_s3c2410_init);
+module_exit (ohci_hcd_s3c2410_cleanup);