[net-next,PATCH v6 3/3] net: phy: realtek: Add property to enable SSC

Marek Vasut
2026-04-05T23:30:30Z

Add support for spread spectrum clocking (SSC) on RTL8211F(D)(I)-CG,
RTL8211FS(I)(-VS)-CG, RTL8211FG(I)(-VS)-CG PHYs. The implementation
follows EMI improvement application note Rev. 1.2 for these PHYs.

The current implementation enables SSC for both RXC and SYSCLK clock
signals. Introduce DT properties 'realtek,clkout-ssc-enable',
'realtek,rxc-ssc-enable' and 'realtek,sysclk-ssc-enable' which control
CLKOUT, RXC and SYSCLK SSC spread spectrum clocking enablement on these
signals.

Signed-off-by: Marek Vasut <marek.vasut@mailbox.org>
---
Cc: "David S. Miller" <davem@davemloft.net>
Cc: Aleksander Jan Bajkowski <olek2@wp.pl>
Cc: Andrew Lunn <andrew@lunn.ch>
Cc: Conor Dooley <conor+dt@kernel.org>
Cc: Eric Dumazet <edumazet@google.com>
Cc: Florian Fainelli <f.fainelli@gmail.com>
Cc: Heiner Kallweit <hkallweit1@gmail.com>
Cc: Ivan Galkin <ivan.galkin@axis.com>
Cc: Jakub Kicinski <kuba@kernel.org>
Cc: Krzysztof Kozlowski <krzk+dt@kernel.org>
Cc: Michael Klein <michael@fossekall.de>
Cc: Paolo Abeni <pabeni@redhat.com>
Cc: Rob Herring <robh@kernel.org>
Cc: Russell King <linux@armlinux.org.uk>
Cc: Vladimir Oltean <vladimir.oltean@nxp.com>
Cc: devicetree@vger.kernel.org
Cc: netdev@vger.kernel.org
---
V2: Split SSC clock control for each CLKOUT, RXC, SYSCLK signal
V3: Update RTL8211FVD PHYCR2 comment to state this PHY has PHYCR2 register,
but SSC configuration is not supported due to different layout.
V4: - Perform all SSC configuration before disabling CLKOUT
- Perform all SSC configuration in the same order as in the SSC appnote
- Rebase on current next, retest using spectrum analyzer again
V5: s@SCC@SSC@ typo
V6: Drop RTL8211F_SSC_CLKOUT write in favor of setting PHYCR2 bit 7,12,13
---
drivers/net/phy/realtek/realtek_main.c | 127 +++++++++++++++++++++++++
1 file changed, 127 insertions(+)

diff --git a/drivers/net/phy/realtek/realtek_main.c b/drivers/net/phy/realtek/realtek_main.c
index 023e47ad605bd..f31969de3ba99 100644
--- a/drivers/net/phy/realtek/realtek_main.c
+++ b/drivers/net/phy/realtek/realtek_main.c
@@ -75,10 +75,18 @@

#define RTL8211F_PHYCR2 0x19
#define RTL8211F_CLKOUT_EN BIT(0)
+#define RTL8211F_SYSCLK_SSC_EN BIT(3)
#define RTL8211F_PHYCR2_PHY_EEE_ENABLE BIT(5)
+#define RTL8211F_CLKOUT_SSC_EN BIT(7)
+#define RTL8211F_CLKOUT_SSC_CAP GENMASK(13, 12)

#define RTL8211F_INSR 0x1d

+/* RTL8211F SSC settings */
+#define RTL8211F_SSC_PAGE 0xc44
+#define RTL8211F_SSC_RXC 0x13
+#define RTL8211F_SSC_SYSCLK 0x17
+
/* RTL8211F LED configuration */
#define RTL8211F_LEDCR_PAGE 0xd04
#define RTL8211F_LEDCR 0x10
@@ -215,6 +223,9 @@ MODULE_LICENSE("GPL");
struct rtl821x_priv {
bool enable_aldps;
bool disable_clk_out;
+ bool enable_clkout_ssc;
+ bool enable_rxc_ssc;
+ bool enable_sysclk_ssc;
struct clk *clk;
/* rtl8211f */
u16 iner;
@@ -278,6 +289,12 @@ static int rtl821x_probe(struct phy_device *phydev)
"realtek,aldps-enable");
priv->disable_clk_out = of_property_read_bool(dev->of_node,
"realtek,clkout-disable");
+ priv->enable_clkout_ssc = of_property_read_bool(dev->of_node,
+ "realtek,clkout-ssc-enable");
+ priv->enable_rxc_ssc = of_property_read_bool(dev->of_node,
+ "realtek,rxc-ssc-enable");
+ priv->enable_sysclk_ssc = of_property_read_bool(dev->of_node,
+ "realtek,sysclk-ssc-enable");

phydev->priv = priv;

@@ -707,6 +724,104 @@ static int rtl8211f_config_phy_eee(struct phy_device *phydev)
RTL8211F_PHYCR2_PHY_EEE_ENABLE, 0);
}

+static int rtl8211f_config_clkout_ssc(struct phy_device *phydev)
+{
+ struct rtl821x_priv *priv = phydev->priv;
+ struct device *dev = &phydev->mdio.dev;
+ int ret;
+
+ /* The value is preserved if the device tree property is absent */
+ if (!priv->enable_clkout_ssc)
+ return 0;
+
+ /* RTL8211FVD has PHYCR2 register, but configuration of CLKOUT SSC
+ * is not currently supported by this driver due to different bit
+ * layout.
+ */
+ if (phydev->drv->phy_id == RTL_8211FVD_PHYID)
+ return 0;
+
+ /* Unnamed registers from EMI improvement parameters application note 1.2 */
+ ret = phy_write_paged(phydev, 0xd09, 0x10, 0xcf00);
+ if (ret < 0) {
+ dev_err(dev, "CLKOUT SSC initialization failed: %pe\n", ERR_PTR(ret));
+ return ret;
+ }
+
+ /* Enable CLKOUT SSC and CLKOUT SSC Capability using PHYCR2
+ * bits 7, 12, 13. This matches the register 25 write 0x38C3
+ * from the EMI improvement parameters application note 1.2
+ * section 2.3, without affecting unrelated bits.
+ */
+ ret = phy_set_bits(phydev, RTL8211F_PHYCR2,
+ RTL8211F_CLKOUT_SSC_CAP | RTL8211F_CLKOUT_SSC_EN);
+ if (ret < 0) {
+ dev_err(dev, "CLKOUT SSC enable failed: %pe\n", ERR_PTR(ret));
+ return ret;
+ }
+
+ return 0;
+}
+
+static int rtl8211f_config_rxc_ssc(struct phy_device *phydev)
+{
+ struct rtl821x_priv *priv = phydev->priv;
+ struct device *dev = &phydev->mdio.dev;
+ int ret;
+
+ /* The value is preserved if the device tree property is absent */
+ if (!priv->enable_rxc_ssc)
+ return 0;
+
+ /* RTL8211FVD has PHYCR2 register, but configuration of RXC SSC
+ * is not currently supported by this driver due to different bit
+ * layout.
+ */
+ if (phydev->drv->phy_id == RTL_8211FVD_PHYID)
+ return 0;
+
+ ret = phy_write_paged(phydev, RTL8211F_SSC_PAGE, RTL8211F_SSC_RXC, 0x5f00);
+ if (ret < 0) {
+ dev_err(dev, "RXC SSC configuration failed: %pe\n", ERR_PTR(ret));
+ return ret;
+ }
+
+ return 0;
+}
+
+static int rtl8211f_config_sysclk_ssc(struct phy_device *phydev)
+{
+ struct rtl821x_priv *priv = phydev->priv;
+ struct device *dev = &phydev->mdio.dev;
+ int ret;
+
+ /* The value is preserved if the device tree property is absent */
+ if (!priv->enable_sysclk_ssc)
+ return 0;
+
+ /* RTL8211FVD has PHYCR2 register, but configuration of SYSCLK SSC
+ * is not currently supported by this driver due to different bit
+ * layout.
+ */
+ if (phydev->drv->phy_id == RTL_8211FVD_PHYID)
+ return 0;
+
+ ret = phy_write_paged(phydev, RTL8211F_SSC_PAGE, RTL8211F_SSC_SYSCLK, 0x4f00);
+ if (ret < 0) {
+ dev_err(dev, "SYSCLK SSC configuration failed: %pe\n", ERR_PTR(ret));
+ return ret;
+ }
+
+ /* Enable SSC */
+ ret = phy_set_bits(phydev, RTL8211F_PHYCR2, RTL8211F_SYSCLK_SSC_EN);
+ if (ret < 0) {
+ dev_err(dev, "SYSCLK SSC enable failed: %pe\n", ERR_PTR(ret));
+ return ret;
+ }
+
+ return 0;
+}
+
static int rtl8211f_config_init(struct phy_device *phydev)
{
struct device *dev = &phydev->mdio.dev;
@@ -723,6 +838,18 @@ static int rtl8211f_config_init(struct phy_device *phydev)
if (ret)
return ret;

+ ret = rtl8211f_config_rxc_ssc(phydev);
+ if (ret)
+ return ret;
+
+ ret = rtl8211f_config_sysclk_ssc(phydev);
+ if (ret)
+ return ret;
+
+ ret = rtl8211f_config_clkout_ssc(phydev);
+ if (ret)
+ return ret;
+
ret = rtl8211f_config_clk_out(phydev);
if (ret) {
dev_err(dev, "clkout configuration failed: %pe\n",
--
2.53.0