The RTL8367S-VB (chip ID 0x6642) is supported by the rtl8365mb driver with the changes that follow in this series. It is identified by its chip ID register, so it does not need a compatible string of its own. List it under realtek,rtl8365mb. Signed-off-by: Mieczyslaw Nalewaj Reviewed-by: Linus Walleij Reviewed-by: Luiz Angelo Daros de Luca --- Documentation/devicetree/bindings/net/dsa/realtek.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/net/dsa/realtek.yaml b/Documentation/devicetree/bindings/net/dsa/realtek.yaml index 473facd..ca775f6 100644 --- a/Documentation/devicetree/bindings/net/dsa/realtek.yaml +++ b/Documentation/devicetree/bindings/net/dsa/realtek.yaml @@ -43,7 +43,7 @@ properties: realtek,rtl8365mb: Use with models RTL8363NB, RTL8363NB-VB, RTL8363SC, RTL8363SC-VB, RTL8364NB, RTL8364NB-VB, RTL8365MB, RTL8366SC, RTL8367RB-VB, RTL8367S, - RTL8367SB, RTL8370MB, RTL8310SR + RTL8367S-VB, RTL8367SB, RTL8370MB, RTL8310SR realtek,rtl8366rb: Use with models RTL8366RB, RTL8366S -- 2.53.0 Add enum rtl8365mb_family and struct rtl8365mb_family_info, which holds the data shared by all chips of one family: the family id, the port count and the init jam table. struct rtl8365mb_chip_info now points to it through a family_info member, and all existing chips use the RTL8365MB_FAMILY_C instance. rtl8365mb_get_family_info() and rtl8365mb_is_d() are declared in a new rtl8365mb.h so that the VLAN code can use them too. Use num_ports from the family info in rtl8365mb_detect() instead of a fixed constant, so that a family with a different port count can be described by its own entry. RTL8365MB_FAMILY_D is defined here but no chip uses it yet; it is needed by the following patches, which add family D handling for chips whose register layout differs from the existing ones. No functional change. Tested-by: Gustavo Foletto Mora Tested-by: Stanislaw Pal Signed-off-by: Mieczyslaw Nalewaj Reviewed-by: Linus Walleij --- drivers/net/dsa/realtek/rtl8365mb.h | 29 +++++++++++++ drivers/net/dsa/realtek/rtl8365mb_main.c | 54 +++++++++++++++--------- 2 files changed, 62 insertions(+), 21 deletions(-) create mode 100644 drivers/net/dsa/realtek/rtl8365mb.h diff --git a/drivers/net/dsa/realtek/rtl8365mb.h b/drivers/net/dsa/realtek/rtl8365mb.h new file mode 100644 index 0000000..daa8f0b --- /dev/null +++ b/drivers/net/dsa/realtek/rtl8365mb.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _RTL8365MB_H +#define _RTL8365MB_H + +#include "realtek.h" + +enum rtl8365mb_family { + RTL8365MB_FAMILY_C, + RTL8365MB_FAMILY_D, +}; + +struct rtl8365mb_jam_tbl_entry; + +struct rtl8365mb_family_info { + enum rtl8365mb_family family; + unsigned int num_ports; + const struct rtl8365mb_jam_tbl_entry *jam_table; + size_t jam_size; +}; + +const struct rtl8365mb_family_info * +rtl8365mb_get_family_info(struct realtek_priv *priv); + +static inline bool rtl8365mb_is_d(struct realtek_priv *priv) +{ + return rtl8365mb_get_family_info(priv)->family == RTL8365MB_FAMILY_D; +} + +#endif /* _RTL8365MB_H */ diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c index da06f95..e25eb7e 100644 --- a/drivers/net/dsa/realtek/rtl8365mb_main.c +++ b/drivers/net/dsa/realtek/rtl8365mb_main.c @@ -110,12 +110,15 @@ #include "rtl83xx.h" #include "rtl8365mb_l2.h" #include "rtl8365mb_vlan.h" +#include "rtl8365mb.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_C_MAX_NUM_PORTS 11 +/* Highest port count of any supported chip, used for sizing */ +#define RTL8365MB_MAX_NUM_PORTS RTL8365MB_C_MAX_NUM_PORTS /* 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. @@ -702,14 +705,20 @@ struct rtl8365mb_extint { unsigned int supported_interfaces; }; +static const struct rtl8365mb_family_info rtl8365mb_family_c = { + .family = RTL8365MB_FAMILY_C, + .num_ports = RTL8365MB_C_MAX_NUM_PORTS, + .jam_table = rtl8365mb_init_jam_8365mb_vc, + .jam_size = ARRAY_SIZE(rtl8365mb_init_jam_8365mb_vc), +}; + /** * struct rtl8365mb_chip_info - static chip-specific info * @name: human-readable chip name * @chip_id: chip identifier * @chip_ver: chip silicon revision + * @family_info: data shared by all chips of the same family * @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 @@ -719,9 +728,8 @@ struct rtl8365mb_chip_info { const char *name; u32 chip_id; u32 chip_ver; + const struct rtl8365mb_family_info *family_info; 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 */ @@ -731,29 +739,28 @@ static const struct rtl8365mb_chip_info rtl8365mb_chip_infos[] = { .name = "RTL8365MB-VC", .chip_id = 0x6367, .chip_ver = 0x0040, + .family_info = &rtl8365mb_family_c, .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, + .family_info = &rtl8365mb_family_c, .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, + .family_info = &rtl8365mb_family_c, .extints = { { 6, 1, PHY_INTF(MII) | PHY_INTF(TMII) | PHY_INTF(RMII) | PHY_INTF(RGMII) | @@ -761,21 +768,18 @@ static const struct rtl8365mb_chip_info rtl8365mb_chip_infos[] = { { 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, + .family_info = &rtl8365mb_family_c, .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), }, }; @@ -874,6 +878,14 @@ struct rtl8365mb { #define pcs_to_rtl8365mb(_pcs) container_of((_pcs), struct rtl8365mb, pcs) +const struct rtl8365mb_family_info * +rtl8365mb_get_family_info(struct realtek_priv *priv) +{ + struct rtl8365mb *mb = priv->chip_data; + + return mb->chip_info->family_info; +} + static int rtl8365mb_phy_poll_busy(struct realtek_priv *priv) { u32 val; @@ -3036,17 +3048,17 @@ static int rtl8365mb_change_tag_protocol(struct dsa_switch *ds, static int rtl8365mb_switch_init(struct realtek_priv *priv) { struct rtl8365mb *mb = priv->chip_data; - const struct rtl8365mb_chip_info *ci; + const struct rtl8365mb_family_info *fi; int ret; int i; - ci = mb->chip_info; + fi = mb->chip_info->family_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); + /* Do any family-specific init jam before getting to the common stuff */ + if (fi->jam_table) { + for (i = 0; i < fi->jam_size; i++) { + ret = regmap_write(priv->map, fi->jam_table[i].reg, + fi->jam_table[i].val); if (ret) return ret; } @@ -3349,7 +3361,7 @@ static int rtl8365mb_detect(struct realtek_priv *priv) dev_info(priv->dev, "found an %s switch\n", mb->chip_info->name); - priv->num_ports = RTL8365MB_MAX_NUM_PORTS; + priv->num_ports = mb->chip_info->family_info->num_ports; mb->priv = priv; mb->cpu.trap_port = RTL8365MB_MAX_NUM_PORTS; mb->cpu.insert = RTL8365MB_CPU_INSERT_TO_ALL; -- 2.53.0 Move the external interface force-mode configuration behind struct rtl8365mb_family_info, so that family D can provide its own versions without is_d() checks in the shared code. The existing code becomes the family C implementation and is otherwise unchanged. The mac_link_up() and mac_link_down() callbacks call it through the family info. No functional change. Tested-by: Gustavo Foletto Mora Tested-by: Stanislaw Pal Signed-off-by: Mieczyslaw Nalewaj --- drivers/net/dsa/realtek/rtl8365mb.h | 3 ++ drivers/net/dsa/realtek/rtl8365mb_main.c | 44 ++++++++++++++---------- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/drivers/net/dsa/realtek/rtl8365mb.h b/drivers/net/dsa/realtek/rtl8365mb.h index daa8f0b..664bddb 100644 --- a/drivers/net/dsa/realtek/rtl8365mb.h +++ b/drivers/net/dsa/realtek/rtl8365mb.h @@ -16,6 +16,9 @@ struct rtl8365mb_family_info { unsigned int num_ports; const struct rtl8365mb_jam_tbl_entry *jam_table; size_t jam_size; + int (*ext_config_forcemode)(struct realtek_priv *priv, int port, + bool link, int speed, int duplex, + bool tx_pause, bool rx_pause); }; const struct rtl8365mb_family_info * diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c index e25eb7e..ef75dc2 100644 --- a/drivers/net/dsa/realtek/rtl8365mb_main.c +++ b/drivers/net/dsa/realtek/rtl8365mb_main.c @@ -348,15 +348,15 @@ #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 : \ +#define RTL8365MB_C_DIGITAL_INTERFACE_FORCE_REG0 0x1310 /* EXT0 */ +#define RTL8365MB_C_DIGITAL_INTERFACE_FORCE_REG1 0x1311 /* EXT1 */ +#define RTL8365MB_C_DIGITAL_INTERFACE_FORCE_REG2 0x13C4 /* EXT2 */ +#define RTL8365MB_C_DIGITAL_INTERFACE_FORCE_REG(_extint) \ + ((_extint) == 0 ? RTL8365MB_C_DIGITAL_INTERFACE_FORCE_REG0 : \ + (_extint) == 1 ? RTL8365MB_C_DIGITAL_INTERFACE_FORCE_REG1 : \ + (_extint) == 2 ? RTL8365MB_C_DIGITAL_INTERFACE_FORCE_REG2 : \ 0x0) -#define RTL8365MB_DIGITAL_INTERFACE_FORCE_EN_MASK BIT(12) +#define RTL8365MB_C_DIGITAL_INTERFACE_FORCE_EN_MASK BIT(12) #define RTL8365MB_DIGITAL_INTERFACE_FORCE_NWAY_MASK BIT(7) #define RTL8365MB_DIGITAL_INTERFACE_FORCE_TXPAUSE_MASK BIT(6) #define RTL8365MB_DIGITAL_INTERFACE_FORCE_RXPAUSE_MASK BIT(5) @@ -705,11 +705,16 @@ struct rtl8365mb_extint { unsigned int supported_interfaces; }; +static int rtl8365mb_c_ext_config_forcemode(struct realtek_priv *priv, int port, + bool link, int speed, int duplex, + bool tx_pause, bool rx_pause); + static const struct rtl8365mb_family_info rtl8365mb_family_c = { .family = RTL8365MB_FAMILY_C, .num_ports = RTL8365MB_C_MAX_NUM_PORTS, .jam_table = rtl8365mb_init_jam_8365mb_vc, .jam_size = ARRAY_SIZE(rtl8365mb_init_jam_8365mb_vc), + .ext_config_forcemode = rtl8365mb_c_ext_config_forcemode, }; /** @@ -1633,9 +1638,9 @@ static const struct phylink_pcs_ops rtl8365mb_pcs_ops = { .pcs_link_up = rtl8365mb_pcs_link_up, }; -static int rtl8365mb_ext_config_forcemode(struct realtek_priv *priv, int port, - bool link, int speed, int duplex, - bool tx_pause, bool rx_pause) +static int rtl8365mb_c_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); @@ -1690,7 +1695,7 @@ static int rtl8365mb_ext_config_forcemode(struct realtek_priv *priv, int port, r_duplex = 0; } - val = FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_EN_MASK, 1) | + val = FIELD_PREP(RTL8365MB_C_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, @@ -1700,7 +1705,7 @@ static int rtl8365mb_ext_config_forcemode(struct realtek_priv *priv, int port, r_duplex) | FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_SPEED_MASK, r_speed); ret = regmap_write(priv->map, - RTL8365MB_DIGITAL_INTERFACE_FORCE_REG(extint->id), + RTL8365MB_C_DIGITAL_INTERFACE_FORCE_REG(extint->id), val); if (ret) return ret; @@ -1816,12 +1821,14 @@ static void rtl8365mb_phylink_mac_link_down(struct phylink_config *config, { struct dsa_port *dp = dsa_phylink_to_port(config); struct realtek_priv *priv = dp->ds->priv; + const struct rtl8365mb_family_info *fi; struct rtl8365mb_port *p; struct rtl8365mb *mb; u8 port = dp->index; int ret; mb = priv->chip_data; + fi = rtl8365mb_get_family_info(priv); p = &mb->ports[port]; cancel_delayed_work_sync(&p->mib_work); @@ -1832,8 +1839,8 @@ static void rtl8365mb_phylink_mac_link_down(struct phylink_config *config, */ if (phy_interface_mode_is_rgmii(interface) || rtl8365mb_interface_is_serdes(interface)) { - ret = rtl8365mb_ext_config_forcemode(priv, port, false, 0, 0, - false, false); + ret = fi->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", @@ -1852,12 +1859,14 @@ static void rtl8365mb_phylink_mac_link_up(struct phylink_config *config, { struct dsa_port *dp = dsa_phylink_to_port(config); struct realtek_priv *priv = dp->ds->priv; + const struct rtl8365mb_family_info *fi; struct rtl8365mb_port *p; struct rtl8365mb *mb; u8 port = dp->index; int ret; mb = priv->chip_data; + fi = rtl8365mb_get_family_info(priv); p = &mb->ports[port]; schedule_delayed_work(&p->mib_work, 0); @@ -1867,9 +1876,8 @@ static void rtl8365mb_phylink_mac_link_up(struct phylink_config *config, */ if (phy_interface_mode_is_rgmii(interface) || rtl8365mb_interface_is_serdes(interface)) { - ret = rtl8365mb_ext_config_forcemode(priv, port, true, speed, - duplex, tx_pause, - rx_pause); + ret = fi->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, -- 2.53.0 On family C hardware, forcing a 2.5 Gbps link speed requires relying strictly on the HSGMII SerDes configuration block, while forcing a dummy 1 Gbps value inside the standard digital interface register. Family D introduces a dedicated hardware link speed value (5) for 2500M. The speed bitfield width has been expanded to 3 bits, splitting the most significant bit into a new separate register field (FORCE_SPEED2), which maps directly to a distinct per-port digital interface force layout. On family D the forced-mode configuration (link, duplex, pause, speed) is written to a per-port register at 0x12c0 + port instead of the per-extint register. The bit positions of TXPAUSE, RXPAUSE, LINK, DUPLEX and the low two speed bits are the same as on family C; the upper speed bits go to bits 13:12 (FORCE_SPEED2), so FORCE_EN (bit 12 on family C) is not set. A second per-port register at 0x12c8 + port enables the forced mode: the vendor RTL8367D API writes 0xFFFF there when the MAC is forced and 0 otherwise. This driver always forces the MAC, as on family C, so it writes 0xFFFF on every call, including link down. Add the family D implementation as rtl8365mb_d_ext_config_forcemode(). The shared code reaches it through the ext_config_forcemode pointer in struct rtl8365mb_family_info, so it needs no is_d() check. Nothing points to it until the family D info is added together with the chip table entry in the last patch. Until then it is not static and is declared in rtl8365mb.h, so that the build does not warn about an unused function. Tested-by: Gustavo Foletto Mora Signed-off-by: Mieczyslaw Nalewaj Reviewed-by: Linus Walleij --- drivers/net/dsa/realtek/rtl8365mb.h | 4 + drivers/net/dsa/realtek/rtl8365mb_main.c | 110 +++++++++++++++++++++++ 2 files changed, 114 insertions(+) diff --git a/drivers/net/dsa/realtek/rtl8365mb.h b/drivers/net/dsa/realtek/rtl8365mb.h index 664bddb..3d607bd 100644 --- a/drivers/net/dsa/realtek/rtl8365mb.h +++ b/drivers/net/dsa/realtek/rtl8365mb.h @@ -24,6 +24,10 @@ struct rtl8365mb_family_info { const struct rtl8365mb_family_info * rtl8365mb_get_family_info(struct realtek_priv *priv); +int rtl8365mb_d_ext_config_forcemode(struct realtek_priv *priv, int port, + bool link, int speed, int duplex, + bool tx_pause, bool rx_pause); + static inline bool rtl8365mb_is_d(struct realtek_priv *priv) { return rtl8365mb_get_family_info(priv)->family == RTL8365MB_FAMILY_D; diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c index ef75dc2..27a6917 100644 --- a/drivers/net/dsa/realtek/rtl8365mb_main.c +++ b/drivers/net/dsa/realtek/rtl8365mb_main.c @@ -346,6 +346,8 @@ #define RTL8365MB_PORT_SPEED_10M 0 #define RTL8365MB_PORT_SPEED_100M 1 #define RTL8365MB_PORT_SPEED_1000M 2 +#define RTL8365MB_D_PORT_SPEED_2500M 5 +#define RTL8365MB_D_PORT_SPEED_MSB_MASK BIT(2) /* External interface force configuration registers 0~2 */ #define RTL8365MB_C_DIGITAL_INTERFACE_FORCE_REG0 0x1310 /* EXT0 */ @@ -364,6 +366,26 @@ #define RTL8365MB_DIGITAL_INTERFACE_FORCE_DUPLEX_MASK BIT(2) #define RTL8365MB_DIGITAL_INTERFACE_FORCE_SPEED_MASK GENMASK(1, 0) +/* bits[13:12]; bit 13 reserved, no defined speed value uses it yet */ +#define RTL8365MB_D_DIGITAL_INTERFACE_FORCE_SPEED2_MASK GENMASK(13, 12) + +#define RTL8365MB_D_DIGITAL_INTERFACE_FORCE_REG_BASE 0x12c0 +#define RTL8365MB_D_DIGITAL_INTERFACE_FORCE_REG(_port) \ + (RTL8365MB_D_DIGITAL_INTERFACE_FORCE_REG_BASE + (_port)) + +#define RTL8365MB_D_DIGITAL_INTERFACE_FORCE_EN_BASE 0x12c8 +#define RTL8365MB_D_DIGITAL_INTERFACE_FORCE_REG_EN(_port) \ + (RTL8365MB_D_DIGITAL_INTERFACE_FORCE_EN_BASE + (_port)) + +/* Per-port force-mode enable. The vendor RTL8367D API + * (dal_rtl8367d_port_macForceLink_set()) treats it as an on/off switch for + * the whole forced configuration: all bits set when the MAC is forced, zero + * when it is not. The meaning of the individual bits is not documented. + * This driver always forces the MAC, as on family C, so all bits are set + * on every call, including link down. + */ +#define RTL8365MB_D_DIGITAL_INTERFACE_FORCE_EN_ALL_MASK GENMASK(15, 0) + /* 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 RTL8365MB_PORT_MASK @@ -1713,6 +1735,94 @@ static int rtl8365mb_c_ext_config_forcemode(struct realtek_priv *priv, int port, return 0; } +int rtl8365mb_d_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_2500) { + r_speed = RTL8365MB_D_PORT_SPEED_2500M; + } else 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_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 & + RTL8365MB_DIGITAL_INTERFACE_FORCE_SPEED_MASK); + + /* Speed is 3 bits on family D: bits[1:0] go into FORCE_SPEED, + * bit[2] goes into FORCE_SPEED2 (register bit 12); only 2500M + * sets it. Register bit 13 is reserved. + */ + val |= FIELD_PREP(RTL8365MB_D_DIGITAL_INTERFACE_FORCE_SPEED2_MASK, + FIELD_GET(RTL8365MB_D_PORT_SPEED_MSB_MASK, + r_speed)); + ret = regmap_write(priv->map, + RTL8365MB_D_DIGITAL_INTERFACE_FORCE_REG(port), + val); + if (ret) + return ret; + + ret = regmap_write(priv->map, + RTL8365MB_D_DIGITAL_INTERFACE_FORCE_REG_EN(port), + RTL8365MB_D_DIGITAL_INTERFACE_FORCE_EN_ALL_MASK); + if (ret) + return ret; + + return 0; +} + static void rtl8365mb_phylink_get_caps(struct dsa_switch *ds, int port, struct phylink_config *config) { -- 2.53.0 On family D, RGMII on extension interface 1 needs three things that the other families do not: the EXT1 RGMII TX clock delay field in EXT_TXC_DLY (0x13f9, bits [5:3]) must be cleared, the pin mux in the top-level configuration register must be set up, and SerDes 1, which shares the pins, must be disabled. The vendor RGMII setup for EXT1 clears the RGMII field of EXT_TXC_DLY (0x13f9, bits [5:3]) in addition to programming TXDELAY in RGMXF. Do the same. The init jam table writes 0x0090 to this register, which leaves the field at 2, so without the clear an extra TX delay would be applied on top of tx-internal-delay-ps. Clearing it makes tx-internal-delay-ps the only source of TX delay. If the internal MAC4 block occupies extension 1, do not advertise RGMII for port 7 in phylink_get_caps(), so the configuration is refused before any EXT1 register is touched. This mirrors the vendor RTL8367D API, which returns an error for RGMII on port 7 when UTP port 4 uses the RGMII interface. The driver logs an error naming the conflict. RGMII is the only interface of port 7 this driver implements, so port 7 then has no supported interface: phylink_create() fails for it, and if it is a CPU port the switch does not probe. When MAC4 does not own the pins, bind the RGMII/MII pins to MAC7 and disable SerDes 1, which shares them. The TX delay field, the pin mux and the SerDes 1 disable are all programmed before RGMII is selected, so the shared pins are never driven by RGMII and SerDes 1 at the same time. The family D branch is not reachable until the chip table entry is added in the last patch. Signed-off-by: Mieczyslaw Nalewaj Reviewed-by: Linus Walleij --- drivers/net/dsa/realtek/rtl8365mb_main.c | 70 +++++++++++++++++++++++- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c index 27a6917..bdc29d0 100644 --- a/drivers/net/dsa/realtek/rtl8365mb_main.c +++ b/drivers/net/dsa/realtek/rtl8365mb_main.c @@ -334,6 +334,12 @@ #define RTL8365MB_SDS_OPTION_ARM_KEY 0x0249 #define RTL8365MB_SDS_OPTION_REG 0x13C1 +#define RTL8365MB_D_SDS_MISC_MODE_FIELD_MASK GENMASK(4, 0) +/* Shared "disable" encoding for both SDS_MISC's and SDS1_MISC0's + * 5-bit mode fields. + */ +#define RTL8365MB_D_PORT_SDS_MODE_DISABLE 0x1f + /* Embedded DW8051 microcontroller control registers. The microcontroller * can run firmware to manage the SerDes link, but this driver keeps it in * reset and disabled: phylink already performs the link management that @@ -466,6 +472,20 @@ #define RTL8365MB_PORT_MISC_CFG_VLAN_EGRESS_MODE_MASK GENMASK(5, 4) #define RTL8365MB_PORT_MISC_CFG_CONGESTION_SUSTAIN_TIME_MASK GENMASK(3, 0) +/* EXT_TXC_DLY holds a 3-bit TX clock delay per external interface, for + * RGMII ([2:0] EXT0, [5:3] EXT1, [8:6] EXT2) and separately for GMII. + * Only the EXT1 RGMII field is used here, and it is cleared in RGMII + * mode so that tx-internal-delay-ps is the only TX delay applied. + */ +#define RTL8365MB_D_REG_EXT_TXC_DLY 0x13f9 +#define RTL8365MB_D_EXT1_RGMII_TX_DLY_MASK GENMASK(5, 3) + +#define RTL8365MB_D_REG_TOP_CON0 0x1d70 +#define RTL8365MB_D_MAC7_SEL_EXT1_MASK BIT(13) +#define RTL8365MB_D_MAC4_SEL_EXT1_MASK BIT(12) + +#define RTL8365MB_D_REG_SDS1_MISC0 0x1d78 + /** * enum rtl8365mb_vlan_egress_mode - port VLAN egress mode * @RTL8365MB_VLAN_EGRESS_MODE_ORIGINAL: follow untag mask in VLAN4k table entry @@ -1263,6 +1283,32 @@ static int rtl8365mb_ext_config_rgmii(struct realtek_priv *priv, int port, if (ret) return ret; + if (rtl8365mb_is_d(priv) && extint->id == 1) { + ret = regmap_update_bits(priv->map, + RTL8365MB_D_REG_EXT_TXC_DLY, + RTL8365MB_D_EXT1_RGMII_TX_DLY_MASK, 0); + if (ret) + return ret; + + /* Bind the RGMII/MII pins to MAC7. MAC4 does not own them: + * RGMII is not advertised otherwise, see + * rtl8365mb_phylink_get_caps(). + */ + ret = regmap_update_bits(priv->map, + RTL8365MB_D_REG_TOP_CON0, + RTL8365MB_D_MAC7_SEL_EXT1_MASK, + RTL8365MB_D_MAC7_SEL_EXT1_MASK); + if (ret) + return ret; + + ret = regmap_update_bits(priv->map, + RTL8365MB_D_REG_SDS1_MISC0, + RTL8365MB_D_SDS_MISC_MODE_FIELD_MASK, + RTL8365MB_D_PORT_SDS_MODE_DISABLE); + 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), @@ -1830,6 +1876,8 @@ static void rtl8365mb_phylink_get_caps(struct dsa_switch *ds, int port, rtl8365mb_get_port_extint(ds->priv, port); struct realtek_priv *priv = ds->priv; struct rtl8365mb *mb = priv->chip_data; + u32 data; + int ret; config->mac_capabilities = MAC_SYM_PAUSE | MAC_ASYM_PAUSE | MAC_10 | MAC_100 | MAC_1000FD; @@ -1859,8 +1907,26 @@ static void rtl8365mb_phylink_get_caps(struct dsa_switch *ds, int port, * which remain unimplemented. */ - if (extint->supported_interfaces & RTL8365MB_PHY_INTERFACE_MODE_RGMII) - phy_interface_set_rgmii(config->supported_interfaces); + if (extint->supported_interfaces & RTL8365MB_PHY_INTERFACE_MODE_RGMII) { + /* On family D the EXT1 pins are shared with MAC4 (UTP port 4). + * If MAC4 owns them, RGMII on this port cannot work. + */ + if (rtl8365mb_is_d(priv) && extint->id == 1) { + ret = regmap_read(priv->map, RTL8365MB_D_REG_TOP_CON0, &data); + if (ret) + dev_err(priv->dev, + "failed to read TOP_CON0, RGMII on port %d disabled: %pe\n", + port, ERR_PTR(ret)); + else if (data & RTL8365MB_D_MAC4_SEL_EXT1_MASK) + dev_err(priv->dev, + "port %d: EXT1 is owned by MAC4, RGMII cannot be used\n", + port); + else + phy_interface_set_rgmii(config->supported_interfaces); + } else { + phy_interface_set_rgmii(config->supported_interfaces); + } + } if (extint->supported_interfaces & RTL8365MB_PHY_INTERFACE_MODE_SGMII && mb->sds_supported) -- 2.53.0 Move the family-specific VLAN 4k table accessors behind struct rtl8365mb_family_info, so that family D can provide its own versions without is_d() checks in the shared code. The existing code becomes the family C implementation and is otherwise unchanged: rename the functions to rtl8365mb_c_vlan_4k_read() and rtl8365mb_c_vlan_4k_write(), export them and call them through the family info. No functional change. Tested-by: Gustavo Foletto Mora Tested-by: Stanislaw Pal Signed-off-by: Mieczyslaw Nalewaj --- drivers/net/dsa/realtek/rtl8365mb.h | 5 +++++ drivers/net/dsa/realtek/rtl8365mb_main.c | 2 ++ drivers/net/dsa/realtek/rtl8365mb_vlan.c | 21 ++++++++++++--------- drivers/net/dsa/realtek/rtl8365mb_vlan.h | 7 +++++++ 4 files changed, 26 insertions(+), 9 deletions(-) diff --git a/drivers/net/dsa/realtek/rtl8365mb.h b/drivers/net/dsa/realtek/rtl8365mb.h index 3d607bd..d3d5fb3 100644 --- a/drivers/net/dsa/realtek/rtl8365mb.h +++ b/drivers/net/dsa/realtek/rtl8365mb.h @@ -10,6 +10,7 @@ enum rtl8365mb_family { }; struct rtl8365mb_jam_tbl_entry; +struct rtl8365mb_vlan4k; struct rtl8365mb_family_info { enum rtl8365mb_family family; @@ -19,6 +20,10 @@ struct rtl8365mb_family_info { int (*ext_config_forcemode)(struct realtek_priv *priv, int port, bool link, int speed, int duplex, bool tx_pause, bool rx_pause); + int (*vlan_4k_read)(struct realtek_priv *priv, u16 vid, + struct rtl8365mb_vlan4k *vlan4k); + int (*vlan_4k_write)(struct realtek_priv *priv, + const struct rtl8365mb_vlan4k *vlan4k); }; const struct rtl8365mb_family_info * diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c index bdc29d0..611e58c 100644 --- a/drivers/net/dsa/realtek/rtl8365mb_main.c +++ b/drivers/net/dsa/realtek/rtl8365mb_main.c @@ -757,6 +757,8 @@ static const struct rtl8365mb_family_info rtl8365mb_family_c = { .jam_table = rtl8365mb_init_jam_8365mb_vc, .jam_size = ARRAY_SIZE(rtl8365mb_init_jam_8365mb_vc), .ext_config_forcemode = rtl8365mb_c_ext_config_forcemode, + .vlan_4k_read = rtl8365mb_c_vlan_4k_read, + .vlan_4k_write = rtl8365mb_c_vlan_4k_write, }; /** diff --git a/drivers/net/dsa/realtek/rtl8365mb_vlan.c b/drivers/net/dsa/realtek/rtl8365mb_vlan.c index 8d49ffa..566c6bc 100644 --- a/drivers/net/dsa/realtek/rtl8365mb_vlan.c +++ b/drivers/net/dsa/realtek/rtl8365mb_vlan.c @@ -48,12 +48,13 @@ #include "rtl8365mb_vlan.h" #include "rtl8365mb_table.h" +#include "rtl8365mb.h" #include #include #include /* CVLAN (i.e. VLAN4k) table entry layout, u16[3] */ -#define RTL8365MB_CVLAN_ENTRY_SIZE 3 /* 48-bits */ +#define RTL8365MB_C_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) @@ -182,10 +183,10 @@ struct rtl8365mb_vlanmc { u8 meteridx : 6; }; -static int rtl8365mb_vlan_4k_read(struct realtek_priv *priv, u16 vid, - struct rtl8365mb_vlan4k *vlan4k) +int rtl8365mb_c_vlan_4k_read(struct realtek_priv *priv, u16 vid, + struct rtl8365mb_vlan4k *vlan4k) { - u16 data[RTL8365MB_CVLAN_ENTRY_SIZE]; + u16 data[RTL8365MB_C_CVLAN_ENTRY_SIZE]; int val; int ret; @@ -230,10 +231,10 @@ static int rtl8365mb_vlan_4k_read(struct realtek_priv *priv, u16 vid, return 0; } -static int rtl8365mb_vlan_4k_write(struct realtek_priv *priv, - const struct rtl8365mb_vlan4k *vlan4k) +int rtl8365mb_c_vlan_4k_write(struct realtek_priv *priv, + const struct rtl8365mb_vlan4k *vlan4k) { - u16 data[RTL8365MB_CVLAN_ENTRY_SIZE] = { 0 }; + u16 data[RTL8365MB_C_CVLAN_ENTRY_SIZE] = { 0 }; u16 vid; int val; @@ -282,6 +283,7 @@ rtl8365mb_vlan_4k_port_set(struct dsa_switch *ds, int port, struct netlink_ext_ack *extack, bool include) { + const struct rtl8365mb_family_info *fi; struct realtek_priv *priv = ds->priv; struct rtl8365mb_vlan4k vlan4k = {0}; int ret; @@ -296,7 +298,8 @@ rtl8365mb_vlan_4k_port_set(struct dsa_switch *ds, int port, return -EINVAL; } - ret = rtl8365mb_vlan_4k_read(priv, vlan->vid, &vlan4k); + fi = rtl8365mb_get_family_info(priv); + ret = fi->vlan_4k_read(priv, vlan->vid, &vlan4k); if (ret) { dev_err(priv->dev, "Failed to read VLAN 4k table\n"); return ret; @@ -313,7 +316,7 @@ rtl8365mb_vlan_4k_port_set(struct dsa_switch *ds, int port, vlan4k.untag &= ~BIT(port); vlan4k.ivl_en = true; /* always use Independent VLAN Learning */ - ret = rtl8365mb_vlan_4k_write(priv, &vlan4k); + ret = fi->vlan_4k_write(priv, &vlan4k); if (ret) { dev_err(priv->dev, "Failed to write VLAN 4k table\n"); return ret; diff --git a/drivers/net/dsa/realtek/rtl8365mb_vlan.h b/drivers/net/dsa/realtek/rtl8365mb_vlan.h index 27a526b..009b127 100644 --- a/drivers/net/dsa/realtek/rtl8365mb_vlan.h +++ b/drivers/net/dsa/realtek/rtl8365mb_vlan.h @@ -18,6 +18,13 @@ enum rtl8365mb_frame_ingress { RTL8365MB_FRAME_TYPE_UNTAGGED_ONLY, }; +struct rtl8365mb_vlan4k; + +int rtl8365mb_c_vlan_4k_read(struct realtek_priv *priv, u16 vid, + struct rtl8365mb_vlan4k *vlan4k); +int rtl8365mb_c_vlan_4k_write(struct realtek_priv *priv, + const struct rtl8365mb_vlan4k *vlan4k); + int rtl8365mb_vlan_port_get_pvid(struct realtek_priv *priv, int port, u16 *pvid); int -- 2.53.0 The hardware structure of 4K VLAN table entries has changed between silicon families. Family D's CVLAN entry is two 16-bit words rather than three: there is no third word extending member/untag past bit 7, and no priority/meter fields at all. The FID field is restricted to a narrower 2-bit width. The chip itself supports both IVL and SVL. IVL_EN (bit 3, versus bit 14 on family C) follows vlan4k->ivl_en. On family D, rtl8365mb_vlan_4k_port_set() enables it while any user port has VLAN filtering enabled (dsa_port_is_vlan_filtering()), so that static and learned FDB/MDB entries use the same key. Family C keeps IVL always enabled, as before. The stored IVL_EN is refreshed on the next port_vlan_add/del for the VID, not on a vlan_filtering change. Reusing the three-word family C layout on family D would read a third word that does not exist as a distinct table word on family D's die. Whatever data happens to be in that register would be folded into member/untag bits [10:8] on read, and written back on every read-modify-write cycle in rtl8365mb_vlan_4k_port_set(). Give family D its own two-word pack/unpack instead: add rtl8365mb_d_vlan_4k_read() and rtl8365mb_d_vlan_4k_write(). The shared code reaches them through the vlan_4k_read and vlan_4k_write pointers in struct rtl8365mb_family_info, so the pack/unpack code needs no is_d() check. The two-word layout matches the vendor switch API for this chip family, which writes only 0x0510/0x0511 for a CVLAN entry. Nothing points to them until the family D info is added together with the chip table entry in the last patch. Until then they are not static and are declared in rtl8365mb_vlan.h, so that the build does not warn about unused functions. Tested-by: Gustavo Foletto Mora Signed-off-by: Mieczyslaw Nalewaj --- drivers/net/dsa/realtek/rtl8365mb_vlan.c | 95 +++++++++++++++++++++++- drivers/net/dsa/realtek/rtl8365mb_vlan.h | 4 + 2 files changed, 97 insertions(+), 2 deletions(-) diff --git a/drivers/net/dsa/realtek/rtl8365mb_vlan.c b/drivers/net/dsa/realtek/rtl8365mb_vlan.c index 566c6bc..bf1a6a9 100644 --- a/drivers/net/dsa/realtek/rtl8365mb_vlan.c +++ b/drivers/net/dsa/realtek/rtl8365mb_vlan.c @@ -53,8 +53,11 @@ #include #include -/* CVLAN (i.e. VLAN4k) table entry layout, u16[3] */ +/* CVLAN (i.e. VLAN4k) table entry layout: u16[3] on family C, u16[2] on + * family D + */ #define RTL8365MB_C_CVLAN_ENTRY_SIZE 3 /* 48-bits */ +#define RTL8365MB_D_CVLAN_ENTRY_SIZE 2 /* 32-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) @@ -66,6 +69,9 @@ #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) +/* Family D CVLAN entry, word 1 (family D has no word 2) */ +#define RTL8365MB_D_CVLAN_ENTRY_D1_IVL_EN_MASK GENMASK(3, 3) +#define RTL8365MB_D_CVLAN_ENTRY_D1_FID_MASK GENMASK(1, 0) /* 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) @@ -231,6 +237,45 @@ int rtl8365mb_c_vlan_4k_read(struct realtek_priv *priv, u16 vid, return 0; } +int rtl8365mb_d_vlan_4k_read(struct realtek_priv *priv, u16 vid, + struct rtl8365mb_vlan4k *vlan4k) +{ + u16 data[RTL8365MB_D_CVLAN_ENTRY_SIZE]; + int val; + int ret; + + ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_CVLAN, + RTL8365MB_TABLE_OP_READ, &vid, 0, 0, + data, RTL8365MB_D_CVLAN_ENTRY_SIZE); + if (ret) + return ret; + + /* Unpack table entry */ + memset(vlan4k, 0, sizeof(*vlan4k)); + vlan4k->vid = vid; + + /* member/untag: d0[7:0]/d0[15:8] on both families. Family C + * extends these into data[2] bits [2:0]/[5:3] for its 9th-11th + * ports; family D's die has only 8 ports and no third table + * word, so data[2] does not exist there and must not be read. + */ + 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_D0_UNTAG_MASK, data[0]); + vlan4k->untag = FIELD_PREP(RTL8365MB_CVLAN_UNTAG_LO_MASK, val); + + vlan4k->fid = FIELD_GET(RTL8365MB_D_CVLAN_ENTRY_D1_FID_MASK, + data[1]); + /* Family D has no priority/meter fields in this entry - + * left zeroed by the memset() above. + */ + vlan4k->ivl_en = FIELD_GET(RTL8365MB_D_CVLAN_ENTRY_D1_IVL_EN_MASK, + data[1]); + + return 0; +} + int rtl8365mb_c_vlan_4k_write(struct realtek_priv *priv, const struct rtl8365mb_vlan4k *vlan4k) { @@ -277,6 +322,38 @@ int rtl8365mb_c_vlan_4k_write(struct realtek_priv *priv, data, ARRAY_SIZE(data)); } +int rtl8365mb_d_vlan_4k_write(struct realtek_priv *priv, + const struct rtl8365mb_vlan4k *vlan4k) +{ + /* Unknown bits of word 1 are written as zero, same as the vendor + * API does for this chip family. + */ + u16 data[RTL8365MB_D_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_D_CVLAN_ENTRY_D1_IVL_EN_MASK, + vlan4k->ivl_en); + data[1] |= FIELD_PREP(RTL8365MB_D_CVLAN_ENTRY_D1_FID_MASK, + vlan4k->fid); + /* No priority/meter/member-untag-extension fields exist in + * family D's 2-word entry - data[1] and data[0] above are + * the whole entry, and data[2] is not part of it at all. + */ + + vid = vlan4k->vid; + return rtl8365mb_table_query(priv, RTL8365MB_TABLE_CVLAN, + RTL8365MB_TABLE_OP_WRITE, &vid, 0, 0, + data, RTL8365MB_D_CVLAN_ENTRY_SIZE); +} + static int rtl8365mb_vlan_4k_port_set(struct dsa_switch *ds, int port, const struct switchdev_obj_port_vlan *vlan, @@ -314,7 +391,21 @@ rtl8365mb_vlan_4k_port_set(struct dsa_switch *ds, int port, vlan4k.untag |= BIT(port); else vlan4k.untag &= ~BIT(port); - vlan4k.ivl_en = true; /* always use Independent VLAN Learning */ + + /* Family C always uses IVL. On family D, IVL is on while any user + * port has VLAN filtering enabled, so that static entries match + * learned ones in a VLAN-unaware bridge (SVL). + */ + if (!rtl8365mb_is_d(priv)) { + vlan4k.ivl_en = true; + } else { + struct dsa_port *dp; + + vlan4k.ivl_en = false; + dsa_switch_for_each_user_port(dp, ds) + if (dsa_port_is_vlan_filtering(dp)) + vlan4k.ivl_en = true; + } ret = fi->vlan_4k_write(priv, &vlan4k); if (ret) { diff --git a/drivers/net/dsa/realtek/rtl8365mb_vlan.h b/drivers/net/dsa/realtek/rtl8365mb_vlan.h index 009b127..012c07d 100644 --- a/drivers/net/dsa/realtek/rtl8365mb_vlan.h +++ b/drivers/net/dsa/realtek/rtl8365mb_vlan.h @@ -24,6 +24,10 @@ int rtl8365mb_c_vlan_4k_read(struct realtek_priv *priv, u16 vid, struct rtl8365mb_vlan4k *vlan4k); int rtl8365mb_c_vlan_4k_write(struct realtek_priv *priv, const struct rtl8365mb_vlan4k *vlan4k); +int rtl8365mb_d_vlan_4k_read(struct realtek_priv *priv, u16 vid, + struct rtl8365mb_vlan4k *vlan4k); +int rtl8365mb_d_vlan_4k_write(struct realtek_priv *priv, + const struct rtl8365mb_vlan4k *vlan4k); int rtl8365mb_vlan_port_get_pvid(struct realtek_priv *priv, int port, u16 *pvid); -- 2.53.0 Family D has no working VLAN member config (MC) table. Its PVID is a raw 12-bit VID in a per-port register at 0x0700 + port, whereas family C stores a 5-bit MC table index, two ports per register, at 0x0700 + (port >> 1). Running the family C code on family D therefore reads an MC table that does not exist, and writes an MC index into the VID register of port (port >> 1) instead of the one of the port being configured. Read the PVID straight from its register in rtl8365mb_vlan_port_get_pvid(), and make rtl8365mb_vlan_pvid_port_set() and rtl8365mb_vlan_pvid_port_clear() use direct-VID variants on family D that skip the MC table. They keep the family C frame filter handling and rollback on error. Also clear the family D PVID registers when the VLAN support is set up. Unlike on family C, where VLANMC index 0 is the neutral PVID, the register holds a raw VID, and after a reset it keeps its hardware (or EEPROM) default. Without clearing it an unconfigured port could report a stale PVID from rtl8365mb_vlan_port_get_pvid(), and rtl8365mb_port_vlan_filtering() would accept untagged frames into it. No functional change for family C. The family D branch is not reachable until the chip table entry is added in the last patch. Tested-by: Gustavo Foletto Mora Signed-off-by: Mieczyslaw Nalewaj Reviewed-by: Linus Walleij --- drivers/net/dsa/realtek/rtl8365mb_main.c | 9 + drivers/net/dsa/realtek/rtl8365mb_vlan.c | 205 ++++++++++++++++++++++- drivers/net/dsa/realtek/rtl8365mb_vlan.h | 1 + 3 files changed, 210 insertions(+), 5 deletions(-) diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c index 611e58c..a13a5e9 100644 --- a/drivers/net/dsa/realtek/rtl8365mb_main.c +++ b/drivers/net/dsa/realtek/rtl8365mb_main.c @@ -2420,6 +2420,15 @@ static int rtl8365mb_vlan_setup(struct dsa_switch *ds) int ret; dsa_switch_for_each_available_port(dp, ds) { + /* Start every port with the neutral PVID */ + ret = rtl8365mb_vlan_port_reset_pvid(priv, dp->index); + if (ret) { + dev_err(priv->dev, + "Failed to reset PVID on port %d\n", + dp->index); + return ret; + } + /* Disable vlan-filtering for all ports */ ret = rtl8365mb_port_vlan_filtering(ds, dp->index, false, NULL); if (ret) { diff --git a/drivers/net/dsa/realtek/rtl8365mb_vlan.c b/drivers/net/dsa/realtek/rtl8365mb_vlan.c index bf1a6a9..7d805c6 100644 --- a/drivers/net/dsa/realtek/rtl8365mb_vlan.c +++ b/drivers/net/dsa/realtek/rtl8365mb_vlan.c @@ -44,6 +44,10 @@ * * VLANMC index 0 is reserved as a neutral PVID, used for standalone ports. * + * Everything above describes chips that have a VLAN membership configuration + * table. RTL8365MB_FAMILY_D chips do not have one: their PVID is a raw 12-bit + * VID in a per-port register, and no VLANMC entries are allocated. + * */ #include "rtl8365mb_vlan.h" @@ -120,6 +124,11 @@ #define RTL8365MB_VLAN_PVID_CTRL_PORT_MCIDX_MASK(_p) \ (0x1F << RTL8365MB_VLAN_PVID_CTRL_PORT_MCIDX_OFFSET(_p)) +#define RTL8365MB_D_VLAN_PVID_CTRL_BASE 0x0700 +#define RTL8365MB_D_VLAN_PVID_CTRL_REG(port) \ + (RTL8365MB_D_VLAN_PVID_CTRL_BASE + (port)) +#define RTL8365MB_D_VLAN_PVID_CTRL_MASK GENMASK(11, 0) + /* Frame type filtering registers */ #define RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_BASE 0x07aa #define RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_REG(port) \ @@ -171,7 +180,7 @@ struct rtl8365mb_vlan4k { * 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 + * - PVID configuration (family C only, family D has no VLAN MC table) * - 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 @@ -773,14 +782,51 @@ int rtl8365mb_vlan_port_get_pvid(struct realtek_priv *priv, int port, u16 *pvid) u8 vlanmc_idx; int ret; - ret = rtl8365mb_vlan_get_pvid_mc(priv, port, &vlanmc_idx, &vlanmc); - if (ret) - return ret; + if (rtl8365mb_is_d(priv)) { + u32 data; + + ret = regmap_read(priv->map, + RTL8365MB_D_VLAN_PVID_CTRL_REG(port), + &data); + if (ret) + return ret; + + *pvid = data & RTL8365MB_D_VLAN_PVID_CTRL_MASK; + } else { + ret = rtl8365mb_vlan_get_pvid_mc(priv, port, &vlanmc_idx, + &vlanmc); + if (ret) + return ret; + + *pvid = vlanmc.evid; + } - *pvid = vlanmc.evid; return 0; } +/** + * rtl8365mb_vlan_port_reset_pvid() - Put a port's PVID in its neutral state + * @priv: realtek_priv pointer + * @port: the port number + * + * On family C the neutral PVID is VLANMC index 0, which is the reset value, so + * there is nothing to do. On family D the PVID is a raw VID in a per-port + * register whose value after reset is not defined by the driver (it may come + * from the hardware default or from an EEPROM), so clear it to 0. + * + * Context: Can sleep. + * Return: 0 on success, or a negative error code on failure. + */ +int rtl8365mb_vlan_port_reset_pvid(struct realtek_priv *priv, int port) +{ + if (!rtl8365mb_is_d(priv)) + return 0; + + return regmap_update_bits(priv->map, + RTL8365MB_D_VLAN_PVID_CTRL_REG(port), + RTL8365MB_D_VLAN_PVID_CTRL_MASK, 0); +} + /* * rtl8365mb_vlan_port_get_framefilter() - Get the ingress frame filtering mode * for a port @@ -842,6 +888,68 @@ rtl8365mb_vlan_port_set_framefilter(struct realtek_priv *priv, val); } +/* + * rtl8365mb_vlan_pvid_port_set_direct() - Configure a port's PVID as a raw + * VID written to its dedicated register, for chip families without a + * working VLAN MC table (RTL8365MB_FAMILY_D) + * + * Reads back the previous PVID and frame filter first so both can be + * restored if enabling the new PVID fails partway through, matching + * the rollback behavior of the family-C implementation below. + * + * Context: Can sleep. Must be called with &priv->vlan_lock held. + * Return: 0 on success, or a negative error code on failure. + */ +static int rtl8365mb_vlan_pvid_port_set_direct(struct realtek_priv *priv, + int port, u16 vid) +{ + enum rtl8365mb_frame_ingress prev_accepted_frame; + u32 prev_pvid; + int ret; + + ret = regmap_read(priv->map, RTL8365MB_D_VLAN_PVID_CTRL_REG(port), + &prev_pvid); + if (ret) { + dev_err(priv->dev, "Failed to read current PVID\n"); + return ret; + } + prev_pvid &= RTL8365MB_D_VLAN_PVID_CTRL_MASK; + + 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; + } + + ret = regmap_update_bits(priv->map, + RTL8365MB_D_VLAN_PVID_CTRL_REG(port), + RTL8365MB_D_VLAN_PVID_CTRL_MASK, + vid & RTL8365MB_D_VLAN_PVID_CTRL_MASK); + if (ret) { + dev_err(priv->dev, "Failed to set port PVID\n"); + return ret; + } + + /* Changing accept frame is what enables PVID (if not enabled before) */ + ret = rtl8365mb_vlan_port_set_framefilter(priv, port, + RTL8365MB_FRAME_TYPE_ANY_FRAME); + if (ret) { + dev_err(priv->dev, "Failed to set port frame filter\n"); + goto undo_pvid_write; + } + + return 0; + +undo_pvid_write: + (void)regmap_update_bits(priv->map, + RTL8365MB_D_VLAN_PVID_CTRL_REG(port), + RTL8365MB_D_VLAN_PVID_CTRL_MASK, prev_pvid); + (void)rtl8365mb_vlan_port_set_framefilter(priv, port, + prev_accepted_frame); + return ret; +} + /* * rtl8365mb_vlan_pvid_port_set() - Configure a port's PVID and associated * VLANMC entry @@ -854,6 +962,9 @@ rtl8365mb_vlan_port_set_framefilter(struct realtek_priv *priv, * PVID. Gracefully unwinds and restores previous configuration if a hardware * write operation fails during execution. * + * On RTL8365MB_FAMILY_D there is no VLANMC table: the VID is written directly + * to the port's PVID register and no VLANMC entry is allocated. + * * 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. @@ -871,6 +982,13 @@ int rtl8365mb_vlan_pvid_port_set(struct dsa_switch *ds, int port, u16 vid, lockdep_assert_held(&priv->vlan_lock); + /* This chip family has no VLAN MC table - PVID is a raw VID in a + * dedicated per-port register, and there is no separate membership + * table entry to allocate/track. + */ + if (rtl8365mb_is_d(priv)) + return rtl8365mb_vlan_pvid_port_set_direct(priv, port, vid); + /* Read the old PVID exclusively to undo in case of error */ ret = rtl8365mb_vlan_get_pvid_mc(priv, port, &prev_vlanmc_idx, &prev_vlanmc); @@ -950,6 +1068,77 @@ undo_vlan_mc_port_set: return ret; } +/* + * rtl8365mb_vlan_pvid_port_clear_direct() - Remove a port's raw-VID PVID + * configuration, for chip families without a working VLAN MC table + * (RTL8365MB_FAMILY_D) + * + * Reads back the previous frame filter first so it can be restored if + * clearing the PVID register fails. + * + * Context: Can sleep. Must be called with &priv->vlan_lock held. + * Return: 0 on success, or a negative error code on failure. + */ +static int rtl8365mb_vlan_pvid_port_clear_direct(struct dsa_switch *ds, + int port, u16 vid) +{ + enum rtl8365mb_frame_ingress prev_accepted_frame = + RTL8365MB_FRAME_TYPE_ANY_FRAME; + struct realtek_priv *priv = ds->priv; + bool filtering; + u32 cur_pvid; + int ret; + + ret = regmap_read(priv->map, RTL8365MB_D_VLAN_PVID_CTRL_REG(port), + &cur_pvid); + if (ret) { + dev_err(priv->dev, "Failed to read current PVID\n"); + return ret; + } + + /* Port is not using this VID as PVID. Nothing to remove. */ + if ((cur_pvid & RTL8365MB_D_VLAN_PVID_CTRL_MASK) != vid) + return 0; + + filtering = dsa_port_is_vlan_filtering(dsa_to_port(ds, port)); + + /* Changing accept frame is what really removes PVID. But only do + * that if VLAN filtering is enabled. + */ + if (filtering) { + 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; + } + + ret = rtl8365mb_vlan_port_set_framefilter( + priv, port, RTL8365MB_FRAME_TYPE_TAGGED_ONLY); + if (ret) { + dev_err(priv->dev, "Failed to set port frame filter\n"); + return ret; + } + } + + ret = regmap_update_bits(priv->map, + RTL8365MB_D_VLAN_PVID_CTRL_REG(port), + RTL8365MB_D_VLAN_PVID_CTRL_MASK, 0); + if (ret) { + dev_err(priv->dev, "Failed to set port PVID to 0\n"); + goto undo_set_framefilter; + } + + return 0; + +undo_set_framefilter: + if (filtering) + (void)rtl8365mb_vlan_port_set_framefilter(priv, port, + prev_accepted_frame); + return ret; +} + /* * rtl8365mb_vlan_pvid_port_clear() - Remove a port's PVID configuration * @ds: dsa switch instance @@ -959,6 +1148,9 @@ undo_vlan_mc_port_set: * 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. * + * On RTL8365MB_FAMILY_D the port's PVID register is reset to 0 directly; there + * is no VLANMC entry to free. + * * 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. @@ -973,6 +1165,9 @@ int rtl8365mb_vlan_pvid_port_clear(struct dsa_switch *ds, int port, u16 vid) lockdep_assert_held(&priv->vlan_lock); + if (rtl8365mb_is_d(priv)) + return rtl8365mb_vlan_pvid_port_clear_direct(ds, port, vid); + ret = rtl8365mb_vlan_get_pvid_mc(priv, port, &vlanmc_idx, &vlanmc); if (ret) { diff --git a/drivers/net/dsa/realtek/rtl8365mb_vlan.h b/drivers/net/dsa/realtek/rtl8365mb_vlan.h index 012c07d..c84b108 100644 --- a/drivers/net/dsa/realtek/rtl8365mb_vlan.h +++ b/drivers/net/dsa/realtek/rtl8365mb_vlan.h @@ -31,6 +31,7 @@ int rtl8365mb_d_vlan_4k_write(struct realtek_priv *priv, int rtl8365mb_vlan_port_get_pvid(struct realtek_priv *priv, int port, u16 *pvid); +int rtl8365mb_vlan_port_reset_pvid(struct realtek_priv *priv, int port); int rtl8365mb_vlan_port_get_framefilter(struct realtek_priv *priv, int port, -- 2.53.0 Extend the existing RTL8367S SGMII/HSGMII PCS implementation in this driver for family D switches, whose CPU SerDes is reached through the indexed SDS13 window and whose SDS_MISC fields differ from family C. The register sequence and tuning tables are derived from the GPL-licensed Realtek RTL8367D port API distributed in the Mercusys MR80X GPL release. Keep the existing family C path unchanged and select the new behavior from the driver's chip-family metadata, making the implementation reusable by other family D boards. This patch only prepares the family D paths; they become reachable when the RTL8367S-VB chip entry is added in the last patch. On family D rtl8365mb_sds_probe_option() does not read the family C option register and always sets sds_supported, so the port 6 rate limits are raised for every family D chip. The only family D chip in this series, the RTL8367S-VB, has a SerDes interface on port 6. Nothing on family D programs a SerDes rate for 10 and 100 Mbit/s: pcs_link_up() does nothing and the MAC force register only sets the MAC side. It is not known whether the SerDes follows that register, and only 1G SGMII and 2500base-x were tested, so do not advertise 10 and 100 Mbit/s on the SerDes port of family D. Tested-by: Gustavo Foletto Mora Tested-by: Stanislaw Pal Co-developed-by: Fabiano Tassotti Signed-off-by: Fabiano Tassotti Signed-off-by: Mieczyslaw Nalewaj Reviewed-by: Linus Walleij --- drivers/net/dsa/realtek/rtl8365mb_main.c | 257 ++++++++++++++++++----- 1 file changed, 199 insertions(+), 58 deletions(-) diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c index a13a5e9..6f9c12f 100644 --- a/drivers/net/dsa/realtek/rtl8365mb_main.c +++ b/drivers/net/dsa/realtek/rtl8365mb_main.c @@ -264,7 +264,8 @@ * interface, indexed by the external port number with port 5 (the first * external port) as the base. Other RTL8367 families index this register * differently (e.g. the RTL8367R uses (id + 1) % 2), so this mapping only - * holds for the RTL8367C-style parts this driver supports. + * holds for the RTL8367C-style parts and for family D, where it is the same + * (one bit per external port, base port 5), as in the vendor RTL8367D API. */ #define RTL8365MB_BYPASS_LINE_RATE_REG 0x03F7 #define RTL8365MB_BYPASS_LINE_RATE_MASK(_port) BIT((_port) - 5) @@ -286,6 +287,7 @@ #define RTL8365MB_SDS_INDACS_CMD_BUSY_MASK BIT(8) #define RTL8365MB_SDS_INDACS_CMD_RUN_MASK BIT(7) #define RTL8365MB_SDS_INDACS_CMD_WR_MASK BIT(6) +#define RTL8365MB_SDS_INDACS_CMD_INDEX_MASK GENMASK(5, 0) #define RTL8365MB_SDS_INDACS_ADR_REG 0x6601 #define RTL8365MB_SDS_INDACS_DATA_REG 0x6602 @@ -315,8 +317,10 @@ #define RTL8365MB_SDS_REG_LINK_STATUS 0x003d #define RTL8365MB_SDS_LINK_STATUS_LINK_MASK BIT(4) -/* The embedded SerDes can only be muxed to external interface 1 (MAC8), - * which is port 6. +/* On family C the embedded SerDes can only be muxed to external interface + * 1 (MAC8), which is port 6. Family D has it on external interface 0 + * (MAC6), see RTL8365MB_D_SDS_EXT0_INDEX. RTL8365MB_SDS_EXT_INTERFACE_ID + * below is the family C id; the SerDes port is 6 on both families. */ #define RTL8365MB_SDS_EXT_INTERFACE_ID 1 #define RTL8365MB_SDS_EXT_INTERFACE_PORT 6 @@ -334,7 +338,22 @@ #define RTL8365MB_SDS_OPTION_ARM_KEY 0x0249 #define RTL8365MB_SDS_OPTION_REG 0x13C1 +/* Family D uses the SDS13 indirect window for its MAC6 SerDes. */ +#define RTL8365MB_D_SDS_EXT0_INDEX 13 +#define RTL8365MB_D_FIBER_CFG2_REG 0x13E8 +#define RTL8365MB_D_FIBER_CFG2_RX_DISABLE_MASK GENMASK(7, 6) +#define RTL8365MB_D_FIBER_CFG2_RX_DISABLE_SDS0 BIT(6) +#define RTL8365MB_D_SDS_MISC_PA33PC_EN BIT(11) +#define RTL8365MB_D_SDS_MISC_PA12PC_EN BIT(10) +#define RTL8365MB_D_SDS_MISC_MAC6_SEL_SDS0 BIT(9) #define RTL8365MB_D_SDS_MISC_MODE_FIELD_MASK GENMASK(4, 0) +#define RTL8365MB_D_SDS_MISC_MODE_SGMII 0x02 +#define RTL8365MB_D_SDS_MISC_MODE_HSGMII 0x12 +#define RTL8365MB_D_SDS_MISC_CFG_MASK \ + (RTL8365MB_D_SDS_MISC_PA33PC_EN | \ + RTL8365MB_D_SDS_MISC_PA12PC_EN | \ + RTL8365MB_D_SDS_MISC_MAC6_SEL_SDS0 | \ + RTL8365MB_D_SDS_MISC_MODE_FIELD_MASK) /* Shared "disable" encoding for both SDS_MISC's and SDS1_MISC0's * 5-bit mode fields. */ @@ -721,6 +740,21 @@ static const struct rtl8365mb_jam_tbl_entry rtl8365mb_sds_jam_hsgmii[] = { { 0x0424, 0xD810 }, { 0x0001, 0x0F80 }, { 0x002E, 0x83F2 }, }; +/* Family D tuning tables from the Realtek vendor port API. */ +static const struct rtl8365mb_jam_tbl_entry rtl8365mb_d_sds_jam_sgmii[] = { + { 0x0427, 0x4E0C }, { 0x0428, 0xAA00 }, { 0x0425, 0x5189 }, + { 0x0424, 0x8414 }, { 0x0423, 0x1020 }, { 0x0410, 0x0002 }, + { 0x0484, 0x011B }, { 0x0421, 0x8E13 }, { 0x0422, 0x1140 }, + { 0x0004, 0x074F }, +}; + +static const struct rtl8365mb_jam_tbl_entry rtl8365mb_d_sds_jam_hsgmii[] = { + { 0x0427, 0x4E0C }, { 0x0428, 0xAA00 }, { 0x0425, 0x5189 }, + { 0x0424, 0x8414 }, { 0x0423, 0x1020 }, { 0x0410, 0x0002 }, + { 0x0504, 0x051B }, { 0x0421, 0x8E13 }, { 0x0422, 0x1140 }, + { 0x0004, 0x074F }, +}; + enum rtl8365mb_phy_interface_mode { RTL8365MB_PHY_INTERFACE_MODE_INVAL = 0, RTL8365MB_PHY_INTERFACE_MODE_INTERNAL = BIT(0), @@ -909,8 +943,9 @@ struct rtl8365mb_port { * @mib_lock: prevent concurrent reads of MIB counters * @ports: per-port data * @pcs: PCS for the SerDes external interface - * @sds_supported: SerDes tuning parameters match the chip option, so the - * SerDes interface modes can be advertised + * @sds_supported: SerDes interface modes can be advertised: the tuning + * parameters match the chip option (family C), or the chip + * is family D, which does not use the option * * Private data for this driver. */ @@ -1323,7 +1358,8 @@ static int rtl8365mb_ext_config_rgmii(struct realtek_priv *priv, int port, return 0; } -static int rtl8365mb_sds_write(struct realtek_priv *priv, u16 addr, u16 data) +static int rtl8365mb_sds_write(struct realtek_priv *priv, u8 index, + u16 addr, u16 data) { int ret; @@ -1341,10 +1377,13 @@ static int rtl8365mb_sds_write(struct realtek_priv *priv, u16 addr, u16 data) */ return regmap_write(priv->map, RTL8365MB_SDS_INDACS_CMD_REG, RTL8365MB_SDS_INDACS_CMD_RUN_MASK | - RTL8365MB_SDS_INDACS_CMD_WR_MASK); + RTL8365MB_SDS_INDACS_CMD_WR_MASK | + FIELD_PREP(RTL8365MB_SDS_INDACS_CMD_INDEX_MASK, + index)); } -static int rtl8365mb_sds_read(struct realtek_priv *priv, u16 addr, u16 *data) +static int rtl8365mb_sds_read(struct realtek_priv *priv, u8 index, + u16 addr, u16 *data) { u32 val; int ret; @@ -1354,7 +1393,9 @@ static int rtl8365mb_sds_read(struct realtek_priv *priv, u16 addr, u16 *data) return ret; ret = regmap_write(priv->map, RTL8365MB_SDS_INDACS_CMD_REG, - RTL8365MB_SDS_INDACS_CMD_RUN_MASK); + RTL8365MB_SDS_INDACS_CMD_RUN_MASK | + FIELD_PREP(RTL8365MB_SDS_INDACS_CMD_INDEX_MASK, + index)); if (ret) return ret; @@ -1394,6 +1435,14 @@ static int rtl8365mb_sds_probe_option(struct realtek_priv *priv) int ret; int i; + /* Family D has a fixed SDS13 programming model and does not use the + * family C option register to select its tuning table. + */ + if (rtl8365mb_is_d(priv)) { + mb->sds_supported = true; + return 0; + } + /* Nothing to probe if no external interface is wired to the SerDes */ for (i = 0; i < RTL8365MB_MAX_NUM_EXTINTS; i++) { extint = &mb->chip_info->extints[i]; @@ -1475,25 +1524,44 @@ static int rtl8365mb_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode, const struct rtl8365mb_jam_tbl_entry *sds_jam; const int id = RTL8365MB_SDS_EXT_INTERFACE_ID; struct rtl8365mb *mb = pcs_to_rtl8365mb(pcs); - struct realtek_priv *priv; + struct realtek_priv *priv = mb->priv; size_t sds_jam_size; - u32 mode; + u32 misc_mask; + u32 misc_val; + u32 sds_mode; + u8 sds_index; + bool is_d; u16 val; int ret; int i; - priv = mb->priv; + is_d = rtl8365mb_is_d(priv); + /* Select the appropriate tuning table and SDS mode */ if (interface == PHY_INTERFACE_MODE_2500BASEX) { - sds_jam = rtl8365mb_sds_jam_hsgmii; - sds_jam_size = ARRAY_SIZE(rtl8365mb_sds_jam_hsgmii); - mode = RTL8365MB_EXT_PORT_MODE_HSGMII; + if (is_d) { + sds_jam = rtl8365mb_d_sds_jam_hsgmii; + sds_jam_size = ARRAY_SIZE(rtl8365mb_d_sds_jam_hsgmii); + sds_mode = RTL8365MB_D_SDS_MISC_MODE_HSGMII; + } else { + sds_jam = rtl8365mb_sds_jam_hsgmii; + sds_jam_size = ARRAY_SIZE(rtl8365mb_sds_jam_hsgmii); + sds_mode = RTL8365MB_EXT_PORT_MODE_HSGMII; + } } else { - sds_jam = rtl8365mb_sds_jam_sgmii; - sds_jam_size = ARRAY_SIZE(rtl8365mb_sds_jam_sgmii); - mode = RTL8365MB_EXT_PORT_MODE_SGMII; + if (is_d) { + sds_jam = rtl8365mb_d_sds_jam_sgmii; + sds_jam_size = ARRAY_SIZE(rtl8365mb_d_sds_jam_sgmii); + sds_mode = RTL8365MB_D_SDS_MISC_MODE_SGMII; + } else { + sds_jam = rtl8365mb_sds_jam_sgmii; + sds_jam_size = ARRAY_SIZE(rtl8365mb_sds_jam_sgmii); + sds_mode = RTL8365MB_EXT_PORT_MODE_SGMII; + } } + sds_index = is_d ? RTL8365MB_D_SDS_EXT0_INDEX : 0; + /* Hold the embedded DW8051 microcontroller in reset and keep it * disabled. The vendor driver loads firmware into it to manage the * SerDes link, but the firmware only duplicates work that phylink @@ -1522,34 +1590,58 @@ static int rtl8365mb_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode, /* Tune the SerDes with vendor-prescribed parameters */ for (i = 0; i < sds_jam_size; i++) { - ret = rtl8365mb_sds_write(priv, sds_jam[i].reg, - sds_jam[i].val); + ret = rtl8365mb_sds_write(priv, sds_index, + sds_jam[i].reg, sds_jam[i].val); if (ret) return ret; } - /* Mux the SerDes to MAC8 in the requested mode */ + /* Family-specific post-tuning configuration */ + if (is_d) { + /* The register and field names follow the vendor RTL8367D API, + * whose SDS numbering differs from the SDS13 window used + * here. As in the vendor sequence the whole 2-bit field + * (bits 7:6) is written, so bit 7 is cleared as well. + */ + ret = regmap_update_bits(priv->map, RTL8365MB_D_FIBER_CFG2_REG, + RTL8365MB_D_FIBER_CFG2_RX_DISABLE_MASK, + RTL8365MB_D_FIBER_CFG2_RX_DISABLE_SDS0); + if (ret) + return ret; + + misc_mask = RTL8365MB_D_SDS_MISC_CFG_MASK; + misc_val = RTL8365MB_D_SDS_MISC_PA33PC_EN | + RTL8365MB_D_SDS_MISC_PA12PC_EN | + RTL8365MB_D_SDS_MISC_MAC6_SEL_SDS0 | sds_mode; + } else { + /* Mux the SerDes to MAC8 in the requested mode */ + misc_mask = RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK | + RTL8365MB_SDS_MISC_MAC8_SEL_HSGMII_MASK; + misc_val = (sds_mode == RTL8365MB_EXT_PORT_MODE_SGMII) ? + RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK : + RTL8365MB_SDS_MISC_MAC8_SEL_HSGMII_MASK; + } + ret = regmap_update_bits(priv->map, RTL8365MB_SDS_MISC_REG, - RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK | - RTL8365MB_SDS_MISC_MAC8_SEL_HSGMII_MASK, - mode == RTL8365MB_EXT_PORT_MODE_SGMII ? - RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK : - RTL8365MB_SDS_MISC_MAC8_SEL_HSGMII_MASK); + misc_mask, misc_val); if (ret) return ret; - val = mode << RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_OFFSET(id); - ret = regmap_update_bits(priv->map, - RTL8365MB_DIGITAL_INTERFACE_SELECT_REG(id), - RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_MASK(id), - val); - if (ret) - return ret; + if (!is_d) { + val = sds_mode << RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_OFFSET(id); + ret = regmap_update_bits(priv->map, + RTL8365MB_DIGITAL_INTERFACE_SELECT_REG(id), + RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_MASK(id), + val); + if (ret) + return ret; + } /* Take the SerDes out of reset. The vendor driver does this only * after the SerDes mux and the interface mode are configured. */ - ret = rtl8365mb_sds_write(priv, RTL8365MB_SDS_REG_RESET, + ret = rtl8365mb_sds_write(priv, sds_index, + RTL8365MB_SDS_REG_RESET, RTL8365MB_SDS_RESET_DEASSERT); if (ret) return ret; @@ -1559,27 +1651,32 @@ static int rtl8365mb_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode, * This flushes the FIFOs and ensures a clean state for the link, * preventing silent drops and CRC errors. */ - ret = rtl8365mb_sds_write(priv, RTL8365MB_SDS_REG_BMCR, + ret = rtl8365mb_sds_write(priv, sds_index, + RTL8365MB_SDS_REG_BMCR, RTL8365MB_SDS_BMCR_DPRST_PHASE1); if (ret) return ret; - ret = rtl8365mb_sds_write(priv, RTL8365MB_SDS_REG_BMCR, + ret = rtl8365mb_sds_write(priv, sds_index, + RTL8365MB_SDS_REG_BMCR, RTL8365MB_SDS_BMCR_DPRST_PHASE2); if (ret) return ret; /* Keep SGMII in-band autonegotiation disabled: the link parameters are - * forced from rtl8365mb_pcs_link_up() instead. + * forced from rtl8365mb_pcs_link_up() instead (family C), or from + * mac_link_up() (family D). */ - ret = rtl8365mb_sds_read(priv, RTL8365MB_SDS_REG_NWAY, &val); + ret = rtl8365mb_sds_read(priv, sds_index, + RTL8365MB_SDS_REG_NWAY, &val); if (ret) return ret; val &= ~RTL8365MB_SDS_NWAY_EN_MASK; val |= RTL8365MB_SDS_NWAY_RESTART_MASK; - return rtl8365mb_sds_write(priv, RTL8365MB_SDS_REG_NWAY, val); + return rtl8365mb_sds_write(priv, sds_index, + RTL8365MB_SDS_REG_NWAY, val); } static bool rtl8365mb_interface_is_serdes(phy_interface_t interface) @@ -1604,18 +1701,27 @@ static void rtl8365mb_pcs_get_state(struct phylink_pcs *pcs, { struct rtl8365mb *mb = pcs_to_rtl8365mb(pcs); struct realtek_priv *priv = mb->priv; + u8 sds_index = 0; u16 status; + bool is_d; u32 val; int ret; - /* In-band autonegotiation is not implemented, so the link parameters are - * forced from rtl8365mb_pcs_link_up(). The real link state must still be - * read from the SerDes itself: the embedded DW8051 microcontroller that + is_d = rtl8365mb_is_d(priv); + + /* In-band autonegotiation is not implemented, so the link parameters + * are forced from rtl8365mb_pcs_link_up() on family C, and from + * mac_link_up() on family D. The real link state must still be read + * from the SerDes itself: the embedded DW8051 microcontroller that * the vendor firmware uses to poll the SerDes is kept disabled (see * rtl8365mb_pcs_config()), so the link status register can be read * directly through the SDS_INDACS window without racing the auto-poll. */ - ret = rtl8365mb_sds_read(priv, RTL8365MB_SDS_REG_LINK_STATUS, &status); + if (is_d) + sds_index = RTL8365MB_D_SDS_EXT0_INDEX; + + ret = rtl8365mb_sds_read(priv, sds_index, + RTL8365MB_SDS_REG_LINK_STATUS, &status); if (ret) { state->link = false; return; @@ -1626,6 +1732,23 @@ static void rtl8365mb_pcs_get_state(struct phylink_pcs *pcs, if (!state->link) return; + if (is_d) { + if (state->interface == PHY_INTERFACE_MODE_2500BASEX) { + state->duplex = DUPLEX_FULL; + state->speed = SPEED_2500; + } else { + /* Keep what phylink passed in, but never report the + * link up with an unknown speed: mac_link_up() cannot + * force the port with it. + */ + if (state->speed == SPEED_UNKNOWN) + state->speed = SPEED_1000; + if (state->duplex == DUPLEX_UNKNOWN) + state->duplex = DUPLEX_FULL; + } + return; + } + /* The speed and duplex are forced; read them back from the values * programmed into the SerDes MISC register. */ @@ -1667,6 +1790,12 @@ static void rtl8365mb_pcs_link_up(struct phylink_pcs *pcs, u32 r_speed; int ret; + /* Family D forces the external MAC ability from mac_link_up(); its + * SDS_MISC fields do not share the family C link-force layout. + */ + if (rtl8365mb_is_d(priv)) + return; + /* The speed field has no value for 2.5 Gbps: the rate is determined by * the HSGMII SerDes configuration, and the vendor driver programs the * 1 Gbps value here. @@ -1931,10 +2060,17 @@ static void rtl8365mb_phylink_get_caps(struct dsa_switch *ds, int port, } if (extint->supported_interfaces & RTL8365MB_PHY_INTERFACE_MODE_SGMII && - mb->sds_supported) + mb->sds_supported) { __set_bit(PHY_INTERFACE_MODE_SGMII, config->supported_interfaces); + /* The SerDes rate is not programmed for 10 and 100 Mbit/s on + * family D, see rtl8365mb_pcs_link_up(). + */ + if (rtl8365mb_is_d(priv)) + config->mac_capabilities &= ~(MAC_10 | MAC_100); + } + if (extint->supported_interfaces & RTL8365MB_PHY_INTERFACE_MODE_HSGMII && mb->sds_supported) { __set_bit(PHY_INTERFACE_MODE_2500BASEX, @@ -2013,7 +2149,7 @@ static void rtl8365mb_phylink_mac_link_down(struct phylink_config *config, /* phylink has no pcs_link_down callback, so on the SerDes path only the * MAC external interface force is reset here. Clearing the MAC force is * enough to bring the link down; the SerDes keeps presenting its last - * forced state until the next pcs_link_up() reprograms it. + * forced state (family C) until the next pcs_link_up() reprograms it. */ if (phy_interface_mode_is_rgmii(interface) || rtl8365mb_interface_is_serdes(interface)) { @@ -2048,9 +2184,11 @@ static void rtl8365mb_phylink_mac_link_up(struct phylink_config *config, p = &mb->ports[port]; schedule_delayed_work(&p->mib_work, 0); - /* The SerDes forced link state is programmed by the PCS in + /* On family C the SerDes forced link state is programmed by the PCS in * rtl8365mb_pcs_link_up(); here only the MAC external interface force - * is configured, for both RGMII and SerDes. + * is configured. On family D pcs_link_up() does nothing, and the MAC + * external interface force is the only forced link state, for both + * RGMII and SerDes. */ if (phy_interface_mode_is_rgmii(interface) || rtl8365mb_interface_is_serdes(interface)) { @@ -2063,18 +2201,21 @@ static void rtl8365mb_phylink_mac_link_up(struct phylink_config *config, return; } - /* The SerDes has its own pause enables; program them from - * the resolved pause modes, as the vendor driver does when - * forcing the link on a SerDes external interface. These - * bits, not the MAC force pause bits, gate pause on the - * SerDes external interface: flow control testing shows - * that pause frames are only emitted with the SerDes TXFC - * bit set, while the MAC force pause bits alone have no - * effect on this port. This is done here rather than in - * rtl8365mb_pcs_link_up() because pcs_link_up() carries no - * pause information. + /* Family C: the SerDes has its own pause enables; program + * them from the resolved pause modes, as the vendor driver + * does when forcing the link on a SerDes external + * interface. These bits, not the MAC force pause bits, gate + * pause on the SerDes external interface: flow control + * testing shows that pause frames are only emitted with the + * SerDes TXFC bit set, while the MAC force pause bits alone + * have no effect on this port. This is done here rather + * than in rtl8365mb_pcs_link_up() because pcs_link_up() + * carries no pause information. Family D does not use this + * path: its pause bits are set in the forced-mode register + * by rtl8365mb_d_ext_config_forcemode(). */ - if (rtl8365mb_interface_is_serdes(interface)) { + if (rtl8365mb_interface_is_serdes(interface) && + !rtl8365mb_is_d(priv)) { u32 val = 0; if (tx_pause) -- 2.53.0 Add L2 MAC pack/unpack helpers and use them in the family C serializers instead of open-coding the byte packing, since the layout of the MAC address words is the same for both families. The now unused per-field MAC masks are dropped. Rename the family C serializers to rtl8365mb_c_l2_*(), export them and reach them through function pointers in struct rtl8365mb_family_info, so that a following patch can add the family D layout without touching the family C code again. No functional change. Tested-by: Gustavo Foletto Mora Tested-by: Stanislaw Pal Signed-off-by: Mieczyslaw Nalewaj --- drivers/net/dsa/realtek/rtl8365mb.h | 7 ++ drivers/net/dsa/realtek/rtl8365mb_l2.c | 125 +++++++++++------------ drivers/net/dsa/realtek/rtl8365mb_l2.h | 12 +++ drivers/net/dsa/realtek/rtl8365mb_main.c | 5 + 4 files changed, 83 insertions(+), 66 deletions(-) diff --git a/drivers/net/dsa/realtek/rtl8365mb.h b/drivers/net/dsa/realtek/rtl8365mb.h index d3d5fb3..244e896 100644 --- a/drivers/net/dsa/realtek/rtl8365mb.h +++ b/drivers/net/dsa/realtek/rtl8365mb.h @@ -11,6 +11,8 @@ enum rtl8365mb_family { struct rtl8365mb_jam_tbl_entry; struct rtl8365mb_vlan4k; +struct rtl8365mb_l2_uc; +struct rtl8365mb_l2_mc; struct rtl8365mb_family_info { enum rtl8365mb_family family; @@ -24,6 +26,11 @@ struct rtl8365mb_family_info { struct rtl8365mb_vlan4k *vlan4k); int (*vlan_4k_write)(struct realtek_priv *priv, const struct rtl8365mb_vlan4k *vlan4k); + size_t l2_entry_size; + void (*l2_data_to_uc)(const u16 *data, struct rtl8365mb_l2_uc *uc); + void (*l2_uc_to_data)(const struct rtl8365mb_l2_uc *uc, u16 *data); + void (*l2_data_to_mc)(const u16 *data, struct rtl8365mb_l2_mc *mc); + void (*l2_mc_to_data)(const struct rtl8365mb_l2_mc *mc, u16 *data); }; const struct rtl8365mb_family_info * diff --git a/drivers/net/dsa/realtek/rtl8365mb_l2.c b/drivers/net/dsa/realtek/rtl8365mb_l2.c index 0494d8a..cd5b333 100644 --- a/drivers/net/dsa/realtek/rtl8365mb_l2.c +++ b/drivers/net/dsa/realtek/rtl8365mb_l2.c @@ -6,18 +6,11 @@ #include +#include "rtl8365mb.h" #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) @@ -36,12 +29,6 @@ #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) @@ -131,16 +118,29 @@ struct rtl8365mb_l2_mc { bool igmp_asic; }; -static void rtl8365mb_l2_data_to_uc(const u16 *data, struct rtl8365mb_l2_uc *uc) +static void rtl8365mb_l2_mac_to_data(const u8 *m, u16 *data) +{ + data[0] = (m[4] << 8) | m[5]; + data[1] = (m[2] << 8) | m[3]; + data[2] = (m[0] << 8) | m[1]; +} + +static void rtl8365mb_l2_data_to_mac(const u16 *data, u8 *m) +{ + m[5] = data[0] & 0xff; + m[4] = data[0] >> 8; + m[3] = data[1] & 0xff; + m[2] = data[1] >> 8; + m[1] = data[2] & 0xff; + m[0] = data[2] >> 8; +} + +void rtl8365mb_c_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]); + rtl8365mb_l2_data_to_mac(data, uc->key.mac_addr); 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]); @@ -161,23 +161,13 @@ static void rtl8365mb_l2_data_to_uc(const u16 *data, struct rtl8365mb_l2_uc *uc) 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) +void rtl8365mb_c_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]); + memset(data, 0, RTL8365MB_C_L2_ENTRY_SIZE * 2); + rtl8365mb_l2_mac_to_data(uc->key.mac_addr, data); 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); @@ -200,16 +190,12 @@ static void rtl8365mb_l2_uc_to_data(const struct rtl8365mb_l2_uc *uc, u16 *data) 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) +void rtl8365mb_c_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]); + rtl8365mb_l2_data_to_mac(data, mc->key.mac_addr); /* 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]); @@ -228,17 +214,13 @@ static void rtl8365mb_l2_data_to_mc(const u16 *data, struct rtl8365mb_l2_mc *mc) 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) +void rtl8365mb_c_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]); + memset(data, 0, RTL8365MB_C_L2_ENTRY_SIZE * 2); + rtl8365mb_l2_mac_to_data(mc->key.mac_addr, data); 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); @@ -283,17 +265,20 @@ 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 }; + const struct rtl8365mb_family_info *fi; struct rtl8365mb_l2_uc uc; int ret; + fi = rtl8365mb_get_family_info(priv); + 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); + port, data, fi->l2_entry_size); if (ret) return ret; - rtl8365mb_l2_data_to_uc(data, &uc); + fi->l2_data_to_uc(data, &uc); ether_addr_copy(entry->mac_addr, uc.key.mac_addr); entry->vid = uc.key.vid; @@ -307,6 +292,7 @@ int rtl8365mb_l2_add_uc(struct realtek_priv *priv, int port, u16 efid, u16 vid) { u16 data[RTL8365MB_L2_ENTRY_SIZE] = { 0 }; + const struct rtl8365mb_family_info *fi; struct rtl8365mb_l2_uc uc = { 0 }; u16 addr; int ret; @@ -326,13 +312,14 @@ int rtl8365mb_l2_add_uc(struct realtek_priv *priv, int port, uc.is_static = true; /* age greater than 0 adds/updates entries */ uc.age = 1; - rtl8365mb_l2_uc_to_data(&uc, data); + fi = rtl8365mb_get_family_info(priv); + fi->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); + data, fi->l2_entry_size); /* Assume the missing new entry as the table is full */ if (ret == -ENOENT) @@ -347,6 +334,7 @@ int rtl8365mb_l2_del_uc(struct realtek_priv *priv, int port, u16 efid, u16 vid) { u16 data[RTL8365MB_L2_ENTRY_SIZE] = { 0 }; + const struct rtl8365mb_family_info *fi; struct rtl8365mb_l2_uc uc = { 0 }; u16 addr; int ret; @@ -358,7 +346,8 @@ int rtl8365mb_l2_del_uc(struct realtek_priv *priv, int port, uc.key.vid = vid; /* age 0 deletes the entry */ uc.age = 0; - rtl8365mb_l2_uc_to_data(&uc, data); + fi = rtl8365mb_get_family_info(priv); + fi->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, @@ -368,7 +357,7 @@ int rtl8365mb_l2_del_uc(struct realtek_priv *priv, int port, ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2, RTL8365MB_TABLE_OP_WRITE, &addr, 0, 0, - data, RTL8365MB_L2_ENTRY_SIZE); + data, fi->l2_entry_size); if (ret == -ENOENT) { dev_dbg(priv->dev, "%s: %pM vid=%d efid=%d missing\n", @@ -451,6 +440,7 @@ int rtl8365mb_l2_add_mc(struct realtek_priv *priv, int port, u16 vid) { u16 data[RTL8365MB_L2_ENTRY_SIZE] = { 0 }; + const struct rtl8365mb_family_info *fi; struct rtl8365mb_l2_mc mc = { 0 }; u16 addr; int ret; @@ -463,16 +453,17 @@ int rtl8365mb_l2_add_mc(struct realtek_priv *priv, int port, */ mc.member |= BIT(port); mc.is_static = 1; - rtl8365mb_l2_mc_to_data(&mc, data); + fi = rtl8365mb_get_family_info(priv); + fi->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); + data, fi->l2_entry_size); if (!ret) { /* There is already an entry... */ - rtl8365mb_l2_data_to_mc(data, &mc); + fi->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, @@ -487,7 +478,7 @@ int rtl8365mb_l2_add_mc(struct realtek_priv *priv, int port, mc.is_static = 1; } - rtl8365mb_l2_mc_to_data(&mc, data); + fi->l2_mc_to_data(&mc, data); } else if (ret == -ENOENT) { /* New entry, no need to update data again as it already * includes the member. @@ -508,7 +499,7 @@ int rtl8365mb_l2_add_mc(struct realtek_priv *priv, int port, ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2, RTL8365MB_TABLE_OP_WRITE, &addr, 0, 0, - data, RTL8365MB_L2_ENTRY_SIZE); + data, fi->l2_entry_size); /* Assume the missing new entry as the table is full */ if (ret == -ENOENT) @@ -522,6 +513,7 @@ int rtl8365mb_l2_del_mc(struct realtek_priv *priv, int port, u16 vid) { u16 data[RTL8365MB_L2_ENTRY_SIZE] = { 0 }; + const struct rtl8365mb_family_info *fi; struct rtl8365mb_l2_mc mc = { 0 }; u16 addr; int ret; @@ -529,13 +521,14 @@ int rtl8365mb_l2_del_mc(struct realtek_priv *priv, int port, memcpy(mc.key.mac_addr, mac_addr, ETH_ALEN); mc.key.vid = vid; mc.key.ivl = true; - rtl8365mb_l2_mc_to_data(&mc, data); + fi = rtl8365mb_get_family_info(priv); + fi->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); + data, fi->l2_entry_size); if (ret == -ENOENT) { dev_dbg(priv->dev, "%s: %pM vid=%d missing\n", __func__, mac_addr, vid); @@ -547,7 +540,7 @@ int rtl8365mb_l2_del_mc(struct realtek_priv *priv, int port, /* Return on any other error */ return ret; - rtl8365mb_l2_data_to_mc(data, &mc); + fi->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, @@ -565,12 +558,12 @@ int rtl8365mb_l2_del_mc(struct realtek_priv *priv, int port, mc.fwd_pri = 0; mc.igmp_asic = 0; } - rtl8365mb_l2_mc_to_data(&mc, data); + fi->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); + data, fi->l2_entry_size); return ret; } diff --git a/drivers/net/dsa/realtek/rtl8365mb_l2.h b/drivers/net/dsa/realtek/rtl8365mb_l2.h index 9470cf0..a9f9ae4 100644 --- a/drivers/net/dsa/realtek/rtl8365mb_l2.h +++ b/drivers/net/dsa/realtek/rtl8365mb_l2.h @@ -12,6 +12,18 @@ #include "realtek.h" +#define RTL8365MB_C_L2_ENTRY_SIZE 6 +/* Highest L2 entry size of any supported chip, used for sizing */ +#define RTL8365MB_L2_ENTRY_SIZE RTL8365MB_C_L2_ENTRY_SIZE + +struct rtl8365mb_l2_uc; +struct rtl8365mb_l2_mc; + +void rtl8365mb_c_l2_data_to_uc(const u16 *data, struct rtl8365mb_l2_uc *uc); +void rtl8365mb_c_l2_uc_to_data(const struct rtl8365mb_l2_uc *uc, u16 *data); +void rtl8365mb_c_l2_data_to_mc(const u16 *data, struct rtl8365mb_l2_mc *mc); +void rtl8365mb_c_l2_mc_to_data(const struct rtl8365mb_l2_mc *mc, u16 *data); + 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, diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c index 6f9c12f..525588e 100644 --- a/drivers/net/dsa/realtek/rtl8365mb_main.c +++ b/drivers/net/dsa/realtek/rtl8365mb_main.c @@ -793,6 +793,11 @@ static const struct rtl8365mb_family_info rtl8365mb_family_c = { .ext_config_forcemode = rtl8365mb_c_ext_config_forcemode, .vlan_4k_read = rtl8365mb_c_vlan_4k_read, .vlan_4k_write = rtl8365mb_c_vlan_4k_write, + .l2_entry_size = RTL8365MB_C_L2_ENTRY_SIZE, + .l2_data_to_uc = rtl8365mb_c_l2_data_to_uc, + .l2_uc_to_data = rtl8365mb_c_l2_uc_to_data, + .l2_data_to_mc = rtl8365mb_c_l2_data_to_mc, + .l2_mc_to_data = rtl8365mb_c_l2_mc_to_data, }; /** -- 2.53.0 The L2 lookup table on family D switches (e.g. RTL8367S-VB) differs from family C in ways that make the current FDB/MDB code misbehave: - An L2 entry is 5 words instead of 6, with a different field layout in words 3 and 4 (port, age, static and multicast member bits). - There is no EFID and no per-bridge FDB isolation. The register at 0x0A32, used as the per-port EFID register on family C, is the LUT aging timer on family D, so writing an EFID there corrupts it. - In the table access interface, the SPA field is 3 bits wide and the address register is 12 bits wide, with no address extension bit. - Only ports 0..7 are present in the LUT and there is no FLUSH_PORT_EXT register. - A VLAN-unaware bridge reaches the driver with VID 0, where the chip learns in SVL (IVL=0, FID=0). An entry programmed by DSA with IVL set gets a different key than the learned one for the same MAC, so both coexist and the learned one keeps winning. Teach the table and L2 code about these differences. The L2 entry layout is selected through struct rtl8365mb_family_info, the other differences with rtl8365mb_is_d(). Family C behavior is unchanged. Specifically: - rtl8365mb_table: use the family D port mask and address width, and read the hit address from status bits [11:0]. - rtl8365mb_l2: add family D unicast/multicast entry (de)serializers, is selected through struct rtl8365mb_family_info. The IVL bit is set only for a non-zero VID, so VID 0 produces the SVL key (IVL=0, FID=0) and static unicast and multicast entries match the learned ones in a VLAN-unaware bridge. Entries read back with IVL=0 are reported with VID 0. Use age 6 for static unicast entries as the vendor code does. Look an entry up before deleting it, so that a missing entry is not allocated by the delete; as the key has no EFID, an entry found on another port or one that is not static is left alone. - rtl8365mb_main: make port_set_efid() a no-op on family D, add a LUT init step that clears IPMC_HASH and BCAM_DISABLE and writes 3000 to the aging timer register, as dal_rtl8367d_l2_init() does in the vendor RTL8367D API (the unit is not documented), and do not advertise fdb_isolation. Without EFID the bridges cannot be told apart in the LUT, so offload only one bridge (max_num_bridges = 1). Further bridges fall back to software forwarding, and traffic between them is isolated by the port isolation masks only. Without EFID, FID 0 is shared by every port that is not in a VLAN-aware bridge: standalone ports, ports of bridges left to software and the offloaded VLAN-unaware bridge. There is no isolation between them in the LUT, only the port isolation masks. DSA does not program FDB/MDB entries of standalone ports here, as fdb_isolation is not set, so those ports get no host address filtering and their traffic goes to the CPU. A MAC learned in FID 0 on a bridge port may therefore also be hit by traffic from a standalone port, or from a software-bridged one, until the entry ages out or is replaced. This was observed by Gustavo Foletto Mora: with the hosts on standalone ports of the same switch, unicast to a host was dropped once its MAC had been learned on the VLAN-unaware bridge side. With learning off on the bridge ports it was forwarded, and with a VLAN-aware bridge it did not happen. Register layouts follow the vendor RTL8367D API; the code itself is an independent implementation. Tested by Gustavo Foletto Mora on a TP-Link EX511 v2 (RTL8367S-VB, CPU port 6 over 1G SGMII to an IPQ5018). With a VLAN-aware bridge: per-VID FDB readback, static entries replacing learned ones and being deleted again on every port and VID, the same MAC as a static entry on two VIDs, MDB entries, VLAN add/delete churn, and moving a port to a second bridge (handled in software). With a VLAN-unaware bridge: a static entry with VID 0 replaces the learned one and traffic follows it, a static entry pointing at the ingress port drops the frames, the MAC relearns on the original port after the delete; traffic to the bridge MAC no longer floods to the WAN port; MDB entries limit the flood to the group members and the flood returns after the delete. Unicast and multicast forwarding were also checked with loopback cables between user ports (known and unknown unicast, static entries on the wrong port, MDB, 20k full-size pings without loss or port errors). MACs of foreign ports (a veth standing in for a Wi-Fi station) become entries on the CPU port through assisted learning. Real Wi-Fi stations, RGMII and 2500base-x were not covered by this test. Leaving a dynamic or foreign-port entry alone in l2_del_uc() was not exercised. Nothing points to them until the family D info is added together with the chip table entry in the last patch. Until then they are not static and are declared in rtl8365mb_l2.h, so that the build does not warn about unused functions. Reported-by: Gustavo Foletto Mora Closes: https://github.com/openwrt/openwrt/pull/19644#issuecomment-6708118180 Tested-by: Gustavo Foletto Mora Tested-by: Stanislaw Pal Signed-off-by: Mieczyslaw Nalewaj --- drivers/net/dsa/realtek/realtek.h | 4 +- drivers/net/dsa/realtek/rtl8365mb_l2.c | 124 +++++++++++++++++++++- drivers/net/dsa/realtek/rtl8365mb_l2.h | 6 ++ drivers/net/dsa/realtek/rtl8365mb_main.c | 55 +++++++++- drivers/net/dsa/realtek/rtl8365mb_table.c | 44 ++++++-- drivers/net/dsa/realtek/rtl83xx.c | 5 +- 6 files changed, 221 insertions(+), 17 deletions(-) diff --git a/drivers/net/dsa/realtek/realtek.h b/drivers/net/dsa/realtek/realtek.h index 1c0ca6d..578490f 100644 --- a/drivers/net/dsa/realtek/realtek.h +++ b/drivers/net/dsa/realtek/realtek.h @@ -71,7 +71,9 @@ struct realtek_priv { * 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. + * over FDB. On the RTL8367S-VB (family D), l2_del_uc also looks the + * entry up before writing it, as two separate table queries, so it + * does depend on this lock. */ struct mutex l2_lock; struct mii_bus *user_mii_bus; diff --git a/drivers/net/dsa/realtek/rtl8365mb_l2.c b/drivers/net/dsa/realtek/rtl8365mb_l2.c index cd5b333..14acc84 100644 --- a/drivers/net/dsa/realtek/rtl8365mb_l2.c +++ b/drivers/net/dsa/realtek/rtl8365mb_l2.c @@ -45,6 +45,21 @@ #define RTL8365MB_L2_MC_D5_MBR_HI2_MSK GENMASK(7, 7) #define RTL8365MB_L2_MC_MBR_HI2_MSK GENMASK(10, 10) +/* Family D unicast (auto-learn / static) entry */ +#define RTL8365MB_D_L2_UC_D3_VID_MSK GENMASK(11, 0) +#define RTL8365MB_D_L2_UC_D3_IVL_MSK BIT(13) +#define RTL8365MB_D_L2_UC_D3_PORT_LO_MSK GENMASK(15, 14) +#define RTL8365MB_D_L2_UC_D4_PORT_HI_MSK BIT(0) +#define RTL8365MB_D_L2_UC_D4_AGE_MSK GENMASK(3, 1) +#define RTL8365MB_D_L2_UC_D4_STATIC_MSK BIT(6) + +/* Family D multicast entry */ +#define RTL8365MB_D_L2_MC_D3_VID_MSK GENMASK(11, 0) +#define RTL8365MB_D_L2_MC_D3_IVL_MSK BIT(13) +#define RTL8365MB_D_L2_MC_D3_MBR_LO_MSK GENMASK(15, 14) +#define RTL8365MB_D_L2_MC_D4_MBR_HI_MSK GENMASK(5, 0) +#define RTL8365MB_D_L2_MC_D4_STATIC_MSK BIT(6) + /* 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. @@ -240,6 +255,76 @@ void rtl8365mb_c_l2_mc_to_data(const struct rtl8365mb_l2_mc *mc, data[5] |= FIELD_PREP(RTL8365MB_L2_MC_D5_MBR_HI2_MSK, val); } +void rtl8365mb_d_l2_data_to_uc(const u16 *data, + struct rtl8365mb_l2_uc *uc) +{ + u16 id = FIELD_GET(RTL8365MB_D_L2_UC_D3_VID_MSK, data[3]); + + rtl8365mb_l2_data_to_mac(data, uc->key.mac_addr); + uc->key.ivl = FIELD_GET(RTL8365MB_D_L2_UC_D3_IVL_MSK, data[3]); + /* D has one shared VID/FID field, selected by IVL */ + uc->key.vid = uc->key.ivl ? id : 0; + uc->key.fid = uc->key.ivl ? 0 : id; + uc->key.efid = 0; + uc->port = FIELD_GET(RTL8365MB_D_L2_UC_D3_PORT_LO_MSK, data[3]) | + (FIELD_GET(RTL8365MB_D_L2_UC_D4_PORT_HI_MSK, data[4]) << 2); + uc->age = FIELD_GET(RTL8365MB_D_L2_UC_D4_AGE_MSK, data[4]); + uc->is_static = FIELD_GET(RTL8365MB_D_L2_UC_D4_STATIC_MSK, data[4]); +} + +void rtl8365mb_d_l2_uc_to_data(const struct rtl8365mb_l2_uc *uc, + u16 *data) +{ + /* A VLAN-unaware bridge reaches us with VID 0. The chip learns such + * traffic in SVL (IVL=0, FID=0), so use the same key here, otherwise + * the static entry never matches the learned one. Only a real VID + * selects IVL. + */ + bool ivl = uc->key.ivl && uc->key.vid; + u16 id = ivl ? uc->key.vid : uc->key.fid; + + memset(data, 0, RTL8365MB_D_L2_ENTRY_SIZE * 2); + rtl8365mb_l2_mac_to_data(uc->key.mac_addr, data); + data[3] = FIELD_PREP(RTL8365MB_D_L2_UC_D3_VID_MSK, id) | + FIELD_PREP(RTL8365MB_D_L2_UC_D3_IVL_MSK, ivl) | + FIELD_PREP(RTL8365MB_D_L2_UC_D3_PORT_LO_MSK, uc->port & 0x3); + data[4] = FIELD_PREP(RTL8365MB_D_L2_UC_D4_PORT_HI_MSK, + (uc->port >> 2) & 0x1) | + FIELD_PREP(RTL8365MB_D_L2_UC_D4_AGE_MSK, uc->age) | + FIELD_PREP(RTL8365MB_D_L2_UC_D4_STATIC_MSK, uc->is_static); +} + +void rtl8365mb_d_l2_data_to_mc(const u16 *data, + struct rtl8365mb_l2_mc *mc) +{ + rtl8365mb_l2_data_to_mac(data, mc->key.mac_addr); + mc->key.vid = FIELD_GET(RTL8365MB_D_L2_MC_D3_VID_MSK, data[3]); + mc->key.ivl = FIELD_GET(RTL8365MB_D_L2_MC_D3_IVL_MSK, data[3]); + mc->member = FIELD_GET(RTL8365MB_D_L2_MC_D3_MBR_LO_MSK, data[3]) | + (FIELD_GET(RTL8365MB_D_L2_MC_D4_MBR_HI_MSK, data[4]) << 2); + mc->is_static = FIELD_GET(RTL8365MB_D_L2_MC_D4_STATIC_MSK, data[4]); + mc->igmpidx = 0; + mc->priority = 0; + mc->fwd_pri = 0; + mc->igmp_asic = 0; +} + +void rtl8365mb_d_l2_mc_to_data(const struct rtl8365mb_l2_mc *mc, + u16 *data) +{ + /* Same as for unicast: VID 0 means a VLAN-unaware bridge, use SVL. */ + bool ivl = mc->key.ivl && mc->key.vid; + + memset(data, 0, RTL8365MB_D_L2_ENTRY_SIZE * 2); + rtl8365mb_l2_mac_to_data(mc->key.mac_addr, data); + data[3] = FIELD_PREP(RTL8365MB_D_L2_MC_D3_VID_MSK, mc->key.vid) | + FIELD_PREP(RTL8365MB_D_L2_MC_D3_IVL_MSK, ivl) | + FIELD_PREP(RTL8365MB_D_L2_MC_D3_MBR_LO_MSK, mc->member & 0x3); + data[4] = FIELD_PREP(RTL8365MB_D_L2_MC_D4_MBR_HI_MSK, + (mc->member >> 2) & 0x3f) | + FIELD_PREP(RTL8365MB_D_L2_MC_D4_STATIC_MSK, mc->is_static); +} + /* * rtl8365mb_l2_get_next_uc() - get the next Unicast L2 entry * @priv: realtek_priv pointer @@ -310,8 +395,11 @@ int rtl8365mb_l2_add_uc(struct realtek_priv *priv, int port, * 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; + /* age greater than 0 adds/updates entries. D has no EFID, so efid + * is not serialized (bridges are isolated by the port isolation + * masks only). Vendor code writes age 6 for D. + */ + uc.age = rtl8365mb_is_d(priv) ? 6 : 1; fi = rtl8365mb_get_family_info(priv); fi->l2_uc_to_data(&uc, data); @@ -349,6 +437,38 @@ int rtl8365mb_l2_del_uc(struct realtek_priv *priv, int port, fi = rtl8365mb_get_family_info(priv); fi->l2_uc_to_data(&uc, data); + if (rtl8365mb_is_d(priv)) { + struct rtl8365mb_l2_uc found = { 0 }; + + /* Do not blind-write: look the entry up first (as the vendor + * code does) so a missing entry is not (re)allocated. The + * lookup and the write are separate table queries, so this + * is not atomic; it relies on FDB operations being + * serialized by the caller. + */ + ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2, + RTL8365MB_TABLE_OP_READ, &addr, + RTL8365MB_TABLE_L2_METHOD_MAC, 0, + data, fi->l2_entry_size); + if (ret == -ENOENT) + return 0; + if (ret) + return ret; + + /* The key has no EFID on family D, so the entry found may + * belong to another port or bridge: leave it alone. DSA only + * programs static entries, so leave a dynamic one alone too, + * as the hardware may have relearned or aged it since the + * lookup. + */ + fi->l2_data_to_uc(data, &found); + if (found.port != port || !found.is_static) + return 0; + + /* rebuild the write payload: the read overwrote data[] */ + fi->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 diff --git a/drivers/net/dsa/realtek/rtl8365mb_l2.h b/drivers/net/dsa/realtek/rtl8365mb_l2.h index a9f9ae4..5bb4e32 100644 --- a/drivers/net/dsa/realtek/rtl8365mb_l2.h +++ b/drivers/net/dsa/realtek/rtl8365mb_l2.h @@ -13,6 +13,7 @@ #include "realtek.h" #define RTL8365MB_C_L2_ENTRY_SIZE 6 +#define RTL8365MB_D_L2_ENTRY_SIZE 5 /* Highest L2 entry size of any supported chip, used for sizing */ #define RTL8365MB_L2_ENTRY_SIZE RTL8365MB_C_L2_ENTRY_SIZE @@ -24,6 +25,11 @@ void rtl8365mb_c_l2_uc_to_data(const struct rtl8365mb_l2_uc *uc, u16 *data); void rtl8365mb_c_l2_data_to_mc(const u16 *data, struct rtl8365mb_l2_mc *mc); void rtl8365mb_c_l2_mc_to_data(const struct rtl8365mb_l2_mc *mc, u16 *data); +void rtl8365mb_d_l2_data_to_uc(const u16 *data, struct rtl8365mb_l2_uc *uc); +void rtl8365mb_d_l2_uc_to_data(const struct rtl8365mb_l2_uc *uc, u16 *data); +void rtl8365mb_d_l2_data_to_mc(const u16 *data, struct rtl8365mb_l2_mc *mc); +void rtl8365mb_d_l2_mc_to_data(const struct rtl8365mb_l2_mc *mc, u16 *data); + 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, diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c index 525588e..5d822ad 100644 --- a/drivers/net/dsa/realtek/rtl8365mb_main.c +++ b/drivers/net/dsa/realtek/rtl8365mb_main.c @@ -435,6 +435,18 @@ #define RTL8365MB_LUT_PORT_LEARN_LIMIT_REG(_physport) \ (RTL8365MB_LUT_PORT_LEARN_LIMIT_BASE + (_physport)) +/* Family D LUT configuration */ +#define RTL8365MB_D_LUT_CFG_REG 0x0A30 +#define RTL8365MB_D_LUT_CFG_BCAM_DISABLE_MASK BIT(6) +#define RTL8365MB_D_LUT_CFG_IPMC_HASH_MASK BIT(4) +/* On family C this address is the per-port EFID register! */ +#define RTL8365MB_D_LUT_CFG2_REG 0x0A32 +/* Written as a whole register, as dal_rtl8367d_l2_init() does in the + * vendor API: the field layout and the unit of this value are not + * documented. + */ +#define RTL8365MB_D_LUT_AGING_DEFAULT 3000 + /* Port isolation (forwarding mask) registers */ #define RTL8365MB_PORT_ISOLATION_REG_BASE 0x08A2 #define RTL8365MB_PORT_ISOLATION_REG(_physport) \ @@ -2650,6 +2662,10 @@ static int rtl8365mb_port_pre_bridge_flags(struct dsa_switch *ds, int port, static int rtl8365mb_port_set_efid(struct realtek_priv *priv, int port, u32 efid) { + /* Family D has no EFID; see RTL8365MB_D_LUT_CFG2_REG. */ + if (rtl8365mb_is_d(priv)) + return 0; + return regmap_update_bits(priv->map, RTL8365MB_PORT_EFID_REG(port), RTL8365MB_PORT_EFID_MASK(port), efid << RTL8365MB_PORT_EFID_OFFSET(port)); @@ -3386,6 +3402,23 @@ static int rtl8365mb_change_tag_protocol(struct dsa_switch *ds, return rtl8365mb_cpu_config(priv); } +static int rtl8365mb_d_lut_init(struct realtek_priv *priv) +{ + int ret; + + /* After the jam table: lookup multicast by MAC (not DIP) and keep + * the CAM enabled. + */ + ret = regmap_update_bits(priv->map, RTL8365MB_D_LUT_CFG_REG, + RTL8365MB_D_LUT_CFG_IPMC_HASH_MASK | + RTL8365MB_D_LUT_CFG_BCAM_DISABLE_MASK, 0); + if (ret) + return ret; + + return regmap_write(priv->map, RTL8365MB_D_LUT_CFG2_REG, + RTL8365MB_D_LUT_AGING_DEFAULT); +} + static int rtl8365mb_switch_init(struct realtek_priv *priv) { struct rtl8365mb *mb = priv->chip_data; @@ -3475,6 +3508,15 @@ static int rtl8365mb_setup(struct dsa_switch *ds) goto out_error; } + if (rtl8365mb_is_d(priv)) { + ret = rtl8365mb_d_lut_init(priv); + if (ret) { + dev_err(priv->dev, "failed to init LUT: %pe\n", + ERR_PTR(ret)); + goto out_error; + } + } + if (mb->sds_supported) { ret = rtl8365mb_sds_raise_rate_limits(priv); if (ret) { @@ -3600,9 +3642,16 @@ static int rtl8365mb_setup(struct dsa_switch *ds) 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); + if (!rtl8365mb_is_d(priv)) { + ds->fdb_isolation = true; + /* The EFID is 3 bits, but EFID 0 is reserved for standalone */ + ds->max_num_bridges = FIELD_MAX(RTL8365MB_EFID_MASK); + } else { + /* No EFID, so bridges cannot be told apart in the LUT: offload + * only one, others fall back to software forwarding. + */ + ds->max_num_bridges = 1; + } ds->configure_vlan_while_not_filtering = true; diff --git a/drivers/net/dsa/realtek/rtl8365mb_table.c b/drivers/net/dsa/realtek/rtl8365mb_table.c index f3c8749..6b1323f 100644 --- a/drivers/net/dsa/realtek/rtl8365mb_table.c +++ b/drivers/net/dsa/realtek/rtl8365mb_table.c @@ -5,6 +5,7 @@ */ #include "rtl8365mb_table.h" +#include "rtl8365mb.h" #include /* Table access control register */ @@ -16,10 +17,13 @@ /* 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) +/* Family D: SPA is 3 bits */ +#define RTL8365MB_D_TABLE_CTRL_PORT_MASK GENMASK(10, 8) /* Table access address register */ #define RTL8365MB_TABLE_ACCESS_ADDR_REG 0x0501 #define RTL8365MB_TABLE_ADDR_MASK GENMASK(12, 0) +#define RTL8365MB_D_TABLE_ADDR_MASK GENMASK(11, 0) /* Table status register */ #define RTL8365MB_TABLE_STATUS_REG 0x0502 @@ -29,6 +33,8 @@ #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) +/* Family D: the address is bits [11:0], there is no EXT bit */ +#define RTL8365MB_D_TABLE_STATUS_ADDRESS_MASK GENMASK(11, 0) /* Table read/write registers */ #define RTL8365MB_TABLE_WRITE_BASE 0x0510 @@ -59,6 +65,7 @@ int rtl8365mb_table_query(struct realtek_priv *priv, { bool addr_as_input = true; bool write_data = false; + bool is_d = rtl8365mb_is_d(priv); int ret = 0; u32 cmd; u32 val; @@ -91,7 +98,13 @@ int rtl8365mb_table_query(struct realtek_priv *priv, 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); + if (is_d) { + cmd |= FIELD_PREP(RTL8365MB_D_TABLE_CTRL_PORT_MASK, + port); + } else { + cmd |= FIELD_PREP(RTL8365MB_TABLE_CTRL_PORT_MASK, + port); + } break; default: return -EINVAL; @@ -146,8 +159,9 @@ int rtl8365mb_table_query(struct realtek_priv *priv, if (addr_as_input) { ret = regmap_write(priv->map_nolock, RTL8365MB_TABLE_ACCESS_ADDR_REG, - FIELD_PREP(RTL8365MB_TABLE_ADDR_MASK, - *addr)); + is_d ? + FIELD_PREP(RTL8365MB_D_TABLE_ADDR_MASK, *addr) : + FIELD_PREP(RTL8365MB_TABLE_ADDR_MASK, *addr)); if (ret) goto out; } @@ -177,13 +191,23 @@ int rtl8365mb_table_query(struct realtek_priv *priv, } /* 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; + if (is_d) { + /* Bit 11 is "TYPE" in the register map, but the entry + * index (2048 hash + 64 CAM) needs 12 bits; the vendor + * code reads [11:0] as well. + */ + *addr = FIELD_GET(RTL8365MB_D_TABLE_STATUS_ADDRESS_MASK, + val); + } else { + *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 */ diff --git a/drivers/net/dsa/realtek/rtl83xx.c b/drivers/net/dsa/realtek/rtl83xx.c index ad42c05..cfe7d42 100644 --- a/drivers/net/dsa/realtek/rtl83xx.c +++ b/drivers/net/dsa/realtek/rtl83xx.c @@ -602,7 +602,8 @@ int rtl83xx_port_fdb_add(struct dsa_switch *ds, int port, /* 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. + * to assisted learning. Chips without an EFID in their L2 table + * (RTL8367S-VB) ignore it in l2_add_uc(). */ efid = db.type == DSA_DB_BRIDGE ? db.bridge.num : 0; @@ -654,6 +655,8 @@ int rtl83xx_port_fdb_del(struct dsa_switch *ds, int port, /* * 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. + * Chips without an EFID in their L2 table (RTL8367S-VB) ignore it in + * l2_del_uc(). */ efid = db.type == DSA_DB_BRIDGE ? db.bridge.num : 0; -- 2.53.0 Add the RTL8367S-VB (chip ID 0x6642, version 0x0010) to the chip table. It is the first chip of family D, whose handling was added in the preceding patches, so this is the patch that makes it reachable. The SGMII/HSGMII SerDes of this chip sits on port 6 and the MII/TMII/RMII/RGMII interface on port 7, as on family C, but the external interface indices are one lower (EXT0 and EXT1 instead of EXT1 and EXT2). The chip reuses the existing initialization jam table. The chip has 8 ports, not 11, which is described by rtl8365mb_family_d. This sets ds->num_ports, the IRQ domain size and the bound of every loop over priv->num_ports for this chip. rtl8365mb_family_d also selects the family D implementations added in the preceding patches: the force-mode configuration, the VLAN 4k accessors and the L2 entry (de)serializers. The L2 learning limit is the same as on family C (2112 entries), as given by the Realtek rtl8367 common API (rtl8367_common V1.4.2), so RTL8365MB_LEARN_LIMIT_MAX needs no per-family handling; only its comment is updated. Tested on a TP-Link Archer AX55 v1 (RTL8367S-VB, CPU port over HSGMII to an IPQ5018) with bridge VLAN filtering and per-port PVIDs, and on a TP-Link EX511 v2 (RTL8367S-VB, CPU port over 1G SGMII to an IPQ5018). RGMII mode has not been tested. See the cover letter for details. Tested-by: Gustavo Foletto Mora Signed-off-by: Mieczyslaw Nalewaj Reviewed-by: Linus Walleij --- drivers/net/dsa/realtek/Kconfig | 3 +- drivers/net/dsa/realtek/rtl8365mb_main.c | 41 ++++++++++++++++++++++-- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/drivers/net/dsa/realtek/Kconfig b/drivers/net/dsa/realtek/Kconfig index d6eb671..98b1727 100644 --- a/drivers/net/dsa/realtek/Kconfig +++ b/drivers/net/dsa/realtek/Kconfig @@ -34,7 +34,8 @@ config NET_DSA_REALTEK_RTL8365MB depends on NET_DSA_REALTEK_SMI || NET_DSA_REALTEK_MDIO select NET_DSA_TAG_RTL8_4 help - Select to enable support for Realtek RTL8365MB-VC and RTL8367S. + Select to enable support for Realtek RTL8365MB-VC, RTL8367S and + RTL8367S-VB. config NET_DSA_REALTEK_RTL8366RB tristate "Realtek RTL8366RB switch driver" diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c index 5d822ad..27ec031 100644 --- a/drivers/net/dsa/realtek/rtl8365mb_main.c +++ b/drivers/net/dsa/realtek/rtl8365mb_main.c @@ -89,6 +89,14 @@ * 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. + * + * The RTL8367S-VB (chip ID 0x6642) is not part of the family listed above. It + * belongs to a newer generation, which this driver calls family D. Its + * registers differ in the VLAN table, the external interface speed and RGMII + * configuration, the SerDes, the L2 lookup table (FDB/MDB entry layout, no + * EFID, LUT configuration registers) and the table access control register, + * so the code paths specific to it are selected with rtl8365mb_is_d() or + * through struct rtl8365mb_family_info */ #include @@ -117,11 +125,12 @@ #define RTL8365MB_NUM_PHYREGS 32 #define RTL8365MB_PHYREGMAX (RTL8365MB_NUM_PHYREGS - 1) #define RTL8365MB_C_MAX_NUM_PORTS 11 +#define RTL8365MB_D_MAX_NUM_PORTS 8 /* Highest port count of any supported chip, used for sizing */ #define RTL8365MB_MAX_NUM_PORTS RTL8365MB_C_MAX_NUM_PORTS -/* 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. +/* Valid for the whole RTL8367C family (and for family D, RTL8367S-VB) + * 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 @@ -812,6 +821,21 @@ static const struct rtl8365mb_family_info rtl8365mb_family_c = { .l2_mc_to_data = rtl8365mb_c_l2_mc_to_data, }; +static const struct rtl8365mb_family_info rtl8365mb_family_d = { + .family = RTL8365MB_FAMILY_D, + .num_ports = RTL8365MB_D_MAX_NUM_PORTS, + .jam_table = rtl8365mb_init_jam_8365mb_vc, + .jam_size = ARRAY_SIZE(rtl8365mb_init_jam_8365mb_vc), + .ext_config_forcemode = rtl8365mb_d_ext_config_forcemode, + .vlan_4k_read = rtl8365mb_d_vlan_4k_read, + .vlan_4k_write = rtl8365mb_d_vlan_4k_write, + .l2_entry_size = RTL8365MB_D_L2_ENTRY_SIZE, + .l2_data_to_uc = rtl8365mb_d_l2_data_to_uc, + .l2_uc_to_data = rtl8365mb_d_l2_uc_to_data, + .l2_data_to_mc = rtl8365mb_d_l2_data_to_mc, + .l2_mc_to_data = rtl8365mb_d_l2_mc_to_data, +}; + /** * struct rtl8365mb_chip_info - static chip-specific info * @name: human-readable chip name @@ -881,6 +905,17 @@ static const struct rtl8365mb_chip_info rtl8365mb_chip_infos[] = { PHY_INTF(RMII) | PHY_INTF(RGMII) }, }, }, + { + .name = "RTL8367S-VB", + .chip_id = 0x6642, + .chip_ver = 0x0010, + .family_info = &rtl8365mb_family_d, + .extints = { + { 6, 0, PHY_INTF(SGMII) | PHY_INTF(HSGMII) }, + { 7, 1, PHY_INTF(MII) | PHY_INTF(TMII) | + PHY_INTF(RMII) | PHY_INTF(RGMII) }, + }, + }, }; enum rtl8365mb_stp_state { -- 2.53.0