0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012 #include <linux/gpio/consumer.h>
0013 #include <linux/kernel.h>
0014 #include <linux/mod_devicetable.h>
0015 #include <linux/module.h>
0016 #include <linux/notifier.h>
0017 #include <linux/of_gpio.h>
0018 #include <linux/platform_device.h>
0019 #include <linux/property.h>
0020 #include <linux/regulator/consumer.h>
0021 #include <linux/slab.h>
0022 #include <linux/usb/role.h>
0023
0024 #define DEVICE_DRIVER_NAME "hisi_hikey_usb"
0025
0026 #define HUB_VBUS_POWER_ON 1
0027 #define HUB_VBUS_POWER_OFF 0
0028 #define USB_SWITCH_TO_HUB 1
0029 #define USB_SWITCH_TO_TYPEC 0
0030 #define TYPEC_VBUS_POWER_ON 1
0031 #define TYPEC_VBUS_POWER_OFF 0
0032
0033 struct hisi_hikey_usb {
0034 struct device *dev;
0035 struct gpio_desc *otg_switch;
0036 struct gpio_desc *typec_vbus;
0037 struct gpio_desc *reset;
0038
0039 struct regulator *regulator;
0040
0041 struct usb_role_switch *hub_role_sw;
0042
0043 struct usb_role_switch *dev_role_sw;
0044 enum usb_role role;
0045
0046 struct mutex lock;
0047 struct work_struct work;
0048
0049 struct notifier_block nb;
0050 };
0051
0052 static void hub_power_ctrl(struct hisi_hikey_usb *hisi_hikey_usb, int value)
0053 {
0054 int ret, status;
0055
0056 if (!hisi_hikey_usb->regulator)
0057 return;
0058
0059 status = regulator_is_enabled(hisi_hikey_usb->regulator);
0060 if (status == !!value)
0061 return;
0062
0063 if (value)
0064 ret = regulator_enable(hisi_hikey_usb->regulator);
0065 else
0066 ret = regulator_disable(hisi_hikey_usb->regulator);
0067
0068 if (ret)
0069 dev_err(hisi_hikey_usb->dev,
0070 "Can't switch regulator state to %s\n",
0071 value ? "enabled" : "disabled");
0072 }
0073
0074 static void usb_switch_ctrl(struct hisi_hikey_usb *hisi_hikey_usb,
0075 int switch_to)
0076 {
0077 if (!hisi_hikey_usb->otg_switch)
0078 return;
0079
0080 gpiod_set_value_cansleep(hisi_hikey_usb->otg_switch, switch_to);
0081 }
0082
0083 static void usb_typec_power_ctrl(struct hisi_hikey_usb *hisi_hikey_usb,
0084 int value)
0085 {
0086 if (!hisi_hikey_usb->typec_vbus)
0087 return;
0088
0089 gpiod_set_value_cansleep(hisi_hikey_usb->typec_vbus, value);
0090 }
0091
0092 static void relay_set_role_switch(struct work_struct *work)
0093 {
0094 struct hisi_hikey_usb *hisi_hikey_usb = container_of(work,
0095 struct hisi_hikey_usb,
0096 work);
0097 struct usb_role_switch *sw;
0098 enum usb_role role;
0099
0100 if (!hisi_hikey_usb || !hisi_hikey_usb->dev_role_sw)
0101 return;
0102
0103 mutex_lock(&hisi_hikey_usb->lock);
0104 switch (hisi_hikey_usb->role) {
0105 case USB_ROLE_NONE:
0106 usb_typec_power_ctrl(hisi_hikey_usb, TYPEC_VBUS_POWER_OFF);
0107 usb_switch_ctrl(hisi_hikey_usb, USB_SWITCH_TO_HUB);
0108 hub_power_ctrl(hisi_hikey_usb, HUB_VBUS_POWER_ON);
0109 break;
0110 case USB_ROLE_HOST:
0111 hub_power_ctrl(hisi_hikey_usb, HUB_VBUS_POWER_OFF);
0112 usb_switch_ctrl(hisi_hikey_usb, USB_SWITCH_TO_TYPEC);
0113 usb_typec_power_ctrl(hisi_hikey_usb, TYPEC_VBUS_POWER_ON);
0114 break;
0115 case USB_ROLE_DEVICE:
0116 hub_power_ctrl(hisi_hikey_usb, HUB_VBUS_POWER_OFF);
0117 usb_typec_power_ctrl(hisi_hikey_usb, TYPEC_VBUS_POWER_OFF);
0118 usb_switch_ctrl(hisi_hikey_usb, USB_SWITCH_TO_TYPEC);
0119 break;
0120 default:
0121 break;
0122 }
0123 sw = hisi_hikey_usb->dev_role_sw;
0124 role = hisi_hikey_usb->role;
0125 mutex_unlock(&hisi_hikey_usb->lock);
0126
0127 usb_role_switch_set_role(sw, role);
0128 }
0129
0130 static int hub_usb_role_switch_set(struct usb_role_switch *sw, enum usb_role role)
0131 {
0132 struct hisi_hikey_usb *hisi_hikey_usb = usb_role_switch_get_drvdata(sw);
0133
0134 if (!hisi_hikey_usb || !hisi_hikey_usb->dev_role_sw)
0135 return -EINVAL;
0136
0137 mutex_lock(&hisi_hikey_usb->lock);
0138 hisi_hikey_usb->role = role;
0139 mutex_unlock(&hisi_hikey_usb->lock);
0140
0141 schedule_work(&hisi_hikey_usb->work);
0142
0143 return 0;
0144 }
0145
0146 static int hisi_hikey_usb_of_role_switch(struct platform_device *pdev,
0147 struct hisi_hikey_usb *hisi_hikey_usb)
0148 {
0149 struct device *dev = &pdev->dev;
0150 struct usb_role_switch_desc hub_role_switch = {NULL};
0151
0152 if (!device_property_read_bool(dev, "usb-role-switch"))
0153 return 0;
0154
0155 hisi_hikey_usb->otg_switch = devm_gpiod_get(dev, "otg-switch",
0156 GPIOD_OUT_HIGH);
0157 if (IS_ERR(hisi_hikey_usb->otg_switch)) {
0158 dev_err(dev, "get otg-switch failed with error %ld\n",
0159 PTR_ERR(hisi_hikey_usb->otg_switch));
0160 return PTR_ERR(hisi_hikey_usb->otg_switch);
0161 }
0162
0163 hisi_hikey_usb->typec_vbus = devm_gpiod_get(dev, "typec-vbus",
0164 GPIOD_OUT_LOW);
0165 if (IS_ERR(hisi_hikey_usb->typec_vbus)) {
0166 dev_err(dev, "get typec-vbus failed with error %ld\n",
0167 PTR_ERR(hisi_hikey_usb->typec_vbus));
0168 return PTR_ERR(hisi_hikey_usb->typec_vbus);
0169 }
0170
0171 hisi_hikey_usb->reset = devm_gpiod_get_optional(dev,
0172 "hub-reset-en",
0173 GPIOD_OUT_HIGH);
0174 if (IS_ERR(hisi_hikey_usb->reset)) {
0175 dev_err(dev, "get hub-reset-en failed with error %ld\n",
0176 PTR_ERR(hisi_hikey_usb->reset));
0177 return PTR_ERR(hisi_hikey_usb->reset);
0178 }
0179
0180 hisi_hikey_usb->dev_role_sw = usb_role_switch_get(dev);
0181 if (!hisi_hikey_usb->dev_role_sw)
0182 return -EPROBE_DEFER;
0183 if (IS_ERR(hisi_hikey_usb->dev_role_sw)) {
0184 dev_err(dev, "get device role switch failed with error %ld\n",
0185 PTR_ERR(hisi_hikey_usb->dev_role_sw));
0186 return PTR_ERR(hisi_hikey_usb->dev_role_sw);
0187 }
0188
0189 INIT_WORK(&hisi_hikey_usb->work, relay_set_role_switch);
0190
0191 hub_role_switch.fwnode = dev_fwnode(dev);
0192 hub_role_switch.set = hub_usb_role_switch_set;
0193 hub_role_switch.driver_data = hisi_hikey_usb;
0194
0195 hisi_hikey_usb->hub_role_sw = usb_role_switch_register(dev,
0196 &hub_role_switch);
0197
0198 if (IS_ERR(hisi_hikey_usb->hub_role_sw)) {
0199 dev_err(dev,
0200 "failed to register hub role with error %ld\n",
0201 PTR_ERR(hisi_hikey_usb->hub_role_sw));
0202 usb_role_switch_put(hisi_hikey_usb->dev_role_sw);
0203 return PTR_ERR(hisi_hikey_usb->hub_role_sw);
0204 }
0205
0206 return 0;
0207 }
0208
0209 static int hisi_hikey_usb_probe(struct platform_device *pdev)
0210 {
0211 struct device *dev = &pdev->dev;
0212 struct hisi_hikey_usb *hisi_hikey_usb;
0213 int ret;
0214
0215 hisi_hikey_usb = devm_kzalloc(dev, sizeof(*hisi_hikey_usb), GFP_KERNEL);
0216 if (!hisi_hikey_usb)
0217 return -ENOMEM;
0218
0219 hisi_hikey_usb->dev = &pdev->dev;
0220 mutex_init(&hisi_hikey_usb->lock);
0221
0222 hisi_hikey_usb->regulator = devm_regulator_get(dev, "hub-vdd");
0223 if (IS_ERR(hisi_hikey_usb->regulator)) {
0224 if (PTR_ERR(hisi_hikey_usb->regulator) == -EPROBE_DEFER) {
0225 dev_info(dev, "waiting for hub-vdd-supply\n");
0226 return PTR_ERR(hisi_hikey_usb->regulator);
0227 }
0228 dev_err(dev, "get hub-vdd-supply failed with error %ld\n",
0229 PTR_ERR(hisi_hikey_usb->regulator));
0230 return PTR_ERR(hisi_hikey_usb->regulator);
0231 }
0232
0233 ret = hisi_hikey_usb_of_role_switch(pdev, hisi_hikey_usb);
0234 if (ret)
0235 return ret;
0236
0237 platform_set_drvdata(pdev, hisi_hikey_usb);
0238
0239 return 0;
0240 }
0241
0242 static int hisi_hikey_usb_remove(struct platform_device *pdev)
0243 {
0244 struct hisi_hikey_usb *hisi_hikey_usb = platform_get_drvdata(pdev);
0245
0246 if (hisi_hikey_usb->hub_role_sw) {
0247 usb_role_switch_unregister(hisi_hikey_usb->hub_role_sw);
0248
0249 if (hisi_hikey_usb->dev_role_sw)
0250 usb_role_switch_put(hisi_hikey_usb->dev_role_sw);
0251 } else {
0252 hub_power_ctrl(hisi_hikey_usb, HUB_VBUS_POWER_OFF);
0253 }
0254
0255 return 0;
0256 }
0257
0258 static const struct of_device_id id_table_hisi_hikey_usb[] = {
0259 { .compatible = "hisilicon,usbhub" },
0260 {}
0261 };
0262 MODULE_DEVICE_TABLE(of, id_table_hisi_hikey_usb);
0263
0264 static struct platform_driver hisi_hikey_usb_driver = {
0265 .probe = hisi_hikey_usb_probe,
0266 .remove = hisi_hikey_usb_remove,
0267 .driver = {
0268 .name = DEVICE_DRIVER_NAME,
0269 .of_match_table = id_table_hisi_hikey_usb,
0270 },
0271 };
0272
0273 module_platform_driver(hisi_hikey_usb_driver);
0274
0275 MODULE_AUTHOR("Yu Chen <chenyu56@huawei.com>");
0276 MODULE_DESCRIPTION("Driver Support for USB functionality of Hikey");
0277 MODULE_LICENSE("GPL v2");