From: "Ze Huang" <huang.ze@linux.dev>
To: "Inochi Amaoto" <inochiama@gmail.com>,
"Vinod Koul" <vkoul@kernel.org>,
"Neil Armstrong" <neil.armstrong@linaro.org>,
"Rob Herring" <robh@kernel.org>,
"Krzysztof Kozlowski" <krzk+dt@kernel.org>,
"Conor Dooley" <conor+dt@kernel.org>,
"Yixun Lan" <dlan@kernel.org>, "Kees Cook" <kees@kernel.org>,
"Gustavo A. R. Silva" <gustavoars@kernel.org>,
"Paul Walmsley" <pjw@kernel.org>,
"Palmer Dabbelt" <palmer@dabbelt.com>,
"Albert Ou" <aou@eecs.berkeley.edu>,
"Alexandre Ghiti" <alex@ghiti.fr>,
"Ze Huang" <huang.ze@linux.dev>,
"Alex Elder" <elder@riscstar.com>
Cc: <linux-phy@lists.infradead.org>, <devicetree@vger.kernel.org>,
<linux-riscv@lists.infradead.org>, <spacemit@lists.linux.dev>,
<linux-kernel@vger.kernel.org>, <linux-hardening@vger.kernel.org>,
"Yixun Lan" <dlan@gentoo.org>,
"Longbin Li" <looong.bin@gmail.com>
Subject: Re: [PATCH 2/2] phy: spacemit: Add USB3/PCIe comb PHY driver for Spacemit K3
Date: Thu, 30 Apr 2026 15:39:51 +0800 [thread overview]
Message-ID: <DI6BHTW4FFN1.2YLR7O1P8F0Y3@linux.dev> (raw)
In-Reply-To: <20260430022843.1090138-3-inochiama@gmail.com>
On Thu Apr 30, 2026 at 10:28 AM CST, Inochi Amaoto wrote:
> The comb PHY on K3 requires to configure a syscon device for the
> right mux configuration. And it requires calibration before any
> usage.
>
> Add USB3/PCIe comb PHY driver for Spacemit K3.
>
> Signed-off-by: Inochi Amaoto <inochiama@gmail.com>
> ---
> drivers/phy/spacemit/Kconfig | 16 ++
> drivers/phy/spacemit/Makefile | 2 +
> drivers/phy/spacemit/phy-k3-combphy.c | 250 ++++++++++++++++
> drivers/phy/spacemit/phy-k3-common.c | 398 ++++++++++++++++++++++++++
> drivers/phy/spacemit/phy-k3-common.h | 27 ++
> 5 files changed, 693 insertions(+)
> create mode 100644 drivers/phy/spacemit/phy-k3-combphy.c
> create mode 100644 drivers/phy/spacemit/phy-k3-common.c
> create mode 100644 drivers/phy/spacemit/phy-k3-common.h
>
> diff --git a/drivers/phy/spacemit/Kconfig b/drivers/phy/spacemit/Kconfig
> index 50b0005acf66..5fdf18fce499 100644
> --- a/drivers/phy/spacemit/Kconfig
> +++ b/drivers/phy/spacemit/Kconfig
> @@ -23,3 +23,19 @@ config PHY_SPACEMIT_K1_USB2
> help
> Enable this to support K1 USB 2.0 PHY driver. This driver takes care of
> enabling and clock setup and will be used by K1 udc/ehci/otg/xhci driver.
> +
> +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
> index a821a21d6142..41be7b0388da 100644
> --- a/drivers/phy/spacemit/Makefile
> +++ b/drivers/phy/spacemit/Makefile
> @@ -1,3 +1,5 @@
> # SPDX-License-Identifier: GPL-2.0-only
> obj-$(CONFIG_PHY_SPACEMIT_K1_PCIE) += phy-k1-pcie.o
> obj-$(CONFIG_PHY_SPACEMIT_K1_USB2) += phy-k1-usb2.o
> +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..66fa6330ad6e
> --- /dev/null
> +++ b/drivers/phy/spacemit/phy-k3-combphy.c
> @@ -0,0 +1,250 @@
> +// 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 <linux/bitfield.h>
> +#include <linux/io.h>
...
> +
> + phy->apb_spare = syscon_regmap_lookup_by_phandle(node, "spacemit,apb-spare");
> + if (IS_ERR(phy->apb_spare))
> + return dev_err_probe(dev, PTR_ERR(phy->apb_spare),
> + "Failed to fine APB SPARE syscon");
typo, s/fine/find
> +
> + 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");
1. typo, s/fine/find
2. PTR_ERR(phy->apb_spare) should be PTR_ERR(apmu)
> +
> + 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 <linux/bitfield.h>
> +#include <linux/cleanup.h>
> +#include <linux/io.h>
> +#include <linux/iopoll.h>
> +#include <linux/module.h>
> +#include <linux/regmap.h>
> +#include <linux/usb.h>
> +
> +#include <dt-bindings/phy/phy.h>
> +
> +#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_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);
This looks like a copy-paste bug.
Read from lane_base but write the modified value to phy_base.
> + }
> +
> + val = readl(phy_base + PHY_PLL_REG2);
> + val |= BIT(20);
> + writel(val, phy_base + PHY_PLL_REG2);
> +
> + writel(0x00006505, phy_base + PCIE_RX_REG1);
Is it intentional? The loop above configured PCIE_RX_REG1, while the
hard-coded 0x00006505 overwrites what's done for lane0.
> +
> + /* 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);
A little confusing here, comment says "pll_reg4[3:0] = 0" but the code is
modifying PHY_PLL_REG1[27:24]
> +
> + 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);
Can we define macros for these values? Just like you did for
PHY_CLK_CFG.
> +
> + /* 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;
> +}
prev parent reply other threads:[~2026-04-30 7:40 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-04-30 2:28 [PATCH 0/2] riscv: spacemit: Add K3 PCIe/USB comb phy support Inochi Amaoto
2026-04-30 2:28 ` [PATCH 1/2] dt-bindings: phy: Add Spacemit K3 USB3/PCIe " Inochi Amaoto
2026-04-30 2:28 ` [PATCH 2/2] phy: spacemit: Add USB3/PCIe comb PHY driver for Spacemit K3 Inochi Amaoto
2026-04-30 7:39 ` Ze Huang [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=DI6BHTW4FFN1.2YLR7O1P8F0Y3@linux.dev \
--to=huang.ze@linux.dev \
--cc=alex@ghiti.fr \
--cc=aou@eecs.berkeley.edu \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=dlan@gentoo.org \
--cc=dlan@kernel.org \
--cc=elder@riscstar.com \
--cc=gustavoars@kernel.org \
--cc=inochiama@gmail.com \
--cc=kees@kernel.org \
--cc=krzk+dt@kernel.org \
--cc=linux-hardening@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-phy@lists.infradead.org \
--cc=linux-riscv@lists.infradead.org \
--cc=looong.bin@gmail.com \
--cc=neil.armstrong@linaro.org \
--cc=palmer@dabbelt.com \
--cc=pjw@kernel.org \
--cc=robh@kernel.org \
--cc=spacemit@lists.linux.dev \
--cc=vkoul@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox