[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <valmrbddij2dn4fjxefr46zud2u6eco2isyaa62sd66d27foyl@4hrhafqftgb5>
Date: Thu, 14 Aug 2025 07:42:48 +0800
From: Inochi Amaoto <inochiama@...il.com>
To: Alex Elder <elder@...cstar.com>, lpieralisi@...nel.org,
kwilczynski@...nel.org, mani@...nel.org, robh@...nel.org, bhelgaas@...gle.com,
krzk+dt@...nel.org, conor+dt@...nel.org, vkoul@...nel.org, kishon@...nel.org
Cc: dlan@...too.org, paul.walmsley@...ive.com, palmer@...belt.com,
aou@...s.berkeley.edu, alex@...ti.fr, p.zabel@...gutronix.de, tglx@...utronix.de,
johan+linaro@...nel.org, thippeswamy.havalige@....com, namcao@...utronix.de,
mayank.rana@....qualcomm.com, shradha.t@...sung.com, inochiama@...il.com,
quic_schintav@...cinc.com, fan.ni@...sung.com, devicetree@...r.kernel.org,
linux-phy@...ts.infradead.org, linux-pci@...r.kernel.org, spacemit@...ts.linux.dev,
linux-riscv@...ts.infradead.org, linux-kernel@...r.kernel.org,
Junzhong Pan <panjunzhong@...ux.spacemit.com>
Subject: Re: [PATCH 4/6] phy: spacemit: introduce PCIe/combo PHY
On Wed, Aug 13, 2025 at 01:46:58PM -0500, Alex Elder wrote:
> Introduce a driver that supports three PHYs found on the SpacemiT
> K1 SoC. The first PHY is a combo PHY that can be configured for
> use for either USB 3 or PCIe. The other two PHYs support PCIe
> only.
>
> All three PHYs must be programmed with an 8 bit receiver termination
> value, which must be determined dynamically; only the combo PHY is
> able to determine this value. The combo PHY performs a special
> calibration step at probe time to discover this, and that value is
> used to program each PHY that operates in PCIe mode. The combo
> PHY must therefore be probed--first--if either of the PCIe-only
> PHYs will be used.
>
> During normal operation, the USB or PCIe driver using the PHY must
> ensure clocks and resets are set up properly. However clocks are
> enabled and resets are de-asserted temporarily by this driver to
> perform the calibration step on the combo PHY.
>
> Tested-by: Junzhong Pan <panjunzhong@...ux.spacemit.com>
> Signed-off-by: Alex Elder <elder@...cstar.com>
> ---
> drivers/phy/Kconfig | 11 +
> drivers/phy/Makefile | 1 +
> drivers/phy/phy-spacemit-k1-pcie.c | 639 +++++++++++++++++++++++++++++
> 3 files changed, 651 insertions(+)
> create mode 100644 drivers/phy/phy-spacemit-k1-pcie.c
>
> diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig
> index 58c911e1b2d20..0fa343203f289 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 c670a8dac4680..20f0078e543c7 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 0000000000000..32dce53170fbb
> --- /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 <linux/bitfield.h>
> +#include <linux/clk.h>
> +#include <linux/iopoll.h>
> +#include <linux/kernel.h>
> +#include <linux/mfd/syscon.h>
> +#include <linux/module.h>
> +#include <linux/phy/phy.h>
> +#include <linux/platform_device.h>
> +#include <linux/regmap.h>
> +#include <linux/reset.h>
> +
> +#include <dt-bindings/phy/phy.h>
> +
> +/*
> + * 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);
> +}
> +
Can we use standard clk_ops and clk_mux to normalize this process?
Regards,
Inochi
Powered by blists - more mailing lists