forked from Minki/linux
1a49e2ac96
This patch (as1564c) converts the EHCI platform drivers to use the central ehci_setup() routine for generic controller initialization rather than each having its own idiosyncratic approach. The major point of difficulty lies in ehci-pci's many vendor- and device-specific workarounds. Some of them have to be applied before calling ehci_setup() and some after, which necessitates a fair amount of code motion. The other platform drivers require much smaller changes. One point not addressed by the patch is whether ports should be powered on or off following initialization. The different drivers appear to handle this pretty much at random. In fact it shouldn't matter, because the hub driver turns on power to all ports when it binds to the root hub. Straightening that out will be left for another day. Signed-off-by: Alan Stern <stern@rowland.harvard.edu> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
248 lines
5.8 KiB
C
248 lines
5.8 KiB
C
/*
|
|
* SAMSUNG S5P USB HOST EHCI Controller
|
|
*
|
|
* Copyright (C) 2011 Samsung Electronics Co.Ltd
|
|
* Author: Jingoo Han <jg1.han@samsung.com>
|
|
* Author: Joonyoung Shim <jy0922.shim@samsung.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation; either version 2 of the License, or (at your
|
|
* option) any later version.
|
|
*
|
|
*/
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/platform_device.h>
|
|
#include <plat/ehci.h>
|
|
#include <plat/usb-phy.h>
|
|
|
|
#define EHCI_INSNREG00(base) (base + 0x90)
|
|
#define EHCI_INSNREG00_ENA_INCR16 (0x1 << 25)
|
|
#define EHCI_INSNREG00_ENA_INCR8 (0x1 << 24)
|
|
#define EHCI_INSNREG00_ENA_INCR4 (0x1 << 23)
|
|
#define EHCI_INSNREG00_ENA_INCRX_ALIGN (0x1 << 22)
|
|
#define EHCI_INSNREG00_ENABLE_DMA_BURST \
|
|
(EHCI_INSNREG00_ENA_INCR16 | EHCI_INSNREG00_ENA_INCR8 | \
|
|
EHCI_INSNREG00_ENA_INCR4 | EHCI_INSNREG00_ENA_INCRX_ALIGN)
|
|
|
|
struct s5p_ehci_hcd {
|
|
struct device *dev;
|
|
struct usb_hcd *hcd;
|
|
struct clk *clk;
|
|
};
|
|
|
|
static const struct hc_driver s5p_ehci_hc_driver = {
|
|
.description = hcd_name,
|
|
.product_desc = "S5P EHCI Host Controller",
|
|
.hcd_priv_size = sizeof(struct ehci_hcd),
|
|
|
|
.irq = ehci_irq,
|
|
.flags = HCD_MEMORY | HCD_USB2,
|
|
|
|
.reset = ehci_setup,
|
|
.start = ehci_run,
|
|
.stop = ehci_stop,
|
|
.shutdown = ehci_shutdown,
|
|
|
|
.get_frame_number = ehci_get_frame,
|
|
|
|
.urb_enqueue = ehci_urb_enqueue,
|
|
.urb_dequeue = ehci_urb_dequeue,
|
|
.endpoint_disable = ehci_endpoint_disable,
|
|
.endpoint_reset = ehci_endpoint_reset,
|
|
|
|
.hub_status_data = ehci_hub_status_data,
|
|
.hub_control = ehci_hub_control,
|
|
.bus_suspend = ehci_bus_suspend,
|
|
.bus_resume = ehci_bus_resume,
|
|
|
|
.relinquish_port = ehci_relinquish_port,
|
|
.port_handed_over = ehci_port_handed_over,
|
|
|
|
.clear_tt_buffer_complete = ehci_clear_tt_buffer_complete,
|
|
};
|
|
|
|
static int __devinit s5p_ehci_probe(struct platform_device *pdev)
|
|
{
|
|
struct s5p_ehci_platdata *pdata;
|
|
struct s5p_ehci_hcd *s5p_ehci;
|
|
struct usb_hcd *hcd;
|
|
struct ehci_hcd *ehci;
|
|
struct resource *res;
|
|
int irq;
|
|
int err;
|
|
|
|
pdata = pdev->dev.platform_data;
|
|
if (!pdata) {
|
|
dev_err(&pdev->dev, "No platform data defined\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
s5p_ehci = devm_kzalloc(&pdev->dev, sizeof(struct s5p_ehci_hcd),
|
|
GFP_KERNEL);
|
|
if (!s5p_ehci)
|
|
return -ENOMEM;
|
|
|
|
s5p_ehci->dev = &pdev->dev;
|
|
|
|
hcd = usb_create_hcd(&s5p_ehci_hc_driver, &pdev->dev,
|
|
dev_name(&pdev->dev));
|
|
if (!hcd) {
|
|
dev_err(&pdev->dev, "Unable to create HCD\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
s5p_ehci->hcd = hcd;
|
|
s5p_ehci->clk = clk_get(&pdev->dev, "usbhost");
|
|
|
|
if (IS_ERR(s5p_ehci->clk)) {
|
|
dev_err(&pdev->dev, "Failed to get usbhost clock\n");
|
|
err = PTR_ERR(s5p_ehci->clk);
|
|
goto fail_clk;
|
|
}
|
|
|
|
err = clk_enable(s5p_ehci->clk);
|
|
if (err)
|
|
goto fail_clken;
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
if (!res) {
|
|
dev_err(&pdev->dev, "Failed to get I/O memory\n");
|
|
err = -ENXIO;
|
|
goto fail_io;
|
|
}
|
|
|
|
hcd->rsrc_start = res->start;
|
|
hcd->rsrc_len = resource_size(res);
|
|
hcd->regs = devm_ioremap(&pdev->dev, res->start, hcd->rsrc_len);
|
|
if (!hcd->regs) {
|
|
dev_err(&pdev->dev, "Failed to remap I/O memory\n");
|
|
err = -ENOMEM;
|
|
goto fail_io;
|
|
}
|
|
|
|
irq = platform_get_irq(pdev, 0);
|
|
if (!irq) {
|
|
dev_err(&pdev->dev, "Failed to get IRQ\n");
|
|
err = -ENODEV;
|
|
goto fail_io;
|
|
}
|
|
|
|
if (pdata->phy_init)
|
|
pdata->phy_init(pdev, S5P_USB_PHY_HOST);
|
|
|
|
ehci = hcd_to_ehci(hcd);
|
|
ehci->caps = hcd->regs;
|
|
|
|
/* DMA burst Enable */
|
|
writel(EHCI_INSNREG00_ENABLE_DMA_BURST, EHCI_INSNREG00(hcd->regs));
|
|
|
|
err = usb_add_hcd(hcd, irq, IRQF_SHARED);
|
|
if (err) {
|
|
dev_err(&pdev->dev, "Failed to add USB HCD\n");
|
|
goto fail_io;
|
|
}
|
|
|
|
platform_set_drvdata(pdev, s5p_ehci);
|
|
|
|
return 0;
|
|
|
|
fail_io:
|
|
clk_disable(s5p_ehci->clk);
|
|
fail_clken:
|
|
clk_put(s5p_ehci->clk);
|
|
fail_clk:
|
|
usb_put_hcd(hcd);
|
|
return err;
|
|
}
|
|
|
|
static int __devexit s5p_ehci_remove(struct platform_device *pdev)
|
|
{
|
|
struct s5p_ehci_platdata *pdata = pdev->dev.platform_data;
|
|
struct s5p_ehci_hcd *s5p_ehci = platform_get_drvdata(pdev);
|
|
struct usb_hcd *hcd = s5p_ehci->hcd;
|
|
|
|
usb_remove_hcd(hcd);
|
|
|
|
if (pdata && pdata->phy_exit)
|
|
pdata->phy_exit(pdev, S5P_USB_PHY_HOST);
|
|
|
|
clk_disable(s5p_ehci->clk);
|
|
clk_put(s5p_ehci->clk);
|
|
|
|
usb_put_hcd(hcd);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void s5p_ehci_shutdown(struct platform_device *pdev)
|
|
{
|
|
struct s5p_ehci_hcd *s5p_ehci = platform_get_drvdata(pdev);
|
|
struct usb_hcd *hcd = s5p_ehci->hcd;
|
|
|
|
if (hcd->driver->shutdown)
|
|
hcd->driver->shutdown(hcd);
|
|
}
|
|
|
|
#ifdef CONFIG_PM
|
|
static int s5p_ehci_suspend(struct device *dev)
|
|
{
|
|
struct s5p_ehci_hcd *s5p_ehci = dev_get_drvdata(dev);
|
|
struct usb_hcd *hcd = s5p_ehci->hcd;
|
|
bool do_wakeup = device_may_wakeup(dev);
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
struct s5p_ehci_platdata *pdata = pdev->dev.platform_data;
|
|
int rc;
|
|
|
|
rc = ehci_suspend(hcd, do_wakeup);
|
|
|
|
if (pdata && pdata->phy_exit)
|
|
pdata->phy_exit(pdev, S5P_USB_PHY_HOST);
|
|
|
|
clk_disable(s5p_ehci->clk);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int s5p_ehci_resume(struct device *dev)
|
|
{
|
|
struct s5p_ehci_hcd *s5p_ehci = dev_get_drvdata(dev);
|
|
struct usb_hcd *hcd = s5p_ehci->hcd;
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
struct s5p_ehci_platdata *pdata = pdev->dev.platform_data;
|
|
|
|
clk_enable(s5p_ehci->clk);
|
|
|
|
if (pdata && pdata->phy_init)
|
|
pdata->phy_init(pdev, S5P_USB_PHY_HOST);
|
|
|
|
/* DMA burst Enable */
|
|
writel(EHCI_INSNREG00_ENABLE_DMA_BURST, EHCI_INSNREG00(hcd->regs));
|
|
|
|
ehci_resume(hcd, false);
|
|
return 0;
|
|
}
|
|
#else
|
|
#define s5p_ehci_suspend NULL
|
|
#define s5p_ehci_resume NULL
|
|
#endif
|
|
|
|
static const struct dev_pm_ops s5p_ehci_pm_ops = {
|
|
.suspend = s5p_ehci_suspend,
|
|
.resume = s5p_ehci_resume,
|
|
};
|
|
|
|
static struct platform_driver s5p_ehci_driver = {
|
|
.probe = s5p_ehci_probe,
|
|
.remove = __devexit_p(s5p_ehci_remove),
|
|
.shutdown = s5p_ehci_shutdown,
|
|
.driver = {
|
|
.name = "s5p-ehci",
|
|
.owner = THIS_MODULE,
|
|
.pm = &s5p_ehci_pm_ops,
|
|
}
|
|
};
|
|
|
|
MODULE_ALIAS("platform:s5p-ehci");
|