Merge branch 'phy-polarity-inversion-via-generic-device-tree-properties'

Vladimir Oltean says:

====================
PHY polarity inversion via generic device tree properties

Using the "rx-polarity" and "tx-polarity" device tree properties
introduced in linux-phy and merged into net-next in
commit 96a2d53f24 ("Merge tag 'phy_common_properties' of git://git.kernel.org/pub/scm/linux/kernel/git/phy/linux-phy")
we convert here two existing networking use cases - the EN8811H Ethernet
PHY and the Mediatek LynxI PCS.

Original cover letter:

Polarity inversion (described in patch 4/10) is a feature with at least
4 potential new users waiting for a generic description:
- Horatiu Vultur with the lan966x SerDes
- Daniel Golle with the MaxLinear GSW1xx switches
- Bjørn Mork with the AN8811HB Ethernet PHY
- Me with a custom SJA1105 board, switch which uses the DesignWare XPCS

I became interested in exploring the problem space because I was averse
to the idea of adding vendor-specific device tree properties to describe
a common need.

This set contains an implementation of a generic feature that should
cater to all known needs that were identified during my documentation
phase.

Apart from what is converted here, we also have the following, which I
did not touch:
- "st,px_rx_pol_inv" - its binding is a .txt file and I don't have time
  for such a large detour to convert it to dtschema.
- "st,pcie-tx-pol-inv" and "st,sata-tx-pol-inv" - these are defined in a
  .txt schema but are not implemented in any driver. My verdict would be
  "delete the properties" but again, I would prefer not introducing such
  dependency to this series.
====================

Link: https://patch.msgid.link/20260119091220.1493761-1-vladimir.oltean@nxp.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski 2026-01-21 19:47:01 -08:00
commit 331cf8fc18
9 changed files with 121 additions and 43 deletions

View File

@ -16,6 +16,7 @@ description:
allOf:
- $ref: ethernet-phy.yaml#
- $ref: /schemas/phy/phy-common-props.yaml#
properties:
compatible:
@ -30,12 +31,18 @@ properties:
description:
Reverse rx polarity of the SERDES. This is the receiving
side of the lines from the MAC towards the EN881H.
This property is deprecated, for details please refer to
Documentation/devicetree/bindings/phy/phy-common-props.yaml
deprecated: true
airoha,pnswap-tx:
type: boolean
description:
Reverse tx polarity of SERDES. This is the transmitting
side of the lines from EN8811H towards the MAC.
This property is deprecated, for details please refer to
Documentation/devicetree/bindings/phy/phy-common-props.yaml
deprecated: true
required:
- reg
@ -44,6 +51,8 @@ unevaluatedProperties: false
examples:
- |
#include <dt-bindings/phy/phy.h>
mdio {
#address-cells = <1>;
#size-cells = <0>;
@ -51,6 +60,6 @@ examples:
ethernet-phy@1 {
compatible = "ethernet-phy-id03a2.a411";
reg = <1>;
airoha,pnswap-rx;
rx-polarity = <PHY_POL_INVERT>;
};
};

View File

@ -39,12 +39,17 @@ properties:
const: 1
mediatek,pnswap:
description: Invert polarity of the SGMII data lanes
description:
Invert polarity of the SGMII data lanes.
This property is deprecated, for details please refer to
Documentation/devicetree/bindings/phy/phy-common-props.yaml.
type: boolean
deprecated: true
pcs:
type: object
description: MediaTek LynxI HSGMII PCS
$ref: /schemas/phy/phy-common-props.yaml#
properties:
compatible:
const: mediatek,mt7988-sgmii

View File

@ -113,8 +113,8 @@ mt7531_create_sgmii(struct mt7530_priv *priv)
ret = PTR_ERR(regmap);
break;
}
pcs = mtk_pcs_lynxi_create(priv->dev, regmap,
MT7531_PHYA_CTRL_SIGNAL3, 0);
pcs = mtk_pcs_lynxi_create(priv->dev, NULL, regmap,
MT7531_PHYA_CTRL_SIGNAL3);
if (!pcs) {
ret = -ENXIO;
break;

View File

@ -4994,7 +4994,6 @@ static int mtk_sgmii_init(struct mtk_eth *eth)
{
struct device_node *np;
struct regmap *regmap;
u32 flags;
int i;
for (i = 0; i < MTK_MAX_DEVS; i++) {
@ -5003,18 +5002,16 @@ static int mtk_sgmii_init(struct mtk_eth *eth)
break;
regmap = syscon_node_to_regmap(np);
flags = 0;
if (of_property_read_bool(np, "mediatek,pnswap"))
flags |= MTK_SGMII_FLAG_PN_SWAP;
of_node_put(np);
if (IS_ERR(regmap))
if (IS_ERR(regmap)) {
of_node_put(np);
return PTR_ERR(regmap);
}
eth->sgmii_pcs[i] = mtk_pcs_lynxi_create(eth->dev, regmap,
eth->soc->ana_rgc3,
flags);
eth->sgmii_pcs[i] = mtk_pcs_lynxi_create(eth->dev,
of_fwnode_handle(np),
regmap,
eth->soc->ana_rgc3);
of_node_put(np);
}
return 0;

View File

@ -20,6 +20,7 @@ config PCS_LYNX
config PCS_MTK_LYNXI
tristate
select PHY_COMMON_PROPS
select REGMAP
help
This module provides helpers to phylink for managing the LynxI PCS

View File

@ -11,6 +11,7 @@
#include <linux/mdio.h>
#include <linux/of.h>
#include <linux/pcs/pcs-mtk-lynxi.h>
#include <linux/phy/phy-common-props.h>
#include <linux/phylink.h>
#include <linux/regmap.h>
@ -62,8 +63,9 @@
/* Register to QPHY wrapper control */
#define SGMSYS_QPHY_WRAP_CTRL 0xec
#define SGMII_PN_SWAP_MASK GENMASK(1, 0)
#define SGMII_PN_SWAP_TX_RX (BIT(0) | BIT(1))
#define SGMII_PN_SWAP_RX BIT(1)
#define SGMII_PN_SWAP_TX BIT(0)
/* struct mtk_pcs_lynxi - This structure holds each sgmii regmap andassociated
* data
@ -81,6 +83,7 @@ struct mtk_pcs_lynxi {
phy_interface_t interface;
struct phylink_pcs pcs;
u32 flags;
struct fwnode_handle *fwnode;
};
static struct mtk_pcs_lynxi *pcs_to_mtk_pcs_lynxi(struct phylink_pcs *pcs)
@ -120,6 +123,42 @@ static void mtk_pcs_lynxi_get_state(struct phylink_pcs *pcs,
FIELD_GET(SGMII_LPA, adv));
}
static int mtk_pcs_config_polarity(struct mtk_pcs_lynxi *mpcs,
phy_interface_t interface)
{
struct fwnode_handle *fwnode = mpcs->fwnode, *pcs_fwnode;
unsigned int pol, default_pol = PHY_POL_NORMAL;
unsigned int val = 0;
int ret;
if (fwnode_property_read_bool(fwnode, "mediatek,pnswap"))
default_pol = PHY_POL_INVERT;
pcs_fwnode = fwnode_get_named_child_node(fwnode, "pcs");
ret = phy_get_rx_polarity(pcs_fwnode, phy_modes(interface),
BIT(PHY_POL_NORMAL) | BIT(PHY_POL_INVERT),
default_pol, &pol);
if (ret) {
fwnode_handle_put(pcs_fwnode);
return ret;
}
if (pol == PHY_POL_INVERT)
val |= SGMII_PN_SWAP_RX;
ret = phy_get_tx_polarity(pcs_fwnode, phy_modes(interface),
BIT(PHY_POL_NORMAL) | BIT(PHY_POL_INVERT),
default_pol, &pol);
fwnode_handle_put(pcs_fwnode);
if (ret)
return ret;
if (pol == PHY_POL_INVERT)
val |= SGMII_PN_SWAP_TX;
return regmap_update_bits(mpcs->regmap, SGMSYS_QPHY_WRAP_CTRL,
SGMII_PN_SWAP_RX | SGMII_PN_SWAP_TX, val);
}
static int mtk_pcs_lynxi_config(struct phylink_pcs *pcs, unsigned int neg_mode,
phy_interface_t interface,
const unsigned long *advertising,
@ -129,6 +168,7 @@ static int mtk_pcs_lynxi_config(struct phylink_pcs *pcs, unsigned int neg_mode,
bool mode_changed = false, changed;
unsigned int rgc3, sgm_mode, bmcr;
int advertise, link_timer;
int ret;
advertise = phylink_mii_c22_pcs_encode_advertisement(interface,
advertising);
@ -168,10 +208,9 @@ static int mtk_pcs_lynxi_config(struct phylink_pcs *pcs, unsigned int neg_mode,
regmap_set_bits(mpcs->regmap, SGMSYS_RESERVED_0,
SGMII_SW_RESET);
if (mpcs->flags & MTK_SGMII_FLAG_PN_SWAP)
regmap_update_bits(mpcs->regmap, SGMSYS_QPHY_WRAP_CTRL,
SGMII_PN_SWAP_MASK,
SGMII_PN_SWAP_TX_RX);
ret = mtk_pcs_config_polarity(mpcs, interface);
if (ret)
return ret;
if (interface == PHY_INTERFACE_MODE_2500BASEX)
rgc3 = SGMII_PHY_SPEED_3_125G;
@ -268,8 +307,8 @@ static const struct phylink_pcs_ops mtk_pcs_lynxi_ops = {
};
struct phylink_pcs *mtk_pcs_lynxi_create(struct device *dev,
struct regmap *regmap, u32 ana_rgc3,
u32 flags)
struct fwnode_handle *fwnode,
struct regmap *regmap, u32 ana_rgc3)
{
struct mtk_pcs_lynxi *mpcs;
u32 id, ver;
@ -303,10 +342,10 @@ struct phylink_pcs *mtk_pcs_lynxi_create(struct device *dev,
mpcs->ana_rgc3 = ana_rgc3;
mpcs->regmap = regmap;
mpcs->flags = flags;
mpcs->pcs.ops = &mtk_pcs_lynxi_ops;
mpcs->pcs.poll = true;
mpcs->interface = PHY_INTERFACE_MODE_NA;
mpcs->fwnode = fwnode_handle_get(fwnode);
__set_bit(PHY_INTERFACE_MODE_SGMII, mpcs->pcs.supported_interfaces);
__set_bit(PHY_INTERFACE_MODE_1000BASEX, mpcs->pcs.supported_interfaces);
@ -318,10 +357,14 @@ EXPORT_SYMBOL(mtk_pcs_lynxi_create);
void mtk_pcs_lynxi_destroy(struct phylink_pcs *pcs)
{
struct mtk_pcs_lynxi *mpcs;
if (!pcs)
return;
kfree(pcs_to_mtk_pcs_lynxi(pcs));
mpcs = pcs_to_mtk_pcs_lynxi(pcs);
fwnode_handle_put(mpcs->fwnode);
kfree(mpcs);
}
EXPORT_SYMBOL(mtk_pcs_lynxi_destroy);

View File

@ -98,6 +98,7 @@ config AS21XXX_PHY
config AIR_EN8811H_PHY
tristate "Airoha EN8811H 2.5 Gigabit PHY"
select PHY_COMMON_PROPS
help
Currently supports the Airoha EN8811H PHY.

View File

@ -14,6 +14,7 @@
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/phy.h>
#include <linux/phy/phy-common-props.h>
#include <linux/firmware.h>
#include <linux/property.h>
#include <linux/wordpart.h>
@ -966,11 +967,45 @@ static int en8811h_probe(struct phy_device *phydev)
return 0;
}
static int en8811h_config_serdes_polarity(struct phy_device *phydev)
{
struct device *dev = &phydev->mdio.dev;
unsigned int pol, default_pol;
u32 pbus_value = 0;
int ret;
default_pol = PHY_POL_NORMAL;
if (device_property_read_bool(dev, "airoha,pnswap-rx"))
default_pol = PHY_POL_INVERT;
ret = phy_get_rx_polarity(dev_fwnode(dev), phy_modes(phydev->interface),
BIT(PHY_POL_NORMAL) | BIT(PHY_POL_INVERT),
default_pol, &pol);
if (ret)
return ret;
if (pol == PHY_POL_INVERT)
pbus_value |= EN8811H_POLARITY_RX_REVERSE;
default_pol = PHY_POL_NORMAL;
if (device_property_read_bool(dev, "airoha,pnswap-tx"))
default_pol = PHY_POL_INVERT;
ret = phy_get_tx_polarity(dev_fwnode(dev), phy_modes(phydev->interface),
BIT(PHY_POL_NORMAL) | BIT(PHY_POL_INVERT),
default_pol, &pol);
if (ret)
return ret;
if (pol == PHY_POL_NORMAL)
pbus_value |= EN8811H_POLARITY_TX_NORMAL;
return air_buckpbus_reg_modify(phydev, EN8811H_POLARITY,
EN8811H_POLARITY_RX_REVERSE |
EN8811H_POLARITY_TX_NORMAL, pbus_value);
}
static int en8811h_config_init(struct phy_device *phydev)
{
struct en8811h_priv *priv = phydev->priv;
struct device *dev = &phydev->mdio.dev;
u32 pbus_value;
int ret;
/* If restart happened in .probe(), no need to restart now */
@ -1003,19 +1038,7 @@ static int en8811h_config_init(struct phy_device *phydev)
if (ret < 0)
return ret;
/* Serdes polarity */
pbus_value = 0;
if (device_property_read_bool(dev, "airoha,pnswap-rx"))
pbus_value |= EN8811H_POLARITY_RX_REVERSE;
else
pbus_value &= ~EN8811H_POLARITY_RX_REVERSE;
if (device_property_read_bool(dev, "airoha,pnswap-tx"))
pbus_value &= ~EN8811H_POLARITY_TX_NORMAL;
else
pbus_value |= EN8811H_POLARITY_TX_NORMAL;
ret = air_buckpbus_reg_modify(phydev, EN8811H_POLARITY,
EN8811H_POLARITY_RX_REVERSE |
EN8811H_POLARITY_TX_NORMAL, pbus_value);
ret = en8811h_config_serdes_polarity(phydev);
if (ret < 0)
return ret;

View File

@ -5,9 +5,8 @@
#include <linux/phylink.h>
#include <linux/regmap.h>
#define MTK_SGMII_FLAG_PN_SWAP BIT(0)
struct phylink_pcs *mtk_pcs_lynxi_create(struct device *dev,
struct regmap *regmap,
u32 ana_rgc3, u32 flags);
struct fwnode_handle *fwnode,
struct regmap *regmap, u32 ana_rgc3);
void mtk_pcs_lynxi_destroy(struct phylink_pcs *pcs);
#endif