From: Luiz Angelo Daros de Luca Date: Sat, 16 May 2026 16:32:04 +0000 (+0200) Subject: kernel: backport rtl8365mb DSA driver improvements X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=refs%2Fpull%2F23738%2Fhead;p=thirdparty%2Fopenwrt.git kernel: backport rtl8365mb DSA driver improvements Backport several fixes and enhancements for the rtl8365mb DSA driver from upstream. The backport includes the following changes: - Fix for rtl8_4 tag (from v7.1) - VLAN and Forwarding offload (from v7.2) Before this backport, all bridge forwarding was handled by the CPU, causing a significant performance drop between LAN ports, similar to standard WAN/LAN routing. Enabling hardware forwarding offload alleviates CPU overhead and restores line-rate switching performance. Link: https://patch.msgid.link/20260408-realtek_fixes-v1-0-915ff1404d56@gmail.com Link: https://patch.msgid.link/20260606-realtek_forward-v13-0-b9e409687cbe@gmail.com Signed-off-by: Luiz Angelo Daros de Luca Link: https://github.com/openwrt/openwrt/pull/23738 Signed-off-by: Robert Marko --- diff --git a/target/linux/generic/backport-6.12/940-01-v7.1-net-dsa-tag_rtl8_4-update-format-description.patch b/target/linux/generic/backport-6.12/940-01-v7.1-net-dsa-tag_rtl8_4-update-format-description.patch new file mode 100644 index 00000000000..d4c129a3653 --- /dev/null +++ b/target/linux/generic/backport-6.12/940-01-v7.1-net-dsa-tag_rtl8_4-update-format-description.patch @@ -0,0 +1,70 @@ +From 297e1f411ed4927a912c7e207ba6f978cb1f9f0e Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Alvin=20=C5=A0ipraga?= +Date: Wed, 8 Apr 2026 17:31:01 -0300 +Subject: [PATCH 1/2] net: dsa: tag_rtl8_4: update format description +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Document the updated tag layout fields (EFID, VSEL/VIDX) and clarify +which bits are set/cleared when emitting tags. + +Co-developed-by: Alvin Å ipraga +Signed-off-by: Alvin Å ipraga +Signed-off-by: Luiz Angelo Daros de Luca +Reviewed-by: Linus Walleij +Link: https://patch.msgid.link/20260408-realtek_fixes-v1-1-915ff1404d56@gmail.com +Signed-off-by: Jakub Kicinski +--- + net/dsa/tag_rtl8_4.c | 17 ++++++++++------- + 1 file changed, 10 insertions(+), 7 deletions(-) + +--- a/net/dsa/tag_rtl8_4.c ++++ b/net/dsa/tag_rtl8_4.c +@@ -17,8 +17,8 @@ + * | (8-bit) | (8-bit) | + * | Protocol [0x04] | REASON | b + * |-----------------------------------+-----------------------------------| y +- * | (1) | (1) | (2) | (1) | (3) | (1) | (1) | (1) | (5) | t +- * | FID_EN | X | FID | PRI_EN | PRI | KEEP | X | LEARN_DIS | X | e ++ * | (1) | (3) | (1) | (3) | (1) | (1) | (1) | (5) | t ++ * | EFID_EN | EFID | PRI_EN | PRI | KEEP | VSEL | LEARN_DIS | VIDX | e + * |-----------------------------------+-----------------------------------| s + * | (1) | (15-bit) | | + * | ALLOW | TX/RX | v +@@ -32,19 +32,22 @@ + * EtherType | note that Realtek uses the same EtherType for + * | other incompatible tag formats (e.g. tag_rtl4_a.c) + * Protocol | 0x04: indicates that this tag conforms to this format +- * X | reserved + * ------------+------------- + * REASON | reason for forwarding packet to CPU + * | 0: packet was forwarded or flooded to CPU + * | 80: packet was trapped to CPU +- * FID_EN | 1: packet has an FID +- * | 0: no FID +- * FID | FID of packet (if FID_EN=1) ++ * EFID_EN | 1: packet has an EFID ++ * | 0: no EFID ++ * EFID | Extended filter ID (EFID) of packet (if EFID_EN=1) + * PRI_EN | 1: force priority of packet + * | 0: don't force priority + * PRI | priority of packet (if PRI_EN=1) + * KEEP | preserve packet VLAN tag format ++ * VSEL | 0: switch should classify packet according to VLAN tag ++ * | 1: switch should classify packet according to VLAN membership ++ * | configuration with index VIDX + * LEARN_DIS | don't learn the source MAC address of the packet ++ * VIDX | index of a VLAN membership configuration to use with VSEL + * ALLOW | 1: treat TX/RX field as an allowance port mask, meaning the + * | packet may only be forwarded to ports specified in the + * | mask +@@ -112,7 +115,7 @@ static void rtl8_4_write_tag(struct sk_b + /* Set Protocol; zero REASON */ + tag16[1] = htons(FIELD_PREP(RTL8_4_PROTOCOL, RTL8_4_PROTOCOL_RTL8365MB)); + +- /* Zero FID_EN, FID, PRI_EN, PRI, KEEP; set LEARN_DIS */ ++ /* Zero EFID_EN, EFID, PRI_EN, PRI, VSEL, VIDX, KEEP; set LEARN_DIS */ + tag16[2] = htons(FIELD_PREP(RTL8_4_LEARN_DIS, 1)); + + /* Zero ALLOW; set RX (CPU->switch) forwarding port mask */ diff --git a/target/linux/generic/backport-6.12/940-02-v7.1-net-dsa-tag_rtl8_4-set-KEEP-flag.patch b/target/linux/generic/backport-6.12/940-02-v7.1-net-dsa-tag_rtl8_4-set-KEEP-flag.patch new file mode 100644 index 00000000000..7a9ea343165 --- /dev/null +++ b/target/linux/generic/backport-6.12/940-02-v7.1-net-dsa-tag_rtl8_4-set-KEEP-flag.patch @@ -0,0 +1,48 @@ +From 82f37bd9a4d779495479c0c13152208d5400c8a4 Mon Sep 17 00:00:00 2001 +From: Luiz Angelo Daros de Luca +Date: Wed, 8 Apr 2026 17:31:02 -0300 +Subject: [PATCH 2/2] net: dsa: tag_rtl8_4: set KEEP flag +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +KEEP=1 is needed because we should respect the format of the packet as +the kernel sends it to us. Unless tx forward offloading is used, the +kernel is giving us the packet exactly as it should leave the specified +port on the wire. Until now this was not needed because the ports were +always functioning in a standalone mode in a VLAN-unaware way, so the +switch would not tag or untag frames anyway. But arguably it should have +been KEEP=1 all along. + +Co-developed-by: Alvin Å ipraga +Signed-off-by: Alvin Å ipraga +Signed-off-by: Luiz Angelo Daros de Luca +Reviewed-by: Linus Walleij +Link: https://patch.msgid.link/20260408-realtek_fixes-v1-2-915ff1404d56@gmail.com +Signed-off-by: Jakub Kicinski +--- + net/dsa/tag_rtl8_4.c | 6 ++++-- + 1 file changed, 4 insertions(+), 2 deletions(-) + +--- a/net/dsa/tag_rtl8_4.c ++++ b/net/dsa/tag_rtl8_4.c +@@ -99,6 +99,7 @@ + #define RTL8_4_REASON_TRAP 80 + + #define RTL8_4_LEARN_DIS BIT(5) ++#define RTL8_4_KEEP BIT(7) + + #define RTL8_4_TX GENMASK(3, 0) + #define RTL8_4_RX GENMASK(10, 0) +@@ -115,8 +116,9 @@ static void rtl8_4_write_tag(struct sk_b + /* Set Protocol; zero REASON */ + tag16[1] = htons(FIELD_PREP(RTL8_4_PROTOCOL, RTL8_4_PROTOCOL_RTL8365MB)); + +- /* Zero EFID_EN, EFID, PRI_EN, PRI, VSEL, VIDX, KEEP; set LEARN_DIS */ +- tag16[2] = htons(FIELD_PREP(RTL8_4_LEARN_DIS, 1)); ++ /* Zero EFID_EN, EFID, PRI_EN, PRI, VSEL, VIDX; set KEEP, LEARN_DIS */ ++ tag16[2] = htons(FIELD_PREP(RTL8_4_LEARN_DIS, 1) | ++ FIELD_PREP(RTL8_4_KEEP, 1)); + + /* Zero ALLOW; set RX (CPU->switch) forwarding port mask */ + tag16[3] = htons(FIELD_PREP(RTL8_4_RX, BIT(dp->index))); diff --git a/target/linux/generic/backport-6.12/942-01-v7.2-net-dsa-realtek-rtl8365mb-use-ERR_PTR.patch b/target/linux/generic/backport-6.12/942-01-v7.2-net-dsa-realtek-rtl8365mb-use-ERR_PTR.patch new file mode 100644 index 00000000000..8a790f59bdc --- /dev/null +++ b/target/linux/generic/backport-6.12/942-01-v7.2-net-dsa-realtek-rtl8365mb-use-ERR_PTR.patch @@ -0,0 +1,154 @@ +From a543687227d86b1f4401f395abaf7f3f710fe964 Mon Sep 17 00:00:00 2001 +From: Luiz Angelo Daros de Luca +Date: Sat, 6 Jun 2026 05:29:25 -0300 +Subject: net: dsa: realtek: rtl8365mb: use ERR_PTR + +Convert numeric error codes into human-readable strings by using %pe +together with ERR_PTR() in dev_err() messages. Also use dev_err_probe() +instead of checking for -EPROBE_DEFER. + +Reviewed-by: Linus Walleij +Reviewed-by: Mieczyslaw Nalewaj +Signed-off-by: Luiz Angelo Daros de Luca +Link: https://patch.msgid.link/20260606-realtek_forward-v13-1-b9e409687cbe@gmail.com +Signed-off-by: Jakub Kicinski +--- + drivers/net/dsa/realtek/rtl8365mb.c | 51 +++++++++++++++++++++---------------- + 1 file changed, 29 insertions(+), 22 deletions(-) + +--- a/drivers/net/dsa/realtek/rtl8365mb.c ++++ b/drivers/net/dsa/realtek/rtl8365mb.c +@@ -803,8 +803,8 @@ static int rtl8365mb_phy_read(struct rea + ret = rtl8365mb_phy_ocp_read(priv, phy, ocp_addr, &val); + if (ret) { + dev_err(priv->dev, +- "failed to read PHY%d reg %02x @ %04x, ret %d\n", phy, +- regnum, ocp_addr, ret); ++ "failed to read PHY%d reg %02x @ %04x, ret %pe\n", phy, ++ regnum, ocp_addr, ERR_PTR(ret)); + return ret; + } + +@@ -831,8 +831,8 @@ static int rtl8365mb_phy_write(struct re + ret = rtl8365mb_phy_ocp_write(priv, phy, ocp_addr, val); + if (ret) { + dev_err(priv->dev, +- "failed to write PHY%d reg %02x @ %04x, ret %d\n", phy, +- regnum, ocp_addr, ret); ++ "failed to write PHY%d reg %02x @ %04x, ret %pe\n", phy, ++ regnum, ocp_addr, ERR_PTR(ret)); + return ret; + } + +@@ -1082,8 +1082,8 @@ static void rtl8365mb_phylink_mac_config + ret = rtl8365mb_ext_config_rgmii(priv, port, state->interface); + if (ret) + dev_err(priv->dev, +- "failed to configure RGMII mode on port %d: %d\n", +- port, ret); ++ "failed to configure RGMII mode on port %d: %pe\n", ++ port, ERR_PTR(ret)); + return; + } + +@@ -1112,8 +1112,8 @@ static void rtl8365mb_phylink_mac_link_d + false, false); + if (ret) + dev_err(priv->dev, +- "failed to reset forced mode on port %d: %d\n", +- port, ret); ++ "failed to reset forced mode on port %d: %pe\n", ++ port, ERR_PTR(ret)); + + return; + } +@@ -1143,8 +1143,8 @@ static void rtl8365mb_phylink_mac_link_u + rx_pause); + if (ret) + dev_err(priv->dev, +- "failed to force mode on port %d: %d\n", port, +- ret); ++ "failed to force mode on port %d: %pe\n", port, ++ ERR_PTR(ret)); + + return; + } +@@ -1299,8 +1299,8 @@ static void rtl8365mb_get_ethtool_stats( + mib->length, &data[i]); + if (ret) { + dev_err(priv->dev, +- "failed to read port %d counters: %d\n", port, +- ret); ++ "failed to read port %d counters: %pe\n", port, ++ ERR_PTR(ret)); + break; + } + } +@@ -1652,7 +1652,8 @@ static irqreturn_t rtl8365mb_irq(int irq + return IRQ_HANDLED; + + out_error: +- dev_err(priv->dev, "failed to read interrupt status: %d\n", ret); ++ dev_err(priv->dev, "failed to read interrupt status: %pe\n", ++ ERR_PTR(ret)); + + out_none: + return IRQ_NONE; +@@ -1725,10 +1726,13 @@ static int rtl8365mb_irq_setup(struct re + /* rtl8365mb IRQs cascade off this one */ + irq = of_irq_get(intc, 0); + if (irq <= 0) { +- if (irq != -EPROBE_DEFER) +- dev_err(priv->dev, "failed to get parent irq: %d\n", +- irq); +- ret = irq ? irq : -EINVAL; ++ if (!irq) { ++ dev_err(priv->dev, "failed to map IRQ\n"); ++ ret = -EINVAL; ++ } else { ++ ret = dev_err_probe(priv->dev, irq, ++ "failed to get parent irq\n"); ++ } + goto out_put_node; + } + +@@ -1790,7 +1794,8 @@ static int rtl8365mb_irq_setup(struct re + ret = request_threaded_irq(irq, NULL, rtl8365mb_irq, IRQF_ONESHOT, + "rtl8365mb", priv); + if (ret) { +- dev_err(priv->dev, "failed to request irq: %d\n", ret); ++ dev_err(priv->dev, "failed to request irq: %pe\n", ++ ERR_PTR(ret)); + goto out_remove_irqdomain; + } + +@@ -1966,14 +1971,16 @@ static int rtl8365mb_setup(struct dsa_sw + + ret = rtl8365mb_reset_chip(priv); + if (ret) { +- dev_err(priv->dev, "failed to reset chip: %d\n", ret); ++ dev_err(priv->dev, "failed to reset chip: %pe\n", ++ ERR_PTR(ret)); + goto out_error; + } + + /* Configure switch to vendor-defined initial state */ + ret = rtl8365mb_switch_init(priv); + if (ret) { +- dev_err(priv->dev, "failed to initialize switch: %d\n", ret); ++ dev_err(priv->dev, "failed to initialize switch: %pe\n", ++ ERR_PTR(ret)); + goto out_error; + } + +@@ -2091,8 +2098,8 @@ static int rtl8365mb_detect(struct realt + + ret = rtl8365mb_get_chip_id_and_ver(priv->map, &chip_id, &chip_ver); + if (ret) { +- dev_err(priv->dev, "failed to read chip id and version: %d\n", +- ret); ++ dev_err(priv->dev, "failed to read chip id and version: %pe\n", ++ ERR_PTR(ret)); + return ret; + } + diff --git a/target/linux/generic/backport-6.12/942-02-v7.2-net-dsa-realtek-rtl8365mb-reject-unsupported-topolog.patch b/target/linux/generic/backport-6.12/942-02-v7.2-net-dsa-realtek-rtl8365mb-reject-unsupported-topolog.patch new file mode 100644 index 00000000000..8a907f80a56 --- /dev/null +++ b/target/linux/generic/backport-6.12/942-02-v7.2-net-dsa-realtek-rtl8365mb-reject-unsupported-topolog.patch @@ -0,0 +1,59 @@ +From 36c572fd60d6bf2f9631088833c162203907c9ca Mon Sep 17 00:00:00 2001 +From: Luiz Angelo Daros de Luca +Date: Sat, 6 Jun 2026 05:29:26 -0300 +Subject: net: dsa: realtek: rtl8365mb: reject unsupported topologies + +Explicitly enforce the presence of a CPU port (-EINVAL) and reject DSA +cascade links (-EOPNOTSUPP) during setup to prevent silent failures. + +These topologies were already non-functional. Without a CPU port, the +driver does not activate CPU tagging. Additionally, the switch hardware +was not designed to be cascaded, and DSA links never worked because +CPU tagging is not enabled for them. + +Reviewed-by: Mieczyslaw Nalewaj +Reviewed-by: Linus Walleij +Signed-off-by: Luiz Angelo Daros de Luca +Link: https://patch.msgid.link/20260606-realtek_forward-v13-2-b9e409687cbe@gmail.com +Signed-off-by: Jakub Kicinski +--- + drivers/net/dsa/realtek/rtl8365mb.c | 21 +++++++++++++++++++++ + 1 file changed, 21 insertions(+) + +--- a/drivers/net/dsa/realtek/rtl8365mb.c ++++ b/drivers/net/dsa/realtek/rtl8365mb.c +@@ -1991,6 +1991,20 @@ static int rtl8365mb_setup(struct dsa_sw + else if (ret) + dev_info(priv->dev, "no interrupt support\n"); + ++ for (i = 0; i < priv->num_ports; i++) { ++ /* Cascading (DSA links) is not supported yet. ++ * Historically, the driver has always been broken ++ * without a dedicated CPU port because CPU tagging ++ * would be disabled, rendering the switch entirely ++ * non-functional for DSA operations. ++ */ ++ if (dsa_is_dsa_port(ds, i)) { ++ dev_err(priv->dev, "Cascading (DSA link) not supported\n"); ++ ret = -EOPNOTSUPP; ++ goto out_teardown_irq; ++ } ++ } ++ + /* Configure CPU tagging */ + dsa_switch_for_each_cpu_port(cpu_dp, ds) { + cpu->mask |= BIT(cpu_dp->index); +@@ -1999,6 +2013,13 @@ static int rtl8365mb_setup(struct dsa_sw + cpu->trap_port = cpu_dp->index; + } + cpu->enable = cpu->mask > 0; ++ ++ if (!cpu->enable) { ++ dev_err(priv->dev, "no CPU port defined\n"); ++ ret = -EINVAL; ++ goto out_teardown_irq; ++ } ++ + ret = rtl8365mb_cpu_config(priv); + if (ret) + goto out_teardown_irq; diff --git a/target/linux/generic/backport-6.12/942-03-v7.2-net-dsa-realtek-rtl8365mb-use-dsa-helpers-for-port-i.patch b/target/linux/generic/backport-6.12/942-03-v7.2-net-dsa-realtek-rtl8365mb-use-dsa-helpers-for-port-i.patch new file mode 100644 index 00000000000..7a625decd9f --- /dev/null +++ b/target/linux/generic/backport-6.12/942-03-v7.2-net-dsa-realtek-rtl8365mb-use-dsa-helpers-for-port-i.patch @@ -0,0 +1,347 @@ +From 51a4a22301b023610a1321614e8dc61251ef9064 Mon Sep 17 00:00:00 2001 +From: Luiz Angelo Daros de Luca +Date: Sat, 6 Jun 2026 05:29:27 -0300 +Subject: net: dsa: realtek: rtl8365mb: use dsa helpers for port iteration + +Convert open-coded port iteration loops to use the DSA helpers and +restructure rtl8365mb_setup() into clear blocking, user, and +CPU port phases. + +As part of this refactoring, unused ports are explicitly placed into a +blocked, isolated state with learning disabled, ensuring safe default +hardware behavior. The driver also does not allocate a virtual IRQ +mapping for unused ports. To accommodate this, a guard check is added to +the interrupt handler (rtl8365mb_irq) to safely skip ports without a +valid IRQ mapping. The irq domain teardown, however, does clean all +ports as external PHYs may still map the IRQ. + +Furthermore, since the new initialization loop starts with all ports +administratively isolated by default, CPU port forwarding and isolation +masks are explicitly configured at the end of the setup phase to prevent +egress traffic from being blocked. + +Suggested-by: Abdulkader Alrezej +Reviewed-by: Linus Walleij +Reviewed-by: Mieczyslaw Nalewaj +Signed-off-by: Luiz Angelo Daros de Luca +Link: https://patch.msgid.link/20260606-realtek_forward-v13-3-b9e409687cbe@gmail.com +Signed-off-by: Jakub Kicinski +--- + drivers/net/dsa/realtek/rtl8365mb.c | 166 +++++++++++++++++++++++------------- + 1 file changed, 105 insertions(+), 61 deletions(-) + +--- a/drivers/net/dsa/realtek/rtl8365mb.c ++++ b/drivers/net/dsa/realtek/rtl8365mb.c +@@ -1554,18 +1554,15 @@ static void rtl8365mb_stats_setup(struct + { + struct rtl8365mb *mb = priv->chip_data; + struct dsa_switch *ds = &priv->ds; +- int i; ++ struct dsa_port *dp; + + /* Per-chip global mutex to protect MIB counter access, since doing + * so requires accessing a series of registers in a particular order. + */ + mutex_init(&mb->mib_lock); + +- for (i = 0; i < priv->num_ports; i++) { +- struct rtl8365mb_port *p = &mb->ports[i]; +- +- if (dsa_is_unused_port(ds, i)) +- continue; ++ dsa_switch_for_each_available_port(dp, ds) { ++ struct rtl8365mb_port *p = &mb->ports[dp->index]; + + /* Per-port spinlock to protect the stats64 data */ + spin_lock_init(&p->stats_lock); +@@ -1581,13 +1578,10 @@ static void rtl8365mb_stats_teardown(str + { + struct rtl8365mb *mb = priv->chip_data; + struct dsa_switch *ds = &priv->ds; +- int i; +- +- for (i = 0; i < priv->num_ports; i++) { +- struct rtl8365mb_port *p = &mb->ports[i]; ++ struct dsa_port *dp; + +- if (dsa_is_unused_port(ds, i)) +- continue; ++ dsa_switch_for_each_available_port(dp, ds) { ++ struct rtl8365mb_port *p = &mb->ports[dp->index]; + + cancel_delayed_work_sync(&p->mib_work); + } +@@ -1646,6 +1640,9 @@ static irqreturn_t rtl8365mb_irq(int irq + for_each_set_bit(line, &line_changes, priv->num_ports) { + int child_irq = irq_find_mapping(priv->irqdomain, line); + ++ if (!child_irq) ++ continue; ++ + handle_nested_irq(child_irq); + } + +@@ -1667,10 +1664,14 @@ static struct irq_chip rtl8365mb_irq_chi + static int rtl8365mb_irq_map(struct irq_domain *domain, unsigned int irq, + irq_hw_number_t hwirq) + { +- irq_set_chip_data(irq, domain->host_data); ++ struct realtek_priv *priv = domain->host_data; ++ struct rtl8365mb *mb = priv->chip_data; ++ ++ irq_set_chip_data(irq, priv); + irq_set_chip_and_handler(irq, &rtl8365mb_irq_chip, handle_simple_irq); + irq_set_nested_thread(irq, 1); + irq_set_noprobe(irq); ++ irq_set_parent(irq, mb->irq); + + return 0; + } +@@ -1709,13 +1710,14 @@ static int rtl8365mb_irq_disable(struct + static int rtl8365mb_irq_setup(struct realtek_priv *priv) + { + struct rtl8365mb *mb = priv->chip_data; ++ struct dsa_switch *ds = &priv->ds; + struct device_node *intc; ++ struct dsa_port *dp; + u32 irq_trig; + int virq; + int irq; + u32 val; + int ret; +- int i; + + intc = of_get_child_by_name(priv->dev->of_node, "interrupt-controller"); + if (!intc) { +@@ -1736,6 +1738,9 @@ static int rtl8365mb_irq_setup(struct re + goto out_put_node; + } + ++ /* Store the irq so that we know to map and free it during teardown */ ++ mb->irq = irq; ++ + priv->irqdomain = irq_domain_add_linear(intc, priv->num_ports, + &rtl8365mb_irqdomain_ops, priv); + if (!priv->irqdomain) { +@@ -1744,8 +1749,8 @@ static int rtl8365mb_irq_setup(struct re + goto out_put_node; + } + +- for (i = 0; i < priv->num_ports; i++) { +- virq = irq_create_mapping(priv->irqdomain, i); ++ dsa_switch_for_each_available_port(dp, ds) { ++ virq = irq_create_mapping(priv->irqdomain, dp->index); + if (!virq) { + dev_err(priv->dev, + "failed to create irq domain mapping\n"); +@@ -1799,9 +1804,6 @@ static int rtl8365mb_irq_setup(struct re + goto out_remove_irqdomain; + } + +- /* Store the irq so that we know to free it during teardown */ +- mb->irq = irq; +- + ret = rtl8365mb_irq_enable(priv); + if (ret) + goto out_free_irq; +@@ -1812,18 +1814,20 @@ static int rtl8365mb_irq_setup(struct re + + out_free_irq: + free_irq(mb->irq, priv); +- mb->irq = 0; + + out_remove_irqdomain: +- for (i = 0; i < priv->num_ports; i++) { +- virq = irq_find_mapping(priv->irqdomain, i); +- irq_dispose_mapping(virq); ++ dsa_switch_for_each_port(dp, ds) { ++ virq = irq_find_mapping(priv->irqdomain, dp->index); ++ ++ if (virq) ++ irq_dispose_mapping(virq); + } + + irq_domain_remove(priv->irqdomain); + priv->irqdomain = NULL; + + out_put_node: ++ mb->irq = 0; + of_node_put(intc); + + return ret; +@@ -1832,8 +1836,9 @@ out_put_node: + static void rtl8365mb_irq_teardown(struct realtek_priv *priv) + { + struct rtl8365mb *mb = priv->chip_data; ++ struct dsa_switch *ds = &priv->ds; ++ struct dsa_port *dp; + int virq; +- int i; + + if (mb->irq) { + free_irq(mb->irq, priv); +@@ -1841,9 +1846,15 @@ static void rtl8365mb_irq_teardown(struc + } + + if (priv->irqdomain) { +- for (i = 0; i < priv->num_ports; i++) { +- virq = irq_find_mapping(priv->irqdomain, i); +- irq_dispose_mapping(virq); ++ /* Unused ports with a linked PHY still have an active IRQ ++ * mapping that must be disposed of during teardown. Loop ++ * through all ports. ++ */ ++ dsa_switch_for_each_port(dp, ds) { ++ virq = irq_find_mapping(priv->irqdomain, dp->index); ++ ++ if (virq) ++ irq_dispose_mapping(virq); + } + + irq_domain_remove(priv->irqdomain); +@@ -1961,10 +1972,11 @@ static int rtl8365mb_setup(struct dsa_sw + { + struct realtek_priv *priv = ds->priv; + struct rtl8365mb_cpu *cpu; +- struct dsa_port *cpu_dp; ++ u32 downports_mask = 0; ++ u32 upports_mask = 0; + struct rtl8365mb *mb; ++ struct dsa_port *dp; + int ret; +- int i; + + mb = priv->chip_data; + cpu = &mb->cpu; +@@ -1991,67 +2003,99 @@ static int rtl8365mb_setup(struct dsa_sw + else if (ret) + dev_info(priv->dev, "no interrupt support\n"); + +- for (i = 0; i < priv->num_ports; i++) { ++ dsa_switch_for_each_port(dp, ds) { + /* Cascading (DSA links) is not supported yet. + * Historically, the driver has always been broken + * without a dedicated CPU port because CPU tagging + * would be disabled, rendering the switch entirely + * non-functional for DSA operations. + */ +- if (dsa_is_dsa_port(ds, i)) { ++ if (dsa_port_is_dsa(dp)) { + dev_err(priv->dev, "Cascading (DSA link) not supported\n"); + ret = -EOPNOTSUPP; + goto out_teardown_irq; + } + } + +- /* Configure CPU tagging */ +- dsa_switch_for_each_cpu_port(cpu_dp, ds) { +- cpu->mask |= BIT(cpu_dp->index); ++ /* Start with all ports blocked, including unused ports */ ++ dsa_switch_for_each_port(dp, ds) { ++ struct rtl8365mb_port *p = &mb->ports[dp->index]; + +- if (cpu->trap_port == RTL8365MB_MAX_NUM_PORTS) +- cpu->trap_port = cpu_dp->index; +- } +- cpu->enable = cpu->mask > 0; ++ /* Set the initial STP state of all ports to DISABLED, otherwise ++ * ports will still forward frames to the CPU despite being ++ * administratively down by default. ++ */ ++ rtl8365mb_port_stp_state_set(ds, dp->index, BR_STATE_DISABLED); + +- if (!cpu->enable) { +- dev_err(priv->dev, "no CPU port defined\n"); +- ret = -EINVAL; +- goto out_teardown_irq; +- } ++ /* Start with all port completely isolated */ ++ ret = rtl8365mb_port_set_isolation(priv, dp->index, 0); ++ if (ret) ++ goto out_teardown_irq; + +- ret = rtl8365mb_cpu_config(priv); +- if (ret) +- goto out_teardown_irq; ++ /* Disable learning */ ++ ret = rtl8365mb_port_set_learning(priv, dp->index, false); ++ if (ret) ++ goto out_teardown_irq; ++ ++ /* Set up per-port private data */ ++ p->priv = priv; ++ p->index = dp->index; + +- /* Configure ports */ +- for (i = 0; i < priv->num_ports; i++) { +- struct rtl8365mb_port *p = &mb->ports[i]; ++ /* Collect CPU ports. If we support cascade switches, it should ++ * also include the upstream DSA ports. ++ */ ++ if (!dsa_port_is_cpu(dp)) ++ continue; ++ ++ upports_mask |= BIT(dp->index); ++ } + +- if (dsa_is_unused_port(ds, i)) ++ /* Configure user ports */ ++ dsa_switch_for_each_port(dp, ds) { ++ if (!dsa_port_is_user(dp)) + continue; + + /* Forward only to the CPU */ +- ret = rtl8365mb_port_set_isolation(priv, i, cpu->mask); ++ ret = rtl8365mb_port_set_isolation(priv, dp->index, ++ upports_mask); + if (ret) + goto out_teardown_irq; + +- /* Disable learning */ +- ret = rtl8365mb_port_set_learning(priv, i, false); ++ /* If we support cascade switches, it should also include the ++ * downstream DSA ports. ++ */ ++ downports_mask |= BIT(dp->index); ++ } ++ ++ /* Configure CPU tagging */ ++ /* If we support cascade switches, it should also include the upstream ++ * DSA ports. ++ */ ++ dsa_switch_for_each_cpu_port(dp, ds) { ++ /* Use the first CPU port as trap_port */ ++ if (cpu->trap_port == RTL8365MB_MAX_NUM_PORTS) ++ cpu->trap_port = dp->index; ++ ++ /* Forward to all user ports */ ++ ret = rtl8365mb_port_set_isolation(priv, dp->index, ++ downports_mask); + if (ret) + goto out_teardown_irq; ++ } + +- /* Set the initial STP state of all ports to DISABLED, otherwise +- * ports will still forward frames to the CPU despite being +- * administratively down by default. +- */ +- rtl8365mb_port_stp_state_set(ds, i, BR_STATE_DISABLED); ++ cpu->mask = upports_mask; ++ cpu->enable = cpu->mask > 0; + +- /* Set up per-port private data */ +- p->priv = priv; +- p->index = i; ++ if (!cpu->enable) { ++ dev_err(priv->dev, "no CPU port defined\n"); ++ ret = -EINVAL; ++ goto out_teardown_irq; + } + ++ ret = rtl8365mb_cpu_config(priv); ++ if (ret) ++ goto out_teardown_irq; ++ + ret = rtl8365mb_port_change_mtu(ds, cpu->trap_port, ETH_DATA_LEN); + if (ret) + goto out_teardown_irq; diff --git a/target/linux/generic/backport-6.12/942-04-v7.2-net-dsa-realtek-rtl8365mb-prepare-for-multiple-sourc.patch b/target/linux/generic/backport-6.12/942-04-v7.2-net-dsa-realtek-rtl8365mb-prepare-for-multiple-sourc.patch new file mode 100644 index 00000000000..f3eb40dbc23 --- /dev/null +++ b/target/linux/generic/backport-6.12/942-04-v7.2-net-dsa-realtek-rtl8365mb-prepare-for-multiple-sourc.patch @@ -0,0 +1,4631 @@ +From 0e692c27fe84db294866fe726f9944f7e125b817 Mon Sep 17 00:00:00 2001 +From: Alvin Å ipraga +Date: Sat, 6 Jun 2026 05:29:28 -0300 +Subject: net: dsa: realtek: rtl8365mb: prepare for multiple source files +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Rename rtl8365mb.c to rtl8365mb_main.c in preparation for subsequent +commits which add additional source files to the driver. + +The trailing backslash in the Makefile is deliberate. It allows for new +files to be added without clobbering git history. + +Signed-off-by: Alvin Å ipraga +Reviewed-by: Linus Walleij +Reviewed-by: Mieczyslaw Nalewaj +Co-developed-by: Luiz Angelo Daros de Luca +Signed-off-by: Luiz Angelo Daros de Luca +Link: https://patch.msgid.link/20260606-realtek_forward-v13-4-b9e409687cbe@gmail.com +Signed-off-by: Jakub Kicinski +--- + drivers/net/dsa/realtek/Makefile | 2 + + drivers/net/dsa/realtek/rtl8365mb.c | 2296 ------------------------------ + drivers/net/dsa/realtek/rtl8365mb_main.c | 2296 ++++++++++++++++++++++++++++++ + 3 files changed, 2298 insertions(+), 2296 deletions(-) + delete mode 100644 drivers/net/dsa/realtek/rtl8365mb.c + create mode 100644 drivers/net/dsa/realtek/rtl8365mb_main.c + +--- a/drivers/net/dsa/realtek/Makefile ++++ b/drivers/net/dsa/realtek/Makefile +@@ -16,3 +16,5 @@ ifdef CONFIG_NET_DSA_REALTEK_RTL8366RB_L + rtl8366-objs += rtl8366rb-leds.o + endif + obj-$(CONFIG_NET_DSA_REALTEK_RTL8365MB) += rtl8365mb.o ++rtl8365mb-objs := rtl8365mb_main.o \ ++# end of rtl8365mb-objs +--- a/drivers/net/dsa/realtek/rtl8365mb.c ++++ /dev/null +@@ -1,2294 +0,0 @@ +-// SPDX-License-Identifier: GPL-2.0 +-/* Realtek SMI subdriver for the Realtek RTL8365MB-VC ethernet switch. +- * +- * Copyright (C) 2021 Alvin Å ipraga +- * Copyright (C) 2021 Michael Rasmussen +- * +- * The RTL8365MB-VC is a 4+1 port 10/100/1000M switch controller. It includes 4 +- * integrated PHYs for the user facing ports, and an extension interface which +- * can be connected to the CPU - or another PHY - via either MII, RMII, or +- * RGMII. The switch is configured via the Realtek Simple Management Interface +- * (SMI), which uses the MDIO/MDC lines. +- * +- * Below is a simplified block diagram of the chip and its relevant interfaces. +- * +- * .-----------------------------------. +- * | | +- * UTP <---------------> Giga PHY <-> PCS <-> P0 GMAC | +- * UTP <---------------> Giga PHY <-> PCS <-> P1 GMAC | +- * UTP <---------------> Giga PHY <-> PCS <-> P2 GMAC | +- * UTP <---------------> Giga PHY <-> PCS <-> P3 GMAC | +- * | | +- * CPU/PHY <-MII/RMII/RGMII---> Extension <---> Extension | +- * | interface 1 GMAC 1 | +- * | | +- * SMI driver/ <-MDC/SCL---> Management ~~~~~~~~~~~~~~ | +- * EEPROM <-MDIO/SDA--> interface ~REALTEK ~~~~~ | +- * | ~RTL8365MB ~~~ | +- * | ~GXXXC TAIWAN~ | +- * GPIO <--------------> Reset ~~~~~~~~~~~~~~ | +- * | | +- * Interrupt <----------> Link UP/DOWN events | +- * controller | | +- * '-----------------------------------' +- * +- * The driver uses DSA to integrate the 4 user and 1 extension ports into the +- * kernel. Netdevices are created for the user ports, as are PHY devices for +- * their integrated PHYs. The device tree firmware should also specify the link +- * partner of the extension port - either via a fixed-link or other phy-handle. +- * See the device tree bindings for more detailed information. Note that the +- * driver has only been tested with a fixed-link, but in principle it should not +- * matter. +- * +- * NOTE: Currently, only the RGMII interface is implemented in this driver. +- * +- * The interrupt line is asserted on link UP/DOWN events. The driver creates a +- * custom irqchip to handle this interrupt and demultiplex the events by reading +- * the status registers via SMI. Interrupts are then propagated to the relevant +- * PHY device. +- * +- * The EEPROM contains initial register values which the chip will read over I2C +- * upon hardware reset. It is also possible to omit the EEPROM. In both cases, +- * the driver will manually reprogram some registers using jam tables to reach +- * an initial state defined by the vendor driver. +- * +- * This Linux driver is written based on an OS-agnostic vendor driver from +- * Realtek. The reference GPL-licensed sources can be found in the OpenWrt +- * source tree under the name rtl8367c. The vendor driver claims to support a +- * number of similar switch controllers from Realtek, but the only hardware we +- * have is the RTL8365MB-VC. Moreover, there does not seem to be any chip under +- * the name RTL8367C. Although one wishes that the 'C' stood for some kind of +- * common hardware revision, there exist examples of chips with the suffix -VC +- * which are explicitly not supported by the rtl8367c driver and which instead +- * require the rtl8367d vendor driver. With all this uncertainty, the driver has +- * been modestly named rtl8365mb. Future implementors may wish to rename things +- * accordingly. +- * +- * In the same family of chips, some carry up to 8 user ports and up to 2 +- * extension ports. Where possible this driver tries to make things generic, but +- * more work must be done to support these configurations. According to +- * documentation from Realtek, the family should include the following chips: +- * +- * - RTL8363NB +- * - RTL8363NB-VB +- * - RTL8363SC +- * - RTL8363SC-VB +- * - RTL8364NB +- * - RTL8364NB-VB +- * - RTL8365MB-VC +- * - RTL8366SC +- * - RTL8367RB-VB +- * - RTL8367SB +- * - RTL8367S +- * - RTL8370MB +- * - RTL8310SR +- * +- * Some of the register logic for these additional chips has been skipped over +- * while implementing this driver. It is therefore not possible to assume that +- * things will work out-of-the-box for other chips, and a careful review of the +- * vendor driver may be needed to expand support. The RTL8365MB-VC seems to be +- * one of the simpler chips. +- */ +- +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +- +-#include "realtek.h" +-#include "realtek-smi.h" +-#include "realtek-mdio.h" +-#include "rtl83xx.h" +- +-/* Family-specific data and limits */ +-#define RTL8365MB_PHYADDRMAX 7 +-#define RTL8365MB_NUM_PHYREGS 32 +-#define RTL8365MB_PHYREGMAX (RTL8365MB_NUM_PHYREGS - 1) +-#define RTL8365MB_MAX_NUM_PORTS 11 +-#define RTL8365MB_MAX_NUM_EXTINTS 3 +-#define RTL8365MB_LEARN_LIMIT_MAX 2112 +- +-/* Chip identification registers */ +-#define RTL8365MB_CHIP_ID_REG 0x1300 +- +-#define RTL8365MB_CHIP_VER_REG 0x1301 +- +-#define RTL8365MB_MAGIC_REG 0x13C2 +-#define RTL8365MB_MAGIC_VALUE 0x0249 +- +-/* Chip reset register */ +-#define RTL8365MB_CHIP_RESET_REG 0x1322 +-#define RTL8365MB_CHIP_RESET_SW_MASK 0x0002 +-#define RTL8365MB_CHIP_RESET_HW_MASK 0x0001 +- +-/* Interrupt polarity register */ +-#define RTL8365MB_INTR_POLARITY_REG 0x1100 +-#define RTL8365MB_INTR_POLARITY_MASK 0x0001 +-#define RTL8365MB_INTR_POLARITY_HIGH 0 +-#define RTL8365MB_INTR_POLARITY_LOW 1 +- +-/* Interrupt control/status register - enable/check specific interrupt types */ +-#define RTL8365MB_INTR_CTRL_REG 0x1101 +-#define RTL8365MB_INTR_STATUS_REG 0x1102 +-#define RTL8365MB_INTR_SLIENT_START_2_MASK 0x1000 +-#define RTL8365MB_INTR_SLIENT_START_MASK 0x0800 +-#define RTL8365MB_INTR_ACL_ACTION_MASK 0x0200 +-#define RTL8365MB_INTR_CABLE_DIAG_FIN_MASK 0x0100 +-#define RTL8365MB_INTR_INTERRUPT_8051_MASK 0x0080 +-#define RTL8365MB_INTR_LOOP_DETECTION_MASK 0x0040 +-#define RTL8365MB_INTR_GREEN_TIMER_MASK 0x0020 +-#define RTL8365MB_INTR_SPECIAL_CONGEST_MASK 0x0010 +-#define RTL8365MB_INTR_SPEED_CHANGE_MASK 0x0008 +-#define RTL8365MB_INTR_LEARN_OVER_MASK 0x0004 +-#define RTL8365MB_INTR_METER_EXCEEDED_MASK 0x0002 +-#define RTL8365MB_INTR_LINK_CHANGE_MASK 0x0001 +-#define RTL8365MB_INTR_ALL_MASK \ +- (RTL8365MB_INTR_SLIENT_START_2_MASK | \ +- RTL8365MB_INTR_SLIENT_START_MASK | \ +- RTL8365MB_INTR_ACL_ACTION_MASK | \ +- RTL8365MB_INTR_CABLE_DIAG_FIN_MASK | \ +- RTL8365MB_INTR_INTERRUPT_8051_MASK | \ +- RTL8365MB_INTR_LOOP_DETECTION_MASK | \ +- RTL8365MB_INTR_GREEN_TIMER_MASK | \ +- RTL8365MB_INTR_SPECIAL_CONGEST_MASK | \ +- RTL8365MB_INTR_SPEED_CHANGE_MASK | \ +- RTL8365MB_INTR_LEARN_OVER_MASK | \ +- RTL8365MB_INTR_METER_EXCEEDED_MASK | \ +- RTL8365MB_INTR_LINK_CHANGE_MASK) +- +-/* Per-port interrupt type status registers */ +-#define RTL8365MB_PORT_LINKDOWN_IND_REG 0x1106 +-#define RTL8365MB_PORT_LINKDOWN_IND_MASK 0x07FF +- +-#define RTL8365MB_PORT_LINKUP_IND_REG 0x1107 +-#define RTL8365MB_PORT_LINKUP_IND_MASK 0x07FF +- +-/* PHY indirect access registers */ +-#define RTL8365MB_INDIRECT_ACCESS_CTRL_REG 0x1F00 +-#define RTL8365MB_INDIRECT_ACCESS_CTRL_RW_MASK 0x0002 +-#define RTL8365MB_INDIRECT_ACCESS_CTRL_RW_READ 0 +-#define RTL8365MB_INDIRECT_ACCESS_CTRL_RW_WRITE 1 +-#define RTL8365MB_INDIRECT_ACCESS_CTRL_CMD_MASK 0x0001 +-#define RTL8365MB_INDIRECT_ACCESS_CTRL_CMD_VALUE 1 +-#define RTL8365MB_INDIRECT_ACCESS_STATUS_REG 0x1F01 +-#define RTL8365MB_INDIRECT_ACCESS_ADDRESS_REG 0x1F02 +-#define RTL8365MB_INDIRECT_ACCESS_ADDRESS_OCPADR_5_1_MASK GENMASK(4, 0) +-#define RTL8365MB_INDIRECT_ACCESS_ADDRESS_PHYNUM_MASK GENMASK(7, 5) +-#define RTL8365MB_INDIRECT_ACCESS_ADDRESS_OCPADR_9_6_MASK GENMASK(11, 8) +-#define RTL8365MB_PHY_BASE 0x2000 +-#define RTL8365MB_INDIRECT_ACCESS_WRITE_DATA_REG 0x1F03 +-#define RTL8365MB_INDIRECT_ACCESS_READ_DATA_REG 0x1F04 +- +-/* PHY OCP address prefix register */ +-#define RTL8365MB_GPHY_OCP_MSB_0_REG 0x1D15 +-#define RTL8365MB_GPHY_OCP_MSB_0_CFG_CPU_OCPADR_MASK 0x0FC0 +-#define RTL8365MB_PHY_OCP_ADDR_PREFIX_MASK 0xFC00 +- +-/* The PHY OCP addresses of PHY registers 0~31 start here */ +-#define RTL8365MB_PHY_OCP_ADDR_PHYREG_BASE 0xA400 +- +-/* External interface port mode values - used in DIGITAL_INTERFACE_SELECT */ +-#define RTL8365MB_EXT_PORT_MODE_DISABLE 0 +-#define RTL8365MB_EXT_PORT_MODE_RGMII 1 +-#define RTL8365MB_EXT_PORT_MODE_MII_MAC 2 +-#define RTL8365MB_EXT_PORT_MODE_MII_PHY 3 +-#define RTL8365MB_EXT_PORT_MODE_TMII_MAC 4 +-#define RTL8365MB_EXT_PORT_MODE_TMII_PHY 5 +-#define RTL8365MB_EXT_PORT_MODE_GMII 6 +-#define RTL8365MB_EXT_PORT_MODE_RMII_MAC 7 +-#define RTL8365MB_EXT_PORT_MODE_RMII_PHY 8 +-#define RTL8365MB_EXT_PORT_MODE_SGMII 9 +-#define RTL8365MB_EXT_PORT_MODE_HSGMII 10 +-#define RTL8365MB_EXT_PORT_MODE_1000X_100FX 11 +-#define RTL8365MB_EXT_PORT_MODE_1000X 12 +-#define RTL8365MB_EXT_PORT_MODE_100FX 13 +- +-/* External interface mode configuration registers 0~1 */ +-#define RTL8365MB_DIGITAL_INTERFACE_SELECT_REG0 0x1305 /* EXT0,EXT1 */ +-#define RTL8365MB_DIGITAL_INTERFACE_SELECT_REG1 0x13C3 /* EXT2 */ +-#define RTL8365MB_DIGITAL_INTERFACE_SELECT_REG(_extint) \ +- ((_extint) <= 1 ? RTL8365MB_DIGITAL_INTERFACE_SELECT_REG0 : \ +- (_extint) == 2 ? RTL8365MB_DIGITAL_INTERFACE_SELECT_REG1 : \ +- 0x0) +-#define RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_MASK(_extint) \ +- (0xF << (((_extint) % 2) * 4)) +-#define RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_OFFSET(_extint) \ +- (((_extint) % 2) * 4) +- +-/* External interface RGMII TX/RX delay configuration registers 0~2 */ +-#define RTL8365MB_EXT_RGMXF_REG0 0x1306 /* EXT0 */ +-#define RTL8365MB_EXT_RGMXF_REG1 0x1307 /* EXT1 */ +-#define RTL8365MB_EXT_RGMXF_REG2 0x13C5 /* EXT2 */ +-#define RTL8365MB_EXT_RGMXF_REG(_extint) \ +- ((_extint) == 0 ? RTL8365MB_EXT_RGMXF_REG0 : \ +- (_extint) == 1 ? RTL8365MB_EXT_RGMXF_REG1 : \ +- (_extint) == 2 ? RTL8365MB_EXT_RGMXF_REG2 : \ +- 0x0) +-#define RTL8365MB_EXT_RGMXF_RXDELAY_MASK 0x0007 +-#define RTL8365MB_EXT_RGMXF_TXDELAY_MASK 0x0008 +- +-/* External interface port speed values - used in DIGITAL_INTERFACE_FORCE */ +-#define RTL8365MB_PORT_SPEED_10M 0 +-#define RTL8365MB_PORT_SPEED_100M 1 +-#define RTL8365MB_PORT_SPEED_1000M 2 +- +-/* External interface force configuration registers 0~2 */ +-#define RTL8365MB_DIGITAL_INTERFACE_FORCE_REG0 0x1310 /* EXT0 */ +-#define RTL8365MB_DIGITAL_INTERFACE_FORCE_REG1 0x1311 /* EXT1 */ +-#define RTL8365MB_DIGITAL_INTERFACE_FORCE_REG2 0x13C4 /* EXT2 */ +-#define RTL8365MB_DIGITAL_INTERFACE_FORCE_REG(_extint) \ +- ((_extint) == 0 ? RTL8365MB_DIGITAL_INTERFACE_FORCE_REG0 : \ +- (_extint) == 1 ? RTL8365MB_DIGITAL_INTERFACE_FORCE_REG1 : \ +- (_extint) == 2 ? RTL8365MB_DIGITAL_INTERFACE_FORCE_REG2 : \ +- 0x0) +-#define RTL8365MB_DIGITAL_INTERFACE_FORCE_EN_MASK 0x1000 +-#define RTL8365MB_DIGITAL_INTERFACE_FORCE_NWAY_MASK 0x0080 +-#define RTL8365MB_DIGITAL_INTERFACE_FORCE_TXPAUSE_MASK 0x0040 +-#define RTL8365MB_DIGITAL_INTERFACE_FORCE_RXPAUSE_MASK 0x0020 +-#define RTL8365MB_DIGITAL_INTERFACE_FORCE_LINK_MASK 0x0010 +-#define RTL8365MB_DIGITAL_INTERFACE_FORCE_DUPLEX_MASK 0x0004 +-#define RTL8365MB_DIGITAL_INTERFACE_FORCE_SPEED_MASK 0x0003 +- +-/* CPU port mask register - controls which ports are treated as CPU ports */ +-#define RTL8365MB_CPU_PORT_MASK_REG 0x1219 +-#define RTL8365MB_CPU_PORT_MASK_MASK 0x07FF +- +-/* CPU control register */ +-#define RTL8365MB_CPU_CTRL_REG 0x121A +-#define RTL8365MB_CPU_CTRL_TRAP_PORT_EXT_MASK 0x0400 +-#define RTL8365MB_CPU_CTRL_TAG_FORMAT_MASK 0x0200 +-#define RTL8365MB_CPU_CTRL_RXBYTECOUNT_MASK 0x0080 +-#define RTL8365MB_CPU_CTRL_TAG_POSITION_MASK 0x0040 +-#define RTL8365MB_CPU_CTRL_TRAP_PORT_MASK 0x0038 +-#define RTL8365MB_CPU_CTRL_INSERTMODE_MASK 0x0006 +-#define RTL8365MB_CPU_CTRL_EN_MASK 0x0001 +- +-/* Maximum packet length register */ +-#define RTL8365MB_CFG0_MAX_LEN_REG 0x088C +-#define RTL8365MB_CFG0_MAX_LEN_MASK 0x3FFF +-#define RTL8365MB_CFG0_MAX_LEN_MAX 0x3FFF +- +-/* Port learning limit registers */ +-#define RTL8365MB_LUT_PORT_LEARN_LIMIT_BASE 0x0A20 +-#define RTL8365MB_LUT_PORT_LEARN_LIMIT_REG(_physport) \ +- (RTL8365MB_LUT_PORT_LEARN_LIMIT_BASE + (_physport)) +- +-/* Port isolation (forwarding mask) registers */ +-#define RTL8365MB_PORT_ISOLATION_REG_BASE 0x08A2 +-#define RTL8365MB_PORT_ISOLATION_REG(_physport) \ +- (RTL8365MB_PORT_ISOLATION_REG_BASE + (_physport)) +-#define RTL8365MB_PORT_ISOLATION_MASK 0x07FF +- +-/* MSTP port state registers - indexed by tree instance */ +-#define RTL8365MB_MSTI_CTRL_BASE 0x0A00 +-#define RTL8365MB_MSTI_CTRL_REG(_msti, _physport) \ +- (RTL8365MB_MSTI_CTRL_BASE + ((_msti) << 1) + ((_physport) >> 3)) +-#define RTL8365MB_MSTI_CTRL_PORT_STATE_OFFSET(_physport) ((_physport) << 1) +-#define RTL8365MB_MSTI_CTRL_PORT_STATE_MASK(_physport) \ +- (0x3 << RTL8365MB_MSTI_CTRL_PORT_STATE_OFFSET((_physport))) +- +-/* MIB counter value registers */ +-#define RTL8365MB_MIB_COUNTER_BASE 0x1000 +-#define RTL8365MB_MIB_COUNTER_REG(_x) (RTL8365MB_MIB_COUNTER_BASE + (_x)) +- +-/* MIB counter address register */ +-#define RTL8365MB_MIB_ADDRESS_REG 0x1004 +-#define RTL8365MB_MIB_ADDRESS_PORT_OFFSET 0x007C +-#define RTL8365MB_MIB_ADDRESS(_p, _x) \ +- (((RTL8365MB_MIB_ADDRESS_PORT_OFFSET) * (_p) + (_x)) >> 2) +- +-#define RTL8365MB_MIB_CTRL0_REG 0x1005 +-#define RTL8365MB_MIB_CTRL0_RESET_MASK 0x0002 +-#define RTL8365MB_MIB_CTRL0_BUSY_MASK 0x0001 +- +-/* The DSA callback .get_stats64 runs in atomic context, so we are not allowed +- * to block. On the other hand, accessing MIB counters absolutely requires us to +- * block. The solution is thus to schedule work which polls the MIB counters +- * asynchronously and updates some private data, which the callback can then +- * fetch atomically. Three seconds should be a good enough polling interval. +- */ +-#define RTL8365MB_STATS_INTERVAL_JIFFIES (3 * HZ) +- +-enum rtl8365mb_mib_counter_index { +- RTL8365MB_MIB_ifInOctets, +- RTL8365MB_MIB_dot3StatsFCSErrors, +- RTL8365MB_MIB_dot3StatsSymbolErrors, +- RTL8365MB_MIB_dot3InPauseFrames, +- RTL8365MB_MIB_dot3ControlInUnknownOpcodes, +- RTL8365MB_MIB_etherStatsFragments, +- RTL8365MB_MIB_etherStatsJabbers, +- RTL8365MB_MIB_ifInUcastPkts, +- RTL8365MB_MIB_etherStatsDropEvents, +- RTL8365MB_MIB_ifInMulticastPkts, +- RTL8365MB_MIB_ifInBroadcastPkts, +- RTL8365MB_MIB_inMldChecksumError, +- RTL8365MB_MIB_inIgmpChecksumError, +- RTL8365MB_MIB_inMldSpecificQuery, +- RTL8365MB_MIB_inMldGeneralQuery, +- RTL8365MB_MIB_inIgmpSpecificQuery, +- RTL8365MB_MIB_inIgmpGeneralQuery, +- RTL8365MB_MIB_inMldLeaves, +- RTL8365MB_MIB_inIgmpLeaves, +- RTL8365MB_MIB_etherStatsOctets, +- RTL8365MB_MIB_etherStatsUnderSizePkts, +- RTL8365MB_MIB_etherOversizeStats, +- RTL8365MB_MIB_etherStatsPkts64Octets, +- RTL8365MB_MIB_etherStatsPkts65to127Octets, +- RTL8365MB_MIB_etherStatsPkts128to255Octets, +- RTL8365MB_MIB_etherStatsPkts256to511Octets, +- RTL8365MB_MIB_etherStatsPkts512to1023Octets, +- RTL8365MB_MIB_etherStatsPkts1024to1518Octets, +- RTL8365MB_MIB_ifOutOctets, +- RTL8365MB_MIB_dot3StatsSingleCollisionFrames, +- RTL8365MB_MIB_dot3StatsMultipleCollisionFrames, +- RTL8365MB_MIB_dot3StatsDeferredTransmissions, +- RTL8365MB_MIB_dot3StatsLateCollisions, +- RTL8365MB_MIB_etherStatsCollisions, +- RTL8365MB_MIB_dot3StatsExcessiveCollisions, +- RTL8365MB_MIB_dot3OutPauseFrames, +- RTL8365MB_MIB_ifOutDiscards, +- RTL8365MB_MIB_dot1dTpPortInDiscards, +- RTL8365MB_MIB_ifOutUcastPkts, +- RTL8365MB_MIB_ifOutMulticastPkts, +- RTL8365MB_MIB_ifOutBroadcastPkts, +- RTL8365MB_MIB_outOampduPkts, +- RTL8365MB_MIB_inOampduPkts, +- RTL8365MB_MIB_inIgmpJoinsSuccess, +- RTL8365MB_MIB_inIgmpJoinsFail, +- RTL8365MB_MIB_inMldJoinsSuccess, +- RTL8365MB_MIB_inMldJoinsFail, +- RTL8365MB_MIB_inReportSuppressionDrop, +- RTL8365MB_MIB_inLeaveSuppressionDrop, +- RTL8365MB_MIB_outIgmpReports, +- RTL8365MB_MIB_outIgmpLeaves, +- RTL8365MB_MIB_outIgmpGeneralQuery, +- RTL8365MB_MIB_outIgmpSpecificQuery, +- RTL8365MB_MIB_outMldReports, +- RTL8365MB_MIB_outMldLeaves, +- RTL8365MB_MIB_outMldGeneralQuery, +- RTL8365MB_MIB_outMldSpecificQuery, +- RTL8365MB_MIB_inKnownMulticastPkts, +- RTL8365MB_MIB_END, +-}; +- +-struct rtl8365mb_mib_counter { +- u32 offset; +- u32 length; +- const char *name; +-}; +- +-#define RTL8365MB_MAKE_MIB_COUNTER(_offset, _length, _name) \ +- [RTL8365MB_MIB_ ## _name] = { _offset, _length, #_name } +- +-static struct rtl8365mb_mib_counter rtl8365mb_mib_counters[] = { +- RTL8365MB_MAKE_MIB_COUNTER(0, 4, ifInOctets), +- RTL8365MB_MAKE_MIB_COUNTER(4, 2, dot3StatsFCSErrors), +- RTL8365MB_MAKE_MIB_COUNTER(6, 2, dot3StatsSymbolErrors), +- RTL8365MB_MAKE_MIB_COUNTER(8, 2, dot3InPauseFrames), +- RTL8365MB_MAKE_MIB_COUNTER(10, 2, dot3ControlInUnknownOpcodes), +- RTL8365MB_MAKE_MIB_COUNTER(12, 2, etherStatsFragments), +- RTL8365MB_MAKE_MIB_COUNTER(14, 2, etherStatsJabbers), +- RTL8365MB_MAKE_MIB_COUNTER(16, 2, ifInUcastPkts), +- RTL8365MB_MAKE_MIB_COUNTER(18, 2, etherStatsDropEvents), +- RTL8365MB_MAKE_MIB_COUNTER(20, 2, ifInMulticastPkts), +- RTL8365MB_MAKE_MIB_COUNTER(22, 2, ifInBroadcastPkts), +- RTL8365MB_MAKE_MIB_COUNTER(24, 2, inMldChecksumError), +- RTL8365MB_MAKE_MIB_COUNTER(26, 2, inIgmpChecksumError), +- RTL8365MB_MAKE_MIB_COUNTER(28, 2, inMldSpecificQuery), +- RTL8365MB_MAKE_MIB_COUNTER(30, 2, inMldGeneralQuery), +- RTL8365MB_MAKE_MIB_COUNTER(32, 2, inIgmpSpecificQuery), +- RTL8365MB_MAKE_MIB_COUNTER(34, 2, inIgmpGeneralQuery), +- RTL8365MB_MAKE_MIB_COUNTER(36, 2, inMldLeaves), +- RTL8365MB_MAKE_MIB_COUNTER(38, 2, inIgmpLeaves), +- RTL8365MB_MAKE_MIB_COUNTER(40, 4, etherStatsOctets), +- RTL8365MB_MAKE_MIB_COUNTER(44, 2, etherStatsUnderSizePkts), +- RTL8365MB_MAKE_MIB_COUNTER(46, 2, etherOversizeStats), +- RTL8365MB_MAKE_MIB_COUNTER(48, 2, etherStatsPkts64Octets), +- RTL8365MB_MAKE_MIB_COUNTER(50, 2, etherStatsPkts65to127Octets), +- RTL8365MB_MAKE_MIB_COUNTER(52, 2, etherStatsPkts128to255Octets), +- RTL8365MB_MAKE_MIB_COUNTER(54, 2, etherStatsPkts256to511Octets), +- RTL8365MB_MAKE_MIB_COUNTER(56, 2, etherStatsPkts512to1023Octets), +- RTL8365MB_MAKE_MIB_COUNTER(58, 2, etherStatsPkts1024to1518Octets), +- RTL8365MB_MAKE_MIB_COUNTER(60, 4, ifOutOctets), +- RTL8365MB_MAKE_MIB_COUNTER(64, 2, dot3StatsSingleCollisionFrames), +- RTL8365MB_MAKE_MIB_COUNTER(66, 2, dot3StatsMultipleCollisionFrames), +- RTL8365MB_MAKE_MIB_COUNTER(68, 2, dot3StatsDeferredTransmissions), +- RTL8365MB_MAKE_MIB_COUNTER(70, 2, dot3StatsLateCollisions), +- RTL8365MB_MAKE_MIB_COUNTER(72, 2, etherStatsCollisions), +- RTL8365MB_MAKE_MIB_COUNTER(74, 2, dot3StatsExcessiveCollisions), +- RTL8365MB_MAKE_MIB_COUNTER(76, 2, dot3OutPauseFrames), +- RTL8365MB_MAKE_MIB_COUNTER(78, 2, ifOutDiscards), +- RTL8365MB_MAKE_MIB_COUNTER(80, 2, dot1dTpPortInDiscards), +- RTL8365MB_MAKE_MIB_COUNTER(82, 2, ifOutUcastPkts), +- RTL8365MB_MAKE_MIB_COUNTER(84, 2, ifOutMulticastPkts), +- RTL8365MB_MAKE_MIB_COUNTER(86, 2, ifOutBroadcastPkts), +- RTL8365MB_MAKE_MIB_COUNTER(88, 2, outOampduPkts), +- RTL8365MB_MAKE_MIB_COUNTER(90, 2, inOampduPkts), +- RTL8365MB_MAKE_MIB_COUNTER(92, 4, inIgmpJoinsSuccess), +- RTL8365MB_MAKE_MIB_COUNTER(96, 2, inIgmpJoinsFail), +- RTL8365MB_MAKE_MIB_COUNTER(98, 2, inMldJoinsSuccess), +- RTL8365MB_MAKE_MIB_COUNTER(100, 2, inMldJoinsFail), +- RTL8365MB_MAKE_MIB_COUNTER(102, 2, inReportSuppressionDrop), +- RTL8365MB_MAKE_MIB_COUNTER(104, 2, inLeaveSuppressionDrop), +- RTL8365MB_MAKE_MIB_COUNTER(106, 2, outIgmpReports), +- RTL8365MB_MAKE_MIB_COUNTER(108, 2, outIgmpLeaves), +- RTL8365MB_MAKE_MIB_COUNTER(110, 2, outIgmpGeneralQuery), +- RTL8365MB_MAKE_MIB_COUNTER(112, 2, outIgmpSpecificQuery), +- RTL8365MB_MAKE_MIB_COUNTER(114, 2, outMldReports), +- RTL8365MB_MAKE_MIB_COUNTER(116, 2, outMldLeaves), +- RTL8365MB_MAKE_MIB_COUNTER(118, 2, outMldGeneralQuery), +- RTL8365MB_MAKE_MIB_COUNTER(120, 2, outMldSpecificQuery), +- RTL8365MB_MAKE_MIB_COUNTER(122, 2, inKnownMulticastPkts), +-}; +- +-static_assert(ARRAY_SIZE(rtl8365mb_mib_counters) == RTL8365MB_MIB_END); +- +-struct rtl8365mb_jam_tbl_entry { +- u16 reg; +- u16 val; +-}; +- +-/* Lifted from the vendor driver sources */ +-static const struct rtl8365mb_jam_tbl_entry rtl8365mb_init_jam_8365mb_vc[] = { +- { 0x13EB, 0x15BB }, { 0x1303, 0x06D6 }, { 0x1304, 0x0700 }, +- { 0x13E2, 0x003F }, { 0x13F9, 0x0090 }, { 0x121E, 0x03CA }, +- { 0x1233, 0x0352 }, { 0x1237, 0x00A0 }, { 0x123A, 0x0030 }, +- { 0x1239, 0x0084 }, { 0x0301, 0x1000 }, { 0x1349, 0x001F }, +- { 0x18E0, 0x4004 }, { 0x122B, 0x241C }, { 0x1305, 0xC000 }, +- { 0x13F0, 0x0000 }, +-}; +- +-static const struct rtl8365mb_jam_tbl_entry rtl8365mb_init_jam_common[] = { +- { 0x1200, 0x7FCB }, { 0x0884, 0x0003 }, { 0x06EB, 0x0001 }, +- { 0x03Fa, 0x0007 }, { 0x08C8, 0x00C0 }, { 0x0A30, 0x020E }, +- { 0x0800, 0x0000 }, { 0x0802, 0x0000 }, { 0x09DA, 0x0013 }, +- { 0x1D32, 0x0002 }, +-}; +- +-enum rtl8365mb_phy_interface_mode { +- RTL8365MB_PHY_INTERFACE_MODE_INVAL = 0, +- RTL8365MB_PHY_INTERFACE_MODE_INTERNAL = BIT(0), +- RTL8365MB_PHY_INTERFACE_MODE_MII = BIT(1), +- RTL8365MB_PHY_INTERFACE_MODE_TMII = BIT(2), +- RTL8365MB_PHY_INTERFACE_MODE_RMII = BIT(3), +- RTL8365MB_PHY_INTERFACE_MODE_RGMII = BIT(4), +- RTL8365MB_PHY_INTERFACE_MODE_SGMII = BIT(5), +- RTL8365MB_PHY_INTERFACE_MODE_HSGMII = BIT(6), +-}; +- +-/** +- * struct rtl8365mb_extint - external interface info +- * @port: the port with an external interface +- * @id: the external interface ID, which is either 0, 1, or 2 +- * @supported_interfaces: a bitmask of supported PHY interface modes +- * +- * Represents a mapping: port -> { id, supported_interfaces }. To be embedded +- * in &struct rtl8365mb_chip_info for every port with an external interface. +- */ +-struct rtl8365mb_extint { +- int port; +- int id; +- unsigned int supported_interfaces; +-}; +- +-/** +- * struct rtl8365mb_chip_info - static chip-specific info +- * @name: human-readable chip name +- * @chip_id: chip identifier +- * @chip_ver: chip silicon revision +- * @extints: available external interfaces +- * @jam_table: chip-specific initialization jam table +- * @jam_size: size of the chip's jam table +- * +- * These data are specific to a given chip in the family of switches supported +- * by this driver. When adding support for another chip in the family, a new +- * chip info should be added to the rtl8365mb_chip_infos array. +- */ +-struct rtl8365mb_chip_info { +- const char *name; +- u32 chip_id; +- u32 chip_ver; +- const struct rtl8365mb_extint extints[RTL8365MB_MAX_NUM_EXTINTS]; +- const struct rtl8365mb_jam_tbl_entry *jam_table; +- size_t jam_size; +-}; +- +-/* Chip info for each supported switch in the family */ +-#define PHY_INTF(_mode) (RTL8365MB_PHY_INTERFACE_MODE_ ## _mode) +-static const struct rtl8365mb_chip_info rtl8365mb_chip_infos[] = { +- { +- .name = "RTL8365MB-VC", +- .chip_id = 0x6367, +- .chip_ver = 0x0040, +- .extints = { +- { 6, 1, PHY_INTF(MII) | PHY_INTF(TMII) | +- PHY_INTF(RMII) | PHY_INTF(RGMII) }, +- }, +- .jam_table = rtl8365mb_init_jam_8365mb_vc, +- .jam_size = ARRAY_SIZE(rtl8365mb_init_jam_8365mb_vc), +- }, +- { +- .name = "RTL8367S", +- .chip_id = 0x6367, +- .chip_ver = 0x00A0, +- .extints = { +- { 6, 1, PHY_INTF(SGMII) | PHY_INTF(HSGMII) }, +- { 7, 2, PHY_INTF(MII) | PHY_INTF(TMII) | +- PHY_INTF(RMII) | PHY_INTF(RGMII) }, +- }, +- .jam_table = rtl8365mb_init_jam_8365mb_vc, +- .jam_size = ARRAY_SIZE(rtl8365mb_init_jam_8365mb_vc), +- }, +- { +- .name = "RTL8367SB", +- .chip_id = 0x6367, +- .chip_ver = 0x0010, +- .extints = { +- { 6, 1, PHY_INTF(MII) | PHY_INTF(TMII) | +- PHY_INTF(RMII) | PHY_INTF(RGMII) | +- PHY_INTF(SGMII) | PHY_INTF(HSGMII) }, +- { 7, 2, PHY_INTF(MII) | PHY_INTF(TMII) | +- PHY_INTF(RMII) | PHY_INTF(RGMII) }, +- }, +- .jam_table = rtl8365mb_init_jam_8365mb_vc, +- .jam_size = ARRAY_SIZE(rtl8365mb_init_jam_8365mb_vc), +- }, +- { +- .name = "RTL8367RB-VB", +- .chip_id = 0x6367, +- .chip_ver = 0x0020, +- .extints = { +- { 6, 1, PHY_INTF(MII) | PHY_INTF(TMII) | +- PHY_INTF(RMII) | PHY_INTF(RGMII) }, +- { 7, 2, PHY_INTF(MII) | PHY_INTF(TMII) | +- PHY_INTF(RMII) | PHY_INTF(RGMII) }, +- }, +- .jam_table = rtl8365mb_init_jam_8365mb_vc, +- .jam_size = ARRAY_SIZE(rtl8365mb_init_jam_8365mb_vc), +- }, +-}; +- +-enum rtl8365mb_stp_state { +- RTL8365MB_STP_STATE_DISABLED = 0, +- RTL8365MB_STP_STATE_BLOCKING = 1, +- RTL8365MB_STP_STATE_LEARNING = 2, +- RTL8365MB_STP_STATE_FORWARDING = 3, +-}; +- +-enum rtl8365mb_cpu_insert { +- RTL8365MB_CPU_INSERT_TO_ALL = 0, +- RTL8365MB_CPU_INSERT_TO_TRAPPING = 1, +- RTL8365MB_CPU_INSERT_TO_NONE = 2, +-}; +- +-enum rtl8365mb_cpu_position { +- RTL8365MB_CPU_POS_AFTER_SA = 0, +- RTL8365MB_CPU_POS_BEFORE_CRC = 1, +-}; +- +-enum rtl8365mb_cpu_format { +- RTL8365MB_CPU_FORMAT_8BYTES = 0, +- RTL8365MB_CPU_FORMAT_4BYTES = 1, +-}; +- +-enum rtl8365mb_cpu_rxlen { +- RTL8365MB_CPU_RXLEN_72BYTES = 0, +- RTL8365MB_CPU_RXLEN_64BYTES = 1, +-}; +- +-/** +- * struct rtl8365mb_cpu - CPU port configuration +- * @enable: enable/disable hardware insertion of CPU tag in switch->CPU frames +- * @mask: port mask of ports that parse should parse CPU tags +- * @trap_port: forward trapped frames to this port +- * @insert: CPU tag insertion mode in switch->CPU frames +- * @position: position of CPU tag in frame +- * @rx_length: minimum CPU RX length +- * @format: CPU tag format +- * +- * Represents the CPU tagging and CPU port configuration of the switch. These +- * settings are configurable at runtime. +- */ +-struct rtl8365mb_cpu { +- bool enable; +- u32 mask; +- u32 trap_port; +- enum rtl8365mb_cpu_insert insert; +- enum rtl8365mb_cpu_position position; +- enum rtl8365mb_cpu_rxlen rx_length; +- enum rtl8365mb_cpu_format format; +-}; +- +-/** +- * struct rtl8365mb_port - private per-port data +- * @priv: pointer to parent realtek_priv data +- * @index: DSA port index, same as dsa_port::index +- * @stats: link statistics populated by rtl8365mb_stats_poll, ready for atomic +- * access via rtl8365mb_get_stats64 +- * @stats_lock: protect the stats structure during read/update +- * @mib_work: delayed work for polling MIB counters +- */ +-struct rtl8365mb_port { +- struct realtek_priv *priv; +- unsigned int index; +- struct rtnl_link_stats64 stats; +- spinlock_t stats_lock; +- struct delayed_work mib_work; +-}; +- +-/** +- * struct rtl8365mb - driver private data +- * @priv: pointer to parent realtek_priv data +- * @irq: registered IRQ or zero +- * @chip_info: chip-specific info about the attached switch +- * @cpu: CPU tagging and CPU port configuration for this chip +- * @mib_lock: prevent concurrent reads of MIB counters +- * @ports: per-port data +- * +- * Private data for this driver. +- */ +-struct rtl8365mb { +- struct realtek_priv *priv; +- int irq; +- const struct rtl8365mb_chip_info *chip_info; +- struct rtl8365mb_cpu cpu; +- struct mutex mib_lock; +- struct rtl8365mb_port ports[RTL8365MB_MAX_NUM_PORTS]; +-}; +- +-static int rtl8365mb_phy_poll_busy(struct realtek_priv *priv) +-{ +- u32 val; +- +- return regmap_read_poll_timeout(priv->map_nolock, +- RTL8365MB_INDIRECT_ACCESS_STATUS_REG, +- val, !val, 10, 100); +-} +- +-static int rtl8365mb_phy_ocp_prepare(struct realtek_priv *priv, int phy, +- u32 ocp_addr) +-{ +- u32 val; +- int ret; +- +- /* Set OCP prefix */ +- val = FIELD_GET(RTL8365MB_PHY_OCP_ADDR_PREFIX_MASK, ocp_addr); +- ret = regmap_update_bits( +- priv->map_nolock, RTL8365MB_GPHY_OCP_MSB_0_REG, +- RTL8365MB_GPHY_OCP_MSB_0_CFG_CPU_OCPADR_MASK, +- FIELD_PREP(RTL8365MB_GPHY_OCP_MSB_0_CFG_CPU_OCPADR_MASK, val)); +- if (ret) +- return ret; +- +- /* Set PHY register address */ +- val = RTL8365MB_PHY_BASE; +- val |= FIELD_PREP(RTL8365MB_INDIRECT_ACCESS_ADDRESS_PHYNUM_MASK, phy); +- val |= FIELD_PREP(RTL8365MB_INDIRECT_ACCESS_ADDRESS_OCPADR_5_1_MASK, +- ocp_addr >> 1); +- val |= FIELD_PREP(RTL8365MB_INDIRECT_ACCESS_ADDRESS_OCPADR_9_6_MASK, +- ocp_addr >> 6); +- ret = regmap_write(priv->map_nolock, +- RTL8365MB_INDIRECT_ACCESS_ADDRESS_REG, val); +- if (ret) +- return ret; +- +- return 0; +-} +- +-static int rtl8365mb_phy_ocp_read(struct realtek_priv *priv, int phy, +- u32 ocp_addr, u16 *data) +-{ +- u32 val; +- int ret; +- +- rtl83xx_lock(priv); +- +- ret = rtl8365mb_phy_poll_busy(priv); +- if (ret) +- goto out; +- +- ret = rtl8365mb_phy_ocp_prepare(priv, phy, ocp_addr); +- if (ret) +- goto out; +- +- /* Execute read operation */ +- val = FIELD_PREP(RTL8365MB_INDIRECT_ACCESS_CTRL_CMD_MASK, +- RTL8365MB_INDIRECT_ACCESS_CTRL_CMD_VALUE) | +- FIELD_PREP(RTL8365MB_INDIRECT_ACCESS_CTRL_RW_MASK, +- RTL8365MB_INDIRECT_ACCESS_CTRL_RW_READ); +- ret = regmap_write(priv->map_nolock, RTL8365MB_INDIRECT_ACCESS_CTRL_REG, +- val); +- if (ret) +- goto out; +- +- ret = rtl8365mb_phy_poll_busy(priv); +- if (ret) +- goto out; +- +- /* Get PHY register data */ +- ret = regmap_read(priv->map_nolock, +- RTL8365MB_INDIRECT_ACCESS_READ_DATA_REG, &val); +- if (ret) +- goto out; +- +- *data = val & 0xFFFF; +- +-out: +- rtl83xx_unlock(priv); +- +- return ret; +-} +- +-static int rtl8365mb_phy_ocp_write(struct realtek_priv *priv, int phy, +- u32 ocp_addr, u16 data) +-{ +- u32 val; +- int ret; +- +- rtl83xx_lock(priv); +- +- ret = rtl8365mb_phy_poll_busy(priv); +- if (ret) +- goto out; +- +- ret = rtl8365mb_phy_ocp_prepare(priv, phy, ocp_addr); +- if (ret) +- goto out; +- +- /* Set PHY register data */ +- ret = regmap_write(priv->map_nolock, +- RTL8365MB_INDIRECT_ACCESS_WRITE_DATA_REG, data); +- if (ret) +- goto out; +- +- /* Execute write operation */ +- val = FIELD_PREP(RTL8365MB_INDIRECT_ACCESS_CTRL_CMD_MASK, +- RTL8365MB_INDIRECT_ACCESS_CTRL_CMD_VALUE) | +- FIELD_PREP(RTL8365MB_INDIRECT_ACCESS_CTRL_RW_MASK, +- RTL8365MB_INDIRECT_ACCESS_CTRL_RW_WRITE); +- ret = regmap_write(priv->map_nolock, RTL8365MB_INDIRECT_ACCESS_CTRL_REG, +- val); +- if (ret) +- goto out; +- +- ret = rtl8365mb_phy_poll_busy(priv); +- if (ret) +- goto out; +- +-out: +- rtl83xx_unlock(priv); +- +- return ret; +-} +- +-static int rtl8365mb_phy_read(struct realtek_priv *priv, int phy, int regnum) +-{ +- u32 ocp_addr; +- u16 val; +- int ret; +- +- if (phy > RTL8365MB_PHYADDRMAX) +- return -EINVAL; +- +- if (regnum > RTL8365MB_PHYREGMAX) +- return -EINVAL; +- +- ocp_addr = RTL8365MB_PHY_OCP_ADDR_PHYREG_BASE + regnum * 2; +- +- ret = rtl8365mb_phy_ocp_read(priv, phy, ocp_addr, &val); +- if (ret) { +- dev_err(priv->dev, +- "failed to read PHY%d reg %02x @ %04x, ret %pe\n", phy, +- regnum, ocp_addr, ERR_PTR(ret)); +- return ret; +- } +- +- dev_dbg(priv->dev, "read PHY%d register 0x%02x @ %04x, val <- %04x\n", +- phy, regnum, ocp_addr, val); +- +- return val; +-} +- +-static int rtl8365mb_phy_write(struct realtek_priv *priv, int phy, int regnum, +- u16 val) +-{ +- u32 ocp_addr; +- int ret; +- +- if (phy > RTL8365MB_PHYADDRMAX) +- return -EINVAL; +- +- if (regnum > RTL8365MB_PHYREGMAX) +- return -EINVAL; +- +- ocp_addr = RTL8365MB_PHY_OCP_ADDR_PHYREG_BASE + regnum * 2; +- +- ret = rtl8365mb_phy_ocp_write(priv, phy, ocp_addr, val); +- if (ret) { +- dev_err(priv->dev, +- "failed to write PHY%d reg %02x @ %04x, ret %pe\n", phy, +- regnum, ocp_addr, ERR_PTR(ret)); +- return ret; +- } +- +- dev_dbg(priv->dev, "write PHY%d register 0x%02x @ %04x, val -> %04x\n", +- phy, regnum, ocp_addr, val); +- +- return 0; +-} +- +-static const struct rtl8365mb_extint * +-rtl8365mb_get_port_extint(struct realtek_priv *priv, int port) +-{ +- struct rtl8365mb *mb = priv->chip_data; +- int i; +- +- for (i = 0; i < RTL8365MB_MAX_NUM_EXTINTS; i++) { +- const struct rtl8365mb_extint *extint = +- &mb->chip_info->extints[i]; +- +- if (!extint->supported_interfaces) +- continue; +- +- if (extint->port == port) +- return extint; +- } +- +- return NULL; +-} +- +-static enum dsa_tag_protocol +-rtl8365mb_get_tag_protocol(struct dsa_switch *ds, int port, +- enum dsa_tag_protocol mp) +-{ +- struct realtek_priv *priv = ds->priv; +- struct rtl8365mb_cpu *cpu; +- struct rtl8365mb *mb; +- +- mb = priv->chip_data; +- cpu = &mb->cpu; +- +- if (cpu->position == RTL8365MB_CPU_POS_BEFORE_CRC) +- return DSA_TAG_PROTO_RTL8_4T; +- +- return DSA_TAG_PROTO_RTL8_4; +-} +- +-static int rtl8365mb_ext_config_rgmii(struct realtek_priv *priv, int port, +- phy_interface_t interface) +-{ +- const struct rtl8365mb_extint *extint = +- rtl8365mb_get_port_extint(priv, port); +- struct dsa_switch *ds = &priv->ds; +- struct device_node *dn; +- struct dsa_port *dp; +- int tx_delay = 0; +- int rx_delay = 0; +- u32 val; +- int ret; +- +- if (!extint) +- return -ENODEV; +- +- dp = dsa_to_port(ds, port); +- dn = dp->dn; +- +- /* Set the RGMII TX/RX delay +- * +- * The Realtek vendor driver indicates the following possible +- * configuration settings: +- * +- * TX delay: +- * 0 = no delay, 1 = 2 ns delay +- * RX delay: +- * 0 = no delay, 7 = maximum delay +- * Each step is approximately 0.3 ns, so the maximum delay is about +- * 2.1 ns. +- * +- * The vendor driver also states that this must be configured *before* +- * forcing the external interface into a particular mode, which is done +- * in the rtl8365mb_phylink_mac_link_{up,down} functions. +- * +- * Only configure an RGMII TX (resp. RX) delay if the +- * tx-internal-delay-ps (resp. rx-internal-delay-ps) OF property is +- * specified. We ignore the detail of the RGMII interface mode +- * (RGMII_{RXID, TXID, etc.}), as this is considered to be a PHY-only +- * property. +- */ +- if (!of_property_read_u32(dn, "tx-internal-delay-ps", &val)) { +- val = val / 1000; /* convert to ns */ +- +- if (val == 0 || val == 2) +- tx_delay = val / 2; +- else +- dev_warn(priv->dev, +- "RGMII TX delay must be 0 or 2 ns\n"); +- } +- +- if (!of_property_read_u32(dn, "rx-internal-delay-ps", &val)) { +- val = DIV_ROUND_CLOSEST(val, 300); /* convert to 0.3 ns step */ +- +- if (val <= 7) +- rx_delay = val; +- else +- dev_warn(priv->dev, +- "RGMII RX delay must be 0 to 2.1 ns\n"); +- } +- +- ret = regmap_update_bits( +- priv->map, RTL8365MB_EXT_RGMXF_REG(extint->id), +- RTL8365MB_EXT_RGMXF_TXDELAY_MASK | +- RTL8365MB_EXT_RGMXF_RXDELAY_MASK, +- FIELD_PREP(RTL8365MB_EXT_RGMXF_TXDELAY_MASK, tx_delay) | +- FIELD_PREP(RTL8365MB_EXT_RGMXF_RXDELAY_MASK, rx_delay)); +- if (ret) +- return ret; +- +- ret = regmap_update_bits( +- priv->map, RTL8365MB_DIGITAL_INTERFACE_SELECT_REG(extint->id), +- RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_MASK(extint->id), +- RTL8365MB_EXT_PORT_MODE_RGMII +- << RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_OFFSET( +- extint->id)); +- if (ret) +- return ret; +- +- return 0; +-} +- +-static int rtl8365mb_ext_config_forcemode(struct realtek_priv *priv, int port, +- bool link, int speed, int duplex, +- bool tx_pause, bool rx_pause) +-{ +- const struct rtl8365mb_extint *extint = +- rtl8365mb_get_port_extint(priv, port); +- u32 r_tx_pause; +- u32 r_rx_pause; +- u32 r_duplex; +- u32 r_speed; +- u32 r_link; +- int val; +- int ret; +- +- if (!extint) +- return -ENODEV; +- +- if (link) { +- /* Force the link up with the desired configuration */ +- r_link = 1; +- r_rx_pause = rx_pause ? 1 : 0; +- r_tx_pause = tx_pause ? 1 : 0; +- +- if (speed == SPEED_1000) { +- r_speed = RTL8365MB_PORT_SPEED_1000M; +- } else if (speed == SPEED_100) { +- r_speed = RTL8365MB_PORT_SPEED_100M; +- } else if (speed == SPEED_10) { +- r_speed = RTL8365MB_PORT_SPEED_10M; +- } else { +- dev_err(priv->dev, "unsupported port speed %s\n", +- phy_speed_to_str(speed)); +- return -EINVAL; +- } +- +- if (duplex == DUPLEX_FULL) { +- r_duplex = 1; +- } else if (duplex == DUPLEX_HALF) { +- r_duplex = 0; +- } else { +- dev_err(priv->dev, "unsupported duplex %s\n", +- phy_duplex_to_str(duplex)); +- return -EINVAL; +- } +- } else { +- /* Force the link down and reset any programmed configuration */ +- r_link = 0; +- r_tx_pause = 0; +- r_rx_pause = 0; +- r_speed = 0; +- r_duplex = 0; +- } +- +- val = FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_EN_MASK, 1) | +- FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_TXPAUSE_MASK, +- r_tx_pause) | +- FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_RXPAUSE_MASK, +- r_rx_pause) | +- FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_LINK_MASK, r_link) | +- FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_DUPLEX_MASK, +- r_duplex) | +- FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_SPEED_MASK, r_speed); +- ret = regmap_write(priv->map, +- RTL8365MB_DIGITAL_INTERFACE_FORCE_REG(extint->id), +- val); +- if (ret) +- return ret; +- +- return 0; +-} +- +-static void rtl8365mb_phylink_get_caps(struct dsa_switch *ds, int port, +- struct phylink_config *config) +-{ +- const struct rtl8365mb_extint *extint = +- rtl8365mb_get_port_extint(ds->priv, port); +- +- config->mac_capabilities = MAC_SYM_PAUSE | MAC_ASYM_PAUSE | +- MAC_10 | MAC_100 | MAC_1000FD; +- +- if (!extint) { +- __set_bit(PHY_INTERFACE_MODE_INTERNAL, +- config->supported_interfaces); +- +- /* GMII is the default interface mode for phylib, so +- * we have to support it for ports with integrated PHY. +- */ +- __set_bit(PHY_INTERFACE_MODE_GMII, +- config->supported_interfaces); +- return; +- } +- +- /* Populate according to the modes supported by _this driver_, +- * not necessarily the modes supported by the hardware, some of +- * which remain unimplemented. +- */ +- +- if (extint->supported_interfaces & RTL8365MB_PHY_INTERFACE_MODE_RGMII) +- phy_interface_set_rgmii(config->supported_interfaces); +-} +- +-static void rtl8365mb_phylink_mac_config(struct phylink_config *config, +- unsigned int mode, +- const struct phylink_link_state *state) +-{ +- struct dsa_port *dp = dsa_phylink_to_port(config); +- struct realtek_priv *priv = dp->ds->priv; +- u8 port = dp->index; +- int ret; +- +- if (mode != MLO_AN_PHY && mode != MLO_AN_FIXED) { +- dev_err(priv->dev, +- "port %d supports only conventional PHY or fixed-link\n", +- port); +- return; +- } +- +- if (phy_interface_mode_is_rgmii(state->interface)) { +- ret = rtl8365mb_ext_config_rgmii(priv, port, state->interface); +- if (ret) +- dev_err(priv->dev, +- "failed to configure RGMII mode on port %d: %pe\n", +- port, ERR_PTR(ret)); +- return; +- } +- +- /* TODO: Implement MII and RMII modes, which the RTL8365MB-VC also +- * supports +- */ +-} +- +-static void rtl8365mb_phylink_mac_link_down(struct phylink_config *config, +- unsigned int mode, +- phy_interface_t interface) +-{ +- struct dsa_port *dp = dsa_phylink_to_port(config); +- struct realtek_priv *priv = dp->ds->priv; +- struct rtl8365mb_port *p; +- struct rtl8365mb *mb; +- u8 port = dp->index; +- int ret; +- +- mb = priv->chip_data; +- p = &mb->ports[port]; +- cancel_delayed_work_sync(&p->mib_work); +- +- if (phy_interface_mode_is_rgmii(interface)) { +- ret = rtl8365mb_ext_config_forcemode(priv, port, false, 0, 0, +- false, false); +- if (ret) +- dev_err(priv->dev, +- "failed to reset forced mode on port %d: %pe\n", +- port, ERR_PTR(ret)); +- +- return; +- } +-} +- +-static void rtl8365mb_phylink_mac_link_up(struct phylink_config *config, +- struct phy_device *phydev, +- unsigned int mode, +- phy_interface_t interface, +- int speed, int duplex, bool tx_pause, +- bool rx_pause) +-{ +- struct dsa_port *dp = dsa_phylink_to_port(config); +- struct realtek_priv *priv = dp->ds->priv; +- struct rtl8365mb_port *p; +- struct rtl8365mb *mb; +- u8 port = dp->index; +- int ret; +- +- mb = priv->chip_data; +- p = &mb->ports[port]; +- schedule_delayed_work(&p->mib_work, 0); +- +- if (phy_interface_mode_is_rgmii(interface)) { +- ret = rtl8365mb_ext_config_forcemode(priv, port, true, speed, +- duplex, tx_pause, +- rx_pause); +- if (ret) +- dev_err(priv->dev, +- "failed to force mode on port %d: %pe\n", port, +- ERR_PTR(ret)); +- +- return; +- } +-} +- +-static int rtl8365mb_port_change_mtu(struct dsa_switch *ds, int port, +- int new_mtu) +-{ +- struct realtek_priv *priv = ds->priv; +- int frame_size; +- +- /* When a new MTU is set, DSA always sets the CPU port's MTU to the +- * largest MTU of the user ports. Because the switch only has a global +- * RX length register, only allowing CPU port here is enough. +- */ +- if (!dsa_is_cpu_port(ds, port)) +- return 0; +- +- frame_size = new_mtu + VLAN_ETH_HLEN + ETH_FCS_LEN; +- +- dev_dbg(priv->dev, "changing mtu to %d (frame size: %d)\n", +- new_mtu, frame_size); +- +- return regmap_update_bits(priv->map, RTL8365MB_CFG0_MAX_LEN_REG, +- RTL8365MB_CFG0_MAX_LEN_MASK, +- FIELD_PREP(RTL8365MB_CFG0_MAX_LEN_MASK, +- frame_size)); +-} +- +-static int rtl8365mb_port_max_mtu(struct dsa_switch *ds, int port) +-{ +- return RTL8365MB_CFG0_MAX_LEN_MAX - VLAN_ETH_HLEN - ETH_FCS_LEN; +-} +- +-static void rtl8365mb_port_stp_state_set(struct dsa_switch *ds, int port, +- u8 state) +-{ +- struct realtek_priv *priv = ds->priv; +- enum rtl8365mb_stp_state val; +- int msti = 0; +- +- switch (state) { +- case BR_STATE_DISABLED: +- val = RTL8365MB_STP_STATE_DISABLED; +- break; +- case BR_STATE_BLOCKING: +- case BR_STATE_LISTENING: +- val = RTL8365MB_STP_STATE_BLOCKING; +- break; +- case BR_STATE_LEARNING: +- val = RTL8365MB_STP_STATE_LEARNING; +- break; +- case BR_STATE_FORWARDING: +- val = RTL8365MB_STP_STATE_FORWARDING; +- break; +- default: +- dev_err(priv->dev, "invalid STP state: %u\n", state); +- return; +- } +- +- regmap_update_bits(priv->map, RTL8365MB_MSTI_CTRL_REG(msti, port), +- RTL8365MB_MSTI_CTRL_PORT_STATE_MASK(port), +- val << RTL8365MB_MSTI_CTRL_PORT_STATE_OFFSET(port)); +-} +- +-static int rtl8365mb_port_set_learning(struct realtek_priv *priv, int port, +- bool enable) +-{ +- /* Enable/disable learning by limiting the number of L2 addresses the +- * port can learn. Realtek documentation states that a limit of zero +- * disables learning. When enabling learning, set it to the chip's +- * maximum. +- */ +- return regmap_write(priv->map, RTL8365MB_LUT_PORT_LEARN_LIMIT_REG(port), +- enable ? RTL8365MB_LEARN_LIMIT_MAX : 0); +-} +- +-static int rtl8365mb_port_set_isolation(struct realtek_priv *priv, int port, +- u32 mask) +-{ +- return regmap_write(priv->map, RTL8365MB_PORT_ISOLATION_REG(port), mask); +-} +- +-static int rtl8365mb_mib_counter_read(struct realtek_priv *priv, int port, +- u32 offset, u32 length, u64 *mibvalue) +-{ +- u64 tmpvalue = 0; +- u32 val; +- int ret; +- int i; +- +- /* The MIB address is an SRAM address. We request a particular address +- * and then poll the control register before reading the value from some +- * counter registers. +- */ +- ret = regmap_write(priv->map, RTL8365MB_MIB_ADDRESS_REG, +- RTL8365MB_MIB_ADDRESS(port, offset)); +- if (ret) +- return ret; +- +- /* Poll for completion */ +- ret = regmap_read_poll_timeout(priv->map, RTL8365MB_MIB_CTRL0_REG, val, +- !(val & RTL8365MB_MIB_CTRL0_BUSY_MASK), +- 10, 100); +- if (ret) +- return ret; +- +- /* Presumably this indicates a MIB counter read failure */ +- if (val & RTL8365MB_MIB_CTRL0_RESET_MASK) +- return -EIO; +- +- /* There are four MIB counter registers each holding a 16 bit word of a +- * MIB counter. Depending on the offset, we should read from the upper +- * two or lower two registers. In case the MIB counter is 4 words, we +- * read from all four registers. +- */ +- if (length == 4) +- offset = 3; +- else +- offset = (offset + 1) % 4; +- +- /* Read the MIB counter 16 bits at a time */ +- for (i = 0; i < length; i++) { +- ret = regmap_read(priv->map, +- RTL8365MB_MIB_COUNTER_REG(offset - i), &val); +- if (ret) +- return ret; +- +- tmpvalue = ((tmpvalue) << 16) | (val & 0xFFFF); +- } +- +- /* Only commit the result if no error occurred */ +- *mibvalue = tmpvalue; +- +- return 0; +-} +- +-static void rtl8365mb_get_ethtool_stats(struct dsa_switch *ds, int port, u64 *data) +-{ +- struct realtek_priv *priv = ds->priv; +- struct rtl8365mb *mb; +- int ret; +- int i; +- +- mb = priv->chip_data; +- +- mutex_lock(&mb->mib_lock); +- for (i = 0; i < RTL8365MB_MIB_END; i++) { +- struct rtl8365mb_mib_counter *mib = &rtl8365mb_mib_counters[i]; +- +- ret = rtl8365mb_mib_counter_read(priv, port, mib->offset, +- mib->length, &data[i]); +- if (ret) { +- dev_err(priv->dev, +- "failed to read port %d counters: %pe\n", port, +- ERR_PTR(ret)); +- break; +- } +- } +- mutex_unlock(&mb->mib_lock); +-} +- +-static void rtl8365mb_get_strings(struct dsa_switch *ds, int port, u32 stringset, u8 *data) +-{ +- int i; +- +- if (stringset != ETH_SS_STATS) +- return; +- +- for (i = 0; i < RTL8365MB_MIB_END; i++) { +- struct rtl8365mb_mib_counter *mib = &rtl8365mb_mib_counters[i]; +- ethtool_puts(&data, mib->name); +- } +-} +- +-static int rtl8365mb_get_sset_count(struct dsa_switch *ds, int port, int sset) +-{ +- if (sset != ETH_SS_STATS) +- return -EOPNOTSUPP; +- +- return RTL8365MB_MIB_END; +-} +- +-static void rtl8365mb_get_phy_stats(struct dsa_switch *ds, int port, +- struct ethtool_eth_phy_stats *phy_stats) +-{ +- struct realtek_priv *priv = ds->priv; +- struct rtl8365mb_mib_counter *mib; +- struct rtl8365mb *mb; +- +- mb = priv->chip_data; +- mib = &rtl8365mb_mib_counters[RTL8365MB_MIB_dot3StatsSymbolErrors]; +- +- mutex_lock(&mb->mib_lock); +- rtl8365mb_mib_counter_read(priv, port, mib->offset, mib->length, +- &phy_stats->SymbolErrorDuringCarrier); +- mutex_unlock(&mb->mib_lock); +-} +- +-static void rtl8365mb_get_mac_stats(struct dsa_switch *ds, int port, +- struct ethtool_eth_mac_stats *mac_stats) +-{ +- u64 cnt[RTL8365MB_MIB_END] = { +- [RTL8365MB_MIB_ifOutOctets] = 1, +- [RTL8365MB_MIB_ifOutUcastPkts] = 1, +- [RTL8365MB_MIB_ifOutMulticastPkts] = 1, +- [RTL8365MB_MIB_ifOutBroadcastPkts] = 1, +- [RTL8365MB_MIB_dot3OutPauseFrames] = 1, +- [RTL8365MB_MIB_ifOutDiscards] = 1, +- [RTL8365MB_MIB_ifInOctets] = 1, +- [RTL8365MB_MIB_ifInUcastPkts] = 1, +- [RTL8365MB_MIB_ifInMulticastPkts] = 1, +- [RTL8365MB_MIB_ifInBroadcastPkts] = 1, +- [RTL8365MB_MIB_dot3InPauseFrames] = 1, +- [RTL8365MB_MIB_dot3StatsSingleCollisionFrames] = 1, +- [RTL8365MB_MIB_dot3StatsMultipleCollisionFrames] = 1, +- [RTL8365MB_MIB_dot3StatsFCSErrors] = 1, +- [RTL8365MB_MIB_dot3StatsDeferredTransmissions] = 1, +- [RTL8365MB_MIB_dot3StatsLateCollisions] = 1, +- [RTL8365MB_MIB_dot3StatsExcessiveCollisions] = 1, +- +- }; +- struct realtek_priv *priv = ds->priv; +- struct rtl8365mb *mb; +- int ret; +- int i; +- +- mb = priv->chip_data; +- +- mutex_lock(&mb->mib_lock); +- for (i = 0; i < RTL8365MB_MIB_END; i++) { +- struct rtl8365mb_mib_counter *mib = &rtl8365mb_mib_counters[i]; +- +- /* Only fetch required MIB counters (marked = 1 above) */ +- if (!cnt[i]) +- continue; +- +- ret = rtl8365mb_mib_counter_read(priv, port, mib->offset, +- mib->length, &cnt[i]); +- if (ret) +- break; +- } +- mutex_unlock(&mb->mib_lock); +- +- /* The RTL8365MB-VC exposes MIB objects, which we have to translate into +- * IEEE 802.3 Managed Objects. This is not always completely faithful, +- * but we try out best. See RFC 3635 for a detailed treatment of the +- * subject. +- */ +- +- mac_stats->FramesTransmittedOK = cnt[RTL8365MB_MIB_ifOutUcastPkts] + +- cnt[RTL8365MB_MIB_ifOutMulticastPkts] + +- cnt[RTL8365MB_MIB_ifOutBroadcastPkts] + +- cnt[RTL8365MB_MIB_dot3OutPauseFrames] - +- cnt[RTL8365MB_MIB_ifOutDiscards]; +- mac_stats->SingleCollisionFrames = +- cnt[RTL8365MB_MIB_dot3StatsSingleCollisionFrames]; +- mac_stats->MultipleCollisionFrames = +- cnt[RTL8365MB_MIB_dot3StatsMultipleCollisionFrames]; +- mac_stats->FramesReceivedOK = cnt[RTL8365MB_MIB_ifInUcastPkts] + +- cnt[RTL8365MB_MIB_ifInMulticastPkts] + +- cnt[RTL8365MB_MIB_ifInBroadcastPkts] + +- cnt[RTL8365MB_MIB_dot3InPauseFrames]; +- mac_stats->FrameCheckSequenceErrors = +- cnt[RTL8365MB_MIB_dot3StatsFCSErrors]; +- mac_stats->OctetsTransmittedOK = cnt[RTL8365MB_MIB_ifOutOctets] - +- 18 * mac_stats->FramesTransmittedOK; +- mac_stats->FramesWithDeferredXmissions = +- cnt[RTL8365MB_MIB_dot3StatsDeferredTransmissions]; +- mac_stats->LateCollisions = cnt[RTL8365MB_MIB_dot3StatsLateCollisions]; +- mac_stats->FramesAbortedDueToXSColls = +- cnt[RTL8365MB_MIB_dot3StatsExcessiveCollisions]; +- mac_stats->OctetsReceivedOK = cnt[RTL8365MB_MIB_ifInOctets] - +- 18 * mac_stats->FramesReceivedOK; +- mac_stats->MulticastFramesXmittedOK = +- cnt[RTL8365MB_MIB_ifOutMulticastPkts]; +- mac_stats->BroadcastFramesXmittedOK = +- cnt[RTL8365MB_MIB_ifOutBroadcastPkts]; +- mac_stats->MulticastFramesReceivedOK = +- cnt[RTL8365MB_MIB_ifInMulticastPkts]; +- mac_stats->BroadcastFramesReceivedOK = +- cnt[RTL8365MB_MIB_ifInBroadcastPkts]; +-} +- +-static void rtl8365mb_get_ctrl_stats(struct dsa_switch *ds, int port, +- struct ethtool_eth_ctrl_stats *ctrl_stats) +-{ +- struct realtek_priv *priv = ds->priv; +- struct rtl8365mb_mib_counter *mib; +- struct rtl8365mb *mb; +- +- mb = priv->chip_data; +- mib = &rtl8365mb_mib_counters[RTL8365MB_MIB_dot3ControlInUnknownOpcodes]; +- +- mutex_lock(&mb->mib_lock); +- rtl8365mb_mib_counter_read(priv, port, mib->offset, mib->length, +- &ctrl_stats->UnsupportedOpcodesReceived); +- mutex_unlock(&mb->mib_lock); +-} +- +-static void rtl8365mb_stats_update(struct realtek_priv *priv, int port) +-{ +- u64 cnt[RTL8365MB_MIB_END] = { +- [RTL8365MB_MIB_ifOutOctets] = 1, +- [RTL8365MB_MIB_ifOutUcastPkts] = 1, +- [RTL8365MB_MIB_ifOutMulticastPkts] = 1, +- [RTL8365MB_MIB_ifOutBroadcastPkts] = 1, +- [RTL8365MB_MIB_ifOutDiscards] = 1, +- [RTL8365MB_MIB_ifInOctets] = 1, +- [RTL8365MB_MIB_ifInUcastPkts] = 1, +- [RTL8365MB_MIB_ifInMulticastPkts] = 1, +- [RTL8365MB_MIB_ifInBroadcastPkts] = 1, +- [RTL8365MB_MIB_etherStatsDropEvents] = 1, +- [RTL8365MB_MIB_etherStatsCollisions] = 1, +- [RTL8365MB_MIB_etherStatsFragments] = 1, +- [RTL8365MB_MIB_etherStatsJabbers] = 1, +- [RTL8365MB_MIB_dot3StatsFCSErrors] = 1, +- [RTL8365MB_MIB_dot3StatsLateCollisions] = 1, +- }; +- struct rtl8365mb *mb = priv->chip_data; +- struct rtnl_link_stats64 *stats; +- int ret; +- int i; +- +- stats = &mb->ports[port].stats; +- +- mutex_lock(&mb->mib_lock); +- for (i = 0; i < RTL8365MB_MIB_END; i++) { +- struct rtl8365mb_mib_counter *c = &rtl8365mb_mib_counters[i]; +- +- /* Only fetch required MIB counters (marked = 1 above) */ +- if (!cnt[i]) +- continue; +- +- ret = rtl8365mb_mib_counter_read(priv, port, c->offset, +- c->length, &cnt[i]); +- if (ret) +- break; +- } +- mutex_unlock(&mb->mib_lock); +- +- /* Don't update statistics if there was an error reading the counters */ +- if (ret) +- return; +- +- spin_lock(&mb->ports[port].stats_lock); +- +- stats->rx_packets = cnt[RTL8365MB_MIB_ifInUcastPkts] + +- cnt[RTL8365MB_MIB_ifInMulticastPkts] + +- cnt[RTL8365MB_MIB_ifInBroadcastPkts]; +- +- stats->tx_packets = cnt[RTL8365MB_MIB_ifOutUcastPkts] + +- cnt[RTL8365MB_MIB_ifOutMulticastPkts] + +- cnt[RTL8365MB_MIB_ifOutBroadcastPkts]; +- +- /* if{In,Out}Octets includes FCS - remove it */ +- stats->rx_bytes = cnt[RTL8365MB_MIB_ifInOctets] - 4 * stats->rx_packets; +- stats->tx_bytes = +- cnt[RTL8365MB_MIB_ifOutOctets] - 4 * stats->tx_packets; +- +- stats->rx_dropped = cnt[RTL8365MB_MIB_etherStatsDropEvents]; +- stats->tx_dropped = cnt[RTL8365MB_MIB_ifOutDiscards]; +- +- stats->multicast = cnt[RTL8365MB_MIB_ifInMulticastPkts]; +- stats->collisions = cnt[RTL8365MB_MIB_etherStatsCollisions]; +- +- stats->rx_length_errors = cnt[RTL8365MB_MIB_etherStatsFragments] + +- cnt[RTL8365MB_MIB_etherStatsJabbers]; +- stats->rx_crc_errors = cnt[RTL8365MB_MIB_dot3StatsFCSErrors]; +- stats->rx_errors = stats->rx_length_errors + stats->rx_crc_errors; +- +- stats->tx_aborted_errors = cnt[RTL8365MB_MIB_ifOutDiscards]; +- stats->tx_window_errors = cnt[RTL8365MB_MIB_dot3StatsLateCollisions]; +- stats->tx_errors = stats->tx_aborted_errors + stats->tx_window_errors; +- +- spin_unlock(&mb->ports[port].stats_lock); +-} +- +-static void rtl8365mb_stats_poll(struct work_struct *work) +-{ +- struct rtl8365mb_port *p = container_of(to_delayed_work(work), +- struct rtl8365mb_port, +- mib_work); +- struct realtek_priv *priv = p->priv; +- +- rtl8365mb_stats_update(priv, p->index); +- +- schedule_delayed_work(&p->mib_work, RTL8365MB_STATS_INTERVAL_JIFFIES); +-} +- +-static void rtl8365mb_get_stats64(struct dsa_switch *ds, int port, +- struct rtnl_link_stats64 *s) +-{ +- struct realtek_priv *priv = ds->priv; +- struct rtl8365mb_port *p; +- struct rtl8365mb *mb; +- +- mb = priv->chip_data; +- p = &mb->ports[port]; +- +- spin_lock(&p->stats_lock); +- memcpy(s, &p->stats, sizeof(*s)); +- spin_unlock(&p->stats_lock); +-} +- +-static void rtl8365mb_stats_setup(struct realtek_priv *priv) +-{ +- struct rtl8365mb *mb = priv->chip_data; +- struct dsa_switch *ds = &priv->ds; +- struct dsa_port *dp; +- +- /* Per-chip global mutex to protect MIB counter access, since doing +- * so requires accessing a series of registers in a particular order. +- */ +- mutex_init(&mb->mib_lock); +- +- dsa_switch_for_each_available_port(dp, ds) { +- struct rtl8365mb_port *p = &mb->ports[dp->index]; +- +- /* Per-port spinlock to protect the stats64 data */ +- spin_lock_init(&p->stats_lock); +- +- /* This work polls the MIB counters and keeps the stats64 data +- * up-to-date. +- */ +- INIT_DELAYED_WORK(&p->mib_work, rtl8365mb_stats_poll); +- } +-} +- +-static void rtl8365mb_stats_teardown(struct realtek_priv *priv) +-{ +- struct rtl8365mb *mb = priv->chip_data; +- struct dsa_switch *ds = &priv->ds; +- struct dsa_port *dp; +- +- dsa_switch_for_each_available_port(dp, ds) { +- struct rtl8365mb_port *p = &mb->ports[dp->index]; +- +- cancel_delayed_work_sync(&p->mib_work); +- } +-} +- +-static int rtl8365mb_get_and_clear_status_reg(struct realtek_priv *priv, u32 reg, +- u32 *val) +-{ +- int ret; +- +- ret = regmap_read(priv->map, reg, val); +- if (ret) +- return ret; +- +- return regmap_write(priv->map, reg, *val); +-} +- +-static irqreturn_t rtl8365mb_irq(int irq, void *data) +-{ +- struct realtek_priv *priv = data; +- unsigned long line_changes = 0; +- u32 stat; +- int line; +- int ret; +- +- ret = rtl8365mb_get_and_clear_status_reg(priv, RTL8365MB_INTR_STATUS_REG, +- &stat); +- if (ret) +- goto out_error; +- +- if (stat & RTL8365MB_INTR_LINK_CHANGE_MASK) { +- u32 linkdown_ind; +- u32 linkup_ind; +- u32 val; +- +- ret = rtl8365mb_get_and_clear_status_reg( +- priv, RTL8365MB_PORT_LINKUP_IND_REG, &val); +- if (ret) +- goto out_error; +- +- linkup_ind = FIELD_GET(RTL8365MB_PORT_LINKUP_IND_MASK, val); +- +- ret = rtl8365mb_get_and_clear_status_reg( +- priv, RTL8365MB_PORT_LINKDOWN_IND_REG, &val); +- if (ret) +- goto out_error; +- +- linkdown_ind = FIELD_GET(RTL8365MB_PORT_LINKDOWN_IND_MASK, val); +- +- line_changes = linkup_ind | linkdown_ind; +- } +- +- if (!line_changes) +- goto out_none; +- +- for_each_set_bit(line, &line_changes, priv->num_ports) { +- int child_irq = irq_find_mapping(priv->irqdomain, line); +- +- if (!child_irq) +- continue; +- +- handle_nested_irq(child_irq); +- } +- +- return IRQ_HANDLED; +- +-out_error: +- dev_err(priv->dev, "failed to read interrupt status: %pe\n", +- ERR_PTR(ret)); +- +-out_none: +- return IRQ_NONE; +-} +- +-static struct irq_chip rtl8365mb_irq_chip = { +- .name = "rtl8365mb", +- /* The hardware doesn't support masking IRQs on a per-port basis */ +-}; +- +-static int rtl8365mb_irq_map(struct irq_domain *domain, unsigned int irq, +- irq_hw_number_t hwirq) +-{ +- struct realtek_priv *priv = domain->host_data; +- struct rtl8365mb *mb = priv->chip_data; +- +- irq_set_chip_data(irq, priv); +- irq_set_chip_and_handler(irq, &rtl8365mb_irq_chip, handle_simple_irq); +- irq_set_nested_thread(irq, 1); +- irq_set_noprobe(irq); +- irq_set_parent(irq, mb->irq); +- +- return 0; +-} +- +-static void rtl8365mb_irq_unmap(struct irq_domain *d, unsigned int irq) +-{ +- irq_set_nested_thread(irq, 0); +- irq_set_chip_and_handler(irq, NULL, NULL); +- irq_set_chip_data(irq, NULL); +-} +- +-static const struct irq_domain_ops rtl8365mb_irqdomain_ops = { +- .map = rtl8365mb_irq_map, +- .unmap = rtl8365mb_irq_unmap, +- .xlate = irq_domain_xlate_onecell, +-}; +- +-static int rtl8365mb_set_irq_enable(struct realtek_priv *priv, bool enable) +-{ +- return regmap_update_bits(priv->map, RTL8365MB_INTR_CTRL_REG, +- RTL8365MB_INTR_LINK_CHANGE_MASK, +- FIELD_PREP(RTL8365MB_INTR_LINK_CHANGE_MASK, +- enable ? 1 : 0)); +-} +- +-static int rtl8365mb_irq_enable(struct realtek_priv *priv) +-{ +- return rtl8365mb_set_irq_enable(priv, true); +-} +- +-static int rtl8365mb_irq_disable(struct realtek_priv *priv) +-{ +- return rtl8365mb_set_irq_enable(priv, false); +-} +- +-static int rtl8365mb_irq_setup(struct realtek_priv *priv) +-{ +- struct rtl8365mb *mb = priv->chip_data; +- struct dsa_switch *ds = &priv->ds; +- struct device_node *intc; +- struct dsa_port *dp; +- u32 irq_trig; +- int virq; +- int irq; +- u32 val; +- int ret; +- +- intc = of_get_child_by_name(priv->dev->of_node, "interrupt-controller"); +- if (!intc) { +- dev_err(priv->dev, "missing child interrupt-controller node\n"); +- return -EINVAL; +- } +- +- /* rtl8365mb IRQs cascade off this one */ +- irq = of_irq_get(intc, 0); +- if (irq <= 0) { +- if (!irq) { +- dev_err(priv->dev, "failed to map IRQ\n"); +- ret = -EINVAL; +- } else { +- ret = dev_err_probe(priv->dev, irq, +- "failed to get parent irq\n"); +- } +- goto out_put_node; +- } +- +- /* Store the irq so that we know to map and free it during teardown */ +- mb->irq = irq; +- +- priv->irqdomain = irq_domain_add_linear(intc, priv->num_ports, +- &rtl8365mb_irqdomain_ops, priv); +- if (!priv->irqdomain) { +- dev_err(priv->dev, "failed to add irq domain\n"); +- ret = -ENOMEM; +- goto out_put_node; +- } +- +- dsa_switch_for_each_available_port(dp, ds) { +- virq = irq_create_mapping(priv->irqdomain, dp->index); +- if (!virq) { +- dev_err(priv->dev, +- "failed to create irq domain mapping\n"); +- ret = -EINVAL; +- goto out_remove_irqdomain; +- } +- +- irq_set_parent(virq, irq); +- } +- +- /* Configure chip interrupt signal polarity */ +- irq_trig = irq_get_trigger_type(irq); +- switch (irq_trig) { +- case IRQF_TRIGGER_RISING: +- case IRQF_TRIGGER_HIGH: +- val = RTL8365MB_INTR_POLARITY_HIGH; +- break; +- case IRQF_TRIGGER_FALLING: +- case IRQF_TRIGGER_LOW: +- val = RTL8365MB_INTR_POLARITY_LOW; +- break; +- default: +- dev_err(priv->dev, "unsupported irq trigger type %u\n", +- irq_trig); +- ret = -EINVAL; +- goto out_remove_irqdomain; +- } +- +- ret = regmap_update_bits(priv->map, RTL8365MB_INTR_POLARITY_REG, +- RTL8365MB_INTR_POLARITY_MASK, +- FIELD_PREP(RTL8365MB_INTR_POLARITY_MASK, val)); +- if (ret) +- goto out_remove_irqdomain; +- +- /* Disable the interrupt in case the chip has it enabled on reset */ +- ret = rtl8365mb_irq_disable(priv); +- if (ret) +- goto out_remove_irqdomain; +- +- /* Clear the interrupt status register */ +- ret = regmap_write(priv->map, RTL8365MB_INTR_STATUS_REG, +- RTL8365MB_INTR_ALL_MASK); +- if (ret) +- goto out_remove_irqdomain; +- +- ret = request_threaded_irq(irq, NULL, rtl8365mb_irq, IRQF_ONESHOT, +- "rtl8365mb", priv); +- if (ret) { +- dev_err(priv->dev, "failed to request irq: %pe\n", +- ERR_PTR(ret)); +- goto out_remove_irqdomain; +- } +- +- ret = rtl8365mb_irq_enable(priv); +- if (ret) +- goto out_free_irq; +- +- of_node_put(intc); +- +- return 0; +- +-out_free_irq: +- free_irq(mb->irq, priv); +- +-out_remove_irqdomain: +- dsa_switch_for_each_port(dp, ds) { +- virq = irq_find_mapping(priv->irqdomain, dp->index); +- +- if (virq) +- irq_dispose_mapping(virq); +- } +- +- irq_domain_remove(priv->irqdomain); +- priv->irqdomain = NULL; +- +-out_put_node: +- mb->irq = 0; +- of_node_put(intc); +- +- return ret; +-} +- +-static void rtl8365mb_irq_teardown(struct realtek_priv *priv) +-{ +- struct rtl8365mb *mb = priv->chip_data; +- struct dsa_switch *ds = &priv->ds; +- struct dsa_port *dp; +- int virq; +- +- if (mb->irq) { +- free_irq(mb->irq, priv); +- mb->irq = 0; +- } +- +- if (priv->irqdomain) { +- /* Unused ports with a linked PHY still have an active IRQ +- * mapping that must be disposed of during teardown. Loop +- * through all ports. +- */ +- dsa_switch_for_each_port(dp, ds) { +- virq = irq_find_mapping(priv->irqdomain, dp->index); +- +- if (virq) +- irq_dispose_mapping(virq); +- } +- +- irq_domain_remove(priv->irqdomain); +- priv->irqdomain = NULL; +- } +-} +- +-static int rtl8365mb_cpu_config(struct realtek_priv *priv) +-{ +- struct rtl8365mb *mb = priv->chip_data; +- struct rtl8365mb_cpu *cpu = &mb->cpu; +- u32 val; +- int ret; +- +- ret = regmap_update_bits(priv->map, RTL8365MB_CPU_PORT_MASK_REG, +- RTL8365MB_CPU_PORT_MASK_MASK, +- FIELD_PREP(RTL8365MB_CPU_PORT_MASK_MASK, +- cpu->mask)); +- if (ret) +- return ret; +- +- val = FIELD_PREP(RTL8365MB_CPU_CTRL_EN_MASK, cpu->enable ? 1 : 0) | +- FIELD_PREP(RTL8365MB_CPU_CTRL_INSERTMODE_MASK, cpu->insert) | +- FIELD_PREP(RTL8365MB_CPU_CTRL_TAG_POSITION_MASK, cpu->position) | +- FIELD_PREP(RTL8365MB_CPU_CTRL_RXBYTECOUNT_MASK, cpu->rx_length) | +- FIELD_PREP(RTL8365MB_CPU_CTRL_TAG_FORMAT_MASK, cpu->format) | +- FIELD_PREP(RTL8365MB_CPU_CTRL_TRAP_PORT_MASK, cpu->trap_port & 0x7) | +- FIELD_PREP(RTL8365MB_CPU_CTRL_TRAP_PORT_EXT_MASK, +- cpu->trap_port >> 3 & 0x1); +- ret = regmap_write(priv->map, RTL8365MB_CPU_CTRL_REG, val); +- if (ret) +- return ret; +- +- return 0; +-} +- +-static int rtl8365mb_change_tag_protocol(struct dsa_switch *ds, +- enum dsa_tag_protocol proto) +-{ +- struct realtek_priv *priv = ds->priv; +- struct rtl8365mb_cpu *cpu; +- struct rtl8365mb *mb; +- +- mb = priv->chip_data; +- cpu = &mb->cpu; +- +- switch (proto) { +- case DSA_TAG_PROTO_RTL8_4: +- cpu->format = RTL8365MB_CPU_FORMAT_8BYTES; +- cpu->position = RTL8365MB_CPU_POS_AFTER_SA; +- break; +- case DSA_TAG_PROTO_RTL8_4T: +- cpu->format = RTL8365MB_CPU_FORMAT_8BYTES; +- cpu->position = RTL8365MB_CPU_POS_BEFORE_CRC; +- break; +- /* The switch also supports a 4-byte format, similar to rtl4a but with +- * the same 0x04 8-bit version and probably 8-bit port source/dest. +- * There is no public doc about it. Not supported yet and it will probably +- * never be. +- */ +- default: +- return -EPROTONOSUPPORT; +- } +- +- return rtl8365mb_cpu_config(priv); +-} +- +-static int rtl8365mb_switch_init(struct realtek_priv *priv) +-{ +- struct rtl8365mb *mb = priv->chip_data; +- const struct rtl8365mb_chip_info *ci; +- int ret; +- int i; +- +- ci = mb->chip_info; +- +- /* Do any chip-specific init jam before getting to the common stuff */ +- if (ci->jam_table) { +- for (i = 0; i < ci->jam_size; i++) { +- ret = regmap_write(priv->map, ci->jam_table[i].reg, +- ci->jam_table[i].val); +- if (ret) +- return ret; +- } +- } +- +- /* Common init jam */ +- for (i = 0; i < ARRAY_SIZE(rtl8365mb_init_jam_common); i++) { +- ret = regmap_write(priv->map, rtl8365mb_init_jam_common[i].reg, +- rtl8365mb_init_jam_common[i].val); +- if (ret) +- return ret; +- } +- +- return 0; +-} +- +-static int rtl8365mb_reset_chip(struct realtek_priv *priv) +-{ +- u32 val; +- +- priv->write_reg_noack(priv, RTL8365MB_CHIP_RESET_REG, +- FIELD_PREP(RTL8365MB_CHIP_RESET_HW_MASK, 1)); +- +- /* Realtek documentation says the chip needs 1 second to reset. Sleep +- * for 100 ms before accessing any registers to prevent ACK timeouts. +- */ +- msleep(100); +- return regmap_read_poll_timeout(priv->map, RTL8365MB_CHIP_RESET_REG, val, +- !(val & RTL8365MB_CHIP_RESET_HW_MASK), +- 20000, 1e6); +-} +- +-static int rtl8365mb_setup(struct dsa_switch *ds) +-{ +- struct realtek_priv *priv = ds->priv; +- struct rtl8365mb_cpu *cpu; +- u32 downports_mask = 0; +- u32 upports_mask = 0; +- struct rtl8365mb *mb; +- struct dsa_port *dp; +- int ret; +- +- mb = priv->chip_data; +- cpu = &mb->cpu; +- +- ret = rtl8365mb_reset_chip(priv); +- if (ret) { +- dev_err(priv->dev, "failed to reset chip: %pe\n", +- ERR_PTR(ret)); +- goto out_error; +- } +- +- /* Configure switch to vendor-defined initial state */ +- ret = rtl8365mb_switch_init(priv); +- if (ret) { +- dev_err(priv->dev, "failed to initialize switch: %pe\n", +- ERR_PTR(ret)); +- goto out_error; +- } +- +- /* Set up cascading IRQs */ +- ret = rtl8365mb_irq_setup(priv); +- if (ret == -EPROBE_DEFER) +- return ret; +- else if (ret) +- dev_info(priv->dev, "no interrupt support\n"); +- +- dsa_switch_for_each_port(dp, ds) { +- /* Cascading (DSA links) is not supported yet. +- * Historically, the driver has always been broken +- * without a dedicated CPU port because CPU tagging +- * would be disabled, rendering the switch entirely +- * non-functional for DSA operations. +- */ +- if (dsa_port_is_dsa(dp)) { +- dev_err(priv->dev, "Cascading (DSA link) not supported\n"); +- ret = -EOPNOTSUPP; +- goto out_teardown_irq; +- } +- } +- +- /* Start with all ports blocked, including unused ports */ +- dsa_switch_for_each_port(dp, ds) { +- struct rtl8365mb_port *p = &mb->ports[dp->index]; +- +- /* Set the initial STP state of all ports to DISABLED, otherwise +- * ports will still forward frames to the CPU despite being +- * administratively down by default. +- */ +- rtl8365mb_port_stp_state_set(ds, dp->index, BR_STATE_DISABLED); +- +- /* Start with all port completely isolated */ +- ret = rtl8365mb_port_set_isolation(priv, dp->index, 0); +- if (ret) +- goto out_teardown_irq; +- +- /* Disable learning */ +- ret = rtl8365mb_port_set_learning(priv, dp->index, false); +- if (ret) +- goto out_teardown_irq; +- +- /* Set up per-port private data */ +- p->priv = priv; +- p->index = dp->index; +- +- /* Collect CPU ports. If we support cascade switches, it should +- * also include the upstream DSA ports. +- */ +- if (!dsa_port_is_cpu(dp)) +- continue; +- +- upports_mask |= BIT(dp->index); +- } +- +- /* Configure user ports */ +- dsa_switch_for_each_port(dp, ds) { +- if (!dsa_port_is_user(dp)) +- continue; +- +- /* Forward only to the CPU */ +- ret = rtl8365mb_port_set_isolation(priv, dp->index, +- upports_mask); +- if (ret) +- goto out_teardown_irq; +- +- /* If we support cascade switches, it should also include the +- * downstream DSA ports. +- */ +- downports_mask |= BIT(dp->index); +- } +- +- /* Configure CPU tagging */ +- /* If we support cascade switches, it should also include the upstream +- * DSA ports. +- */ +- dsa_switch_for_each_cpu_port(dp, ds) { +- /* Use the first CPU port as trap_port */ +- if (cpu->trap_port == RTL8365MB_MAX_NUM_PORTS) +- cpu->trap_port = dp->index; +- +- /* Forward to all user ports */ +- ret = rtl8365mb_port_set_isolation(priv, dp->index, +- downports_mask); +- if (ret) +- goto out_teardown_irq; +- } +- +- cpu->mask = upports_mask; +- cpu->enable = cpu->mask > 0; +- +- if (!cpu->enable) { +- dev_err(priv->dev, "no CPU port defined\n"); +- ret = -EINVAL; +- goto out_teardown_irq; +- } +- +- ret = rtl8365mb_cpu_config(priv); +- if (ret) +- goto out_teardown_irq; +- +- ret = rtl8365mb_port_change_mtu(ds, cpu->trap_port, ETH_DATA_LEN); +- if (ret) +- goto out_teardown_irq; +- +- ret = rtl83xx_setup_user_mdio(ds); +- if (ret) { +- dev_err(priv->dev, "could not set up MDIO bus\n"); +- goto out_teardown_irq; +- } +- +- /* Start statistics counter polling */ +- rtl8365mb_stats_setup(priv); +- +- return 0; +- +-out_teardown_irq: +- rtl8365mb_irq_teardown(priv); +- +-out_error: +- return ret; +-} +- +-static void rtl8365mb_teardown(struct dsa_switch *ds) +-{ +- struct realtek_priv *priv = ds->priv; +- +- rtl8365mb_stats_teardown(priv); +- rtl8365mb_irq_teardown(priv); +-} +- +-static int rtl8365mb_get_chip_id_and_ver(struct regmap *map, u32 *id, u32 *ver) +-{ +- int ret; +- +- /* For some reason we have to write a magic value to an arbitrary +- * register whenever accessing the chip ID/version registers. +- */ +- ret = regmap_write(map, RTL8365MB_MAGIC_REG, RTL8365MB_MAGIC_VALUE); +- if (ret) +- return ret; +- +- ret = regmap_read(map, RTL8365MB_CHIP_ID_REG, id); +- if (ret) +- return ret; +- +- ret = regmap_read(map, RTL8365MB_CHIP_VER_REG, ver); +- if (ret) +- return ret; +- +- /* Reset magic register */ +- ret = regmap_write(map, RTL8365MB_MAGIC_REG, 0); +- if (ret) +- return ret; +- +- return 0; +-} +- +-static int rtl8365mb_detect(struct realtek_priv *priv) +-{ +- struct rtl8365mb *mb = priv->chip_data; +- u32 chip_id; +- u32 chip_ver; +- int ret; +- int i; +- +- ret = rtl8365mb_get_chip_id_and_ver(priv->map, &chip_id, &chip_ver); +- if (ret) { +- dev_err(priv->dev, "failed to read chip id and version: %pe\n", +- ERR_PTR(ret)); +- return ret; +- } +- +- for (i = 0; i < ARRAY_SIZE(rtl8365mb_chip_infos); i++) { +- const struct rtl8365mb_chip_info *ci = &rtl8365mb_chip_infos[i]; +- +- if (ci->chip_id == chip_id && ci->chip_ver == chip_ver) { +- mb->chip_info = ci; +- break; +- } +- } +- +- if (!mb->chip_info) { +- dev_err(priv->dev, +- "unrecognized switch (id=0x%04x, ver=0x%04x)", chip_id, +- chip_ver); +- return -ENODEV; +- } +- +- dev_info(priv->dev, "found an %s switch\n", mb->chip_info->name); +- +- priv->num_ports = RTL8365MB_MAX_NUM_PORTS; +- mb->priv = priv; +- mb->cpu.trap_port = RTL8365MB_MAX_NUM_PORTS; +- mb->cpu.insert = RTL8365MB_CPU_INSERT_TO_ALL; +- mb->cpu.position = RTL8365MB_CPU_POS_AFTER_SA; +- mb->cpu.rx_length = RTL8365MB_CPU_RXLEN_64BYTES; +- mb->cpu.format = RTL8365MB_CPU_FORMAT_8BYTES; +- +- return 0; +-} +- +-static const struct phylink_mac_ops rtl8365mb_phylink_mac_ops = { +- .mac_config = rtl8365mb_phylink_mac_config, +- .mac_link_down = rtl8365mb_phylink_mac_link_down, +- .mac_link_up = rtl8365mb_phylink_mac_link_up, +-}; +- +-static const struct dsa_switch_ops rtl8365mb_switch_ops = { +- .get_tag_protocol = rtl8365mb_get_tag_protocol, +- .change_tag_protocol = rtl8365mb_change_tag_protocol, +- .setup = rtl8365mb_setup, +- .teardown = rtl8365mb_teardown, +- .phylink_get_caps = rtl8365mb_phylink_get_caps, +- .port_stp_state_set = rtl8365mb_port_stp_state_set, +- .get_strings = rtl8365mb_get_strings, +- .get_ethtool_stats = rtl8365mb_get_ethtool_stats, +- .get_sset_count = rtl8365mb_get_sset_count, +- .get_eth_phy_stats = rtl8365mb_get_phy_stats, +- .get_eth_mac_stats = rtl8365mb_get_mac_stats, +- .get_eth_ctrl_stats = rtl8365mb_get_ctrl_stats, +- .get_stats64 = rtl8365mb_get_stats64, +- .port_change_mtu = rtl8365mb_port_change_mtu, +- .port_max_mtu = rtl8365mb_port_max_mtu, +-}; +- +-static const struct realtek_ops rtl8365mb_ops = { +- .detect = rtl8365mb_detect, +- .phy_read = rtl8365mb_phy_read, +- .phy_write = rtl8365mb_phy_write, +-}; +- +-const struct realtek_variant rtl8365mb_variant = { +- .ds_ops = &rtl8365mb_switch_ops, +- .ops = &rtl8365mb_ops, +- .phylink_mac_ops = &rtl8365mb_phylink_mac_ops, +- .clk_delay = 10, +- .cmd_read = 0xb9, +- .cmd_write = 0xb8, +- .chip_data_sz = sizeof(struct rtl8365mb), +-}; +- +-static const struct of_device_id rtl8365mb_of_match[] = { +- { .compatible = "realtek,rtl8365mb", .data = &rtl8365mb_variant, }, +- { /* sentinel */ }, +-}; +-MODULE_DEVICE_TABLE(of, rtl8365mb_of_match); +- +-static struct platform_driver rtl8365mb_smi_driver = { +- .driver = { +- .name = "rtl8365mb-smi", +- .of_match_table = rtl8365mb_of_match, +- }, +- .probe = realtek_smi_probe, +- .remove_new = realtek_smi_remove, +- .shutdown = realtek_smi_shutdown, +-}; +- +-static struct mdio_driver rtl8365mb_mdio_driver = { +- .mdiodrv.driver = { +- .name = "rtl8365mb-mdio", +- .of_match_table = rtl8365mb_of_match, +- }, +- .probe = realtek_mdio_probe, +- .remove = realtek_mdio_remove, +- .shutdown = realtek_mdio_shutdown, +-}; +- +-static int rtl8365mb_init(void) +-{ +- int ret; +- +- ret = realtek_mdio_driver_register(&rtl8365mb_mdio_driver); +- if (ret) +- return ret; +- +- ret = realtek_smi_driver_register(&rtl8365mb_smi_driver); +- if (ret) { +- realtek_mdio_driver_unregister(&rtl8365mb_mdio_driver); +- return ret; +- } +- +- return 0; +-} +-module_init(rtl8365mb_init); +- +-static void __exit rtl8365mb_exit(void) +-{ +- realtek_smi_driver_unregister(&rtl8365mb_smi_driver); +- realtek_mdio_driver_unregister(&rtl8365mb_mdio_driver); +-} +-module_exit(rtl8365mb_exit); +- +-MODULE_AUTHOR("Alvin Å ipraga "); +-MODULE_DESCRIPTION("Driver for RTL8365MB-VC ethernet switch"); +-MODULE_LICENSE("GPL"); +-MODULE_IMPORT_NS(REALTEK_DSA); +--- /dev/null ++++ b/drivers/net/dsa/realtek/rtl8365mb_main.c +@@ -0,0 +1,2294 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* Realtek SMI subdriver for the Realtek RTL8365MB-VC ethernet switch. ++ * ++ * Copyright (C) 2021 Alvin Å ipraga ++ * Copyright (C) 2021 Michael Rasmussen ++ * ++ * The RTL8365MB-VC is a 4+1 port 10/100/1000M switch controller. It includes 4 ++ * integrated PHYs for the user facing ports, and an extension interface which ++ * can be connected to the CPU - or another PHY - via either MII, RMII, or ++ * RGMII. The switch is configured via the Realtek Simple Management Interface ++ * (SMI), which uses the MDIO/MDC lines. ++ * ++ * Below is a simplified block diagram of the chip and its relevant interfaces. ++ * ++ * .-----------------------------------. ++ * | | ++ * UTP <---------------> Giga PHY <-> PCS <-> P0 GMAC | ++ * UTP <---------------> Giga PHY <-> PCS <-> P1 GMAC | ++ * UTP <---------------> Giga PHY <-> PCS <-> P2 GMAC | ++ * UTP <---------------> Giga PHY <-> PCS <-> P3 GMAC | ++ * | | ++ * CPU/PHY <-MII/RMII/RGMII---> Extension <---> Extension | ++ * | interface 1 GMAC 1 | ++ * | | ++ * SMI driver/ <-MDC/SCL---> Management ~~~~~~~~~~~~~~ | ++ * EEPROM <-MDIO/SDA--> interface ~REALTEK ~~~~~ | ++ * | ~RTL8365MB ~~~ | ++ * | ~GXXXC TAIWAN~ | ++ * GPIO <--------------> Reset ~~~~~~~~~~~~~~ | ++ * | | ++ * Interrupt <----------> Link UP/DOWN events | ++ * controller | | ++ * '-----------------------------------' ++ * ++ * The driver uses DSA to integrate the 4 user and 1 extension ports into the ++ * kernel. Netdevices are created for the user ports, as are PHY devices for ++ * their integrated PHYs. The device tree firmware should also specify the link ++ * partner of the extension port - either via a fixed-link or other phy-handle. ++ * See the device tree bindings for more detailed information. Note that the ++ * driver has only been tested with a fixed-link, but in principle it should not ++ * matter. ++ * ++ * NOTE: Currently, only the RGMII interface is implemented in this driver. ++ * ++ * The interrupt line is asserted on link UP/DOWN events. The driver creates a ++ * custom irqchip to handle this interrupt and demultiplex the events by reading ++ * the status registers via SMI. Interrupts are then propagated to the relevant ++ * PHY device. ++ * ++ * The EEPROM contains initial register values which the chip will read over I2C ++ * upon hardware reset. It is also possible to omit the EEPROM. In both cases, ++ * the driver will manually reprogram some registers using jam tables to reach ++ * an initial state defined by the vendor driver. ++ * ++ * This Linux driver is written based on an OS-agnostic vendor driver from ++ * Realtek. The reference GPL-licensed sources can be found in the OpenWrt ++ * source tree under the name rtl8367c. The vendor driver claims to support a ++ * number of similar switch controllers from Realtek, but the only hardware we ++ * have is the RTL8365MB-VC. Moreover, there does not seem to be any chip under ++ * the name RTL8367C. Although one wishes that the 'C' stood for some kind of ++ * common hardware revision, there exist examples of chips with the suffix -VC ++ * which are explicitly not supported by the rtl8367c driver and which instead ++ * require the rtl8367d vendor driver. With all this uncertainty, the driver has ++ * been modestly named rtl8365mb. Future implementors may wish to rename things ++ * accordingly. ++ * ++ * In the same family of chips, some carry up to 8 user ports and up to 2 ++ * extension ports. Where possible this driver tries to make things generic, but ++ * more work must be done to support these configurations. According to ++ * documentation from Realtek, the family should include the following chips: ++ * ++ * - RTL8363NB ++ * - RTL8363NB-VB ++ * - RTL8363SC ++ * - RTL8363SC-VB ++ * - RTL8364NB ++ * - RTL8364NB-VB ++ * - RTL8365MB-VC ++ * - RTL8366SC ++ * - RTL8367RB-VB ++ * - RTL8367SB ++ * - RTL8367S ++ * - RTL8370MB ++ * - RTL8310SR ++ * ++ * Some of the register logic for these additional chips has been skipped over ++ * while implementing this driver. It is therefore not possible to assume that ++ * things will work out-of-the-box for other chips, and a careful review of the ++ * vendor driver may be needed to expand support. The RTL8365MB-VC seems to be ++ * one of the simpler chips. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "realtek.h" ++#include "realtek-smi.h" ++#include "realtek-mdio.h" ++#include "rtl83xx.h" ++ ++/* Family-specific data and limits */ ++#define RTL8365MB_PHYADDRMAX 7 ++#define RTL8365MB_NUM_PHYREGS 32 ++#define RTL8365MB_PHYREGMAX (RTL8365MB_NUM_PHYREGS - 1) ++#define RTL8365MB_MAX_NUM_PORTS 11 ++#define RTL8365MB_MAX_NUM_EXTINTS 3 ++#define RTL8365MB_LEARN_LIMIT_MAX 2112 ++ ++/* Chip identification registers */ ++#define RTL8365MB_CHIP_ID_REG 0x1300 ++ ++#define RTL8365MB_CHIP_VER_REG 0x1301 ++ ++#define RTL8365MB_MAGIC_REG 0x13C2 ++#define RTL8365MB_MAGIC_VALUE 0x0249 ++ ++/* Chip reset register */ ++#define RTL8365MB_CHIP_RESET_REG 0x1322 ++#define RTL8365MB_CHIP_RESET_SW_MASK 0x0002 ++#define RTL8365MB_CHIP_RESET_HW_MASK 0x0001 ++ ++/* Interrupt polarity register */ ++#define RTL8365MB_INTR_POLARITY_REG 0x1100 ++#define RTL8365MB_INTR_POLARITY_MASK 0x0001 ++#define RTL8365MB_INTR_POLARITY_HIGH 0 ++#define RTL8365MB_INTR_POLARITY_LOW 1 ++ ++/* Interrupt control/status register - enable/check specific interrupt types */ ++#define RTL8365MB_INTR_CTRL_REG 0x1101 ++#define RTL8365MB_INTR_STATUS_REG 0x1102 ++#define RTL8365MB_INTR_SLIENT_START_2_MASK 0x1000 ++#define RTL8365MB_INTR_SLIENT_START_MASK 0x0800 ++#define RTL8365MB_INTR_ACL_ACTION_MASK 0x0200 ++#define RTL8365MB_INTR_CABLE_DIAG_FIN_MASK 0x0100 ++#define RTL8365MB_INTR_INTERRUPT_8051_MASK 0x0080 ++#define RTL8365MB_INTR_LOOP_DETECTION_MASK 0x0040 ++#define RTL8365MB_INTR_GREEN_TIMER_MASK 0x0020 ++#define RTL8365MB_INTR_SPECIAL_CONGEST_MASK 0x0010 ++#define RTL8365MB_INTR_SPEED_CHANGE_MASK 0x0008 ++#define RTL8365MB_INTR_LEARN_OVER_MASK 0x0004 ++#define RTL8365MB_INTR_METER_EXCEEDED_MASK 0x0002 ++#define RTL8365MB_INTR_LINK_CHANGE_MASK 0x0001 ++#define RTL8365MB_INTR_ALL_MASK \ ++ (RTL8365MB_INTR_SLIENT_START_2_MASK | \ ++ RTL8365MB_INTR_SLIENT_START_MASK | \ ++ RTL8365MB_INTR_ACL_ACTION_MASK | \ ++ RTL8365MB_INTR_CABLE_DIAG_FIN_MASK | \ ++ RTL8365MB_INTR_INTERRUPT_8051_MASK | \ ++ RTL8365MB_INTR_LOOP_DETECTION_MASK | \ ++ RTL8365MB_INTR_GREEN_TIMER_MASK | \ ++ RTL8365MB_INTR_SPECIAL_CONGEST_MASK | \ ++ RTL8365MB_INTR_SPEED_CHANGE_MASK | \ ++ RTL8365MB_INTR_LEARN_OVER_MASK | \ ++ RTL8365MB_INTR_METER_EXCEEDED_MASK | \ ++ RTL8365MB_INTR_LINK_CHANGE_MASK) ++ ++/* Per-port interrupt type status registers */ ++#define RTL8365MB_PORT_LINKDOWN_IND_REG 0x1106 ++#define RTL8365MB_PORT_LINKDOWN_IND_MASK 0x07FF ++ ++#define RTL8365MB_PORT_LINKUP_IND_REG 0x1107 ++#define RTL8365MB_PORT_LINKUP_IND_MASK 0x07FF ++ ++/* PHY indirect access registers */ ++#define RTL8365MB_INDIRECT_ACCESS_CTRL_REG 0x1F00 ++#define RTL8365MB_INDIRECT_ACCESS_CTRL_RW_MASK 0x0002 ++#define RTL8365MB_INDIRECT_ACCESS_CTRL_RW_READ 0 ++#define RTL8365MB_INDIRECT_ACCESS_CTRL_RW_WRITE 1 ++#define RTL8365MB_INDIRECT_ACCESS_CTRL_CMD_MASK 0x0001 ++#define RTL8365MB_INDIRECT_ACCESS_CTRL_CMD_VALUE 1 ++#define RTL8365MB_INDIRECT_ACCESS_STATUS_REG 0x1F01 ++#define RTL8365MB_INDIRECT_ACCESS_ADDRESS_REG 0x1F02 ++#define RTL8365MB_INDIRECT_ACCESS_ADDRESS_OCPADR_5_1_MASK GENMASK(4, 0) ++#define RTL8365MB_INDIRECT_ACCESS_ADDRESS_PHYNUM_MASK GENMASK(7, 5) ++#define RTL8365MB_INDIRECT_ACCESS_ADDRESS_OCPADR_9_6_MASK GENMASK(11, 8) ++#define RTL8365MB_PHY_BASE 0x2000 ++#define RTL8365MB_INDIRECT_ACCESS_WRITE_DATA_REG 0x1F03 ++#define RTL8365MB_INDIRECT_ACCESS_READ_DATA_REG 0x1F04 ++ ++/* PHY OCP address prefix register */ ++#define RTL8365MB_GPHY_OCP_MSB_0_REG 0x1D15 ++#define RTL8365MB_GPHY_OCP_MSB_0_CFG_CPU_OCPADR_MASK 0x0FC0 ++#define RTL8365MB_PHY_OCP_ADDR_PREFIX_MASK 0xFC00 ++ ++/* The PHY OCP addresses of PHY registers 0~31 start here */ ++#define RTL8365MB_PHY_OCP_ADDR_PHYREG_BASE 0xA400 ++ ++/* External interface port mode values - used in DIGITAL_INTERFACE_SELECT */ ++#define RTL8365MB_EXT_PORT_MODE_DISABLE 0 ++#define RTL8365MB_EXT_PORT_MODE_RGMII 1 ++#define RTL8365MB_EXT_PORT_MODE_MII_MAC 2 ++#define RTL8365MB_EXT_PORT_MODE_MII_PHY 3 ++#define RTL8365MB_EXT_PORT_MODE_TMII_MAC 4 ++#define RTL8365MB_EXT_PORT_MODE_TMII_PHY 5 ++#define RTL8365MB_EXT_PORT_MODE_GMII 6 ++#define RTL8365MB_EXT_PORT_MODE_RMII_MAC 7 ++#define RTL8365MB_EXT_PORT_MODE_RMII_PHY 8 ++#define RTL8365MB_EXT_PORT_MODE_SGMII 9 ++#define RTL8365MB_EXT_PORT_MODE_HSGMII 10 ++#define RTL8365MB_EXT_PORT_MODE_1000X_100FX 11 ++#define RTL8365MB_EXT_PORT_MODE_1000X 12 ++#define RTL8365MB_EXT_PORT_MODE_100FX 13 ++ ++/* External interface mode configuration registers 0~1 */ ++#define RTL8365MB_DIGITAL_INTERFACE_SELECT_REG0 0x1305 /* EXT0,EXT1 */ ++#define RTL8365MB_DIGITAL_INTERFACE_SELECT_REG1 0x13C3 /* EXT2 */ ++#define RTL8365MB_DIGITAL_INTERFACE_SELECT_REG(_extint) \ ++ ((_extint) <= 1 ? RTL8365MB_DIGITAL_INTERFACE_SELECT_REG0 : \ ++ (_extint) == 2 ? RTL8365MB_DIGITAL_INTERFACE_SELECT_REG1 : \ ++ 0x0) ++#define RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_MASK(_extint) \ ++ (0xF << (((_extint) % 2) * 4)) ++#define RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_OFFSET(_extint) \ ++ (((_extint) % 2) * 4) ++ ++/* External interface RGMII TX/RX delay configuration registers 0~2 */ ++#define RTL8365MB_EXT_RGMXF_REG0 0x1306 /* EXT0 */ ++#define RTL8365MB_EXT_RGMXF_REG1 0x1307 /* EXT1 */ ++#define RTL8365MB_EXT_RGMXF_REG2 0x13C5 /* EXT2 */ ++#define RTL8365MB_EXT_RGMXF_REG(_extint) \ ++ ((_extint) == 0 ? RTL8365MB_EXT_RGMXF_REG0 : \ ++ (_extint) == 1 ? RTL8365MB_EXT_RGMXF_REG1 : \ ++ (_extint) == 2 ? RTL8365MB_EXT_RGMXF_REG2 : \ ++ 0x0) ++#define RTL8365MB_EXT_RGMXF_RXDELAY_MASK 0x0007 ++#define RTL8365MB_EXT_RGMXF_TXDELAY_MASK 0x0008 ++ ++/* External interface port speed values - used in DIGITAL_INTERFACE_FORCE */ ++#define RTL8365MB_PORT_SPEED_10M 0 ++#define RTL8365MB_PORT_SPEED_100M 1 ++#define RTL8365MB_PORT_SPEED_1000M 2 ++ ++/* External interface force configuration registers 0~2 */ ++#define RTL8365MB_DIGITAL_INTERFACE_FORCE_REG0 0x1310 /* EXT0 */ ++#define RTL8365MB_DIGITAL_INTERFACE_FORCE_REG1 0x1311 /* EXT1 */ ++#define RTL8365MB_DIGITAL_INTERFACE_FORCE_REG2 0x13C4 /* EXT2 */ ++#define RTL8365MB_DIGITAL_INTERFACE_FORCE_REG(_extint) \ ++ ((_extint) == 0 ? RTL8365MB_DIGITAL_INTERFACE_FORCE_REG0 : \ ++ (_extint) == 1 ? RTL8365MB_DIGITAL_INTERFACE_FORCE_REG1 : \ ++ (_extint) == 2 ? RTL8365MB_DIGITAL_INTERFACE_FORCE_REG2 : \ ++ 0x0) ++#define RTL8365MB_DIGITAL_INTERFACE_FORCE_EN_MASK 0x1000 ++#define RTL8365MB_DIGITAL_INTERFACE_FORCE_NWAY_MASK 0x0080 ++#define RTL8365MB_DIGITAL_INTERFACE_FORCE_TXPAUSE_MASK 0x0040 ++#define RTL8365MB_DIGITAL_INTERFACE_FORCE_RXPAUSE_MASK 0x0020 ++#define RTL8365MB_DIGITAL_INTERFACE_FORCE_LINK_MASK 0x0010 ++#define RTL8365MB_DIGITAL_INTERFACE_FORCE_DUPLEX_MASK 0x0004 ++#define RTL8365MB_DIGITAL_INTERFACE_FORCE_SPEED_MASK 0x0003 ++ ++/* CPU port mask register - controls which ports are treated as CPU ports */ ++#define RTL8365MB_CPU_PORT_MASK_REG 0x1219 ++#define RTL8365MB_CPU_PORT_MASK_MASK 0x07FF ++ ++/* CPU control register */ ++#define RTL8365MB_CPU_CTRL_REG 0x121A ++#define RTL8365MB_CPU_CTRL_TRAP_PORT_EXT_MASK 0x0400 ++#define RTL8365MB_CPU_CTRL_TAG_FORMAT_MASK 0x0200 ++#define RTL8365MB_CPU_CTRL_RXBYTECOUNT_MASK 0x0080 ++#define RTL8365MB_CPU_CTRL_TAG_POSITION_MASK 0x0040 ++#define RTL8365MB_CPU_CTRL_TRAP_PORT_MASK 0x0038 ++#define RTL8365MB_CPU_CTRL_INSERTMODE_MASK 0x0006 ++#define RTL8365MB_CPU_CTRL_EN_MASK 0x0001 ++ ++/* Maximum packet length register */ ++#define RTL8365MB_CFG0_MAX_LEN_REG 0x088C ++#define RTL8365MB_CFG0_MAX_LEN_MASK 0x3FFF ++#define RTL8365MB_CFG0_MAX_LEN_MAX 0x3FFF ++ ++/* Port learning limit registers */ ++#define RTL8365MB_LUT_PORT_LEARN_LIMIT_BASE 0x0A20 ++#define RTL8365MB_LUT_PORT_LEARN_LIMIT_REG(_physport) \ ++ (RTL8365MB_LUT_PORT_LEARN_LIMIT_BASE + (_physport)) ++ ++/* Port isolation (forwarding mask) registers */ ++#define RTL8365MB_PORT_ISOLATION_REG_BASE 0x08A2 ++#define RTL8365MB_PORT_ISOLATION_REG(_physport) \ ++ (RTL8365MB_PORT_ISOLATION_REG_BASE + (_physport)) ++#define RTL8365MB_PORT_ISOLATION_MASK 0x07FF ++ ++/* MSTP port state registers - indexed by tree instance */ ++#define RTL8365MB_MSTI_CTRL_BASE 0x0A00 ++#define RTL8365MB_MSTI_CTRL_REG(_msti, _physport) \ ++ (RTL8365MB_MSTI_CTRL_BASE + ((_msti) << 1) + ((_physport) >> 3)) ++#define RTL8365MB_MSTI_CTRL_PORT_STATE_OFFSET(_physport) ((_physport) << 1) ++#define RTL8365MB_MSTI_CTRL_PORT_STATE_MASK(_physport) \ ++ (0x3 << RTL8365MB_MSTI_CTRL_PORT_STATE_OFFSET((_physport))) ++ ++/* MIB counter value registers */ ++#define RTL8365MB_MIB_COUNTER_BASE 0x1000 ++#define RTL8365MB_MIB_COUNTER_REG(_x) (RTL8365MB_MIB_COUNTER_BASE + (_x)) ++ ++/* MIB counter address register */ ++#define RTL8365MB_MIB_ADDRESS_REG 0x1004 ++#define RTL8365MB_MIB_ADDRESS_PORT_OFFSET 0x007C ++#define RTL8365MB_MIB_ADDRESS(_p, _x) \ ++ (((RTL8365MB_MIB_ADDRESS_PORT_OFFSET) * (_p) + (_x)) >> 2) ++ ++#define RTL8365MB_MIB_CTRL0_REG 0x1005 ++#define RTL8365MB_MIB_CTRL0_RESET_MASK 0x0002 ++#define RTL8365MB_MIB_CTRL0_BUSY_MASK 0x0001 ++ ++/* The DSA callback .get_stats64 runs in atomic context, so we are not allowed ++ * to block. On the other hand, accessing MIB counters absolutely requires us to ++ * block. The solution is thus to schedule work which polls the MIB counters ++ * asynchronously and updates some private data, which the callback can then ++ * fetch atomically. Three seconds should be a good enough polling interval. ++ */ ++#define RTL8365MB_STATS_INTERVAL_JIFFIES (3 * HZ) ++ ++enum rtl8365mb_mib_counter_index { ++ RTL8365MB_MIB_ifInOctets, ++ RTL8365MB_MIB_dot3StatsFCSErrors, ++ RTL8365MB_MIB_dot3StatsSymbolErrors, ++ RTL8365MB_MIB_dot3InPauseFrames, ++ RTL8365MB_MIB_dot3ControlInUnknownOpcodes, ++ RTL8365MB_MIB_etherStatsFragments, ++ RTL8365MB_MIB_etherStatsJabbers, ++ RTL8365MB_MIB_ifInUcastPkts, ++ RTL8365MB_MIB_etherStatsDropEvents, ++ RTL8365MB_MIB_ifInMulticastPkts, ++ RTL8365MB_MIB_ifInBroadcastPkts, ++ RTL8365MB_MIB_inMldChecksumError, ++ RTL8365MB_MIB_inIgmpChecksumError, ++ RTL8365MB_MIB_inMldSpecificQuery, ++ RTL8365MB_MIB_inMldGeneralQuery, ++ RTL8365MB_MIB_inIgmpSpecificQuery, ++ RTL8365MB_MIB_inIgmpGeneralQuery, ++ RTL8365MB_MIB_inMldLeaves, ++ RTL8365MB_MIB_inIgmpLeaves, ++ RTL8365MB_MIB_etherStatsOctets, ++ RTL8365MB_MIB_etherStatsUnderSizePkts, ++ RTL8365MB_MIB_etherOversizeStats, ++ RTL8365MB_MIB_etherStatsPkts64Octets, ++ RTL8365MB_MIB_etherStatsPkts65to127Octets, ++ RTL8365MB_MIB_etherStatsPkts128to255Octets, ++ RTL8365MB_MIB_etherStatsPkts256to511Octets, ++ RTL8365MB_MIB_etherStatsPkts512to1023Octets, ++ RTL8365MB_MIB_etherStatsPkts1024to1518Octets, ++ RTL8365MB_MIB_ifOutOctets, ++ RTL8365MB_MIB_dot3StatsSingleCollisionFrames, ++ RTL8365MB_MIB_dot3StatsMultipleCollisionFrames, ++ RTL8365MB_MIB_dot3StatsDeferredTransmissions, ++ RTL8365MB_MIB_dot3StatsLateCollisions, ++ RTL8365MB_MIB_etherStatsCollisions, ++ RTL8365MB_MIB_dot3StatsExcessiveCollisions, ++ RTL8365MB_MIB_dot3OutPauseFrames, ++ RTL8365MB_MIB_ifOutDiscards, ++ RTL8365MB_MIB_dot1dTpPortInDiscards, ++ RTL8365MB_MIB_ifOutUcastPkts, ++ RTL8365MB_MIB_ifOutMulticastPkts, ++ RTL8365MB_MIB_ifOutBroadcastPkts, ++ RTL8365MB_MIB_outOampduPkts, ++ RTL8365MB_MIB_inOampduPkts, ++ RTL8365MB_MIB_inIgmpJoinsSuccess, ++ RTL8365MB_MIB_inIgmpJoinsFail, ++ RTL8365MB_MIB_inMldJoinsSuccess, ++ RTL8365MB_MIB_inMldJoinsFail, ++ RTL8365MB_MIB_inReportSuppressionDrop, ++ RTL8365MB_MIB_inLeaveSuppressionDrop, ++ RTL8365MB_MIB_outIgmpReports, ++ RTL8365MB_MIB_outIgmpLeaves, ++ RTL8365MB_MIB_outIgmpGeneralQuery, ++ RTL8365MB_MIB_outIgmpSpecificQuery, ++ RTL8365MB_MIB_outMldReports, ++ RTL8365MB_MIB_outMldLeaves, ++ RTL8365MB_MIB_outMldGeneralQuery, ++ RTL8365MB_MIB_outMldSpecificQuery, ++ RTL8365MB_MIB_inKnownMulticastPkts, ++ RTL8365MB_MIB_END, ++}; ++ ++struct rtl8365mb_mib_counter { ++ u32 offset; ++ u32 length; ++ const char *name; ++}; ++ ++#define RTL8365MB_MAKE_MIB_COUNTER(_offset, _length, _name) \ ++ [RTL8365MB_MIB_ ## _name] = { _offset, _length, #_name } ++ ++static struct rtl8365mb_mib_counter rtl8365mb_mib_counters[] = { ++ RTL8365MB_MAKE_MIB_COUNTER(0, 4, ifInOctets), ++ RTL8365MB_MAKE_MIB_COUNTER(4, 2, dot3StatsFCSErrors), ++ RTL8365MB_MAKE_MIB_COUNTER(6, 2, dot3StatsSymbolErrors), ++ RTL8365MB_MAKE_MIB_COUNTER(8, 2, dot3InPauseFrames), ++ RTL8365MB_MAKE_MIB_COUNTER(10, 2, dot3ControlInUnknownOpcodes), ++ RTL8365MB_MAKE_MIB_COUNTER(12, 2, etherStatsFragments), ++ RTL8365MB_MAKE_MIB_COUNTER(14, 2, etherStatsJabbers), ++ RTL8365MB_MAKE_MIB_COUNTER(16, 2, ifInUcastPkts), ++ RTL8365MB_MAKE_MIB_COUNTER(18, 2, etherStatsDropEvents), ++ RTL8365MB_MAKE_MIB_COUNTER(20, 2, ifInMulticastPkts), ++ RTL8365MB_MAKE_MIB_COUNTER(22, 2, ifInBroadcastPkts), ++ RTL8365MB_MAKE_MIB_COUNTER(24, 2, inMldChecksumError), ++ RTL8365MB_MAKE_MIB_COUNTER(26, 2, inIgmpChecksumError), ++ RTL8365MB_MAKE_MIB_COUNTER(28, 2, inMldSpecificQuery), ++ RTL8365MB_MAKE_MIB_COUNTER(30, 2, inMldGeneralQuery), ++ RTL8365MB_MAKE_MIB_COUNTER(32, 2, inIgmpSpecificQuery), ++ RTL8365MB_MAKE_MIB_COUNTER(34, 2, inIgmpGeneralQuery), ++ RTL8365MB_MAKE_MIB_COUNTER(36, 2, inMldLeaves), ++ RTL8365MB_MAKE_MIB_COUNTER(38, 2, inIgmpLeaves), ++ RTL8365MB_MAKE_MIB_COUNTER(40, 4, etherStatsOctets), ++ RTL8365MB_MAKE_MIB_COUNTER(44, 2, etherStatsUnderSizePkts), ++ RTL8365MB_MAKE_MIB_COUNTER(46, 2, etherOversizeStats), ++ RTL8365MB_MAKE_MIB_COUNTER(48, 2, etherStatsPkts64Octets), ++ RTL8365MB_MAKE_MIB_COUNTER(50, 2, etherStatsPkts65to127Octets), ++ RTL8365MB_MAKE_MIB_COUNTER(52, 2, etherStatsPkts128to255Octets), ++ RTL8365MB_MAKE_MIB_COUNTER(54, 2, etherStatsPkts256to511Octets), ++ RTL8365MB_MAKE_MIB_COUNTER(56, 2, etherStatsPkts512to1023Octets), ++ RTL8365MB_MAKE_MIB_COUNTER(58, 2, etherStatsPkts1024to1518Octets), ++ RTL8365MB_MAKE_MIB_COUNTER(60, 4, ifOutOctets), ++ RTL8365MB_MAKE_MIB_COUNTER(64, 2, dot3StatsSingleCollisionFrames), ++ RTL8365MB_MAKE_MIB_COUNTER(66, 2, dot3StatsMultipleCollisionFrames), ++ RTL8365MB_MAKE_MIB_COUNTER(68, 2, dot3StatsDeferredTransmissions), ++ RTL8365MB_MAKE_MIB_COUNTER(70, 2, dot3StatsLateCollisions), ++ RTL8365MB_MAKE_MIB_COUNTER(72, 2, etherStatsCollisions), ++ RTL8365MB_MAKE_MIB_COUNTER(74, 2, dot3StatsExcessiveCollisions), ++ RTL8365MB_MAKE_MIB_COUNTER(76, 2, dot3OutPauseFrames), ++ RTL8365MB_MAKE_MIB_COUNTER(78, 2, ifOutDiscards), ++ RTL8365MB_MAKE_MIB_COUNTER(80, 2, dot1dTpPortInDiscards), ++ RTL8365MB_MAKE_MIB_COUNTER(82, 2, ifOutUcastPkts), ++ RTL8365MB_MAKE_MIB_COUNTER(84, 2, ifOutMulticastPkts), ++ RTL8365MB_MAKE_MIB_COUNTER(86, 2, ifOutBroadcastPkts), ++ RTL8365MB_MAKE_MIB_COUNTER(88, 2, outOampduPkts), ++ RTL8365MB_MAKE_MIB_COUNTER(90, 2, inOampduPkts), ++ RTL8365MB_MAKE_MIB_COUNTER(92, 4, inIgmpJoinsSuccess), ++ RTL8365MB_MAKE_MIB_COUNTER(96, 2, inIgmpJoinsFail), ++ RTL8365MB_MAKE_MIB_COUNTER(98, 2, inMldJoinsSuccess), ++ RTL8365MB_MAKE_MIB_COUNTER(100, 2, inMldJoinsFail), ++ RTL8365MB_MAKE_MIB_COUNTER(102, 2, inReportSuppressionDrop), ++ RTL8365MB_MAKE_MIB_COUNTER(104, 2, inLeaveSuppressionDrop), ++ RTL8365MB_MAKE_MIB_COUNTER(106, 2, outIgmpReports), ++ RTL8365MB_MAKE_MIB_COUNTER(108, 2, outIgmpLeaves), ++ RTL8365MB_MAKE_MIB_COUNTER(110, 2, outIgmpGeneralQuery), ++ RTL8365MB_MAKE_MIB_COUNTER(112, 2, outIgmpSpecificQuery), ++ RTL8365MB_MAKE_MIB_COUNTER(114, 2, outMldReports), ++ RTL8365MB_MAKE_MIB_COUNTER(116, 2, outMldLeaves), ++ RTL8365MB_MAKE_MIB_COUNTER(118, 2, outMldGeneralQuery), ++ RTL8365MB_MAKE_MIB_COUNTER(120, 2, outMldSpecificQuery), ++ RTL8365MB_MAKE_MIB_COUNTER(122, 2, inKnownMulticastPkts), ++}; ++ ++static_assert(ARRAY_SIZE(rtl8365mb_mib_counters) == RTL8365MB_MIB_END); ++ ++struct rtl8365mb_jam_tbl_entry { ++ u16 reg; ++ u16 val; ++}; ++ ++/* Lifted from the vendor driver sources */ ++static const struct rtl8365mb_jam_tbl_entry rtl8365mb_init_jam_8365mb_vc[] = { ++ { 0x13EB, 0x15BB }, { 0x1303, 0x06D6 }, { 0x1304, 0x0700 }, ++ { 0x13E2, 0x003F }, { 0x13F9, 0x0090 }, { 0x121E, 0x03CA }, ++ { 0x1233, 0x0352 }, { 0x1237, 0x00A0 }, { 0x123A, 0x0030 }, ++ { 0x1239, 0x0084 }, { 0x0301, 0x1000 }, { 0x1349, 0x001F }, ++ { 0x18E0, 0x4004 }, { 0x122B, 0x241C }, { 0x1305, 0xC000 }, ++ { 0x13F0, 0x0000 }, ++}; ++ ++static const struct rtl8365mb_jam_tbl_entry rtl8365mb_init_jam_common[] = { ++ { 0x1200, 0x7FCB }, { 0x0884, 0x0003 }, { 0x06EB, 0x0001 }, ++ { 0x03Fa, 0x0007 }, { 0x08C8, 0x00C0 }, { 0x0A30, 0x020E }, ++ { 0x0800, 0x0000 }, { 0x0802, 0x0000 }, { 0x09DA, 0x0013 }, ++ { 0x1D32, 0x0002 }, ++}; ++ ++enum rtl8365mb_phy_interface_mode { ++ RTL8365MB_PHY_INTERFACE_MODE_INVAL = 0, ++ RTL8365MB_PHY_INTERFACE_MODE_INTERNAL = BIT(0), ++ RTL8365MB_PHY_INTERFACE_MODE_MII = BIT(1), ++ RTL8365MB_PHY_INTERFACE_MODE_TMII = BIT(2), ++ RTL8365MB_PHY_INTERFACE_MODE_RMII = BIT(3), ++ RTL8365MB_PHY_INTERFACE_MODE_RGMII = BIT(4), ++ RTL8365MB_PHY_INTERFACE_MODE_SGMII = BIT(5), ++ RTL8365MB_PHY_INTERFACE_MODE_HSGMII = BIT(6), ++}; ++ ++/** ++ * struct rtl8365mb_extint - external interface info ++ * @port: the port with an external interface ++ * @id: the external interface ID, which is either 0, 1, or 2 ++ * @supported_interfaces: a bitmask of supported PHY interface modes ++ * ++ * Represents a mapping: port -> { id, supported_interfaces }. To be embedded ++ * in &struct rtl8365mb_chip_info for every port with an external interface. ++ */ ++struct rtl8365mb_extint { ++ int port; ++ int id; ++ unsigned int supported_interfaces; ++}; ++ ++/** ++ * struct rtl8365mb_chip_info - static chip-specific info ++ * @name: human-readable chip name ++ * @chip_id: chip identifier ++ * @chip_ver: chip silicon revision ++ * @extints: available external interfaces ++ * @jam_table: chip-specific initialization jam table ++ * @jam_size: size of the chip's jam table ++ * ++ * These data are specific to a given chip in the family of switches supported ++ * by this driver. When adding support for another chip in the family, a new ++ * chip info should be added to the rtl8365mb_chip_infos array. ++ */ ++struct rtl8365mb_chip_info { ++ const char *name; ++ u32 chip_id; ++ u32 chip_ver; ++ const struct rtl8365mb_extint extints[RTL8365MB_MAX_NUM_EXTINTS]; ++ const struct rtl8365mb_jam_tbl_entry *jam_table; ++ size_t jam_size; ++}; ++ ++/* Chip info for each supported switch in the family */ ++#define PHY_INTF(_mode) (RTL8365MB_PHY_INTERFACE_MODE_ ## _mode) ++static const struct rtl8365mb_chip_info rtl8365mb_chip_infos[] = { ++ { ++ .name = "RTL8365MB-VC", ++ .chip_id = 0x6367, ++ .chip_ver = 0x0040, ++ .extints = { ++ { 6, 1, PHY_INTF(MII) | PHY_INTF(TMII) | ++ PHY_INTF(RMII) | PHY_INTF(RGMII) }, ++ }, ++ .jam_table = rtl8365mb_init_jam_8365mb_vc, ++ .jam_size = ARRAY_SIZE(rtl8365mb_init_jam_8365mb_vc), ++ }, ++ { ++ .name = "RTL8367S", ++ .chip_id = 0x6367, ++ .chip_ver = 0x00A0, ++ .extints = { ++ { 6, 1, PHY_INTF(SGMII) | PHY_INTF(HSGMII) }, ++ { 7, 2, PHY_INTF(MII) | PHY_INTF(TMII) | ++ PHY_INTF(RMII) | PHY_INTF(RGMII) }, ++ }, ++ .jam_table = rtl8365mb_init_jam_8365mb_vc, ++ .jam_size = ARRAY_SIZE(rtl8365mb_init_jam_8365mb_vc), ++ }, ++ { ++ .name = "RTL8367SB", ++ .chip_id = 0x6367, ++ .chip_ver = 0x0010, ++ .extints = { ++ { 6, 1, PHY_INTF(MII) | PHY_INTF(TMII) | ++ PHY_INTF(RMII) | PHY_INTF(RGMII) | ++ PHY_INTF(SGMII) | PHY_INTF(HSGMII) }, ++ { 7, 2, PHY_INTF(MII) | PHY_INTF(TMII) | ++ PHY_INTF(RMII) | PHY_INTF(RGMII) }, ++ }, ++ .jam_table = rtl8365mb_init_jam_8365mb_vc, ++ .jam_size = ARRAY_SIZE(rtl8365mb_init_jam_8365mb_vc), ++ }, ++ { ++ .name = "RTL8367RB-VB", ++ .chip_id = 0x6367, ++ .chip_ver = 0x0020, ++ .extints = { ++ { 6, 1, PHY_INTF(MII) | PHY_INTF(TMII) | ++ PHY_INTF(RMII) | PHY_INTF(RGMII) }, ++ { 7, 2, PHY_INTF(MII) | PHY_INTF(TMII) | ++ PHY_INTF(RMII) | PHY_INTF(RGMII) }, ++ }, ++ .jam_table = rtl8365mb_init_jam_8365mb_vc, ++ .jam_size = ARRAY_SIZE(rtl8365mb_init_jam_8365mb_vc), ++ }, ++}; ++ ++enum rtl8365mb_stp_state { ++ RTL8365MB_STP_STATE_DISABLED = 0, ++ RTL8365MB_STP_STATE_BLOCKING = 1, ++ RTL8365MB_STP_STATE_LEARNING = 2, ++ RTL8365MB_STP_STATE_FORWARDING = 3, ++}; ++ ++enum rtl8365mb_cpu_insert { ++ RTL8365MB_CPU_INSERT_TO_ALL = 0, ++ RTL8365MB_CPU_INSERT_TO_TRAPPING = 1, ++ RTL8365MB_CPU_INSERT_TO_NONE = 2, ++}; ++ ++enum rtl8365mb_cpu_position { ++ RTL8365MB_CPU_POS_AFTER_SA = 0, ++ RTL8365MB_CPU_POS_BEFORE_CRC = 1, ++}; ++ ++enum rtl8365mb_cpu_format { ++ RTL8365MB_CPU_FORMAT_8BYTES = 0, ++ RTL8365MB_CPU_FORMAT_4BYTES = 1, ++}; ++ ++enum rtl8365mb_cpu_rxlen { ++ RTL8365MB_CPU_RXLEN_72BYTES = 0, ++ RTL8365MB_CPU_RXLEN_64BYTES = 1, ++}; ++ ++/** ++ * struct rtl8365mb_cpu - CPU port configuration ++ * @enable: enable/disable hardware insertion of CPU tag in switch->CPU frames ++ * @mask: port mask of ports that parse should parse CPU tags ++ * @trap_port: forward trapped frames to this port ++ * @insert: CPU tag insertion mode in switch->CPU frames ++ * @position: position of CPU tag in frame ++ * @rx_length: minimum CPU RX length ++ * @format: CPU tag format ++ * ++ * Represents the CPU tagging and CPU port configuration of the switch. These ++ * settings are configurable at runtime. ++ */ ++struct rtl8365mb_cpu { ++ bool enable; ++ u32 mask; ++ u32 trap_port; ++ enum rtl8365mb_cpu_insert insert; ++ enum rtl8365mb_cpu_position position; ++ enum rtl8365mb_cpu_rxlen rx_length; ++ enum rtl8365mb_cpu_format format; ++}; ++ ++/** ++ * struct rtl8365mb_port - private per-port data ++ * @priv: pointer to parent realtek_priv data ++ * @index: DSA port index, same as dsa_port::index ++ * @stats: link statistics populated by rtl8365mb_stats_poll, ready for atomic ++ * access via rtl8365mb_get_stats64 ++ * @stats_lock: protect the stats structure during read/update ++ * @mib_work: delayed work for polling MIB counters ++ */ ++struct rtl8365mb_port { ++ struct realtek_priv *priv; ++ unsigned int index; ++ struct rtnl_link_stats64 stats; ++ spinlock_t stats_lock; ++ struct delayed_work mib_work; ++}; ++ ++/** ++ * struct rtl8365mb - driver private data ++ * @priv: pointer to parent realtek_priv data ++ * @irq: registered IRQ or zero ++ * @chip_info: chip-specific info about the attached switch ++ * @cpu: CPU tagging and CPU port configuration for this chip ++ * @mib_lock: prevent concurrent reads of MIB counters ++ * @ports: per-port data ++ * ++ * Private data for this driver. ++ */ ++struct rtl8365mb { ++ struct realtek_priv *priv; ++ int irq; ++ const struct rtl8365mb_chip_info *chip_info; ++ struct rtl8365mb_cpu cpu; ++ struct mutex mib_lock; ++ struct rtl8365mb_port ports[RTL8365MB_MAX_NUM_PORTS]; ++}; ++ ++static int rtl8365mb_phy_poll_busy(struct realtek_priv *priv) ++{ ++ u32 val; ++ ++ return regmap_read_poll_timeout(priv->map_nolock, ++ RTL8365MB_INDIRECT_ACCESS_STATUS_REG, ++ val, !val, 10, 100); ++} ++ ++static int rtl8365mb_phy_ocp_prepare(struct realtek_priv *priv, int phy, ++ u32 ocp_addr) ++{ ++ u32 val; ++ int ret; ++ ++ /* Set OCP prefix */ ++ val = FIELD_GET(RTL8365MB_PHY_OCP_ADDR_PREFIX_MASK, ocp_addr); ++ ret = regmap_update_bits( ++ priv->map_nolock, RTL8365MB_GPHY_OCP_MSB_0_REG, ++ RTL8365MB_GPHY_OCP_MSB_0_CFG_CPU_OCPADR_MASK, ++ FIELD_PREP(RTL8365MB_GPHY_OCP_MSB_0_CFG_CPU_OCPADR_MASK, val)); ++ if (ret) ++ return ret; ++ ++ /* Set PHY register address */ ++ val = RTL8365MB_PHY_BASE; ++ val |= FIELD_PREP(RTL8365MB_INDIRECT_ACCESS_ADDRESS_PHYNUM_MASK, phy); ++ val |= FIELD_PREP(RTL8365MB_INDIRECT_ACCESS_ADDRESS_OCPADR_5_1_MASK, ++ ocp_addr >> 1); ++ val |= FIELD_PREP(RTL8365MB_INDIRECT_ACCESS_ADDRESS_OCPADR_9_6_MASK, ++ ocp_addr >> 6); ++ ret = regmap_write(priv->map_nolock, ++ RTL8365MB_INDIRECT_ACCESS_ADDRESS_REG, val); ++ if (ret) ++ return ret; ++ ++ return 0; ++} ++ ++static int rtl8365mb_phy_ocp_read(struct realtek_priv *priv, int phy, ++ u32 ocp_addr, u16 *data) ++{ ++ u32 val; ++ int ret; ++ ++ rtl83xx_lock(priv); ++ ++ ret = rtl8365mb_phy_poll_busy(priv); ++ if (ret) ++ goto out; ++ ++ ret = rtl8365mb_phy_ocp_prepare(priv, phy, ocp_addr); ++ if (ret) ++ goto out; ++ ++ /* Execute read operation */ ++ val = FIELD_PREP(RTL8365MB_INDIRECT_ACCESS_CTRL_CMD_MASK, ++ RTL8365MB_INDIRECT_ACCESS_CTRL_CMD_VALUE) | ++ FIELD_PREP(RTL8365MB_INDIRECT_ACCESS_CTRL_RW_MASK, ++ RTL8365MB_INDIRECT_ACCESS_CTRL_RW_READ); ++ ret = regmap_write(priv->map_nolock, RTL8365MB_INDIRECT_ACCESS_CTRL_REG, ++ val); ++ if (ret) ++ goto out; ++ ++ ret = rtl8365mb_phy_poll_busy(priv); ++ if (ret) ++ goto out; ++ ++ /* Get PHY register data */ ++ ret = regmap_read(priv->map_nolock, ++ RTL8365MB_INDIRECT_ACCESS_READ_DATA_REG, &val); ++ if (ret) ++ goto out; ++ ++ *data = val & 0xFFFF; ++ ++out: ++ rtl83xx_unlock(priv); ++ ++ return ret; ++} ++ ++static int rtl8365mb_phy_ocp_write(struct realtek_priv *priv, int phy, ++ u32 ocp_addr, u16 data) ++{ ++ u32 val; ++ int ret; ++ ++ rtl83xx_lock(priv); ++ ++ ret = rtl8365mb_phy_poll_busy(priv); ++ if (ret) ++ goto out; ++ ++ ret = rtl8365mb_phy_ocp_prepare(priv, phy, ocp_addr); ++ if (ret) ++ goto out; ++ ++ /* Set PHY register data */ ++ ret = regmap_write(priv->map_nolock, ++ RTL8365MB_INDIRECT_ACCESS_WRITE_DATA_REG, data); ++ if (ret) ++ goto out; ++ ++ /* Execute write operation */ ++ val = FIELD_PREP(RTL8365MB_INDIRECT_ACCESS_CTRL_CMD_MASK, ++ RTL8365MB_INDIRECT_ACCESS_CTRL_CMD_VALUE) | ++ FIELD_PREP(RTL8365MB_INDIRECT_ACCESS_CTRL_RW_MASK, ++ RTL8365MB_INDIRECT_ACCESS_CTRL_RW_WRITE); ++ ret = regmap_write(priv->map_nolock, RTL8365MB_INDIRECT_ACCESS_CTRL_REG, ++ val); ++ if (ret) ++ goto out; ++ ++ ret = rtl8365mb_phy_poll_busy(priv); ++ if (ret) ++ goto out; ++ ++out: ++ rtl83xx_unlock(priv); ++ ++ return ret; ++} ++ ++static int rtl8365mb_phy_read(struct realtek_priv *priv, int phy, int regnum) ++{ ++ u32 ocp_addr; ++ u16 val; ++ int ret; ++ ++ if (phy > RTL8365MB_PHYADDRMAX) ++ return -EINVAL; ++ ++ if (regnum > RTL8365MB_PHYREGMAX) ++ return -EINVAL; ++ ++ ocp_addr = RTL8365MB_PHY_OCP_ADDR_PHYREG_BASE + regnum * 2; ++ ++ ret = rtl8365mb_phy_ocp_read(priv, phy, ocp_addr, &val); ++ if (ret) { ++ dev_err(priv->dev, ++ "failed to read PHY%d reg %02x @ %04x, ret %pe\n", phy, ++ regnum, ocp_addr, ERR_PTR(ret)); ++ return ret; ++ } ++ ++ dev_dbg(priv->dev, "read PHY%d register 0x%02x @ %04x, val <- %04x\n", ++ phy, regnum, ocp_addr, val); ++ ++ return val; ++} ++ ++static int rtl8365mb_phy_write(struct realtek_priv *priv, int phy, int regnum, ++ u16 val) ++{ ++ u32 ocp_addr; ++ int ret; ++ ++ if (phy > RTL8365MB_PHYADDRMAX) ++ return -EINVAL; ++ ++ if (regnum > RTL8365MB_PHYREGMAX) ++ return -EINVAL; ++ ++ ocp_addr = RTL8365MB_PHY_OCP_ADDR_PHYREG_BASE + regnum * 2; ++ ++ ret = rtl8365mb_phy_ocp_write(priv, phy, ocp_addr, val); ++ if (ret) { ++ dev_err(priv->dev, ++ "failed to write PHY%d reg %02x @ %04x, ret %pe\n", phy, ++ regnum, ocp_addr, ERR_PTR(ret)); ++ return ret; ++ } ++ ++ dev_dbg(priv->dev, "write PHY%d register 0x%02x @ %04x, val -> %04x\n", ++ phy, regnum, ocp_addr, val); ++ ++ return 0; ++} ++ ++static const struct rtl8365mb_extint * ++rtl8365mb_get_port_extint(struct realtek_priv *priv, int port) ++{ ++ struct rtl8365mb *mb = priv->chip_data; ++ int i; ++ ++ for (i = 0; i < RTL8365MB_MAX_NUM_EXTINTS; i++) { ++ const struct rtl8365mb_extint *extint = ++ &mb->chip_info->extints[i]; ++ ++ if (!extint->supported_interfaces) ++ continue; ++ ++ if (extint->port == port) ++ return extint; ++ } ++ ++ return NULL; ++} ++ ++static enum dsa_tag_protocol ++rtl8365mb_get_tag_protocol(struct dsa_switch *ds, int port, ++ enum dsa_tag_protocol mp) ++{ ++ struct realtek_priv *priv = ds->priv; ++ struct rtl8365mb_cpu *cpu; ++ struct rtl8365mb *mb; ++ ++ mb = priv->chip_data; ++ cpu = &mb->cpu; ++ ++ if (cpu->position == RTL8365MB_CPU_POS_BEFORE_CRC) ++ return DSA_TAG_PROTO_RTL8_4T; ++ ++ return DSA_TAG_PROTO_RTL8_4; ++} ++ ++static int rtl8365mb_ext_config_rgmii(struct realtek_priv *priv, int port, ++ phy_interface_t interface) ++{ ++ const struct rtl8365mb_extint *extint = ++ rtl8365mb_get_port_extint(priv, port); ++ struct dsa_switch *ds = &priv->ds; ++ struct device_node *dn; ++ struct dsa_port *dp; ++ int tx_delay = 0; ++ int rx_delay = 0; ++ u32 val; ++ int ret; ++ ++ if (!extint) ++ return -ENODEV; ++ ++ dp = dsa_to_port(ds, port); ++ dn = dp->dn; ++ ++ /* Set the RGMII TX/RX delay ++ * ++ * The Realtek vendor driver indicates the following possible ++ * configuration settings: ++ * ++ * TX delay: ++ * 0 = no delay, 1 = 2 ns delay ++ * RX delay: ++ * 0 = no delay, 7 = maximum delay ++ * Each step is approximately 0.3 ns, so the maximum delay is about ++ * 2.1 ns. ++ * ++ * The vendor driver also states that this must be configured *before* ++ * forcing the external interface into a particular mode, which is done ++ * in the rtl8365mb_phylink_mac_link_{up,down} functions. ++ * ++ * Only configure an RGMII TX (resp. RX) delay if the ++ * tx-internal-delay-ps (resp. rx-internal-delay-ps) OF property is ++ * specified. We ignore the detail of the RGMII interface mode ++ * (RGMII_{RXID, TXID, etc.}), as this is considered to be a PHY-only ++ * property. ++ */ ++ if (!of_property_read_u32(dn, "tx-internal-delay-ps", &val)) { ++ val = val / 1000; /* convert to ns */ ++ ++ if (val == 0 || val == 2) ++ tx_delay = val / 2; ++ else ++ dev_warn(priv->dev, ++ "RGMII TX delay must be 0 or 2 ns\n"); ++ } ++ ++ if (!of_property_read_u32(dn, "rx-internal-delay-ps", &val)) { ++ val = DIV_ROUND_CLOSEST(val, 300); /* convert to 0.3 ns step */ ++ ++ if (val <= 7) ++ rx_delay = val; ++ else ++ dev_warn(priv->dev, ++ "RGMII RX delay must be 0 to 2.1 ns\n"); ++ } ++ ++ ret = regmap_update_bits( ++ priv->map, RTL8365MB_EXT_RGMXF_REG(extint->id), ++ RTL8365MB_EXT_RGMXF_TXDELAY_MASK | ++ RTL8365MB_EXT_RGMXF_RXDELAY_MASK, ++ FIELD_PREP(RTL8365MB_EXT_RGMXF_TXDELAY_MASK, tx_delay) | ++ FIELD_PREP(RTL8365MB_EXT_RGMXF_RXDELAY_MASK, rx_delay)); ++ if (ret) ++ return ret; ++ ++ ret = regmap_update_bits( ++ priv->map, RTL8365MB_DIGITAL_INTERFACE_SELECT_REG(extint->id), ++ RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_MASK(extint->id), ++ RTL8365MB_EXT_PORT_MODE_RGMII ++ << RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_OFFSET( ++ extint->id)); ++ if (ret) ++ return ret; ++ ++ return 0; ++} ++ ++static int rtl8365mb_ext_config_forcemode(struct realtek_priv *priv, int port, ++ bool link, int speed, int duplex, ++ bool tx_pause, bool rx_pause) ++{ ++ const struct rtl8365mb_extint *extint = ++ rtl8365mb_get_port_extint(priv, port); ++ u32 r_tx_pause; ++ u32 r_rx_pause; ++ u32 r_duplex; ++ u32 r_speed; ++ u32 r_link; ++ int val; ++ int ret; ++ ++ if (!extint) ++ return -ENODEV; ++ ++ if (link) { ++ /* Force the link up with the desired configuration */ ++ r_link = 1; ++ r_rx_pause = rx_pause ? 1 : 0; ++ r_tx_pause = tx_pause ? 1 : 0; ++ ++ if (speed == SPEED_1000) { ++ r_speed = RTL8365MB_PORT_SPEED_1000M; ++ } else if (speed == SPEED_100) { ++ r_speed = RTL8365MB_PORT_SPEED_100M; ++ } else if (speed == SPEED_10) { ++ r_speed = RTL8365MB_PORT_SPEED_10M; ++ } else { ++ dev_err(priv->dev, "unsupported port speed %s\n", ++ phy_speed_to_str(speed)); ++ return -EINVAL; ++ } ++ ++ if (duplex == DUPLEX_FULL) { ++ r_duplex = 1; ++ } else if (duplex == DUPLEX_HALF) { ++ r_duplex = 0; ++ } else { ++ dev_err(priv->dev, "unsupported duplex %s\n", ++ phy_duplex_to_str(duplex)); ++ return -EINVAL; ++ } ++ } else { ++ /* Force the link down and reset any programmed configuration */ ++ r_link = 0; ++ r_tx_pause = 0; ++ r_rx_pause = 0; ++ r_speed = 0; ++ r_duplex = 0; ++ } ++ ++ val = FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_EN_MASK, 1) | ++ FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_TXPAUSE_MASK, ++ r_tx_pause) | ++ FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_RXPAUSE_MASK, ++ r_rx_pause) | ++ FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_LINK_MASK, r_link) | ++ FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_DUPLEX_MASK, ++ r_duplex) | ++ FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_SPEED_MASK, r_speed); ++ ret = regmap_write(priv->map, ++ RTL8365MB_DIGITAL_INTERFACE_FORCE_REG(extint->id), ++ val); ++ if (ret) ++ return ret; ++ ++ return 0; ++} ++ ++static void rtl8365mb_phylink_get_caps(struct dsa_switch *ds, int port, ++ struct phylink_config *config) ++{ ++ const struct rtl8365mb_extint *extint = ++ rtl8365mb_get_port_extint(ds->priv, port); ++ ++ config->mac_capabilities = MAC_SYM_PAUSE | MAC_ASYM_PAUSE | ++ MAC_10 | MAC_100 | MAC_1000FD; ++ ++ if (!extint) { ++ __set_bit(PHY_INTERFACE_MODE_INTERNAL, ++ config->supported_interfaces); ++ ++ /* GMII is the default interface mode for phylib, so ++ * we have to support it for ports with integrated PHY. ++ */ ++ __set_bit(PHY_INTERFACE_MODE_GMII, ++ config->supported_interfaces); ++ return; ++ } ++ ++ /* Populate according to the modes supported by _this driver_, ++ * not necessarily the modes supported by the hardware, some of ++ * which remain unimplemented. ++ */ ++ ++ if (extint->supported_interfaces & RTL8365MB_PHY_INTERFACE_MODE_RGMII) ++ phy_interface_set_rgmii(config->supported_interfaces); ++} ++ ++static void rtl8365mb_phylink_mac_config(struct phylink_config *config, ++ unsigned int mode, ++ const struct phylink_link_state *state) ++{ ++ struct dsa_port *dp = dsa_phylink_to_port(config); ++ struct realtek_priv *priv = dp->ds->priv; ++ u8 port = dp->index; ++ int ret; ++ ++ if (mode != MLO_AN_PHY && mode != MLO_AN_FIXED) { ++ dev_err(priv->dev, ++ "port %d supports only conventional PHY or fixed-link\n", ++ port); ++ return; ++ } ++ ++ if (phy_interface_mode_is_rgmii(state->interface)) { ++ ret = rtl8365mb_ext_config_rgmii(priv, port, state->interface); ++ if (ret) ++ dev_err(priv->dev, ++ "failed to configure RGMII mode on port %d: %pe\n", ++ port, ERR_PTR(ret)); ++ return; ++ } ++ ++ /* TODO: Implement MII and RMII modes, which the RTL8365MB-VC also ++ * supports ++ */ ++} ++ ++static void rtl8365mb_phylink_mac_link_down(struct phylink_config *config, ++ unsigned int mode, ++ phy_interface_t interface) ++{ ++ struct dsa_port *dp = dsa_phylink_to_port(config); ++ struct realtek_priv *priv = dp->ds->priv; ++ struct rtl8365mb_port *p; ++ struct rtl8365mb *mb; ++ u8 port = dp->index; ++ int ret; ++ ++ mb = priv->chip_data; ++ p = &mb->ports[port]; ++ cancel_delayed_work_sync(&p->mib_work); ++ ++ if (phy_interface_mode_is_rgmii(interface)) { ++ ret = rtl8365mb_ext_config_forcemode(priv, port, false, 0, 0, ++ false, false); ++ if (ret) ++ dev_err(priv->dev, ++ "failed to reset forced mode on port %d: %pe\n", ++ port, ERR_PTR(ret)); ++ ++ return; ++ } ++} ++ ++static void rtl8365mb_phylink_mac_link_up(struct phylink_config *config, ++ struct phy_device *phydev, ++ unsigned int mode, ++ phy_interface_t interface, ++ int speed, int duplex, bool tx_pause, ++ bool rx_pause) ++{ ++ struct dsa_port *dp = dsa_phylink_to_port(config); ++ struct realtek_priv *priv = dp->ds->priv; ++ struct rtl8365mb_port *p; ++ struct rtl8365mb *mb; ++ u8 port = dp->index; ++ int ret; ++ ++ mb = priv->chip_data; ++ p = &mb->ports[port]; ++ schedule_delayed_work(&p->mib_work, 0); ++ ++ if (phy_interface_mode_is_rgmii(interface)) { ++ ret = rtl8365mb_ext_config_forcemode(priv, port, true, speed, ++ duplex, tx_pause, ++ rx_pause); ++ if (ret) ++ dev_err(priv->dev, ++ "failed to force mode on port %d: %pe\n", port, ++ ERR_PTR(ret)); ++ ++ return; ++ } ++} ++ ++static int rtl8365mb_port_change_mtu(struct dsa_switch *ds, int port, ++ int new_mtu) ++{ ++ struct realtek_priv *priv = ds->priv; ++ int frame_size; ++ ++ /* When a new MTU is set, DSA always sets the CPU port's MTU to the ++ * largest MTU of the user ports. Because the switch only has a global ++ * RX length register, only allowing CPU port here is enough. ++ */ ++ if (!dsa_is_cpu_port(ds, port)) ++ return 0; ++ ++ frame_size = new_mtu + VLAN_ETH_HLEN + ETH_FCS_LEN; ++ ++ dev_dbg(priv->dev, "changing mtu to %d (frame size: %d)\n", ++ new_mtu, frame_size); ++ ++ return regmap_update_bits(priv->map, RTL8365MB_CFG0_MAX_LEN_REG, ++ RTL8365MB_CFG0_MAX_LEN_MASK, ++ FIELD_PREP(RTL8365MB_CFG0_MAX_LEN_MASK, ++ frame_size)); ++} ++ ++static int rtl8365mb_port_max_mtu(struct dsa_switch *ds, int port) ++{ ++ return RTL8365MB_CFG0_MAX_LEN_MAX - VLAN_ETH_HLEN - ETH_FCS_LEN; ++} ++ ++static void rtl8365mb_port_stp_state_set(struct dsa_switch *ds, int port, ++ u8 state) ++{ ++ struct realtek_priv *priv = ds->priv; ++ enum rtl8365mb_stp_state val; ++ int msti = 0; ++ ++ switch (state) { ++ case BR_STATE_DISABLED: ++ val = RTL8365MB_STP_STATE_DISABLED; ++ break; ++ case BR_STATE_BLOCKING: ++ case BR_STATE_LISTENING: ++ val = RTL8365MB_STP_STATE_BLOCKING; ++ break; ++ case BR_STATE_LEARNING: ++ val = RTL8365MB_STP_STATE_LEARNING; ++ break; ++ case BR_STATE_FORWARDING: ++ val = RTL8365MB_STP_STATE_FORWARDING; ++ break; ++ default: ++ dev_err(priv->dev, "invalid STP state: %u\n", state); ++ return; ++ } ++ ++ regmap_update_bits(priv->map, RTL8365MB_MSTI_CTRL_REG(msti, port), ++ RTL8365MB_MSTI_CTRL_PORT_STATE_MASK(port), ++ val << RTL8365MB_MSTI_CTRL_PORT_STATE_OFFSET(port)); ++} ++ ++static int rtl8365mb_port_set_learning(struct realtek_priv *priv, int port, ++ bool enable) ++{ ++ /* Enable/disable learning by limiting the number of L2 addresses the ++ * port can learn. Realtek documentation states that a limit of zero ++ * disables learning. When enabling learning, set it to the chip's ++ * maximum. ++ */ ++ return regmap_write(priv->map, RTL8365MB_LUT_PORT_LEARN_LIMIT_REG(port), ++ enable ? RTL8365MB_LEARN_LIMIT_MAX : 0); ++} ++ ++static int rtl8365mb_port_set_isolation(struct realtek_priv *priv, int port, ++ u32 mask) ++{ ++ return regmap_write(priv->map, RTL8365MB_PORT_ISOLATION_REG(port), mask); ++} ++ ++static int rtl8365mb_mib_counter_read(struct realtek_priv *priv, int port, ++ u32 offset, u32 length, u64 *mibvalue) ++{ ++ u64 tmpvalue = 0; ++ u32 val; ++ int ret; ++ int i; ++ ++ /* The MIB address is an SRAM address. We request a particular address ++ * and then poll the control register before reading the value from some ++ * counter registers. ++ */ ++ ret = regmap_write(priv->map, RTL8365MB_MIB_ADDRESS_REG, ++ RTL8365MB_MIB_ADDRESS(port, offset)); ++ if (ret) ++ return ret; ++ ++ /* Poll for completion */ ++ ret = regmap_read_poll_timeout(priv->map, RTL8365MB_MIB_CTRL0_REG, val, ++ !(val & RTL8365MB_MIB_CTRL0_BUSY_MASK), ++ 10, 100); ++ if (ret) ++ return ret; ++ ++ /* Presumably this indicates a MIB counter read failure */ ++ if (val & RTL8365MB_MIB_CTRL0_RESET_MASK) ++ return -EIO; ++ ++ /* There are four MIB counter registers each holding a 16 bit word of a ++ * MIB counter. Depending on the offset, we should read from the upper ++ * two or lower two registers. In case the MIB counter is 4 words, we ++ * read from all four registers. ++ */ ++ if (length == 4) ++ offset = 3; ++ else ++ offset = (offset + 1) % 4; ++ ++ /* Read the MIB counter 16 bits at a time */ ++ for (i = 0; i < length; i++) { ++ ret = regmap_read(priv->map, ++ RTL8365MB_MIB_COUNTER_REG(offset - i), &val); ++ if (ret) ++ return ret; ++ ++ tmpvalue = ((tmpvalue) << 16) | (val & 0xFFFF); ++ } ++ ++ /* Only commit the result if no error occurred */ ++ *mibvalue = tmpvalue; ++ ++ return 0; ++} ++ ++static void rtl8365mb_get_ethtool_stats(struct dsa_switch *ds, int port, u64 *data) ++{ ++ struct realtek_priv *priv = ds->priv; ++ struct rtl8365mb *mb; ++ int ret; ++ int i; ++ ++ mb = priv->chip_data; ++ ++ mutex_lock(&mb->mib_lock); ++ for (i = 0; i < RTL8365MB_MIB_END; i++) { ++ struct rtl8365mb_mib_counter *mib = &rtl8365mb_mib_counters[i]; ++ ++ ret = rtl8365mb_mib_counter_read(priv, port, mib->offset, ++ mib->length, &data[i]); ++ if (ret) { ++ dev_err(priv->dev, ++ "failed to read port %d counters: %pe\n", port, ++ ERR_PTR(ret)); ++ break; ++ } ++ } ++ mutex_unlock(&mb->mib_lock); ++} ++ ++static void rtl8365mb_get_strings(struct dsa_switch *ds, int port, u32 stringset, u8 *data) ++{ ++ int i; ++ ++ if (stringset != ETH_SS_STATS) ++ return; ++ ++ for (i = 0; i < RTL8365MB_MIB_END; i++) { ++ struct rtl8365mb_mib_counter *mib = &rtl8365mb_mib_counters[i]; ++ ethtool_puts(&data, mib->name); ++ } ++} ++ ++static int rtl8365mb_get_sset_count(struct dsa_switch *ds, int port, int sset) ++{ ++ if (sset != ETH_SS_STATS) ++ return -EOPNOTSUPP; ++ ++ return RTL8365MB_MIB_END; ++} ++ ++static void rtl8365mb_get_phy_stats(struct dsa_switch *ds, int port, ++ struct ethtool_eth_phy_stats *phy_stats) ++{ ++ struct realtek_priv *priv = ds->priv; ++ struct rtl8365mb_mib_counter *mib; ++ struct rtl8365mb *mb; ++ ++ mb = priv->chip_data; ++ mib = &rtl8365mb_mib_counters[RTL8365MB_MIB_dot3StatsSymbolErrors]; ++ ++ mutex_lock(&mb->mib_lock); ++ rtl8365mb_mib_counter_read(priv, port, mib->offset, mib->length, ++ &phy_stats->SymbolErrorDuringCarrier); ++ mutex_unlock(&mb->mib_lock); ++} ++ ++static void rtl8365mb_get_mac_stats(struct dsa_switch *ds, int port, ++ struct ethtool_eth_mac_stats *mac_stats) ++{ ++ u64 cnt[RTL8365MB_MIB_END] = { ++ [RTL8365MB_MIB_ifOutOctets] = 1, ++ [RTL8365MB_MIB_ifOutUcastPkts] = 1, ++ [RTL8365MB_MIB_ifOutMulticastPkts] = 1, ++ [RTL8365MB_MIB_ifOutBroadcastPkts] = 1, ++ [RTL8365MB_MIB_dot3OutPauseFrames] = 1, ++ [RTL8365MB_MIB_ifOutDiscards] = 1, ++ [RTL8365MB_MIB_ifInOctets] = 1, ++ [RTL8365MB_MIB_ifInUcastPkts] = 1, ++ [RTL8365MB_MIB_ifInMulticastPkts] = 1, ++ [RTL8365MB_MIB_ifInBroadcastPkts] = 1, ++ [RTL8365MB_MIB_dot3InPauseFrames] = 1, ++ [RTL8365MB_MIB_dot3StatsSingleCollisionFrames] = 1, ++ [RTL8365MB_MIB_dot3StatsMultipleCollisionFrames] = 1, ++ [RTL8365MB_MIB_dot3StatsFCSErrors] = 1, ++ [RTL8365MB_MIB_dot3StatsDeferredTransmissions] = 1, ++ [RTL8365MB_MIB_dot3StatsLateCollisions] = 1, ++ [RTL8365MB_MIB_dot3StatsExcessiveCollisions] = 1, ++ ++ }; ++ struct realtek_priv *priv = ds->priv; ++ struct rtl8365mb *mb; ++ int ret; ++ int i; ++ ++ mb = priv->chip_data; ++ ++ mutex_lock(&mb->mib_lock); ++ for (i = 0; i < RTL8365MB_MIB_END; i++) { ++ struct rtl8365mb_mib_counter *mib = &rtl8365mb_mib_counters[i]; ++ ++ /* Only fetch required MIB counters (marked = 1 above) */ ++ if (!cnt[i]) ++ continue; ++ ++ ret = rtl8365mb_mib_counter_read(priv, port, mib->offset, ++ mib->length, &cnt[i]); ++ if (ret) ++ break; ++ } ++ mutex_unlock(&mb->mib_lock); ++ ++ /* The RTL8365MB-VC exposes MIB objects, which we have to translate into ++ * IEEE 802.3 Managed Objects. This is not always completely faithful, ++ * but we try out best. See RFC 3635 for a detailed treatment of the ++ * subject. ++ */ ++ ++ mac_stats->FramesTransmittedOK = cnt[RTL8365MB_MIB_ifOutUcastPkts] + ++ cnt[RTL8365MB_MIB_ifOutMulticastPkts] + ++ cnt[RTL8365MB_MIB_ifOutBroadcastPkts] + ++ cnt[RTL8365MB_MIB_dot3OutPauseFrames] - ++ cnt[RTL8365MB_MIB_ifOutDiscards]; ++ mac_stats->SingleCollisionFrames = ++ cnt[RTL8365MB_MIB_dot3StatsSingleCollisionFrames]; ++ mac_stats->MultipleCollisionFrames = ++ cnt[RTL8365MB_MIB_dot3StatsMultipleCollisionFrames]; ++ mac_stats->FramesReceivedOK = cnt[RTL8365MB_MIB_ifInUcastPkts] + ++ cnt[RTL8365MB_MIB_ifInMulticastPkts] + ++ cnt[RTL8365MB_MIB_ifInBroadcastPkts] + ++ cnt[RTL8365MB_MIB_dot3InPauseFrames]; ++ mac_stats->FrameCheckSequenceErrors = ++ cnt[RTL8365MB_MIB_dot3StatsFCSErrors]; ++ mac_stats->OctetsTransmittedOK = cnt[RTL8365MB_MIB_ifOutOctets] - ++ 18 * mac_stats->FramesTransmittedOK; ++ mac_stats->FramesWithDeferredXmissions = ++ cnt[RTL8365MB_MIB_dot3StatsDeferredTransmissions]; ++ mac_stats->LateCollisions = cnt[RTL8365MB_MIB_dot3StatsLateCollisions]; ++ mac_stats->FramesAbortedDueToXSColls = ++ cnt[RTL8365MB_MIB_dot3StatsExcessiveCollisions]; ++ mac_stats->OctetsReceivedOK = cnt[RTL8365MB_MIB_ifInOctets] - ++ 18 * mac_stats->FramesReceivedOK; ++ mac_stats->MulticastFramesXmittedOK = ++ cnt[RTL8365MB_MIB_ifOutMulticastPkts]; ++ mac_stats->BroadcastFramesXmittedOK = ++ cnt[RTL8365MB_MIB_ifOutBroadcastPkts]; ++ mac_stats->MulticastFramesReceivedOK = ++ cnt[RTL8365MB_MIB_ifInMulticastPkts]; ++ mac_stats->BroadcastFramesReceivedOK = ++ cnt[RTL8365MB_MIB_ifInBroadcastPkts]; ++} ++ ++static void rtl8365mb_get_ctrl_stats(struct dsa_switch *ds, int port, ++ struct ethtool_eth_ctrl_stats *ctrl_stats) ++{ ++ struct realtek_priv *priv = ds->priv; ++ struct rtl8365mb_mib_counter *mib; ++ struct rtl8365mb *mb; ++ ++ mb = priv->chip_data; ++ mib = &rtl8365mb_mib_counters[RTL8365MB_MIB_dot3ControlInUnknownOpcodes]; ++ ++ mutex_lock(&mb->mib_lock); ++ rtl8365mb_mib_counter_read(priv, port, mib->offset, mib->length, ++ &ctrl_stats->UnsupportedOpcodesReceived); ++ mutex_unlock(&mb->mib_lock); ++} ++ ++static void rtl8365mb_stats_update(struct realtek_priv *priv, int port) ++{ ++ u64 cnt[RTL8365MB_MIB_END] = { ++ [RTL8365MB_MIB_ifOutOctets] = 1, ++ [RTL8365MB_MIB_ifOutUcastPkts] = 1, ++ [RTL8365MB_MIB_ifOutMulticastPkts] = 1, ++ [RTL8365MB_MIB_ifOutBroadcastPkts] = 1, ++ [RTL8365MB_MIB_ifOutDiscards] = 1, ++ [RTL8365MB_MIB_ifInOctets] = 1, ++ [RTL8365MB_MIB_ifInUcastPkts] = 1, ++ [RTL8365MB_MIB_ifInMulticastPkts] = 1, ++ [RTL8365MB_MIB_ifInBroadcastPkts] = 1, ++ [RTL8365MB_MIB_etherStatsDropEvents] = 1, ++ [RTL8365MB_MIB_etherStatsCollisions] = 1, ++ [RTL8365MB_MIB_etherStatsFragments] = 1, ++ [RTL8365MB_MIB_etherStatsJabbers] = 1, ++ [RTL8365MB_MIB_dot3StatsFCSErrors] = 1, ++ [RTL8365MB_MIB_dot3StatsLateCollisions] = 1, ++ }; ++ struct rtl8365mb *mb = priv->chip_data; ++ struct rtnl_link_stats64 *stats; ++ int ret; ++ int i; ++ ++ stats = &mb->ports[port].stats; ++ ++ mutex_lock(&mb->mib_lock); ++ for (i = 0; i < RTL8365MB_MIB_END; i++) { ++ struct rtl8365mb_mib_counter *c = &rtl8365mb_mib_counters[i]; ++ ++ /* Only fetch required MIB counters (marked = 1 above) */ ++ if (!cnt[i]) ++ continue; ++ ++ ret = rtl8365mb_mib_counter_read(priv, port, c->offset, ++ c->length, &cnt[i]); ++ if (ret) ++ break; ++ } ++ mutex_unlock(&mb->mib_lock); ++ ++ /* Don't update statistics if there was an error reading the counters */ ++ if (ret) ++ return; ++ ++ spin_lock(&mb->ports[port].stats_lock); ++ ++ stats->rx_packets = cnt[RTL8365MB_MIB_ifInUcastPkts] + ++ cnt[RTL8365MB_MIB_ifInMulticastPkts] + ++ cnt[RTL8365MB_MIB_ifInBroadcastPkts]; ++ ++ stats->tx_packets = cnt[RTL8365MB_MIB_ifOutUcastPkts] + ++ cnt[RTL8365MB_MIB_ifOutMulticastPkts] + ++ cnt[RTL8365MB_MIB_ifOutBroadcastPkts]; ++ ++ /* if{In,Out}Octets includes FCS - remove it */ ++ stats->rx_bytes = cnt[RTL8365MB_MIB_ifInOctets] - 4 * stats->rx_packets; ++ stats->tx_bytes = ++ cnt[RTL8365MB_MIB_ifOutOctets] - 4 * stats->tx_packets; ++ ++ stats->rx_dropped = cnt[RTL8365MB_MIB_etherStatsDropEvents]; ++ stats->tx_dropped = cnt[RTL8365MB_MIB_ifOutDiscards]; ++ ++ stats->multicast = cnt[RTL8365MB_MIB_ifInMulticastPkts]; ++ stats->collisions = cnt[RTL8365MB_MIB_etherStatsCollisions]; ++ ++ stats->rx_length_errors = cnt[RTL8365MB_MIB_etherStatsFragments] + ++ cnt[RTL8365MB_MIB_etherStatsJabbers]; ++ stats->rx_crc_errors = cnt[RTL8365MB_MIB_dot3StatsFCSErrors]; ++ stats->rx_errors = stats->rx_length_errors + stats->rx_crc_errors; ++ ++ stats->tx_aborted_errors = cnt[RTL8365MB_MIB_ifOutDiscards]; ++ stats->tx_window_errors = cnt[RTL8365MB_MIB_dot3StatsLateCollisions]; ++ stats->tx_errors = stats->tx_aborted_errors + stats->tx_window_errors; ++ ++ spin_unlock(&mb->ports[port].stats_lock); ++} ++ ++static void rtl8365mb_stats_poll(struct work_struct *work) ++{ ++ struct rtl8365mb_port *p = container_of(to_delayed_work(work), ++ struct rtl8365mb_port, ++ mib_work); ++ struct realtek_priv *priv = p->priv; ++ ++ rtl8365mb_stats_update(priv, p->index); ++ ++ schedule_delayed_work(&p->mib_work, RTL8365MB_STATS_INTERVAL_JIFFIES); ++} ++ ++static void rtl8365mb_get_stats64(struct dsa_switch *ds, int port, ++ struct rtnl_link_stats64 *s) ++{ ++ struct realtek_priv *priv = ds->priv; ++ struct rtl8365mb_port *p; ++ struct rtl8365mb *mb; ++ ++ mb = priv->chip_data; ++ p = &mb->ports[port]; ++ ++ spin_lock(&p->stats_lock); ++ memcpy(s, &p->stats, sizeof(*s)); ++ spin_unlock(&p->stats_lock); ++} ++ ++static void rtl8365mb_stats_setup(struct realtek_priv *priv) ++{ ++ struct rtl8365mb *mb = priv->chip_data; ++ struct dsa_switch *ds = &priv->ds; ++ struct dsa_port *dp; ++ ++ /* Per-chip global mutex to protect MIB counter access, since doing ++ * so requires accessing a series of registers in a particular order. ++ */ ++ mutex_init(&mb->mib_lock); ++ ++ dsa_switch_for_each_available_port(dp, ds) { ++ struct rtl8365mb_port *p = &mb->ports[dp->index]; ++ ++ /* Per-port spinlock to protect the stats64 data */ ++ spin_lock_init(&p->stats_lock); ++ ++ /* This work polls the MIB counters and keeps the stats64 data ++ * up-to-date. ++ */ ++ INIT_DELAYED_WORK(&p->mib_work, rtl8365mb_stats_poll); ++ } ++} ++ ++static void rtl8365mb_stats_teardown(struct realtek_priv *priv) ++{ ++ struct rtl8365mb *mb = priv->chip_data; ++ struct dsa_switch *ds = &priv->ds; ++ struct dsa_port *dp; ++ ++ dsa_switch_for_each_available_port(dp, ds) { ++ struct rtl8365mb_port *p = &mb->ports[dp->index]; ++ ++ cancel_delayed_work_sync(&p->mib_work); ++ } ++} ++ ++static int rtl8365mb_get_and_clear_status_reg(struct realtek_priv *priv, u32 reg, ++ u32 *val) ++{ ++ int ret; ++ ++ ret = regmap_read(priv->map, reg, val); ++ if (ret) ++ return ret; ++ ++ return regmap_write(priv->map, reg, *val); ++} ++ ++static irqreturn_t rtl8365mb_irq(int irq, void *data) ++{ ++ struct realtek_priv *priv = data; ++ unsigned long line_changes = 0; ++ u32 stat; ++ int line; ++ int ret; ++ ++ ret = rtl8365mb_get_and_clear_status_reg(priv, RTL8365MB_INTR_STATUS_REG, ++ &stat); ++ if (ret) ++ goto out_error; ++ ++ if (stat & RTL8365MB_INTR_LINK_CHANGE_MASK) { ++ u32 linkdown_ind; ++ u32 linkup_ind; ++ u32 val; ++ ++ ret = rtl8365mb_get_and_clear_status_reg( ++ priv, RTL8365MB_PORT_LINKUP_IND_REG, &val); ++ if (ret) ++ goto out_error; ++ ++ linkup_ind = FIELD_GET(RTL8365MB_PORT_LINKUP_IND_MASK, val); ++ ++ ret = rtl8365mb_get_and_clear_status_reg( ++ priv, RTL8365MB_PORT_LINKDOWN_IND_REG, &val); ++ if (ret) ++ goto out_error; ++ ++ linkdown_ind = FIELD_GET(RTL8365MB_PORT_LINKDOWN_IND_MASK, val); ++ ++ line_changes = linkup_ind | linkdown_ind; ++ } ++ ++ if (!line_changes) ++ goto out_none; ++ ++ for_each_set_bit(line, &line_changes, priv->num_ports) { ++ int child_irq = irq_find_mapping(priv->irqdomain, line); ++ ++ if (!child_irq) ++ continue; ++ ++ handle_nested_irq(child_irq); ++ } ++ ++ return IRQ_HANDLED; ++ ++out_error: ++ dev_err(priv->dev, "failed to read interrupt status: %pe\n", ++ ERR_PTR(ret)); ++ ++out_none: ++ return IRQ_NONE; ++} ++ ++static struct irq_chip rtl8365mb_irq_chip = { ++ .name = "rtl8365mb", ++ /* The hardware doesn't support masking IRQs on a per-port basis */ ++}; ++ ++static int rtl8365mb_irq_map(struct irq_domain *domain, unsigned int irq, ++ irq_hw_number_t hwirq) ++{ ++ struct realtek_priv *priv = domain->host_data; ++ struct rtl8365mb *mb = priv->chip_data; ++ ++ irq_set_chip_data(irq, priv); ++ irq_set_chip_and_handler(irq, &rtl8365mb_irq_chip, handle_simple_irq); ++ irq_set_nested_thread(irq, 1); ++ irq_set_noprobe(irq); ++ irq_set_parent(irq, mb->irq); ++ ++ return 0; ++} ++ ++static void rtl8365mb_irq_unmap(struct irq_domain *d, unsigned int irq) ++{ ++ irq_set_nested_thread(irq, 0); ++ irq_set_chip_and_handler(irq, NULL, NULL); ++ irq_set_chip_data(irq, NULL); ++} ++ ++static const struct irq_domain_ops rtl8365mb_irqdomain_ops = { ++ .map = rtl8365mb_irq_map, ++ .unmap = rtl8365mb_irq_unmap, ++ .xlate = irq_domain_xlate_onecell, ++}; ++ ++static int rtl8365mb_set_irq_enable(struct realtek_priv *priv, bool enable) ++{ ++ return regmap_update_bits(priv->map, RTL8365MB_INTR_CTRL_REG, ++ RTL8365MB_INTR_LINK_CHANGE_MASK, ++ FIELD_PREP(RTL8365MB_INTR_LINK_CHANGE_MASK, ++ enable ? 1 : 0)); ++} ++ ++static int rtl8365mb_irq_enable(struct realtek_priv *priv) ++{ ++ return rtl8365mb_set_irq_enable(priv, true); ++} ++ ++static int rtl8365mb_irq_disable(struct realtek_priv *priv) ++{ ++ return rtl8365mb_set_irq_enable(priv, false); ++} ++ ++static int rtl8365mb_irq_setup(struct realtek_priv *priv) ++{ ++ struct rtl8365mb *mb = priv->chip_data; ++ struct dsa_switch *ds = &priv->ds; ++ struct device_node *intc; ++ struct dsa_port *dp; ++ u32 irq_trig; ++ int virq; ++ int irq; ++ u32 val; ++ int ret; ++ ++ intc = of_get_child_by_name(priv->dev->of_node, "interrupt-controller"); ++ if (!intc) { ++ dev_err(priv->dev, "missing child interrupt-controller node\n"); ++ return -EINVAL; ++ } ++ ++ /* rtl8365mb IRQs cascade off this one */ ++ irq = of_irq_get(intc, 0); ++ if (irq <= 0) { ++ if (!irq) { ++ dev_err(priv->dev, "failed to map IRQ\n"); ++ ret = -EINVAL; ++ } else { ++ ret = dev_err_probe(priv->dev, irq, ++ "failed to get parent irq\n"); ++ } ++ goto out_put_node; ++ } ++ ++ /* Store the irq so that we know to map and free it during teardown */ ++ mb->irq = irq; ++ ++ priv->irqdomain = irq_domain_add_linear(intc, priv->num_ports, ++ &rtl8365mb_irqdomain_ops, priv); ++ if (!priv->irqdomain) { ++ dev_err(priv->dev, "failed to add irq domain\n"); ++ ret = -ENOMEM; ++ goto out_put_node; ++ } ++ ++ dsa_switch_for_each_available_port(dp, ds) { ++ virq = irq_create_mapping(priv->irqdomain, dp->index); ++ if (!virq) { ++ dev_err(priv->dev, ++ "failed to create irq domain mapping\n"); ++ ret = -EINVAL; ++ goto out_remove_irqdomain; ++ } ++ ++ irq_set_parent(virq, irq); ++ } ++ ++ /* Configure chip interrupt signal polarity */ ++ irq_trig = irq_get_trigger_type(irq); ++ switch (irq_trig) { ++ case IRQF_TRIGGER_RISING: ++ case IRQF_TRIGGER_HIGH: ++ val = RTL8365MB_INTR_POLARITY_HIGH; ++ break; ++ case IRQF_TRIGGER_FALLING: ++ case IRQF_TRIGGER_LOW: ++ val = RTL8365MB_INTR_POLARITY_LOW; ++ break; ++ default: ++ dev_err(priv->dev, "unsupported irq trigger type %u\n", ++ irq_trig); ++ ret = -EINVAL; ++ goto out_remove_irqdomain; ++ } ++ ++ ret = regmap_update_bits(priv->map, RTL8365MB_INTR_POLARITY_REG, ++ RTL8365MB_INTR_POLARITY_MASK, ++ FIELD_PREP(RTL8365MB_INTR_POLARITY_MASK, val)); ++ if (ret) ++ goto out_remove_irqdomain; ++ ++ /* Disable the interrupt in case the chip has it enabled on reset */ ++ ret = rtl8365mb_irq_disable(priv); ++ if (ret) ++ goto out_remove_irqdomain; ++ ++ /* Clear the interrupt status register */ ++ ret = regmap_write(priv->map, RTL8365MB_INTR_STATUS_REG, ++ RTL8365MB_INTR_ALL_MASK); ++ if (ret) ++ goto out_remove_irqdomain; ++ ++ ret = request_threaded_irq(irq, NULL, rtl8365mb_irq, IRQF_ONESHOT, ++ "rtl8365mb", priv); ++ if (ret) { ++ dev_err(priv->dev, "failed to request irq: %pe\n", ++ ERR_PTR(ret)); ++ goto out_remove_irqdomain; ++ } ++ ++ ret = rtl8365mb_irq_enable(priv); ++ if (ret) ++ goto out_free_irq; ++ ++ of_node_put(intc); ++ ++ return 0; ++ ++out_free_irq: ++ free_irq(mb->irq, priv); ++ ++out_remove_irqdomain: ++ dsa_switch_for_each_port(dp, ds) { ++ virq = irq_find_mapping(priv->irqdomain, dp->index); ++ ++ if (virq) ++ irq_dispose_mapping(virq); ++ } ++ ++ irq_domain_remove(priv->irqdomain); ++ priv->irqdomain = NULL; ++ ++out_put_node: ++ mb->irq = 0; ++ of_node_put(intc); ++ ++ return ret; ++} ++ ++static void rtl8365mb_irq_teardown(struct realtek_priv *priv) ++{ ++ struct rtl8365mb *mb = priv->chip_data; ++ struct dsa_switch *ds = &priv->ds; ++ struct dsa_port *dp; ++ int virq; ++ ++ if (mb->irq) { ++ free_irq(mb->irq, priv); ++ mb->irq = 0; ++ } ++ ++ if (priv->irqdomain) { ++ /* Unused ports with a linked PHY still have an active IRQ ++ * mapping that must be disposed of during teardown. Loop ++ * through all ports. ++ */ ++ dsa_switch_for_each_port(dp, ds) { ++ virq = irq_find_mapping(priv->irqdomain, dp->index); ++ ++ if (virq) ++ irq_dispose_mapping(virq); ++ } ++ ++ irq_domain_remove(priv->irqdomain); ++ priv->irqdomain = NULL; ++ } ++} ++ ++static int rtl8365mb_cpu_config(struct realtek_priv *priv) ++{ ++ struct rtl8365mb *mb = priv->chip_data; ++ struct rtl8365mb_cpu *cpu = &mb->cpu; ++ u32 val; ++ int ret; ++ ++ ret = regmap_update_bits(priv->map, RTL8365MB_CPU_PORT_MASK_REG, ++ RTL8365MB_CPU_PORT_MASK_MASK, ++ FIELD_PREP(RTL8365MB_CPU_PORT_MASK_MASK, ++ cpu->mask)); ++ if (ret) ++ return ret; ++ ++ val = FIELD_PREP(RTL8365MB_CPU_CTRL_EN_MASK, cpu->enable ? 1 : 0) | ++ FIELD_PREP(RTL8365MB_CPU_CTRL_INSERTMODE_MASK, cpu->insert) | ++ FIELD_PREP(RTL8365MB_CPU_CTRL_TAG_POSITION_MASK, cpu->position) | ++ FIELD_PREP(RTL8365MB_CPU_CTRL_RXBYTECOUNT_MASK, cpu->rx_length) | ++ FIELD_PREP(RTL8365MB_CPU_CTRL_TAG_FORMAT_MASK, cpu->format) | ++ FIELD_PREP(RTL8365MB_CPU_CTRL_TRAP_PORT_MASK, cpu->trap_port & 0x7) | ++ FIELD_PREP(RTL8365MB_CPU_CTRL_TRAP_PORT_EXT_MASK, ++ cpu->trap_port >> 3 & 0x1); ++ ret = regmap_write(priv->map, RTL8365MB_CPU_CTRL_REG, val); ++ if (ret) ++ return ret; ++ ++ return 0; ++} ++ ++static int rtl8365mb_change_tag_protocol(struct dsa_switch *ds, ++ enum dsa_tag_protocol proto) ++{ ++ struct realtek_priv *priv = ds->priv; ++ struct rtl8365mb_cpu *cpu; ++ struct rtl8365mb *mb; ++ ++ mb = priv->chip_data; ++ cpu = &mb->cpu; ++ ++ switch (proto) { ++ case DSA_TAG_PROTO_RTL8_4: ++ cpu->format = RTL8365MB_CPU_FORMAT_8BYTES; ++ cpu->position = RTL8365MB_CPU_POS_AFTER_SA; ++ break; ++ case DSA_TAG_PROTO_RTL8_4T: ++ cpu->format = RTL8365MB_CPU_FORMAT_8BYTES; ++ cpu->position = RTL8365MB_CPU_POS_BEFORE_CRC; ++ break; ++ /* The switch also supports a 4-byte format, similar to rtl4a but with ++ * the same 0x04 8-bit version and probably 8-bit port source/dest. ++ * There is no public doc about it. Not supported yet and it will probably ++ * never be. ++ */ ++ default: ++ return -EPROTONOSUPPORT; ++ } ++ ++ return rtl8365mb_cpu_config(priv); ++} ++ ++static int rtl8365mb_switch_init(struct realtek_priv *priv) ++{ ++ struct rtl8365mb *mb = priv->chip_data; ++ const struct rtl8365mb_chip_info *ci; ++ int ret; ++ int i; ++ ++ ci = mb->chip_info; ++ ++ /* Do any chip-specific init jam before getting to the common stuff */ ++ if (ci->jam_table) { ++ for (i = 0; i < ci->jam_size; i++) { ++ ret = regmap_write(priv->map, ci->jam_table[i].reg, ++ ci->jam_table[i].val); ++ if (ret) ++ return ret; ++ } ++ } ++ ++ /* Common init jam */ ++ for (i = 0; i < ARRAY_SIZE(rtl8365mb_init_jam_common); i++) { ++ ret = regmap_write(priv->map, rtl8365mb_init_jam_common[i].reg, ++ rtl8365mb_init_jam_common[i].val); ++ if (ret) ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int rtl8365mb_reset_chip(struct realtek_priv *priv) ++{ ++ u32 val; ++ ++ priv->write_reg_noack(priv, RTL8365MB_CHIP_RESET_REG, ++ FIELD_PREP(RTL8365MB_CHIP_RESET_HW_MASK, 1)); ++ ++ /* Realtek documentation says the chip needs 1 second to reset. Sleep ++ * for 100 ms before accessing any registers to prevent ACK timeouts. ++ */ ++ msleep(100); ++ return regmap_read_poll_timeout(priv->map, RTL8365MB_CHIP_RESET_REG, val, ++ !(val & RTL8365MB_CHIP_RESET_HW_MASK), ++ 20000, 1e6); ++} ++ ++static int rtl8365mb_setup(struct dsa_switch *ds) ++{ ++ struct realtek_priv *priv = ds->priv; ++ struct rtl8365mb_cpu *cpu; ++ u32 downports_mask = 0; ++ u32 upports_mask = 0; ++ struct rtl8365mb *mb; ++ struct dsa_port *dp; ++ int ret; ++ ++ mb = priv->chip_data; ++ cpu = &mb->cpu; ++ ++ ret = rtl8365mb_reset_chip(priv); ++ if (ret) { ++ dev_err(priv->dev, "failed to reset chip: %pe\n", ++ ERR_PTR(ret)); ++ goto out_error; ++ } ++ ++ /* Configure switch to vendor-defined initial state */ ++ ret = rtl8365mb_switch_init(priv); ++ if (ret) { ++ dev_err(priv->dev, "failed to initialize switch: %pe\n", ++ ERR_PTR(ret)); ++ goto out_error; ++ } ++ ++ /* Set up cascading IRQs */ ++ ret = rtl8365mb_irq_setup(priv); ++ if (ret == -EPROBE_DEFER) ++ return ret; ++ else if (ret) ++ dev_info(priv->dev, "no interrupt support\n"); ++ ++ dsa_switch_for_each_port(dp, ds) { ++ /* Cascading (DSA links) is not supported yet. ++ * Historically, the driver has always been broken ++ * without a dedicated CPU port because CPU tagging ++ * would be disabled, rendering the switch entirely ++ * non-functional for DSA operations. ++ */ ++ if (dsa_port_is_dsa(dp)) { ++ dev_err(priv->dev, "Cascading (DSA link) not supported\n"); ++ ret = -EOPNOTSUPP; ++ goto out_teardown_irq; ++ } ++ } ++ ++ /* Start with all ports blocked, including unused ports */ ++ dsa_switch_for_each_port(dp, ds) { ++ struct rtl8365mb_port *p = &mb->ports[dp->index]; ++ ++ /* Set the initial STP state of all ports to DISABLED, otherwise ++ * ports will still forward frames to the CPU despite being ++ * administratively down by default. ++ */ ++ rtl8365mb_port_stp_state_set(ds, dp->index, BR_STATE_DISABLED); ++ ++ /* Start with all port completely isolated */ ++ ret = rtl8365mb_port_set_isolation(priv, dp->index, 0); ++ if (ret) ++ goto out_teardown_irq; ++ ++ /* Disable learning */ ++ ret = rtl8365mb_port_set_learning(priv, dp->index, false); ++ if (ret) ++ goto out_teardown_irq; ++ ++ /* Set up per-port private data */ ++ p->priv = priv; ++ p->index = dp->index; ++ ++ /* Collect CPU ports. If we support cascade switches, it should ++ * also include the upstream DSA ports. ++ */ ++ if (!dsa_port_is_cpu(dp)) ++ continue; ++ ++ upports_mask |= BIT(dp->index); ++ } ++ ++ /* Configure user ports */ ++ dsa_switch_for_each_port(dp, ds) { ++ if (!dsa_port_is_user(dp)) ++ continue; ++ ++ /* Forward only to the CPU */ ++ ret = rtl8365mb_port_set_isolation(priv, dp->index, ++ upports_mask); ++ if (ret) ++ goto out_teardown_irq; ++ ++ /* If we support cascade switches, it should also include the ++ * downstream DSA ports. ++ */ ++ downports_mask |= BIT(dp->index); ++ } ++ ++ /* Configure CPU tagging */ ++ /* If we support cascade switches, it should also include the upstream ++ * DSA ports. ++ */ ++ dsa_switch_for_each_cpu_port(dp, ds) { ++ /* Use the first CPU port as trap_port */ ++ if (cpu->trap_port == RTL8365MB_MAX_NUM_PORTS) ++ cpu->trap_port = dp->index; ++ ++ /* Forward to all user ports */ ++ ret = rtl8365mb_port_set_isolation(priv, dp->index, ++ downports_mask); ++ if (ret) ++ goto out_teardown_irq; ++ } ++ ++ cpu->mask = upports_mask; ++ cpu->enable = cpu->mask > 0; ++ ++ if (!cpu->enable) { ++ dev_err(priv->dev, "no CPU port defined\n"); ++ ret = -EINVAL; ++ goto out_teardown_irq; ++ } ++ ++ ret = rtl8365mb_cpu_config(priv); ++ if (ret) ++ goto out_teardown_irq; ++ ++ ret = rtl8365mb_port_change_mtu(ds, cpu->trap_port, ETH_DATA_LEN); ++ if (ret) ++ goto out_teardown_irq; ++ ++ ret = rtl83xx_setup_user_mdio(ds); ++ if (ret) { ++ dev_err(priv->dev, "could not set up MDIO bus\n"); ++ goto out_teardown_irq; ++ } ++ ++ /* Start statistics counter polling */ ++ rtl8365mb_stats_setup(priv); ++ ++ return 0; ++ ++out_teardown_irq: ++ rtl8365mb_irq_teardown(priv); ++ ++out_error: ++ return ret; ++} ++ ++static void rtl8365mb_teardown(struct dsa_switch *ds) ++{ ++ struct realtek_priv *priv = ds->priv; ++ ++ rtl8365mb_stats_teardown(priv); ++ rtl8365mb_irq_teardown(priv); ++} ++ ++static int rtl8365mb_get_chip_id_and_ver(struct regmap *map, u32 *id, u32 *ver) ++{ ++ int ret; ++ ++ /* For some reason we have to write a magic value to an arbitrary ++ * register whenever accessing the chip ID/version registers. ++ */ ++ ret = regmap_write(map, RTL8365MB_MAGIC_REG, RTL8365MB_MAGIC_VALUE); ++ if (ret) ++ return ret; ++ ++ ret = regmap_read(map, RTL8365MB_CHIP_ID_REG, id); ++ if (ret) ++ return ret; ++ ++ ret = regmap_read(map, RTL8365MB_CHIP_VER_REG, ver); ++ if (ret) ++ return ret; ++ ++ /* Reset magic register */ ++ ret = regmap_write(map, RTL8365MB_MAGIC_REG, 0); ++ if (ret) ++ return ret; ++ ++ return 0; ++} ++ ++static int rtl8365mb_detect(struct realtek_priv *priv) ++{ ++ struct rtl8365mb *mb = priv->chip_data; ++ u32 chip_id; ++ u32 chip_ver; ++ int ret; ++ int i; ++ ++ ret = rtl8365mb_get_chip_id_and_ver(priv->map, &chip_id, &chip_ver); ++ if (ret) { ++ dev_err(priv->dev, "failed to read chip id and version: %pe\n", ++ ERR_PTR(ret)); ++ return ret; ++ } ++ ++ for (i = 0; i < ARRAY_SIZE(rtl8365mb_chip_infos); i++) { ++ const struct rtl8365mb_chip_info *ci = &rtl8365mb_chip_infos[i]; ++ ++ if (ci->chip_id == chip_id && ci->chip_ver == chip_ver) { ++ mb->chip_info = ci; ++ break; ++ } ++ } ++ ++ if (!mb->chip_info) { ++ dev_err(priv->dev, ++ "unrecognized switch (id=0x%04x, ver=0x%04x)", chip_id, ++ chip_ver); ++ return -ENODEV; ++ } ++ ++ dev_info(priv->dev, "found an %s switch\n", mb->chip_info->name); ++ ++ priv->num_ports = RTL8365MB_MAX_NUM_PORTS; ++ mb->priv = priv; ++ mb->cpu.trap_port = RTL8365MB_MAX_NUM_PORTS; ++ mb->cpu.insert = RTL8365MB_CPU_INSERT_TO_ALL; ++ mb->cpu.position = RTL8365MB_CPU_POS_AFTER_SA; ++ mb->cpu.rx_length = RTL8365MB_CPU_RXLEN_64BYTES; ++ mb->cpu.format = RTL8365MB_CPU_FORMAT_8BYTES; ++ ++ return 0; ++} ++ ++static const struct phylink_mac_ops rtl8365mb_phylink_mac_ops = { ++ .mac_config = rtl8365mb_phylink_mac_config, ++ .mac_link_down = rtl8365mb_phylink_mac_link_down, ++ .mac_link_up = rtl8365mb_phylink_mac_link_up, ++}; ++ ++static const struct dsa_switch_ops rtl8365mb_switch_ops = { ++ .get_tag_protocol = rtl8365mb_get_tag_protocol, ++ .change_tag_protocol = rtl8365mb_change_tag_protocol, ++ .setup = rtl8365mb_setup, ++ .teardown = rtl8365mb_teardown, ++ .phylink_get_caps = rtl8365mb_phylink_get_caps, ++ .port_stp_state_set = rtl8365mb_port_stp_state_set, ++ .get_strings = rtl8365mb_get_strings, ++ .get_ethtool_stats = rtl8365mb_get_ethtool_stats, ++ .get_sset_count = rtl8365mb_get_sset_count, ++ .get_eth_phy_stats = rtl8365mb_get_phy_stats, ++ .get_eth_mac_stats = rtl8365mb_get_mac_stats, ++ .get_eth_ctrl_stats = rtl8365mb_get_ctrl_stats, ++ .get_stats64 = rtl8365mb_get_stats64, ++ .port_change_mtu = rtl8365mb_port_change_mtu, ++ .port_max_mtu = rtl8365mb_port_max_mtu, ++}; ++ ++static const struct realtek_ops rtl8365mb_ops = { ++ .detect = rtl8365mb_detect, ++ .phy_read = rtl8365mb_phy_read, ++ .phy_write = rtl8365mb_phy_write, ++}; ++ ++const struct realtek_variant rtl8365mb_variant = { ++ .ds_ops = &rtl8365mb_switch_ops, ++ .ops = &rtl8365mb_ops, ++ .phylink_mac_ops = &rtl8365mb_phylink_mac_ops, ++ .clk_delay = 10, ++ .cmd_read = 0xb9, ++ .cmd_write = 0xb8, ++ .chip_data_sz = sizeof(struct rtl8365mb), ++}; ++ ++static const struct of_device_id rtl8365mb_of_match[] = { ++ { .compatible = "realtek,rtl8365mb", .data = &rtl8365mb_variant, }, ++ { /* sentinel */ }, ++}; ++MODULE_DEVICE_TABLE(of, rtl8365mb_of_match); ++ ++static struct platform_driver rtl8365mb_smi_driver = { ++ .driver = { ++ .name = "rtl8365mb-smi", ++ .of_match_table = rtl8365mb_of_match, ++ }, ++ .probe = realtek_smi_probe, ++ .remove_new = realtek_smi_remove, ++ .shutdown = realtek_smi_shutdown, ++}; ++ ++static struct mdio_driver rtl8365mb_mdio_driver = { ++ .mdiodrv.driver = { ++ .name = "rtl8365mb-mdio", ++ .of_match_table = rtl8365mb_of_match, ++ }, ++ .probe = realtek_mdio_probe, ++ .remove = realtek_mdio_remove, ++ .shutdown = realtek_mdio_shutdown, ++}; ++ ++static int rtl8365mb_init(void) ++{ ++ int ret; ++ ++ ret = realtek_mdio_driver_register(&rtl8365mb_mdio_driver); ++ if (ret) ++ return ret; ++ ++ ret = realtek_smi_driver_register(&rtl8365mb_smi_driver); ++ if (ret) { ++ realtek_mdio_driver_unregister(&rtl8365mb_mdio_driver); ++ return ret; ++ } ++ ++ return 0; ++} ++module_init(rtl8365mb_init); ++ ++static void __exit rtl8365mb_exit(void) ++{ ++ realtek_smi_driver_unregister(&rtl8365mb_smi_driver); ++ realtek_mdio_driver_unregister(&rtl8365mb_mdio_driver); ++} ++module_exit(rtl8365mb_exit); ++ ++MODULE_AUTHOR("Alvin Å ipraga "); ++MODULE_DESCRIPTION("Driver for RTL8365MB-VC ethernet switch"); ++MODULE_LICENSE("GPL"); ++MODULE_IMPORT_NS(REALTEK_DSA); diff --git a/target/linux/generic/backport-6.12/942-05-v7.2-net-dsa-realtek-rtl8365mb-add-table-lookup-interface.patch b/target/linux/generic/backport-6.12/942-05-v7.2-net-dsa-realtek-rtl8365mb-add-table-lookup-interface.patch new file mode 100644 index 00000000000..1239c79a029 --- /dev/null +++ b/target/linux/generic/backport-6.12/942-05-v7.2-net-dsa-realtek-rtl8365mb-add-table-lookup-interface.patch @@ -0,0 +1,394 @@ +From fbafdd3b224a03b7b335de144f44a600de937586 Mon Sep 17 00:00:00 2001 +From: Alvin Å ipraga +Date: Sat, 6 Jun 2026 05:29:29 -0300 +Subject: net: dsa: realtek: rtl8365mb: add table lookup interface +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Add a generic table lookup interface to centralize access to +the RTL8365MB internal tables. + +This interface abstracts the low-level table access logic and +will be used by subsequent commits to implement FDB and VLAN +operations. + +Signed-off-by: Alvin Å ipraga +Reviewed-by: Linus Walleij +Reviewed-by: Mieczyslaw Nalewaj +Co-developed-by: Luiz Angelo Daros de Luca +Signed-off-by: Luiz Angelo Daros de Luca +Link: https://patch.msgid.link/20260606-realtek_forward-v13-5-b9e409687cbe@gmail.com +Signed-off-by: Jakub Kicinski +--- + drivers/net/dsa/realtek/Makefile | 1 + + drivers/net/dsa/realtek/rtl8365mb_table.c | 214 ++++++++++++++++++++++++++++++ + drivers/net/dsa/realtek/rtl8365mb_table.h | 138 +++++++++++++++++++ + 3 files changed, 353 insertions(+) + +--- a/drivers/net/dsa/realtek/Makefile ++++ b/drivers/net/dsa/realtek/Makefile +@@ -17,4 +17,5 @@ rtl8366-objs += rtl8366rb-leds.o + endif + obj-$(CONFIG_NET_DSA_REALTEK_RTL8365MB) += rtl8365mb.o + rtl8365mb-objs := rtl8365mb_main.o \ ++ rtl8365mb_table.o \ + # end of rtl8365mb-objs +--- /dev/null ++++ b/drivers/net/dsa/realtek/rtl8365mb_table.c +@@ -0,0 +1,214 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* Look-up table query interface for the rtl8365mb switch family ++ * ++ * Copyright (C) 2022 Alvin Å ipraga ++ */ ++ ++#include "rtl8365mb_table.h" ++#include ++ ++/* Table access control register */ ++#define RTL8365MB_TABLE_CTRL_REG 0x0500 ++/* Should be one of rtl8365mb_table enum members */ ++#define RTL8365MB_TABLE_CTRL_TABLE_MASK GENMASK(2, 0) ++/* Should be one of rtl8365mb_table_op enum members */ ++#define RTL8365MB_TABLE_CTRL_OP_MASK GENMASK(3, 3) ++/* Should be one of rtl8365mb_table_l2_method enum members */ ++#define RTL8365MB_TABLE_CTRL_METHOD_MASK GENMASK(6, 4) ++#define RTL8365MB_TABLE_CTRL_PORT_MASK GENMASK(11, 8) ++ ++/* Table access address register */ ++#define RTL8365MB_TABLE_ACCESS_ADDR_REG 0x0501 ++#define RTL8365MB_TABLE_ADDR_MASK GENMASK(12, 0) ++ ++/* Table status register */ ++#define RTL8365MB_TABLE_STATUS_REG 0x0502 ++#define RTL8365MB_TABLE_STATUS_ADDRESS_MASK GENMASK(10, 0) ++/* set for L3, unset for L2 */ ++#define RTL8365MB_TABLE_STATUS_ADDR_TYPE_MASK GENMASK(11, 11) ++#define RTL8365MB_TABLE_STATUS_HIT_STATUS_MASK GENMASK(12, 12) ++#define RTL8365MB_TABLE_STATUS_BUSY_FLAG_MASK GENMASK(13, 13) ++#define RTL8365MB_TABLE_STATUS_ADDRESS_EXT_MASK GENMASK(14, 14) ++ ++/* Table read/write registers */ ++#define RTL8365MB_TABLE_WRITE_BASE 0x0510 ++#define RTL8365MB_TABLE_WRITE_REG(_x) \ ++ (RTL8365MB_TABLE_WRITE_BASE + (_x)) ++#define RTL8365MB_TABLE_READ_BASE 0x0520 ++#define RTL8365MB_TABLE_READ_REG(_x) \ ++ (RTL8365MB_TABLE_READ_BASE + (_x)) ++#define RTL8365MB_TABLE_10TH_DATA_MASK GENMASK(3, 0) ++#define RTL8365MB_TABLE_WRITE_10TH_REG \ ++ RTL8365MB_TABLE_WRITE_REG(RTL8365MB_TABLE_ENTRY_MAX_SIZE - 1) ++ ++static int rtl8365mb_table_poll_busy(struct realtek_priv *priv) ++{ ++ u32 val; ++ ++ return regmap_read_poll_timeout(priv->map_nolock, ++ RTL8365MB_TABLE_STATUS_REG, val, ++ !FIELD_GET(RTL8365MB_TABLE_STATUS_BUSY_FLAG_MASK, val), ++ 10, 10000); ++} ++ ++int rtl8365mb_table_query(struct realtek_priv *priv, ++ enum rtl8365mb_table table, ++ enum rtl8365mb_table_op op, u16 *addr, ++ enum rtl8365mb_table_l2_method method, ++ u16 port, u16 *data, size_t size) ++{ ++ bool addr_as_input = true; ++ bool write_data = false; ++ int ret = 0; ++ u32 cmd; ++ u32 val; ++ u32 hit; ++ ++ /* Prepare target table and operation (read or write) */ ++ cmd = 0; ++ cmd |= FIELD_PREP(RTL8365MB_TABLE_CTRL_TABLE_MASK, table); ++ cmd |= FIELD_PREP(RTL8365MB_TABLE_CTRL_OP_MASK, op); ++ if (op == RTL8365MB_TABLE_OP_READ && table == RTL8365MB_TABLE_L2) { ++ cmd |= FIELD_PREP(RTL8365MB_TABLE_CTRL_METHOD_MASK, method); ++ switch (method) { ++ case RTL8365MB_TABLE_L2_METHOD_MAC: ++ /* ++ * Method MAC requires as input the same L2 table format ++ * you'll get as result. However, it might only use mac ++ * address and FID/VID fields. ++ */ ++ write_data = true; ++ ++ /* METHOD_MAC does not use addr as input, but may return ++ * the matched index. ++ */ ++ addr_as_input = false; ++ ++ break; ++ case RTL8365MB_TABLE_L2_METHOD_ADDR: ++ case RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT: ++ case RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC: ++ case RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_MC: ++ break; ++ case RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC_PORT: ++ cmd |= FIELD_PREP(RTL8365MB_TABLE_CTRL_PORT_MASK, port); ++ break; ++ default: ++ return -EINVAL; ++ } ++ } else if (op == RTL8365MB_TABLE_OP_WRITE) { ++ write_data = true; ++ ++ /* Writing to L2 does not use addr as input, as the table index ++ * is derived from key fields. ++ */ ++ if (table == RTL8365MB_TABLE_L2) ++ addr_as_input = false; ++ } ++ ++ /* To prevent concurrent access to the look-up tables, take the regmap ++ * lock manually and access via the map_nolock regmap. ++ */ ++ mutex_lock(&priv->map_lock); ++ ++ /* Protect from a busy table access (i.e. previous access timeouts) */ ++ ret = rtl8365mb_table_poll_busy(priv); ++ if (ret) ++ goto out; ++ ++ /* Write entry data if writing to the table (or L2_METHOD_MAC) */ ++ if (write_data) { ++ /* bulk write data up to 9th word */ ++ ret = regmap_bulk_write(priv->map_nolock, ++ RTL8365MB_TABLE_WRITE_BASE, ++ data, ++ min_t(size_t, size, ++ RTL8365MB_TABLE_ENTRY_MAX_SIZE - ++ 1)); ++ if (ret) ++ goto out; ++ ++ /* 10th register uses only 4 least significant bits */ ++ if (size == RTL8365MB_TABLE_ENTRY_MAX_SIZE) { ++ val = FIELD_PREP(RTL8365MB_TABLE_10TH_DATA_MASK, ++ data[size - 1]); ++ ret = regmap_update_bits(priv->map_nolock, ++ RTL8365MB_TABLE_WRITE_10TH_REG, ++ RTL8365MB_TABLE_10TH_DATA_MASK, ++ val); ++ } ++ ++ if (ret) ++ goto out; ++ } ++ ++ /* Write address (if needed) */ ++ if (addr_as_input) { ++ ret = regmap_write(priv->map_nolock, ++ RTL8365MB_TABLE_ACCESS_ADDR_REG, ++ FIELD_PREP(RTL8365MB_TABLE_ADDR_MASK, ++ *addr)); ++ if (ret) ++ goto out; ++ } ++ ++ /* Execute */ ++ ret = regmap_write(priv->map_nolock, RTL8365MB_TABLE_CTRL_REG, cmd); ++ if (ret) ++ goto out; ++ ++ /* Poll for completion */ ++ ret = rtl8365mb_table_poll_busy(priv); ++ if (ret) ++ goto out; ++ ++ /* For both reads and writes to the L2 table, check status */ ++ if (table == RTL8365MB_TABLE_L2) { ++ ret = regmap_read(priv->map_nolock, RTL8365MB_TABLE_STATUS_REG, ++ &val); ++ if (ret) ++ goto out; ++ ++ /* Did the query find an entry? */ ++ hit = FIELD_GET(RTL8365MB_TABLE_STATUS_HIT_STATUS_MASK, val); ++ if (!hit) { ++ ret = -ENOENT; ++ goto out; ++ } ++ ++ /* If so, extract the address */ ++ *addr = 0; ++ *addr |= FIELD_GET(RTL8365MB_TABLE_STATUS_ADDRESS_MASK, val); ++ *addr |= FIELD_GET(RTL8365MB_TABLE_STATUS_ADDRESS_EXT_MASK, val) ++ << 11; ++ /* only set if it is a L3 address */ ++ *addr |= FIELD_GET(RTL8365MB_TABLE_STATUS_ADDR_TYPE_MASK, val) ++ << 12; ++ } ++ ++ /* Finally, get the table entry if we were reading */ ++ if (op == RTL8365MB_TABLE_OP_READ) { ++ ret = regmap_bulk_read(priv->map_nolock, ++ RTL8365MB_TABLE_READ_BASE, ++ data, size); ++ if (ret) ++ goto out; ++ ++ /* For the biggest table entries, the uppermost table ++ * entry register has space for only one nibble. Mask ++ * out the remainder bits. Empirically I saw nothing ++ * wrong with omitting this mask, but it may prevent ++ * unwanted behaviour. FYI. ++ */ ++ if (size == RTL8365MB_TABLE_ENTRY_MAX_SIZE) { ++ val = FIELD_GET(RTL8365MB_TABLE_10TH_DATA_MASK, ++ data[size - 1]); ++ data[size - 1] = val; ++ } ++ } ++ ++out: ++ mutex_unlock(&priv->map_lock); ++ ++ return ret; ++} +--- /dev/null ++++ b/drivers/net/dsa/realtek/rtl8365mb_table.h +@@ -0,0 +1,138 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* Look-up table query interface for the rtl8365mb switch family ++ * ++ * Copyright (C) 2022 Alvin Å ipraga ++ */ ++ ++#ifndef _REALTEK_RTL8365MB_TABLE_H ++#define _REALTEK_RTL8365MB_TABLE_H ++ ++#include ++#include ++ ++#include "realtek.h" ++ ++#define RTL8365MB_TABLE_ENTRY_MAX_SIZE 10 ++ ++/* ++ * enum rtl8365mb_table - available switch tables ++ * @RTL8365MB_TABLE_ACL_RULE: ACL rules ++ * @RTL8365MB_TABLE_ACL_ACTION: ACL actions ++ * @RTL8365MB_TABLE_CVLAN: VLAN4k configurations ++ * @RTL8365MB_TABLE_L2: filtering database (2K hash table) ++ * @RTL8365MB_TABLE_IGMP_GROUP: IGMP group database (readonly) ++ * ++ * NOTE: Don't change the enum values. They must concur with the field ++ * described by @RTL8365MB_TABLE_CTRL_TABLE_MASK. ++ */ ++enum rtl8365mb_table { ++ RTL8365MB_TABLE_ACL_RULE = 1, ++ RTL8365MB_TABLE_ACL_ACTION = 2, ++ RTL8365MB_TABLE_CVLAN = 3, ++ RTL8365MB_TABLE_L2 = 4, ++ RTL8365MB_TABLE_IGMP_GROUP = 5, ++}; ++ ++/* ++ * enum rtl8365mb_table_op - table query operation ++ * @RTL8365MB_TABLE_OP_READ: read an entry from the target table ++ * @RTL8365MB_TABLE_OP_WRITE: write an entry to the target table ++ * ++ * NOTE: Don't change the enum values. They must concur with the field ++ * described by @RTL8365MB_TABLE_CTRL_OP_MASK. ++ */ ++enum rtl8365mb_table_op { ++ RTL8365MB_TABLE_OP_READ = 0, ++ RTL8365MB_TABLE_OP_WRITE = 1, ++}; ++ ++/* ++ * enum rtl8365mb_table_l2_method - look-up method for read queries of L2 table ++ * @RTL8365MB_TABLE_L2_METHOD_MAC: look-up by source MAC address and FID (or ++ * VID) ++ * @RTL8365MB_TABLE_L2_METHOD_ADDR: look-up by entry address ++ * @RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT: look-up next entry starting from the ++ * supplied address ++ * @RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC: same as ADDR_NEXT but search only ++ * unicast addresses ++ * @RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_MC: same as ADDR_NEXT but search only ++ * multicast addresses ++ * @RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC_PORT: same as ADDR_NEXT_UC but ++ * search only entries with matching source port ++ * ++ * NOTE: Don't change the enum values. They must concur with the field ++ * described by @RTL8365MB_TABLE_CTRL_METHOD_MASK ++ */ ++enum rtl8365mb_table_l2_method { ++ RTL8365MB_TABLE_L2_METHOD_MAC = 0, ++ RTL8365MB_TABLE_L2_METHOD_ADDR = 1, ++ RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT = 2, ++ RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC = 3, ++ RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_MC = 4, ++ /* ++ * RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_MC_L3 = 5, ++ * RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_MC_L2L3 = 6, ++ */ ++ RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC_PORT = 7, ++}; ++ ++/* ++ * rtl8365mb_table_query() - read from or write to a switch table ++ * @priv: driver context ++ * @table: target table, see &enum rtl8365mb_table ++ * @op: read or write operation, see &enum rtl8365mb_table_op ++ * @addr: table address. For indexed tables, this selects the entry to access. ++ * For L2 read queries, it is ignored as input for MAC-based lookup ++ * methods and used as input for address-based lookup methods. On ++ * successful L2 queries, it is updated with the matched entry address. ++ * @method: L2 table lookup method, see &enum rtl8365mb_table_l2_method. ++ * Ignored for non-L2 tables. ++ * @port: for L2 read queries using method ++ * %RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC_PORT, restrict the search ++ * to entries associated with this source port. Ignored otherwise. ++ * @data: data buffer used to read from or write to the table. For L2 MAC ++ * lookups, this buffer provides the lookup key and receives the ++ * matched entry contents on success. ++ * @size: size of @data in 16-bit words. The caller must ensure that @size ++ * matches the target table's entry size and does not exceed ++ * RTL8365MB_TABLE_ENTRY_MAX_SIZE. ++ * ++ * This function provides unified access to the internal tables of the switch. ++ * All tables except the L2 table are simple indexed tables, where @addr ++ * selects the entry and @op determines whether the access is a read or a ++ * write operation. ++ * ++ * The content of @data is used as input when writing to tables or when ++ * specifying the lookup key for L2 MAC searches, and as output for all ++ * successful read operations. It remains unchanged during write operations or ++ * failed read operations that return %-ENOENT. For other errors during read ++ * operations, it is undefined. ++ * ++ * The L2 table is a hash table and supports multiple lookup methods. For ++ * %RTL8365MB_TABLE_L2_METHOD_MAC, an entry is searched based on the MAC ++ * address and FID/VID fields provided in @data, using the same format as ++ * an L2 table entry. Address-based methods either read a specific entry ++ * (%RTL8365MB_TABLE_L2_METHOD_ADDR) or iterate over valid entries starting ++ * from @addr (%RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT and variants). When using ++ * %RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC_PORT, only entries associated with ++ * the specified @port are considered. ++ * ++ * On successful L2 operations, @addr is updated with the matched table address ++ * or allocated entry address. If no matching entry is found, or if an L2 write ++ * operation fails (e.g., due to a full table during addition or a missing entry ++ * during deletion), %-ENOENT is returned and @addr remains unchanged. It is the ++ * caller's responsibility to map the returned error to the appropriate ++ * semantic error. ++ * ++ * @size must match the size of the target table entry, expressed in 16-bit ++ * words. ++ * ++ * Return: 0 on success, or a negative error code on failure. ++ */ ++int rtl8365mb_table_query(struct realtek_priv *priv, ++ enum rtl8365mb_table table, ++ enum rtl8365mb_table_op op, u16 *addr, ++ enum rtl8365mb_table_l2_method method, ++ u16 port, u16 *data, size_t size); ++ ++#endif /* _REALTEK_RTL8365MB_TABLE_H */ diff --git a/target/linux/generic/backport-6.12/942-06-v7.2-net-dsa-realtek-rtl8365mb-add-VLAN-support.patch b/target/linux/generic/backport-6.12/942-06-v7.2-net-dsa-realtek-rtl8365mb-add-VLAN-support.patch new file mode 100644 index 00000000000..3d3e114f28f --- /dev/null +++ b/target/linux/generic/backport-6.12/942-06-v7.2-net-dsa-realtek-rtl8365mb-add-VLAN-support.patch @@ -0,0 +1,1461 @@ +From 9da2c8672f77108a1f09232320f22225ab53dde9 Mon Sep 17 00:00:00 2001 +From: Alvin Å ipraga +Date: Sat, 6 Jun 2026 05:29:30 -0300 +Subject: net: dsa: realtek: rtl8365mb: add VLAN support +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Realtek RTL8365MB switches (a.k.a. RTL8367C family) use two different +structures for VLANs: + +- VLAN4K: A full table with 4096 entries defining port membership and + tagging. +- VLANMC: A smaller table with 32 entries used primarily for PVID + assignment. + +In this hardware, a port's PVID must point to an index in the VLANMC +table rather than a VID directly. Since the VLANMC table is limited to +32 entries, the driver implements a dynamic allocation scheme to +maximize resource usage: + +- VLAN4K is treated by the driver as the source of truth for membership. +- A VLANMC entry is only allocated when a port is configured to use a + specific VID as its PVID. +- VLANMC entries are deleted when no longer needed as a PVID by any port. + +Although VLANMC has a members field, the switch only checks membership +in the VLAN4K table. This driver will use VLANMC members field as way to +track which ports are using that entry as PVID. + +VLANMC index 0, although a valid entry, is reserved in this driver as a +neutral PVID value for ports not using a specific PVID. + +In the subsequent RTL8367D switch family, VLANMC table was +removed and PVID assignment was delegated to a dedicated set of +registers. + +The use of FIELD_PREP for reconstructing LO/HI values was suggested by +Yury Norov. + +Fix for vlan_setup and vlan_filtering was suggested by Abdulkader +Alrezej. + +Suggested-by: Yury Norov +Suggested-by: Abdulkader Alrezej +Signed-off-by: Alvin Å ipraga +Reviewed-by: Linus Walleij +Reviewed-by: Mieczyslaw Nalewaj +Co-developed-by: Luiz Angelo Daros de Luca +Signed-off-by: Luiz Angelo Daros de Luca +Link: https://patch.msgid.link/20260606-realtek_forward-v13-6-b9e409687cbe@gmail.com +Signed-off-by: Jakub Kicinski +--- + drivers/net/dsa/realtek/Makefile | 1 + + drivers/net/dsa/realtek/realtek.h | 5 + + drivers/net/dsa/realtek/rtl8365mb_main.c | 342 +++++++++++ + drivers/net/dsa/realtek/rtl8365mb_vlan.c | 944 +++++++++++++++++++++++++++++++ + drivers/net/dsa/realtek/rtl8365mb_vlan.h | 39 ++ + drivers/net/dsa/realtek/rtl83xx.c | 1 + + 6 files changed, 1332 insertions(+) + +--- a/drivers/net/dsa/realtek/Makefile ++++ b/drivers/net/dsa/realtek/Makefile +@@ -18,4 +18,5 @@ endif + obj-$(CONFIG_NET_DSA_REALTEK_RTL8365MB) += rtl8365mb.o + rtl8365mb-objs := rtl8365mb_main.o \ + rtl8365mb_table.o \ ++ rtl8365mb_vlan.o \ + # end of rtl8365mb-objs +--- a/drivers/net/dsa/realtek/realtek.h ++++ b/drivers/net/dsa/realtek/realtek.h +@@ -57,6 +57,11 @@ struct realtek_priv { + struct regmap *map; + struct regmap *map_nolock; + struct mutex map_lock; ++ /* vlan_lock protects against concurrent Read-Modify-Write operations ++ * on the global VLAN 4K and VLANMC tables, such as when adding or ++ * deleting port VLAN memberships and PVID configurations. ++ */ ++ struct mutex vlan_lock; + struct mii_bus *user_mii_bus; + struct mii_bus *bus; + int mdio_addr; +--- a/drivers/net/dsa/realtek/rtl8365mb_main.c ++++ b/drivers/net/dsa/realtek/rtl8365mb_main.c +@@ -104,6 +104,7 @@ + #include "realtek-smi.h" + #include "realtek-mdio.h" + #include "rtl83xx.h" ++#include "rtl8365mb_vlan.h" + + /* Family-specific data and limits */ + #define RTL8365MB_PHYADDRMAX 7 +@@ -292,6 +293,57 @@ + #define RTL8365MB_MSTI_CTRL_PORT_STATE_MASK(_physport) \ + (0x3 << RTL8365MB_MSTI_CTRL_PORT_STATE_OFFSET((_physport))) + ++/* Miscellaneous port configuration register, incl. VLAN egress mode */ ++#define RTL8365MB_PORT_MISC_CFG_REG_BASE 0x000E ++#define RTL8365MB_PORT_MISC_CFG_REG(_p) \ ++ (RTL8365MB_PORT_MISC_CFG_REG_BASE + ((_p) << 5)) ++#define RTL8365MB_PORT_MISC_CFG_SMALL_TAG_IPG_MASK 0x8000 ++#define RTL8365MB_PORT_MISC_CFG_TX_ITFSP_MODE_MASK 0x4000 ++#define RTL8365MB_PORT_MISC_CFG_FLOWCTRL_INDEP_MASK 0x2000 ++#define RTL8365MB_PORT_MISC_CFG_DOT1Q_REMARK_ENABLE_MASK 0x1000 ++#define RTL8365MB_PORT_MISC_CFG_INGRESSBW_FLOWCTRL_MASK 0x0800 ++#define RTL8365MB_PORT_MISC_CFG_INGRESSBW_IFG_MASK 0x0400 ++#define RTL8365MB_PORT_MISC_CFG_RX_SPC_MASK 0x0200 ++#define RTL8365MB_PORT_MISC_CFG_CRC_SKIP_MASK 0x0100 ++#define RTL8365MB_PORT_MISC_CFG_PKTGEN_TX_FIRST_MASK 0x0080 ++#define RTL8365MB_PORT_MISC_CFG_MAC_LOOPBACK_MASK 0x0040 ++/* See &rtl8365mb_vlan_egress_mode */ ++#define RTL8365MB_PORT_MISC_CFG_VLAN_EGRESS_MODE_MASK 0x0030 ++#define RTL8365MB_PORT_MISC_CFG_CONGESTION_SUSTAIN_TIME_MASK 0x000F ++ ++/** ++ * enum rtl8365mb_vlan_egress_mode - port VLAN egress mode ++ * @RTL8365MB_VLAN_EGRESS_MODE_ORIGINAL: follow untag mask in VLAN4k table entry ++ * @RTL8365MB_VLAN_EGRESS_MODE_KEEP: the VLAN tag format of egressed packets ++ * will remain the same as their ingressed format, but the priority and VID ++ * fields may be altered ++ * @RTL8365MB_VLAN_EGRESS_MODE_PRI_TAG: always egress with priority tag ++ * @RTL8365MB_VLAN_EGRESS_MODE_REAL_KEEP: the VLAN tag format of egressed ++ * packets will remain the same as their ingressed format, and neither the ++ * priority nor VID fields can be altered ++ */ ++enum rtl8365mb_vlan_egress_mode { ++ RTL8365MB_VLAN_EGRESS_MODE_ORIGINAL = 0, ++ RTL8365MB_VLAN_EGRESS_MODE_KEEP = 1, ++ RTL8365MB_VLAN_EGRESS_MODE_PRI_TAG = 2, ++ RTL8365MB_VLAN_EGRESS_MODE_REAL_KEEP = 3, ++}; ++ ++/* VLAN control register */ ++#define RTL8365MB_VLAN_CTRL_REG 0x07A8 ++#define RTL8365MB_VLAN_CTRL_EN_MASK 0x0001 ++ ++/* VLAN ingress filter register */ ++#define RTL8365MB_VLAN_INGRESS_REG 0x07A9 ++#define RTL8365MB_VLAN_INGRESS_MASK GENMASK(10, 0) ++#define RTL8365MB_VLAN_INGRESS_FILTER_PORT_EN_OFFSET(_p) (_p) ++#define RTL8365MB_VLAN_INGRESS_FILTER_PORT_EN_MASK(_p) BIT(_p) ++ ++/* VLAN "transparent" setting registers */ ++#define RTL8365MB_VLAN_EGRESS_TRANSPARENT_REG_BASE 0x09D0 ++#define RTL8365MB_VLAN_EGRESS_TRANSPARENT_REG(_p) \ ++ (RTL8365MB_VLAN_EGRESS_TRANSPARENT_REG_BASE + (_p)) ++ + /* MIB counter value registers */ + #define RTL8365MB_MIB_COUNTER_BASE 0x1000 + #define RTL8365MB_MIB_COUNTER_REG(_x) (RTL8365MB_MIB_COUNTER_BASE + (_x)) +@@ -1210,6 +1262,286 @@ static void rtl8365mb_port_stp_state_set + val << RTL8365MB_MSTI_CTRL_PORT_STATE_OFFSET(port)); + } + ++static int rtl8365mb_port_set_transparent(struct realtek_priv *priv, ++ int igr_port, int egr_port, ++ bool enable) ++{ ++ dev_dbg(priv->dev, "%s transparent VLAN from %d to %d\n", ++ enable ? "Enable" : "Disable", igr_port, egr_port); ++ ++ /* "Transparent" between the two ports means that packets forwarded by ++ * igr_port and egressed on egr_port will not be filtered by the usual ++ * VLAN membership settings. ++ */ ++ return regmap_update_bits(priv->map, ++ RTL8365MB_VLAN_EGRESS_TRANSPARENT_REG(egr_port), ++ BIT(igr_port), enable ? BIT(igr_port) : 0); ++} ++ ++static int rtl8365mb_port_set_ingress_filtering(struct realtek_priv *priv, ++ int port, bool enable) ++{ ++ /* Ingress filtering enabled: Discard VLAN-tagged frames if the port is ++ * not a member of the VLAN with which the packet is associated. ++ * Untagged packets will also be discarded unless the port has a PVID ++ * programmed. Priority-tagged frames are treated as untagged frames. ++ * ++ * Ingress filtering disabled: Accept all tagged and untagged frames. ++ */ ++ return regmap_update_bits(priv->map, RTL8365MB_VLAN_INGRESS_REG, ++ RTL8365MB_VLAN_INGRESS_FILTER_PORT_EN_MASK(port), ++ enable ? ++ RTL8365MB_VLAN_INGRESS_FILTER_PORT_EN_MASK(port) : ++ 0); ++} ++ ++static int ++rtl8365mb_port_set_vlan_egress_mode(struct realtek_priv *priv, int port, ++ enum rtl8365mb_vlan_egress_mode mode) ++{ ++ u32 val; ++ ++ val = FIELD_PREP(RTL8365MB_PORT_MISC_CFG_VLAN_EGRESS_MODE_MASK, mode); ++ return regmap_update_bits(priv->map, ++ RTL8365MB_PORT_MISC_CFG_REG(port), ++ RTL8365MB_PORT_MISC_CFG_VLAN_EGRESS_MODE_MASK, val); ++} ++ ++static int rtl8365mb_port_vlan_filtering(struct dsa_switch *ds, int port, ++ bool vlan_filtering, ++ struct netlink_ext_ack *extack) ++{ ++ enum rtl8365mb_frame_ingress accepted_frame, prev_accepted_frame; ++ enum rtl8365mb_vlan_egress_mode mode; ++ struct realtek_priv *priv = ds->priv; ++ u32 configured_ports = 0; ++ struct dsa_port *dp; ++ u16 pvid_vid; ++ int ret; ++ ++ dev_dbg(priv->dev, "port %d: %s VLAN filtering\n", port, ++ vlan_filtering ? "enable" : "disable"); ++ ++ ret = rtl8365mb_vlan_port_get_framefilter(priv, port, ++ &prev_accepted_frame); ++ if (ret) { ++ NL_SET_ERR_MSG_MOD(extack, ++ "Failed to get current framefilter"); ++ return ret; ++ } ++ ++ /* While filtering, only accepts untagged frames if PVID is enabled */ ++ if (vlan_filtering) { ++ ret = rtl8365mb_vlan_port_get_pvid(priv, port, &pvid_vid); ++ if (ret) ++ return ret; ++ ++ if (pvid_vid) ++ accepted_frame = RTL8365MB_FRAME_TYPE_ANY_FRAME; ++ else ++ accepted_frame = RTL8365MB_FRAME_TYPE_TAGGED_ONLY; ++ } else { ++ accepted_frame = RTL8365MB_FRAME_TYPE_ANY_FRAME; ++ } ++ ++ /* When vlan filter is enable/disabled in a bridge, this function is ++ * called for all member ports. We need to enable/disable ingress ++ * VLAN membership check. ++ */ ++ ret = rtl8365mb_port_set_ingress_filtering(priv, port, vlan_filtering); ++ if (ret) ++ return ret; ++ ++ /* However, we also enable/disable egress filtering because the switch ++ * still consider the egress interface VLAN membership to forward the ++ * traffic. We enable/disable that check disabling/enabling transparent ++ * VLAN between the ingress port and all other available ports. ++ */ ++ dsa_switch_for_each_available_port(dp, ds) { ++ /* port isolation will still keep traffic inside the bridge */ ++ ret = rtl8365mb_port_set_transparent(priv, port, dp->index, ++ !vlan_filtering); ++ if (ret) ++ goto undo_transparent; ++ ++ configured_ports |= BIT(dp->index); ++ } ++ ++ if (accepted_frame != prev_accepted_frame) { ++ ret = rtl8365mb_vlan_port_set_framefilter(priv, port, ++ accepted_frame); ++ if (ret) { ++ NL_SET_ERR_MSG_MOD(extack, ++ "Failed to set port framefilter"); ++ goto undo_transparent; ++ } ++ } ++ ++ /* When VLAN filtering is disabled, preserve frames exactly as received. ++ * Otherwise, the VLAN egress pipeline may still alter tag state ++ * according to VLAN membership and untag configuration. ++ */ ++ if (vlan_filtering) ++ mode = RTL8365MB_VLAN_EGRESS_MODE_ORIGINAL; ++ else ++ mode = RTL8365MB_VLAN_EGRESS_MODE_REAL_KEEP; ++ ++ ret = rtl8365mb_port_set_vlan_egress_mode(priv, port, mode); ++ if (ret) ++ goto undo_set_framefilter; ++ ++ return ret; ++ ++undo_set_framefilter: ++ if (prev_accepted_frame != accepted_frame) ++ rtl8365mb_vlan_port_set_framefilter(priv, port, ++ prev_accepted_frame); ++undo_transparent: ++ /* The DSA core guarantees this callback is only invoked on an actual ++ * state transition, ensuring the previous hardware state was the ++ * opposite (!vlan_filtering). It is also called during setup but, in ++ * that case, any failure here aborts the entire switch initialization. ++ * ++ * VLAN_INGRESS and VLAN_EGRESS_TRANSPARENT states are directly derived ++ * from vlan_filtering. That way, we can simply undo it without ++ * checking the current HW state as we do with VLAN_EGRESS_MODE. ++ */ ++ dsa_switch_for_each_port(dp, ds) { ++ if (configured_ports & BIT(dp->index)) ++ rtl8365mb_port_set_transparent(priv, port, dp->index, ++ vlan_filtering); ++ } ++ ++ rtl8365mb_port_set_ingress_filtering(priv, port, !vlan_filtering); ++ ++ return ret; ++} ++ ++static int rtl8365mb_port_vlan_add(struct dsa_switch *ds, int port, ++ const struct switchdev_obj_port_vlan *vlan, ++ struct netlink_ext_ack *extack) ++{ ++ bool untagged = !!(vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED); ++ bool pvid = !!(vlan->flags & BRIDGE_VLAN_INFO_PVID); ++ u16 pvid_vid; ++ struct realtek_priv *priv = ds->priv; ++ int ret; ++ ++ dev_dbg(priv->dev, "add VLAN %d on port %d, %s, %s\n", ++ vlan->vid, port, untagged ? "untagged" : "tagged", ++ pvid ? "PVID" : "no PVID"); ++ ++ /* VID == 0 is reserved in this driver */ ++ if (vlan->vid == 0) { ++ NL_SET_ERR_MSG_MOD(extack, ++ "VLAN 0 is reserved by this driver"); ++ return -EOPNOTSUPP; ++ } ++ ++ mutex_lock(&priv->vlan_lock); ++ ++ ret = rtl8365mb_vlan_port_get_pvid(priv, port, &pvid_vid); ++ if (ret) ++ goto out_unlock; ++ ++ /* Set PVID if needed */ ++ if (pvid) { ++ ret = rtl8365mb_vlan_pvid_port_set(ds, port, vlan->vid, ++ extack); ++ if (ret) ++ goto out_unlock; ++ } else { ++ /* or try to unset it if not */ ++ ret = rtl8365mb_vlan_pvid_port_clear(ds, port, vlan->vid); ++ if (ret) ++ goto out_unlock; ++ } ++ ++ /* add port to vlan4k. It knows nothing about PVID */ ++ ret = rtl8365mb_vlan_4k_port_add(ds, port, vlan, extack); ++ if (ret) ++ goto undo_set_pvid; ++ ++ ret = 0; ++ goto out_unlock; ++ ++undo_set_pvid: ++ /* undo the pvid definition */ ++ if (pvid != (pvid_vid == vlan->vid)) { ++ if (pvid_vid) ++ (void)rtl8365mb_vlan_pvid_port_set(ds, port, pvid_vid, ++ NULL); ++ else ++ (void)rtl8365mb_vlan_pvid_port_clear(ds, port, ++ vlan->vid); ++ } ++out_unlock: ++ mutex_unlock(&priv->vlan_lock); ++ return ret; ++} ++ ++static int rtl8365mb_port_vlan_del(struct dsa_switch *ds, int port, ++ const struct switchdev_obj_port_vlan *vlan) ++{ ++ bool untagged = !!(vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED); ++ bool pvid = !!(vlan->flags & BRIDGE_VLAN_INFO_PVID); ++ struct realtek_priv *priv = ds->priv; ++ int ret; ++ ++ dev_dbg(priv->dev, "del VLAN %d on port %d, %s, %s\n", ++ vlan->vid, port, untagged ? "untagged" : "tagged", ++ pvid ? "PVID" : "no PVID"); ++ ++ /* VID == 0 is reserved in this driver */ ++ if (vlan->vid == 0) ++ return -EOPNOTSUPP; ++ ++ mutex_lock(&priv->vlan_lock); ++ ret = rtl8365mb_vlan_pvid_port_clear(ds, port, vlan->vid); ++ if (ret) ++ goto out_unlock; ++ ++ ret = rtl8365mb_vlan_4k_port_del(ds, port, vlan); ++ /* There is little incentive to try to undo the removal of PVID (if it ++ * was really in use) as an error here might indicate the ASIC stopped ++ * to answer. ++ */ ++ ++out_unlock: ++ mutex_unlock(&priv->vlan_lock); ++ return ret; ++} ++ ++/* VLAN support is always enabled in the switch. ++ * ++ * Standalone forwarding relies on transparent VLAN mode combined with per-port ++ * isolation masks restricting egress to CPU ports only. ++ * ++ */ ++static int rtl8365mb_vlan_setup(struct dsa_switch *ds) ++{ ++ struct realtek_priv *priv = ds->priv; ++ struct dsa_port *dp; ++ int ret; ++ ++ dsa_switch_for_each_available_port(dp, ds) { ++ /* Disable vlan-filtering for all ports */ ++ ret = rtl8365mb_port_vlan_filtering(ds, dp->index, false, NULL); ++ if (ret) { ++ dev_err(priv->dev, ++ "Failed to disable vlan filtering on port %d\n", ++ dp->index); ++ return ret; ++ } ++ } ++ ++ /* VLAN is always enabled. */ ++ ret = regmap_update_bits(priv->map, RTL8365MB_VLAN_CTRL_REG, ++ RTL8365MB_VLAN_CTRL_EN_MASK, ++ FIELD_PREP(RTL8365MB_VLAN_CTRL_EN_MASK, 1)); ++ return ret; ++} ++ + static int rtl8365mb_port_set_learning(struct realtek_priv *priv, int port, + bool enable) + { +@@ -2100,6 +2432,13 @@ static int rtl8365mb_setup(struct dsa_sw + if (ret) + goto out_teardown_irq; + ++ ds->configure_vlan_while_not_filtering = true; ++ ++ /* Set up VLAN */ ++ ret = rtl8365mb_vlan_setup(ds); ++ if (ret) ++ goto out_teardown_irq; ++ + ret = rtl83xx_setup_user_mdio(ds); + if (ret) { + dev_err(priv->dev, "could not set up MDIO bus\n"); +@@ -2210,6 +2549,9 @@ static const struct dsa_switch_ops rtl83 + .teardown = rtl8365mb_teardown, + .phylink_get_caps = rtl8365mb_phylink_get_caps, + .port_stp_state_set = rtl8365mb_port_stp_state_set, ++ .port_vlan_add = rtl8365mb_port_vlan_add, ++ .port_vlan_del = rtl8365mb_port_vlan_del, ++ .port_vlan_filtering = rtl8365mb_port_vlan_filtering, + .get_strings = rtl8365mb_get_strings, + .get_ethtool_stats = rtl8365mb_get_ethtool_stats, + .get_sset_count = rtl8365mb_get_sset_count, +--- /dev/null ++++ b/drivers/net/dsa/realtek/rtl8365mb_vlan.c +@@ -0,0 +1,944 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* VLAN configuration interface for the rtl8365mb switch family ++ * ++ * Copyright (C) 2022 Alvin Å ipraga ++ * ++ * VLAN configuration takes place in two separate domains of the switch: the ++ * VLAN4k table and the VLAN membership configuration (MC) database. While the ++ * VLAN4k table is exhaustive and can be fully populated with 4096 VLAN ++ * configurations, the same does not hold for the VLAN membership configuration ++ * database, which is limited to 32 entries. ++ * ++ * The switch will normally only use the VLAN4k table when making forwarding ++ * decisions. The VLAN membership configuration database is a vestigial ASIC ++ * design and is only used for a few specific features in the rtl8365mb ++ * family. This means that the limit of 32 entries should not hinder us in ++ * programming a huge number of VLANs into the switch. ++ * ++ * One necessary use of the VLAN membership configuration database is for the ++ * programming of a port-based VLAN ID (PVID). The PVID is programmed on a ++ * per-port basis via register field, which refers to a specific VLAN membership ++ * configuration via an index 0~31. In order to maintain coherent behaviour on a ++ * port with a PVID, it is necessary to keep the VLAN configuration synchronized ++ * between the VLAN4k table and the VLAN membership configuration database. ++ * ++ * Since VLAN membership configs are a scarce resource, it will only be used ++ * when strictly needed (i.e. a VLAN with members using PVID). Otherwise, the ++ * VLAN4k will be enough. ++ * ++ * With some exceptions, the entries in both the VLAN4k table and the VLAN ++ * membership configuration database offer the same configuration options. The ++ * differences are as follows: ++ * ++ * 1. VLAN4k entries can specify whether to use Independent or Shared VLAN ++ * Learning (IVL or SVL respectively). VLAN membership config entries ++ * cannot. This underscores the fact that VLAN membership configs are not ++ * involved in the learning process of the ASIC. ++ * ++ * 2. VLAN membership config entries use an "enhanced VLAN ID" (efid), which has ++ * a range 0~8191 compared with the standard 0~4095 range of the VLAN4k ++ * table. This underscores the fact that VLAN membership configs can be used ++ * to group ports on a layer beyond the standard VLAN configuration, which ++ * may be useful for ACL rules which specify alternative forwarding ++ * decisions. ++ * ++ * VLANMC index 0 is reserved as a neutral PVID, used for standalone ports. ++ * ++ */ ++ ++#include "rtl8365mb_vlan.h" ++#include "rtl8365mb_table.h" ++#include ++#include ++#include ++ ++/* CVLAN (i.e. VLAN4k) table entry layout, u16[3] */ ++#define RTL8365MB_CVLAN_ENTRY_SIZE 3 /* 48-bits */ ++#define RTL8365MB_CVLAN_ENTRY_D0_MBR_MASK GENMASK(7, 0) ++#define RTL8365MB_CVLAN_MBR_LO_MASK GENMASK(7, 0) ++#define RTL8365MB_CVLAN_ENTRY_D0_UNTAG_MASK GENMASK(15, 8) ++#define RTL8365MB_CVLAN_UNTAG_LO_MASK GENMASK(7, 0) ++#define RTL8365MB_CVLAN_ENTRY_D1_FID_MASK GENMASK(3, 0) ++#define RTL8365MB_CVLAN_ENTRY_D1_VBPEN_MASK GENMASK(4, 4) ++#define RTL8365MB_CVLAN_ENTRY_D1_VBPRI_MASK GENMASK(7, 5) ++#define RTL8365MB_CVLAN_ENTRY_D1_ENVLANPOL_MASK GENMASK(8, 8) ++#define RTL8365MB_CVLAN_ENTRY_D1_METERIDX_MASK GENMASK(13, 9) ++#define RTL8365MB_CVLAN_METERIDX_LO_MASK GENMASK(4, 0) ++#define RTL8365MB_CVLAN_ENTRY_D1_IVL_SVL_MASK GENMASK(14, 14) ++/* extends RTL8365MB_CVLAN_ENTRY_D0_MBR_MASK */ ++#define RTL8365MB_CVLAN_ENTRY_D2_MBR_EXT_MASK GENMASK(2, 0) ++#define RTL8365MB_CVLAN_MBR_HI_MASK GENMASK(10, 8) ++/* extends RTL8365MB_CVLAN_ENTRY_D0_UNTAG_MASK */ ++#define RTL8365MB_CVLAN_ENTRY_D2_UNTAG_EXT_MASK GENMASK(5, 3) ++#define RTL8365MB_CVLAN_UNTAG_HI_MASK GENMASK(10, 8) ++/* extends RTL8365MB_CVLAN_ENTRY_D1_METERIDX_MASK */ ++#define RTL8365MB_CVLAN_ENTRY_D2_METERIDX_EXT_MASK GENMASK(6, 6) ++#define RTL8365MB_CVLAN_METERIDX_HI_MASK GENMASK(5, 5) ++ ++/* VLAN member configuration registers 0~31, u16[3] */ ++#define RTL8365MB_VLAN_MC_BASE 0x0728 ++#define RTL8365MB_VLAN_MC_ENTRY_SIZE 4 /* 64-bit */ ++#define RTL8365MB_VLAN_MC_REG(index) \ ++ (RTL8365MB_VLAN_MC_BASE + \ ++ (RTL8365MB_VLAN_MC_ENTRY_SIZE * (index))) ++#define RTL8365MB_VLAN_MC_D0_MBR_MASK GENMASK(10, 0) ++#define RTL8365MB_VLAN_MC_D1_FID_MASK GENMASK(3, 0) ++ ++#define RTL8365MB_VLAN_MC_D2_VBPEN_MASK GENMASK(0, 0) ++#define RTL8365MB_VLAN_MC_D2_VBPRI_MASK GENMASK(3, 1) ++#define RTL8365MB_VLAN_MC_D2_ENVLANPOL_MASK GENMASK(4, 4) ++#define RTL8365MB_VLAN_MC_D2_METERIDX_MASK GENMASK(10, 5) ++#define RTL8365MB_VLAN_MC_D3_EVID_MASK GENMASK(12, 0) ++ ++/* Some limits for VLAN4k/VLAN membership config entries */ ++#define RTL8365MB_PRIORITYMAX 7 ++#define RTL8365MB_FIDMAX 15 ++#define RTL8365MB_METERMAX 63 ++#define RTL8365MB_VLAN_MCMAX 31 ++ ++/* RTL8367S supports 4k vlans (vid<=4095) and 32 enhanced vlans ++ * for VIDs up to 8191 ++ */ ++#define RTL8365MB_MAX_4K_VID 0x0FFF /* 4095 */ ++#define RTL8365MB_MAX_MC_VID 0x1FFF /* 8191 */ ++ ++ /* Port-based VID registers 0~5 - each one holds an MC index for two ports */ ++#define RTL8365MB_VLAN_PVID_CTRL_BASE 0x0700 ++#define RTL8365MB_VLAN_PVID_CTRL_REG(_p) \ ++ (RTL8365MB_VLAN_PVID_CTRL_BASE + ((_p) >> 1)) ++#define RTL8365MB_VLAN_PVID_CTRL_PORT0_MCIDX_MASK 0x001F ++#define RTL8365MB_VLAN_PVID_CTRL_PORT1_MCIDX_MASK 0x1F00 ++#define RTL8365MB_VLAN_PVID_CTRL_PORT_MCIDX_OFFSET(_p) \ ++ (((_p) & 1) << 3) ++#define RTL8365MB_VLAN_PVID_CTRL_PORT_MCIDX_MASK(_p) \ ++ (0x1F << RTL8365MB_VLAN_PVID_CTRL_PORT_MCIDX_OFFSET(_p)) ++ ++/* Frame type filtering registers */ ++#define RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_BASE 0x07aa ++#define RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_REG(port) \ ++ (RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_BASE + ((port) >> 3)) ++/* required as FIELD_PREP cannot use non-constant masks */ ++#define RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_MASK(port) \ ++ (0x3 << RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_OFFSET(port)) ++#define RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_OFFSET(port) \ ++ (((port) & 0x7) << 1) ++ ++/* ++ * struct rtl8365mb_vlan4k - VLAN4k table entry ++ * @vid: VLAN ID (0~4095) ++ * @member: port mask of ports in this VLAN ++ * @untag: port mask of ports which untag on egress ++ * @fid: filter ID - only used with SVL (unused) ++ * @priority: priority classification (unused) ++ * @priority_en: enable priority (unused) ++ * @policing_en: enable policing (unused) ++ * @ivl_en: enable IVL instead of default SVL ++ * @meteridx: metering index (unused) ++ * ++ * This structure is used to get/set entries in the VLAN4k table. The ++ * VLAN4k table dictates the VLAN configuration for the switch for the ++ * vast majority of features. ++ */ ++struct rtl8365mb_vlan4k { ++ u16 vid; ++ u16 member; ++ u16 untag; ++ u8 fid : 4; ++ u8 priority : 3; ++ u8 priority_en : 1; ++ u8 policing_en : 1; ++ u8 ivl_en : 1; ++ u8 meteridx : 6; ++}; ++ ++/* ++ * struct rtl8365mb_vlanmc - VLAN membership config ++ * @evid: Enhanced VLAN ID (0~8191) ++ * @member: port mask of ports in this VLAN ++ * @fid: filter ID - only used with SVL (unused) ++ * @priority: priority classification (unused) ++ * @priority_en: enable priority (unused) ++ * @policing_en: enable policing (unused) ++ * @meteridx: metering index (unused) ++ * ++ * This structure is used to get/set entries in the VLAN membership ++ * configuration database. This feature is largely vestigial, but ++ * still needed for at least the following features: ++ * - PVID configuration ++ * - ACL configuration ++ * - selection of VLAN by the CPU tag when VSEL=1, although the switch ++ * can also select VLAN based on the VLAN tag if VSEL=0 ++ * ++ * This is a low-level structure and it is recommended to interface with ++ * the VLAN membership config database via &struct rtl8365mb_vlanmc_entry. ++ */ ++struct rtl8365mb_vlanmc { ++ u16 evid; ++ u16 member; ++ u8 fid : 4; ++ u8 priority : 3; ++ u8 priority_en : 1; ++ u8 policing_en : 1; ++ u8 meteridx : 6; ++}; ++ ++static int rtl8365mb_vlan_4k_read(struct realtek_priv *priv, u16 vid, ++ struct rtl8365mb_vlan4k *vlan4k) ++{ ++ u16 data[RTL8365MB_CVLAN_ENTRY_SIZE]; ++ int val; ++ int ret; ++ ++ ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_CVLAN, ++ RTL8365MB_TABLE_OP_READ, &vid, 0, 0, ++ data, ARRAY_SIZE(data)); ++ if (ret) ++ return ret; ++ ++ /* Unpack table entry */ ++ memset(vlan4k, 0, sizeof(*vlan4k)); ++ vlan4k->vid = vid; ++ ++ val = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D0_MBR_MASK, data[0]); ++ vlan4k->member = FIELD_PREP(RTL8365MB_CVLAN_MBR_LO_MASK, val); ++ val = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D2_MBR_EXT_MASK, data[2]); ++ vlan4k->member |= FIELD_PREP(RTL8365MB_CVLAN_MBR_HI_MASK, val); ++ ++ val = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D0_UNTAG_MASK, data[0]); ++ vlan4k->untag = FIELD_PREP(RTL8365MB_CVLAN_UNTAG_LO_MASK, val); ++ val = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D2_UNTAG_EXT_MASK, data[2]); ++ vlan4k->untag |= FIELD_PREP(RTL8365MB_CVLAN_UNTAG_HI_MASK, val); ++ ++ vlan4k->fid = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_FID_MASK, data[1]); ++ vlan4k->priority_en = ++ FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_VBPEN_MASK, data[1]); ++ vlan4k->priority = ++ FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_VBPRI_MASK, data[1]); ++ vlan4k->policing_en = ++ FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_ENVLANPOL_MASK, data[1]); ++ ++ val = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_METERIDX_MASK, data[1]); ++ val = FIELD_PREP(RTL8365MB_CVLAN_METERIDX_LO_MASK, val); ++ vlan4k->meteridx = val; ++ val = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D2_METERIDX_EXT_MASK, data[2]); ++ val = FIELD_PREP(RTL8365MB_CVLAN_METERIDX_HI_MASK, val); ++ vlan4k->meteridx |= val; ++ ++ vlan4k->ivl_en = ++ FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_IVL_SVL_MASK, data[1]); ++ ++ return 0; ++} ++ ++static int rtl8365mb_vlan_4k_write(struct realtek_priv *priv, ++ const struct rtl8365mb_vlan4k *vlan4k) ++{ ++ u16 data[RTL8365MB_CVLAN_ENTRY_SIZE] = { 0 }; ++ u16 vid; ++ int val; ++ ++ /* Pack table entry value */ ++ val = FIELD_GET(RTL8365MB_CVLAN_MBR_LO_MASK, vlan4k->member); ++ data[0] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D0_MBR_MASK, val); ++ ++ val = FIELD_GET(RTL8365MB_CVLAN_UNTAG_LO_MASK, vlan4k->untag); ++ data[0] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D0_UNTAG_MASK, val); ++ ++ data[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_FID_MASK, vlan4k->fid); ++ data[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_VBPEN_MASK, ++ vlan4k->priority_en); ++ data[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_VBPRI_MASK, ++ vlan4k->priority); ++ data[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_ENVLANPOL_MASK, ++ vlan4k->policing_en); ++ ++ /* FIELD_* does not play nice with struct bitfield. */ ++ val = vlan4k->meteridx; ++ val = FIELD_GET(RTL8365MB_CVLAN_METERIDX_LO_MASK, val); ++ data[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_METERIDX_MASK, val); ++ ++ data[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_IVL_SVL_MASK, ++ vlan4k->ivl_en); ++ ++ val = FIELD_GET(RTL8365MB_CVLAN_MBR_HI_MASK, vlan4k->member); ++ data[2] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D2_MBR_EXT_MASK, val); ++ ++ val = FIELD_GET(RTL8365MB_CVLAN_UNTAG_HI_MASK, vlan4k->untag); ++ data[2] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D2_UNTAG_EXT_MASK, val); ++ ++ val = vlan4k->meteridx; ++ val = FIELD_GET(RTL8365MB_CVLAN_METERIDX_HI_MASK, val); ++ data[2] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D2_METERIDX_EXT_MASK, val); ++ ++ vid = vlan4k->vid; ++ return rtl8365mb_table_query(priv, RTL8365MB_TABLE_CVLAN, ++ RTL8365MB_TABLE_OP_WRITE, &vid, 0, 0, ++ data, ARRAY_SIZE(data)); ++} ++ ++static int ++rtl8365mb_vlan_4k_port_set(struct dsa_switch *ds, int port, ++ const struct switchdev_obj_port_vlan *vlan, ++ struct netlink_ext_ack *extack, ++ bool include) ++{ ++ struct realtek_priv *priv = ds->priv; ++ struct rtl8365mb_vlan4k vlan4k = {0}; ++ int ret; ++ ++ dev_dbg(priv->dev, "%s VLAN %d 4K on port %d\n", ++ include ? "add" : "del", ++ vlan->vid, port); ++ ++ if (vlan->vid > RTL8365MB_MAX_4K_VID) { ++ NL_SET_ERR_MSG_MOD(extack, "VLAN ID greater than " ++ __stringify(RTL8365MB_MAX_4K_VID)); ++ return -EINVAL; ++ } ++ ++ ret = rtl8365mb_vlan_4k_read(priv, vlan->vid, &vlan4k); ++ if (ret) { ++ dev_err(priv->dev, "Failed to read VLAN 4k table\n"); ++ return ret; ++ } ++ ++ if (include) ++ vlan4k.member |= BIT(port); ++ else ++ vlan4k.member &= ~BIT(port); ++ ++ if (include && (vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED)) ++ vlan4k.untag |= BIT(port); ++ else ++ vlan4k.untag &= ~BIT(port); ++ vlan4k.ivl_en = true; /* always use Independent VLAN Learning */ ++ ++ ret = rtl8365mb_vlan_4k_write(priv, &vlan4k); ++ if (ret) { ++ dev_err(priv->dev, "Failed to write VLAN 4k table\n"); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++/* ++ * rtl8365mb_vlan_4k_port_add() - Add a port to a VLAN 4K table entry ++ * @ds: dsa switch instance ++ * @port: port index ++ * @vlan: switchdev VLAN object containing the target VID and flags ++ * @extack: netlink extended ACK for error reporting ++ * ++ * Adds the specified port to the hardware VLAN 4K membership table. ++ * ++ * Context: Can sleep. Must be called with &priv->vlan_lock held. ++ * Takes and releases &priv->map_lock. ++ * Return: 0 on success, or a negative error code on failure. ++ */ ++int rtl8365mb_vlan_4k_port_add(struct dsa_switch *ds, int port, ++ const struct switchdev_obj_port_vlan *vlan, ++ struct netlink_ext_ack *extack) ++{ ++ struct realtek_priv *priv = ds->priv; ++ ++ lockdep_assert_held(&priv->vlan_lock); ++ ++ return rtl8365mb_vlan_4k_port_set(ds, port, vlan, extack, true); ++} ++ ++/* ++ * rtl8365mb_vlan_4k_port_del() - Remove a port from a VLAN 4K table entry ++ * @ds: dsa switch instance ++ * @port: port index ++ * @vlan: switchdev VLAN object containing the target VID ++ * ++ * Removes the specified port from the hardware VLAN 4K membership table. ++ * ++ * Context: Can sleep. Must be called with &priv->vlan_lock held. ++ * Takes and releases &priv->map_lock. ++ * Return: 0 on success, or a negative error code on failure. ++ */ ++int rtl8365mb_vlan_4k_port_del(struct dsa_switch *ds, int port, ++ const struct switchdev_obj_port_vlan *vlan) ++{ ++ struct realtek_priv *priv = ds->priv; ++ ++ lockdep_assert_held(&priv->vlan_lock); ++ ++ return rtl8365mb_vlan_4k_port_set(ds, port, vlan, NULL, false); ++} ++ ++static int rtl8365mb_vlan_mc_read(struct realtek_priv *priv, u32 index, ++ struct rtl8365mb_vlanmc *vlanmc) ++{ ++ u16 data[RTL8365MB_VLAN_MC_ENTRY_SIZE]; ++ int ret; ++ ++ ret = regmap_bulk_read(priv->map, RTL8365MB_VLAN_MC_REG(index), &data, ++ RTL8365MB_VLAN_MC_ENTRY_SIZE); ++ if (ret) ++ return ret; ++ ++ vlanmc->member = FIELD_GET(RTL8365MB_VLAN_MC_D0_MBR_MASK, data[0]); ++ vlanmc->fid = FIELD_GET(RTL8365MB_VLAN_MC_D1_FID_MASK, data[1]); ++ vlanmc->meteridx = FIELD_GET(RTL8365MB_VLAN_MC_D2_METERIDX_MASK, ++ data[2]); ++ vlanmc->policing_en = FIELD_GET(RTL8365MB_VLAN_MC_D2_ENVLANPOL_MASK, ++ data[2]); ++ vlanmc->priority = FIELD_GET(RTL8365MB_VLAN_MC_D2_VBPRI_MASK, data[2]); ++ vlanmc->priority_en = FIELD_GET(RTL8365MB_VLAN_MC_D2_VBPEN_MASK, ++ data[2]); ++ vlanmc->evid = FIELD_GET(RTL8365MB_VLAN_MC_D3_EVID_MASK, data[3]); ++ ++ return 0; ++} ++ ++static int rtl8365mb_vlan_mc_write(struct realtek_priv *priv, u32 index, ++ const struct rtl8365mb_vlanmc *vlanmc) ++{ ++ u16 data[RTL8365MB_VLAN_MC_ENTRY_SIZE] = { 0 }; ++ int ret; ++ ++ data[0] |= FIELD_PREP(RTL8365MB_VLAN_MC_D0_MBR_MASK, vlanmc->member); ++ data[1] |= FIELD_PREP(RTL8365MB_VLAN_MC_D1_FID_MASK, vlanmc->fid); ++ data[2] |= FIELD_PREP(RTL8365MB_VLAN_MC_D2_METERIDX_MASK, ++ vlanmc->meteridx); ++ data[2] |= FIELD_PREP(RTL8365MB_VLAN_MC_D2_ENVLANPOL_MASK, ++ vlanmc->policing_en); ++ data[2] |= ++ FIELD_PREP(RTL8365MB_VLAN_MC_D2_VBPRI_MASK, vlanmc->priority); ++ data[2] |= FIELD_PREP(RTL8365MB_VLAN_MC_D2_VBPEN_MASK, ++ vlanmc->priority_en); ++ data[3] |= FIELD_PREP(RTL8365MB_VLAN_MC_D3_EVID_MASK, vlanmc->evid); ++ ++ ret = regmap_bulk_write(priv->map, RTL8365MB_VLAN_MC_REG(index), &data, ++ RTL8365MB_VLAN_MC_ENTRY_SIZE); ++ ++ return ret; ++} ++ ++static int rtl8365mb_vlan_mc_erase(struct realtek_priv *priv, u32 index) ++{ ++ u16 data[RTL8365MB_VLAN_MC_ENTRY_SIZE] = { 0 }; ++ int ret; ++ ++ ret = regmap_bulk_write(priv->map, RTL8365MB_VLAN_MC_REG(index), &data, ++ RTL8365MB_VLAN_MC_ENTRY_SIZE); ++ ++ return ret; ++} ++ ++/* ++ * rtl8365mb_vlan_mc_find() - find VLANMC index by VID or the first free index ++ * ++ * @priv: realtek_priv pointer ++ * @vid: VLAN ID ++ * @index: found index ++ * @first_free: found free index ++ * ++ * If a VLAN MC entry using @vid was found, @index will return the matched index ++ * and @first_free is undefined. If not found, @index will return 0 and ++ * @first_free will return the first found free index in VLAN MC or 0 if the ++ * table is full. ++ * ++ * Although 0 is a valid VLAN MC index, it is reserved for ports without PVID, ++ * including standalone, non-member ports. It uses VID == 0. ++ * ++ * Both @index and @first_free will be in the * 1..@RTL8365MB_VLAN_MCMAX range. ++ * ++ * Return: Returns 0 on success, a negative error on failure. ++ */ ++static int rtl8365mb_vlan_mc_find(struct realtek_priv *priv, u16 vid, ++ u8 *index, u8 *first_free) ++{ ++ u32 vlan_entry_d3; ++ u8 vlanmc_idx; ++ u16 evid; ++ int ret; ++ ++ *index = 0; ++ *first_free = 0; ++ ++ /* look for existing entry or an empty one */ ++ /* By design, VlanMC[0] is reserved as a neutral PVID value for ++ * standalone ports. It always has EVID == 0. That way, we assume that ++ * all entries after index 0 with VID == 0 are empty. ++ **/ ++ for (vlanmc_idx = 1; vlanmc_idx <= RTL8365MB_VLAN_MCMAX; vlanmc_idx++) { ++ /* just read the 4th word, where the evid is */ ++ ret = regmap_read(priv->map, ++ RTL8365MB_VLAN_MC_REG(vlanmc_idx) + 3, ++ &vlan_entry_d3); ++ if (ret) ++ return ret; ++ ++ evid = FIELD_GET(RTL8365MB_VLAN_MC_D3_EVID_MASK, vlan_entry_d3); ++ ++ if (evid == vid) { ++ *index = vlanmc_idx; ++ return 0; ++ } ++ ++ if (evid == 0x0 && *first_free < 1) ++ *first_free = vlanmc_idx; ++ } ++ return 0; ++} ++ ++static int rtl8365mb_vlan_port_get_pvid_idx(struct realtek_priv *priv, ++ int port, u8 *vlanmc_idx) ++{ ++ u32 data; ++ int ret; ++ ++ ret = regmap_read(priv->map, RTL8365MB_VLAN_PVID_CTRL_REG(port), &data); ++ if (ret) ++ return ret; ++ ++ *vlanmc_idx = (data & RTL8365MB_VLAN_PVID_CTRL_PORT_MCIDX_MASK(port)) ++ >> RTL8365MB_VLAN_PVID_CTRL_PORT_MCIDX_OFFSET(port); ++ ++ return 0; ++} ++ ++/* ++ * rtl8365mb_vlan_mc_port_set() - include or exclude a port from VlanMC ++ * @ds: dsa switch ++ * @port: the port number ++ * @vid: the vlan VID to include/exclude @port ++ * @pvid: inform if vid is used as pvid in @port ++ * @extack: optional extack to return errors ++ * @include: whether to include or exclude @port ++ * ++ * This function is used to include/exclude ports to the VlanMC table. ++ * ++ * VlanMC stands for VLAN membership config and it is used exclusively for ++ * PVID. If @vlan members are not using PVID, this function will either ++ * remove or not create a new VlanMC entry. ++ * ++ * VlanMC members are used as a reference port map, cleaning the entry once ++ * no port is using it. ++ * ++ * Port PVID and accepted frame type are updated as well. ++ * ++ * Context: Can sleep. Must be called with &priv->vlan_lock held. ++ * Takes and releases &priv->map_lock. ++ * Return: Returns 0 on success, a negative error on failure. ++ */ ++static ++int rtl8365mb_vlan_mc_port_set(struct dsa_switch *ds, int port, ++ u16 vid, bool pvid, ++ struct netlink_ext_ack *extack, ++ bool include) ++{ ++ struct realtek_priv *priv = ds->priv; ++ struct rtl8365mb_vlanmc vlanmc = {0}; ++ u8 first_unused = 0; ++ u8 vlanmc_idx = 0; ++ int ret; ++ ++ dev_dbg(priv->dev, "%s VLAN %d MC on port %d\n", ++ include ? "add" : "del", ++ vid, port); ++ ++ if (vid > RTL8365MB_MAX_MC_VID) { ++ NL_SET_ERR_MSG_MOD(extack, "VLAN ID greater than " ++ __stringify(RTL8365MB_MAX_MC_VID)); ++ return -EINVAL; ++ } ++ ++ /* look for existing entry or an empty slot */ ++ ret = rtl8365mb_vlan_mc_find(priv, vid, &vlanmc_idx, ++ &first_unused); ++ if (ret) { ++ dev_err(priv->dev, "Failed to find a VLAN MC table index\n"); ++ return ret; ++ } ++ ++ if (vlanmc_idx) { ++ ret = rtl8365mb_vlan_mc_read(priv, vlanmc_idx, &vlanmc); ++ if (ret) { ++ dev_err(priv->dev, "Failed to read VLAN MC table\n"); ++ return ret; ++ } ++ } else if (include) { ++ /* for now, vlan_mc is only required for PVID. Defer allocation ++ * until at least one port uses PVID. ++ */ ++ if (!pvid) { ++ dev_dbg(priv->dev, ++ "Not creating VlanMC for vlan %d until a port uses PVID (%d does not)\n", ++ vid, port); ++ return 0; ++ } ++ ++ if (!first_unused) { ++ NL_SET_ERR_MSG_MOD(extack, "All VLAN MC entries (0.." ++ __stringify(RTL8365MB_VLAN_MCMAX) ++ ") are in use."); ++ return -ENOSPC; ++ } ++ ++ vlanmc_idx = first_unused; ++ vlanmc.evid = vid; ++ ++ } else /* excluding and VLANMC not found */ { ++ return 0; ++ } ++ ++ dev_dbg(priv->dev, ++ "VLAN %d (idx: %d) PVID curr members: %08x\n", ++ vid, vlanmc_idx, vlanmc.member); ++ ++ /* here we either have an existing VLANMC (with PVID members) or the ++ * added port is using this VLAN as PVID ++ */ ++ if (include) ++ vlanmc.member |= BIT(port); ++ else ++ vlanmc.member &= ~BIT(port); ++ ++ /* just like we don't need to create a VLAN_MC when there is no port ++ * using it as PVID, we can erase it when there is no more port using ++ * it as PVID. ++ */ ++ if (!vlanmc.member) { ++ dev_dbg(priv->dev, ++ "Clearing VlanMC index %d previously used by VID %d\n", ++ vlanmc_idx, vid); ++ ret = rtl8365mb_vlan_mc_erase(priv, vlanmc_idx); ++ } else { ++ dev_dbg(priv->dev, ++ "Saving VlanMC index %d with VID %d\n", ++ vlanmc_idx, vid); ++ ret = rtl8365mb_vlan_mc_write(priv, vlanmc_idx, &vlanmc); ++ } ++ if (ret) { ++ dev_err(priv->dev, "Failed to write vlan MC entry\n"); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int rtl8365mb_vlan_port_set_pvid(struct realtek_priv *priv, ++ int port, u16 vlanmc_idx) ++{ ++ int ret; ++ u32 val; ++ ++ dev_dbg(priv->dev, "set PVID IDX %d on port %d\n", vlanmc_idx, port); ++ ++ val = vlanmc_idx << RTL8365MB_VLAN_PVID_CTRL_PORT_MCIDX_OFFSET(port); ++ ret = regmap_update_bits(priv->map, ++ RTL8365MB_VLAN_PVID_CTRL_REG(port), ++ RTL8365MB_VLAN_PVID_CTRL_PORT_MCIDX_MASK(port), ++ val); ++ if (ret) ++ return ret; ++ ++ return 0; ++} ++ ++static int rtl8365mb_vlan_get_pvid_mc(struct realtek_priv *priv, ++ int port, u8 *vlanmc_idx, ++ struct rtl8365mb_vlanmc *vlanmc) ++{ ++ int ret; ++ ++ ret = rtl8365mb_vlan_port_get_pvid_idx(priv, port, vlanmc_idx); ++ if (ret) ++ return ret; ++ ++ memset(vlanmc, 0, sizeof(*vlanmc)); ++ ++ if (!*vlanmc_idx) ++ return 0; ++ ++ ret = rtl8365mb_vlan_mc_read(priv, *vlanmc_idx, vlanmc); ++ if (ret) ++ return ret; ++ ++ return 0; ++} ++ ++/* ++ * rtl8365mb_vlan_port_get_pvid - Retrieve the port PVID ++ * @priv: realtek switch private structure ++ * @port: port index ++ * @pvid: pointer to store the retrieved VLAN ID ++ * ++ * Returns the port PVID if defined or 0 if not. ++ * ++ * Context: Can sleep. Takes and releases &priv->map_lock. ++ * Return: 0 on success or a negative error code on failure. ++ */ ++int rtl8365mb_vlan_port_get_pvid(struct realtek_priv *priv, int port, u16 *pvid) ++{ ++ struct rtl8365mb_vlanmc vlanmc; ++ u8 vlanmc_idx; ++ int ret; ++ ++ ret = rtl8365mb_vlan_get_pvid_mc(priv, port, &vlanmc_idx, &vlanmc); ++ if (ret) ++ return ret; ++ ++ *pvid = vlanmc.evid; ++ return 0; ++} ++ ++/* ++ * rtl8365mb_vlan_port_get_framefilter() - Get the ingress frame filtering mode ++ * for a port ++ * @priv: realtek switch private structure ++ * @port: port index ++ * @frame_type: pointer to store the retrieved ingress frame filter type ++ * ++ * Context: Can sleep. Takes and releases &priv->map_lock. ++ * Return: 0 on success, or a negative error code on failure. ++ */ ++int ++rtl8365mb_vlan_port_get_framefilter(struct realtek_priv *priv, ++ int port, ++ enum rtl8365mb_frame_ingress *frame_type) ++{ ++ u32 val; ++ int ret; ++ ++ /* Even if ACCEPT_FRAME_TYPE_ANY, the switch will still check if the ++ * port is a member of vlan PVID ++ */ ++ ++ ret = regmap_read(priv->map, RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_REG(port), ++ &val); ++ if (ret) ++ return ret; ++ ++ *frame_type = field_get(RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_MASK(port), ++ val); ++ ++ return 0; ++} ++ ++/* ++ * rtl8365mb_vlan_port_set_framefilter() - Set the ingress frame filtering mode ++ * for a port ++ * @priv: realtek switch private structure ++ * @port: port index ++ * @frame_type: the ingress frame filter type to configure ++ * ++ * Context: Can sleep. Takes and releases &priv->map_lock. ++ * Return: 0 on success, or a negative error code on failure. ++ */ ++int ++rtl8365mb_vlan_port_set_framefilter(struct realtek_priv *priv, ++ int port, ++ enum rtl8365mb_frame_ingress frame_type) ++{ ++ u32 val; ++ ++ /* Even if ACCEPT_FRAME_TYPE_ANY, the switch will still check if the ++ * port is a member of vlan PVID ++ */ ++ val = frame_type << RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_OFFSET(port); ++ ++ return regmap_update_bits(priv->map, ++ RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_REG(port), ++ RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_MASK(port), ++ val); ++} ++ ++/* ++ * rtl8365mb_vlan_pvid_port_set() - Configure a port's PVID and associated ++ * VLANMC entry ++ * @ds: dsa switch instance ++ * @port: port index ++ * @vid: target VID ++ * @extack: netlink extended ACK for error reporting ++ * ++ * Allocates or reuses a hardware VLANMC entry to map the given port to its new ++ * PVID. Gracefully unwinds and restores previous configuration if a hardware ++ * write operation fails during execution. ++ * ++ * Context: Can sleep. Must be called with &priv->vlan_lock held. ++ * Takes and releases &priv->map_lock. ++ * Return: 0 on success, or a negative error code on failure. ++ */ ++int rtl8365mb_vlan_pvid_port_set(struct dsa_switch *ds, int port, u16 vid, ++ struct netlink_ext_ack *extack) ++{ ++ enum rtl8365mb_frame_ingress accepted_frame, prev_accepted_frame; ++ struct realtek_priv *priv = ds->priv; ++ struct rtl8365mb_vlanmc prev_vlanmc = {0}; ++ u8 _unused_first_free_idx; ++ u8 prev_vlanmc_idx; ++ u8 vlanmc_idx; ++ int ret; ++ ++ lockdep_assert_held(&priv->vlan_lock); ++ ++ /* Read the old PVID exclusively to undo in case of error */ ++ ret = rtl8365mb_vlan_get_pvid_mc(priv, port, &prev_vlanmc_idx, ++ &prev_vlanmc); ++ if (ret) { ++ dev_err(priv->dev, "Failed to read current VLAN MC\n"); ++ return ret; ++ } ++ ++ ret = rtl8365mb_vlan_port_get_framefilter(priv, port, ++ &prev_accepted_frame); ++ if (ret) { ++ dev_err(priv->dev, "Failed to get current framefilter\n"); ++ return ret; ++ } ++ ++ /* Find or allocate a new vlan MC and add port to members, ++ * although members are not checked by the HW in vlan MC. ++ */ ++ ret = rtl8365mb_vlan_mc_port_set(ds, port, vid, true, extack, true); ++ if (ret) ++ return ret; ++ ++ /* look for existing entry */ ++ ret = rtl8365mb_vlan_mc_find(priv, vid, &vlanmc_idx, ++ &_unused_first_free_idx); ++ if (ret) { ++ dev_err(priv->dev, "Failed to find a VLAN MC table index\n"); ++ goto undo_vlan_mc_port_set; ++ } ++ ++ if (!vlanmc_idx) { ++ dev_err(priv->dev, "VLAN should already exist in VLAN MC\n"); ++ ret = -ENOENT; ++ goto undo_vlan_mc_port_set; ++ } ++ ++ ret = rtl8365mb_vlan_port_set_pvid(priv, port, vlanmc_idx); ++ if (ret) { ++ dev_err(priv->dev, "Failed to set port PVID\n"); ++ goto undo_vlan_mc_port_set; ++ } ++ ++ /* Changing accept frame is what enables PVID (if not enabled before) */ ++ accepted_frame = RTL8365MB_FRAME_TYPE_ANY_FRAME; ++ ret = rtl8365mb_vlan_port_set_framefilter(priv, port, accepted_frame); ++ if (ret) { ++ dev_err(priv->dev, "Failed to set port frame filter\n"); ++ goto undo_vlan_port_set_pvid; ++ } ++ ++ /* A VLAN can be added with PVID without removing from the old ++ * PVID VLAN. Clear PVID from the old VLAN MC (if needed). ++ */ ++ if (prev_vlanmc_idx && (prev_vlanmc.evid != vid)) { ++ ret = rtl8365mb_vlan_mc_port_set(ds, port, prev_vlanmc.evid, ++ false, NULL, false); ++ if (ret) { ++ dev_err(priv->dev, "Failed to clear old VLAN MC\n"); ++ goto undo_set_framefilter; ++ } ++ } ++ ++ return 0; ++ ++undo_set_framefilter: ++ (void)rtl8365mb_vlan_port_set_framefilter(priv, port, ++ prev_accepted_frame); ++ ++undo_vlan_port_set_pvid: ++ (void)rtl8365mb_vlan_port_set_pvid(priv, port, prev_vlanmc_idx); ++ ++undo_vlan_mc_port_set: ++ if (prev_vlanmc.evid != vid) ++ (void)rtl8365mb_vlan_mc_port_set(ds, port, vid, false, NULL, ++ false); ++ ++ return ret; ++} ++ ++/* ++ * rtl8365mb_vlan_pvid_port_clear() - Remove a port's PVID configuration ++ * @ds: dsa switch instance ++ * @port: port index ++ * @vid: VLAN VID for PVID ++ * ++ * Resets the target port's hardware PVID allocation to 0. Cleans up and frees ++ * the associated VLANMC entry if no other ports are referencing it. ++ * ++ * Context: Can sleep. Must be called with &priv->vlan_lock held. ++ * Takes and releases &priv->map_lock. ++ * Return: 0 on success, or a negative error code on failure. ++ */ ++int rtl8365mb_vlan_pvid_port_clear(struct dsa_switch *ds, int port, u16 vid) ++{ ++ enum rtl8365mb_frame_ingress accepted_frame, prev_accepted_frame; ++ struct realtek_priv *priv = ds->priv; ++ struct rtl8365mb_vlanmc vlanmc = {0}; ++ u8 vlanmc_idx; ++ int ret; ++ ++ lockdep_assert_held(&priv->vlan_lock); ++ ++ ret = rtl8365mb_vlan_get_pvid_mc(priv, port, &vlanmc_idx, ++ &vlanmc); ++ if (ret) { ++ dev_err(priv->dev, "Failed to read current VLAN MC\n"); ++ return ret; ++ } ++ ++ /* Port is not using PVID. Nothing to remove. */ ++ if (!vlanmc_idx) ++ return 0; ++ ++ /* We are leaving a non PVID vlan, Nothing to remove. */ ++ if (vlanmc.evid != vid) ++ return 0; ++ ++ ret = rtl8365mb_vlan_port_get_framefilter(priv, port, ++ &prev_accepted_frame); ++ if (ret) { ++ dev_err(priv->dev, "Failed to get current framefilter\n"); ++ return ret; ++ } ++ ++ /* Changing accept frame is what really removes PVID. But only do ++ * that if we are filtering vlan ++ */ ++ if (dsa_port_is_vlan_filtering(dsa_to_port(ds, port))) { ++ accepted_frame = RTL8365MB_FRAME_TYPE_TAGGED_ONLY; ++ ++ ret = rtl8365mb_vlan_port_set_framefilter(priv, port, ++ accepted_frame); ++ if (ret) { ++ dev_err(priv->dev, "Failed to set port frame filter\n"); ++ return ret; ++ } ++ } else { ++ /* skip undo_set_framefilter */ ++ accepted_frame = prev_accepted_frame; ++ } ++ ++ ret = rtl8365mb_vlan_port_set_pvid(priv, port, 0); ++ if (ret) { ++ dev_err(priv->dev, "Failed to set port PVID to 0\n"); ++ goto undo_set_framefilter; ++ } ++ ++ /* Clears the VLAN MC membership and maybe VLAN MC entry if empty */ ++ ret = rtl8365mb_vlan_mc_port_set(ds, port, vlanmc.evid, ++ false, NULL, false); ++ if (ret) ++ goto undo_port_set_pvid; ++ ++ return 0; ++ ++undo_port_set_pvid: ++ (void)rtl8365mb_vlan_port_set_pvid(priv, port, vlanmc_idx); ++ ++undo_set_framefilter: ++ if (prev_accepted_frame != accepted_frame) ++ (void)rtl8365mb_vlan_port_set_framefilter(priv, port, ++ prev_accepted_frame); ++ ++ return ret; ++} +--- /dev/null ++++ b/drivers/net/dsa/realtek/rtl8365mb_vlan.h +@@ -0,0 +1,39 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* VLAN configuration interface for the rtl8365mb switch family ++ * ++ * Copyright (C) 2022 Alvin Å ipraga ++ * ++ */ ++ ++#ifndef _REALTEK_RTL8365MB_VLAN_H ++#define _REALTEK_RTL8365MB_VLAN_H ++ ++#include ++ ++#include "realtek.h" ++ ++enum rtl8365mb_frame_ingress { ++ RTL8365MB_FRAME_TYPE_ANY_FRAME = 0, ++ RTL8365MB_FRAME_TYPE_TAGGED_ONLY, ++ RTL8365MB_FRAME_TYPE_UNTAGGED_ONLY, ++}; ++ ++int rtl8365mb_vlan_port_get_pvid(struct realtek_priv *priv, int port, ++ u16 *pvid); ++int ++rtl8365mb_vlan_port_get_framefilter(struct realtek_priv *priv, ++ int port, ++ enum rtl8365mb_frame_ingress *frame_type); ++int ++rtl8365mb_vlan_port_set_framefilter(struct realtek_priv *priv, ++ int port, ++ enum rtl8365mb_frame_ingress frame_type); ++int rtl8365mb_vlan_4k_port_add(struct dsa_switch *ds, int port, ++ const struct switchdev_obj_port_vlan *vlan, ++ struct netlink_ext_ack *extack); ++int rtl8365mb_vlan_4k_port_del(struct dsa_switch *ds, int port, ++ const struct switchdev_obj_port_vlan *vlan); ++int rtl8365mb_vlan_pvid_port_set(struct dsa_switch *ds, int port, u16 vid, ++ struct netlink_ext_ack *extack); ++int rtl8365mb_vlan_pvid_port_clear(struct dsa_switch *ds, int port, u16 vid); ++#endif /* _REALTEK_RTL8365MB_VLAN_H */ +--- a/drivers/net/dsa/realtek/rtl83xx.c ++++ b/drivers/net/dsa/realtek/rtl83xx.c +@@ -155,6 +155,7 @@ rtl83xx_probe(struct device *dev, + return ERR_PTR(-ENOMEM); + + mutex_init(&priv->map_lock); ++ mutex_init(&priv->vlan_lock); + + rc.lock_arg = priv; + priv->map = devm_regmap_init(dev, NULL, priv, &rc); diff --git a/target/linux/generic/backport-6.12/942-07-v7.2-net-dsa-realtek-rtl8365mb-add-FDB-support.patch b/target/linux/generic/backport-6.12/942-07-v7.2-net-dsa-realtek-rtl8365mb-add-FDB-support.patch new file mode 100644 index 00000000000..b48f3bb4992 --- /dev/null +++ b/target/linux/generic/backport-6.12/942-07-v7.2-net-dsa-realtek-rtl8365mb-add-FDB-support.patch @@ -0,0 +1,1131 @@ +From 336e3e4a1ab37b6826fae27e53cd2ac43c9a96ca Mon Sep 17 00:00:00 2001 +From: Alvin Å ipraga +Date: Sat, 6 Jun 2026 05:29:31 -0300 +Subject: net: dsa: realtek: rtl8365mb: add FDB support +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Implement support for FDB and MDB management for the RTL8365MB series +switches. + +The hardware supports IVL by keying the unicast forwarding database with +the {MAC, VID, EFID} tuple. The Extended Filtering ID (EFID) is 3 bits +wide, providing 8 unique filtering domains. This driver reserves EFID 0 +for standalone ports, effectively limiting the hardware offload to a +maximum of 7 bridges. The multicast database uses a {MAC, VID} key, with +ports from different bridges sharing the same multicast group. + +Introduce a mutex lock (l2_lock) to protect concurrent L2 table updates. + +Add support for forwarding database operations, including unicast and +multicast entry handling as well as fast aging support. + +Set DSA switch flags assisted_learning_on_cpu_port and fdb_isolation. + +Signed-off-by: Alvin Å ipraga +Reviewed-by: Linus Walleij +Reviewed-by: Mieczyslaw Nalewaj +Co-developed-by: Luiz Angelo Daros de Luca +Signed-off-by: Luiz Angelo Daros de Luca +Link: https://patch.msgid.link/20260606-realtek_forward-v13-7-b9e409687cbe@gmail.com +Signed-off-by: Jakub Kicinski +--- + drivers/net/dsa/realtek/Makefile | 1 + + drivers/net/dsa/realtek/realtek.h | 28 ++ + drivers/net/dsa/realtek/rtl8365mb_l2.c | 576 +++++++++++++++++++++++++++++++ + drivers/net/dsa/realtek/rtl8365mb_l2.h | 32 ++ + drivers/net/dsa/realtek/rtl8365mb_main.c | 33 +- + drivers/net/dsa/realtek/rtl83xx.c | 292 ++++++++++++++++ + drivers/net/dsa/realtek/rtl83xx.h | 16 + + 7 files changed, 977 insertions(+), 1 deletion(-) + +--- a/drivers/net/dsa/realtek/Makefile ++++ b/drivers/net/dsa/realtek/Makefile +@@ -19,4 +19,5 @@ obj-$(CONFIG_NET_DSA_REALTEK_RTL8365MB) + rtl8365mb-objs := rtl8365mb_main.o \ + rtl8365mb_table.o \ + rtl8365mb_vlan.o \ ++ rtl8365mb_l2.o \ + # end of rtl8365mb-objs +--- a/drivers/net/dsa/realtek/realtek.h ++++ b/drivers/net/dsa/realtek/realtek.h +@@ -48,6 +48,12 @@ struct rtl8366_vlan_4k { + u8 fid; + }; + ++struct realtek_fdb_entry { ++ u8 mac_addr[ETH_ALEN]; ++ u16 vid; ++ bool is_static; ++}; ++ + struct realtek_priv { + struct device *dev; + struct reset_control *reset_ctl; +@@ -62,6 +68,15 @@ struct realtek_priv { + * deleting port VLAN memberships and PVID configurations. + */ + struct mutex vlan_lock; ++ /* l2_lock is used to prevent concurrent modifications of L2 table ++ * entries while another function is reading it. l2_(add,del)_mc ++ * is an example that first read current table entry and then ++ * create/update it. l2_(add|del)_uc uses a single table op and, ++ * internally, it might not need this lock. However, altering FDB ++ * may still collide, as well as l2_flush, with fdb_dump iterating ++ * over FDB. ++ */ ++ struct mutex l2_lock; + struct mii_bus *user_mii_bus; + struct mii_bus *bus; + int mdio_addr; +@@ -115,6 +130,19 @@ struct realtek_ops { + int (*enable_vlan)(struct realtek_priv *priv, bool enable); + int (*enable_vlan4k)(struct realtek_priv *priv, bool enable); + int (*enable_port)(struct realtek_priv *priv, int port, bool enable); ++ int (*l2_add_uc)(struct realtek_priv *priv, int port, ++ const unsigned char addr[ETH_ALEN], ++ u16 efid, u16 vid); ++ int (*l2_del_uc)(struct realtek_priv *priv, int port, ++ const unsigned char addr[ETH_ALEN], ++ u16 efid, u16 vid); ++ int (*l2_get_next_uc)(struct realtek_priv *priv, u16 *addr, ++ int port, struct realtek_fdb_entry *entry); ++ int (*l2_add_mc)(struct realtek_priv *priv, int port, ++ const unsigned char addr[ETH_ALEN], u16 vid); ++ int (*l2_del_mc)(struct realtek_priv *priv, int port, ++ const unsigned char addr[ETH_ALEN], u16 vid); ++ int (*l2_flush)(struct realtek_priv *priv, int port, u16 vid); + int (*phy_read)(struct realtek_priv *priv, int phy, int regnum); + int (*phy_write)(struct realtek_priv *priv, int phy, int regnum, + u16 val); +--- /dev/null ++++ b/drivers/net/dsa/realtek/rtl8365mb_l2.c +@@ -0,0 +1,576 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* Forwarding and multicast database interface for the rtl8365mb switch family ++ * ++ * Copyright (C) 2022 Alvin Å ipraga ++ */ ++ ++#include ++ ++#include "rtl8365mb_l2.h" ++#include "rtl8365mb_table.h" ++#include ++ ++#define RTL8365MB_L2_ENTRY_SIZE 6 ++ ++#define RTL8365MB_L2_UC_D0_MAC5_MSK GENMASK(7, 0) ++#define RTL8365MB_L2_UC_D0_MAC4_MSK GENMASK(15, 8) ++#define RTL8365MB_L2_UC_D1_MAC3_MSK GENMASK(7, 0) ++#define RTL8365MB_L2_UC_D1_MAC2_MSK GENMASK(15, 8) ++#define RTL8365MB_L2_UC_D2_MAC1_MSK GENMASK(7, 0) ++#define RTL8365MB_L2_UC_D2_MAC0_MSK GENMASK(15, 8) ++#define RTL8365MB_L2_UC_D3_VID_MSK GENMASK(11, 0) ++#define RTL8365MB_L2_UC_D3_IVL_MSK GENMASK(13, 13) ++#define RTL8365MB_L2_UC_D3_PORT_EXT_MSK GENMASK(15, 15) ++#define RTL8365MB_L2_UC_PORT_HI_MSK GENMASK(3, 3) ++#define RTL8365MB_L2_UC_D4_EFID_MSK GENMASK(2, 0) ++#define RTL8365MB_L2_UC_D4_FID_MSK GENMASK(6, 3) ++#define RTL8365MB_L2_UC_D4_SA_PRI_MSK GENMASK(7, 7) ++#define RTL8365MB_L2_UC_D4_PORT_MSK GENMASK(10, 8) ++#define RTL8365MB_L2_UC_PORT_LO_MSK GENMASK(2, 0) ++#define RTL8365MB_L2_UC_D4_AGE_MSK GENMASK(13, 11) ++#define RTL8365MB_L2_UC_D4_AUTH_MSK GENMASK(14, 14) ++#define RTL8365MB_L2_UC_D4_SA_BLOCK_MSK GENMASK(15, 15) ++ ++#define RTL8365MB_L2_UC_D5_DA_BLOCK_MSK GENMASK(0, 0) ++#define RTL8365MB_L2_UC_D5_PRIORITY_MSK GENMASK(3, 1) ++#define RTL8365MB_L2_UC_D5_FWD_PRI_MSK GENMASK(4, 4) ++#define RTL8365MB_L2_UC_D5_STATIC_MSK GENMASK(5, 5) ++ ++#define RTL8365MB_L2_MC_D0_MAC5_MSK GENMASK(7, 0) ++#define RTL8365MB_L2_MC_D0_MAC4_MSK GENMASK(15, 8) ++#define RTL8365MB_L2_MC_D1_MAC3_MSK GENMASK(7, 0) ++#define RTL8365MB_L2_MC_D1_MAC2_MSK GENMASK(15, 8) ++#define RTL8365MB_L2_MC_D2_MAC1_MSK GENMASK(7, 0) ++#define RTL8365MB_L2_MC_D2_MAC0_MSK GENMASK(15, 8) ++#define RTL8365MB_L2_MC_D3_VID_MSK GENMASK(11, 0) ++#define RTL8365MB_L2_MC_D3_IVL_MSK GENMASK(13, 13) ++#define RTL8365MB_L2_MC_D3_MBR_HI1_MSK GENMASK(15, 14) ++#define RTL8365MB_L2_MC_MBR_HI1_MSK GENMASK(9, 8) ++ ++#define RTL8365MB_L2_MC_D4_MBR_MSK GENMASK(7, 0) ++#define RTL8365MB_L2_MC_MBR_LO_MSK GENMASK(7, 0) ++#define RTL8365MB_L2_MC_D4_IGMPIDX_MSK GENMASK(15, 8) ++ ++#define RTL8365MB_L2_MC_D5_IGMP_ASIC_MSK GENMASK(0, 0) ++#define RTL8365MB_L2_MC_D5_PRIORITY_MSK GENMASK(3, 1) ++#define RTL8365MB_L2_MC_D5_FWD_PRI_MSK GENMASK(4, 4) ++#define RTL8365MB_L2_MC_D5_STATIC_MSK GENMASK(5, 5) ++#define RTL8365MB_L2_MC_D5_MBR_HI2_MSK GENMASK(7, 7) ++#define RTL8365MB_L2_MC_MBR_HI2_MSK GENMASK(10, 10) ++ ++/* Port flush command registers - writing a 1 to the port's MASK bit will ++ * initiate the flush procedure. Completion is signalled when the corresponding ++ * BUSY bit is 0. ++ */ ++#define RTL8365MB_L2_FLUSH_PORT_REG 0x0A36 ++#define RTL8365MB_L2_FLUSH_PORT_MSK_MSK GENMASK(7, 0) ++#define RTL8365MB_L2_FLUSH_PORT_BUSY_MSK GENMASK(15, 8) ++ ++#define RTL8365MB_L2_FLUSH_PORT_EXT_REG 0x0A35 ++#define RTL8365MB_L2_FLUSH_PORT_EXT_MSK_MSK GENMASK(2, 0) ++#define RTL8365MB_L2_FLUSH_PORT_EXT_BUSY_MSK GENMASK(5, 3) ++ ++#define RTL8365MB_L2_FLUSH_CTRL1_REG 0x0A37 ++#define RTL8365MB_L2_FLUSH_CTRL1_VID_MSK GENMASK(11, 0) ++#define RTL8365MB_L2_FLUSH_CTRL1_FID_MSK GENMASK(15, 12) ++ ++#define RTL8365MB_L2_FLUSH_CTRL2_REG 0x0A38 ++#define RTL8365MB_L2_FLUSH_CTRL2_MODE_MSK GENMASK(1, 0) ++#define RTL8365MB_L2_FLUSH_CTRL2_MODE_PORT 0 ++#define RTL8365MB_L2_FLUSH_CTRL2_MODE_PORT_VID 1 ++#define RTL8365MB_L2_FLUSH_CTRL2_MODE_PORT_FID 2 ++#define RTL8365MB_L2_FLUSH_CTRL2_TYPE_MSK GENMASK(2, 2) ++#define RTL8365MB_L2_FLUSH_CTRL2_TYPE_DYNAMIC 0 ++#define RTL8365MB_L2_FLUSH_CTRL2_TYPE_BOTH 1 ++ ++/* This flushes the entire LUT, reading it back it will turn 0 when the ++ * operation is complete ++ */ ++#define RTL8365MB_L2_FLUSH_CTRL3_REG 0x0A39 ++#define RTL8365MB_L2_FLUSH_CTRL3_MSK GENMASK(0, 0) ++ ++struct rtl8365mb_l2_uc_key { ++ u8 mac_addr[ETH_ALEN]; ++ u16 vid; ++ u16 fid; ++ bool ivl; ++ u16 efid; ++}; ++ ++struct rtl8365mb_l2_uc { ++ struct rtl8365mb_l2_uc_key key; ++ u8 port; ++ u8 age; ++ u8 priority; ++ ++ bool sa_block; ++ bool da_block; ++ bool auth; ++ bool is_static; ++ bool sa_pri; ++ bool fwd_pri; ++}; ++ ++struct rtl8365mb_l2_mc_key { ++ u8 mac_addr[ETH_ALEN]; ++ union { ++ u16 vid; /* IVL */ ++ u16 fid; /* SVL */ ++ }; ++ bool ivl; ++}; ++ ++struct rtl8365mb_l2_mc { ++ struct rtl8365mb_l2_mc_key key; ++ u16 member; ++ u8 priority; ++ u8 igmpidx; ++ ++ bool is_static; ++ bool fwd_pri; ++ bool igmp_asic; ++}; ++ ++static void rtl8365mb_l2_data_to_uc(const u16 *data, struct rtl8365mb_l2_uc *uc) ++{ ++ u32 val; ++ ++ uc->key.mac_addr[5] = FIELD_GET(RTL8365MB_L2_UC_D0_MAC5_MSK, data[0]); ++ uc->key.mac_addr[4] = FIELD_GET(RTL8365MB_L2_UC_D0_MAC4_MSK, data[0]); ++ uc->key.mac_addr[3] = FIELD_GET(RTL8365MB_L2_UC_D1_MAC3_MSK, data[1]); ++ uc->key.mac_addr[2] = FIELD_GET(RTL8365MB_L2_UC_D1_MAC2_MSK, data[1]); ++ uc->key.mac_addr[1] = FIELD_GET(RTL8365MB_L2_UC_D2_MAC1_MSK, data[2]); ++ uc->key.mac_addr[0] = FIELD_GET(RTL8365MB_L2_UC_D2_MAC0_MSK, data[2]); ++ uc->key.efid = FIELD_GET(RTL8365MB_L2_UC_D4_EFID_MSK, data[4]); ++ uc->key.vid = FIELD_GET(RTL8365MB_L2_UC_D3_VID_MSK, data[3]); ++ uc->key.ivl = FIELD_GET(RTL8365MB_L2_UC_D3_IVL_MSK, data[3]); ++ uc->key.fid = FIELD_GET(RTL8365MB_L2_UC_D4_FID_MSK, data[4]); ++ uc->age = FIELD_GET(RTL8365MB_L2_UC_D4_AGE_MSK, data[4]); ++ uc->auth = FIELD_GET(RTL8365MB_L2_UC_D4_AUTH_MSK, data[4]); ++ ++ val = FIELD_GET(RTL8365MB_L2_UC_D4_PORT_MSK, data[4]); ++ uc->port = FIELD_PREP(RTL8365MB_L2_UC_PORT_LO_MSK, val); ++ val = FIELD_GET(RTL8365MB_L2_UC_D3_PORT_EXT_MSK, data[3]); ++ uc->port |= FIELD_PREP(RTL8365MB_L2_UC_PORT_HI_MSK, val); ++ ++ uc->sa_pri = FIELD_GET(RTL8365MB_L2_UC_D4_SA_PRI_MSK, data[4]); ++ uc->fwd_pri = FIELD_GET(RTL8365MB_L2_UC_D5_FWD_PRI_MSK, data[5]); ++ uc->sa_block = FIELD_GET(RTL8365MB_L2_UC_D4_SA_BLOCK_MSK, data[4]); ++ uc->da_block = FIELD_GET(RTL8365MB_L2_UC_D5_DA_BLOCK_MSK, data[5]); ++ uc->priority = FIELD_GET(RTL8365MB_L2_UC_D5_PRIORITY_MSK, data[5]); ++ uc->is_static = FIELD_GET(RTL8365MB_L2_UC_D5_STATIC_MSK, data[5]); ++} ++ ++static void rtl8365mb_l2_uc_to_data(const struct rtl8365mb_l2_uc *uc, u16 *data) ++{ ++ u32 val; ++ ++ memset(data, 0, RTL8365MB_L2_ENTRY_SIZE * 2); ++ data[0] |= ++ FIELD_PREP(RTL8365MB_L2_UC_D0_MAC5_MSK, uc->key.mac_addr[5]); ++ data[0] |= ++ FIELD_PREP(RTL8365MB_L2_UC_D0_MAC4_MSK, uc->key.mac_addr[4]); ++ data[1] |= ++ FIELD_PREP(RTL8365MB_L2_UC_D1_MAC3_MSK, uc->key.mac_addr[3]); ++ data[1] |= ++ FIELD_PREP(RTL8365MB_L2_UC_D1_MAC2_MSK, uc->key.mac_addr[2]); ++ data[2] |= ++ FIELD_PREP(RTL8365MB_L2_UC_D2_MAC1_MSK, uc->key.mac_addr[1]); ++ data[2] |= ++ FIELD_PREP(RTL8365MB_L2_UC_D2_MAC0_MSK, uc->key.mac_addr[0]); ++ data[3] |= FIELD_PREP(RTL8365MB_L2_UC_D3_VID_MSK, uc->key.vid); ++ data[3] |= FIELD_PREP(RTL8365MB_L2_UC_D3_IVL_MSK, uc->key.ivl); ++ ++ val = FIELD_GET(RTL8365MB_L2_UC_PORT_HI_MSK, uc->port); ++ data[3] |= FIELD_PREP(RTL8365MB_L2_UC_D3_PORT_EXT_MSK, val); ++ ++ data[4] |= FIELD_PREP(RTL8365MB_L2_UC_D4_FID_MSK, uc->key.fid); ++ data[4] |= FIELD_PREP(RTL8365MB_L2_UC_D4_EFID_MSK, uc->key.efid); ++ data[4] |= FIELD_PREP(RTL8365MB_L2_UC_D4_AGE_MSK, uc->age); ++ data[4] |= FIELD_PREP(RTL8365MB_L2_UC_D4_AUTH_MSK, uc->auth); ++ ++ val = FIELD_GET(RTL8365MB_L2_UC_PORT_LO_MSK, uc->port); ++ data[4] |= FIELD_PREP(RTL8365MB_L2_UC_D4_PORT_MSK, val); ++ ++ data[4] |= FIELD_PREP(RTL8365MB_L2_UC_D4_SA_PRI_MSK, uc->sa_pri); ++ data[4] |= FIELD_PREP(RTL8365MB_L2_UC_D4_SA_BLOCK_MSK, uc->sa_block); ++ data[5] |= FIELD_PREP(RTL8365MB_L2_UC_D5_FWD_PRI_MSK, uc->fwd_pri); ++ data[5] |= FIELD_PREP(RTL8365MB_L2_UC_D5_DA_BLOCK_MSK, uc->da_block); ++ data[5] |= FIELD_PREP(RTL8365MB_L2_UC_D5_PRIORITY_MSK, uc->priority); ++ data[5] |= FIELD_PREP(RTL8365MB_L2_UC_D5_STATIC_MSK, uc->is_static); ++} ++ ++static void rtl8365mb_l2_data_to_mc(const u16 *data, struct rtl8365mb_l2_mc *mc) ++{ ++ u32 val; ++ ++ mc->key.mac_addr[5] = FIELD_GET(RTL8365MB_L2_MC_D0_MAC5_MSK, data[0]); ++ mc->key.mac_addr[4] = FIELD_GET(RTL8365MB_L2_MC_D0_MAC4_MSK, data[0]); ++ mc->key.mac_addr[3] = FIELD_GET(RTL8365MB_L2_MC_D1_MAC3_MSK, data[1]); ++ mc->key.mac_addr[2] = FIELD_GET(RTL8365MB_L2_MC_D1_MAC2_MSK, data[1]); ++ mc->key.mac_addr[1] = FIELD_GET(RTL8365MB_L2_MC_D2_MAC1_MSK, data[2]); ++ mc->key.mac_addr[0] = FIELD_GET(RTL8365MB_L2_MC_D2_MAC0_MSK, data[2]); ++ /* key.vid,key.fid shares the same memory space */ ++ mc->key.vid = FIELD_GET(RTL8365MB_L2_MC_D3_VID_MSK, data[3]); ++ mc->key.ivl = FIELD_GET(RTL8365MB_L2_MC_D3_IVL_MSK, data[3]); ++ mc->priority = FIELD_GET(RTL8365MB_L2_MC_D5_PRIORITY_MSK, data[5]); ++ mc->fwd_pri = FIELD_GET(RTL8365MB_L2_MC_D5_FWD_PRI_MSK, data[5]); ++ mc->is_static = FIELD_GET(RTL8365MB_L2_MC_D5_STATIC_MSK, data[5]); ++ ++ val = FIELD_GET(RTL8365MB_L2_MC_D4_MBR_MSK, data[4]); ++ mc->member = FIELD_PREP(RTL8365MB_L2_MC_MBR_LO_MSK, val); ++ val = FIELD_GET(RTL8365MB_L2_MC_D3_MBR_HI1_MSK, data[3]); ++ mc->member |= FIELD_PREP(RTL8365MB_L2_MC_MBR_HI1_MSK, val); ++ val = FIELD_GET(RTL8365MB_L2_MC_D5_MBR_HI2_MSK, data[5]); ++ mc->member |= FIELD_PREP(RTL8365MB_L2_MC_MBR_HI2_MSK, val); ++ ++ mc->igmpidx = FIELD_GET(RTL8365MB_L2_MC_D4_IGMPIDX_MSK, data[4]); ++ mc->igmp_asic = FIELD_GET(RTL8365MB_L2_MC_D5_IGMP_ASIC_MSK, data[5]); ++} ++ ++static void rtl8365mb_l2_mc_to_data(const struct rtl8365mb_l2_mc *mc, u16 *data) ++{ ++ u32 val; ++ ++ memset(data, 0, RTL8365MB_L2_ENTRY_SIZE * 2); ++ data[0] |= FIELD_PREP(RTL8365MB_L2_MC_D0_MAC5_MSK, mc->key.mac_addr[5]); ++ data[0] |= FIELD_PREP(RTL8365MB_L2_MC_D0_MAC4_MSK, mc->key.mac_addr[4]); ++ data[1] |= FIELD_PREP(RTL8365MB_L2_MC_D1_MAC3_MSK, mc->key.mac_addr[3]); ++ data[1] |= FIELD_PREP(RTL8365MB_L2_MC_D1_MAC2_MSK, mc->key.mac_addr[2]); ++ data[2] |= FIELD_PREP(RTL8365MB_L2_MC_D2_MAC1_MSK, mc->key.mac_addr[1]); ++ data[2] |= FIELD_PREP(RTL8365MB_L2_MC_D2_MAC0_MSK, mc->key.mac_addr[0]); ++ data[3] |= FIELD_PREP(RTL8365MB_L2_MC_D3_VID_MSK, mc->key.vid); ++ data[3] |= FIELD_PREP(RTL8365MB_L2_MC_D3_IVL_MSK, mc->key.ivl); ++ ++ val = FIELD_GET(RTL8365MB_L2_MC_MBR_HI1_MSK, mc->member); ++ data[3] |= FIELD_PREP(RTL8365MB_L2_MC_D3_MBR_HI1_MSK, val); ++ ++ val = FIELD_GET(RTL8365MB_L2_MC_MBR_LO_MSK, mc->member); ++ data[4] |= FIELD_PREP(RTL8365MB_L2_MC_D4_MBR_MSK, val); ++ ++ data[4] |= FIELD_PREP(RTL8365MB_L2_MC_D4_IGMPIDX_MSK, mc->igmpidx); ++ data[5] |= FIELD_PREP(RTL8365MB_L2_MC_D5_IGMP_ASIC_MSK, mc->igmp_asic); ++ data[5] |= FIELD_PREP(RTL8365MB_L2_MC_D5_PRIORITY_MSK, mc->priority); ++ data[5] |= FIELD_PREP(RTL8365MB_L2_MC_D5_FWD_PRI_MSK, mc->fwd_pri); ++ data[5] |= FIELD_PREP(RTL8365MB_L2_MC_D5_STATIC_MSK, mc->is_static); ++ ++ val = FIELD_GET(RTL8365MB_L2_MC_MBR_HI2_MSK, mc->member); ++ data[5] |= FIELD_PREP(RTL8365MB_L2_MC_D5_MBR_HI2_MSK, val); ++} ++ ++/* ++ * rtl8365mb_l2_get_next_uc() - get the next Unicast L2 entry ++ * @priv: realtek_priv pointer ++ * @addr: as input, the table index to start the walk ++ * as output, the found table index ++ * @port: restrict the walk on entries related to port ++ * @entry: returned L2 Unicast table entry ++ * ++ * This function gets the next unicast L2 table entry starting from @addr ++ * and checking exclusively entries related to @port. ++ * ++ * On success, it returns 0, updates @addr to the index of the found entry, ++ * and populates @entry. If the search reaches the end of the table and ++ * wraps around and @addr will be strictly lower than the input @addr. ++ * Callers must detect this wrap-around condition to prevent infinite loops. ++ * ++ * If the table contains no matching entries at all, it returns -ENOENT ++ * and leaves @addr and @entry unmodified. ++ * ++ * Return: Returns 0 on success, a negative error on failure. ++ **/ ++int rtl8365mb_l2_get_next_uc(struct realtek_priv *priv, u16 *addr, int port, ++ struct realtek_fdb_entry *entry) ++{ ++ u16 data[RTL8365MB_L2_ENTRY_SIZE] = { 0 }; ++ struct rtl8365mb_l2_uc uc; ++ int ret; ++ ++ ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2, ++ RTL8365MB_TABLE_OP_READ, addr, ++ RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC_PORT, ++ port, data, RTL8365MB_L2_ENTRY_SIZE); ++ if (ret) ++ return ret; ++ ++ rtl8365mb_l2_data_to_uc(data, &uc); ++ ++ ether_addr_copy(entry->mac_addr, uc.key.mac_addr); ++ entry->vid = uc.key.vid; ++ entry->is_static = uc.is_static; ++ ++ return 0; ++} ++ ++int rtl8365mb_l2_add_uc(struct realtek_priv *priv, int port, ++ const unsigned char mac_addr[static ETH_ALEN], ++ u16 efid, u16 vid) ++{ ++ u16 data[RTL8365MB_L2_ENTRY_SIZE] = { 0 }; ++ struct rtl8365mb_l2_uc uc = { 0 }; ++ u16 addr; ++ int ret; ++ ++ memcpy(uc.key.mac_addr, mac_addr, ETH_ALEN); ++ uc.key.efid = efid; ++ uc.key.fid = 0; ++ uc.key.ivl = true; ++ uc.key.vid = vid; ++ ++ uc.port = port; ++ /* Entries programmed by DSA (including those dynamically learned by ++ * the software bridge and injected into the CPU port via assisted ++ * learning) must be static. We do not let HW decrease age behind the ++ * OS's back. As a trade-off, these will show up as permanent to users. ++ */ ++ uc.is_static = true; ++ /* age greater than 0 adds/updates entries */ ++ uc.age = 1; ++ rtl8365mb_l2_uc_to_data(&uc, data); ++ ++ /* add the new entry or update an existing one */ ++ ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2, ++ RTL8365MB_TABLE_OP_WRITE, &addr, ++ 0, 0, ++ data, RTL8365MB_L2_ENTRY_SIZE); ++ ++ /* Assume the missing new entry as the table is full */ ++ if (ret == -ENOENT) ++ return -ENOSPC; ++ ++ /* addr will hold the table index, but it is not used here */ ++ return ret; ++} ++ ++int rtl8365mb_l2_del_uc(struct realtek_priv *priv, int port, ++ const unsigned char mac_addr[static ETH_ALEN], ++ u16 efid, u16 vid) ++{ ++ u16 data[RTL8365MB_L2_ENTRY_SIZE] = { 0 }; ++ struct rtl8365mb_l2_uc uc = { 0 }; ++ u16 addr; ++ int ret; ++ ++ memcpy(uc.key.mac_addr, mac_addr, ETH_ALEN); ++ uc.key.efid = efid; ++ uc.key.fid = 0; ++ uc.key.ivl = true; ++ uc.key.vid = vid; ++ /* age 0 deletes the entry */ ++ uc.age = 0; ++ rtl8365mb_l2_uc_to_data(&uc, data); ++ ++ /* it looks like the switch will always add/update the entry, ++ * even when age is 0 or uc.key did not match an existing entry, ++ * just to immediately drop it because age is zero. You can still ++ * get the added/updated address from @addr ++ */ ++ ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2, ++ RTL8365MB_TABLE_OP_WRITE, &addr, ++ 0, 0, ++ data, RTL8365MB_L2_ENTRY_SIZE); ++ ++ if (ret == -ENOENT) { ++ dev_dbg(priv->dev, "%s: %pM vid=%d efid=%d missing\n", ++ __func__, mac_addr, vid, efid); ++ /* Silently return success */ ++ return 0; ++ } ++ ++ /* addr will hold the table index, but it is not used here */ ++ return ret; ++} ++ ++int rtl8365mb_l2_flush(struct realtek_priv *priv, int port, u16 vid) ++{ ++ int mode = vid ? RTL8365MB_L2_FLUSH_CTRL2_MODE_PORT_VID : ++ RTL8365MB_L2_FLUSH_CTRL2_MODE_PORT; ++ u32 val, mask; ++ int ret; ++ ++ mutex_lock(&priv->map_lock); ++ ++ /* Configure flushing mode; only flush dynamic entries */ ++ ret = regmap_write(priv->map_nolock, RTL8365MB_L2_FLUSH_CTRL2_REG, ++ FIELD_PREP(RTL8365MB_L2_FLUSH_CTRL2_MODE_MSK, ++ mode) | ++ FIELD_PREP(RTL8365MB_L2_FLUSH_CTRL2_TYPE_MSK, ++ RTL8365MB_L2_FLUSH_CTRL2_TYPE_DYNAMIC)); ++ if (ret) ++ goto out; ++ ++ ret = regmap_write(priv->map_nolock, RTL8365MB_L2_FLUSH_CTRL1_REG, ++ FIELD_PREP(RTL8365MB_L2_FLUSH_CTRL1_VID_MSK, vid)); ++ ++ if (ret) ++ goto out; ++ /* Now issue the flush command and wait for its completion. There are ++ * two registers for this purpose, and which one to use depends on the ++ * port number. The _EXT register is for ports 8 or higher. ++ */ ++ if (port < 8) { ++ val = FIELD_PREP(RTL8365MB_L2_FLUSH_PORT_MSK_MSK, ++ BIT(port) & 0xFF); ++ ret = regmap_write(priv->map_nolock, ++ RTL8365MB_L2_FLUSH_PORT_REG, val); ++ if (ret) ++ goto out; ++ ++ mask = FIELD_PREP(RTL8365MB_L2_FLUSH_PORT_BUSY_MSK, ++ BIT(port) & 0xFF); ++ ret = regmap_read_poll_timeout(priv->map_nolock, ++ RTL8365MB_L2_FLUSH_PORT_REG, ++ val, !(val & mask), 10, 10000); ++ if (ret) ++ goto out; ++ } else { ++ val = FIELD_PREP(RTL8365MB_L2_FLUSH_PORT_EXT_MSK_MSK, ++ BIT(port) >> 8); ++ ret = regmap_write(priv->map_nolock, ++ RTL8365MB_L2_FLUSH_PORT_EXT_REG, val); ++ if (ret) ++ goto out; ++ ++ mask = FIELD_PREP(RTL8365MB_L2_FLUSH_PORT_EXT_BUSY_MSK, ++ BIT(port) >> 8); ++ ret = regmap_read_poll_timeout(priv->map_nolock, ++ RTL8365MB_L2_FLUSH_PORT_EXT_REG, ++ val, !(val & mask), 10, 10000); ++ if (ret) ++ goto out; ++ } ++ ++out: ++ mutex_unlock(&priv->map_lock); ++ ++ return ret; ++} ++ ++int rtl8365mb_l2_add_mc(struct realtek_priv *priv, int port, ++ const unsigned char mac_addr[static ETH_ALEN], ++ u16 vid) ++{ ++ u16 data[RTL8365MB_L2_ENTRY_SIZE] = { 0 }; ++ struct rtl8365mb_l2_mc mc = { 0 }; ++ u16 addr; ++ int ret; ++ ++ memcpy(mc.key.mac_addr, mac_addr, ETH_ALEN); ++ mc.key.vid = vid; ++ mc.key.ivl = true; ++ /* Already set the port and is_static, although not used in OP_READ, ++ * data will be ready for OP_WRITE if it is a new entry. ++ */ ++ mc.member |= BIT(port); ++ mc.is_static = 1; ++ rtl8365mb_l2_mc_to_data(&mc, data); ++ ++ /* First look for an existing entry (to get existing port members) */ ++ ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2, ++ RTL8365MB_TABLE_OP_READ, &addr, ++ RTL8365MB_TABLE_L2_METHOD_MAC, 0, ++ data, RTL8365MB_L2_ENTRY_SIZE); ++ if (!ret) { ++ /* There is already an entry... */ ++ rtl8365mb_l2_data_to_mc(data, &mc); ++ dev_dbg(priv->dev, ++ "%s: found %pM addr=%d member=0x%x igmpidx=0x%x %s\n", ++ __func__, mac_addr, addr, mc.member, mc.igmpidx, ++ mc.is_static ? "static" : "dynamic"); ++ /* the port must be added as a member */ ++ mc.member |= BIT(port); ++ ++ if (!mc.is_static) { ++ dev_dbg(priv->dev, ++ "%s: promoting addr=%d group to static\n", ++ __func__, addr); ++ mc.is_static = 1; ++ } ++ ++ rtl8365mb_l2_mc_to_data(&mc, data); ++ } else if (ret == -ENOENT) { ++ /* New entry, no need to update data again as it already ++ * includes the member. ++ * ++ * Multicast hardware entries do not support EFID (bridge ++ * isolation). However, traffic isolation is still maintained ++ * because the hardware applies the port isolation masks ++ * (pmasks) configured in bridge_join after the L2 lookup. ++ * Entries from different bridges will collide on the same ++ * MAC+VID slot with an OR'ed member mask, but packets will ++ * only exit through ports allowed by the source port's pmask. ++ */ ++ } else { ++ return ret; ++ } ++ ++ /* add the new entry or update an existing one */ ++ ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2, ++ RTL8365MB_TABLE_OP_WRITE, &addr, ++ 0, 0, ++ data, RTL8365MB_L2_ENTRY_SIZE); ++ ++ /* Assume the missing new entry as the table is full */ ++ if (ret == -ENOENT) ++ return -ENOSPC; ++ ++ return ret; ++} ++ ++int rtl8365mb_l2_del_mc(struct realtek_priv *priv, int port, ++ const unsigned char mac_addr[static ETH_ALEN], ++ u16 vid) ++{ ++ u16 data[RTL8365MB_L2_ENTRY_SIZE] = { 0 }; ++ struct rtl8365mb_l2_mc mc = { 0 }; ++ u16 addr; ++ int ret; ++ ++ memcpy(mc.key.mac_addr, mac_addr, ETH_ALEN); ++ mc.key.vid = vid; ++ mc.key.ivl = true; ++ rtl8365mb_l2_mc_to_data(&mc, data); ++ ++ /* First look for an existing entry (to get existing port members) */ ++ ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2, ++ RTL8365MB_TABLE_OP_READ, &addr, ++ RTL8365MB_TABLE_L2_METHOD_MAC, 0, ++ data, RTL8365MB_L2_ENTRY_SIZE); ++ if (ret == -ENOENT) { ++ dev_dbg(priv->dev, "%s: %pM vid=%d missing\n", ++ __func__, mac_addr, vid); ++ /* Silently return success */ ++ return 0; ++ } ++ ++ if (ret) ++ /* Return on any other error */ ++ return ret; ++ ++ rtl8365mb_l2_data_to_mc(data, &mc); ++ dev_dbg(priv->dev, ++ "%s: found %pM addr=%d member=0x%x igmpidx=0x%x %s\n", ++ __func__, mac_addr, addr, mc.member, mc.igmpidx, ++ mc.is_static ? "static" : "dynamic"); ++ /* the port must be removed as a member */ ++ mc.member &= ~BIT(port); ++ if (!mc.member) { ++ /* Multicast entries do not have an age field. Clearing both ++ * the member portmask and is_static flags is the hardware ++ * signal to invalidate and reclaim the L2 table slot. ++ */ ++ mc.is_static = 0; ++ mc.igmpidx = 0; ++ mc.priority = 0; ++ mc.fwd_pri = 0; ++ mc.igmp_asic = 0; ++ } ++ rtl8365mb_l2_mc_to_data(&mc, data); ++ ++ /* update the existing entry. */ ++ ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2, ++ RTL8365MB_TABLE_OP_WRITE, &addr, ++ 0, 0, ++ data, RTL8365MB_L2_ENTRY_SIZE); ++ return ret; ++} +--- /dev/null ++++ b/drivers/net/dsa/realtek/rtl8365mb_l2.h +@@ -0,0 +1,32 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* Forwarding and multicast database interface for the rtl8365mb switch family ++ * ++ * Copyright (C) 2022 Alvin Å ipraga ++ */ ++ ++#ifndef _REALTEK_RTL8365MB_L2_H ++#define _REALTEK_RTL8365MB_L2_H ++ ++#include ++#include ++ ++#include "realtek.h" ++ ++int rtl8365mb_l2_get_next_uc(struct realtek_priv *priv, u16 *addr, int port, ++ struct realtek_fdb_entry *entry); ++int rtl8365mb_l2_add_uc(struct realtek_priv *priv, int port, ++ const unsigned char addr[static ETH_ALEN], ++ u16 efid, u16 vid); ++int rtl8365mb_l2_del_uc(struct realtek_priv *priv, int port, ++ const unsigned char addr[static ETH_ALEN], ++ u16 efid, u16 vid); ++int rtl8365mb_l2_flush(struct realtek_priv *priv, int port, u16 vid); ++ ++int rtl8365mb_l2_add_mc(struct realtek_priv *priv, int port, ++ const unsigned char mac_addr[static ETH_ALEN], ++ u16 vid); ++int rtl8365mb_l2_del_mc(struct realtek_priv *priv, int port, ++ const unsigned char mac_addr[static ETH_ALEN], ++ u16 vid); ++ ++#endif /* _REALTEK_RTL8365MB_L2_H */ +--- a/drivers/net/dsa/realtek/rtl8365mb_main.c ++++ b/drivers/net/dsa/realtek/rtl8365mb_main.c +@@ -104,6 +104,7 @@ + #include "realtek-smi.h" + #include "realtek-mdio.h" + #include "rtl83xx.h" ++#include "rtl8365mb_l2.h" + #include "rtl8365mb_vlan.h" + + /* Family-specific data and limits */ +@@ -111,8 +112,12 @@ + #define RTL8365MB_NUM_PHYREGS 32 + #define RTL8365MB_PHYREGMAX (RTL8365MB_NUM_PHYREGS - 1) + #define RTL8365MB_MAX_NUM_PORTS 11 +-#define RTL8365MB_MAX_NUM_EXTINTS 3 ++/* Valid for the whole family except RTL8370B, which has 4160 entries. ++ * RTL8370B is mentioned in vendor code but it might not even belong ++ * to the same RTL8367C family. ++ */ + #define RTL8365MB_LEARN_LIMIT_MAX 2112 ++#define RTL8365MB_MAX_NUM_EXTINTS 3 + + /* Chip identification registers */ + #define RTL8365MB_CHIP_ID_REG 0x1300 +@@ -285,6 +290,15 @@ + (RTL8365MB_PORT_ISOLATION_REG_BASE + (_physport)) + #define RTL8365MB_PORT_ISOLATION_MASK 0x07FF + ++/* Extended filter ID registers - used to key forwarding database with IVL */ ++#define RTL8365MB_EFID_MASK GENMASK(2, 0) ++#define RTL8365MB_PORT_EFID_REG_BASE 0x0A32 ++#define RTL8365MB_PORT_EFID_REG(_p) \ ++ (RTL8365MB_PORT_EFID_REG_BASE + ((_p) >> 2)) ++#define RTL8365MB_PORT_EFID_OFFSET(_p) (((_p) & 0x3) << 2) ++#define RTL8365MB_PORT_EFID_MASK(_p) \ ++ (RTL8365MB_EFID_MASK << RTL8365MB_PORT_EFID_OFFSET(_p)) ++ + /* MSTP port state registers - indexed by tree instance */ + #define RTL8365MB_MSTI_CTRL_BASE 0x0A00 + #define RTL8365MB_MSTI_CTRL_REG(_msti, _physport) \ +@@ -2432,6 +2446,11 @@ static int rtl8365mb_setup(struct dsa_sw + if (ret) + goto out_teardown_irq; + ++ ds->assisted_learning_on_cpu_port = true; ++ ds->fdb_isolation = true; ++ /* The EFID is 3 bits, but EFID 0 is reserved for standalone ports */ ++ ds->max_num_bridges = FIELD_MAX(RTL8365MB_EFID_MASK); ++ + ds->configure_vlan_while_not_filtering = true; + + /* Set up VLAN */ +@@ -2549,6 +2568,12 @@ static const struct dsa_switch_ops rtl83 + .teardown = rtl8365mb_teardown, + .phylink_get_caps = rtl8365mb_phylink_get_caps, + .port_stp_state_set = rtl8365mb_port_stp_state_set, ++ .port_fast_age = rtl83xx_port_fast_age, ++ .port_fdb_add = rtl83xx_port_fdb_add, ++ .port_fdb_del = rtl83xx_port_fdb_del, ++ .port_fdb_dump = rtl83xx_port_fdb_dump, ++ .port_mdb_add = rtl83xx_port_mdb_add, ++ .port_mdb_del = rtl83xx_port_mdb_del, + .port_vlan_add = rtl8365mb_port_vlan_add, + .port_vlan_del = rtl8365mb_port_vlan_del, + .port_vlan_filtering = rtl8365mb_port_vlan_filtering, +@@ -2565,6 +2590,12 @@ static const struct dsa_switch_ops rtl83 + + static const struct realtek_ops rtl8365mb_ops = { + .detect = rtl8365mb_detect, ++ .l2_add_uc = rtl8365mb_l2_add_uc, ++ .l2_del_uc = rtl8365mb_l2_del_uc, ++ .l2_get_next_uc = rtl8365mb_l2_get_next_uc, ++ .l2_add_mc = rtl8365mb_l2_add_mc, ++ .l2_del_mc = rtl8365mb_l2_del_mc, ++ .l2_flush = rtl8365mb_l2_flush, + .phy_read = rtl8365mb_phy_read, + .phy_write = rtl8365mb_phy_write, + }; +--- a/drivers/net/dsa/realtek/rtl83xx.c ++++ b/drivers/net/dsa/realtek/rtl83xx.c +@@ -3,6 +3,7 @@ + #include + #include + #include ++#include + + #include "realtek.h" + #include "rtl83xx.h" +@@ -156,6 +157,7 @@ rtl83xx_probe(struct device *dev, + + mutex_init(&priv->map_lock); + mutex_init(&priv->vlan_lock); ++ mutex_init(&priv->l2_lock); + + rc.lock_arg = priv; + priv->map = devm_regmap_init(dev, NULL, priv, &rc); +@@ -326,6 +328,296 @@ void rtl83xx_reset_deassert(struct realt + gpiod_set_value(priv->reset, false); + } + ++/** ++ * rtl83xx_port_fast_age() - flush dynamic FDB entries learned on a port ++ * @ds: DSA switch instance ++ * @port: port index ++ * ++ * This function requests the switch to age out dynamic FDB entries learned on ++ * @port. ++ * ++ * Context: Can sleep. ++ * Return: Nothing. ++ */ ++void rtl83xx_port_fast_age(struct dsa_switch *ds, int port) ++{ ++ struct realtek_priv *priv = ds->priv; ++ int ret; ++ ++ if (!priv->ops->l2_flush) { ++ dev_warn_once(priv->dev, "l2_flush op not defined\n"); ++ return; ++ } ++ ++ dev_dbg(priv->dev, "fast_age port %d\n", port); ++ ++ mutex_lock(&priv->l2_lock); ++ ret = priv->ops->l2_flush(priv, port, 0); ++ mutex_unlock(&priv->l2_lock); ++ if (ret) ++ dev_err(priv->dev, "failed to fast age on port %d: %d\n", port, ++ ret); ++} ++EXPORT_SYMBOL_NS_GPL(rtl83xx_port_fast_age, REALTEK_DSA); ++ ++/** ++ * rtl83xx_port_fdb_add() - add a static FDB entry to a port database ++ * @ds: DSA switch instance ++ * @port: port index ++ * @addr: MAC address to add ++ * @vid: VLAN ID associated with @addr ++ * @db: database where the entry should be added ++ * ++ * This function adds a static unicast FDB entry to the standalone port ++ * database or to a bridge database. ++ * ++ * Context: Can sleep. ++ * Return: 0 on success, negative value for failure. ++ */ ++int rtl83xx_port_fdb_add(struct dsa_switch *ds, int port, ++ const unsigned char *addr, u16 vid, ++ struct dsa_db db) ++{ ++ struct realtek_priv *priv = ds->priv; ++ int efid; ++ int ret; ++ ++ if (is_multicast_ether_addr(addr)) ++ return -EOPNOTSUPP; ++ ++ if (!priv->ops->l2_add_uc) ++ return -EOPNOTSUPP; ++ ++ if (db.type != DSA_DB_PORT && db.type != DSA_DB_BRIDGE) ++ return -EOPNOTSUPP; ++ ++ /* Bridge ports use bridge.num as EFID, while standalone ports use ++ * EFID 0. FDB entries for the CPU port follow the bridge EFID due ++ * to assisted learning. ++ */ ++ efid = db.type == DSA_DB_BRIDGE ? db.bridge.num : 0; ++ ++ dev_dbg(priv->dev, "%s: port:%d addr:%pM efid:%d vid:%d dbtype:%d\n", ++ __func__, port, addr, efid, vid, db.type); ++ ++ mutex_lock(&priv->l2_lock); ++ ret = priv->ops->l2_add_uc(priv, port, addr, efid, vid); ++ ++ mutex_unlock(&priv->l2_lock); ++ ++ if (ret) ++ dev_err(priv->dev, "fdb_add ERROR %pe\n", ERR_PTR(ret)); ++ return ret; ++} ++EXPORT_SYMBOL_NS_GPL(rtl83xx_port_fdb_add, REALTEK_DSA); ++ ++/** ++ * rtl83xx_port_fdb_del() - delete a static FDB entry from a port database ++ * @ds: DSA switch instance ++ * @port: port index ++ * @addr: MAC address to delete ++ * @vid: VLAN ID associated with @addr ++ * @db: database where the entry should be removed ++ * ++ * This function deletes a static unicast FDB entry from the standalone port ++ * database or from a bridge database. ++ * ++ * Context: Can sleep. ++ * Return: 0 on success, negative value for failure. ++ */ ++int rtl83xx_port_fdb_del(struct dsa_switch *ds, int port, ++ const unsigned char *addr, u16 vid, ++ struct dsa_db db) ++{ ++ struct realtek_priv *priv = ds->priv; ++ int efid; ++ int ret; ++ ++ if (is_multicast_ether_addr(addr)) ++ return -EOPNOTSUPP; ++ ++ if (!priv->ops->l2_del_uc) ++ return -EOPNOTSUPP; ++ ++ if (db.type != DSA_DB_PORT && db.type != DSA_DB_BRIDGE) ++ return -EOPNOTSUPP; ++ ++ /* ++ * DSA_DB_BRIDGE ports use bridge number [1..N] as EFID, while ++ * DSA_DB_PORT use the default EFID (0), not used by any bridge. ++ */ ++ efid = db.type == DSA_DB_BRIDGE ? db.bridge.num : 0; ++ ++ dev_dbg(priv->dev, "%s: port:%d addr:%pM efid:%d vid:%d dbtype:%d\n", ++ __func__, port, addr, efid, vid, db.type); ++ ++ mutex_lock(&priv->l2_lock); ++ ret = priv->ops->l2_del_uc(priv, port, addr, efid, vid); ++ mutex_unlock(&priv->l2_lock); ++ ++ if (ret) ++ dev_err(priv->dev, "fdb_del ERROR %pe\n", ERR_PTR(ret)); ++ return ret; ++} ++EXPORT_SYMBOL_NS_GPL(rtl83xx_port_fdb_del, REALTEK_DSA); ++ ++/** ++ * rtl83xx_port_fdb_dump() - iterate over FDB entries associated with a port ++ * @ds: DSA switch instance ++ * @port: port index ++ * @cb: callback invoked for each entry ++ * @data: opaque pointer passed to @cb ++ * ++ * This function walks the unicast FDB entries associated with @port and calls ++ * @cb for each matching entry. ++ * ++ * Context: Can sleep. ++ * Return: 0 on success, or negative value for failure. ++ */ ++int rtl83xx_port_fdb_dump(struct dsa_switch *ds, int port, ++ dsa_fdb_dump_cb_t *cb, void *data) ++{ ++ struct realtek_fdb_entry entry = { 0 }; ++ struct realtek_priv *priv = ds->priv; ++ u16 start_addr, addr = 0; ++ int ret = 0; ++ ++ if (!priv->ops->l2_get_next_uc) ++ return -EOPNOTSUPP; ++ ++ mutex_lock(&priv->l2_lock); ++ while (true) { ++ start_addr = addr; ++ ++ dev_dbg(priv->dev, "l2_get_next_uc, addr:%d, port:%d\n", ++ addr, port); ++ ret = priv->ops->l2_get_next_uc(priv, &addr, port, &entry); ++ dev_dbg(priv->dev, ++ "%s addr:%d mac:%pM vid:%d static:%d ret:%pe\n", ++ __func__, addr, entry.mac_addr, entry.vid, ++ entry.is_static, ERR_PTR(ret)); ++ ++ if (ret == -ENOENT) { ++ /* If the table is empty, returns without errors. Note ++ * that the l2_get_next_uc overflow to the first match ++ * when it reaches the end of the table. ++ */ ++ ret = 0; ++ break; ++ } ++ ++ if (ret) ++ break; ++ ++ /* When the addr returned is before the requested one, it ++ * indicates that we reached the end. ++ */ ++ if (addr < start_addr) ++ break; ++ ++ ret = cb(entry.mac_addr, entry.vid, entry.is_static, data); ++ if (ret) ++ break; ++ ++ addr++; ++ } ++ mutex_unlock(&priv->l2_lock); ++ ++ return ret; ++} ++EXPORT_SYMBOL_NS_GPL(rtl83xx_port_fdb_dump, REALTEK_DSA); ++ ++/** ++ * rtl83xx_port_mdb_add() - add a multicast database entry to a port database ++ * @ds: DSA switch instance ++ * @port: port index ++ * @mdb: multicast database entry to add ++ * @db: database where the entry should be added ++ * ++ * This function adds a multicast database entry to the standalone port ++ * database or to a bridge database. ++ * ++ * Context: Can sleep. ++ * Return: 0 on success, negative value for failure. ++ */ ++int rtl83xx_port_mdb_add(struct dsa_switch *ds, int port, ++ const struct switchdev_obj_port_mdb *mdb, ++ struct dsa_db db) ++{ ++ struct realtek_priv *priv = ds->priv; ++ const unsigned char *addr = mdb->addr; ++ u16 vid = mdb->vid; ++ int efid; ++ int ret; ++ ++ if (!priv->ops->l2_add_mc) ++ return -EOPNOTSUPP; ++ ++ if (db.type != DSA_DB_PORT && db.type != DSA_DB_BRIDGE) ++ return -EOPNOTSUPP; ++ ++ /* EFID is not used by hardware MDB entries; debugging only */ ++ efid = db.type == DSA_DB_BRIDGE ? db.bridge.num : 0; ++ ++ dev_dbg(priv->dev, "%s: port:%d addr:%pM efid:%d vid:%d dbtype:%d\n", ++ __func__, port, addr, efid, vid, db.type); ++ ++ mutex_lock(&priv->l2_lock); ++ ret = priv->ops->l2_add_mc(priv, port, addr, vid); ++ mutex_unlock(&priv->l2_lock); ++ ++ if (ret) ++ dev_err(priv->dev, "mdb_add ERROR %pe\n", ERR_PTR(ret)); ++ return ret; ++} ++EXPORT_SYMBOL_NS_GPL(rtl83xx_port_mdb_add, REALTEK_DSA); ++ ++/** ++ * rtl83xx_port_mdb_del() - delete a multicast database entry from a port ++ * database ++ * @ds: DSA switch instance ++ * @port: port index ++ * @mdb: multicast database entry to delete ++ * @db: database where the entry should be removed ++ * ++ * This function deletes a multicast database entry from the standalone port ++ * database or from a bridge database. ++ * ++ * Context: Can sleep. ++ * Return: 0 on success, negative value for failure. ++ */ ++int rtl83xx_port_mdb_del(struct dsa_switch *ds, int port, ++ const struct switchdev_obj_port_mdb *mdb, ++ struct dsa_db db) ++{ ++ struct realtek_priv *priv = ds->priv; ++ const unsigned char *addr = mdb->addr; ++ u16 vid = mdb->vid; ++ int efid; ++ int ret; ++ ++ if (!priv->ops->l2_del_mc) ++ return -EOPNOTSUPP; ++ ++ if (db.type != DSA_DB_PORT && db.type != DSA_DB_BRIDGE) ++ return -EOPNOTSUPP; ++ ++ /* EFID is not used by hardware MDB entries; debugging only */ ++ efid = db.type == DSA_DB_BRIDGE ? db.bridge.num : 0; ++ ++ dev_dbg(priv->dev, "%s: port:%d addr:%pM efid:%d vid:%d dbtype:%d\n", ++ __func__, port, addr, efid, vid, db.type); ++ ++ mutex_lock(&priv->l2_lock); ++ ret = priv->ops->l2_del_mc(priv, port, addr, vid); ++ mutex_unlock(&priv->l2_lock); ++ ++ if (ret) ++ dev_err(priv->dev, "mdb_del ERROR %pe\n", ERR_PTR(ret)); ++ return ret; ++} ++EXPORT_SYMBOL_NS_GPL(rtl83xx_port_mdb_del, REALTEK_DSA); ++ + MODULE_AUTHOR("Luiz Angelo Daros de Luca "); + MODULE_AUTHOR("Linus Walleij "); + MODULE_DESCRIPTION("Realtek DSA switches common module"); +--- a/drivers/net/dsa/realtek/rtl83xx.h ++++ b/drivers/net/dsa/realtek/rtl83xx.h +@@ -21,4 +21,20 @@ void rtl83xx_remove(struct realtek_priv + void rtl83xx_reset_assert(struct realtek_priv *priv); + void rtl83xx_reset_deassert(struct realtek_priv *priv); + ++void rtl83xx_port_fast_age(struct dsa_switch *ds, int port); ++int rtl83xx_port_fdb_add(struct dsa_switch *ds, int port, ++ const unsigned char *addr, u16 vid, ++ struct dsa_db db); ++int rtl83xx_port_fdb_del(struct dsa_switch *ds, int port, ++ const unsigned char *addr, u16 vid, ++ struct dsa_db db); ++int rtl83xx_port_fdb_dump(struct dsa_switch *ds, int port, ++ dsa_fdb_dump_cb_t *cb, void *data); ++int rtl83xx_port_mdb_add(struct dsa_switch *ds, int port, ++ const struct switchdev_obj_port_mdb *mdb, ++ struct dsa_db db); ++int rtl83xx_port_mdb_del(struct dsa_switch *ds, int port, ++ const struct switchdev_obj_port_mdb *mdb, ++ struct dsa_db db); ++ + #endif /* _RTL83XX_H */ diff --git a/target/linux/generic/backport-6.12/942-08-v7.2-net-dsa-realtek-rtl8365mb-add-port_bridge_-join-leav.patch b/target/linux/generic/backport-6.12/942-08-v7.2-net-dsa-realtek-rtl8365mb-add-port_bridge_-join-leav.patch new file mode 100644 index 00000000000..5395f3dc44e --- /dev/null +++ b/target/linux/generic/backport-6.12/942-08-v7.2-net-dsa-realtek-rtl8365mb-add-port_bridge_-join-leav.patch @@ -0,0 +1,335 @@ +From 183bd68b1fe1d5ad584355a7449eea32da79334a Mon Sep 17 00:00:00 2001 +From: Alvin Å ipraga +Date: Sat, 6 Jun 2026 05:29:32 -0300 +Subject: net: dsa: realtek: rtl8365mb: add port_bridge_{join,leave} +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Implement hardware offloading of bridge functionality. This is achieved +by using the per-port isolation registers, which contain a forwarding +port mask. The switch will refuse to forward packets ingressed on a +given port to a port which is not in its forwarding mask. + +For each bridge that is offloaded, use the DSA-provided bridge number +for the Extended Filtering ID (EFID). When using Independent VLAN +Learning (IVL), the forwarding database is keyed with the tuple +{VID, MAC, EFID}. There are 8 EFIDs available (0~7), but we reserve the +default EFID 0 for standalone ports where learning is disabled. This +fits nicely because DSA indexes the bridge number starting from 1. + +Because of the limited number of EFIDs, we have to set the +max_num_bridges property of our switch to 7: we can't offload more than +that or we will fail to offer IVL as at least two bridges would end up +having to share an EFID. + +All ports start isolated, forwarding exclusively to CPU ports, and +with VLAN transparent, ignoring VLAN membership. Once a member in a +bridge, the port isolation is expanded to include the bridge members. +When that bridge enables VLAN filtering, the VLAN transparent feature is +disabled, letting the switch filter based on VLAN setup. + +Signed-off-by: Alvin Å ipraga +Reviewed-by: Linus Walleij +Reviewed-by: Mieczyslaw Nalewaj +Co-developed-by: Luiz Angelo Daros de Luca +Signed-off-by: Luiz Angelo Daros de Luca +Link: https://patch.msgid.link/20260606-realtek_forward-v13-8-b9e409687cbe@gmail.com +Signed-off-by: Jakub Kicinski +--- + drivers/net/dsa/realtek/realtek.h | 7 ++ + drivers/net/dsa/realtek/rtl8365mb_main.c | 47 ++++++++- + drivers/net/dsa/realtek/rtl83xx.c | 169 +++++++++++++++++++++++++++++++ + drivers/net/dsa/realtek/rtl83xx.h | 7 ++ + 4 files changed, 229 insertions(+), 1 deletion(-) + +--- a/drivers/net/dsa/realtek/realtek.h ++++ b/drivers/net/dsa/realtek/realtek.h +@@ -130,6 +130,13 @@ struct realtek_ops { + int (*enable_vlan)(struct realtek_priv *priv, bool enable); + int (*enable_vlan4k)(struct realtek_priv *priv, bool enable); + int (*enable_port)(struct realtek_priv *priv, int port, bool enable); ++ int (*port_add_isolation)(struct realtek_priv *priv, int port, ++ u32 mask); ++ int (*port_remove_isolation)(struct realtek_priv *priv, int port, ++ u32 mask); ++ int (*port_set_efid)(struct realtek_priv *priv, int port, u32 efid); ++ int (*port_set_learning)(struct realtek_priv *priv, int port, ++ bool enable); + int (*l2_add_uc)(struct realtek_priv *priv, int port, + const unsigned char addr[ETH_ALEN], + u16 efid, u16 vid); +--- a/drivers/net/dsa/realtek/rtl8365mb_main.c ++++ b/drivers/net/dsa/realtek/rtl8365mb_main.c +@@ -1568,10 +1568,44 @@ static int rtl8365mb_port_set_learning(s + enable ? RTL8365MB_LEARN_LIMIT_MAX : 0); + } + ++static int rtl8365mb_port_set_efid(struct realtek_priv *priv, int port, ++ u32 efid) ++{ ++ return regmap_update_bits(priv->map, RTL8365MB_PORT_EFID_REG(port), ++ RTL8365MB_PORT_EFID_MASK(port), ++ efid << RTL8365MB_PORT_EFID_OFFSET(port)); ++} ++ ++/* Port isolation manipulation functions. ++ * ++ * The port isolation register controls the forwarding mask of a given ++ * port. The switch will not forward packets ingressed on a given port ++ * to ports which are not enabled in its forwarding mask. ++ * ++ * The port forwarding mask has the highest priority in forwarding ++ * decisions. The only exception to this rule is when the switch ++ * receives a packet on its CPU port with ALLOW=0. In that case the TX ++ * field of the CPU tag will override the forwarding port mask. ++ */ + static int rtl8365mb_port_set_isolation(struct realtek_priv *priv, int port, + u32 mask) + { +- return regmap_write(priv->map, RTL8365MB_PORT_ISOLATION_REG(port), mask); ++ return regmap_write(priv->map, RTL8365MB_PORT_ISOLATION_REG(port), ++ mask); ++} ++ ++static int rtl8365mb_port_add_isolation(struct realtek_priv *priv, int port, ++ u32 mask) ++{ ++ return regmap_update_bits(priv->map, RTL8365MB_PORT_ISOLATION_REG(port), ++ mask, mask); ++} ++ ++static int rtl8365mb_port_remove_isolation(struct realtek_priv *priv, int port, ++ u32 mask) ++{ ++ return regmap_update_bits(priv->map, RTL8365MB_PORT_ISOLATION_REG(port), ++ mask, 0); + } + + static int rtl8365mb_mib_counter_read(struct realtek_priv *priv, int port, +@@ -2378,6 +2412,11 @@ static int rtl8365mb_setup(struct dsa_sw + if (ret) + goto out_teardown_irq; + ++ /* Set the default EFID 0 for standalone mode */ ++ ret = rtl8365mb_port_set_efid(priv, dp->index, 0); ++ if (ret) ++ goto out_teardown_irq; ++ + /* Disable learning */ + ret = rtl8365mb_port_set_learning(priv, dp->index, false); + if (ret) +@@ -2567,6 +2606,8 @@ static const struct dsa_switch_ops rtl83 + .setup = rtl8365mb_setup, + .teardown = rtl8365mb_teardown, + .phylink_get_caps = rtl8365mb_phylink_get_caps, ++ .port_bridge_join = rtl83xx_port_bridge_join, ++ .port_bridge_leave = rtl83xx_port_bridge_leave, + .port_stp_state_set = rtl8365mb_port_stp_state_set, + .port_fast_age = rtl83xx_port_fast_age, + .port_fdb_add = rtl83xx_port_fdb_add, +@@ -2590,6 +2631,10 @@ static const struct dsa_switch_ops rtl83 + + static const struct realtek_ops rtl8365mb_ops = { + .detect = rtl8365mb_detect, ++ .port_add_isolation = rtl8365mb_port_add_isolation, ++ .port_remove_isolation = rtl8365mb_port_remove_isolation, ++ .port_set_efid = rtl8365mb_port_set_efid, ++ .port_set_learning = rtl8365mb_port_set_learning, + .l2_add_uc = rtl8365mb_l2_add_uc, + .l2_del_uc = rtl8365mb_l2_del_uc, + .l2_get_next_uc = rtl8365mb_l2_get_next_uc, +--- a/drivers/net/dsa/realtek/rtl83xx.c ++++ b/drivers/net/dsa/realtek/rtl83xx.c +@@ -329,6 +329,175 @@ void rtl83xx_reset_deassert(struct realt + } + + /** ++ * rtl83xx_port_bridge_join() - join a port to a bridge ++ * @ds: DSA switch instance ++ * @port: port index ++ * @bridge: bridge being joined ++ * @tx_forward_offload: if the switch can offload TX forwarding ++ * @extack: netlink extended ack for reporting errors ++ * ++ * This function handles joining a port to a bridge. It updates the port ++ * isolation masks and EFID. ++ * ++ * Context: Can sleep. ++ * Return: 0 on success, negative value for failure. ++ */ ++int rtl83xx_port_bridge_join(struct dsa_switch *ds, int port, ++ struct dsa_bridge bridge, ++ bool *tx_forward_offload, ++ struct netlink_ext_ack *extack) ++{ ++ struct realtek_priv *priv = ds->priv; ++ struct dsa_port *dp; ++ u32 mask = 0; ++ int ret; ++ ++ if (!priv->ops->port_add_isolation) ++ return -EOPNOTSUPP; ++ ++ if (!priv->ops->port_set_learning) ++ return -EOPNOTSUPP; ++ ++ dev_dbg(priv->dev, "bridge %d join port %d\n", bridge.num, port); ++ ++ /* Add this port to the isolation group of every other port ++ * offloading this bridge. ++ */ ++ dsa_switch_for_each_user_port(dp, ds) { ++ /* Handle this port after */ ++ if (dp->index == port) ++ continue; ++ ++ /* Skip ports that are not in this bridge */ ++ if (!dsa_port_offloads_bridge(dp, &bridge)) ++ continue; ++ ++ ret = priv->ops->port_add_isolation(priv, dp->index, BIT(port)); ++ if (ret) ++ goto undo_isolation; ++ ++ mask |= BIT(dp->index); ++ } ++ ++ /* If we support cascade switches, it should also include the ++ * downstream DSA ports to the isolation group. ++ */ ++ ++ /* Add those ports to the isolation group of this port */ ++ ret = priv->ops->port_add_isolation(priv, port, mask); ++ if (ret) ++ goto undo_isolation; ++ ++ /* Use the bridge number as the EFID for this port */ ++ if (priv->ops->port_set_efid) { ++ ret = priv->ops->port_set_efid(priv, port, bridge.num); ++ if (ret) ++ goto undo_self_isolation; ++ } ++ ++ ret = priv->ops->port_set_learning(priv, port, true); ++ if (ret) ++ goto undo_efid; ++ ++ return 0; ++ ++undo_efid: ++ if (priv->ops->port_set_efid) ++ priv->ops->port_set_efid(priv, port, 0); ++ ++undo_self_isolation: ++ priv->ops->port_remove_isolation(priv, port, mask); ++ ++undo_isolation: ++ dsa_switch_for_each_port(dp, ds) { ++ if (mask & BIT(dp->index)) ++ priv->ops->port_remove_isolation(priv, dp->index, ++ BIT(port)); ++ } ++ ++ return ret; ++} ++EXPORT_SYMBOL_NS_GPL(rtl83xx_port_bridge_join, REALTEK_DSA); ++ ++/** ++ * rtl83xx_port_bridge_leave() - leave a bridge ++ * @ds: DSA switch instance ++ * @port: port index ++ * @bridge: bridge being left ++ * ++ * This function handles removing a port from a bridge. It updates the port ++ * isolation masks and EFID. ++ * ++ * Context: Can sleep. ++ * Return: nothing ++ */ ++void rtl83xx_port_bridge_leave(struct dsa_switch *ds, int port, ++ struct dsa_bridge bridge) ++{ ++ struct realtek_priv *priv = ds->priv; ++ struct dsa_port *dp; ++ u32 mask = 0; ++ int ret; ++ ++ if (!priv->ops->port_remove_isolation) ++ return; ++ ++ if (!priv->ops->port_set_learning) ++ return; ++ ++ dev_dbg(priv->dev, "bridge %d leave port %d\n", bridge.num, port); ++ ++ /* Remove this port from the isolation group of every other ++ * port offloading this bridge. ++ */ ++ dsa_switch_for_each_user_port(dp, ds) { ++ /* Handle this port after */ ++ if (dp->index == port) ++ continue; ++ ++ /* Skip ports that are not in this bridge */ ++ if (!dsa_port_offloads_bridge(dp, &bridge)) ++ continue; ++ ++ ret = priv->ops->port_remove_isolation(priv, dp->index, ++ BIT(port)); ++ if (ret) ++ dev_err(priv->dev, ++ "failed to isolate port %d from port %d: %pe\n", ++ port, dp->index, ERR_PTR(ret)); ++ ++ mask |= BIT(dp->index); ++ } ++ ++ /* If we support cascade switches, it should also exclude the ++ * downstream DSA ports from the isolation group. ++ */ ++ ++ ret = priv->ops->port_set_learning(priv, port, false); ++ if (ret) ++ dev_err(priv->dev, ++ "failed to disable learning on port %d: %pe\n", ++ port, ERR_PTR(ret)); ++ ++ /* Remove those ports from the isolation group of this port */ ++ ret = priv->ops->port_remove_isolation(priv, port, mask); ++ if (ret) ++ dev_err(priv->dev, ++ "failed to remove isolation mask from port %d: %pe\n", ++ port, ERR_PTR(ret)); ++ ++ /* Revert to the default EFID 0 for standalone mode */ ++ if (priv->ops->port_set_efid) { ++ ret = priv->ops->port_set_efid(priv, port, 0); ++ if (ret) ++ dev_err(priv->dev, ++ "failed to clear EFID on port %d: %pe\n", ++ port, ERR_PTR(ret)); ++ } ++} ++EXPORT_SYMBOL_NS_GPL(rtl83xx_port_bridge_leave, REALTEK_DSA); ++ ++/** + * rtl83xx_port_fast_age() - flush dynamic FDB entries learned on a port + * @ds: DSA switch instance + * @port: port index +--- a/drivers/net/dsa/realtek/rtl83xx.h ++++ b/drivers/net/dsa/realtek/rtl83xx.h +@@ -21,6 +21,13 @@ void rtl83xx_remove(struct realtek_priv + void rtl83xx_reset_assert(struct realtek_priv *priv); + void rtl83xx_reset_deassert(struct realtek_priv *priv); + ++int rtl83xx_port_bridge_join(struct dsa_switch *ds, int port, ++ struct dsa_bridge bridge, ++ bool *tx_forward_offload, ++ struct netlink_ext_ack *extack); ++void rtl83xx_port_bridge_leave(struct dsa_switch *ds, int port, ++ struct dsa_bridge bridge); ++ + void rtl83xx_port_fast_age(struct dsa_switch *ds, int port); + int rtl83xx_port_fdb_add(struct dsa_switch *ds, int port, + const unsigned char *addr, u16 vid, diff --git a/target/linux/generic/backport-6.12/942-09-v7.2-net-dsa-realtek-rtl8365mb-add-bridge-port-flags.patch b/target/linux/generic/backport-6.12/942-09-v7.2-net-dsa-realtek-rtl8365mb-add-bridge-port-flags.patch new file mode 100644 index 00000000000..c2f71e6c1cb --- /dev/null +++ b/target/linux/generic/backport-6.12/942-09-v7.2-net-dsa-realtek-rtl8365mb-add-bridge-port-flags.patch @@ -0,0 +1,279 @@ +From 660a9e399ab02c0cb86d277ed6b0c9d10c350fdd Mon Sep 17 00:00:00 2001 +From: Luiz Angelo Daros de Luca +Date: Sat, 6 Jun 2026 05:29:33 -0300 +Subject: net: dsa: realtek: rtl8365mb: add bridge port flags + +Implement support for bridge port flags to control learning and flooding +behavior. This patch maps hardware functionalities to the following +bridge flags: + +- BR_LEARNING +- BR_FLOOD +- BR_MCAST_FLOOD +- BR_BCAST_FLOOD + +By default, all flooding types are enabled during port setup to ensure +standard bridge behavior. + +Reviewed-by: Linus Walleij +Reviewed-by: Mieczyslaw Nalewaj +Signed-off-by: Luiz Angelo Daros de Luca +Link: https://patch.msgid.link/20260606-realtek_forward-v13-9-b9e409687cbe@gmail.com +Signed-off-by: Jakub Kicinski +--- + drivers/net/dsa/realtek/realtek.h | 6 ++ + drivers/net/dsa/realtek/rtl8365mb_main.c | 68 +++++++++++++++++++++ + drivers/net/dsa/realtek/rtl83xx.c | 101 +++++++++++++++++++++++++++++++ + drivers/net/dsa/realtek/rtl83xx.h | 4 ++ + 4 files changed, 179 insertions(+) + +--- a/drivers/net/dsa/realtek/realtek.h ++++ b/drivers/net/dsa/realtek/realtek.h +@@ -137,6 +137,12 @@ struct realtek_ops { + int (*port_set_efid)(struct realtek_priv *priv, int port, u32 efid); + int (*port_set_learning)(struct realtek_priv *priv, int port, + bool enable); ++ int (*port_set_ucast_flood)(struct realtek_priv *priv, int port, ++ bool enable); ++ int (*port_set_mcast_flood)(struct realtek_priv *priv, int port, ++ bool enable); ++ int (*port_set_bcast_flood)(struct realtek_priv *priv, int port, ++ bool enable); + int (*l2_add_uc)(struct realtek_priv *priv, int port, + const unsigned char addr[ETH_ALEN], + u16 efid, u16 vid); +--- a/drivers/net/dsa/realtek/rtl8365mb_main.c ++++ b/drivers/net/dsa/realtek/rtl8365mb_main.c +@@ -307,6 +307,21 @@ + #define RTL8365MB_MSTI_CTRL_PORT_STATE_MASK(_physport) \ + (0x3 << RTL8365MB_MSTI_CTRL_PORT_STATE_OFFSET((_physport))) + ++/* Unknown unicast DA flooding port mask */ ++#define RTL8365MB_UNKNOWN_UNICAST_FLOODING_PMASK_REG 0x0890 ++#define RTL8365MB_UNKNOWN_UNICAST_FLOODING_PMASK_MASK 0x07FF ++ ++/* Unknown multicast DA flooding port mask */ ++#define RTL8365MB_UNKNOWN_MULTICAST_FLOODING_PMASK_REG 0x0891 ++#define RTL8365MB_UNKNOWN_MULTICAST_FLOODING_PMASK_MASK 0x07FF ++ ++/* Broadcast flooding port mask */ ++#define RTL8365MB_UNKNOWN_BROADCAST_FLOODING_PMASK_REG 0x0892 ++#define RTL8365MB_UNKNOWN_BROADCAST_FLOODING_PMASK_MASK 0x07FF ++ ++#define RTL8365MB_SUPPORTED_BRIDGE_FLAGS \ ++ (BR_LEARNING | BR_FLOOD | BR_MCAST_FLOOD | BR_BCAST_FLOOD) ++ + /* Miscellaneous port configuration register, incl. VLAN egress mode */ + #define RTL8365MB_PORT_MISC_CFG_REG_BASE 0x000E + #define RTL8365MB_PORT_MISC_CFG_REG(_p) \ +@@ -1568,6 +1583,49 @@ static int rtl8365mb_port_set_learning(s + enable ? RTL8365MB_LEARN_LIMIT_MAX : 0); + } + ++static int rtl8365mb_port_set_ucast_flood(struct realtek_priv *priv, int port, ++ bool enable) ++{ ++ /* Frames with unknown unicast DA will be flooded to a programmable ++ * port mask that by default includes all ports. Add or remove ++ * the specified port from this port mask accordingly. ++ */ ++ return regmap_update_bits(priv->map, ++ RTL8365MB_UNKNOWN_UNICAST_FLOODING_PMASK_REG, ++ BIT(port), enable ? BIT(port) : 0); ++} ++ ++static int rtl8365mb_port_set_mcast_flood(struct realtek_priv *priv, int port, ++ bool enable) ++{ ++ return regmap_update_bits(priv->map, ++ RTL8365MB_UNKNOWN_MULTICAST_FLOODING_PMASK_REG, ++ BIT(port), enable ? BIT(port) : 0); ++} ++ ++static int rtl8365mb_port_set_bcast_flood(struct realtek_priv *priv, int port, ++ bool enable) ++{ ++ return regmap_update_bits(priv->map, ++ RTL8365MB_UNKNOWN_BROADCAST_FLOODING_PMASK_REG, ++ BIT(port), enable ? BIT(port) : 0); ++} ++ ++static int rtl8365mb_port_pre_bridge_flags(struct dsa_switch *ds, int port, ++ struct switchdev_brport_flags flags, ++ struct netlink_ext_ack *extack) ++{ ++ struct realtek_priv *priv = ds->priv; ++ ++ dev_dbg(priv->dev, "pre_bridge_flags port:%d flags:%lx supported:%lx\n", ++ port, flags.mask, RTL8365MB_SUPPORTED_BRIDGE_FLAGS); ++ ++ if (flags.mask & ~RTL8365MB_SUPPORTED_BRIDGE_FLAGS) ++ return -EINVAL; ++ ++ return 0; ++} ++ + static int rtl8365mb_port_set_efid(struct realtek_priv *priv, int port, + u32 efid) + { +@@ -2422,6 +2480,11 @@ static int rtl8365mb_setup(struct dsa_sw + if (ret) + goto out_teardown_irq; + ++ /* Enable all types of flooding */ ++ ret = rtl83xx_setup_port_flood_control(priv, dp->index); ++ if (ret) ++ goto out_teardown_irq; ++ + /* Set up per-port private data */ + p->priv = priv; + p->index = dp->index; +@@ -2608,6 +2671,8 @@ static const struct dsa_switch_ops rtl83 + .phylink_get_caps = rtl8365mb_phylink_get_caps, + .port_bridge_join = rtl83xx_port_bridge_join, + .port_bridge_leave = rtl83xx_port_bridge_leave, ++ .port_pre_bridge_flags = rtl8365mb_port_pre_bridge_flags, ++ .port_bridge_flags = rtl83xx_port_bridge_flags, + .port_stp_state_set = rtl8365mb_port_stp_state_set, + .port_fast_age = rtl83xx_port_fast_age, + .port_fdb_add = rtl83xx_port_fdb_add, +@@ -2635,6 +2700,9 @@ static const struct realtek_ops rtl8365m + .port_remove_isolation = rtl8365mb_port_remove_isolation, + .port_set_efid = rtl8365mb_port_set_efid, + .port_set_learning = rtl8365mb_port_set_learning, ++ .port_set_ucast_flood = rtl8365mb_port_set_ucast_flood, ++ .port_set_mcast_flood = rtl8365mb_port_set_mcast_flood, ++ .port_set_bcast_flood = rtl8365mb_port_set_bcast_flood, + .l2_add_uc = rtl8365mb_l2_add_uc, + .l2_del_uc = rtl8365mb_l2_del_uc, + .l2_get_next_uc = rtl8365mb_l2_get_next_uc, +--- a/drivers/net/dsa/realtek/rtl83xx.c ++++ b/drivers/net/dsa/realtek/rtl83xx.c +@@ -3,6 +3,7 @@ + #include + #include + #include ++#include + #include + + #include "realtek.h" +@@ -787,6 +788,106 @@ int rtl83xx_port_mdb_del(struct dsa_swit + } + EXPORT_SYMBOL_NS_GPL(rtl83xx_port_mdb_del, REALTEK_DSA); + ++/** ++ * rtl83xx_port_bridge_flags() - set port bridge flags ++ * @ds: DSA switch instance ++ * @port: port index ++ * @flags: bridge port flags ++ * @extack: netlink extended ack for reporting errors ++ * ++ * This function handles setting bridge port flags like learning and flooding. ++ * ++ * Context: Can sleep. ++ * Return: 0 on success, negative value for failure. ++ */ ++int rtl83xx_port_bridge_flags(struct dsa_switch *ds, int port, ++ struct switchdev_brport_flags flags, ++ struct netlink_ext_ack *extack) ++{ ++ struct realtek_priv *priv = ds->priv; ++ bool enable; ++ int ret; ++ ++ if (flags.mask & BR_LEARNING) { ++ if (!priv->ops->port_set_learning) ++ return -EOPNOTSUPP; ++ ++ enable = !!(flags.val & BR_LEARNING); ++ ret = priv->ops->port_set_learning(priv, port, enable); ++ if (ret) ++ return ret; ++ } ++ ++ if (flags.mask & BR_FLOOD) { ++ if (!priv->ops->port_set_ucast_flood) ++ return -EOPNOTSUPP; ++ ++ enable = !!(flags.val & BR_FLOOD); ++ ret = priv->ops->port_set_ucast_flood(priv, port, enable); ++ if (ret) ++ return ret; ++ } ++ ++ if (flags.mask & BR_MCAST_FLOOD) { ++ if (!priv->ops->port_set_mcast_flood) ++ return -EOPNOTSUPP; ++ ++ enable = !!(flags.val & BR_MCAST_FLOOD); ++ ret = priv->ops->port_set_mcast_flood(priv, port, enable); ++ if (ret) ++ return ret; ++ } ++ ++ if (flags.mask & BR_BCAST_FLOOD) { ++ if (!priv->ops->port_set_bcast_flood) ++ return -EOPNOTSUPP; ++ ++ enable = !!(flags.val & BR_BCAST_FLOOD); ++ ret = priv->ops->port_set_bcast_flood(priv, port, enable); ++ if (ret) ++ return ret; ++ } ++ ++ return 0; ++} ++EXPORT_SYMBOL_NS_GPL(rtl83xx_port_bridge_flags, REALTEK_DSA); ++ ++/** ++ * rtl83xx_setup_port_flood_control() - setup default flood control for a port ++ * @priv: realtek_priv pointer ++ * @port: port index ++ * ++ * This function enables flooding for a given port. ++ * ++ * Context: Can sleep. ++ * Return: 0 on success, negative value for failure. ++ */ ++int rtl83xx_setup_port_flood_control(struct realtek_priv *priv, int port) ++{ ++ int ret; ++ ++ if (priv->ops->port_set_ucast_flood) { ++ ret = priv->ops->port_set_ucast_flood(priv, port, true); ++ if (ret) ++ return ret; ++ } ++ ++ if (priv->ops->port_set_mcast_flood) { ++ ret = priv->ops->port_set_mcast_flood(priv, port, true); ++ if (ret) ++ return ret; ++ } ++ ++ if (priv->ops->port_set_bcast_flood) { ++ ret = priv->ops->port_set_bcast_flood(priv, port, true); ++ if (ret) ++ return ret; ++ } ++ ++ return 0; ++} ++EXPORT_SYMBOL_NS_GPL(rtl83xx_setup_port_flood_control, REALTEK_DSA); ++ + MODULE_AUTHOR("Luiz Angelo Daros de Luca "); + MODULE_AUTHOR("Linus Walleij "); + MODULE_DESCRIPTION("Realtek DSA switches common module"); +--- a/drivers/net/dsa/realtek/rtl83xx.h ++++ b/drivers/net/dsa/realtek/rtl83xx.h +@@ -27,6 +27,10 @@ int rtl83xx_port_bridge_join(struct dsa_ + struct netlink_ext_ack *extack); + void rtl83xx_port_bridge_leave(struct dsa_switch *ds, int port, + struct dsa_bridge bridge); ++int rtl83xx_port_bridge_flags(struct dsa_switch *ds, int port, ++ struct switchdev_brport_flags flags, ++ struct netlink_ext_ack *extack); ++int rtl83xx_setup_port_flood_control(struct realtek_priv *priv, int port); + + void rtl83xx_port_fast_age(struct dsa_switch *ds, int port); + int rtl83xx_port_fdb_add(struct dsa_switch *ds, int port, diff --git a/target/linux/generic/backport-6.18/940-01-v7.1-net-dsa-tag_rtl8_4-update-format-description.patch b/target/linux/generic/backport-6.18/940-01-v7.1-net-dsa-tag_rtl8_4-update-format-description.patch new file mode 100644 index 00000000000..d4c129a3653 --- /dev/null +++ b/target/linux/generic/backport-6.18/940-01-v7.1-net-dsa-tag_rtl8_4-update-format-description.patch @@ -0,0 +1,70 @@ +From 297e1f411ed4927a912c7e207ba6f978cb1f9f0e Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Alvin=20=C5=A0ipraga?= +Date: Wed, 8 Apr 2026 17:31:01 -0300 +Subject: [PATCH 1/2] net: dsa: tag_rtl8_4: update format description +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Document the updated tag layout fields (EFID, VSEL/VIDX) and clarify +which bits are set/cleared when emitting tags. + +Co-developed-by: Alvin Å ipraga +Signed-off-by: Alvin Å ipraga +Signed-off-by: Luiz Angelo Daros de Luca +Reviewed-by: Linus Walleij +Link: https://patch.msgid.link/20260408-realtek_fixes-v1-1-915ff1404d56@gmail.com +Signed-off-by: Jakub Kicinski +--- + net/dsa/tag_rtl8_4.c | 17 ++++++++++------- + 1 file changed, 10 insertions(+), 7 deletions(-) + +--- a/net/dsa/tag_rtl8_4.c ++++ b/net/dsa/tag_rtl8_4.c +@@ -17,8 +17,8 @@ + * | (8-bit) | (8-bit) | + * | Protocol [0x04] | REASON | b + * |-----------------------------------+-----------------------------------| y +- * | (1) | (1) | (2) | (1) | (3) | (1) | (1) | (1) | (5) | t +- * | FID_EN | X | FID | PRI_EN | PRI | KEEP | X | LEARN_DIS | X | e ++ * | (1) | (3) | (1) | (3) | (1) | (1) | (1) | (5) | t ++ * | EFID_EN | EFID | PRI_EN | PRI | KEEP | VSEL | LEARN_DIS | VIDX | e + * |-----------------------------------+-----------------------------------| s + * | (1) | (15-bit) | | + * | ALLOW | TX/RX | v +@@ -32,19 +32,22 @@ + * EtherType | note that Realtek uses the same EtherType for + * | other incompatible tag formats (e.g. tag_rtl4_a.c) + * Protocol | 0x04: indicates that this tag conforms to this format +- * X | reserved + * ------------+------------- + * REASON | reason for forwarding packet to CPU + * | 0: packet was forwarded or flooded to CPU + * | 80: packet was trapped to CPU +- * FID_EN | 1: packet has an FID +- * | 0: no FID +- * FID | FID of packet (if FID_EN=1) ++ * EFID_EN | 1: packet has an EFID ++ * | 0: no EFID ++ * EFID | Extended filter ID (EFID) of packet (if EFID_EN=1) + * PRI_EN | 1: force priority of packet + * | 0: don't force priority + * PRI | priority of packet (if PRI_EN=1) + * KEEP | preserve packet VLAN tag format ++ * VSEL | 0: switch should classify packet according to VLAN tag ++ * | 1: switch should classify packet according to VLAN membership ++ * | configuration with index VIDX + * LEARN_DIS | don't learn the source MAC address of the packet ++ * VIDX | index of a VLAN membership configuration to use with VSEL + * ALLOW | 1: treat TX/RX field as an allowance port mask, meaning the + * | packet may only be forwarded to ports specified in the + * | mask +@@ -112,7 +115,7 @@ static void rtl8_4_write_tag(struct sk_b + /* Set Protocol; zero REASON */ + tag16[1] = htons(FIELD_PREP(RTL8_4_PROTOCOL, RTL8_4_PROTOCOL_RTL8365MB)); + +- /* Zero FID_EN, FID, PRI_EN, PRI, KEEP; set LEARN_DIS */ ++ /* Zero EFID_EN, EFID, PRI_EN, PRI, VSEL, VIDX, KEEP; set LEARN_DIS */ + tag16[2] = htons(FIELD_PREP(RTL8_4_LEARN_DIS, 1)); + + /* Zero ALLOW; set RX (CPU->switch) forwarding port mask */ diff --git a/target/linux/generic/backport-6.18/940-02-v7.1-net-dsa-tag_rtl8_4-set-KEEP-flag.patch b/target/linux/generic/backport-6.18/940-02-v7.1-net-dsa-tag_rtl8_4-set-KEEP-flag.patch new file mode 100644 index 00000000000..7a9ea343165 --- /dev/null +++ b/target/linux/generic/backport-6.18/940-02-v7.1-net-dsa-tag_rtl8_4-set-KEEP-flag.patch @@ -0,0 +1,48 @@ +From 82f37bd9a4d779495479c0c13152208d5400c8a4 Mon Sep 17 00:00:00 2001 +From: Luiz Angelo Daros de Luca +Date: Wed, 8 Apr 2026 17:31:02 -0300 +Subject: [PATCH 2/2] net: dsa: tag_rtl8_4: set KEEP flag +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +KEEP=1 is needed because we should respect the format of the packet as +the kernel sends it to us. Unless tx forward offloading is used, the +kernel is giving us the packet exactly as it should leave the specified +port on the wire. Until now this was not needed because the ports were +always functioning in a standalone mode in a VLAN-unaware way, so the +switch would not tag or untag frames anyway. But arguably it should have +been KEEP=1 all along. + +Co-developed-by: Alvin Å ipraga +Signed-off-by: Alvin Å ipraga +Signed-off-by: Luiz Angelo Daros de Luca +Reviewed-by: Linus Walleij +Link: https://patch.msgid.link/20260408-realtek_fixes-v1-2-915ff1404d56@gmail.com +Signed-off-by: Jakub Kicinski +--- + net/dsa/tag_rtl8_4.c | 6 ++++-- + 1 file changed, 4 insertions(+), 2 deletions(-) + +--- a/net/dsa/tag_rtl8_4.c ++++ b/net/dsa/tag_rtl8_4.c +@@ -99,6 +99,7 @@ + #define RTL8_4_REASON_TRAP 80 + + #define RTL8_4_LEARN_DIS BIT(5) ++#define RTL8_4_KEEP BIT(7) + + #define RTL8_4_TX GENMASK(3, 0) + #define RTL8_4_RX GENMASK(10, 0) +@@ -115,8 +116,9 @@ static void rtl8_4_write_tag(struct sk_b + /* Set Protocol; zero REASON */ + tag16[1] = htons(FIELD_PREP(RTL8_4_PROTOCOL, RTL8_4_PROTOCOL_RTL8365MB)); + +- /* Zero EFID_EN, EFID, PRI_EN, PRI, VSEL, VIDX, KEEP; set LEARN_DIS */ +- tag16[2] = htons(FIELD_PREP(RTL8_4_LEARN_DIS, 1)); ++ /* Zero EFID_EN, EFID, PRI_EN, PRI, VSEL, VIDX; set KEEP, LEARN_DIS */ ++ tag16[2] = htons(FIELD_PREP(RTL8_4_LEARN_DIS, 1) | ++ FIELD_PREP(RTL8_4_KEEP, 1)); + + /* Zero ALLOW; set RX (CPU->switch) forwarding port mask */ + tag16[3] = htons(FIELD_PREP(RTL8_4_RX, BIT(dp->index))); diff --git a/target/linux/generic/backport-6.18/942-01-7.2-net-dsa-realtek-rtl8365mb-use-ERR_PTR.patch b/target/linux/generic/backport-6.18/942-01-7.2-net-dsa-realtek-rtl8365mb-use-ERR_PTR.patch new file mode 100644 index 00000000000..8a790f59bdc --- /dev/null +++ b/target/linux/generic/backport-6.18/942-01-7.2-net-dsa-realtek-rtl8365mb-use-ERR_PTR.patch @@ -0,0 +1,154 @@ +From a543687227d86b1f4401f395abaf7f3f710fe964 Mon Sep 17 00:00:00 2001 +From: Luiz Angelo Daros de Luca +Date: Sat, 6 Jun 2026 05:29:25 -0300 +Subject: net: dsa: realtek: rtl8365mb: use ERR_PTR + +Convert numeric error codes into human-readable strings by using %pe +together with ERR_PTR() in dev_err() messages. Also use dev_err_probe() +instead of checking for -EPROBE_DEFER. + +Reviewed-by: Linus Walleij +Reviewed-by: Mieczyslaw Nalewaj +Signed-off-by: Luiz Angelo Daros de Luca +Link: https://patch.msgid.link/20260606-realtek_forward-v13-1-b9e409687cbe@gmail.com +Signed-off-by: Jakub Kicinski +--- + drivers/net/dsa/realtek/rtl8365mb.c | 51 +++++++++++++++++++++---------------- + 1 file changed, 29 insertions(+), 22 deletions(-) + +--- a/drivers/net/dsa/realtek/rtl8365mb.c ++++ b/drivers/net/dsa/realtek/rtl8365mb.c +@@ -803,8 +803,8 @@ static int rtl8365mb_phy_read(struct rea + ret = rtl8365mb_phy_ocp_read(priv, phy, ocp_addr, &val); + if (ret) { + dev_err(priv->dev, +- "failed to read PHY%d reg %02x @ %04x, ret %d\n", phy, +- regnum, ocp_addr, ret); ++ "failed to read PHY%d reg %02x @ %04x, ret %pe\n", phy, ++ regnum, ocp_addr, ERR_PTR(ret)); + return ret; + } + +@@ -831,8 +831,8 @@ static int rtl8365mb_phy_write(struct re + ret = rtl8365mb_phy_ocp_write(priv, phy, ocp_addr, val); + if (ret) { + dev_err(priv->dev, +- "failed to write PHY%d reg %02x @ %04x, ret %d\n", phy, +- regnum, ocp_addr, ret); ++ "failed to write PHY%d reg %02x @ %04x, ret %pe\n", phy, ++ regnum, ocp_addr, ERR_PTR(ret)); + return ret; + } + +@@ -1082,8 +1082,8 @@ static void rtl8365mb_phylink_mac_config + ret = rtl8365mb_ext_config_rgmii(priv, port, state->interface); + if (ret) + dev_err(priv->dev, +- "failed to configure RGMII mode on port %d: %d\n", +- port, ret); ++ "failed to configure RGMII mode on port %d: %pe\n", ++ port, ERR_PTR(ret)); + return; + } + +@@ -1112,8 +1112,8 @@ static void rtl8365mb_phylink_mac_link_d + false, false); + if (ret) + dev_err(priv->dev, +- "failed to reset forced mode on port %d: %d\n", +- port, ret); ++ "failed to reset forced mode on port %d: %pe\n", ++ port, ERR_PTR(ret)); + + return; + } +@@ -1143,8 +1143,8 @@ static void rtl8365mb_phylink_mac_link_u + rx_pause); + if (ret) + dev_err(priv->dev, +- "failed to force mode on port %d: %d\n", port, +- ret); ++ "failed to force mode on port %d: %pe\n", port, ++ ERR_PTR(ret)); + + return; + } +@@ -1299,8 +1299,8 @@ static void rtl8365mb_get_ethtool_stats( + mib->length, &data[i]); + if (ret) { + dev_err(priv->dev, +- "failed to read port %d counters: %d\n", port, +- ret); ++ "failed to read port %d counters: %pe\n", port, ++ ERR_PTR(ret)); + break; + } + } +@@ -1652,7 +1652,8 @@ static irqreturn_t rtl8365mb_irq(int irq + return IRQ_HANDLED; + + out_error: +- dev_err(priv->dev, "failed to read interrupt status: %d\n", ret); ++ dev_err(priv->dev, "failed to read interrupt status: %pe\n", ++ ERR_PTR(ret)); + + out_none: + return IRQ_NONE; +@@ -1725,10 +1726,13 @@ static int rtl8365mb_irq_setup(struct re + /* rtl8365mb IRQs cascade off this one */ + irq = of_irq_get(intc, 0); + if (irq <= 0) { +- if (irq != -EPROBE_DEFER) +- dev_err(priv->dev, "failed to get parent irq: %d\n", +- irq); +- ret = irq ? irq : -EINVAL; ++ if (!irq) { ++ dev_err(priv->dev, "failed to map IRQ\n"); ++ ret = -EINVAL; ++ } else { ++ ret = dev_err_probe(priv->dev, irq, ++ "failed to get parent irq\n"); ++ } + goto out_put_node; + } + +@@ -1790,7 +1794,8 @@ static int rtl8365mb_irq_setup(struct re + ret = request_threaded_irq(irq, NULL, rtl8365mb_irq, IRQF_ONESHOT, + "rtl8365mb", priv); + if (ret) { +- dev_err(priv->dev, "failed to request irq: %d\n", ret); ++ dev_err(priv->dev, "failed to request irq: %pe\n", ++ ERR_PTR(ret)); + goto out_remove_irqdomain; + } + +@@ -1966,14 +1971,16 @@ static int rtl8365mb_setup(struct dsa_sw + + ret = rtl8365mb_reset_chip(priv); + if (ret) { +- dev_err(priv->dev, "failed to reset chip: %d\n", ret); ++ dev_err(priv->dev, "failed to reset chip: %pe\n", ++ ERR_PTR(ret)); + goto out_error; + } + + /* Configure switch to vendor-defined initial state */ + ret = rtl8365mb_switch_init(priv); + if (ret) { +- dev_err(priv->dev, "failed to initialize switch: %d\n", ret); ++ dev_err(priv->dev, "failed to initialize switch: %pe\n", ++ ERR_PTR(ret)); + goto out_error; + } + +@@ -2091,8 +2098,8 @@ static int rtl8365mb_detect(struct realt + + ret = rtl8365mb_get_chip_id_and_ver(priv->map, &chip_id, &chip_ver); + if (ret) { +- dev_err(priv->dev, "failed to read chip id and version: %d\n", +- ret); ++ dev_err(priv->dev, "failed to read chip id and version: %pe\n", ++ ERR_PTR(ret)); + return ret; + } + diff --git a/target/linux/generic/backport-6.18/942-02-7.2-net-dsa-realtek-rtl8365mb-reject-unsupported-topolog.patch b/target/linux/generic/backport-6.18/942-02-7.2-net-dsa-realtek-rtl8365mb-reject-unsupported-topolog.patch new file mode 100644 index 00000000000..8a907f80a56 --- /dev/null +++ b/target/linux/generic/backport-6.18/942-02-7.2-net-dsa-realtek-rtl8365mb-reject-unsupported-topolog.patch @@ -0,0 +1,59 @@ +From 36c572fd60d6bf2f9631088833c162203907c9ca Mon Sep 17 00:00:00 2001 +From: Luiz Angelo Daros de Luca +Date: Sat, 6 Jun 2026 05:29:26 -0300 +Subject: net: dsa: realtek: rtl8365mb: reject unsupported topologies + +Explicitly enforce the presence of a CPU port (-EINVAL) and reject DSA +cascade links (-EOPNOTSUPP) during setup to prevent silent failures. + +These topologies were already non-functional. Without a CPU port, the +driver does not activate CPU tagging. Additionally, the switch hardware +was not designed to be cascaded, and DSA links never worked because +CPU tagging is not enabled for them. + +Reviewed-by: Mieczyslaw Nalewaj +Reviewed-by: Linus Walleij +Signed-off-by: Luiz Angelo Daros de Luca +Link: https://patch.msgid.link/20260606-realtek_forward-v13-2-b9e409687cbe@gmail.com +Signed-off-by: Jakub Kicinski +--- + drivers/net/dsa/realtek/rtl8365mb.c | 21 +++++++++++++++++++++ + 1 file changed, 21 insertions(+) + +--- a/drivers/net/dsa/realtek/rtl8365mb.c ++++ b/drivers/net/dsa/realtek/rtl8365mb.c +@@ -1991,6 +1991,20 @@ static int rtl8365mb_setup(struct dsa_sw + else if (ret) + dev_info(priv->dev, "no interrupt support\n"); + ++ for (i = 0; i < priv->num_ports; i++) { ++ /* Cascading (DSA links) is not supported yet. ++ * Historically, the driver has always been broken ++ * without a dedicated CPU port because CPU tagging ++ * would be disabled, rendering the switch entirely ++ * non-functional for DSA operations. ++ */ ++ if (dsa_is_dsa_port(ds, i)) { ++ dev_err(priv->dev, "Cascading (DSA link) not supported\n"); ++ ret = -EOPNOTSUPP; ++ goto out_teardown_irq; ++ } ++ } ++ + /* Configure CPU tagging */ + dsa_switch_for_each_cpu_port(cpu_dp, ds) { + cpu->mask |= BIT(cpu_dp->index); +@@ -1999,6 +2013,13 @@ static int rtl8365mb_setup(struct dsa_sw + cpu->trap_port = cpu_dp->index; + } + cpu->enable = cpu->mask > 0; ++ ++ if (!cpu->enable) { ++ dev_err(priv->dev, "no CPU port defined\n"); ++ ret = -EINVAL; ++ goto out_teardown_irq; ++ } ++ + ret = rtl8365mb_cpu_config(priv); + if (ret) + goto out_teardown_irq; diff --git a/target/linux/generic/backport-6.18/942-03-7.2-net-dsa-realtek-rtl8365mb-use-dsa-helpers-for-port-i.patch b/target/linux/generic/backport-6.18/942-03-7.2-net-dsa-realtek-rtl8365mb-use-dsa-helpers-for-port-i.patch new file mode 100644 index 00000000000..95e2d716887 --- /dev/null +++ b/target/linux/generic/backport-6.18/942-03-7.2-net-dsa-realtek-rtl8365mb-use-dsa-helpers-for-port-i.patch @@ -0,0 +1,347 @@ +From 51a4a22301b023610a1321614e8dc61251ef9064 Mon Sep 17 00:00:00 2001 +From: Luiz Angelo Daros de Luca +Date: Sat, 6 Jun 2026 05:29:27 -0300 +Subject: net: dsa: realtek: rtl8365mb: use dsa helpers for port iteration + +Convert open-coded port iteration loops to use the DSA helpers and +restructure rtl8365mb_setup() into clear blocking, user, and +CPU port phases. + +As part of this refactoring, unused ports are explicitly placed into a +blocked, isolated state with learning disabled, ensuring safe default +hardware behavior. The driver also does not allocate a virtual IRQ +mapping for unused ports. To accommodate this, a guard check is added to +the interrupt handler (rtl8365mb_irq) to safely skip ports without a +valid IRQ mapping. The irq domain teardown, however, does clean all +ports as external PHYs may still map the IRQ. + +Furthermore, since the new initialization loop starts with all ports +administratively isolated by default, CPU port forwarding and isolation +masks are explicitly configured at the end of the setup phase to prevent +egress traffic from being blocked. + +Suggested-by: Abdulkader Alrezej +Reviewed-by: Linus Walleij +Reviewed-by: Mieczyslaw Nalewaj +Signed-off-by: Luiz Angelo Daros de Luca +Link: https://patch.msgid.link/20260606-realtek_forward-v13-3-b9e409687cbe@gmail.com +Signed-off-by: Jakub Kicinski +--- + drivers/net/dsa/realtek/rtl8365mb.c | 166 +++++++++++++++++++++++------------- + 1 file changed, 105 insertions(+), 61 deletions(-) + +--- a/drivers/net/dsa/realtek/rtl8365mb.c ++++ b/drivers/net/dsa/realtek/rtl8365mb.c +@@ -1554,18 +1554,15 @@ static void rtl8365mb_stats_setup(struct + { + struct rtl8365mb *mb = priv->chip_data; + struct dsa_switch *ds = &priv->ds; +- int i; ++ struct dsa_port *dp; + + /* Per-chip global mutex to protect MIB counter access, since doing + * so requires accessing a series of registers in a particular order. + */ + mutex_init(&mb->mib_lock); + +- for (i = 0; i < priv->num_ports; i++) { +- struct rtl8365mb_port *p = &mb->ports[i]; +- +- if (dsa_is_unused_port(ds, i)) +- continue; ++ dsa_switch_for_each_available_port(dp, ds) { ++ struct rtl8365mb_port *p = &mb->ports[dp->index]; + + /* Per-port spinlock to protect the stats64 data */ + spin_lock_init(&p->stats_lock); +@@ -1581,13 +1578,10 @@ static void rtl8365mb_stats_teardown(str + { + struct rtl8365mb *mb = priv->chip_data; + struct dsa_switch *ds = &priv->ds; +- int i; +- +- for (i = 0; i < priv->num_ports; i++) { +- struct rtl8365mb_port *p = &mb->ports[i]; ++ struct dsa_port *dp; + +- if (dsa_is_unused_port(ds, i)) +- continue; ++ dsa_switch_for_each_available_port(dp, ds) { ++ struct rtl8365mb_port *p = &mb->ports[dp->index]; + + cancel_delayed_work_sync(&p->mib_work); + } +@@ -1646,6 +1640,9 @@ static irqreturn_t rtl8365mb_irq(int irq + for_each_set_bit(line, &line_changes, priv->num_ports) { + int child_irq = irq_find_mapping(priv->irqdomain, line); + ++ if (!child_irq) ++ continue; ++ + handle_nested_irq(child_irq); + } + +@@ -1667,10 +1664,14 @@ static struct irq_chip rtl8365mb_irq_chi + static int rtl8365mb_irq_map(struct irq_domain *domain, unsigned int irq, + irq_hw_number_t hwirq) + { +- irq_set_chip_data(irq, domain->host_data); ++ struct realtek_priv *priv = domain->host_data; ++ struct rtl8365mb *mb = priv->chip_data; ++ ++ irq_set_chip_data(irq, priv); + irq_set_chip_and_handler(irq, &rtl8365mb_irq_chip, handle_simple_irq); + irq_set_nested_thread(irq, 1); + irq_set_noprobe(irq); ++ irq_set_parent(irq, mb->irq); + + return 0; + } +@@ -1709,13 +1710,14 @@ static int rtl8365mb_irq_disable(struct + static int rtl8365mb_irq_setup(struct realtek_priv *priv) + { + struct rtl8365mb *mb = priv->chip_data; ++ struct dsa_switch *ds = &priv->ds; + struct device_node *intc; ++ struct dsa_port *dp; + u32 irq_trig; + int virq; + int irq; + u32 val; + int ret; +- int i; + + intc = of_get_child_by_name(priv->dev->of_node, "interrupt-controller"); + if (!intc) { +@@ -1736,6 +1738,9 @@ static int rtl8365mb_irq_setup(struct re + goto out_put_node; + } + ++ /* Store the irq so that we know to map and free it during teardown */ ++ mb->irq = irq; ++ + priv->irqdomain = irq_domain_create_linear(of_fwnode_handle(intc), priv->num_ports, + &rtl8365mb_irqdomain_ops, priv); + if (!priv->irqdomain) { +@@ -1744,8 +1749,8 @@ static int rtl8365mb_irq_setup(struct re + goto out_put_node; + } + +- for (i = 0; i < priv->num_ports; i++) { +- virq = irq_create_mapping(priv->irqdomain, i); ++ dsa_switch_for_each_available_port(dp, ds) { ++ virq = irq_create_mapping(priv->irqdomain, dp->index); + if (!virq) { + dev_err(priv->dev, + "failed to create irq domain mapping\n"); +@@ -1799,9 +1804,6 @@ static int rtl8365mb_irq_setup(struct re + goto out_remove_irqdomain; + } + +- /* Store the irq so that we know to free it during teardown */ +- mb->irq = irq; +- + ret = rtl8365mb_irq_enable(priv); + if (ret) + goto out_free_irq; +@@ -1812,18 +1814,20 @@ static int rtl8365mb_irq_setup(struct re + + out_free_irq: + free_irq(mb->irq, priv); +- mb->irq = 0; + + out_remove_irqdomain: +- for (i = 0; i < priv->num_ports; i++) { +- virq = irq_find_mapping(priv->irqdomain, i); +- irq_dispose_mapping(virq); ++ dsa_switch_for_each_port(dp, ds) { ++ virq = irq_find_mapping(priv->irqdomain, dp->index); ++ ++ if (virq) ++ irq_dispose_mapping(virq); + } + + irq_domain_remove(priv->irqdomain); + priv->irqdomain = NULL; + + out_put_node: ++ mb->irq = 0; + of_node_put(intc); + + return ret; +@@ -1832,8 +1836,9 @@ out_put_node: + static void rtl8365mb_irq_teardown(struct realtek_priv *priv) + { + struct rtl8365mb *mb = priv->chip_data; ++ struct dsa_switch *ds = &priv->ds; ++ struct dsa_port *dp; + int virq; +- int i; + + if (mb->irq) { + free_irq(mb->irq, priv); +@@ -1841,9 +1846,15 @@ static void rtl8365mb_irq_teardown(struc + } + + if (priv->irqdomain) { +- for (i = 0; i < priv->num_ports; i++) { +- virq = irq_find_mapping(priv->irqdomain, i); +- irq_dispose_mapping(virq); ++ /* Unused ports with a linked PHY still have an active IRQ ++ * mapping that must be disposed of during teardown. Loop ++ * through all ports. ++ */ ++ dsa_switch_for_each_port(dp, ds) { ++ virq = irq_find_mapping(priv->irqdomain, dp->index); ++ ++ if (virq) ++ irq_dispose_mapping(virq); + } + + irq_domain_remove(priv->irqdomain); +@@ -1961,10 +1972,11 @@ static int rtl8365mb_setup(struct dsa_sw + { + struct realtek_priv *priv = ds->priv; + struct rtl8365mb_cpu *cpu; +- struct dsa_port *cpu_dp; ++ u32 downports_mask = 0; ++ u32 upports_mask = 0; + struct rtl8365mb *mb; ++ struct dsa_port *dp; + int ret; +- int i; + + mb = priv->chip_data; + cpu = &mb->cpu; +@@ -1991,67 +2003,99 @@ static int rtl8365mb_setup(struct dsa_sw + else if (ret) + dev_info(priv->dev, "no interrupt support\n"); + +- for (i = 0; i < priv->num_ports; i++) { ++ dsa_switch_for_each_port(dp, ds) { + /* Cascading (DSA links) is not supported yet. + * Historically, the driver has always been broken + * without a dedicated CPU port because CPU tagging + * would be disabled, rendering the switch entirely + * non-functional for DSA operations. + */ +- if (dsa_is_dsa_port(ds, i)) { ++ if (dsa_port_is_dsa(dp)) { + dev_err(priv->dev, "Cascading (DSA link) not supported\n"); + ret = -EOPNOTSUPP; + goto out_teardown_irq; + } + } + +- /* Configure CPU tagging */ +- dsa_switch_for_each_cpu_port(cpu_dp, ds) { +- cpu->mask |= BIT(cpu_dp->index); ++ /* Start with all ports blocked, including unused ports */ ++ dsa_switch_for_each_port(dp, ds) { ++ struct rtl8365mb_port *p = &mb->ports[dp->index]; + +- if (cpu->trap_port == RTL8365MB_MAX_NUM_PORTS) +- cpu->trap_port = cpu_dp->index; +- } +- cpu->enable = cpu->mask > 0; ++ /* Set the initial STP state of all ports to DISABLED, otherwise ++ * ports will still forward frames to the CPU despite being ++ * administratively down by default. ++ */ ++ rtl8365mb_port_stp_state_set(ds, dp->index, BR_STATE_DISABLED); + +- if (!cpu->enable) { +- dev_err(priv->dev, "no CPU port defined\n"); +- ret = -EINVAL; +- goto out_teardown_irq; +- } ++ /* Start with all port completely isolated */ ++ ret = rtl8365mb_port_set_isolation(priv, dp->index, 0); ++ if (ret) ++ goto out_teardown_irq; + +- ret = rtl8365mb_cpu_config(priv); +- if (ret) +- goto out_teardown_irq; ++ /* Disable learning */ ++ ret = rtl8365mb_port_set_learning(priv, dp->index, false); ++ if (ret) ++ goto out_teardown_irq; ++ ++ /* Set up per-port private data */ ++ p->priv = priv; ++ p->index = dp->index; + +- /* Configure ports */ +- for (i = 0; i < priv->num_ports; i++) { +- struct rtl8365mb_port *p = &mb->ports[i]; ++ /* Collect CPU ports. If we support cascade switches, it should ++ * also include the upstream DSA ports. ++ */ ++ if (!dsa_port_is_cpu(dp)) ++ continue; ++ ++ upports_mask |= BIT(dp->index); ++ } + +- if (dsa_is_unused_port(ds, i)) ++ /* Configure user ports */ ++ dsa_switch_for_each_port(dp, ds) { ++ if (!dsa_port_is_user(dp)) + continue; + + /* Forward only to the CPU */ +- ret = rtl8365mb_port_set_isolation(priv, i, cpu->mask); ++ ret = rtl8365mb_port_set_isolation(priv, dp->index, ++ upports_mask); + if (ret) + goto out_teardown_irq; + +- /* Disable learning */ +- ret = rtl8365mb_port_set_learning(priv, i, false); ++ /* If we support cascade switches, it should also include the ++ * downstream DSA ports. ++ */ ++ downports_mask |= BIT(dp->index); ++ } ++ ++ /* Configure CPU tagging */ ++ /* If we support cascade switches, it should also include the upstream ++ * DSA ports. ++ */ ++ dsa_switch_for_each_cpu_port(dp, ds) { ++ /* Use the first CPU port as trap_port */ ++ if (cpu->trap_port == RTL8365MB_MAX_NUM_PORTS) ++ cpu->trap_port = dp->index; ++ ++ /* Forward to all user ports */ ++ ret = rtl8365mb_port_set_isolation(priv, dp->index, ++ downports_mask); + if (ret) + goto out_teardown_irq; ++ } + +- /* Set the initial STP state of all ports to DISABLED, otherwise +- * ports will still forward frames to the CPU despite being +- * administratively down by default. +- */ +- rtl8365mb_port_stp_state_set(ds, i, BR_STATE_DISABLED); ++ cpu->mask = upports_mask; ++ cpu->enable = cpu->mask > 0; + +- /* Set up per-port private data */ +- p->priv = priv; +- p->index = i; ++ if (!cpu->enable) { ++ dev_err(priv->dev, "no CPU port defined\n"); ++ ret = -EINVAL; ++ goto out_teardown_irq; + } + ++ ret = rtl8365mb_cpu_config(priv); ++ if (ret) ++ goto out_teardown_irq; ++ + ret = rtl8365mb_port_change_mtu(ds, cpu->trap_port, ETH_DATA_LEN); + if (ret) + goto out_teardown_irq; diff --git a/target/linux/generic/backport-6.18/942-04-7.2-net-dsa-realtek-rtl8365mb-prepare-for-multiple-sourc.patch b/target/linux/generic/backport-6.18/942-04-7.2-net-dsa-realtek-rtl8365mb-prepare-for-multiple-sourc.patch new file mode 100644 index 00000000000..55e6845f926 --- /dev/null +++ b/target/linux/generic/backport-6.18/942-04-7.2-net-dsa-realtek-rtl8365mb-prepare-for-multiple-sourc.patch @@ -0,0 +1,4631 @@ +From 0e692c27fe84db294866fe726f9944f7e125b817 Mon Sep 17 00:00:00 2001 +From: Alvin Å ipraga +Date: Sat, 6 Jun 2026 05:29:28 -0300 +Subject: net: dsa: realtek: rtl8365mb: prepare for multiple source files +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Rename rtl8365mb.c to rtl8365mb_main.c in preparation for subsequent +commits which add additional source files to the driver. + +The trailing backslash in the Makefile is deliberate. It allows for new +files to be added without clobbering git history. + +Signed-off-by: Alvin Å ipraga +Reviewed-by: Linus Walleij +Reviewed-by: Mieczyslaw Nalewaj +Co-developed-by: Luiz Angelo Daros de Luca +Signed-off-by: Luiz Angelo Daros de Luca +Link: https://patch.msgid.link/20260606-realtek_forward-v13-4-b9e409687cbe@gmail.com +Signed-off-by: Jakub Kicinski +--- + drivers/net/dsa/realtek/Makefile | 2 + + drivers/net/dsa/realtek/rtl8365mb.c | 2296 ------------------------------ + drivers/net/dsa/realtek/rtl8365mb_main.c | 2296 ++++++++++++++++++++++++++++++ + 3 files changed, 2298 insertions(+), 2296 deletions(-) + delete mode 100644 drivers/net/dsa/realtek/rtl8365mb.c + create mode 100644 drivers/net/dsa/realtek/rtl8365mb_main.c + +--- a/drivers/net/dsa/realtek/Makefile ++++ b/drivers/net/dsa/realtek/Makefile +@@ -16,3 +16,5 @@ ifdef CONFIG_NET_DSA_REALTEK_RTL8366RB_L + rtl8366-objs += rtl8366rb-leds.o + endif + obj-$(CONFIG_NET_DSA_REALTEK_RTL8365MB) += rtl8365mb.o ++rtl8365mb-objs := rtl8365mb_main.o \ ++# end of rtl8365mb-objs +--- a/drivers/net/dsa/realtek/rtl8365mb.c ++++ /dev/null +@@ -1,2294 +0,0 @@ +-// SPDX-License-Identifier: GPL-2.0 +-/* Realtek SMI subdriver for the Realtek RTL8365MB-VC ethernet switch. +- * +- * Copyright (C) 2021 Alvin Å ipraga +- * Copyright (C) 2021 Michael Rasmussen +- * +- * The RTL8365MB-VC is a 4+1 port 10/100/1000M switch controller. It includes 4 +- * integrated PHYs for the user facing ports, and an extension interface which +- * can be connected to the CPU - or another PHY - via either MII, RMII, or +- * RGMII. The switch is configured via the Realtek Simple Management Interface +- * (SMI), which uses the MDIO/MDC lines. +- * +- * Below is a simplified block diagram of the chip and its relevant interfaces. +- * +- * .-----------------------------------. +- * | | +- * UTP <---------------> Giga PHY <-> PCS <-> P0 GMAC | +- * UTP <---------------> Giga PHY <-> PCS <-> P1 GMAC | +- * UTP <---------------> Giga PHY <-> PCS <-> P2 GMAC | +- * UTP <---------------> Giga PHY <-> PCS <-> P3 GMAC | +- * | | +- * CPU/PHY <-MII/RMII/RGMII---> Extension <---> Extension | +- * | interface 1 GMAC 1 | +- * | | +- * SMI driver/ <-MDC/SCL---> Management ~~~~~~~~~~~~~~ | +- * EEPROM <-MDIO/SDA--> interface ~REALTEK ~~~~~ | +- * | ~RTL8365MB ~~~ | +- * | ~GXXXC TAIWAN~ | +- * GPIO <--------------> Reset ~~~~~~~~~~~~~~ | +- * | | +- * Interrupt <----------> Link UP/DOWN events | +- * controller | | +- * '-----------------------------------' +- * +- * The driver uses DSA to integrate the 4 user and 1 extension ports into the +- * kernel. Netdevices are created for the user ports, as are PHY devices for +- * their integrated PHYs. The device tree firmware should also specify the link +- * partner of the extension port - either via a fixed-link or other phy-handle. +- * See the device tree bindings for more detailed information. Note that the +- * driver has only been tested with a fixed-link, but in principle it should not +- * matter. +- * +- * NOTE: Currently, only the RGMII interface is implemented in this driver. +- * +- * The interrupt line is asserted on link UP/DOWN events. The driver creates a +- * custom irqchip to handle this interrupt and demultiplex the events by reading +- * the status registers via SMI. Interrupts are then propagated to the relevant +- * PHY device. +- * +- * The EEPROM contains initial register values which the chip will read over I2C +- * upon hardware reset. It is also possible to omit the EEPROM. In both cases, +- * the driver will manually reprogram some registers using jam tables to reach +- * an initial state defined by the vendor driver. +- * +- * This Linux driver is written based on an OS-agnostic vendor driver from +- * Realtek. The reference GPL-licensed sources can be found in the OpenWrt +- * source tree under the name rtl8367c. The vendor driver claims to support a +- * number of similar switch controllers from Realtek, but the only hardware we +- * have is the RTL8365MB-VC. Moreover, there does not seem to be any chip under +- * the name RTL8367C. Although one wishes that the 'C' stood for some kind of +- * common hardware revision, there exist examples of chips with the suffix -VC +- * which are explicitly not supported by the rtl8367c driver and which instead +- * require the rtl8367d vendor driver. With all this uncertainty, the driver has +- * been modestly named rtl8365mb. Future implementors may wish to rename things +- * accordingly. +- * +- * In the same family of chips, some carry up to 8 user ports and up to 2 +- * extension ports. Where possible this driver tries to make things generic, but +- * more work must be done to support these configurations. According to +- * documentation from Realtek, the family should include the following chips: +- * +- * - RTL8363NB +- * - RTL8363NB-VB +- * - RTL8363SC +- * - RTL8363SC-VB +- * - RTL8364NB +- * - RTL8364NB-VB +- * - RTL8365MB-VC +- * - RTL8366SC +- * - RTL8367RB-VB +- * - RTL8367SB +- * - RTL8367S +- * - RTL8370MB +- * - RTL8310SR +- * +- * Some of the register logic for these additional chips has been skipped over +- * while implementing this driver. It is therefore not possible to assume that +- * things will work out-of-the-box for other chips, and a careful review of the +- * vendor driver may be needed to expand support. The RTL8365MB-VC seems to be +- * one of the simpler chips. +- */ +- +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +- +-#include "realtek.h" +-#include "realtek-smi.h" +-#include "realtek-mdio.h" +-#include "rtl83xx.h" +- +-/* Family-specific data and limits */ +-#define RTL8365MB_PHYADDRMAX 7 +-#define RTL8365MB_NUM_PHYREGS 32 +-#define RTL8365MB_PHYREGMAX (RTL8365MB_NUM_PHYREGS - 1) +-#define RTL8365MB_MAX_NUM_PORTS 11 +-#define RTL8365MB_MAX_NUM_EXTINTS 3 +-#define RTL8365MB_LEARN_LIMIT_MAX 2112 +- +-/* Chip identification registers */ +-#define RTL8365MB_CHIP_ID_REG 0x1300 +- +-#define RTL8365MB_CHIP_VER_REG 0x1301 +- +-#define RTL8365MB_MAGIC_REG 0x13C2 +-#define RTL8365MB_MAGIC_VALUE 0x0249 +- +-/* Chip reset register */ +-#define RTL8365MB_CHIP_RESET_REG 0x1322 +-#define RTL8365MB_CHIP_RESET_SW_MASK 0x0002 +-#define RTL8365MB_CHIP_RESET_HW_MASK 0x0001 +- +-/* Interrupt polarity register */ +-#define RTL8365MB_INTR_POLARITY_REG 0x1100 +-#define RTL8365MB_INTR_POLARITY_MASK 0x0001 +-#define RTL8365MB_INTR_POLARITY_HIGH 0 +-#define RTL8365MB_INTR_POLARITY_LOW 1 +- +-/* Interrupt control/status register - enable/check specific interrupt types */ +-#define RTL8365MB_INTR_CTRL_REG 0x1101 +-#define RTL8365MB_INTR_STATUS_REG 0x1102 +-#define RTL8365MB_INTR_SLIENT_START_2_MASK 0x1000 +-#define RTL8365MB_INTR_SLIENT_START_MASK 0x0800 +-#define RTL8365MB_INTR_ACL_ACTION_MASK 0x0200 +-#define RTL8365MB_INTR_CABLE_DIAG_FIN_MASK 0x0100 +-#define RTL8365MB_INTR_INTERRUPT_8051_MASK 0x0080 +-#define RTL8365MB_INTR_LOOP_DETECTION_MASK 0x0040 +-#define RTL8365MB_INTR_GREEN_TIMER_MASK 0x0020 +-#define RTL8365MB_INTR_SPECIAL_CONGEST_MASK 0x0010 +-#define RTL8365MB_INTR_SPEED_CHANGE_MASK 0x0008 +-#define RTL8365MB_INTR_LEARN_OVER_MASK 0x0004 +-#define RTL8365MB_INTR_METER_EXCEEDED_MASK 0x0002 +-#define RTL8365MB_INTR_LINK_CHANGE_MASK 0x0001 +-#define RTL8365MB_INTR_ALL_MASK \ +- (RTL8365MB_INTR_SLIENT_START_2_MASK | \ +- RTL8365MB_INTR_SLIENT_START_MASK | \ +- RTL8365MB_INTR_ACL_ACTION_MASK | \ +- RTL8365MB_INTR_CABLE_DIAG_FIN_MASK | \ +- RTL8365MB_INTR_INTERRUPT_8051_MASK | \ +- RTL8365MB_INTR_LOOP_DETECTION_MASK | \ +- RTL8365MB_INTR_GREEN_TIMER_MASK | \ +- RTL8365MB_INTR_SPECIAL_CONGEST_MASK | \ +- RTL8365MB_INTR_SPEED_CHANGE_MASK | \ +- RTL8365MB_INTR_LEARN_OVER_MASK | \ +- RTL8365MB_INTR_METER_EXCEEDED_MASK | \ +- RTL8365MB_INTR_LINK_CHANGE_MASK) +- +-/* Per-port interrupt type status registers */ +-#define RTL8365MB_PORT_LINKDOWN_IND_REG 0x1106 +-#define RTL8365MB_PORT_LINKDOWN_IND_MASK 0x07FF +- +-#define RTL8365MB_PORT_LINKUP_IND_REG 0x1107 +-#define RTL8365MB_PORT_LINKUP_IND_MASK 0x07FF +- +-/* PHY indirect access registers */ +-#define RTL8365MB_INDIRECT_ACCESS_CTRL_REG 0x1F00 +-#define RTL8365MB_INDIRECT_ACCESS_CTRL_RW_MASK 0x0002 +-#define RTL8365MB_INDIRECT_ACCESS_CTRL_RW_READ 0 +-#define RTL8365MB_INDIRECT_ACCESS_CTRL_RW_WRITE 1 +-#define RTL8365MB_INDIRECT_ACCESS_CTRL_CMD_MASK 0x0001 +-#define RTL8365MB_INDIRECT_ACCESS_CTRL_CMD_VALUE 1 +-#define RTL8365MB_INDIRECT_ACCESS_STATUS_REG 0x1F01 +-#define RTL8365MB_INDIRECT_ACCESS_ADDRESS_REG 0x1F02 +-#define RTL8365MB_INDIRECT_ACCESS_ADDRESS_OCPADR_5_1_MASK GENMASK(4, 0) +-#define RTL8365MB_INDIRECT_ACCESS_ADDRESS_PHYNUM_MASK GENMASK(7, 5) +-#define RTL8365MB_INDIRECT_ACCESS_ADDRESS_OCPADR_9_6_MASK GENMASK(11, 8) +-#define RTL8365MB_PHY_BASE 0x2000 +-#define RTL8365MB_INDIRECT_ACCESS_WRITE_DATA_REG 0x1F03 +-#define RTL8365MB_INDIRECT_ACCESS_READ_DATA_REG 0x1F04 +- +-/* PHY OCP address prefix register */ +-#define RTL8365MB_GPHY_OCP_MSB_0_REG 0x1D15 +-#define RTL8365MB_GPHY_OCP_MSB_0_CFG_CPU_OCPADR_MASK 0x0FC0 +-#define RTL8365MB_PHY_OCP_ADDR_PREFIX_MASK 0xFC00 +- +-/* The PHY OCP addresses of PHY registers 0~31 start here */ +-#define RTL8365MB_PHY_OCP_ADDR_PHYREG_BASE 0xA400 +- +-/* External interface port mode values - used in DIGITAL_INTERFACE_SELECT */ +-#define RTL8365MB_EXT_PORT_MODE_DISABLE 0 +-#define RTL8365MB_EXT_PORT_MODE_RGMII 1 +-#define RTL8365MB_EXT_PORT_MODE_MII_MAC 2 +-#define RTL8365MB_EXT_PORT_MODE_MII_PHY 3 +-#define RTL8365MB_EXT_PORT_MODE_TMII_MAC 4 +-#define RTL8365MB_EXT_PORT_MODE_TMII_PHY 5 +-#define RTL8365MB_EXT_PORT_MODE_GMII 6 +-#define RTL8365MB_EXT_PORT_MODE_RMII_MAC 7 +-#define RTL8365MB_EXT_PORT_MODE_RMII_PHY 8 +-#define RTL8365MB_EXT_PORT_MODE_SGMII 9 +-#define RTL8365MB_EXT_PORT_MODE_HSGMII 10 +-#define RTL8365MB_EXT_PORT_MODE_1000X_100FX 11 +-#define RTL8365MB_EXT_PORT_MODE_1000X 12 +-#define RTL8365MB_EXT_PORT_MODE_100FX 13 +- +-/* External interface mode configuration registers 0~1 */ +-#define RTL8365MB_DIGITAL_INTERFACE_SELECT_REG0 0x1305 /* EXT0,EXT1 */ +-#define RTL8365MB_DIGITAL_INTERFACE_SELECT_REG1 0x13C3 /* EXT2 */ +-#define RTL8365MB_DIGITAL_INTERFACE_SELECT_REG(_extint) \ +- ((_extint) <= 1 ? RTL8365MB_DIGITAL_INTERFACE_SELECT_REG0 : \ +- (_extint) == 2 ? RTL8365MB_DIGITAL_INTERFACE_SELECT_REG1 : \ +- 0x0) +-#define RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_MASK(_extint) \ +- (0xF << (((_extint) % 2) * 4)) +-#define RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_OFFSET(_extint) \ +- (((_extint) % 2) * 4) +- +-/* External interface RGMII TX/RX delay configuration registers 0~2 */ +-#define RTL8365MB_EXT_RGMXF_REG0 0x1306 /* EXT0 */ +-#define RTL8365MB_EXT_RGMXF_REG1 0x1307 /* EXT1 */ +-#define RTL8365MB_EXT_RGMXF_REG2 0x13C5 /* EXT2 */ +-#define RTL8365MB_EXT_RGMXF_REG(_extint) \ +- ((_extint) == 0 ? RTL8365MB_EXT_RGMXF_REG0 : \ +- (_extint) == 1 ? RTL8365MB_EXT_RGMXF_REG1 : \ +- (_extint) == 2 ? RTL8365MB_EXT_RGMXF_REG2 : \ +- 0x0) +-#define RTL8365MB_EXT_RGMXF_RXDELAY_MASK 0x0007 +-#define RTL8365MB_EXT_RGMXF_TXDELAY_MASK 0x0008 +- +-/* External interface port speed values - used in DIGITAL_INTERFACE_FORCE */ +-#define RTL8365MB_PORT_SPEED_10M 0 +-#define RTL8365MB_PORT_SPEED_100M 1 +-#define RTL8365MB_PORT_SPEED_1000M 2 +- +-/* External interface force configuration registers 0~2 */ +-#define RTL8365MB_DIGITAL_INTERFACE_FORCE_REG0 0x1310 /* EXT0 */ +-#define RTL8365MB_DIGITAL_INTERFACE_FORCE_REG1 0x1311 /* EXT1 */ +-#define RTL8365MB_DIGITAL_INTERFACE_FORCE_REG2 0x13C4 /* EXT2 */ +-#define RTL8365MB_DIGITAL_INTERFACE_FORCE_REG(_extint) \ +- ((_extint) == 0 ? RTL8365MB_DIGITAL_INTERFACE_FORCE_REG0 : \ +- (_extint) == 1 ? RTL8365MB_DIGITAL_INTERFACE_FORCE_REG1 : \ +- (_extint) == 2 ? RTL8365MB_DIGITAL_INTERFACE_FORCE_REG2 : \ +- 0x0) +-#define RTL8365MB_DIGITAL_INTERFACE_FORCE_EN_MASK 0x1000 +-#define RTL8365MB_DIGITAL_INTERFACE_FORCE_NWAY_MASK 0x0080 +-#define RTL8365MB_DIGITAL_INTERFACE_FORCE_TXPAUSE_MASK 0x0040 +-#define RTL8365MB_DIGITAL_INTERFACE_FORCE_RXPAUSE_MASK 0x0020 +-#define RTL8365MB_DIGITAL_INTERFACE_FORCE_LINK_MASK 0x0010 +-#define RTL8365MB_DIGITAL_INTERFACE_FORCE_DUPLEX_MASK 0x0004 +-#define RTL8365MB_DIGITAL_INTERFACE_FORCE_SPEED_MASK 0x0003 +- +-/* CPU port mask register - controls which ports are treated as CPU ports */ +-#define RTL8365MB_CPU_PORT_MASK_REG 0x1219 +-#define RTL8365MB_CPU_PORT_MASK_MASK 0x07FF +- +-/* CPU control register */ +-#define RTL8365MB_CPU_CTRL_REG 0x121A +-#define RTL8365MB_CPU_CTRL_TRAP_PORT_EXT_MASK 0x0400 +-#define RTL8365MB_CPU_CTRL_TAG_FORMAT_MASK 0x0200 +-#define RTL8365MB_CPU_CTRL_RXBYTECOUNT_MASK 0x0080 +-#define RTL8365MB_CPU_CTRL_TAG_POSITION_MASK 0x0040 +-#define RTL8365MB_CPU_CTRL_TRAP_PORT_MASK 0x0038 +-#define RTL8365MB_CPU_CTRL_INSERTMODE_MASK 0x0006 +-#define RTL8365MB_CPU_CTRL_EN_MASK 0x0001 +- +-/* Maximum packet length register */ +-#define RTL8365MB_CFG0_MAX_LEN_REG 0x088C +-#define RTL8365MB_CFG0_MAX_LEN_MASK 0x3FFF +-#define RTL8365MB_CFG0_MAX_LEN_MAX 0x3FFF +- +-/* Port learning limit registers */ +-#define RTL8365MB_LUT_PORT_LEARN_LIMIT_BASE 0x0A20 +-#define RTL8365MB_LUT_PORT_LEARN_LIMIT_REG(_physport) \ +- (RTL8365MB_LUT_PORT_LEARN_LIMIT_BASE + (_physport)) +- +-/* Port isolation (forwarding mask) registers */ +-#define RTL8365MB_PORT_ISOLATION_REG_BASE 0x08A2 +-#define RTL8365MB_PORT_ISOLATION_REG(_physport) \ +- (RTL8365MB_PORT_ISOLATION_REG_BASE + (_physport)) +-#define RTL8365MB_PORT_ISOLATION_MASK 0x07FF +- +-/* MSTP port state registers - indexed by tree instance */ +-#define RTL8365MB_MSTI_CTRL_BASE 0x0A00 +-#define RTL8365MB_MSTI_CTRL_REG(_msti, _physport) \ +- (RTL8365MB_MSTI_CTRL_BASE + ((_msti) << 1) + ((_physport) >> 3)) +-#define RTL8365MB_MSTI_CTRL_PORT_STATE_OFFSET(_physport) ((_physport) << 1) +-#define RTL8365MB_MSTI_CTRL_PORT_STATE_MASK(_physport) \ +- (0x3 << RTL8365MB_MSTI_CTRL_PORT_STATE_OFFSET((_physport))) +- +-/* MIB counter value registers */ +-#define RTL8365MB_MIB_COUNTER_BASE 0x1000 +-#define RTL8365MB_MIB_COUNTER_REG(_x) (RTL8365MB_MIB_COUNTER_BASE + (_x)) +- +-/* MIB counter address register */ +-#define RTL8365MB_MIB_ADDRESS_REG 0x1004 +-#define RTL8365MB_MIB_ADDRESS_PORT_OFFSET 0x007C +-#define RTL8365MB_MIB_ADDRESS(_p, _x) \ +- (((RTL8365MB_MIB_ADDRESS_PORT_OFFSET) * (_p) + (_x)) >> 2) +- +-#define RTL8365MB_MIB_CTRL0_REG 0x1005 +-#define RTL8365MB_MIB_CTRL0_RESET_MASK 0x0002 +-#define RTL8365MB_MIB_CTRL0_BUSY_MASK 0x0001 +- +-/* The DSA callback .get_stats64 runs in atomic context, so we are not allowed +- * to block. On the other hand, accessing MIB counters absolutely requires us to +- * block. The solution is thus to schedule work which polls the MIB counters +- * asynchronously and updates some private data, which the callback can then +- * fetch atomically. Three seconds should be a good enough polling interval. +- */ +-#define RTL8365MB_STATS_INTERVAL_JIFFIES (3 * HZ) +- +-enum rtl8365mb_mib_counter_index { +- RTL8365MB_MIB_ifInOctets, +- RTL8365MB_MIB_dot3StatsFCSErrors, +- RTL8365MB_MIB_dot3StatsSymbolErrors, +- RTL8365MB_MIB_dot3InPauseFrames, +- RTL8365MB_MIB_dot3ControlInUnknownOpcodes, +- RTL8365MB_MIB_etherStatsFragments, +- RTL8365MB_MIB_etherStatsJabbers, +- RTL8365MB_MIB_ifInUcastPkts, +- RTL8365MB_MIB_etherStatsDropEvents, +- RTL8365MB_MIB_ifInMulticastPkts, +- RTL8365MB_MIB_ifInBroadcastPkts, +- RTL8365MB_MIB_inMldChecksumError, +- RTL8365MB_MIB_inIgmpChecksumError, +- RTL8365MB_MIB_inMldSpecificQuery, +- RTL8365MB_MIB_inMldGeneralQuery, +- RTL8365MB_MIB_inIgmpSpecificQuery, +- RTL8365MB_MIB_inIgmpGeneralQuery, +- RTL8365MB_MIB_inMldLeaves, +- RTL8365MB_MIB_inIgmpLeaves, +- RTL8365MB_MIB_etherStatsOctets, +- RTL8365MB_MIB_etherStatsUnderSizePkts, +- RTL8365MB_MIB_etherOversizeStats, +- RTL8365MB_MIB_etherStatsPkts64Octets, +- RTL8365MB_MIB_etherStatsPkts65to127Octets, +- RTL8365MB_MIB_etherStatsPkts128to255Octets, +- RTL8365MB_MIB_etherStatsPkts256to511Octets, +- RTL8365MB_MIB_etherStatsPkts512to1023Octets, +- RTL8365MB_MIB_etherStatsPkts1024to1518Octets, +- RTL8365MB_MIB_ifOutOctets, +- RTL8365MB_MIB_dot3StatsSingleCollisionFrames, +- RTL8365MB_MIB_dot3StatsMultipleCollisionFrames, +- RTL8365MB_MIB_dot3StatsDeferredTransmissions, +- RTL8365MB_MIB_dot3StatsLateCollisions, +- RTL8365MB_MIB_etherStatsCollisions, +- RTL8365MB_MIB_dot3StatsExcessiveCollisions, +- RTL8365MB_MIB_dot3OutPauseFrames, +- RTL8365MB_MIB_ifOutDiscards, +- RTL8365MB_MIB_dot1dTpPortInDiscards, +- RTL8365MB_MIB_ifOutUcastPkts, +- RTL8365MB_MIB_ifOutMulticastPkts, +- RTL8365MB_MIB_ifOutBroadcastPkts, +- RTL8365MB_MIB_outOampduPkts, +- RTL8365MB_MIB_inOampduPkts, +- RTL8365MB_MIB_inIgmpJoinsSuccess, +- RTL8365MB_MIB_inIgmpJoinsFail, +- RTL8365MB_MIB_inMldJoinsSuccess, +- RTL8365MB_MIB_inMldJoinsFail, +- RTL8365MB_MIB_inReportSuppressionDrop, +- RTL8365MB_MIB_inLeaveSuppressionDrop, +- RTL8365MB_MIB_outIgmpReports, +- RTL8365MB_MIB_outIgmpLeaves, +- RTL8365MB_MIB_outIgmpGeneralQuery, +- RTL8365MB_MIB_outIgmpSpecificQuery, +- RTL8365MB_MIB_outMldReports, +- RTL8365MB_MIB_outMldLeaves, +- RTL8365MB_MIB_outMldGeneralQuery, +- RTL8365MB_MIB_outMldSpecificQuery, +- RTL8365MB_MIB_inKnownMulticastPkts, +- RTL8365MB_MIB_END, +-}; +- +-struct rtl8365mb_mib_counter { +- u32 offset; +- u32 length; +- const char *name; +-}; +- +-#define RTL8365MB_MAKE_MIB_COUNTER(_offset, _length, _name) \ +- [RTL8365MB_MIB_ ## _name] = { _offset, _length, #_name } +- +-static struct rtl8365mb_mib_counter rtl8365mb_mib_counters[] = { +- RTL8365MB_MAKE_MIB_COUNTER(0, 4, ifInOctets), +- RTL8365MB_MAKE_MIB_COUNTER(4, 2, dot3StatsFCSErrors), +- RTL8365MB_MAKE_MIB_COUNTER(6, 2, dot3StatsSymbolErrors), +- RTL8365MB_MAKE_MIB_COUNTER(8, 2, dot3InPauseFrames), +- RTL8365MB_MAKE_MIB_COUNTER(10, 2, dot3ControlInUnknownOpcodes), +- RTL8365MB_MAKE_MIB_COUNTER(12, 2, etherStatsFragments), +- RTL8365MB_MAKE_MIB_COUNTER(14, 2, etherStatsJabbers), +- RTL8365MB_MAKE_MIB_COUNTER(16, 2, ifInUcastPkts), +- RTL8365MB_MAKE_MIB_COUNTER(18, 2, etherStatsDropEvents), +- RTL8365MB_MAKE_MIB_COUNTER(20, 2, ifInMulticastPkts), +- RTL8365MB_MAKE_MIB_COUNTER(22, 2, ifInBroadcastPkts), +- RTL8365MB_MAKE_MIB_COUNTER(24, 2, inMldChecksumError), +- RTL8365MB_MAKE_MIB_COUNTER(26, 2, inIgmpChecksumError), +- RTL8365MB_MAKE_MIB_COUNTER(28, 2, inMldSpecificQuery), +- RTL8365MB_MAKE_MIB_COUNTER(30, 2, inMldGeneralQuery), +- RTL8365MB_MAKE_MIB_COUNTER(32, 2, inIgmpSpecificQuery), +- RTL8365MB_MAKE_MIB_COUNTER(34, 2, inIgmpGeneralQuery), +- RTL8365MB_MAKE_MIB_COUNTER(36, 2, inMldLeaves), +- RTL8365MB_MAKE_MIB_COUNTER(38, 2, inIgmpLeaves), +- RTL8365MB_MAKE_MIB_COUNTER(40, 4, etherStatsOctets), +- RTL8365MB_MAKE_MIB_COUNTER(44, 2, etherStatsUnderSizePkts), +- RTL8365MB_MAKE_MIB_COUNTER(46, 2, etherOversizeStats), +- RTL8365MB_MAKE_MIB_COUNTER(48, 2, etherStatsPkts64Octets), +- RTL8365MB_MAKE_MIB_COUNTER(50, 2, etherStatsPkts65to127Octets), +- RTL8365MB_MAKE_MIB_COUNTER(52, 2, etherStatsPkts128to255Octets), +- RTL8365MB_MAKE_MIB_COUNTER(54, 2, etherStatsPkts256to511Octets), +- RTL8365MB_MAKE_MIB_COUNTER(56, 2, etherStatsPkts512to1023Octets), +- RTL8365MB_MAKE_MIB_COUNTER(58, 2, etherStatsPkts1024to1518Octets), +- RTL8365MB_MAKE_MIB_COUNTER(60, 4, ifOutOctets), +- RTL8365MB_MAKE_MIB_COUNTER(64, 2, dot3StatsSingleCollisionFrames), +- RTL8365MB_MAKE_MIB_COUNTER(66, 2, dot3StatsMultipleCollisionFrames), +- RTL8365MB_MAKE_MIB_COUNTER(68, 2, dot3StatsDeferredTransmissions), +- RTL8365MB_MAKE_MIB_COUNTER(70, 2, dot3StatsLateCollisions), +- RTL8365MB_MAKE_MIB_COUNTER(72, 2, etherStatsCollisions), +- RTL8365MB_MAKE_MIB_COUNTER(74, 2, dot3StatsExcessiveCollisions), +- RTL8365MB_MAKE_MIB_COUNTER(76, 2, dot3OutPauseFrames), +- RTL8365MB_MAKE_MIB_COUNTER(78, 2, ifOutDiscards), +- RTL8365MB_MAKE_MIB_COUNTER(80, 2, dot1dTpPortInDiscards), +- RTL8365MB_MAKE_MIB_COUNTER(82, 2, ifOutUcastPkts), +- RTL8365MB_MAKE_MIB_COUNTER(84, 2, ifOutMulticastPkts), +- RTL8365MB_MAKE_MIB_COUNTER(86, 2, ifOutBroadcastPkts), +- RTL8365MB_MAKE_MIB_COUNTER(88, 2, outOampduPkts), +- RTL8365MB_MAKE_MIB_COUNTER(90, 2, inOampduPkts), +- RTL8365MB_MAKE_MIB_COUNTER(92, 4, inIgmpJoinsSuccess), +- RTL8365MB_MAKE_MIB_COUNTER(96, 2, inIgmpJoinsFail), +- RTL8365MB_MAKE_MIB_COUNTER(98, 2, inMldJoinsSuccess), +- RTL8365MB_MAKE_MIB_COUNTER(100, 2, inMldJoinsFail), +- RTL8365MB_MAKE_MIB_COUNTER(102, 2, inReportSuppressionDrop), +- RTL8365MB_MAKE_MIB_COUNTER(104, 2, inLeaveSuppressionDrop), +- RTL8365MB_MAKE_MIB_COUNTER(106, 2, outIgmpReports), +- RTL8365MB_MAKE_MIB_COUNTER(108, 2, outIgmpLeaves), +- RTL8365MB_MAKE_MIB_COUNTER(110, 2, outIgmpGeneralQuery), +- RTL8365MB_MAKE_MIB_COUNTER(112, 2, outIgmpSpecificQuery), +- RTL8365MB_MAKE_MIB_COUNTER(114, 2, outMldReports), +- RTL8365MB_MAKE_MIB_COUNTER(116, 2, outMldLeaves), +- RTL8365MB_MAKE_MIB_COUNTER(118, 2, outMldGeneralQuery), +- RTL8365MB_MAKE_MIB_COUNTER(120, 2, outMldSpecificQuery), +- RTL8365MB_MAKE_MIB_COUNTER(122, 2, inKnownMulticastPkts), +-}; +- +-static_assert(ARRAY_SIZE(rtl8365mb_mib_counters) == RTL8365MB_MIB_END); +- +-struct rtl8365mb_jam_tbl_entry { +- u16 reg; +- u16 val; +-}; +- +-/* Lifted from the vendor driver sources */ +-static const struct rtl8365mb_jam_tbl_entry rtl8365mb_init_jam_8365mb_vc[] = { +- { 0x13EB, 0x15BB }, { 0x1303, 0x06D6 }, { 0x1304, 0x0700 }, +- { 0x13E2, 0x003F }, { 0x13F9, 0x0090 }, { 0x121E, 0x03CA }, +- { 0x1233, 0x0352 }, { 0x1237, 0x00A0 }, { 0x123A, 0x0030 }, +- { 0x1239, 0x0084 }, { 0x0301, 0x1000 }, { 0x1349, 0x001F }, +- { 0x18E0, 0x4004 }, { 0x122B, 0x241C }, { 0x1305, 0xC000 }, +- { 0x13F0, 0x0000 }, +-}; +- +-static const struct rtl8365mb_jam_tbl_entry rtl8365mb_init_jam_common[] = { +- { 0x1200, 0x7FCB }, { 0x0884, 0x0003 }, { 0x06EB, 0x0001 }, +- { 0x03Fa, 0x0007 }, { 0x08C8, 0x00C0 }, { 0x0A30, 0x020E }, +- { 0x0800, 0x0000 }, { 0x0802, 0x0000 }, { 0x09DA, 0x0013 }, +- { 0x1D32, 0x0002 }, +-}; +- +-enum rtl8365mb_phy_interface_mode { +- RTL8365MB_PHY_INTERFACE_MODE_INVAL = 0, +- RTL8365MB_PHY_INTERFACE_MODE_INTERNAL = BIT(0), +- RTL8365MB_PHY_INTERFACE_MODE_MII = BIT(1), +- RTL8365MB_PHY_INTERFACE_MODE_TMII = BIT(2), +- RTL8365MB_PHY_INTERFACE_MODE_RMII = BIT(3), +- RTL8365MB_PHY_INTERFACE_MODE_RGMII = BIT(4), +- RTL8365MB_PHY_INTERFACE_MODE_SGMII = BIT(5), +- RTL8365MB_PHY_INTERFACE_MODE_HSGMII = BIT(6), +-}; +- +-/** +- * struct rtl8365mb_extint - external interface info +- * @port: the port with an external interface +- * @id: the external interface ID, which is either 0, 1, or 2 +- * @supported_interfaces: a bitmask of supported PHY interface modes +- * +- * Represents a mapping: port -> { id, supported_interfaces }. To be embedded +- * in &struct rtl8365mb_chip_info for every port with an external interface. +- */ +-struct rtl8365mb_extint { +- int port; +- int id; +- unsigned int supported_interfaces; +-}; +- +-/** +- * struct rtl8365mb_chip_info - static chip-specific info +- * @name: human-readable chip name +- * @chip_id: chip identifier +- * @chip_ver: chip silicon revision +- * @extints: available external interfaces +- * @jam_table: chip-specific initialization jam table +- * @jam_size: size of the chip's jam table +- * +- * These data are specific to a given chip in the family of switches supported +- * by this driver. When adding support for another chip in the family, a new +- * chip info should be added to the rtl8365mb_chip_infos array. +- */ +-struct rtl8365mb_chip_info { +- const char *name; +- u32 chip_id; +- u32 chip_ver; +- const struct rtl8365mb_extint extints[RTL8365MB_MAX_NUM_EXTINTS]; +- const struct rtl8365mb_jam_tbl_entry *jam_table; +- size_t jam_size; +-}; +- +-/* Chip info for each supported switch in the family */ +-#define PHY_INTF(_mode) (RTL8365MB_PHY_INTERFACE_MODE_ ## _mode) +-static const struct rtl8365mb_chip_info rtl8365mb_chip_infos[] = { +- { +- .name = "RTL8365MB-VC", +- .chip_id = 0x6367, +- .chip_ver = 0x0040, +- .extints = { +- { 6, 1, PHY_INTF(MII) | PHY_INTF(TMII) | +- PHY_INTF(RMII) | PHY_INTF(RGMII) }, +- }, +- .jam_table = rtl8365mb_init_jam_8365mb_vc, +- .jam_size = ARRAY_SIZE(rtl8365mb_init_jam_8365mb_vc), +- }, +- { +- .name = "RTL8367S", +- .chip_id = 0x6367, +- .chip_ver = 0x00A0, +- .extints = { +- { 6, 1, PHY_INTF(SGMII) | PHY_INTF(HSGMII) }, +- { 7, 2, PHY_INTF(MII) | PHY_INTF(TMII) | +- PHY_INTF(RMII) | PHY_INTF(RGMII) }, +- }, +- .jam_table = rtl8365mb_init_jam_8365mb_vc, +- .jam_size = ARRAY_SIZE(rtl8365mb_init_jam_8365mb_vc), +- }, +- { +- .name = "RTL8367SB", +- .chip_id = 0x6367, +- .chip_ver = 0x0010, +- .extints = { +- { 6, 1, PHY_INTF(MII) | PHY_INTF(TMII) | +- PHY_INTF(RMII) | PHY_INTF(RGMII) | +- PHY_INTF(SGMII) | PHY_INTF(HSGMII) }, +- { 7, 2, PHY_INTF(MII) | PHY_INTF(TMII) | +- PHY_INTF(RMII) | PHY_INTF(RGMII) }, +- }, +- .jam_table = rtl8365mb_init_jam_8365mb_vc, +- .jam_size = ARRAY_SIZE(rtl8365mb_init_jam_8365mb_vc), +- }, +- { +- .name = "RTL8367RB-VB", +- .chip_id = 0x6367, +- .chip_ver = 0x0020, +- .extints = { +- { 6, 1, PHY_INTF(MII) | PHY_INTF(TMII) | +- PHY_INTF(RMII) | PHY_INTF(RGMII) }, +- { 7, 2, PHY_INTF(MII) | PHY_INTF(TMII) | +- PHY_INTF(RMII) | PHY_INTF(RGMII) }, +- }, +- .jam_table = rtl8365mb_init_jam_8365mb_vc, +- .jam_size = ARRAY_SIZE(rtl8365mb_init_jam_8365mb_vc), +- }, +-}; +- +-enum rtl8365mb_stp_state { +- RTL8365MB_STP_STATE_DISABLED = 0, +- RTL8365MB_STP_STATE_BLOCKING = 1, +- RTL8365MB_STP_STATE_LEARNING = 2, +- RTL8365MB_STP_STATE_FORWARDING = 3, +-}; +- +-enum rtl8365mb_cpu_insert { +- RTL8365MB_CPU_INSERT_TO_ALL = 0, +- RTL8365MB_CPU_INSERT_TO_TRAPPING = 1, +- RTL8365MB_CPU_INSERT_TO_NONE = 2, +-}; +- +-enum rtl8365mb_cpu_position { +- RTL8365MB_CPU_POS_AFTER_SA = 0, +- RTL8365MB_CPU_POS_BEFORE_CRC = 1, +-}; +- +-enum rtl8365mb_cpu_format { +- RTL8365MB_CPU_FORMAT_8BYTES = 0, +- RTL8365MB_CPU_FORMAT_4BYTES = 1, +-}; +- +-enum rtl8365mb_cpu_rxlen { +- RTL8365MB_CPU_RXLEN_72BYTES = 0, +- RTL8365MB_CPU_RXLEN_64BYTES = 1, +-}; +- +-/** +- * struct rtl8365mb_cpu - CPU port configuration +- * @enable: enable/disable hardware insertion of CPU tag in switch->CPU frames +- * @mask: port mask of ports that parse should parse CPU tags +- * @trap_port: forward trapped frames to this port +- * @insert: CPU tag insertion mode in switch->CPU frames +- * @position: position of CPU tag in frame +- * @rx_length: minimum CPU RX length +- * @format: CPU tag format +- * +- * Represents the CPU tagging and CPU port configuration of the switch. These +- * settings are configurable at runtime. +- */ +-struct rtl8365mb_cpu { +- bool enable; +- u32 mask; +- u32 trap_port; +- enum rtl8365mb_cpu_insert insert; +- enum rtl8365mb_cpu_position position; +- enum rtl8365mb_cpu_rxlen rx_length; +- enum rtl8365mb_cpu_format format; +-}; +- +-/** +- * struct rtl8365mb_port - private per-port data +- * @priv: pointer to parent realtek_priv data +- * @index: DSA port index, same as dsa_port::index +- * @stats: link statistics populated by rtl8365mb_stats_poll, ready for atomic +- * access via rtl8365mb_get_stats64 +- * @stats_lock: protect the stats structure during read/update +- * @mib_work: delayed work for polling MIB counters +- */ +-struct rtl8365mb_port { +- struct realtek_priv *priv; +- unsigned int index; +- struct rtnl_link_stats64 stats; +- spinlock_t stats_lock; +- struct delayed_work mib_work; +-}; +- +-/** +- * struct rtl8365mb - driver private data +- * @priv: pointer to parent realtek_priv data +- * @irq: registered IRQ or zero +- * @chip_info: chip-specific info about the attached switch +- * @cpu: CPU tagging and CPU port configuration for this chip +- * @mib_lock: prevent concurrent reads of MIB counters +- * @ports: per-port data +- * +- * Private data for this driver. +- */ +-struct rtl8365mb { +- struct realtek_priv *priv; +- int irq; +- const struct rtl8365mb_chip_info *chip_info; +- struct rtl8365mb_cpu cpu; +- struct mutex mib_lock; +- struct rtl8365mb_port ports[RTL8365MB_MAX_NUM_PORTS]; +-}; +- +-static int rtl8365mb_phy_poll_busy(struct realtek_priv *priv) +-{ +- u32 val; +- +- return regmap_read_poll_timeout(priv->map_nolock, +- RTL8365MB_INDIRECT_ACCESS_STATUS_REG, +- val, !val, 10, 100); +-} +- +-static int rtl8365mb_phy_ocp_prepare(struct realtek_priv *priv, int phy, +- u32 ocp_addr) +-{ +- u32 val; +- int ret; +- +- /* Set OCP prefix */ +- val = FIELD_GET(RTL8365MB_PHY_OCP_ADDR_PREFIX_MASK, ocp_addr); +- ret = regmap_update_bits( +- priv->map_nolock, RTL8365MB_GPHY_OCP_MSB_0_REG, +- RTL8365MB_GPHY_OCP_MSB_0_CFG_CPU_OCPADR_MASK, +- FIELD_PREP(RTL8365MB_GPHY_OCP_MSB_0_CFG_CPU_OCPADR_MASK, val)); +- if (ret) +- return ret; +- +- /* Set PHY register address */ +- val = RTL8365MB_PHY_BASE; +- val |= FIELD_PREP(RTL8365MB_INDIRECT_ACCESS_ADDRESS_PHYNUM_MASK, phy); +- val |= FIELD_PREP(RTL8365MB_INDIRECT_ACCESS_ADDRESS_OCPADR_5_1_MASK, +- ocp_addr >> 1); +- val |= FIELD_PREP(RTL8365MB_INDIRECT_ACCESS_ADDRESS_OCPADR_9_6_MASK, +- ocp_addr >> 6); +- ret = regmap_write(priv->map_nolock, +- RTL8365MB_INDIRECT_ACCESS_ADDRESS_REG, val); +- if (ret) +- return ret; +- +- return 0; +-} +- +-static int rtl8365mb_phy_ocp_read(struct realtek_priv *priv, int phy, +- u32 ocp_addr, u16 *data) +-{ +- u32 val; +- int ret; +- +- rtl83xx_lock(priv); +- +- ret = rtl8365mb_phy_poll_busy(priv); +- if (ret) +- goto out; +- +- ret = rtl8365mb_phy_ocp_prepare(priv, phy, ocp_addr); +- if (ret) +- goto out; +- +- /* Execute read operation */ +- val = FIELD_PREP(RTL8365MB_INDIRECT_ACCESS_CTRL_CMD_MASK, +- RTL8365MB_INDIRECT_ACCESS_CTRL_CMD_VALUE) | +- FIELD_PREP(RTL8365MB_INDIRECT_ACCESS_CTRL_RW_MASK, +- RTL8365MB_INDIRECT_ACCESS_CTRL_RW_READ); +- ret = regmap_write(priv->map_nolock, RTL8365MB_INDIRECT_ACCESS_CTRL_REG, +- val); +- if (ret) +- goto out; +- +- ret = rtl8365mb_phy_poll_busy(priv); +- if (ret) +- goto out; +- +- /* Get PHY register data */ +- ret = regmap_read(priv->map_nolock, +- RTL8365MB_INDIRECT_ACCESS_READ_DATA_REG, &val); +- if (ret) +- goto out; +- +- *data = val & 0xFFFF; +- +-out: +- rtl83xx_unlock(priv); +- +- return ret; +-} +- +-static int rtl8365mb_phy_ocp_write(struct realtek_priv *priv, int phy, +- u32 ocp_addr, u16 data) +-{ +- u32 val; +- int ret; +- +- rtl83xx_lock(priv); +- +- ret = rtl8365mb_phy_poll_busy(priv); +- if (ret) +- goto out; +- +- ret = rtl8365mb_phy_ocp_prepare(priv, phy, ocp_addr); +- if (ret) +- goto out; +- +- /* Set PHY register data */ +- ret = regmap_write(priv->map_nolock, +- RTL8365MB_INDIRECT_ACCESS_WRITE_DATA_REG, data); +- if (ret) +- goto out; +- +- /* Execute write operation */ +- val = FIELD_PREP(RTL8365MB_INDIRECT_ACCESS_CTRL_CMD_MASK, +- RTL8365MB_INDIRECT_ACCESS_CTRL_CMD_VALUE) | +- FIELD_PREP(RTL8365MB_INDIRECT_ACCESS_CTRL_RW_MASK, +- RTL8365MB_INDIRECT_ACCESS_CTRL_RW_WRITE); +- ret = regmap_write(priv->map_nolock, RTL8365MB_INDIRECT_ACCESS_CTRL_REG, +- val); +- if (ret) +- goto out; +- +- ret = rtl8365mb_phy_poll_busy(priv); +- if (ret) +- goto out; +- +-out: +- rtl83xx_unlock(priv); +- +- return ret; +-} +- +-static int rtl8365mb_phy_read(struct realtek_priv *priv, int phy, int regnum) +-{ +- u32 ocp_addr; +- u16 val; +- int ret; +- +- if (phy > RTL8365MB_PHYADDRMAX) +- return -EINVAL; +- +- if (regnum > RTL8365MB_PHYREGMAX) +- return -EINVAL; +- +- ocp_addr = RTL8365MB_PHY_OCP_ADDR_PHYREG_BASE + regnum * 2; +- +- ret = rtl8365mb_phy_ocp_read(priv, phy, ocp_addr, &val); +- if (ret) { +- dev_err(priv->dev, +- "failed to read PHY%d reg %02x @ %04x, ret %pe\n", phy, +- regnum, ocp_addr, ERR_PTR(ret)); +- return ret; +- } +- +- dev_dbg(priv->dev, "read PHY%d register 0x%02x @ %04x, val <- %04x\n", +- phy, regnum, ocp_addr, val); +- +- return val; +-} +- +-static int rtl8365mb_phy_write(struct realtek_priv *priv, int phy, int regnum, +- u16 val) +-{ +- u32 ocp_addr; +- int ret; +- +- if (phy > RTL8365MB_PHYADDRMAX) +- return -EINVAL; +- +- if (regnum > RTL8365MB_PHYREGMAX) +- return -EINVAL; +- +- ocp_addr = RTL8365MB_PHY_OCP_ADDR_PHYREG_BASE + regnum * 2; +- +- ret = rtl8365mb_phy_ocp_write(priv, phy, ocp_addr, val); +- if (ret) { +- dev_err(priv->dev, +- "failed to write PHY%d reg %02x @ %04x, ret %pe\n", phy, +- regnum, ocp_addr, ERR_PTR(ret)); +- return ret; +- } +- +- dev_dbg(priv->dev, "write PHY%d register 0x%02x @ %04x, val -> %04x\n", +- phy, regnum, ocp_addr, val); +- +- return 0; +-} +- +-static const struct rtl8365mb_extint * +-rtl8365mb_get_port_extint(struct realtek_priv *priv, int port) +-{ +- struct rtl8365mb *mb = priv->chip_data; +- int i; +- +- for (i = 0; i < RTL8365MB_MAX_NUM_EXTINTS; i++) { +- const struct rtl8365mb_extint *extint = +- &mb->chip_info->extints[i]; +- +- if (!extint->supported_interfaces) +- continue; +- +- if (extint->port == port) +- return extint; +- } +- +- return NULL; +-} +- +-static enum dsa_tag_protocol +-rtl8365mb_get_tag_protocol(struct dsa_switch *ds, int port, +- enum dsa_tag_protocol mp) +-{ +- struct realtek_priv *priv = ds->priv; +- struct rtl8365mb_cpu *cpu; +- struct rtl8365mb *mb; +- +- mb = priv->chip_data; +- cpu = &mb->cpu; +- +- if (cpu->position == RTL8365MB_CPU_POS_BEFORE_CRC) +- return DSA_TAG_PROTO_RTL8_4T; +- +- return DSA_TAG_PROTO_RTL8_4; +-} +- +-static int rtl8365mb_ext_config_rgmii(struct realtek_priv *priv, int port, +- phy_interface_t interface) +-{ +- const struct rtl8365mb_extint *extint = +- rtl8365mb_get_port_extint(priv, port); +- struct dsa_switch *ds = &priv->ds; +- struct device_node *dn; +- struct dsa_port *dp; +- int tx_delay = 0; +- int rx_delay = 0; +- u32 val; +- int ret; +- +- if (!extint) +- return -ENODEV; +- +- dp = dsa_to_port(ds, port); +- dn = dp->dn; +- +- /* Set the RGMII TX/RX delay +- * +- * The Realtek vendor driver indicates the following possible +- * configuration settings: +- * +- * TX delay: +- * 0 = no delay, 1 = 2 ns delay +- * RX delay: +- * 0 = no delay, 7 = maximum delay +- * Each step is approximately 0.3 ns, so the maximum delay is about +- * 2.1 ns. +- * +- * The vendor driver also states that this must be configured *before* +- * forcing the external interface into a particular mode, which is done +- * in the rtl8365mb_phylink_mac_link_{up,down} functions. +- * +- * Only configure an RGMII TX (resp. RX) delay if the +- * tx-internal-delay-ps (resp. rx-internal-delay-ps) OF property is +- * specified. We ignore the detail of the RGMII interface mode +- * (RGMII_{RXID, TXID, etc.}), as this is considered to be a PHY-only +- * property. +- */ +- if (!of_property_read_u32(dn, "tx-internal-delay-ps", &val)) { +- val = val / 1000; /* convert to ns */ +- +- if (val == 0 || val == 2) +- tx_delay = val / 2; +- else +- dev_warn(priv->dev, +- "RGMII TX delay must be 0 or 2 ns\n"); +- } +- +- if (!of_property_read_u32(dn, "rx-internal-delay-ps", &val)) { +- val = DIV_ROUND_CLOSEST(val, 300); /* convert to 0.3 ns step */ +- +- if (val <= 7) +- rx_delay = val; +- else +- dev_warn(priv->dev, +- "RGMII RX delay must be 0 to 2.1 ns\n"); +- } +- +- ret = regmap_update_bits( +- priv->map, RTL8365MB_EXT_RGMXF_REG(extint->id), +- RTL8365MB_EXT_RGMXF_TXDELAY_MASK | +- RTL8365MB_EXT_RGMXF_RXDELAY_MASK, +- FIELD_PREP(RTL8365MB_EXT_RGMXF_TXDELAY_MASK, tx_delay) | +- FIELD_PREP(RTL8365MB_EXT_RGMXF_RXDELAY_MASK, rx_delay)); +- if (ret) +- return ret; +- +- ret = regmap_update_bits( +- priv->map, RTL8365MB_DIGITAL_INTERFACE_SELECT_REG(extint->id), +- RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_MASK(extint->id), +- RTL8365MB_EXT_PORT_MODE_RGMII +- << RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_OFFSET( +- extint->id)); +- if (ret) +- return ret; +- +- return 0; +-} +- +-static int rtl8365mb_ext_config_forcemode(struct realtek_priv *priv, int port, +- bool link, int speed, int duplex, +- bool tx_pause, bool rx_pause) +-{ +- const struct rtl8365mb_extint *extint = +- rtl8365mb_get_port_extint(priv, port); +- u32 r_tx_pause; +- u32 r_rx_pause; +- u32 r_duplex; +- u32 r_speed; +- u32 r_link; +- int val; +- int ret; +- +- if (!extint) +- return -ENODEV; +- +- if (link) { +- /* Force the link up with the desired configuration */ +- r_link = 1; +- r_rx_pause = rx_pause ? 1 : 0; +- r_tx_pause = tx_pause ? 1 : 0; +- +- if (speed == SPEED_1000) { +- r_speed = RTL8365MB_PORT_SPEED_1000M; +- } else if (speed == SPEED_100) { +- r_speed = RTL8365MB_PORT_SPEED_100M; +- } else if (speed == SPEED_10) { +- r_speed = RTL8365MB_PORT_SPEED_10M; +- } else { +- dev_err(priv->dev, "unsupported port speed %s\n", +- phy_speed_to_str(speed)); +- return -EINVAL; +- } +- +- if (duplex == DUPLEX_FULL) { +- r_duplex = 1; +- } else if (duplex == DUPLEX_HALF) { +- r_duplex = 0; +- } else { +- dev_err(priv->dev, "unsupported duplex %s\n", +- phy_duplex_to_str(duplex)); +- return -EINVAL; +- } +- } else { +- /* Force the link down and reset any programmed configuration */ +- r_link = 0; +- r_tx_pause = 0; +- r_rx_pause = 0; +- r_speed = 0; +- r_duplex = 0; +- } +- +- val = FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_EN_MASK, 1) | +- FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_TXPAUSE_MASK, +- r_tx_pause) | +- FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_RXPAUSE_MASK, +- r_rx_pause) | +- FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_LINK_MASK, r_link) | +- FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_DUPLEX_MASK, +- r_duplex) | +- FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_SPEED_MASK, r_speed); +- ret = regmap_write(priv->map, +- RTL8365MB_DIGITAL_INTERFACE_FORCE_REG(extint->id), +- val); +- if (ret) +- return ret; +- +- return 0; +-} +- +-static void rtl8365mb_phylink_get_caps(struct dsa_switch *ds, int port, +- struct phylink_config *config) +-{ +- const struct rtl8365mb_extint *extint = +- rtl8365mb_get_port_extint(ds->priv, port); +- +- config->mac_capabilities = MAC_SYM_PAUSE | MAC_ASYM_PAUSE | +- MAC_10 | MAC_100 | MAC_1000FD; +- +- if (!extint) { +- __set_bit(PHY_INTERFACE_MODE_INTERNAL, +- config->supported_interfaces); +- +- /* GMII is the default interface mode for phylib, so +- * we have to support it for ports with integrated PHY. +- */ +- __set_bit(PHY_INTERFACE_MODE_GMII, +- config->supported_interfaces); +- return; +- } +- +- /* Populate according to the modes supported by _this driver_, +- * not necessarily the modes supported by the hardware, some of +- * which remain unimplemented. +- */ +- +- if (extint->supported_interfaces & RTL8365MB_PHY_INTERFACE_MODE_RGMII) +- phy_interface_set_rgmii(config->supported_interfaces); +-} +- +-static void rtl8365mb_phylink_mac_config(struct phylink_config *config, +- unsigned int mode, +- const struct phylink_link_state *state) +-{ +- struct dsa_port *dp = dsa_phylink_to_port(config); +- struct realtek_priv *priv = dp->ds->priv; +- u8 port = dp->index; +- int ret; +- +- if (mode != MLO_AN_PHY && mode != MLO_AN_FIXED) { +- dev_err(priv->dev, +- "port %d supports only conventional PHY or fixed-link\n", +- port); +- return; +- } +- +- if (phy_interface_mode_is_rgmii(state->interface)) { +- ret = rtl8365mb_ext_config_rgmii(priv, port, state->interface); +- if (ret) +- dev_err(priv->dev, +- "failed to configure RGMII mode on port %d: %pe\n", +- port, ERR_PTR(ret)); +- return; +- } +- +- /* TODO: Implement MII and RMII modes, which the RTL8365MB-VC also +- * supports +- */ +-} +- +-static void rtl8365mb_phylink_mac_link_down(struct phylink_config *config, +- unsigned int mode, +- phy_interface_t interface) +-{ +- struct dsa_port *dp = dsa_phylink_to_port(config); +- struct realtek_priv *priv = dp->ds->priv; +- struct rtl8365mb_port *p; +- struct rtl8365mb *mb; +- u8 port = dp->index; +- int ret; +- +- mb = priv->chip_data; +- p = &mb->ports[port]; +- cancel_delayed_work_sync(&p->mib_work); +- +- if (phy_interface_mode_is_rgmii(interface)) { +- ret = rtl8365mb_ext_config_forcemode(priv, port, false, 0, 0, +- false, false); +- if (ret) +- dev_err(priv->dev, +- "failed to reset forced mode on port %d: %pe\n", +- port, ERR_PTR(ret)); +- +- return; +- } +-} +- +-static void rtl8365mb_phylink_mac_link_up(struct phylink_config *config, +- struct phy_device *phydev, +- unsigned int mode, +- phy_interface_t interface, +- int speed, int duplex, bool tx_pause, +- bool rx_pause) +-{ +- struct dsa_port *dp = dsa_phylink_to_port(config); +- struct realtek_priv *priv = dp->ds->priv; +- struct rtl8365mb_port *p; +- struct rtl8365mb *mb; +- u8 port = dp->index; +- int ret; +- +- mb = priv->chip_data; +- p = &mb->ports[port]; +- schedule_delayed_work(&p->mib_work, 0); +- +- if (phy_interface_mode_is_rgmii(interface)) { +- ret = rtl8365mb_ext_config_forcemode(priv, port, true, speed, +- duplex, tx_pause, +- rx_pause); +- if (ret) +- dev_err(priv->dev, +- "failed to force mode on port %d: %pe\n", port, +- ERR_PTR(ret)); +- +- return; +- } +-} +- +-static int rtl8365mb_port_change_mtu(struct dsa_switch *ds, int port, +- int new_mtu) +-{ +- struct realtek_priv *priv = ds->priv; +- int frame_size; +- +- /* When a new MTU is set, DSA always sets the CPU port's MTU to the +- * largest MTU of the user ports. Because the switch only has a global +- * RX length register, only allowing CPU port here is enough. +- */ +- if (!dsa_is_cpu_port(ds, port)) +- return 0; +- +- frame_size = new_mtu + VLAN_ETH_HLEN + ETH_FCS_LEN; +- +- dev_dbg(priv->dev, "changing mtu to %d (frame size: %d)\n", +- new_mtu, frame_size); +- +- return regmap_update_bits(priv->map, RTL8365MB_CFG0_MAX_LEN_REG, +- RTL8365MB_CFG0_MAX_LEN_MASK, +- FIELD_PREP(RTL8365MB_CFG0_MAX_LEN_MASK, +- frame_size)); +-} +- +-static int rtl8365mb_port_max_mtu(struct dsa_switch *ds, int port) +-{ +- return RTL8365MB_CFG0_MAX_LEN_MAX - VLAN_ETH_HLEN - ETH_FCS_LEN; +-} +- +-static void rtl8365mb_port_stp_state_set(struct dsa_switch *ds, int port, +- u8 state) +-{ +- struct realtek_priv *priv = ds->priv; +- enum rtl8365mb_stp_state val; +- int msti = 0; +- +- switch (state) { +- case BR_STATE_DISABLED: +- val = RTL8365MB_STP_STATE_DISABLED; +- break; +- case BR_STATE_BLOCKING: +- case BR_STATE_LISTENING: +- val = RTL8365MB_STP_STATE_BLOCKING; +- break; +- case BR_STATE_LEARNING: +- val = RTL8365MB_STP_STATE_LEARNING; +- break; +- case BR_STATE_FORWARDING: +- val = RTL8365MB_STP_STATE_FORWARDING; +- break; +- default: +- dev_err(priv->dev, "invalid STP state: %u\n", state); +- return; +- } +- +- regmap_update_bits(priv->map, RTL8365MB_MSTI_CTRL_REG(msti, port), +- RTL8365MB_MSTI_CTRL_PORT_STATE_MASK(port), +- val << RTL8365MB_MSTI_CTRL_PORT_STATE_OFFSET(port)); +-} +- +-static int rtl8365mb_port_set_learning(struct realtek_priv *priv, int port, +- bool enable) +-{ +- /* Enable/disable learning by limiting the number of L2 addresses the +- * port can learn. Realtek documentation states that a limit of zero +- * disables learning. When enabling learning, set it to the chip's +- * maximum. +- */ +- return regmap_write(priv->map, RTL8365MB_LUT_PORT_LEARN_LIMIT_REG(port), +- enable ? RTL8365MB_LEARN_LIMIT_MAX : 0); +-} +- +-static int rtl8365mb_port_set_isolation(struct realtek_priv *priv, int port, +- u32 mask) +-{ +- return regmap_write(priv->map, RTL8365MB_PORT_ISOLATION_REG(port), mask); +-} +- +-static int rtl8365mb_mib_counter_read(struct realtek_priv *priv, int port, +- u32 offset, u32 length, u64 *mibvalue) +-{ +- u64 tmpvalue = 0; +- u32 val; +- int ret; +- int i; +- +- /* The MIB address is an SRAM address. We request a particular address +- * and then poll the control register before reading the value from some +- * counter registers. +- */ +- ret = regmap_write(priv->map, RTL8365MB_MIB_ADDRESS_REG, +- RTL8365MB_MIB_ADDRESS(port, offset)); +- if (ret) +- return ret; +- +- /* Poll for completion */ +- ret = regmap_read_poll_timeout(priv->map, RTL8365MB_MIB_CTRL0_REG, val, +- !(val & RTL8365MB_MIB_CTRL0_BUSY_MASK), +- 10, 100); +- if (ret) +- return ret; +- +- /* Presumably this indicates a MIB counter read failure */ +- if (val & RTL8365MB_MIB_CTRL0_RESET_MASK) +- return -EIO; +- +- /* There are four MIB counter registers each holding a 16 bit word of a +- * MIB counter. Depending on the offset, we should read from the upper +- * two or lower two registers. In case the MIB counter is 4 words, we +- * read from all four registers. +- */ +- if (length == 4) +- offset = 3; +- else +- offset = (offset + 1) % 4; +- +- /* Read the MIB counter 16 bits at a time */ +- for (i = 0; i < length; i++) { +- ret = regmap_read(priv->map, +- RTL8365MB_MIB_COUNTER_REG(offset - i), &val); +- if (ret) +- return ret; +- +- tmpvalue = ((tmpvalue) << 16) | (val & 0xFFFF); +- } +- +- /* Only commit the result if no error occurred */ +- *mibvalue = tmpvalue; +- +- return 0; +-} +- +-static void rtl8365mb_get_ethtool_stats(struct dsa_switch *ds, int port, u64 *data) +-{ +- struct realtek_priv *priv = ds->priv; +- struct rtl8365mb *mb; +- int ret; +- int i; +- +- mb = priv->chip_data; +- +- mutex_lock(&mb->mib_lock); +- for (i = 0; i < RTL8365MB_MIB_END; i++) { +- struct rtl8365mb_mib_counter *mib = &rtl8365mb_mib_counters[i]; +- +- ret = rtl8365mb_mib_counter_read(priv, port, mib->offset, +- mib->length, &data[i]); +- if (ret) { +- dev_err(priv->dev, +- "failed to read port %d counters: %pe\n", port, +- ERR_PTR(ret)); +- break; +- } +- } +- mutex_unlock(&mb->mib_lock); +-} +- +-static void rtl8365mb_get_strings(struct dsa_switch *ds, int port, u32 stringset, u8 *data) +-{ +- int i; +- +- if (stringset != ETH_SS_STATS) +- return; +- +- for (i = 0; i < RTL8365MB_MIB_END; i++) { +- struct rtl8365mb_mib_counter *mib = &rtl8365mb_mib_counters[i]; +- ethtool_puts(&data, mib->name); +- } +-} +- +-static int rtl8365mb_get_sset_count(struct dsa_switch *ds, int port, int sset) +-{ +- if (sset != ETH_SS_STATS) +- return -EOPNOTSUPP; +- +- return RTL8365MB_MIB_END; +-} +- +-static void rtl8365mb_get_phy_stats(struct dsa_switch *ds, int port, +- struct ethtool_eth_phy_stats *phy_stats) +-{ +- struct realtek_priv *priv = ds->priv; +- struct rtl8365mb_mib_counter *mib; +- struct rtl8365mb *mb; +- +- mb = priv->chip_data; +- mib = &rtl8365mb_mib_counters[RTL8365MB_MIB_dot3StatsSymbolErrors]; +- +- mutex_lock(&mb->mib_lock); +- rtl8365mb_mib_counter_read(priv, port, mib->offset, mib->length, +- &phy_stats->SymbolErrorDuringCarrier); +- mutex_unlock(&mb->mib_lock); +-} +- +-static void rtl8365mb_get_mac_stats(struct dsa_switch *ds, int port, +- struct ethtool_eth_mac_stats *mac_stats) +-{ +- u64 cnt[RTL8365MB_MIB_END] = { +- [RTL8365MB_MIB_ifOutOctets] = 1, +- [RTL8365MB_MIB_ifOutUcastPkts] = 1, +- [RTL8365MB_MIB_ifOutMulticastPkts] = 1, +- [RTL8365MB_MIB_ifOutBroadcastPkts] = 1, +- [RTL8365MB_MIB_dot3OutPauseFrames] = 1, +- [RTL8365MB_MIB_ifOutDiscards] = 1, +- [RTL8365MB_MIB_ifInOctets] = 1, +- [RTL8365MB_MIB_ifInUcastPkts] = 1, +- [RTL8365MB_MIB_ifInMulticastPkts] = 1, +- [RTL8365MB_MIB_ifInBroadcastPkts] = 1, +- [RTL8365MB_MIB_dot3InPauseFrames] = 1, +- [RTL8365MB_MIB_dot3StatsSingleCollisionFrames] = 1, +- [RTL8365MB_MIB_dot3StatsMultipleCollisionFrames] = 1, +- [RTL8365MB_MIB_dot3StatsFCSErrors] = 1, +- [RTL8365MB_MIB_dot3StatsDeferredTransmissions] = 1, +- [RTL8365MB_MIB_dot3StatsLateCollisions] = 1, +- [RTL8365MB_MIB_dot3StatsExcessiveCollisions] = 1, +- +- }; +- struct realtek_priv *priv = ds->priv; +- struct rtl8365mb *mb; +- int ret; +- int i; +- +- mb = priv->chip_data; +- +- mutex_lock(&mb->mib_lock); +- for (i = 0; i < RTL8365MB_MIB_END; i++) { +- struct rtl8365mb_mib_counter *mib = &rtl8365mb_mib_counters[i]; +- +- /* Only fetch required MIB counters (marked = 1 above) */ +- if (!cnt[i]) +- continue; +- +- ret = rtl8365mb_mib_counter_read(priv, port, mib->offset, +- mib->length, &cnt[i]); +- if (ret) +- break; +- } +- mutex_unlock(&mb->mib_lock); +- +- /* The RTL8365MB-VC exposes MIB objects, which we have to translate into +- * IEEE 802.3 Managed Objects. This is not always completely faithful, +- * but we try out best. See RFC 3635 for a detailed treatment of the +- * subject. +- */ +- +- mac_stats->FramesTransmittedOK = cnt[RTL8365MB_MIB_ifOutUcastPkts] + +- cnt[RTL8365MB_MIB_ifOutMulticastPkts] + +- cnt[RTL8365MB_MIB_ifOutBroadcastPkts] + +- cnt[RTL8365MB_MIB_dot3OutPauseFrames] - +- cnt[RTL8365MB_MIB_ifOutDiscards]; +- mac_stats->SingleCollisionFrames = +- cnt[RTL8365MB_MIB_dot3StatsSingleCollisionFrames]; +- mac_stats->MultipleCollisionFrames = +- cnt[RTL8365MB_MIB_dot3StatsMultipleCollisionFrames]; +- mac_stats->FramesReceivedOK = cnt[RTL8365MB_MIB_ifInUcastPkts] + +- cnt[RTL8365MB_MIB_ifInMulticastPkts] + +- cnt[RTL8365MB_MIB_ifInBroadcastPkts] + +- cnt[RTL8365MB_MIB_dot3InPauseFrames]; +- mac_stats->FrameCheckSequenceErrors = +- cnt[RTL8365MB_MIB_dot3StatsFCSErrors]; +- mac_stats->OctetsTransmittedOK = cnt[RTL8365MB_MIB_ifOutOctets] - +- 18 * mac_stats->FramesTransmittedOK; +- mac_stats->FramesWithDeferredXmissions = +- cnt[RTL8365MB_MIB_dot3StatsDeferredTransmissions]; +- mac_stats->LateCollisions = cnt[RTL8365MB_MIB_dot3StatsLateCollisions]; +- mac_stats->FramesAbortedDueToXSColls = +- cnt[RTL8365MB_MIB_dot3StatsExcessiveCollisions]; +- mac_stats->OctetsReceivedOK = cnt[RTL8365MB_MIB_ifInOctets] - +- 18 * mac_stats->FramesReceivedOK; +- mac_stats->MulticastFramesXmittedOK = +- cnt[RTL8365MB_MIB_ifOutMulticastPkts]; +- mac_stats->BroadcastFramesXmittedOK = +- cnt[RTL8365MB_MIB_ifOutBroadcastPkts]; +- mac_stats->MulticastFramesReceivedOK = +- cnt[RTL8365MB_MIB_ifInMulticastPkts]; +- mac_stats->BroadcastFramesReceivedOK = +- cnt[RTL8365MB_MIB_ifInBroadcastPkts]; +-} +- +-static void rtl8365mb_get_ctrl_stats(struct dsa_switch *ds, int port, +- struct ethtool_eth_ctrl_stats *ctrl_stats) +-{ +- struct realtek_priv *priv = ds->priv; +- struct rtl8365mb_mib_counter *mib; +- struct rtl8365mb *mb; +- +- mb = priv->chip_data; +- mib = &rtl8365mb_mib_counters[RTL8365MB_MIB_dot3ControlInUnknownOpcodes]; +- +- mutex_lock(&mb->mib_lock); +- rtl8365mb_mib_counter_read(priv, port, mib->offset, mib->length, +- &ctrl_stats->UnsupportedOpcodesReceived); +- mutex_unlock(&mb->mib_lock); +-} +- +-static void rtl8365mb_stats_update(struct realtek_priv *priv, int port) +-{ +- u64 cnt[RTL8365MB_MIB_END] = { +- [RTL8365MB_MIB_ifOutOctets] = 1, +- [RTL8365MB_MIB_ifOutUcastPkts] = 1, +- [RTL8365MB_MIB_ifOutMulticastPkts] = 1, +- [RTL8365MB_MIB_ifOutBroadcastPkts] = 1, +- [RTL8365MB_MIB_ifOutDiscards] = 1, +- [RTL8365MB_MIB_ifInOctets] = 1, +- [RTL8365MB_MIB_ifInUcastPkts] = 1, +- [RTL8365MB_MIB_ifInMulticastPkts] = 1, +- [RTL8365MB_MIB_ifInBroadcastPkts] = 1, +- [RTL8365MB_MIB_etherStatsDropEvents] = 1, +- [RTL8365MB_MIB_etherStatsCollisions] = 1, +- [RTL8365MB_MIB_etherStatsFragments] = 1, +- [RTL8365MB_MIB_etherStatsJabbers] = 1, +- [RTL8365MB_MIB_dot3StatsFCSErrors] = 1, +- [RTL8365MB_MIB_dot3StatsLateCollisions] = 1, +- }; +- struct rtl8365mb *mb = priv->chip_data; +- struct rtnl_link_stats64 *stats; +- int ret; +- int i; +- +- stats = &mb->ports[port].stats; +- +- mutex_lock(&mb->mib_lock); +- for (i = 0; i < RTL8365MB_MIB_END; i++) { +- struct rtl8365mb_mib_counter *c = &rtl8365mb_mib_counters[i]; +- +- /* Only fetch required MIB counters (marked = 1 above) */ +- if (!cnt[i]) +- continue; +- +- ret = rtl8365mb_mib_counter_read(priv, port, c->offset, +- c->length, &cnt[i]); +- if (ret) +- break; +- } +- mutex_unlock(&mb->mib_lock); +- +- /* Don't update statistics if there was an error reading the counters */ +- if (ret) +- return; +- +- spin_lock(&mb->ports[port].stats_lock); +- +- stats->rx_packets = cnt[RTL8365MB_MIB_ifInUcastPkts] + +- cnt[RTL8365MB_MIB_ifInMulticastPkts] + +- cnt[RTL8365MB_MIB_ifInBroadcastPkts]; +- +- stats->tx_packets = cnt[RTL8365MB_MIB_ifOutUcastPkts] + +- cnt[RTL8365MB_MIB_ifOutMulticastPkts] + +- cnt[RTL8365MB_MIB_ifOutBroadcastPkts]; +- +- /* if{In,Out}Octets includes FCS - remove it */ +- stats->rx_bytes = cnt[RTL8365MB_MIB_ifInOctets] - 4 * stats->rx_packets; +- stats->tx_bytes = +- cnt[RTL8365MB_MIB_ifOutOctets] - 4 * stats->tx_packets; +- +- stats->rx_dropped = cnt[RTL8365MB_MIB_etherStatsDropEvents]; +- stats->tx_dropped = cnt[RTL8365MB_MIB_ifOutDiscards]; +- +- stats->multicast = cnt[RTL8365MB_MIB_ifInMulticastPkts]; +- stats->collisions = cnt[RTL8365MB_MIB_etherStatsCollisions]; +- +- stats->rx_length_errors = cnt[RTL8365MB_MIB_etherStatsFragments] + +- cnt[RTL8365MB_MIB_etherStatsJabbers]; +- stats->rx_crc_errors = cnt[RTL8365MB_MIB_dot3StatsFCSErrors]; +- stats->rx_errors = stats->rx_length_errors + stats->rx_crc_errors; +- +- stats->tx_aborted_errors = cnt[RTL8365MB_MIB_ifOutDiscards]; +- stats->tx_window_errors = cnt[RTL8365MB_MIB_dot3StatsLateCollisions]; +- stats->tx_errors = stats->tx_aborted_errors + stats->tx_window_errors; +- +- spin_unlock(&mb->ports[port].stats_lock); +-} +- +-static void rtl8365mb_stats_poll(struct work_struct *work) +-{ +- struct rtl8365mb_port *p = container_of(to_delayed_work(work), +- struct rtl8365mb_port, +- mib_work); +- struct realtek_priv *priv = p->priv; +- +- rtl8365mb_stats_update(priv, p->index); +- +- schedule_delayed_work(&p->mib_work, RTL8365MB_STATS_INTERVAL_JIFFIES); +-} +- +-static void rtl8365mb_get_stats64(struct dsa_switch *ds, int port, +- struct rtnl_link_stats64 *s) +-{ +- struct realtek_priv *priv = ds->priv; +- struct rtl8365mb_port *p; +- struct rtl8365mb *mb; +- +- mb = priv->chip_data; +- p = &mb->ports[port]; +- +- spin_lock(&p->stats_lock); +- memcpy(s, &p->stats, sizeof(*s)); +- spin_unlock(&p->stats_lock); +-} +- +-static void rtl8365mb_stats_setup(struct realtek_priv *priv) +-{ +- struct rtl8365mb *mb = priv->chip_data; +- struct dsa_switch *ds = &priv->ds; +- struct dsa_port *dp; +- +- /* Per-chip global mutex to protect MIB counter access, since doing +- * so requires accessing a series of registers in a particular order. +- */ +- mutex_init(&mb->mib_lock); +- +- dsa_switch_for_each_available_port(dp, ds) { +- struct rtl8365mb_port *p = &mb->ports[dp->index]; +- +- /* Per-port spinlock to protect the stats64 data */ +- spin_lock_init(&p->stats_lock); +- +- /* This work polls the MIB counters and keeps the stats64 data +- * up-to-date. +- */ +- INIT_DELAYED_WORK(&p->mib_work, rtl8365mb_stats_poll); +- } +-} +- +-static void rtl8365mb_stats_teardown(struct realtek_priv *priv) +-{ +- struct rtl8365mb *mb = priv->chip_data; +- struct dsa_switch *ds = &priv->ds; +- struct dsa_port *dp; +- +- dsa_switch_for_each_available_port(dp, ds) { +- struct rtl8365mb_port *p = &mb->ports[dp->index]; +- +- cancel_delayed_work_sync(&p->mib_work); +- } +-} +- +-static int rtl8365mb_get_and_clear_status_reg(struct realtek_priv *priv, u32 reg, +- u32 *val) +-{ +- int ret; +- +- ret = regmap_read(priv->map, reg, val); +- if (ret) +- return ret; +- +- return regmap_write(priv->map, reg, *val); +-} +- +-static irqreturn_t rtl8365mb_irq(int irq, void *data) +-{ +- struct realtek_priv *priv = data; +- unsigned long line_changes = 0; +- u32 stat; +- int line; +- int ret; +- +- ret = rtl8365mb_get_and_clear_status_reg(priv, RTL8365MB_INTR_STATUS_REG, +- &stat); +- if (ret) +- goto out_error; +- +- if (stat & RTL8365MB_INTR_LINK_CHANGE_MASK) { +- u32 linkdown_ind; +- u32 linkup_ind; +- u32 val; +- +- ret = rtl8365mb_get_and_clear_status_reg( +- priv, RTL8365MB_PORT_LINKUP_IND_REG, &val); +- if (ret) +- goto out_error; +- +- linkup_ind = FIELD_GET(RTL8365MB_PORT_LINKUP_IND_MASK, val); +- +- ret = rtl8365mb_get_and_clear_status_reg( +- priv, RTL8365MB_PORT_LINKDOWN_IND_REG, &val); +- if (ret) +- goto out_error; +- +- linkdown_ind = FIELD_GET(RTL8365MB_PORT_LINKDOWN_IND_MASK, val); +- +- line_changes = linkup_ind | linkdown_ind; +- } +- +- if (!line_changes) +- goto out_none; +- +- for_each_set_bit(line, &line_changes, priv->num_ports) { +- int child_irq = irq_find_mapping(priv->irqdomain, line); +- +- if (!child_irq) +- continue; +- +- handle_nested_irq(child_irq); +- } +- +- return IRQ_HANDLED; +- +-out_error: +- dev_err(priv->dev, "failed to read interrupt status: %pe\n", +- ERR_PTR(ret)); +- +-out_none: +- return IRQ_NONE; +-} +- +-static struct irq_chip rtl8365mb_irq_chip = { +- .name = "rtl8365mb", +- /* The hardware doesn't support masking IRQs on a per-port basis */ +-}; +- +-static int rtl8365mb_irq_map(struct irq_domain *domain, unsigned int irq, +- irq_hw_number_t hwirq) +-{ +- struct realtek_priv *priv = domain->host_data; +- struct rtl8365mb *mb = priv->chip_data; +- +- irq_set_chip_data(irq, priv); +- irq_set_chip_and_handler(irq, &rtl8365mb_irq_chip, handle_simple_irq); +- irq_set_nested_thread(irq, 1); +- irq_set_noprobe(irq); +- irq_set_parent(irq, mb->irq); +- +- return 0; +-} +- +-static void rtl8365mb_irq_unmap(struct irq_domain *d, unsigned int irq) +-{ +- irq_set_nested_thread(irq, 0); +- irq_set_chip_and_handler(irq, NULL, NULL); +- irq_set_chip_data(irq, NULL); +-} +- +-static const struct irq_domain_ops rtl8365mb_irqdomain_ops = { +- .map = rtl8365mb_irq_map, +- .unmap = rtl8365mb_irq_unmap, +- .xlate = irq_domain_xlate_onecell, +-}; +- +-static int rtl8365mb_set_irq_enable(struct realtek_priv *priv, bool enable) +-{ +- return regmap_update_bits(priv->map, RTL8365MB_INTR_CTRL_REG, +- RTL8365MB_INTR_LINK_CHANGE_MASK, +- FIELD_PREP(RTL8365MB_INTR_LINK_CHANGE_MASK, +- enable ? 1 : 0)); +-} +- +-static int rtl8365mb_irq_enable(struct realtek_priv *priv) +-{ +- return rtl8365mb_set_irq_enable(priv, true); +-} +- +-static int rtl8365mb_irq_disable(struct realtek_priv *priv) +-{ +- return rtl8365mb_set_irq_enable(priv, false); +-} +- +-static int rtl8365mb_irq_setup(struct realtek_priv *priv) +-{ +- struct rtl8365mb *mb = priv->chip_data; +- struct dsa_switch *ds = &priv->ds; +- struct device_node *intc; +- struct dsa_port *dp; +- u32 irq_trig; +- int virq; +- int irq; +- u32 val; +- int ret; +- +- intc = of_get_child_by_name(priv->dev->of_node, "interrupt-controller"); +- if (!intc) { +- dev_err(priv->dev, "missing child interrupt-controller node\n"); +- return -EINVAL; +- } +- +- /* rtl8365mb IRQs cascade off this one */ +- irq = of_irq_get(intc, 0); +- if (irq <= 0) { +- if (!irq) { +- dev_err(priv->dev, "failed to map IRQ\n"); +- ret = -EINVAL; +- } else { +- ret = dev_err_probe(priv->dev, irq, +- "failed to get parent irq\n"); +- } +- goto out_put_node; +- } +- +- /* Store the irq so that we know to map and free it during teardown */ +- mb->irq = irq; +- +- priv->irqdomain = irq_domain_create_linear(of_fwnode_handle(intc), priv->num_ports, +- &rtl8365mb_irqdomain_ops, priv); +- if (!priv->irqdomain) { +- dev_err(priv->dev, "failed to add irq domain\n"); +- ret = -ENOMEM; +- goto out_put_node; +- } +- +- dsa_switch_for_each_available_port(dp, ds) { +- virq = irq_create_mapping(priv->irqdomain, dp->index); +- if (!virq) { +- dev_err(priv->dev, +- "failed to create irq domain mapping\n"); +- ret = -EINVAL; +- goto out_remove_irqdomain; +- } +- +- irq_set_parent(virq, irq); +- } +- +- /* Configure chip interrupt signal polarity */ +- irq_trig = irq_get_trigger_type(irq); +- switch (irq_trig) { +- case IRQF_TRIGGER_RISING: +- case IRQF_TRIGGER_HIGH: +- val = RTL8365MB_INTR_POLARITY_HIGH; +- break; +- case IRQF_TRIGGER_FALLING: +- case IRQF_TRIGGER_LOW: +- val = RTL8365MB_INTR_POLARITY_LOW; +- break; +- default: +- dev_err(priv->dev, "unsupported irq trigger type %u\n", +- irq_trig); +- ret = -EINVAL; +- goto out_remove_irqdomain; +- } +- +- ret = regmap_update_bits(priv->map, RTL8365MB_INTR_POLARITY_REG, +- RTL8365MB_INTR_POLARITY_MASK, +- FIELD_PREP(RTL8365MB_INTR_POLARITY_MASK, val)); +- if (ret) +- goto out_remove_irqdomain; +- +- /* Disable the interrupt in case the chip has it enabled on reset */ +- ret = rtl8365mb_irq_disable(priv); +- if (ret) +- goto out_remove_irqdomain; +- +- /* Clear the interrupt status register */ +- ret = regmap_write(priv->map, RTL8365MB_INTR_STATUS_REG, +- RTL8365MB_INTR_ALL_MASK); +- if (ret) +- goto out_remove_irqdomain; +- +- ret = request_threaded_irq(irq, NULL, rtl8365mb_irq, IRQF_ONESHOT, +- "rtl8365mb", priv); +- if (ret) { +- dev_err(priv->dev, "failed to request irq: %pe\n", +- ERR_PTR(ret)); +- goto out_remove_irqdomain; +- } +- +- ret = rtl8365mb_irq_enable(priv); +- if (ret) +- goto out_free_irq; +- +- of_node_put(intc); +- +- return 0; +- +-out_free_irq: +- free_irq(mb->irq, priv); +- +-out_remove_irqdomain: +- dsa_switch_for_each_port(dp, ds) { +- virq = irq_find_mapping(priv->irqdomain, dp->index); +- +- if (virq) +- irq_dispose_mapping(virq); +- } +- +- irq_domain_remove(priv->irqdomain); +- priv->irqdomain = NULL; +- +-out_put_node: +- mb->irq = 0; +- of_node_put(intc); +- +- return ret; +-} +- +-static void rtl8365mb_irq_teardown(struct realtek_priv *priv) +-{ +- struct rtl8365mb *mb = priv->chip_data; +- struct dsa_switch *ds = &priv->ds; +- struct dsa_port *dp; +- int virq; +- +- if (mb->irq) { +- free_irq(mb->irq, priv); +- mb->irq = 0; +- } +- +- if (priv->irqdomain) { +- /* Unused ports with a linked PHY still have an active IRQ +- * mapping that must be disposed of during teardown. Loop +- * through all ports. +- */ +- dsa_switch_for_each_port(dp, ds) { +- virq = irq_find_mapping(priv->irqdomain, dp->index); +- +- if (virq) +- irq_dispose_mapping(virq); +- } +- +- irq_domain_remove(priv->irqdomain); +- priv->irqdomain = NULL; +- } +-} +- +-static int rtl8365mb_cpu_config(struct realtek_priv *priv) +-{ +- struct rtl8365mb *mb = priv->chip_data; +- struct rtl8365mb_cpu *cpu = &mb->cpu; +- u32 val; +- int ret; +- +- ret = regmap_update_bits(priv->map, RTL8365MB_CPU_PORT_MASK_REG, +- RTL8365MB_CPU_PORT_MASK_MASK, +- FIELD_PREP(RTL8365MB_CPU_PORT_MASK_MASK, +- cpu->mask)); +- if (ret) +- return ret; +- +- val = FIELD_PREP(RTL8365MB_CPU_CTRL_EN_MASK, cpu->enable ? 1 : 0) | +- FIELD_PREP(RTL8365MB_CPU_CTRL_INSERTMODE_MASK, cpu->insert) | +- FIELD_PREP(RTL8365MB_CPU_CTRL_TAG_POSITION_MASK, cpu->position) | +- FIELD_PREP(RTL8365MB_CPU_CTRL_RXBYTECOUNT_MASK, cpu->rx_length) | +- FIELD_PREP(RTL8365MB_CPU_CTRL_TAG_FORMAT_MASK, cpu->format) | +- FIELD_PREP(RTL8365MB_CPU_CTRL_TRAP_PORT_MASK, cpu->trap_port & 0x7) | +- FIELD_PREP(RTL8365MB_CPU_CTRL_TRAP_PORT_EXT_MASK, +- cpu->trap_port >> 3 & 0x1); +- ret = regmap_write(priv->map, RTL8365MB_CPU_CTRL_REG, val); +- if (ret) +- return ret; +- +- return 0; +-} +- +-static int rtl8365mb_change_tag_protocol(struct dsa_switch *ds, +- enum dsa_tag_protocol proto) +-{ +- struct realtek_priv *priv = ds->priv; +- struct rtl8365mb_cpu *cpu; +- struct rtl8365mb *mb; +- +- mb = priv->chip_data; +- cpu = &mb->cpu; +- +- switch (proto) { +- case DSA_TAG_PROTO_RTL8_4: +- cpu->format = RTL8365MB_CPU_FORMAT_8BYTES; +- cpu->position = RTL8365MB_CPU_POS_AFTER_SA; +- break; +- case DSA_TAG_PROTO_RTL8_4T: +- cpu->format = RTL8365MB_CPU_FORMAT_8BYTES; +- cpu->position = RTL8365MB_CPU_POS_BEFORE_CRC; +- break; +- /* The switch also supports a 4-byte format, similar to rtl4a but with +- * the same 0x04 8-bit version and probably 8-bit port source/dest. +- * There is no public doc about it. Not supported yet and it will probably +- * never be. +- */ +- default: +- return -EPROTONOSUPPORT; +- } +- +- return rtl8365mb_cpu_config(priv); +-} +- +-static int rtl8365mb_switch_init(struct realtek_priv *priv) +-{ +- struct rtl8365mb *mb = priv->chip_data; +- const struct rtl8365mb_chip_info *ci; +- int ret; +- int i; +- +- ci = mb->chip_info; +- +- /* Do any chip-specific init jam before getting to the common stuff */ +- if (ci->jam_table) { +- for (i = 0; i < ci->jam_size; i++) { +- ret = regmap_write(priv->map, ci->jam_table[i].reg, +- ci->jam_table[i].val); +- if (ret) +- return ret; +- } +- } +- +- /* Common init jam */ +- for (i = 0; i < ARRAY_SIZE(rtl8365mb_init_jam_common); i++) { +- ret = regmap_write(priv->map, rtl8365mb_init_jam_common[i].reg, +- rtl8365mb_init_jam_common[i].val); +- if (ret) +- return ret; +- } +- +- return 0; +-} +- +-static int rtl8365mb_reset_chip(struct realtek_priv *priv) +-{ +- u32 val; +- +- priv->write_reg_noack(priv, RTL8365MB_CHIP_RESET_REG, +- FIELD_PREP(RTL8365MB_CHIP_RESET_HW_MASK, 1)); +- +- /* Realtek documentation says the chip needs 1 second to reset. Sleep +- * for 100 ms before accessing any registers to prevent ACK timeouts. +- */ +- msleep(100); +- return regmap_read_poll_timeout(priv->map, RTL8365MB_CHIP_RESET_REG, val, +- !(val & RTL8365MB_CHIP_RESET_HW_MASK), +- 20000, 1e6); +-} +- +-static int rtl8365mb_setup(struct dsa_switch *ds) +-{ +- struct realtek_priv *priv = ds->priv; +- struct rtl8365mb_cpu *cpu; +- u32 downports_mask = 0; +- u32 upports_mask = 0; +- struct rtl8365mb *mb; +- struct dsa_port *dp; +- int ret; +- +- mb = priv->chip_data; +- cpu = &mb->cpu; +- +- ret = rtl8365mb_reset_chip(priv); +- if (ret) { +- dev_err(priv->dev, "failed to reset chip: %pe\n", +- ERR_PTR(ret)); +- goto out_error; +- } +- +- /* Configure switch to vendor-defined initial state */ +- ret = rtl8365mb_switch_init(priv); +- if (ret) { +- dev_err(priv->dev, "failed to initialize switch: %pe\n", +- ERR_PTR(ret)); +- goto out_error; +- } +- +- /* Set up cascading IRQs */ +- ret = rtl8365mb_irq_setup(priv); +- if (ret == -EPROBE_DEFER) +- return ret; +- else if (ret) +- dev_info(priv->dev, "no interrupt support\n"); +- +- dsa_switch_for_each_port(dp, ds) { +- /* Cascading (DSA links) is not supported yet. +- * Historically, the driver has always been broken +- * without a dedicated CPU port because CPU tagging +- * would be disabled, rendering the switch entirely +- * non-functional for DSA operations. +- */ +- if (dsa_port_is_dsa(dp)) { +- dev_err(priv->dev, "Cascading (DSA link) not supported\n"); +- ret = -EOPNOTSUPP; +- goto out_teardown_irq; +- } +- } +- +- /* Start with all ports blocked, including unused ports */ +- dsa_switch_for_each_port(dp, ds) { +- struct rtl8365mb_port *p = &mb->ports[dp->index]; +- +- /* Set the initial STP state of all ports to DISABLED, otherwise +- * ports will still forward frames to the CPU despite being +- * administratively down by default. +- */ +- rtl8365mb_port_stp_state_set(ds, dp->index, BR_STATE_DISABLED); +- +- /* Start with all port completely isolated */ +- ret = rtl8365mb_port_set_isolation(priv, dp->index, 0); +- if (ret) +- goto out_teardown_irq; +- +- /* Disable learning */ +- ret = rtl8365mb_port_set_learning(priv, dp->index, false); +- if (ret) +- goto out_teardown_irq; +- +- /* Set up per-port private data */ +- p->priv = priv; +- p->index = dp->index; +- +- /* Collect CPU ports. If we support cascade switches, it should +- * also include the upstream DSA ports. +- */ +- if (!dsa_port_is_cpu(dp)) +- continue; +- +- upports_mask |= BIT(dp->index); +- } +- +- /* Configure user ports */ +- dsa_switch_for_each_port(dp, ds) { +- if (!dsa_port_is_user(dp)) +- continue; +- +- /* Forward only to the CPU */ +- ret = rtl8365mb_port_set_isolation(priv, dp->index, +- upports_mask); +- if (ret) +- goto out_teardown_irq; +- +- /* If we support cascade switches, it should also include the +- * downstream DSA ports. +- */ +- downports_mask |= BIT(dp->index); +- } +- +- /* Configure CPU tagging */ +- /* If we support cascade switches, it should also include the upstream +- * DSA ports. +- */ +- dsa_switch_for_each_cpu_port(dp, ds) { +- /* Use the first CPU port as trap_port */ +- if (cpu->trap_port == RTL8365MB_MAX_NUM_PORTS) +- cpu->trap_port = dp->index; +- +- /* Forward to all user ports */ +- ret = rtl8365mb_port_set_isolation(priv, dp->index, +- downports_mask); +- if (ret) +- goto out_teardown_irq; +- } +- +- cpu->mask = upports_mask; +- cpu->enable = cpu->mask > 0; +- +- if (!cpu->enable) { +- dev_err(priv->dev, "no CPU port defined\n"); +- ret = -EINVAL; +- goto out_teardown_irq; +- } +- +- ret = rtl8365mb_cpu_config(priv); +- if (ret) +- goto out_teardown_irq; +- +- ret = rtl8365mb_port_change_mtu(ds, cpu->trap_port, ETH_DATA_LEN); +- if (ret) +- goto out_teardown_irq; +- +- ret = rtl83xx_setup_user_mdio(ds); +- if (ret) { +- dev_err(priv->dev, "could not set up MDIO bus\n"); +- goto out_teardown_irq; +- } +- +- /* Start statistics counter polling */ +- rtl8365mb_stats_setup(priv); +- +- return 0; +- +-out_teardown_irq: +- rtl8365mb_irq_teardown(priv); +- +-out_error: +- return ret; +-} +- +-static void rtl8365mb_teardown(struct dsa_switch *ds) +-{ +- struct realtek_priv *priv = ds->priv; +- +- rtl8365mb_stats_teardown(priv); +- rtl8365mb_irq_teardown(priv); +-} +- +-static int rtl8365mb_get_chip_id_and_ver(struct regmap *map, u32 *id, u32 *ver) +-{ +- int ret; +- +- /* For some reason we have to write a magic value to an arbitrary +- * register whenever accessing the chip ID/version registers. +- */ +- ret = regmap_write(map, RTL8365MB_MAGIC_REG, RTL8365MB_MAGIC_VALUE); +- if (ret) +- return ret; +- +- ret = regmap_read(map, RTL8365MB_CHIP_ID_REG, id); +- if (ret) +- return ret; +- +- ret = regmap_read(map, RTL8365MB_CHIP_VER_REG, ver); +- if (ret) +- return ret; +- +- /* Reset magic register */ +- ret = regmap_write(map, RTL8365MB_MAGIC_REG, 0); +- if (ret) +- return ret; +- +- return 0; +-} +- +-static int rtl8365mb_detect(struct realtek_priv *priv) +-{ +- struct rtl8365mb *mb = priv->chip_data; +- u32 chip_id; +- u32 chip_ver; +- int ret; +- int i; +- +- ret = rtl8365mb_get_chip_id_and_ver(priv->map, &chip_id, &chip_ver); +- if (ret) { +- dev_err(priv->dev, "failed to read chip id and version: %pe\n", +- ERR_PTR(ret)); +- return ret; +- } +- +- for (i = 0; i < ARRAY_SIZE(rtl8365mb_chip_infos); i++) { +- const struct rtl8365mb_chip_info *ci = &rtl8365mb_chip_infos[i]; +- +- if (ci->chip_id == chip_id && ci->chip_ver == chip_ver) { +- mb->chip_info = ci; +- break; +- } +- } +- +- if (!mb->chip_info) { +- dev_err(priv->dev, +- "unrecognized switch (id=0x%04x, ver=0x%04x)", chip_id, +- chip_ver); +- return -ENODEV; +- } +- +- dev_info(priv->dev, "found an %s switch\n", mb->chip_info->name); +- +- priv->num_ports = RTL8365MB_MAX_NUM_PORTS; +- mb->priv = priv; +- mb->cpu.trap_port = RTL8365MB_MAX_NUM_PORTS; +- mb->cpu.insert = RTL8365MB_CPU_INSERT_TO_ALL; +- mb->cpu.position = RTL8365MB_CPU_POS_AFTER_SA; +- mb->cpu.rx_length = RTL8365MB_CPU_RXLEN_64BYTES; +- mb->cpu.format = RTL8365MB_CPU_FORMAT_8BYTES; +- +- return 0; +-} +- +-static const struct phylink_mac_ops rtl8365mb_phylink_mac_ops = { +- .mac_config = rtl8365mb_phylink_mac_config, +- .mac_link_down = rtl8365mb_phylink_mac_link_down, +- .mac_link_up = rtl8365mb_phylink_mac_link_up, +-}; +- +-static const struct dsa_switch_ops rtl8365mb_switch_ops = { +- .get_tag_protocol = rtl8365mb_get_tag_protocol, +- .change_tag_protocol = rtl8365mb_change_tag_protocol, +- .setup = rtl8365mb_setup, +- .teardown = rtl8365mb_teardown, +- .phylink_get_caps = rtl8365mb_phylink_get_caps, +- .port_stp_state_set = rtl8365mb_port_stp_state_set, +- .get_strings = rtl8365mb_get_strings, +- .get_ethtool_stats = rtl8365mb_get_ethtool_stats, +- .get_sset_count = rtl8365mb_get_sset_count, +- .get_eth_phy_stats = rtl8365mb_get_phy_stats, +- .get_eth_mac_stats = rtl8365mb_get_mac_stats, +- .get_eth_ctrl_stats = rtl8365mb_get_ctrl_stats, +- .get_stats64 = rtl8365mb_get_stats64, +- .port_change_mtu = rtl8365mb_port_change_mtu, +- .port_max_mtu = rtl8365mb_port_max_mtu, +-}; +- +-static const struct realtek_ops rtl8365mb_ops = { +- .detect = rtl8365mb_detect, +- .phy_read = rtl8365mb_phy_read, +- .phy_write = rtl8365mb_phy_write, +-}; +- +-const struct realtek_variant rtl8365mb_variant = { +- .ds_ops = &rtl8365mb_switch_ops, +- .ops = &rtl8365mb_ops, +- .phylink_mac_ops = &rtl8365mb_phylink_mac_ops, +- .clk_delay = 10, +- .cmd_read = 0xb9, +- .cmd_write = 0xb8, +- .chip_data_sz = sizeof(struct rtl8365mb), +-}; +- +-static const struct of_device_id rtl8365mb_of_match[] = { +- { .compatible = "realtek,rtl8365mb", .data = &rtl8365mb_variant, }, +- { /* sentinel */ }, +-}; +-MODULE_DEVICE_TABLE(of, rtl8365mb_of_match); +- +-static struct platform_driver rtl8365mb_smi_driver = { +- .driver = { +- .name = "rtl8365mb-smi", +- .of_match_table = rtl8365mb_of_match, +- }, +- .probe = realtek_smi_probe, +- .remove = realtek_smi_remove, +- .shutdown = realtek_smi_shutdown, +-}; +- +-static struct mdio_driver rtl8365mb_mdio_driver = { +- .mdiodrv.driver = { +- .name = "rtl8365mb-mdio", +- .of_match_table = rtl8365mb_of_match, +- }, +- .probe = realtek_mdio_probe, +- .remove = realtek_mdio_remove, +- .shutdown = realtek_mdio_shutdown, +-}; +- +-static int rtl8365mb_init(void) +-{ +- int ret; +- +- ret = realtek_mdio_driver_register(&rtl8365mb_mdio_driver); +- if (ret) +- return ret; +- +- ret = realtek_smi_driver_register(&rtl8365mb_smi_driver); +- if (ret) { +- realtek_mdio_driver_unregister(&rtl8365mb_mdio_driver); +- return ret; +- } +- +- return 0; +-} +-module_init(rtl8365mb_init); +- +-static void __exit rtl8365mb_exit(void) +-{ +- realtek_smi_driver_unregister(&rtl8365mb_smi_driver); +- realtek_mdio_driver_unregister(&rtl8365mb_mdio_driver); +-} +-module_exit(rtl8365mb_exit); +- +-MODULE_AUTHOR("Alvin Å ipraga "); +-MODULE_DESCRIPTION("Driver for RTL8365MB-VC ethernet switch"); +-MODULE_LICENSE("GPL"); +-MODULE_IMPORT_NS("REALTEK_DSA"); +--- /dev/null ++++ b/drivers/net/dsa/realtek/rtl8365mb_main.c +@@ -0,0 +1,2294 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* Realtek SMI subdriver for the Realtek RTL8365MB-VC ethernet switch. ++ * ++ * Copyright (C) 2021 Alvin Å ipraga ++ * Copyright (C) 2021 Michael Rasmussen ++ * ++ * The RTL8365MB-VC is a 4+1 port 10/100/1000M switch controller. It includes 4 ++ * integrated PHYs for the user facing ports, and an extension interface which ++ * can be connected to the CPU - or another PHY - via either MII, RMII, or ++ * RGMII. The switch is configured via the Realtek Simple Management Interface ++ * (SMI), which uses the MDIO/MDC lines. ++ * ++ * Below is a simplified block diagram of the chip and its relevant interfaces. ++ * ++ * .-----------------------------------. ++ * | | ++ * UTP <---------------> Giga PHY <-> PCS <-> P0 GMAC | ++ * UTP <---------------> Giga PHY <-> PCS <-> P1 GMAC | ++ * UTP <---------------> Giga PHY <-> PCS <-> P2 GMAC | ++ * UTP <---------------> Giga PHY <-> PCS <-> P3 GMAC | ++ * | | ++ * CPU/PHY <-MII/RMII/RGMII---> Extension <---> Extension | ++ * | interface 1 GMAC 1 | ++ * | | ++ * SMI driver/ <-MDC/SCL---> Management ~~~~~~~~~~~~~~ | ++ * EEPROM <-MDIO/SDA--> interface ~REALTEK ~~~~~ | ++ * | ~RTL8365MB ~~~ | ++ * | ~GXXXC TAIWAN~ | ++ * GPIO <--------------> Reset ~~~~~~~~~~~~~~ | ++ * | | ++ * Interrupt <----------> Link UP/DOWN events | ++ * controller | | ++ * '-----------------------------------' ++ * ++ * The driver uses DSA to integrate the 4 user and 1 extension ports into the ++ * kernel. Netdevices are created for the user ports, as are PHY devices for ++ * their integrated PHYs. The device tree firmware should also specify the link ++ * partner of the extension port - either via a fixed-link or other phy-handle. ++ * See the device tree bindings for more detailed information. Note that the ++ * driver has only been tested with a fixed-link, but in principle it should not ++ * matter. ++ * ++ * NOTE: Currently, only the RGMII interface is implemented in this driver. ++ * ++ * The interrupt line is asserted on link UP/DOWN events. The driver creates a ++ * custom irqchip to handle this interrupt and demultiplex the events by reading ++ * the status registers via SMI. Interrupts are then propagated to the relevant ++ * PHY device. ++ * ++ * The EEPROM contains initial register values which the chip will read over I2C ++ * upon hardware reset. It is also possible to omit the EEPROM. In both cases, ++ * the driver will manually reprogram some registers using jam tables to reach ++ * an initial state defined by the vendor driver. ++ * ++ * This Linux driver is written based on an OS-agnostic vendor driver from ++ * Realtek. The reference GPL-licensed sources can be found in the OpenWrt ++ * source tree under the name rtl8367c. The vendor driver claims to support a ++ * number of similar switch controllers from Realtek, but the only hardware we ++ * have is the RTL8365MB-VC. Moreover, there does not seem to be any chip under ++ * the name RTL8367C. Although one wishes that the 'C' stood for some kind of ++ * common hardware revision, there exist examples of chips with the suffix -VC ++ * which are explicitly not supported by the rtl8367c driver and which instead ++ * require the rtl8367d vendor driver. With all this uncertainty, the driver has ++ * been modestly named rtl8365mb. Future implementors may wish to rename things ++ * accordingly. ++ * ++ * In the same family of chips, some carry up to 8 user ports and up to 2 ++ * extension ports. Where possible this driver tries to make things generic, but ++ * more work must be done to support these configurations. According to ++ * documentation from Realtek, the family should include the following chips: ++ * ++ * - RTL8363NB ++ * - RTL8363NB-VB ++ * - RTL8363SC ++ * - RTL8363SC-VB ++ * - RTL8364NB ++ * - RTL8364NB-VB ++ * - RTL8365MB-VC ++ * - RTL8366SC ++ * - RTL8367RB-VB ++ * - RTL8367SB ++ * - RTL8367S ++ * - RTL8370MB ++ * - RTL8310SR ++ * ++ * Some of the register logic for these additional chips has been skipped over ++ * while implementing this driver. It is therefore not possible to assume that ++ * things will work out-of-the-box for other chips, and a careful review of the ++ * vendor driver may be needed to expand support. The RTL8365MB-VC seems to be ++ * one of the simpler chips. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "realtek.h" ++#include "realtek-smi.h" ++#include "realtek-mdio.h" ++#include "rtl83xx.h" ++ ++/* Family-specific data and limits */ ++#define RTL8365MB_PHYADDRMAX 7 ++#define RTL8365MB_NUM_PHYREGS 32 ++#define RTL8365MB_PHYREGMAX (RTL8365MB_NUM_PHYREGS - 1) ++#define RTL8365MB_MAX_NUM_PORTS 11 ++#define RTL8365MB_MAX_NUM_EXTINTS 3 ++#define RTL8365MB_LEARN_LIMIT_MAX 2112 ++ ++/* Chip identification registers */ ++#define RTL8365MB_CHIP_ID_REG 0x1300 ++ ++#define RTL8365MB_CHIP_VER_REG 0x1301 ++ ++#define RTL8365MB_MAGIC_REG 0x13C2 ++#define RTL8365MB_MAGIC_VALUE 0x0249 ++ ++/* Chip reset register */ ++#define RTL8365MB_CHIP_RESET_REG 0x1322 ++#define RTL8365MB_CHIP_RESET_SW_MASK 0x0002 ++#define RTL8365MB_CHIP_RESET_HW_MASK 0x0001 ++ ++/* Interrupt polarity register */ ++#define RTL8365MB_INTR_POLARITY_REG 0x1100 ++#define RTL8365MB_INTR_POLARITY_MASK 0x0001 ++#define RTL8365MB_INTR_POLARITY_HIGH 0 ++#define RTL8365MB_INTR_POLARITY_LOW 1 ++ ++/* Interrupt control/status register - enable/check specific interrupt types */ ++#define RTL8365MB_INTR_CTRL_REG 0x1101 ++#define RTL8365MB_INTR_STATUS_REG 0x1102 ++#define RTL8365MB_INTR_SLIENT_START_2_MASK 0x1000 ++#define RTL8365MB_INTR_SLIENT_START_MASK 0x0800 ++#define RTL8365MB_INTR_ACL_ACTION_MASK 0x0200 ++#define RTL8365MB_INTR_CABLE_DIAG_FIN_MASK 0x0100 ++#define RTL8365MB_INTR_INTERRUPT_8051_MASK 0x0080 ++#define RTL8365MB_INTR_LOOP_DETECTION_MASK 0x0040 ++#define RTL8365MB_INTR_GREEN_TIMER_MASK 0x0020 ++#define RTL8365MB_INTR_SPECIAL_CONGEST_MASK 0x0010 ++#define RTL8365MB_INTR_SPEED_CHANGE_MASK 0x0008 ++#define RTL8365MB_INTR_LEARN_OVER_MASK 0x0004 ++#define RTL8365MB_INTR_METER_EXCEEDED_MASK 0x0002 ++#define RTL8365MB_INTR_LINK_CHANGE_MASK 0x0001 ++#define RTL8365MB_INTR_ALL_MASK \ ++ (RTL8365MB_INTR_SLIENT_START_2_MASK | \ ++ RTL8365MB_INTR_SLIENT_START_MASK | \ ++ RTL8365MB_INTR_ACL_ACTION_MASK | \ ++ RTL8365MB_INTR_CABLE_DIAG_FIN_MASK | \ ++ RTL8365MB_INTR_INTERRUPT_8051_MASK | \ ++ RTL8365MB_INTR_LOOP_DETECTION_MASK | \ ++ RTL8365MB_INTR_GREEN_TIMER_MASK | \ ++ RTL8365MB_INTR_SPECIAL_CONGEST_MASK | \ ++ RTL8365MB_INTR_SPEED_CHANGE_MASK | \ ++ RTL8365MB_INTR_LEARN_OVER_MASK | \ ++ RTL8365MB_INTR_METER_EXCEEDED_MASK | \ ++ RTL8365MB_INTR_LINK_CHANGE_MASK) ++ ++/* Per-port interrupt type status registers */ ++#define RTL8365MB_PORT_LINKDOWN_IND_REG 0x1106 ++#define RTL8365MB_PORT_LINKDOWN_IND_MASK 0x07FF ++ ++#define RTL8365MB_PORT_LINKUP_IND_REG 0x1107 ++#define RTL8365MB_PORT_LINKUP_IND_MASK 0x07FF ++ ++/* PHY indirect access registers */ ++#define RTL8365MB_INDIRECT_ACCESS_CTRL_REG 0x1F00 ++#define RTL8365MB_INDIRECT_ACCESS_CTRL_RW_MASK 0x0002 ++#define RTL8365MB_INDIRECT_ACCESS_CTRL_RW_READ 0 ++#define RTL8365MB_INDIRECT_ACCESS_CTRL_RW_WRITE 1 ++#define RTL8365MB_INDIRECT_ACCESS_CTRL_CMD_MASK 0x0001 ++#define RTL8365MB_INDIRECT_ACCESS_CTRL_CMD_VALUE 1 ++#define RTL8365MB_INDIRECT_ACCESS_STATUS_REG 0x1F01 ++#define RTL8365MB_INDIRECT_ACCESS_ADDRESS_REG 0x1F02 ++#define RTL8365MB_INDIRECT_ACCESS_ADDRESS_OCPADR_5_1_MASK GENMASK(4, 0) ++#define RTL8365MB_INDIRECT_ACCESS_ADDRESS_PHYNUM_MASK GENMASK(7, 5) ++#define RTL8365MB_INDIRECT_ACCESS_ADDRESS_OCPADR_9_6_MASK GENMASK(11, 8) ++#define RTL8365MB_PHY_BASE 0x2000 ++#define RTL8365MB_INDIRECT_ACCESS_WRITE_DATA_REG 0x1F03 ++#define RTL8365MB_INDIRECT_ACCESS_READ_DATA_REG 0x1F04 ++ ++/* PHY OCP address prefix register */ ++#define RTL8365MB_GPHY_OCP_MSB_0_REG 0x1D15 ++#define RTL8365MB_GPHY_OCP_MSB_0_CFG_CPU_OCPADR_MASK 0x0FC0 ++#define RTL8365MB_PHY_OCP_ADDR_PREFIX_MASK 0xFC00 ++ ++/* The PHY OCP addresses of PHY registers 0~31 start here */ ++#define RTL8365MB_PHY_OCP_ADDR_PHYREG_BASE 0xA400 ++ ++/* External interface port mode values - used in DIGITAL_INTERFACE_SELECT */ ++#define RTL8365MB_EXT_PORT_MODE_DISABLE 0 ++#define RTL8365MB_EXT_PORT_MODE_RGMII 1 ++#define RTL8365MB_EXT_PORT_MODE_MII_MAC 2 ++#define RTL8365MB_EXT_PORT_MODE_MII_PHY 3 ++#define RTL8365MB_EXT_PORT_MODE_TMII_MAC 4 ++#define RTL8365MB_EXT_PORT_MODE_TMII_PHY 5 ++#define RTL8365MB_EXT_PORT_MODE_GMII 6 ++#define RTL8365MB_EXT_PORT_MODE_RMII_MAC 7 ++#define RTL8365MB_EXT_PORT_MODE_RMII_PHY 8 ++#define RTL8365MB_EXT_PORT_MODE_SGMII 9 ++#define RTL8365MB_EXT_PORT_MODE_HSGMII 10 ++#define RTL8365MB_EXT_PORT_MODE_1000X_100FX 11 ++#define RTL8365MB_EXT_PORT_MODE_1000X 12 ++#define RTL8365MB_EXT_PORT_MODE_100FX 13 ++ ++/* External interface mode configuration registers 0~1 */ ++#define RTL8365MB_DIGITAL_INTERFACE_SELECT_REG0 0x1305 /* EXT0,EXT1 */ ++#define RTL8365MB_DIGITAL_INTERFACE_SELECT_REG1 0x13C3 /* EXT2 */ ++#define RTL8365MB_DIGITAL_INTERFACE_SELECT_REG(_extint) \ ++ ((_extint) <= 1 ? RTL8365MB_DIGITAL_INTERFACE_SELECT_REG0 : \ ++ (_extint) == 2 ? RTL8365MB_DIGITAL_INTERFACE_SELECT_REG1 : \ ++ 0x0) ++#define RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_MASK(_extint) \ ++ (0xF << (((_extint) % 2) * 4)) ++#define RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_OFFSET(_extint) \ ++ (((_extint) % 2) * 4) ++ ++/* External interface RGMII TX/RX delay configuration registers 0~2 */ ++#define RTL8365MB_EXT_RGMXF_REG0 0x1306 /* EXT0 */ ++#define RTL8365MB_EXT_RGMXF_REG1 0x1307 /* EXT1 */ ++#define RTL8365MB_EXT_RGMXF_REG2 0x13C5 /* EXT2 */ ++#define RTL8365MB_EXT_RGMXF_REG(_extint) \ ++ ((_extint) == 0 ? RTL8365MB_EXT_RGMXF_REG0 : \ ++ (_extint) == 1 ? RTL8365MB_EXT_RGMXF_REG1 : \ ++ (_extint) == 2 ? RTL8365MB_EXT_RGMXF_REG2 : \ ++ 0x0) ++#define RTL8365MB_EXT_RGMXF_RXDELAY_MASK 0x0007 ++#define RTL8365MB_EXT_RGMXF_TXDELAY_MASK 0x0008 ++ ++/* External interface port speed values - used in DIGITAL_INTERFACE_FORCE */ ++#define RTL8365MB_PORT_SPEED_10M 0 ++#define RTL8365MB_PORT_SPEED_100M 1 ++#define RTL8365MB_PORT_SPEED_1000M 2 ++ ++/* External interface force configuration registers 0~2 */ ++#define RTL8365MB_DIGITAL_INTERFACE_FORCE_REG0 0x1310 /* EXT0 */ ++#define RTL8365MB_DIGITAL_INTERFACE_FORCE_REG1 0x1311 /* EXT1 */ ++#define RTL8365MB_DIGITAL_INTERFACE_FORCE_REG2 0x13C4 /* EXT2 */ ++#define RTL8365MB_DIGITAL_INTERFACE_FORCE_REG(_extint) \ ++ ((_extint) == 0 ? RTL8365MB_DIGITAL_INTERFACE_FORCE_REG0 : \ ++ (_extint) == 1 ? RTL8365MB_DIGITAL_INTERFACE_FORCE_REG1 : \ ++ (_extint) == 2 ? RTL8365MB_DIGITAL_INTERFACE_FORCE_REG2 : \ ++ 0x0) ++#define RTL8365MB_DIGITAL_INTERFACE_FORCE_EN_MASK 0x1000 ++#define RTL8365MB_DIGITAL_INTERFACE_FORCE_NWAY_MASK 0x0080 ++#define RTL8365MB_DIGITAL_INTERFACE_FORCE_TXPAUSE_MASK 0x0040 ++#define RTL8365MB_DIGITAL_INTERFACE_FORCE_RXPAUSE_MASK 0x0020 ++#define RTL8365MB_DIGITAL_INTERFACE_FORCE_LINK_MASK 0x0010 ++#define RTL8365MB_DIGITAL_INTERFACE_FORCE_DUPLEX_MASK 0x0004 ++#define RTL8365MB_DIGITAL_INTERFACE_FORCE_SPEED_MASK 0x0003 ++ ++/* CPU port mask register - controls which ports are treated as CPU ports */ ++#define RTL8365MB_CPU_PORT_MASK_REG 0x1219 ++#define RTL8365MB_CPU_PORT_MASK_MASK 0x07FF ++ ++/* CPU control register */ ++#define RTL8365MB_CPU_CTRL_REG 0x121A ++#define RTL8365MB_CPU_CTRL_TRAP_PORT_EXT_MASK 0x0400 ++#define RTL8365MB_CPU_CTRL_TAG_FORMAT_MASK 0x0200 ++#define RTL8365MB_CPU_CTRL_RXBYTECOUNT_MASK 0x0080 ++#define RTL8365MB_CPU_CTRL_TAG_POSITION_MASK 0x0040 ++#define RTL8365MB_CPU_CTRL_TRAP_PORT_MASK 0x0038 ++#define RTL8365MB_CPU_CTRL_INSERTMODE_MASK 0x0006 ++#define RTL8365MB_CPU_CTRL_EN_MASK 0x0001 ++ ++/* Maximum packet length register */ ++#define RTL8365MB_CFG0_MAX_LEN_REG 0x088C ++#define RTL8365MB_CFG0_MAX_LEN_MASK 0x3FFF ++#define RTL8365MB_CFG0_MAX_LEN_MAX 0x3FFF ++ ++/* Port learning limit registers */ ++#define RTL8365MB_LUT_PORT_LEARN_LIMIT_BASE 0x0A20 ++#define RTL8365MB_LUT_PORT_LEARN_LIMIT_REG(_physport) \ ++ (RTL8365MB_LUT_PORT_LEARN_LIMIT_BASE + (_physport)) ++ ++/* Port isolation (forwarding mask) registers */ ++#define RTL8365MB_PORT_ISOLATION_REG_BASE 0x08A2 ++#define RTL8365MB_PORT_ISOLATION_REG(_physport) \ ++ (RTL8365MB_PORT_ISOLATION_REG_BASE + (_physport)) ++#define RTL8365MB_PORT_ISOLATION_MASK 0x07FF ++ ++/* MSTP port state registers - indexed by tree instance */ ++#define RTL8365MB_MSTI_CTRL_BASE 0x0A00 ++#define RTL8365MB_MSTI_CTRL_REG(_msti, _physport) \ ++ (RTL8365MB_MSTI_CTRL_BASE + ((_msti) << 1) + ((_physport) >> 3)) ++#define RTL8365MB_MSTI_CTRL_PORT_STATE_OFFSET(_physport) ((_physport) << 1) ++#define RTL8365MB_MSTI_CTRL_PORT_STATE_MASK(_physport) \ ++ (0x3 << RTL8365MB_MSTI_CTRL_PORT_STATE_OFFSET((_physport))) ++ ++/* MIB counter value registers */ ++#define RTL8365MB_MIB_COUNTER_BASE 0x1000 ++#define RTL8365MB_MIB_COUNTER_REG(_x) (RTL8365MB_MIB_COUNTER_BASE + (_x)) ++ ++/* MIB counter address register */ ++#define RTL8365MB_MIB_ADDRESS_REG 0x1004 ++#define RTL8365MB_MIB_ADDRESS_PORT_OFFSET 0x007C ++#define RTL8365MB_MIB_ADDRESS(_p, _x) \ ++ (((RTL8365MB_MIB_ADDRESS_PORT_OFFSET) * (_p) + (_x)) >> 2) ++ ++#define RTL8365MB_MIB_CTRL0_REG 0x1005 ++#define RTL8365MB_MIB_CTRL0_RESET_MASK 0x0002 ++#define RTL8365MB_MIB_CTRL0_BUSY_MASK 0x0001 ++ ++/* The DSA callback .get_stats64 runs in atomic context, so we are not allowed ++ * to block. On the other hand, accessing MIB counters absolutely requires us to ++ * block. The solution is thus to schedule work which polls the MIB counters ++ * asynchronously and updates some private data, which the callback can then ++ * fetch atomically. Three seconds should be a good enough polling interval. ++ */ ++#define RTL8365MB_STATS_INTERVAL_JIFFIES (3 * HZ) ++ ++enum rtl8365mb_mib_counter_index { ++ RTL8365MB_MIB_ifInOctets, ++ RTL8365MB_MIB_dot3StatsFCSErrors, ++ RTL8365MB_MIB_dot3StatsSymbolErrors, ++ RTL8365MB_MIB_dot3InPauseFrames, ++ RTL8365MB_MIB_dot3ControlInUnknownOpcodes, ++ RTL8365MB_MIB_etherStatsFragments, ++ RTL8365MB_MIB_etherStatsJabbers, ++ RTL8365MB_MIB_ifInUcastPkts, ++ RTL8365MB_MIB_etherStatsDropEvents, ++ RTL8365MB_MIB_ifInMulticastPkts, ++ RTL8365MB_MIB_ifInBroadcastPkts, ++ RTL8365MB_MIB_inMldChecksumError, ++ RTL8365MB_MIB_inIgmpChecksumError, ++ RTL8365MB_MIB_inMldSpecificQuery, ++ RTL8365MB_MIB_inMldGeneralQuery, ++ RTL8365MB_MIB_inIgmpSpecificQuery, ++ RTL8365MB_MIB_inIgmpGeneralQuery, ++ RTL8365MB_MIB_inMldLeaves, ++ RTL8365MB_MIB_inIgmpLeaves, ++ RTL8365MB_MIB_etherStatsOctets, ++ RTL8365MB_MIB_etherStatsUnderSizePkts, ++ RTL8365MB_MIB_etherOversizeStats, ++ RTL8365MB_MIB_etherStatsPkts64Octets, ++ RTL8365MB_MIB_etherStatsPkts65to127Octets, ++ RTL8365MB_MIB_etherStatsPkts128to255Octets, ++ RTL8365MB_MIB_etherStatsPkts256to511Octets, ++ RTL8365MB_MIB_etherStatsPkts512to1023Octets, ++ RTL8365MB_MIB_etherStatsPkts1024to1518Octets, ++ RTL8365MB_MIB_ifOutOctets, ++ RTL8365MB_MIB_dot3StatsSingleCollisionFrames, ++ RTL8365MB_MIB_dot3StatsMultipleCollisionFrames, ++ RTL8365MB_MIB_dot3StatsDeferredTransmissions, ++ RTL8365MB_MIB_dot3StatsLateCollisions, ++ RTL8365MB_MIB_etherStatsCollisions, ++ RTL8365MB_MIB_dot3StatsExcessiveCollisions, ++ RTL8365MB_MIB_dot3OutPauseFrames, ++ RTL8365MB_MIB_ifOutDiscards, ++ RTL8365MB_MIB_dot1dTpPortInDiscards, ++ RTL8365MB_MIB_ifOutUcastPkts, ++ RTL8365MB_MIB_ifOutMulticastPkts, ++ RTL8365MB_MIB_ifOutBroadcastPkts, ++ RTL8365MB_MIB_outOampduPkts, ++ RTL8365MB_MIB_inOampduPkts, ++ RTL8365MB_MIB_inIgmpJoinsSuccess, ++ RTL8365MB_MIB_inIgmpJoinsFail, ++ RTL8365MB_MIB_inMldJoinsSuccess, ++ RTL8365MB_MIB_inMldJoinsFail, ++ RTL8365MB_MIB_inReportSuppressionDrop, ++ RTL8365MB_MIB_inLeaveSuppressionDrop, ++ RTL8365MB_MIB_outIgmpReports, ++ RTL8365MB_MIB_outIgmpLeaves, ++ RTL8365MB_MIB_outIgmpGeneralQuery, ++ RTL8365MB_MIB_outIgmpSpecificQuery, ++ RTL8365MB_MIB_outMldReports, ++ RTL8365MB_MIB_outMldLeaves, ++ RTL8365MB_MIB_outMldGeneralQuery, ++ RTL8365MB_MIB_outMldSpecificQuery, ++ RTL8365MB_MIB_inKnownMulticastPkts, ++ RTL8365MB_MIB_END, ++}; ++ ++struct rtl8365mb_mib_counter { ++ u32 offset; ++ u32 length; ++ const char *name; ++}; ++ ++#define RTL8365MB_MAKE_MIB_COUNTER(_offset, _length, _name) \ ++ [RTL8365MB_MIB_ ## _name] = { _offset, _length, #_name } ++ ++static struct rtl8365mb_mib_counter rtl8365mb_mib_counters[] = { ++ RTL8365MB_MAKE_MIB_COUNTER(0, 4, ifInOctets), ++ RTL8365MB_MAKE_MIB_COUNTER(4, 2, dot3StatsFCSErrors), ++ RTL8365MB_MAKE_MIB_COUNTER(6, 2, dot3StatsSymbolErrors), ++ RTL8365MB_MAKE_MIB_COUNTER(8, 2, dot3InPauseFrames), ++ RTL8365MB_MAKE_MIB_COUNTER(10, 2, dot3ControlInUnknownOpcodes), ++ RTL8365MB_MAKE_MIB_COUNTER(12, 2, etherStatsFragments), ++ RTL8365MB_MAKE_MIB_COUNTER(14, 2, etherStatsJabbers), ++ RTL8365MB_MAKE_MIB_COUNTER(16, 2, ifInUcastPkts), ++ RTL8365MB_MAKE_MIB_COUNTER(18, 2, etherStatsDropEvents), ++ RTL8365MB_MAKE_MIB_COUNTER(20, 2, ifInMulticastPkts), ++ RTL8365MB_MAKE_MIB_COUNTER(22, 2, ifInBroadcastPkts), ++ RTL8365MB_MAKE_MIB_COUNTER(24, 2, inMldChecksumError), ++ RTL8365MB_MAKE_MIB_COUNTER(26, 2, inIgmpChecksumError), ++ RTL8365MB_MAKE_MIB_COUNTER(28, 2, inMldSpecificQuery), ++ RTL8365MB_MAKE_MIB_COUNTER(30, 2, inMldGeneralQuery), ++ RTL8365MB_MAKE_MIB_COUNTER(32, 2, inIgmpSpecificQuery), ++ RTL8365MB_MAKE_MIB_COUNTER(34, 2, inIgmpGeneralQuery), ++ RTL8365MB_MAKE_MIB_COUNTER(36, 2, inMldLeaves), ++ RTL8365MB_MAKE_MIB_COUNTER(38, 2, inIgmpLeaves), ++ RTL8365MB_MAKE_MIB_COUNTER(40, 4, etherStatsOctets), ++ RTL8365MB_MAKE_MIB_COUNTER(44, 2, etherStatsUnderSizePkts), ++ RTL8365MB_MAKE_MIB_COUNTER(46, 2, etherOversizeStats), ++ RTL8365MB_MAKE_MIB_COUNTER(48, 2, etherStatsPkts64Octets), ++ RTL8365MB_MAKE_MIB_COUNTER(50, 2, etherStatsPkts65to127Octets), ++ RTL8365MB_MAKE_MIB_COUNTER(52, 2, etherStatsPkts128to255Octets), ++ RTL8365MB_MAKE_MIB_COUNTER(54, 2, etherStatsPkts256to511Octets), ++ RTL8365MB_MAKE_MIB_COUNTER(56, 2, etherStatsPkts512to1023Octets), ++ RTL8365MB_MAKE_MIB_COUNTER(58, 2, etherStatsPkts1024to1518Octets), ++ RTL8365MB_MAKE_MIB_COUNTER(60, 4, ifOutOctets), ++ RTL8365MB_MAKE_MIB_COUNTER(64, 2, dot3StatsSingleCollisionFrames), ++ RTL8365MB_MAKE_MIB_COUNTER(66, 2, dot3StatsMultipleCollisionFrames), ++ RTL8365MB_MAKE_MIB_COUNTER(68, 2, dot3StatsDeferredTransmissions), ++ RTL8365MB_MAKE_MIB_COUNTER(70, 2, dot3StatsLateCollisions), ++ RTL8365MB_MAKE_MIB_COUNTER(72, 2, etherStatsCollisions), ++ RTL8365MB_MAKE_MIB_COUNTER(74, 2, dot3StatsExcessiveCollisions), ++ RTL8365MB_MAKE_MIB_COUNTER(76, 2, dot3OutPauseFrames), ++ RTL8365MB_MAKE_MIB_COUNTER(78, 2, ifOutDiscards), ++ RTL8365MB_MAKE_MIB_COUNTER(80, 2, dot1dTpPortInDiscards), ++ RTL8365MB_MAKE_MIB_COUNTER(82, 2, ifOutUcastPkts), ++ RTL8365MB_MAKE_MIB_COUNTER(84, 2, ifOutMulticastPkts), ++ RTL8365MB_MAKE_MIB_COUNTER(86, 2, ifOutBroadcastPkts), ++ RTL8365MB_MAKE_MIB_COUNTER(88, 2, outOampduPkts), ++ RTL8365MB_MAKE_MIB_COUNTER(90, 2, inOampduPkts), ++ RTL8365MB_MAKE_MIB_COUNTER(92, 4, inIgmpJoinsSuccess), ++ RTL8365MB_MAKE_MIB_COUNTER(96, 2, inIgmpJoinsFail), ++ RTL8365MB_MAKE_MIB_COUNTER(98, 2, inMldJoinsSuccess), ++ RTL8365MB_MAKE_MIB_COUNTER(100, 2, inMldJoinsFail), ++ RTL8365MB_MAKE_MIB_COUNTER(102, 2, inReportSuppressionDrop), ++ RTL8365MB_MAKE_MIB_COUNTER(104, 2, inLeaveSuppressionDrop), ++ RTL8365MB_MAKE_MIB_COUNTER(106, 2, outIgmpReports), ++ RTL8365MB_MAKE_MIB_COUNTER(108, 2, outIgmpLeaves), ++ RTL8365MB_MAKE_MIB_COUNTER(110, 2, outIgmpGeneralQuery), ++ RTL8365MB_MAKE_MIB_COUNTER(112, 2, outIgmpSpecificQuery), ++ RTL8365MB_MAKE_MIB_COUNTER(114, 2, outMldReports), ++ RTL8365MB_MAKE_MIB_COUNTER(116, 2, outMldLeaves), ++ RTL8365MB_MAKE_MIB_COUNTER(118, 2, outMldGeneralQuery), ++ RTL8365MB_MAKE_MIB_COUNTER(120, 2, outMldSpecificQuery), ++ RTL8365MB_MAKE_MIB_COUNTER(122, 2, inKnownMulticastPkts), ++}; ++ ++static_assert(ARRAY_SIZE(rtl8365mb_mib_counters) == RTL8365MB_MIB_END); ++ ++struct rtl8365mb_jam_tbl_entry { ++ u16 reg; ++ u16 val; ++}; ++ ++/* Lifted from the vendor driver sources */ ++static const struct rtl8365mb_jam_tbl_entry rtl8365mb_init_jam_8365mb_vc[] = { ++ { 0x13EB, 0x15BB }, { 0x1303, 0x06D6 }, { 0x1304, 0x0700 }, ++ { 0x13E2, 0x003F }, { 0x13F9, 0x0090 }, { 0x121E, 0x03CA }, ++ { 0x1233, 0x0352 }, { 0x1237, 0x00A0 }, { 0x123A, 0x0030 }, ++ { 0x1239, 0x0084 }, { 0x0301, 0x1000 }, { 0x1349, 0x001F }, ++ { 0x18E0, 0x4004 }, { 0x122B, 0x241C }, { 0x1305, 0xC000 }, ++ { 0x13F0, 0x0000 }, ++}; ++ ++static const struct rtl8365mb_jam_tbl_entry rtl8365mb_init_jam_common[] = { ++ { 0x1200, 0x7FCB }, { 0x0884, 0x0003 }, { 0x06EB, 0x0001 }, ++ { 0x03Fa, 0x0007 }, { 0x08C8, 0x00C0 }, { 0x0A30, 0x020E }, ++ { 0x0800, 0x0000 }, { 0x0802, 0x0000 }, { 0x09DA, 0x0013 }, ++ { 0x1D32, 0x0002 }, ++}; ++ ++enum rtl8365mb_phy_interface_mode { ++ RTL8365MB_PHY_INTERFACE_MODE_INVAL = 0, ++ RTL8365MB_PHY_INTERFACE_MODE_INTERNAL = BIT(0), ++ RTL8365MB_PHY_INTERFACE_MODE_MII = BIT(1), ++ RTL8365MB_PHY_INTERFACE_MODE_TMII = BIT(2), ++ RTL8365MB_PHY_INTERFACE_MODE_RMII = BIT(3), ++ RTL8365MB_PHY_INTERFACE_MODE_RGMII = BIT(4), ++ RTL8365MB_PHY_INTERFACE_MODE_SGMII = BIT(5), ++ RTL8365MB_PHY_INTERFACE_MODE_HSGMII = BIT(6), ++}; ++ ++/** ++ * struct rtl8365mb_extint - external interface info ++ * @port: the port with an external interface ++ * @id: the external interface ID, which is either 0, 1, or 2 ++ * @supported_interfaces: a bitmask of supported PHY interface modes ++ * ++ * Represents a mapping: port -> { id, supported_interfaces }. To be embedded ++ * in &struct rtl8365mb_chip_info for every port with an external interface. ++ */ ++struct rtl8365mb_extint { ++ int port; ++ int id; ++ unsigned int supported_interfaces; ++}; ++ ++/** ++ * struct rtl8365mb_chip_info - static chip-specific info ++ * @name: human-readable chip name ++ * @chip_id: chip identifier ++ * @chip_ver: chip silicon revision ++ * @extints: available external interfaces ++ * @jam_table: chip-specific initialization jam table ++ * @jam_size: size of the chip's jam table ++ * ++ * These data are specific to a given chip in the family of switches supported ++ * by this driver. When adding support for another chip in the family, a new ++ * chip info should be added to the rtl8365mb_chip_infos array. ++ */ ++struct rtl8365mb_chip_info { ++ const char *name; ++ u32 chip_id; ++ u32 chip_ver; ++ const struct rtl8365mb_extint extints[RTL8365MB_MAX_NUM_EXTINTS]; ++ const struct rtl8365mb_jam_tbl_entry *jam_table; ++ size_t jam_size; ++}; ++ ++/* Chip info for each supported switch in the family */ ++#define PHY_INTF(_mode) (RTL8365MB_PHY_INTERFACE_MODE_ ## _mode) ++static const struct rtl8365mb_chip_info rtl8365mb_chip_infos[] = { ++ { ++ .name = "RTL8365MB-VC", ++ .chip_id = 0x6367, ++ .chip_ver = 0x0040, ++ .extints = { ++ { 6, 1, PHY_INTF(MII) | PHY_INTF(TMII) | ++ PHY_INTF(RMII) | PHY_INTF(RGMII) }, ++ }, ++ .jam_table = rtl8365mb_init_jam_8365mb_vc, ++ .jam_size = ARRAY_SIZE(rtl8365mb_init_jam_8365mb_vc), ++ }, ++ { ++ .name = "RTL8367S", ++ .chip_id = 0x6367, ++ .chip_ver = 0x00A0, ++ .extints = { ++ { 6, 1, PHY_INTF(SGMII) | PHY_INTF(HSGMII) }, ++ { 7, 2, PHY_INTF(MII) | PHY_INTF(TMII) | ++ PHY_INTF(RMII) | PHY_INTF(RGMII) }, ++ }, ++ .jam_table = rtl8365mb_init_jam_8365mb_vc, ++ .jam_size = ARRAY_SIZE(rtl8365mb_init_jam_8365mb_vc), ++ }, ++ { ++ .name = "RTL8367SB", ++ .chip_id = 0x6367, ++ .chip_ver = 0x0010, ++ .extints = { ++ { 6, 1, PHY_INTF(MII) | PHY_INTF(TMII) | ++ PHY_INTF(RMII) | PHY_INTF(RGMII) | ++ PHY_INTF(SGMII) | PHY_INTF(HSGMII) }, ++ { 7, 2, PHY_INTF(MII) | PHY_INTF(TMII) | ++ PHY_INTF(RMII) | PHY_INTF(RGMII) }, ++ }, ++ .jam_table = rtl8365mb_init_jam_8365mb_vc, ++ .jam_size = ARRAY_SIZE(rtl8365mb_init_jam_8365mb_vc), ++ }, ++ { ++ .name = "RTL8367RB-VB", ++ .chip_id = 0x6367, ++ .chip_ver = 0x0020, ++ .extints = { ++ { 6, 1, PHY_INTF(MII) | PHY_INTF(TMII) | ++ PHY_INTF(RMII) | PHY_INTF(RGMII) }, ++ { 7, 2, PHY_INTF(MII) | PHY_INTF(TMII) | ++ PHY_INTF(RMII) | PHY_INTF(RGMII) }, ++ }, ++ .jam_table = rtl8365mb_init_jam_8365mb_vc, ++ .jam_size = ARRAY_SIZE(rtl8365mb_init_jam_8365mb_vc), ++ }, ++}; ++ ++enum rtl8365mb_stp_state { ++ RTL8365MB_STP_STATE_DISABLED = 0, ++ RTL8365MB_STP_STATE_BLOCKING = 1, ++ RTL8365MB_STP_STATE_LEARNING = 2, ++ RTL8365MB_STP_STATE_FORWARDING = 3, ++}; ++ ++enum rtl8365mb_cpu_insert { ++ RTL8365MB_CPU_INSERT_TO_ALL = 0, ++ RTL8365MB_CPU_INSERT_TO_TRAPPING = 1, ++ RTL8365MB_CPU_INSERT_TO_NONE = 2, ++}; ++ ++enum rtl8365mb_cpu_position { ++ RTL8365MB_CPU_POS_AFTER_SA = 0, ++ RTL8365MB_CPU_POS_BEFORE_CRC = 1, ++}; ++ ++enum rtl8365mb_cpu_format { ++ RTL8365MB_CPU_FORMAT_8BYTES = 0, ++ RTL8365MB_CPU_FORMAT_4BYTES = 1, ++}; ++ ++enum rtl8365mb_cpu_rxlen { ++ RTL8365MB_CPU_RXLEN_72BYTES = 0, ++ RTL8365MB_CPU_RXLEN_64BYTES = 1, ++}; ++ ++/** ++ * struct rtl8365mb_cpu - CPU port configuration ++ * @enable: enable/disable hardware insertion of CPU tag in switch->CPU frames ++ * @mask: port mask of ports that parse should parse CPU tags ++ * @trap_port: forward trapped frames to this port ++ * @insert: CPU tag insertion mode in switch->CPU frames ++ * @position: position of CPU tag in frame ++ * @rx_length: minimum CPU RX length ++ * @format: CPU tag format ++ * ++ * Represents the CPU tagging and CPU port configuration of the switch. These ++ * settings are configurable at runtime. ++ */ ++struct rtl8365mb_cpu { ++ bool enable; ++ u32 mask; ++ u32 trap_port; ++ enum rtl8365mb_cpu_insert insert; ++ enum rtl8365mb_cpu_position position; ++ enum rtl8365mb_cpu_rxlen rx_length; ++ enum rtl8365mb_cpu_format format; ++}; ++ ++/** ++ * struct rtl8365mb_port - private per-port data ++ * @priv: pointer to parent realtek_priv data ++ * @index: DSA port index, same as dsa_port::index ++ * @stats: link statistics populated by rtl8365mb_stats_poll, ready for atomic ++ * access via rtl8365mb_get_stats64 ++ * @stats_lock: protect the stats structure during read/update ++ * @mib_work: delayed work for polling MIB counters ++ */ ++struct rtl8365mb_port { ++ struct realtek_priv *priv; ++ unsigned int index; ++ struct rtnl_link_stats64 stats; ++ spinlock_t stats_lock; ++ struct delayed_work mib_work; ++}; ++ ++/** ++ * struct rtl8365mb - driver private data ++ * @priv: pointer to parent realtek_priv data ++ * @irq: registered IRQ or zero ++ * @chip_info: chip-specific info about the attached switch ++ * @cpu: CPU tagging and CPU port configuration for this chip ++ * @mib_lock: prevent concurrent reads of MIB counters ++ * @ports: per-port data ++ * ++ * Private data for this driver. ++ */ ++struct rtl8365mb { ++ struct realtek_priv *priv; ++ int irq; ++ const struct rtl8365mb_chip_info *chip_info; ++ struct rtl8365mb_cpu cpu; ++ struct mutex mib_lock; ++ struct rtl8365mb_port ports[RTL8365MB_MAX_NUM_PORTS]; ++}; ++ ++static int rtl8365mb_phy_poll_busy(struct realtek_priv *priv) ++{ ++ u32 val; ++ ++ return regmap_read_poll_timeout(priv->map_nolock, ++ RTL8365MB_INDIRECT_ACCESS_STATUS_REG, ++ val, !val, 10, 100); ++} ++ ++static int rtl8365mb_phy_ocp_prepare(struct realtek_priv *priv, int phy, ++ u32 ocp_addr) ++{ ++ u32 val; ++ int ret; ++ ++ /* Set OCP prefix */ ++ val = FIELD_GET(RTL8365MB_PHY_OCP_ADDR_PREFIX_MASK, ocp_addr); ++ ret = regmap_update_bits( ++ priv->map_nolock, RTL8365MB_GPHY_OCP_MSB_0_REG, ++ RTL8365MB_GPHY_OCP_MSB_0_CFG_CPU_OCPADR_MASK, ++ FIELD_PREP(RTL8365MB_GPHY_OCP_MSB_0_CFG_CPU_OCPADR_MASK, val)); ++ if (ret) ++ return ret; ++ ++ /* Set PHY register address */ ++ val = RTL8365MB_PHY_BASE; ++ val |= FIELD_PREP(RTL8365MB_INDIRECT_ACCESS_ADDRESS_PHYNUM_MASK, phy); ++ val |= FIELD_PREP(RTL8365MB_INDIRECT_ACCESS_ADDRESS_OCPADR_5_1_MASK, ++ ocp_addr >> 1); ++ val |= FIELD_PREP(RTL8365MB_INDIRECT_ACCESS_ADDRESS_OCPADR_9_6_MASK, ++ ocp_addr >> 6); ++ ret = regmap_write(priv->map_nolock, ++ RTL8365MB_INDIRECT_ACCESS_ADDRESS_REG, val); ++ if (ret) ++ return ret; ++ ++ return 0; ++} ++ ++static int rtl8365mb_phy_ocp_read(struct realtek_priv *priv, int phy, ++ u32 ocp_addr, u16 *data) ++{ ++ u32 val; ++ int ret; ++ ++ rtl83xx_lock(priv); ++ ++ ret = rtl8365mb_phy_poll_busy(priv); ++ if (ret) ++ goto out; ++ ++ ret = rtl8365mb_phy_ocp_prepare(priv, phy, ocp_addr); ++ if (ret) ++ goto out; ++ ++ /* Execute read operation */ ++ val = FIELD_PREP(RTL8365MB_INDIRECT_ACCESS_CTRL_CMD_MASK, ++ RTL8365MB_INDIRECT_ACCESS_CTRL_CMD_VALUE) | ++ FIELD_PREP(RTL8365MB_INDIRECT_ACCESS_CTRL_RW_MASK, ++ RTL8365MB_INDIRECT_ACCESS_CTRL_RW_READ); ++ ret = regmap_write(priv->map_nolock, RTL8365MB_INDIRECT_ACCESS_CTRL_REG, ++ val); ++ if (ret) ++ goto out; ++ ++ ret = rtl8365mb_phy_poll_busy(priv); ++ if (ret) ++ goto out; ++ ++ /* Get PHY register data */ ++ ret = regmap_read(priv->map_nolock, ++ RTL8365MB_INDIRECT_ACCESS_READ_DATA_REG, &val); ++ if (ret) ++ goto out; ++ ++ *data = val & 0xFFFF; ++ ++out: ++ rtl83xx_unlock(priv); ++ ++ return ret; ++} ++ ++static int rtl8365mb_phy_ocp_write(struct realtek_priv *priv, int phy, ++ u32 ocp_addr, u16 data) ++{ ++ u32 val; ++ int ret; ++ ++ rtl83xx_lock(priv); ++ ++ ret = rtl8365mb_phy_poll_busy(priv); ++ if (ret) ++ goto out; ++ ++ ret = rtl8365mb_phy_ocp_prepare(priv, phy, ocp_addr); ++ if (ret) ++ goto out; ++ ++ /* Set PHY register data */ ++ ret = regmap_write(priv->map_nolock, ++ RTL8365MB_INDIRECT_ACCESS_WRITE_DATA_REG, data); ++ if (ret) ++ goto out; ++ ++ /* Execute write operation */ ++ val = FIELD_PREP(RTL8365MB_INDIRECT_ACCESS_CTRL_CMD_MASK, ++ RTL8365MB_INDIRECT_ACCESS_CTRL_CMD_VALUE) | ++ FIELD_PREP(RTL8365MB_INDIRECT_ACCESS_CTRL_RW_MASK, ++ RTL8365MB_INDIRECT_ACCESS_CTRL_RW_WRITE); ++ ret = regmap_write(priv->map_nolock, RTL8365MB_INDIRECT_ACCESS_CTRL_REG, ++ val); ++ if (ret) ++ goto out; ++ ++ ret = rtl8365mb_phy_poll_busy(priv); ++ if (ret) ++ goto out; ++ ++out: ++ rtl83xx_unlock(priv); ++ ++ return ret; ++} ++ ++static int rtl8365mb_phy_read(struct realtek_priv *priv, int phy, int regnum) ++{ ++ u32 ocp_addr; ++ u16 val; ++ int ret; ++ ++ if (phy > RTL8365MB_PHYADDRMAX) ++ return -EINVAL; ++ ++ if (regnum > RTL8365MB_PHYREGMAX) ++ return -EINVAL; ++ ++ ocp_addr = RTL8365MB_PHY_OCP_ADDR_PHYREG_BASE + regnum * 2; ++ ++ ret = rtl8365mb_phy_ocp_read(priv, phy, ocp_addr, &val); ++ if (ret) { ++ dev_err(priv->dev, ++ "failed to read PHY%d reg %02x @ %04x, ret %pe\n", phy, ++ regnum, ocp_addr, ERR_PTR(ret)); ++ return ret; ++ } ++ ++ dev_dbg(priv->dev, "read PHY%d register 0x%02x @ %04x, val <- %04x\n", ++ phy, regnum, ocp_addr, val); ++ ++ return val; ++} ++ ++static int rtl8365mb_phy_write(struct realtek_priv *priv, int phy, int regnum, ++ u16 val) ++{ ++ u32 ocp_addr; ++ int ret; ++ ++ if (phy > RTL8365MB_PHYADDRMAX) ++ return -EINVAL; ++ ++ if (regnum > RTL8365MB_PHYREGMAX) ++ return -EINVAL; ++ ++ ocp_addr = RTL8365MB_PHY_OCP_ADDR_PHYREG_BASE + regnum * 2; ++ ++ ret = rtl8365mb_phy_ocp_write(priv, phy, ocp_addr, val); ++ if (ret) { ++ dev_err(priv->dev, ++ "failed to write PHY%d reg %02x @ %04x, ret %pe\n", phy, ++ regnum, ocp_addr, ERR_PTR(ret)); ++ return ret; ++ } ++ ++ dev_dbg(priv->dev, "write PHY%d register 0x%02x @ %04x, val -> %04x\n", ++ phy, regnum, ocp_addr, val); ++ ++ return 0; ++} ++ ++static const struct rtl8365mb_extint * ++rtl8365mb_get_port_extint(struct realtek_priv *priv, int port) ++{ ++ struct rtl8365mb *mb = priv->chip_data; ++ int i; ++ ++ for (i = 0; i < RTL8365MB_MAX_NUM_EXTINTS; i++) { ++ const struct rtl8365mb_extint *extint = ++ &mb->chip_info->extints[i]; ++ ++ if (!extint->supported_interfaces) ++ continue; ++ ++ if (extint->port == port) ++ return extint; ++ } ++ ++ return NULL; ++} ++ ++static enum dsa_tag_protocol ++rtl8365mb_get_tag_protocol(struct dsa_switch *ds, int port, ++ enum dsa_tag_protocol mp) ++{ ++ struct realtek_priv *priv = ds->priv; ++ struct rtl8365mb_cpu *cpu; ++ struct rtl8365mb *mb; ++ ++ mb = priv->chip_data; ++ cpu = &mb->cpu; ++ ++ if (cpu->position == RTL8365MB_CPU_POS_BEFORE_CRC) ++ return DSA_TAG_PROTO_RTL8_4T; ++ ++ return DSA_TAG_PROTO_RTL8_4; ++} ++ ++static int rtl8365mb_ext_config_rgmii(struct realtek_priv *priv, int port, ++ phy_interface_t interface) ++{ ++ const struct rtl8365mb_extint *extint = ++ rtl8365mb_get_port_extint(priv, port); ++ struct dsa_switch *ds = &priv->ds; ++ struct device_node *dn; ++ struct dsa_port *dp; ++ int tx_delay = 0; ++ int rx_delay = 0; ++ u32 val; ++ int ret; ++ ++ if (!extint) ++ return -ENODEV; ++ ++ dp = dsa_to_port(ds, port); ++ dn = dp->dn; ++ ++ /* Set the RGMII TX/RX delay ++ * ++ * The Realtek vendor driver indicates the following possible ++ * configuration settings: ++ * ++ * TX delay: ++ * 0 = no delay, 1 = 2 ns delay ++ * RX delay: ++ * 0 = no delay, 7 = maximum delay ++ * Each step is approximately 0.3 ns, so the maximum delay is about ++ * 2.1 ns. ++ * ++ * The vendor driver also states that this must be configured *before* ++ * forcing the external interface into a particular mode, which is done ++ * in the rtl8365mb_phylink_mac_link_{up,down} functions. ++ * ++ * Only configure an RGMII TX (resp. RX) delay if the ++ * tx-internal-delay-ps (resp. rx-internal-delay-ps) OF property is ++ * specified. We ignore the detail of the RGMII interface mode ++ * (RGMII_{RXID, TXID, etc.}), as this is considered to be a PHY-only ++ * property. ++ */ ++ if (!of_property_read_u32(dn, "tx-internal-delay-ps", &val)) { ++ val = val / 1000; /* convert to ns */ ++ ++ if (val == 0 || val == 2) ++ tx_delay = val / 2; ++ else ++ dev_warn(priv->dev, ++ "RGMII TX delay must be 0 or 2 ns\n"); ++ } ++ ++ if (!of_property_read_u32(dn, "rx-internal-delay-ps", &val)) { ++ val = DIV_ROUND_CLOSEST(val, 300); /* convert to 0.3 ns step */ ++ ++ if (val <= 7) ++ rx_delay = val; ++ else ++ dev_warn(priv->dev, ++ "RGMII RX delay must be 0 to 2.1 ns\n"); ++ } ++ ++ ret = regmap_update_bits( ++ priv->map, RTL8365MB_EXT_RGMXF_REG(extint->id), ++ RTL8365MB_EXT_RGMXF_TXDELAY_MASK | ++ RTL8365MB_EXT_RGMXF_RXDELAY_MASK, ++ FIELD_PREP(RTL8365MB_EXT_RGMXF_TXDELAY_MASK, tx_delay) | ++ FIELD_PREP(RTL8365MB_EXT_RGMXF_RXDELAY_MASK, rx_delay)); ++ if (ret) ++ return ret; ++ ++ ret = regmap_update_bits( ++ priv->map, RTL8365MB_DIGITAL_INTERFACE_SELECT_REG(extint->id), ++ RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_MASK(extint->id), ++ RTL8365MB_EXT_PORT_MODE_RGMII ++ << RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_OFFSET( ++ extint->id)); ++ if (ret) ++ return ret; ++ ++ return 0; ++} ++ ++static int rtl8365mb_ext_config_forcemode(struct realtek_priv *priv, int port, ++ bool link, int speed, int duplex, ++ bool tx_pause, bool rx_pause) ++{ ++ const struct rtl8365mb_extint *extint = ++ rtl8365mb_get_port_extint(priv, port); ++ u32 r_tx_pause; ++ u32 r_rx_pause; ++ u32 r_duplex; ++ u32 r_speed; ++ u32 r_link; ++ int val; ++ int ret; ++ ++ if (!extint) ++ return -ENODEV; ++ ++ if (link) { ++ /* Force the link up with the desired configuration */ ++ r_link = 1; ++ r_rx_pause = rx_pause ? 1 : 0; ++ r_tx_pause = tx_pause ? 1 : 0; ++ ++ if (speed == SPEED_1000) { ++ r_speed = RTL8365MB_PORT_SPEED_1000M; ++ } else if (speed == SPEED_100) { ++ r_speed = RTL8365MB_PORT_SPEED_100M; ++ } else if (speed == SPEED_10) { ++ r_speed = RTL8365MB_PORT_SPEED_10M; ++ } else { ++ dev_err(priv->dev, "unsupported port speed %s\n", ++ phy_speed_to_str(speed)); ++ return -EINVAL; ++ } ++ ++ if (duplex == DUPLEX_FULL) { ++ r_duplex = 1; ++ } else if (duplex == DUPLEX_HALF) { ++ r_duplex = 0; ++ } else { ++ dev_err(priv->dev, "unsupported duplex %s\n", ++ phy_duplex_to_str(duplex)); ++ return -EINVAL; ++ } ++ } else { ++ /* Force the link down and reset any programmed configuration */ ++ r_link = 0; ++ r_tx_pause = 0; ++ r_rx_pause = 0; ++ r_speed = 0; ++ r_duplex = 0; ++ } ++ ++ val = FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_EN_MASK, 1) | ++ FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_TXPAUSE_MASK, ++ r_tx_pause) | ++ FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_RXPAUSE_MASK, ++ r_rx_pause) | ++ FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_LINK_MASK, r_link) | ++ FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_DUPLEX_MASK, ++ r_duplex) | ++ FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_SPEED_MASK, r_speed); ++ ret = regmap_write(priv->map, ++ RTL8365MB_DIGITAL_INTERFACE_FORCE_REG(extint->id), ++ val); ++ if (ret) ++ return ret; ++ ++ return 0; ++} ++ ++static void rtl8365mb_phylink_get_caps(struct dsa_switch *ds, int port, ++ struct phylink_config *config) ++{ ++ const struct rtl8365mb_extint *extint = ++ rtl8365mb_get_port_extint(ds->priv, port); ++ ++ config->mac_capabilities = MAC_SYM_PAUSE | MAC_ASYM_PAUSE | ++ MAC_10 | MAC_100 | MAC_1000FD; ++ ++ if (!extint) { ++ __set_bit(PHY_INTERFACE_MODE_INTERNAL, ++ config->supported_interfaces); ++ ++ /* GMII is the default interface mode for phylib, so ++ * we have to support it for ports with integrated PHY. ++ */ ++ __set_bit(PHY_INTERFACE_MODE_GMII, ++ config->supported_interfaces); ++ return; ++ } ++ ++ /* Populate according to the modes supported by _this driver_, ++ * not necessarily the modes supported by the hardware, some of ++ * which remain unimplemented. ++ */ ++ ++ if (extint->supported_interfaces & RTL8365MB_PHY_INTERFACE_MODE_RGMII) ++ phy_interface_set_rgmii(config->supported_interfaces); ++} ++ ++static void rtl8365mb_phylink_mac_config(struct phylink_config *config, ++ unsigned int mode, ++ const struct phylink_link_state *state) ++{ ++ struct dsa_port *dp = dsa_phylink_to_port(config); ++ struct realtek_priv *priv = dp->ds->priv; ++ u8 port = dp->index; ++ int ret; ++ ++ if (mode != MLO_AN_PHY && mode != MLO_AN_FIXED) { ++ dev_err(priv->dev, ++ "port %d supports only conventional PHY or fixed-link\n", ++ port); ++ return; ++ } ++ ++ if (phy_interface_mode_is_rgmii(state->interface)) { ++ ret = rtl8365mb_ext_config_rgmii(priv, port, state->interface); ++ if (ret) ++ dev_err(priv->dev, ++ "failed to configure RGMII mode on port %d: %pe\n", ++ port, ERR_PTR(ret)); ++ return; ++ } ++ ++ /* TODO: Implement MII and RMII modes, which the RTL8365MB-VC also ++ * supports ++ */ ++} ++ ++static void rtl8365mb_phylink_mac_link_down(struct phylink_config *config, ++ unsigned int mode, ++ phy_interface_t interface) ++{ ++ struct dsa_port *dp = dsa_phylink_to_port(config); ++ struct realtek_priv *priv = dp->ds->priv; ++ struct rtl8365mb_port *p; ++ struct rtl8365mb *mb; ++ u8 port = dp->index; ++ int ret; ++ ++ mb = priv->chip_data; ++ p = &mb->ports[port]; ++ cancel_delayed_work_sync(&p->mib_work); ++ ++ if (phy_interface_mode_is_rgmii(interface)) { ++ ret = rtl8365mb_ext_config_forcemode(priv, port, false, 0, 0, ++ false, false); ++ if (ret) ++ dev_err(priv->dev, ++ "failed to reset forced mode on port %d: %pe\n", ++ port, ERR_PTR(ret)); ++ ++ return; ++ } ++} ++ ++static void rtl8365mb_phylink_mac_link_up(struct phylink_config *config, ++ struct phy_device *phydev, ++ unsigned int mode, ++ phy_interface_t interface, ++ int speed, int duplex, bool tx_pause, ++ bool rx_pause) ++{ ++ struct dsa_port *dp = dsa_phylink_to_port(config); ++ struct realtek_priv *priv = dp->ds->priv; ++ struct rtl8365mb_port *p; ++ struct rtl8365mb *mb; ++ u8 port = dp->index; ++ int ret; ++ ++ mb = priv->chip_data; ++ p = &mb->ports[port]; ++ schedule_delayed_work(&p->mib_work, 0); ++ ++ if (phy_interface_mode_is_rgmii(interface)) { ++ ret = rtl8365mb_ext_config_forcemode(priv, port, true, speed, ++ duplex, tx_pause, ++ rx_pause); ++ if (ret) ++ dev_err(priv->dev, ++ "failed to force mode on port %d: %pe\n", port, ++ ERR_PTR(ret)); ++ ++ return; ++ } ++} ++ ++static int rtl8365mb_port_change_mtu(struct dsa_switch *ds, int port, ++ int new_mtu) ++{ ++ struct realtek_priv *priv = ds->priv; ++ int frame_size; ++ ++ /* When a new MTU is set, DSA always sets the CPU port's MTU to the ++ * largest MTU of the user ports. Because the switch only has a global ++ * RX length register, only allowing CPU port here is enough. ++ */ ++ if (!dsa_is_cpu_port(ds, port)) ++ return 0; ++ ++ frame_size = new_mtu + VLAN_ETH_HLEN + ETH_FCS_LEN; ++ ++ dev_dbg(priv->dev, "changing mtu to %d (frame size: %d)\n", ++ new_mtu, frame_size); ++ ++ return regmap_update_bits(priv->map, RTL8365MB_CFG0_MAX_LEN_REG, ++ RTL8365MB_CFG0_MAX_LEN_MASK, ++ FIELD_PREP(RTL8365MB_CFG0_MAX_LEN_MASK, ++ frame_size)); ++} ++ ++static int rtl8365mb_port_max_mtu(struct dsa_switch *ds, int port) ++{ ++ return RTL8365MB_CFG0_MAX_LEN_MAX - VLAN_ETH_HLEN - ETH_FCS_LEN; ++} ++ ++static void rtl8365mb_port_stp_state_set(struct dsa_switch *ds, int port, ++ u8 state) ++{ ++ struct realtek_priv *priv = ds->priv; ++ enum rtl8365mb_stp_state val; ++ int msti = 0; ++ ++ switch (state) { ++ case BR_STATE_DISABLED: ++ val = RTL8365MB_STP_STATE_DISABLED; ++ break; ++ case BR_STATE_BLOCKING: ++ case BR_STATE_LISTENING: ++ val = RTL8365MB_STP_STATE_BLOCKING; ++ break; ++ case BR_STATE_LEARNING: ++ val = RTL8365MB_STP_STATE_LEARNING; ++ break; ++ case BR_STATE_FORWARDING: ++ val = RTL8365MB_STP_STATE_FORWARDING; ++ break; ++ default: ++ dev_err(priv->dev, "invalid STP state: %u\n", state); ++ return; ++ } ++ ++ regmap_update_bits(priv->map, RTL8365MB_MSTI_CTRL_REG(msti, port), ++ RTL8365MB_MSTI_CTRL_PORT_STATE_MASK(port), ++ val << RTL8365MB_MSTI_CTRL_PORT_STATE_OFFSET(port)); ++} ++ ++static int rtl8365mb_port_set_learning(struct realtek_priv *priv, int port, ++ bool enable) ++{ ++ /* Enable/disable learning by limiting the number of L2 addresses the ++ * port can learn. Realtek documentation states that a limit of zero ++ * disables learning. When enabling learning, set it to the chip's ++ * maximum. ++ */ ++ return regmap_write(priv->map, RTL8365MB_LUT_PORT_LEARN_LIMIT_REG(port), ++ enable ? RTL8365MB_LEARN_LIMIT_MAX : 0); ++} ++ ++static int rtl8365mb_port_set_isolation(struct realtek_priv *priv, int port, ++ u32 mask) ++{ ++ return regmap_write(priv->map, RTL8365MB_PORT_ISOLATION_REG(port), mask); ++} ++ ++static int rtl8365mb_mib_counter_read(struct realtek_priv *priv, int port, ++ u32 offset, u32 length, u64 *mibvalue) ++{ ++ u64 tmpvalue = 0; ++ u32 val; ++ int ret; ++ int i; ++ ++ /* The MIB address is an SRAM address. We request a particular address ++ * and then poll the control register before reading the value from some ++ * counter registers. ++ */ ++ ret = regmap_write(priv->map, RTL8365MB_MIB_ADDRESS_REG, ++ RTL8365MB_MIB_ADDRESS(port, offset)); ++ if (ret) ++ return ret; ++ ++ /* Poll for completion */ ++ ret = regmap_read_poll_timeout(priv->map, RTL8365MB_MIB_CTRL0_REG, val, ++ !(val & RTL8365MB_MIB_CTRL0_BUSY_MASK), ++ 10, 100); ++ if (ret) ++ return ret; ++ ++ /* Presumably this indicates a MIB counter read failure */ ++ if (val & RTL8365MB_MIB_CTRL0_RESET_MASK) ++ return -EIO; ++ ++ /* There are four MIB counter registers each holding a 16 bit word of a ++ * MIB counter. Depending on the offset, we should read from the upper ++ * two or lower two registers. In case the MIB counter is 4 words, we ++ * read from all four registers. ++ */ ++ if (length == 4) ++ offset = 3; ++ else ++ offset = (offset + 1) % 4; ++ ++ /* Read the MIB counter 16 bits at a time */ ++ for (i = 0; i < length; i++) { ++ ret = regmap_read(priv->map, ++ RTL8365MB_MIB_COUNTER_REG(offset - i), &val); ++ if (ret) ++ return ret; ++ ++ tmpvalue = ((tmpvalue) << 16) | (val & 0xFFFF); ++ } ++ ++ /* Only commit the result if no error occurred */ ++ *mibvalue = tmpvalue; ++ ++ return 0; ++} ++ ++static void rtl8365mb_get_ethtool_stats(struct dsa_switch *ds, int port, u64 *data) ++{ ++ struct realtek_priv *priv = ds->priv; ++ struct rtl8365mb *mb; ++ int ret; ++ int i; ++ ++ mb = priv->chip_data; ++ ++ mutex_lock(&mb->mib_lock); ++ for (i = 0; i < RTL8365MB_MIB_END; i++) { ++ struct rtl8365mb_mib_counter *mib = &rtl8365mb_mib_counters[i]; ++ ++ ret = rtl8365mb_mib_counter_read(priv, port, mib->offset, ++ mib->length, &data[i]); ++ if (ret) { ++ dev_err(priv->dev, ++ "failed to read port %d counters: %pe\n", port, ++ ERR_PTR(ret)); ++ break; ++ } ++ } ++ mutex_unlock(&mb->mib_lock); ++} ++ ++static void rtl8365mb_get_strings(struct dsa_switch *ds, int port, u32 stringset, u8 *data) ++{ ++ int i; ++ ++ if (stringset != ETH_SS_STATS) ++ return; ++ ++ for (i = 0; i < RTL8365MB_MIB_END; i++) { ++ struct rtl8365mb_mib_counter *mib = &rtl8365mb_mib_counters[i]; ++ ethtool_puts(&data, mib->name); ++ } ++} ++ ++static int rtl8365mb_get_sset_count(struct dsa_switch *ds, int port, int sset) ++{ ++ if (sset != ETH_SS_STATS) ++ return -EOPNOTSUPP; ++ ++ return RTL8365MB_MIB_END; ++} ++ ++static void rtl8365mb_get_phy_stats(struct dsa_switch *ds, int port, ++ struct ethtool_eth_phy_stats *phy_stats) ++{ ++ struct realtek_priv *priv = ds->priv; ++ struct rtl8365mb_mib_counter *mib; ++ struct rtl8365mb *mb; ++ ++ mb = priv->chip_data; ++ mib = &rtl8365mb_mib_counters[RTL8365MB_MIB_dot3StatsSymbolErrors]; ++ ++ mutex_lock(&mb->mib_lock); ++ rtl8365mb_mib_counter_read(priv, port, mib->offset, mib->length, ++ &phy_stats->SymbolErrorDuringCarrier); ++ mutex_unlock(&mb->mib_lock); ++} ++ ++static void rtl8365mb_get_mac_stats(struct dsa_switch *ds, int port, ++ struct ethtool_eth_mac_stats *mac_stats) ++{ ++ u64 cnt[RTL8365MB_MIB_END] = { ++ [RTL8365MB_MIB_ifOutOctets] = 1, ++ [RTL8365MB_MIB_ifOutUcastPkts] = 1, ++ [RTL8365MB_MIB_ifOutMulticastPkts] = 1, ++ [RTL8365MB_MIB_ifOutBroadcastPkts] = 1, ++ [RTL8365MB_MIB_dot3OutPauseFrames] = 1, ++ [RTL8365MB_MIB_ifOutDiscards] = 1, ++ [RTL8365MB_MIB_ifInOctets] = 1, ++ [RTL8365MB_MIB_ifInUcastPkts] = 1, ++ [RTL8365MB_MIB_ifInMulticastPkts] = 1, ++ [RTL8365MB_MIB_ifInBroadcastPkts] = 1, ++ [RTL8365MB_MIB_dot3InPauseFrames] = 1, ++ [RTL8365MB_MIB_dot3StatsSingleCollisionFrames] = 1, ++ [RTL8365MB_MIB_dot3StatsMultipleCollisionFrames] = 1, ++ [RTL8365MB_MIB_dot3StatsFCSErrors] = 1, ++ [RTL8365MB_MIB_dot3StatsDeferredTransmissions] = 1, ++ [RTL8365MB_MIB_dot3StatsLateCollisions] = 1, ++ [RTL8365MB_MIB_dot3StatsExcessiveCollisions] = 1, ++ ++ }; ++ struct realtek_priv *priv = ds->priv; ++ struct rtl8365mb *mb; ++ int ret; ++ int i; ++ ++ mb = priv->chip_data; ++ ++ mutex_lock(&mb->mib_lock); ++ for (i = 0; i < RTL8365MB_MIB_END; i++) { ++ struct rtl8365mb_mib_counter *mib = &rtl8365mb_mib_counters[i]; ++ ++ /* Only fetch required MIB counters (marked = 1 above) */ ++ if (!cnt[i]) ++ continue; ++ ++ ret = rtl8365mb_mib_counter_read(priv, port, mib->offset, ++ mib->length, &cnt[i]); ++ if (ret) ++ break; ++ } ++ mutex_unlock(&mb->mib_lock); ++ ++ /* The RTL8365MB-VC exposes MIB objects, which we have to translate into ++ * IEEE 802.3 Managed Objects. This is not always completely faithful, ++ * but we try out best. See RFC 3635 for a detailed treatment of the ++ * subject. ++ */ ++ ++ mac_stats->FramesTransmittedOK = cnt[RTL8365MB_MIB_ifOutUcastPkts] + ++ cnt[RTL8365MB_MIB_ifOutMulticastPkts] + ++ cnt[RTL8365MB_MIB_ifOutBroadcastPkts] + ++ cnt[RTL8365MB_MIB_dot3OutPauseFrames] - ++ cnt[RTL8365MB_MIB_ifOutDiscards]; ++ mac_stats->SingleCollisionFrames = ++ cnt[RTL8365MB_MIB_dot3StatsSingleCollisionFrames]; ++ mac_stats->MultipleCollisionFrames = ++ cnt[RTL8365MB_MIB_dot3StatsMultipleCollisionFrames]; ++ mac_stats->FramesReceivedOK = cnt[RTL8365MB_MIB_ifInUcastPkts] + ++ cnt[RTL8365MB_MIB_ifInMulticastPkts] + ++ cnt[RTL8365MB_MIB_ifInBroadcastPkts] + ++ cnt[RTL8365MB_MIB_dot3InPauseFrames]; ++ mac_stats->FrameCheckSequenceErrors = ++ cnt[RTL8365MB_MIB_dot3StatsFCSErrors]; ++ mac_stats->OctetsTransmittedOK = cnt[RTL8365MB_MIB_ifOutOctets] - ++ 18 * mac_stats->FramesTransmittedOK; ++ mac_stats->FramesWithDeferredXmissions = ++ cnt[RTL8365MB_MIB_dot3StatsDeferredTransmissions]; ++ mac_stats->LateCollisions = cnt[RTL8365MB_MIB_dot3StatsLateCollisions]; ++ mac_stats->FramesAbortedDueToXSColls = ++ cnt[RTL8365MB_MIB_dot3StatsExcessiveCollisions]; ++ mac_stats->OctetsReceivedOK = cnt[RTL8365MB_MIB_ifInOctets] - ++ 18 * mac_stats->FramesReceivedOK; ++ mac_stats->MulticastFramesXmittedOK = ++ cnt[RTL8365MB_MIB_ifOutMulticastPkts]; ++ mac_stats->BroadcastFramesXmittedOK = ++ cnt[RTL8365MB_MIB_ifOutBroadcastPkts]; ++ mac_stats->MulticastFramesReceivedOK = ++ cnt[RTL8365MB_MIB_ifInMulticastPkts]; ++ mac_stats->BroadcastFramesReceivedOK = ++ cnt[RTL8365MB_MIB_ifInBroadcastPkts]; ++} ++ ++static void rtl8365mb_get_ctrl_stats(struct dsa_switch *ds, int port, ++ struct ethtool_eth_ctrl_stats *ctrl_stats) ++{ ++ struct realtek_priv *priv = ds->priv; ++ struct rtl8365mb_mib_counter *mib; ++ struct rtl8365mb *mb; ++ ++ mb = priv->chip_data; ++ mib = &rtl8365mb_mib_counters[RTL8365MB_MIB_dot3ControlInUnknownOpcodes]; ++ ++ mutex_lock(&mb->mib_lock); ++ rtl8365mb_mib_counter_read(priv, port, mib->offset, mib->length, ++ &ctrl_stats->UnsupportedOpcodesReceived); ++ mutex_unlock(&mb->mib_lock); ++} ++ ++static void rtl8365mb_stats_update(struct realtek_priv *priv, int port) ++{ ++ u64 cnt[RTL8365MB_MIB_END] = { ++ [RTL8365MB_MIB_ifOutOctets] = 1, ++ [RTL8365MB_MIB_ifOutUcastPkts] = 1, ++ [RTL8365MB_MIB_ifOutMulticastPkts] = 1, ++ [RTL8365MB_MIB_ifOutBroadcastPkts] = 1, ++ [RTL8365MB_MIB_ifOutDiscards] = 1, ++ [RTL8365MB_MIB_ifInOctets] = 1, ++ [RTL8365MB_MIB_ifInUcastPkts] = 1, ++ [RTL8365MB_MIB_ifInMulticastPkts] = 1, ++ [RTL8365MB_MIB_ifInBroadcastPkts] = 1, ++ [RTL8365MB_MIB_etherStatsDropEvents] = 1, ++ [RTL8365MB_MIB_etherStatsCollisions] = 1, ++ [RTL8365MB_MIB_etherStatsFragments] = 1, ++ [RTL8365MB_MIB_etherStatsJabbers] = 1, ++ [RTL8365MB_MIB_dot3StatsFCSErrors] = 1, ++ [RTL8365MB_MIB_dot3StatsLateCollisions] = 1, ++ }; ++ struct rtl8365mb *mb = priv->chip_data; ++ struct rtnl_link_stats64 *stats; ++ int ret; ++ int i; ++ ++ stats = &mb->ports[port].stats; ++ ++ mutex_lock(&mb->mib_lock); ++ for (i = 0; i < RTL8365MB_MIB_END; i++) { ++ struct rtl8365mb_mib_counter *c = &rtl8365mb_mib_counters[i]; ++ ++ /* Only fetch required MIB counters (marked = 1 above) */ ++ if (!cnt[i]) ++ continue; ++ ++ ret = rtl8365mb_mib_counter_read(priv, port, c->offset, ++ c->length, &cnt[i]); ++ if (ret) ++ break; ++ } ++ mutex_unlock(&mb->mib_lock); ++ ++ /* Don't update statistics if there was an error reading the counters */ ++ if (ret) ++ return; ++ ++ spin_lock(&mb->ports[port].stats_lock); ++ ++ stats->rx_packets = cnt[RTL8365MB_MIB_ifInUcastPkts] + ++ cnt[RTL8365MB_MIB_ifInMulticastPkts] + ++ cnt[RTL8365MB_MIB_ifInBroadcastPkts]; ++ ++ stats->tx_packets = cnt[RTL8365MB_MIB_ifOutUcastPkts] + ++ cnt[RTL8365MB_MIB_ifOutMulticastPkts] + ++ cnt[RTL8365MB_MIB_ifOutBroadcastPkts]; ++ ++ /* if{In,Out}Octets includes FCS - remove it */ ++ stats->rx_bytes = cnt[RTL8365MB_MIB_ifInOctets] - 4 * stats->rx_packets; ++ stats->tx_bytes = ++ cnt[RTL8365MB_MIB_ifOutOctets] - 4 * stats->tx_packets; ++ ++ stats->rx_dropped = cnt[RTL8365MB_MIB_etherStatsDropEvents]; ++ stats->tx_dropped = cnt[RTL8365MB_MIB_ifOutDiscards]; ++ ++ stats->multicast = cnt[RTL8365MB_MIB_ifInMulticastPkts]; ++ stats->collisions = cnt[RTL8365MB_MIB_etherStatsCollisions]; ++ ++ stats->rx_length_errors = cnt[RTL8365MB_MIB_etherStatsFragments] + ++ cnt[RTL8365MB_MIB_etherStatsJabbers]; ++ stats->rx_crc_errors = cnt[RTL8365MB_MIB_dot3StatsFCSErrors]; ++ stats->rx_errors = stats->rx_length_errors + stats->rx_crc_errors; ++ ++ stats->tx_aborted_errors = cnt[RTL8365MB_MIB_ifOutDiscards]; ++ stats->tx_window_errors = cnt[RTL8365MB_MIB_dot3StatsLateCollisions]; ++ stats->tx_errors = stats->tx_aborted_errors + stats->tx_window_errors; ++ ++ spin_unlock(&mb->ports[port].stats_lock); ++} ++ ++static void rtl8365mb_stats_poll(struct work_struct *work) ++{ ++ struct rtl8365mb_port *p = container_of(to_delayed_work(work), ++ struct rtl8365mb_port, ++ mib_work); ++ struct realtek_priv *priv = p->priv; ++ ++ rtl8365mb_stats_update(priv, p->index); ++ ++ schedule_delayed_work(&p->mib_work, RTL8365MB_STATS_INTERVAL_JIFFIES); ++} ++ ++static void rtl8365mb_get_stats64(struct dsa_switch *ds, int port, ++ struct rtnl_link_stats64 *s) ++{ ++ struct realtek_priv *priv = ds->priv; ++ struct rtl8365mb_port *p; ++ struct rtl8365mb *mb; ++ ++ mb = priv->chip_data; ++ p = &mb->ports[port]; ++ ++ spin_lock(&p->stats_lock); ++ memcpy(s, &p->stats, sizeof(*s)); ++ spin_unlock(&p->stats_lock); ++} ++ ++static void rtl8365mb_stats_setup(struct realtek_priv *priv) ++{ ++ struct rtl8365mb *mb = priv->chip_data; ++ struct dsa_switch *ds = &priv->ds; ++ struct dsa_port *dp; ++ ++ /* Per-chip global mutex to protect MIB counter access, since doing ++ * so requires accessing a series of registers in a particular order. ++ */ ++ mutex_init(&mb->mib_lock); ++ ++ dsa_switch_for_each_available_port(dp, ds) { ++ struct rtl8365mb_port *p = &mb->ports[dp->index]; ++ ++ /* Per-port spinlock to protect the stats64 data */ ++ spin_lock_init(&p->stats_lock); ++ ++ /* This work polls the MIB counters and keeps the stats64 data ++ * up-to-date. ++ */ ++ INIT_DELAYED_WORK(&p->mib_work, rtl8365mb_stats_poll); ++ } ++} ++ ++static void rtl8365mb_stats_teardown(struct realtek_priv *priv) ++{ ++ struct rtl8365mb *mb = priv->chip_data; ++ struct dsa_switch *ds = &priv->ds; ++ struct dsa_port *dp; ++ ++ dsa_switch_for_each_available_port(dp, ds) { ++ struct rtl8365mb_port *p = &mb->ports[dp->index]; ++ ++ cancel_delayed_work_sync(&p->mib_work); ++ } ++} ++ ++static int rtl8365mb_get_and_clear_status_reg(struct realtek_priv *priv, u32 reg, ++ u32 *val) ++{ ++ int ret; ++ ++ ret = regmap_read(priv->map, reg, val); ++ if (ret) ++ return ret; ++ ++ return regmap_write(priv->map, reg, *val); ++} ++ ++static irqreturn_t rtl8365mb_irq(int irq, void *data) ++{ ++ struct realtek_priv *priv = data; ++ unsigned long line_changes = 0; ++ u32 stat; ++ int line; ++ int ret; ++ ++ ret = rtl8365mb_get_and_clear_status_reg(priv, RTL8365MB_INTR_STATUS_REG, ++ &stat); ++ if (ret) ++ goto out_error; ++ ++ if (stat & RTL8365MB_INTR_LINK_CHANGE_MASK) { ++ u32 linkdown_ind; ++ u32 linkup_ind; ++ u32 val; ++ ++ ret = rtl8365mb_get_and_clear_status_reg( ++ priv, RTL8365MB_PORT_LINKUP_IND_REG, &val); ++ if (ret) ++ goto out_error; ++ ++ linkup_ind = FIELD_GET(RTL8365MB_PORT_LINKUP_IND_MASK, val); ++ ++ ret = rtl8365mb_get_and_clear_status_reg( ++ priv, RTL8365MB_PORT_LINKDOWN_IND_REG, &val); ++ if (ret) ++ goto out_error; ++ ++ linkdown_ind = FIELD_GET(RTL8365MB_PORT_LINKDOWN_IND_MASK, val); ++ ++ line_changes = linkup_ind | linkdown_ind; ++ } ++ ++ if (!line_changes) ++ goto out_none; ++ ++ for_each_set_bit(line, &line_changes, priv->num_ports) { ++ int child_irq = irq_find_mapping(priv->irqdomain, line); ++ ++ if (!child_irq) ++ continue; ++ ++ handle_nested_irq(child_irq); ++ } ++ ++ return IRQ_HANDLED; ++ ++out_error: ++ dev_err(priv->dev, "failed to read interrupt status: %pe\n", ++ ERR_PTR(ret)); ++ ++out_none: ++ return IRQ_NONE; ++} ++ ++static struct irq_chip rtl8365mb_irq_chip = { ++ .name = "rtl8365mb", ++ /* The hardware doesn't support masking IRQs on a per-port basis */ ++}; ++ ++static int rtl8365mb_irq_map(struct irq_domain *domain, unsigned int irq, ++ irq_hw_number_t hwirq) ++{ ++ struct realtek_priv *priv = domain->host_data; ++ struct rtl8365mb *mb = priv->chip_data; ++ ++ irq_set_chip_data(irq, priv); ++ irq_set_chip_and_handler(irq, &rtl8365mb_irq_chip, handle_simple_irq); ++ irq_set_nested_thread(irq, 1); ++ irq_set_noprobe(irq); ++ irq_set_parent(irq, mb->irq); ++ ++ return 0; ++} ++ ++static void rtl8365mb_irq_unmap(struct irq_domain *d, unsigned int irq) ++{ ++ irq_set_nested_thread(irq, 0); ++ irq_set_chip_and_handler(irq, NULL, NULL); ++ irq_set_chip_data(irq, NULL); ++} ++ ++static const struct irq_domain_ops rtl8365mb_irqdomain_ops = { ++ .map = rtl8365mb_irq_map, ++ .unmap = rtl8365mb_irq_unmap, ++ .xlate = irq_domain_xlate_onecell, ++}; ++ ++static int rtl8365mb_set_irq_enable(struct realtek_priv *priv, bool enable) ++{ ++ return regmap_update_bits(priv->map, RTL8365MB_INTR_CTRL_REG, ++ RTL8365MB_INTR_LINK_CHANGE_MASK, ++ FIELD_PREP(RTL8365MB_INTR_LINK_CHANGE_MASK, ++ enable ? 1 : 0)); ++} ++ ++static int rtl8365mb_irq_enable(struct realtek_priv *priv) ++{ ++ return rtl8365mb_set_irq_enable(priv, true); ++} ++ ++static int rtl8365mb_irq_disable(struct realtek_priv *priv) ++{ ++ return rtl8365mb_set_irq_enable(priv, false); ++} ++ ++static int rtl8365mb_irq_setup(struct realtek_priv *priv) ++{ ++ struct rtl8365mb *mb = priv->chip_data; ++ struct dsa_switch *ds = &priv->ds; ++ struct device_node *intc; ++ struct dsa_port *dp; ++ u32 irq_trig; ++ int virq; ++ int irq; ++ u32 val; ++ int ret; ++ ++ intc = of_get_child_by_name(priv->dev->of_node, "interrupt-controller"); ++ if (!intc) { ++ dev_err(priv->dev, "missing child interrupt-controller node\n"); ++ return -EINVAL; ++ } ++ ++ /* rtl8365mb IRQs cascade off this one */ ++ irq = of_irq_get(intc, 0); ++ if (irq <= 0) { ++ if (!irq) { ++ dev_err(priv->dev, "failed to map IRQ\n"); ++ ret = -EINVAL; ++ } else { ++ ret = dev_err_probe(priv->dev, irq, ++ "failed to get parent irq\n"); ++ } ++ goto out_put_node; ++ } ++ ++ /* Store the irq so that we know to map and free it during teardown */ ++ mb->irq = irq; ++ ++ priv->irqdomain = irq_domain_create_linear(of_fwnode_handle(intc), priv->num_ports, ++ &rtl8365mb_irqdomain_ops, priv); ++ if (!priv->irqdomain) { ++ dev_err(priv->dev, "failed to add irq domain\n"); ++ ret = -ENOMEM; ++ goto out_put_node; ++ } ++ ++ dsa_switch_for_each_available_port(dp, ds) { ++ virq = irq_create_mapping(priv->irqdomain, dp->index); ++ if (!virq) { ++ dev_err(priv->dev, ++ "failed to create irq domain mapping\n"); ++ ret = -EINVAL; ++ goto out_remove_irqdomain; ++ } ++ ++ irq_set_parent(virq, irq); ++ } ++ ++ /* Configure chip interrupt signal polarity */ ++ irq_trig = irq_get_trigger_type(irq); ++ switch (irq_trig) { ++ case IRQF_TRIGGER_RISING: ++ case IRQF_TRIGGER_HIGH: ++ val = RTL8365MB_INTR_POLARITY_HIGH; ++ break; ++ case IRQF_TRIGGER_FALLING: ++ case IRQF_TRIGGER_LOW: ++ val = RTL8365MB_INTR_POLARITY_LOW; ++ break; ++ default: ++ dev_err(priv->dev, "unsupported irq trigger type %u\n", ++ irq_trig); ++ ret = -EINVAL; ++ goto out_remove_irqdomain; ++ } ++ ++ ret = regmap_update_bits(priv->map, RTL8365MB_INTR_POLARITY_REG, ++ RTL8365MB_INTR_POLARITY_MASK, ++ FIELD_PREP(RTL8365MB_INTR_POLARITY_MASK, val)); ++ if (ret) ++ goto out_remove_irqdomain; ++ ++ /* Disable the interrupt in case the chip has it enabled on reset */ ++ ret = rtl8365mb_irq_disable(priv); ++ if (ret) ++ goto out_remove_irqdomain; ++ ++ /* Clear the interrupt status register */ ++ ret = regmap_write(priv->map, RTL8365MB_INTR_STATUS_REG, ++ RTL8365MB_INTR_ALL_MASK); ++ if (ret) ++ goto out_remove_irqdomain; ++ ++ ret = request_threaded_irq(irq, NULL, rtl8365mb_irq, IRQF_ONESHOT, ++ "rtl8365mb", priv); ++ if (ret) { ++ dev_err(priv->dev, "failed to request irq: %pe\n", ++ ERR_PTR(ret)); ++ goto out_remove_irqdomain; ++ } ++ ++ ret = rtl8365mb_irq_enable(priv); ++ if (ret) ++ goto out_free_irq; ++ ++ of_node_put(intc); ++ ++ return 0; ++ ++out_free_irq: ++ free_irq(mb->irq, priv); ++ ++out_remove_irqdomain: ++ dsa_switch_for_each_port(dp, ds) { ++ virq = irq_find_mapping(priv->irqdomain, dp->index); ++ ++ if (virq) ++ irq_dispose_mapping(virq); ++ } ++ ++ irq_domain_remove(priv->irqdomain); ++ priv->irqdomain = NULL; ++ ++out_put_node: ++ mb->irq = 0; ++ of_node_put(intc); ++ ++ return ret; ++} ++ ++static void rtl8365mb_irq_teardown(struct realtek_priv *priv) ++{ ++ struct rtl8365mb *mb = priv->chip_data; ++ struct dsa_switch *ds = &priv->ds; ++ struct dsa_port *dp; ++ int virq; ++ ++ if (mb->irq) { ++ free_irq(mb->irq, priv); ++ mb->irq = 0; ++ } ++ ++ if (priv->irqdomain) { ++ /* Unused ports with a linked PHY still have an active IRQ ++ * mapping that must be disposed of during teardown. Loop ++ * through all ports. ++ */ ++ dsa_switch_for_each_port(dp, ds) { ++ virq = irq_find_mapping(priv->irqdomain, dp->index); ++ ++ if (virq) ++ irq_dispose_mapping(virq); ++ } ++ ++ irq_domain_remove(priv->irqdomain); ++ priv->irqdomain = NULL; ++ } ++} ++ ++static int rtl8365mb_cpu_config(struct realtek_priv *priv) ++{ ++ struct rtl8365mb *mb = priv->chip_data; ++ struct rtl8365mb_cpu *cpu = &mb->cpu; ++ u32 val; ++ int ret; ++ ++ ret = regmap_update_bits(priv->map, RTL8365MB_CPU_PORT_MASK_REG, ++ RTL8365MB_CPU_PORT_MASK_MASK, ++ FIELD_PREP(RTL8365MB_CPU_PORT_MASK_MASK, ++ cpu->mask)); ++ if (ret) ++ return ret; ++ ++ val = FIELD_PREP(RTL8365MB_CPU_CTRL_EN_MASK, cpu->enable ? 1 : 0) | ++ FIELD_PREP(RTL8365MB_CPU_CTRL_INSERTMODE_MASK, cpu->insert) | ++ FIELD_PREP(RTL8365MB_CPU_CTRL_TAG_POSITION_MASK, cpu->position) | ++ FIELD_PREP(RTL8365MB_CPU_CTRL_RXBYTECOUNT_MASK, cpu->rx_length) | ++ FIELD_PREP(RTL8365MB_CPU_CTRL_TAG_FORMAT_MASK, cpu->format) | ++ FIELD_PREP(RTL8365MB_CPU_CTRL_TRAP_PORT_MASK, cpu->trap_port & 0x7) | ++ FIELD_PREP(RTL8365MB_CPU_CTRL_TRAP_PORT_EXT_MASK, ++ cpu->trap_port >> 3 & 0x1); ++ ret = regmap_write(priv->map, RTL8365MB_CPU_CTRL_REG, val); ++ if (ret) ++ return ret; ++ ++ return 0; ++} ++ ++static int rtl8365mb_change_tag_protocol(struct dsa_switch *ds, ++ enum dsa_tag_protocol proto) ++{ ++ struct realtek_priv *priv = ds->priv; ++ struct rtl8365mb_cpu *cpu; ++ struct rtl8365mb *mb; ++ ++ mb = priv->chip_data; ++ cpu = &mb->cpu; ++ ++ switch (proto) { ++ case DSA_TAG_PROTO_RTL8_4: ++ cpu->format = RTL8365MB_CPU_FORMAT_8BYTES; ++ cpu->position = RTL8365MB_CPU_POS_AFTER_SA; ++ break; ++ case DSA_TAG_PROTO_RTL8_4T: ++ cpu->format = RTL8365MB_CPU_FORMAT_8BYTES; ++ cpu->position = RTL8365MB_CPU_POS_BEFORE_CRC; ++ break; ++ /* The switch also supports a 4-byte format, similar to rtl4a but with ++ * the same 0x04 8-bit version and probably 8-bit port source/dest. ++ * There is no public doc about it. Not supported yet and it will probably ++ * never be. ++ */ ++ default: ++ return -EPROTONOSUPPORT; ++ } ++ ++ return rtl8365mb_cpu_config(priv); ++} ++ ++static int rtl8365mb_switch_init(struct realtek_priv *priv) ++{ ++ struct rtl8365mb *mb = priv->chip_data; ++ const struct rtl8365mb_chip_info *ci; ++ int ret; ++ int i; ++ ++ ci = mb->chip_info; ++ ++ /* Do any chip-specific init jam before getting to the common stuff */ ++ if (ci->jam_table) { ++ for (i = 0; i < ci->jam_size; i++) { ++ ret = regmap_write(priv->map, ci->jam_table[i].reg, ++ ci->jam_table[i].val); ++ if (ret) ++ return ret; ++ } ++ } ++ ++ /* Common init jam */ ++ for (i = 0; i < ARRAY_SIZE(rtl8365mb_init_jam_common); i++) { ++ ret = regmap_write(priv->map, rtl8365mb_init_jam_common[i].reg, ++ rtl8365mb_init_jam_common[i].val); ++ if (ret) ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int rtl8365mb_reset_chip(struct realtek_priv *priv) ++{ ++ u32 val; ++ ++ priv->write_reg_noack(priv, RTL8365MB_CHIP_RESET_REG, ++ FIELD_PREP(RTL8365MB_CHIP_RESET_HW_MASK, 1)); ++ ++ /* Realtek documentation says the chip needs 1 second to reset. Sleep ++ * for 100 ms before accessing any registers to prevent ACK timeouts. ++ */ ++ msleep(100); ++ return regmap_read_poll_timeout(priv->map, RTL8365MB_CHIP_RESET_REG, val, ++ !(val & RTL8365MB_CHIP_RESET_HW_MASK), ++ 20000, 1e6); ++} ++ ++static int rtl8365mb_setup(struct dsa_switch *ds) ++{ ++ struct realtek_priv *priv = ds->priv; ++ struct rtl8365mb_cpu *cpu; ++ u32 downports_mask = 0; ++ u32 upports_mask = 0; ++ struct rtl8365mb *mb; ++ struct dsa_port *dp; ++ int ret; ++ ++ mb = priv->chip_data; ++ cpu = &mb->cpu; ++ ++ ret = rtl8365mb_reset_chip(priv); ++ if (ret) { ++ dev_err(priv->dev, "failed to reset chip: %pe\n", ++ ERR_PTR(ret)); ++ goto out_error; ++ } ++ ++ /* Configure switch to vendor-defined initial state */ ++ ret = rtl8365mb_switch_init(priv); ++ if (ret) { ++ dev_err(priv->dev, "failed to initialize switch: %pe\n", ++ ERR_PTR(ret)); ++ goto out_error; ++ } ++ ++ /* Set up cascading IRQs */ ++ ret = rtl8365mb_irq_setup(priv); ++ if (ret == -EPROBE_DEFER) ++ return ret; ++ else if (ret) ++ dev_info(priv->dev, "no interrupt support\n"); ++ ++ dsa_switch_for_each_port(dp, ds) { ++ /* Cascading (DSA links) is not supported yet. ++ * Historically, the driver has always been broken ++ * without a dedicated CPU port because CPU tagging ++ * would be disabled, rendering the switch entirely ++ * non-functional for DSA operations. ++ */ ++ if (dsa_port_is_dsa(dp)) { ++ dev_err(priv->dev, "Cascading (DSA link) not supported\n"); ++ ret = -EOPNOTSUPP; ++ goto out_teardown_irq; ++ } ++ } ++ ++ /* Start with all ports blocked, including unused ports */ ++ dsa_switch_for_each_port(dp, ds) { ++ struct rtl8365mb_port *p = &mb->ports[dp->index]; ++ ++ /* Set the initial STP state of all ports to DISABLED, otherwise ++ * ports will still forward frames to the CPU despite being ++ * administratively down by default. ++ */ ++ rtl8365mb_port_stp_state_set(ds, dp->index, BR_STATE_DISABLED); ++ ++ /* Start with all port completely isolated */ ++ ret = rtl8365mb_port_set_isolation(priv, dp->index, 0); ++ if (ret) ++ goto out_teardown_irq; ++ ++ /* Disable learning */ ++ ret = rtl8365mb_port_set_learning(priv, dp->index, false); ++ if (ret) ++ goto out_teardown_irq; ++ ++ /* Set up per-port private data */ ++ p->priv = priv; ++ p->index = dp->index; ++ ++ /* Collect CPU ports. If we support cascade switches, it should ++ * also include the upstream DSA ports. ++ */ ++ if (!dsa_port_is_cpu(dp)) ++ continue; ++ ++ upports_mask |= BIT(dp->index); ++ } ++ ++ /* Configure user ports */ ++ dsa_switch_for_each_port(dp, ds) { ++ if (!dsa_port_is_user(dp)) ++ continue; ++ ++ /* Forward only to the CPU */ ++ ret = rtl8365mb_port_set_isolation(priv, dp->index, ++ upports_mask); ++ if (ret) ++ goto out_teardown_irq; ++ ++ /* If we support cascade switches, it should also include the ++ * downstream DSA ports. ++ */ ++ downports_mask |= BIT(dp->index); ++ } ++ ++ /* Configure CPU tagging */ ++ /* If we support cascade switches, it should also include the upstream ++ * DSA ports. ++ */ ++ dsa_switch_for_each_cpu_port(dp, ds) { ++ /* Use the first CPU port as trap_port */ ++ if (cpu->trap_port == RTL8365MB_MAX_NUM_PORTS) ++ cpu->trap_port = dp->index; ++ ++ /* Forward to all user ports */ ++ ret = rtl8365mb_port_set_isolation(priv, dp->index, ++ downports_mask); ++ if (ret) ++ goto out_teardown_irq; ++ } ++ ++ cpu->mask = upports_mask; ++ cpu->enable = cpu->mask > 0; ++ ++ if (!cpu->enable) { ++ dev_err(priv->dev, "no CPU port defined\n"); ++ ret = -EINVAL; ++ goto out_teardown_irq; ++ } ++ ++ ret = rtl8365mb_cpu_config(priv); ++ if (ret) ++ goto out_teardown_irq; ++ ++ ret = rtl8365mb_port_change_mtu(ds, cpu->trap_port, ETH_DATA_LEN); ++ if (ret) ++ goto out_teardown_irq; ++ ++ ret = rtl83xx_setup_user_mdio(ds); ++ if (ret) { ++ dev_err(priv->dev, "could not set up MDIO bus\n"); ++ goto out_teardown_irq; ++ } ++ ++ /* Start statistics counter polling */ ++ rtl8365mb_stats_setup(priv); ++ ++ return 0; ++ ++out_teardown_irq: ++ rtl8365mb_irq_teardown(priv); ++ ++out_error: ++ return ret; ++} ++ ++static void rtl8365mb_teardown(struct dsa_switch *ds) ++{ ++ struct realtek_priv *priv = ds->priv; ++ ++ rtl8365mb_stats_teardown(priv); ++ rtl8365mb_irq_teardown(priv); ++} ++ ++static int rtl8365mb_get_chip_id_and_ver(struct regmap *map, u32 *id, u32 *ver) ++{ ++ int ret; ++ ++ /* For some reason we have to write a magic value to an arbitrary ++ * register whenever accessing the chip ID/version registers. ++ */ ++ ret = regmap_write(map, RTL8365MB_MAGIC_REG, RTL8365MB_MAGIC_VALUE); ++ if (ret) ++ return ret; ++ ++ ret = regmap_read(map, RTL8365MB_CHIP_ID_REG, id); ++ if (ret) ++ return ret; ++ ++ ret = regmap_read(map, RTL8365MB_CHIP_VER_REG, ver); ++ if (ret) ++ return ret; ++ ++ /* Reset magic register */ ++ ret = regmap_write(map, RTL8365MB_MAGIC_REG, 0); ++ if (ret) ++ return ret; ++ ++ return 0; ++} ++ ++static int rtl8365mb_detect(struct realtek_priv *priv) ++{ ++ struct rtl8365mb *mb = priv->chip_data; ++ u32 chip_id; ++ u32 chip_ver; ++ int ret; ++ int i; ++ ++ ret = rtl8365mb_get_chip_id_and_ver(priv->map, &chip_id, &chip_ver); ++ if (ret) { ++ dev_err(priv->dev, "failed to read chip id and version: %pe\n", ++ ERR_PTR(ret)); ++ return ret; ++ } ++ ++ for (i = 0; i < ARRAY_SIZE(rtl8365mb_chip_infos); i++) { ++ const struct rtl8365mb_chip_info *ci = &rtl8365mb_chip_infos[i]; ++ ++ if (ci->chip_id == chip_id && ci->chip_ver == chip_ver) { ++ mb->chip_info = ci; ++ break; ++ } ++ } ++ ++ if (!mb->chip_info) { ++ dev_err(priv->dev, ++ "unrecognized switch (id=0x%04x, ver=0x%04x)", chip_id, ++ chip_ver); ++ return -ENODEV; ++ } ++ ++ dev_info(priv->dev, "found an %s switch\n", mb->chip_info->name); ++ ++ priv->num_ports = RTL8365MB_MAX_NUM_PORTS; ++ mb->priv = priv; ++ mb->cpu.trap_port = RTL8365MB_MAX_NUM_PORTS; ++ mb->cpu.insert = RTL8365MB_CPU_INSERT_TO_ALL; ++ mb->cpu.position = RTL8365MB_CPU_POS_AFTER_SA; ++ mb->cpu.rx_length = RTL8365MB_CPU_RXLEN_64BYTES; ++ mb->cpu.format = RTL8365MB_CPU_FORMAT_8BYTES; ++ ++ return 0; ++} ++ ++static const struct phylink_mac_ops rtl8365mb_phylink_mac_ops = { ++ .mac_config = rtl8365mb_phylink_mac_config, ++ .mac_link_down = rtl8365mb_phylink_mac_link_down, ++ .mac_link_up = rtl8365mb_phylink_mac_link_up, ++}; ++ ++static const struct dsa_switch_ops rtl8365mb_switch_ops = { ++ .get_tag_protocol = rtl8365mb_get_tag_protocol, ++ .change_tag_protocol = rtl8365mb_change_tag_protocol, ++ .setup = rtl8365mb_setup, ++ .teardown = rtl8365mb_teardown, ++ .phylink_get_caps = rtl8365mb_phylink_get_caps, ++ .port_stp_state_set = rtl8365mb_port_stp_state_set, ++ .get_strings = rtl8365mb_get_strings, ++ .get_ethtool_stats = rtl8365mb_get_ethtool_stats, ++ .get_sset_count = rtl8365mb_get_sset_count, ++ .get_eth_phy_stats = rtl8365mb_get_phy_stats, ++ .get_eth_mac_stats = rtl8365mb_get_mac_stats, ++ .get_eth_ctrl_stats = rtl8365mb_get_ctrl_stats, ++ .get_stats64 = rtl8365mb_get_stats64, ++ .port_change_mtu = rtl8365mb_port_change_mtu, ++ .port_max_mtu = rtl8365mb_port_max_mtu, ++}; ++ ++static const struct realtek_ops rtl8365mb_ops = { ++ .detect = rtl8365mb_detect, ++ .phy_read = rtl8365mb_phy_read, ++ .phy_write = rtl8365mb_phy_write, ++}; ++ ++const struct realtek_variant rtl8365mb_variant = { ++ .ds_ops = &rtl8365mb_switch_ops, ++ .ops = &rtl8365mb_ops, ++ .phylink_mac_ops = &rtl8365mb_phylink_mac_ops, ++ .clk_delay = 10, ++ .cmd_read = 0xb9, ++ .cmd_write = 0xb8, ++ .chip_data_sz = sizeof(struct rtl8365mb), ++}; ++ ++static const struct of_device_id rtl8365mb_of_match[] = { ++ { .compatible = "realtek,rtl8365mb", .data = &rtl8365mb_variant, }, ++ { /* sentinel */ }, ++}; ++MODULE_DEVICE_TABLE(of, rtl8365mb_of_match); ++ ++static struct platform_driver rtl8365mb_smi_driver = { ++ .driver = { ++ .name = "rtl8365mb-smi", ++ .of_match_table = rtl8365mb_of_match, ++ }, ++ .probe = realtek_smi_probe, ++ .remove = realtek_smi_remove, ++ .shutdown = realtek_smi_shutdown, ++}; ++ ++static struct mdio_driver rtl8365mb_mdio_driver = { ++ .mdiodrv.driver = { ++ .name = "rtl8365mb-mdio", ++ .of_match_table = rtl8365mb_of_match, ++ }, ++ .probe = realtek_mdio_probe, ++ .remove = realtek_mdio_remove, ++ .shutdown = realtek_mdio_shutdown, ++}; ++ ++static int rtl8365mb_init(void) ++{ ++ int ret; ++ ++ ret = realtek_mdio_driver_register(&rtl8365mb_mdio_driver); ++ if (ret) ++ return ret; ++ ++ ret = realtek_smi_driver_register(&rtl8365mb_smi_driver); ++ if (ret) { ++ realtek_mdio_driver_unregister(&rtl8365mb_mdio_driver); ++ return ret; ++ } ++ ++ return 0; ++} ++module_init(rtl8365mb_init); ++ ++static void __exit rtl8365mb_exit(void) ++{ ++ realtek_smi_driver_unregister(&rtl8365mb_smi_driver); ++ realtek_mdio_driver_unregister(&rtl8365mb_mdio_driver); ++} ++module_exit(rtl8365mb_exit); ++ ++MODULE_AUTHOR("Alvin Å ipraga "); ++MODULE_DESCRIPTION("Driver for RTL8365MB-VC ethernet switch"); ++MODULE_LICENSE("GPL"); ++MODULE_IMPORT_NS("REALTEK_DSA"); diff --git a/target/linux/generic/backport-6.18/942-05-7.2-net-dsa-realtek-rtl8365mb-add-table-lookup-interface.patch b/target/linux/generic/backport-6.18/942-05-7.2-net-dsa-realtek-rtl8365mb-add-table-lookup-interface.patch new file mode 100644 index 00000000000..1239c79a029 --- /dev/null +++ b/target/linux/generic/backport-6.18/942-05-7.2-net-dsa-realtek-rtl8365mb-add-table-lookup-interface.patch @@ -0,0 +1,394 @@ +From fbafdd3b224a03b7b335de144f44a600de937586 Mon Sep 17 00:00:00 2001 +From: Alvin Å ipraga +Date: Sat, 6 Jun 2026 05:29:29 -0300 +Subject: net: dsa: realtek: rtl8365mb: add table lookup interface +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Add a generic table lookup interface to centralize access to +the RTL8365MB internal tables. + +This interface abstracts the low-level table access logic and +will be used by subsequent commits to implement FDB and VLAN +operations. + +Signed-off-by: Alvin Å ipraga +Reviewed-by: Linus Walleij +Reviewed-by: Mieczyslaw Nalewaj +Co-developed-by: Luiz Angelo Daros de Luca +Signed-off-by: Luiz Angelo Daros de Luca +Link: https://patch.msgid.link/20260606-realtek_forward-v13-5-b9e409687cbe@gmail.com +Signed-off-by: Jakub Kicinski +--- + drivers/net/dsa/realtek/Makefile | 1 + + drivers/net/dsa/realtek/rtl8365mb_table.c | 214 ++++++++++++++++++++++++++++++ + drivers/net/dsa/realtek/rtl8365mb_table.h | 138 +++++++++++++++++++ + 3 files changed, 353 insertions(+) + +--- a/drivers/net/dsa/realtek/Makefile ++++ b/drivers/net/dsa/realtek/Makefile +@@ -17,4 +17,5 @@ rtl8366-objs += rtl8366rb-leds.o + endif + obj-$(CONFIG_NET_DSA_REALTEK_RTL8365MB) += rtl8365mb.o + rtl8365mb-objs := rtl8365mb_main.o \ ++ rtl8365mb_table.o \ + # end of rtl8365mb-objs +--- /dev/null ++++ b/drivers/net/dsa/realtek/rtl8365mb_table.c +@@ -0,0 +1,214 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* Look-up table query interface for the rtl8365mb switch family ++ * ++ * Copyright (C) 2022 Alvin Å ipraga ++ */ ++ ++#include "rtl8365mb_table.h" ++#include ++ ++/* Table access control register */ ++#define RTL8365MB_TABLE_CTRL_REG 0x0500 ++/* Should be one of rtl8365mb_table enum members */ ++#define RTL8365MB_TABLE_CTRL_TABLE_MASK GENMASK(2, 0) ++/* Should be one of rtl8365mb_table_op enum members */ ++#define RTL8365MB_TABLE_CTRL_OP_MASK GENMASK(3, 3) ++/* Should be one of rtl8365mb_table_l2_method enum members */ ++#define RTL8365MB_TABLE_CTRL_METHOD_MASK GENMASK(6, 4) ++#define RTL8365MB_TABLE_CTRL_PORT_MASK GENMASK(11, 8) ++ ++/* Table access address register */ ++#define RTL8365MB_TABLE_ACCESS_ADDR_REG 0x0501 ++#define RTL8365MB_TABLE_ADDR_MASK GENMASK(12, 0) ++ ++/* Table status register */ ++#define RTL8365MB_TABLE_STATUS_REG 0x0502 ++#define RTL8365MB_TABLE_STATUS_ADDRESS_MASK GENMASK(10, 0) ++/* set for L3, unset for L2 */ ++#define RTL8365MB_TABLE_STATUS_ADDR_TYPE_MASK GENMASK(11, 11) ++#define RTL8365MB_TABLE_STATUS_HIT_STATUS_MASK GENMASK(12, 12) ++#define RTL8365MB_TABLE_STATUS_BUSY_FLAG_MASK GENMASK(13, 13) ++#define RTL8365MB_TABLE_STATUS_ADDRESS_EXT_MASK GENMASK(14, 14) ++ ++/* Table read/write registers */ ++#define RTL8365MB_TABLE_WRITE_BASE 0x0510 ++#define RTL8365MB_TABLE_WRITE_REG(_x) \ ++ (RTL8365MB_TABLE_WRITE_BASE + (_x)) ++#define RTL8365MB_TABLE_READ_BASE 0x0520 ++#define RTL8365MB_TABLE_READ_REG(_x) \ ++ (RTL8365MB_TABLE_READ_BASE + (_x)) ++#define RTL8365MB_TABLE_10TH_DATA_MASK GENMASK(3, 0) ++#define RTL8365MB_TABLE_WRITE_10TH_REG \ ++ RTL8365MB_TABLE_WRITE_REG(RTL8365MB_TABLE_ENTRY_MAX_SIZE - 1) ++ ++static int rtl8365mb_table_poll_busy(struct realtek_priv *priv) ++{ ++ u32 val; ++ ++ return regmap_read_poll_timeout(priv->map_nolock, ++ RTL8365MB_TABLE_STATUS_REG, val, ++ !FIELD_GET(RTL8365MB_TABLE_STATUS_BUSY_FLAG_MASK, val), ++ 10, 10000); ++} ++ ++int rtl8365mb_table_query(struct realtek_priv *priv, ++ enum rtl8365mb_table table, ++ enum rtl8365mb_table_op op, u16 *addr, ++ enum rtl8365mb_table_l2_method method, ++ u16 port, u16 *data, size_t size) ++{ ++ bool addr_as_input = true; ++ bool write_data = false; ++ int ret = 0; ++ u32 cmd; ++ u32 val; ++ u32 hit; ++ ++ /* Prepare target table and operation (read or write) */ ++ cmd = 0; ++ cmd |= FIELD_PREP(RTL8365MB_TABLE_CTRL_TABLE_MASK, table); ++ cmd |= FIELD_PREP(RTL8365MB_TABLE_CTRL_OP_MASK, op); ++ if (op == RTL8365MB_TABLE_OP_READ && table == RTL8365MB_TABLE_L2) { ++ cmd |= FIELD_PREP(RTL8365MB_TABLE_CTRL_METHOD_MASK, method); ++ switch (method) { ++ case RTL8365MB_TABLE_L2_METHOD_MAC: ++ /* ++ * Method MAC requires as input the same L2 table format ++ * you'll get as result. However, it might only use mac ++ * address and FID/VID fields. ++ */ ++ write_data = true; ++ ++ /* METHOD_MAC does not use addr as input, but may return ++ * the matched index. ++ */ ++ addr_as_input = false; ++ ++ break; ++ case RTL8365MB_TABLE_L2_METHOD_ADDR: ++ case RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT: ++ case RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC: ++ case RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_MC: ++ break; ++ case RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC_PORT: ++ cmd |= FIELD_PREP(RTL8365MB_TABLE_CTRL_PORT_MASK, port); ++ break; ++ default: ++ return -EINVAL; ++ } ++ } else if (op == RTL8365MB_TABLE_OP_WRITE) { ++ write_data = true; ++ ++ /* Writing to L2 does not use addr as input, as the table index ++ * is derived from key fields. ++ */ ++ if (table == RTL8365MB_TABLE_L2) ++ addr_as_input = false; ++ } ++ ++ /* To prevent concurrent access to the look-up tables, take the regmap ++ * lock manually and access via the map_nolock regmap. ++ */ ++ mutex_lock(&priv->map_lock); ++ ++ /* Protect from a busy table access (i.e. previous access timeouts) */ ++ ret = rtl8365mb_table_poll_busy(priv); ++ if (ret) ++ goto out; ++ ++ /* Write entry data if writing to the table (or L2_METHOD_MAC) */ ++ if (write_data) { ++ /* bulk write data up to 9th word */ ++ ret = regmap_bulk_write(priv->map_nolock, ++ RTL8365MB_TABLE_WRITE_BASE, ++ data, ++ min_t(size_t, size, ++ RTL8365MB_TABLE_ENTRY_MAX_SIZE - ++ 1)); ++ if (ret) ++ goto out; ++ ++ /* 10th register uses only 4 least significant bits */ ++ if (size == RTL8365MB_TABLE_ENTRY_MAX_SIZE) { ++ val = FIELD_PREP(RTL8365MB_TABLE_10TH_DATA_MASK, ++ data[size - 1]); ++ ret = regmap_update_bits(priv->map_nolock, ++ RTL8365MB_TABLE_WRITE_10TH_REG, ++ RTL8365MB_TABLE_10TH_DATA_MASK, ++ val); ++ } ++ ++ if (ret) ++ goto out; ++ } ++ ++ /* Write address (if needed) */ ++ if (addr_as_input) { ++ ret = regmap_write(priv->map_nolock, ++ RTL8365MB_TABLE_ACCESS_ADDR_REG, ++ FIELD_PREP(RTL8365MB_TABLE_ADDR_MASK, ++ *addr)); ++ if (ret) ++ goto out; ++ } ++ ++ /* Execute */ ++ ret = regmap_write(priv->map_nolock, RTL8365MB_TABLE_CTRL_REG, cmd); ++ if (ret) ++ goto out; ++ ++ /* Poll for completion */ ++ ret = rtl8365mb_table_poll_busy(priv); ++ if (ret) ++ goto out; ++ ++ /* For both reads and writes to the L2 table, check status */ ++ if (table == RTL8365MB_TABLE_L2) { ++ ret = regmap_read(priv->map_nolock, RTL8365MB_TABLE_STATUS_REG, ++ &val); ++ if (ret) ++ goto out; ++ ++ /* Did the query find an entry? */ ++ hit = FIELD_GET(RTL8365MB_TABLE_STATUS_HIT_STATUS_MASK, val); ++ if (!hit) { ++ ret = -ENOENT; ++ goto out; ++ } ++ ++ /* If so, extract the address */ ++ *addr = 0; ++ *addr |= FIELD_GET(RTL8365MB_TABLE_STATUS_ADDRESS_MASK, val); ++ *addr |= FIELD_GET(RTL8365MB_TABLE_STATUS_ADDRESS_EXT_MASK, val) ++ << 11; ++ /* only set if it is a L3 address */ ++ *addr |= FIELD_GET(RTL8365MB_TABLE_STATUS_ADDR_TYPE_MASK, val) ++ << 12; ++ } ++ ++ /* Finally, get the table entry if we were reading */ ++ if (op == RTL8365MB_TABLE_OP_READ) { ++ ret = regmap_bulk_read(priv->map_nolock, ++ RTL8365MB_TABLE_READ_BASE, ++ data, size); ++ if (ret) ++ goto out; ++ ++ /* For the biggest table entries, the uppermost table ++ * entry register has space for only one nibble. Mask ++ * out the remainder bits. Empirically I saw nothing ++ * wrong with omitting this mask, but it may prevent ++ * unwanted behaviour. FYI. ++ */ ++ if (size == RTL8365MB_TABLE_ENTRY_MAX_SIZE) { ++ val = FIELD_GET(RTL8365MB_TABLE_10TH_DATA_MASK, ++ data[size - 1]); ++ data[size - 1] = val; ++ } ++ } ++ ++out: ++ mutex_unlock(&priv->map_lock); ++ ++ return ret; ++} +--- /dev/null ++++ b/drivers/net/dsa/realtek/rtl8365mb_table.h +@@ -0,0 +1,138 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* Look-up table query interface for the rtl8365mb switch family ++ * ++ * Copyright (C) 2022 Alvin Å ipraga ++ */ ++ ++#ifndef _REALTEK_RTL8365MB_TABLE_H ++#define _REALTEK_RTL8365MB_TABLE_H ++ ++#include ++#include ++ ++#include "realtek.h" ++ ++#define RTL8365MB_TABLE_ENTRY_MAX_SIZE 10 ++ ++/* ++ * enum rtl8365mb_table - available switch tables ++ * @RTL8365MB_TABLE_ACL_RULE: ACL rules ++ * @RTL8365MB_TABLE_ACL_ACTION: ACL actions ++ * @RTL8365MB_TABLE_CVLAN: VLAN4k configurations ++ * @RTL8365MB_TABLE_L2: filtering database (2K hash table) ++ * @RTL8365MB_TABLE_IGMP_GROUP: IGMP group database (readonly) ++ * ++ * NOTE: Don't change the enum values. They must concur with the field ++ * described by @RTL8365MB_TABLE_CTRL_TABLE_MASK. ++ */ ++enum rtl8365mb_table { ++ RTL8365MB_TABLE_ACL_RULE = 1, ++ RTL8365MB_TABLE_ACL_ACTION = 2, ++ RTL8365MB_TABLE_CVLAN = 3, ++ RTL8365MB_TABLE_L2 = 4, ++ RTL8365MB_TABLE_IGMP_GROUP = 5, ++}; ++ ++/* ++ * enum rtl8365mb_table_op - table query operation ++ * @RTL8365MB_TABLE_OP_READ: read an entry from the target table ++ * @RTL8365MB_TABLE_OP_WRITE: write an entry to the target table ++ * ++ * NOTE: Don't change the enum values. They must concur with the field ++ * described by @RTL8365MB_TABLE_CTRL_OP_MASK. ++ */ ++enum rtl8365mb_table_op { ++ RTL8365MB_TABLE_OP_READ = 0, ++ RTL8365MB_TABLE_OP_WRITE = 1, ++}; ++ ++/* ++ * enum rtl8365mb_table_l2_method - look-up method for read queries of L2 table ++ * @RTL8365MB_TABLE_L2_METHOD_MAC: look-up by source MAC address and FID (or ++ * VID) ++ * @RTL8365MB_TABLE_L2_METHOD_ADDR: look-up by entry address ++ * @RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT: look-up next entry starting from the ++ * supplied address ++ * @RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC: same as ADDR_NEXT but search only ++ * unicast addresses ++ * @RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_MC: same as ADDR_NEXT but search only ++ * multicast addresses ++ * @RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC_PORT: same as ADDR_NEXT_UC but ++ * search only entries with matching source port ++ * ++ * NOTE: Don't change the enum values. They must concur with the field ++ * described by @RTL8365MB_TABLE_CTRL_METHOD_MASK ++ */ ++enum rtl8365mb_table_l2_method { ++ RTL8365MB_TABLE_L2_METHOD_MAC = 0, ++ RTL8365MB_TABLE_L2_METHOD_ADDR = 1, ++ RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT = 2, ++ RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC = 3, ++ RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_MC = 4, ++ /* ++ * RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_MC_L3 = 5, ++ * RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_MC_L2L3 = 6, ++ */ ++ RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC_PORT = 7, ++}; ++ ++/* ++ * rtl8365mb_table_query() - read from or write to a switch table ++ * @priv: driver context ++ * @table: target table, see &enum rtl8365mb_table ++ * @op: read or write operation, see &enum rtl8365mb_table_op ++ * @addr: table address. For indexed tables, this selects the entry to access. ++ * For L2 read queries, it is ignored as input for MAC-based lookup ++ * methods and used as input for address-based lookup methods. On ++ * successful L2 queries, it is updated with the matched entry address. ++ * @method: L2 table lookup method, see &enum rtl8365mb_table_l2_method. ++ * Ignored for non-L2 tables. ++ * @port: for L2 read queries using method ++ * %RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC_PORT, restrict the search ++ * to entries associated with this source port. Ignored otherwise. ++ * @data: data buffer used to read from or write to the table. For L2 MAC ++ * lookups, this buffer provides the lookup key and receives the ++ * matched entry contents on success. ++ * @size: size of @data in 16-bit words. The caller must ensure that @size ++ * matches the target table's entry size and does not exceed ++ * RTL8365MB_TABLE_ENTRY_MAX_SIZE. ++ * ++ * This function provides unified access to the internal tables of the switch. ++ * All tables except the L2 table are simple indexed tables, where @addr ++ * selects the entry and @op determines whether the access is a read or a ++ * write operation. ++ * ++ * The content of @data is used as input when writing to tables or when ++ * specifying the lookup key for L2 MAC searches, and as output for all ++ * successful read operations. It remains unchanged during write operations or ++ * failed read operations that return %-ENOENT. For other errors during read ++ * operations, it is undefined. ++ * ++ * The L2 table is a hash table and supports multiple lookup methods. For ++ * %RTL8365MB_TABLE_L2_METHOD_MAC, an entry is searched based on the MAC ++ * address and FID/VID fields provided in @data, using the same format as ++ * an L2 table entry. Address-based methods either read a specific entry ++ * (%RTL8365MB_TABLE_L2_METHOD_ADDR) or iterate over valid entries starting ++ * from @addr (%RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT and variants). When using ++ * %RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC_PORT, only entries associated with ++ * the specified @port are considered. ++ * ++ * On successful L2 operations, @addr is updated with the matched table address ++ * or allocated entry address. If no matching entry is found, or if an L2 write ++ * operation fails (e.g., due to a full table during addition or a missing entry ++ * during deletion), %-ENOENT is returned and @addr remains unchanged. It is the ++ * caller's responsibility to map the returned error to the appropriate ++ * semantic error. ++ * ++ * @size must match the size of the target table entry, expressed in 16-bit ++ * words. ++ * ++ * Return: 0 on success, or a negative error code on failure. ++ */ ++int rtl8365mb_table_query(struct realtek_priv *priv, ++ enum rtl8365mb_table table, ++ enum rtl8365mb_table_op op, u16 *addr, ++ enum rtl8365mb_table_l2_method method, ++ u16 port, u16 *data, size_t size); ++ ++#endif /* _REALTEK_RTL8365MB_TABLE_H */ diff --git a/target/linux/generic/backport-6.18/942-06-7.2-net-dsa-realtek-rtl8365mb-add-VLAN-support.patch b/target/linux/generic/backport-6.18/942-06-7.2-net-dsa-realtek-rtl8365mb-add-VLAN-support.patch new file mode 100644 index 00000000000..c9edb77cd79 --- /dev/null +++ b/target/linux/generic/backport-6.18/942-06-7.2-net-dsa-realtek-rtl8365mb-add-VLAN-support.patch @@ -0,0 +1,1461 @@ +From 9da2c8672f77108a1f09232320f22225ab53dde9 Mon Sep 17 00:00:00 2001 +From: Alvin Å ipraga +Date: Sat, 6 Jun 2026 05:29:30 -0300 +Subject: net: dsa: realtek: rtl8365mb: add VLAN support +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Realtek RTL8365MB switches (a.k.a. RTL8367C family) use two different +structures for VLANs: + +- VLAN4K: A full table with 4096 entries defining port membership and + tagging. +- VLANMC: A smaller table with 32 entries used primarily for PVID + assignment. + +In this hardware, a port's PVID must point to an index in the VLANMC +table rather than a VID directly. Since the VLANMC table is limited to +32 entries, the driver implements a dynamic allocation scheme to +maximize resource usage: + +- VLAN4K is treated by the driver as the source of truth for membership. +- A VLANMC entry is only allocated when a port is configured to use a + specific VID as its PVID. +- VLANMC entries are deleted when no longer needed as a PVID by any port. + +Although VLANMC has a members field, the switch only checks membership +in the VLAN4K table. This driver will use VLANMC members field as way to +track which ports are using that entry as PVID. + +VLANMC index 0, although a valid entry, is reserved in this driver as a +neutral PVID value for ports not using a specific PVID. + +In the subsequent RTL8367D switch family, VLANMC table was +removed and PVID assignment was delegated to a dedicated set of +registers. + +The use of FIELD_PREP for reconstructing LO/HI values was suggested by +Yury Norov. + +Fix for vlan_setup and vlan_filtering was suggested by Abdulkader +Alrezej. + +Suggested-by: Yury Norov +Suggested-by: Abdulkader Alrezej +Signed-off-by: Alvin Å ipraga +Reviewed-by: Linus Walleij +Reviewed-by: Mieczyslaw Nalewaj +Co-developed-by: Luiz Angelo Daros de Luca +Signed-off-by: Luiz Angelo Daros de Luca +Link: https://patch.msgid.link/20260606-realtek_forward-v13-6-b9e409687cbe@gmail.com +Signed-off-by: Jakub Kicinski +--- + drivers/net/dsa/realtek/Makefile | 1 + + drivers/net/dsa/realtek/realtek.h | 5 + + drivers/net/dsa/realtek/rtl8365mb_main.c | 342 +++++++++++ + drivers/net/dsa/realtek/rtl8365mb_vlan.c | 944 +++++++++++++++++++++++++++++++ + drivers/net/dsa/realtek/rtl8365mb_vlan.h | 39 ++ + drivers/net/dsa/realtek/rtl83xx.c | 1 + + 6 files changed, 1332 insertions(+) + +--- a/drivers/net/dsa/realtek/Makefile ++++ b/drivers/net/dsa/realtek/Makefile +@@ -18,4 +18,5 @@ endif + obj-$(CONFIG_NET_DSA_REALTEK_RTL8365MB) += rtl8365mb.o + rtl8365mb-objs := rtl8365mb_main.o \ + rtl8365mb_table.o \ ++ rtl8365mb_vlan.o \ + # end of rtl8365mb-objs +--- a/drivers/net/dsa/realtek/realtek.h ++++ b/drivers/net/dsa/realtek/realtek.h +@@ -54,6 +54,11 @@ struct realtek_priv { + struct regmap *map; + struct regmap *map_nolock; + struct mutex map_lock; ++ /* vlan_lock protects against concurrent Read-Modify-Write operations ++ * on the global VLAN 4K and VLANMC tables, such as when adding or ++ * deleting port VLAN memberships and PVID configurations. ++ */ ++ struct mutex vlan_lock; + struct mii_bus *user_mii_bus; + struct mii_bus *bus; + int mdio_addr; +--- a/drivers/net/dsa/realtek/rtl8365mb_main.c ++++ b/drivers/net/dsa/realtek/rtl8365mb_main.c +@@ -104,6 +104,7 @@ + #include "realtek-smi.h" + #include "realtek-mdio.h" + #include "rtl83xx.h" ++#include "rtl8365mb_vlan.h" + + /* Family-specific data and limits */ + #define RTL8365MB_PHYADDRMAX 7 +@@ -292,6 +293,57 @@ + #define RTL8365MB_MSTI_CTRL_PORT_STATE_MASK(_physport) \ + (0x3 << RTL8365MB_MSTI_CTRL_PORT_STATE_OFFSET((_physport))) + ++/* Miscellaneous port configuration register, incl. VLAN egress mode */ ++#define RTL8365MB_PORT_MISC_CFG_REG_BASE 0x000E ++#define RTL8365MB_PORT_MISC_CFG_REG(_p) \ ++ (RTL8365MB_PORT_MISC_CFG_REG_BASE + ((_p) << 5)) ++#define RTL8365MB_PORT_MISC_CFG_SMALL_TAG_IPG_MASK 0x8000 ++#define RTL8365MB_PORT_MISC_CFG_TX_ITFSP_MODE_MASK 0x4000 ++#define RTL8365MB_PORT_MISC_CFG_FLOWCTRL_INDEP_MASK 0x2000 ++#define RTL8365MB_PORT_MISC_CFG_DOT1Q_REMARK_ENABLE_MASK 0x1000 ++#define RTL8365MB_PORT_MISC_CFG_INGRESSBW_FLOWCTRL_MASK 0x0800 ++#define RTL8365MB_PORT_MISC_CFG_INGRESSBW_IFG_MASK 0x0400 ++#define RTL8365MB_PORT_MISC_CFG_RX_SPC_MASK 0x0200 ++#define RTL8365MB_PORT_MISC_CFG_CRC_SKIP_MASK 0x0100 ++#define RTL8365MB_PORT_MISC_CFG_PKTGEN_TX_FIRST_MASK 0x0080 ++#define RTL8365MB_PORT_MISC_CFG_MAC_LOOPBACK_MASK 0x0040 ++/* See &rtl8365mb_vlan_egress_mode */ ++#define RTL8365MB_PORT_MISC_CFG_VLAN_EGRESS_MODE_MASK 0x0030 ++#define RTL8365MB_PORT_MISC_CFG_CONGESTION_SUSTAIN_TIME_MASK 0x000F ++ ++/** ++ * enum rtl8365mb_vlan_egress_mode - port VLAN egress mode ++ * @RTL8365MB_VLAN_EGRESS_MODE_ORIGINAL: follow untag mask in VLAN4k table entry ++ * @RTL8365MB_VLAN_EGRESS_MODE_KEEP: the VLAN tag format of egressed packets ++ * will remain the same as their ingressed format, but the priority and VID ++ * fields may be altered ++ * @RTL8365MB_VLAN_EGRESS_MODE_PRI_TAG: always egress with priority tag ++ * @RTL8365MB_VLAN_EGRESS_MODE_REAL_KEEP: the VLAN tag format of egressed ++ * packets will remain the same as their ingressed format, and neither the ++ * priority nor VID fields can be altered ++ */ ++enum rtl8365mb_vlan_egress_mode { ++ RTL8365MB_VLAN_EGRESS_MODE_ORIGINAL = 0, ++ RTL8365MB_VLAN_EGRESS_MODE_KEEP = 1, ++ RTL8365MB_VLAN_EGRESS_MODE_PRI_TAG = 2, ++ RTL8365MB_VLAN_EGRESS_MODE_REAL_KEEP = 3, ++}; ++ ++/* VLAN control register */ ++#define RTL8365MB_VLAN_CTRL_REG 0x07A8 ++#define RTL8365MB_VLAN_CTRL_EN_MASK 0x0001 ++ ++/* VLAN ingress filter register */ ++#define RTL8365MB_VLAN_INGRESS_REG 0x07A9 ++#define RTL8365MB_VLAN_INGRESS_MASK GENMASK(10, 0) ++#define RTL8365MB_VLAN_INGRESS_FILTER_PORT_EN_OFFSET(_p) (_p) ++#define RTL8365MB_VLAN_INGRESS_FILTER_PORT_EN_MASK(_p) BIT(_p) ++ ++/* VLAN "transparent" setting registers */ ++#define RTL8365MB_VLAN_EGRESS_TRANSPARENT_REG_BASE 0x09D0 ++#define RTL8365MB_VLAN_EGRESS_TRANSPARENT_REG(_p) \ ++ (RTL8365MB_VLAN_EGRESS_TRANSPARENT_REG_BASE + (_p)) ++ + /* MIB counter value registers */ + #define RTL8365MB_MIB_COUNTER_BASE 0x1000 + #define RTL8365MB_MIB_COUNTER_REG(_x) (RTL8365MB_MIB_COUNTER_BASE + (_x)) +@@ -1210,6 +1262,286 @@ static void rtl8365mb_port_stp_state_set + val << RTL8365MB_MSTI_CTRL_PORT_STATE_OFFSET(port)); + } + ++static int rtl8365mb_port_set_transparent(struct realtek_priv *priv, ++ int igr_port, int egr_port, ++ bool enable) ++{ ++ dev_dbg(priv->dev, "%s transparent VLAN from %d to %d\n", ++ enable ? "Enable" : "Disable", igr_port, egr_port); ++ ++ /* "Transparent" between the two ports means that packets forwarded by ++ * igr_port and egressed on egr_port will not be filtered by the usual ++ * VLAN membership settings. ++ */ ++ return regmap_update_bits(priv->map, ++ RTL8365MB_VLAN_EGRESS_TRANSPARENT_REG(egr_port), ++ BIT(igr_port), enable ? BIT(igr_port) : 0); ++} ++ ++static int rtl8365mb_port_set_ingress_filtering(struct realtek_priv *priv, ++ int port, bool enable) ++{ ++ /* Ingress filtering enabled: Discard VLAN-tagged frames if the port is ++ * not a member of the VLAN with which the packet is associated. ++ * Untagged packets will also be discarded unless the port has a PVID ++ * programmed. Priority-tagged frames are treated as untagged frames. ++ * ++ * Ingress filtering disabled: Accept all tagged and untagged frames. ++ */ ++ return regmap_update_bits(priv->map, RTL8365MB_VLAN_INGRESS_REG, ++ RTL8365MB_VLAN_INGRESS_FILTER_PORT_EN_MASK(port), ++ enable ? ++ RTL8365MB_VLAN_INGRESS_FILTER_PORT_EN_MASK(port) : ++ 0); ++} ++ ++static int ++rtl8365mb_port_set_vlan_egress_mode(struct realtek_priv *priv, int port, ++ enum rtl8365mb_vlan_egress_mode mode) ++{ ++ u32 val; ++ ++ val = FIELD_PREP(RTL8365MB_PORT_MISC_CFG_VLAN_EGRESS_MODE_MASK, mode); ++ return regmap_update_bits(priv->map, ++ RTL8365MB_PORT_MISC_CFG_REG(port), ++ RTL8365MB_PORT_MISC_CFG_VLAN_EGRESS_MODE_MASK, val); ++} ++ ++static int rtl8365mb_port_vlan_filtering(struct dsa_switch *ds, int port, ++ bool vlan_filtering, ++ struct netlink_ext_ack *extack) ++{ ++ enum rtl8365mb_frame_ingress accepted_frame, prev_accepted_frame; ++ enum rtl8365mb_vlan_egress_mode mode; ++ struct realtek_priv *priv = ds->priv; ++ u32 configured_ports = 0; ++ struct dsa_port *dp; ++ u16 pvid_vid; ++ int ret; ++ ++ dev_dbg(priv->dev, "port %d: %s VLAN filtering\n", port, ++ vlan_filtering ? "enable" : "disable"); ++ ++ ret = rtl8365mb_vlan_port_get_framefilter(priv, port, ++ &prev_accepted_frame); ++ if (ret) { ++ NL_SET_ERR_MSG_MOD(extack, ++ "Failed to get current framefilter"); ++ return ret; ++ } ++ ++ /* While filtering, only accepts untagged frames if PVID is enabled */ ++ if (vlan_filtering) { ++ ret = rtl8365mb_vlan_port_get_pvid(priv, port, &pvid_vid); ++ if (ret) ++ return ret; ++ ++ if (pvid_vid) ++ accepted_frame = RTL8365MB_FRAME_TYPE_ANY_FRAME; ++ else ++ accepted_frame = RTL8365MB_FRAME_TYPE_TAGGED_ONLY; ++ } else { ++ accepted_frame = RTL8365MB_FRAME_TYPE_ANY_FRAME; ++ } ++ ++ /* When vlan filter is enable/disabled in a bridge, this function is ++ * called for all member ports. We need to enable/disable ingress ++ * VLAN membership check. ++ */ ++ ret = rtl8365mb_port_set_ingress_filtering(priv, port, vlan_filtering); ++ if (ret) ++ return ret; ++ ++ /* However, we also enable/disable egress filtering because the switch ++ * still consider the egress interface VLAN membership to forward the ++ * traffic. We enable/disable that check disabling/enabling transparent ++ * VLAN between the ingress port and all other available ports. ++ */ ++ dsa_switch_for_each_available_port(dp, ds) { ++ /* port isolation will still keep traffic inside the bridge */ ++ ret = rtl8365mb_port_set_transparent(priv, port, dp->index, ++ !vlan_filtering); ++ if (ret) ++ goto undo_transparent; ++ ++ configured_ports |= BIT(dp->index); ++ } ++ ++ if (accepted_frame != prev_accepted_frame) { ++ ret = rtl8365mb_vlan_port_set_framefilter(priv, port, ++ accepted_frame); ++ if (ret) { ++ NL_SET_ERR_MSG_MOD(extack, ++ "Failed to set port framefilter"); ++ goto undo_transparent; ++ } ++ } ++ ++ /* When VLAN filtering is disabled, preserve frames exactly as received. ++ * Otherwise, the VLAN egress pipeline may still alter tag state ++ * according to VLAN membership and untag configuration. ++ */ ++ if (vlan_filtering) ++ mode = RTL8365MB_VLAN_EGRESS_MODE_ORIGINAL; ++ else ++ mode = RTL8365MB_VLAN_EGRESS_MODE_REAL_KEEP; ++ ++ ret = rtl8365mb_port_set_vlan_egress_mode(priv, port, mode); ++ if (ret) ++ goto undo_set_framefilter; ++ ++ return ret; ++ ++undo_set_framefilter: ++ if (prev_accepted_frame != accepted_frame) ++ rtl8365mb_vlan_port_set_framefilter(priv, port, ++ prev_accepted_frame); ++undo_transparent: ++ /* The DSA core guarantees this callback is only invoked on an actual ++ * state transition, ensuring the previous hardware state was the ++ * opposite (!vlan_filtering). It is also called during setup but, in ++ * that case, any failure here aborts the entire switch initialization. ++ * ++ * VLAN_INGRESS and VLAN_EGRESS_TRANSPARENT states are directly derived ++ * from vlan_filtering. That way, we can simply undo it without ++ * checking the current HW state as we do with VLAN_EGRESS_MODE. ++ */ ++ dsa_switch_for_each_port(dp, ds) { ++ if (configured_ports & BIT(dp->index)) ++ rtl8365mb_port_set_transparent(priv, port, dp->index, ++ vlan_filtering); ++ } ++ ++ rtl8365mb_port_set_ingress_filtering(priv, port, !vlan_filtering); ++ ++ return ret; ++} ++ ++static int rtl8365mb_port_vlan_add(struct dsa_switch *ds, int port, ++ const struct switchdev_obj_port_vlan *vlan, ++ struct netlink_ext_ack *extack) ++{ ++ bool untagged = !!(vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED); ++ bool pvid = !!(vlan->flags & BRIDGE_VLAN_INFO_PVID); ++ u16 pvid_vid; ++ struct realtek_priv *priv = ds->priv; ++ int ret; ++ ++ dev_dbg(priv->dev, "add VLAN %d on port %d, %s, %s\n", ++ vlan->vid, port, untagged ? "untagged" : "tagged", ++ pvid ? "PVID" : "no PVID"); ++ ++ /* VID == 0 is reserved in this driver */ ++ if (vlan->vid == 0) { ++ NL_SET_ERR_MSG_MOD(extack, ++ "VLAN 0 is reserved by this driver"); ++ return -EOPNOTSUPP; ++ } ++ ++ mutex_lock(&priv->vlan_lock); ++ ++ ret = rtl8365mb_vlan_port_get_pvid(priv, port, &pvid_vid); ++ if (ret) ++ goto out_unlock; ++ ++ /* Set PVID if needed */ ++ if (pvid) { ++ ret = rtl8365mb_vlan_pvid_port_set(ds, port, vlan->vid, ++ extack); ++ if (ret) ++ goto out_unlock; ++ } else { ++ /* or try to unset it if not */ ++ ret = rtl8365mb_vlan_pvid_port_clear(ds, port, vlan->vid); ++ if (ret) ++ goto out_unlock; ++ } ++ ++ /* add port to vlan4k. It knows nothing about PVID */ ++ ret = rtl8365mb_vlan_4k_port_add(ds, port, vlan, extack); ++ if (ret) ++ goto undo_set_pvid; ++ ++ ret = 0; ++ goto out_unlock; ++ ++undo_set_pvid: ++ /* undo the pvid definition */ ++ if (pvid != (pvid_vid == vlan->vid)) { ++ if (pvid_vid) ++ (void)rtl8365mb_vlan_pvid_port_set(ds, port, pvid_vid, ++ NULL); ++ else ++ (void)rtl8365mb_vlan_pvid_port_clear(ds, port, ++ vlan->vid); ++ } ++out_unlock: ++ mutex_unlock(&priv->vlan_lock); ++ return ret; ++} ++ ++static int rtl8365mb_port_vlan_del(struct dsa_switch *ds, int port, ++ const struct switchdev_obj_port_vlan *vlan) ++{ ++ bool untagged = !!(vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED); ++ bool pvid = !!(vlan->flags & BRIDGE_VLAN_INFO_PVID); ++ struct realtek_priv *priv = ds->priv; ++ int ret; ++ ++ dev_dbg(priv->dev, "del VLAN %d on port %d, %s, %s\n", ++ vlan->vid, port, untagged ? "untagged" : "tagged", ++ pvid ? "PVID" : "no PVID"); ++ ++ /* VID == 0 is reserved in this driver */ ++ if (vlan->vid == 0) ++ return -EOPNOTSUPP; ++ ++ mutex_lock(&priv->vlan_lock); ++ ret = rtl8365mb_vlan_pvid_port_clear(ds, port, vlan->vid); ++ if (ret) ++ goto out_unlock; ++ ++ ret = rtl8365mb_vlan_4k_port_del(ds, port, vlan); ++ /* There is little incentive to try to undo the removal of PVID (if it ++ * was really in use) as an error here might indicate the ASIC stopped ++ * to answer. ++ */ ++ ++out_unlock: ++ mutex_unlock(&priv->vlan_lock); ++ return ret; ++} ++ ++/* VLAN support is always enabled in the switch. ++ * ++ * Standalone forwarding relies on transparent VLAN mode combined with per-port ++ * isolation masks restricting egress to CPU ports only. ++ * ++ */ ++static int rtl8365mb_vlan_setup(struct dsa_switch *ds) ++{ ++ struct realtek_priv *priv = ds->priv; ++ struct dsa_port *dp; ++ int ret; ++ ++ dsa_switch_for_each_available_port(dp, ds) { ++ /* Disable vlan-filtering for all ports */ ++ ret = rtl8365mb_port_vlan_filtering(ds, dp->index, false, NULL); ++ if (ret) { ++ dev_err(priv->dev, ++ "Failed to disable vlan filtering on port %d\n", ++ dp->index); ++ return ret; ++ } ++ } ++ ++ /* VLAN is always enabled. */ ++ ret = regmap_update_bits(priv->map, RTL8365MB_VLAN_CTRL_REG, ++ RTL8365MB_VLAN_CTRL_EN_MASK, ++ FIELD_PREP(RTL8365MB_VLAN_CTRL_EN_MASK, 1)); ++ return ret; ++} ++ + static int rtl8365mb_port_set_learning(struct realtek_priv *priv, int port, + bool enable) + { +@@ -2100,6 +2432,13 @@ static int rtl8365mb_setup(struct dsa_sw + if (ret) + goto out_teardown_irq; + ++ ds->configure_vlan_while_not_filtering = true; ++ ++ /* Set up VLAN */ ++ ret = rtl8365mb_vlan_setup(ds); ++ if (ret) ++ goto out_teardown_irq; ++ + ret = rtl83xx_setup_user_mdio(ds); + if (ret) { + dev_err(priv->dev, "could not set up MDIO bus\n"); +@@ -2210,6 +2549,9 @@ static const struct dsa_switch_ops rtl83 + .teardown = rtl8365mb_teardown, + .phylink_get_caps = rtl8365mb_phylink_get_caps, + .port_stp_state_set = rtl8365mb_port_stp_state_set, ++ .port_vlan_add = rtl8365mb_port_vlan_add, ++ .port_vlan_del = rtl8365mb_port_vlan_del, ++ .port_vlan_filtering = rtl8365mb_port_vlan_filtering, + .get_strings = rtl8365mb_get_strings, + .get_ethtool_stats = rtl8365mb_get_ethtool_stats, + .get_sset_count = rtl8365mb_get_sset_count, +--- /dev/null ++++ b/drivers/net/dsa/realtek/rtl8365mb_vlan.c +@@ -0,0 +1,944 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* VLAN configuration interface for the rtl8365mb switch family ++ * ++ * Copyright (C) 2022 Alvin Å ipraga ++ * ++ * VLAN configuration takes place in two separate domains of the switch: the ++ * VLAN4k table and the VLAN membership configuration (MC) database. While the ++ * VLAN4k table is exhaustive and can be fully populated with 4096 VLAN ++ * configurations, the same does not hold for the VLAN membership configuration ++ * database, which is limited to 32 entries. ++ * ++ * The switch will normally only use the VLAN4k table when making forwarding ++ * decisions. The VLAN membership configuration database is a vestigial ASIC ++ * design and is only used for a few specific features in the rtl8365mb ++ * family. This means that the limit of 32 entries should not hinder us in ++ * programming a huge number of VLANs into the switch. ++ * ++ * One necessary use of the VLAN membership configuration database is for the ++ * programming of a port-based VLAN ID (PVID). The PVID is programmed on a ++ * per-port basis via register field, which refers to a specific VLAN membership ++ * configuration via an index 0~31. In order to maintain coherent behaviour on a ++ * port with a PVID, it is necessary to keep the VLAN configuration synchronized ++ * between the VLAN4k table and the VLAN membership configuration database. ++ * ++ * Since VLAN membership configs are a scarce resource, it will only be used ++ * when strictly needed (i.e. a VLAN with members using PVID). Otherwise, the ++ * VLAN4k will be enough. ++ * ++ * With some exceptions, the entries in both the VLAN4k table and the VLAN ++ * membership configuration database offer the same configuration options. The ++ * differences are as follows: ++ * ++ * 1. VLAN4k entries can specify whether to use Independent or Shared VLAN ++ * Learning (IVL or SVL respectively). VLAN membership config entries ++ * cannot. This underscores the fact that VLAN membership configs are not ++ * involved in the learning process of the ASIC. ++ * ++ * 2. VLAN membership config entries use an "enhanced VLAN ID" (efid), which has ++ * a range 0~8191 compared with the standard 0~4095 range of the VLAN4k ++ * table. This underscores the fact that VLAN membership configs can be used ++ * to group ports on a layer beyond the standard VLAN configuration, which ++ * may be useful for ACL rules which specify alternative forwarding ++ * decisions. ++ * ++ * VLANMC index 0 is reserved as a neutral PVID, used for standalone ports. ++ * ++ */ ++ ++#include "rtl8365mb_vlan.h" ++#include "rtl8365mb_table.h" ++#include ++#include ++#include ++ ++/* CVLAN (i.e. VLAN4k) table entry layout, u16[3] */ ++#define RTL8365MB_CVLAN_ENTRY_SIZE 3 /* 48-bits */ ++#define RTL8365MB_CVLAN_ENTRY_D0_MBR_MASK GENMASK(7, 0) ++#define RTL8365MB_CVLAN_MBR_LO_MASK GENMASK(7, 0) ++#define RTL8365MB_CVLAN_ENTRY_D0_UNTAG_MASK GENMASK(15, 8) ++#define RTL8365MB_CVLAN_UNTAG_LO_MASK GENMASK(7, 0) ++#define RTL8365MB_CVLAN_ENTRY_D1_FID_MASK GENMASK(3, 0) ++#define RTL8365MB_CVLAN_ENTRY_D1_VBPEN_MASK GENMASK(4, 4) ++#define RTL8365MB_CVLAN_ENTRY_D1_VBPRI_MASK GENMASK(7, 5) ++#define RTL8365MB_CVLAN_ENTRY_D1_ENVLANPOL_MASK GENMASK(8, 8) ++#define RTL8365MB_CVLAN_ENTRY_D1_METERIDX_MASK GENMASK(13, 9) ++#define RTL8365MB_CVLAN_METERIDX_LO_MASK GENMASK(4, 0) ++#define RTL8365MB_CVLAN_ENTRY_D1_IVL_SVL_MASK GENMASK(14, 14) ++/* extends RTL8365MB_CVLAN_ENTRY_D0_MBR_MASK */ ++#define RTL8365MB_CVLAN_ENTRY_D2_MBR_EXT_MASK GENMASK(2, 0) ++#define RTL8365MB_CVLAN_MBR_HI_MASK GENMASK(10, 8) ++/* extends RTL8365MB_CVLAN_ENTRY_D0_UNTAG_MASK */ ++#define RTL8365MB_CVLAN_ENTRY_D2_UNTAG_EXT_MASK GENMASK(5, 3) ++#define RTL8365MB_CVLAN_UNTAG_HI_MASK GENMASK(10, 8) ++/* extends RTL8365MB_CVLAN_ENTRY_D1_METERIDX_MASK */ ++#define RTL8365MB_CVLAN_ENTRY_D2_METERIDX_EXT_MASK GENMASK(6, 6) ++#define RTL8365MB_CVLAN_METERIDX_HI_MASK GENMASK(5, 5) ++ ++/* VLAN member configuration registers 0~31, u16[3] */ ++#define RTL8365MB_VLAN_MC_BASE 0x0728 ++#define RTL8365MB_VLAN_MC_ENTRY_SIZE 4 /* 64-bit */ ++#define RTL8365MB_VLAN_MC_REG(index) \ ++ (RTL8365MB_VLAN_MC_BASE + \ ++ (RTL8365MB_VLAN_MC_ENTRY_SIZE * (index))) ++#define RTL8365MB_VLAN_MC_D0_MBR_MASK GENMASK(10, 0) ++#define RTL8365MB_VLAN_MC_D1_FID_MASK GENMASK(3, 0) ++ ++#define RTL8365MB_VLAN_MC_D2_VBPEN_MASK GENMASK(0, 0) ++#define RTL8365MB_VLAN_MC_D2_VBPRI_MASK GENMASK(3, 1) ++#define RTL8365MB_VLAN_MC_D2_ENVLANPOL_MASK GENMASK(4, 4) ++#define RTL8365MB_VLAN_MC_D2_METERIDX_MASK GENMASK(10, 5) ++#define RTL8365MB_VLAN_MC_D3_EVID_MASK GENMASK(12, 0) ++ ++/* Some limits for VLAN4k/VLAN membership config entries */ ++#define RTL8365MB_PRIORITYMAX 7 ++#define RTL8365MB_FIDMAX 15 ++#define RTL8365MB_METERMAX 63 ++#define RTL8365MB_VLAN_MCMAX 31 ++ ++/* RTL8367S supports 4k vlans (vid<=4095) and 32 enhanced vlans ++ * for VIDs up to 8191 ++ */ ++#define RTL8365MB_MAX_4K_VID 0x0FFF /* 4095 */ ++#define RTL8365MB_MAX_MC_VID 0x1FFF /* 8191 */ ++ ++ /* Port-based VID registers 0~5 - each one holds an MC index for two ports */ ++#define RTL8365MB_VLAN_PVID_CTRL_BASE 0x0700 ++#define RTL8365MB_VLAN_PVID_CTRL_REG(_p) \ ++ (RTL8365MB_VLAN_PVID_CTRL_BASE + ((_p) >> 1)) ++#define RTL8365MB_VLAN_PVID_CTRL_PORT0_MCIDX_MASK 0x001F ++#define RTL8365MB_VLAN_PVID_CTRL_PORT1_MCIDX_MASK 0x1F00 ++#define RTL8365MB_VLAN_PVID_CTRL_PORT_MCIDX_OFFSET(_p) \ ++ (((_p) & 1) << 3) ++#define RTL8365MB_VLAN_PVID_CTRL_PORT_MCIDX_MASK(_p) \ ++ (0x1F << RTL8365MB_VLAN_PVID_CTRL_PORT_MCIDX_OFFSET(_p)) ++ ++/* Frame type filtering registers */ ++#define RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_BASE 0x07aa ++#define RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_REG(port) \ ++ (RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_BASE + ((port) >> 3)) ++/* required as FIELD_PREP cannot use non-constant masks */ ++#define RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_MASK(port) \ ++ (0x3 << RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_OFFSET(port)) ++#define RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_OFFSET(port) \ ++ (((port) & 0x7) << 1) ++ ++/* ++ * struct rtl8365mb_vlan4k - VLAN4k table entry ++ * @vid: VLAN ID (0~4095) ++ * @member: port mask of ports in this VLAN ++ * @untag: port mask of ports which untag on egress ++ * @fid: filter ID - only used with SVL (unused) ++ * @priority: priority classification (unused) ++ * @priority_en: enable priority (unused) ++ * @policing_en: enable policing (unused) ++ * @ivl_en: enable IVL instead of default SVL ++ * @meteridx: metering index (unused) ++ * ++ * This structure is used to get/set entries in the VLAN4k table. The ++ * VLAN4k table dictates the VLAN configuration for the switch for the ++ * vast majority of features. ++ */ ++struct rtl8365mb_vlan4k { ++ u16 vid; ++ u16 member; ++ u16 untag; ++ u8 fid : 4; ++ u8 priority : 3; ++ u8 priority_en : 1; ++ u8 policing_en : 1; ++ u8 ivl_en : 1; ++ u8 meteridx : 6; ++}; ++ ++/* ++ * struct rtl8365mb_vlanmc - VLAN membership config ++ * @evid: Enhanced VLAN ID (0~8191) ++ * @member: port mask of ports in this VLAN ++ * @fid: filter ID - only used with SVL (unused) ++ * @priority: priority classification (unused) ++ * @priority_en: enable priority (unused) ++ * @policing_en: enable policing (unused) ++ * @meteridx: metering index (unused) ++ * ++ * This structure is used to get/set entries in the VLAN membership ++ * configuration database. This feature is largely vestigial, but ++ * still needed for at least the following features: ++ * - PVID configuration ++ * - ACL configuration ++ * - selection of VLAN by the CPU tag when VSEL=1, although the switch ++ * can also select VLAN based on the VLAN tag if VSEL=0 ++ * ++ * This is a low-level structure and it is recommended to interface with ++ * the VLAN membership config database via &struct rtl8365mb_vlanmc_entry. ++ */ ++struct rtl8365mb_vlanmc { ++ u16 evid; ++ u16 member; ++ u8 fid : 4; ++ u8 priority : 3; ++ u8 priority_en : 1; ++ u8 policing_en : 1; ++ u8 meteridx : 6; ++}; ++ ++static int rtl8365mb_vlan_4k_read(struct realtek_priv *priv, u16 vid, ++ struct rtl8365mb_vlan4k *vlan4k) ++{ ++ u16 data[RTL8365MB_CVLAN_ENTRY_SIZE]; ++ int val; ++ int ret; ++ ++ ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_CVLAN, ++ RTL8365MB_TABLE_OP_READ, &vid, 0, 0, ++ data, ARRAY_SIZE(data)); ++ if (ret) ++ return ret; ++ ++ /* Unpack table entry */ ++ memset(vlan4k, 0, sizeof(*vlan4k)); ++ vlan4k->vid = vid; ++ ++ val = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D0_MBR_MASK, data[0]); ++ vlan4k->member = FIELD_PREP(RTL8365MB_CVLAN_MBR_LO_MASK, val); ++ val = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D2_MBR_EXT_MASK, data[2]); ++ vlan4k->member |= FIELD_PREP(RTL8365MB_CVLAN_MBR_HI_MASK, val); ++ ++ val = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D0_UNTAG_MASK, data[0]); ++ vlan4k->untag = FIELD_PREP(RTL8365MB_CVLAN_UNTAG_LO_MASK, val); ++ val = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D2_UNTAG_EXT_MASK, data[2]); ++ vlan4k->untag |= FIELD_PREP(RTL8365MB_CVLAN_UNTAG_HI_MASK, val); ++ ++ vlan4k->fid = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_FID_MASK, data[1]); ++ vlan4k->priority_en = ++ FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_VBPEN_MASK, data[1]); ++ vlan4k->priority = ++ FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_VBPRI_MASK, data[1]); ++ vlan4k->policing_en = ++ FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_ENVLANPOL_MASK, data[1]); ++ ++ val = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_METERIDX_MASK, data[1]); ++ val = FIELD_PREP(RTL8365MB_CVLAN_METERIDX_LO_MASK, val); ++ vlan4k->meteridx = val; ++ val = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D2_METERIDX_EXT_MASK, data[2]); ++ val = FIELD_PREP(RTL8365MB_CVLAN_METERIDX_HI_MASK, val); ++ vlan4k->meteridx |= val; ++ ++ vlan4k->ivl_en = ++ FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_IVL_SVL_MASK, data[1]); ++ ++ return 0; ++} ++ ++static int rtl8365mb_vlan_4k_write(struct realtek_priv *priv, ++ const struct rtl8365mb_vlan4k *vlan4k) ++{ ++ u16 data[RTL8365MB_CVLAN_ENTRY_SIZE] = { 0 }; ++ u16 vid; ++ int val; ++ ++ /* Pack table entry value */ ++ val = FIELD_GET(RTL8365MB_CVLAN_MBR_LO_MASK, vlan4k->member); ++ data[0] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D0_MBR_MASK, val); ++ ++ val = FIELD_GET(RTL8365MB_CVLAN_UNTAG_LO_MASK, vlan4k->untag); ++ data[0] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D0_UNTAG_MASK, val); ++ ++ data[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_FID_MASK, vlan4k->fid); ++ data[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_VBPEN_MASK, ++ vlan4k->priority_en); ++ data[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_VBPRI_MASK, ++ vlan4k->priority); ++ data[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_ENVLANPOL_MASK, ++ vlan4k->policing_en); ++ ++ /* FIELD_* does not play nice with struct bitfield. */ ++ val = vlan4k->meteridx; ++ val = FIELD_GET(RTL8365MB_CVLAN_METERIDX_LO_MASK, val); ++ data[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_METERIDX_MASK, val); ++ ++ data[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_IVL_SVL_MASK, ++ vlan4k->ivl_en); ++ ++ val = FIELD_GET(RTL8365MB_CVLAN_MBR_HI_MASK, vlan4k->member); ++ data[2] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D2_MBR_EXT_MASK, val); ++ ++ val = FIELD_GET(RTL8365MB_CVLAN_UNTAG_HI_MASK, vlan4k->untag); ++ data[2] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D2_UNTAG_EXT_MASK, val); ++ ++ val = vlan4k->meteridx; ++ val = FIELD_GET(RTL8365MB_CVLAN_METERIDX_HI_MASK, val); ++ data[2] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D2_METERIDX_EXT_MASK, val); ++ ++ vid = vlan4k->vid; ++ return rtl8365mb_table_query(priv, RTL8365MB_TABLE_CVLAN, ++ RTL8365MB_TABLE_OP_WRITE, &vid, 0, 0, ++ data, ARRAY_SIZE(data)); ++} ++ ++static int ++rtl8365mb_vlan_4k_port_set(struct dsa_switch *ds, int port, ++ const struct switchdev_obj_port_vlan *vlan, ++ struct netlink_ext_ack *extack, ++ bool include) ++{ ++ struct realtek_priv *priv = ds->priv; ++ struct rtl8365mb_vlan4k vlan4k = {0}; ++ int ret; ++ ++ dev_dbg(priv->dev, "%s VLAN %d 4K on port %d\n", ++ include ? "add" : "del", ++ vlan->vid, port); ++ ++ if (vlan->vid > RTL8365MB_MAX_4K_VID) { ++ NL_SET_ERR_MSG_MOD(extack, "VLAN ID greater than " ++ __stringify(RTL8365MB_MAX_4K_VID)); ++ return -EINVAL; ++ } ++ ++ ret = rtl8365mb_vlan_4k_read(priv, vlan->vid, &vlan4k); ++ if (ret) { ++ dev_err(priv->dev, "Failed to read VLAN 4k table\n"); ++ return ret; ++ } ++ ++ if (include) ++ vlan4k.member |= BIT(port); ++ else ++ vlan4k.member &= ~BIT(port); ++ ++ if (include && (vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED)) ++ vlan4k.untag |= BIT(port); ++ else ++ vlan4k.untag &= ~BIT(port); ++ vlan4k.ivl_en = true; /* always use Independent VLAN Learning */ ++ ++ ret = rtl8365mb_vlan_4k_write(priv, &vlan4k); ++ if (ret) { ++ dev_err(priv->dev, "Failed to write VLAN 4k table\n"); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++/* ++ * rtl8365mb_vlan_4k_port_add() - Add a port to a VLAN 4K table entry ++ * @ds: dsa switch instance ++ * @port: port index ++ * @vlan: switchdev VLAN object containing the target VID and flags ++ * @extack: netlink extended ACK for error reporting ++ * ++ * Adds the specified port to the hardware VLAN 4K membership table. ++ * ++ * Context: Can sleep. Must be called with &priv->vlan_lock held. ++ * Takes and releases &priv->map_lock. ++ * Return: 0 on success, or a negative error code on failure. ++ */ ++int rtl8365mb_vlan_4k_port_add(struct dsa_switch *ds, int port, ++ const struct switchdev_obj_port_vlan *vlan, ++ struct netlink_ext_ack *extack) ++{ ++ struct realtek_priv *priv = ds->priv; ++ ++ lockdep_assert_held(&priv->vlan_lock); ++ ++ return rtl8365mb_vlan_4k_port_set(ds, port, vlan, extack, true); ++} ++ ++/* ++ * rtl8365mb_vlan_4k_port_del() - Remove a port from a VLAN 4K table entry ++ * @ds: dsa switch instance ++ * @port: port index ++ * @vlan: switchdev VLAN object containing the target VID ++ * ++ * Removes the specified port from the hardware VLAN 4K membership table. ++ * ++ * Context: Can sleep. Must be called with &priv->vlan_lock held. ++ * Takes and releases &priv->map_lock. ++ * Return: 0 on success, or a negative error code on failure. ++ */ ++int rtl8365mb_vlan_4k_port_del(struct dsa_switch *ds, int port, ++ const struct switchdev_obj_port_vlan *vlan) ++{ ++ struct realtek_priv *priv = ds->priv; ++ ++ lockdep_assert_held(&priv->vlan_lock); ++ ++ return rtl8365mb_vlan_4k_port_set(ds, port, vlan, NULL, false); ++} ++ ++static int rtl8365mb_vlan_mc_read(struct realtek_priv *priv, u32 index, ++ struct rtl8365mb_vlanmc *vlanmc) ++{ ++ u16 data[RTL8365MB_VLAN_MC_ENTRY_SIZE]; ++ int ret; ++ ++ ret = regmap_bulk_read(priv->map, RTL8365MB_VLAN_MC_REG(index), &data, ++ RTL8365MB_VLAN_MC_ENTRY_SIZE); ++ if (ret) ++ return ret; ++ ++ vlanmc->member = FIELD_GET(RTL8365MB_VLAN_MC_D0_MBR_MASK, data[0]); ++ vlanmc->fid = FIELD_GET(RTL8365MB_VLAN_MC_D1_FID_MASK, data[1]); ++ vlanmc->meteridx = FIELD_GET(RTL8365MB_VLAN_MC_D2_METERIDX_MASK, ++ data[2]); ++ vlanmc->policing_en = FIELD_GET(RTL8365MB_VLAN_MC_D2_ENVLANPOL_MASK, ++ data[2]); ++ vlanmc->priority = FIELD_GET(RTL8365MB_VLAN_MC_D2_VBPRI_MASK, data[2]); ++ vlanmc->priority_en = FIELD_GET(RTL8365MB_VLAN_MC_D2_VBPEN_MASK, ++ data[2]); ++ vlanmc->evid = FIELD_GET(RTL8365MB_VLAN_MC_D3_EVID_MASK, data[3]); ++ ++ return 0; ++} ++ ++static int rtl8365mb_vlan_mc_write(struct realtek_priv *priv, u32 index, ++ const struct rtl8365mb_vlanmc *vlanmc) ++{ ++ u16 data[RTL8365MB_VLAN_MC_ENTRY_SIZE] = { 0 }; ++ int ret; ++ ++ data[0] |= FIELD_PREP(RTL8365MB_VLAN_MC_D0_MBR_MASK, vlanmc->member); ++ data[1] |= FIELD_PREP(RTL8365MB_VLAN_MC_D1_FID_MASK, vlanmc->fid); ++ data[2] |= FIELD_PREP(RTL8365MB_VLAN_MC_D2_METERIDX_MASK, ++ vlanmc->meteridx); ++ data[2] |= FIELD_PREP(RTL8365MB_VLAN_MC_D2_ENVLANPOL_MASK, ++ vlanmc->policing_en); ++ data[2] |= ++ FIELD_PREP(RTL8365MB_VLAN_MC_D2_VBPRI_MASK, vlanmc->priority); ++ data[2] |= FIELD_PREP(RTL8365MB_VLAN_MC_D2_VBPEN_MASK, ++ vlanmc->priority_en); ++ data[3] |= FIELD_PREP(RTL8365MB_VLAN_MC_D3_EVID_MASK, vlanmc->evid); ++ ++ ret = regmap_bulk_write(priv->map, RTL8365MB_VLAN_MC_REG(index), &data, ++ RTL8365MB_VLAN_MC_ENTRY_SIZE); ++ ++ return ret; ++} ++ ++static int rtl8365mb_vlan_mc_erase(struct realtek_priv *priv, u32 index) ++{ ++ u16 data[RTL8365MB_VLAN_MC_ENTRY_SIZE] = { 0 }; ++ int ret; ++ ++ ret = regmap_bulk_write(priv->map, RTL8365MB_VLAN_MC_REG(index), &data, ++ RTL8365MB_VLAN_MC_ENTRY_SIZE); ++ ++ return ret; ++} ++ ++/* ++ * rtl8365mb_vlan_mc_find() - find VLANMC index by VID or the first free index ++ * ++ * @priv: realtek_priv pointer ++ * @vid: VLAN ID ++ * @index: found index ++ * @first_free: found free index ++ * ++ * If a VLAN MC entry using @vid was found, @index will return the matched index ++ * and @first_free is undefined. If not found, @index will return 0 and ++ * @first_free will return the first found free index in VLAN MC or 0 if the ++ * table is full. ++ * ++ * Although 0 is a valid VLAN MC index, it is reserved for ports without PVID, ++ * including standalone, non-member ports. It uses VID == 0. ++ * ++ * Both @index and @first_free will be in the * 1..@RTL8365MB_VLAN_MCMAX range. ++ * ++ * Return: Returns 0 on success, a negative error on failure. ++ */ ++static int rtl8365mb_vlan_mc_find(struct realtek_priv *priv, u16 vid, ++ u8 *index, u8 *first_free) ++{ ++ u32 vlan_entry_d3; ++ u8 vlanmc_idx; ++ u16 evid; ++ int ret; ++ ++ *index = 0; ++ *first_free = 0; ++ ++ /* look for existing entry or an empty one */ ++ /* By design, VlanMC[0] is reserved as a neutral PVID value for ++ * standalone ports. It always has EVID == 0. That way, we assume that ++ * all entries after index 0 with VID == 0 are empty. ++ **/ ++ for (vlanmc_idx = 1; vlanmc_idx <= RTL8365MB_VLAN_MCMAX; vlanmc_idx++) { ++ /* just read the 4th word, where the evid is */ ++ ret = regmap_read(priv->map, ++ RTL8365MB_VLAN_MC_REG(vlanmc_idx) + 3, ++ &vlan_entry_d3); ++ if (ret) ++ return ret; ++ ++ evid = FIELD_GET(RTL8365MB_VLAN_MC_D3_EVID_MASK, vlan_entry_d3); ++ ++ if (evid == vid) { ++ *index = vlanmc_idx; ++ return 0; ++ } ++ ++ if (evid == 0x0 && *first_free < 1) ++ *first_free = vlanmc_idx; ++ } ++ return 0; ++} ++ ++static int rtl8365mb_vlan_port_get_pvid_idx(struct realtek_priv *priv, ++ int port, u8 *vlanmc_idx) ++{ ++ u32 data; ++ int ret; ++ ++ ret = regmap_read(priv->map, RTL8365MB_VLAN_PVID_CTRL_REG(port), &data); ++ if (ret) ++ return ret; ++ ++ *vlanmc_idx = (data & RTL8365MB_VLAN_PVID_CTRL_PORT_MCIDX_MASK(port)) ++ >> RTL8365MB_VLAN_PVID_CTRL_PORT_MCIDX_OFFSET(port); ++ ++ return 0; ++} ++ ++/* ++ * rtl8365mb_vlan_mc_port_set() - include or exclude a port from VlanMC ++ * @ds: dsa switch ++ * @port: the port number ++ * @vid: the vlan VID to include/exclude @port ++ * @pvid: inform if vid is used as pvid in @port ++ * @extack: optional extack to return errors ++ * @include: whether to include or exclude @port ++ * ++ * This function is used to include/exclude ports to the VlanMC table. ++ * ++ * VlanMC stands for VLAN membership config and it is used exclusively for ++ * PVID. If @vlan members are not using PVID, this function will either ++ * remove or not create a new VlanMC entry. ++ * ++ * VlanMC members are used as a reference port map, cleaning the entry once ++ * no port is using it. ++ * ++ * Port PVID and accepted frame type are updated as well. ++ * ++ * Context: Can sleep. Must be called with &priv->vlan_lock held. ++ * Takes and releases &priv->map_lock. ++ * Return: Returns 0 on success, a negative error on failure. ++ */ ++static ++int rtl8365mb_vlan_mc_port_set(struct dsa_switch *ds, int port, ++ u16 vid, bool pvid, ++ struct netlink_ext_ack *extack, ++ bool include) ++{ ++ struct realtek_priv *priv = ds->priv; ++ struct rtl8365mb_vlanmc vlanmc = {0}; ++ u8 first_unused = 0; ++ u8 vlanmc_idx = 0; ++ int ret; ++ ++ dev_dbg(priv->dev, "%s VLAN %d MC on port %d\n", ++ include ? "add" : "del", ++ vid, port); ++ ++ if (vid > RTL8365MB_MAX_MC_VID) { ++ NL_SET_ERR_MSG_MOD(extack, "VLAN ID greater than " ++ __stringify(RTL8365MB_MAX_MC_VID)); ++ return -EINVAL; ++ } ++ ++ /* look for existing entry or an empty slot */ ++ ret = rtl8365mb_vlan_mc_find(priv, vid, &vlanmc_idx, ++ &first_unused); ++ if (ret) { ++ dev_err(priv->dev, "Failed to find a VLAN MC table index\n"); ++ return ret; ++ } ++ ++ if (vlanmc_idx) { ++ ret = rtl8365mb_vlan_mc_read(priv, vlanmc_idx, &vlanmc); ++ if (ret) { ++ dev_err(priv->dev, "Failed to read VLAN MC table\n"); ++ return ret; ++ } ++ } else if (include) { ++ /* for now, vlan_mc is only required for PVID. Defer allocation ++ * until at least one port uses PVID. ++ */ ++ if (!pvid) { ++ dev_dbg(priv->dev, ++ "Not creating VlanMC for vlan %d until a port uses PVID (%d does not)\n", ++ vid, port); ++ return 0; ++ } ++ ++ if (!first_unused) { ++ NL_SET_ERR_MSG_MOD(extack, "All VLAN MC entries (0.." ++ __stringify(RTL8365MB_VLAN_MCMAX) ++ ") are in use."); ++ return -ENOSPC; ++ } ++ ++ vlanmc_idx = first_unused; ++ vlanmc.evid = vid; ++ ++ } else /* excluding and VLANMC not found */ { ++ return 0; ++ } ++ ++ dev_dbg(priv->dev, ++ "VLAN %d (idx: %d) PVID curr members: %08x\n", ++ vid, vlanmc_idx, vlanmc.member); ++ ++ /* here we either have an existing VLANMC (with PVID members) or the ++ * added port is using this VLAN as PVID ++ */ ++ if (include) ++ vlanmc.member |= BIT(port); ++ else ++ vlanmc.member &= ~BIT(port); ++ ++ /* just like we don't need to create a VLAN_MC when there is no port ++ * using it as PVID, we can erase it when there is no more port using ++ * it as PVID. ++ */ ++ if (!vlanmc.member) { ++ dev_dbg(priv->dev, ++ "Clearing VlanMC index %d previously used by VID %d\n", ++ vlanmc_idx, vid); ++ ret = rtl8365mb_vlan_mc_erase(priv, vlanmc_idx); ++ } else { ++ dev_dbg(priv->dev, ++ "Saving VlanMC index %d with VID %d\n", ++ vlanmc_idx, vid); ++ ret = rtl8365mb_vlan_mc_write(priv, vlanmc_idx, &vlanmc); ++ } ++ if (ret) { ++ dev_err(priv->dev, "Failed to write vlan MC entry\n"); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int rtl8365mb_vlan_port_set_pvid(struct realtek_priv *priv, ++ int port, u16 vlanmc_idx) ++{ ++ int ret; ++ u32 val; ++ ++ dev_dbg(priv->dev, "set PVID IDX %d on port %d\n", vlanmc_idx, port); ++ ++ val = vlanmc_idx << RTL8365MB_VLAN_PVID_CTRL_PORT_MCIDX_OFFSET(port); ++ ret = regmap_update_bits(priv->map, ++ RTL8365MB_VLAN_PVID_CTRL_REG(port), ++ RTL8365MB_VLAN_PVID_CTRL_PORT_MCIDX_MASK(port), ++ val); ++ if (ret) ++ return ret; ++ ++ return 0; ++} ++ ++static int rtl8365mb_vlan_get_pvid_mc(struct realtek_priv *priv, ++ int port, u8 *vlanmc_idx, ++ struct rtl8365mb_vlanmc *vlanmc) ++{ ++ int ret; ++ ++ ret = rtl8365mb_vlan_port_get_pvid_idx(priv, port, vlanmc_idx); ++ if (ret) ++ return ret; ++ ++ memset(vlanmc, 0, sizeof(*vlanmc)); ++ ++ if (!*vlanmc_idx) ++ return 0; ++ ++ ret = rtl8365mb_vlan_mc_read(priv, *vlanmc_idx, vlanmc); ++ if (ret) ++ return ret; ++ ++ return 0; ++} ++ ++/* ++ * rtl8365mb_vlan_port_get_pvid - Retrieve the port PVID ++ * @priv: realtek switch private structure ++ * @port: port index ++ * @pvid: pointer to store the retrieved VLAN ID ++ * ++ * Returns the port PVID if defined or 0 if not. ++ * ++ * Context: Can sleep. Takes and releases &priv->map_lock. ++ * Return: 0 on success or a negative error code on failure. ++ */ ++int rtl8365mb_vlan_port_get_pvid(struct realtek_priv *priv, int port, u16 *pvid) ++{ ++ struct rtl8365mb_vlanmc vlanmc; ++ u8 vlanmc_idx; ++ int ret; ++ ++ ret = rtl8365mb_vlan_get_pvid_mc(priv, port, &vlanmc_idx, &vlanmc); ++ if (ret) ++ return ret; ++ ++ *pvid = vlanmc.evid; ++ return 0; ++} ++ ++/* ++ * rtl8365mb_vlan_port_get_framefilter() - Get the ingress frame filtering mode ++ * for a port ++ * @priv: realtek switch private structure ++ * @port: port index ++ * @frame_type: pointer to store the retrieved ingress frame filter type ++ * ++ * Context: Can sleep. Takes and releases &priv->map_lock. ++ * Return: 0 on success, or a negative error code on failure. ++ */ ++int ++rtl8365mb_vlan_port_get_framefilter(struct realtek_priv *priv, ++ int port, ++ enum rtl8365mb_frame_ingress *frame_type) ++{ ++ u32 val; ++ int ret; ++ ++ /* Even if ACCEPT_FRAME_TYPE_ANY, the switch will still check if the ++ * port is a member of vlan PVID ++ */ ++ ++ ret = regmap_read(priv->map, RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_REG(port), ++ &val); ++ if (ret) ++ return ret; ++ ++ *frame_type = field_get(RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_MASK(port), ++ val); ++ ++ return 0; ++} ++ ++/* ++ * rtl8365mb_vlan_port_set_framefilter() - Set the ingress frame filtering mode ++ * for a port ++ * @priv: realtek switch private structure ++ * @port: port index ++ * @frame_type: the ingress frame filter type to configure ++ * ++ * Context: Can sleep. Takes and releases &priv->map_lock. ++ * Return: 0 on success, or a negative error code on failure. ++ */ ++int ++rtl8365mb_vlan_port_set_framefilter(struct realtek_priv *priv, ++ int port, ++ enum rtl8365mb_frame_ingress frame_type) ++{ ++ u32 val; ++ ++ /* Even if ACCEPT_FRAME_TYPE_ANY, the switch will still check if the ++ * port is a member of vlan PVID ++ */ ++ val = frame_type << RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_OFFSET(port); ++ ++ return regmap_update_bits(priv->map, ++ RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_REG(port), ++ RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_MASK(port), ++ val); ++} ++ ++/* ++ * rtl8365mb_vlan_pvid_port_set() - Configure a port's PVID and associated ++ * VLANMC entry ++ * @ds: dsa switch instance ++ * @port: port index ++ * @vid: target VID ++ * @extack: netlink extended ACK for error reporting ++ * ++ * Allocates or reuses a hardware VLANMC entry to map the given port to its new ++ * PVID. Gracefully unwinds and restores previous configuration if a hardware ++ * write operation fails during execution. ++ * ++ * Context: Can sleep. Must be called with &priv->vlan_lock held. ++ * Takes and releases &priv->map_lock. ++ * Return: 0 on success, or a negative error code on failure. ++ */ ++int rtl8365mb_vlan_pvid_port_set(struct dsa_switch *ds, int port, u16 vid, ++ struct netlink_ext_ack *extack) ++{ ++ enum rtl8365mb_frame_ingress accepted_frame, prev_accepted_frame; ++ struct realtek_priv *priv = ds->priv; ++ struct rtl8365mb_vlanmc prev_vlanmc = {0}; ++ u8 _unused_first_free_idx; ++ u8 prev_vlanmc_idx; ++ u8 vlanmc_idx; ++ int ret; ++ ++ lockdep_assert_held(&priv->vlan_lock); ++ ++ /* Read the old PVID exclusively to undo in case of error */ ++ ret = rtl8365mb_vlan_get_pvid_mc(priv, port, &prev_vlanmc_idx, ++ &prev_vlanmc); ++ if (ret) { ++ dev_err(priv->dev, "Failed to read current VLAN MC\n"); ++ return ret; ++ } ++ ++ ret = rtl8365mb_vlan_port_get_framefilter(priv, port, ++ &prev_accepted_frame); ++ if (ret) { ++ dev_err(priv->dev, "Failed to get current framefilter\n"); ++ return ret; ++ } ++ ++ /* Find or allocate a new vlan MC and add port to members, ++ * although members are not checked by the HW in vlan MC. ++ */ ++ ret = rtl8365mb_vlan_mc_port_set(ds, port, vid, true, extack, true); ++ if (ret) ++ return ret; ++ ++ /* look for existing entry */ ++ ret = rtl8365mb_vlan_mc_find(priv, vid, &vlanmc_idx, ++ &_unused_first_free_idx); ++ if (ret) { ++ dev_err(priv->dev, "Failed to find a VLAN MC table index\n"); ++ goto undo_vlan_mc_port_set; ++ } ++ ++ if (!vlanmc_idx) { ++ dev_err(priv->dev, "VLAN should already exist in VLAN MC\n"); ++ ret = -ENOENT; ++ goto undo_vlan_mc_port_set; ++ } ++ ++ ret = rtl8365mb_vlan_port_set_pvid(priv, port, vlanmc_idx); ++ if (ret) { ++ dev_err(priv->dev, "Failed to set port PVID\n"); ++ goto undo_vlan_mc_port_set; ++ } ++ ++ /* Changing accept frame is what enables PVID (if not enabled before) */ ++ accepted_frame = RTL8365MB_FRAME_TYPE_ANY_FRAME; ++ ret = rtl8365mb_vlan_port_set_framefilter(priv, port, accepted_frame); ++ if (ret) { ++ dev_err(priv->dev, "Failed to set port frame filter\n"); ++ goto undo_vlan_port_set_pvid; ++ } ++ ++ /* A VLAN can be added with PVID without removing from the old ++ * PVID VLAN. Clear PVID from the old VLAN MC (if needed). ++ */ ++ if (prev_vlanmc_idx && (prev_vlanmc.evid != vid)) { ++ ret = rtl8365mb_vlan_mc_port_set(ds, port, prev_vlanmc.evid, ++ false, NULL, false); ++ if (ret) { ++ dev_err(priv->dev, "Failed to clear old VLAN MC\n"); ++ goto undo_set_framefilter; ++ } ++ } ++ ++ return 0; ++ ++undo_set_framefilter: ++ (void)rtl8365mb_vlan_port_set_framefilter(priv, port, ++ prev_accepted_frame); ++ ++undo_vlan_port_set_pvid: ++ (void)rtl8365mb_vlan_port_set_pvid(priv, port, prev_vlanmc_idx); ++ ++undo_vlan_mc_port_set: ++ if (prev_vlanmc.evid != vid) ++ (void)rtl8365mb_vlan_mc_port_set(ds, port, vid, false, NULL, ++ false); ++ ++ return ret; ++} ++ ++/* ++ * rtl8365mb_vlan_pvid_port_clear() - Remove a port's PVID configuration ++ * @ds: dsa switch instance ++ * @port: port index ++ * @vid: VLAN VID for PVID ++ * ++ * Resets the target port's hardware PVID allocation to 0. Cleans up and frees ++ * the associated VLANMC entry if no other ports are referencing it. ++ * ++ * Context: Can sleep. Must be called with &priv->vlan_lock held. ++ * Takes and releases &priv->map_lock. ++ * Return: 0 on success, or a negative error code on failure. ++ */ ++int rtl8365mb_vlan_pvid_port_clear(struct dsa_switch *ds, int port, u16 vid) ++{ ++ enum rtl8365mb_frame_ingress accepted_frame, prev_accepted_frame; ++ struct realtek_priv *priv = ds->priv; ++ struct rtl8365mb_vlanmc vlanmc = {0}; ++ u8 vlanmc_idx; ++ int ret; ++ ++ lockdep_assert_held(&priv->vlan_lock); ++ ++ ret = rtl8365mb_vlan_get_pvid_mc(priv, port, &vlanmc_idx, ++ &vlanmc); ++ if (ret) { ++ dev_err(priv->dev, "Failed to read current VLAN MC\n"); ++ return ret; ++ } ++ ++ /* Port is not using PVID. Nothing to remove. */ ++ if (!vlanmc_idx) ++ return 0; ++ ++ /* We are leaving a non PVID vlan, Nothing to remove. */ ++ if (vlanmc.evid != vid) ++ return 0; ++ ++ ret = rtl8365mb_vlan_port_get_framefilter(priv, port, ++ &prev_accepted_frame); ++ if (ret) { ++ dev_err(priv->dev, "Failed to get current framefilter\n"); ++ return ret; ++ } ++ ++ /* Changing accept frame is what really removes PVID. But only do ++ * that if we are filtering vlan ++ */ ++ if (dsa_port_is_vlan_filtering(dsa_to_port(ds, port))) { ++ accepted_frame = RTL8365MB_FRAME_TYPE_TAGGED_ONLY; ++ ++ ret = rtl8365mb_vlan_port_set_framefilter(priv, port, ++ accepted_frame); ++ if (ret) { ++ dev_err(priv->dev, "Failed to set port frame filter\n"); ++ return ret; ++ } ++ } else { ++ /* skip undo_set_framefilter */ ++ accepted_frame = prev_accepted_frame; ++ } ++ ++ ret = rtl8365mb_vlan_port_set_pvid(priv, port, 0); ++ if (ret) { ++ dev_err(priv->dev, "Failed to set port PVID to 0\n"); ++ goto undo_set_framefilter; ++ } ++ ++ /* Clears the VLAN MC membership and maybe VLAN MC entry if empty */ ++ ret = rtl8365mb_vlan_mc_port_set(ds, port, vlanmc.evid, ++ false, NULL, false); ++ if (ret) ++ goto undo_port_set_pvid; ++ ++ return 0; ++ ++undo_port_set_pvid: ++ (void)rtl8365mb_vlan_port_set_pvid(priv, port, vlanmc_idx); ++ ++undo_set_framefilter: ++ if (prev_accepted_frame != accepted_frame) ++ (void)rtl8365mb_vlan_port_set_framefilter(priv, port, ++ prev_accepted_frame); ++ ++ return ret; ++} +--- /dev/null ++++ b/drivers/net/dsa/realtek/rtl8365mb_vlan.h +@@ -0,0 +1,39 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* VLAN configuration interface for the rtl8365mb switch family ++ * ++ * Copyright (C) 2022 Alvin Å ipraga ++ * ++ */ ++ ++#ifndef _REALTEK_RTL8365MB_VLAN_H ++#define _REALTEK_RTL8365MB_VLAN_H ++ ++#include ++ ++#include "realtek.h" ++ ++enum rtl8365mb_frame_ingress { ++ RTL8365MB_FRAME_TYPE_ANY_FRAME = 0, ++ RTL8365MB_FRAME_TYPE_TAGGED_ONLY, ++ RTL8365MB_FRAME_TYPE_UNTAGGED_ONLY, ++}; ++ ++int rtl8365mb_vlan_port_get_pvid(struct realtek_priv *priv, int port, ++ u16 *pvid); ++int ++rtl8365mb_vlan_port_get_framefilter(struct realtek_priv *priv, ++ int port, ++ enum rtl8365mb_frame_ingress *frame_type); ++int ++rtl8365mb_vlan_port_set_framefilter(struct realtek_priv *priv, ++ int port, ++ enum rtl8365mb_frame_ingress frame_type); ++int rtl8365mb_vlan_4k_port_add(struct dsa_switch *ds, int port, ++ const struct switchdev_obj_port_vlan *vlan, ++ struct netlink_ext_ack *extack); ++int rtl8365mb_vlan_4k_port_del(struct dsa_switch *ds, int port, ++ const struct switchdev_obj_port_vlan *vlan); ++int rtl8365mb_vlan_pvid_port_set(struct dsa_switch *ds, int port, u16 vid, ++ struct netlink_ext_ack *extack); ++int rtl8365mb_vlan_pvid_port_clear(struct dsa_switch *ds, int port, u16 vid); ++#endif /* _REALTEK_RTL8365MB_VLAN_H */ +--- a/drivers/net/dsa/realtek/rtl83xx.c ++++ b/drivers/net/dsa/realtek/rtl83xx.c +@@ -155,6 +155,7 @@ rtl83xx_probe(struct device *dev, + return ERR_PTR(-ENOMEM); + + mutex_init(&priv->map_lock); ++ mutex_init(&priv->vlan_lock); + + rc.lock_arg = priv; + priv->map = devm_regmap_init(dev, NULL, priv, &rc); diff --git a/target/linux/generic/backport-6.18/942-07-7.2-net-dsa-realtek-rtl8365mb-add-FDB-support.patch b/target/linux/generic/backport-6.18/942-07-7.2-net-dsa-realtek-rtl8365mb-add-FDB-support.patch new file mode 100644 index 00000000000..1f4b9257d05 --- /dev/null +++ b/target/linux/generic/backport-6.18/942-07-7.2-net-dsa-realtek-rtl8365mb-add-FDB-support.patch @@ -0,0 +1,1131 @@ +From 336e3e4a1ab37b6826fae27e53cd2ac43c9a96ca Mon Sep 17 00:00:00 2001 +From: Alvin Å ipraga +Date: Sat, 6 Jun 2026 05:29:31 -0300 +Subject: net: dsa: realtek: rtl8365mb: add FDB support +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Implement support for FDB and MDB management for the RTL8365MB series +switches. + +The hardware supports IVL by keying the unicast forwarding database with +the {MAC, VID, EFID} tuple. The Extended Filtering ID (EFID) is 3 bits +wide, providing 8 unique filtering domains. This driver reserves EFID 0 +for standalone ports, effectively limiting the hardware offload to a +maximum of 7 bridges. The multicast database uses a {MAC, VID} key, with +ports from different bridges sharing the same multicast group. + +Introduce a mutex lock (l2_lock) to protect concurrent L2 table updates. + +Add support for forwarding database operations, including unicast and +multicast entry handling as well as fast aging support. + +Set DSA switch flags assisted_learning_on_cpu_port and fdb_isolation. + +Signed-off-by: Alvin Å ipraga +Reviewed-by: Linus Walleij +Reviewed-by: Mieczyslaw Nalewaj +Co-developed-by: Luiz Angelo Daros de Luca +Signed-off-by: Luiz Angelo Daros de Luca +Link: https://patch.msgid.link/20260606-realtek_forward-v13-7-b9e409687cbe@gmail.com +Signed-off-by: Jakub Kicinski +--- + drivers/net/dsa/realtek/Makefile | 1 + + drivers/net/dsa/realtek/realtek.h | 28 ++ + drivers/net/dsa/realtek/rtl8365mb_l2.c | 576 +++++++++++++++++++++++++++++++ + drivers/net/dsa/realtek/rtl8365mb_l2.h | 32 ++ + drivers/net/dsa/realtek/rtl8365mb_main.c | 33 +- + drivers/net/dsa/realtek/rtl83xx.c | 292 ++++++++++++++++ + drivers/net/dsa/realtek/rtl83xx.h | 16 + + 7 files changed, 977 insertions(+), 1 deletion(-) + +--- a/drivers/net/dsa/realtek/Makefile ++++ b/drivers/net/dsa/realtek/Makefile +@@ -19,4 +19,5 @@ obj-$(CONFIG_NET_DSA_REALTEK_RTL8365MB) + rtl8365mb-objs := rtl8365mb_main.o \ + rtl8365mb_table.o \ + rtl8365mb_vlan.o \ ++ rtl8365mb_l2.o \ + # end of rtl8365mb-objs +--- a/drivers/net/dsa/realtek/realtek.h ++++ b/drivers/net/dsa/realtek/realtek.h +@@ -45,6 +45,12 @@ struct rtl8366_vlan_4k { + u8 fid; + }; + ++struct realtek_fdb_entry { ++ u8 mac_addr[ETH_ALEN]; ++ u16 vid; ++ bool is_static; ++}; ++ + struct realtek_priv { + struct device *dev; + struct reset_control *reset_ctl; +@@ -59,6 +65,15 @@ struct realtek_priv { + * deleting port VLAN memberships and PVID configurations. + */ + struct mutex vlan_lock; ++ /* l2_lock is used to prevent concurrent modifications of L2 table ++ * entries while another function is reading it. l2_(add,del)_mc ++ * is an example that first read current table entry and then ++ * create/update it. l2_(add|del)_uc uses a single table op and, ++ * internally, it might not need this lock. However, altering FDB ++ * may still collide, as well as l2_flush, with fdb_dump iterating ++ * over FDB. ++ */ ++ struct mutex l2_lock; + struct mii_bus *user_mii_bus; + struct mii_bus *bus; + int mdio_addr; +@@ -112,6 +127,19 @@ struct realtek_ops { + int (*enable_vlan)(struct realtek_priv *priv, bool enable); + int (*enable_vlan4k)(struct realtek_priv *priv, bool enable); + int (*enable_port)(struct realtek_priv *priv, int port, bool enable); ++ int (*l2_add_uc)(struct realtek_priv *priv, int port, ++ const unsigned char addr[ETH_ALEN], ++ u16 efid, u16 vid); ++ int (*l2_del_uc)(struct realtek_priv *priv, int port, ++ const unsigned char addr[ETH_ALEN], ++ u16 efid, u16 vid); ++ int (*l2_get_next_uc)(struct realtek_priv *priv, u16 *addr, ++ int port, struct realtek_fdb_entry *entry); ++ int (*l2_add_mc)(struct realtek_priv *priv, int port, ++ const unsigned char addr[ETH_ALEN], u16 vid); ++ int (*l2_del_mc)(struct realtek_priv *priv, int port, ++ const unsigned char addr[ETH_ALEN], u16 vid); ++ int (*l2_flush)(struct realtek_priv *priv, int port, u16 vid); + int (*phy_read)(struct realtek_priv *priv, int phy, int regnum); + int (*phy_write)(struct realtek_priv *priv, int phy, int regnum, + u16 val); +--- /dev/null ++++ b/drivers/net/dsa/realtek/rtl8365mb_l2.c +@@ -0,0 +1,576 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* Forwarding and multicast database interface for the rtl8365mb switch family ++ * ++ * Copyright (C) 2022 Alvin Å ipraga ++ */ ++ ++#include ++ ++#include "rtl8365mb_l2.h" ++#include "rtl8365mb_table.h" ++#include ++ ++#define RTL8365MB_L2_ENTRY_SIZE 6 ++ ++#define RTL8365MB_L2_UC_D0_MAC5_MSK GENMASK(7, 0) ++#define RTL8365MB_L2_UC_D0_MAC4_MSK GENMASK(15, 8) ++#define RTL8365MB_L2_UC_D1_MAC3_MSK GENMASK(7, 0) ++#define RTL8365MB_L2_UC_D1_MAC2_MSK GENMASK(15, 8) ++#define RTL8365MB_L2_UC_D2_MAC1_MSK GENMASK(7, 0) ++#define RTL8365MB_L2_UC_D2_MAC0_MSK GENMASK(15, 8) ++#define RTL8365MB_L2_UC_D3_VID_MSK GENMASK(11, 0) ++#define RTL8365MB_L2_UC_D3_IVL_MSK GENMASK(13, 13) ++#define RTL8365MB_L2_UC_D3_PORT_EXT_MSK GENMASK(15, 15) ++#define RTL8365MB_L2_UC_PORT_HI_MSK GENMASK(3, 3) ++#define RTL8365MB_L2_UC_D4_EFID_MSK GENMASK(2, 0) ++#define RTL8365MB_L2_UC_D4_FID_MSK GENMASK(6, 3) ++#define RTL8365MB_L2_UC_D4_SA_PRI_MSK GENMASK(7, 7) ++#define RTL8365MB_L2_UC_D4_PORT_MSK GENMASK(10, 8) ++#define RTL8365MB_L2_UC_PORT_LO_MSK GENMASK(2, 0) ++#define RTL8365MB_L2_UC_D4_AGE_MSK GENMASK(13, 11) ++#define RTL8365MB_L2_UC_D4_AUTH_MSK GENMASK(14, 14) ++#define RTL8365MB_L2_UC_D4_SA_BLOCK_MSK GENMASK(15, 15) ++ ++#define RTL8365MB_L2_UC_D5_DA_BLOCK_MSK GENMASK(0, 0) ++#define RTL8365MB_L2_UC_D5_PRIORITY_MSK GENMASK(3, 1) ++#define RTL8365MB_L2_UC_D5_FWD_PRI_MSK GENMASK(4, 4) ++#define RTL8365MB_L2_UC_D5_STATIC_MSK GENMASK(5, 5) ++ ++#define RTL8365MB_L2_MC_D0_MAC5_MSK GENMASK(7, 0) ++#define RTL8365MB_L2_MC_D0_MAC4_MSK GENMASK(15, 8) ++#define RTL8365MB_L2_MC_D1_MAC3_MSK GENMASK(7, 0) ++#define RTL8365MB_L2_MC_D1_MAC2_MSK GENMASK(15, 8) ++#define RTL8365MB_L2_MC_D2_MAC1_MSK GENMASK(7, 0) ++#define RTL8365MB_L2_MC_D2_MAC0_MSK GENMASK(15, 8) ++#define RTL8365MB_L2_MC_D3_VID_MSK GENMASK(11, 0) ++#define RTL8365MB_L2_MC_D3_IVL_MSK GENMASK(13, 13) ++#define RTL8365MB_L2_MC_D3_MBR_HI1_MSK GENMASK(15, 14) ++#define RTL8365MB_L2_MC_MBR_HI1_MSK GENMASK(9, 8) ++ ++#define RTL8365MB_L2_MC_D4_MBR_MSK GENMASK(7, 0) ++#define RTL8365MB_L2_MC_MBR_LO_MSK GENMASK(7, 0) ++#define RTL8365MB_L2_MC_D4_IGMPIDX_MSK GENMASK(15, 8) ++ ++#define RTL8365MB_L2_MC_D5_IGMP_ASIC_MSK GENMASK(0, 0) ++#define RTL8365MB_L2_MC_D5_PRIORITY_MSK GENMASK(3, 1) ++#define RTL8365MB_L2_MC_D5_FWD_PRI_MSK GENMASK(4, 4) ++#define RTL8365MB_L2_MC_D5_STATIC_MSK GENMASK(5, 5) ++#define RTL8365MB_L2_MC_D5_MBR_HI2_MSK GENMASK(7, 7) ++#define RTL8365MB_L2_MC_MBR_HI2_MSK GENMASK(10, 10) ++ ++/* Port flush command registers - writing a 1 to the port's MASK bit will ++ * initiate the flush procedure. Completion is signalled when the corresponding ++ * BUSY bit is 0. ++ */ ++#define RTL8365MB_L2_FLUSH_PORT_REG 0x0A36 ++#define RTL8365MB_L2_FLUSH_PORT_MSK_MSK GENMASK(7, 0) ++#define RTL8365MB_L2_FLUSH_PORT_BUSY_MSK GENMASK(15, 8) ++ ++#define RTL8365MB_L2_FLUSH_PORT_EXT_REG 0x0A35 ++#define RTL8365MB_L2_FLUSH_PORT_EXT_MSK_MSK GENMASK(2, 0) ++#define RTL8365MB_L2_FLUSH_PORT_EXT_BUSY_MSK GENMASK(5, 3) ++ ++#define RTL8365MB_L2_FLUSH_CTRL1_REG 0x0A37 ++#define RTL8365MB_L2_FLUSH_CTRL1_VID_MSK GENMASK(11, 0) ++#define RTL8365MB_L2_FLUSH_CTRL1_FID_MSK GENMASK(15, 12) ++ ++#define RTL8365MB_L2_FLUSH_CTRL2_REG 0x0A38 ++#define RTL8365MB_L2_FLUSH_CTRL2_MODE_MSK GENMASK(1, 0) ++#define RTL8365MB_L2_FLUSH_CTRL2_MODE_PORT 0 ++#define RTL8365MB_L2_FLUSH_CTRL2_MODE_PORT_VID 1 ++#define RTL8365MB_L2_FLUSH_CTRL2_MODE_PORT_FID 2 ++#define RTL8365MB_L2_FLUSH_CTRL2_TYPE_MSK GENMASK(2, 2) ++#define RTL8365MB_L2_FLUSH_CTRL2_TYPE_DYNAMIC 0 ++#define RTL8365MB_L2_FLUSH_CTRL2_TYPE_BOTH 1 ++ ++/* This flushes the entire LUT, reading it back it will turn 0 when the ++ * operation is complete ++ */ ++#define RTL8365MB_L2_FLUSH_CTRL3_REG 0x0A39 ++#define RTL8365MB_L2_FLUSH_CTRL3_MSK GENMASK(0, 0) ++ ++struct rtl8365mb_l2_uc_key { ++ u8 mac_addr[ETH_ALEN]; ++ u16 vid; ++ u16 fid; ++ bool ivl; ++ u16 efid; ++}; ++ ++struct rtl8365mb_l2_uc { ++ struct rtl8365mb_l2_uc_key key; ++ u8 port; ++ u8 age; ++ u8 priority; ++ ++ bool sa_block; ++ bool da_block; ++ bool auth; ++ bool is_static; ++ bool sa_pri; ++ bool fwd_pri; ++}; ++ ++struct rtl8365mb_l2_mc_key { ++ u8 mac_addr[ETH_ALEN]; ++ union { ++ u16 vid; /* IVL */ ++ u16 fid; /* SVL */ ++ }; ++ bool ivl; ++}; ++ ++struct rtl8365mb_l2_mc { ++ struct rtl8365mb_l2_mc_key key; ++ u16 member; ++ u8 priority; ++ u8 igmpidx; ++ ++ bool is_static; ++ bool fwd_pri; ++ bool igmp_asic; ++}; ++ ++static void rtl8365mb_l2_data_to_uc(const u16 *data, struct rtl8365mb_l2_uc *uc) ++{ ++ u32 val; ++ ++ uc->key.mac_addr[5] = FIELD_GET(RTL8365MB_L2_UC_D0_MAC5_MSK, data[0]); ++ uc->key.mac_addr[4] = FIELD_GET(RTL8365MB_L2_UC_D0_MAC4_MSK, data[0]); ++ uc->key.mac_addr[3] = FIELD_GET(RTL8365MB_L2_UC_D1_MAC3_MSK, data[1]); ++ uc->key.mac_addr[2] = FIELD_GET(RTL8365MB_L2_UC_D1_MAC2_MSK, data[1]); ++ uc->key.mac_addr[1] = FIELD_GET(RTL8365MB_L2_UC_D2_MAC1_MSK, data[2]); ++ uc->key.mac_addr[0] = FIELD_GET(RTL8365MB_L2_UC_D2_MAC0_MSK, data[2]); ++ uc->key.efid = FIELD_GET(RTL8365MB_L2_UC_D4_EFID_MSK, data[4]); ++ uc->key.vid = FIELD_GET(RTL8365MB_L2_UC_D3_VID_MSK, data[3]); ++ uc->key.ivl = FIELD_GET(RTL8365MB_L2_UC_D3_IVL_MSK, data[3]); ++ uc->key.fid = FIELD_GET(RTL8365MB_L2_UC_D4_FID_MSK, data[4]); ++ uc->age = FIELD_GET(RTL8365MB_L2_UC_D4_AGE_MSK, data[4]); ++ uc->auth = FIELD_GET(RTL8365MB_L2_UC_D4_AUTH_MSK, data[4]); ++ ++ val = FIELD_GET(RTL8365MB_L2_UC_D4_PORT_MSK, data[4]); ++ uc->port = FIELD_PREP(RTL8365MB_L2_UC_PORT_LO_MSK, val); ++ val = FIELD_GET(RTL8365MB_L2_UC_D3_PORT_EXT_MSK, data[3]); ++ uc->port |= FIELD_PREP(RTL8365MB_L2_UC_PORT_HI_MSK, val); ++ ++ uc->sa_pri = FIELD_GET(RTL8365MB_L2_UC_D4_SA_PRI_MSK, data[4]); ++ uc->fwd_pri = FIELD_GET(RTL8365MB_L2_UC_D5_FWD_PRI_MSK, data[5]); ++ uc->sa_block = FIELD_GET(RTL8365MB_L2_UC_D4_SA_BLOCK_MSK, data[4]); ++ uc->da_block = FIELD_GET(RTL8365MB_L2_UC_D5_DA_BLOCK_MSK, data[5]); ++ uc->priority = FIELD_GET(RTL8365MB_L2_UC_D5_PRIORITY_MSK, data[5]); ++ uc->is_static = FIELD_GET(RTL8365MB_L2_UC_D5_STATIC_MSK, data[5]); ++} ++ ++static void rtl8365mb_l2_uc_to_data(const struct rtl8365mb_l2_uc *uc, u16 *data) ++{ ++ u32 val; ++ ++ memset(data, 0, RTL8365MB_L2_ENTRY_SIZE * 2); ++ data[0] |= ++ FIELD_PREP(RTL8365MB_L2_UC_D0_MAC5_MSK, uc->key.mac_addr[5]); ++ data[0] |= ++ FIELD_PREP(RTL8365MB_L2_UC_D0_MAC4_MSK, uc->key.mac_addr[4]); ++ data[1] |= ++ FIELD_PREP(RTL8365MB_L2_UC_D1_MAC3_MSK, uc->key.mac_addr[3]); ++ data[1] |= ++ FIELD_PREP(RTL8365MB_L2_UC_D1_MAC2_MSK, uc->key.mac_addr[2]); ++ data[2] |= ++ FIELD_PREP(RTL8365MB_L2_UC_D2_MAC1_MSK, uc->key.mac_addr[1]); ++ data[2] |= ++ FIELD_PREP(RTL8365MB_L2_UC_D2_MAC0_MSK, uc->key.mac_addr[0]); ++ data[3] |= FIELD_PREP(RTL8365MB_L2_UC_D3_VID_MSK, uc->key.vid); ++ data[3] |= FIELD_PREP(RTL8365MB_L2_UC_D3_IVL_MSK, uc->key.ivl); ++ ++ val = FIELD_GET(RTL8365MB_L2_UC_PORT_HI_MSK, uc->port); ++ data[3] |= FIELD_PREP(RTL8365MB_L2_UC_D3_PORT_EXT_MSK, val); ++ ++ data[4] |= FIELD_PREP(RTL8365MB_L2_UC_D4_FID_MSK, uc->key.fid); ++ data[4] |= FIELD_PREP(RTL8365MB_L2_UC_D4_EFID_MSK, uc->key.efid); ++ data[4] |= FIELD_PREP(RTL8365MB_L2_UC_D4_AGE_MSK, uc->age); ++ data[4] |= FIELD_PREP(RTL8365MB_L2_UC_D4_AUTH_MSK, uc->auth); ++ ++ val = FIELD_GET(RTL8365MB_L2_UC_PORT_LO_MSK, uc->port); ++ data[4] |= FIELD_PREP(RTL8365MB_L2_UC_D4_PORT_MSK, val); ++ ++ data[4] |= FIELD_PREP(RTL8365MB_L2_UC_D4_SA_PRI_MSK, uc->sa_pri); ++ data[4] |= FIELD_PREP(RTL8365MB_L2_UC_D4_SA_BLOCK_MSK, uc->sa_block); ++ data[5] |= FIELD_PREP(RTL8365MB_L2_UC_D5_FWD_PRI_MSK, uc->fwd_pri); ++ data[5] |= FIELD_PREP(RTL8365MB_L2_UC_D5_DA_BLOCK_MSK, uc->da_block); ++ data[5] |= FIELD_PREP(RTL8365MB_L2_UC_D5_PRIORITY_MSK, uc->priority); ++ data[5] |= FIELD_PREP(RTL8365MB_L2_UC_D5_STATIC_MSK, uc->is_static); ++} ++ ++static void rtl8365mb_l2_data_to_mc(const u16 *data, struct rtl8365mb_l2_mc *mc) ++{ ++ u32 val; ++ ++ mc->key.mac_addr[5] = FIELD_GET(RTL8365MB_L2_MC_D0_MAC5_MSK, data[0]); ++ mc->key.mac_addr[4] = FIELD_GET(RTL8365MB_L2_MC_D0_MAC4_MSK, data[0]); ++ mc->key.mac_addr[3] = FIELD_GET(RTL8365MB_L2_MC_D1_MAC3_MSK, data[1]); ++ mc->key.mac_addr[2] = FIELD_GET(RTL8365MB_L2_MC_D1_MAC2_MSK, data[1]); ++ mc->key.mac_addr[1] = FIELD_GET(RTL8365MB_L2_MC_D2_MAC1_MSK, data[2]); ++ mc->key.mac_addr[0] = FIELD_GET(RTL8365MB_L2_MC_D2_MAC0_MSK, data[2]); ++ /* key.vid,key.fid shares the same memory space */ ++ mc->key.vid = FIELD_GET(RTL8365MB_L2_MC_D3_VID_MSK, data[3]); ++ mc->key.ivl = FIELD_GET(RTL8365MB_L2_MC_D3_IVL_MSK, data[3]); ++ mc->priority = FIELD_GET(RTL8365MB_L2_MC_D5_PRIORITY_MSK, data[5]); ++ mc->fwd_pri = FIELD_GET(RTL8365MB_L2_MC_D5_FWD_PRI_MSK, data[5]); ++ mc->is_static = FIELD_GET(RTL8365MB_L2_MC_D5_STATIC_MSK, data[5]); ++ ++ val = FIELD_GET(RTL8365MB_L2_MC_D4_MBR_MSK, data[4]); ++ mc->member = FIELD_PREP(RTL8365MB_L2_MC_MBR_LO_MSK, val); ++ val = FIELD_GET(RTL8365MB_L2_MC_D3_MBR_HI1_MSK, data[3]); ++ mc->member |= FIELD_PREP(RTL8365MB_L2_MC_MBR_HI1_MSK, val); ++ val = FIELD_GET(RTL8365MB_L2_MC_D5_MBR_HI2_MSK, data[5]); ++ mc->member |= FIELD_PREP(RTL8365MB_L2_MC_MBR_HI2_MSK, val); ++ ++ mc->igmpidx = FIELD_GET(RTL8365MB_L2_MC_D4_IGMPIDX_MSK, data[4]); ++ mc->igmp_asic = FIELD_GET(RTL8365MB_L2_MC_D5_IGMP_ASIC_MSK, data[5]); ++} ++ ++static void rtl8365mb_l2_mc_to_data(const struct rtl8365mb_l2_mc *mc, u16 *data) ++{ ++ u32 val; ++ ++ memset(data, 0, RTL8365MB_L2_ENTRY_SIZE * 2); ++ data[0] |= FIELD_PREP(RTL8365MB_L2_MC_D0_MAC5_MSK, mc->key.mac_addr[5]); ++ data[0] |= FIELD_PREP(RTL8365MB_L2_MC_D0_MAC4_MSK, mc->key.mac_addr[4]); ++ data[1] |= FIELD_PREP(RTL8365MB_L2_MC_D1_MAC3_MSK, mc->key.mac_addr[3]); ++ data[1] |= FIELD_PREP(RTL8365MB_L2_MC_D1_MAC2_MSK, mc->key.mac_addr[2]); ++ data[2] |= FIELD_PREP(RTL8365MB_L2_MC_D2_MAC1_MSK, mc->key.mac_addr[1]); ++ data[2] |= FIELD_PREP(RTL8365MB_L2_MC_D2_MAC0_MSK, mc->key.mac_addr[0]); ++ data[3] |= FIELD_PREP(RTL8365MB_L2_MC_D3_VID_MSK, mc->key.vid); ++ data[3] |= FIELD_PREP(RTL8365MB_L2_MC_D3_IVL_MSK, mc->key.ivl); ++ ++ val = FIELD_GET(RTL8365MB_L2_MC_MBR_HI1_MSK, mc->member); ++ data[3] |= FIELD_PREP(RTL8365MB_L2_MC_D3_MBR_HI1_MSK, val); ++ ++ val = FIELD_GET(RTL8365MB_L2_MC_MBR_LO_MSK, mc->member); ++ data[4] |= FIELD_PREP(RTL8365MB_L2_MC_D4_MBR_MSK, val); ++ ++ data[4] |= FIELD_PREP(RTL8365MB_L2_MC_D4_IGMPIDX_MSK, mc->igmpidx); ++ data[5] |= FIELD_PREP(RTL8365MB_L2_MC_D5_IGMP_ASIC_MSK, mc->igmp_asic); ++ data[5] |= FIELD_PREP(RTL8365MB_L2_MC_D5_PRIORITY_MSK, mc->priority); ++ data[5] |= FIELD_PREP(RTL8365MB_L2_MC_D5_FWD_PRI_MSK, mc->fwd_pri); ++ data[5] |= FIELD_PREP(RTL8365MB_L2_MC_D5_STATIC_MSK, mc->is_static); ++ ++ val = FIELD_GET(RTL8365MB_L2_MC_MBR_HI2_MSK, mc->member); ++ data[5] |= FIELD_PREP(RTL8365MB_L2_MC_D5_MBR_HI2_MSK, val); ++} ++ ++/* ++ * rtl8365mb_l2_get_next_uc() - get the next Unicast L2 entry ++ * @priv: realtek_priv pointer ++ * @addr: as input, the table index to start the walk ++ * as output, the found table index ++ * @port: restrict the walk on entries related to port ++ * @entry: returned L2 Unicast table entry ++ * ++ * This function gets the next unicast L2 table entry starting from @addr ++ * and checking exclusively entries related to @port. ++ * ++ * On success, it returns 0, updates @addr to the index of the found entry, ++ * and populates @entry. If the search reaches the end of the table and ++ * wraps around and @addr will be strictly lower than the input @addr. ++ * Callers must detect this wrap-around condition to prevent infinite loops. ++ * ++ * If the table contains no matching entries at all, it returns -ENOENT ++ * and leaves @addr and @entry unmodified. ++ * ++ * Return: Returns 0 on success, a negative error on failure. ++ **/ ++int rtl8365mb_l2_get_next_uc(struct realtek_priv *priv, u16 *addr, int port, ++ struct realtek_fdb_entry *entry) ++{ ++ u16 data[RTL8365MB_L2_ENTRY_SIZE] = { 0 }; ++ struct rtl8365mb_l2_uc uc; ++ int ret; ++ ++ ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2, ++ RTL8365MB_TABLE_OP_READ, addr, ++ RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC_PORT, ++ port, data, RTL8365MB_L2_ENTRY_SIZE); ++ if (ret) ++ return ret; ++ ++ rtl8365mb_l2_data_to_uc(data, &uc); ++ ++ ether_addr_copy(entry->mac_addr, uc.key.mac_addr); ++ entry->vid = uc.key.vid; ++ entry->is_static = uc.is_static; ++ ++ return 0; ++} ++ ++int rtl8365mb_l2_add_uc(struct realtek_priv *priv, int port, ++ const unsigned char mac_addr[static ETH_ALEN], ++ u16 efid, u16 vid) ++{ ++ u16 data[RTL8365MB_L2_ENTRY_SIZE] = { 0 }; ++ struct rtl8365mb_l2_uc uc = { 0 }; ++ u16 addr; ++ int ret; ++ ++ memcpy(uc.key.mac_addr, mac_addr, ETH_ALEN); ++ uc.key.efid = efid; ++ uc.key.fid = 0; ++ uc.key.ivl = true; ++ uc.key.vid = vid; ++ ++ uc.port = port; ++ /* Entries programmed by DSA (including those dynamically learned by ++ * the software bridge and injected into the CPU port via assisted ++ * learning) must be static. We do not let HW decrease age behind the ++ * OS's back. As a trade-off, these will show up as permanent to users. ++ */ ++ uc.is_static = true; ++ /* age greater than 0 adds/updates entries */ ++ uc.age = 1; ++ rtl8365mb_l2_uc_to_data(&uc, data); ++ ++ /* add the new entry or update an existing one */ ++ ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2, ++ RTL8365MB_TABLE_OP_WRITE, &addr, ++ 0, 0, ++ data, RTL8365MB_L2_ENTRY_SIZE); ++ ++ /* Assume the missing new entry as the table is full */ ++ if (ret == -ENOENT) ++ return -ENOSPC; ++ ++ /* addr will hold the table index, but it is not used here */ ++ return ret; ++} ++ ++int rtl8365mb_l2_del_uc(struct realtek_priv *priv, int port, ++ const unsigned char mac_addr[static ETH_ALEN], ++ u16 efid, u16 vid) ++{ ++ u16 data[RTL8365MB_L2_ENTRY_SIZE] = { 0 }; ++ struct rtl8365mb_l2_uc uc = { 0 }; ++ u16 addr; ++ int ret; ++ ++ memcpy(uc.key.mac_addr, mac_addr, ETH_ALEN); ++ uc.key.efid = efid; ++ uc.key.fid = 0; ++ uc.key.ivl = true; ++ uc.key.vid = vid; ++ /* age 0 deletes the entry */ ++ uc.age = 0; ++ rtl8365mb_l2_uc_to_data(&uc, data); ++ ++ /* it looks like the switch will always add/update the entry, ++ * even when age is 0 or uc.key did not match an existing entry, ++ * just to immediately drop it because age is zero. You can still ++ * get the added/updated address from @addr ++ */ ++ ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2, ++ RTL8365MB_TABLE_OP_WRITE, &addr, ++ 0, 0, ++ data, RTL8365MB_L2_ENTRY_SIZE); ++ ++ if (ret == -ENOENT) { ++ dev_dbg(priv->dev, "%s: %pM vid=%d efid=%d missing\n", ++ __func__, mac_addr, vid, efid); ++ /* Silently return success */ ++ return 0; ++ } ++ ++ /* addr will hold the table index, but it is not used here */ ++ return ret; ++} ++ ++int rtl8365mb_l2_flush(struct realtek_priv *priv, int port, u16 vid) ++{ ++ int mode = vid ? RTL8365MB_L2_FLUSH_CTRL2_MODE_PORT_VID : ++ RTL8365MB_L2_FLUSH_CTRL2_MODE_PORT; ++ u32 val, mask; ++ int ret; ++ ++ mutex_lock(&priv->map_lock); ++ ++ /* Configure flushing mode; only flush dynamic entries */ ++ ret = regmap_write(priv->map_nolock, RTL8365MB_L2_FLUSH_CTRL2_REG, ++ FIELD_PREP(RTL8365MB_L2_FLUSH_CTRL2_MODE_MSK, ++ mode) | ++ FIELD_PREP(RTL8365MB_L2_FLUSH_CTRL2_TYPE_MSK, ++ RTL8365MB_L2_FLUSH_CTRL2_TYPE_DYNAMIC)); ++ if (ret) ++ goto out; ++ ++ ret = regmap_write(priv->map_nolock, RTL8365MB_L2_FLUSH_CTRL1_REG, ++ FIELD_PREP(RTL8365MB_L2_FLUSH_CTRL1_VID_MSK, vid)); ++ ++ if (ret) ++ goto out; ++ /* Now issue the flush command and wait for its completion. There are ++ * two registers for this purpose, and which one to use depends on the ++ * port number. The _EXT register is for ports 8 or higher. ++ */ ++ if (port < 8) { ++ val = FIELD_PREP(RTL8365MB_L2_FLUSH_PORT_MSK_MSK, ++ BIT(port) & 0xFF); ++ ret = regmap_write(priv->map_nolock, ++ RTL8365MB_L2_FLUSH_PORT_REG, val); ++ if (ret) ++ goto out; ++ ++ mask = FIELD_PREP(RTL8365MB_L2_FLUSH_PORT_BUSY_MSK, ++ BIT(port) & 0xFF); ++ ret = regmap_read_poll_timeout(priv->map_nolock, ++ RTL8365MB_L2_FLUSH_PORT_REG, ++ val, !(val & mask), 10, 10000); ++ if (ret) ++ goto out; ++ } else { ++ val = FIELD_PREP(RTL8365MB_L2_FLUSH_PORT_EXT_MSK_MSK, ++ BIT(port) >> 8); ++ ret = regmap_write(priv->map_nolock, ++ RTL8365MB_L2_FLUSH_PORT_EXT_REG, val); ++ if (ret) ++ goto out; ++ ++ mask = FIELD_PREP(RTL8365MB_L2_FLUSH_PORT_EXT_BUSY_MSK, ++ BIT(port) >> 8); ++ ret = regmap_read_poll_timeout(priv->map_nolock, ++ RTL8365MB_L2_FLUSH_PORT_EXT_REG, ++ val, !(val & mask), 10, 10000); ++ if (ret) ++ goto out; ++ } ++ ++out: ++ mutex_unlock(&priv->map_lock); ++ ++ return ret; ++} ++ ++int rtl8365mb_l2_add_mc(struct realtek_priv *priv, int port, ++ const unsigned char mac_addr[static ETH_ALEN], ++ u16 vid) ++{ ++ u16 data[RTL8365MB_L2_ENTRY_SIZE] = { 0 }; ++ struct rtl8365mb_l2_mc mc = { 0 }; ++ u16 addr; ++ int ret; ++ ++ memcpy(mc.key.mac_addr, mac_addr, ETH_ALEN); ++ mc.key.vid = vid; ++ mc.key.ivl = true; ++ /* Already set the port and is_static, although not used in OP_READ, ++ * data will be ready for OP_WRITE if it is a new entry. ++ */ ++ mc.member |= BIT(port); ++ mc.is_static = 1; ++ rtl8365mb_l2_mc_to_data(&mc, data); ++ ++ /* First look for an existing entry (to get existing port members) */ ++ ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2, ++ RTL8365MB_TABLE_OP_READ, &addr, ++ RTL8365MB_TABLE_L2_METHOD_MAC, 0, ++ data, RTL8365MB_L2_ENTRY_SIZE); ++ if (!ret) { ++ /* There is already an entry... */ ++ rtl8365mb_l2_data_to_mc(data, &mc); ++ dev_dbg(priv->dev, ++ "%s: found %pM addr=%d member=0x%x igmpidx=0x%x %s\n", ++ __func__, mac_addr, addr, mc.member, mc.igmpidx, ++ mc.is_static ? "static" : "dynamic"); ++ /* the port must be added as a member */ ++ mc.member |= BIT(port); ++ ++ if (!mc.is_static) { ++ dev_dbg(priv->dev, ++ "%s: promoting addr=%d group to static\n", ++ __func__, addr); ++ mc.is_static = 1; ++ } ++ ++ rtl8365mb_l2_mc_to_data(&mc, data); ++ } else if (ret == -ENOENT) { ++ /* New entry, no need to update data again as it already ++ * includes the member. ++ * ++ * Multicast hardware entries do not support EFID (bridge ++ * isolation). However, traffic isolation is still maintained ++ * because the hardware applies the port isolation masks ++ * (pmasks) configured in bridge_join after the L2 lookup. ++ * Entries from different bridges will collide on the same ++ * MAC+VID slot with an OR'ed member mask, but packets will ++ * only exit through ports allowed by the source port's pmask. ++ */ ++ } else { ++ return ret; ++ } ++ ++ /* add the new entry or update an existing one */ ++ ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2, ++ RTL8365MB_TABLE_OP_WRITE, &addr, ++ 0, 0, ++ data, RTL8365MB_L2_ENTRY_SIZE); ++ ++ /* Assume the missing new entry as the table is full */ ++ if (ret == -ENOENT) ++ return -ENOSPC; ++ ++ return ret; ++} ++ ++int rtl8365mb_l2_del_mc(struct realtek_priv *priv, int port, ++ const unsigned char mac_addr[static ETH_ALEN], ++ u16 vid) ++{ ++ u16 data[RTL8365MB_L2_ENTRY_SIZE] = { 0 }; ++ struct rtl8365mb_l2_mc mc = { 0 }; ++ u16 addr; ++ int ret; ++ ++ memcpy(mc.key.mac_addr, mac_addr, ETH_ALEN); ++ mc.key.vid = vid; ++ mc.key.ivl = true; ++ rtl8365mb_l2_mc_to_data(&mc, data); ++ ++ /* First look for an existing entry (to get existing port members) */ ++ ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2, ++ RTL8365MB_TABLE_OP_READ, &addr, ++ RTL8365MB_TABLE_L2_METHOD_MAC, 0, ++ data, RTL8365MB_L2_ENTRY_SIZE); ++ if (ret == -ENOENT) { ++ dev_dbg(priv->dev, "%s: %pM vid=%d missing\n", ++ __func__, mac_addr, vid); ++ /* Silently return success */ ++ return 0; ++ } ++ ++ if (ret) ++ /* Return on any other error */ ++ return ret; ++ ++ rtl8365mb_l2_data_to_mc(data, &mc); ++ dev_dbg(priv->dev, ++ "%s: found %pM addr=%d member=0x%x igmpidx=0x%x %s\n", ++ __func__, mac_addr, addr, mc.member, mc.igmpidx, ++ mc.is_static ? "static" : "dynamic"); ++ /* the port must be removed as a member */ ++ mc.member &= ~BIT(port); ++ if (!mc.member) { ++ /* Multicast entries do not have an age field. Clearing both ++ * the member portmask and is_static flags is the hardware ++ * signal to invalidate and reclaim the L2 table slot. ++ */ ++ mc.is_static = 0; ++ mc.igmpidx = 0; ++ mc.priority = 0; ++ mc.fwd_pri = 0; ++ mc.igmp_asic = 0; ++ } ++ rtl8365mb_l2_mc_to_data(&mc, data); ++ ++ /* update the existing entry. */ ++ ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2, ++ RTL8365MB_TABLE_OP_WRITE, &addr, ++ 0, 0, ++ data, RTL8365MB_L2_ENTRY_SIZE); ++ return ret; ++} +--- /dev/null ++++ b/drivers/net/dsa/realtek/rtl8365mb_l2.h +@@ -0,0 +1,32 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* Forwarding and multicast database interface for the rtl8365mb switch family ++ * ++ * Copyright (C) 2022 Alvin Å ipraga ++ */ ++ ++#ifndef _REALTEK_RTL8365MB_L2_H ++#define _REALTEK_RTL8365MB_L2_H ++ ++#include ++#include ++ ++#include "realtek.h" ++ ++int rtl8365mb_l2_get_next_uc(struct realtek_priv *priv, u16 *addr, int port, ++ struct realtek_fdb_entry *entry); ++int rtl8365mb_l2_add_uc(struct realtek_priv *priv, int port, ++ const unsigned char addr[static ETH_ALEN], ++ u16 efid, u16 vid); ++int rtl8365mb_l2_del_uc(struct realtek_priv *priv, int port, ++ const unsigned char addr[static ETH_ALEN], ++ u16 efid, u16 vid); ++int rtl8365mb_l2_flush(struct realtek_priv *priv, int port, u16 vid); ++ ++int rtl8365mb_l2_add_mc(struct realtek_priv *priv, int port, ++ const unsigned char mac_addr[static ETH_ALEN], ++ u16 vid); ++int rtl8365mb_l2_del_mc(struct realtek_priv *priv, int port, ++ const unsigned char mac_addr[static ETH_ALEN], ++ u16 vid); ++ ++#endif /* _REALTEK_RTL8365MB_L2_H */ +--- a/drivers/net/dsa/realtek/rtl8365mb_main.c ++++ b/drivers/net/dsa/realtek/rtl8365mb_main.c +@@ -104,6 +104,7 @@ + #include "realtek-smi.h" + #include "realtek-mdio.h" + #include "rtl83xx.h" ++#include "rtl8365mb_l2.h" + #include "rtl8365mb_vlan.h" + + /* Family-specific data and limits */ +@@ -111,8 +112,12 @@ + #define RTL8365MB_NUM_PHYREGS 32 + #define RTL8365MB_PHYREGMAX (RTL8365MB_NUM_PHYREGS - 1) + #define RTL8365MB_MAX_NUM_PORTS 11 +-#define RTL8365MB_MAX_NUM_EXTINTS 3 ++/* Valid for the whole family except RTL8370B, which has 4160 entries. ++ * RTL8370B is mentioned in vendor code but it might not even belong ++ * to the same RTL8367C family. ++ */ + #define RTL8365MB_LEARN_LIMIT_MAX 2112 ++#define RTL8365MB_MAX_NUM_EXTINTS 3 + + /* Chip identification registers */ + #define RTL8365MB_CHIP_ID_REG 0x1300 +@@ -285,6 +290,15 @@ + (RTL8365MB_PORT_ISOLATION_REG_BASE + (_physport)) + #define RTL8365MB_PORT_ISOLATION_MASK 0x07FF + ++/* Extended filter ID registers - used to key forwarding database with IVL */ ++#define RTL8365MB_EFID_MASK GENMASK(2, 0) ++#define RTL8365MB_PORT_EFID_REG_BASE 0x0A32 ++#define RTL8365MB_PORT_EFID_REG(_p) \ ++ (RTL8365MB_PORT_EFID_REG_BASE + ((_p) >> 2)) ++#define RTL8365MB_PORT_EFID_OFFSET(_p) (((_p) & 0x3) << 2) ++#define RTL8365MB_PORT_EFID_MASK(_p) \ ++ (RTL8365MB_EFID_MASK << RTL8365MB_PORT_EFID_OFFSET(_p)) ++ + /* MSTP port state registers - indexed by tree instance */ + #define RTL8365MB_MSTI_CTRL_BASE 0x0A00 + #define RTL8365MB_MSTI_CTRL_REG(_msti, _physport) \ +@@ -2432,6 +2446,11 @@ static int rtl8365mb_setup(struct dsa_sw + if (ret) + goto out_teardown_irq; + ++ ds->assisted_learning_on_cpu_port = true; ++ ds->fdb_isolation = true; ++ /* The EFID is 3 bits, but EFID 0 is reserved for standalone ports */ ++ ds->max_num_bridges = FIELD_MAX(RTL8365MB_EFID_MASK); ++ + ds->configure_vlan_while_not_filtering = true; + + /* Set up VLAN */ +@@ -2549,6 +2568,12 @@ static const struct dsa_switch_ops rtl83 + .teardown = rtl8365mb_teardown, + .phylink_get_caps = rtl8365mb_phylink_get_caps, + .port_stp_state_set = rtl8365mb_port_stp_state_set, ++ .port_fast_age = rtl83xx_port_fast_age, ++ .port_fdb_add = rtl83xx_port_fdb_add, ++ .port_fdb_del = rtl83xx_port_fdb_del, ++ .port_fdb_dump = rtl83xx_port_fdb_dump, ++ .port_mdb_add = rtl83xx_port_mdb_add, ++ .port_mdb_del = rtl83xx_port_mdb_del, + .port_vlan_add = rtl8365mb_port_vlan_add, + .port_vlan_del = rtl8365mb_port_vlan_del, + .port_vlan_filtering = rtl8365mb_port_vlan_filtering, +@@ -2565,6 +2590,12 @@ static const struct dsa_switch_ops rtl83 + + static const struct realtek_ops rtl8365mb_ops = { + .detect = rtl8365mb_detect, ++ .l2_add_uc = rtl8365mb_l2_add_uc, ++ .l2_del_uc = rtl8365mb_l2_del_uc, ++ .l2_get_next_uc = rtl8365mb_l2_get_next_uc, ++ .l2_add_mc = rtl8365mb_l2_add_mc, ++ .l2_del_mc = rtl8365mb_l2_del_mc, ++ .l2_flush = rtl8365mb_l2_flush, + .phy_read = rtl8365mb_phy_read, + .phy_write = rtl8365mb_phy_write, + }; +--- a/drivers/net/dsa/realtek/rtl83xx.c ++++ b/drivers/net/dsa/realtek/rtl83xx.c +@@ -3,6 +3,7 @@ + #include + #include + #include ++#include + + #include "realtek.h" + #include "rtl83xx.h" +@@ -156,6 +157,7 @@ rtl83xx_probe(struct device *dev, + + mutex_init(&priv->map_lock); + mutex_init(&priv->vlan_lock); ++ mutex_init(&priv->l2_lock); + + rc.lock_arg = priv; + priv->map = devm_regmap_init(dev, NULL, priv, &rc); +@@ -326,6 +328,296 @@ void rtl83xx_reset_deassert(struct realt + gpiod_set_value(priv->reset, false); + } + ++/** ++ * rtl83xx_port_fast_age() - flush dynamic FDB entries learned on a port ++ * @ds: DSA switch instance ++ * @port: port index ++ * ++ * This function requests the switch to age out dynamic FDB entries learned on ++ * @port. ++ * ++ * Context: Can sleep. ++ * Return: Nothing. ++ */ ++void rtl83xx_port_fast_age(struct dsa_switch *ds, int port) ++{ ++ struct realtek_priv *priv = ds->priv; ++ int ret; ++ ++ if (!priv->ops->l2_flush) { ++ dev_warn_once(priv->dev, "l2_flush op not defined\n"); ++ return; ++ } ++ ++ dev_dbg(priv->dev, "fast_age port %d\n", port); ++ ++ mutex_lock(&priv->l2_lock); ++ ret = priv->ops->l2_flush(priv, port, 0); ++ mutex_unlock(&priv->l2_lock); ++ if (ret) ++ dev_err(priv->dev, "failed to fast age on port %d: %d\n", port, ++ ret); ++} ++EXPORT_SYMBOL_NS_GPL(rtl83xx_port_fast_age, "REALTEK_DSA"); ++ ++/** ++ * rtl83xx_port_fdb_add() - add a static FDB entry to a port database ++ * @ds: DSA switch instance ++ * @port: port index ++ * @addr: MAC address to add ++ * @vid: VLAN ID associated with @addr ++ * @db: database where the entry should be added ++ * ++ * This function adds a static unicast FDB entry to the standalone port ++ * database or to a bridge database. ++ * ++ * Context: Can sleep. ++ * Return: 0 on success, negative value for failure. ++ */ ++int rtl83xx_port_fdb_add(struct dsa_switch *ds, int port, ++ const unsigned char *addr, u16 vid, ++ struct dsa_db db) ++{ ++ struct realtek_priv *priv = ds->priv; ++ int efid; ++ int ret; ++ ++ if (is_multicast_ether_addr(addr)) ++ return -EOPNOTSUPP; ++ ++ if (!priv->ops->l2_add_uc) ++ return -EOPNOTSUPP; ++ ++ if (db.type != DSA_DB_PORT && db.type != DSA_DB_BRIDGE) ++ return -EOPNOTSUPP; ++ ++ /* Bridge ports use bridge.num as EFID, while standalone ports use ++ * EFID 0. FDB entries for the CPU port follow the bridge EFID due ++ * to assisted learning. ++ */ ++ efid = db.type == DSA_DB_BRIDGE ? db.bridge.num : 0; ++ ++ dev_dbg(priv->dev, "%s: port:%d addr:%pM efid:%d vid:%d dbtype:%d\n", ++ __func__, port, addr, efid, vid, db.type); ++ ++ mutex_lock(&priv->l2_lock); ++ ret = priv->ops->l2_add_uc(priv, port, addr, efid, vid); ++ ++ mutex_unlock(&priv->l2_lock); ++ ++ if (ret) ++ dev_err(priv->dev, "fdb_add ERROR %pe\n", ERR_PTR(ret)); ++ return ret; ++} ++EXPORT_SYMBOL_NS_GPL(rtl83xx_port_fdb_add, "REALTEK_DSA"); ++ ++/** ++ * rtl83xx_port_fdb_del() - delete a static FDB entry from a port database ++ * @ds: DSA switch instance ++ * @port: port index ++ * @addr: MAC address to delete ++ * @vid: VLAN ID associated with @addr ++ * @db: database where the entry should be removed ++ * ++ * This function deletes a static unicast FDB entry from the standalone port ++ * database or from a bridge database. ++ * ++ * Context: Can sleep. ++ * Return: 0 on success, negative value for failure. ++ */ ++int rtl83xx_port_fdb_del(struct dsa_switch *ds, int port, ++ const unsigned char *addr, u16 vid, ++ struct dsa_db db) ++{ ++ struct realtek_priv *priv = ds->priv; ++ int efid; ++ int ret; ++ ++ if (is_multicast_ether_addr(addr)) ++ return -EOPNOTSUPP; ++ ++ if (!priv->ops->l2_del_uc) ++ return -EOPNOTSUPP; ++ ++ if (db.type != DSA_DB_PORT && db.type != DSA_DB_BRIDGE) ++ return -EOPNOTSUPP; ++ ++ /* ++ * DSA_DB_BRIDGE ports use bridge number [1..N] as EFID, while ++ * DSA_DB_PORT use the default EFID (0), not used by any bridge. ++ */ ++ efid = db.type == DSA_DB_BRIDGE ? db.bridge.num : 0; ++ ++ dev_dbg(priv->dev, "%s: port:%d addr:%pM efid:%d vid:%d dbtype:%d\n", ++ __func__, port, addr, efid, vid, db.type); ++ ++ mutex_lock(&priv->l2_lock); ++ ret = priv->ops->l2_del_uc(priv, port, addr, efid, vid); ++ mutex_unlock(&priv->l2_lock); ++ ++ if (ret) ++ dev_err(priv->dev, "fdb_del ERROR %pe\n", ERR_PTR(ret)); ++ return ret; ++} ++EXPORT_SYMBOL_NS_GPL(rtl83xx_port_fdb_del, "REALTEK_DSA"); ++ ++/** ++ * rtl83xx_port_fdb_dump() - iterate over FDB entries associated with a port ++ * @ds: DSA switch instance ++ * @port: port index ++ * @cb: callback invoked for each entry ++ * @data: opaque pointer passed to @cb ++ * ++ * This function walks the unicast FDB entries associated with @port and calls ++ * @cb for each matching entry. ++ * ++ * Context: Can sleep. ++ * Return: 0 on success, or negative value for failure. ++ */ ++int rtl83xx_port_fdb_dump(struct dsa_switch *ds, int port, ++ dsa_fdb_dump_cb_t *cb, void *data) ++{ ++ struct realtek_fdb_entry entry = { 0 }; ++ struct realtek_priv *priv = ds->priv; ++ u16 start_addr, addr = 0; ++ int ret = 0; ++ ++ if (!priv->ops->l2_get_next_uc) ++ return -EOPNOTSUPP; ++ ++ mutex_lock(&priv->l2_lock); ++ while (true) { ++ start_addr = addr; ++ ++ dev_dbg(priv->dev, "l2_get_next_uc, addr:%d, port:%d\n", ++ addr, port); ++ ret = priv->ops->l2_get_next_uc(priv, &addr, port, &entry); ++ dev_dbg(priv->dev, ++ "%s addr:%d mac:%pM vid:%d static:%d ret:%pe\n", ++ __func__, addr, entry.mac_addr, entry.vid, ++ entry.is_static, ERR_PTR(ret)); ++ ++ if (ret == -ENOENT) { ++ /* If the table is empty, returns without errors. Note ++ * that the l2_get_next_uc overflow to the first match ++ * when it reaches the end of the table. ++ */ ++ ret = 0; ++ break; ++ } ++ ++ if (ret) ++ break; ++ ++ /* When the addr returned is before the requested one, it ++ * indicates that we reached the end. ++ */ ++ if (addr < start_addr) ++ break; ++ ++ ret = cb(entry.mac_addr, entry.vid, entry.is_static, data); ++ if (ret) ++ break; ++ ++ addr++; ++ } ++ mutex_unlock(&priv->l2_lock); ++ ++ return ret; ++} ++EXPORT_SYMBOL_NS_GPL(rtl83xx_port_fdb_dump, "REALTEK_DSA"); ++ ++/** ++ * rtl83xx_port_mdb_add() - add a multicast database entry to a port database ++ * @ds: DSA switch instance ++ * @port: port index ++ * @mdb: multicast database entry to add ++ * @db: database where the entry should be added ++ * ++ * This function adds a multicast database entry to the standalone port ++ * database or to a bridge database. ++ * ++ * Context: Can sleep. ++ * Return: 0 on success, negative value for failure. ++ */ ++int rtl83xx_port_mdb_add(struct dsa_switch *ds, int port, ++ const struct switchdev_obj_port_mdb *mdb, ++ struct dsa_db db) ++{ ++ struct realtek_priv *priv = ds->priv; ++ const unsigned char *addr = mdb->addr; ++ u16 vid = mdb->vid; ++ int efid; ++ int ret; ++ ++ if (!priv->ops->l2_add_mc) ++ return -EOPNOTSUPP; ++ ++ if (db.type != DSA_DB_PORT && db.type != DSA_DB_BRIDGE) ++ return -EOPNOTSUPP; ++ ++ /* EFID is not used by hardware MDB entries; debugging only */ ++ efid = db.type == DSA_DB_BRIDGE ? db.bridge.num : 0; ++ ++ dev_dbg(priv->dev, "%s: port:%d addr:%pM efid:%d vid:%d dbtype:%d\n", ++ __func__, port, addr, efid, vid, db.type); ++ ++ mutex_lock(&priv->l2_lock); ++ ret = priv->ops->l2_add_mc(priv, port, addr, vid); ++ mutex_unlock(&priv->l2_lock); ++ ++ if (ret) ++ dev_err(priv->dev, "mdb_add ERROR %pe\n", ERR_PTR(ret)); ++ return ret; ++} ++EXPORT_SYMBOL_NS_GPL(rtl83xx_port_mdb_add, "REALTEK_DSA"); ++ ++/** ++ * rtl83xx_port_mdb_del() - delete a multicast database entry from a port ++ * database ++ * @ds: DSA switch instance ++ * @port: port index ++ * @mdb: multicast database entry to delete ++ * @db: database where the entry should be removed ++ * ++ * This function deletes a multicast database entry from the standalone port ++ * database or from a bridge database. ++ * ++ * Context: Can sleep. ++ * Return: 0 on success, negative value for failure. ++ */ ++int rtl83xx_port_mdb_del(struct dsa_switch *ds, int port, ++ const struct switchdev_obj_port_mdb *mdb, ++ struct dsa_db db) ++{ ++ struct realtek_priv *priv = ds->priv; ++ const unsigned char *addr = mdb->addr; ++ u16 vid = mdb->vid; ++ int efid; ++ int ret; ++ ++ if (!priv->ops->l2_del_mc) ++ return -EOPNOTSUPP; ++ ++ if (db.type != DSA_DB_PORT && db.type != DSA_DB_BRIDGE) ++ return -EOPNOTSUPP; ++ ++ /* EFID is not used by hardware MDB entries; debugging only */ ++ efid = db.type == DSA_DB_BRIDGE ? db.bridge.num : 0; ++ ++ dev_dbg(priv->dev, "%s: port:%d addr:%pM efid:%d vid:%d dbtype:%d\n", ++ __func__, port, addr, efid, vid, db.type); ++ ++ mutex_lock(&priv->l2_lock); ++ ret = priv->ops->l2_del_mc(priv, port, addr, vid); ++ mutex_unlock(&priv->l2_lock); ++ ++ if (ret) ++ dev_err(priv->dev, "mdb_del ERROR %pe\n", ERR_PTR(ret)); ++ return ret; ++} ++EXPORT_SYMBOL_NS_GPL(rtl83xx_port_mdb_del, "REALTEK_DSA"); ++ + MODULE_AUTHOR("Luiz Angelo Daros de Luca "); + MODULE_AUTHOR("Linus Walleij "); + MODULE_DESCRIPTION("Realtek DSA switches common module"); +--- a/drivers/net/dsa/realtek/rtl83xx.h ++++ b/drivers/net/dsa/realtek/rtl83xx.h +@@ -21,4 +21,20 @@ void rtl83xx_remove(struct realtek_priv + void rtl83xx_reset_assert(struct realtek_priv *priv); + void rtl83xx_reset_deassert(struct realtek_priv *priv); + ++void rtl83xx_port_fast_age(struct dsa_switch *ds, int port); ++int rtl83xx_port_fdb_add(struct dsa_switch *ds, int port, ++ const unsigned char *addr, u16 vid, ++ struct dsa_db db); ++int rtl83xx_port_fdb_del(struct dsa_switch *ds, int port, ++ const unsigned char *addr, u16 vid, ++ struct dsa_db db); ++int rtl83xx_port_fdb_dump(struct dsa_switch *ds, int port, ++ dsa_fdb_dump_cb_t *cb, void *data); ++int rtl83xx_port_mdb_add(struct dsa_switch *ds, int port, ++ const struct switchdev_obj_port_mdb *mdb, ++ struct dsa_db db); ++int rtl83xx_port_mdb_del(struct dsa_switch *ds, int port, ++ const struct switchdev_obj_port_mdb *mdb, ++ struct dsa_db db); ++ + #endif /* _RTL83XX_H */ diff --git a/target/linux/generic/backport-6.18/942-08-7.2-net-dsa-realtek-rtl8365mb-add-port_bridge_-join-leav.patch b/target/linux/generic/backport-6.18/942-08-7.2-net-dsa-realtek-rtl8365mb-add-port_bridge_-join-leav.patch new file mode 100644 index 00000000000..16cb7f43d26 --- /dev/null +++ b/target/linux/generic/backport-6.18/942-08-7.2-net-dsa-realtek-rtl8365mb-add-port_bridge_-join-leav.patch @@ -0,0 +1,335 @@ +From 183bd68b1fe1d5ad584355a7449eea32da79334a Mon Sep 17 00:00:00 2001 +From: Alvin Å ipraga +Date: Sat, 6 Jun 2026 05:29:32 -0300 +Subject: net: dsa: realtek: rtl8365mb: add port_bridge_{join,leave} +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Implement hardware offloading of bridge functionality. This is achieved +by using the per-port isolation registers, which contain a forwarding +port mask. The switch will refuse to forward packets ingressed on a +given port to a port which is not in its forwarding mask. + +For each bridge that is offloaded, use the DSA-provided bridge number +for the Extended Filtering ID (EFID). When using Independent VLAN +Learning (IVL), the forwarding database is keyed with the tuple +{VID, MAC, EFID}. There are 8 EFIDs available (0~7), but we reserve the +default EFID 0 for standalone ports where learning is disabled. This +fits nicely because DSA indexes the bridge number starting from 1. + +Because of the limited number of EFIDs, we have to set the +max_num_bridges property of our switch to 7: we can't offload more than +that or we will fail to offer IVL as at least two bridges would end up +having to share an EFID. + +All ports start isolated, forwarding exclusively to CPU ports, and +with VLAN transparent, ignoring VLAN membership. Once a member in a +bridge, the port isolation is expanded to include the bridge members. +When that bridge enables VLAN filtering, the VLAN transparent feature is +disabled, letting the switch filter based on VLAN setup. + +Signed-off-by: Alvin Å ipraga +Reviewed-by: Linus Walleij +Reviewed-by: Mieczyslaw Nalewaj +Co-developed-by: Luiz Angelo Daros de Luca +Signed-off-by: Luiz Angelo Daros de Luca +Link: https://patch.msgid.link/20260606-realtek_forward-v13-8-b9e409687cbe@gmail.com +Signed-off-by: Jakub Kicinski +--- + drivers/net/dsa/realtek/realtek.h | 7 ++ + drivers/net/dsa/realtek/rtl8365mb_main.c | 47 ++++++++- + drivers/net/dsa/realtek/rtl83xx.c | 169 +++++++++++++++++++++++++++++++ + drivers/net/dsa/realtek/rtl83xx.h | 7 ++ + 4 files changed, 229 insertions(+), 1 deletion(-) + +--- a/drivers/net/dsa/realtek/realtek.h ++++ b/drivers/net/dsa/realtek/realtek.h +@@ -127,6 +127,13 @@ struct realtek_ops { + int (*enable_vlan)(struct realtek_priv *priv, bool enable); + int (*enable_vlan4k)(struct realtek_priv *priv, bool enable); + int (*enable_port)(struct realtek_priv *priv, int port, bool enable); ++ int (*port_add_isolation)(struct realtek_priv *priv, int port, ++ u32 mask); ++ int (*port_remove_isolation)(struct realtek_priv *priv, int port, ++ u32 mask); ++ int (*port_set_efid)(struct realtek_priv *priv, int port, u32 efid); ++ int (*port_set_learning)(struct realtek_priv *priv, int port, ++ bool enable); + int (*l2_add_uc)(struct realtek_priv *priv, int port, + const unsigned char addr[ETH_ALEN], + u16 efid, u16 vid); +--- a/drivers/net/dsa/realtek/rtl8365mb_main.c ++++ b/drivers/net/dsa/realtek/rtl8365mb_main.c +@@ -1568,10 +1568,44 @@ static int rtl8365mb_port_set_learning(s + enable ? RTL8365MB_LEARN_LIMIT_MAX : 0); + } + ++static int rtl8365mb_port_set_efid(struct realtek_priv *priv, int port, ++ u32 efid) ++{ ++ return regmap_update_bits(priv->map, RTL8365MB_PORT_EFID_REG(port), ++ RTL8365MB_PORT_EFID_MASK(port), ++ efid << RTL8365MB_PORT_EFID_OFFSET(port)); ++} ++ ++/* Port isolation manipulation functions. ++ * ++ * The port isolation register controls the forwarding mask of a given ++ * port. The switch will not forward packets ingressed on a given port ++ * to ports which are not enabled in its forwarding mask. ++ * ++ * The port forwarding mask has the highest priority in forwarding ++ * decisions. The only exception to this rule is when the switch ++ * receives a packet on its CPU port with ALLOW=0. In that case the TX ++ * field of the CPU tag will override the forwarding port mask. ++ */ + static int rtl8365mb_port_set_isolation(struct realtek_priv *priv, int port, + u32 mask) + { +- return regmap_write(priv->map, RTL8365MB_PORT_ISOLATION_REG(port), mask); ++ return regmap_write(priv->map, RTL8365MB_PORT_ISOLATION_REG(port), ++ mask); ++} ++ ++static int rtl8365mb_port_add_isolation(struct realtek_priv *priv, int port, ++ u32 mask) ++{ ++ return regmap_update_bits(priv->map, RTL8365MB_PORT_ISOLATION_REG(port), ++ mask, mask); ++} ++ ++static int rtl8365mb_port_remove_isolation(struct realtek_priv *priv, int port, ++ u32 mask) ++{ ++ return regmap_update_bits(priv->map, RTL8365MB_PORT_ISOLATION_REG(port), ++ mask, 0); + } + + static int rtl8365mb_mib_counter_read(struct realtek_priv *priv, int port, +@@ -2378,6 +2412,11 @@ static int rtl8365mb_setup(struct dsa_sw + if (ret) + goto out_teardown_irq; + ++ /* Set the default EFID 0 for standalone mode */ ++ ret = rtl8365mb_port_set_efid(priv, dp->index, 0); ++ if (ret) ++ goto out_teardown_irq; ++ + /* Disable learning */ + ret = rtl8365mb_port_set_learning(priv, dp->index, false); + if (ret) +@@ -2567,6 +2606,8 @@ static const struct dsa_switch_ops rtl83 + .setup = rtl8365mb_setup, + .teardown = rtl8365mb_teardown, + .phylink_get_caps = rtl8365mb_phylink_get_caps, ++ .port_bridge_join = rtl83xx_port_bridge_join, ++ .port_bridge_leave = rtl83xx_port_bridge_leave, + .port_stp_state_set = rtl8365mb_port_stp_state_set, + .port_fast_age = rtl83xx_port_fast_age, + .port_fdb_add = rtl83xx_port_fdb_add, +@@ -2590,6 +2631,10 @@ static const struct dsa_switch_ops rtl83 + + static const struct realtek_ops rtl8365mb_ops = { + .detect = rtl8365mb_detect, ++ .port_add_isolation = rtl8365mb_port_add_isolation, ++ .port_remove_isolation = rtl8365mb_port_remove_isolation, ++ .port_set_efid = rtl8365mb_port_set_efid, ++ .port_set_learning = rtl8365mb_port_set_learning, + .l2_add_uc = rtl8365mb_l2_add_uc, + .l2_del_uc = rtl8365mb_l2_del_uc, + .l2_get_next_uc = rtl8365mb_l2_get_next_uc, +--- a/drivers/net/dsa/realtek/rtl83xx.c ++++ b/drivers/net/dsa/realtek/rtl83xx.c +@@ -329,6 +329,175 @@ void rtl83xx_reset_deassert(struct realt + } + + /** ++ * rtl83xx_port_bridge_join() - join a port to a bridge ++ * @ds: DSA switch instance ++ * @port: port index ++ * @bridge: bridge being joined ++ * @tx_forward_offload: if the switch can offload TX forwarding ++ * @extack: netlink extended ack for reporting errors ++ * ++ * This function handles joining a port to a bridge. It updates the port ++ * isolation masks and EFID. ++ * ++ * Context: Can sleep. ++ * Return: 0 on success, negative value for failure. ++ */ ++int rtl83xx_port_bridge_join(struct dsa_switch *ds, int port, ++ struct dsa_bridge bridge, ++ bool *tx_forward_offload, ++ struct netlink_ext_ack *extack) ++{ ++ struct realtek_priv *priv = ds->priv; ++ struct dsa_port *dp; ++ u32 mask = 0; ++ int ret; ++ ++ if (!priv->ops->port_add_isolation) ++ return -EOPNOTSUPP; ++ ++ if (!priv->ops->port_set_learning) ++ return -EOPNOTSUPP; ++ ++ dev_dbg(priv->dev, "bridge %d join port %d\n", bridge.num, port); ++ ++ /* Add this port to the isolation group of every other port ++ * offloading this bridge. ++ */ ++ dsa_switch_for_each_user_port(dp, ds) { ++ /* Handle this port after */ ++ if (dp->index == port) ++ continue; ++ ++ /* Skip ports that are not in this bridge */ ++ if (!dsa_port_offloads_bridge(dp, &bridge)) ++ continue; ++ ++ ret = priv->ops->port_add_isolation(priv, dp->index, BIT(port)); ++ if (ret) ++ goto undo_isolation; ++ ++ mask |= BIT(dp->index); ++ } ++ ++ /* If we support cascade switches, it should also include the ++ * downstream DSA ports to the isolation group. ++ */ ++ ++ /* Add those ports to the isolation group of this port */ ++ ret = priv->ops->port_add_isolation(priv, port, mask); ++ if (ret) ++ goto undo_isolation; ++ ++ /* Use the bridge number as the EFID for this port */ ++ if (priv->ops->port_set_efid) { ++ ret = priv->ops->port_set_efid(priv, port, bridge.num); ++ if (ret) ++ goto undo_self_isolation; ++ } ++ ++ ret = priv->ops->port_set_learning(priv, port, true); ++ if (ret) ++ goto undo_efid; ++ ++ return 0; ++ ++undo_efid: ++ if (priv->ops->port_set_efid) ++ priv->ops->port_set_efid(priv, port, 0); ++ ++undo_self_isolation: ++ priv->ops->port_remove_isolation(priv, port, mask); ++ ++undo_isolation: ++ dsa_switch_for_each_port(dp, ds) { ++ if (mask & BIT(dp->index)) ++ priv->ops->port_remove_isolation(priv, dp->index, ++ BIT(port)); ++ } ++ ++ return ret; ++} ++EXPORT_SYMBOL_NS_GPL(rtl83xx_port_bridge_join, "REALTEK_DSA"); ++ ++/** ++ * rtl83xx_port_bridge_leave() - leave a bridge ++ * @ds: DSA switch instance ++ * @port: port index ++ * @bridge: bridge being left ++ * ++ * This function handles removing a port from a bridge. It updates the port ++ * isolation masks and EFID. ++ * ++ * Context: Can sleep. ++ * Return: nothing ++ */ ++void rtl83xx_port_bridge_leave(struct dsa_switch *ds, int port, ++ struct dsa_bridge bridge) ++{ ++ struct realtek_priv *priv = ds->priv; ++ struct dsa_port *dp; ++ u32 mask = 0; ++ int ret; ++ ++ if (!priv->ops->port_remove_isolation) ++ return; ++ ++ if (!priv->ops->port_set_learning) ++ return; ++ ++ dev_dbg(priv->dev, "bridge %d leave port %d\n", bridge.num, port); ++ ++ /* Remove this port from the isolation group of every other ++ * port offloading this bridge. ++ */ ++ dsa_switch_for_each_user_port(dp, ds) { ++ /* Handle this port after */ ++ if (dp->index == port) ++ continue; ++ ++ /* Skip ports that are not in this bridge */ ++ if (!dsa_port_offloads_bridge(dp, &bridge)) ++ continue; ++ ++ ret = priv->ops->port_remove_isolation(priv, dp->index, ++ BIT(port)); ++ if (ret) ++ dev_err(priv->dev, ++ "failed to isolate port %d from port %d: %pe\n", ++ port, dp->index, ERR_PTR(ret)); ++ ++ mask |= BIT(dp->index); ++ } ++ ++ /* If we support cascade switches, it should also exclude the ++ * downstream DSA ports from the isolation group. ++ */ ++ ++ ret = priv->ops->port_set_learning(priv, port, false); ++ if (ret) ++ dev_err(priv->dev, ++ "failed to disable learning on port %d: %pe\n", ++ port, ERR_PTR(ret)); ++ ++ /* Remove those ports from the isolation group of this port */ ++ ret = priv->ops->port_remove_isolation(priv, port, mask); ++ if (ret) ++ dev_err(priv->dev, ++ "failed to remove isolation mask from port %d: %pe\n", ++ port, ERR_PTR(ret)); ++ ++ /* Revert to the default EFID 0 for standalone mode */ ++ if (priv->ops->port_set_efid) { ++ ret = priv->ops->port_set_efid(priv, port, 0); ++ if (ret) ++ dev_err(priv->dev, ++ "failed to clear EFID on port %d: %pe\n", ++ port, ERR_PTR(ret)); ++ } ++} ++EXPORT_SYMBOL_NS_GPL(rtl83xx_port_bridge_leave, "REALTEK_DSA"); ++ ++/** + * rtl83xx_port_fast_age() - flush dynamic FDB entries learned on a port + * @ds: DSA switch instance + * @port: port index +--- a/drivers/net/dsa/realtek/rtl83xx.h ++++ b/drivers/net/dsa/realtek/rtl83xx.h +@@ -21,6 +21,13 @@ void rtl83xx_remove(struct realtek_priv + void rtl83xx_reset_assert(struct realtek_priv *priv); + void rtl83xx_reset_deassert(struct realtek_priv *priv); + ++int rtl83xx_port_bridge_join(struct dsa_switch *ds, int port, ++ struct dsa_bridge bridge, ++ bool *tx_forward_offload, ++ struct netlink_ext_ack *extack); ++void rtl83xx_port_bridge_leave(struct dsa_switch *ds, int port, ++ struct dsa_bridge bridge); ++ + void rtl83xx_port_fast_age(struct dsa_switch *ds, int port); + int rtl83xx_port_fdb_add(struct dsa_switch *ds, int port, + const unsigned char *addr, u16 vid, diff --git a/target/linux/generic/backport-6.18/942-09-7.2-net-dsa-realtek-rtl8365mb-add-bridge-port-flags.patch b/target/linux/generic/backport-6.18/942-09-7.2-net-dsa-realtek-rtl8365mb-add-bridge-port-flags.patch new file mode 100644 index 00000000000..0d029bfde8f --- /dev/null +++ b/target/linux/generic/backport-6.18/942-09-7.2-net-dsa-realtek-rtl8365mb-add-bridge-port-flags.patch @@ -0,0 +1,279 @@ +From 660a9e399ab02c0cb86d277ed6b0c9d10c350fdd Mon Sep 17 00:00:00 2001 +From: Luiz Angelo Daros de Luca +Date: Sat, 6 Jun 2026 05:29:33 -0300 +Subject: net: dsa: realtek: rtl8365mb: add bridge port flags + +Implement support for bridge port flags to control learning and flooding +behavior. This patch maps hardware functionalities to the following +bridge flags: + +- BR_LEARNING +- BR_FLOOD +- BR_MCAST_FLOOD +- BR_BCAST_FLOOD + +By default, all flooding types are enabled during port setup to ensure +standard bridge behavior. + +Reviewed-by: Linus Walleij +Reviewed-by: Mieczyslaw Nalewaj +Signed-off-by: Luiz Angelo Daros de Luca +Link: https://patch.msgid.link/20260606-realtek_forward-v13-9-b9e409687cbe@gmail.com +Signed-off-by: Jakub Kicinski +--- + drivers/net/dsa/realtek/realtek.h | 6 ++ + drivers/net/dsa/realtek/rtl8365mb_main.c | 68 +++++++++++++++++++++ + drivers/net/dsa/realtek/rtl83xx.c | 101 +++++++++++++++++++++++++++++++ + drivers/net/dsa/realtek/rtl83xx.h | 4 ++ + 4 files changed, 179 insertions(+) + +--- a/drivers/net/dsa/realtek/realtek.h ++++ b/drivers/net/dsa/realtek/realtek.h +@@ -134,6 +134,12 @@ struct realtek_ops { + int (*port_set_efid)(struct realtek_priv *priv, int port, u32 efid); + int (*port_set_learning)(struct realtek_priv *priv, int port, + bool enable); ++ int (*port_set_ucast_flood)(struct realtek_priv *priv, int port, ++ bool enable); ++ int (*port_set_mcast_flood)(struct realtek_priv *priv, int port, ++ bool enable); ++ int (*port_set_bcast_flood)(struct realtek_priv *priv, int port, ++ bool enable); + int (*l2_add_uc)(struct realtek_priv *priv, int port, + const unsigned char addr[ETH_ALEN], + u16 efid, u16 vid); +--- a/drivers/net/dsa/realtek/rtl8365mb_main.c ++++ b/drivers/net/dsa/realtek/rtl8365mb_main.c +@@ -307,6 +307,21 @@ + #define RTL8365MB_MSTI_CTRL_PORT_STATE_MASK(_physport) \ + (0x3 << RTL8365MB_MSTI_CTRL_PORT_STATE_OFFSET((_physport))) + ++/* Unknown unicast DA flooding port mask */ ++#define RTL8365MB_UNKNOWN_UNICAST_FLOODING_PMASK_REG 0x0890 ++#define RTL8365MB_UNKNOWN_UNICAST_FLOODING_PMASK_MASK 0x07FF ++ ++/* Unknown multicast DA flooding port mask */ ++#define RTL8365MB_UNKNOWN_MULTICAST_FLOODING_PMASK_REG 0x0891 ++#define RTL8365MB_UNKNOWN_MULTICAST_FLOODING_PMASK_MASK 0x07FF ++ ++/* Broadcast flooding port mask */ ++#define RTL8365MB_UNKNOWN_BROADCAST_FLOODING_PMASK_REG 0x0892 ++#define RTL8365MB_UNKNOWN_BROADCAST_FLOODING_PMASK_MASK 0x07FF ++ ++#define RTL8365MB_SUPPORTED_BRIDGE_FLAGS \ ++ (BR_LEARNING | BR_FLOOD | BR_MCAST_FLOOD | BR_BCAST_FLOOD) ++ + /* Miscellaneous port configuration register, incl. VLAN egress mode */ + #define RTL8365MB_PORT_MISC_CFG_REG_BASE 0x000E + #define RTL8365MB_PORT_MISC_CFG_REG(_p) \ +@@ -1568,6 +1583,49 @@ static int rtl8365mb_port_set_learning(s + enable ? RTL8365MB_LEARN_LIMIT_MAX : 0); + } + ++static int rtl8365mb_port_set_ucast_flood(struct realtek_priv *priv, int port, ++ bool enable) ++{ ++ /* Frames with unknown unicast DA will be flooded to a programmable ++ * port mask that by default includes all ports. Add or remove ++ * the specified port from this port mask accordingly. ++ */ ++ return regmap_update_bits(priv->map, ++ RTL8365MB_UNKNOWN_UNICAST_FLOODING_PMASK_REG, ++ BIT(port), enable ? BIT(port) : 0); ++} ++ ++static int rtl8365mb_port_set_mcast_flood(struct realtek_priv *priv, int port, ++ bool enable) ++{ ++ return regmap_update_bits(priv->map, ++ RTL8365MB_UNKNOWN_MULTICAST_FLOODING_PMASK_REG, ++ BIT(port), enable ? BIT(port) : 0); ++} ++ ++static int rtl8365mb_port_set_bcast_flood(struct realtek_priv *priv, int port, ++ bool enable) ++{ ++ return regmap_update_bits(priv->map, ++ RTL8365MB_UNKNOWN_BROADCAST_FLOODING_PMASK_REG, ++ BIT(port), enable ? BIT(port) : 0); ++} ++ ++static int rtl8365mb_port_pre_bridge_flags(struct dsa_switch *ds, int port, ++ struct switchdev_brport_flags flags, ++ struct netlink_ext_ack *extack) ++{ ++ struct realtek_priv *priv = ds->priv; ++ ++ dev_dbg(priv->dev, "pre_bridge_flags port:%d flags:%lx supported:%lx\n", ++ port, flags.mask, RTL8365MB_SUPPORTED_BRIDGE_FLAGS); ++ ++ if (flags.mask & ~RTL8365MB_SUPPORTED_BRIDGE_FLAGS) ++ return -EINVAL; ++ ++ return 0; ++} ++ + static int rtl8365mb_port_set_efid(struct realtek_priv *priv, int port, + u32 efid) + { +@@ -2422,6 +2480,11 @@ static int rtl8365mb_setup(struct dsa_sw + if (ret) + goto out_teardown_irq; + ++ /* Enable all types of flooding */ ++ ret = rtl83xx_setup_port_flood_control(priv, dp->index); ++ if (ret) ++ goto out_teardown_irq; ++ + /* Set up per-port private data */ + p->priv = priv; + p->index = dp->index; +@@ -2608,6 +2671,8 @@ static const struct dsa_switch_ops rtl83 + .phylink_get_caps = rtl8365mb_phylink_get_caps, + .port_bridge_join = rtl83xx_port_bridge_join, + .port_bridge_leave = rtl83xx_port_bridge_leave, ++ .port_pre_bridge_flags = rtl8365mb_port_pre_bridge_flags, ++ .port_bridge_flags = rtl83xx_port_bridge_flags, + .port_stp_state_set = rtl8365mb_port_stp_state_set, + .port_fast_age = rtl83xx_port_fast_age, + .port_fdb_add = rtl83xx_port_fdb_add, +@@ -2635,6 +2700,9 @@ static const struct realtek_ops rtl8365m + .port_remove_isolation = rtl8365mb_port_remove_isolation, + .port_set_efid = rtl8365mb_port_set_efid, + .port_set_learning = rtl8365mb_port_set_learning, ++ .port_set_ucast_flood = rtl8365mb_port_set_ucast_flood, ++ .port_set_mcast_flood = rtl8365mb_port_set_mcast_flood, ++ .port_set_bcast_flood = rtl8365mb_port_set_bcast_flood, + .l2_add_uc = rtl8365mb_l2_add_uc, + .l2_del_uc = rtl8365mb_l2_del_uc, + .l2_get_next_uc = rtl8365mb_l2_get_next_uc, +--- a/drivers/net/dsa/realtek/rtl83xx.c ++++ b/drivers/net/dsa/realtek/rtl83xx.c +@@ -3,6 +3,7 @@ + #include + #include + #include ++#include + #include + + #include "realtek.h" +@@ -787,6 +788,106 @@ int rtl83xx_port_mdb_del(struct dsa_swit + } + EXPORT_SYMBOL_NS_GPL(rtl83xx_port_mdb_del, "REALTEK_DSA"); + ++/** ++ * rtl83xx_port_bridge_flags() - set port bridge flags ++ * @ds: DSA switch instance ++ * @port: port index ++ * @flags: bridge port flags ++ * @extack: netlink extended ack for reporting errors ++ * ++ * This function handles setting bridge port flags like learning and flooding. ++ * ++ * Context: Can sleep. ++ * Return: 0 on success, negative value for failure. ++ */ ++int rtl83xx_port_bridge_flags(struct dsa_switch *ds, int port, ++ struct switchdev_brport_flags flags, ++ struct netlink_ext_ack *extack) ++{ ++ struct realtek_priv *priv = ds->priv; ++ bool enable; ++ int ret; ++ ++ if (flags.mask & BR_LEARNING) { ++ if (!priv->ops->port_set_learning) ++ return -EOPNOTSUPP; ++ ++ enable = !!(flags.val & BR_LEARNING); ++ ret = priv->ops->port_set_learning(priv, port, enable); ++ if (ret) ++ return ret; ++ } ++ ++ if (flags.mask & BR_FLOOD) { ++ if (!priv->ops->port_set_ucast_flood) ++ return -EOPNOTSUPP; ++ ++ enable = !!(flags.val & BR_FLOOD); ++ ret = priv->ops->port_set_ucast_flood(priv, port, enable); ++ if (ret) ++ return ret; ++ } ++ ++ if (flags.mask & BR_MCAST_FLOOD) { ++ if (!priv->ops->port_set_mcast_flood) ++ return -EOPNOTSUPP; ++ ++ enable = !!(flags.val & BR_MCAST_FLOOD); ++ ret = priv->ops->port_set_mcast_flood(priv, port, enable); ++ if (ret) ++ return ret; ++ } ++ ++ if (flags.mask & BR_BCAST_FLOOD) { ++ if (!priv->ops->port_set_bcast_flood) ++ return -EOPNOTSUPP; ++ ++ enable = !!(flags.val & BR_BCAST_FLOOD); ++ ret = priv->ops->port_set_bcast_flood(priv, port, enable); ++ if (ret) ++ return ret; ++ } ++ ++ return 0; ++} ++EXPORT_SYMBOL_NS_GPL(rtl83xx_port_bridge_flags, "REALTEK_DSA"); ++ ++/** ++ * rtl83xx_setup_port_flood_control() - setup default flood control for a port ++ * @priv: realtek_priv pointer ++ * @port: port index ++ * ++ * This function enables flooding for a given port. ++ * ++ * Context: Can sleep. ++ * Return: 0 on success, negative value for failure. ++ */ ++int rtl83xx_setup_port_flood_control(struct realtek_priv *priv, int port) ++{ ++ int ret; ++ ++ if (priv->ops->port_set_ucast_flood) { ++ ret = priv->ops->port_set_ucast_flood(priv, port, true); ++ if (ret) ++ return ret; ++ } ++ ++ if (priv->ops->port_set_mcast_flood) { ++ ret = priv->ops->port_set_mcast_flood(priv, port, true); ++ if (ret) ++ return ret; ++ } ++ ++ if (priv->ops->port_set_bcast_flood) { ++ ret = priv->ops->port_set_bcast_flood(priv, port, true); ++ if (ret) ++ return ret; ++ } ++ ++ return 0; ++} ++EXPORT_SYMBOL_NS_GPL(rtl83xx_setup_port_flood_control, "REALTEK_DSA"); ++ + MODULE_AUTHOR("Luiz Angelo Daros de Luca "); + MODULE_AUTHOR("Linus Walleij "); + MODULE_DESCRIPTION("Realtek DSA switches common module"); +--- a/drivers/net/dsa/realtek/rtl83xx.h ++++ b/drivers/net/dsa/realtek/rtl83xx.h +@@ -27,6 +27,10 @@ int rtl83xx_port_bridge_join(struct dsa_ + struct netlink_ext_ack *extack); + void rtl83xx_port_bridge_leave(struct dsa_switch *ds, int port, + struct dsa_bridge bridge); ++int rtl83xx_port_bridge_flags(struct dsa_switch *ds, int port, ++ struct switchdev_brport_flags flags, ++ struct netlink_ext_ack *extack); ++int rtl83xx_setup_port_flood_control(struct realtek_priv *priv, int port); + + void rtl83xx_port_fast_age(struct dsa_switch *ds, int port); + int rtl83xx_port_fdb_add(struct dsa_switch *ds, int port,