* [PATCH v2 0/4] Add eMMC PHY support for Axiado AX3000 SoC
From: Tzu-Hao Wei @ 2026-02-06 8:22 UTC (permalink / raw)
To: SriNavmani A, Prasad Bolisetty, Vinod Koul, Neil Armstrong,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: linux-phy, devicetree, linux-arm-kernel, linux-kernel, openbmc,
Tzu-Hao Wei
Axiado AX3000 SoC contains Arasan PHY which provides the interface to the
HS200 eMMC controller.
This series includes:
1. Add bindings for Axiado AX3000 eMMC PHY
2. Add Axiado AX3000 eMMC phy driver
3. Update MAINTAINERS for the new driver
4. Update Axiado AX3000 device tree
Changes in v2:
- Fix dt-binding format
- Fix compilation error in m68k
- Use readl_poll_timeout instead of read_poll_timeout
- Link to v1: https://lore.kernel.org/r/20260109-axiado-ax3000-add-emmc-phy-driver-support-v1-0-dd43459dbfea@axiado.com
Changes: (The previous version was mixed with Host driver, so I separate
the PHY driver as a new thread)
- Fix property order in required section to match properties section
- Fixed example to use lowercase hex and proper node naming
- Removed wrapper functions, use readl/writel directly
- Replaced manual polling loops with read_poll_timeout macro
- Used devm_platform_ioremap_resource instead of separate calls
- Removed unnecessary of_match_node check
- Used dev_err_probe for error reporting
- Added proper Kconfig dependencies (ARCH_AXIADO || COMPILE_TEST)
- Fixed various coding style issues
- Link to previous patches: https://lore.kernel.org/all/20251222-axiado-ax3000-add-emmc-host-driver-support-v1-0-5457d0ebcdb4@axiado.com/
Signed-off-by: Tzu-Hao Wei <twei@axiado.com>
---
SriNavmani A (3):
dt-bindings: phy: axiado,ax3000-emmc-phy: add Axiado eMMC PHY
phy: axiado: add Axiado eMMC PHY driver
arm64: dts: axiado: Add eMMC PHY node
Tzu-Hao Wei (1):
MAINTAINERS: Add Axiado AX3000 eMMC PHY driver
.../bindings/phy/axiado,ax3000-emmc-phy.yaml | 37 ++++
MAINTAINERS | 10 +
arch/arm64/boot/dts/axiado/ax3000.dtsi | 7 +
drivers/phy/Kconfig | 1 +
drivers/phy/Makefile | 1 +
drivers/phy/axiado/Kconfig | 11 +
drivers/phy/axiado/Makefile | 1 +
drivers/phy/axiado/phy-axiado-emmc.c | 221 +++++++++++++++++++++
8 files changed, 289 insertions(+)
---
base-commit: 63804fed149a6750ffd28610c5c1c98cce6bd377
change-id: 20260108-axiado-ax3000-add-emmc-phy-driver-support-d61aead8f622
Best regards,
--
Tzu-Hao Wei <twei@axiado.com>
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* Re: [PATCH 3/4 v2] phy: s32g: Add serdes xpcs subsystem
From: Vincent Guittot @ 2026-02-05 17:02 UTC (permalink / raw)
To: Russell King (Oracle)
Cc: vkoul, neil.armstrong, krzk+dt, conor+dt, ciprianmarian.costea,
s32, p.zabel, ghennadi.procopciuc, Ionut.Vicovan, linux-phy,
devicetree, linux-kernel, linux-arm-kernel, netdev, horms,
Frank.li
In-Reply-To: <aYNl3yQXNa7SkpH9@shell.armlinux.org.uk>
On Wed, 4 Feb 2026 at 16:29, Russell King (Oracle)
<linux@armlinux.org.uk> wrote:
>
> Sorry, I don't have time to finish this review, nor cut down the context
> as I normally would do... I'm being bothered on company Slack, which now
> has the really bloody annoying feature of popping up a window rather than
> using KDE's notification subsystem, and that steals keyboard focus away
> from whatever one is trying to do at the time.
>
> On Tue, Feb 03, 2026 at 05:19:16PM +0100, Vincent Guittot wrote:
> > +static bool s32g_xpcs_poll_timeout(struct s32g_xpcs *xpcs, xpcs_poll_func_t func,
> > + ktime_t timeout)
> > +{
> > + ktime_t cur = ktime_get();
> > +
> > + return func(xpcs) || ktime_after(cur, timeout);
> > +}
> > +
> > +static int s32g_xpcs_wait(struct s32g_xpcs *xpcs, xpcs_poll_func_t func)
> > +{
> > + ktime_t timeout = ktime_add_ms(ktime_get(), XPCS_TIMEOUT_MS);
> > +
> > + spin_until_cond(s32g_xpcs_poll_timeout(xpcs, func, timeout));
> > + if (!func(xpcs))
> > + return -ETIMEDOUT;
>
> XPCS_TIMEOUT_MS is 300ms. spin_until_cond() spins until the condition is
> true. Do you need to tie up this CPU for up to 300ms? That seems
> excessive. What is the reason that read_poll_timeout() or similar
> couldn't be used?
This needs additional tests because some instabilities have been
reported when using read_poll_timeout in some places
>
> The advantage of read_poll_timeout() is that it will correctly handle
> the timeout vs condition being satisfied witout need for special code.
>
> > +
> > + return 0;
> > +}
> > +
> > +static int s32g_xpcs_wait_bits(struct s32g_xpcs *xpcs, unsigned int reg,
> > + unsigned int mask, unsigned int bits)
> > +{
> > + ktime_t cur;
> > + ktime_t timeout = ktime_add_ms(ktime_get(), XPCS_TIMEOUT_MS);
> > +
> > + spin_until_cond((cur = ktime_get(),
> > + (s32g_xpcs_read(xpcs, reg) & mask) == bits ||
> > + ktime_after(cur, timeout)));
> > + if ((s32g_xpcs_read(xpcs, reg) & mask) != bits)
> > + return -ETIMEDOUT;
>
> Same here:
>
> return read_poll_timeout(s32g_xpcs_read, val, (val & mask) == bits,
> 0, XPCS_TIMEOUT_MS, false,
> xpcs, reg);
>
> > +
> > + return 0;
> > +}
> > +
> > +static unsigned int s32g_xpcs_digital_status(struct s32g_xpcs *xpcs)
> > +{
> > + return s32g_xpcs_read(xpcs, VR_MII_DIG_STS);
> > +}
> > +
> > +static int s32g_xpcs_wait_power_good_state(struct s32g_xpcs *xpcs)
> > +{
> > + unsigned int val;
> > +
> > + return read_poll_timeout(s32g_xpcs_digital_status, val,
> > + FIELD_GET(PSEQ_STATE_MASK, val) == POWER_GOOD_STATE,
> > + 0,
> > + XPCS_TIMEOUT_MS, false, xpcs);
>
> This could be:
> return read_poll_timeout(s32g_xpcs_read, val,
> FIELD_GET(PSEQ_STATE_MASK, val) == POWER_GOOD_STATE,
> 0, XPCS_TIMEOUT_MS, false,
> xpcs, VR_MII_DIG_STS);
>
> eliminating the need for s32g_xpcs_digital_status().
fair enough
>
> > +}
> > +
> > +void s32g_xpcs_vreset(struct s32g_xpcs *xpcs)
> > +{
> > + /* Step 19 */
> > + s32g_xpcs_write_bits(xpcs, VR_MII_DIG_CTRL1, VR_RST, VR_RST);
> > +}
> > +
> > +static bool s32g_xpcs_is_not_in_reset(struct s32g_xpcs *xpcs)
> > +{
> > + unsigned int val;
> > +
> > + val = s32g_xpcs_read(xpcs, VR_MII_DIG_CTRL1);
> > +
> > + return !(val & VR_RST);
> > +}
> > +
> > +int s32g_xpcs_wait_vreset(struct s32g_xpcs *xpcs)
> > +{
> > + int ret;
> > +
> > + /* Step 20 */
> > + ret = s32g_xpcs_wait(xpcs, s32g_xpcs_is_not_in_reset);
> > + if (ret)
> > + dev_err(xpcs->dev, "XPCS%d is in reset\n", xpcs->id);
> > +
> > + return ret;
> > +}
> > +
> > +int s32g_xpcs_reset_rx(struct s32g_xpcs *xpcs)
> > +{
> > + int ret = 0;
> > +
> > + ret = s32g_xpcs_wait_power_good_state(xpcs);
> > + if (ret) {
> > + dev_err(xpcs->dev, "Failed to enter in PGOOD state after vendor reset\n");
> > + return ret;
> > + }
> > +
> > + /* Step 21 */
> > + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_RX_GENCTRL1,
> > + RX_RST_0, RX_RST_0);
> > +
> > + /* Step 22 */
> > + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_RX_GENCTRL1,
> > + RX_RST_0, 0);
> > +
> > + /* Step 23 */
> > + /* Wait until SR_MII_STS[LINK_STS] = 1 */
> > +
> > + return ret;
> > +}
> > +
> > +static int s32g_xpcs_ref_clk_sel(struct s32g_xpcs *xpcs,
> > + enum s32g_xpcs_pll ref_pll)
> > +{
> > + switch (ref_pll) {
> > + case XPCS_PLLA:
> > + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_MPLL_CMN_CTRL,
> > + MPLLB_SEL_0, 0);
> > + xpcs->ref = XPCS_PLLA;
> > + break;
> > + case XPCS_PLLB:
> > + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_MPLL_CMN_CTRL,
> > + MPLLB_SEL_0, MPLLB_SEL_0);
> > + xpcs->ref = XPCS_PLLB;
> > + break;
> > + default:
> > + return -EINVAL;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static void s32g_xpcs_electrical_configure(struct s32g_xpcs *xpcs)
> > +{
> > + /* Step 2 */
> > + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_TX_EQ_CTRL0,
> > + TX_EQ_MAIN_MASK, FIELD_PREP(TX_EQ_MAIN_MASK, 0xC));
>
> Prefer hex numbers to be lower case.
ok
>
> > +
> > + /* Step 3 */
> > + s32g_xpcs_write_bits(xpcs, VR_MII_CONSUMER_10G_TX_TERM_CTRL,
> > + TX0_TERM_MASK, FIELD_PREP(TX0_TERM_MASK, 0x4));
> > +}
> > +
> > +static int s32g_xpcs_vco_cfg(struct s32g_xpcs *xpcs, enum s32g_xpcs_pll vco_pll)
> > +{
> > + unsigned int vco_ld = 0;
> > + unsigned int vco_ref = 0;
> > + unsigned int rx_baud = 0;
> > + unsigned int tx_baud = 0;
> > +
> > + switch (vco_pll) {
> > + case XPCS_PLLA:
> > + if (xpcs->mhz125) {
> > + vco_ld = FIELD_PREP(VCO_LD_VAL_0_MASK, 1360);
> > + vco_ref = FIELD_PREP(VCO_REF_LD_0_MASK, 17);
> > + } else {
> > + vco_ld = FIELD_PREP(VCO_LD_VAL_0_MASK, 1350);
> > + vco_ref = FIELD_PREP(VCO_REF_LD_0_MASK, 27);
> > + }
> > +
> > + rx_baud = FIELD_PREP(RX0_RATE_MASK, RX0_BAUD_DIV_8);
> > + tx_baud = FIELD_PREP(TX0_RATE_MASK, TX0_BAUD_DIV_4);
> > + break;
> > + case XPCS_PLLB:
> > + if (xpcs->mhz125) {
> > + vco_ld = FIELD_PREP(VCO_LD_VAL_0_MASK, 1350);
> > + vco_ref = FIELD_PREP(VCO_REF_LD_0_MASK, 27);
> > + } else {
> > + vco_ld = FIELD_PREP(VCO_LD_VAL_0_MASK, 1344);
> > + vco_ref = FIELD_PREP(VCO_REF_LD_0_MASK, 43);
> > + }
> > +
> > + rx_baud = FIELD_PREP(RX0_RATE_MASK, RX0_BAUD_DIV_2);
> > + tx_baud = FIELD_PREP(TX0_RATE_MASK, TX0_BAUD_DIV_1);
> > + break;
> > + default:
> > + return -EINVAL;
> > + }
> > +
> > + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_VCO_CAL_LD0,
> > + VCO_LD_VAL_0_MASK, vco_ld);
> > +
> > + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_VCO_CAL_REF0,
> > + VCO_REF_LD_0_MASK, vco_ref);
> > +
> > + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_TX_RATE_CTRL,
> > + TX0_RATE_MASK, tx_baud);
> > + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_RX_RATE_CTRL,
> > + RX0_RATE_MASK, rx_baud);
> > +
> > + if (vco_pll == XPCS_PLLB) {
> > + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_CDR_CTRL,
> > + VCO_LOW_FREQ_0, VCO_LOW_FREQ_0);
> > + } else {
> > + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_CDR_CTRL,
> > + VCO_LOW_FREQ_0, 0);
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int s32g_xpcs_init_mplla(struct s32g_xpcs *xpcs)
> > +{
> > + unsigned int val;
> > +
> > + if (!xpcs)
> > + return -EINVAL;
> > +
> > + /* Step 7 */
> > + val = 0;
> > + if (xpcs->ext_clk)
> > + val |= REF_USE_PAD;
> > +
> > + if (xpcs->mhz125) {
> > + val |= REF_MPLLA_DIV2;
> > + val |= REF_CLK_DIV2;
> > + val |= FIELD_PREP(REF_RANGE_MASK, RANGE_52_78_MHZ);
> > + } else {
> > + val |= FIELD_PREP(REF_RANGE_MASK, RANGE_26_53_MHZ);
> > + }
> > +
> > + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_REF_CLK_CTRL,
> > + REF_MPLLA_DIV2 | REF_USE_PAD | REF_RANGE_MASK |
> > + REF_CLK_DIV2, val);
> > +
> > + /* Step 8 */
> > + if (xpcs->mhz125)
> > + val = FIELD_PREP(MLLA_MULTIPLIER_MASK, 80);
> > + else
> > + val = FIELD_PREP(MLLA_MULTIPLIER_MASK, 25);
> > +
> > + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_MPLLA_CTRL0,
> > + MPLLA_CAL_DISABLE | MLLA_MULTIPLIER_MASK,
> > + val);
> > +
> > + /* Step 9 */
> > + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_MPLLA_CTRL1,
> > + MPLLA_FRACN_CTRL_MASK, 0);
> > +
> > + /* Step 10 */
> > + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_MPLLA_CTRL2,
> > + MPLLA_TX_CLK_DIV_MASK | MPLLA_DIV10_CLK_EN,
> > + FIELD_PREP(MPLLA_TX_CLK_DIV_MASK, 1) | MPLLA_DIV10_CLK_EN);
> > +
> > + /* Step 11 */
> > + if (xpcs->mhz125)
> > + val = FIELD_PREP(MPLLA_BANDWIDTH_MASK, 43);
> > + else
> > + val = FIELD_PREP(MPLLA_BANDWIDTH_MASK, 357);
> > +
> > + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_MPLLA_CTRL3,
> > + MPLLA_BANDWIDTH_MASK, val);
> > +
> > + return 0;
> > +}
> > +
> > +static int s32g_xpcs_init_mpllb(struct s32g_xpcs *xpcs)
> > +{
> > + unsigned int val;
> > +
> > + if (!xpcs)
> > + return -EINVAL;
> > +
> > + /* Step 7 */
> > + val = 0;
> > + if (xpcs->ext_clk)
> > + val |= REF_USE_PAD;
> > +
> > + if (xpcs->mhz125) {
> > + val |= REF_MPLLB_DIV2;
> > + val |= REF_CLK_DIV2;
> > + val |= FIELD_PREP(REF_RANGE_MASK, RANGE_52_78_MHZ);
> > + } else {
> > + val |= FIELD_PREP(REF_RANGE_MASK, RANGE_26_53_MHZ);
> > + }
> > +
> > + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_REF_CLK_CTRL,
> > + REF_MPLLB_DIV2 | REF_USE_PAD | REF_RANGE_MASK |
> > + REF_CLK_DIV2, val);
> > +
> > + /* Step 8 */
> > + if (xpcs->mhz125)
> > + val = 125 << MLLB_MULTIPLIER_OFF;
> > + else
> > + val = 39 << MLLB_MULTIPLIER_OFF;
> > +
> > + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_MPLLB_CTRL0,
> > + MPLLB_CAL_DISABLE | MLLB_MULTIPLIER_MASK,
> > + val);
> > +
> > + /* Step 9 */
> > + if (xpcs->mhz125)
> > + val = FIELD_PREP(MPLLB_FRACN_CTRL_MASK, 0);
> > + else
> > + val = FIELD_PREP(MPLLB_FRACN_CTRL_MASK, 1044);
> > +
> > + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_MPLLB_CTRL1,
> > + MPLLB_FRACN_CTRL_MASK, val);
> > +
> > + /* Step 10 */
> > + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_MPLLB_CTRL2,
> > + MPLLB_TX_CLK_DIV_MASK | MPLLB_DIV10_CLK_EN,
> > + FIELD_PREP(MPLLB_TX_CLK_DIV_MASK, 5) | MPLLB_DIV10_CLK_EN);
> > +
> > + /* Step 11 */
> > + if (xpcs->mhz125)
> > + val = FIELD_PREP(MPLLB_BANDWIDTH_MASK, 68);
> > + else
> > + val = FIELD_PREP(MPLLB_BANDWIDTH_MASK, 102);
> > +
> > + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_MPLLB_CTRL3,
> > + MPLLB_BANDWIDTH_MASK, val);
> > +
> > + return 0;
> > +}
> > +
> > +static void s32g_serdes_pma_high_freq_recovery(struct s32g_xpcs *xpcs)
> > +{
> > + /* PCS signal protection, PLL railout recovery */
> > + s32g_xpcs_write_bits(xpcs, VR_MII_DBG_CTRL, SUPPRESS_LOS_DET | RX_DT_EN_CTL,
> > + SUPPRESS_LOS_DET | RX_DT_EN_CTL);
> > + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_MISC_CTRL0,
> > + PLL_CTRL, PLL_CTRL);
> > +}
> > +
> > +static void s32g_serdes_pma_configure_tx_eq_post(struct s32g_xpcs *xpcs)
> > +{
> > + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_TX_EQ_CTRL1,
> > + TX_EQ_OVR_RIDE, TX_EQ_OVR_RIDE);
> > +}
> > +
> > +static int s32g_serdes_bifurcation_pll_transit(struct s32g_xpcs *xpcs,
> > + enum s32g_xpcs_pll target_pll)
> > +{
> > + int ret = 0;
> > + struct device *dev = xpcs->dev;
> > +
> > + /* Configure XPCS speed and VCO */
> > + if (target_pll == XPCS_PLLA) {
> > + s32g_xpcs_write_bits(xpcs, VR_MII_DIG_CTRL1, EN_2_5G_MODE, 0);
> > + s32g_xpcs_vco_cfg(xpcs, XPCS_PLLA);
> > + } else {
> > + s32g_xpcs_write_bits(xpcs, VR_MII_DIG_CTRL1,
> > + EN_2_5G_MODE, EN_2_5G_MODE);
> > + s32g_xpcs_vco_cfg(xpcs, XPCS_PLLB);
> > + }
>
> I am really not happy with this driver being
> PHY_INTERFACE_MODE_SGMII-only but supporting running that at 2.5Gbps.
> In the kernel, PHY_INTERFACE_MODE_SGMII is strictly _Cisco_ SGMII only,
> which means the version of it clocked at 1.25GHz, not 3.125GHz.
>
> OCSGMII or whatever random name you call it tends to be only supported
> without inband AN, and we have pushed everyone to adopt
> PHY_INTERFACE_MODE_2500BASEX for that. Please do the same.
>
> Should this SerDes be connected to a SFP cage, it will need to support
> dynamically switching between Cisco SGMII and 2500BASE-X.
okay, this needs to be checked with SoC Team
>
> > +
> > + /* Signal that clock are not available */
> > + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_TX_GENCTRL1,
> > + TX_CLK_RDY_0, 0);
> > +
> > + /* Select PLL reference */
> > + if (target_pll == XPCS_PLLA)
> > + s32g_xpcs_ref_clk_sel(xpcs, XPCS_PLLA);
> > + else
> > + s32g_xpcs_ref_clk_sel(xpcs, XPCS_PLLB);
> > +
> > + /* Initiate transmitter TX reconfiguration request */
> > + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_TX_GENCTRL2,
> > + TX_REQ_0, TX_REQ_0);
> > +
> > + /* Wait for transmitter to reconfigure */
> > + ret = s32g_xpcs_wait_bits(xpcs, VR_MII_GEN5_12G_16G_TX_GENCTRL2,
> > + TX_REQ_0, 0);
> > + if (ret) {
> > + dev_err(dev, "Switch to TX_REQ_0 failed\n");
> > + return ret;
> > + }
> > +
> > + /* Initiate transmitter RX reconfiguration request */
> > + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_RX_GENCTRL2,
> > + RX_REQ_0, RX_REQ_0);
> > +
> > + /* Wait for receiver to reconfigure */
> > + ret = s32g_xpcs_wait_bits(xpcs, VR_MII_GEN5_12G_16G_RX_GENCTRL2,
> > + RX_REQ_0, 0);
> > + if (ret) {
> > + dev_err(dev, "Switch to RX_REQ_0 failed\n");
> > + return ret;
> > + }
> > +
> > + /* Signal that clock are available */
> > + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_TX_GENCTRL1,
> > + TX_CLK_RDY_0, TX_CLK_RDY_0);
> > +
> > + /* Flush internal logic */
> > + s32g_xpcs_write_bits(xpcs, VR_MII_DIG_CTRL1, INIT, INIT);
> > +
> > + /* Wait for init */
> > + ret = s32g_xpcs_wait_bits(xpcs, VR_MII_DIG_CTRL1, INIT, 0);
> > + if (ret) {
> > + dev_err(dev, "XPCS INIT failed\n");
> > + return ret;
> > + }
> > +
> > + return ret;
> > +}
> > +
> > +/*
> > + * phylink_pcs_ops
> > + */
> > +
> > +static unsigned int s32cc_phylink_pcs_inband_caps(struct phylink_pcs *pcs,
> > + phy_interface_t interface)
> > +{
> > + switch (interface) {
> > + case PHY_INTERFACE_MODE_SGMII:
> > + return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
> > +
> > + default:
> > + return 0;
> > + }
> > +}
> > +
> > +static int s32cc_phylink_pcs_config(struct phylink_pcs *pcs,
> > + unsigned int neg_mode,
> > + phy_interface_t interface,
> > + const unsigned long *advertising,
> > + bool permit_pause_to_mac)
> > +{
> > + struct s32g_xpcs *xpcs = phylink_pcs_to_s32g_xpcs(pcs);
> > +
> > + /* Step 1: Disable SGMII AN */
> > + s32g_xpcs_write_bits(xpcs, SR_MII_CTRL, AN_ENABLE, 0);
> > +
> > + s32g_xpcs_write_bits(xpcs, VR_MII_AN_CTRL,
> > + MII_AN_INTR_EN,
> > + 0);
> > +
> > + if (!(neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED))
> > + return 0;
> > +
> > + /* Step 2 */
> > + s32g_xpcs_write_bits(xpcs, VR_MII_AN_CTRL,
> > + PCS_MODE_MASK,
> > + FIELD_PREP(PCS_MODE_MASK, PCS_MODE_SGMII));
> > +
> > + /* Step 3 */
> > + s32g_xpcs_write_bits(xpcs, SR_MII_CTRL,
> > + SS13 | SS6,
> > + SS6);
> > +
> > + /* Step 4 */
> > + s32g_xpcs_write_bits(xpcs, VR_MII_AN_CTRL,
> > + MII_CTRL,
> > + 0);
> > + /* Step 5 and 8 */
> > + if (xpcs->pcie_shared == PCIE_XPCS_2G5) {
> > + s32g_xpcs_write(xpcs, VR_MII_LINK_TIMER_CTRL, 0x2faf);
> > + s32g_xpcs_write_bits(xpcs, VR_MII_DIG_CTRL1,
> > + MAC_AUTO_SW, MAC_AUTO_SW);
> > + } else {
> > + s32g_xpcs_write(xpcs, VR_MII_LINK_TIMER_CTRL, 0x7a1);
> > + s32g_xpcs_write_bits(xpcs, VR_MII_DIG_CTRL1, MAC_AUTO_SW, 0);
> > + }
> > +
> > + /* Step 6 */
> > + s32g_xpcs_write_bits(xpcs, VR_MII_DIG_CTRL1,
> > + CL37_TMR_OVRRIDE, CL37_TMR_OVRRIDE);
> > +
> > + /* Step 7 */
> > + s32g_xpcs_write_bits(xpcs, VR_MII_AN_CTRL,
> > + MII_AN_INTR_EN,
> > + MII_AN_INTR_EN);
> > +
> > + /* Step 9: Enable SGMII AN */
> > + s32g_xpcs_write_bits(xpcs, SR_MII_CTRL, AN_ENABLE, AN_ENABLE);
> > +
> > + return 0;
> > +}
> > +
> > +static void s32cc_phylink_pcs_get_state(struct phylink_pcs *pcs, unsigned int neg_mode,
> > + struct phylink_link_state *state)
> > +{
> > + struct s32g_xpcs *xpcs = phylink_pcs_to_s32g_xpcs(pcs);
> > + bool ss6, ss13, an_enabled;
> > + struct device *dev = xpcs->dev;
> > + unsigned int val, ss;
> > +
> > + an_enabled = (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED);
> > +
> > + if (an_enabled) {
> > + state->link = 0;
> > + val = s32g_xpcs_read(xpcs, VR_MII_AN_INTR_STS);
> > +
> > + /* Interrupt is raised with each SGMII AN that is in cases
> > + * Link down - Every SGMII link timer expire
> > + * Link up - Once before link goes up
> > + * So either linkup or raised interrupt mean AN was completed
> > + */
> > + if ((val & CL37_ANCMPLT_INTR) || (val & CL37_ANSGM_STS_LINK)) {
> > + state->an_complete = 1;
> > + if (val & CL37_ANSGM_STS_LINK)
> > + state->link = 1;
> > + else
> > + return;
> > + if (val & CL37_ANSGM_STS_DUPLEX)
> > + state->duplex = DUPLEX_FULL;
> > + else
> > + state->duplex = DUPLEX_HALF;
> > + ss = FIELD_GET(CL37_ANSGM_STS_SPEED_MASK, val);
> > + } else {
> > + return;
> > + }
> > +
> > + } else {
> > + val = s32g_xpcs_read(xpcs, SR_MII_STS);
> > + state->link = !!(val & LINK_STS);
> > + state->an_complete = 0;
> > + state->pause = MLO_PAUSE_NONE;
> > +
> > + val = s32g_xpcs_read(xpcs, SR_MII_CTRL);
> > + if (val & DUPLEX_MODE)
> > + state->duplex = DUPLEX_FULL;
> > + else
> > + state->duplex = DUPLEX_HALF;
> > +
> > + /*
> > + * Build similar value as CL37_ANSGM_STS_SPEED with
> > + * SS6 and SS13 of SR_MII_CTRL:
> > + * - 0 for 10 Mbps
> > + * - 1 for 100 Mbps
> > + * - 2 for 1000 Mbps
> > + */
> > + ss6 = !!(val & SS6);
> > + ss13 = !!(val & SS13);
> > + ss = ss6 << 1 | ss13;
> > + }
> > +
> > + switch (ss) {
> > + case CL37_ANSGM_10MBPS:
> > + state->speed = SPEED_10;
> > + break;
> > + case CL37_ANSGM_100MBPS:
> > + state->speed = SPEED_100;
> > + break;
> > + case CL37_ANSGM_1000MBPS:
> > + state->speed = SPEED_1000;
> > + break;
> > + default:
> > + dev_err(dev, "Failed to interpret the value of SR_MII_CTRL\n");
> > + break;
> > + }
> > +
> > + val = s32g_xpcs_read(xpcs, VR_MII_DIG_CTRL1);
> > + if ((val & EN_2_5G_MODE) && state->speed == SPEED_1000)
> > + state->speed = SPEED_2500;
> > +
> > + /* Cover SGMII AN inability to distigunish between 1G and 2.5G */
> > + if ((val & EN_2_5G_MODE) &&
> > + state->speed != SPEED_2500 && an_enabled) {
> > + dev_err(dev, "Speed not supported in SGMII AN mode\n");
> > + }
> > +}
> > +
> > +static void s32cc_phylink_pcs_link_up(struct phylink_pcs *pcs,
> > + unsigned int neg_mode,
> > + phy_interface_t interface, int speed,
> > + int duplex)
> > +{
> > + struct s32g_xpcs *xpcs = phylink_pcs_to_s32g_xpcs(pcs);
> > + struct device *dev = xpcs->dev;
> > + bool an_enabled = (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED);
> > + unsigned int val;
> > + int ret;
> > +
> > + dev_dbg(dev, "xpcs_%d: speed=%u duplex=%d an=%d\n", xpcs->id,
> > + speed, duplex, an_enabled);
> > +
> > + if (an_enabled)
> > + return;
> > +
> > + s32g_xpcs_write_bits(xpcs, SR_MII_CTRL, AN_ENABLE, 0);
> > + s32g_xpcs_write_bits(xpcs, VR_MII_AN_CTRL, MII_AN_INTR_EN, 0);
> > + s32g_xpcs_write_bits(xpcs, VR_MII_AN_CTRL, MII_CTRL, 0);
>
> Haven't you already disabled AN in .pcs_config() ? This method doesn't
> change the AN enable state, the only time that happens is when
> .pcs_config() will be called. All other cases of passing neg_mode are
> merely informational.
Fair enough, this is probably not needed anymore with the changes in .pcs_config
>
> > +
> > + if (duplex == DUPLEX_FULL)
> > + val = DUPLEX_MODE;
> > + else
> > + val = 0;
> > +
> > + s32g_xpcs_write_bits(xpcs, SR_MII_CTRL, DUPLEX_MODE, val);
> > +
> > + switch (speed) {
> > + case SPEED_10:
> > + val = 0;
> > + break;
> > + case SPEED_100:
> > + val = SS13;
> > + break;
> > + case SPEED_1000:
> > + val = SS6;
> > + break;
> > + case SPEED_2500:
> > + val = SS6;
> > + break;
> > + default:
> > + dev_err(dev, "Speed not supported\n");
> > + break;
> > + }
> > +
> > + if (speed == SPEED_2500) {
> > + ret = s32g_serdes_bifurcation_pll_transit(xpcs, XPCS_PLLB);
> > + if (ret)
> > + dev_err(dev, "Switch to PLLB failed\n");
> > + } else {
> > + ret = s32g_serdes_bifurcation_pll_transit(xpcs, XPCS_PLLA);
> > + if (ret)
> > + dev_err(dev, "Switch to PLLA failed\n");
> > + }
>
> This is a protocol transition, and isn't something that can be handled
> here. Cisco SGMII (PHY_INTERFACE_MODE_SGMII) does not support 2500Mbps
> and phylink will not allow it.
>
> See my comments for s32g_serdes_bifurcation_pll_transit().
>
> > +
> > + s32g_xpcs_write_bits(xpcs, SR_MII_CTRL, SS6 | SS13, val);
> > +}
> > +
> > +static const struct phylink_pcs_ops s32cc_phylink_pcs_ops = {
> > + .pcs_inband_caps = s32cc_phylink_pcs_inband_caps,
> > + .pcs_get_state = s32cc_phylink_pcs_get_state,
> > + .pcs_config = s32cc_phylink_pcs_config,
> > + .pcs_link_up = s32cc_phylink_pcs_link_up,
> > +};
> > +
> > +/*
> > + * Serdes functions for initializing/configuring/releasing the xpcs
> > + */
> > +
> > +int s32g_xpcs_pre_pcie_2g5(struct s32g_xpcs *xpcs)
> > +{
> > + int ret;
> > +
> > + /* Enable voltage boost */
> > + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_TX_GENCTRL1, VBOOST_EN_0,
> > + VBOOST_EN_0);
> > +
> > + /* TX rate baud */
> > + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_TX_RATE_CTRL, 0x7, 0x0U);
> > +
> > + /* Rx rate baud/2 */
> > + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_RX_RATE_CTRL, 0x3U, 0x1U);
> > +
> > + /* Set low-frequency operating band */
> > + s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_CDR_CTRL, CDR_SSC_EN_0,
> > + VCO_LOW_FREQ_0);
> > +
> > + ret = s32g_serdes_bifurcation_pll_transit(xpcs, XPCS_PLLB);
> > + if (ret)
> > + dev_err(xpcs->dev, "Switch to PLLB failed\n");
> > +
> > + return ret;
> > +}
> > +
> > +int s32g_xpcs_init_plls(struct s32g_xpcs *xpcs)
> > +{
> > + int ret;
> > + struct device *dev = xpcs->dev;
> > +
> > + if (!xpcs->ext_clk) {
> > + /* Step 1 */
> > + s32g_xpcs_write_bits(xpcs, VR_MII_DIG_CTRL1, BYP_PWRUP, BYP_PWRUP);
> > + } else if (xpcs->pcie_shared == NOT_SHARED) {
> > + ret = s32g_xpcs_wait_power_good_state(xpcs);
> > + if (ret)
> > + return ret;
> > + } else if (xpcs->pcie_shared == PCIE_XPCS_2G5) {
> > + ret = s32g_xpcs_wait_power_good_state(xpcs);
> > + if (ret)
> > + return ret;
> > + /* Configure equalization */
> > + s32g_serdes_pma_configure_tx_eq_post(xpcs);
> > + s32g_xpcs_electrical_configure(xpcs);
> > +
> > + /* Enable receiver recover */
> > + s32g_serdes_pma_high_freq_recovery(xpcs);
> > + return 0;
> > + }
> > +
> > + s32g_xpcs_electrical_configure(xpcs);
> > +
> > + s32g_xpcs_ref_clk_sel(xpcs, XPCS_PLLA);
> > + ret = s32g_xpcs_init_mplla(xpcs);
> > + if (ret) {
> > + dev_err(dev, "Failed to initialize PLLA\n");
> > + return ret;
> > + }
> > + ret = s32g_xpcs_init_mpllb(xpcs);
> > + if (ret) {
> > + dev_err(dev, "Failed to initialize PLLB\n");
> > + return ret;
> > + }
> > + s32g_xpcs_vco_cfg(xpcs, XPCS_PLLA);
> > +
> > + /* Step 18 */
> > + if (!xpcs->ext_clk)
> > + s32g_xpcs_write_bits(xpcs, VR_MII_DIG_CTRL1, BYP_PWRUP, 0);
> > +
> > + /* Will be cleared by Step 19 Vreset */
> > + s32g_xpcs_write_bits(xpcs, SR_MII_CTRL, AN_ENABLE, 0);
> > + s32g_xpcs_write_bits(xpcs, SR_MII_CTRL, DUPLEX_MODE, DUPLEX_MODE);
> > +
> > + return ret;
> > +}
> > +
> > +void s32g_xpcs_disable_an(struct s32g_xpcs *xpcs)
> > +{
> > + s32g_xpcs_write_bits(xpcs, SR_MII_CTRL, DUPLEX_MODE, DUPLEX_MODE);
> > + s32g_xpcs_write_bits(xpcs, SR_MII_CTRL, AN_ENABLE, 0);
> > +}
>
> Sorry, but why? You should never override phylink's requests.
Serdes sometimes needs to be reset and the AN is enabled by default
after the reset and even before pcs_config has been called. This is
called during the inti of serdes after an ip reset.
That being said this might not be needed anymore after the change in
pcs_config that disables AN
>
> > +
> > +int s32g_xpcs_init(struct s32g_xpcs *xpcs, struct device *dev,
> > + unsigned char id, void __iomem *base, bool ext_clk,
> > + unsigned long rate, enum s32g_xpcs_shared pcie_shared)
> > +{
> > + struct regmap_config conf;
> > +
> > + if (rate != (125 * HZ_PER_MHZ) && rate != (100 * HZ_PER_MHZ)) {
> > + dev_err(dev, "XPCS cannot operate @%lu HZ\n", rate);
> > + return -EINVAL;
> > + }
> > +
> > + xpcs->base = base;
> > + xpcs->ext_clk = ext_clk;
> > + xpcs->id = id;
> > + xpcs->dev = dev;
> > + xpcs->pcie_shared = pcie_shared;
> > +
> > + if (rate == (125 * HZ_PER_MHZ))
> > + xpcs->mhz125 = true;
> > + else
> > + xpcs->mhz125 = false;
> > +
> > + conf = s32g_xpcs_regmap_config;
> > +
> > + if (!id)
> > + conf.name = "xpcs0";
> > + else
> > + conf.name = "xpcs1";
> > +
> > + xpcs->regmap = devm_regmap_init(dev, NULL, xpcs, &conf);
> > + if (IS_ERR(xpcs->regmap))
> > + return dev_err_probe(dev, PTR_ERR(xpcs->regmap),
> > + "Failed to init register amp\n");
> > +
> > + /* Phylink PCS */
> > + xpcs->pcs.ops = &s32cc_phylink_pcs_ops;
> > + xpcs->pcs.poll = true;
> > + __set_bit(PHY_INTERFACE_MODE_SGMII, xpcs->pcs.supported_interfaces);
> > +
> > + return 0;
> > +}
> > diff --git a/drivers/phy/freescale/Kconfig b/drivers/phy/freescale/Kconfig
> > index 45184a3cdd69..bb7f59897faf 100644
> > --- a/drivers/phy/freescale/Kconfig
> > +++ b/drivers/phy/freescale/Kconfig
> > @@ -66,6 +66,7 @@ config PHY_S32G_SERDES
> > tristate "NXP S32G SERDES support"
> > depends on ARCH_S32 || COMPILE_TEST
> > select GENERIC_PHY
> > + select REGMAP
> > help
> > This option enables support for S23G SerDes PHY used for
> > PCIe & Ethernet
> > diff --git a/drivers/phy/freescale/phy-nxp-s32g-serdes.c b/drivers/phy/freescale/phy-nxp-s32g-serdes.c
> > index 321a80c02be5..f2f7eb5aa327 100644
> > --- a/drivers/phy/freescale/phy-nxp-s32g-serdes.c
> > +++ b/drivers/phy/freescale/phy-nxp-s32g-serdes.c
> > @@ -12,12 +12,14 @@
> > #include <linux/module.h>
> > #include <linux/of_platform.h>
> > #include <linux/of_address.h>
> > +#include <linux/pcs/pcs-nxp-s32g-xpcs.h>
> > #include <linux/phy/phy.h>
> > #include <linux/platform_device.h>
> > #include <linux/processor.h>
> > #include <linux/reset.h>
> > #include <linux/units.h>
> >
> > +#define S32G_SERDES_XPCS_MAX 2
> > #define S32G_SERDES_MODE_MAX 5
> >
> > #define EXTERNAL_CLK_NAME "ext"
> > @@ -32,6 +34,52 @@
> > #define S32G_PCIE_PHY_MPLLA_CTRL 0x10
> > #define MPLL_STATE BIT(30)
> >
> > +#define S32G_PCIE_PHY_MPLLB_CTRL 0x14
> > +#define MPLLB_SSC_EN BIT(1)
> > +
> > +#define S32G_PCIE_PHY_EXT_CTRL_SEL 0x18
> > +#define EXT_PHY_CTRL_SEL BIT(0)
> > +
> > +#define S32G_PCIE_PHY_EXT_BS_CTRL 0x1C
> > +#define EXT_BS_TX_LOWSWING BIT(6)
> > +#define EXT_BS_RX_BIGSWING BIT(5)
> > +#define EXT_BS_RX_LEVEL_MASK GENMASK(4, 0)
> > +
> > +#define S32G_PCIE_PHY_REF_CLK_CTRL 0x20
> > +#define EXT_REF_RANGE_MASK GENMASK(5, 3)
> > +#define REF_CLK_DIV2_EN BIT(2)
> > +#define REF_CLK_MPLLB_DIV2_EN BIT(1)
> > +
> > +#define S32G_PCIE_PHY_EXT_MPLLA_CTRL_1 0x30
> > +#define EXT_MPLLA_BANDWIDTH_MASK GENMASK(15, 0)
> > +
> > +#define S32G_PCIE_PHY_EXT_MPLLB_CTRL_1 0x40
> > +#define EXT_MPLLB_DIV_MULTIPLIER_MASK GENMASK(31, 24)
> > +#define EXT_MPLLB_DIV_CLK_EN BIT(19)
> > +#define EXT_MPLLB_DIV8_CLK_EN BIT(18)
> > +#define EXT_MPLLB_DIV10_CLK_EN BIT(16)
> > +#define EXT_MPLLB_BANDWIDTH_MASK GENMASK(15, 0)
> > +
> > +#define S32G_PCIE_PHY_EXT_MPLLB_CTRL_2 0x44
> > +#define EXT_MPLLB_FRACN_CTRL_MASK GENMASK(22, 12)
> > +#define MPLLB_MULTIPLIER_MASK GENMASK(8, 0)
> > +
> > +#define S32G_PCIE_PHY_EXT_MPLLB_CTRL_3 0x48
> > +#define EXT_MPLLB_WORD_DIV2_EN BIT(31)
> > +#define EXT_MPLLB_TX_CLK_DIV_MASK GENMASK(30, 28)
> > +
> > +#define S32G_PCIE_PHY_EXT_MISC_CTRL_1 0xA0
> > +#define EXT_RX_LOS_THRESHOLD_MASK GENMASK(7, 1)
> > +#define EXT_RX_VREF_CTRL_MASK GENMASK(28, 24)
> > +
> > +#define S32G_PCIE_PHY_EXT_MISC_CTRL_2 0xA4
> > +#define EXT_TX_VBOOST_LVL_MASK GENMASK(18, 16)
> > +#define EXT_TX_TERM_CTRL_MASK GENMASK(26, 24)
> > +
> > +#define S32G_PCIE_PHY_XPCS1_RX_OVRD_CTRL 0xD0
> > +#define XPCS1_RX_VCO_LD_VAL_MASK GENMASK(28, 16)
> > +#define XPCS1_RX_REF_LD_VAL_MASK GENMASK(14, 8)
> > +
> > #define S32G_SS_RW_REG_0 0xF0
> > #define SUBMODE_MASK GENMASK(3, 0)
> > #define CLKEN_MASK BIT(23)
> > @@ -44,6 +92,9 @@
> >
> > #define S32G_PHY_REG_DATA 0x4
> >
> > +#define S32G_PHY_RST_CTRL 0x8
> > +#define WARM_RST BIT(1)
> > +
> > #define RAWLANE0_DIG_PCS_XF_RX_EQ_DELTA_IQ_OVRD_IN 0x3019
> > #define RAWLANE1_DIG_PCS_XF_RX_EQ_DELTA_IQ_OVRD_IN 0x3119
> >
> > @@ -76,16 +127,33 @@ struct s32g_pcie_ctrl {
> > bool powered_on;
> > };
> >
> > +struct s32g_xpcs_ctrl {
> > + struct s32g_xpcs *phys[2];
> > + void __iomem *base0, *base1;
> > +};
> > +
> > struct s32g_serdes {
> > struct s32g_serdes_ctrl ctrl;
> > struct s32g_pcie_ctrl pcie;
> > + struct s32g_xpcs_ctrl xpcs;
> > struct device *dev;
> > + u8 lanes_status;
> > };
> >
> > /* PCIe phy subsystem */
> >
> > #define S32G_SERDES_PCIE_FREQ (100 * HZ_PER_MHZ)
> >
> > +static void s32g_pcie_phy_reset(struct s32g_serdes *serdes)
> > +{
> > + u32 val;
> > +
> > + val = readl(serdes->pcie.phy_base + S32G_PHY_RST_CTRL);
> > + writel(val | WARM_RST, serdes->pcie.phy_base + S32G_PHY_RST_CTRL);
> > + usleep_range(1000, 1100);
> > + writel(val, serdes->pcie.phy_base + S32G_PHY_RST_CTRL);
> > +}
> > +
> > static int s32g_pcie_check_clk(struct s32g_serdes *serdes)
> > {
> > struct s32g_serdes_ctrl *sctrl = &serdes->ctrl;
> > @@ -277,6 +345,192 @@ static struct phy *s32g_serdes_phy_xlate(struct device *dev,
> > return phy;
> > }
> >
> > +/* XPCS subsystem */
> > +
> > +static int s32g_serdes_xpcs_init(struct s32g_serdes *serdes, int id)
> > +{
> > + struct s32g_serdes_ctrl *ctrl = &serdes->ctrl;
> > + struct s32g_xpcs_ctrl *xpcs = &serdes->xpcs;
> > + enum s32g_xpcs_shared shared = NOT_SHARED;
> > + unsigned long rate = ctrl->ref_clk_rate;
> > + struct device *dev = serdes->dev;
> > + void __iomem *base;
> > +
> > + if (!id)
> > + base = xpcs->base0;
> > + else
> > + base = xpcs->base1;
> > +
> > + if (ctrl->ss_mode == 1 || ctrl->ss_mode == 2)
> > + shared = PCIE_XPCS_1G;
> > + else if (ctrl->ss_mode == 5)
> > + shared = PCIE_XPCS_2G5;
> > +
> > + return s32g_xpcs_init(xpcs->phys[id], dev, id, base,
> > + ctrl->ext_clk, rate, shared);
> > +}
> > +
> > +static void s32g_serdes_prepare_pma_mode5(struct s32g_serdes *serdes)
> > +{
> > + u32 val;
> > + /* Configure TX_VBOOST_LVL and TX_TERM_CTRL */
> > + val = readl(serdes->ctrl.ss_base + S32G_PCIE_PHY_EXT_MISC_CTRL_2);
> > + val &= ~(EXT_TX_VBOOST_LVL_MASK | EXT_TX_TERM_CTRL_MASK);
> > + val |= FIELD_PREP(EXT_TX_VBOOST_LVL_MASK, 0x3) |
> > + FIELD_PREP(EXT_TX_TERM_CTRL_MASK, 0x4);
> > + writel(val, serdes->ctrl.ss_base + S32G_PCIE_PHY_EXT_MISC_CTRL_2);
> > +
> > + /* Enable phy external control */
> > + val = readl(serdes->ctrl.ss_base + S32G_PCIE_PHY_EXT_CTRL_SEL);
> > + val |= EXT_PHY_CTRL_SEL;
> > + writel(val, serdes->ctrl.ss_base + S32G_PCIE_PHY_EXT_CTRL_SEL);
> > +
> > + /* Configure ref range, disable PLLB/ref div2 */
> > + val = readl(serdes->ctrl.ss_base + S32G_PCIE_PHY_REF_CLK_CTRL);
> > + val &= ~(REF_CLK_DIV2_EN | REF_CLK_MPLLB_DIV2_EN | EXT_REF_RANGE_MASK);
> > + val |= FIELD_PREP(EXT_REF_RANGE_MASK, 0x3);
> > + writel(val, serdes->ctrl.ss_base + S32G_PCIE_PHY_REF_CLK_CTRL);
> > +
> > + /* Configure multiplier */
> > + val = readl(serdes->ctrl.ss_base + S32G_PCIE_PHY_EXT_MPLLB_CTRL_2);
> > + val &= ~(MPLLB_MULTIPLIER_MASK | EXT_MPLLB_FRACN_CTRL_MASK | BIT(24) | BIT(28));
> > + val |= FIELD_PREP(MPLLB_MULTIPLIER_MASK, 0x27U) |
> > + FIELD_PREP(EXT_MPLLB_FRACN_CTRL_MASK, 0x414);
> > + writel(val, serdes->ctrl.ss_base + S32G_PCIE_PHY_EXT_MPLLB_CTRL_2);
> > +
> > + val = readl(serdes->ctrl.ss_base + S32G_PCIE_PHY_MPLLB_CTRL);
> > + val &= ~MPLLB_SSC_EN;
> > + writel(val, serdes->ctrl.ss_base + S32G_PCIE_PHY_MPLLB_CTRL);
> > +
> > + /* Configure tx lane division, disable word clock div2*/
> > + val = readl(serdes->ctrl.ss_base + S32G_PCIE_PHY_EXT_MPLLB_CTRL_3);
> > + val &= ~(EXT_MPLLB_WORD_DIV2_EN | EXT_MPLLB_TX_CLK_DIV_MASK);
> > + val |= FIELD_PREP(EXT_MPLLB_TX_CLK_DIV_MASK, 0x5);
> > + writel(val, serdes->ctrl.ss_base + S32G_PCIE_PHY_EXT_MPLLB_CTRL_3);
> > +
> > + /* Configure bandwidth for filtering and div10*/
> > + val = readl(serdes->ctrl.ss_base + S32G_PCIE_PHY_EXT_MPLLB_CTRL_1);
> > + val &= ~(EXT_MPLLB_BANDWIDTH_MASK | EXT_MPLLB_DIV_CLK_EN |
> > + EXT_MPLLB_DIV8_CLK_EN | EXT_MPLLB_DIV_MULTIPLIER_MASK);
> > + val |= FIELD_PREP(EXT_MPLLB_BANDWIDTH_MASK, 0x5f) | EXT_MPLLB_DIV10_CLK_EN;
> > + writel(val, serdes->ctrl.ss_base + S32G_PCIE_PHY_EXT_MPLLB_CTRL_1);
> > +
> > + val = readl(serdes->ctrl.ss_base + S32G_PCIE_PHY_EXT_MPLLA_CTRL_1);
> > + val &= ~(EXT_MPLLA_BANDWIDTH_MASK);
> > + val |= FIELD_PREP(EXT_MPLLA_BANDWIDTH_MASK, 0xc5);
> > + writel(val, serdes->ctrl.ss_base + S32G_PCIE_PHY_EXT_MPLLA_CTRL_1);
> > +
> > + /* Configure VCO */
> > + val = readl(serdes->ctrl.ss_base + S32G_PCIE_PHY_XPCS1_RX_OVRD_CTRL);
> > + val &= ~(XPCS1_RX_VCO_LD_VAL_MASK | XPCS1_RX_REF_LD_VAL_MASK);
> > + val |= FIELD_PREP(XPCS1_RX_VCO_LD_VAL_MASK, 0x540) |
> > + FIELD_PREP(XPCS1_RX_REF_LD_VAL_MASK, 0x2b);
> > + writel(val, serdes->ctrl.ss_base + S32G_PCIE_PHY_XPCS1_RX_OVRD_CTRL);
> > +
> > + /* Boundary scan control */
> > + val = readl(serdes->ctrl.ss_base + S32G_PCIE_PHY_EXT_BS_CTRL);
> > + val &= ~(EXT_BS_RX_LEVEL_MASK | EXT_BS_TX_LOWSWING);
> > + val |= FIELD_PREP(EXT_BS_RX_LEVEL_MASK, 0xB) | EXT_BS_RX_BIGSWING;
> > + writel(val, serdes->ctrl.ss_base + S32G_PCIE_PHY_EXT_BS_CTRL);
> > +
> > + /* Rx loss threshold */
> > + val = readl(serdes->ctrl.ss_base + S32G_PCIE_PHY_EXT_MISC_CTRL_1);
> > + val &= ~(EXT_RX_LOS_THRESHOLD_MASK | EXT_RX_VREF_CTRL_MASK);
> > + val |= FIELD_PREP(EXT_RX_LOS_THRESHOLD_MASK, 0x3U) |
> > + FIELD_PREP(EXT_RX_VREF_CTRL_MASK, 0x11U);
> > + writel(val, serdes->ctrl.ss_base + S32G_PCIE_PHY_EXT_MISC_CTRL_1);
> > +}
> > +
> > +static int s32g_serdes_enable_mode5(struct s32g_serdes *serdes, struct s32g_xpcs *xpcs)
> > +{
> > + int ret;
> > +
> > + s32g_serdes_prepare_pma_mode5(serdes);
> > +
> > + ret = s32g_xpcs_pre_pcie_2g5(xpcs);
> > + if (ret) {
> > + dev_err(serdes->dev,
> > + "Failed to prepare SerDes for PCIE & XPCS @ 2G5 mode\n");
> > + return ret;
> > + }
> > +
> > + s32g_pcie_phy_reset(serdes);
> > +
> > + return 0;
> > +}
> > +
> > +static int s32g_serdes_init_clks(struct s32g_serdes *serdes)
> > +{
> > + struct s32g_serdes_ctrl *ctrl = &serdes->ctrl;
> > + struct s32g_xpcs_ctrl *xpcs = &serdes->xpcs;
> > + struct s32g_xpcs *order[2];
> > + size_t i;
> > + int ret;
> > +
> > + switch (ctrl->ss_mode) {
> > + case 0:
> > + return 0;
> > + case 1:
> > + order[0] = xpcs->phys[0];
> > + order[1] = NULL;
> > + break;
> > + case 2:
> > + case 5:
> > + order[0] = xpcs->phys[1];
> > + order[1] = NULL;
> > + break;
> > + case 3:
> > + order[0] = xpcs->phys[1];
> > + order[1] = xpcs->phys[0];
> > + break;
> > + case 4:
> > + order[0] = xpcs->phys[0];
> > + order[1] = xpcs->phys[1];
> > + break;
> > + default:
> > + return -EINVAL;
> > + }
> > +
> > + for (i = 0; i < ARRAY_SIZE(order); i++) {
> > + if (!order[i])
> > + continue;
> > +
> > + ret = s32g_xpcs_init_plls(order[i]);
> > + if (ret)
> > + return ret;
> > + }
> > +
> > + for (i = 0; i < ARRAY_SIZE(order); i++) {
> > + if (!order[i])
> > + continue;
> > +
> > + if (ctrl->ss_mode == 5) {
> > + ret = s32g_serdes_enable_mode5(serdes, order[i]);
> > + if (ret)
> > + return ret;
> > + } else {
> > + s32g_xpcs_vreset(order[i]);
> > + }
> > + }
> > +
> > + for (i = 0; i < ARRAY_SIZE(order); i++) {
> > + if (!order[i])
> > + continue;
> > +
> > + ret = s32g_xpcs_wait_vreset(order[i]);
> > + if (ret)
> > + return ret;
> > +
> > + ret = s32g_xpcs_reset_rx(order[i]);
> > + if (ret)
> > + return ret;
> > +
> > + s32g_xpcs_disable_an(order[i]);
> > + }
> > +
> > + return 0;
> > +}
> > +
> > /* Serdes subsystem */
> >
> > static int s32g_serdes_assert_reset(struct s32g_serdes *serdes)
> > @@ -331,6 +585,10 @@ static int s32g_serdes_init(struct s32g_serdes *serdes)
> > return ret;
> > }
> >
> > + /*
> > + * We have a tight timing for the init sequence and any delay linked to
> > + * printk as an example can fail the init after reset
> > + */
> > ret = s32g_serdes_assert_reset(serdes);
> > if (ret)
> > goto disable_clks;
> > @@ -363,7 +621,13 @@ static int s32g_serdes_init(struct s32g_serdes *serdes)
> > dev_info(serdes->dev, "Using mode %d for SerDes subsystem\n",
> > ctrl->ss_mode);
> >
> > - return 0;
> > + ret = s32g_serdes_init_clks(serdes);
> > + if (ret) {
> > + dev_err(serdes->dev, "XPCS init failed\n");
> > + goto disable_clks;
> > + }
> > +
> > + return ret;
> >
> > disable_clks:
> > clk_bulk_disable_unprepare(serdes->ctrl.nclks,
> > @@ -449,12 +713,32 @@ static int s32g_serdes_get_pcie_resources(struct platform_device *pdev, struct s
> > return 0;
> > }
> >
> > +static int s32g_serdes_get_xpcs_resources(struct platform_device *pdev, struct s32g_serdes *serdes)
> > +{
> > + struct s32g_xpcs_ctrl *xpcs = &serdes->xpcs;
> > + struct device *dev = &pdev->dev;
> > +
> > + xpcs->base0 = devm_platform_ioremap_resource_byname(pdev, "xpcs0");
> > + if (IS_ERR(xpcs->base0)) {
> > + dev_err(dev, "Failed to map 'xpcs0'\n");
> > + return PTR_ERR(xpcs->base0);
> > + }
> > +
> > + xpcs->base1 = devm_platform_ioremap_resource_byname(pdev, "xpcs1");
> > + if (IS_ERR(xpcs->base1)) {
> > + dev_err(dev, "Failed to map 'xpcs1'\n");
> > + return PTR_ERR(xpcs->base1);
> > + }
> > +
> > + return 0;
> > +}
> > +
> > static int s32g2_serdes_create_phy(struct s32g_serdes *serdes, struct device_node *child_node)
> > {
> > struct s32g_serdes_ctrl *ctrl = &serdes->ctrl;
> > struct phy_provider *phy_provider;
> > struct device *dev = serdes->dev;
> > - int ss_mode = ctrl->ss_mode;
> > + int ret, ss_mode = ctrl->ss_mode;
> > struct phy *phy;
> >
> > if (of_device_is_compatible(child_node, "nxp,s32g2-serdes-pcie-phy")) {
> > @@ -476,6 +760,37 @@ static int s32g2_serdes_create_phy(struct s32g_serdes *serdes, struct device_nod
> > if (IS_ERR(phy_provider))
> > return PTR_ERR(phy_provider);
> >
> > + } else if (of_device_is_compatible(child_node, "nxp,s32g2-serdes-xpcs")) {
> > + struct s32g_xpcs_ctrl *xpcs_ctrl = &serdes->xpcs;
> > + struct s32g_xpcs *xpcs;
> > + int port;
> > +
> > + /* no Ethernet phy lane */
> > + if (ss_mode == 0)
> > + return 0;
> > +
> > + /* Get XPCS port number connected to the lane */
> > + if (of_property_read_u32(child_node, "reg", &port))
> > + return -EINVAL;
> > +
> > + /* XPCS1 is not used */
> > + if (ss_mode == 1 && port == 1)
> > + return -EINVAL;
> > +
> > + /* XPCS0 is not used */
> > + if (ss_mode == 2 && port == 0)
> > + return -EINVAL;
> > +
> > + xpcs = devm_kmalloc(dev, sizeof(*xpcs), GFP_KERNEL);
> > + if (!xpcs)
> > + return -ENOMEM;
> > +
> > + xpcs_ctrl->phys[port] = xpcs;
> > +
> > + ret = s32g_serdes_xpcs_init(serdes, port);
> > + if (ret)
> > + return ret;
> > +
> > } else {
> > dev_warn(dev, "Skipping unknown child node %pOFn\n", child_node);
> > }
> > @@ -517,6 +832,10 @@ static int s32g_serdes_probe(struct platform_device *pdev)
> > if (ret)
> > return ret;
> >
> > + ret = s32g_serdes_get_xpcs_resources(pdev, serdes);
> > + if (ret)
> > + return ret;
> > +
> > ret = s32g_serdes_parse_lanes(dev, serdes);
> > if (ret)
> > return ret;
> > @@ -555,6 +874,57 @@ static int __maybe_unused s32g_serdes_resume(struct device *device)
> > return ret;
> > }
> >
> > +struct phylink_pcs *s32g_serdes_pcs_create(struct device *dev, struct device_node *np)
> > +{
> > + struct platform_device *pdev;
> > + struct device_node *pcs_np;
> > + struct s32g_serdes *serdes;
> > + u32 port;
> > +
> > + if (of_property_read_u32(np, "reg", &port))
> > + return ERR_PTR(-EINVAL);
> > +
> > + if (port >= S32G_SERDES_XPCS_MAX)
> > + return ERR_PTR(-EINVAL);
> > +
> > + /* The PCS pdev is attached to the parent node */
> > + pcs_np = of_get_parent(np);
> > + if (!pcs_np)
> > + return ERR_PTR(-ENODEV);
> > +
> > + if (!of_device_is_available(pcs_np)) {
> > + of_node_put(pcs_np);
> > + return ERR_PTR(-ENODEV);
> > + }
> > +
> > + pdev = of_find_device_by_node(pcs_np);
> > + of_node_put(pcs_np);
> > + if (!pdev)
> > + return ERR_PTR(-EPROBE_DEFER);
> > +
> > + serdes = platform_get_drvdata(pdev);
> > + if (!serdes) {
> > + put_device(&pdev->dev);
> > + return ERR_PTR(-EPROBE_DEFER);
> > + }
> > +
> > + if (!serdes->xpcs.phys[port]) {
> > + put_device(&pdev->dev);
> > + return ERR_PTR(-EPROBE_DEFER);
> > + }
> > +
> > + return &serdes->xpcs.phys[port]->pcs;
> > +}
> > +EXPORT_SYMBOL(s32g_serdes_pcs_create);
> > +
> > +void s32g_serdes_pcs_destroy(struct phylink_pcs *pcs)
> > +{
> > + struct s32g_xpcs *xpcs = phylink_pcs_to_s32g_xpcs(pcs);
> > +
> > + put_device(xpcs->dev);
> > +}
> > +EXPORT_SYMBOL(s32g_serdes_pcs_destroy);
> > +
> > static const struct of_device_id s32g_serdes_match[] = {
> > {
> > .compatible = "nxp,s32g2-serdes",
> > diff --git a/include/linux/pcs/pcs-nxp-s32g-xpcs.h b/include/linux/pcs/pcs-nxp-s32g-xpcs.h
> > new file mode 100644
> > index 000000000000..96a0049b93a6
> > --- /dev/null
> > +++ b/include/linux/pcs/pcs-nxp-s32g-xpcs.h
> > @@ -0,0 +1,50 @@
> > +/* SPDX-License-Identifier: GPL-2.0 */
> > +/**
> > + * Copyright 2021-2026 NXP
> > + */
> > +#ifndef PCS_NXP_S32G_XPCS_H
> > +#define PCS_NXP_S32G_XPCS_H
> > +
> > +#include <linux/phylink.h>
> > +
> > +enum s32g_xpcs_shared {
> > + NOT_SHARED,
> > + PCIE_XPCS_1G,
> > + PCIE_XPCS_2G5,
> > +};
> > +
> > +enum s32g_xpcs_pll {
> > + XPCS_PLLA, /* Slow PLL */
> > + XPCS_PLLB, /* Fast PLL */
> > +};
> > +
> > +struct s32g_xpcs {
> > + void __iomem *base;
> > + struct device *dev;
> > + unsigned char id;
> > + struct regmap *regmap;
> > + enum s32g_xpcs_pll ref;
> > + bool ext_clk;
> > + bool mhz125;
> > + enum s32g_xpcs_shared pcie_shared;
> > + struct phylink_pcs pcs;
> > +};
> > +
> > +#define phylink_pcs_to_s32g_xpcs(pl_pcs) \
> > + container_of((pl_pcs), struct s32g_xpcs, pcs)
> > +
> > +int s32g_xpcs_init(struct s32g_xpcs *xpcs, struct device *dev,
> > + unsigned char id, void __iomem *base, bool ext_clk,
> > + unsigned long rate, enum s32g_xpcs_shared pcie_shared);
> > +int s32g_xpcs_init_plls(struct s32g_xpcs *xpcs);
> > +int s32g_xpcs_pre_pcie_2g5(struct s32g_xpcs *xpcs);
> > +void s32g_xpcs_vreset(struct s32g_xpcs *xpcs);
> > +int s32g_xpcs_wait_vreset(struct s32g_xpcs *xpcs);
> > +int s32g_xpcs_reset_rx(struct s32g_xpcs *xpcs);
> > +void s32g_xpcs_disable_an(struct s32g_xpcs *xpcs);
> > +
> > +struct phylink_pcs *s32g_serdes_pcs_create(struct device *dev, struct device_node *np);
> > +void s32g_serdes_pcs_destroy(struct phylink_pcs *pcs);
> > +
> > +#endif /* PCS_NXP_S32G_XPCS_H */
> > +
> > --
> > 2.43.0
> >
> >
>
>
> --
> RMK's Patch system: https://www.armlinux.org.uk/developer/patches/
> FTTP is here! 80Mbps down 10Mbps up. Decent connectivity at last!
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* Re: Lynx 10G SerDes Driver on my kernel
From: Tanjeff Moos @ 2026-02-05 16:47 UTC (permalink / raw)
To: Vladimir Oltean; +Cc: Sean Anderson, linux-phy@lists.infradead.org
In-Reply-To: <20251205124423.gddjli3jtqlvkrkv@skbuf>
Hello Vladimir,
I wish you a great year 2026!
Meanwhile I ported some of your code and I got my networking partially
running. Details follow, but first I have a question.
== Question ==
The lynx-10g driver disables the managed lanes in lynx_10g_init(), and
they are never enabled afterwards. Probably I didn't port the associated
code, but can't find where it is.
I modified lynx_10g_init() to leave the lane enabled, which solves the
problem, but this is a dirty hack.
Question: How is the lane supposed to be enabled?
== Status of my work ==
In case you are interested I provide some details here.
I ported only part of your code (tag lf-6.12.49-2.2.0), mainly the
lynx-10g driver plus some changes you did in pcs-lynx.c, phy-core.c,
guts.c, the dpaa drivers plus their associated header files. I
deliberately omitted code dealing with c72 autonegotiation (for
backplane), as I don't need it. I'm still working with kernel 6.6.119
plus OpenWRT patches and cannot easily upgrade.
So far, I have a 2.5 GbE PHY and can run the following modes:
- 100M : SGMII
- 1G: SGMII
- 2.5G: 2500BaseX
Switching forth and back the modes works seamlessly (with my dirty hack
to power on the lane!). But I didn't test RCW rewriting as it is not
needed in this scenario.
Kind regards so far, Tanjeff
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* [PATCH v3 5/5] phy: qcom: snps-femto-v2: Fix possible NULL-deref on early runtime suspend
From: Loic Poulain @ 2026-02-05 16:02 UTC (permalink / raw)
To: vkoul, kishon
Cc: linux-arm-msm, linux-phy, dmitry.baryshkov, neil.armstrong,
konrad.dybcio, Loic Poulain, Abel Vesa
In-Reply-To: <20260205160240.748371-1-loic.poulain@oss.qualcomm.com>
Enabling runtime PM before attaching the hsphy instance as driver data
can lead to a NULL pointer dereference in runtime PM callbacks that
expect valid driver data. There is a small window where the suspend
callback may run after PM runtime enabling and before runtime forbid.
Attach the hsphy instance as driver data before enabling runtime PM to
prevent NULL pointer dereference in runtime PM callbacks.
Reorder pm_runtime_enable() and pm_runtime_forbid() to prevent a
short window where an unnecessary runtime suspend can occur.
Use the devres-managed version to ensure PM runtime is symmetrically
disabled during driver removal for proper cleanup.
Fixes: 0d75f508a9d5 ("phy: qcom-snps: Add runtime suspend and resume handlers")
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
Reviewed-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
Reviewed-by: Abel Vesa <abel.vesa@oss.qualcomm.com>
Signed-off-by: Loic Poulain <loic.poulain@oss.qualcomm.com>
---
drivers/phy/qualcomm/phy-qcom-snps-femto-v2.c | 15 ++++++++-------
1 file changed, 8 insertions(+), 7 deletions(-)
diff --git a/drivers/phy/qualcomm/phy-qcom-snps-femto-v2.c b/drivers/phy/qualcomm/phy-qcom-snps-femto-v2.c
index eb0b0f61d98e..d1288a6c202e 100644
--- a/drivers/phy/qualcomm/phy-qcom-snps-femto-v2.c
+++ b/drivers/phy/qualcomm/phy-qcom-snps-femto-v2.c
@@ -599,13 +599,17 @@ static int qcom_snps_hsphy_probe(struct platform_device *pdev)
return dev_err_probe(dev, ret,
"failed to get regulator supplies\n");
- pm_runtime_set_active(dev);
- pm_runtime_enable(dev);
+ dev_set_drvdata(dev, hsphy);
+
/*
- * Prevent runtime pm from being ON by default. Users can enable
- * it using power/control in sysfs.
+ * Enable runtime PM support, but forbid it by default.
+ * Users can allow it again via the power/control attribute in sysfs.
*/
+ pm_runtime_set_active(dev);
pm_runtime_forbid(dev);
+ ret = devm_pm_runtime_enable(dev);
+ if (ret)
+ return ret;
generic_phy = devm_phy_create(dev, NULL, &qcom_snps_hsphy_gen_ops);
if (IS_ERR(generic_phy)) {
@@ -615,15 +619,12 @@ static int qcom_snps_hsphy_probe(struct platform_device *pdev)
}
hsphy->phy = generic_phy;
- dev_set_drvdata(dev, hsphy);
phy_set_drvdata(generic_phy, hsphy);
qcom_snps_hsphy_read_override_param_seq(dev);
phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
if (!IS_ERR(phy_provider))
dev_dbg(dev, "Registered Qcom-SNPS HS phy\n");
- else
- pm_runtime_disable(dev);
return PTR_ERR_OR_ZERO(phy_provider);
}
--
2.34.1
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply related
* [PATCH v3 0/5] phy: qcom: Fix possible NULL-deref and runtime PM race conditions
From: Loic Poulain @ 2026-02-05 16:02 UTC (permalink / raw)
To: vkoul, kishon
Cc: linux-arm-msm, linux-phy, dmitry.baryshkov, neil.armstrong,
konrad.dybcio, Loic Poulain
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1: Type: text/plain; charset=y, Size: 1532 bytes --]
Address potential NULL pointer dereferences and race conditions related
to runtime PM in several Qualcomm PHY drivers. In all cases, enabling
runtime PM before the PHY instance is fully initialized can lead to
crashes during early runtime suspend callbacks.
- Attach driver data before enabling runtime PM.
- Introduce initialization flags where needed to avoid dereferencing
uninitialized pointers.
- Reorder pm_runtime_enable() and pm_runtime_forbid() calls to prevent
unnecessary suspend/resume cycles during driver probe.
- Use devres-managed PM runtime helpers for proper cleanup.
Changes in V3:
Rebase on next and remove 2/6 (obsolete)
Changes in V2:
Split patches 2/4 and 3/4 so that the null‑pointer dereference fix and
the runtime‑PM enable/forbid reordering are logically separated.
Loic Poulain (5):
phy: qcom: qmp-combo: Prevent unnecessary PM runtime suspend at boot
phy: qcom: qmp-usbc: Prevent unnecessary PM runtime suspend at boot
phy: qcom: qmp-usb-legacy: Fix possible NULL-deref on early runtime
suspend
phy: qcom: qmp-usb-legacy: Prevent unnecessary PM runtime suspend at
boot
phy: qcom: snps-femto-v2: Fix possible NULL-deref on early runtime
suspend
drivers/phy/qualcomm/phy-qcom-qmp-combo.c | 10 ++++-----
.../phy/qualcomm/phy-qcom-qmp-usb-legacy.c | 21 ++++++++++++-------
drivers/phy/qualcomm/phy-qcom-qmp-usbc.c | 10 ++++-----
drivers/phy/qualcomm/phy-qcom-snps-femto-v2.c | 15 ++++++-------
4 files changed, 32 insertions(+), 24 deletions(-)
--
2.34.1
[-- Attachment #2: Type: text/plain, Size: 112 bytes --]
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* [PATCH v3 4/5] phy: qcom: qmp-usb-legacy: Prevent unnecessary PM runtime suspend at boot
From: Loic Poulain @ 2026-02-05 16:02 UTC (permalink / raw)
To: vkoul, kishon
Cc: linux-arm-msm, linux-phy, dmitry.baryshkov, neil.armstrong,
konrad.dybcio, Loic Poulain, Abel Vesa
In-Reply-To: <20260205160240.748371-1-loic.poulain@oss.qualcomm.com>
There is a small window where the device can suspend after
pm_runtime_enable() and before pm_runtime_forbid(), causing an
unnecessary suspend/resume cycle while the PHY is not yet registered.
Move pm_runtime_forbid() before pm_runtime_enable() to eliminate
this race.
Reviewed-by: Abel Vesa <abel.vesa@oss.qualcomm.com>
Reviewed-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
Signed-off-by: Loic Poulain <loic.poulain@oss.qualcomm.com>
---
drivers/phy/qualcomm/phy-qcom-qmp-usb-legacy.c | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/drivers/phy/qualcomm/phy-qcom-qmp-usb-legacy.c b/drivers/phy/qualcomm/phy-qcom-qmp-usb-legacy.c
index 258e0e966a02..73439d223f1d 100644
--- a/drivers/phy/qualcomm/phy-qcom-qmp-usb-legacy.c
+++ b/drivers/phy/qualcomm/phy-qcom-qmp-usb-legacy.c
@@ -1284,15 +1284,15 @@ static int qmp_usb_legacy_probe(struct platform_device *pdev)
if (ret)
goto err_node_put;
+ /*
+ * Enable runtime PM support, but forbid it by default.
+ * Users can allow it again via the power/control attribute in sysfs.
+ */
pm_runtime_set_active(dev);
+ pm_runtime_forbid(dev);
ret = devm_pm_runtime_enable(dev);
if (ret)
goto err_node_put;
- /*
- * Prevent runtime pm from being ON by default. Users can enable
- * it using power/control in sysfs.
- */
- pm_runtime_forbid(dev);
ret = phy_pipe_clk_register(qmp, np);
if (ret)
--
2.34.1
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply related
* [PATCH v3 3/5] phy: qcom: qmp-usb-legacy: Fix possible NULL-deref on early runtime suspend
From: Loic Poulain @ 2026-02-05 16:02 UTC (permalink / raw)
To: vkoul, kishon
Cc: linux-arm-msm, linux-phy, dmitry.baryshkov, neil.armstrong,
konrad.dybcio, Loic Poulain, Abel Vesa
In-Reply-To: <20260205160240.748371-1-loic.poulain@oss.qualcomm.com>
There is a small window where the runtime suspend callback may run
after pm_runtime_enable() and before pm_runtime_forbid(). In this
case, a crash occurs because runtime suspend/resume dereferences
qmp->phy pointer, which is not yet initialized:
`if (!qmp->phy->init_count) {`
This can also happen if user re-enables runtime-pm via the sysfs
attribute before qmp phy is initialized.
Similarly to other qcom phy drivers, introduce a qmp->phy_initialized
variable that can be used to avoid relying on the possibly uninitialized
phy pointer.
Fixes: e464a3180a43 ("phy: qcom-qmp-usb: split off the legacy USB+dp_com support")
Reviewed-by: Abel Vesa <abel.vesa@oss.qualcomm.com>
Signed-off-by: Loic Poulain <loic.poulain@oss.qualcomm.com>
---
drivers/phy/qualcomm/phy-qcom-qmp-usb-legacy.c | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/drivers/phy/qualcomm/phy-qcom-qmp-usb-legacy.c b/drivers/phy/qualcomm/phy-qcom-qmp-usb-legacy.c
index 8bf951b0490c..258e0e966a02 100644
--- a/drivers/phy/qualcomm/phy-qcom-qmp-usb-legacy.c
+++ b/drivers/phy/qualcomm/phy-qcom-qmp-usb-legacy.c
@@ -541,6 +541,7 @@ struct qmp_usb {
struct regulator_bulk_data *vregs;
enum phy_mode mode;
+ bool phy_initialized;
struct phy *phy;
@@ -895,6 +896,7 @@ static int qmp_usb_legacy_power_off(struct phy *phy)
static int qmp_usb_legacy_enable(struct phy *phy)
{
+ struct qmp_usb *qmp = phy_get_drvdata(phy);
int ret;
ret = qmp_usb_legacy_init(phy);
@@ -904,14 +906,19 @@ static int qmp_usb_legacy_enable(struct phy *phy)
ret = qmp_usb_legacy_power_on(phy);
if (ret)
qmp_usb_legacy_exit(phy);
+ else
+ qmp->phy_initialized = true;
return ret;
}
static int qmp_usb_legacy_disable(struct phy *phy)
{
+ struct qmp_usb *qmp = phy_get_drvdata(phy);
int ret;
+ qmp->phy_initialized = false;
+
ret = qmp_usb_legacy_power_off(phy);
if (ret)
return ret;
@@ -988,7 +995,7 @@ static int __maybe_unused qmp_usb_legacy_runtime_suspend(struct device *dev)
dev_vdbg(dev, "Suspending QMP phy, mode:%d\n", qmp->mode);
- if (!qmp->phy->init_count) {
+ if (!qmp->phy_initialized) {
dev_vdbg(dev, "PHY not initialized, bailing out\n");
return 0;
}
@@ -1009,7 +1016,7 @@ static int __maybe_unused qmp_usb_legacy_runtime_resume(struct device *dev)
dev_vdbg(dev, "Resuming QMP phy, mode:%d\n", qmp->mode);
- if (!qmp->phy->init_count) {
+ if (!qmp->phy_initialized) {
dev_vdbg(dev, "PHY not initialized, bailing out\n");
return 0;
}
--
2.34.1
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply related
* [PATCH v3 1/5] phy: qcom: qmp-combo: Prevent unnecessary PM runtime suspend at boot
From: Loic Poulain @ 2026-02-05 16:02 UTC (permalink / raw)
To: vkoul, kishon
Cc: linux-arm-msm, linux-phy, dmitry.baryshkov, neil.armstrong,
konrad.dybcio, Loic Poulain, Abel Vesa
In-Reply-To: <20260205160240.748371-1-loic.poulain@oss.qualcomm.com>
There is a small window where the device can suspend after
pm_runtime_enable() and before pm_runtime_forbid(), causing an
unnecessary suspend/resume cycle while the PHY is not yet registered.
Move pm_runtime_forbid() before pm_runtime_enable() to eliminate
this race.
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
Reviewed-by: Abel Vesa <abel.vesa@oss.qualcomm.com>
Reviewed-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
Signed-off-by: Loic Poulain <loic.poulain@oss.qualcomm.com>
---
drivers/phy/qualcomm/phy-qcom-qmp-combo.c | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/drivers/phy/qualcomm/phy-qcom-qmp-combo.c b/drivers/phy/qualcomm/phy-qcom-qmp-combo.c
index 93f1aa10d400..c3661872bb7a 100644
--- a/drivers/phy/qualcomm/phy-qcom-qmp-combo.c
+++ b/drivers/phy/qualcomm/phy-qcom-qmp-combo.c
@@ -4932,15 +4932,15 @@ static int qmp_combo_probe(struct platform_device *pdev)
if (ret)
goto err_node_put;
+ /*
+ * Enable runtime PM support, but forbid it by default.
+ * Users can allow it again via the power/control attribute in sysfs.
+ */
pm_runtime_set_active(dev);
+ pm_runtime_forbid(dev);
ret = devm_pm_runtime_enable(dev);
if (ret)
goto err_node_put;
- /*
- * Prevent runtime pm from being ON by default. Users can enable
- * it using power/control in sysfs.
- */
- pm_runtime_forbid(dev);
ret = qmp_combo_register_clocks(qmp, usb_np, dp_np);
if (ret)
--
2.34.1
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply related
* [PATCH v3 2/5] phy: qcom: qmp-usbc: Prevent unnecessary PM runtime suspend at boot
From: Loic Poulain @ 2026-02-05 16:02 UTC (permalink / raw)
To: vkoul, kishon
Cc: linux-arm-msm, linux-phy, dmitry.baryshkov, neil.armstrong,
konrad.dybcio, Loic Poulain, Abel Vesa
In-Reply-To: <20260205160240.748371-1-loic.poulain@oss.qualcomm.com>
There is a small window where the device can suspend after
pm_runtime_enable() and before pm_runtime_forbid(), causing an
unnecessary suspend/resume cycle while the PHY is not yet registered.
Move pm_runtime_forbid() before pm_runtime_enable() to eliminate
this race.
Signed-off-by: Loic Poulain <loic.poulain@oss.qualcomm.com>
Reviewed-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
Reviewed-by: Abel Vesa <abel.vesa@oss.qualcomm.com>
---
drivers/phy/qualcomm/phy-qcom-qmp-usbc.c | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/drivers/phy/qualcomm/phy-qcom-qmp-usbc.c b/drivers/phy/qualcomm/phy-qcom-qmp-usbc.c
index 14feb77789b3..90ea6ca64026 100644
--- a/drivers/phy/qualcomm/phy-qcom-qmp-usbc.c
+++ b/drivers/phy/qualcomm/phy-qcom-qmp-usbc.c
@@ -1959,15 +1959,15 @@ static int qmp_usbc_probe(struct platform_device *pdev)
if (ret)
goto err_node_put;
+ /*
+ * Enable runtime PM support, but forbid it by default.
+ * Users can allow it again via the power/control attribute in sysfs.
+ */
pm_runtime_set_active(dev);
+ pm_runtime_forbid(dev);
ret = devm_pm_runtime_enable(dev);
if (ret)
goto err_node_put;
- /*
- * Prevent runtime pm from being ON by default. Users can enable
- * it using power/control in sysfs.
- */
- pm_runtime_forbid(dev);
ret = qmp_usbc_register_clocks(qmp, np);
if (ret)
--
2.34.1
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply related
* Re: [PATCH v2 2/6] phy: qcom: qmp-usbc: Fix possible NULL-deref on early runtime suspend
From: Loic Poulain @ 2026-02-05 15:57 UTC (permalink / raw)
To: Konrad Dybcio
Cc: vkoul, kishon, linux-arm-msm, linux-phy, dmitry.baryshkov,
neil.armstrong
In-Reply-To: <3facbb23-80ad-4648-b81c-73f4608129f5@oss.qualcomm.com>
On Thu, Jan 22, 2026 at 4:00 PM Konrad Dybcio
<konrad.dybcio@oss.qualcomm.com> wrote:
>
> On 1/21/26 3:28 PM, Loic Poulain wrote:
> > There is a small window where the runtime suspend callback may run
> > after pm_runtime_enable() and before pm_runtime_forbid(). In this
> > case, a crash occurs because runtime suspend/resume dereferences
> > qmp->phy pointer, which is not yet initialized:
> > `if (!qmp->phy->init_count) {`
> >
> > This can also occur if user re-enables runtime-pm via the sysfs
> > attribute before qmp phy is initialized.
> >
> > Use qmp->usb_init_count instead of qmp->phy->init_count to avoid
> > depending on the possibly uninitialized phy pointer.
> >
> > Fixes: 19281571a4d5 ("phy: qcom: qmp-usb: split USB-C PHY driver")
> > Signed-off-by: Loic Poulain <loic.poulain@oss.qualcomm.com>
> > ---
> > drivers/phy/qualcomm/phy-qcom-qmp-usbc.c | 4 ++--
> > 1 file changed, 2 insertions(+), 2 deletions(-)
> >
> > diff --git a/drivers/phy/qualcomm/phy-qcom-qmp-usbc.c b/drivers/phy/qualcomm/phy-qcom-qmp-usbc.c
> > index 5e7fcb26744a..edfaa14db967 100644
> > --- a/drivers/phy/qualcomm/phy-qcom-qmp-usbc.c
> > +++ b/drivers/phy/qualcomm/phy-qcom-qmp-usbc.c
> > @@ -690,7 +690,7 @@ static int __maybe_unused qmp_usbc_runtime_suspend(struct device *dev)
> >
> > dev_vdbg(dev, "Suspending QMP phy, mode:%d\n", qmp->mode);
> >
> > - if (!qmp->phy->init_count) {
> > + if (!qmp->usb_init_count) {
>
> This function changed a month ago, please rebase against next
>
> Although I believe this patch becomes unnecessary with 3 in the picture.
Thanks. I can indeed remove this patch from the series
> I suppose the case that you mention in the commit message, however
> improbable, could be fixed by moving the pm call to after devm_phy_create
For whatever scheduling effect, this behavior has been seen quite
regularly (~1/50 boots) during stress‑test scenarios with other PHY
driver (qcom-qusb2).
As Dmitry noted earlier, PM must be enabled before calling
devm_phy_create(), because phy_create() relies on the PM‑enabled state
to configure its own runtime PM behavior.
>
> Although we'd then rely on devlink to make sure a consumer doesn't snatch
> the reference halfway through .probe...
>
> Konrad
Cheers,
Loic
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* Re: [PATCH 1/8] dt-bindings: pci: xilinx-nwl: Add resets
From: Sean Anderson @ 2026-02-05 15:47 UTC (permalink / raw)
To: Pandey, Radhey Shyam, Laurent Pinchart, Vinod Koul,
linux-phy@lists.infradead.org
Cc: Krzysztof Wilczyński, Lorenzo Pieralisi,
linux-kernel@vger.kernel.org, Simek, Michal,
linux-arm-kernel@lists.infradead.org, linux-pci@vger.kernel.org,
Neil Armstrong, Rob Herring, Havalige, Thippeswamy,
Manivannan Sadhasivam, Bjorn Helgaas, Conor Dooley,
Krzysztof Kozlowski, devicetree@vger.kernel.org
In-Reply-To: <MN0PR12MB59537BC8B60D76CCE0B1391CB798A@MN0PR12MB5953.namprd12.prod.outlook.com>
On 2/4/26 03:32, Pandey, Radhey Shyam wrote:
> [AMD Official Use Only - AMD Internal Distribution Only]
>
>> -----Original Message-----
>> From: Sean Anderson <sean.anderson@linux.dev>
>> Sent: Tuesday, February 3, 2026 5:51 AM
>> To: Laurent Pinchart <laurent.pinchart@ideasonboard.com>; Vinod Koul
>> <vkoul@kernel.org>; linux-phy@lists.infradead.org
>> Cc: Krzysztof Wilczyński <kwilczynski@kernel.org>; Lorenzo Pieralisi
>> <lpieralisi@kernel.org>; Pandey, Radhey Shyam
>> <radhey.shyam.pandey@amd.com>; linux-kernel@vger.kernel.org; Simek, Michal
>> <michal.simek@amd.com>; linux-arm-kernel@lists.infradead.org; linux-
>> pci@vger.kernel.org; Neil Armstrong <neil.armstrong@linaro.org>; Rob Herring
>> <robh@kernel.org>; Havalige, Thippeswamy <thippeswamy.havalige@amd.com>;
>> Manivannan Sadhasivam <mani@kernel.org>; Bjorn Helgaas
>> <bhelgaas@google.com>; Sean Anderson <sean.anderson@linux.dev>; Conor
>> Dooley <conor+dt@kernel.org>; Krzysztof Kozlowski <krzk+dt@kernel.org>;
>> devicetree@vger.kernel.org
>> Subject: [PATCH 1/8] dt-bindings: pci: xilinx-nwl: Add resets
>>
>> Add resets so we can hold the bridge in reset while we perform phy calibration.
>
> Seems like this should a required property?
It's optional as it does not exist in previous versions of the
devicetree. In the past I have received pushback against making these
sort of properties required.
If the resets don't exist we just don't assert them and assume the
bootloader has deasserted them.
--Sean
> Rest looks fine to me.
>
>>
>> Signed-off-by: Sean Anderson <sean.anderson@linux.dev>
>> ---
>>
>> .../devicetree/bindings/pci/xlnx,nwl-pcie.yaml | 17 +++++++++++++++++
>> 1 file changed, 17 insertions(+)
>>
>> diff --git a/Documentation/devicetree/bindings/pci/xlnx,nwl-pcie.yaml
>> b/Documentation/devicetree/bindings/pci/xlnx,nwl-pcie.yaml
>> index 9de3c09efb6e..7efb3dd9955f 100644
>> --- a/Documentation/devicetree/bindings/pci/xlnx,nwl-pcie.yaml
>> +++ b/Documentation/devicetree/bindings/pci/xlnx,nwl-pcie.yaml
>> @@ -69,6 +69,18 @@ properties:
>> power-domains:
>> maxItems: 1
>>
>> + resets:
>> + maxItems: 3
>> +
>> + reset-names:
>> + items:
>> + - description: APB register block reset
>> + const: cfg
>> + - description: AXI-PCIe bridge reset
>> + const: bridge
>> + - description: PCIe MAC reset
>> + const: ctrl
>> +
>> iommus:
>> maxItems: 1
>>
>> @@ -117,6 +129,7 @@ examples:
>> #include <dt-bindings/interrupt-controller/irq.h>
>> #include <dt-bindings/phy/phy.h>
>> #include <dt-bindings/power/xlnx-zynqmp-power.h>
>> + #include <dt-bindings/reset/xlnx-zynqmp-resets.h>
>> soc {
>> #address-cells = <2>;
>> #size-cells = <2>;
>> @@ -146,6 +159,10 @@ examples:
>> msi-parent = <&nwl_pcie>;
>> phys = <&psgtr 0 PHY_TYPE_PCIE 0 0>;
>> power-domains = <&zynqmp_firmware PD_PCIE>;
>> + resets = <&zynqmp_reset ZYNQMP_RESET_PCIE_CFG>,
>> + <&zynqmp_reset ZYNQMP_RESET_PCIE_BRIDGE>,
>> + <&zynqmp_reset ZYNQMP_RESET_PCIE_CTRL>;
>> + reset-names = "cfg", "bridge", "ctrl";
>> iommus = <&smmu 0x4d0>;
>> pcie_intc: legacy-interrupt-controller {
>> interrupt-controller;
>> --
>> 2.35.1.1320.gc452695387.dirty
>
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* [PATCH net-next] doc: generic phy: update generic PHY documentation
From: Russell King (Oracle) @ 2026-02-05 14:56 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong; +Cc: Jonathan Corbet, linux-doc, linux-phy
Update the generic PHY documentation as a result of the discussion for
the s32g submission.
Link: https://lore.kernel.org/r/aXtvDn_-pCuKPrnf@vaman
Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
---
I didn't get any replies to my follow-up question to Vinod:
Please also indicate in the documentation whether changing the submode
of the serdes (particularly for ethernet) is permitted without doing a
phy_power_down()..phy_power_up() dance around the phy_set_mode_ext()
call.
I also didn't get any response to:
For drivers such as stmmac, it will be important that details such as
whether phy_est_mode*() can be called with the PHY powered on are
riveted down and not left up to the generic PHY driver author - without
that, generic PHYs basically aren't usable from SoC/platform
independent code, and stmmac has bazillions of platform specific glue
already because of (a) bad code structuring and (b) lack of
generalisation through standardised interfaces that abstract platform
differences.
I want to be able for core stmmac code, or even phylink code (which
is even more platform generic) to be able to make use of generic PHY
stuff, but if the calls that can be made into generic PHY are platform
dependent, that is a blocking issue against that, and makes me question
why we have the generic PHY subsystem... it's not very generic if it
exposes the differences of each implementation to users of its
interfaces.
Documentation/driver-api/phy/phy.rst | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/Documentation/driver-api/phy/phy.rst b/Documentation/driver-api/phy/phy.rst
index 719a2b3fd2ab..cf73e4fb0951 100644
--- a/Documentation/driver-api/phy/phy.rst
+++ b/Documentation/driver-api/phy/phy.rst
@@ -142,6 +142,7 @@ Order of API calls
[devm_][of_]phy_get()
phy_init()
+ [phy_set_mode[_ext]()]
phy_power_on()
[phy_set_mode[_ext]()]
...
@@ -154,7 +155,7 @@ but controllers should always call these functions to be compatible with other
PHYs. Some PHYs may require :c:func:`phy_set_mode <phy_set_mode_ext>`, while
others may use a default mode (typically configured via devicetree or other
firmware). For compatibility, you should always call this function if you know
-what mode you will be using. Generally, this function should be called after
+what mode you will be using. Generally, this function should be called before
:c:func:`phy_power_on`, although some PHY drivers may allow it at any time.
Releasing a reference to the PHY
--
2.47.3
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply related
* Re: [PATCH v9 1/2] dt-bindings: phy: eswin: Document the EIC7700 SoC SATA PHY
From: Krzysztof Kozlowski @ 2026-02-05 13:41 UTC (permalink / raw)
To: Yulin Lu
Cc: vkoul, neil.armstrong, robh, krzk+dt, conor+dt, p.zabel,
linux-phy, devicetree, linux-kernel, ningyu, linmin, fenglin
In-Reply-To: <20260205082129.1482-1-luyulin@eswincomputing.com>
On Thu, Feb 05, 2026 at 04:21:29PM +0800, Yulin Lu wrote:
> Document the SATA PHY on the EIC7700 SoC platform,
> describing its usage.
>
> Signed-off-by: Yulin Lu <luyulin@eswincomputing.com>
> ---
> .../bindings/phy/eswin,eic7700-sata-phy.yaml | 92 +++++++++++++++++++
> 1 file changed, 92 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/phy/eswin,eic7700-sata-phy.yaml
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
Best regards,
Krzysztof
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* [PATCH] phy: intel: phy-intel-lgm-combo: Add missing boot trigger
From: Florian Eckert @ 2026-02-05 12:02 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Dilip Kota
Cc: linux-phy, linux-kernel, Florian Eckert, Florian Eckert
In the current implementation, the PHY firmware is not started on the LGM.
The PCIe RC cannot establish a connection to the PCIe EP via LTSSM, because
the PCIe RC on the PHY is never started. Setting the bit 1 in the
PCIE_PHY_SRAM_CSR app register boots the PHY firmware.
This changes are based on patched kernel sources of the MaxLinear SDK,
which can be found at https://github.com/maxlinear/linux
Signed-off-by: Florian Eckert <fe@dev.tdt.de>
---
drivers/phy/intel/phy-intel-lgm-combo.c | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/drivers/phy/intel/phy-intel-lgm-combo.c b/drivers/phy/intel/phy-intel-lgm-combo.c
index 9ee3cf61cdd00439f6a9b76c715aec0fa95c8a15..61690d418a5632d4b0c685116a443edba1fe8363 100644
--- a/drivers/phy/intel/phy-intel-lgm-combo.c
+++ b/drivers/phy/intel/phy-intel-lgm-combo.c
@@ -24,6 +24,9 @@
#define PAD_DIS_CFG 0x174
+#define PCIE_PHY_SRAM_CSR 0x08
+#define PCIE_PHY_SRAM_LD_DONE BIT(1)
+
#define PCS_XF_ATE_OVRD_IN_2 0x3008
#define ADAPT_REQ_MSK GENMASK(5, 4)
@@ -345,6 +348,16 @@ static int intel_cbphy_init(struct phy *phy)
cbphy->init_cnt++;
+ combo_phy_w32_off_mask(cbphy->app_base, PCIE_PHY_SRAM_CSR,
+ PCIE_PHY_SRAM_LD_DONE,
+ FIELD_PREP(PCIE_PHY_SRAM_LD_DONE, 1));
+
+ mutex_unlock(&cbphy->lock);
+
+ /* 15ms is required for the FW to take effect after load */
+ mdelay(15);
+ return 0;
+
err:
mutex_unlock(&cbphy->lock);
---
base-commit: 18f7fcd5e69a04df57b563360b88be72471d6b62
change-id: 20260205-phy-intel-lgm-combo-d79252514393
Best regards,
--
Florian Eckert <fe@dev.tdt.de>
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply related
* Re: [PATCH 6/8] mmc: host: axiado: add AX3000 eMMC PHY support to sdhci-of-arasan
From: Krzysztof Kozlowski @ 2026-02-05 9:47 UTC (permalink / raw)
To: Tzu-Hao Wei
Cc: SriNavmani A, Prasad Bolisetty, Vinod Koul, Neil Armstrong,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Harshit Shah,
Ulf Hansson, Adrian Hunter, Michal Simek, linux-phy, devicetree,
linux-arm-kernel, linux-kernel, linux-mmc
In-Reply-To: <c96d21ab-2a76-46ca-8392-6edde3b9ce2d@axiado.com>
On 05/02/2026 04:36, Tzu-Hao Wei wrote:
> On 12/23/2025 10:34 PM, Krzysztof Kozlowski wrote:
>>> @@ -2019,15 +2023,17 @@ static int sdhci_arasan_probe(struct platform_device *pdev)
>>> goto unreg_clk;
>>> }
>>>
>>> - host->mmc_host_ops.hs400_enhanced_strobe =
>>> + if (!of_device_is_compatible(np, "axiado,ax3000-sdhci-5.1-emmc")) {
>>
>> No, don't sprinkle compatibles all around. You should use driver match
>> data for this.
>>
>> Best regards,
>> Krzysztof
>>
> Thanks for suggestion. The new patch uses driver match data instead of sprinkling compatibles.
You replied to a review from more than a month ago, even though I gave
you review after one day. I don't have anything of this in my inbox, so
in case you asked of something - I still expect all feedback to be
implemented.
Best regards,
Krzysztof
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* [PATCH 2/3] phy: qcom: edp: Add per-version LDO configuration callback
From: Yongxing Mou @ 2026-02-05 9:20 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong
Cc: linux-arm-msm, linux-phy, linux-kernel, Yongxing Mou
In-Reply-To: <20260205-edp_phy-v1-0-231882bbf3f1@oss.qualcomm.com>
Introduce the com_ldo_config callback to support per‑PHY LDO
configuration.
Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
---
drivers/phy/qualcomm/phy-qcom-edp.c | 86 ++++++++++++++++++++++++++++++++-----
1 file changed, 76 insertions(+), 10 deletions(-)
diff --git a/drivers/phy/qualcomm/phy-qcom-edp.c b/drivers/phy/qualcomm/phy-qcom-edp.c
index 10cbb7d9a8a0..388226dbad7f 100644
--- a/drivers/phy/qualcomm/phy-qcom-edp.c
+++ b/drivers/phy/qualcomm/phy-qcom-edp.c
@@ -81,6 +81,7 @@ struct phy_ver_ops {
int (*com_clk_fwd_cfg)(const struct qcom_edp *edp);
int (*com_configure_pll)(const struct qcom_edp *edp);
int (*com_configure_ssc)(const struct qcom_edp *edp);
+ int (*com_ldo_config)(const struct qcom_edp *edp);
};
struct qcom_edp_phy_cfg {
@@ -273,7 +274,7 @@ static int qcom_edp_set_voltages(struct qcom_edp *edp, const struct phy_configur
const struct qcom_edp_swing_pre_emph_cfg *cfg = edp->cfg->swing_pre_emph_cfg;
unsigned int v_level = 0;
unsigned int p_level = 0;
- u8 ldo_config;
+ int ret;
u8 swing;
u8 emph;
int i;
@@ -300,13 +301,13 @@ static int qcom_edp_set_voltages(struct qcom_edp *edp, const struct phy_configur
if (swing == 0xff || emph == 0xff)
return -EINVAL;
- ldo_config = edp->is_edp ? 0x0 : 0x1;
+ ret = edp->cfg->ver_ops->com_ldo_config(edp);
+ if (ret)
+ return ret;
- writel(ldo_config, edp->tx0 + TXn_LDO_CONFIG);
writel(swing, edp->tx0 + TXn_TX_DRV_LVL);
writel(emph, edp->tx0 + TXn_TX_EMP_POST1_LVL);
- writel(ldo_config, edp->tx1 + TXn_LDO_CONFIG);
writel(swing, edp->tx1 + TXn_TX_DRV_LVL);
writel(emph, edp->tx1 + TXn_TX_EMP_POST1_LVL);
@@ -530,6 +531,52 @@ static int qcom_edp_com_configure_pll_v4(const struct qcom_edp *edp)
return 0;
}
+static int qcom_edp_ldo_config_v3(const struct qcom_edp *edp)
+{
+ const struct phy_configure_opts_dp *dp_opts = &edp->dp_opts;
+ u32 ldo_config;
+
+ if (!edp->is_edp)
+ ldo_config = 0x0;
+ else if (dp_opts->link_rate <= 2700)
+ ldo_config = 0x81;
+ else
+ ldo_config = 0x41;
+
+ writel(ldo_config, edp->tx0 + TXn_LDO_CONFIG);
+ writel(ldo_config, edp->tx1 + TXn_LDO_CONFIG);
+
+ return 0;
+}
+
+static int qcom_edp_ldo_config_v4(const struct qcom_edp *edp)
+{
+ const struct phy_configure_opts_dp *dp_opts = &edp->dp_opts;
+ u32 ldo_config;
+
+ if (!edp->is_edp)
+ ldo_config = 0x0;
+ else if (dp_opts->link_rate <= 2700)
+ ldo_config = 0xC1;
+ else
+ ldo_config = 0x81;
+
+ writel(ldo_config, edp->tx0 + TXn_LDO_CONFIG);
+ writel(ldo_config, edp->tx1 + TXn_LDO_CONFIG);
+
+ return 0;
+}
+
+static const struct phy_ver_ops qcom_edp_phy_ops_v3 = {
+ .com_power_on = qcom_edp_phy_power_on_v4,
+ .com_resetsm_cntrl = qcom_edp_phy_com_resetsm_cntrl_v4,
+ .com_bias_en_clkbuflr = qcom_edp_com_bias_en_clkbuflr_v4,
+ .com_clk_fwd_cfg = qcom_edp_com_clk_fwd_cfg_v4,
+ .com_configure_pll = qcom_edp_com_configure_pll_v4,
+ .com_configure_ssc = qcom_edp_com_configure_ssc_v4,
+ .com_ldo_config = qcom_edp_ldo_config_v3,
+};
+
static const struct phy_ver_ops qcom_edp_phy_ops_v4 = {
.com_power_on = qcom_edp_phy_power_on_v4,
.com_resetsm_cntrl = qcom_edp_phy_com_resetsm_cntrl_v4,
@@ -537,6 +584,7 @@ static const struct phy_ver_ops qcom_edp_phy_ops_v4 = {
.com_clk_fwd_cfg = qcom_edp_com_clk_fwd_cfg_v4,
.com_configure_pll = qcom_edp_com_configure_pll_v4,
.com_configure_ssc = qcom_edp_com_configure_ssc_v4,
+ .com_ldo_config = qcom_edp_ldo_config_v4,
};
static const struct qcom_edp_phy_cfg sa8775p_dp_phy_cfg = {
@@ -550,7 +598,7 @@ static const struct qcom_edp_phy_cfg sa8775p_dp_phy_cfg = {
static const struct qcom_edp_phy_cfg sc7280_dp_phy_cfg = {
.aux_cfg = edp_phy_aux_cfg_v4,
.vco_div_cfg = edp_phy_vco_div_cfg_v4,
- .ver_ops = &qcom_edp_phy_ops_v4,
+ .ver_ops = &qcom_edp_phy_ops_v3,
};
static const struct qcom_edp_phy_cfg sc8280xp_dp_phy_cfg = {
@@ -733,12 +781,31 @@ static int qcom_edp_com_configure_pll_v6(const struct qcom_edp *edp)
return 0;
}
+static int qcom_edp_ldo_config_v6(const struct qcom_edp *edp)
+{
+ const struct phy_configure_opts_dp *dp_opts = &edp->dp_opts;
+ u32 ldo_config;
+
+ if (!edp->is_edp)
+ ldo_config = 0x0;
+ else if (dp_opts->link_rate <= 2700)
+ ldo_config = 0x51;
+ else
+ ldo_config = 0x91;
+
+ writel(ldo_config, edp->tx0 + TXn_LDO_CONFIG);
+ writel(ldo_config, edp->tx1 + TXn_LDO_CONFIG);
+
+ return 0;
+}
+
static const struct phy_ver_ops qcom_edp_phy_ops_v6 = {
.com_power_on = qcom_edp_phy_power_on_v6,
.com_resetsm_cntrl = qcom_edp_phy_com_resetsm_cntrl_v6,
.com_bias_en_clkbuflr = qcom_edp_com_bias_en_clkbuflr_v6,
.com_configure_pll = qcom_edp_com_configure_pll_v6,
.com_configure_ssc = qcom_edp_com_configure_ssc_v6,
+ .com_ldo_config = qcom_edp_ldo_config_v6,
};
static struct qcom_edp_phy_cfg x1e80100_phy_cfg = {
@@ -918,6 +985,7 @@ static const struct phy_ver_ops qcom_edp_phy_ops_v8 = {
.com_clk_fwd_cfg = qcom_edp_com_clk_fwd_cfg_v8,
.com_configure_pll = qcom_edp_com_configure_pll_v8,
.com_configure_ssc = qcom_edp_com_configure_ssc_v8,
+ .com_ldo_config = qcom_edp_ldo_config_v6,
};
static struct qcom_edp_phy_cfg glymur_phy_cfg = {
@@ -932,7 +1000,6 @@ static int qcom_edp_phy_power_on(struct phy *phy)
const struct qcom_edp *edp = phy_get_drvdata(phy);
u32 bias0_en, drvr0_en, bias1_en, drvr1_en;
unsigned long pixel_freq;
- u8 ldo_config = 0x0;
int ret;
u32 val;
u8 cfg1;
@@ -941,11 +1008,10 @@ static int qcom_edp_phy_power_on(struct phy *phy)
if (ret)
return ret;
- if (edp->cfg->swing_pre_emph_cfg && !edp->is_edp)
- ldo_config = 0x1;
+ ret = edp->cfg->ver_ops->com_ldo_config(edp);
+ if (ret)
+ return ret;
- writel(ldo_config, edp->tx0 + TXn_LDO_CONFIG);
- writel(ldo_config, edp->tx1 + TXn_LDO_CONFIG);
writel(0x00, edp->tx0 + TXn_LANE_MODE_1);
writel(0x00, edp->tx1 + TXn_LANE_MODE_1);
--
2.43.0
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply related
* [PATCH 3/3] phy: qcom: edp: Add eDP phy mode switch support
From: Yongxing Mou @ 2026-02-05 9:20 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong
Cc: linux-arm-msm, linux-phy, linux-kernel, Yongxing Mou
In-Reply-To: <20260205-edp_phy-v1-0-231882bbf3f1@oss.qualcomm.com>
Add DP/eDP switch support by splitting the PHY swing/pre-emphasis tables
into separate DP and eDP configurations. This allows the driver to select
the correct table based on the is_edp flag.
Add a dedicated table for the SC7280/glymur platforms, as they are not
compatible with the others.
Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
---
drivers/phy/qualcomm/phy-qcom-edp.c | 72 ++++++++++++++++++++++++++++---------
1 file changed, 56 insertions(+), 16 deletions(-)
diff --git a/drivers/phy/qualcomm/phy-qcom-edp.c b/drivers/phy/qualcomm/phy-qcom-edp.c
index 388226dbad7f..85caa869a8c0 100644
--- a/drivers/phy/qualcomm/phy-qcom-edp.c
+++ b/drivers/phy/qualcomm/phy-qcom-edp.c
@@ -88,7 +88,8 @@ struct qcom_edp_phy_cfg {
bool is_edp;
const u8 *aux_cfg;
const u8 *vco_div_cfg;
- const struct qcom_edp_swing_pre_emph_cfg *swing_pre_emph_cfg;
+ const struct qcom_edp_swing_pre_emph_cfg *dp_swing_pre_emph_cfg;
+ const struct qcom_edp_swing_pre_emph_cfg *edp_swing_pre_emph_cfg;
const struct phy_ver_ops *ver_ops;
};
@@ -151,6 +152,20 @@ static const struct qcom_edp_swing_pre_emph_cfg dp_phy_swing_pre_emph_cfg = {
.pre_emphasis_hbr3_hbr2 = &dp_pre_emp_hbr2_hbr3,
};
+static const u8 dp_pre_emp_hbr_rbr_v8[4][4] = {
+ { 0x00, 0x0e, 0x15, 0x1a },
+ { 0x00, 0x0e, 0x15, 0xff },
+ { 0x00, 0x0e, 0xff, 0xff },
+ { 0x00, 0xff, 0xff, 0xff }
+};
+
+static const struct qcom_edp_swing_pre_emph_cfg dp_phy_swing_pre_emph_cfg_v8 = {
+ .swing_hbr_rbr = &dp_swing_hbr_rbr,
+ .swing_hbr3_hbr2 = &dp_swing_hbr2_hbr3,
+ .pre_emphasis_hbr_rbr = &dp_pre_emp_hbr_rbr_v8,
+ .pre_emphasis_hbr3_hbr2 = &dp_pre_emp_hbr2_hbr3,
+};
+
static const u8 edp_swing_hbr_rbr[4][4] = {
{ 0x07, 0x0f, 0x16, 0x1f },
{ 0x0d, 0x16, 0x1e, 0xff },
@@ -186,6 +201,27 @@ static const struct qcom_edp_swing_pre_emph_cfg edp_phy_swing_pre_emph_cfg = {
.pre_emphasis_hbr3_hbr2 = &edp_pre_emp_hbr2_hbr3,
};
+static const u8 edp_swing_hbr2_hbr3_v3[4][4] = {
+ { 0x0b, 0x11, 0x16, 0x1b },
+ { 0x0b, 0x19, 0x1f, 0xff },
+ { 0x18, 0x1f, 0xff, 0xff },
+ { 0x1f, 0xff, 0xff, 0xff }
+};
+
+static const u8 edp_pre_emp_hbr2_hbr3_v3[4][4] = {
+ { 0x0c, 0x15, 0x19, 0x1e },
+ { 0x09, 0x14, 0x19, 0xff },
+ { 0x0f, 0x14, 0xff, 0xff },
+ { 0x0d, 0xff, 0xff, 0xff }
+};
+
+static const struct qcom_edp_swing_pre_emph_cfg edp_phy_swing_pre_emph_cfg_v3 = {
+ .swing_hbr_rbr = &edp_swing_hbr_rbr,
+ .swing_hbr3_hbr2 = &edp_swing_hbr2_hbr3_v3,
+ .pre_emphasis_hbr_rbr = &edp_pre_emp_hbr_rbr,
+ .pre_emphasis_hbr3_hbr2 = &edp_pre_emp_hbr2_hbr3_v3,
+};
+
static const u8 edp_phy_aux_cfg_v4[DP_AUX_CFG_SIZE] = {
0x00, 0x13, 0x24, 0x00, 0x0a, 0x26, 0x0a, 0x03, 0x37, 0x03, 0x02, 0x02, 0x00,
};
@@ -242,12 +278,7 @@ static int qcom_edp_phy_init(struct phy *phy)
DP_PHY_PD_CTL_PLL_PWRDN | DP_PHY_PD_CTL_DP_CLAMP_EN,
edp->edp + DP_PHY_PD_CTL);
- /*
- * TODO: Re-work the conditions around setting the cfg8 value
- * when more information becomes available about why this is
- * even needed.
- */
- if (edp->cfg->swing_pre_emph_cfg && !edp->is_edp)
+ if (!edp->is_edp)
aux_cfg[8] = 0xb7;
writel(0xfc, edp->edp + DP_PHY_MODE);
@@ -271,7 +302,7 @@ static int qcom_edp_phy_init(struct phy *phy)
static int qcom_edp_set_voltages(struct qcom_edp *edp, const struct phy_configure_opts_dp *dp_opts)
{
- const struct qcom_edp_swing_pre_emph_cfg *cfg = edp->cfg->swing_pre_emph_cfg;
+ const struct qcom_edp_swing_pre_emph_cfg *cfg;
unsigned int v_level = 0;
unsigned int p_level = 0;
int ret;
@@ -279,12 +310,14 @@ static int qcom_edp_set_voltages(struct qcom_edp *edp, const struct phy_configur
u8 emph;
int i;
+ if (edp->is_edp)
+ cfg = edp->cfg->edp_swing_pre_emph_cfg;
+ else
+ cfg = edp->cfg->dp_swing_pre_emph_cfg;
+
if (!cfg)
return 0;
- if (edp->is_edp)
- cfg = &edp_phy_swing_pre_emph_cfg;
-
for (i = 0; i < dp_opts->lanes; i++) {
v_level = max(v_level, dp_opts->voltage[i]);
p_level = max(p_level, dp_opts->pre[i]);
@@ -591,20 +624,24 @@ static const struct qcom_edp_phy_cfg sa8775p_dp_phy_cfg = {
.is_edp = false,
.aux_cfg = edp_phy_aux_cfg_v5,
.vco_div_cfg = edp_phy_vco_div_cfg_v4,
- .swing_pre_emph_cfg = &edp_phy_swing_pre_emph_cfg,
+ .dp_swing_pre_emph_cfg = &dp_phy_swing_pre_emph_cfg,
+ .edp_swing_pre_emph_cfg = &edp_phy_swing_pre_emph_cfg,
.ver_ops = &qcom_edp_phy_ops_v4,
};
static const struct qcom_edp_phy_cfg sc7280_dp_phy_cfg = {
.aux_cfg = edp_phy_aux_cfg_v4,
.vco_div_cfg = edp_phy_vco_div_cfg_v4,
+ .dp_swing_pre_emph_cfg = &dp_phy_swing_pre_emph_cfg,
+ .edp_swing_pre_emph_cfg = &edp_phy_swing_pre_emph_cfg_v3,
.ver_ops = &qcom_edp_phy_ops_v3,
};
static const struct qcom_edp_phy_cfg sc8280xp_dp_phy_cfg = {
.aux_cfg = edp_phy_aux_cfg_v4,
.vco_div_cfg = edp_phy_vco_div_cfg_v4,
- .swing_pre_emph_cfg = &dp_phy_swing_pre_emph_cfg,
+ .dp_swing_pre_emph_cfg = &dp_phy_swing_pre_emph_cfg,
+ .edp_swing_pre_emph_cfg = &edp_phy_swing_pre_emph_cfg,
.ver_ops = &qcom_edp_phy_ops_v4,
};
@@ -612,7 +649,8 @@ static const struct qcom_edp_phy_cfg sc8280xp_edp_phy_cfg = {
.is_edp = true,
.aux_cfg = edp_phy_aux_cfg_v4,
.vco_div_cfg = edp_phy_vco_div_cfg_v4,
- .swing_pre_emph_cfg = &edp_phy_swing_pre_emph_cfg,
+ .dp_swing_pre_emph_cfg = &dp_phy_swing_pre_emph_cfg,
+ .edp_swing_pre_emph_cfg = &edp_phy_swing_pre_emph_cfg,
.ver_ops = &qcom_edp_phy_ops_v4,
};
@@ -811,7 +849,8 @@ static const struct phy_ver_ops qcom_edp_phy_ops_v6 = {
static struct qcom_edp_phy_cfg x1e80100_phy_cfg = {
.aux_cfg = edp_phy_aux_cfg_v4,
.vco_div_cfg = edp_phy_vco_div_cfg_v4,
- .swing_pre_emph_cfg = &dp_phy_swing_pre_emph_cfg,
+ .dp_swing_pre_emph_cfg = &dp_phy_swing_pre_emph_cfg,
+ .edp_swing_pre_emph_cfg = &edp_phy_swing_pre_emph_cfg,
.ver_ops = &qcom_edp_phy_ops_v6,
};
@@ -991,7 +1030,8 @@ static const struct phy_ver_ops qcom_edp_phy_ops_v8 = {
static struct qcom_edp_phy_cfg glymur_phy_cfg = {
.aux_cfg = edp_phy_aux_cfg_v8,
.vco_div_cfg = edp_phy_vco_div_cfg_v8,
- .swing_pre_emph_cfg = &edp_phy_swing_pre_emph_cfg,
+ .dp_swing_pre_emph_cfg = &dp_phy_swing_pre_emph_cfg_v8,
+ .edp_swing_pre_emph_cfg = &edp_phy_swing_pre_emph_cfg,
.ver_ops = &qcom_edp_phy_ops_v8,
};
--
2.43.0
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply related
* [PATCH 1/3] phy: qcom: edp: Correct and clean up eDP/DP combo PHY configuration values
From: Yongxing Mou @ 2026-02-05 9:20 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong
Cc: linux-arm-msm, linux-phy, linux-kernel, Yongxing Mou
In-Reply-To: <20260205-edp_phy-v1-0-231882bbf3f1@oss.qualcomm.com>
According to the current HPG settings, most eDP/DP combo PHYs can reuse the
same configuration values.
DP mode:
-sa8775p/sc7280/sc8280xp/x1e80100
-glymur
eDP mode(low vdiff):
-glymur/sa8775p/sc8280xp/x1e80100
-sc7280
The current driver still keeps multiple versions of these tables and
doesn't fully support every combo PHY mode. This patch removes the
redundant configs and keeps only the sets we actually use, matching the
platforms listed above.
Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
---
drivers/phy/qualcomm/phy-qcom-edp.c | 41 +++++++++----------------------------
1 file changed, 10 insertions(+), 31 deletions(-)
diff --git a/drivers/phy/qualcomm/phy-qcom-edp.c b/drivers/phy/qualcomm/phy-qcom-edp.c
index 13feab99feec..10cbb7d9a8a0 100644
--- a/drivers/phy/qualcomm/phy-qcom-edp.c
+++ b/drivers/phy/qualcomm/phy-qcom-edp.c
@@ -116,17 +116,17 @@ struct qcom_edp {
};
static const u8 dp_swing_hbr_rbr[4][4] = {
- { 0x08, 0x0f, 0x16, 0x1f },
+ { 0x07, 0x0f, 0x16, 0x1f },
{ 0x11, 0x1e, 0x1f, 0xff },
{ 0x16, 0x1f, 0xff, 0xff },
{ 0x1f, 0xff, 0xff, 0xff }
};
static const u8 dp_pre_emp_hbr_rbr[4][4] = {
- { 0x00, 0x0d, 0x14, 0x1a },
+ { 0x00, 0x0e, 0x15, 0x1a },
{ 0x00, 0x0e, 0x15, 0xff },
{ 0x00, 0x0e, 0xff, 0xff },
- { 0x03, 0xff, 0xff, 0xff }
+ { 0x04, 0xff, 0xff, 0xff }
};
static const u8 dp_swing_hbr2_hbr3[4][4] = {
@@ -158,7 +158,7 @@ static const u8 edp_swing_hbr_rbr[4][4] = {
};
static const u8 edp_pre_emp_hbr_rbr[4][4] = {
- { 0x05, 0x12, 0x17, 0x1d },
+ { 0x05, 0x11, 0x17, 0x1d },
{ 0x05, 0x11, 0x18, 0xff },
{ 0x06, 0x11, 0xff, 0xff },
{ 0x00, 0xff, 0xff, 0xff }
@@ -172,10 +172,10 @@ static const u8 edp_swing_hbr2_hbr3[4][4] = {
};
static const u8 edp_pre_emp_hbr2_hbr3[4][4] = {
- { 0x08, 0x11, 0x17, 0x1b },
- { 0x00, 0x0c, 0x13, 0xff },
- { 0x05, 0x10, 0xff, 0xff },
- { 0x00, 0xff, 0xff, 0xff }
+ { 0x0c, 0x15, 0x19, 0x1e },
+ { 0x0b, 0x15, 0x19, 0xff },
+ { 0x0e, 0x14, 0xff, 0xff },
+ { 0x0d, 0xff, 0xff, 0xff }
};
static const struct qcom_edp_swing_pre_emph_cfg edp_phy_swing_pre_emph_cfg = {
@@ -193,27 +193,6 @@ static const u8 edp_phy_vco_div_cfg_v4[4] = {
0x01, 0x01, 0x02, 0x00,
};
-static const u8 edp_pre_emp_hbr_rbr_v5[4][4] = {
- { 0x05, 0x11, 0x17, 0x1d },
- { 0x05, 0x11, 0x18, 0xff },
- { 0x06, 0x11, 0xff, 0xff },
- { 0x00, 0xff, 0xff, 0xff }
-};
-
-static const u8 edp_pre_emp_hbr2_hbr3_v5[4][4] = {
- { 0x0c, 0x15, 0x19, 0x1e },
- { 0x0b, 0x15, 0x19, 0xff },
- { 0x0e, 0x14, 0xff, 0xff },
- { 0x0d, 0xff, 0xff, 0xff }
-};
-
-static const struct qcom_edp_swing_pre_emph_cfg edp_phy_swing_pre_emph_cfg_v5 = {
- .swing_hbr_rbr = &edp_swing_hbr_rbr,
- .swing_hbr3_hbr2 = &edp_swing_hbr2_hbr3,
- .pre_emphasis_hbr_rbr = &edp_pre_emp_hbr_rbr_v5,
- .pre_emphasis_hbr3_hbr2 = &edp_pre_emp_hbr2_hbr3_v5,
-};
-
static const u8 edp_phy_aux_cfg_v5[DP_AUX_CFG_SIZE] = {
0x00, 0x13, 0xa4, 0x00, 0x0a, 0x26, 0x0a, 0x03, 0x37, 0x03, 0x02, 0x02, 0x00,
};
@@ -564,7 +543,7 @@ static const struct qcom_edp_phy_cfg sa8775p_dp_phy_cfg = {
.is_edp = false,
.aux_cfg = edp_phy_aux_cfg_v5,
.vco_div_cfg = edp_phy_vco_div_cfg_v4,
- .swing_pre_emph_cfg = &edp_phy_swing_pre_emph_cfg_v5,
+ .swing_pre_emph_cfg = &edp_phy_swing_pre_emph_cfg,
.ver_ops = &qcom_edp_phy_ops_v4,
};
@@ -944,7 +923,7 @@ static const struct phy_ver_ops qcom_edp_phy_ops_v8 = {
static struct qcom_edp_phy_cfg glymur_phy_cfg = {
.aux_cfg = edp_phy_aux_cfg_v8,
.vco_div_cfg = edp_phy_vco_div_cfg_v8,
- .swing_pre_emph_cfg = &edp_phy_swing_pre_emph_cfg_v5,
+ .swing_pre_emph_cfg = &edp_phy_swing_pre_emph_cfg,
.ver_ops = &qcom_edp_phy_ops_v8,
};
--
2.43.0
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply related
* [PATCH 0/3] phy: qcom: edp: Add DP/eDP switch for phys
From: Yongxing Mou @ 2026-02-05 9:20 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong
Cc: linux-arm-msm, linux-phy, linux-kernel, Yongxing Mou
Currently the PHY selects the DP/eDP configuration tables in a fixed way,
choosing the table when enable. This driver has known issues:
1. The selected table does not match the actual platform mode.
2. It cannot support both modes at the same time.
As discussed here[1], this series:
1. Cleans up duplicated and incorrect tables based on the HPG.
2. Fixes the LDO programming error in eDP mode.
3. Adds DP/eDP mode switching support.
Note: x1e80100/sa8775p/sc7280 have been tested, while glymur/sc8280xp
have not been tested.
[1] https://lore.kernel.org/all/20260119-klm_dpphy-v2-1-52252190940b@oss.qualcomm.com/
Signed-off-by: Yongxing Mou <yongxing.mou@oss.qualcomm.com>
---
Yongxing Mou (3):
phy: qcom: edp: Correct and clean up eDP/DP combo PHY configuration values
phy: qcom: edp: Add per-version LDO configuration callback
phy: qcom: edp: Add eDP phy mode switch support
drivers/phy/qualcomm/phy-qcom-edp.c | 191 ++++++++++++++++++++++++++----------
1 file changed, 138 insertions(+), 53 deletions(-)
---
base-commit: fc4e91c639c0af93d63c3d5bc0ee45515dd7504a
change-id: 20260205-edp_phy-1eca3ed074c0
Best regards,
--
Yongxing Mou <yongxing.mou@oss.qualcomm.com>
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* [PATCH v9 2/2] phy: eswin: Create eswin directory and add EIC7700 SATA PHY driver
From: Yulin Lu @ 2026-02-05 8:22 UTC (permalink / raw)
To: vkoul, neil.armstrong, robh, krzk+dt, conor+dt, p.zabel,
linux-phy, devicetree, linux-kernel
Cc: ningyu, linmin, fenglin, Yulin Lu
In-Reply-To: <20260205082009.1780-1-luyulin@eswincomputing.com>
Create the eswin phy driver directory and add support for the
SATA PHY driver on the EIC7700 SoC platform.
Signed-off-by: Yulin Lu <luyulin@eswincomputing.com>
---
drivers/phy/Kconfig | 1 +
drivers/phy/Makefile | 1 +
drivers/phy/eswin/Kconfig | 14 ++
drivers/phy/eswin/Makefile | 2 +
drivers/phy/eswin/phy-eic7700-sata.c | 273 +++++++++++++++++++++++++++
5 files changed, 291 insertions(+)
create mode 100644 drivers/phy/eswin/Kconfig
create mode 100644 drivers/phy/eswin/Makefile
create mode 100644 drivers/phy/eswin/phy-eic7700-sata.c
diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig
index 678dd0452f0a..6d50704917f0 100644
--- a/drivers/phy/Kconfig
+++ b/drivers/phy/Kconfig
@@ -105,6 +105,7 @@ source "drivers/phy/allwinner/Kconfig"
source "drivers/phy/amlogic/Kconfig"
source "drivers/phy/broadcom/Kconfig"
source "drivers/phy/cadence/Kconfig"
+source "drivers/phy/eswin/Kconfig"
source "drivers/phy/freescale/Kconfig"
source "drivers/phy/hisilicon/Kconfig"
source "drivers/phy/ingenic/Kconfig"
diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
index bfb27fb5a494..482a143d3417 100644
--- a/drivers/phy/Makefile
+++ b/drivers/phy/Makefile
@@ -17,6 +17,7 @@ obj-y += allwinner/ \
amlogic/ \
broadcom/ \
cadence/ \
+ eswin/ \
freescale/ \
hisilicon/ \
ingenic/ \
diff --git a/drivers/phy/eswin/Kconfig b/drivers/phy/eswin/Kconfig
new file mode 100644
index 000000000000..cf2bf2efc32f
--- /dev/null
+++ b/drivers/phy/eswin/Kconfig
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Phy drivers for ESWIN platforms
+#
+config PHY_EIC7700_SATA
+ tristate "eic7700 Sata SerDes/PHY driver"
+ depends on ARCH_ESWIN || COMPILE_TEST
+ depends on HAS_IOMEM
+ select GENERIC_PHY
+ help
+ Enable this to support SerDes/Phy found on ESWIN's
+ EIC7700 SoC. This Phy supports SATA 1.5 Gb/s,
+ SATA 3.0 Gb/s, SATA 6.0 Gb/s speeds.
+ It supports one SATA host port to accept one SATA device.
diff --git a/drivers/phy/eswin/Makefile b/drivers/phy/eswin/Makefile
new file mode 100644
index 000000000000..db08c66be812
--- /dev/null
+++ b/drivers/phy/eswin/Makefile
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_PHY_EIC7700_SATA) += phy-eic7700-sata.o
diff --git a/drivers/phy/eswin/phy-eic7700-sata.c b/drivers/phy/eswin/phy-eic7700-sata.c
new file mode 100644
index 000000000000..c33653d48daa
--- /dev/null
+++ b/drivers/phy/eswin/phy-eic7700-sata.c
@@ -0,0 +1,273 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ESWIN SATA PHY driver
+ *
+ * Copyright 2026, Beijing ESWIN Computing Technology Co., Ltd..
+ * All rights reserved.
+ *
+ * Authors: Yulin Lu <luyulin@eswincomputing.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+
+#define SATA_AXI_LP_CTRL 0x08
+#define SATA_MPLL_CTRL 0x20
+#define SATA_P0_PHY_STAT 0x24
+#define SATA_PHY_CTRL0 0x28
+#define SATA_PHY_CTRL1 0x2c
+#define SATA_REF_CTRL 0x34
+#define SATA_REF_CTRL1 0x38
+#define SATA_LOS_IDEN 0x3c
+
+#define SATA_CLK_RST_SOURCE_PHY BIT(0)
+#define SATA_P0_PHY_TX_AMPLITUDE_GEN1_MASK GENMASK(6, 0)
+#define SATA_P0_PHY_TX_AMPLITUDE_GEN1_DEFAULT 0x42
+#define SATA_P0_PHY_TX_AMPLITUDE_GEN2_MASK GENMASK(14, 8)
+#define SATA_P0_PHY_TX_AMPLITUDE_GEN2_DEFAULT 0x46
+#define SATA_P0_PHY_TX_AMPLITUDE_GEN3_MASK GENMASK(22, 16)
+#define SATA_P0_PHY_TX_AMPLITUDE_GEN3_DEFAULT 0x73
+#define SATA_P0_PHY_TX_PREEMPH_GEN1_MASK GENMASK(5, 0)
+#define SATA_P0_PHY_TX_PREEMPH_GEN1_DEFAULT 0x5
+#define SATA_P0_PHY_TX_PREEMPH_GEN2_MASK GENMASK(13, 8)
+#define SATA_P0_PHY_TX_PREEMPH_GEN2_DEFAULT 0x5
+#define SATA_P0_PHY_TX_PREEMPH_GEN3_MASK GENMASK(21, 16)
+#define SATA_P0_PHY_TX_PREEMPH_GEN3_DEFAULT 0x23
+#define SATA_LOS_LEVEL_MASK GENMASK(4, 0)
+#define SATA_LOS_BIAS_MASK GENMASK(18, 16)
+#define SATA_M_CSYSREQ BIT(0)
+#define SATA_S_CSYSREQ BIT(16)
+#define SATA_REF_REPEATCLK_EN BIT(0)
+#define SATA_REF_USE_PAD BIT(20)
+#define SATA_MPLL_MULTIPLIER_MASK GENMASK(22, 16)
+#define SATA_P0_PHY_READY BIT(0)
+
+#define PLL_LOCK_SLEEP_US 10
+#define PLL_LOCK_TIMEOUT_US 1000
+
+struct eic7700_sata_phy {
+ u32 tx_amplitude_tuning_val[3];
+ u32 tx_preemph_tuning_val[3];
+ struct reset_control *rst;
+ struct regmap *regmap;
+ struct clk *clk;
+ struct phy *phy;
+};
+
+static const struct regmap_config eic7700_sata_phy_regmap_config = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .max_register = SATA_LOS_IDEN,
+};
+
+static int wait_for_phy_ready(struct regmap *regmap, u32 reg, u32 checkbit,
+ u32 status)
+{
+ u32 val;
+ int ret;
+
+ ret = regmap_read_poll_timeout(regmap, reg, val,
+ (val & checkbit) == status,
+ PLL_LOCK_SLEEP_US, PLL_LOCK_TIMEOUT_US);
+
+ return ret;
+}
+
+static int eic7700_sata_phy_init(struct phy *phy)
+{
+ struct eic7700_sata_phy *sata_phy = phy_get_drvdata(phy);
+ u32 val;
+ int ret;
+
+ ret = clk_prepare_enable(sata_phy->clk);
+ if (ret)
+ return ret;
+
+ regmap_write(sata_phy->regmap, SATA_REF_CTRL1, SATA_CLK_RST_SOURCE_PHY);
+
+ val = FIELD_PREP(SATA_P0_PHY_TX_AMPLITUDE_GEN1_MASK,
+ sata_phy->tx_amplitude_tuning_val[0]) |
+ FIELD_PREP(SATA_P0_PHY_TX_AMPLITUDE_GEN2_MASK,
+ sata_phy->tx_amplitude_tuning_val[1]) |
+ FIELD_PREP(SATA_P0_PHY_TX_AMPLITUDE_GEN3_MASK,
+ sata_phy->tx_amplitude_tuning_val[2]);
+ regmap_write(sata_phy->regmap, SATA_PHY_CTRL0, val);
+
+ val = FIELD_PREP(SATA_P0_PHY_TX_PREEMPH_GEN1_MASK,
+ sata_phy->tx_preemph_tuning_val[0]) |
+ FIELD_PREP(SATA_P0_PHY_TX_PREEMPH_GEN2_MASK,
+ sata_phy->tx_preemph_tuning_val[1]) |
+ FIELD_PREP(SATA_P0_PHY_TX_PREEMPH_GEN3_MASK,
+ sata_phy->tx_preemph_tuning_val[2]);
+ regmap_write(sata_phy->regmap, SATA_PHY_CTRL1, val);
+
+ val = FIELD_PREP(SATA_LOS_LEVEL_MASK, 0x9) |
+ FIELD_PREP(SATA_LOS_BIAS_MASK, 0x2);
+ regmap_write(sata_phy->regmap, SATA_LOS_IDEN, val);
+
+ val = SATA_M_CSYSREQ | SATA_S_CSYSREQ;
+ regmap_write(sata_phy->regmap, SATA_AXI_LP_CTRL, val);
+
+ val = SATA_REF_REPEATCLK_EN | SATA_REF_USE_PAD;
+ regmap_write(sata_phy->regmap, SATA_REF_CTRL, val);
+
+ val = FIELD_PREP(SATA_MPLL_MULTIPLIER_MASK, 0x3c);
+ regmap_write(sata_phy->regmap, SATA_MPLL_CTRL, val);
+
+ usleep_range(15, 20);
+
+ ret = reset_control_deassert(sata_phy->rst);
+ if (ret)
+ goto disable_clk;
+
+ ret = wait_for_phy_ready(sata_phy->regmap, SATA_P0_PHY_STAT,
+ SATA_P0_PHY_READY, 1);
+ if (ret < 0) {
+ dev_err(&sata_phy->phy->dev, "PHY READY check failed\n");
+ goto disable_clk;
+ }
+
+ return 0;
+
+disable_clk:
+ clk_disable_unprepare(sata_phy->clk);
+ return ret;
+}
+
+static int eic7700_sata_phy_exit(struct phy *phy)
+{
+ struct eic7700_sata_phy *sata_phy = phy_get_drvdata(phy);
+ int ret;
+
+ ret = reset_control_assert(sata_phy->rst);
+ if (ret)
+ return ret;
+
+ clk_disable_unprepare(sata_phy->clk);
+
+ return 0;
+}
+
+static const struct phy_ops eic7700_sata_phy_ops = {
+ .init = eic7700_sata_phy_init,
+ .exit = eic7700_sata_phy_exit,
+ .owner = THIS_MODULE,
+};
+
+static void eic7700_get_tuning_param(struct device_node *np,
+ struct eic7700_sata_phy *sata_phy)
+{
+ if (of_property_read_u32_array
+ (np, "eswin,tx-amplitude-tuning",
+ sata_phy->tx_amplitude_tuning_val,
+ ARRAY_SIZE(sata_phy->tx_amplitude_tuning_val))) {
+ sata_phy->tx_amplitude_tuning_val[0] =
+ SATA_P0_PHY_TX_AMPLITUDE_GEN1_DEFAULT;
+ sata_phy->tx_amplitude_tuning_val[1] =
+ SATA_P0_PHY_TX_AMPLITUDE_GEN2_DEFAULT;
+ sata_phy->tx_amplitude_tuning_val[2] =
+ SATA_P0_PHY_TX_AMPLITUDE_GEN3_DEFAULT;
+ }
+
+ if (of_property_read_u32_array
+ (np, "eswin,tx-preemph-tuning",
+ sata_phy->tx_preemph_tuning_val,
+ ARRAY_SIZE(sata_phy->tx_preemph_tuning_val))) {
+ sata_phy->tx_preemph_tuning_val[0] =
+ SATA_P0_PHY_TX_PREEMPH_GEN1_DEFAULT;
+ sata_phy->tx_preemph_tuning_val[1] =
+ SATA_P0_PHY_TX_PREEMPH_GEN2_DEFAULT;
+ sata_phy->tx_preemph_tuning_val[2] =
+ SATA_P0_PHY_TX_PREEMPH_GEN3_DEFAULT;
+ }
+}
+
+static int eic7700_sata_phy_probe(struct platform_device *pdev)
+{
+ struct eic7700_sata_phy *sata_phy;
+ struct phy_provider *phy_provider;
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct resource *res;
+ void __iomem *regs;
+
+ sata_phy = devm_kzalloc(dev, sizeof(*sata_phy), GFP_KERNEL);
+ if (!sata_phy)
+ return -ENOMEM;
+
+ /*
+ * Map the I/O resource with platform_get_resource and devm_ioremap
+ * instead of the devm_platform_ioremap_resource API, because the
+ * address region of the SATA-PHY falls into the region of the HSP
+ * clock & reset that has already been obtained by the HSP
+ * clock-and-reset driver.
+ */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -ENOENT;
+
+ regs = devm_ioremap(dev, res->start, resource_size(res));
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
+ sata_phy->regmap = devm_regmap_init_mmio
+ (dev, regs, &eic7700_sata_phy_regmap_config);
+ if (IS_ERR(sata_phy->regmap))
+ return dev_err_probe(dev, PTR_ERR(sata_phy->regmap),
+ "failed to init regmap\n");
+
+ dev_set_drvdata(dev, sata_phy);
+
+ eic7700_get_tuning_param(np, sata_phy);
+
+ sata_phy->clk = devm_clk_get(dev, "phy");
+ if (IS_ERR(sata_phy->clk))
+ return PTR_ERR(sata_phy->clk);
+
+ sata_phy->rst = devm_reset_control_array_get_exclusive(dev);
+ if (IS_ERR(sata_phy->rst))
+ return dev_err_probe(dev, PTR_ERR(sata_phy->rst),
+ "failed to get reset control\n");
+
+ sata_phy->phy = devm_phy_create(dev, NULL, &eic7700_sata_phy_ops);
+ if (IS_ERR(sata_phy->phy))
+ return dev_err_probe(dev, PTR_ERR(sata_phy->phy),
+ "failed to create PHY\n");
+
+ phy_set_drvdata(sata_phy->phy, sata_phy);
+
+ phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+ if (IS_ERR(phy_provider))
+ return dev_err_probe(dev, PTR_ERR(phy_provider),
+ "failed to register PHY provider\n");
+
+ return 0;
+}
+
+static const struct of_device_id eic7700_sata_phy_of_match[] = {
+ { .compatible = "eswin,eic7700-sata-phy" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, eic7700_sata_phy_of_match);
+
+static struct platform_driver eic7700_sata_phy_driver = {
+ .probe = eic7700_sata_phy_probe,
+ .driver = {
+ .of_match_table = eic7700_sata_phy_of_match,
+ .name = "eic7700-sata-phy",
+ }
+};
+module_platform_driver(eic7700_sata_phy_driver);
+
+MODULE_DESCRIPTION("SATA PHY driver for the ESWIN EIC7700 SoC");
+MODULE_AUTHOR("Yulin Lu <luyulin@eswincomputing.com>");
+MODULE_LICENSE("GPL");
--
2.25.1
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply related
* [PATCH v9 1/2] dt-bindings: phy: eswin: Document the EIC7700 SoC SATA PHY
From: Yulin Lu @ 2026-02-05 8:21 UTC (permalink / raw)
To: vkoul, neil.armstrong, robh, krzk+dt, conor+dt, p.zabel,
linux-phy, devicetree, linux-kernel
Cc: ningyu, linmin, fenglin, Yulin Lu
In-Reply-To: <20260205082009.1780-1-luyulin@eswincomputing.com>
Document the SATA PHY on the EIC7700 SoC platform,
describing its usage.
Signed-off-by: Yulin Lu <luyulin@eswincomputing.com>
---
.../bindings/phy/eswin,eic7700-sata-phy.yaml | 92 +++++++++++++++++++
1 file changed, 92 insertions(+)
create mode 100644 Documentation/devicetree/bindings/phy/eswin,eic7700-sata-phy.yaml
diff --git a/Documentation/devicetree/bindings/phy/eswin,eic7700-sata-phy.yaml b/Documentation/devicetree/bindings/phy/eswin,eic7700-sata-phy.yaml
new file mode 100644
index 000000000000..fc7dbac77acf
--- /dev/null
+++ b/Documentation/devicetree/bindings/phy/eswin,eic7700-sata-phy.yaml
@@ -0,0 +1,92 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/phy/eswin,eic7700-sata-phy.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Eswin EIC7700 SoC SATA PHY
+
+maintainers:
+ - Yulin Lu <luyulin@eswincomputing.com>
+ - Huan He <hehuan1@eswincomputing.com>
+
+properties:
+ compatible:
+ const: eswin,eic7700-sata-phy
+
+ reg:
+ maxItems: 1
+
+ clocks:
+ maxItems: 1
+
+ clock-names:
+ const: phy
+
+ resets:
+ maxItems: 2
+
+ reset-names:
+ items:
+ - const: port
+ - const: phy
+
+ eswin,tx-amplitude-tuning:
+ description: This adjusts the transmitter amplitude signal, and its value
+ is derived from eye diagram tuning. The three values correspond to Gen1,
+ Gen2, and Gen3 parameters respectively.
+ $ref: /schemas/types.yaml#/definitions/uint32-array
+ items:
+ - description: Gen1 parameter.
+ minimum: 0
+ maximum: 0x7f
+ - description: Gen2 parameter.
+ minimum: 0
+ maximum: 0x7f
+ - description: Gen3 parameter.
+ minimum: 0
+ maximum: 0x7f
+ default: [0, 0, 0]
+
+ eswin,tx-preemph-tuning:
+ description: This adjusts the transmitter de-emphasis signal, and its value
+ is derived from eye diagram tuning. The three values correspond to Gen1,
+ Gen2, and Gen3 parameters respectively.
+ $ref: /schemas/types.yaml#/definitions/uint32-array
+ items:
+ - description: Gen1 parameter.
+ minimum: 0
+ maximum: 0x3f
+ - description: Gen2 parameter.
+ minimum: 0
+ maximum: 0x3f
+ - description: Gen3 parameter.
+ minimum: 0
+ maximum: 0x3f
+ default: [0, 0, 0]
+
+ "#phy-cells":
+ const: 0
+
+required:
+ - compatible
+ - reg
+ - clocks
+ - clock-names
+ - resets
+ - reset-names
+ - "#phy-cells"
+
+additionalProperties: false
+
+examples:
+ - |
+ sata-phy@50440300 {
+ compatible = "eswin,eic7700-sata-phy";
+ reg = <0x50440300 0x40>;
+ clocks = <&hspcrg 17>;
+ clock-names = "phy";
+ resets = <&hspcrg 0>, <&hspcrg 1>;
+ reset-names = "port", "phy";
+ #phy-cells = <0>;
+ };
--
2.25.1
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply related
* [PATCH v9 0/2] Add driver support for Eswin EIC7700 SoC SATA PHY
From: Yulin Lu @ 2026-02-05 8:20 UTC (permalink / raw)
To: vkoul, neil.armstrong, robh, krzk+dt, conor+dt, p.zabel,
linux-phy, devicetree, linux-kernel
Cc: ningyu, linmin, fenglin, Yulin Lu
Updates:
v9 -> v8:
- eswin,eic7700-sata-phy.yaml
- Modify the format of the "default" field in the
"eswin,tx-amplitude-tuning" and "eswin,tx-preemph-tuning"
properties.
- phy-eic7700-sata.c
- Correct the incorrectly formatted symbol "-" in the comments.
- Link to v8: https://lore.kernel.org/lkml/20260123024823.1612-1-luyulin@eswincomputing.com/
v8 -> v7:
- eswin,eic7700-sata-phy.yaml
- Add "eswin,tx-amplitude-tuning" and "eswin,tx-preemph-tuning"
properties, because these parameters may vary across different
circuit boards.
- Delete reviewed-by tag of Krzysztof Kozlowski, because the tuning
properties are introduced.
- phy-eic7700-sata.c
- Try to get SATA PHY transmitter amplitude and pre-emphasis signal
eye diagram tuning parameters from dts instead of hardcoded values
in the code. Because, these parameters may vary across different
circuit boards. Define default tuning parameters and use it when
these properties are not declared in dts.
- Add a comment to explain the reason for mapping I/O resources with
platform_get_resource and devm_ioremap instead of using the
devm_platform_ioremap_resource API.
- Link to v7: https://lore.kernel.org/lkml/20260106062944.1529-1-luyulin@eswincomputing.com/
v7 -> v6:
- phy-eic7700-sata.c
- Rename PHY_READY_TIMEOUT to PLL_LOCK_TIMEOUT_US with value 1000.
- Add macro PLL_LOCK_SLEEP_US set to 10.
- Add "goto disable_clk" in the eic7700_sata_phy_init function.
- Modify Copyright year from 2024 to 2026.
- Link to v6: https://lore.kernel.org/lkml/20251201060737.868-1-luyulin@eswincomputing.com/
v6 -> v5:
- eswin,eic7700-ahci.yaml
- Delete this file and it has already been applied in reply[1].
- eswin,eic7700-sata-phy.yaml
- Add clock and reset related properties.
- phy-eic7700-sata.c
- Map the io resource with platform_get_resource and devm_ioremap
instead of devm_platform_ioremap_resource API. Because the address
region of sata-phy falls into the region of hsp clock&reset which
has been got by hsp clock&reset driver.
- Use regmap_read_poll_timeout in wait_for_phy_ready to replace the
while loop check.
- Use devm_regmap_init_mmio and regmap_write to replace writel.
- Adapt to the clock and reset driver framework, replacing the
original readl and writel.
Because we are implementing the HSP layer clock and reset drivers,
the corresponding clock and reset registers can be registered into
the driver framework. And I have tested on the Sifive HiFive
Premier P550 board.
- Link to v5: https://lore.kernel.org/lkml/20250930083754.15-1-luyulin@eswincomputing.com/
v5 -> v4:
- eswin,eic7700-ahci.yaml
- Add "dt-bindings: ata:" prefix to the subject.
- Wrap at 80 characters in the YAML description field.
- Link to v4: https://lore.kernel.org/lkml/20250915125902.375-1-luyulin@eswincomputing.com/
v4 -> v3:
- eswin,eic7700-ahci.yaml
- Fix grammatical errors in patch subject and commit message
- Add an explanation in the commit message of patch 1 for retaining
the "ports-implemented" field, which Rob Herring suggested to
remove in the review comments on v2.
Link to Rob Herring's review:
https://lore.kernel.org/lkml/CAL_JsqKFotNLZZXwiy7S6K8qXLdGRAnsa-1zvZRDQBE39Gf5kg@mail.gmail.com/
Link to my question and Niklas Cassel's reply:
https://lore.kernel.org/lkml/aLBUC116MdJqDGIJ@flawful.org/
In this reply, Niklas Cassel mentioned his view:
If the ports-implemented register gets reset from
ahci_platform_assert_rsts(), then it seems acceptable to
retain the ports-implemented property in the device tree.
This aligns with our design.
Link to my reply:
https://lore.kernel.org/lkml/4ab70c6a.8be.198f47da494.Coremail.luyulin@eswincomputing.com/
Link to Niklas Cassel's question and my further explanation:
https://lore.kernel.org/lkml/aLlYkZWBaI5Yz6fo@ryzen/
https://lore.kernel.org/lkml/7206383a.d98.19918c22570.Coremail.luyulin@eswincomputing.com/
- eswin,eic7700-sata-phy.yaml
- Fix grammatical errors in patch subject and commit message
- Adjust the position of reg in the properties and required arrays
- Add reviewed-by tag of Krzysztof Kozlowski
- phy-eic7700-sata.c
- Correct the loop condition in wait_for_phy_ready() to use the
current jiffies instead of the fixed start time.
- Change the return value from -EFAULT to -ETIMEDOUT to correctly
indicate a timeout condition.
- Remove redundant clock disable handling in probe error path, as
SATA_SYS_CLK_EN is managed in phy_init() and phy_exit().
- Use dev_err_probe return in probe.
- Reorder local variables to follow reverse Xmas tree order.
- Wrap each line in the extended comments to 80 columns before
splitting lines.
- Adjust the position of `#include <linux/io.h>` for proper ordering.
- Link to v3: https://lore.kernel.org/lkml/20250904063427.1954-1-luyulin@eswincomputing.com/
v2 -> v3:
- Use full name in "From" and "Signed-off-by" fields information
- eswin,eic7700-ahci.yaml
- Remove the introduction to the reg, interrupts, phys, and
phy-names fields.
- Modify the usage of the clocks field in the examples.
- Correct the order of dt properties.
- phy-eic7700-sata.c
- Register operations use the GENMASK macro and FIELD_PREP instead
of the original bit offset method, and add
"#include <linux/bitfield.h>".
- Modify some macro definition names.
- Remove the redundant initialization assignments for "ret" and
"val".
- Delete ".suppress_bind_attrs = true".
- Modify the driver name.
- Add "#include <linux/io.h>" to fix the robot test issue.
- Link to v2: https://lore.kernel.org/lkml/20250819134722.220-1-luyulin@eswincomputing.com/
v2 -> v1:
- Delete the original controller driver and use ahci_dwc.c instead.
- Add eswin,eic7700-ahci.yaml
- Correct the descriptions of reset, interrupt and other hardware
resources for the sata controller on EIC7700 SoC.
- The clocks for both sata controller and sata PHY are controlled
via a register bit in the HSP bus and are not registered in the
clock tree. Clock are managed within the PHY driver, therefore it
is not described in this document.
- Add $ref: snps,dwc-ahci-common.yaml#.
- Add eswin,eic7700-sata-phy.yaml
- Add this file to include the description of the PHY on EIC7700 SoC.
- Add an eswin directory under the PHY driver path, and include the
SATA PHY driver code for EIC7700 SoC.
- Link to v1: https://lore.kernel.org/all/20250515085114.1692-1-hehuan1@eswincomputing.com/
Yulin Lu (2):
dt-bindings: phy: eswin: Document the EIC7700 SoC SATA PHY
phy: eswin: Create eswin directory and add EIC7700 SATA PHY driver
.../bindings/phy/eswin,eic7700-sata-phy.yaml | 92 ++++++
drivers/phy/Kconfig | 1 +
drivers/phy/Makefile | 1 +
drivers/phy/eswin/Kconfig | 14 +
drivers/phy/eswin/Makefile | 2 +
drivers/phy/eswin/phy-eic7700-sata.c | 273 ++++++++++++++++++
6 files changed, 383 insertions(+)
create mode 100644 Documentation/devicetree/bindings/phy/eswin,eic7700-sata-phy.yaml
create mode 100644 drivers/phy/eswin/Kconfig
create mode 100644 drivers/phy/eswin/Makefile
create mode 100644 drivers/phy/eswin/phy-eic7700-sata.c
--
2.25.1
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* Re: [PATCH 1/8] dt-bindings: phy: axiado,ax3000-emmc-phy: add Axiado eMMC PHY document
From: Tzu-Hao Wei @ 2026-02-05 3:38 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: SriNavmani A, Prasad Bolisetty, Vinod Koul, Neil Armstrong,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Harshit Shah,
Ulf Hansson, Adrian Hunter, Michal Simek, linux-phy, devicetree,
linux-arm-kernel, linux-kernel, linux-mmc
In-Reply-To: <20251223-nondescript-horned-mouflon-36ba75@quoll>
On 12/23/2025 10:23 PM, Krzysztof Kozlowski wrote:
> On Mon, Dec 22, 2025 at 04:45:00PM +0800, Tzu-Hao Wei wrote:
>> From: SriNavmani A <srinavmani@axiado.com>
>>
>> Add device tree bindings for the Axiado AX3000 eMMC PHY. The bindings
>> define the required properties for configuring the external Arasan
>> eMMC PHY, including register base addresses and clock references
>> needed for HS200 mode operation.
>
> Do not say what the bindings are supposed to do. We can read the diff.
> Explain the hardware.
>
Simplified the messages in the new patch.
>>
>> Signed-off-by: SriNavmani A <srinavmani@axiado.com>
>> Signed-off-by: Tzu-Hao Wei <twei@axiado.com>
>> ---
>> .../bindings/phy/axiado,ax3000-emmc-phy.yaml | 46 ++++++++++++++++++++++
>> 1 file changed, 46 insertions(+)
>>
>> diff --git a/Documentation/devicetree/bindings/phy/axiado,ax3000-emmc-phy.yaml b/Documentation/devicetree/bindings/phy/axiado,ax3000-emmc-phy.yaml
>> new file mode 100644
>> index 0000000000000000000000000000000000000000..f88941ce60cbd99a73e8c504e0bf36c626bbf674
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/phy/axiado,ax3000-emmc-phy.yaml
>> @@ -0,0 +1,46 @@
>> +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
>> +%YAML 1.2
>> +---
>> +$id: http://devicetree.org/schemas/phy/axiado,ax3000-emmc-phy.yaml#
>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
>> +
>> +title: Axiado AX3000 Arasan eMMC PHY
>> +
>> +maintainers:
>> + - SriNavmani A <srinavmani@axiado.com>
>> + - Tzu-Hao Wei <twei@axiado.com>
>> + - Prasad Bolisetty <pbolisetty@axiado.com>
>> +
>> +description: |+
>> + Bindings for eMMC PHY on Axiado AX3000 SoC.
>
> Irrelevant
>
Removed.
>> +
>> + - compatible: Should be one of the following:
>> + "axiado,ax3000-emmc-phy"
>> + - reg:
>> + maxItems: 1
>
> Entire description is not correct. Do you see any file written like
> this/
>
Revised. It can pass DT_SCHEMA_CHECK and CHECK_DTBS now.
>> +
>> +properties:
>> + compatible:
>> + enum:
>> + - axiado,ax3000-emmc-phy
>> +
>> + "#phy-cells":
>> + const: 0
>> +
>> + reg:
>> + maxItems: 1
>> +
>> +required:
>> + - "#phy-cells"
>> + - compatible
>> + - reg
>
> Wrong order, use the same as in properties.
>
Fixed.
>> +
>> +additionalProperties: false
>> +
>> +examples:
>> + - |
>> + emmc_phy: emmc-phy@80801C00 {
>
> Drop label and node is just phy@
>
> Also, use lowercase hex everywhere.
>
> Read DTS coding style - Axiado already received this feedback, so you
> should follow it in all your submissions.
>
> Best regards,
> Krzysztof
>
Dropped label, used lowercase hex.
Thanks for your feedback.
We will follow the style in the future.
Best regards,
TH
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* Re: [PATCH v2 2/2] mmc: sdhci-of-arasan: add support on Axiado AX3000 SoC
From: Tzu-Hao Wei @ 2026-02-05 3:37 UTC (permalink / raw)
To: Adrian Hunter
Cc: linux-phy, devicetree, linux-arm-kernel, linux-kernel, linux-mmc,
openbmc, SriNavmani A, Prasad Bolisetty, Vinod Koul,
Neil Armstrong, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Ulf Hansson, Michal Simek
In-Reply-To: <d370870a-d4f9-4989-ade3-e2b26b2a1134@intel.com>
On 1/12/2026 4:23 PM, Adrian Hunter wrote:
> CAUTION: This email originated from outside of the organization. Do not click links or open attachments unless you recognize the sender and know the content is safe.
>
>
> On 09/01/2026 11:46, Tzu-Hao Wei wrote:
>> From: SriNavmani A <srinavmani@axiado.com>
>>
>> Axiado AX3000 SoC eMMC controller is based on Arasan eMMC 5.1 host
>> controller IP.
>>
>> Signed-off-by: SriNavmani A <srinavmani@axiado.com>
>> Signed-off-by: Tzu-Hao Wei <twei@axiado.com>
>
> Apart from dt-bindings issues:
>
> Acked-by: Adrian Hunter <adrian.hunter@intel.com>
>
>> ---
>> drivers/mmc/host/sdhci-of-arasan.c | 14 ++++++++++++++
>> 1 file changed, 14 insertions(+)
>>
>> diff --git a/drivers/mmc/host/sdhci-of-arasan.c b/drivers/mmc/host/sdhci-of-arasan.c
>> index ab7f0ffe7b4f007a58eb0a26868b08b0b02b40f3..5da915edd7416ab5c725a784867098c5e19236b6 100644
>> --- a/drivers/mmc/host/sdhci-of-arasan.c
>> +++ b/drivers/mmc/host/sdhci-of-arasan.c
>> @@ -1512,6 +1512,16 @@ static struct sdhci_arasan_of_data intel_keembay_sdio_data = {
>> .clk_ops = &arasan_clk_ops,
>> };
>>
>> +static const struct sdhci_pltfm_data sdhci_arasan_axiado_pdata = {
>> + .ops = &sdhci_arasan_ops,
>> + .quirks = SDHCI_QUIRK_BROKEN_CQE,
>> +};
>> +
>> +static struct sdhci_arasan_of_data sdhci_arasan_axiado_data = {
>> + .pdata = &sdhci_arasan_axiado_pdata,
>> + .clk_ops = &arasan_clk_ops,
>> +};
>> +
>> static const struct of_device_id sdhci_arasan_of_match[] = {
>> /* SoC-specific compatible strings w/ soc_ctl_map */
>> {
>> @@ -1538,6 +1548,10 @@ static const struct of_device_id sdhci_arasan_of_match[] = {
>> .compatible = "intel,keembay-sdhci-5.1-sdio",
>> .data = &intel_keembay_sdio_data,
>> },
>> + {
>> + .compatible = "axiado,ax3000-sdhci-5.1-emmc",
>> + .data = &sdhci_arasan_axiado_data,
>> + },
>> /* Generic compatible below here */
>> {
>> .compatible = "arasan,sdhci-8.9a",
>>
>
Thanks for reviewing.
There is one line change in the upcoming latest patch so I didn't add your Acked-by.
Best regards,
TH
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* Re: [PATCH 1/8] dt-bindings: phy: axiado,ax3000-emmc-phy: add Axiado eMMC PHY document
From: Tzu-Hao Wei @ 2026-02-05 3:36 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: SriNavmani A, Prasad Bolisetty, Vinod Koul, Neil Armstrong,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Harshit Shah,
Ulf Hansson, Adrian Hunter, Michal Simek, linux-phy, devicetree,
linux-arm-kernel, linux-kernel, linux-mmc
In-Reply-To: <20251223-adder-of-imminent-recreation-ea90c0@quoll>
On 12/23/2025 10:23 PM, Krzysztof Kozlowski wrote:
> CAUTION: This email originated from outside of the organization. Do not click links or open attachments unless you recognize the sender and know the content is safe.
>
>
> On Mon, Dec 22, 2025 at 04:45:00PM +0800, Tzu-Hao Wei wrote:
>> From: SriNavmani A <srinavmani@axiado.com>
>>
>> Add device tree bindings for the Axiado AX3000 eMMC PHY. The bindings
>> define the required properties for configuring the external Arasan
>
> Ah, and also:
>
> A nit, subject: drop second/last, redundant "document". The
> "dt-bindings" prefix is already stating that these are documents.
> See also:
> https://elixir.bootlin.com/linux/v6.17-rc3/source/Documentation/devicetree/bindings/submitting-patches.rst#L18
>
> Best regards,
> Krzysztof
>
Revised the subject in the new patch
Best regards,
TH
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox