* [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
* Re: [PATCH phy-next 12/13] phy: lynx-10g: new driver
From: sashiko-bot @ 2026-05-28 18:20 UTC (permalink / raw)
To: Vladimir Oltean
Cc: neil.armstrong, conor+dt, olteanv, vkoul, linux-phy, robh,
devicetree
In-Reply-To: <20260528172404.733196-13-vladimir.oltean@nxp.com>
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.
[ ... ]
> +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.
[ ... ]
> +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().
[ ... ]
> +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.
[ ... ]
> +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.
> +
> + 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 09/13] phy: lynx-28g: common probe() and remove()
From: sashiko-bot @ 2026-05-28 18:02 UTC (permalink / raw)
To: Vladimir Oltean; +Cc: vkoul, neil.armstrong, olteanv, linux-phy
In-Reply-To: <20260528172404.733196-10-vladimir.oltean@nxp.com>
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
--
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: sashiko-bot @ 2026-05-28 17:47 UTC (permalink / raw)
To: Vladimir Oltean; +Cc: vkoul, olteanv, neil.armstrong, linux-phy
In-Reply-To: <20260528172404.733196-11-vladimir.oltean@nxp.com>
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
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* Re: [PATCH phy-next 08/13] phy: lynx-28g: make lynx_28g_pll_read_configuration() callable per PLL
From: sashiko-bot @ 2026-05-28 17:46 UTC (permalink / raw)
To: Vladimir Oltean; +Cc: olteanv, neil.armstrong, vkoul, linux-phy
In-Reply-To: <20260528172404.733196-9-vladimir.oltean@nxp.com>
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] Typo in the commit message claiming a change to `lynx_28g_lane_read_configuration()` instead of `lynx_28g_pll_read_configuration()`.
--
commit 8204acc04881cb85ce412b9cf76131f9c69904c5
Author: Vladimir Oltean <vladimir.oltean@nxp.com>
phy: lynx-28g: make lynx_28g_pll_read_configuration() callable per PLL
This commit modifies the lynx_28g_pll_read_configuration function so that
it processes a single PLL structure rather than iterating over all PLLs
internally. The loop iterating over the PLLs is moved to the call site in
lynx_28g_probe.
> 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_lane_read_configuration() to also permit
> reading the PLL configuration individually, and move the for loop at the
> call site.
[Severity: Low]
This isn't a bug, but there appears to be a typo in the commit message.
It mentions changing lynx_28g_lane_read_configuration to permit reading
the PLL configuration individually, but the patch actually modifies
lynx_28g_pll_read_configuration.
Could the commit message be updated to reflect the correct function name?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260528172404.733196-1-vladimir.oltean@nxp.com?part=8
--
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-28 17:45 UTC (permalink / raw)
To: sashiko-reviews
Cc: robh, devicetree, conor+dt, neil.armstrong, linux-phy, olteanv,
vkoul
In-Reply-To: <20260528174044.EBE8A1F000E9@smtp.kernel.org>
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.
--
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: sashiko-bot @ 2026-05-28 17:40 UTC (permalink / raw)
To: Vladimir Oltean
Cc: robh, devicetree, conor+dt, neil.armstrong, linux-phy, olteanv,
vkoul
In-Reply-To: <20260528172404.733196-2-vladimir.oltean@nxp.com>
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
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* [PATCH phy-next 13/13] MAINTAINERS: expand Lynx 28G entry to cover Lynx 10G SerDes
From: Vladimir Oltean @ 2026-05-28 17:24 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: <20260528172404.733196-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>
---
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 phy-next 12/13] phy: lynx-10g: new driver
From: Vladimir Oltean @ 2026-05-28 17:24 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: <20260528172404.733196-1-vladimir.oltean@nxp.com>
Introduce a driver for the networking lanes of the 10G Lynx SerDes
block, present on the majority of Layerscape and QorIQ (Freescale/NXP)
SoCs.
As with the 28G Lynx, the SerDes lanes come pre-initialized out of
reset and the consumers use them that way outside the Generic PHY
framework (for networking, the static configuration remains for the
entire SoC lifetime, whereas for SATA and PCIe, the hardware
reconfigures itself automatically for other link speeds).
The need for the Generic PHY framework comes specifically for networking
use cases where a static lane configuration is not sufficient. For
example a network MAC is connected to an SFP cage, where various SFP or
SFP+ modules can be connected. Each of them may require a different
SerDes protocol (SGMII, 1000Base-X, 10GBase-R), which phylink + sfp-bus
are responsible of figuring out. The phylink drivers are:
- enetc
- felix
- dpaa_eth (fman_memac)
- dpaa2-eth
- dpaa2-switch
and they all need to reconfigure the SerDes for the requested link mode,
using phy_set_mode_ext() (and phy_validate() to see if it is supported
in the first place).
Note that SerDes 2 on LS1088A is exclusively non-networking, so there is
currently no need for this driver. Therefore we skip matching on its
compatible string and do not probe on that device.
Co-developed-by: Ioana Ciornei <ioana.ciornei@nxp.com>
Signed-off-by: Ioana Ciornei <ioana.ciornei@nxp.com>
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>
---
drivers/phy/freescale/Kconfig | 10 +
drivers/phy/freescale/Makefile | 1 +
drivers/phy/freescale/phy-fsl-lynx-10g.c | 1319 +++++++++++++++++++++
drivers/phy/freescale/phy-fsl-lynx-core.c | 12 +
drivers/phy/freescale/phy-fsl-lynx-core.h | 4 +
include/soc/fsl/phy-fsl-lynx.h | 27 +
6 files changed, 1373 insertions(+)
create mode 100644 drivers/phy/freescale/phy-fsl-lynx-10g.c
diff --git a/drivers/phy/freescale/Kconfig b/drivers/phy/freescale/Kconfig
index a87429f634ea..4368eb1668fb 100644
--- a/drivers/phy/freescale/Kconfig
+++ b/drivers/phy/freescale/Kconfig
@@ -57,6 +57,16 @@ config PHY_FSL_LYNX_CORE
Enable this to add common support code for NXP Lynx 10G and Lynx 28G
SerDes blocks.
+config PHY_FSL_LYNX_10G
+ tristate "Freescale Layerscape Lynx 10G 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 10G SerDes PHY as found on
+ NXP's Layerscape platform such as LS1088A or LS1028A.
+
config PHY_FSL_LYNX_28G
tristate "Freescale Layerscape Lynx 28G SerDes PHY support"
depends on OF
diff --git a/drivers/phy/freescale/Makefile b/drivers/phy/freescale/Makefile
index d7aa62cdeb39..5b0e180d6972 100644
--- a/drivers/phy/freescale/Makefile
+++ b/drivers/phy/freescale/Makefile
@@ -5,5 +5,6 @@ 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_10G) += phy-fsl-lynx-10g.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-10g.c b/drivers/phy/freescale/phy-fsl-lynx-10g.c
new file mode 100644
index 000000000000..28b082357468
--- /dev/null
+++ b/drivers/phy/freescale/phy-fsl-lynx-10g.c
@@ -0,0 +1,1319 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* Copyright 2021-2026 NXP */
+
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/phy.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/workqueue.h>
+
+#include "phy-fsl-lynx-core.h"
+
+/* SoC IP wrapper for protocol converters */
+#define PCCR8 0x220
+#define PCCR8_SGMIIa_KX BIT(3)
+#define PCCR8_SGMIIa_CFG BIT(0)
+
+#define PCCR9 0x224
+#define PCCR9_QSGMIIa_CFG BIT(0)
+#define PCCR9_QXGMIIa_CFG BIT(0)
+
+#define PCCRB 0x22c
+#define PCCRB_XFIa_CFG BIT(0)
+#define PCCRB_SXGMIIa_CFG BIT(0)
+
+#define SGMII_CFG(id) (28 - (id) * 4)
+#define QSGMII_CFG(id) (28 - (id) * 4)
+#define SXGMII_CFG(id) (28 - (id) * 4)
+#define QXGMII_CFG(id) (12 - (id) * 4)
+#define XFI_CFG(id) (28 - (id) * 4)
+
+#define CR(x) ((x) * 4)
+
+#define A 0
+#define B 1
+#define C 2
+#define D 3
+#define E 4
+#define F 5
+#define G 6
+#define H 7
+
+#define SGMIIaCR0(id) (0x1800 + (id) * 0x10)
+#define QSGMIIaCR0(id) (0x1880 + (id) * 0x10)
+#define XAUIaCR0(id) (0x1900 + (id) * 0x10)
+#define XFIaCR0(id) (0x1980 + (id) * 0x10)
+#define SXGMIIaCR0(id) (0x1a80 + (id) * 0x10)
+#define QXGMIIaCR0(id) (0x1b00 + (id) * 0x20)
+
+#define SGMIIaCR0_RST_SGM BIT(31)
+#define SGMIIaCR0_RST_SGM_OFF SGMIIaCR0_RST_SGM
+#define SGMIIaCR0_RST_SGM_ON 0
+#define SGMIIaCR0_PD_SGM BIT(30)
+#define SGMIIaCR1_SGPCS_EN BIT(11)
+#define SGMIIaCR1_SGPCS_DIS 0x0
+
+#define QSGMIIaCR0_RST_QSGM BIT(31)
+#define QSGMIIaCR0_RST_QSGM_OFF QSGMIIaCR0_RST_QSGM
+#define QSGMIIaCR0_RST_QSGM_ON 0
+#define QSGMIIaCR0_PD_QSGM BIT(30)
+
+/* Per PLL registers */
+#define PLLnCR0(pll) ((pll) * 0x20 + 0x4)
+
+#define PLLnCR0_POFF BIT(31)
+
+#define PLLnCR0_REFCLK_SEL GENMASK(30, 28)
+#define PLLnCR0_REFCLK_SEL_100MHZ 0x0
+#define PLLnCR0_REFCLK_SEL_125MHZ 0x1
+#define PLLnCR0_REFCLK_SEL_156MHZ 0x2
+#define PLLnCR0_REFCLK_SEL_150MHZ 0x3
+#define PLLnCR0_REFCLK_SEL_161MHZ 0x4
+#define PLLnCR0_PLL_LCK BIT(23)
+#define PLLnCR0_FRATE_SEL GENMASK(19, 16)
+#define PLLnCR0_FRATE_5G 0x0
+#define PLLnCR0_FRATE_5_15625G 0x6
+#define PLLnCR0_FRATE_4G 0x7
+#define PLLnCR0_FRATE_3_125G 0x9
+#define PLLnCR0_FRATE_3G 0xa
+
+/* Per SerDes lane registers */
+
+/* Lane a Protocol Select status register */
+#define LNaPSSR0(lane) (0x100 + (lane) * 0x20)
+#define LNaPSSR0_TYPE GENMASK(30, 26)
+#define LNaPSSR0_IS_QUAD GENMASK(25, 24)
+#define LNaPSSR0_MAC GENMASK(19, 16)
+#define LNaPSSR0_PCS GENMASK(10, 8)
+#define LNaPSSR0_LANE GENMASK(2, 0)
+
+/* Lane a General Control Register */
+#define LNaGCR0(lane) (0x800 + (lane) * 0x40 + 0x0)
+#define LNaGCR0_RPLL_PLLF BIT(31)
+#define LNaGCR0_RPLL_PLLS 0x0
+#define LNaGCR0_RPLL_MSK BIT(31)
+#define LNaGCR0_RRAT_SEL GENMASK(29, 28)
+#define LNaGCR0_TRAT_SEL GENMASK(25, 24)
+#define LNaGCR0_TPLL_PLLF BIT(27)
+#define LNaGCR0_TPLL_PLLS 0x0
+#define LNaGCR0_TPLL_MSK BIT(27)
+#define LNaGCR0_RRST_OFF LNaGCR0_RRST
+#define LNaGCR0_TRST_OFF LNaGCR0_TRST
+#define LNaGCR0_RRST_ON 0x0
+#define LNaGCR0_TRST_ON 0x0
+#define LNaGCR0_RRST BIT(22)
+#define LNaGCR0_TRST BIT(21)
+#define LNaGCR0_RX_PD BIT(20)
+#define LNaGCR0_TX_PD BIT(19)
+#define LNaGCR0_IF20BIT_EN BIT(18)
+#define LNaGCR0_PROTS GENMASK(11, 7)
+
+#define LNaGCR1(lane) (0x800 + (lane) * 0x40 + 0x4)
+#define LNaGCR1_RDAT_INV BIT(31)
+#define LNaGCR1_TDAT_INV BIT(30)
+#define LNaGCR1_OPAD_CTL BIT(26)
+#define LNaGCR1_REIDL_TH GENMASK(22, 20)
+#define LNaGCR1_REIDL_EX_SEL GENMASK(19, 18)
+#define LNaGCR1_REIDL_ET_SEL GENMASK(17, 16)
+#define LNaGCR1_REIDL_EX_MSB BIT(15)
+#define LNaGCR1_REIDL_ET_MSB BIT(14)
+#define LNaGCR1_REQ_CTL_SNP BIT(13)
+#define LNaGCR1_REQ_CDR_SNP BIT(12)
+#define LNaGCR1_TRSTDIR BIT(7)
+#define LNaGCR1_REQ_BIN_SNP BIT(6)
+#define LNaGCR1_ISLEW_RCTL GENMASK(5, 4)
+#define LNaGCR1_OSLEW_RCTL GENMASK(1, 0)
+
+#define LNaRECR0(lane) (0x800 + (lane) * 0x40 + 0x10)
+#define LNaRECR0_RXEQ_BST BIT(28)
+#define LNaRECR0_GK2OVD GENMASK(27, 24)
+#define LNaRECR0_GK3OVD GENMASK(19, 16)
+#define LNaRECR0_GK2OVD_EN BIT(15)
+#define LNaRECR0_GK3OVD_EN BIT(14)
+#define LNaRECR0_OSETOVD_EN BIT(13)
+#define LNaRECR0_BASE_WAND GENMASK(11, 10)
+#define LNaRECR0_OSETOVD GENMASK(6, 0)
+
+#define LNaTECR0(lane) (0x800 + (lane) * 0x40 + 0x18)
+#define LNaTECR0_TEQ_TYPE GENMASK(29, 28)
+#define LNaTECR0_SGN_PREQ BIT(26)
+#define LNaTECR0_RATIO_PREQ GENMASK(25, 22)
+#define LNaTECR0_SGN_POST1Q BIT(21)
+#define LNaTECR0_RATIO_PST1Q GENMASK(20, 16)
+#define LNaTECR0_ADPT_EQ GENMASK(13, 8)
+#define LNaTECR0_AMP_RED GENMASK(5, 0)
+
+#define LNaTTLCR0(lane) (0x800 + (lane) * 0x40 + 0x20)
+#define LNaTTLCR1(lane) (0x800 + (lane) * 0x40 + 0x24)
+#define LNaTTLCR2(lane) (0x800 + (lane) * 0x40 + 0x28)
+
+#define LNaTCSR3(lane) (0x800 + (lane) * 0x40 + 0x3C)
+#define LNaTCSR3_CDR_LCK BIT(27)
+
+enum lynx_10g_rat_sel {
+ RAT_SEL_FULL = 0x0,
+ RAT_SEL_HALF = 0x1,
+ RAT_SEL_QUARTER = 0x2,
+ RAT_SEL_DOUBLE = 0x3,
+};
+
+enum lynx_10g_eq_type {
+ EQ_TYPE_NO_EQ = 0,
+ EQ_TYPE_2TAP = 1,
+ EQ_TYPE_3TAP = 2,
+};
+
+enum lynx_10g_proto_sel {
+ PROTO_SEL_PCIE = 0,
+ PROTO_SEL_SGMII_BASEX_KX_QSGMII = 1,
+ PROTO_SEL_SATA = 2,
+ PROTO_SEL_XAUI = 4,
+ PROTO_SEL_XFI_10GBASER_KR_SXGMII = 0xa,
+};
+
+struct lynx_10g_proto_conf {
+ int proto_sel;
+ int if20bit_en;
+ int reidl_th;
+ int reidl_et_msb;
+ int reidl_et_sel;
+ int reidl_ex_msb;
+ int reidl_ex_sel;
+ int islew_rctl;
+ int oslew_rctl;
+ int rxeq_bst;
+ int gk2ovd;
+ int gk3ovd;
+ int gk2ovd_en;
+ int gk3ovd_en;
+ int base_wand;
+ int teq_type;
+ int sgn_preq;
+ int ratio_preq;
+ int sgn_post1q;
+ int ratio_post1q;
+ int adpt_eq;
+ int amp_red;
+ int ttlcr0;
+};
+
+static const struct lynx_10g_proto_conf lynx_10g_proto_conf[LANE_MODE_MAX] = {
+ [LANE_MODE_1000BASEX_SGMII] = {
+ .proto_sel = PROTO_SEL_SGMII_BASEX_KX_QSGMII,
+ .reidl_th = 1,
+ .reidl_ex_sel = 3,
+ .reidl_et_msb = 1,
+ .islew_rctl = 1,
+ .oslew_rctl = 1,
+ .gk2ovd = 15,
+ .gk3ovd = 15,
+ .gk2ovd_en = 1,
+ .gk3ovd_en = 1,
+ .teq_type = EQ_TYPE_NO_EQ,
+ .adpt_eq = 48,
+ .amp_red = 6,
+ .ttlcr0 = 0x39000400,
+ },
+ [LANE_MODE_2500BASEX] = {
+ .proto_sel = PROTO_SEL_SGMII_BASEX_KX_QSGMII,
+ .islew_rctl = 2,
+ .oslew_rctl = 2,
+ .teq_type = EQ_TYPE_2TAP,
+ .sgn_post1q = 1,
+ .ratio_post1q = 6,
+ .adpt_eq = 48,
+ .ttlcr0 = 0x00000400,
+ },
+ [LANE_MODE_QSGMII] = {
+ .proto_sel = PROTO_SEL_SGMII_BASEX_KX_QSGMII,
+ .islew_rctl = 1,
+ .oslew_rctl = 1,
+ .teq_type = EQ_TYPE_2TAP,
+ .sgn_post1q = 1,
+ .ratio_post1q = 6,
+ .adpt_eq = 48,
+ .amp_red = 2,
+ .ttlcr0 = 0x00000400,
+ },
+ [LANE_MODE_10G_QXGMII] = {
+ .proto_sel = PROTO_SEL_XFI_10GBASER_KR_SXGMII,
+ .if20bit_en = 1,
+ .islew_rctl = 1,
+ .oslew_rctl = 1,
+ .base_wand = 1,
+ .teq_type = EQ_TYPE_NO_EQ,
+ .adpt_eq = 48,
+ .ttlcr0 = 0x00000400,
+ },
+ [LANE_MODE_USXGMII] = {
+ .proto_sel = PROTO_SEL_XFI_10GBASER_KR_SXGMII,
+ .if20bit_en = 1,
+ .islew_rctl = 1,
+ .oslew_rctl = 1,
+ .base_wand = 1,
+ .teq_type = EQ_TYPE_NO_EQ,
+ .sgn_post1q = 1,
+ .adpt_eq = 48,
+ .ttlcr0 = 0x00000400,
+ },
+ [LANE_MODE_10GBASER] = {
+ .proto_sel = PROTO_SEL_XFI_10GBASER_KR_SXGMII,
+ .if20bit_en = 1,
+ .islew_rctl = 2,
+ .oslew_rctl = 2,
+ .rxeq_bst = 1,
+ .base_wand = 1,
+ .teq_type = EQ_TYPE_2TAP,
+ .sgn_post1q = 1,
+ .ratio_post1q = 3,
+ .adpt_eq = 48,
+ .amp_red = 7,
+ .ttlcr0 = 0x00000400,
+ },
+};
+
+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);
+ usleep_range(1, 2);
+ lynx_lane_rmw(lane, LNaGCR0, LNaGCR0_RRST_OFF, LNaGCR0_RRST);
+
+ usleep_range(1, 2);
+}
+
+static void lynx_10g_pll_read_configuration(struct lynx_pll *pll)
+{
+ u32 val;
+
+ val = lynx_pll_read(pll, PLLnCR0);
+ pll->frate_sel = FIELD_GET(PLLnCR0_FRATE_SEL, val);
+ pll->refclk_sel = FIELD_GET(PLLnCR0_REFCLK_SEL, val);
+ pll->enabled = !(val & PLLnCR0_POFF);
+ pll->locked = !!(val & PLLnCR0_PLL_LCK);
+
+ if (!pll->enabled)
+ return;
+
+ switch (pll->frate_sel) {
+ case PLLnCR0_FRATE_5G:
+ /* 5GHz clock net */
+ __set_bit(LANE_MODE_1000BASEX_SGMII, pll->supported);
+ __set_bit(LANE_MODE_QSGMII, pll->supported);
+ break;
+ case PLLnCR0_FRATE_3_125G:
+ __set_bit(LANE_MODE_2500BASEX, pll->supported);
+ break;
+ case PLLnCR0_FRATE_5_15625G:
+ /* 10.3125GHz clock net */
+ __set_bit(LANE_MODE_10GBASER, pll->supported);
+ __set_bit(LANE_MODE_USXGMII, pll->supported);
+ __set_bit(LANE_MODE_10G_QXGMII, pll->supported);
+ break;
+ default:
+ break;
+ }
+}
+
+/* On LS1028A, SGMIIA_CFG, SGMIIB_CFG, and SGMIIC_CFG from PCCR8 have the
+ * ability to map either an ENETC PCS or a Felix switch PCS to the same lane.
+ * The PHY API lacks the capability to distinguish between one consumer and
+ * another, so we don't support changing the initial muxing done by the RCW.
+ * However, when disabling a PCS through PCCR8, we need to properly restore
+ * the original value to keep the same muxing, and for that we need to back
+ * it up (here).
+ */
+static void lynx_10g_backup_pccr_val(struct lynx_lane *lane)
+{
+ u32 val;
+ int err;
+
+ if (lane->mode == LANE_MODE_UNKNOWN)
+ return;
+
+ err = lynx_pccr_read(lane, lane->mode, &val);
+ if (err) {
+ dev_warn(&lane->phy->dev,
+ "The driver doesn't know how to access the PCCR for lane mode %s\n",
+ lynx_lane_mode_str(lane->mode));
+ lane->mode = LANE_MODE_UNKNOWN;
+ return;
+ }
+
+ lane->default_pccr[lane->mode] = val;
+
+ switch (lane->mode) {
+ case LANE_MODE_1000BASEX_SGMII:
+ case LANE_MODE_2500BASEX:
+ lane->default_pccr[LANE_MODE_1000BASEX_SGMII] = val & ~PCCR8_SGMIIa_KX;
+ lane->default_pccr[LANE_MODE_2500BASEX] = val & ~PCCR8_SGMIIa_KX;
+ break;
+ default:
+ break;
+ }
+}
+
+static bool lynx_10g_lane_is_3_125g(struct lynx_lane *lane)
+{
+ struct lynx_priv *priv = lane->priv;
+ struct lynx_pll *pll;
+ u32 gcr0;
+
+ gcr0 = lynx_lane_read(lane, LNaGCR0);
+
+ if (gcr0 & LNaGCR0_TPLL_PLLF)
+ pll = &priv->pll[0];
+ else
+ pll = &priv->pll[1];
+
+ if (pll->frate_sel != PLLnCR0_FRATE_3_125G)
+ return false;
+
+ if (FIELD_GET(LNaGCR0_TRAT_SEL, gcr0) != RAT_SEL_FULL ||
+ FIELD_GET(LNaGCR0_RRAT_SEL, gcr0) != RAT_SEL_FULL)
+ return false;
+
+ return true;
+}
+
+static void lynx_10g_lane_read_configuration(struct lynx_lane *lane)
+{
+ u32 pssr0 = lynx_lane_read(lane, LNaPSSR0);
+ struct lynx_priv *priv = lane->priv;
+ int proto;
+
+ proto = FIELD_GET(LNaPSSR0_TYPE, pssr0);
+ switch (proto) {
+ case PROTO_SEL_SGMII_BASEX_KX_QSGMII:
+ if (lynx_10g_lane_is_3_125g(lane))
+ lane->mode = LANE_MODE_2500BASEX;
+ else if (FIELD_GET(LNaPSSR0_IS_QUAD, pssr0))
+ lane->mode = LANE_MODE_QSGMII;
+ else
+ lane->mode = LANE_MODE_1000BASEX_SGMII;
+ break;
+ case PROTO_SEL_XFI_10GBASER_KR_SXGMII:
+ if (FIELD_GET(LNaPSSR0_IS_QUAD, pssr0))
+ lane->mode = LANE_MODE_10G_QXGMII;
+ else if (priv->info->quirks & LYNX_QUIRK_HAS_HARDCODED_USXGMII)
+ lane->mode = LANE_MODE_USXGMII;
+ else
+ lane->mode = LANE_MODE_10GBASER;
+ break;
+ case PROTO_SEL_PCIE:
+ case PROTO_SEL_SATA:
+ case PROTO_SEL_XAUI:
+ break;
+ default:
+ dev_warn(&lane->phy->dev, "Unknown lane protocol 0x%x\n",
+ proto);
+ }
+
+ lynx_10g_backup_pccr_val(lane);
+}
+
+static int ls1028a_get_pccr(enum lynx_lane_mode lane_mode, int lane,
+ struct lynx_pccr *pccr)
+{
+ switch (lane_mode) {
+ case LANE_MODE_1000BASEX_SGMII:
+ case LANE_MODE_2500BASEX:
+ pccr->offset = PCCR8;
+ pccr->width = 4;
+ pccr->shift = SGMII_CFG(lane);
+ break;
+ case LANE_MODE_QSGMII:
+ if (lane != 1)
+ return -EINVAL;
+
+ pccr->offset = PCCR9;
+ pccr->width = 3;
+ pccr->shift = QSGMII_CFG(A);
+ break;
+ case LANE_MODE_10G_QXGMII:
+ if (lane != 1)
+ return -EINVAL;
+
+ pccr->offset = PCCR9;
+ pccr->width = 3;
+ pccr->shift = QXGMII_CFG(A);
+ break;
+ case LANE_MODE_USXGMII:
+ if (lane != 0)
+ return -EINVAL;
+
+ pccr->offset = PCCRB;
+ pccr->width = 3;
+ pccr->shift = SXGMII_CFG(A);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int ls1028a_get_pcvt_offset(int lane, enum lynx_lane_mode mode)
+{
+ switch (mode) {
+ case LANE_MODE_1000BASEX_SGMII:
+ case LANE_MODE_2500BASEX:
+ return SGMIIaCR0(lane);
+ case LANE_MODE_QSGMII:
+ return lane == 1 ? QSGMIIaCR0(A) : -EINVAL;
+ case LANE_MODE_USXGMII:
+ return lane == 0 ? SXGMIIaCR0(A) : -EINVAL;
+ case LANE_MODE_10G_QXGMII:
+ return lane == 1 ? QXGMIIaCR0(A) : -EINVAL;
+ default:
+ return -EINVAL;
+ }
+}
+
+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,
+ .num_lanes = 4,
+ .index = 1,
+ .quirks = LYNX_QUIRK_HAS_HARDCODED_USXGMII,
+};
+
+static int ls1046a_serdes1_get_pccr(enum lynx_lane_mode lane_mode, int lane,
+ struct lynx_pccr *pccr)
+{
+ switch (lane_mode) {
+ case LANE_MODE_1000BASEX_SGMII:
+ case LANE_MODE_2500BASEX:
+ pccr->offset = PCCR8;
+ pccr->width = 4;
+ pccr->shift = SGMII_CFG(lane);
+ break;
+ case LANE_MODE_QSGMII:
+ if (lane != 1)
+ return -EINVAL;
+
+ pccr->offset = PCCR9;
+ pccr->width = 3;
+ pccr->shift = QSGMII_CFG(B);
+ break;
+ case LANE_MODE_10GBASER:
+ switch (lane) {
+ case 2:
+ pccr->shift = XFI_CFG(A);
+ break;
+ case 3:
+ pccr->shift = XFI_CFG(B);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ pccr->offset = PCCRB;
+ pccr->width = 3;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int ls1046a_serdes1_get_pcvt_offset(int lane, enum lynx_lane_mode mode)
+{
+ switch (mode) {
+ case LANE_MODE_1000BASEX_SGMII:
+ case LANE_MODE_2500BASEX:
+ return SGMIIaCR0(lane);
+ case LANE_MODE_QSGMII:
+ if (lane != 1)
+ return -EINVAL;
+
+ return QSGMIIaCR0(B);
+ case LANE_MODE_10GBASER:
+ switch (lane) {
+ case 2:
+ return XFIaCR0(A);
+ case 3:
+ return XFIaCR0(B);
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct lynx_info lynx_info_ls1046a_serdes1 = {
+ .get_pccr = ls1046a_serdes1_get_pccr,
+ .get_pcvt_offset = ls1046a_serdes1_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,
+ .num_lanes = 4,
+ .index = 1,
+};
+
+static int ls1046a_serdes2_get_pccr(enum lynx_lane_mode lane_mode, int lane,
+ struct lynx_pccr *pccr)
+{
+ switch (lane_mode) {
+ case LANE_MODE_1000BASEX_SGMII:
+ case LANE_MODE_2500BASEX:
+ if (lane != 1)
+ return -EINVAL;
+
+ pccr->offset = PCCR8;
+ pccr->width = 4;
+ pccr->shift = SGMII_CFG(B);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int ls1046a_serdes2_get_pcvt_offset(int lane, enum lynx_lane_mode mode)
+{
+ switch (mode) {
+ case LANE_MODE_1000BASEX_SGMII:
+ case LANE_MODE_2500BASEX:
+ if (lane != 1)
+ return -EINVAL;
+
+ return SGMIIaCR0(B);
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct lynx_info lynx_info_ls1046a_serdes2 = {
+ .get_pccr = ls1046a_serdes2_get_pccr,
+ .get_pcvt_offset = ls1046a_serdes2_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,
+ .num_lanes = 4,
+ .index = 2,
+};
+
+static int ls1088a_serdes1_get_pccr(enum lynx_lane_mode lane_mode, int lane,
+ struct lynx_pccr *pccr)
+{
+ switch (lane_mode) {
+ case LANE_MODE_1000BASEX_SGMII:
+ pccr->offset = PCCR8;
+ pccr->width = 4;
+ pccr->shift = SGMII_CFG(lane);
+ break;
+ case LANE_MODE_QSGMII:
+ switch (lane) {
+ case 0:
+ pccr->shift = QSGMII_CFG(A);
+ break;
+ case 1:
+ case 3:
+ pccr->shift = QSGMII_CFG(B);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ pccr->offset = PCCR9;
+ pccr->width = 3;
+ break;
+ case LANE_MODE_10GBASER:
+ switch (lane) {
+ case 2:
+ pccr->shift = XFI_CFG(A);
+ break;
+ case 3:
+ pccr->shift = XFI_CFG(B);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ pccr->offset = PCCRB;
+ pccr->width = 3;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int ls1088a_serdes1_get_pcvt_offset(int lane, enum lynx_lane_mode mode)
+{
+ switch (mode) {
+ case LANE_MODE_1000BASEX_SGMII:
+ return SGMIIaCR0(lane);
+ case LANE_MODE_QSGMII:
+ switch (lane) {
+ case 0:
+ return QSGMIIaCR0(A);
+ case 1:
+ case 3:
+ return QSGMIIaCR0(B);
+ default:
+ return -EINVAL;
+ }
+ case LANE_MODE_10GBASER:
+ switch (lane) {
+ case 2:
+ return XFIaCR0(A);
+ case 3:
+ return XFIaCR0(B);
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct lynx_info lynx_info_ls1088a_serdes1 = {
+ .get_pccr = ls1088a_serdes1_get_pccr,
+ .get_pcvt_offset = ls1088a_serdes1_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,
+ .num_lanes = 4,
+ .index = 1,
+};
+
+static int ls2088a_serdes1_get_pccr(enum lynx_lane_mode lane_mode, int lane,
+ struct lynx_pccr *pccr)
+{
+ switch (lane_mode) {
+ case LANE_MODE_1000BASEX_SGMII:
+ case LANE_MODE_2500BASEX:
+ pccr->offset = PCCR8;
+ pccr->width = 4;
+ pccr->shift = SGMII_CFG(lane);
+ break;
+ case LANE_MODE_QSGMII:
+ switch (lane) {
+ case 2:
+ case 6:
+ pccr->shift = QSGMII_CFG(A);
+ break;
+ case 7:
+ pccr->shift = QSGMII_CFG(B);
+ break;
+ case 0:
+ case 4:
+ pccr->shift = QSGMII_CFG(C);
+ break;
+ case 1:
+ case 5:
+ pccr->shift = QSGMII_CFG(D);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ pccr->offset = PCCR9;
+ pccr->width = 3;
+ break;
+ case LANE_MODE_10GBASER:
+ pccr->offset = PCCRB;
+ pccr->width = 3;
+ pccr->shift = XFI_CFG(lane);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int ls2088a_serdes1_get_pcvt_offset(int lane, enum lynx_lane_mode mode)
+{
+ switch (mode) {
+ case LANE_MODE_1000BASEX_SGMII:
+ case LANE_MODE_2500BASEX:
+ return SGMIIaCR0(lane);
+ case LANE_MODE_QSGMII:
+ switch (lane) {
+ case 2:
+ case 6:
+ return QSGMIIaCR0(A);
+ case 7:
+ return QSGMIIaCR0(B);
+ case 0:
+ case 4:
+ return QSGMIIaCR0(C);
+ case 1:
+ case 5:
+ return QSGMIIaCR0(D);
+ default:
+ return -EINVAL;
+ }
+ case LANE_MODE_10GBASER:
+ return XFIaCR0(lane);
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct lynx_info lynx_info_ls2088a_serdes1 = {
+ .get_pccr = ls2088a_serdes1_get_pccr,
+ .get_pcvt_offset = ls2088a_serdes1_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,
+ .num_lanes = 8,
+ .index = 1,
+};
+
+static int ls2088a_serdes2_get_pccr(enum lynx_lane_mode lane_mode, int lane,
+ struct lynx_pccr *pccr)
+{
+ switch (lane_mode) {
+ case LANE_MODE_1000BASEX_SGMII:
+ case LANE_MODE_2500BASEX:
+ pccr->offset = PCCR8;
+ pccr->width = 4;
+ pccr->shift = SGMII_CFG(lane);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int ls2088a_serdes2_get_pcvt_offset(int lane, enum lynx_lane_mode mode)
+{
+ switch (mode) {
+ case LANE_MODE_1000BASEX_SGMII:
+ case LANE_MODE_2500BASEX:
+ return SGMIIaCR0(lane);
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct lynx_info lynx_info_ls2088a_serdes2 = {
+ .get_pccr = ls2088a_serdes2_get_pccr,
+ .get_pcvt_offset = ls2088a_serdes2_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,
+ .num_lanes = 8,
+ .index = 2,
+};
+
+/* Halting puts the lane in a mode in which it can be reconfigured */
+static void lynx_10g_lane_halt(struct phy *phy)
+{
+ struct lynx_lane *lane = phy_get_drvdata(phy);
+
+ if (!lane->powered_up)
+ return;
+
+ /* Issue a reset request */
+ lynx_lane_rmw(lane, LNaGCR0,
+ LNaGCR0_RRST_ON | LNaGCR0_TRST_ON,
+ LNaGCR0_RRST | LNaGCR0_TRST);
+
+ /* The RM says to wait for at least 50ns */
+ usleep_range(1, 2);
+}
+
+static void lynx_10g_lane_reset(struct phy *phy)
+{
+ struct lynx_lane *lane = phy_get_drvdata(phy);
+
+ if (!lane->powered_up)
+ return;
+
+ /* Finalize the reset request */
+ lynx_lane_rmw(lane, LNaGCR0,
+ LNaGCR0_RRST_OFF | LNaGCR0_TRST_OFF,
+ LNaGCR0_RRST | LNaGCR0_TRST);
+
+ /* The RM says to wait for at least 50ns */
+ usleep_range(1, 2);
+}
+
+static int lynx_10g_power_off(struct phy *phy)
+{
+ struct lynx_lane *lane = phy_get_drvdata(phy);
+
+ if (!lane->powered_up)
+ return 0;
+
+ /* Issue a reset request with the power down bits set */
+ lynx_lane_rmw(lane, LNaGCR0,
+ LNaGCR0_RRST_ON | LNaGCR0_TRST_ON |
+ LNaGCR0_RX_PD | LNaGCR0_TX_PD,
+ LNaGCR0_RRST | LNaGCR0_TRST |
+ LNaGCR0_RX_PD | LNaGCR0_TX_PD);
+
+ /* The RM says to wait for at least 50ns */
+ usleep_range(1, 2);
+
+ lane->powered_up = false;
+
+ return 0;
+}
+
+static int lynx_10g_power_on(struct phy *phy)
+{
+ struct lynx_lane *lane = phy_get_drvdata(phy);
+
+ if (lane->powered_up)
+ return 0;
+
+ /* The RM says to wait for at least 120ns between per lane setting have
+ * been changed and the lane is taken out of reset
+ */
+ usleep_range(1, 2);
+
+ lynx_lane_rmw(lane, LNaGCR0, LNaGCR0_RRST_OFF | LNaGCR0_TRST_OFF,
+ LNaGCR0_RRST | LNaGCR0_TRST |
+ LNaGCR0_RX_PD | LNaGCR0_TX_PD);
+
+ lane->powered_up = true;
+
+ return 0;
+}
+
+static void lynx_10g_lane_set_nrate(struct lynx_lane *lane,
+ struct lynx_pll *pll,
+ enum lynx_lane_mode mode)
+{
+ switch (pll->frate_sel) {
+ case PLLnCR0_FRATE_5G:
+ switch (mode) {
+ case LANE_MODE_1000BASEX_SGMII:
+ lynx_lane_rmw(lane, LNaGCR0,
+ FIELD_PREP(LNaGCR0_TRAT_SEL, RAT_SEL_QUARTER) |
+ FIELD_PREP(LNaGCR0_RRAT_SEL, RAT_SEL_QUARTER),
+ LNaGCR0_RRAT_SEL | LNaGCR0_TRAT_SEL);
+ break;
+ case LANE_MODE_QSGMII:
+ lynx_lane_rmw(lane, LNaGCR0,
+ FIELD_PREP(LNaGCR0_TRAT_SEL, RAT_SEL_FULL) |
+ FIELD_PREP(LNaGCR0_RRAT_SEL, RAT_SEL_FULL),
+ LNaGCR0_RRAT_SEL | LNaGCR0_TRAT_SEL);
+ break;
+ default:
+ break;
+ }
+ break;
+ case PLLnCR0_FRATE_3_125G:
+ switch (mode) {
+ case LANE_MODE_2500BASEX:
+ lynx_lane_rmw(lane, LNaGCR0,
+ FIELD_PREP(LNaGCR0_TRAT_SEL, RAT_SEL_FULL) |
+ FIELD_PREP(LNaGCR0_RRAT_SEL, RAT_SEL_FULL),
+ LNaGCR0_RRAT_SEL | LNaGCR0_TRAT_SEL);
+ break;
+ default:
+ break;
+ }
+ break;
+ case PLLnCR0_FRATE_5_15625G:
+ switch (mode) {
+ case LANE_MODE_10GBASER:
+ case LANE_MODE_USXGMII:
+ case LANE_MODE_10G_QXGMII:
+ lynx_lane_rmw(lane, LNaGCR0,
+ FIELD_PREP(LNaGCR0_TRAT_SEL, RAT_SEL_DOUBLE) |
+ FIELD_PREP(LNaGCR0_RRAT_SEL, RAT_SEL_DOUBLE),
+ LNaGCR0_RRAT_SEL | LNaGCR0_TRAT_SEL);
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+static void lynx_10g_lane_set_pll(struct lynx_lane *lane,
+ struct lynx_pll *pll)
+{
+ if (pll->id == 0) {
+ lynx_lane_rmw(lane, LNaGCR0,
+ LNaGCR0_RPLL_PLLF | LNaGCR0_TPLL_PLLF,
+ LNaGCR0_RPLL_MSK | LNaGCR0_TPLL_MSK);
+ } else {
+ lynx_lane_rmw(lane, LNaGCR0,
+ LNaGCR0_RPLL_PLLS | LNaGCR0_TPLL_PLLS,
+ LNaGCR0_RPLL_MSK | LNaGCR0_TPLL_MSK);
+ }
+}
+
+static void lynx_10g_lane_remap_pll(struct lynx_lane *lane,
+ enum lynx_lane_mode lane_mode)
+{
+ struct lynx_priv *priv = lane->priv;
+ struct lynx_pll *pll;
+
+ /* Switch to the PLL that works with this interface type */
+ pll = lynx_pll_get(priv, lane_mode);
+ if (unlikely(!pll))
+ return;
+
+ lynx_10g_lane_set_pll(lane, pll);
+
+ /* Choose the portion of clock net to be used on this lane */
+ lynx_10g_lane_set_nrate(lane, pll, lane_mode);
+}
+
+static void lynx_10g_lane_change_proto_conf(struct lynx_lane *lane,
+ enum lynx_lane_mode mode)
+{
+ const struct lynx_10g_proto_conf *conf = &lynx_10g_proto_conf[mode];
+
+ lynx_lane_rmw(lane, LNaGCR0,
+ FIELD_PREP(LNaGCR0_PROTS, conf->proto_sel) |
+ FIELD_PREP(LNaGCR0_IF20BIT_EN, conf->if20bit_en),
+ LNaGCR0_PROTS | LNaGCR0_IF20BIT_EN);
+ lynx_lane_rmw(lane, LNaGCR1,
+ FIELD_PREP(LNaGCR1_REIDL_TH, conf->reidl_th) |
+ FIELD_PREP(LNaGCR1_REIDL_ET_MSB, conf->reidl_et_msb) |
+ FIELD_PREP(LNaGCR1_REIDL_ET_SEL, conf->reidl_et_sel) |
+ FIELD_PREP(LNaGCR1_REIDL_EX_MSB, conf->reidl_ex_msb) |
+ FIELD_PREP(LNaGCR1_REIDL_EX_SEL, conf->reidl_ex_sel) |
+ FIELD_PREP(LNaGCR1_ISLEW_RCTL, conf->islew_rctl) |
+ FIELD_PREP(LNaGCR1_OSLEW_RCTL, conf->oslew_rctl),
+ LNaGCR1_REIDL_TH |
+ LNaGCR1_REIDL_ET_MSB | LNaGCR1_REIDL_ET_SEL |
+ LNaGCR1_REIDL_EX_MSB | LNaGCR1_REIDL_EX_SEL |
+ LNaGCR1_ISLEW_RCTL | LNaGCR1_OSLEW_RCTL);
+ lynx_lane_rmw(lane, LNaRECR0,
+ FIELD_PREP(LNaRECR0_RXEQ_BST, conf->rxeq_bst) |
+ FIELD_PREP(LNaRECR0_GK2OVD, conf->gk2ovd) |
+ FIELD_PREP(LNaRECR0_GK3OVD, conf->gk3ovd) |
+ FIELD_PREP(LNaRECR0_GK2OVD_EN, conf->gk2ovd_en) |
+ FIELD_PREP(LNaRECR0_GK3OVD_EN, conf->gk3ovd_en) |
+ FIELD_PREP(LNaRECR0_BASE_WAND, conf->base_wand),
+ LNaRECR0_RXEQ_BST | LNaRECR0_GK2OVD | LNaRECR0_GK3OVD |
+ LNaRECR0_GK2OVD_EN | LNaRECR0_GK3OVD_EN |
+ LNaRECR0_BASE_WAND);
+ lynx_lane_rmw(lane, LNaTECR0,
+ FIELD_PREP(LNaTECR0_TEQ_TYPE, conf->teq_type) |
+ FIELD_PREP(LNaTECR0_SGN_PREQ, conf->sgn_preq) |
+ FIELD_PREP(LNaTECR0_RATIO_PREQ, conf->ratio_preq) |
+ FIELD_PREP(LNaTECR0_SGN_POST1Q, conf->sgn_post1q) |
+ FIELD_PREP(LNaTECR0_RATIO_PST1Q, conf->ratio_post1q) |
+ FIELD_PREP(LNaTECR0_ADPT_EQ, conf->adpt_eq) |
+ FIELD_PREP(LNaTECR0_AMP_RED, conf->amp_red),
+ LNaTECR0_TEQ_TYPE | LNaTECR0_SGN_PREQ |
+ LNaTECR0_RATIO_PREQ | LNaTECR0_SGN_POST1Q |
+ LNaTECR0_RATIO_PST1Q | LNaTECR0_ADPT_EQ |
+ LNaTECR0_AMP_RED);
+ lynx_lane_write(lane, LNaTTLCR0, conf->ttlcr0);
+}
+
+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);
+ break;
+ case LANE_MODE_QSGMII:
+ err = lynx_pcvt_rmw(lane, mode, CR(0),
+ QSGMIIaCR0_RST_QSGM_ON | QSGMIIaCR0_PD_QSGM,
+ QSGMIIaCR0_RST_QSGM | QSGMIIaCR0_PD_QSGM);
+ if (err)
+ goto out;
+ break;
+ default:
+ err = 0;
+ }
+
+out:
+ spin_unlock(&priv->pcc_lock);
+
+ return err;
+}
+
+static int lynx_10g_lane_enable_pcvt(struct lynx_lane *lane,
+ enum lynx_lane_mode mode)
+{
+ struct lynx_priv *priv = lane->priv;
+ u32 val;
+ int err;
+
+ spin_lock(&priv->pcc_lock);
+
+ switch (mode) {
+ case LANE_MODE_1000BASEX_SGMII:
+ case LANE_MODE_2500BASEX:
+ err = lynx_pcvt_rmw(lane, mode, CR(1), SGMIIaCR1_SGPCS_EN,
+ SGMIIaCR1_SGPCS_EN);
+ if (err)
+ goto out;
+
+ lynx_pcvt_rmw(lane, mode, CR(0), SGMIIaCR0_RST_SGM_OFF,
+ SGMIIaCR0_RST_SGM | SGMIIaCR0_PD_SGM);
+ break;
+ case LANE_MODE_QSGMII:
+ err = lynx_pcvt_rmw(lane, mode, CR(0), QSGMIIaCR0_RST_QSGM_OFF,
+ QSGMIIaCR0_RST_QSGM | QSGMIIaCR0_PD_QSGM);
+ if (err)
+ goto out;
+ break;
+ default:
+ err = 0;
+ }
+
+ if (lane->default_pccr[mode]) {
+ err = lynx_pccr_write(lane, mode, lane->default_pccr[mode]);
+ goto out;
+ }
+
+ val = 0;
+
+ switch (mode) {
+ case LANE_MODE_1000BASEX_SGMII:
+ case LANE_MODE_2500BASEX:
+ val |= PCCR8_SGMIIa_CFG;
+ break;
+ case LANE_MODE_QSGMII:
+ val |= PCCR9_QSGMIIa_CFG;
+ break;
+ case LANE_MODE_10G_QXGMII:
+ val |= PCCR9_QXGMIIa_CFG;
+ break;
+ case LANE_MODE_10GBASER:
+ val |= PCCRB_XFIa_CFG;
+ break;
+ case LANE_MODE_USXGMII:
+ val |= PCCRB_SXGMIIa_CFG;
+ break;
+ default:
+ err = 0;
+ goto out;
+ }
+
+ err = lynx_pccr_write(lane, mode, val);
+out:
+ spin_unlock(&priv->pcc_lock);
+
+ return err;
+}
+
+static bool lynx_10g_lane_mode_needs_rcw_override(struct lynx_lane *lane,
+ enum lynx_lane_mode new)
+{
+ enum lynx_lane_mode curr = lane->mode;
+
+ /* Major protocol changes, which involve changing the PCS connection to
+ * the GMII MAC with the one to the XGMII MAC, require an RCW override
+ * procedure to reconfigure an internal mux, as documented here:
+ * https://lore.kernel.org/linux-phy/20230810102631.bvozjer3t67r67iy@skbuf/
+ * This is SoC-specific, and not yet implemented in drivers/soc/fsl/guts.c.
+ *
+ * So the supported set of protocols depends on the initial lane mode.
+ *
+ * Minor protocol changes (SGMII <-> 1000Base-X <-> 2500Base-X or
+ * 10GBase-R <-> USXGMII) are supported.
+ */
+ if ((lynx_lane_mode_uses_gmii_mac(curr) &&
+ lynx_lane_mode_uses_xgmii_mac(new)) ||
+ (lynx_lane_mode_uses_xgmii_mac(curr) &&
+ lynx_lane_mode_uses_gmii_mac(new)))
+ return true;
+
+ return false;
+}
+
+/* The quad protocols are fixed because the lane has multiple consumers, and
+ * one phy_set_mode_ext() affects the other consumers as well. We have no use
+ * case for dynamic protocol changing here, so disallow it.
+ */
+static enum lynx_lane_mode lynx_fixed_protocols[] = {
+ LANE_MODE_QSGMII,
+ LANE_MODE_10G_QXGMII,
+};
+
+static bool lynx_lane_restrict_fixed_mode_change(struct lynx_lane *lane,
+ enum lynx_lane_mode new)
+{
+ enum lynx_lane_mode curr = lane->mode;
+
+ for (int i = 0; i < ARRAY_SIZE(lynx_fixed_protocols); i++)
+ if ((curr == lynx_fixed_protocols[i] ||
+ new == lynx_fixed_protocols[i]) &&
+ curr != new)
+ return true;
+
+ return false;
+}
+
+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;
+}
+
+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));
+
+ lane->mode = lane_mode;
+
+out:
+ if (powered_up)
+ lynx_10g_lane_reset(phy);
+
+ return err;
+}
+
+static int lynx_10g_init(struct phy *phy)
+{
+ struct lynx_lane *lane = phy_get_drvdata(phy);
+
+ /* Mark the fact that the lane was init */
+ lane->init = true;
+
+ /* SerDes lanes are powered on at boot time. Any lane that is
+ * managed by this driver will get powered off when its consumer
+ * calls phy_init().
+ */
+ lane->powered_up = true;
+ lynx_10g_power_off(phy);
+
+ return 0;
+}
+
+static int lynx_10g_exit(struct phy *phy)
+{
+ struct lynx_lane *lane = phy_get_drvdata(phy);
+
+ /* The lane returns to the state where it isn't managed by the
+ * consumer, so we must treat is as if it isn't initialized, and always
+ * powered on.
+ */
+ lane->init = false;
+ lane->powered_up = false;
+ lynx_10g_power_on(phy);
+
+ return 0;
+}
+
+static const struct phy_ops lynx_10g_ops = {
+ .init = lynx_10g_init,
+ .exit = lynx_10g_exit,
+ .power_on = lynx_10g_power_on,
+ .power_off = lynx_10g_power_off,
+ .set_mode = lynx_10g_set_mode,
+ .validate = lynx_10g_validate,
+ .owner = THIS_MODULE,
+};
+
+static int lynx_10g_probe(struct platform_device *pdev)
+{
+ return lynx_probe(pdev, of_device_get_match_data(&pdev->dev),
+ &lynx_10g_ops);
+}
+
+static const struct of_device_id lynx_10g_of_match_table[] = {
+ { .compatible = "fsl,ls1028a-serdes", .data = &lynx_info_ls1028a },
+ { .compatible = "fsl,ls1046a-serdes1", .data = &lynx_info_ls1046a_serdes1 },
+ { .compatible = "fsl,ls1046a-serdes2", .data = &lynx_info_ls1046a_serdes2 },
+ { .compatible = "fsl,ls1088a-serdes1", .data = &lynx_info_ls1088a_serdes1 },
+ { .compatible = "fsl,ls2088a-serdes1", .data = &lynx_info_ls2088a_serdes1 },
+ { .compatible = "fsl,ls2088a-serdes2", .data = &lynx_info_ls2088a_serdes2 },
+ {}
+};
+MODULE_DEVICE_TABLE(of, lynx_10g_of_match_table);
+
+static struct platform_driver lynx_10g_driver = {
+ .probe = lynx_10g_probe,
+ .remove = lynx_remove,
+ .driver = {
+ .name = "lynx-10g",
+ .of_match_table = lynx_10g_of_match_table,
+ },
+};
+module_platform_driver(lynx_10g_driver);
+
+MODULE_IMPORT_NS("PHY_FSL_LYNX");
+MODULE_AUTHOR("Ioana Ciornei <ioana.ciornei@nxp.com>");
+MODULE_AUTHOR("Vladimir Oltean <vladimir.oltean@nxp.com>");
+MODULE_DESCRIPTION("Lynx 10G SerDes PHY driver for Layerscape SoCs");
+MODULE_LICENSE("GPL");
diff --git a/drivers/phy/freescale/phy-fsl-lynx-core.c b/drivers/phy/freescale/phy-fsl-lynx-core.c
index ca523e59b167..05043d3fc736 100644
--- a/drivers/phy/freescale/phy-fsl-lynx-core.c
+++ b/drivers/phy/freescale/phy-fsl-lynx-core.c
@@ -11,6 +11,12 @@ 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_2500BASEX:
+ return "2500Base-X";
+ case LANE_MODE_QSGMII:
+ return "QSGMII";
+ case LANE_MODE_10G_QXGMII:
+ return "10G-QXGMII";
case LANE_MODE_10GBASER:
return "10GBase-R";
case LANE_MODE_USXGMII:
@@ -29,6 +35,12 @@ enum lynx_lane_mode phy_interface_to_lane_mode(phy_interface_t intf)
case PHY_INTERFACE_MODE_SGMII:
case PHY_INTERFACE_MODE_1000BASEX:
return LANE_MODE_1000BASEX_SGMII;
+ case PHY_INTERFACE_MODE_2500BASEX:
+ return LANE_MODE_2500BASEX;
+ case PHY_INTERFACE_MODE_QSGMII:
+ return LANE_MODE_QSGMII;
+ case PHY_INTERFACE_MODE_10G_QXGMII:
+ return LANE_MODE_10G_QXGMII;
case PHY_INTERFACE_MODE_10GBASER:
return LANE_MODE_10GBASER;
case PHY_INTERFACE_MODE_USXGMII:
diff --git a/drivers/phy/freescale/phy-fsl-lynx-core.h b/drivers/phy/freescale/phy-fsl-lynx-core.h
index 3d9508dfb2c1..137e015eab6b 100644
--- a/drivers/phy/freescale/phy-fsl-lynx-core.h
+++ b/drivers/phy/freescale/phy-fsl-lynx-core.h
@@ -9,6 +9,7 @@
#include <soc/fsl/phy-fsl-lynx.h>
#define LYNX_NUM_PLL 2
+#define LYNX_QUIRK_HAS_HARDCODED_USXGMII BIT(0)
struct lynx_priv;
struct lynx_lane;
@@ -36,6 +37,7 @@ struct lynx_lane {
bool init;
unsigned int id;
enum lynx_lane_mode mode;
+ u32 default_pccr[LANE_MODE_MAX];
};
struct lynx_info {
@@ -48,6 +50,8 @@ struct lynx_info {
void (*cdr_lock_check)(struct lynx_lane *lane);
int first_lane;
int num_lanes;
+ int index;
+ unsigned long quirks;
};
struct lynx_priv {
diff --git a/include/soc/fsl/phy-fsl-lynx.h b/include/soc/fsl/phy-fsl-lynx.h
index 92e8272d5ae1..ff5a7d1835b5 100644
--- a/include/soc/fsl/phy-fsl-lynx.h
+++ b/include/soc/fsl/phy-fsl-lynx.h
@@ -7,10 +7,37 @@
enum lynx_lane_mode {
LANE_MODE_UNKNOWN,
LANE_MODE_1000BASEX_SGMII,
+ LANE_MODE_2500BASEX,
+ LANE_MODE_QSGMII,
+ LANE_MODE_10G_QXGMII,
LANE_MODE_10GBASER,
LANE_MODE_USXGMII,
LANE_MODE_25GBASER,
LANE_MODE_MAX,
};
+static inline bool lynx_lane_mode_uses_gmii_mac(enum lynx_lane_mode mode)
+{
+ switch (mode) {
+ case LANE_MODE_1000BASEX_SGMII:
+ case LANE_MODE_2500BASEX:
+ case LANE_MODE_QSGMII:
+ case LANE_MODE_10G_QXGMII:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static inline bool lynx_lane_mode_uses_xgmii_mac(enum lynx_lane_mode mode)
+{
+ switch (mode) {
+ case LANE_MODE_10GBASER:
+ case LANE_MODE_USXGMII:
+ return true;
+ default:
+ return false;
+ }
+}
+
#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 phy-next 09/13] phy: lynx-28g: common probe() and remove()
From: Vladimir Oltean @ 2026-05-28 17:24 UTC (permalink / raw)
To: linux-phy
Cc: Ioana Ciornei, Vinod Koul, Neil Armstrong, Tanjeff Moos,
linux-kernel
In-Reply-To: <20260528172404.733196-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>
---
drivers/phy/freescale/phy-fsl-lynx-28g.c | 222 +++++-----------------
drivers/phy/freescale/phy-fsl-lynx-core.c | 167 ++++++++++++++++
drivers/phy/freescale/phy-fsl-lynx-core.h | 12 +-
3 files changed, 224 insertions(+), 177 deletions(-)
diff --git a/drivers/phy/freescale/phy-fsl-lynx-28g.c b/drivers/phy/freescale/phy-fsl-lynx-28g.c
index 31598f0042da..84e7a92c888d 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_28g_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,109 +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);
- 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[] = {
@@ -1354,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..bd1cd78e80bb 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,171 @@ 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;
+ }
+
+ 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 phy-next 08/13] phy: lynx-28g: make lynx_28g_pll_read_configuration() callable per PLL
From: Vladimir Oltean @ 2026-05-28 17:23 UTC (permalink / raw)
To: linux-phy
Cc: Ioana Ciornei, Vinod Koul, Neil Armstrong, Tanjeff Moos,
linux-kernel
In-Reply-To: <20260528172404.733196-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_lane_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>
---
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 e4c227e9ebdc..31598f0042da 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_28g_pll_read(pll, PLLnRSTCTL);
- pll->enabled = !(val & PLLnRSTCTL_DIS);
- pll->locked = !!(val & PLLnRSTCTL_LOCK);
+ val = lynx_pll_read(pll, PLLnRSTCTL);
+ pll->enabled = !(val & PLLnRSTCTL_DIS);
+ pll->locked = !!(val & PLLnRSTCTL_LOCK);
- val = lynx_28g_pll_read(pll, PLLnCR0);
- pll->refclk_sel = FIELD_GET(PLLnCR0_REFCLK_SEL, val);
+ val = lynx_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);
+ val = lynx_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;
}
}
@@ -1288,7 +1279,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 phy-next 07/13] phy: lynx-28g: move struct lynx_info definitions downwards
From: Vladimir Oltean @ 2026-05-28 17:23 UTC (permalink / raw)
To: linux-phy
Cc: Ioana Ciornei, Vinod Koul, Neil Armstrong, Tanjeff Moos,
linux-kernel
In-Reply-To: <20260528172404.733196-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>
---
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 84dce3026efc..e4c227e9ebdc 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_28g_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 phy-next 11/13] phy: lynx-28g: optimize read-modify-write operation
From: Vladimir Oltean @ 2026-05-28 17:24 UTC (permalink / raw)
To: linux-phy
Cc: Ioana Ciornei, Vinod Koul, Neil Armstrong, Tanjeff Moos,
linux-kernel
In-Reply-To: <20260528172404.733196-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>
---
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
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