From 7ed1d2b87f376975c4540cfceb90e1dcc6ed8505 Mon Sep 17 00:00:00 2001 From: "Bastien Curutchet (Schneider Electric)" Date: Mon, 27 Jul 2026 12:20:43 +0200 Subject: [PATCH 01/10] net: dsa: microchip: implement ksz8463_setup() KSZ8463 uses the ksz8_setup() as setup() callback for its DSA operations. Its behavior is quite different than other KSZ8 switches, especially its interrupt scheme. Remove from the ksz8_setup()/ksz8_reset_switch() everything that is ksz8463-related. Create a dedicated ksz8463_setup() and a ksz8463_reset_switch() function. This new ksz8463_setup() is widely inspired from ksz8_setup, it has following differences: - it doesn't configure drive strength (not supported on KSZ8463) - it uses the ksz8463_reset_switch() - it doesn't call ksz8_handle_global_errata() (the handled errata only affects the KSZ87xx variant) - it doesn't configure IRQs. Note that ksz8_setup()'s IRQ initialization doesn't work for the KSZ8463 anyway. Proper support for it comes in upcoming patches. Remove the teardown implementation from the KSZ8463 operations. Since PTP and interrupts aren't setup, the common ksz_teardown() wouldn't do anything anyway. Signed-off-by: Bastien Curutchet (Schneider Electric) Link: https://patch.msgid.link/20260727-ksz-new-ptp-v3-1-caba39e680e3@bootlin.com Signed-off-by: Jakub Kicinski --- drivers/net/dsa/microchip/ksz8.c | 126 ++++++++++++++++++++++++++++--- 1 file changed, 114 insertions(+), 12 deletions(-) diff --git a/drivers/net/dsa/microchip/ksz8.c b/drivers/net/dsa/microchip/ksz8.c index c4c769028a20..5ed63f425013 100644 --- a/drivers/net/dsa/microchip/ksz8.c +++ b/drivers/net/dsa/microchip/ksz8.c @@ -181,6 +181,14 @@ static int ksz8_pme_pwrite8(struct ksz_device *dev, int port, int offset, u8 dat return ksz8_ind_write8(dev, table, (u8)(offset), data); } +static int ksz8463_reset_switch(struct ksz_device *dev) +{ + ksz_cfg(dev, KSZ8463_REG_SW_RESET, KSZ8463_GLOBAL_SOFTWARE_RESET, true); + ksz_cfg(dev, KSZ8463_REG_SW_RESET, KSZ8463_GLOBAL_SOFTWARE_RESET, + false); + return 0; +} + static int ksz8_reset_switch(struct ksz_device *dev) { if (ksz_is_ksz88x3(dev)) { @@ -189,11 +197,6 @@ static int ksz8_reset_switch(struct ksz_device *dev) KSZ8863_GLOBAL_SOFTWARE_RESET | KSZ8863_PCS_RESET, true); ksz_cfg(dev, KSZ8863_REG_SW_RESET, KSZ8863_GLOBAL_SOFTWARE_RESET | KSZ8863_PCS_RESET, false); - } else if (ksz_is_ksz8463(dev)) { - ksz_cfg(dev, KSZ8463_REG_SW_RESET, - KSZ8463_GLOBAL_SOFTWARE_RESET, true); - ksz_cfg(dev, KSZ8463_REG_SW_RESET, - KSZ8463_GLOBAL_SOFTWARE_RESET, false); } else { /* reset switch */ ksz_write8(dev, REG_POWER_MANAGEMENT_1, @@ -2300,6 +2303,110 @@ static void ksz88xx_r_mib_stats64(struct ksz_device *dev, int port) spin_unlock(&mib->stats64_lock); } +static int ksz8463_setup(struct dsa_switch *ds) +{ + struct ksz_device *dev = ds->priv; + u16 storm_mask, storm_rate; + struct ksz_port *p; + const u16 *regs; + int i, ret; + + regs = dev->info->regs; + + dev->vlan_cache = devm_kcalloc(dev->dev, sizeof(struct vlan_table), + dev->info->num_vlans, GFP_KERNEL); + if (!dev->vlan_cache) + return -ENOMEM; + + ret = ksz8463_reset_switch(dev); + if (ret) { + dev_err(ds->dev, "failed to reset switch\n"); + return ret; + } + + /* set broadcast storm protection 10% rate */ + storm_mask = BROADCAST_STORM_RATE; + storm_rate = (BROADCAST_STORM_VALUE * BROADCAST_STORM_PROT_RATE) / 100; + storm_mask = swab16(storm_mask); + storm_rate = swab16(storm_rate); + regmap_update_bits(ksz_regmap_16(dev), regs[S_BROADCAST_CTRL], + storm_mask, storm_rate); + + ksz8_config_cpu_port(ds); + + ksz8_enable_stp_addr(dev); + + ds->num_tx_queues = dev->info->num_tx_queues; + + regmap_update_bits(ksz_regmap_8(dev), regs[S_MULTICAST_CTRL], + MULTICAST_STORM_DISABLE, MULTICAST_STORM_DISABLE); + + ksz_init_mib_timer(dev); + + ds->configure_vlan_while_not_filtering = false; + ds->dscp_prio_mapping_is_global = true; + ds->mtu_enforcement_ingress = true; + + /* We rely on software untagging on the CPU port, so that we + * can support both tagged and untagged VLANs + */ + ds->untag_bridge_pvid = true; + + /* VLAN filtering is partly controlled by the global VLAN + * Enable flag + */ + ds->vlan_filtering_is_global = true; + + /* Enable automatic fast aging when link changed detected. */ + ksz_cfg(dev, S_LINK_AGING_CTRL, SW_LINK_AUTO_AGING, true); + + /* Enable aggressive back off algorithm in half duplex mode. */ + ret = ksz_rmw8(dev, REG_SW_CTRL_1, SW_AGGR_BACKOFF, SW_AGGR_BACKOFF); + if (ret) + return ret; + + /* + * Make sure unicast VLAN boundary is set as default and + * enable no excessive collision drop. + */ + ret = ksz_rmw8(dev, REG_SW_CTRL_2, + UNICAST_VLAN_BOUNDARY | NO_EXC_COLLISION_DROP, + UNICAST_VLAN_BOUNDARY | NO_EXC_COLLISION_DROP); + if (ret) + return ret; + + ksz_cfg(dev, S_REPLACE_VID_CTRL, SW_REPLACE_VID, false); + + ksz_cfg(dev, S_MIRROR_CTRL, SW_MIRROR_RX_TX, false); + + for (i = 0; i < (dev->info->num_vlans / 4); i++) + ksz8_r_vlan_entries(dev, i); + + /* Start with learning disabled on standalone user ports, and enabled + * on the CPU port. In lack of other finer mechanisms, learning on the + * CPU port will avoid flooding bridge local addresses on the network + * in some cases. + */ + p = &dev->ports[dev->cpu_port]; + p->learning = true; + + ret = ksz_mdio_register(dev); + if (ret < 0) { + dev_err(dev->dev, "failed to register the mdio"); + return ret; + } + + ret = ksz_dcb_init(dev); + if (ret) + return ret; + + /* start switch */ + regmap_update_bits(ksz_regmap_8(dev), regs[S_START_CTRL], + SW_START, SW_START); + + return 0; +} + /** * ksz88x3_drive_strength_write() - Set the drive strength configuration for * KSZ8863 compatible chip variants. @@ -2445,10 +2552,6 @@ static int ksz8_setup(struct dsa_switch *ds) /* set broadcast storm protection 10% rate */ storm_mask = BROADCAST_STORM_RATE; storm_rate = (BROADCAST_STORM_VALUE * BROADCAST_STORM_PROT_RATE) / 100; - if (ksz_is_ksz8463(dev)) { - storm_mask = swab16(storm_mask); - storm_rate = swab16(storm_rate); - } regmap_update_bits(ksz_regmap_16(dev), regs[S_BROADCAST_CTRL], storm_mask, storm_rate); @@ -2499,7 +2602,7 @@ static int ksz8_setup(struct dsa_switch *ds) ksz_cfg(dev, S_MIRROR_CTRL, SW_MIRROR_RX_TX, false); - if (!ksz_is_ksz88x3(dev) && !ksz_is_ksz8463(dev)) + if (!ksz_is_ksz88x3(dev)) ksz_cfg(dev, REG_SW_CTRL_19, SW_INS_TAG_ENABLE, true); for (i = 0; i < (dev->info->num_vlans / 4); i++) @@ -2889,8 +2992,7 @@ const struct ksz_dev_ops ksz88xx_dev_ops = { const struct dsa_switch_ops ksz8463_switch_ops = { .get_tag_protocol = ksz8463_get_tag_protocol, .connect_tag_protocol = ksz8463_connect_tag_protocol, - .setup = ksz8_setup, - .teardown = ksz_teardown, + .setup = ksz8463_setup, .phy_read = ksz8463_phy_read16, .phy_write = ksz8463_phy_write16, .phylink_get_caps = ksz8_phylink_get_caps, From 9089bde4db6027b84ad07b0a4b6fd802f98397d5 Mon Sep 17 00:00:00 2001 From: "Bastien Curutchet (Schneider Electric)" Date: Mon, 27 Jul 2026 12:20:44 +0200 Subject: [PATCH 02/10] net: dsa: microchip: split ksz8_config_cpu_port() ksz8_config_cpu_port() is only called twice, once by ksz8_setup() and once by ksz8463_setup(). It contains a ksz8463 branch that could be avoided in the ksz8_setup() case and a ksz87xx/ksz88xx branches that could be avoided in ksz8463_setup() case. Create ksz8463_config_cpu_port() that only handles the ksz8463 case and remove the ksz8463 specificities from the common ksz8_config_cpu_port(). Signed-off-by: Bastien Curutchet (Schneider Electric) Link: https://patch.msgid.link/20260727-ksz-new-ptp-v3-2-caba39e680e3@bootlin.com Signed-off-by: Jakub Kicinski --- drivers/net/dsa/microchip/ksz8.c | 73 ++++++++++++++++++++------------ 1 file changed, 45 insertions(+), 28 deletions(-) diff --git a/drivers/net/dsa/microchip/ksz8.c b/drivers/net/dsa/microchip/ksz8.c index 5ed63f425013..3bbca6f9cfc5 100644 --- a/drivers/net/dsa/microchip/ksz8.c +++ b/drivers/net/dsa/microchip/ksz8.c @@ -2029,6 +2029,50 @@ static void ksz88x3_config_rmii_clk(struct ksz_device *dev) KSZ88X3_PORT3_RMII_CLK_INTERNAL, rmii_clk_internal); } +static void ksz8463_config_cpu_port(struct dsa_switch *ds) +{ + struct ksz_device *dev = ds->priv; + struct ksz_port *p; + u8 fiber_ports = 0; + const u32 *masks; + const u16 *regs; + int i; + + masks = dev->info->masks; + regs = dev->info->regs; + + ksz_cfg(dev, regs[S_TAIL_TAG_CTRL], masks[SW_TAIL_TAG_ENABLE], true); + + ksz8_port_setup(dev, dev->cpu_port, true); + + for (i = 0; i < dev->phy_port_cnt; i++) + ksz_port_stp_state_set(ds, i, BR_STATE_DISABLED); + + for (i = 0; i < dev->phy_port_cnt; i++) { + p = &dev->ports[i]; + ksz_port_cfg(dev, i, regs[P_STP_CTRL], PORT_FORCE_FLOW_CTRL, + p->fiber); + if (p->fiber) + fiber_ports |= (1 << i); + } + + /* Setup fiber ports. */ + if (fiber_ports) { + fiber_ports &= 3; + regmap_update_bits(ksz_regmap_16(dev), KSZ8463_REG_CFG_CTRL, + fiber_ports << PORT_COPPER_MODE_S, + 0); + regmap_update_bits(ksz_regmap_16(dev), KSZ8463_REG_DSP_CTRL_6, + COPPER_RECEIVE_ADJUSTMENT, 0); + } + + /* Turn off PTP function as the switch enables it by default */ + regmap_update_bits(ksz_regmap_16(dev), KSZ8463_PTP_MSG_CONF1, + PTP_ENABLE, 0); + regmap_update_bits(ksz_regmap_16(dev), KSZ8463_PTP_CLK_CTRL, + PTP_CLK_ENABLE, 0); +} + static void ksz8_config_cpu_port(struct dsa_switch *ds) { struct ksz_device *dev = ds->priv; @@ -2036,7 +2080,6 @@ static void ksz8_config_cpu_port(struct dsa_switch *ds) const u32 *masks; const u16 *regs; u8 remote; - u8 fiber_ports = 0; int i; masks = dev->info->masks; @@ -2067,32 +2110,6 @@ static void ksz8_config_cpu_port(struct dsa_switch *ds) else ksz_port_cfg(dev, i, regs[P_STP_CTRL], PORT_FORCE_FLOW_CTRL, false); - if (p->fiber) - fiber_ports |= (1 << i); - } - if (ksz_is_ksz8463(dev)) { - /* Setup fiber ports. */ - if (fiber_ports) { - fiber_ports &= 3; - regmap_update_bits(ksz_regmap_16(dev), - KSZ8463_REG_CFG_CTRL, - fiber_ports << PORT_COPPER_MODE_S, - 0); - regmap_update_bits(ksz_regmap_16(dev), - KSZ8463_REG_DSP_CTRL_6, - COPPER_RECEIVE_ADJUSTMENT, 0); - } - - /* Turn off PTP function as the switch's proprietary way of - * handling timestamp is not supported in current Linux PTP - * stack implementation. - */ - regmap_update_bits(ksz_regmap_16(dev), - KSZ8463_PTP_MSG_CONF1, - PTP_ENABLE, 0); - regmap_update_bits(ksz_regmap_16(dev), - KSZ8463_PTP_CLK_CTRL, - PTP_CLK_ENABLE, 0); } } @@ -2332,7 +2349,7 @@ static int ksz8463_setup(struct dsa_switch *ds) regmap_update_bits(ksz_regmap_16(dev), regs[S_BROADCAST_CTRL], storm_mask, storm_rate); - ksz8_config_cpu_port(ds); + ksz8463_config_cpu_port(ds); ksz8_enable_stp_addr(dev); From 2a6aab5d5bd478b4aac42dd3238c4fefb9412a0d Mon Sep 17 00:00:00 2001 From: "Bastien Curutchet (Schneider Electric)" Date: Mon, 27 Jul 2026 12:20:45 +0200 Subject: [PATCH 03/10] net: dsa: microchip: allow the use of other IRQ operations. The IRQ setup uses an hardcoded set of IRQ operations. These operations don't fit with the KSZ8463 which has an inverted bit logic (it uses an 'enable irq' register instead of a 'mask irq' one) and 16-bits registers. Take the IRQ domain operations as input of ksz_irq_common_setup() to allow KSZ8463 to use the already existing setup with its own set of IRQ operations. Expose ksz_irq_common_setup() and ksz_irq_bus_lock/unlock() so they can be used by ksz8.c. Signed-off-by: Bastien Curutchet (Schneider Electric) Link: https://patch.msgid.link/20260727-ksz-new-ptp-v3-3-caba39e680e3@bootlin.com Signed-off-by: Jakub Kicinski --- drivers/net/dsa/microchip/ksz_common.c | 15 ++++++++------- drivers/net/dsa/microchip/ksz_common.h | 5 +++++ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/drivers/net/dsa/microchip/ksz_common.c b/drivers/net/dsa/microchip/ksz_common.c index ff4dd51f6cb0..1a9d6f83a023 100644 --- a/drivers/net/dsa/microchip/ksz_common.c +++ b/drivers/net/dsa/microchip/ksz_common.c @@ -2419,14 +2419,14 @@ static void ksz_irq_unmask(struct irq_data *d) kirq->masked &= ~BIT(d->hwirq); } -static void ksz_irq_bus_lock(struct irq_data *d) +void ksz_irq_bus_lock(struct irq_data *d) { struct ksz_irq *kirq = irq_data_get_irq_chip_data(d); mutex_lock(&kirq->dev->lock_irq); } -static void ksz_irq_bus_sync_unlock(struct irq_data *d) +void ksz_irq_bus_sync_unlock(struct irq_data *d) { struct ksz_irq *kirq = irq_data_get_irq_chip_data(d); struct ksz_device *dev = kirq->dev; @@ -2504,14 +2504,15 @@ static irqreturn_t ksz_irq_thread_fn(int irq, void *dev_id) return (nhandled > 0 ? IRQ_HANDLED : IRQ_NONE); } -static int ksz_irq_common_setup(struct ksz_device *dev, struct ksz_irq *kirq) +int ksz_irq_common_setup(struct ksz_device *dev, struct ksz_irq *kirq, + const struct irq_domain_ops *ops) { int ret, n; kirq->dev = dev; - kirq->domain = irq_domain_create_simple(dev_fwnode(dev->dev), kirq->nirqs, 0, - &ksz_irq_domain_ops, kirq); + kirq->domain = irq_domain_create_simple(dev_fwnode(dev->dev), + kirq->nirqs, 0, ops, kirq); if (!kirq->domain) return -ENOMEM; @@ -2543,7 +2544,7 @@ int ksz_girq_setup(struct ksz_device *dev) girq->irq_num = dev->irq; - return ksz_irq_common_setup(dev, girq); + return ksz_irq_common_setup(dev, girq, &ksz_irq_domain_ops); } int ksz_pirq_setup(struct ksz_device *dev, u8 p) @@ -2560,7 +2561,7 @@ int ksz_pirq_setup(struct ksz_device *dev, u8 p) if (!pirq->irq_num) return -EINVAL; - return ksz_irq_common_setup(dev, pirq); + return ksz_irq_common_setup(dev, pirq, &ksz_irq_domain_ops); } void ksz_teardown(struct dsa_switch *ds) diff --git a/drivers/net/dsa/microchip/ksz_common.h b/drivers/net/dsa/microchip/ksz_common.h index acaf70e6f393..0f2abb22ca91 100644 --- a/drivers/net/dsa/microchip/ksz_common.h +++ b/drivers/net/dsa/microchip/ksz_common.h @@ -8,6 +8,7 @@ #define __KSZ_COMMON_H #include +#include #include #include #include @@ -508,6 +509,10 @@ int ksz_sw_mdio_write(struct mii_bus *bus, int addr, int regnum, u16 val); int ksz_parent_mdio_read(struct mii_bus *bus, int addr, int regnum); int ksz_parent_mdio_write(struct mii_bus *bus, int addr, int regnum, u16 val); int ksz_mdio_register(struct ksz_device *dev); +void ksz_irq_bus_lock(struct irq_data *d); +void ksz_irq_bus_sync_unlock(struct irq_data *d); +int ksz_irq_common_setup(struct ksz_device *dev, struct ksz_irq *kirq, + const struct irq_domain_ops *ops); int ksz_pirq_setup(struct ksz_device *dev, u8 p); int ksz_girq_setup(struct ksz_device *dev); void ksz_irq_free(struct ksz_irq *kirq); From b3422f60e931bb5a55bd5e16456e100434bb5a69 Mon Sep 17 00:00:00 2001 From: "Bastien Curutchet (Schneider Electric)" Date: Mon, 27 Jul 2026 12:20:46 +0200 Subject: [PATCH 04/10] net: dsa: microchip: add PTP interrupt handling for KSZ8463 KSZ8463 PTP interrupts aren't handled by the driver. The interrupt layout in KSZ8463 has nothing to do with the other switches: - Its global interrupt enable register is 16-bits long and follow an 'enable' logic, instead of a 'mask' one - all the interrupts of all ports are grouped into one status register while others have one interrupt register per port - xdelay_req and pdresp timestamps share one single interrupt bit on the KSZ8463 while each of them has its own interrupt bit on other switches Create a KSZ8463-specific set of interrupt domain operations to handle the global IRQ layer. To limit code duplication, it uses the same interrupt handler than the other switches. Since other switches have 8-bits registers, only the high-byte of the interrupt status/enable registers are used. This high-byte is where the PTP interrupts are located. The low-byte contains the wake-up detection interrupts so if at some points these interrupts are needed we'll need a bit of rework here. Create KSZ8463-specific functions to setup the PTP interrupts. The created IRQ domain is tied to the first port of the KSZ8463. Again, the same PTP interrupt handler than the others switches is used. Implement the teardown callback to release the interrupts. Signed-off-by: Bastien Curutchet (Schneider Electric) Link: https://patch.msgid.link/20260727-ksz-new-ptp-v3-4-caba39e680e3@bootlin.com Signed-off-by: Jakub Kicinski --- drivers/net/dsa/microchip/ksz8.c | 93 ++++++++++++++++- drivers/net/dsa/microchip/ksz_ptp.c | 131 ++++++++++++++++++++++++ drivers/net/dsa/microchip/ksz_ptp.h | 9 ++ drivers/net/dsa/microchip/ksz_ptp_reg.h | 6 ++ 4 files changed, 237 insertions(+), 2 deletions(-) diff --git a/drivers/net/dsa/microchip/ksz8.c b/drivers/net/dsa/microchip/ksz8.c index 3bbca6f9cfc5..c099a7005808 100644 --- a/drivers/net/dsa/microchip/ksz8.c +++ b/drivers/net/dsa/microchip/ksz8.c @@ -36,6 +36,13 @@ #include "ksz8_reg.h" #include "ksz8.h" +/* + * We use only the high-byte (so odd addresses) of the 16-bits registers to fit + * in the common IRQ framework + */ +#define KSZ8463_REG_ISR 0x191 +#define KSZ8463_REG_IER 0x193 + /* ksz88x3_drive_strengths - Drive strength mapping for KSZ8863, KSZ8873, .. * variants. * This values are documented in KSZ8873 and KSZ8863 datasheets. @@ -181,6 +188,58 @@ static int ksz8_pme_pwrite8(struct ksz_device *dev, int port, int offset, u8 dat return ksz8_ind_write8(dev, table, (u8)(offset), data); } +static void ksz8463_irq_mask(struct irq_data *d) +{ + struct ksz_irq *kirq = irq_data_get_irq_chip_data(d); + + kirq->masked &= ~BIT(d->hwirq); +} + +static void ksz8463_irq_unmask(struct irq_data *d) +{ + struct ksz_irq *kirq = irq_data_get_irq_chip_data(d); + + kirq->masked |= BIT(d->hwirq); +} + +static const struct irq_chip ksz8463_irq_chip = { + .name = "ksz8463-irq", + .irq_mask = ksz8463_irq_mask, + .irq_unmask = ksz8463_irq_unmask, + .irq_bus_lock = ksz_irq_bus_lock, + .irq_bus_sync_unlock = ksz_irq_bus_sync_unlock, +}; + +static int ksz8463_irq_domain_map(struct irq_domain *d, + unsigned int irq, irq_hw_number_t hwirq) +{ + irq_set_chip_data(irq, d->host_data); + irq_set_chip_and_handler(irq, &ksz8463_irq_chip, handle_level_irq); + irq_set_noprobe(irq); + + return 0; +} + +static const struct irq_domain_ops ksz8463_irq_domain_ops = { + .map = ksz8463_irq_domain_map, + .xlate = irq_domain_xlate_twocell, +}; + +static int ksz8463_girq_setup(struct ksz_device *dev) +{ + struct ksz_irq *girq = &dev->girq; + + girq->nirqs = 8; + girq->reg_mask = KSZ8463_REG_IER; + girq->reg_status = KSZ8463_REG_ISR; + girq->masked = 0; + snprintf(girq->name, sizeof(girq->name), "ksz8463-girq"); + + girq->irq_num = dev->irq; + + return ksz_irq_common_setup(dev, girq, &ksz8463_irq_domain_ops); +} + static int ksz8463_reset_switch(struct ksz_device *dev) { ksz_cfg(dev, KSZ8463_REG_SW_RESET, KSZ8463_GLOBAL_SOFTWARE_RESET, true); @@ -2407,21 +2466,50 @@ static int ksz8463_setup(struct dsa_switch *ds) p = &dev->ports[dev->cpu_port]; p->learning = true; + if (dev->irq > 0) { + ret = ksz8463_girq_setup(dev); + if (ret) + return ret; + + ret = ksz8463_ptp_irq_setup(ds); + if (ret) + goto free_girq; + } + ret = ksz_mdio_register(dev); if (ret < 0) { dev_err(dev->dev, "failed to register the mdio"); - return ret; + goto free_ptp_irq; } ret = ksz_dcb_init(dev); if (ret) - return ret; + goto free_ptp_irq; /* start switch */ regmap_update_bits(ksz_regmap_8(dev), regs[S_START_CTRL], SW_START, SW_START); return 0; + +free_ptp_irq: + if (dev->irq > 0) + ksz8463_ptp_irq_free(ds); +free_girq: + if (dev->irq > 0) + ksz_irq_free(&dev->girq); + + return ret; +} + +static void ksz8463_teardown(struct dsa_switch *ds) +{ + struct ksz_device *dev = ds->priv; + + if (dev->irq > 0) { + ksz8463_ptp_irq_free(ds); + ksz_irq_free(&dev->girq); + } } /** @@ -3010,6 +3098,7 @@ const struct dsa_switch_ops ksz8463_switch_ops = { .get_tag_protocol = ksz8463_get_tag_protocol, .connect_tag_protocol = ksz8463_connect_tag_protocol, .setup = ksz8463_setup, + .teardown = ksz8463_teardown, .phy_read = ksz8463_phy_read16, .phy_write = ksz8463_phy_write16, .phylink_get_caps = ksz8_phylink_get_caps, diff --git a/drivers/net/dsa/microchip/ksz_ptp.c b/drivers/net/dsa/microchip/ksz_ptp.c index 5bdf829a6e38..d8a583ed8b43 100644 --- a/drivers/net/dsa/microchip/ksz_ptp.c +++ b/drivers/net/dsa/microchip/ksz_ptp.c @@ -32,6 +32,15 @@ #define KSZ_PTP_INT_START 13 +/* + * PTP interrupt bit is the bit 12 of the 16-bits ISR/IER. But ksz_common.c only + * accesses the high-byte of these registers so the PTP interrupt bit becomes 4. + */ +#define KSZ8463_SRC_PTP_INT 4 +#define KSZ8463_PTP_PORT1_INT_START 12 +#define KSZ8463_PTP_PORT2_INT_START 14 +#define KSZ8463_PTP_INT_START KSZ8463_PTP_PORT1_INT_START + static int ksz_ptp_tou_gpio(struct ksz_device *dev) { int ret; @@ -1131,6 +1140,128 @@ static int ksz_ptp_msg_irq_setup(struct ksz_port *port, u8 n) return ret; } +static int ksz8463_ptp_port_irq_setup(struct ksz_irq *ptpirq, + struct ksz_port *port, int hw_irq) +{ + u16 ts_reg[] = {KSZ8463_REG_PORT_SYNC_TS, KSZ8463_REG_PORT_DREQ_TS}; + static const char * const name[] = {"sync-msg", "delay-msg"}; + const struct ksz_dev_ops *ops = port->ksz_dev->dev_ops; + struct ksz_ptp_irq *ptpmsg_irq; + int ret; + int i; + + init_completion(&port->tstamp_msg_comp); + + for (i = 0; i < 2; i++) { + ptpmsg_irq = &port->ptpmsg_irq[i]; + ptpmsg_irq->num = irq_create_mapping(ptpirq->domain, + hw_irq + i); + if (!ptpmsg_irq->num) { + ret = -EINVAL; + goto release_msg_irq; + } + + ptpmsg_irq->port = port; + ptpmsg_irq->ts_reg = ops->get_port_addr(port->num, ts_reg[i]); + + strscpy(ptpmsg_irq->name, name[i]); + + ret = request_threaded_irq(ptpmsg_irq->num, NULL, + ksz_ptp_msg_thread_fn, IRQF_ONESHOT, + ptpmsg_irq->name, ptpmsg_irq); + if (ret) { + irq_dispose_mapping(ptpmsg_irq->num); + goto release_msg_irq; + } + } + + return 0; + +release_msg_irq: + while (i--) + ksz_ptp_msg_irq_free(port, i); + + return ret; +} + +static void ksz8463_ptp_port_irq_teardown(struct ksz_port *port) +{ + int i; + + for (i = 0; i < 2; i++) + ksz_ptp_msg_irq_free(port, i); +} + +int ksz8463_ptp_irq_setup(struct dsa_switch *ds) +{ + struct ksz_device *dev = ds->priv; + struct ksz_port *port1, *port2; + struct ksz_irq *ptpirq; + int ret; + + port1 = &dev->ports[0]; + port2 = &dev->ports[1]; + ptpirq = &port1->ptpirq; + + ptpirq->irq_num = irq_find_mapping(dev->girq.domain, + KSZ8463_SRC_PTP_INT); + if (!ptpirq->irq_num) + return -EINVAL; + + ptpirq->dev = dev; + ptpirq->nirqs = 4; + ptpirq->reg_mask = KSZ8463_PTP_TS_IER; + ptpirq->reg_status = KSZ8463_PTP_TS_ISR; + ptpirq->irq0_offset = KSZ8463_PTP_INT_START; + snprintf(ptpirq->name, sizeof(ptpirq->name), "ptp-irq"); + + ptpirq->domain = irq_domain_create_linear(dev_fwnode(dev->dev), + ptpirq->nirqs, + &ksz_ptp_irq_domain_ops, + ptpirq); + if (!ptpirq->domain) + return -ENOMEM; + + ret = ksz8463_ptp_port_irq_setup(ptpirq, port1, + KSZ8463_PTP_PORT1_INT_START - KSZ8463_PTP_INT_START); + if (ret) + goto release_domain; + + ret = ksz8463_ptp_port_irq_setup(ptpirq, port2, + KSZ8463_PTP_PORT2_INT_START - KSZ8463_PTP_INT_START); + if (ret) + goto free_port1; + + ret = request_threaded_irq(ptpirq->irq_num, NULL, ksz_ptp_irq_thread_fn, + IRQF_ONESHOT, ptpirq->name, ptpirq); + if (ret) + goto free_port2; + + return 0; + +free_port2: + ksz8463_ptp_port_irq_teardown(port2); +free_port1: + ksz8463_ptp_port_irq_teardown(port1); +release_domain: + irq_domain_remove(ptpirq->domain); + + return ret; +} + +void ksz8463_ptp_irq_free(struct dsa_switch *ds) +{ + struct ksz_device *dev = ds->priv; + struct ksz_port *port1 = &dev->ports[0]; + struct ksz_port *port2 = &dev->ports[1]; + struct ksz_irq *ptpirq = &port1->ptpirq; + + free_irq(ptpirq->irq_num, ptpirq); + ksz8463_ptp_port_irq_teardown(port2); + ksz8463_ptp_port_irq_teardown(port1); + irq_domain_remove(ptpirq->domain); +} + int ksz_ptp_irq_setup(struct dsa_switch *ds, u8 p) { struct ksz_device *dev = ds->priv; diff --git a/drivers/net/dsa/microchip/ksz_ptp.h b/drivers/net/dsa/microchip/ksz_ptp.h index 3086e519b1b6..11408580031d 100644 --- a/drivers/net/dsa/microchip/ksz_ptp.h +++ b/drivers/net/dsa/microchip/ksz_ptp.h @@ -50,6 +50,8 @@ bool ksz_port_rxtstamp(struct dsa_switch *ds, int port, struct sk_buff *skb, unsigned int type); int ksz_ptp_irq_setup(struct dsa_switch *ds, u8 p); void ksz_ptp_irq_free(struct dsa_switch *ds, u8 p); +int ksz8463_ptp_irq_setup(struct dsa_switch *ds); +void ksz8463_ptp_irq_free(struct dsa_switch *ds); #else @@ -72,6 +74,13 @@ static inline int ksz_ptp_irq_setup(struct dsa_switch *ds, u8 p) static inline void ksz_ptp_irq_free(struct dsa_switch *ds, u8 p) {} +static inline int ksz8463_ptp_irq_setup(struct dsa_switch *ds) +{ + return 0; +} + +static inline void ksz8463_ptp_irq_free(struct dsa_switch *ds) {} + #define ksz_get_ts_info NULL #define ksz_hwtstamp_get NULL diff --git a/drivers/net/dsa/microchip/ksz_ptp_reg.h b/drivers/net/dsa/microchip/ksz_ptp_reg.h index eab9aecb7fa8..1a669d6ee889 100644 --- a/drivers/net/dsa/microchip/ksz_ptp_reg.h +++ b/drivers/net/dsa/microchip/ksz_ptp_reg.h @@ -121,6 +121,12 @@ #define REG_PTP_PORT_SYNC_TS 0x0C0C #define REG_PTP_PORT_PDRESP_TS 0x0C10 +#define KSZ8463_REG_PORT_DREQ_TS 0x0648 +#define KSZ8463_REG_PORT_SYNC_TS 0x064C +#define KSZ8463_REG_PORT_DRESP_TS 0x0650 +#define KSZ8463_PTP_TS_ISR 0x068C +#define KSZ8463_PTP_TS_IER 0x068E + #define REG_PTP_PORT_TX_INT_STATUS__2 0x0C14 #define REG_PTP_PORT_TX_INT_ENABLE__2 0x0C16 From ac99cc6cd3f80308192e884547b3e384de9f6807 Mon Sep 17 00:00:00 2001 From: "Bastien Curutchet (Schneider Electric)" Date: Mon, 27 Jul 2026 12:20:47 +0200 Subject: [PATCH 05/10] net: dsa: microchip: adapt port offset for KSZ8463's PTP register In KSZ8463 register's layout, the offset between port 1 and port 2 registers isn't the same in the generic control register area than in the PTP register area. The get_port_addr() always uses the same offset so it doesn't work when it's used to access PTP registers. Adapt the port offset in get_port_addr() when the accessed register is in the PTP area. Signed-off-by: Bastien Curutchet (Schneider Electric) Link: https://patch.msgid.link/20260727-ksz-new-ptp-v3-5-caba39e680e3@bootlin.com Signed-off-by: Jakub Kicinski --- drivers/net/dsa/microchip/ksz8.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/dsa/microchip/ksz8.c b/drivers/net/dsa/microchip/ksz8.c index c099a7005808..5e5bfc5cae2d 100644 --- a/drivers/net/dsa/microchip/ksz8.c +++ b/drivers/net/dsa/microchip/ksz8.c @@ -2831,6 +2831,9 @@ static u32 ksz8_get_port_addr(int port, int offset) static u32 ksz8463_get_port_addr(int port, int offset) { + if (offset >= KSZ8463_PTP_CLK_CTRL) + return offset + 0x20 * port; + return offset + 0x18 * port; } From e5a0c13163684a8a1c16636437e627535a7b82ab Mon Sep 17 00:00:00 2001 From: "Bastien Curutchet (Schneider Electric)" Date: Mon, 27 Jul 2026 12:20:48 +0200 Subject: [PATCH 06/10] net: dsa: tag_ksz: move the KSZ8795 tag handling below ksz_xmit_timestamp() Upcoming patch reduces code duplication between KSZ8795 and KSZ9893 by introducing a common xmit() function. This rework needs the KSZ8795 handlers to be implemented below ksz_defer_xmit(). Do the move now to reduce the noise in next patch. No functionnal change is intended in this patch. Signed-off-by: Bastien Curutchet (Schneider Electric) Link: https://patch.msgid.link/20260727-ksz-new-ptp-v3-6-caba39e680e3@bootlin.com Signed-off-by: Jakub Kicinski --- net/dsa/tag_ksz.c | 132 +++++++++++++++++++++++----------------------- 1 file changed, 66 insertions(+), 66 deletions(-) diff --git a/net/dsa/tag_ksz.c b/net/dsa/tag_ksz.c index 67fa89f102e0..f58ce0f0e9e4 100644 --- a/net/dsa/tag_ksz.c +++ b/net/dsa/tag_ksz.c @@ -103,72 +103,6 @@ static struct sk_buff *ksz_common_rcv(struct sk_buff *skb, return skb; } -/* - * For Ingress (Host -> KSZ8795), 1 byte is added before FCS. - * --------------------------------------------------------------------------- - * DA(6bytes)|SA(6bytes)|....|Data(nbytes)|tag(1byte)|FCS(4bytes) - * --------------------------------------------------------------------------- - * tag : each bit represents port (eg, 0x01=port1, 0x02=port2, 0x10=port5) - * - * For Egress (KSZ8795 -> Host), 1 byte is added before FCS. - * --------------------------------------------------------------------------- - * DA(6bytes)|SA(6bytes)|....|Data(nbytes)|tag0(1byte)|FCS(4bytes) - * --------------------------------------------------------------------------- - * tag0 : zero-based value represents port - * (eg, 0x0=port1, 0x2=port3, 0x3=port4) - */ - -#define KSZ8795_TAIL_TAG_EG_PORT_M GENMASK(1, 0) -#define KSZ8795_TAIL_TAG_OVERRIDE BIT(6) -#define KSZ8795_TAIL_TAG_LOOKUP BIT(7) - -static struct sk_buff *ksz8795_xmit(struct sk_buff *skb, struct net_device *dev) -{ - struct ethhdr *hdr; - u8 *tag; - - if (skb->ip_summed == CHECKSUM_PARTIAL && skb_checksum_help(skb)) { - kfree_skb(skb); - return NULL; - } - - /* Tag encoding */ - tag = skb_put(skb, KSZ_INGRESS_TAG_LEN); - hdr = skb_eth_hdr(skb); - - *tag = dsa_xmit_port_mask(skb, dev); - if (is_link_local_ether_addr(hdr->h_dest)) - *tag |= KSZ8795_TAIL_TAG_OVERRIDE; - - return skb; -} - -static struct sk_buff *ksz8795_rcv(struct sk_buff *skb, struct net_device *dev) -{ - u8 *tag; - - if (skb_linearize(skb)) { - kfree_skb(skb); - return NULL; - } - - tag = skb_tail_pointer(skb) - KSZ_EGRESS_TAG_LEN; - - return ksz_common_rcv(skb, dev, tag[0] & KSZ8795_TAIL_TAG_EG_PORT_M, - KSZ_EGRESS_TAG_LEN); -} - -static const struct dsa_device_ops ksz8795_netdev_ops = { - .name = KSZ8795_NAME, - .proto = DSA_TAG_PROTO_KSZ8795, - .xmit = ksz8795_xmit, - .rcv = ksz8795_rcv, - .needed_tailroom = KSZ_INGRESS_TAG_LEN, -}; - -DSA_TAG_DRIVER(ksz8795_netdev_ops); -MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_KSZ8795, KSZ8795_NAME); - /* * For Ingress (Host -> KSZ9477), 2/6 bytes are added before FCS. * --------------------------------------------------------------------------- @@ -353,6 +287,72 @@ static const struct dsa_device_ops ksz9477_netdev_ops = { DSA_TAG_DRIVER(ksz9477_netdev_ops); MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_KSZ9477, KSZ9477_NAME); +/* + * For Ingress (Host -> KSZ8795), 1 byte is added before FCS. + * --------------------------------------------------------------------------- + * DA(6bytes)|SA(6bytes)|....|Data(nbytes)|tag(1byte)|FCS(4bytes) + * --------------------------------------------------------------------------- + * tag : each bit represents port (eg, 0x01=port1, 0x02=port2, 0x10=port5) + * + * For Egress (KSZ8795 -> Host), 1 byte is added before FCS. + * --------------------------------------------------------------------------- + * DA(6bytes)|SA(6bytes)|....|Data(nbytes)|tag0(1byte)|FCS(4bytes) + * --------------------------------------------------------------------------- + * tag0 : zero-based value represents port + * (eg, 0x0=port1, 0x2=port3, 0x3=port4) + */ + +#define KSZ8795_TAIL_TAG_EG_PORT_M GENMASK(1, 0) +#define KSZ8795_TAIL_TAG_OVERRIDE BIT(6) +#define KSZ8795_TAIL_TAG_LOOKUP BIT(7) + +static struct sk_buff *ksz8795_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct ethhdr *hdr; + u8 *tag; + + if (skb->ip_summed == CHECKSUM_PARTIAL && skb_checksum_help(skb)) { + kfree_skb(skb); + return NULL; + } + + /* Tag encoding */ + tag = skb_put(skb, KSZ_INGRESS_TAG_LEN); + hdr = skb_eth_hdr(skb); + + *tag = dsa_xmit_port_mask(skb, dev); + if (is_link_local_ether_addr(hdr->h_dest)) + *tag |= KSZ8795_TAIL_TAG_OVERRIDE; + + return skb; +} + +static struct sk_buff *ksz8795_rcv(struct sk_buff *skb, struct net_device *dev) +{ + u8 *tag; + + if (skb_linearize(skb)) { + kfree_skb(skb); + return NULL; + } + + tag = skb_tail_pointer(skb) - KSZ_EGRESS_TAG_LEN; + + return ksz_common_rcv(skb, dev, tag[0] & KSZ8795_TAIL_TAG_EG_PORT_M, + KSZ_EGRESS_TAG_LEN); +} + +static const struct dsa_device_ops ksz8795_netdev_ops = { + .name = KSZ8795_NAME, + .proto = DSA_TAG_PROTO_KSZ8795, + .xmit = ksz8795_xmit, + .rcv = ksz8795_rcv, + .needed_tailroom = KSZ_INGRESS_TAG_LEN, +}; + +DSA_TAG_DRIVER(ksz8795_netdev_ops); +MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_KSZ8795, KSZ8795_NAME); + #define KSZ9893_TAIL_TAG_PRIO GENMASK(4, 3) #define KSZ9893_TAIL_TAG_OVERRIDE BIT(5) #define KSZ9893_TAIL_TAG_LOOKUP BIT(6) From a97c78093e9b900ede8d660b0c6121f76cb03b5f Mon Sep 17 00:00:00 2001 From: "Bastien Curutchet (Schneider Electric)" Date: Mon, 27 Jul 2026 12:20:49 +0200 Subject: [PATCH 07/10] net: dsa: tag_ksz: share code for KSZ8795 and KSZ9893 xmit operations KSZ8795 and KSZ9893 have very similar tag handling in the xmit path, leading to code duplication. There are only two differences between the two ksz*_xmit(): - the KSZ8795 doesn't handle priorities between frames - ksz8795_xmit() directly returns the SKB instead of calling ksz_defer_xmit(). Yet, ksz_defer_xmit() also returns directly the SKB if no clone is present inside the SKB. Clones are only created by the KSZ driver when the PTP feature is enabled. Since KSZ8795 doesn't support PTP, returning the SKB directly or ksz_defer_xmit() is the same. The upcoming support for the KSZ8463 also requires a similar xmit(). Gather the common code from ksz8795_xmit() and ksz9893_xmit() into a new ksz_common_xmit() function that takes three input arguments: - do_tstamp to tell whether ksz_xmit_timestamp() should be called - prio to give the priority tag (if any) - override_mask to give the location of the override bit (if any) Signed-off-by: Bastien Curutchet (Schneider Electric) Link: https://patch.msgid.link/20260727-ksz-new-ptp-v3-7-caba39e680e3@bootlin.com Signed-off-by: Jakub Kicinski --- net/dsa/tag_ksz.c | 73 +++++++++++++++++++++++------------------------ 1 file changed, 35 insertions(+), 38 deletions(-) diff --git a/net/dsa/tag_ksz.c b/net/dsa/tag_ksz.c index f58ce0f0e9e4..f8b40437c5fa 100644 --- a/net/dsa/tag_ksz.c +++ b/net/dsa/tag_ksz.c @@ -218,6 +218,37 @@ static struct sk_buff *ksz_defer_xmit(struct dsa_port *dp, struct sk_buff *skb) return NULL; } +static struct sk_buff *ksz_common_xmit(struct sk_buff *skb, + struct net_device *dev, + bool do_tstamp, + u8 prio, + u8 override_mask) +{ + struct dsa_port *dp = dsa_user_to_port(dev); + struct ethhdr *hdr; + u8 *tag; + + if (skb->ip_summed == CHECKSUM_PARTIAL && skb_checksum_help(skb)) { + kfree_skb(skb); + return NULL; + } + + /* Tag encoding */ + if (do_tstamp) + ksz_xmit_timestamp(dp, skb); + + tag = skb_put(skb, KSZ_INGRESS_TAG_LEN); + hdr = skb_eth_hdr(skb); + + *tag = dsa_xmit_port_mask(skb, dev); + *tag |= prio; + + if (is_link_local_ether_addr(hdr->h_dest)) + *tag |= override_mask; + + return ksz_defer_xmit(dp, skb); +} + static struct sk_buff *ksz9477_xmit(struct sk_buff *skb, struct net_device *dev) { @@ -308,23 +339,7 @@ MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_KSZ9477, KSZ9477_NAME); static struct sk_buff *ksz8795_xmit(struct sk_buff *skb, struct net_device *dev) { - struct ethhdr *hdr; - u8 *tag; - - if (skb->ip_summed == CHECKSUM_PARTIAL && skb_checksum_help(skb)) { - kfree_skb(skb); - return NULL; - } - - /* Tag encoding */ - tag = skb_put(skb, KSZ_INGRESS_TAG_LEN); - hdr = skb_eth_hdr(skb); - - *tag = dsa_xmit_port_mask(skb, dev); - if (is_link_local_ether_addr(hdr->h_dest)) - *tag |= KSZ8795_TAIL_TAG_OVERRIDE; - - return skb; + return ksz_common_xmit(skb, dev, false, 0, KSZ8795_TAIL_TAG_OVERRIDE); } static struct sk_buff *ksz8795_rcv(struct sk_buff *skb, struct net_device *dev) @@ -362,28 +377,10 @@ static struct sk_buff *ksz9893_xmit(struct sk_buff *skb, { u16 queue_mapping = skb_get_queue_mapping(skb); u8 prio = netdev_txq_to_tc(dev, queue_mapping); - struct dsa_port *dp = dsa_user_to_port(dev); - struct ethhdr *hdr; - u8 *tag; - if (skb->ip_summed == CHECKSUM_PARTIAL && skb_checksum_help(skb)) { - kfree_skb(skb); - return NULL; - } - - /* Tag encoding */ - ksz_xmit_timestamp(dp, skb); - - tag = skb_put(skb, KSZ_INGRESS_TAG_LEN); - hdr = skb_eth_hdr(skb); - - *tag = dsa_xmit_port_mask(skb, dev); - *tag |= FIELD_PREP(KSZ9893_TAIL_TAG_PRIO, prio); - - if (is_link_local_ether_addr(hdr->h_dest)) - *tag |= KSZ9893_TAIL_TAG_OVERRIDE; - - return ksz_defer_xmit(dp, skb); + return ksz_common_xmit(skb, dev, true, + FIELD_PREP(KSZ9893_TAIL_TAG_PRIO, prio), + KSZ9893_TAIL_TAG_OVERRIDE); } static const struct dsa_device_ops ksz9893_netdev_ops = { From 46e8ceb399d548dfb7def6b160613e63bd59623d Mon Sep 17 00:00:00 2001 From: "Bastien Curutchet (Schneider Electric)" Date: Mon, 27 Jul 2026 12:20:50 +0200 Subject: [PATCH 08/10] net: dsa: microchip: add KSZ8463 tail tag handling KSZ8463 uses the KSZ9893 DSA TAG driver. However, the KSZ8463 doesn't use the tail tag to convey timestamps to the host as KSZ9893 does. It uses the reserved fields in the PTP header instead. Add a KSZ8463-specific DSA_TAG driver to handle KSZ8463 timestamps. There is no information in the tail tag to distinguish PTP packets from others so use the ptp_classify_raw() helper to find the PTP packets and extract the timestamp from their PTP headers. Signed-off-by: Bastien Curutchet (Schneider Electric) Link: https://patch.msgid.link/20260727-ksz-new-ptp-v3-8-caba39e680e3@bootlin.com Signed-off-by: Jakub Kicinski --- drivers/net/dsa/microchip/ksz8.c | 4 +- include/net/dsa.h | 2 + net/dsa/tag_ksz.c | 66 ++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 2 deletions(-) diff --git a/drivers/net/dsa/microchip/ksz8.c b/drivers/net/dsa/microchip/ksz8.c index 5e5bfc5cae2d..ac9e8ef5774a 100644 --- a/drivers/net/dsa/microchip/ksz8.c +++ b/drivers/net/dsa/microchip/ksz8.c @@ -2966,7 +2966,7 @@ static enum dsa_tag_protocol ksz8463_get_tag_protocol(struct dsa_switch *ds, int port, enum dsa_tag_protocol mp) { - return DSA_TAG_PROTO_KSZ9893; + return DSA_TAG_PROTO_KSZ8463; } static int ksz8463_connect_tag_protocol(struct dsa_switch *ds, @@ -2974,7 +2974,7 @@ static int ksz8463_connect_tag_protocol(struct dsa_switch *ds, { struct ksz_tagger_data *tagger_data; - if (proto != DSA_TAG_PROTO_KSZ9893) + if (proto != DSA_TAG_PROTO_KSZ8463) return -EPROTONOSUPPORT; tagger_data = ksz_tagger_data(ds); diff --git a/include/net/dsa.h b/include/net/dsa.h index 8c16ef23cc10..6f7f5c17b532 100644 --- a/include/net/dsa.h +++ b/include/net/dsa.h @@ -59,6 +59,7 @@ struct tc_action; #define DSA_TAG_PROTO_MXL_GSW1XX_VALUE 31 #define DSA_TAG_PROTO_MXL862_VALUE 32 #define DSA_TAG_PROTO_NETC_VALUE 33 +#define DSA_TAG_PROTO_KSZ8463_VALUE 34 enum dsa_tag_protocol { DSA_TAG_PROTO_NONE = DSA_TAG_PROTO_NONE_VALUE, @@ -95,6 +96,7 @@ enum dsa_tag_protocol { DSA_TAG_PROTO_MXL_GSW1XX = DSA_TAG_PROTO_MXL_GSW1XX_VALUE, DSA_TAG_PROTO_MXL862 = DSA_TAG_PROTO_MXL862_VALUE, DSA_TAG_PROTO_NETC = DSA_TAG_PROTO_NETC_VALUE, + DSA_TAG_PROTO_KSZ8463 = DSA_TAG_PROTO_KSZ8463_VALUE, }; struct dsa_switch; diff --git a/net/dsa/tag_ksz.c b/net/dsa/tag_ksz.c index f8b40437c5fa..b4d70ba930dc 100644 --- a/net/dsa/tag_ksz.c +++ b/net/dsa/tag_ksz.c @@ -12,6 +12,7 @@ #include "tag.h" +#define KSZ8463_NAME "ksz8463" #define KSZ8795_NAME "ksz8795" #define KSZ9477_NAME "ksz9477" #define KSZ9893_NAME "ksz9893" @@ -396,6 +397,70 @@ static const struct dsa_device_ops ksz9893_netdev_ops = { DSA_TAG_DRIVER(ksz9893_netdev_ops); MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_KSZ9893, KSZ9893_NAME); +#define KSZ8463_TAIL_TAG_PRIO GENMASK(4, 3) +#define KSZ8463_TAIL_TAG_EG_PORT_M GENMASK(2, 0) + +static struct sk_buff *ksz8463_xmit(struct sk_buff *skb, + struct net_device *dev) +{ + u16 queue_mapping = skb_get_queue_mapping(skb); + u8 prio = netdev_txq_to_tc(dev, queue_mapping); + + return ksz_common_xmit(skb, dev, false, + FIELD_PREP(KSZ8463_TAIL_TAG_PRIO, prio), + 0); +} + +static struct sk_buff *ksz8463_rcv(struct sk_buff *skb, struct net_device *dev) +{ + unsigned int len = KSZ_EGRESS_TAG_LEN; + struct ptp_header *ptp_hdr; + unsigned int ptp_class; + unsigned int port; + ktime_t ts; + u8 *tag; + + if (skb_linearize(skb)) { + kfree_skb(skb); + return NULL; + } + + KSZ_SKB_CB(skb)->tstamp = 0; + + /* Tag decoding */ + tag = skb_tail_pointer(skb) - KSZ_EGRESS_TAG_LEN; + port = tag[0] & KSZ8463_TAIL_TAG_EG_PORT_M; + + __skb_push(skb, ETH_HLEN); + ptp_class = ptp_classify_raw(skb); + __skb_pull(skb, ETH_HLEN); + if (ptp_class == PTP_CLASS_NONE) + goto common_rcv; + + ptp_hdr = ptp_parse_header(skb, ptp_class); + if (ptp_hdr) { + ts = ksz_decode_tstamp(get_unaligned_be32(&ptp_hdr->reserved2)); + KSZ_SKB_CB(skb)->tstamp = ts; + ptp_hdr->reserved2 = 0; + } + +common_rcv: + return ksz_common_rcv(skb, dev, port, len); +} + +static const struct dsa_device_ops ksz8463_netdev_ops = { + .name = KSZ8463_NAME, + .proto = DSA_TAG_PROTO_KSZ8463, + .xmit = ksz8463_xmit, + .rcv = ksz8463_rcv, + .connect = ksz_connect, + .disconnect = ksz_disconnect, + .needed_tailroom = KSZ_INGRESS_TAG_LEN, +}; + +DSA_TAG_DRIVER(ksz8463_netdev_ops); +MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_KSZ8463, KSZ8463_NAME); + /* For xmit, 2/6 bytes are added before FCS. * --------------------------------------------------------------------------- * DA(6bytes)|SA(6bytes)|....|Data(nbytes)|ts(4bytes)|tag0(1byte)|tag1(1byte)| @@ -468,6 +533,7 @@ DSA_TAG_DRIVER(lan937x_netdev_ops); MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_LAN937X, LAN937X_NAME); static struct dsa_tag_driver *dsa_tag_driver_array[] = { + &DSA_TAG_DRIVER_NAME(ksz8463_netdev_ops), &DSA_TAG_DRIVER_NAME(ksz8795_netdev_ops), &DSA_TAG_DRIVER_NAME(ksz9477_netdev_ops), &DSA_TAG_DRIVER_NAME(ksz9893_netdev_ops), From 172397752efb95d45c10fb21f8ec29103085aba2 Mon Sep 17 00:00:00 2001 From: "Bastien Curutchet (Schneider Electric)" Date: Mon, 27 Jul 2026 12:20:51 +0200 Subject: [PATCH 09/10] net: dsa: microchip: explicitly enable detection of L2 PTP frames Detection of L2 PTP frames needs to be enabled for PTP to work at the L2 layer. The bit enabling this detection is set by default on the switches currently supported by the driver, but it is unset by default on the KSZ8463 for which support will be added in upcoming patches. Explicitly enable the detection of L2 PTP frames for all switches when PTP is enabled. Signed-off-by: Bastien Curutchet (Schneider Electric) Link: https://patch.msgid.link/20260727-ksz-new-ptp-v3-9-caba39e680e3@bootlin.com Signed-off-by: Jakub Kicinski --- drivers/net/dsa/microchip/ksz_ptp.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/net/dsa/microchip/ksz_ptp.c b/drivers/net/dsa/microchip/ksz_ptp.c index d8a583ed8b43..febc3141f201 100644 --- a/drivers/net/dsa/microchip/ksz_ptp.c +++ b/drivers/net/dsa/microchip/ksz_ptp.c @@ -953,8 +953,9 @@ int ksz_ptp_clock_register(struct dsa_switch *ds) /* Currently only P2P mode is supported. When 802_1AS bit is set, it * forwards all PTP packets to host port and none to other ports. */ - ret = ksz_rmw16(dev, regs[PTP_MSG_CONF1], PTP_TC_P2P | PTP_802_1AS, - PTP_TC_P2P | PTP_802_1AS); + ret = ksz_rmw16(dev, regs[PTP_MSG_CONF1], + PTP_TC_P2P | PTP_802_1AS | PTP_ETH_ENABLE, + PTP_TC_P2P | PTP_802_1AS | PTP_ETH_ENABLE); if (ret) return ret; From d06c58f4fcf2b62172d6bf5a9e65e557d2d0d582 Mon Sep 17 00:00:00 2001 From: "Bastien Curutchet (Schneider Electric)" Date: Mon, 27 Jul 2026 12:20:52 +0200 Subject: [PATCH 10/10] net: dsa: microchip: add two-steps PTP support for KSZ8463 The KSZ8463 switch supports PTP but it's not supported by the driver. Add L2 two-step PTP support for the KSZ8463. IPv4 and IPv6 layers aren't supported. Neither is one-step PTP. Use KSZ8463-specific implementations of the .get_ts_info and .port_hwtstamp_set callbacks. The pdelay_req and pdelay_resp timestamps share one interrupt bit status while they're located in two different registers. So introduce last_tx_is_pdelayresp to keep track of the last sent event type. This flag is set by the xmit worker right before sending the packet and then used in the interrupt handler to retrieve the timestamp location. Signed-off-by: Bastien Curutchet (Schneider Electric) Link: https://patch.msgid.link/20260727-ksz-new-ptp-v3-10-caba39e680e3@bootlin.com Signed-off-by: Jakub Kicinski --- drivers/net/dsa/microchip/ksz8.c | 26 +++-- drivers/net/dsa/microchip/ksz8_reg.h | 1 + drivers/net/dsa/microchip/ksz_common.h | 1 + drivers/net/dsa/microchip/ksz_ptp.c | 135 +++++++++++++++++++++++- drivers/net/dsa/microchip/ksz_ptp.h | 7 ++ drivers/net/dsa/microchip/ksz_ptp_reg.h | 4 + 6 files changed, 167 insertions(+), 7 deletions(-) diff --git a/drivers/net/dsa/microchip/ksz8.c b/drivers/net/dsa/microchip/ksz8.c index ac9e8ef5774a..941ae9f66f70 100644 --- a/drivers/net/dsa/microchip/ksz8.c +++ b/drivers/net/dsa/microchip/ksz8.c @@ -242,8 +242,11 @@ static int ksz8463_girq_setup(struct ksz_device *dev) static int ksz8463_reset_switch(struct ksz_device *dev) { - ksz_cfg(dev, KSZ8463_REG_SW_RESET, KSZ8463_GLOBAL_SOFTWARE_RESET, true); - ksz_cfg(dev, KSZ8463_REG_SW_RESET, KSZ8463_GLOBAL_SOFTWARE_RESET, + ksz_cfg(dev, KSZ8463_REG_SW_RESET, + KSZ8463_GLOBAL_SOFTWARE_RESET | KSZ8463_PTP_SOFTWARE_RESET, + true); + ksz_cfg(dev, KSZ8463_REG_SW_RESET, + KSZ8463_GLOBAL_SOFTWARE_RESET | KSZ8463_PTP_SOFTWARE_RESET, false); return 0; } @@ -2474,17 +2477,24 @@ static int ksz8463_setup(struct dsa_switch *ds) ret = ksz8463_ptp_irq_setup(ds); if (ret) goto free_girq; + + ret = ksz_ptp_clock_register(ds); + if (ret) { + dev_err(dev->dev, "Failed to register PTP clock: %d\n", + ret); + goto free_ptp_irq; + } } ret = ksz_mdio_register(dev); if (ret < 0) { dev_err(dev->dev, "failed to register the mdio"); - goto free_ptp_irq; + goto ptp_clock_unregister; } ret = ksz_dcb_init(dev); if (ret) - goto free_ptp_irq; + goto ptp_clock_unregister; /* start switch */ regmap_update_bits(ksz_regmap_8(dev), regs[S_START_CTRL], @@ -2492,6 +2502,9 @@ static int ksz8463_setup(struct dsa_switch *ds) return 0; +ptp_clock_unregister: + if (dev->irq > 0) + ksz_ptp_clock_unregister(ds); free_ptp_irq: if (dev->irq > 0) ksz8463_ptp_irq_free(ds); @@ -2507,6 +2520,7 @@ static void ksz8463_teardown(struct dsa_switch *ds) struct ksz_device *dev = ds->priv; if (dev->irq > 0) { + ksz_ptp_clock_unregister(ds); ksz8463_ptp_irq_free(ds); ksz_irq_free(&dev->girq); } @@ -3129,9 +3143,9 @@ const struct dsa_switch_ops ksz8463_switch_ops = { .port_max_mtu = ksz88xx_max_mtu, .suspend = ksz_suspend, .resume = ksz_resume, - .get_ts_info = ksz_get_ts_info, + .get_ts_info = ksz8463_get_ts_info, .port_hwtstamp_get = ksz_hwtstamp_get, - .port_hwtstamp_set = ksz_hwtstamp_set, + .port_hwtstamp_set = ksz8463_hwtstamp_set, .port_txtstamp = ksz_port_txtstamp, .port_rxtstamp = ksz_port_rxtstamp, .port_setup_tc = ksz8_setup_tc, diff --git a/drivers/net/dsa/microchip/ksz8_reg.h b/drivers/net/dsa/microchip/ksz8_reg.h index 981ab441d9b7..6bc511da1f7d 100644 --- a/drivers/net/dsa/microchip/ksz8_reg.h +++ b/drivers/net/dsa/microchip/ksz8_reg.h @@ -786,6 +786,7 @@ #define KSZ8463_REG_SW_RESET 0x126 #define KSZ8463_GLOBAL_SOFTWARE_RESET BIT(0) +#define KSZ8463_PTP_SOFTWARE_RESET BIT(2) #define KSZ8463_PTP_CLK_CTRL 0x600 diff --git a/drivers/net/dsa/microchip/ksz_common.h b/drivers/net/dsa/microchip/ksz_common.h index 0f2abb22ca91..cbe98494578c 100644 --- a/drivers/net/dsa/microchip/ksz_common.h +++ b/drivers/net/dsa/microchip/ksz_common.h @@ -194,6 +194,7 @@ struct ksz_port { struct kernel_hwtstamp_config tstamp_config; bool hwts_tx_en; bool hwts_rx_en; + bool last_tx_is_pdelayresp; struct ksz_irq ptpirq; struct ksz_ptp_irq ptpmsg_irq[3]; ktime_t tstamp_msg; diff --git a/drivers/net/dsa/microchip/ksz_ptp.c b/drivers/net/dsa/microchip/ksz_ptp.c index febc3141f201..39cc70d65900 100644 --- a/drivers/net/dsa/microchip/ksz_ptp.c +++ b/drivers/net/dsa/microchip/ksz_ptp.c @@ -297,6 +297,31 @@ static int ksz_ptp_enable_mode(struct ksz_device *dev) tag_en ? PTP_ENABLE : 0); } +int ksz8463_get_ts_info(struct dsa_switch *ds, int port, + struct kernel_ethtool_ts_info *ts) +{ + struct ksz_device *dev = ds->priv; + struct ksz_ptp_data *ptp_data; + + ptp_data = &dev->ptp_data; + + if (!ptp_data->clock) + return -ENODEV; + + ts->so_timestamping = SOF_TIMESTAMPING_TX_HARDWARE | + SOF_TIMESTAMPING_RX_HARDWARE | + SOF_TIMESTAMPING_RAW_HARDWARE; + + ts->tx_types = BIT(HWTSTAMP_TX_OFF) | BIT(HWTSTAMP_TX_ON); + + ts->rx_filters = BIT(HWTSTAMP_FILTER_NONE) | + BIT(HWTSTAMP_FILTER_PTP_V2_L2_EVENT); + + ts->phc_index = ptp_clock_index(ptp_data->clock); + + return 0; +} + /* The function is return back the capability of timestamping feature when * requested through ethtool -T utility */ @@ -341,6 +366,72 @@ int ksz_hwtstamp_get(struct dsa_switch *ds, int port, return 0; } +static int ksz8463_set_hwtstamp_config(struct ksz_device *dev, + struct ksz_port *prt, + struct kernel_hwtstamp_config *config) +{ + const u16 *regs = dev->info->regs; + int ret; + + if (config->flags) + return -EINVAL; + + switch (config->tx_type) { + case HWTSTAMP_TX_OFF: + prt->ptpmsg_irq[KSZ8463_SYNC_MSG].ts_en = false; + prt->ptpmsg_irq[KSZ8463_XDREQ_PDRES_MSG].ts_en = false; + prt->hwts_tx_en = false; + break; + case HWTSTAMP_TX_ON: + prt->ptpmsg_irq[KSZ8463_SYNC_MSG].ts_en = true; + prt->ptpmsg_irq[KSZ8463_XDREQ_PDRES_MSG].ts_en = true; + prt->hwts_tx_en = true; + + ret = ksz_rmw16(dev, regs[PTP_MSG_CONF1], PTP_1STEP, 0); + if (ret) + return ret; + + break; + default: + return -ERANGE; + } + + switch (config->rx_filter) { + case HWTSTAMP_FILTER_NONE: + prt->hwts_rx_en = false; + break; + case HWTSTAMP_FILTER_PTP_V2_L2_EVENT: + case HWTSTAMP_FILTER_PTP_V2_L2_SYNC: + config->rx_filter = HWTSTAMP_FILTER_PTP_V2_L2_EVENT; + prt->hwts_rx_en = true; + break; + default: + config->rx_filter = HWTSTAMP_FILTER_NONE; + return -ERANGE; + } + + return ksz_ptp_enable_mode(dev); +} + +int ksz8463_hwtstamp_set(struct dsa_switch *ds, int port, + struct kernel_hwtstamp_config *config, + struct netlink_ext_ack *extack) +{ + struct ksz_device *dev = ds->priv; + struct ksz_port *prt; + int ret; + + prt = &dev->ports[port]; + + ret = ksz8463_set_hwtstamp_config(dev, prt, config); + if (ret) + return ret; + + prt->tstamp_config = *config; + + return 0; +} + static int ksz_set_hwtstamp_config(struct ksz_device *dev, struct ksz_port *prt, struct kernel_hwtstamp_config *config) @@ -571,6 +662,31 @@ static void ksz_ptp_txtstamp_skb(struct ksz_device *dev, skb_complete_tx_timestamp(skb, &hwtstamps); } +static void ksz8463_set_pdelayresp_flag(struct ksz_port *prt, + struct sk_buff *skb) +{ + struct ptp_header *hdr; + unsigned int type; + u8 ptp_msg_type; + + if (!ksz_is_ksz8463(prt->ksz_dev)) + return; + + if (skb_linearize(skb)) + return; + + type = ptp_classify_raw(skb); + if (type == PTP_CLASS_NONE) + return; + + hdr = ptp_parse_header(skb, type); + if (!hdr) + return; + + ptp_msg_type = ptp_get_msgtype(hdr, type); + prt->last_tx_is_pdelayresp = (ptp_msg_type == PTP_MSGTYPE_PDELAY_RESP); +} + void ksz_port_deferred_xmit(struct kthread_work *work) { struct ksz_deferred_xmit_work *xmit_work = work_to_xmit_work(work); @@ -587,6 +703,8 @@ void ksz_port_deferred_xmit(struct kthread_work *work) reinit_completion(&prt->tstamp_msg_comp); + ksz8463_set_pdelayresp_flag(prt, skb); + dsa_enqueue_skb(skb, skb->dev); ksz_ptp_txtstamp_skb(dev, prt, clone); @@ -979,7 +1097,22 @@ void ksz_ptp_clock_unregister(struct dsa_switch *ds) static int ksz_read_ts(struct ksz_port *port, u16 reg, u32 *ts) { - return ksz_read32(port->ksz_dev, reg, ts); + u16 ts_reg = reg; + + /** + * On KSZ8463 DREQ and DRESP timestamps share one interrupt line + * so we have to check the nature of the latest event sent to know + * where the timestamp is located + */ + if (ksz_is_ksz8463(port->ksz_dev)) { + const struct ksz_dev_ops *ops = port->ksz_dev->dev_ops; + + if (port->last_tx_is_pdelayresp && + ts_reg == ops->get_port_addr(port->num, KSZ8463_REG_PORT_DREQ_TS)) + ts_reg += KSZ8463_DRESP_TS_OFFSET; + } + + return ksz_read32(port->ksz_dev, ts_reg, ts); } static irqreturn_t ksz_ptp_msg_thread_fn(int irq, void *dev_id) diff --git a/drivers/net/dsa/microchip/ksz_ptp.h b/drivers/net/dsa/microchip/ksz_ptp.h index 11408580031d..7067ec9bd1e6 100644 --- a/drivers/net/dsa/microchip/ksz_ptp.h +++ b/drivers/net/dsa/microchip/ksz_ptp.h @@ -39,11 +39,16 @@ void ksz_ptp_clock_unregister(struct dsa_switch *ds); int ksz_get_ts_info(struct dsa_switch *ds, int port, struct kernel_ethtool_ts_info *ts); +int ksz8463_get_ts_info(struct dsa_switch *ds, int port, + struct kernel_ethtool_ts_info *ts); int ksz_hwtstamp_get(struct dsa_switch *ds, int port, struct kernel_hwtstamp_config *config); int ksz_hwtstamp_set(struct dsa_switch *ds, int port, struct kernel_hwtstamp_config *config, struct netlink_ext_ack *extack); +int ksz8463_hwtstamp_set(struct dsa_switch *ds, int port, + struct kernel_hwtstamp_config *config, + struct netlink_ext_ack *extack); void ksz_port_txtstamp(struct dsa_switch *ds, int port, struct sk_buff *skb); void ksz_port_deferred_xmit(struct kthread_work *work); bool ksz_port_rxtstamp(struct dsa_switch *ds, int port, struct sk_buff *skb, @@ -82,10 +87,12 @@ static inline int ksz8463_ptp_irq_setup(struct dsa_switch *ds) static inline void ksz8463_ptp_irq_free(struct dsa_switch *ds) {} #define ksz_get_ts_info NULL +#define ksz8463_get_ts_info NULL #define ksz_hwtstamp_get NULL #define ksz_hwtstamp_set NULL +#define ksz8463_hwtstamp_set NULL #define ksz_port_rxtstamp NULL diff --git a/drivers/net/dsa/microchip/ksz_ptp_reg.h b/drivers/net/dsa/microchip/ksz_ptp_reg.h index 1a669d6ee889..65ea8577af75 100644 --- a/drivers/net/dsa/microchip/ksz_ptp_reg.h +++ b/drivers/net/dsa/microchip/ksz_ptp_reg.h @@ -137,4 +137,8 @@ #define KSZ_XDREQ_MSG 1 #define KSZ_PDRES_MSG 0 +#define KSZ8463_DRESP_TS_OFFSET (KSZ8463_REG_PORT_DRESP_TS - KSZ8463_REG_PORT_DREQ_TS) +#define KSZ8463_SYNC_MSG 0 +#define KSZ8463_XDREQ_PDRES_MSG 1 + #endif