1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Generic UHCI HCD (Host Controller Driver) for Platform Devices |
4 | * |
5 | * Copyright (c) 2011 Tony Prisk <linux@prisktech.co.nz> |
6 | * |
7 | * This file is based on uhci-grlib.c |
8 | * (C) Copyright 2004-2007 Alan Stern, stern@rowland.harvard.edu |
9 | */ |
10 | |
11 | #include <linux/of.h> |
12 | #include <linux/device.h> |
13 | #include <linux/platform_device.h> |
14 | |
15 | static int uhci_platform_init(struct usb_hcd *hcd) |
16 | { |
17 | struct uhci_hcd *uhci = hcd_to_uhci(hcd); |
18 | |
19 | /* Probe number of ports if not already provided by DT */ |
20 | if (!uhci->rh_numports) |
21 | uhci->rh_numports = uhci_count_ports(hcd); |
22 | |
23 | /* Set up pointers to to generic functions */ |
24 | uhci->reset_hc = uhci_generic_reset_hc; |
25 | uhci->check_and_reset_hc = uhci_generic_check_and_reset_hc; |
26 | |
27 | /* No special actions need to be taken for the functions below */ |
28 | uhci->configure_hc = NULL; |
29 | uhci->resume_detect_interrupts_are_broken = NULL; |
30 | uhci->global_suspend_mode_is_broken = NULL; |
31 | |
32 | /* Reset if the controller isn't already safely quiescent. */ |
33 | check_and_reset_hc(uhci); |
34 | return 0; |
35 | } |
36 | |
37 | static const struct hc_driver uhci_platform_hc_driver = { |
38 | .description = hcd_name, |
39 | .product_desc = "Generic UHCI Host Controller" , |
40 | .hcd_priv_size = sizeof(struct uhci_hcd), |
41 | |
42 | /* Generic hardware linkage */ |
43 | .irq = uhci_irq, |
44 | .flags = HCD_MEMORY | HCD_DMA | HCD_USB11, |
45 | |
46 | /* Basic lifecycle operations */ |
47 | .reset = uhci_platform_init, |
48 | .start = uhci_start, |
49 | #ifdef CONFIG_PM |
50 | .pci_suspend = NULL, |
51 | .pci_resume = NULL, |
52 | .bus_suspend = uhci_rh_suspend, |
53 | .bus_resume = uhci_rh_resume, |
54 | #endif |
55 | .stop = uhci_stop, |
56 | |
57 | .urb_enqueue = uhci_urb_enqueue, |
58 | .urb_dequeue = uhci_urb_dequeue, |
59 | |
60 | .endpoint_disable = uhci_hcd_endpoint_disable, |
61 | .get_frame_number = uhci_hcd_get_frame_number, |
62 | |
63 | .hub_status_data = uhci_hub_status_data, |
64 | .hub_control = uhci_hub_control, |
65 | }; |
66 | |
67 | static int uhci_hcd_platform_probe(struct platform_device *pdev) |
68 | { |
69 | struct device_node *np = pdev->dev.of_node; |
70 | struct usb_hcd *hcd; |
71 | struct uhci_hcd *uhci; |
72 | struct resource *res; |
73 | int ret; |
74 | |
75 | if (usb_disabled()) |
76 | return -ENODEV; |
77 | |
78 | /* |
79 | * Right now device-tree probed devices don't get dma_mask set. |
80 | * Since shared usb code relies on it, set it here for now. |
81 | * Once we have dma capability bindings this can go away. |
82 | */ |
83 | ret = dma_coerce_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); |
84 | if (ret) |
85 | return ret; |
86 | |
87 | hcd = usb_create_hcd(&uhci_platform_hc_driver, &pdev->dev, |
88 | pdev->name); |
89 | if (!hcd) |
90 | return -ENOMEM; |
91 | |
92 | uhci = hcd_to_uhci(hcd); |
93 | |
94 | hcd->regs = devm_platform_get_and_ioremap_resource(pdev, index: 0, res: &res); |
95 | if (IS_ERR(ptr: hcd->regs)) { |
96 | ret = PTR_ERR(ptr: hcd->regs); |
97 | goto err_rmr; |
98 | } |
99 | hcd->rsrc_start = res->start; |
100 | hcd->rsrc_len = resource_size(res); |
101 | |
102 | uhci->regs = hcd->regs; |
103 | |
104 | /* Grab some things from the device-tree */ |
105 | if (np) { |
106 | u32 num_ports; |
107 | |
108 | if (of_property_read_u32(np, propname: "#ports" , out_value: &num_ports) == 0) { |
109 | uhci->rh_numports = num_ports; |
110 | dev_info(&pdev->dev, |
111 | "Detected %d ports from device-tree\n" , |
112 | num_ports); |
113 | } |
114 | if (of_device_is_compatible(device: np, "aspeed,ast2400-uhci" ) || |
115 | of_device_is_compatible(device: np, "aspeed,ast2500-uhci" ) || |
116 | of_device_is_compatible(device: np, "aspeed,ast2600-uhci" )) { |
117 | uhci->is_aspeed = 1; |
118 | dev_info(&pdev->dev, |
119 | "Enabled Aspeed implementation workarounds\n" ); |
120 | } |
121 | } |
122 | |
123 | /* Get and enable clock if any specified */ |
124 | uhci->clk = devm_clk_get(&pdev->dev, NULL); |
125 | if (IS_ERR(ptr: uhci->clk)) { |
126 | ret = PTR_ERR(ptr: uhci->clk); |
127 | goto err_rmr; |
128 | } |
129 | ret = clk_prepare_enable(uhci->clk); |
130 | if (ret) { |
131 | dev_err(&pdev->dev, "Error couldn't enable clock (%d)\n" , ret); |
132 | goto err_rmr; |
133 | } |
134 | |
135 | ret = platform_get_irq(pdev, 0); |
136 | if (ret < 0) |
137 | goto err_clk; |
138 | |
139 | ret = usb_add_hcd(hcd, ret, IRQF_SHARED); |
140 | if (ret) |
141 | goto err_clk; |
142 | |
143 | device_wakeup_enable(dev: hcd->self.controller); |
144 | return 0; |
145 | |
146 | err_clk: |
147 | clk_disable_unprepare(uhci->clk); |
148 | err_rmr: |
149 | usb_put_hcd(hcd); |
150 | |
151 | return ret; |
152 | } |
153 | |
154 | static void uhci_hcd_platform_remove(struct platform_device *pdev) |
155 | { |
156 | struct usb_hcd *hcd = platform_get_drvdata(pdev); |
157 | struct uhci_hcd *uhci = hcd_to_uhci(hcd); |
158 | |
159 | clk_disable_unprepare(uhci->clk); |
160 | usb_remove_hcd(hcd); |
161 | usb_put_hcd(hcd); |
162 | } |
163 | |
164 | /* Make sure the controller is quiescent and that we're not using it |
165 | * any more. This is mainly for the benefit of programs which, like kexec, |
166 | * expect the hardware to be idle: not doing DMA or generating IRQs. |
167 | * |
168 | * This routine may be called in a damaged or failing kernel. Hence we |
169 | * do not acquire the spinlock before shutting down the controller. |
170 | */ |
171 | static void uhci_hcd_platform_shutdown(struct platform_device *op) |
172 | { |
173 | struct usb_hcd *hcd = platform_get_drvdata(pdev: op); |
174 | |
175 | uhci_hc_died(hcd_to_uhci(hcd)); |
176 | } |
177 | |
178 | static const struct of_device_id platform_uhci_ids[] = { |
179 | { .compatible = "generic-uhci" , }, |
180 | { .compatible = "platform-uhci" , }, |
181 | {} |
182 | }; |
183 | MODULE_DEVICE_TABLE(of, platform_uhci_ids); |
184 | |
185 | static struct platform_driver uhci_platform_driver = { |
186 | .probe = uhci_hcd_platform_probe, |
187 | .remove_new = uhci_hcd_platform_remove, |
188 | .shutdown = uhci_hcd_platform_shutdown, |
189 | .driver = { |
190 | .name = "platform-uhci" , |
191 | .of_match_table = platform_uhci_ids, |
192 | }, |
193 | }; |
194 | |