blob: 3a98a890757c899e60144134837c0014ba3c0aee [file] [log] [blame]
Yu Chen7a6ff4c2020-09-10 08:00:12 +02001// 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
38 struct usb_role_switch *dev_role_sw;
39 enum usb_role role;
40
41 struct mutex lock;
42 struct work_struct work;
43
44 struct notifier_block nb;
45};
46
47static void hub_power_ctrl(struct hisi_hikey_usb *hisi_hikey_usb, int value)
48{
49 gpiod_set_value_cansleep(hisi_hikey_usb->hub_vbus, value);
50}
51
52static void usb_switch_ctrl(struct hisi_hikey_usb *hisi_hikey_usb,
53 int switch_to)
54{
55 gpiod_set_value_cansleep(hisi_hikey_usb->otg_switch, switch_to);
56}
57
58static void usb_typec_power_ctrl(struct hisi_hikey_usb *hisi_hikey_usb,
59 int value)
60{
61 gpiod_set_value_cansleep(hisi_hikey_usb->typec_vbus, value);
62}
63
64
65
66static void relay_set_role_switch(struct work_struct *work)
67{
68 struct hisi_hikey_usb *hisi_hikey_usb = container_of(work,
69 struct hisi_hikey_usb,
70 work);
71 struct usb_role_switch *sw;
72 enum usb_role role;
73
74 if (!hisi_hikey_usb || !hisi_hikey_usb->dev_role_sw)
75 return;
76
77 mutex_lock(&hisi_hikey_usb->lock);
78 switch (hisi_hikey_usb->role) {
79 case USB_ROLE_NONE:
80 usb_typec_power_ctrl(hisi_hikey_usb, TYPEC_VBUS_POWER_OFF);
81 usb_switch_ctrl(hisi_hikey_usb, USB_SWITCH_TO_HUB);
82 hub_power_ctrl(hisi_hikey_usb, HUB_VBUS_POWER_ON);
83 break;
84 case USB_ROLE_HOST:
85 hub_power_ctrl(hisi_hikey_usb, HUB_VBUS_POWER_OFF);
86 usb_switch_ctrl(hisi_hikey_usb, USB_SWITCH_TO_TYPEC);
87 usb_typec_power_ctrl(hisi_hikey_usb, TYPEC_VBUS_POWER_ON);
88 break;
89 case USB_ROLE_DEVICE:
90 hub_power_ctrl(hisi_hikey_usb, HUB_VBUS_POWER_OFF);
91 usb_typec_power_ctrl(hisi_hikey_usb, TYPEC_VBUS_POWER_OFF);
92 usb_switch_ctrl(hisi_hikey_usb, USB_SWITCH_TO_TYPEC);
93 break;
94 default:
95 break;
96 }
97 sw = hisi_hikey_usb->dev_role_sw;
98 role = hisi_hikey_usb->role;
99 mutex_unlock(&hisi_hikey_usb->lock);
100
101 usb_role_switch_set_role(sw, role);
102}
103
104static int hub_usb_role_switch_set(struct usb_role_switch *sw, enum usb_role role)
105{
106 struct hisi_hikey_usb *hisi_hikey_usb = usb_role_switch_get_drvdata(sw);
107
108 if (!hisi_hikey_usb || !hisi_hikey_usb->dev_role_sw)
109 return -EINVAL;
110
111 mutex_lock(&hisi_hikey_usb->lock);
112 hisi_hikey_usb->role = role;
113 mutex_unlock(&hisi_hikey_usb->lock);
114
115 schedule_work(&hisi_hikey_usb->work);
116
117 return 0;
118}
119
120static int hisi_hikey_usb_probe(struct platform_device *pdev)
121{
122 struct device *dev = &pdev->dev;
123 struct hisi_hikey_usb *hisi_hikey_usb;
124 struct usb_role_switch_desc hub_role_switch = {NULL};
125
126 hisi_hikey_usb = devm_kzalloc(dev, sizeof(*hisi_hikey_usb), GFP_KERNEL);
127 if (!hisi_hikey_usb)
128 return -ENOMEM;
129
130 hisi_hikey_usb->typec_vbus = devm_gpiod_get(dev, "typec-vbus",
131 GPIOD_OUT_LOW);
132 if (IS_ERR(hisi_hikey_usb->typec_vbus))
133 return PTR_ERR(hisi_hikey_usb->typec_vbus);
134
135 hisi_hikey_usb->otg_switch = devm_gpiod_get(dev, "otg-switch",
136 GPIOD_OUT_HIGH);
137 if (IS_ERR(hisi_hikey_usb->otg_switch))
138 return PTR_ERR(hisi_hikey_usb->otg_switch);
139
140 /* hub-vdd33-en is optional */
141 hisi_hikey_usb->hub_vbus = devm_gpiod_get_optional(dev, "hub-vdd33-en",
142 GPIOD_OUT_HIGH);
143 if (IS_ERR(hisi_hikey_usb->hub_vbus))
144 return PTR_ERR(hisi_hikey_usb->hub_vbus);
145
146 hisi_hikey_usb->dev_role_sw = usb_role_switch_get(dev);
147 if (!hisi_hikey_usb->dev_role_sw)
148 return -EPROBE_DEFER;
149 if (IS_ERR(hisi_hikey_usb->dev_role_sw))
150 return PTR_ERR(hisi_hikey_usb->dev_role_sw);
151
152
153 INIT_WORK(&hisi_hikey_usb->work, relay_set_role_switch);
154 mutex_init(&hisi_hikey_usb->lock);
155
156 hub_role_switch.fwnode = dev_fwnode(dev);
157 hub_role_switch.set = hub_usb_role_switch_set;
158 hub_role_switch.driver_data = hisi_hikey_usb;
159
160 hisi_hikey_usb->hub_role_sw = usb_role_switch_register(dev,
161 &hub_role_switch);
162
163 if (IS_ERR(hisi_hikey_usb->hub_role_sw)) {
164 usb_role_switch_put(hisi_hikey_usb->dev_role_sw);
165 return PTR_ERR(hisi_hikey_usb->hub_role_sw);
166 }
167
168 platform_set_drvdata(pdev, hisi_hikey_usb);
169
170 return 0;
171}
172
173static int hisi_hikey_usb_remove(struct platform_device *pdev)
174{
175 struct hisi_hikey_usb *hisi_hikey_usb = platform_get_drvdata(pdev);
176
177 if (hisi_hikey_usb->hub_role_sw)
178 usb_role_switch_unregister(hisi_hikey_usb->hub_role_sw);
179
180 if (hisi_hikey_usb->dev_role_sw)
181 usb_role_switch_put(hisi_hikey_usb->dev_role_sw);
182
183 return 0;
184}
185
186static const struct of_device_id id_table_hisi_hikey_usb[] = {
187 {.compatible = "hisilicon,gpio_hubv1"},
188 {}
189};
190MODULE_DEVICE_TABLE(of, id_table_hisi_hikey_usb);
191
192static struct platform_driver hisi_hikey_usb_driver = {
193 .probe = hisi_hikey_usb_probe,
194 .remove = hisi_hikey_usb_remove,
195 .driver = {
196 .name = DEVICE_DRIVER_NAME,
197 .of_match_table = id_table_hisi_hikey_usb,
198 },
199};
200
201module_platform_driver(hisi_hikey_usb_driver);
202
203MODULE_AUTHOR("Yu Chen <chenyu56@huawei.com>");
204MODULE_DESCRIPTION("Driver Support for USB functionality of Hikey");
205MODULE_LICENSE("GPL v2");