2012-03-13 22:57:41 +08:00
|
|
|
/*
|
|
|
|
* xhci-plat.c - xHCI host controller driver platform Bus Glue.
|
|
|
|
*
|
|
|
|
* Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com
|
|
|
|
* Author: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
|
|
|
|
*
|
|
|
|
* A lot of code borrowed from the Linux xHCI driver.
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU General Public License
|
|
|
|
* version 2 as published by the Free Software Foundation.
|
|
|
|
*/
|
|
|
|
|
2014-05-15 18:17:32 +08:00
|
|
|
#include <linux/clk.h>
|
2014-05-15 18:17:31 +08:00
|
|
|
#include <linux/dma-mapping.h>
|
2012-03-13 22:57:41 +08:00
|
|
|
#include <linux/module.h>
|
2013-07-26 07:04:44 +08:00
|
|
|
#include <linux/of.h>
|
2014-05-15 18:17:31 +08:00
|
|
|
#include <linux/platform_device.h>
|
|
|
|
#include <linux/slab.h>
|
2012-03-13 22:57:41 +08:00
|
|
|
|
|
|
|
#include "xhci.h"
|
|
|
|
|
|
|
|
static void xhci_plat_quirks(struct device *dev, struct xhci_hcd *xhci)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* As of now platform drivers don't provide MSI support so we ensure
|
|
|
|
* here that the generic code does not try to make a pci_dev from our
|
|
|
|
* dev struct in order to setup MSI
|
|
|
|
*/
|
xhci-plat: Don't enable legacy PCI interrupts.
The xHCI platform driver calls into usb_add_hcd to register the irq for
its platform device. It does not want the xHCI generic driver to
register an interrupt for it at all. The original code did that by
setting the XHCI_BROKEN_MSI quirk, which tells the xHCI driver to not
enable MSI or MSI-X for a PCI host.
Unfortunately, if CONFIG_PCI is enabled, and CONFIG_USB_DW3 is enabled,
the xHCI generic driver will attempt to register a legacy PCI interrupt
for the xHCI platform device in xhci_try_enable_msi(). This will result
in a bogus irq being registered, since the underlying device is a
platform_device, not a pci_device, and thus the pci_device->irq pointer
will be bogus.
Add a new quirk, XHCI_PLAT, so that the xHCI generic driver can
distinguish between a PCI device that can't handle MSI or MSI-X, and a
platform device that should not have its interrupts touched at all.
This quirk may be useful in the future, in case other corner cases like
this arise.
This patch should be backported to kernels as old as 3.9, that
contain the commit 00eed9c814cb8f281be6f0f5d8f45025dc0a97eb "USB: xhci:
correctly enable interrupts".
Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com>
Reported-by: Yu Y Wang <yu.y.wang@intel.com>
Tested-by: Yu Y Wang <yu.y.wang@intel.com>
Reviewed-by: Felipe Balbi <balbi@ti.com>
Cc: stable@vger.kernel.org
2013-08-09 01:08:34 +08:00
|
|
|
xhci->quirks |= XHCI_PLAT;
|
2012-03-13 22:57:41 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/* called during probe() after chip reset completes */
|
|
|
|
static int xhci_plat_setup(struct usb_hcd *hcd)
|
|
|
|
{
|
|
|
|
return xhci_gen_setup(hcd, xhci_plat_quirks);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct hc_driver xhci_plat_xhci_driver = {
|
|
|
|
.description = "xhci-hcd",
|
|
|
|
.product_desc = "xHCI Host Controller",
|
|
|
|
.hcd_priv_size = sizeof(struct xhci_hcd *),
|
|
|
|
|
|
|
|
/*
|
|
|
|
* generic hardware linkage
|
|
|
|
*/
|
|
|
|
.irq = xhci_irq,
|
|
|
|
.flags = HCD_MEMORY | HCD_USB3 | HCD_SHARED,
|
|
|
|
|
|
|
|
/*
|
|
|
|
* basic lifecycle operations
|
|
|
|
*/
|
|
|
|
.reset = xhci_plat_setup,
|
|
|
|
.start = xhci_run,
|
|
|
|
.stop = xhci_stop,
|
|
|
|
.shutdown = xhci_shutdown,
|
|
|
|
|
|
|
|
/*
|
|
|
|
* managing i/o requests and associated device resources
|
|
|
|
*/
|
|
|
|
.urb_enqueue = xhci_urb_enqueue,
|
|
|
|
.urb_dequeue = xhci_urb_dequeue,
|
|
|
|
.alloc_dev = xhci_alloc_dev,
|
|
|
|
.free_dev = xhci_free_dev,
|
|
|
|
.alloc_streams = xhci_alloc_streams,
|
|
|
|
.free_streams = xhci_free_streams,
|
|
|
|
.add_endpoint = xhci_add_endpoint,
|
|
|
|
.drop_endpoint = xhci_drop_endpoint,
|
|
|
|
.endpoint_reset = xhci_endpoint_reset,
|
|
|
|
.check_bandwidth = xhci_check_bandwidth,
|
|
|
|
.reset_bandwidth = xhci_reset_bandwidth,
|
|
|
|
.address_device = xhci_address_device,
|
2013-12-06 09:07:27 +08:00
|
|
|
.enable_device = xhci_enable_device,
|
2012-03-13 22:57:41 +08:00
|
|
|
.update_hub_device = xhci_update_hub_device,
|
|
|
|
.reset_device = xhci_discover_or_reset_device,
|
|
|
|
|
|
|
|
/*
|
|
|
|
* scheduling support
|
|
|
|
*/
|
|
|
|
.get_frame_number = xhci_get_frame,
|
|
|
|
|
|
|
|
/* Root hub support */
|
|
|
|
.hub_control = xhci_hub_control,
|
|
|
|
.hub_status_data = xhci_hub_status_data,
|
|
|
|
.bus_suspend = xhci_bus_suspend,
|
|
|
|
.bus_resume = xhci_bus_resume,
|
|
|
|
};
|
|
|
|
|
|
|
|
static int xhci_plat_probe(struct platform_device *pdev)
|
|
|
|
{
|
|
|
|
const struct hc_driver *driver;
|
|
|
|
struct xhci_hcd *xhci;
|
|
|
|
struct resource *res;
|
|
|
|
struct usb_hcd *hcd;
|
2014-05-15 18:17:32 +08:00
|
|
|
struct clk *clk;
|
2012-03-13 22:57:41 +08:00
|
|
|
int ret;
|
|
|
|
int irq;
|
|
|
|
|
|
|
|
if (usb_disabled())
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
driver = &xhci_plat_xhci_driver;
|
|
|
|
|
|
|
|
irq = platform_get_irq(pdev, 0);
|
|
|
|
if (irq < 0)
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
|
|
if (!res)
|
|
|
|
return -ENODEV;
|
|
|
|
|
xhci: fix dma mask setup in xhci.c
The function dma_set_mask() tests internally whether the dma_mask pointer
for the device is initialized and fails if the dma_mask pointer is NULL.
On pci platforms, the device dma_mask pointer is initialized, when pci
devices are enumerated, to point to the pci_dev->dma_mask which is 0xffffffff.
However, for non-pci platforms, the dma_mask pointer may not be initialized
and in that case dma_set_mask() will fail.
This patch initializes the dma_mask and the coherent_dma_mask to 32bits
in xhci_plat_probe(), before the call to usb_create_hcd() that sets the
"uses_dma" flag for the usb bus and the call to usb_add_hcd() that creates
coherent dma pools for the usb hcd.
Moreover, a call to dma_set_mask() does not set the device coherent_dma_mask.
Since the xhci-hcd driver calls dma_alloc_coherent() and dma_pool_alloc()
to allocate consistent DMA memory blocks, the coherent DMA address mask
has to be set explicitly.
This patch sets the coherent_dma_mask to 64bits in xhci_gen_setup() when
the xHC is capable for 64-bit DMA addressing.
If dma_set_mask() succeeds, for a given bitmask, it is guaranteed that
the given bitmask is also supported for consistent DMA mappings.
Other changes introduced in this patch are:
- The return value of dma_set_mask() is checked to ensure that the required
dma bitmask conforms with the host system's addressing capabilities.
- The dma_mask setup code for the non-primary hcd was removed since both
primary and non-primary hcd refer to the same generic device whose
dma_mask and coherent_dma_mask are already set during the setup of
the primary hcd.
- The code for reading the HCCPARAMS register to find out the addressing
capabilities of xHC was removed since its value is already cached in
xhci->hccparams.
- hcd->self.controller was replaced with the dev variable since it is
already available.
Signed-off-by: Xenia Ragiadakou <burzalodowa@gmail.com>
Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com>
2013-08-14 10:55:19 +08:00
|
|
|
/* Initialize dma_mask and coherent_dma_mask to 32-bits */
|
|
|
|
ret = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32));
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
if (!pdev->dev.dma_mask)
|
|
|
|
pdev->dev.dma_mask = &pdev->dev.coherent_dma_mask;
|
|
|
|
else
|
|
|
|
dma_set_mask(&pdev->dev, DMA_BIT_MASK(32));
|
|
|
|
|
2012-03-13 22:57:41 +08:00
|
|
|
hcd = usb_create_hcd(driver, &pdev->dev, dev_name(&pdev->dev));
|
|
|
|
if (!hcd)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
hcd->rsrc_start = res->start;
|
|
|
|
hcd->rsrc_len = resource_size(res);
|
|
|
|
|
|
|
|
if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len,
|
|
|
|
driver->description)) {
|
|
|
|
dev_dbg(&pdev->dev, "controller already in use\n");
|
|
|
|
ret = -EBUSY;
|
|
|
|
goto put_hcd;
|
|
|
|
}
|
|
|
|
|
2012-08-10 14:58:30 +08:00
|
|
|
hcd->regs = ioremap_nocache(hcd->rsrc_start, hcd->rsrc_len);
|
2012-03-13 22:57:41 +08:00
|
|
|
if (!hcd->regs) {
|
|
|
|
dev_dbg(&pdev->dev, "error mapping memory\n");
|
|
|
|
ret = -EFAULT;
|
|
|
|
goto release_mem_region;
|
|
|
|
}
|
|
|
|
|
2014-05-15 18:17:32 +08:00
|
|
|
/*
|
|
|
|
* Not all platforms have a clk so it is not an error if the
|
|
|
|
* clock does not exists.
|
|
|
|
*/
|
|
|
|
clk = devm_clk_get(&pdev->dev, NULL);
|
|
|
|
if (!IS_ERR(clk)) {
|
|
|
|
ret = clk_prepare_enable(clk);
|
|
|
|
if (ret)
|
|
|
|
goto unmap_registers;
|
|
|
|
}
|
|
|
|
|
2012-03-13 22:57:41 +08:00
|
|
|
ret = usb_add_hcd(hcd, irq, IRQF_SHARED);
|
|
|
|
if (ret)
|
2014-05-15 18:17:32 +08:00
|
|
|
goto disable_clk;
|
|
|
|
|
2013-11-05 10:46:02 +08:00
|
|
|
device_wakeup_enable(hcd->self.controller);
|
2012-03-13 22:57:41 +08:00
|
|
|
|
|
|
|
/* USB 2.0 roothub is stored in the platform_device now. */
|
2013-05-23 18:18:39 +08:00
|
|
|
hcd = platform_get_drvdata(pdev);
|
2012-03-13 22:57:41 +08:00
|
|
|
xhci = hcd_to_xhci(hcd);
|
2014-05-15 18:17:32 +08:00
|
|
|
xhci->clk = clk;
|
2012-03-13 22:57:41 +08:00
|
|
|
xhci->shared_hcd = usb_create_shared_hcd(driver, &pdev->dev,
|
|
|
|
dev_name(&pdev->dev), hcd);
|
|
|
|
if (!xhci->shared_hcd) {
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto dealloc_usb2_hcd;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Set the xHCI pointer before xhci_plat_setup() (aka hcd_driver.reset)
|
|
|
|
* is called by usb_add_hcd().
|
|
|
|
*/
|
|
|
|
*((struct xhci_hcd **) xhci->shared_hcd->hcd_priv) = xhci;
|
|
|
|
|
2014-02-12 03:36:04 +08:00
|
|
|
if (HCC_MAX_PSA(xhci->hcc_params) >= 4)
|
|
|
|
xhci->shared_hcd->can_do_streams = 1;
|
|
|
|
|
2012-03-13 22:57:41 +08:00
|
|
|
ret = usb_add_hcd(xhci->shared_hcd, irq, IRQF_SHARED);
|
|
|
|
if (ret)
|
|
|
|
goto put_usb3_hcd;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
put_usb3_hcd:
|
|
|
|
usb_put_hcd(xhci->shared_hcd);
|
|
|
|
|
|
|
|
dealloc_usb2_hcd:
|
|
|
|
usb_remove_hcd(hcd);
|
|
|
|
|
2014-05-15 18:17:32 +08:00
|
|
|
disable_clk:
|
|
|
|
if (!IS_ERR(clk))
|
|
|
|
clk_disable_unprepare(clk);
|
|
|
|
|
2012-03-13 22:57:41 +08:00
|
|
|
unmap_registers:
|
|
|
|
iounmap(hcd->regs);
|
|
|
|
|
|
|
|
release_mem_region:
|
|
|
|
release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
|
|
|
|
|
|
|
|
put_hcd:
|
|
|
|
usb_put_hcd(hcd);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int xhci_plat_remove(struct platform_device *dev)
|
|
|
|
{
|
|
|
|
struct usb_hcd *hcd = platform_get_drvdata(dev);
|
|
|
|
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
|
2014-05-15 18:17:32 +08:00
|
|
|
struct clk *clk = xhci->clk;
|
2012-03-13 22:57:41 +08:00
|
|
|
|
|
|
|
usb_remove_hcd(xhci->shared_hcd);
|
|
|
|
usb_put_hcd(xhci->shared_hcd);
|
|
|
|
|
|
|
|
usb_remove_hcd(hcd);
|
2014-05-15 18:17:32 +08:00
|
|
|
if (!IS_ERR(clk))
|
|
|
|
clk_disable_unprepare(clk);
|
2012-03-13 22:57:41 +08:00
|
|
|
iounmap(hcd->regs);
|
2013-06-21 16:29:08 +08:00
|
|
|
release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
|
2012-03-13 22:57:41 +08:00
|
|
|
usb_put_hcd(hcd);
|
|
|
|
kfree(xhci);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-02-11 18:58:00 +08:00
|
|
|
#ifdef CONFIG_PM
|
|
|
|
static int xhci_plat_suspend(struct device *dev)
|
|
|
|
{
|
|
|
|
struct usb_hcd *hcd = dev_get_drvdata(dev);
|
|
|
|
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
|
|
|
|
|
|
|
|
return xhci_suspend(xhci);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int xhci_plat_resume(struct device *dev)
|
|
|
|
{
|
|
|
|
struct usb_hcd *hcd = dev_get_drvdata(dev);
|
|
|
|
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
|
|
|
|
|
|
|
|
return xhci_resume(xhci, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct dev_pm_ops xhci_plat_pm_ops = {
|
|
|
|
SET_SYSTEM_SLEEP_PM_OPS(xhci_plat_suspend, xhci_plat_resume)
|
|
|
|
};
|
|
|
|
#define DEV_PM_OPS (&xhci_plat_pm_ops)
|
|
|
|
#else
|
|
|
|
#define DEV_PM_OPS NULL
|
|
|
|
#endif /* CONFIG_PM */
|
|
|
|
|
2013-07-26 07:04:44 +08:00
|
|
|
#ifdef CONFIG_OF
|
|
|
|
static const struct of_device_id usb_xhci_of_match[] = {
|
2014-02-12 00:54:46 +08:00
|
|
|
{ .compatible = "generic-xhci" },
|
2013-07-26 07:04:44 +08:00
|
|
|
{ .compatible = "xhci-platform" },
|
|
|
|
{ },
|
|
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(of, usb_xhci_of_match);
|
|
|
|
#endif
|
|
|
|
|
2012-03-13 22:57:41 +08:00
|
|
|
static struct platform_driver usb_xhci_driver = {
|
|
|
|
.probe = xhci_plat_probe,
|
|
|
|
.remove = xhci_plat_remove,
|
|
|
|
.driver = {
|
|
|
|
.name = "xhci-hcd",
|
2013-02-11 18:58:00 +08:00
|
|
|
.pm = DEV_PM_OPS,
|
2013-07-26 07:04:44 +08:00
|
|
|
.of_match_table = of_match_ptr(usb_xhci_of_match),
|
2012-03-13 22:57:41 +08:00
|
|
|
},
|
|
|
|
};
|
|
|
|
MODULE_ALIAS("platform:xhci-hcd");
|
|
|
|
|
|
|
|
int xhci_register_plat(void)
|
|
|
|
{
|
|
|
|
return platform_driver_register(&usb_xhci_driver);
|
|
|
|
}
|
|
|
|
|
|
|
|
void xhci_unregister_plat(void)
|
|
|
|
{
|
|
|
|
platform_driver_unregister(&usb_xhci_driver);
|
|
|
|
}
|