Thread (28 messages) 28 messages, 5 authors, 2026-01-30

Re: [PATCH 3/4] phy: s32g: Add serdes xpcs subsystem

From: Vincent Guittot <vincent.guittot@linaro.org>
Date: 2026-01-29 13:24:28
Also in: linux-arm-kernel, linux-devicetree, linux-phy, lkml

On Thu, 29 Jan 2026 at 12:59, Simon Horman [off-list ref] wrote:
On Mon, Jan 26, 2026 at 10:21:58AM +0100, Vincent Guittot wrote:

...
quoted
diff --git a/drivers/phy/freescale/phy-nxp-s32g-serdes.c b/drivers/phy/freescale/phy-nxp-s32g-serdes.c
...
quoted
+static void s32g_serdes_prepare_pma_mode5(struct s32g_serdes *serdes)
+{
+     u32 val;
+     /* Configure TX_VBOOST_LVL and TX_TERM_CTRL */
+     val = readl(serdes->ctrl.ss_base + S32G_PCIE_PHY_EXT_MISC_CTRL_2);
+     val &= ~(EXT_TX_VBOOST_LVL_MASK | EXT_TX_TERM_CTRL_MASK);
+     val |= FIELD_PREP(EXT_TX_VBOOST_LVL_MASK, 0x3) |
+             FIELD_PREP(EXT_TX_TERM_CTRL_MASK, 0x4);
+     writel(val, serdes->ctrl.ss_base + S32G_PCIE_PHY_EXT_MISC_CTRL_2);
This is part of an AI Generated review.
I have looked over it and I think it warrants investigation.
For information on how to reproduce locally, as I did, please see [1].

        The entire s32g_serdes_prepare_pma_mode5() function is ~70 lines of
        magic numbers with zero explanation. These appear to be
        hardware-specific PLL/PHY tuning parameters for 2.5G mode.
Unfortunately there is no additional information in the reference
manual other than
*step 4:
- Write 3h to EXT_TX_VBOOST_LVL.
- Write 4h to EXT_TX_TERM_CTRL
Please consider using #defines, to give values names.

...
quoted
+static int s32g_serdes_init_clks(struct s32g_serdes *serdes)
+{
+     struct s32g_serdes_ctrl *ctrl = &serdes->ctrl;
+     struct s32g_xpcs_ctrl *xpcs = &serdes->xpcs;
+     int ret, order[2], xpcs_id;
+     size_t i;
+
+     switch (ctrl->ss_mode) {
+     case 0:
+             return 0;
+     case 1:
+             order[0] = 0;
+             order[1] = XPCS_DISABLED;
+             break;
+     case 2:
+     case 5:
+             order[0] = 1;
+             order[1] = XPCS_DISABLED;
+             break;
+     case 3:
+             order[0] = 1;
+             order[1] = 0;
+             break;
+     case 4:
+             order[0] = 0;
+             order[1] = 1;
+             break;
+     default:
+             return -EINVAL;
AI review also flags that s32g_serdes_get_ctrl_resources() ensures that
ss_mode is <= 5.  So this check is unnecessary.
okay but providing a default seems a good practice
quoted
+     }
+
+     for (i = 0; i < ARRAY_SIZE(order); i++) {
+             xpcs_id = order[i];
+
+             if (xpcs_id == XPCS_DISABLED)
+                     continue;
+
+             ret = s32g_xpcs_init_plls(xpcs->phys[xpcs_id]);
+             if (ret)
+                     return ret;
+     }
+
+     if (ctrl->ss_mode == 5) {
+             s32g_serdes_prepare_pma_mode5(serdes);
+
+             ret = s32g_xpcs_pre_pcie_2g5(xpcs->phys[1]);
Also from the AI review:

        In mode 5, code directly accesses xpcs->phys[1] without checking if
        it was created. If XPCS1 wasn't created for some reason (DT
        misconfiguration, probe failure), this NULL dereferences.
I will add a check
...
quoted
@@ -460,6 +741,41 @@ static int s32g2_serdes_create_phy(struct s32g_serdes *serdes, struct device_nod
...
quoted
+             xpcs = devm_kmalloc(dev, sizeof(*xpcs), GFP_KERNEL);
+             if (IS_ERR(xpcs)) {
+                     dev_err(dev, "Failed to allocate xpcs\n");
+                     return -ENOMEM;
+             }
AI review also flags:

        devm_kmalloc() returns NULL on failure, not an error pointer.
        This check will never trigger.
okay
[1] https://netdev-ai.bots.linux.dev/ai-local.html
quoted
+
+             xpcs_ctrl->phys[port] = xpcs;
+
+             xpcs->an = of_property_read_bool(dev->of_node, "nxp,xpcs_an");
AI review flags that nxp,xpcs_an is not part of the binding.
That's a leftover that should be removed
...
quoted
+struct phylink_pcs *s32g_serdes_pcs_create(struct device *dev, struct device_node *np)
+{
+     struct platform_device *pdev;
+     struct device_node *pcs_np;
+     struct s32g_serdes *serdes;
+     u32 port;
+
+     if (of_property_read_u32(np, "reg", &port))
+             return ERR_PTR(-EINVAL);
+
+     if (port > S32G_SERDES_XPCS_MAX)
+             return ERR_PTR(-EINVAL);
+
+     /* The PCS pdev is attached to the parent node */
+     pcs_np = of_get_parent(np);
+     if (!pcs_np)
+             return ERR_PTR(-ENODEV);
+
+     if (!of_device_is_available(pcs_np)) {
+             of_node_put(pcs_np);
+             return ERR_PTR(-ENODEV);
+     }
+
+     pdev = of_find_device_by_node(pcs_np);
+     of_node_put(pcs_np);
+     if (!pdev || !platform_get_drvdata(pdev)) {
+             if (pdev)
+                     put_device(&pdev->dev);
+             return ERR_PTR(-EPROBE_DEFER);
+     }
+
+     serdes = platform_get_drvdata(pdev);
Also from the AI review:

        On success, the function gets a reference to pdev but never
        releases it. This leaks a device reference every time a MAC driver
        calls s32g_serdes_pcs_create().
will fix it
quoted
+
+     return &serdes->xpcs.phys[port]->pcs;
Also from the AI review:

        The check port > S32G_SERDES_XPCS_MAX allows port=2,
        but array only has indices 0 and 1.

Also I'm not seeing bounds checking on port in s32g2_serdes_create_phy
which uses port as an index for the same array both directly and indirectly
via s32g_serdes_xpcs_init().
will add it
...
quoted
diff --git a/drivers/phy/freescale/phy-nxp-s32g-xpcs.c b/drivers/phy/freescale/phy-nxp-s32g-xpcs.c
...
quoted
+static int s32g_xpcs_regmap_reg_read(void *context, unsigned int reg,
+                                  unsigned int *result)
+{
+     struct s32g_xpcs *xpcs = context;
+     u16 ofsleft = (reg >> 8) & 0xffffU;
+     u16 ofsright = (reg & 0xffU);
+
+     writew(ofsleft, xpcs->base + ADDR1_OFS);
+     *result = readw(xpcs->base + (ofsright * 4));
+
+     return 0;
+}
+
+static int s32g_xpcs_regmap_reg_write(void *context, unsigned int reg,
+                                   unsigned int val)
+{
+     struct s32g_xpcs *xpcs = context;
+     u16 ofsleft = (reg >> 8) & 0xffffU;
+     u16 ofsright = (reg & 0xffU);
+
+     writew(ofsleft, xpcs->base + ADDR1_OFS);
+     writew(val, xpcs->base + (ofsright * 4));
+
+     return 0;
+}
+
+static const struct regmap_config s32g_xpcs_regmap_config = {
+     .reg_bits = 16,
+     .val_bits = 16,
+     .reg_read = s32g_xpcs_regmap_reg_read,
+     .reg_write = s32g_xpcs_regmap_reg_write,
+     .wr_table = &s32g_xpcs_wr_table,
+     .rd_table = &s32g_xpcs_rd_table,
+     .max_register = 0x1F80E1,
+};
AI review also flags that s32g_xpcs_regmap_reg_read and
s32g_xpcs_regmap_reg_write do not protect against concurrent access.
but regmap framework should
...
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help