From: Conor Dooley <conor@kernel.org>
To: Vladimir Oltean <vladimir.oltean@nxp.com>
Cc: linux-phy@lists.infradead.org, devicetree@vger.kernel.org,
linuxppc-dev@lists.ozlabs.org,
linux-arm-kernel@lists.infradead.org,
Ioana Ciornei <ioana.ciornei@nxp.com>,
Vinod Koul <vkoul@kernel.org>,
Neil Armstrong <neil.armstrong@linaro.org>,
Tanjeff Moos <tanjeff.moos@westermo.com>,
"Christophe Leroy (CS GROUP)" <chleroy@kernel.org>,
Michael Walle <mwalle@kernel.org>,
Shawn Guo <shawnguo@kernel.org>, Frank Li <Frank.Li@nxp.com>,
linux-kernel@vger.kernel.org,
Krzysztof Kozlowski <krzysztof.kozlowski+dt@linaro.org>,
Rob Herring <robh@kernel.org>
Subject: Re: [PATCH v1 phy-next 7/8] soc: fsl: guts: implement the RCW override procedure
Date: Fri, 12 Jun 2026 16:44:30 +0100 [thread overview]
Message-ID: <20260612-twenty-diary-4aab5327f9c2@spud> (raw)
In-Reply-To: <20260611193940.44416-8-vladimir.oltean@nxp.com>
[-- Attachment #1: Type: text/plain, Size: 15588 bytes --]
On Thu, Jun 11, 2026 at 10:39:39PM +0300, Vladimir Oltean wrote:
> From: Ioana Ciornei <ioana.ciornei@nxp.com>
>
> Add support for the RCW override procedure which enables runtime
> reconfiguration of the protocol running on a SerDes lane. The procedure
> is done through the DCFG DCSR space which now can be defined as the
> second memory region of the guts DT node.
> Support is added on the following SoCs: LS1046A, LS1088A, LS2088A.
>
> The procedure is exported to the "client" driver - the Lynx10G SerDes
> PHY driver - through the following functions:
> - fsl_guts_lane_init() used to notify the initial / boot time lane mode
> running on a SerDes lane.
> - fsl_guts_lane_validate() used to validate that changing the protocol
> on a specific lane is supported.
> - fsl_guts_lane_set_mode() which can be used to request the RCW
> procedure be executed for a specific lane.
>
> Since the RCW override procedure is different depending on the SoC, the
> private fsl_soc_data structure is updated with two new per SoC callbacks
> (.serdes_get_rcw_override() and .serdes_init_rcwcr()) which get used
> from the generic fsl_guts_lane_set_mode() function. These two callbacks
> hide all the SoC specific register offsets, masks and values so that the
> _set_mode() procedure is straightforward.
>
> Signed-off-by: Ioana Ciornei <ioana.ciornei@nxp.com>
> Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
> ---
> Cc: Conor Dooley <conor@kernel.org>
> Cc: Krzysztof Kozlowski <krzysztof.kozlowski+dt@linaro.org>
> Cc: Rob Herring <robh@kernel.org>
> Cc: devicetree@vger.kernel.org
Wrong CC list for this specific patch?
ta,
Conor.
> ---
> drivers/soc/fsl/guts.c | 286 ++++++++++++++++++++++++++++++++++++++-
> include/linux/fsl/guts.h | 20 ++-
> 2 files changed, 299 insertions(+), 7 deletions(-)
>
> diff --git a/drivers/soc/fsl/guts.c b/drivers/soc/fsl/guts.c
> index 9f2aff07a274..23ec5750080c 100644
> --- a/drivers/soc/fsl/guts.c
> +++ b/drivers/soc/fsl/guts.c
> @@ -15,6 +15,30 @@
> #include <linux/fsl/guts.h>
>
> #define DCFG_CCSR 0
> +#define DCFG_DCSR 1
> +
> +#define MAX_NUM_LANES 8
> +#define MAX_NUM_SERDES 2
> +
> +#define LS1088A_RCWSR29_SRDS_PRTCL_S1_LNn(lane) \
> + GENMASK(19 + 4 * (3 - lane), 16 + 4 * (3 - lane))
> +#define LS1088A_RCWSR30_SRDS_PRTCL_S2_LNn(lane) \
> + GENMASK(3 + 4 * (3 - lane), 4 * (3 - lane))
> +
> +#define LS1046A_RCWSR5_SRDS_PRTCL_S1(lane) \
> + GENMASK(19 + 4 * (lane), 16 + 4 * (lane))
> +#define SRDS_PRTCL_NONE 0
> +#define SRDS_PRTCL_XFI 1
> +#define SRDS_PRTCL_2500BASEX 2
> +#define SRDS_PRTCL_100BASEX_SGMII 3
> +#define SRDS_PRTCL_QSGMII 4
> +#define SRDS_PRTCL_PCIE 5
> +
> +#define LS2088A_RCWSR30_SRDS_CLK_EN_SEL_XGMII_S1 BIT(14)
> +#define LS2088A_RCWSR30_SRDS_CLK_SEL_XGMII_Ln_S1(lane) BIT(6 + (7 - (lane)))
> +#define LS2088A_RCWSR30_SRDS_CLK_SEL_MSK GENMASK(13, 6)
> +#define SRDS_CLK_SEL_XGMII 1
> +#define SRDS_CLK_SEL_GMII 0
>
> struct fsl_soc_die_attr {
> char *die;
> @@ -22,9 +46,19 @@ struct fsl_soc_die_attr {
> u32 mask;
> };
>
> +struct fsl_soc_serdes_rcw_override {
> + int offset;
> + int mask;
> + int val;
> +};
> +
> struct fsl_soc_data {
> const char *sfp_compat;
> u32 uid_offset;
> + int (*serdes_get_rcw_override)(int index, int lane,
> + enum lynx_lane_mode lane_mode,
> + struct fsl_soc_serdes_rcw_override *override);
> + void (*serdes_init_rcwcr)(int index);
> };
>
> enum qoriq_die {
> @@ -138,9 +172,13 @@ static const struct fsl_soc_die_attr fsl_soc_die[] = {
>
> static struct fsl_soc_guts {
> struct ccsr_guts __iomem *dcfg_ccsr;
> + struct ccsr_guts __iomem *dcfg_dcsr;
> const struct fsl_soc_data *data;
> bool little_endian;
> u32 svr;
> + enum lynx_lane_mode lane_mode[MAX_NUM_SERDES][MAX_NUM_LANES];
> + bool rcwcr_init_done;
> + spinlock_t rcwcr_lock; /* serializes concurrent writes to the RCWCR */
> } soc;
>
> static unsigned int fsl_guts_read(const void __iomem *reg)
> @@ -151,6 +189,28 @@ static unsigned int fsl_guts_read(const void __iomem *reg)
> return ioread32be(reg);
> }
>
> +static void fsl_guts_write(void __iomem *reg, u32 val)
> +{
> + if (soc.little_endian)
> + iowrite32(val, reg);
> + else
> + iowrite32be(val, reg);
> +}
> +
> +/* Some fields of the Reset Configuration Word (RCW) can be overridden at
> + * runtime by writing to the RCWCRn registers contained within the DCSR space
> + * of the Device Configuration (DCFG) block. The layout of the RCWCRn registers
> + * is identical with the read-only RCWSRn from the CCSR space.
> + */
> +static void fsl_guts_rmw(int offset, u32 val, u32 mask)
> +{
> + u32 tmp = fsl_guts_read(&soc.dcfg_ccsr->rcwsr[offset]);
> +
> + tmp &= ~mask;
> + tmp |= val;
> + fsl_guts_write(&soc.dcfg_dcsr->rcwcr[offset], tmp);
> +}
> +
> static bool fsl_soc_die_match_one(u32 svr, const struct fsl_soc_die_attr *match)
> {
> return match->svr == (svr & match->mask);
> @@ -167,6 +227,97 @@ static const struct fsl_soc_die_attr *fsl_soc_die_match(
> return NULL;
> }
>
> +static int
> +fsl_guts_serdes_get_rcw_override(int serdes_idx, int lane,
> + enum lynx_lane_mode lane_mode,
> + struct fsl_soc_serdes_rcw_override *override)
> +{
> + if ((!fsl_soc_die_match_one(soc.svr, &fsl_soc_die[DIE_LS1088A]) &&
> + !fsl_soc_die_match_one(soc.svr, &fsl_soc_die[DIE_LS2088A]) &&
> + !fsl_soc_die_match_one(soc.svr, &fsl_soc_die[DIE_LS1046A])) ||
> + !soc.data || !soc.data->serdes_get_rcw_override) {
> + pr_debug("RCW override not implemented for SoC\n");
> + return -EINVAL;
> + }
> +
> + if (!soc.dcfg_dcsr) {
> + pr_debug("Device tree does not define DCFG_DCSR region necessary for RCW override\n");
> + return -EINVAL;
> + }
> +
> + return soc.data->serdes_get_rcw_override(serdes_idx, lane, lane_mode,
> + override);
> +}
> +
> +/**
> + * fsl_guts_lane_init() - Notify guts module of SerDes lane configuration
> + * @serdes_idx: zero-based SerDes block index
> + * @lane: zero-based lane index within SerDes
> + * @lane_mode: initial / boot time SerDes protocol for lane
> + *
> + * On the LS208xA SoC, the RCW override procedure needs to be aware of all link
> + * modes which are configured on a SerDes block.
> + */
> +void fsl_guts_lane_init(int serdes_idx, int lane, enum lynx_lane_mode lane_mode)
> +{
> + soc.lane_mode[serdes_idx - 1][lane] = lane_mode;
> +}
> +EXPORT_SYMBOL_NS_GPL(fsl_guts_lane_init, "FSL_GUTS");
> +
> +/**
> + * fsl_guts_lane_validate() - Validate that SerDes protocol is implemented and
> + * supported on current SoC
> + * @serdes_idx: zero-based SerDes block index
> + * @lane: zero-based lane index within SerDes
> + * @lane_mode: requested SerDes protocol
> + *
> + * Should be called before actually requesting the RCW override procedure to be
> + * applied using %fsl_guts_lane_set_mode()
> + *
> + * Return: 0 if RCW override to protocol is possible, negative error otherwise
> + */
> +int fsl_guts_lane_validate(int serdes_idx, int lane, enum lynx_lane_mode lane_mode)
> +{
> + struct fsl_soc_serdes_rcw_override override;
> +
> + return fsl_guts_serdes_get_rcw_override(serdes_idx, lane, lane_mode,
> + &override);
> +}
> +EXPORT_SYMBOL_NS_GPL(fsl_guts_lane_validate, "FSL_GUTS");
> +
> +/**
> + * fsl_guts_lane_set_mode() - apply RCW override procedure for SerDes lane
> + * @serdes_idx: zero-based SerDes block index
> + * @lane: zero-based lane index within SerDes
> + * @lane_mode: requested SerDes protocol
> + *
> + * Return: 0 on success, negative error otherwise
> + */
> +int fsl_guts_lane_set_mode(int serdes_idx, int lane, enum lynx_lane_mode lane_mode)
> +{
> + struct fsl_soc_serdes_rcw_override override;
> + int err;
> +
> + err = fsl_guts_serdes_get_rcw_override(serdes_idx, lane, lane_mode,
> + &override);
> + if (err)
> + return err;
> +
> + spin_lock(&soc.rcwcr_lock);
> +
> + if (soc.data->serdes_init_rcwcr)
> + soc.data->serdes_init_rcwcr(serdes_idx);
> +
> + fsl_guts_rmw(override.offset, override.val << __bf_shf(override.mask),
> + override.mask);
> + soc.lane_mode[serdes_idx - 1][lane] = lane_mode;
> +
> + spin_unlock(&soc.rcwcr_lock);
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_NS_GPL(fsl_guts_lane_set_mode, "FSL_GUTS");
> +
> static u64 fsl_guts_get_soc_uid(const char *compat, unsigned int offset)
> {
> struct device_node *np;
> @@ -193,6 +344,128 @@ static u64 fsl_guts_get_soc_uid(const char *compat, unsigned int offset)
> return uid;
> }
>
> +static int ls1088a_serdes_get_rcw_override(int index, int lane,
> + enum lynx_lane_mode lane_mode,
> + struct fsl_soc_serdes_rcw_override *override)
> +{
> + /* The RCW override procedure has to write to different registers
> + * depending on the SerDes block index.
> + */
> + switch (index) {
> + case 1:
> + override->offset = 28;
> + override->mask = LS1088A_RCWSR29_SRDS_PRTCL_S1_LNn(lane);
> + break;
> + case 2:
> + override->offset = 29;
> + override->mask = LS1088A_RCWSR30_SRDS_PRTCL_S2_LNn(lane);
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + if (lynx_lane_mode_uses_xgmii_mac(lane_mode))
> + override->val = SRDS_PRTCL_XFI;
> + else if (lynx_lane_mode_uses_gmii_mac(lane_mode))
> + override->val = SRDS_PRTCL_100BASEX_SGMII;
> + else
> + return -EINVAL;
> +
> + return 0;
> +}
> +
> +static int ls1046a_serdes_get_rcw_override(int index, int lane,
> + enum lynx_lane_mode lane_mode,
> + struct fsl_soc_serdes_rcw_override *override)
> +{
> + /* The RCW override procedure has to write to different registers
> + * depending on the SerDes block index.
> + */
> + switch (index) {
> + case 1:
> + override->offset = 4;
> + override->mask = LS1046A_RCWSR5_SRDS_PRTCL_S1(lane);
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + if (lynx_lane_mode_uses_xgmii_mac(lane_mode))
> + override->val = SRDS_PRTCL_XFI;
> + else if (lynx_lane_mode_uses_gmii_mac(lane_mode))
> + override->val = SRDS_PRTCL_100BASEX_SGMII;
> + else
> + return -EINVAL;
> +
> + return 0;
> +}
> +
> +static int ls2088a_serdes_get_rcw_override(int index, int lane,
> + enum lynx_lane_mode lane_mode,
> + struct fsl_soc_serdes_rcw_override *override)
> +{
> + switch (index) {
> + case 1:
> + override->offset = 29;
> + override->mask = LS2088A_RCWSR30_SRDS_CLK_SEL_XGMII_Ln_S1(lane);
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + if (lynx_lane_mode_uses_xgmii_mac(lane_mode))
> + override->val = SRDS_CLK_SEL_XGMII;
> + else if (lynx_lane_mode_uses_gmii_mac(lane_mode))
> + override->val = SRDS_CLK_SEL_GMII;
> + else
> + return -EINVAL;
> +
> + return 0;
> +}
> +
> +static void ls2088a_serdes_init_rcwcr(int serdes_idx)
> +{
> + u32 reg;
> + int i;
> +
> + if (serdes_idx != 1)
> + return;
> + if (soc.rcwcr_init_done)
> + return;
> +
> + /* SRDS_CLK_EN_SEL_XGMII_S1: SerDes Clock Enable Select XGMII Serdes 1:
> + * Enables to select GMII/XGMII clock according to
> + * SRDS_CLK_SEL_XGMII_Ln_S1
> + */
> + reg = LS2088A_RCWSR30_SRDS_CLK_EN_SEL_XGMII_S1;
> +
> + /* We need to configure the initial state of all lanes for
> + * the SerDes block #1
> + */
> + for (i = 0; i < MAX_NUM_LANES; i++)
> + if (lynx_lane_mode_uses_xgmii_mac(soc.lane_mode[serdes_idx - 1][i]))
> + reg |= LS2088A_RCWSR30_SRDS_CLK_SEL_XGMII_Ln_S1(i);
> +
> + fsl_guts_rmw(29, reg,
> + LS2088A_RCWSR30_SRDS_CLK_EN_SEL_XGMII_S1 |
> + LS2088A_RCWSR30_SRDS_CLK_SEL_MSK);
> +
> + soc.rcwcr_init_done = true;
> +}
> +
> +static const struct fsl_soc_data ls1088a_data = {
> + .serdes_get_rcw_override = ls1088a_serdes_get_rcw_override,
> +};
> +
> +static const struct fsl_soc_data ls1046a_data = {
> + .serdes_get_rcw_override = ls1046a_serdes_get_rcw_override,
> +};
> +
> +static const struct fsl_soc_data ls2088a_data = {
> + .serdes_get_rcw_override = ls2088a_serdes_get_rcw_override,
> + .serdes_init_rcwcr = ls2088a_serdes_init_rcwcr,
> +};
> +
> static const struct fsl_soc_data ls1028a_data = {
> .sfp_compat = "fsl,ls1028a-sfp",
> .uid_offset = 0x21c,
> @@ -221,10 +494,10 @@ static const struct of_device_id fsl_guts_of_match[] = {
> { .compatible = "fsl,mpc8572-guts", },
> { .compatible = "fsl,ls1021a-dcfg", },
> { .compatible = "fsl,ls1043a-dcfg", },
> - { .compatible = "fsl,ls2080a-dcfg", },
> - { .compatible = "fsl,ls1088a-dcfg", },
> + { .compatible = "fsl,ls2080a-dcfg", .data = &ls2088a_data},
> + { .compatible = "fsl,ls1088a-dcfg", .data = &ls1088a_data},
> { .compatible = "fsl,ls1012a-dcfg", },
> - { .compatible = "fsl,ls1046a-dcfg", },
> + { .compatible = "fsl,ls1046a-dcfg", .data = &ls1046a_data},
> { .compatible = "fsl,lx2160a-dcfg", },
> { .compatible = "fsl,ls1028a-dcfg", .data = &ls1028a_data},
> {}
> @@ -250,6 +523,8 @@ static int __init fsl_guts_init(void)
> of_node_put(np);
> return -ENOMEM;
> }
> + /* DCFG_DCSR is optional */
> + soc.dcfg_dcsr = of_iomap(np, DCFG_DCSR);
>
> soc.little_endian = of_property_read_bool(np, "little-endian");
> soc.svr = fsl_guts_read(&soc.dcfg_ccsr->svr);
> @@ -296,6 +571,8 @@ static int __init fsl_guts_init(void)
> goto err;
> }
>
> + spin_lock_init(&soc.rcwcr_lock);
> +
> pr_info("Machine: %s\n", soc_dev_attr->machine);
> pr_info("SoC family: %s\n", soc_dev_attr->family);
> pr_info("SoC ID: %s, Revision: %s\n",
> @@ -305,7 +582,8 @@ static int __init fsl_guts_init(void)
>
> err_nomem:
> ret = -ENOMEM;
> -
> + if (soc.dcfg_dcsr)
> + iounmap(soc.dcfg_dcsr);
> iounmap(soc.dcfg_ccsr);
> err:
> kfree(soc_dev_attr->family);
> diff --git a/include/linux/fsl/guts.h b/include/linux/fsl/guts.h
> index fdb55ca47a4f..176842531241 100644
> --- a/include/linux/fsl/guts.h
> +++ b/include/linux/fsl/guts.h
> @@ -13,6 +13,7 @@
>
> #include <linux/types.h>
> #include <linux/io.h>
> +#include <soc/fsl/phy-fsl-lynx.h>
>
> /*
> * Global Utility Registers.
> @@ -91,9 +92,15 @@ struct ccsr_guts {
> u32 iovselsr; /* 0x.00c0 - I/O voltage select status register
> Called 'elbcvselcr' on 86xx SOCs */
> u8 res0c4[0x100 - 0xc4];
> - u32 rcwsr[16]; /* 0x.0100 - Reset Control Word Status registers
> - There are 16 registers */
> - u8 res140[0x224 - 0x140];
> + /* 0x.0100 - read-only Reset Configuration Word Status registers in
> + * CCSR, or write-only Reset Configuration Word Control registers in
> + * DCSR. In both cases there are 32 registers.
> + */
> + union {
> + u32 rcwsr[32];
> + u32 rcwcr[32];
> + };
> + u8 res180[0x224 - 0x180];
> u32 iodelay1; /* 0x.0224 - IO delay control register 1 */
> u32 iodelay2; /* 0x.0228 - IO delay control register 2 */
> u8 res22c[0x604 - 0x22c];
> @@ -131,6 +138,13 @@ struct ccsr_guts {
> u32 srds2cr1; /* 0x.0f44 - SerDes2 Control Register 0 */
> } __attribute__ ((packed));
>
> +void fsl_guts_lane_init(int serdes_idx, int lane,
> + enum lynx_lane_mode lane_mode);
> +int fsl_guts_lane_validate(int serdes_idx, int lane,
> + enum lynx_lane_mode lane_mode);
> +int fsl_guts_lane_set_mode(int serdes_idx, int lane,
> + enum lynx_lane_mode lane_mode);
> +
> /* Alternate function signal multiplex control */
> #define MPC85xx_PMUXCR_QE(x) (0x8000 >> (x))
>
> --
> 2.34.1
>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
next prev parent reply other threads:[~2026-06-12 15:44 UTC|newest]
Thread overview: 13+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-06-11 19:39 [PATCH v1 phy-next 0/8] RCW override for 10G Lynx dynamic protocol reconfiguration Vladimir Oltean
2026-06-11 19:39 ` [PATCH v1 phy-next 1/8] soc: fsl: guts: use a macro to encode the DCFG CCSR space Vladimir Oltean
2026-06-11 19:39 ` [PATCH v1 phy-next 2/8] soc: fsl: guts: add a global structure to hold state Vladimir Oltean
2026-06-11 19:39 ` [PATCH v1 phy-next 3/8] soc: fsl: guts: add a central fsl_guts_read() function Vladimir Oltean
2026-06-11 19:39 ` [PATCH v1 phy-next 4/8] soc: fsl: guts: make it easier to determine on which SoC we are running Vladimir Oltean
2026-06-11 19:39 ` [PATCH v1 phy-next 5/8] soc: fsl: guts: make fsl_soc_data available after fsl_guts_init() Vladimir Oltean
2026-06-11 19:39 ` [PATCH v1 phy-next 6/8] dt-bindings: fsl: layerscape-dcfg: define DCFG_DCSR region Vladimir Oltean
2026-06-12 15:44 ` Conor Dooley
2026-06-12 20:23 ` Vladimir Oltean
2026-06-11 19:39 ` [PATCH v1 phy-next 7/8] soc: fsl: guts: implement the RCW override procedure Vladimir Oltean
2026-06-12 15:44 ` Conor Dooley [this message]
2026-06-12 20:25 ` Vladimir Oltean
2026-06-11 19:39 ` [PATCH v1 phy-next 8/8] phy: lynx-10g: use RCW override procedure for dynamic protocol change Vladimir Oltean
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260612-twenty-diary-4aab5327f9c2@spud \
--to=conor@kernel.org \
--cc=Frank.Li@nxp.com \
--cc=chleroy@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=ioana.ciornei@nxp.com \
--cc=krzysztof.kozlowski+dt@linaro.org \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-phy@lists.infradead.org \
--cc=linuxppc-dev@lists.ozlabs.org \
--cc=mwalle@kernel.org \
--cc=neil.armstrong@linaro.org \
--cc=robh@kernel.org \
--cc=shawnguo@kernel.org \
--cc=tanjeff.moos@westermo.com \
--cc=vkoul@kernel.org \
--cc=vladimir.oltean@nxp.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox