// SPDX-License-Identifier: GPL-2.0 /* * Support for usb functionality of Hikey series boards * based on Hisilicon Kirin Soc. * * Copyright (C) 2017-2018 Hilisicon Electronics Co., Ltd. * http://www.huawei.com * * Authors: Yu Chen */ #include #include #include #include #include #include #include #include #include #include #include #define DEVICE_DRIVER_NAME "hisi_hikey_usb" #define HUB_VBUS_POWER_ON 1 #define HUB_VBUS_POWER_OFF 0 #define USB_SWITCH_TO_HUB 1 #define USB_SWITCH_TO_TYPEC 0 #define TYPEC_VBUS_POWER_ON 1 #define TYPEC_VBUS_POWER_OFF 0 struct hisi_hikey_usb { struct device *dev; struct gpio_desc *otg_switch; struct gpio_desc *typec_vbus; struct gpio_desc *hub_vbus; struct gpio_desc *reset; struct regulator *regulator; struct usb_role_switch *hub_role_sw; struct usb_role_switch *dev_role_sw; enum usb_role role; struct mutex lock; struct work_struct work; struct notifier_block nb; }; static void hub_power_ctrl(struct hisi_hikey_usb *hisi_hikey_usb, int value) { int ret, status; if (hisi_hikey_usb->hub_vbus) gpiod_set_value_cansleep(hisi_hikey_usb->hub_vbus, value); if (!hisi_hikey_usb->regulator) return; status = regulator_is_enabled(hisi_hikey_usb->regulator); if (status == !!value) return; if (value) ret = regulator_enable(hisi_hikey_usb->regulator); else ret = regulator_disable(hisi_hikey_usb->regulator); if (ret) dev_err(hisi_hikey_usb->dev, "Can't switch regulator state to %s\n", value ? "enabled" : "disabled"); } static void usb_switch_ctrl(struct hisi_hikey_usb *hisi_hikey_usb, int switch_to) { if (!hisi_hikey_usb->otg_switch) return; gpiod_set_value_cansleep(hisi_hikey_usb->otg_switch, switch_to); } static void usb_typec_power_ctrl(struct hisi_hikey_usb *hisi_hikey_usb, int value) { if (!hisi_hikey_usb->typec_vbus) return; gpiod_set_value_cansleep(hisi_hikey_usb->typec_vbus, value); } static void relay_set_role_switch(struct work_struct *work) { struct hisi_hikey_usb *hisi_hikey_usb = container_of(work, struct hisi_hikey_usb, work); struct usb_role_switch *sw; enum usb_role role; if (!hisi_hikey_usb || !hisi_hikey_usb->dev_role_sw) return; mutex_lock(&hisi_hikey_usb->lock); switch (hisi_hikey_usb->role) { case USB_ROLE_NONE: usb_typec_power_ctrl(hisi_hikey_usb, TYPEC_VBUS_POWER_OFF); usb_switch_ctrl(hisi_hikey_usb, USB_SWITCH_TO_HUB); hub_power_ctrl(hisi_hikey_usb, HUB_VBUS_POWER_ON); break; case USB_ROLE_HOST: hub_power_ctrl(hisi_hikey_usb, HUB_VBUS_POWER_OFF); usb_switch_ctrl(hisi_hikey_usb, USB_SWITCH_TO_TYPEC); usb_typec_power_ctrl(hisi_hikey_usb, TYPEC_VBUS_POWER_ON); break; case USB_ROLE_DEVICE: hub_power_ctrl(hisi_hikey_usb, HUB_VBUS_POWER_OFF); usb_typec_power_ctrl(hisi_hikey_usb, TYPEC_VBUS_POWER_OFF); usb_switch_ctrl(hisi_hikey_usb, USB_SWITCH_TO_TYPEC); break; default: break; } sw = hisi_hikey_usb->dev_role_sw; role = hisi_hikey_usb->role; mutex_unlock(&hisi_hikey_usb->lock); usb_role_switch_set_role(sw, role); } static int hub_usb_role_switch_set(struct usb_role_switch *sw, enum usb_role role) { struct hisi_hikey_usb *hisi_hikey_usb = usb_role_switch_get_drvdata(sw); if (!hisi_hikey_usb || !hisi_hikey_usb->dev_role_sw) return -EINVAL; mutex_lock(&hisi_hikey_usb->lock); hisi_hikey_usb->role = role; mutex_unlock(&hisi_hikey_usb->lock); schedule_work(&hisi_hikey_usb->work); return 0; } static int hisi_hikey_usb_parse_kirin970(struct platform_device *pdev, struct hisi_hikey_usb *hisi_hikey_usb) { struct regulator *regulator; regulator = devm_regulator_get(&pdev->dev, "hub-vdd"); if (IS_ERR(regulator)) { if (PTR_ERR(regulator) == -EPROBE_DEFER) { dev_info(&pdev->dev, "waiting for hub-vdd-supply to be probed\n"); return PTR_ERR(regulator); } dev_err(&pdev->dev, "get hub-vdd-supply failed with error %ld\n", PTR_ERR(regulator)); return PTR_ERR(regulator); } hisi_hikey_usb->regulator = regulator; hisi_hikey_usb->reset = devm_gpiod_get(&pdev->dev, "hub_reset_en_gpio", GPIOD_OUT_HIGH); if (IS_ERR(hisi_hikey_usb->reset)) return PTR_ERR(hisi_hikey_usb->reset); return 0; } static int hisi_hikey_usb_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct hisi_hikey_usb *hisi_hikey_usb; struct usb_role_switch_desc hub_role_switch = {NULL}; int ret; hisi_hikey_usb = devm_kzalloc(dev, sizeof(*hisi_hikey_usb), GFP_KERNEL); if (!hisi_hikey_usb) return -ENOMEM; hisi_hikey_usb->dev = &pdev->dev; hisi_hikey_usb->otg_switch = devm_gpiod_get(dev, "otg-switch", GPIOD_OUT_HIGH); if (IS_ERR(hisi_hikey_usb->otg_switch)) return PTR_ERR(hisi_hikey_usb->otg_switch); hisi_hikey_usb->typec_vbus = devm_gpiod_get(dev, "typec-vbus", GPIOD_OUT_LOW); if (IS_ERR(hisi_hikey_usb->typec_vbus)) return PTR_ERR(hisi_hikey_usb->typec_vbus); /* Parse Kirin 970-specific OF data */ if (of_device_is_compatible(pdev->dev.of_node, "hisilicon,kirin970_hikey_usbhub")) { ret = hisi_hikey_usb_parse_kirin970(pdev, hisi_hikey_usb); if (ret) return ret; } else { /* hub-vdd33-en is optional */ hisi_hikey_usb->hub_vbus = devm_gpiod_get_optional(dev, "hub-vdd33-en", GPIOD_OUT_HIGH); if (IS_ERR(hisi_hikey_usb->hub_vbus)) return PTR_ERR(hisi_hikey_usb->hub_vbus); } hisi_hikey_usb->dev_role_sw = usb_role_switch_get(dev); if (!hisi_hikey_usb->dev_role_sw) return -EPROBE_DEFER; if (IS_ERR(hisi_hikey_usb->dev_role_sw)) return PTR_ERR(hisi_hikey_usb->dev_role_sw); INIT_WORK(&hisi_hikey_usb->work, relay_set_role_switch); mutex_init(&hisi_hikey_usb->lock); hub_role_switch.fwnode = dev_fwnode(dev); hub_role_switch.set = hub_usb_role_switch_set; hub_role_switch.driver_data = hisi_hikey_usb; hisi_hikey_usb->hub_role_sw = usb_role_switch_register(dev, &hub_role_switch); if (IS_ERR(hisi_hikey_usb->hub_role_sw)) { usb_role_switch_put(hisi_hikey_usb->dev_role_sw); return PTR_ERR(hisi_hikey_usb->hub_role_sw); } platform_set_drvdata(pdev, hisi_hikey_usb); return 0; } static int hisi_hikey_usb_remove(struct platform_device *pdev) { struct hisi_hikey_usb *hisi_hikey_usb = platform_get_drvdata(pdev); if (hisi_hikey_usb->hub_role_sw) usb_role_switch_unregister(hisi_hikey_usb->hub_role_sw); if (hisi_hikey_usb->dev_role_sw) usb_role_switch_put(hisi_hikey_usb->dev_role_sw); return 0; } static const struct of_device_id id_table_hisi_hikey_usb[] = { { .compatible = "hisilicon,gpio_hubv1" }, { .compatible = "hisilicon,kirin970_hikey_usbhub" }, {} }; MODULE_DEVICE_TABLE(of, id_table_hisi_hikey_usb); static struct platform_driver hisi_hikey_usb_driver = { .probe = hisi_hikey_usb_probe, .remove = hisi_hikey_usb_remove, .driver = { .name = DEVICE_DRIVER_NAME, .of_match_table = id_table_hisi_hikey_usb, }, }; module_platform_driver(hisi_hikey_usb_driver); MODULE_AUTHOR("Yu Chen "); MODULE_DESCRIPTION("Driver Support for USB functionality of Hikey"); MODULE_LICENSE("GPL v2");