diff options
Diffstat (limited to 'drivers/usb/common')
| -rw-r--r-- | drivers/usb/common/Kconfig | 4 | ||||
| -rw-r--r-- | drivers/usb/common/common.c | 155 | ||||
| -rw-r--r-- | drivers/usb/common/debug.c | 93 | ||||
| -rw-r--r-- | drivers/usb/common/led.c | 4 | ||||
| -rw-r--r-- | drivers/usb/common/ulpi.c | 95 | ||||
| -rw-r--r-- | drivers/usb/common/usb-conn-gpio.c | 138 | ||||
| -rw-r--r-- | drivers/usb/common/usb-otg-fsm.c | 7 |
7 files changed, 418 insertions, 78 deletions
diff --git a/drivers/usb/common/Kconfig b/drivers/usb/common/Kconfig index d611477aae41..b856622431a7 100644 --- a/drivers/usb/common/Kconfig +++ b/drivers/usb/common/Kconfig @@ -6,8 +6,7 @@ config USB_COMMON config USB_LED_TRIG bool "USB LED Triggers" - depends on LEDS_CLASS && LEDS_TRIGGERS - select USB_COMMON + depends on LEDS_CLASS && USB_COMMON && LEDS_TRIGGERS help This option adds LED triggers for USB host and/or gadget activity. @@ -40,6 +39,7 @@ 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 diff --git a/drivers/usb/common/common.c b/drivers/usb/common/common.c index 1433260d99b4..fc0845f681be 100644 --- a/drivers/usb/common/common.c +++ b/drivers/usb/common/common.c @@ -11,6 +11,7 @@ #include <linux/kernel.h> #include <linux/module.h> #include <linux/of.h> +#include <linux/platform_device.h> #include <linux/usb/ch9.h> #include <linux/usb/of.h> #include <linux/usb/otg.h> @@ -25,6 +26,12 @@ static const char *const ep_type_names[] = { [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)) @@ -34,6 +41,12 @@ const char *usb_ep_type_string(int ep_type) } EXPORT_SYMBOL_GPL(usb_ep_type_string); +/** + * usb_otg_state_string() - returns human readable name of OTG state. + * @state: the OTG state to return the human readable name of. If it's not + * any of the states defined in usb_otg_state enum, 'UNDEFINED' will be + * returned. + */ const char *usb_otg_state_string(enum usb_otg_state state) { static const char *const names[] = { @@ -69,6 +82,19 @@ static const char *const speed_names[] = { [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)) @@ -77,21 +103,60 @@ const char *usb_speed_string(enum usb_device_speed 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 *p = "maximum-speed"; + int ret; + + ret = device_property_match_property_string(dev, p, ssp_rate, ARRAY_SIZE(ssp_rate)); + if (ret > 0) + return USB_SPEED_SUPER_PLUS; + + ret = device_property_match_property_string(dev, p, speed_names, ARRAY_SIZE(speed_names)); + if (ret > 0) + return ret; + + return USB_SPEED_UNKNOWN; +} +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_SPEED_UNKNOWN; - - ret = match_string(speed_names, ARRAY_SIZE(speed_names), maximum_speed); + return USB_SSP_GEN_UNKNOWN; - return (ret < 0) ? USB_SPEED_UNKNOWN : ret; + 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_speed); +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[] = { @@ -120,6 +185,14 @@ static const char *const usb_dr_modes[] = { [USB_DR_MODE_OTG] = "otg", }; +/** + * usb_get_dr_mode_from_string() - Get dual role mode for given string + * @str: String to find the corresponding dual role mode for + * + * This function performs a lookup for the given string and returns the + * corresponding enum usb_dr_mode. If no match for the string could be found, + * 'USB_DR_MODE_UNKNOWN' is returned. + */ static enum usb_dr_mode usb_get_dr_mode_from_string(const char *str) { int ret; @@ -141,6 +214,67 @@ enum usb_dr_mode usb_get_dr_mode(struct device *dev) } EXPORT_SYMBOL_GPL(usb_get_dr_mode); +/** + * usb_get_role_switch_default_mode - Get default mode for given device + * @dev: Pointer to the given device + * + * The function gets string from property 'role-switch-default-mode', + * and returns the corresponding enum usb_dr_mode. + */ +enum usb_dr_mode usb_get_role_switch_default_mode(struct device *dev) +{ + const char *str; + int ret; + + ret = device_property_read_string(dev, "role-switch-default-mode", &str); + if (ret < 0) + return USB_DR_MODE_UNKNOWN; + + return usb_get_dr_mode_from_string(str); +} +EXPORT_SYMBOL_GPL(usb_get_role_switch_default_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 @@ -155,14 +289,13 @@ EXPORT_SYMBOL_GPL(usb_get_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 device_node *controller; struct of_phandle_args args; const char *dr_mode; int index; int err; - do { - controller = of_find_node_with_property(controller, "phys"); + for_each_node_with_property(controller, "phys") { if (!of_device_is_available(controller)) continue; index = 0; @@ -185,7 +318,7 @@ enum usb_dr_mode of_usb_get_dr_mode_by_phy(struct device_node *np, int arg0) goto finish; index++; } while (args.np); - } while (controller); + } finish: err = of_property_read_string(controller, "dr_mode", &dr_mode); @@ -296,6 +429,9 @@ EXPORT_SYMBOL_GPL(usb_of_get_companion_dev); struct dentry *usb_debug_root; EXPORT_SYMBOL_GPL(usb_debug_root); +DEFINE_MUTEX(usb_dynids_lock); +EXPORT_SYMBOL_GPL(usb_dynids_lock); + static int __init usb_common_init(void) { usb_debug_root = debugfs_create_dir("usb", NULL); @@ -312,4 +448,5 @@ static void __exit usb_common_exit(void) subsys_initcall(usb_common_init); module_exit(usb_common_exit); +MODULE_DESCRIPTION("Common code for host and device side USB"); MODULE_LICENSE("GPL"); diff --git a/drivers/usb/common/debug.c b/drivers/usb/common/debug.c index 92a986aeaa5d..f204cec8d380 100644 --- a/drivers/usb/common/debug.c +++ b/drivers/usb/common/debug.c @@ -1,13 +1,14 @@ // SPDX-License-Identifier: GPL-2.0 -/** +/* * Common USB debugging functions * - * Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com + * 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/kernel.h> #include <linux/usb/ch9.h> static void usb_decode_get_status(__u8 bRequestType, __u16 wIndex, @@ -53,15 +54,15 @@ static const char *usb_decode_device_feature(u16 wValue) static const char *usb_decode_test_mode(u16 wIndex) { switch (wIndex) { - case TEST_J: + case USB_TEST_J: return ": TEST_J"; - case TEST_K: + case USB_TEST_K: return ": TEST_K"; - case TEST_SE0_NAK: + case USB_TEST_SE0_NAK: return ": TEST_SE0_NAK"; - case TEST_PACKET: + case USB_TEST_PACKET: return ": TEST_PACKET"; - case TEST_FORCE_EN: + case USB_TEST_FORCE_ENABLE: return ": TEST_FORCE_EN"; default: return ": UNKNOWN"; @@ -207,12 +208,28 @@ 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) +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: @@ -253,14 +270,48 @@ const char *usb_decode_ctrl(char *str, size_t size, __u8 bRequestType, 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)); + 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; diff --git a/drivers/usb/common/led.c b/drivers/usb/common/led.c index 0865dd44a80a..1de18d90b134 100644 --- a/drivers/usb/common/led.c +++ b/drivers/usb/common/led.c @@ -14,8 +14,6 @@ #define BLINK_DELAY 30 -static unsigned long usb_blink_delay = BLINK_DELAY; - DEFINE_LED_TRIGGER(ledtrig_usb_gadget); DEFINE_LED_TRIGGER(ledtrig_usb_host); @@ -32,7 +30,7 @@ void usb_led_activity(enum usb_led_event ev) break; } /* led_trigger_blink_oneshot() handles trig == NULL gracefully */ - led_trigger_blink_oneshot(trig, &usb_blink_delay, &usb_blink_delay, 0); + led_trigger_blink_oneshot(trig, BLINK_DELAY, BLINK_DELAY, 0); } EXPORT_SYMBOL_GPL(usb_led_activity); diff --git a/drivers/usb/common/ulpi.c b/drivers/usb/common/ulpi.c index 9a2ab6751a23..4a2ee447b213 100644 --- a/drivers/usb/common/ulpi.c +++ b/drivers/usb/common/ulpi.c @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0 -/** +/* * ulpi.c - USB ULPI PHY bus * * Copyright (C) 2015 Intel Corporation @@ -13,6 +13,7 @@ #include <linux/module.h> #include <linux/slab.h> #include <linux/acpi.h> +#include <linux/debugfs.h> #include <linux/of.h> #include <linux/of_device.h> #include <linux/clk/clk-conf.h> @@ -33,14 +34,17 @@ EXPORT_SYMBOL_GPL(ulpi_write); /* -------------------------------------------------------------------------- */ -static int ulpi_match(struct device *dev, struct device_driver *driver) +static int ulpi_match(struct device *dev, const 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 so rely on OF match */ - if (ulpi->id.vendor == 0) + /* + * 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++) @@ -51,9 +55,9 @@ static int ulpi_match(struct device *dev, struct device_driver *driver) return 0; } -static int ulpi_uevent(struct device *dev, struct kobj_uevent_env *env) +static int ulpi_uevent(const struct device *dev, struct kobj_uevent_env *env) { - struct ulpi *ulpi = to_ulpi_dev(dev); + const struct ulpi *ulpi = to_ulpi_dev(dev); int ret; ret = of_device_uevent_modalias(dev, env); @@ -78,17 +82,15 @@ static int ulpi_probe(struct device *dev) return drv->probe(to_ulpi_dev(dev)); } -static int ulpi_remove(struct device *dev) +static void 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 = { +static const struct bus_type ulpi_bus = { .name = "ulpi", .match = ulpi_match, .uevent = ulpi_uevent, @@ -118,7 +120,7 @@ static struct attribute *ulpi_dev_attrs[] = { NULL }; -static struct attribute_group ulpi_dev_attr_group = { +static const struct attribute_group ulpi_dev_attr_group = { .attrs = ulpi_dev_attrs, }; @@ -129,6 +131,7 @@ static const struct attribute_group *ulpi_dev_attr_groups[] = { static void ulpi_dev_release(struct device *dev) { + of_node_put(dev->of_node); kfree(to_ulpi_dev(dev)); } @@ -141,8 +144,9 @@ static const struct device_type ulpi_dev_type = { /* -------------------------------------------------------------------------- */ /** - * ulpi_register_driver - register a driver with the ULPI bus + * __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. */ @@ -225,13 +229,54 @@ static int ulpi_read_id(struct ulpi *ulpi) request_module("ulpi:v%04xp%04x", ulpi->id.vendor, ulpi->id.product); return 0; err: - of_device_request_module(&ulpi->dev); + of_request_module(ulpi->dev.of_node); + return 0; +} + +static int ulpi_regs_show(struct seq_file *seq, void *data) +{ + struct ulpi *ulpi = seq->private; + +#define ulpi_print(name, reg) do { \ + int ret = ulpi_read(ulpi, reg); \ + if (ret < 0) \ + return ret; \ + seq_printf(seq, name " %.02x\n", ret); \ +} while (0) + + ulpi_print("Vendor ID Low ", ULPI_VENDOR_ID_LOW); + ulpi_print("Vendor ID High ", ULPI_VENDOR_ID_HIGH); + ulpi_print("Product ID Low ", ULPI_PRODUCT_ID_LOW); + ulpi_print("Product ID High ", ULPI_PRODUCT_ID_HIGH); + ulpi_print("Function Control ", ULPI_FUNC_CTRL); + ulpi_print("Interface Control ", ULPI_IFC_CTRL); + ulpi_print("OTG Control ", ULPI_OTG_CTRL); + ulpi_print("USB Interrupt Enable Rising ", ULPI_USB_INT_EN_RISE); + ulpi_print("USB Interrupt Enable Falling", ULPI_USB_INT_EN_FALL); + ulpi_print("USB Interrupt Status ", ULPI_USB_INT_STS); + ulpi_print("USB Interrupt Latch ", ULPI_USB_INT_LATCH); + ulpi_print("Debug ", ULPI_DEBUG); + ulpi_print("Scratch Register ", ULPI_SCRATCH); + ulpi_print("Carkit Control ", ULPI_CARKIT_CTRL); + ulpi_print("Carkit Interrupt Delay ", ULPI_CARKIT_INT_DELAY); + ulpi_print("Carkit Interrupt Enable ", ULPI_CARKIT_INT_EN); + ulpi_print("Carkit Interrupt Status ", ULPI_CARKIT_INT_STS); + ulpi_print("Carkit Interrupt Latch ", ULPI_CARKIT_INT_LATCH); + ulpi_print("Carkit Pulse Control ", ULPI_CARKIT_PLS_CTRL); + ulpi_print("Transmit Positive Width ", ULPI_TX_POS_WIDTH); + ulpi_print("Transmit Negative Width ", ULPI_TX_NEG_WIDTH); + ulpi_print("Receive Polarity Recovery ", ULPI_POLARITY_RECOVERY); + return 0; } +DEFINE_SHOW_ATTRIBUTE(ulpi_regs); + +static struct dentry *ulpi_root; static int ulpi_register(struct device *dev, struct ulpi *ulpi) { int ret; + struct dentry *root; ulpi->dev.parent = dev; /* needed early for ops */ ulpi->dev.bus = &ulpi_bus; @@ -245,12 +290,19 @@ static int ulpi_register(struct device *dev, struct ulpi *ulpi) return ret; ret = ulpi_read_id(ulpi); - if (ret) + if (ret) { + of_node_put(ulpi->dev.of_node); return ret; + } ret = device_register(&ulpi->dev); - if (ret) + if (ret) { + put_device(&ulpi->dev); return ret; + } + + root = debugfs_create_dir(dev_name(&ulpi->dev), ulpi_root); + debugfs_create_file("regs", 0444, root, ulpi, &ulpi_regs_fops); dev_dbg(&ulpi->dev, "registered ULPI PHY: vendor %04x, product %04x\n", ulpi->id.vendor, ulpi->id.product); @@ -290,14 +342,14 @@ EXPORT_SYMBOL_GPL(ulpi_register_interface); /** * ulpi_unregister_interface - unregister ULPI interface - * @intrf: struct 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) { - of_node_put(ulpi->dev.of_node); + debugfs_lookup_and_remove(dev_name(&ulpi->dev), ulpi_root); device_unregister(&ulpi->dev); } EXPORT_SYMBOL_GPL(ulpi_unregister_interface); @@ -306,13 +358,20 @@ EXPORT_SYMBOL_GPL(ulpi_unregister_interface); static int __init ulpi_init(void) { - return bus_register(&ulpi_bus); + int ret; + + ulpi_root = debugfs_create_dir(KBUILD_MODNAME, NULL); + ret = bus_register(&ulpi_bus); + if (ret) + debugfs_remove(ulpi_root); + return ret; } subsys_initcall(ulpi_init); static void __exit ulpi_exit(void) { bus_unregister(&ulpi_bus); + debugfs_remove(ulpi_root); } module_exit(ulpi_exit); diff --git a/drivers/usb/common/usb-conn-gpio.c b/drivers/usb/common/usb-conn-gpio.c index ed204cbb63ea..421c3af38e06 100644 --- a/drivers/usb/common/usb-conn-gpio.c +++ b/drivers/usb/common/usb-conn-gpio.c @@ -17,8 +17,13 @@ #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/string_choices.h> #include <linux/usb/role.h> +#include <linux/idr.h> + +static DEFINE_IDA(usb_conn_ida); #define USB_GPIO_DEB_MS 20 /* ms */ #define USB_GPIO_DEB_US ((USB_GPIO_DEB_MS) * 1000) /* us */ @@ -28,6 +33,7 @@ struct usb_conn_info { struct device *dev; + int conn_id; /* store the IDA-allocated ID */ struct usb_role_switch *role_sw; enum usb_role last_role; struct regulator *vbus; @@ -38,9 +44,13 @@ struct usb_conn_info { 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 @@ -79,22 +89,24 @@ static void usb_conn_detect_cable(struct work_struct *work) else role = USB_ROLE_NONE; - dev_dbg(info->dev, "role %d/%d, gpios: id %d, vbus %d\n", - info->last_role, role, id, vbus); + dev_dbg(info->dev, "role %s -> %s, gpios: id %d, vbus %d\n", + usb_role_string(info->last_role), usb_role_string(role), id, vbus); - if (info->last_role == role) { - dev_warn(info->dev, "repeated role: %d\n", role); + if (!info->initial_detection && info->last_role == role) { + dev_warn(info->dev, "repeated role: %s\n", usb_role_string(role)); return; } - if (info->last_role == USB_ROLE_HOST) + 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) { + if (role == USB_ROLE_HOST && info->vbus) { ret = regulator_enable(info->vbus); if (ret) dev_err(info->dev, "enable vbus regulator failed\n"); @@ -102,8 +114,11 @@ static void usb_conn_detect_cable(struct work_struct *work) info->last_role = role; - dev_dbg(info->dev, "vbus regulator is %s\n", - regulator_is_enabled(info->vbus) ? "enabled" : "disabled"); + if (info->vbus) + dev_dbg(info->dev, "vbus regulator is %s\n", + str_enabled_disabled(regulator_is_enabled(info->vbus))); + + power_supply_changed(info->charger); } static void usb_conn_queue_dwork(struct usb_conn_info *info, @@ -121,6 +136,61 @@ static irqreturn_t usb_conn_isr(int irq, void *dev_id) 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 = { + .fwnode = dev_fwnode(dev), + }; + + info->conn_id = ida_alloc(&usb_conn_ida, GFP_KERNEL); + if (info->conn_id < 0) + return info->conn_id; + + desc->name = devm_kasprintf(dev, GFP_KERNEL, "usb-charger-%d", + info->conn_id); + if (!desc->name) { + ida_free(&usb_conn_ida, info->conn_id); + return -ENOMEM; + } + + 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 %d\n", info->conn_id); + ida_free(&usb_conn_ida, info->conn_id); + } + + return PTR_ERR_OR_ZERO(info->charger); +} + static int usb_conn_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -154,20 +224,21 @@ static int usb_conn_probe(struct platform_device *pdev) INIT_DELAYED_WORK(&info->dw_det, usb_conn_detect_cable); - 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\n"); - return PTR_ERR(info->vbus); - } + info->vbus = devm_regulator_get_optional(dev, "vbus"); + if (PTR_ERR(info->vbus) == -ENODEV) + info->vbus = NULL; + + if (IS_ERR(info->vbus)) + return dev_err_probe(dev, PTR_ERR(info->vbus), "failed to get vbus\n"); 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"); + if (IS_ERR(info->role_sw)) + return dev_err_probe(dev, PTR_ERR(info->role_sw), + "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); @@ -204,8 +275,10 @@ static int usb_conn_probe(struct platform_device *pdev) } platform_set_drvdata(pdev, info); + device_set_wakeup_capable(&pdev->dev, true); /* Perform initial detection */ + info->initial_detection = true; usb_conn_queue_dwork(info, 0); return 0; @@ -215,24 +288,33 @@ put_role_sw: return ret; } -static int usb_conn_remove(struct platform_device *pdev) +static void 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) + if (info->charger) + ida_free(&usb_conn_ida, info->conn_id); + + 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 (device_may_wakeup(dev)) { + if (info->id_gpiod) + enable_irq_wake(info->id_irq); + if (info->vbus_gpiod) + enable_irq_wake(info->vbus_irq); + return 0; + } + if (info->id_gpiod) disable_irq(info->id_irq); if (info->vbus_gpiod) @@ -247,6 +329,14 @@ static int __maybe_unused usb_conn_resume(struct device *dev) { struct usb_conn_info *info = dev_get_drvdata(dev); + if (device_may_wakeup(dev)) { + if (info->id_gpiod) + disable_irq_wake(info->id_irq); + if (info->vbus_gpiod) + disable_irq_wake(info->vbus_irq); + return 0; + } + pinctrl_pm_select_default_state(dev); if (info->id_gpiod) diff --git a/drivers/usb/common/usb-otg-fsm.c b/drivers/usb/common/usb-otg-fsm.c index 3740cf95560e..e11803225775 100644 --- a/drivers/usb/common/usb-otg-fsm.c +++ b/drivers/usb/common/usb-otg-fsm.c @@ -193,7 +193,11 @@ static void otg_start_hnp_polling(struct otg_fsm *fsm) if (!fsm->host_req_flag) return; - INIT_DELAYED_WORK(&fsm->hnp_polling_work, otg_hnp_polling_work); + 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)); } @@ -445,4 +449,5 @@ int otg_statemachine(struct otg_fsm *fsm) return fsm->state_changed; } EXPORT_SYMBOL_GPL(otg_statemachine); +MODULE_DESCRIPTION("OTG Finite State Machine"); MODULE_LICENSE("GPL"); |
