diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 10:05:51 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 10:05:51 +0000 |
commit | 5d1646d90e1f2cceb9f0828f4b28318cd0ec7744 (patch) | |
tree | a94efe259b9009378be6d90eb30d2b019d95c194 /drivers/usb/common | |
parent | Initial commit. (diff) | |
download | linux-5d1646d90e1f2cceb9f0828f4b28318cd0ec7744.tar.xz linux-5d1646d90e1f2cceb9f0828f4b28318cd0ec7744.zip |
Adding upstream version 5.10.209.upstream/5.10.209upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/usb/common')
-rw-r--r-- | drivers/usb/common/Kconfig | 51 | ||||
-rw-r--r-- | drivers/usb/common/Makefile | 13 | ||||
-rw-r--r-- | drivers/usb/common/common.c | 415 | ||||
-rw-r--r-- | drivers/usb/common/common.h | 14 | ||||
-rw-r--r-- | drivers/usb/common/debug.c | 318 | ||||
-rw-r--r-- | drivers/usb/common/led.c | 50 | ||||
-rw-r--r-- | drivers/usb/common/ulpi.c | 329 | ||||
-rw-r--r-- | drivers/usb/common/usb-conn-gpio.c | 360 | ||||
-rw-r--r-- | drivers/usb/common/usb-otg-fsm.c | 452 |
9 files changed, 2002 insertions, 0 deletions
diff --git a/drivers/usb/common/Kconfig b/drivers/usb/common/Kconfig new file mode 100644 index 000000000..b85662243 --- /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 && USB_COMMON && LEDS_TRIGGERS + 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 + select POWER_SUPPLY + 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 new file mode 100644 index 000000000..8ac4d21ef --- /dev/null +++ b/drivers/usb/common/Makefile @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the usb common parts. +# + +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 diff --git a/drivers/usb/common/common.c b/drivers/usb/common/common.c new file mode 100644 index 000000000..347fb3d38 --- /dev/null +++ b/drivers/usb/common/common.c @@ -0,0 +1,415 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Provides code common for host and device side USB. + * + * If either host side (ie. CONFIG_USB=y) or device side USB stack + * (ie. CONFIG_USB_GADGET=y) is compiled in the kernel, this module is + * compiled-in as well. Otherwise, if either of the two stacks is + * compiled as module, this file is compiled as module as well. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/usb/ch9.h> +#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", +}; + +/** + * usb_ep_type_string() - Returns human readable-name of the endpoint type. + * @ep_type: The endpoint type to return human-readable name for. If it's not + * any of the types: USB_ENDPOINT_XFER_{CONTROL, ISOC, BULK, INT}, + * usually got by usb_endpoint_type(), the string 'unknown' will be returned. + */ +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) +{ + static const char *const names[] = { + [OTG_STATE_A_IDLE] = "a_idle", + [OTG_STATE_A_WAIT_VRISE] = "a_wait_vrise", + [OTG_STATE_A_WAIT_BCON] = "a_wait_bcon", + [OTG_STATE_A_HOST] = "a_host", + [OTG_STATE_A_SUSPEND] = "a_suspend", + [OTG_STATE_A_PERIPHERAL] = "a_peripheral", + [OTG_STATE_A_WAIT_VFALL] = "a_wait_vfall", + [OTG_STATE_A_VBUS_ERR] = "a_vbus_err", + [OTG_STATE_B_IDLE] = "b_idle", + [OTG_STATE_B_SRP_INIT] = "b_srp_init", + [OTG_STATE_B_PERIPHERAL] = "b_peripheral", + [OTG_STATE_B_WAIT_ACON] = "b_wait_acon", + [OTG_STATE_B_HOST] = "b_host", + }; + + if (state < 0 || state >= ARRAY_SIZE(names)) + return "UNDEFINED"; + + return names[state]; +} +EXPORT_SYMBOL_GPL(usb_otg_state_string); + +static const char *const speed_names[] = { + [USB_SPEED_UNKNOWN] = "UNKNOWN", + [USB_SPEED_LOW] = "low-speed", + [USB_SPEED_FULL] = "full-speed", + [USB_SPEED_HIGH] = "high-speed", + [USB_SPEED_WIRELESS] = "wireless", + [USB_SPEED_SUPER] = "super-speed", + [USB_SPEED_SUPER_PLUS] = "super-speed-plus", +}; + +static const char *const ssp_rate[] = { + [USB_SSP_GEN_UNKNOWN] = "UNKNOWN", + [USB_SSP_GEN_2x1] = "super-speed-plus-gen2x1", + [USB_SSP_GEN_1x2] = "super-speed-plus-gen1x2", + [USB_SSP_GEN_2x2] = "super-speed-plus-gen2x2", +}; + +/** + * usb_speed_string() - Returns human readable-name of the speed. + * @speed: The speed to return human-readable name for. If it's not + * any of the speeds defined in usb_device_speed enum, string for + * USB_SPEED_UNKNOWN will be returned. + */ +const char *usb_speed_string(enum usb_device_speed speed) +{ + if (speed < 0 || speed >= ARRAY_SIZE(speed_names)) + speed = USB_SPEED_UNKNOWN; + return speed_names[speed]; +} +EXPORT_SYMBOL_GPL(usb_speed_string); + +/** + * usb_get_maximum_speed - Get maximum requested speed for a given USB + * controller. + * @dev: Pointer to the given USB controller device + * + * The function gets the maximum speed string from property "maximum-speed", + * and returns the corresponding enum usb_device_speed. + */ +enum usb_device_speed usb_get_maximum_speed(struct device *dev) +{ + const char *maximum_speed; + int ret; + + ret = device_property_read_string(dev, "maximum-speed", &maximum_speed); + if (ret < 0) + return USB_SPEED_UNKNOWN; + + ret = match_string(ssp_rate, ARRAY_SIZE(ssp_rate), maximum_speed); + if (ret > 0) + return USB_SPEED_SUPER_PLUS; + + ret = match_string(speed_names, ARRAY_SIZE(speed_names), maximum_speed); + return (ret < 0) ? USB_SPEED_UNKNOWN : ret; +} +EXPORT_SYMBOL_GPL(usb_get_maximum_speed); + +/** + * usb_get_maximum_ssp_rate - Get the signaling rate generation and lane count + * of a SuperSpeed Plus capable device. + * @dev: Pointer to the given USB controller device + * + * If the string from "maximum-speed" property is super-speed-plus-genXxY where + * 'X' is the generation number and 'Y' is the number of lanes, then this + * function returns the corresponding enum usb_ssp_rate. + */ +enum usb_ssp_rate usb_get_maximum_ssp_rate(struct device *dev) +{ + const char *maximum_speed; + int ret; + + ret = device_property_read_string(dev, "maximum-speed", &maximum_speed); + if (ret < 0) + return USB_SSP_GEN_UNKNOWN; + + ret = match_string(ssp_rate, ARRAY_SIZE(ssp_rate), maximum_speed); + return (ret < 0) ? USB_SSP_GEN_UNKNOWN : ret; +} +EXPORT_SYMBOL_GPL(usb_get_maximum_ssp_rate); + +/** + * usb_state_string - Returns human readable name for the state. + * @state: The state to return a human-readable name for. If it's not + * any of the states devices in usb_device_state_string enum, + * the string UNKNOWN will be returned. + */ +const char *usb_state_string(enum usb_device_state state) +{ + static const char *const names[] = { + [USB_STATE_NOTATTACHED] = "not attached", + [USB_STATE_ATTACHED] = "attached", + [USB_STATE_POWERED] = "powered", + [USB_STATE_RECONNECTING] = "reconnecting", + [USB_STATE_UNAUTHENTICATED] = "unauthenticated", + [USB_STATE_DEFAULT] = "default", + [USB_STATE_ADDRESS] = "addressed", + [USB_STATE_CONFIGURED] = "configured", + [USB_STATE_SUSPENDED] = "suspended", + }; + + if (state < 0 || state >= ARRAY_SIZE(names)) + return "UNKNOWN"; + + return names[state]; +} +EXPORT_SYMBOL_GPL(usb_state_string); + +static const char *const usb_dr_modes[] = { + [USB_DR_MODE_UNKNOWN] = "", + [USB_DR_MODE_HOST] = "host", + [USB_DR_MODE_PERIPHERAL] = "peripheral", + [USB_DR_MODE_OTG] = "otg", +}; + +static enum usb_dr_mode usb_get_dr_mode_from_string(const char *str) +{ + int ret; + + ret = match_string(usb_dr_modes, ARRAY_SIZE(usb_dr_modes), str); + return (ret < 0) ? USB_DR_MODE_UNKNOWN : ret; +} + +enum usb_dr_mode usb_get_dr_mode(struct device *dev) +{ + const char *dr_mode; + int err; + + err = device_property_read_string(dev, "dr_mode", &dr_mode); + if (err < 0) + return USB_DR_MODE_UNKNOWN; + + return usb_get_dr_mode_from_string(dr_mode); +} +EXPORT_SYMBOL_GPL(usb_get_dr_mode); + +/** + * usb_decode_interval - Decode bInterval into the time expressed in 1us unit + * @epd: The descriptor of the endpoint + * @speed: The speed that the endpoint works as + * + * Function returns the interval expressed in 1us unit for servicing + * endpoint for data transfers. + */ +unsigned int usb_decode_interval(const struct usb_endpoint_descriptor *epd, + enum usb_device_speed speed) +{ + unsigned int interval = 0; + + switch (usb_endpoint_type(epd)) { + case USB_ENDPOINT_XFER_CONTROL: + /* uframes per NAK */ + if (speed == USB_SPEED_HIGH) + interval = epd->bInterval; + break; + case USB_ENDPOINT_XFER_ISOC: + interval = 1 << (epd->bInterval - 1); + break; + case USB_ENDPOINT_XFER_BULK: + /* uframes per NAK */ + if (speed == USB_SPEED_HIGH && usb_endpoint_dir_out(epd)) + interval = epd->bInterval; + break; + case USB_ENDPOINT_XFER_INT: + if (speed >= USB_SPEED_HIGH) + interval = 1 << (epd->bInterval - 1); + else + interval = epd->bInterval; + break; + } + + interval *= (speed >= USB_SPEED_HIGH) ? 125 : 1000; + + return interval; +} +EXPORT_SYMBOL_GPL(usb_decode_interval); + +#ifdef CONFIG_OF +/** + * of_usb_get_dr_mode_by_phy - Get dual role mode for the controller device + * which is associated with the given phy device_node + * @np: Pointer to the given phy device_node + * @arg0: phandle args[0] for phy's with #phy-cells >= 1, or -1 for + * phys which do not have phy-cells + * + * In dts a usb controller associates with phy devices. The function gets + * the string from property 'dr_mode' of the controller associated with the + * given phy device node, and returns the correspondig enum usb_dr_mode. + */ +enum usb_dr_mode of_usb_get_dr_mode_by_phy(struct device_node *np, int arg0) +{ + struct device_node *controller = NULL; + struct of_phandle_args args; + const char *dr_mode; + int index; + int err; + + do { + controller = of_find_node_with_property(controller, "phys"); + if (!of_device_is_available(controller)) + continue; + index = 0; + do { + if (arg0 == -1) { + args.np = of_parse_phandle(controller, "phys", + index); + args.args_count = 0; + } else { + err = of_parse_phandle_with_args(controller, + "phys", "#phy-cells", + index, &args); + if (err) + break; + } + + of_node_put(args.np); + if (args.np == np && (args.args_count == 0 || + args.args[0] == arg0)) + goto finish; + index++; + } while (args.np); + } while (controller); + +finish: + err = of_property_read_string(controller, "dr_mode", &dr_mode); + of_node_put(controller); + + if (err < 0) + return USB_DR_MODE_UNKNOWN; + + return usb_get_dr_mode_from_string(dr_mode); +} +EXPORT_SYMBOL_GPL(of_usb_get_dr_mode_by_phy); + +/** + * of_usb_host_tpl_support - to get if Targeted Peripheral List is supported + * for given targeted hosts (non-PC hosts) + * @np: Pointer to the given device_node + * + * The function gets if the targeted hosts support TPL or not + */ +bool of_usb_host_tpl_support(struct device_node *np) +{ + return of_property_read_bool(np, "tpl-support"); +} +EXPORT_SYMBOL_GPL(of_usb_host_tpl_support); + +/** + * of_usb_update_otg_caps - to update usb otg capabilities according to + * the passed properties in DT. + * @np: Pointer to the given device_node + * @otg_caps: Pointer to the target usb_otg_caps to be set + * + * The function updates the otg capabilities + */ +int of_usb_update_otg_caps(struct device_node *np, + struct usb_otg_caps *otg_caps) +{ + u32 otg_rev; + + if (!otg_caps) + return -EINVAL; + + if (!of_property_read_u32(np, "otg-rev", &otg_rev)) { + switch (otg_rev) { + case 0x0100: + case 0x0120: + case 0x0130: + case 0x0200: + /* Choose the lesser one if it's already been set */ + if (otg_caps->otg_rev) + otg_caps->otg_rev = min_t(u16, otg_rev, + otg_caps->otg_rev); + else + otg_caps->otg_rev = otg_rev; + break; + default: + pr_err("%pOF: unsupported otg-rev: 0x%x\n", + np, otg_rev); + return -EINVAL; + } + } else { + /* + * otg-rev is mandatory for otg properties, if not passed + * we set it to be 0 and assume it's a legacy otg device. + * Non-dt platform can set it afterwards. + */ + otg_caps->otg_rev = 0; + } + + if (of_property_read_bool(np, "hnp-disable")) + otg_caps->hnp_support = false; + if (of_property_read_bool(np, "srp-disable")) + otg_caps->srp_support = false; + if (of_property_read_bool(np, "adp-disable") || + (otg_caps->otg_rev < 0x0200)) + otg_caps->adp_support = false; + + return 0; +} +EXPORT_SYMBOL_GPL(of_usb_update_otg_caps); + +/** + * usb_of_get_companion_dev - Find the companion device + * @dev: the device pointer to find a companion + * + * Find the companion device from platform bus. + * + * Takes a reference to the returned struct device which needs to be dropped + * after use. + * + * Return: On success, a pointer to the companion device, %NULL on failure. + */ +struct device *usb_of_get_companion_dev(struct device *dev) +{ + struct device_node *node; + struct platform_device *pdev = NULL; + + node = of_parse_phandle(dev->of_node, "companion", 0); + if (node) + pdev = of_find_device_by_node(node); + + of_node_put(node); + + return pdev ? &pdev->dev : NULL; +} +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 000000000..424a91316 --- /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 000000000..f0c0e8db7 --- /dev/null +++ b/drivers/usb/common/debug.c @@ -0,0 +1,318 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Common USB debugging functions + * + * Copyright (C) 2010-2011 Texas Instruments Incorporated - https://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 USB_TEST_J: + return ": TEST_J"; + case USB_TEST_K: + return ": TEST_K"; + case USB_TEST_SE0_NAK: + return ": TEST_SE0_NAK"; + case USB_TEST_PACKET: + return ": TEST_PACKET"; + case USB_TEST_FORCE_ENABLE: + 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); +} + +static void usb_decode_ctrl_generic(char *str, size_t size, __u8 bRequestType, + __u8 bRequest, __u16 wValue, __u16 wIndex, + __u16 wLength) +{ + u8 recip = bRequestType & USB_RECIP_MASK; + u8 type = bRequestType & USB_TYPE_MASK; + + snprintf(str, size, + "Type=%s Recipient=%s Dir=%s bRequest=%u wValue=%u wIndex=%u wLength=%u", + (type == USB_TYPE_STANDARD) ? "Standard" : + (type == USB_TYPE_VENDOR) ? "Vendor" : + (type == USB_TYPE_CLASS) ? "Class" : "Unknown", + (recip == USB_RECIP_DEVICE) ? "Device" : + (recip == USB_RECIP_INTERFACE) ? "Interface" : + (recip == USB_RECIP_ENDPOINT) ? "Endpoint" : "Unknown", + (bRequestType & USB_DIR_IN) ? "IN" : "OUT", + bRequest, wValue, wIndex, wLength); +} + +static void usb_decode_ctrl_standard(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: + usb_decode_ctrl_generic(str, size, bRequestType, bRequest, + wValue, wIndex, wLength); + break; + } +} + +/** + * usb_decode_ctrl - Returns human readable representation of control request. + * @str: buffer to return a human-readable representation of control request. + * This buffer should have about 200 bytes. + * @size: size of str buffer. + * @bRequestType: matches the USB bmRequestType field + * @bRequest: matches the USB bRequest field + * @wValue: matches the USB wValue field (CPU byte order) + * @wIndex: matches the USB wIndex field (CPU byte order) + * @wLength: matches the USB wLength field (CPU byte order) + * + * Function returns decoded, formatted and human-readable description of + * control request packet. + * + * The usage scenario for this is for tracepoints, so function as a return + * use the same value as in parameters. This approach allows to use this + * function in TP_printk + * + * Important: wValue, wIndex, wLength parameters before invoking this function + * should be processed by le16_to_cpu macro. + */ +const char *usb_decode_ctrl(char *str, size_t size, __u8 bRequestType, + __u8 bRequest, __u16 wValue, __u16 wIndex, + __u16 wLength) +{ + switch (bRequestType & USB_TYPE_MASK) { + case USB_TYPE_STANDARD: + usb_decode_ctrl_standard(str, size, bRequestType, bRequest, + wValue, wIndex, wLength); + break; + case USB_TYPE_VENDOR: + case USB_TYPE_CLASS: + default: + usb_decode_ctrl_generic(str, size, bRequestType, bRequest, + wValue, wIndex, wLength); + break; + } + + return str; +} +EXPORT_SYMBOL_GPL(usb_decode_ctrl); diff --git a/drivers/usb/common/led.c b/drivers/usb/common/led.c new file mode 100644 index 000000000..0865dd44a --- /dev/null +++ b/drivers/usb/common/led.c @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * LED Triggers for USB Activity + * + * Copyright 2014 Michal Sojka <sojka@merica.cz> + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/leds.h> +#include <linux/usb.h> +#include "common.h" + +#define BLINK_DELAY 30 + +static unsigned long usb_blink_delay = BLINK_DELAY; + +DEFINE_LED_TRIGGER(ledtrig_usb_gadget); +DEFINE_LED_TRIGGER(ledtrig_usb_host); + +void usb_led_activity(enum usb_led_event ev) +{ + struct led_trigger *trig = NULL; + + switch (ev) { + case USB_LED_EVENT_GADGET: + trig = ledtrig_usb_gadget; + break; + case USB_LED_EVENT_HOST: + trig = ledtrig_usb_host; + break; + } + /* led_trigger_blink_oneshot() handles trig == NULL gracefully */ + led_trigger_blink_oneshot(trig, &usb_blink_delay, &usb_blink_delay, 0); +} +EXPORT_SYMBOL_GPL(usb_led_activity); + + +void __init ledtrig_usb_init(void) +{ + led_trigger_register_simple("usb-gadget", &ledtrig_usb_gadget); + led_trigger_register_simple("usb-host", &ledtrig_usb_host); +} + +void __exit ledtrig_usb_exit(void) +{ + led_trigger_unregister_simple(ledtrig_usb_gadget); + led_trigger_unregister_simple(ledtrig_usb_host); +} diff --git a/drivers/usb/common/ulpi.c b/drivers/usb/common/ulpi.c new file mode 100644 index 000000000..3c705f1be --- /dev/null +++ b/drivers/usb/common/ulpi.c @@ -0,0 +1,329 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ulpi.c - USB ULPI PHY bus + * + * Copyright (C) 2015 Intel Corporation + * + * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com> + */ + +#include <linux/ulpi/interface.h> +#include <linux/ulpi/driver.h> +#include <linux/ulpi/regs.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/acpi.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/clk/clk-conf.h> + +/* -------------------------------------------------------------------------- */ + +int ulpi_read(struct ulpi *ulpi, u8 addr) +{ + return ulpi->ops->read(ulpi->dev.parent, addr); +} +EXPORT_SYMBOL_GPL(ulpi_read); + +int ulpi_write(struct ulpi *ulpi, u8 addr, u8 val) +{ + return ulpi->ops->write(ulpi->dev.parent, addr, val); +} +EXPORT_SYMBOL_GPL(ulpi_write); + +/* -------------------------------------------------------------------------- */ + +static int ulpi_match(struct device *dev, struct device_driver *driver) +{ + struct ulpi_driver *drv = to_ulpi_driver(driver); + struct ulpi *ulpi = to_ulpi_dev(dev); + const struct ulpi_device_id *id; + + /* + * Some ULPI devices don't have a vendor id + * or provide an id_table so rely on OF match. + */ + if (ulpi->id.vendor == 0 || !drv->id_table) + return of_driver_match_device(dev, driver); + + for (id = drv->id_table; id->vendor; id++) + if (id->vendor == ulpi->id.vendor && + id->product == ulpi->id.product) + return 1; + + return 0; +} + +static int ulpi_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + struct ulpi *ulpi = to_ulpi_dev(dev); + int ret; + + ret = of_device_uevent_modalias(dev, env); + if (ret != -ENODEV) + return ret; + + if (add_uevent_var(env, "MODALIAS=ulpi:v%04xp%04x", + ulpi->id.vendor, ulpi->id.product)) + return -ENOMEM; + return 0; +} + +static int ulpi_probe(struct device *dev) +{ + struct ulpi_driver *drv = to_ulpi_driver(dev->driver); + int ret; + + ret = of_clk_set_defaults(dev->of_node, false); + if (ret < 0) + return ret; + + return drv->probe(to_ulpi_dev(dev)); +} + +static int ulpi_remove(struct device *dev) +{ + struct ulpi_driver *drv = to_ulpi_driver(dev->driver); + + if (drv->remove) + drv->remove(to_ulpi_dev(dev)); + + return 0; +} + +static struct bus_type ulpi_bus = { + .name = "ulpi", + .match = ulpi_match, + .uevent = ulpi_uevent, + .probe = ulpi_probe, + .remove = ulpi_remove, +}; + +/* -------------------------------------------------------------------------- */ + +static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int len; + struct ulpi *ulpi = to_ulpi_dev(dev); + + len = of_device_modalias(dev, buf, PAGE_SIZE); + if (len != -ENODEV) + return len; + + return sprintf(buf, "ulpi:v%04xp%04x\n", + ulpi->id.vendor, ulpi->id.product); +} +static DEVICE_ATTR_RO(modalias); + +static struct attribute *ulpi_dev_attrs[] = { + &dev_attr_modalias.attr, + NULL +}; + +static struct attribute_group ulpi_dev_attr_group = { + .attrs = ulpi_dev_attrs, +}; + +static const struct attribute_group *ulpi_dev_attr_groups[] = { + &ulpi_dev_attr_group, + NULL +}; + +static void ulpi_dev_release(struct device *dev) +{ + of_node_put(dev->of_node); + kfree(to_ulpi_dev(dev)); +} + +static const struct device_type ulpi_dev_type = { + .name = "ulpi_device", + .groups = ulpi_dev_attr_groups, + .release = ulpi_dev_release, +}; + +/* -------------------------------------------------------------------------- */ + +/** + * ulpi_register_driver - register a driver with the ULPI bus + * @drv: driver being registered + * @module: ends up being THIS_MODULE + * + * Registers a driver with the ULPI bus. + */ +int __ulpi_register_driver(struct ulpi_driver *drv, struct module *module) +{ + if (!drv->probe) + return -EINVAL; + + drv->driver.owner = module; + drv->driver.bus = &ulpi_bus; + + return driver_register(&drv->driver); +} +EXPORT_SYMBOL_GPL(__ulpi_register_driver); + +/** + * ulpi_unregister_driver - unregister a driver with the ULPI bus + * @drv: driver to unregister + * + * Unregisters a driver with the ULPI bus. + */ +void ulpi_unregister_driver(struct ulpi_driver *drv) +{ + driver_unregister(&drv->driver); +} +EXPORT_SYMBOL_GPL(ulpi_unregister_driver); + +/* -------------------------------------------------------------------------- */ + +static int ulpi_of_register(struct ulpi *ulpi) +{ + struct device_node *np = NULL, *child; + struct device *parent; + + /* Find a ulpi bus underneath the parent or the grandparent */ + parent = ulpi->dev.parent; + if (parent->of_node) + np = of_get_child_by_name(parent->of_node, "ulpi"); + else if (parent->parent && parent->parent->of_node) + np = of_get_child_by_name(parent->parent->of_node, "ulpi"); + if (!np) + return 0; + + child = of_get_next_available_child(np, NULL); + of_node_put(np); + if (!child) + return -EINVAL; + + ulpi->dev.of_node = child; + + return 0; +} + +static int ulpi_read_id(struct ulpi *ulpi) +{ + int ret; + + /* Test the interface */ + ret = ulpi_write(ulpi, ULPI_SCRATCH, 0xaa); + if (ret < 0) + goto err; + + ret = ulpi_read(ulpi, ULPI_SCRATCH); + if (ret < 0) + return ret; + + if (ret != 0xaa) + goto err; + + ulpi->id.vendor = ulpi_read(ulpi, ULPI_VENDOR_ID_LOW); + ulpi->id.vendor |= ulpi_read(ulpi, ULPI_VENDOR_ID_HIGH) << 8; + + ulpi->id.product = ulpi_read(ulpi, ULPI_PRODUCT_ID_LOW); + ulpi->id.product |= ulpi_read(ulpi, ULPI_PRODUCT_ID_HIGH) << 8; + + /* Some ULPI devices don't have a vendor id so rely on OF match */ + if (ulpi->id.vendor == 0) + goto err; + + request_module("ulpi:v%04xp%04x", ulpi->id.vendor, ulpi->id.product); + return 0; +err: + of_device_request_module(&ulpi->dev); + return 0; +} + +static int ulpi_register(struct device *dev, struct ulpi *ulpi) +{ + int ret; + + ulpi->dev.parent = dev; /* needed early for ops */ + ulpi->dev.bus = &ulpi_bus; + ulpi->dev.type = &ulpi_dev_type; + dev_set_name(&ulpi->dev, "%s.ulpi", dev_name(dev)); + + ACPI_COMPANION_SET(&ulpi->dev, ACPI_COMPANION(dev)); + + ret = ulpi_of_register(ulpi); + if (ret) + return ret; + + ret = ulpi_read_id(ulpi); + if (ret) { + of_node_put(ulpi->dev.of_node); + return ret; + } + + ret = device_register(&ulpi->dev); + if (ret) { + put_device(&ulpi->dev); + return ret; + } + + dev_dbg(&ulpi->dev, "registered ULPI PHY: vendor %04x, product %04x\n", + ulpi->id.vendor, ulpi->id.product); + + return 0; +} + +/** + * ulpi_register_interface - instantiate new ULPI device + * @dev: USB controller's device interface + * @ops: ULPI register access + * + * Allocates and registers a ULPI device and an interface for it. Called from + * the USB controller that provides the ULPI interface. + */ +struct ulpi *ulpi_register_interface(struct device *dev, + const struct ulpi_ops *ops) +{ + struct ulpi *ulpi; + int ret; + + ulpi = kzalloc(sizeof(*ulpi), GFP_KERNEL); + if (!ulpi) + return ERR_PTR(-ENOMEM); + + ulpi->ops = ops; + + ret = ulpi_register(dev, ulpi); + if (ret) { + kfree(ulpi); + return ERR_PTR(ret); + } + + return ulpi; +} +EXPORT_SYMBOL_GPL(ulpi_register_interface); + +/** + * ulpi_unregister_interface - unregister ULPI interface + * @ulpi: struct ulpi_interface + * + * Unregisters a ULPI device and it's interface that was created with + * ulpi_create_interface(). + */ +void ulpi_unregister_interface(struct ulpi *ulpi) +{ + device_unregister(&ulpi->dev); +} +EXPORT_SYMBOL_GPL(ulpi_unregister_interface); + +/* -------------------------------------------------------------------------- */ + +static int __init ulpi_init(void) +{ + return bus_register(&ulpi_bus); +} +subsys_initcall(ulpi_init); + +static void __exit ulpi_exit(void) +{ + bus_unregister(&ulpi_bus); +} +module_exit(ulpi_exit); + +MODULE_AUTHOR("Intel Corporation"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("USB ULPI PHY bus"); diff --git a/drivers/usb/common/usb-conn-gpio.c b/drivers/usb/common/usb-conn-gpio.c new file mode 100644 index 000000000..024460925 --- /dev/null +++ b/drivers/usb/common/usb-conn-gpio.c @@ -0,0 +1,360 @@ +// 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/power_supply.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; + + struct power_supply_desc desc; + struct power_supply *charger; + bool initial_detection; +}; + +/* + * "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->initial_detection && info->last_role == role) { + dev_warn(info->dev, "repeated role: %d\n", role); + return; + } + + info->initial_detection = false; + + if (info->last_role == USB_ROLE_HOST && info->vbus) + 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 && info->vbus) { + ret = regulator_enable(info->vbus); + if (ret) + dev_err(info->dev, "enable vbus regulator failed\n"); + } + + info->last_role = role; + + if (info->vbus) + dev_dbg(info->dev, "vbus regulator is %s\n", + regulator_is_enabled(info->vbus) ? "enabled" : "disabled"); + + power_supply_changed(info->charger); +} + +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 enum power_supply_property usb_charger_properties[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static int usb_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct usb_conn_info *info = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = info->last_role == USB_ROLE_DEVICE; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int usb_conn_psy_register(struct usb_conn_info *info) +{ + struct device *dev = info->dev; + struct power_supply_desc *desc = &info->desc; + struct power_supply_config cfg = { + .of_node = dev->of_node, + }; + + desc->name = "usb-charger"; + desc->properties = usb_charger_properties; + desc->num_properties = ARRAY_SIZE(usb_charger_properties); + desc->get_property = usb_charger_get_property; + desc->type = POWER_SUPPLY_TYPE_USB; + cfg.drv_data = info; + + info->charger = devm_power_supply_register(dev, desc, &cfg); + if (IS_ERR(info->charger)) + dev_err(dev, "Unable to register charger\n"); + + return PTR_ERR_OR_ZERO(info->charger); +} + +static int usb_conn_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct usb_conn_info *info; + bool need_vbus = true; + 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); + + /* + * If the USB connector is a child of a USB port and that port already provides the VBUS + * supply, there's no need for the USB connector to provide it again. + */ + if (dev->parent && dev->parent->of_node) { + if (of_find_property(dev->parent->of_node, "vbus-supply", NULL)) + need_vbus = false; + } + + if (!need_vbus) { + info->vbus = devm_regulator_get_optional(dev, "vbus"); + if (PTR_ERR(info->vbus) == -ENODEV) + info->vbus = NULL; + } else { + info->vbus = devm_regulator_get(dev, "vbus"); + } + + if (IS_ERR(info->vbus)) { + if (PTR_ERR(info->vbus) != -EPROBE_DEFER) + dev_err(dev, "failed to get vbus: %ld\n", PTR_ERR(info->vbus)); + 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); + } + + ret = usb_conn_psy_register(info); + if (ret) + goto put_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 */ + info->initial_detection = true; + 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 && info->vbus) + 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"); diff --git a/drivers/usb/common/usb-otg-fsm.c b/drivers/usb/common/usb-otg-fsm.c new file mode 100644 index 000000000..0697fde51 --- /dev/null +++ b/drivers/usb/common/usb-otg-fsm.c @@ -0,0 +1,452 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * OTG Finite State Machine from OTG spec + * + * Copyright (C) 2007,2008 Freescale Semiconductor, Inc. + * + * Author: Li Yang <LeoLi@freescale.com> + * Jerry Huang <Chang-Ming.Huang@freescale.com> + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/mutex.h> +#include <linux/delay.h> +#include <linux/usb.h> +#include <linux/usb/gadget.h> +#include <linux/usb/otg.h> +#include <linux/usb/otg-fsm.h> + +#ifdef VERBOSE +#define VDBG(fmt, args...) pr_debug("[%s] " fmt, \ + __func__, ## args) +#else +#define VDBG(stuff...) do {} while (0) +#endif + +/* Change USB protocol when there is a protocol change */ +static int otg_set_protocol(struct otg_fsm *fsm, int protocol) +{ + int ret = 0; + + if (fsm->protocol != protocol) { + VDBG("Changing role fsm->protocol= %d; new protocol= %d\n", + fsm->protocol, protocol); + /* stop old protocol */ + if (fsm->protocol == PROTO_HOST) + ret = otg_start_host(fsm, 0); + else if (fsm->protocol == PROTO_GADGET) + ret = otg_start_gadget(fsm, 0); + if (ret) + return ret; + + /* start new protocol */ + if (protocol == PROTO_HOST) + ret = otg_start_host(fsm, 1); + else if (protocol == PROTO_GADGET) + ret = otg_start_gadget(fsm, 1); + if (ret) + return ret; + + fsm->protocol = protocol; + return 0; + } + + return 0; +} + +/* Called when leaving a state. Do state clean up jobs here */ +static void otg_leave_state(struct otg_fsm *fsm, enum usb_otg_state old_state) +{ + switch (old_state) { + case OTG_STATE_B_IDLE: + otg_del_timer(fsm, B_SE0_SRP); + fsm->b_se0_srp = 0; + fsm->adp_sns = 0; + fsm->adp_prb = 0; + break; + case OTG_STATE_B_SRP_INIT: + fsm->data_pulse = 0; + fsm->b_srp_done = 0; + break; + case OTG_STATE_B_PERIPHERAL: + if (fsm->otg->gadget) + fsm->otg->gadget->host_request_flag = 0; + break; + case OTG_STATE_B_WAIT_ACON: + otg_del_timer(fsm, B_ASE0_BRST); + fsm->b_ase0_brst_tmout = 0; + break; + case OTG_STATE_B_HOST: + break; + case OTG_STATE_A_IDLE: + fsm->adp_prb = 0; + break; + case OTG_STATE_A_WAIT_VRISE: + otg_del_timer(fsm, A_WAIT_VRISE); + fsm->a_wait_vrise_tmout = 0; + break; + case OTG_STATE_A_WAIT_BCON: + otg_del_timer(fsm, A_WAIT_BCON); + fsm->a_wait_bcon_tmout = 0; + break; + case OTG_STATE_A_HOST: + otg_del_timer(fsm, A_WAIT_ENUM); + break; + case OTG_STATE_A_SUSPEND: + otg_del_timer(fsm, A_AIDL_BDIS); + fsm->a_aidl_bdis_tmout = 0; + fsm->a_suspend_req_inf = 0; + break; + case OTG_STATE_A_PERIPHERAL: + otg_del_timer(fsm, A_BIDL_ADIS); + fsm->a_bidl_adis_tmout = 0; + if (fsm->otg->gadget) + fsm->otg->gadget->host_request_flag = 0; + break; + case OTG_STATE_A_WAIT_VFALL: + otg_del_timer(fsm, A_WAIT_VFALL); + fsm->a_wait_vfall_tmout = 0; + otg_del_timer(fsm, A_WAIT_VRISE); + break; + case OTG_STATE_A_VBUS_ERR: + break; + default: + break; + } +} + +static void otg_hnp_polling_work(struct work_struct *work) +{ + struct otg_fsm *fsm = container_of(to_delayed_work(work), + struct otg_fsm, hnp_polling_work); + struct usb_device *udev; + enum usb_otg_state state = fsm->otg->state; + u8 flag; + int retval; + + if (state != OTG_STATE_A_HOST && state != OTG_STATE_B_HOST) + return; + + udev = usb_hub_find_child(fsm->otg->host->root_hub, 1); + if (!udev) { + dev_err(fsm->otg->host->controller, + "no usb dev connected, can't start HNP polling\n"); + return; + } + + *fsm->host_req_flag = 0; + /* Get host request flag from connected USB device */ + retval = usb_control_msg(udev, + usb_rcvctrlpipe(udev, 0), + USB_REQ_GET_STATUS, + USB_DIR_IN | USB_RECIP_DEVICE, + 0, + OTG_STS_SELECTOR, + fsm->host_req_flag, + 1, + USB_CTRL_GET_TIMEOUT); + if (retval != 1) { + dev_err(&udev->dev, "Get one byte OTG status failed\n"); + return; + } + + flag = *fsm->host_req_flag; + if (flag == 0) { + /* Continue HNP polling */ + schedule_delayed_work(&fsm->hnp_polling_work, + msecs_to_jiffies(T_HOST_REQ_POLL)); + return; + } else if (flag != HOST_REQUEST_FLAG) { + dev_err(&udev->dev, "host request flag %d is invalid\n", flag); + return; + } + + /* Host request flag is set */ + if (state == OTG_STATE_A_HOST) { + /* Set b_hnp_enable */ + if (!fsm->otg->host->b_hnp_enable) { + retval = usb_control_msg(udev, + usb_sndctrlpipe(udev, 0), + USB_REQ_SET_FEATURE, 0, + USB_DEVICE_B_HNP_ENABLE, + 0, NULL, 0, + USB_CTRL_SET_TIMEOUT); + if (retval >= 0) + fsm->otg->host->b_hnp_enable = 1; + } + fsm->a_bus_req = 0; + } else if (state == OTG_STATE_B_HOST) { + fsm->b_bus_req = 0; + } + + otg_statemachine(fsm); +} + +static void otg_start_hnp_polling(struct otg_fsm *fsm) +{ + /* + * The memory of host_req_flag should be allocated by + * controller driver, otherwise, hnp polling is not started. + */ + if (!fsm->host_req_flag) + return; + + if (!fsm->hnp_work_inited) { + INIT_DELAYED_WORK(&fsm->hnp_polling_work, otg_hnp_polling_work); + fsm->hnp_work_inited = true; + } + + schedule_delayed_work(&fsm->hnp_polling_work, + msecs_to_jiffies(T_HOST_REQ_POLL)); +} + +/* Called when entering a state */ +static int otg_set_state(struct otg_fsm *fsm, enum usb_otg_state new_state) +{ + if (fsm->otg->state == new_state) + return 0; + VDBG("Set state: %s\n", usb_otg_state_string(new_state)); + otg_leave_state(fsm, fsm->otg->state); + switch (new_state) { + case OTG_STATE_B_IDLE: + otg_drv_vbus(fsm, 0); + otg_chrg_vbus(fsm, 0); + otg_loc_conn(fsm, 0); + otg_loc_sof(fsm, 0); + /* + * Driver is responsible for starting ADP probing + * if ADP sensing times out. + */ + otg_start_adp_sns(fsm); + otg_set_protocol(fsm, PROTO_UNDEF); + otg_add_timer(fsm, B_SE0_SRP); + break; + case OTG_STATE_B_SRP_INIT: + otg_start_pulse(fsm); + otg_loc_sof(fsm, 0); + otg_set_protocol(fsm, PROTO_UNDEF); + otg_add_timer(fsm, B_SRP_FAIL); + break; + case OTG_STATE_B_PERIPHERAL: + otg_chrg_vbus(fsm, 0); + otg_loc_sof(fsm, 0); + otg_set_protocol(fsm, PROTO_GADGET); + otg_loc_conn(fsm, 1); + break; + case OTG_STATE_B_WAIT_ACON: + otg_chrg_vbus(fsm, 0); + otg_loc_conn(fsm, 0); + otg_loc_sof(fsm, 0); + otg_set_protocol(fsm, PROTO_HOST); + otg_add_timer(fsm, B_ASE0_BRST); + fsm->a_bus_suspend = 0; + break; + case OTG_STATE_B_HOST: + otg_chrg_vbus(fsm, 0); + otg_loc_conn(fsm, 0); + otg_loc_sof(fsm, 1); + otg_set_protocol(fsm, PROTO_HOST); + usb_bus_start_enum(fsm->otg->host, + fsm->otg->host->otg_port); + otg_start_hnp_polling(fsm); + break; + case OTG_STATE_A_IDLE: + otg_drv_vbus(fsm, 0); + otg_chrg_vbus(fsm, 0); + otg_loc_conn(fsm, 0); + otg_loc_sof(fsm, 0); + otg_start_adp_prb(fsm); + otg_set_protocol(fsm, PROTO_HOST); + break; + case OTG_STATE_A_WAIT_VRISE: + otg_drv_vbus(fsm, 1); + otg_loc_conn(fsm, 0); + otg_loc_sof(fsm, 0); + otg_set_protocol(fsm, PROTO_HOST); + otg_add_timer(fsm, A_WAIT_VRISE); + break; + case OTG_STATE_A_WAIT_BCON: + otg_drv_vbus(fsm, 1); + otg_loc_conn(fsm, 0); + otg_loc_sof(fsm, 0); + otg_set_protocol(fsm, PROTO_HOST); + otg_add_timer(fsm, A_WAIT_BCON); + break; + case OTG_STATE_A_HOST: + otg_drv_vbus(fsm, 1); + otg_loc_conn(fsm, 0); + otg_loc_sof(fsm, 1); + otg_set_protocol(fsm, PROTO_HOST); + /* + * When HNP is triggered while a_bus_req = 0, a_host will + * suspend too fast to complete a_set_b_hnp_en + */ + if (!fsm->a_bus_req || fsm->a_suspend_req_inf) + otg_add_timer(fsm, A_WAIT_ENUM); + otg_start_hnp_polling(fsm); + break; + case OTG_STATE_A_SUSPEND: + otg_drv_vbus(fsm, 1); + otg_loc_conn(fsm, 0); + otg_loc_sof(fsm, 0); + otg_set_protocol(fsm, PROTO_HOST); + otg_add_timer(fsm, A_AIDL_BDIS); + + break; + case OTG_STATE_A_PERIPHERAL: + otg_loc_sof(fsm, 0); + otg_set_protocol(fsm, PROTO_GADGET); + otg_drv_vbus(fsm, 1); + otg_loc_conn(fsm, 1); + otg_add_timer(fsm, A_BIDL_ADIS); + break; + case OTG_STATE_A_WAIT_VFALL: + otg_drv_vbus(fsm, 0); + otg_loc_conn(fsm, 0); + otg_loc_sof(fsm, 0); + otg_set_protocol(fsm, PROTO_HOST); + otg_add_timer(fsm, A_WAIT_VFALL); + break; + case OTG_STATE_A_VBUS_ERR: + otg_drv_vbus(fsm, 0); + otg_loc_conn(fsm, 0); + otg_loc_sof(fsm, 0); + otg_set_protocol(fsm, PROTO_UNDEF); + break; + default: + break; + } + + fsm->otg->state = new_state; + fsm->state_changed = 1; + return 0; +} + +/* State change judgement */ +int otg_statemachine(struct otg_fsm *fsm) +{ + enum usb_otg_state state; + + mutex_lock(&fsm->lock); + + state = fsm->otg->state; + fsm->state_changed = 0; + /* State machine state change judgement */ + + switch (state) { + case OTG_STATE_UNDEFINED: + VDBG("fsm->id = %d\n", fsm->id); + if (fsm->id) + otg_set_state(fsm, OTG_STATE_B_IDLE); + else + otg_set_state(fsm, OTG_STATE_A_IDLE); + break; + case OTG_STATE_B_IDLE: + if (!fsm->id) + otg_set_state(fsm, OTG_STATE_A_IDLE); + else if (fsm->b_sess_vld && fsm->otg->gadget) + otg_set_state(fsm, OTG_STATE_B_PERIPHERAL); + else if ((fsm->b_bus_req || fsm->adp_change || fsm->power_up) && + fsm->b_ssend_srp && fsm->b_se0_srp) + otg_set_state(fsm, OTG_STATE_B_SRP_INIT); + break; + case OTG_STATE_B_SRP_INIT: + if (!fsm->id || fsm->b_srp_done) + otg_set_state(fsm, OTG_STATE_B_IDLE); + break; + case OTG_STATE_B_PERIPHERAL: + if (!fsm->id || !fsm->b_sess_vld) + otg_set_state(fsm, OTG_STATE_B_IDLE); + else if (fsm->b_bus_req && fsm->otg-> + gadget->b_hnp_enable && fsm->a_bus_suspend) + otg_set_state(fsm, OTG_STATE_B_WAIT_ACON); + break; + case OTG_STATE_B_WAIT_ACON: + if (fsm->a_conn) + otg_set_state(fsm, OTG_STATE_B_HOST); + else if (!fsm->id || !fsm->b_sess_vld) + otg_set_state(fsm, OTG_STATE_B_IDLE); + else if (fsm->a_bus_resume || fsm->b_ase0_brst_tmout) { + fsm->b_ase0_brst_tmout = 0; + otg_set_state(fsm, OTG_STATE_B_PERIPHERAL); + } + break; + case OTG_STATE_B_HOST: + if (!fsm->id || !fsm->b_sess_vld) + otg_set_state(fsm, OTG_STATE_B_IDLE); + else if (!fsm->b_bus_req || !fsm->a_conn || fsm->test_device) + otg_set_state(fsm, OTG_STATE_B_PERIPHERAL); + break; + case OTG_STATE_A_IDLE: + if (fsm->id) + otg_set_state(fsm, OTG_STATE_B_IDLE); + else if (!fsm->a_bus_drop && (fsm->a_bus_req || + fsm->a_srp_det || fsm->adp_change || fsm->power_up)) + otg_set_state(fsm, OTG_STATE_A_WAIT_VRISE); + break; + case OTG_STATE_A_WAIT_VRISE: + if (fsm->a_vbus_vld) + otg_set_state(fsm, OTG_STATE_A_WAIT_BCON); + else if (fsm->id || fsm->a_bus_drop || + fsm->a_wait_vrise_tmout) + otg_set_state(fsm, OTG_STATE_A_WAIT_VFALL); + break; + case OTG_STATE_A_WAIT_BCON: + if (!fsm->a_vbus_vld) + otg_set_state(fsm, OTG_STATE_A_VBUS_ERR); + else if (fsm->b_conn) + otg_set_state(fsm, OTG_STATE_A_HOST); + else if (fsm->id || fsm->a_bus_drop || fsm->a_wait_bcon_tmout) + otg_set_state(fsm, OTG_STATE_A_WAIT_VFALL); + break; + case OTG_STATE_A_HOST: + if (fsm->id || fsm->a_bus_drop) + otg_set_state(fsm, OTG_STATE_A_WAIT_VFALL); + else if ((!fsm->a_bus_req || fsm->a_suspend_req_inf) && + fsm->otg->host->b_hnp_enable) + otg_set_state(fsm, OTG_STATE_A_SUSPEND); + else if (!fsm->b_conn) + otg_set_state(fsm, OTG_STATE_A_WAIT_BCON); + else if (!fsm->a_vbus_vld) + otg_set_state(fsm, OTG_STATE_A_VBUS_ERR); + break; + case OTG_STATE_A_SUSPEND: + if (!fsm->b_conn && fsm->otg->host->b_hnp_enable) + otg_set_state(fsm, OTG_STATE_A_PERIPHERAL); + else if (!fsm->b_conn && !fsm->otg->host->b_hnp_enable) + otg_set_state(fsm, OTG_STATE_A_WAIT_BCON); + else if (fsm->a_bus_req || fsm->b_bus_resume) + otg_set_state(fsm, OTG_STATE_A_HOST); + else if (fsm->id || fsm->a_bus_drop || fsm->a_aidl_bdis_tmout) + otg_set_state(fsm, OTG_STATE_A_WAIT_VFALL); + else if (!fsm->a_vbus_vld) + otg_set_state(fsm, OTG_STATE_A_VBUS_ERR); + break; + case OTG_STATE_A_PERIPHERAL: + if (fsm->id || fsm->a_bus_drop) + otg_set_state(fsm, OTG_STATE_A_WAIT_VFALL); + else if (fsm->a_bidl_adis_tmout || fsm->b_bus_suspend) + otg_set_state(fsm, OTG_STATE_A_WAIT_BCON); + else if (!fsm->a_vbus_vld) + otg_set_state(fsm, OTG_STATE_A_VBUS_ERR); + break; + case OTG_STATE_A_WAIT_VFALL: + if (fsm->a_wait_vfall_tmout) + otg_set_state(fsm, OTG_STATE_A_IDLE); + break; + case OTG_STATE_A_VBUS_ERR: + if (fsm->id || fsm->a_bus_drop || fsm->a_clr_err) + otg_set_state(fsm, OTG_STATE_A_WAIT_VFALL); + break; + default: + break; + } + mutex_unlock(&fsm->lock); + + VDBG("quit statemachine, changed = %d\n", fsm->state_changed); + return fsm->state_changed; +} +EXPORT_SYMBOL_GPL(otg_statemachine); +MODULE_LICENSE("GPL"); |