summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--MAINTAINERS3
-rw-r--r--arch/arm/dts/mt7629-rfb.dts8
-rw-r--r--arch/arm/dts/mt7629.dtsi41
-rw-r--r--arch/sandbox/dts/test.dts29
-rw-r--r--doc/device-tree-bindings/phy/phy-mtk-tphy.txt78
-rw-r--r--doc/device-tree-bindings/usb/mediatek,mtk-xhci.txt40
-rw-r--r--drivers/core/ofnode.c11
-rw-r--r--drivers/core/read.c5
-rw-r--r--drivers/phy/phy-mtk-tphy.c316
-rw-r--r--drivers/phy/phy-uclass.c97
-rw-r--r--drivers/usb/dwc3/core.c87
-rw-r--r--drivers/usb/dwc3/dwc3-generic.c7
-rw-r--r--drivers/usb/gadget/dwc2_udc_otg.c93
-rw-r--r--drivers/usb/host/Kconfig6
-rw-r--r--drivers/usb/host/Makefile1
-rw-r--r--drivers/usb/host/xhci-dwc3.c7
-rw-r--r--drivers/usb/host/xhci-mtk.c303
-rw-r--r--drivers/usb/host/xhci.c10
-rw-r--r--include/dm/ofnode.h8
-rw-r--r--include/dm/read.h13
-rw-r--r--include/dwc3-uboot.h11
-rw-r--r--include/generic-phy.h92
-rw-r--r--include/usb/xhci.h3
-rw-r--r--test/dm/ofnode.c21
-rw-r--r--test/dm/phy.c33
25 files changed, 1135 insertions, 188 deletions
diff --git a/MAINTAINERS b/MAINTAINERS
index 66f0b07263..ec59ce8b88 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -244,9 +244,12 @@ S: Maintained
F: arch/arm/mach-mediatek/
F: arch/arm/include/asm/arch-mediatek/
F: board/mediatek/
+F: doc/device-tree-bindings/phy/phy-mtk-*
+F: doc/device-tree-bindings/usb/mediatek,*
F: doc/README.mediatek
F: drivers/clk/mediatek/
F: drivers/mmc/mtk-sd.c
+F: drivers/phy/phy-mtk-*
F: drivers/pinctrl/mediatek/
F: drivers/power/domain/mtk-power-domain.c
F: drivers/ram/mediatek/
diff --git a/arch/arm/dts/mt7629-rfb.dts b/arch/arm/dts/mt7629-rfb.dts
index 687fe1c029..bf84f76344 100644
--- a/arch/arm/dts/mt7629-rfb.dts
+++ b/arch/arm/dts/mt7629-rfb.dts
@@ -82,6 +82,14 @@
status = "okay";
};
+&xhci {
+ status = "okay";
+};
+
+&u3phy {
+ status = "okay";
+};
+
&watchdog {
pinctrl-names = "default";
pinctrl-0 = <&watchdog_pins>;
diff --git a/arch/arm/dts/mt7629.dtsi b/arch/arm/dts/mt7629.dtsi
index 644d2da4a8..6850e0058d 100644
--- a/arch/arm/dts/mt7629.dtsi
+++ b/arch/arm/dts/mt7629.dtsi
@@ -11,6 +11,7 @@
#include <dt-bindings/interrupt-controller/arm-gic.h>
#include <dt-bindings/power/mt7629-power.h>
#include <dt-bindings/reset/mt7629-reset.h>
+#include <dt-bindings/phy/phy.h>
#include "skeleton.dtsi"
/ {
@@ -222,6 +223,46 @@
#size-cells = <0>;
};
+ ssusbsys: ssusbsys@1a000000 {
+ compatible = "mediatek,mt7629-ssusbsys", "syscon";
+ reg = <0x1a000000 0x1000>;
+ #clock-cells = <1>;
+ };
+
+ xhci: usb@1a0c0000 {
+ compatible = "mediatek,mt7629-xhci", "mediatek,mtk-xhci";
+ reg = <0x1a0c0000 0x1000>, <0x1a0c3e00 0x0100>;
+ reg-names = "mac", "ippc";
+ power-domains = <&scpsys MT7629_POWER_DOMAIN_HIF1>;
+ clocks = <&ssusbsys CLK_SSUSB_SYS_EN>,
+ <&ssusbsys CLK_SSUSB_REF_EN>,
+ <&ssusbsys CLK_SSUSB_MCU_EN>,
+ <&ssusbsys CLK_SSUSB_DMA_EN>;
+ clock-names = "sys_ck", "ref_ck", "mcu_ck", "dma_ck";
+ phys = <&u2port0 PHY_TYPE_USB2>, <&u3port0 PHY_TYPE_USB3>;
+ status = "disabled";
+ };
+
+ u3phy: usb-phy@1a0c4000 {
+ compatible = "mediatek,mt7629-tphy", "mediatek,generic-tphy-v2";
+ #address-cells = <1>;
+ #size-cells = <1>;
+ ranges = <0 0x1a0c4000 0x1000>;
+ status = "disabled";
+
+ u2port0: usb-phy@0 {
+ reg = <0x0 0x0700>;
+ #phy-cells = <1>;
+ clocks = <&ssusbsys CLK_SSUSB_U2_PHY_EN>;
+ clock-names = "ref";
+ };
+
+ u3port0: usb-phy@700 {
+ reg = <0x0700 0x0700>;
+ #phy-cells = <1>;
+ };
+ };
+
ethsys: syscon@1b000000 {
compatible = "mediatek,mt7629-ethsys", "syscon";
reg = <0x1b000000 0x1000>;
diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts
index 4bccfbe6e1..15cd2330a3 100644
--- a/arch/sandbox/dts/test.dts
+++ b/arch/sandbox/dts/test.dts
@@ -159,12 +159,23 @@
broken;
};
+ phy_provider2: gen_phy@2 {
+ compatible = "sandbox,phy";
+ #phy-cells = <0>;
+ };
+
gen_phy_user: gen_phy_user {
compatible = "simple-bus";
phys = <&phy_provider0 0>, <&phy_provider0 1>, <&phy_provider1>;
phy-names = "phy1", "phy2", "phy3";
};
+ gen_phy_user1: gen_phy_user1 {
+ compatible = "simple-bus";
+ phys = <&phy_provider0 0>, <&phy_provider2>;
+ phy-names = "phy1", "phy2";
+ };
+
some-bus {
#address-cells = <1>;
#size-cells = <0>;
@@ -218,6 +229,24 @@
compatible = "denx,u-boot-fdt-test1";
};
+ i-test {
+ compatible = "mediatek,u-boot-fdt-test";
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ subnode@0 {
+ reg = <0>;
+ };
+
+ subnode@1 {
+ reg = <1>;
+ };
+
+ subnode@2 {
+ reg = <2>;
+ };
+ };
+
devres-test {
compatible = "denx,u-boot-devres-test";
};
diff --git a/doc/device-tree-bindings/phy/phy-mtk-tphy.txt b/doc/device-tree-bindings/phy/phy-mtk-tphy.txt
index 037c5a4be5..8cd23d8c0b 100644
--- a/doc/device-tree-bindings/phy/phy-mtk-tphy.txt
+++ b/doc/device-tree-bindings/phy/phy-mtk-tphy.txt
@@ -7,10 +7,17 @@ controllers on MediaTek SoCs, such as, USB2.0, USB3.0, PCIe, and SATA.
Required properties (controller (parent) node):
- compatible : should be one of
"mediatek,generic-tphy-v1"
- - clocks : (deprecated, use port's clocks instead) a list of phandle +
- clock-specifier pairs, one for each entry in clock-names
- - clock-names : (deprecated, use port's one instead) must contain
- "u3phya_ref": for reference clock of usb3.0 analog phy.
+ "mediatek,generic-tphy-v2"
+
+- #address-cells: the number of cells used to represent physical
+ base addresses.
+- #size-cells: the number of cells used to represent the size of an address.
+- ranges: the address mapping relationship to the parent, defined with
+ - empty value: if optional 'reg' is used.
+ - non-empty value: if optional 'reg' is not used. should set
+ the child's base address to 0, the physical address
+ within parent's address space, and the length of
+ the address map.
Required nodes : a sub-node is required for each port the controller
provides. Address range information including the usual
@@ -27,12 +34,6 @@ Optional properties (controller (parent) node):
Required properties (port (child) node):
- reg : address and length of the register set for the port.
-- clocks : a list of phandle + clock-specifier pairs, one for each
- entry in clock-names
-- clock-names : must contain
- "ref": 48M reference clock for HighSpeed analog phy; and 26M
- reference clock for SuperSpeed analog phy, sometimes is
- 24M, 25M or 27M, depended on platform.
- #phy-cells : should be 1 (See second example)
cell after port phandle is phy type from:
- PHY_TYPE_USB2
@@ -40,6 +41,17 @@ Required properties (port (child) node):
- PHY_TYPE_PCIE
- PHY_TYPE_SATA
+Optional properties (port (child) node):
+- clocks : a list of phandle + clock-specifier pairs, one for each
+ entry in clock-names
+- clock-names : may contain
+ "ref": 48M reference clock for HighSpeed (digital) phy; and 26M
+ reference clock for SuperSpeed (digital) phy, sometimes is
+ 24M, 25M or 27M, depended on platform.
+ "da_ref": the reference clock of analog phy, used if the clocks
+ of analog and digital phys are separated, otherwise uses
+ "ref" clock only if needed.
+
Example:
u3phy2: usb-phy@1a244000 {
@@ -84,3 +96,49 @@ usb30: usb@11270000 {
phy-names = "usb2-0", "usb3-0";
...
};
+
+Layout differences of banks between TPHY V1 and V2
+-------------------------------------------------------------
+IP V1:
+port offset bank
+shared 0x0000 SPLLC
+ 0x0100 FMREG
+u2 port0 0x0800 U2PHY_COM
+u3 port0 0x0900 U3PHYD
+ 0x0a00 U3PHYD_BANK2
+ 0x0b00 U3PHYA
+ 0x0c00 U3PHYA_DA
+u2 port1 0x1000 U2PHY_COM
+u3 port1 0x1100 U3PHYD
+ 0x1200 U3PHYD_BANK2
+ 0x1300 U3PHYA
+ 0x1400 U3PHYA_DA
+u2 port2 0x1800 U2PHY_COM
+ ...
+
+IP V2:
+port offset bank
+u2 port0 0x0000 MISC
+ 0x0100 FMREG
+ 0x0300 U2PHY_COM
+u3 port0 0x0700 SPLLC
+ 0x0800 CHIP
+ 0x0900 U3PHYD
+ 0x0a00 U3PHYD_BANK2
+ 0x0b00 U3PHYA
+ 0x0c00 U3PHYA_DA
+u2 port1 0x1000 MISC
+ 0x1100 FMREG
+ 0x1300 U2PHY_COM
+u3 port1 0x1700 SPLLC
+ 0x1800 CHIP
+ 0x1900 U3PHYD
+ 0x1a00 U3PHYD_BANK2
+ 0x1b00 U3PHYA
+ 0x1c00 U3PHYA_DA
+u2 port2 0x2000 MISC
+ ...
+
+ SPLLC shared by u3 ports and FMREG shared by u2 ports on
+TPHY V1 are put back into each port; a new bank MISC for
+u2 ports and CHIP for u3 ports are added on TPHY V2.
diff --git a/doc/device-tree-bindings/usb/mediatek,mtk-xhci.txt b/doc/device-tree-bindings/usb/mediatek,mtk-xhci.txt
new file mode 100644
index 0000000000..0447468a2d
--- /dev/null
+++ b/doc/device-tree-bindings/usb/mediatek,mtk-xhci.txt
@@ -0,0 +1,40 @@
+MediaTek xHCI
+
+The device node for USB3 host controller on MediaTek SoCs.
+
+Required properties:
+ - compatible : should be "mediatek,mtk-xhci"
+ - reg : specifies physical base address and size of the registers
+ - reg-names: should be "mac" for xHCI MAC and "ippc" for IP port control
+ - power-domains : a phandle to USB power domain node to control USB's
+ MTCMOS
+ - vusb33-supply : regulator of USB avdd3.3v
+
+ - clocks : a list of phandle + clock-specifier pairs, one for each
+ entry in clock-names
+ - clock-names : must contain
+ "sys_ck": controller clock used by normal mode,
+ the following ones are optional:
+ "ref_ck": reference clock used by low power mode etc,
+ "mcu_ck": mcu_bus clock for register access,
+ "dma_ck": dma_bus clock for data transfer by DMA,
+ "xhci_ck": controller clock
+
+ - phys : list of all the USB PHYs on this HCD
+ - phy-names: name specifier for the USB PHY
+
+Optional properties:
+ - vbus-supply : reference to the VBUS regulator;
+
+Example:
+xhci: usb@1a0c0000 {
+ compatible = "mediatek,mt7629-xhci", "mediatek,mtk-xhci";
+ reg = <0x1a0c0000 0x1000>, <0x1a0c3e00 0x0100>;
+ reg-names = "mac", "ippc";
+ power-domains = <&scpsys MT7629_POWER_DOMAIN_HIF1>;
+ clocks = <&ssusbsys CLK_SSUSB_SYS_EN>, <&ssusbsys CLK_SSUSB_REF_EN>,
+ <&ssusbsys CLK_SSUSB_MCU_EN>, <&ssusbsys CLK_SSUSB_DMA_EN>;
+ clock-names = "sys_ck", "ref_ck", "mcu_ck", "dma_ck";
+ phys = <&u2port0 PHY_TYPE_USB2>, <&u3port0 PHY_TYPE_USB3>;
+ status = "disabled";
+};
diff --git a/drivers/core/ofnode.c b/drivers/core/ofnode.c
index 20871a6815..e3c42dae5c 100644
--- a/drivers/core/ofnode.c
+++ b/drivers/core/ofnode.c
@@ -474,6 +474,17 @@ ofnode ofnode_get_chosen_node(const char *name)
return ofnode_path(prop);
}
+int ofnode_get_child_count(ofnode parent)
+{
+ ofnode child;
+ int num = 0;
+
+ ofnode_for_each_subnode(child, parent)
+ num++;
+
+ return num;
+}
+
static int decode_timing_property(ofnode node, const char *name,
struct timing_entry *result)
{
diff --git a/drivers/core/read.c b/drivers/core/read.c
index 47b8e03446..3d421f7a69 100644
--- a/drivers/core/read.c
+++ b/drivers/core/read.c
@@ -352,3 +352,8 @@ fdt_addr_t dev_read_addr_pci(const struct udevice *dev)
return addr;
}
+
+int dev_get_child_count(const struct udevice *dev)
+{
+ return ofnode_get_child_count(dev_ofnode(dev));
+}
diff --git a/drivers/phy/phy-mtk-tphy.c b/drivers/phy/phy-mtk-tphy.c
index bd089b7a43..81525a48b7 100644
--- a/drivers/phy/phy-mtk-tphy.c
+++ b/drivers/phy/phy-mtk-tphy.c
@@ -20,11 +20,77 @@
/* version V1 sub-banks offset base address */
/* banks shared by multiple phys */
#define SSUSB_SIFSLV_V1_SPLLC 0x000 /* shared by u3 phys */
+#define SSUSB_SIFSLV_V1_U2FREQ 0x100 /* shared by u2 phys */
#define SSUSB_SIFSLV_V1_CHIP 0x300 /* shared by u3 phys */
+/* u2 phy bank */
+#define SSUSB_SIFSLV_V1_U2PHY_COM 0x000
/* u3/pcie/sata phy banks */
#define SSUSB_SIFSLV_V1_U3PHYD 0x000
#define SSUSB_SIFSLV_V1_U3PHYA 0x200
+/* version V2 sub-banks offset base address */
+/* u2 phy banks */
+#define SSUSB_SIFSLV_V2_MISC 0x000
+#define SSUSB_SIFSLV_V2_U2FREQ 0x100
+#define SSUSB_SIFSLV_V2_U2PHY_COM 0x300
+/* u3/pcie/sata phy banks */
+#define SSUSB_SIFSLV_V2_SPLLC 0x000
+#define SSUSB_SIFSLV_V2_CHIP 0x100
+#define SSUSB_SIFSLV_V2_U3PHYD 0x200
+#define SSUSB_SIFSLV_V2_U3PHYA 0x400
+
+#define U3P_USBPHYACR0 0x000
+#define PA0_RG_U2PLL_FORCE_ON BIT(15)
+#define PA0_RG_USB20_INTR_EN BIT(5)
+
+#define U3P_USBPHYACR5 0x014
+#define PA5_RG_U2_HSTX_SRCAL_EN BIT(15)
+#define PA5_RG_U2_HSTX_SRCTRL GENMASK(14, 12)
+#define PA5_RG_U2_HSTX_SRCTRL_VAL(x) ((0x7 & (x)) << 12)
+#define PA5_RG_U2_HS_100U_U3_EN BIT(11)
+
+#define U3P_USBPHYACR6 0x018
+#define PA6_RG_U2_BC11_SW_EN BIT(23)
+#define PA6_RG_U2_OTG_VBUSCMP_EN BIT(20)
+#define PA6_RG_U2_SQTH GENMASK(3, 0)
+#define PA6_RG_U2_SQTH_VAL(x) (0xf & (x))
+
+#define U3P_U2PHYACR4 0x020
+#define P2C_RG_USB20_GPIO_CTL BIT(9)
+#define P2C_USB20_GPIO_MODE BIT(8)
+#define P2C_U2_GPIO_CTR_MSK \
+ (P2C_RG_USB20_GPIO_CTL | P2C_USB20_GPIO_MODE)
+
+#define U3P_U2PHYDTM0 0x068
+#define P2C_FORCE_UART_EN BIT(26)
+#define P2C_FORCE_DATAIN BIT(23)
+#define P2C_FORCE_DM_PULLDOWN BIT(21)
+#define P2C_FORCE_DP_PULLDOWN BIT(20)
+#define P2C_FORCE_XCVRSEL BIT(19)
+#define P2C_FORCE_SUSPENDM BIT(18)
+#define P2C_FORCE_TERMSEL BIT(17)
+#define P2C_RG_DATAIN GENMASK(13, 10)
+#define P2C_RG_DATAIN_VAL(x) ((0xf & (x)) << 10)
+#define P2C_RG_DMPULLDOWN BIT(7)
+#define P2C_RG_DPPULLDOWN BIT(6)
+#define P2C_RG_XCVRSEL GENMASK(5, 4)
+#define P2C_RG_XCVRSEL_VAL(x) ((0x3 & (x)) << 4)
+#define P2C_RG_SUSPENDM BIT(3)
+#define P2C_RG_TERMSEL BIT(2)
+#define P2C_DTM0_PART_MASK \
+ (P2C_FORCE_DATAIN | P2C_FORCE_DM_PULLDOWN | \
+ P2C_FORCE_DP_PULLDOWN | P2C_FORCE_XCVRSEL | \
+ P2C_FORCE_TERMSEL | P2C_RG_DMPULLDOWN | \
+ P2C_RG_DPPULLDOWN | P2C_RG_TERMSEL)
+
+#define U3P_U2PHYDTM1 0x06C
+#define P2C_RG_UART_EN BIT(16)
+#define P2C_FORCE_IDDIG BIT(9)
+#define P2C_RG_VBUSVALID BIT(5)
+#define P2C_RG_SESSEND BIT(4)
+#define P2C_RG_AVALID BIT(2)
+#define P2C_RG_IDDIG BIT(1)
+
#define U3P_U3_CHIP_GPIO_CTLD 0x0c
#define P3C_REG_IP_SW_RST BIT(31)
#define P3C_MCU_BUS_CK_GATE_EN BIT(30)
@@ -42,6 +108,14 @@
#define P3A_RG_CLKDRV_AMP GENMASK(31, 29)
#define P3A_RG_CLKDRV_AMP_VAL(x) ((0x7 & (x)) << 29)
+#define U3P_U3_PHYA_REG6 0x018
+#define P3A_RG_TX_EIDLE_CM GENMASK(31, 28)
+#define P3A_RG_TX_EIDLE_CM_VAL(x) ((0xf & (x)) << 28)
+
+#define U3P_U3_PHYA_REG9 0x024
+#define P3A_RG_RX_DAC_MUX GENMASK(5, 1)
+#define P3A_RG_RX_DAC_MUX_VAL(x) ((0x1f & (x)) << 1)
+
#define U3P_U3_PHYA_DA_REG0 0x100
#define P3A_RG_XTAL_EXT_PE2H GENMASK(17, 16)
#define P3A_RG_XTAL_EXT_PE2H_VAL(x) ((0x3 & (x)) << 16)
@@ -77,6 +151,16 @@
#define P3A_RG_PLL_DELTA_PE2H GENMASK(15, 0)
#define P3A_RG_PLL_DELTA_PE2H_VAL(x) (0xffff & (x))
+#define U3P_U3_PHYD_LFPS1 0x00c
+#define P3D_RG_FWAKE_TH GENMASK(21, 16)
+#define P3D_RG_FWAKE_TH_VAL(x) ((0x3f & (x)) << 16)
+
+#define U3P_U3_PHYD_CDR1 0x05c
+#define P3D_RG_CDR_BIR_LTD1 GENMASK(28, 24)
+#define P3D_RG_CDR_BIR_LTD1_VAL(x) ((0x1f & (x)) << 24)
+#define P3D_RG_CDR_BIR_LTD0 GENMASK(12, 8)
+#define P3D_RG_CDR_BIR_LTD0_VAL(x) ((0x1f & (x)) << 8)
+
#define U3P_U3_PHYD_RXDET1 0x128
#define P3D_RG_RXDET_STB2_SET GENMASK(17, 9)
#define P3D_RG_RXDET_STB2_SET_VAL(x) ((0x1ff & (x)) << 9)
@@ -85,6 +169,21 @@
#define P3D_RG_RXDET_STB2_SET_P3 GENMASK(8, 0)
#define P3D_RG_RXDET_STB2_SET_P3_VAL(x) (0x1ff & (x))
+#define U3P_SPLLC_XTALCTL3 0x018
+#define XC3_RG_U3_XTAL_RX_PWD BIT(9)
+#define XC3_RG_U3_FRC_XTAL_RX_PWD BIT(8)
+
+enum mtk_phy_version {
+ MTK_TPHY_V1 = 1,
+ MTK_TPHY_V2,
+};
+
+struct u2phy_banks {
+ void __iomem *misc;
+ void __iomem *fmreg;
+ void __iomem *com;
+};
+
struct u3phy_banks {
void __iomem *spllc;
void __iomem *chip;
@@ -95,26 +194,136 @@ struct u3phy_banks {
struct mtk_phy_instance {
void __iomem *port_base;
const struct device_node *np;
+ union {
+ struct u2phy_banks u2_banks;
+ struct u3phy_banks u3_banks;
+ };
- struct u3phy_banks u3_banks;
-
- /* reference clock of anolog phy */
- struct clk ref_clk;
+ struct clk ref_clk; /* reference clock of (digital) phy */
+ struct clk da_ref_clk; /* reference clock of analog phy */
u32 index;
- u8 type;
+ u32 type;
};
struct mtk_tphy {
+ struct udevice *dev;
void __iomem *sif_base;
+ enum mtk_phy_version version;
struct mtk_phy_instance **phys;
int nphys;
};
+static void u2_phy_instance_init(struct mtk_tphy *tphy,
+ struct mtk_phy_instance *instance)
+{
+ struct u2phy_banks *u2_banks = &instance->u2_banks;
+
+ /* switch to USB function, and enable usb pll */
+ clrsetbits_le32(u2_banks->com + U3P_U2PHYDTM0,
+ P2C_FORCE_UART_EN | P2C_FORCE_SUSPENDM,
+ P2C_RG_XCVRSEL_VAL(1) | P2C_RG_DATAIN_VAL(0));
+
+ clrbits_le32(u2_banks->com + U3P_U2PHYDTM1, P2C_RG_UART_EN);
+ setbits_le32(u2_banks->com + U3P_USBPHYACR0, PA0_RG_USB20_INTR_EN);
+
+ /* disable switch 100uA current to SSUSB */
+ clrbits_le32(u2_banks->com + U3P_USBPHYACR5, PA5_RG_U2_HS_100U_U3_EN);
+
+ clrbits_le32(u2_banks->com + U3P_U2PHYACR4, P2C_U2_GPIO_CTR_MSK);
+
+ /* DP/DM BC1.1 path Disable */
+ clrsetbits_le32(u2_banks->com + U3P_USBPHYACR6,
+ PA6_RG_U2_BC11_SW_EN | PA6_RG_U2_SQTH,
+ PA6_RG_U2_SQTH_VAL(2));
+
+ /* set HS slew rate */
+ clrsetbits_le32(u2_banks->com + U3P_USBPHYACR5,
+ PA5_RG_U2_HSTX_SRCTRL, PA5_RG_U2_HSTX_SRCTRL_VAL(4));
+
+ dev_dbg(tphy->dev, "%s(%d)\n", __func__, instance->index);
+}
+
+static void u2_phy_instance_power_on(struct mtk_tphy *tphy,
+ struct mtk_phy_instance *instance)
+{
+ struct u2phy_banks *u2_banks = &instance->u2_banks;
+
+ clrbits_le32(u2_banks->com + U3P_U2PHYDTM0,
+ P2C_RG_XCVRSEL | P2C_RG_DATAIN | P2C_DTM0_PART_MASK);
+
+ /* OTG Enable */
+ setbits_le32(u2_banks->com + U3P_USBPHYACR6,
+ PA6_RG_U2_OTG_VBUSCMP_EN);
+
+ clrsetbits_le32(u2_banks->com + U3P_U2PHYDTM1,
+ P2C_RG_SESSEND, P2C_RG_VBUSVALID | P2C_RG_AVALID);
+
+ dev_dbg(tphy->dev, "%s(%d)\n", __func__, instance->index);
+}
+
+static void u2_phy_instance_power_off(struct mtk_tphy *tphy,
+ struct mtk_phy_instance *instance)
+{
+ struct u2phy_banks *u2_banks = &instance->u2_banks;
+
+ clrbits_le32(u2_banks->com + U3P_U2PHYDTM0,
+ P2C_RG_XCVRSEL | P2C_RG_DATAIN);
+
+ /* OTG Disable */
+ clrbits_le32(u2_banks->com + U3P_USBPHYACR6,
+ PA6_RG_U2_OTG_VBUSCMP_EN);
+
+ clrsetbits_le32(u2_banks->com + U3P_U2PHYDTM1,
+ P2C_RG_VBUSVALID | P2C_RG_AVALID, P2C_RG_SESSEND);
+
+ dev_dbg(tphy->dev, "%s(%d)\n", __func__, instance->index);
+}
+
+static void u3_phy_instance_init(struct mtk_tphy *tphy,
+ struct mtk_phy_instance *instance)
+{
+ struct u3phy_banks *u3_banks = &instance->u3_banks;
+
+ /* gating PCIe Analog XTAL clock */
+ setbits_le32(u3_banks->spllc + U3P_SPLLC_XTALCTL3,
+ XC3_RG_U3_XTAL_RX_PWD | XC3_RG_U3_FRC_XTAL_RX_PWD);
+
+ /* gating XSQ */
+ clrsetbits_le32(u3_banks->phya + U3P_U3_PHYA_DA_REG0,
+ P3A_RG_XTAL_EXT_EN_U3, P3A_RG_XTAL_EXT_EN_U3_VAL(2));
+
+ clrsetbits_le32(u3_banks->phya + U3P_U3_PHYA_REG9,
+ P3A_RG_RX_DAC_MUX, P3A_RG_RX_DAC_MUX_VAL(4));
+
+ clrsetbits_le32(u3_banks->phya + U3P_U3_PHYA_REG6,
+ P3A_RG_TX_EIDLE_CM, P3A_RG_TX_EIDLE_CM_VAL(0xe));
+
+ clrsetbits_le32(u3_banks->phyd + U3P_U3_PHYD_CDR1,
+ P3D_RG_CDR_BIR_LTD0 | P3D_RG_CDR_BIR_LTD1,
+ P3D_RG_CDR_BIR_LTD0_VAL(0xc) |
+ P3D_RG_CDR_BIR_LTD1_VAL(0x3));
+
+ clrsetbits_le32(u3_banks->phyd + U3P_U3_PHYD_LFPS1,
+ P3D_RG_FWAKE_TH, P3D_RG_FWAKE_TH_VAL(0x34));
+
+ clrsetbits_le32(u3_banks->phyd + U3P_U3_PHYD_RXDET1,
+ P3D_RG_RXDET_STB2_SET, P3D_RG_RXDET_STB2_SET_VAL(0x10));
+
+ clrsetbits_le32(u3_banks->phyd + U3P_U3_PHYD_RXDET2,
+ P3D_RG_RXDET_STB2_SET_P3,
+ P3D_RG_RXDET_STB2_SET_P3_VAL(0x10));
+
+ dev_dbg(tphy->dev, "%s(%d)\n", __func__, instance->index);
+}
+
static void pcie_phy_instance_init(struct mtk_tphy *tphy,
struct mtk_phy_instance *instance)
{
struct u3phy_banks *u3_banks = &instance->u3_banks;
+ if (tphy->version != MTK_TPHY_V1)
+ return;
+
clrsetbits_le32(u3_banks->phya + U3P_U3_PHYA_DA_REG0,
P3A_RG_XTAL_EXT_PE1H | P3A_RG_XTAL_EXT_PE2H,
P3A_RG_XTAL_EXT_PE1H_VAL(0x2) |
@@ -187,9 +396,16 @@ static void pcie_phy_instance_power_off(struct mtk_tphy *tphy,
static void phy_v1_banks_init(struct mtk_tphy *tphy,
struct mtk_phy_instance *instance)
{
+ struct u2phy_banks *u2_banks = &instance->u2_banks;
struct u3phy_banks *u3_banks = &instance->u3_banks;
switch (instance->type) {
+ case PHY_TYPE_USB2:
+ u2_banks->misc = NULL;
+ u2_banks->fmreg = tphy->sif_base + SSUSB_SIFSLV_V1_U2FREQ;
+ u2_banks->com = instance->port_base + SSUSB_SIFSLV_V1_U2PHY_COM;
+ break;
+ case PHY_TYPE_USB3:
case PHY_TYPE_PCIE:
u3_banks->spllc = tphy->sif_base + SSUSB_SIFSLV_V1_SPLLC;
u3_banks->chip = tphy->sif_base + SSUSB_SIFSLV_V1_CHIP;
@@ -197,6 +413,32 @@ static void phy_v1_banks_init(struct mtk_tphy *tphy,
u3_banks->phya = instance->port_base + SSUSB_SIFSLV_V1_U3PHYA;
break;
default:
+ dev_err(tphy->dev, "incompatible PHY type\n");
+ return;
+ }
+}
+
+static void phy_v2_banks_init(struct mtk_tphy *tphy,
+ struct mtk_phy_instance *instance)
+{
+ struct u2phy_banks *u2_banks = &instance->u2_banks;
+ struct u3phy_banks *u3_banks = &instance->u3_banks;
+
+ switch (instance->type) {
+ case PHY_TYPE_USB2:
+ u2_banks->misc = instance->port_base + SSUSB_SIFSLV_V2_MISC;
+ u2_banks->fmreg = instance->port_base + SSUSB_SIFSLV_V2_U2FREQ;
+ u2_banks->com = instance->port_base + SSUSB_SIFSLV_V2_U2PHY_COM;
+ break;
+ case PHY_TYPE_USB3:
+ case PHY_TYPE_PCIE:
+ u3_banks->spllc = instance->port_base + SSUSB_SIFSLV_V2_SPLLC;
+ u3_banks->chip = instance->port_base + SSUSB_SIFSLV_V2_CHIP;
+ u3_banks->phyd = instance->port_base + SSUSB_SIFSLV_V2_U3PHYD;
+ u3_banks->phya = instance->port_base + SSUSB_SIFSLV_V2_U3PHYA;
+ break;
+ default:
+ dev_err(tphy->dev, "incompatible PHY type\n");
return;
}
}
@@ -208,14 +450,30 @@ static int mtk_phy_init(struct phy *phy)
int ret;
ret = clk_enable(&instance->ref_clk);
- if (ret)
+ if (ret < 0) {
+ dev_err(tphy->dev, "failed to enable ref_clk\n");
+ return ret;
+ }
+
+ ret = clk_enable(&instance->da_ref_clk);
+ if (ret < 0) {
+ dev_err(tphy->dev, "failed to enable da_ref_clk %d\n", ret);
+ clk_disable(&instance->ref_clk);
return ret;
+ }
switch (instance->type) {
+ case PHY_TYPE_USB2:
+ u2_phy_instance_init(tphy, instance);
+ break;
+ case PHY_TYPE_USB3:
+ u3_phy_instance_init(tphy, instance);
+ break;
case PHY_TYPE_PCIE:
pcie_phy_instance_init(tphy, instance);
break;
default:
+ dev_err(tphy->dev, "incompatible PHY type\n");
return -EINVAL;
}
@@ -227,7 +485,10 @@ static int mtk_phy_power_on(struct phy *phy)
struct mtk_tphy *tphy = dev_get_priv(phy->dev);
struct mtk_phy_instance *instance = tphy->phys[phy->id];
- pcie_phy_instance_power_on(tphy, instance);
+ if (instance->type == PHY_TYPE_USB2)
+ u2_phy_instance_power_on(tphy, instance);
+ else if (instance->type == PHY_TYPE_PCIE)
+ pcie_phy_instance_power_on(tphy, instance);
return 0;
}
@@ -237,7 +498,10 @@ static int mtk_phy_power_off(struct phy *phy)
struct mtk_tphy *tphy = dev_get_priv(phy->dev);
struct mtk_phy_instance *instance = tphy->phys[phy->id];
- pcie_phy_instance_power_off(tphy, instance);
+ if (instance->type == PHY_TYPE_USB2)
+ u2_phy_instance_power_off(tphy, instance);
+ else if (instance->type == PHY_TYPE_PCIE)
+ pcie_phy_instance_power_off(tphy, instance);
return 0;
}
@@ -247,6 +511,7 @@ static int mtk_phy_exit(struct phy *phy)
struct mtk_tphy *tphy = dev_get_priv(phy->dev);
struct mtk_phy_instance *instance = tphy->phys[phy->id];
+ clk_disable(&instance->da_ref_clk);
clk_disable(&instance->ref_clk);
return 0;
@@ -285,13 +550,19 @@ static int mtk_phy_xlate(struct phy *phy,
instance->type = args->args[1];
if (!(instance->type == PHY_TYPE_USB2 ||
instance->type == PHY_TYPE_USB3 ||
- instance->type == PHY_TYPE_PCIE ||
- instance->type == PHY_TYPE_SATA)) {
+ instance->type == PHY_TYPE_PCIE)) {
dev_err(phy->dev, "unsupported device type\n");
return -EINVAL;
}
- phy_v1_banks_init(tphy, instance);
+ if (tphy->version == MTK_TPHY_V1) {
+ phy_v1_banks_init(tphy, instance);
+ } else if (tphy->version == MTK_TPHY_V2) {
+ phy_v2_banks_init(tphy, instance);
+ } else {
+ dev_err(phy->dev, "phy version is not supported\n");
+ return -EINVAL;
+ }
return 0;
}
@@ -310,17 +581,22 @@ static int mtk_tphy_probe(struct udevice *dev)
ofnode subnode;
int index = 0;
- dev_for_each_subnode(subnode, dev)
- tphy->nphys++;
+ tphy->nphys = dev_get_child_count(dev);
tphy->phys = devm_kcalloc(dev, tphy->nphys, sizeof(*tphy->phys),
GFP_KERNEL);
if (!tphy->phys)
return -ENOMEM;
- tphy->sif_base = dev_read_addr_ptr(dev);
- if (!tphy->sif_base)
- return -ENOENT;
+ tphy->dev = dev;
+ tphy->version = dev_get_driver_data(dev);
+
+ /* v1 has shared banks */
+ if (tphy->version == MTK_TPHY_V1) {
+ tphy->sif_base = dev_read_addr_ptr(dev);
+ if (!tphy->sif_base)
+ return -ENOENT;
+ }
dev_for_each_subnode(subnode, dev) {
struct mtk_phy_instance *instance;
@@ -345,13 +621,19 @@ static int mtk_tphy_probe(struct udevice *dev)
&instance->ref_clk);
if (err)
return err;
+
+ err = clk_get_optional_nodev(subnode, "da_ref",
+ &instance->da_ref_clk);
+ if (err)
+ return err;
}
return 0;
}
static const struct udevice_id mtk_tphy_id_table[] = {
- { .compatible = "mediatek,generic-tphy-v1", },
+ { .compatible = "mediatek,generic-tphy-v1", .data = MTK_TPHY_V1, },
+ { .compatible = "mediatek,generic-tphy-v2", .data = MTK_TPHY_V2, },
{ }
};
diff --git a/drivers/phy/phy-uclass.c b/drivers/phy/phy-uclass.c
index e463b0b400..6ab78448af 100644
--- a/drivers/phy/phy-uclass.c
+++ b/drivers/phy/phy-uclass.c
@@ -6,6 +6,7 @@
#include <common.h>
#include <dm.h>
+#include <dm/devres.h>
#include <generic-phy.h>
static inline struct phy_ops *phy_dev_ops(struct udevice *dev)
@@ -167,6 +168,102 @@ int generic_phy_power_off(struct phy *phy)
return ops->power_off ? ops->power_off(phy) : 0;
}
+int generic_phy_get_bulk(struct udevice *dev, struct phy_bulk *bulk)
+{
+ int i, ret, count;
+
+ bulk->count = 0;
+
+ /* Return if no phy declared */
+ if (!dev_read_prop(dev, "phys", NULL))
+ return 0;
+
+ count = dev_count_phandle_with_args(dev, "phys", "#phy-cells");
+ if (count < 1)
+ return count;
+
+ bulk->phys = devm_kcalloc(dev, count, sizeof(struct phy), GFP_KERNEL);
+ if (!bulk->phys)
+ return -ENOMEM;
+
+ for (i = 0; i < count; i++) {
+ ret = generic_phy_get_by_index(dev, i, &bulk->phys[i]);
+ if (ret) {
+ pr_err("Failed to get PHY%d for %s\n", i, dev->name);
+ return ret;
+ }
+ bulk->count++;
+ }
+
+ return 0;
+}
+
+int generic_phy_init_bulk(struct phy_bulk *bulk)
+{
+ struct phy *phys = bulk->phys;
+ int i, ret;
+
+ for (i = 0; i < bulk->count; i++) {
+ ret = generic_phy_init(&phys[i]);
+ if (ret) {
+ pr_err("Can't init PHY%d\n", i);
+ goto phys_init_err;
+ }
+ }
+
+ return 0;
+
+phys_init_err:
+ for (; i > 0; i--)
+ generic_phy_exit(&phys[i - 1]);
+
+ return ret;
+}
+
+int generic_phy_exit_bulk(struct phy_bulk *bulk)
+{
+ struct phy *phys = bulk->phys;
+ int i, ret = 0;
+
+ for (i = 0; i < bulk->count; i++)
+ ret |= generic_phy_exit(&phys[i]);
+
+ return ret;
+}
+
+int generic_phy_power_on_bulk(struct phy_bulk *bulk)
+{
+ struct phy *phys = bulk->phys;
+ int i, ret;
+
+ for (i = 0; i < bulk->count; i++) {
+ ret = generic_phy_power_on(&phys[i]);
+ if (ret) {
+ pr_err("Can't power on PHY%d\n", i);
+ goto phys_poweron_err;
+ }
+ }
+
+ return 0;
+
+phys_poweron_err:
+ for (; i > 0; i--)
+ generic_phy_power_off(&phys[i - 1]);
+
+ return ret;
+}
+
+int generic_phy_power_off_bulk(struct phy_bulk *bulk)
+{
+ struct phy *phys = bulk->phys;
+ int i, ret = 0;
+
+ for (i = 0; i < bulk->count; i++)
+ ret |= generic_phy_power_off(&phys[i]);
+
+ return ret;
+}
+
UCLASS_DRIVER(phy) = {
.id = UCLASS_PHY,
.name = "phy",
diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c
index 4ec3f6df6a..a8982bdc09 100644
--- a/drivers/usb/dwc3/core.c
+++ b/drivers/usb/dwc3/core.c
@@ -838,87 +838,32 @@ MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("DesignWare USB3 DRD Controller Driver");
#if CONFIG_IS_ENABLED(PHY) && CONFIG_IS_ENABLED(DM_USB)
-int dwc3_setup_phy(struct udevice *dev, struct phy **array, int *num_phys)
+int dwc3_setup_phy(struct udevice *dev, struct phy_bulk *phys)
{
- int i, ret, count;
- struct phy *usb_phys;
-
- /* Return if no phy declared */
- if (!dev_read_prop(dev, "phys", NULL))
- return 0;
- count = dev_count_phandle_with_args(dev, "phys", "#phy-cells");
- if (count <= 0)
- return count;
-
- usb_phys = devm_kcalloc(dev, count, sizeof(struct phy),
- GFP_KERNEL);
- if (!usb_phys)
- return -ENOMEM;
-
- for (i = 0; i < count; i++) {
- ret = generic_phy_get_by_index(dev, i, &usb_phys[i]);
- if (ret && ret != -ENOENT) {
- pr_err("Failed to get USB PHY%d for %s\n",
- i, dev->name);
- return ret;
- }
- }
-
- for (i = 0; i < count; i++) {
- ret = generic_phy_init(&usb_phys[i]);
- if (ret) {
- pr_err("Can't init USB PHY%d for %s\n",
- i, dev->name);
- goto phys_init_err;
- }
- }
-
- for (i = 0; i < count; i++) {
- ret = generic_phy_power_on(&usb_phys[i]);
- if (ret) {
- pr_err("Can't power USB PHY%d for %s\n",
- i, dev->name);
- goto phys_poweron_err;
- }
- }
-
- *array = usb_phys;
- *num_phys = count;
- return 0;
-
-phys_poweron_err:
- for (i = count - 1; i >= 0; i--)
- generic_phy_power_off(&usb_phys[i]);
+ int ret;
- for (i = 0; i < count; i++)
- generic_phy_exit(&usb_phys[i]);
+ ret = generic_phy_get_bulk(dev, phys);
+ if (ret)
+ return ret;
- return ret;
+ ret = generic_phy_init_bulk(phys);
+ if (ret)
+ return ret;
-phys_init_err:
- for (; i >= 0; i--)
- generic_phy_exit(&usb_phys[i]);
+ ret = generic_phy_power_on_bulk(phys);
+ if (ret)
+ generic_phy_exit_bulk(phys);
return ret;
}
-int dwc3_shutdown_phy(struct udevice *dev, struct phy *usb_phys, int num_phys)
+int dwc3_shutdown_phy(struct udevice *dev, struct phy_bulk *phys)
{
- int i, ret;
-
- for (i = 0; i < num_phys; i++) {
- if (!generic_phy_valid(&usb_phys[i]))
- continue;
-
- ret = generic_phy_power_off(&usb_phys[i]);
- ret |= generic_phy_exit(&usb_phys[i]);
- if (ret) {
- pr_err("Can't shutdown USB PHY%d for %s\n",
- i, dev->name);
- }
- }
+ int ret;
- return 0;
+ ret = generic_phy_power_off_bulk(phys);
+ ret |= generic_phy_exit_bulk(phys);
+ return ret;
}
#endif
diff --git a/drivers/usb/dwc3/dwc3-generic.c b/drivers/usb/dwc3/dwc3-generic.c
index febcfc0f54..eabd53a36d 100644
--- a/drivers/usb/dwc3/dwc3-generic.c
+++ b/drivers/usb/dwc3/dwc3-generic.c
@@ -33,8 +33,7 @@ struct dwc3_generic_plat {
struct dwc3_generic_priv {
void *base;
struct dwc3 dwc3;
- struct phy *phys;
- int num_phys;
+ struct phy_bulk phys;
};
struct dwc3_generic_host_priv {
@@ -56,7 +55,7 @@ static int dwc3_generic_probe(struct udevice *dev,
dwc3_of_parse(dwc3);
#endif
- rc = dwc3_setup_phy(dev, &priv->phys, &priv->num_phys);
+ rc = dwc3_setup_phy(dev, &priv->phys);
if (rc)
return rc;
@@ -79,7 +78,7 @@ static int dwc3_generic_remove(struct udevice *dev,
struct dwc3 *dwc3 = &priv->dwc3;
dwc3_remove(dwc3);
- dwc3_shutdown_phy(dev, priv->phys, priv->num_phys);
+ dwc3_shutdown_phy(dev, &priv->phys);
unmap_physmem(dwc3->regs, MAP_NOCACHE);
return 0;
diff --git a/drivers/usb/gadget/dwc2_udc_otg.c b/drivers/usb/gadget/dwc2_udc_otg.c
index b9c814cf73..9f21af2923 100644
--- a/drivers/usb/gadget/dwc2_udc_otg.c
+++ b/drivers/usb/gadget/dwc2_udc_otg.c
@@ -943,8 +943,7 @@ int usb_gadget_handle_interrupts(int index)
struct dwc2_priv_data {
struct clk_bulk clks;
struct reset_ctl_bulk resets;
- struct phy *phys;
- int num_phys;
+ struct phy_bulk phys;
struct udevice *usb33d_supply;
};
@@ -953,87 +952,29 @@ int dm_usb_gadget_handle_interrupts(struct udevice *dev)
return dwc2_udc_handle_interrupt();
}
-int dwc2_phy_setup(struct udevice *dev, struct phy **array, int *num_phys)
+static int dwc2_phy_setup(struct udevice *dev, struct phy_bulk *phys)
{
- int i, ret, count;
- struct phy *usb_phys;
-
- /* Return if no phy declared */
- if (!dev_read_prop(dev, "phys", NULL))
- return 0;
-
- count = dev_count_phandle_with_args(dev, "phys", "#phy-cells");
- if (count <= 0)
- return count;
-
- usb_phys = devm_kcalloc(dev, count, sizeof(struct phy),
- GFP_KERNEL);
- if (!usb_phys)
- return -ENOMEM;
-
- for (i = 0; i < count; i++) {
- ret = generic_phy_get_by_index(dev, i, &usb_phys[i]);
- if (ret && ret != -ENOENT) {
- dev_err(dev, "Failed to get USB PHY%d for %s\n",
- i, dev->name);
- return ret;
- }
- }
-
- for (i = 0; i < count; i++) {
- ret = generic_phy_init(&usb_phys[i]);
- if (ret) {
- dev_err(dev, "Can't init USB PHY%d for %s\n",
- i, dev->name);
- goto phys_init_err;
- }
- }
-
- for (i = 0; i < count; i++) {
- ret = generic_phy_power_on(&usb_phys[i]);
- if (ret) {
- dev_err(dev, "Can't power USB PHY%d for %s\n",
- i, dev->name);
- goto phys_poweron_err;
- }
- }
-
- *array = usb_phys;
- *num_phys = count;
-
- return 0;
-
-phys_poweron_err:
- for (i = count - 1; i >= 0; i--)
- generic_phy_power_off(&usb_phys[i]);
+ int ret;
- for (i = 0; i < count; i++)
- generic_phy_exit(&usb_phys[i]);
+ ret = generic_phy_get_bulk(dev, phys);
+ if (ret)
+ return ret;
- return ret;
+ ret = generic_phy_init_bulk(phys);
+ if (ret)
+ return ret;
-phys_init_err:
- for (; i >= 0; i--)
- generic_phy_exit(&usb_phys[i]);
+ ret = generic_phy_power_on_bulk(phys);
+ if (ret)
+ generic_phy_exit_bulk(phys);
return ret;
}
-void dwc2_phy_shutdown(struct udevice *dev, struct phy *usb_phys, int num_phys)
+static void dwc2_phy_shutdown(struct udevice *dev, struct phy_bulk *phys)
{
- int i, ret;
-
- for (i = 0; i < num_phys; i++) {
- if (!generic_phy_valid(&usb_phys[i]))
- continue;
-
- ret = generic_phy_power_off(&usb_phys[i]);
- ret |= generic_phy_exit(&usb_phys[i]);
- if (ret) {
- dev_err(dev, "Can't shutdown USB PHY%d for %s\n",
- i, dev->name);
- }
- }
+ generic_phy_power_off_bulk(phys);
+ generic_phy_exit_bulk(phys);
}
static int dwc2_udc_otg_ofdata_to_platdata(struct udevice *dev)
@@ -1158,7 +1099,7 @@ static int dwc2_udc_otg_probe(struct udevice *dev)
if (ret)
return ret;
- ret = dwc2_phy_setup(dev, &priv->phys, &priv->num_phys);
+ ret = dwc2_phy_setup(dev, &priv->phys);
if (ret)
return ret;
@@ -1208,7 +1149,7 @@ static int dwc2_udc_otg_remove(struct udevice *dev)
clk_release_bulk(&priv->clks);
- dwc2_phy_shutdown(dev, priv->phys, priv->num_phys);
+ dwc2_phy_shutdown(dev, &priv->phys);
return dm_scan_fdt_dev(dev);
}
diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig
index 94ac969058..2f381dc958 100644
--- a/drivers/usb/host/Kconfig
+++ b/drivers/usb/host/Kconfig
@@ -30,6 +30,12 @@ config USB_XHCI_DWC3_OF_SIMPLE
Support USB2/3 functionality in simple SoC integrations with
USB controller based on the DesignWare USB3 IP Core.
+config USB_XHCI_MTK
+ bool "Support for MediaTek on-chip xHCI USB controller"
+ depends on ARCH_MEDIATEK
+ help
+ Enables support for the on-chip xHCI controller on MediaTek SoCs.
+
config USB_XHCI_MVEBU
bool "MVEBU USB 3.0 support"
default y
diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile
index b62f346094..e8e3b17e42 100644
--- a/drivers/usb/host/Makefile
+++ b/drivers/usb/host/Makefile
@@ -51,6 +51,7 @@ obj-$(CONFIG_USB_XHCI_DWC3_OF_SIMPLE) += dwc3-of-simple.o
obj-$(CONFIG_USB_XHCI_ROCKCHIP) += xhci-rockchip.o
obj-$(CONFIG_USB_XHCI_EXYNOS) += xhci-exynos5.o
obj-$(CONFIG_USB_XHCI_FSL) += xhci-fsl.o
+obj-$(CONFIG_USB_XHCI_MTK) += xhci-mtk.o
obj-$(CONFIG_USB_XHCI_MVEBU) += xhci-mvebu.o
obj-$(CONFIG_USB_XHCI_OMAP) += xhci-omap.o
obj-$(CONFIG_USB_XHCI_PCI) += xhci-pci.o
diff --git a/drivers/usb/host/xhci-dwc3.c b/drivers/usb/host/xhci-dwc3.c
index 9fcfa39d4b..563db1a426 100644
--- a/drivers/usb/host/xhci-dwc3.c
+++ b/drivers/usb/host/xhci-dwc3.c
@@ -19,8 +19,7 @@
#include <linux/usb/otg.h>
struct xhci_dwc3_platdata {
- struct phy *usb_phys;
- int num_phys;
+ struct phy_bulk *usb_phys;
};
void dwc3_set_mode(struct dwc3 *dwc3_reg, u32 mode)
@@ -125,7 +124,7 @@ static int xhci_dwc3_probe(struct udevice *dev)
hcor = (struct xhci_hcor *)((uintptr_t)hccr +
HC_LENGTH(xhci_readl(&(hccr)->cr_capbase)));
- ret = dwc3_setup_phy(dev, &plat->usb_phys, &plat->num_phys);
+ ret = dwc3_setup_phy(dev, plat->usb_phys);
if (ret && (ret != -ENOTSUPP))
return ret;
@@ -168,7 +167,7 @@ static int xhci_dwc3_remove(struct udevice *dev)
{
struct xhci_dwc3_platdata *plat = dev_get_platdata(dev);
- dwc3_shutdown_phy(dev, plat->usb_phys, plat->num_phys);
+ dwc3_shutdown_phy(dev, plat->usb_phys);
return xhci_deregister(dev);
}
diff --git a/drivers/usb/host/xhci-mtk.c b/drivers/usb/host/xhci-mtk.c
new file mode 100644
index 0000000000..8ff71854fc
--- /dev/null
+++ b/drivers/usb/host/xhci-mtk.c
@@ -0,0 +1,303 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2019 MediaTek, Inc.
+ * Authors: Chunfeng Yun <chunfeng.yun@mediatek.com>
+ */
+
+#include <clk.h>
+#include <common.h>
+#include <dm.h>
+#include <dm/devres.h>
+#include <generic-phy.h>
+#include <malloc.h>
+#include <usb.h>
+#include <linux/errno.h>
+#include <linux/compat.h>
+#include <power/regulator.h>
+#include <linux/iopoll.h>
+#include <usb/xhci.h>
+
+/* IPPC (IP Port Control) registers */
+#define IPPC_IP_PW_CTRL0 0x00
+#define CTRL0_IP_SW_RST BIT(0)
+
+#define IPPC_IP_PW_CTRL1 0x04
+#define CTRL1_IP_HOST_PDN BIT(0)
+
+#define IPPC_IP_PW_STS1 0x10
+#define STS1_IP_SLEEP_STS BIT(30)
+#define STS1_U3_MAC_RST BIT(16)
+#define STS1_XHCI_RST BIT(11)
+#define STS1_SYS125_RST BIT(10)
+#define STS1_REF_RST BIT(8)
+#define STS1_SYSPLL_STABLE BIT(0)
+
+#define IPPC_IP_XHCI_CAP 0x24
+#define CAP_U3_PORT_NUM(p) ((p) & 0xff)
+#define CAP_U2_PORT_NUM(p) (((p) >> 8) & 0xff)
+
+#define IPPC_U3_CTRL_0P 0x30
+#define CTRL_U3_PORT_HOST_SEL BIT(2)
+#define CTRL_U3_PORT_PDN BIT(1)
+#define CTRL_U3_PORT_DIS BIT(0)
+
+#define IPPC_U2_CTRL_0P 0x50
+#define CTRL_U2_PORT_HOST_SEL BIT(2)
+#define CTRL_U2_PORT_PDN BIT(1)
+#define CTRL_U2_PORT_DIS BIT(0)
+
+#define IPPC_U3_CTRL(p) (IPPC_U3_CTRL_0P + ((p) * 0x08))
+#define IPPC_U2_CTRL(p) (IPPC_U2_CTRL_0P + ((p) * 0x08))
+
+struct mtk_xhci {
+ struct xhci_ctrl ctrl; /* Needs to come first in this struct! */
+ struct xhci_hccr *hcd;
+ void __iomem *ippc;
+ struct udevice *dev;
+ struct udevice *vusb33_supply;
+ struct udevice *vbus_supply;
+ struct clk_bulk clks;
+ struct phy_bulk phys;
+ int num_u2ports;
+ int num_u3ports;
+};
+
+static int xhci_mtk_host_enable(struct mtk_xhci *mtk)
+{
+ u32 value;
+ u32 check_val;
+ int ret;
+ int i;
+
+ /* power on host ip */
+ clrbits_le32(mtk->ippc + IPPC_IP_PW_CTRL1, CTRL1_IP_HOST_PDN);
+
+ /* power on and enable all u3 ports */
+ for (i = 0; i < mtk->num_u3ports; i++) {
+ clrsetbits_le32(mtk->ippc + IPPC_U3_CTRL(i),
+ CTRL_U3_PORT_PDN | CTRL_U3_PORT_DIS,
+ CTRL_U3_PORT_HOST_SEL);
+ }
+
+ /* power on and enable all u2 ports */
+ for (i = 0; i < mtk->num_u2ports; i++) {
+ clrsetbits_le32(mtk->ippc + IPPC_U2_CTRL(i),
+ CTRL_U2_PORT_PDN | CTRL_U2_PORT_DIS,
+ CTRL_U2_PORT_HOST_SEL);
+ }
+
+ /*
+ * wait for clocks to be stable, and clock domains reset to
+ * be inactive after power on and enable ports
+ */
+ check_val = STS1_SYSPLL_STABLE | STS1_REF_RST |
+ STS1_SYS125_RST | STS1_XHCI_RST;
+
+ if (mtk->num_u3ports)
+ check_val |= STS1_U3_MAC_RST;
+
+ ret = readl_poll_timeout(mtk->ippc + IPPC_IP_PW_STS1, value,
+ (check_val == (value & check_val)), 20000);
+ if (ret)
+ dev_err(mtk->dev, "clocks are not stable 0x%x!\n", value);
+
+ return ret;
+}
+
+static int xhci_mtk_host_disable(struct mtk_xhci *mtk)
+{
+ int i;
+
+ /* power down all u3 ports */
+ for (i = 0; i < mtk->num_u3ports; i++)
+ setbits_le32(mtk->ippc + IPPC_U3_CTRL(i), CTRL_U3_PORT_PDN);
+
+ /* power down all u2 ports */
+ for (i = 0; i < mtk->num_u2ports; i++)
+ setbits_le32(mtk->ippc + IPPC_U2_CTRL(i), CTRL_U2_PORT_PDN);
+
+ /* power down host ip */
+ setbits_le32(mtk->ippc + IPPC_IP_PW_CTRL1, CTRL1_IP_HOST_PDN);
+
+ return 0;
+}
+
+static int xhci_mtk_ssusb_init(struct mtk_xhci *mtk)
+{
+ u32 value;
+
+ /* reset whole ip */
+ setbits_le32(mtk->ippc + IPPC_IP_PW_CTRL0, CTRL0_IP_SW_RST);
+ udelay(1);
+ clrbits_le32(mtk->ippc + IPPC_IP_PW_CTRL0, CTRL0_IP_SW_RST);
+
+ value = readl(mtk->ippc + IPPC_IP_XHCI_CAP);
+ mtk->num_u3ports = CAP_U3_PORT_NUM(value);
+ mtk->num_u2ports = CAP_U2_PORT_NUM(value);
+ dev_info(mtk->dev, "u2p:%d, u3p:%d\n",
+ mtk->num_u2ports, mtk->num_u3ports);
+
+ return xhci_mtk_host_enable(mtk);
+}
+
+static int xhci_mtk_ofdata_get(struct mtk_xhci *mtk)
+{
+ struct udevice *dev = mtk->dev;
+ int ret = 0;
+
+ mtk->hcd = devfdt_remap_addr_name(dev, "mac");
+ if (!mtk->hcd) {
+ dev_err(dev, "failed to get xHCI base address\n");
+ return -ENXIO;
+ }
+
+ mtk->ippc = devfdt_remap_addr_name(dev, "ippc");
+ if (!mtk->ippc) {
+ dev_err(dev, "failed to get IPPC base address\n");
+ return -ENXIO;
+ }
+
+ dev_info(dev, "hcd: 0x%p, ippc: 0x%p\n", mtk->hcd, mtk->ippc);
+
+ ret = clk_get_bulk(dev, &mtk->clks);
+ if (ret) {
+ dev_err(dev, "failed to get clocks %d!\n", ret);
+ return ret;
+ }
+
+ ret = device_get_supply_regulator(dev, "vusb33-supply",
+ &mtk->vusb33_supply);
+ if (ret)
+ debug("can't get vusb33 regulator %d!\n", ret);
+
+ ret = device_get_supply_regulator(dev, "vbus-supply",
+ &mtk->vbus_supply);
+ if (ret)
+ debug("can't get vbus regulator %d!\n", ret);
+
+ return 0;
+}
+
+static int xhci_mtk_ldos_enable(struct mtk_xhci *mtk)
+{
+ int ret;
+
+ ret = regulator_set_enable(mtk->vusb33_supply, true);
+ if (ret < 0 && ret != -ENOSYS) {
+ dev_err(mtk->dev, "failed to enable vusb33 %d!\n", ret);
+ return ret;
+ }
+
+ ret = regulator_set_enable(mtk->vbus_supply, true);
+ if (ret < 0 && ret != -ENOSYS) {
+ dev_err(mtk->dev, "failed to enable vbus %d!\n", ret);
+ regulator_set_enable(mtk->vusb33_supply, false);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void xhci_mtk_ldos_disable(struct mtk_xhci *mtk)
+{
+ regulator_set_enable(mtk->vbus_supply, false);
+ regulator_set_enable(mtk->vusb33_supply, false);
+}
+
+static int xhci_mtk_phy_setup(struct mtk_xhci *mtk)
+{
+ struct udevice *dev = mtk->dev;
+ struct phy_bulk *phys = &mtk->phys;
+ int ret;
+
+ ret = generic_phy_get_bulk(dev, phys);
+ if (ret)
+ return ret;
+
+ ret = generic_phy_init_bulk(phys);
+ if (ret)
+ return ret;
+
+ ret = generic_phy_power_on_bulk(phys);
+ if (ret)
+ generic_phy_exit_bulk(phys);
+
+ return ret;
+}
+
+static void xhci_mtk_phy_shutdown(struct mtk_xhci *mtk)
+{
+ generic_phy_power_off_bulk(&mtk->phys);
+ generic_phy_exit_bulk(&mtk->phys);
+}
+
+static int xhci_mtk_probe(struct udevice *dev)
+{
+ struct mtk_xhci *mtk = dev_get_priv(dev);
+ struct xhci_hcor *hcor;
+ int ret;
+
+ mtk->dev = dev;
+ ret = xhci_mtk_ofdata_get(mtk);
+ if (ret)
+ return ret;
+
+ ret = xhci_mtk_ldos_enable(mtk);
+ if (ret)
+ goto ldos_err;
+
+ ret = clk_enable_bulk(&mtk->clks);
+ if (ret)
+ goto clks_err;
+
+ ret = xhci_mtk_phy_setup(mtk);
+ if (ret)
+ goto phys_err;
+
+ ret = xhci_mtk_ssusb_init(mtk);
+ if (ret)
+ goto ssusb_init_err;
+
+ hcor = (struct xhci_hcor *)((uintptr_t)mtk->hcd +
+ HC_LENGTH(xhci_readl(&mtk->hcd->cr_capbase)));
+
+ return xhci_register(dev, mtk->hcd, hcor);
+
+ssusb_init_err:
+ xhci_mtk_phy_shutdown(mtk);
+phys_err:
+ clk_disable_bulk(&mtk->clks);
+clks_err:
+ xhci_mtk_ldos_disable(mtk);
+ldos_err:
+ return ret;
+}
+
+static int xhci_mtk_remove(struct udevice *dev)
+{
+ struct mtk_xhci *mtk = dev_get_priv(dev);
+
+ xhci_deregister(dev);
+ xhci_mtk_host_disable(mtk);
+ xhci_mtk_ldos_disable(mtk);
+ clk_disable_bulk(&mtk->clks);
+
+ return 0;
+}
+
+static const struct udevice_id xhci_mtk_ids[] = {
+ { .compatible = "mediatek,mtk-xhci" },
+ { }
+};
+
+U_BOOT_DRIVER(usb_xhci) = {
+ .name = "xhci-mtk",
+ .id = UCLASS_USB,
+ .of_match = xhci_mtk_ids,
+ .probe = xhci_mtk_probe,
+ .remove = xhci_mtk_remove,
+ .ops = &xhci_usb_ops,
+ .bind = dm_scan_fdt_dev,
+ .priv_auto_alloc_size = sizeof(struct mtk_xhci),
+ .flags = DM_FLAG_ALLOC_PRIV_DMA,
+};
diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c
index 40dee2e6d9..c370eb6394 100644
--- a/drivers/usb/host/xhci.c
+++ b/drivers/usb/host/xhci.c
@@ -610,6 +610,16 @@ static int xhci_set_configuration(struct usb_device *udev)
ep_ctx[ep_index]->tx_info =
cpu_to_le32(EP_MAX_ESIT_PAYLOAD_LO(max_esit_payload) |
EP_AVG_TRB_LENGTH(avg_trb_len));
+
+ /*
+ * The MediaTek xHCI defines some extra SW parameters which
+ * are put into reserved DWs in Slot and Endpoint Contexts
+ * for synchronous endpoints.
+ */
+ if (IS_ENABLED(CONFIG_USB_XHCI_MTK)) {
+ ep_ctx[ep_index]->reserved[0] =
+ cpu_to_le32(EP_BPKTS(1) | EP_BBM(1));
+ }
}
return xhci_configure_endpoints(udev, false);
diff --git a/include/dm/ofnode.h b/include/dm/ofnode.h
index 618fc10390..a0d3df7786 100644
--- a/include/dm/ofnode.h
+++ b/include/dm/ofnode.h
@@ -880,6 +880,14 @@ ofnode ofnode_by_prop_value(ofnode from, const char *propname,
node = ofnode_next_subnode(node))
/**
+ * ofnode_get_child_count() - get the child count of a ofnode
+ *
+ * @node: valid node to get its child count
+ * @return the number of subnodes
+ */
+int ofnode_get_child_count(ofnode parent);
+
+/**
* ofnode_translate_address() - Translate a device-tree address
*
* Translate an address from the device-tree into a CPU physical address. This
diff --git a/include/dm/read.h b/include/dm/read.h
index 03c15b8550..b952551d55 100644
--- a/include/dm/read.h
+++ b/include/dm/read.h
@@ -669,6 +669,14 @@ u64 dev_translate_dma_address(const struct udevice *dev,
*/
int dev_read_alias_highest_id(const char *stem);
+/**
+ * dev_get_child_count() - get the child count of a device
+ *
+ * @dev: device to use for interation (struct udevice *)
+ * @return the count of child subnode
+ */
+int dev_get_child_count(const struct udevice *dev);
+
#else /* CONFIG_DM_DEV_READ_INLINE is enabled */
static inline int dev_read_u32(const struct udevice *dev,
@@ -978,6 +986,11 @@ static inline int dev_read_alias_highest_id(const char *stem)
return fdtdec_get_alias_highest_id(gd->fdt_blob, stem);
}
+static inline int dev_get_child_count(const struct udevice *dev)
+{
+ return ofnode_get_child_count(dev_ofnode(dev));
+}
+
#endif /* CONFIG_DM_DEV_READ_INLINE */
/**
diff --git a/include/dwc3-uboot.h b/include/dwc3-uboot.h
index 3c9e204cf0..ce835fd1b2 100644
--- a/include/dwc3-uboot.h
+++ b/include/dwc3-uboot.h
@@ -9,6 +9,7 @@
#ifndef __DWC3_UBOOT_H_
#define __DWC3_UBOOT_H_
+#include <generic-phy.h>
#include <linux/usb/otg.h>
#include <linux/usb/phy.h>
@@ -43,17 +44,15 @@ void dwc3_uboot_handle_interrupt(int index);
struct phy;
#if CONFIG_IS_ENABLED(PHY) && CONFIG_IS_ENABLED(DM_USB)
-int dwc3_setup_phy(struct udevice *dev, struct phy **array, int *num_phys);
-int dwc3_shutdown_phy(struct udevice *dev, struct phy *usb_phys, int num_phys);
+int dwc3_setup_phy(struct udevice *dev, struct phy_bulk *phys);
+int dwc3_shutdown_phy(struct udevice *dev, struct phy_bulk *phys);
#else
-static inline int dwc3_setup_phy(struct udevice *dev, struct phy **array,
- int *num_phys)
+static inline int dwc3_setup_phy(struct udevice *dev, struct phy_bulk *phys)
{
return -ENOTSUPP;
}
-static inline int dwc3_shutdown_phy(struct udevice *dev, struct phy *usb_phys,
- int num_phys)
+static inline int dwc3_shutdown_phy(struct udevice *dev, struct phy_bulk *phys)
{
return -ENOTSUPP;
}
diff --git a/include/generic-phy.h b/include/generic-phy.h
index 73537025c2..55629ae0b4 100644
--- a/include/generic-phy.h
+++ b/include/generic-phy.h
@@ -124,6 +124,23 @@ struct phy_ops {
int (*power_off)(struct phy *phy);
};
+/**
+ * struct phy_bulk - A handle to (allowing control of) a bulk of phys.
+ *
+ * Consumers provide storage for the phy bulk. The content of the structure is
+ * managed solely by the phy API. A phy bulk struct is initialized
+ * by "get"ing the phy bulk struct.
+ * The phy bulk struct is passed to all other bulk phy APIs to apply
+ * the API to all the phy in the bulk struct.
+ *
+ * @phys: An array of phy handles.
+ * @count: The number of phy handles in the phys array.
+ */
+struct phy_bulk {
+ struct phy *phys;
+ unsigned int count;
+};
+
#ifdef CONFIG_PHY
/**
@@ -250,6 +267,55 @@ int generic_phy_get_by_node(ofnode node, int index, struct phy *phy);
int generic_phy_get_by_name(struct udevice *user, const char *phy_name,
struct phy *phy);
+/**
+ * generic_phy_get_bulk - Get all phys of a device.
+ *
+ * This looks up and gets all phys of the consumer device; each device is
+ * assumed to have n phys associated with it somehow, and this function finds
+ * and gets all of them in a separate structure.
+ *
+ * @dev: The consumer device.
+ * @bulk A pointer to a phy bulk struct to initialize.
+ * @return 0 if OK, or a negative error code.
+ */
+int generic_phy_get_bulk(struct udevice *dev, struct phy_bulk *bulk);
+
+/**
+ * generic_phy_init_bulk() - Initialize all phys in a phy bulk struct.
+ *
+ * @bulk: A phy bulk struct that was previously successfully requested
+ * by generic_phy_get_bulk().
+ * @return 0 if OK, or negative error code.
+ */
+int generic_phy_init_bulk(struct phy_bulk *bulk);
+
+/**
+ * generic_phy_exit_bulk() - de-initialize all phys in a phy bulk struct.
+ *
+ * @bulk: A phy bulk struct that was previously successfully requested
+ * by generic_phy_get_bulk().
+ * @return 0 if OK, or negative error code.
+ */
+int generic_phy_exit_bulk(struct phy_bulk *bulk);
+
+/**
+ * generic_phy_power_on_bulk() - Power on all phys in a phy bulk struct.
+ *
+ * @bulk: A phy bulk struct that was previously successfully requested
+ * by generic_phy_get_bulk().
+ * @return 0 if OK, or negative error code.
+ */
+int generic_phy_power_on_bulk(struct phy_bulk *bulk);
+
+/**
+ * generic_phy_power_off_bulk() - Power off all phys in a phy bulk struct.
+ *
+ * @bulk: A phy bulk struct that was previously successfully requested
+ * by generic_phy_get_bulk().
+ * @return 0 if OK, or negative error code.
+ */
+int generic_phy_power_off_bulk(struct phy_bulk *bulk);
+
#else /* CONFIG_PHY */
static inline int generic_phy_init(struct phy *phy)
@@ -289,6 +355,32 @@ static inline int generic_phy_get_by_name(struct udevice *user, const char *phy_
return 0;
}
+static inline int
+generic_phy_get_bulk(struct udevice *dev, struct phy_bulk *bulk)
+{
+ return 0;
+}
+
+static inline int generic_phy_init_bulk(struct phy_bulk *bulk)
+{
+ return 0;
+}
+
+static inline int generic_phy_exit_bulk(struct phy_bulk *bulk)
+{
+ return 0;
+}
+
+static inline int generic_phy_power_on_bulk(struct phy_bulk *bulk)
+{
+ return 0;
+}
+
+static inline int generic_phy_power_off_bulk(struct phy_bulk *bulk)
+{
+ return 0;
+}
+
#endif /* CONFIG_PHY */
/**
diff --git a/include/usb/xhci.h b/include/usb/xhci.h
index 6017504488..20e4a21066 100644
--- a/include/usb/xhci.h
+++ b/include/usb/xhci.h
@@ -670,6 +670,9 @@ struct xhci_ep_ctx {
/* deq bitmasks */
#define EP_CTX_CYCLE_MASK (1 << 0)
+/* reserved[0] bitmasks, MediaTek xHCI used */
+#define EP_BPKTS(p) (((p) & 0x7f) << 0)
+#define EP_BBM(p) (((p) & 0x1) << 11)
/**
* struct xhci_input_control_context
diff --git a/test/dm/ofnode.c b/test/dm/ofnode.c
index 1c49eaf38b..07d5c7d7a6 100644
--- a/test/dm/ofnode.c
+++ b/test/dm/ofnode.c
@@ -113,3 +113,24 @@ static int dm_test_ofnode_read_chosen(struct unit_test_state *uts)
return 0;
}
DM_TEST(dm_test_ofnode_read_chosen, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT);
+
+static int dm_test_ofnode_get_child_count(struct unit_test_state *uts)
+{
+ ofnode node, child_node;
+ u32 val;
+
+ node = ofnode_path("/i-test");
+ ut_assert(ofnode_valid(node));
+
+ val = ofnode_get_child_count(node);
+ ut_asserteq(3, val);
+
+ child_node = ofnode_first_subnode(node);
+ ut_assert(ofnode_valid(child_node));
+ val = ofnode_get_child_count(child_node);
+ ut_asserteq(0, val);
+
+ return 0;
+}
+DM_TEST(dm_test_ofnode_get_child_count,
+ DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT);
diff --git a/test/dm/phy.c b/test/dm/phy.c
index 21d92194b9..92455d94af 100644
--- a/test/dm/phy.c
+++ b/test/dm/phy.c
@@ -110,3 +110,36 @@ static int dm_test_phy_ops(struct unit_test_state *uts)
return 0;
}
DM_TEST(dm_test_phy_ops, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT);
+
+static int dm_test_phy_bulk(struct unit_test_state *uts)
+{
+ struct phy_bulk phys;
+ struct udevice *parent;
+
+ /* test normal operations */
+ ut_assertok(uclass_get_device_by_name(UCLASS_SIMPLE_BUS,
+ "gen_phy_user1", &parent));
+
+ ut_assertok(generic_phy_get_bulk(parent, &phys));
+ ut_asserteq(2, phys.count);
+
+ ut_asserteq(0, generic_phy_init_bulk(&phys));
+ ut_asserteq(0, generic_phy_power_on_bulk(&phys));
+ ut_asserteq(0, generic_phy_power_off_bulk(&phys));
+ ut_asserteq(0, generic_phy_exit_bulk(&phys));
+
+ /* has a known problem phy */
+ ut_assertok(uclass_get_device_by_name(UCLASS_SIMPLE_BUS,
+ "gen_phy_user", &parent));
+
+ ut_assertok(generic_phy_get_bulk(parent, &phys));
+ ut_asserteq(3, phys.count);
+
+ ut_asserteq(0, generic_phy_init_bulk(&phys));
+ ut_asserteq(-EIO, generic_phy_power_on_bulk(&phys));
+ ut_asserteq(-EIO, generic_phy_power_off_bulk(&phys));
+ ut_asserteq(0, generic_phy_exit_bulk(&phys));
+
+ return 0;
+}
+DM_TEST(dm_test_phy_bulk, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT);