forked from Minki/linux
c5cf9212a3
This patch (as1563) removes a lot of duplicated code by moving the EHCI controller suspend/resume routines into the core driver, where the various platform drivers can invoke them as needed. Not only does this simplify these platform drivers, this also makes it easier for other platform drivers to add suspend/resume support in the future. Note: The patch does not touch the ehci-fsl.c file, because its approach to suspend and resume is so different from all the others. It will have to be handled specially by its maintainer. Signed-off-by: Alan Stern <stern@rowland.harvard.edu> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
196 lines
4.6 KiB
C
196 lines
4.6 KiB
C
/*
|
|
* Generic platform ehci driver
|
|
*
|
|
* Copyright 2007 Steven Brown <sbrown@cortland.com>
|
|
* Copyright 2010-2012 Hauke Mehrtens <hauke@hauke-m.de>
|
|
*
|
|
* Derived from the ohci-ssb driver
|
|
* Copyright 2007 Michael Buesch <m@bues.ch>
|
|
*
|
|
* Derived from the EHCI-PCI driver
|
|
* Copyright (c) 2000-2004 by David Brownell
|
|
*
|
|
* Derived from the ohci-pci driver
|
|
* Copyright 1999 Roman Weissgaerber
|
|
* Copyright 2000-2002 David Brownell
|
|
* Copyright 1999 Linus Torvalds
|
|
* Copyright 1999 Gregory P. Smith
|
|
*
|
|
* Licensed under the GNU/GPL. See COPYING for details.
|
|
*/
|
|
#include <linux/platform_device.h>
|
|
#include <linux/usb/ehci_pdriver.h>
|
|
|
|
static int ehci_platform_reset(struct usb_hcd *hcd)
|
|
{
|
|
struct platform_device *pdev = to_platform_device(hcd->self.controller);
|
|
struct usb_ehci_pdata *pdata = pdev->dev.platform_data;
|
|
struct ehci_hcd *ehci = hcd_to_ehci(hcd);
|
|
int retval;
|
|
|
|
hcd->has_tt = pdata->has_tt;
|
|
ehci->has_synopsys_hc_bug = pdata->has_synopsys_hc_bug;
|
|
ehci->big_endian_desc = pdata->big_endian_desc;
|
|
ehci->big_endian_mmio = pdata->big_endian_mmio;
|
|
|
|
ehci->caps = hcd->regs + pdata->caps_offset;
|
|
retval = ehci_setup(hcd);
|
|
if (retval)
|
|
return retval;
|
|
|
|
if (pdata->port_power_on)
|
|
ehci_port_power(ehci, 1);
|
|
if (pdata->port_power_off)
|
|
ehci_port_power(ehci, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct hc_driver ehci_platform_hc_driver = {
|
|
.description = hcd_name,
|
|
.product_desc = "Generic Platform EHCI Controller",
|
|
.hcd_priv_size = sizeof(struct ehci_hcd),
|
|
|
|
.irq = ehci_irq,
|
|
.flags = HCD_MEMORY | HCD_USB2,
|
|
|
|
.reset = ehci_platform_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,
|
|
.endpoint_reset = ehci_endpoint_reset,
|
|
|
|
.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 int __devinit ehci_platform_probe(struct platform_device *dev)
|
|
{
|
|
struct usb_hcd *hcd;
|
|
struct resource *res_mem;
|
|
int irq;
|
|
int err = -ENOMEM;
|
|
|
|
BUG_ON(!dev->dev.platform_data);
|
|
|
|
if (usb_disabled())
|
|
return -ENODEV;
|
|
|
|
irq = platform_get_irq(dev, 0);
|
|
if (irq < 0) {
|
|
pr_err("no irq provided");
|
|
return irq;
|
|
}
|
|
res_mem = platform_get_resource(dev, IORESOURCE_MEM, 0);
|
|
if (!res_mem) {
|
|
pr_err("no memory recourse provided");
|
|
return -ENXIO;
|
|
}
|
|
|
|
hcd = usb_create_hcd(&ehci_platform_hc_driver, &dev->dev,
|
|
dev_name(&dev->dev));
|
|
if (!hcd)
|
|
return -ENOMEM;
|
|
|
|
hcd->rsrc_start = res_mem->start;
|
|
hcd->rsrc_len = resource_size(res_mem);
|
|
|
|
if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, hcd_name)) {
|
|
pr_err("controller already in use");
|
|
err = -EBUSY;
|
|
goto err_put_hcd;
|
|
}
|
|
|
|
hcd->regs = ioremap_nocache(hcd->rsrc_start, hcd->rsrc_len);
|
|
if (!hcd->regs)
|
|
goto err_release_region;
|
|
err = usb_add_hcd(hcd, irq, IRQF_SHARED);
|
|
if (err)
|
|
goto err_iounmap;
|
|
|
|
platform_set_drvdata(dev, hcd);
|
|
|
|
return err;
|
|
|
|
err_iounmap:
|
|
iounmap(hcd->regs);
|
|
err_release_region:
|
|
release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
|
|
err_put_hcd:
|
|
usb_put_hcd(hcd);
|
|
return err;
|
|
}
|
|
|
|
static int __devexit ehci_platform_remove(struct platform_device *dev)
|
|
{
|
|
struct usb_hcd *hcd = platform_get_drvdata(dev);
|
|
|
|
usb_remove_hcd(hcd);
|
|
iounmap(hcd->regs);
|
|
release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
|
|
usb_put_hcd(hcd);
|
|
platform_set_drvdata(dev, NULL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_PM
|
|
|
|
static int ehci_platform_suspend(struct device *dev)
|
|
{
|
|
struct usb_hcd *hcd = dev_get_drvdata(dev);
|
|
bool do_wakeup = device_may_wakeup(dev);
|
|
|
|
return ehci_suspend(hcd, do_wakeup);
|
|
}
|
|
|
|
static int ehci_platform_resume(struct device *dev)
|
|
{
|
|
struct usb_hcd *hcd = dev_get_drvdata(dev);
|
|
|
|
ehci_resume(hcd, false);
|
|
return 0;
|
|
}
|
|
|
|
#else /* !CONFIG_PM */
|
|
#define ehci_platform_suspend NULL
|
|
#define ehci_platform_resume NULL
|
|
#endif /* CONFIG_PM */
|
|
|
|
static const struct platform_device_id ehci_platform_table[] = {
|
|
{ "ehci-platform", 0 },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(platform, ehci_platform_table);
|
|
|
|
static const struct dev_pm_ops ehci_platform_pm_ops = {
|
|
.suspend = ehci_platform_suspend,
|
|
.resume = ehci_platform_resume,
|
|
};
|
|
|
|
static struct platform_driver ehci_platform_driver = {
|
|
.id_table = ehci_platform_table,
|
|
.probe = ehci_platform_probe,
|
|
.remove = __devexit_p(ehci_platform_remove),
|
|
.shutdown = usb_hcd_platform_shutdown,
|
|
.driver = {
|
|
.owner = THIS_MODULE,
|
|
.name = "ehci-platform",
|
|
.pm = &ehci_platform_pm_ops,
|
|
}
|
|
};
|