diff --git a/arch/riscv/boot/dts/spacemit/k3-com260.dts b/arch/riscv/boot/dts/spacemit/k3-com260.dts index 602e7cfcd69a..80b0e37d539c 100644 --- a/arch/riscv/boot/dts/spacemit/k3-com260.dts +++ b/arch/riscv/boot/dts/spacemit/k3-com260.dts @@ -276,6 +276,15 @@ drive-strength = <25>; power-source = <1800>; }; + pcie0-1-cfg { + pcie0-0-pins { + pinmux = , /* pcie0 perst */ + ; /* pcie0 clkreq */ + bias-pull-up; + drive-strength = <38>; + power-source = <3300>; + }; + }; }; }; @@ -346,3 +355,13 @@ }; }; +&combphy { + status = "okay"; +}; + +&pcie0_rc { + pinctrl-names = "default"; + pinctrl-0 = <&pcie0_1_cfg>; + status = "okay"; +}; + diff --git a/arch/riscv/boot/dts/spacemit/k3.dtsi b/arch/riscv/boot/dts/spacemit/k3.dtsi index 1409ac10517d..64f4531ec555 100644 --- a/arch/riscv/boot/dts/spacemit/k3.dtsi +++ b/arch/riscv/boot/dts/spacemit/k3.dtsi @@ -5,6 +5,7 @@ #include #include +#include / { compatible = "spacemit,k3"; @@ -910,6 +911,55 @@ reset-names = "sdh_axi", "sdh2"; status = "disabled"; }; + + combphy: phy@81d00000 { + compatible = "spacemit,k3-comb-phy"; + reg = <0x0 0x81d00000 0x0 0x600000>; + #phy-cells = <2>; + spacemit,apb-spare = <&pll>; + spacemit,apmu = <&syscon_apmu 0x0>; + status = "disabled"; + }; + + pcie0_rc: pcie@80000000 { + compatible = "spacemit,k3-pcie"; + reg = <0x0 0x80000000 0x0 0x00001000>, + <0x0 0x80100000 0x0 0x00001000>, + <0x0 0x80300000 0x0 0x00003f20>, + <0x11 0x00000000 0x0 0x00010000>, + <0x0 0x82900000 0x0 0x00001000>; + reg-names = "dbi", "dbi2", "atu", "config", "link"; + device_type = "pci"; + #address-cells = <3>; + #size-cells = <2>; + bus-range = <0x00 0xff>; + max-link-speed = <3>; + num-lanes = <8>; + ranges = <0x01000000 0x00 0x00010000 0x11 0x00010000 0x0 0x00100000>, + <0x02000000 0x0 0x00110000 0x11 0x00110000 0x0 0x7fef0000>, + <0x43000000 0x18 0x00000000 0x18 0x00000000 0x1 0x00000000>; + clocks = <&syscon_apmu CLK_APMU_PCIE_PORTA_SLV>, + <&syscon_apmu CLK_APMU_PCIE_PORTA_MSTE>, + <&syscon_apmu CLK_APMU_PCIE_PORTA_SLV>; + clock-names = "dbi", "mstr", "slv"; + resets = <&syscon_apmu RESET_APMU_PCIE_PORTA>, + <&syscon_apmu RESET_APMU_PCIE_PORTA>, + <&syscon_apmu RESET_APMU_PCIE_PORTA>; + reset-names = "dbi", "mstr", "slv"; + phys = <&combphy 0 PHY_TYPE_PCIE>, + <&combphy 1 PHY_TYPE_PCIE>, + <&combphy 2 PHY_TYPE_PCIE>, + <&combphy 3 PHY_TYPE_PCIE>, + <&combphy 4 PHY_TYPE_PCIE>, + <&combphy 5 PHY_TYPE_PCIE>; + phy-names = "pcie0", "pcie1", "pcie2", "pcie3", "pcie4", "pcie5"; + linux,pci-domain = <0>; + msi-parent = <&simsic>; + spacemit,apmu = <&syscon_apmu 0x1f0>; + interrupt-parent = <&saplic>; + interrupts = <141 IRQ_TYPE_LEVEL_HIGH>; + status = "disabled"; + }; }; }; diff --git a/arch/riscv/configs/defconfig b/arch/riscv/configs/defconfig index 1838f8b0dbae..8bd8695d1797 100644 --- a/arch/riscv/configs/defconfig +++ b/arch/riscv/configs/defconfig @@ -625,6 +625,7 @@ CONFIG_PCIE_XILINX=y CONFIG_PCIE_CADENCE_PLAT_HOST=y CONFIG_PCIE_CADENCE_SOPHGO=y CONFIG_PCIE_DW_PLAT_HOST=y +CONFIG_PCIE_SPACEMIT_K1=y CONFIG_PCIE_FU740=y CONFIG_CXL_BUS=y CONFIG_CXL_PCI=m @@ -2466,6 +2467,7 @@ CONFIG_PHY_STARFIVE_JH7110_DPHY_RX=m CONFIG_PHY_STARFIVE_JH7110_PCIE=m CONFIG_PHY_STARFIVE_JH7110_USB=m CONFIG_PHY_DW_DPHY=y +CONFIG_PHY_SPACEMIT_K3_COMBO_PHY=y CONFIG_LRW_DDR_PMU=m CONFIG_LRW_PCIE_PMU=m CONFIG_DEV_DAX=m diff --git a/drivers/pci/controller/dwc/Kconfig b/drivers/pci/controller/dwc/Kconfig index b91da52bad3f..037fc3c37826 100644 --- a/drivers/pci/controller/dwc/Kconfig +++ b/drivers/pci/controller/dwc/Kconfig @@ -430,4 +430,14 @@ config PCIE_ULTRARISC requiring no further configuration on that platform. +config PCIE_SPACEMIT_K1 + tristate "SpacemiT K1/K3 PCIe controller (host mode)" + depends on ARCH_SPACEMIT || COMPILE_TEST + depends on HAS_IOMEM + select PCIE_DW_HOST + default ARCH_SPACEMIT + help + Enables support for the DesignWare based PCIe controller in + the SpacemiT K1/K3 SoC operating in host mode. + endmenu diff --git a/drivers/pci/controller/dwc/Makefile b/drivers/pci/controller/dwc/Makefile index 4aecbe95b096..ac0d393dc833 100644 --- a/drivers/pci/controller/dwc/Makefile +++ b/drivers/pci/controller/dwc/Makefile @@ -27,6 +27,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_ULTRARISC) += pcie-ultrarisc.o +obj-$(CONFIG_PCIE_SPACEMIT_K1) += pcie-spacemit-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-spacemit-k1.c b/drivers/pci/controller/dwc/pcie-spacemit-k1.c new file mode 100644 index 000000000000..77a6f0800e00 --- /dev/null +++ b/drivers/pci/controller/dwc/pcie-spacemit-k1.c @@ -0,0 +1,564 @@ +// 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 "pcie-designware.h" +#include "../../pci.h" + +/* DWC GEN3 EQ / coherency registers (not exported by this kernel's designware.h) */ +#ifndef GEN3_EQ_CONTROL_OFF +#define GEN3_EQ_CONTROL_OFF 0x8A8 +#define GEN3_EQ_CONTROL_OFF_PHASE23_EXIT_MODE BIT(4) +#define GEN3_EQ_CONTROL_OFF_PSET_REQ_VEC GENMASK(23, 8) +#endif +#ifndef COHERENCY_CONTROL_3_OFF +#define COHERENCY_CONTROL_3_OFF 0x8E8 +#endif + +#define PCI_VENDOR_ID_SPACEMIT 0x201f +#define PCI_DEVICE_ID_SPACEMIT_K1 0x0001 +#define PCI_DEVICE_ID_SPACEMIT_K3 0x0002 + +/* Offsets and field definitions for 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_STATUS 0x0010 + +#define INTR_ENABLE 0x0014 +#define MSI_CTRL_INT BIT(11) +#define RDLH_LINK_UP_INT BIT(20) + +#define K3_PHY_AHB_IRQSTATUS_INTX 0x0008 + +#define K3_ADDR_INTR_STATUS1 0x0018 + +#define K3_CACHE_MSTR_AWCACHE_MODE GENMASK(14, 11) +#define K3_CACHE_MSTR_AWCACHE_BEHAVIOR 0xf + +#define K3_MAX_PHY_NUMBER 6 + +/* Some controls require APMU regmap access */ +#define SYSCON_APMU "spacemit,apmu" + +/* Offsets and field definitions for APMU 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) /* 1: assert PERST# */ +#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) +#define PCIE_PERSTN_OE BIT(24) +#define PCIE_PERSTN_OUT BIT(25) +#define PCIE_IGNORE_PERSTN BIT(31) + +struct k1_pcie { + struct dw_pcie pci; + struct phy **phy; + int phy_count; + void __iomem *link; + struct regmap *pmu; /* Errors ignored; MMIO-backed regmap */ + u32 pmu_off; +}; + +struct k1_pcie_device_data { + const struct dw_pcie_host_ops *host_ops; + const struct dw_pcie_ops *ops; + int (*parse_port)(struct k1_pcie *k1); +}; + +#define to_k1_pcie(dw_pcie) \ + platform_get_drvdata(to_platform_device((dw_pcie)->dev)) + +static void k1_pcie_toggle_soft_reset(struct k1_pcie *k1) +{ + u32 offset; + u32 val; + + /* + * Write, then read back to guarantee it has reached the device + * before we start the delay. + */ + offset = k1->pmu_off + PCIE_CONTROL_LOGIC; + regmap_set_bits(k1->pmu, offset, PCIE_SOFT_RESET); + regmap_read(k1->pmu, offset, &val); + + mdelay(2); + + regmap_clear_bits(k1->pmu, offset, PCIE_SOFT_RESET); +} + +/* Enable app clocks, deassert resets */ +static int k1_pcie_enable_resources(struct k1_pcie *k1) +{ + struct dw_pcie *pci = &k1->pci; + int ret; + + ret = clk_bulk_prepare_enable(ARRAY_SIZE(pci->app_clks), pci->app_clks); + if (ret) + return ret; + + ret = reset_control_bulk_deassert(ARRAY_SIZE(pci->app_rsts), + pci->app_rsts); + if (ret) + goto err_disable_clks; + + return 0; + +err_disable_clks: + clk_bulk_disable_unprepare(ARRAY_SIZE(pci->app_clks), pci->app_clks); + + return ret; +} + +/* Assert resets, disable app clocks */ +static void k1_pcie_disable_resources(struct k1_pcie *k1) +{ + struct dw_pcie *pci = &k1->pci; + + reset_control_bulk_assert(ARRAY_SIZE(pci->app_rsts), pci->app_rsts); + clk_bulk_disable_unprepare(ARRAY_SIZE(pci->app_clks), pci->app_clks); +} + +/* FIXME: Disable ASPM L1 to avoid errors reported on some NVMe drives */ +static void k1_pcie_disable_aspm_l1(struct k1_pcie *k1) +{ + struct dw_pcie *pci = &k1->pci; + u8 offset; + u32 val; + + offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP); + offset += PCI_EXP_LNKCAP; + + dw_pcie_dbi_ro_wr_en(pci); + val = dw_pcie_readl_dbi(pci, offset); + val &= ~PCI_EXP_LNKCAP_ASPM_L1; + dw_pcie_writel_dbi(pci, offset, val); + dw_pcie_dbi_ro_wr_dis(pci); +} + +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 reset_ctrl; + u32 val; + int ret; + + k1_pcie_toggle_soft_reset(k1); + + ret = k1_pcie_enable_resources(k1); + if (ret) + return ret; + + /* Set the PCI vendor and device ID */ + dw_pcie_dbi_ro_wr_en(pci); + dw_pcie_writew_dbi(pci, PCI_VENDOR_ID, PCI_VENDOR_ID_SPACEMIT); + dw_pcie_writew_dbi(pci, PCI_DEVICE_ID, PCI_DEVICE_ID_SPACEMIT_K1); + dw_pcie_dbi_ro_wr_dis(pci); + + /* + * Start by asserting fundamental reset (drive PERST# low). The + * PCI CEM spec says that PERST# should be deasserted at least + * 100ms after the power becomes stable, so we'll insert that + * delay first. Write, then read it back to guarantee the write + * reaches the device before we start the delay. + */ + reset_ctrl = k1->pmu_off + PCIE_CLK_RESET_CONTROL; + regmap_set_bits(k1->pmu, reset_ctrl, PCIE_RC_PERST); + regmap_read(k1->pmu, reset_ctrl, &val); + mdelay(PCIE_T_PVPERL_MS); + + /* + * Put the controller in root complex mode, and indicate that + * Vaux (3.3v) is present. + */ + regmap_set_bits(k1->pmu, reset_ctrl, DEVICE_TYPE_RC | PCIE_AUX_PWR_DET); + + ret = phy_init(k1->phy[0]); + if (ret) { + k1_pcie_disable_resources(k1); + + return ret; + } + + /* Deassert fundamental reset (drive PERST# high) */ + regmap_clear_bits(k1->pmu, reset_ctrl, PCIE_RC_PERST); + + /* Finally, as a workaround, disable ASPM L1 */ + k1_pcie_disable_aspm_l1(k1); + + return 0; +} + +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); + int i; + + /* Assert fundamental reset (drive PERST# low) */ + regmap_set_bits(k1->pmu, k1->pmu_off + PCIE_CLK_RESET_CONTROL, + PCIE_RC_PERST); + + for (i = 0; i < k1->phy_count; i++) + phy_exit(k1->phy[i]); + + k1_pcie_disable_resources(k1); +} + +static const struct dw_pcie_host_ops k1_pcie_host_ops = { + .host_init = k1_pcie_init, + .host_deinit = k1_pcie_deinit, +}; + +static int k1_pcie_link_up(struct dw_pcie *pci) +{ + struct k1_pcie *k1 = to_k1_pcie(pci); + u32 val; + + val = readl_relaxed(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); + u32 val; + + /* Stop holding the PHY in reset, and enable link training */ + regmap_update_bits(k1->pmu, k1->pmu_off + PCIE_CLK_RESET_CONTROL, + APP_HOLD_PHY_RST | LTSSM_EN, LTSSM_EN); + + /* Enable the MSI interrupt */ + writel_relaxed(MSI_CTRL_INT, k1->link + INTR_ENABLE); + + /* Top-level interrupt enable */ + val = readl_relaxed(k1->link + K1_PHY_AHB_IRQ_EN); + val |= PCIE_INTERRUPT_EN; + writel_relaxed(val, k1->link + K1_PHY_AHB_IRQ_EN); + + return 0; +} + +static void k1_pcie_stop_link(struct dw_pcie *pci) +{ + struct k1_pcie *k1 = to_k1_pcie(pci); + u32 val; + + /* Disable interrupts */ + val = readl_relaxed(k1->link + K1_PHY_AHB_IRQ_EN); + val &= ~PCIE_INTERRUPT_EN; + writel_relaxed(val, k1->link + K1_PHY_AHB_IRQ_EN); + + writel_relaxed(0, k1->link + INTR_ENABLE); + + /* Disable the link and hold the PHY in reset */ + regmap_update_bits(k1->pmu, k1->pmu_off + PCIE_CLK_RESET_CONTROL, + APP_HOLD_PHY_RST | LTSSM_EN, APP_HOLD_PHY_RST); +} + +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 k3_pcie_enable_phy(struct k1_pcie *pcie) +{ + int i, ret; + + for (i = 0; i < pcie->phy_count; i++) { + ret = phy_init(pcie->phy[i]); + if (ret) + goto err_phy; + } + + return 0; + +err_phy: + while (--i >= 0) + phy_exit(pcie->phy[i]); + + return ret; +} + +static int k3_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 reset_ctrl = k1->pmu_off + PCIE_CLK_RESET_CONTROL; + u32 val; + int ret; + + regmap_clear_bits(k1->pmu, reset_ctrl, LTSSM_EN); + + k1_pcie_toggle_soft_reset(k1); + + ret = k1_pcie_enable_resources(k1); + if (ret) + return ret; + + regmap_set_bits(k1->pmu, reset_ctrl, PCIE_AUX_PWR_DET); + regmap_clear_bits(k1->pmu, reset_ctrl, APP_HOLD_PHY_RST); + + ret = k3_pcie_enable_phy(k1); + if (ret) { + k1_pcie_disable_resources(k1); + return ret; + } + + /* K3: Set IGNORE_PERSTN and drive PERSTN_OE high (assert reset) */ + regmap_set_bits(k1->pmu, k1->pmu_off + PCIE_CONTROL_LOGIC, + PCIE_IGNORE_PERSTN | PCIE_PERSTN_OE | PCIE_PERSTN_OUT); + usleep_range(1000, 2000); + regmap_clear_bits(k1->pmu, k1->pmu_off + PCIE_CONTROL_LOGIC, PCIE_PERSTN_OUT); + + msleep(PCIE_T_PVPERL_MS); + + /* + * Put the controller in root complex mode, and indicate that + * Vaux (3.3v) is present. + */ + regmap_set_bits(k1->pmu, k1->pmu_off + PCIE_CONTROL_LOGIC, + PCIE_PERSTN_OUT | PCIE_PERSTN_OE); + + val = dw_pcie_readl_dbi(pci, GEN3_EQ_CONTROL_OFF); + val = u32_replace_bits(val, GEN3_EQ_CONTROL_OFF_PHASE23_EXIT_MODE, + GEN3_EQ_CONTROL_OFF_PSET_REQ_VEC); + dw_pcie_writel_dbi(pci, GEN3_EQ_CONTROL_OFF, val); + + dw_pcie_dbi_ro_wr_en(pci); + dw_pcie_writew_dbi(pci, PCI_VENDOR_ID, PCI_VENDOR_ID_SPACEMIT); + dw_pcie_writew_dbi(pci, PCI_DEVICE_ID, PCI_DEVICE_ID_SPACEMIT_K3); + dw_pcie_dbi_ro_wr_dis(pci); + + /* Finally, as a workaround, disable ASPM L1 */ + k1_pcie_disable_aspm_l1(k1); + + return 0; +} + +static int k3_pcie_msi_host_init(struct dw_pcie_rp *pp) +{ + struct dw_pcie *pci = to_dw_pcie_from_pp(pp); + u32 val; + + dw_pcie_dbi_ro_wr_en(pci); + + val = dw_pcie_readl_dbi(pci, COHERENCY_CONTROL_3_OFF); + val |= u32_replace_bits(val, K3_CACHE_MSTR_AWCACHE_BEHAVIOR, + K3_CACHE_MSTR_AWCACHE_MODE); + dw_pcie_writel_dbi(pci, COHERENCY_CONTROL_3_OFF, val); + + dw_pcie_dbi_ro_wr_dis(pci); + + return 0; +} + +static const struct dw_pcie_host_ops k3_pcie_host_ops = { + .host_init = k3_pcie_init, + .host_deinit = k1_pcie_deinit, + .msi_host_init = k3_pcie_msi_host_init, +}; + +static const struct dw_pcie_ops k3_pcie_ops = { + .link_up = k1_pcie_link_up, + .start_link = k1_pcie_start_link, + .stop_link = k1_pcie_stop_link, +}; + +static void k3_pcie_clear_irq_status(struct k1_pcie *k1, + u32 *status0, u32 *status1, u32 *status2) +{ + *status0 = readl_relaxed(k1->link + K3_PHY_AHB_IRQSTATUS_INTX); + *status1 = readl_relaxed(k1->link + INTR_STATUS); + *status2 = readl_relaxed(k1->link + K3_ADDR_INTR_STATUS1); + + writel_relaxed(*status0, k1->link + K3_PHY_AHB_IRQSTATUS_INTX); + writel_relaxed(*status1, k1->link + INTR_STATUS); + writel_relaxed(*status2, k1->link + K3_ADDR_INTR_STATUS1); +} + +static int k3_pcie_parse_port(struct k1_pcie *k1) +{ + struct device *dev = k1->pci.dev; + u32 status0, status1, status2; + int i; + + k1->phy = devm_kmalloc_array(dev, K3_MAX_PHY_NUMBER, sizeof(*k1->phy), + GFP_KERNEL); + if (!k1->phy) + return -ENOMEM; + + for (i = 0; i < K3_MAX_PHY_NUMBER; i++) { + k1->phy[i] = devm_of_phy_get_by_index(dev, dev->of_node, i); + if (IS_ERR(k1->phy[i])) { + if (PTR_ERR(k1->phy[i]) == -ENODEV) + break; + + return PTR_ERR(k1->phy[i]); + } + } + + k1->phy_count = i; + if (k1->phy_count == 0) + return -EINVAL; + + k3_pcie_clear_irq_status(k1, &status0, &status1, &status2); + + return 0; +} + +static int k1_pcie_parse_port(struct k1_pcie *k1) +{ + struct device *dev = k1->pci.dev; + struct device_node *root_port; + struct phy *phy; + + /* We assume only one root port */ + root_port = of_get_next_available_child(dev_of_node(dev), NULL); + if (!root_port) + return -EINVAL; + + phy = devm_of_phy_get(dev, root_port, NULL); + + of_node_put(root_port); + + if (IS_ERR(phy)) + return PTR_ERR(phy); + + k1->phy = devm_kmalloc_array(dev, 1, sizeof(*k1->phy), GFP_KERNEL); + if (!k1->phy) + return -ENOMEM; + + k1->phy[0] = phy; + k1->phy_count = 1; + + return 0; +} + +static int k1_pcie_probe(struct platform_device *pdev) +{ + const struct k1_pcie_device_data *data; + struct device *dev = &pdev->dev; + struct k1_pcie *k1; + int ret; + + data = device_get_match_data(dev); + if (!data) + return -ENODEV; + + k1 = devm_kzalloc(dev, sizeof(*k1), GFP_KERNEL); + if (!k1) + return -ENOMEM; + + k1->pmu = syscon_regmap_lookup_by_phandle_args(dev_of_node(dev), + SYSCON_APMU, 1, + &k1->pmu_off); + if (IS_ERR(k1->pmu)) + return dev_err_probe(dev, PTR_ERR(k1->pmu), + "failed to lookup PMU registers\n"); + + k1->link = devm_platform_ioremap_resource_byname(pdev, "link"); + if (IS_ERR(k1->link)) + return dev_err_probe(dev, PTR_ERR(k1->link), + "failed to map \"link\" registers\n"); + + k1->pci.dev = dev; + k1->pci.ops = data->ops; + k1->pci.pp.num_vectors = MAX_MSI_IRQS; + dw_pcie_cap_set(&k1->pci, REQ_RES); + + k1->pci.pp.ops = data->host_ops; + + /* Hold the PHY in reset until we start the link */ + regmap_set_bits(k1->pmu, k1->pmu_off + PCIE_CLK_RESET_CONTROL, + APP_HOLD_PHY_RST); + + ret = devm_regulator_get_enable(dev, "vpcie3v3"); + if (ret) + return dev_err_probe(dev, ret, + "failed to get \"vpcie3v3\" supply\n"); + + pm_runtime_set_active(dev); + pm_runtime_no_callbacks(dev); + devm_pm_runtime_enable(dev); + + platform_set_drvdata(pdev, k1); + + ret = data->parse_port(k1); + if (ret) + return dev_err_probe(dev, ret, "failed to parse root port\n"); + + ret = dw_pcie_host_init(&k1->pci.pp); + if (ret) + return dev_err_probe(dev, ret, "failed to initialize host\n"); + + return 0; +} + +static int k1_pcie_remove(struct platform_device *pdev) +{ + struct k1_pcie *k1 = platform_get_drvdata(pdev); + + dw_pcie_host_deinit(&k1->pci.pp); + + return 0; +} + +static const struct k1_pcie_device_data k1_pcie_device_data = { + .host_ops = &k1_pcie_host_ops, + .ops = &k1_pcie_ops, + .parse_port = k1_pcie_parse_port, +}; + +static const struct k1_pcie_device_data k3_pcie_device_data = { + .host_ops = &k3_pcie_host_ops, + .ops = &k3_pcie_ops, + .parse_port = k3_pcie_parse_port, +}; + +static const struct of_device_id k1_pcie_of_match_table[] = { + { .compatible = "spacemit,k1-pcie", .data = &k1_pcie_device_data}, + { .compatible = "spacemit,k3-pcie", .data = &k3_pcie_device_data}, + { } +}; + +static struct platform_driver k1_pcie_driver = { + .probe = k1_pcie_probe, + .remove = k1_pcie_remove, + .driver = { + .name = "spacemit-k1-pcie", + .of_match_table = k1_pcie_of_match_table, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, +}; +module_platform_driver(k1_pcie_driver); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("SpacemiT K1 PCIe host driver"); diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index 50f729360df1..d3bf727433c4 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig @@ -91,6 +91,7 @@ source "drivers/phy/renesas/Kconfig" source "drivers/phy/rockchip/Kconfig" source "drivers/phy/samsung/Kconfig" source "drivers/phy/socionext/Kconfig" +source "drivers/phy/spacemit/Kconfig" source "drivers/phy/st/Kconfig" source "drivers/phy/starfive/Kconfig" source "drivers/phy/sunplus/Kconfig" diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile index df5a0b92f828..584ed4363880 100644 --- a/drivers/phy/Makefile +++ b/drivers/phy/Makefile @@ -30,6 +30,7 @@ obj-y += allwinner/ \ rockchip/ \ samsung/ \ socionext/ \ + spacemit/ \ st/ \ starfive/ \ sunplus/ \ diff --git a/drivers/phy/spacemit/Kconfig b/drivers/phy/spacemit/Kconfig new file mode 100644 index 000000000000..8e1ed8e07532 --- /dev/null +++ b/drivers/phy/spacemit/Kconfig @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Phy drivers for SpacemiT platforms +# +config PHY_SPACEMIT_K3_COMMON_OPS + tristate + select MFD_SYSCON + select GENERIC_PHY + +config PHY_SPACEMIT_K3_COMBO_PHY + tristate "SpacemiT K3 USB3/PCIe PHY support" + depends on (ARCH_SPACEMIT || COMPILE_TEST) && OF + depends on COMMON_CLK + select PHY_SPACEMIT_K3_COMMON_OPS + help + Enable this to support K3 USB3/PCIe combo PHY driver. This + driver takes care of enabling and clock setup and will be used + by K3 dwc3 driver. + If unsure, say N. diff --git a/drivers/phy/spacemit/Makefile b/drivers/phy/spacemit/Makefile new file mode 100644 index 000000000000..5e82dd332ed4 --- /dev/null +++ b/drivers/phy/spacemit/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_PHY_SPACEMIT_K3_COMBO_PHY) += phy-k3-combphy.o +obj-$(CONFIG_PHY_SPACEMIT_K3_COMMON_OPS) += phy-k3-common.o diff --git a/drivers/phy/spacemit/phy-k3-combphy.c b/drivers/phy/spacemit/phy-k3-combphy.c new file mode 100644 index 000000000000..f89cabdf02a3 --- /dev/null +++ b/drivers/phy/spacemit/phy-k3-combphy.c @@ -0,0 +1,259 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * phy-k3-usb3.c - SpacemiT K3 Type-C Orientation Switch Driver + * + * Copyright (c) 2025 SpacemiT Technology Co. Ltd + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "phy-k3-common.h" + +/* + * 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_MASK GENMASK(4, 0) + +#define COMBPHY_MAX_SUBPHYS 6 + +struct k3_comb_phy { + struct device *dev; + struct k3_lane_group groups[COMBPHY_MAX_SUBPHYS]; + void __iomem *base; + struct regmap *apb_spare; +}; + +static const struct k3_phy_lane_group_data k3_combphy_lane_group0 = { + .lanes = 2, + .config = 0x1f, + .mask = 0x1f, + .offsets = { + 0x0, 0x400 + }, +}; + +static const struct k3_phy_lane_group_data k3_combphy_lane_group1 = { + .lanes = 2, + .config = 0x1f, + .mask = 0x1f, + .offsets = { + 0x100000, 0x100400 + }, +}; + +static const struct k3_phy_lane_group_data k3_combphy_lane_group2 = { + .lanes = 1, + .config = 0x14, + .mask = 0x14, + .offsets = { + 0x200000 + }, +}; + +static const struct k3_phy_lane_group_data k3_combphy_lane_group3 = { + .lanes = 1, + .config = 0x12, + .mask = 0x12, + .offsets = { + 0x300000 + }, +}; + +static const struct k3_phy_lane_group_data k3_combphy_lane_group4 = { + .lanes = 1, + .config = 0x11, + .mask = 0x11, + .offsets = { + 0x400000 + }, +}; + +static const struct k3_phy_lane_group_data k3_combphy_lane_group5 = { + .lanes = 1, + .config = 0xff, + .mask = 0xff, + .offsets = { + 0x500000 + }, +}; + +static const struct k3_phy_lane_group_data *k3_combphy_lane_datas[] = { + &k3_combphy_lane_group0, + &k3_combphy_lane_group1, + &k3_combphy_lane_group2, + &k3_combphy_lane_group3, + &k3_combphy_lane_group4, + &k3_combphy_lane_group5, +}; + +static int k3_comb_phy_init_lanes(struct k3_comb_phy *phy, unsigned int config) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(k3_combphy_lane_datas); i++) { + const struct k3_phy_lane_group_data *data = k3_combphy_lane_datas[i]; + struct k3_lane_group *lg = &phy->groups[i]; + const struct phy_ops *ops; + bool is_usb; + + is_usb = (data->mask & config) == data->config; + if (is_usb) + ops = &k3_usb3_phy_ops; + else + ops = &k3_pcie_phy_ops; + + lg->phy = devm_phy_create(phy->dev, NULL, ops); + if (IS_ERR(lg->phy)) + return PTR_ERR(lg->phy); + + lg->is_pcie = !is_usb; + lg->data = data; + lg->base = phy->base; + phy_set_drvdata(lg->phy, lg); + } + + return 0; +} + +static int k3_comb_phy_update_config(struct regmap *apmu, unsigned int config) +{ + if (config & ~PU_MATRIX_CONF_MASK) + return -EINVAL; + + return regmap_update_bits(apmu, PMUA_PCIE_SUBSYS_MGMT, PU_MATRIX_CONF_MASK, config); +} + +static struct phy *k3_comb_phy_xlate(struct device *dev, struct of_phandle_args *args) +{ + struct k3_comb_phy *phy = dev_get_drvdata(dev); + struct k3_lane_group *lg; + + if (args->args_count != 2) { + dev_err(dev, "Invalid number of arguments\n"); + return ERR_PTR(-EINVAL); + } + + if (args->args[0] >= ARRAY_SIZE(k3_combphy_lane_datas)) { + dev_err(dev, "Invalid PHY id\n"); + return ERR_PTR(-EINVAL); + } + + lg = &phy->groups[args->args[0]]; + + if ((lg->is_pcie && args->args[1] != PHY_TYPE_PCIE) || + (!lg->is_pcie && args->args[1] != PHY_TYPE_USB3)) { + dev_err(dev, "Invalid PHY mode\n"); + return ERR_PTR(-EINVAL); + } + + return lg->phy; +} + +static int k3_comb_phy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + struct phy_provider *provider; + struct k3_comb_phy *phy; + struct regmap *apmu; + u32 config = 0; + int ret; + + phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); + if (!phy) + return -ENOMEM; + + phy->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(phy->base)) + return PTR_ERR(phy->base); + + { + struct device_node *apbs_np; + + apbs_np = of_parse_phandle(node, "spacemit,apb-spare", 0); + if (!apbs_np) + return dev_err_probe(dev, -ENODEV, + "Failed to find APB SPARE syscon"); + phy->apb_spare = device_node_to_regmap(apbs_np); + of_node_put(apbs_np); + } + if (IS_ERR(phy->apb_spare)) + return dev_err_probe(dev, PTR_ERR(phy->apb_spare), + "Failed to fine APB SPARE syscon"); + + apmu = syscon_regmap_lookup_by_phandle_args(node, "spacemit,apmu", 1, &config); + if (IS_ERR(apmu)) + return dev_err_probe(dev, PTR_ERR(phy->apb_spare), + "Failed to fine APMU syscon"); + + ret = k3_comb_phy_update_config(apmu, config); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to set lane configuration"); + + phy->dev = dev; + platform_set_drvdata(pdev, phy); + + ret = k3_phy_calibrate(phy->apb_spare); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to calibrate phy"); + + ret = k3_comb_phy_init_lanes(phy, config); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to init lanes"); + + provider = devm_of_phy_provider_register(dev, k3_comb_phy_xlate); + if (IS_ERR(provider)) + return dev_err_probe(dev, PTR_ERR(provider), + "Failed to register provider\n"); + + return 0; +} + +static const struct of_device_id k3_comb_phy_of_match[] = { + { .compatible = "spacemit,k3-comb-phy" }, + { }, +}; +MODULE_DEVICE_TABLE(of, k3_comb_phy_of_match); + +static struct platform_driver k3_comb_phy_driver = { + .probe = k3_comb_phy_probe, + .driver = { + .name = "spacemit,k3-comb-phy", + .of_match_table = k3_comb_phy_of_match, + }, +}; +module_platform_driver(k3_comb_phy_driver); + +MODULE_DESCRIPTION("SpacemiT K3 USB3/PCIe comb PHY driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/phy/spacemit/phy-k3-common.c b/drivers/phy/spacemit/phy-k3-common.c new file mode 100644 index 000000000000..77c4b4073b96 --- /dev/null +++ b/drivers/phy/spacemit/phy-k3-common.c @@ -0,0 +1,398 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "phy-k3-common.h" + +/* PHY Registers */ +#define PHY_VERSION 0x0 + +#define PHY_RESET_CFG 0x04 + +#define PHY_RESET_RXBUF_RST BIT(0) +#define PHY_RESET_SOFT_RST_PCS BIT(1) +#define PHY_RESET_SOFT_RST_AHB BIT(2) +#define PHY_RESET_EN_SD_AFTER_LOCK BIT(6) + +#define PHY_CLK_CFG 0x08 + +#define PHY_CLK_PLL_READY BIT(0) +#define PHY_CLK_TXCLK_INV BIT(2) +#define PHY_CLK_RXCLK_EN BIT(3) +#define PHY_CLK_TXCLK_EN BIT(4) +#define PHY_CLK_PCLK_EN BIT(5) +#define PHY_CLK_PIPE_PCLK_EN BIT(6) +#define PHY_CLK_REFCLK_FREQ GENMASK(10, 7) +#define PHY_CLK_REFCLK_24M 2 +#define PHY_CLK_SW_INIT_DONE BIT(11) +#define PHY_CLK_PU_SSC_OUT BIT(23) + +#define PHY_MODE_CFG 0x0C + +#define PHY_MODE_PCIE_INT_EN BIT(0) +#define PHY_MODE_LFPS_TPERIOD GENMASK(9, 8) +#define PHY_MODE_LFPS_TPERIOD_USB 3 + +#define PHY_PU_SEL 0x40 + +#define PHY_PU_CFG_STATUS BIT(9) +#define PHY_PU_OVRD_STATUS BIT(10) + +#define PHY_PU_CK_REG 0x54 + +#define PHY_PU_REFCLK_100 BIT(25) + +#define PHY_PLL_REG1 0x58 + +#define PHY_PLL_FREF_SEL GENMASK(15, 13) +#define PHY_PLL_FREF_24M 0x1 +#define PHY_PLL_SSC_DEP_SEL GENMASK(27, 24) +#define PHY_PLL_SSC_5000PPM 0xa +#define PHY_PLL_SSC_MODE GENMASK(29, 28) +#define PHY_PLL_SSC_MODE_CENTER_SPREAD 0 +#define PHY_PLL_SSC_MODE_UP_SPREAD 1 +#define PHY_PLL_SSC_MODE_DOWN_SPREAD 2 +#define PHY_PLL_SSC_MODE_DOWN_SPREAD1 3 + +#define PHY_PLL_REG2 0x5c + +#define PHY_PLL_SEL_REF100 BIT(21) + +/* PHY RX Register Definitions */ +#define PHY_RX_REG_A 0x60 + +#define PHY_RX_REG0_RLOAD BIT(4) +#define PHY_RX_REG1_RTERM GENMASK(11, 8) +#define PHY_RX_REG1_RC_CALI GENMASK(15, 12) +#define PHY_RX_REG2_CSEL GENMASK(19, 16) +#define PHY_RX_REG2_FORCE_CSEL BIT(20) +#define PHY_RX_REG2_PSEL GENMASK(23, 21) +#define PHY_RX_REG3_I_LOAD GENMASK(26, 24) +#define PHY_RX_REG3_SEL_CBOOST_CODE BIT(27) +#define PHY_RX_REG3_ADJ_BIAS GENMASK(29, 28) +#define PHY_RX_REG3_RDEG1 GENMASK(31, 30) + +#define PHY_RX_REG_B 0x64 + +#define PHY_RX_REGB_MASK GENMASK(23, 0) + +#define PHY_RX_REG4_RDEG2 GENMASK(2, 1) +#define PHY_RX_REG4_ENVOS BIT(4) +#define PHY_RX_REG4_RTERM_SEL BIT(5) +#define PHY_RX_REG4_MANUAL_CFG BIT(7) +#define PHY_RX_REG5_RCELL_VCM GENMASK(11, 8) +#define PHY_RX_REG5_RCELL_BIAS GENMASK(15, 12) +#define PHY_RX_REG6_H1_REG GENMASK(19, 16) +#define PHY_RX_REG6_ADAPT_GAIN GENMASK(21, 20) +#define PHY_RX_REG6_BYPASS_ADPT BIT(22) + +#define PHY_ADPT_CFG0 0x140 +#define PHY_ADPT_AFE_RST_OVRD_EN BIT(1) +#define PHY_ADPT_AFE_RST_OVRD_VAL BIT(4) + +#define PHY_RXEQ_TIME 0xb4 +#define PHY_RXEQ_TIME_OVRD_POST_C_SOC BIT(21) +#define PHY_RXEQ_TIME_CFG_AMP_SOC GENMASK(23, 22) +#define PHY_RXEQ_TIME_AMP_SOC_650M 0 +#define PHY_RXEQ_TIME_AMP_SOC_800M 1 +#define PHY_RXEQ_TIME_AMP_SOC_870M 2 +#define PHY_RXEQ_TIME_AMP_SOC_900M 3 +#define PHY_RXEQ_TIME_OVRD_AMP_SOC BIT(24) + +#define PCIE_PU_ADDR_CLK_CFG 0x0008 +#define PHY_CLK_PLL_READY BIT(0) +#define PCIE_INITAL_TIMER GENMASK(6, 3) +#define CFG_INTERNAL_TIMER_ADJ GENMASK(10, 7) +#define CFG_SW_PHY_INIT_DONE BIT(11) + +/* Lane RX/TX configuration (per‑lane, at lane_base) */ +#define PCIE_RX_REG1 0x050 +#define PCIE_TX_REG1 0x064 + +#define PCIE_PLL_TIMEOUT 500000 +#define PCIE_POLL_DELAY 500 + +static int k3_usb3phy_init_single(struct k3_lane_group *lg, void __iomem *base) +{ + struct phy *phy = lg->phy; + u32 val, tmp; + int ret; + + val = readl(base + PHY_PU_SEL); + val = u32_replace_bits(val, 0, PHY_PU_CFG_STATUS); + val |= PHY_PU_OVRD_STATUS; + writel(val, base + PHY_PU_SEL); + + udelay(200); + + /* Do not wait CDR lock before sampling data */ + val = readl(base + PHY_RESET_CFG); + val = u32_replace_bits(val, 0, PHY_RESET_EN_SD_AFTER_LOCK); + writel(val, base + PHY_RESET_CFG); + + /* Power down 100MHz refclk buffer */ + val = readl(base + PHY_PU_CK_REG); + val = u32_replace_bits(val, 0, PHY_PU_REFCLK_100); + writel(val, base + PHY_PU_CK_REG); + + /* Program PLL REG1 configure the SSC */ + val = FIELD_PREP(PHY_PLL_SSC_MODE, PHY_PLL_SSC_MODE_DOWN_SPREAD1) | + FIELD_PREP(PHY_PLL_SSC_DEP_SEL, PHY_PLL_SSC_5000PPM) | + FIELD_PREP(PHY_PLL_FREF_SEL, PHY_PLL_FREF_24M); + writel(val, base + PHY_PLL_REG1); + + /* Un-select 100MHz PLL reference */ + val = readl(base + PHY_PLL_REG2); + val = u32_replace_bits(val, 0, PHY_PLL_SEL_REF100); + writel(val, base + PHY_PLL_REG2); + + /* USB LFPS period configuration */ + val = readl(base + PHY_MODE_CFG); + val = u32_replace_bits(val, PHY_MODE_LFPS_TPERIOD_USB, PHY_MODE_LFPS_TPERIOD); + writel(val, base + PHY_MODE_CFG); + + /* Force AFE adaptation reset */ + val = readl(base + PHY_ADPT_CFG0); + val |= PHY_ADPT_AFE_RST_OVRD_EN | PHY_ADPT_AFE_RST_OVRD_VAL; + writel(val, base + PHY_ADPT_CFG0); + + /* Override driver amplitude value to 900m */ + val = readl(base + PHY_RXEQ_TIME); + val |= PHY_RXEQ_TIME_OVRD_AMP_SOC; + val = u32_replace_bits(val, PHY_RXEQ_TIME_AMP_SOC_900M, PHY_RXEQ_TIME_CFG_AMP_SOC); + writel(val, base + PHY_RXEQ_TIME); + + /* Configure RX parameters */ + val = PHY_RX_REG0_RLOAD | + FIELD_PREP(PHY_RX_REG1_RTERM, 0x8) | + FIELD_PREP(PHY_RX_REG1_RC_CALI, 0x7) | + FIELD_PREP(PHY_RX_REG2_CSEL, 0x8) | + PHY_RX_REG2_FORCE_CSEL | + FIELD_PREP(PHY_RX_REG2_PSEL, 0x4) | + FIELD_PREP(PHY_RX_REG3_I_LOAD, 0x7) | + PHY_RX_REG3_SEL_CBOOST_CODE | + FIELD_PREP(PHY_RX_REG3_ADJ_BIAS, 0x1) | + FIELD_PREP(PHY_RX_REG3_RDEG1, 0x3); + writel(val, base + PHY_RX_REG_A); + + val = readl(base + PHY_RX_REG_B); + tmp = FIELD_PREP(PHY_RX_REG4_RDEG2, 0x2) | + PHY_RX_REG4_ENVOS | PHY_RX_REG4_RTERM_SEL | PHY_RX_REG4_MANUAL_CFG | + FIELD_PREP(PHY_RX_REG5_RCELL_VCM, 0x8) | + FIELD_PREP(PHY_RX_REG5_RCELL_BIAS, 0x8) | + FIELD_PREP(PHY_RX_REG6_H1_REG, 0x8) | + FIELD_PREP(PHY_RX_REG6_ADAPT_GAIN, 0x2); + val = u32_replace_bits(val, tmp, PHY_RX_REGB_MASK); + writel(val, base + PHY_RX_REG_B); + + /* + * Inform PHY that all PLL-related configuration is done. + * PLL will not start locking until PHY_CLK_SW_INIT_DONE is set. + */ + val = PHY_CLK_SW_INIT_DONE | PHY_CLK_PU_SSC_OUT | + FIELD_PREP(PHY_CLK_REFCLK_FREQ, PHY_CLK_REFCLK_24M) | + PHY_CLK_RXCLK_EN | PHY_CLK_TXCLK_EN | + PHY_CLK_PCLK_EN | PHY_CLK_PIPE_PCLK_EN; + writel(val, base + PHY_CLK_CFG); + + ret = readl_poll_timeout(base + PHY_CLK_CFG, val, + (val & PHY_CLK_PLL_READY), + PCIE_POLL_DELAY, PCIE_PLL_TIMEOUT); + if (ret) { + dev_err(&phy->dev, "PHY PLL polling timeout\n"); + return ret; + } + + return 0; +} + +static int k3_usb3phy_init(struct phy *phy) +{ + struct k3_lane_group *lg = phy_get_drvdata(phy); + int ret, i; + + for (i = 0; i < lg->data->lanes; i++) { + ret = k3_usb3phy_init_single(lg, lg->base + lg->data->offsets[i]); + if (ret < 0) + return ret; + } + + return 0; +} + +static int k3_usb3phy_set_speed(struct phy *phy, int speed) +{ + struct k3_lane_group *lg = phy_get_drvdata(phy); + void __iomem *base = lg->base + lg->data->offsets[0]; + u32 val; + + if (speed == USB_SPEED_HIGH) { + val = readl(base + PHY_PU_SEL); + val = u32_replace_bits(val, 0, PHY_PU_CFG_STATUS); + val |= PHY_PU_OVRD_STATUS; + writel(val, base + PHY_PU_SEL); + + udelay(200); + } + + return 0; +} + +const struct phy_ops k3_usb3_phy_ops = { + .init = k3_usb3phy_init, + .set_speed = k3_usb3phy_set_speed, + .owner = THIS_MODULE, +}; +EXPORT_SYMBOL_GPL(k3_usb3_phy_ops); + +static int k3_pcie_phy_init(struct phy *phy) +{ + struct k3_lane_group *lg = phy_get_drvdata(phy); + void __iomem *phy_base = lg->base + lg->data->offsets[0]; + u32 val; + int ret; + int i; + + val = readl(phy_base + PHY_PLL_REG1); + val = u32_replace_bits(val, 0x2, GENMASK(15, 12)); + writel(val, phy_base + PHY_PLL_REG1); + + val = readl(phy_base + PHY_PLL_REG2); + val = u32_replace_bits(val, 0, BIT(21)); + writel(val, phy_base + PHY_PLL_REG2); + + for (i = 0; i < lg->data->lanes; i++) { + void __iomem *lane_base = lg->base + lg->data->offsets[i]; + + val = readl(lane_base + PCIE_RX_REG1); + val = u32_replace_bits(val, 0, 0x3); + writel(val, phy_base + PCIE_RX_REG1); + } + + val = readl(phy_base + PHY_PLL_REG2); + val |= BIT(20); + writel(val, phy_base + PHY_PLL_REG2); + + writel(0x00006505, phy_base + PCIE_RX_REG1); + + /* pll_reg1 of lane0, disable SSC: pll_reg4[3:0] = 0 */ + val = readl(phy_base + PHY_PLL_REG1); + val = u32_replace_bits(val, 0, GENMASK(27, 24)); + writel(val, phy_base + PHY_PLL_REG1); + + for (i = 0; i < lg->data->lanes; i++) { + void __iomem *lane_base = lg->base + lg->data->offsets[i]; + + /* set cfg_tx_send_dummy_data to be 1'b1 for disable dash data */ + val = readl(lane_base + PHY_PU_SEL); + val = u32_replace_bits(val, 1, BIT(13)); + writel(val, lane_base + PHY_PU_SEL); + + /* disable en_sample_data_after_cdr_locked */ + val = readl(lane_base + PHY_RESET_CFG); + val = u32_replace_bits(val, 0, BIT(6)); + writel(val, lane_base + PHY_RESET_CFG); + + /* Dynamic Lock */ + val = readl(lane_base + PHY_MODE_CFG); + val = u32_replace_bits(val, 1, BIT(2)); + writel(val, lane_base + PHY_MODE_CFG); + + val = FIELD_PREP(GENMASK(7, 0), 0x10) | + FIELD_PREP(GENMASK(15, 8), 0x78) | + FIELD_PREP(GENMASK(23, 16), 0x98) | + FIELD_PREP(GENMASK(31, 24), 0xdf); + writel(val, lane_base + PHY_RX_REG_A); + + val = readl(lane_base + PHY_RX_REG_B); + val &= ~PHY_RX_REGB_MASK; + val |= FIELD_PREP(GENMASK(7, 0), 0xb4) | + FIELD_PREP(GENMASK(15, 8), 0x88) | + FIELD_PREP(GENMASK(23, 16), 0x28); + writel(val, lane_base + PHY_RX_REG_B); + + /* Set init done */ + val = readl(lane_base + PCIE_PU_ADDR_CLK_CFG); + val = u32_replace_bits(val, 1, CFG_SW_PHY_INIT_DONE); + writel(val, lane_base + PCIE_PU_ADDR_CLK_CFG); + } + + ret = readl_poll_timeout(phy_base + PCIE_PU_ADDR_CLK_CFG, val, + (val & PHY_CLK_PLL_READY), PCIE_POLL_DELAY, + PCIE_PLL_TIMEOUT); + if (ret) { + dev_err(&lg->phy->dev, "PHY PLL lock timeout\n"); + return ret; + } + + return 0; +} + +const struct phy_ops k3_pcie_phy_ops = { + .init = k3_pcie_phy_init, + .owner = THIS_MODULE, +}; +EXPORT_SYMBOL_GPL(k3_pcie_phy_ops); + +/* PHY rcal init requires APB_SPARE regmap access */ + +#define APB_SPARE_PU_CAL 0x178 +#define PU_CAL BIT(17) + +#define APB_SPARE_RCAL_HSIO 0x17c +#define APB_SPARE_PU_CAL_DONE BIT(8) +#define RCAL_OVRD_PTRIM GENMASK(23, 20) +#define RCAL_OVRD_NTRIM GENMASK(27, 24) +#define RCAL_OVRD_PTRIM_EN BIT(28) +#define RCAL_OVRD_NTRIM_EN BIT(29) +#define RCAL_OVRD_STABLE_VAL BIT(30) +#define RCAL_OVRD_STABLE_EN BIT(31) + +#define RCAL_OVRD_TRIM_EN (RCAL_OVRD_NTRIM_EN | RCAL_OVRD_PTRIM_EN) +#define RCAL_OVRD_TRIM_MASK (RCAL_OVRD_NTRIM | RCAL_OVRD_PTRIM) + +#define PU_CAL_TIMEOUT 2000000 + +static DEFINE_MUTEX(calibrate_lock); + +int k3_phy_calibrate(struct regmap *apb_spare) +{ + unsigned int val; + int ret; + + guard(mutex)(&calibrate_lock); + + regmap_read(apb_spare, APB_SPARE_RCAL_HSIO, &val); + if (val & APB_SPARE_PU_CAL_DONE) + return 0; + + regmap_update_bits(apb_spare, APB_SPARE_PU_CAL, PU_CAL, + PU_CAL); + + ret = regmap_read_poll_timeout(apb_spare, APB_SPARE_RCAL_HSIO, + val, (val & APB_SPARE_PU_CAL_DONE), PCIE_POLL_DELAY, + PU_CAL_TIMEOUT); + + if (ret) + regmap_update_bits(apb_spare, APB_SPARE_RCAL_HSIO, + RCAL_OVRD_TRIM_EN | RCAL_OVRD_STABLE_VAL | + RCAL_OVRD_TRIM_MASK | RCAL_OVRD_STABLE_EN, + RCAL_OVRD_TRIM_EN | RCAL_OVRD_STABLE_VAL | + FIELD_PREP(RCAL_OVRD_NTRIM, 0x6) | + FIELD_PREP(RCAL_OVRD_PTRIM, 0xa) | + RCAL_OVRD_STABLE_EN); + + return ret; +} +EXPORT_SYMBOL_GPL(k3_phy_calibrate); + +MODULE_DESCRIPTION("SpacemiT K3 PHY common ops"); +MODULE_LICENSE("GPL"); diff --git a/drivers/phy/spacemit/phy-k3-common.h b/drivers/phy/spacemit/phy-k3-common.h new file mode 100644 index 000000000000..49009c3c313a --- /dev/null +++ b/drivers/phy/spacemit/phy-k3-common.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef _PHY_K3_COMMON_H +#define _PHY_K3_COMMON_H + +#include + +struct k3_phy_lane_group_data { + u32 lanes; + u8 config; + u8 mask; + u32 offsets[] __counted_by(lanes); +}; + +struct k3_lane_group { + const struct k3_phy_lane_group_data *data; + void __iomem *base; + struct phy *phy; + bool is_pcie; +}; + +extern const struct phy_ops k3_pcie_phy_ops; +extern const struct phy_ops k3_usb3_phy_ops; + +int k3_phy_calibrate(struct regmap *apb_spare); + +#endif