blob: 32015bc9ccf60a9ab3d2aa034ea64c7849d2f841 [file] [log] [blame]
b.liue9582032025-04-17 19:18:16 +08001// 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/slab.h>
20#include <linux/usb/role.h>
21
22#define DEVICE_DRIVER_NAME "hisi_hikey_usb"
23
24#define HUB_VBUS_POWER_ON 1
25#define HUB_VBUS_POWER_OFF 0
26#define USB_SWITCH_TO_HUB 1
27#define USB_SWITCH_TO_TYPEC 0
28#define TYPEC_VBUS_POWER_ON 1
29#define TYPEC_VBUS_POWER_OFF 0
30
31struct hisi_hikey_usb {
32 struct gpio_desc *otg_switch;
33 struct gpio_desc *typec_vbus;
34 struct gpio_desc *hub_vbus;
35
36 struct usb_role_switch *hub_role_sw;
37 struct usb_role_switch *dev_role_sw;
38 struct notifier_block nb;
39};
40
41static void hub_power_ctrl(struct hisi_hikey_usb *hisi_hikey_usb, int value)
42{
43 gpiod_set_value_cansleep(hisi_hikey_usb->hub_vbus, value);
44}
45
46static void usb_switch_ctrl(struct hisi_hikey_usb *hisi_hikey_usb,
47 int switch_to)
48{
49 gpiod_set_value_cansleep(hisi_hikey_usb->otg_switch, switch_to);
50}
51
52static void usb_typec_power_ctrl(struct hisi_hikey_usb *hisi_hikey_usb,
53 int value)
54{
55 gpiod_set_value_cansleep(hisi_hikey_usb->typec_vbus, value);
56}
57
58static int hub_usb_role_switch_set(struct device *dev, enum usb_role role)
59{
60 struct hisi_hikey_usb *hisi_hikey_usb = dev_get_drvdata(dev);
61
62 if (!hisi_hikey_usb || !hisi_hikey_usb->dev_role_sw)
63 return -EINVAL;
64
65 switch (role) {
66 case USB_ROLE_NONE:
67 usb_typec_power_ctrl(hisi_hikey_usb, TYPEC_VBUS_POWER_OFF);
68 usb_switch_ctrl(hisi_hikey_usb, USB_SWITCH_TO_HUB);
69 hub_power_ctrl(hisi_hikey_usb, HUB_VBUS_POWER_ON);
70 break;
71 case USB_ROLE_HOST:
72 hub_power_ctrl(hisi_hikey_usb, HUB_VBUS_POWER_OFF);
73 usb_switch_ctrl(hisi_hikey_usb, USB_SWITCH_TO_TYPEC);
74 usb_typec_power_ctrl(hisi_hikey_usb, TYPEC_VBUS_POWER_ON);
75 break;
76 case USB_ROLE_DEVICE:
77 hub_power_ctrl(hisi_hikey_usb, HUB_VBUS_POWER_OFF);
78 usb_typec_power_ctrl(hisi_hikey_usb, TYPEC_VBUS_POWER_OFF);
79 usb_switch_ctrl(hisi_hikey_usb, USB_SWITCH_TO_TYPEC);
80 break;
81 default:
82 break;
83 }
84
85 return usb_role_switch_set_role(hisi_hikey_usb->dev_role_sw, role);
86}
87
88static enum usb_role hub_usb_role_switch_get(struct device *dev)
89{
90 struct hisi_hikey_usb *hisi_hikey_usb = dev_get_drvdata(dev);
91
92 if (!hisi_hikey_usb || !hisi_hikey_usb->dev_role_sw)
93 return -EINVAL;
94
95 return usb_role_switch_get_role(hisi_hikey_usb->dev_role_sw);
96}
97
98static int hisi_hikey_usb_probe(struct platform_device *pdev)
99{
100 struct device *dev = &pdev->dev;
101 struct hisi_hikey_usb *hisi_hikey_usb;
102 struct usb_role_switch_desc hub_role_switch = {NULL};
103
104 hisi_hikey_usb = devm_kzalloc(dev, sizeof(*hisi_hikey_usb), GFP_KERNEL);
105 if (!hisi_hikey_usb)
106 return -ENOMEM;
107
108 hisi_hikey_usb->typec_vbus = devm_gpiod_get(dev, "typec-vbus",
109 GPIOD_OUT_LOW);
110 if (IS_ERR(hisi_hikey_usb->typec_vbus))
111 return PTR_ERR(hisi_hikey_usb->typec_vbus);
112
113 hisi_hikey_usb->otg_switch = devm_gpiod_get(dev, "otg-switch",
114 GPIOD_OUT_HIGH);
115 if (IS_ERR(hisi_hikey_usb->otg_switch))
116 return PTR_ERR(hisi_hikey_usb->otg_switch);
117
118 /* hub-vdd33-en is optional */
119 hisi_hikey_usb->hub_vbus = devm_gpiod_get_optional(dev, "hub-vdd33-en",
120 GPIOD_OUT_HIGH);
121 if (IS_ERR(hisi_hikey_usb->hub_vbus))
122 return PTR_ERR(hisi_hikey_usb->hub_vbus);
123
124 hisi_hikey_usb->dev_role_sw = usb_role_switch_get(dev);
125 if (!hisi_hikey_usb->dev_role_sw)
126 return -EPROBE_DEFER;
127 if (IS_ERR(hisi_hikey_usb->dev_role_sw))
128 return PTR_ERR(hisi_hikey_usb->dev_role_sw);
129
130 hub_role_switch.fwnode = dev_fwnode(dev);
131 hub_role_switch.set = hub_usb_role_switch_set;
132 hub_role_switch.get = hub_usb_role_switch_get;
133 hisi_hikey_usb->hub_role_sw = usb_role_switch_register(dev,
134 &hub_role_switch);
135
136 if (IS_ERR(hisi_hikey_usb->hub_role_sw)) {
137 usb_role_switch_put(hisi_hikey_usb->dev_role_sw);
138 return PTR_ERR(hisi_hikey_usb->hub_role_sw);
139 }
140
141 platform_set_drvdata(pdev, hisi_hikey_usb);
142
143 return 0;
144}
145
146static int hisi_hikey_usb_remove(struct platform_device *pdev)
147{
148 struct hisi_hikey_usb *hisi_hikey_usb = platform_get_drvdata(pdev);
149
150 if (hisi_hikey_usb->hub_role_sw)
151 usb_role_switch_unregister(hisi_hikey_usb->hub_role_sw);
152
153 if (hisi_hikey_usb->dev_role_sw)
154 usb_role_switch_put(hisi_hikey_usb->dev_role_sw);
155
156 return 0;
157}
158
159static const struct of_device_id id_table_hisi_hikey_usb[] = {
160 {.compatible = "hisilicon,gpio_hubv1"},
161 {}
162};
163MODULE_DEVICE_TABLE(of, id_table_hisi_hikey_usb);
164
165static struct platform_driver hisi_hikey_usb_driver = {
166 .probe = hisi_hikey_usb_probe,
167 .remove = hisi_hikey_usb_remove,
168 .driver = {
169 .name = DEVICE_DRIVER_NAME,
170 .of_match_table = id_table_hisi_hikey_usb,
171 },
172};
173
174module_platform_driver(hisi_hikey_usb_driver);
175
176MODULE_AUTHOR("Yu Chen <chenyu56@huawei.com>");
177MODULE_DESCRIPTION("Driver Support for USB functionality of Hikey");
178MODULE_LICENSE("GPL v2");