1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * NOP USB transceiver for all USB transceiver which are either built-in |
4 | * into USB IP or which are mostly autonomous. |
5 | * |
6 | * Copyright (C) 2009 Texas Instruments Inc |
7 | * Author: Ajay Kumar Gupta <ajay.gupta@ti.com> |
8 | * |
9 | * Current status: |
10 | * This provides a "nop" transceiver for PHYs which are |
11 | * autonomous such as isp1504, isp1707, etc. |
12 | */ |
13 | |
14 | #include <linux/module.h> |
15 | #include <linux/platform_device.h> |
16 | #include <linux/dma-mapping.h> |
17 | #include <linux/usb/gadget.h> |
18 | #include <linux/usb/otg.h> |
19 | #include <linux/usb/usb_phy_generic.h> |
20 | #include <linux/slab.h> |
21 | #include <linux/clk.h> |
22 | #include <linux/regulator/consumer.h> |
23 | #include <linux/of.h> |
24 | #include <linux/gpio/consumer.h> |
25 | #include <linux/delay.h> |
26 | |
27 | #include "phy-generic.h" |
28 | |
29 | #define VBUS_IRQ_FLAGS \ |
30 | (IRQF_SHARED | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | \ |
31 | IRQF_ONESHOT) |
32 | |
33 | struct platform_device *usb_phy_generic_register(void) |
34 | { |
35 | return platform_device_register_simple(name: "usb_phy_generic" , |
36 | PLATFORM_DEVID_AUTO, NULL, num: 0); |
37 | } |
38 | EXPORT_SYMBOL_GPL(usb_phy_generic_register); |
39 | |
40 | void usb_phy_generic_unregister(struct platform_device *pdev) |
41 | { |
42 | platform_device_unregister(pdev); |
43 | } |
44 | EXPORT_SYMBOL_GPL(usb_phy_generic_unregister); |
45 | |
46 | static int nop_set_suspend(struct usb_phy *x, int suspend) |
47 | { |
48 | struct usb_phy_generic *nop = dev_get_drvdata(dev: x->dev); |
49 | int ret = 0; |
50 | |
51 | if (suspend) { |
52 | if (!IS_ERR(ptr: nop->clk)) |
53 | clk_disable_unprepare(clk: nop->clk); |
54 | if (!IS_ERR(ptr: nop->vcc) && !device_may_wakeup(dev: x->dev)) |
55 | ret = regulator_disable(regulator: nop->vcc); |
56 | } else { |
57 | if (!IS_ERR(ptr: nop->vcc) && !device_may_wakeup(dev: x->dev)) |
58 | ret = regulator_enable(regulator: nop->vcc); |
59 | if (!IS_ERR(ptr: nop->clk)) |
60 | clk_prepare_enable(clk: nop->clk); |
61 | } |
62 | |
63 | return ret; |
64 | } |
65 | |
66 | static void nop_reset(struct usb_phy_generic *nop) |
67 | { |
68 | if (!nop->gpiod_reset) |
69 | return; |
70 | |
71 | gpiod_set_value_cansleep(desc: nop->gpiod_reset, value: 1); |
72 | usleep_range(min: 10000, max: 20000); |
73 | gpiod_set_value_cansleep(desc: nop->gpiod_reset, value: 0); |
74 | } |
75 | |
76 | /* interface to regulator framework */ |
77 | static int nop_set_vbus(struct usb_otg *otg, bool enable) |
78 | { |
79 | int ret = 0; |
80 | struct usb_phy_generic *nop = dev_get_drvdata(dev: otg->usb_phy->dev); |
81 | |
82 | if (!nop->vbus_draw) |
83 | return 0; |
84 | |
85 | if (enable && !nop->vbus_draw_enabled) { |
86 | ret = regulator_enable(regulator: nop->vbus_draw); |
87 | if (ret) |
88 | nop->vbus_draw_enabled = false; |
89 | else |
90 | nop->vbus_draw_enabled = true; |
91 | |
92 | } else if (!enable && nop->vbus_draw_enabled) { |
93 | ret = regulator_disable(regulator: nop->vbus_draw); |
94 | nop->vbus_draw_enabled = false; |
95 | } |
96 | return ret; |
97 | } |
98 | |
99 | |
100 | static irqreturn_t nop_gpio_vbus_thread(int irq, void *data) |
101 | { |
102 | struct usb_phy_generic *nop = data; |
103 | struct usb_otg *otg = nop->phy.otg; |
104 | int vbus, status; |
105 | |
106 | vbus = gpiod_get_value(desc: nop->gpiod_vbus); |
107 | if ((vbus ^ nop->vbus) == 0) |
108 | return IRQ_HANDLED; |
109 | nop->vbus = vbus; |
110 | |
111 | if (vbus) { |
112 | status = USB_EVENT_VBUS; |
113 | otg->state = OTG_STATE_B_PERIPHERAL; |
114 | nop->phy.last_event = status; |
115 | |
116 | atomic_notifier_call_chain(nh: &nop->phy.notifier, val: status, |
117 | v: otg->gadget); |
118 | } else { |
119 | status = USB_EVENT_NONE; |
120 | otg->state = OTG_STATE_B_IDLE; |
121 | nop->phy.last_event = status; |
122 | |
123 | atomic_notifier_call_chain(nh: &nop->phy.notifier, val: status, |
124 | v: otg->gadget); |
125 | } |
126 | return IRQ_HANDLED; |
127 | } |
128 | |
129 | int usb_gen_phy_init(struct usb_phy *phy) |
130 | { |
131 | struct usb_phy_generic *nop = dev_get_drvdata(dev: phy->dev); |
132 | int ret; |
133 | |
134 | if (!IS_ERR(ptr: nop->vcc)) { |
135 | if (regulator_enable(regulator: nop->vcc)) |
136 | dev_err(phy->dev, "Failed to enable power\n" ); |
137 | } |
138 | |
139 | if (!IS_ERR(ptr: nop->clk)) { |
140 | ret = clk_prepare_enable(clk: nop->clk); |
141 | if (ret) |
142 | return ret; |
143 | } |
144 | |
145 | nop_reset(nop); |
146 | |
147 | return 0; |
148 | } |
149 | EXPORT_SYMBOL_GPL(usb_gen_phy_init); |
150 | |
151 | void usb_gen_phy_shutdown(struct usb_phy *phy) |
152 | { |
153 | struct usb_phy_generic *nop = dev_get_drvdata(dev: phy->dev); |
154 | |
155 | gpiod_set_value_cansleep(desc: nop->gpiod_reset, value: 1); |
156 | |
157 | if (!IS_ERR(ptr: nop->clk)) |
158 | clk_disable_unprepare(clk: nop->clk); |
159 | |
160 | if (!IS_ERR(ptr: nop->vcc)) { |
161 | if (regulator_disable(regulator: nop->vcc)) |
162 | dev_err(phy->dev, "Failed to disable power\n" ); |
163 | } |
164 | } |
165 | EXPORT_SYMBOL_GPL(usb_gen_phy_shutdown); |
166 | |
167 | static int nop_set_peripheral(struct usb_otg *otg, struct usb_gadget *gadget) |
168 | { |
169 | if (!otg) |
170 | return -ENODEV; |
171 | |
172 | if (!gadget) { |
173 | otg->gadget = NULL; |
174 | return -ENODEV; |
175 | } |
176 | |
177 | otg->gadget = gadget; |
178 | if (otg->state == OTG_STATE_B_PERIPHERAL) |
179 | atomic_notifier_call_chain(nh: &otg->usb_phy->notifier, |
180 | val: USB_EVENT_VBUS, v: otg->gadget); |
181 | else |
182 | otg->state = OTG_STATE_B_IDLE; |
183 | return 0; |
184 | } |
185 | |
186 | static int nop_set_host(struct usb_otg *otg, struct usb_bus *host) |
187 | { |
188 | if (!otg) |
189 | return -ENODEV; |
190 | |
191 | if (!host) { |
192 | otg->host = NULL; |
193 | return -ENODEV; |
194 | } |
195 | |
196 | otg->host = host; |
197 | return 0; |
198 | } |
199 | |
200 | int usb_phy_gen_create_phy(struct device *dev, struct usb_phy_generic *nop) |
201 | { |
202 | enum usb_phy_type type = USB_PHY_TYPE_USB2; |
203 | int err = 0; |
204 | |
205 | u32 clk_rate = 0; |
206 | bool needs_clk = false; |
207 | |
208 | if (dev->of_node) { |
209 | struct device_node *node = dev->of_node; |
210 | |
211 | if (of_property_read_u32(np: node, propname: "clock-frequency" , out_value: &clk_rate)) |
212 | clk_rate = 0; |
213 | |
214 | needs_clk = of_property_read_bool(np: node, propname: "clocks" ); |
215 | } |
216 | nop->gpiod_reset = devm_gpiod_get_optional(dev, con_id: "reset" , |
217 | flags: GPIOD_ASIS); |
218 | err = PTR_ERR_OR_ZERO(ptr: nop->gpiod_reset); |
219 | if (!err) { |
220 | nop->gpiod_vbus = devm_gpiod_get_optional(dev, |
221 | con_id: "vbus-detect" , |
222 | flags: GPIOD_ASIS); |
223 | err = PTR_ERR_OR_ZERO(ptr: nop->gpiod_vbus); |
224 | } |
225 | |
226 | if (err) |
227 | return dev_err_probe(dev, err, |
228 | fmt: "Error requesting RESET or VBUS GPIO\n" ); |
229 | if (nop->gpiod_reset) |
230 | gpiod_direction_output(desc: nop->gpiod_reset, value: 1); |
231 | |
232 | nop->phy.otg = devm_kzalloc(dev, size: sizeof(*nop->phy.otg), |
233 | GFP_KERNEL); |
234 | if (!nop->phy.otg) |
235 | return -ENOMEM; |
236 | |
237 | nop->clk = devm_clk_get(dev, id: "main_clk" ); |
238 | if (IS_ERR(ptr: nop->clk)) { |
239 | dev_dbg(dev, "Can't get phy clock: %ld\n" , |
240 | PTR_ERR(nop->clk)); |
241 | if (needs_clk) |
242 | return PTR_ERR(ptr: nop->clk); |
243 | } |
244 | |
245 | if (!IS_ERR(ptr: nop->clk) && clk_rate) { |
246 | err = clk_set_rate(clk: nop->clk, rate: clk_rate); |
247 | if (err) { |
248 | dev_err(dev, "Error setting clock rate\n" ); |
249 | return err; |
250 | } |
251 | } |
252 | |
253 | nop->vcc = devm_regulator_get_optional(dev, id: "vcc" ); |
254 | if (IS_ERR(ptr: nop->vcc) && PTR_ERR(ptr: nop->vcc) != -ENODEV) |
255 | return dev_err_probe(dev, err: PTR_ERR(ptr: nop->vcc), |
256 | fmt: "could not get vcc regulator\n" ); |
257 | |
258 | nop->vbus_draw = devm_regulator_get_exclusive(dev, id: "vbus" ); |
259 | if (PTR_ERR(ptr: nop->vbus_draw) == -ENODEV) |
260 | nop->vbus_draw = NULL; |
261 | if (IS_ERR(ptr: nop->vbus_draw)) |
262 | return dev_err_probe(dev, err: PTR_ERR(ptr: nop->vbus_draw), |
263 | fmt: "could not get vbus regulator\n" ); |
264 | |
265 | nop->dev = dev; |
266 | nop->phy.dev = nop->dev; |
267 | nop->phy.label = "nop-xceiv" ; |
268 | nop->phy.set_suspend = nop_set_suspend; |
269 | nop->phy.type = type; |
270 | |
271 | nop->phy.otg->state = OTG_STATE_UNDEFINED; |
272 | nop->phy.otg->usb_phy = &nop->phy; |
273 | nop->phy.otg->set_host = nop_set_host; |
274 | nop->phy.otg->set_peripheral = nop_set_peripheral; |
275 | nop->phy.otg->set_vbus = nop_set_vbus; |
276 | |
277 | return 0; |
278 | } |
279 | EXPORT_SYMBOL_GPL(usb_phy_gen_create_phy); |
280 | |
281 | static int usb_phy_generic_probe(struct platform_device *pdev) |
282 | { |
283 | struct device *dev = &pdev->dev; |
284 | struct device_node *dn = dev->of_node; |
285 | struct usb_phy_generic *nop; |
286 | int err; |
287 | |
288 | nop = devm_kzalloc(dev, size: sizeof(*nop), GFP_KERNEL); |
289 | if (!nop) |
290 | return -ENOMEM; |
291 | |
292 | err = usb_phy_gen_create_phy(dev, nop); |
293 | if (err) |
294 | return err; |
295 | if (nop->gpiod_vbus) { |
296 | err = devm_request_threaded_irq(dev: &pdev->dev, |
297 | irq: gpiod_to_irq(desc: nop->gpiod_vbus), |
298 | NULL, thread_fn: nop_gpio_vbus_thread, |
299 | VBUS_IRQ_FLAGS, devname: "vbus_detect" , |
300 | dev_id: nop); |
301 | if (err) { |
302 | dev_err(&pdev->dev, "can't request irq %i, err: %d\n" , |
303 | gpiod_to_irq(nop->gpiod_vbus), err); |
304 | return err; |
305 | } |
306 | nop->phy.otg->state = gpiod_get_value(desc: nop->gpiod_vbus) ? |
307 | OTG_STATE_B_PERIPHERAL : OTG_STATE_B_IDLE; |
308 | } |
309 | |
310 | nop->phy.init = usb_gen_phy_init; |
311 | nop->phy.shutdown = usb_gen_phy_shutdown; |
312 | |
313 | err = usb_add_phy_dev(&nop->phy); |
314 | if (err) { |
315 | dev_err(&pdev->dev, "can't register transceiver, err: %d\n" , |
316 | err); |
317 | return err; |
318 | } |
319 | |
320 | platform_set_drvdata(pdev, data: nop); |
321 | |
322 | device_set_wakeup_capable(dev: &pdev->dev, |
323 | capable: of_property_read_bool(np: dn, propname: "wakeup-source" )); |
324 | |
325 | return 0; |
326 | } |
327 | |
328 | static void usb_phy_generic_remove(struct platform_device *pdev) |
329 | { |
330 | struct usb_phy_generic *nop = platform_get_drvdata(pdev); |
331 | |
332 | usb_remove_phy(&nop->phy); |
333 | |
334 | if (nop->vbus_draw && nop->vbus_draw_enabled) |
335 | regulator_disable(regulator: nop->vbus_draw); |
336 | } |
337 | |
338 | static const struct of_device_id nop_xceiv_dt_ids[] = { |
339 | { .compatible = "usb-nop-xceiv" }, |
340 | { } |
341 | }; |
342 | |
343 | MODULE_DEVICE_TABLE(of, nop_xceiv_dt_ids); |
344 | |
345 | static struct platform_driver usb_phy_generic_driver = { |
346 | .probe = usb_phy_generic_probe, |
347 | .remove_new = usb_phy_generic_remove, |
348 | .driver = { |
349 | .name = "usb_phy_generic" , |
350 | .of_match_table = nop_xceiv_dt_ids, |
351 | }, |
352 | }; |
353 | |
354 | static int __init usb_phy_generic_init(void) |
355 | { |
356 | return platform_driver_register(&usb_phy_generic_driver); |
357 | } |
358 | subsys_initcall(usb_phy_generic_init); |
359 | |
360 | static void __exit usb_phy_generic_exit(void) |
361 | { |
362 | platform_driver_unregister(&usb_phy_generic_driver); |
363 | } |
364 | module_exit(usb_phy_generic_exit); |
365 | |
366 | MODULE_ALIAS("platform:usb_phy_generic" ); |
367 | MODULE_AUTHOR("Texas Instruments Inc" ); |
368 | MODULE_DESCRIPTION("NOP USB Transceiver driver" ); |
369 | MODULE_LICENSE("GPL" ); |
370 | |