On Thu, Jun 11, 2026 at 10:39:39PM +0300, Vladimir Oltean wrote:
> From: Ioana Ciornei <[email protected]>
> 
> 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 <[email protected]>
> Signed-off-by: Vladimir Oltean <[email protected]>
> ---
> Cc: Conor Dooley <[email protected]>
> Cc: Krzysztof Kozlowski <[email protected]>
> Cc: Rob Herring <[email protected]>
> Cc: [email protected]

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: signature.asc
Description: PGP signature

Reply via email to