1 // 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/regulator/consumer.h> 20 #include <linux/slab.h> 21 #include <linux/usb/role.h> 22 23 #define DEVICE_DRIVER_NAME "hisi_hikey_usb" 24 25 #define HUB_VBUS_POWER_ON 1 26 #define HUB_VBUS_POWER_OFF 0 27 #define USB_SWITCH_TO_HUB 1 28 #define USB_SWITCH_TO_TYPEC 0 29 #define TYPEC_VBUS_POWER_ON 1 30 #define TYPEC_VBUS_POWER_OFF 0 31 32 struct hisi_hikey_usb { 33 struct device *dev; 34 struct gpio_desc *otg_switch; 35 struct gpio_desc *typec_vbus; 36 struct gpio_desc *reset; 37 38 struct regulator *regulator; 39 40 struct usb_role_switch *hub_role_sw; 41 42 struct usb_role_switch *dev_role_sw; 43 enum usb_role role; 44 45 struct mutex lock; 46 struct work_struct work; 47 48 struct notifier_block nb; 49 }; 50 51 static void hub_power_ctrl(struct hisi_hikey_usb *hisi_hikey_usb, int value) 52 { 53 int ret, status; 54 55 if (!hisi_hikey_usb->regulator) 56 return; 57 58 status = regulator_is_enabled(hisi_hikey_usb->regulator); 59 if (status == !!value) 60 return; 61 62 if (value) 63 ret = regulator_enable(hisi_hikey_usb->regulator); 64 else 65 ret = regulator_disable(hisi_hikey_usb->regulator); 66 67 if (ret) 68 dev_err(hisi_hikey_usb->dev, 69 "Can't switch regulator state to %s\n", 70 value ? "enabled" : "disabled"); 71 } 72 73 static void usb_switch_ctrl(struct hisi_hikey_usb *hisi_hikey_usb, 74 int switch_to) 75 { 76 if (!hisi_hikey_usb->otg_switch) 77 return; 78 79 gpiod_set_value_cansleep(hisi_hikey_usb->otg_switch, switch_to); 80 } 81 82 static void usb_typec_power_ctrl(struct hisi_hikey_usb *hisi_hikey_usb, 83 int value) 84 { 85 if (!hisi_hikey_usb->typec_vbus) 86 return; 87 88 gpiod_set_value_cansleep(hisi_hikey_usb->typec_vbus, value); 89 } 90 91 static void relay_set_role_switch(struct work_struct *work) 92 { 93 struct hisi_hikey_usb *hisi_hikey_usb = container_of(work, 94 struct hisi_hikey_usb, 95 work); 96 struct usb_role_switch *sw; 97 enum usb_role role; 98 99 if (!hisi_hikey_usb || !hisi_hikey_usb->dev_role_sw) 100 return; 101 102 mutex_lock(&hisi_hikey_usb->lock); 103 switch (hisi_hikey_usb->role) { 104 case USB_ROLE_NONE: 105 usb_typec_power_ctrl(hisi_hikey_usb, TYPEC_VBUS_POWER_OFF); 106 usb_switch_ctrl(hisi_hikey_usb, USB_SWITCH_TO_HUB); 107 hub_power_ctrl(hisi_hikey_usb, HUB_VBUS_POWER_ON); 108 break; 109 case USB_ROLE_HOST: 110 hub_power_ctrl(hisi_hikey_usb, HUB_VBUS_POWER_OFF); 111 usb_switch_ctrl(hisi_hikey_usb, USB_SWITCH_TO_TYPEC); 112 usb_typec_power_ctrl(hisi_hikey_usb, TYPEC_VBUS_POWER_ON); 113 break; 114 case USB_ROLE_DEVICE: 115 hub_power_ctrl(hisi_hikey_usb, HUB_VBUS_POWER_OFF); 116 usb_typec_power_ctrl(hisi_hikey_usb, TYPEC_VBUS_POWER_OFF); 117 usb_switch_ctrl(hisi_hikey_usb, USB_SWITCH_TO_TYPEC); 118 break; 119 default: 120 break; 121 } 122 sw = hisi_hikey_usb->dev_role_sw; 123 role = hisi_hikey_usb->role; 124 mutex_unlock(&hisi_hikey_usb->lock); 125 126 usb_role_switch_set_role(sw, role); 127 } 128 129 static int hub_usb_role_switch_set(struct usb_role_switch *sw, enum usb_role role) 130 { 131 struct hisi_hikey_usb *hisi_hikey_usb = usb_role_switch_get_drvdata(sw); 132 133 if (!hisi_hikey_usb || !hisi_hikey_usb->dev_role_sw) 134 return -EINVAL; 135 136 mutex_lock(&hisi_hikey_usb->lock); 137 hisi_hikey_usb->role = role; 138 mutex_unlock(&hisi_hikey_usb->lock); 139 140 schedule_work(&hisi_hikey_usb->work); 141 142 return 0; 143 } 144 145 static int hisi_hikey_usb_of_role_switch(struct platform_device *pdev, 146 struct hisi_hikey_usb *hisi_hikey_usb) 147 { 148 struct device *dev = &pdev->dev; 149 struct usb_role_switch_desc hub_role_switch = {NULL}; 150 151 if (!device_property_read_bool(dev, "usb-role-switch")) 152 return 0; 153 154 hisi_hikey_usb->otg_switch = devm_gpiod_get(dev, "otg-switch", 155 GPIOD_OUT_HIGH); 156 if (IS_ERR(hisi_hikey_usb->otg_switch)) { 157 dev_err(dev, "get otg-switch failed with error %ld\n", 158 PTR_ERR(hisi_hikey_usb->otg_switch)); 159 return PTR_ERR(hisi_hikey_usb->otg_switch); 160 } 161 162 hisi_hikey_usb->typec_vbus = devm_gpiod_get(dev, "typec-vbus", 163 GPIOD_OUT_LOW); 164 if (IS_ERR(hisi_hikey_usb->typec_vbus)) { 165 dev_err(dev, "get typec-vbus failed with error %ld\n", 166 PTR_ERR(hisi_hikey_usb->typec_vbus)); 167 return PTR_ERR(hisi_hikey_usb->typec_vbus); 168 } 169 170 hisi_hikey_usb->reset = devm_gpiod_get_optional(dev, 171 "hub-reset-en", 172 GPIOD_OUT_HIGH); 173 if (IS_ERR(hisi_hikey_usb->reset)) { 174 dev_err(dev, "get hub-reset-en failed with error %ld\n", 175 PTR_ERR(hisi_hikey_usb->reset)); 176 return PTR_ERR(hisi_hikey_usb->reset); 177 } 178 179 hisi_hikey_usb->dev_role_sw = usb_role_switch_get(dev); 180 if (!hisi_hikey_usb->dev_role_sw) 181 return -EPROBE_DEFER; 182 if (IS_ERR(hisi_hikey_usb->dev_role_sw)) { 183 dev_err(dev, "get device role switch failed with error %ld\n", 184 PTR_ERR(hisi_hikey_usb->dev_role_sw)); 185 return PTR_ERR(hisi_hikey_usb->dev_role_sw); 186 } 187 188 INIT_WORK(&hisi_hikey_usb->work, relay_set_role_switch); 189 190 hub_role_switch.fwnode = dev_fwnode(dev); 191 hub_role_switch.set = hub_usb_role_switch_set; 192 hub_role_switch.driver_data = hisi_hikey_usb; 193 194 hisi_hikey_usb->hub_role_sw = usb_role_switch_register(dev, 195 &hub_role_switch); 196 197 if (IS_ERR(hisi_hikey_usb->hub_role_sw)) { 198 dev_err(dev, 199 "failed to register hub role with error %ld\n", 200 PTR_ERR(hisi_hikey_usb->hub_role_sw)); 201 usb_role_switch_put(hisi_hikey_usb->dev_role_sw); 202 return PTR_ERR(hisi_hikey_usb->hub_role_sw); 203 } 204 205 return 0; 206 } 207 208 static int hisi_hikey_usb_probe(struct platform_device *pdev) 209 { 210 struct device *dev = &pdev->dev; 211 struct hisi_hikey_usb *hisi_hikey_usb; 212 int ret; 213 214 hisi_hikey_usb = devm_kzalloc(dev, sizeof(*hisi_hikey_usb), GFP_KERNEL); 215 if (!hisi_hikey_usb) 216 return -ENOMEM; 217 218 hisi_hikey_usb->dev = &pdev->dev; 219 mutex_init(&hisi_hikey_usb->lock); 220 221 hisi_hikey_usb->regulator = devm_regulator_get(dev, "hub-vdd"); 222 if (IS_ERR(hisi_hikey_usb->regulator)) { 223 if (PTR_ERR(hisi_hikey_usb->regulator) == -EPROBE_DEFER) { 224 dev_info(dev, "waiting for hub-vdd-supply\n"); 225 return PTR_ERR(hisi_hikey_usb->regulator); 226 } 227 dev_err(dev, "get hub-vdd-supply failed with error %ld\n", 228 PTR_ERR(hisi_hikey_usb->regulator)); 229 return PTR_ERR(hisi_hikey_usb->regulator); 230 } 231 232 ret = hisi_hikey_usb_of_role_switch(pdev, hisi_hikey_usb); 233 if (ret) 234 return ret; 235 236 platform_set_drvdata(pdev, hisi_hikey_usb); 237 238 return 0; 239 } 240 241 static void hisi_hikey_usb_remove(struct platform_device *pdev) 242 { 243 struct hisi_hikey_usb *hisi_hikey_usb = platform_get_drvdata(pdev); 244 245 if (hisi_hikey_usb->hub_role_sw) { 246 usb_role_switch_unregister(hisi_hikey_usb->hub_role_sw); 247 248 if (hisi_hikey_usb->dev_role_sw) 249 usb_role_switch_put(hisi_hikey_usb->dev_role_sw); 250 } else { 251 hub_power_ctrl(hisi_hikey_usb, HUB_VBUS_POWER_OFF); 252 } 253 } 254 255 static const struct of_device_id id_table_hisi_hikey_usb[] = { 256 { .compatible = "hisilicon,usbhub" }, 257 {} 258 }; 259 MODULE_DEVICE_TABLE(of, id_table_hisi_hikey_usb); 260 261 static struct platform_driver hisi_hikey_usb_driver = { 262 .probe = hisi_hikey_usb_probe, 263 .remove_new = hisi_hikey_usb_remove, 264 .driver = { 265 .name = DEVICE_DRIVER_NAME, 266 .of_match_table = id_table_hisi_hikey_usb, 267 }, 268 }; 269 270 module_platform_driver(hisi_hikey_usb_driver); 271 272 MODULE_AUTHOR("Yu Chen <chenyu56@huawei.com>"); 273 MODULE_DESCRIPTION("Driver Support for USB functionality of Hikey"); 274 MODULE_LICENSE("GPL v2"); 275