This driver handles usb hub power on and typeC port event of HiKey960 board: 1)DP&DM switching between usb hub and typeC port base on typeC port state 2)Control power of usb hub on Hikey960 3)Control vbus of typeC port Cc: Arnd Bergmann <arnd@xxxxxxxx> Cc: Greg Kroah-Hartman <gregkh@xxxxxxxxxxxxxxxxxxx> Cc: John Stultz <john.stultz@xxxxxxxxxx> Cc: Binghui Wang <wangbinghui@xxxxxxxxxxxxx> Cc: Heikki Krogerus <heikki.krogerus@xxxxxxxxxxxxxxx> Signed-off-by: Yu Chen <chenyu56@xxxxxxxxxx> -- v1: 1) Using gpiod API with the gpios. 2) Removing registering usb role switch. 3) Registering usb role switch notifier. -- --- drivers/misc/Kconfig | 7 ++ drivers/misc/Makefile | 1 + drivers/misc/hisi_hikey_usb.c | 198 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 206 insertions(+) create mode 100644 drivers/misc/hisi_hikey_usb.c diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 3726eacdf65d..8e04fc87b685 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -513,6 +513,13 @@ config MISC_RTSX tristate default MISC_RTSX_PCI || MISC_RTSX_USB +config HISI_HIKEY_USB + tristate "USB functionality of HiSilicon Hikey Platform" + depends on GPIOLIB + default n + help + If you say yes here you get support for usb functionality of HiSilicon Hikey Platform. + source "drivers/misc/c2port/Kconfig" source "drivers/misc/eeprom/Kconfig" source "drivers/misc/cb710/Kconfig" diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index af22bbc3d00c..387dd302815c 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -58,3 +58,4 @@ obj-$(CONFIG_ASPEED_LPC_SNOOP) += aspeed-lpc-snoop.o obj-$(CONFIG_PCI_ENDPOINT_TEST) += pci_endpoint_test.o obj-$(CONFIG_OCXL) += ocxl/ obj-$(CONFIG_MISC_RTSX) += cardreader/ +obj-$(CONFIG_HISI_HIKEY_USB) += hisi_hikey_usb.o diff --git a/drivers/misc/hisi_hikey_usb.c b/drivers/misc/hisi_hikey_usb.c new file mode 100644 index 000000000000..f320ed878e44 --- /dev/null +++ b/drivers/misc/hisi_hikey_usb.c @@ -0,0 +1,198 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * hisi_hikey_usb.c + * + * Copyright (c) Hisilicon Tech. Co., Ltd. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/of.h> +#include <linux/gpio/consumer.h> +#include <linux/usb/role.h> +#include <linux/notifier.h> + +#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 + +struct hisi_hikey_usb { + struct gpio_desc *otg_switch; + struct gpio_desc *typec_vbus; + struct gpio_desc *hub_vbus; + int typec_vbus_enable_val; + + struct usb_role_switch *role_sw; + struct notifier_block nb; +}; + +static void hub_power_ctrl(struct hisi_hikey_usb *hisi_hikey_usb, int value) +{ + pr_debug("%s: set hub power %d\n", __func__, value); + gpiod_set_value_cansleep(hisi_hikey_usb->hub_vbus, value); +} + +static void usb_switch_ctrl(struct hisi_hikey_usb *hisi_hikey_usb, + int switch_to) +{ + const char *switch_to_str = (switch_to == USB_SWITCH_TO_HUB) ? + "hub" : "typec"; + + pr_debug("%s: switch to %s\n", __func__, switch_to_str); + 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) +{ + pr_debug("%s: set typec vbus gpio to %d\n", __func__, value); + gpiod_set_value_cansleep(hisi_hikey_usb->typec_vbus, value); +} + +static int hisi_hikey_role_switch(struct notifier_block *nb, + unsigned long state, void *data) +{ + struct hisi_hikey_usb *hisi_hikey_usb; + + if (!nb) + return -EINVAL; + + hisi_hikey_usb = container_of(nb, struct hisi_hikey_usb, nb); + + pr_info("%s:set typec state to %lu\n", __func__, state); + switch (state) { + case USB_ROLE_NONE: + usb_typec_power_ctrl(hisi_hikey_usb, + !hisi_hikey_usb->typec_vbus_enable_val); + 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: + usb_switch_ctrl(hisi_hikey_usb, USB_SWITCH_TO_TYPEC); + usb_typec_power_ctrl(hisi_hikey_usb, + hisi_hikey_usb->typec_vbus_enable_val); + break; + case USB_ROLE_DEVICE: + hub_power_ctrl(hisi_hikey_usb, HUB_VBUS_POWER_OFF); + usb_typec_power_ctrl(hisi_hikey_usb, + !hisi_hikey_usb->typec_vbus_enable_val); + usb_switch_ctrl(hisi_hikey_usb, USB_SWITCH_TO_TYPEC); + break; + default: + break; + } + + return 0; +} + +static int hisi_hikey_usb_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *root = dev->of_node; + struct hisi_hikey_usb *hisi_hikey_usb; + int ret; + + hisi_hikey_usb = devm_kzalloc(dev, sizeof(*hisi_hikey_usb), GFP_KERNEL); + if (!hisi_hikey_usb) + return -ENOMEM; + + hisi_hikey_usb->nb.notifier_call = hisi_hikey_role_switch; + + ret = of_property_read_u32(root, "typc-vbus-enable-val", + &hisi_hikey_usb->typec_vbus_enable_val); + if (ret) { + pr_info("%s: typc_vbus_enable_val can't get\n", __func__); + hisi_hikey_usb->typec_vbus_enable_val = 1; + } + + hisi_hikey_usb->typec_vbus = devm_gpiod_get(dev, "typec-vbus", + hisi_hikey_usb->typec_vbus_enable_val ? + GPIOD_OUT_LOW : GPIOD_OUT_HIGH); + if (IS_ERR_OR_NULL(hisi_hikey_usb->typec_vbus)) { + if (!hisi_hikey_usb->typec_vbus) + ret = -EINVAL; + else + ret = PTR_ERR(hisi_hikey_usb->typec_vbus); + return ret; + } + + hisi_hikey_usb->otg_switch = devm_gpiod_get(dev, "otg-switch", GPIOD_IN); + if (IS_ERR(hisi_hikey_usb->otg_switch)) + return PTR_ERR(hisi_hikey_usb->otg_switch); + + hisi_hikey_usb->hub_vbus = devm_gpiod_get(dev, "hub-vdd33-en", + GPIOD_OUT_LOW); + if (IS_ERR(hisi_hikey_usb->hub_vbus)) + return PTR_ERR(hisi_hikey_usb->hub_vbus); + + hisi_hikey_usb->role_sw = usb_role_switch_get(dev); + if (IS_ERR_OR_NULL(hisi_hikey_usb->role_sw)) { + pr_err("%s: usb_role_switch_get failed\n", __func__); + if (!hisi_hikey_usb->role_sw) + ret = -ENOENT; + else + ret = PTR_ERR(hisi_hikey_usb->role_sw); + return ret; + } + + ret = usb_role_switch_register_notifier(hisi_hikey_usb->role_sw, + &hisi_hikey_usb->nb); + if (ret) { + usb_role_switch_put(hisi_hikey_usb->role_sw); + return ret; + } + + 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); + + usb_role_switch_unregister_notifier(hisi_hikey_usb->role_sw, + &hisi_hikey_usb->nb); + + usb_role_switch_put(hisi_hikey_usb->role_sw); + + return 0; +} + +static const struct of_device_id id_table_hisi_hikey_usb[] = { + {.compatible = "hisilicon,gpio_hubv1"}, + {.compatible = "hisilicon,hikey960_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 = of_match_ptr(id_table_hisi_hikey_usb), + + }, +}; + +module_platform_driver(hisi_hikey_usb_driver); + +MODULE_AUTHOR("Yu Chen <chenyu56@xxxxxxxxxx>"); +MODULE_DESCRIPTION("Driver Support for USB functionality of Hikey"); +MODULE_LICENSE("GPL v2"); -- 2.15.0-rc2