From f3d96f8d23d8e6d0b7642ee946b9b2ac3418fb4d Mon Sep 17 00:00:00 2001 From: Icenowy Zheng Date: Wed, 3 Jan 2018 16:49:44 +0800 Subject: phy: sun4i-usb: add support for R40 USB PHY Allwinner R40 features a USB PHY like the one in A64, but with 3 PHYs. Add support for it. Signed-off-by: Icenowy Zheng Acked-by: Maxime Ripard Acked-by: Rob Herring Tested-by: Hermann Lauer Signed-off-by: Kishon Vijay Abraham I --- Documentation/devicetree/bindings/phy/sun4i-usb-phy.txt | 1 + 1 file changed, 1 insertion(+) (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/phy/sun4i-usb-phy.txt b/Documentation/devicetree/bindings/phy/sun4i-usb-phy.txt index c1ce5a0a652e..07ca4ec4a745 100644 --- a/Documentation/devicetree/bindings/phy/sun4i-usb-phy.txt +++ b/Documentation/devicetree/bindings/phy/sun4i-usb-phy.txt @@ -11,6 +11,7 @@ Required properties: * allwinner,sun8i-a33-usb-phy * allwinner,sun8i-a83t-usb-phy * allwinner,sun8i-h3-usb-phy + * allwinner,sun8i-r40-usb-phy * allwinner,sun8i-v3s-usb-phy * allwinner,sun50i-a64-usb-phy - reg : a list of offset + length pairs -- cgit v1.2.3 From 94ccae556741b3b1e2e526ee41426ed890500cd5 Mon Sep 17 00:00:00 2001 From: Shawn Guo Date: Wed, 24 Jan 2018 13:47:35 +0800 Subject: dt-bindings: hisilicon: add doc for Hi3798CV200 peripheral controller It adds bindings doc for Hi3798CV200 peripheral controller. Signed-off-by: Shawn Guo Signed-off-by: Kishon Vijay Abraham I --- .../bindings/arm/hisilicon/hisilicon.txt | 23 ++++++++++++++++++++++ 1 file changed, 23 insertions(+) (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/arm/hisilicon/hisilicon.txt b/Documentation/devicetree/bindings/arm/hisilicon/hisilicon.txt index 7111fbc82a4e..199cd36fe1ba 100644 --- a/Documentation/devicetree/bindings/arm/hisilicon/hisilicon.txt +++ b/Documentation/devicetree/bindings/arm/hisilicon/hisilicon.txt @@ -74,6 +74,29 @@ Example: reboot-offset = <0x4>; }; +----------------------------------------------------------------------- +Hisilicon Hi3798CV200 Peripheral Controller + +The Hi3798CV200 Peripheral Controller controls peripherals, queries +their status, and configures some functions of peripherals. + +Required properties: +- compatible: Should contain "hisilicon,hi3798cv200-perictrl", "syscon" + and "simple-mfd". +- reg: Register address and size of Peripheral Controller. +- #address-cells: Should be 1. +- #size-cells: Should be 1. + +Examples: + + perictrl: peripheral-controller@8a20000 { + compatible = "hisilicon,hi3798cv200-perictrl", "syscon", + "simple-mfd"; + reg = <0x8a20000 0x1000>; + #address-cells = <1>; + #size-cells = <1>; + }; + ----------------------------------------------------------------------- Hisilicon Hi6220 system controller -- cgit v1.2.3 From 5669f91a086ca377a5f5b15885d03482435006d6 Mon Sep 17 00:00:00 2001 From: Jianguo Sun Date: Wed, 24 Jan 2018 13:47:36 +0800 Subject: dt-bindings: add bindings doc for hi3798cv200 combphy It adds the device tree bindings for PCIE/SATA/USB3 combo PHY found on HiSilicon STB SoCs. Signed-off-by: Jianguo Sun Signed-off-by: Shawn Guo Acked-by: Rob Herring Signed-off-by: Kishon Vijay Abraham I --- .../bindings/phy/phy-hi3798cv200-combphy.txt | 59 ++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 Documentation/devicetree/bindings/phy/phy-hi3798cv200-combphy.txt (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/phy/phy-hi3798cv200-combphy.txt b/Documentation/devicetree/bindings/phy/phy-hi3798cv200-combphy.txt new file mode 100644 index 000000000000..17b0c761370a --- /dev/null +++ b/Documentation/devicetree/bindings/phy/phy-hi3798cv200-combphy.txt @@ -0,0 +1,59 @@ +HiSilicon STB PCIE/SATA/USB3 PHY + +Required properties: +- compatible: Should be "hisilicon,hi3798cv200-combphy" +- reg: Should be the address space for COMBPHY configuration and state + registers in peripheral controller, e.g. PERI_COMBPHY0_CFG and + PERI_COMBPHY0_STATE for COMBPHY0 Hi3798CV200 SoC. +- #phy-cells: Should be 1. The cell number is used to select the phy mode + as defined in . +- clocks: The phandle to clock provider and clock specifier pair. +- resets: The phandle to reset controller and reset specifier pair. + +Refer to phy/phy-bindings.txt for the generic PHY binding properties. + +Optional properties: +- hisilicon,fixed-mode: If the phy device doesn't support mode select + but a fixed mode setting, the property should be present to specify + the particular mode. +- hisilicon,mode-select-bits: If the phy device support mode select, + this property should be present to specify the register bits in + peripheral controller, as a 3 integers tuple: + . + +Notes: +- Between hisilicon,fixed-mode and hisilicon,mode-select-bits, one and only + one of them should be present. +- The device node should be a child of peripheral controller that contains + COMBPHY configuration/state and PERI_CTRL register used to select PHY mode. + Refer to arm/hisilicon/hisilicon.txt for the parent peripheral controller + bindings. + +Examples: + +perictrl: peripheral-controller@8a20000 { + compatible = "hisilicon,hi3798cv200-perictrl", "syscon", + "simple-mfd"; + reg = <0x8a20000 0x1000>; + #address-cells = <1>; + #size-cells = <1>; + ranges = <0x0 0x8a20000 0x1000>; + + combphy0: phy@850 { + compatible = "hisilicon,hi3798cv200-combphy"; + reg = <0x850 0x8>; + #phy-cells = <1>; + clocks = <&crg HISTB_COMBPHY0_CLK>; + resets = <&crg 0x188 4>; + hisilicon,fixed-mode = ; + }; + + combphy1: phy@858 { + compatible = "hisilicon,hi3798cv200-combphy"; + reg = <0x858 0x8>; + #phy-cells = <1>; + clocks = <&crg HISTB_COMBPHY1_CLK>; + resets = <&crg 0x188 12>; + hisilicon,mode-select-bits = <0x0008 11 (0x3 << 11)>; + }; +}; -- cgit v1.2.3 From ecf66ac2a06b16c7de00361c659b123b541e5f6a Mon Sep 17 00:00:00 2001 From: Manu Gautam Date: Tue, 16 Jan 2018 16:27:03 +0530 Subject: dt-bindings: phy-qcom-qusb2: Update binding for QUSB2 V2 version Update generic compatible string for QUSB2 V2 PHY. This will allow all targets using QUSB2 V2 use same string. Acked-by: Rob Herring Signed-off-by: Manu Gautam Reviewed-by: Vivek Gautam Signed-off-by: Kishon Vijay Abraham I --- Documentation/devicetree/bindings/phy/qcom-qusb2-phy.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/phy/qcom-qusb2-phy.txt b/Documentation/devicetree/bindings/phy/qcom-qusb2-phy.txt index aa0fcb05acb3..42c97426836e 100644 --- a/Documentation/devicetree/bindings/phy/qcom-qusb2-phy.txt +++ b/Documentation/devicetree/bindings/phy/qcom-qusb2-phy.txt @@ -4,7 +4,10 @@ Qualcomm QUSB2 phy controller QUSB2 controller supports LS/FS/HS usb connectivity on Qualcomm chipsets. Required properties: - - compatible: compatible list, contains "qcom,msm8996-qusb2-phy". + - compatible: compatible list, contains + "qcom,msm8996-qusb2-phy" for 14nm PHY on msm8996, + "qcom,qusb2-v2-phy" for QUSB2 V2 PHY. + - reg: offset and length of the PHY register set. - #phy-cells: must be 0. -- cgit v1.2.3 From 8587b220f05e4f09e6b2b7c0c078b9ee7f8cfd9e Mon Sep 17 00:00:00 2001 From: Manu Gautam Date: Tue, 16 Jan 2018 16:27:07 +0530 Subject: dt-bindings: phy-qcom-qmp: Update bindings for QMP V3 USB PHY Update compatible string and clock names for QMP version V3 USB PHY. Acked-by: Rob Herring Signed-off-by: Manu Gautam Signed-off-by: Kishon Vijay Abraham I --- Documentation/devicetree/bindings/phy/qcom-qmp-phy.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/phy/qcom-qmp-phy.txt b/Documentation/devicetree/bindings/phy/qcom-qmp-phy.txt index b6a9f2b92bab..dcf1b8f691d5 100644 --- a/Documentation/devicetree/bindings/phy/qcom-qmp-phy.txt +++ b/Documentation/devicetree/bindings/phy/qcom-qmp-phy.txt @@ -8,7 +8,8 @@ Required properties: - compatible: compatible list, contains: "qcom,ipq8074-qmp-pcie-phy" for PCIe phy on IPQ8074 "qcom,msm8996-qmp-pcie-phy" for 14nm PCIe phy on msm8996, - "qcom,msm8996-qmp-usb3-phy" for 14nm USB3 phy on msm8996. + "qcom,msm8996-qmp-usb3-phy" for 14nm USB3 phy on msm8996, + "qcom,qmp-v3-usb3-phy" for USB3 QMP V3 phy. - reg: offset and length of register set for PHY's common serdes block. @@ -25,10 +26,13 @@ Required properties: - clock-names: "cfg_ahb" for phy config clock, "aux" for phy aux clock, "ref" for 19.2 MHz ref clk, + "com_aux" for phy common block aux clock, For "qcom,msm8996-qmp-pcie-phy" must contain: "aux", "cfg_ahb", "ref". For "qcom,msm8996-qmp-usb3-phy" must contain: "aux", "cfg_ahb", "ref". + For "qcom,qmp-v3-usb3-phy" must contain: + "aux", "cfg_ahb", "ref", "com_aux". - resets: a list of phandles and reset controller specifier pairs, one for each entry in reset-names. -- cgit v1.2.3 From d2eced94562f9d69705887e497fee0d17c3099eb Mon Sep 17 00:00:00 2001 From: Martin Blumenstingl Date: Sun, 28 Jan 2018 21:22:41 +0100 Subject: dt-bindings: phy: meson-gxl-usb2-phy: add the reset line and clock The OTG capable USB2 PHY has a reset line (which is shared with other components, such as the USB3 PHY for example) and a clock (which are both part of different registers). Add the properties for the reset line and clocks as optional ones since not all PHYs have them (currently only the OTG capable PHY is known to use these). Signed-off-by: Martin Blumenstingl Reviewed-by: Rob Herring Signed-off-by: Kishon Vijay Abraham I --- Documentation/devicetree/bindings/phy/meson-gxl-usb2-phy.txt | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/phy/meson-gxl-usb2-phy.txt b/Documentation/devicetree/bindings/phy/meson-gxl-usb2-phy.txt index a105494a0fc9..b84a02ebffdf 100644 --- a/Documentation/devicetree/bindings/phy/meson-gxl-usb2-phy.txt +++ b/Documentation/devicetree/bindings/phy/meson-gxl-usb2-phy.txt @@ -6,6 +6,10 @@ Required properties: - #phys-cells: must be 0 (see phy-bindings.txt in this directory) Optional properties: +- clocks: a phandle to the clock of this PHY +- clock-names: must be "phy" +- resets: a phandle to the reset line of this PHY +- reset-names: must be "phy" - phy-supply: see phy-bindings.txt in this directory -- cgit v1.2.3 From 05db0dcc70c61bb51d01845e558bd43add5d55bb Mon Sep 17 00:00:00 2001 From: Martin Blumenstingl Date: Sat, 3 Mar 2018 22:43:02 +0100 Subject: dt-bindings: usb: add the documentation for USB HCDs A USB HCD may have several PHYs which need to be configured before the the HCD starts working. This adds the documentation for such a USB HCD as well as a reference to the new "usb-hcd.txt" from all bindings that implement a USB HCD which support one USB PHY per port. Signed-off-by: Martin Blumenstingl Reviewed-by: Rob Herring Tested-by: Yixun Lan Tested-by: Neil Armstrong Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/mediatek,mtk-xhci.txt | 5 ++++- Documentation/devicetree/bindings/usb/mediatek,mtu3.txt | 5 ++++- Documentation/devicetree/bindings/usb/usb-ehci.txt | 6 ++++-- Documentation/devicetree/bindings/usb/usb-hcd.txt | 9 +++++++++ Documentation/devicetree/bindings/usb/usb-ohci.txt | 6 ++++-- Documentation/devicetree/bindings/usb/usb-uhci.txt | 3 +++ Documentation/devicetree/bindings/usb/usb-xhci.txt | 5 +++++ 7 files changed, 33 insertions(+), 6 deletions(-) create mode 100644 Documentation/devicetree/bindings/usb/usb-hcd.txt (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/usb/mediatek,mtk-xhci.txt b/Documentation/devicetree/bindings/usb/mediatek,mtk-xhci.txt index 88d9f4a4b280..266c2d917a28 100644 --- a/Documentation/devicetree/bindings/usb/mediatek,mtk-xhci.txt +++ b/Documentation/devicetree/bindings/usb/mediatek,mtk-xhci.txt @@ -32,7 +32,7 @@ Required properties: "mcu_ck": mcu_bus clock for register access, "dma_ck": dma_bus clock for data transfer by DMA - - phys : a list of phandle + phy specifier pairs + - phys : see usb-hcd.txt in the current directory Optional properties: - wakeup-source : enable USB remote wakeup; @@ -52,6 +52,9 @@ Optional properties: See: Documentation/devicetree/bindings/pinctrl/pinctrl-bindings.txt - imod-interval-ns: default interrupt moderation interval is 5000ns +additionally the properties from usb-hcd.txt (in the current directory) are +supported. + Example: usb30: usb@11270000 { compatible = "mediatek,mt8173-xhci"; diff --git a/Documentation/devicetree/bindings/usb/mediatek,mtu3.txt b/Documentation/devicetree/bindings/usb/mediatek,mtu3.txt index d589a1ef96a1..3382b5cb471d 100644 --- a/Documentation/devicetree/bindings/usb/mediatek,mtu3.txt +++ b/Documentation/devicetree/bindings/usb/mediatek,mtu3.txt @@ -17,7 +17,7 @@ Required properties: - clock-names : must contain "sys_ck" for clock of controller, the following clocks are optional: "ref_ck", "mcu_ck" and "dam_ck"; - - phys : a list of phandle + phy specifier pairs + - phys : see usb-hcd.txt in the current directory - dr_mode : should be one of "host", "peripheral" or "otg", refer to usb/generic.txt @@ -53,6 +53,9 @@ Optional properties: - mediatek,u3p-dis-msk : mask to disable u3ports, bit0 for u3port0, bit1 for u3port1, ... etc; +additionally the properties from usb-hcd.txt (in the current directory) are +supported. + Sub-nodes: The xhci should be added as subnode to mtu3 as shown in the following example if host mode is enabled. The DT binding details of xhci can be found in: diff --git a/Documentation/devicetree/bindings/usb/usb-ehci.txt b/Documentation/devicetree/bindings/usb/usb-ehci.txt index 3efde12b5d68..0f1b75386207 100644 --- a/Documentation/devicetree/bindings/usb/usb-ehci.txt +++ b/Documentation/devicetree/bindings/usb/usb-ehci.txt @@ -16,10 +16,12 @@ Optional properties: - has-transaction-translator : boolean, set this if EHCI have a Transaction Translator built into the root hub. - clocks : a list of phandle + clock specifier pairs - - phys : phandle + phy specifier pair - - phy-names : "usb" + - phys : see usb-hcd.txt in the current directory - resets : phandle + reset specifier pair +additionally the properties from usb-hcd.txt (in the current directory) are +supported. + Example (Sequoia 440EPx): ehci@e0000300 { compatible = "ibm,usb-ehci-440epx", "usb-ehci"; diff --git a/Documentation/devicetree/bindings/usb/usb-hcd.txt b/Documentation/devicetree/bindings/usb/usb-hcd.txt new file mode 100644 index 000000000000..50529b838c9c --- /dev/null +++ b/Documentation/devicetree/bindings/usb/usb-hcd.txt @@ -0,0 +1,9 @@ +Generic USB HCD (Host Controller Device) Properties + +Optional properties: +- phys: a list of all USB PHYs on this HCD + +Example: + &usb1 { + phys = <&usb2_phy1>, <&usb3_phy1>; + }; diff --git a/Documentation/devicetree/bindings/usb/usb-ohci.txt b/Documentation/devicetree/bindings/usb/usb-ohci.txt index 09e70c875bc6..a8d2103d1f3d 100644 --- a/Documentation/devicetree/bindings/usb/usb-ohci.txt +++ b/Documentation/devicetree/bindings/usb/usb-ohci.txt @@ -13,10 +13,12 @@ Optional properties: - remote-wakeup-connected: remote wakeup is wired on the platform - num-ports : u32, to override the detected port count - clocks : a list of phandle + clock specifier pairs -- phys : phandle + phy specifier pair -- phy-names : "usb" +- phys : see usb-hcd.txt in the current directory - resets : a list of phandle + reset specifier pairs +additionally the properties from usb-hcd.txt (in the current directory) are +supported. + Example: ohci0: usb@1c14400 { diff --git a/Documentation/devicetree/bindings/usb/usb-uhci.txt b/Documentation/devicetree/bindings/usb/usb-uhci.txt index 298133416c97..cc2e6f7d602e 100644 --- a/Documentation/devicetree/bindings/usb/usb-uhci.txt +++ b/Documentation/devicetree/bindings/usb/usb-uhci.txt @@ -6,6 +6,9 @@ Required properties: - reg : Should contain 1 register ranges(address and length) - interrupts : UHCI controller interrupt +additionally the properties from usb-hcd.txt (in the current directory) are +supported. + Example: uhci@d8007b00 { diff --git a/Documentation/devicetree/bindings/usb/usb-xhci.txt b/Documentation/devicetree/bindings/usb/usb-xhci.txt index e2ea59bbca93..2f7663bb69ff 100644 --- a/Documentation/devicetree/bindings/usb/usb-xhci.txt +++ b/Documentation/devicetree/bindings/usb/usb-xhci.txt @@ -32,6 +32,11 @@ Optional properties: - usb3-lpm-capable: determines if platform is USB3 LPM capable - quirk-broken-port-ped: set if the controller has broken port disable mechanism - imod-interval-ns: default interrupt moderation interval is 5000ns + - phys : see usb-hcd.txt in the current directory + +additionally the properties from usb-hcd.txt (in the current directory) are +supported. + Example: usb@f0931000 { -- cgit v1.2.3 From b27560e4d9e5240b5544c9c5650c7442e482646e Mon Sep 17 00:00:00 2001 From: Kai-Heng Feng Date: Thu, 8 Mar 2018 13:37:05 +0800 Subject: usb: core: Add "quirks" parameter for usbcore Trying quirks in usbcore needs to rebuild the driver or the entire kernel if it's builtin. It can save a lot of time if usbcore has similar ability like "usbhid.quirks=" and "usb-storage.quirks=". Rename the original quirk detection function to "static" as we introduce this new "dynamic" function. Now users can use "usbcore.quirks=" as short term workaround before the next kernel release. Also, the quirk parameter can XOR the builtin quirks for debugging purpose. This is inspired by usbhid and usb-storage. Signed-off-by: Kai-Heng Feng Signed-off-by: Greg Kroah-Hartman --- Documentation/admin-guide/kernel-parameters.txt | 55 ++++++++ drivers/usb/core/quirks.c | 177 +++++++++++++++++++++++- drivers/usb/core/usb.c | 1 + drivers/usb/core/usb.h | 1 + 4 files changed, 229 insertions(+), 5 deletions(-) (limited to 'Documentation') diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index 1d1d53f85ddd..70a7398c20e2 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -4368,6 +4368,61 @@ usbcore.nousb [USB] Disable the USB subsystem + usbcore.quirks= + [USB] A list of quirks entries to supplement or + override the built-in usb core quirk list. List + entries are separated by commas. Each entry has + the form VID:PID:Flags where VID and PID are Vendor + and Product ID values (4-digit hex numbers) and + Flags is a set of characters, each corresponding + to a common usb core quirk flag as follows: + a = USB_QUIRK_STRING_FETCH_255 (string + descriptors must not be fetched using + a 255-byte read); + b = USB_QUIRK_RESET_RESUME (device can't resume + correctly so reset it instead); + c = USB_QUIRK_NO_SET_INTF (device can't handle + Set-Interface requests); + d = USB_QUIRK_CONFIG_INTF_STRINGS (device can't + handle its Configuration or Interface + strings); + e = USB_QUIRK_RESET (device can't be reset + (e.g morph devices), don't use reset); + f = USB_QUIRK_HONOR_BNUMINTERFACES (device has + more interface descriptions than the + bNumInterfaces count, and can't handle + talking to these interfaces); + g = USB_QUIRK_DELAY_INIT (device needs a pause + during initialization, after we read + the device descriptor); + h = USB_QUIRK_LINEAR_UFRAME_INTR_BINTERVAL (For + high speed and super speed interrupt + endpoints, the USB 2.0 and USB 3.0 spec + require the interval in microframes (1 + microframe = 125 microseconds) to be + calculated as interval = 2 ^ + (bInterval-1). + Devices with this quirk report their + bInterval as the result of this + calculation instead of the exponent + variable used in the calculation); + i = USB_QUIRK_DEVICE_QUALIFIER (device can't + handle device_qualifier descriptor + requests); + j = USB_QUIRK_IGNORE_REMOTE_WAKEUP (device + generates spurious wakeup, ignore + remote wakeup capability); + k = USB_QUIRK_NO_LPM (device can't handle Link + Power Management); + l = USB_QUIRK_LINEAR_FRAME_INTR_BINTERVAL + (Device reports its bInterval as linear + frames instead of the USB 2.0 + calculation); + m = USB_QUIRK_DISCONNECT_SUSPEND (Device needs + to be disconnected before suspend to + prevent spurious wakeup) + Example: quirks=0781:5580:bk,0a5c:5834:gij + usbhid.mousepoll= [USBHID] The interval which mice are to be polled at. diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c index f4a548471f0f..42faaeead81b 100644 --- a/drivers/usb/core/quirks.c +++ b/drivers/usb/core/quirks.c @@ -11,6 +11,143 @@ #include #include "usb.h" +struct quirk_entry { + u16 vid; + u16 pid; + u32 flags; +}; + +static DEFINE_MUTEX(quirk_mutex); + +static struct quirk_entry *quirk_list; +static unsigned int quirk_count; + +static char quirks_param[128]; + +static int quirks_param_set(const char *val, const struct kernel_param *kp) +{ + char *p, *field; + u16 vid, pid; + u32 flags; + size_t i; + + mutex_lock(&quirk_mutex); + + if (!val || !*val) { + quirk_count = 0; + kfree(quirk_list); + quirk_list = NULL; + goto unlock; + } + + for (quirk_count = 1, i = 0; val[i]; i++) + if (val[i] == ',') + quirk_count++; + + if (quirk_list) { + kfree(quirk_list); + quirk_list = NULL; + } + + quirk_list = kcalloc(quirk_count, sizeof(struct quirk_entry), + GFP_KERNEL); + if (!quirk_list) { + mutex_unlock(&quirk_mutex); + return -ENOMEM; + } + + for (i = 0, p = (char *)val; p && *p;) { + /* Each entry consists of VID:PID:flags */ + field = strsep(&p, ":"); + if (!field) + break; + + if (kstrtou16(field, 16, &vid)) + break; + + field = strsep(&p, ":"); + if (!field) + break; + + if (kstrtou16(field, 16, &pid)) + break; + + field = strsep(&p, ","); + if (!field || !*field) + break; + + /* Collect the flags */ + for (flags = 0; *field; field++) { + switch (*field) { + case 'a': + flags |= USB_QUIRK_STRING_FETCH_255; + break; + case 'b': + flags |= USB_QUIRK_RESET_RESUME; + break; + case 'c': + flags |= USB_QUIRK_NO_SET_INTF; + break; + case 'd': + flags |= USB_QUIRK_CONFIG_INTF_STRINGS; + break; + case 'e': + flags |= USB_QUIRK_RESET; + break; + case 'f': + flags |= USB_QUIRK_HONOR_BNUMINTERFACES; + break; + case 'g': + flags |= USB_QUIRK_DELAY_INIT; + break; + case 'h': + flags |= USB_QUIRK_LINEAR_UFRAME_INTR_BINTERVAL; + break; + case 'i': + flags |= USB_QUIRK_DEVICE_QUALIFIER; + break; + case 'j': + flags |= USB_QUIRK_IGNORE_REMOTE_WAKEUP; + break; + case 'k': + flags |= USB_QUIRK_NO_LPM; + break; + case 'l': + flags |= USB_QUIRK_LINEAR_FRAME_INTR_BINTERVAL; + break; + case 'm': + flags |= USB_QUIRK_DISCONNECT_SUSPEND; + break; + /* Ignore unrecognized flag characters */ + } + } + + quirk_list[i++] = (struct quirk_entry) + { .vid = vid, .pid = pid, .flags = flags }; + } + + if (i < quirk_count) + quirk_count = i; + +unlock: + mutex_unlock(&quirk_mutex); + + return param_set_copystring(val, kp); +} + +static const struct kernel_param_ops quirks_param_ops = { + .set = quirks_param_set, + .get = param_get_string, +}; + +static struct kparam_string quirks_param_string = { + .maxlen = sizeof(quirks_param), + .string = quirks_param, +}; + +module_param_cb(quirks, &quirks_param_ops, &quirks_param_string, 0644); +MODULE_PARM_DESC(quirks, "Add/modify USB quirks by specifying quirks=vendorID:productID:quirks"); + /* Lists of quirky USB devices, split in device quirks and interface quirks. * Device quirks are applied at the very beginning of the enumeration process, * right after reading the device descriptor. They can thus only match on device @@ -320,8 +457,8 @@ static int usb_amd_resume_quirk(struct usb_device *udev) return 0; } -static u32 __usb_detect_quirks(struct usb_device *udev, - const struct usb_device_id *id) +static u32 usb_detect_static_quirks(struct usb_device *udev, + const struct usb_device_id *id) { u32 quirks = 0; @@ -339,21 +476,43 @@ static u32 __usb_detect_quirks(struct usb_device *udev, return quirks; } +static u32 usb_detect_dynamic_quirks(struct usb_device *udev) +{ + u16 vid = le16_to_cpu(udev->descriptor.idVendor); + u16 pid = le16_to_cpu(udev->descriptor.idProduct); + int i, flags = 0; + + mutex_lock(&quirk_mutex); + + for (i = 0; i < quirk_count; i++) { + if (vid == quirk_list[i].vid && pid == quirk_list[i].pid) { + flags = quirk_list[i].flags; + break; + } + } + + mutex_unlock(&quirk_mutex); + + return flags; +} + /* * Detect any quirks the device has, and do any housekeeping for it if needed. */ void usb_detect_quirks(struct usb_device *udev) { - udev->quirks = __usb_detect_quirks(udev, usb_quirk_list); + udev->quirks = usb_detect_static_quirks(udev, usb_quirk_list); /* * Pixart-based mice would trigger remote wakeup issue on AMD * Yangtze chipset, so set them as RESET_RESUME flag. */ if (usb_amd_resume_quirk(udev)) - udev->quirks |= __usb_detect_quirks(udev, + udev->quirks |= usb_detect_static_quirks(udev, usb_amd_resume_quirk_list); + udev->quirks ^= usb_detect_dynamic_quirks(udev); + if (udev->quirks) dev_dbg(&udev->dev, "USB quirks for this device: %x\n", udev->quirks); @@ -372,7 +531,7 @@ void usb_detect_interface_quirks(struct usb_device *udev) { u32 quirks; - quirks = __usb_detect_quirks(udev, usb_interface_quirk_list); + quirks = usb_detect_static_quirks(udev, usb_interface_quirk_list); if (quirks == 0) return; @@ -380,3 +539,11 @@ void usb_detect_interface_quirks(struct usb_device *udev) quirks); udev->quirks |= quirks; } + +void usb_release_quirk_list(void) +{ + mutex_lock(&quirk_mutex); + kfree(quirk_list); + quirk_list = NULL; + mutex_unlock(&quirk_mutex); +} diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c index 2f5fbc56a9dd..0adb6345ff2e 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -1259,6 +1259,7 @@ static void __exit usb_exit(void) if (usb_disabled()) return; + usb_release_quirk_list(); usb_deregister_device_driver(&usb_generic_driver); usb_major_cleanup(); usb_deregister(&usbfs_driver); diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h index 149cc7480971..546a2219454b 100644 --- a/drivers/usb/core/usb.h +++ b/drivers/usb/core/usb.h @@ -36,6 +36,7 @@ extern void usb_deauthorize_interface(struct usb_interface *); extern void usb_authorize_interface(struct usb_interface *); extern void usb_detect_quirks(struct usb_device *udev); extern void usb_detect_interface_quirks(struct usb_device *udev); +extern void usb_release_quirk_list(void); extern int usb_remove_device(struct usb_device *udev); extern int usb_get_device_descriptor(struct usb_device *dev, -- cgit v1.2.3 From 95713fb8aa039e9cd89ff545b62bd2a860c36e39 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Mon, 12 Mar 2018 16:30:40 +0100 Subject: Revert "usb: core: Add "quirks" parameter for usbcore" This reverts commit b27560e4d9e5240b5544c9c5650c7442e482646e as it breaks the build for some arches :( Reported-by: kbuild test robot Cc: Kai-Heng Feng Signed-off-by: Greg Kroah-Hartman diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index 1d1d53f85ddd..70a7398c20e2 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -4368,6 +4368,61 @@ usbcore.nousb [USB] Disable the USB subsystem + usbcore.quirks= + [USB] A list of quirks entries to supplement or + override the built-in usb core quirk list. List + entries are separated by commas. Each entry has + the form VID:PID:Flags where VID and PID are Vendor + and Product ID values (4-digit hex numbers) and + Flags is a set of characters, each corresponding + to a common usb core quirk flag as follows: + a = USB_QUIRK_STRING_FETCH_255 (string + descriptors must not be fetched using + a 255-byte read); + b = USB_QUIRK_RESET_RESUME (device can't resume + correctly so reset it instead); + c = USB_QUIRK_NO_SET_INTF (device can't handle + Set-Interface requests); + d = USB_QUIRK_CONFIG_INTF_STRINGS (device can't + handle its Configuration or Interface + strings); + e = USB_QUIRK_RESET (device can't be reset + (e.g morph devices), don't use reset); + f = USB_QUIRK_HONOR_BNUMINTERFACES (device has + more interface descriptions than the + bNumInterfaces count, and can't handle + talking to these interfaces); + g = USB_QUIRK_DELAY_INIT (device needs a pause + during initialization, after we read + the device descriptor); + h = USB_QUIRK_LINEAR_UFRAME_INTR_BINTERVAL (For + high speed and super speed interrupt + endpoints, the USB 2.0 and USB 3.0 spec + require the interval in microframes (1 + microframe = 125 microseconds) to be + calculated as interval = 2 ^ + (bInterval-1). + Devices with this quirk report their + bInterval as the result of this + calculation instead of the exponent + variable used in the calculation); + i = USB_QUIRK_DEVICE_QUALIFIER (device can't + handle device_qualifier descriptor + requests); + j = USB_QUIRK_IGNORE_REMOTE_WAKEUP (device + generates spurious wakeup, ignore + remote wakeup capability); + k = USB_QUIRK_NO_LPM (device can't handle Link + Power Management); + l = USB_QUIRK_LINEAR_FRAME_INTR_BINTERVAL + (Device reports its bInterval as linear + frames instead of the USB 2.0 + calculation); + m = USB_QUIRK_DISCONNECT_SUSPEND (Device needs + to be disconnected before suspend to + prevent spurious wakeup) + Example: quirks=0781:5580:bk,0a5c:5834:gij + usbhid.mousepoll= [USBHID] The interval which mice are to be polled at. diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c index f4a548471f0f..42faaeead81b 100644 --- a/drivers/usb/core/quirks.c +++ b/drivers/usb/core/quirks.c @@ -11,6 +11,143 @@ #include #include "usb.h" +struct quirk_entry { + u16 vid; + u16 pid; + u32 flags; +}; + +static DEFINE_MUTEX(quirk_mutex); + +static struct quirk_entry *quirk_list; +static unsigned int quirk_count; + +static char quirks_param[128]; + +static int quirks_param_set(const char *val, const struct kernel_param *kp) +{ + char *p, *field; + u16 vid, pid; + u32 flags; + size_t i; + + mutex_lock(&quirk_mutex); + + if (!val || !*val) { + quirk_count = 0; + kfree(quirk_list); + quirk_list = NULL; + goto unlock; + } + + for (quirk_count = 1, i = 0; val[i]; i++) + if (val[i] == ',') + quirk_count++; + + if (quirk_list) { + kfree(quirk_list); + quirk_list = NULL; + } + + quirk_list = kcalloc(quirk_count, sizeof(struct quirk_entry), + GFP_KERNEL); + if (!quirk_list) { + mutex_unlock(&quirk_mutex); + return -ENOMEM; + } + + for (i = 0, p = (char *)val; p && *p;) { + /* Each entry consists of VID:PID:flags */ + field = strsep(&p, ":"); + if (!field) + break; + + if (kstrtou16(field, 16, &vid)) + break; + + field = strsep(&p, ":"); + if (!field) + break; + + if (kstrtou16(field, 16, &pid)) + break; + + field = strsep(&p, ","); + if (!field || !*field) + break; + + /* Collect the flags */ + for (flags = 0; *field; field++) { + switch (*field) { + case 'a': + flags |= USB_QUIRK_STRING_FETCH_255; + break; + case 'b': + flags |= USB_QUIRK_RESET_RESUME; + break; + case 'c': + flags |= USB_QUIRK_NO_SET_INTF; + break; + case 'd': + flags |= USB_QUIRK_CONFIG_INTF_STRINGS; + break; + case 'e': + flags |= USB_QUIRK_RESET; + break; + case 'f': + flags |= USB_QUIRK_HONOR_BNUMINTERFACES; + break; + case 'g': + flags |= USB_QUIRK_DELAY_INIT; + break; + case 'h': + flags |= USB_QUIRK_LINEAR_UFRAME_INTR_BINTERVAL; + break; + case 'i': + flags |= USB_QUIRK_DEVICE_QUALIFIER; + break; + case 'j': + flags |= USB_QUIRK_IGNORE_REMOTE_WAKEUP; + break; + case 'k': + flags |= USB_QUIRK_NO_LPM; + break; + case 'l': + flags |= USB_QUIRK_LINEAR_FRAME_INTR_BINTERVAL; + break; + case 'm': + flags |= USB_QUIRK_DISCONNECT_SUSPEND; + break; + /* Ignore unrecognized flag characters */ + } + } + + quirk_list[i++] = (struct quirk_entry) + { .vid = vid, .pid = pid, .flags = flags }; + } + + if (i < quirk_count) + quirk_count = i; + +unlock: + mutex_unlock(&quirk_mutex); + + return param_set_copystring(val, kp); +} + +static const struct kernel_param_ops quirks_param_ops = { + .set = quirks_param_set, + .get = param_get_string, +}; + +static struct kparam_string quirks_param_string = { + .maxlen = sizeof(quirks_param), + .string = quirks_param, +}; + +module_param_cb(quirks, &quirks_param_ops, &quirks_param_string, 0644); +MODULE_PARM_DESC(quirks, "Add/modify USB quirks by specifying quirks=vendorID:productID:quirks"); + /* Lists of quirky USB devices, split in device quirks and interface quirks. * Device quirks are applied at the very beginning of the enumeration process, * right after reading the device descriptor. They can thus only match on device @@ -320,8 +457,8 @@ static int usb_amd_resume_quirk(struct usb_device *udev) return 0; } -static u32 __usb_detect_quirks(struct usb_device *udev, - const struct usb_device_id *id) +static u32 usb_detect_static_quirks(struct usb_device *udev, + const struct usb_device_id *id) { u32 quirks = 0; @@ -339,21 +476,43 @@ static u32 __usb_detect_quirks(struct usb_device *udev, return quirks; } +static u32 usb_detect_dynamic_quirks(struct usb_device *udev) +{ + u16 vid = le16_to_cpu(udev->descriptor.idVendor); + u16 pid = le16_to_cpu(udev->descriptor.idProduct); + int i, flags = 0; + + mutex_lock(&quirk_mutex); + + for (i = 0; i < quirk_count; i++) { + if (vid == quirk_list[i].vid && pid == quirk_list[i].pid) { + flags = quirk_list[i].flags; + break; + } + } + + mutex_unlock(&quirk_mutex); + + return flags; +} + /* * Detect any quirks the device has, and do any housekeeping for it if needed. */ void usb_detect_quirks(struct usb_device *udev) { - udev->quirks = __usb_detect_quirks(udev, usb_quirk_list); + udev->quirks = usb_detect_static_quirks(udev, usb_quirk_list); /* * Pixart-based mice would trigger remote wakeup issue on AMD * Yangtze chipset, so set them as RESET_RESUME flag. */ if (usb_amd_resume_quirk(udev)) - udev->quirks |= __usb_detect_quirks(udev, + udev->quirks |= usb_detect_static_quirks(udev, usb_amd_resume_quirk_list); + udev->quirks ^= usb_detect_dynamic_quirks(udev); + if (udev->quirks) dev_dbg(&udev->dev, "USB quirks for this device: %x\n", udev->quirks); @@ -372,7 +531,7 @@ void usb_detect_interface_quirks(struct usb_device *udev) { u32 quirks; - quirks = __usb_detect_quirks(udev, usb_interface_quirk_list); + quirks = usb_detect_static_quirks(udev, usb_interface_quirk_list); if (quirks == 0) return; @@ -380,3 +539,11 @@ void usb_detect_interface_quirks(struct usb_device *udev) quirks); udev->quirks |= quirks; } + +void usb_release_quirk_list(void) +{ + mutex_lock(&quirk_mutex); + kfree(quirk_list); + quirk_list = NULL; + mutex_unlock(&quirk_mutex); +} diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c index 2f5fbc56a9dd..0adb6345ff2e 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -1259,6 +1259,7 @@ static void __exit usb_exit(void) if (usb_disabled()) return; + usb_release_quirk_list(); usb_deregister_device_driver(&usb_generic_driver); usb_major_cleanup(); usb_deregister(&usbfs_driver); diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h index 149cc7480971..546a2219454b 100644 --- a/drivers/usb/core/usb.h +++ b/drivers/usb/core/usb.h @@ -36,6 +36,7 @@ extern void usb_deauthorize_interface(struct usb_interface *); extern void usb_authorize_interface(struct usb_interface *); extern void usb_detect_quirks(struct usb_device *udev); extern void usb_detect_interface_quirks(struct usb_device *udev); +extern void usb_release_quirk_list(void); extern int usb_remove_device(struct usb_device *udev); extern int usb_get_device_descriptor(struct usb_device *dev, --- Documentation/admin-guide/kernel-parameters.txt | 55 -------- drivers/usb/core/quirks.c | 177 +----------------------- drivers/usb/core/usb.c | 1 - drivers/usb/core/usb.h | 1 - 4 files changed, 5 insertions(+), 229 deletions(-) (limited to 'Documentation') diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index 70a7398c20e2..1d1d53f85ddd 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -4368,61 +4368,6 @@ usbcore.nousb [USB] Disable the USB subsystem - usbcore.quirks= - [USB] A list of quirks entries to supplement or - override the built-in usb core quirk list. List - entries are separated by commas. Each entry has - the form VID:PID:Flags where VID and PID are Vendor - and Product ID values (4-digit hex numbers) and - Flags is a set of characters, each corresponding - to a common usb core quirk flag as follows: - a = USB_QUIRK_STRING_FETCH_255 (string - descriptors must not be fetched using - a 255-byte read); - b = USB_QUIRK_RESET_RESUME (device can't resume - correctly so reset it instead); - c = USB_QUIRK_NO_SET_INTF (device can't handle - Set-Interface requests); - d = USB_QUIRK_CONFIG_INTF_STRINGS (device can't - handle its Configuration or Interface - strings); - e = USB_QUIRK_RESET (device can't be reset - (e.g morph devices), don't use reset); - f = USB_QUIRK_HONOR_BNUMINTERFACES (device has - more interface descriptions than the - bNumInterfaces count, and can't handle - talking to these interfaces); - g = USB_QUIRK_DELAY_INIT (device needs a pause - during initialization, after we read - the device descriptor); - h = USB_QUIRK_LINEAR_UFRAME_INTR_BINTERVAL (For - high speed and super speed interrupt - endpoints, the USB 2.0 and USB 3.0 spec - require the interval in microframes (1 - microframe = 125 microseconds) to be - calculated as interval = 2 ^ - (bInterval-1). - Devices with this quirk report their - bInterval as the result of this - calculation instead of the exponent - variable used in the calculation); - i = USB_QUIRK_DEVICE_QUALIFIER (device can't - handle device_qualifier descriptor - requests); - j = USB_QUIRK_IGNORE_REMOTE_WAKEUP (device - generates spurious wakeup, ignore - remote wakeup capability); - k = USB_QUIRK_NO_LPM (device can't handle Link - Power Management); - l = USB_QUIRK_LINEAR_FRAME_INTR_BINTERVAL - (Device reports its bInterval as linear - frames instead of the USB 2.0 - calculation); - m = USB_QUIRK_DISCONNECT_SUSPEND (Device needs - to be disconnected before suspend to - prevent spurious wakeup) - Example: quirks=0781:5580:bk,0a5c:5834:gij - usbhid.mousepoll= [USBHID] The interval which mice are to be polled at. diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c index 42faaeead81b..f4a548471f0f 100644 --- a/drivers/usb/core/quirks.c +++ b/drivers/usb/core/quirks.c @@ -11,143 +11,6 @@ #include #include "usb.h" -struct quirk_entry { - u16 vid; - u16 pid; - u32 flags; -}; - -static DEFINE_MUTEX(quirk_mutex); - -static struct quirk_entry *quirk_list; -static unsigned int quirk_count; - -static char quirks_param[128]; - -static int quirks_param_set(const char *val, const struct kernel_param *kp) -{ - char *p, *field; - u16 vid, pid; - u32 flags; - size_t i; - - mutex_lock(&quirk_mutex); - - if (!val || !*val) { - quirk_count = 0; - kfree(quirk_list); - quirk_list = NULL; - goto unlock; - } - - for (quirk_count = 1, i = 0; val[i]; i++) - if (val[i] == ',') - quirk_count++; - - if (quirk_list) { - kfree(quirk_list); - quirk_list = NULL; - } - - quirk_list = kcalloc(quirk_count, sizeof(struct quirk_entry), - GFP_KERNEL); - if (!quirk_list) { - mutex_unlock(&quirk_mutex); - return -ENOMEM; - } - - for (i = 0, p = (char *)val; p && *p;) { - /* Each entry consists of VID:PID:flags */ - field = strsep(&p, ":"); - if (!field) - break; - - if (kstrtou16(field, 16, &vid)) - break; - - field = strsep(&p, ":"); - if (!field) - break; - - if (kstrtou16(field, 16, &pid)) - break; - - field = strsep(&p, ","); - if (!field || !*field) - break; - - /* Collect the flags */ - for (flags = 0; *field; field++) { - switch (*field) { - case 'a': - flags |= USB_QUIRK_STRING_FETCH_255; - break; - case 'b': - flags |= USB_QUIRK_RESET_RESUME; - break; - case 'c': - flags |= USB_QUIRK_NO_SET_INTF; - break; - case 'd': - flags |= USB_QUIRK_CONFIG_INTF_STRINGS; - break; - case 'e': - flags |= USB_QUIRK_RESET; - break; - case 'f': - flags |= USB_QUIRK_HONOR_BNUMINTERFACES; - break; - case 'g': - flags |= USB_QUIRK_DELAY_INIT; - break; - case 'h': - flags |= USB_QUIRK_LINEAR_UFRAME_INTR_BINTERVAL; - break; - case 'i': - flags |= USB_QUIRK_DEVICE_QUALIFIER; - break; - case 'j': - flags |= USB_QUIRK_IGNORE_REMOTE_WAKEUP; - break; - case 'k': - flags |= USB_QUIRK_NO_LPM; - break; - case 'l': - flags |= USB_QUIRK_LINEAR_FRAME_INTR_BINTERVAL; - break; - case 'm': - flags |= USB_QUIRK_DISCONNECT_SUSPEND; - break; - /* Ignore unrecognized flag characters */ - } - } - - quirk_list[i++] = (struct quirk_entry) - { .vid = vid, .pid = pid, .flags = flags }; - } - - if (i < quirk_count) - quirk_count = i; - -unlock: - mutex_unlock(&quirk_mutex); - - return param_set_copystring(val, kp); -} - -static const struct kernel_param_ops quirks_param_ops = { - .set = quirks_param_set, - .get = param_get_string, -}; - -static struct kparam_string quirks_param_string = { - .maxlen = sizeof(quirks_param), - .string = quirks_param, -}; - -module_param_cb(quirks, &quirks_param_ops, &quirks_param_string, 0644); -MODULE_PARM_DESC(quirks, "Add/modify USB quirks by specifying quirks=vendorID:productID:quirks"); - /* Lists of quirky USB devices, split in device quirks and interface quirks. * Device quirks are applied at the very beginning of the enumeration process, * right after reading the device descriptor. They can thus only match on device @@ -457,8 +320,8 @@ static int usb_amd_resume_quirk(struct usb_device *udev) return 0; } -static u32 usb_detect_static_quirks(struct usb_device *udev, - const struct usb_device_id *id) +static u32 __usb_detect_quirks(struct usb_device *udev, + const struct usb_device_id *id) { u32 quirks = 0; @@ -476,43 +339,21 @@ static u32 usb_detect_static_quirks(struct usb_device *udev, return quirks; } -static u32 usb_detect_dynamic_quirks(struct usb_device *udev) -{ - u16 vid = le16_to_cpu(udev->descriptor.idVendor); - u16 pid = le16_to_cpu(udev->descriptor.idProduct); - int i, flags = 0; - - mutex_lock(&quirk_mutex); - - for (i = 0; i < quirk_count; i++) { - if (vid == quirk_list[i].vid && pid == quirk_list[i].pid) { - flags = quirk_list[i].flags; - break; - } - } - - mutex_unlock(&quirk_mutex); - - return flags; -} - /* * Detect any quirks the device has, and do any housekeeping for it if needed. */ void usb_detect_quirks(struct usb_device *udev) { - udev->quirks = usb_detect_static_quirks(udev, usb_quirk_list); + udev->quirks = __usb_detect_quirks(udev, usb_quirk_list); /* * Pixart-based mice would trigger remote wakeup issue on AMD * Yangtze chipset, so set them as RESET_RESUME flag. */ if (usb_amd_resume_quirk(udev)) - udev->quirks |= usb_detect_static_quirks(udev, + udev->quirks |= __usb_detect_quirks(udev, usb_amd_resume_quirk_list); - udev->quirks ^= usb_detect_dynamic_quirks(udev); - if (udev->quirks) dev_dbg(&udev->dev, "USB quirks for this device: %x\n", udev->quirks); @@ -531,7 +372,7 @@ void usb_detect_interface_quirks(struct usb_device *udev) { u32 quirks; - quirks = usb_detect_static_quirks(udev, usb_interface_quirk_list); + quirks = __usb_detect_quirks(udev, usb_interface_quirk_list); if (quirks == 0) return; @@ -539,11 +380,3 @@ void usb_detect_interface_quirks(struct usb_device *udev) quirks); udev->quirks |= quirks; } - -void usb_release_quirk_list(void) -{ - mutex_lock(&quirk_mutex); - kfree(quirk_list); - quirk_list = NULL; - mutex_unlock(&quirk_mutex); -} diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c index 0adb6345ff2e..2f5fbc56a9dd 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -1259,7 +1259,6 @@ static void __exit usb_exit(void) if (usb_disabled()) return; - usb_release_quirk_list(); usb_deregister_device_driver(&usb_generic_driver); usb_major_cleanup(); usb_deregister(&usbfs_driver); diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h index 546a2219454b..149cc7480971 100644 --- a/drivers/usb/core/usb.h +++ b/drivers/usb/core/usb.h @@ -36,7 +36,6 @@ extern void usb_deauthorize_interface(struct usb_interface *); extern void usb_authorize_interface(struct usb_interface *); extern void usb_detect_quirks(struct usb_device *udev); extern void usb_detect_interface_quirks(struct usb_device *udev); -extern void usb_release_quirk_list(void); extern int usb_remove_device(struct usb_device *udev); extern int usb_get_device_descriptor(struct usb_device *dev, -- cgit v1.2.3 From a5d411962c444377d0de0dc89502dbaf2e2a34e2 Mon Sep 17 00:00:00 2001 From: Martin Blumenstingl Date: Sun, 11 Feb 2018 22:15:15 +0100 Subject: dt-bindings: usb: add support for dwc3 controller on Amlogic Meson GX Amlogic Meson GX SoCs (GXL and AXG) come with a (host-only) dwc3 USB controller. This requires a clock to be enabled and a reset line to be pulsed to get the hardware into a known state. Add the documentation for this IP block, similar to "qcom,dwc3.txt". Signed-off-by: Martin Blumenstingl Reviewed-by: Rob Herring Tested-by: Yixun Lan Signed-off-by: Felipe Balbi --- .../devicetree/bindings/usb/amlogic,dwc3.txt | 42 ++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 Documentation/devicetree/bindings/usb/amlogic,dwc3.txt (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/usb/amlogic,dwc3.txt b/Documentation/devicetree/bindings/usb/amlogic,dwc3.txt new file mode 100644 index 000000000000..9a8b631904fd --- /dev/null +++ b/Documentation/devicetree/bindings/usb/amlogic,dwc3.txt @@ -0,0 +1,42 @@ +Amlogic Meson GX DWC3 USB SoC controller + +Required properties: +- compatible: depending on the SoC this should contain one of: + * amlogic,meson-axg-dwc3 + * amlogic,meson-gxl-dwc3 +- clocks: a handle for the "USB general" clock +- clock-names: must be "usb_general" +- resets: a handle for the shared "USB OTG" reset line +- reset-names: must be "usb_otg" + +Required child node: +A child node must exist to represent the core DWC3 IP block. The name of +the node is not important. The content of the node is defined in dwc3.txt. + +PHY documentation is provided in the following places: +- Documentation/devicetree/bindings/phy/meson-gxl-usb2-phy.txt +- Documentation/devicetree/bindings/phy/meson-gxl-usb3-phy.txt + +Example device nodes: + usb0: usb@ff500000 { + compatible = "amlogic,meson-axg-dwc3"; + #address-cells = <2>; + #size-cells = <2>; + ranges; + + clocks = <&clkc CLKID_USB>; + clock-names = "usb_general"; + resets = <&reset RESET_USB_OTG>; + reset-names = "usb_otg"; + + dwc3: dwc3@ff500000 { + compatible = "snps,dwc3"; + reg = <0x0 0xff500000 0x0 0x100000>; + interrupts = ; + dr_mode = "host"; + maximum-speed = "high-speed"; + snps,dis_u2_susphy_quirk; + phys = <&usb3_phy>, <&usb2_phy0>; + phy-names = "usb2-phy", "usb3-phy"; + }; + }; -- cgit v1.2.3 From c55fbcb8cf6b3653103f046232ada503e482b57d Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Mon, 12 Mar 2018 13:25:40 +0800 Subject: dt-bindings: phy-mtk-tphy: add properties for U2 slew rate calibrate Add two properties of ref_clk and coefficient used by U2 slew rate calibrate which may vary on different SoCs Signed-off-by: Chunfeng Yun Reviewed-by: Matthias Brugger Signed-off-by: Kishon Vijay Abraham I --- Documentation/devicetree/bindings/phy/phy-mtk-tphy.txt | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/phy/phy-mtk-tphy.txt b/Documentation/devicetree/bindings/phy/phy-mtk-tphy.txt index 41e09ed2ca70..0d34b2b4a6b7 100644 --- a/Documentation/devicetree/bindings/phy/phy-mtk-tphy.txt +++ b/Documentation/devicetree/bindings/phy/phy-mtk-tphy.txt @@ -27,6 +27,10 @@ Optional properties (controller (parent) node): - reg : offset and length of register shared by multiple ports, exclude port's private register. It is needed on mt2701 and mt8173, but not on mt2712. + - mediatek,src-ref-clk-mhz : frequency of reference clock for slew rate + calibrate + - mediatek,src-coef : coefficient for slew rate calibrate, depends on + SoC process Required properties (port (child) node): - reg : address and length of the register set for the port. -- cgit v1.2.3 From 324e46cee18739efbbed49b30a9100cb7bd0d35e Mon Sep 17 00:00:00 2001 From: Martin Blumenstingl Date: Sat, 3 Mar 2018 19:46:59 +0100 Subject: dt-bindings: phy: Add support for the USB3 PHY on Amlogic Meson GXL SoCs Amlogic Meson GXL SoCs use a dwc3 controller with two (GXM - a variant for GXL, has three) USB2 ports. The first USB2 port supports host and peripheral (also called "device") mode. While the dwc3 controller has no USB3 port enabled we still need the USB3 PHY to be initialized. Otherwise high-speed USB transfers (for example with a USB flash drive) may time out (most often seen on boards with mainline u-boot, where the bootloader does not initialize the USB3 PHY registers). Signed-off-by: Martin Blumenstingl Reviewed-by: Rob Herring Tested-by: Yixun Lan Tested-by: Neil Armstrong Signed-off-by: Kishon Vijay Abraham I --- .../devicetree/bindings/phy/meson-gxl-usb3-phy.txt | 31 ++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 Documentation/devicetree/bindings/phy/meson-gxl-usb3-phy.txt (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/phy/meson-gxl-usb3-phy.txt b/Documentation/devicetree/bindings/phy/meson-gxl-usb3-phy.txt new file mode 100644 index 000000000000..114947e1de3d --- /dev/null +++ b/Documentation/devicetree/bindings/phy/meson-gxl-usb3-phy.txt @@ -0,0 +1,31 @@ +* Amlogic Meson GXL and GXM USB3 PHY and OTG detection binding + +Required properties: +- compatible: Should be "amlogic,meson-gxl-usb3-phy" +- #phys-cells: must be 0 (see phy-bindings.txt in this directory) +- reg: The base address and length of the registers +- interrupts: the interrupt specifier for the OTG detection +- clocks: phandles to the clocks for + - the USB3 PHY + - and peripheral mode/OTG detection +- clock-names: must contain "phy" and "peripheral" +- resets: phandle to the reset lines for: + - the USB3 PHY and + - peripheral mode/OTG detection +- reset-names: must contain "phy" and "peripheral" + +Optional properties: +- phy-supply: see phy-bindings.txt in this directory + + +Example: + usb3_phy0: phy@78080 { + compatible = "amlogic,meson-gxl-usb3-phy"; + #phy-cells = <0>; + reg = <0x0 0x78080 0x0 0x20>; + interrupts = ; + clocks = <&clkc CLKID_USB_OTG>, <&clkc_AO CLKID_AO_CEC_32K>; + clock-names = "phy", "peripheral"; + resets = <&reset RESET_USB_OTG>, <&reset RESET_USB_OTG>; + reset-names = "phy", "peripheral"; + }; -- cgit v1.2.3 From 194303d60b68a8038979560e5f4ca493bbb3cf37 Mon Sep 17 00:00:00 2001 From: Amelie Delaunay Date: Fri, 2 Mar 2018 15:56:24 +0100 Subject: dt-bindings: phy: add support for STM32 USB PHY Controller (USBPHYC) This patch adds the device tree bindings description for STM32 USBPHYC (USB PHY Controller). Signed-off-by: Amelie Delaunay Reviewed-by: Rob Herring Signed-off-by: Kishon Vijay Abraham I --- .../devicetree/bindings/phy/phy-stm32-usbphyc.txt | 73 ++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 Documentation/devicetree/bindings/phy/phy-stm32-usbphyc.txt (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/phy/phy-stm32-usbphyc.txt b/Documentation/devicetree/bindings/phy/phy-stm32-usbphyc.txt new file mode 100644 index 000000000000..725ae71ae653 --- /dev/null +++ b/Documentation/devicetree/bindings/phy/phy-stm32-usbphyc.txt @@ -0,0 +1,73 @@ +STMicroelectronics STM32 USB HS PHY controller + +The STM32 USBPHYC block contains a dual port High Speed UTMI+ PHY and a UTMI +switch. It controls PHY configuration and status, and the UTMI+ switch that +selects either OTG or HOST controller for the second PHY port. It also sets +PLL configuration. + +USBPHYC + |_ PLL + | + |_ PHY port#1 _________________ HOST controller + | _ | + | / 1|________________| + |_ PHY port#2 ----| |________________ + | \_0| | + |_ UTMI switch_______| OTG controller + + +Phy provider node +================= + +Required properties: +- compatible: must be "st,stm32mp1-usbphyc" +- reg: address and length of the usb phy control register set +- clocks: phandle + clock specifier for the PLL phy clock +- #address-cells: number of address cells for phys sub-nodes, must be <1> +- #size-cells: number of size cells for phys sub-nodes, must be <0> + +Optional properties: +- assigned-clocks: phandle + clock specifier for the PLL phy clock +- assigned-clock-parents: the PLL phy clock parent +- resets: phandle + reset specifier + +Required nodes: one sub-node per port the controller provides. + +Phy sub-nodes +============== + +Required properties: +- reg: phy port index +- phy-supply: phandle to the regulator providing 3V3 power to the PHY, + see phy-bindings.txt in the same directory. +- vdda1v1-supply: phandle to the regulator providing 1V1 power to the PHY +- vdda1v8-supply: phandle to the regulator providing 1V8 power to the PHY +- #phy-cells: see phy-bindings.txt in the same directory, must be <0> for PHY + port#1 and must be <1> for PHY port#2, to select USB controller + + +Example: + usbphyc: usb-phy@5a006000 { + compatible = "st,stm32mp1-usbphyc"; + reg = <0x5a006000 0x1000>; + clocks = <&rcc_clk USBPHY_K>; + resets = <&rcc_rst USBPHY_R>; + #address-cells = <1>; + #size-cells = <0>; + + usbphyc_port0: usb-phy@0 { + reg = <0>; + phy-supply = <&vdd_usb>; + vdda1v1-supply = <®11>; + vdda1v8-supply = <®18> + #phy-cells = <0>; + }; + + usbphyc_port1: usb-phy@1 { + reg = <1>; + phy-supply = <&vdd_usb>; + vdda1v1-supply = <®11>; + vdda1v8-supply = <®18> + #phy-cells = <1>; + }; + }; -- cgit v1.2.3 From b1ba68f33c0caa937032615833e3321d03d877b4 Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda Date: Mon, 5 Mar 2018 14:32:43 +0900 Subject: dt-bindings: rcar-gen3-phy-usb2: Add bindings for r8a77965 This patch adds support for r8a77965 (R-Car M3-N). Signed-off-by: Yoshihiro Shimoda Reviewed-by: Geert Uytterhoeven Reviewed-by: Rob Herring Signed-off-by: Kishon Vijay Abraham I --- Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb2.txt | 2 ++ 1 file changed, 2 insertions(+) (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb2.txt b/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb2.txt index 99b651b33110..dbd137c079e2 100644 --- a/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb2.txt +++ b/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb2.txt @@ -8,6 +8,8 @@ Required properties: SoC. "renesas,usb2-phy-r8a7796" if the device is a part of an R8A7796 SoC. + "renesas,usb2-phy-r8a77965" if the device is a part of an + R8A77965 SoC. "renesas,usb2-phy-r8a77995" if the device is a part of an R8A77995 SoC. "renesas,rcar-gen3-usb2-phy" for a generic R-Car Gen3 compatible device. -- cgit v1.2.3 From 0147b57e731f128d68469d8032d53c0da89c8aae Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda Date: Mon, 5 Mar 2018 14:32:45 +0900 Subject: dt-bindings: rcar-gen3-phy-usb3: Add bindings for r8a77965 This patch adds bindings for r8a77965 (R-Car M3-N). Signed-off-by: Yoshihiro Shimoda Reviewed-by: Geert Uytterhoeven Reviewed-by: Rob Herring Signed-off-by: Kishon Vijay Abraham I --- Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb3.txt | 2 ++ 1 file changed, 2 insertions(+) (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb3.txt b/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb3.txt index f94cea48f6b1..47dd296ecead 100644 --- a/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb3.txt +++ b/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb3.txt @@ -11,6 +11,8 @@ Required properties: SoC. "renesas,r8a7796-usb3-phy" if the device is a part of an R8A7796 SoC. + "renesas,r8a77965-usb3-phy" if the device is a part of an + R8A77965 SoC. "renesas,rcar-gen3-usb3-phy" for a generic R-Car Gen3 compatible device. -- cgit v1.2.3 From d581ba62f34be6a190116523521875a021109b02 Mon Sep 17 00:00:00 2001 From: Pengcheng Li Date: Fri, 9 Mar 2018 22:47:00 +0800 Subject: dt-bindings: add bindings doc for HiSilicon INNO USB2 PHY It adds device tree bindings document for HiSilicon INNO USB2 PHY. Signed-off-by: Pengcheng Li Signed-off-by: Jiancheng Xue Signed-off-by: Shawn Guo Reviewed-by: Rob Herring Signed-off-by: Kishon Vijay Abraham I --- .../devicetree/bindings/phy/phy-hisi-inno-usb2.txt | 71 ++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 Documentation/devicetree/bindings/phy/phy-hisi-inno-usb2.txt (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/phy/phy-hisi-inno-usb2.txt b/Documentation/devicetree/bindings/phy/phy-hisi-inno-usb2.txt new file mode 100644 index 000000000000..0d70c8341095 --- /dev/null +++ b/Documentation/devicetree/bindings/phy/phy-hisi-inno-usb2.txt @@ -0,0 +1,71 @@ +Device tree bindings for HiSilicon INNO USB2 PHY + +Required properties: +- compatible: Should be one of the following strings: + "hisilicon,inno-usb2-phy", + "hisilicon,hi3798cv200-usb2-phy". +- reg: Should be the address space for PHY configuration register in peripheral + controller, e.g. PERI_USB0 for USB 2.0 PHY01 on Hi3798CV200 SoC. +- clocks: The phandle and clock specifier pair for INNO USB2 PHY device + reference clock. +- resets: The phandle and reset specifier pair for INNO USB2 PHY device reset + signal. +- #address-cells: Must be 1. +- #size-cells: Must be 0. + +The INNO USB2 PHY device should be a child node of peripheral controller that +contains the PHY configuration register, and each device suppports up to 2 PHY +ports which are represented as child nodes of INNO USB2 PHY device. + +Required properties for PHY port node: +- reg: The PHY port instance number. +- #phy-cells: Defined by generic PHY bindings. Must be 0. +- resets: The phandle and reset specifier pair for PHY port reset signal. + +Refer to phy/phy-bindings.txt for the generic PHY binding properties + +Example: + +perictrl: peripheral-controller@8a20000 { + compatible = "hisilicon,hi3798cv200-perictrl", "simple-mfd"; + reg = <0x8a20000 0x1000>; + #address-cells = <1>; + #size-cells = <1>; + ranges = <0x0 0x8a20000 0x1000>; + + usb2_phy1: usb2-phy@120 { + compatible = "hisilicon,hi3798cv200-usb2-phy"; + reg = <0x120 0x4>; + clocks = <&crg HISTB_USB2_PHY1_REF_CLK>; + resets = <&crg 0xbc 4>; + #address-cells = <1>; + #size-cells = <0>; + + usb2_phy1_port0: phy@0 { + reg = <0>; + #phy-cells = <0>; + resets = <&crg 0xbc 8>; + }; + + usb2_phy1_port1: phy@1 { + reg = <1>; + #phy-cells = <0>; + resets = <&crg 0xbc 9>; + }; + }; + + usb2_phy2: usb2-phy@124 { + compatible = "hisilicon,hi3798cv200-usb2-phy"; + reg = <0x124 0x4>; + clocks = <&crg HISTB_USB2_PHY2_REF_CLK>; + resets = <&crg 0xbc 6>; + #address-cells = <1>; + #size-cells = <0>; + + usb2_phy2_port0: phy@0 { + reg = <0>; + #phy-cells = <0>; + resets = <&crg 0xbc 10>; + }; + }; +}; -- cgit v1.2.3 From 5d1ebbda0318b1ba55eaa1fae3fd867af17b0774 Mon Sep 17 00:00:00 2001 From: Tony Lindgren Date: Thu, 8 Mar 2018 18:37:50 -0800 Subject: phy: mapphone-mdm6600: Add USB PHY driver for MDM6600 on Droid 4 Let's add support for the GPIO controlled USB PHY on the MDM6600 modem. It is used on some Motorola Mapphone series of phones and tablets such as Droid 4. The MDM6600 is hardwired to the first OHCI port in the Droid 4 case, and is controlled by several GPIOs. The USB PHY is integrated into the MDM6600 device it seems. We know this as we get L3 errors from omap-usb-host if trying to use the PHY before MDM6600 is configured. The GPIOs controlling MDM6600 are used to power device on and off, to configure the USB start-up mode (normal mode versus USB flashing), and they also tell the state of the MDM6600 device. The two start-up mode GPIOs are dual-purposed and used for out of band (OOB) wake-up for USB and TS 27.010 serial mux. But we need to configure the USB start-up mode first to get MDM6600 booted in the right mode to be usable in the first place. Note that the Motorola Mapphone Linux kernel tree has a "radio-ctrl" driver for modems. But it really does not control the radio at all, it just controls the modem power and start-up mode for USB. So I came to the conclusion that we're better off having this done in the USB PHY driver. For adding support for USB flashing mode, we can later on add a kernel module option for flash_mode=1 or something similar. Also note that currently there is no PM runtime support for the OHCI on omap variant SoCs. So for low(er) power idle states, currenty both ohci-platform and phy-mapphone-mdm6600 must be unloaded or unbound. For reference here is what I measured for total power consumption on an idle Droid 4 with and without USB related MDM6600 modules: idle lcd off phy-mapphone-mdm6600 ohci-platform 153mW 284mW 344mW So it seems that MDM6600 is currently not yet idling even with it's radio turned off, but that's something that is beyond the control of this USB PHY driver. This patch does get us to the point where modem data and GPS are usable with libqmi and ModemManager for example. Voice calls need more audio driver work. Cc: devicetree@vger.kernel.org Cc: Mark Rutland Cc: Marcel Partap Cc: Michael Scott Cc: Rob Herring Reviewed-by: Rob Herring Signed-off-by: Tony Lindgren Signed-off-by: Kishon Vijay Abraham I --- .../bindings/phy/phy-mapphone-mdm6600.txt | 29 ++ drivers/phy/motorola/Kconfig | 8 + drivers/phy/motorola/Makefile | 1 + drivers/phy/motorola/phy-mapphone-mdm6600.c | 542 +++++++++++++++++++++ 4 files changed, 580 insertions(+) create mode 100644 Documentation/devicetree/bindings/phy/phy-mapphone-mdm6600.txt create mode 100644 drivers/phy/motorola/phy-mapphone-mdm6600.c (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/phy/phy-mapphone-mdm6600.txt b/Documentation/devicetree/bindings/phy/phy-mapphone-mdm6600.txt new file mode 100644 index 000000000000..29427d4f047a --- /dev/null +++ b/Documentation/devicetree/bindings/phy/phy-mapphone-mdm6600.txt @@ -0,0 +1,29 @@ +Device tree binding documentation for Motorola Mapphone MDM6600 USB PHY + +Required properties: +- compatible Must be "motorola,mapphone-mdm6600" +- enable-gpios GPIO to enable the USB PHY +- power-gpios GPIO to power on the device +- reset-gpios GPIO to reset the device +- motorola,mode-gpios Two GPIOs to configure MDM6600 USB start-up mode for + normal mode versus USB flashing mode +- motorola,cmd-gpios Three GPIOs to control the power state of the MDM6600 +- motorola,status-gpios Three GPIOs to read the power state of the MDM6600 + +Example: + +usb-phy { + compatible = "motorola,mapphone-mdm6600"; + enable-gpios = <&gpio3 31 GPIO_ACTIVE_LOW>; + power-gpios = <&gpio2 22 GPIO_ACTIVE_HIGH>; + reset-gpios = <&gpio2 17 GPIO_ACTIVE_HIGH>; + motorola,mode-gpios = <&gpio5 20 GPIO_ACTIVE_HIGH>, + <&gpio5 21 GPIO_ACTIVE_HIGH>; + motorola,cmd-gpios = <&gpio4 7 GPIO_ACTIVE_HIGH>, + <&gpio4 8 GPIO_ACTIVE_HIGH>, + <&gpio5 14 GPIO_ACTIVE_HIGH>; + motorola,status-gpios = <&gpio2 20 GPIO_ACTIVE_HIGH>, + <&gpio2 21 GPIO_ACTIVE_HIGH>, + <&gpio2 23 GPIO_ACTIVE_HIGH>; + #phy-cells = <0>; +}; diff --git a/drivers/phy/motorola/Kconfig b/drivers/phy/motorola/Kconfig index 6bb7d6bdf1bf..82651524ffb9 100644 --- a/drivers/phy/motorola/Kconfig +++ b/drivers/phy/motorola/Kconfig @@ -10,3 +10,11 @@ config PHY_CPCAP_USB help Enable this for USB to work on Motorola phones and tablets such as Droid 4. + +config PHY_MAPPHONE_MDM6600 + tristate "Motorola Mapphone MDM6600 modem USB PHY driver" + depends on OF && USB_SUPPORT + select GENERIC_PHY + help + Enable this for MDM6600 USB modem to work on Motorola phones + and tablets such as Droid 4. diff --git a/drivers/phy/motorola/Makefile b/drivers/phy/motorola/Makefile index b6cd618d671a..3514f985b355 100644 --- a/drivers/phy/motorola/Makefile +++ b/drivers/phy/motorola/Makefile @@ -3,3 +3,4 @@ # obj-$(CONFIG_PHY_CPCAP_USB) += phy-cpcap-usb.o +obj-$(CONFIG_PHY_MAPPHONE_MDM6600) += phy-mapphone-mdm6600.o diff --git a/drivers/phy/motorola/phy-mapphone-mdm6600.c b/drivers/phy/motorola/phy-mapphone-mdm6600.c new file mode 100644 index 000000000000..5439dd90d0dd --- /dev/null +++ b/drivers/phy/motorola/phy-mapphone-mdm6600.c @@ -0,0 +1,542 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Motorola Mapphone MDM6600 modem GPIO controlled USB PHY driver + * Copyright (C) 2018 Tony Lindgren + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define PHY_MDM6600_PHY_DELAY_MS 4000 /* PHY enable 2.2s to 3.5s */ +#define PHY_MDM6600_ENABLED_DELAY_MS 8000 /* 8s more total for MDM6600 */ + +enum phy_mdm6600_ctrl_lines { + PHY_MDM6600_ENABLE, /* USB PHY enable */ + PHY_MDM6600_POWER, /* Device power */ + PHY_MDM6600_RESET, /* Device reset */ + PHY_MDM6600_NR_CTRL_LINES, +}; + +enum phy_mdm6600_bootmode_lines { + PHY_MDM6600_MODE0, /* out USB mode0 and OOB wake */ + PHY_MDM6600_MODE1, /* out USB mode1, in OOB wake */ + PHY_MDM6600_NR_MODE_LINES, +}; + +enum phy_mdm6600_cmd_lines { + PHY_MDM6600_CMD0, + PHY_MDM6600_CMD1, + PHY_MDM6600_CMD2, + PHY_MDM6600_NR_CMD_LINES, +}; + +enum phy_mdm6600_status_lines { + PHY_MDM6600_STATUS0, + PHY_MDM6600_STATUS1, + PHY_MDM6600_STATUS2, + PHY_MDM6600_NR_STATUS_LINES, +}; + +/* + * MDM6600 command codes. These are based on Motorola Mapphone Linux + * kernel tree. + */ +enum phy_mdm6600_cmd { + PHY_MDM6600_CMD_BP_PANIC_ACK, + PHY_MDM6600_CMD_DATA_ONLY_BYPASS, /* Reroute USB to CPCAP PHY */ + PHY_MDM6600_CMD_FULL_BYPASS, /* Reroute USB to CPCAP PHY */ + PHY_MDM6600_CMD_NO_BYPASS, /* Request normal USB mode */ + PHY_MDM6600_CMD_BP_SHUTDOWN_REQ, /* Request device power off */ + PHY_MDM6600_CMD_BP_UNKNOWN_5, + PHY_MDM6600_CMD_BP_UNKNOWN_6, + PHY_MDM6600_CMD_UNDEFINED, +}; + +/* + * MDM6600 status codes. These are based on Motorola Mapphone Linux + * kernel tree. + */ +enum phy_mdm6600_status { + PHY_MDM6600_STATUS_PANIC, /* Seems to be really off */ + PHY_MDM6600_STATUS_PANIC_BUSY_WAIT, + PHY_MDM6600_STATUS_QC_DLOAD, + PHY_MDM6600_STATUS_RAM_DOWNLOADER, /* MDM6600 USB flashing mode */ + PHY_MDM6600_STATUS_PHONE_CODE_AWAKE, /* MDM6600 normal USB mode */ + PHY_MDM6600_STATUS_PHONE_CODE_ASLEEP, + PHY_MDM6600_STATUS_SHUTDOWN_ACK, + PHY_MDM6600_STATUS_UNDEFINED, +}; + +static const char * const +phy_mdm6600_status_name[] = { + "off", "busy", "qc_dl", "ram_dl", "awake", + "asleep", "shutdown", "undefined", +}; + +struct phy_mdm6600 { + struct device *dev; + struct phy *generic_phy; + struct phy_provider *phy_provider; + struct gpio_desc *ctrl_gpios[PHY_MDM6600_NR_CTRL_LINES]; + struct gpio_descs *mode_gpios; + struct gpio_descs *status_gpios; + struct gpio_descs *cmd_gpios; + struct delayed_work bootup_work; + struct delayed_work status_work; + struct completion ack; + bool enabled; /* mdm6600 phy enabled */ + bool running; /* mdm6600 boot done */ + int status; +}; + +static int phy_mdm6600_init(struct phy *x) +{ + struct phy_mdm6600 *ddata = phy_get_drvdata(x); + struct gpio_desc *enable_gpio = ddata->ctrl_gpios[PHY_MDM6600_ENABLE]; + + if (!ddata->enabled) + return -EPROBE_DEFER; + + gpiod_set_value_cansleep(enable_gpio, 0); + + return 0; +} + +static int phy_mdm6600_power_on(struct phy *x) +{ + struct phy_mdm6600 *ddata = phy_get_drvdata(x); + struct gpio_desc *enable_gpio = ddata->ctrl_gpios[PHY_MDM6600_ENABLE]; + + if (!ddata->enabled) + return -ENODEV; + + gpiod_set_value_cansleep(enable_gpio, 1); + + return 0; +} + +static int phy_mdm6600_power_off(struct phy *x) +{ + struct phy_mdm6600 *ddata = phy_get_drvdata(x); + struct gpio_desc *enable_gpio = ddata->ctrl_gpios[PHY_MDM6600_ENABLE]; + + if (!ddata->enabled) + return -ENODEV; + + gpiod_set_value_cansleep(enable_gpio, 0); + + return 0; +} + +static const struct phy_ops gpio_usb_ops = { + .init = phy_mdm6600_init, + .power_on = phy_mdm6600_power_on, + .power_off = phy_mdm6600_power_off, + .owner = THIS_MODULE, +}; + +/** + * phy_mdm6600_cmd() - send a command request to mdm6600 + * @ddata: device driver data + * + * Configures the three command request GPIOs to the specified value. + */ +static void phy_mdm6600_cmd(struct phy_mdm6600 *ddata, int val) +{ + int values[PHY_MDM6600_NR_CMD_LINES]; + int i; + + val &= (1 << PHY_MDM6600_NR_CMD_LINES) - 1; + for (i = 0; i < PHY_MDM6600_NR_CMD_LINES; i++) + values[i] = (val & BIT(i)) >> i; + + gpiod_set_array_value_cansleep(PHY_MDM6600_NR_CMD_LINES, + ddata->cmd_gpios->desc, values); +} + +/** + * phy_mdm6600_status() - read mdm6600 status lines + * @ddata: device driver data + */ +static void phy_mdm6600_status(struct work_struct *work) +{ + struct phy_mdm6600 *ddata; + struct device *dev; + int values[PHY_MDM6600_NR_STATUS_LINES]; + int error, i, val = 0; + + ddata = container_of(work, struct phy_mdm6600, status_work.work); + dev = ddata->dev; + + error = gpiod_get_array_value_cansleep(PHY_MDM6600_NR_CMD_LINES, + ddata->status_gpios->desc, + values); + if (error) + return; + + for (i = 0; i < PHY_MDM6600_NR_CMD_LINES; i++) { + val |= values[i] << i; + dev_dbg(ddata->dev, "XXX %s: i: %i values[i]: %i val: %i\n", + __func__, i, values[i], val); + } + ddata->status = val; + + dev_info(dev, "modem status: %i %s\n", + ddata->status, + phy_mdm6600_status_name[ddata->status & 7]); + complete(&ddata->ack); +} + +static irqreturn_t phy_mdm6600_irq_thread(int irq, void *data) +{ + struct phy_mdm6600 *ddata = data; + + schedule_delayed_work(&ddata->status_work, msecs_to_jiffies(10)); + + return IRQ_HANDLED; +} + +/** + * phy_mdm6600_wakeirq_thread - handle mode1 line OOB wake after booting + * @irq: interrupt + * @data: interrupt handler data + * + * GPIO mode1 is used initially as output to configure the USB boot + * mode for mdm6600. After booting it is used as input for OOB wake + * signal from mdm6600 to the SoC. Just use it for debug info only + * for now. + */ +static irqreturn_t phy_mdm6600_wakeirq_thread(int irq, void *data) +{ + struct phy_mdm6600 *ddata = data; + struct gpio_desc *mode_gpio1; + + mode_gpio1 = ddata->mode_gpios->desc[PHY_MDM6600_MODE1]; + dev_dbg(ddata->dev, "OOB wake on mode_gpio1: %i\n", + gpiod_get_value(mode_gpio1)); + + return IRQ_HANDLED; +} + +/** + * phy_mdm6600_init_irq() - initialize mdm6600 status IRQ lines + * @ddata: device driver data + */ +static void phy_mdm6600_init_irq(struct phy_mdm6600 *ddata) +{ + struct device *dev = ddata->dev; + int i, error, irq; + + for (i = PHY_MDM6600_STATUS0; + i <= PHY_MDM6600_STATUS2; i++) { + struct gpio_desc *gpio = ddata->status_gpios->desc[i]; + + irq = gpiod_to_irq(gpio); + if (irq <= 0) + continue; + + error = devm_request_threaded_irq(dev, irq, NULL, + phy_mdm6600_irq_thread, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + "mdm6600", + ddata); + if (error) + dev_warn(dev, "no modem status irq%i: %i\n", + irq, error); + } +} + +struct phy_mdm6600_map { + const char *name; + int direction; +}; + +static const struct phy_mdm6600_map +phy_mdm6600_ctrl_gpio_map[PHY_MDM6600_NR_CTRL_LINES] = { + { "enable", GPIOD_OUT_LOW, }, /* low = phy disabled */ + { "power", GPIOD_OUT_LOW, }, /* low = off */ + { "reset", GPIOD_OUT_HIGH, }, /* high = reset */ +}; + +/** + * phy_mdm6600_init_lines() - initialize mdm6600 GPIO lines + * @ddata: device driver data + */ +static int phy_mdm6600_init_lines(struct phy_mdm6600 *ddata) +{ + struct device *dev = ddata->dev; + int i; + + /* MDM6600 control lines */ + for (i = 0; i < ARRAY_SIZE(phy_mdm6600_ctrl_gpio_map); i++) { + const struct phy_mdm6600_map *map = + &phy_mdm6600_ctrl_gpio_map[i]; + struct gpio_desc **gpio = &ddata->ctrl_gpios[i]; + + *gpio = devm_gpiod_get(dev, map->name, map->direction); + if (IS_ERR(*gpio)) { + dev_info(dev, "gpio %s error %li\n", + map->name, PTR_ERR(*gpio)); + return PTR_ERR(*gpio); + } + } + + /* MDM6600 USB start-up mode output lines */ + ddata->mode_gpios = devm_gpiod_get_array(dev, "motorola,mode", + GPIOD_OUT_LOW); + if (IS_ERR(ddata->mode_gpios)) + return PTR_ERR(ddata->mode_gpios); + + if (ddata->mode_gpios->ndescs != PHY_MDM6600_NR_MODE_LINES) + return -EINVAL; + + /* MDM6600 status input lines */ + ddata->status_gpios = devm_gpiod_get_array(dev, "motorola,status", + GPIOD_IN); + if (IS_ERR(ddata->status_gpios)) + return PTR_ERR(ddata->status_gpios); + + if (ddata->status_gpios->ndescs != PHY_MDM6600_NR_STATUS_LINES) + return -EINVAL; + + /* MDM6600 cmd output lines */ + ddata->cmd_gpios = devm_gpiod_get_array(dev, "motorola,cmd", + GPIOD_OUT_LOW); + if (IS_ERR(ddata->cmd_gpios)) + return PTR_ERR(ddata->cmd_gpios); + + if (ddata->cmd_gpios->ndescs != PHY_MDM6600_NR_CMD_LINES) + return -EINVAL; + + return 0; +} + +/** + * phy_mdm6600_device_power_on() - power on mdm6600 device + * @ddata: device driver data + * + * To get the integrated USB phy in MDM6600 takes some hoops. We must ensure + * the shared USB bootmode GPIOs are configured, then request modem start-up, + * reset and power-up.. And then we need to recycle the shared USB bootmode + * GPIOs as they are also used for Out of Band (OOB) wake for the USB and + * TS 27.010 serial mux. + */ +static int phy_mdm6600_device_power_on(struct phy_mdm6600 *ddata) +{ + struct gpio_desc *mode_gpio0, *mode_gpio1, *reset_gpio, *power_gpio; + int error = 0, wakeirq; + + mode_gpio0 = ddata->mode_gpios->desc[PHY_MDM6600_MODE0]; + mode_gpio1 = ddata->mode_gpios->desc[PHY_MDM6600_MODE1]; + reset_gpio = ddata->ctrl_gpios[PHY_MDM6600_RESET]; + power_gpio = ddata->ctrl_gpios[PHY_MDM6600_POWER]; + + /* + * Shared GPIOs must be low for normal USB mode. After booting + * they are used for OOB wake signaling. These can be also used + * to configure USB flashing mode later on based on a module + * parameter. + */ + gpiod_set_value_cansleep(mode_gpio0, 0); + gpiod_set_value_cansleep(mode_gpio1, 0); + + /* Request start-up mode */ + phy_mdm6600_cmd(ddata, PHY_MDM6600_CMD_NO_BYPASS); + + /* Request a reset first */ + gpiod_set_value_cansleep(reset_gpio, 0); + msleep(100); + + /* Toggle power GPIO to request mdm6600 to start */ + gpiod_set_value_cansleep(power_gpio, 1); + msleep(100); + gpiod_set_value_cansleep(power_gpio, 0); + + /* + * Looks like the USB PHY needs between 2.2 to 4 seconds. + * If we try to use it before that, we will get L3 errors + * from omap-usb-host trying to access the PHY. See also + * phy_mdm6600_init() for -EPROBE_DEFER. + */ + msleep(PHY_MDM6600_PHY_DELAY_MS); + ddata->enabled = true; + + /* Booting up the rest of MDM6600 will take total about 8 seconds */ + dev_info(ddata->dev, "Waiting for power up request to complete..\n"); + if (wait_for_completion_timeout(&ddata->ack, + msecs_to_jiffies(PHY_MDM6600_ENABLED_DELAY_MS))) { + if (ddata->status > PHY_MDM6600_STATUS_PANIC && + ddata->status < PHY_MDM6600_STATUS_SHUTDOWN_ACK) + dev_info(ddata->dev, "Powered up OK\n"); + } else { + ddata->enabled = false; + error = -ETIMEDOUT; + dev_err(ddata->dev, "Timed out powering up\n"); + } + + /* Reconfigure mode1 GPIO as input for OOB wake */ + gpiod_direction_input(mode_gpio1); + + wakeirq = gpiod_to_irq(mode_gpio1); + if (wakeirq <= 0) + return wakeirq; + + error = devm_request_threaded_irq(ddata->dev, wakeirq, NULL, + phy_mdm6600_wakeirq_thread, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + "mdm6600-wake", + ddata); + if (error) + dev_warn(ddata->dev, "no modem wakeirq irq%i: %i\n", + wakeirq, error); + + ddata->running = true; + + return error; +} + +/** + * phy_mdm6600_device_power_off() - power off mdm6600 device + * @ddata: device driver data + */ +static void phy_mdm6600_device_power_off(struct phy_mdm6600 *ddata) +{ + struct gpio_desc *reset_gpio = + ddata->ctrl_gpios[PHY_MDM6600_RESET]; + + ddata->enabled = false; + phy_mdm6600_cmd(ddata, PHY_MDM6600_CMD_BP_SHUTDOWN_REQ); + msleep(100); + + gpiod_set_value_cansleep(reset_gpio, 1); + + dev_info(ddata->dev, "Waiting for power down request to complete.. "); + if (wait_for_completion_timeout(&ddata->ack, + msecs_to_jiffies(5000))) { + if (ddata->status == PHY_MDM6600_STATUS_PANIC) + dev_info(ddata->dev, "Powered down OK\n"); + } else { + dev_err(ddata->dev, "Timed out powering down\n"); + } +} + +static void phy_mdm6600_deferred_power_on(struct work_struct *work) +{ + struct phy_mdm6600 *ddata; + int error; + + ddata = container_of(work, struct phy_mdm6600, bootup_work.work); + + error = phy_mdm6600_device_power_on(ddata); + if (error) + dev_err(ddata->dev, "Device not functional\n"); +} + +static const struct of_device_id phy_mdm6600_id_table[] = { + { .compatible = "motorola,mapphone-mdm6600", }, + {}, +}; +MODULE_DEVICE_TABLE(of, phy_mdm6600_id_table); + +static int phy_mdm6600_probe(struct platform_device *pdev) +{ + struct phy_mdm6600 *ddata; + int error; + + ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL); + if (!ddata) + return -ENOMEM; + + INIT_DELAYED_WORK(&ddata->bootup_work, + phy_mdm6600_deferred_power_on); + INIT_DELAYED_WORK(&ddata->status_work, phy_mdm6600_status); + init_completion(&ddata->ack); + + ddata->dev = &pdev->dev; + platform_set_drvdata(pdev, ddata); + + error = phy_mdm6600_init_lines(ddata); + if (error) + return error; + + phy_mdm6600_init_irq(ddata); + + ddata->generic_phy = devm_phy_create(ddata->dev, NULL, &gpio_usb_ops); + if (IS_ERR(ddata->generic_phy)) { + error = PTR_ERR(ddata->generic_phy); + goto cleanup; + } + + phy_set_drvdata(ddata->generic_phy, ddata); + + ddata->phy_provider = + devm_of_phy_provider_register(ddata->dev, + of_phy_simple_xlate); + if (IS_ERR(ddata->phy_provider)) { + error = PTR_ERR(ddata->phy_provider); + goto cleanup; + } + + schedule_delayed_work(&ddata->bootup_work, 0); + + /* + * See phy_mdm6600_device_power_on(). We should be able + * to remove this eventually when ohci-platform can deal + * with -EPROBE_DEFER. + */ + msleep(PHY_MDM6600_PHY_DELAY_MS + 500); + + return 0; + +cleanup: + phy_mdm6600_device_power_off(ddata); + return error; +} + +static int phy_mdm6600_remove(struct platform_device *pdev) +{ + struct phy_mdm6600 *ddata = platform_get_drvdata(pdev); + struct gpio_desc *reset_gpio = ddata->ctrl_gpios[PHY_MDM6600_RESET]; + + if (!ddata->running) + wait_for_completion_timeout(&ddata->ack, + msecs_to_jiffies(PHY_MDM6600_ENABLED_DELAY_MS)); + + gpiod_set_value_cansleep(reset_gpio, 1); + phy_mdm6600_device_power_off(ddata); + + cancel_delayed_work_sync(&ddata->bootup_work); + cancel_delayed_work_sync(&ddata->status_work); + + return 0; +} + +static struct platform_driver phy_mdm6600_driver = { + .probe = phy_mdm6600_probe, + .remove = phy_mdm6600_remove, + .driver = { + .name = "phy-mapphone-mdm6600", + .of_match_table = of_match_ptr(phy_mdm6600_id_table), + }, +}; + +module_platform_driver(phy_mdm6600_driver); + +MODULE_ALIAS("platform:gpio_usb"); +MODULE_AUTHOR("Tony Lindgren "); +MODULE_DESCRIPTION("mdm6600 gpio usb phy driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From 6912d4f40bc7b8a40604f2fa02bb5545e39d1a35 Mon Sep 17 00:00:00 2001 From: Enric Balletbo i Serra Date: Fri, 16 Feb 2018 13:09:52 +0100 Subject: dt-bindings: phy-rockchip-typec: deprecate some register properties. As now the following register properties are in the driver, document as deprecated these properties and recommend to not use them on new bindings. The deprecated properties are: - rockchip,typec-conn-dir : the register of type-c connector direction - rockchip,usb3tousb2-en : the register of type-c force usb3 to usb2 enable control. - rockchip,external-psm : the register of type-c phy external psm clock selection. - rockchip,pipe-status : the register of type-c phy pipe status. Signed-off-by: Enric Balletbo i Serra Reviewed-by: Heiko Stuebner Reviewed-by: Rob Herring Signed-off-by: Kishon Vijay Abraham I --- .../devicetree/bindings/phy/phy-rockchip-typec.txt | 33 +++++----------------- 1 file changed, 7 insertions(+), 26 deletions(-) (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/phy/phy-rockchip-typec.txt b/Documentation/devicetree/bindings/phy/phy-rockchip-typec.txt index 6ea867e3176f..f95cd492d6fc 100644 --- a/Documentation/devicetree/bindings/phy/phy-rockchip-typec.txt +++ b/Documentation/devicetree/bindings/phy/phy-rockchip-typec.txt @@ -16,24 +16,6 @@ Required properties: "uphy", "uphy-pipe", "uphy-tcphy" - extcon : extcon specifier for the Power Delivery -Note, there are 2 type-c phys for RK3399, and they are almost identical, except -these registers(description below), every register node contains 3 sections: -offset, enable bit, write mask bit. - - rockchip,typec-conn-dir : the register of type-c connector direction, - for type-c phy0, it must be <0xe580 0 16>; - for type-c phy1, it must be <0xe58c 0 16>; - - rockchip,usb3tousb2-en : the register of type-c force usb3 to usb2 enable - control. - for type-c phy0, it must be <0xe580 3 19>; - for type-c phy1, it must be <0xe58c 3 19>; - - rockchip,external-psm : the register of type-c phy external psm clock - selection. - for type-c phy0, it must be <0xe588 14 30>; - for type-c phy1, it must be <0xe594 14 30>; - - rockchip,pipe-status : the register of type-c phy pipe status. - for type-c phy0, it must be <0xe5c0 0 0>; - for type-c phy1, it must be <0xe5c0 16 16>; - Required nodes : a sub-node is required for each port the phy provides. The sub-node name is used to identify dp or usb3 port, and shall be the following entries: @@ -43,6 +25,13 @@ Required nodes : a sub-node is required for each port the phy provides. Required properties (port (child) node): - #phy-cells : must be 0, See ./phy-bindings.txt for details. +Deprecated properties, do not use in new device tree sources, these +properties are determined by the compatible value: + - rockchip,typec-conn-dir + - rockchip,usb3tousb2-en + - rockchip,external-psm + - rockchip,pipe-status + Example: tcphy0: phy@ff7c0000 { compatible = "rockchip,rk3399-typec-phy"; @@ -58,10 +47,6 @@ Example: <&cru SRST_UPHY0_PIPE_L00>, <&cru SRST_P_UPHY0_TCPHY>; reset-names = "uphy", "uphy-pipe", "uphy-tcphy"; - rockchip,typec-conn-dir = <0xe580 0 16>; - rockchip,usb3tousb2-en = <0xe580 3 19>; - rockchip,external-psm = <0xe588 14 30>; - rockchip,pipe-status = <0xe5c0 0 0>; tcphy0_dp: dp-port { #phy-cells = <0>; @@ -86,10 +71,6 @@ Example: <&cru SRST_UPHY1_PIPE_L00>, <&cru SRST_P_UPHY1_TCPHY>; reset-names = "uphy", "uphy-pipe", "uphy-tcphy"; - rockchip,typec-conn-dir = <0xe58c 0 16>; - rockchip,usb3tousb2-en = <0xe58c 3 19>; - rockchip,external-psm = <0xe594 14 30>; - rockchip,pipe-status = <0xe5c0 16 16>; tcphy1_dp: dp-port { #phy-cells = <0>; -- cgit v1.2.3 From 21bf9bc2a99e7b519b0035155209aeea2bc90696 Mon Sep 17 00:00:00 2001 From: Enric Balletbo i Serra Date: Thu, 1 Mar 2018 16:25:11 +0100 Subject: dt-bindings: phy-rockchip-typec: move extcon property to be optional. The extcon property is used to detect the cable-state but some boards just connect the type-c phy to a regular USB-A connector without any power-delivery and thus no controller reporting the cable-state. So the extcon property is not really a required property, move it to be optional instead. Signed-off-by: Enric Balletbo i Serra Reviewed-by: Heiko Stuebner Signed-off-by: Kishon Vijay Abraham I --- Documentation/devicetree/bindings/phy/phy-rockchip-typec.txt | 2 ++ 1 file changed, 2 insertions(+) (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/phy/phy-rockchip-typec.txt b/Documentation/devicetree/bindings/phy/phy-rockchip-typec.txt index f95cd492d6fc..960da7fcaa9e 100644 --- a/Documentation/devicetree/bindings/phy/phy-rockchip-typec.txt +++ b/Documentation/devicetree/bindings/phy/phy-rockchip-typec.txt @@ -14,6 +14,8 @@ Required properties: - resets : a list of phandle + reset specifier pairs - reset-names : string reset name, must be: "uphy", "uphy-pipe", "uphy-tcphy" + +Optional properties: - extcon : extcon specifier for the Power Delivery Required nodes : a sub-node is required for each port the phy provides. -- cgit v1.2.3 From 027bd6cafd9a1e3a109b5e5682c85ac84e804a8d Mon Sep 17 00:00:00 2001 From: Kai-Heng Feng Date: Tue, 20 Mar 2018 00:26:06 +0800 Subject: usb: core: Add "quirks" parameter for usbcore Trying quirks in usbcore needs to rebuild the driver or the entire kernel if it's builtin. It can save a lot of time if usbcore has similar ability like "usbhid.quirks=" and "usb-storage.quirks=". Rename the original quirk detection function to "static" as we introduce this new "dynamic" function. Now users can use "usbcore.quirks=" as short term workaround before the next kernel release. Also, the quirk parameter can XOR the builtin quirks for debugging purpose. This is inspired by usbhid and usb-storage. Signed-off-by: Kai-Heng Feng Signed-off-by: Greg Kroah-Hartman --- Documentation/admin-guide/kernel-parameters.txt | 56 ++++++++ drivers/usb/core/quirks.c | 178 +++++++++++++++++++++++- drivers/usb/core/usb.c | 1 + drivers/usb/core/usb.h | 1 + 4 files changed, 231 insertions(+), 5 deletions(-) (limited to 'Documentation') diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index 1d1d53f85ddd..e00cdd313dc2 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -4368,6 +4368,62 @@ usbcore.nousb [USB] Disable the USB subsystem + usbcore.quirks= + [USB] A list of quirk entries to augment the built-in + usb core quirk list. List entries are separated by + commas. Each entry has the form + VendorID:ProductID:Flags. The IDs are 4-digit hex + numbers and Flags is a set of letters. Each letter + will change the built-in quirk; setting it if it is + clear and clearing it if it is set. The letters have + the following meanings: + a = USB_QUIRK_STRING_FETCH_255 (string + descriptors must not be fetched using + a 255-byte read); + b = USB_QUIRK_RESET_RESUME (device can't resume + correctly so reset it instead); + c = USB_QUIRK_NO_SET_INTF (device can't handle + Set-Interface requests); + d = USB_QUIRK_CONFIG_INTF_STRINGS (device can't + handle its Configuration or Interface + strings); + e = USB_QUIRK_RESET (device can't be reset + (e.g morph devices), don't use reset); + f = USB_QUIRK_HONOR_BNUMINTERFACES (device has + more interface descriptions than the + bNumInterfaces count, and can't handle + talking to these interfaces); + g = USB_QUIRK_DELAY_INIT (device needs a pause + during initialization, after we read + the device descriptor); + h = USB_QUIRK_LINEAR_UFRAME_INTR_BINTERVAL (For + high speed and super speed interrupt + endpoints, the USB 2.0 and USB 3.0 spec + require the interval in microframes (1 + microframe = 125 microseconds) to be + calculated as interval = 2 ^ + (bInterval-1). + Devices with this quirk report their + bInterval as the result of this + calculation instead of the exponent + variable used in the calculation); + i = USB_QUIRK_DEVICE_QUALIFIER (device can't + handle device_qualifier descriptor + requests); + j = USB_QUIRK_IGNORE_REMOTE_WAKEUP (device + generates spurious wakeup, ignore + remote wakeup capability); + k = USB_QUIRK_NO_LPM (device can't handle Link + Power Management); + l = USB_QUIRK_LINEAR_FRAME_INTR_BINTERVAL + (Device reports its bInterval as linear + frames instead of the USB 2.0 + calculation); + m = USB_QUIRK_DISCONNECT_SUSPEND (Device needs + to be disconnected before suspend to + prevent spurious wakeup) + Example: quirks=0781:5580:bk,0a5c:5834:gij + usbhid.mousepoll= [USBHID] The interval which mice are to be polled at. diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c index 54b019e267c5..6fb8d5433268 100644 --- a/drivers/usb/core/quirks.c +++ b/drivers/usb/core/quirks.c @@ -6,11 +6,149 @@ * Copyright (c) 2007 Greg Kroah-Hartman */ +#include #include #include #include #include "usb.h" +struct quirk_entry { + u16 vid; + u16 pid; + u32 flags; +}; + +static DEFINE_MUTEX(quirk_mutex); + +static struct quirk_entry *quirk_list; +static unsigned int quirk_count; + +static char quirks_param[128]; + +static int quirks_param_set(const char *val, const struct kernel_param *kp) +{ + char *p, *field; + u16 vid, pid; + u32 flags; + size_t i; + + mutex_lock(&quirk_mutex); + + if (!val || !*val) { + quirk_count = 0; + kfree(quirk_list); + quirk_list = NULL; + goto unlock; + } + + for (quirk_count = 1, i = 0; val[i]; i++) + if (val[i] == ',') + quirk_count++; + + if (quirk_list) { + kfree(quirk_list); + quirk_list = NULL; + } + + quirk_list = kcalloc(quirk_count, sizeof(struct quirk_entry), + GFP_KERNEL); + if (!quirk_list) { + mutex_unlock(&quirk_mutex); + return -ENOMEM; + } + + for (i = 0, p = (char *)val; p && *p;) { + /* Each entry consists of VID:PID:flags */ + field = strsep(&p, ":"); + if (!field) + break; + + if (kstrtou16(field, 16, &vid)) + break; + + field = strsep(&p, ":"); + if (!field) + break; + + if (kstrtou16(field, 16, &pid)) + break; + + field = strsep(&p, ","); + if (!field || !*field) + break; + + /* Collect the flags */ + for (flags = 0; *field; field++) { + switch (*field) { + case 'a': + flags |= USB_QUIRK_STRING_FETCH_255; + break; + case 'b': + flags |= USB_QUIRK_RESET_RESUME; + break; + case 'c': + flags |= USB_QUIRK_NO_SET_INTF; + break; + case 'd': + flags |= USB_QUIRK_CONFIG_INTF_STRINGS; + break; + case 'e': + flags |= USB_QUIRK_RESET; + break; + case 'f': + flags |= USB_QUIRK_HONOR_BNUMINTERFACES; + break; + case 'g': + flags |= USB_QUIRK_DELAY_INIT; + break; + case 'h': + flags |= USB_QUIRK_LINEAR_UFRAME_INTR_BINTERVAL; + break; + case 'i': + flags |= USB_QUIRK_DEVICE_QUALIFIER; + break; + case 'j': + flags |= USB_QUIRK_IGNORE_REMOTE_WAKEUP; + break; + case 'k': + flags |= USB_QUIRK_NO_LPM; + break; + case 'l': + flags |= USB_QUIRK_LINEAR_FRAME_INTR_BINTERVAL; + break; + case 'm': + flags |= USB_QUIRK_DISCONNECT_SUSPEND; + break; + /* Ignore unrecognized flag characters */ + } + } + + quirk_list[i++] = (struct quirk_entry) + { .vid = vid, .pid = pid, .flags = flags }; + } + + if (i < quirk_count) + quirk_count = i; + +unlock: + mutex_unlock(&quirk_mutex); + + return param_set_copystring(val, kp); +} + +static const struct kernel_param_ops quirks_param_ops = { + .set = quirks_param_set, + .get = param_get_string, +}; + +static struct kparam_string quirks_param_string = { + .maxlen = sizeof(quirks_param), + .string = quirks_param, +}; + +module_param_cb(quirks, &quirks_param_ops, &quirks_param_string, 0644); +MODULE_PARM_DESC(quirks, "Add/modify USB quirks by specifying quirks=vendorID:productID:quirks"); + /* Lists of quirky USB devices, split in device quirks and interface quirks. * Device quirks are applied at the very beginning of the enumeration process, * right after reading the device descriptor. They can thus only match on device @@ -321,8 +459,8 @@ static int usb_amd_resume_quirk(struct usb_device *udev) return 0; } -static u32 __usb_detect_quirks(struct usb_device *udev, - const struct usb_device_id *id) +static u32 usb_detect_static_quirks(struct usb_device *udev, + const struct usb_device_id *id) { u32 quirks = 0; @@ -340,21 +478,43 @@ static u32 __usb_detect_quirks(struct usb_device *udev, return quirks; } +static u32 usb_detect_dynamic_quirks(struct usb_device *udev) +{ + u16 vid = le16_to_cpu(udev->descriptor.idVendor); + u16 pid = le16_to_cpu(udev->descriptor.idProduct); + int i, flags = 0; + + mutex_lock(&quirk_mutex); + + for (i = 0; i < quirk_count; i++) { + if (vid == quirk_list[i].vid && pid == quirk_list[i].pid) { + flags = quirk_list[i].flags; + break; + } + } + + mutex_unlock(&quirk_mutex); + + return flags; +} + /* * Detect any quirks the device has, and do any housekeeping for it if needed. */ void usb_detect_quirks(struct usb_device *udev) { - udev->quirks = __usb_detect_quirks(udev, usb_quirk_list); + udev->quirks = usb_detect_static_quirks(udev, usb_quirk_list); /* * Pixart-based mice would trigger remote wakeup issue on AMD * Yangtze chipset, so set them as RESET_RESUME flag. */ if (usb_amd_resume_quirk(udev)) - udev->quirks |= __usb_detect_quirks(udev, + udev->quirks |= usb_detect_static_quirks(udev, usb_amd_resume_quirk_list); + udev->quirks ^= usb_detect_dynamic_quirks(udev); + if (udev->quirks) dev_dbg(&udev->dev, "USB quirks for this device: %x\n", udev->quirks); @@ -373,7 +533,7 @@ void usb_detect_interface_quirks(struct usb_device *udev) { u32 quirks; - quirks = __usb_detect_quirks(udev, usb_interface_quirk_list); + quirks = usb_detect_static_quirks(udev, usb_interface_quirk_list); if (quirks == 0) return; @@ -381,3 +541,11 @@ void usb_detect_interface_quirks(struct usb_device *udev) quirks); udev->quirks |= quirks; } + +void usb_release_quirk_list(void) +{ + mutex_lock(&quirk_mutex); + kfree(quirk_list); + quirk_list = NULL; + mutex_unlock(&quirk_mutex); +} diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c index 2f5fbc56a9dd..0adb6345ff2e 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -1259,6 +1259,7 @@ static void __exit usb_exit(void) if (usb_disabled()) return; + usb_release_quirk_list(); usb_deregister_device_driver(&usb_generic_driver); usb_major_cleanup(); usb_deregister(&usbfs_driver); diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h index 149cc7480971..546a2219454b 100644 --- a/drivers/usb/core/usb.h +++ b/drivers/usb/core/usb.h @@ -36,6 +36,7 @@ extern void usb_deauthorize_interface(struct usb_interface *); extern void usb_authorize_interface(struct usb_interface *); extern void usb_detect_quirks(struct usb_device *udev); extern void usb_detect_interface_quirks(struct usb_device *udev); +extern void usb_release_quirk_list(void); extern int usb_remove_device(struct usb_device *udev); extern int usb_get_device_descriptor(struct usb_device *dev, -- cgit v1.2.3 From 48f80609e536bc9be12c64e4c6c69625baee7b02 Mon Sep 17 00:00:00 2001 From: Thinh Nguyen Date: Fri, 16 Mar 2018 15:35:38 -0700 Subject: usb: dwc3: Make TX/RX threshold configurable DWC_usb31 periodic transfer at 48K+ bytes per interval may need modification to the TX/RX packet threshold to achieve optimal result. Add properties to make it configurable. By default, periodic ESS TX and RX threshold are not enabled. To enable TX or RX threshold (host mode only), both packet threshold count and max burst size properties must be set to a valid non-zero value 1-16. DWC_usb31 programming guide section 1.2.3 and 1.2.4. Cc: John Youn Reviewed-by: Rob Herring Signed-off-by: Thinh Nguyen Signed-off-by: Felipe Balbi --- Documentation/devicetree/bindings/usb/dwc3.txt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/usb/dwc3.txt b/Documentation/devicetree/bindings/usb/dwc3.txt index 44e8bab159ad..0dbd3083e7dd 100644 --- a/Documentation/devicetree/bindings/usb/dwc3.txt +++ b/Documentation/devicetree/bindings/usb/dwc3.txt @@ -57,6 +57,22 @@ Optional properties: - snps,quirk-frame-length-adjustment: Value for GFLADJ_30MHZ field of GFLADJ register for post-silicon frame length adjustment when the fladj_30mhz_sdbnd signal is invalid or incorrect. + - snps,rx-thr-num-pkt-prd: periodic ESS RX packet threshold count - host mode + only. Set this and rx-max-burst-prd to a valid, + non-zero value 1-16 (DWC_usb31 programming guide + section 1.2.4) to enable periodic ESS RX threshold. + - snps,rx-max-burst-prd: max periodic ESS RX burst size - host mode only. Set + this and rx-thr-num-pkt-prd to a valid, non-zero value + 1-16 (DWC_usb31 programming guide section 1.2.4) to + enable periodic ESS RX threshold. + - snps,tx-thr-num-pkt-prd: periodic ESS TX packet threshold count - host mode + only. Set this and tx-max-burst-prd to a valid, + non-zero value 1-16 (DWC_usb31 programming guide + section 1.2.3) to enable periodic ESS TX threshold. + - snps,tx-max-burst-prd: max periodic ESS TX burst size - host mode only. Set + this and tx-thr-num-pkt-prd to a valid, non-zero value + 1-16 (DWC_usb31 programming guide section 1.2.3) to + enable periodic ESS TX threshold. - tx-fifo-resize: determines if the FIFO *has* to be reallocated. -- cgit v1.2.3 From 1cbd53c8cd85a63383a075347abee8f6e3f14fbe Mon Sep 17 00:00:00 2001 From: Richard Leitner Date: Tue, 20 Mar 2018 11:17:13 +0100 Subject: usb: core: introduce per-port over-current counters For some userspace applications information on the number of over-current conditions at specific USB hub ports is relevant. In our case we have a series of USB hardware (using the cp210x driver) which communicates using a proprietary protocol. These devices sometimes trigger an over-current situation on some hubs. In case of such an over-current situation the USB devices offer an interface for reducing the max used power. As these conditions are quite rare and imply performance reductions of the device we don't want to reduce the max power always. Therefore give user-space applications the possibility to react adequately by introducing an over_current_counter in the usb port struct which is exported via sysfs. Signed-off-by: Richard Leitner Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-bus-usb | 10 ++++++++++ drivers/usb/core/hub.c | 4 +++- drivers/usb/core/hub.h | 1 + drivers/usb/core/port.c | 10 ++++++++++ 4 files changed, 24 insertions(+), 1 deletion(-) (limited to 'Documentation') diff --git a/Documentation/ABI/testing/sysfs-bus-usb b/Documentation/ABI/testing/sysfs-bus-usb index 0bd731cbb50c..c702c78f24d8 100644 --- a/Documentation/ABI/testing/sysfs-bus-usb +++ b/Documentation/ABI/testing/sysfs-bus-usb @@ -189,6 +189,16 @@ Description: The file will read "hotplug", "wired" and "not used" if the information is available, and "unknown" otherwise. +What: /sys/bus/usb/devices/.../(hub interface)/portX/over_current_count +Date: February 2018 +Contact: Richard Leitner +Description: + Most hubs are able to detect over-current situations on their + ports and report them to the kernel. This attribute is to expose + the number of over-current situation occurred on a specific port + to user space. This file will contain an unsigned 32 bit value + which wraps to 0 after its maximum is reached. + What: /sys/bus/usb/devices/.../(hub interface)/portX/usb3_lpm_permit Date: November 2015 Contact: Lu Baolu diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index ac7bab772a3a..aaeef03c0d83 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -5104,8 +5104,10 @@ static void port_event(struct usb_hub *hub, int port1) if (portchange & USB_PORT_STAT_C_OVERCURRENT) { u16 status = 0, unused; + port_dev->over_current_count++; - dev_dbg(&port_dev->dev, "over-current change\n"); + dev_dbg(&port_dev->dev, "over-current change #%u\n", + port_dev->over_current_count); usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_OVER_CURRENT); msleep(100); /* Cool down */ diff --git a/drivers/usb/core/hub.h b/drivers/usb/core/hub.h index 2a700ccc868c..4dc769ee9c74 100644 --- a/drivers/usb/core/hub.h +++ b/drivers/usb/core/hub.h @@ -96,6 +96,7 @@ struct usb_port { enum usb_port_connect_type connect_type; usb_port_location_t location; struct mutex status_lock; + u32 over_current_count; u8 portnum; unsigned int is_superspeed:1; unsigned int usb3_lpm_u1_permit:1; diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c index 1a01e9ad3804..6979bde87d31 100644 --- a/drivers/usb/core/port.c +++ b/drivers/usb/core/port.c @@ -41,6 +41,15 @@ static ssize_t connect_type_show(struct device *dev, } static DEVICE_ATTR_RO(connect_type); +static ssize_t over_current_count_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_port *port_dev = to_usb_port(dev); + + return sprintf(buf, "%u\n", port_dev->over_current_count); +} +static DEVICE_ATTR_RO(over_current_count); + static ssize_t usb3_lpm_permit_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -109,6 +118,7 @@ static DEVICE_ATTR_RW(usb3_lpm_permit); static struct attribute *port_dev_attrs[] = { &dev_attr_connect_type.attr, + &dev_attr_over_current_count.attr, NULL, }; -- cgit v1.2.3 From f2d9b66d84f3ff5ea3aff111e6a403e04fa8bf37 Mon Sep 17 00:00:00 2001 From: Heikki Krogerus Date: Tue, 20 Mar 2018 15:57:02 +0300 Subject: drivers: base: Unified device connection lookup Several frameworks - clk, gpio, phy, pmw, etc. - maintain lookup tables for describing connections and provide custom API for handling them. This introduces a single generic lookup table and API for the connections. The motivation for this commit is centralizing the connection lookup, but the goal is to ultimately extract the connection descriptions also from firmware by using the fwnode_graph_* functions and other mechanisms that are available. Reviewed-by: Hans de Goede Reviewed-by: Andy Shevchenko Signed-off-by: Heikki Krogerus Signed-off-by: Greg Kroah-Hartman --- Documentation/driver-api/device_connection.rst | 43 ++++++++ drivers/base/Makefile | 3 +- drivers/base/devcon.c | 136 +++++++++++++++++++++++++ include/linux/device.h | 22 ++++ 4 files changed, 203 insertions(+), 1 deletion(-) create mode 100644 Documentation/driver-api/device_connection.rst create mode 100644 drivers/base/devcon.c (limited to 'Documentation') diff --git a/Documentation/driver-api/device_connection.rst b/Documentation/driver-api/device_connection.rst new file mode 100644 index 000000000000..affbc5566ab0 --- /dev/null +++ b/Documentation/driver-api/device_connection.rst @@ -0,0 +1,43 @@ +================== +Device connections +================== + +Introduction +------------ + +Devices often have connections to other devices that are outside of the direct +child/parent relationship. A serial or network communication controller, which +could be a PCI device, may need to be able to get a reference to its PHY +component, which could be attached for example to the I2C bus. Some device +drivers need to be able to control the clocks or the GPIOs for their devices, +and so on. + +Device connections are generic descriptions of any type of connection between +two separate devices. + +Device connections alone do not create a dependency between the two devices. +They are only descriptions which are not tied to either of the devices directly. +A dependency between the two devices exists only if one of the two endpoint +devices requests a reference to the other. The descriptions themselves can be +defined in firmware (not yet supported) or they can be built-in. + +Usage +----- + +Device connections should exist before device ``->probe`` callback is called for +either endpoint device in the description. If the connections are defined in +firmware, this is not a problem. It should be considered if the connection +descriptions are "built-in", and need to be added separately. + +The connection description consists of the names of the two devices with the +connection, i.e. the endpoints, and unique identifier for the connection which +is needed if there are multiple connections between the two devices. + +After a description exists, the devices in it can request reference to the other +endpoint device, or they can request the description itself. + +API +--- + +.. kernel-doc:: drivers/base/devcon.c + : functions: device_connection_find_match device_connection_find device_connection_add device_connection_remove diff --git a/drivers/base/Makefile b/drivers/base/Makefile index e32a52490051..12a7f64d35a9 100644 --- a/drivers/base/Makefile +++ b/drivers/base/Makefile @@ -5,7 +5,8 @@ obj-y := component.o core.o bus.o dd.o syscore.o \ driver.o class.o platform.o \ cpu.o firmware.o init.o map.o devres.o \ attribute_container.o transport_class.o \ - topology.o container.o property.o cacheinfo.o + topology.o container.o property.o cacheinfo.o \ + devcon.o obj-$(CONFIG_DEVTMPFS) += devtmpfs.o obj-$(CONFIG_DMA_CMA) += dma-contiguous.o obj-y += power/ diff --git a/drivers/base/devcon.c b/drivers/base/devcon.c new file mode 100644 index 000000000000..d427e806cd73 --- /dev/null +++ b/drivers/base/devcon.c @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: GPL-2.0 +/** + * Device connections + * + * Copyright (C) 2018 Intel Corporation + * Author: Heikki Krogerus + */ + +#include + +static DEFINE_MUTEX(devcon_lock); +static LIST_HEAD(devcon_list); + +/** + * device_connection_find_match - Find physical connection to a device + * @dev: Device with the connection + * @con_id: Identifier for the connection + * @data: Data for the match function + * @match: Function to check and convert the connection description + * + * Find a connection with unique identifier @con_id between @dev and another + * device. @match will be used to convert the connection description to data the + * caller is expecting to be returned. + */ +void *device_connection_find_match(struct device *dev, const char *con_id, + void *data, + void *(*match)(struct device_connection *con, + int ep, void *data)) +{ + const char *devname = dev_name(dev); + struct device_connection *con; + void *ret = NULL; + int ep; + + if (!match) + return NULL; + + mutex_lock(&devcon_lock); + + list_for_each_entry(con, &devcon_list, list) { + ep = match_string(con->endpoint, 2, devname); + if (ep < 0) + continue; + + if (con_id && strcmp(con->id, con_id)) + continue; + + ret = match(con, !ep, data); + if (ret) + break; + } + + mutex_unlock(&devcon_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(device_connection_find_match); + +extern struct bus_type platform_bus_type; +extern struct bus_type pci_bus_type; +extern struct bus_type i2c_bus_type; +extern struct bus_type spi_bus_type; + +static struct bus_type *generic_match_buses[] = { + &platform_bus_type, +#ifdef CONFIG_PCI + &pci_bus_type, +#endif +#ifdef CONFIG_I2C + &i2c_bus_type, +#endif +#ifdef CONFIG_SPI_MASTER + &spi_bus_type, +#endif + NULL, +}; + +/* This tries to find the device from the most common bus types by name. */ +static void *generic_match(struct device_connection *con, int ep, void *data) +{ + struct bus_type *bus; + struct device *dev; + + for (bus = generic_match_buses[0]; bus; bus++) { + dev = bus_find_device_by_name(bus, NULL, con->endpoint[ep]); + if (dev) + return dev; + } + + /* + * We only get called if a connection was found, tell the caller to + * wait for the other device to show up. + */ + return ERR_PTR(-EPROBE_DEFER); +} + +/** + * device_connection_find - Find two devices connected together + * @dev: Device with the connection + * @con_id: Identifier for the connection + * + * Find a connection with unique identifier @con_id between @dev and + * another device. On success returns handle to the device that is connected + * to @dev, with the reference count for the found device incremented. Returns + * NULL if no matching connection was found, or ERR_PTR(-EPROBE_DEFER) when a + * connection was found but the other device has not been enumerated yet. + */ +struct device *device_connection_find(struct device *dev, const char *con_id) +{ + return device_connection_find_match(dev, con_id, NULL, generic_match); +} +EXPORT_SYMBOL_GPL(device_connection_find); + +/** + * device_connection_add - Register a connection description + * @con: The connection description to be registered + */ +void device_connection_add(struct device_connection *con) +{ + mutex_lock(&devcon_lock); + list_add_tail(&con->list, &devcon_list); + mutex_unlock(&devcon_lock); +} +EXPORT_SYMBOL_GPL(device_connection_add); + +/** + * device_connections_remove - Unregister connection description + * @con: The connection description to be unregistered + */ +void device_connection_remove(struct device_connection *con) +{ + mutex_lock(&devcon_lock); + list_del(&con->list); + mutex_unlock(&devcon_lock); +} +EXPORT_SYMBOL_GPL(device_connection_remove); diff --git a/include/linux/device.h b/include/linux/device.h index b093405ed525..204ff64279fd 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -729,6 +729,28 @@ struct device_dma_parameters { unsigned long segment_boundary_mask; }; +/** + * struct device_connection - Device Connection Descriptor + * @endpoint: The names of the two devices connected together + * @id: Unique identifier for the connection + * @list: List head, private, for internal use only + */ +struct device_connection { + const char *endpoint[2]; + const char *id; + struct list_head list; +}; + +void *device_connection_find_match(struct device *dev, const char *con_id, + void *data, + void *(*match)(struct device_connection *con, + int ep, void *data)); + +struct device *device_connection_find(struct device *dev, const char *con_id); + +void device_connection_add(struct device_connection *con); +void device_connection_remove(struct device_connection *con); + /** * enum device_link_state - Device link states. * @DL_STATE_NONE: The presence of the drivers is not being tracked. -- cgit v1.2.3 From bdecb33af34f79cbfbb656661210f77c8b8b5b5f Mon Sep 17 00:00:00 2001 From: Heikki Krogerus Date: Tue, 20 Mar 2018 15:57:03 +0300 Subject: usb: typec: API for controlling USB Type-C Multiplexers USB Type-C connectors consist of various muxes and switches that route the pins on the connector to the right locations. The USB Type-C drivers need to be able to control the muxes, as they are the ones that know things like the cable plug orientation, and the current mode that was negotiated with the partner. This introduces a small API for registering and controlling cable plug orientation switches, and separate small API for registering and controlling pin multiplexer/demultiplexer switches that are needed with Accessory/Alternate Modes. Reviewed-by: Hans de Goede Reviewed-by: Andy Shevchenko Signed-off-by: Heikki Krogerus Signed-off-by: Greg Kroah-Hartman --- Documentation/driver-api/usb/typec.rst | 73 +- drivers/usb/typec/Makefile | 1 + drivers/usb/typec/class.c | 1435 ++++++++++++++++++++++++++++++++ drivers/usb/typec/mux.c | 191 +++++ drivers/usb/typec/typec.c | 1365 ------------------------------ include/linux/usb/typec.h | 14 + include/linux/usb/typec_mux.h | 55 ++ 7 files changed, 1758 insertions(+), 1376 deletions(-) create mode 100644 drivers/usb/typec/class.c create mode 100644 drivers/usb/typec/mux.c delete mode 100644 drivers/usb/typec/typec.c create mode 100644 include/linux/usb/typec_mux.h (limited to 'Documentation') diff --git a/Documentation/driver-api/usb/typec.rst b/Documentation/driver-api/usb/typec.rst index 8a7249f2ff04..feb31946490b 100644 --- a/Documentation/driver-api/usb/typec.rst +++ b/Documentation/driver-api/usb/typec.rst @@ -61,7 +61,7 @@ Registering the ports The port drivers will describe every Type-C port they control with struct typec_capability data structure, and register them with the following API: -.. kernel-doc:: drivers/usb/typec/typec.c +.. kernel-doc:: drivers/usb/typec/class.c :functions: typec_register_port typec_unregister_port When registering the ports, the prefer_role member in struct typec_capability @@ -80,7 +80,7 @@ typec_partner_desc. The class copies the details of the partner during registration. The class offers the following API for registering/unregistering partners. -.. kernel-doc:: drivers/usb/typec/typec.c +.. kernel-doc:: drivers/usb/typec/class.c :functions: typec_register_partner typec_unregister_partner The class will provide a handle to struct typec_partner if the registration was @@ -92,7 +92,7 @@ should include handle to struct usb_pd_identity instance. The class will then create a sysfs directory for the identity under the partner device. The result of Discover Identity command can then be reported with the following API: -.. kernel-doc:: drivers/usb/typec/typec.c +.. kernel-doc:: drivers/usb/typec/class.c :functions: typec_partner_set_identity Registering Cables @@ -113,7 +113,7 @@ typec_cable_desc and about a plug in struct typec_plug_desc. The class copies the details during registration. The class offers the following API for registering/unregistering cables and their plugs: -.. kernel-doc:: drivers/usb/typec/typec.c +.. kernel-doc:: drivers/usb/typec/class.c :functions: typec_register_cable typec_unregister_cable typec_register_plug typec_unregister_plug The class will provide a handle to struct typec_cable and struct typec_plug if @@ -125,7 +125,7 @@ include handle to struct usb_pd_identity instance. The class will then create a sysfs directory for the identity under the cable device. The result of Discover Identity command can then be reported with the following API: -.. kernel-doc:: drivers/usb/typec/typec.c +.. kernel-doc:: drivers/usb/typec/class.c :functions: typec_cable_set_identity Notifications @@ -135,7 +135,7 @@ When the partner has executed a role change, or when the default roles change during connection of a partner or cable, the port driver must use the following APIs to report it to the class: -.. kernel-doc:: drivers/usb/typec/typec.c +.. kernel-doc:: drivers/usb/typec/class.c :functions: typec_set_data_role typec_set_pwr_role typec_set_vconn_role typec_set_pwr_opmode Alternate Modes @@ -150,7 +150,7 @@ and struct typec_altmode_desc which is a container for all the supported modes. Ports that support Alternate Modes need to register each SVID they support with the following API: -.. kernel-doc:: drivers/usb/typec/typec.c +.. kernel-doc:: drivers/usb/typec/class.c :functions: typec_port_register_altmode If a partner or cable plug provides a list of SVIDs as response to USB Power @@ -159,12 +159,12 @@ registered. API for the partners: -.. kernel-doc:: drivers/usb/typec/typec.c +.. kernel-doc:: drivers/usb/typec/class.c :functions: typec_partner_register_altmode API for the Cable Plugs: -.. kernel-doc:: drivers/usb/typec/typec.c +.. kernel-doc:: drivers/usb/typec/class.c :functions: typec_plug_register_altmode So ports, partners and cable plugs will register the alternate modes with their @@ -172,11 +172,62 @@ own functions, but the registration will always return a handle to struct typec_altmode on success, or NULL. The unregistration will happen with the same function: -.. kernel-doc:: drivers/usb/typec/typec.c +.. kernel-doc:: drivers/usb/typec/class.c :functions: typec_unregister_altmode If a partner or cable plug enters or exits a mode, the port driver needs to notify the class with the following API: -.. kernel-doc:: drivers/usb/typec/typec.c +.. kernel-doc:: drivers/usb/typec/class.c :functions: typec_altmode_update_active + +Multiplexer/DeMultiplexer Switches +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +USB Type-C connectors may have one or more mux/demux switches behind them. Since +the plugs can be inserted right-side-up or upside-down, a switch is needed to +route the correct data pairs from the connector to the USB controllers. If +Alternate or Accessory Modes are supported, another switch is needed that can +route the pins on the connector to some other component besides USB. USB Type-C +Connector Class supplies an API for registering those switches. + +.. kernel-doc:: drivers/usb/typec/mux.c + :functions: typec_switch_register typec_switch_unregister typec_mux_register typec_mux_unregister + +In most cases the same physical mux will handle both the orientation and mode. +However, as the port drivers will be responsible for the orientation, and the +alternate mode drivers for the mode, the two are always separated into their +own logical components: "mux" for the mode and "switch" for the orientation. + +When a port is registered, USB Type-C Connector Class requests both the mux and +the switch for the port. The drivers can then use the following API for +controlling them: + +.. kernel-doc:: drivers/usb/typec/class.c + :functions: typec_set_orientation typec_set_mode + +If the connector is dual-role capable, there may also be a switch for the data +role. USB Type-C Connector Class does not supply separate API for them. The +port drivers can use USB Role Class API with those. + +Illustration of the muxes behind a connector that supports an alternate mode: + + ------------------------ + | Connector | + ------------------------ + | | + ------------------------ + \ Orientation / + -------------------- + | + -------------------- + / Mode \ + ------------------------ + / \ + ------------------------ -------------------- + | Alt Mode | / USB Role \ + ------------------------ ------------------------ + / \ + ------------------------ ------------------------ + | USB Host | | USB Device | + ------------------------ ------------------------ diff --git a/drivers/usb/typec/Makefile b/drivers/usb/typec/Makefile index bb3138a6eaac..56b2e9516ec1 100644 --- a/drivers/usb/typec/Makefile +++ b/drivers/usb/typec/Makefile @@ -1,5 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_TYPEC) += typec.o +typec-y := class.o mux.o obj-$(CONFIG_TYPEC_TCPM) += tcpm.o obj-y += fusb302/ obj-$(CONFIG_TYPEC_WCOVE) += typec_wcove.o diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c new file mode 100644 index 000000000000..2620a694704f --- /dev/null +++ b/drivers/usb/typec/class.c @@ -0,0 +1,1435 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * USB Type-C Connector Class + * + * Copyright (C) 2017, Intel Corporation + * Author: Heikki Krogerus + */ + +#include +#include +#include +#include +#include +#include + +struct typec_mode { + int index; + u32 vdo; + char *desc; + enum typec_port_type roles; + + struct typec_altmode *alt_mode; + + unsigned int active:1; + + char group_name[6]; + struct attribute_group group; + struct attribute *attrs[5]; + struct device_attribute vdo_attr; + struct device_attribute desc_attr; + struct device_attribute active_attr; + struct device_attribute roles_attr; +}; + +struct typec_altmode { + struct device dev; + u16 svid; + int n_modes; + struct typec_mode modes[ALTMODE_MAX_MODES]; + const struct attribute_group *mode_groups[ALTMODE_MAX_MODES]; +}; + +struct typec_plug { + struct device dev; + enum typec_plug_index index; +}; + +struct typec_cable { + struct device dev; + enum typec_plug_type type; + struct usb_pd_identity *identity; + unsigned int active:1; +}; + +struct typec_partner { + struct device dev; + unsigned int usb_pd:1; + struct usb_pd_identity *identity; + enum typec_accessory accessory; +}; + +struct typec_port { + unsigned int id; + struct device dev; + + int prefer_role; + enum typec_data_role data_role; + enum typec_role pwr_role; + enum typec_role vconn_role; + enum typec_pwr_opmode pwr_opmode; + enum typec_port_type port_type; + struct mutex port_type_lock; + + enum typec_orientation orientation; + struct typec_switch *sw; + struct typec_mux *mux; + + const struct typec_capability *cap; +}; + +#define to_typec_port(_dev_) container_of(_dev_, struct typec_port, dev) +#define to_typec_plug(_dev_) container_of(_dev_, struct typec_plug, dev) +#define to_typec_cable(_dev_) container_of(_dev_, struct typec_cable, dev) +#define to_typec_partner(_dev_) container_of(_dev_, struct typec_partner, dev) +#define to_altmode(_dev_) container_of(_dev_, struct typec_altmode, dev) + +static const struct device_type typec_partner_dev_type; +static const struct device_type typec_cable_dev_type; +static const struct device_type typec_plug_dev_type; +static const struct device_type typec_port_dev_type; + +#define is_typec_partner(_dev_) (_dev_->type == &typec_partner_dev_type) +#define is_typec_cable(_dev_) (_dev_->type == &typec_cable_dev_type) +#define is_typec_plug(_dev_) (_dev_->type == &typec_plug_dev_type) +#define is_typec_port(_dev_) (_dev_->type == &typec_port_dev_type) + +static DEFINE_IDA(typec_index_ida); +static struct class *typec_class; + +/* ------------------------------------------------------------------------- */ +/* Common attributes */ + +static const char * const typec_accessory_modes[] = { + [TYPEC_ACCESSORY_NONE] = "none", + [TYPEC_ACCESSORY_AUDIO] = "analog_audio", + [TYPEC_ACCESSORY_DEBUG] = "debug", +}; + +static struct usb_pd_identity *get_pd_identity(struct device *dev) +{ + if (is_typec_partner(dev)) { + struct typec_partner *partner = to_typec_partner(dev); + + return partner->identity; + } else if (is_typec_cable(dev)) { + struct typec_cable *cable = to_typec_cable(dev); + + return cable->identity; + } + return NULL; +} + +static ssize_t id_header_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_pd_identity *id = get_pd_identity(dev); + + return sprintf(buf, "0x%08x\n", id->id_header); +} +static DEVICE_ATTR_RO(id_header); + +static ssize_t cert_stat_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_pd_identity *id = get_pd_identity(dev); + + return sprintf(buf, "0x%08x\n", id->cert_stat); +} +static DEVICE_ATTR_RO(cert_stat); + +static ssize_t product_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_pd_identity *id = get_pd_identity(dev); + + return sprintf(buf, "0x%08x\n", id->product); +} +static DEVICE_ATTR_RO(product); + +static struct attribute *usb_pd_id_attrs[] = { + &dev_attr_id_header.attr, + &dev_attr_cert_stat.attr, + &dev_attr_product.attr, + NULL +}; + +static const struct attribute_group usb_pd_id_group = { + .name = "identity", + .attrs = usb_pd_id_attrs, +}; + +static const struct attribute_group *usb_pd_id_groups[] = { + &usb_pd_id_group, + NULL, +}; + +static void typec_report_identity(struct device *dev) +{ + sysfs_notify(&dev->kobj, "identity", "id_header"); + sysfs_notify(&dev->kobj, "identity", "cert_stat"); + sysfs_notify(&dev->kobj, "identity", "product"); +} + +/* ------------------------------------------------------------------------- */ +/* Alternate Modes */ + +/** + * typec_altmode_update_active - Report Enter/Exit mode + * @alt: Handle to the alternate mode + * @mode: Mode index + * @active: True when the mode has been entered + * + * If a partner or cable plug executes Enter/Exit Mode command successfully, the + * drivers use this routine to report the updated state of the mode. + */ +void typec_altmode_update_active(struct typec_altmode *alt, int mode, + bool active) +{ + struct typec_mode *m = &alt->modes[mode]; + char dir[6]; + + if (m->active == active) + return; + + m->active = active; + snprintf(dir, sizeof(dir), "mode%d", mode); + sysfs_notify(&alt->dev.kobj, dir, "active"); + kobject_uevent(&alt->dev.kobj, KOBJ_CHANGE); +} +EXPORT_SYMBOL_GPL(typec_altmode_update_active); + +/** + * typec_altmode2port - Alternate Mode to USB Type-C port + * @alt: The Alternate Mode + * + * Returns handle to the port that a cable plug or partner with @alt is + * connected to. + */ +struct typec_port *typec_altmode2port(struct typec_altmode *alt) +{ + if (is_typec_plug(alt->dev.parent)) + return to_typec_port(alt->dev.parent->parent->parent); + if (is_typec_partner(alt->dev.parent)) + return to_typec_port(alt->dev.parent->parent); + if (is_typec_port(alt->dev.parent)) + return to_typec_port(alt->dev.parent); + + return NULL; +} +EXPORT_SYMBOL_GPL(typec_altmode2port); + +static ssize_t +typec_altmode_vdo_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct typec_mode *mode = container_of(attr, struct typec_mode, + vdo_attr); + + return sprintf(buf, "0x%08x\n", mode->vdo); +} + +static ssize_t +typec_altmode_desc_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct typec_mode *mode = container_of(attr, struct typec_mode, + desc_attr); + + return sprintf(buf, "%s\n", mode->desc ? mode->desc : ""); +} + +static ssize_t +typec_altmode_active_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct typec_mode *mode = container_of(attr, struct typec_mode, + active_attr); + + return sprintf(buf, "%s\n", mode->active ? "yes" : "no"); +} + +static ssize_t +typec_altmode_active_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct typec_mode *mode = container_of(attr, struct typec_mode, + active_attr); + struct typec_port *port = typec_altmode2port(mode->alt_mode); + bool activate; + int ret; + + if (!port->cap->activate_mode) + return -EOPNOTSUPP; + + ret = kstrtobool(buf, &activate); + if (ret) + return ret; + + ret = port->cap->activate_mode(port->cap, mode->index, activate); + if (ret) + return ret; + + return size; +} + +static ssize_t +typec_altmode_roles_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct typec_mode *mode = container_of(attr, struct typec_mode, + roles_attr); + ssize_t ret; + + switch (mode->roles) { + case TYPEC_PORT_DFP: + ret = sprintf(buf, "source\n"); + break; + case TYPEC_PORT_UFP: + ret = sprintf(buf, "sink\n"); + break; + case TYPEC_PORT_DRP: + default: + ret = sprintf(buf, "source sink\n"); + break; + } + return ret; +} + +static void typec_init_modes(struct typec_altmode *alt, + const struct typec_mode_desc *desc, bool is_port) +{ + int i; + + for (i = 0; i < alt->n_modes; i++, desc++) { + struct typec_mode *mode = &alt->modes[i]; + + /* Not considering the human readable description critical */ + mode->desc = kstrdup(desc->desc, GFP_KERNEL); + if (desc->desc && !mode->desc) + dev_err(&alt->dev, "failed to copy mode%d desc\n", i); + + mode->alt_mode = alt; + mode->vdo = desc->vdo; + mode->roles = desc->roles; + mode->index = desc->index; + sprintf(mode->group_name, "mode%d", desc->index); + + sysfs_attr_init(&mode->vdo_attr.attr); + mode->vdo_attr.attr.name = "vdo"; + mode->vdo_attr.attr.mode = 0444; + mode->vdo_attr.show = typec_altmode_vdo_show; + + sysfs_attr_init(&mode->desc_attr.attr); + mode->desc_attr.attr.name = "description"; + mode->desc_attr.attr.mode = 0444; + mode->desc_attr.show = typec_altmode_desc_show; + + sysfs_attr_init(&mode->active_attr.attr); + mode->active_attr.attr.name = "active"; + mode->active_attr.attr.mode = 0644; + mode->active_attr.show = typec_altmode_active_show; + mode->active_attr.store = typec_altmode_active_store; + + mode->attrs[0] = &mode->vdo_attr.attr; + mode->attrs[1] = &mode->desc_attr.attr; + mode->attrs[2] = &mode->active_attr.attr; + + /* With ports, list the roles that the mode is supported with */ + if (is_port) { + sysfs_attr_init(&mode->roles_attr.attr); + mode->roles_attr.attr.name = "supported_roles"; + mode->roles_attr.attr.mode = 0444; + mode->roles_attr.show = typec_altmode_roles_show; + + mode->attrs[3] = &mode->roles_attr.attr; + } + + mode->group.attrs = mode->attrs; + mode->group.name = mode->group_name; + + alt->mode_groups[i] = &mode->group; + } +} + +static ssize_t svid_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct typec_altmode *alt = to_altmode(dev); + + return sprintf(buf, "%04x\n", alt->svid); +} +static DEVICE_ATTR_RO(svid); + +static struct attribute *typec_altmode_attrs[] = { + &dev_attr_svid.attr, + NULL +}; +ATTRIBUTE_GROUPS(typec_altmode); + +static void typec_altmode_release(struct device *dev) +{ + struct typec_altmode *alt = to_altmode(dev); + int i; + + for (i = 0; i < alt->n_modes; i++) + kfree(alt->modes[i].desc); + kfree(alt); +} + +static const struct device_type typec_altmode_dev_type = { + .name = "typec_alternate_mode", + .groups = typec_altmode_groups, + .release = typec_altmode_release, +}; + +static struct typec_altmode * +typec_register_altmode(struct device *parent, + const struct typec_altmode_desc *desc) +{ + struct typec_altmode *alt; + int ret; + + alt = kzalloc(sizeof(*alt), GFP_KERNEL); + if (!alt) + return ERR_PTR(-ENOMEM); + + alt->svid = desc->svid; + alt->n_modes = desc->n_modes; + typec_init_modes(alt, desc->modes, is_typec_port(parent)); + + alt->dev.parent = parent; + alt->dev.groups = alt->mode_groups; + alt->dev.type = &typec_altmode_dev_type; + dev_set_name(&alt->dev, "svid-%04x", alt->svid); + + ret = device_register(&alt->dev); + if (ret) { + dev_err(parent, "failed to register alternate mode (%d)\n", + ret); + put_device(&alt->dev); + return ERR_PTR(ret); + } + + return alt; +} + +/** + * typec_unregister_altmode - Unregister Alternate Mode + * @alt: The alternate mode to be unregistered + * + * Unregister device created with typec_partner_register_altmode(), + * typec_plug_register_altmode() or typec_port_register_altmode(). + */ +void typec_unregister_altmode(struct typec_altmode *alt) +{ + if (!IS_ERR_OR_NULL(alt)) + device_unregister(&alt->dev); +} +EXPORT_SYMBOL_GPL(typec_unregister_altmode); + +/* ------------------------------------------------------------------------- */ +/* Type-C Partners */ + +static ssize_t accessory_mode_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct typec_partner *p = to_typec_partner(dev); + + return sprintf(buf, "%s\n", typec_accessory_modes[p->accessory]); +} +static DEVICE_ATTR_RO(accessory_mode); + +static ssize_t supports_usb_power_delivery_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct typec_partner *p = to_typec_partner(dev); + + return sprintf(buf, "%s\n", p->usb_pd ? "yes" : "no"); +} +static DEVICE_ATTR_RO(supports_usb_power_delivery); + +static struct attribute *typec_partner_attrs[] = { + &dev_attr_accessory_mode.attr, + &dev_attr_supports_usb_power_delivery.attr, + NULL +}; +ATTRIBUTE_GROUPS(typec_partner); + +static void typec_partner_release(struct device *dev) +{ + struct typec_partner *partner = to_typec_partner(dev); + + kfree(partner); +} + +static const struct device_type typec_partner_dev_type = { + .name = "typec_partner", + .groups = typec_partner_groups, + .release = typec_partner_release, +}; + +/** + * typec_partner_set_identity - Report result from Discover Identity command + * @partner: The partner updated identity values + * + * This routine is used to report that the result of Discover Identity USB power + * delivery command has become available. + */ +int typec_partner_set_identity(struct typec_partner *partner) +{ + if (!partner->identity) + return -EINVAL; + + typec_report_identity(&partner->dev); + return 0; +} +EXPORT_SYMBOL_GPL(typec_partner_set_identity); + +/** + * typec_partner_register_altmode - Register USB Type-C Partner Alternate Mode + * @partner: USB Type-C Partner that supports the alternate mode + * @desc: Description of the alternate mode + * + * This routine is used to register each alternate mode individually that + * @partner has listed in response to Discover SVIDs command. The modes for a + * SVID listed in response to Discover Modes command need to be listed in an + * array in @desc. + * + * Returns handle to the alternate mode on success or NULL on failure. + */ +struct typec_altmode * +typec_partner_register_altmode(struct typec_partner *partner, + const struct typec_altmode_desc *desc) +{ + return typec_register_altmode(&partner->dev, desc); +} +EXPORT_SYMBOL_GPL(typec_partner_register_altmode); + +/** + * typec_register_partner - Register a USB Type-C Partner + * @port: The USB Type-C Port the partner is connected to + * @desc: Description of the partner + * + * Registers a device for USB Type-C Partner described in @desc. + * + * Returns handle to the partner on success or ERR_PTR on failure. + */ +struct typec_partner *typec_register_partner(struct typec_port *port, + struct typec_partner_desc *desc) +{ + struct typec_partner *partner; + int ret; + + partner = kzalloc(sizeof(*partner), GFP_KERNEL); + if (!partner) + return ERR_PTR(-ENOMEM); + + partner->usb_pd = desc->usb_pd; + partner->accessory = desc->accessory; + + if (desc->identity) { + /* + * Creating directory for the identity only if the driver is + * able to provide data to it. + */ + partner->dev.groups = usb_pd_id_groups; + partner->identity = desc->identity; + } + + partner->dev.class = typec_class; + partner->dev.parent = &port->dev; + partner->dev.type = &typec_partner_dev_type; + dev_set_name(&partner->dev, "%s-partner", dev_name(&port->dev)); + + ret = device_register(&partner->dev); + if (ret) { + dev_err(&port->dev, "failed to register partner (%d)\n", ret); + put_device(&partner->dev); + return ERR_PTR(ret); + } + + return partner; +} +EXPORT_SYMBOL_GPL(typec_register_partner); + +/** + * typec_unregister_partner - Unregister a USB Type-C Partner + * @partner: The partner to be unregistered + * + * Unregister device created with typec_register_partner(). + */ +void typec_unregister_partner(struct typec_partner *partner) +{ + if (!IS_ERR_OR_NULL(partner)) + device_unregister(&partner->dev); +} +EXPORT_SYMBOL_GPL(typec_unregister_partner); + +/* ------------------------------------------------------------------------- */ +/* Type-C Cable Plugs */ + +static void typec_plug_release(struct device *dev) +{ + struct typec_plug *plug = to_typec_plug(dev); + + kfree(plug); +} + +static const struct device_type typec_plug_dev_type = { + .name = "typec_plug", + .release = typec_plug_release, +}; + +/** + * typec_plug_register_altmode - Register USB Type-C Cable Plug Alternate Mode + * @plug: USB Type-C Cable Plug that supports the alternate mode + * @desc: Description of the alternate mode + * + * This routine is used to register each alternate mode individually that @plug + * has listed in response to Discover SVIDs command. The modes for a SVID that + * the plug lists in response to Discover Modes command need to be listed in an + * array in @desc. + * + * Returns handle to the alternate mode on success or ERR_PTR on failure. + */ +struct typec_altmode * +typec_plug_register_altmode(struct typec_plug *plug, + const struct typec_altmode_desc *desc) +{ + return typec_register_altmode(&plug->dev, desc); +} +EXPORT_SYMBOL_GPL(typec_plug_register_altmode); + +/** + * typec_register_plug - Register a USB Type-C Cable Plug + * @cable: USB Type-C Cable with the plug + * @desc: Description of the cable plug + * + * Registers a device for USB Type-C Cable Plug described in @desc. A USB Type-C + * Cable Plug represents a plug with electronics in it that can response to USB + * Power Delivery SOP Prime or SOP Double Prime packages. + * + * Returns handle to the cable plug on success or ERR_PTR on failure. + */ +struct typec_plug *typec_register_plug(struct typec_cable *cable, + struct typec_plug_desc *desc) +{ + struct typec_plug *plug; + char name[8]; + int ret; + + plug = kzalloc(sizeof(*plug), GFP_KERNEL); + if (!plug) + return ERR_PTR(-ENOMEM); + + sprintf(name, "plug%d", desc->index); + + plug->index = desc->index; + plug->dev.class = typec_class; + plug->dev.parent = &cable->dev; + plug->dev.type = &typec_plug_dev_type; + dev_set_name(&plug->dev, "%s-%s", dev_name(cable->dev.parent), name); + + ret = device_register(&plug->dev); + if (ret) { + dev_err(&cable->dev, "failed to register plug (%d)\n", ret); + put_device(&plug->dev); + return ERR_PTR(ret); + } + + return plug; +} +EXPORT_SYMBOL_GPL(typec_register_plug); + +/** + * typec_unregister_plug - Unregister a USB Type-C Cable Plug + * @plug: The cable plug to be unregistered + * + * Unregister device created with typec_register_plug(). + */ +void typec_unregister_plug(struct typec_plug *plug) +{ + if (!IS_ERR_OR_NULL(plug)) + device_unregister(&plug->dev); +} +EXPORT_SYMBOL_GPL(typec_unregister_plug); + +/* Type-C Cables */ + +static ssize_t +type_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct typec_cable *cable = to_typec_cable(dev); + + return sprintf(buf, "%s\n", cable->active ? "active" : "passive"); +} +static DEVICE_ATTR_RO(type); + +static const char * const typec_plug_types[] = { + [USB_PLUG_NONE] = "unknown", + [USB_PLUG_TYPE_A] = "type-a", + [USB_PLUG_TYPE_B] = "type-b", + [USB_PLUG_TYPE_C] = "type-c", + [USB_PLUG_CAPTIVE] = "captive", +}; + +static ssize_t plug_type_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct typec_cable *cable = to_typec_cable(dev); + + return sprintf(buf, "%s\n", typec_plug_types[cable->type]); +} +static DEVICE_ATTR_RO(plug_type); + +static struct attribute *typec_cable_attrs[] = { + &dev_attr_type.attr, + &dev_attr_plug_type.attr, + NULL +}; +ATTRIBUTE_GROUPS(typec_cable); + +static void typec_cable_release(struct device *dev) +{ + struct typec_cable *cable = to_typec_cable(dev); + + kfree(cable); +} + +static const struct device_type typec_cable_dev_type = { + .name = "typec_cable", + .groups = typec_cable_groups, + .release = typec_cable_release, +}; + +/** + * typec_cable_set_identity - Report result from Discover Identity command + * @cable: The cable updated identity values + * + * This routine is used to report that the result of Discover Identity USB power + * delivery command has become available. + */ +int typec_cable_set_identity(struct typec_cable *cable) +{ + if (!cable->identity) + return -EINVAL; + + typec_report_identity(&cable->dev); + return 0; +} +EXPORT_SYMBOL_GPL(typec_cable_set_identity); + +/** + * typec_register_cable - Register a USB Type-C Cable + * @port: The USB Type-C Port the cable is connected to + * @desc: Description of the cable + * + * Registers a device for USB Type-C Cable described in @desc. The cable will be + * parent for the optional cable plug devises. + * + * Returns handle to the cable on success or ERR_PTR on failure. + */ +struct typec_cable *typec_register_cable(struct typec_port *port, + struct typec_cable_desc *desc) +{ + struct typec_cable *cable; + int ret; + + cable = kzalloc(sizeof(*cable), GFP_KERNEL); + if (!cable) + return ERR_PTR(-ENOMEM); + + cable->type = desc->type; + cable->active = desc->active; + + if (desc->identity) { + /* + * Creating directory for the identity only if the driver is + * able to provide data to it. + */ + cable->dev.groups = usb_pd_id_groups; + cable->identity = desc->identity; + } + + cable->dev.class = typec_class; + cable->dev.parent = &port->dev; + cable->dev.type = &typec_cable_dev_type; + dev_set_name(&cable->dev, "%s-cable", dev_name(&port->dev)); + + ret = device_register(&cable->dev); + if (ret) { + dev_err(&port->dev, "failed to register cable (%d)\n", ret); + put_device(&cable->dev); + return ERR_PTR(ret); + } + + return cable; +} +EXPORT_SYMBOL_GPL(typec_register_cable); + +/** + * typec_unregister_cable - Unregister a USB Type-C Cable + * @cable: The cable to be unregistered + * + * Unregister device created with typec_register_cable(). + */ +void typec_unregister_cable(struct typec_cable *cable) +{ + if (!IS_ERR_OR_NULL(cable)) + device_unregister(&cable->dev); +} +EXPORT_SYMBOL_GPL(typec_unregister_cable); + +/* ------------------------------------------------------------------------- */ +/* USB Type-C ports */ + +static const char * const typec_roles[] = { + [TYPEC_SINK] = "sink", + [TYPEC_SOURCE] = "source", +}; + +static const char * const typec_data_roles[] = { + [TYPEC_DEVICE] = "device", + [TYPEC_HOST] = "host", +}; + +static const char * const typec_port_types[] = { + [TYPEC_PORT_DFP] = "source", + [TYPEC_PORT_UFP] = "sink", + [TYPEC_PORT_DRP] = "dual", +}; + +static const char * const typec_port_types_drp[] = { + [TYPEC_PORT_DFP] = "dual [source] sink", + [TYPEC_PORT_UFP] = "dual source [sink]", + [TYPEC_PORT_DRP] = "[dual] source sink", +}; + +static ssize_t +preferred_role_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct typec_port *port = to_typec_port(dev); + int role; + int ret; + + if (port->cap->type != TYPEC_PORT_DRP) { + dev_dbg(dev, "Preferred role only supported with DRP ports\n"); + return -EOPNOTSUPP; + } + + if (!port->cap->try_role) { + dev_dbg(dev, "Setting preferred role not supported\n"); + return -EOPNOTSUPP; + } + + role = sysfs_match_string(typec_roles, buf); + if (role < 0) { + if (sysfs_streq(buf, "none")) + role = TYPEC_NO_PREFERRED_ROLE; + else + return -EINVAL; + } + + ret = port->cap->try_role(port->cap, role); + if (ret) + return ret; + + port->prefer_role = role; + return size; +} + +static ssize_t +preferred_role_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct typec_port *port = to_typec_port(dev); + + if (port->cap->type != TYPEC_PORT_DRP) + return 0; + + if (port->prefer_role < 0) + return 0; + + return sprintf(buf, "%s\n", typec_roles[port->prefer_role]); +} +static DEVICE_ATTR_RW(preferred_role); + +static ssize_t data_role_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct typec_port *port = to_typec_port(dev); + int ret; + + if (!port->cap->dr_set) { + dev_dbg(dev, "data role swapping not supported\n"); + return -EOPNOTSUPP; + } + + ret = sysfs_match_string(typec_data_roles, buf); + if (ret < 0) + return ret; + + mutex_lock(&port->port_type_lock); + if (port->port_type != TYPEC_PORT_DRP) { + dev_dbg(dev, "port type fixed at \"%s\"", + typec_port_types[port->port_type]); + ret = -EOPNOTSUPP; + goto unlock_and_ret; + } + + ret = port->cap->dr_set(port->cap, ret); + if (ret) + goto unlock_and_ret; + + ret = size; +unlock_and_ret: + mutex_unlock(&port->port_type_lock); + return ret; +} + +static ssize_t data_role_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct typec_port *port = to_typec_port(dev); + + if (port->cap->type == TYPEC_PORT_DRP) + return sprintf(buf, "%s\n", port->data_role == TYPEC_HOST ? + "[host] device" : "host [device]"); + + return sprintf(buf, "[%s]\n", typec_data_roles[port->data_role]); +} +static DEVICE_ATTR_RW(data_role); + +static ssize_t power_role_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct typec_port *port = to_typec_port(dev); + int ret; + + if (!port->cap->pd_revision) { + dev_dbg(dev, "USB Power Delivery not supported\n"); + return -EOPNOTSUPP; + } + + if (!port->cap->pr_set) { + dev_dbg(dev, "power role swapping not supported\n"); + return -EOPNOTSUPP; + } + + if (port->pwr_opmode != TYPEC_PWR_MODE_PD) { + dev_dbg(dev, "partner unable to swap power role\n"); + return -EIO; + } + + ret = sysfs_match_string(typec_roles, buf); + if (ret < 0) + return ret; + + mutex_lock(&port->port_type_lock); + if (port->port_type != TYPEC_PORT_DRP) { + dev_dbg(dev, "port type fixed at \"%s\"", + typec_port_types[port->port_type]); + ret = -EOPNOTSUPP; + goto unlock_and_ret; + } + + ret = port->cap->pr_set(port->cap, ret); + if (ret) + goto unlock_and_ret; + + ret = size; +unlock_and_ret: + mutex_unlock(&port->port_type_lock); + return ret; +} + +static ssize_t power_role_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct typec_port *port = to_typec_port(dev); + + if (port->cap->type == TYPEC_PORT_DRP) + return sprintf(buf, "%s\n", port->pwr_role == TYPEC_SOURCE ? + "[source] sink" : "source [sink]"); + + return sprintf(buf, "[%s]\n", typec_roles[port->pwr_role]); +} +static DEVICE_ATTR_RW(power_role); + +static ssize_t +port_type_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct typec_port *port = to_typec_port(dev); + int ret; + enum typec_port_type type; + + if (!port->cap->port_type_set || port->cap->type != TYPEC_PORT_DRP) { + dev_dbg(dev, "changing port type not supported\n"); + return -EOPNOTSUPP; + } + + ret = sysfs_match_string(typec_port_types, buf); + if (ret < 0) + return ret; + + type = ret; + mutex_lock(&port->port_type_lock); + + if (port->port_type == type) { + ret = size; + goto unlock_and_ret; + } + + ret = port->cap->port_type_set(port->cap, type); + if (ret) + goto unlock_and_ret; + + port->port_type = type; + ret = size; + +unlock_and_ret: + mutex_unlock(&port->port_type_lock); + return ret; +} + +static ssize_t +port_type_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct typec_port *port = to_typec_port(dev); + + if (port->cap->type == TYPEC_PORT_DRP) + return sprintf(buf, "%s\n", + typec_port_types_drp[port->port_type]); + + return sprintf(buf, "[%s]\n", typec_port_types[port->cap->type]); +} +static DEVICE_ATTR_RW(port_type); + +static const char * const typec_pwr_opmodes[] = { + [TYPEC_PWR_MODE_USB] = "default", + [TYPEC_PWR_MODE_1_5A] = "1.5A", + [TYPEC_PWR_MODE_3_0A] = "3.0A", + [TYPEC_PWR_MODE_PD] = "usb_power_delivery", +}; + +static ssize_t power_operation_mode_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct typec_port *port = to_typec_port(dev); + + return sprintf(buf, "%s\n", typec_pwr_opmodes[port->pwr_opmode]); +} +static DEVICE_ATTR_RO(power_operation_mode); + +static ssize_t vconn_source_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct typec_port *port = to_typec_port(dev); + bool source; + int ret; + + if (!port->cap->pd_revision) { + dev_dbg(dev, "VCONN swap depends on USB Power Delivery\n"); + return -EOPNOTSUPP; + } + + if (!port->cap->vconn_set) { + dev_dbg(dev, "VCONN swapping not supported\n"); + return -EOPNOTSUPP; + } + + ret = kstrtobool(buf, &source); + if (ret) + return ret; + + ret = port->cap->vconn_set(port->cap, (enum typec_role)source); + if (ret) + return ret; + + return size; +} + +static ssize_t vconn_source_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct typec_port *port = to_typec_port(dev); + + return sprintf(buf, "%s\n", + port->vconn_role == TYPEC_SOURCE ? "yes" : "no"); +} +static DEVICE_ATTR_RW(vconn_source); + +static ssize_t supported_accessory_modes_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct typec_port *port = to_typec_port(dev); + ssize_t ret = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(port->cap->accessory); i++) { + if (port->cap->accessory[i]) + ret += sprintf(buf + ret, "%s ", + typec_accessory_modes[port->cap->accessory[i]]); + } + + if (!ret) + return sprintf(buf, "none\n"); + + buf[ret - 1] = '\n'; + + return ret; +} +static DEVICE_ATTR_RO(supported_accessory_modes); + +static ssize_t usb_typec_revision_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct typec_port *port = to_typec_port(dev); + u16 rev = port->cap->revision; + + return sprintf(buf, "%d.%d\n", (rev >> 8) & 0xff, (rev >> 4) & 0xf); +} +static DEVICE_ATTR_RO(usb_typec_revision); + +static ssize_t usb_power_delivery_revision_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct typec_port *p = to_typec_port(dev); + + return sprintf(buf, "%d\n", (p->cap->pd_revision >> 8) & 0xff); +} +static DEVICE_ATTR_RO(usb_power_delivery_revision); + +static struct attribute *typec_attrs[] = { + &dev_attr_data_role.attr, + &dev_attr_power_operation_mode.attr, + &dev_attr_power_role.attr, + &dev_attr_preferred_role.attr, + &dev_attr_supported_accessory_modes.attr, + &dev_attr_usb_power_delivery_revision.attr, + &dev_attr_usb_typec_revision.attr, + &dev_attr_vconn_source.attr, + &dev_attr_port_type.attr, + NULL, +}; +ATTRIBUTE_GROUPS(typec); + +static int typec_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + int ret; + + ret = add_uevent_var(env, "TYPEC_PORT=%s", dev_name(dev)); + if (ret) + dev_err(dev, "failed to add uevent TYPEC_PORT\n"); + + return ret; +} + +static void typec_release(struct device *dev) +{ + struct typec_port *port = to_typec_port(dev); + + ida_simple_remove(&typec_index_ida, port->id); + typec_switch_put(port->sw); + typec_mux_put(port->mux); + kfree(port); +} + +static const struct device_type typec_port_dev_type = { + .name = "typec_port", + .groups = typec_groups, + .uevent = typec_uevent, + .release = typec_release, +}; + +/* --------------------------------------- */ +/* Driver callbacks to report role updates */ + +/** + * typec_set_data_role - Report data role change + * @port: The USB Type-C Port where the role was changed + * @role: The new data role + * + * This routine is used by the port drivers to report data role changes. + */ +void typec_set_data_role(struct typec_port *port, enum typec_data_role role) +{ + if (port->data_role == role) + return; + + port->data_role = role; + sysfs_notify(&port->dev.kobj, NULL, "data_role"); + kobject_uevent(&port->dev.kobj, KOBJ_CHANGE); +} +EXPORT_SYMBOL_GPL(typec_set_data_role); + +/** + * typec_set_pwr_role - Report power role change + * @port: The USB Type-C Port where the role was changed + * @role: The new data role + * + * This routine is used by the port drivers to report power role changes. + */ +void typec_set_pwr_role(struct typec_port *port, enum typec_role role) +{ + if (port->pwr_role == role) + return; + + port->pwr_role = role; + sysfs_notify(&port->dev.kobj, NULL, "power_role"); + kobject_uevent(&port->dev.kobj, KOBJ_CHANGE); +} +EXPORT_SYMBOL_GPL(typec_set_pwr_role); + +/** + * typec_set_pwr_role - Report VCONN source change + * @port: The USB Type-C Port which VCONN role changed + * @role: Source when @port is sourcing VCONN, or Sink when it's not + * + * This routine is used by the port drivers to report if the VCONN source is + * changes. + */ +void typec_set_vconn_role(struct typec_port *port, enum typec_role role) +{ + if (port->vconn_role == role) + return; + + port->vconn_role = role; + sysfs_notify(&port->dev.kobj, NULL, "vconn_source"); + kobject_uevent(&port->dev.kobj, KOBJ_CHANGE); +} +EXPORT_SYMBOL_GPL(typec_set_vconn_role); + +static int partner_match(struct device *dev, void *data) +{ + return is_typec_partner(dev); +} + +/** + * typec_set_pwr_opmode - Report changed power operation mode + * @port: The USB Type-C Port where the mode was changed + * @opmode: New power operation mode + * + * This routine is used by the port drivers to report changed power operation + * mode in @port. The modes are USB (default), 1.5A, 3.0A as defined in USB + * Type-C specification, and "USB Power Delivery" when the power levels are + * negotiated with methods defined in USB Power Delivery specification. + */ +void typec_set_pwr_opmode(struct typec_port *port, + enum typec_pwr_opmode opmode) +{ + struct device *partner_dev; + + if (port->pwr_opmode == opmode) + return; + + port->pwr_opmode = opmode; + sysfs_notify(&port->dev.kobj, NULL, "power_operation_mode"); + kobject_uevent(&port->dev.kobj, KOBJ_CHANGE); + + partner_dev = device_find_child(&port->dev, NULL, partner_match); + if (partner_dev) { + struct typec_partner *partner = to_typec_partner(partner_dev); + + if (opmode == TYPEC_PWR_MODE_PD && !partner->usb_pd) { + partner->usb_pd = 1; + sysfs_notify(&partner_dev->kobj, NULL, + "supports_usb_power_delivery"); + } + put_device(partner_dev); + } +} +EXPORT_SYMBOL_GPL(typec_set_pwr_opmode); + +/* ------------------------------------------ */ +/* API for Multiplexer/DeMultiplexer Switches */ + +/** + * typec_set_orientation - Set USB Type-C cable plug orientation + * @port: USB Type-C Port + * @orientation: USB Type-C cable plug orientation + * + * Set cable plug orientation for @port. + */ +int typec_set_orientation(struct typec_port *port, + enum typec_orientation orientation) +{ + int ret; + + if (port->sw) { + ret = port->sw->set(port->sw, orientation); + if (ret) + return ret; + } + + port->orientation = orientation; + + return 0; +} +EXPORT_SYMBOL_GPL(typec_set_orientation); + +/** + * typec_set_mode - Set mode of operation for USB Type-C connector + * @port: USB Type-C port for the connector + * @mode: Operation mode for the connector + * + * Set mode @mode for @port. This function will configure the muxes needed to + * enter @mode. + */ +int typec_set_mode(struct typec_port *port, int mode) +{ + return port->mux ? port->mux->set(port->mux, mode) : 0; +} +EXPORT_SYMBOL_GPL(typec_set_mode); + +/* --------------------------------------- */ + +/** + * typec_port_register_altmode - Register USB Type-C Port Alternate Mode + * @port: USB Type-C Port that supports the alternate mode + * @desc: Description of the alternate mode + * + * This routine is used to register an alternate mode that @port is capable of + * supporting. + * + * Returns handle to the alternate mode on success or ERR_PTR on failure. + */ +struct typec_altmode * +typec_port_register_altmode(struct typec_port *port, + const struct typec_altmode_desc *desc) +{ + return typec_register_altmode(&port->dev, desc); +} +EXPORT_SYMBOL_GPL(typec_port_register_altmode); + +/** + * typec_register_port - Register a USB Type-C Port + * @parent: Parent device + * @cap: Description of the port + * + * Registers a device for USB Type-C Port described in @cap. + * + * Returns handle to the port on success or ERR_PTR on failure. + */ +struct typec_port *typec_register_port(struct device *parent, + const struct typec_capability *cap) +{ + struct typec_port *port; + int role; + int ret; + int id; + + port = kzalloc(sizeof(*port), GFP_KERNEL); + if (!port) + return ERR_PTR(-ENOMEM); + + id = ida_simple_get(&typec_index_ida, 0, 0, GFP_KERNEL); + if (id < 0) { + kfree(port); + return ERR_PTR(id); + } + + port->sw = typec_switch_get(cap->fwnode ? &port->dev : parent); + if (IS_ERR(port->sw)) { + ret = PTR_ERR(port->sw); + goto err_switch; + } + + port->mux = typec_mux_get(cap->fwnode ? &port->dev : parent); + if (IS_ERR(port->mux)) { + ret = PTR_ERR(port->mux); + goto err_mux; + } + + if (cap->type == TYPEC_PORT_DFP) + role = TYPEC_SOURCE; + else if (cap->type == TYPEC_PORT_UFP) + role = TYPEC_SINK; + else + role = cap->prefer_role; + + if (role == TYPEC_SOURCE) { + port->data_role = TYPEC_HOST; + port->pwr_role = TYPEC_SOURCE; + port->vconn_role = TYPEC_SOURCE; + } else { + port->data_role = TYPEC_DEVICE; + port->pwr_role = TYPEC_SINK; + port->vconn_role = TYPEC_SINK; + } + + port->id = id; + port->cap = cap; + port->port_type = cap->type; + mutex_init(&port->port_type_lock); + port->prefer_role = cap->prefer_role; + + port->dev.class = typec_class; + port->dev.parent = parent; + port->dev.fwnode = cap->fwnode; + port->dev.type = &typec_port_dev_type; + dev_set_name(&port->dev, "port%d", id); + + ret = device_register(&port->dev); + if (ret) { + dev_err(parent, "failed to register port (%d)\n", ret); + put_device(&port->dev); + return ERR_PTR(ret); + } + + return port; + +err_mux: + typec_switch_put(port->sw); + +err_switch: + ida_simple_remove(&typec_index_ida, port->id); + kfree(port); + + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(typec_register_port); + +/** + * typec_unregister_port - Unregister a USB Type-C Port + * @port: The port to be unregistered + * + * Unregister device created with typec_register_port(). + */ +void typec_unregister_port(struct typec_port *port) +{ + if (!IS_ERR_OR_NULL(port)) + device_unregister(&port->dev); +} +EXPORT_SYMBOL_GPL(typec_unregister_port); + +static int __init typec_init(void) +{ + typec_class = class_create(THIS_MODULE, "typec"); + return PTR_ERR_OR_ZERO(typec_class); +} +subsys_initcall(typec_init); + +static void __exit typec_exit(void) +{ + class_destroy(typec_class); + ida_destroy(&typec_index_ida); +} +module_exit(typec_exit); + +MODULE_AUTHOR("Heikki Krogerus "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("USB Type-C Connector Class"); diff --git a/drivers/usb/typec/mux.c b/drivers/usb/typec/mux.c new file mode 100644 index 000000000000..f89093bd7185 --- /dev/null +++ b/drivers/usb/typec/mux.c @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: GPL-2.0 +/** + * USB Type-C Multiplexer/DeMultiplexer Switch support + * + * Copyright (C) 2018 Intel Corporation + * Author: Heikki Krogerus + * Hans de Goede + */ + +#include +#include +#include +#include + +static DEFINE_MUTEX(switch_lock); +static DEFINE_MUTEX(mux_lock); +static LIST_HEAD(switch_list); +static LIST_HEAD(mux_list); + +static void *typec_switch_match(struct device_connection *con, int ep, + void *data) +{ + struct typec_switch *sw; + + list_for_each_entry(sw, &switch_list, entry) + if (!strcmp(con->endpoint[ep], dev_name(sw->dev))) + return sw; + + /* + * We only get called if a connection was found, tell the caller to + * wait for the switch to show up. + */ + return ERR_PTR(-EPROBE_DEFER); +} + +/** + * typec_switch_get - Find USB Type-C orientation switch + * @dev: The caller device + * + * Finds a switch linked with @dev. Returns a reference to the switch on + * success, NULL if no matching connection was found, or + * ERR_PTR(-EPROBE_DEFER) when a connection was found but the switch + * has not been enumerated yet. + */ +struct typec_switch *typec_switch_get(struct device *dev) +{ + struct typec_switch *sw; + + mutex_lock(&switch_lock); + sw = device_connection_find_match(dev, "typec-switch", NULL, + typec_switch_match); + if (!IS_ERR_OR_NULL(sw)) + get_device(sw->dev); + mutex_unlock(&switch_lock); + + return sw; +} +EXPORT_SYMBOL_GPL(typec_switch_get); + +/** + * typec_put_switch - Release USB Type-C orientation switch + * @sw: USB Type-C orientation switch + * + * Decrement reference count for @sw. + */ +void typec_switch_put(struct typec_switch *sw) +{ + if (!IS_ERR_OR_NULL(sw)) + put_device(sw->dev); +} +EXPORT_SYMBOL_GPL(typec_switch_put); + +/** + * typec_switch_register - Register USB Type-C orientation switch + * @sw: USB Type-C orientation switch + * + * This function registers a switch that can be used for routing the correct + * data pairs depending on the cable plug orientation from the USB Type-C + * connector to the USB controllers. USB Type-C plugs can be inserted + * right-side-up or upside-down. + */ +int typec_switch_register(struct typec_switch *sw) +{ + mutex_lock(&switch_lock); + list_add_tail(&sw->entry, &switch_list); + mutex_unlock(&switch_lock); + + return 0; +} +EXPORT_SYMBOL_GPL(typec_switch_register); + +/** + * typec_switch_unregister - Unregister USB Type-C orientation switch + * @sw: USB Type-C orientation switch + * + * Unregister switch that was registered with typec_switch_register(). + */ +void typec_switch_unregister(struct typec_switch *sw) +{ + mutex_lock(&switch_lock); + list_del(&sw->entry); + mutex_unlock(&switch_lock); +} +EXPORT_SYMBOL_GPL(typec_switch_unregister); + +/* ------------------------------------------------------------------------- */ + +static void *typec_mux_match(struct device_connection *con, int ep, void *data) +{ + struct typec_mux *mux; + + list_for_each_entry(mux, &mux_list, entry) + if (!strcmp(con->endpoint[ep], dev_name(mux->dev))) + return mux; + + /* + * We only get called if a connection was found, tell the caller to + * wait for the switch to show up. + */ + return ERR_PTR(-EPROBE_DEFER); +} + +/** + * typec_mux_get - Find USB Type-C Multiplexer + * @dev: The caller device + * + * Finds a mux linked to the caller. This function is primarily meant for the + * Type-C drivers. Returns a reference to the mux on success, NULL if no + * matching connection was found, or ERR_PTR(-EPROBE_DEFER) when a connection + * was found but the mux has not been enumerated yet. + */ +struct typec_mux *typec_mux_get(struct device *dev) +{ + struct typec_mux *mux; + + mutex_lock(&mux_lock); + mux = device_connection_find_match(dev, "typec-mux", NULL, + typec_mux_match); + if (!IS_ERR_OR_NULL(mux)) + get_device(mux->dev); + mutex_unlock(&mux_lock); + + return mux; +} +EXPORT_SYMBOL_GPL(typec_mux_get); + +/** + * typec_mux_put - Release handle to a Multiplexer + * @mux: USB Type-C Connector Multiplexer/DeMultiplexer + * + * Decrements reference count for @mux. + */ +void typec_mux_put(struct typec_mux *mux) +{ + if (!IS_ERR_OR_NULL(mux)) + put_device(mux->dev); +} +EXPORT_SYMBOL_GPL(typec_mux_put); + +/** + * typec_mux_register - Register Multiplexer routing USB Type-C pins + * @mux: USB Type-C Connector Multiplexer/DeMultiplexer + * + * USB Type-C connectors can be used for alternate modes of operation besides + * USB when Accessory/Alternate Modes are supported. With some of those modes, + * the pins on the connector need to be reconfigured. This function registers + * multiplexer switches routing the pins on the connector. + */ +int typec_mux_register(struct typec_mux *mux) +{ + mutex_lock(&mux_lock); + list_add_tail(&mux->entry, &mux_list); + mutex_unlock(&mux_lock); + + return 0; +} +EXPORT_SYMBOL_GPL(typec_mux_register); + +/** + * typec_mux_unregister - Unregister Multiplexer Switch + * @sw: USB Type-C Connector Multiplexer/DeMultiplexer + * + * Unregister mux that was registered with typec_mux_register(). + */ +void typec_mux_unregister(struct typec_mux *mux) +{ + mutex_lock(&mux_lock); + list_del(&mux->entry); + mutex_unlock(&mux_lock); +} +EXPORT_SYMBOL_GPL(typec_mux_unregister); diff --git a/drivers/usb/typec/typec.c b/drivers/usb/typec/typec.c deleted file mode 100644 index dc28ad868d93..000000000000 --- a/drivers/usb/typec/typec.c +++ /dev/null @@ -1,1365 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * USB Type-C Connector Class - * - * Copyright (C) 2017, Intel Corporation - * Author: Heikki Krogerus - */ - -#include -#include -#include -#include -#include - -struct typec_mode { - int index; - u32 vdo; - char *desc; - enum typec_port_type roles; - - struct typec_altmode *alt_mode; - - unsigned int active:1; - - char group_name[6]; - struct attribute_group group; - struct attribute *attrs[5]; - struct device_attribute vdo_attr; - struct device_attribute desc_attr; - struct device_attribute active_attr; - struct device_attribute roles_attr; -}; - -struct typec_altmode { - struct device dev; - u16 svid; - int n_modes; - struct typec_mode modes[ALTMODE_MAX_MODES]; - const struct attribute_group *mode_groups[ALTMODE_MAX_MODES]; -}; - -struct typec_plug { - struct device dev; - enum typec_plug_index index; -}; - -struct typec_cable { - struct device dev; - enum typec_plug_type type; - struct usb_pd_identity *identity; - unsigned int active:1; -}; - -struct typec_partner { - struct device dev; - unsigned int usb_pd:1; - struct usb_pd_identity *identity; - enum typec_accessory accessory; -}; - -struct typec_port { - unsigned int id; - struct device dev; - - int prefer_role; - enum typec_data_role data_role; - enum typec_role pwr_role; - enum typec_role vconn_role; - enum typec_pwr_opmode pwr_opmode; - enum typec_port_type port_type; - struct mutex port_type_lock; - - const struct typec_capability *cap; -}; - -#define to_typec_port(_dev_) container_of(_dev_, struct typec_port, dev) -#define to_typec_plug(_dev_) container_of(_dev_, struct typec_plug, dev) -#define to_typec_cable(_dev_) container_of(_dev_, struct typec_cable, dev) -#define to_typec_partner(_dev_) container_of(_dev_, struct typec_partner, dev) -#define to_altmode(_dev_) container_of(_dev_, struct typec_altmode, dev) - -static const struct device_type typec_partner_dev_type; -static const struct device_type typec_cable_dev_type; -static const struct device_type typec_plug_dev_type; -static const struct device_type typec_port_dev_type; - -#define is_typec_partner(_dev_) (_dev_->type == &typec_partner_dev_type) -#define is_typec_cable(_dev_) (_dev_->type == &typec_cable_dev_type) -#define is_typec_plug(_dev_) (_dev_->type == &typec_plug_dev_type) -#define is_typec_port(_dev_) (_dev_->type == &typec_port_dev_type) - -static DEFINE_IDA(typec_index_ida); -static struct class *typec_class; - -/* Common attributes */ - -static const char * const typec_accessory_modes[] = { - [TYPEC_ACCESSORY_NONE] = "none", - [TYPEC_ACCESSORY_AUDIO] = "analog_audio", - [TYPEC_ACCESSORY_DEBUG] = "debug", -}; - -static struct usb_pd_identity *get_pd_identity(struct device *dev) -{ - if (is_typec_partner(dev)) { - struct typec_partner *partner = to_typec_partner(dev); - - return partner->identity; - } else if (is_typec_cable(dev)) { - struct typec_cable *cable = to_typec_cable(dev); - - return cable->identity; - } - return NULL; -} - -static ssize_t id_header_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct usb_pd_identity *id = get_pd_identity(dev); - - return sprintf(buf, "0x%08x\n", id->id_header); -} -static DEVICE_ATTR_RO(id_header); - -static ssize_t cert_stat_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct usb_pd_identity *id = get_pd_identity(dev); - - return sprintf(buf, "0x%08x\n", id->cert_stat); -} -static DEVICE_ATTR_RO(cert_stat); - -static ssize_t product_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct usb_pd_identity *id = get_pd_identity(dev); - - return sprintf(buf, "0x%08x\n", id->product); -} -static DEVICE_ATTR_RO(product); - -static struct attribute *usb_pd_id_attrs[] = { - &dev_attr_id_header.attr, - &dev_attr_cert_stat.attr, - &dev_attr_product.attr, - NULL -}; - -static const struct attribute_group usb_pd_id_group = { - .name = "identity", - .attrs = usb_pd_id_attrs, -}; - -static const struct attribute_group *usb_pd_id_groups[] = { - &usb_pd_id_group, - NULL, -}; - -static void typec_report_identity(struct device *dev) -{ - sysfs_notify(&dev->kobj, "identity", "id_header"); - sysfs_notify(&dev->kobj, "identity", "cert_stat"); - sysfs_notify(&dev->kobj, "identity", "product"); -} - -/* ------------------------------------------------------------------------- */ -/* Alternate Modes */ - -/** - * typec_altmode_update_active - Report Enter/Exit mode - * @alt: Handle to the alternate mode - * @mode: Mode index - * @active: True when the mode has been entered - * - * If a partner or cable plug executes Enter/Exit Mode command successfully, the - * drivers use this routine to report the updated state of the mode. - */ -void typec_altmode_update_active(struct typec_altmode *alt, int mode, - bool active) -{ - struct typec_mode *m = &alt->modes[mode]; - char dir[6]; - - if (m->active == active) - return; - - m->active = active; - snprintf(dir, sizeof(dir), "mode%d", mode); - sysfs_notify(&alt->dev.kobj, dir, "active"); - kobject_uevent(&alt->dev.kobj, KOBJ_CHANGE); -} -EXPORT_SYMBOL_GPL(typec_altmode_update_active); - -/** - * typec_altmode2port - Alternate Mode to USB Type-C port - * @alt: The Alternate Mode - * - * Returns handle to the port that a cable plug or partner with @alt is - * connected to. - */ -struct typec_port *typec_altmode2port(struct typec_altmode *alt) -{ - if (is_typec_plug(alt->dev.parent)) - return to_typec_port(alt->dev.parent->parent->parent); - if (is_typec_partner(alt->dev.parent)) - return to_typec_port(alt->dev.parent->parent); - if (is_typec_port(alt->dev.parent)) - return to_typec_port(alt->dev.parent); - - return NULL; -} -EXPORT_SYMBOL_GPL(typec_altmode2port); - -static ssize_t -typec_altmode_vdo_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct typec_mode *mode = container_of(attr, struct typec_mode, - vdo_attr); - - return sprintf(buf, "0x%08x\n", mode->vdo); -} - -static ssize_t -typec_altmode_desc_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct typec_mode *mode = container_of(attr, struct typec_mode, - desc_attr); - - return sprintf(buf, "%s\n", mode->desc ? mode->desc : ""); -} - -static ssize_t -typec_altmode_active_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct typec_mode *mode = container_of(attr, struct typec_mode, - active_attr); - - return sprintf(buf, "%s\n", mode->active ? "yes" : "no"); -} - -static ssize_t -typec_altmode_active_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t size) -{ - struct typec_mode *mode = container_of(attr, struct typec_mode, - active_attr); - struct typec_port *port = typec_altmode2port(mode->alt_mode); - bool activate; - int ret; - - if (!port->cap->activate_mode) - return -EOPNOTSUPP; - - ret = kstrtobool(buf, &activate); - if (ret) - return ret; - - ret = port->cap->activate_mode(port->cap, mode->index, activate); - if (ret) - return ret; - - return size; -} - -static ssize_t -typec_altmode_roles_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct typec_mode *mode = container_of(attr, struct typec_mode, - roles_attr); - ssize_t ret; - - switch (mode->roles) { - case TYPEC_PORT_DFP: - ret = sprintf(buf, "source\n"); - break; - case TYPEC_PORT_UFP: - ret = sprintf(buf, "sink\n"); - break; - case TYPEC_PORT_DRP: - default: - ret = sprintf(buf, "source sink\n"); - break; - } - return ret; -} - -static void typec_init_modes(struct typec_altmode *alt, - const struct typec_mode_desc *desc, bool is_port) -{ - int i; - - for (i = 0; i < alt->n_modes; i++, desc++) { - struct typec_mode *mode = &alt->modes[i]; - - /* Not considering the human readable description critical */ - mode->desc = kstrdup(desc->desc, GFP_KERNEL); - if (desc->desc && !mode->desc) - dev_err(&alt->dev, "failed to copy mode%d desc\n", i); - - mode->alt_mode = alt; - mode->vdo = desc->vdo; - mode->roles = desc->roles; - mode->index = desc->index; - sprintf(mode->group_name, "mode%d", desc->index); - - sysfs_attr_init(&mode->vdo_attr.attr); - mode->vdo_attr.attr.name = "vdo"; - mode->vdo_attr.attr.mode = 0444; - mode->vdo_attr.show = typec_altmode_vdo_show; - - sysfs_attr_init(&mode->desc_attr.attr); - mode->desc_attr.attr.name = "description"; - mode->desc_attr.attr.mode = 0444; - mode->desc_attr.show = typec_altmode_desc_show; - - sysfs_attr_init(&mode->active_attr.attr); - mode->active_attr.attr.name = "active"; - mode->active_attr.attr.mode = 0644; - mode->active_attr.show = typec_altmode_active_show; - mode->active_attr.store = typec_altmode_active_store; - - mode->attrs[0] = &mode->vdo_attr.attr; - mode->attrs[1] = &mode->desc_attr.attr; - mode->attrs[2] = &mode->active_attr.attr; - - /* With ports, list the roles that the mode is supported with */ - if (is_port) { - sysfs_attr_init(&mode->roles_attr.attr); - mode->roles_attr.attr.name = "supported_roles"; - mode->roles_attr.attr.mode = 0444; - mode->roles_attr.show = typec_altmode_roles_show; - - mode->attrs[3] = &mode->roles_attr.attr; - } - - mode->group.attrs = mode->attrs; - mode->group.name = mode->group_name; - - alt->mode_groups[i] = &mode->group; - } -} - -static ssize_t svid_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct typec_altmode *alt = to_altmode(dev); - - return sprintf(buf, "%04x\n", alt->svid); -} -static DEVICE_ATTR_RO(svid); - -static struct attribute *typec_altmode_attrs[] = { - &dev_attr_svid.attr, - NULL -}; -ATTRIBUTE_GROUPS(typec_altmode); - -static void typec_altmode_release(struct device *dev) -{ - struct typec_altmode *alt = to_altmode(dev); - int i; - - for (i = 0; i < alt->n_modes; i++) - kfree(alt->modes[i].desc); - kfree(alt); -} - -static const struct device_type typec_altmode_dev_type = { - .name = "typec_alternate_mode", - .groups = typec_altmode_groups, - .release = typec_altmode_release, -}; - -static struct typec_altmode * -typec_register_altmode(struct device *parent, - const struct typec_altmode_desc *desc) -{ - struct typec_altmode *alt; - int ret; - - alt = kzalloc(sizeof(*alt), GFP_KERNEL); - if (!alt) - return ERR_PTR(-ENOMEM); - - alt->svid = desc->svid; - alt->n_modes = desc->n_modes; - typec_init_modes(alt, desc->modes, is_typec_port(parent)); - - alt->dev.parent = parent; - alt->dev.groups = alt->mode_groups; - alt->dev.type = &typec_altmode_dev_type; - dev_set_name(&alt->dev, "svid-%04x", alt->svid); - - ret = device_register(&alt->dev); - if (ret) { - dev_err(parent, "failed to register alternate mode (%d)\n", - ret); - put_device(&alt->dev); - return ERR_PTR(ret); - } - - return alt; -} - -/** - * typec_unregister_altmode - Unregister Alternate Mode - * @alt: The alternate mode to be unregistered - * - * Unregister device created with typec_partner_register_altmode(), - * typec_plug_register_altmode() or typec_port_register_altmode(). - */ -void typec_unregister_altmode(struct typec_altmode *alt) -{ - if (!IS_ERR_OR_NULL(alt)) - device_unregister(&alt->dev); -} -EXPORT_SYMBOL_GPL(typec_unregister_altmode); - -/* ------------------------------------------------------------------------- */ -/* Type-C Partners */ - -static ssize_t accessory_mode_show(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - struct typec_partner *p = to_typec_partner(dev); - - return sprintf(buf, "%s\n", typec_accessory_modes[p->accessory]); -} -static DEVICE_ATTR_RO(accessory_mode); - -static ssize_t supports_usb_power_delivery_show(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - struct typec_partner *p = to_typec_partner(dev); - - return sprintf(buf, "%s\n", p->usb_pd ? "yes" : "no"); -} -static DEVICE_ATTR_RO(supports_usb_power_delivery); - -static struct attribute *typec_partner_attrs[] = { - &dev_attr_accessory_mode.attr, - &dev_attr_supports_usb_power_delivery.attr, - NULL -}; -ATTRIBUTE_GROUPS(typec_partner); - -static void typec_partner_release(struct device *dev) -{ - struct typec_partner *partner = to_typec_partner(dev); - - kfree(partner); -} - -static const struct device_type typec_partner_dev_type = { - .name = "typec_partner", - .groups = typec_partner_groups, - .release = typec_partner_release, -}; - -/** - * typec_partner_set_identity - Report result from Discover Identity command - * @partner: The partner updated identity values - * - * This routine is used to report that the result of Discover Identity USB power - * delivery command has become available. - */ -int typec_partner_set_identity(struct typec_partner *partner) -{ - if (!partner->identity) - return -EINVAL; - - typec_report_identity(&partner->dev); - return 0; -} -EXPORT_SYMBOL_GPL(typec_partner_set_identity); - -/** - * typec_partner_register_altmode - Register USB Type-C Partner Alternate Mode - * @partner: USB Type-C Partner that supports the alternate mode - * @desc: Description of the alternate mode - * - * This routine is used to register each alternate mode individually that - * @partner has listed in response to Discover SVIDs command. The modes for a - * SVID listed in response to Discover Modes command need to be listed in an - * array in @desc. - * - * Returns handle to the alternate mode on success or NULL on failure. - */ -struct typec_altmode * -typec_partner_register_altmode(struct typec_partner *partner, - const struct typec_altmode_desc *desc) -{ - return typec_register_altmode(&partner->dev, desc); -} -EXPORT_SYMBOL_GPL(typec_partner_register_altmode); - -/** - * typec_register_partner - Register a USB Type-C Partner - * @port: The USB Type-C Port the partner is connected to - * @desc: Description of the partner - * - * Registers a device for USB Type-C Partner described in @desc. - * - * Returns handle to the partner on success or ERR_PTR on failure. - */ -struct typec_partner *typec_register_partner(struct typec_port *port, - struct typec_partner_desc *desc) -{ - struct typec_partner *partner; - int ret; - - partner = kzalloc(sizeof(*partner), GFP_KERNEL); - if (!partner) - return ERR_PTR(-ENOMEM); - - partner->usb_pd = desc->usb_pd; - partner->accessory = desc->accessory; - - if (desc->identity) { - /* - * Creating directory for the identity only if the driver is - * able to provide data to it. - */ - partner->dev.groups = usb_pd_id_groups; - partner->identity = desc->identity; - } - - partner->dev.class = typec_class; - partner->dev.parent = &port->dev; - partner->dev.type = &typec_partner_dev_type; - dev_set_name(&partner->dev, "%s-partner", dev_name(&port->dev)); - - ret = device_register(&partner->dev); - if (ret) { - dev_err(&port->dev, "failed to register partner (%d)\n", ret); - put_device(&partner->dev); - return ERR_PTR(ret); - } - - return partner; -} -EXPORT_SYMBOL_GPL(typec_register_partner); - -/** - * typec_unregister_partner - Unregister a USB Type-C Partner - * @partner: The partner to be unregistered - * - * Unregister device created with typec_register_partner(). - */ -void typec_unregister_partner(struct typec_partner *partner) -{ - if (!IS_ERR_OR_NULL(partner)) - device_unregister(&partner->dev); -} -EXPORT_SYMBOL_GPL(typec_unregister_partner); - -/* ------------------------------------------------------------------------- */ -/* Type-C Cable Plugs */ - -static void typec_plug_release(struct device *dev) -{ - struct typec_plug *plug = to_typec_plug(dev); - - kfree(plug); -} - -static const struct device_type typec_plug_dev_type = { - .name = "typec_plug", - .release = typec_plug_release, -}; - -/** - * typec_plug_register_altmode - Register USB Type-C Cable Plug Alternate Mode - * @plug: USB Type-C Cable Plug that supports the alternate mode - * @desc: Description of the alternate mode - * - * This routine is used to register each alternate mode individually that @plug - * has listed in response to Discover SVIDs command. The modes for a SVID that - * the plug lists in response to Discover Modes command need to be listed in an - * array in @desc. - * - * Returns handle to the alternate mode on success or ERR_PTR on failure. - */ -struct typec_altmode * -typec_plug_register_altmode(struct typec_plug *plug, - const struct typec_altmode_desc *desc) -{ - return typec_register_altmode(&plug->dev, desc); -} -EXPORT_SYMBOL_GPL(typec_plug_register_altmode); - -/** - * typec_register_plug - Register a USB Type-C Cable Plug - * @cable: USB Type-C Cable with the plug - * @desc: Description of the cable plug - * - * Registers a device for USB Type-C Cable Plug described in @desc. A USB Type-C - * Cable Plug represents a plug with electronics in it that can response to USB - * Power Delivery SOP Prime or SOP Double Prime packages. - * - * Returns handle to the cable plug on success or ERR_PTR on failure. - */ -struct typec_plug *typec_register_plug(struct typec_cable *cable, - struct typec_plug_desc *desc) -{ - struct typec_plug *plug; - char name[8]; - int ret; - - plug = kzalloc(sizeof(*plug), GFP_KERNEL); - if (!plug) - return ERR_PTR(-ENOMEM); - - sprintf(name, "plug%d", desc->index); - - plug->index = desc->index; - plug->dev.class = typec_class; - plug->dev.parent = &cable->dev; - plug->dev.type = &typec_plug_dev_type; - dev_set_name(&plug->dev, "%s-%s", dev_name(cable->dev.parent), name); - - ret = device_register(&plug->dev); - if (ret) { - dev_err(&cable->dev, "failed to register plug (%d)\n", ret); - put_device(&plug->dev); - return ERR_PTR(ret); - } - - return plug; -} -EXPORT_SYMBOL_GPL(typec_register_plug); - -/** - * typec_unregister_plug - Unregister a USB Type-C Cable Plug - * @plug: The cable plug to be unregistered - * - * Unregister device created with typec_register_plug(). - */ -void typec_unregister_plug(struct typec_plug *plug) -{ - if (!IS_ERR_OR_NULL(plug)) - device_unregister(&plug->dev); -} -EXPORT_SYMBOL_GPL(typec_unregister_plug); - -/* Type-C Cables */ - -static ssize_t -type_show(struct device *dev, struct device_attribute *attr, char *buf) -{ - struct typec_cable *cable = to_typec_cable(dev); - - return sprintf(buf, "%s\n", cable->active ? "active" : "passive"); -} -static DEVICE_ATTR_RO(type); - -static const char * const typec_plug_types[] = { - [USB_PLUG_NONE] = "unknown", - [USB_PLUG_TYPE_A] = "type-a", - [USB_PLUG_TYPE_B] = "type-b", - [USB_PLUG_TYPE_C] = "type-c", - [USB_PLUG_CAPTIVE] = "captive", -}; - -static ssize_t plug_type_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct typec_cable *cable = to_typec_cable(dev); - - return sprintf(buf, "%s\n", typec_plug_types[cable->type]); -} -static DEVICE_ATTR_RO(plug_type); - -static struct attribute *typec_cable_attrs[] = { - &dev_attr_type.attr, - &dev_attr_plug_type.attr, - NULL -}; -ATTRIBUTE_GROUPS(typec_cable); - -static void typec_cable_release(struct device *dev) -{ - struct typec_cable *cable = to_typec_cable(dev); - - kfree(cable); -} - -static const struct device_type typec_cable_dev_type = { - .name = "typec_cable", - .groups = typec_cable_groups, - .release = typec_cable_release, -}; - -/** - * typec_cable_set_identity - Report result from Discover Identity command - * @cable: The cable updated identity values - * - * This routine is used to report that the result of Discover Identity USB power - * delivery command has become available. - */ -int typec_cable_set_identity(struct typec_cable *cable) -{ - if (!cable->identity) - return -EINVAL; - - typec_report_identity(&cable->dev); - return 0; -} -EXPORT_SYMBOL_GPL(typec_cable_set_identity); - -/** - * typec_register_cable - Register a USB Type-C Cable - * @port: The USB Type-C Port the cable is connected to - * @desc: Description of the cable - * - * Registers a device for USB Type-C Cable described in @desc. The cable will be - * parent for the optional cable plug devises. - * - * Returns handle to the cable on success or ERR_PTR on failure. - */ -struct typec_cable *typec_register_cable(struct typec_port *port, - struct typec_cable_desc *desc) -{ - struct typec_cable *cable; - int ret; - - cable = kzalloc(sizeof(*cable), GFP_KERNEL); - if (!cable) - return ERR_PTR(-ENOMEM); - - cable->type = desc->type; - cable->active = desc->active; - - if (desc->identity) { - /* - * Creating directory for the identity only if the driver is - * able to provide data to it. - */ - cable->dev.groups = usb_pd_id_groups; - cable->identity = desc->identity; - } - - cable->dev.class = typec_class; - cable->dev.parent = &port->dev; - cable->dev.type = &typec_cable_dev_type; - dev_set_name(&cable->dev, "%s-cable", dev_name(&port->dev)); - - ret = device_register(&cable->dev); - if (ret) { - dev_err(&port->dev, "failed to register cable (%d)\n", ret); - put_device(&cable->dev); - return ERR_PTR(ret); - } - - return cable; -} -EXPORT_SYMBOL_GPL(typec_register_cable); - -/** - * typec_unregister_cable - Unregister a USB Type-C Cable - * @cable: The cable to be unregistered - * - * Unregister device created with typec_register_cable(). - */ -void typec_unregister_cable(struct typec_cable *cable) -{ - if (!IS_ERR_OR_NULL(cable)) - device_unregister(&cable->dev); -} -EXPORT_SYMBOL_GPL(typec_unregister_cable); - -/* ------------------------------------------------------------------------- */ -/* USB Type-C ports */ - -static const char * const typec_roles[] = { - [TYPEC_SINK] = "sink", - [TYPEC_SOURCE] = "source", -}; - -static const char * const typec_data_roles[] = { - [TYPEC_DEVICE] = "device", - [TYPEC_HOST] = "host", -}; - -static const char * const typec_port_types[] = { - [TYPEC_PORT_DFP] = "source", - [TYPEC_PORT_UFP] = "sink", - [TYPEC_PORT_DRP] = "dual", -}; - -static const char * const typec_port_types_drp[] = { - [TYPEC_PORT_DFP] = "dual [source] sink", - [TYPEC_PORT_UFP] = "dual source [sink]", - [TYPEC_PORT_DRP] = "[dual] source sink", -}; - -static ssize_t -preferred_role_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t size) -{ - struct typec_port *port = to_typec_port(dev); - int role; - int ret; - - if (port->cap->type != TYPEC_PORT_DRP) { - dev_dbg(dev, "Preferred role only supported with DRP ports\n"); - return -EOPNOTSUPP; - } - - if (!port->cap->try_role) { - dev_dbg(dev, "Setting preferred role not supported\n"); - return -EOPNOTSUPP; - } - - role = sysfs_match_string(typec_roles, buf); - if (role < 0) { - if (sysfs_streq(buf, "none")) - role = TYPEC_NO_PREFERRED_ROLE; - else - return -EINVAL; - } - - ret = port->cap->try_role(port->cap, role); - if (ret) - return ret; - - port->prefer_role = role; - return size; -} - -static ssize_t -preferred_role_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct typec_port *port = to_typec_port(dev); - - if (port->cap->type != TYPEC_PORT_DRP) - return 0; - - if (port->prefer_role < 0) - return 0; - - return sprintf(buf, "%s\n", typec_roles[port->prefer_role]); -} -static DEVICE_ATTR_RW(preferred_role); - -static ssize_t data_role_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t size) -{ - struct typec_port *port = to_typec_port(dev); - int ret; - - if (!port->cap->dr_set) { - dev_dbg(dev, "data role swapping not supported\n"); - return -EOPNOTSUPP; - } - - ret = sysfs_match_string(typec_data_roles, buf); - if (ret < 0) - return ret; - - mutex_lock(&port->port_type_lock); - if (port->port_type != TYPEC_PORT_DRP) { - dev_dbg(dev, "port type fixed at \"%s\"", - typec_port_types[port->port_type]); - ret = -EOPNOTSUPP; - goto unlock_and_ret; - } - - ret = port->cap->dr_set(port->cap, ret); - if (ret) - goto unlock_and_ret; - - ret = size; -unlock_and_ret: - mutex_unlock(&port->port_type_lock); - return ret; -} - -static ssize_t data_role_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct typec_port *port = to_typec_port(dev); - - if (port->cap->type == TYPEC_PORT_DRP) - return sprintf(buf, "%s\n", port->data_role == TYPEC_HOST ? - "[host] device" : "host [device]"); - - return sprintf(buf, "[%s]\n", typec_data_roles[port->data_role]); -} -static DEVICE_ATTR_RW(data_role); - -static ssize_t power_role_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t size) -{ - struct typec_port *port = to_typec_port(dev); - int ret; - - if (!port->cap->pd_revision) { - dev_dbg(dev, "USB Power Delivery not supported\n"); - return -EOPNOTSUPP; - } - - if (!port->cap->pr_set) { - dev_dbg(dev, "power role swapping not supported\n"); - return -EOPNOTSUPP; - } - - if (port->pwr_opmode != TYPEC_PWR_MODE_PD) { - dev_dbg(dev, "partner unable to swap power role\n"); - return -EIO; - } - - ret = sysfs_match_string(typec_roles, buf); - if (ret < 0) - return ret; - - mutex_lock(&port->port_type_lock); - if (port->port_type != TYPEC_PORT_DRP) { - dev_dbg(dev, "port type fixed at \"%s\"", - typec_port_types[port->port_type]); - ret = -EOPNOTSUPP; - goto unlock_and_ret; - } - - ret = port->cap->pr_set(port->cap, ret); - if (ret) - goto unlock_and_ret; - - ret = size; -unlock_and_ret: - mutex_unlock(&port->port_type_lock); - return ret; -} - -static ssize_t power_role_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct typec_port *port = to_typec_port(dev); - - if (port->cap->type == TYPEC_PORT_DRP) - return sprintf(buf, "%s\n", port->pwr_role == TYPEC_SOURCE ? - "[source] sink" : "source [sink]"); - - return sprintf(buf, "[%s]\n", typec_roles[port->pwr_role]); -} -static DEVICE_ATTR_RW(power_role); - -static ssize_t -port_type_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t size) -{ - struct typec_port *port = to_typec_port(dev); - int ret; - enum typec_port_type type; - - if (!port->cap->port_type_set || port->cap->type != TYPEC_PORT_DRP) { - dev_dbg(dev, "changing port type not supported\n"); - return -EOPNOTSUPP; - } - - ret = sysfs_match_string(typec_port_types, buf); - if (ret < 0) - return ret; - - type = ret; - mutex_lock(&port->port_type_lock); - - if (port->port_type == type) { - ret = size; - goto unlock_and_ret; - } - - ret = port->cap->port_type_set(port->cap, type); - if (ret) - goto unlock_and_ret; - - port->port_type = type; - ret = size; - -unlock_and_ret: - mutex_unlock(&port->port_type_lock); - return ret; -} - -static ssize_t -port_type_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct typec_port *port = to_typec_port(dev); - - if (port->cap->type == TYPEC_PORT_DRP) - return sprintf(buf, "%s\n", - typec_port_types_drp[port->port_type]); - - return sprintf(buf, "[%s]\n", typec_port_types[port->cap->type]); -} -static DEVICE_ATTR_RW(port_type); - -static const char * const typec_pwr_opmodes[] = { - [TYPEC_PWR_MODE_USB] = "default", - [TYPEC_PWR_MODE_1_5A] = "1.5A", - [TYPEC_PWR_MODE_3_0A] = "3.0A", - [TYPEC_PWR_MODE_PD] = "usb_power_delivery", -}; - -static ssize_t power_operation_mode_show(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - struct typec_port *port = to_typec_port(dev); - - return sprintf(buf, "%s\n", typec_pwr_opmodes[port->pwr_opmode]); -} -static DEVICE_ATTR_RO(power_operation_mode); - -static ssize_t vconn_source_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t size) -{ - struct typec_port *port = to_typec_port(dev); - bool source; - int ret; - - if (!port->cap->pd_revision) { - dev_dbg(dev, "VCONN swap depends on USB Power Delivery\n"); - return -EOPNOTSUPP; - } - - if (!port->cap->vconn_set) { - dev_dbg(dev, "VCONN swapping not supported\n"); - return -EOPNOTSUPP; - } - - ret = kstrtobool(buf, &source); - if (ret) - return ret; - - ret = port->cap->vconn_set(port->cap, (enum typec_role)source); - if (ret) - return ret; - - return size; -} - -static ssize_t vconn_source_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct typec_port *port = to_typec_port(dev); - - return sprintf(buf, "%s\n", - port->vconn_role == TYPEC_SOURCE ? "yes" : "no"); -} -static DEVICE_ATTR_RW(vconn_source); - -static ssize_t supported_accessory_modes_show(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - struct typec_port *port = to_typec_port(dev); - ssize_t ret = 0; - int i; - - for (i = 0; i < ARRAY_SIZE(port->cap->accessory); i++) { - if (port->cap->accessory[i]) - ret += sprintf(buf + ret, "%s ", - typec_accessory_modes[port->cap->accessory[i]]); - } - - if (!ret) - return sprintf(buf, "none\n"); - - buf[ret - 1] = '\n'; - - return ret; -} -static DEVICE_ATTR_RO(supported_accessory_modes); - -static ssize_t usb_typec_revision_show(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - struct typec_port *port = to_typec_port(dev); - u16 rev = port->cap->revision; - - return sprintf(buf, "%d.%d\n", (rev >> 8) & 0xff, (rev >> 4) & 0xf); -} -static DEVICE_ATTR_RO(usb_typec_revision); - -static ssize_t usb_power_delivery_revision_show(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - struct typec_port *p = to_typec_port(dev); - - return sprintf(buf, "%d\n", (p->cap->pd_revision >> 8) & 0xff); -} -static DEVICE_ATTR_RO(usb_power_delivery_revision); - -static struct attribute *typec_attrs[] = { - &dev_attr_data_role.attr, - &dev_attr_power_operation_mode.attr, - &dev_attr_power_role.attr, - &dev_attr_preferred_role.attr, - &dev_attr_supported_accessory_modes.attr, - &dev_attr_usb_power_delivery_revision.attr, - &dev_attr_usb_typec_revision.attr, - &dev_attr_vconn_source.attr, - &dev_attr_port_type.attr, - NULL, -}; -ATTRIBUTE_GROUPS(typec); - -static int typec_uevent(struct device *dev, struct kobj_uevent_env *env) -{ - int ret; - - ret = add_uevent_var(env, "TYPEC_PORT=%s", dev_name(dev)); - if (ret) - dev_err(dev, "failed to add uevent TYPEC_PORT\n"); - - return ret; -} - -static void typec_release(struct device *dev) -{ - struct typec_port *port = to_typec_port(dev); - - ida_simple_remove(&typec_index_ida, port->id); - kfree(port); -} - -static const struct device_type typec_port_dev_type = { - .name = "typec_port", - .groups = typec_groups, - .uevent = typec_uevent, - .release = typec_release, -}; - -/* --------------------------------------- */ -/* Driver callbacks to report role updates */ - -/** - * typec_set_data_role - Report data role change - * @port: The USB Type-C Port where the role was changed - * @role: The new data role - * - * This routine is used by the port drivers to report data role changes. - */ -void typec_set_data_role(struct typec_port *port, enum typec_data_role role) -{ - if (port->data_role == role) - return; - - port->data_role = role; - sysfs_notify(&port->dev.kobj, NULL, "data_role"); - kobject_uevent(&port->dev.kobj, KOBJ_CHANGE); -} -EXPORT_SYMBOL_GPL(typec_set_data_role); - -/** - * typec_set_pwr_role - Report power role change - * @port: The USB Type-C Port where the role was changed - * @role: The new data role - * - * This routine is used by the port drivers to report power role changes. - */ -void typec_set_pwr_role(struct typec_port *port, enum typec_role role) -{ - if (port->pwr_role == role) - return; - - port->pwr_role = role; - sysfs_notify(&port->dev.kobj, NULL, "power_role"); - kobject_uevent(&port->dev.kobj, KOBJ_CHANGE); -} -EXPORT_SYMBOL_GPL(typec_set_pwr_role); - -/** - * typec_set_pwr_role - Report VCONN source change - * @port: The USB Type-C Port which VCONN role changed - * @role: Source when @port is sourcing VCONN, or Sink when it's not - * - * This routine is used by the port drivers to report if the VCONN source is - * changes. - */ -void typec_set_vconn_role(struct typec_port *port, enum typec_role role) -{ - if (port->vconn_role == role) - return; - - port->vconn_role = role; - sysfs_notify(&port->dev.kobj, NULL, "vconn_source"); - kobject_uevent(&port->dev.kobj, KOBJ_CHANGE); -} -EXPORT_SYMBOL_GPL(typec_set_vconn_role); - -static int partner_match(struct device *dev, void *data) -{ - return is_typec_partner(dev); -} - -/** - * typec_set_pwr_opmode - Report changed power operation mode - * @port: The USB Type-C Port where the mode was changed - * @opmode: New power operation mode - * - * This routine is used by the port drivers to report changed power operation - * mode in @port. The modes are USB (default), 1.5A, 3.0A as defined in USB - * Type-C specification, and "USB Power Delivery" when the power levels are - * negotiated with methods defined in USB Power Delivery specification. - */ -void typec_set_pwr_opmode(struct typec_port *port, - enum typec_pwr_opmode opmode) -{ - struct device *partner_dev; - - if (port->pwr_opmode == opmode) - return; - - port->pwr_opmode = opmode; - sysfs_notify(&port->dev.kobj, NULL, "power_operation_mode"); - kobject_uevent(&port->dev.kobj, KOBJ_CHANGE); - - partner_dev = device_find_child(&port->dev, NULL, partner_match); - if (partner_dev) { - struct typec_partner *partner = to_typec_partner(partner_dev); - - if (opmode == TYPEC_PWR_MODE_PD && !partner->usb_pd) { - partner->usb_pd = 1; - sysfs_notify(&partner_dev->kobj, NULL, - "supports_usb_power_delivery"); - } - put_device(partner_dev); - } -} -EXPORT_SYMBOL_GPL(typec_set_pwr_opmode); - -/* --------------------------------------- */ - -/** - * typec_port_register_altmode - Register USB Type-C Port Alternate Mode - * @port: USB Type-C Port that supports the alternate mode - * @desc: Description of the alternate mode - * - * This routine is used to register an alternate mode that @port is capable of - * supporting. - * - * Returns handle to the alternate mode on success or ERR_PTR on failure. - */ -struct typec_altmode * -typec_port_register_altmode(struct typec_port *port, - const struct typec_altmode_desc *desc) -{ - return typec_register_altmode(&port->dev, desc); -} -EXPORT_SYMBOL_GPL(typec_port_register_altmode); - -/** - * typec_register_port - Register a USB Type-C Port - * @parent: Parent device - * @cap: Description of the port - * - * Registers a device for USB Type-C Port described in @cap. - * - * Returns handle to the port on success or ERR_PTR on failure. - */ -struct typec_port *typec_register_port(struct device *parent, - const struct typec_capability *cap) -{ - struct typec_port *port; - int role; - int ret; - int id; - - port = kzalloc(sizeof(*port), GFP_KERNEL); - if (!port) - return ERR_PTR(-ENOMEM); - - id = ida_simple_get(&typec_index_ida, 0, 0, GFP_KERNEL); - if (id < 0) { - kfree(port); - return ERR_PTR(id); - } - - if (cap->type == TYPEC_PORT_DFP) - role = TYPEC_SOURCE; - else if (cap->type == TYPEC_PORT_UFP) - role = TYPEC_SINK; - else - role = cap->prefer_role; - - if (role == TYPEC_SOURCE) { - port->data_role = TYPEC_HOST; - port->pwr_role = TYPEC_SOURCE; - port->vconn_role = TYPEC_SOURCE; - } else { - port->data_role = TYPEC_DEVICE; - port->pwr_role = TYPEC_SINK; - port->vconn_role = TYPEC_SINK; - } - - port->id = id; - port->cap = cap; - port->port_type = cap->type; - mutex_init(&port->port_type_lock); - port->prefer_role = cap->prefer_role; - - port->dev.class = typec_class; - port->dev.parent = parent; - port->dev.fwnode = cap->fwnode; - port->dev.type = &typec_port_dev_type; - dev_set_name(&port->dev, "port%d", id); - - ret = device_register(&port->dev); - if (ret) { - dev_err(parent, "failed to register port (%d)\n", ret); - put_device(&port->dev); - return ERR_PTR(ret); - } - - return port; -} -EXPORT_SYMBOL_GPL(typec_register_port); - -/** - * typec_unregister_port - Unregister a USB Type-C Port - * @port: The port to be unregistered - * - * Unregister device created with typec_register_port(). - */ -void typec_unregister_port(struct typec_port *port) -{ - if (!IS_ERR_OR_NULL(port)) - device_unregister(&port->dev); -} -EXPORT_SYMBOL_GPL(typec_unregister_port); - -static int __init typec_init(void) -{ - typec_class = class_create(THIS_MODULE, "typec"); - return PTR_ERR_OR_ZERO(typec_class); -} -subsys_initcall(typec_init); - -static void __exit typec_exit(void) -{ - class_destroy(typec_class); - ida_destroy(&typec_index_ida); -} -module_exit(typec_exit); - -MODULE_AUTHOR("Heikki Krogerus "); -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("USB Type-C Connector Class"); diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h index 0d44ce6af08f..2408e5c2ed91 100644 --- a/include/linux/usb/typec.h +++ b/include/linux/usb/typec.h @@ -60,6 +60,12 @@ enum typec_accessory { #define TYPEC_MAX_ACCESSORY 3 +enum typec_orientation { + TYPEC_ORIENTATION_NONE, + TYPEC_ORIENTATION_NORMAL, + TYPEC_ORIENTATION_REVERSE, +}; + /* * struct usb_pd_identity - USB Power Delivery identity data * @id_header: ID Header VDO @@ -185,6 +191,8 @@ struct typec_partner_desc { * @pd_revision: USB Power Delivery Specification revision if supported * @prefer_role: Initial role preference * @accessory: Supported Accessory Modes + * @sw: Cable plug orientation switch + * @mux: Multiplexer switch for Alternate/Accessory Modes * @fwnode: Optional fwnode of the port * @try_role: Set data role preference for DRP port * @dr_set: Set Data Role @@ -202,6 +210,8 @@ struct typec_capability { int prefer_role; enum typec_accessory accessory[TYPEC_MAX_ACCESSORY]; + struct typec_switch *sw; + struct typec_mux *mux; struct fwnode_handle *fwnode; int (*try_role)(const struct typec_capability *, @@ -245,4 +255,8 @@ void typec_set_pwr_role(struct typec_port *port, enum typec_role role); void typec_set_vconn_role(struct typec_port *port, enum typec_role role); void typec_set_pwr_opmode(struct typec_port *port, enum typec_pwr_opmode mode); +int typec_set_orientation(struct typec_port *port, + enum typec_orientation orientation); +int typec_set_mode(struct typec_port *port, int mode); + #endif /* __LINUX_USB_TYPEC_H */ diff --git a/include/linux/usb/typec_mux.h b/include/linux/usb/typec_mux.h new file mode 100644 index 000000000000..12c1b057834b --- /dev/null +++ b/include/linux/usb/typec_mux.h @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: GPL-2.0 + +#ifndef __USB_TYPEC_MUX +#define __USB_TYPEC_MUX + +#include +#include + +struct device; + +/** + * struct typec_switch - USB Type-C cable orientation switch + * @dev: Switch device + * @entry: List entry + * @set: Callback to the driver for setting the orientation + * + * USB Type-C pin flipper switch routing the correct data pairs from the + * connector to the USB controller depending on the orientation of the cable + * plug. + */ +struct typec_switch { + struct device *dev; + struct list_head entry; + + int (*set)(struct typec_switch *sw, enum typec_orientation orientation); +}; + +/** + * struct typec_switch - USB Type-C connector pin mux + * @dev: Mux device + * @entry: List entry + * @set: Callback to the driver for setting the state of the mux + * + * Pin Multiplexer/DeMultiplexer switch routing the USB Type-C connector pins to + * different components depending on the requested mode of operation. Used with + * Accessory/Alternate modes. + */ +struct typec_mux { + struct device *dev; + struct list_head entry; + + int (*set)(struct typec_mux *mux, int state); +}; + +struct typec_switch *typec_switch_get(struct device *dev); +void typec_switch_put(struct typec_switch *sw); +int typec_switch_register(struct typec_switch *sw); +void typec_switch_unregister(struct typec_switch *sw); + +struct typec_mux *typec_mux_get(struct device *dev); +void typec_mux_put(struct typec_mux *mux); +int typec_mux_register(struct typec_mux *mux); +void typec_mux_unregister(struct typec_mux *mux); + +#endif /* __USB_TYPEC_MUX */ -- cgit v1.2.3 From fde0aa6c175a4d8aa19e82b86ae0f9278bc8563b Mon Sep 17 00:00:00 2001 From: Heikki Krogerus Date: Tue, 20 Mar 2018 15:57:04 +0300 Subject: usb: common: Small class for USB role switches USB role switch is a device that can be used to choose the data role for USB connector. With dual-role capable USB controllers, the controller itself will be the switch, but on some platforms the USB host and device controllers are separate IPs and there is a mux between them and the connector. On those platforms the mux driver will need to register the switch. With USB Type-C connectors, the host-to-device relationship is negotiated over the Configuration Channel (CC). That means the USB Type-C drivers need to be in control of the role switch. The class provides a simple API for the USB Type-C drivers for the control. For other types of USB connectors (mainly microAB) the class provides user space control via sysfs attribute file that can be used to request role swapping from the switch. Reviewed-by: Hans de Goede Reviewed-by: Andy Shevchenko Signed-off-by: Heikki Krogerus Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-class-usb_role | 21 ++ drivers/usb/Kconfig | 3 + drivers/usb/common/Makefile | 1 + drivers/usb/common/roles.c | 305 +++++++++++++++++++++++++ include/linux/usb/role.h | 53 +++++ 5 files changed, 383 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-class-usb_role create mode 100644 drivers/usb/common/roles.c create mode 100644 include/linux/usb/role.h (limited to 'Documentation') diff --git a/Documentation/ABI/testing/sysfs-class-usb_role b/Documentation/ABI/testing/sysfs-class-usb_role new file mode 100644 index 000000000000..3b810a425a52 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-class-usb_role @@ -0,0 +1,21 @@ +What: /sys/class/usb_role/ +Date: Jan 2018 +Contact: Heikki Krogerus +Description: + Place in sysfs for USB Role Switches. USB Role Switch is a + device that can select the data role (host or device) for USB + port. + +What: /sys/class/usb_role//role +Date: Jan 2018 +Contact: Heikki Krogerus +Description: + The current role of the switch. This attribute can be used for + requesting role swapping with non-USB Type-C ports. With USB + Type-C ports, the ABI defined for USB Type-C connector class + must be used. + + Valid values: + - none + - host + - device diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig index 148f3ee70286..f278958e04ca 100644 --- a/drivers/usb/Kconfig +++ b/drivers/usb/Kconfig @@ -203,4 +203,7 @@ config USB_ULPI_BUS To compile this driver as a module, choose M here: the module will be called ulpi. +config USB_ROLE_SWITCH + tristate + endif # USB_SUPPORT diff --git a/drivers/usb/common/Makefile b/drivers/usb/common/Makefile index 0a7c45e85481..fb4d5ef4165c 100644 --- a/drivers/usb/common/Makefile +++ b/drivers/usb/common/Makefile @@ -9,3 +9,4 @@ usb-common-$(CONFIG_USB_LED_TRIG) += led.o obj-$(CONFIG_USB_OTG_FSM) += usb-otg-fsm.o obj-$(CONFIG_USB_ULPI_BUS) += ulpi.o +obj-$(CONFIG_USB_ROLE_SWITCH) += roles.o diff --git a/drivers/usb/common/roles.c b/drivers/usb/common/roles.c new file mode 100644 index 000000000000..15cc76e22123 --- /dev/null +++ b/drivers/usb/common/roles.c @@ -0,0 +1,305 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * USB Role Switch Support + * + * Copyright (C) 2018 Intel Corporation + * Author: Heikki Krogerus + * Hans de Goede + */ + +#include +#include +#include +#include +#include + +static struct class *role_class; + +struct usb_role_switch { + struct device dev; + struct mutex lock; /* device lock*/ + enum usb_role role; + + /* From descriptor */ + struct device *usb2_port; + struct device *usb3_port; + struct device *udc; + usb_role_switch_set_t set; + usb_role_switch_get_t get; + bool allow_userspace_control; +}; + +#define to_role_switch(d) container_of(d, struct usb_role_switch, dev) + +/** + * usb_role_switch_set_role - Set USB role for a switch + * @sw: USB role switch + * @role: USB role to be switched to + * + * Set USB role @role for @sw. + */ +int usb_role_switch_set_role(struct usb_role_switch *sw, enum usb_role role) +{ + int ret; + + if (IS_ERR_OR_NULL(sw)) + return 0; + + mutex_lock(&sw->lock); + + ret = sw->set(sw->dev.parent, role); + if (!ret) + sw->role = role; + + mutex_unlock(&sw->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(usb_role_switch_set_role); + +/** + * usb_role_switch_get_role - Get the USB role for a switch + * @sw: USB role switch + * + * Depending on the role-switch-driver this function returns either a cached + * value of the last set role, or reads back the actual value from the hardware. + */ +enum usb_role usb_role_switch_get_role(struct usb_role_switch *sw) +{ + enum usb_role role; + + if (IS_ERR_OR_NULL(sw)) + return USB_ROLE_NONE; + + mutex_lock(&sw->lock); + + if (sw->get) + role = sw->get(sw->dev.parent); + else + role = sw->role; + + mutex_unlock(&sw->lock); + + return role; +} +EXPORT_SYMBOL_GPL(usb_role_switch_get_role); + +static int __switch_match(struct device *dev, const void *name) +{ + return !strcmp((const char *)name, dev_name(dev)); +} + +static void *usb_role_switch_match(struct device_connection *con, int ep, + void *data) +{ + struct device *dev; + + dev = class_find_device(role_class, NULL, con->endpoint[ep], + __switch_match); + + return dev ? to_role_switch(dev) : ERR_PTR(-EPROBE_DEFER); +} + +/** + * usb_role_switch_get - Find USB role switch linked with the caller + * @dev: The caller device + * + * Finds and returns role switch linked with @dev. The reference count for the + * found switch is incremented. + */ +struct usb_role_switch *usb_role_switch_get(struct device *dev) +{ + return device_connection_find_match(dev, "usb-role-switch", NULL, + usb_role_switch_match); +} +EXPORT_SYMBOL_GPL(usb_role_switch_get); + +/** + * usb_role_switch_put - Release handle to a switch + * @sw: USB Role Switch + * + * Decrement reference count for @sw. + */ +void usb_role_switch_put(struct usb_role_switch *sw) +{ + if (!IS_ERR_OR_NULL(sw)) + put_device(&sw->dev); +} +EXPORT_SYMBOL_GPL(usb_role_switch_put); + +static umode_t +usb_role_switch_is_visible(struct kobject *kobj, struct attribute *attr, int n) +{ + struct device *dev = container_of(kobj, typeof(*dev), kobj); + struct usb_role_switch *sw = to_role_switch(dev); + + if (sw->allow_userspace_control) + return attr->mode; + + return 0; +} + +static const char * const usb_roles[] = { + [USB_ROLE_NONE] = "none", + [USB_ROLE_HOST] = "host", + [USB_ROLE_DEVICE] = "device", +}; + +static ssize_t +role_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct usb_role_switch *sw = to_role_switch(dev); + enum usb_role role = usb_role_switch_get_role(sw); + + return sprintf(buf, "%s\n", usb_roles[role]); +} + +static ssize_t role_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct usb_role_switch *sw = to_role_switch(dev); + int ret; + + ret = sysfs_match_string(usb_roles, buf); + if (ret < 0) { + bool res; + + /* Extra check if the user wants to disable the switch */ + ret = kstrtobool(buf, &res); + if (ret || res) + return -EINVAL; + } + + ret = usb_role_switch_set_role(sw, ret); + if (ret) + return ret; + + return size; +} +static DEVICE_ATTR_RW(role); + +static struct attribute *usb_role_switch_attrs[] = { + &dev_attr_role.attr, + NULL, +}; + +static const struct attribute_group usb_role_switch_group = { + .is_visible = usb_role_switch_is_visible, + .attrs = usb_role_switch_attrs, +}; + +static const struct attribute_group *usb_role_switch_groups[] = { + &usb_role_switch_group, + NULL, +}; + +static int +usb_role_switch_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + int ret; + + ret = add_uevent_var(env, "USB_ROLE_SWITCH=%s", dev_name(dev)); + if (ret) + dev_err(dev, "failed to add uevent USB_ROLE_SWITCH\n"); + + return ret; +} + +static void usb_role_switch_release(struct device *dev) +{ + struct usb_role_switch *sw = to_role_switch(dev); + + kfree(sw); +} + +static const struct device_type usb_role_dev_type = { + .name = "usb_role_switch", + .groups = usb_role_switch_groups, + .uevent = usb_role_switch_uevent, + .release = usb_role_switch_release, +}; + +/** + * usb_role_switch_register - Register USB Role Switch + * @parent: Parent device for the switch + * @desc: Description of the switch + * + * USB Role Switch is a device capable or choosing the role for USB connector. + * On platforms where the USB controller is dual-role capable, the controller + * driver will need to register the switch. On platforms where the USB host and + * USB device controllers behind the connector are separate, there will be a + * mux, and the driver for that mux will need to register the switch. + * + * Returns handle to a new role switch or ERR_PTR. The content of @desc is + * copied. + */ +struct usb_role_switch * +usb_role_switch_register(struct device *parent, + const struct usb_role_switch_desc *desc) +{ + struct usb_role_switch *sw; + int ret; + + if (!desc || !desc->set) + return ERR_PTR(-EINVAL); + + sw = kzalloc(sizeof(*sw), GFP_KERNEL); + if (!sw) + return ERR_PTR(-ENOMEM); + + mutex_init(&sw->lock); + + sw->allow_userspace_control = desc->allow_userspace_control; + sw->usb2_port = desc->usb2_port; + sw->usb3_port = desc->usb3_port; + sw->udc = desc->udc; + sw->set = desc->set; + sw->get = desc->get; + + sw->dev.parent = parent; + sw->dev.class = role_class; + sw->dev.type = &usb_role_dev_type; + dev_set_name(&sw->dev, "%s-role-switch", dev_name(parent)); + + ret = device_register(&sw->dev); + if (ret) { + put_device(&sw->dev); + return ERR_PTR(ret); + } + + /* TODO: Symlinks for the host port and the device controller. */ + + return sw; +} +EXPORT_SYMBOL_GPL(usb_role_switch_register); + +/** + * usb_role_switch_unregister - Unregsiter USB Role Switch + * @sw: USB Role Switch + * + * Unregister switch that was registered with usb_role_switch_register(). + */ +void usb_role_switch_unregister(struct usb_role_switch *sw) +{ + if (!IS_ERR_OR_NULL(sw)) + device_unregister(&sw->dev); +} +EXPORT_SYMBOL_GPL(usb_role_switch_unregister); + +static int __init usb_roles_init(void) +{ + role_class = class_create(THIS_MODULE, "usb_role"); + return PTR_ERR_OR_ZERO(role_class); +} +subsys_initcall(usb_roles_init); + +static void __exit usb_roles_exit(void) +{ + class_destroy(role_class); +} +module_exit(usb_roles_exit); + +MODULE_AUTHOR("Heikki Krogerus "); +MODULE_AUTHOR("Hans de Goede "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("USB Role Class"); diff --git a/include/linux/usb/role.h b/include/linux/usb/role.h new file mode 100644 index 000000000000..edc51be4a77c --- /dev/null +++ b/include/linux/usb/role.h @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: GPL-2.0 + +#ifndef __LINUX_USB_ROLE_H +#define __LINUX_USB_ROLE_H + +#include + +struct usb_role_switch; + +enum usb_role { + USB_ROLE_NONE, + USB_ROLE_HOST, + USB_ROLE_DEVICE, +}; + +typedef int (*usb_role_switch_set_t)(struct device *dev, enum usb_role role); +typedef enum usb_role (*usb_role_switch_get_t)(struct device *dev); + +/** + * struct usb_role_switch_desc - USB Role Switch Descriptor + * @usb2_port: Optional reference to the host controller port device (USB2) + * @usb3_port: Optional reference to the host controller port device (USB3) + * @udc: Optional reference to the peripheral controller device + * @set: Callback for setting the role + * @get: Callback for getting the role (optional) + * @allow_userspace_control: If true userspace may change the role through sysfs + * + * @usb2_port and @usb3_port will point to the USB host port and @udc to the USB + * device controller behind the USB connector with the role switch. If + * @usb2_port, @usb3_port and @udc are included in the description, the + * reference count for them should be incremented by the caller of + * usb_role_switch_register() before registering the switch. + */ +struct usb_role_switch_desc { + struct device *usb2_port; + struct device *usb3_port; + struct device *udc; + usb_role_switch_set_t set; + usb_role_switch_get_t get; + bool allow_userspace_control; +}; + +int usb_role_switch_set_role(struct usb_role_switch *sw, enum usb_role role); +enum usb_role usb_role_switch_get_role(struct usb_role_switch *sw); +struct usb_role_switch *usb_role_switch_get(struct device *dev); +void usb_role_switch_put(struct usb_role_switch *sw); + +struct usb_role_switch * +usb_role_switch_register(struct device *parent, + const struct usb_role_switch_desc *desc); +void usb_role_switch_unregister(struct usb_role_switch *sw); + +#endif /* __LINUX_USB_ROLE_H */ -- cgit v1.2.3 From 4d8d5a392ae110d9b5889afd2b4beef9a09e712d Mon Sep 17 00:00:00 2001 From: Kai-Heng Feng Date: Sat, 24 Mar 2018 03:26:36 +0800 Subject: usb: core: Add USB_QUIRK_DELAY_CTRL_MSG to usbcore quirks There's a new quirk, USB_QUIRK_DELAY_CTRL_MSG. Add it to usbcore quirks for completeness. Signed-off-by: Kai-Heng Feng Signed-off-by: Greg Kroah-Hartman --- Documentation/admin-guide/kernel-parameters.txt | 4 +++- drivers/usb/core/quirks.c | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) (limited to 'Documentation') diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index e00cdd313dc2..372d48326b1d 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -4421,7 +4421,9 @@ calculation); m = USB_QUIRK_DISCONNECT_SUSPEND (Device needs to be disconnected before suspend to - prevent spurious wakeup) + prevent spurious wakeup); + n = USB_QUIRK_DELAY_CTRL_MSG (Device needs a + pause after every control message); Example: quirks=0781:5580:bk,0a5c:5834:gij usbhid.mousepoll= diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c index 29e5f32b38df..920f48a49a87 100644 --- a/drivers/usb/core/quirks.c +++ b/drivers/usb/core/quirks.c @@ -124,6 +124,9 @@ static int quirks_param_set(const char *val, const struct kernel_param *kp) case 'm': flags |= USB_QUIRK_DISCONNECT_SUSPEND; break; + case 'n': + flags |= USB_QUIRK_DELAY_CTRL_MSG; + break; /* Ignore unrecognized flag characters */ } } -- cgit v1.2.3