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