diff options
Diffstat (limited to 'drivers/net/ethernet/renesas')
-rw-r--r-- | drivers/net/ethernet/renesas/Makefile | 1 | ||||
-rw-r--r-- | drivers/net/ethernet/renesas/ravb_main.c | 3 | ||||
-rw-r--r-- | drivers/net/ethernet/renesas/rcar_gen4_ptp.c | 76 | ||||
-rw-r--r-- | drivers/net/ethernet/renesas/rcar_gen4_ptp.h | 33 | ||||
-rw-r--r-- | drivers/net/ethernet/renesas/rswitch.h | 43 | ||||
-rw-r--r-- | drivers/net/ethernet/renesas/rswitch_l2.c | 316 | ||||
-rw-r--r-- | drivers/net/ethernet/renesas/rswitch_l2.h | 15 | ||||
-rw-r--r-- | drivers/net/ethernet/renesas/rswitch_main.c (renamed from drivers/net/ethernet/renesas/rswitch.c) | 97 | ||||
-rw-r--r-- | drivers/net/ethernet/renesas/rtsn.c | 3 | ||||
-rw-r--r-- | drivers/net/ethernet/renesas/sh_eth.c | 34 |
10 files changed, 500 insertions, 121 deletions
diff --git a/drivers/net/ethernet/renesas/Makefile b/drivers/net/ethernet/renesas/Makefile index f65fc76f8b4d..d63e0c61bb68 100644 --- a/drivers/net/ethernet/renesas/Makefile +++ b/drivers/net/ethernet/renesas/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_SH_ETH) += sh_eth.o ravb-objs := ravb_main.o ravb_ptp.o obj-$(CONFIG_RAVB) += ravb.o +rswitch-objs := rswitch_main.o rswitch_l2.o obj-$(CONFIG_RENESAS_ETHER_SWITCH) += rswitch.o obj-$(CONFIG_RENESAS_GEN4_PTP) += rcar_gen4_ptp.o diff --git a/drivers/net/ethernet/renesas/ravb_main.c b/drivers/net/ethernet/renesas/ravb_main.c index 94b6fb94f8f1..9d3bd65b85ff 100644 --- a/drivers/net/ethernet/renesas/ravb_main.c +++ b/drivers/net/ethernet/renesas/ravb_main.c @@ -802,7 +802,6 @@ static int ravb_rx_gbeth(struct net_device *ndev, int budget, int q) const struct ravb_hw_info *info = priv->info; struct net_device_stats *stats; struct ravb_rx_desc *desc; - struct sk_buff *skb; int rx_packets = 0; u8 desc_status; u16 desc_len; @@ -815,6 +814,8 @@ static int ravb_rx_gbeth(struct net_device *ndev, int budget, int q) stats = &priv->stats[q]; for (i = 0; i < limit; i++, priv->cur_rx[q]++) { + struct sk_buff *skb = NULL; + entry = priv->cur_rx[q] % priv->num_rx_ring[q]; desc = &priv->rx_ring[q].desc[entry]; if (rx_packets == budget || desc->die_dt == DT_FEMPTY) diff --git a/drivers/net/ethernet/renesas/rcar_gen4_ptp.c b/drivers/net/ethernet/renesas/rcar_gen4_ptp.c index 4c3e8cc5046f..d0979abd36de 100644 --- a/drivers/net/ethernet/renesas/rcar_gen4_ptp.c +++ b/drivers/net/ethernet/renesas/rcar_gen4_ptp.c @@ -12,19 +12,18 @@ #include <linux/slab.h> #include "rcar_gen4_ptp.h" -#define ptp_to_priv(ptp) container_of(ptp, struct rcar_gen4_ptp_private, info) -static const struct rcar_gen4_ptp_reg_offset gen4_offs = { - .enable = PTPTMEC, - .disable = PTPTMDC, - .increment = PTPTIVC0, - .config_t0 = PTPTOVC00, - .config_t1 = PTPTOVC10, - .config_t2 = PTPTOVC20, - .monitor_t0 = PTPGPTPTM00, - .monitor_t1 = PTPGPTPTM10, - .monitor_t2 = PTPGPTPTM20, -}; +#define PTPTMEC_REG 0x0010 +#define PTPTMDC_REG 0x0014 +#define PTPTIVC0_REG 0x0020 +#define PTPTOVC00_REG 0x0030 +#define PTPTOVC10_REG 0x0034 +#define PTPTOVC20_REG 0x0038 +#define PTPGPTPTM00_REG 0x0050 +#define PTPGPTPTM10_REG 0x0054 +#define PTPGPTPTM20_REG 0x0058 + +#define ptp_to_priv(ptp) container_of(ptp, struct rcar_gen4_ptp_private, info) static int rcar_gen4_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm) { @@ -38,20 +37,21 @@ static int rcar_gen4_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm) diff = div_s64(addend * scaled_ppm_to_ppb(scaled_ppm), NSEC_PER_SEC); addend = neg_adj ? addend - diff : addend + diff; - iowrite32(addend, ptp_priv->addr + ptp_priv->offs->increment); + iowrite32(addend, ptp_priv->addr + PTPTIVC0_REG); return 0; } -/* Caller must hold the lock */ static void _rcar_gen4_ptp_gettime(struct ptp_clock_info *ptp, struct timespec64 *ts) { struct rcar_gen4_ptp_private *ptp_priv = ptp_to_priv(ptp); - ts->tv_nsec = ioread32(ptp_priv->addr + ptp_priv->offs->monitor_t0); - ts->tv_sec = ioread32(ptp_priv->addr + ptp_priv->offs->monitor_t1) | - ((s64)ioread32(ptp_priv->addr + ptp_priv->offs->monitor_t2) << 32); + lockdep_assert_held(&ptp_priv->lock); + + ts->tv_nsec = ioread32(ptp_priv->addr + PTPGPTPTM00_REG); + ts->tv_sec = ioread32(ptp_priv->addr + PTPGPTPTM10_REG) | + ((s64)ioread32(ptp_priv->addr + PTPGPTPTM20_REG) << 32); } static int rcar_gen4_ptp_gettime(struct ptp_clock_info *ptp, @@ -73,14 +73,14 @@ static void _rcar_gen4_ptp_settime(struct ptp_clock_info *ptp, { struct rcar_gen4_ptp_private *ptp_priv = ptp_to_priv(ptp); - iowrite32(1, ptp_priv->addr + ptp_priv->offs->disable); - iowrite32(0, ptp_priv->addr + ptp_priv->offs->config_t2); - iowrite32(0, ptp_priv->addr + ptp_priv->offs->config_t1); - iowrite32(0, ptp_priv->addr + ptp_priv->offs->config_t0); - iowrite32(1, ptp_priv->addr + ptp_priv->offs->enable); - iowrite32(ts->tv_sec >> 32, ptp_priv->addr + ptp_priv->offs->config_t2); - iowrite32(ts->tv_sec, ptp_priv->addr + ptp_priv->offs->config_t1); - iowrite32(ts->tv_nsec, ptp_priv->addr + ptp_priv->offs->config_t0); + iowrite32(1, ptp_priv->addr + PTPTMDC_REG); + iowrite32(0, ptp_priv->addr + PTPTOVC20_REG); + iowrite32(0, ptp_priv->addr + PTPTOVC10_REG); + iowrite32(0, ptp_priv->addr + PTPTOVC00_REG); + iowrite32(1, ptp_priv->addr + PTPTMEC_REG); + iowrite32(ts->tv_sec >> 32, ptp_priv->addr + PTPTOVC20_REG); + iowrite32(ts->tv_sec, ptp_priv->addr + PTPTOVC10_REG); + iowrite32(ts->tv_nsec, ptp_priv->addr + PTPTOVC00_REG); } static int rcar_gen4_ptp_settime(struct ptp_clock_info *ptp, @@ -130,17 +130,6 @@ static struct ptp_clock_info rcar_gen4_ptp_info = { .enable = rcar_gen4_ptp_enable, }; -static int rcar_gen4_ptp_set_offs(struct rcar_gen4_ptp_private *ptp_priv, - enum rcar_gen4_ptp_reg_layout layout) -{ - if (layout != RCAR_GEN4_PTP_REG_LAYOUT) - return -EINVAL; - - ptp_priv->offs = &gen4_offs; - - return 0; -} - static s64 rcar_gen4_ptp_rate_to_increment(u32 rate) { /* Timer increment in ns. @@ -151,27 +140,20 @@ static s64 rcar_gen4_ptp_rate_to_increment(u32 rate) return div_s64(1000000000LL << 27, rate); } -int rcar_gen4_ptp_register(struct rcar_gen4_ptp_private *ptp_priv, - enum rcar_gen4_ptp_reg_layout layout, u32 rate) +int rcar_gen4_ptp_register(struct rcar_gen4_ptp_private *ptp_priv, u32 rate) { - int ret; - if (ptp_priv->initialized) return 0; spin_lock_init(&ptp_priv->lock); - ret = rcar_gen4_ptp_set_offs(ptp_priv, layout); - if (ret) - return ret; - ptp_priv->default_addend = rcar_gen4_ptp_rate_to_increment(rate); - iowrite32(ptp_priv->default_addend, ptp_priv->addr + ptp_priv->offs->increment); + iowrite32(ptp_priv->default_addend, ptp_priv->addr + PTPTIVC0_REG); ptp_priv->clock = ptp_clock_register(&ptp_priv->info, NULL); if (IS_ERR(ptp_priv->clock)) return PTR_ERR(ptp_priv->clock); - iowrite32(0x01, ptp_priv->addr + ptp_priv->offs->enable); + iowrite32(0x01, ptp_priv->addr + PTPTMEC_REG); ptp_priv->initialized = true; return 0; @@ -180,7 +162,7 @@ EXPORT_SYMBOL_GPL(rcar_gen4_ptp_register); int rcar_gen4_ptp_unregister(struct rcar_gen4_ptp_private *ptp_priv) { - iowrite32(1, ptp_priv->addr + ptp_priv->offs->disable); + iowrite32(1, ptp_priv->addr + PTPTMDC_REG); return ptp_clock_unregister(ptp_priv->clock); } diff --git a/drivers/net/ethernet/renesas/rcar_gen4_ptp.h b/drivers/net/ethernet/renesas/rcar_gen4_ptp.h index e22da5acd53d..f77e79e47357 100644 --- a/drivers/net/ethernet/renesas/rcar_gen4_ptp.h +++ b/drivers/net/ethernet/renesas/rcar_gen4_ptp.h @@ -11,10 +11,6 @@ #define RCAR_GEN4_GPTP_OFFSET_S4 0x00018000 -enum rcar_gen4_ptp_reg_layout { - RCAR_GEN4_PTP_REG_LAYOUT -}; - /* driver's definitions */ #define RCAR_GEN4_RXTSTAMP_ENABLED BIT(0) #define RCAR_GEN4_RXTSTAMP_TYPE_V2_L2_EVENT BIT(1) @@ -23,37 +19,11 @@ enum rcar_gen4_ptp_reg_layout { #define RCAR_GEN4_TXTSTAMP_ENABLED BIT(0) -#define PTPRO 0 - -enum rcar_gen4_ptp_reg { - PTPTMEC = PTPRO + 0x0010, - PTPTMDC = PTPRO + 0x0014, - PTPTIVC0 = PTPRO + 0x0020, - PTPTOVC00 = PTPRO + 0x0030, - PTPTOVC10 = PTPRO + 0x0034, - PTPTOVC20 = PTPRO + 0x0038, - PTPGPTPTM00 = PTPRO + 0x0050, - PTPGPTPTM10 = PTPRO + 0x0054, - PTPGPTPTM20 = PTPRO + 0x0058, -}; - -struct rcar_gen4_ptp_reg_offset { - u16 enable; - u16 disable; - u16 increment; - u16 config_t0; - u16 config_t1; - u16 config_t2; - u16 monitor_t0; - u16 monitor_t1; - u16 monitor_t2; -}; struct rcar_gen4_ptp_private { void __iomem *addr; struct ptp_clock *clock; struct ptp_clock_info info; - const struct rcar_gen4_ptp_reg_offset *offs; spinlock_t lock; /* For multiple registers access */ u32 tstamp_tx_ctrl; u32 tstamp_rx_ctrl; @@ -61,8 +31,7 @@ struct rcar_gen4_ptp_private { bool initialized; }; -int rcar_gen4_ptp_register(struct rcar_gen4_ptp_private *ptp_priv, - enum rcar_gen4_ptp_reg_layout layout, u32 rate); +int rcar_gen4_ptp_register(struct rcar_gen4_ptp_private *ptp_priv, u32 rate); int rcar_gen4_ptp_unregister(struct rcar_gen4_ptp_private *ptp_priv); struct rcar_gen4_ptp_private *rcar_gen4_ptp_alloc(struct platform_device *pdev); diff --git a/drivers/net/ethernet/renesas/rswitch.h b/drivers/net/ethernet/renesas/rswitch.h index 532192cbca4b..a1d4a877e5bd 100644 --- a/drivers/net/ethernet/renesas/rswitch.h +++ b/drivers/net/ethernet/renesas/rswitch.h @@ -1,19 +1,25 @@ /* SPDX-License-Identifier: GPL-2.0 */ /* Renesas Ethernet Switch device driver * - * Copyright (C) 2022 Renesas Electronics Corporation + * Copyright (C) 2022-2025 Renesas Electronics Corporation */ #ifndef __RSWITCH_H__ #define __RSWITCH_H__ #include <linux/platform_device.h> +#include <linux/phy.h> + #include "rcar_gen4_ptp.h" #define RSWITCH_MAX_NUM_QUEUES 128 #define RSWITCH_NUM_AGENTS 5 #define RSWITCH_NUM_PORTS 3 + +#define rswitch_for_all_ports(_priv, _rdev) \ + list_for_each_entry(_rdev, &_priv->port_list, list) + #define rswitch_for_each_enabled_port(priv, i) \ for (i = 0; i < RSWITCH_NUM_PORTS; i++) \ if (priv->rdev[i]->disabled) \ @@ -809,7 +815,8 @@ enum rswitch_gwca_mode { #define FWPC0_IP4EA BIT(10) #define FWPC0_IPDSA BIT(12) #define FWPC0_IPHLA BIT(18) -#define FWPC0_MACSDA BIT(20) +#define FWPC0_MACDSA BIT(20) +#define FWPC0_MACSSA BIT(23) #define FWPC0_MACHLA BIT(26) #define FWPC0_MACHMA BIT(27) #define FWPC0_VLANSA BIT(28) @@ -820,12 +827,30 @@ enum rswitch_gwca_mode { #define FWPC2(i) (FWPC20 + (i) * 0x10) #define FWCP2_LTWFW GENMASK(16 + (RSWITCH_NUM_AGENTS - 1), 16) +#define FWCP2_LTWFW_MASK GENMASK(16 + (RSWITCH_NUM_AGENTS - 1), 16) #define FWPBFC(i) (FWPBFC0 + (i) * 0x10) #define FWPBFC_PBDV GENMASK(RSWITCH_NUM_AGENTS - 1, 0) #define FWPBFCSDC(j, i) (FWPBFCSDC00 + (i) * 0x10 + (j) * 0x04) +#define FWMACHEC_MACHMUE_MASK GENMASK(26, 16) + +#define FWMACTIM_MACTIOG BIT(0) +#define FWMACTIM_MACTR BIT(1) + +#define FWMACAGUSPC_MACAGUSP GENMASK(9, 0) +#define FWMACAGC_MACAGT GENMASK(15, 0) +#define FWMACAGC_MACAGE BIT(16) +#define FWMACAGC_MACAGSL BIT(17) +#define FWMACAGC_MACAGPM BIT(18) +#define FWMACAGC_MACDES BIT(24) +#define FWMACAGC_MACAGOG BIT(28) +#define FWMACAGC_MACDESOG BIT(29) + +#define RSW_AGEING_CLK_PER_US 0x140 +#define RSW_AGEING_TIME 300 + /* TOP */ #define TPEMIMC7(queue) (TPEMIMC70 + (queue) * 4) @@ -994,10 +1019,18 @@ struct rswitch_device { DECLARE_BITMAP(ts_skb_used, TS_TAGS_PER_PORT); bool disabled; + struct list_head list; + int port; struct rswitch_etha *etha; struct device_node *np_port; struct phy *serdes; + + struct net_device *brdev; /* master bridge device */ + unsigned int learning_requested : 1; + unsigned int learning_offloaded : 1; + unsigned int forwarding_requested : 1; + unsigned int forwarding_offloaded : 1; }; struct rswitch_mfwd_mac_table_entry { @@ -1022,11 +1055,17 @@ struct rswitch_private { struct rswitch_etha etha[RSWITCH_NUM_PORTS]; struct rswitch_mfwd mfwd; + struct list_head port_list; + spinlock_t lock; /* lock interrupt registers' control */ struct clk *clk; bool etha_no_runtime_change; bool gwca_halt; + struct net_device *offload_brdev; }; +bool is_rdev(const struct net_device *ndev); +void rswitch_modify(void __iomem *addr, enum rswitch_reg reg, u32 clear, u32 set); + #endif /* #ifndef __RSWITCH_H__ */ diff --git a/drivers/net/ethernet/renesas/rswitch_l2.c b/drivers/net/ethernet/renesas/rswitch_l2.c new file mode 100644 index 000000000000..4a69ec77d69c --- /dev/null +++ b/drivers/net/ethernet/renesas/rswitch_l2.c @@ -0,0 +1,316 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Renesas Ethernet Switch device driver + * + * Copyright (C) 2025 Renesas Electronics Corporation + */ + +#include <linux/err.h> +#include <linux/etherdevice.h> +#include <linux/if_bridge.h> +#include <linux/kernel.h> +#include <net/switchdev.h> + +#include "rswitch.h" +#include "rswitch_l2.h" + +static bool rdev_for_l2_offload(struct rswitch_device *rdev) +{ + return rdev->priv->offload_brdev && + rdev->brdev == rdev->priv->offload_brdev && + (test_bit(rdev->port, rdev->priv->opened_ports)); +} + +static void rswitch_change_l2_hw_offloading(struct rswitch_device *rdev, + bool start, bool learning) +{ + u32 bits = learning ? FWPC0_MACSSA | FWPC0_MACHLA | FWPC0_MACHMA : FWPC0_MACDSA; + u32 clear = start ? 0 : bits; + u32 set = start ? bits : 0; + + if ((learning && rdev->learning_offloaded == start) || + (!learning && rdev->forwarding_offloaded == start)) + return; + + rswitch_modify(rdev->priv->addr, FWPC0(rdev->port), clear, set); + + if (learning) + rdev->learning_offloaded = start; + else + rdev->forwarding_offloaded = start; + + netdev_info(rdev->ndev, "%s hw %s\n", start ? "starting" : "stopping", + learning ? "learning" : "forwarding"); +} + +static void rswitch_update_l2_hw_learning(struct rswitch_private *priv) +{ + struct rswitch_device *rdev; + bool learning_needed; + + rswitch_for_all_ports(priv, rdev) { + if (rdev_for_l2_offload(rdev)) + learning_needed = rdev->learning_requested; + else + learning_needed = false; + + rswitch_change_l2_hw_offloading(rdev, learning_needed, true); + } +} + +static void rswitch_update_l2_hw_forwarding(struct rswitch_private *priv) +{ + struct rswitch_device *rdev; + unsigned int fwd_mask; + + /* calculate fwd_mask with zeroes in bits corresponding to ports that + * shall participate in hardware forwarding + */ + fwd_mask = GENMASK(RSWITCH_NUM_AGENTS - 1, 0); + + rswitch_for_all_ports(priv, rdev) { + if (rdev_for_l2_offload(rdev) && rdev->forwarding_requested) + fwd_mask &= ~BIT(rdev->port); + } + + rswitch_for_all_ports(priv, rdev) { + if ((rdev_for_l2_offload(rdev) && rdev->forwarding_requested) || + rdev->forwarding_offloaded) { + /* Update allowed offload destinations even for ports + * with L2 offload enabled earlier. + * + * Do not allow L2 forwarding to self for hw port. + */ + iowrite32(FIELD_PREP(FWCP2_LTWFW_MASK, fwd_mask | BIT(rdev->port)), + priv->addr + FWPC2(rdev->port)); + } + + if (rdev_for_l2_offload(rdev) && + rdev->forwarding_requested && + !rdev->forwarding_offloaded) { + rswitch_change_l2_hw_offloading(rdev, true, false); + } else if (rdev->forwarding_offloaded) { + rswitch_change_l2_hw_offloading(rdev, false, false); + } + } +} + +void rswitch_update_l2_offload(struct rswitch_private *priv) +{ + rswitch_update_l2_hw_learning(priv); + rswitch_update_l2_hw_forwarding(priv); +} + +static void rswitch_update_offload_brdev(struct rswitch_private *priv) +{ + struct net_device *offload_brdev = NULL; + struct rswitch_device *rdev, *rdev2; + + rswitch_for_all_ports(priv, rdev) { + if (!rdev->brdev) + continue; + rswitch_for_all_ports(priv, rdev2) { + if (rdev2 == rdev) + break; + if (rdev2->brdev == rdev->brdev) { + offload_brdev = rdev->brdev; + break; + } + } + if (offload_brdev) + break; + } + + if (offload_brdev == priv->offload_brdev) + dev_dbg(&priv->pdev->dev, + "changing l2 offload from %s to %s\n", + netdev_name(priv->offload_brdev), + netdev_name(offload_brdev)); + else if (offload_brdev) + dev_dbg(&priv->pdev->dev, "starting l2 offload for %s\n", + netdev_name(offload_brdev)); + else if (!offload_brdev) + dev_dbg(&priv->pdev->dev, "stopping l2 offload for %s\n", + netdev_name(priv->offload_brdev)); + + priv->offload_brdev = offload_brdev; + + rswitch_update_l2_offload(priv); +} + +static bool rswitch_port_check(const struct net_device *ndev) +{ + return is_rdev(ndev); +} + +static void rswitch_port_update_brdev(struct net_device *ndev, + struct net_device *brdev) +{ + struct rswitch_device *rdev; + + if (!is_rdev(ndev)) + return; + + rdev = netdev_priv(ndev); + rdev->brdev = brdev; + rswitch_update_offload_brdev(rdev->priv); +} + +static int rswitch_port_update_stp_state(struct net_device *ndev, u8 stp_state) +{ + struct rswitch_device *rdev; + + if (!is_rdev(ndev)) + return -ENODEV; + + rdev = netdev_priv(ndev); + rdev->learning_requested = (stp_state == BR_STATE_LEARNING || + stp_state == BR_STATE_FORWARDING); + rdev->forwarding_requested = (stp_state == BR_STATE_FORWARDING); + rswitch_update_l2_offload(rdev->priv); + + return 0; +} + +static int rswitch_netdevice_event(struct notifier_block *nb, + unsigned long event, void *ptr) +{ + struct net_device *ndev = netdev_notifier_info_to_dev(ptr); + struct netdev_notifier_changeupper_info *info; + struct net_device *brdev; + + if (!rswitch_port_check(ndev)) + return NOTIFY_DONE; + if (event != NETDEV_CHANGEUPPER) + return NOTIFY_DONE; + + info = ptr; + + if (netif_is_bridge_master(info->upper_dev)) { + brdev = info->linking ? info->upper_dev : NULL; + rswitch_port_update_brdev(ndev, brdev); + } + + return NOTIFY_OK; +} + +static int rswitch_update_ageing_time(struct net_device *ndev, clock_t time) +{ + struct rswitch_device *rdev = netdev_priv(ndev); + u32 reg_val; + + if (!is_rdev(ndev)) + return -ENODEV; + + if (!FIELD_FIT(FWMACAGC_MACAGT, time)) + return -EINVAL; + + reg_val = FIELD_PREP(FWMACAGC_MACAGT, time); + reg_val |= FWMACAGC_MACAGE | FWMACAGC_MACAGSL; + iowrite32(reg_val, rdev->priv->addr + FWMACAGC); + + return 0; +} + +static int rswitch_port_attr_set(struct net_device *ndev, const void *ctx, + const struct switchdev_attr *attr, + struct netlink_ext_ack *extack) +{ + switch (attr->id) { + case SWITCHDEV_ATTR_ID_PORT_STP_STATE: + return rswitch_port_update_stp_state(ndev, attr->u.stp_state); + case SWITCHDEV_ATTR_ID_BRIDGE_AGEING_TIME: + return rswitch_update_ageing_time(ndev, attr->u.ageing_time); + default: + return -EOPNOTSUPP; + } +} + +static int rswitch_switchdev_event(struct notifier_block *nb, + unsigned long event, void *ptr) +{ + struct net_device *ndev = switchdev_notifier_info_to_dev(ptr); + int ret; + + if (event == SWITCHDEV_PORT_ATTR_SET) { + ret = switchdev_handle_port_attr_set(ndev, ptr, + rswitch_port_check, + rswitch_port_attr_set); + return notifier_from_errno(ret); + } + + if (!rswitch_port_check(ndev)) + return NOTIFY_DONE; + + return notifier_from_errno(-EOPNOTSUPP); +} + +static int rswitch_switchdev_blocking_event(struct notifier_block *nb, + unsigned long event, void *ptr) +{ + struct net_device *ndev = switchdev_notifier_info_to_dev(ptr); + int ret; + + switch (event) { + case SWITCHDEV_PORT_OBJ_ADD: + return -EOPNOTSUPP; + case SWITCHDEV_PORT_OBJ_DEL: + return -EOPNOTSUPP; + case SWITCHDEV_PORT_ATTR_SET: + ret = switchdev_handle_port_attr_set(ndev, ptr, + rswitch_port_check, + rswitch_port_attr_set); + break; + default: + if (!rswitch_port_check(ndev)) + return NOTIFY_DONE; + ret = -EOPNOTSUPP; + } + + return notifier_from_errno(ret); +} + +static struct notifier_block rswitch_netdevice_nb = { + .notifier_call = rswitch_netdevice_event, +}; + +static struct notifier_block rswitch_switchdev_nb = { + .notifier_call = rswitch_switchdev_event, +}; + +static struct notifier_block rswitch_switchdev_blocking_nb = { + .notifier_call = rswitch_switchdev_blocking_event, +}; + +int rswitch_register_notifiers(void) +{ + int ret; + + ret = register_netdevice_notifier(&rswitch_netdevice_nb); + if (ret) + goto register_netdevice_notifier_failed; + + ret = register_switchdev_notifier(&rswitch_switchdev_nb); + if (ret) + goto register_switchdev_notifier_failed; + + ret = register_switchdev_blocking_notifier(&rswitch_switchdev_blocking_nb); + if (ret) + goto register_switchdev_blocking_notifier_failed; + + return 0; + +register_switchdev_blocking_notifier_failed: + unregister_switchdev_notifier(&rswitch_switchdev_nb); +register_switchdev_notifier_failed: + unregister_netdevice_notifier(&rswitch_netdevice_nb); +register_netdevice_notifier_failed: + + return ret; +} + +void rswitch_unregister_notifiers(void) +{ + unregister_switchdev_blocking_notifier(&rswitch_switchdev_blocking_nb); + unregister_switchdev_notifier(&rswitch_switchdev_nb); + unregister_netdevice_notifier(&rswitch_netdevice_nb); +} diff --git a/drivers/net/ethernet/renesas/rswitch_l2.h b/drivers/net/ethernet/renesas/rswitch_l2.h new file mode 100644 index 000000000000..57050ede8f31 --- /dev/null +++ b/drivers/net/ethernet/renesas/rswitch_l2.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Renesas Ethernet Switch device driver + * + * Copyright (C) 2025 Renesas Electronics Corporation + */ + +#ifndef __RSWITCH_L2_H__ +#define __RSWITCH_L2_H__ + +void rswitch_update_l2_offload(struct rswitch_private *priv); + +int rswitch_register_notifiers(void); +void rswitch_unregister_notifiers(void); + +#endif /* #ifndef __RSWITCH_L2_H__ */ diff --git a/drivers/net/ethernet/renesas/rswitch.c b/drivers/net/ethernet/renesas/rswitch_main.c index aba772e14555..8d8acc2124b8 100644 --- a/drivers/net/ethernet/renesas/rswitch.c +++ b/drivers/net/ethernet/renesas/rswitch_main.c @@ -1,15 +1,18 @@ // SPDX-License-Identifier: GPL-2.0 /* Renesas Ethernet Switch device driver * - * Copyright (C) 2022 Renesas Electronics Corporation + * Copyright (C) 2022-2025 Renesas Electronics Corporation */ #include <linux/clk.h> #include <linux/dma-mapping.h> #include <linux/err.h> #include <linux/etherdevice.h> +#include <linux/ethtool.h> +#include <linux/ip.h> #include <linux/iopoll.h> #include <linux/kernel.h> +#include <linux/list.h> #include <linux/module.h> #include <linux/net_tstamp.h> #include <linux/of.h> @@ -25,6 +28,7 @@ #include <linux/sys_soc.h> #include "rswitch.h" +#include "rswitch_l2.h" static int rswitch_reg_wait(void __iomem *addr, u32 offs, u32 mask, u32 expected) { @@ -34,7 +38,7 @@ static int rswitch_reg_wait(void __iomem *addr, u32 offs, u32 mask, u32 expected 1, RSWITCH_TIMEOUT_US); } -static void rswitch_modify(void __iomem *addr, enum rswitch_reg reg, u32 clear, u32 set) +void rswitch_modify(void __iomem *addr, enum rswitch_reg reg, u32 clear, u32 set) { iowrite32((ioread32(addr + reg) & ~clear) | set, addr + reg); } @@ -109,10 +113,11 @@ static void rswitch_top_init(struct rswitch_private *priv) } /* Forwarding engine block (MFWD) */ -static void rswitch_fwd_init(struct rswitch_private *priv) +static int rswitch_fwd_init(struct rswitch_private *priv) { u32 all_ports_mask = GENMASK(RSWITCH_NUM_AGENTS - 1, 0); unsigned int i; + u32 reg_val; /* Start with empty configuration */ for (i = 0; i < RSWITCH_NUM_AGENTS; i++) { @@ -128,6 +133,14 @@ static void rswitch_fwd_init(struct rswitch_private *priv) iowrite32(0, priv->addr + FWPBFC(i)); } + /* Configure MAC table aging */ + rswitch_modify(priv->addr, FWMACAGUSPC, FWMACAGUSPC_MACAGUSP, + FIELD_PREP(FWMACAGUSPC_MACAGUSP, RSW_AGEING_CLK_PER_US)); + + reg_val = FIELD_PREP(FWMACAGC_MACAGT, RSW_AGEING_TIME); + reg_val |= FWMACAGC_MACAGE | FWMACAGC_MACAGSL; + iowrite32(reg_val, priv->addr + FWMACAGC); + /* For enabled ETHA ports, setup port based forwarding */ rswitch_for_each_enabled_port(priv, i) { /* Port based forwarding from port i to GWCA port */ @@ -140,6 +153,16 @@ static void rswitch_fwd_init(struct rswitch_private *priv) /* For GWCA port, allow direct descriptor forwarding */ rswitch_modify(priv->addr, FWPC1(priv->gwca.index), FWPC1_DDE, FWPC1_DDE); + + /* Initialize hardware L2 forwarding table */ + + /* Allow entire table to be used for "unsecure" entries */ + rswitch_modify(priv->addr, FWMACHEC, 0, FWMACHEC_MACHMUE_MASK); + + /* Initialize MAC hash table */ + iowrite32(FWMACTIM_MACTIOG, priv->addr + FWMACTIM); + + return rswitch_reg_wait(priv->addr, FWMACTIM, FWMACTIM_MACTIOG, 0); } /* Gateway CPU agent block (GWCA) */ @@ -1602,8 +1625,11 @@ static int rswitch_open(struct net_device *ndev) netif_start_queue(ndev); + if (rdev->brdev) + rswitch_update_l2_offload(rdev->priv); + return 0; -}; +} static int rswitch_stop(struct net_device *ndev) { @@ -1624,12 +1650,13 @@ static int rswitch_stop(struct net_device *ndev) napi_disable(&rdev->napi); + if (rdev->brdev) + rswitch_update_l2_offload(rdev->priv); + if (bitmap_empty(rdev->priv->opened_ports, RSWITCH_NUM_PORTS)) iowrite32(GWCA_TS_IRQ_BIT, rdev->priv->addr + GWTSDID); - for (tag = find_first_bit(rdev->ts_skb_used, TS_TAGS_PER_PORT); - tag < TS_TAGS_PER_PORT; - tag = find_next_bit(rdev->ts_skb_used, TS_TAGS_PER_PORT, tag + 1)) { + for_each_set_bit(tag, rdev->ts_skb_used, TS_TAGS_PER_PORT) { ts_skb = xchg(&rdev->ts_skb[tag], NULL); clear_bit(tag, rdev->ts_skb_used); if (ts_skb) @@ -1637,7 +1664,7 @@ static int rswitch_stop(struct net_device *ndev) } return 0; -}; +} static bool rswitch_ext_desc_set_info1(struct rswitch_device *rdev, struct sk_buff *skb, @@ -1850,16 +1877,46 @@ static int rswitch_eth_ioctl(struct net_device *ndev, struct ifreq *req, int cmd } } +static int rswitch_get_port_parent_id(struct net_device *ndev, + struct netdev_phys_item_id *ppid) +{ + struct rswitch_device *rdev = netdev_priv(ndev); + const char *name; + + name = dev_name(&rdev->priv->pdev->dev); + ppid->id_len = min_t(size_t, strlen(name), sizeof(ppid->id)); + memcpy(ppid->id, name, ppid->id_len); + + return 0; +} + +static int rswitch_get_phys_port_name(struct net_device *ndev, + char *name, size_t len) +{ + struct rswitch_device *rdev = netdev_priv(ndev); + + snprintf(name, len, "tsn%d", rdev->port); + + return 0; +} + static const struct net_device_ops rswitch_netdev_ops = { .ndo_open = rswitch_open, .ndo_stop = rswitch_stop, .ndo_start_xmit = rswitch_start_xmit, .ndo_get_stats = rswitch_get_stats, .ndo_eth_ioctl = rswitch_eth_ioctl, + .ndo_get_port_parent_id = rswitch_get_port_parent_id, + .ndo_get_phys_port_name = rswitch_get_phys_port_name, .ndo_validate_addr = eth_validate_addr, .ndo_set_mac_address = eth_mac_addr, }; +bool is_rdev(const struct net_device *ndev) +{ + return (ndev->netdev_ops == &rswitch_netdev_ops); +} + static int rswitch_get_ts_info(struct net_device *ndev, struct kernel_ethtool_ts_info *info) { struct rswitch_device *rdev = netdev_priv(ndev); @@ -1959,6 +2016,8 @@ static int rswitch_device_alloc(struct rswitch_private *priv, unsigned int index if (err < 0) goto out_txdmac; + list_add_tail(&rdev->list, &priv->port_list); + return 0; out_txdmac: @@ -1978,6 +2037,7 @@ static void rswitch_device_free(struct rswitch_private *priv, unsigned int index struct rswitch_device *rdev = priv->rdev[index]; struct net_device *ndev = rdev->ndev; + list_del(&rdev->list); rswitch_txdmac_free(ndev); rswitch_rxdmac_free(ndev); of_node_put(rdev->np_port); @@ -2024,10 +2084,11 @@ static int rswitch_init(struct rswitch_private *priv) } } - rswitch_fwd_init(priv); + err = rswitch_fwd_init(priv); + if (err < 0) + goto err_fwd_init; - err = rcar_gen4_ptp_register(priv->ptp_priv, RCAR_GEN4_PTP_REG_LAYOUT, - clk_get_rate(priv->clk)); + err = rcar_gen4_ptp_register(priv->ptp_priv, clk_get_rate(priv->clk)); if (err < 0) goto err_ptp_register; @@ -2073,6 +2134,7 @@ err_gwca_ts_request_irq: err_gwca_request_irq: rcar_gen4_ptp_unregister(priv->ptp_priv); +err_fwd_init: err_ptp_register: for (i = 0; i < RSWITCH_NUM_PORTS; i++) rswitch_device_free(priv, i); @@ -2107,6 +2169,7 @@ static int renesas_eth_sw_probe(struct platform_device *pdev) priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; + spin_lock_init(&priv->lock); priv->clk = devm_clk_get(&pdev->dev, NULL); @@ -2144,6 +2207,8 @@ static int renesas_eth_sw_probe(struct platform_device *pdev) if (!priv->gwca.queues) return -ENOMEM; + INIT_LIST_HEAD(&priv->port_list); + pm_runtime_enable(&pdev->dev); pm_runtime_get_sync(&pdev->dev); @@ -2154,6 +2219,15 @@ static int renesas_eth_sw_probe(struct platform_device *pdev) return ret; } + if (list_empty(&priv->port_list)) + dev_warn(&pdev->dev, "could not initialize any ports\n"); + + ret = rswitch_register_notifiers(); + if (ret) { + dev_err(&pdev->dev, "could not register notifiers\n"); + return ret; + } + device_set_wakeup_capable(&pdev->dev, 1); return ret; @@ -2187,6 +2261,7 @@ static void renesas_eth_sw_remove(struct platform_device *pdev) { struct rswitch_private *priv = platform_get_drvdata(pdev); + rswitch_unregister_notifiers(); rswitch_deinit(priv); pm_runtime_put(&pdev->dev); diff --git a/drivers/net/ethernet/renesas/rtsn.c b/drivers/net/ethernet/renesas/rtsn.c index 05c4b6c8c9c3..15a043e85431 100644 --- a/drivers/net/ethernet/renesas/rtsn.c +++ b/drivers/net/ethernet/renesas/rtsn.c @@ -1330,8 +1330,7 @@ static int rtsn_probe(struct platform_device *pdev) device_set_wakeup_capable(&pdev->dev, 1); - ret = rcar_gen4_ptp_register(priv->ptp_priv, RCAR_GEN4_PTP_REG_LAYOUT, - clk_get_rate(priv->clk)); + ret = rcar_gen4_ptp_register(priv->ptp_priv, clk_get_rate(priv->clk)); if (ret) goto error_pm; diff --git a/drivers/net/ethernet/renesas/sh_eth.c b/drivers/net/ethernet/renesas/sh_eth.c index 5fc8027c92c7..6fb0ffc1c844 100644 --- a/drivers/net/ethernet/renesas/sh_eth.c +++ b/drivers/net/ethernet/renesas/sh_eth.c @@ -2233,7 +2233,7 @@ static void sh_eth_get_regs(struct net_device *ndev, struct ethtool_regs *regs, pm_runtime_get_sync(&mdp->pdev->dev); __sh_eth_get_regs(ndev, buf); - pm_runtime_put_sync(&mdp->pdev->dev); + pm_runtime_put(&mdp->pdev->dev); } static u32 sh_eth_get_msglevel(struct net_device *ndev) @@ -2360,6 +2360,7 @@ static int sh_eth_set_ringparam(struct net_device *ndev, return 0; } +#ifdef CONFIG_PM_SLEEP static void sh_eth_get_wol(struct net_device *ndev, struct ethtool_wolinfo *wol) { struct sh_eth_private *mdp = netdev_priv(ndev); @@ -2386,6 +2387,7 @@ static int sh_eth_set_wol(struct net_device *ndev, struct ethtool_wolinfo *wol) return 0; } +#endif static const struct ethtool_ops sh_eth_ethtool_ops = { .get_regs_len = sh_eth_get_regs_len, @@ -2401,8 +2403,10 @@ static const struct ethtool_ops sh_eth_ethtool_ops = { .set_ringparam = sh_eth_set_ringparam, .get_link_ksettings = phy_ethtool_get_link_ksettings, .set_link_ksettings = phy_ethtool_set_link_ksettings, +#ifdef CONFIG_PM_SLEEP .get_wol = sh_eth_get_wol, .set_wol = sh_eth_set_wol, +#endif }; /* network device open function */ @@ -2447,7 +2451,7 @@ out_free_irq: free_irq(ndev->irq, ndev); out_napi_off: napi_disable(&mdp->napi); - pm_runtime_put_sync(&mdp->pdev->dev); + pm_runtime_put(&mdp->pdev->dev); return ret; } @@ -3443,8 +3447,6 @@ static void sh_eth_drv_remove(struct platform_device *pdev) free_netdev(ndev); } -#ifdef CONFIG_PM -#ifdef CONFIG_PM_SLEEP static int sh_eth_wol_setup(struct net_device *ndev) { struct sh_eth_private *mdp = netdev_priv(ndev); @@ -3527,28 +3529,8 @@ static int sh_eth_resume(struct device *dev) return ret; } -#endif - -static int sh_eth_runtime_nop(struct device *dev) -{ - /* Runtime PM callback shared between ->runtime_suspend() - * and ->runtime_resume(). Simply returns success. - * - * This driver re-initializes all registers after - * pm_runtime_get_sync() anyway so there is no need - * to save and restore registers here. - */ - return 0; -} -static const struct dev_pm_ops sh_eth_dev_pm_ops = { - SET_SYSTEM_SLEEP_PM_OPS(sh_eth_suspend, sh_eth_resume) - SET_RUNTIME_PM_OPS(sh_eth_runtime_nop, sh_eth_runtime_nop, NULL) -}; -#define SH_ETH_PM_OPS (&sh_eth_dev_pm_ops) -#else -#define SH_ETH_PM_OPS NULL -#endif +static DEFINE_SIMPLE_DEV_PM_OPS(sh_eth_dev_pm_ops, sh_eth_suspend, sh_eth_resume); static const struct platform_device_id sh_eth_id_table[] = { { "sh7619-ether", (kernel_ulong_t)&sh7619_data }, @@ -3568,7 +3550,7 @@ static struct platform_driver sh_eth_driver = { .id_table = sh_eth_id_table, .driver = { .name = CARDNAME, - .pm = SH_ETH_PM_OPS, + .pm = pm_sleep_ptr(&sh_eth_dev_pm_ops), .of_match_table = of_match_ptr(sh_eth_match_table), }, }; |