diff options
52 files changed, 2241 insertions, 563 deletions
diff --git a/Documentation/ABI/testing/sysfs-bus-usb b/Documentation/ABI/testing/sysfs-bus-usb index c702c78f24d8..c6e9b30f05b1 100644 --- a/Documentation/ABI/testing/sysfs-bus-usb +++ b/Documentation/ABI/testing/sysfs-bus-usb @@ -236,3 +236,21 @@ Description: Supported values are 0 - 15. More information on how besl values map to microseconds can be found in USB 2.0 ECN Errata for Link Power Management, section 4.10) + +What: /sys/bus/usb/devices/.../rx_lanes +Date: March 2018 +Contact: Mathias Nyman <mathias.nyman@linux.intel.com> +Description: + Number of rx lanes the device is using. + USB 3.2 adds Dual-lane support, 2 rx and 2 tx lanes over Type-C. + Inter-Chip SSIC devices support asymmetric lanes up to 4 lanes per + direction. Devices before USB 3.2 are single lane (rx_lanes = 1) + +What: /sys/bus/usb/devices/.../tx_lanes +Date: March 2018 +Contact: Mathias Nyman <mathias.nyman@linux.intel.com> +Description: + Number of tx lanes the device is using. + USB 3.2 adds Dual-lane support, 2 rx and 2 tx -lanes over Type-C. + Inter-Chip SSIC devices support asymmetric lanes up to 4 lanes per + direction. Devices before USB 3.2 are single lane (tx_lanes = 1) diff --git a/Documentation/ABI/testing/sysfs-class-power b/Documentation/ABI/testing/sysfs-class-power index f85ce9e327b9..5e23e22dce1b 100644 --- a/Documentation/ABI/testing/sysfs-class-power +++ b/Documentation/ABI/testing/sysfs-class-power @@ -1,3 +1,458 @@ +===== General Properties ===== + +What: /sys/class/power_supply/<supply_name>/manufacturer +Date: May 2007 +Contact: linux-pm@vger.kernel.org +Description: + Reports the name of the device manufacturer. + + Access: Read + Valid values: Represented as string + +What: /sys/class/power_supply/<supply_name>/model_name +Date: May 2007 +Contact: linux-pm@vger.kernel.org +Description: + Reports the name of the device model. + + Access: Read + Valid values: Represented as string + +What: /sys/class/power_supply/<supply_name>/serial_number +Date: January 2008 +Contact: linux-pm@vger.kernel.org +Description: + Reports the serial number of the device. + + Access: Read + Valid values: Represented as string + +What: /sys/class/power_supply/<supply_name>/type +Date: May 2010 +Contact: linux-pm@vger.kernel.org +Description: + Describes the main type of the supply. + + Access: Read + Valid values: "Battery", "UPS", "Mains", "USB" + +===== Battery Properties ===== + +What: /sys/class/power_supply/<supply_name>/capacity +Date: May 2007 +Contact: linux-pm@vger.kernel.org +Description: + Fine grain representation of battery capacity. + Access: Read + Valid values: 0 - 100 (percent) + +What: /sys/class/power_supply/<supply_name>/capacity_alert_max +Date: July 2012 +Contact: linux-pm@vger.kernel.org +Description: + Maximum battery capacity trip-wire value where the supply will + notify user-space of the event. This is normally used for the + battery discharging scenario where user-space needs to know the + battery has dropped to an upper level so it can take + appropriate action (e.g. warning user that battery level is + low). + + Access: Read, Write + Valid values: 0 - 100 (percent) + +What: /sys/class/power_supply/<supply_name>/capacity_alert_min +Date: July 2012 +Contact: linux-pm@vger.kernel.org +Description: + Minimum battery capacity trip-wire value where the supply will + notify user-space of the event. This is normally used for the + battery discharging scenario where user-space needs to know the + battery has dropped to a lower level so it can take + appropriate action (e.g. warning user that battery level is + critically low). + + Access: Read, Write + Valid values: 0 - 100 (percent) + +What: /sys/class/power_supply/<supply_name>/capacity_level +Date: June 2009 +Contact: linux-pm@vger.kernel.org +Description: + Coarse representation of battery capacity. + + Access: Read + Valid values: "Unknown", "Critical", "Low", "Normal", "High", + "Full" + +What: /sys/class/power_supply/<supply_name>/current_avg +Date: May 2007 +Contact: linux-pm@vger.kernel.org +Description: + Reports an average IBAT current reading for the battery, over a + fixed period. Normally devices will provide a fixed interval in + which they average readings to smooth out the reported value. + + Access: Read + Valid values: Represented in microamps + +What: /sys/class/power_supply/<supply_name>/current_max +Date: October 2010 +Contact: linux-pm@vger.kernel.org +Description: + Reports the maximum IBAT current allowed into the battery. + + Access: Read + Valid values: Represented in microamps + +What: /sys/class/power_supply/<supply_name>/current_now +Date: May 2007 +Contact: linux-pm@vger.kernel.org +Description: + Reports an instant, single IBAT current reading for the battery. + This value is not averaged/smoothed. + + Access: Read + Valid values: Represented in microamps + +What: /sys/class/power_supply/<supply_name>/charge_type +Date: July 2009 +Contact: linux-pm@vger.kernel.org +Description: + Represents the type of charging currently being applied to the + battery. + + Access: Read + Valid values: "Unknown", "N/A", "Trickle", "Fast" + +What: /sys/class/power_supply/<supply_name>/charge_term_current +Date: July 2014 +Contact: linux-pm@vger.kernel.org +Description: + Reports the charging current value which is used to determine + when the battery is considered full and charging should end. + + Access: Read + Valid values: Represented in microamps + +What: /sys/class/power_supply/<supply_name>/health +Date: May 2007 +Contact: linux-pm@vger.kernel.org +Description: + Reports the health of the battery or battery side of charger + functionality. + + Access: Read + Valid values: "Unknown", "Good", "Overheat", "Dead", + "Over voltage", "Unspecified failure", "Cold", + "Watchdog timer expire", "Safety timer expire" + +What: /sys/class/power_supply/<supply_name>/precharge_current +Date: June 2017 +Contact: linux-pm@vger.kernel.org +Description: + Reports the charging current applied during pre-charging phase + for a battery charge cycle. + + Access: Read + Valid values: Represented in microamps + +What: /sys/class/power_supply/<supply_name>/present +Date: May 2007 +Contact: linux-pm@vger.kernel.org +Description: + Reports whether a battery is present or not in the system. + + Access: Read + Valid values: + 0: Absent + 1: Present + +What: /sys/class/power_supply/<supply_name>/status +Date: May 2007 +Contact: linux-pm@vger.kernel.org +Description: + Represents the charging status of the battery. Normally this + is read-only reporting although for some supplies this can be + used to enable/disable charging to the battery. + + Access: Read, Write + Valid values: "Unknown", "Charging", "Discharging", + "Not charging", "Full" + +What: /sys/class/power_supply/<supply_name>/technology +Date: May 2007 +Contact: linux-pm@vger.kernel.org +Description: + Describes the battery technology supported by the supply. + + Access: Read + Valid values: "Unknown", "NiMH", "Li-ion", "Li-poly", "LiFe", + "NiCd", "LiMn" + +What: /sys/class/power_supply/<supply_name>/temp +Date: May 2007 +Contact: linux-pm@vger.kernel.org +Description: + Reports the current TBAT battery temperature reading. + + Access: Read + Valid values: Represented in 1/10 Degrees Celsius + +What: /sys/class/power_supply/<supply_name>/temp_alert_max +Date: July 2012 +Contact: linux-pm@vger.kernel.org +Description: + Maximum TBAT temperature trip-wire value where the supply will + notify user-space of the event. This is normally used for the + battery charging scenario where user-space needs to know the + battery temperature has crossed an upper threshold so it can + take appropriate action (e.g. warning user that battery level is + critically high, and charging has stopped). + + Access: Read + Valid values: Represented in 1/10 Degrees Celsius + +What: /sys/class/power_supply/<supply_name>/temp_alert_min +Date: July 2012 +Contact: linux-pm@vger.kernel.org +Description: + Minimum TBAT temperature trip-wire value where the supply will + notify user-space of the event. This is normally used for the + battery charging scenario where user-space needs to know the + battery temperature has crossed a lower threshold so it can take + appropriate action (e.g. warning user that battery level is + high, and charging current has been reduced accordingly to + remedy the situation). + + Access: Read + Valid values: Represented in 1/10 Degrees Celsius + +What: /sys/class/power_supply/<supply_name>/temp_max +Date: July 2014 +Contact: linux-pm@vger.kernel.org +Description: + Reports the maximum allowed TBAT battery temperature for + charging. + + Access: Read + Valid values: Represented in 1/10 Degrees Celsius + +What: /sys/class/power_supply/<supply_name>/temp_min +Date: July 2014 +Contact: linux-pm@vger.kernel.org +Description: + Reports the minimum allowed TBAT battery temperature for + charging. + + Access: Read + Valid values: Represented in 1/10 Degrees Celsius + +What: /sys/class/power_supply/<supply_name>/voltage_avg, +Date: May 2007 +Contact: linux-pm@vger.kernel.org +Description: + Reports an average VBAT voltage reading for the battery, over a + fixed period. Normally devices will provide a fixed interval in + which they average readings to smooth out the reported value. + + Access: Read + Valid values: Represented in microvolts + +What: /sys/class/power_supply/<supply_name>/voltage_max, +Date: January 2008 +Contact: linux-pm@vger.kernel.org +Description: + Reports the maximum safe VBAT voltage permitted for the battery, + during charging. + + Access: Read + Valid values: Represented in microvolts + +What: /sys/class/power_supply/<supply_name>/voltage_min, +Date: January 2008 +Contact: linux-pm@vger.kernel.org +Description: + Reports the minimum safe VBAT voltage permitted for the battery, + during discharging. + + Access: Read + Valid values: Represented in microvolts + +What: /sys/class/power_supply/<supply_name>/voltage_now, +Date: May 2007 +Contact: linux-pm@vger.kernel.org +Description: + Reports an instant, single VBAT voltage reading for the battery. + This value is not averaged/smoothed. + + Access: Read + Valid values: Represented in microvolts + +===== USB Properties ===== + +What: /sys/class/power_supply/<supply_name>/current_avg +Date: May 2007 +Contact: linux-pm@vger.kernel.org +Description: + Reports an average IBUS current reading over a fixed period. + Normally devices will provide a fixed interval in which they + average readings to smooth out the reported value. + + Access: Read + Valid values: Represented in microamps + + +What: /sys/class/power_supply/<supply_name>/current_max +Date: October 2010 +Contact: linux-pm@vger.kernel.org +Description: + Reports the maximum IBUS current the supply can support. + + Access: Read + Valid values: Represented in microamps + +What: /sys/class/power_supply/<supply_name>/current_now +Date: May 2007 +Contact: linux-pm@vger.kernel.org +Description: + Reports the IBUS current supplied now. This value is generally + read-only reporting, unless the 'online' state of the supply + is set to be programmable, in which case this value can be set + within the reported min/max range. + + Access: Read, Write + Valid values: Represented in microamps + +What: /sys/class/power_supply/<supply_name>/input_current_limit +Date: July 2014 +Contact: linux-pm@vger.kernel.org +Description: + Details the incoming IBUS current limit currently set in the + supply. Normally this is configured based on the type of + connection made (e.g. A configured SDP should output a maximum + of 500mA so the input current limit is set to the same value). + + Access: Read, Write + Valid values: Represented in microamps + +What: /sys/class/power_supply/<supply_name>/online, +Date: May 2007 +Contact: linux-pm@vger.kernel.org +Description: + Indicates if VBUS is present for the supply. When the supply is + online, and the supply allows it, then it's possible to switch + between online states (e.g. Fixed -> Programmable for a PD_PPS + USB supply so voltage and current can be controlled). + + Access: Read, Write + Valid values: + 0: Offline + 1: Online Fixed - Fixed Voltage Supply + 2: Online Programmable - Programmable Voltage Supply + +What: /sys/class/power_supply/<supply_name>/temp +Date: May 2007 +Contact: linux-pm@vger.kernel.org +Description: + Reports the current supply temperature reading. This would + normally be the internal temperature of the device itself (e.g + TJUNC temperature of an IC) + + Access: Read + Valid values: Represented in 1/10 Degrees Celsius + +What: /sys/class/power_supply/<supply_name>/temp_alert_max +Date: July 2012 +Contact: linux-pm@vger.kernel.org +Description: + Maximum supply temperature trip-wire value where the supply will + notify user-space of the event. This is normally used for the + charging scenario where user-space needs to know the supply + temperature has crossed an upper threshold so it can take + appropriate action (e.g. warning user that the supply + temperature is critically high, and charging has stopped to + remedy the situation). + + Access: Read + Valid values: Represented in 1/10 Degrees Celsius + +What: /sys/class/power_supply/<supply_name>/temp_alert_min +Date: July 2012 +Contact: linux-pm@vger.kernel.org +Description: + Minimum supply temperature trip-wire value where the supply will + notify user-space of the event. This is normally used for the + charging scenario where user-space needs to know the supply + temperature has crossed a lower threshold so it can take + appropriate action (e.g. warning user that the supply + temperature is high, and charging current has been reduced + accordingly to remedy the situation). + + Access: Read + Valid values: Represented in 1/10 Degrees Celsius + +What: /sys/class/power_supply/<supply_name>/temp_max +Date: July 2014 +Contact: linux-pm@vger.kernel.org +Description: + Reports the maximum allowed supply temperature for operation. + + Access: Read + Valid values: Represented in 1/10 Degrees Celsius + +What: /sys/class/power_supply/<supply_name>/temp_min +Date: July 2014 +Contact: linux-pm@vger.kernel.org +Description: + Reports the mainimum allowed supply temperature for operation. + + Access: Read + Valid values: Represented in 1/10 Degrees Celsius + +What: /sys/class/power_supply/<supply_name>/usb_type +Date: March 2018 +Contact: linux-pm@vger.kernel.org +Description: + Reports what type of USB connection is currently active for + the supply, for example it can show if USB-PD capable source + is attached. + + Access: Read-Only + Valid values: "Unknown", "SDP", "DCP", "CDP", "ACA", "C", "PD", + "PD_DRP", "PD_PPS", "BrickID" + +What: /sys/class/power_supply/<supply_name>/voltage_max +Date: January 2008 +Contact: linux-pm@vger.kernel.org +Description: + Reports the maximum VBUS voltage the supply can support. + + Access: Read + Valid values: Represented in microvolts + +What: /sys/class/power_supply/<supply_name>/voltage_min +Date: January 2008 +Contact: linux-pm@vger.kernel.org +Description: + Reports the minimum VBUS voltage the supply can support. + + Access: Read + Valid values: Represented in microvolts + +What: /sys/class/power_supply/<supply_name>/voltage_now +Date: May 2007 +Contact: linux-pm@vger.kernel.org +Description: + Reports the VBUS voltage supplied now. This value is generally + read-only reporting, unless the 'online' state of the supply + is set to be programmable, in which case this value can be set + within the reported min/max range. + + Access: Read, Write + Valid values: Represented in microvolts + +===== Device Specific Properties ===== + What: /sys/class/power/ds2760-battery.*/charge_now Date: May 2010 KernelVersion: 2.6.35 diff --git a/Documentation/devicetree/bindings/usb/ci-hdrc-usb2.txt b/Documentation/devicetree/bindings/usb/ci-hdrc-usb2.txt index 0e03344e2e8b..2e9318151df7 100644 --- a/Documentation/devicetree/bindings/usb/ci-hdrc-usb2.txt +++ b/Documentation/devicetree/bindings/usb/ci-hdrc-usb2.txt @@ -76,6 +76,10 @@ Optional properties: needs to make sure it does not send more than 90% maximum_periodic_data_per_frame. The use case is multiple transactions, but less frame rate. +- mux-controls: The mux control for toggling host/device output of this + controller. It's expected that a mux state of 0 indicates device mode and a + mux state of 1 indicates host mode. +- mux-control-names: Shall be "usb_switch" if mux-controls is specified. i.mx specific properties - fsl,usbmisc: phandler of non-core register device, with one @@ -102,4 +106,6 @@ Example: rx-burst-size-dword = <0x10>; extcon = <0>, <&usb_id>; phy-clkgate-delay-us = <400>; + mux-controls = <&usb_switch>; + mux-control-names = "usb_switch"; }; diff --git a/Documentation/devicetree/bindings/usb/fcs,fusb302.txt b/Documentation/devicetree/bindings/usb/fcs,fusb302.txt index 472facfa5a71..6087dc7f209e 100644 --- a/Documentation/devicetree/bindings/usb/fcs,fusb302.txt +++ b/Documentation/devicetree/bindings/usb/fcs,fusb302.txt @@ -6,12 +6,6 @@ Required properties : - interrupts : Interrupt specifier Optional properties : -- fcs,max-sink-microvolt : Maximum voltage to negotiate when configured as sink -- fcs,max-sink-microamp : Maximum current to negotiate when configured as sink -- fcs,max-sink-microwatt : Maximum power to negotiate when configured as sink - If this is less then max-sink-microvolt * - max-sink-microamp then the configured current will - be clamped. - fcs,operating-sink-microwatt : Minimum amount of power accepted from a sink when negotiating diff --git a/Documentation/devicetree/bindings/usb/richtek,rt1711h.txt b/Documentation/devicetree/bindings/usb/richtek,rt1711h.txt new file mode 100644 index 000000000000..09e847e92e5e --- /dev/null +++ b/Documentation/devicetree/bindings/usb/richtek,rt1711h.txt @@ -0,0 +1,17 @@ +Richtek RT1711H TypeC PD Controller. + +Required properties: + - compatible : Must be "richtek,rt1711h". + - reg : Must be 0x4e, it's slave address of RT1711H. + - interrupt-parent : the phandle for the interrupt controller that + provides interrupts for this device. + - interrupts : <a b> where a is the interrupt number and b represents an + encoding of the sense and level information for the interrupt. + +Example : +rt1711h@4e { + compatible = "richtek,rt1711h"; + reg = <0x4e>; + interrupt-parent = <&gpio26>; + interrupts = <0 IRQ_TYPE_LEVEL_LOW>; +}; diff --git a/Documentation/devicetree/bindings/usb/usb-xhci.txt b/Documentation/devicetree/bindings/usb/usb-xhci.txt index c4c00dff4b56..bd1dd316fb23 100644 --- a/Documentation/devicetree/bindings/usb/usb-xhci.txt +++ b/Documentation/devicetree/bindings/usb/usb-xhci.txt @@ -28,7 +28,10 @@ Required properties: - interrupts: one XHCI interrupt should be described here. Optional properties: - - clocks: reference to a clock + - clocks: reference to the clocks + - clock-names: mandatory if there is a second clock, in this case + the name must be "core" for the first clock and "reg" for the + second one - usb2-lpm-disable: indicate if we don't want to enable USB2 HW LPM - usb3-lpm-capable: determines if platform is USB3 LPM capable - quirk-broken-port-ped: set if the controller has broken port disable mechanism diff --git a/MAINTAINERS b/MAINTAINERS index 92be777d060a..a93b8cfca489 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -11270,6 +11270,7 @@ M: Sebastian Reichel <sre@kernel.org> L: linux-pm@vger.kernel.org T: git git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply.git S: Maintained +F: Documentation/ABI/testing/sysfs-class-power F: Documentation/devicetree/bindings/power/supply/ F: include/linux/power_supply.h F: drivers/power/supply/ diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c index feac7b066e6c..ecd68c2053c5 100644 --- a/drivers/power/supply/power_supply_core.c +++ b/drivers/power/supply/power_supply_core.c @@ -843,12 +843,21 @@ __power_supply_register(struct device *parent, { struct device *dev; struct power_supply *psy; - int rc; + int i, rc; if (!parent) pr_warn("%s: Expected proper parent device for '%s'\n", __func__, desc->name); + if (!desc || !desc->name || !desc->properties || !desc->num_properties) + return ERR_PTR(-EINVAL); + + for (i = 0; i < desc->num_properties; ++i) { + if ((desc->properties[i] == POWER_SUPPLY_PROP_USB_TYPE) && + (!desc->usb_types || !desc->num_usb_types)) + return ERR_PTR(-EINVAL); + } + psy = kzalloc(sizeof(*psy), GFP_KERNEL); if (!psy) return ERR_PTR(-ENOMEM); diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c index ca2f869165ed..6170ed8b6854 100644 --- a/drivers/power/supply/power_supply_sysfs.c +++ b/drivers/power/supply/power_supply_sysfs.c @@ -46,6 +46,11 @@ static const char * const power_supply_type_text[] = { "USB_PD", "USB_PD_DRP", "BrickID" }; +static const char * const power_supply_usb_type_text[] = { + "Unknown", "SDP", "DCP", "CDP", "ACA", "C", + "PD", "PD_DRP", "PD_PPS", "BrickID" +}; + static const char * const power_supply_status_text[] = { "Unknown", "Charging", "Discharging", "Not charging", "Full" }; @@ -73,6 +78,41 @@ static const char * const power_supply_scope_text[] = { "Unknown", "System", "Device" }; +static ssize_t power_supply_show_usb_type(struct device *dev, + enum power_supply_usb_type *usb_types, + ssize_t num_usb_types, + union power_supply_propval *value, + char *buf) +{ + enum power_supply_usb_type usb_type; + ssize_t count = 0; + bool match = false; + int i; + + for (i = 0; i < num_usb_types; ++i) { + usb_type = usb_types[i]; + + if (value->intval == usb_type) { + count += sprintf(buf + count, "[%s] ", + power_supply_usb_type_text[usb_type]); + match = true; + } else { + count += sprintf(buf + count, "%s ", + power_supply_usb_type_text[usb_type]); + } + } + + if (!match) { + dev_warn(dev, "driver reporting unsupported connected type\n"); + return -EINVAL; + } + + if (count) + buf[count - 1] = '\n'; + + return count; +} + static ssize_t power_supply_show_property(struct device *dev, struct device_attribute *attr, char *buf) { @@ -122,6 +162,11 @@ static ssize_t power_supply_show_property(struct device *dev, ret = sprintf(buf, "%s\n", power_supply_type_text[value.intval]); break; + case POWER_SUPPLY_PROP_USB_TYPE: + ret = power_supply_show_usb_type(dev, psy->desc->usb_types, + psy->desc->num_usb_types, + &value, buf); + break; case POWER_SUPPLY_PROP_SCOPE: ret = sprintf(buf, "%s\n", power_supply_scope_text[value.intval]); @@ -252,6 +297,7 @@ static struct device_attribute power_supply_attrs[] = { POWER_SUPPLY_ATTR(time_to_full_now), POWER_SUPPLY_ATTR(time_to_full_avg), POWER_SUPPLY_ATTR(type), + POWER_SUPPLY_ATTR(usb_type), POWER_SUPPLY_ATTR(scope), POWER_SUPPLY_ATTR(precharge_current), POWER_SUPPLY_ATTR(charge_term_current), diff --git a/drivers/staging/typec/Kconfig b/drivers/staging/typec/Kconfig index 5359f556d203..3aa981fbc8f5 100644 --- a/drivers/staging/typec/Kconfig +++ b/drivers/staging/typec/Kconfig @@ -9,6 +9,14 @@ config TYPEC_TCPCI help Type-C Port Controller driver for TCPCI-compliant controller. +config TYPEC_RT1711H + tristate "Richtek RT1711H Type-C chip driver" + select TYPEC_TCPCI + help + Richtek RT1711H Type-C chip driver that works with + Type-C Port Controller Manager to provide USB PD and USB + Type-C functionalities. + endif endmenu diff --git a/drivers/staging/typec/Makefile b/drivers/staging/typec/Makefile index 53d649abcb53..7803d485e1b3 100644 --- a/drivers/staging/typec/Makefile +++ b/drivers/staging/typec/Makefile @@ -1 +1,2 @@ obj-$(CONFIG_TYPEC_TCPCI) += tcpci.o +obj-$(CONFIG_TYPEC_RT1711H) += tcpci_rt1711h.o diff --git a/drivers/staging/typec/tcpci.h b/drivers/staging/typec/tcpci.h index 34c865f0dcf6..303ebde26546 100644 --- a/drivers/staging/typec/tcpci.h +++ b/drivers/staging/typec/tcpci.h @@ -59,6 +59,7 @@ #define TCPC_POWER_CTRL_VCONN_ENABLE BIT(0) #define TCPC_CC_STATUS 0x1d +#define TCPC_CC_STATUS_TOGGLING BIT(5) #define TCPC_CC_STATUS_TERM BIT(4) #define TCPC_CC_STATUS_CC2_SHIFT 2 #define TCPC_CC_STATUS_CC2_MASK 0x3 diff --git a/drivers/staging/typec/tcpci_rt1711h.c b/drivers/staging/typec/tcpci_rt1711h.c new file mode 100644 index 000000000000..017389021b96 --- /dev/null +++ b/drivers/staging/typec/tcpci_rt1711h.c @@ -0,0 +1,312 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2018, Richtek Technology Corporation + * + * Richtek RT1711H Type-C Chip Driver + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/gpio/consumer.h> +#include <linux/usb/tcpm.h> +#include <linux/regmap.h> +#include "tcpci.h" + +#define RT1711H_VID 0x29CF +#define RT1711H_PID 0x1711 + +#define RT1711H_RTCTRL8 0x9B + +/* Autoidle timeout = (tout * 2 + 1) * 6.4ms */ +#define RT1711H_RTCTRL8_SET(ck300, ship_off, auto_idle, tout) \ + (((ck300) << 7) | ((ship_off) << 5) | \ + ((auto_idle) << 3) | ((tout) & 0x07)) + +#define RT1711H_RTCTRL11 0x9E + +/* I2C timeout = (tout + 1) * 12.5ms */ +#define RT1711H_RTCTRL11_SET(en, tout) \ + (((en) << 7) | ((tout) & 0x0F)) + +#define RT1711H_RTCTRL13 0xA0 +#define RT1711H_RTCTRL14 0xA1 +#define RT1711H_RTCTRL15 0xA2 +#define RT1711H_RTCTRL16 0xA3 + +struct rt1711h_chip { + struct tcpci_data data; + struct tcpci *tcpci; + struct device *dev; +}; + +static int rt1711h_read16(struct rt1711h_chip *chip, unsigned int reg, u16 *val) +{ + return regmap_raw_read(chip->data.regmap, reg, val, sizeof(u16)); +} + +static int rt1711h_write16(struct rt1711h_chip *chip, unsigned int reg, u16 val) +{ + return regmap_raw_write(chip->data.regmap, reg, &val, sizeof(u16)); +} + +static int rt1711h_read8(struct rt1711h_chip *chip, unsigned int reg, u8 *val) +{ + return regmap_raw_read(chip->data.regmap, reg, val, sizeof(u8)); +} + +static int rt1711h_write8(struct rt1711h_chip *chip, unsigned int reg, u8 val) +{ + return regmap_raw_write(chip->data.regmap, reg, &val, sizeof(u8)); +} + +static const struct regmap_config rt1711h_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = 0xFF, /* 0x80 .. 0xFF are vendor defined */ +}; + +static struct rt1711h_chip *tdata_to_rt1711h(struct tcpci_data *tdata) +{ + return container_of(tdata, struct rt1711h_chip, data); +} + +static int rt1711h_init(struct tcpci *tcpci, struct tcpci_data *tdata) +{ + int ret; + struct rt1711h_chip *chip = tdata_to_rt1711h(tdata); + + /* CK 300K from 320K, shipping off, auto_idle enable, tout = 32ms */ + ret = rt1711h_write8(chip, RT1711H_RTCTRL8, + RT1711H_RTCTRL8_SET(0, 1, 1, 2)); + if (ret < 0) + return ret; + + /* I2C reset : (val + 1) * 12.5ms */ + ret = rt1711h_write8(chip, RT1711H_RTCTRL11, + RT1711H_RTCTRL11_SET(1, 0x0F)); + if (ret < 0) + return ret; + + /* tTCPCfilter : (26.7 * val) us */ + ret = rt1711h_write8(chip, RT1711H_RTCTRL14, 0x0F); + if (ret < 0) + return ret; + + /* tDRP : (51.2 + 6.4 * val) ms */ + ret = rt1711h_write8(chip, RT1711H_RTCTRL15, 0x04); + if (ret < 0) + return ret; + + /* dcSRC.DRP : 33% */ + return rt1711h_write16(chip, RT1711H_RTCTRL16, 330); +} + +static int rt1711h_set_vconn(struct tcpci *tcpci, struct tcpci_data *tdata, + bool enable) +{ + struct rt1711h_chip *chip = tdata_to_rt1711h(tdata); + + return rt1711h_write8(chip, RT1711H_RTCTRL8, + RT1711H_RTCTRL8_SET(0, 1, !enable, 2)); +} + +static int rt1711h_start_drp_toggling(struct tcpci *tcpci, + struct tcpci_data *tdata, + enum typec_cc_status cc) +{ + struct rt1711h_chip *chip = tdata_to_rt1711h(tdata); + int ret; + unsigned int reg = 0; + + switch (cc) { + default: + case TYPEC_CC_RP_DEF: + reg |= (TCPC_ROLE_CTRL_RP_VAL_DEF << + TCPC_ROLE_CTRL_RP_VAL_SHIFT); + break; + case TYPEC_CC_RP_1_5: + reg |= (TCPC_ROLE_CTRL_RP_VAL_1_5 << + TCPC_ROLE_CTRL_RP_VAL_SHIFT); + break; + case TYPEC_CC_RP_3_0: + reg |= (TCPC_ROLE_CTRL_RP_VAL_3_0 << + TCPC_ROLE_CTRL_RP_VAL_SHIFT); + break; + } + + if (cc == TYPEC_CC_RD) + reg |= (TCPC_ROLE_CTRL_CC_RD << TCPC_ROLE_CTRL_CC1_SHIFT) | + (TCPC_ROLE_CTRL_CC_RD << TCPC_ROLE_CTRL_CC2_SHIFT); + else + reg |= (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC1_SHIFT) | + (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC2_SHIFT); + + ret = rt1711h_write8(chip, TCPC_ROLE_CTRL, reg); + if (ret < 0) + return ret; + usleep_range(500, 1000); + + return 0; +} + +static irqreturn_t rt1711h_irq(int irq, void *dev_id) +{ + int ret; + u16 alert; + u8 status; + struct rt1711h_chip *chip = dev_id; + + if (!chip->tcpci) + return IRQ_HANDLED; + + ret = rt1711h_read16(chip, TCPC_ALERT, &alert); + if (ret < 0) + goto out; + + if (alert & TCPC_ALERT_CC_STATUS) { + ret = rt1711h_read8(chip, TCPC_CC_STATUS, &status); + if (ret < 0) + goto out; + /* Clear cc change event triggered by starting toggling */ + if (status & TCPC_CC_STATUS_TOGGLING) + rt1711h_write8(chip, TCPC_ALERT, TCPC_ALERT_CC_STATUS); + } + +out: + return tcpci_irq(chip->tcpci); +} + +static int rt1711h_init_alert(struct rt1711h_chip *chip, + struct i2c_client *client) +{ + int ret; + + /* Disable chip interrupts before requesting irq */ + ret = rt1711h_write16(chip, TCPC_ALERT_MASK, 0); + if (ret < 0) + return ret; + + ret = devm_request_threaded_irq(chip->dev, client->irq, NULL, + rt1711h_irq, + IRQF_ONESHOT | IRQF_TRIGGER_LOW, + dev_name(chip->dev), chip); + if (ret < 0) + return ret; + enable_irq_wake(client->irq); + return 0; +} + +static int rt1711h_sw_reset(struct rt1711h_chip *chip) +{ + int ret; + + ret = rt1711h_write8(chip, RT1711H_RTCTRL13, 0x01); + if (ret < 0) + return ret; + + usleep_range(1000, 2000); + return 0; +} + +static int rt1711h_check_revision(struct i2c_client *i2c) +{ + int ret; + + ret = i2c_smbus_read_word_data(i2c, TCPC_VENDOR_ID); + if (ret < 0) + return ret; + if (ret != RT1711H_VID) { + dev_err(&i2c->dev, "vid is not correct, 0x%04x\n", ret); + return -ENODEV; + } + ret = i2c_smbus_read_word_data(i2c, TCPC_PRODUCT_ID); + if (ret < 0) + return ret; + if (ret != RT1711H_PID) { + dev_err(&i2c->dev, "pid is not correct, 0x%04x\n", ret); + return -ENODEV; + } + return 0; +} + +static int rt1711h_probe(struct i2c_client *client, + const struct i2c_device_id *i2c_id) +{ + int ret; + struct rt1711h_chip *chip; + + ret = rt1711h_check_revision(client); + if (ret < 0) { + dev_err(&client->dev, "check vid/pid fail\n"); + return ret; + } + + chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->data.regmap = devm_regmap_init_i2c(client, + &rt1711h_regmap_config); + if (IS_ERR(chip->data.regmap)) + return PTR_ERR(chip->data.regmap); + + chip->dev = &client->dev; + i2c_set_clientdata(client, chip); + + ret = rt1711h_sw_reset(chip); + if (ret < 0) + return ret; + + ret = rt1711h_init_alert(chip, client); + if (ret < 0) + return ret; + + chip->data.init = rt1711h_init; + chip->data.set_vconn = rt1711h_set_vconn; + chip->data.start_drp_toggling = rt1711h_start_drp_toggling; + chip->tcpci = tcpci_register_port(chip->dev, &chip->data); + if (IS_ERR_OR_NULL(chip->tcpci)) + return PTR_ERR(chip->tcpci); + + return 0; +} + +static int rt1711h_remove(struct i2c_client *client) +{ + struct rt1711h_chip *chip = i2c_get_clientdata(client); + + tcpci_unregister_port(chip->tcpci); + return 0; +} + +static const struct i2c_device_id rt1711h_id[] = { + { "rt1711h", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, rt1711h_id); + +#ifdef CONFIG_OF +static const struct of_device_id rt1711h_of_match[] = { + { .compatible = "richtek,rt1711h", }, + {}, +}; +MODULE_DEVICE_TABLE(of, rt1711h_of_match); +#endif + +static struct i2c_driver rt1711h_i2c_driver = { + .driver = { + .name = "rt1711h", + .of_match_table = of_match_ptr(rt1711h_of_match), + }, + .probe = rt1711h_probe, + .remove = rt1711h_remove, + .id_table = rt1711h_id, +}; +module_i2c_driver(rt1711h_i2c_driver); + +MODULE_AUTHOR("ShuFan Lee <shufan_lee@richtek.com>"); +MODULE_DESCRIPTION("RT1711H USB Type-C Port Controller Interface Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index 777036ae6367..f65c1f287ab3 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -33,7 +33,6 @@ #include <linux/phy/phy.h> #include <linux/usb.h> #include <linux/usb/hcd.h> -#include <linux/usb/phy.h> #include <linux/usb/otg.h> #include "usb.h" @@ -568,6 +567,7 @@ static int rh_call_control (struct usb_hcd *hcd, struct urb *urb) switch (wValue & 0xff00) { case USB_DT_DEVICE << 8: switch (hcd->speed) { + case HCD_USB32: case HCD_USB31: bufp = usb31_rh_dev_descriptor; break; @@ -592,6 +592,7 @@ static int rh_call_control (struct usb_hcd *hcd, struct urb *urb) break; case USB_DT_CONFIG << 8: switch (hcd->speed) { + case HCD_USB32: case HCD_USB31: case HCD_USB3: bufp = ss_rh_config_descriptor; @@ -2739,30 +2740,10 @@ int usb_add_hcd(struct usb_hcd *hcd, int retval; struct usb_device *rhdev; - if (IS_ENABLED(CONFIG_USB_PHY) && !hcd->skip_phy_initialization) { - struct usb_phy *phy = usb_get_phy_dev(hcd->self.sysdev, 0); - - if (IS_ERR(phy)) { - retval = PTR_ERR(phy); - if (retval == -EPROBE_DEFER) - return retval; - } else { - retval = usb_phy_init(phy); - if (retval) { - usb_put_phy(phy); - return retval; - } - hcd->usb_phy = phy; - hcd->remove_phy = 1; - } - } - if (!hcd->skip_phy_initialization && usb_hcd_is_primary_hcd(hcd)) { hcd->phy_roothub = usb_phy_roothub_init(hcd->self.sysdev); - if (IS_ERR(hcd->phy_roothub)) { - retval = PTR_ERR(hcd->phy_roothub); - goto err_phy_roothub_init; - } + if (IS_ERR(hcd->phy_roothub)) + return PTR_ERR(hcd->phy_roothub); retval = usb_phy_roothub_power_on(hcd->phy_roothub); if (retval) @@ -2812,6 +2793,9 @@ int usb_add_hcd(struct usb_hcd *hcd, hcd->self.root_hub = rhdev; mutex_unlock(&usb_port_peer_mutex); + rhdev->rx_lanes = 1; + rhdev->tx_lanes = 1; + switch (hcd->speed) { case HCD_USB11: rhdev->speed = USB_SPEED_FULL; @@ -2825,6 +2809,10 @@ int usb_add_hcd(struct usb_hcd *hcd, case HCD_USB3: rhdev->speed = USB_SPEED_SUPER; break; + case HCD_USB32: + rhdev->rx_lanes = 2; + rhdev->tx_lanes = 2; + /* fall through */ case HCD_USB31: rhdev->speed = USB_SPEED_SUPER_PLUS; break; @@ -2936,12 +2924,7 @@ err_create_buf: usb_phy_roothub_power_off(hcd->phy_roothub); err_usb_phy_roothub_power_on: usb_phy_roothub_exit(hcd->phy_roothub); -err_phy_roothub_init: - if (hcd->remove_phy && hcd->usb_phy) { - usb_phy_shutdown(hcd->usb_phy); - usb_put_phy(hcd->usb_phy); - hcd->usb_phy = NULL; - } + return retval; } EXPORT_SYMBOL_GPL(usb_add_hcd); @@ -3017,12 +3000,6 @@ void usb_remove_hcd(struct usb_hcd *hcd) usb_phy_roothub_power_off(hcd->phy_roothub); usb_phy_roothub_exit(hcd->phy_roothub); - if (hcd->remove_phy && hcd->usb_phy) { - usb_phy_shutdown(hcd->usb_phy); - usb_put_phy(hcd->usb_phy); - hcd->usb_phy = NULL; - } - usb_put_invalidate_rhdev(hcd); hcd->flags = 0; } diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index f6ea16e9f6bb..a86591772352 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -2746,6 +2746,14 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1, if (!udev) return 0; + if (hub_is_superspeedplus(hub->hdev)) { + /* extended portstatus Rx and Tx lane count are zero based */ + udev->rx_lanes = USB_EXT_PORT_RX_LANES(ext_portstatus) + 1; + udev->tx_lanes = USB_EXT_PORT_TX_LANES(ext_portstatus) + 1; + } else { + udev->rx_lanes = 1; + udev->tx_lanes = 1; + } if (hub_is_wusb(hub)) udev->speed = USB_SPEED_WIRELESS; else if (hub_is_superspeedplus(hub->hdev) && @@ -3371,6 +3379,10 @@ static int wait_for_connected(struct usb_device *udev, while (delay_ms < 2000) { if (status || *portstatus & USB_PORT_STAT_CONNECTION) break; + if (!port_is_power_on(hub, *portstatus)) { + status = -ENODEV; + break; + } msleep(20); delay_ms += 20; status = hub_port_status(hub, *port1, portstatus, portchange); @@ -4543,7 +4555,9 @@ hub_port_init(struct usb_hub *hub, struct usb_device *udev, int port1, * reset. But only on the first attempt, * lest we get into a time out/reset loop */ - if (r == 0 || (r == -ETIMEDOUT && retries == 0)) + if (r == 0 || (r == -ETIMEDOUT && + retries == 0 && + udev->speed > USB_SPEED_FULL)) break; } udev->descriptor.bMaxPacketSize0 = @@ -4590,9 +4604,12 @@ hub_port_init(struct usb_hub *hub, struct usb_device *udev, int port1, if (udev->speed >= USB_SPEED_SUPER) { devnum = udev->devnum; dev_info(&udev->dev, - "%s SuperSpeed%s USB device number %d using %s\n", + "%s SuperSpeed%s%s USB device number %d using %s\n", (udev->config) ? "reset" : "new", - (udev->speed == USB_SPEED_SUPER_PLUS) ? "Plus" : "", + (udev->speed == USB_SPEED_SUPER_PLUS) ? + "Plus Gen 2" : " Gen 1", + (udev->rx_lanes == 2 && udev->tx_lanes == 2) ? + "x2" : "", devnum, driver_name); } diff --git a/drivers/usb/core/sysfs.c b/drivers/usb/core/sysfs.c index 27bb34043053..ea18284dfa9a 100644 --- a/drivers/usb/core/sysfs.c +++ b/drivers/usb/core/sysfs.c @@ -175,6 +175,26 @@ static ssize_t speed_show(struct device *dev, struct device_attribute *attr, } static DEVICE_ATTR_RO(speed); +static ssize_t rx_lanes_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_device *udev; + + udev = to_usb_device(dev); + return sprintf(buf, "%d\n", udev->rx_lanes); +} +static DEVICE_ATTR_RO(rx_lanes); + +static ssize_t tx_lanes_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_device *udev; + + udev = to_usb_device(dev); + return sprintf(buf, "%d\n", udev->tx_lanes); +} +static DEVICE_ATTR_RO(tx_lanes); + static ssize_t busnum_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -790,6 +810,8 @@ static struct attribute *dev_attrs[] = { &dev_attr_bNumConfigurations.attr, &dev_attr_bMaxPacketSize0.attr, &dev_attr_speed.attr, + &dev_attr_rx_lanes.attr, + &dev_attr_tx_lanes.attr, &dev_attr_busnum.attr, &dev_attr_devnum.attr, &dev_attr_devpath.attr, diff --git a/drivers/usb/gadget/udc/Kconfig b/drivers/usb/gadget/udc/Kconfig index 0875d38476ee..9c3b4f86965e 100644 --- a/drivers/usb/gadget/udc/Kconfig +++ b/drivers/usb/gadget/udc/Kconfig @@ -179,7 +179,7 @@ config USB_R8A66597 config USB_RENESAS_USBHS_UDC tristate 'Renesas USBHS controller' - depends on USB_RENESAS_USBHS && HAS_DMA + depends on USB_RENESAS_USBHS help Renesas USBHS is a discrete USB host and peripheral controller chip that supports both full and high speed USB 2.0 data transfers. @@ -192,7 +192,7 @@ config USB_RENESAS_USBHS_UDC config USB_RENESAS_USB3 tristate 'Renesas USB3.0 Peripheral controller' depends on ARCH_RENESAS || COMPILE_TEST - depends on EXTCON && HAS_DMA + depends on EXTCON help Renesas USB3.0 Peripheral controller is a USB peripheral controller that supports super, high, and full speed USB 3.0 data transfers. diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index 5d958da8e1bc..9f0aeb068acb 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -234,9 +234,7 @@ config USB_EHCI_TEGRA tristate "NVIDIA Tegra HCD support" depends on ARCH_TEGRA select USB_EHCI_ROOT_HUB_TT - select USB_PHY - select USB_ULPI - select USB_ULPI_VIEWPORT + select USB_TEGRA_PHY help This driver enables support for the internal USB Host Controllers found in NVIDIA Tegra SoCs. The controllers are EHCI compliant. diff --git a/drivers/usb/host/ehci-omap.c b/drivers/usb/host/ehci-omap.c index 8d8bafc70c1f..7e4c13346a1e 100644 --- a/drivers/usb/host/ehci-omap.c +++ b/drivers/usb/host/ehci-omap.c @@ -157,10 +157,7 @@ static int ehci_hcd_omap_probe(struct platform_device *pdev) struct usb_phy *phy; /* get the PHY device */ - if (dev->of_node) - phy = devm_usb_get_phy_by_phandle(dev, "phys", i); - else - phy = devm_usb_get_phy_dev(dev, i); + phy = devm_usb_get_phy_by_phandle(dev, "phys", i); if (IS_ERR(phy)) { /* Don't bail out if PHY is not absolutely necessary */ if (pdata->port_mode[i] != OMAP_EHCI_PORT_MODE_PHY) diff --git a/drivers/usb/host/ehci-tegra.c b/drivers/usb/host/ehci-tegra.c index a6f4389f7e88..4d2cdec4cb78 100644 --- a/drivers/usb/host/ehci-tegra.c +++ b/drivers/usb/host/ehci-tegra.c @@ -36,7 +36,6 @@ #define DRV_NAME "tegra-ehci" static struct hc_driver __read_mostly tegra_ehci_hc_driver; -static bool usb1_reset_attempted; struct tegra_ehci_soc_config { bool has_hostpc; @@ -51,67 +50,54 @@ struct tegra_ehci_hcd { enum tegra_usb_phy_port_speed port_speed; }; -/* - * The 1st USB controller contains some UTMI pad registers that are global for - * all the controllers on the chip. Those registers are also cleared when - * reset is asserted to the 1st controller. This means that the 1st controller - * can only be reset when no other controlled has finished probing. So we'll - * reset the 1st controller before doing any other setup on any of the - * controllers, and then never again. - * - * Since this is a PHY issue, the Tegra PHY driver should probably be doing - * the resetting of the USB controllers. But to keep compatibility with old - * device trees that don't have reset phandles in the PHYs, do it here. - * Those old DTs will be vulnerable to total USB breakage if the 1st EHCI - * device isn't the first one to finish probing, so warn them. - */ static int tegra_reset_usb_controller(struct platform_device *pdev) { struct device_node *phy_np; struct usb_hcd *hcd = platform_get_drvdata(pdev); struct tegra_ehci_hcd *tegra = (struct tegra_ehci_hcd *)hcd_to_ehci(hcd)->priv; - bool has_utmi_pad_registers = false; + struct reset_control *rst; + int err; phy_np = of_parse_phandle(pdev->dev.of_node, "nvidia,phy", 0); if (!phy_np) return -ENOENT; - if (of_property_read_bool(phy_np, "nvidia,has-utmi-pad-registers")) - has_utmi_pad_registers = true; + /* + * The 1st USB controller contains some UTMI pad registers that are + * global for all the controllers on the chip. Those registers are + * also cleared when reset is asserted to the 1st controller. + */ + rst = of_reset_control_get_shared(phy_np, "utmi-pads"); + if (IS_ERR(rst)) { + dev_warn(&pdev->dev, + "can't get utmi-pads reset from the PHY\n"); + dev_warn(&pdev->dev, + "continuing, but please update your DT\n"); + } else { + /* + * PHY driver performs UTMI-pads reset in a case of + * non-legacy DT. + */ + reset_control_put(rst); + } - if (!usb1_reset_attempted) { - struct reset_control *usb1_reset; + of_node_put(phy_np); - if (!has_utmi_pad_registers) - usb1_reset = of_reset_control_get(phy_np, "utmi-pads"); - else - usb1_reset = tegra->rst; - - if (IS_ERR(usb1_reset)) { - dev_warn(&pdev->dev, - "can't get utmi-pads reset from the PHY\n"); - dev_warn(&pdev->dev, - "continuing, but please update your DT\n"); - } else { - reset_control_assert(usb1_reset); - udelay(1); - reset_control_deassert(usb1_reset); - - if (!has_utmi_pad_registers) - reset_control_put(usb1_reset); - } + /* reset control is shared, hence initialize it first */ + err = reset_control_deassert(tegra->rst); + if (err) + return err; - usb1_reset_attempted = true; - } + err = reset_control_assert(tegra->rst); + if (err) + return err; - if (!has_utmi_pad_registers) { - reset_control_assert(tegra->rst); - udelay(1); - reset_control_deassert(tegra->rst); - } + udelay(1); - of_node_put(phy_np); + err = reset_control_deassert(tegra->rst); + if (err) + return err; return 0; } @@ -440,7 +426,7 @@ static int tegra_ehci_probe(struct platform_device *pdev) goto cleanup_hcd_create; } - tegra->rst = devm_reset_control_get(&pdev->dev, "usb"); + tegra->rst = devm_reset_control_get_shared(&pdev->dev, "usb"); if (IS_ERR(tegra->rst)) { dev_err(&pdev->dev, "Can't get ehci reset\n"); err = PTR_ERR(tegra->rst); @@ -452,8 +438,10 @@ static int tegra_ehci_probe(struct platform_device *pdev) goto cleanup_hcd_create; err = tegra_reset_usb_controller(pdev); - if (err) + if (err) { + dev_err(&pdev->dev, "Failed to reset controller\n"); goto cleanup_clk_en; + } u_phy = devm_usb_get_phy_by_phandle(&pdev->dev, "nvidia,phy", 0); if (IS_ERR(u_phy)) { @@ -538,6 +526,9 @@ static int tegra_ehci_remove(struct platform_device *pdev) usb_phy_shutdown(hcd->usb_phy); usb_remove_hcd(hcd); + reset_control_assert(tegra->rst); + udelay(1); + clk_disable_unprepare(tegra->clk); usb_put_hcd(hcd); diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c index df327dcc2bac..596e7a71b666 100644 --- a/drivers/usb/host/xhci-plat.c +++ b/drivers/usb/host/xhci-plat.c @@ -157,6 +157,7 @@ static int xhci_plat_probe(struct platform_device *pdev) struct resource *res; struct usb_hcd *hcd; struct clk *clk; + struct clk *reg_clk; int ret; int irq; @@ -226,17 +227,27 @@ static int xhci_plat_probe(struct platform_device *pdev) hcd->rsrc_len = resource_size(res); /* - * Not all platforms have a clk so it is not an error if the - * clock does not exists. + * Not all platforms have clks so it is not an error if the + * clock do not exist. */ + reg_clk = devm_clk_get(&pdev->dev, "reg"); + if (!IS_ERR(reg_clk)) { + ret = clk_prepare_enable(reg_clk); + if (ret) + goto put_hcd; + } else if (PTR_ERR(reg_clk) == -EPROBE_DEFER) { + ret = -EPROBE_DEFER; + goto put_hcd; + } + clk = devm_clk_get(&pdev->dev, NULL); if (!IS_ERR(clk)) { ret = clk_prepare_enable(clk); if (ret) - goto put_hcd; + goto disable_reg_clk; } else if (PTR_ERR(clk) == -EPROBE_DEFER) { ret = -EPROBE_DEFER; - goto put_hcd; + goto disable_reg_clk; } xhci = hcd_to_xhci(hcd); @@ -252,6 +263,7 @@ static int xhci_plat_probe(struct platform_device *pdev) device_wakeup_enable(hcd->self.controller); xhci->clk = clk; + xhci->reg_clk = reg_clk; xhci->main_hcd = hcd; xhci->shared_hcd = __usb_create_hcd(driver, sysdev, &pdev->dev, dev_name(&pdev->dev), hcd); @@ -320,8 +332,10 @@ put_usb3_hcd: usb_put_hcd(xhci->shared_hcd); disable_clk: - if (!IS_ERR(clk)) - clk_disable_unprepare(clk); + clk_disable_unprepare(clk); + +disable_reg_clk: + clk_disable_unprepare(reg_clk); put_hcd: usb_put_hcd(hcd); @@ -338,6 +352,7 @@ static int xhci_plat_remove(struct platform_device *dev) struct usb_hcd *hcd = platform_get_drvdata(dev); struct xhci_hcd *xhci = hcd_to_xhci(hcd); struct clk *clk = xhci->clk; + struct clk *reg_clk = xhci->reg_clk; xhci->xhc_state |= XHCI_STATE_REMOVING; @@ -347,8 +362,8 @@ static int xhci_plat_remove(struct platform_device *dev) usb_remove_hcd(hcd); usb_put_hcd(xhci->shared_hcd); - if (!IS_ERR(clk)) - clk_disable_unprepare(clk); + clk_disable_unprepare(clk); + clk_disable_unprepare(reg_clk); usb_put_hcd(hcd); pm_runtime_set_suspended(&dev->dev); diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 05c909b04f14..6dfc4867dbcf 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1729,8 +1729,9 @@ struct xhci_hcd { int page_shift; /* msi-x vectors */ int msix_count; - /* optional clock */ + /* optional clocks */ struct clk *clk; + struct clk *reg_clk; /* data structures */ struct xhci_device_context_array *dcbaa; struct xhci_ring *cmd_ring; diff --git a/drivers/usb/isp1760/isp1760-core.c b/drivers/usb/isp1760/isp1760-core.c index 05d22589b5cc..55b94fd10331 100644 --- a/drivers/usb/isp1760/isp1760-core.c +++ b/drivers/usb/isp1760/isp1760-core.c @@ -31,7 +31,7 @@ static void isp1760_init_core(struct isp1760_device *isp) /* Low-level chip reset */ if (isp->rst_gpio) { gpiod_set_value_cansleep(isp->rst_gpio, 1); - mdelay(50); + msleep(50); gpiod_set_value_cansleep(isp->rst_gpio, 0); } diff --git a/drivers/usb/isp1760/isp1760-hcd.c b/drivers/usb/isp1760/isp1760-hcd.c index 42672d6ec525..1045521be293 100644 --- a/drivers/usb/isp1760/isp1760-hcd.c +++ b/drivers/usb/isp1760/isp1760-hcd.c @@ -2093,7 +2093,7 @@ static void isp1760_stop(struct usb_hcd *hcd) isp1760_hub_control(hcd, ClearPortFeature, USB_PORT_FEAT_POWER, 1, NULL, 0); - mdelay(20); + msleep(20); spin_lock_irq(&priv->lock); ehci_reset(hcd); diff --git a/drivers/usb/mon/mon_bin.c b/drivers/usb/mon/mon_bin.c index 2761fad66b95..34e866ad4a81 100644 --- a/drivers/usb/mon/mon_bin.c +++ b/drivers/usb/mon/mon_bin.c @@ -1227,7 +1227,7 @@ static void mon_bin_vma_close(struct vm_area_struct *vma) /* * Map ring pages to user space. */ -static int mon_bin_vma_fault(struct vm_fault *vmf) +static vm_fault_t mon_bin_vma_fault(struct vm_fault *vmf) { struct mon_reader_bin *rp = vmf->vma->vm_private_data; unsigned long offset, chunk_idx; diff --git a/drivers/usb/mtu3/Kconfig b/drivers/usb/mtu3/Kconfig index 25cd61947bee..c0c0eb88e5ea 100644 --- a/drivers/usb/mtu3/Kconfig +++ b/drivers/usb/mtu3/Kconfig @@ -2,7 +2,7 @@ config USB_MTU3 tristate "MediaTek USB3 Dual Role controller" - depends on EXTCON && (USB || USB_GADGET) && HAS_DMA + depends on EXTCON && (USB || USB_GADGET) depends on ARCH_MEDIATEK || COMPILE_TEST select USB_XHCI_MTK if USB_SUPPORT && USB_XHCI_HCD help diff --git a/drivers/usb/mtu3/mtu3_plat.c b/drivers/usb/mtu3/mtu3_plat.c index 628d5ce356ca..46551f6d16fd 100644 --- a/drivers/usb/mtu3/mtu3_plat.c +++ b/drivers/usb/mtu3/mtu3_plat.c @@ -447,8 +447,7 @@ static int mtu3_remove(struct platform_device *pdev) */ static int __maybe_unused mtu3_suspend(struct device *dev) { - struct platform_device *pdev = to_platform_device(dev); - struct ssusb_mtk *ssusb = platform_get_drvdata(pdev); + struct ssusb_mtk *ssusb = dev_get_drvdata(dev); dev_dbg(dev, "%s\n", __func__); @@ -466,8 +465,7 @@ static int __maybe_unused mtu3_suspend(struct device *dev) static int __maybe_unused mtu3_resume(struct device *dev) { - struct platform_device *pdev = to_platform_device(dev); - struct ssusb_mtk *ssusb = platform_get_drvdata(pdev); + struct ssusb_mtk *ssusb = dev_get_drvdata(dev); int ret; dev_dbg(dev, "%s\n", __func__); diff --git a/drivers/usb/musb/omap2430.c b/drivers/usb/musb/omap2430.c index 5d705930ef47..3dd6e1c5e04f 100644 --- a/drivers/usb/musb/omap2430.c +++ b/drivers/usb/musb/omap2430.c @@ -239,21 +239,15 @@ static int omap2430_musb_init(struct musb *musb) * up through ULPI. TWL4030-family PMICs include one, * which needs a driver, drivers aren't always needed. */ - if (dev->parent->of_node) { - musb->phy = devm_phy_get(dev->parent, "usb2-phy"); - - /* We can't totally remove musb->xceiv as of now because - * musb core uses xceiv.state and xceiv.otg. Once we have - * a separate state machine to handle otg, these can be moved - * out of xceiv and then we can start using the generic PHY - * framework - */ - musb->xceiv = devm_usb_get_phy_by_phandle(dev->parent, - "usb-phy", 0); - } else { - musb->xceiv = devm_usb_get_phy_dev(dev, 0); - musb->phy = devm_phy_get(dev, "usb"); - } + musb->phy = devm_phy_get(dev->parent, "usb2-phy"); + + /* We can't totally remove musb->xceiv as of now because + * musb core uses xceiv.state and xceiv.otg. Once we have + * a separate state machine to handle otg, these can be moved + * out of xceiv and then we can start using the generic PHY + * framework + */ + musb->xceiv = devm_usb_get_phy_by_phandle(dev->parent, "usb-phy", 0); if (IS_ERR(musb->xceiv)) { status = PTR_ERR(musb->xceiv); @@ -391,8 +385,13 @@ static int omap2430_probe(struct platform_device *pdev) struct omap2430_glue *glue; struct device_node *np = pdev->dev.of_node; struct musb_hdrc_config *config; + struct device_node *control_node; + struct platform_device *control_pdev; int ret = -ENOMEM, val; + if (!np) + return -ENODEV; + glue = devm_kzalloc(&pdev->dev, sizeof(*glue), GFP_KERNEL); if (!glue) goto err0; @@ -412,47 +411,43 @@ static int omap2430_probe(struct platform_device *pdev) glue->status = MUSB_UNKNOWN; glue->control_otghs = ERR_PTR(-ENODEV); - if (np) { - struct device_node *control_node; - struct platform_device *control_pdev; + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + goto err2; - pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); - if (!pdata) - goto err2; + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + goto err2; - data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); - if (!data) - goto err2; + config = devm_kzalloc(&pdev->dev, sizeof(*config), GFP_KERNEL); + if (!config) + goto err2; - config = devm_kzalloc(&pdev->dev, sizeof(*config), GFP_KERNEL); - if (!config) + of_property_read_u32(np, "mode", (u32 *)&pdata->mode); + of_property_read_u32(np, "interface-type", + (u32 *)&data->interface_type); + of_property_read_u32(np, "num-eps", (u32 *)&config->num_eps); + of_property_read_u32(np, "ram-bits", (u32 *)&config->ram_bits); + of_property_read_u32(np, "power", (u32 *)&pdata->power); + + ret = of_property_read_u32(np, "multipoint", &val); + if (!ret && val) + config->multipoint = true; + + pdata->board_data = data; + pdata->config = config; + + control_node = of_parse_phandle(np, "ctrl-module", 0); + if (control_node) { + control_pdev = of_find_device_by_node(control_node); + if (!control_pdev) { + dev_err(&pdev->dev, "Failed to get control device\n"); + ret = -EINVAL; goto err2; - - of_property_read_u32(np, "mode", (u32 *)&pdata->mode); - of_property_read_u32(np, "interface-type", - (u32 *)&data->interface_type); - of_property_read_u32(np, "num-eps", (u32 *)&config->num_eps); - of_property_read_u32(np, "ram-bits", (u32 *)&config->ram_bits); - of_property_read_u32(np, "power", (u32 *)&pdata->power); - - ret = of_property_read_u32(np, "multipoint", &val); - if (!ret && val) - config->multipoint = true; - - pdata->board_data = data; - pdata->config = config; - - control_node = of_parse_phandle(np, "ctrl-module", 0); - if (control_node) { - control_pdev = of_find_device_by_node(control_node); - if (!control_pdev) { - dev_err(&pdev->dev, "Failed to get control device\n"); - ret = -EINVAL; - goto err2; - } - glue->control_otghs = &control_pdev->dev; } + glue->control_otghs = &control_pdev->dev; } + pdata->platform_ops = &omap2430_ops; platform_set_drvdata(pdev, glue); diff --git a/drivers/usb/phy/Kconfig b/drivers/usb/phy/Kconfig index 0f8ab981d572..b9b0a44be679 100644 --- a/drivers/usb/phy/Kconfig +++ b/drivers/usb/phy/Kconfig @@ -159,6 +159,15 @@ config USB_MXS_PHY MXS Phy is used by some of the i.MX SoCs, for example imx23/28/6x. +config USB_TEGRA_PHY + tristate "NVIDIA Tegra USB PHY Driver" + depends on ARCH_TEGRA + select USB_PHY + select USB_ULPI + help + This driver provides PHY support for the USB controllers found + on NVIDIA Tegra SoC's. + config USB_ULPI bool "Generic ULPI Transceiver Driver" depends on ARM || ARM64 diff --git a/drivers/usb/phy/Makefile b/drivers/usb/phy/Makefile index 25e579fb92b8..df1d99010079 100644 --- a/drivers/usb/phy/Makefile +++ b/drivers/usb/phy/Makefile @@ -16,7 +16,7 @@ obj-$(CONFIG_AM335X_CONTROL_USB) += phy-am335x-control.o obj-$(CONFIG_AM335X_PHY_USB) += phy-am335x.o obj-$(CONFIG_OMAP_OTG) += phy-omap-otg.o obj-$(CONFIG_TWL6030_USB) += phy-twl6030-usb.o -obj-$(CONFIG_USB_EHCI_TEGRA) += phy-tegra-usb.o +obj-$(CONFIG_USB_TEGRA_PHY) += phy-tegra-usb.o obj-$(CONFIG_USB_GPIO_VBUS) += phy-gpio-vbus-usb.o obj-$(CONFIG_USB_ISP1301) += phy-isp1301.o obj-$(CONFIG_USB_MV_OTG) += phy-mv-usb.o diff --git a/drivers/usb/phy/phy-am335x.c b/drivers/usb/phy/phy-am335x.c index b36fa8b953d0..27bdb7222527 100644 --- a/drivers/usb/phy/phy-am335x.c +++ b/drivers/usb/phy/phy-am335x.c @@ -96,8 +96,7 @@ static int am335x_phy_remove(struct platform_device *pdev) #ifdef CONFIG_PM_SLEEP static int am335x_phy_suspend(struct device *dev) { - struct platform_device *pdev = to_platform_device(dev); - struct am335x_phy *am_phy = platform_get_drvdata(pdev); + struct am335x_phy *am_phy = dev_get_drvdata(dev); /* * Enable phy wakeup only if dev->power.can_wakeup is true. @@ -117,8 +116,7 @@ static int am335x_phy_suspend(struct device *dev) static int am335x_phy_resume(struct device *dev) { - struct platform_device *pdev = to_platform_device(dev); - struct am335x_phy *am_phy = platform_get_drvdata(pdev); + struct am335x_phy *am_phy = dev_get_drvdata(dev); phy_ctrl_power(am_phy->phy_ctrl, am_phy->id, am_phy->dr_mode, true); diff --git a/drivers/usb/phy/phy-tegra-usb.c b/drivers/usb/phy/phy-tegra-usb.c index 0e8d23e51732..ea7ef1dc0b42 100644 --- a/drivers/usb/phy/phy-tegra-usb.c +++ b/drivers/usb/phy/phy-tegra-usb.c @@ -236,13 +236,83 @@ static void set_phcd(struct tegra_usb_phy *phy, bool enable) static int utmip_pad_open(struct tegra_usb_phy *phy) { + int ret; + phy->pad_clk = devm_clk_get(phy->u_phy.dev, "utmi-pads"); if (IS_ERR(phy->pad_clk)) { - pr_err("%s: can't get utmip pad clock\n", __func__); - return PTR_ERR(phy->pad_clk); + ret = PTR_ERR(phy->pad_clk); + dev_err(phy->u_phy.dev, + "Failed to get UTMIP pad clock: %d\n", ret); + return ret; } - return 0; + phy->pad_rst = devm_reset_control_get_optional_shared( + phy->u_phy.dev, "utmi-pads"); + if (IS_ERR(phy->pad_rst)) { + ret = PTR_ERR(phy->pad_rst); + dev_err(phy->u_phy.dev, + "Failed to get UTMI-pads reset: %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(phy->pad_clk); + if (ret) { + dev_err(phy->u_phy.dev, + "Failed to enable UTMI-pads clock: %d\n", ret); + return ret; + } + + spin_lock(&utmip_pad_lock); + + ret = reset_control_deassert(phy->pad_rst); + if (ret) { + dev_err(phy->u_phy.dev, + "Failed to initialize UTMI-pads reset: %d\n", ret); + goto unlock; + } + + ret = reset_control_assert(phy->pad_rst); + if (ret) { + dev_err(phy->u_phy.dev, + "Failed to assert UTMI-pads reset: %d\n", ret); + goto unlock; + } + + udelay(1); + + ret = reset_control_deassert(phy->pad_rst); + if (ret) + dev_err(phy->u_phy.dev, + "Failed to deassert UTMI-pads reset: %d\n", ret); +unlock: + spin_unlock(&utmip_pad_lock); + + clk_disable_unprepare(phy->pad_clk); + + return ret; +} + +static int utmip_pad_close(struct tegra_usb_phy *phy) +{ + int ret; + + ret = clk_prepare_enable(phy->pad_clk); + if (ret) { + dev_err(phy->u_phy.dev, + "Failed to enable UTMI-pads clock: %d\n", ret); + return ret; + } + + ret = reset_control_assert(phy->pad_rst); + if (ret) + dev_err(phy->u_phy.dev, + "Failed to assert UTMI-pads reset: %d\n", ret); + + udelay(1); + + clk_disable_unprepare(phy->pad_clk); + + return ret; } static void utmip_pad_power_on(struct tegra_usb_phy *phy) @@ -282,7 +352,7 @@ static int utmip_pad_power_off(struct tegra_usb_phy *phy) void __iomem *base = phy->pad_regs; if (!utmip_pad_count) { - pr_err("%s: utmip pad already powered off\n", __func__); + dev_err(phy->u_phy.dev, "UTMIP pad already powered off\n"); return -EINVAL; } @@ -338,7 +408,8 @@ static void utmi_phy_clk_disable(struct tegra_usb_phy *phy) set_phcd(phy, true); if (utmi_wait_register(base + USB_SUSP_CTRL, USB_PHY_CLK_VALID, 0) < 0) - pr_err("%s: timeout waiting for phy to stabilize\n", __func__); + dev_err(phy->u_phy.dev, + "Timeout waiting for PHY to stabilize on disable\n"); } static void utmi_phy_clk_enable(struct tegra_usb_phy *phy) @@ -370,7 +441,8 @@ static void utmi_phy_clk_enable(struct tegra_usb_phy *phy) if (utmi_wait_register(base + USB_SUSP_CTRL, USB_PHY_CLK_VALID, USB_PHY_CLK_VALID)) - pr_err("%s: timeout waiting for phy to stabilize\n", __func__); + dev_err(phy->u_phy.dev, + "Timeout waiting for PHY to stabilize on enable\n"); } static int utmi_phy_power_on(struct tegra_usb_phy *phy) @@ -617,15 +689,15 @@ static int ulpi_phy_power_on(struct tegra_usb_phy *phy) ret = gpio_direction_output(phy->reset_gpio, 0); if (ret < 0) { - dev_err(phy->u_phy.dev, "gpio %d not set to 0\n", - phy->reset_gpio); + dev_err(phy->u_phy.dev, "GPIO %d not set to 0: %d\n", + phy->reset_gpio, ret); return ret; } msleep(5); ret = gpio_direction_output(phy->reset_gpio, 1); if (ret < 0) { - dev_err(phy->u_phy.dev, "gpio %d not set to 1\n", - phy->reset_gpio); + dev_err(phy->u_phy.dev, "GPIO %d not set to 1: %d\n", + phy->reset_gpio, ret); return ret; } @@ -661,13 +733,13 @@ static int ulpi_phy_power_on(struct tegra_usb_phy *phy) /* Fix VbusInvalid due to floating VBUS */ ret = usb_phy_io_write(phy->ulpi, 0x40, 0x08); if (ret) { - pr_err("%s: ulpi write failed\n", __func__); + dev_err(phy->u_phy.dev, "ULPI write failed: %d\n", ret); return ret; } ret = usb_phy_io_write(phy->ulpi, 0x80, 0x0B); if (ret) { - pr_err("%s: ulpi write failed\n", __func__); + dev_err(phy->u_phy.dev, "ULPI write failed: %d\n", ret); return ret; } @@ -694,6 +766,9 @@ static void tegra_usb_phy_close(struct tegra_usb_phy *phy) if (!IS_ERR(phy->vbus)) regulator_disable(phy->vbus); + if (!phy->is_ulpi_phy) + utmip_pad_close(phy); + clk_disable_unprepare(phy->pll_u); } @@ -728,28 +803,30 @@ static int ulpi_open(struct tegra_usb_phy *phy) phy->clk = devm_clk_get(phy->u_phy.dev, "ulpi-link"); if (IS_ERR(phy->clk)) { - pr_err("%s: can't get ulpi clock\n", __func__); - return PTR_ERR(phy->clk); + err = PTR_ERR(phy->clk); + dev_err(phy->u_phy.dev, "Failed to get ULPI clock: %d\n", err); + return err; } err = devm_gpio_request(phy->u_phy.dev, phy->reset_gpio, "ulpi_phy_reset_b"); if (err < 0) { - dev_err(phy->u_phy.dev, "request failed for gpio: %d\n", - phy->reset_gpio); + dev_err(phy->u_phy.dev, "Request failed for GPIO %d: %d\n", + phy->reset_gpio, err); return err; } err = gpio_direction_output(phy->reset_gpio, 0); if (err < 0) { - dev_err(phy->u_phy.dev, "gpio %d direction not set to output\n", - phy->reset_gpio); + dev_err(phy->u_phy.dev, + "GPIO %d direction not set to output: %d\n", + phy->reset_gpio, err); return err; } phy->ulpi = otg_ulpi_create(&ulpi_viewport_access_ops, 0); if (!phy->ulpi) { - dev_err(phy->u_phy.dev, "otg_ulpi_create returned NULL\n"); + dev_err(phy->u_phy.dev, "Failed to create ULPI OTG\n"); err = -ENOMEM; return err; } @@ -766,8 +843,10 @@ static int tegra_usb_phy_init(struct tegra_usb_phy *phy) phy->pll_u = devm_clk_get(phy->u_phy.dev, "pll_u"); if (IS_ERR(phy->pll_u)) { - pr_err("Can't get pll_u clock\n"); - return PTR_ERR(phy->pll_u); + err = PTR_ERR(phy->pll_u); + dev_err(phy->u_phy.dev, + "Failed to get pll_u clock: %d\n", err); + return err; } err = clk_prepare_enable(phy->pll_u); @@ -782,7 +861,8 @@ static int tegra_usb_phy_init(struct tegra_usb_phy *phy) } } if (!phy->freq) { - pr_err("invalid pll_u parent rate %ld\n", parent_rate); + dev_err(phy->u_phy.dev, "Invalid pll_u parent rate %ld\n", + parent_rate); err = -EINVAL; goto fail; } @@ -791,7 +871,7 @@ static int tegra_usb_phy_init(struct tegra_usb_phy *phy) err = regulator_enable(phy->vbus); if (err) { dev_err(phy->u_phy.dev, - "failed to enable usb vbus regulator: %d\n", + "Failed to enable USB VBUS regulator: %d\n", err); goto fail; } @@ -855,7 +935,8 @@ static int read_utmi_param(struct platform_device *pdev, const char *param, int err = of_property_read_u32(pdev->dev.of_node, param, &value); *dest = (u8)value; if (err < 0) - dev_err(&pdev->dev, "Failed to read USB UTMI parameter %s: %d\n", + dev_err(&pdev->dev, + "Failed to read USB UTMI parameter %s: %d\n", param, err); return err; } @@ -871,14 +952,14 @@ static int utmi_phy_probe(struct tegra_usb_phy *tegra_phy, res = platform_get_resource(pdev, IORESOURCE_MEM, 1); if (!res) { - dev_err(&pdev->dev, "Failed to get UTMI Pad regs\n"); + dev_err(&pdev->dev, "Failed to get UTMI pad regs\n"); return -ENXIO; } tegra_phy->pad_regs = devm_ioremap(&pdev->dev, res->start, resource_size(res)); if (!tegra_phy->pad_regs) { - dev_err(&pdev->dev, "Failed to remap UTMI Pad regs\n"); + dev_err(&pdev->dev, "Failed to remap UTMI pad regs\n"); return -ENOMEM; } @@ -1020,15 +1101,16 @@ static int tegra_usb_phy_probe(struct platform_device *pdev) tegra_phy->reset_gpio = of_get_named_gpio(np, "nvidia,phy-reset-gpio", 0); if (!gpio_is_valid(tegra_phy->reset_gpio)) { - dev_err(&pdev->dev, "invalid gpio: %d\n", - tegra_phy->reset_gpio); + dev_err(&pdev->dev, + "Invalid GPIO: %d\n", tegra_phy->reset_gpio); return tegra_phy->reset_gpio; } tegra_phy->config = NULL; break; default: - dev_err(&pdev->dev, "phy_type is invalid or unsupported\n"); + dev_err(&pdev->dev, "phy_type %u is invalid or unsupported\n", + phy_type); return -EINVAL; } diff --git a/drivers/usb/phy/phy.c b/drivers/usb/phy/phy.c index bceb2c9988dd..0277f62739a2 100644 --- a/drivers/usb/phy/phy.c +++ b/drivers/usb/phy/phy.c @@ -27,7 +27,6 @@ #define DEFAULT_ACA_CUR_MAX 5000 static LIST_HEAD(phy_list); -static LIST_HEAD(phy_bind_list); static DEFINE_SPINLOCK(phy_lock); struct phy_devm { @@ -50,24 +49,6 @@ static struct usb_phy *__usb_find_phy(struct list_head *list, return ERR_PTR(-ENODEV); } -static struct usb_phy *__usb_find_phy_dev(struct device *dev, - struct list_head *list, u8 index) -{ - struct usb_phy_bind *phy_bind = NULL; - - list_for_each_entry(phy_bind, list, list) { - if (!(strcmp(phy_bind->dev_name, dev_name(dev))) && - phy_bind->index == index) { - if (phy_bind->phy) - return phy_bind->phy; - else - return ERR_PTR(-EPROBE_DEFER); - } - } - - return ERR_PTR(-ENODEV); -} - static struct usb_phy *__of_usb_find_phy(struct device_node *node) { struct usb_phy *phy; @@ -585,72 +566,6 @@ struct usb_phy *devm_usb_get_phy_by_phandle(struct device *dev, EXPORT_SYMBOL_GPL(devm_usb_get_phy_by_phandle); /** - * usb_get_phy_dev - find the USB PHY - * @dev - device that requests this phy - * @index - the index of the phy - * - * Returns the phy driver, after getting a refcount to it; or - * -ENODEV if there is no such phy. The caller is responsible for - * calling usb_put_phy() to release that count. - * - * For use by USB host and peripheral drivers. - */ -struct usb_phy *usb_get_phy_dev(struct device *dev, u8 index) -{ - struct usb_phy *phy = NULL; - unsigned long flags; - - spin_lock_irqsave(&phy_lock, flags); - - phy = __usb_find_phy_dev(dev, &phy_bind_list, index); - if (IS_ERR(phy) || !try_module_get(phy->dev->driver->owner)) { - dev_dbg(dev, "unable to find transceiver\n"); - if (!IS_ERR(phy)) - phy = ERR_PTR(-ENODEV); - - goto err0; - } - - get_device(phy->dev); - -err0: - spin_unlock_irqrestore(&phy_lock, flags); - - return phy; -} -EXPORT_SYMBOL_GPL(usb_get_phy_dev); - -/** - * devm_usb_get_phy_dev - find the USB PHY using device ptr and index - * @dev - device that requests this phy - * @index - the index of the phy - * - * Gets the phy using usb_get_phy_dev(), and associates a device with it using - * devres. On driver detach, release function is invoked on the devres data, - * then, devres data is freed. - * - * For use by USB host and peripheral drivers. - */ -struct usb_phy *devm_usb_get_phy_dev(struct device *dev, u8 index) -{ - struct usb_phy **ptr, *phy; - - ptr = devres_alloc(devm_usb_phy_release, sizeof(*ptr), GFP_KERNEL); - if (!ptr) - return NULL; - - phy = usb_get_phy_dev(dev, index); - if (!IS_ERR(phy)) { - *ptr = phy; - devres_add(dev, ptr); - } else - devres_free(ptr); - - return phy; -} -EXPORT_SYMBOL_GPL(devm_usb_get_phy_dev); - -/** * devm_usb_put_phy - release the USB PHY * @dev - device that wants to release this phy * @phy - the phy returned by devm_usb_get_phy() @@ -745,7 +660,6 @@ EXPORT_SYMBOL_GPL(usb_add_phy); */ int usb_add_phy_dev(struct usb_phy *x) { - struct usb_phy_bind *phy_bind; unsigned long flags; int ret; @@ -762,13 +676,9 @@ int usb_add_phy_dev(struct usb_phy *x) ATOMIC_INIT_NOTIFIER_HEAD(&x->notifier); spin_lock_irqsave(&phy_lock, flags); - list_for_each_entry(phy_bind, &phy_bind_list, list) - if (!(strcmp(phy_bind->phy_dev_name, dev_name(x->dev)))) - phy_bind->phy = x; - list_add_tail(&x->head, &phy_list); - spin_unlock_irqrestore(&phy_lock, flags); + return 0; } EXPORT_SYMBOL_GPL(usb_add_phy_dev); @@ -782,54 +692,15 @@ EXPORT_SYMBOL_GPL(usb_add_phy_dev); void usb_remove_phy(struct usb_phy *x) { unsigned long flags; - struct usb_phy_bind *phy_bind; spin_lock_irqsave(&phy_lock, flags); - if (x) { - list_for_each_entry(phy_bind, &phy_bind_list, list) - if (phy_bind->phy == x) - phy_bind->phy = NULL; + if (x) list_del(&x->head); - } spin_unlock_irqrestore(&phy_lock, flags); } EXPORT_SYMBOL_GPL(usb_remove_phy); /** - * usb_bind_phy - bind the phy and the controller that uses the phy - * @dev_name: the device name of the device that will bind to the phy - * @index: index to specify the port number - * @phy_dev_name: the device name of the phy - * - * Fills the phy_bind structure with the dev_name and phy_dev_name. This will - * be used when the phy driver registers the phy and when the controller - * requests this phy. - * - * To be used by platform specific initialization code. - */ -int usb_bind_phy(const char *dev_name, u8 index, - const char *phy_dev_name) -{ - struct usb_phy_bind *phy_bind; - unsigned long flags; - - phy_bind = kzalloc(sizeof(*phy_bind), GFP_KERNEL); - if (!phy_bind) - return -ENOMEM; - - phy_bind->dev_name = dev_name; - phy_bind->phy_dev_name = phy_dev_name; - phy_bind->index = index; - - spin_lock_irqsave(&phy_lock, flags); - list_add_tail(&phy_bind->list, &phy_bind_list); - spin_unlock_irqrestore(&phy_lock, flags); - - return 0; -} -EXPORT_SYMBOL_GPL(usb_bind_phy); - -/** * usb_phy_set_event - set event to phy event * @x: the phy returned by usb_get_phy(); * diff --git a/drivers/usb/renesas_usbhs/common.h b/drivers/usb/renesas_usbhs/common.h index f619afeae2b8..6137f7942c05 100644 --- a/drivers/usb/renesas_usbhs/common.h +++ b/drivers/usb/renesas_usbhs/common.h @@ -276,7 +276,6 @@ struct usbhs_priv { */ struct usbhs_fifo_info fifo_info; - struct usb_phy *usb_phy; struct phy *phy; }; diff --git a/drivers/usb/renesas_usbhs/rcar2.c b/drivers/usb/renesas_usbhs/rcar2.c index 85a0e0933917..0027092b1118 100644 --- a/drivers/usb/renesas_usbhs/rcar2.c +++ b/drivers/usb/renesas_usbhs/rcar2.c @@ -8,7 +8,6 @@ #include <linux/gpio.h> #include <linux/of_gpio.h> #include <linux/phy/phy.h> -#include <linux/usb/phy.h> #include "common.h" #include "rcar2.h" @@ -26,16 +25,6 @@ static int usbhs_rcar2_hardware_init(struct platform_device *pdev) return 0; } - if (IS_ENABLED(CONFIG_USB_PHY)) { - struct usb_phy *usb_phy = usb_get_phy_dev(&pdev->dev, 0); - - if (IS_ERR(usb_phy)) - return PTR_ERR(usb_phy); - - priv->usb_phy = usb_phy; - return 0; - } - return -ENXIO; } @@ -48,11 +37,6 @@ static int usbhs_rcar2_hardware_exit(struct platform_device *pdev) priv->phy = NULL; } - if (priv->usb_phy) { - usb_put_phy(priv->usb_phy); - priv->usb_phy = NULL; - } - return 0; } @@ -75,19 +59,6 @@ static int usbhs_rcar2_power_ctrl(struct platform_device *pdev, } } - if (priv->usb_phy) { - if (enable) { - retval = usb_phy_init(priv->usb_phy); - - if (!retval) - retval = usb_phy_set_suspend(priv->usb_phy, 0); - } else { - usb_phy_set_suspend(priv->usb_phy, 1); - usb_phy_shutdown(priv->usb_phy); - retval = 0; - } - } - return retval; } diff --git a/drivers/usb/roles/intel-xhci-usb-role-switch.c b/drivers/usb/roles/intel-xhci-usb-role-switch.c index de72eedb762e..28102127b9d5 100644 --- a/drivers/usb/roles/intel-xhci-usb-role-switch.c +++ b/drivers/usb/roles/intel-xhci-usb-role-switch.c @@ -144,6 +144,8 @@ static int intel_xhci_usb_probe(struct platform_device *pdev) return -ENOMEM; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -EINVAL; data->base = devm_ioremap_nocache(dev, res->start, resource_size(res)); if (!data->base) return -ENOMEM; diff --git a/drivers/usb/storage/freecom.c b/drivers/usb/storage/freecom.c index ec4d92c92762..4f542df37a44 100644 --- a/drivers/usb/storage/freecom.c +++ b/drivers/usb/storage/freecom.c @@ -464,7 +464,7 @@ static int init_freecom(struct us_data *us) usb_stor_dbg(us, "result from activate reset is %d\n", result); /* wait 250ms */ - mdelay(250); + msleep(250); /* clear reset */ result = usb_stor_control_msg(us, us->send_ctrl_pipe, @@ -472,7 +472,7 @@ static int init_freecom(struct us_data *us) usb_stor_dbg(us, "result from clear reset is %d\n", result); /* wait 3 seconds */ - mdelay(3 * 1000); + msleep(3 * 1000); return USB_STOR_TRANSPORT_GOOD; } diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig index 030f88cb0c3f..2c8eab11a493 100644 --- a/drivers/usb/typec/Kconfig +++ b/drivers/usb/typec/Kconfig @@ -49,6 +49,7 @@ config TYPEC_TCPM tristate "USB Type-C Port Controller Manager" depends on USB select USB_ROLE_SWITCH + select POWER_SUPPLY help The Type-C Port Controller Manager provides a USB PD and USB Type-C state machine for use with Type-C Port Controllers. diff --git a/drivers/usb/typec/fusb302/Kconfig b/drivers/usb/typec/fusb302/Kconfig index 48a4f2fcee03..fce099ff39fe 100644 --- a/drivers/usb/typec/fusb302/Kconfig +++ b/drivers/usb/typec/fusb302/Kconfig @@ -1,6 +1,6 @@ config TYPEC_FUSB302 tristate "Fairchild FUSB302 Type-C chip driver" - depends on I2C && POWER_SUPPLY + depends on I2C help The Fairchild FUSB302 Type-C chip driver that works with Type-C Port Controller Manager to provide USB PD and USB diff --git a/drivers/usb/typec/fusb302/fusb302.c b/drivers/usb/typec/fusb302/fusb302.c index 703617129067..eba6bb890b17 100644 --- a/drivers/usb/typec/fusb302/fusb302.c +++ b/drivers/usb/typec/fusb302/fusb302.c @@ -18,7 +18,6 @@ #include <linux/of_device.h> #include <linux/of_gpio.h> #include <linux/pinctrl/consumer.h> -#include <linux/power_supply.h> #include <linux/proc_fs.h> #include <linux/regulator/consumer.h> #include <linux/sched/clock.h> @@ -99,11 +98,6 @@ struct fusb302_chip { /* lock for sharing chip states */ struct mutex lock; - /* psy + psy status */ - struct power_supply *psy; - u32 current_limit; - u32 supply_voltage; - /* chip status */ enum toggling_mode toggling_mode; enum src_current_status src_current_status; @@ -120,6 +114,7 @@ struct fusb302_chip { enum typec_cc_polarity cc_polarity; enum typec_cc_status cc1; enum typec_cc_status cc2; + u32 snk_pdo[PDO_MAX_OBJECTS]; #ifdef CONFIG_DEBUG_FS struct dentry *dentry; @@ -861,13 +856,11 @@ static int tcpm_set_vbus(struct tcpc_dev *dev, bool on, bool charge) chip->vbus_on = on; fusb302_log(chip, "vbus := %s", on ? "On" : "Off"); } - if (chip->charge_on == charge) { + if (chip->charge_on == charge) fusb302_log(chip, "charge is already %s", charge ? "On" : "Off"); - } else { + else chip->charge_on = charge; - power_supply_changed(chip->psy); - } done: mutex_unlock(&chip->lock); @@ -883,11 +876,6 @@ static int tcpm_set_current_limit(struct tcpc_dev *dev, u32 max_ma, u32 mv) fusb302_log(chip, "current limit: %d ma, %d mv (not implemented)", max_ma, mv); - chip->supply_voltage = mv; - chip->current_limit = max_ma; - - power_supply_changed(chip->psy); - return 0; } @@ -1212,11 +1200,6 @@ static const u32 snk_pdo[] = { static const struct tcpc_config fusb302_tcpc_config = { .src_pdo = src_pdo, .nr_src_pdo = ARRAY_SIZE(src_pdo), - .snk_pdo = snk_pdo, - .nr_snk_pdo = ARRAY_SIZE(snk_pdo), - .max_snk_mv = 5000, - .max_snk_ma = 3000, - .max_snk_mw = 15000, .operating_snk_mw = 2500, .type = TYPEC_PORT_DRP, .data = TYPEC_PORT_DRD, @@ -1686,43 +1669,6 @@ done: return IRQ_HANDLED; } -static int fusb302_psy_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct fusb302_chip *chip = power_supply_get_drvdata(psy); - - switch (psp) { - case POWER_SUPPLY_PROP_ONLINE: - val->intval = chip->charge_on; - break; - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - val->intval = chip->supply_voltage * 1000; /* mV -> µV */ - break; - case POWER_SUPPLY_PROP_CURRENT_MAX: - val->intval = chip->current_limit * 1000; /* mA -> µA */ - break; - default: - return -ENODATA; - } - - return 0; -} - -static enum power_supply_property fusb302_psy_properties[] = { - POWER_SUPPLY_PROP_ONLINE, - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_CURRENT_MAX, -}; - -static const struct power_supply_desc fusb302_psy_desc = { - .name = "fusb302-typec-source", - .type = POWER_SUPPLY_TYPE_USB_TYPE_C, - .properties = fusb302_psy_properties, - .num_properties = ARRAY_SIZE(fusb302_psy_properties), - .get_property = fusb302_psy_get_property, -}; - static int init_gpio(struct fusb302_chip *chip) { struct device_node *node; @@ -1756,13 +1702,35 @@ static int init_gpio(struct fusb302_chip *chip) return 0; } +static int fusb302_composite_snk_pdo_array(struct fusb302_chip *chip) +{ + struct device *dev = chip->dev; + u32 max_uv, max_ua; + + chip->snk_pdo[0] = PDO_FIXED(5000, 400, PDO_FIXED_FLAGS); + + /* + * As max_snk_ma/mv/mw is not needed for tcpc_config, + * those settings should be passed in via sink PDO, so + * "fcs, max-sink-*" properties will be deprecated, to + * perserve compatibility with existing users of them, + * we read those properties to convert them to be a var + * PDO. + */ + if (device_property_read_u32(dev, "fcs,max-sink-microvolt", &max_uv) || + device_property_read_u32(dev, "fcs,max-sink-microamp", &max_ua)) + return 1; + + chip->snk_pdo[1] = PDO_VAR(5000, max_uv / 1000, max_ua / 1000); + return 2; +} + static int fusb302_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct fusb302_chip *chip; struct i2c_adapter *adapter; struct device *dev = &client->dev; - struct power_supply_config cfg = {}; const char *name; int ret = 0; u32 v; @@ -1784,18 +1752,13 @@ static int fusb302_probe(struct i2c_client *client, chip->tcpc_dev.config = &chip->tcpc_config; mutex_init(&chip->lock); - if (!device_property_read_u32(dev, "fcs,max-sink-microvolt", &v)) - chip->tcpc_config.max_snk_mv = v / 1000; - - if (!device_property_read_u32(dev, "fcs,max-sink-microamp", &v)) - chip->tcpc_config.max_snk_ma = v / 1000; - - if (!device_property_read_u32(dev, "fcs,max-sink-microwatt", &v)) - chip->tcpc_config.max_snk_mw = v / 1000; - if (!device_property_read_u32(dev, "fcs,operating-sink-microwatt", &v)) chip->tcpc_config.operating_snk_mw = v / 1000; + /* Composite sink PDO */ + chip->tcpc_config.nr_snk_pdo = fusb302_composite_snk_pdo_array(chip); + chip->tcpc_config.snk_pdo = chip->snk_pdo; + /* * Devicetree platforms should get extcon via phandle (not yet * supported). On ACPI platforms, we get the name from a device prop. @@ -1809,14 +1772,6 @@ static int fusb302_probe(struct i2c_client *client, return -EPROBE_DEFER; } - cfg.drv_data = chip; - chip->psy = devm_power_supply_register(dev, &fusb302_psy_desc, &cfg); - if (IS_ERR(chip->psy)) { - ret = PTR_ERR(chip->psy); - dev_err(chip->dev, "Error registering power-supply: %d\n", ret); - return ret; - } - ret = fusb302_debugfs_init(chip); if (ret < 0) return ret; diff --git a/drivers/usb/typec/tcpm.c b/drivers/usb/typec/tcpm.c index 677d12138dbd..1ee259bc14a5 100644 --- a/drivers/usb/typec/tcpm.c +++ b/drivers/usb/typec/tcpm.c @@ -12,13 +12,16 @@ #include <linux/kernel.h> #include <linux/module.h> #include <linux/mutex.h> +#include <linux/power_supply.h> #include <linux/proc_fs.h> #include <linux/sched/clock.h> #include <linux/seq_file.h> #include <linux/slab.h> #include <linux/spinlock.h> #include <linux/usb/pd.h> +#include <linux/usb/pd_ado.h> #include <linux/usb/pd_bdo.h> +#include <linux/usb/pd_ext_sdb.h> #include <linux/usb/pd_vdo.h> #include <linux/usb/role.h> #include <linux/usb/tcpm.h> @@ -48,6 +51,7 @@ S(SNK_DISCOVERY_DEBOUNCE_DONE), \ S(SNK_WAIT_CAPABILITIES), \ S(SNK_NEGOTIATE_CAPABILITIES), \ + S(SNK_NEGOTIATE_PPS_CAPABILITIES), \ S(SNK_TRANSITION_SINK), \ S(SNK_TRANSITION_SINK_VBUS), \ S(SNK_READY), \ @@ -112,6 +116,11 @@ S(SNK_TRYWAIT_VBUS), \ S(BIST_RX), \ \ + S(GET_STATUS_SEND), \ + S(GET_STATUS_SEND_TIMEOUT), \ + S(GET_PPS_STATUS_SEND), \ + S(GET_PPS_STATUS_SEND_TIMEOUT), \ + \ S(ERROR_RECOVERY), \ S(PORT_RESET), \ S(PORT_RESET_WAIT_OFF) @@ -142,6 +151,7 @@ enum pd_msg_request { PD_MSG_NONE = 0, PD_MSG_CTRL_REJECT, PD_MSG_CTRL_WAIT, + PD_MSG_CTRL_NOT_SUPP, PD_MSG_DATA_SINK_CAP, PD_MSG_DATA_SOURCE_CAP, }; @@ -167,6 +177,16 @@ struct pd_mode_data { struct typec_altmode_desc altmode_desc[SVID_DISCOVERY_MAX]; }; +struct pd_pps_data { + u32 min_volt; + u32 max_volt; + u32 max_curr; + u32 out_volt; + u32 op_curr; + bool supported; + bool active; +}; + struct tcpm_port { struct device *dev; @@ -235,6 +255,7 @@ struct tcpm_port { struct completion swap_complete; int swap_status; + unsigned int negotiated_rev; unsigned int message_id; unsigned int caps_count; unsigned int hard_reset_count; @@ -257,15 +278,18 @@ struct tcpm_port { u32 snk_vdo[VDO_MAX_OBJECTS]; unsigned int nr_snk_vdo; - unsigned int max_snk_mv; - unsigned int max_snk_ma; - unsigned int max_snk_mw; unsigned int operating_snk_mw; + bool update_sink_caps; /* Requested current / voltage */ u32 current_limit; u32 supply_voltage; + /* Used to export TA voltage and current */ + struct power_supply *psy; + struct power_supply_desc psy_desc; + enum power_supply_usb_type usb_type; + u32 bist_request; /* PD state for Vendor Defined Messages */ @@ -277,8 +301,13 @@ struct tcpm_port { /* VDO to retry if UFP responder replied busy */ u32 vdo_retry; - /* Alternate mode data */ + /* PPS */ + struct pd_pps_data pps_data; + struct completion pps_complete; + bool pps_pending; + int pps_status; + /* Alternate mode data */ struct pd_mode_data mode_data; struct typec_altmode *partner_altmode[SVID_DISCOVERY_MAX]; struct typec_altmode *port_altmode[SVID_DISCOVERY_MAX]; @@ -496,6 +525,16 @@ static void tcpm_log_source_caps(struct tcpm_port *port) pdo_max_voltage(pdo), pdo_max_power(pdo)); break; + case PDO_TYPE_APDO: + if (pdo_apdo_type(pdo) == APDO_TYPE_PPS) + scnprintf(msg, sizeof(msg), + "%u-%u mV, %u mA", + pdo_pps_apdo_min_voltage(pdo), + pdo_pps_apdo_max_voltage(pdo), + pdo_pps_apdo_max_current(pdo)); + else + strcpy(msg, "undefined APDO"); + break; default: strcpy(msg, "undefined"); break; @@ -793,11 +832,13 @@ static int tcpm_pd_send_source_caps(struct tcpm_port *port) msg.header = PD_HEADER_LE(PD_CTRL_REJECT, port->pwr_role, port->data_role, + port->negotiated_rev, port->message_id, 0); } else { msg.header = PD_HEADER_LE(PD_DATA_SOURCE_CAP, port->pwr_role, port->data_role, + port->negotiated_rev, port->message_id, port->nr_src_pdo); } @@ -818,11 +859,13 @@ static int tcpm_pd_send_sink_caps(struct tcpm_port *port) msg.header = PD_HEADER_LE(PD_CTRL_REJECT, port->pwr_role, port->data_role, + port->negotiated_rev, port->message_id, 0); } else { msg.header = PD_HEADER_LE(PD_DATA_SINK_CAP, port->pwr_role, port->data_role, + port->negotiated_rev, port->message_id, port->nr_snk_pdo); } @@ -1189,6 +1232,7 @@ static void vdm_run_state_machine(struct tcpm_port *port) msg.header = PD_HEADER_LE(PD_DATA_VENDOR_DEF, port->pwr_role, port->data_role, + port->negotiated_rev, port->message_id, port->vdo_count); for (i = 0; i < port->vdo_count; i++) msg.payload[i] = cpu_to_le32(port->vdo_data[i]); @@ -1260,6 +1304,8 @@ enum pdo_err { PDO_ERR_FIXED_NOT_SORTED, PDO_ERR_VARIABLE_BATT_NOT_SORTED, PDO_ERR_DUPE_PDO, + PDO_ERR_PPS_APDO_NOT_SORTED, + PDO_ERR_DUPE_PPS_APDO, }; static const char * const pdo_err_msg[] = { @@ -1275,6 +1321,10 @@ static const char * const pdo_err_msg[] = { " err: Variable/Battery supply pdos should be in increasing order of their minimum voltage", [PDO_ERR_DUPE_PDO] = " err: Variable/Batt supply pdos cannot have same min/max voltage", + [PDO_ERR_PPS_APDO_NOT_SORTED] = + " err: Programmable power supply apdos should be in increasing order of their maximum voltage", + [PDO_ERR_DUPE_PPS_APDO] = + " err: Programmable power supply apdos cannot have same min/max voltage and max current", }; static enum pdo_err tcpm_caps_err(struct tcpm_port *port, const u32 *pdo, @@ -1324,6 +1374,26 @@ static enum pdo_err tcpm_caps_err(struct tcpm_port *port, const u32 *pdo, pdo_min_voltage(pdo[i - 1]))) return PDO_ERR_DUPE_PDO; break; + /* + * The Programmable Power Supply APDOs, if present, + * shall be sent in Maximum Voltage order; + * lowest to highest. + */ + case PDO_TYPE_APDO: + if (pdo_apdo_type(pdo[i]) != APDO_TYPE_PPS) + break; + + if (pdo_pps_apdo_max_current(pdo[i]) < + pdo_pps_apdo_max_current(pdo[i - 1])) + return PDO_ERR_PPS_APDO_NOT_SORTED; + else if (pdo_pps_apdo_min_voltage(pdo[i]) == + pdo_pps_apdo_min_voltage(pdo[i - 1]) && + pdo_pps_apdo_max_voltage(pdo[i]) == + pdo_pps_apdo_max_voltage(pdo[i - 1]) && + pdo_pps_apdo_max_current(pdo[i]) == + pdo_pps_apdo_max_current(pdo[i - 1])) + return PDO_ERR_DUPE_PPS_APDO; + break; default: tcpm_log_force(port, " Unknown pdo type"); } @@ -1349,11 +1419,48 @@ static int tcpm_validate_caps(struct tcpm_port *port, const u32 *pdo, /* * PD (data, control) command handling functions */ +static inline enum tcpm_state ready_state(struct tcpm_port *port) +{ + if (port->pwr_role == TYPEC_SOURCE) + return SRC_READY; + else + return SNK_READY; +} + +static int tcpm_pd_send_control(struct tcpm_port *port, + enum pd_ctrl_msg_type type); + +static void tcpm_handle_alert(struct tcpm_port *port, const __le32 *payload, + int cnt) +{ + u32 p0 = le32_to_cpu(payload[0]); + unsigned int type = usb_pd_ado_type(p0); + + if (!type) { + tcpm_log(port, "Alert message received with no type"); + return; + } + + /* Just handling non-battery alerts for now */ + if (!(type & USB_PD_ADO_TYPE_BATT_STATUS_CHANGE)) { + switch (port->state) { + case SRC_READY: + case SNK_READY: + tcpm_set_state(port, GET_STATUS_SEND, 0); + break; + default: + tcpm_queue_message(port, PD_MSG_CTRL_WAIT); + break; + } + } +} + static void tcpm_pd_data_request(struct tcpm_port *port, const struct pd_message *msg) { enum pd_data_msg_type type = pd_header_type_le(msg->header); unsigned int cnt = pd_header_cnt_le(msg->header); + unsigned int rev = pd_header_rev_le(msg->header); unsigned int i; switch (type) { @@ -1372,6 +1479,17 @@ static void tcpm_pd_data_request(struct tcpm_port *port, port->nr_source_caps); /* + * Adjust revision in subsequent message headers, as required, + * to comply with 6.2.1.1.5 of the USB PD 3.0 spec. We don't + * support Rev 1.0 so just do nothing in that scenario. + */ + if (rev == PD_REV10) + break; + + if (rev < PD_MAX_REV) + port->negotiated_rev = rev; + + /* * This message may be received even if VBUS is not * present. This is quite unexpected; see USB PD * specification, sections 8.3.3.6.3.1 and 8.3.3.6.3.2. @@ -1392,6 +1510,20 @@ static void tcpm_pd_data_request(struct tcpm_port *port, tcpm_queue_message(port, PD_MSG_CTRL_REJECT); break; } + + /* + * Adjust revision in subsequent message headers, as required, + * to comply with 6.2.1.1.5 of the USB PD 3.0 spec. We don't + * support Rev 1.0 so just reject in that scenario. + */ + if (rev == PD_REV10) { + tcpm_queue_message(port, PD_MSG_CTRL_REJECT); + break; + } + + if (rev < PD_MAX_REV) + port->negotiated_rev = rev; + port->sink_request = le32_to_cpu(msg->payload[0]); tcpm_set_state(port, SRC_NEGOTIATE_CAPABILITIES, 0); break; @@ -1410,12 +1542,29 @@ static void tcpm_pd_data_request(struct tcpm_port *port, tcpm_set_state(port, BIST_RX, 0); } break; + case PD_DATA_ALERT: + tcpm_handle_alert(port, msg->payload, cnt); + break; + case PD_DATA_BATT_STATUS: + case PD_DATA_GET_COUNTRY_INFO: + /* Currently unsupported */ + tcpm_queue_message(port, PD_MSG_CTRL_NOT_SUPP); + break; default: tcpm_log(port, "Unhandled data message type %#x", type); break; } } +static void tcpm_pps_complete(struct tcpm_port *port, int result) +{ + if (port->pps_pending) { + port->pps_status = result; + port->pps_pending = false; + complete(&port->pps_complete); + } +} + static void tcpm_pd_ctrl_request(struct tcpm_port *port, const struct pd_message *msg) { @@ -1483,6 +1632,7 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, break; case PD_CTRL_REJECT: case PD_CTRL_WAIT: + case PD_CTRL_NOT_SUPP: switch (port->state) { case SNK_NEGOTIATE_CAPABILITIES: /* USB PD specification, Figure 8-43 */ @@ -1492,6 +1642,14 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, next_state = SNK_WAIT_CAPABILITIES; tcpm_set_state(port, next_state, 0); break; + case SNK_NEGOTIATE_PPS_CAPABILITIES: + /* Revert data back from any requested PPS updates */ + port->pps_data.out_volt = port->supply_voltage; + port->pps_data.op_curr = port->current_limit; + port->pps_status = (type == PD_CTRL_WAIT ? + -EAGAIN : -EOPNOTSUPP); + tcpm_set_state(port, SNK_READY, 0); + break; case DR_SWAP_SEND: port->swap_status = (type == PD_CTRL_WAIT ? -EAGAIN : -EOPNOTSUPP); @@ -1514,6 +1672,13 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, case PD_CTRL_ACCEPT: switch (port->state) { case SNK_NEGOTIATE_CAPABILITIES: + port->pps_data.active = false; + tcpm_set_state(port, SNK_TRANSITION_SINK, 0); + break; + case SNK_NEGOTIATE_PPS_CAPABILITIES: + port->pps_data.active = true; + port->supply_voltage = port->pps_data.out_volt; + port->current_limit = port->pps_data.op_curr; tcpm_set_state(port, SNK_TRANSITION_SINK, 0); break; case SOFT_RESET_SEND: @@ -1587,12 +1752,75 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, break; } break; + case PD_CTRL_GET_SOURCE_CAP_EXT: + case PD_CTRL_GET_STATUS: + case PD_CTRL_FR_SWAP: + case PD_CTRL_GET_PPS_STATUS: + case PD_CTRL_GET_COUNTRY_CODES: + /* Currently not supported */ + tcpm_queue_message(port, PD_MSG_CTRL_NOT_SUPP); + break; default: tcpm_log(port, "Unhandled ctrl message type %#x", type); break; } } +static void tcpm_pd_ext_msg_request(struct tcpm_port *port, + const struct pd_message *msg) +{ + enum pd_ext_msg_type type = pd_header_type_le(msg->header); + unsigned int data_size = pd_ext_header_data_size_le(msg->ext_msg.header); + + if (!(msg->ext_msg.header && PD_EXT_HDR_CHUNKED)) { + tcpm_log(port, "Unchunked extended messages unsupported"); + return; + } + + if (data_size > PD_EXT_MAX_CHUNK_DATA) { + tcpm_log(port, "Chunk handling not yet supported"); + return; + } + + switch (type) { + case PD_EXT_STATUS: + /* + * If PPS related events raised then get PPS status to clear + * (see USB PD 3.0 Spec, 6.5.2.4) + */ + if (msg->ext_msg.data[USB_PD_EXT_SDB_EVENT_FLAGS] & + USB_PD_EXT_SDB_PPS_EVENTS) + tcpm_set_state(port, GET_PPS_STATUS_SEND, 0); + else + tcpm_set_state(port, ready_state(port), 0); + break; + case PD_EXT_PPS_STATUS: + /* + * For now the PPS status message is used to clear events + * and nothing more. + */ + tcpm_set_state(port, ready_state(port), 0); + break; + case PD_EXT_SOURCE_CAP_EXT: + case PD_EXT_GET_BATT_CAP: + case PD_EXT_GET_BATT_STATUS: + case PD_EXT_BATT_CAP: + case PD_EXT_GET_MANUFACTURER_INFO: + case PD_EXT_MANUFACTURER_INFO: + case PD_EXT_SECURITY_REQUEST: + case PD_EXT_SECURITY_RESPONSE: + case PD_EXT_FW_UPDATE_REQUEST: + case PD_EXT_FW_UPDATE_RESPONSE: + case PD_EXT_COUNTRY_INFO: + case PD_EXT_COUNTRY_CODES: + tcpm_queue_message(port, PD_MSG_CTRL_NOT_SUPP); + break; + default: + tcpm_log(port, "Unhandled extended message type %#x", type); + break; + } +} + static void tcpm_pd_rx_handler(struct work_struct *work) { struct pd_rx_event *event = container_of(work, @@ -1633,7 +1861,9 @@ static void tcpm_pd_rx_handler(struct work_struct *work) "Data role mismatch, initiating error recovery"); tcpm_set_state(port, ERROR_RECOVERY, 0); } else { - if (cnt) + if (msg->header & PD_HEADER_EXT_HDR) + tcpm_pd_ext_msg_request(port, msg); + else if (cnt) tcpm_pd_data_request(port, msg); else tcpm_pd_ctrl_request(port, msg); @@ -1668,6 +1898,7 @@ static int tcpm_pd_send_control(struct tcpm_port *port, memset(&msg, 0, sizeof(msg)); msg.header = PD_HEADER_LE(type, port->pwr_role, port->data_role, + port->negotiated_rev, port->message_id, 0); return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); @@ -1693,6 +1924,9 @@ static bool tcpm_send_queued_message(struct tcpm_port *port) case PD_MSG_CTRL_REJECT: tcpm_pd_send_control(port, PD_CTRL_REJECT); break; + case PD_MSG_CTRL_NOT_SUPP: + tcpm_pd_send_control(port, PD_CTRL_NOT_SUPP); + break; case PD_MSG_DATA_SINK_CAP: tcpm_pd_send_sink_caps(port); break; @@ -1772,84 +2006,254 @@ static int tcpm_pd_check_request(struct tcpm_port *port) return 0; } -static int tcpm_pd_select_pdo(struct tcpm_port *port) +#define min_power(x, y) min(pdo_max_power(x), pdo_max_power(y)) +#define min_current(x, y) min(pdo_max_current(x), pdo_max_current(y)) + +static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_pdo, + int *src_pdo) { - unsigned int i, max_mw = 0, max_mv = 0; + unsigned int i, j, max_src_mv = 0, min_src_mv = 0, max_mw = 0, + max_mv = 0, src_mw = 0, src_ma = 0, max_snk_mv = 0, + min_snk_mv = 0; int ret = -EINVAL; + port->pps_data.supported = false; + port->usb_type = POWER_SUPPLY_USB_TYPE_PD; + /* - * Select the source PDO providing the most power while staying within - * the board's voltage limits. Prefer PDO providing exp + * Select the source PDO providing the most power which has a + * matchig sink cap. */ for (i = 0; i < port->nr_source_caps; i++) { u32 pdo = port->source_caps[i]; enum pd_pdo_type type = pdo_type(pdo); - unsigned int mv, ma, mw; - if (type == PDO_TYPE_FIXED) - mv = pdo_fixed_voltage(pdo); - else - mv = pdo_min_voltage(pdo); + switch (type) { + case PDO_TYPE_FIXED: + max_src_mv = pdo_fixed_voltage(pdo); + min_src_mv = max_src_mv; + break; + case PDO_TYPE_BATT: + case PDO_TYPE_VAR: + max_src_mv = pdo_max_voltage(pdo); + min_src_mv = pdo_min_voltage(pdo); + break; + case PDO_TYPE_APDO: + if (pdo_apdo_type(pdo) == APDO_TYPE_PPS) { + port->pps_data.supported = true; + port->usb_type = + POWER_SUPPLY_USB_TYPE_PD_PPS; + } + continue; + default: + tcpm_log(port, "Invalid source PDO type, ignoring"); + continue; + } - if (type == PDO_TYPE_BATT) { - mw = pdo_max_power(pdo); - } else { - ma = min(pdo_max_current(pdo), - port->max_snk_ma); - mw = ma * mv / 1000; + switch (type) { + case PDO_TYPE_FIXED: + case PDO_TYPE_VAR: + src_ma = pdo_max_current(pdo); + src_mw = src_ma * min_src_mv / 1000; + break; + case PDO_TYPE_BATT: + src_mw = pdo_max_power(pdo); + break; + case PDO_TYPE_APDO: + continue; + default: + tcpm_log(port, "Invalid source PDO type, ignoring"); + continue; } - /* Perfer higher voltages if available */ - if ((mw > max_mw || (mw == max_mw && mv > max_mv)) && - mv <= port->max_snk_mv) { - ret = i; - max_mw = mw; - max_mv = mv; + for (j = 0; j < port->nr_snk_pdo; j++) { + pdo = port->snk_pdo[j]; + + switch (pdo_type(pdo)) { + case PDO_TYPE_FIXED: + max_snk_mv = pdo_fixed_voltage(pdo); + min_snk_mv = max_snk_mv; + break; + case PDO_TYPE_BATT: + case PDO_TYPE_VAR: + max_snk_mv = pdo_max_voltage(pdo); + min_snk_mv = pdo_min_voltage(pdo); + break; + case PDO_TYPE_APDO: + continue; + default: + tcpm_log(port, "Invalid sink PDO type, ignoring"); + continue; + } + + if (max_src_mv <= max_snk_mv && + min_src_mv >= min_snk_mv) { + /* Prefer higher voltages if available */ + if ((src_mw == max_mw && min_src_mv > max_mv) || + src_mw > max_mw) { + *src_pdo = i; + *sink_pdo = j; + max_mw = src_mw; + max_mv = min_src_mv; + ret = 0; + } + } } } return ret; } +#define min_pps_apdo_current(x, y) \ + min(pdo_pps_apdo_max_current(x), pdo_pps_apdo_max_current(y)) + +static unsigned int tcpm_pd_select_pps_apdo(struct tcpm_port *port) +{ + unsigned int i, j, max_mw = 0, max_mv = 0; + unsigned int min_src_mv, max_src_mv, src_ma, src_mw; + unsigned int min_snk_mv, max_snk_mv, snk_ma; + u32 pdo; + unsigned int src_pdo = 0, snk_pdo = 0; + + /* + * Select the source PPS APDO providing the most power while staying + * within the board's limits. We skip the first PDO as this is always + * 5V 3A. + */ + for (i = 1; i < port->nr_source_caps; ++i) { + pdo = port->source_caps[i]; + + switch (pdo_type(pdo)) { + case PDO_TYPE_APDO: + if (pdo_apdo_type(pdo) != APDO_TYPE_PPS) { + tcpm_log(port, "Not PPS APDO (source), ignoring"); + continue; + } + + min_src_mv = pdo_pps_apdo_min_voltage(pdo); + max_src_mv = pdo_pps_apdo_max_voltage(pdo); + src_ma = pdo_pps_apdo_max_current(pdo); + src_mw = (src_ma * max_src_mv) / 1000; + + /* + * Now search through the sink PDOs to find a matching + * PPS APDO. Again skip the first sink PDO as this will + * always be 5V 3A. + */ + for (j = i; j < port->nr_snk_pdo; j++) { + pdo = port->snk_pdo[j]; + + switch (pdo_type(pdo)) { + case PDO_TYPE_APDO: + if (pdo_apdo_type(pdo) != APDO_TYPE_PPS) { + tcpm_log(port, + "Not PPS APDO (sink), ignoring"); + continue; + } + + min_snk_mv = + pdo_pps_apdo_min_voltage(pdo); + max_snk_mv = + pdo_pps_apdo_max_voltage(pdo); + snk_ma = + pdo_pps_apdo_max_current(pdo); + break; + default: + tcpm_log(port, + "Not APDO type (sink), ignoring"); + continue; + } + + if (max_src_mv <= max_snk_mv && + min_src_mv >= min_snk_mv) { + /* Prefer higher voltages if available */ + if ((src_mw == max_mw && + min_src_mv > max_mv) || + src_mw > max_mw) { + src_pdo = i; + snk_pdo = j; + max_mw = src_mw; + max_mv = max_src_mv; + } + } + } + + break; + default: + tcpm_log(port, "Not APDO type (source), ignoring"); + continue; + } + } + + if (src_pdo) { + pdo = port->source_caps[src_pdo]; + + port->pps_data.min_volt = pdo_pps_apdo_min_voltage(pdo); + port->pps_data.max_volt = pdo_pps_apdo_max_voltage(pdo); + port->pps_data.max_curr = + min_pps_apdo_current(pdo, port->snk_pdo[snk_pdo]); + port->pps_data.out_volt = + min(pdo_pps_apdo_max_voltage(pdo), port->pps_data.out_volt); + port->pps_data.op_curr = + min(port->pps_data.max_curr, port->pps_data.op_curr); + } + + return src_pdo; +} + static int tcpm_pd_build_request(struct tcpm_port *port, u32 *rdo) { unsigned int mv, ma, mw, flags; unsigned int max_ma, max_mw; enum pd_pdo_type type; - int index; - u32 pdo; + u32 pdo, matching_snk_pdo; + int src_pdo_index = 0; + int snk_pdo_index = 0; + int ret; - index = tcpm_pd_select_pdo(port); - if (index < 0) - return -EINVAL; - pdo = port->source_caps[index]; + ret = tcpm_pd_select_pdo(port, &snk_pdo_index, &src_pdo_index); + if (ret < 0) + return ret; + + pdo = port->source_caps[src_pdo_index]; + matching_snk_pdo = port->snk_pdo[snk_pdo_index]; type = pdo_type(pdo); - if (type == PDO_TYPE_FIXED) + switch (type) { + case PDO_TYPE_FIXED: mv = pdo_fixed_voltage(pdo); - else + break; + case PDO_TYPE_BATT: + case PDO_TYPE_VAR: mv = pdo_min_voltage(pdo); + break; + default: + tcpm_log(port, "Invalid PDO selected!"); + return -EINVAL; + } - /* Select maximum available current within the board's power limit */ + /* Select maximum available current within the sink pdo's limit */ if (type == PDO_TYPE_BATT) { - mw = pdo_max_power(pdo); - ma = 1000 * min(mw, port->max_snk_mw) / mv; + mw = min_power(pdo, matching_snk_pdo); + ma = 1000 * mw / mv; } else { - ma = min(pdo_max_current(pdo), - 1000 * port->max_snk_mw / mv); + ma = min_current(pdo, matching_snk_pdo); + mw = ma * mv / 1000; } - ma = min(ma, port->max_snk_ma); flags = RDO_USB_COMM | RDO_NO_SUSPEND; /* Set mismatch bit if offered power is less than operating power */ - mw = ma * mv / 1000; max_ma = ma; max_mw = mw; if (mw < port->operating_snk_mw) { flags |= RDO_CAP_MISMATCH; - max_mw = port->operating_snk_mw; - max_ma = max_mw * 1000 / mv; + if (type == PDO_TYPE_BATT && + (pdo_max_power(matching_snk_pdo) > pdo_max_power(pdo))) + max_mw = pdo_max_power(matching_snk_pdo); + else if (pdo_max_current(matching_snk_pdo) > + pdo_max_current(pdo)) + max_ma = pdo_max_current(matching_snk_pdo); } tcpm_log(port, "cc=%d cc1=%d cc2=%d vbus=%d vconn=%s polarity=%d", @@ -1858,16 +2262,16 @@ static int tcpm_pd_build_request(struct tcpm_port *port, u32 *rdo) port->polarity); if (type == PDO_TYPE_BATT) { - *rdo = RDO_BATT(index + 1, mw, max_mw, flags); + *rdo = RDO_BATT(src_pdo_index + 1, mw, max_mw, flags); tcpm_log(port, "Requesting PDO %d: %u mV, %u mW%s", - index, mv, mw, + src_pdo_index, mv, mw, flags & RDO_CAP_MISMATCH ? " [mismatch]" : ""); } else { - *rdo = RDO_FIXED(index + 1, ma, max_ma, flags); + *rdo = RDO_FIXED(src_pdo_index + 1, ma, max_ma, flags); tcpm_log(port, "Requesting PDO %d: %u mV, %u mA%s", - index, mv, ma, + src_pdo_index, mv, ma, flags & RDO_CAP_MISMATCH ? " [mismatch]" : ""); } @@ -1891,6 +2295,105 @@ static int tcpm_pd_send_request(struct tcpm_port *port) msg.header = PD_HEADER_LE(PD_DATA_REQUEST, port->pwr_role, port->data_role, + port->negotiated_rev, + port->message_id, 1); + msg.payload[0] = cpu_to_le32(rdo); + + return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); +} + +static int tcpm_pd_build_pps_request(struct tcpm_port *port, u32 *rdo) +{ + unsigned int out_mv, op_ma, op_mw, min_mv, max_mv, max_ma, flags; + enum pd_pdo_type type; + unsigned int src_pdo_index; + u32 pdo; + + src_pdo_index = tcpm_pd_select_pps_apdo(port); + if (!src_pdo_index) + return -EOPNOTSUPP; + + pdo = port->source_caps[src_pdo_index]; + type = pdo_type(pdo); + + switch (type) { + case PDO_TYPE_APDO: + if (pdo_apdo_type(pdo) != APDO_TYPE_PPS) { + tcpm_log(port, "Invalid APDO selected!"); + return -EINVAL; + } + min_mv = port->pps_data.min_volt; + max_mv = port->pps_data.max_volt; + max_ma = port->pps_data.max_curr; + out_mv = port->pps_data.out_volt; + op_ma = port->pps_data.op_curr; + break; + default: + tcpm_log(port, "Invalid PDO selected!"); + return -EINVAL; + } + + flags = RDO_USB_COMM | RDO_NO_SUSPEND; + + op_mw = (op_ma * out_mv) / 1000; + if (op_mw < port->operating_snk_mw) { + /* + * Try raising current to meet power needs. If that's not enough + * then try upping the voltage. If that's still not enough + * then we've obviously chosen a PPS APDO which really isn't + * suitable so abandon ship. + */ + op_ma = (port->operating_snk_mw * 1000) / out_mv; + if ((port->operating_snk_mw * 1000) % out_mv) + ++op_ma; + op_ma += RDO_PROG_CURR_MA_STEP - (op_ma % RDO_PROG_CURR_MA_STEP); + + if (op_ma > max_ma) { + op_ma = max_ma; + out_mv = (port->operating_snk_mw * 1000) / op_ma; + if ((port->operating_snk_mw * 1000) % op_ma) + ++out_mv; + out_mv += RDO_PROG_VOLT_MV_STEP - + (out_mv % RDO_PROG_VOLT_MV_STEP); + + if (out_mv > max_mv) { + tcpm_log(port, "Invalid PPS APDO selected!"); + return -EINVAL; + } + } + } + + tcpm_log(port, "cc=%d cc1=%d cc2=%d vbus=%d vconn=%s polarity=%d", + port->cc_req, port->cc1, port->cc2, port->vbus_source, + port->vconn_role == TYPEC_SOURCE ? "source" : "sink", + port->polarity); + + *rdo = RDO_PROG(src_pdo_index + 1, out_mv, op_ma, flags); + + tcpm_log(port, "Requesting APDO %d: %u mV, %u mA", + src_pdo_index, out_mv, op_ma); + + port->pps_data.op_curr = op_ma; + port->pps_data.out_volt = out_mv; + + return 0; +} + +static int tcpm_pd_send_pps_request(struct tcpm_port *port) +{ + struct pd_message msg; + int ret; + u32 rdo; + + ret = tcpm_pd_build_pps_request(port, &rdo); + if (ret < 0) + return ret; + + memset(&msg, 0, sizeof(msg)); + msg.header = PD_HEADER_LE(PD_DATA_REQUEST, + port->pwr_role, + port->data_role, + port->negotiated_rev, port->message_id, 1); msg.payload[0] = cpu_to_le32(rdo); @@ -2077,6 +2580,7 @@ static void tcpm_reset_port(struct tcpm_port *port) tcpm_typec_disconnect(port); port->attached = false; port->pd_capable = false; + port->pps_data.supported = false; /* * First Rx ID should be 0; set this to a sentinel of -1 so that @@ -2094,6 +2598,11 @@ static void tcpm_reset_port(struct tcpm_port *port) tcpm_set_attached_state(port, false); port->try_src_count = 0; port->try_snk_count = 0; + port->supply_voltage = 0; + port->current_limit = 0; + port->usb_type = POWER_SUPPLY_USB_TYPE_C; + + power_supply_changed(port->psy); } static void tcpm_detach(struct tcpm_port *port) @@ -2181,14 +2690,6 @@ static inline enum tcpm_state hard_reset_state(struct tcpm_port *port) return SNK_UNATTACHED; } -static inline enum tcpm_state ready_state(struct tcpm_port *port) -{ - if (port->pwr_role == TYPEC_SOURCE) - return SRC_READY; - else - return SNK_READY; -} - static inline enum tcpm_state unattached_state(struct tcpm_port *port) { if (port->port_type == TYPEC_PORT_DRP) { @@ -2338,6 +2839,7 @@ static void run_state_machine(struct tcpm_port *port) typec_set_pwr_opmode(port->typec_port, opmode); port->pwr_opmode = TYPEC_PWR_MODE_USB; port->caps_count = 0; + port->negotiated_rev = PD_MAX_REV; port->message_id = 0; port->rx_msgid = -1; port->explicit_contract = false; @@ -2398,6 +2900,7 @@ static void run_state_machine(struct tcpm_port *port) tcpm_swap_complete(port, 0); tcpm_typec_connect(port); + tcpm_check_send_discover(port); /* * 6.3.5 @@ -2421,6 +2924,7 @@ static void run_state_machine(struct tcpm_port *port) case SNK_UNATTACHED: if (!port->non_pd_role_swap) tcpm_swap_complete(port, -ENOTCONN); + tcpm_pps_complete(port, -ENOTCONN); tcpm_snk_detach(port); if (tcpm_start_drp_toggling(port)) { tcpm_set_state(port, DRP_TOGGLING, 0); @@ -2510,6 +3014,7 @@ static void run_state_machine(struct tcpm_port *port) port->cc2 : port->cc1); typec_set_pwr_opmode(port->typec_port, opmode); port->pwr_opmode = TYPEC_PWR_MODE_USB; + port->negotiated_rev = PD_MAX_REV; port->message_id = 0; port->rx_msgid = -1; port->explicit_contract = false; @@ -2580,6 +3085,24 @@ static void run_state_machine(struct tcpm_port *port) PD_T_SENDER_RESPONSE); } break; + case SNK_NEGOTIATE_PPS_CAPABILITIES: + ret = tcpm_pd_send_pps_request(port); + if (ret < 0) { + port->pps_status = ret; + /* + * If this was called due to updates to sink + * capabilities, and pps is no longer valid, we should + * safely fall back to a standard PDO. + */ + if (port->update_sink_caps) + tcpm_set_state(port, SNK_NEGOTIATE_CAPABILITIES, 0); + else + tcpm_set_state(port, SNK_READY, 0); + } else { + tcpm_set_state_cond(port, hard_reset_state(port), + PD_T_SENDER_RESPONSE); + } + break; case SNK_TRANSITION_SINK: case SNK_TRANSITION_SINK_VBUS: tcpm_set_state(port, hard_reset_state(port), @@ -2587,6 +3110,7 @@ static void run_state_machine(struct tcpm_port *port) break; case SNK_READY: port->try_snk_count = 0; + port->update_sink_caps = false; if (port->explicit_contract) { typec_set_pwr_opmode(port->typec_port, TYPEC_PWR_MODE_PD); @@ -2596,6 +3120,10 @@ static void run_state_machine(struct tcpm_port *port) tcpm_swap_complete(port, 0); tcpm_typec_connect(port); tcpm_check_send_discover(port); + tcpm_pps_complete(port, port->pps_status); + + power_supply_changed(port->psy); + break; /* Accessory states */ @@ -2642,6 +3170,7 @@ static void run_state_machine(struct tcpm_port *port) tcpm_set_state(port, SRC_UNATTACHED, PD_T_PS_SOURCE_ON); break; case SNK_HARD_RESET_SINK_OFF: + memset(&port->pps_data, 0, sizeof(port->pps_data)); tcpm_set_vconn(port, false); tcpm_set_charge(port, false); tcpm_set_roles(port, false, TYPEC_SINK, TYPEC_DEVICE); @@ -2860,8 +3389,25 @@ static void run_state_machine(struct tcpm_port *port) /* Always switch to unattached state */ tcpm_set_state(port, unattached_state(port), 0); break; + case GET_STATUS_SEND: + tcpm_pd_send_control(port, PD_CTRL_GET_STATUS); + tcpm_set_state(port, GET_STATUS_SEND_TIMEOUT, + PD_T_SENDER_RESPONSE); + break; + case GET_STATUS_SEND_TIMEOUT: + tcpm_set_state(port, ready_state(port), 0); + break; + case GET_PPS_STATUS_SEND: + tcpm_pd_send_control(port, PD_CTRL_GET_PPS_STATUS); + tcpm_set_state(port, GET_PPS_STATUS_SEND_TIMEOUT, + PD_T_SENDER_RESPONSE); + break; + case GET_PPS_STATUS_SEND_TIMEOUT: + tcpm_set_state(port, ready_state(port), 0); + break; case ERROR_RECOVERY: tcpm_swap_complete(port, -EPROTO); + tcpm_pps_complete(port, -EPROTO); tcpm_set_state(port, PORT_RESET, 0); break; case PORT_RESET: @@ -3444,6 +3990,162 @@ static int tcpm_try_role(const struct typec_capability *cap, int role) return ret; } +static int tcpm_pps_set_op_curr(struct tcpm_port *port, u16 op_curr) +{ + unsigned int target_mw; + int ret; + + mutex_lock(&port->swap_lock); + mutex_lock(&port->lock); + + if (!port->pps_data.active) { + ret = -EOPNOTSUPP; + goto port_unlock; + } + + if (port->state != SNK_READY) { + ret = -EAGAIN; + goto port_unlock; + } + + if (op_curr > port->pps_data.max_curr) { + ret = -EINVAL; + goto port_unlock; + } + + target_mw = (op_curr * port->pps_data.out_volt) / 1000; + if (target_mw < port->operating_snk_mw) { + ret = -EINVAL; + goto port_unlock; + } + + reinit_completion(&port->pps_complete); + port->pps_data.op_curr = op_curr; + port->pps_status = 0; + port->pps_pending = true; + tcpm_set_state(port, SNK_NEGOTIATE_PPS_CAPABILITIES, 0); + mutex_unlock(&port->lock); + + if (!wait_for_completion_timeout(&port->pps_complete, + msecs_to_jiffies(PD_PPS_CTRL_TIMEOUT))) + ret = -ETIMEDOUT; + else + ret = port->pps_status; + + goto swap_unlock; + +port_unlock: + mutex_unlock(&port->lock); +swap_unlock: + mutex_unlock(&port->swap_lock); + + return ret; +} + +static int tcpm_pps_set_out_volt(struct tcpm_port *port, u16 out_volt) +{ + unsigned int target_mw; + int ret; + + mutex_lock(&port->swap_lock); + mutex_lock(&port->lock); + + if (!port->pps_data.active) { + ret = -EOPNOTSUPP; + goto port_unlock; + } + + if (port->state != SNK_READY) { + ret = -EAGAIN; + goto port_unlock; + } + + if (out_volt < port->pps_data.min_volt || + out_volt > port->pps_data.max_volt) { + ret = -EINVAL; + goto port_unlock; + } + + target_mw = (port->pps_data.op_curr * out_volt) / 1000; + if (target_mw < port->operating_snk_mw) { + ret = -EINVAL; + goto port_unlock; + } + + reinit_completion(&port->pps_complete); + port->pps_data.out_volt = out_volt; + port->pps_status = 0; + port->pps_pending = true; + tcpm_set_state(port, SNK_NEGOTIATE_PPS_CAPABILITIES, 0); + mutex_unlock(&port->lock); + + if (!wait_for_completion_timeout(&port->pps_complete, + msecs_to_jiffies(PD_PPS_CTRL_TIMEOUT))) + ret = -ETIMEDOUT; + else + ret = port->pps_status; + + goto swap_unlock; + +port_unlock: + mutex_unlock(&port->lock); +swap_unlock: + mutex_unlock(&port->swap_lock); + + return ret; +} + +static int tcpm_pps_activate(struct tcpm_port *port, bool activate) +{ + int ret = 0; + + mutex_lock(&port->swap_lock); + mutex_lock(&port->lock); + + if (!port->pps_data.supported) { + ret = -EOPNOTSUPP; + goto port_unlock; + } + + /* Trying to deactivate PPS when already deactivated so just bail */ + if (!port->pps_data.active && !activate) + goto port_unlock; + + if (port->state != SNK_READY) { + ret = -EAGAIN; + goto port_unlock; + } + + reinit_completion(&port->pps_complete); + port->pps_status = 0; + port->pps_pending = true; + + /* Trigger PPS request or move back to standard PDO contract */ + if (activate) { + port->pps_data.out_volt = port->supply_voltage; + port->pps_data.op_curr = port->current_limit; + tcpm_set_state(port, SNK_NEGOTIATE_PPS_CAPABILITIES, 0); + } else { + tcpm_set_state(port, SNK_NEGOTIATE_CAPABILITIES, 0); + } + mutex_unlock(&port->lock); + + if (!wait_for_completion_timeout(&port->pps_complete, + msecs_to_jiffies(PD_PPS_CTRL_TIMEOUT))) + ret = -ETIMEDOUT; + else + ret = port->pps_status; + + goto swap_unlock; + +port_unlock: + mutex_unlock(&port->lock); +swap_unlock: + mutex_unlock(&port->swap_lock); + + return ret; +} + static void tcpm_init(struct tcpm_port *port) { enum typec_cc_status cc1, cc2; @@ -3569,9 +4271,6 @@ EXPORT_SYMBOL_GPL(tcpm_update_source_capabilities); int tcpm_update_sink_capabilities(struct tcpm_port *port, const u32 *pdo, unsigned int nr_pdo, - unsigned int max_snk_mv, - unsigned int max_snk_ma, - unsigned int max_snk_mw, unsigned int operating_snk_mw) { if (tcpm_validate_caps(port, pdo, nr_pdo)) @@ -3579,17 +4278,19 @@ int tcpm_update_sink_capabilities(struct tcpm_port *port, const u32 *pdo, mutex_lock(&port->lock); port->nr_snk_pdo = tcpm_copy_pdos(port->snk_pdo, pdo, nr_pdo); - port->max_snk_mv = max_snk_mv; - port->max_snk_ma = max_snk_ma; - port->max_snk_mw = max_snk_mw; port->operating_snk_mw = operating_snk_mw; + port->update_sink_caps = true; switch (port->state) { case SNK_NEGOTIATE_CAPABILITIES: + case SNK_NEGOTIATE_PPS_CAPABILITIES: case SNK_READY: case SNK_TRANSITION_SINK: case SNK_TRANSITION_SINK_VBUS: - tcpm_set_state(port, SNK_NEGOTIATE_CAPABILITIES, 0); + if (port->pps_data.active) + tcpm_set_state(port, SNK_NEGOTIATE_PPS_CAPABILITIES, 0); + else + tcpm_set_state(port, SNK_NEGOTIATE_CAPABILITIES, 0); break; default: break; @@ -3599,6 +4300,230 @@ int tcpm_update_sink_capabilities(struct tcpm_port *port, const u32 *pdo, } EXPORT_SYMBOL_GPL(tcpm_update_sink_capabilities); +/* Power Supply access to expose source power information */ +enum tcpm_psy_online_states { + TCPM_PSY_OFFLINE = 0, + TCPM_PSY_FIXED_ONLINE, + TCPM_PSY_PROG_ONLINE, +}; + +static enum power_supply_property tcpm_psy_props[] = { + POWER_SUPPLY_PROP_USB_TYPE, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_MIN, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_CURRENT_NOW, +}; + +static int tcpm_psy_get_online(struct tcpm_port *port, + union power_supply_propval *val) +{ + if (port->vbus_charge) { + if (port->pps_data.active) + val->intval = TCPM_PSY_PROG_ONLINE; + else + val->intval = TCPM_PSY_FIXED_ONLINE; + } else { + val->intval = TCPM_PSY_OFFLINE; + } + + return 0; +} + +static int tcpm_psy_get_voltage_min(struct tcpm_port *port, + union power_supply_propval *val) +{ + if (port->pps_data.active) + val->intval = port->pps_data.min_volt * 1000; + else + val->intval = port->supply_voltage * 1000; + + return 0; +} + +static int tcpm_psy_get_voltage_max(struct tcpm_port *port, + union power_supply_propval *val) +{ + if (port->pps_data.active) + val->intval = port->pps_data.max_volt * 1000; + else + val->intval = port->supply_voltage * 1000; + + return 0; +} + +static int tcpm_psy_get_voltage_now(struct tcpm_port *port, + union power_supply_propval *val) +{ + val->intval = port->supply_voltage * 1000; + + return 0; +} + +static int tcpm_psy_get_current_max(struct tcpm_port *port, + union power_supply_propval *val) +{ + if (port->pps_data.active) + val->intval = port->pps_data.max_curr * 1000; + else + val->intval = port->current_limit * 1000; + + return 0; +} + +static int tcpm_psy_get_current_now(struct tcpm_port *port, + union power_supply_propval *val) +{ + val->intval = port->current_limit * 1000; + + return 0; +} + +static int tcpm_psy_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct tcpm_port *port = power_supply_get_drvdata(psy); + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_USB_TYPE: + val->intval = port->usb_type; + break; + case POWER_SUPPLY_PROP_ONLINE: + ret = tcpm_psy_get_online(port, val); + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN: + ret = tcpm_psy_get_voltage_min(port, val); + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + ret = tcpm_psy_get_voltage_max(port, val); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = tcpm_psy_get_voltage_now(port, val); + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + ret = tcpm_psy_get_current_max(port, val); + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + ret = tcpm_psy_get_current_now(port, val); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int tcpm_psy_set_online(struct tcpm_port *port, + const union power_supply_propval *val) +{ + int ret; + + switch (val->intval) { + case TCPM_PSY_FIXED_ONLINE: + ret = tcpm_pps_activate(port, false); + break; + case TCPM_PSY_PROG_ONLINE: + ret = tcpm_pps_activate(port, true); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int tcpm_psy_set_prop(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct tcpm_port *port = power_supply_get_drvdata(psy); + int ret; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + ret = tcpm_psy_set_online(port, val); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + if (val->intval < port->pps_data.min_volt * 1000 || + val->intval > port->pps_data.max_volt * 1000) + ret = -EINVAL; + else + ret = tcpm_pps_set_out_volt(port, val->intval / 1000); + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + if (val->intval > port->pps_data.max_curr * 1000) + ret = -EINVAL; + else + ret = tcpm_pps_set_op_curr(port, val->intval / 1000); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int tcpm_psy_prop_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + case POWER_SUPPLY_PROP_CURRENT_NOW: + return 1; + default: + return 0; + } +} + +static enum power_supply_usb_type tcpm_psy_usb_types[] = { + POWER_SUPPLY_USB_TYPE_C, + POWER_SUPPLY_USB_TYPE_PD, + POWER_SUPPLY_USB_TYPE_PD_PPS, +}; + +static const char *tcpm_psy_name_prefix = "tcpm-source-psy-"; + +static int devm_tcpm_psy_register(struct tcpm_port *port) +{ + struct power_supply_config psy_cfg = {}; + const char *port_dev_name = dev_name(port->dev); + size_t psy_name_len = strlen(tcpm_psy_name_prefix) + + strlen(port_dev_name) + 1; + char *psy_name; + + psy_cfg.drv_data = port; + psy_name = devm_kzalloc(port->dev, psy_name_len, GFP_KERNEL); + if (!psy_name) + return -ENOMEM; + + snprintf(psy_name, psy_name_len, "%s%s", tcpm_psy_name_prefix, + port_dev_name); + port->psy_desc.name = psy_name; + port->psy_desc.type = POWER_SUPPLY_TYPE_USB, + port->psy_desc.usb_types = tcpm_psy_usb_types; + port->psy_desc.num_usb_types = ARRAY_SIZE(tcpm_psy_usb_types); + port->psy_desc.properties = tcpm_psy_props, + port->psy_desc.num_properties = ARRAY_SIZE(tcpm_psy_props), + port->psy_desc.get_property = tcpm_psy_get_prop, + port->psy_desc.set_property = tcpm_psy_set_prop, + port->psy_desc.property_is_writeable = tcpm_psy_prop_writeable, + + port->usb_type = POWER_SUPPLY_USB_TYPE_C; + + port->psy = devm_power_supply_register(port->dev, &port->psy_desc, + &psy_cfg); + + return PTR_ERR_OR_ZERO(port->psy); +} + struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) { struct tcpm_port *port; @@ -3631,6 +4556,7 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) init_completion(&port->tx_complete); init_completion(&port->swap_complete); + init_completion(&port->pps_complete); tcpm_debugfs_init(port); if (tcpm_validate_caps(port, tcpc->config->src_pdo, @@ -3647,9 +4573,6 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) port->nr_snk_vdo = tcpm_copy_vdos(port->snk_vdo, tcpc->config->snk_vdo, tcpc->config->nr_snk_vdo); - port->max_snk_mv = tcpc->config->max_snk_mv; - port->max_snk_ma = tcpc->config->max_snk_ma; - port->max_snk_mw = tcpc->config->max_snk_mw; port->operating_snk_mw = tcpc->config->operating_snk_mw; if (!tcpc->config->try_role_hw) port->try_role = tcpc->config->default_role; @@ -3660,7 +4583,7 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) port->typec_caps.type = tcpc->config->type; port->typec_caps.data = tcpc->config->data; port->typec_caps.revision = 0x0120; /* Type-C spec release 1.2 */ - port->typec_caps.pd_revision = 0x0200; /* USB-PD spec release 2.0 */ + port->typec_caps.pd_revision = 0x0300; /* USB-PD spec release 3.0 */ port->typec_caps.dr_set = tcpm_dr_set; port->typec_caps.pr_set = tcpm_pr_set; port->typec_caps.vconn_set = tcpm_vconn_set; @@ -3676,6 +4599,10 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) goto out_destroy_wq; } + err = devm_tcpm_psy_register(port); + if (err) + goto out_destroy_wq; + port->typec_port = typec_register_port(port->dev, &port->typec_caps); if (IS_ERR(port->typec_port)) { err = PTR_ERR(port->typec_port); diff --git a/drivers/usb/typec/typec_wcove.c b/drivers/usb/typec/typec_wcove.c index 19cca7f1b2c5..39cff11ec7a2 100644 --- a/drivers/usb/typec/typec_wcove.c +++ b/drivers/usb/typec/typec_wcove.c @@ -558,6 +558,7 @@ static const u32 src_pdo[] = { static const u32 snk_pdo[] = { PDO_FIXED(5000, 500, PDO_FIXED_DUAL_ROLE | PDO_FIXED_DATA_SWAP | PDO_FIXED_USB_COMM), + PDO_VAR(5000, 12000, 3000), }; static struct tcpc_config wcove_typec_config = { @@ -566,9 +567,6 @@ static struct tcpc_config wcove_typec_config = { .snk_pdo = snk_pdo, .nr_snk_pdo = ARRAY_SIZE(snk_pdo), - .max_snk_mv = 12000, - .max_snk_ma = 3000, - .max_snk_mw = 36000, .operating_snk_mw = 15000, .type = TYPEC_PORT_DRP, diff --git a/drivers/usb/usbip/stub_dev.c b/drivers/usb/usbip/stub_dev.c index dd8ef36ab10e..7813c1862941 100644 --- a/drivers/usb/usbip/stub_dev.c +++ b/drivers/usb/usbip/stub_dev.c @@ -302,7 +302,7 @@ static int stub_probe(struct usb_device *udev) struct bus_id_priv *busid_priv; int rc; - dev_dbg(&udev->dev, "Enter\n"); + dev_dbg(&udev->dev, "Enter probe\n"); /* check we should claim or not by busid_table */ busid_priv = get_busid_priv(udev_busid); @@ -404,7 +404,7 @@ static void stub_disconnect(struct usb_device *udev) struct bus_id_priv *busid_priv; int rc; - dev_dbg(&udev->dev, "Enter\n"); + dev_dbg(&udev->dev, "Enter disconnect\n"); busid_priv = get_busid_priv(udev_busid); if (!busid_priv) { diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index f0139b460a72..0c9a572a1eb8 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -145,6 +145,7 @@ enum power_supply_property { POWER_SUPPLY_PROP_TIME_TO_FULL_NOW, POWER_SUPPLY_PROP_TIME_TO_FULL_AVG, POWER_SUPPLY_PROP_TYPE, /* use power_supply.type instead */ + POWER_SUPPLY_PROP_USB_TYPE, POWER_SUPPLY_PROP_SCOPE, POWER_SUPPLY_PROP_PRECHARGE_CURRENT, POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, @@ -170,6 +171,19 @@ enum power_supply_type { POWER_SUPPLY_TYPE_APPLE_BRICK_ID, /* Apple Charging Method */ }; +enum power_supply_usb_type { + POWER_SUPPLY_USB_TYPE_UNKNOWN = 0, + POWER_SUPPLY_USB_TYPE_SDP, /* Standard Downstream Port */ + POWER_SUPPLY_USB_TYPE_DCP, /* Dedicated Charging Port */ + POWER_SUPPLY_USB_TYPE_CDP, /* Charging Downstream Port */ + POWER_SUPPLY_USB_TYPE_ACA, /* Accessory Charger Adapters */ + POWER_SUPPLY_USB_TYPE_C, /* Type C Port */ + POWER_SUPPLY_USB_TYPE_PD, /* Power Delivery Port */ + POWER_SUPPLY_USB_TYPE_PD_DRP, /* PD Dual Role Port */ + POWER_SUPPLY_USB_TYPE_PD_PPS, /* PD Programmable Power Supply */ + POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID, /* Apple Charging Method */ +}; + enum power_supply_notifier_events { PSY_EVENT_PROP_CHANGED, }; @@ -196,6 +210,8 @@ struct power_supply_config { struct power_supply_desc { const char *name; enum power_supply_type type; + enum power_supply_usb_type *usb_types; + size_t num_usb_types; enum power_supply_property *properties; size_t num_properties; diff --git a/include/linux/usb.h b/include/linux/usb.h index 0173597e59aa..beffceec4915 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -551,6 +551,8 @@ struct usb3_lpm_parameters { * @route: tree topology hex string for use with xHCI * @state: device state: configured, not attached, etc. * @speed: device speed: high/full/low (or error) + * @rx_lanes: number of rx lanes in use, USB 3.2 adds dual-lane support + * @tx_lanes: number of tx lanes in use, USB 3.2 adds dual-lane support * @tt: Transaction Translator info; used with low/full speed dev, highspeed hub * @ttport: device port on that tt hub * @toggle: one bit for each endpoint, with ([0] = IN, [1] = OUT) endpoints @@ -624,6 +626,8 @@ struct usb_device { u32 route; enum usb_device_state state; enum usb_device_speed speed; + unsigned int rx_lanes; + unsigned int tx_lanes; struct usb_tt *tt; int ttport; diff --git a/include/linux/usb/audio-v2.h b/include/linux/usb/audio-v2.h index aaafecf073ff..49699255cfd3 100644 --- a/include/linux/usb/audio-v2.h +++ b/include/linux/usb/audio-v2.h @@ -94,7 +94,7 @@ struct uac_clock_selector_descriptor { __u8 bClockID; __u8 bNrInPins; __u8 baCSourceID[]; - /* bmControls, bAssocTerminal and iClockSource omitted */ + /* bmControls and iClockSource omitted */ } __attribute__((packed)); /* 4.7.2.3 Clock Multiplier Descriptor */ diff --git a/include/linux/usb/hcd.h b/include/linux/usb/hcd.h index aef50cb2ed1b..34a6ded6f319 100644 --- a/include/linux/usb/hcd.h +++ b/include/linux/usb/hcd.h @@ -150,7 +150,6 @@ struct usb_hcd { unsigned rh_pollable:1; /* may we poll the root hub? */ unsigned msix_enabled:1; /* driver has MSI-X enabled? */ unsigned msi_enabled:1; /* driver has MSI enabled? */ - unsigned remove_phy:1; /* auto-remove USB phy */ /* * do not manage the PHY state in the HCD core, instead let the driver * handle this (for example if the PHY can only be turned on after a @@ -261,6 +260,7 @@ struct hc_driver { #define HCD_USB25 0x0030 /* Wireless USB 1.0 (USB 2.5)*/ #define HCD_USB3 0x0040 /* USB 3.0 */ #define HCD_USB31 0x0050 /* USB 3.1 */ +#define HCD_USB32 0x0060 /* USB 3.2 */ #define HCD_MASK 0x0070 #define HCD_BH 0x0100 /* URB complete in BH context */ diff --git a/include/linux/usb/pd.h b/include/linux/usb/pd.h index ff359bdfdc7b..09b570feb297 100644 --- a/include/linux/usb/pd.h +++ b/include/linux/usb/pd.h @@ -103,8 +103,8 @@ enum pd_ext_msg_type { (((cnt) & PD_HEADER_CNT_MASK) << PD_HEADER_CNT_SHIFT) | \ ((ext_hdr) ? PD_HEADER_EXT_HDR : 0)) -#define PD_HEADER_LE(type, pwr, data, id, cnt) \ - cpu_to_le16(PD_HEADER((type), (pwr), (data), PD_REV20, (id), (cnt), (0))) +#define PD_HEADER_LE(type, pwr, data, rev, id, cnt) \ + cpu_to_le16(PD_HEADER((type), (pwr), (data), (rev), (id), (cnt), (0))) static inline unsigned int pd_header_cnt(u16 header) { diff --git a/include/linux/usb/phy.h b/include/linux/usb/phy.h index b7a2625947f5..e4de6bc1f69b 100644 --- a/include/linux/usb/phy.h +++ b/include/linux/usb/phy.h @@ -157,22 +157,6 @@ struct usb_phy { enum usb_charger_type (*charger_detect)(struct usb_phy *x); }; -/** - * struct usb_phy_bind - represent the binding for the phy - * @dev_name: the device name of the device that will bind to the phy - * @phy_dev_name: the device name of the phy - * @index: used if a single controller uses multiple phys - * @phy: reference to the phy - * @list: to maintain a linked list of the binding information - */ -struct usb_phy_bind { - const char *dev_name; - const char *phy_dev_name; - u8 index; - struct usb_phy *phy; - struct list_head list; -}; - /* for board-specific init logic */ extern int usb_add_phy(struct usb_phy *, enum usb_phy_type type); extern int usb_add_phy_dev(struct usb_phy *); @@ -234,16 +218,12 @@ usb_phy_vbus_off(struct usb_phy *x) extern struct usb_phy *usb_get_phy(enum usb_phy_type type); extern struct usb_phy *devm_usb_get_phy(struct device *dev, enum usb_phy_type type); -extern struct usb_phy *usb_get_phy_dev(struct device *dev, u8 index); -extern struct usb_phy *devm_usb_get_phy_dev(struct device *dev, u8 index); extern struct usb_phy *devm_usb_get_phy_by_phandle(struct device *dev, const char *phandle, u8 index); extern struct usb_phy *devm_usb_get_phy_by_node(struct device *dev, struct device_node *node, struct notifier_block *nb); extern void usb_put_phy(struct usb_phy *); extern void devm_usb_put_phy(struct device *dev, struct usb_phy *x); -extern int usb_bind_phy(const char *dev_name, u8 index, - const char *phy_dev_name); extern void usb_phy_set_event(struct usb_phy *x, unsigned long event); extern void usb_phy_set_charger_current(struct usb_phy *usb_phy, unsigned int mA); @@ -263,16 +243,6 @@ static inline struct usb_phy *devm_usb_get_phy(struct device *dev, return ERR_PTR(-ENXIO); } -static inline struct usb_phy *usb_get_phy_dev(struct device *dev, u8 index) -{ - return ERR_PTR(-ENXIO); -} - -static inline struct usb_phy *devm_usb_get_phy_dev(struct device *dev, u8 index) -{ - return ERR_PTR(-ENXIO); -} - static inline struct usb_phy *devm_usb_get_phy_by_phandle(struct device *dev, const char *phandle, u8 index) { @@ -293,12 +263,6 @@ static inline void devm_usb_put_phy(struct device *dev, struct usb_phy *x) { } -static inline int usb_bind_phy(const char *dev_name, u8 index, - const char *phy_dev_name) -{ - return -EOPNOTSUPP; -} - static inline void usb_phy_set_event(struct usb_phy *x, unsigned long event) { } diff --git a/include/linux/usb/tcpm.h b/include/linux/usb/tcpm.h index f0d839daeaea..b231b9314240 100644 --- a/include/linux/usb/tcpm.h +++ b/include/linux/usb/tcpm.h @@ -36,6 +36,7 @@ enum typec_cc_polarity { /* Time to wait for TCPC to complete transmit */ #define PD_T_TCPC_TX_TIMEOUT 100 /* in ms */ #define PD_ROLE_SWAP_TIMEOUT (MSEC_PER_SEC * 10) +#define PD_PPS_CTRL_TIMEOUT (MSEC_PER_SEC * 10) enum tcpm_transmit_status { TCPC_TX_SUCCESS = 0, @@ -62,9 +63,6 @@ enum tcpm_transmit_type { * @snk_pdo: PDO parameters sent to partner as response to * PD_CTRL_GET_SINK_CAP message * @nr_snk_pdo: Number of entries in @snk_pdo - * @max_snk_mv: Maximum acceptable sink voltage in mV - * @max_snk_ma: Maximum sink current in mA - * @max_snk_mw: Maximum required sink power in mW * @operating_snk_mw: * Required operating sink power in mW * @type: Port type (TYPEC_PORT_DFP, TYPEC_PORT_UFP, or @@ -85,9 +83,6 @@ struct tcpc_config { const u32 *snk_vdo; unsigned int nr_snk_vdo; - unsigned int max_snk_mv; - unsigned int max_snk_ma; - unsigned int max_snk_mw; unsigned int operating_snk_mw; enum typec_port_type type; @@ -174,9 +169,6 @@ int tcpm_update_source_capabilities(struct tcpm_port *port, const u32 *pdo, unsigned int nr_pdo); int tcpm_update_sink_capabilities(struct tcpm_port *port, const u32 *pdo, unsigned int nr_pdo, - unsigned int max_snk_mv, - unsigned int max_snk_ma, - unsigned int max_snk_mw, unsigned int operating_snk_mw); void tcpm_vbus_change(struct tcpm_port *port); diff --git a/include/linux/usb/tegra_usb_phy.h b/include/linux/usb/tegra_usb_phy.h index d641ea1660b7..0c5c3ea8b2d7 100644 --- a/include/linux/usb/tegra_usb_phy.h +++ b/include/linux/usb/tegra_usb_phy.h @@ -17,6 +17,7 @@ #define __TEGRA_USB_PHY_H #include <linux/clk.h> +#include <linux/reset.h> #include <linux/usb/otg.h> /* @@ -76,6 +77,7 @@ struct tegra_usb_phy { bool is_legacy_phy; bool is_ulpi_phy; int reset_gpio; + struct reset_control *pad_rst; }; void tegra_usb_phy_preresume(struct usb_phy *phy); diff --git a/include/uapi/linux/usb/ch11.h b/include/uapi/linux/usb/ch11.h index 29c120c88747..fb0cd24c392c 100644 --- a/include/uapi/linux/usb/ch11.h +++ b/include/uapi/linux/usb/ch11.h @@ -197,6 +197,11 @@ struct usb_port_status { #define USB_EXT_PORT_STAT_RX_LANES 0x00000f00 #define USB_EXT_PORT_STAT_TX_LANES 0x0000f000 +#define USB_EXT_PORT_RX_LANES(p) \ + (((p) & USB_EXT_PORT_STAT_RX_LANES) >> 8) +#define USB_EXT_PORT_TX_LANES(p) \ + (((p) & USB_EXT_PORT_STAT_TX_LANES) >> 12) + /* * wHubCharacteristics (masks) * See USB 2.0 spec Table 11-13, offset 3 |