diff --git a/Documentation/devicetree/bindings/pci/spacemit,k1-pcie-rc.yaml b/Documentation/devicetree/bindings/pci/spacemit,k1-pcie-rc.yaml new file mode 100644 index 00000000000000..6bcca2f91a6fdb --- /dev/null +++ b/Documentation/devicetree/bindings/pci/spacemit,k1-pcie-rc.yaml @@ -0,0 +1,141 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/pci/spacemit,k1-pcie-rc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: SpacemiT K1 PCI Express Root Complex + +maintainers: + - Alex Elder + +description: + The SpacemiT K1 SoC PCIe root complex controller is based on the + Synopsys DesignWare PCIe IP. + +properties: + compatible: + const: spacemit,k1-pcie-rc.yaml + + reg: + items: + - description: DesignWare PCIe registers + - description: ATU address space + - description: PCIe configuration space + - description: Link control registers + + reg-names: + items: + - const: dbi + - const: atu + - const: config + - const: link + + clocks: + items: + - description: DWC PCIe Data Bus Interface (DBI) clock + - description: DWC PCIe application AXI-bus Master interface clock + - description: DWC PCIe application AXI-bus Slave interface clock. + + clock-names: + items: + - const: dbi + - const: mstr + - const: slv + + resets: + items: + - description: DWC PCIe Data Bus Interface (DBI) reset + - description: DWC PCIe application AXI-bus Master interface reset + - description: DWC PCIe application AXI-bus Slave interface reset. + - description: Global reset; must be deasserted for PHY to function + + reset-names: + items: + - const: dbi + - const: mstr + - const: slv + - const: global + + interrupts-extended: + maxItems: 1 + + spacemit,syscon-pmu: + description: + PHandle that refers to the APMU system controller, whose + regmap is used in managing resets and link state. + $ref: /schemas/types.yaml#/definitions/phandle + + device_type: + const: pci + + max-link-speed: + const: 2 + + num-viewport: + const: 8 + +required: + - compatible + - reg + - clocks + - clock-names + - resets + - reset-names + - spacemit,syscon-pmu + - "#address-cells" + - "#size-cells" + - device_type + - max-link-speed + - bus-range + - num-viewport + +additionalProperties: false + +examples: + - | + #include + pcie0: pcie@ca000000 { + compatible = "spacemit,k1-pcie-rc"; + reg = <0x0 0xca000000 0x0 0x00001000>, + <0x0 0xca300000 0x0 0x0001ff24>, + <0x0 0x8f000000 0x0 0x00002000>, + <0x0 0xc0b20000 0x0 0x00001000>; + reg-names = "dbi", + "atu", + "config", + "link"; + + ranges = <0x01000000 0x8f002000 0x0 0x8f002000 0x0 0x100000>, + <0x02000000 0x80000000 0x0 0x80000000 0x0 0x0f000000>; + + clocks = <&syscon_apmu CLK_PCIE0_DBI>, + <&syscon_apmu CLK_PCIE0_MASTER>, + <&syscon_apmu CLK_PCIE0_SLAVE>; + clock-names = "dbi", + "mstr", + "slv"; + + resets = <&syscon_apmu RESET_PCIE0_DBI>, + <&syscon_apmu RESET_PCIE0_MASTER>, + <&syscon_apmu RESET_PCIE0_SLAVE>, + <&syscon_apmu RESET_PCIE0_GLOBAL>; + reset-names = "dbi", + "mstr", + "slv", + "global"; + + interrupts-extended = <&plic 141>; + + spacemit,syscon-pmu = <&syscon_apmu 0x03cc>; + + #address-cells = <3>; + #size-cells = <2>; + + device_type = "pci"; + max-link-speed = <2>; + bus-range = <0x00 0xff>; + num-viewport = <8>; + + status = "disabled"; + }; diff --git a/Documentation/devicetree/bindings/phy/spacemit,k1-combo-phy.yaml b/Documentation/devicetree/bindings/phy/spacemit,k1-combo-phy.yaml new file mode 100644 index 00000000000000..ed78083a532315 --- /dev/null +++ b/Documentation/devicetree/bindings/phy/spacemit,k1-combo-phy.yaml @@ -0,0 +1,110 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/phy/spacemit,k1-combo-phy.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: SpacemiT K1 PCIe/USB3 Combo PHY + +maintainers: + - Alex Elder + +description: + Of the three PHYs on the SpacemiT K1 SoC capable of being used for + PCIe, one is a combo PHY that can also be configured for use by a + USB 3 controller. Using PCIe or USB 3 is a board design decision. + + The combo PHY is also the only PCIe PHY that is able to determine + PCIe calibration values to use, and this must be determined before + the other two PCIe PHYs can be used. This calibration must be + performed with the combo PHY in PCIe mode, and is this is done + when the combo PHY is probed. + + During normal operation, the PCIe or USB port driver is responsible + for ensuring all clocks needed by a PHY are enabled, and all resets + affecting the PHY are deasserted. However, for the combo PHY to + perform calibration independent of whether it's later used for + PCIe or USB, all PCIe mode clocks and resets must be defined. + +properties: + compatible: + const: spacemit,k1-combo-phy + + reg: + items: + - description: PHY control registers + + clocks: + items: + - description: DWC PCIe Data Bus Interface (DBI) clock + - description: DWC PCIe application AXI-bus Master interface clock + - description: DWC PCIe application AXI-bus Slave interface clock. + + clock-names: + items: + - const: dbi + - const: mstr + - const: slv + + resets: + items: + - description: DWC PCIe Data Bus Interface (DBI) reset + - description: DWC PCIe application AXI-bus Master interface reset + - description: DWC PCIe application AXI-bus Slave interface reset. + - description: Global reset; must be deasserted for PHY to function + + reset-names: + items: + - const: dbi + - const: mstr + - const: slv + - const: global + + spacemit,syscon-pmu: + description: + PHandle that refers to the APMU system controller, whose + regmap is used in setting the mode + $ref: /schemas/types.yaml#/definitions/phandle + + "#phy-cells": + const: 1 + description: + The argument value (PHY_TYPE_PCIE or PHY_TYPE_USB3) determines + whether the PHY operates in PCIe or USB3 mode. + +required: + - compatible + - reg + - clocks + - clock-names + - resets + - reset-names + - spacemit,syscon-pmu + - "#phy-cells" + +additionalProperties: false + +examples: + - | + #include + combo_phy: phy@c0b10000 { + compatible = "spacemit,k1-combo-phy"; + reg = <0xc0b10000 0x1000>; + clocks = <&syscon_apmu CLK_PCIE0_DBI>, + <&syscon_apmu CLK_PCIE0_MASTER>, + <&syscon_apmu CLK_PCIE0_SLAVE>; + clock-names = "dbi", + "mstr", + "slv"; + resets = <&syscon_apmu RESET_PCIE0_DBI>, + <&syscon_apmu RESET_PCIE0_MASTER>, + <&syscon_apmu RESET_PCIE0_SLAVE>, + <&syscon_apmu RESET_PCIE0_GLOBAL>; + reset-names = "dbi", + "mstr", + "slv", + "global"; + spacemit,syscon-pmu = <&syscon_apmu>; + #phy-cells = <1>; + status = "disabled"; + }; diff --git a/Documentation/devicetree/bindings/phy/spacemit,k1-pcie-phy.yaml b/Documentation/devicetree/bindings/phy/spacemit,k1-pcie-phy.yaml new file mode 100644 index 00000000000000..b0cbd231d9378f --- /dev/null +++ b/Documentation/devicetree/bindings/phy/spacemit,k1-pcie-phy.yaml @@ -0,0 +1,49 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/phy/spacemit,k1-pcie-phy.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: SpacemiT K1 PCIe PHY + +maintainers: + - Alex Elder + +description: + There are two PHYs on the SpacemiT K1 SoC used for PCIe (only). + These PHYs must be configured using calibration values that are + determined by a third "combo PHY". The combo PHY determines + these calibration values during probe so they can be used for + the two PCIe-only PHYs. + + During normal operation, the PCIe port driver is responsible for + ensuring all clocks needed by a PHY are enabled, and all resets + affecting the PHY are deasserted. + +properties: + compatible: + const: spacemit,k1-pcie-phy + + reg: + items: + - description: PHY control registers + + "#phy-cells": + const: 0 + +required: + - compatible + - reg + - "#phy-cells" + +additionalProperties: false + +examples: + - | + #include + pcie1_phy: phy@c0c10000 { + compatible = "spacemit,k1-pcie-phy"; + reg = <0xc0c10000 0x1000>; + #phy-cells = <0>; + status = "disabled"; + }; diff --git a/arch/riscv/boot/dts/spacemit/k1-bananapi-f3.dts b/arch/riscv/boot/dts/spacemit/k1-bananapi-f3.dts index fe22c747c5012f..1c75e38b1fab91 100644 --- a/arch/riscv/boot/dts/spacemit/k1-bananapi-f3.dts +++ b/arch/riscv/boot/dts/spacemit/k1-bananapi-f3.dts @@ -40,6 +40,34 @@ status = "okay"; }; +&combo_phy { + pinctrl-names = "default"; + pinctrl-0 = <&pcie0_3_cfg>; + status = "okay"; +}; + +&pcie1_phy { + pinctrl-names = "default"; + pinctrl-0 = <&pcie1_3_cfg>; + status = "okay"; +}; + +&pcie2_phy { + pinctrl-names = "default"; + pinctrl-0 = <&pcie2_4_cfg>; + status = "okay"; +}; + +&pcie1 { + phys = <&pcie1_phy>; + status = "okay"; +}; + +&pcie2 { + phys = <&pcie2_phy>; + status = "okay"; +}; + &uart0 { pinctrl-names = "default"; pinctrl-0 = <&uart0_2_cfg>; diff --git a/arch/riscv/boot/dts/spacemit/k1-pinctrl.dtsi b/arch/riscv/boot/dts/spacemit/k1-pinctrl.dtsi index 38105573742281..e7dbecd7389b73 100644 --- a/arch/riscv/boot/dts/spacemit/k1-pinctrl.dtsi +++ b/arch/riscv/boot/dts/spacemit/k1-pinctrl.dtsi @@ -21,6 +21,39 @@ }; }; + pcie0_3_cfg: pcie0-3-cfg { + pcie0-3-pins { + pinmux = , /* PERST# */ + , /* WAKE */ + ; /* CLKREQ# */ + + bias-pull-up = <0>; + drive-strength = <21>; + }; + }; + + pcie1_3_cfg: pcie1-3-cfg { + pcie1-3-pins { + pinmux = , /* PERST# */ + , /* WAKE */ + ; /* CLKREQ# */ + + bias-pull-up = <0>; + drive-strength = <21>; + }; + }; + + pcie2_4_cfg: pcie2-4-cfg { + pcie2-4-pins { + pinmux = , /* PERST# */ + , /* WAKE */ + ; /* CLKREQ# */ + + bias-pull-up = <0>; + drive-strength = <21>; + }; + }; + pwm14_1_cfg: pwm14-1-cfg { pwm14-1-pins { pinmux = ; diff --git a/arch/riscv/boot/dts/spacemit/k1.dtsi b/arch/riscv/boot/dts/spacemit/k1.dtsi index abde8bb07c95c5..6343f6e95284d9 100644 --- a/arch/riscv/boot/dts/spacemit/k1.dtsi +++ b/arch/riscv/boot/dts/spacemit/k1.dtsi @@ -4,6 +4,8 @@ */ #include +#include +#include /dts-v1/; / { @@ -358,6 +360,42 @@ #reset-cells = <1>; }; + combo_phy: phy@c0b10000 { + compatible = "spacemit,k1-combo-phy"; + reg = <0x0 0xc0b10000 0x0 0x1000>; + clocks = <&syscon_apmu CLK_PCIE0_DBI>, + <&syscon_apmu CLK_PCIE0_MASTER>, + <&syscon_apmu CLK_PCIE0_SLAVE>; + clock-names = "dbi", + "mstr", + "slv"; + resets = <&syscon_apmu RESET_PCIE0_DBI>, + <&syscon_apmu RESET_PCIE0_MASTER>, + <&syscon_apmu RESET_PCIE0_SLAVE>, + <&syscon_apmu RESET_PCIE0_GLOBAL>; + reset-names = "dbi", + "mstr", + "slv", + "global"; + spacemit,syscon-pmu = <&syscon_apmu>; + #phy-cells = <1>; + status = "disabled"; + }; + + pcie1_phy: phy@c0c10000 { + compatible = "spacemit,k1-pcie-phy"; + reg = <0x0 0xc0c10000 0x0 0x1000>; + #phy-cells = <0>; + status = "disabled"; + }; + + pcie2_phy: phy@c0d10000 { + compatible = "spacemit,k1-pcie-phy"; + reg = <0x0 0xc0d10000 0x0 0x1000>; + #phy-cells = <0>; + status = "disabled"; + }; + syscon_apbc: system-controller@d4015000 { compatible = "spacemit,k1-syscon-apbc"; reg = <0x0 0xd4015000 0x0 0x1000>; @@ -814,6 +852,137 @@ #size-cells = <2>; dma-ranges = <0x0 0x00000000 0x0 0x00000000 0x0 0x80000000>, <0x0 0xb8000000 0x1 0x38000000 0x3 0x48000000>; + pcie0: pcie@ca000000 { + compatible = "spacemit,k1-pcie-rc"; + reg = <0x0 0xca000000 0x0 0x00001000>, + <0x0 0xca300000 0x0 0x0001ff24>, + <0x0 0x8f000000 0x0 0x00002000>, + <0x0 0xc0b20000 0x0 0x00001000>; + reg-names = "dbi", + "atu", + "config", + "link"; + spacemit,syscon-pmu = <&syscon_apmu 0x03cc>; + + ranges = <0x01000000 0x0 0x8f002000 0 0x8f002000 0x0 0x100000>, + <0x02000000 0x0 0x80000000 0 0x80000000 0x0 0x0f000000>; + + clocks = <&syscon_apmu CLK_PCIE0_DBI>, + <&syscon_apmu CLK_PCIE0_MASTER>, + <&syscon_apmu CLK_PCIE0_SLAVE>; + clock-names = "dbi", + "mstr", + "slv"; + + resets = <&syscon_apmu RESET_PCIE0_DBI>, + <&syscon_apmu RESET_PCIE0_MASTER>, + <&syscon_apmu RESET_PCIE0_SLAVE>, + <&syscon_apmu RESET_PCIE0_GLOBAL>; + reset-names = "dbi", + "mstr", + "slv", + "global"; + + interrupts-extended = <&plic 141>; + + #address-cells = <3>; + #size-cells = <2>; + + device_type = "pci"; + max-link-speed = <2>; + bus-range = <0x00 0xff>; + num-viewport = <8>; + + status = "disabled"; + }; + + pcie1: pcie@ca400000 { + compatible = "spacemit,k1-pcie-rc"; + reg = <0x0 0xca400000 0x0 0x00001000>, + <0x0 0xca700000 0x0 0x0001ff24>, + <0x0 0x9f000000 0x0 0x00002000>, + <0x0 0xc0c20000 0x0 0x00001000>; + reg-names = "dbi", + "atu", + "config", + "link"; + spacemit,syscon-pmu = <&syscon_apmu 0x3d4>; + + ranges = <0x01000000 0x0 0x9f002000 0 0x9f002000 0x0 0x100000>, + <0x02000000 0x0 0x90000000 0 0x90000000 0x0 0x0f000000>; + clocks = <&syscon_apmu CLK_PCIE1_DBI>, + <&syscon_apmu CLK_PCIE1_MASTER>, + <&syscon_apmu CLK_PCIE1_SLAVE>; + clock-names = "dbi", + "mstr", + "slv"; + + resets = <&syscon_apmu RESET_PCIE1_DBI>, + <&syscon_apmu RESET_PCIE1_MASTER>, + <&syscon_apmu RESET_PCIE1_SLAVE>, + <&syscon_apmu RESET_PCIE1_GLOBAL>; + reset-names = "dbi", + "mstr", + "slv", + "global"; + + interrupts-extended = <&plic 142>; + + #address-cells = <3>; + #size-cells = <2>; + + device_type = "pci"; + max-link-speed = <2>; + bus-range = <0x00 0xff>; + num-viewport = <8>; + + status = "disabled"; + }; + + pcie2: pcie@ca800000 { + compatible = "spacemit,k1-pcie-rc"; + reg = <0x0 0xca800000 0x0 0x00001000>, + <0x0 0xcab00000 0x0 0x0001ff24>, + <0x0 0xb7000000 0x0 0x00002000>, + <0x0 0xc0d20000 0x0 0x00001000>; + reg-names = "dbi", + "atu", + "config", + "link"; + + spacemit,syscon-pmu = <&syscon_apmu 0x3dc>; + + ranges = <0x01000000 0x0 0xb7002000 0 0xb7002000 0x0 0x100000>, + <0x42000000 0x0 0xa0000000 0 0xa0000000 0x0 0x10000000>, + <0x02000000 0x0 0xb0000000 0 0xb0000000 0x0 0x7000000>; + clocks = <&syscon_apmu CLK_PCIE2_DBI>, + <&syscon_apmu CLK_PCIE2_MASTER>, + <&syscon_apmu CLK_PCIE2_SLAVE>; + clock-names = "dbi", + "mstr", + "slv"; + + resets = <&syscon_apmu RESET_PCIE2_DBI>, + <&syscon_apmu RESET_PCIE2_MASTER>, + <&syscon_apmu RESET_PCIE2_SLAVE>, + <&syscon_apmu RESET_PCIE2_GLOBAL>; + reset-names = "dbi", + "mstr", + "slv", + "global"; + + interrupts-extended = <&plic 143>; + + #address-cells = <3>; + #size-cells = <2>; + + device_type = "pci"; + max-link-speed = <2>; + bus-range = <0x00 0xff>; + num-viewport = <8>; + + status = "disabled"; + }; }; storage-bus { diff --git a/drivers/pci/controller/dwc/Kconfig b/drivers/pci/controller/dwc/Kconfig index ff6b6d9e18ecfa..ca5782c041ce84 100644 --- a/drivers/pci/controller/dwc/Kconfig +++ b/drivers/pci/controller/dwc/Kconfig @@ -492,4 +492,14 @@ config PCIE_VISCONTI_HOST Say Y here if you want PCIe controller support on Toshiba Visconti SoC. This driver supports TMPV7708 SoC. +config PCIE_K1 + bool "SpacemiT K1 host mode PCIe controller" + depends on ARCH_SPACEMIT || COMPILE_TEST + depends on PCI && OF && HAS_IOMEM + select PCIE_DW_HOST + default ARCH_SPACEMIT + help + Enables support for the PCIe controller in the K1 SoC operating + in host mode. + endmenu diff --git a/drivers/pci/controller/dwc/Makefile b/drivers/pci/controller/dwc/Makefile index 6919d27798d13c..62d9d4e7dd4d38 100644 --- a/drivers/pci/controller/dwc/Makefile +++ b/drivers/pci/controller/dwc/Makefile @@ -31,6 +31,7 @@ obj-$(CONFIG_PCIE_UNIPHIER) += pcie-uniphier.o obj-$(CONFIG_PCIE_UNIPHIER_EP) += pcie-uniphier-ep.o obj-$(CONFIG_PCIE_VISCONTI_HOST) += pcie-visconti.o obj-$(CONFIG_PCIE_RCAR_GEN4) += pcie-rcar-gen4.o +obj-$(CONFIG_PCIE_K1) += pcie-k1.o # The following drivers are for devices that use the generic ACPI # pci_root.c driver but don't support standard ECAM config access. diff --git a/drivers/pci/controller/dwc/pcie-k1.c b/drivers/pci/controller/dwc/pcie-k1.c new file mode 100644 index 00000000000000..e9b1df3428d164 --- /dev/null +++ b/drivers/pci/controller/dwc/pcie-k1.c @@ -0,0 +1,355 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * SpacemiT K1 PCIe host driver + * + * Copyright (C) 2025 by RISCstar Solutions Corporation. All rights reserved. + * Copyright (c) 2023, spacemit Corporation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pcie-designware.h" + +#define K1_PCIE_VENDOR_ID 0x201f +#define K1_PCIE_DEVICE_ID 0x0001 + +/* Offsets and field definitions of link management registers */ + +#define K1_PHY_AHB_IRQ_EN 0x0000 +#define PCIE_INTERRUPT_EN BIT(0) + +#define K1_PHY_AHB_LINK_STS 0x0004 +#define SMLH_LINK_UP BIT(1) +#define RDLH_LINK_UP BIT(12) + +#define INTR_ENABLE 0x0014 +#define MSI_CTRL_INT BIT(11) + +/* Offsets and field definitions for PMU registers */ + +#define PCIE_CLK_RESET_CONTROL 0x0000 +#define LTSSM_EN BIT(6) +#define PCIE_AUX_PWR_DET BIT(9) +#define PCIE_RC_PERST BIT(12) /* 0: PERST# high; 1: low */ +#define APP_HOLD_PHY_RST BIT(30) +#define DEVICE_TYPE_RC BIT(31) /* 0: endpoint; 1: RC */ + +#define PCIE_CONTROL_LOGIC 0x0004 +#define PCIE_SOFT_RESET BIT(0) + +struct k1_pcie { + struct dw_pcie pci; + void __iomem *link; + struct regmap *pmu; + u32 pmu_off; + struct phy *phy; + struct reset_control *global_reset; +}; + +#define to_k1_pcie(dw_pcie) dev_get_drvdata((dw_pcie)->dev) + +static int k1_pcie_toggle_soft_reset(struct k1_pcie *k1) +{ + u32 offset = k1->pmu_off + PCIE_CONTROL_LOGIC; + const u32 mask = PCIE_SOFT_RESET; + int ret; + + ret = regmap_set_bits(k1->pmu, offset, mask); + if (ret) + return ret; + + mdelay(2); + + return regmap_clear_bits(k1->pmu, offset, mask); +} + +/* Enable app clocks, deassert app resets */ +static int k1_pcie_app_enable(struct k1_pcie *k1) +{ + struct dw_pcie *pci = &k1->pci; + u32 clock_count; + u32 reset_count; + int ret; + + clock_count = ARRAY_SIZE(pci->app_clks); + ret = clk_bulk_prepare_enable(clock_count, pci->app_clks); + if (ret) + return ret; + + reset_count = ARRAY_SIZE(pci->app_rsts); + ret = reset_control_bulk_deassert(reset_count, pci->app_rsts); + if (ret) + goto err_disable_clks; + + ret = reset_control_deassert(k1->global_reset); + if (ret) + goto err_assert_resets; + + return 0; + +err_assert_resets: + (void)reset_control_bulk_assert(reset_count, pci->app_rsts); +err_disable_clks: + clk_bulk_disable_unprepare(clock_count, pci->app_clks); + + return ret; +} + +/* Disable app clocks, assert app resets */ +static void k1_pcie_app_disable(struct k1_pcie *k1) +{ + struct dw_pcie *pci = &k1->pci; + u32 count; + int ret; + + (void)reset_control_assert(k1->global_reset); + + count = ARRAY_SIZE(pci->app_rsts); + ret = reset_control_bulk_assert(count, pci->app_rsts); + if (ret) + dev_err(pci->dev, "app reset assert failed (%d)\n", ret); + + count = ARRAY_SIZE(pci->app_clks); + clk_bulk_disable_unprepare(count, pci->app_clks); +} + +static int k1_pcie_init(struct dw_pcie_rp *pp) +{ + struct dw_pcie *pci = to_dw_pcie_from_pp(pp); + struct k1_pcie *k1 = to_k1_pcie(pci); + u32 offset; + u32 mask; + int ret; + + ret = k1_pcie_toggle_soft_reset(k1); + if (ret) + goto err_app_disable; + + ret = k1_pcie_app_enable(k1); + if (ret) + return ret; + + ret = phy_init(k1->phy); + if (ret) + goto err_app_disable; + + /* Set the PCI vendor and device ID */ + dw_pcie_dbi_ro_wr_en(pci); + dw_pcie_writew_dbi(pci, PCI_VENDOR_ID, K1_PCIE_VENDOR_ID); + dw_pcie_writew_dbi(pci, PCI_DEVICE_ID, K1_PCIE_DEVICE_ID); + dw_pcie_dbi_ro_wr_dis(pci); + + /* + * Put the port in root complex mode, record that Vaux is present. + * Assert fundamental reset (drive PERST# low). + */ + offset = k1->pmu_off + PCIE_CLK_RESET_CONTROL; + mask = DEVICE_TYPE_RC | PCIE_AUX_PWR_DET; + mask |= PCIE_RC_PERST; + ret = regmap_set_bits(k1->pmu, offset, mask); + if (ret) + goto err_phy_exit; + + /* Wait the PCIe-mandated 100 msec before deasserting PERST# */ + mdelay(100); + + ret = regmap_clear_bits(k1->pmu, offset, PCIE_RC_PERST); + if (!ret) + return 0; /* Success! */ + +err_phy_exit: + (void)phy_exit(k1->phy); +err_app_disable: + k1_pcie_app_disable(k1); + + return ret; +} + +/* Silently ignore any errors */ +static void k1_pcie_deinit(struct dw_pcie_rp *pp) +{ + struct dw_pcie *pci = to_dw_pcie_from_pp(pp); + struct k1_pcie *k1 = to_k1_pcie(pci); + + /* Re-assert fundamental reset (drive PERST# low) */ + (void)regmap_set_bits(k1->pmu, k1->pmu_off + PCIE_CLK_RESET_CONTROL, + PCIE_RC_PERST); + + (void)phy_exit(k1->phy); + + k1_pcie_app_disable(k1); +} + +static const struct dw_pcie_host_ops k1_pcie_host_ops = { + .init = k1_pcie_init, + .deinit = k1_pcie_deinit, +}; + +static void k1_pcie_enable_interrupts(struct k1_pcie *k1) +{ + void __iomem *virt; + u32 val; + + /* Enable the MSI interrupt */ + writel(MSI_CTRL_INT, k1->link + INTR_ENABLE); + + /* Top-level interrupt enable */ + virt = k1->link + K1_PHY_AHB_IRQ_EN; + val = readl(virt); + val |= PCIE_INTERRUPT_EN; + writel(val, virt); +} + +static void k1_pcie_disable_interrupts(struct k1_pcie *k1) +{ + void __iomem *virt; + u32 val; + + virt = k1->link + K1_PHY_AHB_IRQ_EN; + val = readl(virt); + val &= ~PCIE_INTERRUPT_EN; + writel(val, virt); + + writel(0, k1->link + INTR_ENABLE); +} + +static bool k1_pcie_link_up(struct dw_pcie *pci) +{ + struct k1_pcie *k1 = to_k1_pcie(pci); + u32 val; + + val = readl(k1->link + K1_PHY_AHB_LINK_STS); + + return (val & RDLH_LINK_UP) && (val & SMLH_LINK_UP); +} + +static int k1_pcie_start_link(struct dw_pcie *pci) +{ + struct k1_pcie *k1 = to_k1_pcie(pci); + int ret; + + /* Stop holding the PHY in reset, and enable link training */ + ret = regmap_update_bits(k1->pmu, k1->pmu_off + PCIE_CLK_RESET_CONTROL, + APP_HOLD_PHY_RST | LTSSM_EN, LTSSM_EN); + if (ret) + return ret; + + k1_pcie_enable_interrupts(k1); + + return 0; +} + +static void k1_pcie_stop_link(struct dw_pcie *pci) +{ + struct k1_pcie *k1 = to_k1_pcie(pci); + int ret; + + k1_pcie_disable_interrupts(k1); + + /* Disable the link and hold the PHY in reset */ + ret = regmap_update_bits(k1->pmu, k1->pmu_off + PCIE_CLK_RESET_CONTROL, + APP_HOLD_PHY_RST | LTSSM_EN, APP_HOLD_PHY_RST); + if (ret) + dev_err(pci->dev, "disable LTSSM failed (%d)\n", ret); +} + +static const struct dw_pcie_ops k1_pcie_ops = { + .link_up = k1_pcie_link_up, + .start_link = k1_pcie_start_link, + .stop_link = k1_pcie_stop_link, +}; + +static int k1_pcie_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct dw_pcie_rp *pp; + struct dw_pcie *pci; + struct k1_pcie *k1; + int ret; + + k1 = devm_kzalloc(dev, sizeof(*k1), GFP_KERNEL); + if (!k1) + return -ENOMEM; + dev_set_drvdata(dev, k1); + + k1->pmu = syscon_regmap_lookup_by_phandle_args(dev_of_node(dev), + "spacemit,syscon-pmu", + 1, &k1->pmu_off); + if (IS_ERR(k1->pmu)) + return dev_err_probe(dev, PTR_ERR(k1->pmu), + "lookup PMU regmap failed\n"); + + k1->link = devm_platform_ioremap_resource_byname(pdev, "link"); + if (!k1->link) + return dev_err_probe(dev, -ENOMEM, "map link regs failed\n"); + + k1->global_reset = devm_reset_control_get_shared(dev, "global"); + if (IS_ERR(k1->global_reset)) + return dev_err_probe(dev, PTR_ERR(k1->global_reset), + "get global reset failed\n"); + + /* Hold the PHY in reset until we start the link */ + ret = regmap_set_bits(k1->pmu, k1->pmu_off + PCIE_CLK_RESET_CONTROL, + APP_HOLD_PHY_RST); + if (ret) + return dev_err_probe(dev, ret, "hold PHY in reset failed\n"); + + k1->phy = devm_phy_get(dev, NULL); + if (IS_ERR(k1->phy)) + return dev_err_probe(dev, PTR_ERR(k1->phy), "get PHY failed\n"); + + pci = &k1->pci; + dw_pcie_cap_set(pci, REQ_RES); + pci->dev = dev; + pci->ops = &k1_pcie_ops; + + pp = &pci->pp; + pp->num_vectors = MAX_MSI_IRQS; + pp->ops = &k1_pcie_host_ops; + + ret = dw_pcie_host_init(pp); + if (ret) + return dev_err_probe(dev, ret, "host init failed\n"); + + return 0; +} + +static void k1_pcie_remove(struct platform_device *pdev) +{ + struct k1_pcie *k1 = dev_get_drvdata(&pdev->dev); + struct dw_pcie_rp *pp = &k1->pci.pp; + + dw_pcie_host_deinit(pp); +} + +static const struct of_device_id k1_pcie_of_match_table[] = { + { .compatible = "spacemit,k1-pcie-rc", }, + { }, +}; + +static struct platform_driver k1_pcie_driver = { + .probe = k1_pcie_probe, + .remove = k1_pcie_remove, + .driver = { + .name = "k1-dwc-pcie", + .of_match_table = k1_pcie_of_match_table, + .suppress_bind_attrs = true, + }, +}; +module_platform_driver(k1_pcie_driver); diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index 58c911e1b2d20a..0fa343203f2890 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig @@ -101,6 +101,17 @@ config PHY_NXP_PTN3222 schemes. It supports all three USB 2.0 data rates: Low Speed, Full Speed and High Speed. +config PHY_SPACEMIT_K1_PCIE + tristate "PCIe and combo PHY driver for the SpacemiT K1 SoC" + depends on ARCH_SPACEMIT || COMPILE_TEST + depends on HAS_IOMEM + depends on OF + select GENERIC_PHY + default ARCH_SPACEMIT + help + Enable support for the PCIe and USB 3 combo PHY and two + PCIe-only PHYs used in the SpacemiT K1 SoC. + source "drivers/phy/allwinner/Kconfig" source "drivers/phy/amlogic/Kconfig" source "drivers/phy/broadcom/Kconfig" diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile index c670a8dac46807..20f0078e543c7f 100644 --- a/drivers/phy/Makefile +++ b/drivers/phy/Makefile @@ -13,6 +13,7 @@ obj-$(CONFIG_PHY_SNPS_EUSB2) += phy-snps-eusb2.o obj-$(CONFIG_USB_LGM_PHY) += phy-lgm-usb.o obj-$(CONFIG_PHY_AIROHA_PCIE) += phy-airoha-pcie.o obj-$(CONFIG_PHY_NXP_PTN3222) += phy-nxp-ptn3222.o +obj-$(CONFIG_PHY_SPACEMIT_K1_PCIE) += phy-spacemit-k1-pcie.o obj-y += allwinner/ \ amlogic/ \ broadcom/ \ diff --git a/drivers/phy/phy-spacemit-k1-pcie.c b/drivers/phy/phy-spacemit-k1-pcie.c new file mode 100644 index 00000000000000..32dce53170fbbe --- /dev/null +++ b/drivers/phy/phy-spacemit-k1-pcie.c @@ -0,0 +1,639 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * SpacemiT K1 PCIe and PCIe/USB 3 combo PHY driver + * + * Copyright (C) 2025 by RISCstar Solutions Corporation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* + * Three PCIe ports are supported in the SpacemiT K1 SoC, and this driver + * supports their PHYs. + * + * The PHY for PCIe port A is different from the PHYs for ports B and C: + * - It has one lane, while ports B and C have two + * - It is a combo PHY can be used for PCIe or USB 3 + * - It can automatically calibrate PCIe TX and RX termination settings + * + * The PHY functionality for PCIe ports B and C is identical: + * - They have two PCIe lanes (but can be restricted to 1 via Device Tree) + * - They are used for PCIe only + * - They are configured using TX and RX values computed for port A + * + * A given board is designed to use the combo PHY for either PCIe or USB 3. + * Whether the combo PHY is configured for PCIe or USB 3 is specified in + * Device Tree using a phandle plus an argument. The argument indicates + * the type (either PHY_TYPE_PCIE or PHY_TYPE_USB3). + * + * Each PHY depends on clocks and resets provided by the controller + * hardware (PCIe or USB) it is associated with. The controller drivers + * are required to enable any clocks and de-assert any resets that affect + * PHY operation. + * + * PCIe PHYs must be programmed with RX and TX calibration values. The + * combo PHY is the only one that can determine these values. They are + * determined by temporarily enabling the combo PHY in PCIe mode at probe + * time (if necessary). This calibration only needs to be done once, and + * when it has completed the TX and RX values are saved. + * + * To allow the combo PHY to be enabled for calibration, the resets and + * clocks it uses in PCIe mode must be supplied. + */ + +struct k1_pcie_phy { + struct device *dev; /* PHY provider device */ + struct phy *phy; + void __iomem *regs; + u32 pcie_lanes; /* Max unless limited by DT */ + /* The remaining fields are only used for the combo PHY */ + u32 type; /* PHY_TYPE_PCIE or PHY_TYPE_USB3 */ + struct regmap *pmu; +}; + +#define CALIBRATION_TIMEOUT 500000 /* microseconds */ +#define PLL_TIMEOUT 500000 /* microseconds */ +#define POLL_DELAY 500 /* microseconds */ + +/* Selecting the combo PHY operating mode requires PMU regmap access */ +#define SYSCON_PMU "spacemit,syscon-pmu" + +/* PMU space, for selecting between PCIe and USB3 mode on the combo PHY */ + +#define PMUA_USB_PHY_CTRL0 0x0110 +#define COMBO_PHY_SEL BIT(3) /* 0: PCIe; 1: USB3 */ + +#define PCIE_CLK_RES_CTRL 0x03cc +#define PCIE_APP_HOLD_PHY_RST BIT(30) + +/* PHY register space */ + +/* Offset between lane 0 and lane 1 registers when there are two */ +#define PHY_LANE_OFFSET 0x0400 + +#define PCIE_PU_ADDR_CLK_CFG 0x0008 +#define PLL_READY BIT(0) /* read-only */ +#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_INTERNAL_TIMER_ADJ GENMASK(10, 7) +#define TIMER_ADJ_USB 0x2 +#define TIMER_ADJ_PCIE 0x6 +#define CFG_SW_PHY_INIT_DONE BIT(11) /* We set after PLL config */ + +#define PCIE_RC_DONE_STATUS 0x0018 +#define CFG_FORCE_RCV_RETRY BIT(10) + +#define PCIE_RC_CAL_REG2 0x0020 +#define RC_CAL_TOGGLE BIT(22) +#define CLKSEL GENMASK(31, 29) +#define CLKSEL_24M 0x3 + +#define PCIE_PU_PLL_1 0x0048 +#define REF_100_WSSC BIT(12) /* 1: input is 100MHz, SSC */ +#define FREF_SEL GENMASK(15, 13) +#define FREF_24M 0x1 +#define SSC_DEP_SEL GENMASK(19, 16) +#define SSC_DEP_NONE 0x0 +#define SSC_DEP_5000PPM 0xa +#define SSC_MODE GENMASK(21, 20) +#define SSC_MODE_DOWN_SPREAD 0x3 +#define SSC_OFFSET GENMASK(23, 22) +#define SSC_OFFSET_0_PPM 0x0 + +#define PCIE_PU_PLL_2 0x004c +#define GEN_REF100 BIT(4) /* 1: generate 100MHz clk */ + +#define PCIE_RX_REG1 0x0050 +#define EN_RTERM BIT(3) +#define AFE_RTERM_REG GENMASK(11, 8) + +#define PCIE_RX_REG2 0x0054 +#define RX_RTERM_SEL BIT(5) /* 0: use AFE_RTERM_REG value */ + +#define PCIE_LTSSM_DIS_ENTRY 0x005c +#define CFG_REFCLK_MODE GENMASK(9, 8) +#define RFCLK_MODE_DRIVER 0x1 +#define OVRD_REFCLK_MODE BIT(10) /* 1: use CFG_RFCLK_MODE */ + +#define PCIE_TX_REG1 0x0064 +#define TX_RTERM_REG GENMASK(15, 12) +#define TX_RTERM_SEL BIT(25) /* 1: use TX_RTERM_REG */ + +#define USB3_TEST_CTRL 0x0068 + +#define PCIE_RCAL_RESULT 0x0084 /* Port A PHY only */ +#define RTERM_VALUE_RX GENMASK(3, 0) +#define RTERM_VALUE_TX GENMASK(7, 4) +#define R_TUNE_DONE BIT(10) + +static u32 k1_phy_rterm = ~0; /* Invalid initial value */ + +/* Save the RX and TX receiver termination values */ +static void k1_phy_rterm_set(u32 val) +{ + k1_phy_rterm = val & (RTERM_VALUE_RX | RTERM_VALUE_TX); +} + +static bool k1_phy_rterm_valid(void) +{ + /* Valid if no bits outside those we care about are set */ + return !(k1_phy_rterm & ~(RTERM_VALUE_RX | RTERM_VALUE_TX)); +} + +static u32 k1_phy_rterm_rx(void) +{ + return FIELD_GET(RTERM_VALUE_RX, k1_phy_rterm); +} + +static u32 k1_phy_rterm_tx(void) +{ + return FIELD_GET(RTERM_VALUE_TX, k1_phy_rterm); +} + +/* Only the combo PHY has a PMU pointer defined */ +static bool k1_phy_port_a(struct k1_pcie_phy *k1_phy) +{ + return !!k1_phy->pmu; +} + +/* + * Select PCIe or USB 3 mode for the combo PHY. Return 1 if the bit + * was changed, 0 if it was not, or a negative error value otherwise. + */ +static int k1_combo_phy_sel(struct k1_pcie_phy *k1_phy, bool usb3) +{ + int ret; + + ret = regmap_test_bits(k1_phy->pmu, PMUA_USB_PHY_CTRL0, COMBO_PHY_SEL); + if (ret < 0) + return ret; + + /* If it's already in the desired state, we're done */ + if (!!ret == usb3) + return 0; + + /* Change the bit */ + ret = regmap_assign_bits(k1_phy->pmu, PMUA_USB_PHY_CTRL0, + COMBO_PHY_SEL, usb3); + + return ret < 0 ? ret : 1; +} + +static void k1_pcie_phy_init_pll(struct k1_pcie_phy *k1_phy, + void __iomem *regs, bool pcie) +{ + void __iomem *virt; + u32 timer_adj; + u32 ssc_dep; + u32 val; + + if (pcie) { + timer_adj = TIMER_ADJ_PCIE; + ssc_dep = SSC_DEP_NONE; + } else { + timer_adj = TIMER_ADJ_USB; + ssc_dep = SSC_DEP_5000PPM; + } + + /* + * Disable 100 MHz input reference with spread-spectrum + * clocking and select the 24 MHz clock input frequency + */ + virt = k1_phy->regs + PCIE_PU_PLL_1; + val = readl(virt); + val &= ~REF_100_WSSC; + + val &= ~FREF_SEL; + val |= FIELD_PREP(FREF_SEL, FREF_24M); + + val &= ~SSC_DEP_SEL; + val |= FIELD_PREP(SSC_DEP_SEL, ssc_dep); + + val &= ~SSC_MODE; + val |= FIELD_PREP(SSC_MODE, SSC_MODE_DOWN_SPREAD); + + val &= ~SSC_OFFSET; + val |= FIELD_PREP(SSC_OFFSET, SSC_OFFSET_0_PPM); + writel(val, virt); + + if (pcie) { + virt = regs + PCIE_PU_PLL_2; + val = readl(virt); + val |= GEN_REF100; /* Enable 100 MHz PLL output clock */ + writel(val, virt); + } + + /* Enable clocks and mark PLL initialization done */ + virt = regs + PCIE_PU_ADDR_CLK_CFG; + val = readl(virt); + val |= CFG_RXCLK_EN; + val |= CFG_TXCLK_EN; + val |= CFG_PCLK_EN; + val |= CFG_PIPE_PCLK_EN; + + val &= ~CFG_INTERNAL_TIMER_ADJ; + val |= FIELD_PREP(CFG_INTERNAL_TIMER_ADJ, timer_adj); + + val |= CFG_SW_PHY_INIT_DONE; + writel(val, virt); +} + +static int k1_pcie_pll_lock(struct k1_pcie_phy *k1_phy, bool pcie) +{ + u32 val = pcie ? CFG_FORCE_RCV_RETRY : 0; + void __iomem *virt; + + writel(val, k1_phy->regs + PCIE_RC_DONE_STATUS); + + /* + * Wait for indication the PHY PLL is locked. Lanes for ports + * B and C share a PLL, so it's enough to sample just lane 0. + */ + virt = k1_phy->regs + PCIE_PU_ADDR_CLK_CFG; /* Lane 0 */ + + return readl_poll_timeout(virt, val, val & PLL_READY, + POLL_DELAY, PLL_TIMEOUT); +} + +static int k1_pcie_phy_init_pcie(struct k1_pcie_phy *k1_phy) +{ + u32 rx_rterm = k1_phy_rterm_rx(); + u32 tx_rterm = k1_phy_rterm_tx(); + void __iomem *virt; + void __iomem *regs; + u32 val; + int ret; + int i; + + /* For the combo PHY, set PHY to PCIe mode */ + if (k1_phy_port_a(k1_phy)) { + ret = k1_combo_phy_sel(k1_phy, false); + if (ret < 0) + return ret; + } + + regs = k1_phy->regs; + for (i = 0; i < k1_phy->pcie_lanes; i++) { + virt = regs + PCIE_RX_REG1; + val = readl(virt); + + /* Set RX analog front-end receiver termination value */ + val &= ~AFE_RTERM_REG; + val |= FIELD_PREP(AFE_RTERM_REG, rx_rterm); + + /* And enable refclock receiver termination */ + val |= EN_RTERM; + writel(val, virt); + + virt = regs + PCIE_RX_REG2; + val = readl(virt); + /* Use PCIE_RX_REG1 AFE_RTERM_REG value */ + val &= ~RX_RTERM_SEL; + writel(val, virt); + + virt = regs + PCIE_TX_REG1; + val = readl(virt); + + /* Set TX driver termination value */ + val &= ~TX_RTERM_REG; + val |= FIELD_PREP(TX_RTERM_REG, tx_rterm); + + /* Use PCIE_TX_REG1 TX_RTERM_REG value */ + val |= TX_RTERM_SEL; + writel(val, virt); + + virt = regs + PCIE_RC_CAL_REG2; + val = readl(virt); + + /* Set the input clock to 24 MHz, and clear RC_CAL_TOGGLE */ + val &= CLKSEL; + val |= FIELD_PREP(CLKSEL, CLKSEL_24M); + + val &= ~RC_CAL_TOGGLE; + writel(val, virt); + /* Trigger recalibration by setting RC_CAL_TOGGLE again */ + val |= RC_CAL_TOGGLE; + writel(val, virt); + + virt = regs + PCIE_LTSSM_DIS_ENTRY; + val = readl(virt); + /* Override the reference clock; set to refclk driver mode */ + val |= OVRD_REFCLK_MODE; + + val &= ~CFG_REFCLK_MODE; + val |= FIELD_PREP(CFG_REFCLK_MODE, RFCLK_MODE_DRIVER); + + writel(val, virt); + + k1_pcie_phy_init_pll(k1_phy, regs, true); + + regs += PHY_LANE_OFFSET; /* Next lane */ + } + + return k1_pcie_pll_lock(k1_phy, true); +} + +/* Only called for combo PHY */ +static int k1_pcie_phy_init_usb3_host(struct k1_pcie_phy *k1_phy) +{ + int ret; + + ret = k1_combo_phy_sel(k1_phy, true); + if (ret < 0) + return ret; + + /* We're not doing any testing */ + writel(0, k1_phy->regs + USB3_TEST_CTRL); + + k1_pcie_phy_init_pll(k1_phy, k1_phy->regs, false); + + return k1_pcie_pll_lock(k1_phy, false); +} + +static int k1_pcie_phy_init(struct phy *phy) +{ + struct k1_pcie_phy *k1_phy = phy_get_drvdata(phy); + + if (k1_phy_port_a(k1_phy) && k1_phy->type == PHY_TYPE_USB3) + return k1_pcie_phy_init_usb3_host(k1_phy); + + return k1_pcie_phy_init_pcie(k1_phy); +} + +static const struct phy_ops k1_pcie_phy_ops = { + .init = k1_pcie_phy_init, + .owner = THIS_MODULE, +}; + +/* + * Get values needed for calibrating PHYs operating in PCIe mode. Only + * the combo PHY is able to do this, and its calibration values are used + * for configuring all PCIe PHYs. + * + * We always need to de-assert the (PCIe) global reset on the combo PHY, + * because the USB driver depends on it. If used for PCIe, the driver + * will (also) de-assert this, but by leaving it de-asserted for the + * combo PHY, the USB driver doesn't have to do this. + * of this first. + * + * In addition, we guarantee the APP_HOLD_PHY_RESET bit is clear for the + * combo PHY, so the USB driver doesn't have to manage that either. The + * PCIe driver is free to change this bit for normal operation. + * + * Calibration only needs to be done once. It's possible calibration has + * already completed (e.g., it might have happened in the boot loader, or + * -EPROBE_DEFER might result in this function being called again). So we + * check that early too, to avoid doing it more than once. + * + * Otherwise we temporarily power up the PHY, wait for the hardware to + * indicate calibration is done, grab the value, then shut the PHY down + * again. + */ +static int k1_pcie_combo_phy_calibrate(struct k1_pcie_phy *k1_phy) +{ + struct reset_control_bulk_data data[] = { + { .id = "dbi", }, + { .id = "mstr", }, + { .id = "slv", }, + }; + size_t data_size = ARRAY_SIZE(data); + struct reset_control *global_reset; + struct device *dev = k1_phy->dev; + struct clk_bulk_data *clocks; + void __iomem *virt; + bool mode_changed; + u32 clock_count; + int ret = 0; + int val; + + /* We always de-assert the global reset and leave it that way */ + global_reset = devm_reset_control_get_shared_deasserted(dev, "global"); + if (IS_ERR(global_reset)) + return PTR_ERR(global_reset); + + /* + * We also guarantee the APP_HOLD_PHY_RESET bit is clear. If an + * error occurs we can't go on, but otherwise we can leave this + * bit clear even if an error happens below. + */ + ret = regmap_assign_bits(k1_phy->pmu, PCIE_CLK_RES_CTRL, + PCIE_APP_HOLD_PHY_RST, false); + if (ret < 0) + return ret; + + /* If the receiver termination value is valid, nothing more to do */ + if (k1_phy_rterm_valid()) + return 0; + + /* If the calibration already completed (e.g. by U-Boot), we're done */ + virt = k1_phy->regs + PCIE_RCAL_RESULT; + val = readl(virt); + if (val & R_TUNE_DONE) + goto done; + + /* Make sure the PHY is configured for PCIe */ + ret = k1_combo_phy_sel(k1_phy, false); + if (ret < 0) + return ret; + mode_changed = ret > 0; + + /* Get and enable all clocks */ + ret = clk_bulk_get_all(dev, &clocks); + if (ret < 0) + return ret; + if (!ret) + return -ENOENT; + clock_count = ret; + + ret = clk_bulk_prepare_enable(clock_count, clocks); + if (ret) + goto out_put_clocks; + + /* Get the (not "global") PCIe application resets */ + ret = reset_control_bulk_get_shared(dev, data_size, data); + if (ret) + goto out_disable_clocks; + + /* De-assert the PCIe application resets */ + ret = reset_control_bulk_deassert(data_size, data); + if (ret) + goto out_put_resets; + + /* + * This is the core activity here. Wait for the hardware to + * signal that it has completed calibration/tuning. Once it + * has, the register value will contain the values we'll + * use to configure PCIe PHYs. + */ + ret = readl_poll_timeout(virt, val, val & R_TUNE_DONE, + POLL_DELAY, CALIBRATION_TIMEOUT); + + /* Clean up. We're done with the resets and clocks */ + reset_control_bulk_assert(data_size, data); +out_put_resets: + reset_control_bulk_put(data_size, data); +out_disable_clocks: + clk_bulk_disable_unprepare(clock_count, clocks); +out_put_clocks: + clk_bulk_put_all(clock_count, clocks); + + /* If we changed the mode, restore the original state */ + if (mode_changed) + (void)k1_combo_phy_sel(k1_phy, true); +done: + /* If we got the value without timing out, set k1_phy_rterm */ + if (!ret) + k1_phy_rterm_set(val); + + return ret; +} + +static struct phy * +k1_pcie_combo_phy_xlate(struct device *dev, const struct of_phandle_args *args) +{ + struct k1_pcie_phy *k1_phy = dev_get_drvdata(dev); + u32 type; + + /* The argument specifying the PHY mode is required */ + if (args->args_count != 1) + return ERR_PTR(-EINVAL); + + /* We only support PCIe and USB3 mode */ + type = args->args[0]; + if (type != PHY_TYPE_PCIE && type != PHY_TYPE_USB3) + return ERR_PTR(-EINVAL); + + /* This PHY can only be used once */ + if (k1_phy->type != PHY_NONE) + return ERR_PTR(-EBUSY); + + k1_phy->type = type; + + return k1_phy->phy; +} + +/* Use the maximum number of PCIe lanes unless limited by Device Tree */ +static u32 k1_pcie_num_lanes(struct k1_pcie_phy *k1_phy, bool port_a) +{ + struct device *dev = k1_phy->dev; + u32 count = 0; + u32 max; + int ret; + + ret = of_property_read_u32(dev_of_node(dev), "num-lanes", &count); + if (count == 1) + return 1; + + if (count == 2 && !port_a) + return 2; + + max = port_a ? 1 : 2; + if (ret != -EINVAL) + dev_warn(dev, "bad lane count %u for port; using %u\n", + count, max); + + return max; +} + +static int k1_pcie_combo_phy_probe(struct k1_pcie_phy *k1_phy) +{ + struct device *dev = k1_phy->dev; + struct regmap *regmap; + int ret; + + /* Setting the PHY mode requires access to the PMU regmap */ + regmap = syscon_regmap_lookup_by_phandle(dev_of_node(dev), SYSCON_PMU); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), + "error getting PMU\n"); + k1_phy->pmu = regmap; + + ret = k1_pcie_combo_phy_calibrate(k1_phy); + if (ret) + return dev_err_probe(dev, ret, "calibration failed\n"); + + /* Needed by k1_pcie_combo_phy_xlate(), which also sets k1_phy->type */ + dev_set_drvdata(dev, k1_phy); + + return 0; +} + +static int k1_pcie_phy_probe(struct platform_device *pdev) +{ + struct phy *(*xlate)(struct device *, const struct of_phandle_args *); + struct device *dev = &pdev->dev; + struct phy_provider *provider; + struct k1_pcie_phy *k1_phy; + bool probing_port_a; + int ret; + + xlate = of_device_get_match_data(dev); + probing_port_a = xlate == k1_pcie_combo_phy_xlate; + + /* Only the combo PHY can calibrate, so it must probe first */ + if (!k1_phy_rterm_valid() && !probing_port_a) + return -EPROBE_DEFER; + + k1_phy = devm_kzalloc(dev, sizeof(*k1_phy), GFP_KERNEL); + if (!k1_phy) + return -ENOMEM; + k1_phy->dev = dev; + + k1_phy->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(k1_phy->regs)) + return dev_err_probe(dev, PTR_ERR(k1_phy->regs), + "error mapping registers\n"); + + k1_phy->pcie_lanes = k1_pcie_num_lanes(k1_phy, probing_port_a); + + if (probing_port_a) { + ret = k1_pcie_combo_phy_probe(k1_phy); + if (ret) + return dev_err_probe(dev, ret, + "error probing combo phy\n"); + } + + k1_phy->phy = devm_phy_create(dev, NULL, &k1_pcie_phy_ops); + if (IS_ERR(k1_phy->phy)) + return dev_err_probe(dev, PTR_ERR(k1_phy->phy), + "error creating phy\n"); + phy_set_drvdata(k1_phy->phy, k1_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 0; +} + +static const struct of_device_id k1_pcie_phy_of_match[] = { + { .compatible = "spacemit,k1-combo-phy", k1_pcie_combo_phy_xlate, }, + { .compatible = "spacemit,k1-pcie-phy", of_phy_simple_xlate, }, + { }, +}; +MODULE_DEVICE_TABLE(of, k1_pcie_phy_of_match); + +static struct platform_driver k1_pcie_phy_driver = { + .probe = k1_pcie_phy_probe, + .driver = { + .of_match_table = k1_pcie_phy_of_match, + .name = "k1-pcie-phy", + } +}; +module_platform_driver(k1_pcie_phy_driver); + +MODULE_DESCRIPTION("SpacemiT K1 PCIe 3.0 and USB 3 PHY driver"); +MODULE_LICENSE("GPL");