1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Support for usb functionality of Hikey series boards |
4 | * based on Hisilicon Kirin Soc. |
5 | * |
6 | * Copyright (C) 2017-2018 Hilisicon Electronics Co., Ltd. |
7 | * http://www.huawei.com |
8 | * |
9 | * Authors: Yu Chen <chenyu56@huawei.com> |
10 | */ |
11 | |
12 | #include <linux/gpio/consumer.h> |
13 | #include <linux/kernel.h> |
14 | #include <linux/mod_devicetable.h> |
15 | #include <linux/module.h> |
16 | #include <linux/notifier.h> |
17 | #include <linux/platform_device.h> |
18 | #include <linux/property.h> |
19 | #include <linux/regulator/consumer.h> |
20 | #include <linux/slab.h> |
21 | #include <linux/usb/role.h> |
22 | |
23 | #define DEVICE_DRIVER_NAME "hisi_hikey_usb" |
24 | |
25 | #define HUB_VBUS_POWER_ON 1 |
26 | #define HUB_VBUS_POWER_OFF 0 |
27 | #define USB_SWITCH_TO_HUB 1 |
28 | #define USB_SWITCH_TO_TYPEC 0 |
29 | #define TYPEC_VBUS_POWER_ON 1 |
30 | #define TYPEC_VBUS_POWER_OFF 0 |
31 | |
32 | struct hisi_hikey_usb { |
33 | struct device *dev; |
34 | struct gpio_desc *otg_switch; |
35 | struct gpio_desc *typec_vbus; |
36 | struct gpio_desc *reset; |
37 | |
38 | struct regulator *regulator; |
39 | |
40 | struct usb_role_switch *hub_role_sw; |
41 | |
42 | struct usb_role_switch *dev_role_sw; |
43 | enum usb_role role; |
44 | |
45 | struct mutex lock; |
46 | struct work_struct work; |
47 | |
48 | struct notifier_block nb; |
49 | }; |
50 | |
51 | static void hub_power_ctrl(struct hisi_hikey_usb *hisi_hikey_usb, int value) |
52 | { |
53 | int ret, status; |
54 | |
55 | if (!hisi_hikey_usb->regulator) |
56 | return; |
57 | |
58 | status = regulator_is_enabled(regulator: hisi_hikey_usb->regulator); |
59 | if (status == !!value) |
60 | return; |
61 | |
62 | if (value) |
63 | ret = regulator_enable(regulator: hisi_hikey_usb->regulator); |
64 | else |
65 | ret = regulator_disable(regulator: hisi_hikey_usb->regulator); |
66 | |
67 | if (ret) |
68 | dev_err(hisi_hikey_usb->dev, |
69 | "Can't switch regulator state to %s\n" , |
70 | value ? "enabled" : "disabled" ); |
71 | } |
72 | |
73 | static void usb_switch_ctrl(struct hisi_hikey_usb *hisi_hikey_usb, |
74 | int switch_to) |
75 | { |
76 | if (!hisi_hikey_usb->otg_switch) |
77 | return; |
78 | |
79 | gpiod_set_value_cansleep(desc: hisi_hikey_usb->otg_switch, value: switch_to); |
80 | } |
81 | |
82 | static void usb_typec_power_ctrl(struct hisi_hikey_usb *hisi_hikey_usb, |
83 | int value) |
84 | { |
85 | if (!hisi_hikey_usb->typec_vbus) |
86 | return; |
87 | |
88 | gpiod_set_value_cansleep(desc: hisi_hikey_usb->typec_vbus, value); |
89 | } |
90 | |
91 | static void relay_set_role_switch(struct work_struct *work) |
92 | { |
93 | struct hisi_hikey_usb *hisi_hikey_usb = container_of(work, |
94 | struct hisi_hikey_usb, |
95 | work); |
96 | struct usb_role_switch *sw; |
97 | enum usb_role role; |
98 | |
99 | if (!hisi_hikey_usb || !hisi_hikey_usb->dev_role_sw) |
100 | return; |
101 | |
102 | mutex_lock(&hisi_hikey_usb->lock); |
103 | switch (hisi_hikey_usb->role) { |
104 | case USB_ROLE_NONE: |
105 | usb_typec_power_ctrl(hisi_hikey_usb, TYPEC_VBUS_POWER_OFF); |
106 | usb_switch_ctrl(hisi_hikey_usb, USB_SWITCH_TO_HUB); |
107 | hub_power_ctrl(hisi_hikey_usb, HUB_VBUS_POWER_ON); |
108 | break; |
109 | case USB_ROLE_HOST: |
110 | hub_power_ctrl(hisi_hikey_usb, HUB_VBUS_POWER_OFF); |
111 | usb_switch_ctrl(hisi_hikey_usb, USB_SWITCH_TO_TYPEC); |
112 | usb_typec_power_ctrl(hisi_hikey_usb, TYPEC_VBUS_POWER_ON); |
113 | break; |
114 | case USB_ROLE_DEVICE: |
115 | hub_power_ctrl(hisi_hikey_usb, HUB_VBUS_POWER_OFF); |
116 | usb_typec_power_ctrl(hisi_hikey_usb, TYPEC_VBUS_POWER_OFF); |
117 | usb_switch_ctrl(hisi_hikey_usb, USB_SWITCH_TO_TYPEC); |
118 | break; |
119 | default: |
120 | break; |
121 | } |
122 | sw = hisi_hikey_usb->dev_role_sw; |
123 | role = hisi_hikey_usb->role; |
124 | mutex_unlock(lock: &hisi_hikey_usb->lock); |
125 | |
126 | usb_role_switch_set_role(sw, role); |
127 | } |
128 | |
129 | static int hub_usb_role_switch_set(struct usb_role_switch *sw, enum usb_role role) |
130 | { |
131 | struct hisi_hikey_usb *hisi_hikey_usb = usb_role_switch_get_drvdata(sw); |
132 | |
133 | if (!hisi_hikey_usb || !hisi_hikey_usb->dev_role_sw) |
134 | return -EINVAL; |
135 | |
136 | mutex_lock(&hisi_hikey_usb->lock); |
137 | hisi_hikey_usb->role = role; |
138 | mutex_unlock(lock: &hisi_hikey_usb->lock); |
139 | |
140 | schedule_work(work: &hisi_hikey_usb->work); |
141 | |
142 | return 0; |
143 | } |
144 | |
145 | static int hisi_hikey_usb_of_role_switch(struct platform_device *pdev, |
146 | struct hisi_hikey_usb *hisi_hikey_usb) |
147 | { |
148 | struct device *dev = &pdev->dev; |
149 | struct usb_role_switch_desc hub_role_switch = {NULL}; |
150 | |
151 | if (!device_property_read_bool(dev, propname: "usb-role-switch" )) |
152 | return 0; |
153 | |
154 | hisi_hikey_usb->otg_switch = devm_gpiod_get(dev, con_id: "otg-switch" , |
155 | flags: GPIOD_OUT_HIGH); |
156 | if (IS_ERR(ptr: hisi_hikey_usb->otg_switch)) { |
157 | dev_err(dev, "get otg-switch failed with error %ld\n" , |
158 | PTR_ERR(hisi_hikey_usb->otg_switch)); |
159 | return PTR_ERR(ptr: hisi_hikey_usb->otg_switch); |
160 | } |
161 | |
162 | hisi_hikey_usb->typec_vbus = devm_gpiod_get(dev, con_id: "typec-vbus" , |
163 | flags: GPIOD_OUT_LOW); |
164 | if (IS_ERR(ptr: hisi_hikey_usb->typec_vbus)) { |
165 | dev_err(dev, "get typec-vbus failed with error %ld\n" , |
166 | PTR_ERR(hisi_hikey_usb->typec_vbus)); |
167 | return PTR_ERR(ptr: hisi_hikey_usb->typec_vbus); |
168 | } |
169 | |
170 | hisi_hikey_usb->reset = devm_gpiod_get_optional(dev, |
171 | con_id: "hub-reset-en" , |
172 | flags: GPIOD_OUT_HIGH); |
173 | if (IS_ERR(ptr: hisi_hikey_usb->reset)) { |
174 | dev_err(dev, "get hub-reset-en failed with error %ld\n" , |
175 | PTR_ERR(hisi_hikey_usb->reset)); |
176 | return PTR_ERR(ptr: hisi_hikey_usb->reset); |
177 | } |
178 | |
179 | hisi_hikey_usb->dev_role_sw = usb_role_switch_get(dev); |
180 | if (!hisi_hikey_usb->dev_role_sw) |
181 | return -EPROBE_DEFER; |
182 | if (IS_ERR(ptr: hisi_hikey_usb->dev_role_sw)) { |
183 | dev_err(dev, "get device role switch failed with error %ld\n" , |
184 | PTR_ERR(hisi_hikey_usb->dev_role_sw)); |
185 | return PTR_ERR(ptr: hisi_hikey_usb->dev_role_sw); |
186 | } |
187 | |
188 | INIT_WORK(&hisi_hikey_usb->work, relay_set_role_switch); |
189 | |
190 | hub_role_switch.fwnode = dev_fwnode(dev); |
191 | hub_role_switch.set = hub_usb_role_switch_set; |
192 | hub_role_switch.driver_data = hisi_hikey_usb; |
193 | |
194 | hisi_hikey_usb->hub_role_sw = usb_role_switch_register(parent: dev, |
195 | desc: &hub_role_switch); |
196 | |
197 | if (IS_ERR(ptr: hisi_hikey_usb->hub_role_sw)) { |
198 | dev_err(dev, |
199 | "failed to register hub role with error %ld\n" , |
200 | PTR_ERR(hisi_hikey_usb->hub_role_sw)); |
201 | usb_role_switch_put(sw: hisi_hikey_usb->dev_role_sw); |
202 | return PTR_ERR(ptr: hisi_hikey_usb->hub_role_sw); |
203 | } |
204 | |
205 | return 0; |
206 | } |
207 | |
208 | static int hisi_hikey_usb_probe(struct platform_device *pdev) |
209 | { |
210 | struct device *dev = &pdev->dev; |
211 | struct hisi_hikey_usb *hisi_hikey_usb; |
212 | int ret; |
213 | |
214 | hisi_hikey_usb = devm_kzalloc(dev, size: sizeof(*hisi_hikey_usb), GFP_KERNEL); |
215 | if (!hisi_hikey_usb) |
216 | return -ENOMEM; |
217 | |
218 | hisi_hikey_usb->dev = &pdev->dev; |
219 | mutex_init(&hisi_hikey_usb->lock); |
220 | |
221 | hisi_hikey_usb->regulator = devm_regulator_get(dev, id: "hub-vdd" ); |
222 | if (IS_ERR(ptr: hisi_hikey_usb->regulator)) { |
223 | if (PTR_ERR(ptr: hisi_hikey_usb->regulator) == -EPROBE_DEFER) { |
224 | dev_info(dev, "waiting for hub-vdd-supply\n" ); |
225 | return PTR_ERR(ptr: hisi_hikey_usb->regulator); |
226 | } |
227 | dev_err(dev, "get hub-vdd-supply failed with error %ld\n" , |
228 | PTR_ERR(hisi_hikey_usb->regulator)); |
229 | return PTR_ERR(ptr: hisi_hikey_usb->regulator); |
230 | } |
231 | |
232 | ret = hisi_hikey_usb_of_role_switch(pdev, hisi_hikey_usb); |
233 | if (ret) |
234 | return ret; |
235 | |
236 | platform_set_drvdata(pdev, data: hisi_hikey_usb); |
237 | |
238 | return 0; |
239 | } |
240 | |
241 | static void hisi_hikey_usb_remove(struct platform_device *pdev) |
242 | { |
243 | struct hisi_hikey_usb *hisi_hikey_usb = platform_get_drvdata(pdev); |
244 | |
245 | if (hisi_hikey_usb->hub_role_sw) { |
246 | usb_role_switch_unregister(sw: hisi_hikey_usb->hub_role_sw); |
247 | |
248 | if (hisi_hikey_usb->dev_role_sw) |
249 | usb_role_switch_put(sw: hisi_hikey_usb->dev_role_sw); |
250 | } else { |
251 | hub_power_ctrl(hisi_hikey_usb, HUB_VBUS_POWER_OFF); |
252 | } |
253 | } |
254 | |
255 | static const struct of_device_id id_table_hisi_hikey_usb[] = { |
256 | { .compatible = "hisilicon,usbhub" }, |
257 | {} |
258 | }; |
259 | MODULE_DEVICE_TABLE(of, id_table_hisi_hikey_usb); |
260 | |
261 | static struct platform_driver hisi_hikey_usb_driver = { |
262 | .probe = hisi_hikey_usb_probe, |
263 | .remove_new = hisi_hikey_usb_remove, |
264 | .driver = { |
265 | .name = DEVICE_DRIVER_NAME, |
266 | .of_match_table = id_table_hisi_hikey_usb, |
267 | }, |
268 | }; |
269 | |
270 | module_platform_driver(hisi_hikey_usb_driver); |
271 | |
272 | MODULE_AUTHOR("Yu Chen <chenyu56@huawei.com>" ); |
273 | MODULE_DESCRIPTION("Driver Support for USB functionality of Hikey" ); |
274 | MODULE_LICENSE("GPL v2" ); |
275 | |