17a6ff4c4SYu Chen // SPDX-License-Identifier: GPL-2.0
27a6ff4c4SYu Chen /*
37a6ff4c4SYu Chen * Support for usb functionality of Hikey series boards
47a6ff4c4SYu Chen * based on Hisilicon Kirin Soc.
57a6ff4c4SYu Chen *
67a6ff4c4SYu Chen * Copyright (C) 2017-2018 Hilisicon Electronics Co., Ltd.
77a6ff4c4SYu Chen * http://www.huawei.com
87a6ff4c4SYu Chen *
97a6ff4c4SYu Chen * Authors: Yu Chen <chenyu56@huawei.com>
107a6ff4c4SYu Chen */
117a6ff4c4SYu Chen
127a6ff4c4SYu Chen #include <linux/gpio/consumer.h>
137a6ff4c4SYu Chen #include <linux/kernel.h>
147a6ff4c4SYu Chen #include <linux/mod_devicetable.h>
157a6ff4c4SYu Chen #include <linux/module.h>
167a6ff4c4SYu Chen #include <linux/notifier.h>
17d210a002SMauro Carvalho Chehab #include <linux/of_gpio.h>
187a6ff4c4SYu Chen #include <linux/platform_device.h>
197a6ff4c4SYu Chen #include <linux/property.h>
20d210a002SMauro Carvalho Chehab #include <linux/regulator/consumer.h>
217a6ff4c4SYu Chen #include <linux/slab.h>
227a6ff4c4SYu Chen #include <linux/usb/role.h>
237a6ff4c4SYu Chen
247a6ff4c4SYu Chen #define DEVICE_DRIVER_NAME "hisi_hikey_usb"
257a6ff4c4SYu Chen
267a6ff4c4SYu Chen #define HUB_VBUS_POWER_ON 1
277a6ff4c4SYu Chen #define HUB_VBUS_POWER_OFF 0
287a6ff4c4SYu Chen #define USB_SWITCH_TO_HUB 1
297a6ff4c4SYu Chen #define USB_SWITCH_TO_TYPEC 0
307a6ff4c4SYu Chen #define TYPEC_VBUS_POWER_ON 1
317a6ff4c4SYu Chen #define TYPEC_VBUS_POWER_OFF 0
327a6ff4c4SYu Chen
337a6ff4c4SYu Chen struct hisi_hikey_usb {
34d210a002SMauro Carvalho Chehab struct device *dev;
357a6ff4c4SYu Chen struct gpio_desc *otg_switch;
367a6ff4c4SYu Chen struct gpio_desc *typec_vbus;
37d210a002SMauro Carvalho Chehab struct gpio_desc *reset;
38d210a002SMauro Carvalho Chehab
39d210a002SMauro Carvalho Chehab struct regulator *regulator;
407a6ff4c4SYu Chen
417a6ff4c4SYu Chen struct usb_role_switch *hub_role_sw;
427a6ff4c4SYu Chen
437a6ff4c4SYu Chen struct usb_role_switch *dev_role_sw;
447a6ff4c4SYu Chen enum usb_role role;
457a6ff4c4SYu Chen
467a6ff4c4SYu Chen struct mutex lock;
477a6ff4c4SYu Chen struct work_struct work;
487a6ff4c4SYu Chen
497a6ff4c4SYu Chen struct notifier_block nb;
507a6ff4c4SYu Chen };
517a6ff4c4SYu Chen
hub_power_ctrl(struct hisi_hikey_usb * hisi_hikey_usb,int value)527a6ff4c4SYu Chen static void hub_power_ctrl(struct hisi_hikey_usb *hisi_hikey_usb, int value)
537a6ff4c4SYu Chen {
54d210a002SMauro Carvalho Chehab int ret, status;
55d210a002SMauro Carvalho Chehab
56d210a002SMauro Carvalho Chehab if (!hisi_hikey_usb->regulator)
57d210a002SMauro Carvalho Chehab return;
58d210a002SMauro Carvalho Chehab
59d210a002SMauro Carvalho Chehab status = regulator_is_enabled(hisi_hikey_usb->regulator);
60d210a002SMauro Carvalho Chehab if (status == !!value)
61d210a002SMauro Carvalho Chehab return;
62d210a002SMauro Carvalho Chehab
63d210a002SMauro Carvalho Chehab if (value)
64d210a002SMauro Carvalho Chehab ret = regulator_enable(hisi_hikey_usb->regulator);
65d210a002SMauro Carvalho Chehab else
66d210a002SMauro Carvalho Chehab ret = regulator_disable(hisi_hikey_usb->regulator);
67d210a002SMauro Carvalho Chehab
68d210a002SMauro Carvalho Chehab if (ret)
69d210a002SMauro Carvalho Chehab dev_err(hisi_hikey_usb->dev,
70d210a002SMauro Carvalho Chehab "Can't switch regulator state to %s\n",
71d210a002SMauro Carvalho Chehab value ? "enabled" : "disabled");
727a6ff4c4SYu Chen }
737a6ff4c4SYu Chen
usb_switch_ctrl(struct hisi_hikey_usb * hisi_hikey_usb,int switch_to)747a6ff4c4SYu Chen static void usb_switch_ctrl(struct hisi_hikey_usb *hisi_hikey_usb,
757a6ff4c4SYu Chen int switch_to)
767a6ff4c4SYu Chen {
77d210a002SMauro Carvalho Chehab if (!hisi_hikey_usb->otg_switch)
78d210a002SMauro Carvalho Chehab return;
79d210a002SMauro Carvalho Chehab
807a6ff4c4SYu Chen gpiod_set_value_cansleep(hisi_hikey_usb->otg_switch, switch_to);
817a6ff4c4SYu Chen }
827a6ff4c4SYu Chen
usb_typec_power_ctrl(struct hisi_hikey_usb * hisi_hikey_usb,int value)837a6ff4c4SYu Chen static void usb_typec_power_ctrl(struct hisi_hikey_usb *hisi_hikey_usb,
847a6ff4c4SYu Chen int value)
857a6ff4c4SYu Chen {
86d210a002SMauro Carvalho Chehab if (!hisi_hikey_usb->typec_vbus)
87d210a002SMauro Carvalho Chehab return;
88d210a002SMauro Carvalho Chehab
897a6ff4c4SYu Chen gpiod_set_value_cansleep(hisi_hikey_usb->typec_vbus, value);
907a6ff4c4SYu Chen }
917a6ff4c4SYu Chen
relay_set_role_switch(struct work_struct * work)927a6ff4c4SYu Chen static void relay_set_role_switch(struct work_struct *work)
937a6ff4c4SYu Chen {
947a6ff4c4SYu Chen struct hisi_hikey_usb *hisi_hikey_usb = container_of(work,
957a6ff4c4SYu Chen struct hisi_hikey_usb,
967a6ff4c4SYu Chen work);
977a6ff4c4SYu Chen struct usb_role_switch *sw;
987a6ff4c4SYu Chen enum usb_role role;
997a6ff4c4SYu Chen
1007a6ff4c4SYu Chen if (!hisi_hikey_usb || !hisi_hikey_usb->dev_role_sw)
1017a6ff4c4SYu Chen return;
1027a6ff4c4SYu Chen
1037a6ff4c4SYu Chen mutex_lock(&hisi_hikey_usb->lock);
1047a6ff4c4SYu Chen switch (hisi_hikey_usb->role) {
1057a6ff4c4SYu Chen case USB_ROLE_NONE:
1067a6ff4c4SYu Chen usb_typec_power_ctrl(hisi_hikey_usb, TYPEC_VBUS_POWER_OFF);
1077a6ff4c4SYu Chen usb_switch_ctrl(hisi_hikey_usb, USB_SWITCH_TO_HUB);
1087a6ff4c4SYu Chen hub_power_ctrl(hisi_hikey_usb, HUB_VBUS_POWER_ON);
1097a6ff4c4SYu Chen break;
1107a6ff4c4SYu Chen case USB_ROLE_HOST:
1117a6ff4c4SYu Chen hub_power_ctrl(hisi_hikey_usb, HUB_VBUS_POWER_OFF);
1127a6ff4c4SYu Chen usb_switch_ctrl(hisi_hikey_usb, USB_SWITCH_TO_TYPEC);
1137a6ff4c4SYu Chen usb_typec_power_ctrl(hisi_hikey_usb, TYPEC_VBUS_POWER_ON);
1147a6ff4c4SYu Chen break;
1157a6ff4c4SYu Chen case USB_ROLE_DEVICE:
1167a6ff4c4SYu Chen hub_power_ctrl(hisi_hikey_usb, HUB_VBUS_POWER_OFF);
1177a6ff4c4SYu Chen usb_typec_power_ctrl(hisi_hikey_usb, TYPEC_VBUS_POWER_OFF);
1187a6ff4c4SYu Chen usb_switch_ctrl(hisi_hikey_usb, USB_SWITCH_TO_TYPEC);
1197a6ff4c4SYu Chen break;
1207a6ff4c4SYu Chen default:
1217a6ff4c4SYu Chen break;
1227a6ff4c4SYu Chen }
1237a6ff4c4SYu Chen sw = hisi_hikey_usb->dev_role_sw;
1247a6ff4c4SYu Chen role = hisi_hikey_usb->role;
1257a6ff4c4SYu Chen mutex_unlock(&hisi_hikey_usb->lock);
1267a6ff4c4SYu Chen
1277a6ff4c4SYu Chen usb_role_switch_set_role(sw, role);
1287a6ff4c4SYu Chen }
1297a6ff4c4SYu Chen
hub_usb_role_switch_set(struct usb_role_switch * sw,enum usb_role role)1307a6ff4c4SYu Chen static int hub_usb_role_switch_set(struct usb_role_switch *sw, enum usb_role role)
1317a6ff4c4SYu Chen {
1327a6ff4c4SYu Chen struct hisi_hikey_usb *hisi_hikey_usb = usb_role_switch_get_drvdata(sw);
1337a6ff4c4SYu Chen
1347a6ff4c4SYu Chen if (!hisi_hikey_usb || !hisi_hikey_usb->dev_role_sw)
1357a6ff4c4SYu Chen return -EINVAL;
1367a6ff4c4SYu Chen
1377a6ff4c4SYu Chen mutex_lock(&hisi_hikey_usb->lock);
1387a6ff4c4SYu Chen hisi_hikey_usb->role = role;
1397a6ff4c4SYu Chen mutex_unlock(&hisi_hikey_usb->lock);
1407a6ff4c4SYu Chen
1417a6ff4c4SYu Chen schedule_work(&hisi_hikey_usb->work);
1427a6ff4c4SYu Chen
1437a6ff4c4SYu Chen return 0;
1447a6ff4c4SYu Chen }
1457a6ff4c4SYu Chen
hisi_hikey_usb_of_role_switch(struct platform_device * pdev,struct hisi_hikey_usb * hisi_hikey_usb)146*a34993a2SMauro Carvalho Chehab static int hisi_hikey_usb_of_role_switch(struct platform_device *pdev,
147d210a002SMauro Carvalho Chehab struct hisi_hikey_usb *hisi_hikey_usb)
148d210a002SMauro Carvalho Chehab {
1497a6ff4c4SYu Chen struct device *dev = &pdev->dev;
1507a6ff4c4SYu Chen struct usb_role_switch_desc hub_role_switch = {NULL};
1517a6ff4c4SYu Chen
152*a34993a2SMauro Carvalho Chehab if (!device_property_read_bool(dev, "usb-role-switch"))
153*a34993a2SMauro Carvalho Chehab return 0;
1547a6ff4c4SYu Chen
1557a6ff4c4SYu Chen hisi_hikey_usb->otg_switch = devm_gpiod_get(dev, "otg-switch",
1567a6ff4c4SYu Chen GPIOD_OUT_HIGH);
157*a34993a2SMauro Carvalho Chehab if (IS_ERR(hisi_hikey_usb->otg_switch)) {
158*a34993a2SMauro Carvalho Chehab dev_err(dev, "get otg-switch failed with error %ld\n",
159*a34993a2SMauro Carvalho Chehab PTR_ERR(hisi_hikey_usb->otg_switch));
1607a6ff4c4SYu Chen return PTR_ERR(hisi_hikey_usb->otg_switch);
161*a34993a2SMauro Carvalho Chehab }
1627a6ff4c4SYu Chen
163d210a002SMauro Carvalho Chehab hisi_hikey_usb->typec_vbus = devm_gpiod_get(dev, "typec-vbus",
164d210a002SMauro Carvalho Chehab GPIOD_OUT_LOW);
165*a34993a2SMauro Carvalho Chehab if (IS_ERR(hisi_hikey_usb->typec_vbus)) {
166*a34993a2SMauro Carvalho Chehab dev_err(dev, "get typec-vbus failed with error %ld\n",
167*a34993a2SMauro Carvalho Chehab PTR_ERR(hisi_hikey_usb->typec_vbus));
168d210a002SMauro Carvalho Chehab return PTR_ERR(hisi_hikey_usb->typec_vbus);
169*a34993a2SMauro Carvalho Chehab }
170d210a002SMauro Carvalho Chehab
171*a34993a2SMauro Carvalho Chehab hisi_hikey_usb->reset = devm_gpiod_get_optional(dev,
172*a34993a2SMauro Carvalho Chehab "hub-reset-en",
1737a6ff4c4SYu Chen GPIOD_OUT_HIGH);
174*a34993a2SMauro Carvalho Chehab if (IS_ERR(hisi_hikey_usb->reset)) {
175*a34993a2SMauro Carvalho Chehab dev_err(dev, "get hub-reset-en failed with error %ld\n",
176*a34993a2SMauro Carvalho Chehab PTR_ERR(hisi_hikey_usb->reset));
177*a34993a2SMauro Carvalho Chehab return PTR_ERR(hisi_hikey_usb->reset);
178d210a002SMauro Carvalho Chehab }
1797a6ff4c4SYu Chen
1807a6ff4c4SYu Chen hisi_hikey_usb->dev_role_sw = usb_role_switch_get(dev);
1817a6ff4c4SYu Chen if (!hisi_hikey_usb->dev_role_sw)
1827a6ff4c4SYu Chen return -EPROBE_DEFER;
183*a34993a2SMauro Carvalho Chehab if (IS_ERR(hisi_hikey_usb->dev_role_sw)) {
184*a34993a2SMauro Carvalho Chehab dev_err(dev, "get device role switch failed with error %ld\n",
185*a34993a2SMauro Carvalho Chehab PTR_ERR(hisi_hikey_usb->dev_role_sw));
1867a6ff4c4SYu Chen return PTR_ERR(hisi_hikey_usb->dev_role_sw);
187*a34993a2SMauro Carvalho Chehab }
1887a6ff4c4SYu Chen
1897a6ff4c4SYu Chen INIT_WORK(&hisi_hikey_usb->work, relay_set_role_switch);
1907a6ff4c4SYu Chen
1917a6ff4c4SYu Chen hub_role_switch.fwnode = dev_fwnode(dev);
1927a6ff4c4SYu Chen hub_role_switch.set = hub_usb_role_switch_set;
1937a6ff4c4SYu Chen hub_role_switch.driver_data = hisi_hikey_usb;
1947a6ff4c4SYu Chen
1957a6ff4c4SYu Chen hisi_hikey_usb->hub_role_sw = usb_role_switch_register(dev,
1967a6ff4c4SYu Chen &hub_role_switch);
1977a6ff4c4SYu Chen
1987a6ff4c4SYu Chen if (IS_ERR(hisi_hikey_usb->hub_role_sw)) {
199*a34993a2SMauro Carvalho Chehab dev_err(dev,
200*a34993a2SMauro Carvalho Chehab "failed to register hub role with error %ld\n",
201*a34993a2SMauro Carvalho Chehab PTR_ERR(hisi_hikey_usb->hub_role_sw));
2027a6ff4c4SYu Chen usb_role_switch_put(hisi_hikey_usb->dev_role_sw);
2037a6ff4c4SYu Chen return PTR_ERR(hisi_hikey_usb->hub_role_sw);
2047a6ff4c4SYu Chen }
2057a6ff4c4SYu Chen
206*a34993a2SMauro Carvalho Chehab return 0;
207*a34993a2SMauro Carvalho Chehab }
208*a34993a2SMauro Carvalho Chehab
hisi_hikey_usb_probe(struct platform_device * pdev)209*a34993a2SMauro Carvalho Chehab static int hisi_hikey_usb_probe(struct platform_device *pdev)
210*a34993a2SMauro Carvalho Chehab {
211*a34993a2SMauro Carvalho Chehab struct device *dev = &pdev->dev;
212*a34993a2SMauro Carvalho Chehab struct hisi_hikey_usb *hisi_hikey_usb;
213*a34993a2SMauro Carvalho Chehab int ret;
214*a34993a2SMauro Carvalho Chehab
215*a34993a2SMauro Carvalho Chehab hisi_hikey_usb = devm_kzalloc(dev, sizeof(*hisi_hikey_usb), GFP_KERNEL);
216*a34993a2SMauro Carvalho Chehab if (!hisi_hikey_usb)
217*a34993a2SMauro Carvalho Chehab return -ENOMEM;
218*a34993a2SMauro Carvalho Chehab
219*a34993a2SMauro Carvalho Chehab hisi_hikey_usb->dev = &pdev->dev;
220*a34993a2SMauro Carvalho Chehab mutex_init(&hisi_hikey_usb->lock);
221*a34993a2SMauro Carvalho Chehab
222*a34993a2SMauro Carvalho Chehab hisi_hikey_usb->regulator = devm_regulator_get(dev, "hub-vdd");
223*a34993a2SMauro Carvalho Chehab if (IS_ERR(hisi_hikey_usb->regulator)) {
224*a34993a2SMauro Carvalho Chehab if (PTR_ERR(hisi_hikey_usb->regulator) == -EPROBE_DEFER) {
225*a34993a2SMauro Carvalho Chehab dev_info(dev, "waiting for hub-vdd-supply\n");
226*a34993a2SMauro Carvalho Chehab return PTR_ERR(hisi_hikey_usb->regulator);
227*a34993a2SMauro Carvalho Chehab }
228*a34993a2SMauro Carvalho Chehab dev_err(dev, "get hub-vdd-supply failed with error %ld\n",
229*a34993a2SMauro Carvalho Chehab PTR_ERR(hisi_hikey_usb->regulator));
230*a34993a2SMauro Carvalho Chehab return PTR_ERR(hisi_hikey_usb->regulator);
231*a34993a2SMauro Carvalho Chehab }
232*a34993a2SMauro Carvalho Chehab
233*a34993a2SMauro Carvalho Chehab ret = hisi_hikey_usb_of_role_switch(pdev, hisi_hikey_usb);
234*a34993a2SMauro Carvalho Chehab if (ret)
235*a34993a2SMauro Carvalho Chehab return ret;
236*a34993a2SMauro Carvalho Chehab
2377a6ff4c4SYu Chen platform_set_drvdata(pdev, hisi_hikey_usb);
2387a6ff4c4SYu Chen
2397a6ff4c4SYu Chen return 0;
2407a6ff4c4SYu Chen }
2417a6ff4c4SYu Chen
hisi_hikey_usb_remove(struct platform_device * pdev)2427a6ff4c4SYu Chen static int hisi_hikey_usb_remove(struct platform_device *pdev)
2437a6ff4c4SYu Chen {
2447a6ff4c4SYu Chen struct hisi_hikey_usb *hisi_hikey_usb = platform_get_drvdata(pdev);
2457a6ff4c4SYu Chen
246*a34993a2SMauro Carvalho Chehab if (hisi_hikey_usb->hub_role_sw) {
2477a6ff4c4SYu Chen usb_role_switch_unregister(hisi_hikey_usb->hub_role_sw);
2487a6ff4c4SYu Chen
2497a6ff4c4SYu Chen if (hisi_hikey_usb->dev_role_sw)
2507a6ff4c4SYu Chen usb_role_switch_put(hisi_hikey_usb->dev_role_sw);
251*a34993a2SMauro Carvalho Chehab } else {
252*a34993a2SMauro Carvalho Chehab hub_power_ctrl(hisi_hikey_usb, HUB_VBUS_POWER_OFF);
253*a34993a2SMauro Carvalho Chehab }
2547a6ff4c4SYu Chen
2557a6ff4c4SYu Chen return 0;
2567a6ff4c4SYu Chen }
2577a6ff4c4SYu Chen
2587a6ff4c4SYu Chen static const struct of_device_id id_table_hisi_hikey_usb[] = {
259*a34993a2SMauro Carvalho Chehab { .compatible = "hisilicon,usbhub" },
2607a6ff4c4SYu Chen {}
2617a6ff4c4SYu Chen };
2627a6ff4c4SYu Chen MODULE_DEVICE_TABLE(of, id_table_hisi_hikey_usb);
2637a6ff4c4SYu Chen
2647a6ff4c4SYu Chen static struct platform_driver hisi_hikey_usb_driver = {
2657a6ff4c4SYu Chen .probe = hisi_hikey_usb_probe,
2667a6ff4c4SYu Chen .remove = hisi_hikey_usb_remove,
2677a6ff4c4SYu Chen .driver = {
2687a6ff4c4SYu Chen .name = DEVICE_DRIVER_NAME,
2697a6ff4c4SYu Chen .of_match_table = id_table_hisi_hikey_usb,
2707a6ff4c4SYu Chen },
2717a6ff4c4SYu Chen };
2727a6ff4c4SYu Chen
2737a6ff4c4SYu Chen module_platform_driver(hisi_hikey_usb_driver);
2747a6ff4c4SYu Chen
2757a6ff4c4SYu Chen MODULE_AUTHOR("Yu Chen <chenyu56@huawei.com>");
2767a6ff4c4SYu Chen MODULE_DESCRIPTION("Driver Support for USB functionality of Hikey");
2777a6ff4c4SYu Chen MODULE_LICENSE("GPL v2");
278