xref: /openbmc/linux/drivers/misc/hisi_hikey_usb.c (revision a34993a2)
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