* [PATCH net-next v12 12/12] net: airoha: add phylink support
2026-08-09 20:30 [PATCH net-next v12 00/12] net: pcs: Introduce support for fwnode PCS Christian Marangi
@ 2026-08-09 20:31 ` Christian Marangi
2026-08-10 14:53 ` win847
0 siblings, 1 reply; 5+ messages in thread
From: Christian Marangi @ 2026-08-09 20:31 UTC (permalink / raw)
To: Maxime Chevallier, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Simon Horman, Jonathan Corbet, Shuah Khan,
Christian Marangi, Lorenzo Bianconi, Heiner Kallweit,
Russell King, Philipp Zabel, Nathan Chancellor, Nick Desaulniers,
Bill Wendling, Justin Stitt, netdev, devicetree, linux-kernel,
linux-doc, linux-arm-kernel, linux-mediatek, llvm
Add phylink support for each GDM port. For GDM1 add the internal interface
mode as the only supported mode. For GDM2/3/4 add the required
configuration of the PCS to make the external PHY or attached SFP cage
work.
These needs to be defined in the GDM port node using the pcs-handle
property.
Update and provide a .get/set_link_ksettings function that use phylink
for ethtool OPs now that we fully support phylink.
Acked-by: Lorenzo Bianconi <lorenzo@kernel.org>
Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
---
drivers/net/ethernet/airoha/Kconfig | 2 +
drivers/net/ethernet/airoha/airoha_eth.c | 190 +++++++++++++++++++++-
drivers/net/ethernet/airoha/airoha_eth.h | 5 +
drivers/net/ethernet/airoha/airoha_regs.h | 12 ++
4 files changed, 207 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/airoha/Kconfig b/drivers/net/ethernet/airoha/Kconfig
index 1f6640a15fc9..5c54a07d2a76 100644
--- a/drivers/net/ethernet/airoha/Kconfig
+++ b/drivers/net/ethernet/airoha/Kconfig
@@ -20,6 +20,8 @@ config NET_AIROHA
depends on NET_DSA || !NET_DSA
select NET_AIROHA_NPU
select PAGE_POOL
+ select PCS_AIROHA
+ select PHYLINK
help
This driver supports the gigabit ethernet MACs in the
Airoha SoC family.
diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
index 64619e9a704d..9fe8376aca0f 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
@@ -6,6 +6,7 @@
#include <linux/of.h>
#include <linux/of_net.h>
#include <linux/of_reserved_mem.h>
+#include <linux/pcs/pcs.h>
#include <linux/platform_device.h>
#include <linux/tcp.h>
#include <linux/u64_stats_sync.h>
@@ -1915,6 +1916,14 @@ static int airoha_dev_open(struct net_device *netdev)
struct airoha_qdma *qdma;
int err;
+ err = phylink_of_phy_connect(dev->phylink, netdev->dev.of_node, 0);
+ if (err) {
+ netdev_err(netdev, "could not attach PHY: %d\n", err);
+ return err;
+ }
+
+ phylink_start(dev->phylink);
+
netif_tx_start_all_queues(netdev);
err = airoha_set_vip_for_gdm_port(dev, true);
if (err)
@@ -1945,6 +1954,8 @@ static int airoha_dev_stop(struct net_device *netdev)
struct airoha_gdm_dev *dev = netdev_priv(netdev);
struct airoha_gdm_port *port = dev->port;
+ phylink_stop(dev->phylink);
+
netif_tx_disable(netdev);
airoha_set_vip_for_gdm_port(dev, false);
@@ -1954,6 +1965,9 @@ static int airoha_dev_stop(struct net_device *netdev)
airoha_set_gdm_port_fwd_cfg(dev->eth,
REG_GDM_FWD_CFG(port->id),
FE_PSE_PORT_DROP);
+
+ phylink_disconnect_phy(dev->phylink);
+
return 0;
}
@@ -2509,6 +2523,24 @@ airoha_ethtool_get_rmon_stats(struct net_device *netdev,
} while (u64_stats_fetch_retry(&dev->stats.syncp, start));
}
+static int
+airoha_ethtool_get_link_ksettings(struct net_device *netdev,
+ struct ethtool_link_ksettings *cmd)
+{
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+
+ return phylink_ethtool_ksettings_get(dev->phylink, cmd);
+}
+
+static int
+airoha_ethtool_set_link_ksettings(struct net_device *netdev,
+ const struct ethtool_link_ksettings *cmd)
+{
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+
+ return phylink_ethtool_ksettings_set(dev->phylink, cmd);
+}
+
static int airoha_qdma_set_chan_tx_sched(struct net_device *netdev,
int channel, enum tx_sched_mode mode,
const u16 *weights, u8 n_weights)
@@ -3353,7 +3385,8 @@ static const struct ethtool_ops airoha_ethtool_ops = {
.get_drvinfo = airoha_ethtool_get_drvinfo,
.get_eth_mac_stats = airoha_ethtool_get_mac_stats,
.get_rmon_stats = airoha_ethtool_get_rmon_stats,
- .get_link_ksettings = phy_ethtool_get_link_ksettings,
+ .get_link_ksettings = airoha_ethtool_get_link_ksettings,
+ .set_link_ksettings = airoha_ethtool_set_link_ksettings,
.get_link = ethtool_op_get_link,
};
@@ -3409,6 +3442,155 @@ bool airoha_is_valid_gdm_dev(struct airoha_eth *eth,
return false;
}
+/* Nothing to do in MAC, everything is handled in PCS */
+static void airoha_mac_config(struct phylink_config *config, unsigned int mode,
+ const struct phylink_link_state *state)
+{
+}
+
+static void airoha_mac_link_up(struct phylink_config *config, struct phy_device *phy,
+ unsigned int mode, phy_interface_t interface,
+ int speed, int duplex, bool tx_pause, bool rx_pause)
+{
+ struct airoha_gdm_dev *dev = container_of(config, struct airoha_gdm_dev,
+ phylink_config);
+ struct airoha_gdm_port *port = dev->port;
+ struct airoha_eth *eth = dev->eth;
+ u32 frag_size_tx, frag_size_rx;
+
+ /* TX/RX frag is configured only for GDM4 */
+ if (port->id != AIROHA_GDM4_IDX)
+ return;
+
+ switch (speed) {
+ case SPEED_10000:
+ case SPEED_5000:
+ frag_size_tx = 8;
+ frag_size_rx = 8;
+ break;
+ case SPEED_2500:
+ frag_size_tx = 2;
+ frag_size_rx = 1;
+ break;
+ default:
+ frag_size_tx = 1;
+ frag_size_rx = 0;
+ break;
+ }
+
+ mutex_lock(&port->link_lock);
+
+ /* Configure TX/RX frag based on speed */
+ if (dev->nbq == 1) {
+ airoha_fe_rmw(eth, REG_FE_GDM4_TMBI_FRAG,
+ GDM4_SGMII1_TX_FRAG_SIZE_MASK,
+ FIELD_PREP(GDM4_SGMII1_TX_FRAG_SIZE_MASK,
+ frag_size_tx));
+
+ airoha_fe_rmw(eth, REG_FE_GDM4_RMBI_FRAG,
+ GDM4_SGMII1_RX_FRAG_SIZE_MASK,
+ FIELD_PREP(GDM4_SGMII1_RX_FRAG_SIZE_MASK,
+ frag_size_rx));
+ } else {
+ airoha_fe_rmw(eth, REG_FE_GDM4_TMBI_FRAG,
+ GDM4_SGMII0_TX_FRAG_SIZE_MASK,
+ FIELD_PREP(GDM4_SGMII0_TX_FRAG_SIZE_MASK,
+ frag_size_tx));
+
+ airoha_fe_rmw(eth, REG_FE_GDM4_RMBI_FRAG,
+ GDM4_SGMII0_RX_FRAG_SIZE_MASK,
+ FIELD_PREP(GDM4_SGMII0_RX_FRAG_SIZE_MASK,
+ frag_size_rx));
+ }
+
+ mutex_unlock(&port->link_lock);
+}
+
+/* Nothing to do in MAC, everything is handled in PCS */
+static void airoha_mac_link_down(struct phylink_config *config, unsigned int mode,
+ phy_interface_t interface)
+{
+}
+
+static const struct phylink_mac_ops airoha_phylink_ops = {
+ .mac_config = airoha_mac_config,
+ .mac_link_up = airoha_mac_link_up,
+ .mac_link_down = airoha_mac_link_down,
+};
+
+static int airoha_fill_available_pcs(struct phylink_config *config,
+ struct phylink_pcs **available_pcs,
+ unsigned int num_possible_pcs)
+{
+ struct device *dev = config->dev;
+
+ return fwnode_phylink_pcs_parse(dev_fwnode(dev), available_pcs,
+ num_possible_pcs);
+}
+
+static int airoha_setup_phylink(struct net_device *netdev)
+{
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+ struct device_node *np = netdev->dev.of_node;
+ struct airoha_gdm_port *port = dev->port;
+ struct phylink_config *config;
+ phy_interface_t phy_mode;
+ struct phylink *phylink;
+ int err;
+
+ err = of_get_phy_mode(np, &phy_mode);
+ if (err) {
+ dev_err(&netdev->dev, "incorrect phy-mode\n");
+ return err;
+ }
+
+ config = &dev->phylink_config;
+ config->dev = &netdev->dev;
+ config->type = PHYLINK_NETDEV;
+
+ /*
+ * GDM1 only supports internal for Embedded Switch
+ * and doesn't require a PCS.
+ */
+ if (port->id == AIROHA_GDM1_IDX) {
+ config->mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE |
+ MAC_10000FD;
+
+ __set_bit(PHY_INTERFACE_MODE_INTERNAL,
+ config->supported_interfaces);
+ } else {
+ config->mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE |
+ MAC_10 | MAC_100 | MAC_1000 |
+ MAC_2500FD | MAC_5000FD | MAC_10000FD;
+
+ config->num_possible_pcs = fwnode_phylink_pcs_count(dev_fwnode(config->dev));
+ config->fill_available_pcs = airoha_fill_available_pcs;
+
+ __set_bit(PHY_INTERFACE_MODE_SGMII,
+ config->supported_interfaces);
+ __set_bit(PHY_INTERFACE_MODE_1000BASEX,
+ config->supported_interfaces);
+ __set_bit(PHY_INTERFACE_MODE_2500BASEX,
+ config->supported_interfaces);
+ __set_bit(PHY_INTERFACE_MODE_10GBASER,
+ config->supported_interfaces);
+ __set_bit(PHY_INTERFACE_MODE_USXGMII,
+ config->supported_interfaces);
+
+ phy_interface_copy(config->pcs_interfaces,
+ config->supported_interfaces);
+ }
+
+ phylink = phylink_create(config, of_fwnode_handle(np),
+ phy_mode, &airoha_phylink_ops);
+ if (IS_ERR(phylink))
+ return PTR_ERR(phylink);
+
+ dev->phylink = phylink;
+
+ return 0;
+}
+
static int airoha_alloc_gdm_device(struct airoha_eth *eth,
struct airoha_gdm_port *port,
int nbq, struct device_node *np)
@@ -3474,7 +3656,7 @@ static int airoha_alloc_gdm_device(struct airoha_eth *eth,
dev->nbq = nbq;
port->devs[index] = dev;
- return 0;
+ return airoha_setup_phylink(netdev);
}
static int airoha_alloc_gdm_port(struct airoha_eth *eth,
@@ -3510,6 +3692,7 @@ static int airoha_alloc_gdm_port(struct airoha_eth *eth,
port->id = id;
spin_lock_init(&port->stats_lock);
+ mutex_init(&port->link_lock);
eth->ports[p] = port;
err = airoha_metadata_dst_alloc(port);
@@ -3706,6 +3889,8 @@ static int airoha_probe(struct platform_device *pdev)
netdev = netdev_from_priv(dev);
if (netdev->reg_state == NETREG_REGISTERED)
unregister_netdev(netdev);
+ if (dev->phylink)
+ phylink_destroy(dev->phylink);
of_node_put(netdev->dev.of_node);
}
airoha_metadata_dst_free(port);
@@ -3744,6 +3929,7 @@ static void airoha_remove(struct platform_device *pdev)
netdev = netdev_from_priv(dev);
unregister_netdev(netdev);
+ phylink_destroy(dev->phylink);
of_node_put(netdev->dev.of_node);
}
airoha_metadata_dst_free(port);
diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h
index fa9a8edce22f..83daec003257 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.h
+++ b/drivers/net/ethernet/airoha/airoha_eth.h
@@ -595,6 +595,9 @@ struct airoha_gdm_dev {
* QDMA migration.
*/
spinlock_t txq_lock[AIROHA_NUM_NETDEV_TX_RINGS];
+
+ struct phylink *phylink;
+ struct phylink_config phylink_config;
};
struct airoha_gdm_port {
@@ -604,6 +607,8 @@ struct airoha_gdm_port {
/* protect concurrent hw_stats accesses */
spinlock_t stats_lock;
+ /* protect concurrent GDM4 register access */
+ struct mutex link_lock;
struct metadata_dst *dsa_meta[AIROHA_MAX_DSA_PORTS];
};
diff --git a/drivers/net/ethernet/airoha/airoha_regs.h b/drivers/net/ethernet/airoha/airoha_regs.h
index 442b48c9b991..4ea89057b560 100644
--- a/drivers/net/ethernet/airoha/airoha_regs.h
+++ b/drivers/net/ethernet/airoha/airoha_regs.h
@@ -357,6 +357,18 @@
#define IP_FRAGMENT_PORT_MASK GENMASK(8, 5)
#define IP_FRAGMENT_NBQ_MASK GENMASK(4, 0)
+#define REG_FE_GDM4_TMBI_FRAG 0x2028
+#define GDM4_SGMII1_TX_WEIGHT_MASK GENMASK(31, 26)
+#define GDM4_SGMII1_TX_FRAG_SIZE_MASK GENMASK(25, 16)
+#define GDM4_SGMII0_TX_WEIGHT_MASK GENMASK(15, 10)
+#define GDM4_SGMII0_TX_FRAG_SIZE_MASK GENMASK(9, 0)
+
+#define REG_FE_GDM4_RMBI_FRAG 0x202c
+#define GDM4_SGMII1_RX_WEIGHT_MASK GENMASK(31, 26)
+#define GDM4_SGMII1_RX_FRAG_SIZE_MASK GENMASK(25, 16)
+#define GDM4_SGMII0_RX_WEIGHT_MASK GENMASK(15, 10)
+#define GDM4_SGMII0_RX_FRAG_SIZE_MASK GENMASK(9, 0)
+
#define REG_MC_VLAN_EN 0x2100
#define MC_VLAN_EN_MASK BIT(0)
--
2.53.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH net-next v12 12/12] net: airoha: add phylink support
2026-08-09 20:31 ` [PATCH net-next v12 12/12] net: airoha: add phylink support Christian Marangi
@ 2026-08-10 14:53 ` win847
2026-08-10 14:57 ` Christian Marangi
0 siblings, 1 reply; 5+ messages in thread
From: win847 @ 2026-08-10 14:53 UTC (permalink / raw)
To: Christian Marangi; +Cc: netdev
On Sun, 9 Aug 2026, Christian Marangi wrote:
> Subject: [PATCH net-next v12 12/12] net: airoha: add phylink support
Hi Christian,
Thanks for the series. I have a question about the error path in
`airoha_dev_open()` introduced in this patch, and a suggested cleanup.
In the new code:
err = phylink_of_phy_connect(dev->phylink, netdev->dev.of_node, 0);
if (err) {
netdev_err(netdev, "could not attach PHY: %d\n", err);
return err;
}
phylink_start(dev->phylink);
netif_tx_start_all_queues(netdev);
err = airoha_set_vip_for_gdm_port(dev, true);
if (err)
return err;
If `airoha_set_vip_for_gdm_port(dev, true)` fails after both
`phylink_of_phy_connect()` and `phylink_start()` have already run, we
return `err` directly without doing `phylink_stop()` or
`phylink_disconnect_phy()`. That leaves the phylink in a started /
PHY-connected state. On the next `ndo_open`, `phylink_of_phy_connect()`
will be called again and may fail because the PHY is already connected,
preventing the interface from ever coming back up again.
I understand `airoha_set_vip_for_gdm_port()` currently always returns 0
(based on v12), so today this is a dead path, but it is easy to make the
error handling correct and defensive so a future change to that helper
does not silently break reopen.
Suggested change: add an `err_phy_stop` error label and unwind phylink
before returning:
netif_tx_start_all_queues(netdev);
err = airoha_set_vip_for_gdm_port(dev, true);
if (err)
goto err_phy_stop;
return 0;
err_phy_stop:
netif_tx_stop_all_queues(netdev);
phylink_stop(dev->phylink);
phylink_disconnect_phy(dev->phylink);
return err;
Would you be open to folding this in (or reordering so `phylink_start()`
runs after the VIP setup, leaving fewer error exits that need phylink
teardown)? Happy to write it up as a proper patch if useful.
Best regards,
Wayen
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH net-next v12 12/12] net: airoha: add phylink support
2026-08-10 14:53 ` win847
@ 2026-08-10 14:57 ` Christian Marangi
2026-08-10 15:32 ` win847
0 siblings, 1 reply; 5+ messages in thread
From: Christian Marangi @ 2026-08-10 14:57 UTC (permalink / raw)
To: win847; +Cc: netdev
On Mon, Aug 10, 2026 at 10:53:13PM +0800, win847@gmail.com wrote:
> On Sun, 9 Aug 2026, Christian Marangi wrote:
> > Subject: [PATCH net-next v12 12/12] net: airoha: add phylink support
>
> Hi Christian,
>
> Thanks for the series. I have a question about the error path in
> `airoha_dev_open()` introduced in this patch, and a suggested cleanup.
>
> In the new code:
>
> err = phylink_of_phy_connect(dev->phylink, netdev->dev.of_node, 0);
> if (err) {
> netdev_err(netdev, "could not attach PHY: %d\n", err);
> return err;
> }
>
> phylink_start(dev->phylink);
>
> netif_tx_start_all_queues(netdev);
> err = airoha_set_vip_for_gdm_port(dev, true);
> if (err)
> return err;
>
> If `airoha_set_vip_for_gdm_port(dev, true)` fails after both
> `phylink_of_phy_connect()` and `phylink_start()` have already run, we
> return `err` directly without doing `phylink_stop()` or
> `phylink_disconnect_phy()`. That leaves the phylink in a started /
> PHY-connected state. On the next `ndo_open`, `phylink_of_phy_connect()`
> will be called again and may fail because the PHY is already connected,
> preventing the interface from ever coming back up again.
>
> I understand `airoha_set_vip_for_gdm_port()` currently always returns 0
> (based on v12), so today this is a dead path, but it is easy to make the
> error handling correct and defensive so a future change to that helper
> does not silently break reopen.
>
> Suggested change: add an `err_phy_stop` error label and unwind phylink
> before returning:
>
> netif_tx_start_all_queues(netdev);
> err = airoha_set_vip_for_gdm_port(dev, true);
> if (err)
> goto err_phy_stop;
>
> return 0;
>
> err_phy_stop:
> netif_tx_stop_all_queues(netdev);
> phylink_stop(dev->phylink);
> phylink_disconnect_phy(dev->phylink);
> return err;
>
> Would you be open to folding this in (or reordering so `phylink_start()`
> runs after the VIP setup, leaving fewer error exits that need phylink
> teardown)? Happy to write it up as a proper patch if useful.
>
If you are ok, I can integrate the change in the next revision.
--
Ansuel
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH net-next v12 12/12] net: airoha: add phylink support
[not found] <6a79e6fa.332d3c56.21a33c.b60f@mx.google.com>
@ 2026-08-10 15:14 ` win847
0 siblings, 0 replies; 5+ messages in thread
From: win847 @ 2026-08-10 15:14 UTC (permalink / raw)
To: Christian Marangi; +Cc: netdev
On Mon, Aug 10, 2026 at 10:57:59PM +0800, Christian Marangi wrote:
> If you are ok, I can integrate the change in the next revision.
Thanks Christian, please go ahead and integrate it in the next revision.
Either the `goto err_phy_stop` unwind or moving `phylink_start()` after the
GDM VIP configuration so fewer error exits need teardown works for me —
whichever you find cleaner. Happy to review the diff when you post v13.
Best regards,
Wayen
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH net-next v12 12/12] net: airoha: add phylink support
2026-08-10 14:57 ` Christian Marangi
@ 2026-08-10 15:32 ` win847
0 siblings, 0 replies; 5+ messages in thread
From: win847 @ 2026-08-10 15:32 UTC (permalink / raw)
To: Christian Marangi; +Cc: netdev
On Mon, Aug 10, 2026 at 10:57:59PM +0800, Christian Marangi wrote:
> If you are ok, I can integrate the change in the next revision.
Thanks Christian, please go ahead and integrate it in the next revision.
Either the `goto err_phy_stop` unwind or moving `phylink_start()` after the
GDM VIP configuration so fewer error exits need teardown works for me —
whichever you find cleaner. Happy to review the diff when you post v13.
Best regards,
Wayen
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-08-10 15:33 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
[not found] <6a79e6fa.332d3c56.21a33c.b60f@mx.google.com>
2026-08-10 15:14 ` [PATCH net-next v12 12/12] net: airoha: add phylink support win847
2026-08-09 20:30 [PATCH net-next v12 00/12] net: pcs: Introduce support for fwnode PCS Christian Marangi
2026-08-09 20:31 ` [PATCH net-next v12 12/12] net: airoha: add phylink support Christian Marangi
2026-08-10 14:53 ` win847
2026-08-10 14:57 ` Christian Marangi
2026-08-10 15:32 ` win847
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox