aboutsummaryrefslogtreecommitdiff
path: root/drivers/misc
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/misc')
-rw-r--r--drivers/misc/Kconfig7
-rw-r--r--drivers/misc/Makefile1
-rw-r--r--drivers/misc/hisi_hikey_usb.c319
3 files changed, 327 insertions, 0 deletions
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..4965719c99ae
--- /dev/null
+++ b/drivers/misc/hisi_hikey_usb.c
@@ -0,0 +1,319 @@
+// 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/gpio.h>
+#include <linux/of_gpio.h>
+#include <linux/extcon-provider.h>
+#include <linux/usb/role.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
+
+#define INVALID_GPIO_VALUE (-1)
+
+struct hisi_hikey_usb {
+ int otg_switch_gpio;
+ int typec_vbus_gpio;
+ int typec_vbus_enable_val;
+ int hub_vbus_gpio;
+
+ struct extcon_dev *edev;
+ struct usb_role_switch *role_sw;
+};
+
+static const unsigned int usb_extcon_cable[] = {
+ EXTCON_USB,
+ EXTCON_USB_HOST,
+ EXTCON_NONE,
+};
+
+static void hub_power_ctrl(struct hisi_hikey_usb *hisi_hikey_usb, int value)
+{
+ int gpio = hisi_hikey_usb->hub_vbus_gpio;
+
+ if (gpio_is_valid(gpio))
+ gpio_set_value(gpio, value);
+}
+
+static void usb_switch_ctrl(struct hisi_hikey_usb *hisi_hikey_usb,
+ int switch_to)
+{
+ int gpio = hisi_hikey_usb->otg_switch_gpio;
+ const char *switch_to_str = (switch_to == USB_SWITCH_TO_HUB) ?
+ "hub" : "typec";
+
+ if (!gpio_is_valid(gpio)) {
+ pr_err("%s: otg_switch_gpio is err\n", __func__);
+ return;
+ }
+
+ if (gpio_get_value(gpio) == switch_to) {
+ pr_info("%s: already switch to %s\n", __func__, switch_to_str);
+ return;
+ }
+
+ gpio_direction_output(gpio, switch_to);
+ pr_info("%s: switch to %s\n", __func__, switch_to_str);
+}
+
+static void usb_typec_power_ctrl(struct hisi_hikey_usb *hisi_hikey_usb,
+ int value)
+{
+ int gpio = hisi_hikey_usb->typec_vbus_gpio;
+
+ if (!gpio_is_valid(gpio)) {
+ pr_err("%s: typec power gpio is err\n", __func__);
+ return;
+ }
+
+ if (gpio_get_value(gpio) == value) {
+ pr_info("%s: typec power no change\n", __func__);
+ return;
+ }
+
+ gpio_direction_output(gpio, value);
+ pr_info("%s: set typec vbus gpio to %d\n", __func__, value);
+}
+
+static int extcon_hisi_pd_set_role(struct device *dev, enum usb_role role)
+{
+ struct hisi_hikey_usb *hisi_hikey_usb = dev_get_drvdata(dev);
+
+ dev_info(dev, "%s:set usb role to %d\n", __func__, role);
+ switch (role) {
+ case USB_ROLE_NONE:
+ usb_switch_ctrl(hisi_hikey_usb, USB_SWITCH_TO_HUB);
+ usb_typec_power_ctrl(hisi_hikey_usb,
+ !hisi_hikey_usb->typec_vbus_enable_val);
+ hub_power_ctrl(hisi_hikey_usb, HUB_VBUS_POWER_ON);
+ extcon_set_state_sync(hisi_hikey_usb->edev, EXTCON_USB, false);
+ extcon_set_state_sync(hisi_hikey_usb->edev, EXTCON_USB_HOST,
+ true);
+ 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);
+ extcon_set_state_sync(hisi_hikey_usb->edev, EXTCON_USB, false);
+ extcon_set_state_sync(hisi_hikey_usb->edev, EXTCON_USB_HOST,
+ true);
+ 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);
+ extcon_set_state_sync(hisi_hikey_usb->edev, EXTCON_USB_HOST,
+ false);
+ extcon_set_state_sync(hisi_hikey_usb->edev, EXTCON_USB, true);
+ break;
+ }
+
+ return 0;
+}
+
+static enum usb_role extcon_hisi_pd_get_role(struct device *dev)
+{
+ struct hisi_hikey_usb *hisi_hikey_usb = dev_get_drvdata(dev);
+
+ return usb_role_switch_get_role(hisi_hikey_usb->role_sw);
+}
+
+static const struct usb_role_switch_desc sw_desc = {
+ .set = extcon_hisi_pd_set_role,
+ .get = extcon_hisi_pd_get_role,
+ .allow_userspace_control = true,
+};
+
+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;
+
+ dev_set_name(dev, "hisi_hikey_usb");
+
+ hisi_hikey_usb->hub_vbus_gpio = INVALID_GPIO_VALUE;
+ hisi_hikey_usb->otg_switch_gpio = INVALID_GPIO_VALUE;
+ hisi_hikey_usb->typec_vbus_gpio = INVALID_GPIO_VALUE;
+
+ hisi_hikey_usb->hub_vbus_gpio = of_get_named_gpio(root,
+ "hub_vdd33_en_gpio", 0);
+ if (!gpio_is_valid(hisi_hikey_usb->hub_vbus_gpio)) {
+ pr_err("%s: hub_vbus_gpio is err\n", __func__);
+ return hisi_hikey_usb->hub_vbus_gpio;
+ }
+
+ ret = gpio_request(hisi_hikey_usb->hub_vbus_gpio, "hub_vbus_int_gpio");
+ if (ret) {
+ pr_err("%s: request hub_vbus_gpio err\n", __func__);
+ hisi_hikey_usb->hub_vbus_gpio = INVALID_GPIO_VALUE;
+ return ret;
+ }
+
+ ret = gpio_direction_output(hisi_hikey_usb->hub_vbus_gpio,
+ HUB_VBUS_POWER_ON);
+ if (ret) {
+ pr_err("%s: power on hub vbus err\n", __func__);
+ goto free_gpio1;
+ }
+
+ hisi_hikey_usb->typec_vbus_gpio = of_get_named_gpio(root,
+ "typc_vbus_int_gpio,typec-gpios", 0);
+ if (!gpio_is_valid(hisi_hikey_usb->typec_vbus_gpio)) {
+ pr_err("%s: typec_vbus_gpio is err\n", __func__);
+ ret = hisi_hikey_usb->typec_vbus_gpio;
+ goto free_gpio1;
+ }
+
+ ret = gpio_request(hisi_hikey_usb->typec_vbus_gpio,
+ "typc_vbus_int_gpio");
+ if (ret) {
+ pr_err("%s: request typec_vbus_gpio err\n", __func__);
+ hisi_hikey_usb->typec_vbus_gpio = INVALID_GPIO_VALUE;
+ goto free_gpio1;
+ }
+
+ ret = of_property_read_u32(root, "typc_vbus_enable_val",
+ &hisi_hikey_usb->typec_vbus_enable_val);
+ if (ret) {
+ pr_err("%s: typc_vbus_enable_val can't get\n", __func__);
+ goto free_gpio2;
+ }
+
+ hisi_hikey_usb->typec_vbus_enable_val =
+ !!hisi_hikey_usb->typec_vbus_enable_val;
+
+ ret = gpio_direction_output(hisi_hikey_usb->typec_vbus_gpio,
+ hisi_hikey_usb->typec_vbus_enable_val);
+ if (ret) {
+ pr_err("%s: power on typec vbus err", __func__);
+ goto free_gpio2;
+ }
+
+ if (of_device_is_compatible(root, "hisilicon,hikey960_usb")) {
+ hisi_hikey_usb->otg_switch_gpio = of_get_named_gpio(root,
+ "otg_gpio", 0);
+ if (!gpio_is_valid(hisi_hikey_usb->otg_switch_gpio)) {
+ pr_info("%s: otg_switch_gpio is err\n", __func__);
+ goto free_gpio2;
+ }
+
+ ret = gpio_request(hisi_hikey_usb->otg_switch_gpio,
+ "otg_switch_gpio");
+ if (ret) {
+ hisi_hikey_usb->otg_switch_gpio = INVALID_GPIO_VALUE;
+ pr_err("%s: request typec_vbus_gpio err\n", __func__);
+ goto free_gpio2;
+ }
+ }
+
+ hisi_hikey_usb->edev = devm_extcon_dev_allocate(dev, usb_extcon_cable);
+ if (IS_ERR(hisi_hikey_usb->edev)) {
+ dev_err(dev, "failed to allocate extcon device\n");
+ goto free_gpio2;
+ }
+
+ ret = devm_extcon_dev_register(dev, hisi_hikey_usb->edev);
+ if (ret < 0) {
+ dev_err(dev, "failed to register extcon device\n");
+ goto free_gpio2;
+ }
+ extcon_set_state(hisi_hikey_usb->edev, EXTCON_USB_HOST, true);
+
+ hisi_hikey_usb->role_sw = usb_role_switch_register(dev, &sw_desc);
+ if (IS_ERR(hisi_hikey_usb->role_sw))
+ goto free_gpio2;
+
+ platform_set_drvdata(pdev, hisi_hikey_usb);
+
+ return 0;
+
+free_gpio2:
+ if (gpio_is_valid(hisi_hikey_usb->typec_vbus_gpio)) {
+ gpio_free(hisi_hikey_usb->typec_vbus_gpio);
+ hisi_hikey_usb->typec_vbus_gpio = INVALID_GPIO_VALUE;
+ }
+
+free_gpio1:
+ if (gpio_is_valid(hisi_hikey_usb->hub_vbus_gpio)) {
+ gpio_free(hisi_hikey_usb->hub_vbus_gpio);
+ hisi_hikey_usb->hub_vbus_gpio = INVALID_GPIO_VALUE;
+ }
+
+ return ret;
+}
+
+static int hisi_hikey_usb_remove(struct platform_device *pdev)
+{
+ struct hisi_hikey_usb *hisi_hikey_usb = platform_get_drvdata(pdev);
+
+ if (gpio_is_valid(hisi_hikey_usb->otg_switch_gpio)) {
+ gpio_free(hisi_hikey_usb->otg_switch_gpio);
+ hisi_hikey_usb->otg_switch_gpio = INVALID_GPIO_VALUE;
+ }
+
+ if (gpio_is_valid(hisi_hikey_usb->typec_vbus_gpio)) {
+ gpio_free(hisi_hikey_usb->typec_vbus_gpio);
+ hisi_hikey_usb->typec_vbus_gpio = INVALID_GPIO_VALUE;
+ }
+
+ if (gpio_is_valid(hisi_hikey_usb->hub_vbus_gpio)) {
+ gpio_free(hisi_hikey_usb->hub_vbus_gpio);
+ hisi_hikey_usb->hub_vbus_gpio = INVALID_GPIO_VALUE;
+ }
+
+ usb_role_switch_unregister(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@huawei.com>");
+MODULE_DESCRIPTION("Driver Support for USB functionality of Hikey");
+MODULE_LICENSE("GPL v2");