diff options
Diffstat (limited to 'drivers/usb/dwc3/dwc3-st.c')
-rw-r--r-- | drivers/usb/dwc3/dwc3-st.c | 367 |
1 files changed, 367 insertions, 0 deletions
diff --git a/drivers/usb/dwc3/dwc3-st.c b/drivers/usb/dwc3/dwc3-st.c new file mode 100644 index 000000000000..c7602b5362ad --- /dev/null +++ b/drivers/usb/dwc3/dwc3-st.c | |||
@@ -0,0 +1,367 @@ | |||
1 | /** | ||
2 | * dwc3-st.c Support for dwc3 platform devices on ST Microelectronics platforms | ||
3 | * | ||
4 | * This is a small driver for the dwc3 to provide the glue logic | ||
5 | * to configure the controller. Tested on STi platforms. | ||
6 | * | ||
7 | * Copyright (C) 2014 Stmicroelectronics | ||
8 | * | ||
9 | * Author: Giuseppe Cavallaro <peppe.cavallaro@st.com> | ||
10 | * Contributors: Aymen Bouattay <aymen.bouattay@st.com> | ||
11 | * Peter Griffin <peter.griffin@linaro.org> | ||
12 | * | ||
13 | * This program is free software; you can redistribute it and/or modify | ||
14 | * it under the terms of the GNU General Public License as published by | ||
15 | * the Free Software Foundation; either version 2 of the License, or | ||
16 | * (at your option) any later version. | ||
17 | * | ||
18 | * Inspired by dwc3-omap.c and dwc3-exynos.c. | ||
19 | */ | ||
20 | |||
21 | #include <linux/delay.h> | ||
22 | #include <linux/interrupt.h> | ||
23 | #include <linux/io.h> | ||
24 | #include <linux/ioport.h> | ||
25 | #include <linux/kernel.h> | ||
26 | #include <linux/mfd/syscon.h> | ||
27 | #include <linux/module.h> | ||
28 | #include <linux/of.h> | ||
29 | #include <linux/of_platform.h> | ||
30 | #include <linux/platform_device.h> | ||
31 | #include <linux/slab.h> | ||
32 | #include <linux/regmap.h> | ||
33 | #include <linux/reset.h> | ||
34 | #include <linux/usb/of.h> | ||
35 | |||
36 | #include "core.h" | ||
37 | #include "io.h" | ||
38 | |||
39 | /* glue registers */ | ||
40 | #define CLKRST_CTRL 0x00 | ||
41 | #define AUX_CLK_EN BIT(0) | ||
42 | #define SW_PIPEW_RESET_N BIT(4) | ||
43 | #define EXT_CFG_RESET_N BIT(8) | ||
44 | /* | ||
45 | * 1'b0 : The host controller complies with the xHCI revision 0.96 | ||
46 | * 1'b1 : The host controller complies with the xHCI revision 1.0 | ||
47 | */ | ||
48 | #define XHCI_REVISION BIT(12) | ||
49 | |||
50 | #define USB2_VBUS_MNGMNT_SEL1 0x2C | ||
51 | /* | ||
52 | * For all fields in USB2_VBUS_MNGMNT_SEL1 | ||
53 | * 2’b00 : Override value from Reg 0x30 is selected | ||
54 | * 2’b01 : utmiotg_<signal_name> from usb3_top is selected | ||
55 | * 2’b10 : pipew_<signal_name> from PIPEW instance is selected | ||
56 | * 2’b11 : value is 1'b0 | ||
57 | */ | ||
58 | #define USB2_VBUS_REG30 0x0 | ||
59 | #define USB2_VBUS_UTMIOTG 0x1 | ||
60 | #define USB2_VBUS_PIPEW 0x2 | ||
61 | #define USB2_VBUS_ZERO 0x3 | ||
62 | |||
63 | #define SEL_OVERRIDE_VBUSVALID(n) (n << 0) | ||
64 | #define SEL_OVERRIDE_POWERPRESENT(n) (n << 4) | ||
65 | #define SEL_OVERRIDE_BVALID(n) (n << 8) | ||
66 | |||
67 | /* Static DRD configuration */ | ||
68 | #define USB3_CONTROL_MASK 0xf77 | ||
69 | |||
70 | #define USB3_DEVICE_NOT_HOST BIT(0) | ||
71 | #define USB3_FORCE_VBUSVALID BIT(1) | ||
72 | #define USB3_DELAY_VBUSVALID BIT(2) | ||
73 | #define USB3_SEL_FORCE_OPMODE BIT(4) | ||
74 | #define USB3_FORCE_OPMODE(n) (n << 5) | ||
75 | #define USB3_SEL_FORCE_DPPULLDOWN2 BIT(8) | ||
76 | #define USB3_FORCE_DPPULLDOWN2 BIT(9) | ||
77 | #define USB3_SEL_FORCE_DMPULLDOWN2 BIT(10) | ||
78 | #define USB3_FORCE_DMPULLDOWN2 BIT(11) | ||
79 | |||
80 | /** | ||
81 | * struct st_dwc3 - dwc3-st driver private structure | ||
82 | * @dev: device pointer | ||
83 | * @glue_base: ioaddr for the glue registers | ||
84 | * @regmap: regmap pointer for getting syscfg | ||
85 | * @syscfg_reg_off: usb syscfg control offset | ||
86 | * @dr_mode: drd static host/device config | ||
87 | * @rstc_pwrdn: rest controller for powerdown signal | ||
88 | * @rstc_rst: reset controller for softreset signal | ||
89 | */ | ||
90 | |||
91 | struct st_dwc3 { | ||
92 | struct device *dev; | ||
93 | void __iomem *glue_base; | ||
94 | struct regmap *regmap; | ||
95 | int syscfg_reg_off; | ||
96 | enum usb_dr_mode dr_mode; | ||
97 | struct reset_control *rstc_pwrdn; | ||
98 | struct reset_control *rstc_rst; | ||
99 | }; | ||
100 | |||
101 | static inline u32 st_dwc3_readl(void __iomem *base, u32 offset) | ||
102 | { | ||
103 | return readl_relaxed(base + offset); | ||
104 | } | ||
105 | |||
106 | static inline void st_dwc3_writel(void __iomem *base, u32 offset, u32 value) | ||
107 | { | ||
108 | writel_relaxed(value, base + offset); | ||
109 | } | ||
110 | |||
111 | /** | ||
112 | * st_dwc3_drd_init: program the port | ||
113 | * @dwc3_data: driver private structure | ||
114 | * Description: this function is to program the port as either host or device | ||
115 | * according to the static configuration passed from devicetree. | ||
116 | * OTG and dual role are not yet supported! | ||
117 | */ | ||
118 | static int st_dwc3_drd_init(struct st_dwc3 *dwc3_data) | ||
119 | { | ||
120 | u32 val; | ||
121 | int err; | ||
122 | |||
123 | err = regmap_read(dwc3_data->regmap, dwc3_data->syscfg_reg_off, &val); | ||
124 | if (err) | ||
125 | return err; | ||
126 | |||
127 | val &= USB3_CONTROL_MASK; | ||
128 | |||
129 | switch (dwc3_data->dr_mode) { | ||
130 | case USB_DR_MODE_PERIPHERAL: | ||
131 | |||
132 | val &= ~(USB3_FORCE_VBUSVALID | USB3_DELAY_VBUSVALID | ||
133 | | USB3_SEL_FORCE_OPMODE | USB3_FORCE_OPMODE(0x3) | ||
134 | | USB3_SEL_FORCE_DPPULLDOWN2 | USB3_FORCE_DPPULLDOWN2 | ||
135 | | USB3_SEL_FORCE_DMPULLDOWN2 | USB3_FORCE_DMPULLDOWN2); | ||
136 | |||
137 | val |= USB3_DEVICE_NOT_HOST; | ||
138 | |||
139 | dev_dbg(dwc3_data->dev, "Configuring as Device\n"); | ||
140 | break; | ||
141 | |||
142 | case USB_DR_MODE_HOST: | ||
143 | |||
144 | val &= ~(USB3_DEVICE_NOT_HOST | USB3_FORCE_VBUSVALID | ||
145 | | USB3_SEL_FORCE_OPMODE | USB3_FORCE_OPMODE(0x3) | ||
146 | | USB3_SEL_FORCE_DPPULLDOWN2 | USB3_FORCE_DPPULLDOWN2 | ||
147 | | USB3_SEL_FORCE_DMPULLDOWN2 | USB3_FORCE_DMPULLDOWN2); | ||
148 | |||
149 | /* | ||
150 | * USB3_DELAY_VBUSVALID is ANDed with USB_C_VBUSVALID. Thus, | ||
151 | * when set to ‘0‘, it can delay the arrival of VBUSVALID | ||
152 | * information to VBUSVLDEXT2 input of the pico PHY. | ||
153 | * We don't want to do that so we set the bit to '1'. | ||
154 | */ | ||
155 | |||
156 | val |= USB3_DELAY_VBUSVALID; | ||
157 | |||
158 | dev_dbg(dwc3_data->dev, "Configuring as Host\n"); | ||
159 | break; | ||
160 | |||
161 | default: | ||
162 | dev_err(dwc3_data->dev, "Unsupported mode of operation %d\n", | ||
163 | dwc3_data->dr_mode); | ||
164 | return -EINVAL; | ||
165 | } | ||
166 | |||
167 | return regmap_write(dwc3_data->regmap, dwc3_data->syscfg_reg_off, val); | ||
168 | } | ||
169 | |||
170 | /** | ||
171 | * st_dwc3_init: init the controller via glue logic | ||
172 | * @dwc3_data: driver private structure | ||
173 | */ | ||
174 | static void st_dwc3_init(struct st_dwc3 *dwc3_data) | ||
175 | { | ||
176 | u32 reg = st_dwc3_readl(dwc3_data->glue_base, CLKRST_CTRL); | ||
177 | |||
178 | reg |= AUX_CLK_EN | EXT_CFG_RESET_N | XHCI_REVISION; | ||
179 | reg &= ~SW_PIPEW_RESET_N; | ||
180 | st_dwc3_writel(dwc3_data->glue_base, CLKRST_CTRL, reg); | ||
181 | |||
182 | /* configure mux for vbus, powerpresent and bvalid signals */ | ||
183 | reg = st_dwc3_readl(dwc3_data->glue_base, USB2_VBUS_MNGMNT_SEL1); | ||
184 | |||
185 | reg |= SEL_OVERRIDE_VBUSVALID(USB2_VBUS_UTMIOTG) | | ||
186 | SEL_OVERRIDE_POWERPRESENT(USB2_VBUS_UTMIOTG) | | ||
187 | SEL_OVERRIDE_BVALID(USB2_VBUS_UTMIOTG); | ||
188 | |||
189 | st_dwc3_writel(dwc3_data->glue_base, USB2_VBUS_MNGMNT_SEL1, reg); | ||
190 | |||
191 | reg = st_dwc3_readl(dwc3_data->glue_base, CLKRST_CTRL); | ||
192 | reg |= SW_PIPEW_RESET_N; | ||
193 | st_dwc3_writel(dwc3_data->glue_base, CLKRST_CTRL, reg); | ||
194 | } | ||
195 | |||
196 | static int st_dwc3_probe(struct platform_device *pdev) | ||
197 | { | ||
198 | struct st_dwc3 *dwc3_data; | ||
199 | struct resource *res; | ||
200 | struct device *dev = &pdev->dev; | ||
201 | struct device_node *node = dev->of_node, *child; | ||
202 | struct regmap *regmap; | ||
203 | int ret; | ||
204 | |||
205 | dwc3_data = devm_kzalloc(dev, sizeof(*dwc3_data), GFP_KERNEL); | ||
206 | if (!dwc3_data) | ||
207 | return -ENOMEM; | ||
208 | |||
209 | res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "reg-glue"); | ||
210 | dwc3_data->glue_base = devm_ioremap_resource(dev, res); | ||
211 | if (IS_ERR(dwc3_data->glue_base)) | ||
212 | return PTR_ERR(dwc3_data->glue_base); | ||
213 | |||
214 | regmap = syscon_regmap_lookup_by_phandle(node, "st,syscfg"); | ||
215 | if (IS_ERR(regmap)) | ||
216 | return PTR_ERR(regmap); | ||
217 | |||
218 | dma_set_coherent_mask(dev, dev->coherent_dma_mask); | ||
219 | dwc3_data->dev = dev; | ||
220 | dwc3_data->regmap = regmap; | ||
221 | |||
222 | res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "syscfg-reg"); | ||
223 | if (!res) { | ||
224 | ret = -ENXIO; | ||
225 | goto undo_platform_dev_alloc; | ||
226 | } | ||
227 | |||
228 | dwc3_data->syscfg_reg_off = res->start; | ||
229 | |||
230 | dev_vdbg(&pdev->dev, "glue-logic addr 0x%p, syscfg-reg offset 0x%x\n", | ||
231 | dwc3_data->glue_base, dwc3_data->syscfg_reg_off); | ||
232 | |||
233 | dwc3_data->rstc_pwrdn = devm_reset_control_get(dev, "powerdown"); | ||
234 | if (IS_ERR(dwc3_data->rstc_pwrdn)) { | ||
235 | dev_err(&pdev->dev, "could not get power controller\n"); | ||
236 | ret = PTR_ERR(dwc3_data->rstc_pwrdn); | ||
237 | goto undo_platform_dev_alloc; | ||
238 | } | ||
239 | |||
240 | /* Manage PowerDown */ | ||
241 | reset_control_deassert(dwc3_data->rstc_pwrdn); | ||
242 | |||
243 | dwc3_data->rstc_rst = devm_reset_control_get(dev, "softreset"); | ||
244 | if (IS_ERR(dwc3_data->rstc_rst)) { | ||
245 | dev_err(&pdev->dev, "could not get reset controller\n"); | ||
246 | ret = PTR_ERR(dwc3_data->rstc_pwrdn); | ||
247 | goto undo_powerdown; | ||
248 | } | ||
249 | |||
250 | /* Manage SoftReset */ | ||
251 | reset_control_deassert(dwc3_data->rstc_rst); | ||
252 | |||
253 | child = of_get_child_by_name(node, "dwc3"); | ||
254 | if (!child) { | ||
255 | dev_err(&pdev->dev, "failed to find dwc3 core node\n"); | ||
256 | ret = -ENODEV; | ||
257 | goto undo_softreset; | ||
258 | } | ||
259 | |||
260 | dwc3_data->dr_mode = of_usb_get_dr_mode(child); | ||
261 | |||
262 | /* Allocate and initialize the core */ | ||
263 | ret = of_platform_populate(node, NULL, NULL, dev); | ||
264 | if (ret) { | ||
265 | dev_err(dev, "failed to add dwc3 core\n"); | ||
266 | goto undo_softreset; | ||
267 | } | ||
268 | |||
269 | /* | ||
270 | * Configure the USB port as device or host according to the static | ||
271 | * configuration passed from DT. | ||
272 | * DRD is the only mode currently supported so this will be enhanced | ||
273 | * as soon as OTG is available. | ||
274 | */ | ||
275 | ret = st_dwc3_drd_init(dwc3_data); | ||
276 | if (ret) { | ||
277 | dev_err(dev, "drd initialisation failed\n"); | ||
278 | goto undo_softreset; | ||
279 | } | ||
280 | |||
281 | /* ST glue logic init */ | ||
282 | st_dwc3_init(dwc3_data); | ||
283 | |||
284 | platform_set_drvdata(pdev, dwc3_data); | ||
285 | return 0; | ||
286 | |||
287 | undo_softreset: | ||
288 | reset_control_assert(dwc3_data->rstc_rst); | ||
289 | undo_powerdown: | ||
290 | reset_control_assert(dwc3_data->rstc_pwrdn); | ||
291 | undo_platform_dev_alloc: | ||
292 | platform_device_put(pdev); | ||
293 | return ret; | ||
294 | } | ||
295 | |||
296 | static int st_dwc3_remove(struct platform_device *pdev) | ||
297 | { | ||
298 | struct st_dwc3 *dwc3_data = platform_get_drvdata(pdev); | ||
299 | |||
300 | of_platform_depopulate(&pdev->dev); | ||
301 | |||
302 | reset_control_assert(dwc3_data->rstc_pwrdn); | ||
303 | reset_control_assert(dwc3_data->rstc_rst); | ||
304 | |||
305 | return 0; | ||
306 | } | ||
307 | |||
308 | #ifdef CONFIG_PM_SLEEP | ||
309 | static int st_dwc3_suspend(struct device *dev) | ||
310 | { | ||
311 | struct st_dwc3 *dwc3_data = dev_get_drvdata(dev); | ||
312 | |||
313 | reset_control_assert(dwc3_data->rstc_pwrdn); | ||
314 | reset_control_assert(dwc3_data->rstc_rst); | ||
315 | |||
316 | pinctrl_pm_select_sleep_state(dev); | ||
317 | |||
318 | return 0; | ||
319 | } | ||
320 | |||
321 | static int st_dwc3_resume(struct device *dev) | ||
322 | { | ||
323 | struct st_dwc3 *dwc3_data = dev_get_drvdata(dev); | ||
324 | int ret; | ||
325 | |||
326 | pinctrl_pm_select_default_state(dev); | ||
327 | |||
328 | reset_control_deassert(dwc3_data->rstc_pwrdn); | ||
329 | reset_control_deassert(dwc3_data->rstc_rst); | ||
330 | |||
331 | ret = st_dwc3_drd_init(dwc3_data); | ||
332 | if (ret) { | ||
333 | dev_err(dev, "drd initialisation failed\n"); | ||
334 | return ret; | ||
335 | } | ||
336 | |||
337 | /* ST glue logic init */ | ||
338 | st_dwc3_init(dwc3_data); | ||
339 | |||
340 | return 0; | ||
341 | } | ||
342 | #endif /* CONFIG_PM_SLEEP */ | ||
343 | |||
344 | static SIMPLE_DEV_PM_OPS(st_dwc3_dev_pm_ops, st_dwc3_suspend, st_dwc3_resume); | ||
345 | |||
346 | static const struct of_device_id st_dwc3_match[] = { | ||
347 | { .compatible = "st,stih407-dwc3" }, | ||
348 | { /* sentinel */ }, | ||
349 | }; | ||
350 | |||
351 | MODULE_DEVICE_TABLE(of, st_dwc3_match); | ||
352 | |||
353 | static struct platform_driver st_dwc3_driver = { | ||
354 | .probe = st_dwc3_probe, | ||
355 | .remove = st_dwc3_remove, | ||
356 | .driver = { | ||
357 | .name = "usb-st-dwc3", | ||
358 | .of_match_table = st_dwc3_match, | ||
359 | .pm = &st_dwc3_dev_pm_ops, | ||
360 | }, | ||
361 | }; | ||
362 | |||
363 | module_platform_driver(st_dwc3_driver); | ||
364 | |||
365 | MODULE_AUTHOR("Giuseppe Cavallaro <peppe.cavallaro@st.com>"); | ||
366 | MODULE_DESCRIPTION("DesignWare USB3 STi Glue Layer"); | ||
367 | MODULE_LICENSE("GPL v2"); | ||