From: Jens Emil Schulz Ostergaard <jensemil.schulzostergaard@microchip.com>
To: Vladimir Oltean <olteanv@gmail.com>
Cc: <UNGLinuxDriver@microchip.com>, Andrew Lunn <andrew@lunn.ch>,
"David S. Miller" <davem@davemloft.net>,
Eric Dumazet <edumazet@google.com>,
"Jakub Kicinski" <kuba@kernel.org>,
Paolo Abeni <pabeni@redhat.com>, Simon Horman <horms@kernel.org>,
Rob Herring <robh@kernel.org>,
Krzysztof Kozlowski <krzk+dt@kernel.org>,
Conor Dooley <conor+dt@kernel.org>,
Woojung Huh <woojung.huh@microchip.com>,
Russell King <linux@armlinux.org.uk>,
"Steen Hegelund" <Steen.Hegelund@microchip.com>,
Daniel Machon <daniel.machon@microchip.com>,
<linux-kernel@vger.kernel.org>, <netdev@vger.kernel.org>,
<devicetree@vger.kernel.org>
Subject: Re: [PATCH net-next 4/8] net: dsa: lan9645x: add basic dsa driver for LAN9645X
Date: Wed, 4 Mar 2026 15:37:07 +0100 [thread overview]
Message-ID: <64129f9c14b5d0d09f02a977dcfcaaafeb62d14c.camel@microchip.com> (raw)
In-Reply-To: <20260303141528.yx6r4zkm6izouvzg@skbuf>
On Tue, 2026-03-03 at 16:15 +0200, Vladimir Oltean wrote:
> EXTERNAL EMAIL: Do not click links or open attachments unless you know the content is safe
>
> On Tue, Mar 03, 2026 at 01:22:30PM +0100, Jens Emil Schulz Østergaard wrote:
> > Add the LAN9645X basic DSA driver with initialization, parent regmap
> > requests, port module initialization for NPI, CPU ports and front ports,
> > and phylink integration for MAC side configuration.
> >
> > IPv6 is disabled on the conduit. When enabled, the RFC 4861 frames are
> > dispatched directly on the conduit bypassing the tag driver. The switch
> > parses these frames as if they have an IFH prepended, leading to a
> > garbage in garbage out situation. Therefore, IPv6 on the conduit is not
> > a sensible configuration.
> >
> > Reviewed-by: Steen Hegelund <Steen.Hegelund@microchip.com>
> > Signed-off-by: Jens Emil Schulz Østergaard <jensemil.schulzostergaard@microchip.com>
> > ---
> > drivers/net/dsa/microchip/Makefile | 1 +
> > drivers/net/dsa/microchip/lan9645x/Kconfig | 12 +
> > drivers/net/dsa/microchip/lan9645x/Makefile | 7 +
> > drivers/net/dsa/microchip/lan9645x/lan9645x_main.c | 435 +++++++++++++++++
> > drivers/net/dsa/microchip/lan9645x/lan9645x_main.h | 396 +++++++++++++++
> > drivers/net/dsa/microchip/lan9645x/lan9645x_npi.c | 99 ++++
> > .../net/dsa/microchip/lan9645x/lan9645x_phylink.c | 537 +++++++++++++++++++++
> > drivers/net/dsa/microchip/lan9645x/lan9645x_port.c | 289 +++++++++++
> > drivers/net/ethernet/microchip/Kconfig | 1 +
> > 9 files changed, 1777 insertions(+)
> >
> > diff --git a/drivers/net/dsa/microchip/Makefile b/drivers/net/dsa/microchip/Makefile
> > index 9347cfb3d0b5..e75f17888f75 100644
> > --- a/drivers/net/dsa/microchip/Makefile
> > +++ b/drivers/net/dsa/microchip/Makefile
> > @@ -12,3 +12,4 @@ endif
> > obj-$(CONFIG_NET_DSA_MICROCHIP_KSZ9477_I2C) += ksz9477_i2c.o
> > obj-$(CONFIG_NET_DSA_MICROCHIP_KSZ_SPI) += ksz_spi.o
> > obj-$(CONFIG_NET_DSA_MICROCHIP_KSZ8863_SMI) += ksz8863_smi.o
> > +obj-$(CONFIG_NET_DSA_MICROCHIP_LAN9645X) += lan9645x/
> > diff --git a/drivers/net/dsa/microchip/lan9645x/Kconfig b/drivers/net/dsa/microchip/lan9645x/Kconfig
> > new file mode 100644
> > index 000000000000..8cbac1f9875d
> > --- /dev/null
> > +++ b/drivers/net/dsa/microchip/lan9645x/Kconfig
> > @@ -0,0 +1,12 @@
> > +# SPDX-License-Identifier: GPL-2.0-only
> > +config NET_DSA_MICROCHIP_LAN9645X
> > + tristate "Microchip Lan9645x switch support"
> > + depends on NET_DSA
> > + depends on NET_VENDOR_MICROCHIP
> > + select NET_DSA_TAG_LAN9645X
> > + help
> > + This driver adds DSA support for Microchip Lan9645x switch chips.
> > + The lan9645x switch is a multi-port Gigabit AVB/TSN Ethernet Switch
> > + with five integrated 10/100/1000Base-T PHYs. In addition to the
> > + integrated PHYs, it supports up to 2 RGMII/RMII, up to 2
> > + BASE-X/SERDES/2.5GBASE-X and one Quad-SGMII/Quad-USGMII interfaces.
> > diff --git a/drivers/net/dsa/microchip/lan9645x/Makefile b/drivers/net/dsa/microchip/lan9645x/Makefile
> > new file mode 100644
> > index 000000000000..eea1edc5c0e3
> > --- /dev/null
> > +++ b/drivers/net/dsa/microchip/lan9645x/Makefile
> > @@ -0,0 +1,7 @@
> > +# SPDX-License-Identifier: GPL-2.0-only
> > +obj-$(CONFIG_NET_DSA_MICROCHIP_LAN9645X) += mchp-lan9645x.o
> > +
> > +mchp-lan9645x-objs := lan9645x_main.o \
> > + lan9645x_npi.o \
> > + lan9645x_port.o \
> > + lan9645x_phylink.o \
> > diff --git a/drivers/net/dsa/microchip/lan9645x/lan9645x_main.c b/drivers/net/dsa/microchip/lan9645x/lan9645x_main.c
> > new file mode 100644
> > index 000000000000..739013f049d0
> > --- /dev/null
> > +++ b/drivers/net/dsa/microchip/lan9645x/lan9645x_main.c
> > @@ -0,0 +1,435 @@
> > +// SPDX-License-Identifier: GPL-2.0+
> > +/* Copyright (C) 2026 Microchip Technology Inc.
> > + */
> > +
> > +#include <linux/debugfs.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/phy/phy.h>
>
> You neither need <linux/debugfs.h> nor <linux/phy/phy.h>
>
Ok, I will remove the debugfs.
> > +
> > +#include "lan9645x_main.h"
> > +
> > +static const char *lan9645x_resource_names[NUM_TARGETS + 1] = {
> > + [TARGET_GCB] = "gcb",
> > + [TARGET_QS] = "qs",
> > + [TARGET_CHIP_TOP] = "chip_top",
> > + [TARGET_REW] = "rew",
> > + [TARGET_SYS] = "sys",
> > + [TARGET_HSIO] = "hsio",
> > + [TARGET_DEV] = "dev",
> > + [TARGET_DEV + 1] = "dev1",
> > + [TARGET_DEV + 2] = "dev2",
> > + [TARGET_DEV + 3] = "dev3",
> > + [TARGET_DEV + 4] = "dev4",
> > + [TARGET_DEV + 5] = "dev5",
> > + [TARGET_DEV + 6] = "dev6",
> > + [TARGET_DEV + 7] = "dev7",
> > + [TARGET_DEV + 8] = "dev8",
> > + [TARGET_QSYS] = "qsys",
> > + [TARGET_AFI] = "afi",
> > + [TARGET_ANA] = "ana",
> > + [NUM_TARGETS] = NULL,
> > +};
> > +
> > +static int lan9645x_tag_npi_setup(struct dsa_switch *ds)
> > +{
> > + struct dsa_port *dp, *first_cpu_dp = NULL;
> > + struct lan9645x *lan9645x = ds->priv;
> > +
> > + dsa_switch_for_each_user_port(dp, ds) {
> > + if (first_cpu_dp && dp->cpu_dp != first_cpu_dp) {
> > + dev_err(ds->dev, "Multiple NPI ports not supported\n");
> > + return -EINVAL;
> > + }
> > +
> > + first_cpu_dp = dp->cpu_dp;
> > + }
> > +
> > + if (!first_cpu_dp)
> > + return -EINVAL;
> > +
> > + lan9645x_npi_port_init(lan9645x, first_cpu_dp);
> > +
> > + return 0;
> > +}
> > +
> > +static enum dsa_tag_protocol lan9645x_get_tag_protocol(struct dsa_switch *ds,
> > + int port,
> > + enum dsa_tag_protocol tp)
> > +{
> > + struct lan9645x *lan9645x = ds->priv;
> > +
> > + return lan9645x->tag_proto;
>
> Unnecessary indirection.
>
I will return the tag_proto directly.
> > +}
> > +
> > +static int lan9645x_connect_tag_protocol(struct dsa_switch *ds,
> > + enum dsa_tag_protocol proto)
> > +{
> > + switch (proto) {
> > + case DSA_TAG_PROTO_LAN9645X:
> > + return 0;
> > + default:
> > + return -EPROTONOSUPPORT;
> > + }
> > +}
>
> Completely unnecessary method implementation. See the notes on it in
> Documentation/networking/dsa/dsa.rst.
>
I will remove it.
> > +
> > +static void lan9645x_teardown(struct dsa_switch *ds)
> > +{
> > + struct lan9645x *lan9645x = ds->priv;
> > +
> > + debugfs_remove_recursive(lan9645x->debugfs_root);
> > + lan9645x_npi_port_deinit(lan9645x, lan9645x->npi);
> > +}
> > +
> > +static int lan9645x_change_mtu(struct dsa_switch *ds, int port, int new_mtu)
> > +{
> > + return lan9645x_port_set_maxlen(ds->priv, port, new_mtu);
> > +}
> > +
> > +static int lan9645x_get_max_mtu(struct dsa_switch *ds, int port)
> > +{
> > + struct lan9645x *lan9645x = ds->priv;
> > + int max_mtu;
> > +
> > + /* Actual MAC max MTU is around 16KB. We set 10000 - overhead which
> > + * should be sufficient for all jumbo frames. Larger frames can cause
> > + * problems especially with flow control, since we only have 160K queue
> > + * buffer.
> > + */
> > + max_mtu = 10000 - ETH_HLEN - ETH_FCS_LEN;
> > +
> > + if (port == lan9645x->npi) {
> > + max_mtu -= LAN9645X_IFH_LEN;
> > + max_mtu -= LAN9645X_LONG_PREFIX_LEN;
> > + }
> > +
> > + return max_mtu;
> > +}
> > +
> > +static int lan9645x_reset_switch(struct lan9645x *lan9645x)
> > +{
> > + int val = 0;
> > + int err;
> > +
> > + lan_wr(SYS_RESET_CFG_CORE_ENA_SET(0), lan9645x, SYS_RESET_CFG);
> > + lan_wr(SYS_RAM_INIT_RAM_INIT_SET(1), lan9645x, SYS_RAM_INIT);
> > + err = lan9645x_rd_poll_timeout(lan9645x, SYS_RAM_INIT, val,
> > + SYS_RAM_INIT_RAM_INIT_GET(val) == 0);
> > + if (err) {
> > + dev_err(lan9645x->dev, "Lan9645x setup: failed to init chip RAM.");
> > + return err;
> > + }
> > + lan_wr(SYS_RESET_CFG_CORE_ENA_SET(1), lan9645x, SYS_RESET_CFG);
> > +
> > + return 0;
> > +}
> > +
> > +static int lan9645x_setup(struct dsa_switch *ds)
> > +{
> > + struct lan9645x *lan9645x = ds->priv;
> > + struct device *dev = lan9645x->dev;
> > + struct dsa_port *dp;
> > + u32 all_phys_ports;
> > + int err = 0;
> > +
> > + lan9645x->num_phys_ports = ds->num_ports;
> > + all_phys_ports = GENMASK(lan9645x->num_phys_ports - 1, 0);
> > +
> > + err = lan9645x_reset_switch(lan9645x);
> > + if (err)
> > + return err;
> > +
> > + lan9645x->debugfs_root = debugfs_create_dir("lan9645x_sw", NULL);
>
> DSA drivers do not use debugfs, but more targeted debugging APIs like
> those offered by devlink resources, regions etc. We can give more
> feedback about what debugging infrastructure you may use as a debugfs
> replacement, but this is a simple stub.
>
Removed. I am not too familiar with devlink, but I will look it.
> > +
> > + lan9645x->ports = devm_kcalloc(lan9645x->dev, lan9645x->num_phys_ports,
> > + sizeof(struct lan9645x_port *),
> > + GFP_KERNEL);
> > + if (!lan9645x->ports)
> > + return -ENOMEM;
> > +
> > + for (int port = 0; port < lan9645x->num_phys_ports; port++) {
> > + struct lan9645x_port *p;
> > +
> > + p = devm_kzalloc(lan9645x->dev,
> > + sizeof(struct lan9645x_port), GFP_KERNEL);
> > + if (!p)
> > + return -ENOMEM;
> > +
> > + p->lan9645x = lan9645x;
> > + p->chip_port = port;
> > + lan9645x->ports[port] = p;
> > + }
> > +
> > + err = lan9645x_port_parse_ports_node(lan9645x);
> > + if (err) {
> > + dev_err(dev, "Lan9645x setup: failed to parse ports node.");
> > + return err;
> > + }
> > +
> > + err = lan9645x_tag_npi_setup(ds);
> > + if (err) {
> > + dev_err(dev, "Lan9645x setup: failed to setup NPI port.\n");
> > + return err;
> > + }
> > +
> > + /* Link Aggregation Mode: NETDEV_LAG_HASH_L2 */
> > + lan_wr(ANA_AGGR_CFG_AC_SMAC_ENA |
> > + ANA_AGGR_CFG_AC_DMAC_ENA,
> > + lan9645x, ANA_AGGR_CFG);
> > +
> > + /* Flush queues */
> > + lan_wr(GENMASK(1, 0), lan9645x, QS_XTR_FLUSH);
> > +
> > + /* Allow to drain */
> > + mdelay(1);
> > +
> > + /* All Queues normal */
> > + lan_wr(0x0, lan9645x, QS_XTR_FLUSH);
> > +
> > + /* Set MAC age time to default value, the entry is aged after
> > + * 2 * AGE_PERIOD
> > + */
> > + lan_wr(ANA_AUTOAGE_AGE_PERIOD_SET(BR_DEFAULT_AGEING_TIME / 2 / HZ),
> > + lan9645x, ANA_AUTOAGE);
> > +
> > + /* Disable learning for frames discarded by VLAN ingress filtering */
> > + lan_rmw(ANA_ADVLEARN_VLAN_CHK_SET(1),
> > + ANA_ADVLEARN_VLAN_CHK,
> > + lan9645x, ANA_ADVLEARN);
> > +
> > + /* Queue system frame ageing. We target 2s ageing.
> > + *
> > + * Register unit is 1024 cycles.
> > + *
> > + * ASIC: 165.625 Mhz ~ 6.0377 ns period
> > + *
> > + * 1024 * 6.0377 ns =~ 6182 ns
> > + * val = 2000000000ns / 6182ns
> > + */
> > + lan_wr(SYS_FRM_AGING_AGE_TX_ENA_SET(1) |
> > + SYS_FRM_AGING_MAX_AGE_SET((2000000000 / 6182)),
> > + lan9645x, SYS_FRM_AGING);
> > +
> > + /* Setup flooding PGIDs for IPv4/IPv6 multicast. Control and dataplane
> > + * use the same masks. Control frames are redirected to CPU, and
> > + * the network stack is responsible for forwarding these.
> > + * The dataplane is forwarding according to the offloaded MDB entries.
> > + */
> > + lan_wr(ANA_FLOODING_IPMC_FLD_MC4_DATA_SET(PGID_MCIPV4) |
> > + ANA_FLOODING_IPMC_FLD_MC4_CTRL_SET(PGID_MC) |
> > + ANA_FLOODING_IPMC_FLD_MC6_DATA_SET(PGID_MCIPV6) |
> > + ANA_FLOODING_IPMC_FLD_MC6_CTRL_SET(PGID_MC),
> > + lan9645x, ANA_FLOODING_IPMC);
> > +
> > + /* There are 8 priorities */
> > + for (int prio = 0; prio < 8; ++prio)
> > + lan_wr(ANA_FLOODING_FLD_MULTICAST_SET(PGID_MC) |
> > + ANA_FLOODING_FLD_UNICAST_SET(PGID_UC) |
> > + ANA_FLOODING_FLD_BROADCAST_SET(PGID_BC),
> > + lan9645x, ANA_FLOODING(prio));
> > +
> > + /* Set all the entries to obey VLAN. */
> > + for (int i = 0; i < PGID_ENTRIES; ++i)
> > + lan_wr(ANA_PGID_CFG_OBEY_VLAN_SET(1),
> > + lan9645x, ANA_PGID_CFG(i));
> > +
> > + /* Disable bridging by default */
> > + for (int p = 0; p < lan9645x->num_phys_ports; p++) {
> > + lan_wr(0, lan9645x, ANA_PGID(PGID_SRC + p));
> > +
> > + /* Do not forward BPDU frames to the front ports and copy them
> > + * to CPU
> > + */
> > + lan_wr(ANA_CPU_FWD_BPDU_CFG_BPDU_REDIR_ENA,
> > + lan9645x, ANA_CPU_FWD_BPDU_CFG(p));
> > + }
> > +
> > + /* Set source buffer size for each priority and port to ~1700 bytes */
> > + for (int i = 0; i <= QSYS_Q_RSRV; ++i) {
> > + lan_wr(QS_SRC_BUF_RSV / 64, lan9645x, QSYS_RES_CFG(i));
> > + lan_wr(QS_SRC_BUF_RSV / 64, lan9645x, QSYS_RES_CFG(512 + i));
> > + }
> > +
> > + /* Configure and enable the CPU port */
> > + lan9645x_port_cpu_init(lan9645x);
> > +
> > + /* Multicast to all front ports */
> > + lan_wr(all_phys_ports, lan9645x, ANA_PGID(PGID_MC));
> > +
> > + /* IP multicast to all front ports */
> > + lan_wr(all_phys_ports, lan9645x, ANA_PGID(PGID_MCIPV4));
> > + lan_wr(all_phys_ports, lan9645x, ANA_PGID(PGID_MCIPV6));
> > +
> > + /* Unicast to all front ports */
> > + lan_wr(all_phys_ports, lan9645x, ANA_PGID(PGID_UC));
> > +
> > + /* Broadcast to all ports */
> > + lan_wr(BIT(CPU_PORT) | all_phys_ports, lan9645x, ANA_PGID(PGID_BC));
> > +
> > + dsa_switch_for_each_available_port(dp, ds)
> > + lan9645x_port_init(lan9645x, dp->index);
> > +
> > + lan9645x_port_set_tail_drop_wm(lan9645x);
> > +
> > + ds->mtu_enforcement_ingress = true;
> > + ds->assisted_learning_on_cpu_port = true;
> > + ds->fdb_isolation = true;
> > +
> > + dev_info(lan9645x->dev,
> > + "Setup complete. SKU features: tsn_dis=%d hsr_dis=%d max_ports=%d",
> > + lan9645x->tsn_dis, lan9645x->dd_dis,
> > + lan9645x->num_phys_ports - lan9645x->num_port_dis);
> > +
> > + return 0;
> > +}
> > +
> > +static void lan9645x_port_phylink_get_caps(struct dsa_switch *ds, int port,
> > + struct phylink_config *config)
> > +{
> > + lan9645x_phylink_get_caps(ds->priv, port, config);
> > +}
> > +
> > +static const struct dsa_switch_ops lan9645x_switch_ops = {
> > + .get_tag_protocol = lan9645x_get_tag_protocol,
> > + .connect_tag_protocol = lan9645x_connect_tag_protocol,
> > +
> > + .setup = lan9645x_setup,
> > + .teardown = lan9645x_teardown,
> > +
> > + /* Phylink integration */
> > + .phylink_get_caps = lan9645x_port_phylink_get_caps,
> > +
> > + /* MTU */
> > + .port_change_mtu = lan9645x_change_mtu,
> > + .port_max_mtu = lan9645x_get_max_mtu,
> > +};
> > +
> > +static int lan9645x_request_target_regmaps(struct lan9645x *lan9645x)
> > +{
> > + const char *resource_name;
> > + struct regmap *tgt_map;
> > +
> > + for (int i = 0; i < NUM_TARGETS; i++) {
> > + resource_name = lan9645x_resource_names[i];
> > + if (!resource_name)
> > + continue;
> > +
> > + tgt_map = dev_get_regmap(lan9645x->dev->parent, resource_name);
> > + if (IS_ERR_OR_NULL(tgt_map)) {
> > + dev_err(lan9645x->dev, "Failed to get regmap=%d", i);
> > + return -ENODEV;
> > + }
> > +
> > + lan9645x->rmap[i] = tgt_map;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static void lan9645x_set_feat_dis(struct lan9645x *lan9645x)
> > +{
> > + u32 feat_dis;
> > +
> > + /* The features which can be physically disabled on some SKUs are:
> > + * 1) Number of ports can be 5, 7 or 9. Any ports can be used, the chip
> > + * tracks how many are active.
> > + * 2) HSR/PRP. The duplicate discard table can be disabled.
> > + * 3) TAS, frame preemption and PSFP can be disabled.
> > + */
> > + feat_dis = lan_rd(lan9645x, GCB_FEAT_DISABLE);
> > +
> > + lan9645x->num_port_dis =
> > + GCB_FEAT_DISABLE_FEAT_NUM_PORTS_DIS_GET(feat_dis);
> > + lan9645x->dd_dis = GCB_FEAT_DISABLE_FEAT_DD_DIS_GET(feat_dis);
> > + lan9645x->tsn_dis = GCB_FEAT_DISABLE_FEAT_TSN_DIS_GET(feat_dis);
> > +}
> > +
> > +static int lan9645x_probe(struct platform_device *pdev)
> > +{
> > + struct device *dev = &pdev->dev;
> > + struct lan9645x *lan9645x;
> > + struct dsa_switch *ds;
> > + int err = 0;
> > +
> > + lan9645x = devm_kzalloc(dev, sizeof(*lan9645x), GFP_KERNEL);
> > + if (!lan9645x)
> > + return dev_err_probe(dev, -ENOMEM,
> > + "Failed to allocate LAN9645X");
> > +
> > + dev_set_drvdata(dev, lan9645x);
> > + lan9645x->dev = dev;
> > +
> > + err = lan9645x_request_target_regmaps(lan9645x);
> > + if (err)
> > + return dev_err_probe(dev, err, "Failed to request regmaps");
> > +
> > + ds = devm_kzalloc(dev, sizeof(*ds), GFP_KERNEL);
> > + if (!ds)
> > + return dev_err_probe(dev, -ENOMEM,
> > + "Failed to allocate DSA switch");
> > +
> > + ds->dev = dev;
> > + ds->num_ports = NUM_PHYS_PORTS;
> > + ds->num_tx_queues = NUM_PRIO_QUEUES;
> > + ds->dscp_prio_mapping_is_global = true;
> > +
> > + ds->ops = &lan9645x_switch_ops;
> > + ds->phylink_mac_ops = &lan9645x_phylink_mac_ops;
> > + ds->priv = lan9645x;
> > +
> > + lan9645x->ds = ds;
> > + lan9645x->tag_proto = DSA_TAG_PROTO_LAN9645X;
> > + lan9645x->shared_queue_sz = LAN9645X_BUFFER_MEMORY;
> > +
> > + lan9645x_set_feat_dis(lan9645x);
> > +
> > + err = dsa_register_switch(ds);
> > + if (err)
> > + return dev_err_probe(dev, err, "Failed to register DSA switch");
> > +
> > + return 0;
> > +}
> > +
> > +static void lan9645x_remove(struct platform_device *pdev)
> > +{
> > + struct lan9645x *lan9645x = dev_get_drvdata(&pdev->dev);
> > +
> > + if (!lan9645x)
> > + return;
> > +
> > + /* Calls lan9645x DSA .teardown */
> > + dsa_unregister_switch(lan9645x->ds);
> > + dev_set_drvdata(&pdev->dev, NULL);
> > +}
> > +
> > +static void lan9645x_shutdown(struct platform_device *pdev)
> > +{
> > + struct lan9645x *lan9645x = dev_get_drvdata(&pdev->dev);
> > +
> > + if (!lan9645x)
> > + return;
> > +
> > + dsa_switch_shutdown(lan9645x->ds);
> > +
> > + dev_set_drvdata(&pdev->dev, NULL);
> > +}
> > +
> > +static const struct of_device_id lan9645x_switch_of_match[] = {
> > + { .compatible = "microchip,lan96455s-switch" },
> > + {},
> > +};
> > +MODULE_DEVICE_TABLE(of, lan9645x_switch_of_match);
> > +
> > +static struct platform_driver lan9645x_switch_driver = {
> > + .driver = {
> > + .name = "lan96455s-switch",
> > + .of_match_table = lan9645x_switch_of_match,
> > + },
> > + .probe = lan9645x_probe,
> > + .remove = lan9645x_remove,
> > + .shutdown = lan9645x_shutdown,
> > +};
> > +module_platform_driver(lan9645x_switch_driver);
> > +
> > +MODULE_DESCRIPTION("Lan9645x Switch Driver");
> > +MODULE_AUTHOR("Jens Emil Schulz Østergaard <jensemil.schulzostergaard@microchip.com>");
> > +MODULE_LICENSE("GPL");
> > diff --git a/drivers/net/dsa/microchip/lan9645x/lan9645x_main.h b/drivers/net/dsa/microchip/lan9645x/lan9645x_main.h
> > new file mode 100644
> > index 000000000000..a51b637f28bf
> > --- /dev/null
> > +++ b/drivers/net/dsa/microchip/lan9645x/lan9645x_main.h
> > @@ -0,0 +1,396 @@
> > +/* SPDX-License-Identifier: GPL-2.0+ */
> > +/* Copyright (C) 2026 Microchip Technology Inc.
> > + */
> > +
> > +#ifndef __LAN9645X_MAIN_H__
> > +#define __LAN9645X_MAIN_H__
> > +
> > +#include <linux/dsa/lan9645x.h>
> > +#include <linux/regmap.h>
> > +#include <net/dsa.h>
> > +
> > +#include "lan9645x_regs.h"
> > +
> > +#define lan9645x_for_each_chipport(_lan9645x, _i) \
> > + for ((_i) = 0; (_i) < (_lan9645x)->num_phys_ports; (_i)++)
>
> I would prefer you not introduce this iteration scheme. Eventually you
> will need to get a "struct dsa_port *dp" pointer in the loop, and at that
> point, you will need to call dsa_to_port(lan9645->ds, i). But guess
> what, dsa_to_port() iterates again through the ports to get the "dp".
> Please consider one of the iterators from the dsa_switch_for_each_available_port()
> family as a replacement here, and for "i" do "dp->index". This gives you
> "dp" as a pointer right away if you need it.
>
I will replace it with dsa iterators.
> > +
> > +/* Ports 0-8 are front ports
> > + * Ports 9-10 are CPU ports
> > + *
> > + * CPU ports are logical ports in the chip intended for management. The frame
> > + * delivery mechanism can vary: direct register injection/extraction or one can
> > + * use a front port as CPU port, called a Node Processor Interface (NPI).
> > + *
> > + * This is the approach used by LAN9645X.
> > + */
> > +#define NUM_PHYS_PORTS 9
> > +#define CPU_PORT 9
> > +#define NUM_PRIO_QUEUES 8
> > +#define LAN9645X_NUM_TC 8
> > +
> > +#define QS_SRC_BUF_RSV 1700
> > +
> > +/* Reserved amount for (SRC, PRIO) at index 8*SRC + PRIO
> > + * See QSYS:RES_CTRL[*]:RES_CFG description
> > + */
> > +#define QSYS_Q_RSRV 95
> > +
> > +#define LAN9645X_ISDX_MAX 128
> > +#define LAN9645X_ESDX_MAX 128
> > +#define LAN9645X_SFID_MAX 128
> > +
> > +/* Reserved VLAN IDs. */
> > +#define UNAWARE_PVID 0
> > +#define HOST_PVID 4095
> > +#define VLAN_HSR_PRP 4094
> > +#define VLAN_MAX (VLAN_HSR_PRP - 1)
> > +
> > +/* VLAN flags for VLAN table defined in ANA_VLANTIDX */
> > +#define VLAN_SRC_CHK 0x01
> > +#define VLAN_MIR 0x02
> > +#define VLAN_LRN_DIS 0x04
> > +#define VLAN_PRV_VLAN 0x08
> > +#define VLAN_FLD_DIS 0x10
> > +#define VLAN_S_FWD_ENA 0x20
> > +
> > +/* 160KiB / 1.25Mbit */
> > +#define LAN9645X_BUFFER_MEMORY (160 * 1024)
> > +
> > +/* Port Group Identifiers (PGID) are port-masks applied to all frames.
> > + * The replicated registers are organized like so in HW:
> > + *
> > + * 0-63: Destination analysis
> > + * 64-79: Aggregation analysis
> > + * 80-(80+10-1): Source port analysis
> > + *
> > + * Destination: By default the first 9 port masks == BIT(port_num). Never change
> > + * these except for aggregation. Remaining dst masks are for L2 MC and
> > + * flooding. (See FLOODING and FLOODING_IPMC).
> > + *
> > + * Aggregation: Used to pick a port within an aggregation group. If no
> > + * aggregation is configured, these are all-ones.
> > + *
> > + * Source: Control which ports a given source port can forward to. A frame that
> > + * is received on port n, uses mask 80+n as a mask to filter out destination
> > + * ports. The default values are that all bits are set except for the index
> > + * number (no loopback).
> > + *
> > + * We reserve destination PGIDs at the end of the range.
> > + */
> > +
> > +#define PGID_AGGR 64
> > +#define PGID_SRC 80
> > +#define PGID_ENTRIES 89
> > +
> > +#define PGID_AGGR_NUM (PGID_SRC - PGID_AGGR)
> > +
> > +/* General purpose PGIDs. */
> > +#define PGID_GP_START CPU_PORT
> > +#define PGID_GP_END PGID_MRP
> > +
> > +/* Reserved PGIDs.
> > + * PGID_MRP is a blackhole PGID
> > + */
> > +#define PGID_MRP (PGID_AGGR - 7)
> > +#define PGID_CPU (PGID_AGGR - 6)
> > +#define PGID_UC (PGID_AGGR - 5)
> > +#define PGID_BC (PGID_AGGR - 4)
> > +#define PGID_MC (PGID_AGGR - 3)
> > +#define PGID_MCIPV4 (PGID_AGGR - 2)
> > +#define PGID_MCIPV6 (PGID_AGGR - 1)
> > +
> > +/* Flooding PGIDS:
> > + * PGID_UC
> > + * PGID_MC*
> > + * PGID_BC
> > + *
> > + */
> > +
> > +#define RD_SLEEP_US 3
> > +#define RD_SLEEPTIMEOUT_US 100000
> > +#define SLOW_RD_SLEEP_US 1000
> > +#define SLOW_RD_SLEEPTIMEOUT_US 2000000
> > +
> > +#define lan9645x_rd_poll_timeout(_lan9645x, _reg_macro, _val, _cond) \
> > + regmap_read_poll_timeout(lan_rmap((_lan9645x), _reg_macro), \
> > + lan_rel_addr(_reg_macro), (_val), \
> > + (_cond), RD_SLEEP_US, RD_SLEEPTIMEOUT_US)
> > +
> > +#define lan9645x_rd_poll_slow(_lan9645x, _reg_macro, _val, _cond) \
> > + regmap_read_poll_timeout(lan_rmap((_lan9645x), _reg_macro), \
> > + lan_rel_addr(_reg_macro), (_val), \
> > + (_cond), SLOW_RD_SLEEP_US, \
> > + SLOW_RD_SLEEPTIMEOUT_US)
> > +
> > +/* NPI port prefix config encoding
> > + *
> > + * 0: No CPU extraction header (normal frames)
> > + * 1: CPU extraction header without prefix
> > + * 2: CPU extraction header with short prefix
> > + * 3: CPU extraction header with long prefix
> > + */
> > +enum lan9645x_tag_prefix {
> > + LAN9645X_TAG_PREFIX_DISABLED = 0,
> > + LAN9645X_TAG_PREFIX_NONE = 1,
> > + LAN9645X_TAG_PREFIX_SHORT = 2,
> > + LAN9645X_TAG_PREFIX_LONG = 3,
> > +};
> > +
> > +enum {
> > + LAN9645X_SPEED_DISABLED = 0,
> > + LAN9645X_SPEED_10 = 1,
> > + LAN9645X_SPEED_100 = 2,
> > + LAN9645X_SPEED_1000 = 3,
> > + LAN9645X_SPEED_2500 = 4,
> > +};
> > +
> > +/* Rewriter VLAN port tagging encoding for REW:PORT[0-10]:TAG_CFG.TAG_CFG
> > + *
> > + * 0: Port tagging disabled.
> > + * 1: Tag all frames, except when VID=PORT_VLAN_CFG.PORT_VID or VID=0.
> > + * 2: Tag all frames, except when VID=0.
> > + * 3: Tag all frames.
> > + */
> > +enum lan9645x_vlan_port_tag {
> > + LAN9645X_TAG_DISABLED = 0,
> > + LAN9645X_TAG_NO_PVID_NO_UNAWARE = 1,
> > + LAN9645X_TAG_NO_UNAWARE = 2,
> > + LAN9645X_TAG_ALL = 3,
> > +};
> > +
> > +struct lan9645x {
> > + struct device *dev;
> > + struct dsa_switch *ds;
> > + enum dsa_tag_protocol tag_proto;
> > + struct regmap *rmap[NUM_TARGETS];
> > +
> > + int shared_queue_sz;
> > +
> > + /* NPI chip_port */
> > + int npi;
> > +
> > + u8 num_phys_ports;
> > + struct lan9645x_port **ports;
> > +
> > + /* debugfs */
> > + struct dentry *debugfs_root;
> > +
> > + int num_port_dis;
> > + bool dd_dis;
> > + bool tsn_dis;
> > +};
> > +
> > +struct lan9645x_port {
> > + struct lan9645x *lan9645x;
> > + const char *name;
> > +
> > + u16 pvid;
> > + u16 untagged_vid;
> > + u8 chip_port;
> > + u8 stp_state;
> > + bool vlan_aware;
>
> Unused (in this patch). Even later (I haven't yet looked), you should
> probably use dsa_port_is_vlan_filtering() directly.
>
> > + bool learn_ena;
>
> Also unused, please introduce it on first use.
>
I will reorganize the patches to introduce types and fields on first use.
> > +
> > + phy_interface_t phy_mode;
> > +
> > + int speed; /* internal speed value LAN9645X_SPEED_* */
> > + u8 duplex;
> > + struct list_head path_delays;
> > + u32 rx_delay;
>
> I'm a bit lost among occurrences of RGMII delays and PTP path delays,
> but I don't think this is used.
>
I will remove all the port delay code.
> > +};
> > +
> > +struct lan9645x_path_delay {
> > + struct list_head list;
> > + u32 rx_delay;
> > + u32 tx_delay;
> > + u32 speed;
> > +};
> > +
> > +extern const struct phylink_mac_ops lan9645x_phylink_mac_ops;
> > +
> > +/* PFC_CFG.FC_LINK_SPEED encoding */
> > +static inline int lan9645x_speed_fc_enc(int speed)
> > +{
> > + switch (speed) {
> > + case LAN9645X_SPEED_10:
> > + return 3;
> > + case LAN9645X_SPEED_100:
> > + return 2;
> > + case LAN9645X_SPEED_1000:
> > + return 1;
> > + case LAN9645X_SPEED_2500:
> > + return 0;
> > + default:
> > + WARN_ON_ONCE(1);
> > + return 1;
> > + }
> > +}
> > +
> > +/* Watermark encode. See QSYS:RES_CTRL[*]:RES_CFG.WM_HIGH for details.
> > + * Returns lowest encoded number which will fit request/ is larger than request.
> > + * Or the maximum representable value, if request is too large.
> > + */
> > +static inline u32 lan9645x_wm_enc(u32 value)
> > +{
> > +#define GWM_MULTIPLIER_BIT BIT(8)
> > +#define LAN9645X_BUFFER_CELL_SZ 64
>
> The placement of these definitions inside the function makes it
> difficult to read.
>
Moving these to the top.
> > + value = DIV_ROUND_UP(value, LAN9645X_BUFFER_CELL_SZ);
> > +
> > + if (value >= GWM_MULTIPLIER_BIT) {
> > + value = DIV_ROUND_UP(value, 16);
> > + if (value >= GWM_MULTIPLIER_BIT)
> > + value = (GWM_MULTIPLIER_BIT - 1);
> > + value |= GWM_MULTIPLIER_BIT;
> > + }
> > +
> > + return value;
> > +}
> > +
> > +static inline struct lan9645x_port *lan9645x_to_port(struct lan9645x *lan9645x,
> > + int port)
> > +{
> > + if (WARN_ON(!(port >= 0 && port < lan9645x->num_phys_ports)))
> > + return NULL;
> > +
> > + return lan9645x->ports[port];
> > +}
> > +
> > +static inline struct net_device *lan9645x_port_to_ndev(struct lan9645x_port *p)
> > +{
> > + struct lan9645x *lan9645x = p->lan9645x;
> > + struct dsa_port *dp;
> > +
> > + dp = dsa_to_port(lan9645x->ds, p->chip_port);
> > + if (dp && dp->type == DSA_PORT_TYPE_USER)
> > + return dp->user;
> > +
> > + return NULL;
> > +}
> > +
> > +static inline struct net_device *
> > +lan9645x_chipport_to_ndev(struct lan9645x *lan9645x, int port)
> > +{
> > + return lan9645x_port_to_ndev(lan9645x_to_port(lan9645x, port));
> > +}
>
> Unnecessary, please remove this along with lan9645x_port_to_ndev().
> You really shouldn't need to do this. Ocelot had it because it was a
> driver with two front-ends.
>
I will remove it in the next version.
> > +
> > +static inline bool lan9645x_port_is_used(struct lan9645x *lan9645x, int port)
> > +{
> > + struct dsa_port *dp;
> > +
> > + dp = dsa_to_port(lan9645x->ds, port);
> > + if (!dp)
> > + return false;
> > +
> > + return dp->type != DSA_PORT_TYPE_UNUSED;
> > +}
>
> Unused, please remove.
>
> > +
> > +static inline struct regmap *lan_tgt2rmap(struct lan9645x *lan9645x,
> > + enum lan9645x_target t, int tinst)
> > +{
> > + WARN_ON_ONCE(!lan9645x->rmap[t + tinst]);
>
> Question: what purpose does the WARN_ON_ONCE() serve, if you dereference
> the pointer without NULL checking afterwards?
>
> Suppose the regmap could be NULL. Wouldn't you get a stack trace twice?
>
I think you are right. I wanted to be able to see if I messed up the array,
but I do not think I have ever had it happen. I can remove it in the next
version.
> > + return lan9645x->rmap[t + tinst];
> > +}
> > +
> > +static inline u32 __lan_rel_addr(int gbase, int ginst, int gcnt,
> > + int gwidth, int raddr, int rinst,
> > + int rcnt, int rwidth)
> > +{
> > + WARN_ON(ginst >= gcnt);
> > + WARN_ON(rinst >= rcnt);
> > + return gbase + ginst * gwidth + raddr + rinst * rwidth;
> > +}
> > +
> > +/* Get register address relative to target instance */
> > +static inline u32 lan_rel_addr(enum lan9645x_target t, int tinst, int tcnt,
> > + int gbase, int ginst, int gcnt, int gwidth,
> > + int raddr, int rinst, int rcnt, int rwidth)
> > +{
> > + WARN_ON(tinst >= tcnt);
> > + return __lan_rel_addr(gbase, ginst, gcnt, gwidth, raddr, rinst,
> > + rcnt, rwidth);
> > +}
> > +
> > +static inline u32 lan_rd(struct lan9645x *lan9645x, enum lan9645x_target t,
> > + int tinst, int tcnt, int gbase, int ginst,
> > + int gcnt, int gwidth, int raddr, int rinst,
> > + int rcnt, int rwidth)
> > +{
> > + u32 addr, val = 0;
> > +
> > + addr = lan_rel_addr(t, tinst, tcnt, gbase, ginst, gcnt, gwidth,
> > + raddr, rinst, rcnt, rwidth);
> > +
> > + WARN_ON_ONCE(regmap_read(lan_tgt2rmap(lan9645x, t, tinst), addr, &val));
> > +
> > + return val;
> > +}
> > +
> > +static inline int lan_bulk_rd(void *val, size_t val_count,
> > + struct lan9645x *lan9645x,
> > + enum lan9645x_target t, int tinst, int tcnt,
> > + int gbase, int ginst, int gcnt, int gwidth,
> > + int raddr, int rinst, int rcnt, int rwidth)
> > +{
> > + u32 addr;
> > +
> > + addr = lan_rel_addr(t, tinst, tcnt, gbase, ginst, gcnt, gwidth,
> > + raddr, rinst, rcnt, rwidth);
> > +
> > + return regmap_bulk_read(lan_tgt2rmap(lan9645x, t, tinst), addr, val,
> > + val_count);
> > +}
> > +
> > +static inline struct regmap *lan_rmap(struct lan9645x *lan9645x,
> > + enum lan9645x_target t, int tinst,
> > + int tcnt, int gbase, int ginst,
> > + int gcnt, int gwidth, int raddr,
> > + int rinst, int rcnt, int rwidth)
> > +{
> > + return lan_tgt2rmap(lan9645x, t, tinst);
> > +}
> > +
> > +static inline void lan_wr(u32 val, struct lan9645x *lan9645x,
> > + enum lan9645x_target t, int tinst, int tcnt,
> > + int gbase, int ginst, int gcnt, int gwidth,
> > + int raddr, int rinst, int rcnt, int rwidth)
> > +{
> > + u32 addr;
> > +
> > + addr = lan_rel_addr(t, tinst, tcnt, gbase, ginst, gcnt, gwidth,
> > + raddr, rinst, rcnt, rwidth);
> > +
> > + WARN_ON_ONCE(regmap_write(lan_tgt2rmap(lan9645x, t, tinst), addr, val));
> > +}
> > +
> > +static inline void lan_rmw(u32 val, u32 mask, struct lan9645x *lan9645x,
> > + enum lan9645x_target t, int tinst, int tcnt,
> > + int gbase, int ginst, int gcnt, int gwidth,
> > + int raddr, int rinst, int rcnt, int rwidth)
> > +{
> > + u32 addr;
> > +
> > + addr = lan_rel_addr(t, tinst, tcnt, gbase, ginst, gcnt, gwidth,
> > + raddr, rinst, rcnt, rwidth);
> > +
> > + WARN_ON_ONCE(regmap_update_bits(lan_tgt2rmap(lan9645x, t, tinst),
> > + addr, mask, val));
> > +}
> > +
> > +/* lan9645x_npi.c */
> > +void lan9645x_npi_port_init(struct lan9645x *lan9645x,
> > + struct dsa_port *cpu_port);
> > +void lan9645x_npi_port_deinit(struct lan9645x *lan9645x, int port);
> > +
> > +/* lan9645x_port.c */
> > +int lan9645x_port_init(struct lan9645x *lan9645x, int port);
> > +void lan9645x_port_cpu_init(struct lan9645x *lan9645x);
> > +void lan9645x_port_set_tail_drop_wm(struct lan9645x *lan9645x);
> > +int lan9645x_port_set_maxlen(struct lan9645x *lan9645x, int port, size_t sdu);
> > +int lan9645x_port_parse_ports_node(struct lan9645x *lan9645x);
> > +
> > +/* lan9645x_phylink.c */
> > +void lan9645x_phylink_get_caps(struct lan9645x *lan9645x, int port,
> > + struct phylink_config *c);
> > +void lan9645x_phylink_port_down(struct lan9645x *lan9645x, int port);
> > +
> > +#endif /* __LAN9645X_MAIN_H__ */
> > diff --git a/drivers/net/dsa/microchip/lan9645x/lan9645x_npi.c b/drivers/net/dsa/microchip/lan9645x/lan9645x_npi.c
> > new file mode 100644
> > index 000000000000..0ae8b9acb916
> > --- /dev/null
> > +++ b/drivers/net/dsa/microchip/lan9645x/lan9645x_npi.c
> > @@ -0,0 +1,99 @@
> > +// SPDX-License-Identifier: GPL-2.0+
> > +/* Copyright (C) 2026 Microchip Technology Inc.
> > + */
> > +#include <net/addrconf.h>
> > +
> > +#include "lan9645x_main.h"
> > +
> > +static void disable_conduit_ipv6(struct lan9645x *lan9645x,
> > + struct net_device *conduit)
> > +{
> > + struct inet6_dev *dev_v6;
> > +
> > + if (!conduit)
> > + return;
> > +
> > + /* IPv6 on the conduit will generate frames bypassing our tag driver, so
> > + * they lack an IFH. This will be garbage in garbage out and we want to
> > + * avoid this.
> > + */
> > + rtnl_lock();
> > + dev_v6 = __in6_dev_get(conduit);
> > + if (dev_v6) {
> > + WRITE_ONCE(dev_v6->cnf.disable_ipv6, 1);
> > + dev_warn(lan9645x->dev, "Disabled IPv6 on conduit device: %s\n",
> > + netdev_name(conduit));
> > + }
> > + rtnl_unlock();
> > +}
> > +
> > +void lan9645x_npi_port_init(struct lan9645x *lan9645x,
> > + struct dsa_port *cpu_port)
> > +{
> > + int port = cpu_port->index;
> > + struct lan9645x_port *p;
> > +
> > + p = lan9645x_to_port(lan9645x, port);
> > + lan9645x->npi = port;
> > +
> > + dev_dbg(lan9645x->dev, "NPI port=%d\n", port);
> > +
> > + /* Enabling IPv6 on the conduit will send frames directly on the
> > + * interface, without being intercepted by our tag driver. This causes a
> > + * GIGO situation.
> > + */
> > + disable_conduit_ipv6(lan9645x, cpu_port->conduit);
> > +
> > + /* Any CPU extraction queue frames, are sent to external CPU on given
> > + * port. Never send injected frames back to cpu.
> > + */
> > + lan_wr(QSYS_EXT_CPU_CFG_EXT_CPUQ_MSK |
> > + QSYS_EXT_CPU_CFG_EXT_CPU_PORT_SET(p->chip_port) |
> > + QSYS_EXT_CPU_CFG_EXT_CPU_KILL_ENA_SET(1) |
> > + QSYS_EXT_CPU_CFG_INT_CPU_KILL_ENA_SET(1),
> > + lan9645x, QSYS_EXT_CPU_CFG);
> > +
> > + /* Configure IFH prefix mode for NPI port. */
> > + lan_rmw(SYS_PORT_MODE_INCL_XTR_HDR_SET(LAN9645X_TAG_PREFIX_LONG) |
> > + SYS_PORT_MODE_INCL_INJ_HDR_SET(LAN9645X_TAG_PREFIX_NONE),
> > + SYS_PORT_MODE_INCL_XTR_HDR |
> > + SYS_PORT_MODE_INCL_INJ_HDR,
> > + lan9645x,
> > + SYS_PORT_MODE(p->chip_port));
> > +
> > + /* Rewriting and extraction with IFH does not play nice together. A VLAN
> > + * tag pushed into the frame by REW will cause 4 bytes at the end of the
> > + * extraction header to be overwritten with the top 4 bytes of the DMAC.
> > + *
> > + * We can not use REW_PORT_CFG_NO_REWRITE=1 as that disabled RTAGD
> > + * setting in the IFH
> > + */
> > + lan_rmw(REW_TAG_CFG_TAG_CFG_SET(LAN9645X_TAG_DISABLED),
> > + REW_TAG_CFG_TAG_CFG, lan9645x, REW_TAG_CFG(port));
> > +
> > + /* Make sure frames with src_port=CPU_PORT are not reflected back via
> > + * the NPI port. This could happen if a frame is flooded for instance.
> > + * The *_CPU_KILL_ENA flags above only have an effect when a frame is
> > + * output due to a CPU forwarding decision such as trapping or cpu copy.
> > + */
> > + lan_rmw(0, BIT(port), lan9645x, ANA_PGID(PGID_SRC + CPU_PORT));
> > +}
> > +
> > +void lan9645x_npi_port_deinit(struct lan9645x *lan9645x, int port)
> > +{
> > + struct lan9645x_port *p = lan9645x_to_port(lan9645x, port);
> > +
> > + lan9645x->npi = -1;
> > +
> > + lan_wr(QSYS_EXT_CPU_CFG_EXT_CPU_PORT_SET(0x1f) |
> > + QSYS_EXT_CPU_CFG_EXT_CPU_KILL_ENA_SET(1) |
> > + QSYS_EXT_CPU_CFG_INT_CPU_KILL_ENA_SET(1),
> > + lan9645x, QSYS_EXT_CPU_CFG);
> > +
> > + lan_rmw(SYS_PORT_MODE_INCL_XTR_HDR_SET(LAN9645X_TAG_PREFIX_DISABLED) |
> > + SYS_PORT_MODE_INCL_INJ_HDR_SET(LAN9645X_TAG_PREFIX_DISABLED),
> > + SYS_PORT_MODE_INCL_XTR_HDR |
> > + SYS_PORT_MODE_INCL_INJ_HDR,
> > + lan9645x,
> > + SYS_PORT_MODE(p->chip_port));
> > +}
> > diff --git a/drivers/net/dsa/microchip/lan9645x/lan9645x_phylink.c b/drivers/net/dsa/microchip/lan9645x/lan9645x_phylink.c
> > new file mode 100644
> > index 000000000000..3acc48f12fae
> > --- /dev/null
> > +++ b/drivers/net/dsa/microchip/lan9645x/lan9645x_phylink.c
> > @@ -0,0 +1,537 @@
> > +// SPDX-License-Identifier: GPL-2.0+
> > +/* Copyright (C) 2026 Microchip Technology Inc.
> > + */
> > +
> > +#include <linux/phy.h>
> > +#include <linux/phy/phy.h>
> > +
> > +#include "lan9645x_main.h"
> > +
> > +void lan9645x_phylink_get_caps(struct lan9645x *lan9645x, int port,
> > + struct phylink_config *c)
> > +{
> > + c->mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE | MAC_10 |
> > + MAC_100 | MAC_1000FD | MAC_2500FD;
> > +
> > + switch (port) {
> > + case 0 ... 3:
> > + __set_bit(PHY_INTERFACE_MODE_GMII, c->supported_interfaces);
> > + break;
> > + case 4:
> > + __set_bit(PHY_INTERFACE_MODE_GMII, c->supported_interfaces);
> > + phy_interface_set_rgmii(c->supported_interfaces);
> > + break;
> > + case 5 ... 6:
> > + /* SerDes ports: QSGMII/SGMII/1000BASEX/2500BASEX modes
> > + * require PCS support which is not yet implemented.
> > + */
> > + break;
> > + case 7 ... 8:
> > + /* QSGMII mode on ports 7-8 requires SerDes PCS support,
> > + * which is not yet implemented.
> > + */
> > + phy_interface_set_rgmii(c->supported_interfaces);
> > + break;
> > + default:
> > + break;
> > + }
> > +}
> > +
> > +static int lan9645x_rgmii_setup(struct lan9645x *lan9645x, int port, int speed,
> > + phy_interface_t mode)
> > +{
> > + bool tx_delay = false, rx_delay = false;
> > + u32 rx_idx, tx_idx;
> > + u8 tx_clk;
> > + int idx;
> > +
> > + /* Port 4 or 7 is RGMII_0 and port 8 is RGMII_1 */
> > + idx = port == 8 ? 1 : 0;
> > +
> > + if (!phy_interface_mode_is_rgmii(mode))
> > + return 0;
> > +
> > + tx_clk = speed == SPEED_1000 ? 1 :
> > + speed == SPEED_100 ? 2 :
> > + speed == SPEED_10 ? 3 : 0;
> > +
> > + lan_rmw(HSIO_RGMII_CFG_RGMII_RX_RST_SET(0) |
> > + HSIO_RGMII_CFG_RGMII_TX_RST_SET(0) |
> > + HSIO_RGMII_CFG_TX_CLK_CFG_SET(tx_clk),
> > + HSIO_RGMII_CFG_RGMII_RX_RST |
> > + HSIO_RGMII_CFG_RGMII_TX_RST |
> > + HSIO_RGMII_CFG_TX_CLK_CFG,
> > + lan9645x, HSIO_RGMII_CFG(idx));
> > +
> > + /* We configure delays on the MAC side. When the PHY is not responsible
> > + * for delays, the MAC is, which is why RGMII_TXID results in
> > + * rx_delay=true.
> > + */
> > + if (mode == PHY_INTERFACE_MODE_RGMII ||
> > + mode == PHY_INTERFACE_MODE_RGMII_TXID)
> > + rx_delay = true;
> > +
> > + if (mode == PHY_INTERFACE_MODE_RGMII ||
> > + mode == PHY_INTERFACE_MODE_RGMII_RXID)
> > + tx_delay = true;
>
> I'm not sure everyone would agree on this interpretation.
>
> I would not let the MAC apply internal delays based upon phy-mode.
> I would parse "rx-internal-delay-ps" and "tx-internal-delay-ps" instead,
> and leave the phy-mode to signify a PHY-only description.
>
I looked at those, but I can only turn on/off the FSM, so I did not think it
would fit to accept the delay values. But I see other drivers just look for
{rx,tx}-internal-delay-ps > 0, I can do the same here.
> > +
> > + /* Setup DLL configuration. Register layout:
> > + * 0: RGMII_0_RX
> > + * 1: RGMII_0_TX
> > + * 2: RGMII_1_RX
> > + * 3: RGMII_1_TX
> > + * ...
> > + * (N<<1) RGMII_N_RX,
> > + * (N<<1)+1: RGMII_N_TX,
> > + */
> > + rx_idx = idx << 1;
> > + tx_idx = rx_idx + 1;
> > +
> > + /* Enable DLL in RGMII clock paths, deassert DLL reset, and start the
> > + * delay tune FSM.
> > + */
> > + lan_rmw(HSIO_DLL_CFG_DLL_CLK_ENA_SET(1) |
> > + HSIO_DLL_CFG_DLL_RST_SET(0) |
> > + HSIO_DLL_CFG_DLL_ENA_SET(rx_delay) |
> > + HSIO_DLL_CFG_DELAY_ENA_SET(rx_delay),
> > + HSIO_DLL_CFG_DLL_CLK_ENA |
> > + HSIO_DLL_CFG_DLL_RST |
> > + HSIO_DLL_CFG_DLL_ENA |
> > + HSIO_DLL_CFG_DELAY_ENA,
> > + lan9645x, HSIO_DLL_CFG(rx_idx));
> > +
> > + lan_rmw(HSIO_DLL_CFG_DLL_CLK_ENA_SET(1) |
> > + HSIO_DLL_CFG_DLL_RST_SET(0) |
> > + HSIO_DLL_CFG_DLL_ENA_SET(tx_delay) |
> > + HSIO_DLL_CFG_DELAY_ENA_SET(tx_delay),
> > + HSIO_DLL_CFG_DLL_CLK_ENA |
> > + HSIO_DLL_CFG_DLL_RST |
> > + HSIO_DLL_CFG_DLL_ENA |
> > + HSIO_DLL_CFG_DELAY_ENA,
> > + lan9645x, HSIO_DLL_CFG(tx_idx));
> > +
> > + return 0;
> > +}
> > +
> > +static void lan9645x_phylink_mac_config(struct lan9645x *lan9645x, int port,
> > + unsigned int mode,
> > + const struct phylink_link_state *state)
> > +{
> > + if (phy_interface_mode_is_rgmii(state->interface))
> > + lan9645x_rgmii_setup(lan9645x, port, state->speed,
> > + state->interface);
> > +}
> > +
> > +static int lan9645x_phylink_mac_prepare(struct lan9645x *lan9645x, int port,
> > + unsigned int mode,
> > + phy_interface_t iface)
> > +{
> > + switch (port) {
> > + case 0 ... 3:
> > + lan_rmw(HSIO_HW_CFG_GMII_ENA_SET(BIT(port)),
> > + HSIO_HW_CFG_GMII_ENA_SET(BIT(port)), lan9645x,
> > + HSIO_HW_CFG);
> > + break;
> > + case 4:
> > + lan_rmw(HSIO_HW_CFG_GMII_ENA_SET(BIT(port)),
> > + HSIO_HW_CFG_GMII_ENA_SET(BIT(port)), lan9645x,
> > + HSIO_HW_CFG);
> > +
> > + if (phy_interface_mode_is_rgmii(iface))
> > + lan_rmw(HSIO_HW_CFG_RGMII_0_CFG_SET(1),
> > + HSIO_HW_CFG_RGMII_0_CFG,
> > + lan9645x, HSIO_HW_CFG);
> > +
> > + break;
> > + case 7 ... 8:
> > + lan_rmw(HSIO_HW_CFG_GMII_ENA_SET(BIT(port)),
> > + HSIO_HW_CFG_GMII_ENA_SET(BIT(port)), lan9645x,
> > + HSIO_HW_CFG);
> > + break;
> > + default:
> > + /* Ports 5-6 are SerDes-only and need PCS support (not yet
> > + * implemented). They are excluded from phylink_get_caps.
> > + */
> > + return -EINVAL;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int lan9645x_port_is_cuphy(struct lan9645x *lan9645x, int port,
> > + phy_interface_t interface)
> > +{
> > + return port >= 0 && port <= 4 && interface == PHY_INTERFACE_MODE_GMII;
> > +}
> > +
> > +static void lan9645x_phylink_mac_link_up(struct lan9645x *lan9645x, int port,
> > + unsigned int link_an_mode,
> > + phy_interface_t interface,
> > + struct phy_device *phydev, int speed,
> > + int duplex, bool tx_pause,
> > + bool rx_pause)
> > +{
> > + struct lan9645x_port *p = lan9645x_to_port(lan9645x, port);
> > + int rx_ifg1, rx_ifg2, tx_ifg, gtx_clk = 0;
> > + struct lan9645x_path_delay *path_delay;
> > + int gspeed = LAN9645X_SPEED_DISABLED;
> > + int mode = 0;
> > + int fc_spd;
> > +
> > + /* Configure speed for RGMII modules. */
> > + if (phy_interface_mode_is_rgmii(interface))
> > + lan9645x_rgmii_setup(lan9645x, port, speed, interface);
> > +
> > + if (duplex == DUPLEX_FULL) {
> > + mode |= DEV_MAC_MODE_CFG_FDX_ENA_SET(1);
> > + rx_ifg2 = DEV_MAC_IFG_CFG_RX_IFG2_SET(0x2);
> > + tx_ifg = DEV_MAC_IFG_CFG_TX_IFG_SET(0x5);
> > +
> > + } else {
> > + rx_ifg2 = DEV_MAC_IFG_CFG_RX_IFG2_SET(0x2);
> > + tx_ifg = DEV_MAC_IFG_CFG_TX_IFG_SET(0x6);
> > + }
> > +
> > + switch (speed) {
> > + case SPEED_10:
> > + rx_ifg1 = DEV_MAC_IFG_CFG_RX_IFG1_SET(0x2);
> > + gspeed = LAN9645X_SPEED_10;
> > + break;
> > + case SPEED_100:
> > + rx_ifg1 = DEV_MAC_IFG_CFG_RX_IFG1_SET(0x1);
> > + gspeed = LAN9645X_SPEED_100;
> > + break;
> > + case SPEED_1000:
> > + gspeed = LAN9645X_SPEED_1000;
> > + mode |= DEV_MAC_MODE_CFG_GIGA_MODE_ENA_SET(1);
> > + mode |= DEV_MAC_MODE_CFG_FDX_ENA_SET(1);
> > + rx_ifg1 = DEV_MAC_IFG_CFG_RX_IFG1_SET(0x1);
> > + rx_ifg2 = DEV_MAC_IFG_CFG_RX_IFG2_SET(0x2);
> > + tx_ifg = DEV_MAC_IFG_CFG_TX_IFG_SET(0x6);
> > + gtx_clk = 1;
> > + break;
> > + case SPEED_2500:
> > + gspeed = LAN9645X_SPEED_2500;
> > + mode |= DEV_MAC_MODE_CFG_GIGA_MODE_ENA_SET(1);
> > + mode |= DEV_MAC_MODE_CFG_FDX_ENA_SET(1);
> > + rx_ifg1 = DEV_MAC_IFG_CFG_RX_IFG1_SET(0x1);
> > + rx_ifg2 = DEV_MAC_IFG_CFG_RX_IFG2_SET(0x2);
> > + tx_ifg = DEV_MAC_IFG_CFG_TX_IFG_SET(0x6);
> > + break;
> > + default:
> > + dev_err(lan9645x->dev, "Unsupported speed on port %d: %d\n",
> > + p->chip_port, speed);
> > + return;
> > + }
> > +
> > + p->speed = gspeed;
> > + p->duplex = duplex;
> > + fc_spd = lan9645x_speed_fc_enc(p->speed);
> > +
> > + if (phy_interface_num_ports(interface) == 4 ||
> > + interface == PHY_INTERFACE_MODE_SGMII)
> > + mode |= DEV_MAC_MODE_CFG_GIGA_MODE_ENA_SET(1);
> > +
> > + lan_rmw(mode,
> > + DEV_MAC_MODE_CFG_FDX_ENA |
> > + DEV_MAC_MODE_CFG_GIGA_MODE_ENA,
> > + lan9645x, DEV_MAC_MODE_CFG(p->chip_port));
> > +
> > + lan_rmw(tx_ifg | rx_ifg1 | rx_ifg2,
> > + DEV_MAC_IFG_CFG_TX_IFG |
> > + DEV_MAC_IFG_CFG_RX_IFG1 |
> > + DEV_MAC_IFG_CFG_RX_IFG2,
> > + lan9645x, DEV_MAC_IFG_CFG(p->chip_port));
> > +
> > + lan_rmw(DEV_MAC_HDX_CFG_SEED_SET(p->chip_port) |
> > + DEV_MAC_HDX_CFG_SEED_LOAD_SET(1),
> > + DEV_MAC_HDX_CFG_SEED |
> > + DEV_MAC_HDX_CFG_SEED_LOAD, lan9645x,
> > + DEV_MAC_HDX_CFG(p->chip_port));
> > +
> > + if (lan9645x_port_is_cuphy(lan9645x, port, interface)) {
> > + lan_rmw(CHIP_TOP_CUPHY_PORT_CFG_GTX_CLK_ENA_SET(gtx_clk),
> > + CHIP_TOP_CUPHY_PORT_CFG_GTX_CLK_ENA, lan9645x,
> > + CHIP_TOP_CUPHY_PORT_CFG(p->chip_port));
> > + }
> > +
> > + lan_rmw(DEV_MAC_HDX_CFG_SEED_LOAD_SET(0),
> > + DEV_MAC_HDX_CFG_SEED_LOAD, lan9645x,
> > + DEV_MAC_HDX_CFG(p->chip_port));
> > +
> > + /* Set PFC link speed and enable map */
> > + lan_rmw(ANA_PFC_CFG_FC_LINK_SPEED_SET(fc_spd) |
> > + ANA_PFC_CFG_RX_PFC_ENA_SET(0),
> > + ANA_PFC_CFG_FC_LINK_SPEED |
> > + ANA_PFC_CFG_RX_PFC_ENA,
> > + lan9645x, ANA_PFC_CFG(p->chip_port));
> > +
> > + lan_rmw(DEV_PCS1G_CFG_PCS_ENA_SET(1),
> > + DEV_PCS1G_CFG_PCS_ENA, lan9645x,
> > + DEV_PCS1G_CFG(p->chip_port));
> > +
> > + lan_rmw(DEV_PCS1G_SD_CFG_SD_ENA_SET(0),
> > + DEV_PCS1G_SD_CFG_SD_ENA,
> > + lan9645x, DEV_PCS1G_SD_CFG(p->chip_port));
> > +
> > + lan_rmw(SYS_PAUSE_CFG_PAUSE_ENA_SET(1),
> > + SYS_PAUSE_CFG_PAUSE_ENA,
> > + lan9645x, SYS_PAUSE_CFG(p->chip_port));
> > +
> > + /* Set SMAC of Pause frame (00:00:00:00:00:00) */
> > + lan_wr(0, lan9645x, DEV_FC_MAC_LOW_CFG(p->chip_port));
> > + lan_wr(0, lan9645x, DEV_FC_MAC_HIGH_CFG(p->chip_port));
> > +
> > + /* Flow control */
> > + lan_rmw(SYS_MAC_FC_CFG_FC_LINK_SPEED_SET(fc_spd) |
> > + SYS_MAC_FC_CFG_FC_LATENCY_CFG_SET(0x7) |
> > + SYS_MAC_FC_CFG_ZERO_PAUSE_ENA_SET(1) |
> > + SYS_MAC_FC_CFG_PAUSE_VAL_CFG_SET(0xffff) |
> > + SYS_MAC_FC_CFG_RX_FC_ENA_SET(rx_pause ? 1 : 0) |
> > + SYS_MAC_FC_CFG_TX_FC_ENA_SET(tx_pause ? 1 : 0),
> > + SYS_MAC_FC_CFG_FC_LINK_SPEED |
> > + SYS_MAC_FC_CFG_FC_LATENCY_CFG |
> > + SYS_MAC_FC_CFG_ZERO_PAUSE_ENA |
> > + SYS_MAC_FC_CFG_PAUSE_VAL_CFG |
> > + SYS_MAC_FC_CFG_RX_FC_ENA |
> > + SYS_MAC_FC_CFG_TX_FC_ENA,
> > + lan9645x, SYS_MAC_FC_CFG(p->chip_port));
> > +
> > + list_for_each_entry(path_delay, &p->path_delays, list) {
> > + if (path_delay->speed == speed) {
> > + lan_wr(path_delay->rx_delay + p->rx_delay,
> > + lan9645x, SYS_PTP_RXDLY_CFG(p->chip_port));
> > + lan_wr(path_delay->tx_delay,
> > + lan9645x, SYS_PTP_TXDLY_CFG(p->chip_port));
> > + }
> > + }
> > +
> > + /* Enable MAC module */
> > + lan_wr(DEV_MAC_ENA_CFG_RX_ENA_SET(1) |
> > + DEV_MAC_ENA_CFG_TX_ENA_SET(1),
> > + lan9645x, DEV_MAC_ENA_CFG(p->chip_port));
> > +
> > + /* port _must_ be taken out of reset before MAC. */
> > + lan_rmw(DEV_CLOCK_CFG_PORT_RST_SET(0),
> > + DEV_CLOCK_CFG_PORT_RST,
> > + lan9645x, DEV_CLOCK_CFG(p->chip_port));
> > +
> > + /* Take out the clock from reset. Note this write will set all these
> > + * fields to zero:
> > + *
> > + * DEV_CLOCK_CFG[*].MAC_TX_RST
> > + * DEV_CLOCK_CFG[*].MAC_RX_RST
> > + * DEV_CLOCK_CFG[*].PCS_TX_RST
> > + * DEV_CLOCK_CFG[*].PCS_RX_RST
> > + * DEV_CLOCK_CFG[*].PORT_RST
> > + * DEV_CLOCK_CFG[*].PHY_RST
> > + *
> > + * Note link_down will assert PORT_RST, MAC_RX_RST and MAC_TX_RST, so
> > + * we are effectively taking the mac tx/rx clocks out of reset.
> > + *
> > + * This linkspeed field has a slightly different encoding from others:
> > + *
> > + * - 0 is no-link
> > + * - 1 is both 2500/1000
> > + * - 2 is 100mbit
> > + * - 3 is 10mbit
> > + *
> > + */
> > + lan_wr(DEV_CLOCK_CFG_LINK_SPEED_SET(fc_spd == 0 ? 1 : fc_spd),
> > + lan9645x,
> > + DEV_CLOCK_CFG(p->chip_port));
> > +
> > + /* Core: Enable port for frame transfer */
> > + lan_rmw(QSYS_SW_PORT_MODE_PORT_ENA_SET(1) |
> > + QSYS_SW_PORT_MODE_SCH_NEXT_CFG_SET(1) |
> > + QSYS_SW_PORT_MODE_INGRESS_DROP_MODE_SET(1) |
> > + QSYS_SW_PORT_MODE_TX_PFC_ENA_SET(0),
> > + QSYS_SW_PORT_MODE_PORT_ENA |
> > + QSYS_SW_PORT_MODE_SCH_NEXT_CFG |
> > + QSYS_SW_PORT_MODE_INGRESS_DROP_MODE |
> > + QSYS_SW_PORT_MODE_TX_PFC_ENA,
> > + lan9645x, QSYS_SW_PORT_MODE(p->chip_port));
> > +
> > + lan_rmw(AFI_PORT_CFG_FC_SKIP_TTI_INJ_SET(0) |
> > + AFI_PORT_CFG_FRM_OUT_MAX_SET(16),
> > + AFI_PORT_CFG_FC_SKIP_TTI_INJ |
> > + AFI_PORT_CFG_FRM_OUT_MAX,
> > + lan9645x, AFI_PORT_CFG(p->chip_port));
> > +}
> > +
> > +void lan9645x_phylink_port_down(struct lan9645x *lan9645x, int port)
> > +{
> > + struct lan9645x_port *p = lan9645x_to_port(lan9645x, port);
> > + u32 val;
> > +
> > + /* 0.5: Disable any AFI */
> > + lan_rmw(AFI_PORT_CFG_FC_SKIP_TTI_INJ_SET(1) |
> > + AFI_PORT_CFG_FRM_OUT_MAX_SET(0),
> > + AFI_PORT_CFG_FC_SKIP_TTI_INJ |
> > + AFI_PORT_CFG_FRM_OUT_MAX,
> > + lan9645x, AFI_PORT_CFG(p->chip_port));
> > +
> > + /* wait for reg afi_port_frm_out to become 0 for the port */
> > + if (lan9645x_rd_poll_slow(lan9645x, AFI_PORT_FRM_OUT(p->chip_port),
> > + val,
> > + !AFI_PORT_FRM_OUT_FRM_OUT_CNT_GET(val)))
> > + dev_err(lan9645x->dev, "AFI timeout chip port %u",
> > + p->chip_port);
> > +
> > + /* 2: Disable MAC frame reception */
> > + lan_rmw(DEV_MAC_ENA_CFG_RX_ENA_SET(0),
> > + DEV_MAC_ENA_CFG_RX_ENA,
> > + lan9645x, DEV_MAC_ENA_CFG(p->chip_port));
> > +
> > + /* 1: Reset the PCS Rx clock domain */
> > + lan_rmw(DEV_CLOCK_CFG_PCS_RX_RST_SET(1),
> > + DEV_CLOCK_CFG_PCS_RX_RST,
> > + lan9645x, DEV_CLOCK_CFG(p->chip_port));
> > +
> > + p->speed = LAN9645X_SPEED_DISABLED;
> > + p->duplex = DUPLEX_UNKNOWN;
> > +
> > + /* 3: Disable traffic being sent to or from switch port */
> > + lan_rmw(QSYS_SW_PORT_MODE_PORT_ENA_SET(0),
> > + QSYS_SW_PORT_MODE_PORT_ENA,
> > + lan9645x, QSYS_SW_PORT_MODE(p->chip_port));
> > +
> > + /* 4: Disable dequeuing from the egress queues */
> > + lan_rmw(QSYS_PORT_MODE_DEQUEUE_DIS_SET(1),
> > + QSYS_PORT_MODE_DEQUEUE_DIS,
> > + lan9645x, QSYS_PORT_MODE(p->chip_port));
> > +
> > + /* 5: Disable Flowcontrol */
> > + lan_rmw(SYS_PAUSE_CFG_PAUSE_ENA_SET(0),
> > + SYS_PAUSE_CFG_PAUSE_ENA,
> > + lan9645x, SYS_PAUSE_CFG(p->chip_port));
> > +
> > + /* 5.1: Disable PFC */
> > + lan_rmw(QSYS_SW_PORT_MODE_TX_PFC_ENA_SET(0),
> > + QSYS_SW_PORT_MODE_TX_PFC_ENA,
> > + lan9645x, QSYS_SW_PORT_MODE(p->chip_port));
> > +
> > + /* 6: Wait a worst case time 8ms (10K jumbo/10Mbit) */
> > + usleep_range(8 * USEC_PER_MSEC, 9 * USEC_PER_MSEC);
> > +
> > + /* 7: Disable HDX backpressure. */
> > + lan_rmw(SYS_FRONT_PORT_MODE_HDX_MODE_SET(0),
> > + SYS_FRONT_PORT_MODE_HDX_MODE,
> > + lan9645x, SYS_FRONT_PORT_MODE(p->chip_port));
> > +
> > + /* 8: Flush the queues associated with the port */
> > + lan_rmw(QSYS_SW_PORT_MODE_AGING_MODE_SET(3),
> > + QSYS_SW_PORT_MODE_AGING_MODE,
> > + lan9645x, QSYS_SW_PORT_MODE(p->chip_port));
> > +
> > + /* 9: Enable dequeuing from the egress queues */
> > + lan_rmw(QSYS_PORT_MODE_DEQUEUE_DIS_SET(0),
> > + QSYS_PORT_MODE_DEQUEUE_DIS,
> > + lan9645x, QSYS_PORT_MODE(p->chip_port));
> > +
> > + /* 10: Wait until flushing is complete */
> > + if (lan9645x_rd_poll_slow(lan9645x, QSYS_SW_STATUS(p->chip_port),
> > + val, !QSYS_SW_STATUS_EQ_AVAIL_GET(val)))
> > + dev_err(lan9645x->dev, "Flush timeout chip port %u", port);
> > +
> > + /* 11: Disable MAC tx */
> > + lan_rmw(DEV_MAC_ENA_CFG_TX_ENA_SET(0),
> > + DEV_MAC_ENA_CFG_TX_ENA,
> > + lan9645x, DEV_MAC_ENA_CFG(p->chip_port));
> > +
> > + /* 12: Reset the Port and MAC clock domains */
> > + lan_rmw(DEV_CLOCK_CFG_PORT_RST_SET(1),
> > + DEV_CLOCK_CFG_PORT_RST,
> > + lan9645x, DEV_CLOCK_CFG(p->chip_port));
> > +
> > + /* Wait before resetting MAC clock domains. */
> > + usleep_range(USEC_PER_MSEC, 2 * USEC_PER_MSEC);
> > +
> > + lan_rmw(DEV_CLOCK_CFG_MAC_TX_RST_SET(1) |
> > + DEV_CLOCK_CFG_MAC_RX_RST_SET(1) |
> > + DEV_CLOCK_CFG_PORT_RST_SET(1),
> > + DEV_CLOCK_CFG_MAC_TX_RST |
> > + DEV_CLOCK_CFG_MAC_RX_RST |
> > + DEV_CLOCK_CFG_PORT_RST,
> > + lan9645x, DEV_CLOCK_CFG(p->chip_port));
> > +
> > + /* 13: Clear flushing */
> > + lan_rmw(QSYS_SW_PORT_MODE_AGING_MODE_SET(1),
> > + QSYS_SW_PORT_MODE_AGING_MODE,
> > + lan9645x, QSYS_SW_PORT_MODE(p->chip_port));
> > +}
> > +
> > +static void lan9645x_phylink_mac_link_down(struct lan9645x *lan9645x, int port,
> > + unsigned int link_an_mode,
> > + phy_interface_t interface)
> > +{
> > + struct lan9645x_port *p = lan9645x_to_port(lan9645x, port);
> > +
> > + lan9645x_phylink_port_down(lan9645x, port);
> > +
> > + /* 14: Take PCS out of reset */
> > + lan_rmw(DEV_CLOCK_CFG_PCS_RX_RST_SET(0) |
> > + DEV_CLOCK_CFG_PCS_TX_RST_SET(0),
> > + DEV_CLOCK_CFG_PCS_RX_RST |
> > + DEV_CLOCK_CFG_PCS_TX_RST,
> > + lan9645x, DEV_CLOCK_CFG(p->chip_port));
> > +}
> > +
> > +static struct lan9645x_port *
> > +lan9645x_phylink_config_to_port(struct phylink_config *config)
> > +{
> > + struct dsa_port *dp = dsa_phylink_to_port(config);
> > +
> > + return lan9645x_to_port(dp->ds->priv, dp->index);
> > +}
> > +
> > +static void
> > +lan9645x_port_phylink_mac_config(struct phylink_config *config,
> > + unsigned int mode,
> > + const struct phylink_link_state *state)
> > +{
> > + struct lan9645x_port *p = lan9645x_phylink_config_to_port(config);
> > +
> > + lan9645x_phylink_mac_config(p->lan9645x, p->chip_port, mode, state);
>
> Why the separate functions here and in all other phylink_mac_ops implementations?
>
It is not needed, I will skip the indirection.
> > +}
> > +
> > +static void lan9645x_port_phylink_mac_link_up(struct phylink_config *config,
> > + struct phy_device *phydev,
> > + unsigned int link_an_mode,
> > + phy_interface_t interface,
> > + int speed, int duplex,
> > + bool tx_pause, bool rx_pause)
> > +{
> > + struct lan9645x_port *p = lan9645x_phylink_config_to_port(config);
> > +
> > + lan9645x_phylink_mac_link_up(p->lan9645x, p->chip_port, link_an_mode,
> > + interface, phydev, speed, duplex, tx_pause,
> > + rx_pause);
> > +}
> > +
> > +static void lan9645x_port_phylink_mac_link_down(struct phylink_config *config,
> > + unsigned int link_an_mode,
> > + phy_interface_t interface)
> > +{
> > + struct lan9645x_port *p = lan9645x_phylink_config_to_port(config);
> > +
> > + lan9645x_phylink_mac_link_down(p->lan9645x, p->chip_port, link_an_mode,
> > + interface);
> > +}
> > +
> > +static int lan9645x_port_phylink_mac_prepare(struct phylink_config *config,
> > + unsigned int mode,
> > + phy_interface_t iface)
> > +{
> > + struct lan9645x_port *p = lan9645x_phylink_config_to_port(config);
> > +
> > + return lan9645x_phylink_mac_prepare(p->lan9645x, p->chip_port, mode,
> > + iface);
> > +}
> > +
> > +const struct phylink_mac_ops lan9645x_phylink_mac_ops = {
> > + .mac_config = lan9645x_port_phylink_mac_config,
> > + .mac_link_up = lan9645x_port_phylink_mac_link_up,
> > + .mac_link_down = lan9645x_port_phylink_mac_link_down,
> > + .mac_prepare = lan9645x_port_phylink_mac_prepare,
> > +};
> > diff --git a/drivers/net/dsa/microchip/lan9645x/lan9645x_port.c b/drivers/net/dsa/microchip/lan9645x/lan9645x_port.c
> > new file mode 100644
> > index 000000000000..038868ae0a32
> > --- /dev/null
> > +++ b/drivers/net/dsa/microchip/lan9645x/lan9645x_port.c
> > @@ -0,0 +1,289 @@
> > +// SPDX-License-Identifier: GPL-2.0+
> > +/* Copyright (C) 2026 Microchip Technology Inc.
> > + */
> > +
> > +#include "lan9645x_main.h"
> > +
> > +int lan9645x_port_init(struct lan9645x *lan9645x, int port)
> > +{
> > + struct lan9645x_port *p;
> > +
> > + p = lan9645x_to_port(lan9645x, port);
> > +
> > + /* Disable learning on port */
> > + lan_rmw(ANA_PORT_CFG_LEARN_ENA_SET(0),
> > + ANA_PORT_CFG_LEARN_ENA,
> > + lan9645x, ANA_PORT_CFG(p->chip_port));
> > +
> > + lan9645x_port_set_maxlen(lan9645x, port, ETH_DATA_LEN);
> > +
> > + lan9645x_phylink_port_down(lan9645x, port);
> > +
> > + if (phy_interface_num_ports(p->phy_mode) == 4)
> > + lan_rmw(DEV_CLOCK_CFG_PCS_RX_RST_SET(0) |
> > + DEV_CLOCK_CFG_PCS_TX_RST_SET(0),
> > + DEV_CLOCK_CFG_PCS_RX_RST |
> > + DEV_CLOCK_CFG_PCS_TX_RST,
> > + lan9645x, DEV_CLOCK_CFG(p->chip_port));
> > +
> > + /* Drop frames with multicast source address */
> > + lan_rmw(ANA_DROP_CFG_DROP_MC_SMAC_ENA_SET(1),
> > + ANA_DROP_CFG_DROP_MC_SMAC_ENA, lan9645x,
> > + ANA_DROP_CFG(p->chip_port));
> > +
> > + /* Enable receiving frames on the port, and activate auto-learning of
> > + * MAC addresses.
> > + */
> > + lan_rmw(ANA_PORT_CFG_LEARNAUTO_SET(1) |
> > + ANA_PORT_CFG_RECV_ENA_SET(1) |
> > + ANA_PORT_CFG_PORTID_VAL_SET(p->chip_port),
> > + ANA_PORT_CFG_LEARNAUTO |
>
> How does ANA_PORT_CFG_LEARN_ENA (disabled above) interact with
> ANA_PORT_CFG_LEARNAUTO (enabled here)?
>
> Judging from the comments only, this configuration seems pretty
> inconsistent. Non-bridged user ports should have address learning turned
> off.
>
If LEARN_ENA is cleared, LEARNAUTO is ignored. I will add a comment about this.
> > + ANA_PORT_CFG_RECV_ENA |
> > + ANA_PORT_CFG_PORTID_VAL,
> > + lan9645x, ANA_PORT_CFG(p->chip_port));
> > +
> > + return 0;
> > +}
> > +
> > +void lan9645x_port_cpu_init(struct lan9645x *lan9645x)
> > +{
> > + /* Map the 8 CPU extraction queues to CPU port 9 (datasheet is wrong) */
> > + lan_wr(0, lan9645x, QSYS_CPU_GROUP_MAP);
> > +
> > + /* Configure second cpu port (chip_port 10) for manual frame injection.
> > + * The AFI can not inject frames via the NPI port, unless frame aging is
> > + * disabled on frontports, so we use manual injection for AFI frames.
> > + */
>
> AFI stands for? And what is it used for?
>
It stands for Automatric Frame Injector. It can inject frames in either a timer
triggered manner or delay triggered, with possibility to modify it with the
rewriter. It it not used here, but would be used for MRP for instance.
I will remove this.
> > +
> > + /* Set min-spacing of EOF to SOF on injected frames to 0, on cpu device
> > + * 1. This is required when injecting with IFH.
> > + * Default values emulates delay of std preamble/IFG setting on a front
> > + * port.
> > + */
> > + lan_rmw(QS_INJ_CTRL_GAP_SIZE_SET(0),
> > + QS_INJ_CTRL_GAP_SIZE,
> > + lan9645x, QS_INJ_CTRL(1));
> > +
> > + /* Injection: Mode: manual injection | Byte_swap */
> > + lan_wr(QS_INJ_GRP_CFG_MODE_SET(1) |
> > + QS_INJ_GRP_CFG_BYTE_SWAP_SET(1),
> > + lan9645x, QS_INJ_GRP_CFG(1));
> > +
> > + lan_rmw(QS_INJ_CTRL_GAP_SIZE_SET(0),
> > + QS_INJ_CTRL_GAP_SIZE,
> > + lan9645x, QS_INJ_CTRL(1));
> > +
> > + lan_wr(SYS_PORT_MODE_INCL_INJ_HDR_SET(1),
> > + lan9645x, SYS_PORT_MODE(CPU_PORT + 1));
> > +
> > + /* The CPU will only use its reserved buffer in the shared queue system
> > + * and none of the shared buffer space, therefore we disable resource
> > + * sharing in egress direction. We must not disable resource sharing in
> > + * the ingress direction, because some traffic test scenarios require
> > + * loads of buffer memory for frames initiated by the CPU.
> > + */
> > + lan_rmw(QSYS_EGR_NO_SHARING_EGR_NO_SHARING_SET(BIT(CPU_PORT)),
> > + QSYS_EGR_NO_SHARING_EGR_NO_SHARING_SET(BIT(CPU_PORT)),
> > + lan9645x, QSYS_EGR_NO_SHARING);
> > +
> > + /* The CPU should also discard frames forwarded to it if it has run
> > + * out of the reserved buffer space. Otherwise they will be held back
> > + * in the ingress queues with potential head-of-line blocking effects.
> > + */
> > + lan_rmw(QSYS_EGR_DROP_MODE_EGRESS_DROP_MODE_SET(BIT(CPU_PORT)),
> > + QSYS_EGR_DROP_MODE_EGRESS_DROP_MODE_SET(BIT(CPU_PORT)),
> > + lan9645x, QSYS_EGR_DROP_MODE);
> > +
> > + lan_wr(BIT(CPU_PORT), lan9645x, ANA_PGID(PGID_CPU));
> > +
> > + lan_rmw(ANA_PORT_CFG_PORTID_VAL_SET(CPU_PORT) |
> > + ANA_PORT_CFG_RECV_ENA_SET(1),
> > + ANA_PORT_CFG_PORTID_VAL |
> > + ANA_PORT_CFG_RECV_ENA, lan9645x,
> > + ANA_PORT_CFG(CPU_PORT));
> > +
> > + /* Enable switching to/from cpu port. Keep default aging-mode. */
> > + lan_rmw(QSYS_SW_PORT_MODE_PORT_ENA_SET(1) |
> > + QSYS_SW_PORT_MODE_SCH_NEXT_CFG_SET(1) |
> > + QSYS_SW_PORT_MODE_INGRESS_DROP_MODE_SET(1),
> > + QSYS_SW_PORT_MODE_PORT_ENA |
> > + QSYS_SW_PORT_MODE_SCH_NEXT_CFG |
> > + QSYS_SW_PORT_MODE_INGRESS_DROP_MODE,
> > + lan9645x, QSYS_SW_PORT_MODE(CPU_PORT));
> > +
> > + /* Transmit cpu frames as received without any tagging, timing or other
> > + * updates. This does not affect CPU-over-NPI, only manual extraction.
> > + * On the NPI port we need NO_REWRITE=0 for HSR/PRP.
> > + */
> > + lan_wr(REW_PORT_CFG_NO_REWRITE_SET(1),
> > + lan9645x, REW_PORT_CFG(CPU_PORT));
> > +}
> > +
> > +void lan9645x_port_set_tail_drop_wm(struct lan9645x *lan9645x)
> > +{
> > + int shared_per_port;
> > + int port;
> > +
> > + /* Configure tail dropping watermark */
> > + shared_per_port =
> > + lan9645x->shared_queue_sz / (lan9645x->num_phys_ports + 1);
> > +
> > + /* The total memory size is diveded by number of front ports plus CPU
>
> divided
>
> > + * port.
> > + */
> > + lan9645x_for_each_chipport(lan9645x, port)
> > + lan_wr(lan9645x_wm_enc(shared_per_port), lan9645x,
> > + SYS_ATOP(port));
> > +
> > + /* Tail dropping active based only on per port ATOP wm */
> > + lan_wr(lan9645x_wm_enc(lan9645x->shared_queue_sz), lan9645x,
> > + SYS_ATOP_TOT_CFG);
> > +}
> > +
> > +int lan9645x_port_set_maxlen(struct lan9645x *lan9645x, int port, size_t sdu)
> > +{
> > + struct lan9645x_port *p = lan9645x_to_port(lan9645x, port);
> > +
>
> No blank lines between local variable declarations.
>
> > + int maxlen = sdu + ETH_HLEN + ETH_FCS_LEN;
> > +
> > + if (port == lan9645x->npi) {
> > + maxlen += LAN9645X_IFH_LEN;
> > + maxlen += LAN9645X_LONG_PREFIX_LEN;
> > + }
> > +
> > + lan_wr(DEV_MAC_MAXLEN_CFG_MAX_LEN_SET(maxlen), lan9645x,
> > + DEV_MAC_MAXLEN_CFG(p->chip_port));
> > +
> > + /* Set Pause WM hysteresis */
> > + lan_rmw(SYS_PAUSE_CFG_PAUSE_STOP_SET(lan9645x_wm_enc(4 * maxlen)) |
> > + SYS_PAUSE_CFG_PAUSE_START_SET(lan9645x_wm_enc(6 * maxlen)),
> > + SYS_PAUSE_CFG_PAUSE_START |
> > + SYS_PAUSE_CFG_PAUSE_STOP,
> > + lan9645x,
> > + SYS_PAUSE_CFG(p->chip_port));
> > +
> > + return 0;
> > +}
> > +
> > +static int lan9645x_port_setup_leds(struct lan9645x *lan9645x,
> > + struct fwnode_handle *portnp, int port)
> > +{
> > + u32 drive_mode;
> > + int err;
> > +
> > + err = fwnode_property_read_u32(portnp, "microchip,led-drive-mode",
> > + &drive_mode);
> > + if (err)
> > + return err;
>
> This property is mandatory? 1. the schema doesn't put it in "required",
> and 2. why would it be mandatory?
>
No that is a mistake, the error is ignored now. Now this be reworked.
> > +
> > + lan_rmw(CHIP_TOP_CUPHY_LED_CFG_LED_DRIVE_MODE_SET(drive_mode),
> > + CHIP_TOP_CUPHY_LED_CFG_LED_DRIVE_MODE, lan9645x,
> > + CHIP_TOP_CUPHY_LED_CFG(port));
> > +
> > + return 0;
> > +}
> > +
> > +static int lan9645x_port_parse_delays(struct lan9645x_port *port,
> > + struct fwnode_handle *portnp)
> > +{
> > + struct fwnode_handle *delay;
> > + int err;
> > +
> > + INIT_LIST_HEAD(&port->path_delays);
> > +
> > + fwnode_for_each_available_child_node(portnp, delay) {
> > + struct lan9645x_path_delay *path_delay;
> > + s32 tx_delay;
> > + s32 rx_delay;
> > + u32 speed;
> > +
> > + err = fwnode_property_read_u32(delay, "speed", &speed);
> > + if (err)
> > + return err;
> > +
> > + err = fwnode_property_read_u32(delay, "rx_delay", &rx_delay);
> > + if (err)
> > + return err;
> > +
> > + err = fwnode_property_read_u32(delay, "tx_delay", &tx_delay);
> > + if (err)
> > + return err;
> > +
> > + path_delay = devm_kzalloc(port->lan9645x->dev,
> > + sizeof(*path_delay), GFP_KERNEL);
> > + if (!path_delay)
> > + return -ENOMEM;
> > +
> > + path_delay->rx_delay = rx_delay;
> > + path_delay->tx_delay = tx_delay;
> > + path_delay->speed = speed;
> > + list_add_tail(&path_delay->list, &port->path_delays);
>
> Show prior art about having such device tree properties, or defer this
> feature to a separate series where the PTP maintainer is also on CC, and
> properly explain your end goal and reasoning process towards this solution.
>
I will remove the path delay code.
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +int lan9645x_port_parse_ports_node(struct lan9645x *lan9645x)
> > +{
> > + struct fwnode_handle *ports, *portnp;
> > + struct device *dev = lan9645x->dev;
> > + int max_ports, num_ports = 0;
> > + int err = 0;
> > +
> > + max_ports = NUM_PHYS_PORTS - lan9645x->num_port_dis;
> > +
> > + ports = device_get_named_child_node(dev, "ethernet-ports");
> > + if (!ports)
> > + ports = device_get_named_child_node(dev, "ports");
> > + if (!ports) {
> > + dev_err(dev, "no ethernet-ports or ports child found\n");
> > + return -ENODEV;
> > + }
> > +
> > + fwnode_for_each_available_child_node(ports, portnp) {
> > + int phy_mode;
> > + u32 p;
> > +
> > + num_ports++;
> > +
> > + if (num_ports > max_ports) {
> > + dev_err(dev,
> > + "Too many ports in device tree. Max ports supported by SKU: %d\n",
> > + max_ports);
> > + err = -ENODEV;
> > + goto err_free_ports;
> > + }
> > +
> > + if (fwnode_property_read_u32(portnp, "reg", &p)) {
> > + dev_err(dev, "Port number not defined in device tree (property \"reg\")\n");
> > + err = -ENODEV;
> > + fwnode_handle_put(portnp);
> > + goto err_free_ports;
> > + }
> > +
> > + if (p >= lan9645x->num_phys_ports) {
> > + dev_err(dev,
> > + "Port number in device tree is invalid %u (property \"reg\")\n",
> > + p);
> > + err = -ENODEV;
> > + fwnode_handle_put(portnp);
> > + goto err_free_ports;
> > + }
> > +
> > + phy_mode = fwnode_get_phy_mode(portnp);
> > + if (phy_mode < 0) {
> > + dev_err(dev, "Failed to read phy-mode for port %u", p);
> > + err = -ENODEV;
> > + fwnode_handle_put(portnp);
> > + goto err_free_ports;
> > + }
> > +
> > + lan9645x->ports[p]->phy_mode = phy_mode;
>
> I have pending patches which remove this coding pattern from the ocelot
> driver. Please work with the phy_mode that phylink gives you in mac_config.
> Do not expect it to remain fixed throughout the lifetime of the device.
>
Ok I will rework this.
> > + lan9645x_port_parse_delays(lan9645x->ports[p], portnp);
> > + lan9645x_port_setup_leds(lan9645x, portnp, p);
>
> FWIW you can also implement .port_setup(), get dp = dsa_to_port(ds, port),
> and just call lan9645x_port_setup_leds(dp->dn). You don't need the
> complicated iteration over ports starting from the root switch node.
>
Thank you I will look into that. I thought port_setup was only for devlink
related configuration.
> > + }
> > +
> > +err_free_ports:
> > + fwnode_handle_put(ports);
> > + return err;
> > +}
> > diff --git a/drivers/net/ethernet/microchip/Kconfig b/drivers/net/ethernet/microchip/Kconfig
> > index ee046468652c..740f3c2e8199 100644
> > --- a/drivers/net/ethernet/microchip/Kconfig
> > +++ b/drivers/net/ethernet/microchip/Kconfig
> > @@ -62,5 +62,6 @@ source "drivers/net/ethernet/microchip/lan966x/Kconfig"
> > source "drivers/net/ethernet/microchip/sparx5/Kconfig"
> > source "drivers/net/ethernet/microchip/vcap/Kconfig"
> > source "drivers/net/ethernet/microchip/fdma/Kconfig"
> > +source "drivers/net/dsa/microchip/lan9645x/Kconfig"
>
> The Kconfig of DSA drivers needs to be pulled in through drivers/net/dsa/Kconfig.
> Put the proper "depends on" statements in place, if you're missing any.
>
I will move this.
> >
> > endif # NET_VENDOR_MICROCHIP
> >
> > --
> > 2.52.0
> >
>
Thank you for the comments,
Emil
next prev parent reply other threads:[~2026-03-04 14:38 UTC|newest]
Thread overview: 69+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-03-03 12:22 [PATCH net-next 0/8] net: dsa: add DSA support for the LAN9645x switch chip family Jens Emil Schulz Østergaard
2026-03-03 12:22 ` [PATCH net-next 1/8] net: dsa: add tag driver for LAN9645X Jens Emil Schulz Østergaard
2026-03-03 14:13 ` Andrew Lunn
2026-03-03 15:58 ` Jens Emil Schulz Ostergaard
2026-03-04 15:14 ` Andrew Lunn
2026-03-05 12:59 ` Jens Emil Schulz Ostergaard
2026-03-03 16:11 ` Vladimir Oltean
2026-03-05 13:53 ` Jens Emil Schulz Ostergaard
2026-03-04 15:23 ` Andrew Lunn
2026-03-05 13:01 ` Jens Emil Schulz Ostergaard
2026-03-03 12:22 ` [PATCH net-next 2/8] dt-bindings: net: lan9645x: add LAN9645X switch bindings Jens Emil Schulz Østergaard
2026-03-03 13:22 ` Vladimir Oltean
2026-03-03 16:00 ` Jens Emil Schulz Ostergaard
2026-03-03 14:18 ` Andrew Lunn
2026-03-03 19:04 ` Conor Dooley
2026-03-04 15:57 ` Jens Emil Schulz Ostergaard
2026-03-05 12:57 ` Jens Emil Schulz Ostergaard
2026-03-05 18:31 ` Conor Dooley
2026-03-06 15:08 ` Jens Emil Schulz Ostergaard
2026-03-06 15:20 ` Conor Dooley
2026-03-18 14:19 ` Jens Emil Schulz Ostergaard
2026-03-18 17:18 ` Conor Dooley
2026-03-18 17:20 ` Conor Dooley
2026-03-18 17:26 ` Christian Marangi
2026-03-24 10:31 ` Jens Emil Schulz Ostergaard
2026-03-04 15:55 ` Jens Emil Schulz Ostergaard
2026-03-03 18:49 ` Conor Dooley
2026-03-04 15:58 ` Jens Emil Schulz Ostergaard
2026-03-03 18:56 ` Conor Dooley
2026-03-04 16:10 ` Jens Emil Schulz Ostergaard
2026-03-04 16:14 ` Vladimir Oltean
2026-03-04 19:06 ` Conor Dooley
2026-03-05 13:08 ` Jens Emil Schulz Ostergaard
2026-03-03 12:22 ` [PATCH net-next 3/8] net: dsa: lan9645x: add autogenerated register macros Jens Emil Schulz Østergaard
2026-03-03 12:22 ` [PATCH net-next 4/8] net: dsa: lan9645x: add basic dsa driver for LAN9645X Jens Emil Schulz Østergaard
2026-03-03 14:15 ` Vladimir Oltean
2026-03-04 14:37 ` Jens Emil Schulz Ostergaard [this message]
2026-03-04 15:58 ` Russell King (Oracle)
2026-03-05 14:24 ` Jens Emil Schulz Ostergaard
2026-03-05 14:58 ` Andrew Lunn
2026-03-05 15:10 ` Vladimir Oltean
2026-03-05 16:54 ` Alexander Stein
2026-03-05 17:37 ` Andrew Lunn
2026-03-06 15:03 ` Jens Emil Schulz Ostergaard
2026-03-06 16:33 ` Andrew Lunn
2026-03-09 12:01 ` Jens Emil Schulz Ostergaard
2026-03-06 14:22 ` Russell King (Oracle)
2026-03-06 21:03 ` Jakub Kicinski
2026-03-03 12:22 ` [PATCH net-next 5/8] net: dsa: lan9645x: add bridge support Jens Emil Schulz Østergaard
2026-03-03 14:20 ` Vladimir Oltean
2026-03-03 16:08 ` Jens Emil Schulz Ostergaard
2026-03-03 16:17 ` Vladimir Oltean
2026-03-05 13:14 ` Jens Emil Schulz Ostergaard
2026-03-03 14:51 ` Vladimir Oltean
2026-03-09 12:09 ` Jens Emil Schulz Ostergaard
2026-03-03 12:22 ` [PATCH net-next 6/8] net: dsa: lan9645x: add vlan support Jens Emil Schulz Østergaard
2026-03-03 14:59 ` Vladimir Oltean
2026-03-04 14:40 ` Jens Emil Schulz Ostergaard
2026-03-04 14:52 ` Vladimir Oltean
2026-03-03 12:22 ` [PATCH net-next 7/8] net: dsa: lan9645x: add mac table integration Jens Emil Schulz Østergaard
2026-03-03 15:27 ` Vladimir Oltean
2026-03-04 15:23 ` Jens Emil Schulz Ostergaard
2026-03-04 15:34 ` Andrew Lunn
2026-03-05 13:17 ` Jens Emil Schulz Ostergaard
2026-03-03 12:22 ` [PATCH net-next 8/8] net: dsa: lan9645x: add port statistics Jens Emil Schulz Østergaard
2026-03-03 16:01 ` Vladimir Oltean
2026-03-03 20:21 ` Andrew Lunn
2026-03-04 15:51 ` Jens Emil Schulz Ostergaard
2026-03-04 15:50 ` Jens Emil Schulz Ostergaard
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=64129f9c14b5d0d09f02a977dcfcaaafeb62d14c.camel@microchip.com \
--to=jensemil.schulzostergaard@microchip.com \
--cc=Steen.Hegelund@microchip.com \
--cc=UNGLinuxDriver@microchip.com \
--cc=andrew@lunn.ch \
--cc=conor+dt@kernel.org \
--cc=daniel.machon@microchip.com \
--cc=davem@davemloft.net \
--cc=devicetree@vger.kernel.org \
--cc=edumazet@google.com \
--cc=horms@kernel.org \
--cc=krzk+dt@kernel.org \
--cc=kuba@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux@armlinux.org.uk \
--cc=netdev@vger.kernel.org \
--cc=olteanv@gmail.com \
--cc=pabeni@redhat.com \
--cc=robh@kernel.org \
--cc=woojung.huh@microchip.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox