1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Central probing code for the FOTG210 dual role driver |
4 | * We register one driver for the hardware and then we decide |
5 | * whether to proceed with probing the host or the peripheral |
6 | * driver. |
7 | */ |
8 | #include <linux/bitops.h> |
9 | #include <linux/clk.h> |
10 | #include <linux/device.h> |
11 | #include <linux/mfd/syscon.h> |
12 | #include <linux/module.h> |
13 | #include <linux/of.h> |
14 | #include <linux/platform_device.h> |
15 | #include <linux/regmap.h> |
16 | #include <linux/usb.h> |
17 | #include <linux/usb/otg.h> |
18 | |
19 | #include "fotg210.h" |
20 | |
21 | /* Role Register 0x80 */ |
22 | #define FOTG210_RR 0x80 |
23 | #define FOTG210_RR_ID BIT(21) /* 1 = B-device, 0 = A-device */ |
24 | #define FOTG210_RR_CROLE BIT(20) /* 1 = device, 0 = host */ |
25 | |
26 | /* |
27 | * Gemini-specific initialization function, only executed on the |
28 | * Gemini SoC using the global misc control register. |
29 | * |
30 | * The gemini USB blocks are connected to either Mini-A (host mode) or |
31 | * Mini-B (peripheral mode) plugs. There is no role switch support on the |
32 | * Gemini SoC, just either-or. |
33 | */ |
34 | #define GEMINI_GLOBAL_MISC_CTRL 0x30 |
35 | #define GEMINI_MISC_USB0_WAKEUP BIT(14) |
36 | #define GEMINI_MISC_USB1_WAKEUP BIT(15) |
37 | #define GEMINI_MISC_USB0_VBUS_ON BIT(22) |
38 | #define GEMINI_MISC_USB1_VBUS_ON BIT(23) |
39 | #define GEMINI_MISC_USB0_MINI_B BIT(29) |
40 | #define GEMINI_MISC_USB1_MINI_B BIT(30) |
41 | |
42 | static int fotg210_gemini_init(struct fotg210 *fotg, struct resource *res, |
43 | enum usb_dr_mode mode) |
44 | { |
45 | struct device *dev = fotg->dev; |
46 | struct device_node *np = dev->of_node; |
47 | struct regmap *map; |
48 | bool wakeup; |
49 | u32 mask, val; |
50 | int ret; |
51 | |
52 | map = syscon_regmap_lookup_by_phandle(np, property: "syscon" ); |
53 | if (IS_ERR(ptr: map)) |
54 | return dev_err_probe(dev, err: PTR_ERR(ptr: map), fmt: "no syscon\n" ); |
55 | fotg->map = map; |
56 | wakeup = of_property_read_bool(np, propname: "wakeup-source" ); |
57 | |
58 | /* |
59 | * Figure out if this is USB0 or USB1 by simply checking the |
60 | * physical base address. |
61 | */ |
62 | mask = 0; |
63 | if (res->start == 0x69000000) { |
64 | fotg->port = GEMINI_PORT_1; |
65 | mask = GEMINI_MISC_USB1_VBUS_ON | GEMINI_MISC_USB1_MINI_B | |
66 | GEMINI_MISC_USB1_WAKEUP; |
67 | if (mode == USB_DR_MODE_HOST) |
68 | val = GEMINI_MISC_USB1_VBUS_ON; |
69 | else |
70 | val = GEMINI_MISC_USB1_MINI_B; |
71 | if (wakeup) |
72 | val |= GEMINI_MISC_USB1_WAKEUP; |
73 | } else { |
74 | fotg->port = GEMINI_PORT_0; |
75 | mask = GEMINI_MISC_USB0_VBUS_ON | GEMINI_MISC_USB0_MINI_B | |
76 | GEMINI_MISC_USB0_WAKEUP; |
77 | if (mode == USB_DR_MODE_HOST) |
78 | val = GEMINI_MISC_USB0_VBUS_ON; |
79 | else |
80 | val = GEMINI_MISC_USB0_MINI_B; |
81 | if (wakeup) |
82 | val |= GEMINI_MISC_USB0_WAKEUP; |
83 | } |
84 | |
85 | ret = regmap_update_bits(map, GEMINI_GLOBAL_MISC_CTRL, mask, val); |
86 | if (ret) { |
87 | dev_err(dev, "failed to initialize Gemini PHY\n" ); |
88 | return ret; |
89 | } |
90 | |
91 | dev_info(dev, "initialized Gemini PHY in %s mode\n" , |
92 | (mode == USB_DR_MODE_HOST) ? "host" : "gadget" ); |
93 | return 0; |
94 | } |
95 | |
96 | /** |
97 | * fotg210_vbus() - Called by gadget driver to enable/disable VBUS |
98 | * @enable: true to enable VBUS, false to disable VBUS |
99 | */ |
100 | void fotg210_vbus(struct fotg210 *fotg, bool enable) |
101 | { |
102 | u32 mask; |
103 | u32 val; |
104 | int ret; |
105 | |
106 | switch (fotg->port) { |
107 | case GEMINI_PORT_0: |
108 | mask = GEMINI_MISC_USB0_VBUS_ON; |
109 | val = enable ? GEMINI_MISC_USB0_VBUS_ON : 0; |
110 | break; |
111 | case GEMINI_PORT_1: |
112 | mask = GEMINI_MISC_USB1_VBUS_ON; |
113 | val = enable ? GEMINI_MISC_USB1_VBUS_ON : 0; |
114 | break; |
115 | default: |
116 | return; |
117 | } |
118 | ret = regmap_update_bits(map: fotg->map, GEMINI_GLOBAL_MISC_CTRL, mask, val); |
119 | if (ret) |
120 | dev_err(fotg->dev, "failed to %s VBUS\n" , |
121 | enable ? "enable" : "disable" ); |
122 | dev_info(fotg->dev, "%s: %s VBUS\n" , __func__, enable ? "enable" : "disable" ); |
123 | } |
124 | |
125 | static int fotg210_probe(struct platform_device *pdev) |
126 | { |
127 | struct device *dev = &pdev->dev; |
128 | enum usb_dr_mode mode; |
129 | struct fotg210 *fotg; |
130 | u32 val; |
131 | int ret; |
132 | |
133 | fotg = devm_kzalloc(dev, size: sizeof(*fotg), GFP_KERNEL); |
134 | if (!fotg) |
135 | return -ENOMEM; |
136 | fotg->dev = dev; |
137 | |
138 | fotg->base = devm_platform_get_and_ioremap_resource(pdev, index: 0, res: &fotg->res); |
139 | if (IS_ERR(ptr: fotg->base)) |
140 | return PTR_ERR(ptr: fotg->base); |
141 | |
142 | fotg->pclk = devm_clk_get_optional_enabled(dev, id: "PCLK" ); |
143 | if (IS_ERR(ptr: fotg->pclk)) |
144 | return PTR_ERR(ptr: fotg->pclk); |
145 | |
146 | mode = usb_get_dr_mode(dev); |
147 | |
148 | if (of_device_is_compatible(device: dev->of_node, "cortina,gemini-usb" )) { |
149 | ret = fotg210_gemini_init(fotg, res: fotg->res, mode); |
150 | if (ret) |
151 | return ret; |
152 | } |
153 | |
154 | val = readl(addr: fotg->base + FOTG210_RR); |
155 | if (mode == USB_DR_MODE_PERIPHERAL) { |
156 | if (!(val & FOTG210_RR_CROLE)) |
157 | dev_err(dev, "block not in device role\n" ); |
158 | ret = fotg210_udc_probe(pdev, fotg); |
159 | } else { |
160 | if (val & FOTG210_RR_CROLE) |
161 | dev_err(dev, "block not in host role\n" ); |
162 | ret = fotg210_hcd_probe(pdev, fotg); |
163 | } |
164 | |
165 | return ret; |
166 | } |
167 | |
168 | static void fotg210_remove(struct platform_device *pdev) |
169 | { |
170 | struct device *dev = &pdev->dev; |
171 | enum usb_dr_mode mode; |
172 | |
173 | mode = usb_get_dr_mode(dev); |
174 | |
175 | if (mode == USB_DR_MODE_PERIPHERAL) |
176 | fotg210_udc_remove(pdev); |
177 | else |
178 | fotg210_hcd_remove(pdev); |
179 | } |
180 | |
181 | #ifdef CONFIG_OF |
182 | static const struct of_device_id fotg210_of_match[] = { |
183 | { .compatible = "faraday,fotg200" }, |
184 | { .compatible = "faraday,fotg210" }, |
185 | /* TODO: can we also handle FUSB220? */ |
186 | {}, |
187 | }; |
188 | MODULE_DEVICE_TABLE(of, fotg210_of_match); |
189 | #endif |
190 | |
191 | static struct platform_driver fotg210_driver = { |
192 | .driver = { |
193 | .name = "fotg210" , |
194 | .of_match_table = of_match_ptr(fotg210_of_match), |
195 | }, |
196 | .probe = fotg210_probe, |
197 | .remove_new = fotg210_remove, |
198 | }; |
199 | |
200 | static int __init fotg210_init(void) |
201 | { |
202 | if (IS_ENABLED(CONFIG_USB_FOTG210_HCD) && !usb_disabled()) |
203 | fotg210_hcd_init(); |
204 | return platform_driver_register(&fotg210_driver); |
205 | } |
206 | module_init(fotg210_init); |
207 | |
208 | static void __exit fotg210_cleanup(void) |
209 | { |
210 | platform_driver_unregister(&fotg210_driver); |
211 | if (IS_ENABLED(CONFIG_USB_FOTG210_HCD)) |
212 | fotg210_hcd_cleanup(); |
213 | } |
214 | module_exit(fotg210_cleanup); |
215 | |
216 | MODULE_AUTHOR("Yuan-Hsin Chen, Feng-Hsin Chiang" ); |
217 | MODULE_LICENSE("GPL" ); |
218 | MODULE_DESCRIPTION("FOTG210 Dual Role Controller Driver" ); |
219 | |