diff options
Diffstat (limited to 'drivers/usb/host/uhci-platform.c')
-rw-r--r-- | drivers/usb/host/uhci-platform.c | 196 |
1 files changed, 196 insertions, 0 deletions
diff --git a/drivers/usb/host/uhci-platform.c b/drivers/usb/host/uhci-platform.c new file mode 100644 index 000000000..b2049b47a --- /dev/null +++ b/drivers/usb/host/uhci-platform.c @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Generic UHCI HCD (Host Controller Driver) for Platform Devices + * + * Copyright (c) 2011 Tony Prisk <linux@prisktech.co.nz> + * + * This file is based on uhci-grlib.c + * (C) Copyright 2004-2007 Alan Stern, stern@rowland.harvard.edu + */ + +#include <linux/of.h> +#include <linux/device.h> +#include <linux/platform_device.h> + +static int uhci_platform_init(struct usb_hcd *hcd) +{ + struct uhci_hcd *uhci = hcd_to_uhci(hcd); + + /* Probe number of ports if not already provided by DT */ + if (!uhci->rh_numports) + uhci->rh_numports = uhci_count_ports(hcd); + + /* Set up pointers to to generic functions */ + uhci->reset_hc = uhci_generic_reset_hc; + uhci->check_and_reset_hc = uhci_generic_check_and_reset_hc; + + /* No special actions need to be taken for the functions below */ + uhci->configure_hc = NULL; + uhci->resume_detect_interrupts_are_broken = NULL; + uhci->global_suspend_mode_is_broken = NULL; + + /* Reset if the controller isn't already safely quiescent. */ + check_and_reset_hc(uhci); + return 0; +} + +static const struct hc_driver uhci_platform_hc_driver = { + .description = hcd_name, + .product_desc = "Generic UHCI Host Controller", + .hcd_priv_size = sizeof(struct uhci_hcd), + + /* Generic hardware linkage */ + .irq = uhci_irq, + .flags = HCD_MEMORY | HCD_DMA | HCD_USB11, + + /* Basic lifecycle operations */ + .reset = uhci_platform_init, + .start = uhci_start, +#ifdef CONFIG_PM + .pci_suspend = NULL, + .pci_resume = NULL, + .bus_suspend = uhci_rh_suspend, + .bus_resume = uhci_rh_resume, +#endif + .stop = uhci_stop, + + .urb_enqueue = uhci_urb_enqueue, + .urb_dequeue = uhci_urb_dequeue, + + .endpoint_disable = uhci_hcd_endpoint_disable, + .get_frame_number = uhci_hcd_get_frame_number, + + .hub_status_data = uhci_hub_status_data, + .hub_control = uhci_hub_control, +}; + +static int uhci_hcd_platform_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct usb_hcd *hcd; + struct uhci_hcd *uhci; + struct resource *res; + int ret; + + if (usb_disabled()) + return -ENODEV; + + /* + * Right now device-tree probed devices don't get dma_mask set. + * Since shared usb code relies on it, set it here for now. + * Once we have dma capability bindings this can go away. + */ + ret = dma_coerce_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); + if (ret) + return ret; + + hcd = usb_create_hcd(&uhci_platform_hc_driver, &pdev->dev, + pdev->name); + if (!hcd) + return -ENOMEM; + + uhci = hcd_to_uhci(hcd); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + hcd->regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(hcd->regs)) { + ret = PTR_ERR(hcd->regs); + goto err_rmr; + } + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); + + uhci->regs = hcd->regs; + + /* Grab some things from the device-tree */ + if (np) { + u32 num_ports; + + if (of_property_read_u32(np, "#ports", &num_ports) == 0) { + uhci->rh_numports = num_ports; + dev_info(&pdev->dev, + "Detected %d ports from device-tree\n", + num_ports); + } + if (of_device_is_compatible(np, "aspeed,ast2400-uhci") || + of_device_is_compatible(np, "aspeed,ast2500-uhci") || + of_device_is_compatible(np, "aspeed,ast2600-uhci")) { + uhci->is_aspeed = 1; + dev_info(&pdev->dev, + "Enabled Aspeed implementation workarounds\n"); + } + } + + /* Get and enable clock if any specified */ + uhci->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(uhci->clk)) { + ret = PTR_ERR(uhci->clk); + goto err_rmr; + } + ret = clk_prepare_enable(uhci->clk); + if (ret) { + dev_err(&pdev->dev, "Error couldn't enable clock (%d)\n", ret); + goto err_rmr; + } + + ret = platform_get_irq(pdev, 0); + if (ret < 0) + goto err_clk; + + ret = usb_add_hcd(hcd, ret, IRQF_SHARED); + if (ret) + goto err_clk; + + device_wakeup_enable(hcd->self.controller); + return 0; + +err_clk: + clk_disable_unprepare(uhci->clk); +err_rmr: + usb_put_hcd(hcd); + + return ret; +} + +static int uhci_hcd_platform_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct uhci_hcd *uhci = hcd_to_uhci(hcd); + + clk_disable_unprepare(uhci->clk); + usb_remove_hcd(hcd); + usb_put_hcd(hcd); + + return 0; +} + +/* Make sure the controller is quiescent and that we're not using it + * any more. This is mainly for the benefit of programs which, like kexec, + * expect the hardware to be idle: not doing DMA or generating IRQs. + * + * This routine may be called in a damaged or failing kernel. Hence we + * do not acquire the spinlock before shutting down the controller. + */ +static void uhci_hcd_platform_shutdown(struct platform_device *op) +{ + struct usb_hcd *hcd = platform_get_drvdata(op); + + uhci_hc_died(hcd_to_uhci(hcd)); +} + +static const struct of_device_id platform_uhci_ids[] = { + { .compatible = "generic-uhci", }, + { .compatible = "platform-uhci", }, + {} +}; +MODULE_DEVICE_TABLE(of, platform_uhci_ids); + +static struct platform_driver uhci_platform_driver = { + .probe = uhci_hcd_platform_probe, + .remove = uhci_hcd_platform_remove, + .shutdown = uhci_hcd_platform_shutdown, + .driver = { + .name = "platform-uhci", + .of_match_table = platform_uhci_ids, + }, +}; |