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