--- /dev/null
+What: /sys/bus/i2c/drivers/ucsi_ccg/.../do_flash
+Date: May 2019
+Contact: Ajay Gupta <ajayg@nvidia.com>
+Description:
+ Tell the driver for Cypress CCGx Type-C controller to attempt
+ firmware upgrade by writing [Yy1] to the file.
--- /dev/null
+What: Raise a uevent when a USB Host Controller has died
+Date: 2019-04-17
+KernelVersion: 5.2
+Contact: linux-usb@vger.kernel.org
+Description: When the USB Host Controller has entered a state where it is no
+ longer functional a uevent will be raised. The uevent will
+ contain ACTION=offline and ERROR=DEAD.
+
+ Here is an example taken using udevadm monitor -p:
+
+ KERNEL[130.428945] offline /devices/pci0000:00/0000:00:10.0/usb2 (usb)
+ ACTION=offline
+ BUSNUM=002
+ DEVNAME=/dev/bus/usb/002/001
+ DEVNUM=001
+ DEVPATH=/devices/pci0000:00/0000:00:10.0/usb2
+ DEVTYPE=usb_device
+ DRIVER=usb
+ ERROR=DEAD
+ MAJOR=189
+ MINOR=128
+ PRODUCT=1d6b/2/414
+ SEQNUM=2168
+ SUBSYSTEM=usb
+ TYPE=9/0/1
+
+Users: chromium-os-dev@chromium.org
--- /dev/null
+Broadcom Stingray USB PHY
+
+Required properties:
+ - compatible : should be one of the listed compatibles
+ - "brcm,sr-usb-combo-phy" is combo PHY has two PHYs, one SS and one HS.
+ - "brcm,sr-usb-hs-phy" is a single HS PHY.
+ - reg: offset and length of the PHY blocks registers
+ - #phy-cells:
+ - Must be 1 for brcm,sr-usb-combo-phy as it expects one argument to indicate
+ the PHY number of two PHYs. 0 for HS PHY and 1 for SS PHY.
+ - Must be 0 for brcm,sr-usb-hs-phy.
+
+Refer to phy/phy-bindings.txt for the generic PHY binding properties
+
+Example:
+ usbphy0: usb-phy@0 {
+ compatible = "brcm,sr-usb-combo-phy";
+ reg = <0x00000000 0x100>;
+ #phy-cells = <1>;
+ };
+
+ usbphy1: usb-phy@10000 {
+ compatible = "brcm,sr-usb-combo-phy";
+ reg = <0x00010000 0x100>,
+ #phy-cells = <1>;
+ };
+
+ usbphy2: usb-phy@20000 {
+ compatible = "brcm,sr-usb-hs-phy";
+ reg = <0x00020000 0x100>,
+ #phy-cells = <0>;
+ };
- clocks: phandles to the clocks for each clock listed in clock-names
- clock-names: must contain "phy"
+Optional properties:
+- vbus-supply: A phandle to the regulator for USB VBUS.
+
Example:
usb3_phy0: phy@381f0040 {
compatible = "fsl,imx8mq-usb-phy";
--- /dev/null
+* Amlogic G12A USB2 PHY binding
+
+Required properties:
+- compatible: Should be "amlogic,meson-g12a-usb2-phy"
+- reg: The base address and length of the registers
+- #phys-cells: must be 0 (see phy-bindings.txt in this directory)
+- clocks: a phandle to the clock of this PHY
+- clock-names: must be "xtal"
+- resets: a phandle to the reset line of this PHY
+- reset-names: must be "phy"
+- phy-supply: see phy-bindings.txt in this directory
+
+Example:
+ usb2_phy0: phy@36000 {
+ compatible = "amlogic,g12a-usb2-phy";
+ reg = <0x0 0x36000 0x0 0x2000>;
+ clocks = <&xtal>;
+ clock-names = "xtal";
+ resets = <&reset RESET_USB_PHY21>;
+ reset-names = "phy";
+ #phy-cells = <0>;
+ };
--- /dev/null
+* Amlogic G12A USB3 + PCIE Combo PHY binding
+
+Required properties:
+- compatible: Should be "amlogic,meson-g12a-usb3-pcie-phy"
+- #phys-cells: must be 1. The cell number is used to select the phy mode
+ as defined in <dt-bindings/phy/phy.h> between PHY_TYPE_USB3 and PHY_TYPE_PCIE
+- reg: The base address and length of the registers
+- clocks: a phandle to the 100MHz reference clock of this PHY
+- clock-names: must be "ref_clk"
+- resets: phandle to the reset lines for the PHY control
+- reset-names: must be "phy"
+
+Example:
+ usb3_pcie_phy: phy@46000 {
+ compatible = "amlogic,g12a-usb3-pcie-phy";
+ reg = <0x0 0x46000 0x0 0x2000>;
+ clocks = <&clkc CLKID_PCIE_PLL>;
+ clock-names = "ref_clk";
+ resets = <&reset RESET_PCIE_PHY>;
+ reset-names = "phy";
+ #phy-cells = <1>;
+ };
- Tegra124: "nvidia,tegra124-xusb-padctl"
- Tegra132: "nvidia,tegra132-xusb-padctl", "nvidia,tegra124-xusb-padctl"
- Tegra210: "nvidia,tegra210-xusb-padctl"
+ - Tegra186: "nvidia,tegra186-xusb-padctl"
- reg: Physical base address and length of the controller's registers.
- resets: Must contain an entry for each entry in reset-names.
- reset-names: Must include the following entries:
- "padctl"
+For Tegra186:
+- avdd-pll-erefeut-supply: UPHY brick and reference clock as well as UTMI PHY
+ power supply. Must supply 1.8 V.
+- avdd-usb-supply: USB I/Os, VBUS, ID, REXT, D+/D- power supply. Must supply
+ 3.3 V.
+- vclamp-usb-supply: Bias rail for USB pad. Must supply 1.8 V.
+- vddio-hsic-supply: HSIC PHY power supply. Must supply 1.2 V.
+
Pad nodes:
==========
--- /dev/null
+Hisilicon hi3660 USB PHY
+-----------------------
+
+Required properties:
+- compatible: should be "hisilicon,hi3660-usb-phy"
+- #phy-cells: must be 0
+- hisilicon,pericrg-syscon: phandle of syscon used to control phy.
+- hisilicon,pctrl-syscon: phandle of syscon used to control phy.
+- hisilicon,eye-diagram-param: parameter set for phy
+Refer to phy/phy-bindings.txt for the generic PHY binding properties
+
+This is a subnode of usb3_otg_bc register node.
+
+Example:
+ usb3_otg_bc: usb3_otg_bc@ff200000 {
+ compatible = "syscon", "simple-mfd";
+ reg = <0x0 0xff200000 0x0 0x1000>;
+
+ usb-phy {
+ compatible = "hisilicon,hi3660-usb-phy";
+ #phy-cells = <0>;
+ hisilicon,pericrg-syscon = <&crg_ctrl>;
+ hisilicon,pctrl-syscon = <&pctrl>;
+ hisilicon,eye-diagram-param = <0x22466e4>;
+ };
+ };
--- /dev/null
+MediaTek Universal Flash Storage (UFS) M-PHY binding
+--------------------------------------------------------
+
+UFS M-PHY nodes are defined to describe on-chip UFS M-PHY hardware macro.
+Each UFS M-PHY node should have its own node.
+
+To bind UFS M-PHY with UFS host controller, the controller node should
+contain a phandle reference to UFS M-PHY node.
+
+Required properties for UFS M-PHY nodes:
+- compatible : Compatible list, contains the following controller:
+ "mediatek,mt8183-ufsphy" for ufs phy
+ persent on MT81xx chipsets.
+- reg : Address and length of the UFS M-PHY register set.
+- #phy-cells : This property shall be set to 0.
+- clocks : List of phandle and clock specifier pairs.
+- clock-names : List of clock input name strings sorted in the same
+ order as the clocks property. Following clocks are
+ mandatory.
+ "unipro": Unipro core control clock.
+ "mp": M-PHY core control clock.
+
+Example:
+
+ ufsphy: phy@11fa0000 {
+ compatible = "mediatek,mt8183-ufsphy";
+ reg = <0 0x11fa0000 0 0xc000>;
+ #phy-cells = <0>;
+
+ clocks = <&infracfg_ao INFRACFG_AO_UNIPRO_SCK_CG>,
+ <&infracfg_ao INFRACFG_AO_UFS_MP_SAP_BCLK_CG>;
+ clock-names = "unipro", "mp";
+ };
+
+ ufshci@11270000 {
+ ...
+ phys = <&ufsphy>;
+ };
"qcom,msm8996-qmp-usb3-phy" for 14nm USB3 phy on msm8996,
"qcom,msm8998-qmp-usb3-phy" for USB3 QMP V3 phy on msm8998,
"qcom,msm8998-qmp-ufs-phy" for UFS QMP phy on msm8998,
+ "qcom,msm8998-qmp-pcie-phy" for PCIe QMP phy on msm8998,
"qcom,sdm845-qmp-usb3-phy" for USB3 QMP V3 phy on sdm845,
"qcom,sdm845-qmp-usb3-uni-phy" for USB3 QMP V3 UNI phy on sdm845,
"qcom,sdm845-qmp-ufs-phy" for UFS QMP phy on sdm845.
"aux", "cfg_ahb", "ref".
For "qcom,msm8998-qmp-ufs-phy" must contain:
"ref", "ref_aux".
+ For "qcom,msm8998-qmp-pcie-phy" must contain:
+ "aux", "cfg_ahb", "ref".
For "qcom,sdm845-qmp-usb3-phy" must contain:
"aux", "cfg_ahb", "ref", "com_aux".
For "qcom,sdm845-qmp-usb3-uni-phy" must contain:
one for each entry in reset-names.
- reset-names: "phy" for reset of phy block,
"common" for phy common block reset,
- "cfg" for phy's ahb cfg block reset.
+ "cfg" for phy's ahb cfg block reset,
+ "ufsphy" for the PHY reset in the UFS controller.
For "qcom,ipq8074-qmp-pcie-phy" must contain:
"phy", "common".
"phy", "common".
For "qcom,msm8998-qmp-usb3-phy" must contain
"phy", "common".
- For "qcom,msm8998-qmp-ufs-phy": no resets are listed.
+ For "qcom,msm8998-qmp-ufs-phy": must contain:
+ "ufsphy".
+ For "qcom,msm8998-qmp-pcie-phy" must contain:
+ "phy", "common".
For "qcom,sdm845-qmp-usb3-phy" must contain:
"phy", "common".
For "qcom,sdm845-qmp-usb3-uni-phy" must contain:
"phy", "common".
- For "qcom,sdm845-qmp-ufs-phy": no resets are listed.
+ For "qcom,sdm845-qmp-ufs-phy": must contain:
+ "ufsphy".
- vdda-phy-supply: Phandle to a regulator supply to PHY core block.
- vdda-pll-supply: Phandle to 1.8V regulator supply to PHY refclk pll block.
- compatible: "renesas,usb-phy-r8a7743" if the device is a part of R8A7743 SoC.
"renesas,usb-phy-r8a7744" if the device is a part of R8A7744 SoC.
"renesas,usb-phy-r8a7745" if the device is a part of R8A7745 SoC.
+ "renesas,usb-phy-r8a77470" if the device is a part of R8A77470 SoC.
"renesas,usb-phy-r8a7790" if the device is a part of R8A7790 SoC.
"renesas,usb-phy-r8a7791" if the device is a part of R8A7791 SoC.
"renesas,usb-phy-r8a7794" if the device is a part of R8A7794 SoC.
- #phy-cells: see phy-bindings.txt in the same directory, must be <1>.
The phandle's argument in the PHY specifier is the USB controller selector for
-the USB channel; see the selector meanings below:
+the USB channel other than r8a77470 SoC; see the selector meanings below:
+-----------+---------------+---------------+
|\ Selector | | |
| 2 | PCI EHCI/OHCI | xHCI |
+-----------+---------------+---------------+
+For r8a77470 SoC;see the selector meaning below:
+
++-----------+---------------+---------------+
+|\ Selector | | |
++ --------- + 0 | 1 |
+| Channel \| | |
++-----------+---------------+---------------+
+| 0 | EHCI/OHCI | HS-USB |
++-----------+---------------+---------------+
+
Example (Lager board):
usb-phy@e6590100 {
reg = <0 0xe6590100 0 0x100>;
#address-cells = <1>;
#size-cells = <0>;
- clocks = <&mstp7_clks R8A7790_CLK_HSUSB>;
+ clocks = <&cpg CPG_MOD 704>;
clock-names = "usbhs";
+ power-domains = <&sysc R8A7790_PD_ALWAYS_ON>;
+ resets = <&cpg 704>;
- usb-channel@0 {
+ usb0: usb-channel@0 {
reg = <0>;
#phy-cells = <1>;
};
- usb-channel@2 {
+ usb2: usb-channel@2 {
reg = <2>;
#phy-cells = <1>;
};
};
+
+Example (iWave RZ/G1C sbc):
+
+ usbphy0: usb-phy0@e6590100 {
+ compatible = "renesas,usb-phy-r8a77470",
+ "renesas,rcar-gen2-usb-phy";
+ reg = <0 0xe6590100 0 0x100>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ clocks = <&cpg CPG_MOD 704>;
+ clock-names = "usbhs";
+ power-domains = <&sysc R8A77470_PD_ALWAYS_ON>;
+ resets = <&cpg 704>;
+
+ usb0: usb-channel@0 {
+ reg = <0>;
+ #phy-cells = <1>;
+ };
+ };
+
+ usbphy1: usb-phy@e6598100 {
+ compatible = "renesas,usb-phy-r8a77470",
+ "renesas,rcar-gen2-usb-phy";
+ reg = <0 0xe6598100 0 0x100>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ clocks = <&cpg CPG_MOD 706>;
+ clock-names = "usbhs";
+ power-domains = <&sysc R8A77470_PD_ALWAYS_ON>;
+ resets = <&cpg 706>;
+
+ usb1: usb-channel@0 {
+ reg = <0>;
+ #phy-cells = <1>;
+ };
+ };
* Renesas R-Car generation 3 USB 2.0 PHY
This file provides information on what the device node for the R-Car generation
-3 and RZ/G2 USB 2.0 PHY contain.
+3, RZ/G1C and RZ/G2 USB 2.0 PHY contain.
Required properties:
-- compatible: "renesas,usb2-phy-r8a774a1" if the device is a part of an R8A774A1
+- compatible: "renesas,usb2-phy-r8a77470" if the device is a part of an R8A77470
+ SoC.
+ "renesas,usb2-phy-r8a774a1" if the device is a part of an R8A774A1
SoC.
"renesas,usb2-phy-r8a774c0" if the device is a part of an R8A774C0
SoC.
- reg: offset and length of the partial USB 2.0 Host register block.
- clocks: clock phandle and specifier pair(s).
-- #phy-cells: see phy-bindings.txt in the same directory, must be <0>.
+- #phy-cells: see phy-bindings.txt in the same directory, must be <1> (and
+ using <0> is deprecated).
+
+The phandle's argument in the PHY specifier is the INT_STATUS bit of controller:
+- 1 = USBH_INTA (OHCI)
+- 2 = USBH_INTB (EHCI)
+- 3 = UCOM_INT (OTG and BC)
Optional properties:
To use a USB channel where USB 2.0 Host and HSUSB (USB 2.0 Peripheral) are
- reg: PHY register address offset and length in "general
register files"
-Optional clocks using the clock bindings (see ../clock/clock-bindings.txt),
-specified by name:
+Optional properties:
- clock-names: Should contain "emmcclk". Although this is listed as optional
(because most boards can get basic functionality without having
access to it), it is strongly suggested.
+ See ../clock/clock-bindings.txt for details.
- clocks: Should have a phandle to the card clock exported by the SDHCI driver.
+ - drive-impedance-ohm: Specifies the drive impedance in Ohm.
+ Possible values are 33, 40, 50, 66 and 100.
+ If not set, the default value of 50 will be applied.
Example:
reg = <0xf780 0x20>;
clocks = <&sdhci>;
clock-names = "emmcclk";
+ drive-impedance-ohm = <50>;
#phy-cells = <0>;
};
};
--- /dev/null
+TI AM654 SERDES
+
+Required properties:
+ - compatible: Should be "ti,phy-am654-serdes"
+ - reg : Address and length of the register set for the device.
+ - #phy-cells: determine the number of cells that should be given in the
+ phandle while referencing this phy. Should be "2". The 1st cell
+ corresponds to the phy type (should be one of the types specified in
+ include/dt-bindings/phy/phy.h) and the 2nd cell should be the serdes
+ lane function.
+ If SERDES0 is referenced 2nd cell should be:
+ 0 - USB3
+ 1 - PCIe0 Lane0
+ 2 - ICSS2 SGMII Lane0
+ If SERDES1 is referenced 2nd cell should be:
+ 0 - PCIe1 Lane0
+ 1 - PCIe0 Lane1
+ 2 - ICSS2 SGMII Lane1
+ - power-domains: As documented by the generic PM domain bindings in
+ Documentation/devicetree/bindings/power/power_domain.txt.
+ - clocks: List of clock-specifiers representing the input to the SERDES.
+ Should have 3 items representing the left input clock, external
+ reference clock and right input clock in that order.
+ - clock-output-names: List of clock names for each of the clock outputs of
+ SERDES. Should have 3 items for CMU reference clock,
+ left output clock and right output clock in that order.
+ - assigned-clocks: As defined in
+ Documentation/devicetree/bindings/clock/clock-bindings.txt
+ - assigned-clock-parents: As defined in
+ Documentation/devicetree/bindings/clock/clock-bindings.txt
+ - #clock-cells: Should be <1> to choose between the 3 output clocks.
+ Defined in Documentation/devicetree/bindings/clock/clock-bindings.txt
+
+ The following macros are defined in dt-bindings/phy/phy-am654-serdes.h
+ for selecting the correct reference clock. This can be used while
+ specifying the clocks created by SERDES.
+ => AM654_SERDES_CMU_REFCLK
+ => AM654_SERDES_LO_REFCLK
+ => AM654_SERDES_RO_REFCLK
+
+ - mux-controls: Phandle to the multiplexer that is used to select the lane
+ function. See #phy-cells above to see the multiplex values.
+
+Example:
+
+Example for SERDES0 is given below. It has 3 clock inputs;
+left input reference clock as indicated by <&k3_clks 153 4>, external
+reference clock as indicated by <&k3_clks 153 1> and right input
+reference clock as indicated by <&serdes1 AM654_SERDES_LO_REFCLK>. (The
+right input of SERDES0 is connected to the left output of SERDES1).
+
+SERDES0 registers 3 clock outputs as indicated in clock-output-names. The
+first refers to the CMU reference clock, second refers to the left output
+reference clock and the third refers to the right output reference clock.
+
+The assigned-clocks and assigned-clock-parents is used here to set the
+parent of left input reference clock to MAINHSDIV_CLKOUT4 and parent of
+CMU reference clock to left input reference clock.
+
+serdes0: serdes@900000 {
+ compatible = "ti,phy-am654-serdes";
+ reg = <0x0 0x900000 0x0 0x2000>;
+ reg-names = "serdes";
+ #phy-cells = <2>;
+ power-domains = <&k3_pds 153>;
+ clocks = <&k3_clks 153 4>, <&k3_clks 153 1>,
+ <&serdes1 AM654_SERDES_LO_REFCLK>;
+ clock-output-names = "serdes0_cmu_refclk", "serdes0_lo_refclk",
+ "serdes0_ro_refclk";
+ assigned-clocks = <&k3_clks 153 4>, <&serdes0 AM654_SERDES_CMU_REFCLK>;
+ assigned-clock-parents = <&k3_clks 153 8>, <&k3_clks 153 4>;
+ ti,serdes-clk = <&serdes0_clk>;
+ mux-controls = <&serdes_mux 0>;
+ #clock-cells = <1>;
+};
+
+Example for PCIe consumer node using the SERDES PHY specifier is given below.
+&pcie0_rc {
+ num-lanes = <2>;
+ phys = <&serdes0 PHY_TYPE_PCIE 1>, <&serdes1 PHY_TYPE_PCIE 1>;
+ phy-names = "pcie-phy0", "pcie-phy1";
+};
- vdda-pll-max-microamp : specifies max. load that can be drawn from pll supply
- vddp-ref-clk-supply : phandle to UFS device ref_clk pad power supply
- vddp-ref-clk-max-microamp : specifies max. load that can be drawn from this supply
+- resets : specifies the PHY reset in the UFS controller
Example:
<&clock_gcc clk_ufs_phy_ldo>,
<&clock_gcc clk_gcc_ufs_tx_cfg_clk>,
<&clock_gcc clk_gcc_ufs_rx_cfg_clk>;
+ resets = <&ufshc 0>;
};
- ufshc@fc598000 {
+ ufshc: ufshc@fc598000 {
+ #reset-cells = <1>;
...
phys = <&ufsphy1>;
phy-names = "ufsphy";
-lanes-per-direction : number of lanes available per direction - either 1 or 2.
Note that it is assume same number of lanes is used both
directions at once. If not specified, default is 2 lanes per direction.
+- #reset-cells : Must be <1> for Qualcomm UFS controllers that expose
+ PHY reset from the UFS controller.
- resets : reset node register
- reset-names : describe reset node register, the "rst" corresponds to reset the whole UFS IP.
reset-names = "rst";
phys = <&ufsphy1>;
phy-names = "ufsphy";
+ #reset-cells = <1>;
};
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/usb/generic-ehci.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: USB EHCI Controller Device Tree Bindings
+
+allOf:
+ - $ref: "usb-hcd.yaml"
+
+maintainers:
+ - Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+
+properties:
+ compatible:
+ contains:
+ const: generic-ehci
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ resets:
+ minItems: 1
+ maxItems: 4
+
+ clocks:
+ minItems: 1
+ maxItems: 4
+ description: |
+ In case the Renesas R-Car Gen3 SoCs:
+ - if a host only channel: first clock should be host.
+ - if a USB DRD channel: first clock should be host and second
+ one should be peripheral
+
+ big-endian:
+ $ref: /schemas/types.yaml#/definitions/flag
+ description:
+ Set this flag for HCDs with big endian descriptors and big
+ endian registers.
+
+ big-endian-desc:
+ $ref: /schemas/types.yaml#/definitions/flag
+ description:
+ Set this flag for HCDs with big endian descriptors.
+
+ big-endian-regs:
+ $ref: /schemas/types.yaml#/definitions/flag
+ description:
+ Set this flag for HCDs with big endian registers.
+
+ has-transaction-translator:
+ $ref: /schemas/types.yaml#/definitions/flag
+ description:
+ Set this flag if EHCI has a Transaction Translator built into
+ the root hub.
+
+ needs-reset-on-resume:
+ $ref: /schemas/types.yaml#/definitions/flag
+ description:
+ Set this flag to force EHCI reset after resume.
+
+ phys: true
+
+required:
+ - compatible
+ - reg
+ - interrupts
+
+additionalProperties: false
+
+examples:
+ - |
+ ehci@e0000300 {
+ compatible = "ibm,usb-ehci-440epx", "generic-ehci";
+ interrupt-parent = <&UIC0>;
+ interrupts = <0x1a 4>;
+ reg = <0 0xe0000300 90 0 0xe0000390 70>;
+ big-endian;
+ };
+
+ - |
+ ehci0: usb@1c14000 {
+ compatible = "allwinner,sun4i-a10-ehci", "generic-ehci";
+ reg = <0x01c14000 0x100>;
+ interrupts = <39>;
+ clocks = <&ahb_gates 1>;
+ phys = <&usbphy 1>;
+ phy-names = "usb";
+ };
+
+...
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/usb/generic-ohci.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: USB OHCI Controller Device Tree Bindings
+
+allOf:
+ - $ref: "usb-hcd.yaml"
+
+maintainers:
+ - Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+
+properties:
+ compatible:
+ contains:
+ const: generic-ohci
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ resets:
+ minItems: 1
+ maxItems: 2
+
+ clocks:
+ minItems: 1
+ maxItems: 3
+ description: |
+ In case the Renesas R-Car Gen3 SoCs:
+ - if a host only channel: first clock should be host.
+ - if a USB DRD channel: first clock should be host and second
+ one should be peripheral
+
+ big-endian:
+ $ref: /schemas/types.yaml#/definitions/flag
+ description:
+ Set this flag for HCDs with big endian descriptors and big
+ endian registers.
+
+ big-endian-desc:
+ $ref: /schemas/types.yaml#/definitions/flag
+ description:
+ Set this flag for HCDs with big endian descriptors.
+
+ big-endian-regs:
+ $ref: /schemas/types.yaml#/definitions/flag
+ description:
+ Set this flag for HCDs with big endian registers.
+
+ remote-wakeup-connected:
+ $ref: /schemas/types.yaml#/definitions/flag
+ description:
+ Remote wakeup is wired on the platform.
+
+ no-big-frame-no:
+ $ref: /schemas/types.yaml#/definitions/flag
+ description:
+ Set if frame_no lives in bits [15:0] of HCCA
+
+ num-ports:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description:
+ Overrides the detected port count
+
+ phys: true
+
+required:
+ - compatible
+ - reg
+ - interrupts
+
+additionalProperties: false
+
+examples:
+ - |
+ ohci0: usb@1c14400 {
+ compatible = "allwinner,sun4i-a10-ohci", "generic-ohci";
+ reg = <0x01c14400 0x100>;
+ interrupts = <64>;
+ clocks = <&usb_clk 6>, <&ahb_gates 2>;
+ phys = <&usbphy 1>;
+ };
+
+...
- interrupt-names: must be "mc"
- clocks: phandle to the "udc" clock
- clock-names: must be "udc"
+- phys: phandle to the USB PHY
Example:
+usb_phy: usb-phy@0 {
+ compatible = "usb-nop-xceiv";
+ #phy-cells = <0>;
+};
+
udc: usb@13040000 {
compatible = "ingenic,jz4740-musb";
reg = <0x13040000 0x10000>;
clocks = <&cgu JZ4740_CLK_UDC>;
clock-names = "udc";
+
+ phys = <&usb_phy>;
};
- Tegra124: "nvidia,tegra124-xusb"
- Tegra132: "nvidia,tegra132-xusb", "nvidia,tegra124-xusb"
- Tegra210: "nvidia,tegra210-xusb"
+ - Tegra186: "nvidia,tegra186-xusb"
- reg: Must contain the base and length of the xHCI host registers, XUSB FPCI
registers and XUSB IPFS registers.
- reg-names: Must contain the following entries:
- avdd-pll-uerefe-supply: PLLE reference PLL power supply. Must supply 1.05 V.
- dvdd-pex-pll-supply: PCIe/USB3 PLL power supply. Must supply 1.05 V.
- hvdd-pex-pll-e-supply: High-voltage PLLE power supply. Must supply 1.8 V.
+
+For Tegra210 and Tegra186:
- power-domains: A list of PM domain specifiers that reference each power-domain
used by the xHCI controller. This list must comprise of a specifier for the
XUSBA and XUSBC power-domains. See ../power/power_domain.txt and
- Tegra132: usb2-0, usb2-1, usb2-2, hsic-0, hsic-1, usb3-0, usb3-1
- Tegra210: usb2-0, usb2-1, usb2-2, usb2-3, hsic-0, usb3-0, usb3-1, usb3-2,
usb3-3
+ - Tegra186: usb2-0, usb2-1, usb2-2, hsic-0, usb3-0, usb3-1, usb3-2
Example:
--------
- "renesas,usbhs-r8a7743" for r8a7743 (RZ/G1M) compatible device
- "renesas,usbhs-r8a7744" for r8a7744 (RZ/G1N) compatible device
- "renesas,usbhs-r8a7745" for r8a7745 (RZ/G1E) compatible device
+ - "renesas,usbhs-r8a77470" for r8a77470 (RZ/G1C) compatible device
- "renesas,usbhs-r8a774a1" for r8a774a1 (RZ/G2M) compatible device
- "renesas,usbhs-r8a774c0" for r8a774c0 (RZ/G2E) compatible device
- "renesas,usbhs-r8a7790" for r8a7790 (R-Car H2) compatible device
+++ /dev/null
-USB EHCI controllers
-
-Required properties:
- - compatible : should be "generic-ehci".
- - reg : should contain at least address and length of the standard EHCI
- register set for the device. Optional platform-dependent registers
- (debug-port or other) can be also specified here, but only after
- definition of standard EHCI registers.
- - interrupts : one EHCI interrupt should be described here.
-
-Optional properties:
- - big-endian-regs : boolean, set this for hcds with big-endian registers
- - big-endian-desc : boolean, set this for hcds with big-endian descriptors
- - big-endian : boolean, for hcds with big-endian-regs + big-endian-desc
- - needs-reset-on-resume : boolean, set this to force EHCI reset after resume
- - 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. In case of Renesas
- R-Car Gen3 SoCs:
- - if a host only channel: first clock should be host.
- - if a USB DRD channel: first clock should be host and second one
- should be peripheral.
- - 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";
- interrupt-parent = <&UIC0>;
- interrupts = <1a 4>;
- reg = <0 e0000300 90 0 e0000390 70>;
- big-endian;
- };
-
-Example (Allwinner sun4i A10 SoC):
- ehci0: usb@1c14000 {
- compatible = "allwinner,sun4i-a10-ehci", "generic-ehci";
- reg = <0x01c14000 0x100>;
- interrupts = <39>;
- clocks = <&ahb_gates 1>;
- phys = <&usbphy 1>;
- phy-names = "usb";
- };
+++ /dev/null
-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>;
- };
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/usb/usb-hcd.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Generic USB Host Controller Device Tree Bindings
+
+maintainers:
+ - Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+
+properties:
+ $nodename:
+ pattern: "^usb(@.*)?"
+
+ phys:
+ $ref: /schemas/types.yaml#/definitions/phandle-array
+ description:
+ List of all the USB PHYs on this HCD
+
+examples:
+ - |
+ usb {
+ phys = <&usb2_phy1>, <&usb3_phy1>;
+ };
+++ /dev/null
-USB OHCI controllers
-
-Required properties:
-- compatible : "generic-ohci"
-- reg : ohci controller register range (address and length)
-- interrupts : ohci controller interrupt
-
-Optional properties:
-- big-endian-regs : boolean, set this for hcds with big-endian registers
-- big-endian-desc : boolean, set this for hcds with big-endian descriptors
-- big-endian : boolean, for hcds with big-endian-regs + big-endian-desc
-- no-big-frame-no : boolean, set if frame_no lives in bits [15:0] of HCCA
-- 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. In case of Renesas
- R-Car Gen3 SoCs:
- - if a host only channel: first clock should be host.
- - if a USB DRD channel: first clock should be host and second one
- should be peripheral.
-- 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 {
- compatible = "allwinner,sun4i-a10-ohci", "generic-ohci";
- reg = <0x01c14400 0x100>;
- interrupts = <64>;
- clocks = <&usb_clk 6>, <&ahb_gates 2>;
- phys = <&usbphy 1>;
- phy-names = "usb";
- };
- "renesas,xhci-r8a7743" for r8a7743 SoC
- "renesas,xhci-r8a7744" for r8a7744 SoC
- "renesas,xhci-r8a774a1" for r8a774a1 SoC
+ - "renesas,xhci-r8a774c0" for r8a774c0 SoC
- "renesas,xhci-r8a7790" for r8a7790 SoC
- "renesas,xhci-r8a7791" for r8a7791 SoC
- "renesas,xhci-r8a7793" for r8a7793 SoC
- power-on-time-ms : Specifies the time it takes from the time the host
initiates the power-on sequence to a port until the port has adequate
power. The value is given in ms in a 0 - 510 range (default is 100ms).
- - swap-dx-lanes : Specifies the ports which will swap the differential-pair
- (D+/D-), default is not-swapped.
+ - swap-dx-lanes : Specifies the downstream ports which will swap the
+ differential-pair (D+/D-), default is not-swapped.
+ - swap-us-lanes : Selects the upstream port differential-pair (D+/D-)
+ swapping (boolean, default is not-swapped)
Examples:
usb2512b@2c {
-
+================================
Linux UWB + Wireless USB + WiNET
+================================
+
+ Copyright (C) 2005-2006 Intel Corporation
- (C) 2005-2006 Intel Corporation
Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
This program is free software; you can redistribute it and/or
Wireless USB 1.0 specification (including Wireless USB host controller
and an Intel WiNET controller).
+.. Contents
1. Introduction
1. HWA: Host Wire adapters, your Wireless USB dongle
4. Glossary
- Introduction
+Introduction
+============
UWB is a wide-band communication protocol that is to serve also as the
low-level protocol for others (much like TCP sits on IP). Currently
do the actual WUSB.
- HWA: Host Wire adapters, your Wireless USB dongle
+HWA: Host Wire adapters, your Wireless USB dongle
+-------------------------------------------------
WUSB also defines a device called a Host Wire Adaptor (HWA), which in
mere terms is a USB dongle that enables your PC to have UWB and Wireless
their type and kick into gear.
- DWA: Device Wired Adaptor, a Wireless USB hub for wired devices
+DWA: Device Wired Adaptor, a Wireless USB hub for wired devices
+---------------------------------------------------------------
These are the complement to HWAs. They are a USB host for connecting
wired devices, but it is connected to your PC connected via Wireless
has been done to support that in upcoming releases.
- WHCI: Wireless Host Controller Interface, the PCI WUSB host adapter
+WHCI: Wireless Host Controller Interface, the PCI WUSB host adapter
+-------------------------------------------------------------------
This is your usual PCI device that implements WHCI. Similar in concept
to EHCI, it allows your wireless USB devices (including DWAs) to connect
releases.
- The UWB stack
+The UWB stack
+=============
The main mission of the UWB stack is to keep a tally of which devices
are in radio proximity to allow drivers to connect to them. As well, it
now on), such as to start/stop beaconing, scan, allocate bandwidth, etc.
- Devices and hosts: the basic structure
+Devices and hosts: the basic structure
+--------------------------------------
The main building block here is the UWB device (struct uwb_dev). For
each device that pops up in radio presence (ie: the UWB host receives a
for the PCI connected WHCI controller.
- Host Controller life cycle
+Host Controller life cycle
+--------------------------
So let's say we connect a dongle to the system: it is detected and
firmware uploaded if needed [for Intel's i1480
takes time of tearing everything down safely (or not...).
- On the air: beacons and enumerating the radio neighborhood
+On the air: beacons and enumerating the radio neighborhood
+----------------------------------------------------------
So assuming we have devices and we have agreed for a channel to connect
on (let's say 9), we put the new RC to beacon:
the beacon cache of dead devices].
- Device lists
+Device lists
+------------
All UWB devices are kept in the list of the struct bus_type uwb_bus_type.
- Bandwidth allocation
+Bandwidth allocation
+--------------------
The UWB stack maintains a local copy of DRP availability through
processing of incoming *DRP Availability Change* notifications. This
subject to change.]
- Wireless USB Host Controller drivers
+Wireless USB Host Controller drivers
+====================================
*WARNING* This section needs a lot of work!
Now it all depends on external stimuli.
-*New device connection*
+New device connection
+---------------------
A new device pops up, it scans the radio looking for MMCs that give out
the existence of Wireless USB channels. Once one (or more) are found,
start enumerating and doing transfers through usb_hcd->urb_enqueue() to
read descriptors and move our data.
-*Device life cycle and keep alives*
+Device life cycle and keep alives
+---------------------------------
Every time there is a successful transfer to/from a device, we update a
per-device activity timestamp. If not, every now and then we check and
If the device wants to disconnect, it will either die (ugly) or send a
/DN_Disconnect/ that will prompt a disconnection from the system.
-*Sending and receiving data*
+Sending and receiving data
+--------------------------
Data is sent and received through /Remote Pipes/ (rpipes). An rpipe is
/aimed/ at an endpoint in a WUSB device. This is the same for HWAs and
For IN xfers, we only issue URBs for the segments we want to read and
then wait for the xfer result data.
-*URB mapping into xfers*
+URB mapping into xfers
+^^^^^^^^^^^^^^^^^^^^^^
This is done by hwahc_op_urb_[en|de]queue(). In enqueue() we aim an
rpipe to the endpoint where we have to transmit, create a transfer
called--this will call the URB callback.
- Glossary
+Glossary
+========
*DWA* -- Device Wire Adapter
Design-overview.txt-1.8 (last edited 2006-11-04 12:22:24 by
InakyPerezGonzalez)
-
- Linux ACM driver v0.16
- (c) 1999 Vojtech Pavlik <vojtech@suse.cz>
- Sponsored by SuSE
-----------------------------------------------------------------------------
+======================
+Linux ACM driver v0.16
+======================
+
+Copyright (c) 1999 Vojtech Pavlik <vojtech@suse.cz>
+
+Sponsored by SuSE
0. Disclaimer
~~~~~~~~~~~~~
- This program is free software; you can redistribute it and/or modify it
+This program is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the Free
Software Foundation; either version 2 of the License, or (at your option)
any later version.
- This program is distributed in the hope that it will be useful, but
+This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
more details.
- You should have received a copy of the GNU General Public License along
+You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc., 59
Temple Place, Suite 330, Boston, MA 02111-1307 USA
- Should you need to contact me, the author, you can do so either by e-mail
-- mail your message to <vojtech@suse.cz>, or by paper mail: Vojtech Pavlik,
+Should you need to contact me, the author, you can do so either by e-mail -
+mail your message to <vojtech@suse.cz>, or by paper mail: Vojtech Pavlik,
Ucitelska 1576, Prague 8, 182 00 Czech Republic
- For your convenience, the GNU General Public License version 2 is included
+For your convenience, the GNU General Public License version 2 is included
in the package: See the file COPYING.
1. Usage
~~~~~~~~
- The drivers/usb/class/cdc-acm.c drivers works with USB modems and USB ISDN terminal
+The drivers/usb/class/cdc-acm.c drivers works with USB modems and USB ISDN terminal
adapters that conform to the Universal Serial Bus Communication Device Class
Abstract Control Model (USB CDC ACM) specification.
- Many modems do, here is a list of those I know of:
+Many modems do, here is a list of those I know of:
- 3Com OfficeConnect 56k
- 3Com Voice FaxModem Pro
- 3Com Sportster
- MultiTech MultiModem 56k
- Zoom 2986L FaxModem
- Compaq 56k FaxModem
- ELSA Microlink 56k
+ - 3Com OfficeConnect 56k
+ - 3Com Voice FaxModem Pro
+ - 3Com Sportster
+ - MultiTech MultiModem 56k
+ - Zoom 2986L FaxModem
+ - Compaq 56k FaxModem
+ - ELSA Microlink 56k
- I know of one ISDN TA that does work with the acm driver:
+I know of one ISDN TA that does work with the acm driver:
- 3Com USR ISDN Pro TA
+ - 3Com USR ISDN Pro TA
- Some cell phones also connect via USB. I know the following phones work:
+Some cell phones also connect via USB. I know the following phones work:
- SonyEricsson K800i
+ - SonyEricsson K800i
- Unfortunately many modems and most ISDN TAs use proprietary interfaces and
+Unfortunately many modems and most ISDN TAs use proprietary interfaces and
thus won't work with this drivers. Check for ACM compliance before buying.
- To use the modems you need these modules loaded:
+To use the modems you need these modules loaded::
usbcore.ko
uhci-hcd.ko ohci-hcd.ko or ehci-hcd.ko
cdc-acm.ko
- After that, the modem[s] should be accessible. You should be able to use
+After that, the modem[s] should be accessible. You should be able to use
minicom, ppp and mgetty with them.
2. Verifying that it works
~~~~~~~~~~~~~~~~~~~~~~~~~~
- The first step would be to check /sys/kernel/debug/usb/devices, it should look
-like this:
-
-T: Bus=01 Lev=00 Prnt=00 Port=00 Cnt=00 Dev#= 1 Spd=12 MxCh= 2
-B: Alloc= 0/900 us ( 0%), #Int= 0, #Iso= 0
-D: Ver= 1.00 Cls=09(hub ) Sub=00 Prot=00 MxPS= 8 #Cfgs= 1
-P: Vendor=0000 ProdID=0000 Rev= 0.00
-S: Product=USB UHCI Root Hub
-S: SerialNumber=6800
-C:* #Ifs= 1 Cfg#= 1 Atr=40 MxPwr= 0mA
-I: If#= 0 Alt= 0 #EPs= 1 Cls=09(hub ) Sub=00 Prot=00 Driver=hub
-E: Ad=81(I) Atr=03(Int.) MxPS= 8 Ivl=255ms
-T: Bus=01 Lev=01 Prnt=01 Port=01 Cnt=01 Dev#= 2 Spd=12 MxCh= 0
-D: Ver= 1.00 Cls=02(comm.) Sub=00 Prot=00 MxPS= 8 #Cfgs= 2
-P: Vendor=04c1 ProdID=008f Rev= 2.07
-S: Manufacturer=3Com Inc.
-S: Product=3Com U.S. Robotics Pro ISDN TA
-S: SerialNumber=UFT53A49BVT7
-C: #Ifs= 1 Cfg#= 1 Atr=60 MxPwr= 0mA
-I: If#= 0 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=ff Driver=acm
-E: Ad=85(I) Atr=02(Bulk) MxPS= 64 Ivl= 0ms
-E: Ad=04(O) Atr=02(Bulk) MxPS= 64 Ivl= 0ms
-E: Ad=81(I) Atr=03(Int.) MxPS= 16 Ivl=128ms
-C:* #Ifs= 2 Cfg#= 2 Atr=60 MxPwr= 0mA
-I: If#= 0 Alt= 0 #EPs= 1 Cls=02(comm.) Sub=02 Prot=01 Driver=acm
-E: Ad=81(I) Atr=03(Int.) MxPS= 16 Ivl=128ms
-I: If#= 1 Alt= 0 #EPs= 2 Cls=0a(data ) Sub=00 Prot=00 Driver=acm
-E: Ad=85(I) Atr=02(Bulk) MxPS= 64 Ivl= 0ms
-E: Ad=04(O) Atr=02(Bulk) MxPS= 64 Ivl= 0ms
+
+The first step would be to check /sys/kernel/debug/usb/devices, it should look
+like this::
+
+ T: Bus=01 Lev=00 Prnt=00 Port=00 Cnt=00 Dev#= 1 Spd=12 MxCh= 2
+ B: Alloc= 0/900 us ( 0%), #Int= 0, #Iso= 0
+ D: Ver= 1.00 Cls=09(hub ) Sub=00 Prot=00 MxPS= 8 #Cfgs= 1
+ P: Vendor=0000 ProdID=0000 Rev= 0.00
+ S: Product=USB UHCI Root Hub
+ S: SerialNumber=6800
+ C:* #Ifs= 1 Cfg#= 1 Atr=40 MxPwr= 0mA
+ I: If#= 0 Alt= 0 #EPs= 1 Cls=09(hub ) Sub=00 Prot=00 Driver=hub
+ E: Ad=81(I) Atr=03(Int.) MxPS= 8 Ivl=255ms
+ T: Bus=01 Lev=01 Prnt=01 Port=01 Cnt=01 Dev#= 2 Spd=12 MxCh= 0
+ D: Ver= 1.00 Cls=02(comm.) Sub=00 Prot=00 MxPS= 8 #Cfgs= 2
+ P: Vendor=04c1 ProdID=008f Rev= 2.07
+ S: Manufacturer=3Com Inc.
+ S: Product=3Com U.S. Robotics Pro ISDN TA
+ S: SerialNumber=UFT53A49BVT7
+ C: #Ifs= 1 Cfg#= 1 Atr=60 MxPwr= 0mA
+ I: If#= 0 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=ff Driver=acm
+ E: Ad=85(I) Atr=02(Bulk) MxPS= 64 Ivl= 0ms
+ E: Ad=04(O) Atr=02(Bulk) MxPS= 64 Ivl= 0ms
+ E: Ad=81(I) Atr=03(Int.) MxPS= 16 Ivl=128ms
+ C:* #Ifs= 2 Cfg#= 2 Atr=60 MxPwr= 0mA
+ I: If#= 0 Alt= 0 #EPs= 1 Cls=02(comm.) Sub=02 Prot=01 Driver=acm
+ E: Ad=81(I) Atr=03(Int.) MxPS= 16 Ivl=128ms
+ I: If#= 1 Alt= 0 #EPs= 2 Cls=0a(data ) Sub=00 Prot=00 Driver=acm
+ E: Ad=85(I) Atr=02(Bulk) MxPS= 64 Ivl= 0ms
+ E: Ad=04(O) Atr=02(Bulk) MxPS= 64 Ivl= 0ms
The presence of these three lines (and the Cls= 'comm' and 'data' classes)
is important, it means it's an ACM device. The Driver=acm means the acm
driver is used for the device. If you see only Cls=ff(vend.) then you're out
-of luck, you have a device with vendor specific-interface.
-
-D: Ver= 1.00 Cls=02(comm.) Sub=00 Prot=00 MxPS= 8 #Cfgs= 2
-I: If#= 0 Alt= 0 #EPs= 1 Cls=02(comm.) Sub=02 Prot=01 Driver=acm
-I: If#= 1 Alt= 0 #EPs= 2 Cls=0a(data ) Sub=00 Prot=00 Driver=acm
-
-In the system log you should see:
-
-usb.c: USB new device connect, assigned device number 2
-usb.c: kmalloc IF c7691fa0, numif 1
-usb.c: kmalloc IF c7b5f3e0, numif 2
-usb.c: skipped 4 class/vendor specific interface descriptors
-usb.c: new device strings: Mfr=1, Product=2, SerialNumber=3
-usb.c: USB device number 2 default language ID 0x409
-Manufacturer: 3Com Inc.
-Product: 3Com U.S. Robotics Pro ISDN TA
-SerialNumber: UFT53A49BVT7
-acm.c: probing config 1
-acm.c: probing config 2
-ttyACM0: USB ACM device
-acm.c: acm_control_msg: rq: 0x22 val: 0x0 len: 0x0 result: 0
-acm.c: acm_control_msg: rq: 0x20 val: 0x0 len: 0x7 result: 7
-usb.c: acm driver claimed interface c7b5f3e0
-usb.c: acm driver claimed interface c7b5f3f8
-usb.c: acm driver claimed interface c7691fa0
+of luck, you have a device with vendor specific-interface::
+
+ D: Ver= 1.00 Cls=02(comm.) Sub=00 Prot=00 MxPS= 8 #Cfgs= 2
+ I: If#= 0 Alt= 0 #EPs= 1 Cls=02(comm.) Sub=02 Prot=01 Driver=acm
+ I: If#= 1 Alt= 0 #EPs= 2 Cls=0a(data ) Sub=00 Prot=00 Driver=acm
+
+In the system log you should see::
+
+ usb.c: USB new device connect, assigned device number 2
+ usb.c: kmalloc IF c7691fa0, numif 1
+ usb.c: kmalloc IF c7b5f3e0, numif 2
+ usb.c: skipped 4 class/vendor specific interface descriptors
+ usb.c: new device strings: Mfr=1, Product=2, SerialNumber=3
+ usb.c: USB device number 2 default language ID 0x409
+ Manufacturer: 3Com Inc.
+ Product: 3Com U.S. Robotics Pro ISDN TA
+ SerialNumber: UFT53A49BVT7
+ acm.c: probing config 1
+ acm.c: probing config 2
+ ttyACM0: USB ACM device
+ acm.c: acm_control_msg: rq: 0x22 val: 0x0 len: 0x0 result: 0
+ acm.c: acm_control_msg: rq: 0x20 val: 0x0 len: 0x7 result: 7
+ usb.c: acm driver claimed interface c7b5f3e0
+ usb.c: acm driver claimed interface c7b5f3f8
+ usb.c: acm driver claimed interface c7691fa0
If all this seems to be OK, fire up minicom and set it to talk to the ttyACM
device and try typing 'at'. If it responds with 'OK', then everything is
-
+==============================================================
Authorizing (or not) your USB devices to connect to the system
+==============================================================
-(C) 2007 Inaky Perez-Gonzalez <inaky@linux.intel.com> Intel Corporation
+Copyright (C) 2007 Inaky Perez-Gonzalez <inaky@linux.intel.com> Intel Corporation
This feature allows you to control if a USB device can be used (or
not) in a system. This feature will allow you to implement a lock-down
modification, only if root authorizes the device to be configured will
then it be possible to use it.
-Usage:
+Usage
+=====
-Authorize a device to connect:
+Authorize a device to connect::
-$ echo 1 > /sys/bus/usb/devices/DEVICE/authorized
+ $ echo 1 > /sys/bus/usb/devices/DEVICE/authorized
-Deauthorize a device:
+De-authorize a device::
-$ echo 0 > /sys/bus/usb/devices/DEVICE/authorized
+ $ echo 0 > /sys/bus/usb/devices/DEVICE/authorized
Set new devices connected to hostX to be deauthorized by default (ie:
-lock down):
+lock down)::
-$ echo 0 > /sys/bus/usb/devices/usbX/authorized_default
+ $ echo 0 > /sys/bus/usb/devices/usbX/authorized_default
-Remove the lock down:
+Remove the lock down::
-$ echo 1 > /sys/bus/usb/devices/usbX/authorized_default
+ $ echo 1 > /sys/bus/usb/devices/usbX/authorized_default
By default, Wired USB devices are authorized by default to
connect. Wireless USB hosts deauthorize by default all new connected
Example system lockdown (lame)
------------------------
+------------------------------
Imagine you want to implement a lockdown so only devices of type XYZ
can be connected (for example, it is a kiosk machine with a visible
-USB port):
+USB port)::
-boot up
-rc.local ->
+ boot up
+ rc.local ->
- for host in /sys/bus/usb/devices/usb*
- do
- echo 0 > $host/authorized_default
- done
+ for host in /sys/bus/usb/devices/usb*
+ do
+ echo 0 > $host/authorized_default
+ done
-Hookup an script to udev, for new USB devices
+Hookup an script to udev, for new USB devices::
if device_is_my_type $DEV
then
security verification you can make (or the best, for someone willing
to break it). If you need something secure, use crypto and Certificate
Authentication or stuff like that. Something simple for an storage key
-could be:
+could be::
-function device_is_my_type()
-{
+ function device_is_my_type()
+ {
echo 1 > authorized # temporarily authorize it
# FIXME: make sure none can mount it
mount DEVICENODE /mntpoint
else
echo 0 > authorized
fi
-}
+ }
Of course, this is lame, you'd want to do a real certificate
Interface authorization
-----------------------
+
There is a similar approach to allow or deny specific USB interfaces.
That allows to block only a subset of an USB device.
-Authorize an interface:
-$ echo 1 > /sys/bus/usb/devices/INTERFACE/authorized
+Authorize an interface::
-Deauthorize an interface:
-$ echo 0 > /sys/bus/usb/devices/INTERFACE/authorized
+ $ echo 1 > /sys/bus/usb/devices/INTERFACE/authorized
+
+Deauthorize an interface::
+
+ $ echo 0 > /sys/bus/usb/devices/INTERFACE/authorized
The default value for new interfaces
on a particular USB bus can be changed, too.
-Allow interfaces per default:
-$ echo 1 > /sys/bus/usb/devices/usbX/interface_authorized_default
+Allow interfaces per default::
+
+ $ echo 1 > /sys/bus/usb/devices/usbX/interface_authorized_default
+
+Deny interfaces per default::
-Deny interfaces per default:
-$ echo 0 > /sys/bus/usb/devices/usbX/interface_authorized_default
+ $ echo 0 > /sys/bus/usb/devices/usbX/interface_authorized_default
Per default the interface_authorized_default bit is 1.
So all interfaces would authorized per default.
Note:
-If a deauthorized interface will be authorized so the driver probing must
-be triggered manually by writing INTERFACE to /sys/bus/usb/drivers_probe
+ If a deauthorized interface will be authorized so the driver probing must
+ be triggered manually by writing INTERFACE to /sys/bus/usb/drivers_probe
For drivers that need multiple interfaces all needed interfaces should be
authorized first. After that the drivers should be probed.
+==============================================
+ChipIdea Highspeed Dual Role Controller Driver
+==============================================
+
1. How to test OTG FSM(HNP and SRP)
-----------------------------------
+
To show how to demo OTG HNP and SRP functions via sys input files
with 2 Freescale i.MX6Q sabre SD boards.
1.1 How to enable OTG FSM
----------------------------------------
+-------------------------
+
1.1.1 Select CONFIG_USB_OTG_FSM in menuconfig, rebuild kernel
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
Image and modules. If you want to check some internal
variables for otg fsm, mount debugfs, there are 2 files
-which can show otg fsm variables and some controller registers value:
-cat /sys/kernel/debug/ci_hdrc.0/otg
-cat /sys/kernel/debug/ci_hdrc.0/registers
+which can show otg fsm variables and some controller registers value::
+
+ cat /sys/kernel/debug/ci_hdrc.0/otg
+ cat /sys/kernel/debug/ci_hdrc.0/registers
+
1.1.2 Add below entries in your dts file for your controller node
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+::
+
otg-rev = <0x0200>;
adp-disable;
1.2 Test operations
-------------------
+
1) Power up 2 Freescale i.MX6Q sabre SD boards with gadget class driver loaded
(e.g. g_mass_storage).
The A-device(with micro A plug inserted) should enumerate B-device.
3) Role switch
- On B-device:
- echo 1 > /sys/bus/platform/devices/ci_hdrc.0/inputs/b_bus_req
+
+ On B-device::
+
+ echo 1 > /sys/bus/platform/devices/ci_hdrc.0/inputs/b_bus_req
B-device should take host role and enumerate A-device.
4) A-device switch back to host.
- On B-device:
- echo 0 > /sys/bus/platform/devices/ci_hdrc.0/inputs/b_bus_req
+
+ On B-device::
+
+ echo 0 > /sys/bus/platform/devices/ci_hdrc.0/inputs/b_bus_req
or, by introducing HNP polling, B-Host can know when A-peripheral wish
to be host role, so this role switch also can be trigged in A-peripheral
- side by answering the polling from B-Host, this can be done on A-device:
- echo 1 > /sys/bus/platform/devices/ci_hdrc.0/inputs/a_bus_req
+ side by answering the polling from B-Host, this can be done on A-device::
+
+ echo 1 > /sys/bus/platform/devices/ci_hdrc.0/inputs/a_bus_req
A-device should switch back to host and enumerate B-device.
A-device should NOT enumerate B-device.
if A-device wants to use bus:
- On A-device:
- echo 0 > /sys/bus/platform/devices/ci_hdrc.0/inputs/a_bus_drop
- echo 1 > /sys/bus/platform/devices/ci_hdrc.0/inputs/a_bus_req
+
+ On A-device::
+
+ echo 0 > /sys/bus/platform/devices/ci_hdrc.0/inputs/a_bus_drop
+ echo 1 > /sys/bus/platform/devices/ci_hdrc.0/inputs/a_bus_req
if B-device wants to use bus:
- On B-device:
- echo 1 > /sys/bus/platform/devices/ci_hdrc.0/inputs/b_bus_req
+
+ On B-device::
+
+ echo 1 > /sys/bus/platform/devices/ci_hdrc.0/inputs/b_bus_req
7) A-device power down the bus.
- On A-device:
- echo 1 > /sys/bus/platform/devices/ci_hdrc.0/inputs/a_bus_drop
+
+ On A-device::
+
+ echo 1 > /sys/bus/platform/devices/ci_hdrc.0/inputs/a_bus_drop
A-device should disconnect with B-device and power down the bus.
8) B-device does data pulse for SRP.
- On B-device:
- echo 1 > /sys/bus/platform/devices/ci_hdrc.0/inputs/b_bus_req
+
+ On B-device::
+
+ echo 1 > /sys/bus/platform/devices/ci_hdrc.0/inputs/b_bus_req
A-device should resume usb bus and enumerate B-device.
July 27, 2012 Revision 2.0 version 1.1a"
2. How to enable USB as system wakeup source
------------------------------------
+--------------------------------------------
Below is the example for how to enable USB as system wakeup source
at imx6 platform.
-2.1 Enable core's wakeup
-echo enabled > /sys/bus/platform/devices/ci_hdrc.0/power/wakeup
-2.2 Enable glue layer's wakeup
-echo enabled > /sys/bus/platform/devices/2184000.usb/power/wakeup
-2.3 Enable PHY's wakeup (optional)
-echo enabled > /sys/bus/platform/devices/20c9000.usbphy/power/wakeup
-2.4 Enable roothub's wakeup
-echo enabled > /sys/bus/usb/devices/usb1/power/wakeup
-2.5 Enable related device's wakeup
-echo enabled > /sys/bus/usb/devices/1-1/power/wakeup
+2.1 Enable core's wakeup::
+
+ echo enabled > /sys/bus/platform/devices/ci_hdrc.0/power/wakeup
+
+2.2 Enable glue layer's wakeup::
+
+ echo enabled > /sys/bus/platform/devices/2184000.usb/power/wakeup
+
+2.3 Enable PHY's wakeup (optional)::
+
+ echo enabled > /sys/bus/platform/devices/20c9000.usbphy/power/wakeup
+
+2.4 Enable roothub's wakeup::
+
+ echo enabled > /sys/bus/usb/devices/usb1/power/wakeup
+
+2.5 Enable related device's wakeup::
+
+ echo enabled > /sys/bus/usb/devices/1-1/power/wakeup
If the system has only one usb port, and you want usb wakeup at this port, you
-can use below script to enable usb wakeup.
-for i in $(find /sys -name wakeup | grep usb);do echo enabled > $i;done;
+can use below script to enable usb wakeup::
+ for i in $(find /sys -name wakeup | grep usb);do echo enabled > $i;done;
+===========
+DWC3 driver
+===========
+
+
+TODO
+~~~~
- TODO
-~~~~~~
Please pick something while reading :)
- Convert interrupt handler to per-ep-thread-irq
until the command completes which is bad.
Implementation idea:
+
- dwc core implements a demultiplexing irq chip for interrupts per
endpoint. The interrupt numbers are allocated during probe and belong
to the device. If MSI provides per-endpoint interrupt this dummy
- dwc3_send_gadget_ep_cmd() will sleep in wait_for_completion_timeout()
until the command completes.
- the interrupt handler is split into the following pieces:
+
- primary handler of the device
goes through every event and calls generic_handle_irq() for event
it. On return from generic_handle_irq() in acknowledges the event
for command completion.
Latency:
+
There should be no increase in latency since the interrupt-thread has a
high priority and will be run before an average task in user land
(except the user changed priorities).
+===========
+EHCI driver
+===========
+
27-Dec-2002
The EHCI driver is used to talk to high speed USB 2.0 devices using
<dbrownell@users.sourceforge.net>
-FUNCTIONALITY
+Functionality
+=============
This driver is regularly tested on x86 hardware, and has also been
used on PPC hardware so big/little endianness issues should be gone.
systems with interesting DMA mapping issues.
Transfer Types
+--------------
At this writing the driver should comfortably handle all control, bulk,
and interrupt transfers, including requests to USB 1.1 devices through
most USB audio and video devices can't be connected to high speed buses.
Driver Behavior
+---------------
Transfers of all types can be queued. This means that control transfers
from a driver on one interface (or through usbfs) won't interfere with
and prevent use of polling intervals of less than one frame.
-USE BY
+Use by
+======
Assuming you have an EHCI controller (on a PCI card or motherboard)
-and have compiled this driver as a module, load this like:
+and have compiled this driver as a module, load this like::
# modprobe ehci-hcd
-and remove it by:
+and remove it by::
# rmmod ehci-hcd
debugging support, you'll see three files in the "sysfs" directory for
any EHCI controller:
- "async" dumps the asynchronous schedule, used for control
+ "async"
+ dumps the asynchronous schedule, used for control
and bulk transfers. Shows each active qh and the qtds
pending, usually one qtd per urb. (Look at it with
usb-storage doing disk I/O; watch the request queues!)
- "periodic" dumps the periodic schedule, used for interrupt
+ "periodic"
+ dumps the periodic schedule, used for interrupt
and isochronous transfers. Doesn't show qtds.
- "registers" show controller register state, and
+ "registers"
+ show controller register state, and
The contents of those files can help identify driver problems.
badly when they see different faults than OHCI or UHCI report.
-PERFORMANCE
+Performance
+===========
USB 2.0 throughput is gated by two main factors: how fast the host
controller can process requests, and how fast devices can respond to
approach the quoted 480 MBit/sec transfer rate.
Hardware Performance
+--------------------
At this writing, individual USB 2.0 devices tend to max out at around
20 MByte/sec transfer rates. This is of course subject to change;
it completed in less than 250 usec (depending on transfer size).
Software Performance
+--------------------
To get even 20 MByte/sec transfer rates, Linux-USB device drivers will
need to keep the EHCI queue full. That means issuing large requests,
help make high speed transfers run as fast as they can.
-TBD: Interrupt and ISO transfer performance issues. Those periodic
-transfers are fully scheduled, so the main issue is likely to be how
-to trigger "high bandwidth" modes.
+TBD:
+ Interrupt and ISO transfer performance issues. Those periodic
+ transfers are fully scheduled, so the main issue is likely to be how
+ to trigger "high bandwidth" modes.
-TBD: More than standard 80% periodic bandwidth allocation is possible
-through sysfs uframe_periodic_max parameter. Describe that.
+TBD:
+ More than standard 80% periodic bandwidth allocation is possible
+ through sysfs uframe_periodic_max parameter. Describe that.
-*How FunctionFS works*
+====================
+How FunctionFS works
+====================
From kernel point of view it is just a composite function with some
unique behaviour. It may be added to an USB configuration only after
One can imagine a gadget that has an Ethernet, MTP and HID interfaces
where the last two are implemented via FunctionFS. On user space
-level it would look like this:
+level it would look like this::
-$ insmod g_ffs.ko idVendor=<ID> iSerialNumber=<string> functions=mtp,hid
-$ mkdir /dev/ffs-mtp && mount -t functionfs mtp /dev/ffs-mtp
-$ ( cd /dev/ffs-mtp && mtp-daemon ) &
-$ mkdir /dev/ffs-hid && mount -t functionfs hid /dev/ffs-hid
-$ ( cd /dev/ffs-hid && hid-daemon ) &
+ $ insmod g_ffs.ko idVendor=<ID> iSerialNumber=<string> functions=mtp,hid
+ $ mkdir /dev/ffs-mtp && mount -t functionfs mtp /dev/ffs-mtp
+ $ ( cd /dev/ffs-mtp && mtp-daemon ) &
+ $ mkdir /dev/ffs-hid && mount -t functionfs hid /dev/ffs-hid
+ $ ( cd /dev/ffs-hid && hid-daemon ) &
On kernel level the gadget checks ffs_data->dev_name to identify
whether it's FunctionFS designed for MTP ("mtp") or HID ("hid").
Conversely, the gadget is unregistered after the first USB function
closes its endpoints.
-
+==============
+Gadget Testing
+==============
+
This file summarizes information on basic testing of USB functions
provided by gadgets.
-1. ACM function
-2. ECM function
-3. ECM subset function
-4. EEM function
-5. FFS function
-6. HID function
-7. LOOPBACK function
-8. MASS STORAGE function
-9. MIDI function
-10. NCM function
-11. OBEX function
-12. PHONET function
-13. RNDIS function
-14. SERIAL function
-15. SOURCESINK function
-16. UAC1 function (legacy implementation)
-17. UAC2 function
-18. UVC function
-19. PRINTER function
-20. UAC1 function (new API)
+.. contents
+
+ 1. ACM function
+ 2. ECM function
+ 3. ECM subset function
+ 4. EEM function
+ 5. FFS function
+ 6. HID function
+ 7. LOOPBACK function
+ 8. MASS STORAGE function
+ 9. MIDI function
+ 10. NCM function
+ 11. OBEX function
+ 12. PHONET function
+ 13. RNDIS function
+ 14. SERIAL function
+ 15. SOURCESINK function
+ 16. UAC1 function (legacy implementation)
+ 17. UAC2 function
+ 18. UVC function
+ 19. PRINTER function
+ 20. UAC1 function (new API)
1. ACM function
Testing the ACM function
------------------------
-On the host: cat > /dev/ttyACM<X>
-On the device : cat /dev/ttyGS<Y>
+On the host::
+
+ cat > /dev/ttyACM<X>
+
+On the device::
+
+ cat /dev/ttyGS<Y>
then the other way round
-On the device: cat > /dev/ttyGS<Y>
-On the host: cat /dev/ttyACM<X>
+On the device::
+
+ cat > /dev/ttyGS<Y>
+
+On the host::
+
+ cat /dev/ttyACM<X>
2. ECM function
===============
The function name to use when creating the function directory is "ecm".
The ECM function provides these attributes in its function directory:
- ifname - network device interface name associated with this
+ =============== ==================================================
+ ifname network device interface name associated with this
function instance
- qmult - queue length multiplier for high and super speed
- host_addr - MAC address of host's end of this
+ qmult queue length multiplier for high and super speed
+ host_addr MAC address of host's end of this
Ethernet over USB link
- dev_addr - MAC address of device's end of this
+ dev_addr MAC address of device's end of this
Ethernet over USB link
+ =============== ==================================================
and after creating the functions/ecm.<instance name> they contain default
values: qmult is 5, dev_addr and host_addr are randomly selected.
Configure IP addresses of the device and the host. Then:
-On the device: ping <host's IP>
-On the host: ping <device's IP>
+On the device::
+
+ ping <host's IP>
+
+On the host::
+
+ ping <device's IP>
3. ECM subset function
======================
The function name to use when creating the function directory is "geth".
The ECM subset function provides these attributes in its function directory:
- ifname - network device interface name associated with this
+ =============== ==================================================
+ ifname network device interface name associated with this
function instance
- qmult - queue length multiplier for high and super speed
- host_addr - MAC address of host's end of this
+ qmult queue length multiplier for high and super speed
+ host_addr MAC address of host's end of this
Ethernet over USB link
- dev_addr - MAC address of device's end of this
+ dev_addr MAC address of device's end of this
Ethernet over USB link
+ =============== ==================================================
and after creating the functions/ecm.<instance name> they contain default
values: qmult is 5, dev_addr and host_addr are randomly selected.
Configure IP addresses of the device and the host. Then:
-On the device: ping <host's IP>
-On the host: ping <device's IP>
+On the device::
+
+ ping <host's IP>
+
+On the host::
+
+ ping <device's IP>
4. EEM function
===============
The function name to use when creating the function directory is "eem".
The EEM function provides these attributes in its function directory:
- ifname - network device interface name associated with this
+ =============== ==================================================
+ ifname network device interface name associated with this
function instance
- qmult - queue length multiplier for high and super speed
- host_addr - MAC address of host's end of this
+ qmult queue length multiplier for high and super speed
+ host_addr MAC address of host's end of this
Ethernet over USB link
- dev_addr - MAC address of device's end of this
+ dev_addr MAC address of device's end of this
Ethernet over USB link
+ =============== ==================================================
and after creating the functions/eem.<instance name> they contain default
values: qmult is 5, dev_addr and host_addr are randomly selected.
Configure IP addresses of the device and the host. Then:
-On the device: ping <host's IP>
-On the host: ping <device's IP>
+On the device::
+
+ ping <host's IP>
+
+On the host::
+
+ ping <device's IP>
5. FFS function
===============
------------------------
On the device: start the function's userspace daemon, enable the gadget
+
On the host: use the USB function provided by the device
6. HID function
The function name to use when creating the function directory is "hid".
The HID function provides these attributes in its function directory:
- protocol - HID protocol to use
- report_desc - data to be used in HID reports, except data
+ =============== ===========================================
+ protocol HID protocol to use
+ report_desc data to be used in HID reports, except data
passed with /dev/hidg<X>
- report_length - HID report length
- subclass - HID subclass to use
+ report_length HID report length
+ subclass HID subclass to use
+ =============== ===========================================
For a keyboard the protocol and the subclass are 1, the report_length is 8,
-while the report_desc is:
+while the report_desc is::
-$ hd my_report_desc
-00000000 05 01 09 06 a1 01 05 07 19 e0 29 e7 15 00 25 01 |..........)...%.|
-00000010 75 01 95 08 81 02 95 01 75 08 81 03 95 05 75 01 |u.......u.....u.|
-00000020 05 08 19 01 29 05 91 02 95 01 75 03 91 03 95 06 |....).....u.....|
-00000030 75 08 15 00 25 65 05 07 19 00 29 65 81 00 c0 |u...%e....)e...|
-0000003f
+ $ hd my_report_desc
+ 00000000 05 01 09 06 a1 01 05 07 19 e0 29 e7 15 00 25 01 |..........)...%.|
+ 00000010 75 01 95 08 81 02 95 01 75 08 81 03 95 05 75 01 |u.......u.....u.|
+ 00000020 05 08 19 01 29 05 91 02 95 01 75 03 91 03 95 06 |....).....u.....|
+ 00000030 75 08 15 00 25 65 05 07 19 00 29 65 81 00 c0 |u...%e....)e...|
+ 0000003f
-Such a sequence of bytes can be stored to the attribute with echo:
+Such a sequence of bytes can be stored to the attribute with echo::
-$ echo -ne \\x05\\x01\\x09\\x06\\xa1.....
+ $ echo -ne \\x05\\x01\\x09\\x06\\xa1.....
Testing the HID function
------------------------
Device:
+
- create the gadget
- connect the gadget to a host, preferably not the one used
-to control the gadget
+ to control the gadget
- run a program which writes to /dev/hidg<N>, e.g.
-a userspace program found in Documentation/usb/gadget_hid.txt:
+ a userspace program found in Documentation/usb/gadget_hid.txt::
-$ ./hid_gadget_test /dev/hidg0 keyboard
+ $ ./hid_gadget_test /dev/hidg0 keyboard
Host:
+
- observe the keystrokes from the gadget
7. LOOPBACK function
The function name to use when creating the function directory is "Loopback".
The LOOPBACK function provides these attributes in its function directory:
- qlen - depth of loopback queue
- bulk_buflen - buffer length
+ =============== =======================
+ qlen depth of loopback queue
+ bulk_buflen buffer length
+ =============== =======================
Testing the LOOPBACK function
-----------------------------
device: run the gadget
+
host: test-usb (tools/usb/testusb.c)
8. MASS STORAGE function
The MASS STORAGE function provides these attributes in its directory:
files:
- stall - Set to permit function to halt bulk endpoints.
+ =============== ==============================================
+ stall Set to permit function to halt bulk endpoints.
Disabled on some USB devices known not to work
correctly. You should set it to true.
- num_buffers - Number of pipeline buffers. Valid numbers
+ num_buffers Number of pipeline buffers. Valid numbers
are 2..4. Available only if
CONFIG_USB_GADGET_DEBUG_FILES is set.
+ =============== ==============================================
and a default lun.0 directory corresponding to SCSI LUN #0.
-A new lun can be added with mkdir:
+A new lun can be added with mkdir::
-$ mkdir functions/mass_storage.0/partition.5
+ $ mkdir functions/mass_storage.0/partition.5
Lun numbering does not have to be continuous, except for lun #0 which is
created by default. A maximum of 8 luns can be specified and they all must be
In each lun directory there are the following attribute files:
- file - The path to the backing file for the LUN.
+ =============== ==============================================
+ file The path to the backing file for the LUN.
Required if LUN is not marked as removable.
- ro - Flag specifying access to the LUN shall be
+ ro Flag specifying access to the LUN shall be
read-only. This is implied if CD-ROM emulation
is enabled as well as when it was impossible
to open "filename" in R/W mode.
- removable - Flag specifying that LUN shall be indicated as
+ removable Flag specifying that LUN shall be indicated as
being removable.
- cdrom - Flag specifying that LUN shall be reported as
+ cdrom Flag specifying that LUN shall be reported as
being a CD-ROM.
- nofua - Flag specifying that FUA flag
+ nofua Flag specifying that FUA flag
in SCSI WRITE(10,12)
+ =============== ==============================================
Testing the MASS STORAGE function
---------------------------------
The function name to use when creating the function directory is "midi".
The MIDI function provides these attributes in its function directory:
- buflen - MIDI buffer length
- id - ID string for the USB MIDI adapter
- in_ports - number of MIDI input ports
- index - index value for the USB MIDI adapter
- out_ports - number of MIDI output ports
- qlen - USB read request queue length
+ =============== ====================================
+ buflen MIDI buffer length
+ id ID string for the USB MIDI adapter
+ in_ports number of MIDI input ports
+ index index value for the USB MIDI adapter
+ out_ports number of MIDI output ports
+ qlen USB read request queue length
+ =============== ====================================
Testing the MIDI function
-------------------------
There are two cases: playing a mid from the gadget to
the host and playing a mid from the host to the gadget.
-1) Playing a mid from the gadget to the host
-host)
+1) Playing a mid from the gadget to the host:
+
+host::
-$ arecordmidi -l
- Port Client name Port name
- 14:0 Midi Through Midi Through Port-0
- 24:0 MIDI Gadget MIDI Gadget MIDI 1
-$ arecordmidi -p 24:0 from_gadget.mid
+ $ arecordmidi -l
+ Port Client name Port name
+ 14:0 Midi Through Midi Through Port-0
+ 24:0 MIDI Gadget MIDI Gadget MIDI 1
+ $ arecordmidi -p 24:0 from_gadget.mid
-gadget)
+gadget::
-$ aplaymidi -l
- Port Client name Port name
- 20:0 f_midi f_midi
+ $ aplaymidi -l
+ Port Client name Port name
+ 20:0 f_midi f_midi
-$ aplaymidi -p 20:0 to_host.mid
+ $ aplaymidi -p 20:0 to_host.mid
2) Playing a mid from the host to the gadget
-gadget)
-$ arecordmidi -l
- Port Client name Port name
- 20:0 f_midi f_midi
+gadget::
+
+ $ arecordmidi -l
+ Port Client name Port name
+ 20:0 f_midi f_midi
-$ arecordmidi -p 20:0 from_host.mid
+ $ arecordmidi -p 20:0 from_host.mid
-host)
+host::
-$ aplaymidi -l
- Port Client name Port name
- 14:0 Midi Through Midi Through Port-0
- 24:0 MIDI Gadget MIDI Gadget MIDI 1
+ $ aplaymidi -l
+ Port Client name Port name
+ 14:0 Midi Through Midi Through Port-0
+ 24:0 MIDI Gadget MIDI Gadget MIDI 1
-$ aplaymidi -p24:0 to_gadget.mid
+ $ aplaymidi -p24:0 to_gadget.mid
The from_gadget.mid should sound identical to the to_host.mid.
+
The from_host.id should sound identical to the to_gadget.mid.
-MIDI files can be played to speakers/headphones with e.g. timidity installed
+MIDI files can be played to speakers/headphones with e.g. timidity installed::
-$ aplaymidi -l
- Port Client name Port name
- 14:0 Midi Through Midi Through Port-0
- 24:0 MIDI Gadget MIDI Gadget MIDI 1
-128:0 TiMidity TiMidity port 0
-128:1 TiMidity TiMidity port 1
-128:2 TiMidity TiMidity port 2
-128:3 TiMidity TiMidity port 3
+ $ aplaymidi -l
+ Port Client name Port name
+ 14:0 Midi Through Midi Through Port-0
+ 24:0 MIDI Gadget MIDI Gadget MIDI 1
+ 128:0 TiMidity TiMidity port 0
+ 128:1 TiMidity TiMidity port 1
+ 128:2 TiMidity TiMidity port 2
+ 128:3 TiMidity TiMidity port 3
-$ aplaymidi -p 128:0 file.mid
+ $ aplaymidi -p 128:0 file.mid
-MIDI ports can be logically connected using the aconnect utility, e.g.:
+MIDI ports can be logically connected using the aconnect utility, e.g.::
-$ aconnect 24:0 128:0 # try it on the host
+ $ aconnect 24:0 128:0 # try it on the host
After the gadget's MIDI port is connected to timidity's MIDI port,
whatever is played at the gadget side with aplaymidi -l is audible
The function name to use when creating the function directory is "ncm".
The NCM function provides these attributes in its function directory:
- ifname - network device interface name associated with this
+ =============== ==================================================
+ ifname network device interface name associated with this
function instance
- qmult - queue length multiplier for high and super speed
- host_addr - MAC address of host's end of this
+ qmult queue length multiplier for high and super speed
+ host_addr MAC address of host's end of this
Ethernet over USB link
- dev_addr - MAC address of device's end of this
+ dev_addr MAC address of device's end of this
Ethernet over USB link
+ =============== ==================================================
and after creating the functions/ncm.<instance name> they contain default
values: qmult is 5, dev_addr and host_addr are randomly selected.
Configure IP addresses of the device and the host. Then:
-On the device: ping <host's IP>
-On the host: ping <device's IP>
+On the device::
+
+ ping <host's IP>
+
+On the host::
+
+ ping <device's IP>
11. OBEX function
=================
Testing the OBEX function
-------------------------
-On device: seriald -f /dev/ttyGS<Y> -s 1024
-On host: serialc -v <vendorID> -p <productID> -i<interface#> -a1 -s1024 \
- -t<out endpoint addr> -r<in endpoint addr>
+On device::
+
+ seriald -f /dev/ttyGS<Y> -s 1024
+
+On host::
+
+ serialc -v <vendorID> -p <productID> -i<interface#> -a1 -s1024 \
+ -t<out endpoint addr> -r<in endpoint addr>
where seriald and serialc are Felipe's utilities found here:
-https://github.com/felipebalbi/usb-tools.git master
+ https://github.com/felipebalbi/usb-tools.git master
12. PHONET function
===================
The function name to use when creating the function directory is "phonet".
The PHONET function provides just one attribute in its function directory:
- ifname - network device interface name associated with this
+ =============== ==================================================
+ ifname network device interface name associated with this
function instance
+ =============== ==================================================
Testing the PHONET function
---------------------------
git://git.gitorious.org/meego-cellular/phonet-utils.git
-On the host:
+On the host::
-$ ./phonet -a 0x10 -i usbpn0
-$ ./pnroute add 0x6c usbpn0
-$./pnroute add 0x10 usbpn0
-$ ifconfig usbpn0 up
+ $ ./phonet -a 0x10 -i usbpn0
+ $ ./pnroute add 0x6c usbpn0
+ $./pnroute add 0x10 usbpn0
+ $ ifconfig usbpn0 up
-On the device:
+On the device::
-$ ./phonet -a 0x6c -i upnlink0
-$ ./pnroute add 0x10 upnlink0
-$ ifconfig upnlink0 up
+ $ ./phonet -a 0x6c -i upnlink0
+ $ ./pnroute add 0x10 upnlink0
+ $ ifconfig upnlink0 up
-Then a test program can be used:
+Then a test program can be used::
-http://www.spinics.net/lists/linux-usb/msg85690.html
+ http://www.spinics.net/lists/linux-usb/msg85690.html
-On the device:
+On the device::
-$ ./pnxmit -a 0x6c -r
+ $ ./pnxmit -a 0x6c -r
-On the host:
+On the host::
-$ ./pnxmit -a 0x10 -s 0x6c
+ $ ./pnxmit -a 0x10 -s 0x6c
As a result some data should be sent from host to device.
Then the other way round:
-On the host:
+On the host::
-$ ./pnxmit -a 0x10 -r
+ $ ./pnxmit -a 0x10 -r
-On the device:
+On the device::
-$ ./pnxmit -a 0x6c -s 0x10
+ $ ./pnxmit -a 0x6c -s 0x10
13. RNDIS function
==================
The function name to use when creating the function directory is "rndis".
The RNDIS function provides these attributes in its function directory:
- ifname - network device interface name associated with this
+ =============== ==================================================
+ ifname network device interface name associated with this
function instance
- qmult - queue length multiplier for high and super speed
- host_addr - MAC address of host's end of this
+ qmult queue length multiplier for high and super speed
+ host_addr MAC address of host's end of this
Ethernet over USB link
- dev_addr - MAC address of device's end of this
+ dev_addr MAC address of device's end of this
Ethernet over USB link
+ =============== ==================================================
and after creating the functions/rndis.<instance name> they contain default
values: qmult is 5, dev_addr and host_addr are randomly selected.
Configure IP addresses of the device and the host. Then:
-On the device: ping <host's IP>
-On the host: ping <device's IP>
+On the device::
+
+ ping <host's IP>
+
+On the host::
+
+ ping <device's IP>
14. SERIAL function
===================
Testing the SERIAL function
---------------------------
-On host: insmod usbserial
- echo VID PID >/sys/bus/usb-serial/drivers/generic/new_id
-On host: cat > /dev/ttyUSB<X>
-On target: cat /dev/ttyGS<Y>
+On host::
+
+ insmod usbserial
+ echo VID PID >/sys/bus/usb-serial/drivers/generic/new_id
+
+On host::
+
+ cat > /dev/ttyUSB<X>
+
+On target::
+
+ cat /dev/ttyGS<Y>
then the other way round
-On target: cat > /dev/ttyGS<Y>
-On host: cat /dev/ttyUSB<X>
+On target::
+
+ cat > /dev/ttyGS<Y>
+
+On host::
+
+ cat /dev/ttyUSB<X>
15. SOURCESINK function
=======================
The function name to use when creating the function directory is "SourceSink".
The SOURCESINK function provides these attributes in its function directory:
- pattern - 0 (all zeros), 1 (mod63), 2 (none)
- isoc_interval - 1..16
- isoc_maxpacket - 0 - 1023 (fs), 0 - 1024 (hs/ss)
- isoc_mult - 0..2 (hs/ss only)
- isoc_maxburst - 0..15 (ss only)
- bulk_buflen - buffer length
- bulk_qlen - depth of queue for bulk
- iso_qlen - depth of queue for iso
+ =============== ==================================
+ pattern 0 (all zeros), 1 (mod63), 2 (none)
+ isoc_interval 1..16
+ isoc_maxpacket 0 - 1023 (fs), 0 - 1024 (hs/ss)
+ isoc_mult 0..2 (hs/ss only)
+ isoc_maxburst 0..15 (ss only)
+ bulk_buflen buffer length
+ bulk_qlen depth of queue for bulk
+ iso_qlen depth of queue for iso
+ =============== ==================================
Testing the SOURCESINK function
-------------------------------
device: run the gadget
+
host: test-usb (tools/usb/testusb.c)
16. UAC1 function (legacy implementation)
-=================
+=========================================
The function is provided by usb_f_uac1_legacy.ko module.
is "uac1_legacy".
The uac1 function provides these attributes in its function directory:
- audio_buf_size - audio buffer size
- fn_cap - capture pcm device file name
- fn_cntl - control device file name
- fn_play - playback pcm device file name
- req_buf_size - ISO OUT endpoint request buffer size
- req_count - ISO OUT endpoint request count
+ =============== ====================================
+ audio_buf_size audio buffer size
+ fn_cap capture pcm device file name
+ fn_cntl control device file name
+ fn_play playback pcm device file name
+ req_buf_size ISO OUT endpoint request buffer size
+ req_count ISO OUT endpoint request count
+ =============== ====================================
The attributes have sane default values.
-------------------------
device: run the gadget
-host: aplay -l # should list our USB Audio Gadget
+
+host::
+
+ aplay -l # should list our USB Audio Gadget
17. UAC2 function
=================
The function name to use when creating the function directory is "uac2".
The uac2 function provides these attributes in its function directory:
- c_chmask - capture channel mask
- c_srate - capture sampling rate
- c_ssize - capture sample size (bytes)
- p_chmask - playback channel mask
- p_srate - playback sampling rate
- p_ssize - playback sample size (bytes)
- req_number - the number of pre-allocated request for both capture
- and playback
+ =============== ====================================================
+ c_chmask capture channel mask
+ c_srate capture sampling rate
+ c_ssize capture sample size (bytes)
+ p_chmask playback channel mask
+ p_srate playback sampling rate
+ p_ssize playback sample size (bytes)
+ req_number the number of pre-allocated request for both capture
+ and playback
+ =============== ====================================================
The attributes have sane default values.
This function does not require real hardware support, it just
sends a stream of audio data to/from the host. In order to
actually hear something at the device side, a command similar
-to this must be used at the device side:
+to this must be used at the device side::
-$ arecord -f dat -t wav -D hw:2,0 | aplay -D hw:0,0 &
+ $ arecord -f dat -t wav -D hw:2,0 | aplay -D hw:0,0 &
-e.g.:
+e.g.::
-$ arecord -f dat -t wav -D hw:CARD=UAC2Gadget,DEV=0 | \
-aplay -D default:CARD=OdroidU3
+ $ arecord -f dat -t wav -D hw:CARD=UAC2Gadget,DEV=0 | \
+ aplay -D default:CARD=OdroidU3
18. UVC function
================
The function name to use when creating the function directory is "uvc".
The uvc function provides these attributes in its function directory:
- streaming_interval - interval for polling endpoint for data transfers
- streaming_maxburst - bMaxBurst for super speed companion descriptor
- streaming_maxpacket - maximum packet size this endpoint is capable of
- sending or receiving when this configuration is
- selected
+ =================== ================================================
+ streaming_interval interval for polling endpoint for data transfers
+ streaming_maxburst bMaxBurst for super speed companion descriptor
+ streaming_maxpacket maximum packet size this endpoint is capable of
+ sending or receiving when this configuration is
+ selected
+ =================== ================================================
There are also "control" and "streaming" subdirectories, each of which contain
a number of their subdirectories. There are some sane defaults provided, but
the user must provide the following:
- control header - create in control/header, link from control/class/fs
- and/or control/class/ss
- streaming header - create in streaming/header, link from
- streaming/class/fs and/or streaming/class/hs and/or
- streaming/class/ss
- format description - create in streaming/mjpeg and/or
- streaming/uncompressed
- frame description - create in streaming/mjpeg/<format> and/or in
- streaming/uncompressed/<format>
+ ================== ====================================================
+ control header create in control/header, link from control/class/fs
+ and/or control/class/ss
+ streaming header create in streaming/header, link from
+ streaming/class/fs and/or streaming/class/hs and/or
+ streaming/class/ss
+ format description create in streaming/mjpeg and/or
+ streaming/uncompressed
+ frame description create in streaming/mjpeg/<format> and/or in
+ streaming/uncompressed/<format>
+ ================== ====================================================
Each frame description contains frame interval specification, and each
such specification consists of a number of lines with an inverval value
-in each line. The rules stated above are best illustrated with an example:
-
-# mkdir functions/uvc.usb0/control/header/h
-# cd functions/uvc.usb0/control/
-# ln -s header/h class/fs
-# ln -s header/h class/ss
-# mkdir -p functions/uvc.usb0/streaming/uncompressed/u/360p
-# cat <<EOF > functions/uvc.usb0/streaming/uncompressed/u/360p/dwFrameInterval
-666666
-1000000
-5000000
-EOF
-# cd $GADGET_CONFIGFS_ROOT
-# mkdir functions/uvc.usb0/streaming/header/h
-# cd functions/uvc.usb0/streaming/header/h
-# ln -s ../../uncompressed/u
-# cd ../../class/fs
-# ln -s ../../header/h
-# cd ../../class/hs
-# ln -s ../../header/h
-# cd ../../class/ss
-# ln -s ../../header/h
+in each line. The rules stated above are best illustrated with an example::
+
+ # mkdir functions/uvc.usb0/control/header/h
+ # cd functions/uvc.usb0/control/
+ # ln -s header/h class/fs
+ # ln -s header/h class/ss
+ # mkdir -p functions/uvc.usb0/streaming/uncompressed/u/360p
+ # cat <<EOF > functions/uvc.usb0/streaming/uncompressed/u/360p/dwFrameInterval
+ 666666
+ 1000000
+ 5000000
+ EOF
+ # cd $GADGET_CONFIGFS_ROOT
+ # mkdir functions/uvc.usb0/streaming/header/h
+ # cd functions/uvc.usb0/streaming/header/h
+ # ln -s ../../uncompressed/u
+ # cd ../../class/fs
+ # ln -s ../../header/h
+ # cd ../../class/hs
+ # ln -s ../../header/h
+ # cd ../../class/ss
+ # ln -s ../../header/h
Testing the UVC function
------------------------
-device: run the gadget, modprobe vivid
+device: run the gadget, modprobe vivid::
-# uvc-gadget -u /dev/video<uvc video node #> -v /dev/video<vivid video node #>
+ # uvc-gadget -u /dev/video<uvc video node #> -v /dev/video<vivid video node #>
where uvc-gadget is this program:
-http://git.ideasonboard.org/uvc-gadget.git
+ http://git.ideasonboard.org/uvc-gadget.git
with these patches:
-http://www.spinics.net/lists/linux-usb/msg99220.html
-host: luvcview -f yuv
+ http://www.spinics.net/lists/linux-usb/msg99220.html
+
+host::
+
+ luvcview -f yuv
19. PRINTER function
====================
The function name to use when creating the function directory is "printer".
The printer function provides these attributes in its function directory:
- pnp_string - Data to be passed to the host in pnp string
- q_len - Number of requests per endpoint
+ ========== ===========================================
+ pnp_string Data to be passed to the host in pnp string
+ q_len Number of requests per endpoint
+ ========== ===========================================
Testing the PRINTER function
----------------------------
The most basic testing:
-device: run the gadget
-# ls -l /devices/virtual/usb_printer_gadget/
+device: run the gadget::
+
+ # ls -l /devices/virtual/usb_printer_gadget/
should show g_printer<number>.
host->device transmission:
-device:
-# cat /dev/g_printer<number>
-host:
-# cat > /dev/usb/lp0
+device::
-device->host transmission:
+ # cat /dev/g_printer<number>
-# cat > /dev/g_printer<number>
-host:
-# cat /dev/usb/lp0
+host::
+
+ # cat > /dev/usb/lp0
+
+device->host transmission::
+
+ # cat > /dev/g_printer<number>
+
+host::
+
+ # cat /dev/usb/lp0
More advanced testing can be done with the prn_example
described in Documentation/usb/gadget_printer.txt.
20. UAC1 function (virtual ALSA card, using u_audio API)
-=================
+========================================================
The function is provided by usb_f_uac1.ko module.
It will create a virtual ALSA card and the audio streams are simply
The function name to use when creating the function directory is "uac1".
The uac1 function provides these attributes in its function directory:
- c_chmask - capture channel mask
- c_srate - capture sampling rate
- c_ssize - capture sample size (bytes)
- p_chmask - playback channel mask
- p_srate - playback sampling rate
- p_ssize - playback sample size (bytes)
- req_number - the number of pre-allocated request for both capture
- and playback
+ ========== ====================================================
+ c_chmask capture channel mask
+ c_srate capture sampling rate
+ c_ssize capture sample size (bytes)
+ p_chmask playback channel mask
+ p_srate playback sampling rate
+ p_ssize playback sample size (bytes)
+ req_number the number of pre-allocated request for both capture
+ and playback
+ ========== ====================================================
The attributes have sane default values.
This function does not require real hardware support, it just
sends a stream of audio data to/from the host. In order to
actually hear something at the device side, a command similar
-to this must be used at the device side:
+to this must be used at the device side::
-$ arecord -f dat -t wav -D hw:2,0 | aplay -D hw:0,0 &
+ $ arecord -f dat -t wav -D hw:2,0 | aplay -D hw:0,0 &
-e.g.:
+e.g.::
-$ arecord -f dat -t wav -D hw:CARD=UAC1Gadget,DEV=0 | \
-aplay -D default:CARD=OdroidU3
+ $ arecord -f dat -t wav -D hw:CARD=UAC1Gadget,DEV=0 | \
+ aplay -D default:CARD=OdroidU3
+============================================
+Linux USB gadget configured through configfs
+============================================
-
-
- Linux USB gadget configured through configfs
-
-
- 25th April 2013
+25th April 2013
Creating a gadget means deciding what configurations there will be
and which functions each configuration will provide.
-Configfs (please see Documentation/filesystems/configfs/*) lends itself nicely
+Configfs (please see `Documentation/filesystems/configfs/*`) lends itself nicely
for the purpose of telling the kernel about the above mentioned decision.
This document is about how to do it.
made available through configfs can be seen here:
http://www.spinics.net/lists/linux-usb/msg76388.html)
-$ modprobe libcomposite
-$ mount none $CONFIGFS_HOME -t configfs
+::
+
+ $ modprobe libcomposite
+ $ mount none $CONFIGFS_HOME -t configfs
where CONFIGFS_HOME is the mount point for configfs
1. Creating the gadgets
-----------------------
-For each gadget to be created its corresponding directory must be created:
+For each gadget to be created its corresponding directory must be created::
-$ mkdir $CONFIGFS_HOME/usb_gadget/<gadget name>
+ $ mkdir $CONFIGFS_HOME/usb_gadget/<gadget name>
-e.g.:
+e.g.::
-$ mkdir $CONFIGFS_HOME/usb_gadget/g1
+ $ mkdir $CONFIGFS_HOME/usb_gadget/g1
-...
-...
-...
+ ...
+ ...
+ ...
-$ cd $CONFIGFS_HOME/usb_gadget/g1
+ $ cd $CONFIGFS_HOME/usb_gadget/g1
-Each gadget needs to have its vendor id <VID> and product id <PID> specified:
+Each gadget needs to have its vendor id <VID> and product id <PID> specified::
-$ echo <VID> > idVendor
-$ echo <PID> > idProduct
+ $ echo <VID> > idVendor
+ $ echo <PID> > idProduct
A gadget also needs its serial number, manufacturer and product strings.
In order to have a place to store them, a strings subdirectory must be created
-for each language, e.g.:
+for each language, e.g.::
-$ mkdir strings/0x409
+ $ mkdir strings/0x409
-Then the strings can be specified:
+Then the strings can be specified::
-$ echo <serial number> > strings/0x409/serialnumber
-$ echo <manufacturer> > strings/0x409/manufacturer
-$ echo <product> > strings/0x409/product
+ $ echo <serial number> > strings/0x409/serialnumber
+ $ echo <manufacturer> > strings/0x409/manufacturer
+ $ echo <product> > strings/0x409/product
2. Creating the configurations
------------------------------
$ mkdir configs/<name>.<number>
where <name> can be any string which is legal in a filesystem and the
-<number> is the configuration's number, e.g.:
+<number> is the configuration's number, e.g.::
-$ mkdir configs/c.1
+ $ mkdir configs/c.1
-...
-...
-...
+ ...
+ ...
+ ...
Each configuration also needs its strings, so a subdirectory must be created
-for each language, e.g.:
+for each language, e.g.::
-$ mkdir configs/c.1/strings/0x409
+ $ mkdir configs/c.1/strings/0x409
-Then the configuration string can be specified:
+Then the configuration string can be specified::
-$ echo <configuration> > configs/c.1/strings/0x409/configuration
+ $ echo <configuration> > configs/c.1/strings/0x409/configuration
-Some attributes can also be set for a configuration, e.g.:
+Some attributes can also be set for a configuration, e.g.::
-$ echo 120 > configs/c.1/MaxPower
+ $ echo 120 > configs/c.1/MaxPower
3. Creating the functions
-------------------------
The gadget will provide some functions, for each function its corresponding
-directory must be created:
+directory must be created::
-$ mkdir functions/<name>.<instance name>
+ $ mkdir functions/<name>.<instance name>
where <name> corresponds to one of allowed function names and instance name
-is an arbitrary string allowed in a filesystem, e.g.:
+is an arbitrary string allowed in a filesystem, e.g.::
-$ mkdir functions/ncm.usb0 # usb_f_ncm.ko gets loaded with request_module()
+ $ mkdir functions/ncm.usb0 # usb_f_ncm.ko gets loaded with request_module()
-...
-...
-...
+ ...
+ ...
+ ...
Each function provides its specific set of attributes, with either read-only
or read-write access. Where applicable they need to be written to as
configurations specified and a number of functions available. What remains
is specifying which function is available in which configuration (the same
function can be used in multiple configurations). This is achieved with
-creating symbolic links:
+creating symbolic links::
-$ ln -s functions/<name>.<instance name> configs/<name>.<number>
+ $ ln -s functions/<name>.<instance name> configs/<name>.<number>
-e.g.:
+e.g.::
-$ ln -s functions/ncm.usb0 configs/c.1
+ $ ln -s functions/ncm.usb0 configs/c.1
-...
-...
-...
+ ...
+ ...
+ ...
5. Enabling the gadget
----------------------
All the above steps serve the purpose of composing the gadget of
configurations and functions.
-An example directory structure might look like this:
-
-.
-./strings
-./strings/0x409
-./strings/0x409/serialnumber
-./strings/0x409/product
-./strings/0x409/manufacturer
-./configs
-./configs/c.1
-./configs/c.1/ncm.usb0 -> ../../../../usb_gadget/g1/functions/ncm.usb0
-./configs/c.1/strings
-./configs/c.1/strings/0x409
-./configs/c.1/strings/0x409/configuration
-./configs/c.1/bmAttributes
-./configs/c.1/MaxPower
-./functions
-./functions/ncm.usb0
-./functions/ncm.usb0/ifname
-./functions/ncm.usb0/qmult
-./functions/ncm.usb0/host_addr
-./functions/ncm.usb0/dev_addr
-./UDC
-./bcdUSB
-./bcdDevice
-./idProduct
-./idVendor
-./bMaxPacketSize0
-./bDeviceProtocol
-./bDeviceSubClass
-./bDeviceClass
+An example directory structure might look like this::
+
+ .
+ ./strings
+ ./strings/0x409
+ ./strings/0x409/serialnumber
+ ./strings/0x409/product
+ ./strings/0x409/manufacturer
+ ./configs
+ ./configs/c.1
+ ./configs/c.1/ncm.usb0 -> ../../../../usb_gadget/g1/functions/ncm.usb0
+ ./configs/c.1/strings
+ ./configs/c.1/strings/0x409
+ ./configs/c.1/strings/0x409/configuration
+ ./configs/c.1/bmAttributes
+ ./configs/c.1/MaxPower
+ ./functions
+ ./functions/ncm.usb0
+ ./functions/ncm.usb0/ifname
+ ./functions/ncm.usb0/qmult
+ ./functions/ncm.usb0/host_addr
+ ./functions/ncm.usb0/dev_addr
+ ./UDC
+ ./bcdUSB
+ ./bcdDevice
+ ./idProduct
+ ./idVendor
+ ./bMaxPacketSize0
+ ./bDeviceProtocol
+ ./bDeviceSubClass
+ ./bDeviceClass
Such a gadget must be finally enabled so that the USB host can enumerate it.
-In order to enable the gadget it must be bound to a UDC (USB Device Controller).
-$ echo <udc name> > UDC
+In order to enable the gadget it must be bound to a UDC (USB Device
+Controller)::
+
+ $ echo <udc name> > UDC
where <udc name> is one of those found in /sys/class/udc/*
-e.g.:
+e.g.::
-$ echo s3c-hsotg > UDC
+ $ echo s3c-hsotg > UDC
6. Disabling the gadget
-----------------------
-$ echo "" > UDC
+::
+
+ $ echo "" > UDC
7. Cleaning up
--------------
-Remove functions from configurations:
+Remove functions from configurations::
-$ rm configs/<config name>.<number>/<function>
+ $ rm configs/<config name>.<number>/<function>
where <config name>.<number> specify the configuration and <function> is
-a symlink to a function being removed from the configuration, e.g.:
+a symlink to a function being removed from the configuration, e.g.::
-$ rm configs/c.1/ncm.usb0
+ $ rm configs/c.1/ncm.usb0
-...
-...
-...
+ ...
+ ...
+ ...
-Remove strings directories in configurations
+Remove strings directories in configurations:
-$ rmdir configs/<config name>.<number>/strings/<lang>
+ $ rmdir configs/<config name>.<number>/strings/<lang>
-e.g.:
+e.g.::
-$ rmdir configs/c.1/strings/0x409
+ $ rmdir configs/c.1/strings/0x409
-...
-...
-...
+ ...
+ ...
+ ...
-and remove the configurations
+and remove the configurations::
-$ rmdir configs/<config name>.<number>
+ $ rmdir configs/<config name>.<number>
-e.g.:
+e.g.::
-rmdir configs/c.1
+ rmdir configs/c.1
-...
-...
-...
+ ...
+ ...
+ ...
-Remove functions (function modules are not unloaded, though)
+Remove functions (function modules are not unloaded, though):
-$ rmdir functions/<name>.<instance name>
+ $ rmdir functions/<name>.<instance name>
-e.g.:
+e.g.::
-$ rmdir functions/ncm.usb0
+ $ rmdir functions/ncm.usb0
-...
-...
-...
+ ...
+ ...
+ ...
-Remove strings directories in the gadget
+Remove strings directories in the gadget::
-$ rmdir strings/<lang>
+ $ rmdir strings/<lang>
-e.g.:
+e.g.::
-$ rmdir strings/0x409
+ $ rmdir strings/0x409
-and finally remove the gadget:
+and finally remove the gadget::
-$ cd ..
-$ rmdir <gadget name>
+ $ cd ..
+ $ rmdir <gadget name>
-e.g.:
+e.g.::
-$ rmdir g1
+ $ rmdir g1
larger structures. In the picture below there is a "cs" which contains
a config_item and an "sa" which contains a configfs_attribute.
-The filesystem view would be like this:
+The filesystem view would be like this::
-./
-./cs (directory)
- |
- +--sa (file)
- |
- .
- .
- .
+ ./
+ ./cs (directory)
+ |
+ +--sa (file)
+ |
+ .
+ .
+ .
Whenever a user reads/writes the "sa" file, a function is called
which accepts a struct config_item and a struct configfs_attribute.
from the buffer to the cs), but it is up to the implementer of the
two functions to decide what they actually do.
-typedef struct configured_structure cs;
-typedef struct specific_attribute sa;
-
- sa
- +----------------------------------+
- cs | (*show)(cs *, buffer); |
-+-----------------+ | (*store)(cs *, buffer, length); |
-| | | |
-| +-------------+ | | +------------------+ |
-| | struct |-|----|------>|struct | |
-| | config_item | | | |configfs_attribute| |
-| +-------------+ | | +------------------+ |
-| | +----------------------------------+
-| data to be set | .
-| | .
-+-----------------+ .
+::
+
+ typedef struct configured_structure cs;
+ typedef struct specific_attribute sa;
+
+ sa
+ +----------------------------------+
+ cs | (*show)(cs *, buffer); |
+ +-----------------+ | (*store)(cs *, buffer, length); |
+ | | | |
+ | +-------------+ | | +------------------+ |
+ | | struct |-|----|------>|struct | |
+ | | config_item | | | |configfs_attribute| |
+ | +-------------+ | | +------------------+ |
+ | | +----------------------------------+
+ | data to be set | .
+ | | .
+ +-----------------+ .
The file names are decided by the config item/group designer, while
the directories in general can be named at will. A group can have
a number of its default sub-groups created automatically.
For more information on configfs please see
-Documentation/filesystems/configfs/*.
+`Documentation/filesystems/configfs/*`.
The concepts described above translate to USB gadgets like this:
-
- Linux USB HID gadget driver
+===========================
+Linux USB HID gadget driver
+===========================
Introduction
+============
- The HID Gadget driver provides emulation of USB Human Interface
- Devices (HID). The basic HID handling is done in the kernel,
- and HID reports can be sent/received through I/O on the
- /dev/hidgX character devices.
+The HID Gadget driver provides emulation of USB Human Interface
+Devices (HID). The basic HID handling is done in the kernel,
+and HID reports can be sent/received through I/O on the
+/dev/hidgX character devices.
- For more details about HID, see the developer page on
- http://www.usb.org/developers/hidpage/
+For more details about HID, see the developer page on
+http://www.usb.org/developers/hidpage/
Configuration
+=============
- g_hid is a platform driver, so to use it you need to add
- struct platform_device(s) to your platform code defining the
- HID function descriptors you want to use - E.G. something
- like:
+g_hid is a platform driver, so to use it you need to add
+struct platform_device(s) to your platform code defining the
+HID function descriptors you want to use - E.G. something
+like::
-#include <linux/platform_device.h>
-#include <linux/usb/g_hid.h>
+ #include <linux/platform_device.h>
+ #include <linux/usb/g_hid.h>
-/* hid descriptor for a keyboard */
-static struct hidg_func_descriptor my_hid_data = {
+ /* hid descriptor for a keyboard */
+ static struct hidg_func_descriptor my_hid_data = {
.subclass = 0, /* No subclass */
.protocol = 1, /* Keyboard */
.report_length = 8,
0x81, 0x00, /* INPUT (Data,Ary,Abs) */
0xc0 /* END_COLLECTION */
}
-};
+ };
-static struct platform_device my_hid = {
+ static struct platform_device my_hid = {
.name = "hidg",
.id = 0,
.num_resources = 0,
.resource = 0,
.dev.platform_data = &my_hid_data,
-};
+ };
- You can add as many HID functions as you want, only limited by
- the amount of interrupt endpoints your gadget driver supports.
+You can add as many HID functions as you want, only limited by
+the amount of interrupt endpoints your gadget driver supports.
Configuration with configfs
+===========================
- Instead of adding fake platform devices and drivers in order to pass
- some data to the kernel, if HID is a part of a gadget composed with
- configfs the hidg_func_descriptor.report_desc is passed to the kernel
- by writing the appropriate stream of bytes to a configfs attribute.
+Instead of adding fake platform devices and drivers in order to pass
+some data to the kernel, if HID is a part of a gadget composed with
+configfs the hidg_func_descriptor.report_desc is passed to the kernel
+by writing the appropriate stream of bytes to a configfs attribute.
Send and receive HID reports
+============================
- HID reports can be sent/received using read/write on the
- /dev/hidgX character devices. See below for an example program
- to do this.
+HID reports can be sent/received using read/write on the
+/dev/hidgX character devices. See below for an example program
+to do this.
- hid_gadget_test is a small interactive program to test the HID
- gadget driver. To use, point it at a hidg device and set the
- device type (keyboard / mouse / joystick) - E.G.:
+hid_gadget_test is a small interactive program to test the HID
+gadget driver. To use, point it at a hidg device and set the
+device type (keyboard / mouse / joystick) - E.G.::
- # hid_gadget_test /dev/hidg0 keyboard
+ # hid_gadget_test /dev/hidg0 keyboard
- You are now in the prompt of hid_gadget_test. You can type any
- combination of options and values. Available options and
- values are listed at program start. In keyboard mode you can
- send up to six values.
+You are now in the prompt of hid_gadget_test. You can type any
+combination of options and values. Available options and
+values are listed at program start. In keyboard mode you can
+send up to six values.
- For example type: g i s t r --left-shift
+For example type: g i s t r --left-shift
- Hit return and the corresponding report will be sent by the
- HID gadget.
+Hit return and the corresponding report will be sent by the
+HID gadget.
- Another interesting example is the caps lock test. Type
- --caps-lock and hit return. A report is then sent by the
- gadget and you should receive the host answer, corresponding
- to the caps lock LED status.
+Another interesting example is the caps lock test. Type
+--caps-lock and hit return. A report is then sent by the
+gadget and you should receive the host answer, corresponding
+to the caps lock LED status::
- --caps-lock
- recv report:2
+ --caps-lock
+ recv report:2
- With this command:
+With this command::
- # hid_gadget_test /dev/hidg1 mouse
+ # hid_gadget_test /dev/hidg1 mouse
- You can test the mouse emulation. Values are two signed numbers.
+You can test the mouse emulation. Values are two signed numbers.
-Sample code
+Sample code::
-/* hid_gadget_test */
+ /* hid_gadget_test */
-#include <pthread.h>
-#include <string.h>
-#include <stdio.h>
-#include <ctype.h>
-#include <fcntl.h>
-#include <errno.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
+ #include <pthread.h>
+ #include <string.h>
+ #include <stdio.h>
+ #include <ctype.h>
+ #include <fcntl.h>
+ #include <errno.h>
+ #include <stdio.h>
+ #include <stdlib.h>
+ #include <unistd.h>
-#define BUF_LEN 512
+ #define BUF_LEN 512
-struct options {
+ struct options {
const char *opt;
unsigned char val;
-};
+ };
-static struct options kmod[] = {
+ static struct options kmod[] = {
{.opt = "--left-ctrl", .val = 0x01},
{.opt = "--right-ctrl", .val = 0x10},
{.opt = "--left-shift", .val = 0x02},
{.opt = "--left-meta", .val = 0x08},
{.opt = "--right-meta", .val = 0x80},
{.opt = NULL}
-};
+ };
-static struct options kval[] = {
+ static struct options kval[] = {
{.opt = "--return", .val = 0x28},
{.opt = "--esc", .val = 0x29},
{.opt = "--bckspc", .val = 0x2a},
{.opt = "--up", .val = 0x52},
{.opt = "--num-lock", .val = 0x53},
{.opt = NULL}
-};
+ };
-int keyboard_fill_report(char report[8], char buf[BUF_LEN], int *hold)
-{
+ int keyboard_fill_report(char report[8], char buf[BUF_LEN], int *hold)
+ {
char *tok = strtok(buf, " ");
int key = 0;
int i = 0;
fprintf(stderr, "unknown option: %s\n", tok);
}
return 8;
-}
+ }
-static struct options mmod[] = {
+ static struct options mmod[] = {
{.opt = "--b1", .val = 0x01},
{.opt = "--b2", .val = 0x02},
{.opt = "--b3", .val = 0x04},
{.opt = NULL}
-};
+ };
-int mouse_fill_report(char report[8], char buf[BUF_LEN], int *hold)
-{
+ int mouse_fill_report(char report[8], char buf[BUF_LEN], int *hold)
+ {
char *tok = strtok(buf, " ");
int mvt = 0;
int i = 0;
fprintf(stderr, "unknown option: %s\n", tok);
}
return 3;
-}
+ }
-static struct options jmod[] = {
+ static struct options jmod[] = {
{.opt = "--b1", .val = 0x10},
{.opt = "--b2", .val = 0x20},
{.opt = "--b3", .val = 0x40},
{.opt = "--hat4", .val = 0x03},
{.opt = "--hatneutral", .val = 0x04},
{.opt = NULL}
-};
+ };
-int joystick_fill_report(char report[8], char buf[BUF_LEN], int *hold)
-{
+ int joystick_fill_report(char report[8], char buf[BUF_LEN], int *hold)
+ {
char *tok = strtok(buf, " ");
int mvt = 0;
int i = 0;
fprintf(stderr, "unknown option: %s\n", tok);
}
return 4;
-}
+ }
-void print_options(char c)
-{
+ void print_options(char c)
+ {
int i = 0;
if (c == 'k') {
" three signed numbers\n"
"--quit to close\n");
}
-}
+ }
-int main(int argc, const char *argv[])
-{
+ int main(int argc, const char *argv[])
+ {
const char *filename = NULL;
int fd = 0;
char buf[BUF_LEN];
close(fd);
return 0;
-}
+ }
- -*- org -*-
+==============================
+Multifunction Composite Gadget
+==============================
-* Overview
+Overview
+========
The Multifunction Composite Gadget (or g_multi) is a composite gadget
that makes extensive use of the composite framework to provide
Please note that if you use non-standard configuration (that is enable
CDC ECM) you may need to change vendor and/or product ID.
-* Host drivers
+Host drivers
+============
To make use of the gadget one needs to make it work on host side --
without that there's no hope of achieving anything with the gadget.
As one might expect, things one need to do very from system to system.
-** Linux host drivers
+Linux host drivers
+------------------
Since the gadget uses standard composite framework and appears as such
to Linux host it does not need any additional drivers on Linux host
configuration being the first one. Linux host will use the second
configuration with CDC ECM which should work better under Linux.
-** Windows host drivers
+Windows host drivers
+--------------------
For the gadget to work under Windows two conditions have to be met:
-*** Detecting as composite gadget
+Detecting as composite gadget
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
First of all, Windows need to detect the gadget as an USB composite
gadget which on its own have some conditions[4]. If they are met,
configuration so a dual RNDIS and CDC ECM gadget won't work unless you
create a proper INF -- and of course, if you do submit it!
-*** Installing drivers for each function
+Installing drivers for each function
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The other, trickier thing is making Windows install drivers for each
individual function.
Things are harder with RDNIS and CDC ACM.
-**** RNDIS
+RNDIS
+.....
To make Windows select RNDIS drivers for the first function in the
gadget, one needs to use the [[file:linux.inf]] file provided with this
unless you are trying to develop your own gadget in which case watch
out for this bug.
-**** CDC ACM
+CDC ACM
+.......
Similarly, [[file:linux-cdc-acm.inf]] is provided for CDC ACM.
-**** Customising the gadget
+Customising the gadget
+......................
If you intend to hack the g_multi gadget be advised that rearranging
functions will obviously change interface numbers for each of the
some drivers information (changing USB port may sometimes help plus
you might try using USBDeview[8] to remove the phantom device).
-**** INF testing
+INF testing
+...........
Provided INF files have been tested on Windows XP SP3, Windows Vista
and Windows 7, all 32-bit versions. It should work on 64-bit versions
as well. It most likely won't work on Windows prior to Windows XP
SP2.
-** Other systems
+Other systems
+-------------
At this moment, drivers for any other systems have not been tested.
Knowing how MacOS is based on BSD and BSD is an Open Source it is
Any testing and drivers *are* *welcome*!
-* Authors
+Authors
+=======
This document has been written by Michal Nazarewicz
([[mailto:mina86@mina86.com]]). INF files have been hacked with
template[9], Microchip's CDC ACM INF file and David Brownell's
([[mailto:dbrownell@users.sourceforge.net]]) original INF files.
-* Footnotes
+Footnotes
+=========
[1] Remote Network Driver Interface Specification,
[[http://msdn.microsoft.com/en-us/library/ee484414.aspx]].
+===============================
+Linux USB Printer Gadget Driver
+===============================
- Linux USB Printer Gadget Driver
- 06/04/2007
+06/04/2007
- Copyright (C) 2007 Craig W. Nadler <craig@nadler.us>
+Copyright (C) 2007 Craig W. Nadler <craig@nadler.us>
-GENERAL
+General
=======
This driver may be used if you are writing printer firmware using Linux as
-HOWTO USE THIS DRIVER
+Howto Use This Driver
=====================
To load the USB device controller driver and the printer gadget driver. The
-following example uses the Netchip 2280 USB device controller driver:
+following example uses the Netchip 2280 USB device controller driver::
-modprobe net2280
-modprobe g_printer
+ modprobe net2280
+ modprobe g_printer
The follow command line parameter can be used when loading the printer gadget
(ex: modprobe g_printer idVendor=0x0525 idProduct=0xa4a8 ):
-idVendor - This is the Vendor ID used in the device descriptor. The default is
+idVendor
+ This is the Vendor ID used in the device descriptor. The default is
the Netchip vendor id 0x0525. YOU MUST CHANGE TO YOUR OWN VENDOR ID
BEFORE RELEASING A PRODUCT. If you plan to release a product and don't
already have a Vendor ID please see www.usb.org for details on how to
get one.
-idProduct - This is the Product ID used in the device descriptor. The default
+idProduct
+ This is the Product ID used in the device descriptor. The default
is 0xa4a8, you should change this to an ID that's not used by any of
your other USB products if you have any. It would be a good idea to
start numbering your products starting with say 0x0001.
-bcdDevice - This is the version number of your product. It would be a good idea
+bcdDevice
+ This is the version number of your product. It would be a good idea
to put your firmware version here.
-iManufacturer - A string containing the name of the Vendor.
+iManufacturer
+ A string containing the name of the Vendor.
-iProduct - A string containing the Product Name.
+iProduct
+ A string containing the Product Name.
-iSerialNum - A string containing the Serial Number. This should be changed for
+iSerialNum
+ A string containing the Serial Number. This should be changed for
each unit of your product.
-iPNPstring - The PNP ID string used for this printer. You will want to set
+iPNPstring
+ The PNP ID string used for this printer. You will want to set
either on the command line or hard code the PNP ID string used for
your printer product.
-qlen - The number of 8k buffers to use per endpoint. The default is 10, you
+qlen
+ The number of 8k buffers to use per endpoint. The default is 10, you
should tune this for your product. You may also want to tune the
size of each buffer for your product.
-USING THE EXAMPLE CODE
+Using The Example Code
======================
This example code talks to stdout, instead of a print engine.
To compile the test code below:
1) save it to a file called prn_example.c
-2) compile the code with the follow command:
+2) compile the code with the follow command::
+
gcc prn_example.c -o prn_example
-To read printer data from the host to stdout:
+To read printer data from the host to stdout::
# prn_example -read_data
-To write printer data from a file (data_file) to the host:
+To write printer data from a file (data_file) to the host::
# cat data_file | prn_example -write_data
-To get the current printer status for the gadget driver:
+To get the current printer status for the gadget driver:::
# prn_example -get_status
Printer OK
-To set printer to Selected/On-line:
+To set printer to Selected/On-line::
# prn_example -selected
-To set printer to Not Selected/Off-line:
+To set printer to Not Selected/Off-line::
# prn_example -not_selected
-To set paper status to paper out:
+To set paper status to paper out::
# prn_example -paper_out
-To set paper status to paper loaded:
+To set paper status to paper loaded::
# prn_example -paper_loaded
-To set error status to printer OK:
+To set error status to printer OK::
# prn_example -no_error
-To set error status to ERROR:
+To set error status to ERROR::
# prn_example -error
-EXAMPLE CODE
+Example Code
============
+::
+
-#include <stdio.h>
-#include <stdlib.h>
-#include <fcntl.h>
-#include <linux/poll.h>
-#include <sys/ioctl.h>
-#include <linux/usb/g_printer.h>
+ #include <stdio.h>
+ #include <stdlib.h>
+ #include <fcntl.h>
+ #include <linux/poll.h>
+ #include <sys/ioctl.h>
+ #include <linux/usb/g_printer.h>
-#define PRINTER_FILE "/dev/g_printer"
-#define BUF_SIZE 512
+ #define PRINTER_FILE "/dev/g_printer"
+ #define BUF_SIZE 512
-/*
- * 'usage()' - Show program usage.
- */
+ /*
+ * 'usage()' - Show program usage.
+ */
-static void
-usage(const char *option) /* I - Option string or NULL */
-{
+ static void
+ usage(const char *option) /* I - Option string or NULL */
+ {
if (option) {
fprintf(stderr,"prn_example: Unknown option \"%s\"!\n",
option);
fputs("\n\n", stderr);
exit(1);
-}
+ }
-static int
-read_printer_data()
-{
+ static int
+ read_printer_data()
+ {
struct pollfd fd[1];
/* Open device file for printer gadget. */
close(fd[0].fd);
return 0;
-}
+ }
-static int
-write_printer_data()
-{
+ static int
+ write_printer_data()
+ {
struct pollfd fd[1];
/* Open device file for printer gadget. */
close(fd[0].fd);
return 0;
-}
+ }
-static int
-read_NB_printer_data()
-{
+ static int
+ read_NB_printer_data()
+ {
int fd;
static char buf[BUF_SIZE];
int bytes_read;
close(fd);
return 0;
-}
+ }
-static int
-get_printer_status()
-{
+ static int
+ get_printer_status()
+ {
int retval;
int fd;
close(fd);
return(retval);
-}
+ }
-static int
-set_printer_status(unsigned char buf, int clear_printer_status_bit)
-{
+ static int
+ set_printer_status(unsigned char buf, int clear_printer_status_bit)
+ {
int retval;
int fd;
close(fd);
return 0;
-}
+ }
-static int
-display_printer_status()
-{
+ static int
+ display_printer_status()
+ {
char printer_status;
printer_status = get_printer_status();
}
return(0);
-}
+ }
-int
-main(int argc, char *argv[])
-{
+ int
+ main(int argc, char *argv[])
+ {
int i; /* Looping var */
int retval = 0;
}
exit(retval);
-}
+ }
+===============================
+Linux Gadget Serial Driver v2.0
+===============================
- Linux Gadget Serial Driver v2.0
- 11/20/2004
- (updated 8-May-2008 for v2.3)
+11/20/2004
+
+(updated 8-May-2008 for v2.3)
License and Disclaimer
with a USB development card.
The gadget serial driver talks over USB to either a CDC ACM driver
-or a generic USB serial driver running on a host PC.
+or a generic USB serial driver running on a host PC::
Host
--------------------------------------
modules.
Then you must load the gadget serial driver. To load it as an
-ACM device (recommended for interoperability), do this:
+ACM device (recommended for interoperability), do this::
modprobe g_serial
-To load it as a vendor specific bulk in/out device, do this:
+To load it as a vendor specific bulk in/out device, do this::
modprobe g_serial use_acm=0
Your system should use mdev (from busybox) or udev to make the
device nodes. After this gadget driver has been set up you should
-then see a /dev/ttyGS0 node:
+then see a /dev/ttyGS0 node::
# ls -l /dev/ttyGS0 | cat
crw-rw---- 1 root root 253, 0 May 8 14:10 /dev/ttyGS0
Once the gadget serial driver is loaded and the USB device connected
to the Linux host with a USB cable, the host system should recognize
-the gadget serial device. For example, the command
+the gadget serial device. For example, the command::
cat /sys/kernel/debug/usb/devices
-should show something like this:
-
-T: Bus=01 Lev=01 Prnt=01 Port=01 Cnt=02 Dev#= 5 Spd=480 MxCh= 0
-D: Ver= 2.00 Cls=02(comm.) Sub=00 Prot=00 MxPS=64 #Cfgs= 1
-P: Vendor=0525 ProdID=a4a7 Rev= 2.01
-S: Manufacturer=Linux 2.6.8.1 with net2280
-S: Product=Gadget Serial
-S: SerialNumber=0
-C:* #Ifs= 2 Cfg#= 2 Atr=c0 MxPwr= 2mA
-I: If#= 0 Alt= 0 #EPs= 1 Cls=02(comm.) Sub=02 Prot=01 Driver=acm
-E: Ad=83(I) Atr=03(Int.) MxPS= 8 Ivl=32ms
-I: If#= 1 Alt= 0 #EPs= 2 Cls=0a(data ) Sub=00 Prot=00 Driver=acm
-E: Ad=81(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms
-E: Ad=02(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms
+should show something like this:::
+
+ T: Bus=01 Lev=01 Prnt=01 Port=01 Cnt=02 Dev#= 5 Spd=480 MxCh= 0
+ D: Ver= 2.00 Cls=02(comm.) Sub=00 Prot=00 MxPS=64 #Cfgs= 1
+ P: Vendor=0525 ProdID=a4a7 Rev= 2.01
+ S: Manufacturer=Linux 2.6.8.1 with net2280
+ S: Product=Gadget Serial
+ S: SerialNumber=0
+ C:* #Ifs= 2 Cfg#= 2 Atr=c0 MxPwr= 2mA
+ I: If#= 0 Alt= 0 #EPs= 1 Cls=02(comm.) Sub=02 Prot=01 Driver=acm
+ E: Ad=83(I) Atr=03(Int.) MxPS= 8 Ivl=32ms
+ I: If#= 1 Alt= 0 #EPs= 2 Cls=0a(data ) Sub=00 Prot=00 Driver=acm
+ E: Ad=81(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms
+ E: Ad=02(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms
If the host side Linux system is configured properly, the ACM driver
should be loaded automatically. The command "lsmod" should show the
Once the gadget serial driver is loaded and the USB device connected
to the Linux host with a USB cable, the host system should recognize
-the gadget serial device. For example, the command
+the gadget serial device. For example, the command::
cat /sys/kernel/debug/usb/devices
-should show something like this:
+should show something like this:::
-T: Bus=01 Lev=01 Prnt=01 Port=01 Cnt=02 Dev#= 6 Spd=480 MxCh= 0
-D: Ver= 2.00 Cls=ff(vend.) Sub=00 Prot=00 MxPS=64 #Cfgs= 1
-P: Vendor=0525 ProdID=a4a6 Rev= 2.01
-S: Manufacturer=Linux 2.6.8.1 with net2280
-S: Product=Gadget Serial
-S: SerialNumber=0
-C:* #Ifs= 1 Cfg#= 1 Atr=c0 MxPwr= 2mA
-I: If#= 0 Alt= 0 #EPs= 2 Cls=0a(data ) Sub=00 Prot=00 Driver=serial
-E: Ad=81(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms
-E: Ad=02(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms
+ T: Bus=01 Lev=01 Prnt=01 Port=01 Cnt=02 Dev#= 6 Spd=480 MxCh= 0
+ D: Ver= 2.00 Cls=ff(vend.) Sub=00 Prot=00 MxPS=64 #Cfgs= 1
+ P: Vendor=0525 ProdID=a4a6 Rev= 2.01
+ S: Manufacturer=Linux 2.6.8.1 with net2280
+ S: Product=Gadget Serial
+ S: SerialNumber=0
+ C:* #Ifs= 1 Cfg#= 1 Atr=c0 MxPwr= 2mA
+ I: If#= 0 Alt= 0 #EPs= 2 Cls=0a(data ) Sub=00 Prot=00 Driver=serial
+ E: Ad=81(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms
+ E: Ad=02(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms
You must load the usbserial driver and explicitly set its parameters
-to configure it to recognize the gadget serial device, like this:
+to configure it to recognize the gadget serial device, like this::
echo 0x0525 0xA4A6 >/sys/bus/usb-serial/drivers/generic/new_id
-The legacy way is to use module parameters:
+The legacy way is to use module parameters::
modprobe usbserial vendor=0x0525 product=0xA4A6
+=============================
Infinity Usb Unlimited Readme
------------------------------
+=============================
Hi all,
-How to tune the reader speed ?
+How to tune the reader speed?
+=============================
A few parameters can be used at load time
To use parameters, just unload the module if it is
In case of prebuilt module, use the command
insmod iuu_phoenix param=value.
- Example:
+ Example::
- modprobe iuu_phoenix clockmode=3
+ modprobe iuu_phoenix clockmode=3
The parameters are:
- parm: clockmode:1=3Mhz579,2=3Mhz680,3=6Mhz (int)
- parm: boost:overclock boost percent 100 to 500 (int)
- parm: cdmode:Card detect mode 0=none, 1=CD, 2=!CD, 3=DSR, 4=!DSR, 5=CTS, 6=!CTS, 7=RING, 8=!RING (int)
- parm: xmas:xmas color enabled or not (bool)
- parm: debug:Debug enabled or not (bool)
+clockmode:
+ 1=3Mhz579,2=3Mhz680,3=6Mhz (int)
+boost:
+ overclock boost percent 100 to 500 (int)
+cdmode:
+ Card detect mode
+ 0=none, 1=CD, 2=!CD, 3=DSR, 4=!DSR, 5=CTS, 6=!CTS, 7=RING, 8=!RING (int)
+xmas:
+ xmas color enabled or not (bool)
+debug:
+ Debug enabled or not (bool)
- clockmode will provide 3 different base settings commonly adopted by
different software:
- 1. 3Mhz579
+
+ 1. 3Mhz579
2. 3Mhz680
3. 6Mhz
- boost provide a way to overclock the reader ( my favorite :-) )
- For example to have best performance than a simple clockmode=3, try this:
+ For example to have best performance than a simple clockmode=3, try this::
modprobe boost=195
- debug will produce a lot of debugging messages...
- Last notes:
+Last notes
+==========
Don't worry about the serial settings, the serial emulation
is an abstraction, so use any speed or parity setting will
-* Overview
+=========================
+Mass Storage Gadget (MSG)
+=========================
+
+Overview
+========
Mass Storage Gadget (or MSG) acts as a USB Mass Storage device,
appearing to the host as a disk or a CD-ROM drive. It supports
(which is no longer included in Linux). It will talk only briefly
about how to use MSF within composite gadgets.
-* Module parameters
+Module parameters
+=================
The mass storage gadget accepts the following mass storage specific
module parameters:
- iProduct -- USB Product string (string)
- iSerialNumber -- SerialNumber string (sting)
-* sysfs entries
+sysfs entries
+=============
For each logical unit, the gadget creates a directory in the sysfs
hierarchy. Inside of it the following three files are created:
Other then those, as usual, the values of module parameters can be
read from /sys/module/g_mass_storage/parameters/* files.
-* Other gadgets using mass storage function
+Other gadgets using mass storage function
+=========================================
The Mass Storage Gadget uses the Mass Storage Function to handle
mass storage protocol. As a composite function, MSF may be used by
may take a look at mass_storage.c, acm_ms.c and multi.c (sorted by
complexity).
-* Relation to file storage gadget
+Relation to file storage gadget
+===============================
The Mass Storage Function and thus the Mass Storage Gadget has been
based on the File Storage Gadget. The difference between the two is
+=============================
USB 7-Segment Numeric Display
+=============================
+
Manufactured by Delcom Engineering
Device Information
------------
By default, the driver assumes the display is only 6 characters
The mode for 6 characters is:
+
MSB 0x06; LSB 0x3f
+
For the 8 character display:
+
MSB 0x08; LSB 0xff
+
The device can accept "text" either in raw, hex, or ascii textmode.
raw controls each segment manually,
hex expects a value between 0-15 per character,
To set multiple decimals points sum up each power.
For example, to set the 0th and 3rd decimal place
echo 1001 > /sys/bus/usb/.../decimals
-
-
-CHANGES
+================
+mtouchusb driver
+================
+
+Changes
+=======
- 0.3 - Created based off of scanner & INSTALL from the original touchscreen
driver on freecode (http://freecode.com/projects/3mtouchscreendriver)
- Amended for linux-2.4.18, then 2.4.19
- 0.5 - Complete rewrite using Linux Input in 2.6.3
- Unfortunately no calibration support at this time
+ Unfortunately no calibration support at this time
- 1.4 - Multiple changes to support the EXII 5000UC and house cleaning
- Changed reset from standard USB dev reset to vendor reset
- Changed data sent to host from compensated to raw coordinates
- Eliminated vendor/product module params
- Performed multiple successful tests with an EXII-5010UC
+ Changed reset from standard USB dev reset to vendor reset
+ Changed data sent to host from compensated to raw coordinates
+ Eliminated vendor/product module params
+ Performed multiple successful tests with an EXII-5010UC
+
+Supported Hardware
+==================
-SUPPORTED HARDWARE:
+::
All controllers have the Vendor: 0x0596 & Product: 0x0001
USB Capacitive - Black Case EXII-5030UC
USB Capacitive - No Case EXII-5050UC
-DRIVER NOTES:
+Driver Notes
+============
-Installation is simple, you only need to add Linux Input, Linux USB, and the
+Installation is simple, you only need to add Linux Input, Linux USB, and the
driver to the kernel. The driver can also be optionally built as a module.
This driver appears to be one of possible 2 Linux USB Input Touchscreen
the raw touch data. This is the same for the old and new capacitive USB
controllers.
-Perhaps at some point an abstract function will be placed into evdev so
-generic functions like calibrations, resets, and vendor information can be
+Perhaps at some point an abstract function will be placed into evdev so
+generic functions like calibrations, resets, and vendor information can be
requested from the userspace (And the drivers would handle the vendor specific
tasks).
-TODO:
+TODO
+====
Implement a control urb again to handle requests to and from the device
such as calibration, etc once/if it becomes available.
-DISCLAIMER:
+Disclaimer
+==========
-I am not a MicroTouch/3M employee, nor have I ever been. 3M does not support
+I am not a MicroTouch/3M employee, nor have I ever been. 3M does not support
this driver! If you want touch drivers only supported within X, please go to:
http://www.3m.com/3MTouchSystems/
-THANKS:
+Thanks
+======
A huge thank you to 3M Touch Systems for the EXII-5010UC controllers for
testing!
+====
+OHCI
+====
+
23-Aug-2002
The "ohci-hcd" driver is a USB Host Controller Driver (HCD) that is derived
- David Brownell
<dbrownell@users.sourceforge.net>
-
+============
+Diamonds Rio
+============
+
Copyright (C) 1999, 2000 Bruce Tenison
+
Portions Copyright (C) 1999, 2000 David Nelson
+
Thanks to David Nelson for guidance and the usage of the scanner.txt
and scanner.c files to model our driver and this informative file.
Mar. 2, 2000
-CHANGES
+Changes
+=======
- Initial Revision
-OVERVIEW
+Overview
+========
This README will address issues regarding how to configure the kernel
-to access a RIO 500 mp3 player.
+to access a RIO 500 mp3 player.
Before I explain how to use this to access the Rio500 please be warned:
-W A R N I N G:
---------------
+.. warning::
-Please note that this software is still under development. The authors
-are in no way responsible for any damage that may occur, no matter how
-inconsequential.
+ Please note that this software is still under development. The authors
+ are in no way responsible for any damage that may occur, no matter how
+ inconsequential.
It seems that the Rio has a problem when sending .mp3 with low batteries.
I suggest when the batteries are low and you want to transfer stuff that you
replace it with a fresh one. In my case, what happened is I lost two 16kb
blocks (they are no longer usable to store information to it). But I don't
-know if that's normal or not; it could simply be a problem with the flash
+know if that's normal or not; it could simply be a problem with the flash
memory.
-In an extreme case, I left my Rio playing overnight and the batteries wore
-down to nothing and appear to have corrupted the flash memory. My RIO
-needed to be replaced as a result. Diamond tech support is aware of the
-problem. Do NOT allow your batteries to wear down to nothing before
-changing them. It appears RIO 500 firmware does not handle low battery
-power well at all.
+In an extreme case, I left my Rio playing overnight and the batteries wore
+down to nothing and appear to have corrupted the flash memory. My RIO
+needed to be replaced as a result. Diamond tech support is aware of the
+problem. Do NOT allow your batteries to wear down to nothing before
+changing them. It appears RIO 500 firmware does not handle low battery
+power well at all.
-On systems with OHCI controllers, the kernel OHCI code appears to have
-power on problems with some chipsets. If you are having problems
-connecting to your RIO 500, try turning it on first and then plugging it
-into the USB cable.
+On systems with OHCI controllers, the kernel OHCI code appears to have
+power on problems with some chipsets. If you are having problems
+connecting to your RIO 500, try turning it on first and then plugging it
+into the USB cable.
-Contact information:
---------------------
+Contact Information
+-------------------
The main page for the project is hosted at sourceforge.net in the following
URL: <http://rio500.sourceforge.net>. You can also go to the project's
sourceforge home page at: <http://sourceforge.net/projects/rio500/>.
There is also a mailing list: rio500-users@lists.sourceforge.net
-Authors:
+Authors
-------
-Most of the code was written by Cesar Miquel <miquel@df.uba.ar>. Keith
+Most of the code was written by Cesar Miquel <miquel@df.uba.ar>. Keith
Clayton <kclayton@jps.net> is incharge of the PPC port and making sure
things work there. Bruce Tenison <btenison@dibbs.net> is adding support
for .fon files and also does testing. The program will mostly sure be
re-written and Pete Ikusz along with the rest will re-design it. I would
-also like to thank Tri Nguyen <tmn_3022000@hotmail.com> who provided use
+also like to thank Tri Nguyen <tmn_3022000@hotmail.com> who provided use
with some important information regarding the communication with the Rio.
-ADDITIONAL INFORMATION and Userspace tools
+Additional Information and userspace tools
-http://rio500.sourceforge.net/
+ http://rio500.sourceforge.net/
-REQUIREMENTS
+Requirements
+============
A host with a USB port. Ideally, either a UHCI (Intel) or OHCI
(Compaq and others) hardware port should work.
'lspci' which is only needed to determine the type of USB hardware
available in your machine.
-CONFIGURATION
+Configuration
Using `lspci -v`, determine the type of USB hardware available.
- If you see something like:
+ If you see something like::
USB Controller: ......
Flags: .....
Then you have a UHCI based controller.
- If you see something like:
+ If you see something like::
USB Controller: .....
Flags: ....
(you may need to execute `depmod -a` to update the module
dependencies).
-Add a device for the USB rio500:
- `mknod /dev/usb/rio500 c 180 64`
+Add a device for the USB rio500::
+
+ mknod /dev/usb/rio500 c 180 64
Set appropriate permissions for /dev/usb/rio500 (don't forget about
group and world permissions). Both read and write permissions are
Load the appropriate modules (if compiled as modules):
- OHCI:
+ OHCI::
+
modprobe usbcore
modprobe usb-ohci
modprobe rio500
- UHCI:
+ UHCI::
+
modprobe usbcore
modprobe usb-uhci (or uhci)
modprobe rio500
That's it. The Rio500 Utils at: http://rio500.sourceforge.net should
be able to access the rio500.
-BUGS
+Bugs
+====
If you encounter any problems feel free to drop me an email.
Bruce Tenison
btenison@dibbs.net
-
-usb-help.txt
+==============
+USB references
+==============
+
2008-Mar-7
For USB help other than the readme files that are located in
-Documentation/usb/*, see the following:
+`Documentation/usb/*`, see the following:
-Linux-USB project: http://www.linux-usb.org
- mirrors at http://usb.in.tum.de/linux-usb/
- and http://it.linux-usb.org
-Linux USB Guide: http://linux-usb.sourceforge.net
-Linux-USB device overview (working devices and drivers):
- http://www.qbik.ch/usb/devices/
+- Linux-USB project: http://www.linux-usb.org
+ mirrors at http://usb.in.tum.de/linux-usb/
+ and http://it.linux-usb.org
+- Linux USB Guide: http://linux-usb.sourceforge.net
+- Linux-USB device overview (working devices and drivers):
+ http://www.qbik.ch/usb/devices/
The Linux-USB mailing list is at linux-usb@vger.kernel.org
-
-###
-INTRODUCTION
+==========
+USB serial
+==========
+
+Introduction
+============
The USB serial driver currently supports a number of different USB to
serial converter products, as well as some devices that use a serial
the different devices.
-CONFIGURATION
+Configuration
+=============
Currently the driver can handle up to 256 different serial interfaces at
- one time.
+ one time.
The major number that the driver uses is 188 so to use the driver,
- create the following nodes:
+ create the following nodes::
+
mknod /dev/ttyUSB0 c 188 0
mknod /dev/ttyUSB1 c 188 1
mknod /dev/ttyUSB2 c 188 2
When the device is connected and recognized by the driver, the driver
will print to the system log, which node(s) the device has been bound
to.
-
-SPECIFIC DEVICES SUPPORTED
+
+Specific Devices Supported
+==========================
ConnectTech WhiteHEAT 4 port converter
+--------------------------------------
ConnectTech has been very forthcoming with information about their
device, including providing a unit to test with.
HandSpring Visor, Palm USB, and Clié USB driver
+-----------------------------------------------
This driver works with all HandSpring USB, Palm USB, and Sony Clié USB
devices.
This goes against the current documentation for pilot-xfer and other
packages, but is the only way that it will work due to the hardware
in the device.
-
+
When the device is connected, try talking to it on the second port
(this is usually /dev/ttyUSB1 if you do not have any other usb-serial
devices in the system.) The system log should tell you which port is
try resetting the device, first a hot reset, and then a cold reset if
necessary. Some devices need this before they can talk to the USB port
properly.
-
+
Devices that are not compiled into the kernel can be specified with module
parameters. e.g. modprobe visor vendor=0x54c product=0x66
-
+
There is a webpage and mailing lists for this portion of the driver at:
http://sourceforge.net/projects/usbvisor/
PocketPC PDA Driver
+-------------------
This driver can be used to connect to Compaq iPAQ, HP Jornada, Casio EM500
and other PDAs running Windows CE 3.0 or PocketPC 2002 using a USB
be used to flash the ROM, as well as the microP code.. so much for needing
Toshiba's $350 serial cable for flashing!! :D
NOTE: This has NOT been tested. Use at your own risk.
-
+
For any questions or problems with the driver, please contact Ganesh
Varadarajan <ganesh@veritas.com>
Keyspan PDA Serial Adapter
+--------------------------
Single port DB-9 serial adapter, pushed as a PDA adapter for iMacs (mostly
sold in Macintosh catalogs, comes in a translucent white/green dongle).
This driver also works for the Xircom/Entrega single port serial adapter.
Current status:
+
Things that work:
- basic input/output (tested with 'cu')
- blocking write when serial line can't keep up
- changing baud rates (up to 115200)
- getting/setting modem control pins (TIOCM{GET,SET,BIS,BIC})
- sending break (although duration looks suspect)
+ - basic input/output (tested with 'cu')
+ - blocking write when serial line can't keep up
+ - changing baud rates (up to 115200)
+ - getting/setting modem control pins (TIOCM{GET,SET,BIS,BIC})
+ - sending break (although duration looks suspect)
+
Things that don't:
- device strings (as logged by kernel) have trailing binary garbage
- device ID isn't right, might collide with other Keyspan products
- changing baud rates ought to flush tx/rx to avoid mangled half characters
+ - device strings (as logged by kernel) have trailing binary garbage
+ - device ID isn't right, might collide with other Keyspan products
+ - changing baud rates ought to flush tx/rx to avoid mangled half characters
+
Big Things on the todo list:
- parity, 7 vs 8 bits per char, 1 or 2 stop bits
- HW flow control
- not all of the standard USB descriptors are handled: Get_Status, Set_Feature
- O_NONBLOCK, select()
+ - parity, 7 vs 8 bits per char, 1 or 2 stop bits
+ - HW flow control
+ - not all of the standard USB descriptors are handled:
+ Get_Status, Set_Feature, O_NONBLOCK, select()
For any questions or problems with this driver, please contact Brian
- Warner at warner@lothar.com
+ Warner at warner@lothar.com
Keyspan USA-series Serial Adapters
+----------------------------------
- Single, Dual and Quad port adapters - driver uses Keyspan supplied
+ Single, Dual and Quad port adapters - driver uses Keyspan supplied
firmware and is being developed with their support.
-
+
Current status:
+
The USA-18X, USA-28X, USA-19, USA-19W and USA-49W are supported and
have been pretty thoroughly tested at various baud rates with 8-N-1
character settings. Other character lengths and parity setups are
The USA-28 isn't yet supported though doing so should be pretty
straightforward. Contact the maintainer if you require this
functionality.
-
+
More information is available at:
+
http://www.carnationsoftware.com/carnation/Keyspan.html
-
+
For any questions or problems with this driver, please contact Hugh
Blemings at hugh@misc.nu
FTDI Single Port Serial Driver
+------------------------------
This is a single port DB-25 serial adapter.
Devices supported include:
- -TripNav TN-200 USB GPS
- -Navis Engineering Bureau CH-4711 USB GPS
+
+ - TripNav TN-200 USB GPS
+ - Navis Engineering Bureau CH-4711 USB GPS
For any questions or problems with this driver, please contact Bill Ryder.
ZyXEL omni.net lcd plus ISDN TA
+-------------------------------
This is an ISDN TA. Please report both successes and troubles to
azummo@towertech.it
Cypress M8 CY4601 Family Serial Driver
+--------------------------------------
This driver was in most part developed by Neil "koyama" Whelchel. It
has been improved since that previous form to support dynamic serial
part stable and has been tested on an smp machine. (dual p2)
Chipsets supported under CY4601 family:
-
+
CY7C63723, CY7C63742, CY7C63743, CY7C64013
Devices supported:
- -DeLorme's USB Earthmate GPS (SiRF Star II lp arch)
- -Cypress HID->COM RS232 adapter
-
- Note: Cypress Semiconductor claims no affiliation with the
+ - DeLorme's USB Earthmate GPS (SiRF Star II lp arch)
+ - Cypress HID->COM RS232 adapter
+
+ Note:
+ Cypress Semiconductor claims no affiliation with the
hid->com device.
- Most devices using chipsets under the CY4601 family should
+ Most devices using chipsets under the CY4601 family should
work with the driver. As long as they stay true to the CY4601
usbserial specification.
upon start init to this setting. usbserial core provides the rest
of the termios settings, along with some custom termios so that the
output is in proper format and parsable.
-
- The device can be put into sirf mode by issuing NMEA command:
+
+ The device can be put into sirf mode by issuing NMEA command::
+
$PSRF100,<protocol>,<baud>,<databits>,<stopbits>,<parity>*CHECKSUM
$PSRF100,0,9600,8,1,0*0C
If you have any questions, problems, patches, feature requests, etc. you can
contact me here via email:
+
dignome@gmail.com
+
(your problems/patches can alternately be submitted to usb-devel)
Digi AccelePort Driver
+----------------------
This driver supports the Digi AccelePort USB 2 and 4 devices, 2 port
(plus a parallel port) and 4 port USB serial converters. The driver
Belkin USB Serial Adapter F5U103
+--------------------------------
Single port DB-9/PS-2 serial adapter from Belkin with firmware by eTEK Labs.
The Peracom single port serial adapter also works with this driver, as
well as the GoHubs adapter.
Current status:
+
The following have been tested and work:
- Baud rate 300-230400
- Data bits 5-8
- Stop bits 1-2
- Parity N,E,O,M,S
- Handshake None, Software (XON/XOFF), Hardware (CTSRTS,CTSDTR)*
- Break Set and clear
- Line control Input/Output query and control **
-
- * Hardware input flow control is only enabled for firmware
+
+ - Baud rate 300-230400
+ - Data bits 5-8
+ - Stop bits 1-2
+ - Parity N,E,O,M,S
+ - Handshake None, Software (XON/XOFF), Hardware (CTSRTS,CTSDTR) [1]_
+ - Break Set and clear
+ - Line control Input/Output query and control [2]_
+
+ .. [1]
+ Hardware input flow control is only enabled for firmware
levels above 2.06. Read source code comments describing Belkin
firmware errata. Hardware output flow control is working for all
firmware versions.
- ** Queries of inputs (CTS,DSR,CD,RI) show the last
+
+ .. [2]
+ Queries of inputs (CTS,DSR,CD,RI) show the last
reported state. Queries of outputs (DTR,RTS) show the last
requested state and may not reflect current state as set by
automatic hardware flow control.
TO DO List:
- -- Add true modem control line query capability. Currently tracks the
- states reported by the interrupt and the states requested.
- -- Add error reporting back to application for UART error conditions.
- -- Add support for flush ioctls.
- -- Add everything else that is missing :)
+ - Add true modem control line query capability. Currently tracks the
+ states reported by the interrupt and the states requested.
+ - Add error reporting back to application for UART error conditions.
+ - Add support for flush ioctls.
+ - Add everything else that is missing :)
For any questions or problems with this driver, please contact William
Greathouse at wgreathouse@smva.com
Empeg empeg-car Mark I/II Driver
+--------------------------------
This is an experimental driver to provide connectivity support for the
client synchronization tools for an Empeg empeg-car mp3 player.
MCT USB Single Port Serial Adapter U232
+---------------------------------------
This driver is for the MCT USB-RS232 Converter (25 pin, Model No.
U232-P25) from Magic Control Technology Corp. (there is also a 9 pin
Inside Out Networks Edgeport Driver
+-----------------------------------
This driver supports all devices made by Inside Out Networks, specifically
the following models:
- Edgeport/4
- Rapidport/4
- Edgeport/4t
- Edgeport/2
- Edgeport/4i
- Edgeport/2i
- Edgeport/421
- Edgeport/21
- Edgeport/8
- Edgeport/8 Dual
- Edgeport/2D8
- Edgeport/4D8
- Edgeport/8i
- Edgeport/2 DIN
- Edgeport/4 DIN
- Edgeport/16 Dual
+
+ - Edgeport/4
+ - Rapidport/4
+ - Edgeport/4t
+ - Edgeport/2
+ - Edgeport/4i
+ - Edgeport/2i
+ - Edgeport/421
+ - Edgeport/21
+ - Edgeport/8
+ - Edgeport/8 Dual
+ - Edgeport/2D8
+ - Edgeport/4D8
+ - Edgeport/8i
+ - Edgeport/2 DIN
+ - Edgeport/4 DIN
+ - Edgeport/16 Dual
For any questions or problems with this driver, please contact Greg
Kroah-Hartman at greg@kroah.com
REINER SCT cyberJack pinpad/e-com USB chipcard reader
-
+-----------------------------------------------------
+
Interface to ISO 7816 compatible contactbased chipcards, e.g. GSM SIMs.
-
+
Current status:
+
This is the kernel part of the driver for this USB card reader.
There is also a user part for a CT-API driver available. A site
for downloading is TBA. For now, you can request it from the
Prolific PL2303 Driver
+----------------------
This driver supports any device that has the PL2303 chip from Prolific
in it. This includes a number of single port USB to serial converters,
For any questions or problems with this driver, please contact Greg
Kroah-Hartman at greg@kroah.com
-
+
KL5KUSB105 chipset / PalmConnect USB single-port adapter
-
+--------------------------------------------------------
+
Current status:
+
The driver was put together by looking at the usb bus transactions
done by Palm's driver under Windows, so a lot of functionality is
still missing. Notably, serial ioctls are sometimes faked or not yet
are supported, but handshaking (software or hardware) is not, which is
why it is wise to cut down on the rate used is wise for large
transfers until this is settled.
-
+
See http://www.uuhaus.de/linux/palmconnect.html for up-to-date
information on this driver.
Winchiphead CH341 Driver
+------------------------
This driver is for the Winchiphead CH341 USB-RS232 Converter. This chip
also implements an IEEE 1284 parallel port, I2C and SPI, but that is not
supported by the driver. The protocol was analyzed from the behaviour
of the Windows driver, no datasheet is available at present.
+
The manufacturer's website: http://www.winchiphead.com/.
+
For any questions or problems with this driver, please contact
frank@kingswood-consulting.co.uk.
Moschip MCS7720, MCS7715 driver
+-------------------------------
These chips are present in devices sold by various manufacturers, such as Syba
and Cables Unlimited. There may be others. The 7720 provides two serial
don't have one of these devices, so I can't say for sure.
Generic Serial driver
+---------------------
If your device is not one of the above listed devices, compatible with
the above models, you can try out the "generic" interface. This
interface does not provide any type of control messages sent to the
device, and does not support any kind of device flow control. All that
is required of your device is that it has at least one bulk in endpoint,
- or one bulk out endpoint.
+ or one bulk out endpoint.
+
+ To enable the generic driver to recognize your device, provide::
- To enable the generic driver to recognize your device, provide
echo <vid> <pid> >/sys/bus/usb-serial/drivers/generic/new_id
+
where the <vid> and <pid> is replaced with the hex representation of your
device's vendor id and product id.
If the driver is compiled as a module you can also provide one id when
- loading the module
+ loading the module::
+
insmod usbserial vendor=0x#### product=0x####
This driver has been successfully used to connect to the NetChip USB
Kroah-Hartman at greg@kroah.com
-CONTACT:
+Contact
+=======
If anyone has any problems using these drivers, with any of the above
specified products, please contact the specific driver's author listed
+===============
+USB/IP protocol
+===============
+
PRELIMINARY DRAFT, MAY CONTAIN MISTAKES!
28 Jun 2011
the OP_REP_DEVLIST packet which lists the exported USB devices. Finally the
TCP/IP connection is closed.
+::
+
virtual host controller usb host
"client" "server"
(imports USB devices) (exports USB devices)
USBIP_CMD_UNLINK to unlink a previously submitted URB. The answers of the
server may be USBIP_RET_SUBMIT and USBIP_RET_UNLINK respectively.
+::
+
virtual host controller usb host
"client" "server"
(imports USB devices) (exports USB devices)
byte (MSB) is stored at the lowest address.
-OP_REQ_DEVLIST: Retrieve the list of exported USB devices.
+OP_REQ_DEVLIST:
+ Retrieve the list of exported USB devices.
- Offset | Length | Value | Description
------------+--------+------------+---------------------------------------------------
- 0 | 2 | 0x0100 | Binary-coded decimal USBIP version number: v1.0.0
------------+--------+------------+---------------------------------------------------
- 2 | 2 | 0x8005 | Command code: Retrieve the list of exported USB
- | | | devices.
------------+--------+------------+---------------------------------------------------
- 4 | 4 | 0x00000000 | Status: unused, shall be set to 0
++-----------+--------+------------+---------------------------------------------------+
+| Offset | Length | Value | Description |
++===========+========+============+===================================================+
+| 0 | 2 | 0x0100 | Binary-coded decimal USBIP version number: v1.0.0 |
++-----------+--------+------------+---------------------------------------------------+
+| 2 | 2 | 0x8005 | Command code: Retrieve the list of exported USB |
+| | | | devices. |
++-----------+--------+------------+---------------------------------------------------+
+| 4 | 4 | 0x00000000 | Status: unused, shall be set to 0 |
++-----------+--------+------------+---------------------------------------------------+
-OP_REP_DEVLIST: Reply with the list of exported USB devices.
+OP_REP_DEVLIST:
+ Reply with the list of exported USB devices.
- Offset | Length | Value | Description
------------+--------+------------+---------------------------------------------------
- 0 | 2 | 0x0100 | Binary-coded decimal USBIP version number: v1.0.0.
------------+--------+------------+---------------------------------------------------
- 2 | 2 | 0x0005 | Reply code: The list of exported USB devices.
------------+--------+------------+---------------------------------------------------
- 4 | 4 | 0x00000000 | Status: 0 for OK
------------+--------+------------+---------------------------------------------------
- 8 | 4 | n | Number of exported devices: 0 means no exported
- | | | devices.
------------+--------+------------+---------------------------------------------------
- 0x0C | | | From now on the exported n devices are described,
- | | | if any. If no devices are exported the message
- | | | ends with the previous "number of exported
- | | | devices" field.
------------+--------+------------+---------------------------------------------------
- | 256 | | path: Path of the device on the host exporting the
- | | | USB device, string closed with zero byte, e.g.
- | | | "/sys/devices/pci0000:00/0000:00:1d.1/usb3/3-2"
- | | | The unused bytes shall be filled with zero
- | | | bytes.
------------+--------+------------+---------------------------------------------------
- 0x10C | 32 | | busid: Bus ID of the exported device, string
- | | | closed with zero byte, e.g. "3-2". The unused
- | | | bytes shall be filled with zero bytes.
------------+--------+------------+---------------------------------------------------
- 0x12C | 4 | | busnum
------------+--------+------------+---------------------------------------------------
- 0x130 | 4 | | devnum
------------+--------+------------+---------------------------------------------------
- 0x134 | 4 | | speed
------------+--------+------------+---------------------------------------------------
- 0x138 | 2 | | idVendor
------------+--------+------------+---------------------------------------------------
- 0x13A | 2 | | idProduct
------------+--------+------------+---------------------------------------------------
- 0x13C | 2 | | bcdDevice
------------+--------+------------+---------------------------------------------------
- 0x13E | 1 | | bDeviceClass
------------+--------+------------+---------------------------------------------------
- 0x13F | 1 | | bDeviceSubClass
------------+--------+------------+---------------------------------------------------
- 0x140 | 1 | | bDeviceProtocol
------------+--------+------------+---------------------------------------------------
- 0x141 | 1 | | bConfigurationValue
------------+--------+------------+---------------------------------------------------
- 0x142 | 1 | | bNumConfigurations
------------+--------+------------+---------------------------------------------------
- 0x143 | 1 | | bNumInterfaces
------------+--------+------------+---------------------------------------------------
- 0x144 | | m_0 | From now on each interface is described, all
- | | | together bNumInterfaces times, with the
- | | | the following 4 fields:
------------+--------+------------+---------------------------------------------------
- | 1 | | bInterfaceClass
------------+--------+------------+---------------------------------------------------
- 0x145 | 1 | | bInterfaceSubClass
------------+--------+------------+---------------------------------------------------
- 0x146 | 1 | | bInterfaceProtocol
------------+--------+------------+---------------------------------------------------
- 0x147 | 1 | | padding byte for alignment, shall be set to zero
------------+--------+------------+---------------------------------------------------
- 0xC + | | | The second exported USB device starts at i=1
- i*0x138 + | | | with the busid field.
- m_(i-1)*4 | | |
++-----------+--------+------------+---------------------------------------------------+
+| Offset | Length | Value | Description |
++===========+========+============+===================================================+
+| 0 | 2 | 0x0100 | Binary-coded decimal USBIP version number: v1.0.0.|
++-----------+--------+------------+---------------------------------------------------+
+| 2 | 2 | 0x0005 | Reply code: The list of exported USB devices. |
++-----------+--------+------------+---------------------------------------------------+
+| 4 | 4 | 0x00000000 | Status: 0 for OK |
++-----------+--------+------------+---------------------------------------------------+
+| 8 | 4 | n | Number of exported devices: 0 means no exported |
+| | | | devices. |
++-----------+--------+------------+---------------------------------------------------+
+| 0x0C | | | From now on the exported n devices are described, |
+| | | | if any. If no devices are exported the message |
+| | | | ends with the previous "number of exported |
+| | | | devices" field. |
++-----------+--------+------------+---------------------------------------------------+
+| | 256 | | path: Path of the device on the host exporting the|
+| | | | USB device, string closed with zero byte, e.g. |
+| | | | "/sys/devices/pci0000:00/0000:00:1d.1/usb3/3-2" |
+| | | | The unused bytes shall be filled with zero |
+| | | | bytes. |
++-----------+--------+------------+---------------------------------------------------+
+| 0x10C | 32 | | busid: Bus ID of the exported device, string |
+| | | | closed with zero byte, e.g. "3-2". The unused |
+| | | | bytes shall be filled with zero bytes. |
++-----------+--------+------------+---------------------------------------------------+
+| 0x12C | 4 | | busnum |
++-----------+--------+------------+---------------------------------------------------+
+| 0x130 | 4 | | devnum |
++-----------+--------+------------+---------------------------------------------------+
+| 0x134 | 4 | | speed |
++-----------+--------+------------+---------------------------------------------------+
+| 0x138 | 2 | | idVendor |
++-----------+--------+------------+---------------------------------------------------+
+| 0x13A | 2 | | idProduct |
++-----------+--------+------------+---------------------------------------------------+
+| 0x13C | 2 | | bcdDevice |
++-----------+--------+------------+---------------------------------------------------+
+| 0x13E | 1 | | bDeviceClass |
++-----------+--------+------------+---------------------------------------------------+
+| 0x13F | 1 | | bDeviceSubClass |
++-----------+--------+------------+---------------------------------------------------+
+| 0x140 | 1 | | bDeviceProtocol |
++-----------+--------+------------+---------------------------------------------------+
+| 0x141 | 1 | | bConfigurationValue |
++-----------+--------+------------+---------------------------------------------------+
+| 0x142 | 1 | | bNumConfigurations |
++-----------+--------+------------+---------------------------------------------------+
+| 0x143 | 1 | | bNumInterfaces |
++-----------+--------+------------+---------------------------------------------------+
+| 0x144 | | m_0 | From now on each interface is described, all |
+| | | | together bNumInterfaces times, with the |
+| | | | the following 4 fields: |
++-----------+--------+------------+---------------------------------------------------+
+| | 1 | | bInterfaceClass |
++-----------+--------+------------+---------------------------------------------------+
+| 0x145 | 1 | | bInterfaceSubClass |
++-----------+--------+------------+---------------------------------------------------+
+| 0x146 | 1 | | bInterfaceProtocol |
++-----------+--------+------------+---------------------------------------------------+
+| 0x147 | 1 | | padding byte for alignment, shall be set to zero |
++-----------+--------+------------+---------------------------------------------------+
+| 0xC + | | | The second exported USB device starts at i=1 |
+| i*0x138 + | | | with the busid field. |
+| m_(i-1)*4 | | | |
++-----------+--------+------------+---------------------------------------------------+
-OP_REQ_IMPORT: Request to import (attach) a remote USB device.
+OP_REQ_IMPORT:
+ Request to import (attach) a remote USB device.
- Offset | Length | Value | Description
------------+--------+------------+---------------------------------------------------
- 0 | 2 | 0x0100 | Binary-coded decimal USBIP version number: v1.0.0
------------+--------+------------+---------------------------------------------------
- 2 | 2 | 0x8003 | Command code: import a remote USB device.
------------+--------+------------+---------------------------------------------------
- 4 | 4 | 0x00000000 | Status: unused, shall be set to 0
------------+--------+------------+---------------------------------------------------
- 8 | 32 | | busid: the busid of the exported device on the
- | | | remote host. The possible values are taken
- | | | from the message field OP_REP_DEVLIST.busid.
- | | | A string closed with zero, the unused bytes
- | | | shall be filled with zeros.
------------+--------+------------+---------------------------------------------------
++-----------+--------+------------+---------------------------------------------------+
+| Offset | Length | Value | Description |
++===========+========+============+===================================================+
+| 0 | 2 | 0x0100 | Binary-coded decimal USBIP version number: v1.0.0 |
++-----------+--------+------------+---------------------------------------------------+
+| 2 | 2 | 0x8003 | Command code: import a remote USB device. |
++-----------+--------+------------+---------------------------------------------------+
+| 4 | 4 | 0x00000000 | Status: unused, shall be set to 0 |
++-----------+--------+------------+---------------------------------------------------+
+| 8 | 32 | | busid: the busid of the exported device on the |
+| | | | remote host. The possible values are taken |
+| | | | from the message field OP_REP_DEVLIST.busid. |
+| | | | A string closed with zero, the unused bytes |
+| | | | shall be filled with zeros. |
++-----------+--------+------------+---------------------------------------------------+
-OP_REP_IMPORT: Reply to import (attach) a remote USB device.
+OP_REP_IMPORT:
+ Reply to import (attach) a remote USB device.
- Offset | Length | Value | Description
------------+--------+------------+---------------------------------------------------
- 0 | 2 | 0x0100 | Binary-coded decimal USBIP version number: v1.0.0
------------+--------+------------+---------------------------------------------------
- 2 | 2 | 0x0003 | Reply code: Reply to import.
------------+--------+------------+---------------------------------------------------
- 4 | 4 | 0x00000000 | Status: 0 for OK
- | | | 1 for error
------------+--------+------------+---------------------------------------------------
- 8 | | | From now on comes the details of the imported
- | | | device, if the previous status field was OK (0),
- | | | otherwise the reply ends with the status field.
------------+--------+------------+---------------------------------------------------
- | 256 | | path: Path of the device on the host exporting the
- | | | USB device, string closed with zero byte, e.g.
- | | | "/sys/devices/pci0000:00/0000:00:1d.1/usb3/3-2"
- | | | The unused bytes shall be filled with zero
- | | | bytes.
------------+--------+------------+---------------------------------------------------
- 0x108 | 32 | | busid: Bus ID of the exported device, string
- | | | closed with zero byte, e.g. "3-2". The unused
- | | | bytes shall be filled with zero bytes.
------------+--------+------------+---------------------------------------------------
- 0x128 | 4 | | busnum
------------+--------+------------+---------------------------------------------------
- 0x12C | 4 | | devnum
------------+--------+------------+---------------------------------------------------
- 0x130 | 4 | | speed
------------+--------+------------+---------------------------------------------------
- 0x134 | 2 | | idVendor
------------+--------+------------+---------------------------------------------------
- 0x136 | 2 | | idProduct
------------+--------+------------+---------------------------------------------------
- 0x138 | 2 | | bcdDevice
------------+--------+------------+---------------------------------------------------
- 0x139 | 1 | | bDeviceClass
------------+--------+------------+---------------------------------------------------
- 0x13A | 1 | | bDeviceSubClass
------------+--------+------------+---------------------------------------------------
- 0x13B | 1 | | bDeviceProtocol
------------+--------+------------+---------------------------------------------------
- 0x13C | 1 | | bConfigurationValue
------------+--------+------------+---------------------------------------------------
- 0x13D | 1 | | bNumConfigurations
------------+--------+------------+---------------------------------------------------
- 0x13E | 1 | | bNumInterfaces
++-----------+--------+------------+---------------------------------------------------+
+| Offset | Length | Value | Description |
++===========+========+============+===================================================+
+| 0 | 2 | 0x0100 | Binary-coded decimal USBIP version number: v1.0.0 |
++-----------+--------+------------+---------------------------------------------------+
+| 2 | 2 | 0x0003 | Reply code: Reply to import. |
++-----------+--------+------------+---------------------------------------------------+
+| 4 | 4 | 0x00000000 | Status: |
+| | | | |
+| | | | - 0 for OK |
+| | | | - 1 for error |
++-----------+--------+------------+---------------------------------------------------+
+| 8 | | | From now on comes the details of the imported |
+| | | | device, if the previous status field was OK (0), |
+| | | | otherwise the reply ends with the status field. |
++-----------+--------+------------+---------------------------------------------------+
+| | 256 | | path: Path of the device on the host exporting the|
+| | | | USB device, string closed with zero byte, e.g. |
+| | | | "/sys/devices/pci0000:00/0000:00:1d.1/usb3/3-2" |
+| | | | The unused bytes shall be filled with zero |
+| | | | bytes. |
++-----------+--------+------------+---------------------------------------------------+
+| 0x108 | 32 | | busid: Bus ID of the exported device, string |
+| | | | closed with zero byte, e.g. "3-2". The unused |
+| | | | bytes shall be filled with zero bytes. |
++-----------+--------+------------+---------------------------------------------------+
+| 0x128 | 4 | | busnum |
++-----------+--------+------------+---------------------------------------------------+
+| 0x12C | 4 | | devnum |
++-----------+--------+------------+---------------------------------------------------+
+| 0x130 | 4 | | speed |
++-----------+--------+------------+---------------------------------------------------+
+| 0x134 | 2 | | idVendor |
++-----------+--------+------------+---------------------------------------------------+
+| 0x136 | 2 | | idProduct |
++-----------+--------+------------+---------------------------------------------------+
+| 0x138 | 2 | | bcdDevice |
++-----------+--------+------------+---------------------------------------------------+
+| 0x139 | 1 | | bDeviceClass |
++-----------+--------+------------+---------------------------------------------------+
+| 0x13A | 1 | | bDeviceSubClass |
++-----------+--------+------------+---------------------------------------------------+
+| 0x13B | 1 | | bDeviceProtocol |
++-----------+--------+------------+---------------------------------------------------+
+| 0x13C | 1 | | bConfigurationValue |
++-----------+--------+------------+---------------------------------------------------+
+| 0x13D | 1 | | bNumConfigurations |
++-----------+--------+------------+---------------------------------------------------+
+| 0x13E | 1 | | bNumInterfaces |
++-----------+--------+------------+---------------------------------------------------+
-USBIP_CMD_SUBMIT: Submit an URB
+USBIP_CMD_SUBMIT:
+ Submit an URB
- Offset | Length | Value | Description
------------+--------+------------+---------------------------------------------------
- 0 | 4 | 0x00000001 | command: Submit an URB
------------+--------+------------+---------------------------------------------------
- 4 | 4 | | seqnum: the sequence number of the URB to submit
------------+--------+------------+---------------------------------------------------
- 8 | 4 | | devid
------------+--------+------------+---------------------------------------------------
- 0xC | 4 | | direction: 0: USBIP_DIR_OUT
- | | | 1: USBIP_DIR_IN
------------+--------+------------+---------------------------------------------------
- 0x10 | 4 | | ep: endpoint number, possible values are: 0...15
------------+--------+------------+---------------------------------------------------
- 0x14 | 4 | | transfer_flags: possible values depend on the
- | | | URB transfer type, see below
------------+--------+------------+---------------------------------------------------
- 0x18 | 4 | | transfer_buffer_length
------------+--------+------------+---------------------------------------------------
- 0x1C | 4 | | start_frame: specify the selected frame to
- | | | transmit an ISO frame, ignored if URB_ISO_ASAP
- | | | is specified at transfer_flags
------------+--------+------------+---------------------------------------------------
- 0x20 | 4 | | number_of_packets: number of ISO packets
------------+--------+------------+---------------------------------------------------
- 0x24 | 4 | | interval: maximum time for the request on the
- | | | server-side host controller
------------+--------+------------+---------------------------------------------------
- 0x28 | 8 | | setup: data bytes for USB setup, filled with
- | | | zeros if not used
------------+--------+------------+---------------------------------------------------
- 0x30 | | | URB data. For ISO transfers the padding between
- | | | each ISO packets is not transmitted.
++-----------+--------+------------+---------------------------------------------------+
+| Offset | Length | Value | Description |
++===========+========+============+===================================================+
+| 0 | 4 | 0x00000001 | command: Submit an URB |
++-----------+--------+------------+---------------------------------------------------+
+| 4 | 4 | | seqnum: the sequence number of the URB to submit |
++-----------+--------+------------+---------------------------------------------------+
+| 8 | 4 | | devid |
++-----------+--------+------------+---------------------------------------------------+
+| 0xC | 4 | | direction: |
+| | | | |
+| | | | - 0: USBIP_DIR_OUT |
+| | | | - 1: USBIP_DIR_IN |
++-----------+--------+------------+---------------------------------------------------+
+| 0x10 | 4 | | ep: endpoint number, possible values are: 0...15 |
++-----------+--------+------------+---------------------------------------------------+
+| 0x14 | 4 | | transfer_flags: possible values depend on the |
+| | | | URB transfer type, see below |
++-----------+--------+------------+---------------------------------------------------+
+| 0x18 | 4 | | transfer_buffer_length |
++-----------+--------+------------+---------------------------------------------------+
+| 0x1C | 4 | | start_frame: specify the selected frame to |
+| | | | transmit an ISO frame, ignored if URB_ISO_ASAP |
+| | | | is specified at transfer_flags |
++-----------+--------+------------+---------------------------------------------------+
+| 0x20 | 4 | | number_of_packets: number of ISO packets |
++-----------+--------+------------+---------------------------------------------------+
+| 0x24 | 4 | | interval: maximum time for the request on the |
+| | | | server-side host controller |
++-----------+--------+------------+---------------------------------------------------+
+| 0x28 | 8 | | setup: data bytes for USB setup, filled with |
+| | | | zeros if not used |
++-----------+--------+------------+---------------------------------------------------+
+| 0x30 | | | URB data. For ISO transfers the padding between |
+| | | | each ISO packets is not transmitted. |
++-----------+--------+------------+---------------------------------------------------+
- Allowed transfer_flags | value | control | interrupt | bulk | isochronous
- -------------------------+------------+---------+-----------+----------+-------------
- URB_SHORT_NOT_OK | 0x00000001 | only in | only in | only in | no
- URB_ISO_ASAP | 0x00000002 | no | no | no | yes
- URB_NO_TRANSFER_DMA_MAP | 0x00000004 | yes | yes | yes | yes
- URB_ZERO_PACKET | 0x00000040 | no | no | only out | no
- URB_NO_INTERRUPT | 0x00000080 | yes | yes | yes | yes
- URB_FREE_BUFFER | 0x00000100 | yes | yes | yes | yes
- URB_DIR_MASK | 0x00000200 | yes | yes | yes | yes
+ +-------------------------+------------+---------+-----------+----------+-------------+
+ | Allowed transfer_flags | value | control | interrupt | bulk | isochronous |
+ +=========================+============+=========+===========+==========+=============+
+ | URB_SHORT_NOT_OK | 0x00000001 | only in | only in | only in | no |
+ +-------------------------+------------+---------+-----------+----------+-------------+
+ | URB_ISO_ASAP | 0x00000002 | no | no | no | yes |
+ +-------------------------+------------+---------+-----------+----------+-------------+
+ | URB_NO_TRANSFER_DMA_MAP | 0x00000004 | yes | yes | yes | yes |
+ +-------------------------+------------+---------+-----------+----------+-------------+
+ | URB_ZERO_PACKET | 0x00000040 | no | no | only out | no |
+ +-------------------------+------------+---------+-----------+----------+-------------+
+ | URB_NO_INTERRUPT | 0x00000080 | yes | yes | yes | yes |
+ +-------------------------+------------+---------+-----------+----------+-------------+
+ | URB_FREE_BUFFER | 0x00000100 | yes | yes | yes | yes |
+ +-------------------------+------------+---------+-----------+----------+-------------+
+ | URB_DIR_MASK | 0x00000200 | yes | yes | yes | yes |
+ +-------------------------+------------+---------+-----------+----------+-------------+
-USBIP_RET_SUBMIT: Reply for submitting an URB
+USBIP_RET_SUBMIT:
+ Reply for submitting an URB
- Offset | Length | Value | Description
------------+--------+------------+---------------------------------------------------
- 0 | 4 | 0x00000003 | command
------------+--------+------------+---------------------------------------------------
- 4 | 4 | | seqnum: URB sequence number
------------+--------+------------+---------------------------------------------------
- 8 | 4 | | devid
------------+--------+------------+---------------------------------------------------
- 0xC | 4 | | direction: 0: USBIP_DIR_OUT
- | | | 1: USBIP_DIR_IN
------------+--------+------------+---------------------------------------------------
- 0x10 | 4 | | ep: endpoint number
------------+--------+------------+---------------------------------------------------
- 0x14 | 4 | | status: zero for successful URB transaction,
- | | | otherwise some kind of error happened.
------------+--------+------------+---------------------------------------------------
- 0x18 | 4 | n | actual_length: number of URB data bytes
------------+--------+------------+---------------------------------------------------
- 0x1C | 4 | | start_frame: for an ISO frame the actually
- | | | selected frame for transmit.
------------+--------+------------+---------------------------------------------------
- 0x20 | 4 | | number_of_packets
------------+--------+------------+---------------------------------------------------
- 0x24 | 4 | | error_count
------------+--------+------------+---------------------------------------------------
- 0x28 | 8 | | setup: data bytes for USB setup, filled with
- | | | zeros if not used
------------+--------+------------+---------------------------------------------------
- 0x30 | n | | URB data bytes. For ISO transfers the padding
- | | | between each ISO packets is not transmitted.
++-----------+--------+------------+---------------------------------------------------+
+| Offset | Length | Value | Description |
++===========+========+============+===================================================+
+| 0 | 4 | 0x00000003 | command |
++-----------+--------+------------+---------------------------------------------------+
+| 4 | 4 | | seqnum: URB sequence number |
++-----------+--------+------------+---------------------------------------------------+
+| 8 | 4 | | devid |
++-----------+--------+------------+---------------------------------------------------+
+| 0xC | 4 | | direction: |
+| | | | |
+| | | | - 0: USBIP_DIR_OUT |
+| | | | - 1: USBIP_DIR_IN |
++-----------+--------+------------+---------------------------------------------------+
+| 0x10 | 4 | | ep: endpoint number |
++-----------+--------+------------+---------------------------------------------------+
+| 0x14 | 4 | | status: zero for successful URB transaction, |
+| | | | otherwise some kind of error happened. |
++-----------+--------+------------+---------------------------------------------------+
+| 0x18 | 4 | n | actual_length: number of URB data bytes |
++-----------+--------+------------+---------------------------------------------------+
+| 0x1C | 4 | | start_frame: for an ISO frame the actually |
+| | | | selected frame for transmit. |
++-----------+--------+------------+---------------------------------------------------+
+| 0x20 | 4 | | number_of_packets |
++-----------+--------+------------+---------------------------------------------------+
+| 0x24 | 4 | | error_count |
++-----------+--------+------------+---------------------------------------------------+
+| 0x28 | 8 | | setup: data bytes for USB setup, filled with |
+| | | | zeros if not used |
++-----------+--------+------------+---------------------------------------------------+
+| 0x30 | n | | URB data bytes. For ISO transfers the padding |
+| | | | between each ISO packets is not transmitted. |
++-----------+--------+------------+---------------------------------------------------+
-USBIP_CMD_UNLINK: Unlink an URB
+USBIP_CMD_UNLINK:
+ Unlink an URB
- Offset | Length | Value | Description
------------+--------+------------+---------------------------------------------------
- 0 | 4 | 0x00000002 | command: URB unlink command
------------+--------+------------+---------------------------------------------------
- 4 | 4 | | seqnum: URB sequence number to unlink: FIXME: is this so?
------------+--------+------------+---------------------------------------------------
- 8 | 4 | | devid
------------+--------+------------+---------------------------------------------------
- 0xC | 4 | | direction: 0: USBIP_DIR_OUT
- | | | 1: USBIP_DIR_IN
------------+--------+------------+---------------------------------------------------
- 0x10 | 4 | | ep: endpoint number: zero
------------+--------+------------+---------------------------------------------------
- 0x14 | 4 | | seqnum: the URB sequence number given previously
- | | | at USBIP_CMD_SUBMIT.seqnum field
------------+--------+------------+---------------------------------------------------
- 0x30 | n | | URB data bytes. For ISO transfers the padding
- | | | between each ISO packets is not transmitted.
++-----------+--------+------------+---------------------------------------------------+
+| Offset | Length | Value | Description |
++===========+========+============+===================================================+
+| 0 | 4 | 0x00000002 | command: URB unlink command |
++-----------+--------+------------+---------------------------------------------------+
+| 4 | 4 | | seqnum: URB sequence number to unlink: |
+| | | | |
+| | | | FIXME: |
+| | | | is this so? |
++-----------+--------+------------+---------------------------------------------------+
+| 8 | 4 | | devid |
++-----------+--------+------------+---------------------------------------------------+
+| 0xC | 4 | | direction: |
+| | | | |
+| | | | - 0: USBIP_DIR_OUT |
+| | | | - 1: USBIP_DIR_IN |
++-----------+--------+------------+---------------------------------------------------+
+| 0x10 | 4 | | ep: endpoint number: zero |
++-----------+--------+------------+---------------------------------------------------+
+| 0x14 | 4 | | seqnum: the URB sequence number given previously |
+| | | | at USBIP_CMD_SUBMIT.seqnum field |
++-----------+--------+------------+---------------------------------------------------+
+| 0x30 | n | | URB data bytes. For ISO transfers the padding |
+| | | | between each ISO packets is not transmitted. |
++-----------+--------+------------+---------------------------------------------------+
-USBIP_RET_UNLINK: Reply for URB unlink
+USBIP_RET_UNLINK:
+ Reply for URB unlink
- Offset | Length | Value | Description
------------+--------+------------+---------------------------------------------------
- 0 | 4 | 0x00000004 | command: reply for the URB unlink command
------------+--------+------------+---------------------------------------------------
- 4 | 4 | | seqnum: the unlinked URB sequence number
------------+--------+------------+---------------------------------------------------
- 8 | 4 | | devid
------------+--------+------------+---------------------------------------------------
- 0xC | 4 | | direction: 0: USBIP_DIR_OUT
- | | | 1: USBIP_DIR_IN
------------+--------+------------+---------------------------------------------------
- 0x10 | 4 | | ep: endpoint number
------------+--------+------------+---------------------------------------------------
- 0x14 | 4 | | status: This is the value contained in the
- | | | urb->status in the URB completition handler.
- | | | FIXME: a better explanation needed.
------------+--------+------------+---------------------------------------------------
- 0x30 | n | | URB data bytes. For ISO transfers the padding
- | | | between each ISO packets is not transmitted.
++-----------+--------+------------+---------------------------------------------------+
+| Offset | Length | Value | Description |
++===========+========+============+===================================================+
+| 0 | 4 | 0x00000004 | command: reply for the URB unlink command |
++-----------+--------+------------+---------------------------------------------------+
+| 4 | 4 | | seqnum: the unlinked URB sequence number |
++-----------+--------+------------+---------------------------------------------------+
+| 8 | 4 | | devid |
++-----------+--------+------------+---------------------------------------------------+
+| 0xC | 4 | | direction: |
+| | | | |
+| | | | - 0: USBIP_DIR_OUT |
+| | | | - 1: USBIP_DIR_IN |
++-----------+--------+------------+---------------------------------------------------+
+| 0x10 | 4 | | ep: endpoint number |
++-----------+--------+------------+---------------------------------------------------+
+| 0x14 | 4 | | status: This is the value contained in the |
+| | | | urb->status in the URB completition handler. |
+| | | | |
+| | | | FIXME: |
+| | | | a better explanation needed. |
++-----------+--------+------------+---------------------------------------------------+
+| 0x30 | n | | URB data bytes. For ISO transfers the padding |
+| | | | between each ISO packets is not transmitted. |
++-----------+--------+------------+---------------------------------------------------+
-* Introduction
+======
+usbmon
+======
+
+Introduction
+============
The name "usbmon" in lowercase refers to a facility in kernel which is
used to collect traces of I/O on the USB bus. This function is analogous
is available through a character device in /dev namespace and is an ABI.
The text API is deprecated since 2.6.35, but available for convenience.
-* How to use usbmon to collect raw text traces
+How to use usbmon to collect raw text traces
+============================================
Unlike the packet socket, usbmon has an interface which provides traces
in a text format. This is used for two purposes. First, it serves as a
To collect a raw text trace, execute following steps.
1. Prepare
+----------
Mount debugfs (it has to be enabled in your kernel configuration), and
load the usbmon module (if built as module). The second step is skipped
-if usbmon is built into the kernel.
+if usbmon is built into the kernel::
-# mount -t debugfs none_debugs /sys/kernel/debug
-# modprobe usbmon
-#
+ # mount -t debugfs none_debugs /sys/kernel/debug
+ # modprobe usbmon
+ #
-Verify that bus sockets are present.
+Verify that bus sockets are present:
-# ls /sys/kernel/debug/usb/usbmon
-0s 0u 1s 1t 1u 2s 2t 2u 3s 3t 3u 4s 4t 4u
-#
+ # ls /sys/kernel/debug/usb/usbmon
+ 0s 0u 1s 1t 1u 2s 2t 2u 3s 3t 3u 4s 4t 4u
+ #
Now you can choose to either use the socket '0u' (to capture packets on all
buses), and skip to step #3, or find the bus used by your device with step #2.
This allows to filter away annoying devices that talk continuously.
2. Find which bus connects to the desired device
+------------------------------------------------
Run "cat /sys/kernel/debug/usb/devices", and find the T-line which corresponds
to the device. Usually you do it by looking for the vendor string. If you have
many similar devices, unplug one and compare the two
/sys/kernel/debug/usb/devices outputs. The T-line will have a bus number.
-Example:
-T: Bus=03 Lev=01 Prnt=01 Port=00 Cnt=01 Dev#= 2 Spd=12 MxCh= 0
-D: Ver= 1.10 Cls=00(>ifc ) Sub=00 Prot=00 MxPS= 8 #Cfgs= 1
-P: Vendor=0557 ProdID=2004 Rev= 1.00
-S: Manufacturer=ATEN
-S: Product=UC100KM V2.00
+Example::
+
+ T: Bus=03 Lev=01 Prnt=01 Port=00 Cnt=01 Dev#= 2 Spd=12 MxCh= 0
+ D: Ver= 1.10 Cls=00(>ifc ) Sub=00 Prot=00 MxPS= 8 #Cfgs= 1
+ P: Vendor=0557 ProdID=2004 Rev= 1.00
+ S: Manufacturer=ATEN
+ S: Product=UC100KM V2.00
"Bus=03" means it's bus 3. Alternatively, you can look at the output from
"lsusb" and get the bus number from the appropriate line. Example:
Bus 003 Device 002: ID 0557:2004 ATEN UC100KM V2.00
3. Start 'cat'
+--------------
+
+::
-# cat /sys/kernel/debug/usb/usbmon/3u > /tmp/1.mon.out
+ # cat /sys/kernel/debug/usb/usbmon/3u > /tmp/1.mon.out
-to listen on a single bus, otherwise, to listen on all buses, type:
+to listen on a single bus, otherwise, to listen on all buses, type::
-# cat /sys/kernel/debug/usb/usbmon/0u > /tmp/1.mon.out
+ # cat /sys/kernel/debug/usb/usbmon/0u > /tmp/1.mon.out
This process will read until it is killed. Naturally, the output can be
redirected to a desirable location. This is preferred, because it is going
to be quite long.
4. Perform the desired operation on the USB bus
+-----------------------------------------------
This is where you do something that creates the traffic: plug in a flash key,
copy files, control a webcam, etc.
5. Kill cat
+-----------
Usually it's done with a keyboard interrupt (Control-C).
sent by e-mail, or inspected with a text editor. In the last case make sure
that the file size is not excessive for your favourite editor.
-* Raw text data format
+Raw text data format
+====================
Two formats are supported currently: the original, or '1t' format, and
the '1u' format. The '1t' format is deprecated in kernel 2.6.21. The '1u'
- "Address" word (formerly a "pipe"). It consists of four fields, separated by
colons: URB type and direction, Bus number, Device address, Endpoint number.
Type and direction are encoded with two bytes in the following manner:
+
+ == == =============================
Ci Co Control input and output
Zi Zo Isochronous input and output
Ii Io Interrupt input and output
Bi Bo Bulk input and output
+ == == =============================
+
Bus number, Device address, and Endpoint are decimal numbers, but they may
have leading zeros, for the sake of human readers.
Examples:
-An input control transfer to get a port status.
+An input control transfer to get a port status::
-d5ea89a0 3575914555 S Ci:1:001:0 s a3 00 0000 0003 0004 4 <
-d5ea89a0 3575914560 C Ci:1:001:0 0 4 = 01050000
+ d5ea89a0 3575914555 S Ci:1:001:0 s a3 00 0000 0003 0004 4 <
+ d5ea89a0 3575914560 C Ci:1:001:0 0 4 = 01050000
An output bulk transfer to send a SCSI command 0x28 (READ_10) in a 31-byte
-Bulk wrapper to a storage device at address 5:
+Bulk wrapper to a storage device at address 5::
-dd65f0e8 4128379752 S Bo:1:005:2 -115 31 = 55534243 ad000000 00800000 80010a28 20000000 20000040 00000000 000000
-dd65f0e8 4128379808 C Bo:1:005:2 0 31 >
+ dd65f0e8 4128379752 S Bo:1:005:2 -115 31 = 55534243 ad000000 00800000 80010a28 20000000 20000040 00000000 000000
+ dd65f0e8 4128379808 C Bo:1:005:2 0 31 >
-* Raw binary format and API
+Raw binary format and API
+=========================
The overall architecture of the API is about the same as the one above,
only the events are delivered in binary format. Each event is sent in
-the following structure (its name is made up, so that we can refer to it):
+the following structure (its name is made up, so that we can refer to it)::
-struct usbmon_packet {
+ struct usbmon_packet {
u64 id; /* 0: URB ID - from submission to callback */
unsigned char type; /* 8: Same as text; extensible. */
unsigned char xfer_type; /* ISO (0), Intr, Control, Bulk (3) */
int start_frame; /* 52: For ISO */
unsigned int xfer_flags; /* 56: copy of URB's transfer_flags */
unsigned int ndesc; /* 60: Actual number of ISO descriptors */
-}; /* 64 total length */
+ }; /* 64 total length */
These events can be received from a character device by reading with read(2),
with an ioctl(2), or by accessing the buffer with mmap. However, read(2)
MON_IOCG_STATS, defined as _IOR(MON_IOC_MAGIC, 3, struct mon_bin_stats)
-The argument is a pointer to the following structure:
+The argument is a pointer to the following structure::
-struct mon_bin_stats {
+ struct mon_bin_stats {
u32 queued;
u32 dropped;
-};
+ };
The member "queued" refers to the number of events currently queued in the
buffer (and not to the number of events processed since the last reset).
These calls wait for events to arrive if none were in the kernel buffer,
then return the first event. The argument is a pointer to the following
-structure:
+structure::
-struct mon_get_arg {
+ struct mon_get_arg {
struct usbmon_packet *hdr;
void *data;
size_t alloc; /* Length of data (can be zero) */
-};
+ };
Before the call, hdr, data, and alloc should be filled. Upon return, the area
pointed by hdr contains the next event structure, and the data buffer contains
MON_IOCX_MFETCH, defined as _IOWR(MON_IOC_MAGIC, 7, struct mon_mfetch_arg)
This ioctl is primarily used when the application accesses the buffer
-with mmap(2). Its argument is a pointer to the following structure:
+with mmap(2). Its argument is a pointer to the following structure::
-struct mon_mfetch_arg {
+ struct mon_mfetch_arg {
uint32_t *offvec; /* Vector of events fetched */
uint32_t nfetch; /* Number of events to fetch (out: fetched) */
uint32_t nflush; /* Number of events to flush */
-};
+ };
The ioctl operates in 3 stages.
The basic idea is simple:
To prepare, map the buffer by getting the current size, then using mmap(2).
-Then, execute a loop similar to the one written in pseudo-code below:
+Then, execute a loop similar to the one written in pseudo-code below::
struct mon_mfetch_arg fetch;
struct usbmon_packet *hdr;
S: Supported
F: drivers/spi/spi-tegra*
+TEGRA XUSB PADCTL DRIVER
+M: JC Kuo <jckuo@nvidia.com>
+S: Supported
+F: drivers/phy/tegra/xusb*
+
TEHUTI ETHERNET DRIVER
M: Andy Gospodarek <andy@greyhouse.net>
L: netdev@vger.kernel.org
S: Maintained
F: drivers/usb/roles/intel-xhci-usb-role-switch.c
+USB IP DRIVER FOR HISILICON KIRIN
+M: Yu Chen <chenyu56@huawei.com>
+M: Binghui Wang <wangbinghui@hisilicon.com>
+L: linux-usb@vger.kernel.org
+S: Maintained
+F: Documentation/devicetree/bindings/phy/phy-hi3660-usb3.txt
+F: drivers/phy/hisilicon/phy-hi3660-usb3.c
+
USB ISP116X DRIVER
M: Olav Kongas <ok@artecdesign.ee>
L: linux-usb@vger.kernel.org
};
MODULE_DEVICE_TABLE(pci, gpu_i2c_ids);
+static const struct property_entry ccgx_props[] = {
+ /* Use FW built for NVIDIA (nv) only */
+ PROPERTY_ENTRY_U16("ccgx,firmware-build", ('n' << 8) | 'v'),
+ { }
+};
+
static int gpu_populate_client(struct gpu_i2c_dev *i2cd, int irq)
{
struct i2c_client *ccgx_client;
sizeof(i2cd->gpu_ccgx_ucsi->type));
i2cd->gpu_ccgx_ucsi->addr = 0x8;
i2cd->gpu_ccgx_ucsi->irq = irq;
+ i2cd->gpu_ccgx_ucsi->properties = ccgx_props;
ccgx_client = i2c_new_device(&i2cd->adapter, i2cd->gpu_ccgx_ucsi);
if (!ccgx_client)
return -ENODEV;
Enable this to support the Meson USB3 PHY and OTG detection
IP block found in Meson GXL and GXM SoCs.
If unsure, say N.
+
+config PHY_MESON_G12A_USB2
+ tristate "Meson G12A USB2 PHY driver"
+ default ARCH_MESON
+ depends on OF && (ARCH_MESON || COMPILE_TEST)
+ select GENERIC_PHY
+ select REGMAP_MMIO
+ help
+ Enable this to support the Meson USB2 PHYs found in Meson
+ G12A SoCs.
+ If unsure, say N.
+
+config PHY_MESON_G12A_USB3_PCIE
+ tristate "Meson G12A USB3+PCIE Combo PHY driver"
+ default ARCH_MESON
+ depends on OF && (ARCH_MESON || COMPILE_TEST)
+ select GENERIC_PHY
+ select REGMAP_MMIO
+ help
+ Enable this to support the Meson USB3 + PCIE Combo PHY found
+ in Meson G12A SoCs.
+ If unsure, say N.
obj-$(CONFIG_PHY_MESON8B_USB2) += phy-meson8b-usb2.o
obj-$(CONFIG_PHY_MESON_GXL_USB2) += phy-meson-gxl-usb2.o
+obj-$(CONFIG_PHY_MESON_G12A_USB2) += phy-meson-g12a-usb2.o
obj-$(CONFIG_PHY_MESON_GXL_USB3) += phy-meson-gxl-usb3.o
+obj-$(CONFIG_PHY_MESON_G12A_USB3_PCIE) += phy-meson-g12a-usb3-pcie.o
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Meson G12A USB2 PHY driver
+ *
+ * Copyright (C) 2017 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
+ * Copyright (C) 2017 Amlogic, Inc. All rights reserved
+ * Copyright (C) 2019 BayLibre, SAS
+ * Author: Neil Armstrong <narmstrong@baylibre.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+
+#define PHY_CTRL_R0 0x0
+#define PHY_CTRL_R1 0x4
+#define PHY_CTRL_R2 0x8
+#define PHY_CTRL_R3 0xc
+ #define PHY_CTRL_R3_SQUELCH_REF GENMASK(1, 0)
+ #define PHY_CTRL_R3_HSDIC_REF GENMASK(3, 2)
+ #define PHY_CTRL_R3_DISC_THRESH GENMASK(7, 4)
+
+#define PHY_CTRL_R4 0x10
+ #define PHY_CTRL_R4_CALIB_CODE_7_0 GENMASK(7, 0)
+ #define PHY_CTRL_R4_CALIB_CODE_15_8 GENMASK(15, 8)
+ #define PHY_CTRL_R4_CALIB_CODE_23_16 GENMASK(23, 16)
+ #define PHY_CTRL_R4_I_C2L_CAL_EN BIT(24)
+ #define PHY_CTRL_R4_I_C2L_CAL_RESET_N BIT(25)
+ #define PHY_CTRL_R4_I_C2L_CAL_DONE BIT(26)
+ #define PHY_CTRL_R4_TEST_BYPASS_MODE_EN BIT(27)
+ #define PHY_CTRL_R4_I_C2L_BIAS_TRIM_1_0 GENMASK(29, 28)
+ #define PHY_CTRL_R4_I_C2L_BIAS_TRIM_3_2 GENMASK(31, 30)
+
+#define PHY_CTRL_R5 0x14
+#define PHY_CTRL_R6 0x18
+#define PHY_CTRL_R7 0x1c
+#define PHY_CTRL_R8 0x20
+#define PHY_CTRL_R9 0x24
+#define PHY_CTRL_R10 0x28
+#define PHY_CTRL_R11 0x2c
+#define PHY_CTRL_R12 0x30
+#define PHY_CTRL_R13 0x34
+ #define PHY_CTRL_R13_CUSTOM_PATTERN_19 GENMASK(7, 0)
+ #define PHY_CTRL_R13_LOAD_STAT BIT(14)
+ #define PHY_CTRL_R13_UPDATE_PMA_SIGNALS BIT(15)
+ #define PHY_CTRL_R13_MIN_COUNT_FOR_SYNC_DET GENMASK(20, 16)
+ #define PHY_CTRL_R13_CLEAR_HOLD_HS_DISCONNECT BIT(21)
+ #define PHY_CTRL_R13_BYPASS_HOST_DISCONNECT_VAL BIT(22)
+ #define PHY_CTRL_R13_BYPASS_HOST_DISCONNECT_EN BIT(23)
+ #define PHY_CTRL_R13_I_C2L_HS_EN BIT(24)
+ #define PHY_CTRL_R13_I_C2L_FS_EN BIT(25)
+ #define PHY_CTRL_R13_I_C2L_LS_EN BIT(26)
+ #define PHY_CTRL_R13_I_C2L_HS_OE BIT(27)
+ #define PHY_CTRL_R13_I_C2L_FS_OE BIT(28)
+ #define PHY_CTRL_R13_I_C2L_HS_RX_EN BIT(29)
+ #define PHY_CTRL_R13_I_C2L_FSLS_RX_EN BIT(30)
+
+#define PHY_CTRL_R14 0x38
+ #define PHY_CTRL_R14_I_RDP_EN BIT(0)
+ #define PHY_CTRL_R14_I_RPU_SW1_EN BIT(1)
+ #define PHY_CTRL_R14_I_RPU_SW2_EN GENMASK(2, 3)
+ #define PHY_CTRL_R14_PG_RSTN BIT(4)
+ #define PHY_CTRL_R14_I_C2L_DATA_16_8 BIT(5)
+ #define PHY_CTRL_R14_I_C2L_ASSERT_SINGLE_EN_ZERO BIT(6)
+ #define PHY_CTRL_R14_BYPASS_CTRL_7_0 GENMASK(15, 8)
+ #define PHY_CTRL_R14_BYPASS_CTRL_15_8 GENMASK(23, 16)
+
+#define PHY_CTRL_R15 0x3c
+#define PHY_CTRL_R16 0x40
+ #define PHY_CTRL_R16_MPLL_M GENMASK(8, 0)
+ #define PHY_CTRL_R16_MPLL_N GENMASK(14, 10)
+ #define PHY_CTRL_R16_MPLL_TDC_MODE BIT(20)
+ #define PHY_CTRL_R16_MPLL_SDM_EN BIT(21)
+ #define PHY_CTRL_R16_MPLL_LOAD BIT(22)
+ #define PHY_CTRL_R16_MPLL_DCO_SDM_EN BIT(23)
+ #define PHY_CTRL_R16_MPLL_LOCK_LONG GENMASK(25, 24)
+ #define PHY_CTRL_R16_MPLL_LOCK_F BIT(26)
+ #define PHY_CTRL_R16_MPLL_FAST_LOCK BIT(27)
+ #define PHY_CTRL_R16_MPLL_EN BIT(28)
+ #define PHY_CTRL_R16_MPLL_RESET BIT(29)
+ #define PHY_CTRL_R16_MPLL_LOCK BIT(30)
+ #define PHY_CTRL_R16_MPLL_LOCK_DIG BIT(31)
+
+#define PHY_CTRL_R17 0x44
+ #define PHY_CTRL_R17_MPLL_FRAC_IN GENMASK(13, 0)
+ #define PHY_CTRL_R17_MPLL_FIX_EN BIT(16)
+ #define PHY_CTRL_R17_MPLL_LAMBDA1 GENMASK(19, 17)
+ #define PHY_CTRL_R17_MPLL_LAMBDA0 GENMASK(22, 20)
+ #define PHY_CTRL_R17_MPLL_FILTER_MODE BIT(23)
+ #define PHY_CTRL_R17_MPLL_FILTER_PVT2 GENMASK(27, 24)
+ #define PHY_CTRL_R17_MPLL_FILTER_PVT1 GENMASK(31, 28)
+
+#define PHY_CTRL_R18 0x48
+ #define PHY_CTRL_R18_MPLL_LKW_SEL GENMASK(1, 0)
+ #define PHY_CTRL_R18_MPLL_LK_W GENMASK(5, 2)
+ #define PHY_CTRL_R18_MPLL_LK_S GENMASK(11, 6)
+ #define PHY_CTRL_R18_MPLL_DCO_M_EN BIT(12)
+ #define PHY_CTRL_R18_MPLL_DCO_CLK_SEL BIT(13)
+ #define PHY_CTRL_R18_MPLL_PFD_GAIN GENMASK(15, 14)
+ #define PHY_CTRL_R18_MPLL_ROU GENMASK(18, 16)
+ #define PHY_CTRL_R18_MPLL_DATA_SEL GENMASK(21, 19)
+ #define PHY_CTRL_R18_MPLL_BIAS_ADJ GENMASK(23, 22)
+ #define PHY_CTRL_R18_MPLL_BB_MODE GENMASK(25, 24)
+ #define PHY_CTRL_R18_MPLL_ALPHA GENMASK(28, 26)
+ #define PHY_CTRL_R18_MPLL_ADJ_LDO GENMASK(30, 29)
+ #define PHY_CTRL_R18_MPLL_ACG_RANGE BIT(31)
+
+#define PHY_CTRL_R19 0x4c
+#define PHY_CTRL_R20 0x50
+ #define PHY_CTRL_R20_USB2_IDDET_EN BIT(0)
+ #define PHY_CTRL_R20_USB2_OTG_VBUS_TRIM_2_0 GENMASK(3, 1)
+ #define PHY_CTRL_R20_USB2_OTG_VBUSDET_EN BIT(4)
+ #define PHY_CTRL_R20_USB2_AMON_EN BIT(5)
+ #define PHY_CTRL_R20_USB2_CAL_CODE_R5 BIT(6)
+ #define PHY_CTRL_R20_BYPASS_OTG_DET BIT(7)
+ #define PHY_CTRL_R20_USB2_DMON_EN BIT(8)
+ #define PHY_CTRL_R20_USB2_DMON_SEL_3_0 GENMASK(12, 9)
+ #define PHY_CTRL_R20_USB2_EDGE_DRV_EN BIT(13)
+ #define PHY_CTRL_R20_USB2_EDGE_DRV_TRIM_1_0 GENMASK(15, 14)
+ #define PHY_CTRL_R20_USB2_BGR_ADJ_4_0 GENMASK(20, 16)
+ #define PHY_CTRL_R20_USB2_BGR_START BIT(21)
+ #define PHY_CTRL_R20_USB2_BGR_VREF_4_0 GENMASK(28, 24)
+ #define PHY_CTRL_R20_USB2_BGR_DBG_1_0 GENMASK(30, 29)
+ #define PHY_CTRL_R20_BYPASS_CAL_DONE_R5 BIT(31)
+
+#define PHY_CTRL_R21 0x54
+ #define PHY_CTRL_R21_USB2_BGR_FORCE BIT(0)
+ #define PHY_CTRL_R21_USB2_CAL_ACK_EN BIT(1)
+ #define PHY_CTRL_R21_USB2_OTG_ACA_EN BIT(2)
+ #define PHY_CTRL_R21_USB2_TX_STRG_PD BIT(3)
+ #define PHY_CTRL_R21_USB2_OTG_ACA_TRIM_1_0 GENMASK(5, 4)
+ #define PHY_CTRL_R21_BYPASS_UTMI_CNTR GENMASK(15, 6)
+ #define PHY_CTRL_R21_BYPASS_UTMI_REG GENMASK(25, 20)
+
+#define PHY_CTRL_R22 0x58
+#define PHY_CTRL_R23 0x5c
+
+#define RESET_COMPLETE_TIME 1000
+#define PLL_RESET_COMPLETE_TIME 100
+
+struct phy_meson_g12a_usb2_priv {
+ struct device *dev;
+ struct regmap *regmap;
+ struct clk *clk;
+ struct reset_control *reset;
+};
+
+static const struct regmap_config phy_meson_g12a_usb2_regmap_conf = {
+ .reg_bits = 8,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .max_register = PHY_CTRL_R23,
+};
+
+static int phy_meson_g12a_usb2_init(struct phy *phy)
+{
+ struct phy_meson_g12a_usb2_priv *priv = phy_get_drvdata(phy);
+ int ret;
+
+ ret = reset_control_reset(priv->reset);
+ if (ret)
+ return ret;
+
+ udelay(RESET_COMPLETE_TIME);
+
+ /* usb2_otg_aca_en == 0 */
+ regmap_update_bits(priv->regmap, PHY_CTRL_R21,
+ PHY_CTRL_R21_USB2_OTG_ACA_EN, 0);
+
+ /* PLL Setup : 24MHz * 20 / 1 = 480MHz */
+ regmap_write(priv->regmap, PHY_CTRL_R16,
+ FIELD_PREP(PHY_CTRL_R16_MPLL_M, 20) |
+ FIELD_PREP(PHY_CTRL_R16_MPLL_N, 1) |
+ PHY_CTRL_R16_MPLL_LOAD |
+ FIELD_PREP(PHY_CTRL_R16_MPLL_LOCK_LONG, 1) |
+ PHY_CTRL_R16_MPLL_FAST_LOCK |
+ PHY_CTRL_R16_MPLL_EN |
+ PHY_CTRL_R16_MPLL_RESET);
+
+ regmap_write(priv->regmap, PHY_CTRL_R17,
+ FIELD_PREP(PHY_CTRL_R17_MPLL_FRAC_IN, 0) |
+ FIELD_PREP(PHY_CTRL_R17_MPLL_LAMBDA1, 7) |
+ FIELD_PREP(PHY_CTRL_R17_MPLL_LAMBDA0, 7) |
+ FIELD_PREP(PHY_CTRL_R17_MPLL_FILTER_PVT2, 2) |
+ FIELD_PREP(PHY_CTRL_R17_MPLL_FILTER_PVT1, 9));
+
+ regmap_write(priv->regmap, PHY_CTRL_R18,
+ FIELD_PREP(PHY_CTRL_R18_MPLL_LKW_SEL, 1) |
+ FIELD_PREP(PHY_CTRL_R18_MPLL_LK_W, 9) |
+ FIELD_PREP(PHY_CTRL_R18_MPLL_LK_S, 0x27) |
+ FIELD_PREP(PHY_CTRL_R18_MPLL_PFD_GAIN, 1) |
+ FIELD_PREP(PHY_CTRL_R18_MPLL_ROU, 7) |
+ FIELD_PREP(PHY_CTRL_R18_MPLL_DATA_SEL, 3) |
+ FIELD_PREP(PHY_CTRL_R18_MPLL_BIAS_ADJ, 1) |
+ FIELD_PREP(PHY_CTRL_R18_MPLL_BB_MODE, 0) |
+ FIELD_PREP(PHY_CTRL_R18_MPLL_ALPHA, 3) |
+ FIELD_PREP(PHY_CTRL_R18_MPLL_ADJ_LDO, 1) |
+ PHY_CTRL_R18_MPLL_ACG_RANGE);
+
+ udelay(PLL_RESET_COMPLETE_TIME);
+
+ /* UnReset PLL */
+ regmap_write(priv->regmap, PHY_CTRL_R16,
+ FIELD_PREP(PHY_CTRL_R16_MPLL_M, 20) |
+ FIELD_PREP(PHY_CTRL_R16_MPLL_N, 1) |
+ PHY_CTRL_R16_MPLL_LOAD |
+ FIELD_PREP(PHY_CTRL_R16_MPLL_LOCK_LONG, 1) |
+ PHY_CTRL_R16_MPLL_FAST_LOCK |
+ PHY_CTRL_R16_MPLL_EN);
+
+ /* PHY Tuning */
+ regmap_write(priv->regmap, PHY_CTRL_R20,
+ FIELD_PREP(PHY_CTRL_R20_USB2_OTG_VBUS_TRIM_2_0, 4) |
+ PHY_CTRL_R20_USB2_OTG_VBUSDET_EN |
+ FIELD_PREP(PHY_CTRL_R20_USB2_DMON_SEL_3_0, 15) |
+ PHY_CTRL_R20_USB2_EDGE_DRV_EN |
+ FIELD_PREP(PHY_CTRL_R20_USB2_EDGE_DRV_TRIM_1_0, 3) |
+ FIELD_PREP(PHY_CTRL_R20_USB2_BGR_ADJ_4_0, 0) |
+ FIELD_PREP(PHY_CTRL_R20_USB2_BGR_VREF_4_0, 0) |
+ FIELD_PREP(PHY_CTRL_R20_USB2_BGR_DBG_1_0, 0));
+
+ regmap_write(priv->regmap, PHY_CTRL_R4,
+ FIELD_PREP(PHY_CTRL_R4_CALIB_CODE_7_0, 0xf) |
+ FIELD_PREP(PHY_CTRL_R4_CALIB_CODE_15_8, 0xf) |
+ FIELD_PREP(PHY_CTRL_R4_CALIB_CODE_23_16, 0xf) |
+ PHY_CTRL_R4_TEST_BYPASS_MODE_EN |
+ FIELD_PREP(PHY_CTRL_R4_I_C2L_BIAS_TRIM_1_0, 0) |
+ FIELD_PREP(PHY_CTRL_R4_I_C2L_BIAS_TRIM_3_2, 0));
+
+ /* Tuning Disconnect Threshold */
+ regmap_write(priv->regmap, PHY_CTRL_R3,
+ FIELD_PREP(PHY_CTRL_R3_SQUELCH_REF, 0) |
+ FIELD_PREP(PHY_CTRL_R3_HSDIC_REF, 1) |
+ FIELD_PREP(PHY_CTRL_R3_DISC_THRESH, 3));
+
+ /* Analog Settings */
+ regmap_write(priv->regmap, PHY_CTRL_R14, 0);
+ regmap_write(priv->regmap, PHY_CTRL_R13,
+ PHY_CTRL_R13_UPDATE_PMA_SIGNALS |
+ FIELD_PREP(PHY_CTRL_R13_MIN_COUNT_FOR_SYNC_DET, 7));
+
+ return 0;
+}
+
+static int phy_meson_g12a_usb2_exit(struct phy *phy)
+{
+ struct phy_meson_g12a_usb2_priv *priv = phy_get_drvdata(phy);
+
+ return reset_control_reset(priv->reset);
+}
+
+/* set_mode is not needed, mode setting is handled via the UTMI bus */
+static const struct phy_ops phy_meson_g12a_usb2_ops = {
+ .init = phy_meson_g12a_usb2_init,
+ .exit = phy_meson_g12a_usb2_exit,
+ .owner = THIS_MODULE,
+};
+
+static int phy_meson_g12a_usb2_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct phy_provider *phy_provider;
+ struct resource *res;
+ struct phy_meson_g12a_usb2_priv *priv;
+ struct phy *phy;
+ void __iomem *base;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->dev = dev;
+ platform_set_drvdata(pdev, priv);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ priv->regmap = devm_regmap_init_mmio(dev, base,
+ &phy_meson_g12a_usb2_regmap_conf);
+ if (IS_ERR(priv->regmap))
+ return PTR_ERR(priv->regmap);
+
+ priv->clk = devm_clk_get(dev, "xtal");
+ if (IS_ERR(priv->clk))
+ return PTR_ERR(priv->clk);
+
+ priv->reset = devm_reset_control_get(dev, "phy");
+ if (IS_ERR(priv->reset))
+ return PTR_ERR(priv->reset);
+
+ ret = reset_control_deassert(priv->reset);
+ if (ret)
+ return ret;
+
+ phy = devm_phy_create(dev, NULL, &phy_meson_g12a_usb2_ops);
+ if (IS_ERR(phy)) {
+ ret = PTR_ERR(phy);
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "failed to create PHY\n");
+
+ return ret;
+ }
+
+ phy_set_bus_width(phy, 8);
+ phy_set_drvdata(phy, priv);
+
+ phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static const struct of_device_id phy_meson_g12a_usb2_of_match[] = {
+ { .compatible = "amlogic,g12a-usb2-phy", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, phy_meson_g12a_usb2_of_match);
+
+static struct platform_driver phy_meson_g12a_usb2_driver = {
+ .probe = phy_meson_g12a_usb2_probe,
+ .driver = {
+ .name = "phy-meson-g12a-usb2",
+ .of_match_table = phy_meson_g12a_usb2_of_match,
+ },
+};
+module_platform_driver(phy_meson_g12a_usb2_driver);
+
+MODULE_AUTHOR("Martin Blumenstingl <martin.blumenstingl@googlemail.com>");
+MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
+MODULE_DESCRIPTION("Meson G12A USB2 PHY driver");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Amlogic G12A USB3 + PCIE Combo PHY driver
+ *
+ * Copyright (C) 2017 Amlogic, Inc. All rights reserved
+ * Copyright (C) 2019 BayLibre, SAS
+ * Author: Neil Armstrong <narmstrong@baylibre.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/phy/phy.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <linux/platform_device.h>
+#include <dt-bindings/phy/phy.h>
+
+#define PHY_R0 0x00
+ #define PHY_R0_PCIE_POWER_STATE GENMASK(4, 0)
+ #define PHY_R0_PCIE_USB3_SWITCH GENMASK(6, 5)
+
+#define PHY_R1 0x04
+ #define PHY_R1_PHY_TX1_TERM_OFFSET GENMASK(4, 0)
+ #define PHY_R1_PHY_TX0_TERM_OFFSET GENMASK(9, 5)
+ #define PHY_R1_PHY_RX1_EQ GENMASK(12, 10)
+ #define PHY_R1_PHY_RX0_EQ GENMASK(15, 13)
+ #define PHY_R1_PHY_LOS_LEVEL GENMASK(20, 16)
+ #define PHY_R1_PHY_LOS_BIAS GENMASK(23, 21)
+ #define PHY_R1_PHY_REF_CLKDIV2 BIT(24)
+ #define PHY_R1_PHY_MPLL_MULTIPLIER GENMASK(31, 25)
+
+#define PHY_R2 0x08
+ #define PHY_R2_PCS_TX_DEEMPH_GEN2_6DB GENMASK(5, 0)
+ #define PHY_R2_PCS_TX_DEEMPH_GEN2_3P5DB GENMASK(11, 6)
+ #define PHY_R2_PCS_TX_DEEMPH_GEN1 GENMASK(17, 12)
+ #define PHY_R2_PHY_TX_VBOOST_LVL GENMASK(20, 18)
+
+#define PHY_R4 0x10
+ #define PHY_R4_PHY_CR_WRITE BIT(0)
+ #define PHY_R4_PHY_CR_READ BIT(1)
+ #define PHY_R4_PHY_CR_DATA_IN GENMASK(17, 2)
+ #define PHY_R4_PHY_CR_CAP_DATA BIT(18)
+ #define PHY_R4_PHY_CR_CAP_ADDR BIT(19)
+
+#define PHY_R5 0x14
+ #define PHY_R5_PHY_CR_DATA_OUT GENMASK(15, 0)
+ #define PHY_R5_PHY_CR_ACK BIT(16)
+ #define PHY_R5_PHY_BS_OUT BIT(17)
+
+struct phy_g12a_usb3_pcie_priv {
+ struct regmap *regmap;
+ struct regmap *regmap_cr;
+ struct clk *clk_ref;
+ struct reset_control *reset;
+ struct phy *phy;
+ unsigned int mode;
+};
+
+static const struct regmap_config phy_g12a_usb3_pcie_regmap_conf = {
+ .reg_bits = 8,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .max_register = PHY_R5,
+};
+
+static int phy_g12a_usb3_pcie_cr_bus_addr(struct phy_g12a_usb3_pcie_priv *priv,
+ unsigned int addr)
+{
+ unsigned int val, reg;
+ int ret;
+
+ reg = FIELD_PREP(PHY_R4_PHY_CR_DATA_IN, addr);
+
+ regmap_write(priv->regmap, PHY_R4, reg);
+ regmap_write(priv->regmap, PHY_R4, reg);
+
+ regmap_write(priv->regmap, PHY_R4, reg | PHY_R4_PHY_CR_CAP_ADDR);
+
+ ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val,
+ (val & PHY_R5_PHY_CR_ACK),
+ 5, 1000);
+ if (ret)
+ return ret;
+
+ regmap_write(priv->regmap, PHY_R4, reg);
+
+ ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val,
+ !(val & PHY_R5_PHY_CR_ACK),
+ 5, 1000);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int phy_g12a_usb3_pcie_cr_bus_read(void *context, unsigned int addr,
+ unsigned int *data)
+{
+ struct phy_g12a_usb3_pcie_priv *priv = context;
+ unsigned int val;
+ int ret;
+
+ ret = phy_g12a_usb3_pcie_cr_bus_addr(priv, addr);
+ if (ret)
+ return ret;
+
+ regmap_write(priv->regmap, PHY_R4, 0);
+ regmap_write(priv->regmap, PHY_R4, PHY_R4_PHY_CR_READ);
+
+ ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val,
+ (val & PHY_R5_PHY_CR_ACK),
+ 5, 1000);
+ if (ret)
+ return ret;
+
+ *data = FIELD_GET(PHY_R5_PHY_CR_DATA_OUT, val);
+
+ regmap_write(priv->regmap, PHY_R4, 0);
+
+ ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val,
+ !(val & PHY_R5_PHY_CR_ACK),
+ 5, 1000);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int phy_g12a_usb3_pcie_cr_bus_write(void *context, unsigned int addr,
+ unsigned int data)
+{
+ struct phy_g12a_usb3_pcie_priv *priv = context;
+ unsigned int val, reg;
+ int ret;
+
+ ret = phy_g12a_usb3_pcie_cr_bus_addr(priv, addr);
+ if (ret)
+ return ret;
+
+ reg = FIELD_PREP(PHY_R4_PHY_CR_DATA_IN, data);
+
+ regmap_write(priv->regmap, PHY_R4, reg);
+ regmap_write(priv->regmap, PHY_R4, reg);
+
+ regmap_write(priv->regmap, PHY_R4, reg | PHY_R4_PHY_CR_CAP_DATA);
+
+ ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val,
+ (val & PHY_R5_PHY_CR_ACK),
+ 5, 1000);
+ if (ret)
+ return ret;
+
+ regmap_write(priv->regmap, PHY_R4, reg);
+
+ ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val,
+ (val & PHY_R5_PHY_CR_ACK) == 0,
+ 5, 1000);
+ if (ret)
+ return ret;
+
+ regmap_write(priv->regmap, PHY_R4, reg);
+
+ regmap_write(priv->regmap, PHY_R4, reg | PHY_R4_PHY_CR_WRITE);
+
+ ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val,
+ (val & PHY_R5_PHY_CR_ACK),
+ 5, 1000);
+ if (ret)
+ return ret;
+
+ regmap_write(priv->regmap, PHY_R4, reg);
+
+ ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val,
+ (val & PHY_R5_PHY_CR_ACK) == 0,
+ 5, 1000);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static const struct regmap_config phy_g12a_usb3_pcie_cr_regmap_conf = {
+ .reg_bits = 16,
+ .val_bits = 16,
+ .reg_read = phy_g12a_usb3_pcie_cr_bus_read,
+ .reg_write = phy_g12a_usb3_pcie_cr_bus_write,
+ .max_register = 0xffff,
+ .fast_io = true,
+};
+
+static int phy_g12a_usb3_init(struct phy *phy)
+{
+ struct phy_g12a_usb3_pcie_priv *priv = phy_get_drvdata(phy);
+ int data, ret;
+
+ /* Switch PHY to USB3 */
+ /* TODO figure out how to handle when PCIe was set in the bootloader */
+ regmap_update_bits(priv->regmap, PHY_R0,
+ PHY_R0_PCIE_USB3_SWITCH,
+ PHY_R0_PCIE_USB3_SWITCH);
+
+ /*
+ * WORKAROUND: There is SSPHY suspend bug due to
+ * which USB enumerates
+ * in HS mode instead of SS mode. Workaround it by asserting
+ * LANE0.TX_ALT_BLOCK.EN_ALT_BUS to enable TX to use alt bus
+ * mode
+ */
+ ret = regmap_update_bits(priv->regmap_cr, 0x102d, BIT(7), BIT(7));
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(priv->regmap_cr, 0x1010, 0xff0, 20);
+ if (ret)
+ return ret;
+
+ /*
+ * Fix RX Equalization setting as follows
+ * LANE0.RX_OVRD_IN_HI. RX_EQ_EN set to 0
+ * LANE0.RX_OVRD_IN_HI.RX_EQ_EN_OVRD set to 1
+ * LANE0.RX_OVRD_IN_HI.RX_EQ set to 3
+ * LANE0.RX_OVRD_IN_HI.RX_EQ_OVRD set to 1
+ */
+ ret = regmap_read(priv->regmap_cr, 0x1006, &data);
+ if (ret)
+ return ret;
+
+ data &= ~BIT(6);
+ data |= BIT(7);
+ data &= ~(0x7 << 8);
+ data |= (0x3 << 8);
+ data |= (1 << 11);
+ ret = regmap_write(priv->regmap_cr, 0x1006, data);
+ if (ret)
+ return ret;
+
+ /*
+ * Set EQ and TX launch amplitudes as follows
+ * LANE0.TX_OVRD_DRV_LO.PREEMPH set to 22
+ * LANE0.TX_OVRD_DRV_LO.AMPLITUDE set to 127
+ * LANE0.TX_OVRD_DRV_LO.EN set to 1.
+ */
+ ret = regmap_read(priv->regmap_cr, 0x1002, &data);
+ if (ret)
+ return ret;
+
+ data &= ~0x3f80;
+ data |= (0x16 << 7);
+ data &= ~0x7f;
+ data |= (0x7f | BIT(14));
+ ret = regmap_write(priv->regmap_cr, 0x1002, data);
+ if (ret)
+ return ret;
+
+ /* MPLL_LOOP_CTL.PROP_CNTRL = 8 */
+ ret = regmap_update_bits(priv->regmap_cr, 0x30, 0xf << 4, 8 << 4);
+ if (ret)
+ return ret;
+
+ regmap_update_bits(priv->regmap, PHY_R2,
+ PHY_R2_PHY_TX_VBOOST_LVL,
+ FIELD_PREP(PHY_R2_PHY_TX_VBOOST_LVL, 0x4));
+
+ regmap_update_bits(priv->regmap, PHY_R1,
+ PHY_R1_PHY_LOS_BIAS | PHY_R1_PHY_LOS_LEVEL,
+ FIELD_PREP(PHY_R1_PHY_LOS_BIAS, 4) |
+ FIELD_PREP(PHY_R1_PHY_LOS_LEVEL, 9));
+
+ return 0;
+}
+
+static int phy_g12a_usb3_pcie_init(struct phy *phy)
+{
+ struct phy_g12a_usb3_pcie_priv *priv = phy_get_drvdata(phy);
+ int ret;
+
+ ret = reset_control_reset(priv->reset);
+ if (ret)
+ return ret;
+
+ if (priv->mode == PHY_TYPE_USB3)
+ return phy_g12a_usb3_init(phy);
+
+ /* Power UP PCIE */
+ /* TODO figure out when the bootloader has set USB3 mode before */
+ regmap_update_bits(priv->regmap, PHY_R0,
+ PHY_R0_PCIE_POWER_STATE,
+ FIELD_PREP(PHY_R0_PCIE_POWER_STATE, 0x1c));
+
+ return 0;
+}
+
+static int phy_g12a_usb3_pcie_exit(struct phy *phy)
+{
+ struct phy_g12a_usb3_pcie_priv *priv = phy_get_drvdata(phy);
+
+ return reset_control_reset(priv->reset);
+}
+
+static struct phy *phy_g12a_usb3_pcie_xlate(struct device *dev,
+ struct of_phandle_args *args)
+{
+ struct phy_g12a_usb3_pcie_priv *priv = dev_get_drvdata(dev);
+ unsigned int mode;
+
+ if (args->args_count < 1) {
+ dev_err(dev, "invalid number of arguments\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ mode = args->args[0];
+
+ if (mode != PHY_TYPE_USB3 && mode != PHY_TYPE_PCIE) {
+ dev_err(dev, "invalid phy mode select argument\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ priv->mode = mode;
+
+ return priv->phy;
+}
+
+static const struct phy_ops phy_g12a_usb3_pcie_ops = {
+ .init = phy_g12a_usb3_pcie_init,
+ .exit = phy_g12a_usb3_pcie_exit,
+ .owner = THIS_MODULE,
+};
+
+static int phy_g12a_usb3_pcie_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct phy_g12a_usb3_pcie_priv *priv;
+ struct resource *res;
+ struct phy_provider *phy_provider;
+ void __iomem *base;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ priv->regmap = devm_regmap_init_mmio(dev, base,
+ &phy_g12a_usb3_pcie_regmap_conf);
+ if (IS_ERR(priv->regmap))
+ return PTR_ERR(priv->regmap);
+
+ priv->regmap_cr = devm_regmap_init(dev, NULL, priv,
+ &phy_g12a_usb3_pcie_cr_regmap_conf);
+ if (IS_ERR(priv->regmap_cr))
+ return PTR_ERR(priv->regmap_cr);
+
+ priv->clk_ref = devm_clk_get(dev, "ref_clk");
+ if (IS_ERR(priv->clk_ref))
+ return PTR_ERR(priv->clk_ref);
+
+ ret = clk_prepare_enable(priv->clk_ref);
+ if (ret)
+ goto err_disable_clk_ref;
+
+ priv->reset = devm_reset_control_array_get(dev, false, false);
+ if (IS_ERR(priv->reset))
+ return PTR_ERR(priv->reset);
+
+ priv->phy = devm_phy_create(dev, np, &phy_g12a_usb3_pcie_ops);
+ if (IS_ERR(priv->phy)) {
+ ret = PTR_ERR(priv->phy);
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "failed to create PHY\n");
+
+ return ret;
+ }
+
+ phy_set_drvdata(priv->phy, priv);
+ dev_set_drvdata(dev, priv);
+
+ phy_provider = devm_of_phy_provider_register(dev,
+ phy_g12a_usb3_pcie_xlate);
+
+ return PTR_ERR_OR_ZERO(phy_provider);
+
+err_disable_clk_ref:
+ clk_disable_unprepare(priv->clk_ref);
+
+ return ret;
+}
+
+static const struct of_device_id phy_g12a_usb3_pcie_of_match[] = {
+ { .compatible = "amlogic,g12a-usb3-pcie-phy", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, phy_g12a_usb3_pcie_of_match);
+
+static struct platform_driver phy_g12a_usb3_pcie_driver = {
+ .probe = phy_g12a_usb3_pcie_probe,
+ .driver = {
+ .name = "phy-g12a-usb3-pcie",
+ .of_match_table = phy_g12a_usb3_pcie_of_match,
+ },
+};
+module_platform_driver(phy_g12a_usb3_pcie_driver);
+
+MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
+MODULE_DESCRIPTION("Amlogic G12A USB3 + PCIE Combo PHY driver");
+MODULE_LICENSE("GPL v2");
if (IS_ERR(priv->regmap))
return PTR_ERR(priv->regmap);
- priv->clk = devm_clk_get(dev, "phy");
- if (IS_ERR(priv->clk)) {
- ret = PTR_ERR(priv->clk);
- if (ret == -ENOENT)
- priv->clk = NULL;
- else
- return ret;
- }
+ priv->clk = devm_clk_get_optional(dev, "phy");
+ if (IS_ERR(priv->clk))
+ return PTR_ERR(priv->clk);
priv->reset = devm_reset_control_get_optional_shared(dev, "phy");
if (IS_ERR(priv->reset))
Enable this to support the Broadcom Cygnus PCIe PHY.
If unsure, say N.
+config PHY_BCM_SR_USB
+ tristate "Broadcom Stingray USB PHY driver"
+ depends on OF && (ARCH_BCM_IPROC || COMPILE_TEST)
+ select GENERIC_PHY
+ default ARCH_BCM_IPROC
+ help
+ Enable this to support the Broadcom Stingray USB PHY
+ driver. It supports all versions of Superspeed and
+ Highspeed PHYs.
+ If unsure, say N.
+
config BCM_KONA_USB2_PHY
tristate "Broadcom Kona USB2 PHY Driver"
depends on HAS_IOMEM
phy-brcm-usb-dvr-objs := phy-brcm-usb.o phy-brcm-usb-init.o
obj-$(CONFIG_PHY_BCM_SR_PCIE) += phy-bcm-sr-pcie.o
+obj-$(CONFIG_PHY_BCM_SR_USB) += phy-bcm-sr-usb.o
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2016-2018 Broadcom
+ */
+
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+
+enum bcm_usb_phy_version {
+ BCM_SR_USB_COMBO_PHY,
+ BCM_SR_USB_HS_PHY,
+};
+
+enum bcm_usb_phy_reg {
+ PLL_NDIV_FRAC,
+ PLL_NDIV_INT,
+ PLL_CTRL,
+ PHY_CTRL,
+ PHY_PLL_CTRL,
+};
+
+/* USB PHY registers */
+
+static const u8 bcm_usb_combo_phy_ss[] = {
+ [PLL_CTRL] = 0x18,
+ [PHY_CTRL] = 0x14,
+};
+
+static const u8 bcm_usb_combo_phy_hs[] = {
+ [PLL_NDIV_FRAC] = 0x04,
+ [PLL_NDIV_INT] = 0x08,
+ [PLL_CTRL] = 0x0c,
+ [PHY_CTRL] = 0x10,
+};
+
+#define HSPLL_NDIV_INT_VAL 0x13
+#define HSPLL_NDIV_FRAC_VAL 0x1005
+
+static const u8 bcm_usb_hs_phy[] = {
+ [PLL_NDIV_FRAC] = 0x0,
+ [PLL_NDIV_INT] = 0x4,
+ [PLL_CTRL] = 0x8,
+ [PHY_CTRL] = 0xc,
+};
+
+enum pll_ctrl_bits {
+ PLL_RESETB,
+ SSPLL_SUSPEND_EN,
+ PLL_SEQ_START,
+ PLL_LOCK,
+ PLL_PDIV,
+};
+
+static const u8 u3pll_ctrl[] = {
+ [PLL_RESETB] = 0,
+ [SSPLL_SUSPEND_EN] = 1,
+ [PLL_SEQ_START] = 2,
+ [PLL_LOCK] = 3,
+};
+
+#define HSPLL_PDIV_MASK 0xF
+#define HSPLL_PDIV_VAL 0x1
+
+static const u8 u2pll_ctrl[] = {
+ [PLL_PDIV] = 1,
+ [PLL_RESETB] = 5,
+ [PLL_LOCK] = 6,
+};
+
+enum bcm_usb_phy_ctrl_bits {
+ CORERDY,
+ AFE_LDO_PWRDWNB,
+ AFE_PLL_PWRDWNB,
+ AFE_BG_PWRDWNB,
+ PHY_ISO,
+ PHY_RESETB,
+ PHY_PCTL,
+};
+
+#define PHY_PCTL_MASK 0xffff
+/*
+ * 0x0806 of PCTL_VAL has below bits set
+ * BIT-8 : refclk divider 1
+ * BIT-3:2: device mode; mode is not effect
+ * BIT-1: soft reset active low
+ */
+#define HSPHY_PCTL_VAL 0x0806
+#define SSPHY_PCTL_VAL 0x0006
+
+static const u8 u3phy_ctrl[] = {
+ [PHY_RESETB] = 1,
+ [PHY_PCTL] = 2,
+};
+
+static const u8 u2phy_ctrl[] = {
+ [CORERDY] = 0,
+ [AFE_LDO_PWRDWNB] = 1,
+ [AFE_PLL_PWRDWNB] = 2,
+ [AFE_BG_PWRDWNB] = 3,
+ [PHY_ISO] = 4,
+ [PHY_RESETB] = 5,
+ [PHY_PCTL] = 6,
+};
+
+struct bcm_usb_phy_cfg {
+ uint32_t type;
+ uint32_t version;
+ void __iomem *regs;
+ struct phy *phy;
+ const u8 *offset;
+};
+
+#define PLL_LOCK_RETRY_COUNT 1000
+
+enum bcm_usb_phy_type {
+ USB_HS_PHY,
+ USB_SS_PHY,
+};
+
+#define NUM_BCM_SR_USB_COMBO_PHYS 2
+
+static inline void bcm_usb_reg32_clrbits(void __iomem *addr, uint32_t clear)
+{
+ writel(readl(addr) & ~clear, addr);
+}
+
+static inline void bcm_usb_reg32_setbits(void __iomem *addr, uint32_t set)
+{
+ writel(readl(addr) | set, addr);
+}
+
+static int bcm_usb_pll_lock_check(void __iomem *addr, u32 bit)
+{
+ int retry;
+ u32 rd_data;
+
+ retry = PLL_LOCK_RETRY_COUNT;
+ do {
+ rd_data = readl(addr);
+ if (rd_data & bit)
+ return 0;
+ udelay(1);
+ } while (--retry > 0);
+
+ pr_err("%s: FAIL\n", __func__);
+ return -ETIMEDOUT;
+}
+
+static int bcm_usb_ss_phy_init(struct bcm_usb_phy_cfg *phy_cfg)
+{
+ int ret = 0;
+ void __iomem *regs = phy_cfg->regs;
+ const u8 *offset;
+ u32 rd_data;
+
+ offset = phy_cfg->offset;
+
+ /* Set pctl with mode and soft reset */
+ rd_data = readl(regs + offset[PHY_CTRL]);
+ rd_data &= ~(PHY_PCTL_MASK << u3phy_ctrl[PHY_PCTL]);
+ rd_data |= (SSPHY_PCTL_VAL << u3phy_ctrl[PHY_PCTL]);
+ writel(rd_data, regs + offset[PHY_CTRL]);
+
+ bcm_usb_reg32_clrbits(regs + offset[PLL_CTRL],
+ BIT(u3pll_ctrl[SSPLL_SUSPEND_EN]));
+ bcm_usb_reg32_setbits(regs + offset[PLL_CTRL],
+ BIT(u3pll_ctrl[PLL_SEQ_START]));
+ bcm_usb_reg32_setbits(regs + offset[PLL_CTRL],
+ BIT(u3pll_ctrl[PLL_RESETB]));
+
+ /* Maximum timeout for PLL reset done */
+ msleep(30);
+
+ ret = bcm_usb_pll_lock_check(regs + offset[PLL_CTRL],
+ BIT(u3pll_ctrl[PLL_LOCK]));
+
+ return ret;
+}
+
+static int bcm_usb_hs_phy_init(struct bcm_usb_phy_cfg *phy_cfg)
+{
+ int ret = 0;
+ void __iomem *regs = phy_cfg->regs;
+ const u8 *offset;
+ u32 rd_data;
+
+ offset = phy_cfg->offset;
+
+ writel(HSPLL_NDIV_INT_VAL, regs + offset[PLL_NDIV_INT]);
+ writel(HSPLL_NDIV_FRAC_VAL, regs + offset[PLL_NDIV_FRAC]);
+
+ rd_data = readl(regs + offset[PLL_CTRL]);
+ rd_data &= ~(HSPLL_PDIV_MASK << u2pll_ctrl[PLL_PDIV]);
+ rd_data |= (HSPLL_PDIV_VAL << u2pll_ctrl[PLL_PDIV]);
+ writel(rd_data, regs + offset[PLL_CTRL]);
+
+ /* Set Core Ready high */
+ bcm_usb_reg32_setbits(regs + offset[PHY_CTRL],
+ BIT(u2phy_ctrl[CORERDY]));
+
+ /* Maximum timeout for Core Ready done */
+ msleep(30);
+
+ bcm_usb_reg32_setbits(regs + offset[PLL_CTRL],
+ BIT(u2pll_ctrl[PLL_RESETB]));
+ bcm_usb_reg32_setbits(regs + offset[PHY_CTRL],
+ BIT(u2phy_ctrl[PHY_RESETB]));
+
+
+ rd_data = readl(regs + offset[PHY_CTRL]);
+ rd_data &= ~(PHY_PCTL_MASK << u2phy_ctrl[PHY_PCTL]);
+ rd_data |= (HSPHY_PCTL_VAL << u2phy_ctrl[PHY_PCTL]);
+ writel(rd_data, regs + offset[PHY_CTRL]);
+
+ /* Maximum timeout for PLL reset done */
+ msleep(30);
+
+ ret = bcm_usb_pll_lock_check(regs + offset[PLL_CTRL],
+ BIT(u2pll_ctrl[PLL_LOCK]));
+
+ return ret;
+}
+
+static int bcm_usb_phy_reset(struct phy *phy)
+{
+ struct bcm_usb_phy_cfg *phy_cfg = phy_get_drvdata(phy);
+ void __iomem *regs = phy_cfg->regs;
+ const u8 *offset;
+
+ offset = phy_cfg->offset;
+
+ if (phy_cfg->type == USB_HS_PHY) {
+ bcm_usb_reg32_clrbits(regs + offset[PHY_CTRL],
+ BIT(u2phy_ctrl[CORERDY]));
+ bcm_usb_reg32_setbits(regs + offset[PHY_CTRL],
+ BIT(u2phy_ctrl[CORERDY]));
+ }
+
+ return 0;
+}
+
+static int bcm_usb_phy_init(struct phy *phy)
+{
+ struct bcm_usb_phy_cfg *phy_cfg = phy_get_drvdata(phy);
+ int ret = -EINVAL;
+
+ if (phy_cfg->type == USB_SS_PHY)
+ ret = bcm_usb_ss_phy_init(phy_cfg);
+ else if (phy_cfg->type == USB_HS_PHY)
+ ret = bcm_usb_hs_phy_init(phy_cfg);
+
+ return ret;
+}
+
+static struct phy_ops sr_phy_ops = {
+ .init = bcm_usb_phy_init,
+ .reset = bcm_usb_phy_reset,
+ .owner = THIS_MODULE,
+};
+
+static struct phy *bcm_usb_phy_xlate(struct device *dev,
+ struct of_phandle_args *args)
+{
+ struct bcm_usb_phy_cfg *phy_cfg;
+ int phy_idx;
+
+ phy_cfg = dev_get_drvdata(dev);
+ if (!phy_cfg)
+ return ERR_PTR(-EINVAL);
+
+ if (phy_cfg->version == BCM_SR_USB_COMBO_PHY) {
+ phy_idx = args->args[0];
+
+ if (WARN_ON(phy_idx > 1))
+ return ERR_PTR(-ENODEV);
+
+ return phy_cfg[phy_idx].phy;
+ } else
+ return phy_cfg->phy;
+}
+
+static int bcm_usb_phy_create(struct device *dev, struct device_node *node,
+ void __iomem *regs, uint32_t version)
+{
+ struct bcm_usb_phy_cfg *phy_cfg;
+ int idx;
+
+ if (version == BCM_SR_USB_COMBO_PHY) {
+ phy_cfg = devm_kzalloc(dev, NUM_BCM_SR_USB_COMBO_PHYS *
+ sizeof(struct bcm_usb_phy_cfg),
+ GFP_KERNEL);
+ if (!phy_cfg)
+ return -ENOMEM;
+
+ for (idx = 0; idx < NUM_BCM_SR_USB_COMBO_PHYS; idx++) {
+ phy_cfg[idx].regs = regs;
+ phy_cfg[idx].version = version;
+ if (idx == 0) {
+ phy_cfg[idx].offset = bcm_usb_combo_phy_hs;
+ phy_cfg[idx].type = USB_HS_PHY;
+ } else {
+ phy_cfg[idx].offset = bcm_usb_combo_phy_ss;
+ phy_cfg[idx].type = USB_SS_PHY;
+ }
+ phy_cfg[idx].phy = devm_phy_create(dev, node,
+ &sr_phy_ops);
+ if (IS_ERR(phy_cfg[idx].phy))
+ return PTR_ERR(phy_cfg[idx].phy);
+
+ phy_set_drvdata(phy_cfg[idx].phy, &phy_cfg[idx]);
+ }
+ } else if (version == BCM_SR_USB_HS_PHY) {
+ phy_cfg = devm_kzalloc(dev, sizeof(struct bcm_usb_phy_cfg),
+ GFP_KERNEL);
+ if (!phy_cfg)
+ return -ENOMEM;
+
+ phy_cfg->regs = regs;
+ phy_cfg->version = version;
+ phy_cfg->offset = bcm_usb_hs_phy;
+ phy_cfg->type = USB_HS_PHY;
+ phy_cfg->phy = devm_phy_create(dev, node, &sr_phy_ops);
+ if (IS_ERR(phy_cfg->phy))
+ return PTR_ERR(phy_cfg->phy);
+
+ phy_set_drvdata(phy_cfg->phy, phy_cfg);
+ } else
+ return -ENODEV;
+
+ dev_set_drvdata(dev, phy_cfg);
+
+ return 0;
+}
+
+static const struct of_device_id bcm_usb_phy_of_match[] = {
+ {
+ .compatible = "brcm,sr-usb-combo-phy",
+ .data = (void *)BCM_SR_USB_COMBO_PHY,
+ },
+ {
+ .compatible = "brcm,sr-usb-hs-phy",
+ .data = (void *)BCM_SR_USB_HS_PHY,
+ },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, bcm_usb_phy_of_match);
+
+static int bcm_usb_phy_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *dn = dev->of_node;
+ const struct of_device_id *of_id;
+ struct resource *res;
+ void __iomem *regs;
+ int ret;
+ enum bcm_usb_phy_version version;
+ struct phy_provider *phy_provider;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ regs = devm_ioremap_resource(dev, res);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
+ of_id = of_match_node(bcm_usb_phy_of_match, dn);
+ if (of_id)
+ version = (enum bcm_usb_phy_version)of_id->data;
+ else
+ return -ENODEV;
+
+ ret = bcm_usb_phy_create(dev, dn, regs, version);
+ if (ret)
+ return ret;
+
+ phy_provider = devm_of_phy_provider_register(dev, bcm_usb_phy_xlate);
+
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static struct platform_driver bcm_usb_phy_driver = {
+ .driver = {
+ .name = "phy-bcm-sr-usb",
+ .of_match_table = bcm_usb_phy_of_match,
+ },
+ .probe = bcm_usb_phy_probe,
+};
+module_platform_driver(bcm_usb_phy_driver);
+
+MODULE_AUTHOR("Broadcom");
+MODULE_DESCRIPTION("Broadcom stingray USB Phy driver");
+MODULE_LICENSE("GPL v2");
#include <linux/module.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
#define PHY_CTRL0 0x0
#define PHY_CTRL0_REF_SSP_EN BIT(2)
struct phy *phy;
struct clk *clk;
void __iomem *base;
+ struct regulator *vbus;
};
static int imx8mq_usb_phy_init(struct phy *phy)
static int imx8mq_phy_power_on(struct phy *phy)
{
struct imx8mq_usb_phy *imx_phy = phy_get_drvdata(phy);
+ int ret;
+
+ ret = regulator_enable(imx_phy->vbus);
+ if (ret)
+ return ret;
return clk_prepare_enable(imx_phy->clk);
}
struct imx8mq_usb_phy *imx_phy = phy_get_drvdata(phy);
clk_disable_unprepare(imx_phy->clk);
+ regulator_disable(imx_phy->vbus);
return 0;
}
if (IS_ERR(imx_phy->phy))
return PTR_ERR(imx_phy->phy);
+ imx_phy->vbus = devm_regulator_get(dev, "vbus");
+ if (IS_ERR(imx_phy->vbus))
+ return PTR_ERR(imx_phy->vbus);
+
phy_set_drvdata(imx_phy->phy, imx_phy);
phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
To compile this driver as a module, choose M here.
+config PHY_HI3660_USB
+ tristate "hi3660 USB PHY support"
+ depends on (ARCH_HISI && ARM64) || COMPILE_TEST
+ select GENERIC_PHY
+ select MFD_SYSCON
+ help
+ Enable this to support the HISILICON HI3660 USB PHY.
+
+ To compile this driver as a module, choose M here.
+
config PHY_HISTB_COMBPHY
tristate "HiSilicon STB SoCs COMBPHY support"
depends on (ARCH_HISI && ARM64) || COMPILE_TEST
obj-$(CONFIG_PHY_HI6220_USB) += phy-hi6220-usb.o
+obj-$(CONFIG_PHY_HI3660_USB) += phy-hi3660-usb3.o
obj-$(CONFIG_PHY_HISTB_COMBPHY) += phy-histb-combphy.o
obj-$(CONFIG_PHY_HISI_INNO_USB2) += phy-hisi-inno-usb2.o
obj-$(CONFIG_PHY_HIX5HD2_SATA) += phy-hix5hd2-sata.o
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Phy provider for USB 3.0 controller on HiSilicon 3660 platform
+ *
+ * Copyright (C) 2017-2018 Hilisicon Electronics Co., Ltd.
+ * http://www.huawei.com
+ *
+ * Authors: Yu Chen <chenyu56@huawei.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#define PERI_CRG_CLK_EN4 0x40
+#define PERI_CRG_CLK_DIS4 0x44
+#define GT_CLK_USB3OTG_REF BIT(0)
+#define GT_ACLK_USB3OTG BIT(1)
+
+#define PERI_CRG_RSTEN4 0x90
+#define PERI_CRG_RSTDIS4 0x94
+#define IP_RST_USB3OTGPHY_POR BIT(3)
+#define IP_RST_USB3OTG BIT(5)
+
+#define PERI_CRG_ISODIS 0x148
+#define USB_REFCLK_ISO_EN BIT(25)
+
+#define PCTRL_PERI_CTRL3 0x10
+#define PCTRL_PERI_CTRL3_MSK_START 16
+#define USB_TCXO_EN BIT(1)
+
+#define PCTRL_PERI_CTRL24 0x64
+#define SC_CLK_USB3PHY_3MUX1_SEL BIT(25)
+
+#define USBOTG3_CTRL0 0x00
+#define SC_USB3PHY_ABB_GT_EN BIT(15)
+
+#define USBOTG3_CTRL2 0x08
+#define USBOTG3CTRL2_POWERDOWN_HSP BIT(0)
+#define USBOTG3CTRL2_POWERDOWN_SSP BIT(1)
+
+#define USBOTG3_CTRL3 0x0C
+#define USBOTG3_CTRL3_VBUSVLDEXT BIT(6)
+#define USBOTG3_CTRL3_VBUSVLDEXTSEL BIT(5)
+
+#define USBOTG3_CTRL4 0x10
+
+#define USBOTG3_CTRL7 0x1c
+#define REF_SSP_EN BIT(16)
+
+/* This value config the default txtune parameter of the usb 2.0 phy */
+#define HI3660_USB_DEFAULT_PHY_PARAM 0x1c466e3
+
+struct hi3660_priv {
+ struct device *dev;
+ struct regmap *peri_crg;
+ struct regmap *pctrl;
+ struct regmap *otg_bc;
+ u32 eye_diagram_param;
+};
+
+static int hi3660_phy_init(struct phy *phy)
+{
+ struct hi3660_priv *priv = phy_get_drvdata(phy);
+ u32 val, mask;
+ int ret;
+
+ /* usb refclk iso disable */
+ ret = regmap_write(priv->peri_crg, PERI_CRG_ISODIS, USB_REFCLK_ISO_EN);
+ if (ret)
+ goto out;
+
+ /* enable usb_tcxo_en */
+ val = USB_TCXO_EN | (USB_TCXO_EN << PCTRL_PERI_CTRL3_MSK_START);
+ ret = regmap_write(priv->pctrl, PCTRL_PERI_CTRL3, val);
+ if (ret)
+ goto out;
+
+ /* assert phy */
+ val = IP_RST_USB3OTGPHY_POR | IP_RST_USB3OTG;
+ ret = regmap_write(priv->peri_crg, PERI_CRG_RSTEN4, val);
+ if (ret)
+ goto out;
+
+ /* enable phy ref clk */
+ val = SC_USB3PHY_ABB_GT_EN;
+ mask = val;
+ ret = regmap_update_bits(priv->otg_bc, USBOTG3_CTRL0, mask, val);
+ if (ret)
+ goto out;
+
+ val = REF_SSP_EN;
+ mask = val;
+ ret = regmap_update_bits(priv->otg_bc, USBOTG3_CTRL7, mask, val);
+ if (ret)
+ goto out;
+
+ /* exit from IDDQ mode */
+ mask = USBOTG3CTRL2_POWERDOWN_HSP | USBOTG3CTRL2_POWERDOWN_SSP;
+ ret = regmap_update_bits(priv->otg_bc, USBOTG3_CTRL2, mask, 0);
+ if (ret)
+ goto out;
+
+ /* delay for exit from IDDQ mode */
+ usleep_range(100, 120);
+
+ /* deassert phy */
+ val = IP_RST_USB3OTGPHY_POR | IP_RST_USB3OTG;
+ ret = regmap_write(priv->peri_crg, PERI_CRG_RSTDIS4, val);
+ if (ret)
+ goto out;
+
+ /* delay for phy deasserted */
+ usleep_range(10000, 15000);
+
+ /* fake vbus valid signal */
+ val = USBOTG3_CTRL3_VBUSVLDEXT | USBOTG3_CTRL3_VBUSVLDEXTSEL;
+ mask = val;
+ ret = regmap_update_bits(priv->otg_bc, USBOTG3_CTRL3, mask, val);
+ if (ret)
+ goto out;
+
+ /* delay for vbus valid */
+ usleep_range(100, 120);
+
+ ret = regmap_write(priv->otg_bc, USBOTG3_CTRL4,
+ priv->eye_diagram_param);
+ if (ret)
+ goto out;
+
+ return 0;
+out:
+ dev_err(priv->dev, "failed to init phy ret: %d\n", ret);
+ return ret;
+}
+
+static int hi3660_phy_exit(struct phy *phy)
+{
+ struct hi3660_priv *priv = phy_get_drvdata(phy);
+ u32 val;
+ int ret;
+
+ /* assert phy */
+ val = IP_RST_USB3OTGPHY_POR;
+ ret = regmap_write(priv->peri_crg, PERI_CRG_RSTEN4, val);
+ if (ret)
+ goto out;
+
+ /* disable usb_tcxo_en */
+ val = USB_TCXO_EN << PCTRL_PERI_CTRL3_MSK_START;
+ ret = regmap_write(priv->pctrl, PCTRL_PERI_CTRL3, val);
+ if (ret)
+ goto out;
+
+ return 0;
+out:
+ dev_err(priv->dev, "failed to exit phy ret: %d\n", ret);
+ return ret;
+}
+
+static struct phy_ops hi3660_phy_ops = {
+ .init = hi3660_phy_init,
+ .exit = hi3660_phy_exit,
+ .owner = THIS_MODULE,
+};
+
+static int hi3660_phy_probe(struct platform_device *pdev)
+{
+ struct phy_provider *phy_provider;
+ struct device *dev = &pdev->dev;
+ struct phy *phy;
+ struct hi3660_priv *priv;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->dev = dev;
+ priv->peri_crg = syscon_regmap_lookup_by_phandle(dev->of_node,
+ "hisilicon,pericrg-syscon");
+ if (IS_ERR(priv->peri_crg)) {
+ dev_err(dev, "no hisilicon,pericrg-syscon\n");
+ return PTR_ERR(priv->peri_crg);
+ }
+
+ priv->pctrl = syscon_regmap_lookup_by_phandle(dev->of_node,
+ "hisilicon,pctrl-syscon");
+ if (IS_ERR(priv->pctrl)) {
+ dev_err(dev, "no hisilicon,pctrl-syscon\n");
+ return PTR_ERR(priv->pctrl);
+ }
+
+ /* node of hi3660 phy is a sub-node of usb3_otg_bc */
+ priv->otg_bc = syscon_node_to_regmap(dev->parent->of_node);
+ if (IS_ERR(priv->otg_bc)) {
+ dev_err(dev, "no hisilicon,usb3-otg-bc-syscon\n");
+ return PTR_ERR(priv->otg_bc);
+ }
+
+ if (of_property_read_u32(dev->of_node, "hisilicon,eye-diagram-param",
+ &(priv->eye_diagram_param)))
+ priv->eye_diagram_param = HI3660_USB_DEFAULT_PHY_PARAM;
+
+ phy = devm_phy_create(dev, NULL, &hi3660_phy_ops);
+ if (IS_ERR(phy))
+ return PTR_ERR(phy);
+
+ phy_set_drvdata(phy, priv);
+ phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static const struct of_device_id hi3660_phy_of_match[] = {
+ {.compatible = "hisilicon,hi3660-usb-phy",},
+ { }
+};
+MODULE_DEVICE_TABLE(of, hi3660_phy_of_match);
+
+static struct platform_driver hi3660_phy_driver = {
+ .probe = hi3660_phy_probe,
+ .driver = {
+ .name = "hi3660-usb-phy",
+ .of_match_table = hi3660_phy_of_match,
+ }
+};
+module_platform_driver(hi3660_phy_driver);
+
+MODULE_AUTHOR("Yu Chen <chenyu56@huawei.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Hilisicon Hi3660 USB3 PHY Driver");
multi-ports is first version, otherwise is second veriosn,
so you can easily distinguish them by banks layout.
+config PHY_MTK_UFS
+ tristate "MediaTek UFS M-PHY driver"
+ depends on ARCH_MEDIATEK && OF
+ select GENERIC_PHY
+ help
+ Support for UFS M-PHY on MediaTek chipsets.
+ Enable this to provide vendor-specific probing,
+ initialization, power on and power off flow of
+ specified M-PHYs.
+
config PHY_MTK_XSPHY
tristate "MediaTek XS-PHY Driver"
depends on ARCH_MEDIATEK && OF
#
obj-$(CONFIG_PHY_MTK_TPHY) += phy-mtk-tphy.o
+obj-$(CONFIG_PHY_MTK_UFS) += phy-mtk-ufs.o
obj-$(CONFIG_PHY_MTK_XSPHY) += phy-mtk-xsphy.o
}
/* it's deprecated, make it optional for backward compatibility */
- tphy->u3phya_ref = devm_clk_get(dev, "u3phya_ref");
- if (IS_ERR(tphy->u3phya_ref)) {
- if (PTR_ERR(tphy->u3phya_ref) == -EPROBE_DEFER)
- return -EPROBE_DEFER;
-
- tphy->u3phya_ref = NULL;
- }
+ tphy->u3phya_ref = devm_clk_get_optional(dev, "u3phya_ref");
+ if (IS_ERR(tphy->u3phya_ref))
+ return PTR_ERR(tphy->u3phya_ref);
tphy->src_ref_clk = U3P_REF_CLK;
tphy->src_coef = U3P_SLEW_RATE_COEF;
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019 MediaTek Inc.
+ * Author: Stanley Chu <stanley.chu@mediatek.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+
+/* mphy register and offsets */
+#define MP_GLB_DIG_8C 0x008C
+#define FRC_PLL_ISO_EN BIT(8)
+#define PLL_ISO_EN BIT(9)
+#define FRC_FRC_PWR_ON BIT(10)
+#define PLL_PWR_ON BIT(11)
+
+#define MP_LN_DIG_RX_9C 0xA09C
+#define FSM_DIFZ_FRC BIT(18)
+
+#define MP_LN_DIG_RX_AC 0xA0AC
+#define FRC_RX_SQ_EN BIT(0)
+#define RX_SQ_EN BIT(1)
+
+#define MP_LN_RX_44 0xB044
+#define FRC_CDR_PWR_ON BIT(17)
+#define CDR_PWR_ON BIT(18)
+#define FRC_CDR_ISO_EN BIT(19)
+#define CDR_ISO_EN BIT(20)
+
+struct ufs_mtk_phy {
+ struct device *dev;
+ void __iomem *mmio;
+ struct clk *mp_clk;
+ struct clk *unipro_clk;
+};
+
+static inline u32 mphy_readl(struct ufs_mtk_phy *phy, u32 reg)
+{
+ return readl(phy->mmio + reg);
+}
+
+static inline void mphy_writel(struct ufs_mtk_phy *phy, u32 val, u32 reg)
+{
+ writel(val, phy->mmio + reg);
+}
+
+static void mphy_set_bit(struct ufs_mtk_phy *phy, u32 reg, u32 bit)
+{
+ u32 val;
+
+ val = mphy_readl(phy, reg);
+ val |= bit;
+ mphy_writel(phy, val, reg);
+}
+
+static void mphy_clr_bit(struct ufs_mtk_phy *phy, u32 reg, u32 bit)
+{
+ u32 val;
+
+ val = mphy_readl(phy, reg);
+ val &= ~bit;
+ mphy_writel(phy, val, reg);
+}
+
+static struct ufs_mtk_phy *get_ufs_mtk_phy(struct phy *generic_phy)
+{
+ return (struct ufs_mtk_phy *)phy_get_drvdata(generic_phy);
+}
+
+static int ufs_mtk_phy_clk_init(struct ufs_mtk_phy *phy)
+{
+ struct device *dev = phy->dev;
+
+ phy->unipro_clk = devm_clk_get(dev, "unipro");
+ if (IS_ERR(phy->unipro_clk)) {
+ dev_err(dev, "failed to get clock: unipro");
+ return PTR_ERR(phy->unipro_clk);
+ }
+
+ phy->mp_clk = devm_clk_get(dev, "mp");
+ if (IS_ERR(phy->mp_clk)) {
+ dev_err(dev, "failed to get clock: mp");
+ return PTR_ERR(phy->mp_clk);
+ }
+
+ return 0;
+}
+
+static void ufs_mtk_phy_set_active(struct ufs_mtk_phy *phy)
+{
+ /* release DA_MP_PLL_PWR_ON */
+ mphy_set_bit(phy, MP_GLB_DIG_8C, PLL_PWR_ON);
+ mphy_clr_bit(phy, MP_GLB_DIG_8C, FRC_FRC_PWR_ON);
+
+ /* release DA_MP_PLL_ISO_EN */
+ mphy_clr_bit(phy, MP_GLB_DIG_8C, PLL_ISO_EN);
+ mphy_clr_bit(phy, MP_GLB_DIG_8C, FRC_PLL_ISO_EN);
+
+ /* release DA_MP_CDR_PWR_ON */
+ mphy_set_bit(phy, MP_LN_RX_44, CDR_PWR_ON);
+ mphy_clr_bit(phy, MP_LN_RX_44, FRC_CDR_PWR_ON);
+
+ /* release DA_MP_CDR_ISO_EN */
+ mphy_clr_bit(phy, MP_LN_RX_44, CDR_ISO_EN);
+ mphy_clr_bit(phy, MP_LN_RX_44, FRC_CDR_ISO_EN);
+
+ /* release DA_MP_RX0_SQ_EN */
+ mphy_set_bit(phy, MP_LN_DIG_RX_AC, RX_SQ_EN);
+ mphy_clr_bit(phy, MP_LN_DIG_RX_AC, FRC_RX_SQ_EN);
+
+ /* delay 1us to wait DIFZ stable */
+ udelay(1);
+
+ /* release DIFZ */
+ mphy_clr_bit(phy, MP_LN_DIG_RX_9C, FSM_DIFZ_FRC);
+}
+
+static void ufs_mtk_phy_set_deep_hibern(struct ufs_mtk_phy *phy)
+{
+ /* force DIFZ */
+ mphy_set_bit(phy, MP_LN_DIG_RX_9C, FSM_DIFZ_FRC);
+
+ /* force DA_MP_RX0_SQ_EN */
+ mphy_set_bit(phy, MP_LN_DIG_RX_AC, FRC_RX_SQ_EN);
+ mphy_clr_bit(phy, MP_LN_DIG_RX_AC, RX_SQ_EN);
+
+ /* force DA_MP_CDR_ISO_EN */
+ mphy_set_bit(phy, MP_LN_RX_44, FRC_CDR_ISO_EN);
+ mphy_set_bit(phy, MP_LN_RX_44, CDR_ISO_EN);
+
+ /* force DA_MP_CDR_PWR_ON */
+ mphy_set_bit(phy, MP_LN_RX_44, FRC_CDR_PWR_ON);
+ mphy_clr_bit(phy, MP_LN_RX_44, CDR_PWR_ON);
+
+ /* force DA_MP_PLL_ISO_EN */
+ mphy_set_bit(phy, MP_GLB_DIG_8C, FRC_PLL_ISO_EN);
+ mphy_set_bit(phy, MP_GLB_DIG_8C, PLL_ISO_EN);
+
+ /* force DA_MP_PLL_PWR_ON */
+ mphy_set_bit(phy, MP_GLB_DIG_8C, FRC_FRC_PWR_ON);
+ mphy_clr_bit(phy, MP_GLB_DIG_8C, PLL_PWR_ON);
+}
+
+static int ufs_mtk_phy_power_on(struct phy *generic_phy)
+{
+ struct ufs_mtk_phy *phy = get_ufs_mtk_phy(generic_phy);
+ int ret;
+
+ ret = clk_prepare_enable(phy->unipro_clk);
+ if (ret) {
+ dev_err(phy->dev, "unipro_clk enable failed %d\n", ret);
+ goto out;
+ }
+
+ ret = clk_prepare_enable(phy->mp_clk);
+ if (ret) {
+ dev_err(phy->dev, "mp_clk enable failed %d\n", ret);
+ goto out_unprepare_unipro_clk;
+ }
+
+ ufs_mtk_phy_set_active(phy);
+
+ return 0;
+
+out_unprepare_unipro_clk:
+ clk_disable_unprepare(phy->unipro_clk);
+out:
+ return ret;
+}
+
+static int ufs_mtk_phy_power_off(struct phy *generic_phy)
+{
+ struct ufs_mtk_phy *phy = get_ufs_mtk_phy(generic_phy);
+
+ ufs_mtk_phy_set_deep_hibern(phy);
+
+ clk_disable_unprepare(phy->unipro_clk);
+ clk_disable_unprepare(phy->mp_clk);
+
+ return 0;
+}
+
+static const struct phy_ops ufs_mtk_phy_ops = {
+ .power_on = ufs_mtk_phy_power_on,
+ .power_off = ufs_mtk_phy_power_off,
+ .owner = THIS_MODULE,
+};
+
+static int ufs_mtk_phy_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct phy *generic_phy;
+ struct phy_provider *phy_provider;
+ struct resource *res;
+ struct ufs_mtk_phy *phy;
+ int ret;
+
+ phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
+ if (!phy)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ phy->mmio = devm_ioremap_resource(dev, res);
+ if (IS_ERR(phy->mmio))
+ return PTR_ERR(phy->mmio);
+
+ phy->dev = dev;
+
+ ret = ufs_mtk_phy_clk_init(phy);
+ if (ret)
+ return ret;
+
+ generic_phy = devm_phy_create(dev, NULL, &ufs_mtk_phy_ops);
+ if (IS_ERR(generic_phy))
+ return PTR_ERR(generic_phy);
+
+ phy_set_drvdata(generic_phy, phy);
+
+ phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static const struct of_device_id ufs_mtk_phy_of_match[] = {
+ {.compatible = "mediatek,mt8183-ufsphy"},
+ {},
+};
+MODULE_DEVICE_TABLE(of, ufs_mtk_phy_of_match);
+
+static struct platform_driver ufs_mtk_phy_driver = {
+ .probe = ufs_mtk_phy_probe,
+ .driver = {
+ .of_match_table = ufs_mtk_phy_of_match,
+ .name = "ufs_mtk_phy",
+ },
+};
+module_platform_driver(ufs_mtk_phy_driver);
+
+MODULE_DESCRIPTION("Universal Flash Storage (UFS) MediaTek MPHY");
+MODULE_AUTHOR("Stanley Chu <stanley.chu@mediatek.com>");
+MODULE_LICENSE("GPL v2");
struct serdes_ctrl *ctrl;
};
+#define MCB_S6G_CFG_TIMEOUT 50
+
+static int __serdes_write_mcb_s6g(struct regmap *regmap, u8 macro, u32 op)
+{
+ unsigned int regval = 0;
+
+ regmap_write(regmap, HSIO_MCB_S6G_ADDR_CFG, op |
+ HSIO_MCB_S6G_ADDR_CFG_SERDES6G_ADDR(BIT(macro)));
+
+ return regmap_read_poll_timeout(regmap, HSIO_MCB_S6G_ADDR_CFG, regval,
+ (regval & op) != op, 100,
+ MCB_S6G_CFG_TIMEOUT * 1000);
+}
+
+static int serdes_commit_mcb_s6g(struct regmap *regmap, u8 macro)
+{
+ return __serdes_write_mcb_s6g(regmap, macro,
+ HSIO_MCB_S6G_ADDR_CFG_SERDES6G_WR_ONE_SHOT);
+}
+
+static int serdes_update_mcb_s6g(struct regmap *regmap, u8 macro)
+{
+ return __serdes_write_mcb_s6g(regmap, macro,
+ HSIO_MCB_S6G_ADDR_CFG_SERDES6G_RD_ONE_SHOT);
+}
+
+static int serdes_init_s6g(struct regmap *regmap, u8 serdes, int mode)
+{
+ u32 pll_fsm_ctrl_data;
+ u32 ob_ena1v_mode;
+ u32 des_bw_ana;
+ u32 ob_ena_cas;
+ u32 if_mode;
+ u32 ob_lev;
+ u32 qrate;
+ int ret;
+
+ if (mode == PHY_INTERFACE_MODE_QSGMII) {
+ pll_fsm_ctrl_data = 120;
+ ob_ena1v_mode = 0;
+ ob_ena_cas = 0;
+ des_bw_ana = 5;
+ ob_lev = 24;
+ if_mode = 3;
+ qrate = 0;
+ } else {
+ pll_fsm_ctrl_data = 60;
+ ob_ena1v_mode = 1;
+ ob_ena_cas = 2;
+ des_bw_ana = 3;
+ ob_lev = 48;
+ if_mode = 1;
+ qrate = 1;
+ }
+
+ ret = serdes_update_mcb_s6g(regmap, serdes);
+ if (ret)
+ return ret;
+
+ /* Test pattern */
+
+ regmap_update_bits(regmap, HSIO_S6G_COMMON_CFG,
+ HSIO_S6G_COMMON_CFG_SYS_RST, 0);
+
+ regmap_update_bits(regmap, HSIO_S6G_PLL_CFG,
+ HSIO_S6G_PLL_CFG_PLL_FSM_ENA, 0);
+
+ regmap_update_bits(regmap, HSIO_S6G_IB_CFG,
+ HSIO_S6G_IB_CFG_IB_SIG_DET_ENA |
+ HSIO_S6G_IB_CFG_IB_REG_ENA |
+ HSIO_S6G_IB_CFG_IB_SAM_ENA |
+ HSIO_S6G_IB_CFG_IB_EQZ_ENA |
+ HSIO_S6G_IB_CFG_IB_CONCUR |
+ HSIO_S6G_IB_CFG_IB_CAL_ENA,
+ HSIO_S6G_IB_CFG_IB_SIG_DET_ENA |
+ HSIO_S6G_IB_CFG_IB_REG_ENA |
+ HSIO_S6G_IB_CFG_IB_SAM_ENA |
+ HSIO_S6G_IB_CFG_IB_EQZ_ENA |
+ HSIO_S6G_IB_CFG_IB_CONCUR);
+
+ regmap_update_bits(regmap, HSIO_S6G_IB_CFG1,
+ HSIO_S6G_IB_CFG1_IB_FRC_OFFSET |
+ HSIO_S6G_IB_CFG1_IB_FRC_LP |
+ HSIO_S6G_IB_CFG1_IB_FRC_MID |
+ HSIO_S6G_IB_CFG1_IB_FRC_HP |
+ HSIO_S6G_IB_CFG1_IB_FILT_OFFSET |
+ HSIO_S6G_IB_CFG1_IB_FILT_LP |
+ HSIO_S6G_IB_CFG1_IB_FILT_MID |
+ HSIO_S6G_IB_CFG1_IB_FILT_HP,
+ HSIO_S6G_IB_CFG1_IB_FILT_OFFSET |
+ HSIO_S6G_IB_CFG1_IB_FILT_HP |
+ HSIO_S6G_IB_CFG1_IB_FILT_LP |
+ HSIO_S6G_IB_CFG1_IB_FILT_MID);
+
+ regmap_update_bits(regmap, HSIO_S6G_IB_CFG2,
+ HSIO_S6G_IB_CFG2_IB_UREG_M,
+ HSIO_S6G_IB_CFG2_IB_UREG(4));
+
+ regmap_update_bits(regmap, HSIO_S6G_IB_CFG3,
+ HSIO_S6G_IB_CFG3_IB_INI_OFFSET_M |
+ HSIO_S6G_IB_CFG3_IB_INI_LP_M |
+ HSIO_S6G_IB_CFG3_IB_INI_MID_M |
+ HSIO_S6G_IB_CFG3_IB_INI_HP_M,
+ HSIO_S6G_IB_CFG3_IB_INI_OFFSET(31) |
+ HSIO_S6G_IB_CFG3_IB_INI_LP(1) |
+ HSIO_S6G_IB_CFG3_IB_INI_MID(31) |
+ HSIO_S6G_IB_CFG3_IB_INI_HP(0));
+
+ regmap_update_bits(regmap, HSIO_S6G_MISC_CFG,
+ HSIO_S6G_MISC_CFG_LANE_RST,
+ HSIO_S6G_MISC_CFG_LANE_RST);
+
+ ret = serdes_commit_mcb_s6g(regmap, serdes);
+ if (ret)
+ return ret;
+
+ /* OB + DES + IB + SER CFG */
+ regmap_update_bits(regmap, HSIO_S6G_OB_CFG,
+ HSIO_S6G_OB_CFG_OB_IDLE |
+ HSIO_S6G_OB_CFG_OB_ENA1V_MODE |
+ HSIO_S6G_OB_CFG_OB_POST0_M |
+ HSIO_S6G_OB_CFG_OB_PREC_M,
+ (ob_ena1v_mode ? HSIO_S6G_OB_CFG_OB_ENA1V_MODE : 0) |
+ HSIO_S6G_OB_CFG_OB_POST0(0) |
+ HSIO_S6G_OB_CFG_OB_PREC(0));
+
+ regmap_update_bits(regmap, HSIO_S6G_OB_CFG1,
+ HSIO_S6G_OB_CFG1_OB_ENA_CAS_M |
+ HSIO_S6G_OB_CFG1_OB_LEV_M,
+ HSIO_S6G_OB_CFG1_OB_LEV(ob_lev) |
+ HSIO_S6G_OB_CFG1_OB_ENA_CAS(ob_ena_cas));
+
+ regmap_update_bits(regmap, HSIO_S6G_DES_CFG,
+ HSIO_S6G_DES_CFG_DES_PHS_CTRL_M |
+ HSIO_S6G_DES_CFG_DES_CPMD_SEL_M |
+ HSIO_S6G_DES_CFG_DES_BW_ANA_M,
+ HSIO_S6G_DES_CFG_DES_PHS_CTRL(2) |
+ HSIO_S6G_DES_CFG_DES_CPMD_SEL(0) |
+ HSIO_S6G_DES_CFG_DES_BW_ANA(des_bw_ana));
+
+ regmap_update_bits(regmap, HSIO_S6G_IB_CFG,
+ HSIO_S6G_IB_CFG_IB_SIG_DET_CLK_SEL_M |
+ HSIO_S6G_IB_CFG_IB_REG_PAT_SEL_OFFSET_M,
+ HSIO_S6G_IB_CFG_IB_REG_PAT_SEL_OFFSET(0) |
+ HSIO_S6G_IB_CFG_IB_SIG_DET_CLK_SEL(0));
+
+ regmap_update_bits(regmap, HSIO_S6G_IB_CFG1,
+ HSIO_S6G_IB_CFG1_IB_TSDET_M,
+ HSIO_S6G_IB_CFG1_IB_TSDET(16));
+
+ regmap_update_bits(regmap, HSIO_S6G_SER_CFG,
+ HSIO_S6G_SER_CFG_SER_ALISEL_M |
+ HSIO_S6G_SER_CFG_SER_ENALI,
+ HSIO_S6G_SER_CFG_SER_ALISEL(0));
+
+ regmap_update_bits(regmap, HSIO_S6G_PLL_CFG,
+ HSIO_S6G_PLL_CFG_PLL_DIV4 |
+ HSIO_S6G_PLL_CFG_PLL_ENA_ROT |
+ HSIO_S6G_PLL_CFG_PLL_FSM_CTRL_DATA_M |
+ HSIO_S6G_PLL_CFG_PLL_ROT_DIR |
+ HSIO_S6G_PLL_CFG_PLL_ROT_FRQ,
+ HSIO_S6G_PLL_CFG_PLL_FSM_CTRL_DATA
+ (pll_fsm_ctrl_data));
+
+ regmap_update_bits(regmap, HSIO_S6G_COMMON_CFG,
+ HSIO_S6G_COMMON_CFG_SYS_RST |
+ HSIO_S6G_COMMON_CFG_ENA_LANE |
+ HSIO_S6G_COMMON_CFG_PWD_RX |
+ HSIO_S6G_COMMON_CFG_PWD_TX |
+ HSIO_S6G_COMMON_CFG_HRATE |
+ HSIO_S6G_COMMON_CFG_QRATE |
+ HSIO_S6G_COMMON_CFG_ENA_ELOOP |
+ HSIO_S6G_COMMON_CFG_ENA_FLOOP |
+ HSIO_S6G_COMMON_CFG_IF_MODE_M,
+ HSIO_S6G_COMMON_CFG_SYS_RST |
+ HSIO_S6G_COMMON_CFG_ENA_LANE |
+ (qrate ? HSIO_S6G_COMMON_CFG_QRATE : 0) |
+ HSIO_S6G_COMMON_CFG_IF_MODE(if_mode));
+
+ regmap_update_bits(regmap, HSIO_S6G_MISC_CFG,
+ HSIO_S6G_MISC_CFG_LANE_RST |
+ HSIO_S6G_MISC_CFG_DES_100FX_CPMD_ENA |
+ HSIO_S6G_MISC_CFG_RX_LPI_MODE_ENA |
+ HSIO_S6G_MISC_CFG_TX_LPI_MODE_ENA,
+ HSIO_S6G_MISC_CFG_LANE_RST |
+ HSIO_S6G_MISC_CFG_RX_LPI_MODE_ENA);
+
+
+ ret = serdes_commit_mcb_s6g(regmap, serdes);
+ if (ret)
+ return ret;
+
+ regmap_update_bits(regmap, HSIO_S6G_PLL_CFG,
+ HSIO_S6G_PLL_CFG_PLL_FSM_ENA,
+ HSIO_S6G_PLL_CFG_PLL_FSM_ENA);
+
+ ret = serdes_commit_mcb_s6g(regmap, serdes);
+ if (ret)
+ return ret;
+
+ /* Wait for PLL bringup */
+ msleep(20);
+
+ regmap_update_bits(regmap, HSIO_S6G_IB_CFG,
+ HSIO_S6G_IB_CFG_IB_CAL_ENA,
+ HSIO_S6G_IB_CFG_IB_CAL_ENA);
+
+ regmap_update_bits(regmap, HSIO_S6G_MISC_CFG,
+ HSIO_S6G_MISC_CFG_LANE_RST, 0);
+
+ ret = serdes_commit_mcb_s6g(regmap, serdes);
+ if (ret)
+ return ret;
+
+ /* Wait for calibration */
+ msleep(60);
+
+ regmap_update_bits(regmap, HSIO_S6G_IB_CFG,
+ HSIO_S6G_IB_CFG_IB_REG_PAT_SEL_OFFSET_M |
+ HSIO_S6G_IB_CFG_IB_SIG_DET_CLK_SEL_M,
+ HSIO_S6G_IB_CFG_IB_REG_PAT_SEL_OFFSET(0) |
+ HSIO_S6G_IB_CFG_IB_SIG_DET_CLK_SEL(7));
+
+ regmap_update_bits(regmap, HSIO_S6G_IB_CFG1,
+ HSIO_S6G_IB_CFG1_IB_TSDET_M,
+ HSIO_S6G_IB_CFG1_IB_TSDET(3));
+
+ /* IB CFG */
+
+ return 0;
+}
+
#define MCB_S1G_CFG_TIMEOUT 50
static int __serdes_write_mcb_s1g(struct regmap *regmap, u8 macro, u32 op)
u32 mux;
};
-#define SERDES_MUX(_idx, _port, _mode, _submode, _mask, _mux) { \
+#define SERDES_MUX(_idx, _port, _mode, _submode, _mask, _mux) { \
.idx = _idx, \
.port = _port, \
.mode = _mode, \
if (macro->idx <= SERDES1G_MAX)
return serdes_init_s1g(macro->ctrl->regs, macro->idx);
+ else if (macro->idx <= SERDES6G_MAX)
+ return serdes_init_s6g(macro->ctrl->regs,
+ macro->idx - (SERDES1G_MAX + 1),
+ ocelot_serdes_muxes[i].submode);
- /* SERDES6G and PCIe not supported yet */
+ /* PCIe not supported yet */
return -EOPNOTSUPP;
}
if (!phy || !phy->ops->reset)
return 0;
+ ret = phy_pm_runtime_get_sync(phy);
+ if (ret < 0 && ret != -ENOTSUPP)
+ return ret;
+
mutex_lock(&phy->mutex);
ret = phy->ops->reset(phy);
mutex_unlock(&phy->mutex);
+ phy_pm_runtime_put(phy);
+
return ret;
}
EXPORT_SYMBOL_GPL(phy_reset);
if (!phy || IS_ERR(phy))
return;
+ mutex_lock(&phy->mutex);
+ if (phy->ops->release)
+ phy->ops->release(phy);
+ mutex_unlock(&phy->mutex);
+
module_put(phy->ops->owner);
put_device(&phy->dev);
}
QMP_PHY_INIT_CFG(QPHY_TXDEEMPH_M3P5DB_V0, 0x0e),
};
+static const struct qmp_phy_init_tbl msm8998_pcie_serdes_tbl[] = {
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_BIAS_EN_CLKBUFLR_EN, 0x14),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_CLK_SELECT, 0x30),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_PLL_IVCO, 0x0f),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_CMN_CONFIG, 0x06),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_LOCK_CMP_EN, 0x01),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_RESETSM_CNTRL, 0x20),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_VCO_TUNE_MAP, 0x00),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_VCO_TUNE2_MODE0, 0x01),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_VCO_TUNE1_MODE0, 0xc9),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_VCO_TUNE_TIMER1, 0xff),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_VCO_TUNE_TIMER2, 0x3f),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_SVS_MODE_CLK_SEL, 0x01),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_CORE_CLK_EN, 0x00),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_CORECLK_DIV_MODE0, 0x0a),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_CLK_EP_DIV, 0x19),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_CLK_ENABLE1, 0x90),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_DEC_START_MODE0, 0x82),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_DIV_FRAC_START3_MODE0, 0x03),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_DIV_FRAC_START2_MODE0, 0x55),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_DIV_FRAC_START1_MODE0, 0x55),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_LOCK_CMP3_MODE0, 0x00),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_LOCK_CMP2_MODE0, 0x0d),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_LOCK_CMP1_MODE0, 0x04),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_HSCLK_SEL, 0x00),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_CP_CTRL_MODE0, 0x08),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_PLL_RCTRL_MODE0, 0x16),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_PLL_CCTRL_MODE0, 0x34),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_CMN_CONFIG, 0x06),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_CLK_SELECT, 0x33),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_SYS_CLK_CTRL, 0x02),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_SYSCLK_BUF_ENABLE, 0x07),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_SYSCLK_EN_SEL, 0x04),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_INTEGLOOP_GAIN1_MODE0, 0x00),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_INTEGLOOP_GAIN0_MODE0, 0x3f),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_BG_TIMER, 0x09),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_EN_CENTER, 0x01),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_PER1, 0x40),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_PER2, 0x01),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_ADJ_PER1, 0x02),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_ADJ_PER2, 0x00),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_STEP_SIZE1, 0x7e),
+ QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_STEP_SIZE2, 0x15),
+};
+
+static const struct qmp_phy_init_tbl msm8998_pcie_tx_tbl[] = {
+ QMP_PHY_INIT_CFG(QSERDES_V3_TX_RES_CODE_LANE_OFFSET_TX, 0x02),
+ QMP_PHY_INIT_CFG(QSERDES_V3_TX_RCV_DETECT_LVL_2, 0x12),
+ QMP_PHY_INIT_CFG(QSERDES_V3_TX_HIGHZ_DRVR_EN, 0x10),
+ QMP_PHY_INIT_CFG(QSERDES_V3_TX_LANE_MODE_1, 0x06),
+};
+
+static const struct qmp_phy_init_tbl msm8998_pcie_rx_tbl[] = {
+ QMP_PHY_INIT_CFG(QSERDES_V3_RX_SIGDET_CNTRL, 0x03),
+ QMP_PHY_INIT_CFG(QSERDES_V3_RX_SIGDET_ENABLES, 0x1c),
+ QMP_PHY_INIT_CFG(QSERDES_V3_RX_SIGDET_DEGLITCH_CNTRL, 0x14),
+ QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_EQU_ADAPTOR_CNTRL2, 0x0a),
+ QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_EQU_ADAPTOR_CNTRL3, 0x04),
+ QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_EQU_ADAPTOR_CNTRL4, 0x1a),
+ QMP_PHY_INIT_CFG(QSERDES_V3_RX_UCDR_SO_SATURATION_AND_ENABLE, 0x4b),
+ QMP_PHY_INIT_CFG(QSERDES_V3_RX_UCDR_SO_GAIN, 0x04),
+ QMP_PHY_INIT_CFG(QSERDES_V3_RX_UCDR_SO_GAIN_HALF, 0x04),
+ QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_EQ_OFFSET_ADAPTOR_CNTRL1, 0x00),
+ QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_OFFSET_ADAPTOR_CNTRL2, 0x80),
+ QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_INTERFACE_MODE, 0x40),
+ QMP_PHY_INIT_CFG(QSERDES_V3_RX_UCDR_PI_CONTROLS, 0x71),
+ QMP_PHY_INIT_CFG(QSERDES_V3_RX_UCDR_FASTLOCK_COUNT_LOW, 0x40),
+};
+
+static const struct qmp_phy_init_tbl msm8998_pcie_pcs_tbl[] = {
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_ENDPOINT_REFCLK_DRIVE, 0x04),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_OSC_DTCT_ACTIONS, 0x00),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_PWRUP_RESET_DLY_TIME_AUXCLK, 0x01),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_L1SS_WAKEUP_DLY_TIME_AUXCLK_MSB, 0x00),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_L1SS_WAKEUP_DLY_TIME_AUXCLK_LSB, 0x20),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_LP_WAKEUP_DLY_TIME_AUXCLK_MSB, 0x00),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_LP_WAKEUP_DLY_TIME_AUXCLK, 0x01),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_PLL_LOCK_CHK_DLY_TIME, 0x73),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_RX_SIGDET_LVL, 0x99),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_SIGDET_CNTRL, 0x03),
+};
+
static const struct qmp_phy_init_tbl msm8996_usb3_serdes_tbl[] = {
QMP_PHY_INIT_CFG(QSERDES_COM_SYSCLK_EN_SEL, 0x14),
QMP_PHY_INIT_CFG(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x08),
* @init_count: phy common block initialization count
* @phy_initialized: indicate if PHY has been initialized
* @mode: current PHY mode
+ * @ufs_reset: optional UFS PHY reset handle
*/
struct qcom_qmp {
struct device *dev;
int init_count;
bool phy_initialized;
enum phy_mode mode;
+
+ struct reset_control *ufs_reset;
};
static inline void qphy_setbits(void __iomem *base, u32 offset, u32 val)
.no_pcs_sw_reset = true,
};
+static const struct qmp_phy_cfg msm8998_pciephy_cfg = {
+ .type = PHY_TYPE_PCIE,
+ .nlanes = 1,
+
+ .serdes_tbl = msm8998_pcie_serdes_tbl,
+ .serdes_tbl_num = ARRAY_SIZE(msm8998_pcie_serdes_tbl),
+ .tx_tbl = msm8998_pcie_tx_tbl,
+ .tx_tbl_num = ARRAY_SIZE(msm8998_pcie_tx_tbl),
+ .rx_tbl = msm8998_pcie_rx_tbl,
+ .rx_tbl_num = ARRAY_SIZE(msm8998_pcie_rx_tbl),
+ .pcs_tbl = msm8998_pcie_pcs_tbl,
+ .pcs_tbl_num = ARRAY_SIZE(msm8998_pcie_pcs_tbl),
+ .clk_list = msm8996_phy_clk_l,
+ .num_clks = ARRAY_SIZE(msm8996_phy_clk_l),
+ .reset_list = ipq8074_pciephy_reset_l,
+ .num_resets = ARRAY_SIZE(ipq8074_pciephy_reset_l),
+ .vreg_list = qmp_phy_vreg_l,
+ .num_vregs = ARRAY_SIZE(qmp_phy_vreg_l),
+ .regs = pciephy_regs_layout,
+
+ .start_ctrl = SERDES_START | PCS_START,
+ .pwrdn_ctrl = SW_PWRDN | REFCLK_DRV_DSBL,
+ .mask_com_pcs_ready = PCS_READY,
+};
+
static const struct qmp_phy_cfg msm8998_usb3phy_cfg = {
.type = PHY_TYPE_USB3,
.nlanes = 1,
return 0;
}
+ reset_control_assert(qmp->ufs_reset);
if (cfg->has_phy_com_ctrl) {
qphy_setbits(serdes, cfg->regs[QPHY_COM_START_CONTROL],
SERDES_START | PCS_START);
return 0;
}
-/* PHY Initialization */
-static int qcom_qmp_phy_init(struct phy *phy)
+static int qcom_qmp_phy_enable(struct phy *phy)
{
struct qmp_phy *qphy = phy_get_drvdata(phy);
struct qcom_qmp *qmp = qphy->qmp;
dev_vdbg(qmp->dev, "Initializing QMP phy\n");
+ if (cfg->no_pcs_sw_reset) {
+ /*
+ * Get UFS reset, which is delayed until now to avoid a
+ * circular dependency where UFS needs its PHY, but the PHY
+ * needs this UFS reset.
+ */
+ if (!qmp->ufs_reset) {
+ qmp->ufs_reset =
+ devm_reset_control_get_exclusive(qmp->dev,
+ "ufsphy");
+
+ if (IS_ERR(qmp->ufs_reset)) {
+ ret = PTR_ERR(qmp->ufs_reset);
+ dev_err(qmp->dev,
+ "failed to get UFS reset: %d\n",
+ ret);
+
+ qmp->ufs_reset = NULL;
+ return ret;
+ }
+ }
+
+ ret = reset_control_assert(qmp->ufs_reset);
+ if (ret)
+ goto err_lane_rst;
+ }
+
ret = qcom_qmp_phy_com_init(qphy);
if (ret)
return ret;
cfg->rx_tbl, cfg->rx_tbl_num);
qcom_qmp_phy_configure(pcs, cfg->regs, cfg->pcs_tbl, cfg->pcs_tbl_num);
-
- /*
- * UFS PHY requires the deassert of software reset before serdes start.
- * For UFS PHYs that do not have software reset control bits, defer
- * starting serdes until the power on callback.
- */
- if ((cfg->type == PHY_TYPE_UFS) && cfg->no_pcs_sw_reset)
- goto out;
+ ret = reset_control_deassert(qmp->ufs_reset);
+ if (ret)
+ goto err_lane_rst;
/*
* Pull out PHY from POWER DOWN state.
usleep_range(cfg->pwrdn_delay_min, cfg->pwrdn_delay_max);
/* Pull PHY out of reset state */
- qphy_clrbits(pcs, cfg->regs[QPHY_SW_RESET], SW_RESET);
+ if (!cfg->no_pcs_sw_reset)
+ qphy_clrbits(pcs, cfg->regs[QPHY_SW_RESET], SW_RESET);
+
if (cfg->has_phy_dp_com_ctrl)
qphy_clrbits(dp_com, QPHY_V3_DP_COM_SW_RESET, SW_RESET);
goto err_pcs_ready;
}
qmp->phy_initialized = true;
-
-out:
- return ret;
+ return 0;
err_pcs_ready:
+ reset_control_assert(qmp->ufs_reset);
clk_disable_unprepare(qphy->pipe_clk);
err_clk_enable:
if (cfg->has_lane_rst)
return ret;
}
-static int qcom_qmp_phy_exit(struct phy *phy)
+static int qcom_qmp_phy_disable(struct phy *phy)
{
struct qmp_phy *qphy = phy_get_drvdata(phy);
struct qcom_qmp *qmp = qphy->qmp;
return 0;
}
-static int qcom_qmp_phy_poweron(struct phy *phy)
-{
- struct qmp_phy *qphy = phy_get_drvdata(phy);
- struct qcom_qmp *qmp = qphy->qmp;
- const struct qmp_phy_cfg *cfg = qmp->cfg;
- void __iomem *pcs = qphy->pcs;
- void __iomem *status;
- unsigned int mask, val;
- int ret = 0;
-
- if (cfg->type != PHY_TYPE_UFS)
- return 0;
-
- /*
- * For UFS PHY that has not software reset control, serdes start
- * should only happen when UFS driver explicitly calls phy_power_on
- * after it deasserts software reset.
- */
- if (cfg->no_pcs_sw_reset && !qmp->phy_initialized &&
- (qmp->init_count != 0)) {
- /* start SerDes and Phy-Coding-Sublayer */
- qphy_setbits(pcs, cfg->regs[QPHY_START_CTRL], cfg->start_ctrl);
-
- status = pcs + cfg->regs[QPHY_PCS_READY_STATUS];
- mask = cfg->mask_pcs_ready;
-
- ret = readl_poll_timeout(status, val, !(val & mask), 1,
- PHY_INIT_COMPLETE_TIMEOUT);
- if (ret) {
- dev_err(qmp->dev, "phy initialization timed-out\n");
- return ret;
- }
- qmp->phy_initialized = true;
- }
-
- return ret;
-}
-
static int qcom_qmp_phy_set_mode(struct phy *phy,
enum phy_mode mode, int submode)
{
}
static const struct phy_ops qcom_qmp_phy_gen_ops = {
- .init = qcom_qmp_phy_init,
- .exit = qcom_qmp_phy_exit,
- .power_on = qcom_qmp_phy_poweron,
+ .init = qcom_qmp_phy_enable,
+ .exit = qcom_qmp_phy_disable,
+ .set_mode = qcom_qmp_phy_set_mode,
+ .owner = THIS_MODULE,
+};
+
+static const struct phy_ops qcom_qmp_ufs_ops = {
+ .power_on = qcom_qmp_phy_enable,
+ .power_off = qcom_qmp_phy_disable,
.set_mode = qcom_qmp_phy_set_mode,
.owner = THIS_MODULE,
};
struct qcom_qmp *qmp = dev_get_drvdata(dev);
struct phy *generic_phy;
struct qmp_phy *qphy;
+ const struct phy_ops *ops = &qcom_qmp_phy_gen_ops;
char prop_name[MAX_PROP_NAME];
int ret;
}
}
- generic_phy = devm_phy_create(dev, np, &qcom_qmp_phy_gen_ops);
+ if (qmp->cfg->type == PHY_TYPE_UFS)
+ ops = &qcom_qmp_ufs_ops;
+
+ generic_phy = devm_phy_create(dev, np, ops);
if (IS_ERR(generic_phy)) {
ret = PTR_ERR(generic_phy);
dev_err(dev, "failed to create qphy %d\n", ret);
}, {
.compatible = "qcom,msm8996-qmp-usb3-phy",
.data = &msm8996_usb3phy_cfg,
+ }, {
+ .compatible = "qcom,msm8998-qmp-pcie-phy",
+ .data = &msm8998_pciephy_cfg,
}, {
.compatible = "qcom,msm8998-qmp-ufs-phy",
.data = &sdm845_ufsphy_cfg,
#define QSERDES_V3_RX_RX_BAND 0x110
#define QSERDES_V3_RX_RX_INTERFACE_MODE 0x11c
#define QSERDES_V3_RX_RX_MODE_00 0x164
+#define QSERDES_V3_RX_RX_MODE_01 0x168
/* Only for QMP V3 PHY - PCS registers */
#define QPHY_V3_PCS_POWER_DOWN_CONTROL 0x004
#define QPHY_V3_PCS_TSYNC_RSYNC_TIME 0x08c
#define QPHY_V3_PCS_PWRUP_RESET_DLY_TIME_AUXCLK 0x0a0
#define QPHY_V3_PCS_LP_WAKEUP_DLY_TIME_AUXCLK 0x0a4
+#define QPHY_V3_PCS_PLL_LOCK_CHK_DLY_TIME 0x0a8
#define QPHY_V3_PCS_LFPS_TX_ECSTART_EQTLOCK 0x0b0
#define QPHY_V3_PCS_RXEQTRAINING_WAIT_TIME 0x0b8
#define QPHY_V3_PCS_RXEQTRAINING_RUN_TIME 0x0bc
#define QPHY_V3_PCS_RX_MIN_HIBERN8_TIME 0x138
#define QPHY_V3_PCS_RX_SIGDET_CTRL1 0x13c
#define QPHY_V3_PCS_RX_SIGDET_CTRL2 0x140
+#define QPHY_V3_PCS_LP_WAKEUP_DLY_TIME_AUXCLK_MSB 0x1a8
+#define QPHY_V3_PCS_OSC_DTCT_ACTIONS 0x1ac
+#define QPHY_V3_PCS_SIGDET_CNTRL 0x1b0
#define QPHY_V3_PCS_TX_MID_TERM_CTRL1 0x1bc
#define QPHY_V3_PCS_MULTI_LANE_CTRL1 0x1c4
#define QPHY_V3_PCS_RX_SIGDET_LVL 0x1d8
+#define QPHY_V3_PCS_L1SS_WAKEUP_DLY_TIME_AUXCLK_LSB 0x1dc
+#define QPHY_V3_PCS_L1SS_WAKEUP_DLY_TIME_AUXCLK_MSB 0x1e0
#define QPHY_V3_PCS_REFGEN_REQ_CONFIG1 0x20c
#define QPHY_V3_PCS_REFGEN_REQ_CONFIG2 0x210
/* Only for QMP V3 PHY - PCS_MISC registers */
#define QPHY_V3_PCS_MISC_CLAMP_ENABLE 0x0c
+#define QPHY_V3_PCS_MISC_OSC_DTCT_CONFIG2 0x2c
+#define QPHY_V3_PCS_MISC_PCIE_INT_AUX_CLK_CONFIG1 0x44
+#define QPHY_V3_PCS_MISC_OSC_DTCT_MODE2_CONFIG2 0x54
+#define QPHY_V3_PCS_MISC_OSC_DTCT_MODE2_CONFIG4 0x5c
+#define QPHY_V3_PCS_MISC_OSC_DTCT_MODE2_CONFIG5 0x60
#endif
return ret;
}
- qphy->iface_clk = devm_clk_get(dev, "iface");
- if (IS_ERR(qphy->iface_clk)) {
- ret = PTR_ERR(qphy->iface_clk);
- if (ret == -EPROBE_DEFER)
- return ret;
- qphy->iface_clk = NULL;
- dev_dbg(dev, "failed to get iface clk, %d\n", ret);
- }
+ qphy->iface_clk = devm_clk_get_optional(dev, "iface");
+ if (IS_ERR(qphy->iface_clk))
+ return PTR_ERR(qphy->iface_clk);
qphy->phy_reset = devm_reset_control_get_by_index(&pdev->dev, 0);
if (IS_ERR(qphy->phy_reset)) {
#include <linux/clk.h>
#include <linux/phy/phy.h>
#include <linux/regulator/consumer.h>
+#include <linux/reset.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/io.h>
char name[UFS_QCOM_PHY_NAME_LEN];
struct ufs_qcom_phy_calibration *cached_regs;
int cached_regs_table_size;
- bool is_powered_on;
- bool is_started;
struct ufs_qcom_phy_specific_ops *phy_spec_ops;
enum phy_mode mode;
+ struct reset_control *ufs_reset;
};
/**
* and writes to QSERDES_RX_SIGDET_CNTRL attribute
*/
struct ufs_qcom_phy_specific_ops {
+ int (*calibrate)(struct ufs_qcom_phy *ufs_qcom_phy, bool is_rate_B);
void (*start_serdes)(struct ufs_qcom_phy *phy);
int (*is_physical_coding_sublayer_ready)(struct ufs_qcom_phy *phy);
void (*set_tx_lane_enable)(struct ufs_qcom_phy *phy, u32 val);
UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE;
}
-static int ufs_qcom_phy_qmp_14nm_init(struct phy *generic_phy)
-{
- struct ufs_qcom_phy *phy_common = get_ufs_qcom_phy(generic_phy);
- bool is_rate_B = false;
- int ret;
-
- if (phy_common->mode == PHY_MODE_UFS_HS_B)
- is_rate_B = true;
-
- ret = ufs_qcom_phy_qmp_14nm_phy_calibrate(phy_common, is_rate_B);
- if (!ret)
- /* phy calibrated, but yet to be started */
- phy_common->is_started = false;
-
- return ret;
-}
-
-static int ufs_qcom_phy_qmp_14nm_exit(struct phy *generic_phy)
-{
- return 0;
-}
-
static
int ufs_qcom_phy_qmp_14nm_set_mode(struct phy *generic_phy,
enum phy_mode mode, int submode)
}
static const struct phy_ops ufs_qcom_phy_qmp_14nm_phy_ops = {
- .init = ufs_qcom_phy_qmp_14nm_init,
- .exit = ufs_qcom_phy_qmp_14nm_exit,
.power_on = ufs_qcom_phy_power_on,
.power_off = ufs_qcom_phy_power_off,
.set_mode = ufs_qcom_phy_qmp_14nm_set_mode,
};
static struct ufs_qcom_phy_specific_ops phy_14nm_ops = {
+ .calibrate = ufs_qcom_phy_qmp_14nm_phy_calibrate,
.start_serdes = ufs_qcom_phy_qmp_14nm_start_serdes,
.is_physical_coding_sublayer_ready = ufs_qcom_phy_qmp_14nm_is_pcs_ready,
.set_tx_lane_enable = ufs_qcom_phy_qmp_14nm_set_tx_lane_enable,
UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE;
}
-static int ufs_qcom_phy_qmp_20nm_init(struct phy *generic_phy)
-{
- struct ufs_qcom_phy *phy_common = get_ufs_qcom_phy(generic_phy);
- bool is_rate_B = false;
- int ret;
-
- if (phy_common->mode == PHY_MODE_UFS_HS_B)
- is_rate_B = true;
-
- ret = ufs_qcom_phy_qmp_20nm_phy_calibrate(phy_common, is_rate_B);
- if (!ret)
- /* phy calibrated, but yet to be started */
- phy_common->is_started = false;
-
- return ret;
-}
-
-static int ufs_qcom_phy_qmp_20nm_exit(struct phy *generic_phy)
-{
- return 0;
-}
-
static
int ufs_qcom_phy_qmp_20nm_set_mode(struct phy *generic_phy,
enum phy_mode mode, int submode)
}
static const struct phy_ops ufs_qcom_phy_qmp_20nm_phy_ops = {
- .init = ufs_qcom_phy_qmp_20nm_init,
- .exit = ufs_qcom_phy_qmp_20nm_exit,
.power_on = ufs_qcom_phy_power_on,
.power_off = ufs_qcom_phy_power_off,
.set_mode = ufs_qcom_phy_qmp_20nm_set_mode,
};
static struct ufs_qcom_phy_specific_ops phy_20nm_ops = {
+ .calibrate = ufs_qcom_phy_qmp_20nm_phy_calibrate,
.start_serdes = ufs_qcom_phy_qmp_20nm_start_serdes,
.is_physical_coding_sublayer_ready = ufs_qcom_phy_qmp_20nm_is_pcs_ready,
.set_tx_lane_enable = ufs_qcom_phy_qmp_20nm_set_tx_lane_enable,
}
EXPORT_SYMBOL_GPL(ufs_qcom_phy_generic_probe);
+static int ufs_qcom_phy_get_reset(struct ufs_qcom_phy *phy_common)
+{
+ struct reset_control *reset;
+
+ if (phy_common->ufs_reset)
+ return 0;
+
+ reset = devm_reset_control_get_exclusive_by_index(phy_common->dev, 0);
+ if (IS_ERR(reset))
+ return PTR_ERR(reset);
+
+ phy_common->ufs_reset = reset;
+ return 0;
+}
+
static int __ufs_qcom_phy_clk_get(struct device *dev,
const char *name, struct clk **clk_out, bool err_print)
{
{
struct ufs_qcom_phy *phy_common = get_ufs_qcom_phy(generic_phy);
struct device *dev = phy_common->dev;
+ bool is_rate_B = false;
int err;
- if (phy_common->is_powered_on)
- return 0;
+ err = ufs_qcom_phy_get_reset(phy_common);
+ if (err)
+ return err;
- if (!phy_common->is_started) {
- err = ufs_qcom_phy_start_serdes(phy_common);
- if (err)
- return err;
+ err = reset_control_assert(phy_common->ufs_reset);
+ if (err)
+ return err;
- err = ufs_qcom_phy_is_pcs_ready(phy_common);
- if (err)
- return err;
+ if (phy_common->mode == PHY_MODE_UFS_HS_B)
+ is_rate_B = true;
- phy_common->is_started = true;
+ err = phy_common->phy_spec_ops->calibrate(phy_common, is_rate_B);
+ if (err)
+ return err;
+
+ err = reset_control_deassert(phy_common->ufs_reset);
+ if (err) {
+ dev_err(dev, "Failed to assert UFS PHY reset");
+ return err;
}
+ err = ufs_qcom_phy_start_serdes(phy_common);
+ if (err)
+ return err;
+
+ err = ufs_qcom_phy_is_pcs_ready(phy_common);
+ if (err)
+ return err;
+
err = ufs_qcom_phy_enable_vreg(dev, &phy_common->vdda_phy);
if (err) {
dev_err(dev, "%s enable vdda_phy failed, err=%d\n",
}
}
- phy_common->is_powered_on = true;
goto out;
out_disable_ref_clk:
{
struct ufs_qcom_phy *phy_common = get_ufs_qcom_phy(generic_phy);
- if (!phy_common->is_powered_on)
- return 0;
-
phy_common->phy_spec_ops->power_control(phy_common, false);
if (phy_common->vddp_ref_clk.reg)
ufs_qcom_phy_disable_vreg(phy_common->dev, &phy_common->vdda_pll);
ufs_qcom_phy_disable_vreg(phy_common->dev, &phy_common->vdda_phy);
- phy_common->is_powered_on = false;
-
+ reset_control_assert(phy_common->ufs_reset);
return 0;
}
EXPORT_SYMBOL_GPL(ufs_qcom_phy_power_off);
config PHY_RCAR_GEN3_USB2
tristate "Renesas R-Car generation 3 USB 2.0 PHY driver"
depends on ARCH_RENESAS
- depends on EXTCON
+ depends on EXTCON || !EXTCON # if EXTCON=m, this cannot be built-in
depends on USB_SUPPORT
select GENERIC_PHY
select USB_COMMON
*
* Copyright (C) 2014 Renesas Solutions Corp.
* Copyright (C) 2014 Cogent Embedded, Inc.
+ * Copyright (C) 2019 Renesas Electronics Corp.
*/
#include <linux/clk.h>
#include <linux/platform_device.h>
#include <linux/spinlock.h>
#include <linux/atomic.h>
+#include <linux/of_device.h>
#define USBHS_LPSTS 0x02
#define USBHS_UGCTRL 0x80
#define USBHS_UGCTRL2_USB0SEL 0x00000030
#define USBHS_UGCTRL2_USB0SEL_PCI 0x00000010
#define USBHS_UGCTRL2_USB0SEL_HS_USB 0x00000030
+#define USBHS_UGCTRL2_USB0SEL_USB20 0x00000010
+#define USBHS_UGCTRL2_USB0SEL_HS_USB20 0x00000020
/* USB General status register (UGSTS) */
#define USBHS_UGSTS_LOCK 0x00000100 /* From technical update */
struct rcar_gen2_channel *channels;
};
+struct rcar_gen2_phy_data {
+ const struct phy_ops *gen2_phy_ops;
+ const u32 (*select_value)[PHYS_PER_CHANNEL];
+};
+
static int rcar_gen2_phy_init(struct phy *p)
{
struct rcar_gen2_phy *phy = phy_get_drvdata(p);
return 0;
}
+static int rz_g1c_phy_power_on(struct phy *p)
+{
+ struct rcar_gen2_phy *phy = phy_get_drvdata(p);
+ struct rcar_gen2_phy_driver *drv = phy->channel->drv;
+ void __iomem *base = drv->base;
+ unsigned long flags;
+ u32 value;
+
+ spin_lock_irqsave(&drv->lock, flags);
+
+ /* Power on USBHS PHY */
+ value = readl(base + USBHS_UGCTRL);
+ value &= ~USBHS_UGCTRL_PLLRESET;
+ writel(value, base + USBHS_UGCTRL);
+
+ /* As per the data sheet wait 340 micro sec for power stable */
+ udelay(340);
+
+ if (phy->select_value == USBHS_UGCTRL2_USB0SEL_HS_USB20) {
+ value = readw(base + USBHS_LPSTS);
+ value |= USBHS_LPSTS_SUSPM;
+ writew(value, base + USBHS_LPSTS);
+ }
+
+ spin_unlock_irqrestore(&drv->lock, flags);
+
+ return 0;
+}
+
+static int rz_g1c_phy_power_off(struct phy *p)
+{
+ struct rcar_gen2_phy *phy = phy_get_drvdata(p);
+ struct rcar_gen2_phy_driver *drv = phy->channel->drv;
+ void __iomem *base = drv->base;
+ unsigned long flags;
+ u32 value;
+
+ spin_lock_irqsave(&drv->lock, flags);
+ /* Power off USBHS PHY */
+ if (phy->select_value == USBHS_UGCTRL2_USB0SEL_HS_USB20) {
+ value = readw(base + USBHS_LPSTS);
+ value &= ~USBHS_LPSTS_SUSPM;
+ writew(value, base + USBHS_LPSTS);
+ }
+
+ value = readl(base + USBHS_UGCTRL);
+ value |= USBHS_UGCTRL_PLLRESET;
+ writel(value, base + USBHS_UGCTRL);
+
+ spin_unlock_irqrestore(&drv->lock, flags);
+
+ return 0;
+}
+
static const struct phy_ops rcar_gen2_phy_ops = {
.init = rcar_gen2_phy_init,
.exit = rcar_gen2_phy_exit,
.owner = THIS_MODULE,
};
+static const struct phy_ops rz_g1c_phy_ops = {
+ .init = rcar_gen2_phy_init,
+ .exit = rcar_gen2_phy_exit,
+ .power_on = rz_g1c_phy_power_on,
+ .power_off = rz_g1c_phy_power_off,
+ .owner = THIS_MODULE,
+};
+
+static const u32 pci_select_value[][PHYS_PER_CHANNEL] = {
+ [0] = { USBHS_UGCTRL2_USB0SEL_PCI, USBHS_UGCTRL2_USB0SEL_HS_USB },
+ [2] = { USBHS_UGCTRL2_USB2SEL_PCI, USBHS_UGCTRL2_USB2SEL_USB30 },
+};
+
+static const u32 usb20_select_value[][PHYS_PER_CHANNEL] = {
+ { USBHS_UGCTRL2_USB0SEL_USB20, USBHS_UGCTRL2_USB0SEL_HS_USB20 },
+};
+
+static const struct rcar_gen2_phy_data rcar_gen2_usb_phy_data = {
+ .gen2_phy_ops = &rcar_gen2_phy_ops,
+ .select_value = pci_select_value,
+};
+
+static const struct rcar_gen2_phy_data rz_g1c_usb_phy_data = {
+ .gen2_phy_ops = &rz_g1c_phy_ops,
+ .select_value = usb20_select_value,
+};
+
static const struct of_device_id rcar_gen2_phy_match_table[] = {
- { .compatible = "renesas,usb-phy-r8a7790" },
- { .compatible = "renesas,usb-phy-r8a7791" },
- { .compatible = "renesas,usb-phy-r8a7794" },
- { .compatible = "renesas,rcar-gen2-usb-phy" },
- { }
+ {
+ .compatible = "renesas,usb-phy-r8a77470",
+ .data = &rz_g1c_usb_phy_data,
+ },
+ {
+ .compatible = "renesas,usb-phy-r8a7790",
+ .data = &rcar_gen2_usb_phy_data,
+ },
+ {
+ .compatible = "renesas,usb-phy-r8a7791",
+ .data = &rcar_gen2_usb_phy_data,
+ },
+ {
+ .compatible = "renesas,usb-phy-r8a7794",
+ .data = &rcar_gen2_usb_phy_data,
+ },
+ {
+ .compatible = "renesas,rcar-gen2-usb-phy",
+ .data = &rcar_gen2_usb_phy_data,
+ },
+ { /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, rcar_gen2_phy_match_table);
[2] = USBHS_UGCTRL2_USB2SEL,
};
-static const u32 select_value[][PHYS_PER_CHANNEL] = {
- [0] = { USBHS_UGCTRL2_USB0SEL_PCI, USBHS_UGCTRL2_USB0SEL_HS_USB },
- [2] = { USBHS_UGCTRL2_USB2SEL_PCI, USBHS_UGCTRL2_USB2SEL_USB30 },
-};
-
static int rcar_gen2_phy_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct resource *res;
void __iomem *base;
struct clk *clk;
+ const struct rcar_gen2_phy_data *data;
int i = 0;
if (!dev->of_node) {
drv->clk = clk;
drv->base = base;
+ data = of_device_get_match_data(dev);
+ if (!data)
+ return -EINVAL;
+
drv->num_channels = of_get_child_count(dev->of_node);
drv->channels = devm_kcalloc(dev, drv->num_channels,
sizeof(struct rcar_gen2_channel),
phy->channel = channel;
phy->number = n;
- phy->select_value = select_value[channel_num][n];
+ phy->select_value = data->select_value[channel_num][n];
phy->phy = devm_phy_create(dev, NULL,
- &rcar_gen2_phy_ops);
+ data->gen2_phy_ops);
if (IS_ERR(phy->phy)) {
dev_err(dev, "Failed to create PHY\n");
return PTR_ERR(phy->phy);
/* INT_ENABLE */
#define USB2_INT_ENABLE_UCOM_INTEN BIT(3)
-#define USB2_INT_ENABLE_USBH_INTB_EN BIT(2)
-#define USB2_INT_ENABLE_USBH_INTA_EN BIT(1)
-#define USB2_INT_ENABLE_INIT (USB2_INT_ENABLE_UCOM_INTEN | \
- USB2_INT_ENABLE_USBH_INTB_EN | \
- USB2_INT_ENABLE_USBH_INTA_EN)
+#define USB2_INT_ENABLE_USBH_INTB_EN BIT(2) /* For EHCI */
+#define USB2_INT_ENABLE_USBH_INTA_EN BIT(1) /* For OHCI */
/* USBCTR */
#define USB2_USBCTR_DIRPD BIT(2)
#define USB2_ADPCTRL_IDPULLUP BIT(5) /* 1 = ID sampling is enabled */
#define USB2_ADPCTRL_DRVVBUS BIT(4)
+#define NUM_OF_PHYS 4
+enum rcar_gen3_phy_index {
+ PHY_INDEX_BOTH_HC,
+ PHY_INDEX_OHCI,
+ PHY_INDEX_EHCI,
+ PHY_INDEX_HSUSB
+};
+
+static const u32 rcar_gen3_int_enable[NUM_OF_PHYS] = {
+ USB2_INT_ENABLE_USBH_INTB_EN | USB2_INT_ENABLE_USBH_INTA_EN,
+ USB2_INT_ENABLE_USBH_INTA_EN,
+ USB2_INT_ENABLE_USBH_INTB_EN,
+ 0
+};
+
+struct rcar_gen3_phy {
+ struct phy *phy;
+ struct rcar_gen3_chan *ch;
+ u32 int_enable_bits;
+ bool initialized;
+ bool otg_initialized;
+ bool powered;
+};
+
struct rcar_gen3_chan {
void __iomem *base;
+ struct device *dev; /* platform_device's device */
struct extcon_dev *extcon;
- struct phy *phy;
+ struct rcar_gen3_phy rphys[NUM_OF_PHYS];
struct regulator *vbus;
struct work_struct work;
enum usb_dr_mode dr_mode;
void __iomem *usb2_base = ch->base;
u32 val = readl(usb2_base + USB2_COMMCTRL);
- dev_vdbg(&ch->phy->dev, "%s: %08x, %d\n", __func__, val, host);
+ dev_vdbg(ch->dev, "%s: %08x, %d\n", __func__, val, host);
if (host)
val &= ~USB2_COMMCTRL_OTG_PERI;
else
void __iomem *usb2_base = ch->base;
u32 val = readl(usb2_base + USB2_LINECTRL1);
- dev_vdbg(&ch->phy->dev, "%s: %08x, %d, %d\n", __func__, val, dp, dm);
+ dev_vdbg(ch->dev, "%s: %08x, %d, %d\n", __func__, val, dp, dm);
val &= ~(USB2_LINECTRL1_DP_RPD | USB2_LINECTRL1_DM_RPD);
if (dp)
val |= USB2_LINECTRL1_DP_RPD;
void __iomem *usb2_base = ch->base;
u32 val = readl(usb2_base + USB2_ADPCTRL);
- dev_vdbg(&ch->phy->dev, "%s: %08x, %d\n", __func__, val, vbus);
+ dev_vdbg(ch->dev, "%s: %08x, %d\n", __func__, val, vbus);
if (vbus)
val |= USB2_ADPCTRL_DRVVBUS;
else
return PHY_MODE_USB_DEVICE;
}
+static bool rcar_gen3_is_any_rphy_initialized(struct rcar_gen3_chan *ch)
+{
+ int i;
+
+ for (i = 0; i < NUM_OF_PHYS; i++) {
+ if (ch->rphys[i].initialized)
+ return true;
+ }
+
+ return false;
+}
+
+static bool rcar_gen3_needs_init_otg(struct rcar_gen3_chan *ch)
+{
+ int i;
+
+ for (i = 0; i < NUM_OF_PHYS; i++) {
+ if (ch->rphys[i].otg_initialized)
+ return false;
+ }
+
+ return true;
+}
+
+static bool rcar_gen3_are_all_rphys_power_off(struct rcar_gen3_chan *ch)
+{
+ int i;
+
+ for (i = 0; i < NUM_OF_PHYS; i++) {
+ if (ch->rphys[i].powered)
+ return false;
+ }
+
+ return true;
+}
+
static ssize_t role_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
bool is_b_device;
enum phy_mode cur_mode, new_mode;
- if (!ch->is_otg_channel || !ch->phy->init_count)
+ if (!ch->is_otg_channel || !rcar_gen3_is_any_rphy_initialized(ch))
return -EIO;
if (!strncmp(buf, "host", strlen("host")))
{
struct rcar_gen3_chan *ch = dev_get_drvdata(dev);
- if (!ch->is_otg_channel || !ch->phy->init_count)
+ if (!ch->is_otg_channel || !rcar_gen3_is_any_rphy_initialized(ch))
return -EIO;
return sprintf(buf, "%s\n", rcar_gen3_is_host(ch) ? "host" :
static int rcar_gen3_phy_usb2_init(struct phy *p)
{
- struct rcar_gen3_chan *channel = phy_get_drvdata(p);
+ struct rcar_gen3_phy *rphy = phy_get_drvdata(p);
+ struct rcar_gen3_chan *channel = rphy->ch;
void __iomem *usb2_base = channel->base;
+ u32 val;
/* Initialize USB2 part */
- writel(USB2_INT_ENABLE_INIT, usb2_base + USB2_INT_ENABLE);
+ val = readl(usb2_base + USB2_INT_ENABLE);
+ val |= USB2_INT_ENABLE_UCOM_INTEN | rphy->int_enable_bits;
+ writel(val, usb2_base + USB2_INT_ENABLE);
writel(USB2_SPD_RSM_TIMSET_INIT, usb2_base + USB2_SPD_RSM_TIMSET);
writel(USB2_OC_TIMSET_INIT, usb2_base + USB2_OC_TIMSET);
/* Initialize otg part */
- if (channel->is_otg_channel)
- rcar_gen3_init_otg(channel);
+ if (channel->is_otg_channel) {
+ if (rcar_gen3_needs_init_otg(channel))
+ rcar_gen3_init_otg(channel);
+ rphy->otg_initialized = true;
+ }
+
+ rphy->initialized = true;
return 0;
}
static int rcar_gen3_phy_usb2_exit(struct phy *p)
{
- struct rcar_gen3_chan *channel = phy_get_drvdata(p);
+ struct rcar_gen3_phy *rphy = phy_get_drvdata(p);
+ struct rcar_gen3_chan *channel = rphy->ch;
+ void __iomem *usb2_base = channel->base;
+ u32 val;
+
+ rphy->initialized = false;
+
+ if (channel->is_otg_channel)
+ rphy->otg_initialized = false;
- writel(0, channel->base + USB2_INT_ENABLE);
+ val = readl(usb2_base + USB2_INT_ENABLE);
+ val &= ~rphy->int_enable_bits;
+ if (!rcar_gen3_is_any_rphy_initialized(channel))
+ val &= ~USB2_INT_ENABLE_UCOM_INTEN;
+ writel(val, usb2_base + USB2_INT_ENABLE);
return 0;
}
static int rcar_gen3_phy_usb2_power_on(struct phy *p)
{
- struct rcar_gen3_chan *channel = phy_get_drvdata(p);
+ struct rcar_gen3_phy *rphy = phy_get_drvdata(p);
+ struct rcar_gen3_chan *channel = rphy->ch;
void __iomem *usb2_base = channel->base;
u32 val;
int ret;
+ if (!rcar_gen3_are_all_rphys_power_off(channel))
+ return 0;
+
if (channel->vbus) {
ret = regulator_enable(channel->vbus);
if (ret)
val &= ~USB2_USBCTR_PLL_RST;
writel(val, usb2_base + USB2_USBCTR);
+ rphy->powered = true;
+
return 0;
}
static int rcar_gen3_phy_usb2_power_off(struct phy *p)
{
- struct rcar_gen3_chan *channel = phy_get_drvdata(p);
+ struct rcar_gen3_phy *rphy = phy_get_drvdata(p);
+ struct rcar_gen3_chan *channel = rphy->ch;
int ret = 0;
+ rphy->powered = false;
+
+ if (!rcar_gen3_are_all_rphys_power_off(channel))
+ return 0;
+
if (channel->vbus)
ret = regulator_disable(channel->vbus);
.owner = THIS_MODULE,
};
+static const struct phy_ops rz_g1c_phy_usb2_ops = {
+ .init = rcar_gen3_phy_usb2_init,
+ .exit = rcar_gen3_phy_usb2_exit,
+ .owner = THIS_MODULE,
+};
+
static irqreturn_t rcar_gen3_phy_usb2_irq(int irq, void *_ch)
{
struct rcar_gen3_chan *ch = _ch;
irqreturn_t ret = IRQ_NONE;
if (status & USB2_OBINT_BITS) {
- dev_vdbg(&ch->phy->dev, "%s: %08x\n", __func__, status);
+ dev_vdbg(ch->dev, "%s: %08x\n", __func__, status);
writel(USB2_OBINT_BITS, usb2_base + USB2_OBINTSTA);
rcar_gen3_device_recognition(ch);
ret = IRQ_HANDLED;
}
static const struct of_device_id rcar_gen3_phy_usb2_match_table[] = {
- { .compatible = "renesas,usb2-phy-r8a7795" },
- { .compatible = "renesas,usb2-phy-r8a7796" },
- { .compatible = "renesas,usb2-phy-r8a77965" },
- { .compatible = "renesas,rcar-gen3-usb2-phy" },
- { }
+ {
+ .compatible = "renesas,usb2-phy-r8a77470",
+ .data = &rz_g1c_phy_usb2_ops,
+ },
+ {
+ .compatible = "renesas,usb2-phy-r8a7795",
+ .data = &rcar_gen3_phy_usb2_ops,
+ },
+ {
+ .compatible = "renesas,usb2-phy-r8a7796",
+ .data = &rcar_gen3_phy_usb2_ops,
+ },
+ {
+ .compatible = "renesas,usb2-phy-r8a77965",
+ .data = &rcar_gen3_phy_usb2_ops,
+ },
+ {
+ .compatible = "renesas,rcar-gen3-usb2-phy",
+ .data = &rcar_gen3_phy_usb2_ops,
+ },
+ { /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, rcar_gen3_phy_usb2_match_table);
EXTCON_NONE,
};
+static struct phy *rcar_gen3_phy_usb2_xlate(struct device *dev,
+ struct of_phandle_args *args)
+{
+ struct rcar_gen3_chan *ch = dev_get_drvdata(dev);
+
+ if (args->args_count == 0) /* For old version dts */
+ return ch->rphys[PHY_INDEX_BOTH_HC].phy;
+ else if (args->args_count > 1) /* Prevent invalid args count */
+ return ERR_PTR(-ENODEV);
+
+ if (args->args[0] >= NUM_OF_PHYS)
+ return ERR_PTR(-ENODEV);
+
+ return ch->rphys[args->args[0]].phy;
+}
+
+static enum usb_dr_mode rcar_gen3_get_dr_mode(struct device_node *np)
+{
+ enum usb_dr_mode candidate = USB_DR_MODE_UNKNOWN;
+ int i;
+
+ /*
+ * If one of device nodes has other dr_mode except UNKNOWN,
+ * this function returns UNKNOWN. To achieve backward compatibility,
+ * this loop starts the index as 0.
+ */
+ for (i = 0; i < NUM_OF_PHYS; i++) {
+ enum usb_dr_mode mode = of_usb_get_dr_mode_by_phy(np, i);
+
+ if (mode != USB_DR_MODE_UNKNOWN) {
+ if (candidate == USB_DR_MODE_UNKNOWN)
+ candidate = mode;
+ else if (candidate != mode)
+ return USB_DR_MODE_UNKNOWN;
+ }
+ }
+
+ return candidate;
+}
+
static int rcar_gen3_phy_usb2_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct rcar_gen3_chan *channel;
struct phy_provider *provider;
struct resource *res;
- int irq, ret = 0;
+ const struct phy_ops *phy_usb2_ops;
+ int irq, ret = 0, i;
if (!dev->of_node) {
dev_err(dev, "This driver needs device tree\n");
dev_err(dev, "No irq handler (%d)\n", irq);
}
- channel->dr_mode = of_usb_get_dr_mode_by_phy(dev->of_node, 0);
+ channel->dr_mode = rcar_gen3_get_dr_mode(dev->of_node);
if (channel->dr_mode != USB_DR_MODE_UNKNOWN) {
int ret;
* And then, phy-core will manage runtime pm for this device.
*/
pm_runtime_enable(dev);
- channel->phy = devm_phy_create(dev, NULL, &rcar_gen3_phy_usb2_ops);
- if (IS_ERR(channel->phy)) {
- dev_err(dev, "Failed to create USB2 PHY\n");
- ret = PTR_ERR(channel->phy);
- goto error;
+ phy_usb2_ops = of_device_get_match_data(dev);
+ if (!phy_usb2_ops)
+ return -EINVAL;
+
+ for (i = 0; i < NUM_OF_PHYS; i++) {
+ channel->rphys[i].phy = devm_phy_create(dev, NULL,
+ phy_usb2_ops);
+ if (IS_ERR(channel->rphys[i].phy)) {
+ dev_err(dev, "Failed to create USB2 PHY\n");
+ ret = PTR_ERR(channel->rphys[i].phy);
+ goto error;
+ }
+ channel->rphys[i].ch = channel;
+ channel->rphys[i].int_enable_bits = rcar_gen3_int_enable[i];
+ phy_set_drvdata(channel->rphys[i].phy, &channel->rphys[i]);
}
channel->vbus = devm_regulator_get_optional(dev, "vbus");
}
platform_set_drvdata(pdev, channel);
- phy_set_drvdata(channel->phy, channel);
+ channel->dev = dev;
- provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+ provider = devm_of_phy_provider_register(dev, rcar_gen3_phy_usb2_xlate);
if (IS_ERR(provider)) {
dev_err(dev, "Failed to register PHY provider\n");
ret = PTR_ERR(provider);
unsigned int reg_offset;
struct regmap *reg_base;
struct clk *emmcclk;
+ unsigned int drive_impedance;
};
static int rockchip_emmc_phy_power(struct phy *phy, bool on_off)
{
struct rockchip_emmc_phy *rk_phy = phy_get_drvdata(phy);
- /* Drive impedance: 50 Ohm */
+ /* Drive impedance: from DTS */
regmap_write(rk_phy->reg_base,
rk_phy->reg_offset + GRF_EMMCPHY_CON6,
- HIWORD_UPDATE(PHYCTRL_DR_50OHM,
+ HIWORD_UPDATE(rk_phy->drive_impedance,
PHYCTRL_DR_MASK,
PHYCTRL_DR_SHIFT));
.owner = THIS_MODULE,
};
+static u32 convert_drive_impedance_ohm(struct platform_device *pdev, u32 dr_ohm)
+{
+ switch (dr_ohm) {
+ case 100:
+ return PHYCTRL_DR_100OHM;
+ case 66:
+ return PHYCTRL_DR_66OHM;
+ case 50:
+ return PHYCTRL_DR_50OHM;
+ case 40:
+ return PHYCTRL_DR_40OHM;
+ case 33:
+ return PHYCTRL_DR_33OHM;
+ }
+
+ dev_warn(&pdev->dev, "Invalid value %u for drive-impedance-ohm.\n",
+ dr_ohm);
+ return PHYCTRL_DR_50OHM;
+}
+
static int rockchip_emmc_phy_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct phy_provider *phy_provider;
struct regmap *grf;
unsigned int reg_offset;
+ u32 val;
if (!dev->parent || !dev->parent->of_node)
return -ENODEV;
rk_phy->reg_offset = reg_offset;
rk_phy->reg_base = grf;
+ rk_phy->drive_impedance = PHYCTRL_DR_50OHM;
+
+ if (!of_property_read_u32(dev->of_node, "drive-impedance-ohm", &val))
+ rk_phy->drive_impedance = convert_drive_impedance_ohm(pdev, val);
generic_phy = devm_phy_create(dev, dev->of_node, &ops);
if (IS_ERR(generic_phy)) {
if (IS_ERR(priv->clk_parent))
return PTR_ERR(priv->clk_parent);
- priv->clk_ext = devm_clk_get(dev, "phy-ext");
- if (IS_ERR(priv->clk_ext)) {
- if (PTR_ERR(priv->clk_ext) == -ENOENT)
- priv->clk_ext = NULL;
- else
- return PTR_ERR(priv->clk_ext);
- }
+ priv->clk_ext = devm_clk_get_optional(dev, "phy-ext");
+ if (IS_ERR(priv->clk_ext))
+ return PTR_ERR(priv->clk_ext);
priv->rst = devm_reset_control_get_shared(dev, "phy");
if (IS_ERR(priv->rst))
if (IS_ERR(priv->clk))
return PTR_ERR(priv->clk);
- priv->clk_ext = devm_clk_get(dev, "phy-ext");
- if (IS_ERR(priv->clk_ext)) {
- if (PTR_ERR(priv->clk_ext) == -ENOENT)
- priv->clk_ext = NULL;
- else
- return PTR_ERR(priv->clk_ext);
- }
+ priv->clk_ext = devm_clk_get_optional(dev, "phy-ext");
+ if (IS_ERR(priv->clk_ext))
+ return PTR_ERR(priv->clk_ext);
priv->rst = devm_reset_control_get_shared(dev, "phy");
if (IS_ERR(priv->rst))
phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_124_SOC) += xusb-tegra124.o
phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_132_SOC) += xusb-tegra124.o
phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_210_SOC) += xusb-tegra210.o
+phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_186_SOC) += xusb-tegra186.o
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2016-2019, NVIDIA CORPORATION. All rights reserved.
+ */
+
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/phy/phy.h>
+#include <linux/regulator/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/slab.h>
+
+#include <soc/tegra/fuse.h>
+
+#include "xusb.h"
+
+/* FUSE USB_CALIB registers */
+#define HS_CURR_LEVEL_PADX_SHIFT(x) ((x) ? (11 + (x - 1) * 6) : 0)
+#define HS_CURR_LEVEL_PAD_MASK 0x3f
+#define HS_TERM_RANGE_ADJ_SHIFT 7
+#define HS_TERM_RANGE_ADJ_MASK 0xf
+#define HS_SQUELCH_SHIFT 29
+#define HS_SQUELCH_MASK 0x7
+
+#define RPD_CTRL_SHIFT 0
+#define RPD_CTRL_MASK 0x1f
+
+/* XUSB PADCTL registers */
+#define XUSB_PADCTL_USB2_PAD_MUX 0x4
+#define USB2_PORT_SHIFT(x) ((x) * 2)
+#define USB2_PORT_MASK 0x3
+#define PORT_XUSB 1
+#define HSIC_PORT_SHIFT(x) ((x) + 20)
+#define HSIC_PORT_MASK 0x1
+#define PORT_HSIC 0
+
+#define XUSB_PADCTL_USB2_PORT_CAP 0x8
+#define XUSB_PADCTL_SS_PORT_CAP 0xc
+#define PORTX_CAP_SHIFT(x) ((x) * 4)
+#define PORT_CAP_MASK 0x3
+#define PORT_CAP_DISABLED 0x0
+#define PORT_CAP_HOST 0x1
+#define PORT_CAP_DEVICE 0x2
+#define PORT_CAP_OTG 0x3
+
+#define XUSB_PADCTL_ELPG_PROGRAM 0x20
+#define USB2_PORT_WAKE_INTERRUPT_ENABLE(x) BIT(x)
+#define USB2_PORT_WAKEUP_EVENT(x) BIT((x) + 7)
+#define SS_PORT_WAKE_INTERRUPT_ENABLE(x) BIT((x) + 14)
+#define SS_PORT_WAKEUP_EVENT(x) BIT((x) + 21)
+#define USB2_HSIC_PORT_WAKE_INTERRUPT_ENABLE(x) BIT((x) + 28)
+#define USB2_HSIC_PORT_WAKEUP_EVENT(x) BIT((x) + 30)
+#define ALL_WAKE_EVENTS \
+ (USB2_PORT_WAKEUP_EVENT(0) | USB2_PORT_WAKEUP_EVENT(1) | \
+ USB2_PORT_WAKEUP_EVENT(2) | SS_PORT_WAKEUP_EVENT(0) | \
+ SS_PORT_WAKEUP_EVENT(1) | SS_PORT_WAKEUP_EVENT(2) | \
+ USB2_HSIC_PORT_WAKEUP_EVENT(0))
+
+#define XUSB_PADCTL_ELPG_PROGRAM_1 0x24
+#define SSPX_ELPG_CLAMP_EN(x) BIT(0 + (x) * 3)
+#define SSPX_ELPG_CLAMP_EN_EARLY(x) BIT(1 + (x) * 3)
+#define SSPX_ELPG_VCORE_DOWN(x) BIT(2 + (x) * 3)
+
+#define XUSB_PADCTL_USB2_OTG_PADX_CTL0(x) (0x88 + (x) * 0x40)
+#define HS_CURR_LEVEL(x) ((x) & 0x3f)
+#define TERM_SEL BIT(25)
+#define USB2_OTG_PD BIT(26)
+#define USB2_OTG_PD2 BIT(27)
+#define USB2_OTG_PD2_OVRD_EN BIT(28)
+#define USB2_OTG_PD_ZI BIT(29)
+
+#define XUSB_PADCTL_USB2_OTG_PADX_CTL1(x) (0x8c + (x) * 0x40)
+#define USB2_OTG_PD_DR BIT(2)
+#define TERM_RANGE_ADJ(x) (((x) & 0xf) << 3)
+#define RPD_CTRL(x) (((x) & 0x1f) << 26)
+
+#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0 0x284
+#define BIAS_PAD_PD BIT(11)
+#define HS_SQUELCH_LEVEL(x) (((x) & 0x7) << 0)
+
+#define XUSB_PADCTL_USB2_BIAS_PAD_CTL1 0x288
+#define USB2_TRK_START_TIMER(x) (((x) & 0x7f) << 12)
+#define USB2_TRK_DONE_RESET_TIMER(x) (((x) & 0x7f) << 19)
+#define USB2_PD_TRK BIT(26)
+
+#define XUSB_PADCTL_HSIC_PADX_CTL0(x) (0x300 + (x) * 0x20)
+#define HSIC_PD_TX_DATA0 BIT(1)
+#define HSIC_PD_TX_STROBE BIT(3)
+#define HSIC_PD_RX_DATA0 BIT(4)
+#define HSIC_PD_RX_STROBE BIT(6)
+#define HSIC_PD_ZI_DATA0 BIT(7)
+#define HSIC_PD_ZI_STROBE BIT(9)
+#define HSIC_RPD_DATA0 BIT(13)
+#define HSIC_RPD_STROBE BIT(15)
+#define HSIC_RPU_DATA0 BIT(16)
+#define HSIC_RPU_STROBE BIT(18)
+
+#define XUSB_PADCTL_HSIC_PAD_TRK_CTL0 0x340
+#define HSIC_TRK_START_TIMER(x) (((x) & 0x7f) << 5)
+#define HSIC_TRK_DONE_RESET_TIMER(x) (((x) & 0x7f) << 12)
+#define HSIC_PD_TRK BIT(19)
+
+#define USB2_VBUS_ID 0x360
+#define VBUS_OVERRIDE BIT(14)
+#define ID_OVERRIDE(x) (((x) & 0xf) << 18)
+#define ID_OVERRIDE_FLOATING ID_OVERRIDE(8)
+#define ID_OVERRIDE_GROUNDED ID_OVERRIDE(0)
+
+#define TEGRA186_LANE(_name, _offset, _shift, _mask, _type) \
+ { \
+ .name = _name, \
+ .offset = _offset, \
+ .shift = _shift, \
+ .mask = _mask, \
+ .num_funcs = ARRAY_SIZE(tegra186_##_type##_functions), \
+ .funcs = tegra186_##_type##_functions, \
+ }
+
+struct tegra_xusb_fuse_calibration {
+ u32 *hs_curr_level;
+ u32 hs_squelch;
+ u32 hs_term_range_adj;
+ u32 rpd_ctrl;
+};
+
+struct tegra186_xusb_padctl {
+ struct tegra_xusb_padctl base;
+
+ struct tegra_xusb_fuse_calibration calib;
+
+ /* UTMI bias and tracking */
+ struct clk *usb2_trk_clk;
+ unsigned int bias_pad_enable;
+};
+
+static inline struct tegra186_xusb_padctl *
+to_tegra186_xusb_padctl(struct tegra_xusb_padctl *padctl)
+{
+ return container_of(padctl, struct tegra186_xusb_padctl, base);
+}
+
+/* USB 2.0 UTMI PHY support */
+static struct tegra_xusb_lane *
+tegra186_usb2_lane_probe(struct tegra_xusb_pad *pad, struct device_node *np,
+ unsigned int index)
+{
+ struct tegra_xusb_usb2_lane *usb2;
+ int err;
+
+ usb2 = kzalloc(sizeof(*usb2), GFP_KERNEL);
+ if (!usb2)
+ return ERR_PTR(-ENOMEM);
+
+ INIT_LIST_HEAD(&usb2->base.list);
+ usb2->base.soc = &pad->soc->lanes[index];
+ usb2->base.index = index;
+ usb2->base.pad = pad;
+ usb2->base.np = np;
+
+ err = tegra_xusb_lane_parse_dt(&usb2->base, np);
+ if (err < 0) {
+ kfree(usb2);
+ return ERR_PTR(err);
+ }
+
+ return &usb2->base;
+}
+
+static void tegra186_usb2_lane_remove(struct tegra_xusb_lane *lane)
+{
+ struct tegra_xusb_usb2_lane *usb2 = to_usb2_lane(lane);
+
+ kfree(usb2);
+}
+
+static const struct tegra_xusb_lane_ops tegra186_usb2_lane_ops = {
+ .probe = tegra186_usb2_lane_probe,
+ .remove = tegra186_usb2_lane_remove,
+};
+
+static void tegra186_utmi_bias_pad_power_on(struct tegra_xusb_padctl *padctl)
+{
+ struct tegra186_xusb_padctl *priv = to_tegra186_xusb_padctl(padctl);
+ struct device *dev = padctl->dev;
+ u32 value;
+ int err;
+
+ mutex_lock(&padctl->lock);
+
+ if (priv->bias_pad_enable++ > 0) {
+ mutex_unlock(&padctl->lock);
+ return;
+ }
+
+ err = clk_prepare_enable(priv->usb2_trk_clk);
+ if (err < 0)
+ dev_warn(dev, "failed to enable USB2 trk clock: %d\n", err);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL1);
+ value &= ~USB2_TRK_START_TIMER(~0);
+ value |= USB2_TRK_START_TIMER(0x1e);
+ value &= ~USB2_TRK_DONE_RESET_TIMER(~0);
+ value |= USB2_TRK_DONE_RESET_TIMER(0xa);
+ padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL1);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL0);
+ value &= ~BIAS_PAD_PD;
+ value &= ~HS_SQUELCH_LEVEL(~0);
+ value |= HS_SQUELCH_LEVEL(priv->calib.hs_squelch);
+ padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL0);
+
+ udelay(1);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL1);
+ value &= ~USB2_PD_TRK;
+ padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL1);
+
+ mutex_unlock(&padctl->lock);
+}
+
+static void tegra186_utmi_bias_pad_power_off(struct tegra_xusb_padctl *padctl)
+{
+ struct tegra186_xusb_padctl *priv = to_tegra186_xusb_padctl(padctl);
+ u32 value;
+
+ mutex_lock(&padctl->lock);
+
+ if (WARN_ON(priv->bias_pad_enable == 0)) {
+ mutex_unlock(&padctl->lock);
+ return;
+ }
+
+ if (--priv->bias_pad_enable > 0) {
+ mutex_unlock(&padctl->lock);
+ return;
+ }
+
+ value = padctl_readl(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL1);
+ value |= USB2_PD_TRK;
+ padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL1);
+
+ clk_disable_unprepare(priv->usb2_trk_clk);
+
+ mutex_unlock(&padctl->lock);
+}
+
+static void tegra_phy_xusb_utmi_pad_power_on(struct phy *phy)
+{
+ struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+ struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+ struct tegra_xusb_usb2_port *port;
+ struct device *dev = padctl->dev;
+ unsigned int index = lane->index;
+ u32 value;
+
+ if (!phy)
+ return;
+
+ port = tegra_xusb_find_usb2_port(padctl, index);
+ if (!port) {
+ dev_err(dev, "no port found for USB2 lane %u\n", index);
+ return;
+ }
+
+ tegra186_utmi_bias_pad_power_on(padctl);
+
+ udelay(2);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_USB2_OTG_PADX_CTL0(index));
+ value &= ~USB2_OTG_PD;
+ padctl_writel(padctl, value, XUSB_PADCTL_USB2_OTG_PADX_CTL0(index));
+
+ value = padctl_readl(padctl, XUSB_PADCTL_USB2_OTG_PADX_CTL1(index));
+ value &= ~USB2_OTG_PD_DR;
+ padctl_writel(padctl, value, XUSB_PADCTL_USB2_OTG_PADX_CTL1(index));
+}
+
+static void tegra_phy_xusb_utmi_pad_power_down(struct phy *phy)
+{
+ struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+ struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+ unsigned int index = lane->index;
+ u32 value;
+
+ if (!phy)
+ return;
+
+ value = padctl_readl(padctl, XUSB_PADCTL_USB2_OTG_PADX_CTL0(index));
+ value |= USB2_OTG_PD;
+ padctl_writel(padctl, value, XUSB_PADCTL_USB2_OTG_PADX_CTL0(index));
+
+ value = padctl_readl(padctl, XUSB_PADCTL_USB2_OTG_PADX_CTL1(index));
+ value |= USB2_OTG_PD_DR;
+ padctl_writel(padctl, value, XUSB_PADCTL_USB2_OTG_PADX_CTL1(index));
+
+ udelay(2);
+
+ tegra186_utmi_bias_pad_power_off(padctl);
+}
+
+static int tegra186_utmi_phy_power_on(struct phy *phy)
+{
+ struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+ struct tegra_xusb_usb2_lane *usb2 = to_usb2_lane(lane);
+ struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+ struct tegra186_xusb_padctl *priv = to_tegra186_xusb_padctl(padctl);
+ struct tegra_xusb_usb2_port *port;
+ unsigned int index = lane->index;
+ struct device *dev = padctl->dev;
+ u32 value;
+
+ port = tegra_xusb_find_usb2_port(padctl, index);
+ if (!port) {
+ dev_err(dev, "no port found for USB2 lane %u\n", index);
+ return -ENODEV;
+ }
+
+ value = padctl_readl(padctl, XUSB_PADCTL_USB2_PAD_MUX);
+ value &= ~(USB2_PORT_MASK << USB2_PORT_SHIFT(index));
+ value |= (PORT_XUSB << USB2_PORT_SHIFT(index));
+ padctl_writel(padctl, value, XUSB_PADCTL_USB2_PAD_MUX);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_USB2_PORT_CAP);
+ value &= ~(PORT_CAP_MASK << PORTX_CAP_SHIFT(index));
+
+ if (port->mode == USB_DR_MODE_UNKNOWN)
+ value |= (PORT_CAP_DISABLED << PORTX_CAP_SHIFT(index));
+ else if (port->mode == USB_DR_MODE_PERIPHERAL)
+ value |= (PORT_CAP_DEVICE << PORTX_CAP_SHIFT(index));
+ else if (port->mode == USB_DR_MODE_HOST)
+ value |= (PORT_CAP_HOST << PORTX_CAP_SHIFT(index));
+ else if (port->mode == USB_DR_MODE_OTG)
+ value |= (PORT_CAP_OTG << PORTX_CAP_SHIFT(index));
+
+ padctl_writel(padctl, value, XUSB_PADCTL_USB2_PORT_CAP);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_USB2_OTG_PADX_CTL0(index));
+ value &= ~USB2_OTG_PD_ZI;
+ value |= TERM_SEL;
+ value &= ~HS_CURR_LEVEL(~0);
+
+ if (usb2->hs_curr_level_offset) {
+ int hs_current_level;
+
+ hs_current_level = (int)priv->calib.hs_curr_level[index] +
+ usb2->hs_curr_level_offset;
+
+ if (hs_current_level < 0)
+ hs_current_level = 0;
+ if (hs_current_level > 0x3f)
+ hs_current_level = 0x3f;
+
+ value |= HS_CURR_LEVEL(hs_current_level);
+ } else {
+ value |= HS_CURR_LEVEL(priv->calib.hs_curr_level[index]);
+ }
+
+ padctl_writel(padctl, value, XUSB_PADCTL_USB2_OTG_PADX_CTL0(index));
+
+ value = padctl_readl(padctl, XUSB_PADCTL_USB2_OTG_PADX_CTL1(index));
+ value &= ~TERM_RANGE_ADJ(~0);
+ value |= TERM_RANGE_ADJ(priv->calib.hs_term_range_adj);
+ value &= ~RPD_CTRL(~0);
+ value |= RPD_CTRL(priv->calib.rpd_ctrl);
+ padctl_writel(padctl, value, XUSB_PADCTL_USB2_OTG_PADX_CTL1(index));
+
+ /* TODO: pad power saving */
+ tegra_phy_xusb_utmi_pad_power_on(phy);
+ return 0;
+}
+
+static int tegra186_utmi_phy_power_off(struct phy *phy)
+{
+ /* TODO: pad power saving */
+ tegra_phy_xusb_utmi_pad_power_down(phy);
+
+ return 0;
+}
+
+static int tegra186_utmi_phy_init(struct phy *phy)
+{
+ struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+ struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+ struct tegra_xusb_usb2_port *port;
+ unsigned int index = lane->index;
+ struct device *dev = padctl->dev;
+ int err;
+
+ port = tegra_xusb_find_usb2_port(padctl, index);
+ if (!port) {
+ dev_err(dev, "no port found for USB2 lane %u\n", index);
+ return -ENODEV;
+ }
+
+ if (port->supply && port->mode == USB_DR_MODE_HOST) {
+ err = regulator_enable(port->supply);
+ if (err) {
+ dev_err(dev, "failed to enable port %u VBUS: %d\n",
+ index, err);
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+static int tegra186_utmi_phy_exit(struct phy *phy)
+{
+ struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+ struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+ struct tegra_xusb_usb2_port *port;
+ unsigned int index = lane->index;
+ struct device *dev = padctl->dev;
+ int err;
+
+ port = tegra_xusb_find_usb2_port(padctl, index);
+ if (!port) {
+ dev_err(dev, "no port found for USB2 lane %u\n", index);
+ return -ENODEV;
+ }
+
+ if (port->supply && port->mode == USB_DR_MODE_HOST) {
+ err = regulator_disable(port->supply);
+ if (err) {
+ dev_err(dev, "failed to disable port %u VBUS: %d\n",
+ index, err);
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+static const struct phy_ops utmi_phy_ops = {
+ .init = tegra186_utmi_phy_init,
+ .exit = tegra186_utmi_phy_exit,
+ .power_on = tegra186_utmi_phy_power_on,
+ .power_off = tegra186_utmi_phy_power_off,
+ .owner = THIS_MODULE,
+};
+
+static struct tegra_xusb_pad *
+tegra186_usb2_pad_probe(struct tegra_xusb_padctl *padctl,
+ const struct tegra_xusb_pad_soc *soc,
+ struct device_node *np)
+{
+ struct tegra186_xusb_padctl *priv = to_tegra186_xusb_padctl(padctl);
+ struct tegra_xusb_usb2_pad *usb2;
+ struct tegra_xusb_pad *pad;
+ int err;
+
+ usb2 = kzalloc(sizeof(*usb2), GFP_KERNEL);
+ if (!usb2)
+ return ERR_PTR(-ENOMEM);
+
+ pad = &usb2->base;
+ pad->ops = &tegra186_usb2_lane_ops;
+ pad->soc = soc;
+
+ err = tegra_xusb_pad_init(pad, padctl, np);
+ if (err < 0) {
+ kfree(usb2);
+ goto out;
+ }
+
+ priv->usb2_trk_clk = devm_clk_get(&pad->dev, "trk");
+ if (IS_ERR(priv->usb2_trk_clk)) {
+ err = PTR_ERR(priv->usb2_trk_clk);
+ dev_dbg(&pad->dev, "failed to get usb2 trk clock: %d\n", err);
+ goto unregister;
+ }
+
+ err = tegra_xusb_pad_register(pad, &utmi_phy_ops);
+ if (err < 0)
+ goto unregister;
+
+ dev_set_drvdata(&pad->dev, pad);
+
+ return pad;
+
+unregister:
+ device_unregister(&pad->dev);
+out:
+ return ERR_PTR(err);
+}
+
+static void tegra186_usb2_pad_remove(struct tegra_xusb_pad *pad)
+{
+ struct tegra_xusb_usb2_pad *usb2 = to_usb2_pad(pad);
+
+ kfree(usb2);
+}
+
+static const struct tegra_xusb_pad_ops tegra186_usb2_pad_ops = {
+ .probe = tegra186_usb2_pad_probe,
+ .remove = tegra186_usb2_pad_remove,
+};
+
+static const char * const tegra186_usb2_functions[] = {
+ "xusb",
+};
+
+static const struct tegra_xusb_lane_soc tegra186_usb2_lanes[] = {
+ TEGRA186_LANE("usb2-0", 0, 0, 0, usb2),
+ TEGRA186_LANE("usb2-1", 0, 0, 0, usb2),
+ TEGRA186_LANE("usb2-2", 0, 0, 0, usb2),
+};
+
+static const struct tegra_xusb_pad_soc tegra186_usb2_pad = {
+ .name = "usb2",
+ .num_lanes = ARRAY_SIZE(tegra186_usb2_lanes),
+ .lanes = tegra186_usb2_lanes,
+ .ops = &tegra186_usb2_pad_ops,
+};
+
+static int tegra186_usb2_port_enable(struct tegra_xusb_port *port)
+{
+ return 0;
+}
+
+static void tegra186_usb2_port_disable(struct tegra_xusb_port *port)
+{
+}
+
+static struct tegra_xusb_lane *
+tegra186_usb2_port_map(struct tegra_xusb_port *port)
+{
+ return tegra_xusb_find_lane(port->padctl, "usb2", port->index);
+}
+
+static const struct tegra_xusb_port_ops tegra186_usb2_port_ops = {
+ .enable = tegra186_usb2_port_enable,
+ .disable = tegra186_usb2_port_disable,
+ .map = tegra186_usb2_port_map,
+};
+
+/* SuperSpeed PHY support */
+static struct tegra_xusb_lane *
+tegra186_usb3_lane_probe(struct tegra_xusb_pad *pad, struct device_node *np,
+ unsigned int index)
+{
+ struct tegra_xusb_usb3_lane *usb3;
+ int err;
+
+ usb3 = kzalloc(sizeof(*usb3), GFP_KERNEL);
+ if (!usb3)
+ return ERR_PTR(-ENOMEM);
+
+ INIT_LIST_HEAD(&usb3->base.list);
+ usb3->base.soc = &pad->soc->lanes[index];
+ usb3->base.index = index;
+ usb3->base.pad = pad;
+ usb3->base.np = np;
+
+ err = tegra_xusb_lane_parse_dt(&usb3->base, np);
+ if (err < 0) {
+ kfree(usb3);
+ return ERR_PTR(err);
+ }
+
+ return &usb3->base;
+}
+
+static void tegra186_usb3_lane_remove(struct tegra_xusb_lane *lane)
+{
+ struct tegra_xusb_usb3_lane *usb3 = to_usb3_lane(lane);
+
+ kfree(usb3);
+}
+
+static const struct tegra_xusb_lane_ops tegra186_usb3_lane_ops = {
+ .probe = tegra186_usb3_lane_probe,
+ .remove = tegra186_usb3_lane_remove,
+};
+static int tegra186_usb3_port_enable(struct tegra_xusb_port *port)
+{
+ return 0;
+}
+
+static void tegra186_usb3_port_disable(struct tegra_xusb_port *port)
+{
+}
+
+static struct tegra_xusb_lane *
+tegra186_usb3_port_map(struct tegra_xusb_port *port)
+{
+ return tegra_xusb_find_lane(port->padctl, "usb3", port->index);
+}
+
+static const struct tegra_xusb_port_ops tegra186_usb3_port_ops = {
+ .enable = tegra186_usb3_port_enable,
+ .disable = tegra186_usb3_port_disable,
+ .map = tegra186_usb3_port_map,
+};
+
+static int tegra186_usb3_phy_power_on(struct phy *phy)
+{
+ struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+ struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+ struct tegra_xusb_usb3_port *port;
+ struct tegra_xusb_usb2_port *usb2;
+ unsigned int index = lane->index;
+ struct device *dev = padctl->dev;
+ u32 value;
+
+ port = tegra_xusb_find_usb3_port(padctl, index);
+ if (!port) {
+ dev_err(dev, "no port found for USB3 lane %u\n", index);
+ return -ENODEV;
+ }
+
+ usb2 = tegra_xusb_find_usb2_port(padctl, port->port);
+ if (!usb2) {
+ dev_err(dev, "no companion port found for USB3 lane %u\n",
+ index);
+ return -ENODEV;
+ }
+
+ mutex_lock(&padctl->lock);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_SS_PORT_CAP);
+ value &= ~(PORT_CAP_MASK << PORTX_CAP_SHIFT(index));
+
+ if (usb2->mode == USB_DR_MODE_UNKNOWN)
+ value |= (PORT_CAP_DISABLED << PORTX_CAP_SHIFT(index));
+ else if (usb2->mode == USB_DR_MODE_PERIPHERAL)
+ value |= (PORT_CAP_DEVICE << PORTX_CAP_SHIFT(index));
+ else if (usb2->mode == USB_DR_MODE_HOST)
+ value |= (PORT_CAP_HOST << PORTX_CAP_SHIFT(index));
+ else if (usb2->mode == USB_DR_MODE_OTG)
+ value |= (PORT_CAP_OTG << PORTX_CAP_SHIFT(index));
+
+ padctl_writel(padctl, value, XUSB_PADCTL_SS_PORT_CAP);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM_1);
+ value &= ~SSPX_ELPG_VCORE_DOWN(index);
+ padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM_1);
+
+ usleep_range(100, 200);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM_1);
+ value &= ~SSPX_ELPG_CLAMP_EN_EARLY(index);
+ padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM_1);
+
+ usleep_range(100, 200);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM_1);
+ value &= ~SSPX_ELPG_CLAMP_EN(index);
+ padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM_1);
+
+ mutex_unlock(&padctl->lock);
+
+ return 0;
+}
+
+static int tegra186_usb3_phy_power_off(struct phy *phy)
+{
+ struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+ struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+ struct tegra_xusb_usb3_port *port;
+ unsigned int index = lane->index;
+ struct device *dev = padctl->dev;
+ u32 value;
+
+ port = tegra_xusb_find_usb3_port(padctl, index);
+ if (!port) {
+ dev_err(dev, "no port found for USB3 lane %u\n", index);
+ return -ENODEV;
+ }
+
+ mutex_lock(&padctl->lock);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM_1);
+ value |= SSPX_ELPG_CLAMP_EN_EARLY(index);
+ padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM_1);
+
+ usleep_range(100, 200);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM_1);
+ value |= SSPX_ELPG_CLAMP_EN(index);
+ padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM_1);
+
+ usleep_range(250, 350);
+
+ value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM_1);
+ value |= SSPX_ELPG_VCORE_DOWN(index);
+ padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM_1);
+
+ mutex_unlock(&padctl->lock);
+
+ return 0;
+}
+
+static int tegra186_usb3_phy_init(struct phy *phy)
+{
+ return 0;
+}
+
+static int tegra186_usb3_phy_exit(struct phy *phy)
+{
+ return 0;
+}
+
+static const struct phy_ops usb3_phy_ops = {
+ .init = tegra186_usb3_phy_init,
+ .exit = tegra186_usb3_phy_exit,
+ .power_on = tegra186_usb3_phy_power_on,
+ .power_off = tegra186_usb3_phy_power_off,
+ .owner = THIS_MODULE,
+};
+
+static struct tegra_xusb_pad *
+tegra186_usb3_pad_probe(struct tegra_xusb_padctl *padctl,
+ const struct tegra_xusb_pad_soc *soc,
+ struct device_node *np)
+{
+ struct tegra_xusb_usb3_pad *usb3;
+ struct tegra_xusb_pad *pad;
+ int err;
+
+ usb3 = kzalloc(sizeof(*usb3), GFP_KERNEL);
+ if (!usb3)
+ return ERR_PTR(-ENOMEM);
+
+ pad = &usb3->base;
+ pad->ops = &tegra186_usb3_lane_ops;
+ pad->soc = soc;
+
+ err = tegra_xusb_pad_init(pad, padctl, np);
+ if (err < 0) {
+ kfree(usb3);
+ goto out;
+ }
+
+ err = tegra_xusb_pad_register(pad, &usb3_phy_ops);
+ if (err < 0)
+ goto unregister;
+
+ dev_set_drvdata(&pad->dev, pad);
+
+ return pad;
+
+unregister:
+ device_unregister(&pad->dev);
+out:
+ return ERR_PTR(err);
+}
+
+static void tegra186_usb3_pad_remove(struct tegra_xusb_pad *pad)
+{
+ struct tegra_xusb_usb2_pad *usb2 = to_usb2_pad(pad);
+
+ kfree(usb2);
+}
+
+static const struct tegra_xusb_pad_ops tegra186_usb3_pad_ops = {
+ .probe = tegra186_usb3_pad_probe,
+ .remove = tegra186_usb3_pad_remove,
+};
+
+static const char * const tegra186_usb3_functions[] = {
+ "xusb",
+};
+
+static const struct tegra_xusb_lane_soc tegra186_usb3_lanes[] = {
+ TEGRA186_LANE("usb3-0", 0, 0, 0, usb3),
+ TEGRA186_LANE("usb3-1", 0, 0, 0, usb3),
+ TEGRA186_LANE("usb3-2", 0, 0, 0, usb3),
+};
+
+static const struct tegra_xusb_pad_soc tegra186_usb3_pad = {
+ .name = "usb3",
+ .num_lanes = ARRAY_SIZE(tegra186_usb3_lanes),
+ .lanes = tegra186_usb3_lanes,
+ .ops = &tegra186_usb3_pad_ops,
+};
+
+static const struct tegra_xusb_pad_soc * const tegra186_pads[] = {
+ &tegra186_usb2_pad,
+ &tegra186_usb3_pad,
+#if 0 /* TODO implement */
+ &tegra186_hsic_pad,
+#endif
+};
+
+static int
+tegra186_xusb_read_fuse_calibration(struct tegra186_xusb_padctl *padctl)
+{
+ struct device *dev = padctl->base.dev;
+ unsigned int i, count;
+ u32 value, *level;
+ int err;
+
+ count = padctl->base.soc->ports.usb2.count;
+
+ level = devm_kcalloc(dev, count, sizeof(u32), GFP_KERNEL);
+ if (!level)
+ return -ENOMEM;
+
+ err = tegra_fuse_readl(TEGRA_FUSE_SKU_CALIB_0, &value);
+ if (err) {
+ dev_err(dev, "failed to read calibration fuse: %d\n", err);
+ return err;
+ }
+
+ dev_dbg(dev, "FUSE_USB_CALIB_0 %#x\n", value);
+
+ for (i = 0; i < count; i++)
+ level[i] = (value >> HS_CURR_LEVEL_PADX_SHIFT(i)) &
+ HS_CURR_LEVEL_PAD_MASK;
+
+ padctl->calib.hs_curr_level = level;
+
+ padctl->calib.hs_squelch = (value >> HS_SQUELCH_SHIFT) &
+ HS_SQUELCH_MASK;
+ padctl->calib.hs_term_range_adj = (value >> HS_TERM_RANGE_ADJ_SHIFT) &
+ HS_TERM_RANGE_ADJ_MASK;
+
+ err = tegra_fuse_readl(TEGRA_FUSE_USB_CALIB_EXT_0, &value);
+ if (err) {
+ dev_err(dev, "failed to read calibration fuse: %d\n", err);
+ return err;
+ }
+
+ dev_dbg(dev, "FUSE_USB_CALIB_EXT_0 %#x\n", value);
+
+ padctl->calib.rpd_ctrl = (value >> RPD_CTRL_SHIFT) & RPD_CTRL_MASK;
+
+ return 0;
+}
+
+static struct tegra_xusb_padctl *
+tegra186_xusb_padctl_probe(struct device *dev,
+ const struct tegra_xusb_padctl_soc *soc)
+{
+ struct tegra186_xusb_padctl *priv;
+ int err;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return ERR_PTR(-ENOMEM);
+
+ priv->base.dev = dev;
+ priv->base.soc = soc;
+
+ err = tegra186_xusb_read_fuse_calibration(priv);
+ if (err < 0)
+ return ERR_PTR(err);
+
+ return &priv->base;
+}
+
+static void tegra186_xusb_padctl_remove(struct tegra_xusb_padctl *padctl)
+{
+}
+
+static const struct tegra_xusb_padctl_ops tegra186_xusb_padctl_ops = {
+ .probe = tegra186_xusb_padctl_probe,
+ .remove = tegra186_xusb_padctl_remove,
+};
+
+static const char * const tegra186_xusb_padctl_supply_names[] = {
+ "avdd-pll-erefeut",
+ "avdd-usb",
+ "vclamp-usb",
+ "vddio-hsic",
+};
+
+const struct tegra_xusb_padctl_soc tegra186_xusb_padctl_soc = {
+ .num_pads = ARRAY_SIZE(tegra186_pads),
+ .pads = tegra186_pads,
+ .ports = {
+ .usb2 = {
+ .ops = &tegra186_usb2_port_ops,
+ .count = 3,
+ },
+#if 0 /* TODO implement */
+ .hsic = {
+ .ops = &tegra186_hsic_port_ops,
+ .count = 1,
+ },
+#endif
+ .usb3 = {
+ .ops = &tegra186_usb3_port_ops,
+ .count = 3,
+ },
+ },
+ .ops = &tegra186_xusb_padctl_ops,
+ .supply_names = tegra186_xusb_padctl_supply_names,
+ .num_supplies = ARRAY_SIZE(tegra186_xusb_padctl_supply_names),
+};
+EXPORT_SYMBOL_GPL(tegra186_xusb_padctl_soc);
+
+MODULE_AUTHOR("JC Kuo <jckuo@nvidia.com>");
+MODULE_DESCRIPTION("NVIDIA Tegra186 XUSB Pad Controller driver");
+MODULE_LICENSE("GPL v2");
/*
- * Copyright (c) 2014-2015, NVIDIA CORPORATION. All rights reserved.
+ * Copyright (c) 2014-2016, NVIDIA CORPORATION. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
.compatible = "nvidia,tegra210-xusb-padctl",
.data = &tegra210_xusb_padctl_soc,
},
+#endif
+#if defined(CONFIG_ARCH_TEGRA_186_SOC)
+ {
+ .compatible = "nvidia,tegra186-xusb-padctl",
+ .data = &tegra186_xusb_padctl_soc,
+ },
#endif
{ }
};
const struct tegra_xusb_lane_soc *soc = lane->soc;
u32 value;
+ /* skip single function lanes */
+ if (soc->num_funcs < 2)
+ return;
+
/* choose function */
value = padctl_readl(padctl, soc->offset);
value &= ~(soc->mask << soc->shift);
device_unregister(&port->dev);
}
+static const char *const modes[] = {
+ [USB_DR_MODE_UNKNOWN] = "",
+ [USB_DR_MODE_HOST] = "host",
+ [USB_DR_MODE_PERIPHERAL] = "peripheral",
+ [USB_DR_MODE_OTG] = "otg",
+};
+
static int tegra_xusb_usb2_port_parse_dt(struct tegra_xusb_usb2_port *usb2)
{
struct tegra_xusb_port *port = &usb2->base;
struct device_node *np = port->dev.of_node;
+ const char *mode;
usb2->internal = of_property_read_bool(np, "nvidia,internal");
+ if (!of_property_read_string(np, "mode", &mode)) {
+ int err = match_string(modes, ARRAY_SIZE(modes), mode);
+ if (err < 0) {
+ dev_err(&port->dev, "invalid value %s for \"mode\"\n",
+ mode);
+ usb2->mode = USB_DR_MODE_UNKNOWN;
+ } else {
+ usb2->mode = err;
+ }
+ } else {
+ usb2->mode = USB_DR_MODE_HOST;
+ }
+
usb2->supply = devm_regulator_get(&port->dev, "vbus");
return PTR_ERR_OR_ZERO(usb2->supply);
}
struct tegra_xusb_padctl *padctl;
const struct of_device_id *match;
struct resource *res;
+ unsigned int i;
int err;
/* for backwards compatibility with old device trees */
goto remove;
}
+ padctl->supplies = devm_kcalloc(&pdev->dev, padctl->soc->num_supplies,
+ sizeof(*padctl->supplies), GFP_KERNEL);
+ if (!padctl->supplies) {
+ err = -ENOMEM;
+ goto remove;
+ }
+
+ for (i = 0; i < padctl->soc->num_supplies; i++)
+ padctl->supplies[i].supply = padctl->soc->supply_names[i];
+
+ err = devm_regulator_bulk_get(&pdev->dev, padctl->soc->num_supplies,
+ padctl->supplies);
+ if (err < 0) {
+ dev_err(&pdev->dev, "failed to get regulators: %d\n", err);
+ goto remove;
+ }
+
err = reset_control_deassert(padctl->rst);
if (err < 0)
goto remove;
+ err = regulator_bulk_enable(padctl->soc->num_supplies,
+ padctl->supplies);
+ if (err < 0) {
+ dev_err(&pdev->dev, "failed to enable supplies: %d\n", err);
+ goto reset;
+ }
+
err = tegra_xusb_setup_pads(padctl);
if (err < 0) {
dev_err(&pdev->dev, "failed to setup pads: %d\n", err);
- goto reset;
+ goto power_down;
}
err = tegra_xusb_setup_ports(padctl);
remove_pads:
tegra_xusb_remove_pads(padctl);
+power_down:
+ regulator_bulk_disable(padctl->soc->num_supplies, padctl->supplies);
reset:
reset_control_assert(padctl->rst);
remove:
tegra_xusb_remove_ports(padctl);
tegra_xusb_remove_pads(padctl);
+ err = regulator_bulk_disable(padctl->soc->num_supplies,
+ padctl->supplies);
+ if (err < 0)
+ dev_err(&pdev->dev, "failed to disable supplies: %d\n", err);
+
err = reset_control_assert(padctl->rst);
if (err < 0)
dev_err(&pdev->dev, "failed to assert reset: %d\n", err);
#include <linux/mutex.h>
#include <linux/workqueue.h>
+#include <linux/usb/otg.h>
+
/* legacy entry points for backwards-compatibility */
int tegra_xusb_padctl_legacy_probe(struct platform_device *pdev);
int tegra_xusb_padctl_legacy_remove(struct platform_device *pdev);
int tegra_xusb_lane_parse_dt(struct tegra_xusb_lane *lane,
struct device_node *np);
+struct tegra_xusb_usb3_lane {
+ struct tegra_xusb_lane base;
+};
+
+static inline struct tegra_xusb_usb3_lane *
+to_usb3_lane(struct tegra_xusb_lane *lane)
+{
+ return container_of(lane, struct tegra_xusb_usb3_lane, base);
+}
+
struct tegra_xusb_usb2_lane {
struct tegra_xusb_lane base;
u32 hs_curr_level_offset;
+ bool powered_on;
};
static inline struct tegra_xusb_usb2_lane *
const struct phy_ops *ops);
void tegra_xusb_pad_unregister(struct tegra_xusb_pad *pad);
+struct tegra_xusb_usb3_pad {
+ struct tegra_xusb_pad base;
+
+ unsigned int enable;
+ struct mutex lock;
+};
+
+static inline struct tegra_xusb_usb3_pad *
+to_usb3_pad(struct tegra_xusb_pad *pad)
+{
+ return container_of(pad, struct tegra_xusb_usb3_pad, base);
+}
+
struct tegra_xusb_usb2_pad {
struct tegra_xusb_pad base;
struct tegra_xusb_port base;
struct regulator *supply;
+ enum usb_dr_mode mode;
bool internal;
};
} ports;
const struct tegra_xusb_padctl_ops *ops;
+
+ const char * const *supply_names;
+ unsigned int num_supplies;
};
struct tegra_xusb_padctl {
unsigned int enable;
struct clk *clk;
+
+ struct regulator_bulk_data *supplies;
};
static inline void padctl_writel(struct tegra_xusb_padctl *padctl, u32 value,
#if defined(CONFIG_ARCH_TEGRA_210_SOC)
extern const struct tegra_xusb_padctl_soc tegra210_xusb_padctl_soc;
#endif
+#if defined(CONFIG_ARCH_TEGRA_186_SOC)
+extern const struct tegra_xusb_padctl_soc tegra186_xusb_padctl_soc;
+#endif
#endif /* __PHY_TEGRA_XUSB_H */
help
Enable this for dm816x USB to work.
+config PHY_AM654_SERDES
+ tristate "TI AM654 SERDES support"
+ depends on OF && ARCH_K3 || COMPILE_TEST
+ depends on COMMON_CLK
+ select GENERIC_PHY
+ select MULTIPLEXER
+ select REGMAP_MMIO
+ select MUX_MMIO
+ help
+ This option enables support for TI AM654 SerDes PHY used for
+ PCIe.
+
config OMAP_CONTROL_PHY
tristate "OMAP CONTROL PHY Driver"
depends on ARCH_OMAP2PLUS || COMPILE_TEST
obj-$(CONFIG_TI_PIPE3) += phy-ti-pipe3.o
obj-$(CONFIG_PHY_TUSB1210) += phy-tusb1210.o
obj-$(CONFIG_TWL4030_USB) += phy-twl4030-usb.o
+obj-$(CONFIG_PHY_AM654_SERDES) += phy-am654-serdes.o
obj-$(CONFIG_PHY_TI_GMII_SEL) += phy-gmii-sel.o
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/**
+ * PCIe SERDES driver for AM654x SoC
+ *
+ * Copyright (C) 2018 - 2019 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Kishon Vijay Abraham I <kishon@ti.com>
+ */
+
+#include <dt-bindings/phy/phy.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/mfd/syscon.h>
+#include <linux/mux/consumer.h>
+#include <linux/of_address.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+
+#define CMU_R07C 0x7c
+
+#define COMLANE_R138 0xb38
+#define VERSION 0x70
+
+#define COMLANE_R190 0xb90
+
+#define COMLANE_R194 0xb94
+
+#define SERDES_CTRL 0x1fd0
+
+#define WIZ_LANEXCTL_STS 0x1fe0
+#define TX0_DISABLE_STATE 0x4
+#define TX0_SLEEP_STATE 0x5
+#define TX0_SNOOZE_STATE 0x6
+#define TX0_ENABLE_STATE 0x7
+
+#define RX0_DISABLE_STATE 0x4
+#define RX0_SLEEP_STATE 0x5
+#define RX0_SNOOZE_STATE 0x6
+#define RX0_ENABLE_STATE 0x7
+
+#define WIZ_PLL_CTRL 0x1ff4
+#define PLL_DISABLE_STATE 0x4
+#define PLL_SLEEP_STATE 0x5
+#define PLL_SNOOZE_STATE 0x6
+#define PLL_ENABLE_STATE 0x7
+
+#define PLL_LOCK_TIME 100000 /* in microseconds */
+#define SLEEP_TIME 100 /* in microseconds */
+
+#define LANE_USB3 0x0
+#define LANE_PCIE0_LANE0 0x1
+
+#define LANE_PCIE1_LANE0 0x0
+#define LANE_PCIE0_LANE1 0x1
+
+#define SERDES_NUM_CLOCKS 3
+
+#define AM654_SERDES_CTRL_CLKSEL_MASK GENMASK(7, 4)
+#define AM654_SERDES_CTRL_CLKSEL_SHIFT 4
+
+struct serdes_am654_clk_mux {
+ struct clk_hw hw;
+ struct regmap *regmap;
+ unsigned int reg;
+ int clk_id;
+ struct clk_init_data clk_data;
+};
+
+#define to_serdes_am654_clk_mux(_hw) \
+ container_of(_hw, struct serdes_am654_clk_mux, hw)
+
+static struct regmap_config serdes_am654_regmap_config = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .fast_io = true,
+};
+
+static const struct reg_field cmu_master_cdn_o = REG_FIELD(CMU_R07C, 24, 24);
+static const struct reg_field config_version = REG_FIELD(COMLANE_R138, 16, 23);
+static const struct reg_field l1_master_cdn_o = REG_FIELD(COMLANE_R190, 9, 9);
+static const struct reg_field cmu_ok_i_0 = REG_FIELD(COMLANE_R194, 19, 19);
+static const struct reg_field por_en = REG_FIELD(SERDES_CTRL, 29, 29);
+static const struct reg_field tx0_enable = REG_FIELD(WIZ_LANEXCTL_STS, 29, 31);
+static const struct reg_field rx0_enable = REG_FIELD(WIZ_LANEXCTL_STS, 13, 15);
+static const struct reg_field pll_enable = REG_FIELD(WIZ_PLL_CTRL, 29, 31);
+static const struct reg_field pll_ok = REG_FIELD(WIZ_PLL_CTRL, 28, 28);
+
+struct serdes_am654 {
+ struct regmap *regmap;
+ struct regmap_field *cmu_master_cdn_o;
+ struct regmap_field *config_version;
+ struct regmap_field *l1_master_cdn_o;
+ struct regmap_field *cmu_ok_i_0;
+ struct regmap_field *por_en;
+ struct regmap_field *tx0_enable;
+ struct regmap_field *rx0_enable;
+ struct regmap_field *pll_enable;
+ struct regmap_field *pll_ok;
+
+ struct device *dev;
+ struct mux_control *control;
+ bool busy;
+ u32 type;
+ struct device_node *of_node;
+ struct clk_onecell_data clk_data;
+ struct clk *clks[SERDES_NUM_CLOCKS];
+};
+
+static int serdes_am654_enable_pll(struct serdes_am654 *phy)
+{
+ int ret;
+ u32 val;
+
+ ret = regmap_field_write(phy->pll_enable, PLL_ENABLE_STATE);
+ if (ret)
+ return ret;
+
+ return regmap_field_read_poll_timeout(phy->pll_ok, val, val, 1000,
+ PLL_LOCK_TIME);
+}
+
+static void serdes_am654_disable_pll(struct serdes_am654 *phy)
+{
+ struct device *dev = phy->dev;
+ int ret;
+
+ ret = regmap_field_write(phy->pll_enable, PLL_DISABLE_STATE);
+ if (ret)
+ dev_err(dev, "Failed to disable PLL\n");
+}
+
+static int serdes_am654_enable_txrx(struct serdes_am654 *phy)
+{
+ int ret;
+
+ /* Enable TX */
+ ret = regmap_field_write(phy->tx0_enable, TX0_ENABLE_STATE);
+ if (ret)
+ return ret;
+
+ /* Enable RX */
+ ret = regmap_field_write(phy->rx0_enable, RX0_ENABLE_STATE);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int serdes_am654_disable_txrx(struct serdes_am654 *phy)
+{
+ int ret;
+
+ /* Disable TX */
+ ret = regmap_field_write(phy->tx0_enable, TX0_DISABLE_STATE);
+ if (ret)
+ return ret;
+
+ /* Disable RX */
+ ret = regmap_field_write(phy->rx0_enable, RX0_DISABLE_STATE);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int serdes_am654_power_on(struct phy *x)
+{
+ struct serdes_am654 *phy = phy_get_drvdata(x);
+ struct device *dev = phy->dev;
+ int ret;
+ u32 val;
+
+ ret = serdes_am654_enable_pll(phy);
+ if (ret) {
+ dev_err(dev, "Failed to enable PLL\n");
+ return ret;
+ }
+
+ ret = serdes_am654_enable_txrx(phy);
+ if (ret) {
+ dev_err(dev, "Failed to enable TX RX\n");
+ return ret;
+ }
+
+ return regmap_field_read_poll_timeout(phy->cmu_ok_i_0, val, val,
+ SLEEP_TIME, PLL_LOCK_TIME);
+}
+
+static int serdes_am654_power_off(struct phy *x)
+{
+ struct serdes_am654 *phy = phy_get_drvdata(x);
+
+ serdes_am654_disable_txrx(phy);
+ serdes_am654_disable_pll(phy);
+
+ return 0;
+}
+
+static int serdes_am654_init(struct phy *x)
+{
+ struct serdes_am654 *phy = phy_get_drvdata(x);
+ int ret;
+
+ ret = regmap_field_write(phy->config_version, VERSION);
+ if (ret)
+ return ret;
+
+ ret = regmap_field_write(phy->cmu_master_cdn_o, 0x1);
+ if (ret)
+ return ret;
+
+ ret = regmap_field_write(phy->l1_master_cdn_o, 0x1);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int serdes_am654_reset(struct phy *x)
+{
+ struct serdes_am654 *phy = phy_get_drvdata(x);
+ int ret;
+
+ ret = regmap_field_write(phy->por_en, 0x1);
+ if (ret)
+ return ret;
+
+ mdelay(1);
+
+ ret = regmap_field_write(phy->por_en, 0x0);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static void serdes_am654_release(struct phy *x)
+{
+ struct serdes_am654 *phy = phy_get_drvdata(x);
+
+ phy->type = PHY_NONE;
+ phy->busy = false;
+ mux_control_deselect(phy->control);
+}
+
+struct phy *serdes_am654_xlate(struct device *dev, struct of_phandle_args
+ *args)
+{
+ struct serdes_am654 *am654_phy;
+ struct phy *phy;
+ int ret;
+
+ phy = of_phy_simple_xlate(dev, args);
+ if (IS_ERR(phy))
+ return phy;
+
+ am654_phy = phy_get_drvdata(phy);
+ if (am654_phy->busy)
+ return ERR_PTR(-EBUSY);
+
+ ret = mux_control_select(am654_phy->control, args->args[1]);
+ if (ret) {
+ dev_err(dev, "Failed to select SERDES Lane Function\n");
+ return ERR_PTR(ret);
+ }
+
+ am654_phy->busy = true;
+ am654_phy->type = args->args[0];
+
+ return phy;
+}
+
+static const struct phy_ops ops = {
+ .reset = serdes_am654_reset,
+ .init = serdes_am654_init,
+ .power_on = serdes_am654_power_on,
+ .power_off = serdes_am654_power_off,
+ .release = serdes_am654_release,
+ .owner = THIS_MODULE,
+};
+
+#define SERDES_NUM_MUX_COMBINATIONS 16
+
+#define LICLK 0
+#define EXT_REFCLK 1
+#define RICLK 2
+
+static const int
+serdes_am654_mux_table[SERDES_NUM_MUX_COMBINATIONS][SERDES_NUM_CLOCKS] = {
+ /*
+ * Each combination maps to one of
+ * "Figure 12-1986. SerDes Reference Clock Distribution"
+ * in TRM.
+ */
+ /* Parent of CMU refclk, Left output, Right output
+ * either of EXT_REFCLK, LICLK, RICLK
+ */
+ { EXT_REFCLK, EXT_REFCLK, EXT_REFCLK }, /* 0000 */
+ { RICLK, EXT_REFCLK, EXT_REFCLK }, /* 0001 */
+ { EXT_REFCLK, RICLK, LICLK }, /* 0010 */
+ { RICLK, RICLK, EXT_REFCLK }, /* 0011 */
+ { LICLK, EXT_REFCLK, EXT_REFCLK }, /* 0100 */
+ { EXT_REFCLK, EXT_REFCLK, EXT_REFCLK }, /* 0101 */
+ { LICLK, RICLK, LICLK }, /* 0110 */
+ { EXT_REFCLK, RICLK, LICLK }, /* 0111 */
+ { EXT_REFCLK, EXT_REFCLK, LICLK }, /* 1000 */
+ { RICLK, EXT_REFCLK, LICLK }, /* 1001 */
+ { EXT_REFCLK, RICLK, EXT_REFCLK }, /* 1010 */
+ { RICLK, RICLK, EXT_REFCLK }, /* 1011 */
+ { LICLK, EXT_REFCLK, LICLK }, /* 1100 */
+ { EXT_REFCLK, EXT_REFCLK, LICLK }, /* 1101 */
+ { LICLK, RICLK, EXT_REFCLK }, /* 1110 */
+ { EXT_REFCLK, RICLK, EXT_REFCLK }, /* 1111 */
+};
+
+static u8 serdes_am654_clk_mux_get_parent(struct clk_hw *hw)
+{
+ struct serdes_am654_clk_mux *mux = to_serdes_am654_clk_mux(hw);
+ struct regmap *regmap = mux->regmap;
+ unsigned int reg = mux->reg;
+ unsigned int val;
+
+ regmap_read(regmap, reg, &val);
+ val &= AM654_SERDES_CTRL_CLKSEL_MASK;
+ val >>= AM654_SERDES_CTRL_CLKSEL_SHIFT;
+
+ return serdes_am654_mux_table[val][mux->clk_id];
+}
+
+static int serdes_am654_clk_mux_set_parent(struct clk_hw *hw, u8 index)
+{
+ struct serdes_am654_clk_mux *mux = to_serdes_am654_clk_mux(hw);
+ struct regmap *regmap = mux->regmap;
+ unsigned int reg = mux->reg;
+ int clk_id = mux->clk_id;
+ int parents[SERDES_NUM_CLOCKS];
+ const int *p;
+ u32 val;
+ int found, i;
+ int ret;
+
+ /* get existing setting */
+ regmap_read(regmap, reg, &val);
+ val &= AM654_SERDES_CTRL_CLKSEL_MASK;
+ val >>= AM654_SERDES_CTRL_CLKSEL_SHIFT;
+
+ for (i = 0; i < SERDES_NUM_CLOCKS; i++)
+ parents[i] = serdes_am654_mux_table[val][i];
+
+ /* change parent of this clock. others left intact */
+ parents[clk_id] = index;
+
+ /* Find the match */
+ for (val = 0; val < SERDES_NUM_MUX_COMBINATIONS; val++) {
+ p = serdes_am654_mux_table[val];
+ found = 1;
+ for (i = 0; i < SERDES_NUM_CLOCKS; i++) {
+ if (parents[i] != p[i]) {
+ found = 0;
+ break;
+ }
+ }
+
+ if (found)
+ break;
+ }
+
+ if (!found) {
+ /*
+ * This can never happen, unless we missed
+ * a valid combination in serdes_am654_mux_table.
+ */
+ WARN(1, "Failed to find the parent of %s clock\n",
+ hw->init->name);
+ return -EINVAL;
+ }
+
+ val <<= AM654_SERDES_CTRL_CLKSEL_SHIFT;
+ ret = regmap_update_bits(regmap, reg, AM654_SERDES_CTRL_CLKSEL_MASK,
+ val);
+
+ return ret;
+}
+
+static const struct clk_ops serdes_am654_clk_mux_ops = {
+ .set_parent = serdes_am654_clk_mux_set_parent,
+ .get_parent = serdes_am654_clk_mux_get_parent,
+};
+
+static int serdes_am654_clk_register(struct serdes_am654 *am654_phy,
+ const char *clock_name, int clock_num)
+{
+ struct device_node *node = am654_phy->of_node;
+ struct device *dev = am654_phy->dev;
+ struct serdes_am654_clk_mux *mux;
+ struct device_node *regmap_node;
+ const char **parent_names;
+ struct clk_init_data *init;
+ unsigned int num_parents;
+ struct regmap *regmap;
+ const __be32 *addr;
+ unsigned int reg;
+ struct clk *clk;
+
+ mux = devm_kzalloc(dev, sizeof(*mux), GFP_KERNEL);
+ if (!mux)
+ return -ENOMEM;
+
+ init = &mux->clk_data;
+
+ regmap_node = of_parse_phandle(node, "ti,serdes-clk", 0);
+ of_node_put(regmap_node);
+ if (!regmap_node) {
+ dev_err(dev, "Fail to get serdes-clk node\n");
+ return -ENODEV;
+ }
+
+ regmap = syscon_node_to_regmap(regmap_node->parent);
+ if (IS_ERR(regmap)) {
+ dev_err(dev, "Fail to get Syscon regmap\n");
+ return PTR_ERR(regmap);
+ }
+
+ num_parents = of_clk_get_parent_count(node);
+ if (num_parents < 2) {
+ dev_err(dev, "SERDES clock must have parents\n");
+ return -EINVAL;
+ }
+
+ parent_names = devm_kzalloc(dev, (sizeof(char *) * num_parents),
+ GFP_KERNEL);
+ if (!parent_names)
+ return -ENOMEM;
+
+ of_clk_parent_fill(node, parent_names, num_parents);
+
+ addr = of_get_address(regmap_node, 0, NULL, NULL);
+ if (!addr)
+ return -EINVAL;
+
+ reg = be32_to_cpu(*addr);
+
+ init->ops = &serdes_am654_clk_mux_ops;
+ init->flags = CLK_SET_RATE_NO_REPARENT;
+ init->parent_names = parent_names;
+ init->num_parents = num_parents;
+ init->name = clock_name;
+
+ mux->regmap = regmap;
+ mux->reg = reg;
+ mux->clk_id = clock_num;
+ mux->hw.init = init;
+
+ clk = devm_clk_register(dev, &mux->hw);
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+
+ am654_phy->clks[clock_num] = clk;
+
+ return 0;
+}
+
+static const struct of_device_id serdes_am654_id_table[] = {
+ {
+ .compatible = "ti,phy-am654-serdes",
+ },
+ {}
+};
+MODULE_DEVICE_TABLE(of, serdes_am654_id_table);
+
+static int serdes_am654_regfield_init(struct serdes_am654 *am654_phy)
+{
+ struct regmap *regmap = am654_phy->regmap;
+ struct device *dev = am654_phy->dev;
+
+ am654_phy->cmu_master_cdn_o = devm_regmap_field_alloc(dev, regmap,
+ cmu_master_cdn_o);
+ if (IS_ERR(am654_phy->cmu_master_cdn_o)) {
+ dev_err(dev, "CMU_MASTER_CDN_O reg field init failed\n");
+ return PTR_ERR(am654_phy->cmu_master_cdn_o);
+ }
+
+ am654_phy->config_version = devm_regmap_field_alloc(dev, regmap,
+ config_version);
+ if (IS_ERR(am654_phy->config_version)) {
+ dev_err(dev, "CONFIG_VERSION reg field init failed\n");
+ return PTR_ERR(am654_phy->config_version);
+ }
+
+ am654_phy->l1_master_cdn_o = devm_regmap_field_alloc(dev, regmap,
+ l1_master_cdn_o);
+ if (IS_ERR(am654_phy->l1_master_cdn_o)) {
+ dev_err(dev, "L1_MASTER_CDN_O reg field init failed\n");
+ return PTR_ERR(am654_phy->l1_master_cdn_o);
+ }
+
+ am654_phy->cmu_ok_i_0 = devm_regmap_field_alloc(dev, regmap,
+ cmu_ok_i_0);
+ if (IS_ERR(am654_phy->cmu_ok_i_0)) {
+ dev_err(dev, "CMU_OK_I_0 reg field init failed\n");
+ return PTR_ERR(am654_phy->cmu_ok_i_0);
+ }
+
+ am654_phy->por_en = devm_regmap_field_alloc(dev, regmap, por_en);
+ if (IS_ERR(am654_phy->por_en)) {
+ dev_err(dev, "POR_EN reg field init failed\n");
+ return PTR_ERR(am654_phy->por_en);
+ }
+
+ am654_phy->tx0_enable = devm_regmap_field_alloc(dev, regmap,
+ tx0_enable);
+ if (IS_ERR(am654_phy->tx0_enable)) {
+ dev_err(dev, "TX0_ENABLE reg field init failed\n");
+ return PTR_ERR(am654_phy->tx0_enable);
+ }
+
+ am654_phy->rx0_enable = devm_regmap_field_alloc(dev, regmap,
+ rx0_enable);
+ if (IS_ERR(am654_phy->rx0_enable)) {
+ dev_err(dev, "RX0_ENABLE reg field init failed\n");
+ return PTR_ERR(am654_phy->rx0_enable);
+ }
+
+ am654_phy->pll_enable = devm_regmap_field_alloc(dev, regmap,
+ pll_enable);
+ if (IS_ERR(am654_phy->pll_enable)) {
+ dev_err(dev, "PLL_ENABLE reg field init failed\n");
+ return PTR_ERR(am654_phy->pll_enable);
+ }
+
+ am654_phy->pll_ok = devm_regmap_field_alloc(dev, regmap, pll_ok);
+ if (IS_ERR(am654_phy->pll_ok)) {
+ dev_err(dev, "PLL_OK reg field init failed\n");
+ return PTR_ERR(am654_phy->pll_ok);
+ }
+
+ return 0;
+}
+
+static int serdes_am654_probe(struct platform_device *pdev)
+{
+ struct phy_provider *phy_provider;
+ struct device *dev = &pdev->dev;
+ struct device_node *node = dev->of_node;
+ struct clk_onecell_data *clk_data;
+ struct serdes_am654 *am654_phy;
+ struct mux_control *control;
+ const char *clock_name;
+ struct regmap *regmap;
+ void __iomem *base;
+ struct phy *phy;
+ int ret;
+ int i;
+
+ am654_phy = devm_kzalloc(dev, sizeof(*am654_phy), GFP_KERNEL);
+ if (!am654_phy)
+ return -ENOMEM;
+
+ base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ regmap = devm_regmap_init_mmio(dev, base, &serdes_am654_regmap_config);
+ if (IS_ERR(regmap)) {
+ dev_err(dev, "Failed to initialize regmap\n");
+ return PTR_ERR(regmap);
+ }
+
+ control = devm_mux_control_get(dev, NULL);
+ if (IS_ERR(control))
+ return PTR_ERR(control);
+
+ am654_phy->dev = dev;
+ am654_phy->of_node = node;
+ am654_phy->regmap = regmap;
+ am654_phy->control = control;
+ am654_phy->type = PHY_NONE;
+
+ ret = serdes_am654_regfield_init(am654_phy);
+ if (ret) {
+ dev_err(dev, "Failed to initialize regfields\n");
+ return ret;
+ }
+
+ platform_set_drvdata(pdev, am654_phy);
+
+ for (i = 0; i < SERDES_NUM_CLOCKS; i++) {
+ ret = of_property_read_string_index(node, "clock-output-names",
+ i, &clock_name);
+ if (ret) {
+ dev_err(dev, "Failed to get clock name\n");
+ return ret;
+ }
+
+ ret = serdes_am654_clk_register(am654_phy, clock_name, i);
+ if (ret) {
+ dev_err(dev, "Failed to initialize clock %s\n",
+ clock_name);
+ return ret;
+ }
+ }
+
+ clk_data = &am654_phy->clk_data;
+ clk_data->clks = am654_phy->clks;
+ clk_data->clk_num = SERDES_NUM_CLOCKS;
+ ret = of_clk_add_provider(node, of_clk_src_onecell_get, clk_data);
+ if (ret)
+ return ret;
+
+ pm_runtime_enable(dev);
+
+ phy = devm_phy_create(dev, NULL, &ops);
+ if (IS_ERR(phy))
+ return PTR_ERR(phy);
+
+ phy_set_drvdata(phy, am654_phy);
+ phy_provider = devm_of_phy_provider_register(dev, serdes_am654_xlate);
+ if (IS_ERR(phy_provider)) {
+ ret = PTR_ERR(phy_provider);
+ goto clk_err;
+ }
+
+ return 0;
+
+clk_err:
+ of_clk_del_provider(node);
+
+ return ret;
+}
+
+static int serdes_am654_remove(struct platform_device *pdev)
+{
+ struct serdes_am654 *am654_phy = platform_get_drvdata(pdev);
+ struct device_node *node = am654_phy->of_node;
+
+ pm_runtime_disable(&pdev->dev);
+ of_clk_del_provider(node);
+
+ return 0;
+}
+
+static struct platform_driver serdes_am654_driver = {
+ .probe = serdes_am654_probe,
+ .remove = serdes_am654_remove,
+ .driver = {
+ .name = "phy-am654",
+ .of_match_table = serdes_am654_id_table,
+ },
+};
+module_platform_driver(serdes_am654_driver);
+
+MODULE_AUTHOR("Texas Instruments Inc.");
+MODULE_DESCRIPTION("TI AM654x SERDES driver");
+MODULE_LICENSE("GPL v2");
#define SATA_PLL_SOFT_RESET BIT(18)
-#define PIPE3_PHY_PWRCTL_CLK_CMD_MASK 0x003FC000
+#define PIPE3_PHY_PWRCTL_CLK_CMD_MASK GENMASK(21, 14)
#define PIPE3_PHY_PWRCTL_CLK_CMD_SHIFT 14
-#define PIPE3_PHY_PWRCTL_CLK_FREQ_MASK 0xFFC00000
+#define PIPE3_PHY_PWRCTL_CLK_FREQ_MASK GENMASK(31, 22)
#define PIPE3_PHY_PWRCTL_CLK_FREQ_SHIFT 22
-#define PIPE3_PHY_TX_RX_POWERON 0x3
-#define PIPE3_PHY_TX_RX_POWEROFF 0x0
+#define PIPE3_PHY_RX_POWERON (0x1 << PIPE3_PHY_PWRCTL_CLK_CMD_SHIFT)
+#define PIPE3_PHY_TX_POWERON (0x2 << PIPE3_PHY_PWRCTL_CLK_CMD_SHIFT)
#define PCIE_PCS_MASK 0xFF0000
#define PCIE_PCS_DELAY_COUNT_SHIFT 0x10
-#define PCIEPHYRX_ANA_PROGRAMMABILITY 0x0000000C
+#define PIPE3_PHY_RX_ANA_PROGRAMMABILITY 0x0000000C
#define INTERFACE_MASK GENMASK(31, 27)
#define INTERFACE_SHIFT 27
+#define INTERFACE_MODE_USBSS BIT(4)
+#define INTERFACE_MODE_SATA_1P5 BIT(3)
+#define INTERFACE_MODE_SATA_3P0 BIT(2)
+#define INTERFACE_MODE_PCIE BIT(0)
+
#define LOSD_MASK GENMASK(17, 14)
#define LOSD_SHIFT 14
#define MEM_PLLDIV GENMASK(6, 5)
-#define PCIEPHYRX_TRIM 0x0000001C
-#define MEM_DLL_TRIM_SEL GENMASK(31, 30)
+#define PIPE3_PHY_RX_TRIM 0x0000001C
+#define MEM_DLL_TRIM_SEL_MASK GENMASK(31, 30)
#define MEM_DLL_TRIM_SHIFT 30
-#define PCIEPHYRX_DLL 0x00000024
-#define MEM_DLL_PHINT_RATE GENMASK(31, 30)
+#define PIPE3_PHY_RX_DLL 0x00000024
+#define MEM_DLL_PHINT_RATE_MASK GENMASK(31, 30)
+#define MEM_DLL_PHINT_RATE_SHIFT 30
-#define PCIEPHYRX_DIGITAL_MODES 0x00000028
+#define PIPE3_PHY_RX_DIGITAL_MODES 0x00000028
+#define MEM_HS_RATE_MASK GENMASK(28, 27)
+#define MEM_HS_RATE_SHIFT 27
+#define MEM_OVRD_HS_RATE BIT(26)
+#define MEM_OVRD_HS_RATE_SHIFT 26
#define MEM_CDR_FASTLOCK BIT(23)
-#define MEM_CDR_LBW GENMASK(22, 21)
-#define MEM_CDR_STEPCNT GENMASK(20, 19)
+#define MEM_CDR_FASTLOCK_SHIFT 23
+#define MEM_CDR_LBW_MASK GENMASK(22, 21)
+#define MEM_CDR_LBW_SHIFT 21
+#define MEM_CDR_STEPCNT_MASK GENMASK(20, 19)
+#define MEM_CDR_STEPCNT_SHIFT 19
#define MEM_CDR_STL_MASK GENMASK(18, 16)
#define MEM_CDR_STL_SHIFT 16
#define MEM_CDR_THR_MASK GENMASK(15, 13)
#define MEM_CDR_THR_SHIFT 13
#define MEM_CDR_THR_MODE BIT(12)
-#define MEM_CDR_CDR_2NDO_SDM_MODE BIT(11)
-#define MEM_OVRD_HS_RATE BIT(26)
-
-#define PCIEPHYRX_EQUALIZER 0x00000038
-#define MEM_EQLEV GENMASK(31, 16)
-#define MEM_EQFTC GENMASK(15, 11)
-#define MEM_EQCTL GENMASK(10, 7)
+#define MEM_CDR_THR_MODE_SHIFT 12
+#define MEM_CDR_2NDO_SDM_MODE BIT(11)
+#define MEM_CDR_2NDO_SDM_MODE_SHIFT 11
+
+#define PIPE3_PHY_RX_EQUALIZER 0x00000038
+#define MEM_EQLEV_MASK GENMASK(31, 16)
+#define MEM_EQLEV_SHIFT 16
+#define MEM_EQFTC_MASK GENMASK(15, 11)
+#define MEM_EQFTC_SHIFT 11
+#define MEM_EQCTL_MASK GENMASK(10, 7)
#define MEM_EQCTL_SHIFT 7
#define MEM_OVRD_EQLEV BIT(2)
+#define MEM_OVRD_EQLEV_SHIFT 2
#define MEM_OVRD_EQFTC BIT(1)
+#define MEM_OVRD_EQFTC_SHIFT 1
+
+#define SATA_PHY_RX_IO_AND_A2D_OVERRIDES 0x44
+#define MEM_CDR_LOS_SOURCE_MASK GENMASK(10, 9)
+#define MEM_CDR_LOS_SOURCE_SHIFT 9
/*
* This is an Empirical value that works, need to confirm the actual
#define PLL_IDLE_TIME 100 /* in milliseconds */
#define PLL_LOCK_TIME 100 /* in milliseconds */
+enum pipe3_mode { PIPE3_MODE_PCIE = 1,
+ PIPE3_MODE_SATA,
+ PIPE3_MODE_USBSS };
+
struct pipe3_dpll_params {
u16 m;
u8 n;
struct pipe3_dpll_params params;
};
+struct pipe3_settings {
+ u8 ana_interface;
+ u8 ana_losd;
+ u8 dig_fastlock;
+ u8 dig_lbw;
+ u8 dig_stepcnt;
+ u8 dig_stl;
+ u8 dig_thr;
+ u8 dig_thr_mode;
+ u8 dig_2ndo_sdm_mode;
+ u8 dig_hs_rate;
+ u8 dig_ovrd_hs_rate;
+ u8 dll_trim_sel;
+ u8 dll_phint_rate;
+ u8 eq_lev;
+ u8 eq_ftc;
+ u8 eq_ctl;
+ u8 eq_ovrd_lev;
+ u8 eq_ovrd_ftc;
+};
+
struct ti_pipe3 {
void __iomem *pll_ctrl_base;
void __iomem *phy_rx;
unsigned int power_reg; /* power reg. index within syscon */
unsigned int pcie_pcs_reg; /* pcs reg. index in syscon */
bool sata_refclk_enabled;
+ enum pipe3_mode mode;
+ struct pipe3_settings settings;
};
static struct pipe3_dpll_map dpll_map_usb[] = {
{ }, /* Terminator */
};
+struct pipe3_data {
+ enum pipe3_mode mode;
+ struct pipe3_dpll_map *dpll_map;
+ struct pipe3_settings settings;
+};
+
+static struct pipe3_data data_usb = {
+ .mode = PIPE3_MODE_USBSS,
+ .dpll_map = dpll_map_usb,
+ .settings = {
+ /* DRA75x TRM Table 26-17 Preferred USB3_PHY_RX SCP Register Settings */
+ .ana_interface = INTERFACE_MODE_USBSS,
+ .ana_losd = 0xa,
+ .dig_fastlock = 1,
+ .dig_lbw = 3,
+ .dig_stepcnt = 0,
+ .dig_stl = 0x3,
+ .dig_thr = 1,
+ .dig_thr_mode = 1,
+ .dig_2ndo_sdm_mode = 0,
+ .dig_hs_rate = 0,
+ .dig_ovrd_hs_rate = 1,
+ .dll_trim_sel = 0x2,
+ .dll_phint_rate = 0x3,
+ .eq_lev = 0,
+ .eq_ftc = 0,
+ .eq_ctl = 0x9,
+ .eq_ovrd_lev = 0,
+ .eq_ovrd_ftc = 0,
+ },
+};
+
+static struct pipe3_data data_sata = {
+ .mode = PIPE3_MODE_SATA,
+ .dpll_map = dpll_map_sata,
+ .settings = {
+ /* DRA75x TRM Table 26-9 Preferred SATA_PHY_RX SCP Register Settings */
+ .ana_interface = INTERFACE_MODE_SATA_3P0,
+ .ana_losd = 0x5,
+ .dig_fastlock = 1,
+ .dig_lbw = 3,
+ .dig_stepcnt = 0,
+ .dig_stl = 0x3,
+ .dig_thr = 1,
+ .dig_thr_mode = 1,
+ .dig_2ndo_sdm_mode = 0,
+ .dig_hs_rate = 0, /* Not in TRM preferred settings */
+ .dig_ovrd_hs_rate = 0, /* Not in TRM preferred settings */
+ .dll_trim_sel = 0x1,
+ .dll_phint_rate = 0x2, /* for 1.5 GHz DPLL clock */
+ .eq_lev = 0,
+ .eq_ftc = 0x1f,
+ .eq_ctl = 0,
+ .eq_ovrd_lev = 1,
+ .eq_ovrd_ftc = 1,
+ },
+};
+
+static struct pipe3_data data_pcie = {
+ .mode = PIPE3_MODE_PCIE,
+ .settings = {
+ /* DRA75x TRM Table 26-62 Preferred PCIe_PHY_RX SCP Register Settings */
+ .ana_interface = INTERFACE_MODE_PCIE,
+ .ana_losd = 0xa,
+ .dig_fastlock = 1,
+ .dig_lbw = 3,
+ .dig_stepcnt = 0,
+ .dig_stl = 0x3,
+ .dig_thr = 1,
+ .dig_thr_mode = 1,
+ .dig_2ndo_sdm_mode = 0,
+ .dig_hs_rate = 0,
+ .dig_ovrd_hs_rate = 0,
+ .dll_trim_sel = 0x2,
+ .dll_phint_rate = 0x3,
+ .eq_lev = 0,
+ .eq_ftc = 0x1f,
+ .eq_ctl = 1,
+ .eq_ovrd_lev = 0,
+ .eq_ovrd_ftc = 0,
+ },
+};
+
static inline u32 ti_pipe3_readl(void __iomem *addr, unsigned offset)
{
return __raw_readl(addr + offset);
static int ti_pipe3_power_off(struct phy *x)
{
- u32 val;
int ret;
struct ti_pipe3 *phy = phy_get_drvdata(x);
return 0;
}
- val = PIPE3_PHY_TX_RX_POWEROFF << PIPE3_PHY_PWRCTL_CLK_CMD_SHIFT;
-
ret = regmap_update_bits(phy->phy_power_syscon, phy->power_reg,
- PIPE3_PHY_PWRCTL_CLK_CMD_MASK, val);
+ PIPE3_PHY_PWRCTL_CLK_CMD_MASK, 0);
return ret;
}
+static void ti_pipe3_calibrate(struct ti_pipe3 *phy);
+
static int ti_pipe3_power_on(struct phy *x)
{
u32 val;
int ret;
unsigned long rate;
struct ti_pipe3 *phy = phy_get_drvdata(x);
+ bool rx_pending = false;
if (!phy->phy_power_syscon) {
omap_control_phy_power(phy->control_dev, 1);
return -EINVAL;
}
rate = rate / 1000000;
- mask = OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_CMD_MASK |
- OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_FREQ_MASK;
- val = PIPE3_PHY_TX_RX_POWERON << PIPE3_PHY_PWRCTL_CLK_CMD_SHIFT;
- val |= rate << OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_FREQ_SHIFT;
-
+ mask = OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_FREQ_MASK;
+ val = rate << OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_FREQ_SHIFT;
ret = regmap_update_bits(phy->phy_power_syscon, phy->power_reg,
mask, val);
- return ret;
+ /*
+ * For PCIe, TX and RX must be powered on simultaneously.
+ * For USB and SATA, TX must be powered on before RX
+ */
+ mask = OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_CMD_MASK;
+ if (phy->mode == PIPE3_MODE_SATA || phy->mode == PIPE3_MODE_USBSS) {
+ val = PIPE3_PHY_TX_POWERON;
+ rx_pending = true;
+ } else {
+ val = PIPE3_PHY_TX_POWERON | PIPE3_PHY_RX_POWERON;
+ }
+
+ regmap_update_bits(phy->phy_power_syscon, phy->power_reg,
+ mask, val);
+
+ if (rx_pending) {
+ val = PIPE3_PHY_TX_POWERON | PIPE3_PHY_RX_POWERON;
+ regmap_update_bits(phy->phy_power_syscon, phy->power_reg,
+ mask, val);
+ }
+
+ if (phy->mode == PIPE3_MODE_PCIE)
+ ti_pipe3_calibrate(phy);
+
+ return 0;
}
static int ti_pipe3_dpll_wait_lock(struct ti_pipe3 *phy)
static void ti_pipe3_calibrate(struct ti_pipe3 *phy)
{
u32 val;
+ struct pipe3_settings *s = &phy->settings;
- val = ti_pipe3_readl(phy->phy_rx, PCIEPHYRX_ANA_PROGRAMMABILITY);
+ val = ti_pipe3_readl(phy->phy_rx, PIPE3_PHY_RX_ANA_PROGRAMMABILITY);
val &= ~(INTERFACE_MASK | LOSD_MASK | MEM_PLLDIV);
- val = (0x1 << INTERFACE_SHIFT | 0xA << LOSD_SHIFT);
- ti_pipe3_writel(phy->phy_rx, PCIEPHYRX_ANA_PROGRAMMABILITY, val);
-
- val = ti_pipe3_readl(phy->phy_rx, PCIEPHYRX_DIGITAL_MODES);
- val &= ~(MEM_CDR_STEPCNT | MEM_CDR_STL_MASK | MEM_CDR_THR_MASK |
- MEM_CDR_CDR_2NDO_SDM_MODE | MEM_OVRD_HS_RATE);
- val |= (MEM_CDR_FASTLOCK | MEM_CDR_LBW | 0x3 << MEM_CDR_STL_SHIFT |
- 0x1 << MEM_CDR_THR_SHIFT | MEM_CDR_THR_MODE);
- ti_pipe3_writel(phy->phy_rx, PCIEPHYRX_DIGITAL_MODES, val);
-
- val = ti_pipe3_readl(phy->phy_rx, PCIEPHYRX_TRIM);
- val &= ~MEM_DLL_TRIM_SEL;
- val |= 0x2 << MEM_DLL_TRIM_SHIFT;
- ti_pipe3_writel(phy->phy_rx, PCIEPHYRX_TRIM, val);
-
- val = ti_pipe3_readl(phy->phy_rx, PCIEPHYRX_DLL);
- val |= MEM_DLL_PHINT_RATE;
- ti_pipe3_writel(phy->phy_rx, PCIEPHYRX_DLL, val);
-
- val = ti_pipe3_readl(phy->phy_rx, PCIEPHYRX_EQUALIZER);
- val &= ~(MEM_EQLEV | MEM_EQCTL | MEM_OVRD_EQLEV | MEM_OVRD_EQFTC);
- val |= MEM_EQFTC | 0x1 << MEM_EQCTL_SHIFT;
- ti_pipe3_writel(phy->phy_rx, PCIEPHYRX_EQUALIZER, val);
+ val |= (s->ana_interface << INTERFACE_SHIFT | s->ana_losd << LOSD_SHIFT);
+ ti_pipe3_writel(phy->phy_rx, PIPE3_PHY_RX_ANA_PROGRAMMABILITY, val);
+
+ val = ti_pipe3_readl(phy->phy_rx, PIPE3_PHY_RX_DIGITAL_MODES);
+ val &= ~(MEM_HS_RATE_MASK | MEM_OVRD_HS_RATE | MEM_CDR_FASTLOCK |
+ MEM_CDR_LBW_MASK | MEM_CDR_STEPCNT_MASK | MEM_CDR_STL_MASK |
+ MEM_CDR_THR_MASK | MEM_CDR_THR_MODE | MEM_CDR_2NDO_SDM_MODE);
+ val |= s->dig_hs_rate << MEM_HS_RATE_SHIFT |
+ s->dig_ovrd_hs_rate << MEM_OVRD_HS_RATE_SHIFT |
+ s->dig_fastlock << MEM_CDR_FASTLOCK_SHIFT |
+ s->dig_lbw << MEM_CDR_LBW_SHIFT |
+ s->dig_stepcnt << MEM_CDR_STEPCNT_SHIFT |
+ s->dig_stl << MEM_CDR_STL_SHIFT |
+ s->dig_thr << MEM_CDR_THR_SHIFT |
+ s->dig_thr_mode << MEM_CDR_THR_MODE_SHIFT |
+ s->dig_2ndo_sdm_mode << MEM_CDR_2NDO_SDM_MODE_SHIFT;
+ ti_pipe3_writel(phy->phy_rx, PIPE3_PHY_RX_DIGITAL_MODES, val);
+
+ val = ti_pipe3_readl(phy->phy_rx, PIPE3_PHY_RX_TRIM);
+ val &= ~MEM_DLL_TRIM_SEL_MASK;
+ val |= s->dll_trim_sel << MEM_DLL_TRIM_SHIFT;
+ ti_pipe3_writel(phy->phy_rx, PIPE3_PHY_RX_TRIM, val);
+
+ val = ti_pipe3_readl(phy->phy_rx, PIPE3_PHY_RX_DLL);
+ val &= ~MEM_DLL_PHINT_RATE_MASK;
+ val |= s->dll_phint_rate << MEM_DLL_PHINT_RATE_SHIFT;
+ ti_pipe3_writel(phy->phy_rx, PIPE3_PHY_RX_DLL, val);
+
+ val = ti_pipe3_readl(phy->phy_rx, PIPE3_PHY_RX_EQUALIZER);
+ val &= ~(MEM_EQLEV_MASK | MEM_EQFTC_MASK | MEM_EQCTL_MASK |
+ MEM_OVRD_EQLEV | MEM_OVRD_EQFTC);
+ val |= s->eq_lev << MEM_EQLEV_SHIFT |
+ s->eq_ftc << MEM_EQFTC_SHIFT |
+ s->eq_ctl << MEM_EQCTL_SHIFT |
+ s->eq_ovrd_lev << MEM_OVRD_EQLEV_SHIFT |
+ s->eq_ovrd_ftc << MEM_OVRD_EQFTC_SHIFT;
+ ti_pipe3_writel(phy->phy_rx, PIPE3_PHY_RX_EQUALIZER, val);
+
+ if (phy->mode == PIPE3_MODE_SATA) {
+ val = ti_pipe3_readl(phy->phy_rx,
+ SATA_PHY_RX_IO_AND_A2D_OVERRIDES);
+ val &= ~MEM_CDR_LOS_SOURCE_MASK;
+ ti_pipe3_writel(phy->phy_rx, SATA_PHY_RX_IO_AND_A2D_OVERRIDES,
+ val);
+ }
}
static int ti_pipe3_init(struct phy *x)
* as recommended in AM572x TRM SPRUHZ6, section 18.5.2.2, table
* 18-1804.
*/
- if (of_device_is_compatible(phy->dev->of_node, "ti,phy-pipe3-pcie")) {
+ if (phy->mode == PIPE3_MODE_PCIE) {
if (!phy->pcs_syscon) {
omap_control_pcie_pcs(phy->control_dev, 0x96);
return 0;
val = 0x96 << OMAP_CTRL_PCIE_PCS_DELAY_COUNT_SHIFT;
ret = regmap_update_bits(phy->pcs_syscon, phy->pcie_pcs_reg,
PCIE_PCS_MASK, val);
- if (ret)
- return ret;
-
- ti_pipe3_calibrate(phy);
-
- return 0;
+ return ret;
}
/* Bring it out of IDLE if it is IDLE */
/* SATA has issues if re-programmed when locked */
val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_STATUS);
- if ((val & PLL_LOCK) && of_device_is_compatible(phy->dev->of_node,
- "ti,phy-pipe3-sata"))
+ if ((val & PLL_LOCK) && phy->mode == PIPE3_MODE_SATA)
return ret;
/* Program the DPLL */
return -EINVAL;
}
+ ti_pipe3_calibrate(phy);
+
return ret;
}
/* If dpll_reset_syscon is not present we wont power down SATA DPLL
* due to Errata i783
*/
- if (of_device_is_compatible(phy->dev->of_node, "ti,phy-pipe3-sata") &&
- !phy->dpll_reset_syscon)
+ if (phy->mode == PIPE3_MODE_SATA && !phy->dpll_reset_syscon)
return 0;
/* PCIe doesn't have internal DPLL */
- if (!of_device_is_compatible(phy->dev->of_node, "ti,phy-pipe3-pcie")) {
+ if (phy->mode != PIPE3_MODE_PCIE) {
/* Put DPLL in IDLE mode */
val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_CONFIGURATION2);
val |= PLL_IDLE;
}
/* i783: SATA needs control bit toggle after PLL unlock */
- if (of_device_is_compatible(phy->dev->of_node, "ti,phy-pipe3-sata")) {
+ if (phy->mode == PIPE3_MODE_SATA) {
regmap_update_bits(phy->dpll_reset_syscon, phy->dpll_reset_reg,
SATA_PLL_SOFT_RESET, SATA_PLL_SOFT_RESET);
regmap_update_bits(phy->dpll_reset_syscon, phy->dpll_reset_reg,
{
struct clk *clk;
struct device *dev = phy->dev;
- struct device_node *node = dev->of_node;
phy->refclk = devm_clk_get(dev, "refclk");
if (IS_ERR(phy->refclk)) {
/* older DTBs have missing refclk in SATA PHY
* so don't bail out in case of SATA PHY.
*/
- if (!of_device_is_compatible(node, "ti,phy-pipe3-sata"))
+ if (phy->mode != PIPE3_MODE_SATA)
return PTR_ERR(phy->refclk);
}
- if (!of_device_is_compatible(node, "ti,phy-pipe3-sata")) {
+ if (phy->mode != PIPE3_MODE_SATA) {
phy->wkupclk = devm_clk_get(dev, "wkupclk");
if (IS_ERR(phy->wkupclk)) {
dev_err(dev, "unable to get wkupclk\n");
phy->wkupclk = ERR_PTR(-ENODEV);
}
- if (!of_device_is_compatible(node, "ti,phy-pipe3-pcie") ||
- phy->phy_power_syscon) {
+ if (phy->mode != PIPE3_MODE_PCIE || phy->phy_power_syscon) {
phy->sys_clk = devm_clk_get(dev, "sysclk");
if (IS_ERR(phy->sys_clk)) {
dev_err(dev, "unable to get sysclk\n");
}
}
- if (of_device_is_compatible(node, "ti,phy-pipe3-pcie")) {
+ if (phy->mode == PIPE3_MODE_PCIE) {
clk = devm_clk_get(dev, "dpll_ref");
if (IS_ERR(clk)) {
dev_err(dev, "unable to get dpll ref clk\n");
phy->control_dev = &control_pdev->dev;
}
- if (of_device_is_compatible(node, "ti,phy-pipe3-pcie")) {
+ if (phy->mode == PIPE3_MODE_PCIE) {
phy->pcs_syscon = syscon_regmap_lookup_by_phandle(node,
"syscon-pcs");
if (IS_ERR(phy->pcs_syscon)) {
}
}
- if (of_device_is_compatible(node, "ti,phy-pipe3-sata")) {
+ if (phy->mode == PIPE3_MODE_SATA) {
phy->dpll_reset_syscon = syscon_regmap_lookup_by_phandle(node,
"syscon-pllreset");
if (IS_ERR(phy->dpll_reset_syscon)) {
{
struct resource *res;
struct device *dev = phy->dev;
- struct device_node *node = dev->of_node;
struct platform_device *pdev = to_platform_device(dev);
- if (!of_device_is_compatible(node, "ti,phy-pipe3-pcie"))
- return 0;
-
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
"phy_rx");
phy->phy_rx = devm_ioremap_resource(dev, res);
static int ti_pipe3_get_pll_base(struct ti_pipe3 *phy)
{
struct resource *res;
- const struct of_device_id *match;
struct device *dev = phy->dev;
- struct device_node *node = dev->of_node;
struct platform_device *pdev = to_platform_device(dev);
- if (of_device_is_compatible(node, "ti,phy-pipe3-pcie"))
+ if (phy->mode == PIPE3_MODE_PCIE)
return 0;
- match = of_match_device(ti_pipe3_id_table, dev);
- if (!match)
- return -EINVAL;
-
- phy->dpll_map = (struct pipe3_dpll_map *)match->data;
- if (!phy->dpll_map) {
- dev_err(dev, "no DPLL data\n");
- return -EINVAL;
- }
-
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
"pll_ctrl");
phy->pll_ctrl_base = devm_ioremap_resource(dev, res);
struct ti_pipe3 *phy;
struct phy *generic_phy;
struct phy_provider *phy_provider;
- struct device_node *node = pdev->dev.of_node;
struct device *dev = &pdev->dev;
int ret;
+ const struct of_device_id *match;
+ struct pipe3_data *data;
phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
if (!phy)
return -ENOMEM;
- phy->dev = dev;
+ match = of_match_device(ti_pipe3_id_table, dev);
+ if (!match)
+ return -EINVAL;
+
+ data = (struct pipe3_data *)match->data;
+ if (!data) {
+ dev_err(dev, "no driver data\n");
+ return -EINVAL;
+ }
+
+ phy->dev = dev;
+ phy->mode = data->mode;
+ phy->dpll_map = data->dpll_map;
+ phy->settings = data->settings;
ret = ti_pipe3_get_pll_base(phy);
if (ret)
/*
* Prevent auto-disable of refclk for SATA PHY due to Errata i783
*/
- if (of_device_is_compatible(node, "ti,phy-pipe3-sata")) {
+ if (phy->mode == PIPE3_MODE_SATA) {
if (!IS_ERR(phy->refclk)) {
clk_prepare_enable(phy->refclk);
phy->sata_refclk_enabled = true;
static const struct of_device_id ti_pipe3_id_table[] = {
{
.compatible = "ti,phy-usb3",
- .data = dpll_map_usb,
+ .data = &data_usb,
},
{
.compatible = "ti,omap-usb3",
- .data = dpll_map_usb,
+ .data = &data_usb,
},
{
.compatible = "ti,phy-pipe3-sata",
- .data = dpll_map_sata,
+ .data = &data_sata,
},
{
.compatible = "ti,phy-pipe3-pcie",
+ .data = &data_pcie,
},
{}
};
config SCSI_UFS_QCOM
tristate "QCOM specific hooks to UFS controller platform driver"
depends on SCSI_UFSHCD_PLATFORM && ARCH_QCOM
+ select RESET_CONTROLLER
help
This selects the QCOM specific additions to UFSHCD platform driver.
UFS host on QCOM needs some vendor specific configuration before
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/phy/phy.h>
+#include <linux/reset-controller.h>
#include "ufshcd.h"
#include "ufshcd-pltfrm.h"
static int ufs_qcom_set_dme_vs_core_clk_ctrl_clear_div(struct ufs_hba *hba,
u32 clk_cycles);
+static struct ufs_qcom_host *rcdev_to_ufs_host(struct reset_controller_dev *rcd)
+{
+ return container_of(rcd, struct ufs_qcom_host, rcdev);
+}
+
static void ufs_qcom_dump_regs_wrapper(struct ufs_hba *hba, int offset, int len,
const char *prefix, void *priv)
{
if (is_rate_B)
phy_set_mode(phy, PHY_MODE_UFS_HS_B);
- /* Assert PHY reset and apply PHY calibration values */
- ufs_qcom_assert_reset(hba);
- /* provide 1ms delay to let the reset pulse propagate */
- usleep_range(1000, 1100);
-
/* phy initialization - calibrate the phy */
ret = phy_init(phy);
if (ret) {
goto out;
}
- /* De-assert PHY reset and start serdes */
- ufs_qcom_deassert_reset(hba);
-
- /*
- * after reset deassertion, phy will need all ref clocks,
- * voltage, current to settle down before starting serdes.
- */
- usleep_range(1000, 1100);
-
/* power on phy - start serdes and phy's power and clocks */
ret = phy_power_on(phy);
if (ret) {
return 0;
out_disable_phy:
- ufs_qcom_assert_reset(hba);
phy_exit(phy);
out:
return ret;
ufs_qcom_disable_lane_clks(host);
phy_power_off(phy);
- /* Assert PHY soft reset */
- ufs_qcom_assert_reset(hba);
- goto out;
- }
-
- /*
- * If UniPro link is not active, PHY ref_clk, main PHY analog power
- * rail and low noise analog power rail for PLL can be switched off.
- */
- if (!ufs_qcom_is_link_active(hba)) {
+ } else if (!ufs_qcom_is_link_active(hba)) {
ufs_qcom_disable_lane_clks(host);
- phy_power_off(phy);
}
-out:
return ret;
}
struct phy *phy = host->generic_phy;
int err;
- err = phy_power_on(phy);
- if (err) {
- dev_err(hba->dev, "%s: failed enabling regs, err = %d\n",
- __func__, err);
- goto out;
- }
+ if (ufs_qcom_is_link_off(hba)) {
+ err = phy_power_on(phy);
+ if (err) {
+ dev_err(hba->dev, "%s: failed PHY power on: %d\n",
+ __func__, err);
+ return err;
+ }
- err = ufs_qcom_enable_lane_clks(host);
- if (err)
- goto out;
+ err = ufs_qcom_enable_lane_clks(host);
+ if (err)
+ return err;
- hba->is_sys_suspended = false;
+ } else if (!ufs_qcom_is_link_active(hba)) {
+ err = ufs_qcom_enable_lane_clks(host);
+ if (err)
+ return err;
+ }
-out:
- return err;
+ hba->is_sys_suspended = false;
+ return 0;
}
struct ufs_qcom_dev_params {
return 0;
if (on && (status == POST_CHANGE)) {
- phy_power_on(host->generic_phy);
-
/* enable the device ref clock for HS mode*/
if (ufshcd_is_hs_mode(&hba->pwr_info))
ufs_qcom_dev_ref_clk_ctrl(host, true);
if (!ufs_qcom_is_link_active(hba)) {
/* disable device ref_clk */
ufs_qcom_dev_ref_clk_ctrl(host, false);
-
- /* powering off PHY during aggressive clk gating */
- phy_power_off(host->generic_phy);
}
vote = host->bus_vote.min_bw_vote;
return err;
}
+static int
+ufs_qcom_reset_assert(struct reset_controller_dev *rcdev, unsigned long id)
+{
+ struct ufs_qcom_host *host = rcdev_to_ufs_host(rcdev);
+
+ /* Currently this code only knows about a single reset. */
+ WARN_ON(id);
+ ufs_qcom_assert_reset(host->hba);
+ /* provide 1ms delay to let the reset pulse propagate. */
+ usleep_range(1000, 1100);
+ return 0;
+}
+
+static int
+ufs_qcom_reset_deassert(struct reset_controller_dev *rcdev, unsigned long id)
+{
+ struct ufs_qcom_host *host = rcdev_to_ufs_host(rcdev);
+
+ /* Currently this code only knows about a single reset. */
+ WARN_ON(id);
+ ufs_qcom_deassert_reset(host->hba);
+
+ /*
+ * after reset deassertion, phy will need all ref clocks,
+ * voltage, current to settle down before starting serdes.
+ */
+ usleep_range(1000, 1100);
+ return 0;
+}
+
+static const struct reset_control_ops ufs_qcom_reset_ops = {
+ .assert = ufs_qcom_reset_assert,
+ .deassert = ufs_qcom_reset_deassert,
+};
+
#define ANDROID_BOOT_DEV_MAX 30
static char android_boot_dev[ANDROID_BOOT_DEV_MAX];
host->hba = hba;
ufshcd_set_variant(hba, host);
+ /* Fire up the reset controller. Failure here is non-fatal. */
+ host->rcdev.of_node = dev->of_node;
+ host->rcdev.ops = &ufs_qcom_reset_ops;
+ host->rcdev.owner = dev->driver->owner;
+ host->rcdev.nr_resets = 1;
+ err = devm_reset_controller_register(dev, &host->rcdev);
+ if (err) {
+ dev_warn(dev, "Failed to register reset controller\n");
+ err = 0;
+ }
+
/*
* voting/devoting device ref_clk source is time consuming hence
* skip devoting it during aggressive clock gating. This clock
#ifndef UFS_QCOM_H_
#define UFS_QCOM_H_
+#include <linux/reset-controller.h>
+
#define MAX_UFS_QCOM_HOSTS 1
#define MAX_U32 (~(u32)0)
#define MPHY_TX_FSM_STATE 0x41
/* Bitmask for enabling debug prints */
u32 dbg_print_en;
struct ufs_qcom_testbus testbus;
+
+ struct reset_controller_dev rcdev;
};
static inline u32
config SUNXI_SRAM
bool
default ARCH_SUNXI
+ select REGMAP_MMIO
help
Say y here to enable the SRAM controller support. This
device is responsible on mapping the SRAM in the sunXi SoCs
if (IS_ERR(clk))
return PTR_ERR(clk);
- ci->fs_clk = clk = devm_clk_get(&pdev->dev, "fs");
- if (IS_ERR(clk)) {
- if (PTR_ERR(clk) == -EPROBE_DEFER)
- return -EPROBE_DEFER;
- ci->fs_clk = NULL;
- }
+ ci->fs_clk = clk = devm_clk_get_optional(&pdev->dev, "fs");
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
ci->base = devm_ioremap_resource(&pdev->dev, res);
{
struct acm_rb *rb = urb->context;
struct acm *acm = rb->instance;
- unsigned long flags;
int status = urb->status;
+ bool stopped = false;
+ bool stalled = false;
dev_vdbg(&acm->data->dev, "got urb %d, len %d, status %d\n",
rb->index, urb->actual_length, status);
- set_bit(rb->index, &acm->read_urbs_free);
-
if (!acm->dev) {
dev_dbg(&acm->data->dev, "%s - disconnected\n", __func__);
return;
break;
case -EPIPE:
set_bit(EVENT_RX_STALL, &acm->flags);
- schedule_work(&acm->work);
- return;
+ stalled = true;
+ break;
case -ENOENT:
case -ECONNRESET:
case -ESHUTDOWN:
dev_dbg(&acm->data->dev,
"%s - urb shutting down with status: %d\n",
__func__, status);
- return;
+ stopped = true;
+ break;
default:
dev_dbg(&acm->data->dev,
"%s - nonzero urb status received: %d\n",
}
/*
- * Unthrottle may run on another CPU which needs to see events
- * in the same order. Submission has an implict barrier
+ * Make sure URB processing is done before marking as free to avoid
+ * racing with unthrottle() on another CPU. Matches the barriers
+ * implied by the test_and_clear_bit() in acm_submit_read_urb().
*/
smp_mb__before_atomic();
+ set_bit(rb->index, &acm->read_urbs_free);
+ /*
+ * Make sure URB is marked as free before checking the throttled flag
+ * to avoid racing with unthrottle() on another CPU. Matches the
+ * smp_mb() in unthrottle().
+ */
+ smp_mb__after_atomic();
- /* throttle device if requested by tty */
- spin_lock_irqsave(&acm->read_lock, flags);
- acm->throttled = acm->throttle_req;
- if (!acm->throttled) {
- spin_unlock_irqrestore(&acm->read_lock, flags);
- acm_submit_read_urb(acm, rb->index, GFP_ATOMIC);
- } else {
- spin_unlock_irqrestore(&acm->read_lock, flags);
+ if (stopped || stalled) {
+ if (stalled)
+ schedule_work(&acm->work);
+ return;
}
+
+ if (test_bit(ACM_THROTTLED, &acm->flags))
+ return;
+
+ acm_submit_read_urb(acm, rb->index, GFP_ATOMIC);
}
/* data interface wrote those outgoing bytes */
/*
* Unthrottle device in case the TTY was closed while throttled.
*/
- spin_lock_irq(&acm->read_lock);
- acm->throttled = 0;
- acm->throttle_req = 0;
- spin_unlock_irq(&acm->read_lock);
+ clear_bit(ACM_THROTTLED, &acm->flags);
retval = acm_submit_read_urbs(acm, GFP_KERNEL);
if (retval)
{
struct acm *acm = tty->driver_data;
- spin_lock_irq(&acm->read_lock);
- acm->throttle_req = 1;
- spin_unlock_irq(&acm->read_lock);
+ set_bit(ACM_THROTTLED, &acm->flags);
}
static void acm_tty_unthrottle(struct tty_struct *tty)
{
struct acm *acm = tty->driver_data;
- unsigned int was_throttled;
- spin_lock_irq(&acm->read_lock);
- was_throttled = acm->throttled;
- acm->throttled = 0;
- acm->throttle_req = 0;
- spin_unlock_irq(&acm->read_lock);
+ clear_bit(ACM_THROTTLED, &acm->flags);
+
+ /* Matches the smp_mb__after_atomic() in acm_read_bulk_callback(). */
+ smp_mb();
- if (was_throttled)
- acm_submit_read_urbs(acm, GFP_KERNEL);
+ acm_submit_read_urbs(acm, GFP_KERNEL);
}
static int acm_tty_break_ctl(struct tty_struct *tty, int state)
unsigned long flags;
# define EVENT_TTY_WAKEUP 0
# define EVENT_RX_STALL 1
+# define ACM_THROTTLED 2
struct usb_cdc_line_coding line; /* bits, stop, parity */
struct work_struct work; /* work queue entry for line discipline waking up */
unsigned int ctrlin; /* input control lines (DCD, DSR, RI, break, overruns) */
unsigned int ctrl_caps; /* control capabilities from the class specific header */
unsigned int susp_count; /* number of suspended interfaces */
unsigned int combined_interfaces:1; /* control and data collapsed */
- unsigned int throttled:1; /* actually throttled */
- unsigned int throttle_req:1; /* throttle requested */
u8 bInterval;
struct usb_anchor delayed; /* writes queued for a device about to be woken */
unsigned long quirks;
#include <linux/usb/otg.h>
#include <linux/of_platform.h>
+static const char *const ep_type_names[] = {
+ [USB_ENDPOINT_XFER_CONTROL] = "ctrl",
+ [USB_ENDPOINT_XFER_ISOC] = "isoc",
+ [USB_ENDPOINT_XFER_BULK] = "bulk",
+ [USB_ENDPOINT_XFER_INT] = "intr",
+};
+
+const char *usb_ep_type_string(int ep_type)
+{
+ if (ep_type < 0 || ep_type >= ARRAY_SIZE(ep_type_names))
+ return "unknown";
+
+ return ep_type_names[ep_type];
+}
+EXPORT_SYMBOL_GPL(usb_ep_type_string);
+
const char *usb_otg_state_string(enum usb_otg_state state)
{
static const char *const names[] = {
/* kick hcd */
unlink1(hcd, urb, -ESHUTDOWN);
dev_dbg (hcd->self.controller,
- "shutdown urb %pK ep%d%s%s\n",
+ "shutdown urb %pK ep%d%s-%s\n",
urb, usb_endpoint_num(&ep->desc),
is_in ? "in" : "out",
- ({ char *s;
-
- switch (usb_endpoint_type(&ep->desc)) {
- case USB_ENDPOINT_XFER_CONTROL:
- s = ""; break;
- case USB_ENDPOINT_XFER_BULK:
- s = "-bulk"; break;
- case USB_ENDPOINT_XFER_INT:
- s = "-intr"; break;
- default:
- s = "-iso"; break;
- };
- s;
- }));
+ usb_ep_type_string(usb_endpoint_type(&ep->desc)));
usb_put_urb (urb);
/* list contents may have changed */
/*-------------------------------------------------------------------------*/
+/* Workqueue routine for when the root-hub has died. */
+static void hcd_died_work(struct work_struct *work)
+{
+ struct usb_hcd *hcd = container_of(work, struct usb_hcd, died_work);
+ static char *env[] = {
+ "ERROR=DEAD",
+ NULL
+ };
+
+ /* Notify user space that the host controller has died */
+ kobject_uevent_env(&hcd->self.root_hub->dev.kobj, KOBJ_OFFLINE, env);
+}
+
/**
* usb_hc_died - report abnormal shutdown of a host controller (bus glue)
* @hcd: pointer to the HCD representing the controller
usb_kick_hub_wq(hcd->self.root_hub);
}
}
+
+ /* Handle the case where this function gets called with a shared HCD */
+ if (usb_hcd_is_primary_hcd(hcd))
+ schedule_work(&hcd->died_work);
+ else
+ schedule_work(&hcd->primary_hcd->died_work);
+
spin_unlock_irqrestore (&hcd_root_hub_lock, flags);
/* Make sure that the other roothub is also deallocated. */
}
INIT_WORK(&hcd->wakeup_work, hcd_resume_work);
#endif
+ INIT_WORK(&hcd->died_work, hcd_died_work);
+
hcd->driver = driver;
hcd->speed = driver->flags & HCD_MASK;
hcd->product_desc = (driver->product_desc) ? driver->product_desc :
#ifdef CONFIG_PM
cancel_work_sync(&hcd->wakeup_work);
#endif
+ cancel_work_sync(&hcd->died_work);
mutex_lock(&usb_bus_idr_lock);
usb_disconnect(&rhdev); /* Sets rhdev to NULL */
mutex_unlock(&usb_bus_idr_lock);
#ifdef CONFIG_PM
cancel_work_sync(&hcd->wakeup_work);
#endif
+ cancel_work_sync(&hcd->died_work);
mutex_lock(&usb_bus_idr_lock);
usb_disconnect(&rhdev); /* Sets rhdev to NULL */
{
struct usb_hcd *hcd = platform_get_drvdata(dev);
+ /* No need for pm_runtime_put(), we're shutting down */
+ pm_runtime_get_sync(&dev->dev);
+
if (hcd->driver->shutdown)
hcd->driver->shutdown(hcd);
}
struct usb_hub *hub = usb_get_intfdata(intf);
struct usb_device *hdev = hub->hdev;
unsigned port1;
- int status;
/*
* Warn if children aren't already suspended.
if (hub_is_superspeed(hdev) && hdev->do_remote_wakeup) {
/* Enable hub to send remote wakeup for all ports. */
for (port1 = 1; port1 <= hdev->maxchild; port1++) {
- status = set_port_feature(hdev,
- port1 |
- USB_PORT_FEAT_REMOTE_WAKE_CONNECT |
- USB_PORT_FEAT_REMOTE_WAKE_DISCONNECT |
- USB_PORT_FEAT_REMOTE_WAKE_OVER_CURRENT,
- USB_PORT_FEAT_REMOTE_WAKE_MASK);
+ set_port_feature(hdev,
+ port1 |
+ USB_PORT_FEAT_REMOTE_WAKE_CONNECT |
+ USB_PORT_FEAT_REMOTE_WAKE_DISCONNECT |
+ USB_PORT_FEAT_REMOTE_WAKE_OVER_CURRENT,
+ USB_PORT_FEAT_REMOTE_WAKE_MASK);
}
}
cintf->needs_binding = 1;
}
}
- usb_unbind_and_rebind_marked_interfaces(udev);
+
+ /* If the reset failed, hub_wq will unbind drivers later */
+ if (ret == 0)
+ usb_unbind_and_rebind_marked_interfaces(udev);
}
usb_autosuspend_device(udev);
}
/* Clock */
- hsotg->clk = devm_clk_get(hsotg->dev, "otg");
+ hsotg->clk = devm_clk_get_optional(hsotg->dev, "otg");
if (IS_ERR(hsotg->clk)) {
- hsotg->clk = NULL;
- dev_dbg(hsotg->dev, "cannot get otg clock\n");
+ dev_err(hsotg->dev, "cannot get otg clock\n");
+ return PTR_ERR(hsotg->clk);
}
/* Regulators */
config USB_DWC3_OMAP
tristate "Texas Instruments OMAP5 and similar Platforms"
- depends on EXTCON && (ARCH_OMAP2PLUS || COMPILE_TEST)
+ depends on ARCH_OMAP2PLUS || COMPILE_TEST
+ depends on EXTCON || !EXTCON
depends on OF
default USB_DWC3
help
config USB_DWC3_QCOM
tristate "Qualcomm Platform"
- depends on EXTCON && (ARCH_QCOM || COMPILE_TEST)
+ depends on ARCH_QCOM || COMPILE_TEST
+ depends on EXTCON || !EXTCON
depends on OF
default USB_DWC3
help
static int ast_vhub_epn_enable(struct usb_ep* u_ep,
const struct usb_endpoint_descriptor *desc)
{
- static const char *ep_type_string[] __maybe_unused = { "ctrl",
- "isoc",
- "bulk",
- "intr" };
struct ast_vhub_ep *ep = to_ast_ep(u_ep);
struct ast_vhub_dev *dev;
struct ast_vhub *vhub;
ep->epn.wedged = false;
EPDBG(ep, "Enabling [%s] %s num %d maxpacket=%d\n",
- ep->epn.is_in ? "in" : "out", ep_type_string[type],
+ ep->epn.is_in ? "in" : "out", usb_ep_type_string(type),
usb_endpoint_num(desc), maxpacket);
/* Can we use DMA descriptor mode ? */
_ep->name,
desc->bEndpointAddress & 0x0f,
(desc->bEndpointAddress & USB_DIR_IN) ? "in" : "out",
- ({ char *val;
- switch (usb_endpoint_type(desc)) {
- case USB_ENDPOINT_XFER_BULK:
- val = "bulk";
- break;
- case USB_ENDPOINT_XFER_ISOC:
- val = "iso";
- break;
- case USB_ENDPOINT_XFER_INT:
- val = "intr";
- break;
- default:
- val = "ctrl";
- break;
- } val; }),
+ usb_ep_type_string(usb_endpoint_type(desc)),
max, ep->stream_en ? "enabled" : "disabled");
/* at this point real hardware should be NAKing transfers
}
ed->speed = (urb->dev->speed == USB_SPEED_LOW) ?
FHCI_LOW_SPEED : FHCI_FULL_SPEED;
- ed->max_pkt_size = usb_maxpacket(urb->dev,
- urb->pipe, usb_pipeout(urb->pipe));
+ ed->max_pkt_size = usb_endpoint_maxp(&urb->ep->desc);
urb->ep->hcpriv = ed;
fhci_dbg(fhci, "new ep speed=%d max_pkt_size=%d\n",
ed->speed, ed->max_pkt_size);
if (urb->transfer_flags & URB_ZERO_PACKET &&
urb->transfer_buffer_length > 0 &&
((urb->transfer_buffer_length %
- usb_maxpacket(urb->dev, urb->pipe,
- usb_pipeout(urb->pipe))) == 0))
+ usb_endpoint_maxp(&urb->ep->desc)) == 0))
urb_state = US_BULK0;
while (data_len > 4096) {
td = fhci_td_fill(fhci, urb, urb_priv, ed, cnt,
break;
case FHCI_TF_CTRL:
ed->dev_addr = usb_pipedevice(urb->pipe);
- ed->max_pkt_size = usb_maxpacket(urb->dev, urb->pipe,
- usb_pipeout(urb->pipe));
+ ed->max_pkt_size = usb_endpoint_maxp(&urb->ep->desc);
+
/* setup stage */
td = fhci_td_fill(fhci, urb, urb_priv, ed, cnt++, FHCI_TA_SETUP,
USB_TD_TOGGLE_DATA0, urb->setup_packet, 8, 0, 0, true);
return -ENODEV;
printk(KERN_INFO "driver %s\n", hcd_name);
workqueue = create_singlethread_workqueue("u132");
+ if (!workqueue)
+ return -ENOMEM;
retval = platform_driver_register(&u132_platform_driver);
if (retval)
destroy_workqueue(workqueue);
/* Write 1 to disable the port */
writel(port_status | PORT_PE, addr);
port_status = readl(addr);
- xhci_dbg(xhci, "disable port, actual port %d status = 0x%x\n",
- wIndex, port_status);
+ xhci_dbg(xhci, "disable port %d-%d, portsc: 0x%x\n",
+ hcd->self.busnum, wIndex + 1, port_status);
}
static void xhci_clear_port_change_bit(struct xhci_hcd *xhci, u16 wValue,
/* Change bits are all write 1 to clear */
writel(port_status | status, addr);
port_status = readl(addr);
- xhci_dbg(xhci, "clear port %s change, actual port %d status = 0x%x\n",
- port_change_bit, wIndex, port_status);
+
+ xhci_dbg(xhci, "clear port%d %s change, portsc: 0x%x\n",
+ wIndex + 1, port_change_bit, port_status);
}
struct xhci_hub *xhci_get_rhub(struct usb_hcd *hcd)
rhub = xhci_get_rhub(hcd);
port = rhub->ports[index];
temp = readl(port->addr);
+
+ xhci_dbg(xhci, "set port power %d-%d %s, portsc: 0x%x\n",
+ hcd->self.busnum, index + 1, on ? "ON" : "OFF", temp);
+
temp = xhci_port_state_to_neutral(temp);
+
if (on) {
/* Power on */
writel(temp | PORT_POWER, port->addr);
- temp = readl(port->addr);
- xhci_dbg(xhci, "set port power, actual port %d status = 0x%x\n",
- index, temp);
+ readl(port->addr);
} else {
/* Power off */
writel(temp & ~PORT_POWER, port->addr);
u32 link_state)
{
u32 temp;
+ u32 portsc;
- temp = readl(port->addr);
- temp = xhci_port_state_to_neutral(temp);
+ portsc = readl(port->addr);
+ temp = xhci_port_state_to_neutral(portsc);
temp &= ~PORT_PLS_MASK;
temp |= PORT_LINK_STROBE | link_state;
writel(temp, port->addr);
+
+ xhci_dbg(xhci, "Set port %d-%d link state, portsc: 0x%x, write 0x%x",
+ port->rhub->hcd->self.busnum, port->hcd_portnum + 1,
+ portsc, temp);
}
static void xhci_set_remote_wake_mask(struct xhci_hcd *xhci,
} else if (time_after_eq(jiffies, bus_state->resume_done[wIndex])) {
int time_left;
- xhci_dbg(xhci, "Resume USB2 port %d\n", wIndex + 1);
+ xhci_dbg(xhci, "resume USB2 port %d-%d\n",
+ hcd->self.busnum, wIndex + 1);
+
bus_state->resume_done[wIndex] = 0;
clear_bit(wIndex, &bus_state->resuming_ports);
} else {
int port_status = readl(port->addr);
- xhci_warn(xhci, "Port resume %i msec timed out, portsc = 0x%x\n",
- XHCI_MAX_REXIT_TIMEOUT_MS,
- port_status);
+ xhci_warn(xhci, "Port resume timed out, port %d-%d: 0x%x\n",
+ hcd->self.busnum, wIndex + 1, port_status);
*status |= USB_PORT_STAT_SUSPEND;
clear_bit(wIndex, &bus_state->rexit_ports);
}
if (status == 0xffffffff)
goto error;
- xhci_dbg(xhci, "get port status, actual port %d status = 0x%x\n",
- wIndex, temp);
- xhci_dbg(xhci, "Get port status returned 0x%x\n", status);
+ xhci_dbg(xhci, "Get port status %d-%d read: 0x%x, return 0x%x",
+ hcd->self.busnum, wIndex + 1, temp, status);
put_unaligned(cpu_to_le32(status), (__le32 *) buf);
/* if USB 3.1 extended port status return additional 4 bytes */
temp = readl(ports[wIndex]->addr);
if ((temp & PORT_PE) == 0 || (temp & PORT_RESET)
|| (temp & PORT_PLS_MASK) >= XDEV_U3) {
- xhci_warn(xhci, "USB core suspending device not in U0/U1/U2.\n");
+ xhci_warn(xhci, "USB core suspending port %d-%d not in U0/U1/U2\n",
+ hcd->self.busnum, wIndex + 1);
goto error;
}
return xhci_mtk_host_enable(mtk);
}
-/* ignore the error if the clock does not exist */
-static struct clk *optional_clk_get(struct device *dev, const char *id)
-{
- struct clk *opt_clk;
-
- opt_clk = devm_clk_get(dev, id);
- /* ignore error number except EPROBE_DEFER */
- if (IS_ERR(opt_clk) && (PTR_ERR(opt_clk) != -EPROBE_DEFER))
- opt_clk = NULL;
-
- return opt_clk;
-}
-
static int xhci_mtk_clks_get(struct xhci_hcd_mtk *mtk)
{
struct device *dev = mtk->dev;
return PTR_ERR(mtk->sys_clk);
}
- mtk->ref_clk = optional_clk_get(dev, "ref_ck");
+ mtk->ref_clk = devm_clk_get_optional(dev, "ref_ck");
if (IS_ERR(mtk->ref_clk))
return PTR_ERR(mtk->ref_clk);
- mtk->mcu_clk = optional_clk_get(dev, "mcu_ck");
+ mtk->mcu_clk = devm_clk_get_optional(dev, "mcu_ck");
if (IS_ERR(mtk->mcu_clk))
return PTR_ERR(mtk->mcu_clk);
- mtk->dma_clk = optional_clk_get(dev, "dma_ck");
+ mtk->dma_clk = devm_clk_get_optional(dev, "dma_ck");
return PTR_ERR_OR_ZERO(mtk->dma_clk);
}
struct xhci_hcd *xhci;
struct resource *res;
struct usb_hcd *hcd;
- struct clk *clk;
- struct clk *reg_clk;
int ret;
int irq;
hcd->rsrc_start = res->start;
hcd->rsrc_len = resource_size(res);
+ xhci = hcd_to_xhci(hcd);
+
/*
* Not all platforms have clks so it is not an error if the
* clock do not exist.
*/
- reg_clk = devm_clk_get(&pdev->dev, "reg");
- if (!IS_ERR(reg_clk)) {
- ret = clk_prepare_enable(reg_clk);
- if (ret)
- goto put_hcd;
- } else if (PTR_ERR(reg_clk) == -EPROBE_DEFER) {
- ret = -EPROBE_DEFER;
+ xhci->reg_clk = devm_clk_get_optional(&pdev->dev, "reg");
+ if (IS_ERR(xhci->reg_clk)) {
+ ret = PTR_ERR(xhci->reg_clk);
goto put_hcd;
}
- clk = devm_clk_get(&pdev->dev, NULL);
- if (!IS_ERR(clk)) {
- ret = clk_prepare_enable(clk);
- if (ret)
- goto disable_reg_clk;
- } else if (PTR_ERR(clk) == -EPROBE_DEFER) {
- ret = -EPROBE_DEFER;
+ ret = clk_prepare_enable(xhci->reg_clk);
+ if (ret)
+ goto put_hcd;
+
+ xhci->clk = devm_clk_get_optional(&pdev->dev, NULL);
+ if (IS_ERR(xhci->clk)) {
+ ret = PTR_ERR(xhci->clk);
goto disable_reg_clk;
}
- xhci = hcd_to_xhci(hcd);
+ ret = clk_prepare_enable(xhci->clk);
+ if (ret)
+ goto disable_reg_clk;
+
priv_match = of_device_get_match_data(&pdev->dev);
if (priv_match) {
struct xhci_plat_priv *priv = hcd_to_xhci_priv(hcd);
device_wakeup_enable(hcd->self.controller);
- xhci->clk = clk;
- xhci->reg_clk = reg_clk;
xhci->main_hcd = hcd;
xhci->shared_hcd = __usb_create_hcd(driver, sysdev, &pdev->dev,
dev_name(&pdev->dev), hcd);
usb_put_hcd(xhci->shared_hcd);
disable_clk:
- clk_disable_unprepare(clk);
+ clk_disable_unprepare(xhci->clk);
disable_reg_clk:
- clk_disable_unprepare(reg_clk);
+ clk_disable_unprepare(xhci->reg_clk);
put_hcd:
usb_put_hcd(hcd);
"WARN: xHC returned failed port status event\n");
port_id = GET_PORT_ID(le32_to_cpu(event->generic.field[0]));
- xhci_dbg(xhci, "Port Status Change Event for port %d\n", port_id);
-
max_ports = HCS_MAX_PORTS(xhci->hcs_params1);
+
if ((port_id <= 0) || (port_id > max_ports)) {
- xhci_warn(xhci, "Invalid port id %d\n", port_id);
+ xhci_warn(xhci, "Port change event with invalid port ID %d\n",
+ port_id);
inc_deq(xhci, xhci->event_ring);
return;
}
port = &xhci->hw_ports[port_id - 1];
if (!port || !port->rhub || port->hcd_portnum == DUPLICATE_ENTRY) {
- xhci_warn(xhci, "Event for invalid port %u\n", port_id);
+ xhci_warn(xhci, "Port change event, no port for port ID %u\n",
+ port_id);
bogus_port_status = true;
goto cleanup;
}
hcd_portnum = port->hcd_portnum;
portsc = readl(port->addr);
+ xhci_dbg(xhci, "Port change event, %d-%d, id %d, portsc: 0x%x\n",
+ hcd->self.busnum, hcd_portnum + 1, port_id, portsc);
+
trace_xhci_handle_port_status(hcd_portnum, portsc);
if (hcd->state == HC_STATE_SUSPENDED) {
field |= TRB_IOC;
more_trbs_coming = false;
td->last_trb = ring->enqueue;
+
+ if (xhci_urb_suitable_for_idt(urb)) {
+ memcpy(&send_addr, urb->transfer_buffer,
+ trb_buff_len);
+ field |= TRB_IDT;
+ }
}
/* Only set interrupt on short packet for IN endpoints */
if (urb->transfer_buffer_length > 0) {
u32 length_field, remainder;
+ if (xhci_urb_suitable_for_idt(urb)) {
+ memcpy(&urb->transfer_dma, urb->transfer_buffer,
+ urb->transfer_buffer_length);
+ field |= TRB_IDT;
+ }
+
remainder = xhci_td_remainder(xhci, 0,
urb->transfer_buffer_length,
urb->transfer_buffer_length,
} ports;
bool scale_ss_clock;
+ bool has_ipfs;
};
struct tegra_xusb {
return IRQ_HANDLED;
}
-static void tegra_xusb_ipfs_config(struct tegra_xusb *tegra,
- struct resource *regs)
+static void tegra_xusb_config(struct tegra_xusb *tegra,
+ struct resource *regs)
{
u32 value;
- value = ipfs_readl(tegra, IPFS_XUSB_HOST_CONFIGURATION_0);
- value |= IPFS_EN_FPCI;
- ipfs_writel(tegra, value, IPFS_XUSB_HOST_CONFIGURATION_0);
+ if (tegra->soc->has_ipfs) {
+ value = ipfs_readl(tegra, IPFS_XUSB_HOST_CONFIGURATION_0);
+ value |= IPFS_EN_FPCI;
+ ipfs_writel(tegra, value, IPFS_XUSB_HOST_CONFIGURATION_0);
- usleep_range(10, 20);
+ usleep_range(10, 20);
+ }
/* Program BAR0 space */
value = fpci_readl(tegra, XUSB_CFG_4);
value |= XUSB_IO_SPACE_EN | XUSB_MEM_SPACE_EN | XUSB_BUS_MASTER_EN;
fpci_writel(tegra, value, XUSB_CFG_1);
- /* Enable interrupt assertion */
- value = ipfs_readl(tegra, IPFS_XUSB_HOST_INTR_MASK_0);
- value |= IPFS_IP_INT_MASK;
- ipfs_writel(tegra, value, IPFS_XUSB_HOST_INTR_MASK_0);
+ if (tegra->soc->has_ipfs) {
+ /* Enable interrupt assertion */
+ value = ipfs_readl(tegra, IPFS_XUSB_HOST_INTR_MASK_0);
+ value |= IPFS_IP_INT_MASK;
+ ipfs_writel(tegra, value, IPFS_XUSB_HOST_INTR_MASK_0);
- /* Set hysteresis */
- ipfs_writel(tegra, 0x80, IPFS_XUSB_HOST_CLKGATE_HYSTERESIS_0);
+ /* Set hysteresis */
+ ipfs_writel(tegra, 0x80, IPFS_XUSB_HOST_CLKGATE_HYSTERESIS_0);
+ }
}
static int tegra_xusb_clk_enable(struct tegra_xusb *tegra)
if (IS_ERR(tegra->fpci_base))
return PTR_ERR(tegra->fpci_base);
- res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
- tegra->ipfs_base = devm_ioremap_resource(&pdev->dev, res);
- if (IS_ERR(tegra->ipfs_base))
- return PTR_ERR(tegra->ipfs_base);
+ if (tegra->soc->has_ipfs) {
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
+ tegra->ipfs_base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(tegra->ipfs_base))
+ return PTR_ERR(tegra->ipfs_base);
+ }
tegra->xhci_irq = platform_get_irq(pdev, 0);
if (tegra->xhci_irq < 0)
goto disable_rpm;
}
- tegra_xusb_ipfs_config(tegra, regs);
+ tegra_xusb_config(tegra, regs);
err = tegra_xusb_load_firmware(tegra);
if (err < 0) {
.usb3 = { .offset = 0, .count = 2, },
},
.scale_ss_clock = true,
+ .has_ipfs = true,
};
MODULE_FIRMWARE("nvidia/tegra124/xusb.bin");
.usb3 = { .offset = 0, .count = 4, },
},
.scale_ss_clock = false,
+ .has_ipfs = true,
};
MODULE_FIRMWARE("nvidia/tegra210/xusb.bin");
+static const char * const tegra186_supply_names[] = {
+};
+
+static const struct tegra_xusb_phy_type tegra186_phy_types[] = {
+ { .name = "usb3", .num = 3, },
+ { .name = "usb2", .num = 3, },
+ { .name = "hsic", .num = 1, },
+};
+
+static const struct tegra_xusb_soc tegra186_soc = {
+ .firmware = "nvidia/tegra186/xusb.bin",
+ .supply_names = tegra186_supply_names,
+ .num_supplies = ARRAY_SIZE(tegra186_supply_names),
+ .phy_types = tegra186_phy_types,
+ .num_types = ARRAY_SIZE(tegra186_phy_types),
+ .ports = {
+ .usb3 = { .offset = 0, .count = 3, },
+ .usb2 = { .offset = 3, .count = 3, },
+ .hsic = { .offset = 6, .count = 1, },
+ },
+ .scale_ss_clock = false,
+ .has_ipfs = false,
+};
+
static const struct of_device_id tegra_xusb_of_match[] = {
{ .compatible = "nvidia,tegra124-xusb", .data = &tegra124_soc },
{ .compatible = "nvidia,tegra210-xusb", .data = &tegra210_soc },
+ { .compatible = "nvidia,tegra186-xusb", .data = &tegra186_soc },
{ },
};
MODULE_DEVICE_TABLE(of, tegra_xusb_of_match);
TP_ARGS(ctx)
);
+DEFINE_EVENT(xhci_log_ep_ctx, xhci_add_endpoint,
+ TP_PROTO(struct xhci_ep_ctx *ctx),
+ TP_ARGS(ctx)
+);
+
DECLARE_EVENT_CLASS(xhci_log_slot_ctx,
TP_PROTO(struct xhci_slot_ctx *ctx),
TP_ARGS(ctx),
TP_ARGS(ctx)
);
+DECLARE_EVENT_CLASS(xhci_log_ctrl_ctx,
+ TP_PROTO(struct xhci_input_control_ctx *ctrl_ctx),
+ TP_ARGS(ctrl_ctx),
+ TP_STRUCT__entry(
+ __field(u32, drop)
+ __field(u32, add)
+ ),
+ TP_fast_assign(
+ __entry->drop = le32_to_cpu(ctrl_ctx->drop_flags);
+ __entry->add = le32_to_cpu(ctrl_ctx->add_flags);
+ ),
+ TP_printk("%s", xhci_decode_ctrl_ctx(__entry->drop, __entry->add)
+ )
+);
+
+DEFINE_EVENT(xhci_log_ctrl_ctx, xhci_address_ctrl_ctx,
+ TP_PROTO(struct xhci_input_control_ctx *ctrl_ctx),
+ TP_ARGS(ctrl_ctx)
+);
+
+DEFINE_EVENT(xhci_log_ctrl_ctx, xhci_configure_endpoint_ctrl_ctx,
+ TP_PROTO(struct xhci_input_control_ctx *ctrl_ctx),
+ TP_ARGS(ctrl_ctx)
+);
+
DECLARE_EVENT_CLASS(xhci_log_ring,
TP_PROTO(struct xhci_ring *ring),
TP_ARGS(ring),
struct xhci_port **ports;
int port_index;
unsigned long flags;
- u32 t1, t2;
+ u32 t1, t2, portsc;
spin_lock_irqsave(&xhci->lock, flags);
ports = xhci->usb3_rhub.ports;
while (port_index--) {
t1 = readl(ports[port_index]->addr);
+ portsc = t1;
t1 = xhci_port_state_to_neutral(t1);
t2 = t1 & ~PORT_WAKE_BITS;
- if (t1 != t2)
+ if (t1 != t2) {
writel(t2, ports[port_index]->addr);
+ xhci_dbg(xhci, "disable wake bits port %d-%d, portsc: 0x%x, write: 0x%x\n",
+ xhci->usb3_rhub.hcd->self.busnum,
+ port_index + 1, portsc, t2);
+ }
}
/* disable usb2 ports Wake bits */
ports = xhci->usb2_rhub.ports;
while (port_index--) {
t1 = readl(ports[port_index]->addr);
+ portsc = t1;
t1 = xhci_port_state_to_neutral(t1);
t2 = t1 & ~PORT_WAKE_BITS;
- if (t1 != t2)
+ if (t1 != t2) {
writel(t2, ports[port_index]->addr);
+ xhci_dbg(xhci, "disable wake bits port %d-%d, portsc: 0x%x, write: 0x%x\n",
+ xhci->usb2_rhub.hcd->self.busnum,
+ port_index + 1, portsc, t2);
+ }
}
-
spin_unlock_irqrestore(&xhci->lock, flags);
}
/*-------------------------------------------------------------------------*/
+/*
+ * Bypass the DMA mapping if URB is suitable for Immediate Transfer (IDT),
+ * we'll copy the actual data into the TRB address register. This is limited to
+ * transfers up to 8 bytes on output endpoints of any kind with wMaxPacketSize
+ * >= 8 bytes. If suitable for IDT only one Transfer TRB per TD is allowed.
+ */
+static int xhci_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb,
+ gfp_t mem_flags)
+{
+ if (xhci_urb_suitable_for_idt(urb))
+ return 0;
+
+ return usb_hcd_map_urb_for_dma(hcd, urb, mem_flags);
+}
+
/**
* xhci_get_endpoint_index - Used for passing endpoint bitmasks between the core and
* HCDs. Find the index for an endpoint given its descriptor. Use the return
struct xhci_container_ctx *in_ctx;
unsigned int ep_index;
struct xhci_input_control_ctx *ctrl_ctx;
+ struct xhci_ep_ctx *ep_ctx;
u32 added_ctxs;
u32 new_add_flags, new_drop_flags;
struct xhci_virt_device *virt_dev;
/* Store the usb_device pointer for later use */
ep->hcpriv = udev;
+ ep_ctx = xhci_get_ep_ctx(xhci, virt_dev->in_ctx, ep_index);
+ trace_xhci_add_endpoint(ep_ctx);
+
xhci_debugfs_create_endpoint(xhci, virt_dev, ep_index);
xhci_dbg(xhci, "add ep 0x%x, slot id %d, new drop flags = %#x, new add flags = %#x\n",
}
slot_ctx = xhci_get_slot_ctx(xhci, command->in_ctx);
+
+ trace_xhci_configure_endpoint_ctrl_ctx(ctrl_ctx);
trace_xhci_configure_endpoint(slot_ctx);
if (!ctx_change)
trace_xhci_address_ctx(xhci, virt_dev->in_ctx,
le32_to_cpu(slot_ctx->dev_info) >> 27);
+ trace_xhci_address_ctrl_ctx(ctrl_ctx);
spin_lock_irqsave(&xhci->lock, flags);
trace_xhci_setup_device(virt_dev);
ret = xhci_queue_address_device(xhci, command, virt_dev->in_ctx->dma,
/*
* managing i/o requests and associated device resources
*/
+ .map_urb_for_dma = xhci_map_urb_for_dma,
.urb_enqueue = xhci_urb_enqueue,
.urb_dequeue = xhci_urb_dequeue,
.alloc_dev = xhci_alloc_dev,
#define TRB_IOC (1<<5)
/* The buffer pointer contains immediate data */
#define TRB_IDT (1<<6)
+/* TDs smaller than this might use IDT */
+#define TRB_IDT_MAX_SIZE 8
/* Block Event Interrupt */
#define TRB_BEI (1<<9)
urb->stream_id);
}
+/*
+ * TODO: As per spec Isochronous IDT transmissions are supported. We bypass
+ * them anyways as we where unable to find a device that matches the
+ * constraints.
+ */
+static inline bool xhci_urb_suitable_for_idt(struct urb *urb)
+{
+ if (!usb_endpoint_xfer_isoc(&urb->ep->desc) && usb_urb_dir_out(urb) &&
+ usb_endpoint_maxp(&urb->ep->desc) >= TRB_IDT_MAX_SIZE &&
+ urb->transfer_buffer_length <= TRB_IDT_MAX_SIZE)
+ return true;
+
+ return false;
+}
+
static inline char *xhci_slot_state_string(u32 state)
{
switch (state) {
return str;
}
+static inline const char *xhci_decode_ctrl_ctx(unsigned long drop,
+ unsigned long add)
+{
+ static char str[1024];
+ unsigned int bit;
+ int ret = 0;
+
+ if (drop) {
+ ret = sprintf(str, "Drop:");
+ for_each_set_bit(bit, &drop, 32)
+ ret += sprintf(str + ret, " %d%s",
+ bit / 2,
+ bit % 2 ? "in":"out");
+ ret += sprintf(str + ret, ", ");
+ }
+
+ if (add) {
+ ret += sprintf(str + ret, "Add:%s%s",
+ (add & SLOT_FLAG) ? " slot":"",
+ (add & EP0_FLAG) ? " ep0":"");
+ add &= ~(SLOT_FLAG | EP0_FLAG);
+ for_each_set_bit(bit, &add, 32)
+ ret += sprintf(str + ret, " %d%s",
+ bit / 2,
+ bit % 2 ? "in":"out");
+ }
+ return str;
+}
+
static inline const char *xhci_decode_slot_context(u32 info, u32 info2,
u32 tt_info, u32 state)
{
mem_reads8(hcd->regs, qtd->payload_addr,
qtd->data_buffer,
qtd->actual_length);
- /* Fall through (?) */
+ /* Fall through */
case OUT_PID:
qtd->urb->actual_length +=
qtd->actual_length;
- /* Fall through ... */
+ /* Fall through */
case SETUP_PID:
break;
}
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
+#include <linux/gpio/driver.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/nls.h>
.product_str = "USB2517i",
};
+#ifdef CONFIG_GPIOLIB
+static int usb251xb_check_dev_children(struct device *dev, void *child)
+{
+ if (dev->type == &i2c_adapter_type) {
+ return device_for_each_child(dev, child,
+ usb251xb_check_dev_children);
+ }
+
+ return (dev == child);
+}
+
+static int usb251x_check_gpio_chip(struct usb251xb *hub)
+{
+ struct gpio_chip *gc = gpiod_to_chip(hub->gpio_reset);
+ struct i2c_adapter *adap = hub->i2c->adapter;
+ int ret;
+
+ if (!hub->gpio_reset)
+ return 0;
+
+ if (!gc)
+ return -EINVAL;
+
+ ret = usb251xb_check_dev_children(&adap->dev, gc->parent);
+ if (ret) {
+ dev_err(hub->dev, "Reset GPIO chip is at the same i2c-bus\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+#else
+static int usb251x_check_gpio_chip(struct usb251xb *hub)
+{
+ return 0;
+}
+#endif
+
static void usb251xb_reset(struct usb251xb *hub, int state)
{
if (!hub->gpio_reset)
return;
+ i2c_lock_bus(hub->i2c->adapter, I2C_LOCK_SEGMENT);
+
gpiod_set_value_cansleep(hub->gpio_reset, state);
/* wait for hub recovery/stabilization */
usleep_range(500, 750); /* >=500us at power on */
else
usleep_range(1, 10); /* >=1us at power down */
+
+ i2c_unlock_bus(hub->i2c->adapter, I2C_LOCK_SEGMENT);
}
static int usb251xb_connect(struct usb251xb *hub)
}
#ifdef CONFIG_OF
+static void usb251xb_get_ports_field(struct usb251xb *hub,
+ const char *prop_name, u8 port_cnt, u8 *fld)
+{
+ struct device *dev = hub->dev;
+ struct property *prop;
+ const __be32 *p;
+ u32 port;
+
+ of_property_for_each_u32(dev->of_node, prop_name, prop, p, port) {
+ if ((port >= 1) && (port <= port_cnt))
+ *fld |= BIT(port);
+ else
+ dev_warn(dev, "port %u doesn't exist\n", port);
+ }
+}
+
static int usb251xb_get_ofdata(struct usb251xb *hub,
struct usb251xb_data *data)
{
struct device *dev = hub->dev;
struct device_node *np = dev->of_node;
- int len, err, i;
- u32 port, property_u32 = 0;
- const u32 *cproperty_u32;
+ int len, err;
+ u32 property_u32 = 0;
const char *cproperty_char;
char str[USB251XB_STRING_BUFSIZE / 2];
- struct property *prop;
- const __be32 *p;
if (!np) {
dev_err(dev, "failed to get ofdata\n");
hub->conf_data3 |= BIT(0);
hub->non_rem_dev = USB251XB_DEF_NON_REMOVABLE_DEVICES;
- cproperty_u32 = of_get_property(np, "non-removable-ports", &len);
- if (cproperty_u32 && (len / sizeof(u32)) > 0) {
- for (i = 0; i < len / sizeof(u32); i++) {
- u32 port = be32_to_cpu(cproperty_u32[i]);
-
- if ((port >= 1) && (port <= data->port_cnt))
- hub->non_rem_dev |= BIT(port);
- else
- dev_warn(dev, "NRD port %u doesn't exist\n",
- port);
- }
- }
+ usb251xb_get_ports_field(hub, "non-removable-ports", data->port_cnt,
+ &hub->non_rem_dev);
hub->port_disable_sp = USB251XB_DEF_PORT_DISABLE_SELF;
- cproperty_u32 = of_get_property(np, "sp-disabled-ports", &len);
- if (cproperty_u32 && (len / sizeof(u32)) > 0) {
- for (i = 0; i < len / sizeof(u32); i++) {
- u32 port = be32_to_cpu(cproperty_u32[i]);
-
- if ((port >= 1) && (port <= data->port_cnt))
- hub->port_disable_sp |= BIT(port);
- else
- dev_warn(dev, "PDS port %u doesn't exist\n",
- port);
- }
- }
+ usb251xb_get_ports_field(hub, "sp-disabled-ports", data->port_cnt,
+ &hub->port_disable_sp);
hub->port_disable_bp = USB251XB_DEF_PORT_DISABLE_BUS;
- cproperty_u32 = of_get_property(np, "bp-disabled-ports", &len);
- if (cproperty_u32 && (len / sizeof(u32)) > 0) {
- for (i = 0; i < len / sizeof(u32); i++) {
- u32 port = be32_to_cpu(cproperty_u32[i]);
-
- if ((port >= 1) && (port <= data->port_cnt))
- hub->port_disable_bp |= BIT(port);
- else
- dev_warn(dev, "PDB port %u doesn't exist\n",
- port);
- }
- }
+ usb251xb_get_ports_field(hub, "bp-disabled-ports", data->port_cnt,
+ &hub->port_disable_bp);
hub->max_power_sp = USB251XB_DEF_MAX_POWER_SELF;
if (!of_property_read_u32(np, "sp-max-total-current-microamp",
* register controls the USB DP/DM signal swapping for each port.
*/
hub->port_swap = USB251XB_DEF_PORT_SWAP;
- of_property_for_each_u32(np, "swap-dx-lanes", prop, p, port) {
- if (port <= data->port_cnt)
- hub->port_swap |= BIT(port);
- }
+ usb251xb_get_ports_field(hub, "swap-dx-lanes", data->port_cnt,
+ &hub->port_swap);
+ if (of_get_property(np, "swap-us-lanes", NULL))
+ hub->port_swap |= BIT(0);
/* The following parameters are currently not exposed to devicetree, but
* may be as soon as needed.
}
}
+ /*
+ * usb251x SMBus-slave SCL lane is muxed with CFG_SEL0 pin. So if anyone
+ * tries to work with the bus at the moment the hub reset is released,
+ * it may cause an invalid config being latched by usb251x. Particularly
+ * one of the config modes makes the hub loading a default registers
+ * value without SMBus-slave interface activation. If the hub
+ * accidentally gets this mode, this will cause the driver SMBus-
+ * functions failure. Normally we could just lock the SMBus-segment the
+ * hub i2c-interface resides for the device-specific reset timing. But
+ * the GPIO controller, which is used to handle the hub reset, might be
+ * placed at the same i2c-bus segment. In this case an error should be
+ * returned since we can't safely use the GPIO controller to clear the
+ * reset state (it may affect the hub configuration) and we can't lock
+ * the i2c-bus segment (it will cause a deadlock).
+ */
+ err = usb251x_check_gpio_chip(hub);
+ if (err)
+ return err;
+
err = usb251xb_connect(hub);
if (err) {
dev_err(dev, "Failed to connect hub (%d)\n", err);
hub->gpio_reset = pdata->gpio_reset;
hub->mode = pdata->initial_mode;
} else if (np) {
- struct clk *clk;
u32 rate = 0;
hub->port_off_mask = 0;
}
}
- clk = devm_clk_get(dev, "refclk");
- if (IS_ERR(clk) && PTR_ERR(clk) != -ENOENT) {
+ hub->clk = devm_clk_get_optional(dev, "refclk");
+ if (IS_ERR(hub->clk)) {
dev_err(dev, "unable to request refclk (%ld)\n",
- PTR_ERR(clk));
- return PTR_ERR(clk);
+ PTR_ERR(hub->clk));
+ return PTR_ERR(hub->clk);
}
- if (!IS_ERR(clk)) {
- hub->clk = clk;
-
- if (rate != 0) {
- err = clk_set_rate(hub->clk, rate);
- if (err) {
- dev_err(dev,
- "unable to set reference clock rate to %d\n",
- (int) rate);
- return err;
- }
- }
-
- err = clk_prepare_enable(hub->clk);
+ if (rate != 0) {
+ err = clk_set_rate(hub->clk, rate);
if (err) {
dev_err(dev,
- "unable to enable reference clock\n");
+ "unable to set reference clock rate to %d\n",
+ (int)rate);
return err;
}
}
+ err = clk_prepare_enable(hub->clk);
+ if (err) {
+ dev_err(dev, "unable to enable reference clock\n");
+ return err;
+ }
+
property = of_get_property(np, "disabled-ports", &len);
if (property && (len / sizeof(u32)) > 0) {
int i;
struct usb3503 *hub;
hub = i2c_get_clientdata(i2c);
- if (hub->clk)
- clk_disable_unprepare(hub->clk);
+ clk_disable_unprepare(hub->clk);
return 0;
}
struct usb3503 *hub;
hub = platform_get_drvdata(pdev);
- if (hub->clk)
- clk_disable_unprepare(hub->clk);
+ clk_disable_unprepare(hub->clk);
return 0;
}
static int usb3503_suspend(struct usb3503 *hub)
{
usb3503_switch_mode(hub, USB3503_MODE_STANDBY);
-
- if (hub->clk)
- clk_disable_unprepare(hub->clk);
+ clk_disable_unprepare(hub->clk);
return 0;
}
static int usb3503_resume(struct usb3503 *hub)
{
- if (hub->clk)
- clk_prepare_enable(hub->clk);
-
+ clk_prepare_enable(hub->clk);
usb3503_switch_mode(hub, hub->mode);
return 0;
ccflags-$(CONFIG_USB_MTU3_DEBUG) += -DDEBUG
+# define_trace.h needs to know how to find our header
+CFLAGS_mtu3_trace.o := -I$(src)
+
obj-$(CONFIG_USB_MTU3) += mtu3.o
mtu3-y := mtu3_plat.o
+ifneq ($(CONFIG_TRACING),)
+ mtu3-y += mtu3_trace.o
+endif
+
ifneq ($(filter y,$(CONFIG_USB_MTU3_HOST) $(CONFIG_USB_MTU3_DUAL_ROLE)),)
mtu3-y += mtu3_host.o
endif
ifneq ($(CONFIG_USB_MTU3_DUAL_ROLE),)
mtu3-y += mtu3_dr.o
endif
+
+ifneq ($(CONFIG_DEBUG_FS),)
+ mtu3-y += mtu3_debugfs.o
+endif
#define MTU3_U3_IP_SLOT_DEFAULT 2
#define MTU3_U2_IP_SLOT_DEFAULT 1
+/**
+ * IP TRUNK version
+ * from 0x1003 version, USB3 Gen2 is supported, two changes affect driver:
+ * 1. MAXPKT and MULTI bits layout of TXCSR1 and RXCSR1 are adjusted,
+ * but not backward compatible
+ * 2. QMU extend buffer length supported
+ */
+#define MTU3_TRUNK_VERS_1003 0x1003
+
/**
* Normally the device works on HS or SS, to simplify fifo management,
* devide fifo into some 512B parts, use bitmap to manage it; And
* The format of TX GPD is a little different from RX one.
* And the size of GPD is 16 bytes.
*
- * @flag:
+ * @dw0_info:
* bit0: Hardware Own (HWO)
* bit1: Buffer Descriptor Present (BDP), always 0, BD is not supported
* bit2: Bypass (BPS), 1: HW skips this GPD if HWO = 1
+ * bit6: [EL] Zero Length Packet (ZLP), moved from @dw3_info[29]
* bit7: Interrupt On Completion (IOC)
- * @chksum: This is used to validate the contents of this GPD;
- * If TXQ_CS_EN / RXQ_CS_EN bit is set, an interrupt is issued
- * when checksum validation fails;
- * Checksum value is calculated over the 16 bytes of the GPD by default;
- * @data_buf_len (RX ONLY): This value indicates the length of
- * the assigned data buffer
- * @tx_ext_addr (TX ONLY): [3:0] are 4 extension bits of @buffer,
- * [7:4] are 4 extension bits of @next_gpd
+ * bit[31:16]: ([EL] bit[31:12]) allow data buffer length (RX ONLY),
+ * the buffer length of the data to receive
+ * bit[23:16]: ([EL] bit[31:24]) extension address (TX ONLY),
+ * lower 4 bits are extension bits of @buffer,
+ * upper 4 bits are extension bits of @next_gpd
* @next_gpd: Physical address of the next GPD
* @buffer: Physical address of the data buffer
- * @buf_len:
- * (TX): This value indicates the length of the assigned data buffer
- * (RX): The total length of data received
- * @ext_len: reserved
- * @rx_ext_addr(RX ONLY): [3:0] are 4 extension bits of @buffer,
- * [7:4] are 4 extension bits of @next_gpd
- * @ext_flag:
- * bit5 (TX ONLY): Zero Length Packet (ZLP),
+ * @dw3_info:
+ * bit[15:0]: ([EL] bit[19:0]) data buffer length,
+ * (TX): the buffer length of the data to transmit
+ * (RX): The total length of data received
+ * bit[23:16]: ([EL] bit[31:24]) extension address (RX ONLY),
+ * lower 4 bits are extension bits of @buffer,
+ * upper 4 bits are extension bits of @next_gpd
+ * bit29: ([EL] abandoned) Zero Length Packet (ZLP) (TX ONLY)
*/
struct qmu_gpd {
- __u8 flag;
- __u8 chksum;
- union {
- __le16 data_buf_len;
- __le16 tx_ext_addr;
- };
+ __le32 dw0_info;
__le32 next_gpd;
__le32 buffer;
- __le16 buf_len;
- union {
- __u8 ext_len;
- __u8 rx_ext_addr;
- };
- __u8 ext_flag;
+ __le32 dw3_info;
} __packed;
/**
* @may_wakeup: means device's remote wakeup is enabled
* @is_self_powered: is reported in device status and the config descriptor
* @delayed_status: true when function drivers ask for delayed status
+ * @gen2cp: compatible with USB3 Gen2 IP
* @ep0_req: dummy request used while handling standard USB requests
* for GET_STATUS and SET_SEL
* @setup_buf: ep0 response buffer for GET_STATUS and SET_SEL requests
unsigned u2_enable:1;
unsigned is_u3_ip:1;
unsigned delayed_status:1;
+ unsigned gen2cp:1;
u8 address;
u8 test_mode_nr;
#include <linux/platform_device.h>
#include "mtu3.h"
+#include "mtu3_debug.h"
+#include "mtu3_trace.h"
static int ep_fifo_alloc(struct mtu3_ep *mep, u32 seg_size)
{
int interval, int burst, int mult)
{
void __iomem *mbase = mtu->mac_base;
+ bool gen2cp = mtu->gen2cp;
int epnum = mep->epnum;
u32 csr0, csr1, csr2;
int fifo_sgsz, fifo_addr;
num_pkts = (burst + 1) * (mult + 1) - 1;
csr1 = TX_SS_BURST(burst) | TX_SLOT(mep->slot);
- csr1 |= TX_MAX_PKT(num_pkts) | TX_MULT(mult);
+ csr1 |= TX_MAX_PKT(gen2cp, num_pkts) | TX_MULT(gen2cp, mult);
csr2 = TX_FIFOADDR(fifo_addr >> 4);
csr2 |= TX_FIFOSEGSIZE(fifo_sgsz);
num_pkts = (burst + 1) * (mult + 1) - 1;
csr1 = RX_SS_BURST(burst) | RX_SLOT(mep->slot);
- csr1 |= RX_MAX_PKT(num_pkts) | RX_MULT(mult);
+ csr1 |= RX_MAX_PKT(gen2cp, num_pkts) | RX_MULT(gen2cp, mult);
csr2 = RX_FIFOADDR(fifo_addr >> 4);
csr2 |= RX_FIFOSEGSIZE(fifo_sgsz);
mtu3_clrbits(mbase, U3D_MISC_CTRL, VBUS_FRC_EN | VBUS_ON);
/* enable automatical HWRW from L1 */
mtu3_setbits(mbase, U3D_POWER_MANAGEMENT, LPM_HRWE);
+
+ /* use new QMU format when HW version >= 0x1003 */
+ if (mtu->gen2cp)
+ mtu3_writel(mbase, U3D_QFCR, ~0x0);
}
static irqreturn_t mtu3_link_isr(struct mtu3 *mtu)
break;
}
dev_dbg(mtu->dev, "%s: %s\n", __func__, usb_speed_string(udev_speed));
+ mtu3_dbg_trace(mtu->dev, "link speed %s",
+ usb_speed_string(udev_speed));
mtu->g.speed = udev_speed;
mtu->g.ep0->maxpacket = maxpkt;
ltssm &= mtu3_readl(mbase, U3D_LTSSM_INTR_ENABLE);
mtu3_writel(mbase, U3D_LTSSM_INTR, ltssm); /* W1C */
dev_dbg(mtu->dev, "=== LTSSM[%x] ===\n", ltssm);
+ trace_mtu3_u3_ltssm_isr(ltssm);
if (ltssm & (HOT_RST_INTR | WARM_RST_INTR))
mtu3_gadget_reset(mtu);
u2comm &= mtu3_readl(mbase, U3D_COMMON_USB_INTR_ENABLE);
mtu3_writel(mbase, U3D_COMMON_USB_INTR, u2comm); /* W1C */
dev_dbg(mtu->dev, "=== U2COMM[%x] ===\n", u2comm);
+ trace_mtu3_u2_common_isr(u2comm);
if (u2comm & SUSPEND_INTR)
mtu3_gadget_suspend(mtu);
static int mtu3_hw_init(struct mtu3 *mtu)
{
- u32 cap_dev;
+ u32 value;
int ret;
- mtu->hw_version = mtu3_readl(mtu->ippc_base, U3D_SSUSB_HW_ID);
+ value = mtu3_readl(mtu->ippc_base, U3D_SSUSB_IP_TRUNK_VERS);
+ mtu->hw_version = IP_TRUNK_VERS(value);
+ mtu->gen2cp = !!(mtu->hw_version >= MTU3_TRUNK_VERS_1003);
- cap_dev = mtu3_readl(mtu->ippc_base, U3D_SSUSB_IP_DEV_CAP);
- mtu->is_u3_ip = !!SSUSB_IP_DEV_U3_PORT_NUM(cap_dev);
+ value = mtu3_readl(mtu->ippc_base, U3D_SSUSB_IP_DEV_CAP);
+ mtu->is_u3_ip = !!SSUSB_IP_DEV_U3_PORT_NUM(value);
dev_info(mtu->dev, "IP version 0x%x(%s IP)\n", mtu->hw_version,
mtu->is_u3_ip ? "U3" : "U2");
if (mtu->ssusb->dr_mode == USB_DR_MODE_OTG)
mtu3_stop(mtu);
+ ssusb_dev_debugfs_init(ssusb);
+
dev_dbg(dev, " %s() done...\n", __func__);
return 0;
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * mtu3_debug.h - debug header
+ *
+ * Copyright (C) 2019 MediaTek Inc.
+ *
+ * Author: Chunfeng Yun <chunfeng.yun@mediatek.com>
+ */
+
+#ifndef __MTU3_DEBUG_H__
+#define __MTU3_DEBUG_H__
+
+#include <linux/debugfs.h>
+
+#define MTU3_DEBUGFS_NAME_LEN 32
+
+struct mtu3_regset {
+ char name[MTU3_DEBUGFS_NAME_LEN];
+ struct debugfs_regset32 regset;
+ size_t nregs;
+};
+
+struct mtu3_file_map {
+ const char *name;
+ int (*show)(struct seq_file *s, void *unused);
+};
+
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+void ssusb_dev_debugfs_init(struct ssusb_mtk *ssusb);
+void ssusb_dr_debugfs_init(struct ssusb_mtk *ssusb);
+void ssusb_debugfs_create_root(struct ssusb_mtk *ssusb);
+void ssusb_debugfs_remove_root(struct ssusb_mtk *ssusb);
+
+#else
+static inline void ssusb_dev_debugfs_init(struct ssusb_mtk *ssusb) {}
+static inline void ssusb_dr_debugfs_init(struct ssusb_mtk *ssusb) {}
+static inline void ssusb_debugfs_create_root(struct ssusb_mtk *ssusb) {}
+static inline void ssusb_debugfs_remove_root(struct ssusb_mtk *ssusb) {}
+
+#endif /* CONFIG_DEBUG_FS */
+
+#if IS_ENABLED(CONFIG_TRACING)
+void mtu3_dbg_trace(struct device *dev, const char *fmt, ...);
+
+#else
+static inline void mtu3_dbg_trace(struct device *dev, const char *fmt, ...) {}
+
+#endif /* CONFIG_TRACING */
+
+#endif /* __MTU3_DEBUG_H__ */
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * mtu3_debugfs.c - debugfs interface
+ *
+ * Copyright (C) 2019 MediaTek Inc.
+ *
+ * Author: Chunfeng Yun <chunfeng.yun@mediatek.com>
+ */
+
+#include <linux/uaccess.h>
+
+#include "mtu3.h"
+#include "mtu3_dr.h"
+#include "mtu3_debug.h"
+
+#define dump_register(nm) \
+{ \
+ .name = __stringify(nm), \
+ .offset = U3D_ ##nm, \
+}
+
+#define dump_prb_reg(nm, os) \
+{ \
+ .name = nm, \
+ .offset = os, \
+}
+
+static const struct debugfs_reg32 mtu3_ippc_regs[] = {
+ dump_register(SSUSB_IP_PW_CTRL0),
+ dump_register(SSUSB_IP_PW_CTRL1),
+ dump_register(SSUSB_IP_PW_CTRL2),
+ dump_register(SSUSB_IP_PW_CTRL3),
+ dump_register(SSUSB_OTG_STS),
+ dump_register(SSUSB_IP_XHCI_CAP),
+ dump_register(SSUSB_IP_DEV_CAP),
+ dump_register(SSUSB_U3_CTRL_0P),
+ dump_register(SSUSB_U2_CTRL_0P),
+ dump_register(SSUSB_HW_ID),
+ dump_register(SSUSB_HW_SUB_ID),
+ dump_register(SSUSB_IP_SPARE0),
+};
+
+static const struct debugfs_reg32 mtu3_dev_regs[] = {
+ dump_register(LV1ISR),
+ dump_register(LV1IER),
+ dump_register(EPISR),
+ dump_register(EPIER),
+ dump_register(EP0CSR),
+ dump_register(RXCOUNT0),
+ dump_register(QISAR0),
+ dump_register(QIER0),
+ dump_register(QISAR1),
+ dump_register(QIER1),
+ dump_register(CAP_EPNTXFFSZ),
+ dump_register(CAP_EPNRXFFSZ),
+ dump_register(CAP_EPINFO),
+ dump_register(MISC_CTRL),
+};
+
+static const struct debugfs_reg32 mtu3_csr_regs[] = {
+ dump_register(DEVICE_CONF),
+ dump_register(DEV_LINK_INTR_ENABLE),
+ dump_register(DEV_LINK_INTR),
+ dump_register(LTSSM_CTRL),
+ dump_register(USB3_CONFIG),
+ dump_register(LINK_STATE_MACHINE),
+ dump_register(LTSSM_INTR_ENABLE),
+ dump_register(LTSSM_INTR),
+ dump_register(U3U2_SWITCH_CTRL),
+ dump_register(POWER_MANAGEMENT),
+ dump_register(DEVICE_CONTROL),
+ dump_register(COMMON_USB_INTR_ENABLE),
+ dump_register(COMMON_USB_INTR),
+ dump_register(USB20_MISC_CONTROL),
+ dump_register(USB20_OPSTATE),
+};
+
+static int mtu3_link_state_show(struct seq_file *sf, void *unused)
+{
+ struct mtu3 *mtu = sf->private;
+ void __iomem *mbase = mtu->mac_base;
+
+ seq_printf(sf, "opstate: %#x, ltssm: %#x\n",
+ mtu3_readl(mbase, U3D_USB20_OPSTATE),
+ LTSSM_STATE(mtu3_readl(mbase, U3D_LINK_STATE_MACHINE)));
+
+ return 0;
+}
+
+static int mtu3_ep_used_show(struct seq_file *sf, void *unused)
+{
+ struct mtu3 *mtu = sf->private;
+ struct mtu3_ep *mep;
+ unsigned long flags;
+ int used = 0;
+ int i;
+
+ spin_lock_irqsave(&mtu->lock, flags);
+
+ for (i = 0; i < mtu->num_eps; i++) {
+ mep = mtu->in_eps + i;
+ if (mep->flags & MTU3_EP_ENABLED) {
+ seq_printf(sf, "%s - type: %d\n", mep->name, mep->type);
+ used++;
+ }
+
+ mep = mtu->out_eps + i;
+ if (mep->flags & MTU3_EP_ENABLED) {
+ seq_printf(sf, "%s - type: %d\n", mep->name, mep->type);
+ used++;
+ }
+ }
+ seq_printf(sf, "total used: %d eps\n", used);
+
+ spin_unlock_irqrestore(&mtu->lock, flags);
+
+ return 0;
+}
+
+DEFINE_SHOW_ATTRIBUTE(mtu3_link_state);
+DEFINE_SHOW_ATTRIBUTE(mtu3_ep_used);
+
+static void mtu3_debugfs_regset(struct mtu3 *mtu, void __iomem *base,
+ const struct debugfs_reg32 *regs, size_t nregs,
+ const char *name, struct dentry *parent)
+{
+ struct debugfs_regset32 *regset;
+ struct mtu3_regset *mregs;
+
+ mregs = devm_kzalloc(mtu->dev, sizeof(*regset), GFP_KERNEL);
+ if (!mregs)
+ return;
+
+ sprintf(mregs->name, "%s", name);
+ regset = &mregs->regset;
+ regset->regs = regs;
+ regset->nregs = nregs;
+ regset->base = base;
+
+ debugfs_create_regset32(mregs->name, 0444, parent, regset);
+}
+
+static void mtu3_debugfs_ep_regset(struct mtu3 *mtu, struct mtu3_ep *mep,
+ struct dentry *parent)
+{
+ struct debugfs_reg32 *regs;
+ int epnum = mep->epnum;
+ int in = mep->is_in;
+
+ regs = devm_kcalloc(mtu->dev, 7, sizeof(*regs), GFP_KERNEL);
+ if (!regs)
+ return;
+
+ regs[0].name = in ? "TCR0" : "RCR0";
+ regs[0].offset = in ? MU3D_EP_TXCR0(epnum) : MU3D_EP_RXCR0(epnum);
+ regs[1].name = in ? "TCR1" : "RCR1";
+ regs[1].offset = in ? MU3D_EP_TXCR1(epnum) : MU3D_EP_RXCR1(epnum);
+ regs[2].name = in ? "TCR2" : "RCR2";
+ regs[2].offset = in ? MU3D_EP_TXCR2(epnum) : MU3D_EP_RXCR2(epnum);
+ regs[3].name = in ? "TQHIAR" : "RQHIAR";
+ regs[3].offset = in ? USB_QMU_TQHIAR(epnum) : USB_QMU_RQHIAR(epnum);
+ regs[4].name = in ? "TQCSR" : "RQCSR";
+ regs[4].offset = in ? USB_QMU_TQCSR(epnum) : USB_QMU_RQCSR(epnum);
+ regs[5].name = in ? "TQSAR" : "RQSAR";
+ regs[5].offset = in ? USB_QMU_TQSAR(epnum) : USB_QMU_RQSAR(epnum);
+ regs[6].name = in ? "TQCPR" : "RQCPR";
+ regs[6].offset = in ? USB_QMU_TQCPR(epnum) : USB_QMU_RQCPR(epnum);
+
+ mtu3_debugfs_regset(mtu, mtu->mac_base, regs, 7, "ep-regs", parent);
+}
+
+static int mtu3_ep_info_show(struct seq_file *sf, void *unused)
+{
+ struct mtu3_ep *mep = sf->private;
+ struct mtu3 *mtu = mep->mtu;
+ unsigned long flags;
+
+ spin_lock_irqsave(&mtu->lock, flags);
+ seq_printf(sf, "ep - type:%d, maxp:%d, slot:%d, flags:%x\n",
+ mep->type, mep->maxp, mep->slot, mep->flags);
+ spin_unlock_irqrestore(&mtu->lock, flags);
+
+ return 0;
+}
+
+static int mtu3_fifo_show(struct seq_file *sf, void *unused)
+{
+ struct mtu3_ep *mep = sf->private;
+ struct mtu3 *mtu = mep->mtu;
+ unsigned long flags;
+
+ spin_lock_irqsave(&mtu->lock, flags);
+ seq_printf(sf, "fifo - seg_size:%d, addr:%d, size:%d\n",
+ mep->fifo_seg_size, mep->fifo_addr, mep->fifo_size);
+ spin_unlock_irqrestore(&mtu->lock, flags);
+
+ return 0;
+}
+
+static int mtu3_qmu_ring_show(struct seq_file *sf, void *unused)
+{
+ struct mtu3_ep *mep = sf->private;
+ struct mtu3 *mtu = mep->mtu;
+ struct mtu3_gpd_ring *ring;
+ unsigned long flags;
+
+ ring = &mep->gpd_ring;
+ spin_lock_irqsave(&mtu->lock, flags);
+ seq_printf(sf,
+ "qmu-ring - dma:%pad, start:%p, end:%p, enq:%p, dep:%p\n",
+ &ring->dma, ring->start, ring->end,
+ ring->enqueue, ring->dequeue);
+ spin_unlock_irqrestore(&mtu->lock, flags);
+
+ return 0;
+}
+
+static int mtu3_qmu_gpd_show(struct seq_file *sf, void *unused)
+{
+ struct mtu3_ep *mep = sf->private;
+ struct mtu3 *mtu = mep->mtu;
+ struct mtu3_gpd_ring *ring;
+ struct qmu_gpd *gpd;
+ dma_addr_t dma;
+ unsigned long flags;
+ int i;
+
+ spin_lock_irqsave(&mtu->lock, flags);
+ ring = &mep->gpd_ring;
+ gpd = ring->start;
+ if (!gpd || !(mep->flags & MTU3_EP_ENABLED)) {
+ seq_puts(sf, "empty!\n");
+ goto out;
+ }
+
+ for (i = 0; i < MAX_GPD_NUM; i++, gpd++) {
+ dma = ring->dma + i * sizeof(*gpd);
+ seq_printf(sf, "gpd.%03d -> %pad, %p: %08x %08x %08x %08x\n",
+ i, &dma, gpd, gpd->dw0_info, gpd->next_gpd,
+ gpd->buffer, gpd->dw3_info);
+ }
+
+out:
+ spin_unlock_irqrestore(&mtu->lock, flags);
+
+ return 0;
+}
+
+static const struct mtu3_file_map mtu3_ep_files[] = {
+ {"ep-info", mtu3_ep_info_show, },
+ {"fifo", mtu3_fifo_show, },
+ {"qmu-ring", mtu3_qmu_ring_show, },
+ {"qmu-gpd", mtu3_qmu_gpd_show, },
+};
+
+static int mtu3_ep_open(struct inode *inode, struct file *file)
+{
+ const char *file_name = file_dentry(file)->d_iname;
+ const struct mtu3_file_map *f_map;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(mtu3_ep_files); i++) {
+ f_map = &mtu3_ep_files[i];
+
+ if (strcmp(f_map->name, file_name) == 0)
+ break;
+ }
+
+ return single_open(file, f_map->show, inode->i_private);
+}
+
+static const struct file_operations mtu3_ep_fops = {
+ .open = mtu3_ep_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static struct debugfs_reg32 mtu3_prb_regs[] = {
+ dump_prb_reg("enable", U3D_SSUSB_PRB_CTRL0),
+ dump_prb_reg("byte-sell", U3D_SSUSB_PRB_CTRL1),
+ dump_prb_reg("byte-selh", U3D_SSUSB_PRB_CTRL2),
+ dump_prb_reg("module-sel", U3D_SSUSB_PRB_CTRL3),
+ dump_prb_reg("sw-out", U3D_SSUSB_PRB_CTRL4),
+ dump_prb_reg("data", U3D_SSUSB_PRB_CTRL5),
+};
+
+static int mtu3_probe_show(struct seq_file *sf, void *unused)
+{
+ const char *file_name = file_dentry(sf->file)->d_iname;
+ struct mtu3 *mtu = sf->private;
+ const struct debugfs_reg32 *regs;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(mtu3_prb_regs); i++) {
+ regs = &mtu3_prb_regs[i];
+
+ if (strcmp(regs->name, file_name) == 0)
+ break;
+ }
+
+ seq_printf(sf, "0x%04x - 0x%08x\n", (u32)regs->offset,
+ mtu3_readl(mtu->ippc_base, (u32)regs->offset));
+
+ return 0;
+}
+
+static int mtu3_probe_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, mtu3_probe_show, inode->i_private);
+}
+
+static ssize_t mtu3_probe_write(struct file *file, const char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ const char *file_name = file_dentry(file)->d_iname;
+ struct seq_file *sf = file->private_data;
+ struct mtu3 *mtu = sf->private;
+ const struct debugfs_reg32 *regs;
+ char buf[32];
+ u32 val;
+ int i;
+
+ if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count)))
+ return -EFAULT;
+
+ if (kstrtou32(buf, 0, &val))
+ return -EINVAL;
+
+ for (i = 0; i < ARRAY_SIZE(mtu3_prb_regs); i++) {
+ regs = &mtu3_prb_regs[i];
+
+ if (strcmp(regs->name, file_name) == 0)
+ break;
+ }
+ mtu3_writel(mtu->ippc_base, (u32)regs->offset, val);
+
+ return count;
+}
+
+static const struct file_operations mtu3_probe_fops = {
+ .open = mtu3_probe_open,
+ .write = mtu3_probe_write,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static void mtu3_debugfs_create_prb_files(struct mtu3 *mtu)
+{
+ struct ssusb_mtk *ssusb = mtu->ssusb;
+ struct debugfs_reg32 *regs;
+ struct dentry *dir_prb;
+ int i;
+
+ dir_prb = debugfs_create_dir("probe", ssusb->dbgfs_root);
+
+ for (i = 0; i < ARRAY_SIZE(mtu3_prb_regs); i++) {
+ regs = &mtu3_prb_regs[i];
+ debugfs_create_file(regs->name, 0644, dir_prb,
+ mtu, &mtu3_probe_fops);
+ }
+
+ mtu3_debugfs_regset(mtu, mtu->ippc_base, mtu3_prb_regs,
+ ARRAY_SIZE(mtu3_prb_regs), "regs", dir_prb);
+}
+
+static void mtu3_debugfs_create_ep_dir(struct mtu3_ep *mep,
+ struct dentry *parent)
+{
+ const struct mtu3_file_map *files;
+ struct dentry *dir_ep;
+ int i;
+
+ dir_ep = debugfs_create_dir(mep->name, parent);
+ mtu3_debugfs_ep_regset(mep->mtu, mep, dir_ep);
+
+ for (i = 0; i < ARRAY_SIZE(mtu3_ep_files); i++) {
+ files = &mtu3_ep_files[i];
+
+ debugfs_create_file(files->name, 0444, dir_ep,
+ mep, &mtu3_ep_fops);
+ }
+}
+
+static void mtu3_debugfs_create_ep_dirs(struct mtu3 *mtu)
+{
+ struct ssusb_mtk *ssusb = mtu->ssusb;
+ struct dentry *dir_eps;
+ int i;
+
+ dir_eps = debugfs_create_dir("eps", ssusb->dbgfs_root);
+
+ for (i = 1; i < mtu->num_eps; i++) {
+ mtu3_debugfs_create_ep_dir(mtu->in_eps + i, dir_eps);
+ mtu3_debugfs_create_ep_dir(mtu->out_eps + i, dir_eps);
+ }
+}
+
+void ssusb_dev_debugfs_init(struct ssusb_mtk *ssusb)
+{
+ struct mtu3 *mtu = ssusb->u3d;
+ struct dentry *dir_regs;
+
+ dir_regs = debugfs_create_dir("regs", ssusb->dbgfs_root);
+
+ mtu3_debugfs_regset(mtu, mtu->ippc_base,
+ mtu3_ippc_regs, ARRAY_SIZE(mtu3_ippc_regs),
+ "reg-ippc", dir_regs);
+
+ mtu3_debugfs_regset(mtu, mtu->mac_base,
+ mtu3_dev_regs, ARRAY_SIZE(mtu3_dev_regs),
+ "reg-dev", dir_regs);
+
+ mtu3_debugfs_regset(mtu, mtu->mac_base,
+ mtu3_csr_regs, ARRAY_SIZE(mtu3_csr_regs),
+ "reg-csr", dir_regs);
+
+ mtu3_debugfs_create_ep_dirs(mtu);
+
+ mtu3_debugfs_create_prb_files(mtu);
+
+ debugfs_create_file("link-state", 0444, ssusb->dbgfs_root,
+ mtu, &mtu3_link_state_fops);
+ debugfs_create_file("ep-used", 0444, ssusb->dbgfs_root,
+ mtu, &mtu3_ep_used_fops);
+}
+
+static int ssusb_mode_show(struct seq_file *sf, void *unused)
+{
+ struct ssusb_mtk *ssusb = sf->private;
+
+ seq_printf(sf, "current mode: %s(%s drd)\n(echo device/host)\n",
+ ssusb->is_host ? "host" : "device",
+ ssusb->otg_switch.manual_drd_enabled ? "manual" : "auto");
+
+ return 0;
+}
+
+static int ssusb_mode_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, ssusb_mode_show, inode->i_private);
+}
+
+static ssize_t ssusb_mode_write(struct file *file, const char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ struct seq_file *sf = file->private_data;
+ struct ssusb_mtk *ssusb = sf->private;
+ char buf[16];
+
+ if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count)))
+ return -EFAULT;
+
+ if (!strncmp(buf, "host", 4) && !ssusb->is_host) {
+ ssusb_mode_manual_switch(ssusb, 1);
+ } else if (!strncmp(buf, "device", 6) && ssusb->is_host) {
+ ssusb_mode_manual_switch(ssusb, 0);
+ } else {
+ dev_err(ssusb->dev, "wrong or duplicated setting\n");
+ return -EINVAL;
+ }
+
+ return count;
+}
+
+static const struct file_operations ssusb_mode_fops = {
+ .open = ssusb_mode_open,
+ .write = ssusb_mode_write,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int ssusb_vbus_show(struct seq_file *sf, void *unused)
+{
+ struct ssusb_mtk *ssusb = sf->private;
+ struct otg_switch_mtk *otg_sx = &ssusb->otg_switch;
+
+ seq_printf(sf, "vbus state: %s\n(echo on/off)\n",
+ regulator_is_enabled(otg_sx->vbus) ? "on" : "off");
+
+ return 0;
+}
+
+static int ssusb_vbus_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, ssusb_vbus_show, inode->i_private);
+}
+
+static ssize_t ssusb_vbus_write(struct file *file, const char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ struct seq_file *sf = file->private_data;
+ struct ssusb_mtk *ssusb = sf->private;
+ struct otg_switch_mtk *otg_sx = &ssusb->otg_switch;
+ char buf[16];
+ bool enable;
+
+ if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count)))
+ return -EFAULT;
+
+ if (kstrtobool(buf, &enable)) {
+ dev_err(ssusb->dev, "wrong setting\n");
+ return -EINVAL;
+ }
+
+ ssusb_set_vbus(otg_sx, enable);
+
+ return count;
+}
+
+static const struct file_operations ssusb_vbus_fops = {
+ .open = ssusb_vbus_open,
+ .write = ssusb_vbus_write,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+void ssusb_dr_debugfs_init(struct ssusb_mtk *ssusb)
+{
+ struct dentry *root = ssusb->dbgfs_root;
+
+ debugfs_create_file("mode", 0644, root, ssusb, &ssusb_mode_fops);
+ debugfs_create_file("vbus", 0644, root, ssusb, &ssusb_vbus_fops);
+}
+
+void ssusb_debugfs_create_root(struct ssusb_mtk *ssusb)
+{
+ ssusb->dbgfs_root =
+ debugfs_create_dir(dev_name(ssusb->dev), usb_debug_root);
+}
+
+void ssusb_debugfs_remove_root(struct ssusb_mtk *ssusb)
+{
+ debugfs_remove_recursive(ssusb->dbgfs_root);
+ ssusb->dbgfs_root = NULL;
+}
* Author: Chunfeng Yun <chunfeng.yun@mediatek.com>
*/
-#include <linux/debugfs.h>
-#include <linux/irq.h>
-#include <linux/kernel.h>
-#include <linux/of_device.h>
-#include <linux/pinctrl/consumer.h>
-#include <linux/seq_file.h>
-#include <linux/uaccess.h>
-
#include "mtu3.h"
#include "mtu3_dr.h"
+#include "mtu3_debug.h"
#define USB2_PORT 2
#define USB3_PORT 3
MTU3_VBUS_VALID,
};
+static char *mailbox_state_string(enum mtu3_vbus_id_state state)
+{
+ switch (state) {
+ case MTU3_ID_FLOAT:
+ return "ID_FLOAT";
+ case MTU3_ID_GROUND:
+ return "ID_GROUND";
+ case MTU3_VBUS_OFF:
+ return "VBUS_OFF";
+ case MTU3_VBUS_VALID:
+ return "VBUS_VALID";
+ default:
+ return "UNKNOWN";
+ }
+}
+
static void toggle_opstate(struct ssusb_mtk *ssusb)
{
if (!ssusb->otg_switch.is_u3_drd) {
container_of(otg_sx, struct ssusb_mtk, otg_switch);
struct mtu3 *mtu = ssusb->u3d;
- dev_dbg(ssusb->dev, "mailbox state(%d)\n", status);
+ dev_dbg(ssusb->dev, "mailbox %s\n", mailbox_state_string(status));
+ mtu3_dbg_trace(ssusb->dev, "mailbox %s", mailbox_state_string(status));
switch (status) {
case MTU3_ID_GROUND:
otg_sx->vbus_nb.notifier_call = ssusb_vbus_notifier;
ret = devm_extcon_register_notifier(ssusb->dev, edev, EXTCON_USB,
&otg_sx->vbus_nb);
- if (ret < 0)
+ if (ret < 0) {
dev_err(ssusb->dev, "failed to register notifier for USB\n");
+ return ret;
+ }
otg_sx->id_nb.notifier_call = ssusb_id_notifier;
ret = devm_extcon_register_notifier(ssusb->dev, edev, EXTCON_USB_HOST,
&otg_sx->id_nb);
- if (ret < 0)
+ if (ret < 0) {
dev_err(ssusb->dev, "failed to register notifier for USB-HOST\n");
+ return ret;
+ }
dev_dbg(ssusb->dev, "EXTCON_USB: %d, EXTCON_USB_HOST: %d\n",
extcon_get_state(edev, EXTCON_USB),
* This is useful in special cases, such as uses TYPE-A receptacle but also
* wants to support dual-role mode.
*/
-static void ssusb_mode_manual_switch(struct ssusb_mtk *ssusb, int to_host)
+void ssusb_mode_manual_switch(struct ssusb_mtk *ssusb, int to_host)
{
struct otg_switch_mtk *otg_sx = &ssusb->otg_switch;
}
}
-static int ssusb_mode_show(struct seq_file *sf, void *unused)
-{
- struct ssusb_mtk *ssusb = sf->private;
-
- seq_printf(sf, "current mode: %s(%s drd)\n(echo device/host)\n",
- ssusb->is_host ? "host" : "device",
- ssusb->otg_switch.manual_drd_enabled ? "manual" : "auto");
-
- return 0;
-}
-
-static int ssusb_mode_open(struct inode *inode, struct file *file)
-{
- return single_open(file, ssusb_mode_show, inode->i_private);
-}
-
-static ssize_t ssusb_mode_write(struct file *file,
- const char __user *ubuf, size_t count, loff_t *ppos)
-{
- struct seq_file *sf = file->private_data;
- struct ssusb_mtk *ssusb = sf->private;
- char buf[16];
-
- if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count)))
- return -EFAULT;
-
- if (!strncmp(buf, "host", 4) && !ssusb->is_host) {
- ssusb_mode_manual_switch(ssusb, 1);
- } else if (!strncmp(buf, "device", 6) && ssusb->is_host) {
- ssusb_mode_manual_switch(ssusb, 0);
- } else {
- dev_err(ssusb->dev, "wrong or duplicated setting\n");
- return -EINVAL;
- }
-
- return count;
-}
-
-static const struct file_operations ssusb_mode_fops = {
- .open = ssusb_mode_open,
- .write = ssusb_mode_write,
- .read = seq_read,
- .llseek = seq_lseek,
- .release = single_release,
-};
-
-static int ssusb_vbus_show(struct seq_file *sf, void *unused)
-{
- struct ssusb_mtk *ssusb = sf->private;
- struct otg_switch_mtk *otg_sx = &ssusb->otg_switch;
-
- seq_printf(sf, "vbus state: %s\n(echo on/off)\n",
- regulator_is_enabled(otg_sx->vbus) ? "on" : "off");
-
- return 0;
-}
-
-static int ssusb_vbus_open(struct inode *inode, struct file *file)
-{
- return single_open(file, ssusb_vbus_show, inode->i_private);
-}
-
-static ssize_t ssusb_vbus_write(struct file *file,
- const char __user *ubuf, size_t count, loff_t *ppos)
-{
- struct seq_file *sf = file->private_data;
- struct ssusb_mtk *ssusb = sf->private;
- struct otg_switch_mtk *otg_sx = &ssusb->otg_switch;
- char buf[16];
- bool enable;
-
- if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count)))
- return -EFAULT;
-
- if (kstrtobool(buf, &enable)) {
- dev_err(ssusb->dev, "wrong setting\n");
- return -EINVAL;
- }
-
- ssusb_set_vbus(otg_sx, enable);
-
- return count;
-}
-
-static const struct file_operations ssusb_vbus_fops = {
- .open = ssusb_vbus_open,
- .write = ssusb_vbus_write,
- .read = seq_read,
- .llseek = seq_lseek,
- .release = single_release,
-};
-
-static void ssusb_debugfs_init(struct ssusb_mtk *ssusb)
-{
- struct dentry *root;
-
- root = debugfs_create_dir(dev_name(ssusb->dev), usb_debug_root);
- ssusb->dbgfs_root = root;
-
- debugfs_create_file("mode", 0644, root, ssusb, &ssusb_mode_fops);
- debugfs_create_file("vbus", 0644, root, ssusb, &ssusb_vbus_fops);
-}
-
-static void ssusb_debugfs_exit(struct ssusb_mtk *ssusb)
-{
- debugfs_remove_recursive(ssusb->dbgfs_root);
-}
-
void ssusb_set_force_mode(struct ssusb_mtk *ssusb,
enum mtu3_dr_force_mode mode)
{
int ssusb_otg_switch_init(struct ssusb_mtk *ssusb)
{
struct otg_switch_mtk *otg_sx = &ssusb->otg_switch;
+ int ret = 0;
INIT_WORK(&otg_sx->id_work, ssusb_id_work);
INIT_WORK(&otg_sx->vbus_work, ssusb_vbus_work);
if (otg_sx->manual_drd_enabled)
- ssusb_debugfs_init(ssusb);
+ ssusb_dr_debugfs_init(ssusb);
else
- ssusb_extcon_register(otg_sx);
+ ret = ssusb_extcon_register(otg_sx);
- return 0;
+ return ret;
}
void ssusb_otg_switch_exit(struct ssusb_mtk *ssusb)
{
struct otg_switch_mtk *otg_sx = &ssusb->otg_switch;
- if (otg_sx->manual_drd_enabled)
- ssusb_debugfs_exit(ssusb);
-
cancel_work_sync(&otg_sx->id_work);
cancel_work_sync(&otg_sx->vbus_work);
}
#if IS_ENABLED(CONFIG_USB_MTU3_DUAL_ROLE)
int ssusb_otg_switch_init(struct ssusb_mtk *ssusb);
void ssusb_otg_switch_exit(struct ssusb_mtk *ssusb);
+void ssusb_mode_manual_switch(struct ssusb_mtk *ssusb, int to_host);
int ssusb_set_vbus(struct otg_switch_mtk *otg_sx, int is_on);
void ssusb_set_force_mode(struct ssusb_mtk *ssusb,
enum mtu3_dr_force_mode mode);
static inline void ssusb_otg_switch_exit(struct ssusb_mtk *ssusb)
{}
+static inline void
+ssusb_mode_manual_switch(struct ssusb_mtk *ssusb, int to_host) {}
+
static inline int ssusb_set_vbus(struct otg_switch_mtk *otg_sx, int is_on)
{
return 0;
*/
#include "mtu3.h"
+#include "mtu3_trace.h"
void mtu3_req_complete(struct mtu3_ep *mep,
struct usb_request *req, int status)
mtu = mreq->mtu;
mep->busy = 1;
+
+ trace_mtu3_req_complete(mreq);
spin_unlock(&mtu->lock);
/* ep0 makes use of PIO, needn't unmap it */
spin_unlock_irqrestore(&mtu->lock, flags);
dev_dbg(mtu->dev, "%s active_ep=%d\n", __func__, mtu->active_ep);
+ trace_mtu3_gadget_ep_enable(mep);
return ret;
}
unsigned long flags;
dev_dbg(mtu->dev, "%s %s\n", __func__, mep->name);
+ trace_mtu3_gadget_ep_disable(mep);
if (!(mep->flags & MTU3_EP_ENABLED)) {
dev_warn(mtu->dev, "%s is already disabled\n", mep->name);
mreq->request.dma = DMA_ADDR_INVALID;
mreq->epnum = mep->epnum;
mreq->mep = mep;
+ trace_mtu3_alloc_request(mreq);
return &mreq->request;
}
void mtu3_free_request(struct usb_ep *ep, struct usb_request *req)
{
- kfree(to_mtu3_request(req));
+ struct mtu3_request *mreq = to_mtu3_request(req);
+
+ trace_mtu3_free_request(mreq);
+ kfree(mreq);
}
static int mtu3_gadget_queue(struct usb_ep *ep,
__func__, mep->is_in ? "TX" : "RX", mreq->epnum, ep->name,
mreq, ep->maxpacket, mreq->request.length);
- if (req->length > GPD_BUF_SIZE) {
+ if (req->length > GPD_BUF_SIZE ||
+ (mtu->gen2cp && req->length > GPD_BUF_SIZE_EL)) {
dev_warn(mtu->dev,
"req length > supported MAX:%d requested:%d\n",
- GPD_BUF_SIZE, req->length);
+ mtu->gen2cp ? GPD_BUF_SIZE_EL : GPD_BUF_SIZE,
+ req->length);
return -EOPNOTSUPP;
}
error:
spin_unlock_irqrestore(&mtu->lock, flags);
+ trace_mtu3_gadget_queue(mreq);
return ret;
}
return -EINVAL;
dev_dbg(mtu->dev, "%s : req=%p\n", __func__, req);
+ trace_mtu3_gadget_dequeue(mreq);
spin_lock_irqsave(&mtu->lock, flags);
done:
spin_unlock_irqrestore(&mtu->lock, flags);
+ trace_mtu3_gadget_ep_set_halt(mep);
return ret;
}
#include <linux/usb/composite.h>
#include "mtu3.h"
+#include "mtu3_debug.h"
+#include "mtu3_trace.h"
/* ep0 is always mtu3->in_eps[0] */
#define next_ep0_request(mtu) next_request((mtu)->ep0)
int handled = 0;
ep0_read_setup(mtu, &setup);
+ trace_mtu3_handle_setup(&setup);
if ((setup.bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD)
handled = handle_standard_request(mtu, &setup);
ret = IRQ_HANDLED;
}
dev_dbg(mtu->dev, "ep0_state: %s\n", decode_ep0_state(mtu));
+ mtu3_dbg_trace(mtu->dev, "ep0_state %s", decode_ep0_state(mtu));
switch (mtu->ep0_state) {
case MU3D_EP0_STATE_TX:
#define U3D_QCR1 (SSUSB_DEV_BASE + 0x0404)
#define U3D_QCR2 (SSUSB_DEV_BASE + 0x0408)
#define U3D_QCR3 (SSUSB_DEV_BASE + 0x040C)
+#define U3D_QFCR (SSUSB_DEV_BASE + 0x0428)
#define U3D_TXQHIAR1 (SSUSB_DEV_BASE + 0x0484)
#define U3D_RXQHIAR1 (SSUSB_DEV_BASE + 0x04C4)
#define TX_W1C_BITS (~(TX_SENTSTALL))
/* U3D_TX1CSR1 */
-#define TX_MULT(x) (((x) & 0x3) << 22)
-#define TX_MAX_PKT(x) (((x) & 0x3f) << 16)
+#define TX_MAX_PKT_G2(x) (((x) & 0x7f) << 24)
+#define TX_MULT_G2(x) (((x) & 0x7) << 21)
+#define TX_MULT_OG(x) (((x) & 0x3) << 22)
+#define TX_MAX_PKT_OG(x) (((x) & 0x3f) << 16)
#define TX_SLOT(x) (((x) & 0x3f) << 8)
#define TX_TYPE(x) (((x) & 0x3) << 4)
#define TX_SS_BURST(x) (((x) & 0xf) << 0)
+#define TX_MULT(g2c, x) \
+({ \
+ typeof(x) x_ = (x); \
+ (g2c) ? TX_MULT_G2(x_) : TX_MULT_OG(x_); \
+})
+#define TX_MAX_PKT(g2c, x) \
+({ \
+ typeof(x) x_ = (x); \
+ (g2c) ? TX_MAX_PKT_G2(x_) : TX_MAX_PKT_OG(x_); \
+})
/* for TX_TYPE & RX_TYPE */
#define TYPE_BULK (0x0)
#define RX_W1C_BITS (~(RX_SENTSTALL | RX_RXPKTRDY))
/* U3D_RX1CSR1 */
-#define RX_MULT(x) (((x) & 0x3) << 22)
-#define RX_MAX_PKT(x) (((x) & 0x3f) << 16)
+#define RX_MAX_PKT_G2(x) (((x) & 0x7f) << 24)
+#define RX_MULT_G2(x) (((x) & 0x7) << 21)
+#define RX_MULT_OG(x) (((x) & 0x3) << 22)
+#define RX_MAX_PKT_OG(x) (((x) & 0x3f) << 16)
#define RX_SLOT(x) (((x) & 0x3f) << 8)
#define RX_TYPE(x) (((x) & 0x3) << 4)
#define RX_SS_BURST(x) (((x) & 0xf) << 0)
+#define RX_MULT(g2c, x) \
+({ \
+ typeof(x) x_ = (x); \
+ (g2c) ? RX_MULT_G2(x_) : RX_MULT_OG(x_); \
+})
+#define RX_MAX_PKT(g2c, x) \
+({ \
+ typeof(x) x_ = (x); \
+ (g2c) ? RX_MAX_PKT_G2(x_) : RX_MAX_PKT_OG(x_); \
+})
/* U3D_RX1CSR2 */
#define RX_BINTERVAL(x) (((x) & 0xff) << 24)
#define U3D_LTSSM_CTRL (SSUSB_USB3_MAC_CSR_BASE + 0x0010)
#define U3D_USB3_CONFIG (SSUSB_USB3_MAC_CSR_BASE + 0x001C)
+#define U3D_LINK_STATE_MACHINE (SSUSB_USB3_MAC_CSR_BASE + 0x0134)
#define U3D_LTSSM_INTR_ENABLE (SSUSB_USB3_MAC_CSR_BASE + 0x013C)
#define U3D_LTSSM_INTR (SSUSB_USB3_MAC_CSR_BASE + 0x0140)
/* U3D_USB3_CONFIG */
#define USB3_EN BIT(0)
+/* U3D_LINK_STATE_MACHINE */
+#define LTSSM_STATE(x) ((x) & 0x1f)
+
/* U3D_LTSSM_INTR_ENABLE */
/* U3D_LTSSM_INTR */
#define U3_RESUME_INTR BIT(18)
#define U3D_USB20_FRAME_NUM (SSUSB_USB2_CSR_BASE + 0x003C)
#define U3D_USB20_LPM_PARAMETER (SSUSB_USB2_CSR_BASE + 0x0044)
#define U3D_USB20_MISC_CONTROL (SSUSB_USB2_CSR_BASE + 0x004C)
+#define U3D_USB20_OPSTATE (SSUSB_USB2_CSR_BASE + 0x0060)
/*---------------- SSUSB_USB2_CSR FIELD DEFINITION ----------------*/
#define U3D_SSUSB_DEV_RST_CTRL (SSUSB_SIFSLV_IPPC_BASE + 0x0098)
#define U3D_SSUSB_HW_ID (SSUSB_SIFSLV_IPPC_BASE + 0x00A0)
#define U3D_SSUSB_HW_SUB_ID (SSUSB_SIFSLV_IPPC_BASE + 0x00A4)
+#define U3D_SSUSB_IP_TRUNK_VERS (U3D_SSUSB_HW_SUB_ID)
+#define U3D_SSUSB_PRB_CTRL0 (SSUSB_SIFSLV_IPPC_BASE + 0x00B0)
+#define U3D_SSUSB_PRB_CTRL1 (SSUSB_SIFSLV_IPPC_BASE + 0x00B4)
+#define U3D_SSUSB_PRB_CTRL2 (SSUSB_SIFSLV_IPPC_BASE + 0x00B8)
+#define U3D_SSUSB_PRB_CTRL3 (SSUSB_SIFSLV_IPPC_BASE + 0x00BC)
+#define U3D_SSUSB_PRB_CTRL4 (SSUSB_SIFSLV_IPPC_BASE + 0x00C0)
+#define U3D_SSUSB_PRB_CTRL5 (SSUSB_SIFSLV_IPPC_BASE + 0x00C4)
#define U3D_SSUSB_IP_SPARE0 (SSUSB_SIFSLV_IPPC_BASE + 0x00C8)
/*---------------- SSUSB_SIFSLV_IPPC FIELD DEFINITION ----------------*/
/* U3D_SSUSB_DEV_RST_CTRL */
#define SSUSB_DEV_SW_RST BIT(0)
+/* U3D_SSUSB_IP_TRUNK_VERS */
+#define IP_TRUNK_VERS(x) (((x) >> 16) & 0xffff)
+
#endif /* _SSUSB_HW_REGS_H_ */
#include "mtu3.h"
#include "mtu3_dr.h"
+#include "mtu3_debug.h"
/* u2-port0 should be powered on and enabled; */
int ssusb_check_clocks(struct ssusb_mtk *ssusb, u32 ex_clks)
mtu3_setbits(ssusb->ippc_base, U3D_SSUSB_IP_PW_CTRL2, SSUSB_IP_DEV_PDN);
}
-/* ignore the error if the clock does not exist */
-static struct clk *get_optional_clk(struct device *dev, const char *id)
-{
- struct clk *opt_clk;
-
- opt_clk = devm_clk_get(dev, id);
- /* ignore error number except EPROBE_DEFER */
- if (IS_ERR(opt_clk) && (PTR_ERR(opt_clk) != -EPROBE_DEFER))
- opt_clk = NULL;
-
- return opt_clk;
-}
-
static int get_ssusb_rscs(struct platform_device *pdev, struct ssusb_mtk *ssusb)
{
struct device_node *node = pdev->dev.of_node;
struct otg_switch_mtk *otg_sx = &ssusb->otg_switch;
struct device *dev = &pdev->dev;
- struct regulator *vbus;
struct resource *res;
int i;
int ret;
- ssusb->vusb33 = devm_regulator_get(&pdev->dev, "vusb33");
+ ssusb->vusb33 = devm_regulator_get(dev, "vusb33");
if (IS_ERR(ssusb->vusb33)) {
dev_err(dev, "failed to get vusb33\n");
return PTR_ERR(ssusb->vusb33);
return PTR_ERR(ssusb->sys_clk);
}
- ssusb->ref_clk = get_optional_clk(dev, "ref_ck");
+ ssusb->ref_clk = devm_clk_get_optional(dev, "ref_ck");
if (IS_ERR(ssusb->ref_clk))
return PTR_ERR(ssusb->ref_clk);
- ssusb->mcu_clk = get_optional_clk(dev, "mcu_ck");
+ ssusb->mcu_clk = devm_clk_get_optional(dev, "mcu_ck");
if (IS_ERR(ssusb->mcu_clk))
return PTR_ERR(ssusb->mcu_clk);
- ssusb->dma_clk = get_optional_clk(dev, "dma_ck");
+ ssusb->dma_clk = devm_clk_get_optional(dev, "dma_ck");
if (IS_ERR(ssusb->dma_clk))
return PTR_ERR(ssusb->dma_clk);
ssusb->dr_mode = USB_DR_MODE_OTG;
if (ssusb->dr_mode == USB_DR_MODE_PERIPHERAL)
- return 0;
+ goto out;
/* if host role is supported */
ret = ssusb_wakeup_of_property_parse(ssusb, node);
of_property_read_u32(node, "mediatek,u3p-dis-msk",
&ssusb->u3p_dis_msk);
- vbus = devm_regulator_get(&pdev->dev, "vbus");
- if (IS_ERR(vbus)) {
+ otg_sx->vbus = devm_regulator_get(dev, "vbus");
+ if (IS_ERR(otg_sx->vbus)) {
dev_err(dev, "failed to get vbus\n");
- return PTR_ERR(vbus);
+ return PTR_ERR(otg_sx->vbus);
}
- otg_sx->vbus = vbus;
if (ssusb->dr_mode == USB_DR_MODE_HOST)
- return 0;
+ goto out;
/* if dual-role mode is supported */
otg_sx->is_u3_drd = of_property_read_bool(node, "mediatek,usb3-drd");
}
}
+out:
dev_info(dev, "dr_mode: %d, is_u3_dr: %d, u3p_dis_msk: %x, drd: %s\n",
ssusb->dr_mode, otg_sx->is_u3_drd, ssusb->u3p_dis_msk,
otg_sx->manual_drd_enabled ? "manual" : "auto");
if (ret)
return ret;
+ ssusb_debugfs_create_root(ssusb);
+
/* enable power domain */
pm_runtime_enable(dev);
pm_runtime_get_sync(dev);
goto gadget_exit;
}
- ssusb_otg_switch_init(ssusb);
+ ret = ssusb_otg_switch_init(ssusb);
+ if (ret) {
+ dev_err(dev, "failed to initialize switch\n");
+ goto host_exit;
+ }
break;
default:
dev_err(dev, "unsupported mode: %d\n", ssusb->dr_mode);
return 0;
+host_exit:
+ ssusb_host_exit(ssusb);
gadget_exit:
ssusb_gadget_exit(ssusb);
comm_exit:
comm_init_err:
pm_runtime_put_sync(dev);
pm_runtime_disable(dev);
+ ssusb_debugfs_remove_root(ssusb);
return ret;
}
ssusb_rscs_exit(ssusb);
pm_runtime_put_sync(&pdev->dev);
pm_runtime_disable(&pdev->dev);
+ ssusb_debugfs_remove_root(ssusb);
return 0;
}
#include <linux/iopoll.h>
#include "mtu3.h"
+#include "mtu3_trace.h"
#define QMU_CHECKSUM_LEN 16
#define GPD_FLAGS_HWO BIT(0)
#define GPD_FLAGS_BDP BIT(1)
#define GPD_FLAGS_BPS BIT(2)
+#define GPD_FLAGS_ZLP BIT(6)
#define GPD_FLAGS_IOC BIT(7)
-
-#define GPD_EXT_FLAG_ZLP BIT(5)
-#define GPD_EXT_NGP(x) (((x) & 0xf) << 4)
-#define GPD_EXT_BUF(x) (((x) & 0xf) << 0)
+#define GET_GPD_HWO(gpd) (le32_to_cpu((gpd)->dw0_info) & GPD_FLAGS_HWO)
+
+#define GPD_RX_BUF_LEN_OG(x) (((x) & 0xffff) << 16)
+#define GPD_RX_BUF_LEN_EL(x) (((x) & 0xfffff) << 12)
+#define GPD_RX_BUF_LEN(mtu, x) \
+({ \
+ typeof(x) x_ = (x); \
+ ((mtu)->gen2cp) ? GPD_RX_BUF_LEN_EL(x_) : GPD_RX_BUF_LEN_OG(x_); \
+})
+
+#define GPD_DATA_LEN_OG(x) ((x) & 0xffff)
+#define GPD_DATA_LEN_EL(x) ((x) & 0xfffff)
+#define GPD_DATA_LEN(mtu, x) \
+({ \
+ typeof(x) x_ = (x); \
+ ((mtu)->gen2cp) ? GPD_DATA_LEN_EL(x_) : GPD_DATA_LEN_OG(x_); \
+})
+
+#define GPD_EXT_FLAG_ZLP BIT(29)
+#define GPD_EXT_NGP_OG(x) (((x) & 0xf) << 20)
+#define GPD_EXT_BUF_OG(x) (((x) & 0xf) << 16)
+#define GPD_EXT_NGP_EL(x) (((x) & 0xf) << 28)
+#define GPD_EXT_BUF_EL(x) (((x) & 0xf) << 24)
+#define GPD_EXT_NGP(mtu, x) \
+({ \
+ typeof(x) x_ = (x); \
+ ((mtu)->gen2cp) ? GPD_EXT_NGP_EL(x_) : GPD_EXT_NGP_OG(x_); \
+})
+
+#define GPD_EXT_BUF(mtu, x) \
+({ \
+ typeof(x) x_ = (x); \
+ ((mtu)->gen2cp) ? GPD_EXT_BUF_EL(x_) : GPD_EXT_BUF_OG(x_); \
+})
#define HILO_GEN64(hi, lo) (((u64)(hi) << 32) + (lo))
#define HILO_DMA(hi, lo) \
struct qmu_gpd *gpd = ring->start;
if (gpd) {
- gpd->flag &= ~GPD_FLAGS_HWO;
+ gpd->dw0_info &= cpu_to_le32(~GPD_FLAGS_HWO);
gpd_ring_init(ring, gpd);
}
}
struct mtu3_gpd_ring *ring = &mep->gpd_ring;
struct qmu_gpd *gpd = ring->enqueue;
struct usb_request *req = &mreq->request;
+ struct mtu3 *mtu = mep->mtu;
dma_addr_t enq_dma;
- u16 ext_addr;
-
- /* set all fields to zero as default value */
- memset(gpd, 0, sizeof(*gpd));
+ u32 ext_addr;
+ gpd->dw0_info = 0; /* SW own it */
gpd->buffer = cpu_to_le32(lower_32_bits(req->dma));
- ext_addr = GPD_EXT_BUF(upper_32_bits(req->dma));
- gpd->buf_len = cpu_to_le16(req->length);
- gpd->flag |= GPD_FLAGS_IOC;
+ ext_addr = GPD_EXT_BUF(mtu, upper_32_bits(req->dma));
+ gpd->dw3_info = cpu_to_le32(GPD_DATA_LEN(mtu, req->length));
/* get the next GPD */
enq = advance_enq_gpd(ring);
dev_dbg(mep->mtu->dev, "TX-EP%d queue gpd=%p, enq=%p, qdma=%pad\n",
mep->epnum, gpd, enq, &enq_dma);
- enq->flag &= ~GPD_FLAGS_HWO;
+ enq->dw0_info &= cpu_to_le32(~GPD_FLAGS_HWO);
gpd->next_gpd = cpu_to_le32(lower_32_bits(enq_dma));
- ext_addr |= GPD_EXT_NGP(upper_32_bits(enq_dma));
- gpd->tx_ext_addr = cpu_to_le16(ext_addr);
-
- if (req->zero)
- gpd->ext_flag |= GPD_EXT_FLAG_ZLP;
+ ext_addr |= GPD_EXT_NGP(mtu, upper_32_bits(enq_dma));
+ gpd->dw0_info = cpu_to_le32(ext_addr);
+
+ if (req->zero) {
+ if (mtu->gen2cp)
+ gpd->dw0_info |= cpu_to_le32(GPD_FLAGS_ZLP);
+ else
+ gpd->dw3_info |= cpu_to_le32(GPD_EXT_FLAG_ZLP);
+ }
- gpd->flag |= GPD_FLAGS_HWO;
+ gpd->dw0_info |= cpu_to_le32(GPD_FLAGS_IOC | GPD_FLAGS_HWO);
mreq->gpd = gpd;
+ trace_mtu3_prepare_gpd(mep, gpd);
return 0;
}
struct mtu3_gpd_ring *ring = &mep->gpd_ring;
struct qmu_gpd *gpd = ring->enqueue;
struct usb_request *req = &mreq->request;
+ struct mtu3 *mtu = mep->mtu;
dma_addr_t enq_dma;
- u16 ext_addr;
-
- /* set all fields to zero as default value */
- memset(gpd, 0, sizeof(*gpd));
+ u32 ext_addr;
+ gpd->dw0_info = 0; /* SW own it */
gpd->buffer = cpu_to_le32(lower_32_bits(req->dma));
- ext_addr = GPD_EXT_BUF(upper_32_bits(req->dma));
- gpd->data_buf_len = cpu_to_le16(req->length);
- gpd->flag |= GPD_FLAGS_IOC;
+ ext_addr = GPD_EXT_BUF(mtu, upper_32_bits(req->dma));
+ gpd->dw0_info = cpu_to_le32(GPD_RX_BUF_LEN(mtu, req->length));
/* get the next GPD */
enq = advance_enq_gpd(ring);
dev_dbg(mep->mtu->dev, "RX-EP%d queue gpd=%p, enq=%p, qdma=%pad\n",
mep->epnum, gpd, enq, &enq_dma);
- enq->flag &= ~GPD_FLAGS_HWO;
+ enq->dw0_info &= cpu_to_le32(~GPD_FLAGS_HWO);
gpd->next_gpd = cpu_to_le32(lower_32_bits(enq_dma));
- ext_addr |= GPD_EXT_NGP(upper_32_bits(enq_dma));
- gpd->rx_ext_addr = cpu_to_le16(ext_addr);
- gpd->flag |= GPD_FLAGS_HWO;
+ ext_addr |= GPD_EXT_NGP(mtu, upper_32_bits(enq_dma));
+ gpd->dw3_info = cpu_to_le32(ext_addr);
+ gpd->dw0_info |= cpu_to_le32(GPD_FLAGS_IOC | GPD_FLAGS_HWO);
mreq->gpd = gpd;
+ trace_mtu3_prepare_gpd(mep, gpd);
return 0;
}
struct mtu3_gpd_ring *ring = &mep->gpd_ring;
void __iomem *mbase = mtu->mac_base;
struct qmu_gpd *gpd_current = NULL;
- struct usb_request *req = NULL;
struct mtu3_request *mreq;
dma_addr_t cur_gpd_dma;
u32 txcsr = 0;
int ret;
mreq = next_request(mep);
- if (mreq && mreq->request.length == 0)
- req = &mreq->request;
- else
+ if (mreq && mreq->request.length != 0)
return;
cur_gpd_dma = read_txq_cur_addr(mbase, epnum);
gpd_current = gpd_dma_to_virt(ring, cur_gpd_dma);
- if (le16_to_cpu(gpd_current->buf_len) != 0) {
+ if (GPD_DATA_LEN(mtu, le32_to_cpu(gpd_current->dw3_info)) != 0) {
dev_err(mtu->dev, "TX EP%d buffer length error(!=0)\n", epnum);
return;
}
- dev_dbg(mtu->dev, "%s send ZLP for req=%p\n", __func__, req);
+ dev_dbg(mtu->dev, "%s send ZLP for req=%p\n", __func__, mreq);
+ trace_mtu3_zlp_exp_gpd(mep, gpd_current);
mtu3_clrbits(mbase, MU3D_EP_TXCR0(mep->epnum), TX_DMAREQEN);
mtu3_setbits(mbase, MU3D_EP_TXCR0(mep->epnum), TX_TXPKTRDY);
/* by pass the current GDP */
- gpd_current->flag |= GPD_FLAGS_BPS;
- gpd_current->flag |= GPD_FLAGS_HWO;
+ gpd_current->dw0_info |= cpu_to_le32(GPD_FLAGS_BPS | GPD_FLAGS_HWO);
/*enable DMAREQEN, switch back to QMU mode */
mtu3_setbits(mbase, MU3D_EP_TXCR0(mep->epnum), TX_DMAREQEN);
dev_dbg(mtu->dev, "%s EP%d, last=%p, current=%p, enq=%p\n",
__func__, epnum, gpd, gpd_current, ring->enqueue);
- while (gpd != gpd_current && !(gpd->flag & GPD_FLAGS_HWO)) {
+ while (gpd != gpd_current && !GET_GPD_HWO(gpd)) {
mreq = next_request(mep);
}
request = &mreq->request;
- request->actual = le16_to_cpu(gpd->buf_len);
+ request->actual = GPD_DATA_LEN(mtu, le32_to_cpu(gpd->dw3_info));
+ trace_mtu3_complete_gpd(mep, gpd);
mtu3_req_complete(mep, request, 0);
gpd = advance_deq_gpd(ring);
dev_dbg(mtu->dev, "%s EP%d, last=%p, current=%p, enq=%p\n",
__func__, epnum, gpd, gpd_current, ring->enqueue);
- while (gpd != gpd_current && !(gpd->flag & GPD_FLAGS_HWO)) {
+ while (gpd != gpd_current && !GET_GPD_HWO(gpd)) {
mreq = next_request(mep);
}
req = &mreq->request;
- req->actual = le16_to_cpu(gpd->buf_len);
+ req->actual = GPD_DATA_LEN(mtu, le32_to_cpu(gpd->dw3_info));
+ trace_mtu3_complete_gpd(mep, gpd);
mtu3_req_complete(mep, req, 0);
gpd = advance_deq_gpd(ring);
dev_dbg(mtu->dev, "=== QMUdone[tx=%x, rx=%x] QMUexp[%x] ===\n",
(qmu_done_status & 0xFFFF), qmu_done_status >> 16,
qmu_status);
+ trace_mtu3_qmu_isr(qmu_done_status, qmu_status);
if (qmu_done_status)
qmu_done_isr(mtu, qmu_done_status);
#define QMU_GPD_RING_SIZE (MAX_GPD_NUM * QMU_GPD_SIZE)
#define GPD_BUF_SIZE 65532
+#define GPD_BUF_SIZE_EL 1048572
void mtu3_qmu_stop(struct mtu3_ep *mep);
int mtu3_qmu_start(struct mtu3_ep *mep);
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/**
+ * mtu3_trace.c - trace support
+ *
+ * Copyright (C) 2019 MediaTek Inc.
+ *
+ * Author: Chunfeng Yun <chunfeng.yun@mediatek.com>
+ */
+
+#define CREATE_TRACE_POINTS
+#include "mtu3_trace.h"
+
+void mtu3_dbg_trace(struct device *dev, const char *fmt, ...)
+{
+ struct va_format vaf;
+ va_list args;
+
+ va_start(args, fmt);
+ vaf.fmt = fmt;
+ vaf.va = &args;
+ trace_mtu3_log(dev, &vaf);
+ va_end(args);
+}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/**
+ * mtu3_trace.h - trace support
+ *
+ * Copyright (C) 2019 MediaTek Inc.
+ *
+ * Author: Chunfeng Yun <chunfeng.yun@mediatek.com>
+ */
+
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM mtu3
+
+#if !defined(__MTU3_TRACE_H__) || defined(TRACE_HEADER_MULTI_READ)
+#define __MTU3_TRACE_H__
+
+#include <linux/types.h>
+#include <linux/tracepoint.h>
+
+#include "mtu3.h"
+
+#define MTU3_MSG_MAX 256
+
+TRACE_EVENT(mtu3_log,
+ TP_PROTO(struct device *dev, struct va_format *vaf),
+ TP_ARGS(dev, vaf),
+ TP_STRUCT__entry(
+ __string(name, dev_name(dev))
+ __dynamic_array(char, msg, MTU3_MSG_MAX)
+ ),
+ TP_fast_assign(
+ __assign_str(name, dev_name(dev));
+ vsnprintf(__get_str(msg), MTU3_MSG_MAX, vaf->fmt, *vaf->va);
+ ),
+ TP_printk("%s: %s", __get_str(name), __get_str(msg))
+);
+
+TRACE_EVENT(mtu3_u3_ltssm_isr,
+ TP_PROTO(u32 intr),
+ TP_ARGS(intr),
+ TP_STRUCT__entry(
+ __field(u32, intr)
+ ),
+ TP_fast_assign(
+ __entry->intr = intr;
+ ),
+ TP_printk("(%08x) %s %s %s %s %s %s", __entry->intr,
+ __entry->intr & HOT_RST_INTR ? "HOT_RST" : "",
+ __entry->intr & WARM_RST_INTR ? "WARM_RST" : "",
+ __entry->intr & ENTER_U3_INTR ? "ENT_U3" : "",
+ __entry->intr & EXIT_U3_INTR ? "EXIT_U3" : "",
+ __entry->intr & VBUS_RISE_INTR ? "VBUS_RISE" : "",
+ __entry->intr & VBUS_FALL_INTR ? "VBUS_FALL" : ""
+ )
+);
+
+TRACE_EVENT(mtu3_u2_common_isr,
+ TP_PROTO(u32 intr),
+ TP_ARGS(intr),
+ TP_STRUCT__entry(
+ __field(u32, intr)
+ ),
+ TP_fast_assign(
+ __entry->intr = intr;
+ ),
+ TP_printk("(%08x) %s %s %s", __entry->intr,
+ __entry->intr & SUSPEND_INTR ? "SUSPEND" : "",
+ __entry->intr & RESUME_INTR ? "RESUME" : "",
+ __entry->intr & RESET_INTR ? "RESET" : ""
+ )
+);
+
+TRACE_EVENT(mtu3_qmu_isr,
+ TP_PROTO(u32 done_intr, u32 exp_intr),
+ TP_ARGS(done_intr, exp_intr),
+ TP_STRUCT__entry(
+ __field(u32, done_intr)
+ __field(u32, exp_intr)
+ ),
+ TP_fast_assign(
+ __entry->done_intr = done_intr;
+ __entry->exp_intr = exp_intr;
+ ),
+ TP_printk("done (tx %04x, rx %04x), exp (%08x)",
+ __entry->done_intr & 0xffff,
+ __entry->done_intr >> 16,
+ __entry->exp_intr
+ )
+);
+
+DECLARE_EVENT_CLASS(mtu3_log_setup,
+ TP_PROTO(struct usb_ctrlrequest *setup),
+ TP_ARGS(setup),
+ TP_STRUCT__entry(
+ __field(__u8, bRequestType)
+ __field(__u8, bRequest)
+ __field(__u16, wValue)
+ __field(__u16, wIndex)
+ __field(__u16, wLength)
+ ),
+ TP_fast_assign(
+ __entry->bRequestType = setup->bRequestType;
+ __entry->bRequest = setup->bRequest;
+ __entry->wValue = le16_to_cpu(setup->wValue);
+ __entry->wIndex = le16_to_cpu(setup->wIndex);
+ __entry->wLength = le16_to_cpu(setup->wLength);
+ ),
+ TP_printk("setup - %02x %02x %04x %04x %04x",
+ __entry->bRequestType, __entry->bRequest,
+ __entry->wValue, __entry->wIndex, __entry->wLength
+ )
+);
+
+DEFINE_EVENT(mtu3_log_setup, mtu3_handle_setup,
+ TP_PROTO(struct usb_ctrlrequest *setup),
+ TP_ARGS(setup)
+);
+
+DECLARE_EVENT_CLASS(mtu3_log_request,
+ TP_PROTO(struct mtu3_request *mreq),
+ TP_ARGS(mreq),
+ TP_STRUCT__entry(
+ __string(name, mreq->mep->name)
+ __field(struct mtu3_request *, mreq)
+ __field(struct qmu_gpd *, gpd)
+ __field(unsigned int, actual)
+ __field(unsigned int, length)
+ __field(int, status)
+ __field(int, zero)
+ __field(int, no_interrupt)
+ ),
+ TP_fast_assign(
+ __assign_str(name, mreq->mep->name);
+ __entry->mreq = mreq;
+ __entry->gpd = mreq->gpd;
+ __entry->actual = mreq->request.actual;
+ __entry->length = mreq->request.length;
+ __entry->status = mreq->request.status;
+ __entry->zero = mreq->request.zero;
+ __entry->no_interrupt = mreq->request.no_interrupt;
+ ),
+ TP_printk("%s: req %p gpd %p len %u/%u %s%s --> %d",
+ __get_str(name), __entry->mreq, __entry->gpd,
+ __entry->actual, __entry->length,
+ __entry->zero ? "Z" : "z",
+ __entry->no_interrupt ? "i" : "I",
+ __entry->status
+ )
+);
+
+DEFINE_EVENT(mtu3_log_request, mtu3_alloc_request,
+ TP_PROTO(struct mtu3_request *req),
+ TP_ARGS(req)
+);
+
+DEFINE_EVENT(mtu3_log_request, mtu3_free_request,
+ TP_PROTO(struct mtu3_request *req),
+ TP_ARGS(req)
+);
+
+DEFINE_EVENT(mtu3_log_request, mtu3_gadget_queue,
+ TP_PROTO(struct mtu3_request *req),
+ TP_ARGS(req)
+);
+
+DEFINE_EVENT(mtu3_log_request, mtu3_gadget_dequeue,
+ TP_PROTO(struct mtu3_request *req),
+ TP_ARGS(req)
+);
+
+DEFINE_EVENT(mtu3_log_request, mtu3_req_complete,
+ TP_PROTO(struct mtu3_request *req),
+ TP_ARGS(req)
+);
+
+DECLARE_EVENT_CLASS(mtu3_log_gpd,
+ TP_PROTO(struct mtu3_ep *mep, struct qmu_gpd *gpd),
+ TP_ARGS(mep, gpd),
+ TP_STRUCT__entry(
+ __string(name, mep->name)
+ __field(struct qmu_gpd *, gpd)
+ __field(u32, dw0)
+ __field(u32, dw1)
+ __field(u32, dw2)
+ __field(u32, dw3)
+ ),
+ TP_fast_assign(
+ __assign_str(name, mep->name);
+ __entry->gpd = gpd;
+ __entry->dw0 = le32_to_cpu(gpd->dw0_info);
+ __entry->dw1 = le32_to_cpu(gpd->next_gpd);
+ __entry->dw2 = le32_to_cpu(gpd->buffer);
+ __entry->dw3 = le32_to_cpu(gpd->dw3_info);
+ ),
+ TP_printk("%s: gpd %p - %08x %08x %08x %08x",
+ __get_str(name), __entry->gpd,
+ __entry->dw0, __entry->dw1,
+ __entry->dw2, __entry->dw3
+ )
+);
+
+DEFINE_EVENT(mtu3_log_gpd, mtu3_prepare_gpd,
+ TP_PROTO(struct mtu3_ep *mep, struct qmu_gpd *gpd),
+ TP_ARGS(mep, gpd)
+);
+
+DEFINE_EVENT(mtu3_log_gpd, mtu3_complete_gpd,
+ TP_PROTO(struct mtu3_ep *mep, struct qmu_gpd *gpd),
+ TP_ARGS(mep, gpd)
+);
+
+DEFINE_EVENT(mtu3_log_gpd, mtu3_zlp_exp_gpd,
+ TP_PROTO(struct mtu3_ep *mep, struct qmu_gpd *gpd),
+ TP_ARGS(mep, gpd)
+);
+
+DECLARE_EVENT_CLASS(mtu3_log_ep,
+ TP_PROTO(struct mtu3_ep *mep),
+ TP_ARGS(mep),
+ TP_STRUCT__entry(
+ __string(name, mep->name)
+ __field(unsigned int, type)
+ __field(unsigned int, slot)
+ __field(unsigned int, maxp)
+ __field(unsigned int, mult)
+ __field(unsigned int, maxburst)
+ __field(unsigned int, flags)
+ __field(unsigned int, direction)
+ __field(struct mtu3_gpd_ring *, gpd_ring)
+ ),
+ TP_fast_assign(
+ __assign_str(name, mep->name);
+ __entry->type = mep->type;
+ __entry->slot = mep->slot;
+ __entry->maxp = mep->ep.maxpacket;
+ __entry->mult = mep->ep.mult;
+ __entry->maxburst = mep->ep.maxburst;
+ __entry->flags = mep->flags;
+ __entry->direction = mep->is_in;
+ __entry->gpd_ring = &mep->gpd_ring;
+ ),
+ TP_printk("%s: type %d maxp %d slot %d mult %d burst %d ring %p/%pad flags %c:%c%c%c:%c",
+ __get_str(name), __entry->type,
+ __entry->maxp, __entry->slot,
+ __entry->mult, __entry->maxburst,
+ __entry->gpd_ring, &__entry->gpd_ring->dma,
+ __entry->flags & MTU3_EP_ENABLED ? 'E' : 'e',
+ __entry->flags & MTU3_EP_STALL ? 'S' : 's',
+ __entry->flags & MTU3_EP_WEDGE ? 'W' : 'w',
+ __entry->flags & MTU3_EP_BUSY ? 'B' : 'b',
+ __entry->direction ? '<' : '>'
+ )
+);
+
+DEFINE_EVENT(mtu3_log_ep, mtu3_gadget_ep_enable,
+ TP_PROTO(struct mtu3_ep *mep),
+ TP_ARGS(mep)
+);
+
+DEFINE_EVENT(mtu3_log_ep, mtu3_gadget_ep_disable,
+ TP_PROTO(struct mtu3_ep *mep),
+ TP_ARGS(mep)
+);
+
+DEFINE_EVENT(mtu3_log_ep, mtu3_gadget_ep_set_halt,
+ TP_PROTO(struct mtu3_ep *mep),
+ TP_ARGS(mep)
+);
+
+#endif /* __MTU3_TRACE_H__ */
+
+/* this part has to be here */
+
+#undef TRACE_INCLUDE_PATH
+#define TRACE_INCLUDE_PATH .
+
+#undef TRACE_INCLUDE_FILE
+#define TRACE_INCLUDE_FILE mtu3_trace
+
+#include <trace/define_trace.h>
depends on NOP_USB_XCEIV
depends on PHY_SUN4I_USB
depends on EXTCON
- depends on GENERIC_PHY
+ select GENERIC_PHY
select SUNXI_SRAM
config USB_MUSB_DAVINCI
static int jz4740_musb_init(struct musb *musb)
{
- usb_phy_generic_register();
- musb->xceiv = usb_get_phy(USB_PHY_TYPE_USB2);
+ struct device *dev = musb->controller->parent;
+
+ if (dev->of_node)
+ musb->xceiv = devm_usb_get_phy_by_phandle(dev, "phys", 0);
+ else
+ musb->xceiv = devm_usb_get_phy(dev, USB_PHY_TYPE_USB2);
if (IS_ERR(musb->xceiv)) {
- pr_err("HS UDC: no transceiver configured\n");
+ dev_err(dev, "No transceiver configured\n");
return PTR_ERR(musb->xceiv);
}
return 0;
}
-static int jz4740_musb_exit(struct musb *musb)
-{
- usb_put_phy(musb->xceiv);
-
- return 0;
-}
-
/*
* DMA has not been confirmed to work with CONFIG_USB_INVENTRA_DMA,
* so let's not set up the dma function pointers yet.
.quirks = MUSB_DMA_INVENTRA | MUSB_INDEXED_EP,
.fifo_mode = 2,
.init = jz4740_musb_init,
- .exit = jz4740_musb_exit,
};
static int jz4740_probe(struct platform_device *pdev)
struct jz4740_glue *glue = platform_get_drvdata(pdev);
platform_device_unregister(glue->musb);
- usb_phy_generic_unregister(pdev);
clk_disable_unprepare(glue->clk);
return 0;
} else {
musb->is_multipoint = 0;
type = "";
-#ifndef CONFIG_USB_OTG_BLACKLIST_HUB
- pr_err("%s: kernel must blacklist external hubs\n",
- musb_driver_name);
-#endif
+ if (IS_ENABLED(CONFIG_USB) &&
+ !IS_ENABLED(CONFIG_USB_OTG_BLACKLIST_HUB)) {
+ pr_err("%s: kernel must blacklist external hubs\n",
+ musb_driver_name);
+ }
}
/* log release info */
static void dsps_musb_enable(struct musb *musb)
{
struct device *dev = musb->controller;
- struct platform_device *pdev = to_platform_device(dev->parent);
- struct dsps_glue *glue = platform_get_drvdata(pdev);
+ struct dsps_glue *glue = dev_get_drvdata(dev->parent);
const struct dsps_musb_wrapper *wrp = glue->wrp;
void __iomem *reg_base = musb->ctrl_base;
u32 epmask, coremask;
static void dsps_musb_disable(struct musb *musb)
{
struct device *dev = musb->controller;
- struct platform_device *pdev = to_platform_device(dev->parent);
- struct dsps_glue *glue = platform_get_drvdata(pdev);
+ struct dsps_glue *glue = dev_get_drvdata(dev->parent);
const struct dsps_musb_wrapper *wrp = glue->wrp;
void __iomem *reg_base = musb->ctrl_base;
omap2430_low_level_exit(musb);
+ phy_power_off(musb->phy);
+ phy_exit(musb->phy);
+
return 0;
}
if (!musb)
return 0;
+ phy_init(musb->phy);
+ phy_power_on(musb->phy);
+
omap2430_low_level_init(musb);
musb_writel(musb->mregs, OTG_INTERFSEL,
musb->context.otg_interfsel);
return 0;
}
-static void ark3116_init_termios(struct tty_struct *tty)
-{
- struct ktermios *termios = &tty->termios;
- *termios = tty_std_termios;
- termios->c_cflag = B9600 | CS8
- | CREAD | HUPCL | CLOCAL;
- termios->c_ispeed = 9600;
- termios->c_ospeed = 9600;
-}
-
static void ark3116_set_termios(struct tty_struct *tty,
struct usb_serial_port *port,
struct ktermios *old_termios)
.port_probe = ark3116_port_probe,
.port_remove = ark3116_port_remove,
.set_termios = ark3116_set_termios,
- .init_termios = ark3116_init_termios,
.get_serial = ark3116_get_serial_info,
.tiocmget = ark3116_tiocmget,
.tiocmset = ark3116_tiocmset,
int write_urb_interval; /* interval to use for write urb */
int read_urb_interval; /* interval to use for read urb */
int comm_is_ok; /* true if communication is (still) ok */
- int termios_initialized;
__u8 line_control; /* holds dtr / rts value */
__u8 current_status; /* received from last read - info on dsr,cts,cd,ri,etc */
__u8 current_config; /* stores the current configuration byte */
int get_cfg_unsafe; /* If true, the CYPRESS_GET_CONFIG is unsafe */
int baud_rate; /* stores current baud rate in
integer form */
- int isthrottled; /* if throttled, discard reads */
char prev_status; /* used for TIOCMIWAIT */
- /* we pass a pointer to this as the argument sent to
- cypress_set_termios old_termios */
- struct ktermios tmp_termios; /* stores the old termios settings */
};
/* function prototypes for the Cypress USB to serial device */
const unsigned char *buf, int count);
static void cypress_send(struct usb_serial_port *port);
static int cypress_write_room(struct tty_struct *tty);
+static void cypress_earthmate_init_termios(struct tty_struct *tty);
static void cypress_set_termios(struct tty_struct *tty,
struct usb_serial_port *port, struct ktermios *old);
static int cypress_tiocmget(struct tty_struct *tty);
.dtr_rts = cypress_dtr_rts,
.write = cypress_write,
.write_room = cypress_write_room,
+ .init_termios = cypress_earthmate_init_termios,
.set_termios = cypress_set_termios,
.tiocmget = cypress_tiocmget,
.tiocmset = cypress_tiocmset,
priv->cmd_ctrl = 0;
priv->line_control = 0;
- priv->termios_initialized = 0;
priv->rx_flags = 0;
/* Default packet format setting is determined by packet size.
Anything with a size larger then 9 must have a separate
cypress_send(port);
if (tty)
- cypress_set_termios(tty, port, &priv->tmp_termios);
+ cypress_set_termios(tty, port, NULL);
/* setup the port and start reading from the device */
usb_fill_int_urb(port->interrupt_in_urb, serial->dev,
return cypress_write(tty, port, NULL, 0);
}
+static void cypress_earthmate_init_termios(struct tty_struct *tty)
+{
+ tty_encode_baud_rate(tty, 4800, 4800);
+}
+
static void cypress_set_termios(struct tty_struct *tty,
struct usb_serial_port *port, struct ktermios *old_termios)
{
__u8 oldlines;
int linechange = 0;
- spin_lock_irqsave(&priv->lock, flags);
- /* We can't clean this one up as we don't know the device type
- early enough */
- if (!priv->termios_initialized) {
- if (priv->chiptype == CT_EARTHMATE) {
- tty->termios = tty_std_termios;
- tty->termios.c_cflag = B4800 | CS8 | CREAD | HUPCL |
- CLOCAL;
- tty->termios.c_ispeed = 4800;
- tty->termios.c_ospeed = 4800;
- } else if (priv->chiptype == CT_CYPHIDCOM) {
- tty->termios = tty_std_termios;
- tty->termios.c_cflag = B9600 | CS8 | CREAD | HUPCL |
- CLOCAL;
- tty->termios.c_ispeed = 9600;
- tty->termios.c_ospeed = 9600;
- } else if (priv->chiptype == CT_CA42V2) {
- tty->termios = tty_std_termios;
- tty->termios.c_cflag = B9600 | CS8 | CREAD | HUPCL |
- CLOCAL;
- tty->termios.c_ispeed = 9600;
- tty->termios.c_ospeed = 9600;
- }
- priv->termios_initialized = 1;
- }
- spin_unlock_irqrestore(&priv->lock, flags);
-
/* Unsupported features need clearing */
tty->termios.c_cflag &= ~(CMSPAR|CRTSCTS);
cflag = tty->termios.c_cflag;
- /* check if there are new settings */
- if (old_termios) {
- spin_lock_irqsave(&priv->lock, flags);
- priv->tmp_termios = tty->termios;
- spin_unlock_irqrestore(&priv->lock, flags);
- }
-
/* set number of data bits, parity, stop bits */
/* when parity is disabled the parity type bit is ignored */
ret = usb_submit_urb(oob_port->write_urb, GFP_ATOMIC);
if (ret == 0) {
oob_priv->dp_write_urb_in_use = 1;
- port_priv->dp_modem_signals =
- (port_priv->dp_modem_signals&~(TIOCM_DTR|TIOCM_RTS))
- | (modem_signals&(TIOCM_DTR|TIOCM_RTS));
+ port_priv->dp_modem_signals &= ~(TIOCM_DTR | TIOCM_RTS);
+ port_priv->dp_modem_signals |=
+ modem_signals & (TIOCM_DTR | TIOCM_RTS);
}
spin_unlock(&port_priv->dp_port_lock);
spin_unlock_irqrestore(&oob_priv->dp_port_lock, flags);
/* set parity */
tty->termios.c_cflag &= ~CMSPAR;
- if ((cflag&(PARENB|PARODD)) != (old_cflag&(PARENB|PARODD))) {
- if (cflag&PARENB) {
- if (cflag&PARODD)
+ if ((cflag & (PARENB | PARODD)) != (old_cflag & (PARENB | PARODD))) {
+ if (cflag & PARENB) {
+ if (cflag & PARODD)
arg = DIGI_PARITY_ODD;
else
arg = DIGI_PARITY_EVEN;
buf[i++] = 0;
}
/* set word size */
- if ((cflag&CSIZE) != (old_cflag&CSIZE)) {
+ if ((cflag & CSIZE) != (old_cflag & CSIZE)) {
arg = -1;
- switch (cflag&CSIZE) {
+ switch (cflag & CSIZE) {
case CS5: arg = DIGI_WORD_SIZE_5; break;
case CS6: arg = DIGI_WORD_SIZE_6; break;
case CS7: arg = DIGI_WORD_SIZE_7; break;
default:
dev_dbg(dev,
"digi_set_termios: can't handle word size %d\n",
- (cflag&CSIZE));
+ cflag & CSIZE);
break;
}
}
/* set stop bits */
- if ((cflag&CSTOPB) != (old_cflag&CSTOPB)) {
+ if ((cflag & CSTOPB) != (old_cflag & CSTOPB)) {
- if ((cflag&CSTOPB))
+ if ((cflag & CSTOPB))
arg = DIGI_STOP_BITS_2;
else
arg = DIGI_STOP_BITS_1;
}
/* set input flow control */
- if ((iflag&IXOFF) != (old_iflag&IXOFF)
- || (cflag&CRTSCTS) != (old_cflag&CRTSCTS)) {
+ if ((iflag & IXOFF) != (old_iflag & IXOFF) ||
+ (cflag & CRTSCTS) != (old_cflag & CRTSCTS)) {
arg = 0;
- if (iflag&IXOFF)
+ if (iflag & IXOFF)
arg |= DIGI_INPUT_FLOW_CONTROL_XON_XOFF;
else
arg &= ~DIGI_INPUT_FLOW_CONTROL_XON_XOFF;
- if (cflag&CRTSCTS) {
+ if (cflag & CRTSCTS) {
arg |= DIGI_INPUT_FLOW_CONTROL_RTS;
/* On USB-4 it is necessary to assert RTS prior */
}
/* set output flow control */
- if ((iflag & IXON) != (old_iflag & IXON)
- || (cflag & CRTSCTS) != (old_cflag & CRTSCTS)) {
+ if ((iflag & IXON) != (old_iflag & IXON) ||
+ (cflag & CRTSCTS) != (old_cflag & CRTSCTS)) {
arg = 0;
if (iflag & IXON)
arg |= DIGI_OUTPUT_FLOW_CONTROL_XON_XOFF;
else
arg &= ~DIGI_OUTPUT_FLOW_CONTROL_XON_XOFF;
- if (cflag & CRTSCTS) {
+ if (cflag & CRTSCTS)
arg |= DIGI_OUTPUT_FLOW_CONTROL_CTS;
- } else {
+ else
arg &= ~DIGI_OUTPUT_FLOW_CONTROL_CTS;
- }
buf[i++] = DIGI_CMD_SET_OUTPUT_FLOW_CONTROL;
buf[i++] = priv->dp_port_num;
static void digi_dtr_rts(struct usb_serial_port *port, int on)
{
/* Adjust DTR and RTS */
- digi_set_modem_signals(port, on * (TIOCM_DTR|TIOCM_RTS), 1);
+ digi_set_modem_signals(port, on * (TIOCM_DTR | TIOCM_RTS), 1);
}
static int digi_open(struct tty_struct *tty, struct usb_serial_port *port)
MODULE_DEVICE_TABLE(usb, id_table);
/* Maximum baudrate for F81232 */
-#define F81232_MAX_BAUDRATE 115200
+#define F81232_MAX_BAUDRATE 1500000
+#define F81232_DEF_BAUDRATE 9600
/* USB Control EP parameter */
#define F81232_REGISTER_REQUEST 0xa0
#define FIFO_CONTROL_REGISTER (0x02 + SERIAL_BASE_ADDRESS)
#define LINE_CONTROL_REGISTER (0x03 + SERIAL_BASE_ADDRESS)
#define MODEM_CONTROL_REGISTER (0x04 + SERIAL_BASE_ADDRESS)
+#define LINE_STATUS_REGISTER (0x05 + SERIAL_BASE_ADDRESS)
#define MODEM_STATUS_REGISTER (0x06 + SERIAL_BASE_ADDRESS)
+/*
+ * F81232 Clock registers (106h)
+ *
+ * Bit1-0: Clock source selector
+ * 00: 1.846MHz.
+ * 01: 18.46MHz.
+ * 10: 24MHz.
+ * 11: 14.77MHz.
+ */
+#define F81232_CLK_REGISTER 0x106
+#define F81232_CLK_1_846_MHZ 0
+#define F81232_CLK_18_46_MHZ BIT(0)
+#define F81232_CLK_24_MHZ BIT(1)
+#define F81232_CLK_14_77_MHZ (BIT(1) | BIT(0))
+#define F81232_CLK_MASK GENMASK(1, 0)
+
struct f81232_private {
struct mutex lock;
u8 modem_control;
u8 modem_status;
+ u8 shadow_lcr;
+ speed_t baud_base;
+ struct work_struct lsr_work;
struct work_struct interrupt_work;
struct usb_serial_port *port;
};
-static int calc_baud_divisor(speed_t baudrate)
+static u32 const baudrate_table[] = { 115200, 921600, 1152000, 1500000 };
+static u8 const clock_table[] = { F81232_CLK_1_846_MHZ, F81232_CLK_14_77_MHZ,
+ F81232_CLK_18_46_MHZ, F81232_CLK_24_MHZ };
+
+static int calc_baud_divisor(speed_t baudrate, speed_t clockrate)
{
- return DIV_ROUND_CLOSEST(F81232_MAX_BAUDRATE, baudrate);
+ if (!baudrate)
+ return 0;
+
+ return DIV_ROUND_CLOSEST(clockrate, baudrate);
}
static int f81232_get_register(struct usb_serial_port *port, u16 reg, u8 *val)
return status;
}
+static int f81232_set_mask_register(struct usb_serial_port *port, u16 reg,
+ u8 mask, u8 val)
+{
+ int status;
+ u8 tmp;
+
+ status = f81232_get_register(port, reg, &tmp);
+ if (status)
+ return status;
+
+ tmp = (tmp & ~mask) | (val & mask);
+
+ return f81232_set_register(port, reg, tmp);
+}
+
static void f81232_read_msr(struct usb_serial_port *port)
{
int status;
static void f81232_process_read_urb(struct urb *urb)
{
struct usb_serial_port *port = urb->context;
+ struct f81232_private *priv = usb_get_serial_port_data(port);
unsigned char *data = urb->transfer_buffer;
char tty_flag;
unsigned int i;
if (lsr & UART_LSR_OE) {
port->icount.overrun++;
+ schedule_work(&priv->lsr_work);
tty_insert_flip_char(&port->port, 0,
TTY_OVERRUN);
}
static void f81232_break_ctl(struct tty_struct *tty, int break_state)
{
- /* FIXME - Stubbed out for now */
+ struct usb_serial_port *port = tty->driver_data;
+ struct f81232_private *priv = usb_get_serial_port_data(port);
+ int status;
- /*
- * break_state = -1 to turn on break, and 0 to turn off break
- * see drivers/char/tty_io.c to see it used.
- * last_set_data_urb_value NEVER has the break bit set in it.
- */
+ mutex_lock(&priv->lock);
+
+ if (break_state)
+ priv->shadow_lcr |= UART_LCR_SBC;
+ else
+ priv->shadow_lcr &= ~UART_LCR_SBC;
+
+ status = f81232_set_register(port, LINE_CONTROL_REGISTER,
+ priv->shadow_lcr);
+ if (status)
+ dev_err(&port->dev, "set break failed: %d\n", status);
+
+ mutex_unlock(&priv->lock);
}
-static void f81232_set_baudrate(struct usb_serial_port *port, speed_t baudrate)
+static int f81232_find_clk(speed_t baudrate)
{
+ int idx;
+
+ for (idx = 0; idx < ARRAY_SIZE(baudrate_table); ++idx) {
+ if (baudrate <= baudrate_table[idx] &&
+ baudrate_table[idx] % baudrate == 0)
+ return idx;
+ }
+
+ return -EINVAL;
+}
+
+static void f81232_set_baudrate(struct tty_struct *tty,
+ struct usb_serial_port *port, speed_t baudrate,
+ speed_t old_baudrate)
+{
+ struct f81232_private *priv = usb_get_serial_port_data(port);
u8 lcr;
int divisor;
int status = 0;
+ int i;
+ int idx;
+ speed_t baud_list[] = { baudrate, old_baudrate, F81232_DEF_BAUDRATE };
+
+ for (i = 0; i < ARRAY_SIZE(baud_list); ++i) {
+ idx = f81232_find_clk(baud_list[i]);
+ if (idx >= 0) {
+ baudrate = baud_list[i];
+ tty_encode_baud_rate(tty, baudrate, baudrate);
+ break;
+ }
+ }
- divisor = calc_baud_divisor(baudrate);
+ if (idx < 0)
+ return;
+
+ priv->baud_base = baudrate_table[idx];
+ divisor = calc_baud_divisor(baudrate, priv->baud_base);
+
+ status = f81232_set_mask_register(port, F81232_CLK_REGISTER,
+ F81232_CLK_MASK, clock_table[idx]);
+ if (status) {
+ dev_err(&port->dev, "%s failed to set CLK_REG: %d\n",
+ __func__, status);
+ return;
+ }
status = f81232_get_register(port, LINE_CONTROL_REGISTER,
&lcr); /* get LCR */
static void f81232_set_termios(struct tty_struct *tty,
struct usb_serial_port *port, struct ktermios *old_termios)
{
+ struct f81232_private *priv = usb_get_serial_port_data(port);
u8 new_lcr = 0;
int status = 0;
speed_t baudrate;
+ speed_t old_baud;
/* Don't change anything if nothing has changed */
if (old_termios && !tty_termios_hw_change(&tty->termios, old_termios))
baudrate = tty_get_baud_rate(tty);
if (baudrate > 0) {
- if (baudrate > F81232_MAX_BAUDRATE) {
- baudrate = F81232_MAX_BAUDRATE;
- tty_encode_baud_rate(tty, baudrate, baudrate);
- }
- f81232_set_baudrate(port, baudrate);
+ if (old_termios)
+ old_baud = tty_termios_baud_rate(old_termios);
+ else
+ old_baud = F81232_DEF_BAUDRATE;
+
+ f81232_set_baudrate(tty, port, baudrate, old_baud);
}
if (C_PARENB(tty)) {
break;
}
+ mutex_lock(&priv->lock);
+
+ new_lcr |= (priv->shadow_lcr & UART_LCR_SBC);
status = f81232_set_register(port, LINE_CONTROL_REGISTER, new_lcr);
if (status) {
dev_err(&port->dev, "%s failed to set LCR: %d\n",
__func__, status);
}
+
+ priv->shadow_lcr = new_lcr;
+
+ mutex_unlock(&priv->lock);
}
static int f81232_tiocmget(struct tty_struct *tty)
static void f81232_close(struct usb_serial_port *port)
{
+ struct f81232_private *port_priv = usb_get_serial_port_data(port);
+
f81232_port_disable(port);
usb_serial_generic_close(port);
usb_kill_urb(port->interrupt_in_urb);
+ flush_work(&port_priv->interrupt_work);
+ flush_work(&port_priv->lsr_work);
}
static void f81232_dtr_rts(struct usb_serial_port *port, int on)
struct serial_struct *ss)
{
struct usb_serial_port *port = tty->driver_data;
+ struct f81232_private *priv = usb_get_serial_port_data(port);
ss->type = PORT_16550A;
ss->line = port->minor;
ss->port = port->port_number;
- ss->baud_base = F81232_MAX_BAUDRATE;
+ ss->baud_base = priv->baud_base;
return 0;
}
f81232_read_msr(priv->port);
}
+static void f81232_lsr_worker(struct work_struct *work)
+{
+ struct f81232_private *priv;
+ struct usb_serial_port *port;
+ int status;
+ u8 tmp;
+
+ priv = container_of(work, struct f81232_private, lsr_work);
+ port = priv->port;
+
+ status = f81232_get_register(port, LINE_STATUS_REGISTER, &tmp);
+ if (status)
+ dev_warn(&port->dev, "read LSR failed: %d\n", status);
+}
+
static int f81232_port_probe(struct usb_serial_port *port)
{
struct f81232_private *priv;
mutex_init(&priv->lock);
INIT_WORK(&priv->interrupt_work, f81232_interrupt_work);
+ INIT_WORK(&priv->lsr_work, f81232_lsr_worker);
usb_set_serial_port_data(port, priv);
return 0;
}
+static int f81232_suspend(struct usb_serial *serial, pm_message_t message)
+{
+ struct usb_serial_port *port = serial->port[0];
+ struct f81232_private *port_priv = usb_get_serial_port_data(port);
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(port->read_urbs); ++i)
+ usb_kill_urb(port->read_urbs[i]);
+
+ usb_kill_urb(port->interrupt_in_urb);
+
+ if (port_priv) {
+ flush_work(&port_priv->interrupt_work);
+ flush_work(&port_priv->lsr_work);
+ }
+
+ return 0;
+}
+
+static int f81232_resume(struct usb_serial *serial)
+{
+ struct usb_serial_port *port = serial->port[0];
+ int result;
+
+ if (tty_port_initialized(&port->port)) {
+ result = usb_submit_urb(port->interrupt_in_urb, GFP_NOIO);
+ if (result) {
+ dev_err(&port->dev, "submit interrupt urb failed: %d\n",
+ result);
+ return result;
+ }
+ }
+
+ return usb_serial_generic_resume(serial);
+}
+
static struct usb_serial_driver f81232_device = {
.driver = {
.owner = THIS_MODULE,
.read_int_callback = f81232_read_int_callback,
.port_probe = f81232_port_probe,
.port_remove = f81232_port_remove,
+ .suspend = f81232_suspend,
+ .resume = f81232_resume,
};
static struct usb_serial_driver * const serial_drivers[] = {
int usb_serial_generic_open(struct tty_struct *tty, struct usb_serial_port *port)
{
int result = 0;
- unsigned long flags;
- spin_lock_irqsave(&port->lock, flags);
- port->throttled = 0;
- port->throttle_req = 0;
- spin_unlock_irqrestore(&port->lock, flags);
+ clear_bit(USB_SERIAL_THROTTLED, &port->flags);
if (port->bulk_in_size)
result = usb_serial_generic_submit_read_urbs(port, GFP_KERNEL);
{
struct usb_serial_port *port = urb->context;
unsigned char *data = urb->transfer_buffer;
- unsigned long flags;
+ bool stopped = false;
int status = urb->status;
int i;
if (urb == port->read_urbs[i])
break;
}
- set_bit(i, &port->read_urbs_free);
dev_dbg(&port->dev, "%s - urb %d, len %d\n", __func__, i,
urb->actual_length);
switch (status) {
case 0:
+ usb_serial_debug_data(&port->dev, __func__, urb->actual_length,
+ data);
+ port->serial->type->process_read_urb(urb);
break;
case -ENOENT:
case -ECONNRESET:
case -ESHUTDOWN:
dev_dbg(&port->dev, "%s - urb stopped: %d\n",
__func__, status);
- return;
+ stopped = true;
+ break;
case -EPIPE:
dev_err(&port->dev, "%s - urb stopped: %d\n",
__func__, status);
- return;
+ stopped = true;
+ break;
default:
dev_dbg(&port->dev, "%s - nonzero urb status: %d\n",
__func__, status);
- goto resubmit;
+ break;
}
- usb_serial_debug_data(&port->dev, __func__, urb->actual_length, data);
- port->serial->type->process_read_urb(urb);
+ /*
+ * Make sure URB processing is done before marking as free to avoid
+ * racing with unthrottle() on another CPU. Matches the barriers
+ * implied by the test_and_clear_bit() in
+ * usb_serial_generic_submit_read_urb().
+ */
+ smp_mb__before_atomic();
+ set_bit(i, &port->read_urbs_free);
+ /*
+ * Make sure URB is marked as free before checking the throttled flag
+ * to avoid racing with unthrottle() on another CPU. Matches the
+ * smp_mb() in unthrottle().
+ */
+ smp_mb__after_atomic();
-resubmit:
- /* Throttle the device if requested by tty */
- spin_lock_irqsave(&port->lock, flags);
- port->throttled = port->throttle_req;
- if (!port->throttled) {
- spin_unlock_irqrestore(&port->lock, flags);
- usb_serial_generic_submit_read_urb(port, i, GFP_ATOMIC);
- } else {
- spin_unlock_irqrestore(&port->lock, flags);
- }
+ if (stopped)
+ return;
+
+ if (test_bit(USB_SERIAL_THROTTLED, &port->flags))
+ return;
+
+ usb_serial_generic_submit_read_urb(port, i, GFP_ATOMIC);
}
EXPORT_SYMBOL_GPL(usb_serial_generic_read_bulk_callback);
default:
dev_err_console(port, "%s - nonzero urb status: %d\n",
__func__, status);
- goto resubmit;
+ break;
}
-resubmit:
usb_serial_generic_write_start(port, GFP_ATOMIC);
usb_serial_port_softint(port);
}
void usb_serial_generic_throttle(struct tty_struct *tty)
{
struct usb_serial_port *port = tty->driver_data;
- unsigned long flags;
- spin_lock_irqsave(&port->lock, flags);
- port->throttle_req = 1;
- spin_unlock_irqrestore(&port->lock, flags);
+ set_bit(USB_SERIAL_THROTTLED, &port->flags);
}
EXPORT_SYMBOL_GPL(usb_serial_generic_throttle);
void usb_serial_generic_unthrottle(struct tty_struct *tty)
{
struct usb_serial_port *port = tty->driver_data;
- int was_throttled;
- spin_lock_irq(&port->lock);
- was_throttled = port->throttled;
- port->throttled = port->throttle_req = 0;
- spin_unlock_irq(&port->lock);
+ clear_bit(USB_SERIAL_THROTTLED, &port->flags);
+
+ /*
+ * Matches the smp_mb__after_atomic() in
+ * usb_serial_generic_read_bulk_callback().
+ */
+ smp_mb();
- if (was_throttled)
- usb_serial_generic_submit_read_urbs(port, GFP_KERNEL);
+ usb_serial_generic_submit_read_urbs(port, GFP_KERNEL);
}
EXPORT_SYMBOL_GPL(usb_serial_generic_unthrottle);
edge_serial->rxState = EXPECT_HDR2;
break;
}
- /* otherwise, drop on through */
+ /* Fall through */
case EXPECT_HDR2:
edge_serial->rxHeader2 = *buffer;
++buffer;
edge_serial->rxHeader2, 0);
edge_serial->rxState = EXPECT_HDR1;
break;
- } else {
- edge_serial->rxPort =
- IOSP_GET_HDR_PORT(edge_serial->rxHeader1);
- edge_serial->rxBytesRemaining =
- IOSP_GET_HDR_DATA_LEN(
- edge_serial->rxHeader1,
- edge_serial->rxHeader2);
- dev_dbg(dev, "%s - Data for Port %u Len %u\n",
- __func__,
- edge_serial->rxPort,
- edge_serial->rxBytesRemaining);
-
- /* ASSERT(DevExt->RxPort < DevExt->NumPorts);
- * ASSERT(DevExt->RxBytesRemaining <
- * IOSP_MAX_DATA_LENGTH);
- */
-
- if (bufferLength == 0) {
- edge_serial->rxState = EXPECT_DATA;
- break;
- }
- /* Else, drop through */
}
+
+ edge_serial->rxPort = IOSP_GET_HDR_PORT(edge_serial->rxHeader1);
+ edge_serial->rxBytesRemaining = IOSP_GET_HDR_DATA_LEN(edge_serial->rxHeader1,
+ edge_serial->rxHeader2);
+ dev_dbg(dev, "%s - Data for Port %u Len %u\n", __func__,
+ edge_serial->rxPort,
+ edge_serial->rxBytesRemaining);
+
+ if (bufferLength == 0) {
+ edge_serial->rxState = EXPECT_DATA;
+ break;
+ }
+ /* Fall through */
case EXPECT_DATA: /* Expect data */
if (bufferLength < edge_serial->rxBytesRemaining) {
rxLen = bufferLength;
static void iuu_init_termios(struct tty_struct *tty)
{
- tty->termios = tty_std_termios;
- tty->termios.c_cflag = CLOCAL | CREAD | CS8 | B9600
- | TIOCM_CTS | CSTOPB | PARENB;
+ tty->termios.c_cflag = B9600 | CS8 | CSTOPB | CREAD | PARENB | CLOCAL;
tty->termios.c_ispeed = 9600;
tty->termios.c_ospeed = 9600;
tty->termios.c_lflag = 0;
static void oti6858_init_termios(struct tty_struct *tty)
{
- tty->termios = tty_std_termios;
- tty->termios.c_cflag = B38400 | CS8 | CREAD | HUPCL | CLOCAL;
- tty->termios.c_ispeed = 38400;
- tty->termios.c_ospeed = 38400;
+ tty_encode_baud_rate(tty, 38400, 38400);
}
static void oti6858_set_termios(struct tty_struct *tty,
#define UART_OVERRUN_ERROR 0x40
#define UART_CTS 0x80
+#define PL2303_FLOWCTRL_MASK 0xf0
+
static void pl2303_set_break(struct usb_serial_port *port, bool enable);
enum pl2303_type {
struct pl2303_type_data {
speed_t max_baud_rate;
unsigned long quirks;
+ unsigned int no_autoxonxoff:1;
};
struct pl2303_serial_private {
static const struct pl2303_type_data pl2303_type_data[TYPE_COUNT] = {
[TYPE_01] = {
- .max_baud_rate = 1228800,
- .quirks = PL2303_QUIRK_LEGACY,
+ .max_baud_rate = 1228800,
+ .quirks = PL2303_QUIRK_LEGACY,
+ .no_autoxonxoff = true,
},
[TYPE_HX] = {
- .max_baud_rate = 12000000,
+ .max_baud_rate = 12000000,
},
};
return 0;
}
+static int pl2303_update_reg(struct usb_serial *serial, u8 reg, u8 mask, u8 val)
+{
+ int ret = 0;
+ u8 *buf;
+
+ buf = kmalloc(1, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ ret = pl2303_vendor_read(serial, reg | 0x80, buf);
+ if (ret)
+ goto out_free;
+
+ *buf &= ~mask;
+ *buf |= val & mask;
+
+ ret = pl2303_vendor_write(serial, reg, *buf);
+out_free:
+ kfree(buf);
+
+ return ret;
+}
+
static int pl2303_probe(struct usb_serial *serial,
const struct usb_device_id *id)
{
return tty_termios_hw_change(a, b) || ixon_change;
}
+static bool pl2303_enable_xonxoff(struct tty_struct *tty, const struct pl2303_type_data *type)
+{
+ if (!I_IXON(tty) || I_IXANY(tty))
+ return false;
+
+ if (START_CHAR(tty) != 0x11 || STOP_CHAR(tty) != 0x13)
+ return false;
+
+ if (type->no_autoxonxoff)
+ return false;
+
+ return true;
+}
+
static void pl2303_set_termios(struct tty_struct *tty,
struct usb_serial_port *port, struct ktermios *old_termios)
{
if (C_CRTSCTS(tty)) {
if (spriv->quirks & PL2303_QUIRK_LEGACY)
- pl2303_vendor_write(serial, 0x0, 0x41);
+ pl2303_update_reg(serial, 0, PL2303_FLOWCTRL_MASK, 0x40);
else
- pl2303_vendor_write(serial, 0x0, 0x61);
- } else if (I_IXON(tty) && !I_IXANY(tty) && START_CHAR(tty) == 0x11 &&
- STOP_CHAR(tty) == 0x13) {
- pl2303_vendor_write(serial, 0x0, 0xc0);
+ pl2303_update_reg(serial, 0, PL2303_FLOWCTRL_MASK, 0x60);
+ } else if (pl2303_enable_xonxoff(tty, spriv->type)) {
+ pl2303_update_reg(serial, 0, PL2303_FLOWCTRL_MASK, 0xc0);
} else {
- pl2303_vendor_write(serial, 0x0, 0x0);
+ pl2303_update_reg(serial, 0, PL2303_FLOWCTRL_MASK, 0);
}
kfree(buf);
static void spcp8x5_init_termios(struct tty_struct *tty)
{
- tty->termios = tty_std_termios;
- tty->termios.c_cflag = B115200 | CS8 | CREAD | HUPCL | CLOCAL;
- tty->termios.c_ispeed = 115200;
- tty->termios.c_ospeed = 115200;
+ tty_encode_baud_rate(tty, 115200, 115200);
}
static void spcp8x5_set_termios(struct tty_struct *tty,
* @driver: the driver (USB in our case)
* @tty: the tty being created
*
- * Create the termios objects for this tty. We use the default
+ * Initialise the termios structure for this tty. We use the default
* USB serial settings but permit them to be overridden by
- * serial->type->init_termios.
+ * serial->type->init_termios on first open.
*
* This is the first place a new tty gets used. Hence this is where we
* acquire references to the usb_serial structure and the driver module,
int idx = tty->index;
struct usb_serial *serial;
struct usb_serial_port *port;
+ bool init_termios;
int retval = -ENODEV;
port = usb_serial_port_get_by_minor(idx);
if (retval)
goto error_get_interface;
+ init_termios = (driver->termios[idx] == NULL);
+
retval = tty_standard_install(driver, tty);
if (retval)
goto error_init_termios;
mutex_unlock(&serial->disc_mutex);
- /* allow the driver to update the settings */
- if (serial->type->init_termios)
+ /* allow the driver to update the initial settings */
+ if (init_termios && serial->type->init_termios)
serial->type->init_termios(tty);
tty->driver_data = port;
static int slave_alloc (struct scsi_device *sdev)
{
struct us_data *us = host_to_us(sdev->host);
+ int maxp;
/*
* Set the INQUIRY transfer length to 36. We don't use any of
sdev->inquiry_len = 36;
/*
- * USB has unusual DMA-alignment requirements: Although the
- * starting address of each scatter-gather element doesn't matter,
- * the length of each element except the last must be divisible
- * by the Bulk maxpacket value. There's currently no way to
- * express this by block-layer constraints, so we'll cop out
- * and simply require addresses to be aligned at 512-byte
- * boundaries. This is okay since most block I/O involves
- * hardware sectors that are multiples of 512 bytes in length,
- * and since host controllers up through USB 2.0 have maxpacket
- * values no larger than 512.
- *
- * But it doesn't suffice for Wireless USB, where Bulk maxpacket
- * values can be as large as 2048. To make that work properly
- * will require changes to the block layer.
+ * USB has unusual scatter-gather requirements: the length of each
+ * scatterlist element except the last must be divisible by the
+ * Bulk maxpacket value. Fortunately this value is always a
+ * power of 2. Inform the block layer about this requirement.
+ */
+ maxp = usb_maxpacket(us->pusb_dev, us->recv_bulk_pipe, 0);
+ blk_queue_virt_boundary(sdev->request_queue, maxp - 1);
+
+ /*
+ * Some host controllers may have alignment requirements.
+ * We'll play it safe by requiring 512-byte alignment always.
*/
blk_queue_update_dma_alignment(sdev->request_queue, (512 - 1));
kfree(swocInfo);
}
complete:
- result = device_create_file(&us->pusb_intf->dev, &dev_attr_truinst);
-
- return 0;
+ return device_create_file(&us->pusb_intf->dev, &dev_attr_truinst);
}
{
struct uas_dev_info *devinfo =
(struct uas_dev_info *)sdev->host->hostdata;
+ int maxp;
sdev->hostdata = devinfo;
/*
- * USB has unusual DMA-alignment requirements: Although the
- * starting address of each scatter-gather element doesn't matter,
- * the length of each element except the last must be divisible
- * by the Bulk maxpacket value. There's currently no way to
- * express this by block-layer constraints, so we'll cop out
- * and simply require addresses to be aligned at 512-byte
- * boundaries. This is okay since most block I/O involves
- * hardware sectors that are multiples of 512 bytes in length,
- * and since host controllers up through USB 2.0 have maxpacket
- * values no larger than 512.
+ * We have two requirements here. We must satisfy the requirements
+ * of the physical HC and the demands of the protocol, as we
+ * definitely want no additional memory allocation in this path
+ * ruling out using bounce buffers.
*
- * But it doesn't suffice for Wireless USB, where Bulk maxpacket
- * values can be as large as 2048. To make that work properly
- * will require changes to the block layer.
+ * For a transmission on USB to continue we must never send
+ * a package that is smaller than maxpacket. Hence the length of each
+ * scatterlist element except the last must be divisible by the
+ * Bulk maxpacket value.
+ * If the HC does not ensure that through SG,
+ * the upper layer must do that. We must assume nothing
+ * about the capabilities off the HC, so we use the most
+ * pessimistic requirement.
+ */
+
+ maxp = usb_maxpacket(devinfo->udev, devinfo->data_in_pipe, 0);
+ blk_queue_virt_boundary(sdev->request_queue, maxp - 1);
+
+ /*
+ * The protocol has no requirements on alignment in the strict sense.
+ * Controllers may or may not have alignment restrictions.
+ * As this is not exported, we use an extremely conservative guess.
*/
blk_queue_update_dma_alignment(sdev->request_queue, (512 - 1));
To compile this driver as a module, choose M here: the
module will be called typec_displayport.
+config TYPEC_NVIDIA_ALTMODE
+ tristate "NVIDIA Alternate Mode driver"
+ depends on TYPEC_DP_ALTMODE
+ help
+ Latest NVIDIA GPUs support VirtualLink devices. Select this
+ to enable support for VirtualLink devices with NVIDIA GPUs.
+
+ To compile this driver as a module, choose M here: the
+ module will be called typec_displayport.
+
endmenu
obj-$(CONFIG_TYPEC_DP_ALTMODE) += typec_displayport.o
typec_displayport-y := displayport.o
+obj-$(CONFIG_TYPEC_NVIDIA_ALTMODE) += typec_nvidia.o
+typec_nvidia-y := nvidia.o
#include <linux/usb/pd_vdo.h>
#include <linux/usb/typec_dp.h>
-#define DP_HEADER(cmd) (VDO(USB_TYPEC_DP_SID, 1, cmd) | \
+#define DP_HEADER(_dp, cmd) (VDO((_dp)->alt->svid, 1, cmd) | \
VDO_OPOS(USB_TYPEC_DP_MODE))
enum {
if (dp->data.status & DP_STATUS_PREFER_MULTI_FUNC &&
pin_assign & DP_PIN_ASSIGN_MULTI_FUNC_MASK)
pin_assign &= DP_PIN_ASSIGN_MULTI_FUNC_MASK;
- else
+ else if (pin_assign & DP_PIN_ASSIGN_DP_ONLY_MASK)
pin_assign &= DP_PIN_ASSIGN_DP_ONLY_MASK;
if (!pin_assign)
static int dp_altmode_configure_vdm(struct dp_altmode *dp, u32 conf)
{
- u32 header = DP_HEADER(DP_CMD_CONFIGURE);
+ u32 header = DP_HEADER(dp, DP_CMD_CONFIGURE);
int ret;
ret = typec_altmode_notify(dp->alt, TYPEC_STATE_SAFE, &dp->data);
dev_err(&dp->alt->dev, "failed to enter mode\n");
break;
case DP_STATE_UPDATE:
- header = DP_HEADER(DP_CMD_STATUS_UPDATE);
+ header = DP_HEADER(dp, DP_CMD_STATUS_UPDATE);
vdo = 1;
ret = typec_altmode_vdm(dp->alt, header, &vdo, 2);
if (ret)
.attrs = dp_altmode_attrs,
};
-static int dp_altmode_probe(struct typec_altmode *alt)
+int dp_altmode_probe(struct typec_altmode *alt)
{
const struct typec_altmode *port = typec_altmode_get_partner(alt);
struct dp_altmode *dp;
return 0;
}
+EXPORT_SYMBOL_GPL(dp_altmode_probe);
-static void dp_altmode_remove(struct typec_altmode *alt)
+void dp_altmode_remove(struct typec_altmode *alt)
{
struct dp_altmode *dp = typec_altmode_get_drvdata(alt);
sysfs_remove_group(&alt->dev.kobj, &dp_altmode_group);
cancel_work_sync(&dp->work);
}
+EXPORT_SYMBOL_GPL(dp_altmode_remove);
static const struct typec_device_id dp_typec_id[] = {
{ USB_TYPEC_DP_SID, USB_TYPEC_DP_MODE },
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0 */
+#if IS_ENABLED(CONFIG_TYPEC_DP_ALTMODE)
+int dp_altmode_probe(struct typec_altmode *alt);
+void dp_altmode_remove(struct typec_altmode *alt);
+#else
+int dp_altmode_probe(struct typec_altmode *alt) { return -ENOTSUPP; }
+void dp_altmode_remove(struct typec_altmode *alt) { }
+#endif /* CONFIG_TYPEC_DP_ALTMODE */
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019 NVIDIA Corporation. All rights reserved.
+ *
+ * NVIDIA USB Type-C Alt Mode Driver
+ */
+#include <linux/module.h>
+#include <linux/usb/typec_altmode.h>
+#include <linux/usb/typec_dp.h>
+#include "displayport.h"
+
+static int nvidia_altmode_probe(struct typec_altmode *alt)
+{
+ if (alt->svid == USB_TYPEC_NVIDIA_VLINK_SID)
+ return dp_altmode_probe(alt);
+ else
+ return -ENOTSUPP;
+}
+
+static void nvidia_altmode_remove(struct typec_altmode *alt)
+{
+ if (alt->svid == USB_TYPEC_NVIDIA_VLINK_SID)
+ dp_altmode_remove(alt);
+}
+
+static const struct typec_device_id nvidia_typec_id[] = {
+ { USB_TYPEC_NVIDIA_VLINK_SID, TYPEC_ANY_MODE },
+ { },
+};
+MODULE_DEVICE_TABLE(typec, nvidia_typec_id);
+
+static struct typec_altmode_driver nvidia_altmode_driver = {
+ .id_table = nvidia_typec_id,
+ .probe = nvidia_altmode_probe,
+ .remove = nvidia_altmode_remove,
+ .driver = {
+ .name = "typec_nvidia",
+ .owner = THIS_MODULE,
+ },
+};
+module_typec_altmode_driver(nvidia_altmode_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("NVIDIA USB Type-C Alt Mode Driver");
switch (state) {
case TYPEC_STATE_SAFE:
- new_conf = PI3USB30532_CONF_OPEN;
+ new_conf = (new_conf & PI3USB30532_CONF_SWAP) |
+ PI3USB30532_CONF_OPEN;
break;
case TYPEC_STATE_USB:
new_conf = (new_conf & PI3USB30532_CONF_SWAP) |
#include <linux/sched/clock.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
+#include <linux/spinlock.h>
#include <linux/string.h>
#include <linux/types.h>
#include <linux/usb/typec.h>
struct regulator *vbus;
+ spinlock_t irq_lock;
+ struct work_struct irq_work;
+ bool irq_suspended;
+ bool irq_while_suspended;
int gpio_int_n;
int gpio_int_n_irq;
struct extcon_dev *extcon;
struct workqueue_struct *wq;
struct delayed_work bc_lvl_handler;
- atomic_t pm_suspend;
- atomic_t i2c_busy;
-
/* lock for sharing chip states */
struct mutex lock;
bool intr_comp_chng;
/* port status */
- bool pull_up;
bool vconn_on;
bool vbus_on;
bool charge_on;
*/
#ifdef CONFIG_DEBUG_FS
-
static bool fusb302_log_full(struct fusb302_chip *chip)
{
return chip->logbuffer_tail ==
(chip->logbuffer_head + 1) % LOG_BUFFER_ENTRIES;
}
+__printf(2, 0)
static void _fusb302_log(struct fusb302_chip *chip, const char *fmt,
va_list args)
{
#endif
-#define FUSB302_RESUME_RETRY 10
-#define FUSB302_RESUME_RETRY_SLEEP 50
-
-static bool fusb302_is_suspended(struct fusb302_chip *chip)
-{
- int retry_cnt;
-
- for (retry_cnt = 0; retry_cnt < FUSB302_RESUME_RETRY; retry_cnt++) {
- if (atomic_read(&chip->pm_suspend)) {
- dev_err(chip->dev, "i2c: pm suspend, retry %d/%d\n",
- retry_cnt + 1, FUSB302_RESUME_RETRY);
- msleep(FUSB302_RESUME_RETRY_SLEEP);
- } else {
- return false;
- }
- }
-
- return true;
-}
-
static int fusb302_i2c_write(struct fusb302_chip *chip,
u8 address, u8 data)
{
int ret = 0;
- atomic_set(&chip->i2c_busy, 1);
-
- if (fusb302_is_suspended(chip)) {
- atomic_set(&chip->i2c_busy, 0);
- return -ETIMEDOUT;
- }
-
ret = i2c_smbus_write_byte_data(chip->i2c_client, address, data);
if (ret < 0)
fusb302_log(chip, "cannot write 0x%02x to 0x%02x, ret=%d",
data, address, ret);
- atomic_set(&chip->i2c_busy, 0);
return ret;
}
if (length <= 0)
return ret;
- atomic_set(&chip->i2c_busy, 1);
-
- if (fusb302_is_suspended(chip)) {
- atomic_set(&chip->i2c_busy, 0);
- return -ETIMEDOUT;
- }
ret = i2c_smbus_write_i2c_block_data(chip->i2c_client, address,
length, data);
if (ret < 0)
fusb302_log(chip, "cannot block write 0x%02x, len=%d, ret=%d",
address, length, ret);
- atomic_set(&chip->i2c_busy, 0);
return ret;
}
{
int ret = 0;
- atomic_set(&chip->i2c_busy, 1);
-
- if (fusb302_is_suspended(chip)) {
- atomic_set(&chip->i2c_busy, 0);
- return -ETIMEDOUT;
- }
-
ret = i2c_smbus_read_byte_data(chip->i2c_client, address);
*data = (u8)ret;
if (ret < 0)
fusb302_log(chip, "cannot read %02x, ret=%d", address, ret);
- atomic_set(&chip->i2c_busy, 0);
return ret;
}
if (length <= 0)
return ret;
- atomic_set(&chip->i2c_busy, 1);
-
- if (fusb302_is_suspended(chip)) {
- atomic_set(&chip->i2c_busy, 0);
- return -ETIMEDOUT;
- }
ret = i2c_smbus_read_i2c_block_data(chip->i2c_client, address,
length, data);
}
done:
- atomic_set(&chip->i2c_busy, 0);
-
return ret;
}
return current_limit;
}
-static int fusb302_set_cc_pull(struct fusb302_chip *chip,
- bool pull_up, bool pull_down)
-{
- int ret = 0;
- u8 data = 0x00;
- u8 mask = FUSB_REG_SWITCHES0_CC1_PU_EN |
- FUSB_REG_SWITCHES0_CC2_PU_EN |
- FUSB_REG_SWITCHES0_CC1_PD_EN |
- FUSB_REG_SWITCHES0_CC2_PD_EN;
-
- if (pull_up)
- data |= (chip->cc_polarity == TYPEC_POLARITY_CC1) ?
- FUSB_REG_SWITCHES0_CC1_PU_EN :
- FUSB_REG_SWITCHES0_CC2_PU_EN;
- if (pull_down)
- data |= FUSB_REG_SWITCHES0_CC1_PD_EN |
- FUSB_REG_SWITCHES0_CC2_PD_EN;
- ret = fusb302_i2c_mask_write(chip, FUSB_REG_SWITCHES0,
- mask, data);
- if (ret < 0)
- return ret;
- chip->pull_up = pull_up;
-
- return ret;
-}
-
static int fusb302_set_src_current(struct fusb302_chip *chip,
enum src_current_status status)
{
return ret;
chip->intr_togdone = false;
} else {
+ /* Datasheet says vconn MUST be off when toggling */
+ WARN(chip->vconn_on, "Vconn is on during toggle start");
/* unmask TOGDONE interrupt */
ret = fusb302_i2c_clear_bits(chip, FUSB_REG_MASKA,
FUSB_REG_MASKA_TOGDONE);
{
struct fusb302_chip *chip = container_of(dev, struct fusb302_chip,
tcpc_dev);
+ u8 switches0_mask = FUSB_REG_SWITCHES0_CC1_PU_EN |
+ FUSB_REG_SWITCHES0_CC2_PU_EN |
+ FUSB_REG_SWITCHES0_CC1_PD_EN |
+ FUSB_REG_SWITCHES0_CC2_PD_EN;
+ u8 rd_mda, switches0_data = 0x00;
int ret = 0;
- bool pull_up, pull_down;
- u8 rd_mda;
- enum toggling_mode mode;
mutex_lock(&chip->lock);
switch (cc) {
case TYPEC_CC_OPEN:
- pull_up = false;
- pull_down = false;
break;
case TYPEC_CC_RD:
- pull_up = false;
- pull_down = true;
+ switches0_data |= FUSB_REG_SWITCHES0_CC1_PD_EN |
+ FUSB_REG_SWITCHES0_CC2_PD_EN;
break;
case TYPEC_CC_RP_DEF:
case TYPEC_CC_RP_1_5:
case TYPEC_CC_RP_3_0:
- pull_up = true;
- pull_down = false;
+ switches0_data |= (chip->cc_polarity == TYPEC_POLARITY_CC1) ?
+ FUSB_REG_SWITCHES0_CC1_PU_EN :
+ FUSB_REG_SWITCHES0_CC2_PU_EN;
break;
default:
fusb302_log(chip, "unsupported cc value %s",
ret = -EINVAL;
goto done;
}
+
+ fusb302_log(chip, "cc := %s", typec_cc_status_name[cc]);
+
ret = fusb302_set_toggling(chip, TOGGLING_MODE_OFF);
if (ret < 0) {
- fusb302_log(chip, "cannot stop toggling, ret=%d", ret);
+ fusb302_log(chip, "cannot set toggling mode, ret=%d", ret);
goto done;
}
- ret = fusb302_set_cc_pull(chip, pull_up, pull_down);
+
+ ret = fusb302_i2c_mask_write(chip, FUSB_REG_SWITCHES0,
+ switches0_mask, switches0_data);
if (ret < 0) {
- fusb302_log(chip,
- "cannot set cc pulling up %s, down %s, ret = %d",
- pull_up ? "True" : "False",
- pull_down ? "True" : "False",
- ret);
+ fusb302_log(chip, "cannot set pull-up/-down, ret = %d", ret);
goto done;
}
/* reset the cc status */
chip->cc1 = TYPEC_CC_OPEN;
chip->cc2 = TYPEC_CC_OPEN;
+
/* adjust current for SRC */
- if (pull_up) {
- ret = fusb302_set_src_current(chip, cc_src_current[cc]);
- if (ret < 0) {
- fusb302_log(chip, "cannot set src current %s, ret=%d",
- typec_cc_status_name[cc], ret);
- goto done;
- }
+ ret = fusb302_set_src_current(chip, cc_src_current[cc]);
+ if (ret < 0) {
+ fusb302_log(chip, "cannot set src current %s, ret=%d",
+ typec_cc_status_name[cc], ret);
+ goto done;
}
+
/* enable/disable interrupts, BC_LVL for SNK and COMP_CHNG for SRC */
- if (pull_up) {
+ switch (cc) {
+ case TYPEC_CC_RP_DEF:
+ case TYPEC_CC_RP_1_5:
+ case TYPEC_CC_RP_3_0:
rd_mda = rd_mda_value[cc_src_current[cc]];
ret = fusb302_i2c_write(chip, FUSB_REG_MEASURE, rd_mda);
if (ret < 0) {
ret);
goto done;
}
- chip->intr_bc_lvl = false;
chip->intr_comp_chng = true;
- }
- if (pull_down) {
+ break;
+ case TYPEC_CC_RD:
ret = fusb302_i2c_mask_write(chip, FUSB_REG_MASK,
FUSB_REG_MASK_BC_LVL |
FUSB_REG_MASK_COMP_CHNG,
goto done;
}
chip->intr_bc_lvl = true;
- chip->intr_comp_chng = false;
- }
- fusb302_log(chip, "cc := %s", typec_cc_status_name[cc]);
-
- /* Enable detection for fixed SNK or SRC only roles */
- switch (cc) {
- case TYPEC_CC_RD:
- mode = TOGGLING_MODE_SNK;
- break;
- case TYPEC_CC_RP_DEF:
- case TYPEC_CC_RP_1_5:
- case TYPEC_CC_RP_3_0:
- mode = TOGGLING_MODE_SRC;
break;
default:
- mode = TOGGLING_MODE_OFF;
break;
}
-
- if (mode != TOGGLING_MODE_OFF) {
- ret = fusb302_set_toggling(chip, mode);
- if (ret < 0)
- fusb302_log(chip,
- "cannot set fixed role toggling mode, ret=%d",
- ret);
- }
done:
mutex_unlock(&chip->lock);
return ret;
}
-static int tcpm_start_drp_toggling(struct tcpc_dev *dev,
- enum typec_cc_status cc)
+static int tcpm_start_toggling(struct tcpc_dev *dev,
+ enum typec_port_type port_type,
+ enum typec_cc_status cc)
{
struct fusb302_chip *chip = container_of(dev, struct fusb302_chip,
tcpc_dev);
+ enum toggling_mode mode = TOGGLING_MODE_OFF;
int ret = 0;
+ switch (port_type) {
+ case TYPEC_PORT_SRC:
+ mode = TOGGLING_MODE_SRC;
+ break;
+ case TYPEC_PORT_SNK:
+ mode = TOGGLING_MODE_SNK;
+ break;
+ case TYPEC_PORT_DRP:
+ mode = TOGGLING_MODE_DRP;
+ break;
+ }
+
mutex_lock(&chip->lock);
ret = fusb302_set_src_current(chip, cc_src_current[cc]);
if (ret < 0) {
typec_cc_status_name[cc], ret);
goto done;
}
- ret = fusb302_set_toggling(chip, TOGGLING_MODE_DRP);
+ ret = fusb302_set_toggling(chip, mode);
if (ret < 0) {
fusb302_log(chip,
"unable to start drp toggling, ret=%d", ret);
fusb302_tcpc_dev->set_vbus = tcpm_set_vbus;
fusb302_tcpc_dev->set_pd_rx = tcpm_set_pd_rx;
fusb302_tcpc_dev->set_roles = tcpm_set_roles;
- fusb302_tcpc_dev->start_drp_toggling = tcpm_start_drp_toggling;
+ fusb302_tcpc_dev->start_toggling = tcpm_start_toggling;
fusb302_tcpc_dev->pd_transmit = tcpm_pd_transmit;
}
[TYPEC_POLARITY_CC2] = "Polarity_CC2",
};
-static int fusb302_set_cc_polarity(struct fusb302_chip *chip,
- enum typec_cc_polarity cc_polarity)
+static int fusb302_set_cc_polarity_and_pull(struct fusb302_chip *chip,
+ enum typec_cc_polarity cc_polarity,
+ bool pull_up, bool pull_down)
{
int ret = 0;
- u8 switches0_mask = FUSB_REG_SWITCHES0_CC1_PU_EN |
- FUSB_REG_SWITCHES0_CC2_PU_EN |
- FUSB_REG_SWITCHES0_VCONN_CC1 |
- FUSB_REG_SWITCHES0_VCONN_CC2 |
- FUSB_REG_SWITCHES0_MEAS_CC1 |
- FUSB_REG_SWITCHES0_MEAS_CC2;
u8 switches0_data = 0x00;
u8 switches1_mask = FUSB_REG_SWITCHES1_TXCC1_EN |
FUSB_REG_SWITCHES1_TXCC2_EN;
u8 switches1_data = 0x00;
+ if (pull_down)
+ switches0_data |= FUSB_REG_SWITCHES0_CC1_PD_EN |
+ FUSB_REG_SWITCHES0_CC2_PD_EN;
+
if (cc_polarity == TYPEC_POLARITY_CC1) {
- switches0_data = FUSB_REG_SWITCHES0_MEAS_CC1;
+ switches0_data |= FUSB_REG_SWITCHES0_MEAS_CC1;
if (chip->vconn_on)
switches0_data |= FUSB_REG_SWITCHES0_VCONN_CC2;
- if (chip->pull_up)
+ if (pull_up)
switches0_data |= FUSB_REG_SWITCHES0_CC1_PU_EN;
switches1_data = FUSB_REG_SWITCHES1_TXCC1_EN;
} else {
- switches0_data = FUSB_REG_SWITCHES0_MEAS_CC2;
+ switches0_data |= FUSB_REG_SWITCHES0_MEAS_CC2;
if (chip->vconn_on)
switches0_data |= FUSB_REG_SWITCHES0_VCONN_CC1;
- if (chip->pull_up)
+ if (pull_up)
switches0_data |= FUSB_REG_SWITCHES0_CC2_PU_EN;
switches1_data = FUSB_REG_SWITCHES1_TXCC2_EN;
}
- ret = fusb302_i2c_mask_write(chip, FUSB_REG_SWITCHES0,
- switches0_mask, switches0_data);
+ ret = fusb302_i2c_write(chip, FUSB_REG_SWITCHES0, switches0_data);
if (ret < 0)
return ret;
ret = fusb302_i2c_mask_write(chip, FUSB_REG_SWITCHES1,
enum typec_cc_polarity cc_polarity;
enum typec_cc_status cc_status_active, cc1, cc2;
- /* set pull_up, pull_down */
- ret = fusb302_set_cc_pull(chip, false, true);
- if (ret < 0) {
- fusb302_log(chip, "cannot set cc to pull down, ret=%d", ret);
- return ret;
- }
- /* set polarity */
+ /* set polarity and pull_up, pull_down */
cc_polarity = (togdone_result == FUSB_REG_STATUS1A_TOGSS_SNK1) ?
TYPEC_POLARITY_CC1 : TYPEC_POLARITY_CC2;
- ret = fusb302_set_cc_polarity(chip, cc_polarity);
+ ret = fusb302_set_cc_polarity_and_pull(chip, cc_polarity, false, true);
if (ret < 0) {
fusb302_log(chip, "cannot set cc polarity %s, ret=%d",
cc_polarity_name[cc_polarity], ret);
return ret;
}
+/* On error returns < 0, otherwise a typec_cc_status value */
+static int fusb302_get_src_cc_status(struct fusb302_chip *chip,
+ enum typec_cc_polarity cc_polarity,
+ enum typec_cc_status *cc)
+{
+ u8 ra_mda = ra_mda_value[chip->src_current_status];
+ u8 rd_mda = rd_mda_value[chip->src_current_status];
+ u8 switches0_data, status0;
+ int ret;
+
+ /* Step 1: Set switches so that we measure the right CC pin */
+ switches0_data = (cc_polarity == TYPEC_POLARITY_CC1) ?
+ FUSB_REG_SWITCHES0_CC1_PU_EN | FUSB_REG_SWITCHES0_MEAS_CC1 :
+ FUSB_REG_SWITCHES0_CC2_PU_EN | FUSB_REG_SWITCHES0_MEAS_CC2;
+ ret = fusb302_i2c_write(chip, FUSB_REG_SWITCHES0, switches0_data);
+ if (ret < 0)
+ return ret;
+
+ fusb302_i2c_read(chip, FUSB_REG_SWITCHES0, &status0);
+ fusb302_log(chip, "get_src_cc_status switches: 0x%0x", status0);
+
+ /* Step 2: Set compararator volt to differentiate between Open and Rd */
+ ret = fusb302_i2c_write(chip, FUSB_REG_MEASURE, rd_mda);
+ if (ret < 0)
+ return ret;
+
+ usleep_range(50, 100);
+ ret = fusb302_i2c_read(chip, FUSB_REG_STATUS0, &status0);
+ if (ret < 0)
+ return ret;
+
+ fusb302_log(chip, "get_src_cc_status rd_mda status0: 0x%0x", status0);
+ if (status0 & FUSB_REG_STATUS0_COMP) {
+ *cc = TYPEC_CC_OPEN;
+ return 0;
+ }
+
+ /* Step 3: Set compararator input to differentiate between Rd and Ra. */
+ ret = fusb302_i2c_write(chip, FUSB_REG_MEASURE, ra_mda);
+ if (ret < 0)
+ return ret;
+
+ usleep_range(50, 100);
+ ret = fusb302_i2c_read(chip, FUSB_REG_STATUS0, &status0);
+ if (ret < 0)
+ return ret;
+
+ fusb302_log(chip, "get_src_cc_status ra_mda status0: 0x%0x", status0);
+ if (status0 & FUSB_REG_STATUS0_COMP)
+ *cc = TYPEC_CC_RD;
+ else
+ *cc = TYPEC_CC_RA;
+
+ return 0;
+}
+
static int fusb302_handle_togdone_src(struct fusb302_chip *chip,
u8 togdone_result)
{
* - set I_COMP interrupt on
*/
int ret = 0;
- u8 status0;
- u8 ra_mda = ra_mda_value[chip->src_current_status];
u8 rd_mda = rd_mda_value[chip->src_current_status];
- bool ra_comp, rd_comp;
+ enum toggling_mode toggling_mode = chip->toggling_mode;
enum typec_cc_polarity cc_polarity;
- enum typec_cc_status cc_status_active, cc1, cc2;
+ enum typec_cc_status cc1, cc2;
- /* set pull_up, pull_down */
- ret = fusb302_set_cc_pull(chip, true, false);
- if (ret < 0) {
- fusb302_log(chip, "cannot set cc to pull up, ret=%d", ret);
+ /*
+ * The toggle-engine will stop in a src state if it sees either Ra or
+ * Rd. Determine the status for both CC pins, starting with the one
+ * where toggling stopped, as that is where the switches point now.
+ */
+ if (togdone_result == FUSB_REG_STATUS1A_TOGSS_SRC1)
+ ret = fusb302_get_src_cc_status(chip, TYPEC_POLARITY_CC1, &cc1);
+ else
+ ret = fusb302_get_src_cc_status(chip, TYPEC_POLARITY_CC2, &cc2);
+ if (ret < 0)
return ret;
- }
- /* set polarity */
- cc_polarity = (togdone_result == FUSB_REG_STATUS1A_TOGSS_SRC1) ?
- TYPEC_POLARITY_CC1 : TYPEC_POLARITY_CC2;
- ret = fusb302_set_cc_polarity(chip, cc_polarity);
+ /* we must turn off toggling before we can measure the other pin */
+ ret = fusb302_set_toggling(chip, TOGGLING_MODE_OFF);
if (ret < 0) {
- fusb302_log(chip, "cannot set cc polarity %s, ret=%d",
- cc_polarity_name[cc_polarity], ret);
+ fusb302_log(chip, "cannot set toggling mode off, ret=%d", ret);
return ret;
}
- /* fusb302_set_cc_polarity() has set the correct measure block */
- ret = fusb302_i2c_write(chip, FUSB_REG_MEASURE, rd_mda);
- if (ret < 0)
- return ret;
- usleep_range(50, 100);
- ret = fusb302_i2c_read(chip, FUSB_REG_STATUS0, &status0);
+ /* get the status of the other pin */
+ if (togdone_result == FUSB_REG_STATUS1A_TOGSS_SRC1)
+ ret = fusb302_get_src_cc_status(chip, TYPEC_POLARITY_CC2, &cc2);
+ else
+ ret = fusb302_get_src_cc_status(chip, TYPEC_POLARITY_CC1, &cc1);
if (ret < 0)
return ret;
- rd_comp = !!(status0 & FUSB_REG_STATUS0_COMP);
- if (!rd_comp) {
- ret = fusb302_i2c_write(chip, FUSB_REG_MEASURE, ra_mda);
- if (ret < 0)
- return ret;
- usleep_range(50, 100);
- ret = fusb302_i2c_read(chip, FUSB_REG_STATUS0, &status0);
- if (ret < 0)
- return ret;
- ra_comp = !!(status0 & FUSB_REG_STATUS0_COMP);
+
+ /* determine polarity based on the status of both pins */
+ if (cc1 == TYPEC_CC_RD &&
+ (cc2 == TYPEC_CC_OPEN || cc2 == TYPEC_CC_RA)) {
+ cc_polarity = TYPEC_POLARITY_CC1;
+ } else if (cc2 == TYPEC_CC_RD &&
+ (cc1 == TYPEC_CC_OPEN || cc1 == TYPEC_CC_RA)) {
+ cc_polarity = TYPEC_POLARITY_CC2;
+ } else {
+ fusb302_log(chip, "unexpected CC status cc1=%s, cc2=%s, restarting toggling",
+ typec_cc_status_name[cc1],
+ typec_cc_status_name[cc2]);
+ return fusb302_set_toggling(chip, toggling_mode);
}
- if (rd_comp)
- cc_status_active = TYPEC_CC_OPEN;
- else if (ra_comp)
- cc_status_active = TYPEC_CC_RD;
- else
- /* Ra is not supported, report as Open */
- cc_status_active = TYPEC_CC_OPEN;
- /* restart toggling if the cc status on the active line is OPEN */
- if (cc_status_active == TYPEC_CC_OPEN) {
- fusb302_log(chip, "restart toggling as CC_OPEN detected");
- ret = fusb302_set_toggling(chip, chip->toggling_mode);
+ /* set polarity and pull_up, pull_down */
+ ret = fusb302_set_cc_polarity_and_pull(chip, cc_polarity, true, false);
+ if (ret < 0) {
+ fusb302_log(chip, "cannot set cc polarity %s, ret=%d",
+ cc_polarity_name[cc_polarity], ret);
return ret;
}
/* update tcpm with the new cc value */
- cc1 = (cc_polarity == TYPEC_POLARITY_CC1) ?
- cc_status_active : TYPEC_CC_OPEN;
- cc2 = (cc_polarity == TYPEC_POLARITY_CC2) ?
- cc_status_active : TYPEC_CC_OPEN;
if ((chip->cc1 != cc1) || (chip->cc2 != cc2)) {
chip->cc1 = cc1;
chip->cc2 = cc2;
tcpm_cc_change(chip->tcpm_port);
}
- /* turn off toggling */
- ret = fusb302_set_toggling(chip, TOGGLING_MODE_OFF);
- if (ret < 0) {
- fusb302_log(chip,
- "cannot set toggling mode off, ret=%d", ret);
- return ret;
- }
/* set MDAC to Rd threshold, and unmask I_COMP for unplug detection */
ret = fusb302_i2c_write(chip, FUSB_REG_MEASURE, rd_mda);
if (ret < 0)
FUSB_REG_MASK_COMP_CHNG);
if (ret < 0) {
fusb302_log(chip,
- "cannot unmask bc_lcl interrupt, ret=%d", ret);
+ "cannot unmask comp_chng interrupt, ret=%d", ret);
return ret;
}
chip->intr_comp_chng = true;
static irqreturn_t fusb302_irq_intn(int irq, void *dev_id)
{
struct fusb302_chip *chip = dev_id;
+ unsigned long flags;
+
+ /* Disable our level triggered IRQ until our irq_work has cleared it */
+ disable_irq_nosync(chip->gpio_int_n_irq);
+
+ spin_lock_irqsave(&chip->irq_lock, flags);
+ if (chip->irq_suspended)
+ chip->irq_while_suspended = true;
+ else
+ schedule_work(&chip->irq_work);
+ spin_unlock_irqrestore(&chip->irq_lock, flags);
+
+ return IRQ_HANDLED;
+}
+
+static void fusb302_irq_work(struct work_struct *work)
+{
+ struct fusb302_chip *chip = container_of(work, struct fusb302_chip,
+ irq_work);
int ret = 0;
u8 interrupt;
u8 interrupta;
fusb302_log(chip, "IRQ: COMP_CHNG, comp=%s",
comp_result ? "true" : "false");
if (comp_result) {
- /* cc level > Rd_threashold, detach */
- if (chip->cc_polarity == TYPEC_POLARITY_CC1)
- chip->cc1 = TYPEC_CC_OPEN;
- else
- chip->cc2 = TYPEC_CC_OPEN;
+ /* cc level > Rd_threshold, detach */
+ chip->cc1 = TYPEC_CC_OPEN;
+ chip->cc2 = TYPEC_CC_OPEN;
tcpm_cc_change(chip->tcpm_port);
}
}
}
done:
mutex_unlock(&chip->lock);
-
- return IRQ_HANDLED;
+ enable_irq(chip->gpio_int_n_irq);
}
static int init_gpio(struct fusb302_chip *chip)
if (!chip->wq)
return -ENOMEM;
+ spin_lock_init(&chip->irq_lock);
+ INIT_WORK(&chip->irq_work, fusb302_irq_work);
INIT_DELAYED_WORK(&chip->bc_lvl_handler, fusb302_bc_lvl_handler_work);
init_tcpc_dev(&chip->tcpc_dev);
goto destroy_workqueue;
}
- ret = devm_request_threaded_irq(chip->dev, chip->gpio_int_n_irq,
- NULL, fusb302_irq_intn,
- IRQF_ONESHOT | IRQF_TRIGGER_LOW,
- "fsc_interrupt_int_n", chip);
+ ret = request_irq(chip->gpio_int_n_irq, fusb302_irq_intn,
+ IRQF_ONESHOT | IRQF_TRIGGER_LOW,
+ "fsc_interrupt_int_n", chip);
if (ret < 0) {
dev_err(dev, "cannot request IRQ for GPIO Int_N, ret=%d", ret);
goto tcpm_unregister_port;
{
struct fusb302_chip *chip = i2c_get_clientdata(client);
+ disable_irq_wake(chip->gpio_int_n_irq);
+ free_irq(chip->gpio_int_n_irq, chip);
+ cancel_work_sync(&chip->irq_work);
+ cancel_delayed_work_sync(&chip->bc_lvl_handler);
tcpm_unregister_port(chip->tcpm_port);
destroy_workqueue(chip->wq);
fusb302_debugfs_exit(chip);
static int fusb302_pm_suspend(struct device *dev)
{
struct fusb302_chip *chip = dev->driver_data;
+ unsigned long flags;
- if (atomic_read(&chip->i2c_busy))
- return -EBUSY;
- atomic_set(&chip->pm_suspend, 1);
+ spin_lock_irqsave(&chip->irq_lock, flags);
+ chip->irq_suspended = true;
+ spin_unlock_irqrestore(&chip->irq_lock, flags);
+ /* Make sure any pending irq_work is finished before the bus suspends */
+ flush_work(&chip->irq_work);
return 0;
}
static int fusb302_pm_resume(struct device *dev)
{
struct fusb302_chip *chip = dev->driver_data;
+ unsigned long flags;
- atomic_set(&chip->pm_suspend, 0);
+ spin_lock_irqsave(&chip->irq_lock, flags);
+ if (chip->irq_while_suspended) {
+ schedule_work(&chip->irq_work);
+ chip->irq_while_suspended = false;
+ }
+ chip->irq_suspended = false;
+ spin_unlock_irqrestore(&chip->irq_lock, flags);
return 0;
}
return 0;
}
-static int tcpci_start_drp_toggling(struct tcpc_dev *tcpc,
- enum typec_cc_status cc)
+static int tcpci_start_toggling(struct tcpc_dev *tcpc,
+ enum typec_port_type port_type,
+ enum typec_cc_status cc)
{
int ret;
struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
unsigned int reg = TCPC_ROLE_CTRL_DRP;
+ if (port_type != TYPEC_PORT_DRP)
+ return -EOPNOTSUPP;
+
/* Handle vendor drp toggling */
if (tcpci->data->start_drp_toggling) {
ret = tcpci->data->start_drp_toggling(tcpci, tcpci->data, cc);
tcpci->tcpc.get_cc = tcpci_get_cc;
tcpci->tcpc.set_polarity = tcpci_set_polarity;
tcpci->tcpc.set_vconn = tcpci_set_vconn;
- tcpci->tcpc.start_drp_toggling = tcpci_start_drp_toggling;
+ tcpci->tcpc.start_toggling = tcpci_start_toggling;
tcpci->tcpc.set_pd_rx = tcpci_set_pd_rx;
tcpci->tcpc.set_roles = tcpci_set_roles;
#define FOREACH_STATE(S) \
S(INVALID_STATE), \
- S(DRP_TOGGLING), \
+ S(TOGGLING), \
S(SRC_UNATTACHED), \
S(SRC_ATTACH_WAIT), \
S(SRC_ATTACHED), \
/* Do not log while disconnected and unattached */
if (tcpm_port_is_disconnected(port) &&
(port->state == SRC_UNATTACHED || port->state == SNK_UNATTACHED ||
- port->state == DRP_TOGGLING))
+ port->state == TOGGLING))
return;
va_start(args, fmt);
return 0;
}
-static bool tcpm_start_drp_toggling(struct tcpm_port *port,
- enum typec_cc_status cc)
+static bool tcpm_start_toggling(struct tcpm_port *port, enum typec_cc_status cc)
{
int ret;
- if (port->tcpc->start_drp_toggling &&
- port->port_type == TYPEC_PORT_DRP) {
- tcpm_log_force(port, "Start DRP toggling");
- ret = port->tcpc->start_drp_toggling(port->tcpc, cc);
- if (!ret)
- return true;
- }
+ if (!port->tcpc->start_toggling)
+ return false;
- return false;
+ tcpm_log_force(port, "Start toggling");
+ ret = port->tcpc->start_toggling(port->tcpc, port->port_type, cc);
+ return ret == 0;
}
static void tcpm_set_cc(struct tcpm_port *port, enum typec_cc_status cc)
port->enter_state = port->state;
switch (port->state) {
- case DRP_TOGGLING:
+ case TOGGLING:
break;
/* SRC states */
case SRC_UNATTACHED:
if (!port->non_pd_role_swap)
tcpm_swap_complete(port, -ENOTCONN);
tcpm_src_detach(port);
- if (tcpm_start_drp_toggling(port, tcpm_rp_cc(port))) {
- tcpm_set_state(port, DRP_TOGGLING, 0);
+ if (tcpm_start_toggling(port, tcpm_rp_cc(port))) {
+ tcpm_set_state(port, TOGGLING, 0);
break;
}
tcpm_set_cc(port, tcpm_rp_cc(port));
tcpm_swap_complete(port, -ENOTCONN);
tcpm_pps_complete(port, -ENOTCONN);
tcpm_snk_detach(port);
- if (tcpm_start_drp_toggling(port, TYPEC_CC_RD)) {
- tcpm_set_state(port, DRP_TOGGLING, 0);
+ if (tcpm_start_toggling(port, TYPEC_CC_RD)) {
+ tcpm_set_state(port, TOGGLING, 0);
break;
}
tcpm_set_cc(port, TYPEC_CC_RD);
: "connected");
switch (port->state) {
- case DRP_TOGGLING:
+ case TOGGLING:
if (tcpm_port_is_debug(port) || tcpm_port_is_audio(port) ||
tcpm_port_is_source(port))
tcpm_set_state(port, SRC_ATTACH_WAIT, 0);
return regmap_write(wcove->regmap, USBC_TXCMD, cmd | USBC_TXCMD_START);
}
-static int wcove_start_drp_toggling(struct tcpc_dev *tcpc,
- enum typec_cc_status cc)
+static int wcove_start_toggling(struct tcpc_dev *tcpc,
+ enum typec_port_type port_type,
+ enum typec_cc_status cc)
{
struct wcove_typec *wcove = tcpc_to_wcove(tcpc);
unsigned int usbc_ctrl;
+ if (port_type != TYPEC_PORT_DRP)
+ return -EOPNOTSUPP;
+
usbc_ctrl = USBC_CONTROL1_MODE_DRP | USBC_CONTROL1_DRPTOGGLE_RANDOM;
switch (cc) {
PDO_VAR(5000, 12000, 3000),
};
-static struct tcpc_config wcove_typec_config = {
- .src_pdo = src_pdo,
- .nr_src_pdo = ARRAY_SIZE(src_pdo),
- .snk_pdo = snk_pdo,
- .nr_snk_pdo = ARRAY_SIZE(snk_pdo),
-
- .operating_snk_mw = 15000,
-
- .type = TYPEC_PORT_DRP,
- .data = TYPEC_PORT_DRD,
- .default_role = TYPEC_SINK,
+static const struct property_entry wcove_props[] = {
+ PROPERTY_ENTRY_STRING("data-role", "dual"),
+ PROPERTY_ENTRY_STRING("power-role", "dual"),
+ PROPERTY_ENTRY_STRING("try-power-role", "sink"),
+ PROPERTY_ENTRY_U32_ARRAY("source-pdos", src_pdo),
+ PROPERTY_ENTRY_U32_ARRAY("sink-pdos", snk_pdo),
+ PROPERTY_ENTRY_U32("op-sink-microwatt", 15000),
+ { }
};
static int wcove_typec_probe(struct platform_device *pdev)
wcove->tcpc.set_polarity = wcove_set_polarity;
wcove->tcpc.set_vconn = wcove_set_vconn;
wcove->tcpc.set_current_limit = wcove_set_current_limit;
- wcove->tcpc.start_drp_toggling = wcove_start_drp_toggling;
+ wcove->tcpc.start_toggling = wcove_start_toggling;
wcove->tcpc.set_pd_rx = wcove_set_pd_rx;
wcove->tcpc.set_roles = wcove_set_roles;
wcove->tcpc.pd_transmit = wcove_pd_transmit;
- wcove->tcpc.config = &wcove_typec_config;
+ wcove->tcpc.fwnode = fwnode_create_software_node(wcove_props, NULL);
+ if (IS_ERR(wcove->tcpc.fwnode))
+ return PTR_ERR(wcove->tcpc.fwnode);
wcove->tcpm = tcpm_register_port(wcove->dev, &wcove->tcpc);
- if (IS_ERR(wcove->tcpm))
+ if (IS_ERR(wcove->tcpm)) {
+ fwnode_remove_software_node(wcove->tcpc.fwnode);
return PTR_ERR(wcove->tcpm);
+ }
ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
wcove_typec_irq, IRQF_ONESHOT,
"wcove_typec", wcove);
if (ret) {
tcpm_unregister_port(wcove->tcpm);
+ fwnode_remove_software_node(wcove->tcpc.fwnode);
return ret;
}
regmap_write(wcove->regmap, USBC_IRQMASK2, val | USBC_IRQMASK2_ALL);
tcpm_unregister_port(wcove->tcpm);
+ fwnode_remove_software_node(wcove->tcpc.fwnode);
return 0;
}
# SPDX-License-Identifier: GPL-2.0
-CFLAGS_trace.o := -I$(src)
+CFLAGS_trace.o := -I$(src)
-obj-$(CONFIG_TYPEC_UCSI) += typec_ucsi.o
+obj-$(CONFIG_TYPEC_UCSI) += typec_ucsi.o
-typec_ucsi-y := ucsi.o
+typec_ucsi-y := ucsi.o
-typec_ucsi-$(CONFIG_TRACING) += trace.o
+typec_ucsi-$(CONFIG_TRACING) += trace.o
-obj-$(CONFIG_UCSI_ACPI) += ucsi_acpi.o
+ifneq ($(CONFIG_TYPEC_DP_ALTMODE),)
+ typec_ucsi-y += displayport.o
+endif
-obj-$(CONFIG_UCSI_CCG) += ucsi_ccg.o
+obj-$(CONFIG_UCSI_ACPI) += ucsi_acpi.o
+obj-$(CONFIG_UCSI_CCG) += ucsi_ccg.o
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * UCSI DisplayPort Alternate Mode Support
+ *
+ * Copyright (C) 2018, Intel Corporation
+ * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com>
+ */
+
+#include <linux/usb/typec_dp.h>
+#include <linux/usb/pd_vdo.h>
+
+#include "ucsi.h"
+
+#define UCSI_CMD_SET_NEW_CAM(_con_num_, _enter_, _cam_, _am_) \
+ (UCSI_SET_NEW_CAM | ((_con_num_) << 16) | ((_enter_) << 23) | \
+ ((_cam_) << 24) | ((u64)(_am_) << 32))
+
+struct ucsi_dp {
+ struct typec_displayport_data data;
+ struct ucsi_connector *con;
+ struct typec_altmode *alt;
+ struct work_struct work;
+ int offset;
+
+ bool override;
+ bool initialized;
+
+ u32 header;
+ u32 *vdo_data;
+ u8 vdo_size;
+};
+
+/*
+ * Note. Alternate mode control is optional feature in UCSI. It means that even
+ * if the system supports alternate modes, the OS may not be aware of them.
+ *
+ * In most cases however, the OS will be able to see the supported alternate
+ * modes, but it may still not be able to configure them, not even enter or exit
+ * them. That is because UCSI defines alt mode details and alt mode "overriding"
+ * as separate options.
+ *
+ * In case alt mode details are supported, but overriding is not, the driver
+ * will still display the supported pin assignments and configuration, but any
+ * changes the user attempts to do will lead into failure with return value of
+ * -EOPNOTSUPP.
+ */
+
+static int ucsi_displayport_enter(struct typec_altmode *alt)
+{
+ struct ucsi_dp *dp = typec_altmode_get_drvdata(alt);
+ struct ucsi_control ctrl;
+ u8 cur = 0;
+ int ret;
+
+ mutex_lock(&dp->con->lock);
+
+ if (!dp->override && dp->initialized) {
+ const struct typec_altmode *p = typec_altmode_get_partner(alt);
+
+ dev_warn(&p->dev,
+ "firmware doesn't support alternate mode overriding\n");
+ mutex_unlock(&dp->con->lock);
+ return -EOPNOTSUPP;
+ }
+
+ UCSI_CMD_GET_CURRENT_CAM(ctrl, dp->con->num);
+ ret = ucsi_send_command(dp->con->ucsi, &ctrl, &cur, sizeof(cur));
+ if (ret < 0) {
+ if (dp->con->ucsi->ppm->data->version > 0x0100) {
+ mutex_unlock(&dp->con->lock);
+ return ret;
+ }
+ cur = 0xff;
+ }
+
+ if (cur != 0xff) {
+ mutex_unlock(&dp->con->lock);
+ return -EBUSY;
+ }
+
+ /*
+ * We can't send the New CAM command yet to the PPM as it needs the
+ * configuration value as well. Pretending that we have now entered the
+ * mode, and letting the alt mode driver continue.
+ */
+
+ dp->header = VDO(USB_TYPEC_DP_SID, 1, CMD_ENTER_MODE);
+ dp->header |= VDO_OPOS(USB_TYPEC_DP_MODE);
+ dp->header |= VDO_CMDT(CMDT_RSP_ACK);
+
+ dp->vdo_data = NULL;
+ dp->vdo_size = 1;
+
+ schedule_work(&dp->work);
+
+ mutex_unlock(&dp->con->lock);
+
+ return 0;
+}
+
+static int ucsi_displayport_exit(struct typec_altmode *alt)
+{
+ struct ucsi_dp *dp = typec_altmode_get_drvdata(alt);
+ struct ucsi_control ctrl;
+ int ret = 0;
+
+ mutex_lock(&dp->con->lock);
+
+ if (!dp->override) {
+ const struct typec_altmode *p = typec_altmode_get_partner(alt);
+
+ dev_warn(&p->dev,
+ "firmware doesn't support alternate mode overriding\n");
+ ret = -EOPNOTSUPP;
+ goto out_unlock;
+ }
+
+ ctrl.raw_cmd = UCSI_CMD_SET_NEW_CAM(dp->con->num, 0, dp->offset, 0);
+ ret = ucsi_send_command(dp->con->ucsi, &ctrl, NULL, 0);
+ if (ret < 0)
+ goto out_unlock;
+
+ dp->header = VDO(USB_TYPEC_DP_SID, 1, CMD_EXIT_MODE);
+ dp->header |= VDO_OPOS(USB_TYPEC_DP_MODE);
+ dp->header |= VDO_CMDT(CMDT_RSP_ACK);
+
+ dp->vdo_data = NULL;
+ dp->vdo_size = 1;
+
+ schedule_work(&dp->work);
+
+out_unlock:
+ mutex_unlock(&dp->con->lock);
+
+ return ret;
+}
+
+/*
+ * We do not actually have access to the Status Update VDO, so we have to guess
+ * things.
+ */
+static int ucsi_displayport_status_update(struct ucsi_dp *dp)
+{
+ u32 cap = dp->alt->vdo;
+
+ dp->data.status = DP_STATUS_ENABLED;
+
+ /*
+ * If pin assignement D is supported, claiming always
+ * that Multi-function is preferred.
+ */
+ if (DP_CAP_CAPABILITY(cap) & DP_CAP_UFP_D) {
+ dp->data.status |= DP_STATUS_CON_UFP_D;
+
+ if (DP_CAP_UFP_D_PIN_ASSIGN(cap) & BIT(DP_PIN_ASSIGN_D))
+ dp->data.status |= DP_STATUS_PREFER_MULTI_FUNC;
+ } else {
+ dp->data.status |= DP_STATUS_CON_DFP_D;
+
+ if (DP_CAP_DFP_D_PIN_ASSIGN(cap) & BIT(DP_PIN_ASSIGN_D))
+ dp->data.status |= DP_STATUS_PREFER_MULTI_FUNC;
+ }
+
+ dp->vdo_data = &dp->data.status;
+ dp->vdo_size = 2;
+
+ return 0;
+}
+
+static int ucsi_displayport_configure(struct ucsi_dp *dp)
+{
+ u32 pins = DP_CONF_GET_PIN_ASSIGN(dp->data.conf);
+ struct ucsi_control ctrl;
+
+ if (!dp->override)
+ return 0;
+
+ ctrl.raw_cmd = UCSI_CMD_SET_NEW_CAM(dp->con->num, 1, dp->offset, pins);
+
+ return ucsi_send_command(dp->con->ucsi, &ctrl, NULL, 0);
+}
+
+static int ucsi_displayport_vdm(struct typec_altmode *alt,
+ u32 header, const u32 *data, int count)
+{
+ struct ucsi_dp *dp = typec_altmode_get_drvdata(alt);
+ int cmd_type = PD_VDO_CMDT(header);
+ int cmd = PD_VDO_CMD(header);
+
+ mutex_lock(&dp->con->lock);
+
+ if (!dp->override && dp->initialized) {
+ const struct typec_altmode *p = typec_altmode_get_partner(alt);
+
+ dev_warn(&p->dev,
+ "firmware doesn't support alternate mode overriding\n");
+ mutex_unlock(&dp->con->lock);
+ return -EOPNOTSUPP;
+ }
+
+ switch (cmd_type) {
+ case CMDT_INIT:
+ dp->header = VDO(USB_TYPEC_DP_SID, 1, cmd);
+ dp->header |= VDO_OPOS(USB_TYPEC_DP_MODE);
+
+ switch (cmd) {
+ case DP_CMD_STATUS_UPDATE:
+ if (ucsi_displayport_status_update(dp))
+ dp->header |= VDO_CMDT(CMDT_RSP_NAK);
+ else
+ dp->header |= VDO_CMDT(CMDT_RSP_ACK);
+ break;
+ case DP_CMD_CONFIGURE:
+ dp->data.conf = *data;
+ if (ucsi_displayport_configure(dp)) {
+ dp->header |= VDO_CMDT(CMDT_RSP_NAK);
+ } else {
+ dp->header |= VDO_CMDT(CMDT_RSP_ACK);
+ if (dp->initialized)
+ ucsi_altmode_update_active(dp->con);
+ else
+ dp->initialized = true;
+ }
+ break;
+ default:
+ dp->header |= VDO_CMDT(CMDT_RSP_ACK);
+ break;
+ }
+
+ schedule_work(&dp->work);
+ break;
+ default:
+ break;
+ }
+
+ mutex_unlock(&dp->con->lock);
+
+ return 0;
+}
+
+static const struct typec_altmode_ops ucsi_displayport_ops = {
+ .enter = ucsi_displayport_enter,
+ .exit = ucsi_displayport_exit,
+ .vdm = ucsi_displayport_vdm,
+};
+
+static void ucsi_displayport_work(struct work_struct *work)
+{
+ struct ucsi_dp *dp = container_of(work, struct ucsi_dp, work);
+ int ret;
+
+ mutex_lock(&dp->con->lock);
+
+ ret = typec_altmode_vdm(dp->alt, dp->header,
+ dp->vdo_data, dp->vdo_size);
+ if (ret)
+ dev_err(&dp->alt->dev, "VDM 0x%x failed\n", dp->header);
+
+ dp->vdo_data = NULL;
+ dp->vdo_size = 0;
+ dp->header = 0;
+
+ mutex_unlock(&dp->con->lock);
+}
+
+void ucsi_displayport_remove_partner(struct typec_altmode *alt)
+{
+ struct ucsi_dp *dp;
+
+ if (!alt)
+ return;
+
+ dp = typec_altmode_get_drvdata(alt);
+ dp->data.conf = 0;
+ dp->data.status = 0;
+ dp->initialized = false;
+}
+
+struct typec_altmode *ucsi_register_displayport(struct ucsi_connector *con,
+ bool override, int offset,
+ struct typec_altmode_desc *desc)
+{
+ u8 all_assignments = BIT(DP_PIN_ASSIGN_C) | BIT(DP_PIN_ASSIGN_D) |
+ BIT(DP_PIN_ASSIGN_E);
+ struct typec_altmode *alt;
+ struct ucsi_dp *dp;
+
+ /* We can't rely on the firmware with the capabilities. */
+ desc->vdo |= DP_CAP_DP_SIGNALING | DP_CAP_RECEPTACLE;
+
+ /* Claiming that we support all pin assignments */
+ desc->vdo |= all_assignments << 8;
+ desc->vdo |= all_assignments << 16;
+
+ alt = typec_port_register_altmode(con->port, desc);
+ if (IS_ERR(alt))
+ return alt;
+
+ dp = devm_kzalloc(&alt->dev, sizeof(*dp), GFP_KERNEL);
+ if (!dp) {
+ typec_unregister_altmode(alt);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ INIT_WORK(&dp->work, ucsi_displayport_work);
+ dp->override = override;
+ dp->offset = offset;
+ dp->con = con;
+ dp->alt = alt;
+
+ alt->ops = &ucsi_displayport_ops;
+ typec_altmode_set_drvdata(alt, dp);
+
+ return alt;
+}
return "";
}
+
+static const char * const ucsi_recipient_strs[] = {
+ [UCSI_RECIPIENT_CON] = "port",
+ [UCSI_RECIPIENT_SOP] = "partner",
+ [UCSI_RECIPIENT_SOP_P] = "plug (prime)",
+ [UCSI_RECIPIENT_SOP_PP] = "plug (double prime)",
+};
+
+const char *ucsi_recipient_str(u8 recipient)
+{
+ return ucsi_recipient_strs[recipient];
+}
#define __UCSI_TRACE_H
#include <linux/tracepoint.h>
+#include <linux/usb/typec_altmode.h>
const char *ucsi_cmd_str(u64 raw_cmd);
const char *ucsi_ack_str(u8 ack);
TP_ARGS(port, status)
);
+DECLARE_EVENT_CLASS(ucsi_log_register_altmode,
+ TP_PROTO(u8 recipient, struct typec_altmode *alt),
+ TP_ARGS(recipient, alt),
+ TP_STRUCT__entry(
+ __field(u8, recipient)
+ __field(u16, svid)
+ __field(u8, mode)
+ __field(u32, vdo)
+ ),
+ TP_fast_assign(
+ __entry->recipient = recipient;
+ __entry->svid = alt->svid;
+ __entry->mode = alt->mode;
+ __entry->vdo = alt->vdo;
+ ),
+ TP_printk("%s alt mode: svid %04x, mode %d vdo %x",
+ ucsi_recipient_str(__entry->recipient), __entry->svid,
+ __entry->mode, __entry->vdo)
+);
+
+DEFINE_EVENT(ucsi_log_register_altmode, ucsi_register_altmode,
+ TP_PROTO(u8 recipient, struct typec_altmode *alt),
+ TP_ARGS(recipient, alt)
+);
+
#endif /* __UCSI_TRACE_H */
/* This part must be outside protection */
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/slab.h>
-#include <linux/usb/typec.h>
+#include <linux/usb/typec_dp.h>
#include "ucsi.h"
#include "trace.h"
*/
#define UCSI_SWAP_TIMEOUT_MS 5000
-enum ucsi_status {
- UCSI_IDLE = 0,
- UCSI_BUSY,
- UCSI_ERROR,
-};
-
-struct ucsi_connector {
- int num;
-
- struct ucsi *ucsi;
- struct work_struct work;
- struct completion complete;
-
- struct typec_port *port;
- struct typec_partner *partner;
-
- struct typec_capability typec_cap;
-
- struct ucsi_connector_status status;
- struct ucsi_connector_capability cap;
-};
-
-struct ucsi {
- struct device *dev;
- struct ucsi_ppm *ppm;
-
- enum ucsi_status status;
- struct completion complete;
- struct ucsi_capability cap;
- struct ucsi_connector *connector;
-
- struct work_struct work;
-
- /* PPM Communication lock */
- struct mutex ppm_lock;
-
- /* PPM communication flags */
- unsigned long flags;
-#define EVENT_PENDING 0
-#define COMMAND_PENDING 1
-#define ACK_PENDING 2
-};
-
static inline int ucsi_sync(struct ucsi *ucsi)
{
if (ucsi->ppm && ucsi->ppm->sync)
return ret;
}
+int ucsi_send_command(struct ucsi *ucsi, struct ucsi_control *ctrl,
+ void *retval, size_t size)
+{
+ int ret;
+
+ mutex_lock(&ucsi->ppm_lock);
+ ret = ucsi_run_command(ucsi, ctrl, retval, size);
+ mutex_unlock(&ucsi->ppm_lock);
+
+ return ret;
+}
+
/* -------------------------------------------------------------------------- */
+void ucsi_altmode_update_active(struct ucsi_connector *con)
+{
+ const struct typec_altmode *altmode = NULL;
+ struct ucsi_control ctrl;
+ int ret;
+ u8 cur;
+ int i;
+
+ UCSI_CMD_GET_CURRENT_CAM(ctrl, con->num);
+ ret = ucsi_run_command(con->ucsi, &ctrl, &cur, sizeof(cur));
+ if (ret < 0) {
+ if (con->ucsi->ppm->data->version > 0x0100) {
+ dev_err(con->ucsi->dev,
+ "GET_CURRENT_CAM command failed\n");
+ return;
+ }
+ cur = 0xff;
+ }
+
+ if (cur < UCSI_MAX_ALTMODES)
+ altmode = typec_altmode_get_partner(con->port_altmode[cur]);
+
+ for (i = 0; con->partner_altmode[i]; i++)
+ typec_altmode_update_active(con->partner_altmode[i],
+ con->partner_altmode[i] == altmode);
+}
+
+static u8 ucsi_altmode_next_mode(struct typec_altmode **alt, u16 svid)
+{
+ u8 mode = 1;
+ int i;
+
+ for (i = 0; alt[i]; i++)
+ if (alt[i]->svid == svid)
+ mode++;
+
+ return mode;
+}
+
+static int ucsi_next_altmode(struct typec_altmode **alt)
+{
+ int i = 0;
+
+ for (i = 0; i < UCSI_MAX_ALTMODES; i++)
+ if (!alt[i])
+ return i;
+
+ return -ENOENT;
+}
+
+static int ucsi_register_altmode(struct ucsi_connector *con,
+ struct typec_altmode_desc *desc,
+ u8 recipient)
+{
+ struct typec_altmode *alt;
+ bool override;
+ int ret;
+ int i;
+
+ override = !!(con->ucsi->cap.features & UCSI_CAP_ALT_MODE_OVERRIDE);
+
+ switch (recipient) {
+ case UCSI_RECIPIENT_CON:
+ i = ucsi_next_altmode(con->port_altmode);
+ if (i < 0) {
+ ret = i;
+ goto err;
+ }
+
+ desc->mode = ucsi_altmode_next_mode(con->port_altmode,
+ desc->svid);
+
+ switch (desc->svid) {
+ case USB_TYPEC_DP_SID:
+ case USB_TYPEC_NVIDIA_VLINK_SID:
+ alt = ucsi_register_displayport(con, override, i, desc);
+ break;
+ default:
+ alt = typec_port_register_altmode(con->port, desc);
+ break;
+ }
+
+ if (IS_ERR(alt)) {
+ ret = PTR_ERR(alt);
+ goto err;
+ }
+
+ con->port_altmode[i] = alt;
+ break;
+ case UCSI_RECIPIENT_SOP:
+ i = ucsi_next_altmode(con->partner_altmode);
+ if (i < 0) {
+ ret = i;
+ goto err;
+ }
+
+ desc->mode = ucsi_altmode_next_mode(con->partner_altmode,
+ desc->svid);
+
+ alt = typec_partner_register_altmode(con->partner, desc);
+ if (IS_ERR(alt)) {
+ ret = PTR_ERR(alt);
+ goto err;
+ }
+
+ con->partner_altmode[i] = alt;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ trace_ucsi_register_altmode(recipient, alt);
+
+ return 0;
+
+err:
+ dev_err(con->ucsi->dev, "failed to registers svid 0x%04x mode %d\n",
+ desc->svid, desc->mode);
+
+ return ret;
+}
+
+static int ucsi_register_altmodes(struct ucsi_connector *con, u8 recipient)
+{
+ int max_altmodes = UCSI_MAX_ALTMODES;
+ struct typec_altmode_desc desc;
+ struct ucsi_altmode alt[2];
+ struct ucsi_control ctrl;
+ int num = 1;
+ int ret;
+ int len;
+ int j;
+ int i;
+
+ if (!(con->ucsi->cap.features & UCSI_CAP_ALT_MODE_DETAILS))
+ return 0;
+
+ if (recipient == UCSI_RECIPIENT_SOP && con->partner_altmode[0])
+ return 0;
+
+ if (recipient == UCSI_RECIPIENT_CON)
+ max_altmodes = con->ucsi->cap.num_alt_modes;
+
+ for (i = 0; i < max_altmodes;) {
+ memset(alt, 0, sizeof(alt));
+ UCSI_CMD_GET_ALTERNATE_MODES(ctrl, recipient, con->num, i, 1);
+ len = ucsi_run_command(con->ucsi, &ctrl, alt, sizeof(alt));
+ if (len <= 0)
+ return len;
+
+ /*
+ * This code is requesting one alt mode at a time, but some PPMs
+ * may still return two. If that happens both alt modes need be
+ * registered and the offset for the next alt mode has to be
+ * incremented.
+ */
+ num = len / sizeof(alt[0]);
+ i += num;
+
+ for (j = 0; j < num; j++) {
+ if (!alt[j].svid)
+ return 0;
+
+ memset(&desc, 0, sizeof(desc));
+ desc.vdo = alt[j].mid;
+ desc.svid = alt[j].svid;
+ desc.roles = TYPEC_PORT_DRD;
+
+ ret = ucsi_register_altmode(con, &desc, recipient);
+ if (ret)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static void ucsi_unregister_altmodes(struct ucsi_connector *con, u8 recipient)
+{
+ const struct typec_altmode *pdev;
+ struct typec_altmode **adev;
+ int i = 0;
+
+ switch (recipient) {
+ case UCSI_RECIPIENT_CON:
+ adev = con->port_altmode;
+ break;
+ case UCSI_RECIPIENT_SOP:
+ adev = con->partner_altmode;
+ break;
+ default:
+ return;
+ }
+
+ while (adev[i]) {
+ if (recipient == UCSI_RECIPIENT_SOP &&
+ (adev[i]->svid == USB_TYPEC_DP_SID ||
+ adev[i]->svid == USB_TYPEC_NVIDIA_VLINK_SID)) {
+ pdev = typec_altmode_get_partner(adev[i]);
+ ucsi_displayport_remove_partner((void *)pdev);
+ }
+ typec_unregister_altmode(adev[i]);
+ adev[i++] = NULL;
+ }
+}
+
static void ucsi_pwr_opmode_change(struct ucsi_connector *con)
{
switch (con->status.pwr_op_mode) {
if (!con->partner)
return;
+ ucsi_unregister_altmodes(con, UCSI_RECIPIENT_SOP);
typec_unregister_partner(con->partner);
con->partner = NULL;
}
+static void ucsi_partner_change(struct ucsi_connector *con)
+{
+ int ret;
+
+ if (!con->partner)
+ return;
+
+ switch (con->status.partner_type) {
+ case UCSI_CONSTAT_PARTNER_TYPE_UFP:
+ typec_set_data_role(con->port, TYPEC_HOST);
+ break;
+ case UCSI_CONSTAT_PARTNER_TYPE_DFP:
+ typec_set_data_role(con->port, TYPEC_DEVICE);
+ break;
+ default:
+ break;
+ }
+
+ /* Complete pending data role swap */
+ if (!completion_done(&con->complete))
+ complete(&con->complete);
+
+ /* Can't rely on Partner Flags field. Always checking the alt modes. */
+ ret = ucsi_register_altmodes(con, UCSI_RECIPIENT_SOP);
+ if (ret)
+ dev_err(con->ucsi->dev,
+ "con%d: failed to register partner alternate modes\n",
+ con->num);
+ else
+ ucsi_altmode_update_active(con);
+}
+
static void ucsi_connector_change(struct work_struct *work)
{
struct ucsi_connector *con = container_of(work, struct ucsi_connector,
struct ucsi_control ctrl;
int ret;
- mutex_lock(&ucsi->ppm_lock);
+ mutex_lock(&con->lock);
UCSI_CMD_GET_CONNECTOR_STATUS(ctrl, con->num);
- ret = ucsi_run_command(ucsi, &ctrl, &con->status, sizeof(con->status));
+ ret = ucsi_send_command(ucsi, &ctrl, &con->status, sizeof(con->status));
if (ret < 0) {
dev_err(ucsi->dev, "%s: GET_CONNECTOR_STATUS failed (%d)\n",
__func__, ret);
complete(&con->complete);
}
- if (con->status.change & UCSI_CONSTAT_PARTNER_CHANGE) {
- switch (con->status.partner_type) {
- case UCSI_CONSTAT_PARTNER_TYPE_UFP:
- typec_set_data_role(con->port, TYPEC_HOST);
- break;
- case UCSI_CONSTAT_PARTNER_TYPE_DFP:
- typec_set_data_role(con->port, TYPEC_DEVICE);
- break;
- default:
- break;
- }
-
- /* Complete pending data role swap */
- if (!completion_done(&con->complete))
- complete(&con->complete);
- }
-
if (con->status.change & UCSI_CONSTAT_CONNECT_CHANGE) {
typec_set_pwr_role(con->port, con->status.pwr_dir);
ucsi_unregister_partner(con);
}
+ if (con->status.change & UCSI_CONSTAT_CAM_CHANGE) {
+ /*
+ * We don't need to know the currently supported alt modes here.
+ * Running GET_CAM_SUPPORTED command just to make sure the PPM
+ * does not get stuck in case it assumes we do so.
+ */
+ UCSI_CMD_GET_CAM_SUPPORTED(ctrl, con->num);
+ ucsi_run_command(con->ucsi, &ctrl, NULL, 0);
+ }
+
+ if (con->status.change & UCSI_CONSTAT_PARTNER_CHANGE)
+ ucsi_partner_change(con);
+
ret = ucsi_ack(ucsi, UCSI_ACK_EVENT);
if (ret)
dev_err(ucsi->dev, "%s: ACK failed (%d)", __func__, ret);
out_unlock:
clear_bit(EVENT_PENDING, &ucsi->flags);
- mutex_unlock(&ucsi->ppm_lock);
+ mutex_unlock(&con->lock);
}
/**
UCSI_CMD_CONNECTOR_RESET(ctrl, con, hard);
- return ucsi_run_command(con->ucsi, &ctrl, NULL, 0);
+ return ucsi_send_command(con->ucsi, &ctrl, NULL, 0);
}
static int ucsi_reset_ppm(struct ucsi *ucsi)
{
int ret;
- ret = ucsi_run_command(con->ucsi, ctrl, NULL, 0);
+ ret = ucsi_send_command(con->ucsi, ctrl, NULL, 0);
if (ret == -ETIMEDOUT) {
struct ucsi_control c;
/* PPM most likely stopped responding. Resetting everything. */
+ mutex_lock(&con->ucsi->ppm_lock);
ucsi_reset_ppm(con->ucsi);
+ mutex_unlock(&con->ucsi->ppm_lock);
UCSI_CMD_SET_NTFY_ENABLE(c, UCSI_ENABLE_NTFY_ALL);
- ucsi_run_command(con->ucsi, &c, NULL, 0);
+ ucsi_send_command(con->ucsi, &c, NULL, 0);
ucsi_reset_connector(con, true);
}
struct ucsi_control ctrl;
int ret = 0;
- if (!con->partner)
- return -ENOTCONN;
+ mutex_lock(&con->lock);
- mutex_lock(&con->ucsi->ppm_lock);
+ if (!con->partner) {
+ ret = -ENOTCONN;
+ goto out_unlock;
+ }
if ((con->status.partner_type == UCSI_CONSTAT_PARTNER_TYPE_DFP &&
role == TYPEC_DEVICE) ||
if (ret < 0)
goto out_unlock;
- mutex_unlock(&con->ucsi->ppm_lock);
-
if (!wait_for_completion_timeout(&con->complete,
msecs_to_jiffies(UCSI_SWAP_TIMEOUT_MS)))
- return -ETIMEDOUT;
-
- return 0;
+ ret = -ETIMEDOUT;
out_unlock:
- mutex_unlock(&con->ucsi->ppm_lock);
+ mutex_unlock(&con->lock);
- return ret;
+ return ret < 0 ? ret : 0;
}
static int
struct ucsi_control ctrl;
int ret = 0;
- if (!con->partner)
- return -ENOTCONN;
+ mutex_lock(&con->lock);
- mutex_lock(&con->ucsi->ppm_lock);
+ if (!con->partner) {
+ ret = -ENOTCONN;
+ goto out_unlock;
+ }
if (con->status.pwr_dir == role)
goto out_unlock;
if (ret < 0)
goto out_unlock;
- mutex_unlock(&con->ucsi->ppm_lock);
-
if (!wait_for_completion_timeout(&con->complete,
- msecs_to_jiffies(UCSI_SWAP_TIMEOUT_MS)))
- return -ETIMEDOUT;
-
- mutex_lock(&con->ucsi->ppm_lock);
+ msecs_to_jiffies(UCSI_SWAP_TIMEOUT_MS))) {
+ ret = -ETIMEDOUT;
+ goto out_unlock;
+ }
/* Something has gone wrong while swapping the role */
if (con->status.pwr_op_mode != UCSI_CONSTAT_PWR_OPMODE_PD) {
}
out_unlock:
- mutex_unlock(&con->ucsi->ppm_lock);
+ mutex_unlock(&con->lock);
return ret;
}
INIT_WORK(&con->work, ucsi_connector_change);
init_completion(&con->complete);
+ mutex_init(&con->lock);
con->num = index + 1;
con->ucsi = ucsi;
if (IS_ERR(con->port))
return PTR_ERR(con->port);
+ /* Alternate modes */
+ ret = ucsi_register_altmodes(con, UCSI_RECIPIENT_CON);
+ if (ret)
+ dev_err(ucsi->dev, "con%d: failed to register alt modes\n",
+ con->num);
+
/* Get the status */
UCSI_CMD_GET_CONNECTOR_STATUS(ctrl, con->num);
ret = ucsi_run_command(ucsi, &ctrl, &con->status, sizeof(con->status));
if (con->status.connected)
ucsi_register_partner(con);
+ if (con->partner) {
+ ret = ucsi_register_altmodes(con, UCSI_RECIPIENT_SOP);
+ if (ret)
+ dev_err(ucsi->dev,
+ "con%d: failed to register alternate modes\n",
+ con->num);
+ else
+ ucsi_altmode_update_active(con);
+ }
+
trace_ucsi_register_port(con->num, &con->status);
return 0;
err_unregister:
for (con = ucsi->connector; con->port; con++) {
ucsi_unregister_partner(con);
+ ucsi_unregister_altmodes(con, UCSI_RECIPIENT_CON);
typec_unregister_port(con->port);
con->port = NULL;
}
/* Make sure that we are not in the middle of driver initialization */
cancel_work_sync(&ucsi->work);
- mutex_lock(&ucsi->ppm_lock);
-
/* Disable everything except command complete notification */
UCSI_CMD_SET_NTFY_ENABLE(ctrl, UCSI_ENABLE_NTFY_CMD_COMPLETE)
- ucsi_run_command(ucsi, &ctrl, NULL, 0);
-
- mutex_unlock(&ucsi->ppm_lock);
+ ucsi_send_command(ucsi, &ctrl, NULL, 0);
for (i = 0; i < ucsi->cap.num_connectors; i++) {
cancel_work_sync(&ucsi->connector[i].work);
ucsi_unregister_partner(&ucsi->connector[i]);
+ ucsi_unregister_altmodes(&ucsi->connector[i],
+ UCSI_RECIPIENT_CON);
typec_unregister_port(ucsi->connector[i].port);
}
#include <linux/bitops.h>
#include <linux/device.h>
#include <linux/types.h>
+#include <linux/usb/typec.h>
/* -------------------------------------------------------------------------- */
u16:6; /* reserved */
} __packed;
+/* Get Alternate Modes Command structure */
+struct ucsi_altmode_cmd {
+ u8 cmd;
+ u8 length;
+ u8 recipient;
+#define UCSI_RECIPIENT_CON 0
+#define UCSI_RECIPIENT_SOP 1
+#define UCSI_RECIPIENT_SOP_P 2
+#define UCSI_RECIPIENT_SOP_PP 3
+ u8 con_num;
+ u8 offset;
+ u8 num_altmodes;
+} __packed;
+
struct ucsi_control {
union {
u64 raw_cmd;
struct ucsi_uor_cmd uor;
struct ucsi_ack_cmd ack;
struct ucsi_con_rst con_rst;
+ struct ucsi_altmode_cmd alt;
};
};
(_ctrl_).cmd.data = _con_; \
}
+/* Helper for preparing ucsi_control for GET_ALTERNATE_MODES command. */
+#define UCSI_CMD_GET_ALTERNATE_MODES(_ctrl_, _r_, _con_num_, _o_, _num_)\
+{ \
+ __UCSI_CMD((_ctrl_), UCSI_GET_ALTERNATE_MODES) \
+ _ctrl_.alt.recipient = (_r_); \
+ _ctrl_.alt.con_num = (_con_num_); \
+ _ctrl_.alt.offset = (_o_); \
+ _ctrl_.alt.num_altmodes = (_num_) - 1; \
+}
+
+/* Helper for preparing ucsi_control for GET_CAM_SUPPORTED command. */
+#define UCSI_CMD_GET_CAM_SUPPORTED(_ctrl_, _con_) \
+{ \
+ __UCSI_CMD((_ctrl_), UCSI_GET_CAM_SUPPORTED) \
+ _ctrl_.cmd.data = (_con_); \
+}
+
+/* Helper for preparing ucsi_control for GET_CAM_SUPPORTED command. */
+#define UCSI_CMD_GET_CURRENT_CAM(_ctrl_, _con_) \
+{ \
+ __UCSI_CMD((_ctrl_), UCSI_GET_CURRENT_CAM) \
+ _ctrl_.cmd.data = (_con_); \
+}
+
/* Helper for preparing ucsi_control for GET_CONNECTOR_STATUS command. */
#define UCSI_CMD_GET_CONNECTOR_STATUS(_ctrl_, _con_) \
{ \
void ucsi_unregister_ppm(struct ucsi *ucsi);
void ucsi_notify(struct ucsi *ucsi);
+/* -------------------------------------------------------------------------- */
+
+enum ucsi_status {
+ UCSI_IDLE = 0,
+ UCSI_BUSY,
+ UCSI_ERROR,
+};
+
+struct ucsi {
+ struct device *dev;
+ struct ucsi_ppm *ppm;
+
+ enum ucsi_status status;
+ struct completion complete;
+ struct ucsi_capability cap;
+ struct ucsi_connector *connector;
+
+ struct work_struct work;
+
+ /* PPM Communication lock */
+ struct mutex ppm_lock;
+
+ /* PPM communication flags */
+ unsigned long flags;
+#define EVENT_PENDING 0
+#define COMMAND_PENDING 1
+#define ACK_PENDING 2
+};
+
+#define UCSI_MAX_SVID 5
+#define UCSI_MAX_ALTMODES (UCSI_MAX_SVID * 6)
+
+struct ucsi_connector {
+ int num;
+
+ struct ucsi *ucsi;
+ struct mutex lock; /* port lock */
+ struct work_struct work;
+ struct completion complete;
+
+ struct typec_port *port;
+ struct typec_partner *partner;
+
+ struct typec_altmode *port_altmode[UCSI_MAX_ALTMODES];
+ struct typec_altmode *partner_altmode[UCSI_MAX_ALTMODES];
+
+ struct typec_capability typec_cap;
+
+ struct ucsi_connector_status status;
+ struct ucsi_connector_capability cap;
+};
+
+int ucsi_send_command(struct ucsi *ucsi, struct ucsi_control *ctrl,
+ void *retval, size_t size);
+
+void ucsi_altmode_update_active(struct ucsi_connector *con);
+
+#if IS_ENABLED(CONFIG_TYPEC_DP_ALTMODE)
+struct typec_altmode *
+ucsi_register_displayport(struct ucsi_connector *con,
+ bool override, int offset,
+ struct typec_altmode_desc *desc);
+
+void ucsi_displayport_remove_partner(struct typec_altmode *adev);
+
+#else
+static inline struct typec_altmode *
+ucsi_register_displayport(struct ucsi_connector *con,
+ bool override, int offset,
+ struct typec_altmode_desc *desc)
+{
+ return NULL;
+}
+
+static inline void
+ucsi_displayport_remove_partner(struct typec_altmode *adev) { }
+#endif /* CONFIG_TYPEC_DP_ALTMODE */
+
#endif /* __DRIVER_USB_TYPEC_UCSI_H */
*/
#include <linux/acpi.h>
#include <linux/delay.h>
+#include <linux/firmware.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <asm/unaligned.h>
#include "ucsi.h"
+enum enum_fw_mode {
+ BOOT, /* bootloader */
+ FW1, /* FW partition-1 (contains secondary fw) */
+ FW2, /* FW partition-2 (contains primary fw) */
+ FW_INVALID,
+};
+
+#define CCGX_RAB_DEVICE_MODE 0x0000
+#define CCGX_RAB_INTR_REG 0x0006
+#define DEV_INT BIT(0)
+#define PORT0_INT BIT(1)
+#define PORT1_INT BIT(2)
+#define UCSI_READ_INT BIT(7)
+#define CCGX_RAB_JUMP_TO_BOOT 0x0007
+#define TO_BOOT 'J'
+#define TO_ALT_FW 'A'
+#define CCGX_RAB_RESET_REQ 0x0008
+#define RESET_SIG 'R'
+#define CMD_RESET_I2C 0x0
+#define CMD_RESET_DEV 0x1
+#define CCGX_RAB_ENTER_FLASHING 0x000A
+#define FLASH_ENTER_SIG 'P'
+#define CCGX_RAB_VALIDATE_FW 0x000B
+#define CCGX_RAB_FLASH_ROW_RW 0x000C
+#define FLASH_SIG 'F'
+#define FLASH_RD_CMD 0x0
+#define FLASH_WR_CMD 0x1
+#define FLASH_FWCT1_WR_CMD 0x2
+#define FLASH_FWCT2_WR_CMD 0x3
+#define FLASH_FWCT_SIG_WR_CMD 0x4
+#define CCGX_RAB_READ_ALL_VER 0x0010
+#define CCGX_RAB_READ_FW2_VER 0x0020
+#define CCGX_RAB_UCSI_CONTROL 0x0039
+#define CCGX_RAB_UCSI_CONTROL_START BIT(0)
+#define CCGX_RAB_UCSI_CONTROL_STOP BIT(1)
+#define CCGX_RAB_UCSI_DATA_BLOCK(offset) (0xf000 | ((offset) & 0xff))
+#define REG_FLASH_RW_MEM 0x0200
+#define DEV_REG_IDX CCGX_RAB_DEVICE_MODE
+#define CCGX_RAB_PDPORT_ENABLE 0x002C
+#define PDPORT_1 BIT(0)
+#define PDPORT_2 BIT(1)
+#define CCGX_RAB_RESPONSE 0x007E
+#define ASYNC_EVENT BIT(7)
+
+/* CCGx events & async msg codes */
+#define RESET_COMPLETE 0x80
+#define EVENT_INDEX RESET_COMPLETE
+#define PORT_CONNECT_DET 0x84
+#define PORT_DISCONNECT_DET 0x85
+#define ROLE_SWAP_COMPELETE 0x87
+
+/* ccg firmware */
+#define CYACD_LINE_SIZE 527
+#define CCG4_ROW_SIZE 256
+#define FW1_METADATA_ROW 0x1FF
+#define FW2_METADATA_ROW 0x1FE
+#define FW_CFG_TABLE_SIG_SIZE 256
+
+static int secondary_fw_min_ver = 41;
+
+enum enum_flash_mode {
+ SECONDARY_BL, /* update secondary using bootloader */
+ PRIMARY, /* update primary using secondary */
+ SECONDARY, /* update secondary using primary */
+ FLASH_NOT_NEEDED, /* update not required */
+ FLASH_INVALID,
+};
+
+static const char * const ccg_fw_names[] = {
+ "ccg_boot.cyacd",
+ "ccg_primary.cyacd",
+ "ccg_secondary.cyacd"
+};
+
+struct ccg_dev_info {
+#define CCG_DEVINFO_FWMODE_SHIFT (0)
+#define CCG_DEVINFO_FWMODE_MASK (0x3 << CCG_DEVINFO_FWMODE_SHIFT)
+#define CCG_DEVINFO_PDPORTS_SHIFT (2)
+#define CCG_DEVINFO_PDPORTS_MASK (0x3 << CCG_DEVINFO_PDPORTS_SHIFT)
+ u8 mode;
+ u8 bl_mode;
+ __le16 silicon_id;
+ __le16 bl_last_row;
+} __packed;
+
+struct version_format {
+ __le16 build;
+ u8 patch;
+ u8 ver;
+#define CCG_VERSION_MIN_SHIFT (0)
+#define CCG_VERSION_MIN_MASK (0xf << CCG_VERSION_MIN_SHIFT)
+#define CCG_VERSION_MAJ_SHIFT (4)
+#define CCG_VERSION_MAJ_MASK (0xf << CCG_VERSION_MAJ_SHIFT)
+} __packed;
+
+struct version_info {
+ struct version_format base;
+ struct version_format app;
+};
+
+struct fw_config_table {
+ u32 identity;
+ u16 table_size;
+ u8 fwct_version;
+ u8 is_key_change;
+ u8 guid[16];
+ struct version_format base;
+ struct version_format app;
+ u8 primary_fw_digest[32];
+ u32 key_exp_length;
+ u8 key_modulus[256];
+ u8 key_exp[4];
+};
+
+/* CCGx response codes */
+enum ccg_resp_code {
+ CMD_NO_RESP = 0x00,
+ CMD_SUCCESS = 0x02,
+ FLASH_DATA_AVAILABLE = 0x03,
+ CMD_INVALID = 0x05,
+ FLASH_UPDATE_FAIL = 0x07,
+ INVALID_FW = 0x08,
+ INVALID_ARG = 0x09,
+ CMD_NOT_SUPPORT = 0x0A,
+ TRANSACTION_FAIL = 0x0C,
+ PD_CMD_FAIL = 0x0D,
+ UNDEF_ERROR = 0x0F,
+ INVALID_RESP = 0x10,
+};
+
+#define CCG_EVENT_MAX (EVENT_INDEX + 43)
+
+struct ccg_cmd {
+ u16 reg;
+ u32 data;
+ int len;
+ u32 delay; /* ms delay for cmd timeout */
+};
+
+struct ccg_resp {
+ u8 code;
+ u8 length;
+};
+
struct ucsi_ccg {
struct device *dev;
struct ucsi *ucsi;
struct ucsi_ppm ppm;
struct i2c_client *client;
-};
+ struct ccg_dev_info info;
+ /* version info for boot, primary and secondary */
+ struct version_info version[FW2 + 1];
+ /* CCG HPI communication flags */
+ unsigned long flags;
+#define RESET_PENDING 0
+#define DEV_CMD_PENDING 1
+ struct ccg_resp dev_resp;
+ u8 cmd_resp;
+ int port_num;
+ int irq;
+ struct work_struct work;
+ struct mutex lock; /* to sync between user and driver thread */
-#define CCGX_RAB_INTR_REG 0x06
-#define CCGX_RAB_UCSI_CONTROL 0x39
-#define CCGX_RAB_UCSI_CONTROL_START BIT(0)
-#define CCGX_RAB_UCSI_CONTROL_STOP BIT(1)
-#define CCGX_RAB_UCSI_DATA_BLOCK(offset) (0xf000 | ((offset) & 0xff))
+ /* fw build with vendor information */
+ u16 fw_build;
+};
static int ccg_read(struct ucsi_ccg *uc, u16 rab, u8 *data, u32 len)
{
return IRQ_HANDLED;
}
+static int get_fw_info(struct ucsi_ccg *uc)
+{
+ int err;
+
+ err = ccg_read(uc, CCGX_RAB_READ_ALL_VER, (u8 *)(&uc->version),
+ sizeof(uc->version));
+ if (err < 0)
+ return err;
+
+ err = ccg_read(uc, CCGX_RAB_DEVICE_MODE, (u8 *)(&uc->info),
+ sizeof(uc->info));
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+
+static inline bool invalid_async_evt(int code)
+{
+ return (code >= CCG_EVENT_MAX) || (code < EVENT_INDEX);
+}
+
+static void ccg_process_response(struct ucsi_ccg *uc)
+{
+ struct device *dev = uc->dev;
+
+ if (uc->dev_resp.code & ASYNC_EVENT) {
+ if (uc->dev_resp.code == RESET_COMPLETE) {
+ if (test_bit(RESET_PENDING, &uc->flags))
+ uc->cmd_resp = uc->dev_resp.code;
+ get_fw_info(uc);
+ }
+ if (invalid_async_evt(uc->dev_resp.code))
+ dev_err(dev, "invalid async evt %d\n",
+ uc->dev_resp.code);
+ } else {
+ if (test_bit(DEV_CMD_PENDING, &uc->flags)) {
+ uc->cmd_resp = uc->dev_resp.code;
+ clear_bit(DEV_CMD_PENDING, &uc->flags);
+ } else {
+ dev_err(dev, "dev resp 0x%04x but no cmd pending\n",
+ uc->dev_resp.code);
+ }
+ }
+}
+
+static int ccg_read_response(struct ucsi_ccg *uc)
+{
+ unsigned long target = jiffies + msecs_to_jiffies(1000);
+ struct device *dev = uc->dev;
+ u8 intval;
+ int status;
+
+ /* wait for interrupt status to get updated */
+ do {
+ status = ccg_read(uc, CCGX_RAB_INTR_REG, &intval,
+ sizeof(intval));
+ if (status < 0)
+ return status;
+
+ if (intval & DEV_INT)
+ break;
+ usleep_range(500, 600);
+ } while (time_is_after_jiffies(target));
+
+ if (time_is_before_jiffies(target)) {
+ dev_err(dev, "response timeout error\n");
+ return -ETIME;
+ }
+
+ status = ccg_read(uc, CCGX_RAB_RESPONSE, (u8 *)&uc->dev_resp,
+ sizeof(uc->dev_resp));
+ if (status < 0)
+ return status;
+
+ status = ccg_write(uc, CCGX_RAB_INTR_REG, &intval, sizeof(intval));
+ if (status < 0)
+ return status;
+
+ return 0;
+}
+
+/* Caller must hold uc->lock */
+static int ccg_send_command(struct ucsi_ccg *uc, struct ccg_cmd *cmd)
+{
+ struct device *dev = uc->dev;
+ int ret;
+
+ switch (cmd->reg & 0xF000) {
+ case DEV_REG_IDX:
+ set_bit(DEV_CMD_PENDING, &uc->flags);
+ break;
+ default:
+ dev_err(dev, "invalid cmd register\n");
+ break;
+ }
+
+ ret = ccg_write(uc, cmd->reg, (u8 *)&cmd->data, cmd->len);
+ if (ret < 0)
+ return ret;
+
+ msleep(cmd->delay);
+
+ ret = ccg_read_response(uc);
+ if (ret < 0) {
+ dev_err(dev, "response read error\n");
+ switch (cmd->reg & 0xF000) {
+ case DEV_REG_IDX:
+ clear_bit(DEV_CMD_PENDING, &uc->flags);
+ break;
+ default:
+ dev_err(dev, "invalid cmd register\n");
+ break;
+ }
+ return -EIO;
+ }
+ ccg_process_response(uc);
+
+ return uc->cmd_resp;
+}
+
+static int ccg_cmd_enter_flashing(struct ucsi_ccg *uc)
+{
+ struct ccg_cmd cmd;
+ int ret;
+
+ cmd.reg = CCGX_RAB_ENTER_FLASHING;
+ cmd.data = FLASH_ENTER_SIG;
+ cmd.len = 1;
+ cmd.delay = 50;
+
+ mutex_lock(&uc->lock);
+
+ ret = ccg_send_command(uc, &cmd);
+
+ mutex_unlock(&uc->lock);
+
+ if (ret != CMD_SUCCESS) {
+ dev_err(uc->dev, "enter flashing failed ret=%d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ccg_cmd_reset(struct ucsi_ccg *uc)
+{
+ struct ccg_cmd cmd;
+ u8 *p;
+ int ret;
+
+ p = (u8 *)&cmd.data;
+ cmd.reg = CCGX_RAB_RESET_REQ;
+ p[0] = RESET_SIG;
+ p[1] = CMD_RESET_DEV;
+ cmd.len = 2;
+ cmd.delay = 5000;
+
+ mutex_lock(&uc->lock);
+
+ set_bit(RESET_PENDING, &uc->flags);
+
+ ret = ccg_send_command(uc, &cmd);
+ if (ret != RESET_COMPLETE)
+ goto err_clear_flag;
+
+ ret = 0;
+
+err_clear_flag:
+ clear_bit(RESET_PENDING, &uc->flags);
+
+ mutex_unlock(&uc->lock);
+
+ return ret;
+}
+
+static int ccg_cmd_port_control(struct ucsi_ccg *uc, bool enable)
+{
+ struct ccg_cmd cmd;
+ int ret;
+
+ cmd.reg = CCGX_RAB_PDPORT_ENABLE;
+ if (enable)
+ cmd.data = (uc->port_num == 1) ?
+ PDPORT_1 : (PDPORT_1 | PDPORT_2);
+ else
+ cmd.data = 0x0;
+ cmd.len = 1;
+ cmd.delay = 10;
+
+ mutex_lock(&uc->lock);
+
+ ret = ccg_send_command(uc, &cmd);
+
+ mutex_unlock(&uc->lock);
+
+ if (ret != CMD_SUCCESS) {
+ dev_err(uc->dev, "port control failed ret=%d\n", ret);
+ return ret;
+ }
+ return 0;
+}
+
+static int ccg_cmd_jump_boot_mode(struct ucsi_ccg *uc, int bl_mode)
+{
+ struct ccg_cmd cmd;
+ int ret;
+
+ cmd.reg = CCGX_RAB_JUMP_TO_BOOT;
+
+ if (bl_mode)
+ cmd.data = TO_BOOT;
+ else
+ cmd.data = TO_ALT_FW;
+
+ cmd.len = 1;
+ cmd.delay = 100;
+
+ mutex_lock(&uc->lock);
+
+ set_bit(RESET_PENDING, &uc->flags);
+
+ ret = ccg_send_command(uc, &cmd);
+ if (ret != RESET_COMPLETE)
+ goto err_clear_flag;
+
+ ret = 0;
+
+err_clear_flag:
+ clear_bit(RESET_PENDING, &uc->flags);
+
+ mutex_unlock(&uc->lock);
+
+ return ret;
+}
+
+static int
+ccg_cmd_write_flash_row(struct ucsi_ccg *uc, u16 row,
+ const void *data, u8 fcmd)
+{
+ struct i2c_client *client = uc->client;
+ struct ccg_cmd cmd;
+ u8 buf[CCG4_ROW_SIZE + 2];
+ u8 *p;
+ int ret;
+
+ /* Copy the data into the flash read/write memory. */
+ put_unaligned_le16(REG_FLASH_RW_MEM, buf);
+
+ memcpy(buf + 2, data, CCG4_ROW_SIZE);
+
+ mutex_lock(&uc->lock);
+
+ ret = i2c_master_send(client, buf, CCG4_ROW_SIZE + 2);
+ if (ret != CCG4_ROW_SIZE + 2) {
+ dev_err(uc->dev, "REG_FLASH_RW_MEM write fail %d\n", ret);
+ mutex_unlock(&uc->lock);
+ return ret < 0 ? ret : -EIO;
+ }
+
+ /* Use the FLASH_ROW_READ_WRITE register to trigger */
+ /* writing of data to the desired flash row */
+ p = (u8 *)&cmd.data;
+ cmd.reg = CCGX_RAB_FLASH_ROW_RW;
+ p[0] = FLASH_SIG;
+ p[1] = fcmd;
+ put_unaligned_le16(row, &p[2]);
+ cmd.len = 4;
+ cmd.delay = 50;
+ if (fcmd == FLASH_FWCT_SIG_WR_CMD)
+ cmd.delay += 400;
+ if (row == 510)
+ cmd.delay += 220;
+ ret = ccg_send_command(uc, &cmd);
+
+ mutex_unlock(&uc->lock);
+
+ if (ret != CMD_SUCCESS) {
+ dev_err(uc->dev, "write flash row failed ret=%d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ccg_cmd_validate_fw(struct ucsi_ccg *uc, unsigned int fwid)
+{
+ struct ccg_cmd cmd;
+ int ret;
+
+ cmd.reg = CCGX_RAB_VALIDATE_FW;
+ cmd.data = fwid;
+ cmd.len = 1;
+ cmd.delay = 500;
+
+ mutex_lock(&uc->lock);
+
+ ret = ccg_send_command(uc, &cmd);
+
+ mutex_unlock(&uc->lock);
+
+ if (ret != CMD_SUCCESS)
+ return ret;
+
+ return 0;
+}
+
+static bool ccg_check_vendor_version(struct ucsi_ccg *uc,
+ struct version_format *app,
+ struct fw_config_table *fw_cfg)
+{
+ struct device *dev = uc->dev;
+
+ /* Check if the fw build is for supported vendors */
+ if (le16_to_cpu(app->build) != uc->fw_build) {
+ dev_info(dev, "current fw is not from supported vendor\n");
+ return false;
+ }
+
+ /* Check if the new fw build is for supported vendors */
+ if (le16_to_cpu(fw_cfg->app.build) != uc->fw_build) {
+ dev_info(dev, "new fw is not from supported vendor\n");
+ return false;
+ }
+ return true;
+}
+
+static bool ccg_check_fw_version(struct ucsi_ccg *uc, const char *fw_name,
+ struct version_format *app)
+{
+ const struct firmware *fw = NULL;
+ struct device *dev = uc->dev;
+ struct fw_config_table fw_cfg;
+ u32 cur_version, new_version;
+ bool is_later = false;
+
+ if (request_firmware(&fw, fw_name, dev) != 0) {
+ dev_err(dev, "error: Failed to open cyacd file %s\n", fw_name);
+ return false;
+ }
+
+ /*
+ * check if signed fw
+ * last part of fw image is fw cfg table and signature
+ */
+ if (fw->size < sizeof(fw_cfg) + FW_CFG_TABLE_SIG_SIZE)
+ goto out_release_firmware;
+
+ memcpy((uint8_t *)&fw_cfg, fw->data + fw->size -
+ sizeof(fw_cfg) - FW_CFG_TABLE_SIG_SIZE, sizeof(fw_cfg));
+
+ if (fw_cfg.identity != ('F' | 'W' << 8 | 'C' << 16 | 'T' << 24)) {
+ dev_info(dev, "not a signed image\n");
+ goto out_release_firmware;
+ }
+
+ /* compare input version with FWCT version */
+ cur_version = le16_to_cpu(app->build) | app->patch << 16 |
+ app->ver << 24;
+
+ new_version = le16_to_cpu(fw_cfg.app.build) | fw_cfg.app.patch << 16 |
+ fw_cfg.app.ver << 24;
+
+ if (!ccg_check_vendor_version(uc, app, &fw_cfg))
+ goto out_release_firmware;
+
+ if (new_version > cur_version)
+ is_later = true;
+
+out_release_firmware:
+ release_firmware(fw);
+ return is_later;
+}
+
+static int ccg_fw_update_needed(struct ucsi_ccg *uc,
+ enum enum_flash_mode *mode)
+{
+ struct device *dev = uc->dev;
+ int err;
+ struct version_info version[3];
+
+ err = ccg_read(uc, CCGX_RAB_DEVICE_MODE, (u8 *)(&uc->info),
+ sizeof(uc->info));
+ if (err) {
+ dev_err(dev, "read device mode failed\n");
+ return err;
+ }
+
+ err = ccg_read(uc, CCGX_RAB_READ_ALL_VER, (u8 *)version,
+ sizeof(version));
+ if (err) {
+ dev_err(dev, "read device mode failed\n");
+ return err;
+ }
+
+ if (memcmp(&version[FW1], "\0\0\0\0\0\0\0\0",
+ sizeof(struct version_info)) == 0) {
+ dev_info(dev, "secondary fw is not flashed\n");
+ *mode = SECONDARY_BL;
+ } else if (le16_to_cpu(version[FW1].base.build) <
+ secondary_fw_min_ver) {
+ dev_info(dev, "secondary fw version is too low (< %d)\n",
+ secondary_fw_min_ver);
+ *mode = SECONDARY;
+ } else if (memcmp(&version[FW2], "\0\0\0\0\0\0\0\0",
+ sizeof(struct version_info)) == 0) {
+ dev_info(dev, "primary fw is not flashed\n");
+ *mode = PRIMARY;
+ } else if (ccg_check_fw_version(uc, ccg_fw_names[PRIMARY],
+ &version[FW2].app)) {
+ dev_info(dev, "found primary fw with later version\n");
+ *mode = PRIMARY;
+ } else {
+ dev_info(dev, "secondary and primary fw are the latest\n");
+ *mode = FLASH_NOT_NEEDED;
+ }
+ return 0;
+}
+
+static int do_flash(struct ucsi_ccg *uc, enum enum_flash_mode mode)
+{
+ struct device *dev = uc->dev;
+ const struct firmware *fw = NULL;
+ const char *p, *s;
+ const char *eof;
+ int err, row, len, line_sz, line_cnt = 0;
+ unsigned long start_time = jiffies;
+ struct fw_config_table fw_cfg;
+ u8 fw_cfg_sig[FW_CFG_TABLE_SIG_SIZE];
+ u8 *wr_buf;
+
+ err = request_firmware(&fw, ccg_fw_names[mode], dev);
+ if (err) {
+ dev_err(dev, "request %s failed err=%d\n",
+ ccg_fw_names[mode], err);
+ return err;
+ }
+
+ if (((uc->info.mode & CCG_DEVINFO_FWMODE_MASK) >>
+ CCG_DEVINFO_FWMODE_SHIFT) == FW2) {
+ err = ccg_cmd_port_control(uc, false);
+ if (err < 0)
+ goto release_fw;
+ err = ccg_cmd_jump_boot_mode(uc, 0);
+ if (err < 0)
+ goto release_fw;
+ }
+
+ eof = fw->data + fw->size;
+
+ /*
+ * check if signed fw
+ * last part of fw image is fw cfg table and signature
+ */
+ if (fw->size < sizeof(fw_cfg) + sizeof(fw_cfg_sig))
+ goto not_signed_fw;
+
+ memcpy((uint8_t *)&fw_cfg, fw->data + fw->size -
+ sizeof(fw_cfg) - sizeof(fw_cfg_sig), sizeof(fw_cfg));
+
+ if (fw_cfg.identity != ('F' | ('W' << 8) | ('C' << 16) | ('T' << 24))) {
+ dev_info(dev, "not a signed image\n");
+ goto not_signed_fw;
+ }
+ eof = fw->data + fw->size - sizeof(fw_cfg) - sizeof(fw_cfg_sig);
+
+ memcpy((uint8_t *)&fw_cfg_sig,
+ fw->data + fw->size - sizeof(fw_cfg_sig), sizeof(fw_cfg_sig));
+
+ /* flash fw config table and signature first */
+ err = ccg_cmd_write_flash_row(uc, 0, (u8 *)&fw_cfg,
+ FLASH_FWCT1_WR_CMD);
+ if (err)
+ goto release_fw;
+
+ err = ccg_cmd_write_flash_row(uc, 0, (u8 *)&fw_cfg + CCG4_ROW_SIZE,
+ FLASH_FWCT2_WR_CMD);
+ if (err)
+ goto release_fw;
+
+ err = ccg_cmd_write_flash_row(uc, 0, &fw_cfg_sig,
+ FLASH_FWCT_SIG_WR_CMD);
+ if (err)
+ goto release_fw;
+
+not_signed_fw:
+ wr_buf = kzalloc(CCG4_ROW_SIZE + 4, GFP_KERNEL);
+ if (!wr_buf)
+ return -ENOMEM;
+
+ err = ccg_cmd_enter_flashing(uc);
+ if (err)
+ goto release_mem;
+
+ /*****************************************************************
+ * CCG firmware image (.cyacd) file line format
+ *
+ * :00rrrrllll[dd....]cc/r/n
+ *
+ * :00 header
+ * rrrr is row number to flash (4 char)
+ * llll is data len to flash (4 char)
+ * dd is a data field represents one byte of data (512 char)
+ * cc is checksum (2 char)
+ * \r\n newline
+ *
+ * Total length: 3 + 4 + 4 + 512 + 2 + 2 = 527
+ *
+ *****************************************************************/
+
+ p = strnchr(fw->data, fw->size, ':');
+ while (p < eof) {
+ s = strnchr(p + 1, eof - p - 1, ':');
+
+ if (!s)
+ s = eof;
+
+ line_sz = s - p;
+
+ if (line_sz != CYACD_LINE_SIZE) {
+ dev_err(dev, "Bad FW format line_sz=%d\n", line_sz);
+ err = -EINVAL;
+ goto release_mem;
+ }
+
+ if (hex2bin(wr_buf, p + 3, CCG4_ROW_SIZE + 4)) {
+ err = -EINVAL;
+ goto release_mem;
+ }
+
+ row = get_unaligned_be16(wr_buf);
+ len = get_unaligned_be16(&wr_buf[2]);
+
+ if (len != CCG4_ROW_SIZE) {
+ err = -EINVAL;
+ goto release_mem;
+ }
+
+ err = ccg_cmd_write_flash_row(uc, row, wr_buf + 4,
+ FLASH_WR_CMD);
+ if (err)
+ goto release_mem;
+
+ line_cnt++;
+ p = s;
+ }
+
+ dev_info(dev, "total %d row flashed. time: %dms\n",
+ line_cnt, jiffies_to_msecs(jiffies - start_time));
+
+ err = ccg_cmd_validate_fw(uc, (mode == PRIMARY) ? FW2 : FW1);
+ if (err)
+ dev_err(dev, "%s validation failed err=%d\n",
+ (mode == PRIMARY) ? "FW2" : "FW1", err);
+ else
+ dev_info(dev, "%s validated\n",
+ (mode == PRIMARY) ? "FW2" : "FW1");
+
+ err = ccg_cmd_port_control(uc, false);
+ if (err < 0)
+ goto release_mem;
+
+ err = ccg_cmd_reset(uc);
+ if (err < 0)
+ goto release_mem;
+
+ err = ccg_cmd_port_control(uc, true);
+ if (err < 0)
+ goto release_mem;
+
+release_mem:
+ kfree(wr_buf);
+
+release_fw:
+ release_firmware(fw);
+ return err;
+}
+
+/*******************************************************************************
+ * CCG4 has two copies of the firmware in addition to the bootloader.
+ * If the device is running FW1, FW2 can be updated with the new version.
+ * Dual firmware mode allows the CCG device to stay in a PD contract and support
+ * USB PD and Type-C functionality while a firmware update is in progress.
+ ******************************************************************************/
+static int ccg_fw_update(struct ucsi_ccg *uc, enum enum_flash_mode flash_mode)
+{
+ int err;
+
+ while (flash_mode != FLASH_NOT_NEEDED) {
+ err = do_flash(uc, flash_mode);
+ if (err < 0)
+ return err;
+ err = ccg_fw_update_needed(uc, &flash_mode);
+ if (err < 0)
+ return err;
+ }
+ dev_info(uc->dev, "CCG FW update successful\n");
+
+ return err;
+}
+
+static int ccg_restart(struct ucsi_ccg *uc)
+{
+ struct device *dev = uc->dev;
+ int status;
+
+ status = ucsi_ccg_init(uc);
+ if (status < 0) {
+ dev_err(dev, "ucsi_ccg_start fail, err=%d\n", status);
+ return status;
+ }
+
+ status = request_threaded_irq(uc->irq, NULL, ccg_irq_handler,
+ IRQF_ONESHOT | IRQF_TRIGGER_HIGH,
+ dev_name(dev), uc);
+ if (status < 0) {
+ dev_err(dev, "request_threaded_irq failed - %d\n", status);
+ return status;
+ }
+
+ uc->ucsi = ucsi_register_ppm(dev, &uc->ppm);
+ if (IS_ERR(uc->ucsi)) {
+ dev_err(uc->dev, "ucsi_register_ppm failed\n");
+ return PTR_ERR(uc->ucsi);
+ }
+
+ return 0;
+}
+
+static void ccg_update_firmware(struct work_struct *work)
+{
+ struct ucsi_ccg *uc = container_of(work, struct ucsi_ccg, work);
+ enum enum_flash_mode flash_mode;
+ int status;
+
+ status = ccg_fw_update_needed(uc, &flash_mode);
+ if (status < 0)
+ return;
+
+ if (flash_mode != FLASH_NOT_NEEDED) {
+ ucsi_unregister_ppm(uc->ucsi);
+ free_irq(uc->irq, uc);
+
+ ccg_fw_update(uc, flash_mode);
+ ccg_restart(uc);
+ }
+}
+
+static ssize_t do_flash_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t n)
+{
+ struct ucsi_ccg *uc = i2c_get_clientdata(to_i2c_client(dev));
+ bool flash;
+
+ if (kstrtobool(buf, &flash))
+ return -EINVAL;
+
+ if (!flash)
+ return n;
+
+ if (uc->fw_build == 0x0) {
+ dev_err(dev, "fail to flash FW due to missing FW build info\n");
+ return -EINVAL;
+ }
+
+ schedule_work(&uc->work);
+ return n;
+}
+
+static DEVICE_ATTR_WO(do_flash);
+
+static struct attribute *ucsi_ccg_sysfs_attrs[] = {
+ &dev_attr_do_flash.attr,
+ NULL,
+};
+
+static struct attribute_group ucsi_ccg_attr_group = {
+ .attrs = ucsi_ccg_sysfs_attrs,
+};
+
static int ucsi_ccg_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
uc->ppm.sync = ucsi_ccg_sync;
uc->dev = dev;
uc->client = client;
+ mutex_init(&uc->lock);
+ INIT_WORK(&uc->work, ccg_update_firmware);
+
+ /* Only fail FW flashing when FW build information is not provided */
+ status = device_property_read_u16(dev, "ccgx,firmware-build",
+ &uc->fw_build);
+ if (status)
+ dev_err(uc->dev, "failed to get FW build information\n");
/* reset ccg device and initialize ucsi */
status = ucsi_ccg_init(uc);
return status;
}
- status = devm_request_threaded_irq(dev, client->irq, NULL,
- ccg_irq_handler,
- IRQF_ONESHOT | IRQF_TRIGGER_HIGH,
- dev_name(dev), uc);
+ status = get_fw_info(uc);
+ if (status < 0) {
+ dev_err(uc->dev, "get_fw_info failed - %d\n", status);
+ return status;
+ }
+
+ uc->port_num = 1;
+
+ if (uc->info.mode & CCG_DEVINFO_PDPORTS_MASK)
+ uc->port_num++;
+
+ status = request_threaded_irq(client->irq, NULL, ccg_irq_handler,
+ IRQF_ONESHOT | IRQF_TRIGGER_HIGH,
+ dev_name(dev), uc);
if (status < 0) {
dev_err(uc->dev, "request_threaded_irq failed - %d\n", status);
return status;
}
+ uc->irq = client->irq;
+
uc->ucsi = ucsi_register_ppm(dev, &uc->ppm);
if (IS_ERR(uc->ucsi)) {
dev_err(uc->dev, "ucsi_register_ppm failed\n");
}
i2c_set_clientdata(client, uc);
+
+ status = sysfs_create_group(&uc->dev->kobj, &ucsi_ccg_attr_group);
+ if (status)
+ dev_err(uc->dev, "cannot create sysfs group: %d\n", status);
+
return 0;
}
{
struct ucsi_ccg *uc = i2c_get_clientdata(client);
+ cancel_work_sync(&uc->work);
ucsi_unregister_ppm(uc->ucsi);
+ free_irq(uc->irq, uc);
+ sysfs_remove_group(&uc->dev->kobj, &ucsi_ccg_attr_group);
return 0;
}
req = (struct usb_ctrlrequest *) urb->setup_packet;
- return (req->bRequest == USB_REQ_CLEAR_FEATURE) &&
- (req->bRequestType == USB_RECIP_ENDPOINT) &&
- (req->wValue == USB_ENDPOINT_HALT);
+ return (req->bRequest == USB_REQ_CLEAR_FEATURE) &&
+ (req->bRequestType == USB_RECIP_ENDPOINT) &&
+ (req->wValue == USB_ENDPOINT_HALT);
}
static int is_set_interface_cmd(struct urb *urb)
case USB_PORT_FEAT_U1_TIMEOUT:
usbip_dbg_vhci_rh(
" SetPortFeature: USB_PORT_FEAT_U1_TIMEOUT\n");
+ /* Fall through */
case USB_PORT_FEAT_U2_TIMEOUT:
usbip_dbg_vhci_rh(
" SetPortFeature: USB_PORT_FEAT_U2_TIMEOUT\n");
static void vhci_tx_urb(struct urb *urb, struct vhci_device *vdev)
{
struct vhci_priv *priv;
- struct vhci_hcd *vhci_hcd;
+ struct vhci_hcd *vhci_hcd = vdev_to_vhci_hcd(vdev);
unsigned long flags;
- if (!vdev) {
- pr_err("could not get virtual device");
- return;
- }
- vhci_hcd = vdev_to_vhci_hcd(vdev);
-
priv = kzalloc(sizeof(struct vhci_priv), GFP_ATOMIC);
if (!priv) {
usbip_event_add(&vdev->ud, VDEV_EVENT_ERROR_MALLOC);
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * This header provides constants for AM654 SERDES.
+ */
+
+#ifndef _DT_BINDINGS_AM654_SERDES
+#define _DT_BINDINGS_AM654_SERDES
+
+#define AM654_SERDES_CMU_REFCLK 0
+#define AM654_SERDES_LO_REFCLK 1
+#define AM654_SERDES_RO_REFCLK 2
+
+#endif /* _DT_BINDINGS_AM654_SERDES */
* @set_mode: set the mode of the phy
* @reset: resetting the phy
* @calibrate: calibrate the phy
+ * @release: ops to be performed while the consumer relinquishes the PHY
* @owner: the module owner containing the ops
*/
struct phy_ops {
union phy_configure_opts *opts);
int (*reset)(struct phy *phy);
int (*calibrate)(struct phy *phy);
+ void (*release)(struct phy *phy);
struct module *owner;
};
struct urb {
/* private: usb core and host controller only fields in the urb */
struct kref kref; /* reference count of the URB */
+ int unlinked; /* unlink error code */
void *hcpriv; /* private data for host controller */
atomic_t use_count; /* concurrent submissions counter */
atomic_t reject; /* submissions will fail */
- int unlinked; /* unlink error code */
/* public: documented fields in the urb that can be used by drivers */
struct list_head urb_list; /* list head for use by the urb's
#include <linux/device.h>
#include <uapi/linux/usb/ch9.h>
+/**
+ * usb_ep_type_string() - Returns human readable-name of the endpoint type.
+ * @ep_type: The endpoint type to return human-readable name for. If it's not
+ * any of the types: USB_ENDPOINT_XFER_{CONTROL, ISOC, BULK, INT},
+ * usually got by usb_endpoint_type(), the string 'unknown' will be returned.
+ */
+extern const char *usb_ep_type_string(int ep_type);
+
/**
* usb_speed_string() - Returns human readable-name of the speed.
* @speed: The speed to return human-readable name for. If it's not
#ifdef CONFIG_PM
struct work_struct wakeup_work; /* for remote wakeup */
#endif
+ struct work_struct died_work; /* for when the device dies */
/*
* hardware info/state
/* The maximum number of ports one device can grab at once */
#define MAX_NUM_PORTS 16
-/* parity check flag */
-#define RELEVANT_IFLAG(iflag) (iflag & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK))
-
/* USB serial flags */
#define USB_SERIAL_WRITE_BUSY 0
+#define USB_SERIAL_THROTTLED 1
/**
* usb_serial_port: structure for the specific ports of a device.
* @flags: usb serial port flags
* @write_wait: a wait_queue_head_t used by the port.
* @work: work queue entry for the line discipline waking up.
- * @throttled: nonzero if the read urb is inactive to throttle the device
- * @throttle_req: nonzero if the tty wants to throttle us
* @dev: pointer to the serial device
*
* This structure is used by the usb-serial core and drivers for the specific
unsigned long flags;
wait_queue_head_t write_wait;
struct work_struct work;
- char throttled;
- char throttle_req;
unsigned long sysrq; /* sysrq timeout */
struct device dev;
};
* with partner.
* @set_pd_rx: Called to enable or disable reception of PD messages
* @set_roles: Called to set power and data roles
- * @start_drp_toggling:
- * Optional; if supported by hardware, called to start DRP
- * toggling. DRP toggling is stopped automatically if
- * a connection is established.
+ * @start_toggling:
+ * Optional; if supported by hardware, called to start dual-role
+ * toggling or single-role connection detection. Toggling stops
+ * automatically if a connection is established.
* @try_role: Optional; called to set a preferred role
* @pd_transmit:Called to transmit PD message
* @mux: Pointer to multiplexer data
int (*set_pd_rx)(struct tcpc_dev *dev, bool on);
int (*set_roles)(struct tcpc_dev *dev, bool attached,
enum typec_role role, enum typec_data_role data);
- int (*start_drp_toggling)(struct tcpc_dev *dev,
- enum typec_cc_status cc);
+ int (*start_toggling)(struct tcpc_dev *dev,
+ enum typec_port_type port_type,
+ enum typec_cc_status cc);
int (*try_role)(struct tcpc_dev *dev, int role);
int (*pd_transmit)(struct tcpc_dev *dev, enum tcpm_transmit_type type,
const struct pd_message *msg);
#include <linux/usb/typec_altmode.h>
#define USB_TYPEC_DP_SID 0xff01
+/* USB IF has not assigned a Standard ID (SID) for VirtualLink,
+ * so the manufacturers of VirtualLink adapters use their Vendor
+ * IDs as the SVID.
+ */
+#define USB_TYPEC_NVIDIA_VLINK_SID 0x955 /* NVIDIA VirtualLink */
#define USB_TYPEC_DP_MODE 1
/*