* [PATCH v2 phy-next 15/15] MAINTAINERS: expand Lynx 28G entry to cover Lynx 10G SerDes
From: Vladimir Oltean @ 2026-05-29 17:15 UTC (permalink / raw)
To: linux-phy
Cc: Ioana Ciornei, Vinod Koul, Neil Armstrong, Tanjeff Moos,
linux-kernel, devicetree, Conor Dooley, Krzysztof Kozlowski,
Rob Herring
In-Reply-To: <20260529171509.1163787-1-vladimir.oltean@nxp.com>
The lynx-28g and lynx-10g drivers share code and hardware architecture,
so let them be covered by a single MAINTAINERS entry.
Add myself as a second maintainer alongside Ioana Ciornei.
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
---
Cc: devicetree@vger.kernel.org
Cc: Conor Dooley <conor+dt@kernel.org>
Cc: Krzysztof Kozlowski <krzk+dt@kernel.org>
Cc: Rob Herring <robh@kernel.org>
v1->v2: none
---
MAINTAINERS | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/MAINTAINERS b/MAINTAINERS
index 4087b67bbc69..bdae5acf8d50 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15405,12 +15405,18 @@ S: Maintained
F: Documentation/devicetree/bindings/iio/light/liteon,ltr390.yaml
F: drivers/iio/light/ltr390.c
-LYNX 28G SERDES PHY DRIVER
+LYNX SERDES PHY DRIVERS
M: Ioana Ciornei <ioana.ciornei@nxp.com>
+M: Vladimir Oltean <vladimir.oltean@nxp.com>
L: netdev@vger.kernel.org
S: Supported
+F: Documentation/devicetree/bindings/phy/fsl,lynx-10g.yaml
F: Documentation/devicetree/bindings/phy/fsl,lynx-28g.yaml
+F: drivers/phy/freescale/phy-fsl-lynx-10g.c
F: drivers/phy/freescale/phy-fsl-lynx-28g.c
+F: drivers/phy/freescale/phy-fsl-lynx-core.c
+F: drivers/phy/freescale/phy-fsl-lynx-core.h
+F: include/soc/fsl/phy-fsl-lynx.h
LYNX PCS MODULE
M: Ioana Ciornei <ioana.ciornei@nxp.com>
--
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 v2 phy-next 12/15] phy: lynx-28g: improve phy_validate() procedure
From: Vladimir Oltean @ 2026-05-29 17:15 UTC (permalink / raw)
To: linux-phy
Cc: Ioana Ciornei, Vinod Koul, Neil Armstrong, Tanjeff Moos,
linux-kernel
In-Reply-To: <20260529171509.1163787-1-vladimir.oltean@nxp.com>
lynx_28g_validate() suffers from the following shortcomings:
- Changing the protocol should not be possible if the source protocol of
the lane is unsupported. This is because lynx_28g_proto_conf[] only
covers the register deltas between any pair of supported lane modes,
but that delta is probably incomplete if the source protocol is, say,
PCIe (which is currently assimilated by the driver to
LANE_MODE_UNKNOWN).
lynx_28g_proto_conf() does refuse changing the protocol if the current
one is unsupported, but we shouldn't advertise it via phy_validate()
at all.
The phy_set_mode_ext() call should perform the exact same
verifications as phy_validate() did, in case the caller bypassed
phy_validate(). So we need to centralize the logic into a common
validation. But lynx_28g_set_mode() later needs the lane_mode that
this validation needs to compute anyway, so name the common helper
lynx_phy_mode_to_lane_mode() and let it return that lane_mode.
- Future core sanity checks on phy_validate() will want to differentiate
the case where this optional method is not implemented from the case
where the mode/submode is really not supported. So we shouldn't return
-EOPNOTSUPP from lynx_28g_validate(), but -EINVAL to signal that we do
implement the operation:
https://lore.kernel.org/linux-phy/aY2lFTIALH7qEJmM@shell.armlinux.org.uk/
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
---
v1->v2: patch is new
---
drivers/phy/freescale/phy-fsl-lynx-28g.c | 38 +++++++----------------
drivers/phy/freescale/phy-fsl-lynx-core.c | 30 ++++++++++++++++++
drivers/phy/freescale/phy-fsl-lynx-core.h | 2 ++
3 files changed, 43 insertions(+), 27 deletions(-)
diff --git a/drivers/phy/freescale/phy-fsl-lynx-28g.c b/drivers/phy/freescale/phy-fsl-lynx-28g.c
index 50b991870edb..38afcd081a2a 100644
--- a/drivers/phy/freescale/phy-fsl-lynx-28g.c
+++ b/drivers/phy/freescale/phy-fsl-lynx-28g.c
@@ -968,22 +968,22 @@ static int lynx_28g_lane_enable_pcvt(struct lynx_28g_lane *lane,
return err;
}
+static int lynx_28g_validate(struct phy *phy, enum phy_mode mode, int submode,
+ union phy_configure_opts *opts)
+{
+ return lynx_phy_mode_to_lane_mode(phy, mode, submode, NULL);
+}
+
static int lynx_28g_set_mode(struct phy *phy, enum phy_mode mode, int submode)
{
- struct lynx_28g_lane *lane = phy_get_drvdata(phy);
+ struct lynx_lane *lane = phy_get_drvdata(phy);
int powered_up = lane->powered_up;
enum lynx_lane_mode lane_mode;
- int err = 0;
-
- if (mode != PHY_MODE_ETHERNET)
- return -EOPNOTSUPP;
-
- if (lane->mode == LANE_MODE_UNKNOWN)
- return -EOPNOTSUPP;
+ int err;
- lane_mode = phy_interface_to_lane_mode(submode);
- if (!lynx_lane_supports_mode(lane, lane_mode))
- return -EOPNOTSUPP;
+ err = lynx_phy_mode_to_lane_mode(phy, mode, submode, &lane_mode);
+ if (err)
+ return err;
if (lane_mode == lane->mode)
return 0;
@@ -1014,22 +1014,6 @@ static int lynx_28g_set_mode(struct phy *phy, enum phy_mode mode, int submode)
return err;
}
-static int lynx_28g_validate(struct phy *phy, enum phy_mode mode, int submode,
- union phy_configure_opts *opts __always_unused)
-{
- struct lynx_28g_lane *lane = phy_get_drvdata(phy);
- enum lynx_lane_mode lane_mode;
-
- if (mode != PHY_MODE_ETHERNET)
- return -EOPNOTSUPP;
-
- lane_mode = phy_interface_to_lane_mode(submode);
- if (!lynx_lane_supports_mode(lane, lane_mode))
- return -EOPNOTSUPP;
-
- return 0;
-}
-
static int lynx_28g_init(struct phy *phy)
{
struct lynx_28g_lane *lane = phy_get_drvdata(phy);
diff --git a/drivers/phy/freescale/phy-fsl-lynx-core.c b/drivers/phy/freescale/phy-fsl-lynx-core.c
index 92f8c82f5b8d..a9fda85a7783 100644
--- a/drivers/phy/freescale/phy-fsl-lynx-core.c
+++ b/drivers/phy/freescale/phy-fsl-lynx-core.c
@@ -89,6 +89,36 @@ bool lynx_lane_supports_mode(struct lynx_lane *lane, enum lynx_lane_mode mode)
}
EXPORT_SYMBOL_NS_GPL(lynx_lane_supports_mode, "PHY_FSL_LYNX");
+/* Translate the mode/submode from phy_validate() and phy_set_mode_ext() to a
+ * lane_mode and return 0 if it is supported and we can transition to it from
+ * the current lane mode, or return negative error otherwise.
+ */
+int lynx_phy_mode_to_lane_mode(struct phy *phy, enum phy_mode mode,
+ int submode, enum lynx_lane_mode *lane_mode)
+{
+ struct lynx_lane *lane = phy_get_drvdata(phy);
+ enum lynx_lane_mode tmp_lane_mode;
+
+ /* The protocol configuration tables are incomplete for full lane
+ * reconfiguration from an arbitrary protocol.
+ */
+ if (lane->mode == LANE_MODE_UNKNOWN)
+ return -EINVAL;
+
+ if (mode != PHY_MODE_ETHERNET)
+ return -EINVAL;
+
+ tmp_lane_mode = phy_interface_to_lane_mode(submode);
+ if (!lynx_lane_supports_mode(lane, tmp_lane_mode))
+ return -EINVAL;
+
+ if (lane_mode)
+ *lane_mode = tmp_lane_mode;
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(lynx_phy_mode_to_lane_mode, "PHY_FSL_LYNX");
+
struct lynx_pll *lynx_pll_get(struct lynx_priv *priv, enum lynx_lane_mode mode)
{
struct lynx_pll *pll;
diff --git a/drivers/phy/freescale/phy-fsl-lynx-core.h b/drivers/phy/freescale/phy-fsl-lynx-core.h
index 3d9508dfb2c1..37fa4b544faa 100644
--- a/drivers/phy/freescale/phy-fsl-lynx-core.h
+++ b/drivers/phy/freescale/phy-fsl-lynx-core.h
@@ -113,6 +113,8 @@ void lynx_remove(struct platform_device *pdev);
const char *lynx_lane_mode_str(enum lynx_lane_mode lane_mode);
enum lynx_lane_mode phy_interface_to_lane_mode(phy_interface_t intf);
bool lynx_lane_supports_mode(struct lynx_lane *lane, enum lynx_lane_mode mode);
+int lynx_phy_mode_to_lane_mode(struct phy *phy, enum phy_mode mode,
+ int submode, enum lynx_lane_mode *lane_mode);
struct lynx_pll *lynx_pll_get(struct lynx_priv *priv, enum lynx_lane_mode mode);
--
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 v2 phy-next 13/15] dt-bindings: phy: lynx-10g: initial document
From: Vladimir Oltean @ 2026-05-29 17:15 UTC (permalink / raw)
To: linux-phy
Cc: Ioana Ciornei, Vinod Koul, Neil Armstrong, Tanjeff Moos,
linux-kernel, devicetree, Conor Dooley, Krzysztof Kozlowski,
Rob Herring
In-Reply-To: <20260529171509.1163787-1-vladimir.oltean@nxp.com>
Add a schema for the 10G Lynx SerDes. This is very similar to the modern
form of the 28G Lynx SerDes, which is very much the intention.
We allow both forms of #phy-cells = <1> in the top-level provider
and #phy-cells = <0> in the per-lane provider for more flexibility to
consumers, and because the kernel code is shared with the 28G Lynx which
already has that support for compatibility reasons.
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
---
Cc: devicetree@vger.kernel.org
Cc: Conor Dooley <conor+dt@kernel.org>
Cc: Krzysztof Kozlowski <krzk+dt@kernel.org>
Cc: Rob Herring <robh@kernel.org>
v1->v2:
- move patch later in series, right before driver
- deliberately ignoring this Sashiko feedback:
https://lore.kernel.org/linux-phy/20260529125017.ifqunh52gdzhthdg@skbuf/
---
.../devicetree/bindings/phy/fsl,lynx-10g.yaml | 131 ++++++++++++++++++
1 file changed, 131 insertions(+)
create mode 100644 Documentation/devicetree/bindings/phy/fsl,lynx-10g.yaml
diff --git a/Documentation/devicetree/bindings/phy/fsl,lynx-10g.yaml b/Documentation/devicetree/bindings/phy/fsl,lynx-10g.yaml
new file mode 100644
index 000000000000..993f076bba4e
--- /dev/null
+++ b/Documentation/devicetree/bindings/phy/fsl,lynx-10g.yaml
@@ -0,0 +1,131 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/phy/fsl,lynx-10g.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Freescale Lynx 10G SerDes PHY
+
+maintainers:
+ - Vladimir Oltean <vladimir.oltean@nxp.com>
+
+description:
+ The 10G Lynx is a multi-protocol SerDes block which handles networking, PCIe,
+ SATA and other high-speed interfaces. It is present on most QorIQ and
+ Layerscape SoCs. The register map is common, but the integration is
+ SoC-specific, with the differences consisting in register endianness, the
+ number of lanes, protocol converters available per lane and their location in
+ the PCCR registers. Some SoCs have multiple SerDes blocks and those differ in
+ their protocol capabilities per lane.
+
+properties:
+ compatible:
+ description:
+ There is intentionally no generic fsl,lynx-10g compatible string due to
+ the hardware inability to report its capabilities, despite having a
+ common register map.
+ enum:
+ - fsl,ls1028a-serdes
+ - fsl,ls1046a-serdes1
+ - fsl,ls1046a-serdes2
+ - fsl,ls1088a-serdes1
+ - fsl,ls1088a-serdes2
+ - fsl,ls2088a-serdes1
+ - fsl,ls2088a-serdes2
+
+ reg:
+ maxItems: 1
+
+ big-endian: true
+
+ "#phy-cells":
+ const: 1
+
+ "#address-cells":
+ const: 1
+
+ "#size-cells":
+ const: 0
+
+patternProperties:
+ "^phy@[0-7]$":
+ type: object
+ description: SerDes lane (single RX/TX differential pair)
+
+ properties:
+ reg:
+ minimum: 0
+ maximum: 7
+ description: Lane index as seen in register map
+
+ "#phy-cells":
+ const: 0
+
+ required:
+ - reg
+ - "#phy-cells"
+
+ additionalProperties: false
+
+required:
+ - compatible
+ - reg
+ - "#phy-cells"
+ - "#address-cells"
+ - "#size-cells"
+
+allOf:
+ - if:
+ properties:
+ compatible:
+ contains:
+ enum:
+ - fsl,ls1028a-serdes
+ - fsl,ls1046a-serdes1
+ - fsl,ls1046a-serdes2
+ - fsl,ls1088a-serdes1
+ - fsl,ls1088a-serdes2
+ then:
+ patternProperties:
+ "^phy@[0-7]$":
+ properties:
+ reg:
+ minimum: 0
+ maximum: 3
+
+additionalProperties: false
+
+examples:
+ - |
+ soc {
+ #address-cells = <2>;
+ #size-cells = <2>;
+
+ serdes@1ea0000 {
+ compatible = "fsl,ls1028a-serdes";
+ reg = <0x0 0x1ea0000 0x0 0xffff>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ #phy-cells = <1>;
+
+ phy@0 {
+ reg = <0>;
+ #phy-cells = <0>;
+ };
+
+ phy@1 {
+ reg = <1>;
+ #phy-cells = <0>;
+ };
+
+ phy@2 {
+ reg = <2>;
+ #phy-cells = <0>;
+ };
+
+ phy@3 {
+ reg = <3>;
+ #phy-cells = <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 v2 phy-next 10/15] phy: lynx-28g: add support for big endian register maps
From: Vladimir Oltean @ 2026-05-29 17:15 UTC (permalink / raw)
To: linux-phy
Cc: Ioana Ciornei, Vinod Koul, Neil Armstrong, Tanjeff Moos,
linux-kernel
In-Reply-To: <20260529171509.1163787-1-vladimir.oltean@nxp.com>
Some 10G Lynx SerDes blocks are big endian and require byte swapping
because the CPUs are little endian armv8 (LS1046A). Parse the
"big-endian" device tree property, and modify the base lynx_read() and
lynx_write() accessors to test this property before issuing either the
ioread32() or ioread32be() variants (as per
Documentation/driver-api/device-io.rst).
All other accessors - lynx_rmw(), lynx_lane_read(), lynx_lane_write(),
lynx_lane_rmw(), lynx_pll_read() - need to go through these endian-aware
helpers.
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
---
v1->v2:
- none. Deliberately ignoring this Sashiko feedback:
https://lore.kernel.org/linux-phy/20260529120005.icj44ffdvdk25fjm@skbuf/
---
drivers/phy/freescale/phy-fsl-lynx-core.c | 1 +
drivers/phy/freescale/phy-fsl-lynx-core.h | 36 ++++++++++++++++-------
2 files changed, 27 insertions(+), 10 deletions(-)
diff --git a/drivers/phy/freescale/phy-fsl-lynx-core.c b/drivers/phy/freescale/phy-fsl-lynx-core.c
index b14d67e574f7..92f8c82f5b8d 100644
--- a/drivers/phy/freescale/phy-fsl-lynx-core.c
+++ b/drivers/phy/freescale/phy-fsl-lynx-core.c
@@ -295,6 +295,7 @@ int lynx_probe(struct platform_device *pdev, const struct lynx_info *info,
priv->dev = dev;
priv->info = info;
+ priv->big_endian = device_property_read_bool(dev, "big-endian");
dev_set_drvdata(dev, priv);
spin_lock_init(&priv->pcc_lock);
INIT_DELAYED_WORK(&priv->cdr_check, lynx_cdr_lock_check);
diff --git a/drivers/phy/freescale/phy-fsl-lynx-core.h b/drivers/phy/freescale/phy-fsl-lynx-core.h
index e8b280cc9b38..d82e529fa65a 100644
--- a/drivers/phy/freescale/phy-fsl-lynx-core.h
+++ b/drivers/phy/freescale/phy-fsl-lynx-core.h
@@ -58,36 +58,52 @@ struct lynx_priv {
* like PCCn
*/
spinlock_t pcc_lock;
+ bool big_endian;
struct lynx_pll pll[LYNX_NUM_PLL];
struct lynx_lane *lane;
struct delayed_work cdr_check;
};
+static inline u32 lynx_read(struct lynx_priv *priv, unsigned long off)
+{
+ void __iomem *reg = priv->base + off;
+
+ if (priv->big_endian)
+ return ioread32be(reg);
+
+ return ioread32(reg);
+}
+
+static inline void lynx_write(struct lynx_priv *priv, unsigned long off, u32 val)
+{
+ void __iomem *reg = priv->base + off;
+
+ if (priv->big_endian)
+ return iowrite32be(val, reg);
+
+ return iowrite32(val, reg);
+}
+
static inline void lynx_rmw(struct lynx_priv *priv, unsigned long off, u32 val,
u32 mask)
{
- void __iomem *reg = priv->base + off;
u32 orig, tmp;
- orig = ioread32(reg);
+ orig = lynx_read(priv, off);
tmp = orig & ~mask;
tmp |= val;
- iowrite32(tmp, reg);
+ lynx_write(priv, off, tmp);
}
-#define lynx_read(priv, off) \
- ioread32((priv)->base + (off))
-#define lynx_write(priv, off, val) \
- iowrite32(val, (priv)->base + (off))
#define lynx_lane_rmw(lane, reg, val, mask) \
lynx_rmw((lane)->priv, reg(lane->id), val, mask)
#define lynx_lane_read(lane, reg) \
- ioread32((lane)->priv->base + reg((lane)->id))
+ lynx_read((lane)->priv, reg((lane)->id))
#define lynx_lane_write(lane, reg, val) \
- iowrite32(val, (lane)->priv->base + reg((lane)->id))
+ lynx_write((lane)->priv, reg((lane)->id), val)
#define lynx_pll_read(pll, reg) \
- ioread32((pll)->priv->base + reg((pll)->id))
+ lynx_read((pll)->priv, reg((pll)->id))
int lynx_probe(struct platform_device *pdev, const struct lynx_info *info,
const struct phy_ops *phy_ops);
--
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 v2 phy-next 11/15] phy: lynx-28g: optimize read-modify-write operation
From: Vladimir Oltean @ 2026-05-29 17:15 UTC (permalink / raw)
To: linux-phy
Cc: Ioana Ciornei, Vinod Koul, Neil Armstrong, Tanjeff Moos,
linux-kernel
In-Reply-To: <20260529171509.1163787-1-vladimir.oltean@nxp.com>
It is unnecessary to rewrite a register if the masked field already
contains the desired value upon reading. The hardware behaviour does not
depend upon register writes with identical values.
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
---
v1->v2: none
---
drivers/phy/freescale/phy-fsl-lynx-core.h | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/phy/freescale/phy-fsl-lynx-core.h b/drivers/phy/freescale/phy-fsl-lynx-core.h
index d82e529fa65a..3d9508dfb2c1 100644
--- a/drivers/phy/freescale/phy-fsl-lynx-core.h
+++ b/drivers/phy/freescale/phy-fsl-lynx-core.h
@@ -93,7 +93,8 @@ static inline void lynx_rmw(struct lynx_priv *priv, unsigned long off, u32 val,
orig = lynx_read(priv, off);
tmp = orig & ~mask;
tmp |= val;
- lynx_write(priv, off, tmp);
+ if (orig != tmp)
+ lynx_write(priv, off, tmp);
}
#define lynx_lane_rmw(lane, reg, val, mask) \
--
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 v2 phy-next 09/15] phy: lynx-28g: common probe() and remove()
From: Vladimir Oltean @ 2026-05-29 17:15 UTC (permalink / raw)
To: linux-phy
Cc: Ioana Ciornei, Vinod Koul, Neil Armstrong, Tanjeff Moos,
linux-kernel
In-Reply-To: <20260529171509.1163787-1-vladimir.oltean@nxp.com>
Factor the device-agnostic logic from lynx_28g_probe() and
lynx_28g_remove() into lynx_probe() and lynx_remove() inside
phy-fsl-lynx-core.c. These will be shared with the 10G Lynx driver.
Since the PLL configuration, lane configuration and CDR lock detection
procedure are going to be different, introduce lynx_info function
pointers so that this code remains in the 28G Lynx driver.
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
---
v1->v2:
- adapt to the addition of a NULL check for the "info" pointer. Note
that lynx_28g_probe() does not need to test for NULL because it only
compares the pointer to a value.
---
drivers/phy/freescale/phy-fsl-lynx-28g.c | 225 +++++-----------------
drivers/phy/freescale/phy-fsl-lynx-core.c | 170 ++++++++++++++++
drivers/phy/freescale/phy-fsl-lynx-core.h | 12 +-
3 files changed, 227 insertions(+), 180 deletions(-)
diff --git a/drivers/phy/freescale/phy-fsl-lynx-28g.c b/drivers/phy/freescale/phy-fsl-lynx-28g.c
index 25aeab478d49..50b991870edb 100644
--- a/drivers/phy/freescale/phy-fsl-lynx-28g.c
+++ b/drivers/phy/freescale/phy-fsl-lynx-28g.c
@@ -12,7 +12,6 @@
#include "phy-fsl-lynx-core.h"
#define LYNX_28G_NUM_LANE 8
-#define LYNX_28G_NUM_PLL LYNX_NUM_PLL
/* SoC IP wrapper for protocol converters */
#define PCC8 0x10a0
@@ -781,6 +780,30 @@ static bool lynx_28g_compat_lane_supports_mode(int lane,
}
}
+static void lynx_28g_cdr_lock_check(struct lynx_lane *lane)
+{
+ u32 rrstctl;
+ int err;
+
+ rrstctl = lynx_28g_lane_read(lane, LNaRRSTCTL);
+ if (!!(rrstctl & LNaRRSTCTL_CDR_LOCK))
+ return;
+
+ lynx_28g_lane_rmw(lane, LNaRRSTCTL, LNaRRSTCTL_RST_REQ,
+ LNaRRSTCTL_RST_REQ);
+
+ err = read_poll_timeout(lynx_28g_lane_read, rrstctl,
+ !!(rrstctl & LNaRRSTCTL_RST_DONE),
+ LYNX_28G_LANE_RESET_SLEEP_US,
+ LYNX_28G_LANE_RESET_TIMEOUT_US,
+ false, lane, LNaRRSTCTL);
+ if (err) {
+ dev_warn_once(&lane->phy->dev,
+ "Lane %c receiver reset failed: %pe\n",
+ 'A' + lane->id, ERR_PTR(err));
+ }
+}
+
static void lynx_28g_lane_remap_pll(struct lynx_lane *lane,
enum lynx_lane_mode lane_mode)
{
@@ -1088,50 +1111,6 @@ static void lynx_28g_pll_read_configuration(struct lynx_pll *pll)
}
}
-#define work_to_lynx(w) container_of((w), struct lynx_28g_priv, cdr_check.work)
-
-static void lynx_28g_cdr_lock_check(struct work_struct *work)
-{
- struct lynx_28g_priv *priv = work_to_lynx(work);
- struct lynx_28g_lane *lane;
- u32 rrstctl;
- int err, i;
-
- for (i = priv->info->first_lane; i < LYNX_28G_NUM_LANE; i++) {
- lane = &priv->lane[i];
- if (!lane->phy)
- continue;
-
- mutex_lock(&lane->phy->mutex);
-
- if (!lane->init || !lane->powered_up) {
- mutex_unlock(&lane->phy->mutex);
- continue;
- }
-
- rrstctl = lynx_28g_lane_read(lane, LNaRRSTCTL);
- if (!(rrstctl & LNaRRSTCTL_CDR_LOCK)) {
- lynx_28g_lane_rmw(lane, LNaRRSTCTL, LNaRRSTCTL_RST_REQ,
- LNaRRSTCTL_RST_REQ);
-
- err = read_poll_timeout(lynx_28g_lane_read, rrstctl,
- !!(rrstctl & LNaRRSTCTL_RST_DONE),
- LYNX_28G_LANE_RESET_SLEEP_US,
- LYNX_28G_LANE_RESET_TIMEOUT_US,
- false, lane, LNaRRSTCTL);
- if (err) {
- dev_warn_once(&lane->phy->dev,
- "Lane %c receiver reset failed: %pe\n",
- 'A' + lane->id, ERR_PTR(err));
- }
- }
-
- mutex_unlock(&lane->phy->mutex);
- }
- queue_delayed_work(system_power_efficient_wq, &priv->cdr_check,
- msecs_to_jiffies(1000));
-}
-
static void lynx_28g_lane_read_configuration(struct lynx_28g_lane *lane)
{
u32 pccr, pss, protocol;
@@ -1157,49 +1136,13 @@ static void lynx_28g_lane_read_configuration(struct lynx_28g_lane *lane)
}
}
-static struct phy *lynx_28g_xlate(struct device *dev,
- const struct of_phandle_args *args)
-{
- struct lynx_28g_priv *priv = dev_get_drvdata(dev);
- int idx;
-
- if (args->args_count == 0)
- return of_phy_simple_xlate(dev, args);
- else if (args->args_count != 1)
- return ERR_PTR(-ENODEV);
-
- idx = args->args[0];
-
- if (WARN_ON(idx >= LYNX_28G_NUM_LANE ||
- idx < priv->info->first_lane))
- return ERR_PTR(-EINVAL);
-
- return priv->lane[idx].phy;
-}
-
-static int lynx_28g_probe_lane(struct lynx_28g_priv *priv, int id,
- struct device_node *dn)
-{
- struct lynx_28g_lane *lane = &priv->lane[id];
- struct phy *phy;
-
- phy = devm_phy_create(priv->dev, dn, &lynx_28g_ops);
- if (IS_ERR(phy))
- return PTR_ERR(phy);
-
- lane->priv = priv;
- lane->phy = phy;
- lane->id = id;
- phy_set_drvdata(phy, lane);
- lynx_28g_lane_read_configuration(lane);
-
- return 0;
-}
-
static const struct lynx_info lynx_info_compat = {
.get_pccr = lynx_28g_get_pccr,
.get_pcvt_offset = lynx_28g_get_pcvt_offset,
.lane_supports_mode = lynx_28g_compat_lane_supports_mode,
+ .pll_read_configuration = lynx_28g_pll_read_configuration,
+ .lane_read_configuration = lynx_28g_lane_read_configuration,
+ .cdr_lock_check = lynx_28g_cdr_lock_check,
.num_lanes = LYNX_28G_NUM_LANE,
};
@@ -1207,6 +1150,9 @@ static const struct lynx_info lynx_info_lx2160a_serdes1 = {
.get_pccr = lynx_28g_get_pccr,
.get_pcvt_offset = lynx_28g_get_pcvt_offset,
.lane_supports_mode = lx2160a_serdes1_lane_supports_mode,
+ .pll_read_configuration = lynx_28g_pll_read_configuration,
+ .lane_read_configuration = lynx_28g_lane_read_configuration,
+ .cdr_lock_check = lynx_28g_cdr_lock_check,
.num_lanes = LYNX_28G_NUM_LANE,
};
@@ -1214,6 +1160,9 @@ static const struct lynx_info lynx_info_lx2160a_serdes2 = {
.get_pccr = lynx_28g_get_pccr,
.get_pcvt_offset = lynx_28g_get_pcvt_offset,
.lane_supports_mode = lx2160a_serdes2_lane_supports_mode,
+ .pll_read_configuration = lynx_28g_pll_read_configuration,
+ .lane_read_configuration = lynx_28g_lane_read_configuration,
+ .cdr_lock_check = lynx_28g_cdr_lock_check,
.num_lanes = LYNX_28G_NUM_LANE,
};
@@ -1221,6 +1170,9 @@ static const struct lynx_info lynx_info_lx2160a_serdes3 = {
.get_pccr = lynx_28g_get_pccr,
.get_pcvt_offset = lynx_28g_get_pcvt_offset,
.lane_supports_mode = lx2160a_serdes3_lane_supports_mode,
+ .pll_read_configuration = lynx_28g_pll_read_configuration,
+ .lane_read_configuration = lynx_28g_lane_read_configuration,
+ .cdr_lock_check = lynx_28g_cdr_lock_check,
.num_lanes = LYNX_28G_NUM_LANE,
};
@@ -1228,6 +1180,9 @@ static const struct lynx_info lynx_info_lx2162a_serdes1 = {
.get_pccr = lynx_28g_get_pccr,
.get_pcvt_offset = lynx_28g_get_pcvt_offset,
.lane_supports_mode = lx2162a_serdes1_lane_supports_mode,
+ .pll_read_configuration = lynx_28g_pll_read_configuration,
+ .lane_read_configuration = lynx_28g_lane_read_configuration,
+ .cdr_lock_check = lynx_28g_cdr_lock_check,
.first_lane = 4,
.num_lanes = LYNX_28G_NUM_LANE,
};
@@ -1236,112 +1191,26 @@ static const struct lynx_info lynx_info_lx2162a_serdes2 = {
.get_pccr = lynx_28g_get_pccr,
.get_pcvt_offset = lynx_28g_get_pcvt_offset,
.lane_supports_mode = lx2162a_serdes2_lane_supports_mode,
+ .pll_read_configuration = lynx_28g_pll_read_configuration,
+ .lane_read_configuration = lynx_28g_lane_read_configuration,
+ .cdr_lock_check = lynx_28g_cdr_lock_check,
.num_lanes = LYNX_28G_NUM_LANE,
};
static int lynx_28g_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
- struct phy_provider *provider;
- struct lynx_28g_priv *priv;
- struct device_node *dn;
- int err;
-
- dn = dev_of_node(dev);
- if (!dn) {
- dev_err(dev, "Device requires an OF node\n");
- return -EINVAL;
- }
-
- priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
- if (!priv)
- return -ENOMEM;
-
- priv->dev = dev;
- priv->info = of_device_get_match_data(dev);
- if (!priv->info)
- return -ENODEV;
-
- dev_set_drvdata(dev, priv);
- spin_lock_init(&priv->pcc_lock);
- INIT_DELAYED_WORK(&priv->cdr_check, lynx_28g_cdr_lock_check);
+ const struct lynx_info *info;
/*
* If we get here it means we probed on a device tree where
* "fsl,lynx-28g" wasn't the fallback, but the sole compatible string.
*/
- if (priv->info == &lynx_info_compat)
+ info = of_device_get_match_data(dev);
+ if (info == &lynx_info_compat)
dev_warn(dev, "Please update device tree to use per-device compatible strings\n");
- priv->lane = devm_kcalloc(dev, priv->info->num_lanes,
- sizeof(*priv->lane), GFP_KERNEL);
- if (!priv->lane)
- return -ENOMEM;
-
- priv->base = devm_platform_ioremap_resource(pdev, 0);
- if (IS_ERR(priv->base))
- return PTR_ERR(priv->base);
-
- for (int i = 0; i < LYNX_28G_NUM_PLL; i++) {
- struct lynx_28g_pll *pll = &priv->pll[i];
-
- pll->priv = priv;
- pll->id = i;
- lynx_28g_pll_read_configuration(pll);
- }
-
- if (of_get_child_count(dn)) {
- struct device_node *child;
-
- for_each_available_child_of_node(dn, child) {
- u32 reg;
-
- /* PHY subnode name must be 'phy'. */
- if (!(of_node_name_eq(child, "phy")))
- continue;
-
- if (of_property_read_u32(child, "reg", ®)) {
- dev_err(dev, "No \"reg\" property for %pOF\n", child);
- of_node_put(child);
- return -EINVAL;
- }
-
- if (reg < priv->info->first_lane || reg >= LYNX_28G_NUM_LANE) {
- dev_err(dev, "\"reg\" property out of range for %pOF\n", child);
- of_node_put(child);
- return -EINVAL;
- }
-
- err = lynx_28g_probe_lane(priv, reg, child);
- if (err) {
- of_node_put(child);
- return err;
- }
- }
- } else {
- for (int i = priv->info->first_lane; i < LYNX_28G_NUM_LANE; i++) {
- err = lynx_28g_probe_lane(priv, i, NULL);
- if (err)
- return err;
- }
- }
-
- provider = devm_of_phy_provider_register(dev, lynx_28g_xlate);
- if (IS_ERR(provider))
- return PTR_ERR(provider);
-
- queue_delayed_work(system_power_efficient_wq, &priv->cdr_check,
- msecs_to_jiffies(1000));
-
- return 0;
-}
-
-static void lynx_28g_remove(struct platform_device *pdev)
-{
- struct device *dev = &pdev->dev;
- struct lynx_28g_priv *priv = dev_get_drvdata(dev);
-
- cancel_delayed_work_sync(&priv->cdr_check);
+ return lynx_probe(pdev, info, &lynx_28g_ops);
}
static const struct of_device_id lynx_28g_of_match_table[] = {
@@ -1357,7 +1226,7 @@ MODULE_DEVICE_TABLE(of, lynx_28g_of_match_table);
static struct platform_driver lynx_28g_driver = {
.probe = lynx_28g_probe,
- .remove = lynx_28g_remove,
+ .remove = lynx_remove,
.driver = {
.name = "lynx-28g",
.of_match_table = lynx_28g_of_match_table,
diff --git a/drivers/phy/freescale/phy-fsl-lynx-core.c b/drivers/phy/freescale/phy-fsl-lynx-core.c
index 802e32dc6dca..b14d67e574f7 100644
--- a/drivers/phy/freescale/phy-fsl-lynx-core.c
+++ b/drivers/phy/freescale/phy-fsl-lynx-core.c
@@ -2,6 +2,7 @@
/* Copyright 2025-2026 NXP */
#include <linux/module.h>
+#include <linux/platform_device.h>
#include "phy-fsl-lynx-core.h"
@@ -202,5 +203,174 @@ int lynx_pcvt_rmw(struct lynx_lane *lane, enum lynx_lane_mode mode, int cr,
}
EXPORT_SYMBOL_NS_GPL(lynx_pcvt_rmw, "PHY_FSL_LYNX");
+#define work_to_lynx(w) container_of((w), struct lynx_priv, cdr_check.work)
+
+static void lynx_cdr_lock_check(struct work_struct *work)
+{
+ struct lynx_priv *priv = work_to_lynx(work);
+ struct lynx_lane *lane;
+
+ for (int i = priv->info->first_lane; i < priv->info->num_lanes; i++) {
+ lane = &priv->lane[i];
+ if (!lane->phy)
+ continue;
+
+ mutex_lock(&lane->phy->mutex);
+
+ if (!lane->init || !lane->powered_up) {
+ mutex_unlock(&lane->phy->mutex);
+ continue;
+ }
+
+ priv->info->cdr_lock_check(lane);
+
+ mutex_unlock(&lane->phy->mutex);
+ }
+
+ queue_delayed_work(system_power_efficient_wq, &priv->cdr_check,
+ msecs_to_jiffies(1000));
+}
+
+static struct phy *lynx_xlate(struct device *dev,
+ const struct of_phandle_args *args)
+{
+ struct lynx_priv *priv = dev_get_drvdata(dev);
+ int idx;
+
+ if (args->args_count == 0)
+ return of_phy_simple_xlate(dev, args);
+ else if (args->args_count != 1)
+ return ERR_PTR(-ENODEV);
+
+ idx = args->args[0];
+
+ if (WARN_ON(idx >= priv->info->num_lanes ||
+ idx < priv->info->first_lane))
+ return ERR_PTR(-EINVAL);
+
+ return priv->lane[idx].phy;
+}
+
+static int lynx_probe_lane(struct lynx_priv *priv, int id,
+ struct device_node *dn,
+ const struct phy_ops *phy_ops)
+{
+ struct lynx_lane *lane = &priv->lane[id];
+ struct phy *phy;
+
+ phy = devm_phy_create(priv->dev, dn, phy_ops);
+ if (IS_ERR(phy))
+ return PTR_ERR(phy);
+
+ lane->priv = priv;
+ lane->phy = phy;
+ lane->id = id;
+ phy_set_drvdata(phy, lane);
+ priv->info->lane_read_configuration(lane);
+
+ return 0;
+}
+
+int lynx_probe(struct platform_device *pdev, const struct lynx_info *info,
+ const struct phy_ops *phy_ops)
+{
+ struct device *dev = &pdev->dev;
+ struct phy_provider *provider;
+ struct device_node *dn;
+ struct lynx_priv *priv;
+ int err;
+
+ dn = dev_of_node(dev);
+ if (!dn) {
+ dev_err(dev, "Device requires an OF node\n");
+ return -EINVAL;
+ }
+
+ if (!info)
+ return -ENODEV;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->dev = dev;
+ priv->info = info;
+ dev_set_drvdata(dev, priv);
+ spin_lock_init(&priv->pcc_lock);
+ INIT_DELAYED_WORK(&priv->cdr_check, lynx_cdr_lock_check);
+
+ priv->lane = devm_kcalloc(dev, priv->info->num_lanes,
+ sizeof(*priv->lane), GFP_KERNEL);
+ if (!priv->lane)
+ return -ENOMEM;
+
+ priv->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(priv->base))
+ return PTR_ERR(priv->base);
+
+ for (int i = 0; i < LYNX_NUM_PLL; i++) {
+ struct lynx_pll *pll = &priv->pll[i];
+
+ pll->priv = priv;
+ pll->id = i;
+ priv->info->pll_read_configuration(pll);
+ }
+
+ if (of_get_child_count(dn)) {
+ struct device_node *child;
+
+ for_each_available_child_of_node(dn, child) {
+ u32 reg;
+
+ /* PHY subnode name must be 'phy'. */
+ if (!(of_node_name_eq(child, "phy")))
+ continue;
+
+ if (of_property_read_u32(child, "reg", ®)) {
+ dev_err(dev, "No \"reg\" property for %pOF\n", child);
+ of_node_put(child);
+ return -EINVAL;
+ }
+
+ if (reg < priv->info->first_lane || reg >= priv->info->num_lanes) {
+ dev_err(dev, "\"reg\" property out of range for %pOF\n", child);
+ of_node_put(child);
+ return -EINVAL;
+ }
+
+ err = lynx_probe_lane(priv, reg, child, phy_ops);
+ if (err) {
+ of_node_put(child);
+ return err;
+ }
+ }
+ } else {
+ for (int i = priv->info->first_lane; i < priv->info->num_lanes; i++) {
+ err = lynx_probe_lane(priv, i, NULL, phy_ops);
+ if (err)
+ return err;
+ }
+ }
+
+ provider = devm_of_phy_provider_register(dev, lynx_xlate);
+ if (IS_ERR(provider))
+ return PTR_ERR(provider);
+
+ queue_delayed_work(system_power_efficient_wq, &priv->cdr_check,
+ msecs_to_jiffies(1000));
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(lynx_probe, "PHY_FSL_LYNX");
+
+void lynx_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct lynx_priv *priv = dev_get_drvdata(dev);
+
+ cancel_delayed_work_sync(&priv->cdr_check);
+}
+EXPORT_SYMBOL_NS_GPL(lynx_remove, "PHY_FSL_LYNX");
+
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Freescale Lynx SerDes core functionality");
diff --git a/drivers/phy/freescale/phy-fsl-lynx-core.h b/drivers/phy/freescale/phy-fsl-lynx-core.h
index 5cd86c9543cb..e8b280cc9b38 100644
--- a/drivers/phy/freescale/phy-fsl-lynx-core.h
+++ b/drivers/phy/freescale/phy-fsl-lynx-core.h
@@ -10,14 +10,15 @@
#define LYNX_NUM_PLL 2
+struct lynx_priv;
+struct lynx_lane;
+
struct lynx_pccr {
int offset;
int width;
int shift;
};
-struct lynx_priv;
-
struct lynx_pll {
struct lynx_priv *priv;
int id;
@@ -42,6 +43,9 @@ struct lynx_info {
struct lynx_pccr *pccr);
int (*get_pcvt_offset)(int lane, enum lynx_lane_mode mode);
bool (*lane_supports_mode)(int lane, enum lynx_lane_mode mode);
+ void (*pll_read_configuration)(struct lynx_pll *pll);
+ void (*lane_read_configuration)(struct lynx_lane *lane);
+ void (*cdr_lock_check)(struct lynx_lane *lane);
int first_lane;
int num_lanes;
};
@@ -85,6 +89,10 @@ static inline void lynx_rmw(struct lynx_priv *priv, unsigned long off, u32 val,
#define lynx_pll_read(pll, reg) \
ioread32((pll)->priv->base + reg((pll)->id))
+int lynx_probe(struct platform_device *pdev, const struct lynx_info *info,
+ const struct phy_ops *phy_ops);
+void lynx_remove(struct platform_device *pdev);
+
const char *lynx_lane_mode_str(enum lynx_lane_mode lane_mode);
enum lynx_lane_mode phy_interface_to_lane_mode(phy_interface_t intf);
bool lynx_lane_supports_mode(struct lynx_lane *lane, enum lynx_lane_mode mode);
--
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 v2 phy-next 08/15] phy: lynx-28g: make lynx_28g_pll_read_configuration() callable per PLL
From: Vladimir Oltean @ 2026-05-29 17:15 UTC (permalink / raw)
To: linux-phy
Cc: Ioana Ciornei, Vinod Koul, Neil Armstrong, Tanjeff Moos,
linux-kernel
In-Reply-To: <20260529171509.1163787-1-vladimir.oltean@nxp.com>
In a future change, lynx_28g_pll_read_configuration() and
lynx_28g_lane_read_configuration() will be made methods of struct
lynx_info.
There is no functional reason, but lynx_28g_lane_read_configuration() is
called per lane and lynx_28g_pll_read_configuration() iterates over PLLs
internally. So the API exported by the lynx_info structure would not be
uniform. Change lynx_28g_pll_read_configuration() to also permit reading
the PLL configuration individually, and move the for loop at the call
site.
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
---
v1->v2: fix typo in commit message (spotted by Sashiko)
---
drivers/phy/freescale/phy-fsl-lynx-28g.c | 73 ++++++++++++------------
1 file changed, 35 insertions(+), 38 deletions(-)
diff --git a/drivers/phy/freescale/phy-fsl-lynx-28g.c b/drivers/phy/freescale/phy-fsl-lynx-28g.c
index 0076de537763..25aeab478d49 100644
--- a/drivers/phy/freescale/phy-fsl-lynx-28g.c
+++ b/drivers/phy/freescale/phy-fsl-lynx-28g.c
@@ -272,7 +272,6 @@
#define lynx_28g_lane_rmw lynx_lane_rmw
#define lynx_28g_lane_read lynx_lane_read
#define lynx_28g_lane_write lynx_lane_write
-#define lynx_28g_pll_read lynx_pll_read
#define lynx_28g_priv lynx_priv
#define lynx_28g_lane lynx_lane
@@ -1051,49 +1050,41 @@ static const struct phy_ops lynx_28g_ops = {
.owner = THIS_MODULE,
};
-static void lynx_28g_pll_read_configuration(struct lynx_28g_priv *priv)
+static void lynx_28g_pll_read_configuration(struct lynx_pll *pll)
{
- struct lynx_28g_pll *pll;
u32 val;
- int i;
- for (i = 0; i < LYNX_28G_NUM_PLL; i++) {
- pll = &priv->pll[i];
- pll->priv = priv;
- pll->id = i;
+ val = lynx_pll_read(pll, PLLnRSTCTL);
+ pll->enabled = !(val & PLLnRSTCTL_DIS);
+ pll->locked = !!(val & PLLnRSTCTL_LOCK);
- val = lynx_28g_pll_read(pll, PLLnRSTCTL);
- pll->enabled = !(val & PLLnRSTCTL_DIS);
- pll->locked = !!(val & PLLnRSTCTL_LOCK);
+ val = lynx_pll_read(pll, PLLnCR0);
+ pll->refclk_sel = FIELD_GET(PLLnCR0_REFCLK_SEL, val);
- val = lynx_28g_pll_read(pll, PLLnCR0);
- pll->refclk_sel = FIELD_GET(PLLnCR0_REFCLK_SEL, val);
+ val = lynx_pll_read(pll, PLLnCR1);
+ pll->frate_sel = FIELD_GET(PLLnCR1_FRATE_SEL, val);
- val = lynx_28g_pll_read(pll, PLLnCR1);
- pll->frate_sel = FIELD_GET(PLLnCR1_FRATE_SEL, val);
-
- if (!pll->enabled)
- continue;
+ if (!pll->enabled)
+ return;
- switch (pll->frate_sel) {
- case PLLnCR1_FRATE_5G_10GVCO:
- case PLLnCR1_FRATE_5G_25GVCO:
- /* 5GHz clock net */
- __set_bit(LANE_MODE_1000BASEX_SGMII, pll->supported);
- break;
- case PLLnCR1_FRATE_10G_20GVCO:
- /* 10.3125GHz clock net */
- __set_bit(LANE_MODE_10GBASER, pll->supported);
- __set_bit(LANE_MODE_USXGMII, pll->supported);
- break;
- case PLLnCR1_FRATE_12G_25GVCO:
- /* 12.890625GHz clock net */
- __set_bit(LANE_MODE_25GBASER, pll->supported);
- break;
- default:
- /* 6GHz, 8GHz */
- break;
- }
+ switch (pll->frate_sel) {
+ case PLLnCR1_FRATE_5G_10GVCO:
+ case PLLnCR1_FRATE_5G_25GVCO:
+ /* 5GHz clock net */
+ __set_bit(LANE_MODE_1000BASEX_SGMII, pll->supported);
+ break;
+ case PLLnCR1_FRATE_10G_20GVCO:
+ /* 10.3125GHz clock net */
+ __set_bit(LANE_MODE_10GBASER, pll->supported);
+ __set_bit(LANE_MODE_USXGMII, pll->supported);
+ break;
+ case PLLnCR1_FRATE_12G_25GVCO:
+ /* 12.890625GHz clock net */
+ __set_bit(LANE_MODE_25GBASER, pll->supported);
+ break;
+ default:
+ /* 6GHz, 8GHz */
+ break;
}
}
@@ -1291,7 +1282,13 @@ static int lynx_28g_probe(struct platform_device *pdev)
if (IS_ERR(priv->base))
return PTR_ERR(priv->base);
- lynx_28g_pll_read_configuration(priv);
+ for (int i = 0; i < LYNX_28G_NUM_PLL; i++) {
+ struct lynx_28g_pll *pll = &priv->pll[i];
+
+ pll->priv = priv;
+ pll->id = i;
+ lynx_28g_pll_read_configuration(pll);
+ }
if (of_get_child_count(dn)) {
struct device_node *child;
--
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 v2 phy-next 07/15] phy: lynx-28g: move struct lynx_info definitions downwards
From: Vladimir Oltean @ 2026-05-29 17:15 UTC (permalink / raw)
To: linux-phy
Cc: Ioana Ciornei, Vinod Koul, Neil Armstrong, Tanjeff Moos,
linux-kernel
In-Reply-To: <20260529171509.1163787-1-vladimir.oltean@nxp.com>
We need to be able to reference more function pointers in upcoming
patches. The struct lynx_info definitions are currently placed a bit up
in lynx-28g.c in order to be able to do that without function prototype
forward declarations, so move them downward to avoid that situation.
No functional change intended.
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
---
v1->v2: adapt to lynx_28g_lane_remap_pll() prototype change in context
---
drivers/phy/freescale/phy-fsl-lynx-28g.c | 86 ++++++++++++------------
1 file changed, 43 insertions(+), 43 deletions(-)
diff --git a/drivers/phy/freescale/phy-fsl-lynx-28g.c b/drivers/phy/freescale/phy-fsl-lynx-28g.c
index 5e914c810505..0076de537763 100644
--- a/drivers/phy/freescale/phy-fsl-lynx-28g.c
+++ b/drivers/phy/freescale/phy-fsl-lynx-28g.c
@@ -782,49 +782,6 @@ static bool lynx_28g_compat_lane_supports_mode(int lane,
}
}
-static const struct lynx_info lynx_info_compat = {
- .get_pccr = lynx_28g_get_pccr,
- .get_pcvt_offset = lynx_28g_get_pcvt_offset,
- .lane_supports_mode = lynx_28g_compat_lane_supports_mode,
- .num_lanes = LYNX_28G_NUM_LANE,
-};
-
-static const struct lynx_info lynx_info_lx2160a_serdes1 = {
- .get_pccr = lynx_28g_get_pccr,
- .get_pcvt_offset = lynx_28g_get_pcvt_offset,
- .lane_supports_mode = lx2160a_serdes1_lane_supports_mode,
- .num_lanes = LYNX_28G_NUM_LANE,
-};
-
-static const struct lynx_info lynx_info_lx2160a_serdes2 = {
- .get_pccr = lynx_28g_get_pccr,
- .get_pcvt_offset = lynx_28g_get_pcvt_offset,
- .lane_supports_mode = lx2160a_serdes2_lane_supports_mode,
- .num_lanes = LYNX_28G_NUM_LANE,
-};
-
-static const struct lynx_info lynx_info_lx2160a_serdes3 = {
- .get_pccr = lynx_28g_get_pccr,
- .get_pcvt_offset = lynx_28g_get_pcvt_offset,
- .lane_supports_mode = lx2160a_serdes3_lane_supports_mode,
- .num_lanes = LYNX_28G_NUM_LANE,
-};
-
-static const struct lynx_info lynx_info_lx2162a_serdes1 = {
- .get_pccr = lynx_28g_get_pccr,
- .get_pcvt_offset = lynx_28g_get_pcvt_offset,
- .lane_supports_mode = lx2162a_serdes1_lane_supports_mode,
- .first_lane = 4,
- .num_lanes = LYNX_28G_NUM_LANE,
-};
-
-static const struct lynx_info lynx_info_lx2162a_serdes2 = {
- .get_pccr = lynx_28g_get_pccr,
- .get_pcvt_offset = lynx_28g_get_pcvt_offset,
- .lane_supports_mode = lx2162a_serdes2_lane_supports_mode,
- .num_lanes = LYNX_28G_NUM_LANE,
-};
-
static void lynx_28g_lane_remap_pll(struct lynx_lane *lane,
enum lynx_lane_mode lane_mode)
{
@@ -1248,6 +1205,49 @@ static int lynx_28g_probe_lane(struct lynx_28g_priv *priv, int id,
return 0;
}
+static const struct lynx_info lynx_info_compat = {
+ .get_pccr = lynx_28g_get_pccr,
+ .get_pcvt_offset = lynx_28g_get_pcvt_offset,
+ .lane_supports_mode = lynx_28g_compat_lane_supports_mode,
+ .num_lanes = LYNX_28G_NUM_LANE,
+};
+
+static const struct lynx_info lynx_info_lx2160a_serdes1 = {
+ .get_pccr = lynx_28g_get_pccr,
+ .get_pcvt_offset = lynx_28g_get_pcvt_offset,
+ .lane_supports_mode = lx2160a_serdes1_lane_supports_mode,
+ .num_lanes = LYNX_28G_NUM_LANE,
+};
+
+static const struct lynx_info lynx_info_lx2160a_serdes2 = {
+ .get_pccr = lynx_28g_get_pccr,
+ .get_pcvt_offset = lynx_28g_get_pcvt_offset,
+ .lane_supports_mode = lx2160a_serdes2_lane_supports_mode,
+ .num_lanes = LYNX_28G_NUM_LANE,
+};
+
+static const struct lynx_info lynx_info_lx2160a_serdes3 = {
+ .get_pccr = lynx_28g_get_pccr,
+ .get_pcvt_offset = lynx_28g_get_pcvt_offset,
+ .lane_supports_mode = lx2160a_serdes3_lane_supports_mode,
+ .num_lanes = LYNX_28G_NUM_LANE,
+};
+
+static const struct lynx_info lynx_info_lx2162a_serdes1 = {
+ .get_pccr = lynx_28g_get_pccr,
+ .get_pcvt_offset = lynx_28g_get_pcvt_offset,
+ .lane_supports_mode = lx2162a_serdes1_lane_supports_mode,
+ .first_lane = 4,
+ .num_lanes = LYNX_28G_NUM_LANE,
+};
+
+static const struct lynx_info lynx_info_lx2162a_serdes2 = {
+ .get_pccr = lynx_28g_get_pccr,
+ .get_pcvt_offset = lynx_28g_get_pcvt_offset,
+ .lane_supports_mode = lx2162a_serdes2_lane_supports_mode,
+ .num_lanes = LYNX_28G_NUM_LANE,
+};
+
static int lynx_28g_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
--
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 v2 phy-next 05/15] phy: lynx-28g: generalize protocol converter accessors
From: Vladimir Oltean @ 2026-05-29 17:14 UTC (permalink / raw)
To: linux-phy
Cc: Ioana Ciornei, Vinod Koul, Neil Armstrong, Tanjeff Moos,
linux-kernel
In-Reply-To: <20260529171509.1163787-1-vladimir.oltean@nxp.com>
The protocol converters on the 10G Lynx are architecturally similar, but
different in layout from the 28G Lynx ones.
Move lynx_pccr_read(), lynx_pccr_write(), lynx_pcvt_read() and
lynx_pcvt_write() from the 28G Lynx driver to the common module, and
permit each SerDes driver to provide just its own bits in order to use
this common API.
Currently, that just means that the direct calls to
lynx_28g_get_pcvt_offset() are modified to go through the
lynx->info->get_pcvt_offset() indirect function call, and similarly,
lynx_28g_get_pccr() through lynx->info->get_pccr().
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
---
v1->v2: adapt to lynx_28g_lane_remap_pll() prototype change in context
---
drivers/phy/freescale/phy-fsl-lynx-28g.c | 102 +++-------------------
drivers/phy/freescale/phy-fsl-lynx-core.c | 90 +++++++++++++++++++
drivers/phy/freescale/phy-fsl-lynx-core.h | 13 +++
3 files changed, 115 insertions(+), 90 deletions(-)
diff --git a/drivers/phy/freescale/phy-fsl-lynx-28g.c b/drivers/phy/freescale/phy-fsl-lynx-28g.c
index 504d03ac8a0f..5e914c810505 100644
--- a/drivers/phy/freescale/phy-fsl-lynx-28g.c
+++ b/drivers/phy/freescale/phy-fsl-lynx-28g.c
@@ -269,8 +269,6 @@
#define LYNX_28G_LANE_STOP_SLEEP_US 100
#define LYNX_28G_LANE_STOP_TIMEOUT_US 1000000
-#define lynx_28g_read lynx_read
-#define lynx_28g_write lynx_write
#define lynx_28g_lane_rmw lynx_lane_rmw
#define lynx_28g_lane_read lynx_lane_read
#define lynx_28g_lane_write lynx_lane_write
@@ -785,124 +783,48 @@ static bool lynx_28g_compat_lane_supports_mode(int lane,
}
static const struct lynx_info lynx_info_compat = {
+ .get_pccr = lynx_28g_get_pccr,
+ .get_pcvt_offset = lynx_28g_get_pcvt_offset,
.lane_supports_mode = lynx_28g_compat_lane_supports_mode,
.num_lanes = LYNX_28G_NUM_LANE,
};
static const struct lynx_info lynx_info_lx2160a_serdes1 = {
+ .get_pccr = lynx_28g_get_pccr,
+ .get_pcvt_offset = lynx_28g_get_pcvt_offset,
.lane_supports_mode = lx2160a_serdes1_lane_supports_mode,
.num_lanes = LYNX_28G_NUM_LANE,
};
static const struct lynx_info lynx_info_lx2160a_serdes2 = {
+ .get_pccr = lynx_28g_get_pccr,
+ .get_pcvt_offset = lynx_28g_get_pcvt_offset,
.lane_supports_mode = lx2160a_serdes2_lane_supports_mode,
.num_lanes = LYNX_28G_NUM_LANE,
};
static const struct lynx_info lynx_info_lx2160a_serdes3 = {
+ .get_pccr = lynx_28g_get_pccr,
+ .get_pcvt_offset = lynx_28g_get_pcvt_offset,
.lane_supports_mode = lx2160a_serdes3_lane_supports_mode,
.num_lanes = LYNX_28G_NUM_LANE,
};
static const struct lynx_info lynx_info_lx2162a_serdes1 = {
+ .get_pccr = lynx_28g_get_pccr,
+ .get_pcvt_offset = lynx_28g_get_pcvt_offset,
.lane_supports_mode = lx2162a_serdes1_lane_supports_mode,
.first_lane = 4,
.num_lanes = LYNX_28G_NUM_LANE,
};
static const struct lynx_info lynx_info_lx2162a_serdes2 = {
+ .get_pccr = lynx_28g_get_pccr,
+ .get_pcvt_offset = lynx_28g_get_pcvt_offset,
.lane_supports_mode = lx2162a_serdes2_lane_supports_mode,
.num_lanes = LYNX_28G_NUM_LANE,
};
-static int lynx_pccr_read(struct lynx_28g_lane *lane, enum lynx_lane_mode mode,
- u32 *val)
-{
- struct lynx_28g_priv *priv = lane->priv;
- struct lynx_pccr pccr;
- u32 tmp;
- int err;
-
- err = lynx_28g_get_pccr(mode, lane->id, &pccr);
- if (err)
- return err;
-
- tmp = lynx_28g_read(priv, pccr.offset);
- *val = (tmp >> pccr.shift) & GENMASK(pccr.width - 1, 0);
-
- return 0;
-}
-
-static int lynx_pccr_write(struct lynx_28g_lane *lane,
- enum lynx_lane_mode lane_mode, u32 val)
-{
- struct lynx_28g_priv *priv = lane->priv;
- struct lynx_pccr pccr;
- u32 old, tmp, mask;
- int err;
-
- err = lynx_28g_get_pccr(lane_mode, lane->id, &pccr);
- if (err)
- return err;
-
- old = lynx_28g_read(priv, pccr.offset);
- mask = GENMASK(pccr.width - 1, 0) << pccr.shift;
- tmp = (old & ~mask) | (val << pccr.shift);
- lynx_28g_write(priv, pccr.offset, tmp);
-
- dev_dbg(&lane->phy->dev, "PCCR@0x%x: 0x%x -> 0x%x\n",
- pccr.offset, old, tmp);
-
- return 0;
-}
-
-static int lynx_pcvt_read(struct lynx_28g_lane *lane,
- enum lynx_lane_mode lane_mode, int cr, u32 *val)
-{
- struct lynx_28g_priv *priv = lane->priv;
- int offset;
-
- offset = lynx_28g_get_pcvt_offset(lane->id, lane_mode);
- if (offset < 0)
- return offset;
-
- *val = lynx_28g_read(priv, offset + cr);
-
- return 0;
-}
-
-static int lynx_pcvt_write(struct lynx_28g_lane *lane,
- enum lynx_lane_mode lane_mode, int cr, u32 val)
-{
- struct lynx_28g_priv *priv = lane->priv;
- int offset;
-
- offset = lynx_28g_get_pcvt_offset(lane->id, lane_mode);
- if (offset < 0)
- return offset;
-
- lynx_28g_write(priv, offset + cr, val);
-
- return 0;
-}
-
-static int lynx_pcvt_rmw(struct lynx_28g_lane *lane,
- enum lynx_lane_mode lane_mode,
- int cr, u32 val, u32 mask)
-{
- int err;
- u32 tmp;
-
- err = lynx_pcvt_read(lane, lane_mode, cr, &tmp);
- if (err)
- return err;
-
- tmp &= ~mask;
- tmp |= val;
-
- return lynx_pcvt_write(lane, lane_mode, cr, tmp);
-}
-
static void lynx_28g_lane_remap_pll(struct lynx_lane *lane,
enum lynx_lane_mode lane_mode)
{
diff --git a/drivers/phy/freescale/phy-fsl-lynx-core.c b/drivers/phy/freescale/phy-fsl-lynx-core.c
index 5e5bcaa54d09..f49d594622cb 100644
--- a/drivers/phy/freescale/phy-fsl-lynx-core.c
+++ b/drivers/phy/freescale/phy-fsl-lynx-core.c
@@ -87,5 +87,95 @@ struct lynx_pll *lynx_pll_get(struct lynx_priv *priv, enum lynx_lane_mode mode)
}
EXPORT_SYMBOL_NS_GPL(lynx_pll_get, "PHY_FSL_LYNX");
+int lynx_pccr_read(struct lynx_lane *lane, enum lynx_lane_mode mode, u32 *val)
+{
+ struct lynx_priv *priv = lane->priv;
+ struct lynx_pccr pccr;
+ u32 tmp;
+ int err;
+
+ err = priv->info->get_pccr(mode, lane->id, &pccr);
+ if (err)
+ return err;
+
+ tmp = lynx_read(priv, pccr.offset);
+ *val = (tmp >> pccr.shift) & GENMASK(pccr.width - 1, 0);
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(lynx_pccr_read, "PHY_FSL_LYNX");
+
+int lynx_pccr_write(struct lynx_lane *lane, enum lynx_lane_mode mode, u32 val)
+{
+ struct lynx_priv *priv = lane->priv;
+ struct lynx_pccr pccr;
+ u32 old, tmp, mask;
+ int err;
+
+ err = priv->info->get_pccr(mode, lane->id, &pccr);
+ if (err)
+ return err;
+
+ old = lynx_read(priv, pccr.offset);
+ mask = GENMASK(pccr.width - 1, 0) << pccr.shift;
+ tmp = (old & ~mask) | (val << pccr.shift);
+ lynx_write(priv, pccr.offset, tmp);
+
+ dev_dbg(&lane->phy->dev, "PCCR@0x%x: 0x%x -> 0x%x\n",
+ pccr.offset, old, tmp);
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(lynx_pccr_write, "PHY_FSL_LYNX");
+
+int lynx_pcvt_read(struct lynx_lane *lane, enum lynx_lane_mode mode, int cr,
+ u32 *val)
+{
+ struct lynx_priv *priv = lane->priv;
+ int offset;
+
+ offset = priv->info->get_pcvt_offset(lane->id, mode);
+ if (offset < 0)
+ return offset;
+
+ *val = lynx_read(priv, offset + cr);
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(lynx_pcvt_read, "PHY_FSL_LYNX");
+
+int lynx_pcvt_write(struct lynx_lane *lane, enum lynx_lane_mode mode, int cr,
+ u32 val)
+{
+ struct lynx_priv *priv = lane->priv;
+ int offset;
+
+ offset = priv->info->get_pcvt_offset(lane->id, mode);
+ if (offset < 0)
+ return offset;
+
+ lynx_write(priv, offset + cr, val);
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(lynx_pcvt_write, "PHY_FSL_LYNX");
+
+int lynx_pcvt_rmw(struct lynx_lane *lane, enum lynx_lane_mode mode, int cr,
+ u32 val, u32 mask)
+{
+ int err;
+ u32 tmp;
+
+ err = lynx_pcvt_read(lane, mode, cr, &tmp);
+ if (err)
+ return err;
+
+ tmp &= ~mask;
+ tmp |= val;
+
+ return lynx_pcvt_write(lane, mode, cr, tmp);
+}
+EXPORT_SYMBOL_NS_GPL(lynx_pcvt_rmw, "PHY_FSL_LYNX");
+
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Freescale Lynx SerDes core functionality");
diff --git a/drivers/phy/freescale/phy-fsl-lynx-core.h b/drivers/phy/freescale/phy-fsl-lynx-core.h
index b726ff21972b..5cd86c9543cb 100644
--- a/drivers/phy/freescale/phy-fsl-lynx-core.h
+++ b/drivers/phy/freescale/phy-fsl-lynx-core.h
@@ -4,6 +4,7 @@
#ifndef _PHY_FSL_LYNX_CORE_H
#define _PHY_FSL_LYNX_CORE_H
+#include <linux/phy/phy.h>
#include <linux/phy.h>
#include <soc/fsl/phy-fsl-lynx.h>
@@ -37,6 +38,9 @@ struct lynx_lane {
};
struct lynx_info {
+ int (*get_pccr)(enum lynx_lane_mode lane_mode, int lane,
+ struct lynx_pccr *pccr);
+ int (*get_pcvt_offset)(int lane, enum lynx_lane_mode mode);
bool (*lane_supports_mode)(int lane, enum lynx_lane_mode mode);
int first_lane;
int num_lanes;
@@ -87,4 +91,13 @@ bool lynx_lane_supports_mode(struct lynx_lane *lane, enum lynx_lane_mode mode);
struct lynx_pll *lynx_pll_get(struct lynx_priv *priv, enum lynx_lane_mode mode);
+int lynx_pccr_read(struct lynx_lane *lane, enum lynx_lane_mode mode, u32 *val);
+int lynx_pccr_write(struct lynx_lane *lane, enum lynx_lane_mode mode, u32 val);
+int lynx_pcvt_read(struct lynx_lane *lane, enum lynx_lane_mode mode, int cr,
+ u32 *val);
+int lynx_pcvt_write(struct lynx_lane *lane, enum lynx_lane_mode mode, int cr,
+ u32 val);
+int lynx_pcvt_rmw(struct lynx_lane *lane, enum lynx_lane_mode mode, int cr,
+ u32 val, u32 mask);
+
#endif
--
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 v2 phy-next 06/15] phy: lynx-28g: provide default lynx_lane_supports_mode() implementation
From: Vladimir Oltean @ 2026-05-29 17:15 UTC (permalink / raw)
To: linux-phy
Cc: Ioana Ciornei, Vinod Koul, Neil Armstrong, Tanjeff Moos,
linux-kernel
In-Reply-To: <20260529171509.1163787-1-vladimir.oltean@nxp.com>
For the 28G Lynx, there are situations where a protocol is not supported
on a lane despite there being a PCCR register and protocol converter
available:
- LX2160A SerDes 1: reference manual documents PCCD fields E25GC_CFG and
E25GD_CFG and protocol converter registers E25GCCR1..E25GCCR3 /
E25GDCR1..E25GDCR3, but nonetheless, Table 289. SerDes 1 protocol
mapping shows no RCW[SRDS_PRTCL_S1] value for which lanes C and D
support 25G
- when using the "fsl,lynx-28g" fallback compatible string, we don't
want to offer 25GbE because we don't know if the lane supports it,
even though we know how to reach the PCCR and protocol converter
registers for it.
But for the upcoming 10G Lynx SerDes, the above situations don't exist.
There, if we know how to reach the PCCR and protocol converter
registers on a lane, we implicitly know that the protocol is supported
there, so implementing priv->info->lane_supports_mode() would be
redundant.
Implement lynx_lane_supports_mode_default() which decides whether a lane
mode is supported just based on priv->info->get_pccr() and
priv->info->get_pcvt_offset().
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
---
v1->v2: none
---
drivers/phy/freescale/phy-fsl-lynx-core.c | 27 ++++++++++++++++++++++-
1 file changed, 26 insertions(+), 1 deletion(-)
diff --git a/drivers/phy/freescale/phy-fsl-lynx-core.c b/drivers/phy/freescale/phy-fsl-lynx-core.c
index f49d594622cb..802e32dc6dca 100644
--- a/drivers/phy/freescale/phy-fsl-lynx-core.c
+++ b/drivers/phy/freescale/phy-fsl-lynx-core.c
@@ -40,6 +40,27 @@ enum lynx_lane_mode phy_interface_to_lane_mode(phy_interface_t intf)
}
EXPORT_SYMBOL_NS_GPL(phy_interface_to_lane_mode, "PHY_FSL_LYNX");
+/* By default, assume that if we know how to get the PCCR register and
+ * protocol converter for a lane, that protocol is supported.
+ */
+static bool lynx_lane_supports_mode_default(struct lynx_lane *lane,
+ enum lynx_lane_mode mode)
+{
+ struct lynx_priv *priv = lane->priv;
+ struct lynx_pccr pccr;
+
+ if (!priv->info->get_pccr || !priv->info->get_pcvt_offset)
+ return false;
+
+ if (priv->info->get_pccr(mode, lane->id, &pccr) < 0)
+ return false;
+
+ if (priv->info->get_pcvt_offset(lane->id, mode) < 0)
+ return false;
+
+ return true;
+}
+
/* A lane mode is supported if we have a PLL that can provide its required
* clock net, and if there is a protocol converter for that mode on that lane.
*/
@@ -48,8 +69,12 @@ bool lynx_lane_supports_mode(struct lynx_lane *lane, enum lynx_lane_mode mode)
struct lynx_priv *priv = lane->priv;
int i;
- if (!priv->info->lane_supports_mode(lane->id, mode))
+ if (priv->info->lane_supports_mode) {
+ if (!priv->info->lane_supports_mode(lane->id, mode))
+ return false;
+ } else if (!lynx_lane_supports_mode_default(lane, mode)) {
return false;
+ }
for (i = 0; i < LYNX_NUM_PLL; i++) {
if (!priv->pll[i].enabled)
--
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 v2 phy-next 04/15] phy: lynx-28g: common lynx_pll_get()
From: Vladimir Oltean @ 2026-05-29 17:14 UTC (permalink / raw)
To: linux-phy
Cc: Ioana Ciornei, Vinod Koul, Neil Armstrong, Tanjeff Moos,
linux-kernel
In-Reply-To: <20260529171509.1163787-1-vladimir.oltean@nxp.com>
The logic should be absolutely unchanged in the new 10G Lynx SerDes
driver, so let's move this to phy-fsl-lynx-core.c and update the 28G
Lynx driver to use the common variant.
While at it, update the call site, lynx_28g_lane_remap_pll(), to use the
new data structures, and refactor the NULL pll pointer check (the
current form triggers a checkpatch CHECK).
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
---
v1->v2: add the drive-by refactoring
---
drivers/phy/freescale/phy-fsl-lynx-28g.c | 34 ++++-------------------
drivers/phy/freescale/phy-fsl-lynx-core.c | 24 ++++++++++++++++
drivers/phy/freescale/phy-fsl-lynx-core.h | 2 ++
3 files changed, 31 insertions(+), 29 deletions(-)
diff --git a/drivers/phy/freescale/phy-fsl-lynx-28g.c b/drivers/phy/freescale/phy-fsl-lynx-28g.c
index 482c5d8fdc6a..504d03ac8a0f 100644
--- a/drivers/phy/freescale/phy-fsl-lynx-28g.c
+++ b/drivers/phy/freescale/phy-fsl-lynx-28g.c
@@ -467,30 +467,6 @@ static const struct lynx_28g_proto_conf lynx_28g_proto_conf[LANE_MODE_MAX] = {
},
};
-static struct lynx_28g_pll *lynx_28g_pll_get(struct lynx_28g_priv *priv,
- enum lynx_lane_mode mode)
-{
- struct lynx_28g_pll *pll;
- int i;
-
- for (i = 0; i < LYNX_28G_NUM_PLL; i++) {
- pll = &priv->pll[i];
-
- if (!pll->enabled)
- continue;
-
- if (test_bit(mode, pll->supported))
- return pll;
- }
-
- /* no pll supports requested mode, either caller forgot to check
- * lynx_lane_supports_mode(), or this is a bug.
- */
- dev_WARN_ONCE(priv->dev, 1, "no pll for lane mode %s\n",
- lynx_lane_mode_str(mode));
- return NULL;
-}
-
static void lynx_28g_lane_set_nrate(struct lynx_28g_lane *lane,
struct lynx_28g_pll *pll,
enum lynx_lane_mode lane_mode)
@@ -927,15 +903,15 @@ static int lynx_pcvt_rmw(struct lynx_28g_lane *lane,
return lynx_pcvt_write(lane, lane_mode, cr, tmp);
}
-static void lynx_28g_lane_remap_pll(struct lynx_28g_lane *lane,
+static void lynx_28g_lane_remap_pll(struct lynx_lane *lane,
enum lynx_lane_mode lane_mode)
{
- struct lynx_28g_priv *priv = lane->priv;
- struct lynx_28g_pll *pll;
+ struct lynx_priv *priv = lane->priv;
+ struct lynx_pll *pll;
/* Switch to the PLL that works with this interface type */
- pll = lynx_28g_pll_get(priv, lane_mode);
- if (unlikely(pll == NULL))
+ pll = lynx_pll_get(priv, lane_mode);
+ if (unlikely(!pll))
return;
lynx_28g_lane_set_pll(lane, pll);
diff --git a/drivers/phy/freescale/phy-fsl-lynx-core.c b/drivers/phy/freescale/phy-fsl-lynx-core.c
index de45b14d3fb6..5e5bcaa54d09 100644
--- a/drivers/phy/freescale/phy-fsl-lynx-core.c
+++ b/drivers/phy/freescale/phy-fsl-lynx-core.c
@@ -63,5 +63,29 @@ bool lynx_lane_supports_mode(struct lynx_lane *lane, enum lynx_lane_mode mode)
}
EXPORT_SYMBOL_NS_GPL(lynx_lane_supports_mode, "PHY_FSL_LYNX");
+struct lynx_pll *lynx_pll_get(struct lynx_priv *priv, enum lynx_lane_mode mode)
+{
+ struct lynx_pll *pll;
+ int i;
+
+ for (i = 0; i < LYNX_NUM_PLL; i++) {
+ pll = &priv->pll[i];
+
+ if (!pll->enabled)
+ continue;
+
+ if (test_bit(mode, pll->supported))
+ return pll;
+ }
+
+ /* no pll supports requested mode, either caller forgot to check
+ * lynx_lane_supports_mode(), or this is a bug.
+ */
+ dev_WARN_ONCE(priv->dev, 1, "no pll for lane mode %s\n",
+ lynx_lane_mode_str(mode));
+ return NULL;
+}
+EXPORT_SYMBOL_NS_GPL(lynx_pll_get, "PHY_FSL_LYNX");
+
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Freescale Lynx SerDes core functionality");
diff --git a/drivers/phy/freescale/phy-fsl-lynx-core.h b/drivers/phy/freescale/phy-fsl-lynx-core.h
index f0cb3e805235..b726ff21972b 100644
--- a/drivers/phy/freescale/phy-fsl-lynx-core.h
+++ b/drivers/phy/freescale/phy-fsl-lynx-core.h
@@ -85,4 +85,6 @@ const char *lynx_lane_mode_str(enum lynx_lane_mode lane_mode);
enum lynx_lane_mode phy_interface_to_lane_mode(phy_interface_t intf);
bool lynx_lane_supports_mode(struct lynx_lane *lane, enum lynx_lane_mode mode);
+struct lynx_pll *lynx_pll_get(struct lynx_priv *priv, enum lynx_lane_mode mode);
+
#endif
--
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 v2 phy-next 03/15] phy: lynx-28g: move data structures to core
From: Vladimir Oltean @ 2026-05-29 17:14 UTC (permalink / raw)
To: linux-phy
Cc: Ioana Ciornei, Vinod Koul, Neil Armstrong, Tanjeff Moos,
linux-kernel
In-Reply-To: <20260529171509.1163787-1-vladimir.oltean@nxp.com>
The goal is to avoid duplicating the core data structures when
introducing the new lynx-10g driver.
We move the following to phy-fsl-lynx-core:
- struct lynx_28g_pll -> struct lynx_pll. This has some
hardware-specific register fields which need to become hardware
agnostic (the PLL register layout is different for Lynx 10G), So:
- PLLnRSTCTL_DIS(pll->rstctl) becomes !pll->enabled
- PLLnRSTCTL_LOCK(pll->rstctl) becomes pll->locked
- FIELD_GET(PLLnCR1_FRATE_SEL, pll->cr1) becomes pll->frate_sel
- FIELD_GET(PLLnCR0_REFCLK_SEL, pll->cr0) becomes pll->refclk_sel
- struct lynx_28g_lane -> struct lynx_lane
- struct lynx_28g_priv -> struct lynx_priv
- field lane[LYNX_28G_NUM_LANE] has to be dynamically allocated. Not
all Lynx 10G SerDes blocks have 8 lanes.
- LYNX_28G_NUM_PLL -> LYNX_NUM_PLL. This is an architectural constant
which is the same for Lynx 10G as well.
To avoid major noise in the lynx-28g driver, we keep compatibility shims
(for now) where the old lynx_28g names are preserved, but translate to
the common data structures.
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
---
v1->v2: none
---
drivers/phy/freescale/phy-fsl-lynx-28g.c | 135 +++++++---------------
drivers/phy/freescale/phy-fsl-lynx-core.c | 23 ++++
drivers/phy/freescale/phy-fsl-lynx-core.h | 64 ++++++++++
3 files changed, 129 insertions(+), 93 deletions(-)
diff --git a/drivers/phy/freescale/phy-fsl-lynx-28g.c b/drivers/phy/freescale/phy-fsl-lynx-28g.c
index 27587ad87f22..482c5d8fdc6a 100644
--- a/drivers/phy/freescale/phy-fsl-lynx-28g.c
+++ b/drivers/phy/freescale/phy-fsl-lynx-28g.c
@@ -12,7 +12,7 @@
#include "phy-fsl-lynx-core.h"
#define LYNX_28G_NUM_LANE 8
-#define LYNX_28G_NUM_PLL 2
+#define LYNX_28G_NUM_PLL LYNX_NUM_PLL
/* SoC IP wrapper for protocol converters */
#define PCC8 0x10a0
@@ -43,8 +43,8 @@
/* Per PLL registers */
#define PLLnRSTCTL(pll) (0x400 + (pll) * 0x100 + 0x0)
-#define PLLnRSTCTL_DIS(rstctl) (((rstctl) & BIT(24)) >> 24)
-#define PLLnRSTCTL_LOCK(rstctl) (((rstctl) & BIT(23)) >> 23)
+#define PLLnRSTCTL_DIS BIT(24)
+#define PLLnRSTCTL_LOCK BIT(23)
#define PLLnCR0(pll) (0x400 + (pll) * 0x100 + 0x4)
#define PLLnCR0_REFCLK_SEL GENMASK(20, 16)
@@ -269,6 +269,17 @@
#define LYNX_28G_LANE_STOP_SLEEP_US 100
#define LYNX_28G_LANE_STOP_TIMEOUT_US 1000000
+#define lynx_28g_read lynx_read
+#define lynx_28g_write lynx_write
+#define lynx_28g_lane_rmw lynx_lane_rmw
+#define lynx_28g_lane_read lynx_lane_read
+#define lynx_28g_lane_write lynx_lane_write
+#define lynx_28g_pll_read lynx_pll_read
+
+#define lynx_28g_priv lynx_priv
+#define lynx_28g_lane lynx_lane
+#define lynx_28g_pll lynx_pll
+
enum lynx_28g_eq_type {
EQ_TYPE_NO_EQ = 0,
EQ_TYPE_2TAP = 1,
@@ -456,86 +467,6 @@ static const struct lynx_28g_proto_conf lynx_28g_proto_conf[LANE_MODE_MAX] = {
},
};
-struct lynx_28g_priv;
-
-struct lynx_28g_pll {
- struct lynx_28g_priv *priv;
- u32 rstctl, cr0, cr1;
- int id;
- DECLARE_BITMAP(supported, LANE_MODE_MAX);
-};
-
-struct lynx_28g_lane {
- struct lynx_28g_priv *priv;
- struct phy *phy;
- bool powered_up;
- bool init;
- unsigned int id;
- enum lynx_lane_mode mode;
-};
-
-struct lynx_28g_priv {
- void __iomem *base;
- struct device *dev;
- const struct lynx_info *info;
- /* Serialize concurrent access to registers shared between lanes,
- * like PCCn
- */
- spinlock_t pcc_lock;
- struct lynx_28g_pll pll[LYNX_28G_NUM_PLL];
- struct lynx_28g_lane lane[LYNX_28G_NUM_LANE];
-
- struct delayed_work cdr_check;
-};
-
-static void lynx_28g_rmw(struct lynx_28g_priv *priv, unsigned long off,
- u32 val, u32 mask)
-{
- void __iomem *reg = priv->base + off;
- u32 orig, tmp;
-
- orig = ioread32(reg);
- tmp = orig & ~mask;
- tmp |= val;
- iowrite32(tmp, reg);
-}
-
-#define lynx_28g_read(priv, off) \
- ioread32((priv)->base + (off))
-#define lynx_28g_write(priv, off, val) \
- iowrite32(val, (priv)->base + (off))
-#define lynx_28g_lane_rmw(lane, reg, val, mask) \
- lynx_28g_rmw((lane)->priv, reg(lane->id), val, mask)
-#define lynx_28g_lane_read(lane, reg) \
- ioread32((lane)->priv->base + reg((lane)->id))
-#define lynx_28g_lane_write(lane, reg, val) \
- iowrite32(val, (lane)->priv->base + reg((lane)->id))
-#define lynx_28g_pll_read(pll, reg) \
- ioread32((pll)->priv->base + reg((pll)->id))
-
-/* A lane mode is supported if we have a PLL that can provide its required
- * clock net, and if there is a protocol converter for that mode on that lane.
- */
-static bool lynx_28g_supports_lane_mode(struct lynx_28g_lane *lane,
- enum lynx_lane_mode mode)
-{
- struct lynx_28g_priv *priv = lane->priv;
- int i;
-
- if (!priv->info->lane_supports_mode(lane->id, mode))
- return false;
-
- for (i = 0; i < LYNX_28G_NUM_PLL; i++) {
- if (PLLnRSTCTL_DIS(priv->pll[i].rstctl))
- continue;
-
- if (test_bit(mode, priv->pll[i].supported))
- return true;
- }
-
- return false;
-}
-
static struct lynx_28g_pll *lynx_28g_pll_get(struct lynx_28g_priv *priv,
enum lynx_lane_mode mode)
{
@@ -545,7 +476,7 @@ static struct lynx_28g_pll *lynx_28g_pll_get(struct lynx_28g_priv *priv,
for (i = 0; i < LYNX_28G_NUM_PLL; i++) {
pll = &priv->pll[i];
- if (PLLnRSTCTL_DIS(pll->rstctl))
+ if (!pll->enabled)
continue;
if (test_bit(mode, pll->supported))
@@ -553,7 +484,7 @@ static struct lynx_28g_pll *lynx_28g_pll_get(struct lynx_28g_priv *priv,
}
/* no pll supports requested mode, either caller forgot to check
- * lynx_28g_supports_lane_mode, or this is a bug.
+ * lynx_lane_supports_mode(), or this is a bug.
*/
dev_WARN_ONCE(priv->dev, 1, "no pll for lane mode %s\n",
lynx_lane_mode_str(mode));
@@ -564,7 +495,7 @@ static void lynx_28g_lane_set_nrate(struct lynx_28g_lane *lane,
struct lynx_28g_pll *pll,
enum lynx_lane_mode lane_mode)
{
- switch (FIELD_GET(PLLnCR1_FRATE_SEL, pll->cr1)) {
+ switch (pll->frate_sel) {
case PLLnCR1_FRATE_5G_10GVCO:
case PLLnCR1_FRATE_5G_25GVCO:
switch (lane_mode) {
@@ -879,27 +810,33 @@ static bool lynx_28g_compat_lane_supports_mode(int lane,
static const struct lynx_info lynx_info_compat = {
.lane_supports_mode = lynx_28g_compat_lane_supports_mode,
+ .num_lanes = LYNX_28G_NUM_LANE,
};
static const struct lynx_info lynx_info_lx2160a_serdes1 = {
.lane_supports_mode = lx2160a_serdes1_lane_supports_mode,
+ .num_lanes = LYNX_28G_NUM_LANE,
};
static const struct lynx_info lynx_info_lx2160a_serdes2 = {
.lane_supports_mode = lx2160a_serdes2_lane_supports_mode,
+ .num_lanes = LYNX_28G_NUM_LANE,
};
static const struct lynx_info lynx_info_lx2160a_serdes3 = {
.lane_supports_mode = lx2160a_serdes3_lane_supports_mode,
+ .num_lanes = LYNX_28G_NUM_LANE,
};
static const struct lynx_info lynx_info_lx2162a_serdes1 = {
.lane_supports_mode = lx2162a_serdes1_lane_supports_mode,
.first_lane = 4,
+ .num_lanes = LYNX_28G_NUM_LANE,
};
static const struct lynx_info lynx_info_lx2162a_serdes2 = {
.lane_supports_mode = lx2162a_serdes2_lane_supports_mode,
+ .num_lanes = LYNX_28G_NUM_LANE,
};
static int lynx_pccr_read(struct lynx_28g_lane *lane, enum lynx_lane_mode mode,
@@ -1168,7 +1105,7 @@ static int lynx_28g_set_mode(struct phy *phy, enum phy_mode mode, int submode)
return -EOPNOTSUPP;
lane_mode = phy_interface_to_lane_mode(submode);
- if (!lynx_28g_supports_lane_mode(lane, lane_mode))
+ if (!lynx_lane_supports_mode(lane, lane_mode))
return -EOPNOTSUPP;
if (lane_mode == lane->mode)
@@ -1210,7 +1147,7 @@ static int lynx_28g_validate(struct phy *phy, enum phy_mode mode, int submode,
return -EOPNOTSUPP;
lane_mode = phy_interface_to_lane_mode(submode);
- if (!lynx_28g_supports_lane_mode(lane, lane_mode))
+ if (!lynx_lane_supports_mode(lane, lane_mode))
return -EOPNOTSUPP;
return 0;
@@ -1262,6 +1199,7 @@ static const struct phy_ops lynx_28g_ops = {
static void lynx_28g_pll_read_configuration(struct lynx_28g_priv *priv)
{
struct lynx_28g_pll *pll;
+ u32 val;
int i;
for (i = 0; i < LYNX_28G_NUM_PLL; i++) {
@@ -1269,14 +1207,20 @@ static void lynx_28g_pll_read_configuration(struct lynx_28g_priv *priv)
pll->priv = priv;
pll->id = i;
- pll->rstctl = lynx_28g_pll_read(pll, PLLnRSTCTL);
- pll->cr0 = lynx_28g_pll_read(pll, PLLnCR0);
- pll->cr1 = lynx_28g_pll_read(pll, PLLnCR1);
+ val = lynx_28g_pll_read(pll, PLLnRSTCTL);
+ pll->enabled = !(val & PLLnRSTCTL_DIS);
+ pll->locked = !!(val & PLLnRSTCTL_LOCK);
- if (PLLnRSTCTL_DIS(pll->rstctl))
+ val = lynx_28g_pll_read(pll, PLLnCR0);
+ pll->refclk_sel = FIELD_GET(PLLnCR0_REFCLK_SEL, val);
+
+ val = lynx_28g_pll_read(pll, PLLnCR1);
+ pll->frate_sel = FIELD_GET(PLLnCR1_FRATE_SEL, val);
+
+ if (!pll->enabled)
continue;
- switch (FIELD_GET(PLLnCR1_FRATE_SEL, pll->cr1)) {
+ switch (pll->frate_sel) {
case PLLnCR1_FRATE_5G_10GVCO:
case PLLnCR1_FRATE_5G_25GVCO:
/* 5GHz clock net */
@@ -1440,6 +1384,11 @@ static int lynx_28g_probe(struct platform_device *pdev)
if (priv->info == &lynx_info_compat)
dev_warn(dev, "Please update device tree to use per-device compatible strings\n");
+ priv->lane = devm_kcalloc(dev, priv->info->num_lanes,
+ sizeof(*priv->lane), GFP_KERNEL);
+ if (!priv->lane)
+ return -ENOMEM;
+
priv->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(priv->base))
return PTR_ERR(priv->base);
diff --git a/drivers/phy/freescale/phy-fsl-lynx-core.c b/drivers/phy/freescale/phy-fsl-lynx-core.c
index d56f189c162d..de45b14d3fb6 100644
--- a/drivers/phy/freescale/phy-fsl-lynx-core.c
+++ b/drivers/phy/freescale/phy-fsl-lynx-core.c
@@ -40,5 +40,28 @@ enum lynx_lane_mode phy_interface_to_lane_mode(phy_interface_t intf)
}
EXPORT_SYMBOL_NS_GPL(phy_interface_to_lane_mode, "PHY_FSL_LYNX");
+/* A lane mode is supported if we have a PLL that can provide its required
+ * clock net, and if there is a protocol converter for that mode on that lane.
+ */
+bool lynx_lane_supports_mode(struct lynx_lane *lane, enum lynx_lane_mode mode)
+{
+ struct lynx_priv *priv = lane->priv;
+ int i;
+
+ if (!priv->info->lane_supports_mode(lane->id, mode))
+ return false;
+
+ for (i = 0; i < LYNX_NUM_PLL; i++) {
+ if (!priv->pll[i].enabled)
+ continue;
+
+ if (test_bit(mode, priv->pll[i].supported))
+ return true;
+ }
+
+ return false;
+}
+EXPORT_SYMBOL_NS_GPL(lynx_lane_supports_mode, "PHY_FSL_LYNX");
+
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Freescale Lynx SerDes core functionality");
diff --git a/drivers/phy/freescale/phy-fsl-lynx-core.h b/drivers/phy/freescale/phy-fsl-lynx-core.h
index fe15986482b0..f0cb3e805235 100644
--- a/drivers/phy/freescale/phy-fsl-lynx-core.h
+++ b/drivers/phy/freescale/phy-fsl-lynx-core.h
@@ -7,18 +7,82 @@
#include <linux/phy.h>
#include <soc/fsl/phy-fsl-lynx.h>
+#define LYNX_NUM_PLL 2
+
struct lynx_pccr {
int offset;
int width;
int shift;
};
+struct lynx_priv;
+
+struct lynx_pll {
+ struct lynx_priv *priv;
+ int id;
+ int refclk_sel;
+ int frate_sel;
+ bool enabled;
+ bool locked;
+ DECLARE_BITMAP(supported, LANE_MODE_MAX);
+};
+
+struct lynx_lane {
+ struct lynx_priv *priv;
+ struct phy *phy;
+ bool powered_up;
+ bool init;
+ unsigned int id;
+ enum lynx_lane_mode mode;
+};
+
struct lynx_info {
bool (*lane_supports_mode)(int lane, enum lynx_lane_mode mode);
int first_lane;
+ int num_lanes;
};
+struct lynx_priv {
+ void __iomem *base;
+ struct device *dev;
+ const struct lynx_info *info;
+ /* Serialize concurrent access to registers shared between lanes,
+ * like PCCn
+ */
+ spinlock_t pcc_lock;
+ struct lynx_pll pll[LYNX_NUM_PLL];
+ struct lynx_lane *lane;
+
+ struct delayed_work cdr_check;
+};
+
+static inline void lynx_rmw(struct lynx_priv *priv, unsigned long off, u32 val,
+ u32 mask)
+{
+ void __iomem *reg = priv->base + off;
+ u32 orig, tmp;
+
+ orig = ioread32(reg);
+ tmp = orig & ~mask;
+ tmp |= val;
+ iowrite32(tmp, reg);
+}
+
+#define lynx_read(priv, off) \
+ ioread32((priv)->base + (off))
+#define lynx_write(priv, off, val) \
+ iowrite32(val, (priv)->base + (off))
+#define lynx_lane_rmw(lane, reg, val, mask) \
+ lynx_rmw((lane)->priv, reg(lane->id), val, mask)
+#define lynx_lane_read(lane, reg) \
+ ioread32((lane)->priv->base + reg((lane)->id))
+#define lynx_lane_write(lane, reg, val) \
+ iowrite32(val, (lane)->priv->base + reg((lane)->id))
+#define lynx_pll_read(pll, reg) \
+ ioread32((pll)->priv->base + reg((pll)->id))
+
const char *lynx_lane_mode_str(enum lynx_lane_mode lane_mode);
enum lynx_lane_mode phy_interface_to_lane_mode(phy_interface_t intf);
+bool lynx_lane_supports_mode(struct lynx_lane *lane, enum lynx_lane_mode mode);
#endif
--
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 v2 phy-next 02/15] phy: lynx-28g: move lane mode helpers to new core module
From: Vladimir Oltean @ 2026-05-29 17:14 UTC (permalink / raw)
To: linux-phy
Cc: Ioana Ciornei, Vinod Koul, Neil Armstrong, Tanjeff Moos,
linux-kernel
In-Reply-To: <20260529171509.1163787-1-vladimir.oltean@nxp.com>
Do some preparation work for the introduction of the lynx-10g driver,
which will share a common backbone with the 28G Lynx SerDes.
This is just trivial stuff which can be moved without any surgery, and
is easy to follow but otherwise pollutes more serious changes.
The lane modes themselves are exported to a public header, because on
the 10G Lynx, the hardware requires implementing a procedure called
"RCW override". This requires coordination with drivers/soc/fsl/guts.c
to tell it that a SerDes lane needs to be switched to a different
protocol (enum lynx_lane_mode).
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
---
v1->v2: none
---
drivers/phy/freescale/Kconfig | 7 +++
drivers/phy/freescale/Makefile | 1 +
drivers/phy/freescale/phy-fsl-lynx-28g.c | 56 ++---------------------
drivers/phy/freescale/phy-fsl-lynx-core.c | 44 ++++++++++++++++++
drivers/phy/freescale/phy-fsl-lynx-core.h | 24 ++++++++++
include/soc/fsl/phy-fsl-lynx.h | 16 +++++++
6 files changed, 95 insertions(+), 53 deletions(-)
create mode 100644 drivers/phy/freescale/phy-fsl-lynx-core.c
create mode 100644 drivers/phy/freescale/phy-fsl-lynx-core.h
create mode 100644 include/soc/fsl/phy-fsl-lynx.h
diff --git a/drivers/phy/freescale/Kconfig b/drivers/phy/freescale/Kconfig
index 81f53564ee15..a87429f634ea 100644
--- a/drivers/phy/freescale/Kconfig
+++ b/drivers/phy/freescale/Kconfig
@@ -51,11 +51,18 @@ config PHY_FSL_SAMSUNG_HDMI_PHY
Enable this to add support for the Samsung HDMI PHY in i.MX8MP.
endif
+config PHY_FSL_LYNX_CORE
+ tristate
+ help
+ Enable this to add common support code for NXP Lynx 10G and Lynx 28G
+ SerDes blocks.
+
config PHY_FSL_LYNX_28G
tristate "Freescale Layerscape Lynx 28G SerDes PHY support"
depends on OF
depends on ARCH_LAYERSCAPE || COMPILE_TEST
select GENERIC_PHY
+ select PHY_FSL_LYNX_CORE
help
Enable this to add support for the Lynx SerDes 28G PHY as
found on NXP's Layerscape platforms such as LX2160A.
diff --git a/drivers/phy/freescale/Makefile b/drivers/phy/freescale/Makefile
index 658eac7d0a62..d7aa62cdeb39 100644
--- a/drivers/phy/freescale/Makefile
+++ b/drivers/phy/freescale/Makefile
@@ -4,5 +4,6 @@ obj-$(CONFIG_PHY_MIXEL_LVDS_PHY) += phy-fsl-imx8qm-lvds-phy.o
obj-$(CONFIG_PHY_MIXEL_MIPI_DPHY) += phy-fsl-imx8-mipi-dphy.o
obj-$(CONFIG_PHY_FSL_IMX8M_PCIE) += phy-fsl-imx8m-pcie.o
obj-$(CONFIG_PHY_FSL_IMX8QM_HSIO) += phy-fsl-imx8qm-hsio.o
+obj-$(CONFIG_PHY_FSL_LYNX_CORE) += phy-fsl-lynx-core.o
obj-$(CONFIG_PHY_FSL_LYNX_28G) += phy-fsl-lynx-28g.o
obj-$(CONFIG_PHY_FSL_SAMSUNG_HDMI_PHY) += phy-fsl-samsung-hdmi.o
diff --git a/drivers/phy/freescale/phy-fsl-lynx-28g.c b/drivers/phy/freescale/phy-fsl-lynx-28g.c
index 4461b47a16ad..27587ad87f22 100644
--- a/drivers/phy/freescale/phy-fsl-lynx-28g.c
+++ b/drivers/phy/freescale/phy-fsl-lynx-28g.c
@@ -9,6 +9,8 @@
#include <linux/platform_device.h>
#include <linux/workqueue.h>
+#include "phy-fsl-lynx-core.h"
+
#define LYNX_28G_NUM_LANE 8
#define LYNX_28G_NUM_PLL 2
@@ -282,15 +284,6 @@ enum lynx_28g_proto_sel {
PROTO_SEL_25G_50G_100G = 0x1a,
};
-enum lynx_lane_mode {
- LANE_MODE_UNKNOWN,
- LANE_MODE_1000BASEX_SGMII,
- LANE_MODE_10GBASER,
- LANE_MODE_USXGMII,
- LANE_MODE_25GBASER,
- LANE_MODE_MAX,
-};
-
struct lynx_28g_proto_conf {
/* LNaGCR0 */
int proto_sel;
@@ -463,12 +456,6 @@ static const struct lynx_28g_proto_conf lynx_28g_proto_conf[LANE_MODE_MAX] = {
},
};
-struct lynx_pccr {
- int offset;
- int width;
- int shift;
-};
-
struct lynx_28g_priv;
struct lynx_28g_pll {
@@ -487,11 +474,6 @@ struct lynx_28g_lane {
enum lynx_lane_mode mode;
};
-struct lynx_info {
- bool (*lane_supports_mode)(int lane, enum lynx_lane_mode mode);
- int first_lane;
-};
-
struct lynx_28g_priv {
void __iomem *base;
struct device *dev;
@@ -531,39 +513,6 @@ static void lynx_28g_rmw(struct lynx_28g_priv *priv, unsigned long off,
#define lynx_28g_pll_read(pll, reg) \
ioread32((pll)->priv->base + reg((pll)->id))
-static const char *lynx_lane_mode_str(enum lynx_lane_mode lane_mode)
-{
- switch (lane_mode) {
- case LANE_MODE_1000BASEX_SGMII:
- return "1000Base-X/SGMII";
- case LANE_MODE_10GBASER:
- return "10GBase-R";
- case LANE_MODE_USXGMII:
- return "USXGMII";
- case LANE_MODE_25GBASER:
- return "25GBase-R";
- default:
- return "unknown";
- }
-}
-
-static enum lynx_lane_mode phy_interface_to_lane_mode(phy_interface_t intf)
-{
- switch (intf) {
- case PHY_INTERFACE_MODE_SGMII:
- case PHY_INTERFACE_MODE_1000BASEX:
- return LANE_MODE_1000BASEX_SGMII;
- case PHY_INTERFACE_MODE_10GBASER:
- return LANE_MODE_10GBASER;
- case PHY_INTERFACE_MODE_USXGMII:
- return LANE_MODE_USXGMII;
- case PHY_INTERFACE_MODE_25GBASER:
- return LANE_MODE_25GBASER;
- default:
- return LANE_MODE_UNKNOWN;
- }
-}
-
/* A lane mode is supported if we have a PLL that can provide its required
* clock net, and if there is a protocol converter for that mode on that lane.
*/
@@ -1572,6 +1521,7 @@ static struct platform_driver lynx_28g_driver = {
};
module_platform_driver(lynx_28g_driver);
+MODULE_IMPORT_NS("PHY_FSL_LYNX");
MODULE_AUTHOR("Ioana Ciornei <ioana.ciornei@nxp.com>");
MODULE_DESCRIPTION("Lynx 28G SerDes PHY driver for Layerscape SoCs");
MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/freescale/phy-fsl-lynx-core.c b/drivers/phy/freescale/phy-fsl-lynx-core.c
new file mode 100644
index 000000000000..d56f189c162d
--- /dev/null
+++ b/drivers/phy/freescale/phy-fsl-lynx-core.c
@@ -0,0 +1,44 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* Copyright 2025-2026 NXP */
+
+#include <linux/module.h>
+
+#include "phy-fsl-lynx-core.h"
+
+const char *lynx_lane_mode_str(enum lynx_lane_mode lane_mode)
+{
+ switch (lane_mode) {
+ case LANE_MODE_1000BASEX_SGMII:
+ return "1000Base-X/SGMII";
+ case LANE_MODE_10GBASER:
+ return "10GBase-R";
+ case LANE_MODE_USXGMII:
+ return "USXGMII";
+ case LANE_MODE_25GBASER:
+ return "25GBase-R";
+ default:
+ return "unknown";
+ }
+}
+EXPORT_SYMBOL_NS_GPL(lynx_lane_mode_str, "PHY_FSL_LYNX");
+
+enum lynx_lane_mode phy_interface_to_lane_mode(phy_interface_t intf)
+{
+ switch (intf) {
+ case PHY_INTERFACE_MODE_SGMII:
+ case PHY_INTERFACE_MODE_1000BASEX:
+ return LANE_MODE_1000BASEX_SGMII;
+ case PHY_INTERFACE_MODE_10GBASER:
+ return LANE_MODE_10GBASER;
+ case PHY_INTERFACE_MODE_USXGMII:
+ return LANE_MODE_USXGMII;
+ case PHY_INTERFACE_MODE_25GBASER:
+ return LANE_MODE_25GBASER;
+ default:
+ return LANE_MODE_UNKNOWN;
+ }
+}
+EXPORT_SYMBOL_NS_GPL(phy_interface_to_lane_mode, "PHY_FSL_LYNX");
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Freescale Lynx SerDes core functionality");
diff --git a/drivers/phy/freescale/phy-fsl-lynx-core.h b/drivers/phy/freescale/phy-fsl-lynx-core.h
new file mode 100644
index 000000000000..fe15986482b0
--- /dev/null
+++ b/drivers/phy/freescale/phy-fsl-lynx-core.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/* Copyright 2025-2026 NXP */
+
+#ifndef _PHY_FSL_LYNX_CORE_H
+#define _PHY_FSL_LYNX_CORE_H
+
+#include <linux/phy.h>
+#include <soc/fsl/phy-fsl-lynx.h>
+
+struct lynx_pccr {
+ int offset;
+ int width;
+ int shift;
+};
+
+struct lynx_info {
+ bool (*lane_supports_mode)(int lane, enum lynx_lane_mode mode);
+ int first_lane;
+};
+
+const char *lynx_lane_mode_str(enum lynx_lane_mode lane_mode);
+enum lynx_lane_mode phy_interface_to_lane_mode(phy_interface_t intf);
+
+#endif
diff --git a/include/soc/fsl/phy-fsl-lynx.h b/include/soc/fsl/phy-fsl-lynx.h
new file mode 100644
index 000000000000..92e8272d5ae1
--- /dev/null
+++ b/include/soc/fsl/phy-fsl-lynx.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/* Copyright 2023-2026 NXP */
+
+#ifndef __PHY_FSL_LYNX_H_
+#define __PHY_FSL_LYNX_H_
+
+enum lynx_lane_mode {
+ LANE_MODE_UNKNOWN,
+ LANE_MODE_1000BASEX_SGMII,
+ LANE_MODE_10GBASER,
+ LANE_MODE_USXGMII,
+ LANE_MODE_25GBASER,
+ LANE_MODE_MAX,
+};
+
+#endif /* __PHY_FSL_LYNX_H_ */
--
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 v2 phy-next 01/15] phy: lynx-28g: reject probing on devices with unsupported OF nodes
From: Vladimir Oltean @ 2026-05-29 17:14 UTC (permalink / raw)
To: linux-phy
Cc: Ioana Ciornei, Vinod Koul, Neil Armstrong, Tanjeff Moos,
linux-kernel
In-Reply-To: <20260529171509.1163787-1-vladimir.oltean@nxp.com>
It is possible to bind the lynx-28g driver to an arbitrary device with
an OF node, using the driver_override mechanism that is available for
the platform bus, and trigger a crash this way:
$ echo 1ea0000.serdes > /sys/bus/platform/drivers/lynx-10g/unbind
$ echo lynx-28g > /sys/bus/platform/devices/1ea0000.serdes/driver_override
$ echo 1ea0000.serdes > /sys/bus/platform/drivers/lynx-28g/bind
Internal error: Oops: 0000000096000004 [#1] SMP
Hardware name: LS1028A RDB Board (DT)
pc : lynx_probe+0x118/0x4fc
lr : lynx_probe+0x110/0x4fc
Call trace:
lynx_probe+0x118/0x4fc (P)
lynx_28g_probe+0x54/0x7c
platform_probe+0x68/0xa4
really_probe+0x14c/0x2ec
__driver_probe_device+0xc8/0x170
device_driver_attach+0x58/0xa8
bind_store+0xd8/0x118
drv_attr_store+0x24/0x38
The crash is caused by the fact that of_device_get_match_data() returns
NULL (the bound device has a different compatible string) and this is
not checked.
There was a previous attempt to avoid this in commit c9d80e861034 ("phy:
lynx-28g: require an OF node to probe"), but the mechanism was not fully
understood and it only covered the case where the driver was bound to a
device with no OF node.
The issue was found during Sashiko review. Elevated privilege is
required to override the driver for a device, so the real life impact of
the issue should not be very high.
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
---
v1->v2: patch is new
---
drivers/phy/freescale/phy-fsl-lynx-28g.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/drivers/phy/freescale/phy-fsl-lynx-28g.c b/drivers/phy/freescale/phy-fsl-lynx-28g.c
index 92bfc5f65e0b..4461b47a16ad 100644
--- a/drivers/phy/freescale/phy-fsl-lynx-28g.c
+++ b/drivers/phy/freescale/phy-fsl-lynx-28g.c
@@ -1477,6 +1477,9 @@ static int lynx_28g_probe(struct platform_device *pdev)
priv->dev = dev;
priv->info = of_device_get_match_data(dev);
+ if (!priv->info)
+ return -ENODEV;
+
dev_set_drvdata(dev, priv);
spin_lock_init(&priv->pcc_lock);
INIT_DELAYED_WORK(&priv->cdr_check, lynx_28g_cdr_lock_check);
--
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 v2 phy-next 00/15] New Generic PHY driver for Lynx 10G SerDes
From: Vladimir Oltean @ 2026-05-29 17:14 UTC (permalink / raw)
To: linux-phy
Cc: Ioana Ciornei, Vinod Koul, Neil Armstrong, Tanjeff Moos,
linux-kernel
The 10G Lynx SerDes is present on NXP LS1028A, LS1046A, LS1088A,
LS2088A and other SoCs (unsupported here). It is an older generation of
the 28G Lynx present in LX2160A and LX2162A.
Modify the Generic PHY driver for lynx-28g to create a common portion
(lynx-common) and add a new driver for lynx-10g and DT bindings.
Main use case is networking - dynamic SerDes protocol changing - but
initial support is limited to minor changes (1GbE <-> 2.5GbE) due to
lack of support for the RCW override procedure necessary for major
protocol changes (1GbE <-> 10GbE). This is the next step once the base
driver is available.
Changes since v1:
- 2 new patches (01/15 and 12/15) made in response to Sashiko
- minor drive-by refactoring in patch 04/15
- fix typo in commit message of 08/15 (Sashiko)
- minor cleanups in lynx-10g driver (14/15)
v1 at:
https://lore.kernel.org/linux-phy/20260528172404.733196-1-vladimir.oltean@nxp.com/
Vladimir Oltean (15):
phy: lynx-28g: reject probing on devices with unsupported OF nodes
phy: lynx-28g: move lane mode helpers to new core module
phy: lynx-28g: move data structures to core
phy: lynx-28g: common lynx_pll_get()
phy: lynx-28g: generalize protocol converter accessors
phy: lynx-28g: provide default lynx_lane_supports_mode()
implementation
phy: lynx-28g: move struct lynx_info definitions downwards
phy: lynx-28g: make lynx_28g_pll_read_configuration() callable per PLL
phy: lynx-28g: common probe() and remove()
phy: lynx-28g: add support for big endian register maps
phy: lynx-28g: optimize read-modify-write operation
phy: lynx-28g: improve phy_validate() procedure
dt-bindings: phy: lynx-10g: initial document
phy: lynx-10g: new driver
MAINTAINERS: expand Lynx 28G entry to cover Lynx 10G SerDes
.../devicetree/bindings/phy/fsl,lynx-10g.yaml | 131 ++
MAINTAINERS | 8 +-
drivers/phy/freescale/Kconfig | 17 +
drivers/phy/freescale/Makefile | 2 +
drivers/phy/freescale/phy-fsl-lynx-10g.c | 1280 +++++++++++++++++
drivers/phy/freescale/phy-fsl-lynx-28g.c | 622 ++------
drivers/phy/freescale/phy-fsl-lynx-core.c | 445 ++++++
drivers/phy/freescale/phy-fsl-lynx-core.h | 134 ++
include/soc/fsl/phy-fsl-lynx.h | 43 +
9 files changed, 2195 insertions(+), 487 deletions(-)
create mode 100644 Documentation/devicetree/bindings/phy/fsl,lynx-10g.yaml
create mode 100644 drivers/phy/freescale/phy-fsl-lynx-10g.c
create mode 100644 drivers/phy/freescale/phy-fsl-lynx-core.c
create mode 100644 drivers/phy/freescale/phy-fsl-lynx-core.h
create mode 100644 include/soc/fsl/phy-fsl-lynx.h
--
2.34.1
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* Re: [PATCH v23 3/8] dt-bindings: phy: Add Freescale iMX8MQ DP and HDMI PHY
From: Krzysztof Kozlowski @ 2026-05-29 15:45 UTC (permalink / raw)
To: Laurentiu Palcu
Cc: Vinod Koul, Neil Armstrong, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, dri-devel, devicetree, linux-kernel, linux-phy,
imx, linux-arm-kernel, linux, Alexander Stein, Ying Liu
In-Reply-To: <20260519-dcss-hdmi-upstreaming-v23-3-5615524a9c63@oss.nxp.com>
On Tue, May 19, 2026 at 02:42:26PM +0000, Laurentiu Palcu wrote:
> From: Sandor Yu <Sandor.yu@nxp.com>
>
> Add bindings for Freescale iMX8MQ DP and HDMI PHY.
>
> Signed-off-by: Sandor Yu <Sandor.yu@nxp.com>
> Signed-off-by: Laurentiu Palcu <laurentiu.palcu@oss.nxp.com>
> ---
> .../bindings/phy/fsl,imx8mq-hdptx-phy.yaml | 52 ++++++++++++++++++++++
> 1 file changed, 52 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/phy/fsl,imx8mq-hdptx-phy.yaml b/Documentation/devicetree/bindings/phy/fsl,imx8mq-hdptx-phy.yaml
> new file mode 100644
> index 0000000000000..b544c260aa073
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/phy/fsl,imx8mq-hdptx-phy.yaml
> @@ -0,0 +1,52 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/phy/fsl,imx8mq-hdptx-phy.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Cadence HDP-TX DP/HDMI PHY for Freescale i.MX8MQ SoC
> +
> +maintainers:
> + - Laurentiu Palcu <laurentiu.palcu@oss.nxp.com>
> +
> +description:
> + The Cadence HDP-TX DP/HDMI PHY is a child node of the MHDP8501 bridge,
> + sharing the same MMIO region as the parent bridge node.
As commented in bridge bindings, I have doubts in such case it is a
separate component.
Best regards,
Krzysztof
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* Re: [PATCH phy-next 01/13] dt-bindings: phy: lynx-10g: initial document
From: Vladimir Oltean @ 2026-05-29 12:50 UTC (permalink / raw)
To: sashiko-reviews
Cc: robh, devicetree, conor+dt, neil.armstrong, linux-phy, olteanv,
vkoul
In-Reply-To: <20260528174508.bqu6ks4rgkysvgdg@skbuf>
On Thu, May 28, 2026 at 08:45:08PM +0300, Vladimir Oltean wrote:
> On Thu, May 28, 2026 at 05:40:44PM +0000, sashiko-bot@kernel.org wrote:
> > Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> > - [Low] Invalid JSON Schema syntax for the `reg` property constraint.
> > --
> >
> > commit e219405276839837989c30627d109ff39c15e361
> > Author: Vladimir Oltean <vladimir.oltean@nxp.com>
> >
> > dt-bindings: phy: lynx-10g: initial document
> >
> > This commit adds a new device tree binding schema for the 10G Lynx SerDes.
> > It defines the register map constraints and allows flexibility for the
> > #phy-cells property.
> >
> > > diff --git a/Documentation/devicetree/bindings/phy/fsl,lynx-10g.yaml b/Documentation/devicetree/bindings/phy/fsl,lynx-10g.yaml
> > > new file mode 100644
> > > index 0000000000000..993f076bba4e6
> > > --- /dev/null
> > > +++ b/Documentation/devicetree/bindings/phy/fsl,lynx-10g.yaml
> > [ ... ]
> > > patternProperties:
> > > "^phy@[0-7]$":
> > > type: object
> > > description: SerDes lane (single RX/TX differential pair)
> > >
> > > properties:
> > > reg:
> > > minimum: 0
> > > maximum: 7
> >
> > [Severity: Low]
> > Will this cause a meta-schema validation error during make dt_binding_check?
> >
> > The reg property is parsed as an array type by dt-schema. Applying minimum
> > and maximum directly to an array property is invalid in JSON Schema, as those
> > keywords are only applicable to numeric types.
> >
> > Should these constraints be wrapped inside an items list?
> >
> > [ ... ]
> > > allOf:
> > > - if:
> > > properties:
> > > compatible:
> > > contains:
> > > enum:
> > > - fsl,ls1028a-serdes
> > > - fsl,ls1046a-serdes1
> > > - fsl,ls1046a-serdes2
> > > - fsl,ls1088a-serdes1
> > > - fsl,ls1088a-serdes2
> > > then:
> > > patternProperties:
> > > "^phy@[0-7]$":
> > > properties:
> > > reg:
> > > minimum: 0
> > > maximum: 3
> >
> > [Severity: Low]
> > Could the same meta-schema validation issue occur here?
> >
> > Since reg is an array, perhaps this constraint would be better enforced by
> > rejecting invalid unit addresses in the then clause entirely, for example,
> > by setting "^phy@[4-7]$": false?
> >
> > --
> > Sashiko AI review · https://sashiko.dev/#/patchset/20260528172404.733196-1-vladimir.oltean@nxp.com?part=1
>
> We have the same pattern in fsl,lynx-28g.yaml and it didn't cause schema
> validation failures there.
Yes, so the dt_binding_check test passed fine:
https://patchwork.kernel.org/project/linux-phy/patch/20260528172404.733196-2-vladimir.oltean@nxp.com/
I will not be making any change to this patch in response to this LLM
feedback, just humans.
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* Re: [PATCH phy-next 09/13] phy: lynx-28g: common probe() and remove()
From: Vladimir Oltean @ 2026-05-29 12:47 UTC (permalink / raw)
To: sashiko-reviews; +Cc: vkoul, neil.armstrong, olteanv, linux-phy
In-Reply-To: <20260528180215.9B1D41F000E9@smtp.kernel.org>
On Thu, May 28, 2026 at 06:02:15PM +0000, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
>
> Pre-existing issues:
> - [High] Missing NULL check for the result of `of_device_get_match_data(dev)`.
> --
>
> commit 4307af42a82313eaf19153e90d7c48e0bd5f9626
> Author: Vladimir Oltean <vladimir.oltean@nxp.com>
>
> phy: lynx-28g: common probe() and remove()
>
> Factor the device-agnostic logic from lynx_28g_probe() and
> lynx_28g_remove() into lynx_probe() and lynx_remove() inside
> phy-fsl-lynx-core.c. These will be shared with the 10G Lynx driver.
>
> > diff --git a/drivers/phy/freescale/phy-fsl-lynx-28g.c b/drivers/phy/freescale/phy-fsl-lynx-28g.c
> > --- a/drivers/phy/freescale/phy-fsl-lynx-28g.c
> > +++ b/drivers/phy/freescale/phy-fsl-lynx-28g.c
> [ ... ]
> > static int lynx_28g_probe(struct platform_device *pdev)
> > {
> > struct device *dev = &pdev->dev;
> > + const struct lynx_info *info;
> >
> > /*
> > * If we get here it means we probed on a device tree where
> > * "fsl,lynx-28g" wasn't the fallback, but the sole compatible string.
> > */
> > + info = of_device_get_match_data(dev);
>
> [Severity: High]
> This is a pre-existing issue, but should we check if info is NULL before
> proceeding?
>
> If a user binds the driver to a device using the sysfs bind interface, and
> the device's OF node does not match the driver's of_match_table,
> of_device_get_match_data() will return NULL.
>
> > + if (info == &lynx_info_compat)
> > dev_warn(dev, "Please update device tree to use per-device compatible strings\n");
> >
> > + return lynx_probe(pdev, info, &lynx_28g_ops);
> > }
>
> [ ... ]
> > diff --git a/drivers/phy/freescale/phy-fsl-lynx-core.c b/drivers/phy/freescale/phy-fsl-lynx-core.c
> > --- a/drivers/phy/freescale/phy-fsl-lynx-core.c
> > +++ b/drivers/phy/freescale/phy-fsl-lynx-core.c
> [ ... ]
> > +int lynx_probe(struct platform_device *pdev, const struct lynx_info *info,
> > + const struct phy_ops *phy_ops)
> > +{
> [ ... ]
> > + priv->dev = dev;
> > + priv->info = info;
> > + dev_set_drvdata(dev, priv);
> > + spin_lock_init(&priv->pcc_lock);
> > + INIT_DELAYED_WORK(&priv->cdr_check, lynx_cdr_lock_check);
> > +
> > + priv->lane = devm_kcalloc(dev, priv->info->num_lanes,
>
> [Severity: High]
> This isn't a bug introduced by this patch, but if info was NULL as mentioned
> above, would this unconditionally dereference it and cause a kernel panic?
>
> > + sizeof(*priv->lane), GFP_KERNEL);
> > + if (!priv->lane)
> > + return -ENOMEM;
> [ ... ]
>
> --
> Sashiko AI review · https://sashiko.dev/#/patchset/20260528172404.733196-1-vladimir.oltean@nxp.com?part=9
This seems to be true. I was able to trigger a kernel oops by running:
$ echo 1ea0000.phy > /sys/bus/platform/drivers/lynx-10g/unbind
$ echo lynx-28g > /sys/bus/platform/devices/1ea0000.phy/driver_override
$ echo 1ea0000.phy > /sys/bus/platform/drivers/lynx-28g/bind
Internal error: Oops: 0000000096000004 [#1] SMP
Hardware name: LS1028A RDB Board (DT)
pc : lynx_probe+0x118/0x4fc
lr : lynx_probe+0x110/0x4fc
Call trace:
lynx_probe+0x118/0x4fc (P)
lynx_28g_probe+0x54/0x7c
platform_probe+0x68/0xa4
really_probe+0x14c/0x2ec
__driver_probe_device+0xc8/0x170
device_driver_attach+0x58/0xa8
bind_store+0xd8/0x118
drv_attr_store+0x24/0x38
It seems driver_override is a huge foot gun, which probably many drivers
are vulnerable to. I will include a patch protecting from this in the
next series.
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* Re: [PATCH phy-next 12/13] phy: lynx-10g: new driver
From: Vladimir Oltean @ 2026-05-29 12:34 UTC (permalink / raw)
To: sashiko-reviews
Cc: neil.armstrong, conor+dt, olteanv, vkoul, linux-phy, robh,
devicetree
In-Reply-To: <20260528182030.9027B1F000E9@smtp.kernel.org>
On Thu, May 28, 2026 at 06:20:29PM +0000, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
>
> New issues:
> - [High] Lockless read-modify-write operations on the LNaGCR0 register create a data race between asynchronous CDR lock checks and PHY configuration.
> - [Medium] The return value of lynx_10g_lane_enable_pcvt() is discarded when called inside WARN_ON(), masking hardware configuration failures.
> - [Medium] The return value of lynx_pcvt_rmw() for the CR(0) register is completely ignored, masking potential hardware failures.
> - [Medium] lynx_10g_validate() incorrectly permits transitions from LANE_MODE_UNKNOWN, which are later rejected by lynx_10g_set_mode().
>
> Pre-existing issues:
> - [Critical] The delayed work priv->cdr_check may unconditionally re-arm itself, leading to a Use-After-Free upon device unbind or module removal.
> --
>
> commit 8617183053a524e1cbc4453e0e8f8e478c4cf8ef
> Author: Vladimir Oltean <vladimir.oltean@nxp.com>
>
> phy: lynx-10g: new driver
>
> Introduce a driver for the networking lanes of the 10G Lynx SerDes
> block, present on the majority of Layerscape and QorIQ (Freescale/NXP)
> SoCs. The consumers use the Generic PHY framework to reconfigure the SerDes
> for requested link modes.
>
> > diff --git a/drivers/phy/freescale/phy-fsl-lynx-10g.c b/drivers/phy/freescale/phy-fsl-lynx-10g.c
> > new file mode 100644
> > index 0000000000000..28b0823574684
> > --- /dev/null
> > +++ b/drivers/phy/freescale/phy-fsl-lynx-10g.c
> > @@ -0,0 +1,1319 @@
>
> [ ... ]
>
> > +static void lynx_10g_cdr_lock_check(struct lynx_lane *lane)
> > +{
> > + u32 tcsr3 = lynx_lane_read(lane, LNaTCSR3);
> > +
> > + if (tcsr3 & LNaTCSR3_CDR_LCK)
> > + return;
> > +
> > + dev_dbg(&lane->phy->dev,
> > + "Lane %c CDR unlocked, resetting receiver...\n",
> > + 'A' + lane->id);
> > +
> > + lynx_lane_rmw(lane, LNaGCR0, LNaGCR0_RRST_ON, LNaGCR0_RRST);
>
> [Severity: High]
> Can concurrent modifications to the LNaGCR0 register corrupt its state?
>
> Since the priv->cdr_check work runs asynchronously, it could call
> lynx_10g_cdr_lock_check() and modify LNaGCR0 at the same time the PHY
> framework calls phy_power_on(), phy_power_off(), or phy_set_mode(). These
> functions also read-modify-write LNaGCR0 without per-lane locking,
> potentially dropping reset or power-down bits.
This is why lynx_cdr_lock_check() acquires &lane->phy->mutex prior to
calling priv->info->cdr_lock_check() (lynx_10g_cdr_lock_check). The
concurrency is blocked, this is a false report.
> [ ... ]
>
> > +static const struct lynx_info lynx_info_ls1028a = {
> > + .get_pccr = ls1028a_get_pccr,
> > + .get_pcvt_offset = ls1028a_get_pcvt_offset,
> > + .pll_read_configuration = lynx_10g_pll_read_configuration,
> > + .lane_read_configuration = lynx_10g_lane_read_configuration,
> > + .cdr_lock_check = lynx_10g_cdr_lock_check,
>
> [Severity: Critical]
> This is a pre-existing issue, but can the delayed work priv->cdr_check cause
> a use-after-free upon module removal?
>
> If lynx_cdr_lock_check() unconditionally re-arms itself with
> queue_delayed_work(), cancel_delayed_work_sync() during remove might wait
> for the current execution to finish, only for it to queue itself again.
> After device memory is freed, the newly armed timer would fire and access
> freed memory.
>
> [ ... ]
I asked an LLM to look at whether cancel_delayed_work_sync() protects
against attempts from the work to reschedule itself, and it looks like
it does.
The disable count is incremented at the very beginning of the
cancellation process and decremented just before the function returns.
Here is the call chain in kernel/workqueue.c:
1. cancel_delayed_work_sync() calls __cancel_work_sync(work, WORK_CANCEL_DELAYED).
2. __cancel_work_sync() immediately calls:
1 __cancel_work(work, cflags | WORK_CANCEL_DISABLE);
2 // where cflags is WORK_CANCEL_DELAYED, so this includes the DISABLE flag.
3. __cancel_work() performs the increment:
* It calls work_grab_pending() to claim the work's pending bit.
* It calls work_offqd_unpack() to read the current state from work->data.
* It calls work_offqd_disable(&offqd), which increments offqd.disable++.
* It packs this updated state back into work->data using set_work_pool_and_clear_pending().
4. __cancel_work_sync() then waits:
* It calls __flush_work(work, true) to wait for any currently executing
instance of the work to finish.
* Crucially: While it is waiting, if the running work function tries to
re-queue itself (like lynx_28g_cdr_lock_check does), the
queue_work logic checks this disable count and fails the
enqueue.
5. __cancel_work_sync() performs the decrement:
* At the end of the function, it checks if it should re-enable the work:
1 if (!(cflags & WORK_CANCEL_DISABLE))
2 enable_work(work);
* Since cancel_delayed_work_sync passed WORK_CANCEL_DELAYED (which does
not include the persistent DISABLE flag), enable_work(work) is called.
* enable_work() calls work_offqd_enable(), which decrements the count back
to its original value.
The LLM then makes a note that since commit 86898fa6b8cd ("workqueue:
Implement disable/enable for (delayed) work items") from kernel v6.10, a
new disable_delayed_work_sync() API function exists, which as opposed to
cancel_delayed_work_sync(), does not increment the disable count just
for the duration of the function call, but rather, leaves the work
persistently disabled, such that no future attempts to queue it from any
context will succeed. For this case, though, cancel_delayed_work_sync()
vs disable_delayed_work_sync() makes no functional difference, since we
just schedule the delayed work once during probe, and then the only
other reschedule source is itself.
> > +static int lynx_10g_lane_disable_pcvt(struct lynx_lane *lane,
> > + enum lynx_lane_mode mode)
> > +{
> > + struct lynx_priv *priv = lane->priv;
> > + int err;
> > +
> > + spin_lock(&priv->pcc_lock);
> > +
> > + err = lynx_pccr_write(lane, mode, 0);
> > + if (err)
> > + goto out;
> > +
> > + switch (mode) {
> > + case LANE_MODE_1000BASEX_SGMII:
> > + case LANE_MODE_2500BASEX:
> > + err = lynx_pcvt_rmw(lane, mode, CR(1), SGMIIaCR1_SGPCS_DIS,
> > + SGMIIaCR1_SGPCS_EN);
> > + if (err)
> > + goto out;
> > +
> > + lynx_pcvt_rmw(lane, mode, CR(0),
> > + SGMIIaCR0_RST_SGM_ON | SGMIIaCR0_PD_SGM,
> > + SGMIIaCR0_RST_SGM | SGMIIaCR0_PD_SGM);
>
> [Severity: Medium]
> Could this mask potential hardware failures?
>
> While the return value of lynx_pcvt_rmw() for the CR(1) register is checked,
> the subsequent call for the CR(0) register discards its return value. If the
> register access fails, the error would be swallowed here and similarly in
> lynx_10g_lane_enable_pcvt().
This is intentional. The reasons for which lynx_pcvt_rmw() can fail are
not time-variable, so a single error check is sufficient. If the LLM
looked at the errors being propagated it would have seen they are
because we were unable to get a protocol converter for the lane mode,
which would be a code bug. The "potential hardware failure" being masked
is a hallucination.
> [ ... ]
>
> > +static int lynx_10g_validate(struct phy *phy, enum phy_mode mode, int submode,
> > + union phy_configure_opts *opts)
> > +{
> > + struct lynx_lane *lane = phy_get_drvdata(phy);
> > + enum lynx_lane_mode lane_mode;
> > +
> > + if (mode != PHY_MODE_ETHERNET)
> > + return -EINVAL;
> > +
> > + lane_mode = phy_interface_to_lane_mode(submode);
> > + if (!lynx_lane_supports_mode(lane, lane_mode))
> > + return -EINVAL;
> > +
> > + if (lynx_lane_restrict_fixed_mode_change(lane, lane_mode))
> > + return -EINVAL;
> > +
> > + if (lynx_10g_lane_mode_needs_rcw_override(lane, lane_mode))
> > + return -EINVAL;
> > +
> > + return 0;
> > +}
>
> [Severity: Medium]
> Should validate() explicitly reject transitions from LANE_MODE_UNKNOWN?
>
> When a lane is initialized to a non-networking protocol, it defaults to
> LANE_MODE_UNKNOWN. This function permits the transition because
> lynx_10g_lane_mode_needs_rcw_override() returns false. However, when
> lynx_10g_set_mode() subsequently calls lynx_10g_lane_disable_pcvt(),
> the underlying get_pccr() function returns -EINVAL for LANE_MODE_UNKNOWN,
> causing set_mode() to fail.
>
> [ ... ]
It seems reasonable to reject transitions from LANE_MODE_UNKNOWN in
phy_validate(). First and foremost, the lynx_10g_proto_conf[] registers
will most likely not be sufficient to transition a lane into a supported
protocol. Only supported <-> supported protocol changes have been tested
and should work. I will add a test both here and in lynx-28g.
> > +static int lynx_10g_set_mode(struct phy *phy, enum phy_mode mode, int submode)
> > +{
> > + struct lynx_lane *lane = phy_get_drvdata(phy);
> > + bool powered_up = lane->powered_up;
> > + enum lynx_lane_mode lane_mode;
> > + int err;
> > +
> > + err = lynx_10g_validate(phy, mode, submode, NULL);
> > + if (err)
> > + return err;
> > +
> > + lane_mode = phy_interface_to_lane_mode(submode);
> > + /* lynx_10g_validate() already made sure the lane_mode is supported */
> > +
> > + if (lane_mode == lane->mode)
> > + return 0;
> > +
> > + /* If the lane is powered up, put the lane into the halt state while
> > + * the reconfiguration is being done.
> > + */
> > + if (powered_up)
> > + lynx_10g_lane_halt(phy);
> > +
> > + err = lynx_10g_lane_disable_pcvt(lane, lane->mode);
> > + if (err)
> > + goto out;
> > +
> > + lynx_10g_lane_change_proto_conf(lane, lane_mode);
> > + lynx_10g_lane_remap_pll(lane, lane_mode);
> > + WARN_ON(lynx_10g_lane_enable_pcvt(lane, lane_mode));
>
> [Severity: Medium]
> Does WARN_ON() discard the error code returned by lynx_10g_lane_enable_pcvt()?
>
> Because WARN_ON() evaluates to a boolean, the actual integer error code is
> ignored. If hardware configuration fails, the system might proceed as if
> successful, incorrectly update lane->mode, and leave the driver in an
> inconsistent state.
Intentional. lynx_10g_lane_enable_pcvt() fails for the same reasons as
lynx_10g_lane_disable_pcvt(), reasons which are not time variant.
We already checked lynx_10g_lane_disable_pcvt() above, so there is no
reason to check lynx_10g_lane_enable_pcvt() too, and complicate the
control flow.
> > +
> > + lane->mode = lane_mode;
> > +
> > +out:
> > + if (powered_up)
> > + lynx_10g_lane_reset(phy);
> > +
> > + return err;
> > +}
>
> --
> Sashiko AI review · https://sashiko.dev/#/patchset/20260528172404.733196-1-vladimir.oltean@nxp.com?part=12
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* Re: [PATCH phy-next 10/13] phy: lynx-28g: add support for big endian register maps
From: Vladimir Oltean @ 2026-05-29 12:00 UTC (permalink / raw)
To: sashiko-reviews; +Cc: vkoul, olteanv, neil.armstrong, linux-phy
In-Reply-To: <20260528174757.9AFF31F000E9@smtp.kernel.org>
On Thu, May 28, 2026 at 05:47:57PM +0000, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [Low] Returning an expression in a function with a 'void' return type is a constraint violation in standard C.
> --
>
> commit f3cbe0a1122ada7c6725f37e9dc47932febfd4ad
> Author: Vladimir Oltean <vladimir.oltean@nxp.com>
>
> phy: lynx-28g: add support for big endian register maps
>
> This commit adds support for big-endian 10G Lynx SerDes blocks. It reads
> the "big-endian" device tree property and updates the lynx_read() and
> lynx_write() accessors to use the appropriate ioread32be/iowrite32be or
> ioread32/iowrite32 variants based on the endianness.
>
> > diff --git a/drivers/phy/freescale/phy-fsl-lynx-core.h b/drivers/phy/freescale/phy-fsl-lynx-core.h
> > index e8b280cc9b38c..d82e529fa65a6 100644
> > --- a/drivers/phy/freescale/phy-fsl-lynx-core.h
> > +++ b/drivers/phy/freescale/phy-fsl-lynx-core.h
>
> [ ... ]
>
> > @@ -58,36 +58,52 @@ struct lynx_priv {
> > * like PCCn
> > */
> > spinlock_t pcc_lock;
> > + bool big_endian;
> > struct lynx_pll pll[LYNX_NUM_PLL];
> > struct lynx_lane *lane;
> >
> > struct delayed_work cdr_check;
> > };
> >
> > +static inline u32 lynx_read(struct lynx_priv *priv, unsigned long off)
> > +{
> > + void __iomem *reg = priv->base + off;
> > +
> > + if (priv->big_endian)
> > + return ioread32be(reg);
> > +
> > + return ioread32(reg);
> > +}
> > +
> > +static inline void lynx_write(struct lynx_priv *priv, unsigned long off, u32 val)
> > +{
> > + void __iomem *reg = priv->base + off;
> > +
> > + if (priv->big_endian)
> > + return iowrite32be(val, reg);
>
> [Severity: Low]
> Does this code unintentionally return a void expression?
>
> The lynx_write() function is declared with a void return type, but it uses
> the return keyword before calling iowrite32be() and iowrite32() which also
> evaluate to void.
>
> While GNU C allows returning a void expression as an extension, standard C
> does not permit it, and it could trigger warnings in strict static analysis
> tools. Would it be better to just drop the return keywords here?
>
> > +
> > + return iowrite32(val, reg);
> > +}
>
> --
> Sashiko AI review · https://sashiko.dev/#/patchset/20260528172404.733196-1-vladimir.oltean@nxp.com?part=10
This was intentional, because "return iowrite32be()" also stops the control flow
and is more succinct than "{ iowrite32be(); return; }".
I thought the pattern was widely accepted, see include/linux/skbuff.h:
static inline void consume_skb(struct sk_buff *skb)
{
return kfree_skb(skb);
}
Unless a human has a valid objection, I will disregard this feedback.
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* Re: [PATCH v15 4/9] phy: rockchip: phy-rockchip-typec: Add typec_mux/typec_switch support
From: Chaoyi Chen @ 2026-05-29 8:13 UTC (permalink / raw)
To: Nicolas Frattaroli
Cc: Heikki Krogerus, Greg Kroah-Hartman, Dmitry Baryshkov, Peter Chen,
Luca Ceresoli, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Vinod Koul, Kishon Vijay Abraham I, Heiko Stuebner, Sandy Huang,
Andy Yan, Yubing Zhang, Frank Wang, Andrzej Hajda, Neil Armstrong,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter, Amit Sunil Dhamne, Dragan Simic, Johan Jonker,
Diederik de Haas, Peter Robinson, Hugh Cole-Baker, dri-devel,
linux-usb, devicetree, linux-kernel, linux-phy, linux-arm-kernel,
linux-rockchip, Chaoyi Chen
In-Reply-To: <Ueak618ET1-5Ceu6vq06lg@collabora.com>
Hello Nicolas,
Thank you for your review! :) In the next version, I will follow your
suggestions to make the changes and split this patch into a new series.
On 5/29/2026 4:16 AM, Nicolas Frattaroli wrote:
> On Wednesday, 4 March 2026 10:41:47 Central European Summer Time Chaoyi Chen wrote:
>> From: Chaoyi Chen <chaoyi.chen@rock-chips.com>
>>
>> This patch add support for Type-C Port Controller Manager. Each PHY
>> will register typec_mux and typec_switch when external Type-C
>> controller is present. Type-C events are handled by TCPM without
>> extcon.
>>
>> The extcon device should still be supported.
>>
>> Signed-off-by: Chaoyi Chen <chaoyi.chen@rock-chips.com>
>> ---
>>
>> (no changes since v7)
>>
>> Changes in v6:
>> - Fix depend in Kconfig.
>> - Check DP svid in tcphy_typec_mux_set().
>> - Remove mode setting in tcphy_orien_sw_set().
>>
>> (no changes since v5)
>>
>> Changes in v4:
>> - Remove notify DP HPD state by USB/DP PHY.
>>
>> (no changes since v3)
>>
>> Changes in v2:
>> - Fix compile error when CONFIG_TYPEC is not enabled.
>> - Notify DP HPD state by USB/DP PHY.
>> ---
>>
>> drivers/phy/rockchip/Kconfig | 1 +
>> drivers/phy/rockchip/phy-rockchip-typec.c | 368 +++++++++++++++++++++-
>> 2 files changed, 353 insertions(+), 16 deletions(-)
>>
>> diff --git a/drivers/phy/rockchip/Kconfig b/drivers/phy/rockchip/Kconfig
>> index 14698571b607..db4adc7c53da 100644
>> --- a/drivers/phy/rockchip/Kconfig
>> +++ b/drivers/phy/rockchip/Kconfig
>> @@ -119,6 +119,7 @@ config PHY_ROCKCHIP_SNPS_PCIE3
>> config PHY_ROCKCHIP_TYPEC
>> tristate "Rockchip TYPEC PHY Driver"
>> depends on OF && (ARCH_ROCKCHIP || COMPILE_TEST)
>> + depends on TYPEC || TYPEC=n
>> select EXTCON
>> select GENERIC_PHY
>> select RESET_CONTROLLER
>> diff --git a/drivers/phy/rockchip/phy-rockchip-typec.c b/drivers/phy/rockchip/phy-rockchip-typec.c
>> index d9701b6106d5..1f5b4142cbe4 100644
>> --- a/drivers/phy/rockchip/phy-rockchip-typec.c
>> +++ b/drivers/phy/rockchip/phy-rockchip-typec.c
>> @@ -54,6 +54,8 @@
>>
>> #include <linux/mfd/syscon.h>
>> #include <linux/phy/phy.h>
>> +#include <linux/usb/typec_dp.h>
>> +#include <linux/usb/typec_mux.h>
>>
>> #define CMN_SSM_BANDGAP (0x21 << 2)
>> #define CMN_SSM_BIAS (0x22 << 2)
>> @@ -286,12 +288,23 @@
>> #define RX_DIAG_SC2C_DELAY (0x81e1 << 2)
>>
>> #define PMA_LANE_CFG (0xc000 << 2)
>> +#define PMA_LANE3_DP_LANE_SEL(x) (((x) & 0x3) << 14)
>> +#define PMA_LANE3_INTERFACE_SEL(x) (((x) & 0x1) << 12)
>> +#define PMA_LANE2_DP_LANE_SEL(x) (((x) & 0x3) << 10)
>> +#define PMA_LANE2_INTERFACE_SEL(x) (((x) & 0x1) << 8)
>> +#define PMA_LANE1_DP_LANE_SEL(x) (((x) & 0x3) << 6)
>> +#define PMA_LANE1_INTERFACE_SEL(x) (((x) & 0x1) << 4)
>> +#define PMA_LANE0_DP_LANE_SEL(x) (((x) & 0x3) << 2)
>> +#define PMA_LANE0_INTERFACE_SEL(x) (((x) & 0x1) << 0)
>> #define PIPE_CMN_CTRL1 (0xc001 << 2)
>> #define PIPE_CMN_CTRL2 (0xc002 << 2)
>> #define PIPE_COM_LOCK_CFG1 (0xc003 << 2)
>> #define PIPE_COM_LOCK_CFG2 (0xc004 << 2)
>> #define PIPE_RCV_DET_INH (0xc005 << 2)
>> #define DP_MODE_CTL (0xc008 << 2)
>> +#define PHY_DP_POWER_STATE_ACK_MASK GENMASK(7, 4)
>> +#define PHY_DP_POWER_STATE_ACK_SHIFT 4
>> +#define PHY_DP_POWER_STATE_MASK GENMASK(3, 0)
>> #define DP_CLK_CTL (0xc009 << 2)
>> #define STS (0xc00F << 2)
>> #define PHY_ISO_CMN_CTRL (0xc010 << 2)
>> @@ -327,8 +340,15 @@
>>
>> #define DP_MODE_A0 BIT(4)
>> #define DP_MODE_A2 BIT(6)
>> -#define DP_MODE_ENTER_A0 0xc101
>> -#define DP_MODE_ENTER_A2 0xc104
>> +
>> +#define DP_MODE_MASK 0xf
>> +#define DP_MODE_ENTER_A0 BIT(0)
>> +#define DP_MODE_ENTER_A2 BIT(2)
>> +#define DP_MODE_ENTER_A3 BIT(3)
>> +#define DP_MODE_A0_ACK BIT(4)
>> +#define DP_MODE_A2_ACK BIT(6)
>> +#define DP_MODE_A3_ACK BIT(7)
>> +#define DP_LINK_RESET_DEASSERTED BIT(8)
>>
>> #define PHY_MODE_SET_TIMEOUT 100000
>>
>> @@ -340,6 +360,31 @@
>> #define MODE_DFP_USB BIT(1)
>> #define MODE_DFP_DP BIT(2)
>>
>> +enum phy_dp_lane_num {
>> + PHY_DP_LANE_0 = 0,
>> + PHY_DP_LANE_1,
>> + PHY_DP_LANE_2,
>> + PHY_DP_LANE_3,
>> +};
>> +
>> +enum phy_pma_if {
>> + PMA_IF_PIPE_PCS = 0,
>> + PMA_IF_PHY_DP,
>> +};
>> +
>> +enum phy_typec_role {
>> + TYPEC_PHY_USB = 0,
>> + TYPEC_PHY_DP,
>> + TYPEC_PHY_MAX,
>> +};
>> +
>> +enum phy_dp_power_state {
>> + PHY_DP_POWER_STATE_A0 = 0,
>> + PHY_DP_POWER_STATE_A1,
>> + PHY_DP_POWER_STATE_A2,
>> + PHY_DP_POWER_STATE_A3,
>> +};
>> +
>> struct usb3phy_reg {
>> u32 offset;
>> u32 enable_bit;
>> @@ -372,18 +417,22 @@ struct rockchip_typec_phy {
>> struct device *dev;
>> void __iomem *base;
>> struct extcon_dev *extcon;
>> + struct typec_mux_dev *mux;
>> + struct typec_switch_dev *sw;
>> struct regmap *grf_regs;
>> struct clk *clk_core;
>> struct clk *clk_ref;
>> struct reset_control *uphy_rst;
>> struct reset_control *pipe_rst;
>> struct reset_control *tcphy_rst;
>> + struct phy *phys[TYPEC_PHY_MAX];
>> const struct rockchip_usb3phy_port_cfg *port_cfgs;
>> /* mutex to protect access to individual PHYs */
>> struct mutex lock;
>>
>> bool flip;
>> u8 mode;
>> + u8 new_mode;
>> };
>>
>> struct phy_reg {
>> @@ -454,6 +503,99 @@ static const struct rockchip_usb3phy_port_cfg rk3399_usb3phy_port_cfgs[] = {
>> { /* sentinel */ }
>> };
>>
>> +static int tcphy_cfg_usb3_to_usb2_only(struct rockchip_typec_phy *tcphy,
>> + bool value);
>> +
>
> If possible, please avoid forward declarations of this static
> function and just move the implementation here if it needs to
> be declared earlier.
>
Then I also need to move "property_enable()" to here.
Okay, I will fix this in next version.
>> +static int tcphy_dp_set_power_state(struct rockchip_typec_phy *tcphy,
>> + enum phy_dp_power_state state)
>> +{
>> + u32 ack, reg, sts = BIT(state);
>> + int ret;
>> +
>> + /*
>> + * Power state changes must not be requested until after the cmn_ready
>> + * signal has gone active.
>> + */
>> + reg = readl(tcphy->base + PMA_CMN_CTRL1);
>> + if (!(reg & CMN_READY)) {
>> + dev_err(tcphy->dev, "cmn_ready in the inactive state\n");
>> + return -EINVAL;
>> + }
>
> You can use readl in the if condition directly here, since reg
> isn't used otherwise, but I'm also fine with it as-is if you think
> it helps readability.
>
Hmmm. From the context, the current version should be more readable.
>> +
>> + reg = readl(tcphy->base + DP_MODE_CTL);
>> + reg &= ~PHY_DP_POWER_STATE_MASK;
>> + reg |= sts;
>> + writel(reg, tcphy->base + DP_MODE_CTL);
>> +
>> + ret = readl_poll_timeout(tcphy->base + DP_MODE_CTL,
>> + ack, (((ack & PHY_DP_POWER_STATE_ACK_MASK) >>
>> + PHY_DP_POWER_STATE_ACK_SHIFT) == sts), 10,
>> + PHY_MODE_SET_TIMEOUT);
>
> Here please use FIELD_GET() from <linux/bitfield.h> like this:
>
> ret = readl_poll_timeout(tcphy->base + DP_MODE_CTL, ack,
> FIELD_GET(PHY_DP_POWER_STATE_ACK_MASK, ack) == sts,
> 10, PHY_MODE_SET_TIMEOUT);
>
> PHY_DP_POWER_STATE_ACK_SHIFT is then no longer needed.
>
Great. Will fix in next version.
>> + if (ret < 0) {
>
> Nitpick: `if (ret) {` suffices here, readl_poll_timeout returns 0 on
> success and negative errno on failure.
>
>> + dev_err(tcphy->dev, "failed to enter power state %d\n", state);
>> + return ret;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +/*
>> + * For the TypeC PHY, the 4 lanes are mapping to the USB TypeC receptacle pins
>> + * as follows:
>> + * -------------------------------------------------------------------
>> + * PHY Lanes/Module Pins TypeC Receptacle Pins
>> + * -------------------------------------------------------------------
>> + * Lane0 (tx_p/m_ln_0) TX1+/TX1- (pins A2/A3)
>> + * Lane1 (tx_rx_p/m_ln_1) RX1+/RX1- (pins B11/B10)
>> + * Lane2 (tx_rx_p/m_ln_2) RX2+/RX2- (pins A11/A10)
>> + * Lane3 (tx_p/m_ln_3) TX2+/TX2- (pins B2/B3)
>> + * -------------------------------------------------------------------
>> + *
>> + * USB and DP lanes mapping to TypeC PHY lanes for each of pin assignment
>> + * options (normal connector orientation) described in the VESA DisplayPort
>> + * Alt Mode on USB TypeC Standard as follows:
>> + *
>> + * ----------------------------------------------------------------------
>> + * PHY Lanes A B C D E F
>> + * ----------------------------------------------------------------------
>> + * 0 ML1 SSTX ML2 SSTX ML2 SSTX
>> + * 1 ML3 SSRX ML3 SSRX ML3 SSRX
>> + * 2 ML2 ML1 ML0 ML0 ML0 ML0
>> + * 3 ML0 ML0 ML1 ML1 ML1 ML1
>> + * ----------------------------------------------------------------------
>> + */
>> +static void tcphy_set_lane_mapping(struct rockchip_typec_phy *tcphy, u8 mode)
>> +{
>> + /*
>> + * The PMA_LANE_CFG register is used to select whether a PMA lane
>> + * is mapped for USB or PHY DP. The PMA_LANE_CFG register is
>> + * configured based on a normal connector orientation. Logic in the
>> + * PHY automatically handles the flipped connector case based on the
>> + * setting of orientation of TypeC PHY.
>> + */
>> + if (mode == MODE_DFP_DP) {
>> + /* This maps to VESA DP Alt Mode pin assignments C and E. */
>> + writel(PMA_LANE3_DP_LANE_SEL(PHY_DP_LANE_1) |
>> + PMA_LANE3_INTERFACE_SEL(PMA_IF_PHY_DP) |
>> + PMA_LANE2_DP_LANE_SEL(PHY_DP_LANE_0) |
>> + PMA_LANE2_INTERFACE_SEL(PMA_IF_PHY_DP) |
>> + PMA_LANE1_DP_LANE_SEL(PHY_DP_LANE_3) |
>> + PMA_LANE1_INTERFACE_SEL(PMA_IF_PHY_DP) |
>> + PMA_LANE0_DP_LANE_SEL(PHY_DP_LANE_2) |
>> + PMA_LANE0_INTERFACE_SEL(PMA_IF_PHY_DP),
>> + tcphy->base + PMA_LANE_CFG);
>> + } else {
>> + /* This maps to VESA DP Alt Mode pin assignments D and F. */
>> + writel(PMA_LANE3_DP_LANE_SEL(PHY_DP_LANE_1) |
>> + PMA_LANE3_INTERFACE_SEL(PMA_IF_PHY_DP) |
>> + PMA_LANE2_DP_LANE_SEL(PHY_DP_LANE_0) |
>> + PMA_LANE2_INTERFACE_SEL(PMA_IF_PHY_DP) |
>> + PMA_LANE1_INTERFACE_SEL(PMA_IF_PIPE_PCS) |
>> + PMA_LANE0_INTERFACE_SEL(PMA_IF_PIPE_PCS),
>> + tcphy->base + PMA_LANE_CFG);
>> + }
>> +}
>> +
>> static void tcphy_cfg_24m(struct rockchip_typec_phy *tcphy)
>> {
>> u32 i, rdata;
>> @@ -743,8 +885,10 @@ static int tcphy_phy_init(struct rockchip_typec_phy *tcphy, u8 mode)
>> tcphy_dp_aux_set_flip(tcphy);
>>
>> tcphy_cfg_24m(tcphy);
>> + tcphy_set_lane_mapping(tcphy, mode);
>>
>> if (mode == MODE_DFP_DP) {
>> + tcphy_cfg_usb3_to_usb2_only(tcphy, true);
>> tcphy_cfg_dp_pll(tcphy);
>> for (i = 0; i < 4; i++)
>> tcphy_dp_cfg_lane(tcphy, i);
>
> Is there a difference between the values tcphy_set_lane_mapping() writes
> to PMA_LANE_CFG, and what this if block writes to PMA_LANE_CFG at the
> end (either PIN_ASSIGN_C_E or PIN_ASSIGN_D_F)?
>
> If not, then I think think the second write to it may be redundant.
>
That's right. I will remove them.
>> @@ -768,7 +912,10 @@ static int tcphy_phy_init(struct rockchip_typec_phy *tcphy, u8 mode)
>> writel(PIN_ASSIGN_D_F, tcphy->base + PMA_LANE_CFG);
>> }
>>
>> - writel(DP_MODE_ENTER_A2, tcphy->base + DP_MODE_CTL);
>> + val = readl(tcphy->base + DP_MODE_CTL);
>> + val &= ~DP_MODE_MASK;
>> + val |= DP_MODE_ENTER_A2 | DP_LINK_RESET_DEASSERTED;
>> + writel(val, tcphy->base + DP_MODE_CTL);
>>
>> reset_control_deassert(tcphy->uphy_rst);
>>
>> @@ -811,8 +958,9 @@ static int tcphy_get_mode(struct rockchip_typec_phy *tcphy)
>> u8 mode;
>> int ret, ufp, dp;
>>
>> + /* If extcon not exist, try to use tcpm mode */
>> if (!edev)
>> - return MODE_DFP_USB;
>> + return tcphy->new_mode;
>>
>> ufp = extcon_get_state(edev, EXTCON_USB);
>> dp = extcon_get_state(edev, EXTCON_DISP_DP);
>> @@ -850,6 +998,71 @@ static int tcphy_get_mode(struct rockchip_typec_phy *tcphy)
>> return mode;
>> }
>>
>> +#if IS_ENABLED(CONFIG_TYPEC)
>> +static int tcphy_orien_sw_set(struct typec_switch_dev *sw,
>> + enum typec_orientation orien)
>> +{
>> + struct rockchip_typec_phy *tcphy = typec_switch_get_drvdata(sw);
>> +
>> + mutex_lock(&tcphy->lock);
>
> Instead of this you can use
>
> guard(mutex)(&tcphy->lock);
>
> from <linux/cleanup.h>
>
> and get rid of the manual unlock and goto. The lock held by the guard
> statement will be dropped as soon as the scope of automatic variable
> declaration is left, so no manual goto unwind needs to be done.
>
Thanks. I will try it.
>> +
>> + if (orien == TYPEC_ORIENTATION_NONE) {
>> + tcphy->new_mode = MODE_DISCONNECT;
>> + goto unlock_ret;
>> + }
>> +
>> + tcphy->flip = (orien == TYPEC_ORIENTATION_REVERSE) ? true : false;
>> +
>> +unlock_ret:
>> + mutex_unlock(&tcphy->lock);
>> + return 0;
>> +}
>> +
>> +static void udphy_orien_switch_unregister(void *data)
>> +{
>> + struct rockchip_typec_phy *tcphy = data;
>> +
>> + typec_switch_unregister(tcphy->sw);
>> +}
>> +
>> +static int tcphy_setup_orien_switch(struct rockchip_typec_phy *tcphy)
>> +{
>> + struct typec_switch_desc sw_desc = { };
>> + struct device_node *np;
>> + int ret = 0;
>> +
>> + np = of_get_child_by_name(tcphy->dev->of_node, "usb3-port");
>> + if (!np)
>> + return 0;
>> +
>> + if (!of_property_read_bool(np, "orientation-switch"))
>> + goto put_np;
>
> np isn't needed after this check. Instead of manual freeing of np
> with a goto, you can use the `__free(device_node)` attribute.
>
> static int foo(struct rockchip_typec_phy *tcphy)
> {
> /* if return can happen before np assigned, NULL-init it here */
> struct device_node __free(device_node) *np;
>
> np = of_get_child_by_name(tcphy->dev->of_node, "usb3-port");
> if (!np)
> return 0;
>
> if (!of_property_read_bool(np, "orientation-switch"))
> return 0; /* no more manual put needed, done when scope left */
>
> /* ... etc etc. ... */
>
> return devm_add_action_or_reset(tcphy->dev, ...)
> }
>
Will fix in next version.
>> +
>> + sw_desc.drvdata = tcphy;
>> + sw_desc.fwnode = device_get_named_child_node(tcphy->dev, "usb3-port");
>> + sw_desc.set = tcphy_orien_sw_set;
>> +
>> + tcphy->sw = typec_switch_register(tcphy->dev, &sw_desc);
>> + if (IS_ERR(tcphy->sw)) {
>> + dev_err(tcphy->dev, "Error register typec orientation switch: %ld\n",
>> + PTR_ERR(tcphy->sw));
>
> Instead of %ld, use %pe and drop the `PTR_ERR()`, so just:
>
> dev_err(tcphy->dev, "Error register typec orientation switch: %pe\n",
> tcphy->sw);
>
Will fix in next version.
>> + ret = PTR_ERR(tcphy->sw);
>> + goto put_np;
>> + }
>> +
>> + ret = devm_add_action_or_reset(tcphy->dev, udphy_orien_switch_unregister, tcphy);
>> +
>> +put_np:
>> + of_node_put(np);
>> + return ret;
>> +}
>> +#else
>> +static int tcphy_setup_orien_switch(struct rockchip_typec_phy *tcphy)
>> +{
>> + return 0;
>> +}
>> +#endif
>> +
>> static int tcphy_cfg_usb3_to_usb2_only(struct rockchip_typec_phy *tcphy,
>> bool value)
>> {
>> @@ -989,14 +1202,9 @@ static int rockchip_dp_phy_power_on(struct phy *phy)
>>
>> tcphy_dp_aux_calibration(tcphy);
>>
>> - writel(DP_MODE_ENTER_A0, tcphy->base + DP_MODE_CTL);
>> -
>> - ret = readx_poll_timeout(readl, tcphy->base + DP_MODE_CTL,
>> - val, val & DP_MODE_A0, 1000,
>> - PHY_MODE_SET_TIMEOUT);
>> - if (ret < 0) {
>> - writel(DP_MODE_ENTER_A2, tcphy->base + DP_MODE_CTL);
>> - dev_err(tcphy->dev, "failed to wait TCPHY enter A0\n");
>> + ret = tcphy_dp_set_power_state(tcphy, PHY_DP_POWER_STATE_A0);
>> + if (ret) {
>> + dev_err(tcphy->dev, "failed to enter A0 power state\n");
>> goto power_on_finish;
>> }
>>
>> @@ -1013,6 +1221,7 @@ static int rockchip_dp_phy_power_on(struct phy *phy)
>> static int rockchip_dp_phy_power_off(struct phy *phy)
>> {
>> struct rockchip_typec_phy *tcphy = phy_get_drvdata(phy);
>> + int ret;
>>
>> mutex_lock(&tcphy->lock);
>>
>> @@ -1021,7 +1230,11 @@ static int rockchip_dp_phy_power_off(struct phy *phy)
>>
>> tcphy->mode &= ~MODE_DFP_DP;
>>
>> - writel(DP_MODE_ENTER_A2, tcphy->base + DP_MODE_CTL);
>> + ret = tcphy_dp_set_power_state(tcphy, PHY_DP_POWER_STATE_A2);
>> + if (ret) {
>> + dev_err(tcphy->dev, "failed to enter A2 power state\n");
>> + goto unlock;
>> + }
>>
>> if (tcphy->mode == MODE_DISCONNECT)
>> tcphy_phy_deinit(tcphy);
>> @@ -1037,6 +1250,93 @@ static const struct phy_ops rockchip_dp_phy_ops = {
>> .owner = THIS_MODULE,
>> };
>>
>> +#if IS_ENABLED(CONFIG_TYPEC)
>> +static int tcphy_typec_mux_set(struct typec_mux_dev *mux, struct typec_mux_state *state)
>> +{
>> + struct rockchip_typec_phy *tcphy = typec_mux_get_drvdata(mux);
>> + struct typec_displayport_data *data;
>> + int hpd = 0;
>
> hpd doesn't need to be initialised here.
>
That's right.
>> +
>> + mutex_lock(&tcphy->lock);
>
> Prefer guard(mutex)(&tcphy->lock); here to combat potential for
> future lock leaking bugs.
>
Will fix in next version.
>> +
>> + switch (state->mode) {
>> + case TYPEC_STATE_SAFE:
>> + fallthrough;
>> + case TYPEC_STATE_USB:
>> + tcphy->new_mode = MODE_DFP_USB;
>> + phy_set_bus_width(tcphy->phys[TYPEC_PHY_DP], 0);
>> + break;
>> + case TYPEC_DP_STATE_C:
>> + case TYPEC_DP_STATE_E:
>> + if (state->alt->svid != USB_TYPEC_DP_SID)
>> + break;
>> + tcphy->new_mode = MODE_DFP_DP;
>> + data = state->data;
>> + hpd = !!(data->status & DP_STATUS_HPD_STATE);
>> + phy_set_bus_width(tcphy->phys[TYPEC_PHY_DP], hpd ? 4 : 0);
>> + break;
>> + case TYPEC_DP_STATE_D:
>> + if (state->alt->svid != USB_TYPEC_DP_SID)
>> + break;
>> + tcphy->new_mode = MODE_DFP_DP | MODE_DFP_USB;
>> + data = state->data;
>> + hpd = !!(data->status & DP_STATUS_HPD_STATE);
>> + phy_set_bus_width(tcphy->phys[TYPEC_PHY_DP], hpd ? 2 : 0);
>> + break;
>> + default:
>> + break;
>
> Might be good to return -EINVAL here. No additional ret local needed
> with above guard statement. :)
>
It make sense. Will fix in next version.
>> + }
>> +
>> + mutex_unlock(&tcphy->lock);
>> +
>> + return 0;
>> +}
>> +
>> +static void tcphy_typec_mux_unregister(void *data)
>> +{
>> + struct rockchip_typec_phy *tcphy = data;
>> +
>> + typec_mux_unregister(tcphy->mux);
>> +}
>> +
>> +static int tcphy_setup_typec_mux(struct rockchip_typec_phy *tcphy)
>> +{
>> + struct typec_mux_desc mux_desc = {};
>> + struct device_node *np;
>> + int ret = 0;
>> +
>> + np = of_get_child_by_name(tcphy->dev->of_node, "dp-port");
>> + if (!np)
>> + return 0;
>> +
>> + if (!of_property_read_bool(np, "mode-switch"))
>> + goto put_np;
>
> __free attribute on np can get rid of the manual goto put_np stuff
> here as well.
>
Will fix in next version.
>> +
>> + mux_desc.drvdata = tcphy;
>> + mux_desc.fwnode = device_get_named_child_node(tcphy->dev, "dp-port");
>> + mux_desc.set = tcphy_typec_mux_set;
>> +
>> + tcphy->mux = typec_mux_register(tcphy->dev, &mux_desc);
>> + if (IS_ERR(tcphy->mux)) {
>> + dev_err(tcphy->dev, "Error register typec mux: %ld\n",
>> + PTR_ERR(tcphy->mux));
>
> %pe format specifier again.
>
Will fix in next version.
>> + ret = PTR_ERR(tcphy->mux);
>> + goto put_np;
>> + }
>> +
>> + ret = devm_add_action_or_reset(tcphy->dev, tcphy_typec_mux_unregister, tcphy);
>> +
>> +put_np:
>> + of_node_put(np);
>> + return ret;
>> +}
>> +#else
>> +static int tcphy_setup_typec_mux(struct rockchip_typec_phy *tcphy)
>> +{
>> + return 0;
>> +}
>> +#endif
>> +
>> static int tcphy_parse_dt(struct rockchip_typec_phy *tcphy,
>> struct device *dev)
>> {
>> @@ -1095,6 +1395,25 @@ static void typec_phy_pre_init(struct rockchip_typec_phy *tcphy)
>> tcphy->mode = MODE_DISCONNECT;
>> }
>>
>> +static int typec_dp_lane_get(struct rockchip_typec_phy *tcphy)
>> +{
>> + int dp_lanes;
>> +
>> + switch (tcphy->new_mode) {
>> + case MODE_DFP_DP:
>> + dp_lanes = 4;
>> + break;
>> + case MODE_DFP_DP | MODE_DFP_USB:
>> + dp_lanes = 2;
>> + break;
>> + default:
>> + dp_lanes = 0;
>> + break;
>> + }
>> +
>> + return dp_lanes;
>
> dp_lanes local doesn't need to exist here, you
> can just return from the switch statement directly:
>
> static int typec_dp_lane_get(struct rockchip_typec_phy *tcphy)
> {
> switch (tcphy->new_mode) {
> case MODE_DFP_DP:
> return 4;
> case MODE_DFP_DP | MODE_DFP_USB:
> return 2;
> default:
> return 0;
> }
> }
>
>
Exactly. Will fix in next version.
>> +}
>> +
>> static int rockchip_typec_phy_probe(struct platform_device *pdev)
>> {
>> struct device *dev = &pdev->dev;
>> @@ -1142,6 +1461,7 @@ static int rockchip_typec_phy_probe(struct platform_device *pdev)
>> return ret;
>>
>> tcphy->dev = dev;
>> + tcphy->new_mode = MODE_DFP_USB;
>> platform_set_drvdata(pdev, tcphy);
>> mutex_init(&tcphy->lock);
>>
>> @@ -1151,6 +1471,7 @@ static int rockchip_typec_phy_probe(struct platform_device *pdev)
>> if (IS_ERR(tcphy->extcon)) {
>> if (PTR_ERR(tcphy->extcon) == -ENODEV) {
>> tcphy->extcon = NULL;
>> + dev_info(dev, "extcon not exist, try to use typec mux\n");
>> } else {
>> if (PTR_ERR(tcphy->extcon) != -EPROBE_DEFER)
>> dev_err(dev, "Invalid or missing extcon\n");
>> @@ -1158,19 +1479,34 @@ static int rockchip_typec_phy_probe(struct platform_device *pdev)
>> }
>> }
>>
>> + ret = tcphy_setup_orien_switch(tcphy);
>> + if (ret)
>> + return ret;
>> +
>> + ret = tcphy_setup_typec_mux(tcphy);
>> + if (ret)
>> + return ret;
>
> If they are just used in the probe function, you can make the error
> prints in tcphy_setup_orien_switch() and tcphy_setup_typec_mux()
> use dev_err_probe instead. That way, if the probe function fails,
> the error message is shown in the devices_deferred debugfs file.
>
Nice, I will use it in next version.
>> +
>> pm_runtime_enable(dev);
>>
>> for_each_available_child_of_node(np, child_np) {
>> struct phy *phy;
>>
>> - if (of_node_name_eq(child_np, "dp-port"))
>> + if (of_node_name_eq(child_np, "dp-port")) {
>> phy = devm_phy_create(dev, child_np,
>> &rockchip_dp_phy_ops);
>> - else if (of_node_name_eq(child_np, "usb3-port"))
>> + if (!IS_ERR(phy)) {
>> + tcphy->phys[TYPEC_PHY_DP] = phy;
>> + phy_set_bus_width(phy, typec_dp_lane_get(tcphy));
>> + }
>> + } else if (of_node_name_eq(child_np, "usb3-port")) {
>> phy = devm_phy_create(dev, child_np,
>> &rockchip_usb3_phy_ops);
>> - else
>> + if (!IS_ERR(phy))
>> + tcphy->phys[TYPEC_PHY_USB] = phy;
>> + } else {
>> continue;
>> + }
>>
>> if (IS_ERR(phy)) {
>> dev_err(dev, "failed to create phy: %pOFn\n",
>>
>
> Kind regards,
> Nicolas Frattaroli
>
>
>
--
Best,
Chaoyi
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* Re: [PATCH RFC v4 5/9] phy: qcom: qmp-pcie: Refactor pipe clk register and parse_dt helpers
From: Qiang Yu @ 2026-05-29 7:02 UTC (permalink / raw)
To: Dmitry Baryshkov
Cc: Manivannan Sadhasivam, Vinod Koul, Neil Armstrong, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Philipp Zabel, Bjorn Andersson,
Konrad Dybcio, linux-arm-msm, linux-phy, devicetree, linux-kernel
In-Reply-To: <ol436i3oqgdns74dliw72qns22gqfgygm6qkz7mo4g7oiywlsg@johrhdyv4rqx>
On Thu, May 28, 2026 at 04:48:24PM +0300, Dmitry Baryshkov wrote:
> On Fri, May 22, 2026 at 04:27:35PM +0530, Manivannan Sadhasivam wrote:
> > On Wed, May 20, 2026 at 07:25:01PM +0300, Dmitry Baryshkov wrote:
> > > On Mon, May 18, 2026 at 10:47:16PM -0700, Qiang Yu wrote:
> > > > Some QMP PCIe PHY hardware blocks can be split into multiple sub-PHYs
> > > > under a single DT node, each requiring its own pipe clock registration and
> > > > DT resource mapping. The current helpers are tightly coupled to a single
> > > > qmp_pcie instance, which prevents reuse across sub-PHY instances.
> > > >
> > > > Refactor __phy_pipe_clk_register() as a generic helper and reduce
> > > > phy_pipe_clk_register() to a thin wrapper around it. Similarly, extract
> > > > qmp_pcie_parse_dt_common() from qmp_pcie_parse_dt() to hold the register-
> > > > mapping and pipe-clock setup that will be shared between sub-PHY instances,
> > > > with pipe clock names parameterised per instance.
> > > >
> > > > This is a preparatory step before adding multi-PHY support. No functional
> > > > change for existing platforms.
> > > >
> > > > Signed-off-by: Qiang Yu <qiang.yu@oss.qualcomm.com>
> > > > ---
> > > > drivers/phy/qualcomm/phy-qcom-qmp-pcie.c | 76 ++++++++++++++++++--------------
> > > > 1 file changed, 44 insertions(+), 32 deletions(-)
> > >
> > > I'd suggest splitting the Glymur PHY to a separate driver. Otherwise we
> > > end up having too many single-platform, single-device specifics which
> > > don't apply to other platforms.
> > >
> >
> > I don't think that's really needed. This shared PHY concept is going to be
> > applicable to upcoming SoCs as well. And moreover, the split won't be clean
> > either. We still need to reuse a lot of common logic in the 'phy-qcom-qmp-pcie'
> > driver and may only end up keeping very minimal code in
> > 'phy-qcom-qmp-pcie-glymur'.
>
> Then splitting makes even more sense. Let's not clutter the existing
> driver with too many conditions and options.
>
> >
> > If you are concerned about the file size of 'phy-qcom-qmp-pcie', then we should
> > move the SoC specific 'cfg' structs into a separate file as that's what
> > occupying majority of the space.
>
> No, it's really the 'shared' part.
>
To confirm, are you okay with some code duplication between the new
Glymur-specific driver and phy-qcom-qmp-pcie driver.
- Qiang Yu
> >
> > - Mani
> >
> > --
> > மணிவண்ணன் சதாசிவம்
> >
> > --
> > linux-phy mailing list
> > linux-phy@lists.infradead.org
> > https://lists.infradead.org/mailman/listinfo/linux-phy
>
> --
> With best wishes
> Dmitry
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* Re: [PATCH RESEND v8 00/10] SPMI: Implement sub-devices and migrate drivers
From: Stephen Boyd @ 2026-05-29 1:43 UTC (permalink / raw)
To: AngeloGioacchino Del Regno, Jonathan Cameron
Cc: dlechner, nuno.sa, andy, arnd, gregkh, srini, vkoul,
neil.armstrong, sre, krzk, dmitry.baryshkov, quic_wcheng,
melody.olvera, quic_nsekar, ivo.ivanov.ivanov1, abelvesa,
luca.weiss, konrad.dybcio, mitltlatltl, krishna.kurapati,
linux-arm-msm, linux-iio, linux-kernel, linux-phy, linux-pm,
kernel
In-Reply-To: <07739437-4720-4a15-87cc-40f6f92f3759@kernel.org>
Quoting AngeloGioacchino Del Regno (2026-05-20 04:51:26)
> On 5/11/26 15:17, Jonathan Cameron wrote:
> > On Mon, 11 May 2026 12:07:55 +0200
> > AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com> wrote:
> >
> > Hi Angelo,
> >
> > Why the resend? If marking a series with that I expect to see it
> > called out as first thing in the cover letter.
> >
>
> Right, forgot to mention why.
>
> The v8 was sent on January 2026, I pinged maintainers to pick it on
> March 2026, even if this was fully reviewed by multiple people nobody
> picked anything here.
>
> ....and I've resent it because pinging multiple times didn't work, and
> because the series got old all that much. :-)
>
I take it you want me to apply these patches to the spmi tree? Can you
respond to the Sashiko bot comments about why it's wrong or fix issues
it found?
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* Re: [PATCH v15 1/9] drm/bridge: Implement generic USB Type-C DP HPD bridge
From: Chaoyi Chen @ 2026-05-29 1:18 UTC (permalink / raw)
To: Nicolas Frattaroli
Cc: Heikki Krogerus, Greg Kroah-Hartman, Dmitry Baryshkov, Peter Chen,
Luca Ceresoli, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Vinod Koul, Kishon Vijay Abraham I, Heiko Stuebner, Sandy Huang,
Andy Yan, Yubing Zhang, Frank Wang, Andrzej Hajda, Neil Armstrong,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter, Amit Sunil Dhamne, Dragan Simic, Johan Jonker,
Diederik de Haas, Peter Robinson, Hugh Cole-Baker, dri-devel,
linux-usb, devicetree, linux-kernel, linux-phy, linux-arm-kernel,
linux-rockchip, Chaoyi Chen
In-Reply-To: <q6ufdWF5QJ60i9KlIiE_AA@collabora.com>
Hello Nicolas,
On 5/28/2026 11:37 PM, Nicolas Frattaroli wrote:
> On Wednesday, 4 March 2026 10:41:44 Central European Summer Time Chaoyi Chen wrote:
>> From: Chaoyi Chen <chaoyi.chen@rock-chips.com>
>>
>> The HPD function of Type-C DP is implemented through
>> drm_connector_oob_hotplug_event(). For embedded DP, it is required
>> that the DRM connector fwnode corresponds to the Type-C port fwnode.
>>
>> To describe the relationship between the DP controller and the Type-C
>> port device, we usually using drm_bridge to build a bridge chain.
>>
>> Now several USB-C controller drivers have already implemented the DP
>> HPD bridge function provided by aux-hpd-bridge.c, it will build a DP
>> HPD bridge on USB-C connector port device.
>>
>> But this requires the USB-C controller driver to manually register the
>> HPD bridge. If the driver does not implement this feature, the bridge
>> will not be create.
>>
>> So this patch implements a generic DP HPD bridge based on
>> aux-hpd-bridge.c. It will monitor Type-C bus events, and when a
>> Type-C port device containing the DP svid is registered, it will
>> create an HPD bridge for it without the need for the USB-C controller
>> driver to implement it.
>>
>> Signed-off-by: Chaoyi Chen <chaoyi.chen@rock-chips.com>
>> Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
>> ---
>>
>> (no changes since v14)
>>
>> Changes in v13:
>> - Only register drm dp hpd bridge for typec port altmode device.
>>
>> (no changes since v12)
>>
>> Changes in v11:
>> - Switch to using typec bus notifiers.
>>
>> (no changes since v10)
>>
>> Changes in v9:
>> - Remove the exposed DRM_AUX_HPD_BRIDGE option, and select
>> DRM_AUX_HPD_TYPEC_BRIDGE when it is available.
>> - Add more commit comment about problem background.
>>
>> Changes in v8:
>> - Merge generic DP HPD bridge into one module.
>> ---
>>
>> drivers/gpu/drm/bridge/Kconfig | 10 ++++
>> drivers/gpu/drm/bridge/Makefile | 1 +
>> .../gpu/drm/bridge/aux-hpd-typec-dp-bridge.c | 49 +++++++++++++++++++
>> 3 files changed, 60 insertions(+)
>> create mode 100644 drivers/gpu/drm/bridge/aux-hpd-typec-dp-bridge.c
>>
>> diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
>> index a250afd8d662..559487aa09a9 100644
>> --- a/drivers/gpu/drm/bridge/Kconfig
>> +++ b/drivers/gpu/drm/bridge/Kconfig
>> @@ -30,6 +30,16 @@ config DRM_AUX_HPD_BRIDGE
>> Simple bridge that terminates the bridge chain and provides HPD
>> support.
>>
>> +if DRM_AUX_HPD_BRIDGE
>> +config DRM_AUX_HPD_TYPEC_BRIDGE
>> + tristate
>> + depends on TYPEC || !TYPEC
>> + default TYPEC
>> + help
>> + Simple bridge that terminates the bridge chain and provides HPD
>> + support. It build bridge on each USB-C connector device node.
>> +endif
>> +
>> menu "Display Interface Bridges"
>> depends on DRM && DRM_BRIDGE
>>
>> diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
>> index c7dc03182e59..a3a0393d2e72 100644
>> --- a/drivers/gpu/drm/bridge/Makefile
>> +++ b/drivers/gpu/drm/bridge/Makefile
>> @@ -1,6 +1,7 @@
>> # SPDX-License-Identifier: GPL-2.0
>> obj-$(CONFIG_DRM_AUX_BRIDGE) += aux-bridge.o
>> obj-$(CONFIG_DRM_AUX_HPD_BRIDGE) += aux-hpd-bridge.o
>> +obj-$(CONFIG_DRM_AUX_HPD_TYPEC_BRIDGE) += aux-hpd-typec-dp-bridge.o
>> obj-$(CONFIG_DRM_CHIPONE_ICN6211) += chipone-icn6211.o
>> obj-$(CONFIG_DRM_CHRONTEL_CH7033) += chrontel-ch7033.o
>> obj-$(CONFIG_DRM_CROS_EC_ANX7688) += cros-ec-anx7688.o
>> diff --git a/drivers/gpu/drm/bridge/aux-hpd-typec-dp-bridge.c b/drivers/gpu/drm/bridge/aux-hpd-typec-dp-bridge.c
>> new file mode 100644
>> index 000000000000..d915e0fb0668
>> --- /dev/null
>> +++ b/drivers/gpu/drm/bridge/aux-hpd-typec-dp-bridge.c
>> @@ -0,0 +1,49 @@
>> +// SPDX-License-Identifier: GPL-2.0+
>
> Having a copyright statement added here is likely required.
> Something like
>
> /*
> * Copyright (C) 2026 Rockchip Electronics Co., Ltd.
> *
> * Author: Chaoyi Chen <chaoyi.chen@rock-chips.com>
> */
>
> will suffice. While I hope it's never relevant for a driver this
> small, explicit copyright statements in this form have a legal
> effect in some jurisdictions like the US, where by law it
> automatically dismisses the defense that the copyright holder
> wasn't known.
>
Oh! I hadn't noticed that. Thank you for your legal advice.
>> +#include <linux/of.h>
>> +#include <linux/usb/typec_altmode.h>
>> +#include <linux/usb/typec_dp.h>
>> +
>> +#include <drm/bridge/aux-bridge.h>
>> +
>> +static int drm_typec_bus_event(struct notifier_block *nb,
>> + unsigned long action, void *data)
>> +{
>> + struct device *dev = (struct device *)data;
>
> Small nitpick: The explicit (struct device *) cast of data isn't
> needed here. Just
>
> struct device *dev = data;
>
> will work fine. The benefit is that if the type of "data" ever changes
> (e.g. from void *data to const void *data) then we're not silencing
> a warning/error through an explicit cast.
>
Yep. Will fix in v2.
>> + struct typec_altmode *alt = to_typec_altmode(dev);
>> +
>> + if (action != BUS_NOTIFY_ADD_DEVICE)
>> + goto done;
>> +
>> + /*
>> + * alt->dev.parent->parent : USB-C controller device
>> + * alt->dev.parent : USB-C connector device
>> + */
>> + if (is_typec_port_altmode(&alt->dev) && alt->svid == USB_TYPEC_DP_SID)
>> + drm_dp_hpd_bridge_register(alt->dev.parent->parent,
>> + to_of_node(alt->dev.parent->fwnode));
>> +
>> +done:
>> + return NOTIFY_OK;
>> +}
>
> Nitpicking: the "done" label and "goto done" here is redundant.
> You could remove the label and replace the goto with another
> "return NOTIFY_OK;". There's no functional change here though.
>
Will fix in v2.
>> +
>> +static struct notifier_block drm_typec_event_nb = {
>> + .notifier_call = drm_typec_bus_event,
>> +};
>> +
>> +static void drm_aux_hpd_typec_dp_bridge_module_exit(void)
>> +{
>> + bus_unregister_notifier(&typec_bus, &drm_typec_event_nb);
>> +}
>> +
>> +static int __init drm_aux_hpd_typec_dp_bridge_module_init(void)
>> +{
>> + bus_register_notifier(&typec_bus, &drm_typec_event_nb);
>> +
>> + return 0;
>> +}
>> +
>> +module_init(drm_aux_hpd_typec_dp_bridge_module_init);
>> +module_exit(drm_aux_hpd_typec_dp_bridge_module_exit);
>> +
>> +MODULE_DESCRIPTION("DRM TYPEC DP HPD BRIDGE");
>> +MODULE_LICENSE("GPL");
>>
>
> Feel free to add yourself as MODULE_AUTHOR here. :)
>
> With those things changed:
>
> Reviewed-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
>
> Kind regards,
> Nicolas Frattaroli
>
>
>
--
Best,
Chaoyi
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* Re: [PATCH v15 4/9] phy: rockchip: phy-rockchip-typec: Add typec_mux/typec_switch support
From: Nicolas Frattaroli @ 2026-05-28 20:16 UTC (permalink / raw)
To: Heikki Krogerus, Greg Kroah-Hartman, Dmitry Baryshkov, Peter Chen,
Luca Ceresoli, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Vinod Koul, Kishon Vijay Abraham I, Heiko Stuebner, Sandy Huang,
Andy Yan, Yubing Zhang, Frank Wang, Andrzej Hajda, Neil Armstrong,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter, Amit Sunil Dhamne, Dragan Simic, Johan Jonker,
Diederik de Haas, Peter Robinson, Hugh Cole-Baker, dri-devel,
Chaoyi Chen
Cc: linux-usb, devicetree, linux-kernel, linux-phy, linux-arm-kernel,
linux-rockchip, dri-devel, Chaoyi Chen
In-Reply-To: <20260304094152.92-5-kernel@airkyi.com>
On Wednesday, 4 March 2026 10:41:47 Central European Summer Time Chaoyi Chen wrote:
> From: Chaoyi Chen <chaoyi.chen@rock-chips.com>
>
> This patch add support for Type-C Port Controller Manager. Each PHY
> will register typec_mux and typec_switch when external Type-C
> controller is present. Type-C events are handled by TCPM without
> extcon.
>
> The extcon device should still be supported.
>
> Signed-off-by: Chaoyi Chen <chaoyi.chen@rock-chips.com>
> ---
>
> (no changes since v7)
>
> Changes in v6:
> - Fix depend in Kconfig.
> - Check DP svid in tcphy_typec_mux_set().
> - Remove mode setting in tcphy_orien_sw_set().
>
> (no changes since v5)
>
> Changes in v4:
> - Remove notify DP HPD state by USB/DP PHY.
>
> (no changes since v3)
>
> Changes in v2:
> - Fix compile error when CONFIG_TYPEC is not enabled.
> - Notify DP HPD state by USB/DP PHY.
> ---
>
> drivers/phy/rockchip/Kconfig | 1 +
> drivers/phy/rockchip/phy-rockchip-typec.c | 368 +++++++++++++++++++++-
> 2 files changed, 353 insertions(+), 16 deletions(-)
>
> diff --git a/drivers/phy/rockchip/Kconfig b/drivers/phy/rockchip/Kconfig
> index 14698571b607..db4adc7c53da 100644
> --- a/drivers/phy/rockchip/Kconfig
> +++ b/drivers/phy/rockchip/Kconfig
> @@ -119,6 +119,7 @@ config PHY_ROCKCHIP_SNPS_PCIE3
> config PHY_ROCKCHIP_TYPEC
> tristate "Rockchip TYPEC PHY Driver"
> depends on OF && (ARCH_ROCKCHIP || COMPILE_TEST)
> + depends on TYPEC || TYPEC=n
> select EXTCON
> select GENERIC_PHY
> select RESET_CONTROLLER
> diff --git a/drivers/phy/rockchip/phy-rockchip-typec.c b/drivers/phy/rockchip/phy-rockchip-typec.c
> index d9701b6106d5..1f5b4142cbe4 100644
> --- a/drivers/phy/rockchip/phy-rockchip-typec.c
> +++ b/drivers/phy/rockchip/phy-rockchip-typec.c
> @@ -54,6 +54,8 @@
>
> #include <linux/mfd/syscon.h>
> #include <linux/phy/phy.h>
> +#include <linux/usb/typec_dp.h>
> +#include <linux/usb/typec_mux.h>
>
> #define CMN_SSM_BANDGAP (0x21 << 2)
> #define CMN_SSM_BIAS (0x22 << 2)
> @@ -286,12 +288,23 @@
> #define RX_DIAG_SC2C_DELAY (0x81e1 << 2)
>
> #define PMA_LANE_CFG (0xc000 << 2)
> +#define PMA_LANE3_DP_LANE_SEL(x) (((x) & 0x3) << 14)
> +#define PMA_LANE3_INTERFACE_SEL(x) (((x) & 0x1) << 12)
> +#define PMA_LANE2_DP_LANE_SEL(x) (((x) & 0x3) << 10)
> +#define PMA_LANE2_INTERFACE_SEL(x) (((x) & 0x1) << 8)
> +#define PMA_LANE1_DP_LANE_SEL(x) (((x) & 0x3) << 6)
> +#define PMA_LANE1_INTERFACE_SEL(x) (((x) & 0x1) << 4)
> +#define PMA_LANE0_DP_LANE_SEL(x) (((x) & 0x3) << 2)
> +#define PMA_LANE0_INTERFACE_SEL(x) (((x) & 0x1) << 0)
> #define PIPE_CMN_CTRL1 (0xc001 << 2)
> #define PIPE_CMN_CTRL2 (0xc002 << 2)
> #define PIPE_COM_LOCK_CFG1 (0xc003 << 2)
> #define PIPE_COM_LOCK_CFG2 (0xc004 << 2)
> #define PIPE_RCV_DET_INH (0xc005 << 2)
> #define DP_MODE_CTL (0xc008 << 2)
> +#define PHY_DP_POWER_STATE_ACK_MASK GENMASK(7, 4)
> +#define PHY_DP_POWER_STATE_ACK_SHIFT 4
> +#define PHY_DP_POWER_STATE_MASK GENMASK(3, 0)
> #define DP_CLK_CTL (0xc009 << 2)
> #define STS (0xc00F << 2)
> #define PHY_ISO_CMN_CTRL (0xc010 << 2)
> @@ -327,8 +340,15 @@
>
> #define DP_MODE_A0 BIT(4)
> #define DP_MODE_A2 BIT(6)
> -#define DP_MODE_ENTER_A0 0xc101
> -#define DP_MODE_ENTER_A2 0xc104
> +
> +#define DP_MODE_MASK 0xf
> +#define DP_MODE_ENTER_A0 BIT(0)
> +#define DP_MODE_ENTER_A2 BIT(2)
> +#define DP_MODE_ENTER_A3 BIT(3)
> +#define DP_MODE_A0_ACK BIT(4)
> +#define DP_MODE_A2_ACK BIT(6)
> +#define DP_MODE_A3_ACK BIT(7)
> +#define DP_LINK_RESET_DEASSERTED BIT(8)
>
> #define PHY_MODE_SET_TIMEOUT 100000
>
> @@ -340,6 +360,31 @@
> #define MODE_DFP_USB BIT(1)
> #define MODE_DFP_DP BIT(2)
>
> +enum phy_dp_lane_num {
> + PHY_DP_LANE_0 = 0,
> + PHY_DP_LANE_1,
> + PHY_DP_LANE_2,
> + PHY_DP_LANE_3,
> +};
> +
> +enum phy_pma_if {
> + PMA_IF_PIPE_PCS = 0,
> + PMA_IF_PHY_DP,
> +};
> +
> +enum phy_typec_role {
> + TYPEC_PHY_USB = 0,
> + TYPEC_PHY_DP,
> + TYPEC_PHY_MAX,
> +};
> +
> +enum phy_dp_power_state {
> + PHY_DP_POWER_STATE_A0 = 0,
> + PHY_DP_POWER_STATE_A1,
> + PHY_DP_POWER_STATE_A2,
> + PHY_DP_POWER_STATE_A3,
> +};
> +
> struct usb3phy_reg {
> u32 offset;
> u32 enable_bit;
> @@ -372,18 +417,22 @@ struct rockchip_typec_phy {
> struct device *dev;
> void __iomem *base;
> struct extcon_dev *extcon;
> + struct typec_mux_dev *mux;
> + struct typec_switch_dev *sw;
> struct regmap *grf_regs;
> struct clk *clk_core;
> struct clk *clk_ref;
> struct reset_control *uphy_rst;
> struct reset_control *pipe_rst;
> struct reset_control *tcphy_rst;
> + struct phy *phys[TYPEC_PHY_MAX];
> const struct rockchip_usb3phy_port_cfg *port_cfgs;
> /* mutex to protect access to individual PHYs */
> struct mutex lock;
>
> bool flip;
> u8 mode;
> + u8 new_mode;
> };
>
> struct phy_reg {
> @@ -454,6 +503,99 @@ static const struct rockchip_usb3phy_port_cfg rk3399_usb3phy_port_cfgs[] = {
> { /* sentinel */ }
> };
>
> +static int tcphy_cfg_usb3_to_usb2_only(struct rockchip_typec_phy *tcphy,
> + bool value);
> +
If possible, please avoid forward declarations of this static
function and just move the implementation here if it needs to
be declared earlier.
> +static int tcphy_dp_set_power_state(struct rockchip_typec_phy *tcphy,
> + enum phy_dp_power_state state)
> +{
> + u32 ack, reg, sts = BIT(state);
> + int ret;
> +
> + /*
> + * Power state changes must not be requested until after the cmn_ready
> + * signal has gone active.
> + */
> + reg = readl(tcphy->base + PMA_CMN_CTRL1);
> + if (!(reg & CMN_READY)) {
> + dev_err(tcphy->dev, "cmn_ready in the inactive state\n");
> + return -EINVAL;
> + }
You can use readl in the if condition directly here, since reg
isn't used otherwise, but I'm also fine with it as-is if you think
it helps readability.
> +
> + reg = readl(tcphy->base + DP_MODE_CTL);
> + reg &= ~PHY_DP_POWER_STATE_MASK;
> + reg |= sts;
> + writel(reg, tcphy->base + DP_MODE_CTL);
> +
> + ret = readl_poll_timeout(tcphy->base + DP_MODE_CTL,
> + ack, (((ack & PHY_DP_POWER_STATE_ACK_MASK) >>
> + PHY_DP_POWER_STATE_ACK_SHIFT) == sts), 10,
> + PHY_MODE_SET_TIMEOUT);
Here please use FIELD_GET() from <linux/bitfield.h> like this:
ret = readl_poll_timeout(tcphy->base + DP_MODE_CTL, ack,
FIELD_GET(PHY_DP_POWER_STATE_ACK_MASK, ack) == sts,
10, PHY_MODE_SET_TIMEOUT);
PHY_DP_POWER_STATE_ACK_SHIFT is then no longer needed.
> + if (ret < 0) {
Nitpick: `if (ret) {` suffices here, readl_poll_timeout returns 0 on
success and negative errno on failure.
> + dev_err(tcphy->dev, "failed to enter power state %d\n", state);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +/*
> + * For the TypeC PHY, the 4 lanes are mapping to the USB TypeC receptacle pins
> + * as follows:
> + * -------------------------------------------------------------------
> + * PHY Lanes/Module Pins TypeC Receptacle Pins
> + * -------------------------------------------------------------------
> + * Lane0 (tx_p/m_ln_0) TX1+/TX1- (pins A2/A3)
> + * Lane1 (tx_rx_p/m_ln_1) RX1+/RX1- (pins B11/B10)
> + * Lane2 (tx_rx_p/m_ln_2) RX2+/RX2- (pins A11/A10)
> + * Lane3 (tx_p/m_ln_3) TX2+/TX2- (pins B2/B3)
> + * -------------------------------------------------------------------
> + *
> + * USB and DP lanes mapping to TypeC PHY lanes for each of pin assignment
> + * options (normal connector orientation) described in the VESA DisplayPort
> + * Alt Mode on USB TypeC Standard as follows:
> + *
> + * ----------------------------------------------------------------------
> + * PHY Lanes A B C D E F
> + * ----------------------------------------------------------------------
> + * 0 ML1 SSTX ML2 SSTX ML2 SSTX
> + * 1 ML3 SSRX ML3 SSRX ML3 SSRX
> + * 2 ML2 ML1 ML0 ML0 ML0 ML0
> + * 3 ML0 ML0 ML1 ML1 ML1 ML1
> + * ----------------------------------------------------------------------
> + */
> +static void tcphy_set_lane_mapping(struct rockchip_typec_phy *tcphy, u8 mode)
> +{
> + /*
> + * The PMA_LANE_CFG register is used to select whether a PMA lane
> + * is mapped for USB or PHY DP. The PMA_LANE_CFG register is
> + * configured based on a normal connector orientation. Logic in the
> + * PHY automatically handles the flipped connector case based on the
> + * setting of orientation of TypeC PHY.
> + */
> + if (mode == MODE_DFP_DP) {
> + /* This maps to VESA DP Alt Mode pin assignments C and E. */
> + writel(PMA_LANE3_DP_LANE_SEL(PHY_DP_LANE_1) |
> + PMA_LANE3_INTERFACE_SEL(PMA_IF_PHY_DP) |
> + PMA_LANE2_DP_LANE_SEL(PHY_DP_LANE_0) |
> + PMA_LANE2_INTERFACE_SEL(PMA_IF_PHY_DP) |
> + PMA_LANE1_DP_LANE_SEL(PHY_DP_LANE_3) |
> + PMA_LANE1_INTERFACE_SEL(PMA_IF_PHY_DP) |
> + PMA_LANE0_DP_LANE_SEL(PHY_DP_LANE_2) |
> + PMA_LANE0_INTERFACE_SEL(PMA_IF_PHY_DP),
> + tcphy->base + PMA_LANE_CFG);
> + } else {
> + /* This maps to VESA DP Alt Mode pin assignments D and F. */
> + writel(PMA_LANE3_DP_LANE_SEL(PHY_DP_LANE_1) |
> + PMA_LANE3_INTERFACE_SEL(PMA_IF_PHY_DP) |
> + PMA_LANE2_DP_LANE_SEL(PHY_DP_LANE_0) |
> + PMA_LANE2_INTERFACE_SEL(PMA_IF_PHY_DP) |
> + PMA_LANE1_INTERFACE_SEL(PMA_IF_PIPE_PCS) |
> + PMA_LANE0_INTERFACE_SEL(PMA_IF_PIPE_PCS),
> + tcphy->base + PMA_LANE_CFG);
> + }
> +}
> +
> static void tcphy_cfg_24m(struct rockchip_typec_phy *tcphy)
> {
> u32 i, rdata;
> @@ -743,8 +885,10 @@ static int tcphy_phy_init(struct rockchip_typec_phy *tcphy, u8 mode)
> tcphy_dp_aux_set_flip(tcphy);
>
> tcphy_cfg_24m(tcphy);
> + tcphy_set_lane_mapping(tcphy, mode);
>
> if (mode == MODE_DFP_DP) {
> + tcphy_cfg_usb3_to_usb2_only(tcphy, true);
> tcphy_cfg_dp_pll(tcphy);
> for (i = 0; i < 4; i++)
> tcphy_dp_cfg_lane(tcphy, i);
Is there a difference between the values tcphy_set_lane_mapping() writes
to PMA_LANE_CFG, and what this if block writes to PMA_LANE_CFG at the
end (either PIN_ASSIGN_C_E or PIN_ASSIGN_D_F)?
If not, then I think think the second write to it may be redundant.
> @@ -768,7 +912,10 @@ static int tcphy_phy_init(struct rockchip_typec_phy *tcphy, u8 mode)
> writel(PIN_ASSIGN_D_F, tcphy->base + PMA_LANE_CFG);
> }
>
> - writel(DP_MODE_ENTER_A2, tcphy->base + DP_MODE_CTL);
> + val = readl(tcphy->base + DP_MODE_CTL);
> + val &= ~DP_MODE_MASK;
> + val |= DP_MODE_ENTER_A2 | DP_LINK_RESET_DEASSERTED;
> + writel(val, tcphy->base + DP_MODE_CTL);
>
> reset_control_deassert(tcphy->uphy_rst);
>
> @@ -811,8 +958,9 @@ static int tcphy_get_mode(struct rockchip_typec_phy *tcphy)
> u8 mode;
> int ret, ufp, dp;
>
> + /* If extcon not exist, try to use tcpm mode */
> if (!edev)
> - return MODE_DFP_USB;
> + return tcphy->new_mode;
>
> ufp = extcon_get_state(edev, EXTCON_USB);
> dp = extcon_get_state(edev, EXTCON_DISP_DP);
> @@ -850,6 +998,71 @@ static int tcphy_get_mode(struct rockchip_typec_phy *tcphy)
> return mode;
> }
>
> +#if IS_ENABLED(CONFIG_TYPEC)
> +static int tcphy_orien_sw_set(struct typec_switch_dev *sw,
> + enum typec_orientation orien)
> +{
> + struct rockchip_typec_phy *tcphy = typec_switch_get_drvdata(sw);
> +
> + mutex_lock(&tcphy->lock);
Instead of this you can use
guard(mutex)(&tcphy->lock);
from <linux/cleanup.h>
and get rid of the manual unlock and goto. The lock held by the guard
statement will be dropped as soon as the scope of automatic variable
declaration is left, so no manual goto unwind needs to be done.
> +
> + if (orien == TYPEC_ORIENTATION_NONE) {
> + tcphy->new_mode = MODE_DISCONNECT;
> + goto unlock_ret;
> + }
> +
> + tcphy->flip = (orien == TYPEC_ORIENTATION_REVERSE) ? true : false;
> +
> +unlock_ret:
> + mutex_unlock(&tcphy->lock);
> + return 0;
> +}
> +
> +static void udphy_orien_switch_unregister(void *data)
> +{
> + struct rockchip_typec_phy *tcphy = data;
> +
> + typec_switch_unregister(tcphy->sw);
> +}
> +
> +static int tcphy_setup_orien_switch(struct rockchip_typec_phy *tcphy)
> +{
> + struct typec_switch_desc sw_desc = { };
> + struct device_node *np;
> + int ret = 0;
> +
> + np = of_get_child_by_name(tcphy->dev->of_node, "usb3-port");
> + if (!np)
> + return 0;
> +
> + if (!of_property_read_bool(np, "orientation-switch"))
> + goto put_np;
np isn't needed after this check. Instead of manual freeing of np
with a goto, you can use the `__free(device_node)` attribute.
static int foo(struct rockchip_typec_phy *tcphy)
{
/* if return can happen before np assigned, NULL-init it here */
struct device_node __free(device_node) *np;
np = of_get_child_by_name(tcphy->dev->of_node, "usb3-port");
if (!np)
return 0;
if (!of_property_read_bool(np, "orientation-switch"))
return 0; /* no more manual put needed, done when scope left */
/* ... etc etc. ... */
return devm_add_action_or_reset(tcphy->dev, ...)
}
> +
> + sw_desc.drvdata = tcphy;
> + sw_desc.fwnode = device_get_named_child_node(tcphy->dev, "usb3-port");
> + sw_desc.set = tcphy_orien_sw_set;
> +
> + tcphy->sw = typec_switch_register(tcphy->dev, &sw_desc);
> + if (IS_ERR(tcphy->sw)) {
> + dev_err(tcphy->dev, "Error register typec orientation switch: %ld\n",
> + PTR_ERR(tcphy->sw));
Instead of %ld, use %pe and drop the `PTR_ERR()`, so just:
dev_err(tcphy->dev, "Error register typec orientation switch: %pe\n",
tcphy->sw);
> + ret = PTR_ERR(tcphy->sw);
> + goto put_np;
> + }
> +
> + ret = devm_add_action_or_reset(tcphy->dev, udphy_orien_switch_unregister, tcphy);
> +
> +put_np:
> + of_node_put(np);
> + return ret;
> +}
> +#else
> +static int tcphy_setup_orien_switch(struct rockchip_typec_phy *tcphy)
> +{
> + return 0;
> +}
> +#endif
> +
> static int tcphy_cfg_usb3_to_usb2_only(struct rockchip_typec_phy *tcphy,
> bool value)
> {
> @@ -989,14 +1202,9 @@ static int rockchip_dp_phy_power_on(struct phy *phy)
>
> tcphy_dp_aux_calibration(tcphy);
>
> - writel(DP_MODE_ENTER_A0, tcphy->base + DP_MODE_CTL);
> -
> - ret = readx_poll_timeout(readl, tcphy->base + DP_MODE_CTL,
> - val, val & DP_MODE_A0, 1000,
> - PHY_MODE_SET_TIMEOUT);
> - if (ret < 0) {
> - writel(DP_MODE_ENTER_A2, tcphy->base + DP_MODE_CTL);
> - dev_err(tcphy->dev, "failed to wait TCPHY enter A0\n");
> + ret = tcphy_dp_set_power_state(tcphy, PHY_DP_POWER_STATE_A0);
> + if (ret) {
> + dev_err(tcphy->dev, "failed to enter A0 power state\n");
> goto power_on_finish;
> }
>
> @@ -1013,6 +1221,7 @@ static int rockchip_dp_phy_power_on(struct phy *phy)
> static int rockchip_dp_phy_power_off(struct phy *phy)
> {
> struct rockchip_typec_phy *tcphy = phy_get_drvdata(phy);
> + int ret;
>
> mutex_lock(&tcphy->lock);
>
> @@ -1021,7 +1230,11 @@ static int rockchip_dp_phy_power_off(struct phy *phy)
>
> tcphy->mode &= ~MODE_DFP_DP;
>
> - writel(DP_MODE_ENTER_A2, tcphy->base + DP_MODE_CTL);
> + ret = tcphy_dp_set_power_state(tcphy, PHY_DP_POWER_STATE_A2);
> + if (ret) {
> + dev_err(tcphy->dev, "failed to enter A2 power state\n");
> + goto unlock;
> + }
>
> if (tcphy->mode == MODE_DISCONNECT)
> tcphy_phy_deinit(tcphy);
> @@ -1037,6 +1250,93 @@ static const struct phy_ops rockchip_dp_phy_ops = {
> .owner = THIS_MODULE,
> };
>
> +#if IS_ENABLED(CONFIG_TYPEC)
> +static int tcphy_typec_mux_set(struct typec_mux_dev *mux, struct typec_mux_state *state)
> +{
> + struct rockchip_typec_phy *tcphy = typec_mux_get_drvdata(mux);
> + struct typec_displayport_data *data;
> + int hpd = 0;
hpd doesn't need to be initialised here.
> +
> + mutex_lock(&tcphy->lock);
Prefer guard(mutex)(&tcphy->lock); here to combat potential for
future lock leaking bugs.
> +
> + switch (state->mode) {
> + case TYPEC_STATE_SAFE:
> + fallthrough;
> + case TYPEC_STATE_USB:
> + tcphy->new_mode = MODE_DFP_USB;
> + phy_set_bus_width(tcphy->phys[TYPEC_PHY_DP], 0);
> + break;
> + case TYPEC_DP_STATE_C:
> + case TYPEC_DP_STATE_E:
> + if (state->alt->svid != USB_TYPEC_DP_SID)
> + break;
> + tcphy->new_mode = MODE_DFP_DP;
> + data = state->data;
> + hpd = !!(data->status & DP_STATUS_HPD_STATE);
> + phy_set_bus_width(tcphy->phys[TYPEC_PHY_DP], hpd ? 4 : 0);
> + break;
> + case TYPEC_DP_STATE_D:
> + if (state->alt->svid != USB_TYPEC_DP_SID)
> + break;
> + tcphy->new_mode = MODE_DFP_DP | MODE_DFP_USB;
> + data = state->data;
> + hpd = !!(data->status & DP_STATUS_HPD_STATE);
> + phy_set_bus_width(tcphy->phys[TYPEC_PHY_DP], hpd ? 2 : 0);
> + break;
> + default:
> + break;
Might be good to return -EINVAL here. No additional ret local needed
with above guard statement. :)
> + }
> +
> + mutex_unlock(&tcphy->lock);
> +
> + return 0;
> +}
> +
> +static void tcphy_typec_mux_unregister(void *data)
> +{
> + struct rockchip_typec_phy *tcphy = data;
> +
> + typec_mux_unregister(tcphy->mux);
> +}
> +
> +static int tcphy_setup_typec_mux(struct rockchip_typec_phy *tcphy)
> +{
> + struct typec_mux_desc mux_desc = {};
> + struct device_node *np;
> + int ret = 0;
> +
> + np = of_get_child_by_name(tcphy->dev->of_node, "dp-port");
> + if (!np)
> + return 0;
> +
> + if (!of_property_read_bool(np, "mode-switch"))
> + goto put_np;
__free attribute on np can get rid of the manual goto put_np stuff
here as well.
> +
> + mux_desc.drvdata = tcphy;
> + mux_desc.fwnode = device_get_named_child_node(tcphy->dev, "dp-port");
> + mux_desc.set = tcphy_typec_mux_set;
> +
> + tcphy->mux = typec_mux_register(tcphy->dev, &mux_desc);
> + if (IS_ERR(tcphy->mux)) {
> + dev_err(tcphy->dev, "Error register typec mux: %ld\n",
> + PTR_ERR(tcphy->mux));
%pe format specifier again.
> + ret = PTR_ERR(tcphy->mux);
> + goto put_np;
> + }
> +
> + ret = devm_add_action_or_reset(tcphy->dev, tcphy_typec_mux_unregister, tcphy);
> +
> +put_np:
> + of_node_put(np);
> + return ret;
> +}
> +#else
> +static int tcphy_setup_typec_mux(struct rockchip_typec_phy *tcphy)
> +{
> + return 0;
> +}
> +#endif
> +
> static int tcphy_parse_dt(struct rockchip_typec_phy *tcphy,
> struct device *dev)
> {
> @@ -1095,6 +1395,25 @@ static void typec_phy_pre_init(struct rockchip_typec_phy *tcphy)
> tcphy->mode = MODE_DISCONNECT;
> }
>
> +static int typec_dp_lane_get(struct rockchip_typec_phy *tcphy)
> +{
> + int dp_lanes;
> +
> + switch (tcphy->new_mode) {
> + case MODE_DFP_DP:
> + dp_lanes = 4;
> + break;
> + case MODE_DFP_DP | MODE_DFP_USB:
> + dp_lanes = 2;
> + break;
> + default:
> + dp_lanes = 0;
> + break;
> + }
> +
> + return dp_lanes;
dp_lanes local doesn't need to exist here, you
can just return from the switch statement directly:
static int typec_dp_lane_get(struct rockchip_typec_phy *tcphy)
{
switch (tcphy->new_mode) {
case MODE_DFP_DP:
return 4;
case MODE_DFP_DP | MODE_DFP_USB:
return 2;
default:
return 0;
}
}
> +}
> +
> static int rockchip_typec_phy_probe(struct platform_device *pdev)
> {
> struct device *dev = &pdev->dev;
> @@ -1142,6 +1461,7 @@ static int rockchip_typec_phy_probe(struct platform_device *pdev)
> return ret;
>
> tcphy->dev = dev;
> + tcphy->new_mode = MODE_DFP_USB;
> platform_set_drvdata(pdev, tcphy);
> mutex_init(&tcphy->lock);
>
> @@ -1151,6 +1471,7 @@ static int rockchip_typec_phy_probe(struct platform_device *pdev)
> if (IS_ERR(tcphy->extcon)) {
> if (PTR_ERR(tcphy->extcon) == -ENODEV) {
> tcphy->extcon = NULL;
> + dev_info(dev, "extcon not exist, try to use typec mux\n");
> } else {
> if (PTR_ERR(tcphy->extcon) != -EPROBE_DEFER)
> dev_err(dev, "Invalid or missing extcon\n");
> @@ -1158,19 +1479,34 @@ static int rockchip_typec_phy_probe(struct platform_device *pdev)
> }
> }
>
> + ret = tcphy_setup_orien_switch(tcphy);
> + if (ret)
> + return ret;
> +
> + ret = tcphy_setup_typec_mux(tcphy);
> + if (ret)
> + return ret;
If they are just used in the probe function, you can make the error
prints in tcphy_setup_orien_switch() and tcphy_setup_typec_mux()
use dev_err_probe instead. That way, if the probe function fails,
the error message is shown in the devices_deferred debugfs file.
> +
> pm_runtime_enable(dev);
>
> for_each_available_child_of_node(np, child_np) {
> struct phy *phy;
>
> - if (of_node_name_eq(child_np, "dp-port"))
> + if (of_node_name_eq(child_np, "dp-port")) {
> phy = devm_phy_create(dev, child_np,
> &rockchip_dp_phy_ops);
> - else if (of_node_name_eq(child_np, "usb3-port"))
> + if (!IS_ERR(phy)) {
> + tcphy->phys[TYPEC_PHY_DP] = phy;
> + phy_set_bus_width(phy, typec_dp_lane_get(tcphy));
> + }
> + } else if (of_node_name_eq(child_np, "usb3-port")) {
> phy = devm_phy_create(dev, child_np,
> &rockchip_usb3_phy_ops);
> - else
> + if (!IS_ERR(phy))
> + tcphy->phys[TYPEC_PHY_USB] = phy;
> + } else {
> continue;
> + }
>
> if (IS_ERR(phy)) {
> dev_err(dev, "failed to create phy: %pOFn\n",
>
Kind regards,
Nicolas Frattaroli
--
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