]>
Commit | Line | Data |
---|---|---|
0e08d2a7 FW |
1 | /* |
2 | * Rockchip USB2.0 PHY with Innosilicon IP block driver | |
3 | * | |
4 | * Copyright (C) 2016 Fuzhou Rockchip Electronics Co., Ltd | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License as published by | |
8 | * the Free Software Foundation; either version 2 of the License, or | |
9 | * (at your option) any later version. | |
10 | * | |
11 | * This program is distributed in the hope that it will be useful, | |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | * GNU General Public License for more details. | |
15 | */ | |
16 | ||
17 | #include <linux/clk.h> | |
18 | #include <linux/clk-provider.h> | |
19 | #include <linux/delay.h> | |
98898f3b | 20 | #include <linux/extcon.h> |
0e08d2a7 FW |
21 | #include <linux/interrupt.h> |
22 | #include <linux/io.h> | |
23 | #include <linux/gpio/consumer.h> | |
24 | #include <linux/jiffies.h> | |
25 | #include <linux/kernel.h> | |
26 | #include <linux/module.h> | |
27 | #include <linux/mutex.h> | |
28 | #include <linux/of.h> | |
29 | #include <linux/of_address.h> | |
30 | #include <linux/of_irq.h> | |
31 | #include <linux/of_platform.h> | |
32 | #include <linux/phy/phy.h> | |
33 | #include <linux/platform_device.h> | |
98898f3b | 34 | #include <linux/power_supply.h> |
0e08d2a7 FW |
35 | #include <linux/regmap.h> |
36 | #include <linux/mfd/syscon.h> | |
98898f3b WW |
37 | #include <linux/usb/of.h> |
38 | #include <linux/usb/otg.h> | |
0e08d2a7 FW |
39 | |
40 | #define BIT_WRITEABLE_SHIFT 16 | |
98898f3b WW |
41 | #define SCHEDULE_DELAY (60 * HZ) |
42 | #define OTG_SCHEDULE_DELAY (2 * HZ) | |
0e08d2a7 FW |
43 | |
44 | enum rockchip_usb2phy_port_id { | |
45 | USB2PHY_PORT_OTG, | |
46 | USB2PHY_PORT_HOST, | |
47 | USB2PHY_NUM_PORTS, | |
48 | }; | |
49 | ||
50 | enum rockchip_usb2phy_host_state { | |
51 | PHY_STATE_HS_ONLINE = 0, | |
52 | PHY_STATE_DISCONNECT = 1, | |
53 | PHY_STATE_CONNECT = 2, | |
54 | PHY_STATE_FS_LS_ONLINE = 4, | |
55 | }; | |
56 | ||
98898f3b WW |
57 | /** |
58 | * Different states involved in USB charger detection. | |
59 | * USB_CHG_STATE_UNDEFINED USB charger is not connected or detection | |
60 | * process is not yet started. | |
61 | * USB_CHG_STATE_WAIT_FOR_DCD Waiting for Data pins contact. | |
62 | * USB_CHG_STATE_DCD_DONE Data pin contact is detected. | |
63 | * USB_CHG_STATE_PRIMARY_DONE Primary detection is completed (Detects | |
64 | * between SDP and DCP/CDP). | |
65 | * USB_CHG_STATE_SECONDARY_DONE Secondary detection is completed (Detects | |
66 | * between DCP and CDP). | |
67 | * USB_CHG_STATE_DETECTED USB charger type is determined. | |
68 | */ | |
69 | enum usb_chg_state { | |
70 | USB_CHG_STATE_UNDEFINED = 0, | |
71 | USB_CHG_STATE_WAIT_FOR_DCD, | |
72 | USB_CHG_STATE_DCD_DONE, | |
73 | USB_CHG_STATE_PRIMARY_DONE, | |
74 | USB_CHG_STATE_SECONDARY_DONE, | |
75 | USB_CHG_STATE_DETECTED, | |
76 | }; | |
77 | ||
78 | static const unsigned int rockchip_usb2phy_extcon_cable[] = { | |
79 | EXTCON_USB, | |
80 | EXTCON_USB_HOST, | |
81 | EXTCON_CHG_USB_SDP, | |
82 | EXTCON_CHG_USB_CDP, | |
83 | EXTCON_CHG_USB_DCP, | |
84 | EXTCON_CHG_USB_SLOW, | |
85 | EXTCON_NONE, | |
86 | }; | |
87 | ||
0e08d2a7 FW |
88 | struct usb2phy_reg { |
89 | unsigned int offset; | |
90 | unsigned int bitend; | |
91 | unsigned int bitstart; | |
92 | unsigned int disable; | |
93 | unsigned int enable; | |
94 | }; | |
95 | ||
98898f3b WW |
96 | /** |
97 | * struct rockchip_chg_det_reg: usb charger detect registers | |
98 | * @cp_det: charging port detected successfully. | |
99 | * @dcp_det: dedicated charging port detected successfully. | |
100 | * @dp_det: assert data pin connect successfully. | |
101 | * @idm_sink_en: open dm sink curren. | |
102 | * @idp_sink_en: open dp sink current. | |
103 | * @idp_src_en: open dm source current. | |
104 | * @rdm_pdwn_en: open dm pull down resistor. | |
105 | * @vdm_src_en: open dm voltage source. | |
106 | * @vdp_src_en: open dp voltage source. | |
107 | * @opmode: utmi operational mode. | |
108 | */ | |
109 | struct rockchip_chg_det_reg { | |
110 | struct usb2phy_reg cp_det; | |
111 | struct usb2phy_reg dcp_det; | |
112 | struct usb2phy_reg dp_det; | |
113 | struct usb2phy_reg idm_sink_en; | |
114 | struct usb2phy_reg idp_sink_en; | |
115 | struct usb2phy_reg idp_src_en; | |
116 | struct usb2phy_reg rdm_pdwn_en; | |
117 | struct usb2phy_reg vdm_src_en; | |
118 | struct usb2phy_reg vdp_src_en; | |
119 | struct usb2phy_reg opmode; | |
120 | }; | |
121 | ||
0e08d2a7 FW |
122 | /** |
123 | * struct rockchip_usb2phy_port_cfg: usb-phy port configuration. | |
124 | * @phy_sus: phy suspend register. | |
98898f3b WW |
125 | * @bvalid_det_en: vbus valid rise detection enable register. |
126 | * @bvalid_det_st: vbus valid rise detection status register. | |
127 | * @bvalid_det_clr: vbus valid rise detection clear register. | |
0e08d2a7 FW |
128 | * @ls_det_en: linestate detection enable register. |
129 | * @ls_det_st: linestate detection state register. | |
130 | * @ls_det_clr: linestate detection clear register. | |
98898f3b WW |
131 | * @utmi_avalid: utmi vbus avalid status register. |
132 | * @utmi_bvalid: utmi vbus bvalid status register. | |
0e08d2a7 FW |
133 | * @utmi_ls: utmi linestate state register. |
134 | * @utmi_hstdet: utmi host disconnect register. | |
135 | */ | |
136 | struct rockchip_usb2phy_port_cfg { | |
137 | struct usb2phy_reg phy_sus; | |
98898f3b WW |
138 | struct usb2phy_reg bvalid_det_en; |
139 | struct usb2phy_reg bvalid_det_st; | |
140 | struct usb2phy_reg bvalid_det_clr; | |
0e08d2a7 FW |
141 | struct usb2phy_reg ls_det_en; |
142 | struct usb2phy_reg ls_det_st; | |
143 | struct usb2phy_reg ls_det_clr; | |
98898f3b WW |
144 | struct usb2phy_reg utmi_avalid; |
145 | struct usb2phy_reg utmi_bvalid; | |
0e08d2a7 FW |
146 | struct usb2phy_reg utmi_ls; |
147 | struct usb2phy_reg utmi_hstdet; | |
148 | }; | |
149 | ||
150 | /** | |
151 | * struct rockchip_usb2phy_cfg: usb-phy configuration. | |
152 | * @reg: the address offset of grf for usb-phy config. | |
153 | * @num_ports: specify how many ports that the phy has. | |
154 | * @clkout_ctl: keep on/turn off output clk of phy. | |
98898f3b | 155 | * @chg_det: charger detection registers. |
0e08d2a7 FW |
156 | */ |
157 | struct rockchip_usb2phy_cfg { | |
158 | unsigned int reg; | |
159 | unsigned int num_ports; | |
160 | struct usb2phy_reg clkout_ctl; | |
161 | const struct rockchip_usb2phy_port_cfg port_cfgs[USB2PHY_NUM_PORTS]; | |
98898f3b | 162 | const struct rockchip_chg_det_reg chg_det; |
0e08d2a7 FW |
163 | }; |
164 | ||
165 | /** | |
166 | * struct rockchip_usb2phy_port: usb-phy port data. | |
167 | * @port_id: flag for otg port or host port. | |
168 | * @suspended: phy suspended flag. | |
98898f3b WW |
169 | * @utmi_avalid: utmi avalid status usage flag. |
170 | * true - use avalid to get vbus status | |
171 | * flase - use bvalid to get vbus status | |
172 | * @vbus_attached: otg device vbus status. | |
173 | * @bvalid_irq: IRQ number assigned for vbus valid rise detection. | |
0e08d2a7 FW |
174 | * @ls_irq: IRQ number assigned for linestate detection. |
175 | * @mutex: for register updating in sm_work. | |
98898f3b WW |
176 | * @chg_work: charge detect work. |
177 | * @otg_sm_work: OTG state machine work. | |
178 | * @sm_work: HOST state machine work. | |
0e08d2a7 | 179 | * @phy_cfg: port register configuration, assigned by driver data. |
98898f3b WW |
180 | * @event_nb: hold event notification callback. |
181 | * @state: define OTG enumeration states before device reset. | |
182 | * @mode: the dr_mode of the controller. | |
0e08d2a7 FW |
183 | */ |
184 | struct rockchip_usb2phy_port { | |
185 | struct phy *phy; | |
186 | unsigned int port_id; | |
187 | bool suspended; | |
98898f3b WW |
188 | bool utmi_avalid; |
189 | bool vbus_attached; | |
190 | int bvalid_irq; | |
0e08d2a7 FW |
191 | int ls_irq; |
192 | struct mutex mutex; | |
98898f3b WW |
193 | struct delayed_work chg_work; |
194 | struct delayed_work otg_sm_work; | |
0e08d2a7 FW |
195 | struct delayed_work sm_work; |
196 | const struct rockchip_usb2phy_port_cfg *port_cfg; | |
98898f3b WW |
197 | struct notifier_block event_nb; |
198 | enum usb_otg_state state; | |
199 | enum usb_dr_mode mode; | |
0e08d2a7 FW |
200 | }; |
201 | ||
202 | /** | |
203 | * struct rockchip_usb2phy: usb2.0 phy driver data. | |
204 | * @grf: General Register Files regmap. | |
205 | * @clk: clock struct of phy input clk. | |
206 | * @clk480m: clock struct of phy output clk. | |
207 | * @clk_hw: clock struct of phy output clk management. | |
98898f3b WW |
208 | * @chg_state: states involved in USB charger detection. |
209 | * @chg_type: USB charger types. | |
210 | * @dcd_retries: The retry count used to track Data contact | |
211 | * detection process. | |
212 | * @edev: extcon device for notification registration | |
0e08d2a7 FW |
213 | * @phy_cfg: phy register configuration, assigned by driver data. |
214 | * @ports: phy port instance. | |
215 | */ | |
216 | struct rockchip_usb2phy { | |
217 | struct device *dev; | |
218 | struct regmap *grf; | |
219 | struct clk *clk; | |
220 | struct clk *clk480m; | |
221 | struct clk_hw clk480m_hw; | |
98898f3b WW |
222 | enum usb_chg_state chg_state; |
223 | enum power_supply_type chg_type; | |
224 | u8 dcd_retries; | |
225 | struct extcon_dev *edev; | |
0e08d2a7 FW |
226 | const struct rockchip_usb2phy_cfg *phy_cfg; |
227 | struct rockchip_usb2phy_port ports[USB2PHY_NUM_PORTS]; | |
228 | }; | |
229 | ||
230 | static inline int property_enable(struct rockchip_usb2phy *rphy, | |
231 | const struct usb2phy_reg *reg, bool en) | |
232 | { | |
233 | unsigned int val, mask, tmp; | |
234 | ||
235 | tmp = en ? reg->enable : reg->disable; | |
236 | mask = GENMASK(reg->bitend, reg->bitstart); | |
237 | val = (tmp << reg->bitstart) | (mask << BIT_WRITEABLE_SHIFT); | |
238 | ||
239 | return regmap_write(rphy->grf, reg->offset, val); | |
240 | } | |
241 | ||
242 | static inline bool property_enabled(struct rockchip_usb2phy *rphy, | |
243 | const struct usb2phy_reg *reg) | |
244 | { | |
245 | int ret; | |
246 | unsigned int tmp, orig; | |
247 | unsigned int mask = GENMASK(reg->bitend, reg->bitstart); | |
248 | ||
249 | ret = regmap_read(rphy->grf, reg->offset, &orig); | |
250 | if (ret) | |
251 | return false; | |
252 | ||
253 | tmp = (orig & mask) >> reg->bitstart; | |
254 | return tmp == reg->enable; | |
255 | } | |
256 | ||
ae9fc711 | 257 | static int rockchip_usb2phy_clk480m_prepare(struct clk_hw *hw) |
0e08d2a7 FW |
258 | { |
259 | struct rockchip_usb2phy *rphy = | |
260 | container_of(hw, struct rockchip_usb2phy, clk480m_hw); | |
261 | int ret; | |
262 | ||
263 | /* turn on 480m clk output if it is off */ | |
264 | if (!property_enabled(rphy, &rphy->phy_cfg->clkout_ctl)) { | |
265 | ret = property_enable(rphy, &rphy->phy_cfg->clkout_ctl, true); | |
266 | if (ret) | |
267 | return ret; | |
268 | ||
882e1492 WW |
269 | /* waiting for the clk become stable */ |
270 | usleep_range(1200, 1300); | |
0e08d2a7 FW |
271 | } |
272 | ||
273 | return 0; | |
274 | } | |
275 | ||
ae9fc711 | 276 | static void rockchip_usb2phy_clk480m_unprepare(struct clk_hw *hw) |
0e08d2a7 FW |
277 | { |
278 | struct rockchip_usb2phy *rphy = | |
279 | container_of(hw, struct rockchip_usb2phy, clk480m_hw); | |
280 | ||
281 | /* turn off 480m clk output */ | |
282 | property_enable(rphy, &rphy->phy_cfg->clkout_ctl, false); | |
283 | } | |
284 | ||
ae9fc711 | 285 | static int rockchip_usb2phy_clk480m_prepared(struct clk_hw *hw) |
0e08d2a7 FW |
286 | { |
287 | struct rockchip_usb2phy *rphy = | |
288 | container_of(hw, struct rockchip_usb2phy, clk480m_hw); | |
289 | ||
290 | return property_enabled(rphy, &rphy->phy_cfg->clkout_ctl); | |
291 | } | |
292 | ||
293 | static unsigned long | |
294 | rockchip_usb2phy_clk480m_recalc_rate(struct clk_hw *hw, | |
295 | unsigned long parent_rate) | |
296 | { | |
297 | return 480000000; | |
298 | } | |
299 | ||
300 | static const struct clk_ops rockchip_usb2phy_clkout_ops = { | |
ae9fc711 WW |
301 | .prepare = rockchip_usb2phy_clk480m_prepare, |
302 | .unprepare = rockchip_usb2phy_clk480m_unprepare, | |
303 | .is_prepared = rockchip_usb2phy_clk480m_prepared, | |
0e08d2a7 FW |
304 | .recalc_rate = rockchip_usb2phy_clk480m_recalc_rate, |
305 | }; | |
306 | ||
307 | static void rockchip_usb2phy_clk480m_unregister(void *data) | |
308 | { | |
309 | struct rockchip_usb2phy *rphy = data; | |
310 | ||
311 | of_clk_del_provider(rphy->dev->of_node); | |
312 | clk_unregister(rphy->clk480m); | |
313 | } | |
314 | ||
315 | static int | |
316 | rockchip_usb2phy_clk480m_register(struct rockchip_usb2phy *rphy) | |
317 | { | |
318 | struct device_node *node = rphy->dev->of_node; | |
319 | struct clk_init_data init; | |
320 | const char *clk_name; | |
321 | int ret; | |
322 | ||
323 | init.flags = 0; | |
324 | init.name = "clk_usbphy_480m"; | |
325 | init.ops = &rockchip_usb2phy_clkout_ops; | |
326 | ||
327 | /* optional override of the clockname */ | |
328 | of_property_read_string(node, "clock-output-names", &init.name); | |
329 | ||
330 | if (rphy->clk) { | |
331 | clk_name = __clk_get_name(rphy->clk); | |
332 | init.parent_names = &clk_name; | |
333 | init.num_parents = 1; | |
334 | } else { | |
335 | init.parent_names = NULL; | |
336 | init.num_parents = 0; | |
337 | } | |
338 | ||
339 | rphy->clk480m_hw.init = &init; | |
340 | ||
341 | /* register the clock */ | |
342 | rphy->clk480m = clk_register(rphy->dev, &rphy->clk480m_hw); | |
343 | if (IS_ERR(rphy->clk480m)) { | |
344 | ret = PTR_ERR(rphy->clk480m); | |
345 | goto err_ret; | |
346 | } | |
347 | ||
348 | ret = of_clk_add_provider(node, of_clk_src_simple_get, rphy->clk480m); | |
349 | if (ret < 0) | |
350 | goto err_clk_provider; | |
351 | ||
352 | ret = devm_add_action(rphy->dev, rockchip_usb2phy_clk480m_unregister, | |
353 | rphy); | |
354 | if (ret < 0) | |
355 | goto err_unreg_action; | |
356 | ||
357 | return 0; | |
358 | ||
359 | err_unreg_action: | |
360 | of_clk_del_provider(node); | |
361 | err_clk_provider: | |
362 | clk_unregister(rphy->clk480m); | |
363 | err_ret: | |
364 | return ret; | |
365 | } | |
366 | ||
98898f3b | 367 | static int rockchip_usb2phy_extcon_register(struct rockchip_usb2phy *rphy) |
0e08d2a7 | 368 | { |
0e08d2a7 | 369 | int ret; |
98898f3b WW |
370 | struct device_node *node = rphy->dev->of_node; |
371 | struct extcon_dev *edev; | |
372 | ||
373 | if (of_property_read_bool(node, "extcon")) { | |
374 | edev = extcon_get_edev_by_phandle(rphy->dev, 0); | |
375 | if (IS_ERR(edev)) { | |
376 | if (PTR_ERR(edev) != -EPROBE_DEFER) | |
377 | dev_err(rphy->dev, "Invalid or missing extcon\n"); | |
378 | return PTR_ERR(edev); | |
379 | } | |
380 | } else { | |
381 | /* Initialize extcon device */ | |
382 | edev = devm_extcon_dev_allocate(rphy->dev, | |
383 | rockchip_usb2phy_extcon_cable); | |
0e08d2a7 | 384 | |
98898f3b WW |
385 | if (IS_ERR(edev)) |
386 | return -ENOMEM; | |
0e08d2a7 | 387 | |
98898f3b | 388 | ret = devm_extcon_dev_register(rphy->dev, edev); |
0e08d2a7 | 389 | if (ret) { |
98898f3b | 390 | dev_err(rphy->dev, "failed to register extcon device\n"); |
0e08d2a7 FW |
391 | return ret; |
392 | } | |
98898f3b | 393 | } |
0e08d2a7 | 394 | |
98898f3b WW |
395 | rphy->edev = edev; |
396 | ||
397 | return 0; | |
398 | } | |
399 | ||
400 | static int rockchip_usb2phy_init(struct phy *phy) | |
401 | { | |
402 | struct rockchip_usb2phy_port *rport = phy_get_drvdata(phy); | |
403 | struct rockchip_usb2phy *rphy = dev_get_drvdata(phy->dev.parent); | |
404 | int ret = 0; | |
405 | ||
406 | mutex_lock(&rport->mutex); | |
407 | ||
408 | if (rport->port_id == USB2PHY_PORT_OTG) { | |
409 | if (rport->mode != USB_DR_MODE_HOST) { | |
410 | /* clear bvalid status and enable bvalid detect irq */ | |
411 | ret = property_enable(rphy, | |
412 | &rport->port_cfg->bvalid_det_clr, | |
413 | true); | |
414 | if (ret) | |
415 | goto out; | |
416 | ||
417 | ret = property_enable(rphy, | |
418 | &rport->port_cfg->bvalid_det_en, | |
419 | true); | |
420 | if (ret) | |
421 | goto out; | |
422 | ||
423 | schedule_delayed_work(&rport->otg_sm_work, | |
424 | OTG_SCHEDULE_DELAY); | |
425 | } else { | |
426 | /* If OTG works in host only mode, do nothing. */ | |
427 | dev_dbg(&rport->phy->dev, "mode %d\n", rport->mode); | |
0e08d2a7 | 428 | } |
98898f3b WW |
429 | } else if (rport->port_id == USB2PHY_PORT_HOST) { |
430 | /* clear linestate and enable linestate detect irq */ | |
431 | ret = property_enable(rphy, &rport->port_cfg->ls_det_clr, true); | |
432 | if (ret) | |
433 | goto out; | |
434 | ||
435 | ret = property_enable(rphy, &rport->port_cfg->ls_det_en, true); | |
436 | if (ret) | |
437 | goto out; | |
0e08d2a7 | 438 | |
0e08d2a7 FW |
439 | schedule_delayed_work(&rport->sm_work, SCHEDULE_DELAY); |
440 | } | |
441 | ||
98898f3b WW |
442 | out: |
443 | mutex_unlock(&rport->mutex); | |
444 | return ret; | |
0e08d2a7 FW |
445 | } |
446 | ||
447 | static int rockchip_usb2phy_power_on(struct phy *phy) | |
448 | { | |
449 | struct rockchip_usb2phy_port *rport = phy_get_drvdata(phy); | |
450 | struct rockchip_usb2phy *rphy = dev_get_drvdata(phy->dev.parent); | |
451 | int ret; | |
452 | ||
453 | dev_dbg(&rport->phy->dev, "port power on\n"); | |
454 | ||
455 | if (!rport->suspended) | |
456 | return 0; | |
457 | ||
458 | ret = clk_prepare_enable(rphy->clk480m); | |
459 | if (ret) | |
460 | return ret; | |
461 | ||
462 | ret = property_enable(rphy, &rport->port_cfg->phy_sus, false); | |
463 | if (ret) | |
464 | return ret; | |
465 | ||
466 | rport->suspended = false; | |
467 | return 0; | |
468 | } | |
469 | ||
470 | static int rockchip_usb2phy_power_off(struct phy *phy) | |
471 | { | |
472 | struct rockchip_usb2phy_port *rport = phy_get_drvdata(phy); | |
473 | struct rockchip_usb2phy *rphy = dev_get_drvdata(phy->dev.parent); | |
474 | int ret; | |
475 | ||
476 | dev_dbg(&rport->phy->dev, "port power off\n"); | |
477 | ||
478 | if (rport->suspended) | |
479 | return 0; | |
480 | ||
481 | ret = property_enable(rphy, &rport->port_cfg->phy_sus, true); | |
482 | if (ret) | |
483 | return ret; | |
484 | ||
485 | rport->suspended = true; | |
486 | clk_disable_unprepare(rphy->clk480m); | |
487 | ||
488 | return 0; | |
489 | } | |
490 | ||
491 | static int rockchip_usb2phy_exit(struct phy *phy) | |
492 | { | |
493 | struct rockchip_usb2phy_port *rport = phy_get_drvdata(phy); | |
494 | ||
98898f3b WW |
495 | if (rport->port_id == USB2PHY_PORT_OTG && |
496 | rport->mode != USB_DR_MODE_HOST) { | |
497 | cancel_delayed_work_sync(&rport->otg_sm_work); | |
498 | cancel_delayed_work_sync(&rport->chg_work); | |
499 | } else if (rport->port_id == USB2PHY_PORT_HOST) | |
0e08d2a7 FW |
500 | cancel_delayed_work_sync(&rport->sm_work); |
501 | ||
502 | return 0; | |
503 | } | |
504 | ||
505 | static const struct phy_ops rockchip_usb2phy_ops = { | |
506 | .init = rockchip_usb2phy_init, | |
507 | .exit = rockchip_usb2phy_exit, | |
508 | .power_on = rockchip_usb2phy_power_on, | |
509 | .power_off = rockchip_usb2phy_power_off, | |
510 | .owner = THIS_MODULE, | |
511 | }; | |
512 | ||
98898f3b WW |
513 | static void rockchip_usb2phy_otg_sm_work(struct work_struct *work) |
514 | { | |
515 | struct rockchip_usb2phy_port *rport = | |
516 | container_of(work, struct rockchip_usb2phy_port, | |
517 | otg_sm_work.work); | |
518 | struct rockchip_usb2phy *rphy = dev_get_drvdata(rport->phy->dev.parent); | |
519 | static unsigned int cable; | |
520 | unsigned long delay; | |
521 | bool vbus_attach, sch_work, notify_charger; | |
522 | ||
523 | if (rport->utmi_avalid) | |
524 | vbus_attach = | |
525 | property_enabled(rphy, &rport->port_cfg->utmi_avalid); | |
526 | else | |
527 | vbus_attach = | |
528 | property_enabled(rphy, &rport->port_cfg->utmi_bvalid); | |
529 | ||
530 | sch_work = false; | |
531 | notify_charger = false; | |
532 | delay = OTG_SCHEDULE_DELAY; | |
533 | dev_dbg(&rport->phy->dev, "%s otg sm work\n", | |
534 | usb_otg_state_string(rport->state)); | |
535 | ||
536 | switch (rport->state) { | |
537 | case OTG_STATE_UNDEFINED: | |
538 | rport->state = OTG_STATE_B_IDLE; | |
539 | if (!vbus_attach) | |
540 | rockchip_usb2phy_power_off(rport->phy); | |
541 | /* fall through */ | |
542 | case OTG_STATE_B_IDLE: | |
543 | if (extcon_get_cable_state_(rphy->edev, EXTCON_USB_HOST) > 0) { | |
544 | dev_dbg(&rport->phy->dev, "usb otg host connect\n"); | |
545 | rport->state = OTG_STATE_A_HOST; | |
546 | rockchip_usb2phy_power_on(rport->phy); | |
547 | return; | |
548 | } else if (vbus_attach) { | |
549 | dev_dbg(&rport->phy->dev, "vbus_attach\n"); | |
550 | switch (rphy->chg_state) { | |
551 | case USB_CHG_STATE_UNDEFINED: | |
552 | schedule_delayed_work(&rport->chg_work, 0); | |
553 | return; | |
554 | case USB_CHG_STATE_DETECTED: | |
555 | switch (rphy->chg_type) { | |
556 | case POWER_SUPPLY_TYPE_USB: | |
557 | dev_dbg(&rport->phy->dev, | |
558 | "sdp cable is connecetd\n"); | |
559 | rockchip_usb2phy_power_on(rport->phy); | |
560 | rport->state = OTG_STATE_B_PERIPHERAL; | |
561 | notify_charger = true; | |
562 | sch_work = true; | |
563 | cable = EXTCON_CHG_USB_SDP; | |
564 | break; | |
565 | case POWER_SUPPLY_TYPE_USB_DCP: | |
566 | dev_dbg(&rport->phy->dev, | |
567 | "dcp cable is connecetd\n"); | |
568 | rockchip_usb2phy_power_off(rport->phy); | |
569 | notify_charger = true; | |
570 | sch_work = true; | |
571 | cable = EXTCON_CHG_USB_DCP; | |
572 | break; | |
573 | case POWER_SUPPLY_TYPE_USB_CDP: | |
574 | dev_dbg(&rport->phy->dev, | |
575 | "cdp cable is connecetd\n"); | |
576 | rockchip_usb2phy_power_on(rport->phy); | |
577 | rport->state = OTG_STATE_B_PERIPHERAL; | |
578 | notify_charger = true; | |
579 | sch_work = true; | |
580 | cable = EXTCON_CHG_USB_CDP; | |
581 | break; | |
582 | default: | |
583 | break; | |
584 | } | |
585 | break; | |
586 | default: | |
587 | break; | |
588 | } | |
589 | } else { | |
590 | notify_charger = true; | |
591 | rphy->chg_state = USB_CHG_STATE_UNDEFINED; | |
592 | rphy->chg_type = POWER_SUPPLY_TYPE_UNKNOWN; | |
593 | } | |
594 | ||
595 | if (rport->vbus_attached != vbus_attach) { | |
596 | rport->vbus_attached = vbus_attach; | |
597 | ||
598 | if (notify_charger && rphy->edev) | |
599 | extcon_set_cable_state_(rphy->edev, | |
600 | cable, vbus_attach); | |
601 | } | |
602 | break; | |
603 | case OTG_STATE_B_PERIPHERAL: | |
604 | if (!vbus_attach) { | |
605 | dev_dbg(&rport->phy->dev, "usb disconnect\n"); | |
606 | rphy->chg_state = USB_CHG_STATE_UNDEFINED; | |
607 | rphy->chg_type = POWER_SUPPLY_TYPE_UNKNOWN; | |
608 | rport->state = OTG_STATE_B_IDLE; | |
609 | delay = 0; | |
610 | rockchip_usb2phy_power_off(rport->phy); | |
611 | } | |
612 | sch_work = true; | |
613 | break; | |
614 | case OTG_STATE_A_HOST: | |
615 | if (extcon_get_cable_state_(rphy->edev, EXTCON_USB_HOST) == 0) { | |
616 | dev_dbg(&rport->phy->dev, "usb otg host disconnect\n"); | |
617 | rport->state = OTG_STATE_B_IDLE; | |
618 | rockchip_usb2phy_power_off(rport->phy); | |
619 | } | |
620 | break; | |
621 | default: | |
622 | break; | |
623 | } | |
624 | ||
625 | if (sch_work) | |
626 | schedule_delayed_work(&rport->otg_sm_work, delay); | |
627 | } | |
628 | ||
629 | static const char *chg_to_string(enum power_supply_type chg_type) | |
630 | { | |
631 | switch (chg_type) { | |
632 | case POWER_SUPPLY_TYPE_USB: | |
633 | return "USB_SDP_CHARGER"; | |
634 | case POWER_SUPPLY_TYPE_USB_DCP: | |
635 | return "USB_DCP_CHARGER"; | |
636 | case POWER_SUPPLY_TYPE_USB_CDP: | |
637 | return "USB_CDP_CHARGER"; | |
638 | default: | |
639 | return "INVALID_CHARGER"; | |
640 | } | |
641 | } | |
642 | ||
643 | static void rockchip_chg_enable_dcd(struct rockchip_usb2phy *rphy, | |
644 | bool en) | |
645 | { | |
646 | property_enable(rphy, &rphy->phy_cfg->chg_det.rdm_pdwn_en, en); | |
647 | property_enable(rphy, &rphy->phy_cfg->chg_det.idp_src_en, en); | |
648 | } | |
649 | ||
650 | static void rockchip_chg_enable_primary_det(struct rockchip_usb2phy *rphy, | |
651 | bool en) | |
652 | { | |
653 | property_enable(rphy, &rphy->phy_cfg->chg_det.vdp_src_en, en); | |
654 | property_enable(rphy, &rphy->phy_cfg->chg_det.idm_sink_en, en); | |
655 | } | |
656 | ||
657 | static void rockchip_chg_enable_secondary_det(struct rockchip_usb2phy *rphy, | |
658 | bool en) | |
659 | { | |
660 | property_enable(rphy, &rphy->phy_cfg->chg_det.vdm_src_en, en); | |
661 | property_enable(rphy, &rphy->phy_cfg->chg_det.idp_sink_en, en); | |
662 | } | |
663 | ||
664 | #define CHG_DCD_POLL_TIME (100 * HZ / 1000) | |
665 | #define CHG_DCD_MAX_RETRIES 6 | |
666 | #define CHG_PRIMARY_DET_TIME (40 * HZ / 1000) | |
667 | #define CHG_SECONDARY_DET_TIME (40 * HZ / 1000) | |
668 | static void rockchip_chg_detect_work(struct work_struct *work) | |
669 | { | |
670 | struct rockchip_usb2phy_port *rport = | |
671 | container_of(work, struct rockchip_usb2phy_port, chg_work.work); | |
672 | struct rockchip_usb2phy *rphy = dev_get_drvdata(rport->phy->dev.parent); | |
673 | bool is_dcd, tmout, vout; | |
674 | unsigned long delay; | |
675 | ||
676 | dev_dbg(&rport->phy->dev, "chg detection work state = %d\n", | |
677 | rphy->chg_state); | |
678 | switch (rphy->chg_state) { | |
679 | case USB_CHG_STATE_UNDEFINED: | |
680 | if (!rport->suspended) | |
681 | rockchip_usb2phy_power_off(rport->phy); | |
682 | /* put the controller in non-driving mode */ | |
683 | property_enable(rphy, &rphy->phy_cfg->chg_det.opmode, false); | |
684 | /* Start DCD processing stage 1 */ | |
685 | rockchip_chg_enable_dcd(rphy, true); | |
686 | rphy->chg_state = USB_CHG_STATE_WAIT_FOR_DCD; | |
687 | rphy->dcd_retries = 0; | |
688 | delay = CHG_DCD_POLL_TIME; | |
689 | break; | |
690 | case USB_CHG_STATE_WAIT_FOR_DCD: | |
691 | /* get data contact detection status */ | |
692 | is_dcd = property_enabled(rphy, &rphy->phy_cfg->chg_det.dp_det); | |
693 | tmout = ++rphy->dcd_retries == CHG_DCD_MAX_RETRIES; | |
694 | /* stage 2 */ | |
695 | if (is_dcd || tmout) { | |
696 | /* stage 4 */ | |
697 | /* Turn off DCD circuitry */ | |
698 | rockchip_chg_enable_dcd(rphy, false); | |
699 | /* Voltage Source on DP, Probe on DM */ | |
700 | rockchip_chg_enable_primary_det(rphy, true); | |
701 | delay = CHG_PRIMARY_DET_TIME; | |
702 | rphy->chg_state = USB_CHG_STATE_DCD_DONE; | |
703 | } else { | |
704 | /* stage 3 */ | |
705 | delay = CHG_DCD_POLL_TIME; | |
706 | } | |
707 | break; | |
708 | case USB_CHG_STATE_DCD_DONE: | |
709 | vout = property_enabled(rphy, &rphy->phy_cfg->chg_det.cp_det); | |
710 | rockchip_chg_enable_primary_det(rphy, false); | |
711 | if (vout) { | |
712 | /* Voltage Source on DM, Probe on DP */ | |
713 | rockchip_chg_enable_secondary_det(rphy, true); | |
714 | delay = CHG_SECONDARY_DET_TIME; | |
715 | rphy->chg_state = USB_CHG_STATE_PRIMARY_DONE; | |
716 | } else { | |
dd796e92 | 717 | if (rphy->dcd_retries == CHG_DCD_MAX_RETRIES) { |
98898f3b WW |
718 | /* floating charger found */ |
719 | rphy->chg_type = POWER_SUPPLY_TYPE_USB_DCP; | |
720 | rphy->chg_state = USB_CHG_STATE_DETECTED; | |
721 | delay = 0; | |
722 | } else { | |
723 | rphy->chg_type = POWER_SUPPLY_TYPE_USB; | |
724 | rphy->chg_state = USB_CHG_STATE_DETECTED; | |
725 | delay = 0; | |
726 | } | |
727 | } | |
728 | break; | |
729 | case USB_CHG_STATE_PRIMARY_DONE: | |
730 | vout = property_enabled(rphy, &rphy->phy_cfg->chg_det.dcp_det); | |
731 | /* Turn off voltage source */ | |
732 | rockchip_chg_enable_secondary_det(rphy, false); | |
733 | if (vout) | |
734 | rphy->chg_type = POWER_SUPPLY_TYPE_USB_DCP; | |
735 | else | |
736 | rphy->chg_type = POWER_SUPPLY_TYPE_USB_CDP; | |
737 | /* fall through */ | |
738 | case USB_CHG_STATE_SECONDARY_DONE: | |
739 | rphy->chg_state = USB_CHG_STATE_DETECTED; | |
740 | delay = 0; | |
741 | /* fall through */ | |
742 | case USB_CHG_STATE_DETECTED: | |
743 | /* put the controller in normal mode */ | |
744 | property_enable(rphy, &rphy->phy_cfg->chg_det.opmode, true); | |
745 | rockchip_usb2phy_otg_sm_work(&rport->otg_sm_work.work); | |
746 | dev_info(&rport->phy->dev, "charger = %s\n", | |
747 | chg_to_string(rphy->chg_type)); | |
748 | return; | |
749 | default: | |
750 | return; | |
751 | } | |
752 | ||
753 | schedule_delayed_work(&rport->chg_work, delay); | |
754 | } | |
755 | ||
0e08d2a7 FW |
756 | /* |
757 | * The function manage host-phy port state and suspend/resume phy port | |
758 | * to save power. | |
759 | * | |
760 | * we rely on utmi_linestate and utmi_hostdisconnect to identify whether | |
761 | * devices is disconnect or not. Besides, we do not need care it is FS/LS | |
762 | * disconnected or HS disconnected, actually, we just only need get the | |
763 | * device is disconnected at last through rearm the delayed work, | |
764 | * to suspend the phy port in _PHY_STATE_DISCONNECT_ case. | |
765 | * | |
766 | * NOTE: It may invoke *phy_powr_off or *phy_power_on which will invoke | |
767 | * some clk related APIs, so do not invoke it from interrupt context directly. | |
768 | */ | |
769 | static void rockchip_usb2phy_sm_work(struct work_struct *work) | |
770 | { | |
771 | struct rockchip_usb2phy_port *rport = | |
772 | container_of(work, struct rockchip_usb2phy_port, sm_work.work); | |
773 | struct rockchip_usb2phy *rphy = dev_get_drvdata(rport->phy->dev.parent); | |
774 | unsigned int sh = rport->port_cfg->utmi_hstdet.bitend - | |
775 | rport->port_cfg->utmi_hstdet.bitstart + 1; | |
776 | unsigned int ul, uhd, state; | |
777 | unsigned int ul_mask, uhd_mask; | |
778 | int ret; | |
779 | ||
780 | mutex_lock(&rport->mutex); | |
781 | ||
782 | ret = regmap_read(rphy->grf, rport->port_cfg->utmi_ls.offset, &ul); | |
783 | if (ret < 0) | |
784 | goto next_schedule; | |
785 | ||
786 | ret = regmap_read(rphy->grf, rport->port_cfg->utmi_hstdet.offset, | |
787 | &uhd); | |
788 | if (ret < 0) | |
789 | goto next_schedule; | |
790 | ||
791 | uhd_mask = GENMASK(rport->port_cfg->utmi_hstdet.bitend, | |
792 | rport->port_cfg->utmi_hstdet.bitstart); | |
793 | ul_mask = GENMASK(rport->port_cfg->utmi_ls.bitend, | |
794 | rport->port_cfg->utmi_ls.bitstart); | |
795 | ||
796 | /* stitch on utmi_ls and utmi_hstdet as phy state */ | |
797 | state = ((uhd & uhd_mask) >> rport->port_cfg->utmi_hstdet.bitstart) | | |
798 | (((ul & ul_mask) >> rport->port_cfg->utmi_ls.bitstart) << sh); | |
799 | ||
800 | switch (state) { | |
801 | case PHY_STATE_HS_ONLINE: | |
802 | dev_dbg(&rport->phy->dev, "HS online\n"); | |
803 | break; | |
804 | case PHY_STATE_FS_LS_ONLINE: | |
805 | /* | |
806 | * For FS/LS device, the online state share with connect state | |
807 | * from utmi_ls and utmi_hstdet register, so we distinguish | |
808 | * them via suspended flag. | |
809 | * | |
810 | * Plus, there are two cases, one is D- Line pull-up, and D+ | |
811 | * line pull-down, the state is 4; another is D+ line pull-up, | |
812 | * and D- line pull-down, the state is 2. | |
813 | */ | |
814 | if (!rport->suspended) { | |
815 | /* D- line pull-up, D+ line pull-down */ | |
816 | dev_dbg(&rport->phy->dev, "FS/LS online\n"); | |
817 | break; | |
818 | } | |
819 | /* fall through */ | |
820 | case PHY_STATE_CONNECT: | |
821 | if (rport->suspended) { | |
822 | dev_dbg(&rport->phy->dev, "Connected\n"); | |
823 | rockchip_usb2phy_power_on(rport->phy); | |
824 | rport->suspended = false; | |
825 | } else { | |
826 | /* D+ line pull-up, D- line pull-down */ | |
827 | dev_dbg(&rport->phy->dev, "FS/LS online\n"); | |
828 | } | |
829 | break; | |
830 | case PHY_STATE_DISCONNECT: | |
831 | if (!rport->suspended) { | |
832 | dev_dbg(&rport->phy->dev, "Disconnected\n"); | |
833 | rockchip_usb2phy_power_off(rport->phy); | |
834 | rport->suspended = true; | |
835 | } | |
836 | ||
837 | /* | |
838 | * activate the linestate detection to get the next device | |
839 | * plug-in irq. | |
840 | */ | |
841 | property_enable(rphy, &rport->port_cfg->ls_det_clr, true); | |
842 | property_enable(rphy, &rport->port_cfg->ls_det_en, true); | |
843 | ||
844 | /* | |
845 | * we don't need to rearm the delayed work when the phy port | |
846 | * is suspended. | |
847 | */ | |
848 | mutex_unlock(&rport->mutex); | |
849 | return; | |
850 | default: | |
851 | dev_dbg(&rport->phy->dev, "unknown phy state\n"); | |
852 | break; | |
853 | } | |
854 | ||
855 | next_schedule: | |
856 | mutex_unlock(&rport->mutex); | |
857 | schedule_delayed_work(&rport->sm_work, SCHEDULE_DELAY); | |
858 | } | |
859 | ||
860 | static irqreturn_t rockchip_usb2phy_linestate_irq(int irq, void *data) | |
861 | { | |
862 | struct rockchip_usb2phy_port *rport = data; | |
863 | struct rockchip_usb2phy *rphy = dev_get_drvdata(rport->phy->dev.parent); | |
864 | ||
865 | if (!property_enabled(rphy, &rport->port_cfg->ls_det_st)) | |
866 | return IRQ_NONE; | |
867 | ||
868 | mutex_lock(&rport->mutex); | |
869 | ||
870 | /* disable linestate detect irq and clear its status */ | |
871 | property_enable(rphy, &rport->port_cfg->ls_det_en, false); | |
872 | property_enable(rphy, &rport->port_cfg->ls_det_clr, true); | |
873 | ||
874 | mutex_unlock(&rport->mutex); | |
875 | ||
876 | /* | |
877 | * In this case for host phy port, a new device is plugged in, | |
878 | * meanwhile, if the phy port is suspended, we need rearm the work to | |
879 | * resume it and mange its states; otherwise, we do nothing about that. | |
880 | */ | |
881 | if (rport->suspended && rport->port_id == USB2PHY_PORT_HOST) | |
882 | rockchip_usb2phy_sm_work(&rport->sm_work.work); | |
883 | ||
884 | return IRQ_HANDLED; | |
885 | } | |
886 | ||
98898f3b WW |
887 | static irqreturn_t rockchip_usb2phy_bvalid_irq(int irq, void *data) |
888 | { | |
889 | struct rockchip_usb2phy_port *rport = data; | |
890 | struct rockchip_usb2phy *rphy = dev_get_drvdata(rport->phy->dev.parent); | |
891 | ||
892 | if (!property_enabled(rphy, &rport->port_cfg->bvalid_det_st)) | |
893 | return IRQ_NONE; | |
894 | ||
895 | mutex_lock(&rport->mutex); | |
896 | ||
897 | /* clear bvalid detect irq pending status */ | |
898 | property_enable(rphy, &rport->port_cfg->bvalid_det_clr, true); | |
899 | ||
900 | mutex_unlock(&rport->mutex); | |
901 | ||
902 | rockchip_usb2phy_otg_sm_work(&rport->otg_sm_work.work); | |
903 | ||
904 | return IRQ_HANDLED; | |
905 | } | |
906 | ||
0e08d2a7 FW |
907 | static int rockchip_usb2phy_host_port_init(struct rockchip_usb2phy *rphy, |
908 | struct rockchip_usb2phy_port *rport, | |
909 | struct device_node *child_np) | |
910 | { | |
911 | int ret; | |
912 | ||
913 | rport->port_id = USB2PHY_PORT_HOST; | |
914 | rport->port_cfg = &rphy->phy_cfg->port_cfgs[USB2PHY_PORT_HOST]; | |
915 | rport->suspended = true; | |
916 | ||
917 | mutex_init(&rport->mutex); | |
918 | INIT_DELAYED_WORK(&rport->sm_work, rockchip_usb2phy_sm_work); | |
919 | ||
920 | rport->ls_irq = of_irq_get_byname(child_np, "linestate"); | |
921 | if (rport->ls_irq < 0) { | |
922 | dev_err(rphy->dev, "no linestate irq provided\n"); | |
923 | return rport->ls_irq; | |
924 | } | |
925 | ||
926 | ret = devm_request_threaded_irq(rphy->dev, rport->ls_irq, NULL, | |
927 | rockchip_usb2phy_linestate_irq, | |
928 | IRQF_ONESHOT, | |
929 | "rockchip_usb2phy", rport); | |
930 | if (ret) { | |
98898f3b | 931 | dev_err(rphy->dev, "failed to request linestate irq handle\n"); |
0e08d2a7 FW |
932 | return ret; |
933 | } | |
934 | ||
935 | return 0; | |
936 | } | |
937 | ||
98898f3b WW |
938 | static int rockchip_otg_event(struct notifier_block *nb, |
939 | unsigned long event, void *ptr) | |
940 | { | |
941 | struct rockchip_usb2phy_port *rport = | |
942 | container_of(nb, struct rockchip_usb2phy_port, event_nb); | |
943 | ||
944 | schedule_delayed_work(&rport->otg_sm_work, OTG_SCHEDULE_DELAY); | |
945 | ||
946 | return NOTIFY_DONE; | |
947 | } | |
948 | ||
949 | static int rockchip_usb2phy_otg_port_init(struct rockchip_usb2phy *rphy, | |
950 | struct rockchip_usb2phy_port *rport, | |
951 | struct device_node *child_np) | |
952 | { | |
953 | int ret; | |
954 | ||
955 | rport->port_id = USB2PHY_PORT_OTG; | |
956 | rport->port_cfg = &rphy->phy_cfg->port_cfgs[USB2PHY_PORT_OTG]; | |
957 | rport->state = OTG_STATE_UNDEFINED; | |
958 | ||
959 | /* | |
960 | * set suspended flag to true, but actually don't | |
961 | * put phy in suspend mode, it aims to enable usb | |
962 | * phy and clock in power_on() called by usb controller | |
963 | * driver during probe. | |
964 | */ | |
965 | rport->suspended = true; | |
966 | rport->vbus_attached = false; | |
967 | ||
968 | mutex_init(&rport->mutex); | |
969 | ||
970 | rport->mode = of_usb_get_dr_mode_by_phy(child_np, -1); | |
971 | if (rport->mode == USB_DR_MODE_HOST) { | |
972 | ret = 0; | |
973 | goto out; | |
974 | } | |
975 | ||
976 | INIT_DELAYED_WORK(&rport->chg_work, rockchip_chg_detect_work); | |
977 | INIT_DELAYED_WORK(&rport->otg_sm_work, rockchip_usb2phy_otg_sm_work); | |
978 | ||
979 | rport->utmi_avalid = | |
980 | of_property_read_bool(child_np, "rockchip,utmi-avalid"); | |
981 | ||
982 | rport->bvalid_irq = of_irq_get_byname(child_np, "otg-bvalid"); | |
983 | if (rport->bvalid_irq < 0) { | |
984 | dev_err(rphy->dev, "no vbus valid irq provided\n"); | |
985 | ret = rport->bvalid_irq; | |
986 | goto out; | |
987 | } | |
988 | ||
989 | ret = devm_request_threaded_irq(rphy->dev, rport->bvalid_irq, NULL, | |
990 | rockchip_usb2phy_bvalid_irq, | |
991 | IRQF_ONESHOT, | |
992 | "rockchip_usb2phy_bvalid", rport); | |
993 | if (ret) { | |
994 | dev_err(rphy->dev, "failed to request otg-bvalid irq handle\n"); | |
995 | goto out; | |
996 | } | |
997 | ||
998 | if (!IS_ERR(rphy->edev)) { | |
999 | rport->event_nb.notifier_call = rockchip_otg_event; | |
1000 | ||
1001 | ret = extcon_register_notifier(rphy->edev, EXTCON_USB_HOST, | |
1002 | &rport->event_nb); | |
1003 | if (ret) | |
1004 | dev_err(rphy->dev, "register USB HOST notifier failed\n"); | |
1005 | } | |
1006 | ||
1007 | out: | |
1008 | return ret; | |
1009 | } | |
1010 | ||
0e08d2a7 FW |
1011 | static int rockchip_usb2phy_probe(struct platform_device *pdev) |
1012 | { | |
1013 | struct device *dev = &pdev->dev; | |
1014 | struct device_node *np = dev->of_node; | |
1015 | struct device_node *child_np; | |
1016 | struct phy_provider *provider; | |
1017 | struct rockchip_usb2phy *rphy; | |
1018 | const struct rockchip_usb2phy_cfg *phy_cfgs; | |
1019 | const struct of_device_id *match; | |
1020 | unsigned int reg; | |
1021 | int index, ret; | |
1022 | ||
1023 | rphy = devm_kzalloc(dev, sizeof(*rphy), GFP_KERNEL); | |
1024 | if (!rphy) | |
1025 | return -ENOMEM; | |
1026 | ||
1027 | match = of_match_device(dev->driver->of_match_table, dev); | |
1028 | if (!match || !match->data) { | |
1029 | dev_err(dev, "phy configs are not assigned!\n"); | |
1030 | return -EINVAL; | |
1031 | } | |
1032 | ||
1033 | if (!dev->parent || !dev->parent->of_node) | |
1034 | return -EINVAL; | |
1035 | ||
1036 | rphy->grf = syscon_node_to_regmap(dev->parent->of_node); | |
1037 | if (IS_ERR(rphy->grf)) | |
1038 | return PTR_ERR(rphy->grf); | |
1039 | ||
1040 | if (of_property_read_u32(np, "reg", ®)) { | |
1041 | dev_err(dev, "the reg property is not assigned in %s node\n", | |
1042 | np->name); | |
1043 | return -EINVAL; | |
1044 | } | |
1045 | ||
1046 | rphy->dev = dev; | |
1047 | phy_cfgs = match->data; | |
98898f3b WW |
1048 | rphy->chg_state = USB_CHG_STATE_UNDEFINED; |
1049 | rphy->chg_type = POWER_SUPPLY_TYPE_UNKNOWN; | |
0e08d2a7 FW |
1050 | platform_set_drvdata(pdev, rphy); |
1051 | ||
98898f3b WW |
1052 | ret = rockchip_usb2phy_extcon_register(rphy); |
1053 | if (ret) | |
1054 | return ret; | |
1055 | ||
0e08d2a7 FW |
1056 | /* find out a proper config which can be matched with dt. */ |
1057 | index = 0; | |
1058 | while (phy_cfgs[index].reg) { | |
1059 | if (phy_cfgs[index].reg == reg) { | |
1060 | rphy->phy_cfg = &phy_cfgs[index]; | |
1061 | break; | |
1062 | } | |
1063 | ||
1064 | ++index; | |
1065 | } | |
1066 | ||
1067 | if (!rphy->phy_cfg) { | |
1068 | dev_err(dev, "no phy-config can be matched with %s node\n", | |
1069 | np->name); | |
1070 | return -EINVAL; | |
1071 | } | |
1072 | ||
1073 | rphy->clk = of_clk_get_by_name(np, "phyclk"); | |
1074 | if (!IS_ERR(rphy->clk)) { | |
1075 | clk_prepare_enable(rphy->clk); | |
1076 | } else { | |
1077 | dev_info(&pdev->dev, "no phyclk specified\n"); | |
1078 | rphy->clk = NULL; | |
1079 | } | |
1080 | ||
1081 | ret = rockchip_usb2phy_clk480m_register(rphy); | |
1082 | if (ret) { | |
1083 | dev_err(dev, "failed to register 480m output clock\n"); | |
1084 | goto disable_clks; | |
1085 | } | |
1086 | ||
1087 | index = 0; | |
1088 | for_each_available_child_of_node(np, child_np) { | |
1089 | struct rockchip_usb2phy_port *rport = &rphy->ports[index]; | |
1090 | struct phy *phy; | |
1091 | ||
98898f3b WW |
1092 | /* This driver aims to support both otg-port and host-port */ |
1093 | if (of_node_cmp(child_np->name, "host-port") && | |
1094 | of_node_cmp(child_np->name, "otg-port")) | |
0e08d2a7 FW |
1095 | goto next_child; |
1096 | ||
1097 | phy = devm_phy_create(dev, child_np, &rockchip_usb2phy_ops); | |
1098 | if (IS_ERR(phy)) { | |
1099 | dev_err(dev, "failed to create phy\n"); | |
1100 | ret = PTR_ERR(phy); | |
1101 | goto put_child; | |
1102 | } | |
1103 | ||
1104 | rport->phy = phy; | |
1105 | phy_set_drvdata(rport->phy, rport); | |
1106 | ||
98898f3b WW |
1107 | /* initialize otg/host port separately */ |
1108 | if (!of_node_cmp(child_np->name, "host-port")) { | |
1109 | ret = rockchip_usb2phy_host_port_init(rphy, rport, | |
1110 | child_np); | |
1111 | if (ret) | |
1112 | goto put_child; | |
1113 | } else { | |
1114 | ret = rockchip_usb2phy_otg_port_init(rphy, rport, | |
1115 | child_np); | |
1116 | if (ret) | |
1117 | goto put_child; | |
1118 | } | |
0e08d2a7 FW |
1119 | |
1120 | next_child: | |
1121 | /* to prevent out of boundary */ | |
1122 | if (++index >= rphy->phy_cfg->num_ports) | |
1123 | break; | |
1124 | } | |
1125 | ||
1126 | provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); | |
1127 | return PTR_ERR_OR_ZERO(provider); | |
1128 | ||
1129 | put_child: | |
1130 | of_node_put(child_np); | |
1131 | disable_clks: | |
1132 | if (rphy->clk) { | |
1133 | clk_disable_unprepare(rphy->clk); | |
1134 | clk_put(rphy->clk); | |
1135 | } | |
1136 | return ret; | |
1137 | } | |
1138 | ||
1139 | static const struct rockchip_usb2phy_cfg rk3366_phy_cfgs[] = { | |
1140 | { | |
1141 | .reg = 0x700, | |
1142 | .num_ports = 2, | |
1143 | .clkout_ctl = { 0x0724, 15, 15, 1, 0 }, | |
1144 | .port_cfgs = { | |
1145 | [USB2PHY_PORT_HOST] = { | |
1146 | .phy_sus = { 0x0728, 15, 0, 0, 0x1d1 }, | |
1147 | .ls_det_en = { 0x0680, 4, 4, 0, 1 }, | |
1148 | .ls_det_st = { 0x0690, 4, 4, 0, 1 }, | |
1149 | .ls_det_clr = { 0x06a0, 4, 4, 0, 1 }, | |
1150 | .utmi_ls = { 0x049c, 14, 13, 0, 1 }, | |
1151 | .utmi_hstdet = { 0x049c, 12, 12, 0, 1 } | |
1152 | } | |
1153 | }, | |
1154 | }, | |
1155 | { /* sentinel */ } | |
1156 | }; | |
1157 | ||
1158 | static const struct rockchip_usb2phy_cfg rk3399_phy_cfgs[] = { | |
1159 | { | |
98898f3b | 1160 | .reg = 0xe450, |
0e08d2a7 FW |
1161 | .num_ports = 2, |
1162 | .clkout_ctl = { 0xe450, 4, 4, 1, 0 }, | |
1163 | .port_cfgs = { | |
98898f3b WW |
1164 | [USB2PHY_PORT_OTG] = { |
1165 | .phy_sus = { 0xe454, 1, 0, 2, 1 }, | |
1166 | .bvalid_det_en = { 0xe3c0, 3, 3, 0, 1 }, | |
1167 | .bvalid_det_st = { 0xe3e0, 3, 3, 0, 1 }, | |
1168 | .bvalid_det_clr = { 0xe3d0, 3, 3, 0, 1 }, | |
1169 | .utmi_avalid = { 0xe2ac, 7, 7, 0, 1 }, | |
1170 | .utmi_bvalid = { 0xe2ac, 12, 12, 0, 1 }, | |
1171 | }, | |
0e08d2a7 FW |
1172 | [USB2PHY_PORT_HOST] = { |
1173 | .phy_sus = { 0xe458, 1, 0, 0x2, 0x1 }, | |
1174 | .ls_det_en = { 0xe3c0, 6, 6, 0, 1 }, | |
1175 | .ls_det_st = { 0xe3e0, 6, 6, 0, 1 }, | |
1176 | .ls_det_clr = { 0xe3d0, 6, 6, 0, 1 }, | |
1177 | .utmi_ls = { 0xe2ac, 22, 21, 0, 1 }, | |
1178 | .utmi_hstdet = { 0xe2ac, 23, 23, 0, 1 } | |
1179 | } | |
1180 | }, | |
98898f3b WW |
1181 | .chg_det = { |
1182 | .opmode = { 0xe454, 3, 0, 5, 1 }, | |
1183 | .cp_det = { 0xe2ac, 2, 2, 0, 1 }, | |
1184 | .dcp_det = { 0xe2ac, 1, 1, 0, 1 }, | |
1185 | .dp_det = { 0xe2ac, 0, 0, 0, 1 }, | |
1186 | .idm_sink_en = { 0xe450, 8, 8, 0, 1 }, | |
1187 | .idp_sink_en = { 0xe450, 7, 7, 0, 1 }, | |
1188 | .idp_src_en = { 0xe450, 9, 9, 0, 1 }, | |
1189 | .rdm_pdwn_en = { 0xe450, 10, 10, 0, 1 }, | |
1190 | .vdm_src_en = { 0xe450, 12, 12, 0, 1 }, | |
1191 | .vdp_src_en = { 0xe450, 11, 11, 0, 1 }, | |
1192 | }, | |
0e08d2a7 FW |
1193 | }, |
1194 | { | |
98898f3b | 1195 | .reg = 0xe460, |
0e08d2a7 FW |
1196 | .num_ports = 2, |
1197 | .clkout_ctl = { 0xe460, 4, 4, 1, 0 }, | |
1198 | .port_cfgs = { | |
98898f3b WW |
1199 | [USB2PHY_PORT_OTG] = { |
1200 | .phy_sus = { 0xe464, 1, 0, 2, 1 }, | |
1201 | .bvalid_det_en = { 0xe3c0, 8, 8, 0, 1 }, | |
1202 | .bvalid_det_st = { 0xe3e0, 8, 8, 0, 1 }, | |
1203 | .bvalid_det_clr = { 0xe3d0, 8, 8, 0, 1 }, | |
1204 | .utmi_avalid = { 0xe2ac, 10, 10, 0, 1 }, | |
1205 | .utmi_bvalid = { 0xe2ac, 16, 16, 0, 1 }, | |
1206 | }, | |
0e08d2a7 FW |
1207 | [USB2PHY_PORT_HOST] = { |
1208 | .phy_sus = { 0xe468, 1, 0, 0x2, 0x1 }, | |
1209 | .ls_det_en = { 0xe3c0, 11, 11, 0, 1 }, | |
1210 | .ls_det_st = { 0xe3e0, 11, 11, 0, 1 }, | |
1211 | .ls_det_clr = { 0xe3d0, 11, 11, 0, 1 }, | |
1212 | .utmi_ls = { 0xe2ac, 26, 25, 0, 1 }, | |
1213 | .utmi_hstdet = { 0xe2ac, 27, 27, 0, 1 } | |
1214 | } | |
1215 | }, | |
1216 | }, | |
1217 | { /* sentinel */ } | |
1218 | }; | |
1219 | ||
1220 | static const struct of_device_id rockchip_usb2phy_dt_match[] = { | |
1221 | { .compatible = "rockchip,rk3366-usb2phy", .data = &rk3366_phy_cfgs }, | |
1222 | { .compatible = "rockchip,rk3399-usb2phy", .data = &rk3399_phy_cfgs }, | |
1223 | {} | |
1224 | }; | |
1225 | MODULE_DEVICE_TABLE(of, rockchip_usb2phy_dt_match); | |
1226 | ||
1227 | static struct platform_driver rockchip_usb2phy_driver = { | |
1228 | .probe = rockchip_usb2phy_probe, | |
1229 | .driver = { | |
1230 | .name = "rockchip-usb2phy", | |
1231 | .of_match_table = rockchip_usb2phy_dt_match, | |
1232 | }, | |
1233 | }; | |
1234 | module_platform_driver(rockchip_usb2phy_driver); | |
1235 | ||
1236 | MODULE_AUTHOR("Frank Wang <frank.wang@rock-chips.com>"); | |
1237 | MODULE_DESCRIPTION("Rockchip USB2.0 PHY driver"); | |
1238 | MODULE_LICENSE("GPL v2"); |