diff --git a/arch/riscv/boot/dts/spacemit/k3-com260.dts b/arch/riscv/boot/dts/spacemit/k3-com260.dts index c71aba704596..5732b38b3b5f 100644 --- a/arch/riscv/boot/dts/spacemit/k3-com260.dts +++ b/arch/riscv/boot/dts/spacemit/k3-com260.dts @@ -3,6 +3,7 @@ #include "k3.dtsi" #include "k3-pinctrl.dtsi" +#include #include #include @@ -60,6 +61,17 @@ vin-supply = <&p5v>; status = "okay"; }; + + hub_reset: hub-reset { + compatible = "regulator-fixed"; + regulator-name = "hub-reset"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + enable-active-high; + regulator-boot-on; + regulator-always-on; + gpio = <&gpio 1 21 GPIO_ACTIVE_HIGH>; + }; }; &resmem { @@ -345,3 +357,34 @@ }; }; }; + +&usb3_portb_u2phy { + status = "okay"; +}; + +&usb3_portb_u3phy { + status = "okay"; +}; + +&usb3_portb { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + reset-on-resume; + + /* VL817 USB2.0 hub (4 ports) */ + hub_2_0: hub@1 { + compatible = "usb2109,2817"; + reg = <1>; + peer-hub = <&hub_3_0>; + vdd-supply = <&hub_reset>; + }; + + /* VL817 USB3.0 hub (4 ports) */ + hub_3_0: hub@2 { + compatible = "usb2109,817"; + reg = <2>; + peer-hub = <&hub_2_0>; + }; +}; + diff --git a/arch/riscv/boot/dts/spacemit/k3.dtsi b/arch/riscv/boot/dts/spacemit/k3.dtsi index 974ea1ea6cf0..8726a5a6eebc 100644 --- a/arch/riscv/boot/dts/spacemit/k3.dtsi +++ b/arch/riscv/boot/dts/spacemit/k3.dtsi @@ -505,7 +505,7 @@ }; pll: clock-controller@d4090000 { - compatible = "spacemit,k3-pll"; + compatible = "spacemit,k3-pll", "syscon", "simple-mfd"; reg = <0x0 0xd4090000 0x0 0x10000>; clocks = <&vctcxo_24m>; spacemit,mpmu = <&syscon_mpmu>; @@ -910,5 +910,45 @@ reset-names = "sdh_axi", "sdh2"; status = "disabled"; }; + + usb3_portb_u2phy: phy@81500000 { + compatible = "spacemit,k3-usb2-phy"; + reg = <0x0 0x81500000 0x0 0x200>; + #phy-cells = <0>; + status = "disabled"; + }; + + usb3_portb_u3phy: phy@81f00000 { + compatible = "spacemit,k3-usb3-phy"; + spacemit,syscon-apmu = <&syscon_apmu>; + spacemit,syscon-apb-spare = <&pll>; + reg = <0x0 0x81f00000 0x0 0x200>; + combo-usb-bit = <2>; + #phy-cells = <0>; + status = "disabled"; + }; + + usb3_portb: usb3@81400000 { + compatible = "spacemit,k3-dwc3"; + reg = <0x0 0x81400000 0x0 0x10000>; + spacemit,syscon-apmu = <&syscon_apmu 0x3c8>; + interrupts = <125 IRQ_TYPE_LEVEL_HIGH>, + <157 IRQ_TYPE_LEVEL_HIGH>; + interrupt-names = "dwc3", "wakeup"; + interrupt-parent = <&saplic>; + clocks = <&syscon_apmu CLK_APMU_USB3_PORTB_BUS>; + resets = <&syscon_apmu RESET_APMU_USB3_PORTB>; + phys = <&usb3_portb_u2phy>, <&usb3_portb_u3phy>; + phy-names = "usb2-phy", "usb3-phy"; + phy_type = "utmi"; + snps,dis_enblslpm_quirk; + snps,dis_u2_susphy_quirk; + snps,dis_u3_susphy_quirk; + snps,dis-del-phy-power-chg-quirk; + snps,dis-tx-ipgap-linecheck-quirk; + snps,parkmode-disable-ss-quirk; + dr_mode = "host"; + status = "disabled"; + }; }; }; diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index 50f729360df1..852b71aa402a 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig @@ -99,5 +99,6 @@ source "drivers/phy/ti/Kconfig" source "drivers/phy/intel/Kconfig" source "drivers/phy/xilinx/Kconfig" source "drivers/phy/synopsys/Kconfig" +source "drivers/phy/spacemit/Kconfig" endmenu diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile index df5a0b92f828..01765631af5c 100644 --- a/drivers/phy/Makefile +++ b/drivers/phy/Makefile @@ -36,4 +36,5 @@ obj-y += allwinner/ \ tegra/ \ ti/ \ xilinx/ \ + spacemit/ \ synopsys/ diff --git a/drivers/phy/spacemit/Kconfig b/drivers/phy/spacemit/Kconfig new file mode 100644 index 000000000000..fee8db9ff15e --- /dev/null +++ b/drivers/phy/spacemit/Kconfig @@ -0,0 +1,23 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Phy drivers for SpacemiT platforms +# +config PHY_SPACEMIT_K3_USB3 + tristate "SpacemiT K3 USB 3.0 PHY support" + depends on (ARCH_SPACEMIT || COMPILE_TEST) && OF + depends on COMMON_CLK + depends on USB_COMMON + depends on TYPEC || TYPEC=n + select GENERIC_PHY + help + Enable this to support K3 USB 3.0 PHY driver. + +config PHY_SPACEMIT_K1_USB2 + tristate "SpacemiT K1/K3 USB 2.0 PHY support" + depends on (ARCH_SPACEMIT || COMPILE_TEST) && OF + depends on COMMON_CLK + depends on USB_COMMON + select GENERIC_PHY + help + Enable this to support K1 USB 2.0 PHY driver. This driver takes care of + enabling and clock setup and will be used by K1 udc/ehci/otg/xhci driver. diff --git a/drivers/phy/spacemit/Makefile b/drivers/phy/spacemit/Makefile new file mode 100644 index 000000000000..5d7fc8b280f3 --- /dev/null +++ b/drivers/phy/spacemit/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_PHY_SPACEMIT_K1_USB2) += phy-k1-usb2.o +obj-$(CONFIG_PHY_SPACEMIT_K3_USB3) += phy-k3-usb3.o diff --git a/drivers/phy/spacemit/phy-k1-usb2.c b/drivers/phy/spacemit/phy-k1-usb2.c new file mode 100644 index 000000000000..13f92efcb8ea --- /dev/null +++ b/drivers/phy/spacemit/phy-k1-usb2.c @@ -0,0 +1,210 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * SpacemiT K1 USB 2.0 PHY driver + * + * Copyright (C) 2025 SpacemiT (Hangzhou) Technology Co. Ltd + * Copyright (C) 2025 Ze Huang + */ + +#include +#include +#include +#include +#include +#include + +#define PHY_RST_MODE_CTRL 0x04 +#define PHY_PLL_RDY BIT(0) +#define PHY_CLK_CDR_EN BIT(1) +#define PHY_CLK_PLL_EN BIT(2) +#define PHY_CLK_MAC_EN BIT(3) +#define PHY_MAC_RSTN BIT(5) +#define PHY_CDR_RSTN BIT(6) +#define PHY_PLL_RSTN BIT(7) +/* + * hs line state sel (Bit 13): + * - 1 (Default): Internal HS line state is set to 01 when usb_hs_tx_en is valid. + * - 0: Internal HS line state is always driven by usb_hs_lstate. + * + * fs line state sel (Bit 14): + * - 1 (Default): FS line state is determined by the output data + * (usb_fs_datain/b). + * - 0: FS line state is always determined by the input data (dmo/dpo). + */ +#define PHY_HS_LINE_TX_MODE BIT(13) +#define PHY_FS_LINE_TX_MODE BIT(14) + +#define PHY_INIT_MODE_BITS (PHY_FS_LINE_TX_MODE | PHY_HS_LINE_TX_MODE) +#define PHY_CLK_ENABLE_BITS (PHY_CLK_PLL_EN | PHY_CLK_CDR_EN | \ + PHY_CLK_MAC_EN) +#define PHY_DEASSERT_RST_BITS (PHY_PLL_RSTN | PHY_CDR_RSTN | \ + PHY_MAC_RSTN) + +#define PHY_TX_HOST_CTRL 0x10 +#define PHY_HST_DISC_AUTO_CLR BIT(2) /* autoclear hs host disc when re-connect */ + +#define PHY_HSTXP_HW_CTRL 0x34 +#define PHY_HSTXP_RSTN BIT(2) /* generate reset for clock hstxp */ +#define PHY_CLK_HSTXP_EN BIT(3) /* clock hstxp enable */ +#define PHY_HSTXP_MODE BIT(4) /* 0: force en_txp to be 1; 1: no force */ + +#define PHY_PLL_DIV_CFG 0x98 +#define PHY_FDIV_FRACT_8_15 GENMASK(7, 0) +#define PHY_FDIV_FRACT_16_19 GENMASK(11, 8) +#define PHY_FDIV_FRACT_20_21 BIT(12) /* fdiv_reg<21>, <20>, bit21 == bit20 */ +/* + * freq_sel<1:0> + * if ref clk freq=24.0MHz-->freq_sel<2:0> == 3b'001, then internal divider value == 80 + */ +#define PHY_FDIV_FRACT_0_1 GENMASK(14, 13) +/* + * pll divider value selection + * 1: divider value will choose internal default value ,dependent on freq_sel<1:0> + * 0: divider value will be over ride by fdiv_reg<21:0> + */ +#define PHY_DIV_LOCAL_EN BIT(15) + +#define PHY_SEL_FREQ_24MHZ 0x01 +#define FDIV_REG_MASK (PHY_FDIV_FRACT_20_21 | PHY_FDIV_FRACT_16_19 | \ + PHY_FDIV_FRACT_8_15) +#define FDIV_REG_VAL 0x1ec4 /* 0x100 selects 24MHz, rest are default */ + +#define K1_USB2PHY_RESET_TIME_MS 50 + +struct spacemit_usb2phy { + struct phy *phy; + struct clk *clk; + struct regmap *regmap_base; +}; + +static const struct regmap_config phy_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = 0x200, +}; + +static int spacemit_usb2phy_init(struct phy *phy) +{ + struct spacemit_usb2phy *sphy = phy_get_drvdata(phy); + struct regmap *map = sphy->regmap_base; + u32 val; + int ret; + + ret = clk_enable(sphy->clk); + if (ret) { + dev_err(&phy->dev, "failed to enable clock\n"); + clk_disable(sphy->clk); + return ret; + } + + /* + * make sure the usb controller is not under reset process before + * any configuration + */ + usleep_range(150, 200); + + /* 24M ref clk */ + val = FIELD_PREP(FDIV_REG_MASK, FDIV_REG_VAL) | + FIELD_PREP(PHY_FDIV_FRACT_0_1, PHY_SEL_FREQ_24MHZ) | + PHY_DIV_LOCAL_EN; + regmap_write(map, PHY_PLL_DIV_CFG, val); + + ret = regmap_read_poll_timeout(map, PHY_RST_MODE_CTRL, val, + (val & PHY_PLL_RDY), + 500, K1_USB2PHY_RESET_TIME_MS * 1000); + if (ret) { + dev_err(&phy->dev, "wait PLLREADY timeout\n"); + clk_disable(sphy->clk); + return ret; + } + + /* release usb2 phy internal reset and enable clock gating */ + val = (PHY_INIT_MODE_BITS | PHY_CLK_ENABLE_BITS | PHY_DEASSERT_RST_BITS); + regmap_write(map, PHY_RST_MODE_CTRL, val); + + val = (PHY_HSTXP_RSTN | PHY_CLK_HSTXP_EN | PHY_HSTXP_MODE); + regmap_write(map, PHY_HSTXP_HW_CTRL, val); + + /* auto clear host disc */ + regmap_update_bits(map, PHY_TX_HOST_CTRL, PHY_HST_DISC_AUTO_CLR, + PHY_HST_DISC_AUTO_CLR); + + return 0; +} + +static int spacemit_usb2phy_exit(struct phy *phy) +{ + struct spacemit_usb2phy *sphy = phy_get_drvdata(phy); + + clk_disable(sphy->clk); + + return 0; +} + +static const struct phy_ops spacemit_k1_usb2phy_ops = { + .init = spacemit_usb2phy_init, + .exit = spacemit_usb2phy_exit, + .owner = THIS_MODULE, +}; + +static const struct phy_ops spacemit_k3_usb2phy_ops = { + .init = spacemit_usb2phy_init, + .exit = spacemit_usb2phy_exit, + .owner = THIS_MODULE, +}; + +static int spacemit_usb2phy_probe(struct platform_device *pdev) +{ + struct phy_provider *phy_provider; + struct device *dev = &pdev->dev; + struct spacemit_usb2phy *sphy; + const struct phy_ops *ops; + void __iomem *base; + + sphy = devm_kzalloc(dev, sizeof(*sphy), GFP_KERNEL); + if (!sphy) + return -ENOMEM; + + ops = device_get_match_data(dev); + + sphy->clk = devm_clk_get_optional_prepared(&pdev->dev, NULL); + if (IS_ERR(sphy->clk)) + return dev_err_probe(dev, PTR_ERR(sphy->clk), "Failed to get clock\n"); + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + sphy->regmap_base = devm_regmap_init_mmio(dev, base, &phy_regmap_config); + if (IS_ERR(sphy->regmap_base)) + return dev_err_probe(dev, PTR_ERR(sphy->regmap_base), "Failed to init regmap\n"); + + sphy->phy = devm_phy_create(dev, NULL, ops); + if (IS_ERR(sphy->phy)) + return dev_err_probe(dev, PTR_ERR(sphy->phy), "Failed to create phy\n"); + + phy_set_drvdata(sphy->phy, sphy); + 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 spacemit_usb2phy_dt_match[] = { + { .compatible = "spacemit,k1-usb2-phy", .data = &spacemit_k1_usb2phy_ops }, + { .compatible = "spacemit,k3-usb2-phy", .data = &spacemit_k3_usb2phy_ops }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, spacemit_usb2phy_dt_match); + +static struct platform_driver spacemit_usb2_phy_driver = { + .probe = spacemit_usb2phy_probe, + .driver = { + .name = "spacemit-usb2-phy", + .of_match_table = spacemit_usb2phy_dt_match, + }, +}; +module_platform_driver(spacemit_usb2_phy_driver); + +MODULE_DESCRIPTION("Spacemit USB 2.0 PHY driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/phy/spacemit/phy-k3-usb3.c b/drivers/phy/spacemit/phy-k3-usb3.c new file mode 100644 index 000000000000..eb8fd12332ce --- /dev/null +++ b/drivers/phy/spacemit/phy-k3-usb3.c @@ -0,0 +1,635 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * phy-k3-usb3.c - SpacemiT K3 USB3.0 PHY & Type-C Orientation Switch Driver + * + * Copyright (c) 2025 SpacemiT Technology Co. Ltd + */ + +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include + +#include +#include + +#define MAX_NUM_PHY 2 + +#define PLL_TIMEOUT 500000 /* For PHY PLL lock (usec) */ +#define PU_CAL_TIMEOUT 2000000 +#define POLL_DELAY 500 /* Time between polls (usec) */ + +/* Selecting the combo PHY operating mode requires APMU regmap access */ +#define SYSCON_APMU "spacemit,syscon-apmu" + +/* + * The PCIE/USB Subsystem on SpacemiT K3 have 3 single lane PIPE3 PHYs + * (PHY2/3/4) shared by PCIE PortC/D and USB3 PortB/C/D. + * + * PMUA_PCIE_SUBSYS_MGMT[4:0] + * + * bit4 = 0 : PCIe A X8 mode, all 8 lanes dedicated to PCIe Port A + * 1 : PHY lanes shared between PCIe or USB according to [3:0] + * + * All PHY matrix combinations according to [4:0]: + * + * 0x0X : PCIe-A X8 + * 0x10 : PCIe-C x2 (PHY2+PHY3) + PCIe-D x1 (PHY4) + * 0x11 : PCIe-C x2 (PHY2+PHY3) + USB-D (PHY4) + * 0x12 : PCIe-C x1 (PHY2) + USB-C (PHY3) + * 0x13 : PCIe-C x1 (PHY2) + USB-C (PHY3) + USB-D (PHY4) + * 0x14 : PCIe-C x1 (PHY3) + USB-B (PHY2) + * 0x15 : PCIe-C x1 (PHY3) + USB-B (PHY2) + USB-D (PHY4) + * 0x16 : USB-B (PHY2) + USB-C (PHY3) + PCIe D x1 (PHY4) + * 0x17 : USB-B (PHY2) + USB-C (PHY3) + USB-D (PHY4) + * + * So any USB Port B/C/D operation requires PCIe A X8 mode to be disabled. + */ +#define PMUA_PCIE_SUBSYS_MGMT 0x1d8 +#define PU_MATRIX_CONF_X8_DISABLE BIT(4) +#define PU_MATRIX_CONF_USB_MASK GENMASK(2, 0) + +#define PMUA_TYPEC_CTRL 0x110 +#define TYPEC_ORIENT_FLIP BIT(2) +#define TYPEC_ORIENT_OVRD_EN BIT(3) +#define TYPEC_ORIENT_OVRD BIT(4) + +/* PHY rcal init requires APB_SPARE regmap access */ +#define SYSCON_APB_SPARE "spacemit,syscon-apb-spare" + +#define APB_SPARE_PU_CAL 0x178 +#define PU_CAL BIT(17) + +#define APB_SPARE_RCAL_HSIO 0x17c +#define PU_CAL_DONE BIT(8) +#define R_CAL_OVRD_STABLE_EN BIT(31) +#define R_CAL_OVRD_STABLE_VAL BIT(30) +#define R_CAL_OVRD_NTRIM_EN BIT(29) +#define R_CAL_OVRD_PTRIM_EN BIT(28) +#define R_CAL_OVRD_TRIM_EN (R_CAL_OVRD_NTRIM_EN | R_CAL_OVRD_PTRIM_EN) +#define R_CAL_OVRD_NTRIM_MASK GENMASK(27, 24) +#define R_CAL_OVRD_NTRIM_VAL(val) FIELD_PREP(R_CAL_OVRD_NTRIM_MASK, val) +#define NTRIM_DEFAULT 0x6 +#define R_CAL_OVRD_PTRIM_MASK GENMASK(23, 20) +#define R_CAL_OVRD_PTRIM_VAL(val) FIELD_PREP(R_CAL_OVRD_PTRIM_MASK, val) +#define PTRIM_DEFAULT 0xa +#define R_CAL_OVRD_TRIM_MASK (R_CAL_OVRD_NTRIM_MASK | R_CAL_OVRD_PTRIM_MASK) + +/* PHY Registers */ +#define PHY_VERSION 0x0 + +#define PHY_RESET_CFG 0x04 +#define EN_SAMPLE_DATA_AFTER_LOCK BIT(6) + +#define PHY_CLK_CFG 0x08 +#define PLL_READY BIT(0) +#define CFG_RXCLK_EN BIT(3) +#define CFG_TXCLK_EN BIT(4) +#define CFG_PCLK_EN BIT(5) +#define CFG_PIPE_PCLK_EN BIT(6) +#define CFG_REFCLK_FREQ GENMASK(10, 7) +#define REFCLK_24M 0x2 +#define CFG_SW_INIT_DONE BIT(11) +#define CFG_PU_SSC_OUT BIT(23) + +#define PCIE_PHY_OVERRIDE 0x18 +#define OVRD_MPU_U3 BIT(17) +#define CFG_MPU_U3 BIT(16) + +#define PHY_MODE_CFG 0x0C +#define CFG_LFPS_RX_FILTER_EN BIT(11) +#define CFG_LFPS_TPERIOD GENMASK(9, 8) +#define LFPS_TPERIOD_USB 0x3 + +#define PHY_PU_SEL 0x40 +#define OVRD_STATUS BIT(10) +#define CFG_STATUS BIT(9) + +#define PHY_PU_CK_REG 0x54 +#define PU_REFCLK_100 BIT(25) + +#define PHY_PLL_REG1 0x58 +#define FREF_SEL GENMASK(15, 13) +#define FREF_24M 0x1 +#define SSC_DEP_SEL GENMASK(27, 24) +#define SSC_5000PPM 0xa +#define SSC_MODE GENMASK(29, 28) +#define SSC_CENTER_SPREAD 0x0 +#define SSC_UP_SPREAD 0x1 +#define SSC_DOWN_SPREAD 0x2 +#define SSC_DOWN_SPREAD1 0x3 + +#define PHY_PLL_REG2 0x5c +#define SEL_REF100 BIT(21) + +/* PHY RX Register Definitions */ +#define PHY_RX_REG_A 0x60 +#define RX_REG3_MASK GENMASK(31, 24) +#define RX_REG3_RDEG1(n) FIELD_PREP(GENMASK(31, 30), (n)) +#define RX_REG3_RDEG1_DEFAULT 0x3 +#define RX_REG3_ADJ_BIAS(n) FIELD_PREP(GENMASK(29, 28), (n)) +#define RX_REG3_ADJ_BIAS_DEFAULT 0x1 +#define RX_REG3_SEL_CBOOST_CODE BIT(27) +#define RX_REG3_I_LOAD_REG(n) FIELD_PREP(GENMASK(26, 24), (n)) +#define RX_REG3_I_LOAD_REG_DEFAULT 0x7 +#define RX_REG2_MASK GENMASK(23, 16) +#define RX_REG2_PSEL(n) FIELD_PREP(GENMASK(23, 21), (n)) +#define RX_REG2_PSEL_DEFAULT 0x4 +#define RX_REG2_FORCE_CSEL BIT(20) +#define RX_REG2_CSEL(n) FIELD_PREP(GENMASK(19, 16), (n)) +#define RX_REG2_CSEL_DEFAULT 0x8 +#define RX_REG1_MASK GENMASK(15, 8) +#define RX_REG1_RC_CALI_REG(n) FIELD_PREP(GENMASK(15, 12), (n)) +#define RX_REG1_RC_CALI_REG_DEFAULT 0x7 +#define RX_REG1_RTERM_REG(n) FIELD_PREP(GENMASK(11, 8), (n)) +#define RX_REG1_RTERM_REG_DEFAULT 0x8 +#define RX_REG0_MASK GENMASK(7, 0) +#define RX_REG0_RLOAD BIT(4) + +#define PHY_RX_REG_B 0x64 +#define RX_REG6_MASK GENMASK(23, 16) +#define RX_REG6_BYPASS_ADPT BIT(22) +#define RX_REG6_ADAPT_GAIN(n) FIELD_PREP(GENMASK(21, 20), (n)) +#define RX_REG6_ADAPT_GAIN_DEFAULT 0x2 +#define RX_REG6_H1_REG(n) FIELD_PREP(GENMASK(19, 16), (n)) +#define RX_REG6_H1_REG_DEFAULT 0x8 +#define RX_REG5_MASK GENMASK(15, 8) +#define RX_REG5_RCELL_BIAS(n) FIELD_PREP(GENMASK(15, 12), (n)) +#define RX_REG5_RCELL_BIAS_DEFAULT 0x8 +#define RX_REG5_RCELL_VCM(n) FIELD_PREP(GENMASK(11, 8), (n)) +#define RX_REG5_RCELL_VCM_DEFAULT 0x8 +#define RX_REG4_MASK GENMASK(7, 0) +#define RX_REG4_MANUAL_CFG BIT(7) +#define RX_REG4_RTERM_SEL BIT(5) +#define RX_REG4_ENVOS BIT(4) +#define RX_REG4_RDEG2(n) FIELD_PREP(GENMASK(2, 1), (n)) +#define RX_REG4_RDEG2_DEFAULT 0x2 + +#define PHY_RXEQ_TIME 0xb4 +#define RXEQ_TIME_OVRD_AMP_SOC BIT(24) +#define RXEQ_TIME_CFG_AMP_SOC(n) FIELD_PREP(GENMASK(23, 22), (n)) +#define AMP_SOC_650M 0x0 +#define AMP_SOC_800M 0x1 +#define AMP_SOC_870M 0x2 +#define AMP_SOC_900M 0x3 +#define OVRD_POST_C_SOC BIT(21) +#define CFG_POST_C_SOC(n) FIELD_PREP(GENMASK(20, 19), (n)) +#define OVRD_PRE_C_SOC BIT(18) +#define CFG_PRE_C_SOC(n) FIELD_PREP(GENMASK(17, 16), (n)) +#define CFG_RXEQ_TIMEOUT(n) FIELD_PREP(GENMASK(15, 0), (n)) + +#define PHY_ADPT_CFG0 0x140 +#define AFE_ADPT_RST_OVRD_EN BIT(1) +#define AFE_ADPT_RST_OVRD_VAL BIT(4) + +struct k3_usb3phy { + struct device *dev; + struct phy *phy; + /* dual phy for orentation switch */ + struct regmap *regmap_bases[MAX_NUM_PHY]; + + bool is_combo; + u32 combo_sel_bit; + + /* MMIO regmap (no errors) */ + struct regmap *pmu; + struct regmap *apb_spare; + + /* For USB only */ + bool nop; + bool orientation_flip; + struct typec_switch_dev *sw; +}; + +static void k3_usb3phy_combo_set_usb(struct k3_usb3phy *k3_phy, bool usb) +{ + u32 combo_mode_mask = BIT(k3_phy->combo_sel_bit); + u32 combo_mode_val = usb << k3_phy->combo_sel_bit; + + combo_mode_mask |= PU_MATRIX_CONF_X8_DISABLE; + combo_mode_val |= usb ? PU_MATRIX_CONF_X8_DISABLE : 0; + + if (k3_phy->is_combo && + !regmap_test_bits(k3_phy->pmu, PMUA_PCIE_SUBSYS_MGMT, + combo_mode_val) == usb) { + regmap_update_bits(k3_phy->pmu, PMUA_PCIE_SUBSYS_MGMT, + combo_mode_mask, combo_mode_val); + dev_info(k3_phy->dev, "Update Combo Mode %d to %s Mode\n", + combo_mode_val, usb ? "USB" : "PCIE"); + } +} + +static void k3_usb3phy_update_status(struct regmap *regm) +{ + int ret; + + ret = regmap_update_bits(regm, PHY_PU_SEL, + CFG_STATUS | OVRD_STATUS, + OVRD_STATUS); + if (ret != 0) { + pr_err("regmap update PHY_PU_SEL failed, ret=%d\n", ret); + return; + } + usleep_range(200, 300); +} + +static int k3_usb3phy_init_single(struct k3_usb3phy *k3_phy, + struct regmap *regm) +{ + struct phy *phy = k3_phy->phy; + struct regmap *apb_spare = k3_phy->apb_spare; + int ret; + u32 version, reg; + + regmap_update_bits(regm, PCIE_PHY_OVERRIDE, + OVRD_MPU_U3 | CFG_MPU_U3, 0); + + ret = regmap_read(regm, PHY_CLK_CFG, ®); + if (ret) + return ret; + + if (reg & CFG_SW_INIT_DONE) { + dev_dbg(&phy->dev, "PHY already initialized, skip init\n"); + return 0; + } + + ret = regmap_read(regm, PHY_VERSION, &version); + if (ret) + return ret; + + regmap_update_bits(apb_spare, APB_SPARE_PU_CAL, PU_CAL, + PU_CAL); + + ret = regmap_read_poll_timeout(apb_spare, APB_SPARE_RCAL_HSIO, + reg, (reg & PU_CAL_DONE), POLL_DELAY, + PU_CAL_TIMEOUT); + if (ret) { + dev_warn(k3_phy->dev, "PU PHY RCAL timeout, trim override\n"); + + regmap_update_bits(apb_spare, APB_SPARE_RCAL_HSIO, + R_CAL_OVRD_TRIM_EN | R_CAL_OVRD_STABLE_VAL | + R_CAL_OVRD_TRIM_MASK, + R_CAL_OVRD_TRIM_EN | R_CAL_OVRD_STABLE_VAL | + R_CAL_OVRD_NTRIM_VAL(NTRIM_DEFAULT) | + R_CAL_OVRD_PTRIM_VAL(PTRIM_DEFAULT)); + regmap_set_bits(apb_spare, APB_SPARE_RCAL_HSIO, R_CAL_OVRD_STABLE_EN); + } + + /* Do not wait CDR lock before sampling data */ + regmap_update_bits(regm, PHY_RESET_CFG, EN_SAMPLE_DATA_AFTER_LOCK, + 0); + + /* Power down 100MHz refclk buffer */ + regmap_update_bits(regm, PHY_PU_CK_REG, PU_REFCLK_100, 0); + + /* Program PLL REG1 configure the SSC */ + regmap_write(regm, PHY_PLL_REG1, + FIELD_PREP(SSC_MODE, SSC_DOWN_SPREAD1) | + FIELD_PREP(SSC_DEP_SEL, SSC_5000PPM) | + FIELD_PREP(FREF_SEL, FREF_24M)); + + /* Un-select 100MHz PLL reference */ + regmap_update_bits(regm, PHY_PLL_REG2, SEL_REF100, 0); + + /* USB LFPS period configuration */ + regmap_update_bits(regm, PHY_MODE_CFG, CFG_LFPS_TPERIOD, + FIELD_PREP(CFG_LFPS_TPERIOD, + LFPS_TPERIOD_USB)); + + /* Force AFE adaptation reset */ + regmap_update_bits(regm, PHY_ADPT_CFG0, + AFE_ADPT_RST_OVRD_EN | AFE_ADPT_RST_OVRD_VAL, + AFE_ADPT_RST_OVRD_EN | AFE_ADPT_RST_OVRD_VAL); + + /* Override driver amplitude value to 900m */ + regmap_set_bits(regm, PHY_RXEQ_TIME, + RXEQ_TIME_OVRD_AMP_SOC | RXEQ_TIME_CFG_AMP_SOC(AMP_SOC_900M)); + + /* Configure RX parameters */ + regmap_update_bits(regm, PHY_RX_REG_A, RX_REG0_MASK, RX_REG0_RLOAD); + regmap_update_bits(regm, PHY_RX_REG_A, RX_REG1_MASK, + RX_REG1_RC_CALI_REG(RX_REG1_RC_CALI_REG_DEFAULT) | + RX_REG1_RTERM_REG(RX_REG1_RTERM_REG_DEFAULT)); + regmap_update_bits(regm, PHY_RX_REG_A, RX_REG2_MASK, + RX_REG2_PSEL(RX_REG2_PSEL_DEFAULT) | RX_REG2_FORCE_CSEL | + RX_REG2_CSEL(RX_REG2_CSEL_DEFAULT)); + regmap_update_bits(regm, PHY_RX_REG_A, RX_REG3_MASK, + RX_REG3_RDEG1(RX_REG3_RDEG1_DEFAULT) | + RX_REG3_ADJ_BIAS(RX_REG3_ADJ_BIAS_DEFAULT) | + RX_REG3_SEL_CBOOST_CODE | + RX_REG3_I_LOAD_REG(RX_REG3_I_LOAD_REG_DEFAULT)); + regmap_update_bits(regm, PHY_RX_REG_B, RX_REG4_MASK, + RX_REG4_MANUAL_CFG | RX_REG4_RTERM_SEL | RX_REG4_ENVOS | + RX_REG4_RDEG2(RX_REG4_RDEG2_DEFAULT)); + regmap_update_bits(regm, PHY_RX_REG_B, RX_REG5_MASK, + RX_REG5_RCELL_BIAS(RX_REG5_RCELL_BIAS_DEFAULT) | + RX_REG5_RCELL_VCM(RX_REG5_RCELL_VCM_DEFAULT)); + regmap_update_bits(regm, PHY_RX_REG_B, RX_REG6_MASK, + RX_REG6_ADAPT_GAIN(RX_REG6_ADAPT_GAIN_DEFAULT) | + RX_REG6_H1_REG(RX_REG6_H1_REG_DEFAULT)); + dev_info(&phy->dev, "PUPHY Rx Reg Configured\n"); + + /* + * Inform PHY that all PLL-related configuration is done. + * PLL will not start locking until CFG_SW_INIT_DONE is set. + */ + regmap_write(regm, PHY_CLK_CFG, + CFG_SW_INIT_DONE | + CFG_PU_SSC_OUT | + FIELD_PREP(CFG_REFCLK_FREQ, REFCLK_24M) | + CFG_RXCLK_EN | CFG_PCLK_EN | + CFG_PIPE_PCLK_EN | CFG_TXCLK_EN); + + ret = regmap_read_poll_timeout(regm, PHY_CLK_CFG, reg, + (reg & PLL_READY), POLL_DELAY, + PLL_TIMEOUT); + if (ret) { + dev_err(&phy->dev, "PHY PLL polling Timeout!\n"); + return -ETIMEDOUT; + } + + dev_info(&phy->dev, "PHY version: 0x%x init as USB3 mode\n", version); + + return 0; +} + +static int k3_usb3phy_power_on(struct phy *phy) +{ + struct k3_usb3phy *k3_phy = phy_get_drvdata(phy); + + if (k3_phy->nop) + return 0; + + /* RX Filter requires soc 24M, which is disabled during system sleep */ + for (int i = 0; i < MAX_NUM_PHY && k3_phy->regmap_bases[i]; i++) + regmap_set_bits(k3_phy->regmap_bases[i], PHY_MODE_CFG, + CFG_LFPS_RX_FILTER_EN); + + return 0; +} + +static int k3_usb3phy_power_off(struct phy *phy) +{ + struct k3_usb3phy *k3_phy = phy_get_drvdata(phy); + + if (k3_phy->nop) + return 0; + + for (int i = 0; i < MAX_NUM_PHY && k3_phy->regmap_bases[i]; i++) + regmap_clear_bits(k3_phy->regmap_bases[i], PHY_MODE_CFG, + CFG_LFPS_RX_FILTER_EN); + + return 0; +} + +static int k3_usb3phy_init(struct phy *phy) +{ + struct k3_usb3phy *k3_phy = phy_get_drvdata(phy); + + if (k3_phy->nop) { + dev_info(&phy->dev, + "maximum high-speed configuration requested\n"); + return 0; + } + + k3_usb3phy_combo_set_usb(k3_phy, true); + + k3_usb3phy_init_single(k3_phy, k3_phy->regmap_bases[0]); + if (k3_phy->regmap_bases[1]) + k3_usb3phy_init_single(k3_phy, k3_phy->regmap_bases[1]); + + return 0; +} + +static int k3_usb3phy_exit(struct phy *phy) +{ + struct k3_usb3phy *k3_phy = phy_get_drvdata(phy); + int ret; + + if (k3_phy->nop) + return 0; + + /* Completely shutdown the phy */ + for (int i = 0; i < MAX_NUM_PHY; i++) { + if (!k3_phy->regmap_bases[i]) + continue; + + ret = regmap_update_bits(k3_phy->regmap_bases[i], PHY_CLK_CFG, + CFG_SW_INIT_DONE, 0); + if (ret) + return ret; + + ret = regmap_update_bits(k3_phy->regmap_bases[i], PCIE_PHY_OVERRIDE, + OVRD_MPU_U3 | CFG_MPU_U3, OVRD_MPU_U3); + if (ret) + return ret; + } + + return 0; +} + +static int k3_usb3phy_set_speed(struct phy *phy, int speed) +{ + struct k3_usb3phy *k3_phy = phy_get_drvdata(phy); + + switch (speed) { + case USB_SPEED_HIGH: + k3_usb3phy_update_status(k3_phy->regmap_bases[0]); + k3_phy->nop = true; + default: + break; + } + return 0; +} + +static const struct phy_ops k3_usb3phy_ops = { + .init = k3_usb3phy_init, + .exit = k3_usb3phy_exit, + .power_on = k3_usb3phy_power_on, + .power_off = k3_usb3phy_power_off, + .set_speed = k3_usb3phy_set_speed, + .owner = THIS_MODULE, +}; + +#if IS_ENABLED(CONFIG_TYPEC) +static int k3_usb3phy_switch_set(struct typec_switch_dev *sw, + enum typec_orientation orientation) +{ + struct k3_usb3phy *k3_phy = typec_switch_get_drvdata(sw); + bool val; + + val = orientation == TYPEC_ORIENTATION_REVERSE; + if (k3_phy->orientation_flip) + val = !val; + + if (regmap_test_bits(k3_phy->pmu, PMUA_TYPEC_CTRL, + TYPEC_ORIENT_OVRD | TYPEC_ORIENT_OVRD_EN) != val) + regmap_update_bits(k3_phy->pmu, PMUA_TYPEC_CTRL, + TYPEC_ORIENT_OVRD | TYPEC_ORIENT_OVRD_EN, + val ? TYPEC_ORIENT_OVRD | TYPEC_ORIENT_OVRD_EN : 0); + + dev_dbg(k3_phy->dev, "Override orientation with %d\n", val); + return 0; +} + +static void k3_usb3_typec_unregister(void *data) +{ + struct k3_usb3phy *k3_phy = data; + + typec_switch_unregister(k3_phy->sw); +} + +static int k3_usb3_typec_register(struct k3_usb3phy *k3_phy) +{ + struct typec_switch_desc sw_desc = {}; + struct device *dev = k3_phy->dev; + + sw_desc.drvdata = k3_phy; + sw_desc.name = dev_name(dev); + sw_desc.fwnode = dev_fwnode(dev); + sw_desc.set = k3_usb3phy_switch_set; + k3_phy->sw = typec_switch_register(dev, &sw_desc); + if (IS_ERR(k3_phy->sw)) { + dev_err(dev, "Unable to register typec switch: %pe\n", + k3_phy->sw); + return PTR_ERR(k3_phy->sw); + } + + dev_info(dev, "Using orientation-switch mode, flip: %d\n", + k3_phy->orientation_flip); + + return devm_add_action_or_reset(dev, k3_usb3_typec_unregister, k3_phy); +} +#else +static int k3_usb3_typec_register(struct k3_usb3phy *k3_phy) +{ + return 0; +} +#endif + +static int k3_usb3phy_switch_init(struct k3_usb3phy *k3_phy) +{ + struct device *dev = k3_phy->dev; + bool flip, override; + u32 val = 0; + + if (!device_is_compatible(dev, "spacemit,k3-typec-switch")) + return 0; + + flip = device_property_read_bool(dev, "orientation-flip"); + override = device_property_read_bool(dev, "orientation-switch"); + if (flip && !override) + regmap_update_bits(k3_phy->pmu, PMUA_TYPEC_CTRL, + TYPEC_ORIENT_FLIP, val); + + if (!override) + return 0; + + k3_phy->orientation_flip = flip; + return k3_usb3_typec_register(k3_phy); +}; + +static int k3_usb3phy_probe(struct platform_device *pdev) +{ + struct phy *(*xlate)(struct device *dev, + struct of_phandle_args *args); + struct device *dev = &pdev->dev; + struct k3_usb3phy *k3_phy; + struct phy_provider *provider; + void __iomem *base; + int num_phy, ret; + + xlate = of_device_get_match_data(dev); + + k3_phy = devm_kzalloc(dev, sizeof(*k3_phy), GFP_KERNEL); + if (!k3_phy) + return -ENOMEM; + + k3_phy->is_combo = device_property_read_bool(dev, "combo-usb-bit"); + if (k3_phy->is_combo) { + ret = device_property_read_u32(dev, "combo-usb-bit", &k3_phy->combo_sel_bit); + if (ret || !(BIT(k3_phy->combo_sel_bit) & PU_MATRIX_CONF_USB_MASK)) + return dev_err_probe(dev, ret, "Wrong combo-usb-bit configuration"); + } + + k3_phy->dev = dev; + + k3_phy->pmu = + syscon_regmap_lookup_by_phandle(dev_of_node(dev), SYSCON_APMU); + if (IS_ERR(k3_phy->pmu)) + return dev_err_probe(dev, PTR_ERR(k3_phy->pmu), + SYSCON_APMU " lookup failed"); + + k3_phy->apb_spare = syscon_regmap_lookup_by_phandle(dev_of_node(dev), + SYSCON_APB_SPARE); + if (IS_ERR(k3_phy->apb_spare)) + return dev_err_probe(dev, PTR_ERR(k3_phy->apb_spare), + SYSCON_APB_SPARE " lookup failed"); + + /* dual phy for orientation switch */ + for (num_phy = 0; num_phy < MAX_NUM_PHY; num_phy++) + if (!platform_get_resource(pdev, IORESOURCE_MEM, num_phy)) + break; + + for (unsigned int i = 0; i < num_phy; ++i) { + static struct regmap_config phy_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = 0x200, + }; + + base = devm_platform_ioremap_resource(pdev, i); + if (IS_ERR(base)) + return dev_err_probe(dev, PTR_ERR(base), + "error mapping registers\n"); + + phy_regmap_config.name = devm_kasprintf(dev, GFP_KERNEL, + "%s-%d", dev_name(dev), i); + k3_phy->regmap_bases[i] = + devm_regmap_init_mmio(dev, base, &phy_regmap_config); + if (IS_ERR(k3_phy->regmap_bases[i])) + return dev_err_probe(dev, PTR_ERR(k3_phy->regmap_bases[i]), + "Failed to init regmap\n"); + } + + k3_phy->phy = devm_phy_create(dev, NULL, &k3_usb3phy_ops); + if (IS_ERR(k3_phy->phy)) + return dev_err_probe(dev, PTR_ERR(k3_phy->phy), + "Failed to create phy\n"); + phy_set_drvdata(k3_phy->phy, k3_phy); + + provider = devm_of_phy_provider_register(dev, xlate); + if (IS_ERR(provider)) + return dev_err_probe(dev, PTR_ERR(provider), + "error registering provider\n"); + + return k3_usb3phy_switch_init(k3_phy); +} + +static const struct of_device_id k3_usb3phy_of_match[] = { + { .compatible = "spacemit,k3-usb3-phy", of_phy_simple_xlate }, + {}, +}; +MODULE_DEVICE_TABLE(of, k3_usb3phy_of_match); + +static struct platform_driver k3_usb3phy_driver = { + .probe = k3_usb3phy_probe, + .driver = { + .name = "spacemit,k3-usb3-phy", + .of_match_table = k3_usb3phy_of_match, + }, +}; +module_platform_driver(k3_usb3phy_driver); + +MODULE_DESCRIPTION("SpacemiT K3 USB3 PHY Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/dwc3/Kconfig b/drivers/usb/dwc3/Kconfig index 000bfa3a4ddf..0d1ebf4c8a41 100644 --- a/drivers/usb/dwc3/Kconfig +++ b/drivers/usb/dwc3/Kconfig @@ -198,4 +198,15 @@ config USB_DWC3_THEAD Only the host mode is currently supported. Say 'Y' or 'M' here if you have one such device. +config USB_DWC3_GENERIC_PLAT + tristate "DWC3 Generic Platform Driver" + depends on OF && COMMON_CLK + default USB_DWC3 + help + Support USB3 functionality in simple SoC integrations. + Currently supports SpacemiT DWC USB3. Platforms using + dwc3-of-simple can easily switch to dwc3-generic by flattening + the dwc3 child node in the device tree. + Say 'Y' or 'M' here if your platform integrates DWC3 in a similar way. + endif diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile index 79afa5c03c10..dc6b1e9f6d69 100644 --- a/drivers/usb/dwc3/Makefile +++ b/drivers/usb/dwc3/Makefile @@ -57,3 +57,4 @@ obj-$(CONFIG_USB_DWC3_XILINX) += dwc3-xilinx.o obj-$(CONFIG_USB_DWC3_OCTEON) += dwc3-octeon.o obj-$(CONFIG_USB_DWC3_RTK) += dwc3-rtk.o obj-$(CONFIG_USB_DWC3_THEAD) += dwc3-thead.o +obj-$(CONFIG_USB_DWC3_GENERIC_PLAT) += dwc3-generic-plat.o diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index c00d6328f052..6a57c494b968 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -37,6 +37,7 @@ #include "core.h" #include "gadget.h" +#include "glue.h" #include "io.h" #include "debug.h" @@ -1940,26 +1941,15 @@ static struct power_supply *dwc3_get_usb_power_supply(struct dwc3 *dwc) return usb_psy; } -static int dwc3_probe(struct platform_device *pdev) +int dwc3_core_probe(const struct dwc3_probe_data *data) { - struct device *dev = &pdev->dev; - struct resource *res, dwc_res; + struct dwc3 *dwc = data->dwc; + struct device *dev = dwc->dev; + struct resource dwc_res; void __iomem *regs; - struct dwc3 *dwc; + struct resource *res = data->res; int ret; - dwc = devm_kzalloc(dev, sizeof(*dwc), GFP_KERNEL); - if (!dwc) - return -ENOMEM; - - dwc->dev = dev; - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) { - dev_err(dev, "missing memory resource\n"); - return -ENODEV; - } - dwc->xhci_resources[0].start = res->start; dwc->xhci_resources[0].end = dwc->xhci_resources[0].start + DWC3_XHCI_REGS_END; @@ -1997,15 +1987,17 @@ static int dwc3_probe(struct platform_device *pdev) if (IS_ERR(dwc->usb_psy)) return dev_err_probe(dev, PTR_ERR(dwc->usb_psy), "couldn't get usb power supply\n"); - dwc->reset = devm_reset_control_array_get_optional_shared(dev); - if (IS_ERR(dwc->reset)) { - ret = PTR_ERR(dwc->reset); - goto err_put_psy; - } + if (!data->ignore_clocks_and_resets) { + dwc->reset = devm_reset_control_array_get_optional_shared(dev); + if (IS_ERR(dwc->reset)) { + ret = PTR_ERR(dwc->reset); + goto err_put_psy; + } - ret = dwc3_get_clocks(dwc); - if (ret) - goto err_put_psy; + ret = dwc3_get_clocks(dwc); + if (ret) + goto err_put_psy; + } ret = reset_control_deassert(dwc->reset); if (ret) @@ -2021,7 +2013,7 @@ static int dwc3_probe(struct platform_device *pdev) goto err_disable_clks; } - platform_set_drvdata(pdev, dwc); + dev_set_drvdata(dev, dwc); dwc3_cache_hwparams(dwc); if (!dev_is_pci(dwc->sysdev) && @@ -2103,12 +2095,35 @@ static int dwc3_probe(struct platform_device *pdev) return ret; } +EXPORT_SYMBOL_GPL(dwc3_core_probe); -static void dwc3_remove(struct platform_device *pdev) +static int dwc3_probe(struct platform_device *pdev) { - struct dwc3 *dwc = platform_get_drvdata(pdev); + struct dwc3_probe_data probe_data = {}; + struct resource *res; + struct dwc3 *dwc; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "missing memory resource\n"); + return -ENODEV; + } + + dwc = devm_kzalloc(&pdev->dev, sizeof(*dwc), GFP_KERNEL); + if (!dwc) + return -ENOMEM; + + dwc->dev = &pdev->dev; + + probe_data.dwc = dwc; + probe_data.res = res; - pm_runtime_get_sync(&pdev->dev); + return dwc3_core_probe(&probe_data); +} + +void dwc3_core_remove(struct dwc3 *dwc) +{ + pm_runtime_get_sync(dwc->dev); dwc3_core_exit_mode(dwc); dwc3_debugfs_exit(dwc); @@ -2116,16 +2131,16 @@ static void dwc3_remove(struct platform_device *pdev) dwc3_core_exit(dwc); dwc3_ulpi_exit(dwc); - pm_runtime_allow(&pdev->dev); - pm_runtime_disable(&pdev->dev); - pm_runtime_dont_use_autosuspend(&pdev->dev); - pm_runtime_put_noidle(&pdev->dev); + pm_runtime_allow(dwc->dev); + pm_runtime_disable(dwc->dev); + pm_runtime_dont_use_autosuspend(dwc->dev); + pm_runtime_put_noidle(dwc->dev); /* * HACK: Clear the driver data, which is currently accessed by parent * glue drivers, before allowing the parent to suspend. */ - platform_set_drvdata(pdev, NULL); - pm_runtime_set_suspended(&pdev->dev); + dev_set_drvdata(dwc->dev, NULL); + pm_runtime_set_suspended(dwc->dev); dwc3_free_event_buffers(dwc); @@ -2134,6 +2149,12 @@ static void dwc3_remove(struct platform_device *pdev) power_supply_put(dwc->usb_psy); } } +EXPORT_SYMBOL_GPL(dwc3_core_remove); + +static void dwc3_remove(struct platform_device *pdev) +{ + dwc3_core_remove(platform_get_drvdata(pdev)); +} #ifdef CONFIG_PM static int dwc3_core_init_for_resume(struct dwc3 *dwc) @@ -2317,9 +2338,8 @@ static int dwc3_runtime_checks(struct dwc3 *dwc) return 0; } -static int dwc3_runtime_suspend(struct device *dev) +int dwc3_runtime_suspend(struct dwc3 *dwc) { - struct dwc3 *dwc = dev_get_drvdata(dev); int ret; if (dwc3_runtime_checks(dwc)) @@ -2331,10 +2351,11 @@ static int dwc3_runtime_suspend(struct device *dev) return 0; } +EXPORT_SYMBOL_GPL(dwc3_runtime_suspend); -static int dwc3_runtime_resume(struct device *dev) +int dwc3_runtime_resume(struct dwc3 *dwc) { - struct dwc3 *dwc = dev_get_drvdata(dev); + struct device *dev = dwc->dev; int ret; ret = dwc3_resume_common(dwc, PMSG_AUTO_RESUME); @@ -2344,7 +2365,7 @@ static int dwc3_runtime_resume(struct device *dev) switch (dwc->current_dr_role) { case DWC3_GCTL_PRTCAP_DEVICE: if (dwc->pending_events) { - pm_runtime_put(dwc->dev); + pm_runtime_put(dev); dwc->pending_events = false; enable_irq(dwc->irq_gadget); } @@ -2359,10 +2380,11 @@ static int dwc3_runtime_resume(struct device *dev) return 0; } +EXPORT_SYMBOL_GPL(dwc3_runtime_resume); -static int dwc3_runtime_idle(struct device *dev) +int dwc3_runtime_idle(struct dwc3 *dwc) { - struct dwc3 *dwc = dev_get_drvdata(dev); + struct device *dev = dwc->dev; switch (dwc->current_dr_role) { case DWC3_GCTL_PRTCAP_DEVICE: @@ -2380,12 +2402,28 @@ static int dwc3_runtime_idle(struct device *dev) return 0; } +EXPORT_SYMBOL_GPL(dwc3_runtime_idle); + +static int dwc3_plat_runtime_suspend(struct device *dev) +{ + return dwc3_runtime_suspend(dev_get_drvdata(dev)); +} + +static int dwc3_plat_runtime_resume(struct device *dev) +{ + return dwc3_runtime_resume(dev_get_drvdata(dev)); +} + +static int dwc3_plat_runtime_idle(struct device *dev) +{ + return dwc3_runtime_idle(dev_get_drvdata(dev)); +} #endif /* CONFIG_PM */ #ifdef CONFIG_PM_SLEEP -static int dwc3_suspend(struct device *dev) +int dwc3_pm_suspend(struct dwc3 *dwc) { - struct dwc3 *dwc = dev_get_drvdata(dev); + struct device *dev = dwc->dev; int ret; ret = dwc3_suspend_common(dwc, PMSG_SUSPEND); @@ -2396,10 +2434,11 @@ static int dwc3_suspend(struct device *dev) return 0; } +EXPORT_SYMBOL_GPL(dwc3_pm_suspend); -static int dwc3_resume(struct device *dev) +int dwc3_pm_resume(struct dwc3 *dwc) { - struct dwc3 *dwc = dev_get_drvdata(dev); + struct device *dev = dwc->dev; int ret; pinctrl_pm_select_default_state(dev); @@ -2414,10 +2453,10 @@ static int dwc3_resume(struct device *dev) return 0; } +EXPORT_SYMBOL_GPL(dwc3_pm_resume); -static void dwc3_complete(struct device *dev) +void dwc3_pm_complete(struct dwc3 *dwc) { - struct dwc3 *dwc = dev_get_drvdata(dev); u32 reg; if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_HOST && @@ -2427,21 +2466,37 @@ static void dwc3_complete(struct device *dev) dwc3_writel(dwc->regs, DWC3_GUCTL3, reg); } } +EXPORT_SYMBOL_GPL(dwc3_pm_complete); + +static int dwc3_plat_suspend(struct device *dev) +{ + return dwc3_pm_suspend(dev_get_drvdata(dev)); +} + +static int dwc3_plat_resume(struct device *dev) +{ + return dwc3_pm_resume(dev_get_drvdata(dev)); +} + +static void dwc3_plat_complete(struct device *dev) +{ + dwc3_pm_complete(dev_get_drvdata(dev)); +} #else -#define dwc3_complete NULL +#define dwc3_plat_complete NULL #endif /* CONFIG_PM_SLEEP */ static const struct dev_pm_ops dwc3_dev_pm_ops = { - SET_SYSTEM_SLEEP_PM_OPS(dwc3_suspend, dwc3_resume) - .complete = dwc3_complete, + SET_SYSTEM_SLEEP_PM_OPS(dwc3_plat_suspend, dwc3_plat_resume) + .complete = dwc3_plat_complete, /* * Runtime suspend halts the controller on disconnection. It relies on * platforms with custom connection notification to start the controller * again. */ - SET_RUNTIME_PM_OPS(dwc3_runtime_suspend, dwc3_runtime_resume, - dwc3_runtime_idle) + SET_RUNTIME_PM_OPS(dwc3_plat_runtime_suspend, dwc3_plat_runtime_resume, + dwc3_plat_runtime_idle) }; #ifdef CONFIG_OF diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index 45084ca66806..699a665d1119 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -184,6 +184,8 @@ #define DWC3_GSBUSCFG0_INCR4BRSTENA (1 << 1) /* INCR4 burst */ #define DWC3_GSBUSCFG0_INCRBRSTENA (1 << 0) /* undefined length enable */ #define DWC3_GSBUSCFG0_INCRBRST_MASK 0xff +#define DWC3_GSBUSCFG0_REQINFO(n) (((n) & 0xffff) << 16) +#define DWC3_GSBUSCFG0_REQINFO_UNSPECIFIED 0xffffffff /* Global Debug LSP MUX Select */ #define DWC3_GDBGLSPMUX_ENDBC BIT(15) /* Host only */ diff --git a/drivers/usb/dwc3/dwc3-generic-plat.c b/drivers/usb/dwc3/dwc3-generic-plat.c new file mode 100644 index 000000000000..36d392ec615d --- /dev/null +++ b/drivers/usb/dwc3/dwc3-generic-plat.c @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * dwc3-generic-plat.c - DesignWare USB3 generic platform driver + * + * Copyright (C) 2025 Ze Huang + * + * Inspired by dwc3-qcom.c and dwc3-of-simple.c + */ + +#include +#include +#include +#include "glue.h" + +struct dwc3_generic { + struct device *dev; + struct dwc3 dwc; + struct clk_bulk_data *clks; + int num_clocks; + struct reset_control *resets; +}; + +#define to_dwc3_generic(d) container_of((d), struct dwc3_generic, dwc) + +static void dwc3_generic_reset_control_assert(void *data) +{ + reset_control_assert(data); +} + +static int dwc3_generic_probe(struct platform_device *pdev) +{ + struct dwc3_probe_data probe_data = {}; + struct device *dev = &pdev->dev; + struct dwc3_generic *dwc3g; + struct resource *res; + int ret; + + dwc3g = devm_kzalloc(dev, sizeof(*dwc3g), GFP_KERNEL); + if (!dwc3g) + return -ENOMEM; + + dwc3g->dev = dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "missing memory resource\n"); + return -ENODEV; + } + + dwc3g->resets = devm_reset_control_array_get_optional_exclusive(dev); + if (IS_ERR(dwc3g->resets)) + return dev_err_probe(dev, PTR_ERR(dwc3g->resets), "failed to get resets\n"); + + ret = reset_control_assert(dwc3g->resets); + if (ret) + return dev_err_probe(dev, ret, "failed to assert resets\n"); + + /* Not strict timing, just for safety */ + udelay(2); + + ret = reset_control_deassert(dwc3g->resets); + if (ret) + return dev_err_probe(dev, ret, "failed to deassert resets\n"); + + ret = devm_add_action_or_reset(dev, dwc3_generic_reset_control_assert, dwc3g->resets); + if (ret) + return ret; + + ret = devm_clk_bulk_get_all(dwc3g->dev, &dwc3g->clks); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to get clocks\n"); + + dwc3g->num_clocks = ret; + + ret = clk_bulk_prepare_enable(dwc3g->num_clocks, dwc3g->clks); + if (ret) + return dev_err_probe(dev, ret, "failed to enable clocks\n"); + dwc3g->dwc.dev = dev; + probe_data.dwc = &dwc3g->dwc; + probe_data.res = res; + probe_data.ignore_clocks_and_resets = true; + ret = dwc3_core_probe(&probe_data); + if (ret) + return dev_err_probe(dev, ret, "failed to register DWC3 Core\n"); + + return 0; +} + +static void dwc3_generic_remove(struct platform_device *pdev) +{ + struct dwc3 *dwc = platform_get_drvdata(pdev); + struct dwc3_generic *dwc3g = to_dwc3_generic(dwc); + + dwc3_core_remove(dwc); + + clk_bulk_disable_unprepare(dwc3g->num_clocks, dwc3g->clks); +} + +static int dwc3_generic_suspend(struct device *dev) +{ + struct dwc3 *dwc = dev_get_drvdata(dev); + struct dwc3_generic *dwc3g = to_dwc3_generic(dwc); + int ret; + + ret = dwc3_pm_suspend(dwc); + if (ret) + return ret; + + clk_bulk_disable_unprepare(dwc3g->num_clocks, dwc3g->clks); + + return 0; +} + +static int dwc3_generic_resume(struct device *dev) +{ + struct dwc3 *dwc = dev_get_drvdata(dev); + struct dwc3_generic *dwc3g = to_dwc3_generic(dwc); + int ret; + + ret = clk_bulk_prepare_enable(dwc3g->num_clocks, dwc3g->clks); + if (ret) + return ret; + + ret = dwc3_pm_resume(dwc); + if (ret) + return ret; + + return 0; +} + +static int dwc3_generic_runtime_suspend(struct device *dev) +{ + return dwc3_runtime_suspend(dev_get_drvdata(dev)); +} + +static int dwc3_generic_runtime_resume(struct device *dev) +{ + return dwc3_runtime_resume(dev_get_drvdata(dev)); +} + +static int dwc3_generic_runtime_idle(struct device *dev) +{ + return dwc3_runtime_idle(dev_get_drvdata(dev)); +} + +static const struct dev_pm_ops dwc3_generic_dev_pm_ops = { + SYSTEM_SLEEP_PM_OPS(dwc3_generic_suspend, dwc3_generic_resume) + RUNTIME_PM_OPS(dwc3_generic_runtime_suspend, dwc3_generic_runtime_resume, + dwc3_generic_runtime_idle) +}; + +static const struct of_device_id dwc3_generic_of_match[] = { + { .compatible = "spacemit,k3-dwc3", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, dwc3_generic_of_match); + +static struct platform_driver dwc3_generic_driver = { + .probe = dwc3_generic_probe, + .remove_new = dwc3_generic_remove, + .driver = { + .name = "dwc3-generic-plat", + .of_match_table = dwc3_generic_of_match, + .pm = pm_ptr(&dwc3_generic_dev_pm_ops), + }, +}; +module_platform_driver(dwc3_generic_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("DesignWare USB3 generic platform driver"); diff --git a/drivers/usb/dwc3/glue.h b/drivers/usb/dwc3/glue.h new file mode 100644 index 000000000000..4ec09f32d8b9 --- /dev/null +++ b/drivers/usb/dwc3/glue.h @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * glue.h - DesignWare USB3 DRD glue header + */ + +#ifndef __DRIVERS_USB_DWC3_GLUE_H +#define __DRIVERS_USB_DWC3_GLUE_H + +#include +#include "core.h" + +/** + * dwc3_properties: DWC3 core properties + * @gsbuscfg0_reqinfo: Value to be programmed in the GSBUSCFG0.REQINFO field + * @needs_full_reinit: indicate the controller may not remain power during system + * pm and need full initialization + */ +struct dwc3_properties { + u32 gsbuscfg0_reqinfo; + unsigned needs_full_reinit:1; +}; + +#define DWC3_DEFAULT_PROPERTIES ((struct dwc3_properties){ \ + .gsbuscfg0_reqinfo = DWC3_GSBUSCFG0_REQINFO_UNSPECIFIED, \ + }) + +/** + * dwc3_probe_data: Initialization parameters passed to dwc3_core_probe() + * @dwc: Reference to dwc3 context structure + * @res: resource for the DWC3 core mmio region + * @ignore_clocks_and_resets: clocks and resets defined for the device should + * be ignored by the DWC3 core, as they are managed by the glue + */ +struct dwc3_probe_data { + struct dwc3 *dwc; + struct resource *res; + bool ignore_clocks_and_resets; + struct dwc3_properties properties; +}; + +int dwc3_core_probe(const struct dwc3_probe_data *data); +void dwc3_core_remove(struct dwc3 *dwc); + +int dwc3_runtime_suspend(struct dwc3 *dwc); +int dwc3_runtime_resume(struct dwc3 *dwc); +int dwc3_runtime_idle(struct dwc3 *dwc); +int dwc3_pm_suspend(struct dwc3 *dwc); +int dwc3_pm_resume(struct dwc3 *dwc); +void dwc3_pm_complete(struct dwc3 *dwc); + +#endif