1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) 2016 Linaro Ltd |
4 | */ |
5 | #include <linux/module.h> |
6 | #include <linux/ulpi/driver.h> |
7 | #include <linux/ulpi/regs.h> |
8 | #include <linux/clk.h> |
9 | #include <linux/regulator/consumer.h> |
10 | #include <linux/of.h> |
11 | #include <linux/phy/phy.h> |
12 | #include <linux/reset.h> |
13 | #include <linux/extcon.h> |
14 | #include <linux/notifier.h> |
15 | |
16 | #define ULPI_PWR_CLK_MNG_REG 0x88 |
17 | # define ULPI_PWR_OTG_COMP_DISABLE BIT(0) |
18 | |
19 | #define ULPI_MISC_A 0x96 |
20 | # define ULPI_MISC_A_VBUSVLDEXTSEL BIT(1) |
21 | # define ULPI_MISC_A_VBUSVLDEXT BIT(0) |
22 | |
23 | |
24 | struct ulpi_seq { |
25 | u8 addr; |
26 | u8 val; |
27 | }; |
28 | |
29 | struct qcom_usb_hs_phy { |
30 | struct ulpi *ulpi; |
31 | struct phy *phy; |
32 | struct clk *ref_clk; |
33 | struct clk *sleep_clk; |
34 | struct regulator *v1p8; |
35 | struct regulator *v3p3; |
36 | struct reset_control *reset; |
37 | struct ulpi_seq *init_seq; |
38 | struct extcon_dev *vbus_edev; |
39 | struct notifier_block vbus_notify; |
40 | }; |
41 | |
42 | static int qcom_usb_hs_phy_set_mode(struct phy *phy, |
43 | enum phy_mode mode, int submode) |
44 | { |
45 | struct qcom_usb_hs_phy *uphy = phy_get_drvdata(phy); |
46 | u8 addr; |
47 | int ret; |
48 | |
49 | if (!uphy->vbus_edev) { |
50 | u8 val = 0; |
51 | |
52 | switch (mode) { |
53 | case PHY_MODE_USB_OTG: |
54 | case PHY_MODE_USB_HOST: |
55 | val |= ULPI_INT_IDGRD; |
56 | fallthrough; |
57 | case PHY_MODE_USB_DEVICE: |
58 | val |= ULPI_INT_SESS_VALID; |
59 | break; |
60 | default: |
61 | break; |
62 | } |
63 | |
64 | ret = ulpi_write(ulpi: uphy->ulpi, ULPI_USB_INT_EN_RISE, val); |
65 | if (ret) |
66 | return ret; |
67 | ret = ulpi_write(ulpi: uphy->ulpi, ULPI_USB_INT_EN_FALL, val); |
68 | } else { |
69 | switch (mode) { |
70 | case PHY_MODE_USB_OTG: |
71 | case PHY_MODE_USB_DEVICE: |
72 | addr = ULPI_SET(ULPI_MISC_A); |
73 | break; |
74 | case PHY_MODE_USB_HOST: |
75 | addr = ULPI_CLR(ULPI_MISC_A); |
76 | break; |
77 | default: |
78 | return -EINVAL; |
79 | } |
80 | |
81 | ret = ulpi_write(ulpi: uphy->ulpi, ULPI_SET(ULPI_PWR_CLK_MNG_REG), |
82 | ULPI_PWR_OTG_COMP_DISABLE); |
83 | if (ret) |
84 | return ret; |
85 | ret = ulpi_write(ulpi: uphy->ulpi, addr, ULPI_MISC_A_VBUSVLDEXTSEL); |
86 | } |
87 | |
88 | return ret; |
89 | } |
90 | |
91 | static int |
92 | qcom_usb_hs_phy_vbus_notifier(struct notifier_block *nb, unsigned long event, |
93 | void *ptr) |
94 | { |
95 | struct qcom_usb_hs_phy *uphy; |
96 | u8 addr; |
97 | |
98 | uphy = container_of(nb, struct qcom_usb_hs_phy, vbus_notify); |
99 | |
100 | if (event) |
101 | addr = ULPI_SET(ULPI_MISC_A); |
102 | else |
103 | addr = ULPI_CLR(ULPI_MISC_A); |
104 | |
105 | return ulpi_write(ulpi: uphy->ulpi, addr, ULPI_MISC_A_VBUSVLDEXT); |
106 | } |
107 | |
108 | static int qcom_usb_hs_phy_power_on(struct phy *phy) |
109 | { |
110 | struct qcom_usb_hs_phy *uphy = phy_get_drvdata(phy); |
111 | struct ulpi *ulpi = uphy->ulpi; |
112 | const struct ulpi_seq *seq; |
113 | int ret, state; |
114 | |
115 | ret = clk_prepare_enable(clk: uphy->ref_clk); |
116 | if (ret) |
117 | return ret; |
118 | |
119 | ret = clk_prepare_enable(clk: uphy->sleep_clk); |
120 | if (ret) |
121 | goto err_sleep; |
122 | |
123 | ret = regulator_set_load(regulator: uphy->v1p8, load_uA: 50000); |
124 | if (ret < 0) |
125 | goto err_1p8; |
126 | |
127 | ret = regulator_enable(regulator: uphy->v1p8); |
128 | if (ret) |
129 | goto err_1p8; |
130 | |
131 | ret = regulator_set_voltage_triplet(regulator: uphy->v3p3, min_uV: 3050000, target_uV: 3300000, |
132 | max_uV: 3300000); |
133 | if (ret) |
134 | goto err_3p3; |
135 | |
136 | ret = regulator_set_load(regulator: uphy->v3p3, load_uA: 50000); |
137 | if (ret < 0) |
138 | goto err_3p3; |
139 | |
140 | ret = regulator_enable(regulator: uphy->v3p3); |
141 | if (ret) |
142 | goto err_3p3; |
143 | |
144 | for (seq = uphy->init_seq; seq->addr; seq++) { |
145 | ret = ulpi_write(ulpi, ULPI_EXT_VENDOR_SPECIFIC + seq->addr, |
146 | val: seq->val); |
147 | if (ret) |
148 | goto err_ulpi; |
149 | } |
150 | |
151 | if (uphy->reset) { |
152 | ret = reset_control_reset(rstc: uphy->reset); |
153 | if (ret) |
154 | goto err_ulpi; |
155 | } |
156 | |
157 | if (uphy->vbus_edev) { |
158 | state = extcon_get_state(edev: uphy->vbus_edev, EXTCON_USB); |
159 | /* setup initial state */ |
160 | qcom_usb_hs_phy_vbus_notifier(nb: &uphy->vbus_notify, event: state, |
161 | ptr: uphy->vbus_edev); |
162 | ret = extcon_register_notifier(edev: uphy->vbus_edev, EXTCON_USB, |
163 | nb: &uphy->vbus_notify); |
164 | if (ret) |
165 | goto err_ulpi; |
166 | } |
167 | |
168 | return 0; |
169 | err_ulpi: |
170 | regulator_disable(regulator: uphy->v3p3); |
171 | err_3p3: |
172 | regulator_disable(regulator: uphy->v1p8); |
173 | err_1p8: |
174 | clk_disable_unprepare(clk: uphy->sleep_clk); |
175 | err_sleep: |
176 | clk_disable_unprepare(clk: uphy->ref_clk); |
177 | return ret; |
178 | } |
179 | |
180 | static int qcom_usb_hs_phy_power_off(struct phy *phy) |
181 | { |
182 | struct qcom_usb_hs_phy *uphy = phy_get_drvdata(phy); |
183 | |
184 | if (uphy->vbus_edev) |
185 | extcon_unregister_notifier(edev: uphy->vbus_edev, EXTCON_USB, |
186 | nb: &uphy->vbus_notify); |
187 | regulator_disable(regulator: uphy->v3p3); |
188 | regulator_disable(regulator: uphy->v1p8); |
189 | clk_disable_unprepare(clk: uphy->sleep_clk); |
190 | clk_disable_unprepare(clk: uphy->ref_clk); |
191 | |
192 | return 0; |
193 | } |
194 | |
195 | static const struct phy_ops qcom_usb_hs_phy_ops = { |
196 | .power_on = qcom_usb_hs_phy_power_on, |
197 | .power_off = qcom_usb_hs_phy_power_off, |
198 | .set_mode = qcom_usb_hs_phy_set_mode, |
199 | .owner = THIS_MODULE, |
200 | }; |
201 | |
202 | static int qcom_usb_hs_phy_probe(struct ulpi *ulpi) |
203 | { |
204 | struct qcom_usb_hs_phy *uphy; |
205 | struct phy_provider *p; |
206 | struct clk *clk; |
207 | struct regulator *reg; |
208 | struct reset_control *reset; |
209 | int size; |
210 | int ret; |
211 | |
212 | uphy = devm_kzalloc(dev: &ulpi->dev, size: sizeof(*uphy), GFP_KERNEL); |
213 | if (!uphy) |
214 | return -ENOMEM; |
215 | ulpi_set_drvdata(ulpi, data: uphy); |
216 | uphy->ulpi = ulpi; |
217 | |
218 | size = of_property_count_u8_elems(np: ulpi->dev.of_node, propname: "qcom,init-seq" ); |
219 | if (size < 0) |
220 | size = 0; |
221 | uphy->init_seq = devm_kmalloc_array(dev: &ulpi->dev, n: (size / 2) + 1, |
222 | size: sizeof(*uphy->init_seq), GFP_KERNEL); |
223 | if (!uphy->init_seq) |
224 | return -ENOMEM; |
225 | ret = of_property_read_u8_array(np: ulpi->dev.of_node, propname: "qcom,init-seq" , |
226 | out_values: (u8 *)uphy->init_seq, sz: size); |
227 | if (ret && size) |
228 | return ret; |
229 | /* NUL terminate */ |
230 | uphy->init_seq[size / 2].addr = uphy->init_seq[size / 2].val = 0; |
231 | |
232 | uphy->ref_clk = clk = devm_clk_get(dev: &ulpi->dev, id: "ref" ); |
233 | if (IS_ERR(ptr: clk)) |
234 | return PTR_ERR(ptr: clk); |
235 | |
236 | uphy->sleep_clk = clk = devm_clk_get(dev: &ulpi->dev, id: "sleep" ); |
237 | if (IS_ERR(ptr: clk)) |
238 | return PTR_ERR(ptr: clk); |
239 | |
240 | uphy->v1p8 = reg = devm_regulator_get(dev: &ulpi->dev, id: "v1p8" ); |
241 | if (IS_ERR(ptr: reg)) |
242 | return PTR_ERR(ptr: reg); |
243 | |
244 | uphy->v3p3 = reg = devm_regulator_get(dev: &ulpi->dev, id: "v3p3" ); |
245 | if (IS_ERR(ptr: reg)) |
246 | return PTR_ERR(ptr: reg); |
247 | |
248 | uphy->reset = reset = devm_reset_control_get(dev: &ulpi->dev, id: "por" ); |
249 | if (IS_ERR(ptr: reset)) { |
250 | if (PTR_ERR(ptr: reset) == -EPROBE_DEFER) |
251 | return PTR_ERR(ptr: reset); |
252 | uphy->reset = NULL; |
253 | } |
254 | |
255 | uphy->phy = devm_phy_create(dev: &ulpi->dev, node: ulpi->dev.of_node, |
256 | ops: &qcom_usb_hs_phy_ops); |
257 | if (IS_ERR(ptr: uphy->phy)) |
258 | return PTR_ERR(ptr: uphy->phy); |
259 | |
260 | uphy->vbus_edev = extcon_get_edev_by_phandle(dev: &ulpi->dev, index: 0); |
261 | if (IS_ERR(ptr: uphy->vbus_edev)) { |
262 | if (PTR_ERR(ptr: uphy->vbus_edev) != -ENODEV) |
263 | return PTR_ERR(ptr: uphy->vbus_edev); |
264 | uphy->vbus_edev = NULL; |
265 | } |
266 | |
267 | uphy->vbus_notify.notifier_call = qcom_usb_hs_phy_vbus_notifier; |
268 | phy_set_drvdata(phy: uphy->phy, data: uphy); |
269 | |
270 | p = devm_of_phy_provider_register(&ulpi->dev, of_phy_simple_xlate); |
271 | return PTR_ERR_OR_ZERO(ptr: p); |
272 | } |
273 | |
274 | static const struct of_device_id qcom_usb_hs_phy_match[] = { |
275 | { .compatible = "qcom,usb-hs-phy" , }, |
276 | { } |
277 | }; |
278 | MODULE_DEVICE_TABLE(of, qcom_usb_hs_phy_match); |
279 | |
280 | static struct ulpi_driver qcom_usb_hs_phy_driver = { |
281 | .probe = qcom_usb_hs_phy_probe, |
282 | .driver = { |
283 | .name = "qcom_usb_hs_phy" , |
284 | .of_match_table = qcom_usb_hs_phy_match, |
285 | }, |
286 | }; |
287 | module_ulpi_driver(qcom_usb_hs_phy_driver); |
288 | |
289 | MODULE_DESCRIPTION("Qualcomm USB HS phy" ); |
290 | MODULE_LICENSE("GPL v2" ); |
291 | |