Update Linux to v5.4.2
Change-Id: Idf6911045d9d382da2cfe01b1edff026404ac8fd
diff --git a/drivers/usb/common/Kconfig b/drivers/usb/common/Kconfig
new file mode 100644
index 0000000..d611477
--- /dev/null
+++ b/drivers/usb/common/Kconfig
@@ -0,0 +1,51 @@
+# SPDX-License-Identifier: GPL-2.0
+
+config USB_COMMON
+ tristate
+
+
+config USB_LED_TRIG
+ bool "USB LED Triggers"
+ depends on LEDS_CLASS && LEDS_TRIGGERS
+ select USB_COMMON
+ help
+ This option adds LED triggers for USB host and/or gadget activity.
+
+ Say Y here if you are working on a system with led-class supported
+ LEDs and you want to use them as activity indicators for USB host or
+ gadget.
+
+config USB_ULPI_BUS
+ tristate "USB ULPI PHY interface support"
+ select USB_COMMON
+ help
+ UTMI+ Low Pin Interface (ULPI) is specification for a commonly used
+ USB 2.0 PHY interface. The ULPI specification defines a standard set
+ of registers that can be used to detect the vendor and product which
+ allows ULPI to be handled as a bus. This module is the driver for that
+ bus.
+
+ The ULPI interfaces (the buses) are registered by the drivers for USB
+ controllers which support ULPI register access and have ULPI PHY
+ attached to them. The ULPI PHY drivers themselves are normal PHY
+ drivers.
+
+ ULPI PHYs provide often functions such as ADP sensing/probing (OTG
+ protocol) and USB charger detection.
+
+ To compile this driver as a module, choose M here: the module will
+ be called ulpi.
+
+config USB_CONN_GPIO
+ tristate "USB GPIO Based Connection Detection Driver"
+ depends on GPIOLIB
+ select USB_ROLE_SWITCH
+ help
+ The driver supports USB role switch between host and device via GPIO
+ based USB cable detection, used typically if an input GPIO is used
+ to detect USB ID pin, and another input GPIO may be also used to detect
+ Vbus pin at the same time, it also can be used to enable/disable
+ device if an input GPIO is only used to detect Vbus pin.
+
+ To compile the driver as a module, choose M here: the module will
+ be called usb-conn-gpio.ko
diff --git a/drivers/usb/common/Makefile b/drivers/usb/common/Makefile
index fb4d5ef..8ac4d21 100644
--- a/drivers/usb/common/Makefile
+++ b/drivers/usb/common/Makefile
@@ -5,8 +5,9 @@
obj-$(CONFIG_USB_COMMON) += usb-common.o
usb-common-y += common.o
+usb-common-$(CONFIG_TRACING) += debug.o
usb-common-$(CONFIG_USB_LED_TRIG) += led.o
+obj-$(CONFIG_USB_CONN_GPIO) += usb-conn-gpio.o
obj-$(CONFIG_USB_OTG_FSM) += usb-otg-fsm.o
obj-$(CONFIG_USB_ULPI_BUS) += ulpi.o
-obj-$(CONFIG_USB_ROLE_SWITCH) += roles.o
diff --git a/drivers/usb/common/common.c b/drivers/usb/common/common.c
index 48277bb..1433260 100644
--- a/drivers/usb/common/common.c
+++ b/drivers/usb/common/common.c
@@ -15,6 +15,24 @@
#include <linux/usb/of.h>
#include <linux/usb/otg.h>
#include <linux/of_platform.h>
+#include <linux/debugfs.h>
+#include "common.h"
+
+static const char *const ep_type_names[] = {
+ [USB_ENDPOINT_XFER_CONTROL] = "ctrl",
+ [USB_ENDPOINT_XFER_ISOC] = "isoc",
+ [USB_ENDPOINT_XFER_BULK] = "bulk",
+ [USB_ENDPOINT_XFER_INT] = "intr",
+};
+
+const char *usb_ep_type_string(int ep_type)
+{
+ if (ep_type < 0 || ep_type >= ARRAY_SIZE(ep_type_names))
+ return "unknown";
+
+ return ep_type_names[ep_type];
+}
+EXPORT_SYMBOL_GPL(usb_ep_type_string);
const char *usb_otg_state_string(enum usb_otg_state state)
{
@@ -145,6 +163,8 @@
do {
controller = of_find_node_with_property(controller, "phys");
+ if (!of_device_is_available(controller))
+ continue;
index = 0;
do {
if (arg0 == -1) {
@@ -273,4 +293,23 @@
EXPORT_SYMBOL_GPL(usb_of_get_companion_dev);
#endif
+struct dentry *usb_debug_root;
+EXPORT_SYMBOL_GPL(usb_debug_root);
+
+static int __init usb_common_init(void)
+{
+ usb_debug_root = debugfs_create_dir("usb", NULL);
+ ledtrig_usb_init();
+ return 0;
+}
+
+static void __exit usb_common_exit(void)
+{
+ ledtrig_usb_exit();
+ debugfs_remove_recursive(usb_debug_root);
+}
+
+subsys_initcall(usb_common_init);
+module_exit(usb_common_exit);
+
MODULE_LICENSE("GPL");
diff --git a/drivers/usb/common/common.h b/drivers/usb/common/common.h
new file mode 100644
index 0000000..424a913
--- /dev/null
+++ b/drivers/usb/common/common.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef __LINUX_USB_COMMON_H
+#define __LINUX_USB_COMMON_H
+
+#if defined(CONFIG_USB_LED_TRIG)
+void ledtrig_usb_init(void);
+void ledtrig_usb_exit(void);
+#else
+static inline void ledtrig_usb_init(void) { }
+static inline void ledtrig_usb_exit(void) { }
+#endif
+
+#endif /* __LINUX_USB_COMMON_H */
diff --git a/drivers/usb/common/debug.c b/drivers/usb/common/debug.c
new file mode 100644
index 0000000..92a986a
--- /dev/null
+++ b/drivers/usb/common/debug.c
@@ -0,0 +1,268 @@
+// SPDX-License-Identifier: GPL-2.0
+/**
+ * Common USB debugging functions
+ *
+ * Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com
+ *
+ * Authors: Felipe Balbi <balbi@ti.com>,
+ * Sebastian Andrzej Siewior <bigeasy@linutronix.de>
+ */
+
+#include <linux/usb/ch9.h>
+
+static void usb_decode_get_status(__u8 bRequestType, __u16 wIndex,
+ __u16 wLength, char *str, size_t size)
+{
+ switch (bRequestType & USB_RECIP_MASK) {
+ case USB_RECIP_DEVICE:
+ snprintf(str, size, "Get Device Status(Length = %d)", wLength);
+ break;
+ case USB_RECIP_INTERFACE:
+ snprintf(str, size,
+ "Get Interface Status(Intf = %d, Length = %d)",
+ wIndex, wLength);
+ break;
+ case USB_RECIP_ENDPOINT:
+ snprintf(str, size, "Get Endpoint Status(ep%d%s)",
+ wIndex & ~USB_DIR_IN,
+ wIndex & USB_DIR_IN ? "in" : "out");
+ break;
+ }
+}
+
+static const char *usb_decode_device_feature(u16 wValue)
+{
+ switch (wValue) {
+ case USB_DEVICE_SELF_POWERED:
+ return "Self Powered";
+ case USB_DEVICE_REMOTE_WAKEUP:
+ return "Remote Wakeup";
+ case USB_DEVICE_TEST_MODE:
+ return "Test Mode";
+ case USB_DEVICE_U1_ENABLE:
+ return "U1 Enable";
+ case USB_DEVICE_U2_ENABLE:
+ return "U2 Enable";
+ case USB_DEVICE_LTM_ENABLE:
+ return "LTM Enable";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+static const char *usb_decode_test_mode(u16 wIndex)
+{
+ switch (wIndex) {
+ case TEST_J:
+ return ": TEST_J";
+ case TEST_K:
+ return ": TEST_K";
+ case TEST_SE0_NAK:
+ return ": TEST_SE0_NAK";
+ case TEST_PACKET:
+ return ": TEST_PACKET";
+ case TEST_FORCE_EN:
+ return ": TEST_FORCE_EN";
+ default:
+ return ": UNKNOWN";
+ }
+}
+
+static void usb_decode_set_clear_feature(__u8 bRequestType,
+ __u8 bRequest, __u16 wValue,
+ __u16 wIndex, char *str, size_t size)
+{
+ switch (bRequestType & USB_RECIP_MASK) {
+ case USB_RECIP_DEVICE:
+ snprintf(str, size, "%s Device Feature(%s%s)",
+ bRequest == USB_REQ_CLEAR_FEATURE ? "Clear" : "Set",
+ usb_decode_device_feature(wValue),
+ wValue == USB_DEVICE_TEST_MODE ?
+ usb_decode_test_mode(wIndex) : "");
+ break;
+ case USB_RECIP_INTERFACE:
+ snprintf(str, size, "%s Interface Feature(%s)",
+ bRequest == USB_REQ_CLEAR_FEATURE ? "Clear" : "Set",
+ wValue == USB_INTRF_FUNC_SUSPEND ?
+ "Function Suspend" : "UNKNOWN");
+ break;
+ case USB_RECIP_ENDPOINT:
+ snprintf(str, size, "%s Endpoint Feature(%s ep%d%s)",
+ bRequest == USB_REQ_CLEAR_FEATURE ? "Clear" : "Set",
+ wValue == USB_ENDPOINT_HALT ? "Halt" : "UNKNOWN",
+ wIndex & ~USB_DIR_IN,
+ wIndex & USB_DIR_IN ? "in" : "out");
+ break;
+ }
+}
+
+static void usb_decode_set_address(__u16 wValue, char *str, size_t size)
+{
+ snprintf(str, size, "Set Address(Addr = %02x)", wValue);
+}
+
+static void usb_decode_get_set_descriptor(__u8 bRequestType, __u8 bRequest,
+ __u16 wValue, __u16 wIndex,
+ __u16 wLength, char *str, size_t size)
+{
+ char *s;
+
+ switch (wValue >> 8) {
+ case USB_DT_DEVICE:
+ s = "Device";
+ break;
+ case USB_DT_CONFIG:
+ s = "Configuration";
+ break;
+ case USB_DT_STRING:
+ s = "String";
+ break;
+ case USB_DT_INTERFACE:
+ s = "Interface";
+ break;
+ case USB_DT_ENDPOINT:
+ s = "Endpoint";
+ break;
+ case USB_DT_DEVICE_QUALIFIER:
+ s = "Device Qualifier";
+ break;
+ case USB_DT_OTHER_SPEED_CONFIG:
+ s = "Other Speed Config";
+ break;
+ case USB_DT_INTERFACE_POWER:
+ s = "Interface Power";
+ break;
+ case USB_DT_OTG:
+ s = "OTG";
+ break;
+ case USB_DT_DEBUG:
+ s = "Debug";
+ break;
+ case USB_DT_INTERFACE_ASSOCIATION:
+ s = "Interface Association";
+ break;
+ case USB_DT_BOS:
+ s = "BOS";
+ break;
+ case USB_DT_DEVICE_CAPABILITY:
+ s = "Device Capability";
+ break;
+ case USB_DT_PIPE_USAGE:
+ s = "Pipe Usage";
+ break;
+ case USB_DT_SS_ENDPOINT_COMP:
+ s = "SS Endpoint Companion";
+ break;
+ case USB_DT_SSP_ISOC_ENDPOINT_COMP:
+ s = "SSP Isochronous Endpoint Companion";
+ break;
+ default:
+ s = "UNKNOWN";
+ break;
+ }
+
+ snprintf(str, size, "%s %s Descriptor(Index = %d, Length = %d)",
+ bRequest == USB_REQ_GET_DESCRIPTOR ? "Get" : "Set",
+ s, wValue & 0xff, wLength);
+}
+
+static void usb_decode_get_configuration(__u16 wLength, char *str, size_t size)
+{
+ snprintf(str, size, "Get Configuration(Length = %d)", wLength);
+}
+
+static void usb_decode_set_configuration(__u8 wValue, char *str, size_t size)
+{
+ snprintf(str, size, "Set Configuration(Config = %d)", wValue);
+}
+
+static void usb_decode_get_intf(__u16 wIndex, __u16 wLength, char *str,
+ size_t size)
+{
+ snprintf(str, size, "Get Interface(Intf = %d, Length = %d)",
+ wIndex, wLength);
+}
+
+static void usb_decode_set_intf(__u8 wValue, __u16 wIndex, char *str,
+ size_t size)
+{
+ snprintf(str, size, "Set Interface(Intf = %d, Alt.Setting = %d)",
+ wIndex, wValue);
+}
+
+static void usb_decode_synch_frame(__u16 wIndex, __u16 wLength,
+ char *str, size_t size)
+{
+ snprintf(str, size, "Synch Frame(Endpoint = %d, Length = %d)",
+ wIndex, wLength);
+}
+
+static void usb_decode_set_sel(__u16 wLength, char *str, size_t size)
+{
+ snprintf(str, size, "Set SEL(Length = %d)", wLength);
+}
+
+static void usb_decode_set_isoch_delay(__u8 wValue, char *str, size_t size)
+{
+ snprintf(str, size, "Set Isochronous Delay(Delay = %d ns)", wValue);
+}
+
+/**
+ * usb_decode_ctrl - returns a string representation of ctrl request
+ */
+const char *usb_decode_ctrl(char *str, size_t size, __u8 bRequestType,
+ __u8 bRequest, __u16 wValue, __u16 wIndex,
+ __u16 wLength)
+{
+ switch (bRequest) {
+ case USB_REQ_GET_STATUS:
+ usb_decode_get_status(bRequestType, wIndex, wLength, str, size);
+ break;
+ case USB_REQ_CLEAR_FEATURE:
+ case USB_REQ_SET_FEATURE:
+ usb_decode_set_clear_feature(bRequestType, bRequest, wValue,
+ wIndex, str, size);
+ break;
+ case USB_REQ_SET_ADDRESS:
+ usb_decode_set_address(wValue, str, size);
+ break;
+ case USB_REQ_GET_DESCRIPTOR:
+ case USB_REQ_SET_DESCRIPTOR:
+ usb_decode_get_set_descriptor(bRequestType, bRequest, wValue,
+ wIndex, wLength, str, size);
+ break;
+ case USB_REQ_GET_CONFIGURATION:
+ usb_decode_get_configuration(wLength, str, size);
+ break;
+ case USB_REQ_SET_CONFIGURATION:
+ usb_decode_set_configuration(wValue, str, size);
+ break;
+ case USB_REQ_GET_INTERFACE:
+ usb_decode_get_intf(wIndex, wLength, str, size);
+ break;
+ case USB_REQ_SET_INTERFACE:
+ usb_decode_set_intf(wValue, wIndex, str, size);
+ break;
+ case USB_REQ_SYNCH_FRAME:
+ usb_decode_synch_frame(wIndex, wLength, str, size);
+ break;
+ case USB_REQ_SET_SEL:
+ usb_decode_set_sel(wLength, str, size);
+ break;
+ case USB_REQ_SET_ISOCH_DELAY:
+ usb_decode_set_isoch_delay(wValue, str, size);
+ break;
+ default:
+ snprintf(str, size, "%02x %02x %02x %02x %02x %02x %02x %02x",
+ bRequestType, bRequest,
+ (u8)(cpu_to_le16(wValue) & 0xff),
+ (u8)(cpu_to_le16(wValue) >> 8),
+ (u8)(cpu_to_le16(wIndex) & 0xff),
+ (u8)(cpu_to_le16(wIndex) >> 8),
+ (u8)(cpu_to_le16(wLength) & 0xff),
+ (u8)(cpu_to_le16(wLength) >> 8));
+ }
+
+ return str;
+}
+EXPORT_SYMBOL_GPL(usb_decode_ctrl);
diff --git a/drivers/usb/common/led.c b/drivers/usb/common/led.c
index 7bd81166..0865dd4 100644
--- a/drivers/usb/common/led.c
+++ b/drivers/usb/common/led.c
@@ -10,6 +10,7 @@
#include <linux/init.h>
#include <linux/leds.h>
#include <linux/usb.h>
+#include "common.h"
#define BLINK_DELAY 30
@@ -36,18 +37,14 @@
EXPORT_SYMBOL_GPL(usb_led_activity);
-static int __init ledtrig_usb_init(void)
+void __init ledtrig_usb_init(void)
{
led_trigger_register_simple("usb-gadget", &ledtrig_usb_gadget);
led_trigger_register_simple("usb-host", &ledtrig_usb_host);
- return 0;
}
-static void __exit ledtrig_usb_exit(void)
+void __exit ledtrig_usb_exit(void)
{
led_trigger_unregister_simple(ledtrig_usb_gadget);
led_trigger_unregister_simple(ledtrig_usb_host);
}
-
-module_init(ledtrig_usb_init);
-module_exit(ledtrig_usb_exit);
diff --git a/drivers/usb/common/roles.c b/drivers/usb/common/roles.c
deleted file mode 100644
index 99116af..0000000
--- a/drivers/usb/common/roles.c
+++ /dev/null
@@ -1,314 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * USB Role Switch Support
- *
- * Copyright (C) 2018 Intel Corporation
- * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com>
- * Hans de Goede <hdegoede@redhat.com>
- */
-
-#include <linux/usb/role.h>
-#include <linux/device.h>
-#include <linux/module.h>
-#include <linux/mutex.h>
-#include <linux/slab.h>
-
-static struct class *role_class;
-
-struct usb_role_switch {
- struct device dev;
- struct mutex lock; /* device lock*/
- enum usb_role role;
-
- /* From descriptor */
- struct device *usb2_port;
- struct device *usb3_port;
- struct device *udc;
- usb_role_switch_set_t set;
- usb_role_switch_get_t get;
- bool allow_userspace_control;
-};
-
-#define to_role_switch(d) container_of(d, struct usb_role_switch, dev)
-
-/**
- * usb_role_switch_set_role - Set USB role for a switch
- * @sw: USB role switch
- * @role: USB role to be switched to
- *
- * Set USB role @role for @sw.
- */
-int usb_role_switch_set_role(struct usb_role_switch *sw, enum usb_role role)
-{
- int ret;
-
- if (IS_ERR_OR_NULL(sw))
- return 0;
-
- mutex_lock(&sw->lock);
-
- ret = sw->set(sw->dev.parent, role);
- if (!ret)
- sw->role = role;
-
- mutex_unlock(&sw->lock);
-
- return ret;
-}
-EXPORT_SYMBOL_GPL(usb_role_switch_set_role);
-
-/**
- * usb_role_switch_get_role - Get the USB role for a switch
- * @sw: USB role switch
- *
- * Depending on the role-switch-driver this function returns either a cached
- * value of the last set role, or reads back the actual value from the hardware.
- */
-enum usb_role usb_role_switch_get_role(struct usb_role_switch *sw)
-{
- enum usb_role role;
-
- if (IS_ERR_OR_NULL(sw))
- return USB_ROLE_NONE;
-
- mutex_lock(&sw->lock);
-
- if (sw->get)
- role = sw->get(sw->dev.parent);
- else
- role = sw->role;
-
- mutex_unlock(&sw->lock);
-
- return role;
-}
-EXPORT_SYMBOL_GPL(usb_role_switch_get_role);
-
-static int __switch_match(struct device *dev, const void *name)
-{
- return !strcmp((const char *)name, dev_name(dev));
-}
-
-static void *usb_role_switch_match(struct device_connection *con, int ep,
- void *data)
-{
- struct device *dev;
-
- dev = class_find_device(role_class, NULL, con->endpoint[ep],
- __switch_match);
-
- return dev ? to_role_switch(dev) : ERR_PTR(-EPROBE_DEFER);
-}
-
-/**
- * usb_role_switch_get - Find USB role switch linked with the caller
- * @dev: The caller device
- *
- * Finds and returns role switch linked with @dev. The reference count for the
- * found switch is incremented.
- */
-struct usb_role_switch *usb_role_switch_get(struct device *dev)
-{
- struct usb_role_switch *sw;
-
- sw = device_connection_find_match(dev, "usb-role-switch", NULL,
- usb_role_switch_match);
-
- if (!IS_ERR_OR_NULL(sw))
- WARN_ON(!try_module_get(sw->dev.parent->driver->owner));
-
- return sw;
-}
-EXPORT_SYMBOL_GPL(usb_role_switch_get);
-
-/**
- * usb_role_switch_put - Release handle to a switch
- * @sw: USB Role Switch
- *
- * Decrement reference count for @sw.
- */
-void usb_role_switch_put(struct usb_role_switch *sw)
-{
- if (!IS_ERR_OR_NULL(sw)) {
- put_device(&sw->dev);
- module_put(sw->dev.parent->driver->owner);
- }
-}
-EXPORT_SYMBOL_GPL(usb_role_switch_put);
-
-static umode_t
-usb_role_switch_is_visible(struct kobject *kobj, struct attribute *attr, int n)
-{
- struct device *dev = container_of(kobj, typeof(*dev), kobj);
- struct usb_role_switch *sw = to_role_switch(dev);
-
- if (sw->allow_userspace_control)
- return attr->mode;
-
- return 0;
-}
-
-static const char * const usb_roles[] = {
- [USB_ROLE_NONE] = "none",
- [USB_ROLE_HOST] = "host",
- [USB_ROLE_DEVICE] = "device",
-};
-
-static ssize_t
-role_show(struct device *dev, struct device_attribute *attr, char *buf)
-{
- struct usb_role_switch *sw = to_role_switch(dev);
- enum usb_role role = usb_role_switch_get_role(sw);
-
- return sprintf(buf, "%s\n", usb_roles[role]);
-}
-
-static ssize_t role_store(struct device *dev, struct device_attribute *attr,
- const char *buf, size_t size)
-{
- struct usb_role_switch *sw = to_role_switch(dev);
- int ret;
-
- ret = sysfs_match_string(usb_roles, buf);
- if (ret < 0) {
- bool res;
-
- /* Extra check if the user wants to disable the switch */
- ret = kstrtobool(buf, &res);
- if (ret || res)
- return -EINVAL;
- }
-
- ret = usb_role_switch_set_role(sw, ret);
- if (ret)
- return ret;
-
- return size;
-}
-static DEVICE_ATTR_RW(role);
-
-static struct attribute *usb_role_switch_attrs[] = {
- &dev_attr_role.attr,
- NULL,
-};
-
-static const struct attribute_group usb_role_switch_group = {
- .is_visible = usb_role_switch_is_visible,
- .attrs = usb_role_switch_attrs,
-};
-
-static const struct attribute_group *usb_role_switch_groups[] = {
- &usb_role_switch_group,
- NULL,
-};
-
-static int
-usb_role_switch_uevent(struct device *dev, struct kobj_uevent_env *env)
-{
- int ret;
-
- ret = add_uevent_var(env, "USB_ROLE_SWITCH=%s", dev_name(dev));
- if (ret)
- dev_err(dev, "failed to add uevent USB_ROLE_SWITCH\n");
-
- return ret;
-}
-
-static void usb_role_switch_release(struct device *dev)
-{
- struct usb_role_switch *sw = to_role_switch(dev);
-
- kfree(sw);
-}
-
-static const struct device_type usb_role_dev_type = {
- .name = "usb_role_switch",
- .groups = usb_role_switch_groups,
- .uevent = usb_role_switch_uevent,
- .release = usb_role_switch_release,
-};
-
-/**
- * usb_role_switch_register - Register USB Role Switch
- * @parent: Parent device for the switch
- * @desc: Description of the switch
- *
- * USB Role Switch is a device capable or choosing the role for USB connector.
- * On platforms where the USB controller is dual-role capable, the controller
- * driver will need to register the switch. On platforms where the USB host and
- * USB device controllers behind the connector are separate, there will be a
- * mux, and the driver for that mux will need to register the switch.
- *
- * Returns handle to a new role switch or ERR_PTR. The content of @desc is
- * copied.
- */
-struct usb_role_switch *
-usb_role_switch_register(struct device *parent,
- const struct usb_role_switch_desc *desc)
-{
- struct usb_role_switch *sw;
- int ret;
-
- if (!desc || !desc->set)
- return ERR_PTR(-EINVAL);
-
- sw = kzalloc(sizeof(*sw), GFP_KERNEL);
- if (!sw)
- return ERR_PTR(-ENOMEM);
-
- mutex_init(&sw->lock);
-
- sw->allow_userspace_control = desc->allow_userspace_control;
- sw->usb2_port = desc->usb2_port;
- sw->usb3_port = desc->usb3_port;
- sw->udc = desc->udc;
- sw->set = desc->set;
- sw->get = desc->get;
-
- sw->dev.parent = parent;
- sw->dev.class = role_class;
- sw->dev.type = &usb_role_dev_type;
- dev_set_name(&sw->dev, "%s-role-switch", dev_name(parent));
-
- ret = device_register(&sw->dev);
- if (ret) {
- put_device(&sw->dev);
- return ERR_PTR(ret);
- }
-
- /* TODO: Symlinks for the host port and the device controller. */
-
- return sw;
-}
-EXPORT_SYMBOL_GPL(usb_role_switch_register);
-
-/**
- * usb_role_switch_unregister - Unregsiter USB Role Switch
- * @sw: USB Role Switch
- *
- * Unregister switch that was registered with usb_role_switch_register().
- */
-void usb_role_switch_unregister(struct usb_role_switch *sw)
-{
- if (!IS_ERR_OR_NULL(sw))
- device_unregister(&sw->dev);
-}
-EXPORT_SYMBOL_GPL(usb_role_switch_unregister);
-
-static int __init usb_roles_init(void)
-{
- role_class = class_create(THIS_MODULE, "usb_role");
- return PTR_ERR_OR_ZERO(role_class);
-}
-subsys_initcall(usb_roles_init);
-
-static void __exit usb_roles_exit(void)
-{
- class_destroy(role_class);
-}
-module_exit(usb_roles_exit);
-
-MODULE_AUTHOR("Heikki Krogerus <heikki.krogerus@linux.intel.com>");
-MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
-MODULE_LICENSE("GPL v2");
-MODULE_DESCRIPTION("USB Role Class");
diff --git a/drivers/usb/common/usb-conn-gpio.c b/drivers/usb/common/usb-conn-gpio.c
new file mode 100644
index 0000000..87338f9
--- /dev/null
+++ b/drivers/usb/common/usb-conn-gpio.c
@@ -0,0 +1,284 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * USB GPIO Based Connection Detection Driver
+ *
+ * Copyright (C) 2019 MediaTek Inc.
+ *
+ * Author: Chunfeng Yun <chunfeng.yun@mediatek.com>
+ *
+ * Some code borrowed from drivers/extcon/extcon-usb-gpio.c
+ */
+
+#include <linux/device.h>
+#include <linux/gpio/consumer.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/usb/role.h>
+
+#define USB_GPIO_DEB_MS 20 /* ms */
+#define USB_GPIO_DEB_US ((USB_GPIO_DEB_MS) * 1000) /* us */
+
+#define USB_CONN_IRQF \
+ (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT)
+
+struct usb_conn_info {
+ struct device *dev;
+ struct usb_role_switch *role_sw;
+ enum usb_role last_role;
+ struct regulator *vbus;
+ struct delayed_work dw_det;
+ unsigned long debounce_jiffies;
+
+ struct gpio_desc *id_gpiod;
+ struct gpio_desc *vbus_gpiod;
+ int id_irq;
+ int vbus_irq;
+};
+
+/**
+ * "DEVICE" = VBUS and "HOST" = !ID, so we have:
+ * Both "DEVICE" and "HOST" can't be set as active at the same time
+ * so if "HOST" is active (i.e. ID is 0) we keep "DEVICE" inactive
+ * even if VBUS is on.
+ *
+ * Role | ID | VBUS
+ * ------------------------------------
+ * [1] DEVICE | H | H
+ * [2] NONE | H | L
+ * [3] HOST | L | H
+ * [4] HOST | L | L
+ *
+ * In case we have only one of these signals:
+ * - VBUS only - we want to distinguish between [1] and [2], so ID is always 1
+ * - ID only - we want to distinguish between [1] and [4], so VBUS = ID
+ */
+static void usb_conn_detect_cable(struct work_struct *work)
+{
+ struct usb_conn_info *info;
+ enum usb_role role;
+ int id, vbus, ret;
+
+ info = container_of(to_delayed_work(work),
+ struct usb_conn_info, dw_det);
+
+ /* check ID and VBUS */
+ id = info->id_gpiod ?
+ gpiod_get_value_cansleep(info->id_gpiod) : 1;
+ vbus = info->vbus_gpiod ?
+ gpiod_get_value_cansleep(info->vbus_gpiod) : id;
+
+ if (!id)
+ role = USB_ROLE_HOST;
+ else if (vbus)
+ role = USB_ROLE_DEVICE;
+ else
+ role = USB_ROLE_NONE;
+
+ dev_dbg(info->dev, "role %d/%d, gpios: id %d, vbus %d\n",
+ info->last_role, role, id, vbus);
+
+ if (info->last_role == role) {
+ dev_warn(info->dev, "repeated role: %d\n", role);
+ return;
+ }
+
+ if (info->last_role == USB_ROLE_HOST)
+ regulator_disable(info->vbus);
+
+ ret = usb_role_switch_set_role(info->role_sw, role);
+ if (ret)
+ dev_err(info->dev, "failed to set role: %d\n", ret);
+
+ if (role == USB_ROLE_HOST) {
+ ret = regulator_enable(info->vbus);
+ if (ret)
+ dev_err(info->dev, "enable vbus regulator failed\n");
+ }
+
+ info->last_role = role;
+
+ dev_dbg(info->dev, "vbus regulator is %s\n",
+ regulator_is_enabled(info->vbus) ? "enabled" : "disabled");
+}
+
+static void usb_conn_queue_dwork(struct usb_conn_info *info,
+ unsigned long delay)
+{
+ queue_delayed_work(system_power_efficient_wq, &info->dw_det, delay);
+}
+
+static irqreturn_t usb_conn_isr(int irq, void *dev_id)
+{
+ struct usb_conn_info *info = dev_id;
+
+ usb_conn_queue_dwork(info, info->debounce_jiffies);
+
+ return IRQ_HANDLED;
+}
+
+static int usb_conn_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct usb_conn_info *info;
+ int ret = 0;
+
+ info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ info->dev = dev;
+ info->id_gpiod = devm_gpiod_get_optional(dev, "id", GPIOD_IN);
+ if (IS_ERR(info->id_gpiod))
+ return PTR_ERR(info->id_gpiod);
+
+ info->vbus_gpiod = devm_gpiod_get_optional(dev, "vbus", GPIOD_IN);
+ if (IS_ERR(info->vbus_gpiod))
+ return PTR_ERR(info->vbus_gpiod);
+
+ if (!info->id_gpiod && !info->vbus_gpiod) {
+ dev_err(dev, "failed to get gpios\n");
+ return -ENODEV;
+ }
+
+ if (info->id_gpiod)
+ ret = gpiod_set_debounce(info->id_gpiod, USB_GPIO_DEB_US);
+ if (!ret && info->vbus_gpiod)
+ ret = gpiod_set_debounce(info->vbus_gpiod, USB_GPIO_DEB_US);
+ if (ret < 0)
+ info->debounce_jiffies = msecs_to_jiffies(USB_GPIO_DEB_MS);
+
+ INIT_DELAYED_WORK(&info->dw_det, usb_conn_detect_cable);
+
+ info->vbus = devm_regulator_get(dev, "vbus");
+ if (IS_ERR(info->vbus)) {
+ dev_err(dev, "failed to get vbus\n");
+ return PTR_ERR(info->vbus);
+ }
+
+ info->role_sw = usb_role_switch_get(dev);
+ if (IS_ERR(info->role_sw)) {
+ if (PTR_ERR(info->role_sw) != -EPROBE_DEFER)
+ dev_err(dev, "failed to get role switch\n");
+
+ return PTR_ERR(info->role_sw);
+ }
+
+ if (info->id_gpiod) {
+ info->id_irq = gpiod_to_irq(info->id_gpiod);
+ if (info->id_irq < 0) {
+ dev_err(dev, "failed to get ID IRQ\n");
+ ret = info->id_irq;
+ goto put_role_sw;
+ }
+
+ ret = devm_request_threaded_irq(dev, info->id_irq, NULL,
+ usb_conn_isr, USB_CONN_IRQF,
+ pdev->name, info);
+ if (ret < 0) {
+ dev_err(dev, "failed to request ID IRQ\n");
+ goto put_role_sw;
+ }
+ }
+
+ if (info->vbus_gpiod) {
+ info->vbus_irq = gpiod_to_irq(info->vbus_gpiod);
+ if (info->vbus_irq < 0) {
+ dev_err(dev, "failed to get VBUS IRQ\n");
+ ret = info->vbus_irq;
+ goto put_role_sw;
+ }
+
+ ret = devm_request_threaded_irq(dev, info->vbus_irq, NULL,
+ usb_conn_isr, USB_CONN_IRQF,
+ pdev->name, info);
+ if (ret < 0) {
+ dev_err(dev, "failed to request VBUS IRQ\n");
+ goto put_role_sw;
+ }
+ }
+
+ platform_set_drvdata(pdev, info);
+
+ /* Perform initial detection */
+ usb_conn_queue_dwork(info, 0);
+
+ return 0;
+
+put_role_sw:
+ usb_role_switch_put(info->role_sw);
+ return ret;
+}
+
+static int usb_conn_remove(struct platform_device *pdev)
+{
+ struct usb_conn_info *info = platform_get_drvdata(pdev);
+
+ cancel_delayed_work_sync(&info->dw_det);
+
+ if (info->last_role == USB_ROLE_HOST)
+ regulator_disable(info->vbus);
+
+ usb_role_switch_put(info->role_sw);
+
+ return 0;
+}
+
+static int __maybe_unused usb_conn_suspend(struct device *dev)
+{
+ struct usb_conn_info *info = dev_get_drvdata(dev);
+
+ if (info->id_gpiod)
+ disable_irq(info->id_irq);
+ if (info->vbus_gpiod)
+ disable_irq(info->vbus_irq);
+
+ pinctrl_pm_select_sleep_state(dev);
+
+ return 0;
+}
+
+static int __maybe_unused usb_conn_resume(struct device *dev)
+{
+ struct usb_conn_info *info = dev_get_drvdata(dev);
+
+ pinctrl_pm_select_default_state(dev);
+
+ if (info->id_gpiod)
+ enable_irq(info->id_irq);
+ if (info->vbus_gpiod)
+ enable_irq(info->vbus_irq);
+
+ usb_conn_queue_dwork(info, 0);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(usb_conn_pm_ops,
+ usb_conn_suspend, usb_conn_resume);
+
+static const struct of_device_id usb_conn_dt_match[] = {
+ { .compatible = "gpio-usb-b-connector", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, usb_conn_dt_match);
+
+static struct platform_driver usb_conn_driver = {
+ .probe = usb_conn_probe,
+ .remove = usb_conn_remove,
+ .driver = {
+ .name = "usb-conn-gpio",
+ .pm = &usb_conn_pm_ops,
+ .of_match_table = usb_conn_dt_match,
+ },
+};
+
+module_platform_driver(usb_conn_driver);
+
+MODULE_AUTHOR("Chunfeng Yun <chunfeng.yun@mediatek.com>");
+MODULE_DESCRIPTION("USB GPIO based connection detection driver");
+MODULE_LICENSE("GPL v2");