From e86d1c0b883e1be99f416089012af36219158c08 Mon Sep 17 00:00:00 2001 From: Siddharth Vadapalli Date: Mon, 6 Apr 2026 18:15:57 +0530 Subject: net: phy: dp83867: default to 2ns delay if unspecified in device-tree Since Linux commit c360eb0c3ccb ("dt-bindings: net: ethernet-controller: Add informative text about RGMII delays"), the interpretation of RGMII delays has changed. Prior to the commit, the RGMII Variant among "rgmii", "rgmii-id", "rgmii-rxid" and "rgmii-txid" clearly specified whether it is the MAC or the PHY that "should" add the delay. However, post that commit, the RGMII Variant only specifies whether or not there is a delay on the PCB traces between the MAC and the PHY, leaving it open as to who adds the delay. Hence, instead of enforcing the existence of the device-tree properties "ti,rx-internal-delay" and "ti,tx-internal-delay", default to a delay of 2ns, while continuing to override this delay with the aforementioned properties, if they exist in the device-tree. This is in line with the Linux driver implementation updated by commit 6bf78849371d ("net: phy: dp83867: use 2ns delay if not specified in DTB"). Signed-off-by: Siddharth Vadapalli Reviewed-by: Anshul Dalal --- drivers/net/phy/dp83867.c | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/drivers/net/phy/dp83867.c b/drivers/net/phy/dp83867.c index 7ce03b59b6a..ebed61de133 100644 --- a/drivers/net/phy/dp83867.c +++ b/drivers/net/phy/dp83867.c @@ -203,32 +203,24 @@ static int dp83867_of_init(struct phy_device *phydev) "Should be 'rgmii-id' to use internal delays\n"); } - /* RX delay *must* be specified if internal delay of RX is used. */ + dp83867->rx_id_delay = DP83867_RGMIIDCTL_2_00_NS; if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID || phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID) { ret = ofnode_read_u32(node, "ti,rx-internal-delay", &dp83867->rx_id_delay); - if (ret) { - pr_debug("ti,rx-internal-delay must be specified\n"); - return ret; - } - if (dp83867->rx_id_delay > DP83867_RGMII_RX_CLK_DELAY_MAX) { + if (!ret && dp83867->rx_id_delay > DP83867_RGMII_RX_CLK_DELAY_MAX) { pr_debug("ti,rx-internal-delay value of %u out of range\n", dp83867->rx_id_delay); return -EINVAL; } } - /* TX delay *must* be specified if internal delay of RX is used. */ + dp83867->tx_id_delay = DP83867_RGMIIDCTL_2_00_NS; if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID || phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID) { ret = ofnode_read_u32(node, "ti,tx-internal-delay", &dp83867->tx_id_delay); - if (ret) { - debug("ti,tx-internal-delay must be specified\n"); - return ret; - } - if (dp83867->tx_id_delay > DP83867_RGMII_TX_CLK_DELAY_MAX) { + if (!ret && dp83867->tx_id_delay > DP83867_RGMII_TX_CLK_DELAY_MAX) { pr_debug("ti,tx-internal-delay value of %u out of range\n", dp83867->tx_id_delay); return -EINVAL; -- cgit v1.3.1 From d6694018eaddefac6aae974f9cec72fd6e58f1bc Mon Sep 17 00:00:00 2001 From: Sebastian Josue Alba Vives Date: Thu, 9 Apr 2026 10:44:40 -0600 Subject: net: nfs: fix buffer overflow in nfs_readlink_reply() nfs_readlink_reply() validates rlen only against the incoming packet length (inherited from CVE-2019-14195), but not against the destination buffer nfs_path_buff[2048]. A malicious NFS server can send a valid READLINK reply where pathlen + rlen exceeds sizeof(nfs_path_buff), overflowing the BSS buffer into adjacent memory. The recent fix in fd6e3d34097f addressed the same overflow class in net/lwip/nfs.c but left the legacy path in net/nfs-common.c unpatched. Add bounds checks before both memcpy calls in nfs_readlink_reply(): - relative path branch: reject if pathlen + rlen >= sizeof(nfs_path_buff) - absolute path branch: reject if rlen >= sizeof(nfs_path_buff) Fixes: cf3a4f1e86 ("net: nfs: Fix CVE-2019-14195") Cc: stable@vger.kernel.org Signed-off-by: Sebastian Alba Vives Reviewed-by: Jerome Forissier --- net/nfs-common.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/net/nfs-common.c b/net/nfs-common.c index 4fbde67a760..72d8fd823e3 100644 --- a/net/nfs-common.c +++ b/net/nfs-common.c @@ -674,11 +674,15 @@ static int nfs_readlink_reply(uchar *pkt, unsigned int len) strcat(nfs_path, "/"); pathlen = strlen(nfs_path); + if (pathlen + rlen >= sizeof(nfs_path_buff)) + return -NFS_RPC_DROP; memcpy(nfs_path + pathlen, (uchar *)&rpc_pkt.u.reply.data[2 + nfsv3_data_offset], rlen); nfs_path[pathlen + rlen] = 0; } else { + if (rlen >= sizeof(nfs_path_buff)) + return -NFS_RPC_DROP; memcpy(nfs_path, (uchar *)&rpc_pkt.u.reply.data[2 + nfsv3_data_offset], rlen); -- cgit v1.3.1 From 4877a07ed3cf75fd6fe1acef0b3140a7b76eb680 Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Tue, 28 Apr 2026 20:14:32 +0200 Subject: net: lwip/wget: missing linefeed in diagnostic output With NET_LWIP wget produces this output with an overlong line and missing white space: => wget $kernel_addr_r http://example.com/ ################################################# 4 GiB540 bytes transferred in 2 ms (263.7 KiB/s) Bytes transferred = 540 (21c hex) Removing the condition on inserting a line feed yields: => wget $kernel_addr_r http://example.com/ ################################################# 4 GiB 540 bytes transferred in 2 ms (263.7 KiB/s) Bytes transferred = 540 (21c hex) Signed-off-by: Heinrich Schuchardt Reviewed-by: Jerome Forissier --- net/lwip/wget.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/net/lwip/wget.c b/net/lwip/wget.c index 008f3b395e7..b6c25be6115 100644 --- a/net/lwip/wget.c +++ b/net/lwip/wget.c @@ -20,7 +20,6 @@ #define SERVER_NAME_SIZE 254 #define HTTP_PORT_DEFAULT 80 #define HTTPS_PORT_DEFAULT 443 -#define PROGRESS_PRINT_STEP_BYTES (100 * 1024) enum done_state { NOT_DONE = 0, @@ -251,9 +250,7 @@ static void httpc_result_cb(void *arg, httpc_result_t httpc_result, if (!elapsed) elapsed = 1; if (!wget_info->silent) { - if (rx_content_len > PROGRESS_PRINT_STEP_BYTES) - printf("\n"); - printf("%u bytes transferred in %lu ms (", rx_content_len, + printf("\n%u bytes transferred in %lu ms (", rx_content_len, elapsed); print_size(rx_content_len / elapsed * 1000, "/s)\n"); printf("Bytes transferred = %lu (%lx hex)\n", ctx->size, -- cgit v1.3.1 From e093a4ecbe0a74083b1baf16ef7c67fcddc437db Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Tue, 28 Apr 2026 20:14:33 +0200 Subject: net: lwip/wget: don't print content size twice If wget_info->silent is set, we should not print anything. If wget_info->silent we print the received content size. Printing the value of the Content-Length header is redundant For chunked transfer no Content-Length header is sent. The content length is returned as HTTPC_CONTENT_LEN_INVALID by the LwIP library. In this case we were incorrectly printing '4 GiB'. Signed-off-by: Heinrich Schuchardt Reviewed-by: Jerome Forissier --- net/lwip/wget.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/net/lwip/wget.c b/net/lwip/wget.c index b6c25be6115..983abe2ccd3 100644 --- a/net/lwip/wget.c +++ b/net/lwip/wget.c @@ -243,8 +243,6 @@ static void httpc_result_cb(void *arg, httpc_result_t httpc_result, putc('#'); ctx->hash_count++; } - puts(" "); - print_size(ctx->content_len, ""); elapsed = get_timer(ctx->start_time); if (!elapsed) -- cgit v1.3.1 From 94625af0119dd7d6fc809ccf4d6e277fe2a4b242 Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Tue, 28 Apr 2026 20:14:34 +0200 Subject: net: lwip/wget: don't print progress bar when silent When the EFI sub-system request to silence output, do not output a progress bar. Signed-off-by: Heinrich Schuchardt Reviewed-by: Jerome Forissier --- net/lwip/wget.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/net/lwip/wget.c b/net/lwip/wget.c index 983abe2ccd3..502c0faebb2 100644 --- a/net/lwip/wget.c +++ b/net/lwip/wget.c @@ -177,6 +177,9 @@ static int store_block(struct wget_ctx *ctx, void *src, u16_t len) ctx->daddr += len; ctx->size += len; + if (wget_info->silent) + return 0; + pos = clamp(ctx->size, 0UL, ctx->content_len); while (ctx->hash_count < pos * 50 / ctx->content_len) { @@ -239,9 +242,11 @@ static void httpc_result_cb(void *arg, httpc_result_t httpc_result, } /* Print hash marks for the last packet received */ - while (ctx->hash_count < 49) { - putc('#'); - ctx->hash_count++; + if (!wget_info->silent) { + while (ctx->hash_count < 49) { + putc('#'); + ctx->hash_count++; + } } elapsed = get_timer(ctx->start_time); -- cgit v1.3.1 From 845c55dde827b497eb6f5264c0b55324bafa4183 Mon Sep 17 00:00:00 2001 From: "Markus Schneider-Pargmann (TI)" Date: Fri, 10 Apr 2026 13:04:03 +0200 Subject: net: cpsw: Fix error message It should complain about mac_control here. Signed-off-by: Markus Schneider-Pargmann (TI) --- drivers/net/ti/cpsw.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ti/cpsw.c b/drivers/net/ti/cpsw.c index d7746f454ba..6bfdbb60091 100644 --- a/drivers/net/ti/cpsw.c +++ b/drivers/net/ti/cpsw.c @@ -1177,7 +1177,7 @@ static int cpsw_eth_of_to_plat(struct udevice *dev) ret = dev_read_u32(dev, "mac_control", &data->mac_control); if (ret) { - printf("error: ale_entries not found in dt\n"); + printf("error: mac_control not found in dt\n"); return ret; } -- cgit v1.3.1 From 63f6f88bb0d3794d34e849086f7643cb877d6e1d Mon Sep 17 00:00:00 2001 From: "Markus Schneider-Pargmann (TI)" Date: Fri, 10 Apr 2026 13:04:04 +0200 Subject: net: cpsw: Remove compat string argument The string is already in the priv struct, remove it from the argument list. Signed-off-by: Markus Schneider-Pargmann (TI) --- drivers/net/ti/cpsw.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/net/ti/cpsw.c b/drivers/net/ti/cpsw.c index 6bfdbb60091..c844416d50c 100644 --- a/drivers/net/ti/cpsw.c +++ b/drivers/net/ti/cpsw.c @@ -1064,9 +1064,10 @@ static void cpsw_gmii_sel_dra7xx(struct cpsw_priv *priv, writel(reg, priv->data->gmii_sel); } -static void cpsw_phy_sel(struct cpsw_priv *priv, const char *compat, - phy_interface_t phy_mode) +static void cpsw_phy_sel(struct cpsw_priv *priv, phy_interface_t phy_mode) { + const char *compat = priv->data->phy_sel_compat; + if (!strcmp(compat, "ti,am3352-cpsw-phy-sel")) cpsw_gmii_sel_am3352(priv, phy_mode); if (!strcmp(compat, "ti,am43xx-cpsw-phy-sel")) @@ -1084,8 +1085,7 @@ static int cpsw_eth_probe(struct udevice *dev) priv->data = pdata->priv_pdata; ti_cm_get_macid(dev, priv->data, pdata->enetaddr); /* Select phy interface in control module */ - cpsw_phy_sel(priv, priv->data->phy_sel_compat, - pdata->phy_interface); + cpsw_phy_sel(priv, pdata->phy_interface); return _cpsw_register(priv); } -- cgit v1.3.1 From a1bc52f4894a57c3753e24a0e85658010681f5b9 Mon Sep 17 00:00:00 2001 From: "Markus Schneider-Pargmann (TI)" Date: Fri, 10 Apr 2026 13:04:05 +0200 Subject: net: cpsw: Use driver data for phy_sel Use driver data to pass the correct gmii_sel function. This way new compatibles don't need manual compatible matching as is done in cpsw_phy_sel(). Signed-off-by: Markus Schneider-Pargmann (TI) --- drivers/net/ti/cpsw.c | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/drivers/net/ti/cpsw.c b/drivers/net/ti/cpsw.c index c844416d50c..b1fb009822e 100644 --- a/drivers/net/ti/cpsw.c +++ b/drivers/net/ti/cpsw.c @@ -216,6 +216,10 @@ struct cpsw_priv { u32 phy_mask; }; +struct cpsw_driver_data { + void (*gmii_sel)(struct cpsw_priv *priv, phy_interface_t phy_mode); +}; + static inline int cpsw_ale_get_field(u32 *ale_entry, u32 start, u32 bits) { int idx; @@ -1067,6 +1071,13 @@ static void cpsw_gmii_sel_dra7xx(struct cpsw_priv *priv, static void cpsw_phy_sel(struct cpsw_priv *priv, phy_interface_t phy_mode) { const char *compat = priv->data->phy_sel_compat; + const struct cpsw_driver_data *drv_data = + (const struct cpsw_driver_data *)dev_get_driver_data(priv->dev); + + if (drv_data && drv_data->gmii_sel) { + drv_data->gmii_sel(priv, phy_mode); + return; + } if (!strcmp(compat, "ti,am3352-cpsw-phy-sel")) cpsw_gmii_sel_am3352(priv, phy_mode); @@ -1222,13 +1233,6 @@ static int cpsw_eth_of_to_plat(struct udevice *dev) if (ofnode_read_bool(subnode, "rmii-clock-ext")) data->rmii_clock_external = true; - - data->phy_sel_compat = ofnode_read_string(subnode, - "compatible"); - if (!data->phy_sel_compat) { - pr_err("Not able to get gmii_sel compatible\n"); - return -ENOENT; - } } } @@ -1253,9 +1257,19 @@ static int cpsw_eth_of_to_plat(struct udevice *dev) return 0; } +static const struct cpsw_driver_data cpsw_data_am3352 = { + .gmii_sel = cpsw_gmii_sel_am3352, +}; + +static const struct cpsw_driver_data cpsw_data_dra7xx = { + .gmii_sel = cpsw_gmii_sel_dra7xx, +}; + static const struct udevice_id cpsw_eth_ids[] = { - { .compatible = "ti,cpsw" }, - { .compatible = "ti,am335x-cpsw" }, + { .compatible = "ti,cpsw", .data = (ulong)&cpsw_data_am3352 }, + { .compatible = "ti,am335x-cpsw", .data = (ulong)&cpsw_data_am3352 }, + { .compatible = "ti,am4372-cpsw", .data = (ulong)&cpsw_data_am3352 }, + { .compatible = "ti,dra7-cpsw", .data = (ulong)&cpsw_data_dra7xx }, { } }; #endif -- cgit v1.3.1 From 5bb7ae8921eea48e3fa1a810dc5668123ceacb38 Mon Sep 17 00:00:00 2001 From: "Markus Schneider-Pargmann (TI)" Date: Fri, 10 Apr 2026 13:04:06 +0200 Subject: net: cpsw: Support new cpsw-switch DT bindings Upstream devicetrees use a newer DT binding using cpsw-switch compatibles. The bindings are a bit different, so two functions are introduced to capture the differences, cpsw_eth_of_to_plat_switch() and cpsw_eth_of_to_plat_legacy(). Signed-off-by: Markus Schneider-Pargmann (TI) --- drivers/net/ti/cpsw.c | 148 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 115 insertions(+), 33 deletions(-) diff --git a/drivers/net/ti/cpsw.c b/drivers/net/ti/cpsw.c index b1fb009822e..7a7cb83bd98 100644 --- a/drivers/net/ti/cpsw.c +++ b/drivers/net/ti/cpsw.c @@ -33,6 +33,7 @@ #define PKT_MAX (1500 + 14 + 4 + 4) #define CLEAR_BIT 1 #define GIGABITEN BIT(7) +#define GMII_EN BIT(5) #define FULLDUPLEXEN BIT(0) #define MIIEN BIT(15) #define CTL_EXT_EN BIT(18) @@ -1133,33 +1134,13 @@ static void cpsw_eth_of_parse_slave(struct cpsw_platform_data *data, "max-speed", 0); } -static int cpsw_eth_of_to_plat(struct udevice *dev) +static int cpsw_eth_of_to_plat_legacy(struct udevice *dev, + struct cpsw_platform_data *data) { - struct eth_pdata *pdata = dev_get_plat(dev); - struct cpsw_platform_data *data; - struct gpio_desc *mode_gpios; int slave_index = 0; - int num_mode_gpios; ofnode subnode; int ret; - data = calloc(1, sizeof(struct cpsw_platform_data)); - if (!data) - return -ENOMEM; - - pdata->priv_pdata = data; - pdata->iobase = dev_read_addr(dev); - data->version = CPSW_CTRL_VERSION_2; - data->bd_ram_ofs = CPSW_BD_OFFSET; - data->ale_reg_ofs = CPSW_ALE_OFFSET; - data->cpdma_reg_ofs = CPSW_CPDMA_OFFSET; - data->mdio_div = CPSW_MDIO_DIV; - data->host_port_reg_ofs = CPSW_HOST_PORT_OFFSET, - - pdata->phy_interface = -1; - - data->cpsw_base = pdata->iobase; - ret = dev_read_s32(dev, "cpdma_channels", &data->channels); if (ret) { printf("error: cpdma_channels not found in dt\n"); @@ -1192,17 +1173,6 @@ static int cpsw_eth_of_to_plat(struct udevice *dev) return ret; } - num_mode_gpios = gpio_get_list_count(dev, "mode-gpios"); - if (num_mode_gpios > 0) { - mode_gpios = malloc(sizeof(struct gpio_desc) * - num_mode_gpios); - gpio_request_list_by_name(dev, "mode-gpios", mode_gpios, - num_mode_gpios, GPIOD_IS_OUT); - free(mode_gpios); - } - - data->active_slave = dev_read_u32_default(dev, "active_slave", 0); - ofnode_for_each_subnode(subnode, dev_ofnode(dev)) { const char *name; @@ -1236,6 +1206,115 @@ static int cpsw_eth_of_to_plat(struct udevice *dev) } } + return 0; +} + +static int cpsw_eth_of_to_plat_switch(struct udevice *dev, + struct cpsw_platform_data *data) +{ + ofnode eth_ports_node, subnode; + int ret; + + data->channels = 8; + data->ale_entries = 1024; + data->mac_control = GMII_EN; + + eth_ports_node = ofnode_find_subnode(dev_ofnode(dev), "ethernet-ports"); + data->slaves = ofnode_get_child_count(eth_ports_node); + if (!data->slaves) { + pr_err("cpsw: No ethernet-ports defined\n"); + return -EINVAL; + } + + data->slave_data = malloc(sizeof(struct cpsw_slave_data) * data->slaves); + if (!data->slave_data) + return -ENOMEM; + + ofnode_for_each_subnode(subnode, eth_ports_node) { + struct ofnode_phandle_args args; + u32 port_id; + + ret = ofnode_read_u32(subnode, "reg", &port_id); + if (ret || !port_id || port_id > data->slaves) { + pr_err("cpsw: invalid or missing reg in port node\n"); + return -EINVAL; + } + + cpsw_eth_of_parse_slave(data, port_id - 1, subnode); + + if (!data->gmii_sel) { + ret = ofnode_parse_phandle_with_args(subnode, "phys", "#phy-cells", + 0, 0, &args); + if (!ret) + data->gmii_sel = ofnode_get_addr(args.node); + } + } + + if (!data->gmii_sel) { + pr_err("No port specified phys correctly\n"); + return -ENOENT; + } + + ofnode_for_each_subnode(subnode, dev_ofnode(dev)) { + const char *name = ofnode_get_name(subnode); + + if (strncmp(name, "mdio", 4)) + continue; + + data->mdio_base = ofnode_get_addr(subnode); + if (data->mdio_base == FDT_ADDR_T_NONE) { + pr_err("Not able to get MDIO address space\n"); + return -ENOENT; + } + } + + return 0; +} + +static int cpsw_eth_of_to_plat(struct udevice *dev) +{ + struct eth_pdata *pdata = dev_get_plat(dev); + struct cpsw_platform_data *data; + struct gpio_desc *mode_gpios; + int num_mode_gpios; + int ret; + bool switch_dt_bindings = + ofnode_valid(ofnode_find_subnode(dev_ofnode(dev), "ethernet-ports")); + + data = calloc(1, sizeof(struct cpsw_platform_data)); + if (!data) + return -ENOMEM; + + pdata->priv_pdata = data; + pdata->iobase = dev_read_addr(dev); + data->version = CPSW_CTRL_VERSION_2; + data->bd_ram_ofs = CPSW_BD_OFFSET; + data->ale_reg_ofs = CPSW_ALE_OFFSET; + data->cpdma_reg_ofs = CPSW_CPDMA_OFFSET; + data->mdio_div = CPSW_MDIO_DIV; + data->host_port_reg_ofs = CPSW_HOST_PORT_OFFSET; + + pdata->phy_interface = -1; + + data->cpsw_base = pdata->iobase; + + num_mode_gpios = gpio_get_list_count(dev, "mode-gpios"); + if (num_mode_gpios > 0) { + mode_gpios = malloc(sizeof(struct gpio_desc) * num_mode_gpios); + gpio_request_list_by_name(dev, "mode-gpios", mode_gpios, + num_mode_gpios, GPIOD_IS_OUT); + free(mode_gpios); + } + + data->active_slave = dev_read_u32_default(dev, "active_slave", 0); + + if (switch_dt_bindings) + ret = cpsw_eth_of_to_plat_switch(dev, data); + else + ret = cpsw_eth_of_to_plat_legacy(dev, data); + if (ret) + return ret; + data->slave_data[0].slave_reg_ofs = CPSW_SLAVE0_OFFSET; data->slave_data[0].sliver_reg_ofs = CPSW_SLIVER0_OFFSET; @@ -1270,6 +1349,9 @@ static const struct udevice_id cpsw_eth_ids[] = { { .compatible = "ti,am335x-cpsw", .data = (ulong)&cpsw_data_am3352 }, { .compatible = "ti,am4372-cpsw", .data = (ulong)&cpsw_data_am3352 }, { .compatible = "ti,dra7-cpsw", .data = (ulong)&cpsw_data_dra7xx }, + { .compatible = "ti,am335x-cpsw-switch", .data = (ulong)&cpsw_data_am3352 }, + { .compatible = "ti,am4372-cpsw-switch", .data = (ulong)&cpsw_data_am3352 }, + { .compatible = "ti,dra7-cpsw-switch", .data = (ulong)&cpsw_data_dra7xx }, { } }; #endif -- cgit v1.3.1 From 9eca7fd0d3902e30563f54b0e95d8d4c5f23b70c Mon Sep 17 00:00:00 2001 From: "Lucien.Jheng" Date: Sat, 25 Apr 2026 16:06:47 +0800 Subject: phy: add common PHY properties support Add a new PHY_COMMON_PROPS library that provides helper functions for PHY drivers to read standardized polarity properties from the device tree node: - phy_get_rx_polarity() / phy_get_tx_polarity() - phy_get_manual_rx_polarity() / phy_get_manual_tx_polarity() The dt-bindings/phy/phy.h header with PHY_POL_NORMAL, PHY_POL_INVERT, and PHY_POL_AUTO constants is provided via dts/upstream/include, which is already in the build include path. Ported from Merge tag 'phy-for-7.0': git://git.kernel.org/pub/scm/linux/kernel/git/phy/linux-phy Link: https://git.kernel.org/linus/e7556b59ba65179612bce3fa56bb53d1b4fb20db Signed-off-by: Lucien.Jheng --- drivers/phy/Kconfig | 8 + drivers/phy/Makefile | 1 + drivers/phy/phy-common-props.c | 286 +++++++++++++++++++++++++++++++++++ include/linux/phy/phy-common-props.h | 69 +++++++++ 4 files changed, 364 insertions(+) create mode 100644 drivers/phy/phy-common-props.c create mode 100644 include/linux/phy/phy-common-props.h diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index fc4daa00661..5c8ec2b146f 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig @@ -350,4 +350,12 @@ source "drivers/phy/qcom/Kconfig" source "drivers/phy/renesas/Kconfig" source "drivers/phy/starfive/Kconfig" +config PHY_COMMON_PROPS + bool "Common PHY properties support" + help + Enable support for common PHY properties defined in the device tree, + such as rx-polarity and tx-polarity. This provides helpers for PHY + drivers to read polarity and other standard PHY properties from the + device tree node. + endmenu diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile index 684e9a99af8..e46c362878d 100644 --- a/drivers/phy/Makefile +++ b/drivers/phy/Makefile @@ -43,6 +43,7 @@ obj-$(CONFIG_PHY_NPCM_USB) += phy-npcm-usb.o obj-$(CONFIG_$(PHASE_)PHY_IMX8MQ_USB) += phy-imx8mq-usb.o obj-$(CONFIG_PHY_IMX8M_PCIE) += phy-imx8m-pcie.o obj-$(CONFIG_PHY_XILINX_ZYNQMP) += phy-zynqmp.o +obj-$(CONFIG_PHY_COMMON_PROPS) += phy-common-props.o obj-y += cadence/ obj-y += ti/ obj-y += qcom/ diff --git a/drivers/phy/phy-common-props.c b/drivers/phy/phy-common-props.c new file mode 100644 index 00000000000..0a0b4439430 --- /dev/null +++ b/drivers/phy/phy-common-props.c @@ -0,0 +1,286 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * phy-common-props.c -- Common PHY properties + * + * Copyright 2025-2026 NXP + */ +#include +#include +#include +#include +#include +#include +#include + +/** + * ofnode_count_u32_prop - Count number of u32 elements in a property + * @node: Device tree node + * @propname: Property name + * + * Return: number of u32 elements, or negative error code + */ +static int ofnode_count_u32_prop(ofnode node, const char *propname) +{ + int size; + + size = ofnode_read_size(node, propname); + if (size < 0) { + pr_debug("%s: property '%s' not found (err=%d)\n", + __func__, propname, size); + return size; + } + + pr_debug("%s: property '%s' has %zu bytes (%zu elements)\n", + __func__, propname, (size_t)size, (size_t)(size / sizeof(u32))); + + return size / sizeof(u32); +} + +/** + * ofnode_get_u32_prop_for_name - Find u32 property by name, or default value + * @node: Device tree node; if invalid or @props_title is absent, @default_val is used + * @name: Property name used as lookup key in @names_title (must not be NULL) + * @props_title: Name of u32 array property holding values + * @names_title: Name of string array property holding lookup keys + * @default_val: Default value if @node is invalid, @props_title is absent, or empty + * @val: Pointer to store the returned value + * + * This function retrieves a u32 value from @props_title based on a name lookup + * in @names_title. The value stored in @val is determined as follows: + * + * - If @node is invalid or @props_title is absent: @default_val is used + * - If @props_title exists but is empty: @default_val is used + * - If @props_title has exactly one element and @names_title is empty: + * that element is used + * - Otherwise: @val is set to the element at the same index where @name is + * found in @names_title. + * - If @name is not found, the function looks for a "default" entry in + * @names_title and uses the corresponding value from @props_title + * + * When both @props_title and @names_title are present, they must have the + * same number of elements (except when @props_title has exactly one element). + * + * Return: zero on success, negative error on failure. + */ +static int ofnode_get_u32_prop_for_name(ofnode node, const char *name, + const char *props_title, + const char *names_title, + unsigned int default_val, + unsigned int *val) +{ + int err, n_props, n_names, idx; + u32 *props; + + if (!name) { + pr_err("Error: Lookup key inside \"%s\" is mandatory\n", + names_title); + return -EINVAL; + } + + pr_debug("%s: looking up '%s' in props='%s' names='%s' default=%u\n", + __func__, name, props_title, names_title, default_val); + + n_props = ofnode_count_u32_prop(node, props_title); + if (n_props < 0) { + /* property is absent */ + pr_debug("%s: '%s' is absent, using default value %u\n", + __func__, props_title, default_val); + *val = default_val; + return 0; + } + if (n_props == 0) { + /* property exists but is empty, use default */ + pr_debug("%s: '%s' is empty, using default value %u\n", + __func__, props_title, default_val); + *val = default_val; + return 0; + } + + n_names = ofnode_read_string_count(node, names_title); + + pr_debug("%s: '%s' has %d elements, '%s' has %d entries\n", + __func__, props_title, n_props, names_title, n_names); + if (n_names >= 0 && n_props != n_names) { + pr_err("Error: mismatch between \"%s\" and \"%s\" property count (%d vs %d)\n", + props_title, names_title, n_props, n_names); + return -EINVAL; + } + + idx = ofnode_stringlist_search(node, names_title, name); + if (idx >= 0) { + pr_debug("%s: found '%s' at index %d in '%s'\n", + __func__, name, idx, names_title); + } else { + pr_debug("%s: '%s' not found in '%s', trying 'default'\n", + __func__, name, names_title); + idx = ofnode_stringlist_search(node, names_title, "default"); + if (idx >= 0) + pr_debug("%s: 'default' entry found at index %d\n", + __func__, idx); + else + pr_debug("%s: 'default' entry not found in '%s'\n", + __func__, names_title); + } + /* + * If the mode name is missing, it can only mean the specified property + * is the default one for all modes, so reject any other property count + * than 1. + */ + if (idx < 0 && n_props != 1) { + pr_err("Error: \"%s\" property has %d elements, but cannot find \"%s\" in \"%s\" and there is no default value\n", + props_title, n_props, name, names_title); + return -EINVAL; + } + + if (n_props == 1) { + pr_debug("%s: single-element '%s', reading directly\n", + __func__, props_title); + err = ofnode_read_u32(node, props_title, val); + if (err) { + pr_debug("%s: failed to read '%s' (err=%d)\n", + __func__, props_title, err); + return err; + } + pr_debug("%s: resolved value %u for name '%s' from '%s'\n", + __func__, *val, name, props_title); + return 0; + } + + /* We implicitly know idx >= 0 here */ + props = calloc(n_props, sizeof(*props)); + if (!props) + return -ENOMEM; + + err = ofnode_read_u32_array(node, props_title, props, n_props); + if (err >= 0) { + *val = props[idx]; + pr_debug("%s: resolved value %u at index %d for name '%s' from '%s'\n", + __func__, *val, idx, name, props_title); + } else { + pr_debug("%s: failed to read u32 array '%s' (err=%d)\n", + __func__, props_title, err); + } + + free(props); + + return err; +} + +/** + * phy_get_polarity_for_mode - Get polarity for a specific PHY mode + * @node: Device tree node + * @mode_name: The name of the PHY mode to look up + * @supported: Bit mask of supported polarity values + * @default_val: Default polarity value if property is missing + * @polarity_prop: Name of the polarity property + * @names_prop: Name of the names property + * @val: Pointer to returned polarity + * + * Return: zero on success, negative error on failure. + */ +static int phy_get_polarity_for_mode(ofnode node, const char *mode_name, + unsigned int supported, + unsigned int default_val, + const char *polarity_prop, + const char *names_prop, + unsigned int *val) +{ + int err; + + pr_debug("%s: querying '%s' for mode '%s' (supported=0x%x, default=%u)\n", + __func__, polarity_prop, mode_name, supported, default_val); + + err = ofnode_get_u32_prop_for_name(node, mode_name, polarity_prop, + names_prop, default_val, val); + if (err) { + pr_debug("%s: '%s' lookup failed for mode '%s' (err=%d)\n", + __func__, polarity_prop, mode_name, err); + return err; + } + + pr_debug("%s: '%s' for mode '%s' = %u\n", + __func__, polarity_prop, mode_name, *val); + + if (!(supported & BIT(*val))) { + pr_err("Error: %d is not a supported value for '%s' element '%s'\n", + *val, polarity_prop, mode_name); + err = -EOPNOTSUPP; + } + + return err; +} + +/** + * phy_get_rx_polarity - Get RX polarity for PHY differential lane + * @node: Pointer to the PHY's device tree node. + * @mode_name: The name of the PHY mode to look up. + * @supported: Bit mask of PHY_POL_NORMAL, PHY_POL_INVERT and PHY_POL_AUTO + * @default_val: Default polarity value if property is missing + * @val: Pointer to returned polarity. + * + * Return: zero on success, negative error on failure. + */ +int phy_get_rx_polarity(ofnode node, const char *mode_name, + unsigned int supported, unsigned int default_val, + unsigned int *val) +{ + return phy_get_polarity_for_mode(node, mode_name, supported, + default_val, "rx-polarity", + "rx-polarity-names", val); +} + +/** + * phy_get_tx_polarity - Get TX polarity for PHY differential lane + * @node: Pointer to the PHY's device tree node. + * @mode_name: The name of the PHY mode to look up. + * @supported: Bit mask of PHY_POL_NORMAL, PHY_POL_INVERT and PHY_POL_AUTO + * @default_val: Default polarity value if property is missing + * @val: Pointer to returned polarity. + * + * Return: zero on success, negative error on failure. + */ +int phy_get_tx_polarity(ofnode node, const char *mode_name, + unsigned int supported, unsigned int default_val, + unsigned int *val) +{ + return phy_get_polarity_for_mode(node, mode_name, supported, + default_val, "tx-polarity", + "tx-polarity-names", val); +} + +/** + * phy_get_manual_rx_polarity - Get manual RX polarity for PHY differential lane + * @node: Pointer to the PHY's device tree node. + * @mode_name: The name of the PHY mode to look up. + * @val: Pointer to returned polarity. + * + * Helper for PHYs which do not support protocols with automatic RX polarity + * detection and correction. + * + * Return: zero on success, negative error on failure. + */ +int phy_get_manual_rx_polarity(ofnode node, const char *mode_name, + unsigned int *val) +{ + return phy_get_rx_polarity(node, mode_name, + BIT(PHY_POL_NORMAL) | BIT(PHY_POL_INVERT), + PHY_POL_NORMAL, val); +} + +/** + * phy_get_manual_tx_polarity - Get manual TX polarity for PHY differential lane + * @node: Pointer to the PHY's device tree node. + * @mode_name: The name of the PHY mode to look up. + * @val: Pointer to returned polarity. + * + * Helper for PHYs without any custom default value for the TX polarity. + * + * Return: zero on success, negative error on failure. + */ +int phy_get_manual_tx_polarity(ofnode node, const char *mode_name, + unsigned int *val) +{ + return phy_get_tx_polarity(node, mode_name, + BIT(PHY_POL_NORMAL) | BIT(PHY_POL_INVERT), + PHY_POL_NORMAL, val); +} diff --git a/include/linux/phy/phy-common-props.h b/include/linux/phy/phy-common-props.h new file mode 100644 index 00000000000..9158851f2e1 --- /dev/null +++ b/include/linux/phy/phy-common-props.h @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * phy-common-props.h -- Common properties for generic PHYs + * + * Copyright 2025-2026 NXP + */ + +#ifndef __PHY_COMMON_PROPS_H +#define __PHY_COMMON_PROPS_H + +#include +#include + +/** + * phy_get_rx_polarity - Get RX polarity for PHY differential lane + * @node: Pointer to the PHY's device tree node. + * @mode_name: The name of the PHY mode to look up. + * @supported: Bit mask of PHY_POL_NORMAL, PHY_POL_INVERT and PHY_POL_AUTO + * @default_val: Default polarity value if property is missing + * @val: Pointer to returned polarity. + * + * Return: zero on success, negative error on failure. + */ +int phy_get_rx_polarity(ofnode node, const char *mode_name, + unsigned int supported, unsigned int default_val, + unsigned int *val); + +/** + * phy_get_tx_polarity - Get TX polarity for PHY differential lane + * @node: Pointer to the PHY's device tree node. + * @mode_name: The name of the PHY mode to look up. + * @supported: Bit mask of PHY_POL_NORMAL, PHY_POL_INVERT and PHY_POL_AUTO + * @default_val: Default polarity value if property is missing + * @val: Pointer to returned polarity. + * + * Return: zero on success, negative error on failure. + */ +int phy_get_tx_polarity(ofnode node, const char *mode_name, + unsigned int supported, unsigned int default_val, + unsigned int *val); + +/** + * phy_get_manual_rx_polarity - Get manual RX polarity for PHY differential lane + * @node: Pointer to the PHY's device tree node. + * @mode_name: The name of the PHY mode to look up. + * @val: Pointer to returned polarity. + * + * Helper for PHYs which do not support protocols with automatic RX polarity + * detection and correction. + * + * Return: zero on success, negative error on failure. + */ +int phy_get_manual_rx_polarity(ofnode node, const char *mode_name, + unsigned int *val); + +/** + * phy_get_manual_tx_polarity - Get manual TX polarity for PHY differential lane + * @node: Pointer to the PHY's device tree node. + * @mode_name: The name of the PHY mode to look up. + * @val: Pointer to returned polarity. + * + * Helper for PHYs without any custom default value for the TX polarity. + * + * Return: zero on success, negative error on failure. + */ +int phy_get_manual_tx_polarity(ofnode node, const char *mode_name, + unsigned int *val); + +#endif /* __PHY_COMMON_PROPS_H */ -- cgit v1.3.1 From c008ffdf61c01d0be99719adc8795ac00c55f90a Mon Sep 17 00:00:00 2001 From: "Lucien.Jheng" Date: Sat, 25 Apr 2026 16:06:48 +0800 Subject: net: phy: airoha: air_en8811: use standard rx-polarity/tx-polarity properties Replace the proprietary airoha,pnswap-rx / airoha,pnswap-tx boolean device tree properties with the standard rx-polarity and tx-polarity properties defined in phy-common-props.yaml. Backward compatibility is maintained by reading the legacy boolean properties first and passing them as the default_pol argument to phy_get_rx/tx_polarity(). If the standard properties are absent the legacy values are used transparently, so existing device trees remain functional without modification. Link: https://git.kernel.org/linus/66d8a334b57e64e43810623b3d88f0ce9745270b Signed-off-by: Lucien.Jheng --- drivers/net/phy/airoha/Kconfig | 1 + drivers/net/phy/airoha/air_en8811.c | 60 +++++++++++++++++++++++++++---------- 2 files changed, 45 insertions(+), 16 deletions(-) diff --git a/drivers/net/phy/airoha/Kconfig b/drivers/net/phy/airoha/Kconfig index da8747939e3..4139df343ad 100644 --- a/drivers/net/phy/airoha/Kconfig +++ b/drivers/net/phy/airoha/Kconfig @@ -7,6 +7,7 @@ config PHY_AIROHA_EN8811 depends on PHY_AIROHA depends on SUPPORTS_FW_LOADER select FW_LOADER + select PHY_COMMON_PROPS help AIROHA EN8811H supported. AIROHA AN8811HB supported. diff --git a/drivers/net/phy/airoha/air_en8811.c b/drivers/net/phy/airoha/air_en8811.c index 0b974472732..32f06dd6dfa 100644 --- a/drivers/net/phy/airoha/air_en8811.c +++ b/drivers/net/phy/airoha/air_en8811.c @@ -23,6 +23,7 @@ #include #include #include +#include /* MII Registers */ #define AIR_AUX_CTRL_STATUS 0x1d @@ -1046,11 +1047,50 @@ static int air_leds_init(struct phy_device *phydev, int num, u16 dur, int mode) return 0; } -static int en8811h_config(struct phy_device *phydev) +static int en8811h_config_serdes_polarity(struct phy_device *phydev) { - struct en8811h_priv *priv = phydev->priv; ofnode node = phy_get_ofnode(phydev); + unsigned int pol, default_pol; u32 pbus_value = 0; + int ret; + + if (!ofnode_valid(node)) + return 0; + + default_pol = PHY_POL_NORMAL; + if (ofnode_read_bool(node, "airoha,pnswap-rx")) + default_pol = PHY_POL_INVERT; + + ret = phy_get_rx_polarity(node, + phy_string_for_interface(phydev->interface), + BIT(PHY_POL_NORMAL) | BIT(PHY_POL_INVERT), + default_pol, &pol); + if (ret) + return ret; + if (pol == PHY_POL_INVERT) + pbus_value |= EN8811H_POLARITY_RX_REVERSE; + + default_pol = PHY_POL_NORMAL; + if (ofnode_read_bool(node, "airoha,pnswap-tx")) + default_pol = PHY_POL_INVERT; + + ret = phy_get_tx_polarity(node, + phy_string_for_interface(phydev->interface), + BIT(PHY_POL_NORMAL) | BIT(PHY_POL_INVERT), + default_pol, &pol); + if (ret) + return ret; + if (pol == PHY_POL_NORMAL) + pbus_value |= EN8811H_POLARITY_TX_NORMAL; + + return air_buckpbus_reg_modify(phydev, EN8811H_POLARITY, + EN8811H_POLARITY_RX_REVERSE | + EN8811H_POLARITY_TX_NORMAL, pbus_value); +} + +static int en8811h_config(struct phy_device *phydev) +{ + struct en8811h_priv *priv = phydev->priv; int ret = 0; /* If restart happened in .probe(), no need to restart now */ @@ -1081,20 +1121,8 @@ static int en8811h_config(struct phy_device *phydev) if (ret < 0) return ret; - /* Serdes polarity */ - pbus_value = 0; - if (ofnode_read_bool(node, "airoha,pnswap-rx")) - pbus_value |= EN8811H_POLARITY_RX_REVERSE; - else - pbus_value &= ~EN8811H_POLARITY_RX_REVERSE; - if (ofnode_read_bool(node, "airoha,pnswap-tx")) - pbus_value &= ~EN8811H_POLARITY_TX_NORMAL; - else - pbus_value |= EN8811H_POLARITY_TX_NORMAL; - ret = air_buckpbus_reg_modify(phydev, EN8811H_POLARITY, - EN8811H_POLARITY_RX_REVERSE | - EN8811H_POLARITY_TX_NORMAL, - pbus_value); + /* Configure Serdes polarity from device tree */ + ret = en8811h_config_serdes_polarity(phydev); if (ret < 0) return ret; -- cgit v1.3.1 From 615c5367170272b5dda8b7ceae515f18dcfcafbf Mon Sep 17 00:00:00 2001 From: "Lucien.Jheng" Date: Sat, 25 Apr 2026 16:06:49 +0800 Subject: test: dm: add PHY common props unit tests and sandbox DT nodes Add sandbox DM unit tests for the PHY common properties library and the corresponding device tree test nodes to arch/sandbox/dts/test.dts. Also enable CONFIG_PHY_COMMON_PROPS in configs/sandbox_defconfig so the tests are built and run in the sandbox environment. The test file covers rx/tx polarity lookups for all relevant cases: - missing property (defaults to PHY_POL_NORMAL) - single value without names array (applies to all modes) - count mismatch between values and names arrays (-EINVAL) - name found by exact match - name not found with no "default" fallback (-EINVAL) - name not found with a "default" entry (uses fallback value) - unsupported polarity value (-EOPNOTSUPP) Ported from Linux KUnit test: linux/drivers/phy/phy-common-props-test.c Signed-off-by: Lucien.Jheng --- arch/sandbox/dts/test.dts | 60 +++++++++ configs/sandbox_defconfig | 1 + test/dm/Makefile | 1 + test/dm/phy_common_props.c | 319 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 381 insertions(+) create mode 100644 test/dm/phy_common_props.c diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts index ac92ebf1afd..0887de4333b 100644 --- a/arch/sandbox/dts/test.dts +++ b/arch/sandbox/dts/test.dts @@ -16,6 +16,7 @@ #include #include #include +#include / { model = "sandbox"; @@ -502,6 +503,65 @@ phy-names = "phy1"; }; + /* PHY common props test nodes */ + phy_common_props_missing: phy-common-props-missing { + /* empty: no rx-polarity or tx-polarity properties */ + }; + + phy_common_props_more_values: phy-common-props-more-values { + /* 3 values but only 2 names => count mismatch */ + rx-polarity = ; + rx-polarity-names = "sgmii", "2500base-x"; + tx-polarity = ; + tx-polarity-names = "sgmii", "2500base-x"; + }; + + phy_common_props_single: phy-common-props-single { + /* 1 value, no names array => value applies to all modes */ + rx-polarity = ; + tx-polarity = ; + }; + + phy_common_props_more_names: phy-common-props-more-names { + /* 2 values but 3 names => count mismatch */ + rx-polarity = ; + rx-polarity-names = "sgmii", "2500base-x", "1000base-x"; + tx-polarity = ; + tx-polarity-names = "sgmii", "2500base-x", "1000base-x"; + }; + + phy_common_props_find_by_name: phy-common-props-find-by-name { + /* valid 3-element arrays, lookup by mode name */ + rx-polarity = ; + rx-polarity-names = "sgmii", "2500base-x", "usb-ss"; + tx-polarity = ; + tx-polarity-names = "sgmii", "2500base-x", "1000base-x"; + }; + + phy_common_props_no_default: phy-common-props-no-default { + /* name not in array, no "default" entry => -EINVAL */ + rx-polarity = ; + rx-polarity-names = "2500base-x", "1000base-x"; + tx-polarity = ; + tx-polarity-names = "2500base-x", "1000base-x"; + }; + + phy_common_props_with_default: phy-common-props-with-default { + /* name not in array, but "default" entry exists */ + rx-polarity = ; + rx-polarity-names = "2500base-x", "default"; + tx-polarity = ; + tx-polarity-names = "2500base-x", "default"; + }; + + phy_common_props_unsupported: phy-common-props-unsupported { + /* PHY_POL_AUTO is not supported for manual-only modes */ + rx-polarity = ; + rx-polarity-names = "sgmii"; + tx-polarity = ; + tx-polarity-names = "sgmii"; + }; + some-bus { #address-cells = <1>; #size-cells = <0>; diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig index f26295103f1..55dc7845544 100644 --- a/configs/sandbox_defconfig +++ b/configs/sandbox_defconfig @@ -285,6 +285,7 @@ CONFIG_PCI_REGION_MULTI_ENTRY=y CONFIG_PCI_FTPCI100=y CONFIG_PCI_SANDBOX=y CONFIG_PHY=y +CONFIG_PHY_COMMON_PROPS=y CONFIG_PHY_SANDBOX=y CONFIG_PINCTRL=y CONFIG_PINCONF=y diff --git a/test/dm/Makefile b/test/dm/Makefile index 771b703b737..d69b0e08d66 100644 --- a/test/dm/Makefile +++ b/test/dm/Makefile @@ -88,6 +88,7 @@ obj-$(CONFIG_P2SB) += p2sb.o obj-$(CONFIG_PCI_ENDPOINT) += pci_ep.o obj-$(CONFIG_PCH) += pch.o obj-$(CONFIG_PHY) += phy.o +obj-$(CONFIG_PHY_COMMON_PROPS) += phy_common_props.o ifneq ($(CONFIG_PINMUX),) obj-$(CONFIG_PINCONF) += pinmux.o endif diff --git a/test/dm/phy_common_props.c b/test/dm/phy_common_props.c new file mode 100644 index 00000000000..21f5042b7a0 --- /dev/null +++ b/test/dm/phy_common_props.c @@ -0,0 +1,319 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * U-Boot sandbox DM tests for PHY common props + * + * Ported from Linux KUnit test: + * linux/drivers/phy/phy-common-props-test.c + * + * Copyright 2025-2026 NXP + */ +#include +#include +#include +#include +#include +#include +#include +#include + +/* --- RX polarity tests -------------------------------------------------- */ + +/* Test: rx-polarity property is missing => default PHY_POL_NORMAL */ +static int dm_test_phy_common_props_rx_missing(struct unit_test_state *uts) +{ + ofnode node = ofnode_path("/phy-common-props-missing"); + unsigned int val; + int ret; + + ut_assert(ofnode_valid(node)); + + ret = phy_get_manual_rx_polarity(node, "sgmii", &val); + ut_asserteq(0, ret); + ut_asserteq(PHY_POL_NORMAL, val); + + return 0; +} + +DM_TEST(dm_test_phy_common_props_rx_missing, UTF_SCAN_FDT); + +/* Test: rx-polarity has more values than rx-polarity-names => -EINVAL */ +static int dm_test_phy_common_props_rx_more_values(struct unit_test_state *uts) +{ + ofnode node = ofnode_path("/phy-common-props-more-values"); + unsigned int val; + int ret; + + ut_assert(ofnode_valid(node)); + + ret = phy_get_manual_rx_polarity(node, "sgmii", &val); + ut_asserteq(-EINVAL, ret); + + return 0; +} + +DM_TEST(dm_test_phy_common_props_rx_more_values, UTF_SCAN_FDT); + +/* Test: rx-polarity has 1 value and rx-polarity-names does not exist */ +static int dm_test_phy_common_props_rx_single_value(struct unit_test_state *uts) +{ + ofnode node = ofnode_path("/phy-common-props-single"); + unsigned int val; + int ret; + + ut_assert(ofnode_valid(node)); + + ret = phy_get_manual_rx_polarity(node, "sgmii", &val); + ut_asserteq(0, ret); + ut_asserteq(PHY_POL_INVERT, val); + + return 0; +} + +DM_TEST(dm_test_phy_common_props_rx_single_value, UTF_SCAN_FDT); + +/* Test: rx-polarity-names has more values than rx-polarity => -EINVAL */ +static int dm_test_phy_common_props_rx_more_names(struct unit_test_state *uts) +{ + ofnode node = ofnode_path("/phy-common-props-more-names"); + unsigned int val; + int ret; + + ut_assert(ofnode_valid(node)); + + ret = phy_get_manual_rx_polarity(node, "sgmii", &val); + ut_asserteq(-EINVAL, ret); + + return 0; +} + +DM_TEST(dm_test_phy_common_props_rx_more_names, UTF_SCAN_FDT); + +/* Test: valid arrays, find polarity by mode name */ +static int dm_test_phy_common_props_rx_find_by_name(struct unit_test_state *uts) +{ + ofnode node = ofnode_path("/phy-common-props-find-by-name"); + unsigned int val; + int ret; + + ut_assert(ofnode_valid(node)); + + ret = phy_get_manual_rx_polarity(node, "sgmii", &val); + ut_asserteq(0, ret); + ut_asserteq(PHY_POL_NORMAL, val); + + ret = phy_get_manual_rx_polarity(node, "2500base-x", &val); + ut_asserteq(0, ret); + ut_asserteq(PHY_POL_INVERT, val); + + /* "usb-ss" has PHY_POL_AUTO; auto is supported here */ + ret = phy_get_rx_polarity(node, "usb-ss", BIT(PHY_POL_AUTO), + PHY_POL_AUTO, &val); + ut_asserteq(0, ret); + ut_asserteq(PHY_POL_AUTO, val); + + return 0; +} + +DM_TEST(dm_test_phy_common_props_rx_find_by_name, UTF_SCAN_FDT); + +/* Test: name not found, no "default" entry => -EINVAL */ +static int dm_test_phy_common_props_rx_no_default(struct unit_test_state *uts) +{ + ofnode node = ofnode_path("/phy-common-props-no-default"); + unsigned int val; + int ret; + + ut_assert(ofnode_valid(node)); + + ret = phy_get_manual_rx_polarity(node, "sgmii", &val); + ut_asserteq(-EINVAL, ret); + + return 0; +} + +DM_TEST(dm_test_phy_common_props_rx_no_default, UTF_SCAN_FDT); + +/* Test: name not found, "default" entry exists => use default polarity */ +static int dm_test_phy_common_props_rx_with_default(struct unit_test_state *uts) +{ + ofnode node = ofnode_path("/phy-common-props-with-default"); + unsigned int val; + int ret; + + ut_assert(ofnode_valid(node)); + + ret = phy_get_manual_rx_polarity(node, "sgmii", &val); + ut_asserteq(0, ret); + ut_asserteq(PHY_POL_INVERT, val); + + return 0; +} + +DM_TEST(dm_test_phy_common_props_rx_with_default, UTF_SCAN_FDT); + +/* Test: polarity value found but not in supported set => -EOPNOTSUPP */ +static int dm_test_phy_common_props_rx_unsupported(struct unit_test_state *uts) +{ + ofnode node = ofnode_path("/phy-common-props-unsupported"); + unsigned int val; + int ret; + + ut_assert(ofnode_valid(node)); + + ret = phy_get_manual_rx_polarity(node, "sgmii", &val); + ut_asserteq(-EOPNOTSUPP, ret); + + return 0; +} + +DM_TEST(dm_test_phy_common_props_rx_unsupported, UTF_SCAN_FDT); + +/* --- TX polarity tests -------------------------------------------------- */ + +/* Test: tx-polarity property is missing => default PHY_POL_NORMAL */ +static int dm_test_phy_common_props_tx_missing(struct unit_test_state *uts) +{ + ofnode node = ofnode_path("/phy-common-props-missing"); + unsigned int val; + int ret; + + ut_assert(ofnode_valid(node)); + + ret = phy_get_manual_tx_polarity(node, "sgmii", &val); + ut_asserteq(0, ret); + ut_asserteq(PHY_POL_NORMAL, val); + + return 0; +} + +DM_TEST(dm_test_phy_common_props_tx_missing, UTF_SCAN_FDT); + +/* Test: tx-polarity has more values than tx-polarity-names => -EINVAL */ +static int dm_test_phy_common_props_tx_more_values(struct unit_test_state *uts) +{ + ofnode node = ofnode_path("/phy-common-props-more-values"); + unsigned int val; + int ret; + + ut_assert(ofnode_valid(node)); + + ret = phy_get_manual_tx_polarity(node, "sgmii", &val); + ut_asserteq(-EINVAL, ret); + + return 0; +} + +DM_TEST(dm_test_phy_common_props_tx_more_values, UTF_SCAN_FDT); + +/* Test: tx-polarity has 1 value and tx-polarity-names does not exist */ +static int dm_test_phy_common_props_tx_single_value(struct unit_test_state *uts) +{ + ofnode node = ofnode_path("/phy-common-props-single"); + unsigned int val; + int ret; + + ut_assert(ofnode_valid(node)); + + ret = phy_get_manual_tx_polarity(node, "sgmii", &val); + ut_asserteq(0, ret); + ut_asserteq(PHY_POL_INVERT, val); + + return 0; +} + +DM_TEST(dm_test_phy_common_props_tx_single_value, UTF_SCAN_FDT); + +/* Test: tx-polarity-names has more values than tx-polarity => -EINVAL */ +static int dm_test_phy_common_props_tx_more_names(struct unit_test_state *uts) +{ + ofnode node = ofnode_path("/phy-common-props-more-names"); + unsigned int val; + int ret; + + ut_assert(ofnode_valid(node)); + + ret = phy_get_manual_tx_polarity(node, "sgmii", &val); + ut_asserteq(-EINVAL, ret); + + return 0; +} + +DM_TEST(dm_test_phy_common_props_tx_more_names, UTF_SCAN_FDT); + +/* Test: valid arrays, find polarity by mode name */ +static int dm_test_phy_common_props_tx_find_by_name(struct unit_test_state *uts) +{ + ofnode node = ofnode_path("/phy-common-props-find-by-name"); + unsigned int val; + int ret; + + ut_assert(ofnode_valid(node)); + + ret = phy_get_manual_tx_polarity(node, "sgmii", &val); + ut_asserteq(0, ret); + ut_asserteq(PHY_POL_NORMAL, val); + + ret = phy_get_manual_tx_polarity(node, "2500base-x", &val); + ut_asserteq(0, ret); + ut_asserteq(PHY_POL_INVERT, val); + + ret = phy_get_manual_tx_polarity(node, "1000base-x", &val); + ut_asserteq(0, ret); + ut_asserteq(PHY_POL_NORMAL, val); + + return 0; +} + +DM_TEST(dm_test_phy_common_props_tx_find_by_name, UTF_SCAN_FDT); + +/* Test: name not found, no "default" entry => -EINVAL */ +static int dm_test_phy_common_props_tx_no_default(struct unit_test_state *uts) +{ + ofnode node = ofnode_path("/phy-common-props-no-default"); + unsigned int val; + int ret; + + ut_assert(ofnode_valid(node)); + + ret = phy_get_manual_tx_polarity(node, "sgmii", &val); + ut_asserteq(-EINVAL, ret); + + return 0; +} + +DM_TEST(dm_test_phy_common_props_tx_no_default, UTF_SCAN_FDT); + +/* Test: name not found, "default" entry exists => use default polarity */ +static int dm_test_phy_common_props_tx_with_default(struct unit_test_state *uts) +{ + ofnode node = ofnode_path("/phy-common-props-with-default"); + unsigned int val; + int ret; + + ut_assert(ofnode_valid(node)); + + ret = phy_get_manual_tx_polarity(node, "sgmii", &val); + ut_asserteq(0, ret); + ut_asserteq(PHY_POL_INVERT, val); + + return 0; +} + +DM_TEST(dm_test_phy_common_props_tx_with_default, UTF_SCAN_FDT); + +/* Test: polarity value found but not in supported set => -EOPNOTSUPP */ +static int dm_test_phy_common_props_tx_unsupported(struct unit_test_state *uts) +{ + ofnode node = ofnode_path("/phy-common-props-unsupported"); + unsigned int val; + int ret; + + ut_assert(ofnode_valid(node)); + + ret = phy_get_manual_tx_polarity(node, "sgmii", &val); + ut_asserteq(-EOPNOTSUPP, ret); + + return 0; +} + +DM_TEST(dm_test_phy_common_props_tx_unsupported, UTF_SCAN_FDT); -- cgit v1.3.1 From 52309be1d56766ac7e0db3af26309b8573ac3bbf Mon Sep 17 00:00:00 2001 From: Rasmus Villemoes Date: Tue, 28 Apr 2026 13:15:32 +0200 Subject: net: phy: adin: add support for the ADIN1200 phy The ADIN1200 chip is register compatible with the ADIN1300, but only supports 10/100 Mbit. Signed-off-by: Rasmus Villemoes --- drivers/net/phy/adin.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/net/phy/adin.c b/drivers/net/phy/adin.c index ce448810ff6..4d42e56dada 100644 --- a/drivers/net/phy/adin.c +++ b/drivers/net/phy/adin.c @@ -10,6 +10,7 @@ #include #include +#define PHY_ID_ADIN1200 0x0283bc20 #define PHY_ID_ADIN1300 0x0283bc30 #define ADIN1300_EXT_REG_PTR 0x10 #define ADIN1300_EXT_REG_DATA 0x11 @@ -263,6 +264,18 @@ static int adin1300_config(struct phy_device *phydev) return genphy_config(phydev); } +U_BOOT_PHY_DRIVER(ADIN1200) = { + .name = "ADIN1200", + .uid = PHY_ID_ADIN1200, + .mask = 0xffffffff, + .features = PHY_BASIC_FEATURES, + .config = adin1300_config, + .startup = genphy_startup, + .shutdown = genphy_shutdown, + .readext = adin_extread, + .writeext = adin_extwrite, +}; + U_BOOT_PHY_DRIVER(ADIN1300) = { .name = "ADIN1300", .uid = PHY_ID_ADIN1300, -- cgit v1.3.1 From 9e23095298d12e086dd3d5d35972eb3e588d190d Mon Sep 17 00:00:00 2001 From: Christian DREHER Date: Tue, 28 Apr 2026 20:04:05 +0200 Subject: net: macb: include arch/clk.h only when necessary It does not exist in my setup (an on-going arm64 SoC), and removing it does not cause any missing declaration, but some code called when CONFIG_CLK is missing calls get_macb_pclk_rate, which is only defined in arch/arm/mach-at91/include/mach/clk.h Signed-off-by: Christian DREHER --- drivers/net/macb.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/net/macb.c b/drivers/net/macb.c index cbf5f605518..719aef39a3f 100644 --- a/drivers/net/macb.c +++ b/drivers/net/macb.c @@ -38,9 +38,13 @@ #include #include #include -#include #include +/* Without CLK, we rely on the arch definition */ +#if !defined(CONFIG_CLK) +#include +#endif + #include "macb.h" DECLARE_GLOBAL_DATA_PTR; -- cgit v1.3.1 From 361bb8f827b094cfff6adb56fc247e8d847209cd Mon Sep 17 00:00:00 2001 From: Christian DREHER Date: Tue, 28 Apr 2026 20:04:06 +0200 Subject: net: macb: use SA1 for MAC filtering on GEM The MACB uses specific address registers (SA Top and Bottom) to filter source or destination MAC addresses. On the Gigabit Ethernet version, SA1B is @0x88. On the non-GEM version, SA1B is @0x98. Before this commit, the code was always writing 0x98. By chance, on GEM, this is the address of SA3B, allowing the driver to work anyway. The motivation for this change is to be able to use the driver on an instance of the GEM with less than 4 SA registers. Signed-off-by: Christian DREHER --- drivers/net/macb.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/net/macb.c b/drivers/net/macb.c index 719aef39a3f..807a038e071 100644 --- a/drivers/net/macb.c +++ b/drivers/net/macb.c @@ -1007,9 +1007,14 @@ static int _macb_write_hwaddr(struct macb_device *macb, unsigned char *enetaddr) /* set hardware address */ hwaddr_bottom = enetaddr[0] | enetaddr[1] << 8 | enetaddr[2] << 16 | enetaddr[3] << 24; - macb_writel(macb, SA1B, hwaddr_bottom); hwaddr_top = enetaddr[4] | enetaddr[5] << 8; - macb_writel(macb, SA1T, hwaddr_top); + if (macb_is_gem(macb)) { + gem_writel(macb, SA1B, hwaddr_bottom); + gem_writel(macb, SA1T, hwaddr_top); + } else { + macb_writel(macb, SA1B, hwaddr_bottom); + macb_writel(macb, SA1T, hwaddr_top); + } return 0; } -- cgit v1.3.1 From d7fe1f4333a50b4a87fad1a22fae4524bba874f3 Mon Sep 17 00:00:00 2001 From: Christian DREHER Date: Tue, 28 Apr 2026 20:04:07 +0200 Subject: net: macb: do not set user_io when it does not exist Cadence Ethernet MAC has a feature named user_io, which provides some input and some output signals for arbitrary purpose in the SoC. From the driver code, I understand that, on Atmel SoC, it is used to drive the PHY mode. At least on Cadence IP7014 r1p12, this feature is optional, and I am working on a SoC that does not instantiate it. The presence of this feature is advertised in DCFG1, this patch merely disables the access to the user_io register based on this information. I did not apply this change to the non-gigabit capable versions of the IP, as I do not have documentation for them, and a new non-gigabit instance is unlikely to appear. I prefer avoiding regressions on old systems. Signed-off-by: Christian DREHER --- drivers/net/macb.c | 51 ++++++++++++++++++++++++++++++++------------------- drivers/net/macb.h | 2 ++ 2 files changed, 34 insertions(+), 19 deletions(-) diff --git a/drivers/net/macb.c b/drivers/net/macb.c index 807a038e071..5cc29ecb214 100644 --- a/drivers/net/macb.c +++ b/drivers/net/macb.c @@ -927,26 +927,39 @@ static int _macb_init(struct udevice *dev, const char *name) /* Check the multi queue and initialize the queue for tx */ gmac_init_multi_queues(macb); - /* - * When the GMAC IP with GE feature, this bit is used to - * select interface between RGMII and GMII. - * When the GMAC IP without GE feature, this bit is used - * to select interface between RMII and MII. + /* This driver uses the user I/O to select the PHY features, + * but some GEM instances come with a fixed configuration and + * no USERIO. */ - if (macb->phy_interface == PHY_INTERFACE_MODE_RGMII || - macb->phy_interface == PHY_INTERFACE_MODE_RGMII_ID || - macb->phy_interface == PHY_INTERFACE_MODE_RGMII_RXID || - macb->phy_interface == PHY_INTERFACE_MODE_RGMII_TXID) - val = macb->config->usrio->rgmii; - else if (macb->phy_interface == PHY_INTERFACE_MODE_RMII) - val = macb->config->usrio->rmii; - else if (macb->phy_interface == PHY_INTERFACE_MODE_MII) - val = macb->config->usrio->mii; - - if (macb->config->caps & MACB_CAPS_USRIO_HAS_CLKEN) - val |= macb->config->usrio->clken; - - gem_writel(macb, USRIO, val); + if (gem_readl(macb, DCFG1) & GEM_BIT(USERIO)) { + /* + * When the GMAC IP with GE feature, this bit is used to + * select interface between RGMII and GMII. + * When he GMAC IP without GE feature, this bit is used + * to select interface between RMII and MII. + */ + switch (macb->phy_interface) { + case PHY_INTERFACE_MODE_RGMII: + case PHY_INTERFACE_MODE_RGMII_ID: + case PHY_INTERFACE_MODE_RGMII_RXID: + case PHY_INTERFACE_MODE_RGMII_TXID: + val = macb->config->usrio->rgmii; + break; + case PHY_INTERFACE_MODE_RMII: + val = macb->config->usrio->rmii; + break; + case PHY_INTERFACE_MODE_MII: + val = macb->config->usrio->mii; + break; + default: + break; + } + + if (macb->config->caps & MACB_CAPS_USRIO_HAS_CLKEN) + val |= macb->config->usrio->clken; + + gem_writel(macb, USRIO, val); + } if (macb->phy_interface == PHY_INTERFACE_MODE_SGMII) { unsigned int ncfgr = macb_readl(macb, NCFGR); diff --git a/drivers/net/macb.h b/drivers/net/macb.h index 0eb90574618..002d5bd31b2 100644 --- a/drivers/net/macb.h +++ b/drivers/net/macb.h @@ -443,6 +443,8 @@ #define MACB_REV_SIZE 16 /* Bitfields in DCFG1. */ +#define GEM_USERIO_OFFSET 9 +#define GEM_USERIO_SIZE 1 #define GEM_IRQCOR_OFFSET 23 #define GEM_IRQCOR_SIZE 1 #define GEM_DBWDEF_OFFSET 25 -- cgit v1.3.1 From 9717831e293708a8e4dcba0eaa40cd3b6afe78f6 Mon Sep 17 00:00:00 2001 From: Christian DREHER Date: Tue, 28 Apr 2026 20:04:08 +0200 Subject: net: macb: add gigabit implementation for fixed-link A fixed gigabit link on a non-gigabit controller is only rejected during PHY init (even though there is no PHY to init), because, on device-tree parsing, the controller is not probed, and it is still unknown whether it is gigabit-capable. This code was only tested on emulator with a full-duplex RGMII interface, but is expected to work in GMII or half-duplex as well. Signed-off-by: Christian DREHER --- drivers/net/macb.c | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/drivers/net/macb.c b/drivers/net/macb.c index 5cc29ecb214..bea1dfed892 100644 --- a/drivers/net/macb.c +++ b/drivers/net/macb.c @@ -772,18 +772,40 @@ static int macb_phy_init(struct udevice *dev, const char *name) lpa); } else { /* if macb port is a fixed link */ - /* TODO : manage gigabit capable processors */ + const char *human_readable_speed; + speed = macb->speed; duplex = macb->duplex; + switch (speed) { + case 2: + human_readable_speed = "1000"; + break; + case 1: + human_readable_speed = "100"; + break; + case 0: + human_readable_speed = "10"; + break; + default: + printf("%s: speed %d not supported\n", name, speed); + return -EINVAL; + } printf("%s: link up, %sMbps %s-duplex\n", name, - speed ? "100" : "10", + human_readable_speed, duplex ? "full" : "half"); } ncfgr = macb_readl(macb, NCFGR); ncfgr &= ~(MACB_BIT(SPD) | MACB_BIT(FD) | GEM_BIT(GBE)); - if (speed) { + if (speed == 2) { + if (!gem_is_gigabit_capable(macb)) { + printf("%s: is not gigabit Ethernet capable\n", name); + return -EINVAL; + } + ncfgr |= GEM_BIT(GBE); + ret = macb_linkspd_cb(dev, _1000BASET); + } else if (speed == 1) { ncfgr |= MACB_BIT(SPD); ret = macb_linkspd_cb(dev, _100BASET); } else { @@ -1329,7 +1351,9 @@ static int macb_eth_of_to_plat(struct udevice *dev) macb->phy_addr = PHY_MAX_ADDR + 1; macb->duplex = fdtdec_get_bool(blob, fl_node, "full-duplex"); speed_fdt = fdtdec_get_int(blob, fl_node, "speed", 0); - if (speed_fdt == 100) { + if (speed_fdt == 1000) { + macb->speed = 2; + } else if (speed_fdt == 100) { macb->speed = 1; } else if (speed_fdt == 10) { macb->speed = 0; -- cgit v1.3.1 From 5245bdc98b9fff46e4bcec2e44e915be44824537 Mon Sep 17 00:00:00 2001 From: Charles Perry Date: Tue, 5 May 2026 06:57:49 -0700 Subject: net: phy: mscc: add support for the VSC8572 This is similar to the VSC8574 according to the Linux commit that adds support for it [1]. This was tested on an HX1000 board with SGMII (PIC64-HX SoC which has a GEM MAC). [1]: https://lore.kernel.org/all/dfabe39a52efcd2cfff9358f271b8673143503b8.1480497966.git.neill.whillans@codethink.co.uk/ Signed-off-by: Charles Perry Reviewed-by: Manikandan Muralidharan --- drivers/net/phy/mscc.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/net/phy/mscc.c b/drivers/net/phy/mscc.c index a65e81dff0c..d96970949bc 100644 --- a/drivers/net/phy/mscc.c +++ b/drivers/net/phy/mscc.c @@ -23,6 +23,7 @@ #define PHY_ID_VSC8502 0x00070630 #define PHY_ID_VSC8540 0x00070760 #define PHY_ID_VSC8541 0x00070770 +#define PHY_ID_VSC8572 0x000704d0 #define PHY_ID_VSC8574 0x000704a0 #define PHY_ID_VSC8584 0x000707c0 @@ -1612,6 +1613,16 @@ U_BOOT_PHY_DRIVER(vsc8541) = { .shutdown = &genphy_shutdown, }; +U_BOOT_PHY_DRIVER(vsc8572) = { + .name = "Microsemi VSC8572", + .uid = PHY_ID_VSC8572, + .mask = 0x000ffff0, + .features = PHY_GBIT_FEATURES, + .config = &vsc8574_config, + .startup = &mscc_startup, + .shutdown = &genphy_shutdown, +}; + U_BOOT_PHY_DRIVER(vsc8574) = { .name = "Microsemi VSC8574", .uid = PHY_ID_VSC8574, -- cgit v1.3.1