diff options
-rw-r--r-- | drivers/usb/core/hub.c | 10 | ||||
-rw-r--r-- | drivers/usb/core/usb.h | 1 | ||||
-rw-r--r-- | drivers/usb/host/ehci-hcd.c | 19 | ||||
-rw-r--r-- | drivers/usb/host/ehci-hub.c | 13 | ||||
-rw-r--r-- | drivers/usb/host/ehci-langwell-pci.c | 195 | ||||
-rw-r--r-- | drivers/usb/host/ehci-pci.c | 15 | ||||
-rw-r--r-- | drivers/usb/host/ehci.h | 6 | ||||
-rw-r--r-- | include/linux/usb/hcd.h | 6 |
8 files changed, 262 insertions, 3 deletions
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 27115b45edc..4e14c00c6f6 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -1549,6 +1549,14 @@ static void hub_free_dev(struct usb_device *udev) hcd->driver->free_dev(hcd, udev); } +static void otg_notify(struct usb_device *udev, unsigned action) +{ + struct usb_hcd *hcd = bus_to_hcd(udev->bus); + + if (hcd->otg_notify) + hcd->otg_notify(udev, action); +} + /** * usb_disconnect - disconnect a device (usbcore-internal) * @pdev: pointer to device being disconnected @@ -1606,6 +1614,7 @@ void usb_disconnect(struct usb_device **pdev) * notifier chain (used by usbfs and possibly others). */ device_del(&udev->dev); + otg_notify(udev, USB_DEVICE_REMOVE); /* Free the device number and delete the parent's children[] * (or root_hub) pointer. @@ -1829,6 +1838,7 @@ int usb_new_device(struct usb_device *udev) dev_err(&udev->dev, "can't device_add, error %d\n", err); goto fail; } + otg_notify(udev, USB_DEVICE_ADD); (void) usb_create_ep_devs(&udev->dev, &udev->ep0, udev); return err; diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h index cd882203ad3..12276501064 100644 --- a/drivers/usb/core/usb.h +++ b/drivers/usb/core/usb.h @@ -147,4 +147,3 @@ extern void usb_notify_add_device(struct usb_device *udev); extern void usb_notify_remove_device(struct usb_device *udev); extern void usb_notify_add_bus(struct usb_bus *ubus); extern void usb_notify_remove_bus(struct usb_bus *ubus); - diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index e9062806d4a..c59361e6e91 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -43,6 +43,8 @@ #include <asm/irq.h> #include <asm/system.h> #include <asm/unaligned.h> +#include <linux/usb/otg.h> +#include <linux/usb/langwell_otg.h> /*-------------------------------------------------------------------------*/ @@ -1154,6 +1156,10 @@ MODULE_LICENSE ("GPL"); #ifdef CONFIG_PCI #include "ehci-pci.c" #define PCI_DRIVER ehci_pci_driver +#ifdef CONFIG_USB_LANGWELL_OTG +#include "ehci-langwell-pci.c" +#define LNW_OTG_HOST_DRIVER ehci_otg_driver +#endif #endif #ifdef CONFIG_USB_EHCI_FSL @@ -1278,8 +1284,18 @@ static int __init ehci_hcd_init(void) if (retval < 0) goto clean4; #endif + +#ifdef LNW_OTG_HOST_DRIVER + retval = langwell_register_host(&LNW_OTG_HOST_DRIVER); + if (retval < 0) + goto clean5; +#endif return retval; +#ifdef LNW_OTG_HOST_DRIVER +clean5: + langwell_unregister_host(&LNW_OTG_HOST_DRIVER); +#endif #ifdef XILINX_OF_PLATFORM_DRIVER /* of_unregister_platform_driver(&XILINX_OF_PLATFORM_DRIVER); */ clean4: @@ -1312,6 +1328,9 @@ module_init(ehci_hcd_init); static void __exit ehci_hcd_cleanup(void) { +#ifdef LNW_OTG_HOST_DRIVER + langwell_unregister_host(&LNW_OTG_HOST_DRIVER); +#endif #ifdef XILINX_OF_PLATFORM_DRIVER of_unregister_platform_driver(&XILINX_OF_PLATFORM_DRIVER); #endif diff --git a/drivers/usb/host/ehci-hub.c b/drivers/usb/host/ehci-hub.c index 796ea0c8900..86f081548fa 100644 --- a/drivers/usb/host/ehci-hub.c +++ b/drivers/usb/host/ehci-hub.c @@ -178,6 +178,7 @@ static int ehci_bus_suspend (struct usb_hcd *hcd) int port; int mask; int changed; + int rc = 0; ehci_dbg(ehci, "suspend root hub\n"); @@ -297,13 +298,17 @@ static int ehci_bus_suspend (struct usb_hcd *hcd) ehci_readl(ehci, &ehci->regs->intr_enable); ehci->next_statechange = jiffies + msecs_to_jiffies(10); + + if (ehci->has_otg && ehci->otg_suspend) + rc = ehci->otg_suspend(hcd); + spin_unlock_irq (&ehci->lock); /* ehci_work() may have re-enabled the watchdog timer, which we do not * want, and so we must delete any pending watchdog timer events. */ del_timer_sync(&ehci->watchdog); - return 0; + return rc; } @@ -315,6 +320,7 @@ static int ehci_bus_resume (struct usb_hcd *hcd) u32 power_okay; int i; u8 resume_needed = 0; + int rc = 0; if (time_before (jiffies, ehci->next_statechange)) msleep(5); @@ -428,9 +434,12 @@ static int ehci_bus_resume (struct usb_hcd *hcd) /* Now we can safely re-enable irqs */ ehci_writel(ehci, INTR_MASK, &ehci->regs->intr_enable); + if (ehci->has_otg && ehci->otg_resume) + rc = ehci->otg_resume(hcd); + spin_unlock_irq (&ehci->lock); ehci_handover_companion_ports(ehci); - return 0; + return rc; } #else diff --git a/drivers/usb/host/ehci-langwell-pci.c b/drivers/usb/host/ehci-langwell-pci.c new file mode 100644 index 00000000000..64ffd6020e3 --- /dev/null +++ b/drivers/usb/host/ehci-langwell-pci.c @@ -0,0 +1,195 @@ +/* + * Intel Moorestown Platform Langwell OTG EHCI Controller PCI Bus Glue. + * + * Copyright (c) 2008 - 2009, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License 2 as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +static int usb_otg_suspend(struct usb_hcd *hcd) +{ + struct otg_transceiver *otg; + struct langwell_otg *iotg; + + otg = otg_get_transceiver(); + if (otg == NULL) { + printk(KERN_ERR "%s Failed to get otg transceiver\n", __func__); + return -EINVAL; + } + iotg = container_of(otg, struct langwell_otg, otg); + printk(KERN_INFO "%s OTG HNP update suspend\n", __func__); + if (iotg->otg.default_a) + iotg->hsm.a_suspend_req = 1; + else + iotg->hsm.b_bus_req = 0; + langwell_update_transceiver(); + otg_put_transceiver(otg); + return 0; +} + +static int usb_otg_resume(struct usb_hcd *hcd) +{ + struct otg_transceiver *otg; + struct langwell_otg *iotg; + + otg = otg_get_transceiver(); + if (otg == NULL) { + printk(KERN_ERR "%s Failed to get otg transceiver\n", __func__); + return -EINVAL; + } + iotg = container_of(otg, struct langwell_otg, otg); + printk(KERN_INFO "%s OTG HNP update resume\n", __func__); + if (iotg->otg.default_a) { + iotg->hsm.b_bus_resume = 1; + langwell_update_transceiver(); + } + otg_put_transceiver(otg); + return 0; +} + +/* the root hub will call this callback when device added/removed */ +static void otg_notify(struct usb_device *udev, unsigned action) +{ + struct otg_transceiver *otg; + struct langwell_otg *iotg; + + otg = otg_get_transceiver(); + if (otg == NULL) { + printk(KERN_ERR "%s Failed to get otg transceiver\n", __func__); + return; + } + iotg = container_of(otg, struct langwell_otg, otg); + + switch (action) { + case USB_DEVICE_ADD: + pr_debug("Notify OTG HNP add device\n"); + if (iotg->otg.default_a == 1) + iotg->hsm.b_conn = 1; + else + iotg->hsm.a_conn = 1; + break; + case USB_DEVICE_REMOVE: + pr_debug("Notify OTG HNP delete device\n"); + if (iotg->otg.default_a == 1) + iotg->hsm.b_conn = 0; + else + iotg->hsm.a_conn = 0; + break; + default: + otg_put_transceiver(otg); + return ; + } + if (spin_trylock(&iotg->wq_lock)) { + langwell_update_transceiver(); + spin_unlock(&iotg->wq_lock); + } + otg_put_transceiver(otg); + return; +} + +static int ehci_langwell_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + struct hc_driver *driver; + struct langwell_otg *iotg; + struct otg_transceiver *otg; + struct usb_hcd *hcd; + struct ehci_hcd *ehci; + int irq; + int retval; + + pr_debug("initializing Langwell USB OTG Host Controller\n"); + + /* we need not call pci_enable_dev since otg transceiver already take + * the control of this device and this probe actaully gets called by + * otg transceiver driver with HNP protocol. + */ + irq = pdev->irq; + + if (!id) + return -EINVAL; + driver = (struct hc_driver *)id->driver_data; + if (!driver) + return -EINVAL; + + hcd = usb_create_hcd(driver, &pdev->dev, dev_name(&pdev->dev)); + if (!hcd) { + retval = -ENOMEM; + goto err1; + } + + hcd->self.otg_port = 1; + ehci = hcd_to_ehci(hcd); + /* this will be called in ehci_bus_suspend and ehci_bus_resume */ + ehci->otg_suspend = usb_otg_suspend; + ehci->otg_resume = usb_otg_resume; + /* this will be called by root hub code */ + hcd->otg_notify = otg_notify; + otg = otg_get_transceiver(); + if (otg == NULL) { + printk(KERN_ERR "%s Failed to get otg transceiver\n", __func__); + retval = -EINVAL; + goto err1; + } + iotg = container_of(otg, struct langwell_otg, otg); + hcd->regs = iotg->regs; + hcd->rsrc_start = pci_resource_start(pdev, 0); + hcd->rsrc_len = pci_resource_len(pdev, 0); + + if (hcd->regs == NULL) { + dev_dbg(&pdev->dev, "error mapping memory\n"); + retval = -EFAULT; + goto err2; + } + retval = usb_add_hcd(hcd, irq, IRQF_DISABLED | IRQF_SHARED); + if (retval != 0) + goto err2; + retval = otg_set_host(otg, &hcd->self); + if (!otg->default_a) + hcd->self.is_b_host = 1; + otg_put_transceiver(otg); + return retval; + +err2: + usb_put_hcd(hcd); +err1: + dev_err(&pdev->dev, "init %s fail, %d\n", dev_name(&pdev->dev), retval); + return retval; +} + +void ehci_langwell_remove(struct pci_dev *dev) +{ + struct usb_hcd *hcd = pci_get_drvdata(dev); + + if (!hcd) + return; + usb_remove_hcd(hcd); + usb_put_hcd(hcd); +} + +/* Langwell OTG EHCI driver */ +static struct pci_driver ehci_otg_driver = { + .name = "ehci-langwell", + .id_table = pci_ids, + + .probe = ehci_langwell_probe, + .remove = ehci_langwell_remove, + +#ifdef CONFIG_PM_SLEEP + .driver = { + .pm = &usb_hcd_pci_pm_ops + }, +#endif + .shutdown = usb_hcd_pci_shutdown, +}; diff --git a/drivers/usb/host/ehci-pci.c b/drivers/usb/host/ehci-pci.c index 655f3c9f88b..c3637bb299c 100644 --- a/drivers/usb/host/ehci-pci.c +++ b/drivers/usb/host/ehci-pci.c @@ -50,6 +50,7 @@ static int ehci_pci_setup(struct usb_hcd *hcd) u8 rev; u32 temp; int retval; + int force_otg_hc_mode = 0; switch (pdev->vendor) { case PCI_VENDOR_ID_TOSHIBA_2: @@ -63,6 +64,18 @@ static int ehci_pci_setup(struct usb_hcd *hcd) #endif } break; + case PCI_VENDOR_ID_INTEL: + if (pdev->device == 0x0811) { + ehci_info(ehci, "Detected Langwell OTG HC\n"); + hcd->has_tt = 1; + ehci->has_hostpc = 1; + ehci->has_otg = 1; + force_otg_hc_mode = 1; + } else if (pdev->device == 0x0806) { + ehci_info(ehci, "Detected Langwell MPH\n"); + hcd->has_tt = 1; + ehci->has_hostpc = 1; + } } ehci->caps = hcd->regs; @@ -98,6 +111,8 @@ static int ehci_pci_setup(struct usb_hcd *hcd) /* cache this readonly data; minimize chip reads */ ehci->hcs_params = ehci_readl(ehci, &ehci->caps->hcs_params); + if (force_otg_hc_mode) + ehci_reset(ehci); retval = ehci_halt(ehci); if (retval) diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index ba8eab366b8..d78a781a49b 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -145,6 +145,8 @@ struct ehci_hcd { /* one per controller */ unsigned has_hostpc:1; unsigned has_lpm:1; /* support link power management */ unsigned has_ppcd:1; /* support per-port change bits */ + + unsigned has_otg:1; /* if it is otg host*/ u8 sbrn; /* packed release number */ /* irq statistics */ @@ -155,6 +157,10 @@ struct ehci_hcd { /* one per controller */ # define COUNT(x) do {} while (0) #endif + /* otg host has additional bus_suspend and bus_resume */ + int (*otg_suspend)(struct usb_hcd *hcd); + int (*otg_resume)(struct usb_hcd *hcd); + /* debug files */ #ifdef DEBUG struct dentry *debug_dir; diff --git a/include/linux/usb/hcd.h b/include/linux/usb/hcd.h index 0b6e751ea0b..57098cd8476 100644 --- a/include/linux/usb/hcd.h +++ b/include/linux/usb/hcd.h @@ -163,6 +163,12 @@ struct usb_hcd { * (ohci 32, uhci 1024, ehci 256/512/1024). */ + /* some otg HCDs need this to get USB_DEVICE_ADD and USB_DEVICE_REMOVE + * from root hub, we do not want to use USB notification chain, since + * it would be a over kill to use high level notification. + */ + void (*otg_notify) (struct usb_device *udev, unsigned action); + /* The HC driver's private data is stored at the end of * this structure. */ |