summaryrefslogtreecommitdiff
path: root/drivers/net/dsa
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/dsa')
-rw-r--r--drivers/net/dsa/Kconfig19
-rw-r--r--drivers/net/dsa/Makefile3
-rw-r--r--drivers/net/dsa/b53/b53_mdio.c2
-rw-r--r--drivers/net/dsa/b53/b53_srab.c2
-rw-r--r--drivers/net/dsa/bcm_sf2.c2
-rw-r--r--drivers/net/dsa/hirschmann/hellcreek.c2
-rw-r--r--drivers/net/dsa/ks8995.c857
-rw-r--r--drivers/net/dsa/lan9303_i2c.c2
-rw-r--r--drivers/net/dsa/lan9303_mdio.c2
-rw-r--r--drivers/net/dsa/lantiq/lantiq_gswip.h2
-rw-r--r--drivers/net/dsa/lantiq/lantiq_gswip_common.c19
-rw-r--r--drivers/net/dsa/lantiq/mxl-gsw1xx.c66
-rw-r--r--drivers/net/dsa/lantiq/mxl-gsw1xx.h2
-rw-r--r--drivers/net/dsa/microchip/Kconfig1
-rw-r--r--drivers/net/dsa/microchip/ksz8.c254
-rw-r--r--drivers/net/dsa/microchip/ksz8.h2
-rw-r--r--drivers/net/dsa/microchip/ksz8_reg.h2
-rw-r--r--drivers/net/dsa/microchip/ksz9477.c1
-rw-r--r--drivers/net/dsa/microchip/ksz_common.c136
-rw-r--r--drivers/net/dsa/microchip/ksz_common.h18
-rw-r--r--drivers/net/dsa/microchip/ksz_dcb.c68
-rw-r--r--drivers/net/dsa/microchip/ksz_ptp.c490
-rw-r--r--drivers/net/dsa/microchip/ksz_ptp.h9
-rw-r--r--drivers/net/dsa/microchip/ksz_ptp_reg.h16
-rw-r--r--drivers/net/dsa/microchip/ksz_spi.c33
-rw-r--r--drivers/net/dsa/microchip/lan937x_main.c1
-rw-r--r--drivers/net/dsa/motorcomm/Kconfig17
-rw-r--r--drivers/net/dsa/motorcomm/Makefile9
-rw-r--r--drivers/net/dsa/motorcomm/chip.c2680
-rw-r--r--drivers/net/dsa/motorcomm/chip.h568
-rw-r--r--drivers/net/dsa/motorcomm/leds.c655
-rw-r--r--drivers/net/dsa/motorcomm/leds.h118
-rw-r--r--drivers/net/dsa/motorcomm/mdio_bus.c299
-rw-r--r--drivers/net/dsa/motorcomm/mdio_bus.h54
-rw-r--r--drivers/net/dsa/motorcomm/mib.c431
-rw-r--r--drivers/net/dsa/motorcomm/mib.h167
-rw-r--r--drivers/net/dsa/motorcomm/pcs-921x.c245
-rw-r--r--drivers/net/dsa/motorcomm/pcs.h13
-rw-r--r--drivers/net/dsa/motorcomm/smi.c180
-rw-r--r--drivers/net/dsa/motorcomm/smi.h61
-rw-r--r--drivers/net/dsa/motorcomm/tc.c1711
-rw-r--r--drivers/net/dsa/motorcomm/tc.h387
-rw-r--r--drivers/net/dsa/mt7530-mdio.c14
-rw-r--r--drivers/net/dsa/mt7530-mmio.c2
-rw-r--r--drivers/net/dsa/mt7530.c836
-rw-r--r--drivers/net/dsa/mt7530.h221
-rw-r--r--drivers/net/dsa/mv88e6060.c3
-rw-r--r--drivers/net/dsa/mv88e6xxx/chip.c9
-rw-r--r--drivers/net/dsa/qca/qca8k-8xxx.c70
-rw-r--r--drivers/net/dsa/qca/qca8k-common.c15
-rw-r--r--drivers/net/dsa/realtek/Makefile28
-rw-r--r--drivers/net/dsa/realtek/realtek.h8
-rw-r--r--drivers/net/dsa/realtek/rtl8365mb_main.c313
-rw-r--r--drivers/net/dsa/realtek/rtl8365mb_vlan.c4
-rw-r--r--drivers/net/dsa/realtek/rtl8366rb.c2
-rw-r--r--drivers/net/dsa/realtek/rtl83xx.c43
-rw-r--r--drivers/net/dsa/rzn1_a5psw.c2
-rw-r--r--drivers/net/dsa/sja1105/sja1105_main.c2
-rw-r--r--drivers/net/dsa/vitesse-vsc73xx-spi.c9
-rw-r--r--drivers/net/dsa/yt921x.c4982
-rw-r--r--drivers/net/dsa/yt921x.h977
61 files changed, 9437 insertions, 7709 deletions
diff --git a/drivers/net/dsa/Kconfig b/drivers/net/dsa/Kconfig
index 676fb7dffe14..fe8cd5338fda 100644
--- a/drivers/net/dsa/Kconfig
+++ b/drivers/net/dsa/Kconfig
@@ -82,6 +82,8 @@ config NET_DSA_MV88E6060
source "drivers/net/dsa/microchip/Kconfig"
+source "drivers/net/dsa/motorcomm/Kconfig"
+
source "drivers/net/dsa/mv88e6xxx/Kconfig"
source "drivers/net/dsa/mxl862xx/Kconfig"
@@ -110,10 +112,13 @@ config NET_DSA_RZN1_A5PSW
config NET_DSA_KS8995
tristate "Micrel KS8995 family 5-ports 10/100 Ethernet switches"
depends on SPI
- select NET_DSA_TAG_NONE
+ select NET_DSA_MICROCHIP_KSZ_COMMON
+ select NET_DSA_MICROCHIP_KSZ_SPI
help
- This driver supports the Micrel KS8995 family of 10/100 Mbit ethernet
- switches, managed over SPI.
+ This is a transitional option for users who had NET_DSA_KS8995
+ enabled. It automatically enables the new NET_DSA_MICROCHIP_KSZ_SPI
+ driver which supersedes it. This option will be removed in a future
+ kernel release.
config NET_DSA_SMSC_LAN9303
tristate
@@ -168,12 +173,4 @@ config NET_DSA_VITESSE_VSC73XX_PLATFORM
This enables support for the Vitesse VSC7385, VSC7388, VSC7395
and VSC7398 SparX integrated ethernet switches, connected over
a CPU-attached address bus and work in memory-mapped I/O mode.
-
-config NET_DSA_YT921X
- tristate "Motorcomm YT9215 ethernet switch chip support"
- select NET_DSA_TAG_YT921X
- select NET_IEEE8021Q_HELPERS if DCB
- help
- This enables support for the Motorcomm YT9215 ethernet switch
- chip.
endmenu
diff --git a/drivers/net/dsa/Makefile b/drivers/net/dsa/Makefile
index 6ceb78a755d7..7e637d56b35c 100644
--- a/drivers/net/dsa/Makefile
+++ b/drivers/net/dsa/Makefile
@@ -2,7 +2,6 @@
obj-$(CONFIG_NET_DSA_BCM_SF2) += bcm-sf2.o
bcm-sf2-objs := bcm_sf2.o bcm_sf2_cfp.o
obj-$(CONFIG_NET_DSA_LOOP) += dsa_loop.o
-obj-$(CONFIG_NET_DSA_KS8995) += ks8995.o
obj-$(CONFIG_NET_DSA_MT7530) += mt7530.o
obj-$(CONFIG_NET_DSA_MT7530_MDIO) += mt7530-mdio.o
obj-$(CONFIG_NET_DSA_MT7530_MMIO) += mt7530-mmio.o
@@ -15,11 +14,11 @@ obj-$(CONFIG_NET_DSA_SMSC_LAN9303_MDIO) += lan9303_mdio.o
obj-$(CONFIG_NET_DSA_VITESSE_VSC73XX) += vitesse-vsc73xx-core.o
obj-$(CONFIG_NET_DSA_VITESSE_VSC73XX_PLATFORM) += vitesse-vsc73xx-platform.o
obj-$(CONFIG_NET_DSA_VITESSE_VSC73XX_SPI) += vitesse-vsc73xx-spi.o
-obj-$(CONFIG_NET_DSA_YT921X) += yt921x.o
obj-y += b53/
obj-y += hirschmann/
obj-y += lantiq/
obj-y += microchip/
+obj-y += motorcomm/
obj-y += mv88e6xxx/
obj-y += mxl862xx/
obj-y += netc/
diff --git a/drivers/net/dsa/b53/b53_mdio.c b/drivers/net/dsa/b53/b53_mdio.c
index 43a3b37b731b..fc1ce57a80a3 100644
--- a/drivers/net/dsa/b53/b53_mdio.c
+++ b/drivers/net/dsa/b53/b53_mdio.c
@@ -384,7 +384,7 @@ static const struct of_device_id b53_of_match[] = {
{ .compatible = "brcm,bcm5395" },
{ .compatible = "brcm,bcm5397" },
{ .compatible = "brcm,bcm5398" },
- { /* sentinel */ },
+ { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, b53_of_match);
diff --git a/drivers/net/dsa/b53/b53_srab.c b/drivers/net/dsa/b53/b53_srab.c
index b9939bbd2cd5..83a03fca142a 100644
--- a/drivers/net/dsa/b53/b53_srab.c
+++ b/drivers/net/dsa/b53/b53_srab.c
@@ -515,7 +515,7 @@ static const struct of_device_id b53_srab_of_match[] = {
{ .compatible = "brcm,cygnus-srab", .data = (void *)BCM583XX_DEVICE_ID },
{ .compatible = "brcm,nsp-srab", .data = (void *)BCM58XX_DEVICE_ID },
{ .compatible = "brcm,omega-srab", .data = (void *)BCM583XX_DEVICE_ID },
- { /* sentinel */ },
+ { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, b53_srab_of_match);
diff --git a/drivers/net/dsa/bcm_sf2.c b/drivers/net/dsa/bcm_sf2.c
index de3efa3ce9a7..9e5713015187 100644
--- a/drivers/net/dsa/bcm_sf2.c
+++ b/drivers/net/dsa/bcm_sf2.c
@@ -1358,7 +1358,7 @@ static const struct of_device_id bcm_sf2_of_match[] = {
{ .compatible = "brcm,bcm7278-switch-v4.8",
.data = &bcm_sf2_7278_data
},
- { /* sentinel */ },
+ { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, bcm_sf2_of_match);
diff --git a/drivers/net/dsa/hirschmann/hellcreek.c b/drivers/net/dsa/hirschmann/hellcreek.c
index 2c6cabf61143..aec50414986e 100644
--- a/drivers/net/dsa/hirschmann/hellcreek.c
+++ b/drivers/net/dsa/hirschmann/hellcreek.c
@@ -2093,7 +2093,7 @@ static const struct of_device_id hellcreek_of_match[] = {
.compatible = "hirschmann,hellcreek-de1soc-r1",
.data = &de1soc_r1_pdata,
},
- { /* sentinel */ },
+ { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, hellcreek_of_match);
diff --git a/drivers/net/dsa/ks8995.c b/drivers/net/dsa/ks8995.c
deleted file mode 100644
index 77d8b842693c..000000000000
--- a/drivers/net/dsa/ks8995.c
+++ /dev/null
@@ -1,857 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * SPI driver for Micrel/Kendin KS8995M and KSZ8864RMN ethernet switches
- *
- * Copyright (C) 2008 Gabor Juhos <juhosg at openwrt.org>
- * Copyright (C) 2025 Linus Walleij <linus.walleij@linaro.org>
- *
- * This file was based on: drivers/spi/at25.c
- * Copyright (C) 2006 David Brownell
- */
-
-#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
-
-#include <linux/bits.h>
-#include <linux/if_bridge.h>
-#include <linux/if_vlan.h>
-#include <linux/types.h>
-#include <linux/kernel.h>
-#include <linux/module.h>
-#include <linux/delay.h>
-#include <linux/device.h>
-#include <linux/gpio/consumer.h>
-#include <linux/of.h>
-#include <linux/spi/spi.h>
-#include <net/dsa.h>
-
-#define DRV_VERSION "0.1.1"
-#define DRV_DESC "Micrel KS8995 Ethernet switch SPI driver"
-
-/* ------------------------------------------------------------------------ */
-
-#define KS8995_REG_ID0 0x00 /* Chip ID0 */
-#define KS8995_REG_ID1 0x01 /* Chip ID1 */
-
-#define KS8995_REG_GC0 0x02 /* Global Control 0 */
-
-#define KS8995_GC0_P5_PHY BIT(3) /* Port 5 PHY enabled */
-
-#define KS8995_REG_GC1 0x03 /* Global Control 1 */
-#define KS8995_REG_GC2 0x04 /* Global Control 2 */
-
-#define KS8995_GC2_HUGE BIT(2) /* Huge packet support */
-#define KS8995_GC2_LEGAL BIT(1) /* Legal size override */
-
-#define KS8995_REG_GC3 0x05 /* Global Control 3 */
-#define KS8995_REG_GC4 0x06 /* Global Control 4 */
-
-#define KS8995_GC4_10BT BIT(4) /* Force switch to 10Mbit */
-#define KS8995_GC4_MII_FLOW BIT(5) /* MII full-duplex flow control enable */
-#define KS8995_GC4_MII_HD BIT(6) /* MII half-duplex mode enable */
-
-#define KS8995_REG_GC5 0x07 /* Global Control 5 */
-#define KS8995_REG_GC6 0x08 /* Global Control 6 */
-#define KS8995_REG_GC7 0x09 /* Global Control 7 */
-#define KS8995_REG_GC8 0x0a /* Global Control 8 */
-#define KS8995_REG_GC9 0x0b /* Global Control 9 */
-
-#define KS8995_GC9_SPECIAL BIT(0) /* Special tagging mode (DSA) */
-
-/* In DSA the ports 1-4 are numbered 0-3 and the CPU port is port 4 */
-#define KS8995_REG_PC(p, r) (0x10 + (0x10 * (p)) + (r)) /* Port Control */
-#define KS8995_REG_PS(p, r) (0x1e + (0x10 * (p)) + (r)) /* Port Status */
-
-#define KS8995_REG_PC0 0x00 /* Port Control 0 */
-#define KS8995_REG_PC1 0x01 /* Port Control 1 */
-#define KS8995_REG_PC2 0x02 /* Port Control 2 */
-#define KS8995_REG_PC3 0x03 /* Port Control 3 */
-#define KS8995_REG_PC4 0x04 /* Port Control 4 */
-#define KS8995_REG_PC5 0x05 /* Port Control 5 */
-#define KS8995_REG_PC6 0x06 /* Port Control 6 */
-#define KS8995_REG_PC7 0x07 /* Port Control 7 */
-#define KS8995_REG_PC8 0x08 /* Port Control 8 */
-#define KS8995_REG_PC9 0x09 /* Port Control 9 */
-#define KS8995_REG_PC10 0x0a /* Port Control 10 */
-#define KS8995_REG_PC11 0x0b /* Port Control 11 */
-#define KS8995_REG_PC12 0x0c /* Port Control 12 */
-#define KS8995_REG_PC13 0x0d /* Port Control 13 */
-
-#define KS8995_PC0_TAG_INS BIT(2) /* Enable tag insertion on port */
-#define KS8995_PC0_TAG_REM BIT(1) /* Enable tag removal on port */
-#define KS8995_PC0_PRIO_EN BIT(0) /* Enable priority handling */
-
-#define KS8995_PC2_TXEN BIT(2) /* Enable TX on port */
-#define KS8995_PC2_RXEN BIT(1) /* Enable RX on port */
-#define KS8995_PC2_LEARN_DIS BIT(0) /* Disable learning on port */
-
-#define KS8995_PC13_TXDIS BIT(6) /* Disable transmitter */
-#define KS8995_PC13_PWDN BIT(3) /* Power down */
-
-#define KS8995_REG_TPC0 0x60 /* TOS Priority Control 0 */
-#define KS8995_REG_TPC1 0x61 /* TOS Priority Control 1 */
-#define KS8995_REG_TPC2 0x62 /* TOS Priority Control 2 */
-#define KS8995_REG_TPC3 0x63 /* TOS Priority Control 3 */
-#define KS8995_REG_TPC4 0x64 /* TOS Priority Control 4 */
-#define KS8995_REG_TPC5 0x65 /* TOS Priority Control 5 */
-#define KS8995_REG_TPC6 0x66 /* TOS Priority Control 6 */
-#define KS8995_REG_TPC7 0x67 /* TOS Priority Control 7 */
-
-#define KS8995_REG_MAC0 0x68 /* MAC address 0 */
-#define KS8995_REG_MAC1 0x69 /* MAC address 1 */
-#define KS8995_REG_MAC2 0x6a /* MAC address 2 */
-#define KS8995_REG_MAC3 0x6b /* MAC address 3 */
-#define KS8995_REG_MAC4 0x6c /* MAC address 4 */
-#define KS8995_REG_MAC5 0x6d /* MAC address 5 */
-
-#define KS8995_REG_IAC0 0x6e /* Indirect Access Control 0 */
-#define KS8995_REG_IAC1 0x6f /* Indirect Access Control 0 */
-#define KS8995_REG_IAD7 0x70 /* Indirect Access Data 7 */
-#define KS8995_REG_IAD6 0x71 /* Indirect Access Data 6 */
-#define KS8995_REG_IAD5 0x72 /* Indirect Access Data 5 */
-#define KS8995_REG_IAD4 0x73 /* Indirect Access Data 4 */
-#define KS8995_REG_IAD3 0x74 /* Indirect Access Data 3 */
-#define KS8995_REG_IAD2 0x75 /* Indirect Access Data 2 */
-#define KS8995_REG_IAD1 0x76 /* Indirect Access Data 1 */
-#define KS8995_REG_IAD0 0x77 /* Indirect Access Data 0 */
-
-#define KSZ8864_REG_ID1 0xfe /* Chip ID in bit 7 */
-
-#define KS8995_REGS_SIZE 0x80
-#define KSZ8864_REGS_SIZE 0x100
-#define KSZ8795_REGS_SIZE 0x100
-
-#define ID1_CHIPID_M 0xf
-#define ID1_CHIPID_S 4
-#define ID1_REVISION_M 0x7
-#define ID1_REVISION_S 1
-#define ID1_START_SW 1 /* start the switch */
-
-#define FAMILY_KS8995 0x95
-#define FAMILY_KSZ8795 0x87
-#define CHIPID_M 0
-#define KS8995_CHIP_ID 0x00
-#define KSZ8864_CHIP_ID 0x01
-#define KSZ8795_CHIP_ID 0x09
-
-#define KS8995_CMD_WRITE 0x02U
-#define KS8995_CMD_READ 0x03U
-
-#define KS8995_CPU_PORT 4
-#define KS8995_NUM_PORTS 5 /* 5 ports including the CPU port */
-#define KS8995_RESET_DELAY 10 /* usec */
-
-enum ks8995_chip_variant {
- ks8995,
- ksz8864,
- ksz8795,
- max_variant
-};
-
-struct ks8995_chip_params {
- char *name;
- int family_id;
- int chip_id;
- int regs_size;
- int addr_width;
- int addr_shift;
-};
-
-static const struct ks8995_chip_params ks8995_chip[] = {
- [ks8995] = {
- .name = "KS8995MA",
- .family_id = FAMILY_KS8995,
- .chip_id = KS8995_CHIP_ID,
- .regs_size = KS8995_REGS_SIZE,
- .addr_width = 8,
- .addr_shift = 0,
- },
- [ksz8864] = {
- .name = "KSZ8864RMN",
- .family_id = FAMILY_KS8995,
- .chip_id = KSZ8864_CHIP_ID,
- .regs_size = KSZ8864_REGS_SIZE,
- .addr_width = 8,
- .addr_shift = 0,
- },
- [ksz8795] = {
- .name = "KSZ8795CLX",
- .family_id = FAMILY_KSZ8795,
- .chip_id = KSZ8795_CHIP_ID,
- .regs_size = KSZ8795_REGS_SIZE,
- .addr_width = 12,
- .addr_shift = 1,
- },
-};
-
-struct ks8995_switch {
- struct spi_device *spi;
- struct device *dev;
- struct dsa_switch *ds;
- struct mutex lock;
- struct gpio_desc *reset_gpio;
- struct bin_attribute regs_attr;
- const struct ks8995_chip_params *chip;
- int revision_id;
- unsigned int max_mtu[KS8995_NUM_PORTS];
-};
-
-static const struct spi_device_id ks8995_id[] = {
- {"ks8995", ks8995},
- {"ksz8864", ksz8864},
- {"ksz8795", ksz8795},
- { }
-};
-MODULE_DEVICE_TABLE(spi, ks8995_id);
-
-static const struct of_device_id ks8995_spi_of_match[] = {
- { .compatible = "micrel,ks8995" },
- { .compatible = "micrel,ksz8864" },
- { .compatible = "micrel,ksz8795" },
- { },
-};
-MODULE_DEVICE_TABLE(of, ks8995_spi_of_match);
-
-static inline u8 get_chip_id(u8 val)
-{
- return (val >> ID1_CHIPID_S) & ID1_CHIPID_M;
-}
-
-static inline u8 get_chip_rev(u8 val)
-{
- return (val >> ID1_REVISION_S) & ID1_REVISION_M;
-}
-
-/* create_spi_cmd - create a chip specific SPI command header
- * @ks: pointer to switch instance
- * @cmd: SPI command for switch
- * @address: register address for command
- *
- * Different chip families use different bit pattern to address the switches
- * registers:
- *
- * KS8995: 8bit command + 8bit address
- * KSZ8795: 3bit command + 12bit address + 1bit TR (?)
- */
-static inline __be16 create_spi_cmd(struct ks8995_switch *ks, int cmd,
- unsigned address)
-{
- u16 result = cmd;
-
- /* make room for address (incl. address shift) */
- result <<= ks->chip->addr_width + ks->chip->addr_shift;
- /* add address */
- result |= address << ks->chip->addr_shift;
- /* SPI protocol needs big endian */
- return cpu_to_be16(result);
-}
-/* ------------------------------------------------------------------------ */
-static int ks8995_read(struct ks8995_switch *ks, char *buf,
- unsigned offset, size_t count)
-{
- __be16 cmd;
- struct spi_transfer t[2];
- struct spi_message m;
- int err;
-
- cmd = create_spi_cmd(ks, KS8995_CMD_READ, offset);
- spi_message_init(&m);
-
- memset(&t, 0, sizeof(t));
-
- t[0].tx_buf = &cmd;
- t[0].len = sizeof(cmd);
- spi_message_add_tail(&t[0], &m);
-
- t[1].rx_buf = buf;
- t[1].len = count;
- spi_message_add_tail(&t[1], &m);
-
- mutex_lock(&ks->lock);
- err = spi_sync(ks->spi, &m);
- mutex_unlock(&ks->lock);
-
- return err ? err : count;
-}
-
-static int ks8995_write(struct ks8995_switch *ks, char *buf,
- unsigned offset, size_t count)
-{
- __be16 cmd;
- struct spi_transfer t[2];
- struct spi_message m;
- int err;
-
- cmd = create_spi_cmd(ks, KS8995_CMD_WRITE, offset);
- spi_message_init(&m);
-
- memset(&t, 0, sizeof(t));
-
- t[0].tx_buf = &cmd;
- t[0].len = sizeof(cmd);
- spi_message_add_tail(&t[0], &m);
-
- t[1].tx_buf = buf;
- t[1].len = count;
- spi_message_add_tail(&t[1], &m);
-
- mutex_lock(&ks->lock);
- err = spi_sync(ks->spi, &m);
- mutex_unlock(&ks->lock);
-
- return err ? err : count;
-}
-
-static inline int ks8995_read_reg(struct ks8995_switch *ks, u8 addr, u8 *buf)
-{
- return ks8995_read(ks, buf, addr, 1) != 1;
-}
-
-static inline int ks8995_write_reg(struct ks8995_switch *ks, u8 addr, u8 val)
-{
- char buf = val;
-
- return ks8995_write(ks, &buf, addr, 1) != 1;
-}
-
-/* ------------------------------------------------------------------------ */
-
-static int ks8995_stop(struct ks8995_switch *ks)
-{
- return ks8995_write_reg(ks, KS8995_REG_ID1, 0);
-}
-
-static int ks8995_start(struct ks8995_switch *ks)
-{
- return ks8995_write_reg(ks, KS8995_REG_ID1, 1);
-}
-
-static int ks8995_reset(struct ks8995_switch *ks)
-{
- int err;
-
- err = ks8995_stop(ks);
- if (err)
- return err;
-
- udelay(KS8995_RESET_DELAY);
-
- return ks8995_start(ks);
-}
-
-/* ks8995_get_revision - get chip revision
- * @ks: pointer to switch instance
- *
- * Verify chip family and id and get chip revision.
- */
-static int ks8995_get_revision(struct ks8995_switch *ks)
-{
- int err;
- u8 id0, id1, ksz8864_id;
-
- /* read family id */
- err = ks8995_read_reg(ks, KS8995_REG_ID0, &id0);
- if (err) {
- err = -EIO;
- goto err_out;
- }
-
- /* verify family id */
- if (id0 != ks->chip->family_id) {
- dev_err(&ks->spi->dev, "chip family id mismatch: expected 0x%02x but 0x%02x read\n",
- ks->chip->family_id, id0);
- err = -ENODEV;
- goto err_out;
- }
-
- switch (ks->chip->family_id) {
- case FAMILY_KS8995:
- /* try reading chip id at CHIP ID1 */
- err = ks8995_read_reg(ks, KS8995_REG_ID1, &id1);
- if (err) {
- err = -EIO;
- goto err_out;
- }
-
- /* verify chip id */
- if ((get_chip_id(id1) == CHIPID_M) &&
- (get_chip_id(id1) == ks->chip->chip_id)) {
- /* KS8995MA */
- ks->revision_id = get_chip_rev(id1);
- } else if (get_chip_id(id1) != CHIPID_M) {
- /* KSZ8864RMN */
- err = ks8995_read_reg(ks, KS8995_REG_ID1, &ksz8864_id);
- if (err) {
- err = -EIO;
- goto err_out;
- }
-
- if ((ksz8864_id & 0x80) &&
- (ks->chip->chip_id == KSZ8864_CHIP_ID)) {
- ks->revision_id = get_chip_rev(id1);
- }
-
- } else {
- dev_err(&ks->spi->dev, "unsupported chip id for KS8995 family: 0x%02x\n",
- id1);
- err = -ENODEV;
- }
- break;
- case FAMILY_KSZ8795:
- /* try reading chip id at CHIP ID1 */
- err = ks8995_read_reg(ks, KS8995_REG_ID1, &id1);
- if (err) {
- err = -EIO;
- goto err_out;
- }
-
- if (get_chip_id(id1) == ks->chip->chip_id) {
- ks->revision_id = get_chip_rev(id1);
- } else {
- dev_err(&ks->spi->dev, "unsupported chip id for KSZ8795 family: 0x%02x\n",
- id1);
- err = -ENODEV;
- }
- break;
- default:
- dev_err(&ks->spi->dev, "unsupported family id: 0x%02x\n", id0);
- err = -ENODEV;
- break;
- }
-err_out:
- return err;
-}
-
-static int ks8995_check_config(struct ks8995_switch *ks)
-{
- int ret;
- u8 val;
-
- ret = ks8995_read_reg(ks, KS8995_REG_GC0, &val);
- if (ret) {
- dev_err(ks->dev, "failed to read KS8995_REG_GC0\n");
- return ret;
- }
-
- dev_dbg(ks->dev, "port 5 PHY %senabled\n",
- (val & KS8995_GC0_P5_PHY) ? "" : "not ");
-
- val |= KS8995_GC0_P5_PHY;
- ret = ks8995_write_reg(ks, KS8995_REG_GC0, val);
- if (ret)
- dev_err(ks->dev, "failed to set KS8995_REG_GC0\n");
-
- dev_dbg(ks->dev, "set KS8995_REG_GC0 to 0x%02x\n", val);
-
- return 0;
-}
-
-static void
-ks8995_mac_config(struct phylink_config *config, unsigned int mode,
- const struct phylink_link_state *state)
-{
-}
-
-static void
-ks8995_mac_link_up(struct phylink_config *config, struct phy_device *phydev,
- unsigned int mode, phy_interface_t interface,
- int speed, int duplex, bool tx_pause, bool rx_pause)
-{
- struct dsa_port *dp = dsa_phylink_to_port(config);
- struct ks8995_switch *ks = dp->ds->priv;
- int port = dp->index;
- int ret;
- u8 val;
-
- /* Allow forcing the mode on the fixed CPU port, no autonegotiation.
- * We assume autonegotiation works on the PHY-facing ports.
- */
- if (port != KS8995_CPU_PORT)
- return;
-
- dev_dbg(ks->dev, "MAC link up on CPU port (%d)\n", port);
-
- ret = ks8995_read_reg(ks, KS8995_REG_GC4, &val);
- if (ret) {
- dev_err(ks->dev, "failed to read KS8995_REG_GC4\n");
- return;
- }
-
- /* Conjure port config */
- switch (speed) {
- case SPEED_10:
- dev_dbg(ks->dev, "set switch MII to 100Mbit mode\n");
- val |= KS8995_GC4_10BT;
- break;
- case SPEED_100:
- default:
- dev_dbg(ks->dev, "set switch MII to 100Mbit mode\n");
- val &= ~KS8995_GC4_10BT;
- break;
- }
-
- if (duplex == DUPLEX_HALF) {
- dev_dbg(ks->dev, "set switch MII to half duplex\n");
- val |= KS8995_GC4_MII_HD;
- } else {
- dev_dbg(ks->dev, "set switch MII to full duplex\n");
- val &= ~KS8995_GC4_MII_HD;
- }
-
- dev_dbg(ks->dev, "set KS8995_REG_GC4 to %02x\n", val);
-
- /* Enable the CPU port */
- ret = ks8995_write_reg(ks, KS8995_REG_GC4, val);
- if (ret)
- dev_err(ks->dev, "failed to set KS8995_REG_GC4\n");
-}
-
-static void
-ks8995_mac_link_down(struct phylink_config *config, unsigned int mode,
- phy_interface_t interface)
-{
- struct dsa_port *dp = dsa_phylink_to_port(config);
- struct ks8995_switch *ks = dp->ds->priv;
- int port = dp->index;
-
- if (port != KS8995_CPU_PORT)
- return;
-
- dev_dbg(ks->dev, "MAC link down on CPU port (%d)\n", port);
-
- /* Disable the CPU port */
-}
-
-static const struct phylink_mac_ops ks8995_phylink_mac_ops = {
- .mac_config = ks8995_mac_config,
- .mac_link_up = ks8995_mac_link_up,
- .mac_link_down = ks8995_mac_link_down,
-};
-
-static enum
-dsa_tag_protocol ks8995_get_tag_protocol(struct dsa_switch *ds,
- int port,
- enum dsa_tag_protocol mp)
-{
- /* This switch actually uses the 6 byte KS8995 protocol */
- return DSA_TAG_PROTO_NONE;
-}
-
-static int ks8995_setup(struct dsa_switch *ds)
-{
- return 0;
-}
-
-static int ks8995_port_enable(struct dsa_switch *ds, int port,
- struct phy_device *phy)
-{
- struct ks8995_switch *ks = ds->priv;
-
- dev_dbg(ks->dev, "enable port %d\n", port);
-
- return 0;
-}
-
-static void ks8995_port_disable(struct dsa_switch *ds, int port)
-{
- struct ks8995_switch *ks = ds->priv;
-
- dev_dbg(ks->dev, "disable port %d\n", port);
-}
-
-static int ks8995_port_pre_bridge_flags(struct dsa_switch *ds, int port,
- struct switchdev_brport_flags flags,
- struct netlink_ext_ack *extack)
-{
- /* We support enabling/disabling learning */
- if (flags.mask & ~(BR_LEARNING))
- return -EINVAL;
-
- return 0;
-}
-
-static int ks8995_port_bridge_flags(struct dsa_switch *ds, int port,
- struct switchdev_brport_flags flags,
- struct netlink_ext_ack *extack)
-{
- struct ks8995_switch *ks = ds->priv;
- int ret;
- u8 val;
-
- if (flags.mask & BR_LEARNING) {
- ret = ks8995_read_reg(ks, KS8995_REG_PC(port, KS8995_REG_PC2), &val);
- if (ret) {
- dev_err(ks->dev, "failed to read KS8995_REG_PC2 on port %d\n", port);
- return ret;
- }
-
- if (flags.val & BR_LEARNING)
- val &= ~KS8995_PC2_LEARN_DIS;
- else
- val |= KS8995_PC2_LEARN_DIS;
-
- ret = ks8995_write_reg(ks, KS8995_REG_PC(port, KS8995_REG_PC2), val);
- if (ret) {
- dev_err(ks->dev, "failed to write KS8995_REG_PC2 on port %d\n", port);
- return ret;
- }
- }
-
- return 0;
-}
-
-static void ks8995_port_stp_state_set(struct dsa_switch *ds, int port, u8 state)
-{
- struct ks8995_switch *ks = ds->priv;
- int ret;
- u8 val;
-
- ret = ks8995_read_reg(ks, KS8995_REG_PC(port, KS8995_REG_PC2), &val);
- if (ret) {
- dev_err(ks->dev, "failed to read KS8995_REG_PC2 on port %d\n", port);
- return;
- }
-
- /* Set the bits for the different STP states in accordance with
- * the datasheet, pages 36-37 "Spanning tree support".
- */
- switch (state) {
- case BR_STATE_DISABLED:
- case BR_STATE_BLOCKING:
- case BR_STATE_LISTENING:
- val &= ~KS8995_PC2_TXEN;
- val &= ~KS8995_PC2_RXEN;
- val |= KS8995_PC2_LEARN_DIS;
- break;
- case BR_STATE_LEARNING:
- val &= ~KS8995_PC2_TXEN;
- val &= ~KS8995_PC2_RXEN;
- val &= ~KS8995_PC2_LEARN_DIS;
- break;
- case BR_STATE_FORWARDING:
- val |= KS8995_PC2_TXEN;
- val |= KS8995_PC2_RXEN;
- val &= ~KS8995_PC2_LEARN_DIS;
- break;
- default:
- dev_err(ks->dev, "unknown bridge state requested\n");
- return;
- }
-
- ret = ks8995_write_reg(ks, KS8995_REG_PC(port, KS8995_REG_PC2), val);
- if (ret) {
- dev_err(ks->dev, "failed to write KS8995_REG_PC2 on port %d\n", port);
- return;
- }
-
- dev_dbg(ks->dev, "set KS8995_REG_PC2 for port %d to %02x\n", port, val);
-}
-
-static void ks8995_phylink_get_caps(struct dsa_switch *dsa, int port,
- struct phylink_config *config)
-{
- unsigned long *interfaces = config->supported_interfaces;
-
- if (port == KS8995_CPU_PORT)
- __set_bit(PHY_INTERFACE_MODE_MII, interfaces);
-
- if (port <= 3) {
- /* Internal PHYs */
- __set_bit(PHY_INTERFACE_MODE_INTERNAL, interfaces);
- /* phylib default */
- __set_bit(PHY_INTERFACE_MODE_MII, interfaces);
- }
-
- config->mac_capabilities = MAC_SYM_PAUSE | MAC_10 | MAC_100;
-}
-
-/* Huge packet support up to 1916 byte packages "inclusive"
- * which means that tags are included. If the bit is not set
- * it is 1536 bytes "inclusive". We present the length without
- * tags or ethernet headers. The setting affects all ports.
- */
-static int ks8995_change_mtu(struct dsa_switch *ds, int port, int new_mtu)
-{
- struct ks8995_switch *ks = ds->priv;
- unsigned int max_mtu;
- int ret;
- u8 val;
- int i;
-
- ks->max_mtu[port] = new_mtu;
-
- /* Roof out the MTU for the entire switch to the greatest
- * common denominator: the biggest set for any one port will
- * be the biggest MTU for the switch.
- */
- max_mtu = ETH_DATA_LEN;
- for (i = 0; i < KS8995_NUM_PORTS; i++) {
- if (ks->max_mtu[i] > max_mtu)
- max_mtu = ks->max_mtu[i];
- }
-
- /* Translate to layer 2 size.
- * Add ethernet and (possible) VLAN headers, and checksum to the size.
- * For ETH_DATA_LEN (1500 bytes) this will add up to 1522 bytes.
- */
- max_mtu += VLAN_ETH_HLEN;
- max_mtu += ETH_FCS_LEN;
-
- ret = ks8995_read_reg(ks, KS8995_REG_GC2, &val);
- if (ret) {
- dev_err(ks->dev, "failed to read KS8995_REG_GC2\n");
- return ret;
- }
-
- if (max_mtu <= 1522) {
- val &= ~KS8995_GC2_HUGE;
- val &= ~KS8995_GC2_LEGAL;
- } else if (max_mtu > 1522 && max_mtu <= 1536) {
- /* This accepts packets up to 1536 bytes */
- val &= ~KS8995_GC2_HUGE;
- val |= KS8995_GC2_LEGAL;
- } else {
- /* This accepts packets up to 1916 bytes */
- val |= KS8995_GC2_HUGE;
- val |= KS8995_GC2_LEGAL;
- }
-
- dev_dbg(ks->dev, "new max MTU %d bytes (inclusive)\n", max_mtu);
-
- ret = ks8995_write_reg(ks, KS8995_REG_GC2, val);
- if (ret)
- dev_err(ks->dev, "failed to set KS8995_REG_GC2\n");
-
- return ret;
-}
-
-static int ks8995_get_max_mtu(struct dsa_switch *ds, int port)
-{
- return 1916 - ETH_HLEN - ETH_FCS_LEN;
-}
-
-static const struct dsa_switch_ops ks8995_ds_ops = {
- .get_tag_protocol = ks8995_get_tag_protocol,
- .setup = ks8995_setup,
- .port_pre_bridge_flags = ks8995_port_pre_bridge_flags,
- .port_bridge_flags = ks8995_port_bridge_flags,
- .port_enable = ks8995_port_enable,
- .port_disable = ks8995_port_disable,
- .port_stp_state_set = ks8995_port_stp_state_set,
- .port_change_mtu = ks8995_change_mtu,
- .port_max_mtu = ks8995_get_max_mtu,
- .phylink_get_caps = ks8995_phylink_get_caps,
-};
-
-/* ------------------------------------------------------------------------ */
-static int ks8995_probe(struct spi_device *spi)
-{
- struct ks8995_switch *ks;
- int err;
- int variant = spi_get_device_id(spi)->driver_data;
-
- if (variant >= max_variant) {
- dev_err(&spi->dev, "bad chip variant %d\n", variant);
- return -ENODEV;
- }
-
- ks = devm_kzalloc(&spi->dev, sizeof(*ks), GFP_KERNEL);
- if (!ks)
- return -ENOMEM;
-
- mutex_init(&ks->lock);
- ks->spi = spi;
- ks->dev = &spi->dev;
- ks->chip = &ks8995_chip[variant];
-
- ks->reset_gpio = devm_gpiod_get_optional(&spi->dev, "reset",
- GPIOD_OUT_HIGH);
- err = PTR_ERR_OR_ZERO(ks->reset_gpio);
- if (err) {
- dev_err(&spi->dev,
- "failed to get reset gpio: %d\n", err);
- return err;
- }
-
- err = gpiod_set_consumer_name(ks->reset_gpio, "switch-reset");
- if (err)
- return err;
-
- if (ks->reset_gpio) {
- /*
- * If a reset line was obtained, wait for 100us after
- * de-asserting RESET before accessing any registers, see
- * the KS8995MA datasheet, page 44.
- */
- gpiod_set_value_cansleep(ks->reset_gpio, 0);
- udelay(100);
- }
-
- spi_set_drvdata(spi, ks);
-
- spi->mode = SPI_MODE_0;
- spi->bits_per_word = 8;
- err = spi_setup(spi);
- if (err) {
- dev_err(&spi->dev, "spi_setup failed, err=%d\n", err);
- return err;
- }
-
- err = ks8995_get_revision(ks);
- if (err)
- return err;
-
- err = ks8995_reset(ks);
- if (err)
- return err;
-
- dev_info(&spi->dev, "%s device found, Chip ID:%x, Revision:%x\n",
- ks->chip->name, ks->chip->chip_id, ks->revision_id);
-
- err = ks8995_check_config(ks);
- if (err)
- return err;
-
- ks->ds = devm_kzalloc(&spi->dev, sizeof(*ks->ds), GFP_KERNEL);
- if (!ks->ds)
- return -ENOMEM;
-
- ks->ds->dev = &spi->dev;
- ks->ds->num_ports = KS8995_NUM_PORTS;
- ks->ds->ops = &ks8995_ds_ops;
- ks->ds->phylink_mac_ops = &ks8995_phylink_mac_ops;
- ks->ds->priv = ks;
-
- err = dsa_register_switch(ks->ds);
- if (err)
- return dev_err_probe(&spi->dev, err,
- "unable to register DSA switch\n");
-
- return 0;
-}
-
-static void ks8995_remove(struct spi_device *spi)
-{
- struct ks8995_switch *ks = spi_get_drvdata(spi);
-
- dsa_unregister_switch(ks->ds);
- /* assert reset */
- gpiod_set_value_cansleep(ks->reset_gpio, 1);
-}
-
-/* ------------------------------------------------------------------------ */
-static struct spi_driver ks8995_driver = {
- .driver = {
- .name = "spi-ks8995",
- .of_match_table = ks8995_spi_of_match,
- },
- .probe = ks8995_probe,
- .remove = ks8995_remove,
- .id_table = ks8995_id,
-};
-
-module_spi_driver(ks8995_driver);
-
-MODULE_DESCRIPTION(DRV_DESC);
-MODULE_VERSION(DRV_VERSION);
-MODULE_AUTHOR("Gabor Juhos <juhosg at openwrt.org>");
-MODULE_LICENSE("GPL v2");
diff --git a/drivers/net/dsa/lan9303_i2c.c b/drivers/net/dsa/lan9303_i2c.c
index 3a09f3907f44..7b70e22371fc 100644
--- a/drivers/net/dsa/lan9303_i2c.c
+++ b/drivers/net/dsa/lan9303_i2c.c
@@ -96,7 +96,7 @@ MODULE_DEVICE_TABLE(i2c, lan9303_i2c_id);
static const struct of_device_id lan9303_i2c_of_match[] = {
{ .compatible = "smsc,lan9303-i2c", },
- { /* sentinel */ },
+ { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, lan9303_i2c_of_match);
diff --git a/drivers/net/dsa/lan9303_mdio.c b/drivers/net/dsa/lan9303_mdio.c
index 0ac4857e5ee8..d3b5062905b1 100644
--- a/drivers/net/dsa/lan9303_mdio.c
+++ b/drivers/net/dsa/lan9303_mdio.c
@@ -157,7 +157,7 @@ static void lan9303_mdio_shutdown(struct mdio_device *mdiodev)
static const struct of_device_id lan9303_mdio_of_match[] = {
{ .compatible = "smsc,lan9303-mdio" },
{ .compatible = "microchip,lan9354-mdio" },
- { /* sentinel */ },
+ { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, lan9303_mdio_of_match);
diff --git a/drivers/net/dsa/lantiq/lantiq_gswip.h b/drivers/net/dsa/lantiq/lantiq_gswip.h
index 0b75be14dc10..4f95877c59d1 100644
--- a/drivers/net/dsa/lantiq/lantiq_gswip.h
+++ b/drivers/net/dsa/lantiq/lantiq_gswip.h
@@ -261,6 +261,8 @@ struct gswip_hw_info {
const struct gswip_pce_microcode (*pce_microcode)[];
size_t pce_microcode_size;
enum dsa_tag_protocol tag_protocol;
+
+ int (*setup)(struct dsa_switch *ds);
void (*phylink_get_caps)(struct dsa_switch *ds, int port,
struct phylink_config *config);
struct phylink_pcs *(*mac_select_pcs)(struct phylink_config *config,
diff --git a/drivers/net/dsa/lantiq/lantiq_gswip_common.c b/drivers/net/dsa/lantiq/lantiq_gswip_common.c
index 0e8eedf64d3a..5bb5f4ab4cbf 100644
--- a/drivers/net/dsa/lantiq/lantiq_gswip_common.c
+++ b/drivers/net/dsa/lantiq/lantiq_gswip_common.c
@@ -689,6 +689,16 @@ static int gswip_setup(struct dsa_switch *ds)
*/
regmap_write(priv->mdio, GSWIP_MDIO_MDC_CFG0, 0x0);
+ /* GSW1xx will wake up the PHYs here, so it makes sense that it happens
+ * after the auto-polling deactivation above, but before the MDIO bus
+ * registration below
+ */
+ if (priv->hw_info->setup) {
+ err = priv->hw_info->setup(ds);
+ if (err)
+ return err;
+ }
+
/* Configure the MDIO Clock 2.5 MHz */
regmap_write_bits(priv->mdio, GSWIP_MDIO_MDC_CFG1, 0xff, 0x09);
@@ -1279,12 +1289,8 @@ static int gswip_port_change_mtu(struct dsa_switch *ds, int port, int new_mtu)
/* Enable MLEN for ports with non-standard MTUs, including the special
* header on the CPU port added above.
*/
- if (new_mtu != ETH_DATA_LEN)
- regmap_set_bits(priv->gswip, GSWIP_MAC_CTRL_2p(port),
- GSWIP_MAC_CTRL_2_MLEN);
- else
- regmap_clear_bits(priv->gswip, GSWIP_MAC_CTRL_2p(port),
- GSWIP_MAC_CTRL_2_MLEN);
+ regmap_assign_bits(priv->gswip, GSWIP_MAC_CTRL_2p(port),
+ GSWIP_MAC_CTRL_2_MLEN, new_mtu != ETH_DATA_LEN);
return 0;
}
@@ -1339,6 +1345,7 @@ static void gswip_port_set_speed(struct gswip_priv *priv, int port, int speed,
break;
case SPEED_1000:
+ case SPEED_2500:
mdio_phy = GSWIP_MDIO_PHY_SPEED_G1;
mii_cfg = GSWIP_MII_CFG_RATE_M125;
diff --git a/drivers/net/dsa/lantiq/mxl-gsw1xx.c b/drivers/net/dsa/lantiq/mxl-gsw1xx.c
index a1104b2f92a9..a26f78e1a44c 100644
--- a/drivers/net/dsa/lantiq/mxl-gsw1xx.c
+++ b/drivers/net/dsa/lantiq/mxl-gsw1xx.c
@@ -352,8 +352,22 @@ static int gsw1xx_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
/* mark PCS configuration as incomplete */
priv->tbi_interface = PHY_INTERFACE_MODE_NA;
- if (!reconf)
+ if (!reconf) {
+ /* setup SerDes clock speed */
+ if (interface == PHY_INTERFACE_MODE_2500BASEX)
+ nco_ctrl = GSW1XX_SGMII_2G5 | GSW1XX_SGMII_2G5_NCO2;
+ else
+ nco_ctrl = GSW1XX_SGMII_1G | GSW1XX_SGMII_1G_NCO1;
+
+ ret = regmap_update_bits(priv->clk, GSW1XX_CLK_NCO_CTRL,
+ GSW1XX_SGMII_HSP_MASK |
+ GSW1XX_SGMII_SEL,
+ nco_ctrl);
+ if (ret)
+ return ret;
+
ret = gsw1xx_pcs_reset(priv, interface);
+ }
if (ret)
return ret;
@@ -423,19 +437,6 @@ static int gsw1xx_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
return ret;
if (!reconf) {
- /* setup SerDes clock speed */
- if (interface == PHY_INTERFACE_MODE_2500BASEX)
- nco_ctrl = GSW1XX_SGMII_2G5 | GSW1XX_SGMII_2G5_NCO2;
- else
- nco_ctrl = GSW1XX_SGMII_1G | GSW1XX_SGMII_1G_NCO1;
-
- ret = regmap_update_bits(priv->clk, GSW1XX_CLK_NCO_CTRL,
- GSW1XX_SGMII_HSP_MASK |
- GSW1XX_SGMII_SEL,
- nco_ctrl);
- if (ret)
- return ret;
-
ret = gsw1xx_pcs_phy_xaui_write(priv, 0x30, 0x80);
if (ret)
return ret;
@@ -588,6 +589,37 @@ static void gsw150_phylink_get_caps(struct dsa_switch *ds, int port,
gsw1xx_phylink_get_lpi_caps(config);
}
+static int gsw1xx_setup(struct dsa_switch *ds)
+{
+ struct gsw1xx_priv *priv = container_of(ds->priv, struct gsw1xx_priv, gswip);
+ struct gswip_priv *gswip_priv = ds->priv;
+ u32 active_mask = 0;
+ u32 phy_mask = 0;
+ int port, ret;
+
+ for (port = 0; port < gswip_priv->hw_info->max_ports; port++) {
+ struct phylink_config cfg = {};
+
+ gswip_priv->hw_info->phylink_get_caps(ds, port, &cfg);
+ if (!test_bit(PHY_INTERFACE_MODE_INTERNAL, cfg.supported_interfaces))
+ break;
+
+ phy_mask |= GSW1XX_RST_REQ_PHY(port);
+
+ if (!dsa_is_unused_port(ds, port))
+ active_mask |= GSW1XX_RST_REQ_PHY(port);
+ }
+
+ ret = regmap_update_bits(priv->shell, GSW1XX_SHELL_RST_REQ, phy_mask, ~active_mask);
+ if (ret)
+ return ret;
+
+ if (active_mask)
+ msleep(300);
+
+ return 0;
+}
+
static struct phylink_pcs *gsw1xx_phylink_mac_select_pcs(struct phylink_config *config,
phy_interface_t interface)
{
@@ -829,6 +861,7 @@ static const struct gswip_hw_info gsw12x_data = {
[GSW1XX_MII_PORT] = GSWIP_MII_PCDU0,
[GSW1XX_MII_PORT + 1 ... GSWIP_MAX_PORTS - 1] = -1,
},
+ .setup = gsw1xx_setup,
.mac_select_pcs = gsw1xx_phylink_mac_select_pcs,
.phylink_get_caps = &gsw1xx_phylink_get_caps,
.supports_2500m = true,
@@ -851,6 +884,7 @@ static const struct gswip_hw_info gsw140_data = {
[GSW1XX_MII_PORT] = GSWIP_MII_PCDU0,
[GSW1XX_MII_PORT + 1 ... GSWIP_MAX_PORTS - 1] = -1,
},
+ .setup = gsw1xx_setup,
.mac_select_pcs = gsw1xx_phylink_mac_select_pcs,
.phylink_get_caps = &gsw1xx_phylink_get_caps,
.supports_2500m = true,
@@ -873,6 +907,7 @@ static const struct gswip_hw_info gsw141_data = {
[GSW1XX_MII_PORT] = GSWIP_MII_PCDU0,
[GSW1XX_MII_PORT + 1 ... GSWIP_MAX_PORTS - 1] = -1,
},
+ .setup = gsw1xx_setup,
.mac_select_pcs = gsw1xx_phylink_mac_select_pcs,
.phylink_get_caps = gsw1xx_phylink_get_caps,
.port_setup = gsw1xx_port_setup,
@@ -894,6 +929,7 @@ static const struct gswip_hw_info gsw150_data = {
[5] = 1,
[6] = 11,
},
+ .setup = gsw1xx_setup,
.phylink_get_caps = gsw150_phylink_get_caps,
/* There is only a single RGMII_SLEW_CFG register in GSW150 and it is
* unknown if RGMII slew configuration affects both RGMII ports
@@ -918,7 +954,7 @@ static const struct of_device_id gsw1xx_of_match[] = {
{ .compatible = "maxlinear,gsw140", .data = &gsw140_data },
{ .compatible = "maxlinear,gsw141", .data = &gsw141_data },
{ .compatible = "maxlinear,gsw145", .data = &gsw140_data },
- { /* sentinel */ },
+ { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, gsw1xx_of_match);
diff --git a/drivers/net/dsa/lantiq/mxl-gsw1xx.h b/drivers/net/dsa/lantiq/mxl-gsw1xx.h
index caa8f1008587..81bc00eabbfb 100644
--- a/drivers/net/dsa/lantiq/mxl-gsw1xx.h
+++ b/drivers/net/dsa/lantiq/mxl-gsw1xx.h
@@ -110,6 +110,8 @@
#define GSW1XX_SHELL_BASE 0xfa00
#define GSW1XX_SHELL_RST_REQ 0x01
#define GSW1XX_RST_REQ_SGMII_SHELL BIT(5)
+/* Valid only for internal-PHY ports (vary per switch type) */
+#define GSW1XX_RST_REQ_PHY(p) BIT(p)
#define GSW1XX_SHELL_MANU_ID 0x10
#define GSW1XX_SHELL_MANU_ID_PNUML GENMASK(15, 12)
#define GSW1XX_SHELL_MANU_ID_MANID GENMASK(11, 1)
diff --git a/drivers/net/dsa/microchip/Kconfig b/drivers/net/dsa/microchip/Kconfig
index c71d3fd5dfeb..75c9b2114afd 100644
--- a/drivers/net/dsa/microchip/Kconfig
+++ b/drivers/net/dsa/microchip/Kconfig
@@ -2,6 +2,7 @@
menuconfig NET_DSA_MICROCHIP_KSZ_COMMON
tristate "Microchip KSZ8XXX/KSZ9XXX/LAN937X series switch support"
depends on NET_DSA
+ select NET_DSA_TAG_KS8995
select NET_DSA_TAG_KSZ
select NET_DSA_TAG_NONE
select NET_IEEE8021Q_HELPERS
diff --git a/drivers/net/dsa/microchip/ksz8.c b/drivers/net/dsa/microchip/ksz8.c
index 941ae9f66f70..3c96dd4581cf 100644
--- a/drivers/net/dsa/microchip/ksz8.c
+++ b/drivers/net/dsa/microchip/ksz8.c
@@ -3,6 +3,7 @@
* Microchip KSZ8XXX series switch driver
*
* It supports the following switches:
+ * - KSZ8995XA (the oldest ancestor)
* - KSZ8463
* - KSZ8863, KSZ8873 aka KSZ88X3
* - KSZ8895, KSZ8864 aka KSZ8895 family
@@ -20,6 +21,7 @@
#include <linux/export.h>
#include <linux/gpio/consumer.h>
#include <linux/if_vlan.h>
+#include <linux/iopoll.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_data/microchip-ksz.h>
@@ -253,7 +255,35 @@ static int ksz8463_reset_switch(struct ksz_device *dev)
static int ksz8_reset_switch(struct ksz_device *dev)
{
- if (ksz_is_ksz88x3(dev)) {
+ if (ksz_is_ksz8995xa(dev)) {
+ unsigned int port, reg, val;
+ int ret;
+
+ /* The KSZ8995XA switch itself cannot be reset by software, but
+ * often boot loaders have fun with ports, so reset them. This
+ * reset bit is self-clearing.
+ */
+ for (port = 0; port < dev->info->port_cnt; port++) {
+ ret = ksz_prmw8(dev, port, REG_PORT_STATUS_3,
+ PORT_PHY_SOFT_RESET,
+ PORT_PHY_SOFT_RESET);
+ if (ret)
+ return ret;
+ }
+
+ /* IEEE 802.3 allows up to 500 ms for a PHY reset. */
+ for (port = 0; port < dev->info->port_cnt; port++) {
+ reg = dev->dev_ops->get_port_addr(port, REG_PORT_STATUS_3);
+ ret = regmap_read_poll_timeout(ksz_regmap_8(dev), reg, val,
+ !(val & PORT_PHY_SOFT_RESET),
+ 1000, 600000);
+ if (ret)
+ return ret;
+ }
+
+ /* Some PHYs need additional settling time after reset. */
+ usleep_range(1000, 2000);
+ } else if (ksz_is_ksz88x3(dev)) {
/* reset switch */
ksz_cfg(dev, KSZ8863_REG_SW_RESET,
KSZ8863_GLOBAL_SOFTWARE_RESET | KSZ8863_PCS_RESET, true);
@@ -269,6 +299,31 @@ static int ksz8_reset_switch(struct ksz_device *dev)
return 0;
}
+static int ksz8995xa_change_mtu(struct dsa_switch *ds, int port, int mtu)
+{
+ struct ksz_device *dev = ds->priv;
+ int frame_size;
+ u8 ctrl2 = 0;
+
+ if (!dsa_is_cpu_port(dev->ds, port))
+ return 0;
+
+ frame_size = mtu + VLAN_ETH_HLEN + ETH_FCS_LEN;
+
+ /* The legal packet size bit polarity documented in the KSZ8995XA
+ * manual appears to be wrong. Practical tests show that the same
+ * semantics as the other switches are correct; using the documented
+ * polarity makes the switch hang on big packets.
+ */
+ if (frame_size <= KSZ8995XA_LEGAL_PACKET_SIZE)
+ ctrl2 |= KSZ8863_LEGAL_PACKET_ENABLE;
+ else if (frame_size > KSZ8863_NORMAL_PACKET_SIZE)
+ ctrl2 |= KSZ8863_HUGE_PACKET_ENABLE;
+
+ return ksz_rmw8(dev, REG_SW_CTRL_2, KSZ8863_LEGAL_PACKET_ENABLE |
+ KSZ8863_HUGE_PACKET_ENABLE, ctrl2);
+}
+
static int ksz88xx_change_mtu(struct dsa_switch *ds, int port, int mtu)
{
struct ksz_device *dev = ds->priv;
@@ -325,8 +380,8 @@ static int ksz88xx_max_mtu(struct dsa_switch *ds, int port)
static int ksz8_port_queue_split(struct ksz_device *dev, int port, int queues)
{
- u8 mask_4q, mask_2q;
- u8 reg_4q, reg_2q;
+ u8 mask_4q = 0, mask_2q;
+ u8 reg_4q = 0, reg_2q;
u8 data_4q = 0;
u8 data_2q = 0;
int ret;
@@ -352,6 +407,10 @@ static int ksz8_port_queue_split(struct ksz_device *dev, int port, int queues)
mask_2q = KSZ8873_PORT_2QUEUE_SPLIT_EN;
reg_4q = P1CR1;
reg_2q = P1CR1 + 1;
+ } else if (ksz_is_ksz8995xa(dev)) {
+ /* This switch has no 4way split support */
+ mask_2q = KSZ8795_PORT_2QUEUE_SPLIT_EN;
+ reg_2q = REG_PORT_CTRL_0;
} else {
mask_4q = KSZ8795_PORT_4QUEUE_SPLIT_EN;
mask_2q = KSZ8795_PORT_2QUEUE_SPLIT_EN;
@@ -373,9 +432,11 @@ static int ksz8_port_queue_split(struct ksz_device *dev, int port, int queues)
else if (queues == 2)
data_2q = mask_2q;
- ret = ksz_prmw8(dev, port, reg_4q, mask_4q, data_4q);
- if (ret)
- return ret;
+ if (mask_4q) {
+ ret = ksz_prmw8(dev, port, reg_4q, mask_4q, data_4q);
+ if (ret)
+ return ret;
+ }
return ksz_prmw8(dev, port, reg_2q, mask_2q, data_2q);
}
@@ -970,6 +1031,10 @@ static int ksz8_r_phy_ctrl(struct ksz_device *dev, int port, u16 *val)
if (reg_val & PORT_MDIX_STATUS)
*val |= KSZ886X_CTRL_MDIX_STAT;
+ /* KSZ8995XA has no fancy features in register 0xA */
+ if (ksz_is_ksz8995xa(dev))
+ return 0;
+
ret = ksz_pread8(dev, port, REG_PORT_LINK_MD_CTRL, &reg_val);
if (ret < 0)
return ret;
@@ -1068,8 +1133,10 @@ static int ksz8_r_phy_bmcr(struct ksz_device *dev, u16 port, u16 *val)
if (ctrl & PORT_FORCE_FULL_DUPLEX)
*val |= BMCR_FULLDPLX;
- if (speed & PORT_HP_MDIX)
- *val |= KSZ886X_BMCR_HP_MDIX;
+ if (!ksz_is_ksz8995xa(dev)) {
+ if (speed & PORT_HP_MDIX)
+ *val |= KSZ886X_BMCR_HP_MDIX;
+ }
if (restart & PORT_FORCE_MDIX)
*val |= KSZ886X_BMCR_FORCE_MDI;
@@ -1164,6 +1231,9 @@ static int ksz8_r_phy(struct ksz_device *dev, u16 phy, u16 reg, u16 *val)
data |= LPA_LPACK;
break;
case PHY_REG_LINK_MD:
+ if (ksz_is_ksz8995xa(dev))
+ return -EOPNOTSUPP;
+
ret = ksz_pread8(dev, p, REG_PORT_LINK_MD_CTRL, &val1);
if (ret)
return ret;
@@ -1307,13 +1377,15 @@ static int ksz8_w_phy_bmcr(struct ksz_device *dev, u16 port, u16 val)
if (val & BMCR_RESET)
return 0;
- speed = 0;
- if (val & KSZ886X_BMCR_HP_MDIX)
- speed |= PORT_HP_MDIX;
+ if (!ksz_is_ksz8995xa(dev)) {
+ speed = 0;
+ if (val & KSZ886X_BMCR_HP_MDIX)
+ speed |= PORT_HP_MDIX;
- ret = ksz_prmw8(dev, port, regs[P_SPEED_STATUS], PORT_HP_MDIX, speed);
- if (ret)
- return ret;
+ ret = ksz_prmw8(dev, port, regs[P_SPEED_STATUS], PORT_HP_MDIX, speed);
+ if (ret)
+ return ret;
+ }
ctrl = 0;
if (ksz_is_ksz88x3(dev)) {
@@ -1423,11 +1495,17 @@ static int ksz8_w_phy(struct ksz_device *dev, u16 phy, u16 reg, u16 val)
}
break;
case PHY_REG_LINK_MD:
+ if (ksz_is_ksz8995xa(dev))
+ return -EOPNOTSUPP;
+
if (val & PHY_START_CABLE_DIAG)
ksz_port_cfg(dev, p, REG_PORT_LINK_MD_CTRL, PORT_START_CABLE_DIAG, true);
break;
case PHY_REG_PHY_CTRL:
+ if (ksz_is_ksz8995xa(dev))
+ return -EOPNOTSUPP;
+
ret = ksz8_w_phy_ctrl(dev, p, val);
if (ret)
return ret;
@@ -1527,8 +1605,18 @@ static void ksz8_flush_dyn_mac_table(struct dsa_switch *ds, int port)
if (ksz_is_ksz8463(dev)) {
reg = KSZ8463_FLUSH_TABLE_CTRL;
mask = KSZ8463_FLUSH_DYN_MAC_TABLE;
+ } else if (ksz_is_ksz8995xa(dev)) {
+ reg = REG_SW_CTRL_1;
+ mask = SW_FAST_AGING;
}
ksz_cfg(dev, reg, mask, true);
+ if (ksz_is_ksz8995xa(dev)) {
+ /* Allow the 800 us fast-age cycle to finish before restoring
+ * normal ageing and port learning.
+ */
+ usleep_range(1000, 2000);
+ ksz_cfg(dev, reg, mask, false);
+ }
for (index = first; index < cnt; index++) {
if (!(learn[index] & PORT_LEARN_DISABLE))
ksz_pwrite8(dev, index, regs[P_STP_CTRL], learn[index]);
@@ -2043,12 +2131,14 @@ static void ksz8_port_setup(struct ksz_device *dev, int port, bool cpu_port)
ksz8_port_queue_split(dev, port, dev->info->num_tx_queues);
- /* replace priority */
- offset = P_802_1P_CTRL;
- if (ksz_is_ksz8463(dev))
- offset = P1CR2;
- ksz_port_cfg(dev, port, offset,
- masks[PORT_802_1P_REMAPPING], false);
+ if (!ksz_is_ksz8995xa(dev)) {
+ /* replace priority */
+ offset = P_802_1P_CTRL;
+ if (ksz_is_ksz8463(dev))
+ offset = P1CR2;
+ ksz_port_cfg(dev, port, offset,
+ masks[PORT_802_1P_REMAPPING], false);
+ }
if (cpu_port)
member = dsa_user_ports(ds);
@@ -2057,6 +2147,20 @@ static void ksz8_port_setup(struct ksz_device *dev, int port, bool cpu_port)
ksz8_cfg_port_member(dev, port, member);
+ if (ksz_is_ksz8995xa(dev)) {
+ /* Use VID 0 to identify untagged frames in the special tag */
+ ksz_pwrite16(dev, port, REG_PORT_CTRL_VID, 0);
+
+ /* The KSZ8995XA has a special tag format in the front of the frame
+ * that need to be inserted by the CPU and then removed by each
+ * port. PORT_REMOVE_TAG simply means "remove tags coming from the
+ * CPU port" it does not affect ingress packets.
+ */
+ ksz_prmw8(dev, port, REG_PORT_CTRL_0,
+ PORT_INSERT_TAG | PORT_REMOVE_TAG,
+ cpu_port ? PORT_INSERT_TAG : PORT_REMOVE_TAG);
+ }
+
/* Disable all WoL options by default. Otherwise
* ksz_switch_macaddr_get/put logic will not work properly.
* CPU port 4 has no WoL functionality.
@@ -2073,6 +2177,7 @@ static int ksz8_dsa_port_setup(struct dsa_switch *ds, int port)
return 0;
ksz8_port_setup(dev, port, false);
+
return ksz_dcb_init_port(dev, port);
}
@@ -2147,7 +2252,9 @@ static void ksz8_config_cpu_port(struct dsa_switch *ds)
masks = dev->info->masks;
regs = dev->info->regs;
- ksz_cfg(dev, regs[S_TAIL_TAG_CTRL], masks[SW_TAIL_TAG_ENABLE], true);
+ /* KSZ8995XA uses a tag in the header instead of the tail */
+ if (!ksz_is_ksz8995xa(dev))
+ ksz_cfg(dev, regs[S_TAIL_TAG_CTRL], masks[SW_TAIL_TAG_ENABLE], true);
ksz8_port_setup(dev, dev->cpu_port, true);
@@ -2323,6 +2430,10 @@ static int ksz8_enable_stp_addr(struct ksz_device *dev)
{
struct alu_struct alu;
+ /* KSZ8995XA lacks STP */
+ if (ksz_is_ksz8995xa(dev))
+ return 0;
+
/* Setup STP address for STP operation. */
memset(&alu, 0, sizeof(alu));
ether_addr_copy(alu.mac, eth_stp_addr);
@@ -2478,6 +2589,7 @@ static int ksz8463_setup(struct dsa_switch *ds)
if (ret)
goto free_girq;
+ ksz8463_ptp_set_caps(ds);
ret = ksz_ptp_clock_register(ds);
if (ret) {
dev_err(dev->dev, "Failed to register PTP clock: %d\n",
@@ -2650,13 +2762,17 @@ static int ksz8_setup(struct dsa_switch *ds)
struct ksz_port *p;
const u16 *regs;
int i, ret;
+ u8 val;
regs = dev->info->regs;
- dev->vlan_cache = devm_kcalloc(dev->dev, sizeof(struct vlan_table),
- dev->info->num_vlans, GFP_KERNEL);
- if (!dev->vlan_cache)
- return -ENOMEM;
+ /* KSZ8995XA has no SW controlled VLAN handling */
+ if (!ksz_is_ksz8995xa(dev)) {
+ dev->vlan_cache = devm_kcalloc(dev->dev, sizeof(struct vlan_table),
+ dev->info->num_vlans, GFP_KERNEL);
+ if (!dev->vlan_cache)
+ return -ENOMEM;
+ }
ret = ksz8_reset_switch(dev);
if (ret) {
@@ -2685,19 +2801,22 @@ static int ksz8_setup(struct dsa_switch *ds)
ksz_init_mib_timer(dev);
- ds->configure_vlan_while_not_filtering = false;
ds->dscp_prio_mapping_is_global = true;
ds->mtu_enforcement_ingress = true;
- /* We rely on software untagging on the CPU port, so that we
- * can support both tagged and untagged VLANs
- */
- ds->untag_bridge_pvid = true;
+ if (!ksz_is_ksz8995xa(dev)) {
+ ds->configure_vlan_while_not_filtering = false;
- /* VLAN filtering is partly controlled by the global VLAN
- * Enable flag
- */
- ds->vlan_filtering_is_global = true;
+ /* We rely on software untagging on the CPU port, so that we
+ * can support both tagged and untagged VLANs
+ */
+ ds->untag_bridge_pvid = true;
+
+ /* VLAN filtering is partly controlled by the global VLAN
+ * Enable flag
+ */
+ ds->vlan_filtering_is_global = true;
+ }
/* Enable automatic fast aging when link changed detected. */
ksz_cfg(dev, S_LINK_AGING_CTRL, SW_LINK_AUTO_AGING, true);
@@ -2711,9 +2830,10 @@ static int ksz8_setup(struct dsa_switch *ds)
* Make sure unicast VLAN boundary is set as default and
* enable no excessive collision drop.
*/
- ret = ksz_rmw8(dev, REG_SW_CTRL_2,
- UNICAST_VLAN_BOUNDARY | NO_EXC_COLLISION_DROP,
- UNICAST_VLAN_BOUNDARY | NO_EXC_COLLISION_DROP);
+ val = NO_EXC_COLLISION_DROP;
+ if (!ksz_is_ksz8995xa(dev))
+ val |= UNICAST_VLAN_BOUNDARY;
+ ret = ksz_rmw8(dev, REG_SW_CTRL_2, val, val);
if (ret)
return ret;
@@ -2721,11 +2841,15 @@ static int ksz8_setup(struct dsa_switch *ds)
ksz_cfg(dev, S_MIRROR_CTRL, SW_MIRROR_RX_TX, false);
- if (!ksz_is_ksz88x3(dev))
+ if (ksz_is_ksz8995xa(dev))
+ ksz_cfg(dev, REG_SW_CTRL_9, SW_SPECIAL_TAG, true);
+ else if (!ksz_is_ksz88x3(dev))
ksz_cfg(dev, REG_SW_CTRL_19, SW_INS_TAG_ENABLE, true);
- for (i = 0; i < (dev->info->num_vlans / 4); i++)
- ksz8_r_vlan_entries(dev, i);
+ if (!ksz_is_ksz8995xa(dev)) {
+ for (i = 0; i < (dev->info->num_vlans / 4); i++)
+ ksz8_r_vlan_entries(dev, i);
+ }
/* Make sure PME (WoL) is not enabled. If requested, it will
* be enabled by ksz_wol_pre_shutdown(). Otherwise, some PMICs
@@ -2775,6 +2899,7 @@ static int ksz8_setup(struct dsa_switch *ds)
}
if (dev->info->ptp_capable) {
+ ksz_ptp_set_caps(ds);
ret = ksz_ptp_clock_register(ds);
if (ret) {
dev_err(dev->dev, "Failed to register PTP clock: %d\n",
@@ -2976,6 +3101,13 @@ static int ksz8_switch_init(struct ksz_device *dev)
return 0;
}
+static enum dsa_tag_protocol ksz8995xa_get_tag_protocol(struct dsa_switch *ds,
+ int port,
+ enum dsa_tag_protocol mp)
+{
+ return DSA_TAG_PROTO_KS8995;
+}
+
static enum dsa_tag_protocol ksz8463_get_tag_protocol(struct dsa_switch *ds,
int port,
enum dsa_tag_protocol mp)
@@ -3072,6 +3204,16 @@ const struct phylink_mac_ops ksz8_phylink_mac_ops = {
.mac_enable_tx_lpi = ksz_phylink_mac_enable_tx_lpi,
};
+/*
+ * The KS(Z)8995XA has no indirect access, meaning no MIB counters,
+ * no FDB access, and no VLAN handling.
+ */
+const struct ksz_dev_ops ksz8995xa_dev_ops = {
+ .get_port_addr = ksz8_get_port_addr,
+ .cfg_port_member = ksz8_cfg_port_member,
+ .init = ksz8_switch_init,
+};
+
const struct ksz_dev_ops ksz8463_dev_ops = {
.get_port_addr = ksz8463_get_port_addr,
.cfg_port_member = ksz8_cfg_port_member,
@@ -3111,6 +3253,38 @@ const struct ksz_dev_ops ksz88xx_dev_ops = {
.pme_pwrite8 = ksz8_pme_pwrite8,
};
+/*
+ * Restricted operations for KSZ8995XA, so many things are not supported
+ * by this old switch that we need diet DSA operations.
+ */
+const struct dsa_switch_ops ksz8995xa_switch_ops = {
+ .get_tag_protocol = ksz8995xa_get_tag_protocol,
+ .setup = ksz8_setup,
+ .teardown = ksz_teardown,
+ .phy_read = ksz8_phy_read16,
+ .phy_write = ksz8_phy_write16,
+ .phylink_get_caps = ksz8_phylink_get_caps,
+ .port_setup = ksz8_dsa_port_setup,
+ .port_bridge_join = ksz_port_bridge_join,
+ .port_bridge_leave = ksz_port_bridge_leave,
+ .port_set_mac_address = ksz_port_set_mac_address,
+ .port_stp_state_set = ksz_port_stp_state_set,
+ .port_pre_bridge_flags = ksz_port_pre_bridge_flags,
+ .port_bridge_flags = ksz_port_bridge_flags,
+ .port_fast_age = ksz8_flush_dyn_mac_table,
+ .port_mirror_add = ksz8_port_mirror_add,
+ .port_mirror_del = ksz8_port_mirror_del,
+ .port_change_mtu = ksz8995xa_change_mtu,
+ .port_max_mtu = ksz88xx_max_mtu,
+ .port_get_default_prio = ksz_port_get_default_prio,
+ .port_set_default_prio = ksz_port_set_default_prio,
+ .port_get_dscp_prio = ksz_port_get_dscp_prio,
+ .port_add_dscp_prio = ksz_port_add_dscp_prio,
+ .port_del_dscp_prio = ksz_port_del_dscp_prio,
+ .port_get_apptrust = ksz_port_get_apptrust,
+ .port_set_apptrust = ksz_port_set_apptrust,
+};
+
const struct dsa_switch_ops ksz8463_switch_ops = {
.get_tag_protocol = ksz8463_get_tag_protocol,
.connect_tag_protocol = ksz8463_connect_tag_protocol,
diff --git a/drivers/net/dsa/microchip/ksz8.h b/drivers/net/dsa/microchip/ksz8.h
index bc371cc26c6f..9b37c56c9617 100644
--- a/drivers/net/dsa/microchip/ksz8.h
+++ b/drivers/net/dsa/microchip/ksz8.h
@@ -12,11 +12,13 @@
#include <net/dsa.h>
#include "ksz_common.h"
+extern const struct ksz_dev_ops ksz8995xa_dev_ops;
extern const struct ksz_dev_ops ksz8463_dev_ops;
extern const struct ksz_dev_ops ksz87xx_dev_ops;
extern const struct ksz_dev_ops ksz88xx_dev_ops;
extern const struct phylink_mac_ops ksz88x3_phylink_mac_ops;
extern const struct phylink_mac_ops ksz8_phylink_mac_ops;
+extern const struct dsa_switch_ops ksz8995xa_switch_ops;
extern const struct dsa_switch_ops ksz8463_switch_ops;
extern const struct dsa_switch_ops ksz87xx_switch_ops;
extern const struct dsa_switch_ops ksz88xx_switch_ops;
diff --git a/drivers/net/dsa/microchip/ksz8_reg.h b/drivers/net/dsa/microchip/ksz8_reg.h
index 6bc511da1f7d..f858bce84bfd 100644
--- a/drivers/net/dsa/microchip/ksz8_reg.h
+++ b/drivers/net/dsa/microchip/ksz8_reg.h
@@ -95,6 +95,8 @@
#define SW_LED_LINK_ACT_DUPLEX 2
#define SW_LED_LINK_DUPLEX 3
+#define SW_SPECIAL_TAG BIT(0) /* KSZ8995XA only */
+
#define REG_SW_CTRL_10 0x0C
#define SW_PASS_PAUSE BIT(0)
diff --git a/drivers/net/dsa/microchip/ksz9477.c b/drivers/net/dsa/microchip/ksz9477.c
index 3ee995545c57..72528a53b67d 100644
--- a/drivers/net/dsa/microchip/ksz9477.c
+++ b/drivers/net/dsa/microchip/ksz9477.c
@@ -1781,6 +1781,7 @@ static int ksz9477_setup(struct dsa_switch *ds)
}
if (dev->info->ptp_capable) {
+ ksz_ptp_set_caps(ds);
ret = ksz_ptp_clock_register(ds);
if (ret) {
dev_err(dev->dev, "Failed to register PTP clock: %d\n",
diff --git a/drivers/net/dsa/microchip/ksz_common.c b/drivers/net/dsa/microchip/ksz_common.c
index 1a9d6f83a023..767c8fe8e819 100644
--- a/drivers/net/dsa/microchip/ksz_common.c
+++ b/drivers/net/dsa/microchip/ksz_common.c
@@ -446,6 +446,20 @@ static const u8 ksz8895_shifts[] = {
[DYNAMIC_MAC_SRC_PORT] = 24,
};
+static const u16 ksz8995xa_regs[] = {
+ [REG_SW_MAC_ADDR] = 0x68,
+ [P_FORCE_CTRL] = 0x0C,
+ [P_LINK_STATUS] = 0x0E,
+ [P_LOCAL_CTRL] = 0x0C,
+ [P_NEG_RESTART_CTRL] = 0x0D,
+ [P_REMOTE_STATUS] = 0x0E,
+ [P_SPEED_STATUS] = 0x09,
+ [P_STP_CTRL] = 0x02,
+ [S_START_CTRL] = 0x01,
+ [S_BROADCAST_CTRL] = 0x06,
+ [S_MULTICAST_CTRL] = 0x04,
+};
+
static const u16 ksz9477_regs[] = {
[REG_SW_MAC_ADDR] = 0x0302,
[P_STP_CTRL] = 0x0B04,
@@ -1161,6 +1175,8 @@ const struct ksz_chip_data ksz_switch_chips[] = {
.supports_mii = {false, false, true},
.supports_rmii = {false, false, true},
.internal_phy = {true, true, false},
+ .n_pins = 12,
+ .n_per_out = 12,
},
[KSZ8563] = {
@@ -1194,6 +1210,8 @@ const struct ksz_chip_data ksz_switch_chips[] = {
.ptp_capable = true,
.wr_table = &ksz8563_register_set,
.rd_table = &ksz8563_register_set,
+ .n_pins = 2,
+ .n_per_out = 3,
},
[KSZ8795] = {
@@ -1377,6 +1395,21 @@ const struct ksz_chip_data ksz_switch_chips[] = {
.internal_phy = {true, true, true, true, false},
},
+ [KSZ8995XA] = {
+ .chip_id = KSZ8995XA_CHIP_ID, /* Also known as KS8995XA */
+ .dev_name = "KSZ8995XA",
+ .cpu_ports = 0x10, /* can be configured as cpu port */
+ .port_cnt = 5, /* total cpu and user ports */
+ .num_tx_queues = 2, /* low/hi priority queues, no more */
+ .num_ipms = 2,
+ .ops = &ksz8995xa_dev_ops,
+ .switch_ops = &ksz8995xa_switch_ops,
+ .phylink_mac_ops = &ksz88x3_phylink_mac_ops,
+ .regs = ksz8995xa_regs,
+ .supports_mii = {true, true, true, true, true},
+ .internal_phy = {true, true, true, true, false},
+ },
+
[KSZ9477] = {
.chip_id = KSZ9477_CHIP_ID,
.dev_name = "KSZ9477",
@@ -1414,6 +1447,8 @@ const struct ksz_chip_data ksz_switch_chips[] = {
.sgmii_port = 7,
.wr_table = &ksz9477_register_set,
.rd_table = &ksz9477_register_set,
+ .n_pins = 2,
+ .n_per_out = 3,
},
[KSZ9896] = {
@@ -1486,6 +1521,41 @@ const struct ksz_chip_data ksz_switch_chips[] = {
.gbit_capable = {true, true, true, true, true, true, true},
},
+ [KSZ9897S] = {
+ .chip_id = KSZ9897S_CHIP_ID,
+ .dev_name = "KSZ9897S",
+ .num_vlans = 4096,
+ .num_alus = 4096,
+ .num_statics = 16,
+ .cpu_ports = 0x7F, /* can be configured as cpu port */
+ .port_cnt = 7, /* total physical port count */
+ .port_nirqs = 2,
+ .num_tx_queues = 4,
+ .num_ipms = 8,
+ .ops = &ksz9477_dev_ops,
+ .switch_ops = &ksz9477_switch_ops,
+ .phylink_mac_ops = &ksz9477_phylink_mac_ops,
+ .phy_errata_9477 = true,
+ .mib_names = ksz9477_mib_names,
+ .mib_cnt = ARRAY_SIZE(ksz9477_mib_names),
+ .reg_mib_cnt = MIB_COUNTER_NUM,
+ .regs = ksz9477_regs,
+ .masks = ksz9477_masks,
+ .shifts = ksz9477_shifts,
+ .xmii_ctrl0 = ksz9477_xmii_ctrl0,
+ .xmii_ctrl1 = ksz9477_xmii_ctrl1,
+ .supports_mii = {false, false, false, false,
+ false, true, false},
+ .supports_rmii = {false, false, false, false,
+ false, true, false},
+ .supports_rgmii = {false, false, false, false,
+ false, true, false},
+ .internal_phy = {true, true, true, true,
+ true, false, false},
+ .gbit_capable = {true, true, true, true, true, true, true},
+ .sgmii_port = 7,
+ },
+
[KSZ9893] = {
.chip_id = KSZ9893_CHIP_ID,
.dev_name = "KSZ9893",
@@ -1544,6 +1614,8 @@ const struct ksz_chip_data ksz_switch_chips[] = {
.internal_phy = {true, true, false},
.gbit_capable = {true, true, true},
.ptp_capable = true,
+ .n_pins = 2,
+ .n_per_out = 3,
},
[KSZ8567] = {
@@ -1581,6 +1653,8 @@ const struct ksz_chip_data ksz_switch_chips[] = {
.gbit_capable = {false, false, false, false, false,
true, true},
.ptp_capable = true,
+ .n_pins = 2,
+ .n_per_out = 3,
},
[KSZ9567] = {
@@ -1615,6 +1689,8 @@ const struct ksz_chip_data ksz_switch_chips[] = {
true, false, false},
.gbit_capable = {true, true, true, true, true, true, true},
.ptp_capable = true,
+ .n_pins = 2,
+ .n_per_out = 3,
},
[LAN9370] = {
@@ -1646,6 +1722,8 @@ const struct ksz_chip_data ksz_switch_chips[] = {
.supports_rgmii = {false, false, false, false, true},
.internal_phy = {true, true, true, true, false},
.ptp_capable = true,
+ .n_pins = 2,
+ .n_per_out = 3,
},
[LAN9371] = {
@@ -1677,6 +1755,8 @@ const struct ksz_chip_data ksz_switch_chips[] = {
.supports_rgmii = {false, false, false, false, true, true},
.internal_phy = {true, true, true, true, false, false},
.ptp_capable = true,
+ .n_pins = 2,
+ .n_per_out = 3,
},
[LAN9372] = {
@@ -1712,6 +1792,8 @@ const struct ksz_chip_data ksz_switch_chips[] = {
.internal_phy = {true, true, true, true,
false, false, true, true},
.ptp_capable = true,
+ .n_pins = 2,
+ .n_per_out = 3,
},
[LAN9373] = {
@@ -1747,6 +1829,8 @@ const struct ksz_chip_data ksz_switch_chips[] = {
.internal_phy = {true, true, true, false,
false, false, true, true},
.ptp_capable = true,
+ .n_pins = 2,
+ .n_per_out = 3,
},
[LAN9374] = {
@@ -1782,6 +1866,8 @@ const struct ksz_chip_data ksz_switch_chips[] = {
.internal_phy = {true, true, true, true,
false, false, true, true},
.ptp_capable = true,
+ .n_pins = 2,
+ .n_per_out = 3,
},
[LAN9646] = {
@@ -2649,6 +2735,10 @@ void ksz_init_mib_timer(struct ksz_device *dev)
{
int i;
+ /* KSZ8995XA lacks MiB features */
+ if (ksz_is_ksz8995xa(dev))
+ return;
+
INIT_DELAYED_WORK(&dev->mib_read, ksz_mib_read_work);
for (i = 0; i < dev->info->port_cnt; i++) {
@@ -2944,11 +3034,15 @@ static int ksz_switch_detect(struct ksz_device *dev)
return -ENODEV;
break;
case KSZ8895_FAMILY_ID:
- if (id2 == KSZ8895_CHIP_ID_95 ||
- id2 == KSZ8895_CHIP_ID_95R)
+ if (id2 == KSZ8895_CHIP_ID_95XA) {
+ dev->chip_id = KSZ8995XA_CHIP_ID;
+ break;
+ } else if (id2 == KSZ8895_CHIP_ID_95 ||
+ id2 == KSZ8895_CHIP_ID_95R) {
dev->chip_id = KSZ8895_CHIP_ID;
- else
+ } else {
return -ENODEV;
+ }
ret = ksz_read8(dev, REG_KSZ8864_CHIP_ID, &id4);
if (ret)
return ret;
@@ -3897,6 +3991,7 @@ int ksz_switch_register(struct ksz_device *dev)
struct device_node *ports;
phy_interface_t interface;
unsigned int port_num;
+ u32 lookup_chip_id;
int ret;
int i;
@@ -3933,7 +4028,30 @@ int ksz_switch_register(struct ksz_device *dev)
if (ret)
return ret;
- info = ksz_lookup_info(dev->chip_id);
+ lookup_chip_id = dev->chip_id;
+
+ /* The KSZ9897S and the KSZ9897R report the same chip ID and differ
+ * only in chip_data: only the S has the SGMII port 7, the R has a
+ * second RGMII port instead. Bit 7 of the port 7 XMII control 0
+ * register is read-only and tells them apart; it reads one on the S.
+ * Compare the KSZ9897S data sheet DS00002394C section 5.2.4.1 with
+ * the KSZ9897R data sheet DS00002330D section 5.2.3.1.
+ *
+ * Only the chip_data entry differs, so dev->chip_id keeps the shared
+ * chip ID and nothing else has to know about the variant.
+ */
+ if (dev->chip_id == KSZ9897_CHIP_ID) {
+ u8 val;
+
+ ret = ksz_read8(dev, KSZ9897_REG_PORT7_XMII_CTRL_0, &val);
+ if (ret)
+ return ret;
+
+ if (val & KSZ9897_PORT7_SGMII_SEL)
+ lookup_chip_id = KSZ9897S_CHIP_ID;
+ }
+
+ info = ksz_lookup_info(lookup_chip_id);
if (!info)
return -ENODEV;
@@ -4029,11 +4147,13 @@ int ksz_switch_register(struct ksz_device *dev)
if (ret)
return ret;
- /* Read MIB counters every 30 seconds to avoid overflow. */
- dev->mib_read_interval = msecs_to_jiffies(5000);
+ if (!ksz_is_ksz8995xa(dev)) {
+ /* Read MIB counters every 30 seconds to avoid overflow. */
+ dev->mib_read_interval = msecs_to_jiffies(5000);
- /* Start the MIB timer. */
- schedule_delayed_work(&dev->mib_read, 0);
+ /* Start the MIB timer. */
+ schedule_delayed_work(&dev->mib_read, 0);
+ }
return ret;
}
diff --git a/drivers/net/dsa/microchip/ksz_common.h b/drivers/net/dsa/microchip/ksz_common.h
index f6104f714a8d..8819670cbb05 100644
--- a/drivers/net/dsa/microchip/ksz_common.h
+++ b/drivers/net/dsa/microchip/ksz_common.h
@@ -139,6 +139,8 @@ struct ksz_chip_data {
u8 sgmii_port;
const struct regmap_access_table *wr_table;
const struct regmap_access_table *rd_table;
+ const u8 n_pins;
+ const u8 n_per_out;
};
struct ksz_irq {
@@ -281,9 +283,11 @@ enum ksz_model {
KSZ88X3,
KSZ8864,
KSZ8895,
+ KSZ8995XA,
KSZ9477,
KSZ9896,
KSZ9897,
+ KSZ9897S,
KSZ9893,
KSZ9563,
KSZ9567,
@@ -789,7 +793,13 @@ static inline bool ksz_is_ksz88x3(struct ksz_device *dev)
static inline bool ksz_is_8895_family(struct ksz_device *dev)
{
return dev->chip_id == KSZ8895_CHIP_ID ||
- dev->chip_id == KSZ8864_CHIP_ID;
+ dev->chip_id == KSZ8864_CHIP_ID ||
+ dev->chip_id == KSZ8995XA_CHIP_ID;
+}
+
+static inline bool ksz_is_ksz8995xa(struct ksz_device *dev)
+{
+ return dev->chip_id == KSZ8995XA_CHIP_ID;
}
static inline bool is_ksz8(struct ksz_device *dev)
@@ -860,6 +870,7 @@ static inline bool ksz_is_sgmii_port(struct ksz_device *dev, int port)
#define KSZ87_CHIP_ID_94 0x6
#define KSZ87_CHIP_ID_95 0x9
#define KSZ88_CHIP_ID_63 0x3
+#define KSZ8895_CHIP_ID_95XA 0x0
#define KSZ8895_CHIP_ID_95 0x4
#define KSZ8895_CHIP_ID_95R 0x6
@@ -874,6 +885,10 @@ static inline bool ksz_is_sgmii_port(struct ksz_device *dev, int port)
#define SKU_ID_KSZ8563 0x3c
#define SKU_ID_KSZ9563 0x1c
+/* KSZ9897 specific register: port 7 XMII control 0 */
+#define KSZ9897_REG_PORT7_XMII_CTRL_0 0x7300
+#define KSZ9897_PORT7_SGMII_SEL BIT(7)
+
/* Driver set switch broadcast storm protection at 10% rate. */
#define BROADCAST_STORM_PROT_RATE 10
@@ -923,6 +938,7 @@ static inline bool ksz_is_sgmii_port(struct ksz_device *dev, int port)
#define KSZ8863_HUGE_PACKET_SIZE 1916
#define KSZ8863_NORMAL_PACKET_SIZE 1536
#define KSZ8_LEGAL_PACKET_SIZE 1518
+#define KSZ8995XA_LEGAL_PACKET_SIZE 1522
#define KSZ9477_MAX_FRAME_SIZE 9000
#define KSZ8873_REG_GLOBAL_CTRL_12 0x0e
diff --git a/drivers/net/dsa/microchip/ksz_dcb.c b/drivers/net/dsa/microchip/ksz_dcb.c
index 7131c5caac54..aa0260f07252 100644
--- a/drivers/net/dsa/microchip/ksz_dcb.c
+++ b/drivers/net/dsa/microchip/ksz_dcb.c
@@ -20,10 +20,12 @@
#define KSZ8_PORT_DIFFSERV_ENABLE BIT(6)
#define KSZ8_PORT_802_1P_ENABLE BIT(5)
#define KSZ8_PORT_BASED_PRIO_M GENMASK(4, 3)
+#define KSZ8995XA_PORT_BASED_PRIO BIT(4)
#define KSZ8463_REG_TOS_DSCP_CTRL 0x16
#define KSZ88X3_REG_TOS_DSCP_CTRL 0x60
#define KSZ8765_REG_TOS_DSCP_CTRL 0x90
+#define KSZ8995XA_REG_TOS_DSCP_CTRL_7 0x67
#define KSZ9477_REG_SW_MAC_TOS_CTRL 0x033e
#define KSZ9477_SW_TOS_DSCP_REMAP BIT(0)
@@ -98,8 +100,11 @@ static void ksz_get_default_port_prio_reg(struct ksz_device *dev, int *reg,
{
if (is_ksz8(dev)) {
*reg = KSZ8_REG_PORT_1_CTRL_0;
- *mask = KSZ8_PORT_BASED_PRIO_M;
- *shift = __bf_shf(KSZ8_PORT_BASED_PRIO_M);
+ if (ksz_is_ksz8995xa(dev))
+ *mask = KSZ8995XA_PORT_BASED_PRIO;
+ else
+ *mask = KSZ8_PORT_BASED_PRIO_M;
+ *shift = __bf_shf(*mask);
if (ksz_is_ksz8463(dev))
*reg = KSZ8463_REG_PORT_1_CTRL_0;
} else {
@@ -112,31 +117,45 @@ static void ksz_get_default_port_prio_reg(struct ksz_device *dev, int *reg,
/**
* ksz_get_dscp_prio_reg - Retrieves the DSCP-to-priority-mapping register
* @dev: Pointer to the KSZ switch device structure
+ * @dscp: DSCP value for which to retrieve the register
* @reg: Pointer to the register address to be set
- * @per_reg: Pointer to the number of DSCP values per register
* @mask: Pointer to the mask to be set
+ * @shift: Pointer to the bit shift to be set
*
- * This function retrieves the DSCP to priority mapping register, the number of
- * DSCP values per register, and the mask to be set.
+ * This function retrieves the register, mask and shift for a DSCP to priority
+ * mapping entry.
*/
-static void ksz_get_dscp_prio_reg(struct ksz_device *dev, int *reg,
- int *per_reg, u8 *mask)
+static void ksz_get_dscp_prio_reg(struct ksz_device *dev, u8 dscp, int *reg,
+ u8 *mask, int *shift)
{
+ int per_reg;
+
+ if (ksz_is_ksz8995xa(dev)) {
+ /* KSZ8995XA stores DSCP groups in descending register order. */
+ *reg = KSZ8995XA_REG_TOS_DSCP_CTRL_7 - dscp / 8;
+ *mask = BIT(0);
+ *shift = dscp % 8;
+ return;
+ }
+
if (ksz_is_ksz87xx(dev) || ksz_is_8895_family(dev)) {
*reg = KSZ8765_REG_TOS_DSCP_CTRL;
- *per_reg = 4;
+ per_reg = 4;
*mask = GENMASK(1, 0);
} else if (ksz_is_ksz88x3(dev) || ksz_is_ksz8463(dev)) {
*reg = KSZ88X3_REG_TOS_DSCP_CTRL;
- *per_reg = 4;
+ per_reg = 4;
*mask = GENMASK(1, 0);
if (ksz_is_ksz8463(dev))
*reg = KSZ8463_REG_TOS_DSCP_CTRL;
} else {
*reg = KSZ9477_REG_DIFFSERV_PRIO_MAP;
- *per_reg = 2;
+ per_reg = 2;
*mask = GENMASK(2, 0);
}
+
+ *reg += dscp / per_reg;
+ *shift = (dscp % per_reg) * (8 / per_reg);
}
/**
@@ -236,10 +255,10 @@ int ksz_port_set_default_prio(struct dsa_switch *ds, int port, u8 prio)
int ksz_port_get_dscp_prio(struct dsa_switch *ds, int port, u8 dscp)
{
struct ksz_device *dev = ds->priv;
- int reg, per_reg, ret, shift;
+ int reg, ret, shift;
u8 data, mask;
- ksz_get_dscp_prio_reg(dev, &reg, &per_reg, &mask);
+ ksz_get_dscp_prio_reg(dev, dscp, &reg, &mask, &shift);
/* If DSCP remapping is disabled, DSCP bits 3-5 are used as Internal
* Priority Map (IPM)
@@ -260,13 +279,10 @@ int ksz_port_get_dscp_prio(struct dsa_switch *ds, int port, u8 dscp)
/* In case DSCP remapping is enabled, we need to write the DSCP to
* priority mapping table.
*/
- reg += dscp / per_reg;
ret = ksz_read8(dev, reg, &data);
if (ret)
return ret;
- shift = (dscp % per_reg) * (8 / per_reg);
-
return (data >> shift) & mask;
}
@@ -283,15 +299,12 @@ int ksz_port_get_dscp_prio(struct dsa_switch *ds, int port, u8 dscp)
*/
static int ksz_set_global_dscp_entry(struct ksz_device *dev, u8 dscp, u8 ipm)
{
- int reg, per_reg, shift;
+ int reg, shift;
u8 mask;
- ksz_get_dscp_prio_reg(dev, &reg, &per_reg, &mask);
+ ksz_get_dscp_prio_reg(dev, dscp, &reg, &mask, &shift);
- shift = (dscp % per_reg) * (8 / per_reg);
-
- return ksz_rmw8(dev, reg + (dscp / per_reg), mask << shift,
- ipm << shift);
+ return ksz_rmw8(dev, reg, mask << shift, ipm << shift);
}
/**
@@ -441,6 +454,9 @@ static void ksz_apptrust_error(struct ksz_device *dev)
* 2. IEEE_8021QAZ_APP_SEL_DSCP - Differentiated Services Code Point selector
* (lowest priority)
*
+ * KSZ8995XA instead ORs the classification results, so it supports only
+ * one trusted selector at a time.
+ *
* Return: 0 on success, or a negative error code on failure
*/
static int ksz_port_set_apptrust_validate(struct ksz_device *dev, int port,
@@ -449,6 +465,12 @@ static int ksz_port_set_apptrust_validate(struct ksz_device *dev, int port,
int i, j, found;
int j_prev = 0;
+ if (ksz_is_ksz8995xa(dev) && nsel > 1) {
+ dev_err(dev->dev,
+ "KSZ8995XA supports only one apptrust selector at a time\n");
+ return -EOPNOTSUPP;
+ }
+
/* Iterate through the requested selectors */
for (i = 0; i < nsel; i++) {
found = 0;
@@ -552,6 +574,10 @@ int ksz_port_get_apptrust(struct dsa_switch *ds, int port, u8 *sel, int *nsel)
if (ret)
return ret;
+ /* XA's combined classification cannot be reported as a trust order. */
+ if (ksz_is_ksz8995xa(dev) && (data & mask) == mask)
+ return -EOPNOTSUPP;
+
*nsel = 0;
for (i = 0; i < ARRAY_SIZE(ksz_supported_apptrust); i++) {
if (data & map[i].bit)
diff --git a/drivers/net/dsa/microchip/ksz_ptp.c b/drivers/net/dsa/microchip/ksz_ptp.c
index 39cc70d65900..1c59428b159b 100644
--- a/drivers/net/dsa/microchip/ksz_ptp.c
+++ b/drivers/net/dsa/microchip/ksz_ptp.c
@@ -26,6 +26,7 @@
*/
#define KSZ_MAX_DRIFT_CORR 6249999
#define KSZ_MAX_PULSE_WIDTH 125000000LL
+#define KSZ8463_MAX_PULSE_WIDTH 500000LL
#define KSZ_PTP_INC_NS 40ULL /* HW clock is incremented every 40 ns (by 40) */
#define KSZ_PTP_SUBNS_BITS 32
@@ -63,6 +64,17 @@ static int ksz_ptp_tou_gpio(struct ksz_device *dev)
LED_SRC_PTP_GPIO_1 | LED_SRC_PTP_GPIO_2);
}
+static int ksz8463_ptp_tou_reset(struct ksz_device *dev, u8 unit)
+{
+ int ret;
+
+ ret = ksz_rmw16(dev, KSZ8463_TOU_SW_RST, BIT(unit), BIT(unit));
+ if (ret)
+ return ret;
+
+ return ksz_rmw16(dev, KSZ8463_TOU_SW_RST, BIT(unit), 0);
+}
+
static int ksz_ptp_tou_reset(struct ksz_device *dev, u8 unit)
{
u32 data;
@@ -86,7 +98,7 @@ static int ksz_ptp_tou_reset(struct ksz_device *dev, u8 unit)
0);
}
-static int ksz_ptp_tou_pulse_verify(u64 pulse_ns)
+static int ksz_ptp_tou_pulse_verify(u64 pulse_ns, u32 mask)
{
u32 data;
@@ -94,7 +106,7 @@ static int ksz_ptp_tou_pulse_verify(u64 pulse_ns)
return -EINVAL;
data = (pulse_ns / 8);
- if (!FIELD_FIT(TRIG_PULSE_WIDTH_M, data))
+ if ((mask & data) != data)
return -ERANGE;
return 0;
@@ -120,6 +132,28 @@ static int ksz_ptp_tou_target_time_set(struct ksz_device *dev,
return 0;
}
+static int ksz8463_ptp_tou_start(struct ksz_device *dev, u8 unit)
+{
+ u16 data;
+ int ret;
+
+ ret = ksz_rmw16(dev, KSZ8463_TOU_EN, BIT(unit), BIT(unit));
+ if (ret)
+ return ret;
+
+ ret = ksz_read16(dev, KSZ8463_TOU_ACTIVE, &data);
+ if (ret)
+ return ret;
+
+ if (!(data & BIT(unit))) {
+ dev_err(dev->dev, "%s: Trigger unit%d error!\n", __func__,
+ unit);
+ return -EIO;
+ }
+
+ return 0;
+}
+
static int ksz_ptp_tou_start(struct ksz_device *dev, u8 unit)
{
u32 data;
@@ -147,6 +181,56 @@ static int ksz_ptp_tou_start(struct ksz_device *dev, u8 unit)
return 0;
}
+static int ksz8463_ptp_configure_perout(struct ksz_device *dev,
+ struct ptp_perout_request const *request,
+ u32 cycle_width_ns, u32 pulse_width_ns,
+ u8 index)
+{
+ struct ptp_pin_desc *pin = &dev->ptp_data.pin_config[index];
+ u16 cfg_base = KSZ8463_TRIG1_CFG + KSZ8463_TRIGN_CFG_SIZE * pin->chan;
+ u16 data;
+ int ret;
+
+ /* Hardware has only 32 bit for the second field */
+ if ((request->start.sec & 0xffffffff) != request->start.sec)
+ return -EINVAL;
+
+ data = KSZ8463_NOTIFY_BIT |
+ FIELD_PREP(KSZ8463_PATTERN_M, TRIG_POS_PERIOD) |
+ pin->index;
+ ret = ksz_write16(dev, cfg_base + KSZ8463_PATTERN_OFF, data);
+ if (ret)
+ return ret;
+
+ ret = ksz_write32(dev, cfg_base + KSZ8463_CYCLE_WIDTH_OFF,
+ cycle_width_ns);
+ if (ret)
+ return ret;
+
+ /* Set cycle count 0 - Infinite */
+ ret = ksz_write16(dev, cfg_base + KSZ8463_CYCLE_CNT_OFF, 0);
+ if (ret)
+ return ret;
+
+ /* KSZ8463 uses a 8 ns unit value to compute the pulse width */
+ data = (pulse_width_ns / 8);
+ ret = ksz_write16(dev, cfg_base + KSZ8463_PULSE_WIDTH_OFF, data);
+ if (ret)
+ return ret;
+
+ ret = ksz_write32(dev, cfg_base + KSZ8463_TARGET_NSEC,
+ request->start.nsec);
+ if (ret)
+ return ret;
+
+ ret = ksz_write32(dev, cfg_base + KSZ8463_TARGET_SEC,
+ request->start.sec);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
static int ksz_ptp_configure_perout(struct ksz_device *dev,
u32 cycle_width_ns, u32 pulse_width_ns,
struct timespec64 const *target_time,
@@ -183,17 +267,11 @@ static int ksz_ptp_configure_perout(struct ksz_device *dev,
return 0;
}
-static int ksz_ptp_enable_perout(struct ksz_device *dev,
- struct ptp_perout_request const *request,
- int on)
+static int ksz_ptp_get_pin(struct ksz_device *dev,
+ struct ptp_perout_request const *request)
{
struct ksz_ptp_data *ptp_data = &dev->ptp_data;
- u64 req_pulse_width_ns;
- u64 cycle_width_ns;
- u64 pulse_width_ns;
- int pin = 0;
- u32 data32;
- int ret;
+ int pin;
if (request->flags & ~PTP_PEROUT_DUTY_CYCLE)
return -EOPNOTSUPP;
@@ -206,6 +284,119 @@ static int ksz_ptp_enable_perout(struct ksz_device *dev,
if (pin < 0)
return -EINVAL;
+ return pin;
+}
+
+static int ksz_ptp_compute_perout_cycle(struct ksz_device *dev,
+ struct ptp_perout_request const *request,
+ u64 max_pulse_width,
+ u64 *cycle_width_ns,
+ u64 *pulse_width_ns)
+{
+ struct timespec64 tmp;
+
+ if (request->period.sec < 0)
+ return -EINVAL;
+
+ if (!request->period.sec && !request->period.nsec)
+ return -EINVAL;
+
+ tmp.tv_sec = request->period.sec;
+ tmp.tv_nsec = request->period.nsec;
+ *cycle_width_ns = timespec64_to_ns(&tmp);
+ if ((*cycle_width_ns & TRIG_CYCLE_WIDTH_M) != *cycle_width_ns) {
+ *cycle_width_ns = 0;
+ *pulse_width_ns = 0;
+ return -EINVAL;
+ }
+
+ if (request->flags & PTP_PEROUT_DUTY_CYCLE) {
+ *pulse_width_ns = request->on.sec * NSEC_PER_SEC
+ + request->on.nsec;
+ return 0;
+ }
+
+ /* Use a duty cycle of 50%. Maximum pulse width supported by the
+ * hardware is a little bit more than 125 ms.
+ */
+ *pulse_width_ns = (request->period.sec * NSEC_PER_SEC +
+ request->period.nsec) / 2;
+ *pulse_width_ns = min_t(u64, *pulse_width_ns, max_pulse_width);
+
+ return 0;
+}
+
+static int ksz8463_ptp_enable_perout(struct ksz_device *dev,
+ struct ptp_perout_request const *request,
+ int on)
+{
+ struct ksz_ptp_data *ptp_data = &dev->ptp_data;
+ u64 cycle_width_ns;
+ u64 pulse_width_ns;
+ int pin;
+ int ret;
+
+ pin = ksz_ptp_get_pin(dev, request);
+ if (pin < 0)
+ return pin;
+
+ ret = ksz8463_ptp_tou_reset(dev, request->index);
+ if (ret)
+ return ret;
+
+ if (!on) {
+ ptp_data->tou_mode = KSZ_PTP_TOU_IDLE;
+ return 0;
+ }
+
+ ret = ksz_ptp_compute_perout_cycle(dev, request,
+ KSZ8463_MAX_PULSE_WIDTH,
+ &cycle_width_ns, &pulse_width_ns);
+ if (ret)
+ return ret;
+
+ ret = ksz_ptp_tou_pulse_verify(pulse_width_ns,
+ KSZ8463_TRIG_PULSE_WIDTH_M);
+ if (ret)
+ return ret;
+
+ ret = ksz8463_ptp_configure_perout(dev, request,
+ cycle_width_ns, pulse_width_ns,
+ pin);
+ if (ret)
+ return ret;
+
+ ret = ksz_ptp_tou_gpio(dev);
+ if (ret)
+ return ret;
+
+ ret = ksz8463_ptp_tou_start(dev, request->index);
+ if (ret)
+ return ret;
+
+ memcpy(&ptp_data->perout_request, request,
+ sizeof(struct ptp_perout_request));
+ ptp_data->tou_mode = KSZ_PTP_TOU_PEROUT;
+
+ return 0;
+}
+
+static int ksz_ptp_enable_perout(struct ksz_device *dev,
+ struct ptp_perout_request const *request,
+ int on)
+{
+ struct ksz_ptp_data *ptp_data = &dev->ptp_data;
+ struct timespec64 tmp;
+ u64 cycle_width_ns;
+ u64 pulse_width_ns;
+ int pin = 0;
+ u32 data32;
+ int ret;
+
+ pin = ksz_ptp_get_pin(dev, request);
+ if (pin < 0)
+ return pin;
+
data32 = FIELD_PREP(PTP_GPIO_INDEX, pin) |
FIELD_PREP(PTP_TOU_INDEX, request->index);
ret = ksz_rmw32(dev, REG_PTP_UNIT_INDEX__4,
@@ -222,36 +413,19 @@ static int ksz_ptp_enable_perout(struct ksz_device *dev,
return 0;
}
- ptp_data->perout_target_time_first.tv_sec = request->start.sec;
- ptp_data->perout_target_time_first.tv_nsec = request->start.nsec;
-
- ptp_data->perout_period.tv_sec = request->period.sec;
- ptp_data->perout_period.tv_nsec = request->period.nsec;
-
- cycle_width_ns = timespec64_to_ns(&ptp_data->perout_period);
- if ((cycle_width_ns & TRIG_CYCLE_WIDTH_M) != cycle_width_ns)
- return -EINVAL;
-
- if (request->flags & PTP_PEROUT_DUTY_CYCLE) {
- pulse_width_ns = request->on.sec * NSEC_PER_SEC +
- request->on.nsec;
- } else {
- /* Use a duty cycle of 50%. Maximum pulse width supported by the
- * hardware is a little bit more than 125 ms.
- */
- req_pulse_width_ns = (request->period.sec * NSEC_PER_SEC +
- request->period.nsec) / 2;
- pulse_width_ns = min_t(u64, req_pulse_width_ns,
- KSZ_MAX_PULSE_WIDTH);
- }
+ ret = ksz_ptp_compute_perout_cycle(dev, request, KSZ_MAX_PULSE_WIDTH,
+ &cycle_width_ns, &pulse_width_ns);
+ if (ret)
+ return ret;
- ret = ksz_ptp_tou_pulse_verify(pulse_width_ns);
+ ret = ksz_ptp_tou_pulse_verify(pulse_width_ns, TRIG_PULSE_WIDTH_M);
if (ret)
return ret;
+ tmp.tv_sec = request->start.sec;
+ tmp.tv_nsec = request->start.nsec;
ret = ksz_ptp_configure_perout(dev, cycle_width_ns, pulse_width_ns,
- &ptp_data->perout_target_time_first,
- pin);
+ &tmp, pin);
if (ret)
return ret;
@@ -263,6 +437,8 @@ static int ksz_ptp_enable_perout(struct ksz_device *dev,
if (ret)
return ret;
+ memcpy(&ptp_data->perout_request, request,
+ sizeof(struct ptp_perout_request));
ptp_data->tou_mode = KSZ_PTP_TOU_PEROUT;
return 0;
@@ -756,13 +932,14 @@ static int ksz_ptp_gettime(struct ptp_clock_info *ptp, struct timespec64 *ts)
return ret;
}
-static int ksz_ptp_restart_perout(struct ksz_device *dev)
+static int ksz_ptp_prepare_restart_perout(struct ksz_device *dev,
+ struct ptp_perout_request *request)
{
struct ksz_ptp_data *ptp_data = &dev->ptp_data;
s64 now_ns, first_ns, period_ns, next_ns;
- struct ptp_perout_request request;
struct timespec64 next;
struct timespec64 now;
+ struct timespec64 tmp;
unsigned int count;
int ret;
@@ -773,10 +950,14 @@ static int ksz_ptp_restart_perout(struct ksz_device *dev)
return ret;
now_ns = timespec64_to_ns(&now);
- first_ns = timespec64_to_ns(&ptp_data->perout_target_time_first);
+ tmp.tv_sec = ptp_data->perout_request.start.sec;
+ tmp.tv_nsec = ptp_data->perout_request.start.nsec;
+ first_ns = timespec64_to_ns(&tmp);
/* Calculate next perout event based on start time and period */
- period_ns = timespec64_to_ns(&ptp_data->perout_period);
+ tmp.tv_sec = ptp_data->perout_request.period.sec;
+ tmp.tv_nsec = ptp_data->perout_request.period.nsec;
+ period_ns = timespec64_to_ns(&tmp);
if (first_ns < now_ns) {
count = div_u64(now_ns - first_ns, period_ns);
@@ -791,41 +972,110 @@ static int ksz_ptp_restart_perout(struct ksz_device *dev)
/* Restart periodic output signal */
next = ns_to_timespec64(next_ns);
- request.start.sec = next.tv_sec;
- request.start.nsec = next.tv_nsec;
- request.period.sec = ptp_data->perout_period.tv_sec;
- request.period.nsec = ptp_data->perout_period.tv_nsec;
- request.index = 0;
- request.flags = 0;
+ memcpy(request, &ptp_data->perout_request,
+ sizeof(struct ptp_perout_request));
+ request->start.sec = next.tv_sec;
+ request->start.nsec = next.tv_nsec;
+
+ return 0;
+}
+
+static int ksz_ptp_restart_perout(struct ksz_device *dev)
+{
+ struct ptp_perout_request request;
+ int ret;
+
+ ret = ksz_ptp_prepare_restart_perout(dev, &request);
+ if (ret)
+ return ret;
return ksz_ptp_enable_perout(dev, &request, 1);
}
-static int ksz_ptp_settime(struct ptp_clock_info *ptp,
- const struct timespec64 *ts)
+static int ksz8463_ptp_restart_perout(struct ksz_device *dev)
{
- struct ksz_ptp_data *ptp_data = ptp_caps_to_data(ptp);
- struct ksz_device *dev = ptp_data_to_ksz_dev(ptp_data);
- const u16 *regs = dev->info->regs;
+ struct ptp_perout_request request;
int ret;
- mutex_lock(&ptp_data->lock);
+ ret = ksz_ptp_prepare_restart_perout(dev, &request);
+ if (ret)
+ return ret;
+
+ return ksz8463_ptp_enable_perout(dev, &request, 1);
+}
+
+static int __ksz_ptp_settime(struct ksz_device *dev,
+ const struct timespec64 *ts)
+{
+ const u16 *regs = dev->info->regs;
+ int ret;
/* Write to shadow registers and Load PTP clock */
ret = ksz_write16(dev, regs[PTP_RTC_SUB_NANOSEC], PTP_RTC_0NS);
if (ret)
- goto unlock;
+ return ret;
ret = ksz_write32(dev, regs[PTP_RTC_NANOSEC], ts->tv_nsec);
if (ret)
- goto unlock;
+ return ret;
ret = ksz_write32(dev, regs[PTP_RTC_SEC], ts->tv_sec);
if (ret)
- goto unlock;
+ return ret;
ret = ksz_rmw16(dev, regs[PTP_CLK_CTRL], PTP_LOAD_TIME, PTP_LOAD_TIME);
if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int ksz8463_ptp_settime(struct ptp_clock_info *ptp,
+ const struct timespec64 *ts)
+{
+ struct ksz_ptp_data *ptp_data = ptp_caps_to_data(ptp);
+ struct ksz_device *dev = ptp_data_to_ksz_dev(ptp_data);
+ int ret;
+
+ mutex_lock(&ptp_data->lock);
+
+ ret = __ksz_ptp_settime(dev, ts);
+ if (ret)
+ goto unlock;
+
+ switch (ptp_data->tou_mode) {
+ case KSZ_PTP_TOU_IDLE:
+ break;
+
+ case KSZ_PTP_TOU_PEROUT:
+ ret = ksz8463_ptp_restart_perout(dev);
+ if (ret)
+ goto unlock;
+
+ break;
+ }
+
+ spin_lock_bh(&ptp_data->clock_lock);
+ ptp_data->clock_time = *ts;
+ spin_unlock_bh(&ptp_data->clock_lock);
+
+unlock:
+ mutex_unlock(&ptp_data->lock);
+
+ return ret;
+}
+
+static int ksz_ptp_settime(struct ptp_clock_info *ptp,
+ const struct timespec64 *ts)
+{
+ struct ksz_ptp_data *ptp_data = ptp_caps_to_data(ptp);
+ struct ksz_device *dev = ptp_data_to_ksz_dev(ptp_data);
+ int ret;
+
+ mutex_lock(&ptp_data->lock);
+
+ ret = __ksz_ptp_settime(dev, ts);
+ if (ret)
goto unlock;
switch (ptp_data->tou_mode) {
@@ -890,18 +1140,13 @@ unlock:
return ret;
}
-static int ksz_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
+static int __ksz_ptp_adjtime(struct ksz_device *dev, s64 delta)
{
- struct ksz_ptp_data *ptp_data = ptp_caps_to_data(ptp);
- struct ksz_device *dev = ptp_data_to_ksz_dev(ptp_data);
- struct timespec64 delta64 = ns_to_timespec64(delta);
const u16 *regs = dev->info->regs;
s32 sec, nsec;
u16 data16;
int ret;
- mutex_lock(&ptp_data->lock);
-
/* do not use ns_to_timespec64(),
* both sec and nsec are subtracted by hw
*/
@@ -909,15 +1154,15 @@ static int ksz_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
ret = ksz_write32(dev, regs[PTP_RTC_NANOSEC], abs(nsec));
if (ret)
- goto unlock;
+ return ret;
ret = ksz_write32(dev, regs[PTP_RTC_SEC], abs(sec));
if (ret)
- goto unlock;
+ return ret;
ret = ksz_read16(dev, regs[PTP_CLK_CTRL], &data16);
if (ret)
- goto unlock;
+ return ret;
data16 |= PTP_STEP_ADJ;
@@ -929,6 +1174,56 @@ static int ksz_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
ret = ksz_write16(dev, regs[PTP_CLK_CTRL], data16);
if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int ksz8463_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
+{
+ struct ksz_ptp_data *ptp_data = ptp_caps_to_data(ptp);
+ struct ksz_device *dev = ptp_data_to_ksz_dev(ptp_data);
+ struct timespec64 delta64 = ns_to_timespec64(delta);
+ int ret;
+
+ mutex_lock(&ptp_data->lock);
+
+ ret = __ksz_ptp_adjtime(dev, delta);
+ if (ret)
+ goto unlock;
+
+ switch (ptp_data->tou_mode) {
+ case KSZ_PTP_TOU_IDLE:
+ break;
+
+ case KSZ_PTP_TOU_PEROUT:
+ ret = ksz8463_ptp_restart_perout(dev);
+ if (ret)
+ goto unlock;
+
+ break;
+ }
+
+ spin_lock_bh(&ptp_data->clock_lock);
+ ptp_data->clock_time = timespec64_add(ptp_data->clock_time, delta64);
+ spin_unlock_bh(&ptp_data->clock_lock);
+
+unlock:
+ mutex_unlock(&ptp_data->lock);
+ return ret;
+}
+
+static int ksz_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
+{
+ struct ksz_ptp_data *ptp_data = ptp_caps_to_data(ptp);
+ struct ksz_device *dev = ptp_data_to_ksz_dev(ptp_data);
+ struct timespec64 delta64 = ns_to_timespec64(delta);
+ int ret;
+
+ mutex_lock(&ptp_data->lock);
+
+ ret = __ksz_ptp_adjtime(dev, delta);
+ if (ret)
goto unlock;
switch (ptp_data->tou_mode) {
@@ -952,6 +1247,26 @@ unlock:
return ret;
}
+static int ksz8463_ptp_enable(struct ptp_clock_info *ptp,
+ struct ptp_clock_request *req, int on)
+{
+ struct ksz_ptp_data *ptp_data = ptp_caps_to_data(ptp);
+ struct ksz_device *dev = ptp_data_to_ksz_dev(ptp_data);
+ int ret;
+
+ switch (req->type) {
+ case PTP_CLK_REQ_PEROUT:
+ mutex_lock(&ptp_data->lock);
+ ret = ksz8463_ptp_enable_perout(dev, &req->perout, on);
+ mutex_unlock(&ptp_data->lock);
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return ret;
+}
+
static int ksz_ptp_enable(struct ptp_clock_info *ptp,
struct ptp_clock_request *req, int on)
{
@@ -1028,17 +1343,33 @@ static int ksz_ptp_start_clock(struct ksz_device *dev)
return 0;
}
-int ksz_ptp_clock_register(struct dsa_switch *ds)
+void ksz8463_ptp_set_caps(struct dsa_switch *ds)
+{
+ struct ksz_device *dev = ds->priv;
+ struct ksz_ptp_data *ptp_data;
+
+ ptp_data = &dev->ptp_data;
+
+ ptp_data->caps.owner = THIS_MODULE;
+ snprintf(ptp_data->caps.name, 16, "Microchip Clock");
+ ptp_data->caps.max_adj = KSZ_MAX_DRIFT_CORR;
+ ptp_data->caps.gettime64 = ksz_ptp_gettime;
+ ptp_data->caps.settime64 = ksz8463_ptp_settime;
+ ptp_data->caps.adjfine = ksz_ptp_adjfine;
+ ptp_data->caps.adjtime = ksz8463_ptp_adjtime;
+ ptp_data->caps.do_aux_work = ksz_ptp_do_aux_work;
+ ptp_data->caps.enable = ksz8463_ptp_enable;
+ ptp_data->caps.verify = ksz_ptp_verify_pin;
+ ptp_data->caps.n_pins = dev->info->n_pins;
+ ptp_data->caps.n_per_out = dev->info->n_per_out;
+}
+
+void ksz_ptp_set_caps(struct dsa_switch *ds)
{
struct ksz_device *dev = ds->priv;
- const u16 *regs = dev->info->regs;
struct ksz_ptp_data *ptp_data;
- int ret;
- u8 i;
ptp_data = &dev->ptp_data;
- mutex_init(&ptp_data->lock);
- spin_lock_init(&ptp_data->clock_lock);
ptp_data->caps.owner = THIS_MODULE;
snprintf(ptp_data->caps.name, 16, "Microchip Clock");
@@ -1050,14 +1381,27 @@ int ksz_ptp_clock_register(struct dsa_switch *ds)
ptp_data->caps.do_aux_work = ksz_ptp_do_aux_work;
ptp_data->caps.enable = ksz_ptp_enable;
ptp_data->caps.verify = ksz_ptp_verify_pin;
- ptp_data->caps.n_pins = KSZ_PTP_N_GPIO;
- ptp_data->caps.n_per_out = 3;
+ ptp_data->caps.n_pins = dev->info->n_pins;
+ ptp_data->caps.n_per_out = dev->info->n_per_out;
+}
+
+int ksz_ptp_clock_register(struct dsa_switch *ds)
+{
+ struct ksz_device *dev = ds->priv;
+ const u16 *regs = dev->info->regs;
+ struct ksz_ptp_data *ptp_data;
+ int ret;
+ u8 i;
+
+ ptp_data = &dev->ptp_data;
+ mutex_init(&ptp_data->lock);
+ spin_lock_init(&ptp_data->clock_lock);
ret = ksz_ptp_start_clock(dev);
if (ret)
return ret;
- for (i = 0; i < KSZ_PTP_N_GPIO; i++) {
+ for (i = 0; i < dev->info->n_pins; i++) {
struct ptp_pin_desc *ptp_pin = &ptp_data->pin_config[i];
snprintf(ptp_pin->name,
diff --git a/drivers/net/dsa/microchip/ksz_ptp.h b/drivers/net/dsa/microchip/ksz_ptp.h
index 7067ec9bd1e6..22898a22723c 100644
--- a/drivers/net/dsa/microchip/ksz_ptp.h
+++ b/drivers/net/dsa/microchip/ksz_ptp.h
@@ -12,7 +12,7 @@
#include <linux/ptp_clock_kernel.h>
-#define KSZ_PTP_N_GPIO 2
+#define KSZ_PTP_N_GPIO 12
enum ksz_ptp_tou_mode {
KSZ_PTP_TOU_IDLE,
@@ -29,10 +29,11 @@ struct ksz_ptp_data {
spinlock_t clock_lock;
struct timespec64 clock_time;
enum ksz_ptp_tou_mode tou_mode;
- struct timespec64 perout_target_time_first; /* start of first pulse */
- struct timespec64 perout_period;
+ struct ptp_perout_request perout_request;
};
+void ksz_ptp_set_caps(struct dsa_switch *ds);
+void ksz8463_ptp_set_caps(struct dsa_switch *ds);
int ksz_ptp_clock_register(struct dsa_switch *ds);
void ksz_ptp_clock_unregister(struct dsa_switch *ds);
@@ -65,6 +66,8 @@ struct ksz_ptp_data {
struct mutex lock;
};
+static inline void ksz_ptp_set_caps(struct dsa_switch *ds) { }
+static inline void ksz8463_ptp_set_caps(struct dsa_switch *ds) { }
static inline int ksz_ptp_clock_register(struct dsa_switch *ds)
{
return 0;
diff --git a/drivers/net/dsa/microchip/ksz_ptp_reg.h b/drivers/net/dsa/microchip/ksz_ptp_reg.h
index 65ea8577af75..16f117fa5d49 100644
--- a/drivers/net/dsa/microchip/ksz_ptp_reg.h
+++ b/drivers/net/dsa/microchip/ksz_ptp_reg.h
@@ -51,6 +51,22 @@
#define REG_PTP_UNIT_INDEX__4 0x0520
+#define KSZ8463_TOU_ACTIVE 0x202
+#define KSZ8463_TOU_EN 0x206
+#define KSZ8463_TOU_SW_RST 0x208
+
+#define KSZ8463_TRIGN_CFG_SIZE 0x20
+#define KSZ8463_TRIG1_CFG 0x220
+#define KSZ8463_TARGET_NSEC 0x000
+#define KSZ8463_TARGET_SEC 0x004
+#define KSZ8463_PATTERN_OFF 0x008
+#define KSZ8463_NOTIFY_BIT BIT(8)
+#define KSZ8463_PATTERN_M GENMASK(6, 4)
+#define KSZ8463_PULSE_WIDTH_OFF 0x00A
+#define KSZ8463_TRIG_PULSE_WIDTH_M GENMASK(15, 0)
+#define KSZ8463_CYCLE_WIDTH_OFF 0x00C
+#define KSZ8463_CYCLE_CNT_OFF 0x010
+
#define PTP_GPIO_INDEX GENMASK(19, 16)
#define PTP_TSI_INDEX BIT(8)
#define PTP_TOU_INDEX GENMASK(1, 0)
diff --git a/drivers/net/dsa/microchip/ksz_spi.c b/drivers/net/dsa/microchip/ksz_spi.c
index 373e9054947c..e3bb9e604186 100644
--- a/drivers/net/dsa/microchip/ksz_spi.c
+++ b/drivers/net/dsa/microchip/ksz_spi.c
@@ -161,7 +161,8 @@ static int ksz_spi_probe(struct spi_device *spi)
chip->chip_id == KSZ8794_CHIP_ID ||
chip->chip_id == KSZ8765_CHIP_ID)
regmap_config = ksz8795_regmap_config;
- else if (chip->chip_id == KSZ8895_CHIP_ID ||
+ else if (chip->chip_id == KSZ8995XA_CHIP_ID ||
+ chip->chip_id == KSZ8895_CHIP_ID ||
chip->chip_id == KSZ8864_CHIP_ID)
regmap_config = ksz8863_regmap_config;
else
@@ -185,7 +186,10 @@ static int ksz_spi_probe(struct spi_device *spi)
dev->pdata = spi->dev.platform_data;
/* setup spi */
- spi->mode = SPI_MODE_3;
+ if (chip->chip_id == KSZ8995XA_CHIP_ID)
+ spi->mode = SPI_MODE_0;
+ else
+ spi->mode = SPI_MODE_3;
ret = spi_setup(spi);
if (ret)
return ret;
@@ -224,6 +228,25 @@ static void ksz_spi_shutdown(struct spi_device *spi)
}
static const struct of_device_id ksz_dt_ids[] = {
+ /*
+ * Legacy Micrel bindings. In 2015 Microchip acquired
+ * Micrel which is the originator of the KSZ series, and
+ * devices branded for Micrel already existed, as well as
+ * some device tree bindings. These two products are identical
+ * to the same Microchip products.
+ */
+ {
+ .compatible = "micrel,ksz8864",
+ .data = &ksz_switch_chips[KSZ8864]
+ },
+ {
+ .compatible = "micrel,ksz8795",
+ .data = &ksz_switch_chips[KSZ8795]
+ },
+ {
+ .compatible = "micrel,ks8995",
+ .data = &ksz_switch_chips[KSZ8995XA]
+ },
{
.compatible = "microchip,ksz8463",
.data = &ksz_switch_chips[KSZ8463]
@@ -257,6 +280,10 @@ static const struct of_device_id ksz_dt_ids[] = {
.data = &ksz_switch_chips[KSZ8895]
},
{
+ .compatible = "microchip,ksz8995xa",
+ .data = &ksz_switch_chips[KSZ8995XA]
+ },
+ {
.compatible = "microchip,ksz9477",
.data = &ksz_switch_chips[KSZ9477]
},
@@ -317,6 +344,7 @@ static const struct of_device_id ksz_dt_ids[] = {
MODULE_DEVICE_TABLE(of, ksz_dt_ids);
static const struct spi_device_id ksz_spi_ids[] = {
+ { "ks8995" },
{ "ksz8463" },
{ "ksz8765" },
{ "ksz8794" },
@@ -325,6 +353,7 @@ static const struct spi_device_id ksz_spi_ids[] = {
{ "ksz8864" },
{ "ksz8873" },
{ "ksz8895" },
+ { "ksz8995xa" },
{ "ksz9477" },
{ "ksz9896" },
{ "ksz9897" },
diff --git a/drivers/net/dsa/microchip/lan937x_main.c b/drivers/net/dsa/microchip/lan937x_main.c
index 86ce3a86705f..3a209122fc7d 100644
--- a/drivers/net/dsa/microchip/lan937x_main.c
+++ b/drivers/net/dsa/microchip/lan937x_main.c
@@ -867,6 +867,7 @@ static int lan937x_setup(struct dsa_switch *ds)
}
}
+ ksz_ptp_set_caps(ds);
ret = ksz_ptp_clock_register(ds);
if (ret) {
dev_err(dev->dev, "Failed to register PTP clock: %d\n",
diff --git a/drivers/net/dsa/motorcomm/Kconfig b/drivers/net/dsa/motorcomm/Kconfig
new file mode 100644
index 000000000000..79cdd79a1fd2
--- /dev/null
+++ b/drivers/net/dsa/motorcomm/Kconfig
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config NET_DSA_YT921X
+ tristate "Motorcomm YT9215 ethernet switch chip support"
+ select NET_DSA_TAG_YT921X
+ select NET_IEEE8021Q_HELPERS if DCB
+ help
+ This enables support for the Motorcomm YT9215 ethernet switch
+ chip.
+
+config NET_DSA_YT921X_LEDS
+ bool "LED support for Motorcomm YT9215"
+ default y
+ depends on NET_DSA_YT921X
+ depends on LEDS_CLASS=y || LEDS_CLASS=NET_DSA_YT921X
+ help
+ This enables support for controlling the LEDs attached to the
+ Motorcomm YT9215 switch chips.
diff --git a/drivers/net/dsa/motorcomm/Makefile b/drivers/net/dsa/motorcomm/Makefile
new file mode 100644
index 000000000000..9eaa59f699e8
--- /dev/null
+++ b/drivers/net/dsa/motorcomm/Makefile
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_NET_DSA_YT921X) += yt921x.o
+yt921x-objs := chip.o
+yt921x-$(CONFIG_NET_DSA_YT921X_LEDS) += leds.o
+yt921x-objs += mdio_bus.o
+yt921x-objs += mib.o
+yt921x-objs += pcs-921x.o
+yt921x-objs += smi.o
+yt921x-objs += tc.o
diff --git a/drivers/net/dsa/motorcomm/chip.c b/drivers/net/dsa/motorcomm/chip.c
new file mode 100644
index 000000000000..d4fc20c52a58
--- /dev/null
+++ b/drivers/net/dsa/motorcomm/chip.c
@@ -0,0 +1,2680 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for Motorcomm YT921x Switch
+ *
+ * Should work on YT9213/YT9214/YT9215/YT9218, but only tested on YT9215+SGMII,
+ * be sure to do your own checks before porting to another chip.
+ *
+ * Copyright (c) 2025 David Yang
+ */
+
+#include <linux/dcbnl.h>
+#include <linux/etherdevice.h>
+#include <linux/if_bridge.h>
+#include <linux/if_hsr.h>
+#include <linux/if_vlan.h>
+#include <linux/mdio.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_mdio.h>
+#include <linux/of_net.h>
+#include <linux/sort.h>
+#include <linux/u64_stats_sync.h>
+
+#include <net/dsa.h>
+#include <net/dscp.h>
+#include <net/ieee8021q.h>
+
+#include "chip.h"
+#include "leds.h"
+#include "mdio_bus.h"
+#include "mib.h"
+#include "pcs.h"
+#include "smi.h"
+#include "tc.h"
+
+static const struct yt921x_info yt921x_infos[] = {
+ {
+ "YT9215SC", YT9215_MAJOR, 1, 0,
+ GENMASK(4, 0),
+ BIT(9),
+ BIT(8) | BIT(9),
+ },
+ {
+ "YT9215S", YT9215_MAJOR, 2, 0,
+ GENMASK(4, 0),
+ BIT(9),
+ BIT(8),
+ },
+ {
+ "YT9215RB", YT9215_MAJOR, 3, 0,
+ GENMASK(4, 0),
+ BIT(8) | BIT(9),
+ 0,
+ },
+ {
+ "YT9214NB", YT9215_MAJOR, 3, 2,
+ BIT(1) | BIT(3),
+ BIT(9),
+ BIT(8),
+ },
+ {
+ "YT9213NB", YT9215_MAJOR, 3, 3,
+ BIT(1) | BIT(3),
+ BIT(9),
+ BIT(9),
+ },
+ {
+ "YT9218N", YT9218_MAJOR, 0, 0,
+ GENMASK(7, 0),
+ 0,
+ 0,
+ },
+ {
+ "YT9218MB", YT9218_MAJOR, 1, 0,
+ GENMASK(7, 0),
+ BIT(8) | BIT(9),
+ BIT(8) | BIT(9),
+ },
+ {}
+};
+
+#define YT921X_VID_UNWARE 4095
+
+struct yt921x_reg_mdio {
+ struct mii_bus *bus;
+ int addr;
+ /* SWITCH_ID_1 / SWITCH_ID_0 of the device
+ *
+ * This is a way to multiplex multiple devices on the same MII phyaddr
+ * and should be configurable in DT. However, MDIO core simply doesn't
+ * allow multiple devices over one reg addr, so this is a fixed value
+ * for now until a solution is found.
+ *
+ * Keep this because we need switchid to form MII regaddrs anyway.
+ */
+ unsigned char switchid;
+};
+
+/* TODO: SPI/I2C */
+
+static int yt921x_reg_mdio_read(void *context, u32 reg, u32 *valp)
+{
+ struct yt921x_reg_mdio *mdio = context;
+ struct mii_bus *bus = mdio->bus;
+ int addr = mdio->addr;
+ u32 reg_addr;
+ u32 reg_data;
+ u32 val;
+ int res;
+
+ /* Hold the mdio bus lock to avoid (un)locking for 4 times */
+ mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
+
+ reg_addr = YT921X_SMI_SWITCHID(mdio->switchid) | YT921X_SMI_ADDR |
+ YT921X_SMI_READ;
+ res = __mdiobus_write(bus, addr, reg_addr, (u16)(reg >> 16));
+ if (res)
+ goto end;
+ res = __mdiobus_write(bus, addr, reg_addr, (u16)reg);
+ if (res)
+ goto end;
+
+ reg_data = YT921X_SMI_SWITCHID(mdio->switchid) | YT921X_SMI_DATA |
+ YT921X_SMI_READ;
+ res = __mdiobus_read(bus, addr, reg_data);
+ if (res < 0)
+ goto end;
+ val = (u16)res;
+ res = __mdiobus_read(bus, addr, reg_data);
+ if (res < 0)
+ goto end;
+ val = (val << 16) | (u16)res;
+
+ *valp = val;
+ res = 0;
+
+end:
+ mutex_unlock(&bus->mdio_lock);
+ return res;
+}
+
+static int yt921x_reg_mdio_write(void *context, u32 reg, u32 val)
+{
+ struct yt921x_reg_mdio *mdio = context;
+ struct mii_bus *bus = mdio->bus;
+ int addr = mdio->addr;
+ u32 reg_addr;
+ u32 reg_data;
+ int res;
+
+ mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
+
+ reg_addr = YT921X_SMI_SWITCHID(mdio->switchid) | YT921X_SMI_ADDR |
+ YT921X_SMI_WRITE;
+ res = __mdiobus_write(bus, addr, reg_addr, (u16)(reg >> 16));
+ if (res)
+ goto end;
+ res = __mdiobus_write(bus, addr, reg_addr, (u16)reg);
+ if (res)
+ goto end;
+
+ reg_data = YT921X_SMI_SWITCHID(mdio->switchid) | YT921X_SMI_DATA |
+ YT921X_SMI_WRITE;
+ res = __mdiobus_write(bus, addr, reg_data, (u16)(val >> 16));
+ if (res)
+ goto end;
+ res = __mdiobus_write(bus, addr, reg_data, (u16)val);
+ if (res)
+ goto end;
+
+ res = 0;
+
+end:
+ mutex_unlock(&bus->mdio_lock);
+ return res;
+}
+
+static const struct yt921x_reg_ops yt921x_reg_ops_mdio = {
+ .read = yt921x_reg_mdio_read,
+ .write = yt921x_reg_mdio_write,
+};
+
+/* TODO: SPI/I2C */
+
+static int
+yt921x_set_eee(struct yt921x_priv *priv, int port, struct ethtool_keee *e)
+{
+ /* Poor datasheet for EEE operations; don't ask if you are confused */
+
+ bool enable = e->eee_enabled;
+ u16 new_mask;
+ int res;
+
+ /* Enable / disable global EEE */
+ new_mask = priv->eee_ports_mask;
+ new_mask &= ~BIT(port);
+ new_mask |= !enable ? 0 : BIT(port);
+
+ if (!!new_mask != !!priv->eee_ports_mask) {
+ res = yt921x_reg_toggle_bits(priv, YT921X_PON_STRAP_FUNC,
+ YT921X_PON_STRAP_EEE, !!new_mask);
+ if (res)
+ return res;
+ res = yt921x_reg_toggle_bits(priv, YT921X_PON_STRAP_VAL,
+ YT921X_PON_STRAP_EEE, !!new_mask);
+ if (res)
+ return res;
+ }
+
+ priv->eee_ports_mask = new_mask;
+
+ /* Enable / disable port EEE */
+ res = yt921x_reg_toggle_bits(priv, YT921X_EEE_CTRL,
+ YT921X_EEE_CTRL_ENn(port), enable);
+ if (res)
+ return res;
+ res = yt921x_reg_toggle_bits(priv, YT921X_EEEn_VAL(port),
+ YT921X_EEE_VAL_DATA, enable);
+ if (res)
+ return res;
+
+ return 0;
+}
+
+static int
+yt921x_dsa_set_mac_eee(struct dsa_switch *ds, int port, struct ethtool_keee *e)
+{
+ struct yt921x_priv *priv = dsa_to_yt921x_priv(ds);
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_set_eee(priv, port, e);
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+static int
+yt921x_dsa_port_change_mtu(struct dsa_switch *ds, int port, int new_mtu)
+{
+ /* Only serves as packet filter, since the frame size is always set to
+ * maximum after reset
+ */
+
+ struct yt921x_priv *priv = dsa_to_yt921x_priv(ds);
+ struct dsa_port *dp = dsa_to_port(ds, port);
+ int frame_size;
+ int res;
+
+ frame_size = new_mtu + ETH_HLEN + ETH_FCS_LEN;
+ if (dsa_port_is_cpu(dp))
+ frame_size += YT921X_TAG_LEN;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_reg_update_bits(priv, YT921X_MACn_FRAME(port),
+ YT921X_MAC_FRAME_SIZE_M,
+ YT921X_MAC_FRAME_SIZE(frame_size));
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+static int yt921x_dsa_port_max_mtu(struct dsa_switch *ds, int port)
+{
+ /* Only called for user ports, exclude tag len here */
+ return YT921X_FRAME_SIZE_MAX - ETH_HLEN - ETH_FCS_LEN - YT921X_TAG_LEN;
+}
+
+static int yt921x_lag_hash(struct yt921x_priv *priv, u32 ctrl, bool unique_lag,
+ struct netlink_ext_ack *extack)
+{
+ u32 val;
+ int res;
+
+ /* Hash Mode is global. Make sure the same Hash Mode is set to all the
+ * 2 possible lags.
+ * If we are the unique LAG we can set whatever hash mode we want.
+ * To change hash mode it's needed to remove all LAG and change the mode
+ * with the latest.
+ */
+ if (unique_lag) {
+ res = yt921x_reg_write(priv, YT921X_LAG_HASH, ctrl);
+ if (res)
+ return res;
+ } else {
+ res = yt921x_reg_read(priv, YT921X_LAG_HASH, &val);
+ if (res)
+ return res;
+
+ if (val != ctrl) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Mismatched Hash Mode across different lags is not supported");
+ return -EOPNOTSUPP;
+ }
+ }
+
+ return 0;
+}
+
+static int yt921x_lag_set(struct yt921x_priv *priv, u8 index, u16 ports_mask)
+{
+ unsigned long targets_mask = ports_mask;
+ unsigned int cnt;
+ u32 ctrl;
+ int port;
+ int res;
+
+ cnt = 0;
+ for_each_set_bit(port, &targets_mask, YT921X_PORT_NUM) {
+ ctrl = YT921X_LAG_MEMBER_PORT(port);
+ res = yt921x_reg_write(priv, YT921X_LAG_MEMBERnm(index, cnt),
+ ctrl);
+ if (res)
+ return res;
+
+ cnt++;
+ }
+
+ ctrl = YT921X_LAG_GROUP_PORTS(ports_mask) |
+ YT921X_LAG_GROUP_MEMBER_NUM(cnt);
+ return yt921x_reg_write(priv, YT921X_LAG_GROUPn(index), ctrl);
+}
+
+static int
+yt921x_dsa_port_lag_leave(struct dsa_switch *ds, int port, struct dsa_lag lag)
+{
+ struct yt921x_priv *priv = dsa_to_yt921x_priv(ds);
+ struct dsa_port *dp;
+ u32 ctrl;
+ int res;
+
+ if (!lag.id)
+ return -EINVAL;
+
+ ctrl = 0;
+ dsa_lag_foreach_port(dp, ds->dst, &lag)
+ ctrl |= BIT(dp->index);
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_lag_set(priv, lag.id - 1, ctrl);
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+static int
+yt921x_dsa_port_lag_check(struct dsa_switch *ds, struct dsa_lag lag,
+ struct netdev_lag_upper_info *info,
+ struct netlink_ext_ack *extack)
+{
+ unsigned int members;
+ struct dsa_port *dp;
+
+ if (!lag.id)
+ return -EINVAL;
+
+ members = 0;
+ dsa_lag_foreach_port(dp, ds->dst, &lag)
+ /* Includes the port joining the LAG */
+ members++;
+
+ if (members > YT921X_LAG_PORT_NUM) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Cannot offload more than 4 LAG ports");
+ return -EOPNOTSUPP;
+ }
+
+ if (info->tx_type != NETDEV_LAG_TX_TYPE_HASH) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Can only offload LAG using hash TX type");
+ return -EOPNOTSUPP;
+ }
+
+ if (info->hash_type != NETDEV_LAG_HASH_L2 &&
+ info->hash_type != NETDEV_LAG_HASH_L23 &&
+ info->hash_type != NETDEV_LAG_HASH_L34) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Can only offload L2 or L2+L3 or L3+L4 TX hash");
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static int
+yt921x_dsa_port_lag_join(struct dsa_switch *ds, int port, struct dsa_lag lag,
+ struct netdev_lag_upper_info *info,
+ struct netlink_ext_ack *extack)
+{
+ struct yt921x_priv *priv = dsa_to_yt921x_priv(ds);
+ struct dsa_port *dp;
+ bool unique_lag;
+ unsigned int i;
+ u32 ctrl;
+ int res;
+
+ res = yt921x_dsa_port_lag_check(ds, lag, info, extack);
+ if (res)
+ return res;
+
+ ctrl = 0;
+ switch (info->hash_type) {
+ case NETDEV_LAG_HASH_L34:
+ ctrl |= YT921X_LAG_HASH_IP_DST;
+ ctrl |= YT921X_LAG_HASH_IP_SRC;
+ ctrl |= YT921X_LAG_HASH_IP_PROTO;
+
+ ctrl |= YT921X_LAG_HASH_L4_DPORT;
+ ctrl |= YT921X_LAG_HASH_L4_SPORT;
+ break;
+ case NETDEV_LAG_HASH_L23:
+ ctrl |= YT921X_LAG_HASH_MAC_DA;
+ ctrl |= YT921X_LAG_HASH_MAC_SA;
+
+ ctrl |= YT921X_LAG_HASH_IP_DST;
+ ctrl |= YT921X_LAG_HASH_IP_SRC;
+ ctrl |= YT921X_LAG_HASH_IP_PROTO;
+ break;
+ case NETDEV_LAG_HASH_L2:
+ ctrl |= YT921X_LAG_HASH_MAC_DA;
+ ctrl |= YT921X_LAG_HASH_MAC_SA;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ /* Check if we are the unique configured LAG */
+ unique_lag = true;
+ dsa_lags_foreach_id(i, ds->dst)
+ if (i != lag.id && dsa_lag_by_id(ds->dst, i)) {
+ unique_lag = false;
+ break;
+ }
+
+ mutex_lock(&priv->reg_lock);
+ do {
+ res = yt921x_lag_hash(priv, ctrl, unique_lag, extack);
+ if (res)
+ break;
+
+ ctrl = 0;
+ dsa_lag_foreach_port(dp, ds->dst, &lag)
+ ctrl |= BIT(dp->index);
+ res = yt921x_lag_set(priv, lag.id - 1, ctrl);
+ } while (0);
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+static int yt921x_fdb_wait(struct yt921x_priv *priv, u32 *valp)
+{
+ struct device *dev = yt921x_priv_to_device(priv);
+ u32 val = YT921X_FDB_RESULT_DONE;
+ int res;
+
+ res = yt921x_reg_wait(priv, YT921X_FDB_RESULT, YT921X_FDB_RESULT_DONE,
+ &val);
+ if (res) {
+ dev_err(dev, "FDB probably stuck\n");
+ return res;
+ }
+
+ *valp = val;
+ return 0;
+}
+
+static int
+yt921x_fdb_in01(struct yt921x_priv *priv, const unsigned char *addr,
+ u16 vid, u32 ctrl1)
+{
+ u32 ctrl;
+ int res;
+
+ ctrl = ethaddr_hi4_to_u32(addr);
+ res = yt921x_reg_write(priv, YT921X_FDB_IN0, ctrl);
+ if (res)
+ return res;
+
+ ctrl = ctrl1 | YT921X_FDB_IO1_FID(vid) | ethaddr_lo2_to_u32(addr);
+ return yt921x_reg_write(priv, YT921X_FDB_IN1, ctrl);
+}
+
+static int
+yt921x_fdb_has(struct yt921x_priv *priv, const unsigned char *addr, u16 vid,
+ u16 *indexp)
+{
+ u32 ctrl;
+ u32 val;
+ int res;
+
+ res = yt921x_fdb_in01(priv, addr, vid, 0);
+ if (res)
+ return res;
+
+ ctrl = 0;
+ res = yt921x_reg_write(priv, YT921X_FDB_IN2, ctrl);
+ if (res)
+ return res;
+
+ ctrl = YT921X_FDB_OP_OP_GET_ONE | YT921X_FDB_OP_START;
+ res = yt921x_reg_write(priv, YT921X_FDB_OP, ctrl);
+ if (res)
+ return res;
+
+ res = yt921x_fdb_wait(priv, &val);
+ if (res)
+ return res;
+ if (val & YT921X_FDB_RESULT_NOTFOUND) {
+ *indexp = YT921X_FDB_NUM;
+ return 0;
+ }
+
+ *indexp = FIELD_GET(YT921X_FDB_RESULT_INDEX_M, val);
+ return 0;
+}
+
+static int
+yt921x_fdb_read(struct yt921x_priv *priv, unsigned char *addr, u16 *vidp,
+ u16 *ports_maskp, u16 *indexp, u8 *statusp)
+{
+ struct device *dev = yt921x_priv_to_device(priv);
+ u16 index;
+ u32 data0;
+ u32 data1;
+ u32 data2;
+ u32 val;
+ int res;
+
+ res = yt921x_fdb_wait(priv, &val);
+ if (res)
+ return res;
+ if (val & YT921X_FDB_RESULT_NOTFOUND) {
+ *ports_maskp = 0;
+ return 0;
+ }
+ index = FIELD_GET(YT921X_FDB_RESULT_INDEX_M, val);
+
+ res = yt921x_reg_read(priv, YT921X_FDB_OUT1, &data1);
+ if (res)
+ return res;
+ if ((data1 & YT921X_FDB_IO1_STATUS_M) ==
+ YT921X_FDB_IO1_STATUS_INVALID) {
+ *ports_maskp = 0;
+ return 0;
+ }
+
+ res = yt921x_reg_read(priv, YT921X_FDB_OUT0, &data0);
+ if (res)
+ return res;
+ res = yt921x_reg_read(priv, YT921X_FDB_OUT2, &data2);
+ if (res)
+ return res;
+
+ addr[0] = data0 >> 24;
+ addr[1] = data0 >> 16;
+ addr[2] = data0 >> 8;
+ addr[3] = data0;
+ addr[4] = data1 >> 8;
+ addr[5] = data1;
+ *vidp = FIELD_GET(YT921X_FDB_IO1_FID_M, data1);
+ *indexp = index;
+ *ports_maskp = FIELD_GET(YT921X_FDB_IO2_EGR_PORTS_M, data2);
+ *statusp = FIELD_GET(YT921X_FDB_IO1_STATUS_M, data1);
+
+ dev_dbg(dev,
+ "%s: index 0x%x, mac %02x:%02x:%02x:%02x:%02x:%02x, vid %d, ports 0x%x, status %d\n",
+ __func__, *indexp, addr[0], addr[1], addr[2], addr[3],
+ addr[4], addr[5], *vidp, *ports_maskp, *statusp);
+ return 0;
+}
+
+static int
+yt921x_fdb_dump(struct yt921x_priv *priv, u16 ports_mask,
+ dsa_fdb_dump_cb_t *cb, void *data)
+{
+ unsigned char addr[ETH_ALEN];
+ u8 status;
+ u16 pmask;
+ u16 index;
+ u32 ctrl;
+ u16 vid;
+ int res;
+
+ ctrl = YT921X_FDB_OP_INDEX(0) | YT921X_FDB_OP_MODE_INDEX |
+ YT921X_FDB_OP_OP_GET_ONE | YT921X_FDB_OP_START;
+ res = yt921x_reg_write(priv, YT921X_FDB_OP, ctrl);
+ if (res)
+ return res;
+ res = yt921x_fdb_read(priv, addr, &vid, &pmask, &index, &status);
+ if (res)
+ return res;
+ if ((pmask & ports_mask) && !is_multicast_ether_addr(addr)) {
+ res = cb(addr, vid,
+ status == YT921X_FDB_ENTRY_STATUS_STATIC, data);
+ if (res)
+ return res;
+ }
+
+ ctrl = YT921X_FDB_IO2_EGR_PORTS(ports_mask);
+ res = yt921x_reg_write(priv, YT921X_FDB_IN2, ctrl);
+ if (res)
+ return res;
+
+ index = 0;
+ do {
+ ctrl = YT921X_FDB_OP_INDEX(index) | YT921X_FDB_OP_MODE_INDEX |
+ YT921X_FDB_OP_NEXT_TYPE_UCAST_PORT |
+ YT921X_FDB_OP_OP_GET_NEXT | YT921X_FDB_OP_START;
+ res = yt921x_reg_write(priv, YT921X_FDB_OP, ctrl);
+ if (res)
+ return res;
+
+ res = yt921x_fdb_read(priv, addr, &vid, &pmask, &index,
+ &status);
+ if (res)
+ return res;
+ if (!pmask)
+ break;
+
+ if ((pmask & ports_mask) && !is_multicast_ether_addr(addr)) {
+ res = cb(addr, vid,
+ status == YT921X_FDB_ENTRY_STATUS_STATIC,
+ data);
+ if (res)
+ return res;
+ }
+
+ /* Never call GET_NEXT with 4095, otherwise it will hang
+ * forever until a reset!
+ */
+ } while (index < YT921X_FDB_NUM - 1);
+
+ return 0;
+}
+
+static int
+yt921x_fdb_flush_raw(struct yt921x_priv *priv, u16 ports_mask, u16 vid,
+ bool flush_static)
+{
+ u32 ctrl;
+ u32 val;
+ int res;
+
+ if (vid < 4096) {
+ ctrl = YT921X_FDB_IO1_FID(vid);
+ res = yt921x_reg_write(priv, YT921X_FDB_IN1, ctrl);
+ if (res)
+ return res;
+ }
+
+ ctrl = YT921X_FDB_IO2_EGR_PORTS(ports_mask);
+ res = yt921x_reg_write(priv, YT921X_FDB_IN2, ctrl);
+ if (res)
+ return res;
+
+ ctrl = YT921X_FDB_OP_OP_FLUSH | YT921X_FDB_OP_START;
+ if (vid >= 4096)
+ ctrl |= YT921X_FDB_OP_FLUSH_PORT;
+ else
+ ctrl |= YT921X_FDB_OP_FLUSH_PORT_VID;
+ if (flush_static)
+ ctrl |= YT921X_FDB_OP_FLUSH_STATIC;
+ res = yt921x_reg_write(priv, YT921X_FDB_OP, ctrl);
+ if (res)
+ return res;
+
+ res = yt921x_fdb_wait(priv, &val);
+ if (res)
+ return res;
+
+ return 0;
+}
+
+static int
+yt921x_fdb_flush_port(struct yt921x_priv *priv, int port, bool flush_static)
+{
+ return yt921x_fdb_flush_raw(priv, BIT(port), 4096, flush_static);
+}
+
+static int
+yt921x_fdb_add_index_in12(struct yt921x_priv *priv, u16 index, u16 ctrl1,
+ u16 ctrl2)
+{
+ u32 ctrl;
+ u32 val;
+ int res;
+
+ res = yt921x_reg_write(priv, YT921X_FDB_IN1, ctrl1);
+ if (res)
+ return res;
+ res = yt921x_reg_write(priv, YT921X_FDB_IN2, ctrl2);
+ if (res)
+ return res;
+
+ ctrl = YT921X_FDB_OP_INDEX(index) | YT921X_FDB_OP_MODE_INDEX |
+ YT921X_FDB_OP_OP_ADD | YT921X_FDB_OP_START;
+ res = yt921x_reg_write(priv, YT921X_FDB_OP, ctrl);
+ if (res)
+ return res;
+
+ return yt921x_fdb_wait(priv, &val);
+}
+
+static int
+yt921x_fdb_add(struct yt921x_priv *priv, const unsigned char *addr, u16 vid,
+ u16 ports_mask)
+{
+ u32 ctrl;
+ u32 val;
+ int res;
+
+ ctrl = YT921X_FDB_IO1_STATUS_STATIC;
+ res = yt921x_fdb_in01(priv, addr, vid, ctrl);
+ if (res)
+ return res;
+
+ ctrl = YT921X_FDB_IO2_EGR_PORTS(ports_mask);
+ res = yt921x_reg_write(priv, YT921X_FDB_IN2, ctrl);
+ if (res)
+ return res;
+
+ ctrl = YT921X_FDB_OP_OP_ADD | YT921X_FDB_OP_START;
+ res = yt921x_reg_write(priv, YT921X_FDB_OP, ctrl);
+ if (res)
+ return res;
+
+ return yt921x_fdb_wait(priv, &val);
+}
+
+static int
+yt921x_fdb_leave(struct yt921x_priv *priv, const unsigned char *addr,
+ u16 vid, u16 ports_mask)
+{
+ u16 index;
+ u32 ctrl1;
+ u32 ctrl2;
+ u32 ctrl;
+ u32 val2;
+ u32 val;
+ int res;
+
+ /* Check for presence */
+ res = yt921x_fdb_has(priv, addr, vid, &index);
+ if (res)
+ return res;
+ if (index >= YT921X_FDB_NUM)
+ return 0;
+
+ /* Check if action required */
+ res = yt921x_reg_read(priv, YT921X_FDB_OUT2, &val2);
+ if (res)
+ return res;
+
+ ctrl2 = val2 & ~YT921X_FDB_IO2_EGR_PORTS(ports_mask);
+ if (ctrl2 == val2)
+ return 0;
+ if (!(ctrl2 & YT921X_FDB_IO2_EGR_PORTS_M)) {
+ ctrl = YT921X_FDB_OP_OP_DEL | YT921X_FDB_OP_START;
+ res = yt921x_reg_write(priv, YT921X_FDB_OP, ctrl);
+ if (res)
+ return res;
+
+ return yt921x_fdb_wait(priv, &val);
+ }
+
+ res = yt921x_reg_read(priv, YT921X_FDB_OUT1, &ctrl1);
+ if (res)
+ return res;
+
+ return yt921x_fdb_add_index_in12(priv, index, ctrl1, ctrl2);
+}
+
+static int
+yt921x_fdb_join(struct yt921x_priv *priv, const unsigned char *addr, u16 vid,
+ u16 ports_mask)
+{
+ u16 index;
+ u32 ctrl1;
+ u32 ctrl2;
+ u32 val1;
+ u32 val2;
+ int res;
+
+ /* Check for presence */
+ res = yt921x_fdb_has(priv, addr, vid, &index);
+ if (res)
+ return res;
+ if (index >= YT921X_FDB_NUM)
+ return yt921x_fdb_add(priv, addr, vid, ports_mask);
+
+ /* Check if action required */
+ res = yt921x_reg_read(priv, YT921X_FDB_OUT1, &val1);
+ if (res)
+ return res;
+ res = yt921x_reg_read(priv, YT921X_FDB_OUT2, &val2);
+ if (res)
+ return res;
+
+ ctrl1 = val1 & ~YT921X_FDB_IO1_STATUS_M;
+ ctrl1 |= YT921X_FDB_IO1_STATUS_STATIC;
+ ctrl2 = val2 | YT921X_FDB_IO2_EGR_PORTS(ports_mask);
+ if (ctrl1 == val1 && ctrl2 == val2)
+ return 0;
+
+ return yt921x_fdb_add_index_in12(priv, index, ctrl1, ctrl2);
+}
+
+static int
+yt921x_dsa_port_fdb_dump(struct dsa_switch *ds, int port,
+ dsa_fdb_dump_cb_t *cb, void *data)
+{
+ struct yt921x_priv *priv = dsa_to_yt921x_priv(ds);
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+ /* Hardware FDB is shared for fdb and mdb, "bridge fdb show"
+ * only wants to see unicast
+ */
+ res = yt921x_fdb_dump(priv, BIT(port), cb, data);
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+static void yt921x_dsa_port_fast_age(struct dsa_switch *ds, int port)
+{
+ struct yt921x_priv *priv = dsa_to_yt921x_priv(ds);
+ struct device *dev = yt921x_priv_to_device(priv);
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_fdb_flush_port(priv, port, false);
+ mutex_unlock(&priv->reg_lock);
+
+ if (res)
+ dev_err(dev, "Failed to %s port %d: %i\n", "clear FDB for",
+ port, res);
+}
+
+static int
+yt921x_dsa_set_ageing_time(struct dsa_switch *ds, unsigned int msecs)
+{
+ struct yt921x_priv *priv = dsa_to_yt921x_priv(ds);
+ u32 ctrl;
+ int res;
+
+ /* AGEING reg is set in 5s step */
+ ctrl = clamp(msecs / 5000, 1, U16_MAX);
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_reg_write(priv, YT921X_AGEING, ctrl);
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+static int
+yt921x_dsa_port_fdb_del(struct dsa_switch *ds, int port,
+ const unsigned char *addr, u16 vid, struct dsa_db db)
+{
+ struct yt921x_priv *priv = dsa_to_yt921x_priv(ds);
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_fdb_leave(priv, addr, vid, BIT(port));
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+static int
+yt921x_dsa_port_fdb_add(struct dsa_switch *ds, int port,
+ const unsigned char *addr, u16 vid, struct dsa_db db)
+{
+ struct yt921x_priv *priv = dsa_to_yt921x_priv(ds);
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_fdb_join(priv, addr, vid, BIT(port));
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+static int
+yt921x_dsa_port_mdb_del(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_mdb *mdb,
+ struct dsa_db db)
+{
+ struct yt921x_priv *priv = dsa_to_yt921x_priv(ds);
+ const unsigned char *addr = mdb->addr;
+ u16 vid = mdb->vid;
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_fdb_leave(priv, addr, vid, BIT(port));
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+static int
+yt921x_dsa_port_mdb_add(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_mdb *mdb,
+ struct dsa_db db)
+{
+ struct yt921x_priv *priv = dsa_to_yt921x_priv(ds);
+ const unsigned char *addr = mdb->addr;
+ u16 vid = mdb->vid;
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_fdb_join(priv, addr, vid, BIT(port));
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+static int
+yt921x_vlan_aware_set(struct yt921x_priv *priv, int port, bool vlan_aware)
+{
+ u32 ctrl;
+
+ /* Abuse SVLAN for PCP parsing without polluting the FDB - it just works
+ * despite YT921X_VLAN_CTRL_SVLAN_EN never being set
+ */
+ if (!vlan_aware)
+ ctrl = YT921X_PORT_IGR_TPIDn_STAG(0);
+ else
+ ctrl = YT921X_PORT_IGR_TPIDn_CTAG(0);
+ return yt921x_reg_write(priv, YT921X_PORTn_IGR_TPID(port), ctrl);
+}
+
+static int
+yt921x_port_set_pvid(struct yt921x_priv *priv, int port, u16 vid)
+{
+ u32 mask;
+ u32 ctrl;
+
+ mask = YT921X_PORT_VLAN_CTRL_CVID_M;
+ ctrl = YT921X_PORT_VLAN_CTRL_CVID(vid);
+ return yt921x_reg_update_bits(priv, YT921X_PORTn_VLAN_CTRL(port),
+ mask, ctrl);
+}
+
+static int
+yt921x_vlan_filtering(struct yt921x_priv *priv, int port, bool vlan_filtering)
+{
+ struct dsa_port *dp = dsa_to_port(&priv->ds, port);
+ struct net_device *bdev;
+ u16 pvid;
+ u32 mask;
+ u32 ctrl;
+ int res;
+
+ bdev = dsa_port_bridge_dev_get(dp);
+
+ if (!bdev || !vlan_filtering)
+ pvid = YT921X_VID_UNWARE;
+ else
+ br_vlan_get_pvid(bdev, &pvid);
+ res = yt921x_port_set_pvid(priv, port, pvid);
+ if (res)
+ return res;
+
+ mask = YT921X_PORT_VLAN_CTRL1_CVLAN_DROP_TAGGED |
+ YT921X_PORT_VLAN_CTRL1_CVLAN_DROP_UNTAGGED;
+ ctrl = 0;
+ /* Do not drop tagged frames here; let VLAN_IGR_FILTER do it */
+ if (vlan_filtering && !pvid)
+ ctrl |= YT921X_PORT_VLAN_CTRL1_CVLAN_DROP_UNTAGGED;
+ res = yt921x_reg_update_bits(priv, YT921X_PORTn_VLAN_CTRL1(port),
+ mask, ctrl);
+ if (res)
+ return res;
+
+ res = yt921x_reg_toggle_bits(priv, YT921X_VLAN_IGR_FILTER,
+ YT921X_VLAN_IGR_FILTER_PORTn(port),
+ vlan_filtering);
+ if (res)
+ return res;
+
+ res = yt921x_vlan_aware_set(priv, port, vlan_filtering);
+ if (res)
+ return res;
+
+ return 0;
+}
+
+static int yt921x_vlan_del(struct yt921x_priv *priv, int port, u16 vid)
+{
+ u32 masks[2];
+
+ masks[0] = YT921X_VLAN_CTRLa_PORTn(port);
+ masks[1] = YT921X_VLAN_CTRLb_UNTAG_PORTn(port);
+
+ return yt921x_reg64_clear_bits(priv, YT921X_VLANn_CTRL(vid), masks);
+}
+
+static int
+yt921x_vlan_add(struct yt921x_priv *priv, int port, u16 vid, bool untagged)
+{
+ u32 masks[2];
+ u32 ctrls[2];
+
+ masks[0] = YT921X_VLAN_CTRLa_PORTn(port) |
+ YT921X_VLAN_CTRLa_PORTS(priv->cpu_ports_mask);
+ ctrls[0] = masks[0];
+
+ masks[1] = YT921X_VLAN_CTRLb_UNTAG_PORTn(port);
+ ctrls[1] = untagged ? masks[1] : 0;
+
+ return yt921x_reg64_update_bits(priv, YT921X_VLANn_CTRL(vid),
+ masks, ctrls);
+}
+
+static int
+yt921x_pvid_clear(struct yt921x_priv *priv, int port)
+{
+ struct dsa_port *dp = dsa_to_port(&priv->ds, port);
+ bool vlan_filtering;
+ u32 mask;
+ int res;
+
+ vlan_filtering = dsa_port_is_vlan_filtering(dp);
+
+ res = yt921x_port_set_pvid(priv, port,
+ vlan_filtering ? 0 : YT921X_VID_UNWARE);
+ if (res)
+ return res;
+
+ if (vlan_filtering) {
+ mask = YT921X_PORT_VLAN_CTRL1_CVLAN_DROP_UNTAGGED;
+ res = yt921x_reg_set_bits(priv, YT921X_PORTn_VLAN_CTRL1(port),
+ mask);
+ if (res)
+ return res;
+ }
+
+ return 0;
+}
+
+static int
+yt921x_pvid_set(struct yt921x_priv *priv, int port, u16 vid)
+{
+ struct dsa_port *dp = dsa_to_port(&priv->ds, port);
+ bool vlan_filtering;
+ u32 mask;
+ int res;
+
+ vlan_filtering = dsa_port_is_vlan_filtering(dp);
+
+ if (vlan_filtering) {
+ res = yt921x_port_set_pvid(priv, port, vid);
+ if (res)
+ return res;
+ }
+
+ mask = YT921X_PORT_VLAN_CTRL1_CVLAN_DROP_UNTAGGED;
+ res = yt921x_reg_clear_bits(priv, YT921X_PORTn_VLAN_CTRL1(port), mask);
+ if (res)
+ return res;
+
+ return 0;
+}
+
+static int
+yt921x_dsa_port_vlan_filtering(struct dsa_switch *ds, int port,
+ bool vlan_filtering,
+ struct netlink_ext_ack *extack)
+{
+ struct yt921x_priv *priv = dsa_to_yt921x_priv(ds);
+ int res;
+
+ if (dsa_is_cpu_port(ds, port))
+ return 0;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_vlan_filtering(priv, port, vlan_filtering);
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+static int
+yt921x_dsa_port_vlan_del(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_vlan *vlan)
+{
+ struct yt921x_priv *priv = dsa_to_yt921x_priv(ds);
+ u16 vid = vlan->vid;
+ u16 pvid;
+ int res;
+
+ if (dsa_is_cpu_port(ds, port))
+ return 0;
+
+ mutex_lock(&priv->reg_lock);
+ do {
+ struct dsa_port *dp = dsa_to_port(ds, port);
+ struct net_device *bdev;
+
+ res = yt921x_vlan_del(priv, port, vid);
+ if (res)
+ break;
+
+ bdev = dsa_port_bridge_dev_get(dp);
+ if (bdev) {
+ br_vlan_get_pvid(bdev, &pvid);
+ if (pvid == vid)
+ res = yt921x_pvid_clear(priv, port);
+ }
+ } while (0);
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+static int
+yt921x_dsa_port_vlan_add(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_vlan *vlan,
+ struct netlink_ext_ack *extack)
+{
+ struct yt921x_priv *priv = dsa_to_yt921x_priv(ds);
+ u16 vid = vlan->vid;
+ u16 pvid;
+ int res;
+
+ /* CPU port is supposed to be a member of every VLAN; see
+ * yt921x_vlan_add() and yt921x_port_setup()
+ */
+ if (dsa_is_cpu_port(ds, port))
+ return 0;
+
+ mutex_lock(&priv->reg_lock);
+ do {
+ struct dsa_port *dp = dsa_to_port(ds, port);
+ struct net_device *bdev;
+
+ res = yt921x_vlan_add(priv, port, vid,
+ vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED);
+ if (res)
+ break;
+
+ bdev = dsa_port_bridge_dev_get(dp);
+ if (bdev) {
+ if (vlan->flags & BRIDGE_VLAN_INFO_PVID) {
+ res = yt921x_pvid_set(priv, port, vid);
+ } else {
+ br_vlan_get_pvid(bdev, &pvid);
+ if (pvid == vid)
+ res = yt921x_pvid_clear(priv, port);
+ }
+ }
+ } while (0);
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+static int yt921x_userport_standalone(struct yt921x_priv *priv, int port)
+{
+ u32 mask;
+ u32 ctrl;
+ int res;
+
+ ctrl = ~priv->cpu_ports_mask;
+ res = yt921x_reg_write(priv, YT921X_PORTn_ISOLATION(port), ctrl);
+ if (res)
+ return res;
+
+ /* Turn off FDB learning to prevent FDB pollution */
+ mask = YT921X_PORT_LEARN_DIS;
+ res = yt921x_reg_set_bits(priv, YT921X_PORTn_LEARN(port), mask);
+ if (res)
+ return res;
+
+ /* Turn off VLAN awareness */
+ res = yt921x_vlan_aware_set(priv, port, false);
+ if (res)
+ return res;
+
+ /* Unrelated since learning is off and all packets are trapped;
+ * set it anyway
+ */
+ res = yt921x_port_set_pvid(priv, port, YT921X_VID_UNWARE);
+ if (res)
+ return res;
+
+ return 0;
+}
+
+static int yt921x_userport_bridge(struct yt921x_priv *priv, int port)
+{
+ u32 mask;
+ int res;
+
+ mask = YT921X_PORT_LEARN_DIS;
+ res = yt921x_reg_clear_bits(priv, YT921X_PORTn_LEARN(port), mask);
+ if (res)
+ return res;
+
+ return 0;
+}
+
+static int yt921x_isolate(struct yt921x_priv *priv, int port)
+{
+ u32 mask;
+ int res;
+
+ mask = BIT(port);
+ for (int i = 0; i < YT921X_PORT_NUM; i++) {
+ if ((BIT(i) & priv->cpu_ports_mask) || i == port)
+ continue;
+
+ res = yt921x_reg_set_bits(priv, YT921X_PORTn_ISOLATION(i),
+ mask);
+ if (res)
+ return res;
+ }
+
+ return 0;
+}
+
+/* Make sure to include the CPU port in ports_mask, or your bridge will
+ * not have it.
+ */
+static int yt921x_bridge(struct yt921x_priv *priv, u16 ports_mask)
+{
+ unsigned long targets_mask = ports_mask & ~priv->cpu_ports_mask;
+ u32 isolated_mask;
+ u32 ctrl;
+ int port;
+ int res;
+
+ isolated_mask = 0;
+ for_each_set_bit(port, &targets_mask, YT921X_PORT_NUM) {
+ struct yt921x_port *pp = &priv->ports[port];
+
+ if (pp->isolated)
+ isolated_mask |= BIT(port);
+ }
+
+ /* Block from non-cpu bridge ports ... */
+ for_each_set_bit(port, &targets_mask, YT921X_PORT_NUM) {
+ struct yt921x_port *pp = &priv->ports[port];
+
+ /* to non-bridge ports */
+ ctrl = ~ports_mask;
+ /* to isolated ports when isolated */
+ if (pp->isolated)
+ ctrl |= isolated_mask;
+ /* to itself when non-hairpin */
+ if (!pp->hairpin)
+ ctrl |= BIT(port);
+ else
+ ctrl &= ~BIT(port);
+
+ res = yt921x_reg_write(priv, YT921X_PORTn_ISOLATION(port),
+ ctrl);
+ if (res)
+ return res;
+ }
+
+ return 0;
+}
+
+static int yt921x_bridge_leave(struct yt921x_priv *priv, int port)
+{
+ int res;
+
+ res = yt921x_userport_standalone(priv, port);
+ if (res)
+ return res;
+
+ res = yt921x_isolate(priv, port);
+ if (res)
+ return res;
+
+ return 0;
+}
+
+static int
+yt921x_bridge_join(struct yt921x_priv *priv, int port, u16 ports_mask)
+{
+ int res;
+
+ res = yt921x_userport_bridge(priv, port);
+ if (res)
+ return res;
+
+ res = yt921x_bridge(priv, ports_mask);
+ if (res)
+ return res;
+
+ return 0;
+}
+
+static int
+yt921x_bridge_flags(struct yt921x_priv *priv, int port,
+ struct switchdev_brport_flags flags)
+{
+ struct yt921x_port *pp = &priv->ports[port];
+ bool do_flush;
+ u32 mask;
+ int res;
+
+ if (flags.mask & BR_LEARNING) {
+ bool learning = flags.val & BR_LEARNING;
+
+ mask = YT921X_PORT_LEARN_DIS;
+ res = yt921x_reg_toggle_bits(priv, YT921X_PORTn_LEARN(port),
+ mask, !learning);
+ if (res)
+ return res;
+ }
+
+ /* BR_FLOOD, BR_MCAST_FLOOD: see the comment where ACT_UNK_ACTn_TRAP
+ * is set
+ */
+
+ /* BR_BCAST_FLOOD: we can filter bcast, but cannot trap them */
+
+ do_flush = false;
+ if (flags.mask & BR_HAIRPIN_MODE) {
+ pp->hairpin = flags.val & BR_HAIRPIN_MODE;
+ do_flush = true;
+ }
+ if (flags.mask & BR_ISOLATED) {
+ pp->isolated = flags.val & BR_ISOLATED;
+ do_flush = true;
+ }
+ if (do_flush) {
+ struct dsa_switch *ds = &priv->ds;
+ struct dsa_port *dp = dsa_to_port(ds, port);
+ struct net_device *bdev;
+
+ bdev = dsa_port_bridge_dev_get(dp);
+ if (bdev) {
+ u32 ports_mask;
+
+ ports_mask = dsa_bridge_ports(ds, bdev);
+ ports_mask |= priv->cpu_ports_mask;
+ res = yt921x_bridge(priv, ports_mask);
+ if (res)
+ return res;
+ }
+ }
+
+ return 0;
+}
+
+static int
+yt921x_dsa_port_pre_bridge_flags(struct dsa_switch *ds, int port,
+ struct switchdev_brport_flags flags,
+ struct netlink_ext_ack *extack)
+{
+ if (flags.mask & ~(BR_HAIRPIN_MODE | BR_LEARNING | BR_FLOOD |
+ BR_MCAST_FLOOD | BR_ISOLATED))
+ return -EINVAL;
+ return 0;
+}
+
+static int
+yt921x_dsa_port_bridge_flags(struct dsa_switch *ds, int port,
+ struct switchdev_brport_flags flags,
+ struct netlink_ext_ack *extack)
+{
+ struct yt921x_priv *priv = dsa_to_yt921x_priv(ds);
+ int res;
+
+ if (dsa_is_cpu_port(ds, port))
+ return 0;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_bridge_flags(priv, port, flags);
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+static void
+yt921x_dsa_port_bridge_leave(struct dsa_switch *ds, int port,
+ struct dsa_bridge bridge)
+{
+ struct yt921x_priv *priv = dsa_to_yt921x_priv(ds);
+ struct device *dev = yt921x_priv_to_device(priv);
+ int res;
+
+ if (dsa_is_cpu_port(ds, port))
+ return;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_bridge_leave(priv, port);
+ mutex_unlock(&priv->reg_lock);
+
+ if (res)
+ dev_err(dev, "Failed to %s port %d: %i\n", "unbridge",
+ port, res);
+}
+
+static int
+yt921x_dsa_port_bridge_join(struct dsa_switch *ds, int port,
+ struct dsa_bridge bridge, bool *tx_fwd_offload,
+ struct netlink_ext_ack *extack)
+{
+ struct yt921x_priv *priv = dsa_to_yt921x_priv(ds);
+ u16 ports_mask;
+ int res;
+
+ if (dsa_is_cpu_port(ds, port))
+ return 0;
+
+ ports_mask = dsa_bridge_ports(ds, bridge.dev);
+ ports_mask |= priv->cpu_ports_mask;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_bridge_join(priv, port, ports_mask);
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+static int
+yt921x_dsa_port_mst_state_set(struct dsa_switch *ds, int port,
+ const struct switchdev_mst_state *st)
+{
+ struct yt921x_priv *priv = dsa_to_yt921x_priv(ds);
+ u32 mask;
+ u32 ctrl;
+ int res;
+
+ mask = YT921X_STP_PORTn_M(port);
+ switch (st->state) {
+ case BR_STATE_DISABLED:
+ ctrl = YT921X_STP_PORTn_DISABLED(port);
+ break;
+ case BR_STATE_LISTENING:
+ case BR_STATE_LEARNING:
+ ctrl = YT921X_STP_PORTn_LEARNING(port);
+ break;
+ case BR_STATE_FORWARDING:
+ default:
+ ctrl = YT921X_STP_PORTn_FORWARD(port);
+ break;
+ case BR_STATE_BLOCKING:
+ ctrl = YT921X_STP_PORTn_BLOCKING(port);
+ break;
+ }
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_reg_update_bits(priv, YT921X_STPn(st->msti), mask, ctrl);
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+static int
+yt921x_dsa_vlan_msti_set(struct dsa_switch *ds, struct dsa_bridge bridge,
+ const struct switchdev_vlan_msti *msti)
+{
+ struct yt921x_priv *priv = dsa_to_yt921x_priv(ds);
+ u32 masks[2];
+ u32 ctrls[2];
+ int res;
+
+ if (!msti->vid)
+ return -EINVAL;
+ if (!msti->msti || msti->msti >= YT921X_MSTI_NUM)
+ return -EINVAL;
+
+ masks[0] = 0;
+ ctrls[0] = 0;
+ masks[1] = YT921X_VLAN_CTRLb_STP_ID_M;
+ ctrls[1] = YT921X_VLAN_CTRLb_STP_ID(msti->msti);
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_reg64_update_bits(priv, YT921X_VLANn_CTRL(msti->vid),
+ masks, ctrls);
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+static void
+yt921x_dsa_port_stp_state_set(struct dsa_switch *ds, int port, u8 state)
+{
+ struct yt921x_priv *priv = dsa_to_yt921x_priv(ds);
+ struct device *dev = yt921x_priv_to_device(priv);
+ struct dsa_port *dp = dsa_to_port(ds, port);
+ bool learning;
+ u32 mask;
+ u32 ctrl;
+ int res;
+
+ mask = YT921X_STP_PORTn_M(port);
+ learning = false;
+ switch (state) {
+ case BR_STATE_DISABLED:
+ ctrl = YT921X_STP_PORTn_DISABLED(port);
+ break;
+ case BR_STATE_LISTENING:
+ ctrl = YT921X_STP_PORTn_LEARNING(port);
+ break;
+ case BR_STATE_LEARNING:
+ ctrl = YT921X_STP_PORTn_LEARNING(port);
+ learning = dp->learning;
+ break;
+ case BR_STATE_FORWARDING:
+ default:
+ ctrl = YT921X_STP_PORTn_FORWARD(port);
+ learning = dp->learning;
+ break;
+ case BR_STATE_BLOCKING:
+ ctrl = YT921X_STP_PORTn_BLOCKING(port);
+ break;
+ }
+
+ mutex_lock(&priv->reg_lock);
+ do {
+ res = yt921x_reg_update_bits(priv, YT921X_STPn(0), mask, ctrl);
+ if (res)
+ break;
+
+ mask = YT921X_PORT_LEARN_DIS;
+ ctrl = !learning ? YT921X_PORT_LEARN_DIS : 0;
+ res = yt921x_reg_update_bits(priv, YT921X_PORTn_LEARN(port),
+ mask, ctrl);
+ } while (0);
+ mutex_unlock(&priv->reg_lock);
+
+ if (res)
+ dev_err(dev, "Failed to %s port %d: %i\n", "set STP state for",
+ port, res);
+}
+
+static int __maybe_unused
+yt921x_dsa_port_get_default_prio(struct dsa_switch *ds, int port)
+{
+ struct yt921x_priv *priv = dsa_to_yt921x_priv(ds);
+ u32 val;
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_reg_read(priv, YT921X_PORTn_QOS(port), &val);
+ mutex_unlock(&priv->reg_lock);
+
+ if (res)
+ return res;
+
+ return FIELD_GET(YT921X_PORT_QOS_PRIO_M, val);
+}
+
+static int __maybe_unused
+yt921x_dsa_port_set_default_prio(struct dsa_switch *ds, int port, u8 prio)
+{
+ struct yt921x_priv *priv = dsa_to_yt921x_priv(ds);
+ u32 mask;
+ u32 ctrl;
+ int res;
+
+ if (prio >= YT921X_PRIO_NUM)
+ return -EINVAL;
+
+ mutex_lock(&priv->reg_lock);
+ mask = YT921X_PORT_QOS_PRIO_M | YT921X_PORT_QOS_PRIO_EN;
+ ctrl = YT921X_PORT_QOS_PRIO(prio) | YT921X_PORT_QOS_PRIO_EN;
+ res = yt921x_reg_update_bits(priv, YT921X_PORTn_QOS(port), mask, ctrl);
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+static int __maybe_unused appprios_cmp(const void *a, const void *b)
+{
+ return ((const u8 *)b)[1] - ((const u8 *)a)[1];
+}
+
+static int __maybe_unused
+yt921x_dsa_port_get_apptrust(struct dsa_switch *ds, int port, u8 *sel,
+ int *nselp)
+{
+ struct yt921x_priv *priv = dsa_to_yt921x_priv(ds);
+ u8 appprios[2][2] = {};
+ int nsel;
+ u32 val;
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_reg_read(priv, YT921X_PORTn_PRIO_ORD(port), &val);
+ mutex_unlock(&priv->reg_lock);
+
+ if (res)
+ return res;
+
+ appprios[0][0] = IEEE_8021QAZ_APP_SEL_DSCP;
+ appprios[0][1] = (val >> (3 * YT921X_APP_SEL_DSCP)) & 7;
+ appprios[1][0] = DCB_APP_SEL_PCP;
+ appprios[1][1] = (val >> (3 * YT921X_APP_SEL_CVLAN_PCP)) & 7;
+ sort(appprios, ARRAY_SIZE(appprios), sizeof(appprios[0]), appprios_cmp,
+ NULL);
+
+ nsel = 0;
+ for (int i = 0; i < ARRAY_SIZE(appprios) && appprios[i][1]; i++) {
+ sel[nsel] = appprios[i][0];
+ nsel++;
+ }
+ *nselp = nsel;
+
+ return 0;
+}
+
+static int __maybe_unused
+yt921x_dsa_port_set_apptrust(struct dsa_switch *ds, int port, const u8 *sel,
+ int nsel)
+{
+ struct yt921x_priv *priv = dsa_to_yt921x_priv(ds);
+ struct device *dev = yt921x_priv_to_device(priv);
+ u32 ctrl;
+ int res;
+
+ if (nsel > YT921X_APP_SEL_NUM)
+ return -EINVAL;
+
+ ctrl = 0;
+ for (int i = 0; i < nsel; i++) {
+ switch (sel[i]) {
+ case IEEE_8021QAZ_APP_SEL_DSCP:
+ ctrl |= YT921X_PORT_PRIO_ORD_APPm(YT921X_APP_SEL_DSCP,
+ 7 - i);
+ break;
+ case DCB_APP_SEL_PCP:
+ ctrl |= YT921X_PORT_PRIO_ORD_APPm(YT921X_APP_SEL_CVLAN_PCP,
+ 7 - i);
+ ctrl |= YT921X_PORT_PRIO_ORD_APPm(YT921X_APP_SEL_SVLAN_PCP,
+ 7 - i);
+ break;
+ default:
+ dev_err(dev,
+ "Invalid apptrust selector (at %d-th). Supported: dscp, pcp\n",
+ i + 1);
+ return -EOPNOTSUPP;
+ }
+ }
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_reg_write(priv, YT921X_PORTn_PRIO_ORD(port), ctrl);
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+static int yt921x_port_down(struct yt921x_priv *priv, int port)
+{
+ const struct yt921x_info *info = priv->info;
+ u32 mask;
+ int res;
+
+ mask = YT921X_PORT_CTRL_LINK_AN | YT921X_PORT_RX_MAC_EN |
+ YT921X_PORT_TX_MAC_EN;
+ res = yt921x_reg_clear_bits(priv, YT921X_PORTn_CTRL(port), mask);
+ if (res)
+ return res;
+
+ if (BIT(port) & info->serdes_mask) {
+ mask = YT921X_SERDES_LINK;
+ res = yt921x_reg_clear_bits(priv, YT921X_SERDESn(port), mask);
+ if (res)
+ return res;
+ }
+ if (BIT(port) & info->xmii_mask) {
+ mask = YT921X_XMII_LINK;
+ res = yt921x_reg_clear_bits(priv, YT921X_XMIIn(port), mask);
+ if (res)
+ return res;
+ }
+
+ return 0;
+}
+
+static int
+yt921x_port_up(struct yt921x_priv *priv, int port, unsigned int mode,
+ phy_interface_t interface, int speed, int duplex,
+ bool tx_pause, bool rx_pause)
+{
+ const struct yt921x_info *info = priv->info;
+ struct yt921x_port *pp = &priv->ports[port];
+ int ps = ethtool_speed_to_yt921x(speed);
+ u32 mask;
+ u32 ctrl;
+ int res;
+
+ if (ps == YT921X_SPEED_INVALID)
+ return -EINVAL;
+
+ mask = YT921X_PORT_SPEED_M | YT921X_PORT_TX_MAC_EN |
+ YT921X_PORT_RX_MAC_EN | YT921X_PORT_TX_PAUSE |
+ YT921X_PORT_RX_PAUSE | YT921X_PORT_DUPLEX_FULL |
+ YT921X_PORT_CTRL_LINK_AN;
+ ctrl = YT921X_PORT_SPEED(ps);
+ if (duplex == DUPLEX_FULL)
+ ctrl |= YT921X_PORT_DUPLEX_FULL;
+ if (tx_pause)
+ ctrl |= YT921X_PORT_TX_PAUSE;
+ if (rx_pause)
+ ctrl |= YT921X_PORT_RX_PAUSE;
+ ctrl |= YT921X_PORT_RX_MAC_EN | YT921X_PORT_TX_MAC_EN;
+ if (pp->serdes && pp->inband)
+ ctrl |= YT921X_PORT_CTRL_LINK_AN;
+ res = yt921x_reg_update_bits(priv, YT921X_PORTn_CTRL(port), mask, ctrl);
+ if (res)
+ return res;
+
+ if (!(BIT(port) & (info->serdes_mask | info->xmii_mask)))
+ return 0;
+
+ if (pp->serdes) {
+ mask = YT921X_SERDES_SPEED_M;
+ ctrl = YT921X_SERDES_SPEED(ps);
+ mask |= YT921X_SERDES_DUPLEX_FULL;
+ if (duplex == DUPLEX_FULL)
+ ctrl |= YT921X_SERDES_DUPLEX_FULL;
+ mask |= YT921X_SERDES_TX_PAUSE;
+ if (tx_pause)
+ ctrl |= YT921X_SERDES_TX_PAUSE;
+ mask |= YT921X_SERDES_RX_PAUSE;
+ if (rx_pause)
+ ctrl |= YT921X_SERDES_RX_PAUSE;
+ mask |= YT921X_SERDES_LINK;
+ if (!pp->inband)
+ ctrl |= YT921X_SERDES_LINK;
+ res = yt921x_reg_update_bits(priv, YT921X_SERDESn(port),
+ mask, ctrl);
+ if (res)
+ return res;
+ } else {
+ mask = YT921X_XMII_LINK;
+ res = yt921x_reg_set_bits(priv, YT921X_XMIIn(port), mask);
+ if (res)
+ return res;
+
+ ctrl = YT921X_MDIO_POLLING_SPEED(ps);
+ if (duplex == DUPLEX_FULL)
+ ctrl |= YT921X_MDIO_POLLING_DUPLEX_FULL;
+ ctrl |= YT921X_MDIO_POLLING_LINK;
+ res = yt921x_reg_write(priv, YT921X_MDIO_POLLINGn(port), ctrl);
+ if (res)
+ return res;
+ }
+
+ return 0;
+}
+
+static int
+yt921x_port_config(struct yt921x_priv *priv, int port, unsigned int mode,
+ phy_interface_t interface)
+{
+ struct device *dev = yt921x_priv_to_device(priv);
+ const struct yt921x_info *info = priv->info;
+ struct yt921x_port *pp = &priv->ports[port];
+ u32 mask;
+ int res;
+
+ if (BIT(port) & info->internal_mask) {
+ if (interface != PHY_INTERFACE_MODE_INTERNAL)
+ goto err;
+ return 0;
+ }
+
+ switch (interface) {
+ /* SERDES */
+ case PHY_INTERFACE_MODE_SGMII:
+ case PHY_INTERFACE_MODE_100BASEX:
+ case PHY_INTERFACE_MODE_1000BASEX:
+ case PHY_INTERFACE_MODE_2500BASEX:
+ if (!(BIT(port) & info->serdes_mask))
+ goto err;
+
+ mask = YT921X_SERDES_CTRL_PORTn(port);
+ res = yt921x_reg_set_bits(priv, YT921X_SERDES_CTRL, mask);
+ if (res)
+ return res;
+
+ mask = YT921X_XMII_CTRL_PORTn(port);
+ res = yt921x_reg_clear_bits(priv, YT921X_XMII_CTRL, mask);
+ if (res)
+ return res;
+
+ /* The order is quite arbitrary - we can't return to a safe
+ * state on IO errors.
+ */
+ pp->serdes = true;
+
+ break;
+ /* add XMII support here */
+ default:
+ goto err;
+ }
+
+ return 0;
+
+err:
+ dev_err(dev, "Wrong mode %d on port %d\n", interface, port);
+ return -EINVAL;
+}
+
+static struct phylink_pcs *
+yt921x_phylink_mac_select_pcs(struct phylink_config *config,
+ phy_interface_t interface)
+{
+ struct dsa_port *dp = dsa_phylink_to_port(config);
+ struct yt921x_priv *priv = dsa_to_yt921x_priv(dp->ds);
+ const struct yt921x_info *info = priv->info;
+ int port = dp->index;
+
+ if (!(BIT(port) & info->serdes_mask))
+ return NULL;
+
+ switch (interface) {
+ case PHY_INTERFACE_MODE_SGMII:
+ case PHY_INTERFACE_MODE_100BASEX:
+ case PHY_INTERFACE_MODE_1000BASEX:
+ case PHY_INTERFACE_MODE_2500BASEX:
+ return &priv->ports[port].pcs;
+ default:
+ return NULL;
+ }
+}
+
+static void
+yt921x_phylink_mac_link_down(struct phylink_config *config, unsigned int mode,
+ phy_interface_t interface)
+{
+ struct dsa_port *dp = dsa_phylink_to_port(config);
+ struct yt921x_priv *priv = dsa_to_yt921x_priv(dp->ds);
+ struct yt921x_mib *pm;
+ int port = dp->index;
+ int res;
+
+ /* No need to sync; port control block is hold until device remove */
+ pm = priv->ports[port].mib;
+ if (pm)
+ cancel_delayed_work(&pm->work);
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_port_down(priv, port);
+ mutex_unlock(&priv->reg_lock);
+
+ if (res)
+ dev_err(dp->ds->dev, "Failed to %s port %d: %i\n", "bring down",
+ port, res);
+}
+
+static void
+yt921x_phylink_mac_link_up(struct phylink_config *config,
+ struct phy_device *phydev, unsigned int mode,
+ phy_interface_t interface, int speed, int duplex,
+ bool tx_pause, bool rx_pause)
+{
+ struct dsa_port *dp = dsa_phylink_to_port(config);
+ struct yt921x_priv *priv = dsa_to_yt921x_priv(dp->ds);
+ struct yt921x_mib *pm;
+ int port = dp->index;
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_port_up(priv, port, mode, interface, speed, duplex,
+ tx_pause, rx_pause);
+ mutex_unlock(&priv->reg_lock);
+
+ if (res)
+ dev_err(dp->ds->dev, "Failed to %s port %d: %i\n", "bring up",
+ port, res);
+
+ pm = priv->ports[port].mib;
+ if (pm)
+ schedule_delayed_work(&pm->work, 0);
+}
+
+static void
+yt921x_phylink_mac_config(struct phylink_config *config, unsigned int mode,
+ const struct phylink_link_state *state)
+{
+ struct dsa_port *dp = dsa_phylink_to_port(config);
+ struct yt921x_priv *priv = dsa_to_yt921x_priv(dp->ds);
+ int port = dp->index;
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_port_config(priv, port, mode, state->interface);
+ mutex_unlock(&priv->reg_lock);
+
+ if (res)
+ dev_err(dp->ds->dev, "Failed to %s port %d: %i\n", "config",
+ port, res);
+}
+
+static void
+yt921x_dsa_phylink_get_caps(struct dsa_switch *ds, int port,
+ struct phylink_config *config)
+{
+ struct yt921x_priv *priv = dsa_to_yt921x_priv(ds);
+ const struct yt921x_info *info = priv->info;
+
+ config->mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE |
+ MAC_10 | MAC_100 | MAC_1000;
+
+ if (info->internal_mask & BIT(port)) {
+ /* Port 10 for MCU should probably go here too. But since that
+ * is untested yet, turn it down for the moment by letting it
+ * fall to the default branch.
+ */
+ __set_bit(PHY_INTERFACE_MODE_INTERNAL,
+ config->supported_interfaces);
+ return;
+ }
+
+ if (BIT(port) & info->serdes_mask) {
+ __set_bit(PHY_INTERFACE_MODE_SGMII,
+ config->supported_interfaces);
+ /* REVSGMII (SGMII in PHY role) should go here, once
+ * PHY_INTERFACE_MODE_REVSGMII is introduced.
+ */
+ __set_bit(PHY_INTERFACE_MODE_100BASEX,
+ config->supported_interfaces);
+ __set_bit(PHY_INTERFACE_MODE_1000BASEX,
+ config->supported_interfaces);
+ __set_bit(PHY_INTERFACE_MODE_2500BASEX,
+ config->supported_interfaces);
+ config->mac_capabilities |= MAC_2500FD;
+ }
+ if (BIT(port) & info->xmii_mask) {
+ /* Not tested. To add support for XMII:
+ * - Add proper interface modes below
+ * - Handle them in yt921x_port_config()
+ */
+ }
+ /* no such port: empty supported_interfaces causes phylink to turn it
+ * down
+ */
+}
+
+static int yt921x_port_setup(struct yt921x_priv *priv, int port)
+{
+ struct dsa_switch *ds = &priv->ds;
+ u32 ctrl;
+ int res;
+
+ res = yt921x_userport_standalone(priv, port);
+ if (res)
+ return res;
+
+ /* Clear prio order (even if DCB is not enabled) to avoid unsolicited
+ * priorities
+ */
+ res = yt921x_reg_write(priv, YT921X_PORTn_PRIO_ORD(port), 0);
+ if (res)
+ return res;
+
+ if (dsa_is_cpu_port(ds, port)) {
+ /* Egress of CPU port is supposed to be completely controlled
+ * via tagging, so set to oneway isolated (drop all packets
+ * without tag).
+ */
+ ctrl = ~(u32)0;
+ res = yt921x_reg_write(priv, YT921X_PORTn_ISOLATION(port),
+ ctrl);
+ if (res)
+ return res;
+
+ /* To simplify FDB "isolation" simulation, we also disable
+ * learning on the CPU port, and let software identify packets
+ * towarding CPU (either trapped or a static FDB entry is
+ * matched, no matter which bridge that entry is for), which is
+ * already done by yt921x_userport_standalone(). As a result,
+ * VLAN-awareness becomes unrelated on the CPU port (set to
+ * VLAN-unaware by the way).
+ */
+ }
+
+ return 0;
+}
+
+static enum dsa_tag_protocol
+yt921x_dsa_get_tag_protocol(struct dsa_switch *ds, int port,
+ enum dsa_tag_protocol m)
+{
+ return DSA_TAG_PROTO_YT921X;
+}
+
+static void yt921x_dsa_port_teardown(struct dsa_switch *ds, int port)
+{
+ struct yt921x_priv *priv = dsa_to_yt921x_priv(ds);
+ struct yt921x_port *pp = &priv->ports[port];
+ struct yt921x_mib *pm = pp->mib;
+
+ /* get_stats64() might not be synchronized with port_teardown(), hold
+ * the MIB block till the device teardown
+ */
+ if (pm)
+ disable_delayed_work_sync(&pm->work);
+}
+
+static int yt921x_dsa_port_setup(struct dsa_switch *ds, int port)
+{
+ struct yt921x_priv *priv = dsa_to_yt921x_priv(ds);
+ struct yt921x_port *pp = &priv->ports[port];
+ const struct yt921x_info *info = priv->info;
+ struct device *dev = yt921x_priv_to_device(priv);
+ struct yt921x_mib *pm = pp->mib;
+ int res;
+
+ if (!pm && (BIT(port) & yt921x_info_ports_mask(info))) {
+ pm = devm_kzalloc(dev, sizeof(*pm), GFP_KERNEL);
+ if (!pm)
+ return -ENOMEM;
+ pp->mib = pm;
+
+ pm->port = pp;
+ u64_stats_init(&pm->syncp);
+ INIT_DELAYED_WORK(&pm->work, yt921x_mib_poll);
+ /* Start disabled, so that the enable below always pairs with
+ * either this disable or the one from a previous teardown
+ */
+ disable_delayed_work_sync(&pm->work);
+ }
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_port_setup(priv, port);
+ mutex_unlock(&priv->reg_lock);
+ if (res)
+ return res;
+
+ if (pm)
+ enable_delayed_work(&pm->work);
+
+ return 0;
+}
+
+/* Not "port" - DSCP mapping is global */
+static int __maybe_unused
+yt921x_dsa_port_get_dscp_prio(struct dsa_switch *ds, int port, u8 dscp)
+{
+ struct yt921x_priv *priv = dsa_to_yt921x_priv(ds);
+ u32 val;
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_reg_read(priv, YT921X_IPM_DSCPn(dscp), &val);
+ mutex_unlock(&priv->reg_lock);
+
+ if (res)
+ return res;
+
+ return FIELD_GET(YT921X_IPM_PRIO_M, val);
+}
+
+static int __maybe_unused
+yt921x_dsa_port_del_dscp_prio(struct dsa_switch *ds, int port, u8 dscp, u8 prio)
+{
+ struct yt921x_priv *priv = dsa_to_yt921x_priv(ds);
+ u32 val;
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+ /* During a "dcb app replace" command, the new app table entry will be
+ * added first, then the old one will be deleted. But the hardware only
+ * supports one QoS class per DSCP value (duh), so if we blindly delete
+ * the app table entry for this DSCP value, we end up deleting the
+ * entry with the new priority. Avoid that by checking whether user
+ * space wants to delete the priority which is currently configured, or
+ * something else which is no longer current.
+ */
+ res = yt921x_reg_read(priv, YT921X_IPM_DSCPn(dscp), &val);
+ if (!res && FIELD_GET(YT921X_IPM_PRIO_M, val) == prio)
+ res = yt921x_reg_write(priv, YT921X_IPM_DSCPn(dscp),
+ YT921X_IPM_PRIO(IEEE8021Q_TT_BK));
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+static int __maybe_unused
+yt921x_dsa_port_add_dscp_prio(struct dsa_switch *ds, int port, u8 dscp, u8 prio)
+{
+ struct yt921x_priv *priv = dsa_to_yt921x_priv(ds);
+ int res;
+
+ if (prio >= YT921X_PRIO_NUM)
+ return -EINVAL;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_reg_write(priv, YT921X_IPM_DSCPn(dscp),
+ YT921X_IPM_PRIO(prio));
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+static int yt921x_edata_wait(struct yt921x_priv *priv, u32 *valp)
+{
+ u32 val = YT921X_EDATA_DATA_IDLE;
+ int res;
+
+ res = yt921x_reg_wait(priv, YT921X_EDATA_DATA,
+ YT921X_EDATA_DATA_STATUS_M, &val);
+ if (res)
+ return res;
+
+ *valp = val;
+ return 0;
+}
+
+static int
+yt921x_edata_read_cont(struct yt921x_priv *priv, u8 addr, u8 *valp)
+{
+ u32 ctrl;
+ u32 val;
+ int res;
+
+ ctrl = YT921X_EDATA_CTRL_ADDR(addr) | YT921X_EDATA_CTRL_READ;
+ res = yt921x_reg_write(priv, YT921X_EDATA_CTRL, ctrl);
+ if (res)
+ return res;
+ res = yt921x_edata_wait(priv, &val);
+ if (res)
+ return res;
+
+ *valp = FIELD_GET(YT921X_EDATA_DATA_DATA_M, val);
+ return 0;
+}
+
+static int yt921x_edata_read(struct yt921x_priv *priv, u8 addr, u8 *valp)
+{
+ u32 val;
+ int res;
+
+ res = yt921x_edata_wait(priv, &val);
+ if (res)
+ return res;
+ return yt921x_edata_read_cont(priv, addr, valp);
+}
+
+static void yt921x_dsa_teardown(struct dsa_switch *ds)
+{
+#if IS_ENABLED(CONFIG_NET_DSA_YT921X_LEDS)
+ struct yt921x_priv *priv = dsa_to_yt921x_priv(ds);
+
+ yt921x_leds_remove(priv);
+#endif
+}
+
+static int yt921x_chip_detect(struct yt921x_priv *priv)
+{
+ struct device *dev = yt921x_priv_to_device(priv);
+ const struct yt921x_info *info;
+ u8 extmode;
+ u32 chipid;
+ u32 major;
+ u32 mode;
+ u32 val;
+ int res;
+
+ res = yt921x_reg_read(priv, YT921X_CHIP_ID, &chipid);
+ if (res)
+ return res;
+
+ major = FIELD_GET(YT921X_CHIP_ID_MAJOR, chipid);
+
+ for (info = yt921x_infos; info->name; info++)
+ if (info->major == major)
+ break;
+ if (!info->name) {
+ dev_err(dev, "Unexpected chipid 0x%x\n", chipid);
+ return -ENODEV;
+ }
+
+ res = yt921x_reg_read(priv, YT921X_CHIP_MODE, &mode);
+ if (res)
+ return res;
+ res = yt921x_edata_read(priv, YT921X_EDATA_EXTMODE, &extmode);
+ if (res)
+ return res;
+
+ for (; info->name; info++)
+ if (info->major == major && info->mode == mode &&
+ info->extmode == extmode)
+ break;
+ if (!info->name) {
+ dev_err(dev,
+ "Unsupported chipid 0x%x with chipmode 0x%x 0x%x\n",
+ chipid, mode, extmode);
+ return -ENODEV;
+ }
+
+ res = yt921x_reg_read(priv, YT921X_SYS_CLK, &val);
+ if (res)
+ return res;
+ switch (FIELD_GET(YT921X_SYS_CLK_SEL_M, val)) {
+ case 0:
+ priv->cycle_ns = info->major == YT9215_MAJOR ? 8 : 6;
+ break;
+ case YT921X_SYS_CLK_143M:
+ priv->cycle_ns = 7;
+ break;
+ default:
+ priv->cycle_ns = 8;
+ }
+
+ /* Print chipid here since we are interested in lower 16 bits */
+ dev_info(dev,
+ "Motorcomm %s ethernet switch, chipid: 0x%x, chipmode: 0x%x 0x%x\n",
+ info->name, chipid, mode, extmode);
+
+ priv->info = info;
+
+ return 0;
+}
+
+static int yt921x_chip_reset(struct yt921x_priv *priv)
+{
+ struct device *dev = yt921x_priv_to_device(priv);
+ u16 eth_p_tag;
+ u32 val;
+ int res;
+
+ res = yt921x_chip_detect(priv);
+ if (res)
+ return res;
+
+ /* Reset */
+ res = yt921x_reg_write(priv, YT921X_RST, YT921X_RST_HW);
+ if (res)
+ return res;
+
+ /* RST_HW is almost same as GPIO hard reset, so we need this delay. */
+ fsleep(YT921X_RST_DELAY_US);
+
+ val = 0;
+ res = yt921x_reg_wait(priv, YT921X_RST, ~0, &val);
+ if (res)
+ return res;
+
+ /* Check for tag EtherType; do it after reset in case you messed it up
+ * before.
+ */
+ res = yt921x_reg_read(priv, YT921X_CPU_TAG_TPID, &val);
+ if (res)
+ return res;
+ eth_p_tag = FIELD_GET(YT921X_CPU_TAG_TPID_TPID_M, val);
+ if (eth_p_tag != ETH_P_YT921X) {
+ dev_err(dev, "Tag type 0x%x != 0x%x\n", eth_p_tag,
+ ETH_P_YT921X);
+ /* Despite being possible, we choose not to set CPU_TAG_TPID,
+ * since there is no way it can be different unless you have the
+ * wrong chip.
+ */
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int yt921x_chip_setup_dsa(struct yt921x_priv *priv)
+{
+ struct dsa_switch *ds = &priv->ds;
+ unsigned long cpu_ports_mask;
+ u32 ctrls[2];
+ u32 ctrl;
+ int port;
+ int res;
+
+ /* Enable DSA */
+ priv->cpu_ports_mask = dsa_cpu_ports(ds);
+
+ ctrl = YT921X_EXT_CPU_PORT_TAG_EN | YT921X_EXT_CPU_PORT_PORT_EN |
+ YT921X_EXT_CPU_PORT_PORT(__ffs(priv->cpu_ports_mask));
+ res = yt921x_reg_write(priv, YT921X_EXT_CPU_PORT, ctrl);
+ if (res)
+ return res;
+
+ /* Setup software switch */
+ ctrl = YT921X_CPU_COPY_TO_EXT_CPU;
+ res = yt921x_reg_write(priv, YT921X_CPU_COPY, ctrl);
+ if (res)
+ return res;
+
+ ctrl = GENMASK(10, 0);
+ res = yt921x_reg_write(priv, YT921X_FILTER_UNK_UCAST, ctrl);
+ if (res)
+ return res;
+ res = yt921x_reg_write(priv, YT921X_FILTER_UNK_MCAST, ctrl);
+ if (res)
+ return res;
+
+ /* YT921x does not support native DSA port bridging, so we use port
+ * isolation to emulate it. However, be especially careful that port
+ * isolation takes _after_ FDB lookups, i.e. if an FDB entry (from
+ * another bridge) is matched and the destination port (in another
+ * bridge) is blocked, the packet will be dropped instead of flooding to
+ * the "bridged" ports, thus we need to trap and handle those packets by
+ * software.
+ *
+ * If there is no more than one bridge, we might be able to drop them
+ * directly given some conditions are met, but we trap them in all cases
+ * for now.
+ */
+ ctrl = 0;
+ for (int i = 0; i < YT921X_PORT_NUM; i++)
+ ctrl |= YT921X_ACT_UNK_ACTn_TRAP(i);
+ /* Except for CPU ports, if any packets are sent via CPU ports without
+ * tag, they should be dropped.
+ */
+ cpu_ports_mask = priv->cpu_ports_mask;
+ for_each_set_bit(port, &cpu_ports_mask, YT921X_PORT_NUM) {
+ ctrl &= ~YT921X_ACT_UNK_ACTn_M(port);
+ ctrl |= YT921X_ACT_UNK_ACTn_DROP(port);
+ }
+ res = yt921x_reg_write(priv, YT921X_ACT_UNK_UCAST, ctrl);
+ if (res)
+ return res;
+ res = yt921x_reg_write(priv, YT921X_ACT_UNK_MCAST, ctrl);
+ if (res)
+ return res;
+
+ /* Tagged VID 0 should be treated as untagged, which confuses the
+ * hardware a lot
+ */
+ ctrls[0] = YT921X_VLAN_CTRLa_LEARN_DIS | YT921X_VLAN_CTRLa_PORTS_M;
+ ctrls[1] = 0;
+ res = yt921x_reg64_write(priv, YT921X_VLANn_CTRL(0), ctrls);
+ if (res)
+ return res;
+
+ return 0;
+}
+
+static int __maybe_unused yt921x_chip_setup_qos(struct yt921x_priv *priv)
+{
+ u32 ctrl;
+ int res;
+
+ /* DSCP to internal priorities */
+ for (u8 dscp = 0; dscp < DSCP_MAX; dscp++) {
+ int prio = ietf_dscp_to_ieee8021q_tt(dscp);
+
+ if (prio < 0)
+ return prio;
+
+ res = yt921x_reg_write(priv, YT921X_IPM_DSCPn(dscp),
+ YT921X_IPM_PRIO(prio));
+ if (res)
+ return res;
+ }
+
+ /* 802.1Q QoS to internal priorities */
+ for (u8 pcp = 0; pcp < 8; pcp++)
+ for (u8 dei = 0; dei < 2; dei++) {
+ ctrl = YT921X_IPM_PRIO(pcp);
+ if (dei)
+ /* "Red" almost means drop, so it's not that
+ * useful. Note that tc police does not support
+ * Three-Color very well
+ */
+ ctrl |= YT921X_IPM_COLOR_YELLOW;
+
+ for (u8 svlan = 0; svlan < 2; svlan++) {
+ u32 reg = YT921X_IPM_PCPn(svlan, dei, pcp);
+
+ res = yt921x_reg_write(priv, reg, ctrl);
+ if (res)
+ return res;
+ }
+ }
+
+ return 0;
+}
+
+static int yt921x_chip_setup(struct yt921x_priv *priv)
+{
+ u32 ctrl;
+ int res;
+
+ ctrl = YT921X_FUNC_MIB | YT921X_FUNC_ACL | YT921X_FUNC_METER;
+ res = yt921x_reg_set_bits(priv, YT921X_FUNC, ctrl);
+ if (res)
+ return res;
+
+ res = yt921x_chip_setup_dsa(priv);
+ if (res)
+ return res;
+
+ res = yt921x_chip_setup_tc(priv);
+ if (res)
+ return res;
+
+ res = yt921x_chip_setup_acl(priv);
+ if (res)
+ return res;
+
+#if IS_ENABLED(CONFIG_DCB)
+ res = yt921x_chip_setup_qos(priv);
+ if (res)
+ return res;
+#endif
+
+ /* Miscellaneous */
+ res = yt921x_reg_set_bits(priv, YT921X_SENSOR, YT921X_SENSOR_TEMP);
+ if (res)
+ return res;
+
+ res = yt921x_reg_clear_bits(priv, YT921X_SERDES_CTRL,
+ YT921X_SERDES_CTRL_TEST);
+ if (res)
+ return res;
+
+ return 0;
+}
+
+static int yt921x_dsa_setup(struct dsa_switch *ds)
+{
+ struct yt921x_priv *priv = dsa_to_yt921x_priv(ds);
+ struct device *dev = yt921x_priv_to_device(priv);
+ struct device_node *np = dev->of_node;
+ struct device_node *child;
+ unsigned long mask;
+ int port;
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_chip_reset(priv);
+ mutex_unlock(&priv->reg_lock);
+
+ if (res)
+ return res;
+
+ /* Register the internal mdio bus. Nodes for internal ports should have
+ * proper phy-handle pointing to their PHYs. Not enabling the internal
+ * bus is possible, though pretty wired, if internal ports are not used.
+ */
+ child = of_get_child_by_name(np, "mdio");
+ if (child) {
+ res = yt921x_mbus_int_init(priv, child);
+ of_node_put(child);
+ if (res)
+ return res;
+ }
+
+ /* External mdio bus is optional */
+ child = of_get_child_by_name(np, "mdio-external");
+ if (child) {
+ res = yt921x_mbus_ext_init(priv, child);
+ of_node_put(child);
+ if (res)
+ return res;
+
+ dev_err(dev, "Untested external mdio bus\n");
+ return -ENODEV;
+ }
+
+ mask = priv->info->serdes_mask;
+ for_each_set_bit(port, &mask, YT921X_PORT_NUM) {
+ struct yt921x_port *pp = &priv->ports[port];
+
+ pp->pcs.ops = &yt921x_phylink_pcs_ops;
+ pp->pcs.poll = true;
+
+ __set_bit(PHY_INTERFACE_MODE_SGMII,
+ pp->pcs.supported_interfaces);
+ __set_bit(PHY_INTERFACE_MODE_100BASEX,
+ pp->pcs.supported_interfaces);
+ __set_bit(PHY_INTERFACE_MODE_1000BASEX,
+ pp->pcs.supported_interfaces);
+ __set_bit(PHY_INTERFACE_MODE_2500BASEX,
+ pp->pcs.supported_interfaces);
+ }
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_chip_setup(priv);
+ mutex_unlock(&priv->reg_lock);
+
+ if (res)
+ return res;
+
+#if IS_ENABLED(CONFIG_NET_DSA_YT921X_LEDS)
+ res = yt921x_leds_setup(priv);
+ if (res)
+ dev_warn(dev, "Failed to setup LEDs: %d\n", res);
+#endif
+
+ return 0;
+}
+
+static const struct phylink_mac_ops yt921x_phylink_mac_ops = {
+ .mac_select_pcs = yt921x_phylink_mac_select_pcs,
+ .mac_link_down = yt921x_phylink_mac_link_down,
+ .mac_link_up = yt921x_phylink_mac_link_up,
+ .mac_config = yt921x_phylink_mac_config,
+};
+
+static const struct dsa_switch_ops yt921x_dsa_switch_ops = {
+ /* mib */
+ .get_strings = yt921x_dsa_get_strings,
+ .get_ethtool_stats = yt921x_dsa_get_ethtool_stats,
+ .get_sset_count = yt921x_dsa_get_sset_count,
+ .get_eth_mac_stats = yt921x_dsa_get_eth_mac_stats,
+ .get_eth_ctrl_stats = yt921x_dsa_get_eth_ctrl_stats,
+ .get_rmon_stats = yt921x_dsa_get_rmon_stats,
+ .get_stats64 = yt921x_dsa_get_stats64,
+ .get_pause_stats = yt921x_dsa_get_pause_stats,
+ /* eee */
+ .support_eee = dsa_supports_eee,
+ .set_mac_eee = yt921x_dsa_set_mac_eee,
+ /* mtu */
+ .port_change_mtu = yt921x_dsa_port_change_mtu,
+ .port_max_mtu = yt921x_dsa_port_max_mtu,
+ /* rate */
+ .port_policer_del = yt921x_dsa_port_policer_del,
+ .port_policer_add = yt921x_dsa_port_policer_add,
+ .port_setup_tc = yt921x_dsa_port_setup_tc,
+ /* acl */
+ .cls_flower_del = yt921x_dsa_cls_flower_del,
+ .cls_flower_add = yt921x_dsa_cls_flower_add,
+ /* hsr */
+ .port_hsr_leave = dsa_port_simple_hsr_leave,
+ .port_hsr_join = dsa_port_simple_hsr_join,
+ /* mirror */
+ .port_mirror_del = yt921x_dsa_port_mirror_del,
+ .port_mirror_add = yt921x_dsa_port_mirror_add,
+ /* lag */
+ .port_lag_leave = yt921x_dsa_port_lag_leave,
+ .port_lag_join = yt921x_dsa_port_lag_join,
+ /* fdb */
+ .port_fdb_dump = yt921x_dsa_port_fdb_dump,
+ .port_fast_age = yt921x_dsa_port_fast_age,
+ .set_ageing_time = yt921x_dsa_set_ageing_time,
+ .port_fdb_del = yt921x_dsa_port_fdb_del,
+ .port_fdb_add = yt921x_dsa_port_fdb_add,
+ .port_mdb_del = yt921x_dsa_port_mdb_del,
+ .port_mdb_add = yt921x_dsa_port_mdb_add,
+ /* vlan */
+ .port_vlan_filtering = yt921x_dsa_port_vlan_filtering,
+ .port_vlan_del = yt921x_dsa_port_vlan_del,
+ .port_vlan_add = yt921x_dsa_port_vlan_add,
+ /* bridge */
+ .port_pre_bridge_flags = yt921x_dsa_port_pre_bridge_flags,
+ .port_bridge_flags = yt921x_dsa_port_bridge_flags,
+ .port_bridge_leave = yt921x_dsa_port_bridge_leave,
+ .port_bridge_join = yt921x_dsa_port_bridge_join,
+ /* mst */
+ .port_mst_state_set = yt921x_dsa_port_mst_state_set,
+ .vlan_msti_set = yt921x_dsa_vlan_msti_set,
+ .port_stp_state_set = yt921x_dsa_port_stp_state_set,
+#if IS_ENABLED(CONFIG_DCB)
+ /* dcb */
+ .port_get_default_prio = yt921x_dsa_port_get_default_prio,
+ .port_set_default_prio = yt921x_dsa_port_set_default_prio,
+ .port_get_apptrust = yt921x_dsa_port_get_apptrust,
+ .port_set_apptrust = yt921x_dsa_port_set_apptrust,
+#endif
+ /* port */
+ .get_tag_protocol = yt921x_dsa_get_tag_protocol,
+ .phylink_get_caps = yt921x_dsa_phylink_get_caps,
+ .port_teardown = yt921x_dsa_port_teardown,
+ .port_setup = yt921x_dsa_port_setup,
+#if IS_ENABLED(CONFIG_DCB)
+ /* dscp */
+ .port_get_dscp_prio = yt921x_dsa_port_get_dscp_prio,
+ .port_del_dscp_prio = yt921x_dsa_port_del_dscp_prio,
+ .port_add_dscp_prio = yt921x_dsa_port_add_dscp_prio,
+#endif
+ /* chip */
+ .teardown = yt921x_dsa_teardown,
+ .setup = yt921x_dsa_setup,
+};
+
+static void yt921x_mdio_shutdown(struct mdio_device *mdiodev)
+{
+ struct yt921x_priv *priv = mdiodev_get_drvdata(mdiodev);
+
+ if (!priv)
+ return;
+
+ dsa_switch_shutdown(&priv->ds);
+}
+
+static void yt921x_mdio_remove(struct mdio_device *mdiodev)
+{
+ struct yt921x_priv *priv = mdiodev_get_drvdata(mdiodev);
+
+ if (!priv)
+ return;
+
+ dsa_unregister_switch(&priv->ds);
+
+ for (unsigned int i = 0; i < ARRAY_SIZE(priv->acl_blks); i++) {
+ struct yt921x_acl_blk *aclblk = priv->acl_blks[i];
+
+ if (!aclblk)
+ continue;
+ for (unsigned int j = 0; j < ARRAY_SIZE(aclblk->rules); j++) {
+ struct yt921x_acl_rule *aclrule = aclblk->rules[j];
+
+ if (!aclrule)
+ continue;
+ kvfree(aclrule);
+ }
+ kvfree(aclblk);
+ }
+ mutex_destroy(&priv->reg_lock);
+}
+
+static int yt921x_mdio_probe(struct mdio_device *mdiodev)
+{
+ struct device *dev = &mdiodev->dev;
+ struct yt921x_reg_mdio *mdio;
+ struct yt921x_priv *priv;
+ struct dsa_switch *ds;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ mdio = devm_kzalloc(dev, sizeof(*mdio), GFP_KERNEL);
+ if (!mdio)
+ return -ENOMEM;
+
+ mdio->bus = mdiodev->bus;
+ mdio->addr = mdiodev->addr;
+ mdio->switchid = 0;
+
+ mutex_init(&priv->reg_lock);
+
+ priv->reg_ops = &yt921x_reg_ops_mdio;
+ priv->reg_ctx = mdio;
+
+ for (size_t i = 0; i < ARRAY_SIZE(priv->ports); i++) {
+ struct yt921x_port *pp = &priv->ports[i];
+
+ pp->index = i;
+ }
+
+ ds = &priv->ds;
+ ds->dev = dev;
+ ds->assisted_learning_on_cpu_port = true;
+ ds->dscp_prio_mapping_is_global = true;
+ ds->priv = priv;
+ ds->ops = &yt921x_dsa_switch_ops;
+ ds->ageing_time_min = 1 * 5000;
+ ds->ageing_time_max = U16_MAX * 5000;
+ ds->phylink_mac_ops = &yt921x_phylink_mac_ops;
+ ds->num_lag_ids = YT921X_LAG_NUM;
+ ds->num_ports = YT921X_PORT_NUM;
+
+ mdiodev_set_drvdata(mdiodev, priv);
+
+ return dsa_register_switch(ds);
+}
+
+static const struct of_device_id yt921x_of_match[] = {
+ { .compatible = "motorcomm,yt9215" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, yt921x_of_match);
+
+static struct mdio_driver yt921x_mdio_driver = {
+ .probe = yt921x_mdio_probe,
+ .remove = yt921x_mdio_remove,
+ .shutdown = yt921x_mdio_shutdown,
+ .mdiodrv.driver = {
+ .name = YT921X_NAME,
+ .of_match_table = yt921x_of_match,
+ },
+};
+
+mdio_module_driver(yt921x_mdio_driver);
+
+MODULE_AUTHOR("David Yang <mmyangfl@gmail.com>");
+MODULE_DESCRIPTION("Driver for Motorcomm YT921x Switch");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/dsa/motorcomm/chip.h b/drivers/net/dsa/motorcomm/chip.h
new file mode 100644
index 000000000000..fc315f1d8f87
--- /dev/null
+++ b/drivers/net/dsa/motorcomm/chip.h
@@ -0,0 +1,568 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2025 David Yang
+ */
+
+#ifndef __YT921X_H
+#define __YT921X_H
+
+#include <net/dsa.h>
+
+#define YT921X_SMI_SWITCHID_M GENMASK(3, 2)
+#define YT921X_SMI_SWITCHID(x) FIELD_PREP(YT921X_SMI_SWITCHID_M, (x))
+#define YT921X_SMI_AD BIT(1)
+#define YT921X_SMI_ADDR 0
+#define YT921X_SMI_DATA YT921X_SMI_AD
+#define YT921X_SMI_RW BIT(0)
+#define YT921X_SMI_WRITE 0
+#define YT921X_SMI_READ YT921X_SMI_RW
+
+#define YT921X_SWITCHID_NUM 4
+
+#define YT921X_RST 0x80000
+#define YT921X_RST_HW BIT(31)
+#define YT921X_RST_SW BIT(1)
+#define YT921X_FUNC 0x80004
+#define YT921X_FUNC_METER BIT(4)
+#define YT921X_FUNC_ACL BIT(2)
+#define YT921X_FUNC_MIB BIT(1)
+#define YT921X_CHIP_ID 0x80008
+#define YT921X_CHIP_ID_MAJOR GENMASK(31, 16)
+#define YT921X_EXT_CPU_PORT 0x8000c
+#define YT921X_EXT_CPU_PORT_TAG_EN BIT(15)
+#define YT921X_EXT_CPU_PORT_PORT_EN BIT(14)
+#define YT921X_EXT_CPU_PORT_PORT_M GENMASK(3, 0)
+#define YT921X_EXT_CPU_PORT_PORT(x) FIELD_PREP(YT921X_EXT_CPU_PORT_PORT_M, (x))
+#define YT921X_CPU_TAG_TPID 0x80010
+#define YT921X_CPU_TAG_TPID_TPID_M GENMASK(15, 0)
+/* Same as ETH_P_YT921X, but this represents the true HW default, while the
+ * former is a local convention chosen by us.
+ */
+#define YT921X_CPU_TAG_TPID_TPID_DEFAULT 0x9988
+#define YT921X_PVID_SEL 0x80014
+#define YT921X_PVID_SEL_SVID_PORTn(port) BIT(port)
+#define YT921X_SERDES_CTRL 0x80028
+#define YT921X_SERDES_CTRL_TEST BIT(6)
+#define YT921X_SERDES_CTRL_PORTn(port) BIT((port) - 8)
+#define YT921X_IO_LEVEL 0x80030
+#define YT9215_IO_LEVEL_NORMAL_M GENMASK(5, 4)
+#define YT9215_IO_LEVEL_NORMAL(x) FIELD_PREP(YT9215_IO_LEVEL_NORMAL_M, (x))
+#define YT9215_IO_LEVEL_NORMAL_3V3 YT9215_IO_LEVEL_NORMAL(0)
+#define YT9215_IO_LEVEL_NORMAL_1V8 YT9215_IO_LEVEL_NORMAL(3)
+#define YT9215_IO_LEVEL_RGMII1_M GENMASK(3, 2)
+#define YT9215_IO_LEVEL_RGMII1(x) FIELD_PREP(YT9215_IO_LEVEL_RGMII1_M, (x))
+#define YT9215_IO_LEVEL_RGMII1_3V3 YT9215_IO_LEVEL_RGMII1(0)
+#define YT9215_IO_LEVEL_RGMII1_2V5 YT9215_IO_LEVEL_RGMII1(1)
+#define YT9215_IO_LEVEL_RGMII1_1V8 YT9215_IO_LEVEL_RGMII1(2)
+#define YT9215_IO_LEVEL_RGMII0_M GENMASK(1, 0)
+#define YT9215_IO_LEVEL_RGMII0(x) FIELD_PREP(YT9215_IO_LEVEL_RGMII0_M, (x))
+#define YT9215_IO_LEVEL_RGMII0_3V3 YT9215_IO_LEVEL_RGMII0(0)
+#define YT9215_IO_LEVEL_RGMII0_2V5 YT9215_IO_LEVEL_RGMII0(1)
+#define YT9215_IO_LEVEL_RGMII0_1V8 YT9215_IO_LEVEL_RGMII0(2)
+#define YT9218_IO_LEVEL_RGMII1_M GENMASK(5, 4)
+#define YT9218_IO_LEVEL_RGMII1(x) FIELD_PREP(YT9218_IO_LEVEL_RGMII1_M, (x))
+#define YT9218_IO_LEVEL_RGMII1_3V3 YT9218_IO_LEVEL_RGMII1(0)
+#define YT9218_IO_LEVEL_RGMII1_2V5 YT9218_IO_LEVEL_RGMII1(1)
+#define YT9218_IO_LEVEL_RGMII1_1V8 YT9218_IO_LEVEL_RGMII1(2)
+#define YT9218_IO_LEVEL_RGMII0_M GENMASK(3, 2)
+#define YT9218_IO_LEVEL_RGMII0(x) FIELD_PREP(YT9218_IO_LEVEL_RGMII0_M, (x))
+#define YT9218_IO_LEVEL_RGMII0_3V3 YT9218_IO_LEVEL_RGMII0(0)
+#define YT9218_IO_LEVEL_RGMII0_2V5 YT9218_IO_LEVEL_RGMII0(1)
+#define YT9218_IO_LEVEL_RGMII0_1V8 YT9218_IO_LEVEL_RGMII0(2)
+#define YT9218_IO_LEVEL_NORMAL_M GENMASK(1, 0)
+#define YT9218_IO_LEVEL_NORMAL(x) FIELD_PREP(YT9218_IO_LEVEL_NORMAL_M, (x))
+#define YT9218_IO_LEVEL_NORMAL_3V3 YT9218_IO_LEVEL_NORMAL(0)
+#define YT9218_IO_LEVEL_NORMAL_1V8 YT9218_IO_LEVEL_NORMAL(3)
+#define YT921X_MAC_ADDR_HI2 0x80080
+#define YT921X_MAC_ADDR_LO4 0x80084
+#define YT921X_SERDESn(port) (0x8008c + 4 * ((port) - 8))
+#define YT921X_SERDES_MODE_M GENMASK(9, 7)
+#define YT921X_SERDES_MODE(x) FIELD_PREP(YT921X_SERDES_MODE_M, (x))
+#define YT921X_SERDES_MODE_SGMII YT921X_SERDES_MODE(0)
+#define YT921X_SERDES_MODE_REVSGMII YT921X_SERDES_MODE(1)
+#define YT921X_SERDES_MODE_1000BASEX YT921X_SERDES_MODE(2)
+#define YT921X_SERDES_MODE_100BASEX YT921X_SERDES_MODE(3)
+#define YT921X_SERDES_MODE_2500BASEX YT921X_SERDES_MODE(4)
+#define YT921X_SERDES_RX_PAUSE BIT(6)
+#define YT921X_SERDES_TX_PAUSE BIT(5)
+#define YT921X_SERDES_LINK BIT(4) /* force link */
+#define YT921X_SERDES_DUPLEX_FULL BIT(3)
+#define YT921X_SERDES_SPEED_M GENMASK(2, 0)
+#define YT921X_SERDES_SPEED(x) FIELD_PREP(YT921X_SERDES_SPEED_M, (x))
+#define YT921X_PORTn_CTRL(port) (0x80100 + 4 * (port)) /* Bit 7-0 shared with PORT_STATUS */
+#define YT921X_PORT_CTRL_PAUSE_AN BIT(10)
+#define YT921X_PORT_CTRL_LINK_AN BIT(9)
+#define YT921X_PORT_CTRL_HALF_PAUSE BIT(8) /* Half-duplex back pressure mode */
+#define YT921X_PORTn_STATUS(port) (0x80200 + 4 * (port))
+#define YT921X_PORT_LINK BIT(8)
+#define YT921X_PORT_DUPLEX_FULL BIT(7)
+#define YT921X_PORT_RX_PAUSE BIT(6)
+#define YT921X_PORT_TX_PAUSE BIT(5)
+#define YT921X_PORT_RX_MAC_EN BIT(4)
+#define YT921X_PORT_TX_MAC_EN BIT(3)
+#define YT921X_PORT_SPEED_M GENMASK(2, 0)
+#define YT921X_PORT_SPEED(x) FIELD_PREP(YT921X_PORT_SPEED_M, (x))
+#define YT921X_PON_STRAP_FUNC 0x80320
+#define YT921X_PON_STRAP_VAL 0x80324
+#define YT921X_PON_STRAP_CAP 0x80328
+#define YT921X_PON_STRAP_EEE BIT(16)
+#define YT921X_PON_STRAP_LOOP_DETECT BIT(7)
+#define YT921X_MDIO_POLLINGn(port) (0x80364 + 4 * ((port) - 8))
+#define YT921X_MDIO_POLLING_DUPLEX_FULL BIT(4)
+#define YT921X_MDIO_POLLING_LINK BIT(3)
+#define YT921X_MDIO_POLLING_SPEED_M GENMASK(2, 0)
+#define YT921X_MDIO_POLLING_SPEED(x) FIELD_PREP(YT921X_MDIO_POLLING_SPEED_M, (x))
+
+enum yt921x_speed {
+ YT921X_SPEED_10,
+ YT921X_SPEED_100,
+ YT921X_SPEED_1000,
+ YT921X_SPEED_10000,
+ YT921X_SPEED_2500,
+ YT921X_SPEED_INVALID
+};
+
+#define YT921X_SENSOR 0x8036c
+#define YT921X_SENSOR_TEMP BIT(18)
+#define YT921X_TEMP 0x80374
+#define YT921X_CHIP_MODE 0x80388
+#define YT921X_CHIP_MODE_MODE GENMASK(1, 0)
+#define YT921X_XMII_CTRL 0x80394
+#define YT921X_XMII_CTRL_PORTn(port) BIT(9 - (port)) /* Yes, it's reversed */
+#define YT921X_XMIIn(port) (0x80400 + 8 * ((port) - 8))
+#define YT921X_XMII_MODE_M GENMASK(31, 29)
+#define YT921X_XMII_MODE(x) FIELD_PREP(YT921X_XMII_MODE_M, (x))
+#define YT921X_XMII_MODE_MII YT921X_XMII_MODE(0)
+#define YT921X_XMII_MODE_REVMII YT921X_XMII_MODE(1)
+#define YT921X_XMII_MODE_RMII YT921X_XMII_MODE(2)
+#define YT921X_XMII_MODE_REVRMII YT921X_XMII_MODE(3)
+#define YT921X_XMII_MODE_RGMII YT921X_XMII_MODE(4)
+#define YT921X_XMII_MODE_DISABLE YT921X_XMII_MODE(5)
+#define YT921X_XMII_LINK BIT(19) /* force link */
+#define YT921X_XMII_EN BIT(18)
+#define YT921X_XMII_SOFT_RST BIT(17)
+#define YT921X_XMII_RGMII_TX_DELAY_150PS_M GENMASK(16, 13)
+#define YT921X_XMII_RGMII_TX_DELAY_150PS(x) FIELD_PREP(YT921X_XMII_RGMII_TX_DELAY_150PS_M, (x))
+#define YT921X_XMII_TX_CLK_IN BIT(11)
+#define YT921X_XMII_RX_CLK_IN BIT(10)
+#define YT921X_XMII_RGMII_TX_DELAY_2NS BIT(8)
+#define YT921X_XMII_RGMII_TX_CLK_OUT BIT(7)
+#define YT921X_XMII_RGMII_RX_DELAY_150PS_M GENMASK(6, 3)
+#define YT921X_XMII_RGMII_RX_DELAY_150PS(x) FIELD_PREP(YT921X_XMII_RGMII_RX_DELAY_150PS_M, (x))
+#define YT921X_XMII_RMII_PHY_TX_CLK_OUT BIT(2)
+#define YT921X_XMII_REVMII_TX_CLK_OUT BIT(1)
+#define YT921X_XMII_REVMII_RX_CLK_OUT BIT(0)
+
+#define YT921X_MACn_FRAME(port) (0x81008 + 0x1000 * (port))
+#define YT921X_MAC_FRAME_SIZE_M GENMASK(21, 8)
+#define YT921X_MAC_FRAME_SIZE(x) FIELD_PREP(YT921X_MAC_FRAME_SIZE_M, (x))
+
+#define YT921X_EEEn_VAL(port) (0xa0000 + 0x40 * (port))
+#define YT921X_EEE_VAL_DATA BIT(1)
+
+#define YT921X_EEE_CTRL 0xb0000
+#define YT921X_EEE_CTRL_ENn(port) BIT(port)
+
+#define YT921X_EDATA_CTRL 0xe0000
+#define YT921X_EDATA_CTRL_ADDR_M GENMASK(15, 8)
+#define YT921X_EDATA_CTRL_ADDR(x) FIELD_PREP(YT921X_EDATA_CTRL_ADDR_M, (x))
+#define YT921X_EDATA_CTRL_OP_M GENMASK(3, 0)
+#define YT921X_EDATA_CTRL_OP(x) FIELD_PREP(YT921X_EDATA_CTRL_OP_M, (x))
+#define YT921X_EDATA_CTRL_READ YT921X_EDATA_CTRL_OP(5)
+#define YT921X_EDATA_DATA 0xe0004
+#define YT921X_EDATA_DATA_DATA_M GENMASK(31, 24)
+#define YT921X_EDATA_DATA_STATUS_M GENMASK(3, 0)
+#define YT921X_EDATA_DATA_STATUS(x) FIELD_PREP(YT921X_EDATA_DATA_STATUS_M, (x))
+#define YT921X_EDATA_DATA_IDLE YT921X_EDATA_DATA_STATUS(3)
+#define YT921X_SYS_CLK 0xe0040
+#define YT921X_SYS_CLK_SEL_M GENMASK(1, 0) /* unknown: 167M */
+#define YT9215_SYS_CLK_125M 0
+#define YT9218_SYS_CLK_167M 0
+#define YT921X_SYS_CLK_143M 1
+
+#define YT921X_PORTn_EGR(port) (0x100000 + 4 * (port))
+#define YT921X_PORT_EGR_TPID_CTAG_M GENMASK(5, 4)
+#define YT921X_PORT_EGR_TPID_CTAG(x) FIELD_PREP(YT921X_PORT_EGR_TPID_CTAG_M, (x))
+#define YT921X_PORT_EGR_TPID_STAG_M GENMASK(3, 2)
+#define YT921X_PORT_EGR_TPID_STAG(x) FIELD_PREP(YT921X_PORT_EGR_TPID_STAG_M, (x))
+#define YT921X_TPID_EGRn(x) (0x100300 + 4 * (x)) /* [0, 3] */
+#define YT921X_TPID_EGR_TPID_M GENMASK(15, 0)
+
+#define YT921X_IPM_DSCPn(n) (0x180000 + 4 * (n)) /* Internal Priority Map */
+#define YT921X_IPM_PCPn(map, dei, pcp) (0x180100 + 4 * (16 * (map) + 8 * (dei) + (pcp)))
+#define YT921X_IPM_PRIO_M GENMASK(4, 2)
+#define YT921X_IPM_PRIO(x) FIELD_PREP(YT921X_IPM_PRIO_M, (x))
+#define YT921X_IPM_COLOR_M GENMASK(1, 0)
+#define YT921X_IPM_COLOR(x) FIELD_PREP(YT921X_IPM_COLOR_M, (x))
+#define YT921X_IPM_COLOR_GREEN YT921X_IPM_COLOR(0)
+#define YT921X_IPM_COLOR_YELLOW YT921X_IPM_COLOR(1)
+#define YT921X_IPM_COLOR_RED YT921X_IPM_COLOR(2)
+#define YT921X_PORTn_QOS(port) (0x180180 + 4 * (port))
+#define YT921X_PORT_QOS_CVLAN_PRIO_MAP_ID BIT(5)
+#define YT921X_PORT_QOS_SVLAN_PRIO_MAP_ID BIT(4)
+#define YT921X_PORT_QOS_PRIO_M GENMASK(3, 1)
+#define YT921X_PORT_QOS_PRIO(x) FIELD_PREP(YT921X_PORT_QOS_PRIO_M, (x))
+#define YT921X_PORT_QOS_PRIO_EN BIT(0)
+#define YT921X_PORTn_PRIO_ORD(port) (0x180200 + 4 * (port))
+#define YT921X_PORT_PRIO_ORD_APPm_M(m) GENMASK(3 * (m) + 2, 3 * (m))
+#define YT921X_PORT_PRIO_ORD_APPm(m, x) ((x) << (3 * (m))) /* 0: disabled, except PORT_QOS_PRIO */
+
+enum yt921x_app_selector {
+ YT921X_APP_SEL_MAC_SA,
+ YT921X_APP_SEL_MAC_DA,
+ YT921X_APP_SEL_VID,
+ YT921X_APP_SEL_ACL,
+ YT921X_APP_SEL_DSCP,
+ YT921X_APP_SEL_CVLAN_PCP,
+ YT921X_APP_SEL_SVLAN_PCP,
+ /* The physical port, i.e. YT921X_PORT_QOS_PRIO */
+ YT921X_APP_SEL_PORT,
+ YT921X_APP_SEL_NUM
+};
+
+#define YT921X_VLAN_IGR_FILTER 0x180280
+#define YT921X_VLAN_IGR_FILTER_PORTn_BYPASS_IGMP(port) BIT((port) + 11)
+#define YT921X_VLAN_IGR_FILTER_PORTn(port) BIT(port)
+#define YT921X_PORTn_ISOLATION(port) (0x180294 + 4 * (port))
+#define YT921X_PORT_ISOLATION_BLOCKn(port) BIT(port)
+#define YT921X_STPn(n) (0x18038c + 4 * (n))
+#define YT921X_STP_PORTn_M(port) GENMASK(2 * (port) + 1, 2 * (port))
+#define YT921X_STP_PORTn(port, x) ((x) << (2 * (port)))
+#define YT921X_STP_PORTn_DISABLED(port) YT921X_STP_PORTn(port, 0)
+#define YT921X_STP_PORTn_LEARNING(port) YT921X_STP_PORTn(port, 1)
+#define YT921X_STP_PORTn_BLOCKING(port) YT921X_STP_PORTn(port, 2)
+#define YT921X_STP_PORTn_FORWARD(port) YT921X_STP_PORTn(port, 3)
+#define YT921X_PORTn_LEARN(port) (0x1803d0 + 4 * (port))
+#define YT921X_PORT_LEARN_VID_LEARN_MULTI_EN BIT(22)
+#define YT921X_PORT_LEARN_VID_LEARN_MODE BIT(21)
+#define YT921X_PORT_LEARN_VID_LEARN_EN BIT(20)
+#define YT921X_PORT_LEARN_SUSPEND_COPY_EN BIT(19)
+#define YT921X_PORT_LEARN_SUSPEND_DROP_EN BIT(18)
+#define YT921X_PORT_LEARN_DIS BIT(17)
+#define YT921X_PORT_LEARN_LIMIT_EN BIT(16)
+#define YT921X_PORT_LEARN_LIMIT_M GENMASK(15, 8)
+#define YT921X_PORT_LEARN_LIMIT(x) FIELD_PREP(YT921X_PORT_LEARN_LIMIT_M, (x))
+#define YT921X_PORT_LEARN_DROP_ON_EXCEEDED BIT(2)
+#define YT921X_PORT_LEARN_MODE_M GENMASK(1, 0)
+#define YT921X_PORT_LEARN_MODE(x) FIELD_PREP(YT921X_PORT_LEARN_MODE_M, (x))
+#define YT921X_PORT_LEARN_MODE_AUTO YT921X_PORT_LEARN_MODE(0)
+#define YT921X_PORT_LEARN_MODE_AUTO_AND_COPY YT921X_PORT_LEARN_MODE(1)
+#define YT921X_PORT_LEARN_MODE_CPU_CONTROL YT921X_PORT_LEARN_MODE(2)
+#define YT921X_AGEING 0x180440
+#define YT921X_AGEING_INTERVAL_M GENMASK(15, 0)
+#define YT921X_FDB_IN0 0x180454
+#define YT921X_FDB_IN1 0x180458
+#define YT921X_FDB_IN2 0x18045c
+#define YT921X_FDB_OP 0x180460
+#define YT921X_FDB_OP_INDEX_M GENMASK(22, 11)
+#define YT921X_FDB_OP_INDEX(x) FIELD_PREP(YT921X_FDB_OP_INDEX_M, (x))
+#define YT921X_FDB_OP_MODE_INDEX BIT(10) /* mac+fid / index */
+#define YT921X_FDB_OP_FLUSH_MCAST BIT(9) /* ucast / mcast */
+#define YT921X_FDB_OP_FLUSH_M GENMASK(8, 7)
+#define YT921X_FDB_OP_FLUSH(x) FIELD_PREP(YT921X_FDB_OP_FLUSH_M, (x))
+#define YT921X_FDB_OP_FLUSH_ALL YT921X_FDB_OP_FLUSH(0)
+#define YT921X_FDB_OP_FLUSH_PORT YT921X_FDB_OP_FLUSH(1)
+#define YT921X_FDB_OP_FLUSH_PORT_VID YT921X_FDB_OP_FLUSH(2)
+#define YT921X_FDB_OP_FLUSH_VID YT921X_FDB_OP_FLUSH(3)
+#define YT921X_FDB_OP_FLUSH_STATIC BIT(6)
+#define YT921X_FDB_OP_NEXT_TYPE_M GENMASK(5, 4)
+#define YT921X_FDB_OP_NEXT_TYPE(x) FIELD_PREP(YT921X_FDB_OP_NEXT_TYPE_M, (x))
+#define YT921X_FDB_OP_NEXT_TYPE_UCAST_PORT YT921X_FDB_OP_NEXT_TYPE(0)
+#define YT921X_FDB_OP_NEXT_TYPE_UCAST_VID YT921X_FDB_OP_NEXT_TYPE(1)
+#define YT921X_FDB_OP_NEXT_TYPE_UCAST YT921X_FDB_OP_NEXT_TYPE(2)
+#define YT921X_FDB_OP_NEXT_TYPE_MCAST YT921X_FDB_OP_NEXT_TYPE(3)
+#define YT921X_FDB_OP_OP_M GENMASK(3, 1)
+#define YT921X_FDB_OP_OP(x) FIELD_PREP(YT921X_FDB_OP_OP_M, (x))
+#define YT921X_FDB_OP_OP_ADD YT921X_FDB_OP_OP(0)
+#define YT921X_FDB_OP_OP_DEL YT921X_FDB_OP_OP(1)
+#define YT921X_FDB_OP_OP_GET_ONE YT921X_FDB_OP_OP(2)
+#define YT921X_FDB_OP_OP_GET_NEXT YT921X_FDB_OP_OP(3)
+#define YT921X_FDB_OP_OP_FLUSH YT921X_FDB_OP_OP(4)
+#define YT921X_FDB_OP_START BIT(0)
+#define YT921X_FDB_RESULT 0x180464
+#define YT921X_FDB_RESULT_DONE BIT(15)
+#define YT921X_FDB_RESULT_NOTFOUND BIT(14)
+#define YT921X_FDB_RESULT_OVERWRITED BIT(13)
+#define YT921X_FDB_RESULT_INDEX_M GENMASK(11, 0)
+#define YT921X_FDB_RESULT_INDEX(x) FIELD_PREP(YT921X_FDB_RESULT_INDEX_M, (x))
+#define YT921X_FDB_OUT0 0x1804b0
+#define YT921X_FDB_IO0_ADDR_HI4_M GENMASK(31, 0)
+#define YT921X_FDB_OUT1 0x1804b4
+#define YT921X_FDB_IO1_EGR_PRIO_EN BIT(31)
+#define YT921X_FDB_IO1_STATUS_M GENMASK(30, 28)
+#define YT921X_FDB_IO1_STATUS(x) FIELD_PREP(YT921X_FDB_IO1_STATUS_M, (x))
+#define YT921X_FDB_IO1_STATUS_INVALID YT921X_FDB_IO1_STATUS(0)
+#define YT921X_FDB_IO1_STATUS_MIN_TIME YT921X_FDB_IO1_STATUS(1)
+#define YT921X_FDB_IO1_STATUS_MOVE_AGING_MAX_TIME YT921X_FDB_IO1_STATUS(3)
+#define YT921X_FDB_IO1_STATUS_MAX_TIME YT921X_FDB_IO1_STATUS(5)
+#define YT921X_FDB_IO1_STATUS_PENDING YT921X_FDB_IO1_STATUS(6)
+#define YT921X_FDB_IO1_STATUS_STATIC YT921X_FDB_IO1_STATUS(7)
+#define YT921X_FDB_IO1_FID_M GENMASK(27, 16) /* filtering ID (VID) */
+#define YT921X_FDB_IO1_FID(x) FIELD_PREP(YT921X_FDB_IO1_FID_M, (x))
+#define YT921X_FDB_IO1_ADDR_LO2_M GENMASK(15, 0)
+#define YT921X_FDB_OUT2 0x1804b8
+#define YT921X_FDB_IO2_MOVE_AGING_STATUS_M GENMASK(31, 30)
+#define YT921X_FDB_IO2_IGR_DROP BIT(29)
+#define YT921X_FDB_IO2_EGR_PORTS_M GENMASK(28, 18)
+#define YT921X_FDB_IO2_EGR_PORTS(x) FIELD_PREP(YT921X_FDB_IO2_EGR_PORTS_M, (x))
+#define YT921X_FDB_IO2_EGR_DROP BIT(17)
+#define YT921X_FDB_IO2_COPY_TO_CPU BIT(16)
+#define YT921X_FDB_IO2_IGR_PRIO_EN BIT(15)
+#define YT921X_FDB_IO2_PRIO_M GENMASK(14, 12)
+#define YT921X_FDB_IO2_PRIO(x) FIELD_PREP(YT921X_FDB_IO2_PRIO_M, (x))
+#define YT921X_FDB_IO2_NEW_VID_M GENMASK(11, 0)
+#define YT921X_FDB_IO2_NEW_VID(x) FIELD_PREP(YT921X_FDB_IO2_NEW_VID_M, (x))
+#define YT921X_FILTER_UNK_UCAST 0x180508
+#define YT921X_FILTER_UNK_MCAST 0x18050c
+#define YT921X_FILTER_MCAST 0x180510
+#define YT921X_FILTER_BCAST 0x180514
+#define YT921X_FILTER_PORTS_M GENMASK(10, 0)
+#define YT921X_FILTER_PORTS(x) FIELD_PREP(YT921X_FILTER_PORTS_M, (x))
+#define YT921X_FILTER_PORTn(port) BIT(port)
+#define YT921X_VLAN_EGR_FILTER 0x180598
+#define YT921X_VLAN_EGR_FILTER_PORTn(port) BIT(port)
+#define YT921X_LAG_GROUPn(n) (0x1805a8 + 4 * (n))
+#define YT921X_LAG_GROUP_PORTS_M GENMASK(13, 3)
+#define YT921X_LAG_GROUP_PORTS(x) FIELD_PREP(YT921X_LAG_GROUP_PORTS_M, (x))
+#define YT921X_LAG_GROUP_MEMBER_NUM_M GENMASK(2, 0)
+#define YT921X_LAG_GROUP_MEMBER_NUM(x) FIELD_PREP(YT921X_LAG_GROUP_MEMBER_NUM_M, (x))
+#define YT921X_LAG_MEMBERnm(n, m) (0x1805b0 + 4 * (4 * (n) + (m)))
+#define YT921X_LAG_MEMBER_PORT_M GENMASK(3, 0)
+#define YT921X_LAG_MEMBER_PORT(x) FIELD_PREP(YT921X_LAG_MEMBER_PORT_M, (x))
+#define YT921X_CPU_COPY 0x180690
+#define YT921X_CPU_COPY_FORCE_INT_PORT BIT(2)
+#define YT921X_CPU_COPY_TO_INT_CPU BIT(1)
+#define YT921X_CPU_COPY_TO_EXT_CPU BIT(0)
+#define YT921X_ACL_PERMIT_UNMATCH 0x1806a0
+#define YT921X_ACL_PERMIT_UNMATCH_PORTS_M GENMASK(10, 0)
+#define YT921X_ACL_PERMIT_UNMATCH_PORTS(x) FIELD_PREP(YT921X_ACL_PERMIT_UNMATCH_PORTS_M, (x))
+#define YT921X_ACL_PERMIT_UNMATCH_PORTn(port) BIT(port)
+#define YT921X_ACT_UNK_UCAST 0x180734
+#define YT921X_ACT_UNK_MCAST 0x180738
+#define YT921X_ACT_UNK_MCAST_BYPASS_DROP_RMA BIT(23)
+#define YT921X_ACT_UNK_MCAST_BYPASS_DROP_IGMP BIT(22)
+#define YT921X_ACT_UNK_ACTn_M(port) GENMASK(2 * (port) + 1, 2 * (port))
+#define YT921X_ACT_UNK_ACTn(port, x) ((x) << (2 * (port)))
+#define YT921X_ACT_UNK_ACTn_FORWARD(port) YT921X_ACT_UNK_ACTn(port, 0) /* flood */
+#define YT921X_ACT_UNK_ACTn_TRAP(port) YT921X_ACT_UNK_ACTn(port, 1) /* steer to CPU */
+#define YT921X_ACT_UNK_ACTn_DROP(port) YT921X_ACT_UNK_ACTn(port, 2) /* discard */
+/* NEVER use this action; see comments in the tag driver */
+#define YT921X_ACT_UNK_ACTn_COPY(port) YT921X_ACT_UNK_ACTn(port, 3) /* flood and copy */
+#define YT921X_FDB_HW_FLUSH 0x180958
+#define YT921X_FDB_HW_FLUSH_ON_LINKDOWN BIT(0)
+
+#define YT921X_VLANn_CTRL(vlan) (0x188000 + 8 * (vlan))
+#define YT921X_VLAN_CTRLb_UNTAG_PORTS_M GENMASK(18, 8)
+#define YT921X_VLAN_CTRLb_UNTAG_PORTS(x) FIELD_PREP(YT921X_VLAN_CTRLb_UNTAG_PORTS_M, (x))
+#define YT921X_VLAN_CTRLb_UNTAG_PORTn(port) BIT((port) + 8)
+#define YT921X_VLAN_CTRLb_STP_ID_M GENMASK(7, 4)
+#define YT921X_VLAN_CTRLb_STP_ID(x) FIELD_PREP(YT921X_VLAN_CTRLb_STP_ID_M, (x))
+#define YT921X_VLAN_CTRLb_SVLAN_EN BIT(3)
+#define YT921X_VLAN_CTRLab_FID_M GENMASK_ULL(34, 23)
+#define YT921X_VLAN_CTRLab_FID(x) FIELD_PREP(YT921X_VLAN_CTRLab_FID_M, (x))
+#define YT921X_VLAN_CTRLa_LEARN_DIS BIT(22)
+#define YT921X_VLAN_CTRLa_PRIO_EN BIT(21)
+#define YT921X_VLAN_CTRLa_PRIO_M GENMASK(20, 18)
+#define YT921X_VLAN_CTRLa_PRIO(x) FIELD_PREP(YT921X_VLAN_CTRLa_PRIO_M, (x))
+#define YT921X_VLAN_CTRLa_PORTS_M GENMASK(17, 7)
+#define YT921X_VLAN_CTRLa_PORTS(x) FIELD_PREP(YT921X_VLAN_CTRLa_PORTS_M, (x))
+#define YT921X_VLAN_CTRLa_PORTn(port) BIT((port) + 7)
+#define YT921X_VLAN_CTRLa_BYPASS_1X_AC BIT(6)
+#define YT921X_VLAN_CTRLa_METER_EN BIT(5)
+#define YT921X_VLAN_CTRLa_METER_ID_M GENMASK(4, 0)
+
+#define YT921X_TPID_IGRn(x) (0x210000 + 4 * (x)) /* [0, 3] */
+#define YT921X_TPID_IGR_TPID_M GENMASK(15, 0)
+#define YT921X_PORTn_IGR_TPID(port) (0x210010 + 4 * (port))
+#define YT921X_PORT_IGR_TPIDn_STAG_M GENMASK(7, 4)
+#define YT921X_PORT_IGR_TPIDn_STAG(x) BIT((x) + 4)
+#define YT921X_PORT_IGR_TPIDn_CTAG_M GENMASK(3, 0)
+#define YT921X_PORT_IGR_TPIDn_CTAG(x) BIT(x)
+#define YT921X_LAG_HASH 0x210090
+#define YT921X_LAG_HASH_L4_SPORT BIT(7)
+#define YT921X_LAG_HASH_L4_DPORT BIT(6)
+#define YT921X_LAG_HASH_IP_PROTO BIT(5)
+#define YT921X_LAG_HASH_IP_SRC BIT(4)
+#define YT921X_LAG_HASH_IP_DST BIT(3)
+#define YT921X_LAG_HASH_MAC_SA BIT(2)
+#define YT921X_LAG_HASH_MAC_DA BIT(1)
+#define YT921X_LAG_HASH_SRC_PORT BIT(0)
+
+#define YT921X_PORTn_VLAN_CTRL(port) (0x230010 + 4 * (port))
+#define YT921X_PORT_VLAN_CTRL_SVLAN_PRIO_EN BIT(31)
+#define YT921X_PORT_VLAN_CTRL_CVLAN_PRIO_EN BIT(30)
+#define YT921X_PORT_VLAN_CTRL_SVID_M GENMASK(29, 18)
+#define YT921X_PORT_VLAN_CTRL_SVID(x) FIELD_PREP(YT921X_PORT_VLAN_CTRL_SVID_M, (x))
+#define YT921X_PORT_VLAN_CTRL_CVID_M GENMASK(17, 6)
+#define YT921X_PORT_VLAN_CTRL_CVID(x) FIELD_PREP(YT921X_PORT_VLAN_CTRL_CVID_M, (x))
+#define YT921X_PORT_VLAN_CTRL_SVLAN_PRIO_M GENMASK(5, 3)
+#define YT921X_PORT_VLAN_CTRL_CVLAN_PRIO_M GENMASK(2, 0)
+#define YT921X_PORTn_VLAN_CTRL1(port) (0x230080 + 4 * (port))
+#define YT921X_PORT_VLAN_CTRL1_VLAN_RANGE_EN BIT(8)
+#define YT921X_PORT_VLAN_CTRL1_VLAN_RANGE_PROFILE_ID_M GENMASK(7, 4)
+#define YT921X_PORT_VLAN_CTRL1_SVLAN_DROP_TAGGED BIT(3)
+#define YT921X_PORT_VLAN_CTRL1_SVLAN_DROP_UNTAGGED BIT(2)
+#define YT921X_PORT_VLAN_CTRL1_CVLAN_DROP_TAGGED BIT(1)
+#define YT921X_PORT_VLAN_CTRL1_CVLAN_DROP_UNTAGGED BIT(0)
+
+#define YT921X_EDATA_EXTMODE 0xfb
+#define YT921X_EDATA_LEN 0x100
+
+#define YT921X_FDB_NUM 4096
+
+enum yt921x_fdb_entry_status {
+ YT921X_FDB_ENTRY_STATUS_INVALID = 0,
+ YT921X_FDB_ENTRY_STATUS_MIN_TIME = 1,
+ YT921X_FDB_ENTRY_STATUS_MOVE_AGING_MAX_TIME = 3,
+ YT921X_FDB_ENTRY_STATUS_MAX_TIME = 5,
+ YT921X_FDB_ENTRY_STATUS_PENDING = 6,
+ YT921X_FDB_ENTRY_STATUS_STATIC = 7,
+};
+
+#define YT921X_MSTI_NUM 16
+
+/* Custom meters only, not including dedicated port meters (11) */
+#define YT921X_METER_NUM 64
+
+#define YT921X_LAG_NUM 2
+#define YT921X_LAG_PORT_NUM 4
+
+#define YT921X_PRIO_NUM 8
+
+#define YT9215_MAJOR 0x9002
+#define YT9218_MAJOR 0x9001
+
+/* required for a hard reset */
+#define YT921X_RST_DELAY_US 10000
+
+#define YT921X_FRAME_SIZE_MAX 0x2400 /* 9216 */
+
+#define YT921X_TAG_LEN 8
+
+#define YT921X_ACL_BLK_NUM 48
+#define YT921X_UDF_NUM 16
+
+#define YT921X_LED_GROUP_NUM 3
+#define YT921X_LED_PORT_NUM 10
+
+/* 8 internal + 2 external + 1 mcu */
+#define YT921X_PORT_NUM 11
+
+#define YT921X_NAME "yt921x"
+
+struct yt921x_port {
+ unsigned char index;
+
+ /* SerDes in use */
+ bool serdes:1;
+ /* Link from in-band status (PHYLINK_PCS_NEG_INBAND) */
+ bool inband:1;
+ /* BR_HAIRPIN_MODE */
+ bool hairpin:1;
+ /* BR_ISOLATED */
+ bool isolated:1;
+
+ unsigned short acl_cnt;
+
+#if IS_ENABLED(CONFIG_NET_DSA_YT921X_LEDS)
+ unsigned char led_duty;
+ unsigned short led_cycle;
+
+ unsigned char led_duty_mask;
+ unsigned char led_cycle_mask;
+
+ struct yt921x_led *leds[YT921X_LED_GROUP_NUM];
+#endif
+
+ struct yt921x_mib *mib;
+ struct phylink_pcs pcs;
+};
+
+#define pcs_to_yt921x_port(_pcs) container_of((_pcs), struct yt921x_port, pcs)
+
+struct yt921x_reg_ops {
+ int (*read)(void *context, u32 reg, u32 *valp);
+ int (*write)(void *context, u32 reg, u32 val);
+};
+
+struct yt921x_info {
+ const char *name;
+ u16 major;
+ /* Unknown, seems to be plain enumeration */
+ u8 mode;
+ u8 extmode;
+ /* Ports with integral GbE PHYs, not including MCU Port 10 */
+ u16 internal_mask;
+ /* Note: xmii_mask and serdes_mask may overlap */
+ u16 xmii_mask;
+ u16 serdes_mask;
+};
+
+static inline u16 yt921x_info_ports_mask(const struct yt921x_info *info)
+{
+ return info->internal_mask | info->xmii_mask | info->serdes_mask;
+}
+
+struct yt921x_priv {
+ struct dsa_switch ds;
+
+ const struct yt921x_info *info;
+ unsigned int meter_slot_ns;
+ unsigned int port_shape_slot_ns;
+ /* cache of dsa_cpu_ports(ds) */
+ u16 cpu_ports_mask;
+ unsigned char cycle_ns;
+
+ /* protect the access to the switch registers */
+ struct mutex reg_lock;
+ const struct yt921x_reg_ops *reg_ops;
+ void *reg_ctx;
+
+ /* mdio master bus */
+ struct mii_bus *mbus_int;
+ struct mii_bus *mbus_ext;
+
+ struct yt921x_port ports[YT921X_PORT_NUM];
+
+ u16 eee_ports_mask;
+
+ DECLARE_BITMAP(meters_map, YT921X_METER_NUM);
+
+ u8 acl_masks[YT921X_ACL_BLK_NUM];
+ struct yt921x_acl_blk *acl_blks[YT921X_ACL_BLK_NUM];
+};
+
+#define dsa_to_yt921x_priv(_ds) container_of_const(_ds, struct yt921x_priv, ds)
+#define yt921x_port_to_priv(pp) \
+ container_of_const((pp), struct yt921x_priv, ports[(pp)->index])
+#define yt921x_priv_to_device(priv) ((priv)->ds.dev)
+
+static inline int ethtool_speed_to_yt921x(int speed)
+{
+ switch (speed) {
+ case SPEED_10:
+ return YT921X_SPEED_10;
+ case SPEED_100:
+ return YT921X_SPEED_100;
+ case SPEED_1000:
+ return YT921X_SPEED_1000;
+ case SPEED_2500:
+ return YT921X_SPEED_2500;
+ case SPEED_10000:
+ return YT921X_SPEED_10000;
+ default:
+ return YT921X_SPEED_INVALID;
+ }
+}
+
+static inline u32 ethaddr_hi4_to_u32(const unsigned char *addr)
+{
+ return (addr[0] << 24) | (addr[1] << 16) | (addr[2] << 8) | addr[3];
+}
+
+static inline u32 ethaddr_lo2_to_u32(const unsigned char *addr)
+{
+ return (addr[4] << 8) | addr[5];
+}
+
+#endif
diff --git a/drivers/net/dsa/motorcomm/leds.c b/drivers/net/dsa/motorcomm/leds.c
new file mode 100644
index 000000000000..08fbe86c3334
--- /dev/null
+++ b/drivers/net/dsa/motorcomm/leds.c
@@ -0,0 +1,655 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 David Yang
+ */
+
+#include <linux/uleds.h>
+
+#include "chip.h"
+#include "leds.h"
+#include "smi.h"
+
+#define to_yt921x_led(led_cdev) \
+ container_of_const((led_cdev), struct yt921x_led, cdev)
+#define to_yt921x_port(led) ((led)->port)
+
+static u32 yt921x_led_regaddr(struct yt921x_priv *priv, int port, int group)
+{
+ switch (group) {
+ case 0:
+ default:
+ return YT921X_LED0_PORTn(port);
+ case 1:
+ return YT921X_LED1_PORTn(port);
+ case 2:
+ return YT921X_LED2_PORTn(port);
+ }
+}
+
+static int
+yt921x_led_force_set(struct yt921x_priv *priv, int port, int group, bool on)
+{
+ struct yt921x_port *pp = &priv->ports[port];
+ u32 ctrl;
+ u32 mask;
+
+ /* No reversion - LED is sacrificed in case of IO error */
+ pp->led_duty_mask &= ~BIT(group);
+ pp->led_cycle_mask &= ~BIT(group);
+
+ mask = YT921X_LED2_PORT_FORCEn_M(group);
+ ctrl = on ? YT921X_LED2_PORT_FORCEn_ON(group) :
+ YT921X_LED2_PORT_FORCEn_OFF(group);
+ return yt921x_reg_update_bits(priv, YT921X_LED2_PORTn(port), mask,
+ ctrl);
+}
+
+static int
+yt921x_led_blink_select(const struct yt921x_priv *priv, unsigned long on,
+ unsigned long off, unsigned short *cyclep,
+ unsigned char *dutyp)
+{
+ static const unsigned char dutys[] = {
+ YT921X_LED_DUTY(1, 6),
+ YT921X_LED_DUTY(1, 4),
+ YT921X_LED_DUTY(1, 3),
+ YT921X_LED_DUTY(1, 2),
+ };
+ unsigned int cycle_upper;
+ unsigned int cycle_req;
+ unsigned int duty_req;
+ unsigned int cycle;
+ unsigned int duty;
+
+ cycle = YT921X_LED_BLINK_MAX;
+ cycle_upper = cycle * 11585 / 8192 + 1; /* M_SQRT2 * cycle */
+ if (check_add_overflow(on, off, &cycle_req) || cycle_req >= cycle_upper)
+ return -EOPNOTSUPP;
+
+ for (; cycle > YT921X_LED_BLINK_MIN; cycle_upper >>= 1, cycle >>= 1)
+ if (cycle_req >= cycle_upper >> 1)
+ break;
+ *cyclep = cycle;
+
+ duty_req = DIV_ROUND_CLOSEST(YT921X_LED_DUTY_DENOM *
+ (on > off ? off : on), cycle_req);
+ for (unsigned int i = ARRAY_SIZE(dutys) - 1;; i--)
+ if (i == 0 || duty_req >= (dutys[i - 1] + dutys[i]) / 2) {
+ duty = dutys[i];
+ break;
+ }
+ if (on > off)
+ duty = YT921X_LED_DUTY_DENOM - duty;
+ *dutyp = duty;
+
+ return 0;
+}
+
+static int
+yt921x_led_blink_set(struct yt921x_priv *priv, int port, int group,
+ unsigned long *onp, unsigned long *offp)
+{
+ struct yt921x_port *pp = &priv->ports[port];
+ unsigned short cycle;
+ unsigned char duty;
+ bool use_cycle;
+ u32 ctrl;
+ u32 mask;
+ u32 val;
+ int res;
+
+ if (!*onp && !*offp) {
+ cycle = YT921X_LED_BLINK_DEF;
+ duty = (pp->led_duty_mask & ~BIT(group)) ? pp->led_duty :
+ YT921X_LED_DUTY(1, 2);
+
+ use_cycle = false;
+ } else if (!*onp || !*offp) {
+ return yt921x_led_force_set(priv, port, group, *onp);
+ } else {
+ res = yt921x_led_blink_select(priv, *onp, *offp, &cycle, &duty);
+ if (res)
+ return res;
+
+ use_cycle = cycle < YT921X_LED_BLINK_DEF;
+
+ if (use_cycle && cycle != pp->led_cycle &&
+ (pp->led_cycle_mask & ~BIT(group)))
+ return -EOPNOTSUPP;
+ if (duty != pp->led_duty && (pp->led_duty_mask & ~BIT(group)))
+ return -EOPNOTSUPP;
+ }
+
+ pp->led_cycle_mask &= ~BIT(group);
+ pp->led_duty_mask &= ~BIT(group);
+
+ /* The chip seems to jam a while if changing duty directly */
+ res = yt921x_reg_read(priv, YT921X_LED2_PORTn(port), &val);
+ if (res)
+ return res;
+
+ ctrl = val & ~YT921X_LED2_PORT_FORCEn_M(group);
+ ctrl |= YT921X_LED2_PORT_FORCEn_OFF(group);
+ if (val != ctrl) {
+ res = yt921x_reg_write(priv, YT921X_LED2_PORTn(port), ctrl);
+ if (res)
+ return res;
+ }
+
+ mask = YT921X_LED1_PORT_BLINK_DUTY_M | YT921X_LED1_PORT_BLINK_DUTY_COMP;
+ switch (duty >= YT921X_LED_DUTY(1, 2) ? duty :
+ YT921X_LED_DUTY_DENOM - duty) {
+ default:
+ duty = YT921X_LED_DUTY(1, 2);
+ fallthrough;
+ case YT921X_LED_DUTY(1, 2):
+ ctrl = YT921X_LED1_PORT_BLINK_DUTY_1_2;
+ break;
+ case YT921X_LED_DUTY(2, 3):
+ ctrl = YT921X_LED1_PORT_BLINK_DUTY_2_3;
+ break;
+ case YT921X_LED_DUTY(3, 4):
+ ctrl = YT921X_LED1_PORT_BLINK_DUTY_3_4;
+ break;
+ case YT921X_LED_DUTY(5, 6):
+ ctrl = YT921X_LED1_PORT_BLINK_DUTY_5_6;
+ break;
+ }
+ if (duty < YT921X_LED_DUTY(1, 2))
+ ctrl |= YT921X_LED1_PORT_BLINK_DUTY_COMP;
+ if (use_cycle) {
+ mask |= YT921X_LED1_PORT_OTHER_BLINK_M;
+ ctrl |= YT921X_LED1_PORT_OTHER_BLINK(9 - __fls(cycle));
+ }
+ res = yt921x_reg_update_bits(priv, YT921X_LED1_PORTn(port), mask, ctrl);
+ if (res)
+ return res;
+
+ ctrl = val & ~(YT921X_LED2_PORT_FORCEn_M(group) |
+ YT921X_LED2_PORT_FORCE_BLINKn_M(group));
+ ctrl |= YT921X_LED2_PORT_FORCEn_BLINK(group);
+ if (use_cycle)
+ ctrl |= YT921X_LED2_PORT_FORCE_BLINKn_OTHER(group);
+ else
+ ctrl |= YT921X_LED2_PORT_FORCE_BLINKn(group, __fls(cycle) - 9);
+ res = yt921x_reg_write(priv, YT921X_LED2_PORTn(port), ctrl);
+ if (res)
+ return res;
+
+ if (use_cycle) {
+ pp->led_cycle_mask |= BIT(group);
+ pp->led_cycle = cycle;
+ }
+ pp->led_duty_mask |= BIT(group);
+ pp->led_duty = duty;
+
+ *onp = DIV_ROUND_CLOSEST(duty * cycle, YT921X_LED_DUTY_DENOM);
+ *offp = cycle - *onp;
+ return 0;
+}
+
+struct yt921x_led_trigger_map {
+ unsigned long flags;
+ u32 mask;
+};
+
+static const struct yt921x_led_trigger_map yt921x_led_trigger_maps[] = {
+ {BIT(TRIGGER_NETDEV_LINK),
+ YT921X_LEDx_PORT_ACT_DUPLEX_HALF | YT921X_LEDx_PORT_ACT_DUPLEX_FULL},
+ {BIT(TRIGGER_NETDEV_LINK_10), YT921X_LEDx_PORT_ACT_10M},
+ {BIT(TRIGGER_NETDEV_LINK_100), YT921X_LEDx_PORT_ACT_100M},
+ {BIT(TRIGGER_NETDEV_LINK_1000), YT921X_LEDx_PORT_ACT_1000M},
+ {BIT(TRIGGER_NETDEV_HALF_DUPLEX), YT921X_LEDx_PORT_ACT_DUPLEX_HALF},
+ {BIT(TRIGGER_NETDEV_FULL_DUPLEX), YT921X_LEDx_PORT_ACT_DUPLEX_FULL},
+ {BIT(TRIGGER_NETDEV_TX), YT921X_LEDx_PORT_ACT_TX_BLINK},
+ {BIT(TRIGGER_NETDEV_RX), YT921X_LEDx_PORT_ACT_RX_BLINK},
+ {BIT(TRIGGER_NETDEV_TX_ERR) | BIT(TRIGGER_NETDEV_RX_ERR),
+ YT921X_LEDx_PORT_ACT_COLLISION_BLINK},
+};
+
+static bool
+yt921x_led_trigger_is_supported(const struct yt921x_priv *priv, int port,
+ int group, unsigned long flags)
+{
+ if (!flags)
+ return true;
+
+ for (unsigned int i = 0; i < ARRAY_SIZE(yt921x_led_trigger_maps); i++) {
+ const struct yt921x_led_trigger_map *map = &yt921x_led_trigger_maps[i];
+
+ if ((flags & map->flags) == map->flags) {
+ flags &= ~map->flags;
+ if (!flags)
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static int
+yt921x_led_trigger_get(struct yt921x_priv *priv, int port, int group,
+ unsigned long *flagsp)
+{
+ u32 addr;
+ u32 val;
+ int res;
+
+ res = yt921x_reg_read(priv, YT921X_LED2_PORTn(port), &val);
+ if (res)
+ return res;
+
+ if ((val & YT921X_LED2_PORT_FORCEn_M(group)) !=
+ YT921X_LED2_PORT_FORCEn_DONTCARE(group)) {
+ *flagsp = 0;
+ return 0;
+ }
+
+ if (group != 2) {
+ addr = yt921x_led_regaddr(priv, port, group);
+ res = yt921x_reg_read(priv, addr, &val);
+ if (res)
+ return res;
+ }
+
+ *flagsp = 0;
+ for (unsigned int i = 0; i < ARRAY_SIZE(yt921x_led_trigger_maps); i++) {
+ const struct yt921x_led_trigger_map *map = &yt921x_led_trigger_maps[i];
+
+ if ((val & map->mask) == map->mask)
+ *flagsp |= map->flags;
+ }
+
+ return 0;
+}
+
+static int
+yt921x_led_trigger_set(struct yt921x_priv *priv, int port, int group,
+ unsigned long flags)
+{
+ struct yt921x_port *pp = &priv->ports[port];
+ u32 addr;
+ u32 ctrl;
+ u32 mask;
+ int res;
+
+ ctrl = 0;
+ for (unsigned int i = 0; i < ARRAY_SIZE(yt921x_led_trigger_maps); i++) {
+ const struct yt921x_led_trigger_map *map = &yt921x_led_trigger_maps[i];
+
+ if ((flags & map->flags) == map->flags) {
+ flags &= ~map->flags;
+ ctrl |= map->mask;
+ if (!flags)
+ break;
+ }
+ }
+ if (flags)
+ return -EOPNOTSUPP;
+
+ pp->led_duty_mask &= ~BIT(group);
+ pp->led_cycle_mask &= ~BIT(group);
+
+ mask = !group ? YT921X_LED0_PORT_ACT_M : YT921X_LEDx_PORT_ACT_M;
+ if (group == 2) {
+ mask |= YT921X_LED2_PORT_FORCEn_M(group);
+ ctrl |= YT921X_LED2_PORT_FORCEn_DONTCARE(group);
+ }
+ addr = yt921x_led_regaddr(priv, port, group);
+ res = yt921x_reg_update_bits(priv, addr, mask, ctrl);
+ if (res)
+ return res;
+
+ if (group != 2) {
+ mask = YT921X_LED2_PORT_FORCEn_M(group);
+ ctrl = YT921X_LED2_PORT_FORCEn_DONTCARE(group);
+ res = yt921x_reg_update_bits(priv, YT921X_LED2_PORTn(port),
+ mask, ctrl);
+ if (res)
+ return res;
+ }
+
+ return 0;
+}
+
+static int
+yt921x_cled_brightness_set_blocking(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct yt921x_led *led = to_yt921x_led(led_cdev);
+ struct yt921x_port *pp = to_yt921x_port(led);
+ struct yt921x_priv *priv = yt921x_port_to_priv(pp);
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_led_force_set(priv, pp->index, led->group, brightness);
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+static int
+yt921x_cled_blink_set(struct led_classdev *led_cdev, unsigned long *delay_on,
+ unsigned long *delay_off)
+{
+ struct yt921x_led *led = to_yt921x_led(led_cdev);
+ struct yt921x_port *pp = to_yt921x_port(led);
+ struct yt921x_priv *priv = yt921x_port_to_priv(pp);
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_led_blink_set(priv, pp->index, led->group, delay_on,
+ delay_off);
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+static struct device __maybe_unused *
+yt921x_cled_hw_control_get_device(struct led_classdev *led_cdev)
+{
+ struct yt921x_led *led = to_yt921x_led(led_cdev);
+ struct yt921x_port *pp = to_yt921x_port(led);
+ struct yt921x_priv *priv = yt921x_port_to_priv(pp);
+ struct dsa_port *dp;
+
+ dp = dsa_to_port(&priv->ds, pp->index);
+ if (!dp)
+ return NULL;
+
+ if (dsa_port_is_user(dp))
+ return !dp->user ? NULL : &dp->user->dev;
+ if (dsa_port_is_cpu(dp))
+ return !dp->conduit ? NULL : &dp->conduit->dev;
+
+ return NULL;
+}
+
+static int __maybe_unused
+yt921x_cled_hw_control_is_supported(struct led_classdev *led_cdev,
+ unsigned long flags)
+{
+ struct yt921x_led *led = to_yt921x_led(led_cdev);
+ struct yt921x_port *pp = to_yt921x_port(led);
+ struct yt921x_priv *priv = yt921x_port_to_priv(pp);
+
+ if (yt921x_led_trigger_is_supported(priv, pp->index, led->group, flags))
+ return 0;
+ return -EOPNOTSUPP;
+}
+
+static int __maybe_unused
+yt921x_cled_hw_control_get(struct led_classdev *led_cdev, unsigned long *flagsp)
+{
+ struct yt921x_led *led = to_yt921x_led(led_cdev);
+ struct yt921x_port *pp = to_yt921x_port(led);
+ struct yt921x_priv *priv = yt921x_port_to_priv(pp);
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_led_trigger_get(priv, pp->index, led->group, flagsp);
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+static int __maybe_unused
+yt921x_cled_hw_control_set(struct led_classdev *led_cdev, unsigned long flags)
+{
+ struct yt921x_led *led = to_yt921x_led(led_cdev);
+ struct yt921x_port *pp = to_yt921x_port(led);
+ struct yt921x_priv *priv = yt921x_port_to_priv(pp);
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_led_trigger_set(priv, pp->index, led->group, flags);
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+static int
+yt921x_led_setup(struct yt921x_priv *priv, int port,
+ struct fwnode_handle *fwnode)
+{
+ struct device *dev = yt921x_priv_to_device(priv);
+ struct yt921x_port *pp = &priv->ports[port];
+ struct led_init_data init_data;
+ struct led_classdev *led_cdev;
+ char name[LED_MAX_NAME_SIZE];
+ enum led_default_state state;
+ struct yt921x_led *led;
+ int polarity_orig;
+ bool force_high;
+ bool force_low;
+ u32 led2_orig;
+ int polarity;
+ u32 group;
+ u32 mask;
+ u32 ctrl;
+ u32 val;
+ bool on;
+ int res;
+ int ret;
+
+ res = fwnode_property_read_u32(fwnode, "reg", &group);
+ if (res) {
+ dev_err(dev, "Missing LED reg for %pfw\n", fwnode);
+ return res;
+ }
+ if (group >= YT921X_LED_GROUP_NUM) {
+ dev_err(dev, "Invalid LED reg %u for port %d\n", group, port);
+ return -EINVAL;
+ }
+ if (pp->leds[group]) {
+ res = -EEXIST;
+ goto err;
+ }
+
+ force_high = fwnode_property_read_bool(fwnode, "active-high");
+ force_low = fwnode_property_read_bool(fwnode, "active-low");
+ if (force_high && force_low) {
+ dev_err(dev, "Duplicate polarities for LED %02d:%02u\n",
+ port, group);
+ return -EINVAL;
+ }
+ polarity = force_high ? 1 : force_low ? -1 : 0;
+
+ led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
+ if (!led) {
+ res = -ENOMEM;
+ goto err;
+ }
+ pp->leds[group] = led;
+
+ led->port = pp;
+ led->group = group;
+
+ state = led_init_default_state_get(fwnode);
+
+ mutex_lock(&priv->reg_lock);
+
+ /* Inversion is internal - force on will give low logic.
+ * In the rest of the file, treat LEDs as if active-low.
+ */
+ polarity_orig = 0;
+ if (polarity) {
+ res = yt921x_reg_read(priv, YT921X_LED_PAR_INV, &val);
+ if (res)
+ goto err_inv;
+
+ mask = YT921X_LED_PAR_INV_INVnm(group, port);
+ ctrl = polarity > 0 ? val | mask : val & ~mask;
+ if (ctrl != val) {
+ res = yt921x_reg_write(priv, YT921X_LED_PAR_INV, ctrl);
+ if (res)
+ goto err_inv;
+
+ polarity_orig = -polarity;
+ }
+ }
+
+ led2_orig = U32_MAX;
+ res = yt921x_reg_read(priv, YT921X_LED2_PORTn(port), &val);
+ if (res)
+ goto err_led2;
+ mask = YT921X_LED2_PORT_FORCEn_M(group);
+
+ switch (state) {
+ case LEDS_DEFSTATE_OFF:
+ case LEDS_DEFSTATE_ON:
+ default:
+ on = state == LEDS_DEFSTATE_ON;
+ ctrl = (val & ~mask) |
+ (on ? YT921X_LED2_PORT_FORCEn_ON(group) :
+ YT921X_LED2_PORT_FORCEn_OFF(group));
+ if (ctrl != val) {
+ res = yt921x_reg_write(priv, YT921X_LED2_PORTn(port),
+ ctrl);
+ if (res)
+ goto err_led2;
+
+ led2_orig = val & mask;
+ }
+ break;
+ case LEDS_DEFSTATE_KEEP:
+ on = (val & mask) == YT921X_LED2_PORT_FORCEn_ON(group);
+ break;
+ }
+
+ mutex_unlock(&priv->reg_lock);
+
+ led_cdev = &led->cdev;
+ led_cdev->brightness = on;
+ led_cdev->max_brightness = 1;
+ led_cdev->flags = LED_RETAIN_AT_SHUTDOWN;
+ led_cdev->brightness_set_blocking = yt921x_cled_brightness_set_blocking;
+ led_cdev->blink_set = yt921x_cled_blink_set;
+#ifdef CONFIG_LEDS_TRIGGERS
+ led_cdev->hw_control_trigger = "netdev";
+ led_cdev->hw_control_get_device = yt921x_cled_hw_control_get_device;
+ led_cdev->hw_control_is_supported = yt921x_cled_hw_control_is_supported;
+ led_cdev->hw_control_get = yt921x_cled_hw_control_get;
+ led_cdev->hw_control_set = yt921x_cled_hw_control_set;
+#endif
+
+ snprintf(name, sizeof(name), YT921X_NAME "-%u:%02d:%02u",
+ priv->ds.index, port, group);
+ init_data = (typeof(init_data)){
+ .fwnode = fwnode,
+ .default_label = ":port",
+ .devicename = name,
+ .devname_mandatory = true,
+ };
+ res = devm_led_classdev_register_ext(dev, led_cdev, &init_data);
+ if (res)
+ goto revoke;
+
+ return 0;
+
+revoke:
+ mutex_lock(&priv->reg_lock);
+ if (led2_orig != U32_MAX) {
+ ret = yt921x_reg_update_bits(priv, YT921X_LED2_PORTn(port),
+ YT921X_LED2_PORT_FORCEn_M(group),
+ led2_orig);
+ if (ret)
+ dev_err(dev,
+ "Failed to restore %s for LED %02d:%02u: %d\n",
+ "LED2_PORT", port, group, ret);
+ }
+err_led2:
+ if (polarity_orig) {
+ ret = yt921x_reg_toggle_bits(priv, YT921X_LED_PAR_INV,
+ YT921X_LED_PAR_INV_INVnm(group, port),
+ polarity_orig > 0);
+ if (ret)
+ dev_err(dev,
+ "Failed to restore %s for LED %02d:%02u: %d\n",
+ "LED_PAR_INV", port, group, ret);
+ }
+err_inv:
+ mutex_unlock(&priv->reg_lock);
+
+ pp->leds[group] = NULL;
+ devm_kfree(dev, led);
+
+err:
+ dev_err(dev, "Failed to initialize LED %02d:%02u: %d\n",
+ port, group, res);
+ return res;
+}
+
+static void yt921x_leds_remove_port(struct yt921x_priv *priv, int port)
+{
+ struct device *dev = yt921x_priv_to_device(priv);
+ struct yt921x_port *pp = &priv->ports[port];
+
+ for (int group = 0; group < YT921X_LED_GROUP_NUM; group++) {
+ struct yt921x_led *led = pp->leds[group];
+
+ if (led) {
+ devm_led_classdev_unregister(dev, &led->cdev);
+ pp->leds[group] = NULL;
+ devm_kfree(dev, led);
+ }
+ }
+
+ pp->led_duty_mask = 0;
+ pp->led_cycle_mask = 0;
+}
+
+static int yt921x_leds_setup_port(struct yt921x_priv *priv, int port)
+{
+ struct device *dev = yt921x_priv_to_device(priv);
+ struct dsa_switch *ds = &priv->ds;
+ struct device_node *leds_np;
+ struct dsa_port *dp;
+
+ dp = dsa_to_port(ds, port);
+ leds_np = of_get_child_by_name(dp->dn, "leds");
+ if (!leds_np)
+ return 0;
+
+ if (port >= YT921X_LED_PORT_NUM) {
+ dev_err(dev, "Cannot configure LEDs for port %d\n", port);
+ of_node_put(leds_np);
+ return -EINVAL;
+ }
+
+ for_each_child_of_node_scoped(leds_np, led_np) {
+ yt921x_led_setup(priv, port, of_fwnode_handle(led_np));
+ /* Allow partial configuration: LEDs are optional */
+ }
+
+ of_node_put(leds_np);
+ return 0;
+}
+
+void yt921x_leds_remove(struct yt921x_priv *priv)
+{
+ for (int port = 0; port < YT921X_LED_PORT_NUM; port++)
+ yt921x_leds_remove_port(priv, port);
+}
+
+int yt921x_leds_setup(struct yt921x_priv *priv)
+{
+ struct dsa_switch *ds = &priv->ds;
+ struct dsa_port *dp;
+
+ /* LEDs are always enabled. There is no way to disable them altogether
+ * (as far as I know).
+ */
+
+ dsa_switch_for_each_port(dp, ds) {
+ int port = dp->index;
+
+ if (!dp->dn)
+ continue;
+
+ yt921x_leds_setup_port(priv, port);
+ /* Allow partial configuration: LEDs are optional */
+ }
+
+ return 0;
+}
diff --git a/drivers/net/dsa/motorcomm/leds.h b/drivers/net/dsa/motorcomm/leds.h
new file mode 100644
index 000000000000..11df479ee581
--- /dev/null
+++ b/drivers/net/dsa/motorcomm/leds.h
@@ -0,0 +1,118 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2026 David Yang
+ */
+
+#ifndef _YT_LEDS_H
+#define _YT_LEDS_H
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/kconfig.h>
+#include <linux/leds.h>
+
+#define YT921X_LED_CTRL 0xd0000
+#define YT921X_LED_CTRL_UNK BIT(21)
+#define YT921X_LED_CTRL_LOOPDETECT_BLINK_M GENMASK(20, 19) /* cycle = 512 * x ms */
+#define YT921X_LED_CTRL_LOOPDETECT_BLINK(x) FIELD_PREP(YT921X_LED_CTRL_LOOPDETECT_BLINK_M, (x))
+#define YT921X_LED_CTRL_PORT_NUM_M GENMASK(16, 13)
+#define YT921X_LED_CTRL_PORT_NUM(x) FIELD_PREP(YT921X_LED_CTRL_PORT_NUM_M, (x))
+#define YT921X_LED_CTRL_MODE_M GENMASK(1, 0)
+#define YT921X_LED_CTRL_MODE(x) FIELD_PREP(YT921X_LED_CTRL_MODE_M, (x))
+#define YT921X_LED_CTRL_MODE_PARALLEL YT921X_LED_CTRL_MODE(0)
+#define YT921X_LED_CTRL_MODE_SERIAL YT921X_LED_CTRL_MODE(2)
+#define YT921X_LED0_PORTn(port) (0xd0004 + 4 * (port))
+#define YT921X_LED0_PORT_ACT_M GENMASK(17, 0)
+#define YT921X_LED0_PORT_ACT_LINK_TRY_DIS BIT(17)
+#define YT921X_LED0_PORT_ACT_COLLISION_BLINK_INDI BIT(16)
+#define YT921X_LED1_PORTn(port) (0xd0040 + 4 * (port))
+#define YT921X_LED1_PORT_OTHER_BLINK_M GENMASK(31, 30) /* cycle = 512 >> x ms */
+#define YT921X_LED1_PORT_OTHER_BLINK(x) FIELD_PREP(YT921X_LED1_PORT_OTHER_BLINK_M, (x))
+#define YT921X_LED1_PORT_EEE_BLINK_M GENMASK(29, 28) /* cycle = 512 >> x ms */
+#define YT921X_LED1_PORT_EEE_BLINK(x) FIELD_PREP(YT921X_LED1_PORT_EEE_BLINK_M, (x))
+#define YT921X_LED1_PORT_BLINK_DUTY_COMP BIT(27)
+#define YT921X_LED1_PORT_BLINK_DUTY_M GENMASK(26, 25)
+#define YT921X_LED1_PORT_BLINK_DUTY(x) FIELD_PREP(YT921X_LED1_PORT_BLINK_DUTY_M, (x))
+#define YT921X_LED1_PORT_BLINK_DUTY_1_2 YT921X_LED1_PORT_BLINK_DUTY(0)
+#define YT921X_LED1_PORT_BLINK_DUTY_2_3 YT921X_LED1_PORT_BLINK_DUTY(1)
+#define YT921X_LED1_PORT_BLINK_DUTY_3_4 YT921X_LED1_PORT_BLINK_DUTY(2)
+#define YT921X_LED1_PORT_BLINK_DUTY_5_6 YT921X_LED1_PORT_BLINK_DUTY(3)
+#define YT921X_LED2_PORTn(port) (0xd0080 + 4 * (port))
+#define YT921X_LED2_PORT_FORCEn_M(grp) GENMASK(4 * (grp) + 19, 4 * (grp) + 18)
+#define YT921X_LED2_PORT_FORCEn(grp, x) ((x) << (4 * (grp) + 18))
+#define YT921X_LED2_PORT_FORCEn_DONTCARE(grp) YT921X_LED2_PORT_FORCEn(grp, 0)
+#define YT921X_LED2_PORT_FORCEn_BLINK(grp) YT921X_LED2_PORT_FORCEn(grp, 1)
+#define YT921X_LED2_PORT_FORCEn_ON(grp) YT921X_LED2_PORT_FORCEn(grp, 2)
+#define YT921X_LED2_PORT_FORCEn_OFF(grp) YT921X_LED2_PORT_FORCEn(grp, 3)
+#define YT921X_LED2_PORT_FORCE_BLINKn_M(grp) GENMASK(4 * (grp) + 17, 4 * (grp) + 16) /* cycle = 512 << x ms */
+#define YT921X_LED2_PORT_FORCE_BLINKn(grp, x) ((x) << (4 * (grp) + 16))
+#define YT921X_LED2_PORT_FORCE_BLINKn_OTHER(grp) YT921X_LED2_PORT_FORCE_BLINKn(grp, 3)
+#define YT921X_LEDx_PORT_ACT_M GENMASK(15, 0)
+#define YT921X_LEDx_PORT_ACT_EEE_BLINK BIT(15)
+#define YT921X_LEDx_PORT_ACT_LOOPDETECT_BLINK BIT(14)
+#define YT921X_LEDx_PORT_ACT_ACTIVE_BLINK BIT(13)
+#define YT921X_LEDx_PORT_ACT_DUPLEX_FULL BIT(12)
+#define YT921X_LEDx_PORT_ACT_DUPLEX_HALF BIT(11)
+#define YT921X_LEDx_PORT_ACT_TX_BLINK BIT(10)
+#define YT921X_LEDx_PORT_ACT_RX_BLINK BIT(9)
+#define YT921X_LEDx_PORT_ACT_TX BIT(8)
+#define YT921X_LEDx_PORT_ACT_RX BIT(7)
+#define YT921X_LEDx_PORT_ACT_1000M BIT(6)
+#define YT921X_LEDx_PORT_ACT_100M BIT(5)
+#define YT921X_LEDx_PORT_ACT_10M BIT(4)
+#define YT921X_LEDx_PORT_ACT_COLLISION_BLINK BIT(3)
+#define YT921X_LEDx_PORT_ACT_1000M_BLINK BIT(2)
+#define YT921X_LEDx_PORT_ACT_100M_BLINK BIT(1)
+#define YT921X_LEDx_PORT_ACT_10M_BLINK BIT(0)
+#define YT921X_LED_SER_CTRL 0xd0100
+#define YT921X_LED_SER_CTRL_UNK GENMASK(25, 24) /* delay? */
+#define YT921X_LED_SER_CTRL_ACTIVE_LOW BIT(4)
+#define YT921X_LED_SER_CTRL_GRP_NUM_M GENMASK(1, 0) /* #grp - 1 */
+#define YT921X_LED_SER_CTRL_GRP_NUM(x) FIELD_PREP(YT921X_LED_SER_CTRL_GRP_NUM_M, (x))
+#define YT921X_LED_SER_MAPnm(grp, port) (0xd0104 + 8 * (2 - (grp)) + 4 * ((port) / 5))
+#define YT921X_LED_SER_MAP_DSTn_PORT_M(port) GENMASK(6 * ((port) % 5) + 5, 6 * ((port) % 5) + 2)
+#define YT921X_LED_SER_MAP_DSTn_PORT(port, x) ((x) << (6 * ((port) % 5) + 2))
+#define YT921X_LED_SER_MAP_DSTn_LED_M(port) GENMASK(6 * ((port) % 5) + 1, 6 * ((port) % 5))
+#define YT921X_LED_SER_MAP_DSTn_LED(port, x) ((x) << (6 * ((port) % 5)))
+#define YT921X_LED_PAR_PORTS 0xd01c4
+#define YT921X_LED_PAR_INV 0xd01c8
+#define YT921X_LED_PAR_INV_INVnm(grp, port) BIT(10 * (grp) + (port))
+#define YT921X_LED_PAR_MAPn(port) (0xd01d0 + 4 * (port))
+#define YT921X_LED_PAR_MAP_DSTn_PORT_M(grp) GENMASK(6 * (grp) + 5, 6 * (grp) + 2)
+#define YT921X_LED_PAR_MAP_DSTn_PORT(grp, x) ((x) << (6 * (grp) + 2))
+#define YT921X_LED_PAR_MAP_DSTn_LED_M(grp) GENMASK(6 * (grp) + 1, 6 * (grp))
+#define YT921X_LED_PAR_MAP_DSTn_LED(grp, x) ((x) << (6 * (grp)))
+
+#define YT921X_LED_BLINK_MIN 64
+#define YT921X_LED_BLINK_DEF 512
+#define YT921X_LED_BLINK_MAX 2048
+
+/* 2 * lcm(2, 3, 4, 6) */
+#define YT921X_LED_DUTY_DENOM 24
+#define YT921X_LED_DUTY(nom, denom) (YT921X_LED_DUTY_DENOM * (nom) / (denom))
+
+struct yt921x_priv;
+
+struct yt921x_led {
+ struct led_classdev cdev;
+ struct yt921x_port *port;
+ unsigned char group;
+};
+
+#if IS_ENABLED(CONFIG_NET_DSA_YT921X_LEDS)
+
+void yt921x_leds_remove(struct yt921x_priv *priv);
+int yt921x_leds_setup(struct yt921x_priv *priv);
+
+#else
+
+static inline void yt921x_leds_remove(struct yt921x_priv *priv) {}
+
+static inline int yt921x_leds_setup(struct yt921x_priv *priv)
+{
+ return 0;
+}
+
+#endif
+
+#endif
diff --git a/drivers/net/dsa/motorcomm/mdio_bus.c b/drivers/net/dsa/motorcomm/mdio_bus.c
new file mode 100644
index 000000000000..dd6c52f37bbf
--- /dev/null
+++ b/drivers/net/dsa/motorcomm/mdio_bus.c
@@ -0,0 +1,299 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 David Yang
+ */
+
+#include <linux/of_mdio.h>
+
+#include "chip.h"
+#include "mdio_bus.h"
+#include "smi.h"
+
+static int yt921x_intif_wait(struct yt921x_priv *priv)
+{
+ u32 val = 0;
+
+ return yt921x_reg_wait(priv, YT921X_INT_MBUS_OP, YT921X_MBUS_OP_START,
+ &val);
+}
+
+int yt921x_intif_read(struct yt921x_priv *priv, int port, int reg, u16 *valp)
+{
+ struct device *dev = yt921x_priv_to_device(priv);
+ u32 mask;
+ u32 ctrl;
+ u32 val;
+ int res;
+
+ res = yt921x_intif_wait(priv);
+ if (res)
+ return res;
+
+ mask = YT921X_MBUS_CTRL_PORT_M | YT921X_MBUS_CTRL_REG_M |
+ YT921X_MBUS_CTRL_OP_M;
+ ctrl = YT921X_MBUS_CTRL_PORT(port) | YT921X_MBUS_CTRL_REG(reg) |
+ YT921X_MBUS_CTRL_READ;
+ res = yt921x_reg_update_bits(priv, YT921X_INT_MBUS_CTRL, mask, ctrl);
+ if (res)
+ return res;
+ res = yt921x_reg_write(priv, YT921X_INT_MBUS_OP, YT921X_MBUS_OP_START);
+ if (res)
+ return res;
+
+ res = yt921x_intif_wait(priv);
+ if (res)
+ return res;
+ res = yt921x_reg_read(priv, YT921X_INT_MBUS_DIN, &val);
+ if (res)
+ return res;
+
+ if ((u16)val != val)
+ dev_info(dev,
+ "%s: port %d, reg 0x%x: Expected u16, got 0x%08x\n",
+ __func__, port, reg, val);
+ *valp = (u16)val;
+ return 0;
+}
+
+int yt921x_intif_write(struct yt921x_priv *priv, int port, int reg, u16 val)
+{
+ u32 mask;
+ u32 ctrl;
+ int res;
+
+ res = yt921x_intif_wait(priv);
+ if (res)
+ return res;
+
+ mask = YT921X_MBUS_CTRL_PORT_M | YT921X_MBUS_CTRL_REG_M |
+ YT921X_MBUS_CTRL_OP_M;
+ ctrl = YT921X_MBUS_CTRL_PORT(port) | YT921X_MBUS_CTRL_REG(reg) |
+ YT921X_MBUS_CTRL_WRITE;
+ res = yt921x_reg_update_bits(priv, YT921X_INT_MBUS_CTRL, mask, ctrl);
+ if (res)
+ return res;
+ res = yt921x_reg_write(priv, YT921X_INT_MBUS_DOUT, val);
+ if (res)
+ return res;
+ res = yt921x_reg_write(priv, YT921X_INT_MBUS_OP, YT921X_MBUS_OP_START);
+ if (res)
+ return res;
+
+ return yt921x_intif_wait(priv);
+}
+
+int
+yt921x_intif_modify_changed(struct yt921x_priv *priv, int port, int reg,
+ u16 mask, u16 val)
+{
+ int res;
+ u16 v;
+ u16 u;
+
+ res = yt921x_intif_read(priv, port, reg, &v);
+ if (res)
+ return res;
+
+ u = v;
+ u &= ~mask;
+ u |= val;
+ if (u == v)
+ return 0;
+
+ res = yt921x_intif_write(priv, port, reg, u);
+ if (res)
+ return res;
+
+ return 1;
+}
+
+static int yt921x_mbus_int_read(struct mii_bus *mbus, int port, int reg)
+{
+ struct yt921x_priv *priv = mbus->priv;
+ u16 val;
+ int res;
+
+ if (port >= YT921X_PORT_NUM)
+ return U16_MAX;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_intif_read(priv, port, reg, &val);
+ mutex_unlock(&priv->reg_lock);
+
+ if (res)
+ return res;
+ return val;
+}
+
+static int
+yt921x_mbus_int_write(struct mii_bus *mbus, int port, int reg, u16 data)
+{
+ struct yt921x_priv *priv = mbus->priv;
+ int res;
+
+ if (port >= YT921X_PORT_NUM)
+ return -ENODEV;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_intif_write(priv, port, reg, data);
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+int yt921x_mbus_int_init(struct yt921x_priv *priv, struct device_node *mnp)
+{
+ struct device *dev = yt921x_priv_to_device(priv);
+ struct mii_bus *mbus;
+ int res;
+
+ mbus = devm_mdiobus_alloc(dev);
+ if (!mbus)
+ return -ENOMEM;
+
+ mbus->name = "YT921x internal MDIO bus";
+ snprintf(mbus->id, MII_BUS_ID_SIZE, "%s", dev_name(dev));
+ mbus->priv = priv;
+ mbus->read = yt921x_mbus_int_read;
+ mbus->write = yt921x_mbus_int_write;
+ mbus->parent = dev;
+ mbus->phy_mask = (u32)~GENMASK(YT921X_PORT_NUM - 1, 0);
+
+ res = devm_of_mdiobus_register(dev, mbus, mnp);
+ if (res)
+ return res;
+
+ priv->mbus_int = mbus;
+
+ return 0;
+}
+
+static int yt921x_extif_wait(struct yt921x_priv *priv)
+{
+ u32 val = 0;
+
+ return yt921x_reg_wait(priv, YT921X_EXT_MBUS_OP, YT921X_MBUS_OP_START,
+ &val);
+}
+
+static int
+yt921x_extif_read(struct yt921x_priv *priv, int port, int reg, u16 *valp)
+{
+ struct device *dev = yt921x_priv_to_device(priv);
+ u32 mask;
+ u32 ctrl;
+ u32 val;
+ int res;
+
+ res = yt921x_extif_wait(priv);
+ if (res)
+ return res;
+
+ mask = YT921X_MBUS_CTRL_PORT_M | YT921X_MBUS_CTRL_REG_M |
+ YT921X_MBUS_CTRL_TYPE_M | YT921X_MBUS_CTRL_OP_M;
+ ctrl = YT921X_MBUS_CTRL_PORT(port) | YT921X_MBUS_CTRL_REG(reg) |
+ YT921X_MBUS_CTRL_TYPE_C22 | YT921X_MBUS_CTRL_READ;
+ res = yt921x_reg_update_bits(priv, YT921X_EXT_MBUS_CTRL, mask, ctrl);
+ if (res)
+ return res;
+ res = yt921x_reg_write(priv, YT921X_EXT_MBUS_OP, YT921X_MBUS_OP_START);
+ if (res)
+ return res;
+
+ res = yt921x_extif_wait(priv);
+ if (res)
+ return res;
+ res = yt921x_reg_read(priv, YT921X_EXT_MBUS_DIN, &val);
+ if (res)
+ return res;
+
+ if ((u16)val != val)
+ dev_info(dev,
+ "%s: port %d, reg 0x%x: Expected u16, got 0x%08x\n",
+ __func__, port, reg, val);
+ *valp = (u16)val;
+ return 0;
+}
+
+static int
+yt921x_extif_write(struct yt921x_priv *priv, int port, int reg, u16 val)
+{
+ u32 mask;
+ u32 ctrl;
+ int res;
+
+ res = yt921x_extif_wait(priv);
+ if (res)
+ return res;
+
+ mask = YT921X_MBUS_CTRL_PORT_M | YT921X_MBUS_CTRL_REG_M |
+ YT921X_MBUS_CTRL_TYPE_M | YT921X_MBUS_CTRL_OP_M;
+ ctrl = YT921X_MBUS_CTRL_PORT(port) | YT921X_MBUS_CTRL_REG(reg) |
+ YT921X_MBUS_CTRL_TYPE_C22 | YT921X_MBUS_CTRL_WRITE;
+ res = yt921x_reg_update_bits(priv, YT921X_EXT_MBUS_CTRL, mask, ctrl);
+ if (res)
+ return res;
+ res = yt921x_reg_write(priv, YT921X_EXT_MBUS_DOUT, val);
+ if (res)
+ return res;
+ res = yt921x_reg_write(priv, YT921X_EXT_MBUS_OP, YT921X_MBUS_OP_START);
+ if (res)
+ return res;
+
+ return yt921x_extif_wait(priv);
+}
+
+static int yt921x_mbus_ext_read(struct mii_bus *mbus, int port, int reg)
+{
+ struct yt921x_priv *priv = mbus->priv;
+ u16 val;
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_extif_read(priv, port, reg, &val);
+ mutex_unlock(&priv->reg_lock);
+
+ if (res)
+ return res;
+ return val;
+}
+
+static int
+yt921x_mbus_ext_write(struct mii_bus *mbus, int port, int reg, u16 data)
+{
+ struct yt921x_priv *priv = mbus->priv;
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_extif_write(priv, port, reg, data);
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+int yt921x_mbus_ext_init(struct yt921x_priv *priv, struct device_node *mnp)
+{
+ struct device *dev = yt921x_priv_to_device(priv);
+ struct mii_bus *mbus;
+ int res;
+
+ mbus = devm_mdiobus_alloc(dev);
+ if (!mbus)
+ return -ENOMEM;
+
+ mbus->name = "YT921x external MDIO bus";
+ snprintf(mbus->id, MII_BUS_ID_SIZE, "%s@ext", dev_name(dev));
+ mbus->priv = priv;
+ /* TODO: c45? */
+ mbus->read = yt921x_mbus_ext_read;
+ mbus->write = yt921x_mbus_ext_write;
+ mbus->parent = dev;
+
+ res = devm_of_mdiobus_register(dev, mbus, mnp);
+ if (res)
+ return res;
+
+ priv->mbus_ext = mbus;
+
+ return 0;
+}
diff --git a/drivers/net/dsa/motorcomm/mdio_bus.h b/drivers/net/dsa/motorcomm/mdio_bus.h
new file mode 100644
index 000000000000..e79b725d435b
--- /dev/null
+++ b/drivers/net/dsa/motorcomm/mdio_bus.h
@@ -0,0 +1,54 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2026 David Yang
+ */
+
+#ifndef _YT_MDIO_BUS_H
+#define _YT_MDIO_BUS_H
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/of.h>
+
+#define YT921X_EXT_MBUS_OP 0x6a000
+#define YT921X_INT_MBUS_OP 0xf0000
+#define YT921X_MBUS_OP_START BIT(0)
+#define YT921X_EXT_MBUS_CTRL 0x6a004
+#define YT921X_INT_MBUS_CTRL 0xf0004
+#define YT921X_MBUS_CTRL_PORT_M GENMASK(25, 21)
+#define YT921X_MBUS_CTRL_PORT(x) FIELD_PREP(YT921X_MBUS_CTRL_PORT_M, (x))
+#define YT921X_MBUS_CTRL_REG_M GENMASK(20, 16)
+#define YT921X_MBUS_CTRL_REG(x) FIELD_PREP(YT921X_MBUS_CTRL_REG_M, (x))
+#define YT921X_MBUS_CTRL_TYPE_M GENMASK(11, 8) /* wild guess */
+#define YT921X_MBUS_CTRL_TYPE(x) FIELD_PREP(YT921X_MBUS_CTRL_TYPE_M, (x))
+#define YT921X_MBUS_CTRL_TYPE_C22 YT921X_MBUS_CTRL_TYPE(4)
+#define YT921X_MBUS_CTRL_OP_M GENMASK(3, 2) /* wild guess */
+#define YT921X_MBUS_CTRL_OP(x) FIELD_PREP(YT921X_MBUS_CTRL_OP_M, (x))
+#define YT921X_MBUS_CTRL_WRITE YT921X_MBUS_CTRL_OP(1)
+#define YT921X_MBUS_CTRL_READ YT921X_MBUS_CTRL_OP(2)
+#define YT921X_EXT_MBUS_DOUT 0x6a008
+#define YT921X_INT_MBUS_DOUT 0xf0008
+#define YT921X_EXT_MBUS_DIN 0x6a00c
+#define YT921X_INT_MBUS_DIN 0xf000c
+
+struct yt921x_priv;
+
+int yt921x_intif_read(struct yt921x_priv *priv, int port, int reg, u16 *valp);
+int yt921x_intif_write(struct yt921x_priv *priv, int port, int reg, u16 val);
+int yt921x_intif_modify_changed(struct yt921x_priv *priv, int port, int reg,
+ u16 mask, u16 val);
+
+static inline int
+yt921x_intif_modify(struct yt921x_priv *priv, int port, int reg, u16 mask,
+ u16 val)
+{
+ int res;
+
+ res = yt921x_intif_modify_changed(priv, port, reg, mask, val);
+ return res >= 0 ? 0 : res;
+}
+
+int yt921x_mbus_int_init(struct yt921x_priv *priv, struct device_node *mnp);
+int yt921x_mbus_ext_init(struct yt921x_priv *priv, struct device_node *mnp);
+
+#endif
diff --git a/drivers/net/dsa/motorcomm/mib.c b/drivers/net/dsa/motorcomm/mib.c
new file mode 100644
index 000000000000..6b170fc6e187
--- /dev/null
+++ b/drivers/net/dsa/motorcomm/mib.c
@@ -0,0 +1,431 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 David Yang
+ */
+
+#include "chip.h"
+#include "mib.h"
+#include "smi.h"
+
+struct yt921x_mib_desc {
+ unsigned int size;
+ unsigned int offset;
+ const char *name;
+};
+
+#define MIB_DESC(_size, _offset, _name) \
+ {_size, _offset, _name}
+
+/* Must agree with yt921x_mib_stats
+ *
+ * Unstructured fields (name != NULL) will appear in get_ethtool_stats(),
+ * structured go to their *_stats() methods, but we need their sizes and offsets
+ * to perform 32bit MIB overflow wraparound.
+ */
+static const struct yt921x_mib_desc yt921x_mib_descs[] = {
+ MIB_DESC(1, YT921X_MIB_DATA_RX_BROADCAST, NULL),
+ MIB_DESC(1, YT921X_MIB_DATA_RX_PAUSE, NULL),
+ MIB_DESC(1, YT921X_MIB_DATA_RX_MULTICAST, NULL),
+ MIB_DESC(1, YT921X_MIB_DATA_RX_CRC_ERR, NULL),
+
+ MIB_DESC(1, YT921X_MIB_DATA_RX_ALIGN_ERR, NULL),
+ MIB_DESC(1, YT921X_MIB_DATA_RX_UNDERSIZE_ERR, NULL),
+ MIB_DESC(1, YT921X_MIB_DATA_RX_FRAG_ERR, NULL),
+ MIB_DESC(1, YT921X_MIB_DATA_RX_PKT_SZ_64, NULL),
+
+ MIB_DESC(1, YT921X_MIB_DATA_RX_PKT_SZ_65_TO_127, NULL),
+ MIB_DESC(1, YT921X_MIB_DATA_RX_PKT_SZ_128_TO_255, NULL),
+ MIB_DESC(1, YT921X_MIB_DATA_RX_PKT_SZ_256_TO_511, NULL),
+ MIB_DESC(1, YT921X_MIB_DATA_RX_PKT_SZ_512_TO_1023, NULL),
+
+ MIB_DESC(1, YT921X_MIB_DATA_RX_PKT_SZ_1024_TO_1518, NULL),
+ MIB_DESC(1, YT921X_MIB_DATA_RX_PKT_SZ_1519_TO_MAX, NULL),
+ MIB_DESC(2, YT921X_MIB_DATA_RX_GOOD_BYTES, NULL),
+
+ MIB_DESC(2, YT921X_MIB_DATA_RX_BAD_BYTES, "RxBadBytes"),
+ MIB_DESC(1, YT921X_MIB_DATA_RX_OVERSIZE_ERR, NULL),
+
+ MIB_DESC(1, YT921X_MIB_DATA_RX_DROPPED, NULL),
+ MIB_DESC(1, YT921X_MIB_DATA_TX_BROADCAST, NULL),
+ MIB_DESC(1, YT921X_MIB_DATA_TX_PAUSE, NULL),
+ MIB_DESC(1, YT921X_MIB_DATA_TX_MULTICAST, NULL),
+
+ MIB_DESC(1, YT921X_MIB_DATA_TX_UNDERSIZE_ERR, NULL),
+ MIB_DESC(1, YT921X_MIB_DATA_TX_PKT_SZ_64, NULL),
+ MIB_DESC(1, YT921X_MIB_DATA_TX_PKT_SZ_65_TO_127, NULL),
+ MIB_DESC(1, YT921X_MIB_DATA_TX_PKT_SZ_128_TO_255, NULL),
+
+ MIB_DESC(1, YT921X_MIB_DATA_TX_PKT_SZ_256_TO_511, NULL),
+ MIB_DESC(1, YT921X_MIB_DATA_TX_PKT_SZ_512_TO_1023, NULL),
+ MIB_DESC(1, YT921X_MIB_DATA_TX_PKT_SZ_1024_TO_1518, NULL),
+ MIB_DESC(1, YT921X_MIB_DATA_TX_PKT_SZ_1519_TO_MAX, NULL),
+
+ MIB_DESC(2, YT921X_MIB_DATA_TX_GOOD_BYTES, NULL),
+ MIB_DESC(1, YT921X_MIB_DATA_TX_COLLISION, NULL),
+
+ MIB_DESC(1, YT921X_MIB_DATA_TX_EXCESSIVE_COLLISION, NULL),
+ MIB_DESC(1, YT921X_MIB_DATA_TX_MULTIPLE_COLLISION, NULL),
+ MIB_DESC(1, YT921X_MIB_DATA_TX_SINGLE_COLLISION, NULL),
+ MIB_DESC(1, YT921X_MIB_DATA_TX_PKT, NULL),
+
+ MIB_DESC(1, YT921X_MIB_DATA_TX_DEFERRED, NULL),
+ MIB_DESC(1, YT921X_MIB_DATA_TX_LATE_COLLISION, NULL),
+ MIB_DESC(1, YT921X_MIB_DATA_RX_OAM, "RxOAM"),
+ MIB_DESC(1, YT921X_MIB_DATA_TX_OAM, "TxOAM"),
+};
+
+static_assert(ARRAY_SIZE(yt921x_mib_descs) == YT921X_MIB_STAT_NUM);
+
+/* The interval should be small enough to avoid overflow of 32bit MIBs.
+ *
+ * Until we can read MIBs from stats64 call directly (i.e. sleep
+ * there), we have to poll stats more frequently then it is actually needed.
+ * For overflow protection, normally, 100 sec interval should have been OK.
+ */
+#define YT921X_STATS_INTERVAL_JIFFIES (3 * HZ)
+
+/* Read and handle overflow of 32bit MIBs. MIB buffer must be zeroed before. */
+static int yt921x_mib_read(struct yt921x_priv *priv, int port)
+{
+ struct device *dev = yt921x_priv_to_device(priv);
+ struct yt921x_port *pp = &priv->ports[port];
+ struct yt921x_mib *pm = pp->mib;
+ struct yt921x_mib_stats *mib;
+ u64 *buf = pm->data;
+ unsigned long flags;
+ u64 rx_frames;
+ u64 tx_frames;
+ int res = 0;
+
+ mib = &pm->stats;
+
+ for (size_t i = 0; i < ARRAY_SIZE(yt921x_mib_descs); i++) {
+ const struct yt921x_mib_desc *desc = &yt921x_mib_descs[i];
+ u32 reg = YT921X_MIBn_DATA0(port) + desc->offset;
+ u32 val0;
+ u64 val;
+
+ res = yt921x_reg_read(priv, reg, &val0);
+ if (res)
+ break;
+
+ if (desc->size <= 1) {
+ u64 old_val = buf[i];
+
+ val = (old_val & ~(u64)U32_MAX) | val0;
+ if (val < old_val)
+ val += 1ull << 32;
+ } else {
+ u32 val1;
+
+ res = yt921x_reg_read(priv, reg + 4, &val1);
+ if (res)
+ break;
+ val = ((u64)val1 << 32) | val0;
+ }
+
+ buf[i] = val;
+ }
+
+ flags = u64_stats_update_begin_irqsave(&pm->syncp);
+ for (size_t i = 0; i < ARRAY_SIZE(yt921x_mib_descs); i++)
+ u64_stats_set(&((u64_stats_t *)mib)[i], buf[i]);
+ rx_frames = u64_stats_read(&mib->rx_64byte) +
+ u64_stats_read(&mib->rx_65_127byte) +
+ u64_stats_read(&mib->rx_128_255byte) +
+ u64_stats_read(&mib->rx_256_511byte) +
+ u64_stats_read(&mib->rx_512_1023byte) +
+ u64_stats_read(&mib->rx_1024_1518byte) +
+ u64_stats_read(&mib->rx_jumbo);
+ tx_frames = u64_stats_read(&mib->tx_64byte) +
+ u64_stats_read(&mib->tx_65_127byte) +
+ u64_stats_read(&mib->tx_128_255byte) +
+ u64_stats_read(&mib->tx_256_511byte) +
+ u64_stats_read(&mib->tx_512_1023byte) +
+ u64_stats_read(&mib->tx_1024_1518byte) +
+ u64_stats_read(&mib->tx_jumbo);
+ u64_stats_set(&pm->rx_frames, rx_frames);
+ u64_stats_set(&pm->tx_frames, tx_frames);
+ u64_stats_update_end_irqrestore(&pm->syncp, flags);
+
+ if (res)
+ dev_err(dev, "Failed to %s port %d: %i\n", "read stats for",
+ port, res);
+ return res;
+}
+
+void yt921x_mib_poll(struct work_struct *work)
+{
+ struct yt921x_mib *pm = container_of_const(work, struct yt921x_mib,
+ work.work);
+ struct yt921x_port *pp = pm->port;
+ unsigned long delay = YT921X_STATS_INTERVAL_JIFFIES;
+ struct yt921x_priv *priv = yt921x_port_to_priv(pp);
+ int port = pp->index;
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_mib_read(priv, port);
+ mutex_unlock(&priv->reg_lock);
+ if (res)
+ delay *= 4;
+
+ schedule_delayed_work(&pm->work, delay);
+}
+
+void
+yt921x_dsa_get_strings(struct dsa_switch *ds, int port, u32 stringset,
+ uint8_t *data)
+{
+ if (stringset != ETH_SS_STATS)
+ return;
+
+ for (size_t i = 0; i < ARRAY_SIZE(yt921x_mib_descs); i++) {
+ const struct yt921x_mib_desc *desc = &yt921x_mib_descs[i];
+
+ if (desc->name)
+ ethtool_puts(&data, desc->name);
+ }
+}
+
+void
+yt921x_dsa_get_ethtool_stats(struct dsa_switch *ds, int port, uint64_t *data)
+{
+ struct yt921x_priv *priv = dsa_to_yt921x_priv(ds);
+ struct yt921x_port *pp = &priv->ports[port];
+ struct yt921x_mib *pm = pp->mib;
+ u64 *buf;
+ size_t j;
+
+ if (!pm)
+ return;
+ buf = pm->data;
+
+ mutex_lock(&priv->reg_lock);
+
+ yt921x_mib_read(priv, port);
+
+ j = 0;
+ for (size_t i = 0; i < ARRAY_SIZE(yt921x_mib_descs); i++) {
+ const struct yt921x_mib_desc *desc = &yt921x_mib_descs[i];
+
+ if (!desc->name)
+ continue;
+
+ data[j] = buf[i];
+ j++;
+ }
+
+ mutex_unlock(&priv->reg_lock);
+}
+
+int yt921x_dsa_get_sset_count(struct dsa_switch *ds, int port, int sset)
+{
+ int cnt = 0;
+
+ if (sset != ETH_SS_STATS)
+ return 0;
+
+ for (size_t i = 0; i < ARRAY_SIZE(yt921x_mib_descs); i++) {
+ const struct yt921x_mib_desc *desc = &yt921x_mib_descs[i];
+
+ if (desc->name)
+ cnt++;
+ }
+
+ return cnt;
+}
+
+void
+yt921x_dsa_get_eth_mac_stats(struct dsa_switch *ds, int port,
+ struct ethtool_eth_mac_stats *mac_stats)
+{
+ struct yt921x_priv *priv = dsa_to_yt921x_priv(ds);
+ struct yt921x_port *pp = &priv->ports[port];
+ struct yt921x_mib *pm = pp->mib;
+ struct yt921x_mib_stats *mib;
+
+ if (!pm)
+ return;
+ mib = &pm->stats;
+
+ mutex_lock(&priv->reg_lock);
+
+ yt921x_mib_read(priv, port);
+
+ mac_stats->FramesTransmittedOK = u64_stats_read(&pm->tx_frames);
+ mac_stats->SingleCollisionFrames = u64_stats_read(&mib->tx_single_collisions);
+ mac_stats->MultipleCollisionFrames = u64_stats_read(&mib->tx_multiple_collisions);
+ mac_stats->FramesReceivedOK = u64_stats_read(&pm->rx_frames);
+ mac_stats->FrameCheckSequenceErrors = u64_stats_read(&mib->rx_crc_errors);
+ mac_stats->AlignmentErrors = u64_stats_read(&mib->rx_alignment_errors);
+ mac_stats->OctetsTransmittedOK = u64_stats_read(&mib->tx_good_bytes);
+ mac_stats->FramesWithDeferredXmissions = u64_stats_read(&mib->tx_deferred);
+ mac_stats->LateCollisions = u64_stats_read(&mib->tx_late_collisions);
+ mac_stats->FramesAbortedDueToXSColls = u64_stats_read(&mib->tx_aborted_errors);
+ /* mac_stats->FramesLostDueToIntMACXmitError */
+ /* mac_stats->CarrierSenseErrors */
+ mac_stats->OctetsReceivedOK = u64_stats_read(&mib->rx_good_bytes);
+ /* mac_stats->FramesLostDueToIntMACRcvError */
+ mac_stats->MulticastFramesXmittedOK = u64_stats_read(&mib->tx_multicast);
+ mac_stats->BroadcastFramesXmittedOK = u64_stats_read(&mib->tx_broadcast);
+ /* mac_stats->FramesWithExcessiveDeferral */
+ mac_stats->MulticastFramesReceivedOK = u64_stats_read(&mib->rx_multicast);
+ mac_stats->BroadcastFramesReceivedOK = u64_stats_read(&mib->rx_broadcast);
+ /* mac_stats->InRangeLengthErrors */
+ /* mac_stats->OutOfRangeLengthField */
+ mac_stats->FrameTooLongErrors = u64_stats_read(&mib->rx_oversize_errors);
+
+ mutex_unlock(&priv->reg_lock);
+}
+
+void
+yt921x_dsa_get_eth_ctrl_stats(struct dsa_switch *ds, int port,
+ struct ethtool_eth_ctrl_stats *ctrl_stats)
+{
+ struct yt921x_priv *priv = dsa_to_yt921x_priv(ds);
+ struct yt921x_port *pp = &priv->ports[port];
+ struct yt921x_mib *pm = pp->mib;
+ struct yt921x_mib_stats *mib;
+
+ if (!pm)
+ return;
+ mib = &pm->stats;
+
+ mutex_lock(&priv->reg_lock);
+
+ yt921x_mib_read(priv, port);
+
+ ctrl_stats->MACControlFramesTransmitted = u64_stats_read(&mib->tx_pause);
+ ctrl_stats->MACControlFramesReceived = u64_stats_read(&mib->rx_pause);
+ /* ctrl_stats->UnsupportedOpcodesReceived */
+
+ mutex_unlock(&priv->reg_lock);
+}
+
+static const struct ethtool_rmon_hist_range yt921x_rmon_ranges[] = {
+ { 0, 64 },
+ { 65, 127 },
+ { 128, 255 },
+ { 256, 511 },
+ { 512, 1023 },
+ { 1024, 1518 },
+ { 1519, YT921X_FRAME_SIZE_MAX },
+ {}
+};
+
+void
+yt921x_dsa_get_rmon_stats(struct dsa_switch *ds, int port,
+ struct ethtool_rmon_stats *rmon_stats,
+ const struct ethtool_rmon_hist_range **ranges)
+{
+ struct yt921x_priv *priv = dsa_to_yt921x_priv(ds);
+ struct yt921x_port *pp = &priv->ports[port];
+ struct yt921x_mib *pm = pp->mib;
+ struct yt921x_mib_stats *mib;
+
+ if (!pm)
+ return;
+ mib = &pm->stats;
+
+ mutex_lock(&priv->reg_lock);
+
+ yt921x_mib_read(priv, port);
+
+ *ranges = yt921x_rmon_ranges;
+
+ rmon_stats->undersize_pkts = u64_stats_read(&mib->rx_undersize_errors);
+ rmon_stats->oversize_pkts = u64_stats_read(&mib->rx_oversize_errors);
+ rmon_stats->fragments = u64_stats_read(&mib->rx_alignment_errors);
+ /* rmon_stats->jabbers */
+
+ rmon_stats->hist[0] = u64_stats_read(&mib->rx_64byte);
+ rmon_stats->hist[1] = u64_stats_read(&mib->rx_65_127byte);
+ rmon_stats->hist[2] = u64_stats_read(&mib->rx_128_255byte);
+ rmon_stats->hist[3] = u64_stats_read(&mib->rx_256_511byte);
+ rmon_stats->hist[4] = u64_stats_read(&mib->rx_512_1023byte);
+ rmon_stats->hist[5] = u64_stats_read(&mib->rx_1024_1518byte);
+ rmon_stats->hist[6] = u64_stats_read(&mib->rx_jumbo);
+
+ rmon_stats->hist_tx[0] = u64_stats_read(&mib->tx_64byte);
+ rmon_stats->hist_tx[1] = u64_stats_read(&mib->tx_65_127byte);
+ rmon_stats->hist_tx[2] = u64_stats_read(&mib->tx_128_255byte);
+ rmon_stats->hist_tx[3] = u64_stats_read(&mib->tx_256_511byte);
+ rmon_stats->hist_tx[4] = u64_stats_read(&mib->tx_512_1023byte);
+ rmon_stats->hist_tx[5] = u64_stats_read(&mib->tx_1024_1518byte);
+ rmon_stats->hist_tx[6] = u64_stats_read(&mib->tx_jumbo);
+
+ mutex_unlock(&priv->reg_lock);
+}
+
+void
+yt921x_dsa_get_stats64(struct dsa_switch *ds, int port,
+ struct rtnl_link_stats64 *stats)
+{
+ struct yt921x_priv *priv = dsa_to_yt921x_priv(ds);
+ struct yt921x_port *pp = &priv->ports[port];
+ struct yt921x_mib *pm = pp->mib;
+ struct yt921x_mib_stats *mib;
+ unsigned int start;
+
+ if (!pm)
+ return;
+ mib = &pm->stats;
+
+ do {
+ start = u64_stats_fetch_begin(&pm->syncp);
+
+ stats->rx_length_errors = u64_stats_read(&mib->rx_undersize_errors) +
+ u64_stats_read(&mib->rx_fragment_errors);
+ stats->rx_over_errors = u64_stats_read(&mib->rx_oversize_errors);
+ stats->rx_crc_errors = u64_stats_read(&mib->rx_crc_errors);
+ stats->rx_frame_errors = u64_stats_read(&mib->rx_alignment_errors);
+ /* stats->rx_fifo_errors */
+ /* stats->rx_missed_errors */
+
+ stats->tx_aborted_errors = u64_stats_read(&mib->tx_aborted_errors);
+ /* stats->tx_carrier_errors */
+ stats->tx_fifo_errors = u64_stats_read(&mib->tx_undersize_errors);
+ /* stats->tx_heartbeat_errors */
+ stats->tx_window_errors = u64_stats_read(&mib->tx_late_collisions);
+
+ stats->rx_packets = u64_stats_read(&pm->rx_frames);
+ stats->tx_packets = u64_stats_read(&pm->tx_frames);
+ stats->rx_bytes = u64_stats_read(&mib->rx_good_bytes) -
+ ETH_FCS_LEN * stats->rx_packets;
+ stats->tx_bytes = u64_stats_read(&mib->tx_good_bytes) -
+ ETH_FCS_LEN * stats->tx_packets;
+ stats->rx_dropped = u64_stats_read(&mib->rx_dropped);
+ /* stats->tx_dropped */
+ stats->multicast = u64_stats_read(&mib->rx_multicast);
+ stats->collisions = u64_stats_read(&mib->tx_collisions);
+ } while (u64_stats_fetch_retry(&pm->syncp, start));
+
+ stats->rx_errors = stats->rx_length_errors + stats->rx_over_errors +
+ stats->rx_crc_errors + stats->rx_frame_errors;
+ stats->tx_errors = stats->tx_aborted_errors + stats->tx_fifo_errors +
+ stats->tx_window_errors;
+}
+
+void
+yt921x_dsa_get_pause_stats(struct dsa_switch *ds, int port,
+ struct ethtool_pause_stats *pause_stats)
+{
+ struct yt921x_priv *priv = dsa_to_yt921x_priv(ds);
+ struct yt921x_port *pp = &priv->ports[port];
+ struct yt921x_mib *pm = pp->mib;
+ struct yt921x_mib_stats *mib;
+
+ if (!pm)
+ return;
+ mib = &pm->stats;
+
+ mutex_lock(&priv->reg_lock);
+
+ yt921x_mib_read(priv, port);
+
+ pause_stats->tx_pause_frames = u64_stats_read(&mib->tx_pause);
+ pause_stats->rx_pause_frames = u64_stats_read(&mib->rx_pause);
+
+ mutex_unlock(&priv->reg_lock);
+}
diff --git a/drivers/net/dsa/motorcomm/mib.h b/drivers/net/dsa/motorcomm/mib.h
new file mode 100644
index 000000000000..9c24a52d46cc
--- /dev/null
+++ b/drivers/net/dsa/motorcomm/mib.h
@@ -0,0 +1,167 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2026 David Yang
+ */
+
+#ifndef _YT_MIB_H
+#define _YT_MIB_H
+
+#include <linux/u64_stats_sync.h>
+
+#include <net/dsa.h>
+
+#define YT921X_MIB_CTRL 0xc0004
+#define YT921X_MIB_CTRL_CLEAN BIT(30)
+#define YT921X_MIB_CTRL_PORT_M GENMASK(6, 3)
+#define YT921X_MIB_CTRL_PORT(x) FIELD_PREP(YT921X_MIB_CTRL_PORT_M, (x))
+#define YT921X_MIB_CTRL_ONE_PORT BIT(1)
+#define YT921X_MIB_CTRL_ALL_PORT BIT(0)
+#define YT921X_MIBn_DATA0(port) (0xc0100 + 0x100 * (port))
+#define YT921X_MIBn_DATAm(port, x) (YT921X_MIBn_DATA0(port) + 4 * (x))
+#define YT921X_MIB_DATA_RX_BROADCAST 0x00
+#define YT921X_MIB_DATA_RX_PAUSE 0x04
+#define YT921X_MIB_DATA_RX_MULTICAST 0x08
+#define YT921X_MIB_DATA_RX_CRC_ERR 0x0c
+
+#define YT921X_MIB_DATA_RX_ALIGN_ERR 0x10
+#define YT921X_MIB_DATA_RX_UNDERSIZE_ERR 0x14
+#define YT921X_MIB_DATA_RX_FRAG_ERR 0x18
+#define YT921X_MIB_DATA_RX_PKT_SZ_64 0x1c
+
+#define YT921X_MIB_DATA_RX_PKT_SZ_65_TO_127 0x20
+#define YT921X_MIB_DATA_RX_PKT_SZ_128_TO_255 0x24
+#define YT921X_MIB_DATA_RX_PKT_SZ_256_TO_511 0x28
+#define YT921X_MIB_DATA_RX_PKT_SZ_512_TO_1023 0x2c
+
+#define YT921X_MIB_DATA_RX_PKT_SZ_1024_TO_1518 0x30
+#define YT921X_MIB_DATA_RX_PKT_SZ_1519_TO_MAX 0x34
+/* 0x38: unused */
+#define YT921X_MIB_DATA_RX_GOOD_BYTES 0x3c
+
+/* 0x40: 64 bytes */
+#define YT921X_MIB_DATA_RX_BAD_BYTES 0x44
+/* 0x48: 64 bytes */
+#define YT921X_MIB_DATA_RX_OVERSIZE_ERR 0x4c
+
+#define YT921X_MIB_DATA_RX_DROPPED 0x50
+#define YT921X_MIB_DATA_TX_BROADCAST 0x54
+#define YT921X_MIB_DATA_TX_PAUSE 0x58
+#define YT921X_MIB_DATA_TX_MULTICAST 0x5c
+
+#define YT921X_MIB_DATA_TX_UNDERSIZE_ERR 0x60
+#define YT921X_MIB_DATA_TX_PKT_SZ_64 0x64
+#define YT921X_MIB_DATA_TX_PKT_SZ_65_TO_127 0x68
+#define YT921X_MIB_DATA_TX_PKT_SZ_128_TO_255 0x6c
+
+#define YT921X_MIB_DATA_TX_PKT_SZ_256_TO_511 0x70
+#define YT921X_MIB_DATA_TX_PKT_SZ_512_TO_1023 0x74
+#define YT921X_MIB_DATA_TX_PKT_SZ_1024_TO_1518 0x78
+#define YT921X_MIB_DATA_TX_PKT_SZ_1519_TO_MAX 0x7c
+
+/* 0x80: unused */
+#define YT921X_MIB_DATA_TX_GOOD_BYTES 0x84
+/* 0x88: 64 bytes */
+#define YT921X_MIB_DATA_TX_COLLISION 0x8c
+
+#define YT921X_MIB_DATA_TX_EXCESSIVE_COLLISION 0x90
+#define YT921X_MIB_DATA_TX_MULTIPLE_COLLISION 0x94
+#define YT921X_MIB_DATA_TX_SINGLE_COLLISION 0x98
+#define YT921X_MIB_DATA_TX_PKT 0x9c
+
+#define YT921X_MIB_DATA_TX_DEFERRED 0xa0
+#define YT921X_MIB_DATA_TX_LATE_COLLISION 0xa4
+#define YT921X_MIB_DATA_RX_OAM 0xa8
+#define YT921X_MIB_DATA_TX_OAM 0xac
+
+struct yt921x_mib_stats {
+ u64_stats_t rx_broadcast;
+ u64_stats_t rx_pause;
+ u64_stats_t rx_multicast;
+ u64_stats_t rx_crc_errors;
+
+ u64_stats_t rx_alignment_errors;
+ u64_stats_t rx_undersize_errors;
+ u64_stats_t rx_fragment_errors;
+ u64_stats_t rx_64byte;
+
+ u64_stats_t rx_65_127byte;
+ u64_stats_t rx_128_255byte;
+ u64_stats_t rx_256_511byte;
+ u64_stats_t rx_512_1023byte;
+
+ u64_stats_t rx_1024_1518byte;
+ u64_stats_t rx_jumbo;
+ u64_stats_t rx_good_bytes;
+
+ u64_stats_t rx_bad_bytes;
+ u64_stats_t rx_oversize_errors;
+
+ u64_stats_t rx_dropped;
+ u64_stats_t tx_broadcast;
+ u64_stats_t tx_pause;
+ u64_stats_t tx_multicast;
+
+ u64_stats_t tx_undersize_errors;
+ u64_stats_t tx_64byte;
+ u64_stats_t tx_65_127byte;
+ u64_stats_t tx_128_255byte;
+
+ u64_stats_t tx_256_511byte;
+ u64_stats_t tx_512_1023byte;
+ u64_stats_t tx_1024_1518byte;
+ u64_stats_t tx_jumbo;
+
+ u64_stats_t tx_good_bytes;
+ u64_stats_t tx_collisions;
+
+ u64_stats_t tx_aborted_errors;
+ u64_stats_t tx_multiple_collisions;
+ u64_stats_t tx_single_collisions;
+ u64_stats_t tx_good;
+
+ u64_stats_t tx_deferred;
+ u64_stats_t tx_late_collisions;
+ u64_stats_t rx_oam;
+ u64_stats_t tx_oam;
+};
+
+#define YT921X_MIB_STAT_NUM (sizeof(struct yt921x_mib_stats) / sizeof(u64_stats_t))
+
+struct yt921x_mib {
+ struct yt921x_port *port;
+
+ struct delayed_work work;
+ struct u64_stats_sync syncp;
+ /* writers: priv->reg_lock + syncp; readers: priv->reg_lock or syncp */
+ struct yt921x_mib_stats stats;
+ u64_stats_t rx_frames;
+ u64_stats_t tx_frames;
+ /* protected by priv->reg_lock */
+ u64 data[YT921X_MIB_STAT_NUM];
+};
+
+void yt921x_mib_poll(struct work_struct *work);
+void
+yt921x_dsa_get_strings(struct dsa_switch *ds, int port, u32 stringset,
+ uint8_t *data);
+void
+yt921x_dsa_get_ethtool_stats(struct dsa_switch *ds, int port, uint64_t *data);
+int yt921x_dsa_get_sset_count(struct dsa_switch *ds, int port, int sset);
+void
+yt921x_dsa_get_eth_mac_stats(struct dsa_switch *ds, int port,
+ struct ethtool_eth_mac_stats *mac_stats);
+void
+yt921x_dsa_get_eth_ctrl_stats(struct dsa_switch *ds, int port,
+ struct ethtool_eth_ctrl_stats *ctrl_stats);
+void
+yt921x_dsa_get_rmon_stats(struct dsa_switch *ds, int port,
+ struct ethtool_rmon_stats *rmon_stats,
+ const struct ethtool_rmon_hist_range **ranges);
+void
+yt921x_dsa_get_stats64(struct dsa_switch *ds, int port,
+ struct rtnl_link_stats64 *stats);
+void
+yt921x_dsa_get_pause_stats(struct dsa_switch *ds, int port,
+ struct ethtool_pause_stats *pause_stats);
+
+#endif
diff --git a/drivers/net/dsa/motorcomm/pcs-921x.c b/drivers/net/dsa/motorcomm/pcs-921x.c
new file mode 100644
index 000000000000..e942b61d8eb2
--- /dev/null
+++ b/drivers/net/dsa/motorcomm/pcs-921x.c
@@ -0,0 +1,245 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 David Yang
+ */
+
+#include "chip.h"
+#include "mdio_bus.h"
+#include "pcs.h"
+#include "smi.h"
+
+static int
+yt921x_serdes_config(struct yt921x_priv *priv, int port, unsigned int neg_mode,
+ phy_interface_t interface,
+ const unsigned long *advertising, bool permit_pause_to_mac)
+{
+ bool inband = neg_mode & PHYLINK_PCS_NEG_INBAND;
+ struct yt921x_port *pp = &priv->ports[port];
+ bool changed = false;
+ u16 bmcr;
+ u32 mask;
+ u32 ctrl;
+ u16 val;
+ int adv;
+ int res;
+
+ switch (interface) {
+ case PHY_INTERFACE_MODE_SGMII:
+ ctrl = YT921X_SERDES_MODE_SGMII;
+ break;
+ case PHY_INTERFACE_MODE_100BASEX:
+ ctrl = YT921X_SERDES_MODE_100BASEX;
+ break;
+ case PHY_INTERFACE_MODE_1000BASEX:
+ ctrl = YT921X_SERDES_MODE_1000BASEX;
+ break;
+ case PHY_INTERFACE_MODE_2500BASEX:
+ ctrl = YT921X_SERDES_MODE_2500BASEX;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ res = yt921x_reg_update_bits(priv, YT921X_SERDESn(port),
+ YT921X_SERDES_MODE_M, ctrl);
+ if (res)
+ return res;
+
+ mask = YT921X_PORT_CTRL_LINK_AN | YT921X_PORT_CTRL_PAUSE_AN;
+ ctrl = 0;
+ if (inband)
+ ctrl |= YT921X_PORT_CTRL_LINK_AN;
+ if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED && permit_pause_to_mac &&
+ interface != PHY_INTERFACE_MODE_SGMII)
+ ctrl |= YT921X_PORT_CTRL_PAUSE_AN;
+ res = yt921x_reg_update_bits(priv, YT921X_PORTn_CTRL(port), mask, ctrl);
+ if (res)
+ return res;
+
+ if (inband) {
+ res = yt921x_reg_clear_bits(priv, YT921X_SERDESn(port),
+ YT921X_SERDES_LINK);
+ if (res)
+ return res;
+ }
+
+ adv = phylink_mii_c22_pcs_encode_advertisement(interface, advertising);
+ if (adv >= 0) {
+ res = yt921x_intif_modify_changed(priv, port, MII_ADVERTISE,
+ U16_MAX, adv);
+ if (res < 0)
+ return res;
+
+ changed = !!res;
+ }
+
+ res = yt921x_intif_read(priv, port, MII_BMCR, &val);
+ if (res)
+ return res;
+
+ bmcr = val;
+ if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED)
+ bmcr |= BMCR_ANENABLE;
+ else
+ bmcr &= ~BMCR_ANENABLE;
+
+ /* If the ANENABLE bit was changed, the PHY will restart negotiation,
+ * so we don't need to flag a change to trigger its own restart.
+ */
+ if (bmcr != val)
+ changed = false;
+
+ bmcr &= ~BMCR_ISOLATE;
+ res = yt921x_intif_write(priv, port, MII_BMCR, bmcr);
+ if (res)
+ return res;
+
+ pp->inband = inband;
+ return changed;
+}
+
+static unsigned int
+yt921x_phylink_pcs_inband_caps(struct phylink_pcs *pcs,
+ phy_interface_t interface)
+{
+ if (interface == PHY_INTERFACE_MODE_100BASEX)
+ return LINK_INBAND_DISABLE;
+
+ return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
+}
+
+static void
+yt921x_phylink_pcs_get_state(struct phylink_pcs *pcs, unsigned int neg_mode,
+ struct phylink_link_state *state)
+{
+ struct yt921x_port *pp = pcs_to_yt921x_port(pcs);
+ struct yt921x_priv *priv = yt921x_port_to_priv(pp);
+ struct device *dev = yt921x_priv_to_device(priv);
+ int port = pp->index;
+ u16 bmsr;
+ u16 lpa;
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+
+ res = yt921x_intif_read(priv, port, MII_BMSR, &bmsr);
+ if (res)
+ goto end;
+
+ res = yt921x_intif_read(priv, port, MII_LPA, &lpa);
+
+end:
+ mutex_unlock(&priv->reg_lock);
+
+ if (res) {
+ dev_err_ratelimited(dev, "Failed to %s PCS port %d: %i\n",
+ "get state of", port, res);
+ state->link = false;
+ return;
+ }
+
+ if (state->interface == PHY_INTERFACE_MODE_100BASEX) {
+ state->speed = SPEED_100;
+ state->duplex = DUPLEX_FULL;
+ state->pause |= MLO_PAUSE_TX | MLO_PAUSE_RX;
+ state->link = !!(bmsr & BMSR_LSTATUS);
+ return;
+ }
+
+ phylink_mii_c22_pcs_decode_state(state, neg_mode, bmsr, lpa);
+}
+
+static void yt921x_phylink_pcs_an_restart(struct phylink_pcs *pcs)
+{
+ struct yt921x_port *pp = pcs_to_yt921x_port(pcs);
+ struct yt921x_priv *priv = yt921x_port_to_priv(pp);
+ struct device *dev = yt921x_priv_to_device(priv);
+ int port = pp->index;
+ u16 val;
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+
+ res = yt921x_intif_read(priv, port, MII_BMCR, &val);
+ if (res)
+ goto end;
+
+ val |= BMCR_ANRESTART;
+ res = yt921x_intif_write(priv, port, MII_BMCR, val);
+
+end:
+ mutex_unlock(&priv->reg_lock);
+
+ if (res)
+ dev_err(dev, "Failed to %s PCS port %d: %i\n", "restart",
+ port, res);
+}
+
+static void yt921x_phylink_pcs_disable(struct phylink_pcs *pcs)
+{
+ struct yt921x_port *pp = pcs_to_yt921x_port(pcs);
+ struct yt921x_priv *priv = yt921x_port_to_priv(pp);
+ struct device *dev = yt921x_priv_to_device(priv);
+ int port = pp->index;
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_intif_modify(priv, port, MII_BMCR, BMCR_PDOWN, BMCR_PDOWN);
+ mutex_unlock(&priv->reg_lock);
+
+ if (res)
+ dev_err(dev, "Failed to %s PCS port %d: %i\n", "power down",
+ port, res);
+}
+
+static int yt921x_phylink_pcs_enable(struct phylink_pcs *pcs)
+{
+ struct yt921x_port *pp = pcs_to_yt921x_port(pcs);
+ struct yt921x_priv *priv = yt921x_port_to_priv(pp);
+ int port = pp->index;
+ u16 val;
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+
+ res = yt921x_intif_read(priv, port, MII_BMCR, &val);
+ if (res)
+ goto end;
+
+ val &= ~BMCR_PDOWN;
+ val |= BMCR_ANRESTART;
+ res = yt921x_intif_write(priv, port, MII_BMCR, val);
+
+end:
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+static int
+yt921x_phylink_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
+ phy_interface_t interface,
+ const unsigned long *advertising,
+ bool permit_pause_to_mac)
+{
+ struct yt921x_port *pp = pcs_to_yt921x_port(pcs);
+ struct yt921x_priv *priv = yt921x_port_to_priv(pp);
+ int port = pp->index;
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_serdes_config(priv, port, neg_mode, interface,
+ advertising, permit_pause_to_mac);
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+const struct phylink_pcs_ops yt921x_phylink_pcs_ops = {
+ .pcs_inband_caps = yt921x_phylink_pcs_inband_caps,
+ .pcs_get_state = yt921x_phylink_pcs_get_state,
+ .pcs_an_restart = yt921x_phylink_pcs_an_restart,
+ .pcs_disable = yt921x_phylink_pcs_disable,
+ .pcs_enable = yt921x_phylink_pcs_enable,
+ .pcs_config = yt921x_phylink_pcs_config,
+};
diff --git a/drivers/net/dsa/motorcomm/pcs.h b/drivers/net/dsa/motorcomm/pcs.h
new file mode 100644
index 000000000000..42426558086a
--- /dev/null
+++ b/drivers/net/dsa/motorcomm/pcs.h
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2026 David Yang
+ */
+
+#ifndef _YT_PCS_H
+#define _YT_PCS_H
+
+#include <linux/phylink.h>
+
+extern const struct phylink_pcs_ops yt921x_phylink_pcs_ops;
+
+#endif
diff --git a/drivers/net/dsa/motorcomm/smi.c b/drivers/net/dsa/motorcomm/smi.c
new file mode 100644
index 000000000000..bf3adfd64165
--- /dev/null
+++ b/drivers/net/dsa/motorcomm/smi.c
@@ -0,0 +1,180 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 David Yang
+ */
+
+#include <linux/iopoll.h>
+
+#include "chip.h"
+#include "smi.h"
+
+#define YT921X_POLL_SLEEP_US 10000
+#define YT921X_POLL_TIMEOUT_US 100000
+
+int yt921x_reg_read(struct yt921x_priv *priv, u32 reg, u32 *valp)
+{
+ lockdep_assert_held_once(&priv->reg_lock);
+
+ return priv->reg_ops->read(priv->reg_ctx, reg, valp);
+}
+
+int yt921x_reg_write(struct yt921x_priv *priv, u32 reg, u32 val)
+{
+ lockdep_assert_held_once(&priv->reg_lock);
+
+ return priv->reg_ops->write(priv->reg_ctx, reg, val);
+}
+
+int yt921x_reg_wait(struct yt921x_priv *priv, u32 reg, u32 mask, u32 *valp)
+{
+ u32 val;
+ int res;
+ int ret;
+
+ ret = read_poll_timeout(yt921x_reg_read, res,
+ res || (val & mask) == *valp,
+ YT921X_POLL_SLEEP_US, YT921X_POLL_TIMEOUT_US,
+ false, priv, reg, &val);
+ if (ret)
+ return ret;
+ if (res)
+ return res;
+
+ *valp = val;
+ return 0;
+}
+
+int yt921x_reg_update_bits(struct yt921x_priv *priv, u32 reg, u32 mask, u32 val)
+{
+ int res;
+ u32 v;
+ u32 u;
+
+ res = yt921x_reg_read(priv, reg, &v);
+ if (res)
+ return res;
+
+ u = v;
+ u &= ~mask;
+ u |= val;
+ if (u == v)
+ return 0;
+
+ return yt921x_reg_write(priv, reg, u);
+}
+
+static int
+yt921x_regs_read(struct yt921x_priv *priv, u32 reg, u32 *vals,
+ unsigned int num_regs)
+{
+ int res;
+
+ for (unsigned int i = 0; i < num_regs; i++) {
+ res = yt921x_reg_read(priv, reg + 4 * i, &vals[i]);
+ if (res)
+ return res;
+ }
+
+ return 0;
+}
+
+static int
+yt921x_regs_write(struct yt921x_priv *priv, u32 reg, const u32 *vals,
+ unsigned int num_regs)
+{
+ int res;
+
+ for (unsigned int i = 0; i < num_regs; i++) {
+ res = yt921x_reg_write(priv, reg + 4 * i, vals[i]);
+ if (res)
+ return res;
+ }
+
+ return 0;
+}
+
+static int
+yt921x_regs_update_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks,
+ const u32 *vals, unsigned int num_regs)
+{
+ bool changed = false;
+ u32 vs[4];
+ int res;
+
+ BUILD_BUG_ON(num_regs > ARRAY_SIZE(vs));
+
+ res = yt921x_regs_read(priv, reg, vs, num_regs);
+ if (res)
+ return res;
+
+ for (unsigned int i = 0; i < num_regs; i++) {
+ u32 u = vs[i];
+
+ u &= ~masks[i];
+ u |= vals[i];
+ if (u != vs[i])
+ changed = true;
+
+ vs[i] = u;
+ }
+
+ if (!changed)
+ return 0;
+
+ return yt921x_regs_write(priv, reg, vs, num_regs);
+}
+
+static int
+yt921x_regs_clear_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks,
+ unsigned int num_regs)
+{
+ bool changed = false;
+ u32 vs[4];
+ int res;
+
+ BUILD_BUG_ON(num_regs > ARRAY_SIZE(vs));
+
+ res = yt921x_regs_read(priv, reg, vs, num_regs);
+ if (res)
+ return res;
+
+ for (unsigned int i = 0; i < num_regs; i++) {
+ u32 u = vs[i];
+
+ u &= ~masks[i];
+ if (u != vs[i])
+ changed = true;
+
+ vs[i] = u;
+ }
+
+ if (!changed)
+ return 0;
+
+ return yt921x_regs_write(priv, reg, vs, num_regs);
+}
+
+int
+yt921x_reg64_write(struct yt921x_priv *priv, u32 reg, const u32 *vals)
+{
+ return yt921x_regs_write(priv, reg, vals, 2);
+}
+
+int
+yt921x_reg64_update_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks,
+ const u32 *vals)
+{
+ return yt921x_regs_update_bits(priv, reg, masks, vals, 2);
+}
+
+int
+yt921x_reg64_clear_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks)
+{
+ return yt921x_regs_clear_bits(priv, reg, masks, 2);
+}
+
+int
+yt921x_reg96_write(struct yt921x_priv *priv, u32 reg, const u32 *vals)
+{
+ return yt921x_regs_write(priv, reg, vals, 3);
+}
diff --git a/drivers/net/dsa/motorcomm/smi.h b/drivers/net/dsa/motorcomm/smi.h
new file mode 100644
index 000000000000..212e20f71d80
--- /dev/null
+++ b/drivers/net/dsa/motorcomm/smi.h
@@ -0,0 +1,61 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2026 David Yang
+ */
+
+#ifndef _YT_SMI_H
+#define _YT_SMI_H
+
+#include <linux/types.h>
+#include <linux/wordpart.h>
+
+struct yt921x_priv;
+
+int yt921x_reg_read(struct yt921x_priv *priv, u32 reg, u32 *valp);
+int yt921x_reg_write(struct yt921x_priv *priv, u32 reg, u32 val);
+int yt921x_reg_wait(struct yt921x_priv *priv, u32 reg, u32 mask, u32 *valp);
+int yt921x_reg_update_bits(struct yt921x_priv *priv, u32 reg, u32 mask,
+ u32 val);
+
+static inline int
+yt921x_reg_set_bits(struct yt921x_priv *priv, u32 reg, u32 mask)
+{
+ return yt921x_reg_update_bits(priv, reg, 0, mask);
+}
+
+static inline int
+yt921x_reg_clear_bits(struct yt921x_priv *priv, u32 reg, u32 mask)
+{
+ return yt921x_reg_update_bits(priv, reg, mask, 0);
+}
+
+static inline int
+yt921x_reg_toggle_bits(struct yt921x_priv *priv, u32 reg, u32 mask, bool set)
+{
+ return yt921x_reg_update_bits(priv, reg, mask, !set ? 0 : mask);
+}
+
+/* Some multi-word registers, like VLANn_CTRL, should be treated as a single
+ * long register. More specifically, writes to parts of its words won't become
+ * visible, until the last word is written.
+ *
+ * Here we require full read and write operations over these registers to
+ * eliminate potential issues, although partial reads/writes are also possible.
+ */
+
+static inline void update_ctrls_unaligned(u32 *lo, u32 *hi, u64 mask, u64 val)
+{
+ *lo &= ~lower_32_bits(mask);
+ *hi &= ~upper_32_bits(mask);
+ *lo |= lower_32_bits(val);
+ *hi |= upper_32_bits(val);
+}
+
+int yt921x_reg64_write(struct yt921x_priv *priv, u32 reg, const u32 *vals);
+int yt921x_reg64_update_bits(struct yt921x_priv *priv, u32 reg,
+ const u32 *masks, const u32 *vals);
+int yt921x_reg64_clear_bits(struct yt921x_priv *priv, u32 reg,
+ const u32 *masks);
+int yt921x_reg96_write(struct yt921x_priv *priv, u32 reg, const u32 *vals);
+
+#endif
diff --git a/drivers/net/dsa/motorcomm/tc.c b/drivers/net/dsa/motorcomm/tc.c
new file mode 100644
index 000000000000..9dbd61e48fee
--- /dev/null
+++ b/drivers/net/dsa/motorcomm/tc.c
@@ -0,0 +1,1711 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 David Yang
+ */
+
+#include "chip.h"
+#include "smi.h"
+#include "tc.h"
+
+static int yt921x_mtu_fetch(struct yt921x_priv *priv, int port)
+{
+ struct dsa_port *dp = dsa_to_port(&priv->ds, port);
+
+ return dp->user ? READ_ONCE(dp->user->mtu) : ETH_DATA_LEN;
+}
+
+/* v * 2^e */
+static u64 ldexpu64(u64 v, int e)
+{
+ return e >= 0 ? v << e : v >> -e;
+}
+
+/* slot (ns) * rate (/s) / 10^9 (ns/s) = 2^C * token * 4^unit */
+static u32 rate2token(u64 rate, unsigned int slot_ns, int unit, int C)
+{
+ int e = 2 * unit + C + YT921X_TOKEN_RATE_C;
+
+ return div_u64(ldexpu64(slot_ns * rate, -e), NSEC_PER_SEC);
+}
+
+static u64 token2rate(u32 token, unsigned int slot_ns, int unit, int C)
+{
+ int e = 2 * unit + C + YT921X_TOKEN_RATE_C;
+
+ return div_u64(ldexpu64(mul_u32_u32(NSEC_PER_SEC, token), e), slot_ns);
+}
+
+/* burst = 2^C * token * 4^unit */
+static u32 burst2token(u64 burst, int unit, int C)
+{
+ return ldexpu64(burst, -(2 * unit + C));
+}
+
+static u64 token2burst(u32 token, int unit, int C)
+{
+ return ldexpu64(token, 2 * unit + C);
+}
+
+struct yt921x_marker {
+ u32 cir;
+ u32 cbs;
+ u32 ebs;
+ int unit;
+ bool pkt_mode;
+};
+
+#define YT921X_MARKER_PKT_MODE BIT(0)
+#define YT921X_MARKER_SINGLE_BUCKET BIT(1)
+
+static int
+yt921x_marker_tfm(struct yt921x_marker *marker, u64 rate, u64 burst,
+ unsigned int flags, unsigned int slot_ns, u32 cir_max,
+ u32 cbs_max, int unit_max, struct yt921x_priv *priv, int port,
+ struct netlink_ext_ack *extack)
+{
+ const int C = flags & YT921X_MARKER_PKT_MODE ? YT921X_TOKEN_PKT_C :
+ YT921X_TOKEN_BYTE_C;
+ struct device *dev = yt921x_priv_to_device(priv);
+ struct yt921x_marker m;
+ u64 burst_est;
+ u64 burst_sug;
+ u64 burst_max;
+ u64 rate_max;
+
+ m.unit = unit_max;
+ rate_max = token2rate(cir_max, slot_ns, m.unit, C);
+ burst_max = token2burst(cbs_max, m.unit, C);
+
+ /* Check for unusual values */
+ if (rate > rate_max || burst > burst_max) {
+ NL_SET_ERR_MSG_MOD(extack, "Unexpected tremendous rate");
+ return -ERANGE;
+ }
+
+ /* Check for matching burst */
+ burst_est = div_u64(slot_ns * rate, NSEC_PER_SEC);
+ burst_sug = burst_est;
+ if (flags & YT921X_MARKER_PKT_MODE)
+ burst_sug++;
+ else
+ burst_sug += ETH_HLEN + yt921x_mtu_fetch(priv, port) +
+ ETH_FCS_LEN;
+ if (burst_sug > burst)
+ NL_SET_ERR_MSG_FMT_MOD(extack,
+ "Consider match rate %llu with burst at least %llu",
+ rate, burst_sug);
+
+ /* Select unit */
+ for (; m.unit > 0; m.unit--) {
+ if (rate > (rate_max >> 2) || burst > (burst_max >> 2))
+ break;
+ rate_max >>= 2;
+ burst_max >>= 2;
+ }
+
+ /* Calculate information rate and bucket size */
+ m.cir = rate2token(rate, slot_ns, m.unit, C);
+ if (!m.cir)
+ m.cir = 1;
+ else if (WARN_ON(m.cir > cir_max))
+ m.cir = cir_max;
+ m.cbs = burst2token(burst, m.unit, C);
+ if (!m.cbs)
+ m.cbs = 1;
+ else if (WARN_ON(m.cbs > cbs_max))
+ m.cbs = cbs_max;
+
+ /* Cut EBS */
+ m.ebs = 0;
+ if (!(flags & YT921X_MARKER_SINGLE_BUCKET)) {
+ /* We don't have a chance to adjust rate when MTU is changed */
+ if (flags & YT921X_MARKER_PKT_MODE)
+ burst_est++;
+ else
+ burst_est += YT921X_FRAME_SIZE_MAX;
+
+ if (burst_est < burst) {
+ u32 pbs = m.cbs;
+
+ m.cbs = burst2token(burst_est, m.unit, C);
+ if (!m.cbs)
+ m.cbs = 1;
+ else if (WARN_ON(m.cbs > cbs_max))
+ m.cbs = cbs_max;
+
+ if (pbs > m.cbs)
+ m.ebs = pbs - m.cbs;
+ }
+ }
+
+ dev_dbg(dev,
+ "slot %u ns, rate %llu, burst %llu -> unit %d, cir %u, cbs %u, ebs %u\n",
+ slot_ns, rate, burst, m.unit, m.cir, m.cbs, m.ebs);
+
+ m.pkt_mode = flags & YT921X_MARKER_PKT_MODE;
+ *marker = m;
+ return 0;
+}
+
+static int
+yt921x_marker_tfm_police(struct yt921x_marker *marker,
+ const struct flow_action_police *police,
+ unsigned int flags, struct yt921x_priv *priv, int port,
+ struct netlink_ext_ack *extack)
+{
+ bool pkt_mode = !!police->rate_pkt_ps;
+ u64 burst;
+ u64 rate;
+
+ rate = pkt_mode ? police->rate_pkt_ps : police->rate_bytes_ps;
+ burst = pkt_mode ? police->burst_pkt : police->burst;
+ if (pkt_mode)
+ flags |= YT921X_MARKER_PKT_MODE;
+
+ return yt921x_marker_tfm(marker, rate, burst, flags,
+ priv->meter_slot_ns, YT921X_METER_CIR_MAX,
+ YT921X_METER_CBS_MAX, YT921X_METER_UNIT_MAX,
+ priv, port, extack);
+}
+
+static int
+yt921x_marker_tfm_shape(struct yt921x_marker *marker, u64 rate, u64 burst,
+ unsigned int flags, struct yt921x_priv *priv, int port,
+ struct netlink_ext_ack *extack)
+{
+ return yt921x_marker_tfm(marker, rate, burst, flags,
+ priv->port_shape_slot_ns, YT921X_SHAPE_CIR_MAX,
+ YT921X_SHAPE_CBS_MAX, YT921X_SHAPE_UNIT_MAX,
+ priv, port, extack);
+}
+
+static int
+yt921x_police_validate(const struct flow_action_police *police,
+ const struct flow_action *action,
+ const struct flow_action_entry *act,
+ struct netlink_ext_ack *extack)
+{
+ if (police->exceed.act_id != FLOW_ACTION_DROP) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Offload not supported when exceed action is not drop");
+ return -EOPNOTSUPP;
+ }
+
+ if (police->notexceed.act_id != FLOW_ACTION_PIPE &&
+ police->notexceed.act_id != FLOW_ACTION_ACCEPT) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Offload not supported when conform action is not pipe or ok");
+ return -EOPNOTSUPP;
+ }
+
+ if (police->notexceed.act_id == FLOW_ACTION_ACCEPT && action && act &&
+ !flow_action_is_last_entry(action, act)) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Offload not supported when conform action is ok, but action is not last");
+ return -EOPNOTSUPP;
+ }
+
+ /* mtu defaults to unlimited but we got 2040 here, don't know why */
+ if (police->peakrate_bytes_ps || police->avrate || police->overhead) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Offload not supported when peakrate/avrate/overhead is configured");
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static int
+yt921x_meter_config(struct yt921x_priv *priv, unsigned int id,
+ const struct yt921x_marker *marker)
+{
+ u32 ctrls[3];
+
+ ctrls[0] = 0;
+ ctrls[1] = YT921X_METER_CTRLb_CIR(marker->cir);
+ ctrls[2] = YT921X_METER_CTRLc_UNIT(marker->unit) |
+ YT921X_METER_CTRLc_DROP_R |
+ YT921X_METER_CTRLc_TOKEN_OVERFLOW_EN |
+ YT921X_METER_CTRLc_METER_EN;
+ if (marker->pkt_mode)
+ ctrls[2] |= YT921X_METER_CTRLc_PKT_MODE;
+ update_ctrls_unaligned(&ctrls[0], &ctrls[1],
+ YT921X_METER_CTRLab_EBS_M,
+ YT921X_METER_CTRLab_EBS(marker->ebs));
+ update_ctrls_unaligned(&ctrls[1], &ctrls[2],
+ YT921X_METER_CTRLbc_CBS_M,
+ YT921X_METER_CTRLbc_CBS(marker->cbs));
+
+ return yt921x_reg96_write(priv, YT921X_METERn_CTRL(id), ctrls);
+}
+
+void yt921x_dsa_port_policer_del(struct dsa_switch *ds, int port)
+{
+ struct yt921x_priv *priv = dsa_to_yt921x_priv(ds);
+ struct device *dev = yt921x_priv_to_device(priv);
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_reg_write(priv, YT921X_PORTn_METER(port), 0);
+ mutex_unlock(&priv->reg_lock);
+
+ if (res)
+ dev_err(dev, "Failed to %s port %d: %i\n", "delete policer on",
+ port, res);
+}
+
+int
+yt921x_dsa_port_policer_add(struct dsa_switch *ds, int port,
+ const struct flow_action_police *police,
+ struct netlink_ext_ack *extack)
+{
+ struct yt921x_priv *priv = dsa_to_yt921x_priv(ds);
+ struct yt921x_marker marker;
+ u32 ctrl;
+ int res;
+
+ res = yt921x_police_validate(police, NULL, NULL, extack);
+ if (res)
+ return res;
+
+ res = yt921x_marker_tfm_police(&marker, police, 0, priv, port, extack);
+ if (res)
+ return res;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_meter_config(priv, port + YT921X_METER_NUM, &marker);
+ if (res)
+ goto end;
+
+ ctrl = YT921X_PORT_METER_ID(port) | YT921X_PORT_METER_EN;
+ res = yt921x_reg_write(priv, YT921X_PORTn_METER(port), ctrl);
+end:
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+static int
+yt921x_dsa_port_setup_tc_tbf_port(struct dsa_switch *ds, int port,
+ const struct tc_tbf_qopt_offload *qopt)
+{
+ struct yt921x_priv *priv = dsa_to_yt921x_priv(ds);
+ struct netlink_ext_ack *extack = qopt->extack;
+ u32 ctrls[2];
+ int res;
+
+ if (qopt->parent != TC_H_ROOT)
+ return -EOPNOTSUPP;
+
+ switch (qopt->command) {
+ case TC_TBF_STATS:
+ /* Unfortunately the convention for TC_*_STATS is a mess,
+ * neither 0 nor -EOPNOTSUPP is perfect here.
+ */
+ return -EOPNOTSUPP;
+ case TC_TBF_DESTROY:
+ ctrls[0] = 0;
+ ctrls[1] = 0;
+ break;
+ case TC_TBF_REPLACE: {
+ const struct tc_tbf_qopt_offload_replace_params *p;
+ struct yt921x_marker marker;
+
+ p = &qopt->replace_params;
+
+ res = yt921x_marker_tfm_shape(&marker, p->rate.rate_bytes_ps,
+ p->max_size,
+ YT921X_MARKER_SINGLE_BUCKET,
+ priv, port, extack);
+ if (res)
+ return res;
+
+ ctrls[0] = YT921X_PORT_SHAPE_CTRLa_CIR(marker.cir) |
+ YT921X_PORT_SHAPE_CTRLa_CBS(marker.cbs);
+ ctrls[1] = YT921X_PORT_SHAPE_CTRLb_UNIT(marker.unit) |
+ YT921X_PORT_SHAPE_CTRLb_EN;
+ break;
+ }
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_reg64_write(priv, YT921X_PORTn_SHAPE_CTRL(port), ctrls);
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+int
+yt921x_dsa_port_setup_tc(struct dsa_switch *ds, int port,
+ enum tc_setup_type type, void *type_data)
+{
+ switch (type) {
+ case TC_SETUP_QDISC_TBF: {
+ const struct tc_tbf_qopt_offload *qopt = type_data;
+
+ return yt921x_dsa_port_setup_tc_tbf_port(ds, port, qopt);
+ }
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+/* ACL: 48 blocks * 8 entries
+ *
+ * One rule can span multiple entries, but within a block.
+ */
+
+static void
+yt921x_acl_entry_set(struct yt921x_acl_entry *entry, unsigned int offset,
+ u32 flags, bool set)
+{
+ if (set)
+ entry->key[offset] |= flags;
+ entry->mask[offset] |= flags;
+}
+
+static unsigned int
+yt921x_acl_entries_set_is_fragment(struct yt921x_acl_entry *entries,
+ unsigned int size, bool set)
+{
+ for (unsigned int i = 0; i < size; i++)
+ switch (FIELD_GET(YT921X_ACL_KEYb_TYPE_M, entries[i].key[1])) {
+ case YT921X_ACL_TYPE_IPV4_DA:
+ case YT921X_ACL_TYPE_IPV4_SA:
+ yt921x_acl_entry_set(&entries[i], 1,
+ YT921X_ACL_BINb_IPV4_FRAG, set);
+ return size;
+ case YT921X_ACL_TYPE_IPV6_DA3:
+ case YT921X_ACL_TYPE_IPV6_SA3:
+ yt921x_acl_entry_set(&entries[i], 1,
+ YT921X_ACL_BINb_IPV6_xA3_FRAG,
+ set);
+ return size;
+ case YT921X_ACL_TYPE_MISC:
+ yt921x_acl_entry_set(&entries[i], 1,
+ YT921X_ACL_BINb_MISC_FRAG, set);
+ return size;
+ case YT921X_ACL_TYPE_L4:
+ yt921x_acl_entry_set(&entries[i], 1,
+ YT921X_ACL_BINb_L4_FRAG, set);
+ return size;
+ }
+
+ if (size >= YT921X_ACL_ENT_PER_BLK)
+ return 0;
+
+ entries[size] = (typeof(*entries)){};
+ entries[size].key[1] = YT921X_ACL_KEYb_TYPE(YT921X_ACL_TYPE_MISC);
+ yt921x_acl_entry_set(&entries[size], 1, YT921X_ACL_BINb_MISC_FRAG, set);
+
+ return size + 1;
+}
+
+static unsigned int
+yt921x_acl_entries_set_first_frag(struct yt921x_acl_entry *entries,
+ unsigned int size, bool set)
+{
+ for (unsigned int i = 0; i < size; i++)
+ switch (FIELD_GET(YT921X_ACL_KEYb_TYPE_M, entries[i].key[1])) {
+ case YT921X_ACL_TYPE_IPV6_DA2:
+ case YT921X_ACL_TYPE_IPV6_SA2:
+ yt921x_acl_entry_set(&entries[i], 1,
+ YT921X_ACL_BINb_IPV6_xA2_FIRST_FRAG,
+ set);
+ return size;
+ case YT921X_ACL_TYPE_MISC:
+ yt921x_acl_entry_set(&entries[i], 0,
+ YT921X_ACL_BINa_MISC_FIRST_FRAG,
+ set);
+ return size;
+ }
+
+ if (size >= YT921X_ACL_ENT_PER_BLK)
+ return 0;
+
+ entries[size] = (typeof(*entries)){};
+ entries[size].key[1] = YT921X_ACL_KEYb_TYPE(YT921X_ACL_TYPE_MISC);
+ yt921x_acl_entry_set(&entries[size], 0,
+ YT921X_ACL_BINa_MISC_FIRST_FRAG, set);
+
+ return size + 1;
+}
+
+static unsigned int
+yt921x_acl_entries_set_l3_type(struct yt921x_acl_entry *entries,
+ unsigned int size, enum yt921x_l3_type type)
+{
+ for (unsigned int i = 0; i < size; i++)
+ switch (FIELD_GET(YT921X_ACL_KEYb_TYPE_M, entries[i].key[1])) {
+ case YT921X_ACL_TYPE_MAC_DA0:
+ case YT921X_ACL_TYPE_MAC_SA0:
+ entries[i].key[1] |= YT921X_ACL_BINb_MAC_xA0_L3_TYPE(type);
+ entries[i].mask[1] |= YT921X_ACL_BINb_MAC_xA0_L3_TYPE_M;
+ return size;
+ case YT921X_ACL_TYPE_MISC:
+ entries[i].key[0] |= YT921X_ACL_BINa_MISC_L3_TYPE(type);
+ entries[i].mask[0] |= YT921X_ACL_BINa_MISC_L3_TYPE_M;
+ return size;
+ }
+
+ if (size >= YT921X_ACL_ENT_PER_BLK)
+ return 0;
+
+ entries[size] = (typeof(*entries)){};
+ entries[size].key[0] = YT921X_ACL_BINa_MISC_L3_TYPE(type);
+ entries[size].key[1] = YT921X_ACL_KEYb_TYPE(YT921X_ACL_TYPE_MISC);
+ entries[size].mask[0] = YT921X_ACL_BINa_MISC_L3_TYPE_M;
+
+ return size + 1;
+}
+
+static unsigned int
+yt921x_acl_entries_set_l4_type(struct yt921x_acl_entry *entries,
+ unsigned int size, enum yt921x_l4_type type)
+{
+ for (unsigned int i = 0; i < size; i++)
+ switch (FIELD_GET(YT921X_ACL_KEYb_TYPE_M, entries[i].key[1])) {
+ case YT921X_ACL_TYPE_IPV4_DA:
+ case YT921X_ACL_TYPE_IPV4_SA:
+ entries[i].key[1] |= YT921X_ACL_BINb_IPV4_L4_TYPE(type);
+ entries[i].mask[1] |= YT921X_ACL_BINb_IPV4_L4_TYPE_M;
+ return size;
+ case YT921X_ACL_TYPE_IPV6_DA0:
+ case YT921X_ACL_TYPE_IPV6_DA1:
+ case YT921X_ACL_TYPE_IPV6_DA2:
+ case YT921X_ACL_TYPE_IPV6_DA3:
+ case YT921X_ACL_TYPE_IPV6_SA0:
+ case YT921X_ACL_TYPE_IPV6_SA1:
+ case YT921X_ACL_TYPE_IPV6_SA2:
+ case YT921X_ACL_TYPE_IPV6_SA3:
+ entries[i].key[1] |= YT921X_ACL_BINb_IPV6_L4_TYPE(type);
+ entries[i].mask[1] |= YT921X_ACL_BINb_IPV6_L4_TYPE_M;
+ return size;
+ case YT921X_ACL_TYPE_L4:
+ entries[i].key[1] |= YT921X_ACL_BINb_L4_TYPE(type);
+ entries[i].mask[1] |= YT921X_ACL_BINb_L4_TYPE_M;
+ return size;
+ case YT921X_ACL_TYPE_MISC:
+ entries[i].key[1] |= YT921X_ACL_BINb_MISC_L4_TYPE(type);
+ entries[i].mask[1] |= YT921X_ACL_BINb_MISC_L4_TYPE_M;
+ return size;
+ }
+
+ if (size >= YT921X_ACL_ENT_PER_BLK)
+ return 0;
+
+ entries[size] = (typeof(*entries)){};
+ entries[size].key[1] = YT921X_ACL_BINb_MISC_L4_TYPE(type) |
+ YT921X_ACL_KEYb_TYPE(YT921X_ACL_TYPE_MISC);
+ entries[size].mask[1] = YT921X_ACL_BINb_MISC_L4_TYPE_M;
+
+ return size + 1;
+}
+
+static struct yt921x_acl_entry *
+yt921x_acl_entries_new(struct yt921x_acl_entry *entries, unsigned int *sizep,
+ u32 type)
+{
+ unsigned int size = *sizep;
+
+ if (size >= YT921X_ACL_ENT_PER_BLK)
+ return NULL;
+
+ entries[size] = (typeof(*entries)){};
+ entries[size].key[1] = YT921X_ACL_KEYb_TYPE(type);
+
+ (*sizep)++;
+ return &entries[size];
+}
+
+static struct yt921x_acl_entry *
+yt921x_acl_entries_find(struct yt921x_acl_entry *entries, unsigned int *sizep,
+ u32 type)
+{
+ for (unsigned int i = 0; i < *sizep; i++)
+ if (FIELD_GET(YT921X_ACL_KEYb_TYPE_M, entries[i].key[1]) ==
+ type)
+ return &entries[i];
+ return yt921x_acl_entries_new(entries, sizep, type);
+}
+
+static void
+yt921x_acl_rule_set_ports(struct yt921x_acl_rule *aclrule, u16 ord,
+ u16 ports_mask)
+{
+ struct yt921x_acl_entry *entries = aclrule->entries;
+
+ for (unsigned int i = 0; i < hweight8(aclrule->mask); i++) {
+ entries[i].key[1] |= YT921X_ACL_KEYb_SPORTS(ports_mask) |
+ YT921X_ACL_KEYb_ORD(ord);
+ }
+}
+
+struct yt921x_udf_entry {
+ unsigned char type;
+ unsigned char offset;
+ u8 key;
+ u8 mask;
+};
+
+struct yt921x_udf_map {
+ unsigned char type;
+ unsigned char offset;
+};
+
+/* UDF windows are preselected - that's enough for now. */
+static struct yt921x_udf_map yt921x_udf_maps[] = {
+ {YT921X_UDF_TYPE_L3, 7},
+ {YT921X_UDF_TYPE_L4, 0},
+};
+
+struct yt921x_acl_rule_ext {
+ struct yt921x_acl_rule r;
+
+ struct yt921x_marker marker;
+};
+
+static int
+yt921x_acl_rule_ext_parse_flow_entries(struct yt921x_acl_rule_ext *ruleext,
+ const struct flow_cls_offload *cls)
+{
+ const struct flow_rule *rule = flow_cls_offload_flow_rule(cls);
+ struct yt921x_acl_entry *entries = ruleext->r.entries;
+ struct netlink_ext_ack *extack = cls->common.extack;
+ struct yt921x_udf_entry udfs[YT921X_UDF_NUM];
+ const struct flow_dissector *dissector;
+ struct yt921x_acl_entry *entry;
+ unsigned int udfs_cnt = 0;
+ unsigned int size = 0;
+ __be16 n_proto = 0;
+ u8 ip_proto = 0;
+ bool use_dport;
+ bool use_sport;
+
+ /* Incomplete and probably won't, since it supports custom u32 filters.
+ * New adapters are welcome.
+ */
+ dissector = rule->match.dissector;
+ if (dissector->used_keys &
+ ~(BIT_ULL(FLOW_DISSECTOR_KEY_CONTROL) |
+ BIT_ULL(FLOW_DISSECTOR_KEY_BASIC) |
+ BIT_ULL(FLOW_DISSECTOR_KEY_IPV4_ADDRS) |
+ BIT_ULL(FLOW_DISSECTOR_KEY_IPV6_ADDRS) |
+ BIT_ULL(FLOW_DISSECTOR_KEY_PORTS) |
+ BIT_ULL(FLOW_DISSECTOR_KEY_PORTS_RANGE) |
+ BIT_ULL(FLOW_DISSECTOR_KEY_ETH_ADDRS) |
+ BIT_ULL(FLOW_DISSECTOR_KEY_IP) |
+ BIT_ULL(FLOW_DISSECTOR_KEY_TCP) |
+ BIT_ULL(FLOW_DISSECTOR_KEY_ICMP))) {
+ NL_SET_ERR_MSG_MOD(extack, "Unsupported keys used");
+ return -EOPNOTSUPP;
+ }
+
+ /* Entries */
+ if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_IPV4_ADDRS)) {
+ struct flow_match_ipv4_addrs match;
+
+ flow_rule_match_ipv4_addrs(rule, &match);
+
+ if (match.mask->dst) {
+ entry = yt921x_acl_entries_new(entries, &size,
+ YT921X_ACL_TYPE_IPV4_DA);
+ if (!entry)
+ goto err;
+
+ entry->key[0] |= ntohl(match.key->dst);
+ entry->mask[0] |= ntohl(match.mask->dst);
+ }
+
+ if (match.mask->src) {
+ entry = yt921x_acl_entries_new(entries, &size,
+ YT921X_ACL_TYPE_IPV4_SA);
+ if (!entry)
+ goto err;
+
+ entry->key[0] |= ntohl(match.key->src);
+ entry->mask[0] |= ntohl(match.mask->src);
+ }
+ }
+
+ if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_IPV6_ADDRS)) {
+ struct flow_match_ipv6_addrs match;
+
+ flow_rule_match_ipv6_addrs(rule, &match);
+
+ for (unsigned int i = 0; i < 4; i++) {
+ if (!match.mask->dst.s6_addr32[i])
+ continue;
+
+ entry = yt921x_acl_entries_new(entries, &size,
+ YT921X_ACL_TYPE_IPV6_DA0 + i);
+ if (!entry)
+ goto err;
+
+ entry->key[0] |= ntohl(match.key->dst.s6_addr32[i]);
+ entry->mask[0] |= ntohl(match.mask->dst.s6_addr32[i]);
+ }
+
+ for (unsigned int i = 0; i < 4; i++) {
+ if (!match.mask->src.s6_addr32[i])
+ continue;
+
+ entry = yt921x_acl_entries_new(entries, &size,
+ YT921X_ACL_TYPE_IPV6_SA0 + i);
+ if (!entry)
+ goto err;
+
+ entry->key[0] |= ntohl(match.key->src.s6_addr32[i]);
+ entry->mask[0] |= ntohl(match.mask->src.s6_addr32[i]);
+ }
+ }
+
+ use_dport = false;
+ use_sport = false;
+ if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_PORTS)) {
+ struct flow_match_ports match;
+
+ entry = yt921x_acl_entries_new(entries, &size,
+ YT921X_ACL_TYPE_L4);
+ if (!entry)
+ goto err;
+
+ flow_rule_match_ports(rule, &match);
+
+ use_dport = !!match.mask->dst;
+ use_sport = !!match.mask->src;
+
+ entry->key[0] |= (ntohs(match.key->dst) << 16) |
+ ntohs(match.key->src);
+ entry->mask[0] |= (ntohs(match.mask->dst) << 16) |
+ ntohs(match.mask->src);
+ }
+
+ if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_PORTS_RANGE)) {
+ struct flow_match_ports_range match;
+
+ entry = yt921x_acl_entries_find(entries, &size,
+ YT921X_ACL_TYPE_L4);
+ if (!entry)
+ goto err;
+
+ flow_rule_match_ports_range(rule, &match);
+
+ if ((use_dport && match.mask->tp.dst) ||
+ (use_sport && match.mask->tp.src)) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Port mask and range are mutually exclusive");
+ return -EINVAL;
+ }
+
+ if (match.mask->tp.dst) {
+ entry->key[0] |= ntohs(match.key->tp_min.dst) << 16;
+ entry->key[1] |= YT921X_ACL_KEYb_L4_DPORT_RANGE_EN;
+ entry->mask[0] |= ntohs(match.key->tp_max.dst) << 16;
+ }
+
+ if (match.mask->tp.src) {
+ entry->key[0] |= ntohs(match.key->tp_min.src);
+ entry->key[1] |= YT921X_ACL_KEYb_L4_SPORT_RANGE_EN;
+ entry->mask[0] |= ntohs(match.key->tp_max.src);
+ }
+ }
+
+ if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_ETH_ADDRS)) {
+ struct flow_match_eth_addrs match;
+ u32 mask;
+
+ flow_rule_match_eth_addrs(rule, &match);
+
+ mask = ethaddr_hi4_to_u32(match.mask->dst);
+ if (mask) {
+ entry = yt921x_acl_entries_new(entries, &size,
+ YT921X_ACL_TYPE_MAC_DA0);
+ if (!entry)
+ goto err;
+
+ entry->key[0] |= ethaddr_hi4_to_u32(match.key->dst);
+ entry->mask[0] |= mask;
+ }
+
+ mask = ethaddr_hi4_to_u32(match.mask->src);
+ if (mask) {
+ entry = yt921x_acl_entries_new(entries, &size,
+ YT921X_ACL_TYPE_MAC_SA0);
+ if (!entry)
+ goto err;
+
+ entry->key[0] |= ethaddr_hi4_to_u32(match.key->src);
+ entry->mask[0] |= mask;
+ }
+
+ mask = (ethaddr_lo2_to_u32(match.mask->dst) << 16) |
+ ethaddr_lo2_to_u32(match.mask->src);
+ if (mask) {
+ entry = yt921x_acl_entries_new(entries, &size,
+ YT921X_ACL_TYPE_MAC_DA1_SA1);
+ if (!entry)
+ goto err;
+
+ entry->key[0] |= (ethaddr_lo2_to_u32(match.key->dst) << 16) |
+ ethaddr_lo2_to_u32(match.key->src);
+ entry->mask[0] |= mask;
+ }
+ }
+
+ /* Entries + Misc */
+ if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_BASIC)) {
+ struct flow_match_basic match;
+
+ flow_rule_match_basic(rule, &match);
+
+ if (match.mask->n_proto) {
+ enum yt921x_l3_type l3type = YT921X_L3_TYPE_OTHER;
+
+ if (match.mask->n_proto == htons(~0)) {
+ n_proto = match.key->n_proto;
+
+ switch (match.key->n_proto) {
+ case htons(ETH_P_IP):
+ l3type = YT921X_L3_TYPE_IPV4;
+ break;
+ case htons(ETH_P_IPV6):
+ l3type = YT921X_L3_TYPE_IPV6;
+ break;
+ case htons(ETH_P_ARP):
+ l3type = YT921X_L3_TYPE_ARP;
+ break;
+ case htons(ETH_P_LLDP):
+ l3type = YT921X_L3_TYPE_LLDP;
+ break;
+ case htons(ETH_P_PAE):
+ l3type = YT921X_L3_TYPE_PAE;
+ break;
+ case htons(ETH_P_CFM):
+ l3type = YT921X_L3_TYPE_ERP;
+ break;
+ }
+ }
+
+ if (l3type != YT921X_L3_TYPE_OTHER) {
+ size = yt921x_acl_entries_set_l3_type(entries,
+ size,
+ l3type);
+ if (!size)
+ goto err;
+ } else {
+ entry = yt921x_acl_entries_new(entries, &size,
+ YT921X_ACL_TYPE_ETHERTYPE);
+ if (!entry)
+ goto err;
+
+ entry->key[0] |= ntohs(match.key->n_proto);
+ entry->mask[0] |= ntohs(match.mask->n_proto);
+ }
+ }
+
+ if (match.mask->ip_proto) {
+ enum yt921x_l4_type l4type = YT921X_L4_TYPE_OTHER;
+
+ if (match.mask->ip_proto == (u8)~0) {
+ ip_proto = match.key->ip_proto;
+
+ switch (match.key->ip_proto) {
+ case IPPROTO_TCP:
+ l4type = YT921X_L4_TYPE_TCP;
+ break;
+ case IPPROTO_UDP:
+ l4type = YT921X_L4_TYPE_UDP;
+ break;
+ case IPPROTO_UDPLITE:
+ l4type = YT921X_L4_TYPE_UDPLITE;
+ break;
+ case IPPROTO_ICMP:
+ l4type = YT921X_L4_TYPE_ICMP;
+ break;
+ case IPPROTO_IGMP:
+ l4type = YT921X_L4_TYPE_IGMP;
+ break;
+ }
+ }
+
+ if (l4type != YT921X_L4_TYPE_OTHER) {
+ size = yt921x_acl_entries_set_l4_type(entries,
+ size,
+ l4type);
+ if (!size)
+ goto err;
+ } else {
+ entry = yt921x_acl_entries_find(entries, &size,
+ YT921X_ACL_TYPE_MISC);
+ if (!entry)
+ goto err;
+
+ entry->key[0] |= YT921X_ACL_BINa_MISC_IP_PROTO(match.key->ip_proto);
+ entry->mask[0] |= YT921X_ACL_BINa_MISC_IP_PROTO(match.mask->ip_proto);
+ }
+ }
+ }
+
+ if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_CONTROL)) {
+ u32 supp_flags = FLOW_DIS_IS_FRAGMENT | FLOW_DIS_FIRST_FRAG;
+ struct flow_match_control match;
+
+ flow_rule_match_control(rule, &match);
+ if (!flow_rule_is_supp_control_flags(supp_flags,
+ match.mask->flags, extack))
+ return -EOPNOTSUPP;
+
+ if (match.mask->flags & FLOW_DIS_IS_FRAGMENT) {
+ bool set = match.key->flags & FLOW_DIS_IS_FRAGMENT;
+
+ size = yt921x_acl_entries_set_is_fragment(entries, size,
+ set);
+ if (!size)
+ goto err;
+ }
+ if (match.mask->flags & FLOW_DIS_FIRST_FRAG) {
+ bool set = match.key->flags & FLOW_DIS_FIRST_FRAG;
+
+ size = yt921x_acl_entries_set_first_frag(entries, size,
+ set);
+ if (!size)
+ goto err;
+ }
+ }
+
+ /* Misc only */
+ if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_IP)) {
+ struct flow_match_ip match;
+
+ flow_rule_match_ip(rule, &match);
+
+ if (match.mask->ttl) {
+ if (n_proto != htons(ETH_P_IP) &&
+ n_proto != htons(ETH_P_IPV6)) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Matching on TTL requires protocol ip or ipv6");
+ return -EOPNOTSUPP;
+ }
+
+ if (udfs_cnt >= YT921X_UDF_NUM)
+ goto err;
+
+ udfs[udfs_cnt] = (struct yt921x_udf_entry){
+ .type = YT921X_UDF_TYPE_L3,
+ .offset = n_proto == htons(ETH_P_IP) ? 8 : 7,
+ .key = match.key->ttl,
+ .mask = match.mask->ttl,
+ };
+ udfs_cnt++;
+ }
+
+ if (match.mask->tos) {
+ entry = yt921x_acl_entries_find(entries, &size,
+ YT921X_ACL_TYPE_MISC);
+ if (!entry)
+ goto err;
+
+ entry->key[0] |= YT921X_ACL_BINa_MISC_TOS(match.key->tos);
+ entry->mask[0] |= YT921X_ACL_BINa_MISC_TOS(match.mask->tos);
+ }
+ }
+
+ if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_TCP)) {
+ struct flow_match_tcp match;
+
+ flow_rule_match_tcp(rule, &match);
+ if (match.mask->flags & htons(~0xff)) {
+ NL_SET_ERR_MSG_MOD(extack, "Unsupported TCP flags");
+ return -EOPNOTSUPP;
+ }
+
+ if (match.mask->flags) {
+ entry = yt921x_acl_entries_find(entries, &size,
+ YT921X_ACL_TYPE_MISC);
+ if (!entry)
+ goto err;
+
+ entry->key[0] |= YT921X_ACL_BINa_MISC_TCP_FLAGS(ntohs(match.key->flags));
+ entry->mask[0] |= YT921X_ACL_BINa_MISC_TCP_FLAGS(ntohs(match.mask->flags));
+ }
+ }
+
+ if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_ICMP)) {
+ struct flow_match_icmp match;
+
+ flow_rule_match_icmp(rule, &match);
+
+ if (match.mask->type || match.mask->code) {
+ if (ip_proto != IPPROTO_ICMP &&
+ ip_proto != IPPROTO_ICMPV6) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Matching on ICMP type/code requires ip_proto icmp or icmpv6");
+ return -EOPNOTSUPP;
+ }
+
+ if (match.mask->type) {
+ if (udfs_cnt >= YT921X_UDF_NUM)
+ goto err;
+
+ udfs[udfs_cnt] = (struct yt921x_udf_entry){
+ .type = YT921X_UDF_TYPE_L4,
+ .offset = 0,
+ .key = match.key->type,
+ .mask = match.mask->type,
+ };
+ udfs_cnt++;
+ }
+
+ if (match.mask->code) {
+ if (udfs_cnt >= YT921X_UDF_NUM)
+ goto err;
+
+ udfs[udfs_cnt] = (struct yt921x_udf_entry){
+ .type = YT921X_UDF_TYPE_L4,
+ .offset = 1,
+ .key = match.key->code,
+ .mask = match.mask->code,
+ };
+ udfs_cnt++;
+ }
+ }
+ }
+
+ /* Compile UDFs */
+ for (unsigned int i = 0; i < udfs_cnt; i++) {
+ unsigned int o;
+ u32 mask;
+ u32 key;
+
+ for (o = 0; o < ARRAY_SIZE(yt921x_udf_maps); o++)
+ if (udfs[i].type == yt921x_udf_maps[o].type &&
+ (udfs[i].offset == yt921x_udf_maps[o].offset ||
+ udfs[i].offset == yt921x_udf_maps[o].offset + 1))
+ break;
+
+ if (WARN_ON(o == ARRAY_SIZE(yt921x_udf_maps)))
+ return -EOPNOTSUPP;
+
+ entry = yt921x_acl_entries_find(entries, &size,
+ YT921X_ACL_TYPE_UDF0 + o / 2);
+ if (!entry)
+ goto err;
+
+ key = udfs[i].key;
+ mask = udfs[i].mask;
+ if (udfs[i].offset == yt921x_udf_maps[o].offset) {
+ key <<= 8;
+ mask <<= 8;
+ }
+ if (!(o % 2)) {
+ key <<= 16;
+ mask <<= 16;
+ }
+
+ entry->key[0] |= key;
+ entry->mask[0] |= mask;
+ }
+
+ if (!size) {
+ NL_SET_ERR_MSG_MOD(extack, "Empty rule generated, this should not happen");
+ return -EOPNOTSUPP;
+ }
+
+ ruleext->r.mask = (1 << size) - 1;
+ return 0;
+
+err:
+ NL_SET_ERR_MSG_MOD(extack, "Rule too complex");
+ return -EOPNOTSUPP;
+}
+
+static int
+yt921x_acl_rule_ext_parse_flow_action(struct yt921x_acl_rule_ext *ruleext,
+ const struct flow_cls_offload *cls,
+ struct yt921x_priv *priv, int port)
+{
+ const struct flow_rule *rule = flow_cls_offload_flow_rule(cls);
+ const struct flow_action *flow_action = &rule->action;
+ struct netlink_ext_ack *extack = cls->common.extack;
+ enum flow_action_id redir_act = NUM_FLOW_ACTIONS;
+ const struct flow_action_entry *act;
+ u32 *action = ruleext->r.action;
+ bool seen_priority = false;
+ const char *reason = NULL;
+ bool seen_police = false;
+ unsigned int i;
+ int res;
+
+ memset(action, 0, 3 * sizeof(*action));
+ flow_action_for_each(i, act, flow_action)
+ switch (act->id) {
+ case FLOW_ACTION_ACCEPT:
+ case FLOW_ACTION_DROP:
+ case FLOW_ACTION_REDIRECT:
+ if (redir_act != NUM_FLOW_ACTIONS &&
+ redir_act != act->id) {
+ reason = "Different redirect actions";
+ goto fallback;
+ }
+ redir_act = act->id;
+
+ switch (act->id) {
+ case FLOW_ACTION_ACCEPT:
+ action[2] |= YT921X_ACL_ACTc_FWD_EN |
+ YT921X_ACL_ACTc_FWD_FWD;
+ break;
+ case FLOW_ACTION_DROP:
+ action[2] |= YT921X_ACL_ACTc_FWD_EN |
+ YT921X_ACL_ACTc_FWD_REDIR;
+ break;
+ case FLOW_ACTION_REDIRECT: {
+ struct dsa_port *to_dp;
+
+ to_dp = dsa_port_from_netdev(act->dev);
+ if (IS_ERR(to_dp) || to_dp->ds != &priv->ds) {
+ reason = "Redirect to non-local port";
+ goto fallback;
+ }
+
+ action[2] |= YT921X_ACL_ACTc_FWD_EN |
+ YT921X_ACL_ACTc_FWD_REDIR |
+ YT921X_ACL_ACTc_FWD_REDIR_DPORTn(to_dp->index);
+ break;
+ }
+ default:
+ break;
+ }
+ break;
+ case FLOW_ACTION_PRIORITY:
+ if (seen_priority) {
+ action[0] &= ~YT921X_ACL_ACTa_PRIO_EN;
+ action[1] &= ~YT921X_ACL_ACTb_PRIO_M;
+
+ reason = "Multiple priority actions";
+ goto fallback;
+ }
+ seen_priority = true;
+
+ if (act->priority >= YT921X_PRIO_NUM) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Priority value is too high");
+ return -EOPNOTSUPP;
+ }
+ action[0] |= YT921X_ACL_ACTa_PRIO_EN;
+ action[1] |= YT921X_ACL_ACTb_PRIO(act->priority);
+ break;
+ case FLOW_ACTION_POLICE: {
+ const struct flow_action_police *police = &act->police;
+
+ if (seen_police) {
+ action[0] &= ~YT921X_ACL_ACTa_METER_EN;
+
+ reason = "Multiple police actions";
+ goto fallback;
+ }
+ seen_police = true;
+
+ res = yt921x_police_validate(police, flow_action, act,
+ extack);
+ if (res)
+ return res;
+
+ res = yt921x_marker_tfm_police(&ruleext->marker, police,
+ 0, priv, port, extack);
+ if (res)
+ return res;
+
+ action[0] |= YT921X_ACL_ACTa_METER_EN;
+ break;
+ }
+ default:
+fallback:
+ if (cls->common.skip_sw) {
+ NL_SET_ERR_MSG_FMT_MOD(extack,
+ "Action not supported when skip_sw: %s",
+ reason);
+ return -EOPNOTSUPP;
+ }
+ fallthrough;
+ case FLOW_ACTION_TRAP:
+ redir_act = FLOW_ACTION_TRAP;
+
+ action[2] &= ~YT921X_ACL_ACTc_FWD_REDIR_DPORTS_M &
+ ~YT921X_ACL_ACTc_FWD_M;
+ action[2] |= YT921X_ACL_ACTc_FWD_EN |
+ YT921X_ACL_ACTc_FWD_TRAP;
+ break;
+ }
+
+ ruleext->r.sw_assisted = !cls->common.skip_sw;
+ return 0;
+}
+
+static int
+yt921x_acl_rule_ext_parse_flow(struct yt921x_acl_rule_ext *ruleext, int port,
+ const struct flow_cls_offload *cls, bool ingress,
+ struct yt921x_priv *priv)
+{
+ const unsigned int scale = (U16_MAX + 1) / YT921X_ACL_ORD_NUM;
+ struct netlink_ext_ack *extack = cls->common.extack;
+ u32 prio = cls->common.prio;
+ u32 ord;
+ int res;
+
+ if (!ingress) {
+ NL_SET_ERR_MSG_MOD(extack, "Only ingress is supported");
+ return -EOPNOTSUPP;
+ }
+
+ if (cls->common.chain_index) {
+ NL_SET_ERR_MSG(extack, "Only chain 0 is supported");
+ return -EOPNOTSUPP;
+ }
+
+ /* Lower TC priorities take precedence, while larger orders win in
+ * hardware. Spread the 512 hardware orders over the TC priority range
+ * as evenly as possible, retaining the relative order:
+ *
+ * prio 65535 -> ORD 0 catchall filter
+ * prio 65408 -> ORD 1 highest mappable TC priority
+ * ...
+ * prio 49152 -> ORD 128 default value for the first tc flower rule
+ * ...
+ * prio 128 -> ORD 511
+ * prio 0 (invalid TC priority)
+ *
+ * The catchall filter always comes with priority 65535; pin it to ORD
+ * 0, the lowest hardware order, instead of rejecting it.
+ *
+ * Prefer explicit rejects over implicit behaviour changes: reject
+ * the priorities which do not map onto a hardware order instead of
+ * silently colliding with a neighbouring one.
+ */
+ if (prio == U16_MAX) {
+ ord = 0;
+ } else if (!(prio % scale)) {
+ ord = YT921X_ACL_ORD_NUM - prio / scale;
+ } else {
+ NL_SET_ERR_MSG_FMT_MOD(extack,
+ "Invalid priority %u, must be a multiple of %u or 65535",
+ prio, scale);
+ return -EOPNOTSUPP;
+ }
+
+ res = yt921x_acl_rule_ext_parse_flow_action(ruleext, cls, priv, port);
+ if (res)
+ return res;
+ res = yt921x_acl_rule_ext_parse_flow_entries(ruleext, cls);
+ if (res)
+ return res;
+
+ yt921x_acl_rule_set_ports(&ruleext->r, ord, BIT(port));
+ ruleext->r.tag = cls->cookie;
+ ruleext->r.type = TC_SETUP_CLSFLOWER;
+ return 0;
+}
+
+/* Ports that _should_ have ACL activated on. ACL_PORT \succeq activated_ports()
+ */
+static u16 yt921x_acl_activated_ports(const struct yt921x_priv *priv)
+{
+ u16 mask = 0;
+
+ for (int port = 0; port < YT921X_PORT_NUM; port++)
+ if (priv->ports[port].acl_cnt)
+ mask |= BIT(port);
+
+ return mask;
+}
+
+static unsigned int
+yt921x_acl_find(const struct yt921x_priv *priv, enum tc_setup_type type,
+ unsigned long tag)
+{
+ for (unsigned int blkid = 0; blkid < YT921X_ACL_BLK_NUM; blkid++) {
+ const struct yt921x_acl_blk *aclblk = priv->acl_blks[blkid];
+
+ if (!aclblk)
+ continue;
+
+ for (unsigned int i = 0; i < YT921X_ACL_ENT_PER_BLK; i++)
+ if (aclblk->rules[i] && aclblk->rules[i]->tag == tag &&
+ aclblk->rules[i]->type == type)
+ return YT921X_ACL_ENT_PER_BLK * blkid + i;
+ }
+
+ return UINT_MAX;
+}
+
+static unsigned int
+yt921x_acl_reserve(struct yt921x_priv *priv, unsigned int entscnt,
+ struct netlink_ext_ack *extack)
+{
+ int candidates[YT921X_ACL_ENT_PER_BLK + 1];
+ unsigned int acl_used_cnt = 0;
+
+ if (WARN_ON(entscnt > YT921X_ACL_ENT_PER_BLK))
+ return UINT_MAX;
+
+ for (unsigned int i = 0; i < ARRAY_SIZE(candidates); i++)
+ candidates[i] = -1;
+ for (unsigned int i = YT921X_ACL_BLK_NUM; i-- > 0;) {
+ unsigned int blk_used_cnt = hweight8(priv->acl_masks[i]);
+
+ candidates[blk_used_cnt] = i;
+ acl_used_cnt += blk_used_cnt;
+ }
+
+ if (acl_used_cnt >= YT921X_ACL_NUM) {
+ NL_SET_ERR_MSG_MOD(extack, "ACL entry limit reached");
+ return UINT_MAX;
+ }
+ if (acl_used_cnt + entscnt <= YT921X_ACL_NUM)
+ for (unsigned int i = YT921X_ACL_ENT_PER_BLK - entscnt + 1;
+ i-- > 0;)
+ if (candidates[i] >= 0)
+ return YT921X_ACL_ENT_PER_BLK * candidates[i] +
+ ffz(priv->acl_masks[candidates[i]]);
+
+ NL_SET_ERR_MSG_MOD(extack,
+ "ACL entry allocation failed, simplify your rules or remove existing rules");
+ return UINT_MAX;
+}
+
+static int
+yt921x_acl_commit(struct yt921x_priv *priv, unsigned int entid, u8 entsmask)
+{
+ const struct yt921x_acl_rule *aclrule;
+ const struct yt921x_acl_blk *aclblk;
+ unsigned int blkid;
+ unsigned int binid;
+ unsigned long mask;
+ u32 zeros[3] = {};
+ unsigned int i;
+ unsigned int o;
+ u32 ctrl;
+ int res;
+
+ blkid = entid / YT921X_ACL_ENT_PER_BLK;
+ binid = entid % YT921X_ACL_ENT_PER_BLK;
+ aclblk = priv->acl_blks[blkid];
+ aclrule = aclblk->rules[binid];
+
+ /* Write actions */
+ res = yt921x_reg96_write(priv, YT921X_ACLn_ACT(entid),
+ aclrule ? aclrule->action : zeros);
+ if (res)
+ return res;
+
+ /* Select the block */
+ ctrl = YT921X_ACL_BLK_CMD_MODIFY | YT921X_ACL_BLK_CMD_BLKID(blkid);
+ res = yt921x_reg_write(priv, YT921X_ACL_BLK_CMD, ctrl);
+ if (res)
+ return res;
+
+ /* Write keys and masks */
+ ctrl = 0;
+ for (unsigned int i = 0; i < YT921X_ACL_ENT_PER_BLK; i++)
+ ctrl |= YT921X_ACL_BLK_KEEP_KEEPn(i);
+
+ mask = entsmask;
+ i = 0;
+ for_each_set_bit(o, &mask, YT921X_ACL_ENT_PER_BLK) {
+ res = yt921x_reg64_write(priv, YT921X_ACLn_KEYm(blkid, o),
+ aclrule ? aclrule->entries[i].key :
+ zeros);
+ if (res)
+ return res;
+
+ res = yt921x_reg64_write(priv, YT921X_ACLn_MASKm(blkid, o),
+ aclrule ? aclrule->entries[i].mask :
+ zeros);
+ if (res)
+ return res;
+
+ ctrl &= ~YT921X_ACL_BLK_KEEP_KEEPn(o);
+ i++;
+ }
+
+ res = yt921x_reg_write(priv, YT921X_ACL_BLK_KEEP, ctrl);
+ if (res)
+ return res;
+
+ ctrl = 0;
+ for (unsigned int i = 0; i < YT921X_ACL_ENT_PER_BLK; i++) {
+ const struct yt921x_acl_rule *other = aclblk->rules[i];
+
+ if (!other)
+ continue;
+
+ mask = other->mask;
+ for_each_set_bit(o, &mask, YT921X_ACL_ENT_PER_BLK)
+ ctrl |= YT921X_ACL_ENTRY_ENm(o) |
+ YT921X_ACL_ENTRY_GRPIDm(o, i);
+ }
+ res = yt921x_reg_write(priv, YT921X_ACLn_ENTRY(blkid), ctrl);
+ if (res)
+ return res;
+
+ /* Commit the block */
+ ctrl = YT921X_ACL_BLK_CMD_BLKID(blkid);
+ res = yt921x_reg_write(priv, YT921X_ACL_BLK_CMD, ctrl);
+ if (res)
+ return res;
+
+ return 0;
+}
+
+static int
+yt921x_acl_del(struct yt921x_priv *priv, enum tc_setup_type type,
+ unsigned long tag)
+{
+ struct yt921x_acl_rule *aclrule;
+ struct yt921x_acl_blk *aclblk;
+ bool refresh_en = false;
+ unsigned int binid;
+ unsigned int blkid;
+ unsigned int entid;
+ unsigned long m;
+ unsigned int o;
+ u32 ctrl;
+ int res;
+ int ret;
+
+ entid = yt921x_acl_find(priv, type, tag);
+ if (entid == UINT_MAX)
+ return -ENOENT;
+
+ blkid = entid / YT921X_ACL_ENT_PER_BLK;
+ binid = entid % YT921X_ACL_ENT_PER_BLK;
+ aclblk = priv->acl_blks[blkid];
+ aclrule = aclblk->rules[binid];
+
+ aclblk->rules[binid] = NULL;
+ ret = yt921x_acl_commit(priv, entid, aclrule->mask);
+ /* the kernel never rolls back on failure */
+
+ m = FIELD_GET(YT921X_ACL_KEYb_SPORTS_M, aclrule->entries[0].key[1]);
+ for_each_set_bit(o, &m, YT921X_PORT_NUM) {
+ struct yt921x_port *pp = &priv->ports[o];
+
+ if (!WARN_ON(!pp->acl_cnt)) {
+ pp->acl_cnt--;
+ if (pp->acl_cnt)
+ continue;
+ }
+ refresh_en = true;
+ }
+ if (refresh_en) {
+ ctrl = yt921x_acl_activated_ports(priv);
+ res = yt921x_reg_write(priv, YT921X_ACL_PORT, ctrl);
+ if (res)
+ ret = res;
+ }
+
+ if (aclrule->action[0] & YT921X_ACL_ACTa_METER_EN)
+ clear_bit(FIELD_GET(YT921X_ACL_ACTa_METER_ID_M,
+ aclrule->action[0]),
+ priv->meters_map);
+ priv->acl_masks[blkid] &= ~aclrule->mask;
+ kvfree(aclrule);
+ if (!priv->acl_masks[blkid]) {
+ kvfree(aclblk);
+ priv->acl_blks[blkid] = NULL;
+ }
+ return ret;
+}
+
+static int
+yt921x_acl_add(struct yt921x_priv *priv,
+ const struct yt921x_acl_rule_ext *ruleext,
+ struct netlink_ext_ack *extack)
+{
+ unsigned int entscnt = hweight8(ruleext->r.mask);
+ struct yt921x_acl_rule *aclrule;
+ struct yt921x_acl_blk *aclblk;
+ bool use_trap = false;
+ unsigned int meterid;
+ unsigned int binid;
+ unsigned int blkid;
+ unsigned int entid;
+ unsigned long m;
+ unsigned int o;
+ u32 ctrl;
+ int res;
+
+ /* Allocate resources */
+ entid = yt921x_acl_reserve(priv, entscnt, extack);
+ if (entid == UINT_MAX)
+ return -EOPNOTSUPP;
+
+ if (!(ruleext->r.action[0] & YT921X_ACL_ACTa_METER_EN)) {
+ meterid = YT921X_METER_NUM;
+ } else {
+ meterid = find_first_zero_bit(priv->meters_map,
+ YT921X_METER_NUM);
+ if (meterid < YT921X_METER_NUM) {
+ res = yt921x_meter_config(priv, meterid,
+ &ruleext->marker);
+ if (res)
+ return res;
+ } else if (ruleext->r.sw_assisted) {
+ use_trap = true;
+ } else {
+ NL_SET_ERR_MSG_MOD(extack,
+ "No more meters available");
+ return -EOPNOTSUPP;
+ }
+ }
+
+ /* Prepare acl block ctrlblk */
+ blkid = entid / YT921X_ACL_ENT_PER_BLK;
+ binid = entid % YT921X_ACL_ENT_PER_BLK;
+ aclblk = priv->acl_blks[blkid];
+ if (!aclblk) {
+ aclblk = kvzalloc_obj(*aclblk);
+ if (!aclblk)
+ return -ENOMEM;
+ priv->acl_blks[blkid] = aclblk;
+ }
+
+ /* Prepare acl rule ctrlblk */
+ aclrule = kvmemdup(&ruleext->r,
+ offsetof(struct yt921x_acl_rule, entries[entscnt]),
+ GFP_KERNEL);
+ if (!aclrule) {
+ res = -ENOMEM;
+ goto err;
+ }
+
+ /* Replace the placeholder resource IDs */
+ aclrule->mask = 0;
+ m = priv->acl_masks[blkid];
+ for_each_clear_bit(o, &m, YT921X_ACL_ENT_PER_BLK) {
+ aclrule->mask |= BIT(o);
+ entscnt--;
+ if (!entscnt)
+ break;
+ }
+
+ if (use_trap) {
+ aclrule->action[2] &= ~YT921X_ACL_ACTc_FWD_REDIR_DPORTS_M &
+ ~YT921X_ACL_ACTc_FWD_M;
+ aclrule->action[2] |= YT921X_ACL_ACTc_FWD_EN |
+ YT921X_ACL_ACTc_FWD_TRAP;
+ }
+ if (meterid < YT921X_METER_NUM)
+ aclrule->action[0] |= YT921X_ACL_ACTa_METER_ID(meterid);
+ else
+ aclrule->action[0] &= ~YT921X_ACL_ACTa_METER_EN;
+
+ /* Activate ACL if needed */
+ ctrl = 0;
+ m = FIELD_GET(YT921X_ACL_KEYb_SPORTS_M, ruleext->r.entries[0].key[1]);
+ for_each_set_bit(o, &m, YT921X_PORT_NUM)
+ if (!priv->ports[o].acl_cnt)
+ ctrl |= YT921X_ACL_PORT_PORTn(o);
+ if (ctrl) {
+ ctrl |= yt921x_acl_activated_ports(priv);
+ res = yt921x_reg_write(priv, YT921X_ACL_PORT, ctrl);
+ if (res)
+ goto err;
+ }
+
+ /* Write rules */
+ aclblk->rules[binid] = aclrule;
+ res = yt921x_acl_commit(priv, entid, aclrule->mask);
+ if (res) {
+ aclblk->rules[binid] = NULL;
+ goto err;
+ }
+
+ /* Bookkeeping */
+ m = FIELD_GET(YT921X_ACL_KEYb_SPORTS_M, ruleext->r.entries[0].key[1]);
+ for_each_set_bit(o, &m, YT921X_PORT_NUM)
+ priv->ports[o].acl_cnt++;
+
+ if (meterid < YT921X_METER_NUM)
+ set_bit(meterid, priv->meters_map);
+ priv->acl_masks[blkid] |= aclrule->mask;
+ return 0;
+
+err:
+ kvfree(aclrule);
+ if (!priv->acl_masks[blkid]) {
+ kvfree(aclblk);
+ priv->acl_blks[blkid] = NULL;
+ }
+ return res;
+}
+
+int
+yt921x_dsa_cls_flower_del(struct dsa_switch *ds, int port,
+ struct flow_cls_offload *cls, bool ingress)
+{
+ struct yt921x_priv *priv = dsa_to_yt921x_priv(ds);
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_acl_del(priv, TC_SETUP_CLSFLOWER, cls->cookie);
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+int
+yt921x_dsa_cls_flower_add(struct dsa_switch *ds, int port,
+ struct flow_cls_offload *cls, bool ingress)
+{
+ struct netlink_ext_ack *extack = cls->common.extack;
+ struct yt921x_priv *priv = dsa_to_yt921x_priv(ds);
+ struct yt921x_acl_rule_ext ruleext;
+ int res;
+
+ res = yt921x_acl_rule_ext_parse_flow(&ruleext, port, cls, ingress,
+ priv);
+ if (res)
+ return res;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_acl_add(priv, &ruleext, extack);
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+static int
+yt921x_mirror_del(struct yt921x_priv *priv, int port, bool ingress)
+{
+ u32 mask;
+
+ if (ingress)
+ mask = YT921X_MIRROR_IGR_PORTn(port);
+ else
+ mask = YT921X_MIRROR_EGR_PORTn(port);
+ return yt921x_reg_clear_bits(priv, YT921X_MIRROR, mask);
+}
+
+static int
+yt921x_mirror_add(struct yt921x_priv *priv, int port, bool ingress,
+ int to_local_port, struct netlink_ext_ack *extack)
+{
+ u32 srcs;
+ u32 ctrl;
+ u32 val;
+ u32 dst;
+ int res;
+
+ if (ingress)
+ srcs = YT921X_MIRROR_IGR_PORTn(port);
+ else
+ srcs = YT921X_MIRROR_EGR_PORTn(port);
+ dst = YT921X_MIRROR_PORT(to_local_port);
+
+ res = yt921x_reg_read(priv, YT921X_MIRROR, &val);
+ if (res)
+ return res;
+
+ /* other mirror tasks & different dst port -> conflict */
+ if ((val & ~srcs & (YT921X_MIRROR_EGR_PORTS_M |
+ YT921X_MIRROR_IGR_PORTS_M)) &&
+ (val & YT921X_MIRROR_PORT_M) != dst) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Sniffer port is already configured, delete existing rules & retry");
+ return -EBUSY;
+ }
+
+ ctrl = val & ~YT921X_MIRROR_PORT_M;
+ ctrl |= srcs;
+ ctrl |= dst;
+
+ if (ctrl == val)
+ return 0;
+
+ return yt921x_reg_write(priv, YT921X_MIRROR, ctrl);
+}
+
+void
+yt921x_dsa_port_mirror_del(struct dsa_switch *ds, int port,
+ struct dsa_mall_mirror_tc_entry *mirror)
+{
+ struct yt921x_priv *priv = dsa_to_yt921x_priv(ds);
+ struct device *dev = yt921x_priv_to_device(priv);
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_mirror_del(priv, port, mirror->ingress);
+ mutex_unlock(&priv->reg_lock);
+
+ if (res)
+ dev_err(dev, "Failed to %s port %d: %i\n", "unmirror",
+ port, res);
+}
+
+int
+yt921x_dsa_port_mirror_add(struct dsa_switch *ds, int port,
+ struct dsa_mall_mirror_tc_entry *mirror,
+ bool ingress, struct netlink_ext_ack *extack)
+{
+ struct yt921x_priv *priv = dsa_to_yt921x_priv(ds);
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_mirror_add(priv, port, ingress,
+ mirror->to_local_port, extack);
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+int yt921x_chip_setup_tc(struct yt921x_priv *priv)
+{
+ unsigned int op_ns;
+ u32 ctrl;
+ int res;
+
+ op_ns = 8 * priv->cycle_ns;
+
+ ctrl = max(priv->meter_slot_ns / op_ns, YT921X_METER_SLOT_MIN);
+ res = yt921x_reg_write(priv, YT921X_METER_SLOT, ctrl);
+ if (res)
+ return res;
+ priv->meter_slot_ns = ctrl * op_ns;
+
+ ctrl = max(priv->port_shape_slot_ns / op_ns,
+ YT921X_PORT_SHAPE_SLOT_MIN);
+ res = yt921x_reg_write(priv, YT921X_PORT_SHAPE_SLOT, ctrl);
+ if (res)
+ return res;
+ priv->port_shape_slot_ns = ctrl * op_ns;
+
+ return 0;
+}
+
+int yt921x_chip_setup_acl(struct yt921x_priv *priv)
+{
+ u32 ctrl;
+ int res;
+
+ ctrl = YT921X_ACL_PERMIT_UNMATCH_PORTS_M;
+ res = yt921x_reg_write(priv, YT921X_ACL_PERMIT_UNMATCH, ctrl);
+ if (res)
+ return res;
+
+ res = yt921x_reg_write(priv, YT921X_ACL_PORT, 0);
+ if (res)
+ return res;
+
+ for (unsigned int o = 0; o < ARRAY_SIZE(yt921x_udf_maps); o++) {
+ ctrl = YT921X_UDF_CTRL_UDF_OFFSET(yt921x_udf_maps[o].offset) |
+ YT921X_UDF_CTRL_UDF_TYPE(yt921x_udf_maps[o].type);
+ res = yt921x_reg_write(priv, YT921X_UDFn_CTRL(o), ctrl);
+ if (res)
+ return res;
+ }
+
+ return 0;
+}
diff --git a/drivers/net/dsa/motorcomm/tc.h b/drivers/net/dsa/motorcomm/tc.h
new file mode 100644
index 000000000000..cc53f148d690
--- /dev/null
+++ b/drivers/net/dsa/motorcomm/tc.h
@@ -0,0 +1,387 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2026 David Yang
+ */
+
+#ifndef _YT_TC_H
+#define _YT_TC_H
+
+#include <net/dsa.h>
+#include <net/pkt_cls.h>
+
+#define YT921X_ACLn_ACT(n) (0x1c0000 + 0x10 * (n))
+#define YT921X_ACL_ACTc_STAG_M GENMASK(26, 25)
+#define YT921X_ACL_ACTc_STAG(x) FIELD_PREP(YT921X_ACL_ACTc_STAG_M, (x))
+#define YT921X_ACL_ACTc_STAG_DONTCARE YT921X_ACL_ACTc_STAG(0)
+#define YT921X_ACL_ACTc_STAG_UNTAG YT921X_ACL_ACTc_STAG(1)
+#define YT921X_ACL_ACTc_STAG_TAG YT921X_ACL_ACTc_STAG(2)
+#define YT921X_ACL_ACTc_STAG_KEEP YT921X_ACL_ACTc_STAG(3)
+#define YT921X_ACL_ACTc_CTAG_M GENMASK(24, 23)
+#define YT921X_ACL_ACTc_CTAG(x) FIELD_PREP(YT921X_ACL_ACTc_CTAG_M, (x))
+#define YT921X_ACL_ACTc_CTAG_DONTCARE YT921X_ACL_ACTc_CTAG(0)
+#define YT921X_ACL_ACTc_CTAG_UNTAG YT921X_ACL_ACTc_CTAG(1)
+#define YT921X_ACL_ACTc_CTAG_TAG YT921X_ACL_ACTc_CTAG(2)
+#define YT921X_ACL_ACTc_CTAG_KEEP YT921X_ACL_ACTc_CTAG(3)
+#define YT921X_ACL_ACTc_FWD_M GENMASK(22, 21)
+#define YT921X_ACL_ACTc_FWD(x) FIELD_PREP(YT921X_ACL_ACTc_FWD_M, (x))
+#define YT921X_ACL_ACTc_FWD_FWD YT921X_ACL_ACTc_FWD(0)
+#define YT921X_ACL_ACTc_FWD_COPY YT921X_ACL_ACTc_FWD(1)
+#define YT921X_ACL_ACTc_FWD_REDIR YT921X_ACL_ACTc_FWD(2)
+#define YT921X_ACL_ACTc_FWD_TRAP YT921X_ACL_ACTc_FWD(3)
+#define YT921X_ACL_ACTc_FWD_REDIR_DPORTS_M GENMASK(20, 10)
+#define YT921X_ACL_ACTc_FWD_REDIR_DPORTS(x) FIELD_PREP(YT921X_ACL_ACTc_FWD_REDIR_DPORTS_M, (x))
+#define YT921X_ACL_ACTc_FWD_REDIR_DPORTn(port) BIT((port) + 10)
+#define YT921X_ACL_ACTc_FWD_EN BIT(9)
+#define YT921X_ACL_ACTc_SDEI BIT(8)
+#define YT921X_ACL_ACTc_SDEI_REPLACE BIT(7)
+#define YT921X_ACL_ACTc_SPRI_M GENMASK(6, 4)
+#define YT921X_ACL_ACTc_SPRI(x) FIELD_PREP(YT921X_ACL_ACTc_SPRI_M, (x))
+#define YT921X_ACL_ACTc_SPRI_REPLACE BIT(3)
+#define YT921X_ACL_ACTbc_SVID_M GENMASK_ULL(34, 23)
+#define YT921X_ACL_ACTbc_SVID(x) FIELD_PREP(YT921X_ACL_ACTbc_SVID_M, (x))
+#define YT921X_ACL_ACTb_SVID_REPLACE BIT(22)
+#define YT921X_ACL_ACTb_CDEI BIT(21)
+#define YT921X_ACL_ACTb_CDEI_REPLACE BIT(20)
+#define YT921X_ACL_ACTb_CPRI_M GENMASK(19, 17)
+#define YT921X_ACL_ACTb_CPRI(x) FIELD_PREP(YT921X_ACL_ACTb_CPRI_M, (x))
+#define YT921X_ACL_ACTb_CPRI_REPLACE BIT(16)
+#define YT921X_ACL_ACTb_CVID_M GENMASK(15, 4)
+#define YT921X_ACL_ACTb_CVID(x) FIELD_PREP(YT921X_ACL_ACTb_CVID_M, (x))
+#define YT921X_ACL_ACTb_CVID_REPLACE BIT(3)
+#define YT921X_ACL_ACTb_PRIO_M GENMASK(2, 0)
+#define YT921X_ACL_ACTb_PRIO(x) FIELD_PREP(YT921X_ACL_ACTb_PRIO_M, (x))
+#define YT921X_ACL_ACTa_PRIO_EN BIT(31)
+#define YT921X_ACL_ACTa_COLOR_M GENMASK(30, 29)
+#define YT921X_ACL_ACTa_COLOR(x) FIELD_PREP(YT921X_ACL_ACTa_COLOR_M, (x))
+#define YT921X_ACL_ACTa_COLOR_GREEN YT921X_ACL_ACTa_COLOR(0)
+#define YT921X_ACL_ACTa_COLOR_YELLOW YT921X_ACL_ACTa_COLOR(1)
+#define YT921X_ACL_ACTa_COLOR_RED YT921X_ACL_ACTa_COLOR(2)
+#define YT921X_ACL_ACTa_COLOR_EN BIT(28)
+#define YT921X_ACL_ACTa_DSCP_M GENMASK(27, 22)
+#define YT921X_ACL_ACTa_DSCP(x) FIELD_PREP(YT921X_ACL_ACTa_DSCP_M, (x))
+#define YT921X_ACL_ACTa_DSCP_REPLACE BIT(21)
+#define YT921X_ACL_ACTa_METER_ID_M GENMASK(20, 15)
+#define YT921X_ACL_ACTa_METER_ID(x) FIELD_PREP(YT921X_ACL_ACTa_METER_ID_M, (x))
+#define YT921X_ACL_ACTa_METER_EN BIT(14)
+#define YT921X_ACL_ACTa_MIRROR_EN BIT(13)
+#define YT921X_ACL_ACTa_FLOWSTAT_EN BIT(12)
+#define YT921X_ACL_ACTa_FLOWSTAT_ID_M GENMASK(11, 6)
+#define YT921X_ACL_ACTa_FLOWSTAT_ID(x) FIELD_PREP(YT921X_ACL_ACTa_FLOWSTAT_ID_M, (x))
+#define YT921X_ACL_ACTa_GPIO_EN BIT(5)
+#define YT921X_ACL_ACTa_GPIO_PIN_M GENMASK(4, 1)
+#define YT921X_ACL_ACTa_GPIO_PIN(x) FIELD_PREP(YT921X_ACL_ACTa_GPIO_PIN_M, (x))
+#define YT921X_ACL_ACTa_INTR_EN BIT(0)
+#define YT921X_ACL_BLK_KEEP 0x201000
+#define YT921X_ACL_BLK_KEEP_GRPIDn_M(bin) (7 << (4 * (bin) + 1))
+#define YT921X_ACL_BLK_KEEP_GRPIDn(bin, x) ((x) << (4 * (bin) + 1))
+#define YT921X_ACL_BLK_KEEP_KEEPn(bin) BIT(4 * (bin))
+#define YT921X_ACL_PORT 0x202000
+#define YT921X_ACL_PORT_PORTS_M GENMASK(10, 0)
+#define YT921X_ACL_PORT_PORTS(x) FIELD_PREP(YT921X_ACL_PORT_PORTS_M, (x))
+#define YT921X_ACL_PORT_PORTn(port) BIT(port)
+#define YT921X_ACL_BLK_CMD 0x202004
+#define YT921X_ACL_BLK_CMD_BLKID_M GENMASK(6, 1)
+#define YT921X_ACL_BLK_CMD_BLKID(x) FIELD_PREP(YT921X_ACL_BLK_CMD_BLKID_M, (x))
+#define YT921X_ACL_BLK_CMD_MODIFY BIT(0)
+#define YT921X_ACLn_ENTRY(blk) (0x203000 + 4 * (blk))
+#define YT921X_ACL_ENTRY_GRPIDm_M(bin) (7 << (4 * (bin) + 1))
+#define YT921X_ACL_ENTRY_GRPIDm(bin, x) ((x) << (4 * (bin) + 1))
+#define YT921X_ACL_ENTRY_ENm(bin) BIT(4 * (bin))
+#define YT921X_ACLn_KEYm(blk, bin) (0x204000 + 0x200 * (bin) + 8 * (blk))
+#define YT921X_ACL_KEYb_ORD_M GENMASK(29, 21)
+#define YT921X_ACL_KEYb_ORD(x) FIELD_PREP(YT921X_ACL_KEYb_ORD_M, (x))
+#define YT921X_ACL_KEYb_SPORTS_M GENMASK(20, 10)
+#define YT921X_ACL_KEYb_SPORTS(x) FIELD_PREP(YT921X_ACL_KEYb_SPORTS_M, (x))
+#define YT921X_ACL_KEYb_SPORTn(port) BIT((port) + 10)
+#define YT921X_ACL_KEYb_REVERSE BIT(9) /* reverse match */
+#define YT921X_ACL_KEYb_TYPE_M GENMASK(8, 4)
+#define YT921X_ACL_KEYb_TYPE(x) FIELD_PREP(YT921X_ACL_KEYb_TYPE_M, (x))
+/* KEY_* fields need no masks */
+#define YT921X_ACLn_MASKm(blk, bin) (0x205000 + 0x200 * (bin) + 8 * (blk))
+
+enum yt921x_acl_type {
+ YT921X_ACL_TYPE_NA,
+ YT921X_ACL_TYPE_MAC_DA0,
+ YT921X_ACL_TYPE_MAC_SA0,
+ YT921X_ACL_TYPE_MAC_DA1_SA1,
+ YT921X_ACL_TYPE_VLAN,
+ YT921X_ACL_TYPE_VTAG,
+ YT921X_ACL_TYPE_IPV4_DA,
+ YT921X_ACL_TYPE_IPV4_SA,
+ YT921X_ACL_TYPE_IPV6_DA0,
+ YT921X_ACL_TYPE_IPV6_DA1,
+ YT921X_ACL_TYPE_IPV6_DA2,
+ YT921X_ACL_TYPE_IPV6_DA3,
+ YT921X_ACL_TYPE_IPV6_SA0,
+ YT921X_ACL_TYPE_IPV6_SA1,
+ YT921X_ACL_TYPE_IPV6_SA2,
+ YT921X_ACL_TYPE_IPV6_SA3,
+ YT921X_ACL_TYPE_MISC,
+ YT921X_ACL_TYPE_L4,
+ /* Each User Defined Filter entry contains 2 UDF windows (UDF_CTRL 2i
+ * and 2i+1)
+ */
+ YT921X_ACL_TYPE_UDF0,
+ YT921X_ACL_TYPE_UDF1,
+ YT921X_ACL_TYPE_UDF2,
+ YT921X_ACL_TYPE_UDF3,
+ YT921X_ACL_TYPE_UDF4,
+ YT921X_ACL_TYPE_UDF5,
+ YT921X_ACL_TYPE_UDF6,
+ YT921X_ACL_TYPE_UDF7,
+ YT921X_ACL_TYPE_ETHERTYPE,
+ YT921X_ACL_TYPE_NUM
+};
+
+/* Range: turn KEY:MASK into MIN:MAX */
+
+#define YT921X_ACL_BINb_MAC_xA0_L3_TYPE_M GENMASK(3, 0)
+#define YT921X_ACL_BINb_MAC_xA0_L3_TYPE(x) FIELD_PREP(YT921X_ACL_BINb_MAC_xA0_L3_TYPE_M, (x))
+#define YT921X_ACL_BINa_MAC_xA0_MAC_xA0_M GENMASK(31, 0)
+
+#define YT921X_ACL_BINb_MAC_DA1_SA1_L2_TYPE_M GENMASK(2, 0)
+#define YT921X_ACL_BINb_MAC_DA1_SA1_L2_TYPE(x) FIELD_PREP(YT921X_ACL_BINb_MAC_DA1_SA1_L2_TYPE_M, (x))
+#define YT921X_ACL_BINa_MAC_DA1_SA1_MAC_DA1_M GENMASK(31, 16)
+#define YT921X_ACL_BINa_MAC_DA1_SA1_MAC_SA1_M GENMASK(15, 0)
+
+#define YT921X_ACL_KEYb_VLAN_SVID_RANGE_EN BIT(31)
+#define YT921X_ACL_KEYb_VLAN_CVID_RANGE_EN BIT(30)
+#define YT921X_ACL_BINb_VLAN_CDEI BIT(3)
+#define YT921X_ACL_BINb_VLAN_CPRI_M GENMASK(2, 0)
+#define YT921X_ACL_BINb_VLAN_CPRI(x) FIELD_PREP(YT921X_ACL_BINb_VLAN_CPRI_M, (x))
+#define YT921X_ACL_BINa_VLAN_CTAG_FMT_M GENMASK(31, 30)
+#define YT921X_ACL_BINa_VLAN_CTAG_FMT(x) FIELD_PREP(YT921X_ACL_BINa_VLAN_CTAG_FMT_M, (x))
+#define YT921X_ACL_BINa_VLAN_SDEI BIT(29)
+#define YT921X_ACL_BINa_VLAN_SPRI_M GENMASK(28, 26)
+#define YT921X_ACL_BINa_VLAN_SPRI(x) FIELD_PREP(YT921X_ACL_BINa_VLAN_SPRI_M, (x))
+#define YT921X_ACL_BINa_VLAN_STAG_FMT_M GENMASK(25, 24)
+#define YT921X_ACL_BINa_VLAN_STAG_FMT(x) FIELD_PREP(YT921X_ACL_BINa_VLAN_STAG_FMT_M, (x))
+#define YT921X_ACL_BINa_VLAN_SVID_M GENMASK(23, 12)
+#define YT921X_ACL_BINa_VLAN_SVID(x) FIELD_PREP(YT921X_ACL_BINa_VLAN_SVID_M, (x))
+#define YT921X_ACL_BINa_VLAN_CVID_M GENMASK(11, 0)
+#define YT921X_ACL_BINa_VLAN_CVID(x) FIELD_PREP(YT921X_ACL_BINa_VLAN_CVID_M, (x))
+
+#define YT921X_ACL_KEYb_VTAG_SVID_RANGE_EN BIT(31)
+#define YT921X_ACL_KEYb_VTAG_CVID_RANGE_EN BIT(30)
+#define YT921X_ACL_BINa_VTAG_CDEI BIT(31)
+#define YT921X_ACL_BINa_VTAG_CPRI_M GENMASK(30, 28)
+#define YT921X_ACL_BINa_VTAG_CPRI(x) FIELD_PREP(YT921X_ACL_BINa_VTAG_CPRI_M, (x))
+#define YT921X_ACL_BINa_VTAG_SDEI BIT(27)
+#define YT921X_ACL_BINa_VTAG_SPRI_M GENMASK(26, 24)
+#define YT921X_ACL_BINa_VTAG_SPRI(x) FIELD_PREP(YT921X_ACL_BINa_VTAG_SPRI_M, (x))
+#define YT921X_ACL_BINa_VTAG_SVID_M GENMASK(23, 12)
+#define YT921X_ACL_BINa_VTAG_SVID(x) FIELD_PREP(YT921X_ACL_BINa_VTAG_SVID_M, (x))
+#define YT921X_ACL_BINa_VTAG_CVID_M GENMASK(11, 0)
+#define YT921X_ACL_BINa_VTAG_CVID(x) FIELD_PREP(YT921X_ACL_BINa_VTAG_CVID_M, (x))
+
+#define YT921X_ACL_KEYb_IPV4_ADDR_RANGE_EN BIT(30)
+#define YT921X_ACL_BINb_IPV4_FRAG BIT(3)
+#define YT921X_ACL_BINb_IPV4_L4_TYPE_M GENMASK(2, 0)
+#define YT921X_ACL_BINb_IPV4_L4_TYPE(x) FIELD_PREP(YT921X_ACL_BINb_IPV4_L4_TYPE_M, (x))
+#define YT921X_ACL_BINa_IPV4_ADDR_M GENMASK(31, 0)
+
+#define YT921X_ACL_BINb_IPV6_L4_TYPE_M GENMASK(2, 0)
+#define YT921X_ACL_BINb_IPV6_L4_TYPE(x) FIELD_PREP(YT921X_ACL_BINb_IPV6_L4_TYPE_M, (x))
+#define YT921X_ACL_BINa_IPV6_ADDRx_M GENMASK(31, 0)
+
+#define YT921X_ACL_BINb_IPV6_xA1_IP_OPTION BIT(3)
+
+#define YT921X_ACL_BINb_IPV6_xA2_FIRST_FRAG BIT(3)
+
+#define YT921X_ACL_KEYb_IPV6_xA3_ADDR_RANGE_EN BIT(30)
+#define YT921X_ACL_BINb_IPV6_xA3_FRAG BIT(3)
+
+#define YT921X_ACL_BINb_MISC_FRAG BIT(3)
+#define YT921X_ACL_BINb_MISC_L4_TYPE_M GENMASK(2, 0)
+#define YT921X_ACL_BINb_MISC_L4_TYPE(x) FIELD_PREP(YT921X_ACL_BINb_MISC_L4_TYPE_M, (x))
+#define YT921X_ACL_BINa_MISC_PPPOE_FLAG BIT(30)
+#define YT921X_ACL_BINa_MISC_FIRST_FRAG BIT(29)
+#define YT921X_ACL_BINa_MISC_IP_OPTION BIT(28)
+#define YT921X_ACL_BINa_MISC_TCP_FLAGS_M GENMASK(27, 20)
+#define YT921X_ACL_BINa_MISC_TCP_FLAGS(x) FIELD_PREP(YT921X_ACL_BINa_MISC_TCP_FLAGS_M, (x))
+#define YT921X_ACL_BINa_MISC_IP_PROTO_M GENMASK(19, 12)
+#define YT921X_ACL_BINa_MISC_IP_PROTO(x) FIELD_PREP(YT921X_ACL_BINa_MISC_IP_PROTO_M, (x))
+#define YT921X_ACL_BINa_MISC_TOS_M GENMASK(11, 4)
+#define YT921X_ACL_BINa_MISC_TOS(x) FIELD_PREP(YT921X_ACL_BINa_MISC_TOS_M, (x))
+#define YT921X_ACL_BINa_MISC_L3_TYPE_M GENMASK(3, 0)
+#define YT921X_ACL_BINa_MISC_L3_TYPE(x) FIELD_PREP(YT921X_ACL_BINa_MISC_L3_TYPE_M, (x))
+
+#define YT921X_ACL_KEYb_L4_DPORT_RANGE_EN BIT(31)
+#define YT921X_ACL_KEYb_L4_SPORT_RANGE_EN BIT(30)
+#define YT921X_ACL_BINb_L4_FRAG BIT(3)
+#define YT921X_ACL_BINb_L4_TYPE_M GENMASK(2, 0)
+#define YT921X_ACL_BINb_L4_TYPE(x) FIELD_PREP(YT921X_ACL_BINb_L4_TYPE_M, (x))
+#define YT921X_ACL_BINa_L4_DPORT_M GENMASK(31, 16)
+#define YT921X_ACL_BINa_L4_SPORT_M GENMASK(15, 0)
+
+#define YT921X_ACL_BINb_UDF_IS_IGMP BIT(0)
+#define YT921X_ACL_BINa_UDF_UDF0_M GENMASK(31, 16)
+#define YT921X_ACL_BINa_UDF_UDF0(x) FIELD_PREP(YT921X_ACL_BINa_UDF_UDF0_M, (x))
+#define YT921X_ACL_BINa_UDF_UDF1_M GENMASK(15, 0)
+#define YT921X_ACL_BINa_UDF_UDF1(x) FIELD_PREP(YT921X_ACL_BINa_UDF_UDF1_M, (x))
+
+#define YT921X_ACL_KEYb_ETHERTYPE_ETHERTYPE_RANGE_EN BIT(30)
+#define YT921X_ACL_BINb_ETHERTYPE_L4_TYPE_M GENMASK(2, 0)
+#define YT921X_ACL_BINb_ETHERTYPE_L4_TYPE(x) FIELD_PREP(YT921X_ACL_BINb_ETHERTYPE_L4_TYPE_M, (x))
+#define YT921X_ACL_BINa_ETHERTYPE_ETHERTYPE_M GENMASK(15, 0)
+#define YT921X_ACL_BINa_ETHERTYPE_ETHERTYPE(x) FIELD_PREP(YT921X_ACL_BINa_ETHERTYPE_ETHERTYPE_M, (x))
+
+enum yt921x_l2_type {
+ YT921X_L2_TYPE_ETH,
+ YT921X_L2_TYPE_ETHV2,
+ YT921X_L2_TYPE_ETHSAP,
+ YT921X_L2_TYPE_ETHSNAP,
+};
+
+enum yt921x_l3_type {
+ YT921X_L3_TYPE_OTHER,
+ YT921X_L3_TYPE_IPV4,
+ YT921X_L3_TYPE_IPV6,
+ YT921X_L3_TYPE_ARP,
+ YT921X_L3_TYPE_LLDP,
+ YT921X_L3_TYPE_PAE,
+ YT921X_L3_TYPE_ERP,
+ YT921X_L3_TYPE_SLOW_PROTOCOL,
+};
+
+enum yt921x_l4_type {
+ YT921X_L4_TYPE_OTHER,
+ YT921X_L4_TYPE_TCP,
+ YT921X_L4_TYPE_UDP,
+ YT921X_L4_TYPE_UDPLITE,
+ YT921X_L4_TYPE_ICMP,
+ YT921X_L4_TYPE_IGMP,
+ YT921X_L4_TYPE_MLD,
+ YT921X_L4_TYPE_ND,
+};
+
+#define YT921X_UDFn_CTRL(x) (0x210094 + 4 * (x))
+#define YT921X_UDF_CTRL_UDF_TYPE_M GENMASK(8, 7)
+#define YT921X_UDF_CTRL_UDF_TYPE(x) FIELD_PREP(YT921X_UDF_CTRL_UDF_TYPE_M, (x))
+#define YT921X_UDF_CTRL_UDF_OFFSET_M GENMASK(6, 0)
+#define YT921X_UDF_CTRL_UDF_OFFSET(x) FIELD_PREP(YT921X_UDF_CTRL_UDF_OFFSET_M, (x))
+
+enum yt921x_udf_type {
+ YT921X_UDF_TYPE_L2,
+ YT921X_UDF_TYPE_L3,
+ YT921X_UDF_TYPE_L4,
+};
+
+#define YT921X_PORTn_RATE(port) (0x220000 + 4 * (port))
+#define YT921X_PORT_RATE_GAP_VALUE GENMASK(4, 0) /* default 20 */
+#define YT921X_METER_SLOT 0x220104
+#define YT921X_METER_SLOT_SLOT_M GENMASK(11, 0)
+#define YT921X_PORTn_METER(port) (0x220108 + 4 * (port))
+#define YT921X_PORT_METER_EN BIT(4)
+#define YT921X_PORT_METER_ID_M GENMASK(3, 0)
+#define YT921X_PORT_METER_ID(x) FIELD_PREP(YT921X_PORT_METER_ID_M, (x))
+#define YT921X_METERn_CTRL(x) (0x220800 + 0x10 * (x))
+#define YT921X_METER_CTRLc_METER_EN BIT(14)
+#define YT921X_METER_CTRLc_TOKEN_OVERFLOW_EN BIT(13) /* RFC4115: yellow use unused green bw */
+#define YT921X_METER_CTRLc_DROP_M GENMASK(12, 11)
+#define YT921X_METER_CTRLc_DROP(x) FIELD_PREP(YT921X_METER_CTRLc_DROP_M, (x))
+#define YT921X_METER_CTRLc_DROP_GYR YT921X_METER_CTRLc_DROP(0)
+#define YT921X_METER_CTRLc_DROP_YR YT921X_METER_CTRLc_DROP(1)
+#define YT921X_METER_CTRLc_DROP_R YT921X_METER_CTRLc_DROP(2)
+#define YT921X_METER_CTRLc_DROP_NONE YT921X_METER_CTRLc_DROP(3)
+#define YT921X_METER_CTRLc_COLOR_BLIND BIT(10)
+#define YT921X_METER_CTRLc_UNIT_M GENMASK(9, 7)
+#define YT921X_METER_CTRLc_UNIT(x) FIELD_PREP(YT921X_METER_CTRLc_UNIT_M, (x))
+#define YT921X_METER_CTRLc_BYTE_MODE_INCLUDE_GAP BIT(6) /* +GAP_VALUE bytes each packet */
+#define YT921X_METER_CTRLc_PKT_MODE BIT(5) /* 0: byte rate mode */
+#define YT921X_METER_CTRLc_RFC2698 BIT(4) /* 0: RFC4115 */
+#define YT921X_METER_CTRLbc_CBS_M GENMASK_ULL(35, 20)
+#define YT921X_METER_CTRLbc_CBS(x) FIELD_PREP(YT921X_METER_CTRLbc_CBS_M, (x))
+#define YT921X_METER_CTRLb_CIR_M GENMASK(19, 2)
+#define YT921X_METER_CTRLb_CIR(x) FIELD_PREP(YT921X_METER_CTRLb_CIR_M, (x))
+#define YT921X_METER_CTRLab_EBS_M GENMASK_ULL(33, 18)
+#define YT921X_METER_CTRLab_EBS(x) FIELD_PREP(YT921X_METER_CTRLab_EBS_M, (x))
+#define YT921X_METER_CTRLa_EIR_M GENMASK(17, 0)
+#define YT921X_METER_CTRLa_EIR(x) FIELD_PREP(YT921X_METER_CTRLa_EIR_M, (x))
+#define YT921X_METERn_STAT(x) (0x221000 + 8 * (x))
+
+#define YT921X_MIRROR 0x300300
+#define YT921X_MIRROR_IGR_PORTS_M GENMASK(26, 16)
+#define YT921X_MIRROR_IGR_PORTS(x) FIELD_PREP(YT921X_MIRROR_IGR_PORTS_M, (x))
+#define YT921X_MIRROR_IGR_PORTn(port) BIT((port) + 16)
+#define YT921X_MIRROR_EGR_PORTS_M GENMASK(14, 4)
+#define YT921X_MIRROR_EGR_PORTS(x) FIELD_PREP(YT921X_MIRROR_EGR_PORTS_M, (x))
+#define YT921X_MIRROR_EGR_PORTn(port) BIT((port) + 4)
+#define YT921X_MIRROR_PORT_M GENMASK(3, 0)
+#define YT921X_MIRROR_PORT(x) FIELD_PREP(YT921X_MIRROR_PORT_M, (x))
+
+#define YT921X_PORT_SHAPE_SLOT 0x34000c
+#define YT921X_PORT_SHAPE_SLOT_SLOT_M GENMASK(11, 0)
+#define YT921X_PORTn_SHAPE_CTRL(port) (0x354000 + 8 * (port))
+#define YT921X_PORT_SHAPE_CTRLb_EN BIT(4)
+#define YT921X_PORT_SHAPE_CTRLb_PKT_MODE BIT(3) /* 0: byte rate mode */
+#define YT921X_PORT_SHAPE_CTRLb_UNIT_M GENMASK(2, 0)
+#define YT921X_PORT_SHAPE_CTRLb_UNIT(x) FIELD_PREP(YT921X_PORT_SHAPE_CTRLb_UNIT_M, (x))
+#define YT921X_PORT_SHAPE_CTRLa_CBS_M GENMASK(31, 18)
+#define YT921X_PORT_SHAPE_CTRLa_CBS(x) FIELD_PREP(YT921X_PORT_SHAPE_CTRLa_CBS_M, (x))
+#define YT921X_PORT_SHAPE_CTRLa_CIR_M GENMASK(17, 0)
+#define YT921X_PORT_SHAPE_CTRLa_CIR(x) FIELD_PREP(YT921X_PORT_SHAPE_CTRLa_CIR_M, (x))
+#define YT921X_PORTn_SHAPE_STAT(port) (0x356000 + 4 * (port))
+
+#define YT921X_TOKEN_BYTE_C 1 /* 1 token = 2^1 byte */
+#define YT921X_TOKEN_PKT_C -6 /* 1 token = 2^-6 packets */
+#define YT921X_TOKEN_RATE_C -15
+#define YT921X_METER_SLOT_MIN 80
+#define YT921X_METER_UNIT_MAX ((1 << 3) - 1)
+#define YT921X_METER_CIR_MAX ((1 << 18) - 1)
+#define YT921X_METER_CBS_MAX ((1 << 16) - 1)
+#define YT921X_PORT_SHAPE_SLOT_MIN 80
+#define YT921X_SHAPE_UNIT_MAX ((1 << 3) - 1)
+#define YT921X_SHAPE_CIR_MAX ((1 << 18) - 1)
+#define YT921X_SHAPE_CBS_MAX ((1 << 14) - 1)
+
+#define YT921X_ACL_ENT_PER_BLK 8
+#define YT921X_ACL_NUM 384
+#define YT921X_ACL_ORD_NUM 512
+
+struct yt921x_acl_entry {
+ u32 key[2];
+ u32 mask[2];
+};
+
+struct yt921x_acl_rule {
+ unsigned long tag;
+ enum tc_setup_type type;
+
+ u32 action[3];
+ bool sw_assisted;
+
+ u8 mask;
+ struct yt921x_acl_entry entries[YT921X_ACL_ENT_PER_BLK];
+};
+
+struct yt921x_acl_blk {
+ struct yt921x_acl_rule *rules[YT921X_ACL_ENT_PER_BLK];
+};
+
+void yt921x_dsa_port_policer_del(struct dsa_switch *ds, int port);
+int
+yt921x_dsa_port_policer_add(struct dsa_switch *ds, int port,
+ const struct flow_action_police *police,
+ struct netlink_ext_ack *extack);
+int
+yt921x_dsa_port_setup_tc(struct dsa_switch *ds, int port,
+ enum tc_setup_type type, void *type_data);
+int
+yt921x_dsa_cls_flower_del(struct dsa_switch *ds, int port,
+ struct flow_cls_offload *cls, bool ingress);
+int
+yt921x_dsa_cls_flower_add(struct dsa_switch *ds, int port,
+ struct flow_cls_offload *cls, bool ingress);
+void
+yt921x_dsa_port_mirror_del(struct dsa_switch *ds, int port,
+ struct dsa_mall_mirror_tc_entry *mirror);
+int
+yt921x_dsa_port_mirror_add(struct dsa_switch *ds, int port,
+ struct dsa_mall_mirror_tc_entry *mirror,
+ bool ingress, struct netlink_ext_ack *extack);
+
+int yt921x_chip_setup_tc(struct yt921x_priv *priv);
+int yt921x_chip_setup_acl(struct yt921x_priv *priv);
+
+#endif
diff --git a/drivers/net/dsa/mt7530-mdio.c b/drivers/net/dsa/mt7530-mdio.c
index de42f70afcfa..0be107dc6759 100644
--- a/drivers/net/dsa/mt7530-mdio.c
+++ b/drivers/net/dsa/mt7530-mdio.c
@@ -48,6 +48,9 @@ mt7530_regmap_read(void *context, unsigned int reg, unsigned int *val)
u16 page, r, lo, hi;
int ret;
+ /* Callers do not check for errors, keep the value deterministic */
+ *val = 0;
+
page = (reg >> 6) & 0x3ff;
r = (reg >> 2) & 0xf;
@@ -139,7 +142,7 @@ static const struct of_device_id mt7530_of_match[] = {
{ .compatible = "mediatek,mt7621", .data = &mt753x_table[ID_MT7621], },
{ .compatible = "mediatek,mt7530", .data = &mt753x_table[ID_MT7530], },
{ .compatible = "mediatek,mt7531", .data = &mt753x_table[ID_MT7531], },
- { /* sentinel */ },
+ { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, mt7530_of_match);
@@ -148,12 +151,14 @@ static const struct regmap_config regmap_config = {
.val_bits = 32,
.reg_stride = 4,
.max_register = MT7530_CREV,
- .disable_locking = true,
+ .lock = mt7530_mdio_regmap_lock,
+ .unlock = mt7530_mdio_regmap_unlock,
};
static int
mt7530_probe(struct mdio_device *mdiodev)
{
+ struct regmap_config rc = regmap_config;
struct mt7530_priv *priv;
struct device_node *dn;
int ret;
@@ -207,8 +212,9 @@ mt7530_probe(struct mdio_device *mdiodev)
return PTR_ERR(priv->io_pwr);
}
- priv->regmap = devm_regmap_init(priv->dev, &mt7530_regmap_bus, priv,
- &regmap_config);
+ rc.lock_arg = &priv->bus->mdio_lock;
+ priv->regmap = devm_regmap_init(priv->dev, &mt7530_regmap_bus,
+ priv, &rc);
if (IS_ERR(priv->regmap))
return PTR_ERR(priv->regmap);
diff --git a/drivers/net/dsa/mt7530-mmio.c b/drivers/net/dsa/mt7530-mmio.c
index fd68b1cd0630..cf3bdb23a2d5 100644
--- a/drivers/net/dsa/mt7530-mmio.c
+++ b/drivers/net/dsa/mt7530-mmio.c
@@ -14,7 +14,7 @@ static const struct of_device_id mt7988_of_match[] = {
{ .compatible = "airoha,en7581-switch", .data = &mt753x_table[ID_EN7581], },
{ .compatible = "econet,en7528-switch", .data = &mt753x_table[ID_EN7528], },
{ .compatible = "mediatek,mt7988-switch", .data = &mt753x_table[ID_MT7988], },
- { /* sentinel */ },
+ { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, mt7988_of_match);
diff --git a/drivers/net/dsa/mt7530.c b/drivers/net/dsa/mt7530.c
index 96832852c65a..7781a63b4e6f 100644
--- a/drivers/net/dsa/mt7530.c
+++ b/drivers/net/dsa/mt7530.c
@@ -155,97 +155,6 @@ core_clear(struct mt7530_priv *priv, u32 reg, u32 val)
}
static int
-mt7530_mii_write(struct mt7530_priv *priv, u32 reg, u32 val)
-{
- int ret;
-
- ret = regmap_write(priv->regmap, reg, val);
-
- if (ret < 0)
- dev_err(priv->dev,
- "failed to write mt7530 register\n");
-
- return ret;
-}
-
-static u32
-mt7530_mii_read(struct mt7530_priv *priv, u32 reg)
-{
- int ret;
- u32 val;
-
- ret = regmap_read(priv->regmap, reg, &val);
- if (ret) {
- WARN_ON_ONCE(1);
- dev_err(priv->dev,
- "failed to read mt7530 register\n");
- return 0;
- }
-
- return val;
-}
-
-static int
-mt7530_write(struct mt7530_priv *priv, u32 reg, u32 val)
-{
- int ret;
-
- mt7530_mutex_lock(priv);
-
- ret = mt7530_mii_write(priv, reg, val);
-
- mt7530_mutex_unlock(priv);
-
- return ret;
-}
-
-static u32
-_mt7530_read(struct mt7530_dummy_poll *p)
-{
- u32 val;
-
- mt7530_mutex_lock(p->priv);
-
- val = mt7530_mii_read(p->priv, p->reg);
-
- mt7530_mutex_unlock(p->priv);
-
- return val;
-}
-
-static u32
-mt7530_read(struct mt7530_priv *priv, u32 reg)
-{
- struct mt7530_dummy_poll p;
-
- INIT_MT7530_DUMMY_POLL(&p, priv, reg);
- return _mt7530_read(&p);
-}
-
-static void
-mt7530_rmw(struct mt7530_priv *priv, u32 reg,
- u32 mask, u32 set)
-{
- mt7530_mutex_lock(priv);
-
- regmap_update_bits(priv->regmap, reg, mask, set);
-
- mt7530_mutex_unlock(priv);
-}
-
-static void
-mt7530_set(struct mt7530_priv *priv, u32 reg, u32 val)
-{
- mt7530_rmw(priv, reg, val, val);
-}
-
-static void
-mt7530_clear(struct mt7530_priv *priv, u32 reg, u32 val)
-{
- mt7530_rmw(priv, reg, val, 0);
-}
-
-static int
mt7530_fdb_cmd(struct mt7530_priv *priv, enum mt7530_fdb_cmd cmd, u32 *rsp)
{
u32 val;
@@ -253,19 +162,15 @@ mt7530_fdb_cmd(struct mt7530_priv *priv, enum mt7530_fdb_cmd cmd, u32 *rsp)
/* Set the command operating upon the MAC address entries */
val = ATC_BUSY | ATC_MAT(0) | cmd;
- ret = mt7530_write(priv, MT7530_ATC, val);
+ ret = regmap_write(priv->regmap, MT7530_ATC, val);
if (ret)
return ret;
- mt7530_mutex_lock(priv);
-
ret = regmap_read_poll_timeout(priv->regmap, MT7530_ATC, val,
!(val & ATC_BUSY), 20, 20000);
if (!ret)
ret = regmap_read(priv->regmap, MT7530_ATC, &val);
- mt7530_mutex_unlock(priv);
-
if (ret < 0) {
dev_err(priv->dev, "reset timeout\n");
return ret;
@@ -283,6 +188,26 @@ mt7530_fdb_cmd(struct mt7530_priv *priv, enum mt7530_fdb_cmd cmd, u32 *rsp)
return 0;
}
+static void mt7530_port_fast_age(struct dsa_switch *ds, int port)
+{
+ struct mt7530_priv *priv = ds->priv;
+ u32 val;
+ int ret;
+
+ mutex_lock(&priv->reg_mutex);
+
+ /* Flush all non-static MAC address entries */
+ val = ATC_BUSY | ATC_MAT_NON_STATIC_MAC | MT7530_FDB_FLUSH;
+ ret = regmap_write(priv->regmap, MT7530_ATC, val);
+ if (!ret)
+ ret = regmap_read_poll_timeout(priv->regmap, MT7530_ATC, val,
+ !(val & ATC_BUSY), 20, 20000);
+ if (ret < 0)
+ dev_err(priv->dev, "fast age failed: %d\n", ret);
+
+ mutex_unlock(&priv->reg_mutex);
+}
+
static void
mt7530_fdb_read(struct mt7530_priv *priv, struct mt7530_fdb *fdb)
{
@@ -291,22 +216,23 @@ mt7530_fdb_read(struct mt7530_priv *priv, struct mt7530_fdb *fdb)
/* Read from ARL table into an array */
for (i = 0; i < 3; i++) {
- reg[i] = mt7530_read(priv, MT7530_TSRA1 + (i * 4));
+ regmap_read(priv->regmap, MT7530_TSRA1 + (i * 4),
+ &reg[i]);
dev_dbg(priv->dev, "%s(%d) reg[%d]=0x%x\n",
__func__, __LINE__, i, reg[i]);
}
- fdb->vid = (reg[1] >> CVID) & CVID_MASK;
- fdb->aging = (reg[2] >> AGE_TIMER) & AGE_TIMER_MASK;
- fdb->port_mask = (reg[2] >> PORT_MAP) & PORT_MAP_MASK;
- fdb->mac[0] = (reg[0] >> MAC_BYTE_0) & MAC_BYTE_MASK;
- fdb->mac[1] = (reg[0] >> MAC_BYTE_1) & MAC_BYTE_MASK;
- fdb->mac[2] = (reg[0] >> MAC_BYTE_2) & MAC_BYTE_MASK;
- fdb->mac[3] = (reg[0] >> MAC_BYTE_3) & MAC_BYTE_MASK;
- fdb->mac[4] = (reg[1] >> MAC_BYTE_4) & MAC_BYTE_MASK;
- fdb->mac[5] = (reg[1] >> MAC_BYTE_5) & MAC_BYTE_MASK;
- fdb->noarp = ((reg[2] >> ENT_STATUS) & ENT_STATUS_MASK) == STATIC_ENT;
+ fdb->vid = FIELD_GET(CVID_MASK, reg[1]);
+ fdb->aging = FIELD_GET(AGE_TIMER_MASK, reg[2]);
+ fdb->port_mask = FIELD_GET(PORT_MAP_MASK, reg[2]);
+ fdb->mac[0] = FIELD_GET(MAC_BYTE_0_MASK, reg[0]);
+ fdb->mac[1] = FIELD_GET(MAC_BYTE_1_MASK, reg[0]);
+ fdb->mac[2] = FIELD_GET(MAC_BYTE_2_MASK, reg[0]);
+ fdb->mac[3] = FIELD_GET(MAC_BYTE_3_MASK, reg[0]);
+ fdb->mac[4] = FIELD_GET(MAC_BYTE_4_MASK, reg[1]);
+ fdb->mac[5] = FIELD_GET(MAC_BYTE_5_MASK, reg[1]);
+ fdb->noarp = FIELD_GET(ENT_STATUS_MASK, reg[2]) == STATIC_ENT;
}
static void
@@ -317,26 +243,26 @@ mt7530_fdb_write(struct mt7530_priv *priv, u16 vid,
u32 reg[3] = { 0 };
int i;
- reg[1] |= vid & CVID_MASK;
+ reg[1] |= FIELD_PREP(CVID_MASK, vid);
reg[1] |= ATA2_IVL;
reg[1] |= ATA2_FID(FID_BRIDGED);
- reg[2] |= (aging & AGE_TIMER_MASK) << AGE_TIMER;
- reg[2] |= (port_mask & PORT_MAP_MASK) << PORT_MAP;
+ reg[2] |= FIELD_PREP(AGE_TIMER_MASK, aging);
+ reg[2] |= FIELD_PREP(PORT_MAP_MASK, port_mask);
/* STATIC_ENT indicate that entry is static wouldn't
* be aged out and STATIC_EMP specified as erasing an
* entry
*/
- reg[2] |= (type & ENT_STATUS_MASK) << ENT_STATUS;
- reg[1] |= mac[5] << MAC_BYTE_5;
- reg[1] |= mac[4] << MAC_BYTE_4;
- reg[0] |= mac[3] << MAC_BYTE_3;
- reg[0] |= mac[2] << MAC_BYTE_2;
- reg[0] |= mac[1] << MAC_BYTE_1;
- reg[0] |= mac[0] << MAC_BYTE_0;
+ reg[2] |= FIELD_PREP(ENT_STATUS_MASK, type);
+ reg[1] |= FIELD_PREP(MAC_BYTE_5_MASK, mac[5]);
+ reg[1] |= FIELD_PREP(MAC_BYTE_4_MASK, mac[4]);
+ reg[0] |= FIELD_PREP(MAC_BYTE_3_MASK, mac[3]);
+ reg[0] |= FIELD_PREP(MAC_BYTE_2_MASK, mac[2]);
+ reg[0] |= FIELD_PREP(MAC_BYTE_1_MASK, mac[1]);
+ reg[0] |= FIELD_PREP(MAC_BYTE_0_MASK, mac[0]);
/* Write array into the ARL table */
for (i = 0; i < 3; i++)
- mt7530_write(priv, MT7530_ATA1 + (i * 4), reg[i]);
+ regmap_write(priv->regmap, MT7530_ATA1 + (i * 4), reg[i]);
}
/* Set up switch core clock for MT7530 */
@@ -390,14 +316,16 @@ mt7530_setup_port6(struct dsa_switch *ds, phy_interface_t interface)
core_clear(priv, CORE_TRGMII_GSW_CLK_CG, REG_TRGMIICK_EN);
if (interface == PHY_INTERFACE_MODE_RGMII) {
- mt7530_rmw(priv, MT7530_P6ECR, P6_INTF_MODE_MASK,
- P6_INTF_MODE(0));
+ regmap_update_bits(priv->regmap, MT7530_P6ECR,
+ P6_INTF_MODE_MASK, P6_INTF_MODE(0));
return;
}
- mt7530_rmw(priv, MT7530_P6ECR, P6_INTF_MODE_MASK, P6_INTF_MODE(1));
+ regmap_update_bits(priv->regmap, MT7530_P6ECR, P6_INTF_MODE_MASK,
+ P6_INTF_MODE(1));
- xtal = mt7530_read(priv, MT753X_MTRAP) & MT7530_XTAL_MASK;
+ regmap_read(priv->regmap, MT753X_MTRAP, &xtal);
+ xtal &= MT7530_XTAL_MASK;
if (xtal == MT7530_XTAL_25MHZ)
ssc_delta = 0x57;
@@ -441,9 +369,9 @@ mt7531_pll_setup(struct mt7530_priv *priv)
u32 hwstrap;
u32 val;
- val = mt7530_read(priv, MT7531_CREV);
- top_sig = mt7530_read(priv, MT7531_TOP_SIG_SR);
- hwstrap = mt7530_read(priv, MT753X_TRAP);
+ regmap_read(priv->regmap, MT7531_CREV, &val);
+ regmap_read(priv->regmap, MT7531_TOP_SIG_SR, &top_sig);
+ regmap_read(priv->regmap, MT753X_TRAP, &hwstrap);
if ((val & CHIP_REV_M) > 0)
xtal = (top_sig & PAD_MCM_SMI_EN) ? MT7531_XTAL_FSEL_40MHZ :
MT7531_XTAL_FSEL_25MHZ;
@@ -452,72 +380,72 @@ mt7531_pll_setup(struct mt7530_priv *priv)
MT7531_XTAL_FSEL_40MHZ;
/* Step 1 : Disable MT7531 COREPLL */
- val = mt7530_read(priv, MT7531_PLLGP_EN);
+ regmap_read(priv->regmap, MT7531_PLLGP_EN, &val);
val &= ~EN_COREPLL;
- mt7530_write(priv, MT7531_PLLGP_EN, val);
+ regmap_write(priv->regmap, MT7531_PLLGP_EN, val);
/* Step 2: switch to XTAL output */
- val = mt7530_read(priv, MT7531_PLLGP_EN);
+ regmap_read(priv->regmap, MT7531_PLLGP_EN, &val);
val |= SW_CLKSW;
- mt7530_write(priv, MT7531_PLLGP_EN, val);
+ regmap_write(priv->regmap, MT7531_PLLGP_EN, val);
- val = mt7530_read(priv, MT7531_PLLGP_CR0);
+ regmap_read(priv->regmap, MT7531_PLLGP_CR0, &val);
val &= ~RG_COREPLL_EN;
- mt7530_write(priv, MT7531_PLLGP_CR0, val);
+ regmap_write(priv->regmap, MT7531_PLLGP_CR0, val);
/* Step 3: disable PLLGP and enable program PLLGP */
- val = mt7530_read(priv, MT7531_PLLGP_EN);
+ regmap_read(priv->regmap, MT7531_PLLGP_EN, &val);
val |= SW_PLLGP;
- mt7530_write(priv, MT7531_PLLGP_EN, val);
+ regmap_write(priv->regmap, MT7531_PLLGP_EN, val);
/* Step 4: program COREPLL output frequency to 500MHz */
- val = mt7530_read(priv, MT7531_PLLGP_CR0);
- val &= ~RG_COREPLL_POSDIV_M;
- val |= 2 << RG_COREPLL_POSDIV_S;
- mt7530_write(priv, MT7531_PLLGP_CR0, val);
+ regmap_read(priv->regmap, MT7531_PLLGP_CR0, &val);
+ val &= ~RG_COREPLL_POSDIV_MASK;
+ val |= RG_COREPLL_POSDIV(2);
+ regmap_write(priv->regmap, MT7531_PLLGP_CR0, val);
usleep_range(25, 35);
switch (xtal) {
case MT7531_XTAL_FSEL_25MHZ:
- val = mt7530_read(priv, MT7531_PLLGP_CR0);
- val &= ~RG_COREPLL_SDM_PCW_M;
- val |= 0x140000 << RG_COREPLL_SDM_PCW_S;
- mt7530_write(priv, MT7531_PLLGP_CR0, val);
+ regmap_read(priv->regmap, MT7531_PLLGP_CR0, &val);
+ val &= ~RG_COREPLL_SDM_PCW_MASK;
+ val |= RG_COREPLL_SDM_PCW(0x140000);
+ regmap_write(priv->regmap, MT7531_PLLGP_CR0, val);
break;
case MT7531_XTAL_FSEL_40MHZ:
- val = mt7530_read(priv, MT7531_PLLGP_CR0);
- val &= ~RG_COREPLL_SDM_PCW_M;
- val |= 0x190000 << RG_COREPLL_SDM_PCW_S;
- mt7530_write(priv, MT7531_PLLGP_CR0, val);
+ regmap_read(priv->regmap, MT7531_PLLGP_CR0, &val);
+ val &= ~RG_COREPLL_SDM_PCW_MASK;
+ val |= RG_COREPLL_SDM_PCW(0x190000);
+ regmap_write(priv->regmap, MT7531_PLLGP_CR0, val);
break;
}
/* Set feedback divide ratio update signal to high */
- val = mt7530_read(priv, MT7531_PLLGP_CR0);
+ regmap_read(priv->regmap, MT7531_PLLGP_CR0, &val);
val |= RG_COREPLL_SDM_PCW_CHG;
- mt7530_write(priv, MT7531_PLLGP_CR0, val);
+ regmap_write(priv->regmap, MT7531_PLLGP_CR0, val);
/* Wait for at least 16 XTAL clocks */
usleep_range(10, 20);
/* Step 5: set feedback divide ratio update signal to low */
- val = mt7530_read(priv, MT7531_PLLGP_CR0);
+ regmap_read(priv->regmap, MT7531_PLLGP_CR0, &val);
val &= ~RG_COREPLL_SDM_PCW_CHG;
- mt7530_write(priv, MT7531_PLLGP_CR0, val);
+ regmap_write(priv->regmap, MT7531_PLLGP_CR0, val);
/* Enable 325M clock for SGMII */
- mt7530_write(priv, MT7531_ANA_PLLGP_CR5, 0xad0000);
+ regmap_write(priv->regmap, MT7531_ANA_PLLGP_CR5, 0xad0000);
/* Enable 250SSC clock for RGMII */
- mt7530_write(priv, MT7531_ANA_PLLGP_CR2, 0x4f40000);
+ regmap_write(priv->regmap, MT7531_ANA_PLLGP_CR2, 0x4f40000);
/* Step 6: Enable MT7531 PLL */
- val = mt7530_read(priv, MT7531_PLLGP_CR0);
+ regmap_read(priv->regmap, MT7531_PLLGP_CR0, &val);
val |= RG_COREPLL_EN;
- mt7530_write(priv, MT7531_PLLGP_CR0, val);
+ regmap_write(priv->regmap, MT7531_PLLGP_CR0, val);
- val = mt7530_read(priv, MT7531_PLLGP_EN);
+ regmap_read(priv->regmap, MT7531_PLLGP_EN, &val);
val |= EN_COREPLL;
- mt7530_write(priv, MT7531_PLLGP_EN, val);
+ regmap_write(priv->regmap, MT7531_PLLGP_EN, val);
usleep_range(25, 35);
}
@@ -526,8 +454,8 @@ mt7530_mib_reset(struct dsa_switch *ds)
{
struct mt7530_priv *priv = ds->priv;
- mt7530_write(priv, MT7530_MIB_CCR, CCR_MIB_FLUSH);
- mt7530_write(priv, MT7530_MIB_CCR, CCR_MIB_ACTIVATE);
+ regmap_write(priv->regmap, MT7530_MIB_CCR, CCR_MIB_FLUSH);
+ regmap_write(priv->regmap, MT7530_MIB_CCR, CCR_MIB_ACTIVATE);
}
static int mt7530_phy_read_c22(struct mt7530_priv *priv, int port, int regnum)
@@ -560,7 +488,7 @@ mt7531_ind_c45_phy_read(struct mt7530_priv *priv, int port, int devad,
u32 reg, val;
int ret;
- mt7530_mutex_lock(priv);
+ mutex_lock(&priv->reg_mutex);
ret = regmap_read_poll_timeout(priv->regmap, MT7531_PHY_IAC, val,
!(val & MT7531_PHY_ACS_ST), 20, 100000);
@@ -571,7 +499,8 @@ mt7531_ind_c45_phy_read(struct mt7530_priv *priv, int port, int devad,
reg = MT7531_MDIO_CL45_ADDR | MT7531_MDIO_PHY_ADDR(port) |
MT7531_MDIO_DEV_ADDR(devad) | regnum;
- ret = mt7530_mii_write(priv, MT7531_PHY_IAC, reg | MT7531_PHY_ACS_ST);
+ ret = regmap_write(priv->regmap, MT7531_PHY_IAC,
+ reg | MT7531_PHY_ACS_ST);
if (ret < 0)
goto out;
@@ -584,7 +513,8 @@ mt7531_ind_c45_phy_read(struct mt7530_priv *priv, int port, int devad,
reg = MT7531_MDIO_CL45_READ | MT7531_MDIO_PHY_ADDR(port) |
MT7531_MDIO_DEV_ADDR(devad);
- ret = mt7530_mii_write(priv, MT7531_PHY_IAC, reg | MT7531_PHY_ACS_ST);
+ ret = regmap_write(priv->regmap, MT7531_PHY_IAC,
+ reg | MT7531_PHY_ACS_ST);
if (ret < 0)
goto out;
@@ -597,7 +527,7 @@ mt7531_ind_c45_phy_read(struct mt7530_priv *priv, int port, int devad,
ret = val & MT7531_MDIO_RW_DATA_MASK;
out:
- mt7530_mutex_unlock(priv);
+ mutex_unlock(&priv->reg_mutex);
return ret;
}
@@ -609,7 +539,7 @@ mt7531_ind_c45_phy_write(struct mt7530_priv *priv, int port, int devad,
u32 val, reg;
int ret;
- mt7530_mutex_lock(priv);
+ mutex_lock(&priv->reg_mutex);
ret = regmap_read_poll_timeout(priv->regmap, MT7531_PHY_IAC, val,
!(val & MT7531_PHY_ACS_ST), 20, 100000);
@@ -620,7 +550,8 @@ mt7531_ind_c45_phy_write(struct mt7530_priv *priv, int port, int devad,
reg = MT7531_MDIO_CL45_ADDR | MT7531_MDIO_PHY_ADDR(port) |
MT7531_MDIO_DEV_ADDR(devad) | regnum;
- ret = mt7530_mii_write(priv, MT7531_PHY_IAC, reg | MT7531_PHY_ACS_ST);
+ ret = regmap_write(priv->regmap, MT7531_PHY_IAC,
+ reg | MT7531_PHY_ACS_ST);
if (ret < 0)
goto out;
@@ -633,7 +564,8 @@ mt7531_ind_c45_phy_write(struct mt7530_priv *priv, int port, int devad,
reg = MT7531_MDIO_CL45_WRITE | MT7531_MDIO_PHY_ADDR(port) |
MT7531_MDIO_DEV_ADDR(devad) | data;
- ret = mt7530_mii_write(priv, MT7531_PHY_IAC, reg | MT7531_PHY_ACS_ST);
+ ret = regmap_write(priv->regmap, MT7531_PHY_IAC,
+ reg | MT7531_PHY_ACS_ST);
if (ret < 0)
goto out;
@@ -645,7 +577,7 @@ mt7531_ind_c45_phy_write(struct mt7530_priv *priv, int port, int devad,
}
out:
- mt7530_mutex_unlock(priv);
+ mutex_unlock(&priv->reg_mutex);
return ret;
}
@@ -656,7 +588,7 @@ mt7531_ind_c22_phy_read(struct mt7530_priv *priv, int port, int regnum)
int ret;
u32 val;
- mt7530_mutex_lock(priv);
+ mutex_lock(&priv->reg_mutex);
ret = regmap_read_poll_timeout(priv->regmap, MT7531_PHY_IAC, val,
!(val & MT7531_PHY_ACS_ST), 20, 100000);
@@ -668,7 +600,8 @@ mt7531_ind_c22_phy_read(struct mt7530_priv *priv, int port, int regnum)
val = MT7531_MDIO_CL22_READ | MT7531_MDIO_PHY_ADDR(port) |
MT7531_MDIO_REG_ADDR(regnum);
- ret = mt7530_mii_write(priv, MT7531_PHY_IAC, val | MT7531_PHY_ACS_ST);
+ ret = regmap_write(priv->regmap, MT7531_PHY_IAC,
+ val | MT7531_PHY_ACS_ST);
if (ret < 0)
goto out;
@@ -681,7 +614,7 @@ mt7531_ind_c22_phy_read(struct mt7530_priv *priv, int port, int regnum)
ret = val & MT7531_MDIO_RW_DATA_MASK;
out:
- mt7530_mutex_unlock(priv);
+ mutex_unlock(&priv->reg_mutex);
return ret;
}
@@ -693,7 +626,7 @@ mt7531_ind_c22_phy_write(struct mt7530_priv *priv, int port, int regnum,
int ret;
u32 reg;
- mt7530_mutex_lock(priv);
+ mutex_lock(&priv->reg_mutex);
ret = regmap_read_poll_timeout(priv->regmap, MT7531_PHY_IAC, reg,
!(reg & MT7531_PHY_ACS_ST), 20, 100000);
@@ -705,7 +638,8 @@ mt7531_ind_c22_phy_write(struct mt7530_priv *priv, int port, int regnum,
reg = MT7531_MDIO_CL22_WRITE | MT7531_MDIO_PHY_ADDR(port) |
MT7531_MDIO_REG_ADDR(regnum) | data;
- ret = mt7530_mii_write(priv, MT7531_PHY_IAC, reg | MT7531_PHY_ACS_ST);
+ ret = regmap_write(priv->regmap, MT7531_PHY_IAC,
+ reg | MT7531_PHY_ACS_ST);
if (ret < 0)
goto out;
@@ -717,7 +651,7 @@ mt7531_ind_c22_phy_write(struct mt7530_priv *priv, int port, int regnum,
}
out:
- mt7530_mutex_unlock(priv);
+ mutex_unlock(&priv->reg_mutex);
return ret;
}
@@ -774,11 +708,11 @@ mt7530_read_port_stats(struct mt7530_priv *priv, int port,
{
u32 val, reg = MT7530_PORT_MIB_COUNTER(port) + offset;
- val = mt7530_read(priv, reg);
+ regmap_read(priv->regmap, reg, &val);
*data = val;
if (size == 2) {
- val = mt7530_read(priv, reg + 4);
+ regmap_read(priv->regmap, reg + 4, &val);
*data |= (u64)val << 32;
}
}
@@ -1060,7 +994,8 @@ mt7530_set_ageing_time(struct dsa_switch *ds, unsigned int msecs)
}
}
- mt7530_write(priv, MT7530_AAC, AGE_CNT(age_count) | AGE_UNIT(age_unit));
+ regmap_write(priv->regmap, MT7530_AAC,
+ AGE_CNT(age_count) | AGE_UNIT(age_unit));
return 0;
}
@@ -1081,11 +1016,11 @@ static void mt7530_setup_port5(struct dsa_switch *ds, phy_interface_t interface)
{
struct mt7530_priv *priv = ds->priv;
u8 tx_delay = 0;
- int val;
+ u32 val;
mutex_lock(&priv->reg_mutex);
- val = mt7530_read(priv, MT753X_MTRAP);
+ regmap_read(priv->regmap, MT753X_MTRAP, &val);
val &= ~MT7530_P5_PHY0_SEL & ~MT7530_P5_MAC_SEL & ~MT7530_P5_RGMII_MODE;
@@ -1098,7 +1033,7 @@ static void mt7530_setup_port5(struct dsa_switch *ds, phy_interface_t interface)
/* MUX_PHY_P4: P4 -> P5 -> SoC MAC */
case MUX_PHY_P4:
/* Setup the MAC by default for the cpu port */
- mt7530_write(priv, MT753X_PMCR_P(5), 0x56300);
+ regmap_write(priv->regmap, MT753X_PMCR_P(5), 0x56300);
break;
/* GMAC5: P5 -> SoC MAC or external PHY */
@@ -1112,7 +1047,8 @@ static void mt7530_setup_port5(struct dsa_switch *ds, phy_interface_t interface)
val |= MT7530_P5_RGMII_MODE;
/* P5 RGMII RX Clock Control: delay setting for 1000M */
- mt7530_write(priv, MT7530_P5RGMIIRXCR, CSR_RGMII_EDGE_ALIGN);
+ regmap_write(priv->regmap, MT7530_P5RGMIIRXCR,
+ CSR_RGMII_EDGE_ALIGN);
/* Don't set delay in DSA mode */
if (!dsa_is_dsa_port(priv->ds, 5) &&
@@ -1121,15 +1057,15 @@ static void mt7530_setup_port5(struct dsa_switch *ds, phy_interface_t interface)
tx_delay = 4; /* n * 0.5 ns */
/* P5 RGMII TX Clock Control: delay x */
- mt7530_write(priv, MT7530_P5RGMIITXCR,
+ regmap_write(priv->regmap, MT7530_P5RGMIITXCR,
CSR_RGMII_TXC_CFG(0x10 + tx_delay));
/* reduce P5 RGMII Tx driving, 8mA */
- mt7530_write(priv, MT7530_IO_DRV_CR,
+ regmap_write(priv->regmap, MT7530_IO_DRV_CR,
P5_IO_CLK_DRV(1) | P5_IO_DATA_DRV(1));
}
- mt7530_write(priv, MT753X_MTRAP, val);
+ regmap_write(priv->regmap, MT753X_MTRAP, val);
dev_dbg(ds->dev, "Setup P5, HWTRAP=0x%x, mode=%s, phy-mode=%s\n", val,
mt7530_p5_mode_str(priv->p5_mode), phy_modes(interface));
@@ -1314,35 +1250,35 @@ mt753x_trap_frames(struct mt7530_priv *priv)
* switch egress VLAN tag processing. This preserves VLAN tags
* for reception on VLAN sub-interfaces.
*/
- mt7530_rmw(priv, MT753X_BPC,
- PAE_BPDU_FR | PAE_EG_TAG_MASK | PAE_PORT_FW_MASK |
- BPDU_EG_TAG_MASK | BPDU_PORT_FW_MASK,
- PAE_BPDU_FR | PAE_EG_TAG(MT7530_VLAN_EG_DISABLED) |
- PAE_PORT_FW(TO_CPU_FW_CPU_ONLY) |
- BPDU_EG_TAG(MT7530_VLAN_EG_DISABLED) |
- TO_CPU_FW_CPU_ONLY);
+ regmap_update_bits(priv->regmap, MT753X_BPC,
+ PAE_BPDU_FR | PAE_EG_TAG_MASK | PAE_PORT_FW_MASK |
+ BPDU_EG_TAG_MASK | BPDU_PORT_FW_MASK,
+ PAE_BPDU_FR | PAE_EG_TAG(MT7530_VLAN_EG_DISABLED) |
+ PAE_PORT_FW(TO_CPU_FW_CPU_ONLY) |
+ BPDU_EG_TAG(MT7530_VLAN_EG_DISABLED) |
+ TO_CPU_FW_CPU_ONLY);
/* Trap frames with :01 and :02 MAC DAs to the CPU port(s) and
* egress them with EG_TAG disabled.
*/
- mt7530_rmw(priv, MT753X_RGAC1,
- R02_BPDU_FR | R02_EG_TAG_MASK | R02_PORT_FW_MASK |
- R01_BPDU_FR | R01_EG_TAG_MASK | R01_PORT_FW_MASK,
- R02_BPDU_FR | R02_EG_TAG(MT7530_VLAN_EG_DISABLED) |
- R02_PORT_FW(TO_CPU_FW_CPU_ONLY) | R01_BPDU_FR |
- R01_EG_TAG(MT7530_VLAN_EG_DISABLED) |
- TO_CPU_FW_CPU_ONLY);
+ regmap_update_bits(priv->regmap, MT753X_RGAC1,
+ R02_BPDU_FR | R02_EG_TAG_MASK | R02_PORT_FW_MASK |
+ R01_BPDU_FR | R01_EG_TAG_MASK | R01_PORT_FW_MASK,
+ R02_BPDU_FR | R02_EG_TAG(MT7530_VLAN_EG_DISABLED) |
+ R02_PORT_FW(TO_CPU_FW_CPU_ONLY) | R01_BPDU_FR |
+ R01_EG_TAG(MT7530_VLAN_EG_DISABLED) |
+ TO_CPU_FW_CPU_ONLY);
/* Trap frames with :03 and :0E MAC DAs to the CPU port(s) and
* egress them with EG_TAG disabled.
*/
- mt7530_rmw(priv, MT753X_RGAC2,
- R0E_BPDU_FR | R0E_EG_TAG_MASK | R0E_PORT_FW_MASK |
- R03_BPDU_FR | R03_EG_TAG_MASK | R03_PORT_FW_MASK,
- R0E_BPDU_FR | R0E_EG_TAG(MT7530_VLAN_EG_DISABLED) |
- R0E_PORT_FW(TO_CPU_FW_CPU_ONLY) | R03_BPDU_FR |
- R03_EG_TAG(MT7530_VLAN_EG_DISABLED) |
- TO_CPU_FW_CPU_ONLY);
+ regmap_update_bits(priv->regmap, MT753X_RGAC2,
+ R0E_BPDU_FR | R0E_EG_TAG_MASK | R0E_PORT_FW_MASK |
+ R03_BPDU_FR | R03_EG_TAG_MASK | R03_PORT_FW_MASK,
+ R0E_BPDU_FR | R0E_EG_TAG(MT7530_VLAN_EG_DISABLED) |
+ R0E_PORT_FW(TO_CPU_FW_CPU_ONLY) | R03_BPDU_FR |
+ R03_EG_TAG(MT7530_VLAN_EG_DISABLED) |
+ TO_CPU_FW_CPU_ONLY);
}
static void
@@ -1351,12 +1287,11 @@ mt753x_cpu_port_enable(struct dsa_switch *ds, int port)
struct mt7530_priv *priv = ds->priv;
/* Enable Mediatek header mode on the cpu port */
- mt7530_write(priv, MT7530_PVC_P(port),
- PORT_SPEC_TAG);
+ regmap_write(priv->regmap, MT7530_PVC_P(port), PORT_SPEC_TAG);
/* Enable flooding on the CPU port */
- mt7530_set(priv, MT753X_MFC, BC_FFP(BIT(port)) | UNM_FFP(BIT(port)) |
- UNU_FFP(BIT(port)));
+ regmap_set_bits(priv->regmap, MT753X_MFC,
+ BC_FFP(BIT(port)) | UNM_FFP(BIT(port)) | UNU_FFP(BIT(port)));
/* Add the CPU port to the CPU port bitmap for MT7531 and the switch on
* the MT7988 SoC. Trapped frames will be forwarded to the CPU port that
@@ -1364,17 +1299,18 @@ mt753x_cpu_port_enable(struct dsa_switch *ds, int port)
*/
if (priv->id == ID_MT7531 || priv->id == ID_MT7988 ||
priv->id == ID_EN7581 || priv->id == ID_AN7583)
- mt7530_set(priv, MT7531_CFC, MT7531_CPU_PMAP(BIT(port)));
+ regmap_set_bits(priv->regmap, MT7531_CFC,
+ MT7531_CPU_PMAP(BIT(port)));
/* CPU port gets connected to all user ports of
* the switch.
*/
- mt7530_write(priv, MT7530_PCR_P(port),
+ regmap_write(priv->regmap, MT7530_PCR_P(port),
PCR_MATRIX(dsa_user_ports(priv->ds)));
/* Set to fallback mode for independent VLAN learning */
- mt7530_rmw(priv, MT7530_PCR_P(port), PCR_PORT_VLAN_MASK,
- MT7530_PORT_FALLBACK_MODE);
+ regmap_update_bits(priv->regmap, MT7530_PCR_P(port),
+ PCR_PORT_VLAN_MASK, MT7530_PORT_FALLBACK_MODE);
}
static int
@@ -1396,8 +1332,8 @@ mt7530_port_enable(struct dsa_switch *ds, int port,
priv->ports[port].pm |= PCR_MATRIX(BIT(cpu_dp->index));
}
priv->ports[port].enable = true;
- mt7530_rmw(priv, MT7530_PCR_P(port), PCR_MATRIX_MASK,
- priv->ports[port].pm);
+ regmap_update_bits(priv->regmap, MT7530_PCR_P(port), PCR_MATRIX_MASK,
+ priv->ports[port].pm);
mutex_unlock(&priv->reg_mutex);
@@ -1405,9 +1341,9 @@ mt7530_port_enable(struct dsa_switch *ds, int port,
return 0;
if (port == 5)
- mt7530_clear(priv, MT753X_MTRAP, MT7530_P5_DIS);
+ regmap_clear_bits(priv->regmap, MT753X_MTRAP, MT7530_P5_DIS);
else if (port == 6)
- mt7530_clear(priv, MT753X_MTRAP, MT7530_P6_DIS);
+ regmap_clear_bits(priv->regmap, MT753X_MTRAP, MT7530_P6_DIS);
return 0;
}
@@ -1423,8 +1359,8 @@ mt7530_port_disable(struct dsa_switch *ds, int port)
* enablement for the port.
*/
priv->ports[port].enable = false;
- mt7530_rmw(priv, MT7530_PCR_P(port), PCR_MATRIX_MASK,
- PCR_MATRIX_CLR);
+ regmap_update_bits(priv->regmap, MT7530_PCR_P(port), PCR_MATRIX_MASK,
+ PCR_MATRIX_CLR);
mutex_unlock(&priv->reg_mutex);
@@ -1433,9 +1369,9 @@ mt7530_port_disable(struct dsa_switch *ds, int port)
/* Do not set MT7530_P5_DIS when port 5 is being used for PHY muxing. */
if (port == 5 && priv->p5_mode == GMAC5)
- mt7530_set(priv, MT753X_MTRAP, MT7530_P5_DIS);
+ regmap_set_bits(priv->regmap, MT753X_MTRAP, MT7530_P5_DIS);
else if (port == 6)
- mt7530_set(priv, MT753X_MTRAP, MT7530_P6_DIS);
+ regmap_set_bits(priv->regmap, MT753X_MTRAP, MT7530_P6_DIS);
}
static int
@@ -1452,9 +1388,7 @@ mt7530_port_change_mtu(struct dsa_switch *ds, int port, int new_mtu)
if (!dsa_is_cpu_port(ds, port))
return 0;
- mt7530_mutex_lock(priv);
-
- val = mt7530_mii_read(priv, MT7530_GMACCR);
+ regmap_read(priv->regmap, MT7530_GMACCR, &val);
val &= ~MAX_RX_PKT_LEN_MASK;
/* RX length also includes Ethernet header, MTK tag, and FCS length */
@@ -1471,9 +1405,7 @@ mt7530_port_change_mtu(struct dsa_switch *ds, int port, int new_mtu)
val |= MAX_RX_PKT_LEN_JUMBO;
}
- mt7530_mii_write(priv, MT7530_GMACCR, val);
-
- mt7530_mutex_unlock(priv);
+ regmap_write(priv->regmap, MT7530_GMACCR, val);
return 0;
}
@@ -1509,8 +1441,9 @@ mt7530_stp_state_set(struct dsa_switch *ds, int port, u8 state)
break;
}
- mt7530_rmw(priv, MT7530_SSP_P(port), FID_PST_MASK(FID_BRIDGED),
- FID_PST(FID_BRIDGED, stp_state));
+ regmap_update_bits(priv->regmap, MT7530_SSP_P(port),
+ FID_PST_MASK(FID_BRIDGED),
+ FID_PST(FID_BRIDGED, stp_state));
}
static void mt7530_update_port_member(struct mt7530_priv *priv, int port,
@@ -1549,8 +1482,9 @@ static void mt7530_update_port_member(struct mt7530_priv *priv, int port,
}
if (other_p->enable)
- mt7530_rmw(priv, MT7530_PCR_P(other_port),
- PCR_MATRIX_MASK, other_p->pm);
+ regmap_update_bits(priv->regmap,
+ MT7530_PCR_P(other_port),
+ PCR_MATRIX_MASK, other_p->pm);
}
/* Add/remove the all other ports to this port matrix. For !join
@@ -1559,7 +1493,8 @@ static void mt7530_update_port_member(struct mt7530_priv *priv, int port,
*/
p->pm = PCR_MATRIX(port_bitmap);
if (priv->ports[port].enable)
- mt7530_rmw(priv, MT7530_PCR_P(port), PCR_MATRIX_MASK, p->pm);
+ regmap_update_bits(priv->regmap, MT7530_PCR_P(port),
+ PCR_MATRIX_MASK, p->pm);
}
static int
@@ -1582,20 +1517,23 @@ mt7530_port_bridge_flags(struct dsa_switch *ds, int port,
struct mt7530_priv *priv = ds->priv;
if (flags.mask & BR_LEARNING)
- mt7530_rmw(priv, MT7530_PSC_P(port), SA_DIS,
- flags.val & BR_LEARNING ? 0 : SA_DIS);
+ regmap_update_bits(priv->regmap, MT7530_PSC_P(port), SA_DIS,
+ flags.val & BR_LEARNING ? 0 : SA_DIS);
if (flags.mask & BR_FLOOD)
- mt7530_rmw(priv, MT753X_MFC, UNU_FFP(BIT(port)),
- flags.val & BR_FLOOD ? UNU_FFP(BIT(port)) : 0);
+ regmap_update_bits(priv->regmap, MT753X_MFC,
+ UNU_FFP(BIT(port)),
+ flags.val & BR_FLOOD ? UNU_FFP(BIT(port)) : 0);
if (flags.mask & BR_MCAST_FLOOD)
- mt7530_rmw(priv, MT753X_MFC, UNM_FFP(BIT(port)),
- flags.val & BR_MCAST_FLOOD ? UNM_FFP(BIT(port)) : 0);
+ regmap_update_bits(priv->regmap, MT753X_MFC,
+ UNM_FFP(BIT(port)),
+ flags.val & BR_MCAST_FLOOD ? UNM_FFP(BIT(port)) : 0);
if (flags.mask & BR_BCAST_FLOOD)
- mt7530_rmw(priv, MT753X_MFC, BC_FFP(BIT(port)),
- flags.val & BR_BCAST_FLOOD ? BC_FFP(BIT(port)) : 0);
+ regmap_update_bits(priv->regmap, MT753X_MFC,
+ BC_FFP(BIT(port)),
+ flags.val & BR_BCAST_FLOOD ? BC_FFP(BIT(port)) : 0);
if (flags.mask & BR_ISOLATED) {
struct dsa_port *dp = dsa_to_port(ds, port);
@@ -1623,8 +1561,8 @@ mt7530_port_bridge_join(struct dsa_switch *ds, int port,
mt7530_update_port_member(priv, port, bridge.dev, true);
/* Set to fallback mode for independent VLAN learning */
- mt7530_rmw(priv, MT7530_PCR_P(port), PCR_PORT_VLAN_MASK,
- MT7530_PORT_FALLBACK_MODE);
+ regmap_update_bits(priv->regmap, MT7530_PCR_P(port),
+ PCR_PORT_VLAN_MASK, MT7530_PORT_FALLBACK_MODE);
mutex_unlock(&priv->reg_mutex);
@@ -1637,20 +1575,16 @@ mt7530_vlan_cmd(struct mt7530_priv *priv, enum mt7530_vlan_cmd cmd, u16 vid)
u32 val;
int ret;
- val = VTCR_BUSY | VTCR_FUNC(cmd) | vid;
- ret = mt7530_write(priv, MT7530_VTCR, val);
+ val = VTCR_BUSY | VTCR_FUNC(cmd) | VTCR_VID(vid);
+ ret = regmap_write(priv->regmap, MT7530_VTCR, val);
if (ret)
return ret;
- mt7530_mutex_lock(priv);
-
ret = regmap_read_poll_timeout(priv->regmap, MT7530_VTCR, val,
!(val & VTCR_BUSY), 20, 20000);
if (!ret)
ret = regmap_read(priv->regmap, MT7530_VTCR, &val);
- mt7530_mutex_unlock(priv);
-
if (ret < 0) {
dev_err(priv->dev, "poll timeout\n");
return ret;
@@ -1674,8 +1608,8 @@ mt7530_setup_vlan0(struct mt7530_priv *priv)
*/
val = IVL_MAC | EG_CON | PORT_MEM(MT7530_ALL_MEMBERS) | FID(FID_BRIDGED) |
VLAN_VALID;
- mt7530_write(priv, MT7530_VAWD1, val);
- mt7530_write(priv, MT7530_VAWD2, 0);
+ regmap_write(priv->regmap, MT7530_VAWD1, val);
+ regmap_write(priv->regmap, MT7530_VAWD2, 0);
return mt7530_vlan_cmd(priv, MT7530_VTCR_WR_VID, 0);
}
@@ -1691,18 +1625,19 @@ mt7530_port_set_vlan_unaware(struct dsa_switch *ds, int port)
* bridge. Don't set standalone ports to fallback mode.
*/
if (dsa_port_bridge_dev_get(dsa_to_port(ds, port)))
- mt7530_rmw(priv, MT7530_PCR_P(port), PCR_PORT_VLAN_MASK,
- MT7530_PORT_FALLBACK_MODE);
-
- mt7530_rmw(priv, MT7530_PVC_P(port),
- VLAN_ATTR_MASK | PVC_EG_TAG_MASK | ACC_FRM_MASK,
- VLAN_ATTR(MT7530_VLAN_TRANSPARENT) |
- PVC_EG_TAG(MT7530_VLAN_EG_CONSISTENT) |
- MT7530_VLAN_ACC_ALL);
+ regmap_update_bits(priv->regmap, MT7530_PCR_P(port),
+ PCR_PORT_VLAN_MASK,
+ MT7530_PORT_FALLBACK_MODE);
+
+ regmap_update_bits(priv->regmap, MT7530_PVC_P(port),
+ VLAN_ATTR_MASK | PVC_EG_TAG_MASK | ACC_FRM_MASK,
+ VLAN_ATTR(MT7530_VLAN_TRANSPARENT) |
+ PVC_EG_TAG(MT7530_VLAN_EG_CONSISTENT) |
+ MT7530_VLAN_ACC_ALL);
/* Set PVID to 0 */
- mt7530_rmw(priv, MT7530_PPBV1_P(port), G0_PORT_VID_MASK,
- G0_PORT_VID_DEF);
+ regmap_update_bits(priv->regmap, MT7530_PPBV1_P(port),
+ G0_PORT_VID_MASK, G0_PORT_VID_DEF);
for (i = 0; i < priv->ds->num_ports; i++) {
if (i == port)
@@ -1733,24 +1668,27 @@ mt7530_port_set_vlan_aware(struct dsa_switch *ds, int port)
* table lookup.
*/
if (dsa_is_user_port(ds, port)) {
- mt7530_rmw(priv, MT7530_PCR_P(port), PCR_PORT_VLAN_MASK,
- MT7530_PORT_SECURITY_MODE);
- mt7530_rmw(priv, MT7530_PPBV1_P(port), G0_PORT_VID_MASK,
- G0_PORT_VID(priv->ports[port].pvid));
+ regmap_update_bits(priv->regmap, MT7530_PCR_P(port),
+ PCR_PORT_VLAN_MASK,
+ MT7530_PORT_SECURITY_MODE);
+ regmap_update_bits(priv->regmap, MT7530_PPBV1_P(port),
+ G0_PORT_VID_MASK,
+ G0_PORT_VID(priv->ports[port].pvid));
/* Only accept tagged frames if PVID is not set */
if (!priv->ports[port].pvid)
- mt7530_rmw(priv, MT7530_PVC_P(port), ACC_FRM_MASK,
- MT7530_VLAN_ACC_TAGGED);
+ regmap_update_bits(priv->regmap, MT7530_PVC_P(port),
+ ACC_FRM_MASK,
+ MT7530_VLAN_ACC_TAGGED);
/* Set the port as a user port which is to be able to recognize
* VID from incoming packets before fetching entry within the
* VLAN table.
*/
- mt7530_rmw(priv, MT7530_PVC_P(port),
- VLAN_ATTR_MASK | PVC_EG_TAG_MASK,
- VLAN_ATTR(MT7530_VLAN_USER) |
- PVC_EG_TAG(MT7530_VLAN_EG_DISABLED));
+ regmap_update_bits(priv->regmap, MT7530_PVC_P(port),
+ VLAN_ATTR_MASK | PVC_EG_TAG_MASK,
+ VLAN_ATTR(MT7530_VLAN_USER) |
+ PVC_EG_TAG(MT7530_VLAN_EG_DISABLED));
} else {
/* Also set CPU ports to the "user" VLAN port attribute, to
* allow VLAN classification, but keep the EG_TAG attribute as
@@ -1759,8 +1697,9 @@ mt7530_port_set_vlan_aware(struct dsa_switch *ds, int port)
* are forwarded to user ports as tagged, and untagged as
* untagged.
*/
- mt7530_rmw(priv, MT7530_PVC_P(port), VLAN_ATTR_MASK,
- VLAN_ATTR(MT7530_VLAN_USER));
+ regmap_update_bits(priv->regmap, MT7530_PVC_P(port),
+ VLAN_ATTR_MASK,
+ VLAN_ATTR(MT7530_VLAN_USER));
}
}
@@ -1778,8 +1717,8 @@ mt7530_port_bridge_leave(struct dsa_switch *ds, int port,
* back to the default as is at initial boot which is a VLAN-unaware
* port.
*/
- mt7530_rmw(priv, MT7530_PCR_P(port), PCR_PORT_VLAN_MASK,
- MT7530_PORT_MATRIX_MODE);
+ regmap_update_bits(priv->regmap, MT7530_PCR_P(port),
+ PCR_PORT_VLAN_MASK, MT7530_PORT_MATRIX_MODE);
mutex_unlock(&priv->reg_mutex);
}
@@ -1862,14 +1801,16 @@ mt7530_port_mdb_add(struct dsa_switch *ds, int port,
const u8 *addr = mdb->addr;
u16 vid = mdb->vid;
u8 port_mask = 0;
+ u32 val;
int ret;
mutex_lock(&priv->reg_mutex);
mt7530_fdb_write(priv, vid, 0, addr, 0, STATIC_EMP);
- if (!mt7530_fdb_cmd(priv, MT7530_FDB_READ, NULL))
- port_mask = (mt7530_read(priv, MT7530_ATRD) >> PORT_MAP)
- & PORT_MAP_MASK;
+ if (!mt7530_fdb_cmd(priv, MT7530_FDB_READ, NULL)) {
+ regmap_read(priv->regmap, MT7530_ATRD, &val);
+ port_mask = FIELD_GET(PORT_MAP_MASK, val);
+ }
port_mask |= BIT(port);
mt7530_fdb_write(priv, vid, port_mask, addr, -1, STATIC_ENT);
@@ -1889,14 +1830,16 @@ mt7530_port_mdb_del(struct dsa_switch *ds, int port,
const u8 *addr = mdb->addr;
u16 vid = mdb->vid;
u8 port_mask = 0;
+ u32 val;
int ret;
mutex_lock(&priv->reg_mutex);
mt7530_fdb_write(priv, vid, 0, addr, 0, STATIC_EMP);
- if (!mt7530_fdb_cmd(priv, MT7530_FDB_READ, NULL))
- port_mask = (mt7530_read(priv, MT7530_ATRD) >> PORT_MAP)
- & PORT_MAP_MASK;
+ if (!mt7530_fdb_cmd(priv, MT7530_FDB_READ, NULL)) {
+ regmap_read(priv->regmap, MT7530_ATRD, &val);
+ port_mask = FIELD_GET(PORT_MAP_MASK, val);
+ }
port_mask &= ~BIT(port);
mt7530_fdb_write(priv, vid, port_mask, addr, -1,
@@ -1945,7 +1888,7 @@ mt7530_hw_vlan_add(struct mt7530_priv *priv,
*/
val = IVL_MAC | VTAG_EN | PORT_MEM(new_members) | FID(FID_BRIDGED) |
VLAN_VALID;
- mt7530_write(priv, MT7530_VAWD1, val);
+ regmap_write(priv->regmap, MT7530_VAWD1, val);
/* Decide whether adding tag or not for those outgoing packets from the
* port inside the VLAN.
@@ -1960,9 +1903,9 @@ mt7530_hw_vlan_add(struct mt7530_priv *priv,
val = MT7530_VLAN_EGRESS_UNTAG;
else
val = MT7530_VLAN_EGRESS_TAG;
- mt7530_rmw(priv, MT7530_VAWD2,
- ETAG_CTRL_P_MASK(entry->port),
- ETAG_CTRL_P(entry->port, val));
+ regmap_update_bits(priv->regmap, MT7530_VAWD2,
+ ETAG_CTRL_P_MASK(entry->port),
+ ETAG_CTRL_P(entry->port, val));
}
static void
@@ -1974,7 +1917,7 @@ mt7530_hw_vlan_del(struct mt7530_priv *priv,
new_members = entry->old_members & ~BIT(entry->port);
- val = mt7530_read(priv, MT7530_VAWD1);
+ regmap_read(priv->regmap, MT7530_VAWD1, &val);
if (!(val & VLAN_VALID)) {
dev_err(priv->dev,
"Cannot be deleted due to invalid entry\n");
@@ -1984,10 +1927,10 @@ mt7530_hw_vlan_del(struct mt7530_priv *priv,
if (new_members) {
val = IVL_MAC | VTAG_EN | PORT_MEM(new_members) |
VLAN_VALID;
- mt7530_write(priv, MT7530_VAWD1, val);
+ regmap_write(priv->regmap, MT7530_VAWD1, val);
} else {
- mt7530_write(priv, MT7530_VAWD1, 0);
- mt7530_write(priv, MT7530_VAWD2, 0);
+ regmap_write(priv->regmap, MT7530_VAWD1, 0);
+ regmap_write(priv->regmap, MT7530_VAWD2, 0);
}
}
@@ -2001,9 +1944,9 @@ mt7530_hw_vlan_update(struct mt7530_priv *priv, u16 vid,
/* Fetch entry */
mt7530_vlan_cmd(priv, MT7530_VTCR_RD_VID, vid);
- val = mt7530_read(priv, MT7530_VAWD1);
+ regmap_read(priv->regmap, MT7530_VAWD1, &val);
- entry->old_members = (val >> PORT_MEM_SHFT) & PORT_MEM_MASK;
+ entry->old_members = FIELD_GET(PORT_MEM_MASK, val);
/* Manipulate entry */
vlan_op(priv, entry);
@@ -2040,25 +1983,26 @@ skip_vlan_table:
priv->ports[port].pvid = vlan->vid;
/* Accept all frames if PVID is set */
- mt7530_rmw(priv, MT7530_PVC_P(port), ACC_FRM_MASK,
- MT7530_VLAN_ACC_ALL);
+ regmap_update_bits(priv->regmap, MT7530_PVC_P(port),
+ ACC_FRM_MASK, MT7530_VLAN_ACC_ALL);
/* Only configure PVID if VLAN filtering is enabled */
if (dsa_port_is_vlan_filtering(dsa_to_port(ds, port)))
- mt7530_rmw(priv, MT7530_PPBV1_P(port),
- G0_PORT_VID_MASK,
- G0_PORT_VID(vlan->vid));
+ regmap_update_bits(priv->regmap, MT7530_PPBV1_P(port),
+ G0_PORT_VID_MASK,
+ G0_PORT_VID(vlan->vid));
} else if (vlan->vid && priv->ports[port].pvid == vlan->vid) {
/* This VLAN is overwritten without PVID, so unset it */
priv->ports[port].pvid = G0_PORT_VID_DEF;
/* Only accept tagged frames if the port is VLAN-aware */
if (dsa_port_is_vlan_filtering(dsa_to_port(ds, port)))
- mt7530_rmw(priv, MT7530_PVC_P(port), ACC_FRM_MASK,
- MT7530_VLAN_ACC_TAGGED);
+ regmap_update_bits(priv->regmap, MT7530_PVC_P(port),
+ ACC_FRM_MASK,
+ MT7530_VLAN_ACC_TAGGED);
- mt7530_rmw(priv, MT7530_PPBV1_P(port), G0_PORT_VID_MASK,
- G0_PORT_VID_DEF);
+ regmap_update_bits(priv->regmap, MT7530_PPBV1_P(port),
+ G0_PORT_VID_MASK, G0_PORT_VID_DEF);
}
mutex_unlock(&priv->reg_mutex);
@@ -2092,11 +2036,12 @@ skip_vlan_table:
/* Only accept tagged frames if the port is VLAN-aware */
if (dsa_port_is_vlan_filtering(dsa_to_port(ds, port)))
- mt7530_rmw(priv, MT7530_PVC_P(port), ACC_FRM_MASK,
- MT7530_VLAN_ACC_TAGGED);
+ regmap_update_bits(priv->regmap, MT7530_PVC_P(port),
+ ACC_FRM_MASK,
+ MT7530_VLAN_ACC_TAGGED);
- mt7530_rmw(priv, MT7530_PPBV1_P(port), G0_PORT_VID_MASK,
- G0_PORT_VID_DEF);
+ regmap_update_bits(priv->regmap, MT7530_PPBV1_P(port),
+ G0_PORT_VID_MASK, G0_PORT_VID_DEF);
}
@@ -2117,7 +2062,7 @@ static int mt753x_port_mirror_add(struct dsa_switch *ds, int port,
if ((ingress ? priv->mirror_rx : priv->mirror_tx) & BIT(port))
return -EEXIST;
- val = mt7530_read(priv, MT753X_MIRROR_REG(priv->id));
+ regmap_read(priv->regmap, MT753X_MIRROR_REG(priv->id), &val);
/* MT7530 only supports one monitor port */
monitor_port = MT753X_MIRROR_PORT_GET(priv->id, val);
@@ -2128,9 +2073,9 @@ static int mt753x_port_mirror_add(struct dsa_switch *ds, int port,
val |= MT753X_MIRROR_EN(priv->id);
val &= ~MT753X_MIRROR_PORT_MASK(priv->id);
val |= MT753X_MIRROR_PORT_SET(priv->id, mirror->to_local_port);
- mt7530_write(priv, MT753X_MIRROR_REG(priv->id), val);
+ regmap_write(priv->regmap, MT753X_MIRROR_REG(priv->id), val);
- val = mt7530_read(priv, MT7530_PCR_P(port));
+ regmap_read(priv->regmap, MT7530_PCR_P(port), &val);
if (ingress) {
val |= PORT_RX_MIR;
priv->mirror_rx |= BIT(port);
@@ -2138,7 +2083,7 @@ static int mt753x_port_mirror_add(struct dsa_switch *ds, int port,
val |= PORT_TX_MIR;
priv->mirror_tx |= BIT(port);
}
- mt7530_write(priv, MT7530_PCR_P(port), val);
+ regmap_write(priv->regmap, MT7530_PCR_P(port), val);
return 0;
}
@@ -2149,7 +2094,7 @@ static void mt753x_port_mirror_del(struct dsa_switch *ds, int port,
struct mt7530_priv *priv = ds->priv;
u32 val;
- val = mt7530_read(priv, MT7530_PCR_P(port));
+ regmap_read(priv->regmap, MT7530_PCR_P(port), &val);
if (mirror->ingress) {
val &= ~PORT_RX_MIR;
priv->mirror_rx &= ~BIT(port);
@@ -2157,12 +2102,12 @@ static void mt753x_port_mirror_del(struct dsa_switch *ds, int port,
val &= ~PORT_TX_MIR;
priv->mirror_tx &= ~BIT(port);
}
- mt7530_write(priv, MT7530_PCR_P(port), val);
+ regmap_write(priv->regmap, MT7530_PCR_P(port), val);
if (!priv->mirror_rx && !priv->mirror_tx) {
- val = mt7530_read(priv, MT753X_MIRROR_REG(priv->id));
+ regmap_read(priv->regmap, MT753X_MIRROR_REG(priv->id), &val);
val &= ~MT753X_MIRROR_EN(priv->id);
- mt7530_write(priv, MT753X_MIRROR_REG(priv->id), val);
+ regmap_write(priv->regmap, MT753X_MIRROR_REG(priv->id), val);
}
}
@@ -2192,8 +2137,11 @@ mt7530_gpio_get(struct gpio_chip *gc, unsigned int offset)
{
struct mt7530_priv *priv = gpiochip_get_data(gc);
u32 bit = mt7530_gpio_to_bit(offset);
+ u32 val;
+
+ regmap_read(priv->regmap, MT7530_LED_GPIO_DATA, &val);
- return !!(mt7530_read(priv, MT7530_LED_GPIO_DATA) & bit);
+ return !!(val & bit);
}
static int
@@ -2202,10 +2150,7 @@ mt7530_gpio_set(struct gpio_chip *gc, unsigned int offset, int value)
struct mt7530_priv *priv = gpiochip_get_data(gc);
u32 bit = mt7530_gpio_to_bit(offset);
- if (value)
- mt7530_set(priv, MT7530_LED_GPIO_DATA, bit);
- else
- mt7530_clear(priv, MT7530_LED_GPIO_DATA, bit);
+ regmap_assign_bits(priv->regmap, MT7530_LED_GPIO_DATA, bit, value);
return 0;
}
@@ -2215,8 +2160,11 @@ mt7530_gpio_get_direction(struct gpio_chip *gc, unsigned int offset)
{
struct mt7530_priv *priv = gpiochip_get_data(gc);
u32 bit = mt7530_gpio_to_bit(offset);
+ u32 val;
- return (mt7530_read(priv, MT7530_LED_GPIO_DIR) & bit) ?
+ regmap_read(priv->regmap, MT7530_LED_GPIO_DIR, &val);
+
+ return (val & bit) ?
GPIO_LINE_DIRECTION_OUT : GPIO_LINE_DIRECTION_IN;
}
@@ -2226,8 +2174,8 @@ mt7530_gpio_direction_input(struct gpio_chip *gc, unsigned int offset)
struct mt7530_priv *priv = gpiochip_get_data(gc);
u32 bit = mt7530_gpio_to_bit(offset);
- mt7530_clear(priv, MT7530_LED_GPIO_OE, bit);
- mt7530_clear(priv, MT7530_LED_GPIO_DIR, bit);
+ regmap_clear_bits(priv->regmap, MT7530_LED_GPIO_OE, bit);
+ regmap_clear_bits(priv->regmap, MT7530_LED_GPIO_DIR, bit);
return 0;
}
@@ -2238,14 +2186,11 @@ mt7530_gpio_direction_output(struct gpio_chip *gc, unsigned int offset, int valu
struct mt7530_priv *priv = gpiochip_get_data(gc);
u32 bit = mt7530_gpio_to_bit(offset);
- mt7530_set(priv, MT7530_LED_GPIO_DIR, bit);
+ regmap_set_bits(priv->regmap, MT7530_LED_GPIO_DIR, bit);
- if (value)
- mt7530_set(priv, MT7530_LED_GPIO_DATA, bit);
- else
- mt7530_clear(priv, MT7530_LED_GPIO_DATA, bit);
+ regmap_assign_bits(priv->regmap, MT7530_LED_GPIO_DATA, bit, value);
- mt7530_set(priv, MT7530_LED_GPIO_OE, bit);
+ regmap_set_bits(priv->regmap, MT7530_LED_GPIO_OE, bit);
return 0;
}
@@ -2260,9 +2205,9 @@ mt7530_setup_gpio(struct mt7530_priv *priv)
if (!gc)
return -ENOMEM;
- mt7530_write(priv, MT7530_LED_GPIO_OE, 0);
- mt7530_write(priv, MT7530_LED_GPIO_DIR, 0);
- mt7530_write(priv, MT7530_LED_IO_MODE, 0);
+ regmap_write(priv->regmap, MT7530_LED_GPIO_OE, 0);
+ regmap_write(priv->regmap, MT7530_LED_GPIO_DIR, 0);
+ regmap_write(priv->regmap, MT7530_LED_IO_MODE, 0);
gc->label = "mt7530";
gc->parent = dev;
@@ -2319,21 +2264,6 @@ static const struct regmap_irq mt7530_irqs[] = {
REGMAP_IRQ_REG_LINE(31, 32), /* ACL */
};
-/* Serialize regmap-irq's mask sync like every other regmap user */
-static int mt7530_irq_mask_sync(int index, unsigned int mask_buf_def,
- unsigned int mask_buf, void *irq_drv_data)
-{
- struct mt7530_priv *priv = irq_drv_data;
- int ret;
-
- mt7530_mutex_lock(priv);
- ret = regmap_update_bits(priv->regmap, MT7530_SYS_INT_EN,
- mask_buf_def, ~mask_buf);
- mt7530_mutex_unlock(priv);
-
- return ret;
-}
-
static const struct regmap_irq_chip mt7530_regmap_irq_chip = {
.name = KBUILD_MODNAME,
.status_base = MT7530_SYS_INT_STS,
@@ -2343,14 +2273,12 @@ static const struct regmap_irq_chip mt7530_regmap_irq_chip = {
.irqs = mt7530_irqs,
.num_irqs = ARRAY_SIZE(mt7530_irqs),
.num_regs = 1,
- .handle_mask_sync = mt7530_irq_mask_sync,
};
static int
mt7530_setup_irq(struct mt7530_priv *priv)
{
struct regmap_irq_chip_data *irq_data;
- struct regmap_irq_chip *chip;
struct device *dev = priv->dev;
struct device_node *np = dev->of_node;
int irq, ret;
@@ -2368,19 +2296,13 @@ mt7530_setup_irq(struct mt7530_priv *priv)
/* This register must be set for MT7530 to properly fire interrupts */
if (priv->id == ID_MT7530 || priv->id == ID_MT7621)
- mt7530_set(priv, MT7530_TOP_SIG_CTRL, TOP_SIG_CTRL_NORMAL);
-
- chip = devm_kmemdup(dev, &mt7530_regmap_irq_chip, sizeof(*chip),
- GFP_KERNEL);
- if (!chip)
- return -ENOMEM;
-
- chip->irq_drv_data = priv;
+ regmap_set_bits(priv->regmap, MT7530_TOP_SIG_CTRL,
+ TOP_SIG_CTRL_NORMAL);
ret = devm_regmap_add_irq_chip_fwnode(dev, dev_fwnode(dev),
priv->regmap, irq,
IRQF_ONESHOT,
- 0, chip,
+ 0, &mt7530_regmap_irq_chip,
&irq_data);
if (ret)
return ret;
@@ -2461,7 +2383,6 @@ mt7530_setup(struct dsa_switch *ds)
struct device_node *dn = NULL;
struct device_node *phy_node;
struct device_node *mac_np;
- struct mt7530_dummy_poll p;
phy_interface_t interface;
struct dsa_port *cpu_dp;
u32 id, val;
@@ -2522,16 +2443,15 @@ mt7530_setup(struct dsa_switch *ds)
}
/* Waiting for MT7530 got to stable */
- INIT_MT7530_DUMMY_POLL(&p, priv, MT753X_TRAP);
- ret = readx_poll_timeout(_mt7530_read, &p, val, val != 0,
- 20, 1000000);
+ ret = regmap_read_poll_timeout(priv->regmap, MT753X_TRAP, val,
+ val != 0, 20, 1000000);
if (ret < 0) {
dev_err(priv->dev, "reset timeout\n");
return ret;
}
- id = mt7530_read(priv, MT7530_CREV);
- id >>= CHIP_NAME_SHIFT;
+ regmap_read(priv->regmap, MT7530_CREV, &id);
+ id = FIELD_GET(CHIP_NAME_MASK, id);
if (id != MT7530_ID) {
dev_err(priv->dev, "chip %x can't be supported\n", id);
return -ENODEV;
@@ -2544,24 +2464,24 @@ mt7530_setup(struct dsa_switch *ds)
}
/* Reset the switch through internal reset */
- mt7530_write(priv, MT7530_SYS_CTRL,
- SYS_CTRL_PHY_RST | SYS_CTRL_SW_RST |
- SYS_CTRL_REG_RST);
+ regmap_write(priv->regmap, MT7530_SYS_CTRL,
+ SYS_CTRL_PHY_RST | SYS_CTRL_SW_RST | SYS_CTRL_REG_RST);
/* Lower Tx driving for TRGMII path */
for (i = 0; i < NUM_TRGMII_CTRL; i++)
- mt7530_write(priv, MT7530_TRGMII_TD_ODT(i),
+ regmap_write(priv->regmap, MT7530_TRGMII_TD_ODT(i),
TD_DM_DRVP(8) | TD_DM_DRVN(8));
for (i = 0; i < NUM_TRGMII_CTRL; i++)
- mt7530_rmw(priv, MT7530_TRGMII_RD(i),
- RD_TAP_MASK, RD_TAP(16));
+ regmap_update_bits(priv->regmap, MT7530_TRGMII_RD(i),
+ RD_TAP_MASK, RD_TAP(16));
/* Allow modifying the trap and directly access PHY registers via the
* MDIO bus the switch is on.
*/
- mt7530_rmw(priv, MT753X_MTRAP, MT7530_CHG_TRAP |
- MT7530_PHY_INDIRECT_ACCESS, MT7530_CHG_TRAP);
+ regmap_update_bits(priv->regmap, MT753X_MTRAP,
+ MT7530_CHG_TRAP | MT7530_PHY_INDIRECT_ACCESS,
+ MT7530_CHG_TRAP);
if ((val & MT7530_XTAL_MASK) == MT7530_XTAL_40MHZ)
mt7530_pll_setup(priv);
@@ -2575,17 +2495,16 @@ mt7530_setup(struct dsa_switch *ds)
/* Clear link settings and enable force mode to force link down
* on all ports until they're enabled later.
*/
- mt7530_rmw(priv, MT753X_PMCR_P(i),
- PMCR_LINK_SETTINGS_MASK |
- MT753X_FORCE_MODE(priv->id),
- MT753X_FORCE_MODE(priv->id));
+ regmap_update_bits(priv->regmap, MT753X_PMCR_P(i),
+ PMCR_LINK_SETTINGS_MASK | MT753X_FORCE_MODE(priv->id),
+ MT753X_FORCE_MODE(priv->id));
/* Disable forwarding by default on all ports */
- mt7530_rmw(priv, MT7530_PCR_P(i), PCR_MATRIX_MASK,
- PCR_MATRIX_CLR);
+ regmap_update_bits(priv->regmap, MT7530_PCR_P(i),
+ PCR_MATRIX_MASK, PCR_MATRIX_CLR);
/* Disable learning by default on all ports */
- mt7530_set(priv, MT7530_PSC_P(i), SA_DIS);
+ regmap_set_bits(priv->regmap, MT7530_PSC_P(i), SA_DIS);
if (dsa_is_cpu_port(ds, i)) {
mt753x_cpu_port_enable(ds, i);
@@ -2593,16 +2512,17 @@ mt7530_setup(struct dsa_switch *ds)
mt7530_port_disable(ds, i);
/* Set default PVID to 0 on all user ports */
- mt7530_rmw(priv, MT7530_PPBV1_P(i), G0_PORT_VID_MASK,
- G0_PORT_VID_DEF);
+ regmap_update_bits(priv->regmap, MT7530_PPBV1_P(i),
+ G0_PORT_VID_MASK, G0_PORT_VID_DEF);
}
/* Enable consistent egress tag */
- mt7530_rmw(priv, MT7530_PVC_P(i), PVC_EG_TAG_MASK,
- PVC_EG_TAG(MT7530_VLAN_EG_CONSISTENT));
+ regmap_update_bits(priv->regmap, MT7530_PVC_P(i),
+ PVC_EG_TAG_MASK,
+ PVC_EG_TAG(MT7530_VLAN_EG_CONSISTENT));
}
/* Allow mirroring frames received on the local port (monitor port). */
- mt7530_set(priv, MT753X_AGC, LOCAL_EN);
+ regmap_set_bits(priv->regmap, MT753X_AGC, LOCAL_EN);
/* Setup VLAN ID 0 for VLAN-unaware bridges */
ret = mt7530_setup_vlan0(priv);
@@ -2649,7 +2569,8 @@ mt7530_setup(struct dsa_switch *ds)
if (priv->p5_mode == MUX_PHY_P0 ||
priv->p5_mode == MUX_PHY_P4) {
- mt7530_clear(priv, MT753X_MTRAP, MT7530_P5_DIS);
+ regmap_clear_bits(priv->regmap, MT753X_MTRAP,
+ MT7530_P5_DIS);
mt7530_setup_port5(ds, interface);
}
}
@@ -2688,26 +2609,26 @@ mt7531_setup_common(struct dsa_switch *ds)
mt7530_mib_reset(ds);
/* Disable flooding on all ports */
- mt7530_clear(priv, MT753X_MFC, BC_FFP_MASK | UNM_FFP_MASK |
- UNU_FFP_MASK);
+ regmap_clear_bits(priv->regmap, MT753X_MFC,
+ BC_FFP_MASK | UNM_FFP_MASK | UNU_FFP_MASK);
for (i = 0; i < priv->ds->num_ports; i++) {
/* Clear link settings and enable force mode to force link down
* on all ports until they're enabled later.
*/
- mt7530_rmw(priv, MT753X_PMCR_P(i),
- PMCR_LINK_SETTINGS_MASK |
- MT753X_FORCE_MODE(priv->id),
- MT753X_FORCE_MODE(priv->id));
+ regmap_update_bits(priv->regmap, MT753X_PMCR_P(i),
+ PMCR_LINK_SETTINGS_MASK | MT753X_FORCE_MODE(priv->id),
+ MT753X_FORCE_MODE(priv->id));
/* Disable forwarding by default on all ports */
- mt7530_rmw(priv, MT7530_PCR_P(i), PCR_MATRIX_MASK,
- PCR_MATRIX_CLR);
+ regmap_update_bits(priv->regmap, MT7530_PCR_P(i),
+ PCR_MATRIX_MASK, PCR_MATRIX_CLR);
/* Disable learning by default on all ports */
- mt7530_set(priv, MT7530_PSC_P(i), SA_DIS);
+ regmap_set_bits(priv->regmap, MT7530_PSC_P(i), SA_DIS);
- mt7530_set(priv, MT7531_DBG_CNT(i), MT7531_DIS_CLR);
+ regmap_set_bits(priv->regmap, MT7531_DBG_CNT(i),
+ MT7531_DIS_CLR);
if (dsa_is_cpu_port(ds, i)) {
mt753x_cpu_port_enable(ds, i);
@@ -2715,21 +2636,22 @@ mt7531_setup_common(struct dsa_switch *ds)
mt7530_port_disable(ds, i);
/* Set default PVID to 0 on all user ports */
- mt7530_rmw(priv, MT7530_PPBV1_P(i), G0_PORT_VID_MASK,
- G0_PORT_VID_DEF);
+ regmap_update_bits(priv->regmap, MT7530_PPBV1_P(i),
+ G0_PORT_VID_MASK, G0_PORT_VID_DEF);
}
/* Enable consistent egress tag */
- mt7530_rmw(priv, MT7530_PVC_P(i), PVC_EG_TAG_MASK,
- PVC_EG_TAG(MT7530_VLAN_EG_CONSISTENT));
+ regmap_update_bits(priv->regmap, MT7530_PVC_P(i),
+ PVC_EG_TAG_MASK,
+ PVC_EG_TAG(MT7530_VLAN_EG_CONSISTENT));
}
/* Allow mirroring frames received on the local port (monitor port). */
- mt7530_set(priv, MT753X_AGC, LOCAL_EN);
+ regmap_set_bits(priv->regmap, MT753X_AGC, LOCAL_EN);
/* Enable Special Tag for rx frames */
if (priv->id == ID_EN7581 || priv->id == ID_AN7583)
- mt7530_write(priv, MT753X_CPORT_SPTAG_CFG,
+ regmap_write(priv->regmap, MT753X_CPORT_SPTAG_CFG,
CPORT_SW2FE_STAG_EN | CPORT_FE2SW_STAG_EN);
/* Flush the FDB table */
@@ -2745,7 +2667,6 @@ static int
mt7531_setup(struct dsa_switch *ds)
{
struct mt7530_priv *priv = ds->priv;
- struct mt7530_dummy_poll p;
u32 val, id;
int ret, i;
@@ -2763,16 +2684,15 @@ mt7531_setup(struct dsa_switch *ds)
}
/* Waiting for MT7530 got to stable */
- INIT_MT7530_DUMMY_POLL(&p, priv, MT753X_TRAP);
- ret = readx_poll_timeout(_mt7530_read, &p, val, val != 0,
- 20, 1000000);
+ ret = regmap_read_poll_timeout(priv->regmap, MT753X_TRAP, val,
+ val != 0, 20, 1000000);
if (ret < 0) {
dev_err(priv->dev, "reset timeout\n");
return ret;
}
- id = mt7530_read(priv, MT7531_CREV);
- id >>= CHIP_NAME_SHIFT;
+ regmap_read(priv->regmap, MT7531_CREV, &id);
+ id = FIELD_GET(CHIP_NAME_MASK, id);
if (id != MT7531_ID) {
dev_err(priv->dev, "chip %x can't be supported\n", id);
@@ -2782,15 +2702,17 @@ mt7531_setup(struct dsa_switch *ds)
/* MT7531AE has got two SGMII units. One for port 5, one for port 6.
* MT7531BE has got only one SGMII unit which is for port 6.
*/
- val = mt7530_read(priv, MT7531_TOP_SIG_SR);
+ regmap_read(priv->regmap, MT7531_TOP_SIG_SR, &val);
priv->p5_sgmii = !!(val & PAD_DUAL_SGMII_EN);
/* Force link down on all ports before internal reset */
for (i = 0; i < priv->ds->num_ports; i++)
- mt7530_write(priv, MT753X_PMCR_P(i), MT7531_FORCE_MODE_LNK);
+ regmap_write(priv->regmap, MT753X_PMCR_P(i),
+ MT7531_FORCE_MODE_LNK);
/* Reset the switch through internal reset */
- mt7530_write(priv, MT7530_SYS_CTRL, SYS_CTRL_SW_RST | SYS_CTRL_REG_RST);
+ regmap_write(priv->regmap, MT7530_SYS_CTRL,
+ SYS_CTRL_SW_RST | SYS_CTRL_REG_RST);
if (!priv->p5_sgmii) {
mt7531_pll_setup(priv);
@@ -2799,14 +2721,16 @@ mt7531_setup(struct dsa_switch *ds)
* MT7531AE. Set the GPIO 11-12 pins to function as MDC and MDIO
* to expose the MDIO bus of the switch.
*/
- mt7530_rmw(priv, MT7531_GPIO_MODE1, MT7531_GPIO11_RG_RXD2_MASK,
- MT7531_EXT_P_MDC_11);
- mt7530_rmw(priv, MT7531_GPIO_MODE1, MT7531_GPIO12_RG_RXD3_MASK,
- MT7531_EXT_P_MDIO_12);
+ regmap_update_bits(priv->regmap, MT7531_GPIO_MODE1,
+ MT7531_GPIO11_RG_RXD2_MASK,
+ MT7531_EXT_P_MDC_11);
+ regmap_update_bits(priv->regmap, MT7531_GPIO_MODE1,
+ MT7531_GPIO12_RG_RXD3_MASK,
+ MT7531_EXT_P_MDIO_12);
}
- mt7530_rmw(priv, MT7531_GPIO_MODE0, MT7531_GPIO0_MASK,
- MT7531_GPIO0_INTERRUPT);
+ regmap_update_bits(priv->regmap, MT7531_GPIO_MODE0, MT7531_GPIO0_MASK,
+ MT7531_GPIO0_INTERRUPT);
/* Enable Energy-Efficient Ethernet (EEE) and PHY core PLL, since
* phy_device has not yet been created provided for
@@ -2998,7 +2922,7 @@ static void mt7531_rgmii_setup(struct mt7530_priv *priv,
{
u32 val;
- val = mt7530_read(priv, MT7531_CLKGEN_CTRL);
+ regmap_read(priv->regmap, MT7531_CLKGEN_CTRL, &val);
val |= GP_CLK_EN;
val &= ~GP_MODE_MASK;
val |= GP_MODE(MT7531_GP_MODE_RGMII);
@@ -3029,7 +2953,7 @@ static void mt7531_rgmii_setup(struct mt7530_priv *priv,
}
}
- mt7530_write(priv, MT7531_CLKGEN_CTRL, val);
+ regmap_write(priv->regmap, MT7531_CLKGEN_CTRL, val);
}
static void
@@ -3082,7 +3006,8 @@ mt753x_phylink_mac_config(struct phylink_config *config, unsigned int mode,
/* Are we connected to external phy */
if (port == 5 && dsa_is_user_port(ds, 5))
- mt7530_set(priv, MT753X_PMCR_P(port), PMCR_EXT_PHY);
+ regmap_set_bits(priv->regmap, MT753X_PMCR_P(port),
+ PMCR_EXT_PHY);
}
static void mt753x_phylink_mac_link_down(struct phylink_config *config,
@@ -3092,7 +3017,8 @@ static void mt753x_phylink_mac_link_down(struct phylink_config *config,
struct dsa_port *dp = dsa_phylink_to_port(config);
struct mt7530_priv *priv = dp->ds->priv;
- mt7530_clear(priv, MT753X_PMCR_P(dp->index), PMCR_LINK_SETTINGS_MASK);
+ regmap_clear_bits(priv->regmap, MT753X_PMCR_P(dp->index),
+ PMCR_LINK_SETTINGS_MASK);
}
static void mt753x_phylink_mac_link_up(struct phylink_config *config,
@@ -3126,7 +3052,7 @@ static void mt753x_phylink_mac_link_up(struct phylink_config *config,
mcr |= PMCR_FORCE_RX_FC_EN;
}
- mt7530_set(priv, MT753X_PMCR_P(dp->index), mcr);
+ regmap_set_bits(priv->regmap, MT753X_PMCR_P(dp->index), mcr);
}
static void mt753x_phylink_mac_disable_tx_lpi(struct phylink_config *config)
@@ -3134,8 +3060,8 @@ static void mt753x_phylink_mac_disable_tx_lpi(struct phylink_config *config)
struct dsa_port *dp = dsa_phylink_to_port(config);
struct mt7530_priv *priv = dp->ds->priv;
- mt7530_clear(priv, MT753X_PMCR_P(dp->index),
- PMCR_FORCE_EEE1G | PMCR_FORCE_EEE100);
+ regmap_clear_bits(priv->regmap, MT753X_PMCR_P(dp->index),
+ PMCR_FORCE_EEE1G | PMCR_FORCE_EEE100);
}
static int mt753x_phylink_mac_enable_tx_lpi(struct phylink_config *config,
@@ -3156,11 +3082,11 @@ static int mt753x_phylink_mac_enable_tx_lpi(struct phylink_config *config,
else
val = LPI_THRESH_MASK;
- mt7530_rmw(priv, MT753X_PMEEECR_P(dp->index),
- LPI_THRESH_MASK | LPI_MODE_EN, val);
+ regmap_update_bits(priv->regmap, MT753X_PMEEECR_P(dp->index),
+ LPI_THRESH_MASK | LPI_MODE_EN, val);
- mt7530_set(priv, MT753X_PMCR_P(dp->index),
- PMCR_FORCE_EEE1G | PMCR_FORCE_EEE100);
+ regmap_set_bits(priv->regmap, MT753X_PMCR_P(dp->index),
+ PMCR_FORCE_EEE1G | PMCR_FORCE_EEE100);
return 0;
}
@@ -3182,7 +3108,9 @@ static void mt753x_phylink_get_caps(struct dsa_switch *ds, int port,
*/
if (priv->id != ID_EN7528 &&
config->mac_capabilities & (MAC_100FD | MAC_1000FD)) {
- u32 eeecr = mt7530_read(priv, MT753X_PMEEECR_P(port));
+ u32 eeecr;
+
+ regmap_read(priv->regmap, MT753X_PMEEECR_P(port), &eeecr);
/* LPI above 1 Gbps is not supported */
config->lpi_capabilities = config->mac_capabilities &
@@ -3218,7 +3146,7 @@ static void mt7530_pcs_get_state(struct phylink_pcs *pcs, unsigned int neg_mode,
int port = pcs_to_mt753x_pcs(pcs)->port;
u32 pmsr;
- pmsr = mt7530_read(priv, MT7530_PMSR_P(port));
+ regmap_read(priv->regmap, MT7530_PMSR_P(port), &pmsr);
state->link = (pmsr & PMSR_LINK);
state->an_complete = state->link;
@@ -3323,6 +3251,43 @@ static int mt753x_set_mac_eee(struct dsa_switch *ds, int port,
return 0;
}
+static int
+mt753x_port_change_conduit(struct dsa_switch *ds, int port,
+ struct net_device *conduit,
+ struct netlink_ext_ack *extack)
+{
+ struct dsa_port *new_cpu_dp = conduit->dsa_ptr;
+ struct dsa_port *dp = dsa_to_port(ds, port);
+ struct mt7530_priv *priv = ds->priv;
+
+ if (priv->id != ID_MT7531) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Changing DSA conduit is only supported on MT7531");
+ return -EOPNOTSUPP;
+ }
+
+ if (new_cpu_dp->ds != ds) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Cannot assign a conduit on a different switch");
+ return -EOPNOTSUPP;
+ }
+
+ mutex_lock(&priv->reg_mutex);
+
+ /* dp->cpu_dp still points to the old CPU port */
+ priv->ports[port].pm &= ~PCR_MATRIX(BIT(dp->cpu_dp->index));
+ priv->ports[port].pm |= PCR_MATRIX(BIT(new_cpu_dp->index));
+ if (priv->ports[port].enable)
+ regmap_update_bits(priv->regmap, MT7530_PCR_P(port),
+ PCR_MATRIX_MASK, priv->ports[port].pm);
+
+ mutex_unlock(&priv->reg_mutex);
+
+ mt7530_port_fast_age(ds, port);
+
+ return 0;
+}
+
static void
mt753x_conduit_state_change(struct dsa_switch *ds,
const struct net_device *conduit,
@@ -3353,7 +3318,8 @@ mt753x_conduit_state_change(struct dsa_switch *ds,
MT7530_CPU_PORT(__ffs(priv->active_cpu_ports));
}
- mt7530_rmw(priv, MT753X_MFC, MT7530_CPU_EN | MT7530_CPU_PORT_MASK, val);
+ regmap_update_bits(priv->regmap, MT753X_MFC,
+ MT7530_CPU_EN | MT7530_CPU_PORT_MASK, val);
}
static int mt753x_tc_setup_qdisc_tbf(struct dsa_switch *ds, int port,
@@ -3370,8 +3336,8 @@ static int mt753x_tc_setup_qdisc_tbf(struct dsa_switch *ds, int port,
case TC_TBF_DESTROY: {
u32 val, tick;
- mt7530_rmw(priv, MT753X_GERLCR, EGR_BC_MASK,
- EGR_BC_CRC_IPG_PREAMBLE);
+ regmap_update_bits(priv->regmap, MT753X_GERLCR, EGR_BC_MASK,
+ EGR_BC_CRC_IPG_PREAMBLE);
/* if rate is greater than 10Mbps tick is 1/32 ms,
* 1ms otherwise
@@ -3382,7 +3348,7 @@ static int mt753x_tc_setup_qdisc_tbf(struct dsa_switch *ds, int port,
FIELD_PREP(ERLCR_EXP_MASK, tick) |
ERLCR_TBF_MODE_MASK |
FIELD_PREP(ERLCR_MANT_MASK, 0xf);
- mt7530_write(priv, MT753X_ERLCR_P(port), val);
+ regmap_write(priv->regmap, MT753X_ERLCR_P(port), val);
break;
}
default:
@@ -3415,16 +3381,16 @@ static int mt7988_setup(struct dsa_switch *ds)
/* AN7583 require additional tweak to CONN_CFG */
if (priv->id == ID_AN7583)
- mt7530_rmw(priv, AN7583_GEPHY_CONN_CFG,
- AN7583_CSR_DPHY_CKIN_SEL |
- AN7583_CSR_PHY_CORE_REG_CLK_SEL |
- AN7583_CSR_ETHER_AFE_PWD,
- AN7583_CSR_DPHY_CKIN_SEL |
- AN7583_CSR_PHY_CORE_REG_CLK_SEL |
- FIELD_PREP(AN7583_CSR_ETHER_AFE_PWD, 0));
+ regmap_update_bits(priv->regmap, AN7583_GEPHY_CONN_CFG,
+ AN7583_CSR_DPHY_CKIN_SEL |
+ AN7583_CSR_PHY_CORE_REG_CLK_SEL |
+ AN7583_CSR_ETHER_AFE_PWD,
+ AN7583_CSR_DPHY_CKIN_SEL |
+ AN7583_CSR_PHY_CORE_REG_CLK_SEL |
+ FIELD_PREP(AN7583_CSR_ETHER_AFE_PWD, 0));
/* Reset the switch PHYs */
- mt7530_write(priv, MT7530_SYS_CTRL, SYS_CTRL_PHY_RST);
+ regmap_write(priv->regmap, MT7530_SYS_CTRL, SYS_CTRL_PHY_RST);
return mt7531_setup_common(ds);
}
@@ -3434,6 +3400,7 @@ static const struct dsa_switch_ops mt7530_switch_ops = {
.setup = mt753x_setup,
.teardown = mt753x_teardown,
.preferred_default_local_cpu_port = mt753x_preferred_default_local_cpu_port,
+ .port_change_conduit = mt753x_port_change_conduit,
.get_strings = mt7530_get_strings,
.get_ethtool_stats = mt7530_get_ethtool_stats,
.get_sset_count = mt7530_get_sset_count,
@@ -3451,6 +3418,7 @@ static const struct dsa_switch_ops mt7530_switch_ops = {
.port_bridge_flags = mt7530_port_bridge_flags,
.port_bridge_join = mt7530_port_bridge_join,
.port_bridge_leave = mt7530_port_bridge_leave,
+ .port_fast_age = mt7530_port_fast_age,
.port_fdb_add = mt7530_port_fdb_add,
.port_fdb_del = mt7530_port_fdb_del,
.port_fdb_dump = mt7530_port_fdb_dump,
diff --git a/drivers/net/dsa/mt7530.h b/drivers/net/dsa/mt7530.h
index 5f1e841f42c0..2bbbe617b52e 100644
--- a/drivers/net/dsa/mt7530.h
+++ b/drivers/net/dsa/mt7530.h
@@ -6,6 +6,8 @@
#ifndef __MT7530_H
#define __MT7530_H
+#include <linux/bitfield.h>
+
#define MT7530_NUM_PORTS 7
#define MT7530_NUM_PHYS 5
#define MT7530_NUM_FDB_RECORDS 2048
@@ -147,20 +149,24 @@ enum mt753x_to_cpu_fw {
#define STATIC_ENT 3
#define MT7530_ATA2 0x78
#define ATA2_IVL BIT(15)
-#define ATA2_FID(x) (((x) & 0x7) << 12)
+#define ATA2_FID_MASK GENMASK(14, 12)
+#define ATA2_FID(x) FIELD_PREP(ATA2_FID_MASK, x)
/* Register for address table write data */
#define MT7530_ATWD 0x7c
/* Register for address table control */
#define MT7530_ATC 0x80
-#define ATC_HASH (((x) & 0xfff) << 16)
+#define ATC_HASH_MASK GENMASK(27, 16)
+#define ATC_HASH(x) FIELD_PREP(ATC_HASH_MASK, x)
#define ATC_BUSY BIT(15)
#define ATC_SRCH_END BIT(14)
#define ATC_SRCH_HIT BIT(13)
#define ATC_INVALID BIT(12)
-#define ATC_MAT(x) (((x) & 0xf) << 8)
+#define ATC_MAT_MASK GENMASK(11, 8)
+#define ATC_MAT(x) FIELD_PREP(ATC_MAT_MASK, x)
#define ATC_MAT_MACTAB ATC_MAT(0)
+#define ATC_MAT_NON_STATIC_MAC ATC_MAT(4)
enum mt7530_fdb_cmd {
MT7530_FDB_READ = 0,
@@ -172,32 +178,29 @@ enum mt7530_fdb_cmd {
/* Registers for table search read address */
#define MT7530_TSRA1 0x84
-#define MAC_BYTE_0 24
-#define MAC_BYTE_1 16
-#define MAC_BYTE_2 8
-#define MAC_BYTE_3 0
-#define MAC_BYTE_MASK 0xff
+#define MAC_BYTE_0_MASK GENMASK(31, 24)
+#define MAC_BYTE_1_MASK GENMASK(23, 16)
+#define MAC_BYTE_2_MASK GENMASK(15, 8)
+#define MAC_BYTE_3_MASK GENMASK(7, 0)
#define MT7530_TSRA2 0x88
-#define MAC_BYTE_4 24
-#define MAC_BYTE_5 16
-#define CVID 0
-#define CVID_MASK 0xfff
+#define MAC_BYTE_4_MASK GENMASK(31, 24)
+#define MAC_BYTE_5_MASK GENMASK(23, 16)
+#define CVID_MASK GENMASK(11, 0)
#define MT7530_ATRD 0x8C
-#define AGE_TIMER 24
-#define AGE_TIMER_MASK 0xff
-#define PORT_MAP 4
-#define PORT_MAP_MASK 0xff
-#define ENT_STATUS 2
-#define ENT_STATUS_MASK 0x3
+#define AGE_TIMER_MASK GENMASK(31, 24)
+#define PORT_MAP_MASK GENMASK(11, 4)
+#define ENT_STATUS_MASK GENMASK(3, 2)
/* Register for vlan table control */
#define MT7530_VTCR 0x90
#define VTCR_BUSY BIT(31)
#define VTCR_INVALID BIT(16)
-#define VTCR_FUNC(x) (((x) & 0xf) << 12)
-#define VTCR_VID ((x) & 0xfff)
+#define VTCR_FUNC_MASK GENMASK(15, 12)
+#define VTCR_FUNC(x) FIELD_PREP(VTCR_FUNC_MASK, x)
+#define VTCR_VID_MASK GENMASK(11, 0)
+#define VTCR_VID(x) FIELD_PREP(VTCR_VID_MASK, x)
enum mt7530_vlan_cmd {
/* Read/Write the specified VID entry from VAWD register based
@@ -217,13 +220,13 @@ enum mt7530_vlan_cmd {
/* Per VLAN Egress Tag Control */
#define VTAG_EN BIT(28)
/* VLAN Member Control */
-#define PORT_MEM(x) (((x) & 0xff) << 16)
+#define PORT_MEM_MASK GENMASK(23, 16)
+#define PORT_MEM(x) FIELD_PREP(PORT_MEM_MASK, x)
/* Filter ID */
-#define FID(x) (((x) & 0x7) << 1)
+#define FID_MASK GENMASK(3, 1)
+#define FID(x) FIELD_PREP(FID_MASK, x)
/* VLAN Entry Valid */
#define VLAN_VALID BIT(0)
-#define PORT_MEM_SHFT 16
-#define PORT_MEM_MASK 0xff
enum mt7530_fid {
FID_STANDALONE = 0,
@@ -248,11 +251,11 @@ enum mt7530_vlan_egress_attr {
/* Age count */
#define AGE_CNT_MASK GENMASK(19, 12)
#define AGE_CNT_MAX 0xff
-#define AGE_CNT(x) (AGE_CNT_MASK & ((x) << 12))
+#define AGE_CNT(x) FIELD_PREP(AGE_CNT_MASK, x)
/* Age unit */
#define AGE_UNIT_MASK GENMASK(11, 0)
#define AGE_UNIT_MAX 0xfff
-#define AGE_UNIT(x) (AGE_UNIT_MASK & (x))
+#define AGE_UNIT(x) FIELD_PREP(AGE_UNIT_MASK, x)
#define MT753X_ERLCR_P(x) (0x1040 + ((x) * 0x100))
#define ERLCR_CIR_MASK GENMASK(31, 16)
@@ -283,30 +286,31 @@ enum mt7530_stp_state {
#define MT7530_PCR_P(x) (0x2004 + ((x) * 0x100))
#define PORT_TX_MIR BIT(9)
#define PORT_RX_MIR BIT(8)
-#define PORT_VLAN(x) ((x) & 0x3)
+#define PCR_PORT_VLAN_MASK GENMASK(1, 0)
enum mt7530_port_mode {
/* Port Matrix Mode: Frames are forwarded by the PCR_MATRIX members. */
- MT7530_PORT_MATRIX_MODE = PORT_VLAN(0),
+ MT7530_PORT_MATRIX_MODE = 0,
/* Fallback Mode: Forward received frames with ingress ports that do
* not belong to the VLAN member. Frames whose VID is not listed on
* the VLAN table are forwarded by the PCR_MATRIX members.
*/
- MT7530_PORT_FALLBACK_MODE = PORT_VLAN(1),
+ MT7530_PORT_FALLBACK_MODE = 1,
/* Security Mode: Discard any frame due to ingress membership
* violation or VID missed on the VLAN table.
*/
- MT7530_PORT_SECURITY_MODE = PORT_VLAN(3),
+ MT7530_PORT_SECURITY_MODE = 3,
};
-#define PCR_MATRIX(x) (((x) & 0xff) << 16)
-#define PORT_PRI(x) (((x) & 0x7) << 24)
-#define EG_TAG(x) (((x) & 0x3) << 28)
-#define PCR_MATRIX_MASK PCR_MATRIX(0xff)
+#define PCR_MATRIX_MASK GENMASK(23, 16)
+#define PCR_MATRIX(x) FIELD_PREP(PCR_MATRIX_MASK, x)
+#define PORT_PRI_MASK GENMASK(26, 24)
+#define PORT_PRI(x) FIELD_PREP(PORT_PRI_MASK, x)
+#define EG_TAG_MASK GENMASK(29, 28)
+#define EG_TAG(x) FIELD_PREP(EG_TAG_MASK, x)
#define PCR_MATRIX_CLR PCR_MATRIX(0)
-#define PCR_PORT_VLAN_MASK PORT_VLAN(3)
/* Register for port security control */
#define MT7530_PSC_P(x) (0x200c + ((x) * 0x100))
@@ -315,10 +319,10 @@ enum mt7530_port_mode {
/* Register for port vlan control */
#define MT7530_PVC_P(x) (0x2010 + ((x) * 0x100))
#define PORT_SPEC_TAG BIT(5)
-#define PVC_EG_TAG(x) (((x) & 0x7) << 8)
-#define PVC_EG_TAG_MASK PVC_EG_TAG(7)
-#define VLAN_ATTR(x) (((x) & 0x3) << 6)
-#define VLAN_ATTR_MASK VLAN_ATTR(3)
+#define PVC_EG_TAG_MASK GENMASK(10, 8)
+#define PVC_EG_TAG(x) FIELD_PREP(PVC_EG_TAG_MASK, x)
+#define VLAN_ATTR_MASK GENMASK(7, 6)
+#define VLAN_ATTR(x) FIELD_PREP(VLAN_ATTR_MASK, x)
#define ACC_FRM_MASK GENMASK(1, 0)
enum mt7530_vlan_port_eg_tag {
@@ -338,12 +342,13 @@ enum mt7530_vlan_port_acc_frm {
MT7530_VLAN_ACC_UNTAGGED = 2,
};
-#define STAG_VPID (((x) & 0xffff) << 16)
+#define STAG_VPID_MASK GENMASK(31, 16)
+#define STAG_VPID(x) FIELD_PREP(STAG_VPID_MASK, x)
/* Register for port port-and-protocol based vlan 1 control */
#define MT7530_PPBV1_P(x) (0x2014 + ((x) * 0x100))
-#define G0_PORT_VID(x) (((x) & 0xfff) << 0)
-#define G0_PORT_VID_MASK G0_PORT_VID(0xfff)
+#define G0_PORT_VID_MASK GENMASK(11, 0)
+#define G0_PORT_VID(x) FIELD_PREP(G0_PORT_VID_MASK, x)
#define G0_PORT_VID_DEF G0_PORT_VID(0)
/* Register for port MAC control register */
@@ -419,8 +424,8 @@ enum mt7530_vlan_port_acc_frm {
#define MT7531_DIS_CLR BIT(31)
#define MT7530_GMACCR 0x30e0
-#define MAX_RX_JUMBO(x) ((x) << 2)
#define MAX_RX_JUMBO_MASK GENMASK(5, 2)
+#define MAX_RX_JUMBO(x) FIELD_PREP(MAX_RX_JUMBO_MASK, x)
#define MAX_RX_PKT_LEN_MASK GENMASK(1, 0)
#define MAX_RX_PKT_LEN_1522 0x0
#define MAX_RX_PKT_LEN_1536 0x1
@@ -506,16 +511,16 @@ enum mt7530_vlan_port_acc_frm {
/* Register for PHY Indirect Access Control */
#define MT7531_PHY_IAC 0x701C
#define MT7531_PHY_ACS_ST BIT(31)
-#define MT7531_MDIO_REG_ADDR_MASK (0x1f << 25)
-#define MT7531_MDIO_PHY_ADDR_MASK (0x1f << 20)
-#define MT7531_MDIO_CMD_MASK (0x3 << 18)
-#define MT7531_MDIO_ST_MASK (0x3 << 16)
-#define MT7531_MDIO_RW_DATA_MASK (0xffff)
-#define MT7531_MDIO_REG_ADDR(x) (((x) & 0x1f) << 25)
-#define MT7531_MDIO_DEV_ADDR(x) (((x) & 0x1f) << 25)
-#define MT7531_MDIO_PHY_ADDR(x) (((x) & 0x1f) << 20)
-#define MT7531_MDIO_CMD(x) (((x) & 0x3) << 18)
-#define MT7531_MDIO_ST(x) (((x) & 0x3) << 16)
+#define MT7531_MDIO_REG_ADDR_MASK GENMASK(29, 25)
+#define MT7531_MDIO_PHY_ADDR_MASK GENMASK(24, 20)
+#define MT7531_MDIO_CMD_MASK GENMASK(19, 18)
+#define MT7531_MDIO_ST_MASK GENMASK(17, 16)
+#define MT7531_MDIO_RW_DATA_MASK GENMASK(15, 0)
+#define MT7531_MDIO_REG_ADDR(x) FIELD_PREP(MT7531_MDIO_REG_ADDR_MASK, x)
+#define MT7531_MDIO_DEV_ADDR(x) FIELD_PREP(MT7531_MDIO_REG_ADDR_MASK, x)
+#define MT7531_MDIO_PHY_ADDR(x) FIELD_PREP(MT7531_MDIO_PHY_ADDR_MASK, x)
+#define MT7531_MDIO_CMD(x) FIELD_PREP(MT7531_MDIO_CMD_MASK, x)
+#define MT7531_MDIO_ST(x) FIELD_PREP(MT7531_MDIO_ST_MASK, x)
enum mt7531_phy_iac_cmd {
MT7531_MDIO_ADDR = 0,
@@ -543,14 +548,14 @@ enum mt7531_mdio_st {
/* Register for RGMII clock phase */
#define MT7531_CLKGEN_CTRL 0x7500
-#define CLK_SKEW_OUT(x) (((x) & 0x3) << 8)
#define CLK_SKEW_OUT_MASK GENMASK(9, 8)
-#define CLK_SKEW_IN(x) (((x) & 0x3) << 6)
+#define CLK_SKEW_OUT(x) FIELD_PREP(CLK_SKEW_OUT_MASK, x)
#define CLK_SKEW_IN_MASK GENMASK(7, 6)
+#define CLK_SKEW_IN(x) FIELD_PREP(CLK_SKEW_IN_MASK, x)
#define RXCLK_NO_DELAY BIT(5)
#define TXCLK_NO_REVERSE BIT(4)
-#define GP_MODE(x) (((x) & 0x3) << 1)
#define GP_MODE_MASK GENMASK(2, 1)
+#define GP_MODE(x) FIELD_PREP(GP_MODE_MASK, x)
#define GP_CLK_EN BIT(0)
enum mt7531_gp_mode {
@@ -600,8 +605,10 @@ enum mt7531_xtal_fsel {
#define PAD_MCM_SMI_EN BIT(0)
#define MT7530_IO_DRV_CR 0x7810
-#define P5_IO_CLK_DRV(x) ((x) & 0x3)
-#define P5_IO_DATA_DRV(x) (((x) & 0x3) << 4)
+#define P5_IO_CLK_DRV_MASK GENMASK(1, 0)
+#define P5_IO_CLK_DRV(x) FIELD_PREP(P5_IO_CLK_DRV_MASK, x)
+#define P5_IO_DATA_DRV_MASK GENMASK(5, 4)
+#define P5_IO_DATA_DRV(x) FIELD_PREP(P5_IO_DATA_DRV_MASK, x)
#define MT7531_CHIP_REV 0x781C
@@ -611,15 +618,15 @@ enum mt7531_xtal_fsel {
#define SW_PLLGP BIT(0)
#define MT7530_P6ECR 0x7830
-#define P6_INTF_MODE_MASK 0x3
-#define P6_INTF_MODE(x) ((x) & 0x3)
+#define P6_INTF_MODE_MASK GENMASK(1, 0)
+#define P6_INTF_MODE(x) FIELD_PREP(P6_INTF_MODE_MASK, x)
#define MT7531_PLLGP_CR0 0x78a8
#define RG_COREPLL_EN BIT(22)
-#define RG_COREPLL_POSDIV_S 23
-#define RG_COREPLL_POSDIV_M 0x3800000
-#define RG_COREPLL_SDM_PCW_S 1
-#define RG_COREPLL_SDM_PCW_M 0x3ffffe
+#define RG_COREPLL_POSDIV_MASK GENMASK(25, 23)
+#define RG_COREPLL_POSDIV(x) FIELD_PREP(RG_COREPLL_POSDIV_MASK, x)
+#define RG_COREPLL_SDM_PCW_MASK GENMASK(21, 1)
+#define RG_COREPLL_SDM_PCW(x) FIELD_PREP(RG_COREPLL_SDM_PCW_MASK, x)
#define RG_COREPLL_SDM_PCW_CHG BIT(0)
/* Registers for RGMII and SGMII PLL clock */
@@ -630,10 +637,10 @@ enum mt7531_xtal_fsel {
#define MT7530_TRGMII_RCK_CTRL 0x7a00
#define RX_RST BIT(31)
#define RXC_DQSISEL BIT(30)
-#define DQSI1_TAP_MASK (0x7f << 8)
-#define DQSI0_TAP_MASK 0x7f
-#define DQSI1_TAP(x) (((x) & 0x7f) << 8)
-#define DQSI0_TAP(x) ((x) & 0x7f)
+#define DQSI1_TAP_MASK GENMASK(14, 8)
+#define DQSI0_TAP_MASK GENMASK(6, 0)
+#define DQSI1_TAP(x) FIELD_PREP(DQSI1_TAP_MASK, x)
+#define DQSI0_TAP(x) FIELD_PREP(DQSI0_TAP_MASK, x)
#define MT7530_TRGMII_RCK_RTT 0x7a04
#define DQS1_GATE BIT(31)
@@ -642,8 +649,8 @@ enum mt7531_xtal_fsel {
#define MT7530_TRGMII_RD(x) (0x7a10 + (x) * 8)
#define BSLIP_EN BIT(31)
#define EDGE_CHK BIT(30)
-#define RD_TAP_MASK 0x7f
-#define RD_TAP(x) ((x) & 0x7f)
+#define RD_TAP_MASK GENMASK(6, 0)
+#define RD_TAP(x) FIELD_PREP(RD_TAP_MASK, x)
#define MT7530_TRGMII_TXCTRL 0x7a40
#define TRAIN_TXEN BIT(31)
@@ -651,18 +658,23 @@ enum mt7531_xtal_fsel {
#define TX_RST BIT(28)
#define MT7530_TRGMII_TD_ODT(i) (0x7a54 + 8 * (i))
-#define TD_DM_DRVP(x) ((x) & 0xf)
-#define TD_DM_DRVN(x) (((x) & 0xf) << 4)
+#define TD_DM_DRVP_MASK GENMASK(3, 0)
+#define TD_DM_DRVP(x) FIELD_PREP(TD_DM_DRVP_MASK, x)
+#define TD_DM_DRVN_MASK GENMASK(7, 4)
+#define TD_DM_DRVN(x) FIELD_PREP(TD_DM_DRVN_MASK, x)
#define MT7530_TRGMII_TCK_CTRL 0x7a78
-#define TCK_TAP(x) (((x) & 0xf) << 8)
+#define TCK_TAP_MASK GENMASK(11, 8)
+#define TCK_TAP(x) FIELD_PREP(TCK_TAP_MASK, x)
#define MT7530_P5RGMIIRXCR 0x7b00
#define CSR_RGMII_EDGE_ALIGN BIT(8)
-#define CSR_RGMII_RXC_0DEG_CFG(x) ((x) & 0xf)
+#define CSR_RGMII_RXC_0DEG_CFG_MASK GENMASK(3, 0)
+#define CSR_RGMII_RXC_0DEG_CFG(x) FIELD_PREP(CSR_RGMII_RXC_0DEG_CFG_MASK, x)
#define MT7530_P5RGMIITXCR 0x7b04
-#define CSR_RGMII_TXC_CFG(x) ((x) & 0x1f)
+#define CSR_RGMII_TXC_CFG_MASK GENMASK(4, 0)
+#define CSR_RGMII_TXC_CFG(x) FIELD_PREP(CSR_RGMII_TXC_CFG_MASK, x)
/* Registers for GPIO mode */
#define MT7531_GPIO_MODE0 0x7c0c
@@ -671,9 +683,9 @@ enum mt7531_xtal_fsel {
#define MT7531_GPIO_MODE1 0x7c10
#define MT7531_GPIO11_RG_RXD2_MASK GENMASK(15, 12)
-#define MT7531_EXT_P_MDC_11 (2 << 12)
+#define MT7531_EXT_P_MDC_11 FIELD_PREP(MT7531_GPIO11_RG_RXD2_MASK, 2)
#define MT7531_GPIO12_RG_RXD3_MASK GENMASK(19, 16)
-#define MT7531_EXT_P_MDIO_12 (2 << 16)
+#define MT7531_EXT_P_MDIO_12 FIELD_PREP(MT7531_GPIO12_RG_RXD3_MASK, 2)
#define MT753X_CPORT_SPTAG_CFG 0x7c10
#define CPORT_SW2FE_STAG_EN BIT(1)
@@ -705,7 +717,7 @@ enum mt7531_xtal_fsel {
#define MT7530_LED_GPIO_DATA 0x7d18
#define MT7530_CREV 0x7ffc
-#define CHIP_NAME_SHIFT 16
+#define CHIP_NAME_MASK GENMASK(31, 16)
#define MT7530_ID 0x7530
#define MT7531_CREV 0x781C
@@ -717,10 +729,13 @@ enum mt7531_xtal_fsel {
#define RG_SYSPLL_EN_NORMAL BIT(15)
#define RG_SYSPLL_VODEN BIT(14)
#define RG_SYSPLL_LF BIT(13)
-#define RG_SYSPLL_RST_DLY(x) (((x) & 0x3) << 12)
+#define RG_SYSPLL_RST_DLY_MASK GENMASK(13, 12)
+#define RG_SYSPLL_RST_DLY(x) FIELD_PREP(RG_SYSPLL_RST_DLY_MASK, x)
#define RG_SYSPLL_LVROD_EN BIT(10)
-#define RG_SYSPLL_PREDIV(x) (((x) & 0x3) << 8)
-#define RG_SYSPLL_POSDIV(x) (((x) & 0x3) << 5)
+#define RG_SYSPLL_PREDIV_MASK GENMASK(9, 8)
+#define RG_SYSPLL_PREDIV(x) FIELD_PREP(RG_SYSPLL_PREDIV_MASK, x)
+#define RG_SYSPLL_POSDIV_MASK GENMASK(6, 5)
+#define RG_SYSPLL_POSDIV(x) FIELD_PREP(RG_SYSPLL_POSDIV_MASK, x)
#define RG_SYSPLL_FBKSEL BIT(4)
#define RT_SYSPLL_EN_AFE_OLT BIT(0)
@@ -732,38 +747,48 @@ enum mt7531_xtal_fsel {
#define MT7531_PHY_PLL_OFF BIT(5)
#define MT7531_PHY_PLL_BYPASS_MODE BIT(4)
-#define MT753X_CTRL_PHY_ADDR(addr) ((addr + 1) & 0x1f)
+#define MT753X_CTRL_PHY_ADDR(addr) (((addr) + 1) & (PHY_MAX_ADDR - 1))
#define CORE_PLL_GROUP5 0x404
-#define RG_LCDDS_PCW_NCPO1(x) ((x) & 0xffff)
+#define RG_LCDDS_PCW_NCPO1_MASK GENMASK(15, 0)
+#define RG_LCDDS_PCW_NCPO1(x) FIELD_PREP(RG_LCDDS_PCW_NCPO1_MASK, x)
#define CORE_PLL_GROUP6 0x405
-#define RG_LCDDS_PCW_NCPO0(x) ((x) & 0xffff)
+#define RG_LCDDS_PCW_NCPO0_MASK GENMASK(15, 0)
+#define RG_LCDDS_PCW_NCPO0(x) FIELD_PREP(RG_LCDDS_PCW_NCPO0_MASK, x)
#define CORE_PLL_GROUP7 0x406
#define RG_LCDDS_PWDB BIT(15)
#define RG_LCDDS_ISO_EN BIT(13)
-#define RG_LCCDS_C(x) (((x) & 0x7) << 4)
+#define RG_LCCDS_C_MASK GENMASK(6, 4)
+#define RG_LCCDS_C(x) FIELD_PREP(RG_LCCDS_C_MASK, x)
#define RG_LCDDS_PCW_NCPO_CHG BIT(3)
#define CORE_PLL_GROUP10 0x409
-#define RG_LCDDS_SSC_DELTA(x) ((x) & 0xfff)
+#define RG_LCDDS_SSC_DELTA_MASK GENMASK(11, 0)
+#define RG_LCDDS_SSC_DELTA(x) FIELD_PREP(RG_LCDDS_SSC_DELTA_MASK, x)
#define CORE_PLL_GROUP11 0x40a
-#define RG_LCDDS_SSC_DELTA1(x) ((x) & 0xfff)
+#define RG_LCDDS_SSC_DELTA1_MASK GENMASK(11, 0)
+#define RG_LCDDS_SSC_DELTA1(x) FIELD_PREP(RG_LCDDS_SSC_DELTA1_MASK, x)
#define CORE_GSWPLL_GRP1 0x40d
-#define RG_GSWPLL_PREDIV(x) (((x) & 0x3) << 14)
-#define RG_GSWPLL_POSDIV_200M(x) (((x) & 0x3) << 12)
+#define RG_GSWPLL_PREDIV_MASK GENMASK(15, 14)
+#define RG_GSWPLL_PREDIV(x) FIELD_PREP(RG_GSWPLL_PREDIV_MASK, x)
+#define RG_GSWPLL_POSDIV_200M_MASK GENMASK(13, 12)
+#define RG_GSWPLL_POSDIV_200M(x) FIELD_PREP(RG_GSWPLL_POSDIV_200M_MASK, x)
#define RG_GSWPLL_EN_PRE BIT(11)
#define RG_GSWPLL_FBKSEL BIT(10)
#define RG_GSWPLL_BP BIT(9)
#define RG_GSWPLL_BR BIT(8)
-#define RG_GSWPLL_FBKDIV_200M(x) ((x) & 0xff)
+#define RG_GSWPLL_FBKDIV_200M_MASK GENMASK(7, 0)
+#define RG_GSWPLL_FBKDIV_200M(x) FIELD_PREP(RG_GSWPLL_FBKDIV_200M_MASK, x)
#define CORE_GSWPLL_GRP2 0x40e
-#define RG_GSWPLL_POSDIV_500M(x) (((x) & 0x3) << 8)
-#define RG_GSWPLL_FBKDIV_500M(x) ((x) & 0xff)
+#define RG_GSWPLL_POSDIV_500M_MASK GENMASK(9, 8)
+#define RG_GSWPLL_POSDIV_500M(x) FIELD_PREP(RG_GSWPLL_POSDIV_500M_MASK, x)
+#define RG_GSWPLL_FBKDIV_500M_MASK GENMASK(7, 0)
+#define RG_GSWPLL_FBKDIV_500M(x) FIELD_PREP(RG_GSWPLL_FBKDIV_500M_MASK, x)
#define CORE_TRGMII_GSW_CLK_CG 0x410
#define REG_GSWCK_EN BIT(0)
@@ -933,18 +958,6 @@ struct mt7530_hw_stats {
u8 sizeof_stat;
};
-struct mt7530_dummy_poll {
- struct mt7530_priv *priv;
- u32 reg;
-};
-
-static inline void INIT_MT7530_DUMMY_POLL(struct mt7530_dummy_poll *p,
- struct mt7530_priv *priv, u32 reg)
-{
- p->priv = priv;
- p->reg = reg;
-}
-
int mt7530_probe_common(struct mt7530_priv *priv);
void mt7530_remove_common(struct mt7530_priv *priv);
diff --git a/drivers/net/dsa/mv88e6060.c b/drivers/net/dsa/mv88e6060.c
index 9c8ac14cd4f5..3bb485a90429 100644
--- a/drivers/net/dsa/mv88e6060.c
+++ b/drivers/net/dsa/mv88e6060.c
@@ -362,8 +362,9 @@ static const struct of_device_id mv88e6060_of_match[] = {
{
.compatible = "marvell,mv88e6060",
},
- { /* sentinel */ },
+ { /* sentinel */ }
};
+MODULE_DEVICE_TABLE(of, mv88e6060_of_match);
static struct mdio_driver mv88e6060_driver = {
.probe = mv88e6060_probe,
diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index a4a8c7e11bf4..2a975ecd79c7 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -2457,7 +2457,7 @@ static int mv88e6xxx_get_rxnfc(struct dsa_switch *ds, int port,
case ETHTOOL_GRXCLSRULE:
err = -ENOENT;
policy = idr_find(&chip->policies, fs->location);
- if (policy) {
+ if (policy && policy->port == port) {
memcpy(fs, &policy->fs, sizeof(*fs));
err = 0;
}
@@ -2503,8 +2503,9 @@ static int mv88e6xxx_set_rxnfc(struct dsa_switch *ds, int port,
break;
case ETHTOOL_SRXCLSRLDEL:
err = -ENOENT;
- policy = idr_remove(&chip->policies, fs->location);
- if (policy) {
+ policy = idr_find(&chip->policies, fs->location);
+ if (policy && policy->port == port) {
+ idr_remove(&chip->policies, fs->location);
policy->action = MV88E6XXX_POLICY_ACTION_NORMAL;
err = mv88e6xxx_policy_apply(chip, port, policy);
devm_kfree(chip->dev, policy);
@@ -7581,7 +7582,7 @@ static const struct of_device_id mv88e6xxx_of_match[] = {
.compatible = "marvell,mv88e6250",
.data = &mv88e6xxx_table[MV88E6250],
},
- { /* sentinel */ },
+ { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, mv88e6xxx_of_match);
diff --git a/drivers/net/dsa/qca/qca8k-8xxx.c b/drivers/net/dsa/qca/qca8k-8xxx.c
index 4c928983b862..07227ae8bc04 100644
--- a/drivers/net/dsa/qca/qca8k-8xxx.c
+++ b/drivers/net/dsa/qca/qca8k-8xxx.c
@@ -129,13 +129,16 @@ err:
return ret;
}
-static void
+static int
qca8k_mii_write32(struct mii_bus *bus, int phy_id, u32 regnum, u32 val)
{
- if (qca8k_mii_write_lo(bus, phy_id, regnum, val) < 0)
- return;
+ int ret;
- qca8k_mii_write_hi(bus, phy_id, regnum + 1, val);
+ ret = qca8k_mii_write_lo(bus, phy_id, regnum, val);
+ if (ret < 0)
+ return ret;
+
+ return qca8k_mii_write_hi(bus, phy_id, regnum + 1, val);
}
static int
@@ -150,6 +153,8 @@ qca8k_set_page(struct qca8k_priv *priv, u16 page)
ret = bus->write(bus, 0x18, 0, page);
if (ret < 0) {
+ /* The switch may or may not have switched pages. */
+ *cached_page = 0xffff;
dev_err_ratelimited(&bus->dev,
"failed to set qca8k page\n");
return ret;
@@ -462,7 +467,7 @@ qca8k_write_mii(struct qca8k_priv *priv, uint32_t reg, uint32_t val)
if (ret < 0)
goto exit;
- qca8k_mii_write32(bus, 0x10 | r2, r1, val);
+ ret = qca8k_mii_write32(bus, 0x10 | r2, r1, val);
exit:
mutex_unlock(&bus->mdio_lock);
@@ -492,7 +497,7 @@ qca8k_regmap_update_bits_mii(struct qca8k_priv *priv, uint32_t reg,
val &= ~mask;
val |= write_val;
- qca8k_mii_write32(bus, 0x10 | r2, r1, val);
+ ret = qca8k_mii_write32(bus, 0x10 | r2, r1, val);
exit:
mutex_unlock(&bus->mdio_lock);
@@ -723,12 +728,15 @@ qca8k_phy_eth_command(struct qca8k_priv *priv, bool read, int phy,
}
ret = read_poll_timeout(qca8k_phy_eth_busy_wait, ret1,
- !(val & QCA8K_MDIO_MASTER_BUSY), 0,
+ ret1 < 0 || !(val & QCA8K_MDIO_MASTER_BUSY), 0,
QCA8K_BUSY_WAIT_TIMEOUT * USEC_PER_MSEC, false,
mgmt_eth_data, read_skb, &val);
- if (ret < 0 && ret1 < 0) {
+ if (ret1 < 0)
ret = ret1;
+
+ if (ret < 0) {
+ kfree_skb(read_skb);
goto exit;
}
@@ -794,19 +802,18 @@ static int
qca8k_mdio_busy_wait(struct mii_bus *bus, u32 reg, u32 mask)
{
u16 r1, r2, page;
- u32 val;
int ret, ret1;
+ u32 val;
qca8k_split_addr(reg, &r1, &r2, &page);
- ret = read_poll_timeout(qca8k_mii_read_hi, ret1, !(val & mask), 0,
+ ret = read_poll_timeout(qca8k_mii_read_hi, ret1,
+ ret1 < 0 || !(val & mask), 0,
QCA8K_BUSY_WAIT_TIMEOUT * USEC_PER_MSEC, false,
bus, 0x10 | r2, r1 + 1, &val);
- /* Check if qca8k_read has failed for a different reason
- * before returnting -ETIMEDOUT
- */
- if (ret < 0 && ret1 < 0)
+ /* Preserve an MDIO read error instead of treating it as ready. */
+ if (ret1 < 0)
return ret1;
return ret;
@@ -817,8 +824,8 @@ qca8k_mdio_write(struct qca8k_priv *priv, int phy, int regnum, u16 data)
{
struct mii_bus *bus = priv->bus;
u16 r1, r2, page;
+ int ret, ret1;
u32 val;
- int ret;
if (regnum >= QCA8K_MDIO_MASTER_MAX_REG)
return -EINVAL;
@@ -834,17 +841,22 @@ qca8k_mdio_write(struct qca8k_priv *priv, int phy, int regnum, u16 data)
ret = qca8k_set_page(priv, page);
if (ret)
- goto exit;
+ goto unlock;
- qca8k_mii_write32(bus, 0x10 | r2, r1, val);
+ ret = qca8k_mii_write32(bus, 0x10 | r2, r1, val);
+ if (ret < 0)
+ goto exit;
ret = qca8k_mdio_busy_wait(bus, QCA8K_MDIO_MASTER_CTRL,
QCA8K_MDIO_MASTER_BUSY);
exit:
/* even if the busy_wait timeouts try to clear the MASTER_EN */
- qca8k_mii_write_hi(bus, 0x10 | r2, r1 + 1, 0);
+ ret1 = qca8k_mii_write_hi(bus, 0x10 | r2, r1 + 1, 0);
+ if (!ret)
+ ret = ret1;
+unlock:
mutex_unlock(&bus->mdio_lock);
return ret;
@@ -855,8 +867,8 @@ qca8k_mdio_read(struct qca8k_priv *priv, int phy, int regnum)
{
struct mii_bus *bus = priv->bus;
u16 r1, r2, page;
+ int ret, ret1;
u32 val;
- int ret;
if (regnum >= QCA8K_MDIO_MASTER_MAX_REG)
return -EINVAL;
@@ -871,9 +883,11 @@ qca8k_mdio_read(struct qca8k_priv *priv, int phy, int regnum)
ret = qca8k_set_page(priv, page);
if (ret)
- goto exit;
+ goto unlock;
- qca8k_mii_write_hi(bus, 0x10 | r2, r1 + 1, val);
+ ret = qca8k_mii_write_hi(bus, 0x10 | r2, r1 + 1, val);
+ if (ret < 0)
+ goto exit;
ret = qca8k_mdio_busy_wait(bus, QCA8K_MDIO_MASTER_CTRL,
QCA8K_MDIO_MASTER_BUSY);
@@ -884,8 +898,11 @@ qca8k_mdio_read(struct qca8k_priv *priv, int phy, int regnum)
exit:
/* even if the busy_wait timeouts try to clear the MASTER_EN */
- qca8k_mii_write_hi(bus, 0x10 | r2, r1 + 1, 0);
+ ret1 = qca8k_mii_write_hi(bus, 0x10 | r2, r1 + 1, 0);
+ if (!ret)
+ ret = ret1;
+unlock:
mutex_unlock(&bus->mdio_lock);
if (ret >= 0)
@@ -919,12 +936,7 @@ qca8k_internal_mdio_read(struct mii_bus *slave_bus, int phy, int regnum)
if (ret >= 0)
return ret;
- ret = qca8k_mdio_read(priv, phy, regnum);
-
- if (ret < 0)
- return 0xffff;
-
- return ret;
+ return qca8k_mdio_read(priv, phy, regnum);
}
static int
@@ -2215,7 +2227,7 @@ static const struct of_device_id qca8k_of_match[] = {
{ .compatible = "qca,qca8328", .data = &qca8328 },
{ .compatible = "qca,qca8334", .data = &qca833x },
{ .compatible = "qca,qca8337", .data = &qca833x },
- { /* sentinel */ },
+ { /* sentinel */ }
};
static struct mdio_driver qca8kmdio_driver = {
diff --git a/drivers/net/dsa/qca/qca8k-common.c b/drivers/net/dsa/qca/qca8k-common.c
index 13005f10edb7..ab978cc8367f 100644
--- a/drivers/net/dsa/qca/qca8k-common.c
+++ b/drivers/net/dsa/qca/qca8k-common.c
@@ -471,10 +471,7 @@ void qca8k_port_set_status(struct qca8k_priv *priv, int port, int enable)
if (port > 0 && port < 6)
mask |= QCA8K_PORT_STATUS_LINK_AUTO;
- if (enable)
- regmap_set_bits(priv->regmap, QCA8K_REG_PORT_STATUS(port), mask);
- else
- regmap_clear_bits(priv->regmap, QCA8K_REG_PORT_STATUS(port), mask);
+ regmap_assign_bits(priv->regmap, QCA8K_REG_PORT_STATUS(port), mask, enable);
}
void qca8k_get_strings(struct dsa_switch *ds, int port, u32 stringset,
@@ -562,14 +559,8 @@ static int qca8k_port_configure_learning(struct dsa_switch *ds, int port,
{
struct qca8k_priv *priv = ds->priv;
- if (learning)
- return regmap_set_bits(priv->regmap,
- QCA8K_PORT_LOOKUP_CTRL(port),
- QCA8K_PORT_LOOKUP_LEARN);
- else
- return regmap_clear_bits(priv->regmap,
- QCA8K_PORT_LOOKUP_CTRL(port),
- QCA8K_PORT_LOOKUP_LEARN);
+ return regmap_assign_bits(priv->regmap, QCA8K_PORT_LOOKUP_CTRL(port),
+ QCA8K_PORT_LOOKUP_LEARN, learning);
}
void qca8k_port_stp_state_set(struct dsa_switch *ds, int port, u8 state)
diff --git a/drivers/net/dsa/realtek/Makefile b/drivers/net/dsa/realtek/Makefile
index 72bb42e80c95..0a94a619b2c1 100644
--- a/drivers/net/dsa/realtek/Makefile
+++ b/drivers/net/dsa/realtek/Makefile
@@ -1,23 +1,15 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_NET_DSA_REALTEK) += realtek_dsa.o
-realtek_dsa-objs := rtl83xx.o
-
-ifdef CONFIG_NET_DSA_REALTEK_MDIO
-realtek_dsa-objs += realtek-mdio.o
-endif
-
-ifdef CONFIG_NET_DSA_REALTEK_SMI
-realtek_dsa-objs += realtek-smi.o
-endif
+realtek_dsa-y := rtl83xx.o
+realtek_dsa-$(CONFIG_NET_DSA_REALTEK_MDIO) += realtek-mdio.o
+realtek_dsa-$(CONFIG_NET_DSA_REALTEK_SMI) += realtek-smi.o
obj-$(CONFIG_NET_DSA_REALTEK_RTL8366RB) += rtl8366.o
-rtl8366-objs := rtl8366-core.o rtl8366rb.o
-ifdef CONFIG_NET_DSA_REALTEK_RTL8366RB_LEDS
-rtl8366-objs += rtl8366rb-leds.o
-endif
+rtl8366-y := rtl8366-core.o rtl8366rb.o
+rtl8366-$(CONFIG_NET_DSA_REALTEK_RTL8366RB_LEDS) += rtl8366rb-leds.o
+
obj-$(CONFIG_NET_DSA_REALTEK_RTL8365MB) += rtl8365mb.o
-rtl8365mb-objs := rtl8365mb_main.o \
- rtl8365mb_table.o \
- rtl8365mb_vlan.o \
- rtl8365mb_l2.o \
-# end of rtl8365mb-objs
+rtl8365mb-y := rtl8365mb_main.o \
+ rtl8365mb_table.o \
+ rtl8365mb_vlan.o \
+ rtl8365mb_l2.o
diff --git a/drivers/net/dsa/realtek/realtek.h b/drivers/net/dsa/realtek/realtek.h
index 6e0148cee8d8..33f3b4c249fc 100644
--- a/drivers/net/dsa/realtek/realtek.h
+++ b/drivers/net/dsa/realtek/realtek.h
@@ -97,7 +97,6 @@ struct realtek_priv {
int vlan_enabled;
int vlan4k_enabled;
- char buf[4096];
void *chip_data; /* Per-chip extra variant data */
};
@@ -156,6 +155,10 @@ struct realtek_ops {
int (*phy_read)(struct realtek_priv *priv, int phy, int regnum);
int (*phy_write)(struct realtek_priv *priv, int phy, int regnum,
u16 val);
+ int (*phy_read_c45)(struct realtek_priv *priv, int phy, int devad,
+ int regnum);
+ int (*phy_write_c45)(struct realtek_priv *priv, int phy, int devad,
+ int regnum, u16 val);
};
struct realtek_variant {
@@ -166,6 +169,9 @@ struct realtek_variant {
u8 cmd_read;
u8 cmd_write;
size_t chip_data_sz;
+ /* Regulator supplies to enable at probe, or NULL */
+ const char *const *supplies;
+ int num_supplies;
};
/* RTL8366 library helpers */
diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c
index 728231d8f94c..da06f952a053 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_main.c
+++ b/drivers/net/dsa/realtek/rtl8365mb_main.c
@@ -101,6 +101,7 @@
#include <linux/regmap.h>
#include <linux/if_bridge.h>
#include <linux/if_vlan.h>
+#include <linux/mdio.h>
#include <linux/phylink.h>
#include "realtek.h"
@@ -131,32 +132,32 @@
#define RTL8365MB_MAGIC_VALUE 0x0249
/* Chip reset register */
-#define RTL8365MB_CHIP_RESET_REG 0x1322
-#define RTL8365MB_CHIP_RESET_DW8051_MASK 0x0010
-#define RTL8365MB_CHIP_RESET_SW_MASK 0x0002
-#define RTL8365MB_CHIP_RESET_HW_MASK 0x0001
+#define RTL8365MB_CHIP_RESET_REG 0x1322
+#define RTL8365MB_CHIP_RESET_DW8051_MASK BIT(4)
+#define RTL8365MB_CHIP_RESET_SW_MASK BIT(1)
+#define RTL8365MB_CHIP_RESET_HW_MASK BIT(0)
/* Interrupt polarity register */
#define RTL8365MB_INTR_POLARITY_REG 0x1100
-#define RTL8365MB_INTR_POLARITY_MASK 0x0001
+#define RTL8365MB_INTR_POLARITY_MASK BIT(0)
#define RTL8365MB_INTR_POLARITY_HIGH 0
#define RTL8365MB_INTR_POLARITY_LOW 1
/* Interrupt control/status register - enable/check specific interrupt types */
#define RTL8365MB_INTR_CTRL_REG 0x1101
-#define RTL8365MB_INTR_STATUS_REG 0x1102
-#define RTL8365MB_INTR_SLIENT_START_2_MASK 0x1000
-#define RTL8365MB_INTR_SLIENT_START_MASK 0x0800
-#define RTL8365MB_INTR_ACL_ACTION_MASK 0x0200
-#define RTL8365MB_INTR_CABLE_DIAG_FIN_MASK 0x0100
-#define RTL8365MB_INTR_INTERRUPT_8051_MASK 0x0080
-#define RTL8365MB_INTR_LOOP_DETECTION_MASK 0x0040
-#define RTL8365MB_INTR_GREEN_TIMER_MASK 0x0020
-#define RTL8365MB_INTR_SPECIAL_CONGEST_MASK 0x0010
-#define RTL8365MB_INTR_SPEED_CHANGE_MASK 0x0008
-#define RTL8365MB_INTR_LEARN_OVER_MASK 0x0004
-#define RTL8365MB_INTR_METER_EXCEEDED_MASK 0x0002
-#define RTL8365MB_INTR_LINK_CHANGE_MASK 0x0001
+#define RTL8365MB_INTR_STATUS_REG 0x1102
+#define RTL8365MB_INTR_SLIENT_START_2_MASK BIT(12)
+#define RTL8365MB_INTR_SLIENT_START_MASK BIT(11)
+#define RTL8365MB_INTR_ACL_ACTION_MASK BIT(9)
+#define RTL8365MB_INTR_CABLE_DIAG_FIN_MASK BIT(8)
+#define RTL8365MB_INTR_INTERRUPT_8051_MASK BIT(7)
+#define RTL8365MB_INTR_LOOP_DETECTION_MASK BIT(6)
+#define RTL8365MB_INTR_GREEN_TIMER_MASK BIT(5)
+#define RTL8365MB_INTR_SPECIAL_CONGEST_MASK BIT(4)
+#define RTL8365MB_INTR_SPEED_CHANGE_MASK BIT(3)
+#define RTL8365MB_INTR_LEARN_OVER_MASK BIT(2)
+#define RTL8365MB_INTR_METER_EXCEEDED_MASK BIT(1)
+#define RTL8365MB_INTR_LINK_CHANGE_MASK BIT(0)
#define RTL8365MB_INTR_ALL_MASK \
(RTL8365MB_INTR_SLIENT_START_2_MASK | \
RTL8365MB_INTR_SLIENT_START_MASK | \
@@ -171,20 +172,22 @@
RTL8365MB_INTR_METER_EXCEEDED_MASK | \
RTL8365MB_INTR_LINK_CHANGE_MASK)
+#define RTL8365MB_PORT_MASK GENMASK(10, 0)
+
/* Per-port interrupt type status registers */
#define RTL8365MB_PORT_LINKDOWN_IND_REG 0x1106
-#define RTL8365MB_PORT_LINKDOWN_IND_MASK 0x07FF
+#define RTL8365MB_PORT_LINKDOWN_IND_MASK RTL8365MB_PORT_MASK
#define RTL8365MB_PORT_LINKUP_IND_REG 0x1107
-#define RTL8365MB_PORT_LINKUP_IND_MASK 0x07FF
+#define RTL8365MB_PORT_LINKUP_IND_MASK RTL8365MB_PORT_MASK
/* PHY indirect access registers */
#define RTL8365MB_INDIRECT_ACCESS_CTRL_REG 0x1F00
-#define RTL8365MB_INDIRECT_ACCESS_CTRL_RW_MASK 0x0002
-#define RTL8365MB_INDIRECT_ACCESS_CTRL_RW_READ 0
-#define RTL8365MB_INDIRECT_ACCESS_CTRL_RW_WRITE 1
-#define RTL8365MB_INDIRECT_ACCESS_CTRL_CMD_MASK 0x0001
-#define RTL8365MB_INDIRECT_ACCESS_CTRL_CMD_VALUE 1
+#define RTL8365MB_INDIRECT_ACCESS_CTRL_RW_MASK BIT(1)
+#define RTL8365MB_INDIRECT_ACCESS_CTRL_RW_READ 0
+#define RTL8365MB_INDIRECT_ACCESS_CTRL_RW_WRITE 1
+#define RTL8365MB_INDIRECT_ACCESS_CTRL_CMD_MASK BIT(0)
+#define RTL8365MB_INDIRECT_ACCESS_CTRL_CMD_VALUE 1
#define RTL8365MB_INDIRECT_ACCESS_STATUS_REG 0x1F01
#define RTL8365MB_INDIRECT_ACCESS_ADDRESS_REG 0x1F02
#define RTL8365MB_INDIRECT_ACCESS_ADDRESS_OCPADR_5_1_MASK GENMASK(4, 0)
@@ -196,12 +199,24 @@
/* PHY OCP address prefix register */
#define RTL8365MB_GPHY_OCP_MSB_0_REG 0x1D15
-#define RTL8365MB_GPHY_OCP_MSB_0_CFG_CPU_OCPADR_MASK 0x0FC0
-#define RTL8365MB_PHY_OCP_ADDR_PREFIX_MASK 0xFC00
+#define RTL8365MB_GPHY_OCP_MSB_0_CFG_CPU_OCPADR_MASK GENMASK(11, 6)
+#define RTL8365MB_PHY_OCP_ADDR_PREFIX_MASK GENMASK(15, 10)
+
+/* The full 16-bit OCP address is split across two registers: bits [15:10] are
+ * the prefix (RTL8365MB_PHY_OCP_ADDR_PREFIX_MASK above), and bits [9:1] go into
+ * the ADDRESS register as two fields, [5:1] and [9:6]. Bit 0 is always 0 - PHY
+ * OCP registers are 2-byte aligned.
+ */
+#define RTL8365MB_PHY_OCP_ADDR_5_1_MASK GENMASK(5, 1)
+#define RTL8365MB_PHY_OCP_ADDR_9_6_MASK GENMASK(9, 6)
/* The PHY OCP addresses of PHY registers 0~31 start here */
#define RTL8365MB_PHY_OCP_ADDR_PHYREG_BASE 0xA400
+#define RTL8365MB_PHY_OCP_ADDR_EEE_ABLE 0xA5C4
+#define RTL8365MB_PHY_OCP_ADDR_EEE_ADV 0xA5D0
+#define RTL8365MB_PHY_OCP_ADDR_EEE_LPABLE 0xA5D2
+
/* External interface port mode values - used in DIGITAL_INTERFACE_SELECT */
#define RTL8365MB_EXT_PORT_MODE_DISABLE 0
#define RTL8365MB_EXT_PORT_MODE_RGMII 1
@@ -239,8 +254,8 @@
(_extint) == 1 ? RTL8365MB_EXT_RGMXF_REG1 : \
(_extint) == 2 ? RTL8365MB_EXT_RGMXF_REG2 : \
0x0)
-#define RTL8365MB_EXT_RGMXF_RXDELAY_MASK 0x0007
-#define RTL8365MB_EXT_RGMXF_TXDELAY_MASK 0x0008
+#define RTL8365MB_EXT_RGMXF_RXDELAY_MASK GENMASK(2, 0)
+#define RTL8365MB_EXT_RGMXF_TXDELAY_MASK BIT(3)
/* External interface line rate bypass register - one bit per external
* interface, indexed by the external port number with port 5 (the first
@@ -258,28 +273,28 @@
*/
#define RTL8365MB_INGRESSBW_PORT6_RATE_CTRL0_REG 0x00CF
#define RTL8365MB_INGRESSBW_PORT6_RATE_CTRL1_REG 0x00D0
-#define RTL8365MB_INGRESSBW_PORT6_RATE_CTRL1_MASK 0x0007
+#define RTL8365MB_INGRESSBW_PORT6_RATE_CTRL1_MASK GENMASK(2, 0)
#define RTL8365MB_PORT6_EGRESSBW_CTRL0_REG 0x0398
#define RTL8365MB_PORT6_EGRESSBW_CTRL1_REG 0x0399
-#define RTL8365MB_PORT6_EGRESSBW_CTRL1_MASK 0x0007
+#define RTL8365MB_PORT6_EGRESSBW_CTRL1_MASK GENMASK(2, 0)
/* SerDes indirect access registers */
#define RTL8365MB_SDS_INDACS_CMD_REG 0x6600
-#define RTL8365MB_SDS_INDACS_CMD_BUSY_MASK 0x0100
-#define RTL8365MB_SDS_INDACS_CMD_RUN_MASK 0x0080
-#define RTL8365MB_SDS_INDACS_CMD_WR_MASK 0x0040
+#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_ADR_REG 0x6601
#define RTL8365MB_SDS_INDACS_DATA_REG 0x6602
/* SerDes miscellaneous configuration register */
#define RTL8365MB_SDS_MISC_REG 0x1D11
-#define RTL8365MB_SDS_MISC_SGMII_RXFC_MASK 0x4000
-#define RTL8365MB_SDS_MISC_SGMII_TXFC_MASK 0x2000
-#define RTL8365MB_SDS_MISC_MAC8_SEL_HSGMII_MASK 0x0800
-#define RTL8365MB_SDS_MISC_SGMII_FDUP_MASK 0x0400
-#define RTL8365MB_SDS_MISC_SGMII_LINK_MASK 0x0200
-#define RTL8365MB_SDS_MISC_SGMII_SPD_MASK 0x0180
-#define RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK 0x0040
+#define RTL8365MB_SDS_MISC_SGMII_RXFC_MASK BIT(14)
+#define RTL8365MB_SDS_MISC_SGMII_TXFC_MASK BIT(13)
+#define RTL8365MB_SDS_MISC_MAC8_SEL_HSGMII_MASK BIT(11)
+#define RTL8365MB_SDS_MISC_SGMII_FDUP_MASK BIT(10)
+#define RTL8365MB_SDS_MISC_SGMII_LINK_MASK BIT(9)
+#define RTL8365MB_SDS_MISC_SGMII_SPD_MASK GENMASK(8, 7)
+#define RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK BIT(6)
/* SerDes internal registers, accessed via the SDS_INDACS registers. The BMCR
* data path reset holds BMCR_ANENABLE | BMCR_ISOLATE while toggling the
@@ -290,12 +305,12 @@
#define RTL8365MB_SDS_BMCR_DPRST_PHASE1 (BMCR_ANENABLE | BMCR_ISOLATE | 0x1)
#define RTL8365MB_SDS_BMCR_DPRST_PHASE2 (BMCR_ANENABLE | BMCR_ISOLATE | 0x3)
#define RTL8365MB_SDS_REG_NWAY 0x0002
-#define RTL8365MB_SDS_NWAY_EN_MASK 0x0200
-#define RTL8365MB_SDS_NWAY_RESTART_MASK 0x0100
+#define RTL8365MB_SDS_NWAY_EN_MASK BIT(9)
+#define RTL8365MB_SDS_NWAY_RESTART_MASK BIT(8)
#define RTL8365MB_SDS_REG_RESET 0x0003
#define RTL8365MB_SDS_RESET_DEASSERT 0x7106
#define RTL8365MB_SDS_REG_LINK_STATUS 0x003d
-#define RTL8365MB_SDS_LINK_STATUS_LINK_MASK 0x0010
+#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.
@@ -322,7 +337,7 @@
* the firmware would otherwise do.
*/
#define RTL8365MB_MISC_CFG0_REG 0x130C
-#define RTL8365MB_MISC_CFG0_DW8051_EN_MASK 0x0020
+#define RTL8365MB_MISC_CFG0_DW8051_EN_MASK BIT(5)
/* External interface port speed values - used in DIGITAL_INTERFACE_FORCE */
#define RTL8365MB_PORT_SPEED_10M 0
@@ -338,31 +353,31 @@
(_extint) == 1 ? RTL8365MB_DIGITAL_INTERFACE_FORCE_REG1 : \
(_extint) == 2 ? RTL8365MB_DIGITAL_INTERFACE_FORCE_REG2 : \
0x0)
-#define RTL8365MB_DIGITAL_INTERFACE_FORCE_EN_MASK 0x1000
-#define RTL8365MB_DIGITAL_INTERFACE_FORCE_NWAY_MASK 0x0080
-#define RTL8365MB_DIGITAL_INTERFACE_FORCE_TXPAUSE_MASK 0x0040
-#define RTL8365MB_DIGITAL_INTERFACE_FORCE_RXPAUSE_MASK 0x0020
-#define RTL8365MB_DIGITAL_INTERFACE_FORCE_LINK_MASK 0x0010
-#define RTL8365MB_DIGITAL_INTERFACE_FORCE_DUPLEX_MASK 0x0004
-#define RTL8365MB_DIGITAL_INTERFACE_FORCE_SPEED_MASK 0x0003
+#define RTL8365MB_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)
+#define RTL8365MB_DIGITAL_INTERFACE_FORCE_LINK_MASK BIT(4)
+#define RTL8365MB_DIGITAL_INTERFACE_FORCE_DUPLEX_MASK BIT(2)
+#define RTL8365MB_DIGITAL_INTERFACE_FORCE_SPEED_MASK GENMASK(1, 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 0x07FF
+#define RTL8365MB_CPU_PORT_MASK_MASK RTL8365MB_PORT_MASK
/* CPU control register */
#define RTL8365MB_CPU_CTRL_REG 0x121A
-#define RTL8365MB_CPU_CTRL_TRAP_PORT_EXT_MASK 0x0400
-#define RTL8365MB_CPU_CTRL_TAG_FORMAT_MASK 0x0200
-#define RTL8365MB_CPU_CTRL_RXBYTECOUNT_MASK 0x0080
-#define RTL8365MB_CPU_CTRL_TAG_POSITION_MASK 0x0040
-#define RTL8365MB_CPU_CTRL_TRAP_PORT_MASK 0x0038
-#define RTL8365MB_CPU_CTRL_INSERTMODE_MASK 0x0006
-#define RTL8365MB_CPU_CTRL_EN_MASK 0x0001
+#define RTL8365MB_CPU_CTRL_TRAP_PORT_EXT_MASK BIT(10)
+#define RTL8365MB_CPU_CTRL_TAG_FORMAT_MASK BIT(9)
+#define RTL8365MB_CPU_CTRL_RXBYTECOUNT_MASK BIT(7)
+#define RTL8365MB_CPU_CTRL_TAG_POSITION_MASK BIT(6)
+#define RTL8365MB_CPU_CTRL_TRAP_PORT_MASK GENMASK(5, 3)
+#define RTL8365MB_CPU_CTRL_INSERTMODE_MASK GENMASK(2, 1)
+#define RTL8365MB_CPU_CTRL_EN_MASK BIT(0)
/* Maximum packet length register */
#define RTL8365MB_CFG0_MAX_LEN_REG 0x088C
-#define RTL8365MB_CFG0_MAX_LEN_MASK 0x3FFF
+#define RTL8365MB_CFG0_MAX_LEN_MASK GENMASK(13, 0)
#define RTL8365MB_CFG0_MAX_LEN_MAX 0x3FFF
/* Port learning limit registers */
@@ -374,7 +389,7 @@
#define RTL8365MB_PORT_ISOLATION_REG_BASE 0x08A2
#define RTL8365MB_PORT_ISOLATION_REG(_physport) \
(RTL8365MB_PORT_ISOLATION_REG_BASE + (_physport))
-#define RTL8365MB_PORT_ISOLATION_MASK 0x07FF
+#define RTL8365MB_PORT_ISOLATION_MASK RTL8365MB_PORT_MASK
/* Extended filter ID registers - used to key forwarding database with IVL */
#define RTL8365MB_EFID_MASK GENMASK(2, 0)
@@ -394,16 +409,16 @@
(0x3 << RTL8365MB_MSTI_CTRL_PORT_STATE_OFFSET((_physport)))
/* Unknown unicast DA flooding port mask */
-#define RTL8365MB_UNKNOWN_UNICAST_FLOODING_PMASK_REG 0x0890
-#define RTL8365MB_UNKNOWN_UNICAST_FLOODING_PMASK_MASK 0x07FF
+#define RTL8365MB_UNKNOWN_UNICAST_FLOODING_PMASK_REG 0x0890
+#define RTL8365MB_UNKNOWN_UNICAST_FLOODING_PMASK_MASK RTL8365MB_PORT_MASK
/* Unknown multicast DA flooding port mask */
-#define RTL8365MB_UNKNOWN_MULTICAST_FLOODING_PMASK_REG 0x0891
-#define RTL8365MB_UNKNOWN_MULTICAST_FLOODING_PMASK_MASK 0x07FF
+#define RTL8365MB_UNKNOWN_MULTICAST_FLOODING_PMASK_REG 0x0891
+#define RTL8365MB_UNKNOWN_MULTICAST_FLOODING_PMASK_MASK RTL8365MB_PORT_MASK
/* Broadcast flooding port mask */
-#define RTL8365MB_UNKNOWN_BROADCAST_FLOODING_PMASK_REG 0x0892
-#define RTL8365MB_UNKNOWN_BROADCAST_FLOODING_PMASK_MASK 0x07FF
+#define RTL8365MB_UNKNOWN_BROADCAST_FLOODING_PMASK_REG 0x0892
+#define RTL8365MB_UNKNOWN_BROADCAST_FLOODING_PMASK_MASK RTL8365MB_PORT_MASK
#define RTL8365MB_SUPPORTED_BRIDGE_FLAGS \
(BR_LEARNING | BR_FLOOD | BR_MCAST_FLOOD | BR_BCAST_FLOOD)
@@ -412,19 +427,19 @@
#define RTL8365MB_PORT_MISC_CFG_REG_BASE 0x000E
#define RTL8365MB_PORT_MISC_CFG_REG(_p) \
(RTL8365MB_PORT_MISC_CFG_REG_BASE + ((_p) << 5))
-#define RTL8365MB_PORT_MISC_CFG_SMALL_TAG_IPG_MASK 0x8000
-#define RTL8365MB_PORT_MISC_CFG_TX_ITFSP_MODE_MASK 0x4000
-#define RTL8365MB_PORT_MISC_CFG_FLOWCTRL_INDEP_MASK 0x2000
-#define RTL8365MB_PORT_MISC_CFG_DOT1Q_REMARK_ENABLE_MASK 0x1000
-#define RTL8365MB_PORT_MISC_CFG_INGRESSBW_FLOWCTRL_MASK 0x0800
-#define RTL8365MB_PORT_MISC_CFG_INGRESSBW_IFG_MASK 0x0400
-#define RTL8365MB_PORT_MISC_CFG_RX_SPC_MASK 0x0200
-#define RTL8365MB_PORT_MISC_CFG_CRC_SKIP_MASK 0x0100
-#define RTL8365MB_PORT_MISC_CFG_PKTGEN_TX_FIRST_MASK 0x0080
-#define RTL8365MB_PORT_MISC_CFG_MAC_LOOPBACK_MASK 0x0040
+#define RTL8365MB_PORT_MISC_CFG_SMALL_TAG_IPG_MASK BIT(15)
+#define RTL8365MB_PORT_MISC_CFG_TX_ITFSP_MODE_MASK BIT(14)
+#define RTL8365MB_PORT_MISC_CFG_FLOWCTRL_INDEP_MASK BIT(13)
+#define RTL8365MB_PORT_MISC_CFG_DOT1Q_REMARK_ENABLE_MASK BIT(12)
+#define RTL8365MB_PORT_MISC_CFG_INGRESSBW_FLOWCTRL_MASK BIT(11)
+#define RTL8365MB_PORT_MISC_CFG_INGRESSBW_IFG_MASK BIT(10)
+#define RTL8365MB_PORT_MISC_CFG_RX_SPC_MASK BIT(9)
+#define RTL8365MB_PORT_MISC_CFG_CRC_SKIP_MASK BIT(8)
+#define RTL8365MB_PORT_MISC_CFG_PKTGEN_TX_FIRST_MASK BIT(7)
+#define RTL8365MB_PORT_MISC_CFG_MAC_LOOPBACK_MASK BIT(6)
/* See &rtl8365mb_vlan_egress_mode */
-#define RTL8365MB_PORT_MISC_CFG_VLAN_EGRESS_MODE_MASK 0x0030
-#define RTL8365MB_PORT_MISC_CFG_CONGESTION_SUSTAIN_TIME_MASK 0x000F
+#define RTL8365MB_PORT_MISC_CFG_VLAN_EGRESS_MODE_MASK GENMASK(5, 4)
+#define RTL8365MB_PORT_MISC_CFG_CONGESTION_SUSTAIN_TIME_MASK GENMASK(3, 0)
/**
* enum rtl8365mb_vlan_egress_mode - port VLAN egress mode
@@ -446,7 +461,7 @@ enum rtl8365mb_vlan_egress_mode {
/* VLAN control register */
#define RTL8365MB_VLAN_CTRL_REG 0x07A8
-#define RTL8365MB_VLAN_CTRL_EN_MASK 0x0001
+#define RTL8365MB_VLAN_CTRL_EN_MASK BIT(0)
/* VLAN ingress filter register */
#define RTL8365MB_VLAN_INGRESS_REG 0x07A9
@@ -470,8 +485,8 @@ enum rtl8365mb_vlan_egress_mode {
(((RTL8365MB_MIB_ADDRESS_PORT_OFFSET) * (_p) + (_x)) >> 2)
#define RTL8365MB_MIB_CTRL0_REG 0x1005
-#define RTL8365MB_MIB_CTRL0_RESET_MASK 0x0002
-#define RTL8365MB_MIB_CTRL0_BUSY_MASK 0x0001
+#define RTL8365MB_MIB_CTRL0_RESET_MASK BIT(1)
+#define RTL8365MB_MIB_CTRL0_BUSY_MASK BIT(0)
/* The DSA callback .get_stats64 runs in atomic context, so we are not allowed
* to block. On the other hand, accessing MIB counters absolutely requires us to
@@ -871,6 +886,8 @@ static int rtl8365mb_phy_poll_busy(struct realtek_priv *priv)
static int rtl8365mb_phy_ocp_prepare(struct realtek_priv *priv, int phy,
u32 ocp_addr)
{
+ u16 ocp_addr_lo = FIELD_GET(RTL8365MB_PHY_OCP_ADDR_5_1_MASK, ocp_addr);
+ u16 ocp_addr_hi = FIELD_GET(RTL8365MB_PHY_OCP_ADDR_9_6_MASK, ocp_addr);
u32 val;
int ret;
@@ -887,9 +904,9 @@ static int rtl8365mb_phy_ocp_prepare(struct realtek_priv *priv, int phy,
val = RTL8365MB_PHY_BASE;
val |= FIELD_PREP(RTL8365MB_INDIRECT_ACCESS_ADDRESS_PHYNUM_MASK, phy);
val |= FIELD_PREP(RTL8365MB_INDIRECT_ACCESS_ADDRESS_OCPADR_5_1_MASK,
- ocp_addr >> 1);
+ ocp_addr_lo);
val |= FIELD_PREP(RTL8365MB_INDIRECT_ACCESS_ADDRESS_OCPADR_9_6_MASK,
- ocp_addr >> 6);
+ ocp_addr_hi);
ret = regmap_write(priv->map_nolock,
RTL8365MB_INDIRECT_ACCESS_ADDRESS_REG, val);
if (ret)
@@ -1040,6 +1057,66 @@ static int rtl8365mb_phy_write(struct realtek_priv *priv, int phy, int regnum,
return 0;
}
+static int rtl8365mb_phy_read_c45(struct realtek_priv *priv, int phy, int devad,
+ int regnum)
+{
+ u32 ocp_addr;
+ u16 val;
+ int ret;
+
+ if (phy > RTL8365MB_PHYADDRMAX)
+ return -EINVAL;
+
+ if (devad == MDIO_MMD_PCS && regnum == MDIO_PCS_EEE_ABLE)
+ ocp_addr = RTL8365MB_PHY_OCP_ADDR_EEE_ABLE;
+ else if (devad == MDIO_MMD_AN && regnum == MDIO_AN_EEE_ADV)
+ ocp_addr = RTL8365MB_PHY_OCP_ADDR_EEE_ADV;
+ else if (devad == MDIO_MMD_AN && regnum == MDIO_AN_EEE_LPABLE)
+ ocp_addr = RTL8365MB_PHY_OCP_ADDR_EEE_LPABLE;
+ else
+ /* Only the EEE registers are mapped; others read as 0, as the
+ * hardware does, so the generic MMD code is not tripped up by
+ * an error.
+ */
+ return 0;
+
+ ret = rtl8365mb_phy_ocp_read(priv, phy, ocp_addr, &val);
+ if (ret) {
+ dev_err(priv->dev,
+ "failed to read PHY%d OCP %04x, ret %d\n", phy, ocp_addr,
+ ret);
+ return ret;
+ }
+
+ return val;
+}
+
+static int rtl8365mb_phy_write_c45(struct realtek_priv *priv, int phy,
+ int devad, int regnum, u16 val)
+{
+ int ret;
+
+ if (phy > RTL8365MB_PHYADDRMAX)
+ return -EINVAL;
+
+ /* Only the EEE advertisement register is writable; writes to other
+ * registers are ignored, as the hardware does.
+ */
+ if (devad != MDIO_MMD_AN || regnum != MDIO_AN_EEE_ADV)
+ return 0;
+
+ ret = rtl8365mb_phy_ocp_write(priv, phy, RTL8365MB_PHY_OCP_ADDR_EEE_ADV,
+ val);
+ if (ret) {
+ dev_err(priv->dev,
+ "failed to write PHY%d OCP %04x, ret %d\n", phy,
+ RTL8365MB_PHY_OCP_ADDR_EEE_ADV, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
static const struct rtl8365mb_extint *
rtl8365mb_get_port_extint(struct realtek_priv *priv, int port)
{
@@ -1639,6 +1716,14 @@ static void rtl8365mb_phylink_get_caps(struct dsa_switch *ds, int port,
*/
__set_bit(PHY_INTERFACE_MODE_GMII,
config->supported_interfaces);
+
+ /* Integrated PHYs support EEE at 100M/1G; the hardware manages
+ * LPI on its own, so just advertise LPI awareness to phylink.
+ */
+ memcpy(config->lpi_interfaces, config->supported_interfaces,
+ sizeof(config->lpi_interfaces));
+ config->lpi_capabilities = MAC_100FD | MAC_1000FD;
+ config->eee_enabled_default = true;
return;
}
@@ -3275,18 +3360,66 @@ static int rtl8365mb_detect(struct realtek_priv *priv)
return 0;
}
+static int rtl8365mb_phylink_mac_enable_tx_lpi(struct phylink_config *config,
+ u32 timer, bool tx_clock_stop)
+{
+ /* The hardware manages LPI itself; there is no MAC-level LPI control.
+ * This callback only signals LPI awareness to phylink.
+ */
+ return 0;
+}
+
+static void rtl8365mb_phylink_mac_disable_tx_lpi(struct phylink_config *config)
+{
+}
+
static const struct phylink_mac_ops rtl8365mb_phylink_mac_ops = {
.mac_select_pcs = rtl8365mb_phylink_mac_select_pcs,
.mac_config = rtl8365mb_phylink_mac_config,
.mac_link_down = rtl8365mb_phylink_mac_link_down,
.mac_link_up = rtl8365mb_phylink_mac_link_up,
+ .mac_enable_tx_lpi = rtl8365mb_phylink_mac_enable_tx_lpi,
+ .mac_disable_tx_lpi = rtl8365mb_phylink_mac_disable_tx_lpi,
};
+static bool rtl8365mb_support_eee(struct dsa_switch *ds, int port)
+{
+ /* Only integrated-PHY ports support EEE, not the external RGMII ports. */
+ return !rtl8365mb_get_port_extint(ds->priv, port);
+}
+
+static int rtl8365mb_set_mac_eee(struct dsa_switch *ds, int port,
+ struct ethtool_keee *e)
+{
+ struct realtek_priv *priv = ds->priv;
+
+ /* The only LPI timing control (tx_lpi_timer) is a single global
+ * per-speed register shared by all ports, so it cannot be set from this
+ * per-port callback; leave it at its reset default. Per-port EEE is
+ * driven through the PHY advertisement. Reject the per-port TX LPI knobs
+ * rather than silently ignoring them.
+ */
+ if (!e->tx_lpi_enabled) {
+ dev_err(priv->dev, "disabling EEE TX LPI is not supported\n");
+ return -EOPNOTSUPP;
+ }
+
+ if (e->tx_lpi_timer) {
+ dev_err(priv->dev,
+ "setting the EEE TX LPI timer is not supported\n");
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
static const struct dsa_switch_ops rtl8365mb_switch_ops = {
.get_tag_protocol = rtl8365mb_get_tag_protocol,
.change_tag_protocol = rtl8365mb_change_tag_protocol,
.setup = rtl8365mb_setup,
.teardown = rtl8365mb_teardown,
+ .support_eee = rtl8365mb_support_eee,
+ .set_mac_eee = rtl8365mb_set_mac_eee,
.phylink_get_caps = rtl8365mb_phylink_get_caps,
.port_bridge_join = rtl83xx_port_bridge_join,
.port_bridge_leave = rtl83xx_port_bridge_leave,
@@ -3332,6 +3465,12 @@ static const struct realtek_ops rtl8365mb_ops = {
.l2_flush = rtl8365mb_l2_flush,
.phy_read = rtl8365mb_phy_read,
.phy_write = rtl8365mb_phy_write,
+ .phy_read_c45 = rtl8365mb_phy_read_c45,
+ .phy_write_c45 = rtl8365mb_phy_write_c45,
+};
+
+static const char *const rtl8365mb_supplies[] = {
+ "avddh", "avddl", "dvddio", "dvddio1", "dvddl", "pllvddl",
};
const struct realtek_variant rtl8365mb_variant = {
@@ -3342,11 +3481,13 @@ const struct realtek_variant rtl8365mb_variant = {
.cmd_read = 0xb9,
.cmd_write = 0xb8,
.chip_data_sz = sizeof(struct rtl8365mb),
+ .supplies = rtl8365mb_supplies,
+ .num_supplies = ARRAY_SIZE(rtl8365mb_supplies),
};
static const struct of_device_id rtl8365mb_of_match[] = {
{ .compatible = "realtek,rtl8365mb", .data = &rtl8365mb_variant, },
- { /* sentinel */ },
+ { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, rtl8365mb_of_match);
diff --git a/drivers/net/dsa/realtek/rtl8365mb_vlan.c b/drivers/net/dsa/realtek/rtl8365mb_vlan.c
index 64dd8d90a5ee..8d49ffadce92 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_vlan.c
+++ b/drivers/net/dsa/realtek/rtl8365mb_vlan.c
@@ -106,8 +106,8 @@
#define RTL8365MB_VLAN_PVID_CTRL_BASE 0x0700
#define RTL8365MB_VLAN_PVID_CTRL_REG(_p) \
(RTL8365MB_VLAN_PVID_CTRL_BASE + ((_p) >> 1))
-#define RTL8365MB_VLAN_PVID_CTRL_PORT0_MCIDX_MASK 0x001F
-#define RTL8365MB_VLAN_PVID_CTRL_PORT1_MCIDX_MASK 0x1F00
+#define RTL8365MB_VLAN_PVID_CTRL_PORT0_MCIDX_MASK GENMASK(4, 0)
+#define RTL8365MB_VLAN_PVID_CTRL_PORT1_MCIDX_MASK GENMASK(12, 8)
#define RTL8365MB_VLAN_PVID_CTRL_PORT_MCIDX_OFFSET(_p) \
(((_p) & 1) << 3)
#define RTL8365MB_VLAN_PVID_CTRL_PORT_MCIDX_MASK(_p) \
diff --git a/drivers/net/dsa/realtek/rtl8366rb.c b/drivers/net/dsa/realtek/rtl8366rb.c
index f11831b66de8..093a06c599d4 100644
--- a/drivers/net/dsa/realtek/rtl8366rb.c
+++ b/drivers/net/dsa/realtek/rtl8366rb.c
@@ -1839,7 +1839,7 @@ const struct realtek_variant rtl8366rb_variant = {
static const struct of_device_id rtl8366rb_of_match[] = {
{ .compatible = "realtek,rtl8366rb", .data = &rtl8366rb_variant, },
- { /* sentinel */ },
+ { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, rtl8366rb_of_match);
diff --git a/drivers/net/dsa/realtek/rtl83xx.c b/drivers/net/dsa/realtek/rtl83xx.c
index a96beea72ef5..ad42c05d0a88 100644
--- a/drivers/net/dsa/realtek/rtl83xx.c
+++ b/drivers/net/dsa/realtek/rtl83xx.c
@@ -2,6 +2,7 @@
#include <linux/module.h>
#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
#include <linux/of_mdio.h>
#include <linux/if_bridge.h>
#include <linux/etherdevice.h>
@@ -61,6 +62,22 @@ static int rtl83xx_user_mdio_write(struct mii_bus *bus, int addr, int regnum,
return priv->ops->phy_write(priv, addr, regnum, val);
}
+static int rtl83xx_user_mdio_read_c45(struct mii_bus *bus, int addr, int devad,
+ int regnum)
+{
+ struct realtek_priv *priv = bus->priv;
+
+ return priv->ops->phy_read_c45(priv, addr, devad, regnum);
+}
+
+static int rtl83xx_user_mdio_write_c45(struct mii_bus *bus, int addr, int devad,
+ int regnum, u16 val)
+{
+ struct realtek_priv *priv = bus->priv;
+
+ return priv->ops->phy_write_c45(priv, addr, devad, regnum, val);
+}
+
/**
* rtl83xx_setup_user_mdio() - register the user mii bus driver
* @ds: DSA switch associated with this user_mii_bus
@@ -94,6 +111,10 @@ int rtl83xx_setup_user_mdio(struct dsa_switch *ds)
bus->name = "Realtek user MII";
bus->read = rtl83xx_user_mdio_read;
bus->write = rtl83xx_user_mdio_write;
+ if (priv->ops->phy_read_c45)
+ bus->read_c45 = rtl83xx_user_mdio_read_c45;
+ if (priv->ops->phy_write_c45)
+ bus->write_c45 = rtl83xx_user_mdio_write_c45;
snprintf(bus->id, MII_BUS_ID_SIZE, "%s:user_mii", dev_name(priv->dev));
bus->parent = priv->dev;
@@ -195,17 +216,24 @@ rtl83xx_probe(struct device *dev,
priv->leds_disabled = of_property_read_bool(dev->of_node,
"realtek,disable-leds");
- /* TODO: if power is software controlled, set up any regulators here */
+ /* Enable the supplies before the reset line is requested and driven,
+ * so the chip is powered before its pins are driven.
+ */
+ if (var->num_supplies) {
+ ret = devm_regulator_bulk_get_enable(dev, var->num_supplies,
+ var->supplies);
+ if (ret)
+ return dev_err_ptr_probe(dev, ret, "failed to enable supplies\n");
+ }
+
priv->reset_ctl = devm_reset_control_get_optional(dev, NULL);
if (IS_ERR(priv->reset_ctl))
return dev_err_cast_probe(dev, priv->reset_ctl,
"failed to get reset control\n");
priv->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
- if (IS_ERR(priv->reset)) {
- dev_err(dev, "failed to get RESET GPIO\n");
- return ERR_CAST(priv->reset);
- }
+ if (IS_ERR(priv->reset))
+ return dev_err_cast_probe(dev, priv->reset, "failed to get RESET GPIO\n");
dev_set_drvdata(dev, priv);
@@ -216,6 +244,11 @@ rtl83xx_probe(struct device *dev,
rtl83xx_reset_deassert(priv);
msleep(REALTEK_HW_START_DELAY);
dev_dbg(dev, "deasserted RESET\n");
+ } else if (var->num_supplies) {
+ /* Powered but no reset line: still wait for the chip to boot
+ * before the first register access.
+ */
+ msleep(REALTEK_HW_START_DELAY);
}
return priv;
diff --git a/drivers/net/dsa/rzn1_a5psw.c b/drivers/net/dsa/rzn1_a5psw.c
index 4d857e3be10b..85ff1fd2bbfb 100644
--- a/drivers/net/dsa/rzn1_a5psw.c
+++ b/drivers/net/dsa/rzn1_a5psw.c
@@ -1299,7 +1299,7 @@ static void a5psw_shutdown(struct platform_device *pdev)
static const struct of_device_id a5psw_of_mtable[] = {
{ .compatible = "renesas,rzn1-a5psw", },
- { /* sentinel */ },
+ { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, a5psw_of_mtable);
diff --git a/drivers/net/dsa/sja1105/sja1105_main.c b/drivers/net/dsa/sja1105/sja1105_main.c
index 1653e83d9123..8984ed55ad59 100644
--- a/drivers/net/dsa/sja1105/sja1105_main.c
+++ b/drivers/net/dsa/sja1105/sja1105_main.c
@@ -3374,7 +3374,7 @@ static const struct of_device_id sja1105_dt_ids[] = {
{ .compatible = "nxp,sja1110b", .data = &sja1110b_info },
{ .compatible = "nxp,sja1110c", .data = &sja1110c_info },
{ .compatible = "nxp,sja1110d", .data = &sja1110d_info },
- { /* sentinel */ },
+ { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, sja1105_dt_ids);
diff --git a/drivers/net/dsa/vitesse-vsc73xx-spi.c b/drivers/net/dsa/vitesse-vsc73xx-spi.c
index 85b9a0f51dd8..7b784320c41e 100644
--- a/drivers/net/dsa/vitesse-vsc73xx-spi.c
+++ b/drivers/net/dsa/vitesse-vsc73xx-spi.c
@@ -153,10 +153,15 @@ static int vsc73xx_spi_probe(struct spi_device *spi)
ret = spi_setup(spi);
if (ret < 0) {
dev_err(dev, "spi setup failed.\n");
+ spi_dev_put(vsc_spi->spi);
return ret;
}
- return vsc73xx_probe(&vsc_spi->vsc);
+ ret = vsc73xx_probe(&vsc_spi->vsc);
+ if (ret)
+ spi_dev_put(vsc_spi->spi);
+
+ return ret;
}
static void vsc73xx_spi_remove(struct spi_device *spi)
@@ -167,6 +172,8 @@ static void vsc73xx_spi_remove(struct spi_device *spi)
return;
vsc73xx_remove(&vsc_spi->vsc);
+
+ spi_dev_put(vsc_spi->spi);
}
static void vsc73xx_spi_shutdown(struct spi_device *spi)
diff --git a/drivers/net/dsa/yt921x.c b/drivers/net/dsa/yt921x.c
deleted file mode 100644
index 159b16606f6c..000000000000
--- a/drivers/net/dsa/yt921x.c
+++ /dev/null
@@ -1,4982 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-/*
- * Driver for Motorcomm YT921x Switch
- *
- * Should work on YT9213/YT9214/YT9215/YT9218, but only tested on YT9215+SGMII,
- * be sure to do your own checks before porting to another chip.
- *
- * Copyright (c) 2025 David Yang
- */
-
-#include <linux/dcbnl.h>
-#include <linux/etherdevice.h>
-#include <linux/if_bridge.h>
-#include <linux/if_hsr.h>
-#include <linux/if_vlan.h>
-#include <linux/iopoll.h>
-#include <linux/mdio.h>
-#include <linux/module.h>
-#include <linux/of.h>
-#include <linux/of_mdio.h>
-#include <linux/of_net.h>
-#include <linux/sort.h>
-
-#include <net/dsa.h>
-#include <net/dscp.h>
-#include <net/ieee8021q.h>
-#include <net/pkt_cls.h>
-
-#include "yt921x.h"
-
-struct yt921x_mib_desc {
- unsigned int size;
- unsigned int offset;
- const char *name;
-};
-
-#define MIB_DESC(_size, _offset, _name) \
- {_size, _offset, _name}
-
-/* Must agree with yt921x_mib
- *
- * Unstructured fields (name != NULL) will appear in get_ethtool_stats(),
- * structured go to their *_stats() methods, but we need their sizes and offsets
- * to perform 32bit MIB overflow wraparound.
- */
-static const struct yt921x_mib_desc yt921x_mib_descs[] = {
- MIB_DESC(1, YT921X_MIB_DATA_RX_BROADCAST, NULL),
- MIB_DESC(1, YT921X_MIB_DATA_RX_PAUSE, NULL),
- MIB_DESC(1, YT921X_MIB_DATA_RX_MULTICAST, NULL),
- MIB_DESC(1, YT921X_MIB_DATA_RX_CRC_ERR, NULL),
-
- MIB_DESC(1, YT921X_MIB_DATA_RX_ALIGN_ERR, NULL),
- MIB_DESC(1, YT921X_MIB_DATA_RX_UNDERSIZE_ERR, NULL),
- MIB_DESC(1, YT921X_MIB_DATA_RX_FRAG_ERR, NULL),
- MIB_DESC(1, YT921X_MIB_DATA_RX_PKT_SZ_64, NULL),
-
- MIB_DESC(1, YT921X_MIB_DATA_RX_PKT_SZ_65_TO_127, NULL),
- MIB_DESC(1, YT921X_MIB_DATA_RX_PKT_SZ_128_TO_255, NULL),
- MIB_DESC(1, YT921X_MIB_DATA_RX_PKT_SZ_256_TO_511, NULL),
- MIB_DESC(1, YT921X_MIB_DATA_RX_PKT_SZ_512_TO_1023, NULL),
-
- MIB_DESC(1, YT921X_MIB_DATA_RX_PKT_SZ_1024_TO_1518, NULL),
- MIB_DESC(1, YT921X_MIB_DATA_RX_PKT_SZ_1519_TO_MAX, NULL),
- MIB_DESC(2, YT921X_MIB_DATA_RX_GOOD_BYTES, NULL),
-
- MIB_DESC(2, YT921X_MIB_DATA_RX_BAD_BYTES, "RxBadBytes"),
- MIB_DESC(1, YT921X_MIB_DATA_RX_OVERSIZE_ERR, NULL),
-
- MIB_DESC(1, YT921X_MIB_DATA_RX_DROPPED, NULL),
- MIB_DESC(1, YT921X_MIB_DATA_TX_BROADCAST, NULL),
- MIB_DESC(1, YT921X_MIB_DATA_TX_PAUSE, NULL),
- MIB_DESC(1, YT921X_MIB_DATA_TX_MULTICAST, NULL),
-
- MIB_DESC(1, YT921X_MIB_DATA_TX_UNDERSIZE_ERR, NULL),
- MIB_DESC(1, YT921X_MIB_DATA_TX_PKT_SZ_64, NULL),
- MIB_DESC(1, YT921X_MIB_DATA_TX_PKT_SZ_65_TO_127, NULL),
- MIB_DESC(1, YT921X_MIB_DATA_TX_PKT_SZ_128_TO_255, NULL),
-
- MIB_DESC(1, YT921X_MIB_DATA_TX_PKT_SZ_256_TO_511, NULL),
- MIB_DESC(1, YT921X_MIB_DATA_TX_PKT_SZ_512_TO_1023, NULL),
- MIB_DESC(1, YT921X_MIB_DATA_TX_PKT_SZ_1024_TO_1518, NULL),
- MIB_DESC(1, YT921X_MIB_DATA_TX_PKT_SZ_1519_TO_MAX, NULL),
-
- MIB_DESC(2, YT921X_MIB_DATA_TX_GOOD_BYTES, NULL),
- MIB_DESC(1, YT921X_MIB_DATA_TX_COLLISION, NULL),
-
- MIB_DESC(1, YT921X_MIB_DATA_TX_EXCESSIVE_COLLISION, NULL),
- MIB_DESC(1, YT921X_MIB_DATA_TX_MULTIPLE_COLLISION, NULL),
- MIB_DESC(1, YT921X_MIB_DATA_TX_SINGLE_COLLISION, NULL),
- MIB_DESC(1, YT921X_MIB_DATA_TX_PKT, NULL),
-
- MIB_DESC(1, YT921X_MIB_DATA_TX_DEFERRED, NULL),
- MIB_DESC(1, YT921X_MIB_DATA_TX_LATE_COLLISION, NULL),
- MIB_DESC(1, YT921X_MIB_DATA_RX_OAM, "RxOAM"),
- MIB_DESC(1, YT921X_MIB_DATA_TX_OAM, "TxOAM"),
-};
-
-struct yt921x_info {
- const char *name;
- u16 major;
- /* Unknown, seems to be plain enumeration */
- u8 mode;
- u8 extmode;
- /* Ports with integral GbE PHYs, not including MCU Port 10 */
- u16 internal_mask;
- /* TODO: see comments in yt921x_dsa_phylink_get_caps() */
- u16 external_mask;
-};
-
-#define YT921X_PORT_MASK_INTn(port) BIT(port)
-#define YT921X_PORT_MASK_INT0_n(n) GENMASK((n) - 1, 0)
-#define YT921X_PORT_MASK_EXT0 BIT(8)
-#define YT921X_PORT_MASK_EXT1 BIT(9)
-
-static const struct yt921x_info yt921x_infos[] = {
- {
- "YT9215SC", YT9215_MAJOR, 1, 0,
- YT921X_PORT_MASK_INT0_n(5),
- YT921X_PORT_MASK_EXT0 | YT921X_PORT_MASK_EXT1,
- },
- {
- "YT9215S", YT9215_MAJOR, 2, 0,
- YT921X_PORT_MASK_INT0_n(5),
- YT921X_PORT_MASK_EXT0 | YT921X_PORT_MASK_EXT1,
- },
- {
- "YT9215RB", YT9215_MAJOR, 3, 0,
- YT921X_PORT_MASK_INT0_n(5),
- YT921X_PORT_MASK_EXT0 | YT921X_PORT_MASK_EXT1,
- },
- {
- "YT9214NB", YT9215_MAJOR, 3, 2,
- YT921X_PORT_MASK_INTn(1) | YT921X_PORT_MASK_INTn(3),
- YT921X_PORT_MASK_EXT0 | YT921X_PORT_MASK_EXT1,
- },
- {
- "YT9213NB", YT9215_MAJOR, 3, 3,
- YT921X_PORT_MASK_INTn(1) | YT921X_PORT_MASK_INTn(3),
- YT921X_PORT_MASK_EXT1,
- },
- {
- "YT9218N", YT9218_MAJOR, 0, 0,
- YT921X_PORT_MASK_INT0_n(8),
- 0,
- },
- {
- "YT9218MB", YT9218_MAJOR, 1, 0,
- YT921X_PORT_MASK_INT0_n(8),
- YT921X_PORT_MASK_EXT0 | YT921X_PORT_MASK_EXT1,
- },
- {}
-};
-
-#define YT921X_NAME "yt921x"
-
-#define YT921X_VID_UNWARE 4095
-
-#define YT921X_POLL_SLEEP_US 10000
-#define YT921X_POLL_TIMEOUT_US 100000
-
-/* The interval should be small enough to avoid overflow of 32bit MIBs.
- *
- * Until we can read MIBs from stats64 call directly (i.e. sleep
- * there), we have to poll stats more frequently then it is actually needed.
- * For overflow protection, normally, 100 sec interval should have been OK.
- */
-#define YT921X_STATS_INTERVAL_JIFFIES (3 * HZ)
-
-struct yt921x_reg_mdio {
- struct mii_bus *bus;
- int addr;
- /* SWITCH_ID_1 / SWITCH_ID_0 of the device
- *
- * This is a way to multiplex multiple devices on the same MII phyaddr
- * and should be configurable in DT. However, MDIO core simply doesn't
- * allow multiple devices over one reg addr, so this is a fixed value
- * for now until a solution is found.
- *
- * Keep this because we need switchid to form MII regaddrs anyway.
- */
- unsigned char switchid;
-};
-
-/* TODO: SPI/I2C */
-
-#define to_yt921x_priv(_ds) container_of_const(_ds, struct yt921x_priv, ds)
-#define to_device(priv) ((priv)->ds.dev)
-
-static u32 ethaddr_hi4_to_u32(const unsigned char *addr)
-{
- return (addr[0] << 24) | (addr[1] << 16) | (addr[2] << 8) | addr[3];
-}
-
-static u32 ethaddr_lo2_to_u32(const unsigned char *addr)
-{
- return (addr[4] << 8) | addr[5];
-}
-
-static int yt921x_reg_read(struct yt921x_priv *priv, u32 reg, u32 *valp)
-{
- WARN_ON(!mutex_is_locked(&priv->reg_lock));
-
- return priv->reg_ops->read(priv->reg_ctx, reg, valp);
-}
-
-static int yt921x_reg_write(struct yt921x_priv *priv, u32 reg, u32 val)
-{
- WARN_ON(!mutex_is_locked(&priv->reg_lock));
-
- return priv->reg_ops->write(priv->reg_ctx, reg, val);
-}
-
-static int
-yt921x_reg_wait(struct yt921x_priv *priv, u32 reg, u32 mask, u32 *valp)
-{
- u32 val;
- int res;
- int ret;
-
- ret = read_poll_timeout(yt921x_reg_read, res,
- res || (val & mask) == *valp,
- YT921X_POLL_SLEEP_US, YT921X_POLL_TIMEOUT_US,
- false, priv, reg, &val);
- if (ret)
- return ret;
- if (res)
- return res;
-
- *valp = val;
- return 0;
-}
-
-static int
-yt921x_reg_update_bits(struct yt921x_priv *priv, u32 reg, u32 mask, u32 val)
-{
- int res;
- u32 v;
- u32 u;
-
- res = yt921x_reg_read(priv, reg, &v);
- if (res)
- return res;
-
- u = v;
- u &= ~mask;
- u |= val;
- if (u == v)
- return 0;
-
- return yt921x_reg_write(priv, reg, u);
-}
-
-static int yt921x_reg_set_bits(struct yt921x_priv *priv, u32 reg, u32 mask)
-{
- return yt921x_reg_update_bits(priv, reg, 0, mask);
-}
-
-static int yt921x_reg_clear_bits(struct yt921x_priv *priv, u32 reg, u32 mask)
-{
- return yt921x_reg_update_bits(priv, reg, mask, 0);
-}
-
-static int
-yt921x_reg_toggle_bits(struct yt921x_priv *priv, u32 reg, u32 mask, bool set)
-{
- return yt921x_reg_update_bits(priv, reg, mask, !set ? 0 : mask);
-}
-
-/* Some multi-word registers, like VLANn_CTRL, should be treated as a single
- * long register. More specifically, writes to parts of its words won't become
- * visible, until the last word is written.
- *
- * Here we require full read and write operations over these registers to
- * eliminate potential issues, although partial reads/writes are also possible.
- */
-
-static void update_ctrls_unaligned(u32 *lo, u32 *hi, u64 mask, u64 val)
-{
- *lo &= ~lower_32_bits(mask);
- *hi &= ~upper_32_bits(mask);
- *lo |= lower_32_bits(val);
- *hi |= upper_32_bits(val);
-}
-
-static int
-yt921x_regs_read(struct yt921x_priv *priv, u32 reg, u32 *vals,
- unsigned int num_regs)
-{
- int res;
-
- for (unsigned int i = 0; i < num_regs; i++) {
- res = yt921x_reg_read(priv, reg + 4 * i, &vals[i]);
- if (res)
- return res;
- }
-
- return 0;
-}
-
-static int
-yt921x_regs_write(struct yt921x_priv *priv, u32 reg, const u32 *vals,
- unsigned int num_regs)
-{
- int res;
-
- for (unsigned int i = 0; i < num_regs; i++) {
- res = yt921x_reg_write(priv, reg + 4 * i, vals[i]);
- if (res)
- return res;
- }
-
- return 0;
-}
-
-static int
-yt921x_regs_update_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks,
- const u32 *vals, unsigned int num_regs)
-{
- bool changed = false;
- u32 vs[4];
- int res;
-
- BUILD_BUG_ON(num_regs > ARRAY_SIZE(vs));
-
- res = yt921x_regs_read(priv, reg, vs, num_regs);
- if (res)
- return res;
-
- for (unsigned int i = 0; i < num_regs; i++) {
- u32 u = vs[i];
-
- u &= ~masks[i];
- u |= vals[i];
- if (u != vs[i])
- changed = true;
-
- vs[i] = u;
- }
-
- if (!changed)
- return 0;
-
- return yt921x_regs_write(priv, reg, vs, num_regs);
-}
-
-static int
-yt921x_regs_clear_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks,
- unsigned int num_regs)
-{
- bool changed = false;
- u32 vs[4];
- int res;
-
- BUILD_BUG_ON(num_regs > ARRAY_SIZE(vs));
-
- res = yt921x_regs_read(priv, reg, vs, num_regs);
- if (res)
- return res;
-
- for (unsigned int i = 0; i < num_regs; i++) {
- u32 u = vs[i];
-
- u &= ~masks[i];
- if (u != vs[i])
- changed = true;
-
- vs[i] = u;
- }
-
- if (!changed)
- return 0;
-
- return yt921x_regs_write(priv, reg, vs, num_regs);
-}
-
-static int
-yt921x_reg64_write(struct yt921x_priv *priv, u32 reg, const u32 *vals)
-{
- return yt921x_regs_write(priv, reg, vals, 2);
-}
-
-static int
-yt921x_reg64_update_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks,
- const u32 *vals)
-{
- return yt921x_regs_update_bits(priv, reg, masks, vals, 2);
-}
-
-static int
-yt921x_reg64_clear_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks)
-{
- return yt921x_regs_clear_bits(priv, reg, masks, 2);
-}
-
-static int
-yt921x_reg96_write(struct yt921x_priv *priv, u32 reg, const u32 *vals)
-{
- return yt921x_regs_write(priv, reg, vals, 3);
-}
-
-static int yt921x_reg_mdio_read(void *context, u32 reg, u32 *valp)
-{
- struct yt921x_reg_mdio *mdio = context;
- struct mii_bus *bus = mdio->bus;
- int addr = mdio->addr;
- u32 reg_addr;
- u32 reg_data;
- u32 val;
- int res;
-
- /* Hold the mdio bus lock to avoid (un)locking for 4 times */
- mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
-
- reg_addr = YT921X_SMI_SWITCHID(mdio->switchid) | YT921X_SMI_ADDR |
- YT921X_SMI_READ;
- res = __mdiobus_write(bus, addr, reg_addr, (u16)(reg >> 16));
- if (res)
- goto end;
- res = __mdiobus_write(bus, addr, reg_addr, (u16)reg);
- if (res)
- goto end;
-
- reg_data = YT921X_SMI_SWITCHID(mdio->switchid) | YT921X_SMI_DATA |
- YT921X_SMI_READ;
- res = __mdiobus_read(bus, addr, reg_data);
- if (res < 0)
- goto end;
- val = (u16)res;
- res = __mdiobus_read(bus, addr, reg_data);
- if (res < 0)
- goto end;
- val = (val << 16) | (u16)res;
-
- *valp = val;
- res = 0;
-
-end:
- mutex_unlock(&bus->mdio_lock);
- return res;
-}
-
-static int yt921x_reg_mdio_write(void *context, u32 reg, u32 val)
-{
- struct yt921x_reg_mdio *mdio = context;
- struct mii_bus *bus = mdio->bus;
- int addr = mdio->addr;
- u32 reg_addr;
- u32 reg_data;
- int res;
-
- mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
-
- reg_addr = YT921X_SMI_SWITCHID(mdio->switchid) | YT921X_SMI_ADDR |
- YT921X_SMI_WRITE;
- res = __mdiobus_write(bus, addr, reg_addr, (u16)(reg >> 16));
- if (res)
- goto end;
- res = __mdiobus_write(bus, addr, reg_addr, (u16)reg);
- if (res)
- goto end;
-
- reg_data = YT921X_SMI_SWITCHID(mdio->switchid) | YT921X_SMI_DATA |
- YT921X_SMI_WRITE;
- res = __mdiobus_write(bus, addr, reg_data, (u16)(val >> 16));
- if (res)
- goto end;
- res = __mdiobus_write(bus, addr, reg_data, (u16)val);
- if (res)
- goto end;
-
- res = 0;
-
-end:
- mutex_unlock(&bus->mdio_lock);
- return res;
-}
-
-static const struct yt921x_reg_ops yt921x_reg_ops_mdio = {
- .read = yt921x_reg_mdio_read,
- .write = yt921x_reg_mdio_write,
-};
-
-/* TODO: SPI/I2C */
-
-static int yt921x_intif_wait(struct yt921x_priv *priv)
-{
- u32 val = 0;
-
- return yt921x_reg_wait(priv, YT921X_INT_MBUS_OP, YT921X_MBUS_OP_START,
- &val);
-}
-
-static int
-yt921x_intif_read(struct yt921x_priv *priv, int port, int reg, u16 *valp)
-{
- struct device *dev = to_device(priv);
- u32 mask;
- u32 ctrl;
- u32 val;
- int res;
-
- res = yt921x_intif_wait(priv);
- if (res)
- return res;
-
- mask = YT921X_MBUS_CTRL_PORT_M | YT921X_MBUS_CTRL_REG_M |
- YT921X_MBUS_CTRL_OP_M;
- ctrl = YT921X_MBUS_CTRL_PORT(port) | YT921X_MBUS_CTRL_REG(reg) |
- YT921X_MBUS_CTRL_READ;
- res = yt921x_reg_update_bits(priv, YT921X_INT_MBUS_CTRL, mask, ctrl);
- if (res)
- return res;
- res = yt921x_reg_write(priv, YT921X_INT_MBUS_OP, YT921X_MBUS_OP_START);
- if (res)
- return res;
-
- res = yt921x_intif_wait(priv);
- if (res)
- return res;
- res = yt921x_reg_read(priv, YT921X_INT_MBUS_DIN, &val);
- if (res)
- return res;
-
- if ((u16)val != val)
- dev_info(dev,
- "%s: port %d, reg 0x%x: Expected u16, got 0x%08x\n",
- __func__, port, reg, val);
- *valp = (u16)val;
- return 0;
-}
-
-static int
-yt921x_intif_write(struct yt921x_priv *priv, int port, int reg, u16 val)
-{
- u32 mask;
- u32 ctrl;
- int res;
-
- res = yt921x_intif_wait(priv);
- if (res)
- return res;
-
- mask = YT921X_MBUS_CTRL_PORT_M | YT921X_MBUS_CTRL_REG_M |
- YT921X_MBUS_CTRL_OP_M;
- ctrl = YT921X_MBUS_CTRL_PORT(port) | YT921X_MBUS_CTRL_REG(reg) |
- YT921X_MBUS_CTRL_WRITE;
- res = yt921x_reg_update_bits(priv, YT921X_INT_MBUS_CTRL, mask, ctrl);
- if (res)
- return res;
- res = yt921x_reg_write(priv, YT921X_INT_MBUS_DOUT, val);
- if (res)
- return res;
- res = yt921x_reg_write(priv, YT921X_INT_MBUS_OP, YT921X_MBUS_OP_START);
- if (res)
- return res;
-
- return yt921x_intif_wait(priv);
-}
-
-static int yt921x_mbus_int_read(struct mii_bus *mbus, int port, int reg)
-{
- struct yt921x_priv *priv = mbus->priv;
- u16 val;
- int res;
-
- if (port >= YT921X_PORT_NUM)
- return U16_MAX;
-
- mutex_lock(&priv->reg_lock);
- res = yt921x_intif_read(priv, port, reg, &val);
- mutex_unlock(&priv->reg_lock);
-
- if (res)
- return res;
- return val;
-}
-
-static int
-yt921x_mbus_int_write(struct mii_bus *mbus, int port, int reg, u16 data)
-{
- struct yt921x_priv *priv = mbus->priv;
- int res;
-
- if (port >= YT921X_PORT_NUM)
- return -ENODEV;
-
- mutex_lock(&priv->reg_lock);
- res = yt921x_intif_write(priv, port, reg, data);
- mutex_unlock(&priv->reg_lock);
-
- return res;
-}
-
-static int
-yt921x_mbus_int_init(struct yt921x_priv *priv, struct device_node *mnp)
-{
- struct device *dev = to_device(priv);
- struct mii_bus *mbus;
- int res;
-
- mbus = devm_mdiobus_alloc(dev);
- if (!mbus)
- return -ENOMEM;
-
- mbus->name = "YT921x internal MDIO bus";
- snprintf(mbus->id, MII_BUS_ID_SIZE, "%s", dev_name(dev));
- mbus->priv = priv;
- mbus->read = yt921x_mbus_int_read;
- mbus->write = yt921x_mbus_int_write;
- mbus->parent = dev;
- mbus->phy_mask = (u32)~GENMASK(YT921X_PORT_NUM - 1, 0);
-
- res = devm_of_mdiobus_register(dev, mbus, mnp);
- if (res)
- return res;
-
- priv->mbus_int = mbus;
-
- return 0;
-}
-
-static int yt921x_extif_wait(struct yt921x_priv *priv)
-{
- u32 val = 0;
-
- return yt921x_reg_wait(priv, YT921X_EXT_MBUS_OP, YT921X_MBUS_OP_START,
- &val);
-}
-
-static int
-yt921x_extif_read(struct yt921x_priv *priv, int port, int reg, u16 *valp)
-{
- struct device *dev = to_device(priv);
- u32 mask;
- u32 ctrl;
- u32 val;
- int res;
-
- res = yt921x_extif_wait(priv);
- if (res)
- return res;
-
- mask = YT921X_MBUS_CTRL_PORT_M | YT921X_MBUS_CTRL_REG_M |
- YT921X_MBUS_CTRL_TYPE_M | YT921X_MBUS_CTRL_OP_M;
- ctrl = YT921X_MBUS_CTRL_PORT(port) | YT921X_MBUS_CTRL_REG(reg) |
- YT921X_MBUS_CTRL_TYPE_C22 | YT921X_MBUS_CTRL_READ;
- res = yt921x_reg_update_bits(priv, YT921X_EXT_MBUS_CTRL, mask, ctrl);
- if (res)
- return res;
- res = yt921x_reg_write(priv, YT921X_EXT_MBUS_OP, YT921X_MBUS_OP_START);
- if (res)
- return res;
-
- res = yt921x_extif_wait(priv);
- if (res)
- return res;
- res = yt921x_reg_read(priv, YT921X_EXT_MBUS_DIN, &val);
- if (res)
- return res;
-
- if ((u16)val != val)
- dev_info(dev,
- "%s: port %d, reg 0x%x: Expected u16, got 0x%08x\n",
- __func__, port, reg, val);
- *valp = (u16)val;
- return 0;
-}
-
-static int
-yt921x_extif_write(struct yt921x_priv *priv, int port, int reg, u16 val)
-{
- u32 mask;
- u32 ctrl;
- int res;
-
- res = yt921x_extif_wait(priv);
- if (res)
- return res;
-
- mask = YT921X_MBUS_CTRL_PORT_M | YT921X_MBUS_CTRL_REG_M |
- YT921X_MBUS_CTRL_TYPE_M | YT921X_MBUS_CTRL_OP_M;
- ctrl = YT921X_MBUS_CTRL_PORT(port) | YT921X_MBUS_CTRL_REG(reg) |
- YT921X_MBUS_CTRL_TYPE_C22 | YT921X_MBUS_CTRL_WRITE;
- res = yt921x_reg_update_bits(priv, YT921X_EXT_MBUS_CTRL, mask, ctrl);
- if (res)
- return res;
- res = yt921x_reg_write(priv, YT921X_EXT_MBUS_DOUT, val);
- if (res)
- return res;
- res = yt921x_reg_write(priv, YT921X_EXT_MBUS_OP, YT921X_MBUS_OP_START);
- if (res)
- return res;
-
- return yt921x_extif_wait(priv);
-}
-
-static int yt921x_mbus_ext_read(struct mii_bus *mbus, int port, int reg)
-{
- struct yt921x_priv *priv = mbus->priv;
- u16 val;
- int res;
-
- mutex_lock(&priv->reg_lock);
- res = yt921x_extif_read(priv, port, reg, &val);
- mutex_unlock(&priv->reg_lock);
-
- if (res)
- return res;
- return val;
-}
-
-static int
-yt921x_mbus_ext_write(struct mii_bus *mbus, int port, int reg, u16 data)
-{
- struct yt921x_priv *priv = mbus->priv;
- int res;
-
- mutex_lock(&priv->reg_lock);
- res = yt921x_extif_write(priv, port, reg, data);
- mutex_unlock(&priv->reg_lock);
-
- return res;
-}
-
-static int
-yt921x_mbus_ext_init(struct yt921x_priv *priv, struct device_node *mnp)
-{
- struct device *dev = to_device(priv);
- struct mii_bus *mbus;
- int res;
-
- mbus = devm_mdiobus_alloc(dev);
- if (!mbus)
- return -ENOMEM;
-
- mbus->name = "YT921x external MDIO bus";
- snprintf(mbus->id, MII_BUS_ID_SIZE, "%s@ext", dev_name(dev));
- mbus->priv = priv;
- /* TODO: c45? */
- mbus->read = yt921x_mbus_ext_read;
- mbus->write = yt921x_mbus_ext_write;
- mbus->parent = dev;
-
- res = devm_of_mdiobus_register(dev, mbus, mnp);
- if (res)
- return res;
-
- priv->mbus_ext = mbus;
-
- return 0;
-}
-
-/* Read and handle overflow of 32bit MIBs. MIB buffer must be zeroed before. */
-static int yt921x_read_mib(struct yt921x_priv *priv, int port)
-{
- struct yt921x_port *pp = &priv->ports[port];
- struct device *dev = to_device(priv);
- struct yt921x_mib *mib = &pp->mib;
- int res = 0;
-
- /* Reading of yt921x_port::mib is not protected by a lock and it's vain
- * to keep its consistency, since we have to read registers one by one
- * and there is no way to make a snapshot of MIB stats.
- *
- * Writing (by this function only) is and should be protected by
- * reg_lock.
- */
-
- for (size_t i = 0; i < ARRAY_SIZE(yt921x_mib_descs); i++) {
- const struct yt921x_mib_desc *desc = &yt921x_mib_descs[i];
- u32 reg = YT921X_MIBn_DATA0(port) + desc->offset;
- u64 *valp = &((u64 *)mib)[i];
- u32 val0;
- u64 val;
-
- res = yt921x_reg_read(priv, reg, &val0);
- if (res)
- break;
-
- if (desc->size <= 1) {
- u64 old_val = *valp;
-
- val = (old_val & ~(u64)U32_MAX) | val0;
- if (val < old_val)
- val += 1ull << 32;
- } else {
- u32 val1;
-
- res = yt921x_reg_read(priv, reg + 4, &val1);
- if (res)
- break;
- val = ((u64)val1 << 32) | val0;
- }
-
- WRITE_ONCE(*valp, val);
- }
-
- pp->rx_frames = mib->rx_64byte + mib->rx_65_127byte +
- mib->rx_128_255byte + mib->rx_256_511byte +
- mib->rx_512_1023byte + mib->rx_1024_1518byte +
- mib->rx_jumbo;
- pp->tx_frames = mib->tx_64byte + mib->tx_65_127byte +
- mib->tx_128_255byte + mib->tx_256_511byte +
- mib->tx_512_1023byte + mib->tx_1024_1518byte +
- mib->tx_jumbo;
-
- if (res)
- dev_err(dev, "Failed to %s port %d: %i\n", "read stats for",
- port, res);
- return res;
-}
-
-static void yt921x_poll_mib(struct work_struct *work)
-{
- struct yt921x_port *pp = container_of_const(work, struct yt921x_port,
- mib_read.work);
- struct yt921x_priv *priv = (void *)(pp - pp->index) -
- offsetof(struct yt921x_priv, ports);
- unsigned long delay = YT921X_STATS_INTERVAL_JIFFIES;
- int port = pp->index;
- int res;
-
- mutex_lock(&priv->reg_lock);
- res = yt921x_read_mib(priv, port);
- mutex_unlock(&priv->reg_lock);
- if (res)
- delay *= 4;
-
- schedule_delayed_work(&pp->mib_read, delay);
-}
-
-static void
-yt921x_dsa_get_strings(struct dsa_switch *ds, int port, u32 stringset,
- uint8_t *data)
-{
- if (stringset != ETH_SS_STATS)
- return;
-
- for (size_t i = 0; i < ARRAY_SIZE(yt921x_mib_descs); i++) {
- const struct yt921x_mib_desc *desc = &yt921x_mib_descs[i];
-
- if (desc->name)
- ethtool_puts(&data, desc->name);
- }
-}
-
-static void
-yt921x_dsa_get_ethtool_stats(struct dsa_switch *ds, int port, uint64_t *data)
-{
- struct yt921x_priv *priv = to_yt921x_priv(ds);
- struct yt921x_port *pp = &priv->ports[port];
- struct yt921x_mib *mib = &pp->mib;
- size_t j;
-
- mutex_lock(&priv->reg_lock);
- yt921x_read_mib(priv, port);
- mutex_unlock(&priv->reg_lock);
-
- j = 0;
- for (size_t i = 0; i < ARRAY_SIZE(yt921x_mib_descs); i++) {
- const struct yt921x_mib_desc *desc = &yt921x_mib_descs[i];
-
- if (!desc->name)
- continue;
-
- data[j] = ((u64 *)mib)[i];
- j++;
- }
-}
-
-static int yt921x_dsa_get_sset_count(struct dsa_switch *ds, int port, int sset)
-{
- int cnt = 0;
-
- if (sset != ETH_SS_STATS)
- return 0;
-
- for (size_t i = 0; i < ARRAY_SIZE(yt921x_mib_descs); i++) {
- const struct yt921x_mib_desc *desc = &yt921x_mib_descs[i];
-
- if (desc->name)
- cnt++;
- }
-
- return cnt;
-}
-
-static void
-yt921x_dsa_get_eth_mac_stats(struct dsa_switch *ds, int port,
- struct ethtool_eth_mac_stats *mac_stats)
-{
- struct yt921x_priv *priv = to_yt921x_priv(ds);
- struct yt921x_port *pp = &priv->ports[port];
- struct yt921x_mib *mib = &pp->mib;
-
- mutex_lock(&priv->reg_lock);
- yt921x_read_mib(priv, port);
- mutex_unlock(&priv->reg_lock);
-
- mac_stats->FramesTransmittedOK = pp->tx_frames;
- mac_stats->SingleCollisionFrames = mib->tx_single_collisions;
- mac_stats->MultipleCollisionFrames = mib->tx_multiple_collisions;
- mac_stats->FramesReceivedOK = pp->rx_frames;
- mac_stats->FrameCheckSequenceErrors = mib->rx_crc_errors;
- mac_stats->AlignmentErrors = mib->rx_alignment_errors;
- mac_stats->OctetsTransmittedOK = mib->tx_good_bytes;
- mac_stats->FramesWithDeferredXmissions = mib->tx_deferred;
- mac_stats->LateCollisions = mib->tx_late_collisions;
- mac_stats->FramesAbortedDueToXSColls = mib->tx_aborted_errors;
- /* mac_stats->FramesLostDueToIntMACXmitError */
- /* mac_stats->CarrierSenseErrors */
- mac_stats->OctetsReceivedOK = mib->rx_good_bytes;
- /* mac_stats->FramesLostDueToIntMACRcvError */
- mac_stats->MulticastFramesXmittedOK = mib->tx_multicast;
- mac_stats->BroadcastFramesXmittedOK = mib->tx_broadcast;
- /* mac_stats->FramesWithExcessiveDeferral */
- mac_stats->MulticastFramesReceivedOK = mib->rx_multicast;
- mac_stats->BroadcastFramesReceivedOK = mib->rx_broadcast;
- /* mac_stats->InRangeLengthErrors */
- /* mac_stats->OutOfRangeLengthField */
- mac_stats->FrameTooLongErrors = mib->rx_oversize_errors;
-}
-
-static void
-yt921x_dsa_get_eth_ctrl_stats(struct dsa_switch *ds, int port,
- struct ethtool_eth_ctrl_stats *ctrl_stats)
-{
- struct yt921x_priv *priv = to_yt921x_priv(ds);
- struct yt921x_port *pp = &priv->ports[port];
- struct yt921x_mib *mib = &pp->mib;
-
- mutex_lock(&priv->reg_lock);
- yt921x_read_mib(priv, port);
- mutex_unlock(&priv->reg_lock);
-
- ctrl_stats->MACControlFramesTransmitted = mib->tx_pause;
- ctrl_stats->MACControlFramesReceived = mib->rx_pause;
- /* ctrl_stats->UnsupportedOpcodesReceived */
-}
-
-static const struct ethtool_rmon_hist_range yt921x_rmon_ranges[] = {
- { 0, 64 },
- { 65, 127 },
- { 128, 255 },
- { 256, 511 },
- { 512, 1023 },
- { 1024, 1518 },
- { 1519, YT921X_FRAME_SIZE_MAX },
- {}
-};
-
-static void
-yt921x_dsa_get_rmon_stats(struct dsa_switch *ds, int port,
- struct ethtool_rmon_stats *rmon_stats,
- const struct ethtool_rmon_hist_range **ranges)
-{
- struct yt921x_priv *priv = to_yt921x_priv(ds);
- struct yt921x_port *pp = &priv->ports[port];
- struct yt921x_mib *mib = &pp->mib;
-
- mutex_lock(&priv->reg_lock);
- yt921x_read_mib(priv, port);
- mutex_unlock(&priv->reg_lock);
-
- *ranges = yt921x_rmon_ranges;
-
- rmon_stats->undersize_pkts = mib->rx_undersize_errors;
- rmon_stats->oversize_pkts = mib->rx_oversize_errors;
- rmon_stats->fragments = mib->rx_alignment_errors;
- /* rmon_stats->jabbers */
-
- rmon_stats->hist[0] = mib->rx_64byte;
- rmon_stats->hist[1] = mib->rx_65_127byte;
- rmon_stats->hist[2] = mib->rx_128_255byte;
- rmon_stats->hist[3] = mib->rx_256_511byte;
- rmon_stats->hist[4] = mib->rx_512_1023byte;
- rmon_stats->hist[5] = mib->rx_1024_1518byte;
- rmon_stats->hist[6] = mib->rx_jumbo;
-
- rmon_stats->hist_tx[0] = mib->tx_64byte;
- rmon_stats->hist_tx[1] = mib->tx_65_127byte;
- rmon_stats->hist_tx[2] = mib->tx_128_255byte;
- rmon_stats->hist_tx[3] = mib->tx_256_511byte;
- rmon_stats->hist_tx[4] = mib->tx_512_1023byte;
- rmon_stats->hist_tx[5] = mib->tx_1024_1518byte;
- rmon_stats->hist_tx[6] = mib->tx_jumbo;
-}
-
-static void
-yt921x_dsa_get_stats64(struct dsa_switch *ds, int port,
- struct rtnl_link_stats64 *stats)
-{
- struct yt921x_priv *priv = to_yt921x_priv(ds);
- struct yt921x_port *pp = &priv->ports[port];
- struct yt921x_mib *mib = &pp->mib;
-
- stats->rx_length_errors = mib->rx_undersize_errors +
- mib->rx_fragment_errors;
- stats->rx_over_errors = mib->rx_oversize_errors;
- stats->rx_crc_errors = mib->rx_crc_errors;
- stats->rx_frame_errors = mib->rx_alignment_errors;
- /* stats->rx_fifo_errors */
- /* stats->rx_missed_errors */
-
- stats->tx_aborted_errors = mib->tx_aborted_errors;
- /* stats->tx_carrier_errors */
- stats->tx_fifo_errors = mib->tx_undersize_errors;
- /* stats->tx_heartbeat_errors */
- stats->tx_window_errors = mib->tx_late_collisions;
-
- stats->rx_packets = pp->rx_frames;
- stats->tx_packets = pp->tx_frames;
- stats->rx_bytes = mib->rx_good_bytes - ETH_FCS_LEN * stats->rx_packets;
- stats->tx_bytes = mib->tx_good_bytes - ETH_FCS_LEN * stats->tx_packets;
- stats->rx_errors = stats->rx_length_errors + stats->rx_over_errors +
- stats->rx_crc_errors + stats->rx_frame_errors;
- stats->tx_errors = stats->tx_aborted_errors + stats->tx_fifo_errors +
- stats->tx_window_errors;
- stats->rx_dropped = mib->rx_dropped;
- /* stats->tx_dropped */
- stats->multicast = mib->rx_multicast;
- stats->collisions = mib->tx_collisions;
-}
-
-static void
-yt921x_dsa_get_pause_stats(struct dsa_switch *ds, int port,
- struct ethtool_pause_stats *pause_stats)
-{
- struct yt921x_priv *priv = to_yt921x_priv(ds);
- struct yt921x_port *pp = &priv->ports[port];
- struct yt921x_mib *mib = &pp->mib;
-
- mutex_lock(&priv->reg_lock);
- yt921x_read_mib(priv, port);
- mutex_unlock(&priv->reg_lock);
-
- pause_stats->tx_pause_frames = mib->tx_pause;
- pause_stats->rx_pause_frames = mib->rx_pause;
-}
-
-static int
-yt921x_set_eee(struct yt921x_priv *priv, int port, struct ethtool_keee *e)
-{
- /* Poor datasheet for EEE operations; don't ask if you are confused */
-
- bool enable = e->eee_enabled;
- u16 new_mask;
- int res;
-
- /* Enable / disable global EEE */
- new_mask = priv->eee_ports_mask;
- new_mask &= ~BIT(port);
- new_mask |= !enable ? 0 : BIT(port);
-
- if (!!new_mask != !!priv->eee_ports_mask) {
- res = yt921x_reg_toggle_bits(priv, YT921X_PON_STRAP_FUNC,
- YT921X_PON_STRAP_EEE, !!new_mask);
- if (res)
- return res;
- res = yt921x_reg_toggle_bits(priv, YT921X_PON_STRAP_VAL,
- YT921X_PON_STRAP_EEE, !!new_mask);
- if (res)
- return res;
- }
-
- priv->eee_ports_mask = new_mask;
-
- /* Enable / disable port EEE */
- res = yt921x_reg_toggle_bits(priv, YT921X_EEE_CTRL,
- YT921X_EEE_CTRL_ENn(port), enable);
- if (res)
- return res;
- res = yt921x_reg_toggle_bits(priv, YT921X_EEEn_VAL(port),
- YT921X_EEE_VAL_DATA, enable);
- if (res)
- return res;
-
- return 0;
-}
-
-static int
-yt921x_dsa_set_mac_eee(struct dsa_switch *ds, int port, struct ethtool_keee *e)
-{
- struct yt921x_priv *priv = to_yt921x_priv(ds);
- int res;
-
- mutex_lock(&priv->reg_lock);
- res = yt921x_set_eee(priv, port, e);
- mutex_unlock(&priv->reg_lock);
-
- return res;
-}
-
-static int yt921x_mtu_fetch(struct yt921x_priv *priv, int port)
-{
- struct dsa_port *dp = dsa_to_port(&priv->ds, port);
-
- return dp->user ? READ_ONCE(dp->user->mtu) : ETH_DATA_LEN;
-}
-
-static int
-yt921x_dsa_port_change_mtu(struct dsa_switch *ds, int port, int new_mtu)
-{
- /* Only serves as packet filter, since the frame size is always set to
- * maximum after reset
- */
-
- struct yt921x_priv *priv = to_yt921x_priv(ds);
- struct dsa_port *dp = dsa_to_port(ds, port);
- int frame_size;
- int res;
-
- frame_size = new_mtu + ETH_HLEN + ETH_FCS_LEN;
- if (dsa_port_is_cpu(dp))
- frame_size += YT921X_TAG_LEN;
-
- mutex_lock(&priv->reg_lock);
- res = yt921x_reg_update_bits(priv, YT921X_MACn_FRAME(port),
- YT921X_MAC_FRAME_SIZE_M,
- YT921X_MAC_FRAME_SIZE(frame_size));
- mutex_unlock(&priv->reg_lock);
-
- return res;
-}
-
-static int yt921x_dsa_port_max_mtu(struct dsa_switch *ds, int port)
-{
- /* Only called for user ports, exclude tag len here */
- return YT921X_FRAME_SIZE_MAX - ETH_HLEN - ETH_FCS_LEN - YT921X_TAG_LEN;
-}
-
-/* v * 2^e */
-static u64 ldexpu64(u64 v, int e)
-{
- return e >= 0 ? v << e : v >> -e;
-}
-
-/* slot (ns) * rate (/s) / 10^9 (ns/s) = 2^C * token * 4^unit */
-static u32 rate2token(u64 rate, unsigned int slot_ns, int unit, int C)
-{
- int e = 2 * unit + C + YT921X_TOKEN_RATE_C;
-
- return div_u64(ldexpu64(slot_ns * rate, -e), 1000000000);
-}
-
-static u64 token2rate(u32 token, unsigned int slot_ns, int unit, int C)
-{
- int e = 2 * unit + C + YT921X_TOKEN_RATE_C;
-
- return div_u64(ldexpu64(mul_u32_u32(1000000000, token), e), slot_ns);
-}
-
-/* burst = 2^C * token * 4^unit */
-static u32 burst2token(u64 burst, int unit, int C)
-{
- return ldexpu64(burst, -(2 * unit + C));
-}
-
-static u64 token2burst(u32 token, int unit, int C)
-{
- return ldexpu64(token, 2 * unit + C);
-}
-
-struct yt921x_marker {
- u32 cir;
- u32 cbs;
- u32 ebs;
- int unit;
- bool pkt_mode;
-};
-
-#define YT921X_MARKER_PKT_MODE BIT(0)
-#define YT921X_MARKER_SINGLE_BUCKET BIT(1)
-
-static int
-yt921x_marker_tfm(struct yt921x_marker *marker, u64 rate, u64 burst,
- unsigned int flags, unsigned int slot_ns, u32 cir_max,
- u32 cbs_max, int unit_max, struct yt921x_priv *priv, int port,
- struct netlink_ext_ack *extack)
-{
- const int C = flags & YT921X_MARKER_PKT_MODE ? YT921X_TOKEN_PKT_C :
- YT921X_TOKEN_BYTE_C;
- struct device *dev = to_device(priv);
- struct yt921x_marker m;
- u64 burst_est;
- u64 burst_sug;
- u64 burst_max;
- u64 rate_max;
-
- m.unit = unit_max;
- rate_max = token2rate(cir_max, slot_ns, m.unit, C);
- burst_max = token2burst(cbs_max, m.unit, C);
-
- /* Check for unusual values */
- if (rate > rate_max || burst > burst_max) {
- NL_SET_ERR_MSG_MOD(extack, "Unexpected tremendous rate");
- return -ERANGE;
- }
-
- /* Check for matching burst */
- burst_est = div_u64(slot_ns * rate, 1000000000);
- burst_sug = burst_est;
- if (flags & YT921X_MARKER_PKT_MODE)
- burst_sug++;
- else
- burst_sug += ETH_HLEN + yt921x_mtu_fetch(priv, port) +
- ETH_FCS_LEN;
- if (burst_sug > burst)
- NL_SET_ERR_MSG_FMT_MOD(extack,
- "Consider match rate %llu with burst at least %llu",
- rate, burst_sug);
-
- /* Select unit */
- for (; m.unit > 0; m.unit--) {
- if (rate > (rate_max >> 2) || burst > (burst_max >> 2))
- break;
- rate_max >>= 2;
- burst_max >>= 2;
- }
-
- /* Calculate information rate and bucket size */
- m.cir = rate2token(rate, slot_ns, m.unit, C);
- if (!m.cir)
- m.cir = 1;
- else if (WARN_ON(m.cir > cir_max))
- m.cir = cir_max;
- m.cbs = burst2token(burst, m.unit, C);
- if (!m.cbs)
- m.cbs = 1;
- else if (WARN_ON(m.cbs > cbs_max))
- m.cbs = cbs_max;
-
- /* Cut EBS */
- m.ebs = 0;
- if (!(flags & YT921X_MARKER_SINGLE_BUCKET)) {
- /* We don't have a chance to adjust rate when MTU is changed */
- if (flags & YT921X_MARKER_PKT_MODE)
- burst_est++;
- else
- burst_est += YT921X_FRAME_SIZE_MAX;
-
- if (burst_est < burst) {
- u32 pbs = m.cbs;
-
- m.cbs = burst2token(burst_est, m.unit, C);
- if (!m.cbs)
- m.cbs = 1;
- else if (WARN_ON(m.cbs > cbs_max))
- m.cbs = cbs_max;
-
- if (pbs > m.cbs)
- m.ebs = pbs - m.cbs;
- }
- }
-
- dev_dbg(dev,
- "slot %u ns, rate %llu, burst %llu -> unit %d, cir %u, cbs %u, ebs %u\n",
- slot_ns, rate, burst, m.unit, m.cir, m.cbs, m.ebs);
-
- m.pkt_mode = flags & YT921X_MARKER_PKT_MODE;
- *marker = m;
- return 0;
-}
-
-static int
-yt921x_marker_tfm_police(struct yt921x_marker *marker,
- const struct flow_action_police *police,
- unsigned int flags, struct yt921x_priv *priv, int port,
- struct netlink_ext_ack *extack)
-{
- bool pkt_mode = !!police->rate_pkt_ps;
- u64 burst;
- u64 rate;
-
- rate = pkt_mode ? police->rate_pkt_ps : police->rate_bytes_ps;
- burst = pkt_mode ? police->burst_pkt : police->burst;
- if (pkt_mode)
- flags |= YT921X_MARKER_PKT_MODE;
-
- return yt921x_marker_tfm(marker, rate, burst, flags,
- priv->meter_slot_ns, YT921X_METER_CIR_MAX,
- YT921X_METER_CBS_MAX, YT921X_METER_UNIT_MAX,
- priv, port, extack);
-}
-
-static int
-yt921x_marker_tfm_shape(struct yt921x_marker *marker, u64 rate, u64 burst,
- unsigned int flags, struct yt921x_priv *priv, int port,
- struct netlink_ext_ack *extack)
-{
- return yt921x_marker_tfm(marker, rate, burst, flags,
- priv->port_shape_slot_ns, YT921X_SHAPE_CIR_MAX,
- YT921X_SHAPE_CBS_MAX, YT921X_SHAPE_UNIT_MAX,
- priv, port, extack);
-}
-
-static int
-yt921x_police_validate(const struct flow_action_police *police,
- const struct flow_action *action,
- const struct flow_action_entry *act,
- struct netlink_ext_ack *extack)
-{
- if (police->exceed.act_id != FLOW_ACTION_DROP) {
- NL_SET_ERR_MSG_MOD(extack,
- "Offload not supported when exceed action is not drop");
- return -EOPNOTSUPP;
- }
-
- if (police->notexceed.act_id != FLOW_ACTION_PIPE &&
- police->notexceed.act_id != FLOW_ACTION_ACCEPT) {
- NL_SET_ERR_MSG_MOD(extack,
- "Offload not supported when conform action is not pipe or ok");
- return -EOPNOTSUPP;
- }
-
- if (police->notexceed.act_id == FLOW_ACTION_ACCEPT && action && act &&
- !flow_action_is_last_entry(action, act)) {
- NL_SET_ERR_MSG_MOD(extack,
- "Offload not supported when conform action is ok, but action is not last");
- return -EOPNOTSUPP;
- }
-
- /* mtu defaults to unlimited but we got 2040 here, don't know why */
- if (police->peakrate_bytes_ps || police->avrate || police->overhead) {
- NL_SET_ERR_MSG_MOD(extack,
- "Offload not supported when peakrate/avrate/overhead is configured");
- return -EOPNOTSUPP;
- }
-
- return 0;
-}
-
-static int
-yt921x_meter_config(struct yt921x_priv *priv, unsigned int id,
- const struct yt921x_marker *marker)
-{
- u32 ctrls[3];
-
- ctrls[0] = 0;
- ctrls[1] = YT921X_METER_CTRLb_CIR(marker->cir);
- ctrls[2] = YT921X_METER_CTRLc_UNIT(marker->unit) |
- YT921X_METER_CTRLc_DROP_R |
- YT921X_METER_CTRLc_TOKEN_OVERFLOW_EN |
- YT921X_METER_CTRLc_METER_EN;
- if (marker->pkt_mode)
- ctrls[2] |= YT921X_METER_CTRLc_PKT_MODE;
- update_ctrls_unaligned(&ctrls[0], &ctrls[1],
- YT921X_METER_CTRLab_EBS_M,
- YT921X_METER_CTRLab_EBS(marker->ebs));
- update_ctrls_unaligned(&ctrls[1], &ctrls[2],
- YT921X_METER_CTRLbc_CBS_M,
- YT921X_METER_CTRLbc_CBS(marker->cbs));
-
- return yt921x_reg96_write(priv, YT921X_METERn_CTRL(id), ctrls);
-}
-
-static void yt921x_dsa_port_policer_del(struct dsa_switch *ds, int port)
-{
- struct yt921x_priv *priv = to_yt921x_priv(ds);
- struct device *dev = to_device(priv);
- int res;
-
- mutex_lock(&priv->reg_lock);
- res = yt921x_reg_write(priv, YT921X_PORTn_METER(port), 0);
- mutex_unlock(&priv->reg_lock);
-
- if (res)
- dev_err(dev, "Failed to %s port %d: %i\n", "delete policer on",
- port, res);
-}
-
-static int
-yt921x_dsa_port_policer_add(struct dsa_switch *ds, int port,
- const struct flow_action_police *police,
- struct netlink_ext_ack *extack)
-{
- struct yt921x_priv *priv = to_yt921x_priv(ds);
- struct yt921x_marker marker;
- u32 ctrl;
- int res;
-
- res = yt921x_police_validate(police, NULL, NULL, extack);
- if (res)
- return res;
-
- res = yt921x_marker_tfm_police(&marker, police, 0, priv, port, extack);
- if (res)
- return res;
-
- mutex_lock(&priv->reg_lock);
- res = yt921x_meter_config(priv, port + YT921X_METER_NUM, &marker);
- if (res)
- goto end;
-
- ctrl = YT921X_PORT_METER_ID(port) | YT921X_PORT_METER_EN;
- res = yt921x_reg_write(priv, YT921X_PORTn_METER(port), ctrl);
-end:
- mutex_unlock(&priv->reg_lock);
-
- return res;
-}
-
-static int
-yt921x_dsa_port_setup_tc_tbf_port(struct dsa_switch *ds, int port,
- const struct tc_tbf_qopt_offload *qopt)
-{
- struct yt921x_priv *priv = to_yt921x_priv(ds);
- struct netlink_ext_ack *extack = qopt->extack;
- u32 ctrls[2];
- int res;
-
- if (qopt->parent != TC_H_ROOT)
- return -EOPNOTSUPP;
-
- switch (qopt->command) {
- case TC_TBF_STATS:
- /* Unfortunately the convention for TC_*_STATS is a mess,
- * neither 0 nor -EOPNOTSUPP is perfect here.
- */
- return -EOPNOTSUPP;
- case TC_TBF_DESTROY:
- ctrls[0] = 0;
- ctrls[1] = 0;
- break;
- case TC_TBF_REPLACE: {
- const struct tc_tbf_qopt_offload_replace_params *p;
- struct yt921x_marker marker;
-
- p = &qopt->replace_params;
-
- res = yt921x_marker_tfm_shape(&marker, p->rate.rate_bytes_ps,
- p->max_size,
- YT921X_MARKER_SINGLE_BUCKET,
- priv, port, extack);
- if (res)
- return res;
-
- ctrls[0] = YT921X_PORT_SHAPE_CTRLa_CIR(marker.cir) |
- YT921X_PORT_SHAPE_CTRLa_CBS(marker.cbs);
- ctrls[1] = YT921X_PORT_SHAPE_CTRLb_UNIT(marker.unit) |
- YT921X_PORT_SHAPE_CTRLb_EN;
- break;
- }
- default:
- return -EOPNOTSUPP;
- }
-
- mutex_lock(&priv->reg_lock);
- res = yt921x_reg64_write(priv, YT921X_PORTn_SHAPE_CTRL(port), ctrls);
- mutex_unlock(&priv->reg_lock);
-
- return res;
-}
-
-static int
-yt921x_dsa_port_setup_tc(struct dsa_switch *ds, int port,
- enum tc_setup_type type, void *type_data)
-{
- switch (type) {
- case TC_SETUP_QDISC_TBF: {
- const struct tc_tbf_qopt_offload *qopt = type_data;
-
- return yt921x_dsa_port_setup_tc_tbf_port(ds, port, qopt);
- }
- default:
- return -EOPNOTSUPP;
- }
-}
-
-/* ACL: 48 blocks * 8 entries
- *
- * One rule can span multiple entries, but within a block.
- */
-
-static void
-yt921x_acl_entry_set(struct yt921x_acl_entry *entry, unsigned int offset,
- u32 flags, bool set)
-{
- if (set)
- entry->key[offset] |= flags;
- entry->mask[offset] |= flags;
-}
-
-static unsigned int
-yt921x_acl_entries_set_is_fragment(struct yt921x_acl_entry *entries,
- unsigned int size, bool set)
-{
- for (unsigned int i = 0; i < size; i++)
- switch (FIELD_GET(YT921X_ACL_KEYb_TYPE_M, entries[i].key[1])) {
- case YT921X_ACL_TYPE_IPV4_DA:
- case YT921X_ACL_TYPE_IPV4_SA:
- yt921x_acl_entry_set(&entries[i], 1,
- YT921X_ACL_BINb_IPV4_FRAG, set);
- return size;
- case YT921X_ACL_TYPE_IPV6_DA3:
- case YT921X_ACL_TYPE_IPV6_SA3:
- yt921x_acl_entry_set(&entries[i], 1,
- YT921X_ACL_BINb_IPV6_xA3_FRAG,
- set);
- return size;
- case YT921X_ACL_TYPE_MISC:
- yt921x_acl_entry_set(&entries[i], 1,
- YT921X_ACL_BINb_MISC_FRAG, set);
- return size;
- case YT921X_ACL_TYPE_L4:
- yt921x_acl_entry_set(&entries[i], 1,
- YT921X_ACL_BINb_L4_FRAG, set);
- return size;
- }
-
- if (size >= YT921X_ACL_ENT_PER_BLK)
- return 0;
-
- entries[size] = (typeof(*entries)){};
- entries[size].key[1] = YT921X_ACL_KEYb_TYPE(YT921X_ACL_TYPE_MISC);
- yt921x_acl_entry_set(&entries[size], 1, YT921X_ACL_BINb_MISC_FRAG, set);
-
- return size + 1;
-}
-
-static unsigned int
-yt921x_acl_entries_set_first_frag(struct yt921x_acl_entry *entries,
- unsigned int size, bool set)
-{
- for (unsigned int i = 0; i < size; i++)
- switch (FIELD_GET(YT921X_ACL_KEYb_TYPE_M, entries[i].key[1])) {
- case YT921X_ACL_TYPE_IPV6_DA2:
- case YT921X_ACL_TYPE_IPV6_SA2:
- yt921x_acl_entry_set(&entries[i], 1,
- YT921X_ACL_BINb_IPV6_xA2_FIRST_FRAG,
- set);
- return size;
- case YT921X_ACL_TYPE_MISC:
- yt921x_acl_entry_set(&entries[i], 0,
- YT921X_ACL_BINa_MISC_FIRST_FRAG,
- set);
- return size;
- }
-
- if (size >= YT921X_ACL_ENT_PER_BLK)
- return 0;
-
- entries[size] = (typeof(*entries)){};
- entries[size].key[1] = YT921X_ACL_KEYb_TYPE(YT921X_ACL_TYPE_MISC);
- yt921x_acl_entry_set(&entries[size], 0,
- YT921X_ACL_BINa_MISC_FIRST_FRAG, set);
-
- return size + 1;
-}
-
-static unsigned int
-yt921x_acl_entries_set_l3_type(struct yt921x_acl_entry *entries,
- unsigned int size, enum yt921x_l3_type type)
-{
- for (unsigned int i = 0; i < size; i++)
- switch (FIELD_GET(YT921X_ACL_KEYb_TYPE_M, entries[i].key[1])) {
- case YT921X_ACL_TYPE_MAC_DA0:
- case YT921X_ACL_TYPE_MAC_SA0:
- entries[i].key[1] |= YT921X_ACL_BINb_MAC_xA0_L3_TYPE(type);
- entries[i].mask[1] |= YT921X_ACL_BINb_MAC_xA0_L3_TYPE_M;
- return size;
- case YT921X_ACL_TYPE_MISC:
- entries[i].key[0] |= YT921X_ACL_BINa_MISC_L3_TYPE(type);
- entries[i].mask[0] |= YT921X_ACL_BINa_MISC_L3_TYPE_M;
- return size;
- }
-
- if (size >= YT921X_ACL_ENT_PER_BLK)
- return 0;
-
- entries[size] = (typeof(*entries)){};
- entries[size].key[0] = YT921X_ACL_BINa_MISC_L3_TYPE(type);
- entries[size].key[1] = YT921X_ACL_KEYb_TYPE(YT921X_ACL_TYPE_MISC);
- entries[size].mask[0] = YT921X_ACL_BINa_MISC_L3_TYPE_M;
-
- return size + 1;
-}
-
-static unsigned int
-yt921x_acl_entries_set_l4_type(struct yt921x_acl_entry *entries,
- unsigned int size, enum yt921x_l4_type type)
-{
- for (unsigned int i = 0; i < size; i++)
- switch (FIELD_GET(YT921X_ACL_KEYb_TYPE_M, entries[i].key[1])) {
- case YT921X_ACL_TYPE_IPV4_DA:
- case YT921X_ACL_TYPE_IPV4_SA:
- entries[i].key[1] |= YT921X_ACL_BINb_IPV4_L4_TYPE(type);
- entries[i].mask[1] |= YT921X_ACL_BINb_IPV4_L4_TYPE_M;
- return size;
- case YT921X_ACL_TYPE_IPV6_DA0:
- case YT921X_ACL_TYPE_IPV6_DA1:
- case YT921X_ACL_TYPE_IPV6_DA2:
- case YT921X_ACL_TYPE_IPV6_DA3:
- case YT921X_ACL_TYPE_IPV6_SA0:
- case YT921X_ACL_TYPE_IPV6_SA1:
- case YT921X_ACL_TYPE_IPV6_SA2:
- case YT921X_ACL_TYPE_IPV6_SA3:
- entries[i].key[1] |= YT921X_ACL_BINb_IPV6_L4_TYPE(type);
- entries[i].mask[1] |= YT921X_ACL_BINb_IPV6_L4_TYPE_M;
- return size;
- case YT921X_ACL_TYPE_L4:
- entries[i].key[1] |= YT921X_ACL_BINb_L4_TYPE(type);
- entries[i].mask[1] |= YT921X_ACL_BINb_L4_TYPE_M;
- return size;
- case YT921X_ACL_TYPE_MISC:
- entries[i].key[1] |= YT921X_ACL_BINb_MISC_L4_TYPE(type);
- entries[i].mask[1] |= YT921X_ACL_BINb_MISC_L4_TYPE_M;
- return size;
- }
-
- if (size >= YT921X_ACL_ENT_PER_BLK)
- return 0;
-
- entries[size] = (typeof(*entries)){};
- entries[size].key[1] = YT921X_ACL_BINb_MISC_L4_TYPE(type) |
- YT921X_ACL_KEYb_TYPE(YT921X_ACL_TYPE_MISC);
- entries[size].mask[1] = YT921X_ACL_BINb_MISC_L4_TYPE_M;
-
- return size + 1;
-}
-
-static struct yt921x_acl_entry *
-yt921x_acl_entries_new(struct yt921x_acl_entry *entries, unsigned int *sizep,
- u32 type)
-{
- unsigned int size = *sizep;
-
- if (size >= YT921X_ACL_ENT_PER_BLK)
- return NULL;
-
- entries[size] = (typeof(*entries)){};
- entries[size].key[1] = YT921X_ACL_KEYb_TYPE(type);
-
- (*sizep)++;
- return &entries[size];
-}
-
-static struct yt921x_acl_entry *
-yt921x_acl_entries_find(struct yt921x_acl_entry *entries, unsigned int *sizep,
- u32 type)
-{
- for (unsigned int i = 0; i < *sizep; i++)
- if (FIELD_GET(YT921X_ACL_KEYb_TYPE_M, entries[i].key[1]) ==
- type)
- return &entries[i];
- return yt921x_acl_entries_new(entries, sizep, type);
-}
-
-static void
-yt921x_acl_rule_set_ports(struct yt921x_acl_rule *aclrule, u16 ord,
- u16 ports_mask)
-{
- struct yt921x_acl_entry *entries = aclrule->entries;
-
- for (unsigned int i = 0; i < hweight8(aclrule->mask); i++) {
- entries[i].key[1] |= YT921X_ACL_KEYb_SPORTS(ports_mask) |
- YT921X_ACL_KEYb_ORD(ord);
- }
-}
-
-struct yt921x_acl_rule_ext {
- struct yt921x_acl_rule r;
-
- struct yt921x_marker marker;
-};
-
-static int
-yt921x_acl_rule_ext_parse_flow_entries(struct yt921x_acl_rule_ext *ruleext,
- const struct flow_cls_offload *cls)
-{
- const struct flow_rule *rule = flow_cls_offload_flow_rule(cls);
- struct yt921x_acl_entry *entries = ruleext->r.entries;
- struct netlink_ext_ack *extack = cls->common.extack;
- const struct flow_dissector *dissector;
- struct yt921x_acl_entry *entry;
- unsigned int size = 0;
- bool use_dport;
- bool use_sport;
-
- /* Incomplete and probably won't, since it supports custom u32 filters.
- * New adapters are welcome.
- */
- dissector = rule->match.dissector;
- if (dissector->used_keys &
- ~(BIT_ULL(FLOW_DISSECTOR_KEY_CONTROL) |
- BIT_ULL(FLOW_DISSECTOR_KEY_BASIC) |
- BIT_ULL(FLOW_DISSECTOR_KEY_IPV4_ADDRS) |
- BIT_ULL(FLOW_DISSECTOR_KEY_IPV6_ADDRS) |
- BIT_ULL(FLOW_DISSECTOR_KEY_PORTS) |
- BIT_ULL(FLOW_DISSECTOR_KEY_PORTS_RANGE) |
- BIT_ULL(FLOW_DISSECTOR_KEY_ETH_ADDRS) |
- BIT_ULL(FLOW_DISSECTOR_KEY_IP) |
- BIT_ULL(FLOW_DISSECTOR_KEY_TCP))) {
- NL_SET_ERR_MSG_MOD(extack, "Unsupported keys used");
- return -EOPNOTSUPP;
- }
-
- /* Entries */
- if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_IPV4_ADDRS)) {
- struct flow_match_ipv4_addrs match;
-
- flow_rule_match_ipv4_addrs(rule, &match);
-
- if (match.mask->dst) {
- entry = yt921x_acl_entries_new(entries, &size,
- YT921X_ACL_TYPE_IPV4_DA);
- if (!entry)
- goto err;
-
- entry->key[0] |= ntohl(match.key->dst);
- entry->mask[0] |= ntohl(match.mask->dst);
- }
-
- if (match.mask->src) {
- entry = yt921x_acl_entries_new(entries, &size,
- YT921X_ACL_TYPE_IPV4_SA);
- if (!entry)
- goto err;
-
- entry->key[0] |= ntohl(match.key->src);
- entry->mask[0] |= ntohl(match.mask->src);
- }
- }
-
- if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_IPV6_ADDRS)) {
- struct flow_match_ipv6_addrs match;
-
- flow_rule_match_ipv6_addrs(rule, &match);
-
- for (unsigned int i = 0; i < 4; i++) {
- if (!match.mask->dst.s6_addr32[i])
- continue;
-
- entry = yt921x_acl_entries_new(entries, &size,
- YT921X_ACL_TYPE_IPV6_DA0 + i);
- if (!entry)
- goto err;
-
- entry->key[0] |= ntohl(match.key->dst.s6_addr32[i]);
- entry->mask[0] |= ntohl(match.mask->dst.s6_addr32[i]);
- }
-
- for (unsigned int i = 0; i < 4; i++) {
- if (!match.mask->src.s6_addr32[i])
- continue;
-
- entry = yt921x_acl_entries_new(entries, &size,
- YT921X_ACL_TYPE_IPV6_SA0 + i);
- if (!entry)
- goto err;
-
- entry->key[0] |= ntohl(match.key->src.s6_addr32[i]);
- entry->mask[0] |= ntohl(match.mask->src.s6_addr32[i]);
- }
- }
-
- use_dport = false;
- use_sport = false;
- if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_PORTS)) {
- struct flow_match_ports match;
-
- entry = yt921x_acl_entries_new(entries, &size,
- YT921X_ACL_TYPE_L4);
- if (!entry)
- goto err;
-
- flow_rule_match_ports(rule, &match);
-
- use_dport = !!match.mask->dst;
- use_sport = !!match.mask->src;
-
- entry->key[0] |= (ntohs(match.key->dst) << 16) |
- ntohs(match.key->src);
- entry->mask[0] |= (ntohs(match.mask->dst) << 16) |
- ntohs(match.mask->src);
- }
-
- if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_PORTS_RANGE)) {
- struct flow_match_ports_range match;
-
- entry = yt921x_acl_entries_find(entries, &size,
- YT921X_ACL_TYPE_L4);
- if (!entry)
- goto err;
-
- flow_rule_match_ports_range(rule, &match);
-
- if ((use_dport && match.mask->tp.dst) ||
- (use_sport && match.mask->tp.src)) {
- NL_SET_ERR_MSG_MOD(extack,
- "Port mask and range are mutually exclusive");
- return -EINVAL;
- }
-
- if (match.mask->tp.dst) {
- entry->key[0] |= ntohs(match.key->tp_min.dst) << 16;
- entry->key[1] |= YT921X_ACL_KEYb_L4_DPORT_RANGE_EN;
- entry->mask[0] |= ntohs(match.key->tp_max.dst) << 16;
- }
-
- if (match.mask->tp.src) {
- entry->key[0] |= ntohs(match.key->tp_min.src);
- entry->key[1] |= YT921X_ACL_KEYb_L4_SPORT_RANGE_EN;
- entry->mask[0] |= ntohs(match.key->tp_max.src);
- }
- }
-
- if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_ETH_ADDRS)) {
- struct flow_match_eth_addrs match;
- u32 mask;
-
- flow_rule_match_eth_addrs(rule, &match);
-
- mask = ethaddr_hi4_to_u32(match.mask->dst);
- if (mask) {
- entry = yt921x_acl_entries_new(entries, &size,
- YT921X_ACL_TYPE_MAC_DA0);
- if (!entry)
- goto err;
-
- entry->key[0] |= ethaddr_hi4_to_u32(match.key->dst);
- entry->mask[0] |= mask;
- }
-
- mask = ethaddr_hi4_to_u32(match.mask->src);
- if (mask) {
- entry = yt921x_acl_entries_new(entries, &size,
- YT921X_ACL_TYPE_MAC_SA0);
- if (!entry)
- goto err;
-
- entry->key[0] |= ethaddr_hi4_to_u32(match.key->src);
- entry->mask[0] |= mask;
- }
-
- mask = (ethaddr_lo2_to_u32(match.mask->dst) << 16) |
- ethaddr_lo2_to_u32(match.mask->src);
- if (mask) {
- entry = yt921x_acl_entries_new(entries, &size,
- YT921X_ACL_TYPE_MAC_DA1_SA1);
- if (!entry)
- goto err;
-
- entry->key[0] |= (ethaddr_lo2_to_u32(match.key->dst) << 16) |
- ethaddr_lo2_to_u32(match.key->src);
- entry->mask[0] |= mask;
- }
- }
-
- /* Entries + Misc */
- if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_BASIC)) {
- struct flow_match_basic match;
-
- flow_rule_match_basic(rule, &match);
-
- if (match.mask->n_proto) {
- enum yt921x_l3_type l3type = YT921X_L3_TYPE_OTHER;
-
- if (match.mask->n_proto == htons(~0))
- switch (match.key->n_proto) {
- case htons(ETH_P_IP):
- l3type = YT921X_L3_TYPE_IPV4;
- break;
- case htons(ETH_P_IPV6):
- l3type = YT921X_L3_TYPE_IPV6;
- break;
- case htons(ETH_P_ARP):
- l3type = YT921X_L3_TYPE_ARP;
- break;
- case htons(ETH_P_LLDP):
- l3type = YT921X_L3_TYPE_LLDP;
- break;
- case htons(ETH_P_PAE):
- l3type = YT921X_L3_TYPE_PAE;
- break;
- case htons(ETH_P_CFM):
- l3type = YT921X_L3_TYPE_ERP;
- break;
- }
-
- if (l3type != YT921X_L3_TYPE_OTHER) {
- size = yt921x_acl_entries_set_l3_type(entries,
- size,
- l3type);
- if (!size)
- goto err;
- } else {
- entry = yt921x_acl_entries_new(entries, &size,
- YT921X_ACL_TYPE_ETHERTYPE);
- if (!entry)
- goto err;
-
- entry->key[0] |= ntohs(match.key->n_proto);
- entry->mask[0] |= ntohs(match.mask->n_proto);
- }
- }
-
- if (match.mask->ip_proto) {
- enum yt921x_l4_type l4type = YT921X_L4_TYPE_OTHER;
-
- if (match.mask->ip_proto == (u8)~0)
- switch (match.key->ip_proto) {
- case IPPROTO_TCP:
- l4type = YT921X_L4_TYPE_TCP;
- break;
- case IPPROTO_UDP:
- l4type = YT921X_L4_TYPE_UDP;
- break;
- case IPPROTO_UDPLITE:
- l4type = YT921X_L4_TYPE_UDPLITE;
- break;
- case IPPROTO_ICMP:
- l4type = YT921X_L4_TYPE_ICMP;
- break;
- case IPPROTO_IGMP:
- l4type = YT921X_L4_TYPE_IGMP;
- break;
- }
-
- if (l4type != YT921X_L4_TYPE_OTHER) {
- size = yt921x_acl_entries_set_l4_type(entries,
- size,
- l4type);
- if (!size)
- goto err;
- } else {
- entry = yt921x_acl_entries_find(entries, &size,
- YT921X_ACL_TYPE_MISC);
- if (!entry)
- goto err;
-
- entry->key[0] |= YT921X_ACL_BINa_MISC_IP_PROTO(match.key->ip_proto);
- entry->mask[0] |= YT921X_ACL_BINa_MISC_IP_PROTO(match.mask->ip_proto);
- }
- }
- }
-
- if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_CONTROL)) {
- u32 supp_flags = FLOW_DIS_IS_FRAGMENT | FLOW_DIS_FIRST_FRAG;
- struct flow_match_control match;
-
- flow_rule_match_control(rule, &match);
- if (!flow_rule_is_supp_control_flags(supp_flags,
- match.mask->flags, extack))
- return -EOPNOTSUPP;
-
- if (match.mask->flags & FLOW_DIS_IS_FRAGMENT) {
- bool set = match.key->flags & FLOW_DIS_IS_FRAGMENT;
-
- size = yt921x_acl_entries_set_is_fragment(entries, size,
- set);
- if (!size)
- goto err;
- }
- if (match.mask->flags & FLOW_DIS_FIRST_FRAG) {
- bool set = match.key->flags & FLOW_DIS_FIRST_FRAG;
-
- size = yt921x_acl_entries_set_first_frag(entries, size,
- set);
- if (!size)
- goto err;
- }
- }
-
- /* Misc only */
- if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_IP)) {
- struct flow_match_ip match;
-
- flow_rule_match_ip(rule, &match);
- if (match.mask->ttl) {
- NL_SET_ERR_MSG_MOD(extack,
- "Matching on TTL not supported");
- return -EOPNOTSUPP;
- }
-
- if (match.mask->tos) {
- entry = yt921x_acl_entries_find(entries, &size,
- YT921X_ACL_TYPE_MISC);
- if (!entry)
- goto err;
-
- entry->key[0] |= YT921X_ACL_BINa_MISC_TOS(match.key->tos);
- entry->mask[0] |= YT921X_ACL_BINa_MISC_TOS(match.mask->tos);
- }
- }
-
- if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_TCP)) {
- struct flow_match_tcp match;
-
- flow_rule_match_tcp(rule, &match);
- if (match.mask->flags & htons(~0xff)) {
- NL_SET_ERR_MSG_MOD(extack, "Unsupported TCP flags");
- return -EOPNOTSUPP;
- }
-
- if (match.mask->flags) {
- entry = yt921x_acl_entries_find(entries, &size,
- YT921X_ACL_TYPE_MISC);
- if (!entry)
- goto err;
-
- entry->key[0] |= YT921X_ACL_BINa_MISC_TCP_FLAGS(ntohs(match.key->flags));
- entry->mask[0] |= YT921X_ACL_BINa_MISC_TCP_FLAGS(ntohs(match.mask->flags));
- }
- }
-
- if (!size) {
- NL_SET_ERR_MSG_MOD(extack, "Empty rule generated, this should not happen");
- return -EOPNOTSUPP;
- }
-
- ruleext->r.mask = (1 << size) - 1;
- return 0;
-
-err:
- NL_SET_ERR_MSG_MOD(extack, "Rule too complex");
- return -EOPNOTSUPP;
-}
-
-static int
-yt921x_acl_rule_ext_parse_flow_action(struct yt921x_acl_rule_ext *ruleext,
- const struct flow_cls_offload *cls,
- struct yt921x_priv *priv, int port)
-{
- const struct flow_rule *rule = flow_cls_offload_flow_rule(cls);
- const struct flow_action *flow_action = &rule->action;
- struct netlink_ext_ack *extack = cls->common.extack;
- enum flow_action_id redir_act = NUM_FLOW_ACTIONS;
- const struct flow_action_entry *act;
- u32 *action = ruleext->r.action;
- bool seen_priority = false;
- const char *reason = NULL;
- bool seen_police = false;
- unsigned int i;
- int res;
-
- memset(action, 0, 3 * sizeof(*action));
- flow_action_for_each(i, act, flow_action)
- switch (act->id) {
- case FLOW_ACTION_ACCEPT:
- case FLOW_ACTION_DROP:
- case FLOW_ACTION_REDIRECT:
- if (redir_act != NUM_FLOW_ACTIONS &&
- redir_act != act->id) {
- reason = "Different redirect actions";
- goto fallback;
- }
- redir_act = act->id;
-
- switch (act->id) {
- case FLOW_ACTION_ACCEPT:
- action[2] |= YT921X_ACL_ACTc_FWD_EN |
- YT921X_ACL_ACTc_FWD_FWD;
- break;
- case FLOW_ACTION_DROP:
- action[2] |= YT921X_ACL_ACTc_FWD_EN |
- YT921X_ACL_ACTc_FWD_REDIR;
- break;
- case FLOW_ACTION_REDIRECT: {
- struct dsa_port *to_dp;
-
- to_dp = dsa_port_from_netdev(act->dev);
- if (IS_ERR(to_dp) || to_dp->ds != &priv->ds) {
- reason = "Redirect to non-local port";
- goto fallback;
- }
-
- action[2] |= YT921X_ACL_ACTc_FWD_EN |
- YT921X_ACL_ACTc_FWD_REDIR |
- YT921X_ACL_ACTc_FWD_REDIR_DPORTn(to_dp->index);
- break;
- }
- default:
- break;
- }
- break;
- case FLOW_ACTION_PRIORITY:
- if (seen_priority) {
- action[0] &= ~YT921X_ACL_ACTa_PRIO_EN;
- action[1] &= ~YT921X_ACL_ACTb_PRIO_M;
-
- reason = "Multiple priority actions";
- goto fallback;
- }
- seen_priority = true;
-
- if (act->priority >= YT921X_PRIO_NUM) {
- NL_SET_ERR_MSG_MOD(extack,
- "Priority value is too high");
- return -EOPNOTSUPP;
- }
- action[0] |= YT921X_ACL_ACTa_PRIO_EN;
- action[1] |= YT921X_ACL_ACTb_PRIO(act->priority);
- break;
- case FLOW_ACTION_POLICE: {
- const struct flow_action_police *police = &act->police;
-
- if (seen_police) {
- action[0] &= ~YT921X_ACL_ACTa_METER_EN;
-
- reason = "Multiple police actions";
- goto fallback;
- }
- seen_police = true;
-
- res = yt921x_police_validate(police, flow_action, act,
- extack);
- if (res)
- return res;
-
- res = yt921x_marker_tfm_police(&ruleext->marker, police,
- 0, priv, port, extack);
- if (res)
- return res;
-
- action[0] |= YT921X_ACL_ACTa_METER_EN;
- break;
- }
- default:
-fallback:
- if (cls->common.skip_sw) {
- NL_SET_ERR_MSG_FMT_MOD(extack,
- "Action not supported when skip_sw: %s",
- reason);
- return -EOPNOTSUPP;
- }
- fallthrough;
- case FLOW_ACTION_TRAP:
- redir_act = FLOW_ACTION_TRAP;
-
- action[2] &= ~YT921X_ACL_ACTc_FWD_REDIR_DPORTS_M &
- ~YT921X_ACL_ACTc_FWD_M;
- action[2] |= YT921X_ACL_ACTc_FWD_EN |
- YT921X_ACL_ACTc_FWD_TRAP;
- break;
- }
-
- ruleext->r.sw_assisted = !cls->common.skip_sw;
- return 0;
-}
-
-static int
-yt921x_acl_rule_ext_parse_flow(struct yt921x_acl_rule_ext *ruleext, int port,
- const struct flow_cls_offload *cls, bool ingress,
- struct yt921x_priv *priv)
-{
- struct netlink_ext_ack *extack = cls->common.extack;
- int res;
-
- if (!ingress) {
- NL_SET_ERR_MSG_MOD(extack, "Only ingress is supported");
- return -EOPNOTSUPP;
- }
-
- if (cls->common.chain_index) {
- NL_SET_ERR_MSG(extack, "Only chain 0 is supported");
- return -EOPNOTSUPP;
- }
-
- res = yt921x_acl_rule_ext_parse_flow_action(ruleext, cls, priv, port);
- if (res)
- return res;
- res = yt921x_acl_rule_ext_parse_flow_entries(ruleext, cls);
- if (res)
- return res;
-
- yt921x_acl_rule_set_ports(&ruleext->r, 0, BIT(port));
- ruleext->r.tag = cls->cookie;
- ruleext->r.type = TC_SETUP_CLSFLOWER;
- return 0;
-}
-
-static unsigned int
-yt921x_acl_find(const struct yt921x_priv *priv, enum tc_setup_type type,
- unsigned long tag)
-{
- for (unsigned int blkid = 0; blkid < YT921X_ACL_BLK_NUM; blkid++) {
- const struct yt921x_acl_blk *aclblk = priv->acl_blks[blkid];
-
- if (!aclblk)
- continue;
-
- for (unsigned int i = 0; i < YT921X_ACL_ENT_PER_BLK; i++)
- if (aclblk->rules[i] && aclblk->rules[i]->tag == tag &&
- aclblk->rules[i]->type == type)
- return YT921X_ACL_ENT_PER_BLK * blkid + i;
- }
-
- return UINT_MAX;
-}
-
-static unsigned int
-yt921x_acl_reserve(struct yt921x_priv *priv, unsigned int entscnt,
- struct netlink_ext_ack *extack)
-{
- int candidates[YT921X_ACL_ENT_PER_BLK + 1];
- unsigned int acl_used_cnt = 0;
-
- if (WARN_ON(entscnt > YT921X_ACL_ENT_PER_BLK))
- return UINT_MAX;
-
- for (unsigned int i = 0; i < ARRAY_SIZE(candidates); i++)
- candidates[i] = -1;
- for (unsigned int i = YT921X_ACL_BLK_NUM; i-- > 0;) {
- unsigned int blk_used_cnt = hweight8(priv->acl_masks[i]);
-
- candidates[blk_used_cnt] = i;
- acl_used_cnt += blk_used_cnt;
- }
-
- if (acl_used_cnt >= YT921X_ACL_NUM) {
- NL_SET_ERR_MSG_MOD(extack, "ACL entry limit reached");
- return UINT_MAX;
- }
- if (acl_used_cnt + entscnt <= YT921X_ACL_NUM)
- for (unsigned int i = YT921X_ACL_ENT_PER_BLK - entscnt + 1;
- i-- > 0;)
- if (candidates[i] >= 0)
- return YT921X_ACL_ENT_PER_BLK * candidates[i] +
- ffz(priv->acl_masks[candidates[i]]);
-
- NL_SET_ERR_MSG_MOD(extack,
- "ACL entry allocation failed, simplify your rules or remove existing rules");
- return UINT_MAX;
-}
-
-static int
-yt921x_acl_commit(struct yt921x_priv *priv, unsigned int entid, u8 entsmask)
-{
- const struct yt921x_acl_rule *aclrule;
- const struct yt921x_acl_blk *aclblk;
- unsigned int blkid;
- unsigned int binid;
- unsigned long mask;
- u32 zeros[3] = {};
- unsigned int i;
- unsigned int o;
- u32 ctrl;
- int res;
-
- blkid = entid / YT921X_ACL_ENT_PER_BLK;
- binid = entid % YT921X_ACL_ENT_PER_BLK;
- aclblk = priv->acl_blks[blkid];
- aclrule = aclblk->rules[binid];
-
- /* Write actions */
- res = yt921x_reg96_write(priv, YT921X_ACLn_ACT(entid),
- aclrule ? aclrule->action : zeros);
- if (res)
- return res;
-
- /* Select the block */
- ctrl = YT921X_ACL_BLK_CMD_MODIFY | YT921X_ACL_BLK_CMD_BLKID(blkid);
- res = yt921x_reg_write(priv, YT921X_ACL_BLK_CMD, ctrl);
- if (res)
- return res;
-
- /* Write keys and masks */
- ctrl = 0;
- for (unsigned int i = 0; i < YT921X_ACL_ENT_PER_BLK; i++)
- ctrl |= YT921X_ACL_BLK_KEEP_KEEPn(i);
-
- mask = entsmask;
- i = 0;
- for_each_set_bit(o, &mask, YT921X_ACL_ENT_PER_BLK) {
- res = yt921x_reg64_write(priv, YT921X_ACLn_KEYm(blkid, o),
- aclrule ? aclrule->entries[i].key :
- zeros);
- if (res)
- return res;
-
- res = yt921x_reg64_write(priv, YT921X_ACLn_MASKm(blkid, o),
- aclrule ? aclrule->entries[i].mask :
- zeros);
- if (res)
- return res;
-
- ctrl &= ~YT921X_ACL_BLK_KEEP_KEEPn(o);
- i++;
- }
-
- res = yt921x_reg_write(priv, YT921X_ACL_BLK_KEEP, ctrl);
- if (res)
- return res;
-
- ctrl = 0;
- for (unsigned int i = 0; i < YT921X_ACL_ENT_PER_BLK; i++) {
- const struct yt921x_acl_rule *other = aclblk->rules[i];
-
- if (!other)
- continue;
-
- mask = other->mask;
- for_each_set_bit(o, &mask, YT921X_ACL_ENT_PER_BLK)
- ctrl |= YT921X_ACL_ENTRY_ENm(o) |
- YT921X_ACL_ENTRY_GRPIDm(o, i);
- }
- res = yt921x_reg_write(priv, YT921X_ACLn_ENTRY(blkid), ctrl);
- if (res)
- return res;
-
- /* Commit the block */
- ctrl = YT921X_ACL_BLK_CMD_BLKID(blkid);
- res = yt921x_reg_write(priv, YT921X_ACL_BLK_CMD, ctrl);
- if (res)
- return res;
-
- return 0;
-}
-
-static int
-yt921x_acl_del(struct yt921x_priv *priv, enum tc_setup_type type,
- unsigned long tag)
-{
- struct yt921x_acl_rule *aclrule;
- struct yt921x_acl_blk *aclblk;
- unsigned int binid;
- unsigned int blkid;
- unsigned int entid;
- int res;
-
- entid = yt921x_acl_find(priv, type, tag);
- if (entid == UINT_MAX)
- return -ENOENT;
-
- blkid = entid / YT921X_ACL_ENT_PER_BLK;
- binid = entid % YT921X_ACL_ENT_PER_BLK;
- aclblk = priv->acl_blks[blkid];
- aclrule = aclblk->rules[binid];
-
- aclblk->rules[binid] = NULL;
- res = yt921x_acl_commit(priv, entid, aclrule->mask);
- /* the kernel never rolls back on failure */
-
- if (aclrule->action[0] & YT921X_ACL_ACTa_METER_EN)
- clear_bit(FIELD_GET(YT921X_ACL_ACTa_METER_ID_M,
- aclrule->action[0]),
- priv->meters_map);
- priv->acl_masks[blkid] &= ~aclrule->mask;
- kvfree(aclrule);
- if (!priv->acl_masks[blkid]) {
- kvfree(aclblk);
- priv->acl_blks[blkid] = NULL;
- }
- return res;
-}
-
-static int
-yt921x_acl_add(struct yt921x_priv *priv,
- const struct yt921x_acl_rule_ext *ruleext,
- struct netlink_ext_ack *extack)
-{
- unsigned int entscnt = hweight8(ruleext->r.mask);
- struct yt921x_acl_rule *aclrule;
- struct yt921x_acl_blk *aclblk;
- bool use_trap = false;
- unsigned int meterid;
- unsigned long mask;
- unsigned int binid;
- unsigned int blkid;
- unsigned int entid;
- unsigned int o;
- int res;
-
- /* Allocate resources */
- entid = yt921x_acl_reserve(priv, entscnt, extack);
- if (entid == UINT_MAX)
- return -EOPNOTSUPP;
-
- if (!(ruleext->r.action[0] & YT921X_ACL_ACTa_METER_EN)) {
- meterid = YT921X_METER_NUM;
- } else {
- meterid = find_first_zero_bit(priv->meters_map,
- YT921X_METER_NUM);
- if (meterid < YT921X_METER_NUM) {
- res = yt921x_meter_config(priv, meterid,
- &ruleext->marker);
- if (res)
- return res;
- } else if (ruleext->r.sw_assisted) {
- use_trap = true;
- } else {
- NL_SET_ERR_MSG_MOD(extack,
- "No more meters available");
- return -EOPNOTSUPP;
- }
- }
-
- /* Prepare acl block ctrlblk */
- blkid = entid / YT921X_ACL_ENT_PER_BLK;
- binid = entid % YT921X_ACL_ENT_PER_BLK;
- aclblk = priv->acl_blks[blkid];
- if (!aclblk) {
- aclblk = kvzalloc_obj(*aclblk);
- if (!aclblk)
- return -ENOMEM;
- priv->acl_blks[blkid] = aclblk;
- }
-
- /* Prepare acl rule ctrlblk */
- aclrule = kvmemdup(&ruleext->r,
- offsetof(struct yt921x_acl_rule, entries[entscnt]),
- GFP_KERNEL);
- if (!aclrule) {
- res = -ENOMEM;
- goto err;
- }
-
- /* Replace the placeholder resource IDs */
- aclrule->mask = 0;
- mask = priv->acl_masks[blkid];
- for_each_clear_bit(o, &mask, YT921X_ACL_ENT_PER_BLK) {
- aclrule->mask |= BIT(o);
- entscnt--;
- if (!entscnt)
- break;
- }
-
- if (use_trap) {
- aclrule->action[2] &= ~YT921X_ACL_ACTc_FWD_REDIR_DPORTS_M &
- ~YT921X_ACL_ACTc_FWD_M;
- aclrule->action[2] |= YT921X_ACL_ACTc_FWD_EN |
- YT921X_ACL_ACTc_FWD_TRAP;
- }
- if (meterid < YT921X_METER_NUM)
- aclrule->action[0] |= YT921X_ACL_ACTa_METER_ID(meterid);
- else
- aclrule->action[0] &= ~YT921X_ACL_ACTa_METER_EN;
-
- /* Write rules */
- aclblk->rules[binid] = aclrule;
- res = yt921x_acl_commit(priv, entid, aclrule->mask);
- if (res) {
- aclblk->rules[binid] = NULL;
- kvfree(aclrule);
- goto err;
- }
-
- if (meterid < YT921X_METER_NUM)
- set_bit(meterid, priv->meters_map);
- priv->acl_masks[blkid] |= aclrule->mask;
- return 0;
-
-err:
- if (!priv->acl_masks[blkid]) {
- kvfree(aclblk);
- priv->acl_blks[blkid] = NULL;
- }
- return res;
-}
-
-static int
-yt921x_dsa_cls_flower_del(struct dsa_switch *ds, int port,
- struct flow_cls_offload *cls, bool ingress)
-{
- struct yt921x_priv *priv = to_yt921x_priv(ds);
- int res;
-
- mutex_lock(&priv->reg_lock);
- res = yt921x_acl_del(priv, TC_SETUP_CLSFLOWER, cls->cookie);
- mutex_unlock(&priv->reg_lock);
-
- return res;
-}
-
-static int
-yt921x_dsa_cls_flower_add(struct dsa_switch *ds, int port,
- struct flow_cls_offload *cls, bool ingress)
-{
- struct netlink_ext_ack *extack = cls->common.extack;
- struct yt921x_priv *priv = to_yt921x_priv(ds);
- struct yt921x_acl_rule_ext ruleext;
- int res;
-
- res = yt921x_acl_rule_ext_parse_flow(&ruleext, port, cls, ingress,
- priv);
- if (res)
- return res;
-
- mutex_lock(&priv->reg_lock);
- res = yt921x_acl_add(priv, &ruleext, extack);
- mutex_unlock(&priv->reg_lock);
-
- return res;
-}
-
-static int
-yt921x_mirror_del(struct yt921x_priv *priv, int port, bool ingress)
-{
- u32 mask;
-
- if (ingress)
- mask = YT921X_MIRROR_IGR_PORTn(port);
- else
- mask = YT921X_MIRROR_EGR_PORTn(port);
- return yt921x_reg_clear_bits(priv, YT921X_MIRROR, mask);
-}
-
-static int
-yt921x_mirror_add(struct yt921x_priv *priv, int port, bool ingress,
- int to_local_port, struct netlink_ext_ack *extack)
-{
- u32 srcs;
- u32 ctrl;
- u32 val;
- u32 dst;
- int res;
-
- if (ingress)
- srcs = YT921X_MIRROR_IGR_PORTn(port);
- else
- srcs = YT921X_MIRROR_EGR_PORTn(port);
- dst = YT921X_MIRROR_PORT(to_local_port);
-
- res = yt921x_reg_read(priv, YT921X_MIRROR, &val);
- if (res)
- return res;
-
- /* other mirror tasks & different dst port -> conflict */
- if ((val & ~srcs & (YT921X_MIRROR_EGR_PORTS_M |
- YT921X_MIRROR_IGR_PORTS_M)) &&
- (val & YT921X_MIRROR_PORT_M) != dst) {
- NL_SET_ERR_MSG_MOD(extack,
- "Sniffer port is already configured, delete existing rules & retry");
- return -EBUSY;
- }
-
- ctrl = val & ~YT921X_MIRROR_PORT_M;
- ctrl |= srcs;
- ctrl |= dst;
-
- if (ctrl == val)
- return 0;
-
- return yt921x_reg_write(priv, YT921X_MIRROR, ctrl);
-}
-
-static void
-yt921x_dsa_port_mirror_del(struct dsa_switch *ds, int port,
- struct dsa_mall_mirror_tc_entry *mirror)
-{
- struct yt921x_priv *priv = to_yt921x_priv(ds);
- struct device *dev = to_device(priv);
- int res;
-
- mutex_lock(&priv->reg_lock);
- res = yt921x_mirror_del(priv, port, mirror->ingress);
- mutex_unlock(&priv->reg_lock);
-
- if (res)
- dev_err(dev, "Failed to %s port %d: %i\n", "unmirror",
- port, res);
-}
-
-static int
-yt921x_dsa_port_mirror_add(struct dsa_switch *ds, int port,
- struct dsa_mall_mirror_tc_entry *mirror,
- bool ingress, struct netlink_ext_ack *extack)
-{
- struct yt921x_priv *priv = to_yt921x_priv(ds);
- int res;
-
- mutex_lock(&priv->reg_lock);
- res = yt921x_mirror_add(priv, port, ingress,
- mirror->to_local_port, extack);
- mutex_unlock(&priv->reg_lock);
-
- return res;
-}
-
-static int yt921x_lag_hash(struct yt921x_priv *priv, u32 ctrl, bool unique_lag,
- struct netlink_ext_ack *extack)
-{
- u32 val;
- int res;
-
- /* Hash Mode is global. Make sure the same Hash Mode is set to all the
- * 2 possible lags.
- * If we are the unique LAG we can set whatever hash mode we want.
- * To change hash mode it's needed to remove all LAG and change the mode
- * with the latest.
- */
- if (unique_lag) {
- res = yt921x_reg_write(priv, YT921X_LAG_HASH, ctrl);
- if (res)
- return res;
- } else {
- res = yt921x_reg_read(priv, YT921X_LAG_HASH, &val);
- if (res)
- return res;
-
- if (val != ctrl) {
- NL_SET_ERR_MSG_MOD(extack,
- "Mismatched Hash Mode across different lags is not supported");
- return -EOPNOTSUPP;
- }
- }
-
- return 0;
-}
-
-static int yt921x_lag_set(struct yt921x_priv *priv, u8 index, u16 ports_mask)
-{
- unsigned long targets_mask = ports_mask;
- unsigned int cnt;
- u32 ctrl;
- int port;
- int res;
-
- cnt = 0;
- for_each_set_bit(port, &targets_mask, YT921X_PORT_NUM) {
- ctrl = YT921X_LAG_MEMBER_PORT(port);
- res = yt921x_reg_write(priv, YT921X_LAG_MEMBERnm(index, cnt),
- ctrl);
- if (res)
- return res;
-
- cnt++;
- }
-
- ctrl = YT921X_LAG_GROUP_PORTS(ports_mask) |
- YT921X_LAG_GROUP_MEMBER_NUM(cnt);
- return yt921x_reg_write(priv, YT921X_LAG_GROUPn(index), ctrl);
-}
-
-static int
-yt921x_dsa_port_lag_leave(struct dsa_switch *ds, int port, struct dsa_lag lag)
-{
- struct yt921x_priv *priv = to_yt921x_priv(ds);
- struct dsa_port *dp;
- u32 ctrl;
- int res;
-
- if (!lag.id)
- return -EINVAL;
-
- ctrl = 0;
- dsa_lag_foreach_port(dp, ds->dst, &lag)
- ctrl |= BIT(dp->index);
-
- mutex_lock(&priv->reg_lock);
- res = yt921x_lag_set(priv, lag.id - 1, ctrl);
- mutex_unlock(&priv->reg_lock);
-
- return res;
-}
-
-static int
-yt921x_dsa_port_lag_check(struct dsa_switch *ds, struct dsa_lag lag,
- struct netdev_lag_upper_info *info,
- struct netlink_ext_ack *extack)
-{
- unsigned int members;
- struct dsa_port *dp;
-
- if (!lag.id)
- return -EINVAL;
-
- members = 0;
- dsa_lag_foreach_port(dp, ds->dst, &lag)
- /* Includes the port joining the LAG */
- members++;
-
- if (members > YT921X_LAG_PORT_NUM) {
- NL_SET_ERR_MSG_MOD(extack,
- "Cannot offload more than 4 LAG ports");
- return -EOPNOTSUPP;
- }
-
- if (info->tx_type != NETDEV_LAG_TX_TYPE_HASH) {
- NL_SET_ERR_MSG_MOD(extack,
- "Can only offload LAG using hash TX type");
- return -EOPNOTSUPP;
- }
-
- if (info->hash_type != NETDEV_LAG_HASH_L2 &&
- info->hash_type != NETDEV_LAG_HASH_L23 &&
- info->hash_type != NETDEV_LAG_HASH_L34) {
- NL_SET_ERR_MSG_MOD(extack,
- "Can only offload L2 or L2+L3 or L3+L4 TX hash");
- return -EOPNOTSUPP;
- }
-
- return 0;
-}
-
-static int
-yt921x_dsa_port_lag_join(struct dsa_switch *ds, int port, struct dsa_lag lag,
- struct netdev_lag_upper_info *info,
- struct netlink_ext_ack *extack)
-{
- struct yt921x_priv *priv = to_yt921x_priv(ds);
- struct dsa_port *dp;
- bool unique_lag;
- unsigned int i;
- u32 ctrl;
- int res;
-
- res = yt921x_dsa_port_lag_check(ds, lag, info, extack);
- if (res)
- return res;
-
- ctrl = 0;
- switch (info->hash_type) {
- case NETDEV_LAG_HASH_L34:
- ctrl |= YT921X_LAG_HASH_IP_DST;
- ctrl |= YT921X_LAG_HASH_IP_SRC;
- ctrl |= YT921X_LAG_HASH_IP_PROTO;
-
- ctrl |= YT921X_LAG_HASH_L4_DPORT;
- ctrl |= YT921X_LAG_HASH_L4_SPORT;
- break;
- case NETDEV_LAG_HASH_L23:
- ctrl |= YT921X_LAG_HASH_MAC_DA;
- ctrl |= YT921X_LAG_HASH_MAC_SA;
-
- ctrl |= YT921X_LAG_HASH_IP_DST;
- ctrl |= YT921X_LAG_HASH_IP_SRC;
- ctrl |= YT921X_LAG_HASH_IP_PROTO;
- break;
- case NETDEV_LAG_HASH_L2:
- ctrl |= YT921X_LAG_HASH_MAC_DA;
- ctrl |= YT921X_LAG_HASH_MAC_SA;
- break;
- default:
- return -EOPNOTSUPP;
- }
-
- /* Check if we are the unique configured LAG */
- unique_lag = true;
- dsa_lags_foreach_id(i, ds->dst)
- if (i != lag.id && dsa_lag_by_id(ds->dst, i)) {
- unique_lag = false;
- break;
- }
-
- mutex_lock(&priv->reg_lock);
- do {
- res = yt921x_lag_hash(priv, ctrl, unique_lag, extack);
- if (res)
- break;
-
- ctrl = 0;
- dsa_lag_foreach_port(dp, ds->dst, &lag)
- ctrl |= BIT(dp->index);
- res = yt921x_lag_set(priv, lag.id - 1, ctrl);
- } while (0);
- mutex_unlock(&priv->reg_lock);
-
- return res;
-}
-
-static int yt921x_fdb_wait(struct yt921x_priv *priv, u32 *valp)
-{
- struct device *dev = to_device(priv);
- u32 val = YT921X_FDB_RESULT_DONE;
- int res;
-
- res = yt921x_reg_wait(priv, YT921X_FDB_RESULT, YT921X_FDB_RESULT_DONE,
- &val);
- if (res) {
- dev_err(dev, "FDB probably stuck\n");
- return res;
- }
-
- *valp = val;
- return 0;
-}
-
-static int
-yt921x_fdb_in01(struct yt921x_priv *priv, const unsigned char *addr,
- u16 vid, u32 ctrl1)
-{
- u32 ctrl;
- int res;
-
- ctrl = ethaddr_hi4_to_u32(addr);
- res = yt921x_reg_write(priv, YT921X_FDB_IN0, ctrl);
- if (res)
- return res;
-
- ctrl = ctrl1 | YT921X_FDB_IO1_FID(vid) | ethaddr_lo2_to_u32(addr);
- return yt921x_reg_write(priv, YT921X_FDB_IN1, ctrl);
-}
-
-static int
-yt921x_fdb_has(struct yt921x_priv *priv, const unsigned char *addr, u16 vid,
- u16 *indexp)
-{
- u32 ctrl;
- u32 val;
- int res;
-
- res = yt921x_fdb_in01(priv, addr, vid, 0);
- if (res)
- return res;
-
- ctrl = 0;
- res = yt921x_reg_write(priv, YT921X_FDB_IN2, ctrl);
- if (res)
- return res;
-
- ctrl = YT921X_FDB_OP_OP_GET_ONE | YT921X_FDB_OP_START;
- res = yt921x_reg_write(priv, YT921X_FDB_OP, ctrl);
- if (res)
- return res;
-
- res = yt921x_fdb_wait(priv, &val);
- if (res)
- return res;
- if (val & YT921X_FDB_RESULT_NOTFOUND) {
- *indexp = YT921X_FDB_NUM;
- return 0;
- }
-
- *indexp = FIELD_GET(YT921X_FDB_RESULT_INDEX_M, val);
- return 0;
-}
-
-static int
-yt921x_fdb_read(struct yt921x_priv *priv, unsigned char *addr, u16 *vidp,
- u16 *ports_maskp, u16 *indexp, u8 *statusp)
-{
- struct device *dev = to_device(priv);
- u16 index;
- u32 data0;
- u32 data1;
- u32 data2;
- u32 val;
- int res;
-
- res = yt921x_fdb_wait(priv, &val);
- if (res)
- return res;
- if (val & YT921X_FDB_RESULT_NOTFOUND) {
- *ports_maskp = 0;
- return 0;
- }
- index = FIELD_GET(YT921X_FDB_RESULT_INDEX_M, val);
-
- res = yt921x_reg_read(priv, YT921X_FDB_OUT1, &data1);
- if (res)
- return res;
- if ((data1 & YT921X_FDB_IO1_STATUS_M) ==
- YT921X_FDB_IO1_STATUS_INVALID) {
- *ports_maskp = 0;
- return 0;
- }
-
- res = yt921x_reg_read(priv, YT921X_FDB_OUT0, &data0);
- if (res)
- return res;
- res = yt921x_reg_read(priv, YT921X_FDB_OUT2, &data2);
- if (res)
- return res;
-
- addr[0] = data0 >> 24;
- addr[1] = data0 >> 16;
- addr[2] = data0 >> 8;
- addr[3] = data0;
- addr[4] = data1 >> 8;
- addr[5] = data1;
- *vidp = FIELD_GET(YT921X_FDB_IO1_FID_M, data1);
- *indexp = index;
- *ports_maskp = FIELD_GET(YT921X_FDB_IO2_EGR_PORTS_M, data2);
- *statusp = FIELD_GET(YT921X_FDB_IO1_STATUS_M, data1);
-
- dev_dbg(dev,
- "%s: index 0x%x, mac %02x:%02x:%02x:%02x:%02x:%02x, vid %d, ports 0x%x, status %d\n",
- __func__, *indexp, addr[0], addr[1], addr[2], addr[3],
- addr[4], addr[5], *vidp, *ports_maskp, *statusp);
- return 0;
-}
-
-static int
-yt921x_fdb_dump(struct yt921x_priv *priv, u16 ports_mask,
- dsa_fdb_dump_cb_t *cb, void *data)
-{
- unsigned char addr[ETH_ALEN];
- u8 status;
- u16 pmask;
- u16 index;
- u32 ctrl;
- u16 vid;
- int res;
-
- ctrl = YT921X_FDB_OP_INDEX(0) | YT921X_FDB_OP_MODE_INDEX |
- YT921X_FDB_OP_OP_GET_ONE | YT921X_FDB_OP_START;
- res = yt921x_reg_write(priv, YT921X_FDB_OP, ctrl);
- if (res)
- return res;
- res = yt921x_fdb_read(priv, addr, &vid, &pmask, &index, &status);
- if (res)
- return res;
- if ((pmask & ports_mask) && !is_multicast_ether_addr(addr)) {
- res = cb(addr, vid,
- status == YT921X_FDB_ENTRY_STATUS_STATIC, data);
- if (res)
- return res;
- }
-
- ctrl = YT921X_FDB_IO2_EGR_PORTS(ports_mask);
- res = yt921x_reg_write(priv, YT921X_FDB_IN2, ctrl);
- if (res)
- return res;
-
- index = 0;
- do {
- ctrl = YT921X_FDB_OP_INDEX(index) | YT921X_FDB_OP_MODE_INDEX |
- YT921X_FDB_OP_NEXT_TYPE_UCAST_PORT |
- YT921X_FDB_OP_OP_GET_NEXT | YT921X_FDB_OP_START;
- res = yt921x_reg_write(priv, YT921X_FDB_OP, ctrl);
- if (res)
- return res;
-
- res = yt921x_fdb_read(priv, addr, &vid, &pmask, &index,
- &status);
- if (res)
- return res;
- if (!pmask)
- break;
-
- if ((pmask & ports_mask) && !is_multicast_ether_addr(addr)) {
- res = cb(addr, vid,
- status == YT921X_FDB_ENTRY_STATUS_STATIC,
- data);
- if (res)
- return res;
- }
-
- /* Never call GET_NEXT with 4095, otherwise it will hang
- * forever until a reset!
- */
- } while (index < YT921X_FDB_NUM - 1);
-
- return 0;
-}
-
-static int
-yt921x_fdb_flush_raw(struct yt921x_priv *priv, u16 ports_mask, u16 vid,
- bool flush_static)
-{
- u32 ctrl;
- u32 val;
- int res;
-
- if (vid < 4096) {
- ctrl = YT921X_FDB_IO1_FID(vid);
- res = yt921x_reg_write(priv, YT921X_FDB_IN1, ctrl);
- if (res)
- return res;
- }
-
- ctrl = YT921X_FDB_IO2_EGR_PORTS(ports_mask);
- res = yt921x_reg_write(priv, YT921X_FDB_IN2, ctrl);
- if (res)
- return res;
-
- ctrl = YT921X_FDB_OP_OP_FLUSH | YT921X_FDB_OP_START;
- if (vid >= 4096)
- ctrl |= YT921X_FDB_OP_FLUSH_PORT;
- else
- ctrl |= YT921X_FDB_OP_FLUSH_PORT_VID;
- if (flush_static)
- ctrl |= YT921X_FDB_OP_FLUSH_STATIC;
- res = yt921x_reg_write(priv, YT921X_FDB_OP, ctrl);
- if (res)
- return res;
-
- res = yt921x_fdb_wait(priv, &val);
- if (res)
- return res;
-
- return 0;
-}
-
-static int
-yt921x_fdb_flush_port(struct yt921x_priv *priv, int port, bool flush_static)
-{
- return yt921x_fdb_flush_raw(priv, BIT(port), 4096, flush_static);
-}
-
-static int
-yt921x_fdb_add_index_in12(struct yt921x_priv *priv, u16 index, u16 ctrl1,
- u16 ctrl2)
-{
- u32 ctrl;
- u32 val;
- int res;
-
- res = yt921x_reg_write(priv, YT921X_FDB_IN1, ctrl1);
- if (res)
- return res;
- res = yt921x_reg_write(priv, YT921X_FDB_IN2, ctrl2);
- if (res)
- return res;
-
- ctrl = YT921X_FDB_OP_INDEX(index) | YT921X_FDB_OP_MODE_INDEX |
- YT921X_FDB_OP_OP_ADD | YT921X_FDB_OP_START;
- res = yt921x_reg_write(priv, YT921X_FDB_OP, ctrl);
- if (res)
- return res;
-
- return yt921x_fdb_wait(priv, &val);
-}
-
-static int
-yt921x_fdb_add(struct yt921x_priv *priv, const unsigned char *addr, u16 vid,
- u16 ports_mask)
-{
- u32 ctrl;
- u32 val;
- int res;
-
- ctrl = YT921X_FDB_IO1_STATUS_STATIC;
- res = yt921x_fdb_in01(priv, addr, vid, ctrl);
- if (res)
- return res;
-
- ctrl = YT921X_FDB_IO2_EGR_PORTS(ports_mask);
- res = yt921x_reg_write(priv, YT921X_FDB_IN2, ctrl);
- if (res)
- return res;
-
- ctrl = YT921X_FDB_OP_OP_ADD | YT921X_FDB_OP_START;
- res = yt921x_reg_write(priv, YT921X_FDB_OP, ctrl);
- if (res)
- return res;
-
- return yt921x_fdb_wait(priv, &val);
-}
-
-static int
-yt921x_fdb_leave(struct yt921x_priv *priv, const unsigned char *addr,
- u16 vid, u16 ports_mask)
-{
- u16 index;
- u32 ctrl1;
- u32 ctrl2;
- u32 ctrl;
- u32 val2;
- u32 val;
- int res;
-
- /* Check for presence */
- res = yt921x_fdb_has(priv, addr, vid, &index);
- if (res)
- return res;
- if (index >= YT921X_FDB_NUM)
- return 0;
-
- /* Check if action required */
- res = yt921x_reg_read(priv, YT921X_FDB_OUT2, &val2);
- if (res)
- return res;
-
- ctrl2 = val2 & ~YT921X_FDB_IO2_EGR_PORTS(ports_mask);
- if (ctrl2 == val2)
- return 0;
- if (!(ctrl2 & YT921X_FDB_IO2_EGR_PORTS_M)) {
- ctrl = YT921X_FDB_OP_OP_DEL | YT921X_FDB_OP_START;
- res = yt921x_reg_write(priv, YT921X_FDB_OP, ctrl);
- if (res)
- return res;
-
- return yt921x_fdb_wait(priv, &val);
- }
-
- res = yt921x_reg_read(priv, YT921X_FDB_OUT1, &ctrl1);
- if (res)
- return res;
-
- return yt921x_fdb_add_index_in12(priv, index, ctrl1, ctrl2);
-}
-
-static int
-yt921x_fdb_join(struct yt921x_priv *priv, const unsigned char *addr, u16 vid,
- u16 ports_mask)
-{
- u16 index;
- u32 ctrl1;
- u32 ctrl2;
- u32 val1;
- u32 val2;
- int res;
-
- /* Check for presence */
- res = yt921x_fdb_has(priv, addr, vid, &index);
- if (res)
- return res;
- if (index >= YT921X_FDB_NUM)
- return yt921x_fdb_add(priv, addr, vid, ports_mask);
-
- /* Check if action required */
- res = yt921x_reg_read(priv, YT921X_FDB_OUT1, &val1);
- if (res)
- return res;
- res = yt921x_reg_read(priv, YT921X_FDB_OUT2, &val2);
- if (res)
- return res;
-
- ctrl1 = val1 & ~YT921X_FDB_IO1_STATUS_M;
- ctrl1 |= YT921X_FDB_IO1_STATUS_STATIC;
- ctrl2 = val2 | YT921X_FDB_IO2_EGR_PORTS(ports_mask);
- if (ctrl1 == val1 && ctrl2 == val2)
- return 0;
-
- return yt921x_fdb_add_index_in12(priv, index, ctrl1, ctrl2);
-}
-
-static int
-yt921x_dsa_port_fdb_dump(struct dsa_switch *ds, int port,
- dsa_fdb_dump_cb_t *cb, void *data)
-{
- struct yt921x_priv *priv = to_yt921x_priv(ds);
- int res;
-
- mutex_lock(&priv->reg_lock);
- /* Hardware FDB is shared for fdb and mdb, "bridge fdb show"
- * only wants to see unicast
- */
- res = yt921x_fdb_dump(priv, BIT(port), cb, data);
- mutex_unlock(&priv->reg_lock);
-
- return res;
-}
-
-static void yt921x_dsa_port_fast_age(struct dsa_switch *ds, int port)
-{
- struct yt921x_priv *priv = to_yt921x_priv(ds);
- struct device *dev = to_device(priv);
- int res;
-
- mutex_lock(&priv->reg_lock);
- res = yt921x_fdb_flush_port(priv, port, false);
- mutex_unlock(&priv->reg_lock);
-
- if (res)
- dev_err(dev, "Failed to %s port %d: %i\n", "clear FDB for",
- port, res);
-}
-
-static int
-yt921x_dsa_set_ageing_time(struct dsa_switch *ds, unsigned int msecs)
-{
- struct yt921x_priv *priv = to_yt921x_priv(ds);
- u32 ctrl;
- int res;
-
- /* AGEING reg is set in 5s step */
- ctrl = clamp(msecs / 5000, 1, U16_MAX);
-
- mutex_lock(&priv->reg_lock);
- res = yt921x_reg_write(priv, YT921X_AGEING, ctrl);
- mutex_unlock(&priv->reg_lock);
-
- return res;
-}
-
-static int
-yt921x_dsa_port_fdb_del(struct dsa_switch *ds, int port,
- const unsigned char *addr, u16 vid, struct dsa_db db)
-{
- struct yt921x_priv *priv = to_yt921x_priv(ds);
- int res;
-
- mutex_lock(&priv->reg_lock);
- res = yt921x_fdb_leave(priv, addr, vid, BIT(port));
- mutex_unlock(&priv->reg_lock);
-
- return res;
-}
-
-static int
-yt921x_dsa_port_fdb_add(struct dsa_switch *ds, int port,
- const unsigned char *addr, u16 vid, struct dsa_db db)
-{
- struct yt921x_priv *priv = to_yt921x_priv(ds);
- int res;
-
- mutex_lock(&priv->reg_lock);
- res = yt921x_fdb_join(priv, addr, vid, BIT(port));
- mutex_unlock(&priv->reg_lock);
-
- return res;
-}
-
-static int
-yt921x_dsa_port_mdb_del(struct dsa_switch *ds, int port,
- const struct switchdev_obj_port_mdb *mdb,
- struct dsa_db db)
-{
- struct yt921x_priv *priv = to_yt921x_priv(ds);
- const unsigned char *addr = mdb->addr;
- u16 vid = mdb->vid;
- int res;
-
- mutex_lock(&priv->reg_lock);
- res = yt921x_fdb_leave(priv, addr, vid, BIT(port));
- mutex_unlock(&priv->reg_lock);
-
- return res;
-}
-
-static int
-yt921x_dsa_port_mdb_add(struct dsa_switch *ds, int port,
- const struct switchdev_obj_port_mdb *mdb,
- struct dsa_db db)
-{
- struct yt921x_priv *priv = to_yt921x_priv(ds);
- const unsigned char *addr = mdb->addr;
- u16 vid = mdb->vid;
- int res;
-
- mutex_lock(&priv->reg_lock);
- res = yt921x_fdb_join(priv, addr, vid, BIT(port));
- mutex_unlock(&priv->reg_lock);
-
- return res;
-}
-
-static int
-yt921x_vlan_aware_set(struct yt921x_priv *priv, int port, bool vlan_aware)
-{
- u32 ctrl;
-
- /* Abuse SVLAN for PCP parsing without polluting the FDB - it just works
- * despite YT921X_VLAN_CTRL_SVLAN_EN never being set
- */
- if (!vlan_aware)
- ctrl = YT921X_PORT_IGR_TPIDn_STAG(0);
- else
- ctrl = YT921X_PORT_IGR_TPIDn_CTAG(0);
- return yt921x_reg_write(priv, YT921X_PORTn_IGR_TPID(port), ctrl);
-}
-
-static int
-yt921x_port_set_pvid(struct yt921x_priv *priv, int port, u16 vid)
-{
- u32 mask;
- u32 ctrl;
-
- mask = YT921X_PORT_VLAN_CTRL_CVID_M;
- ctrl = YT921X_PORT_VLAN_CTRL_CVID(vid);
- return yt921x_reg_update_bits(priv, YT921X_PORTn_VLAN_CTRL(port),
- mask, ctrl);
-}
-
-static int
-yt921x_vlan_filtering(struct yt921x_priv *priv, int port, bool vlan_filtering)
-{
- struct dsa_port *dp = dsa_to_port(&priv->ds, port);
- struct net_device *bdev;
- u16 pvid;
- u32 mask;
- u32 ctrl;
- int res;
-
- bdev = dsa_port_bridge_dev_get(dp);
-
- if (!bdev || !vlan_filtering)
- pvid = YT921X_VID_UNWARE;
- else
- br_vlan_get_pvid(bdev, &pvid);
- res = yt921x_port_set_pvid(priv, port, pvid);
- if (res)
- return res;
-
- mask = YT921X_PORT_VLAN_CTRL1_CVLAN_DROP_TAGGED |
- YT921X_PORT_VLAN_CTRL1_CVLAN_DROP_UNTAGGED;
- ctrl = 0;
- /* Do not drop tagged frames here; let VLAN_IGR_FILTER do it */
- if (vlan_filtering && !pvid)
- ctrl |= YT921X_PORT_VLAN_CTRL1_CVLAN_DROP_UNTAGGED;
- res = yt921x_reg_update_bits(priv, YT921X_PORTn_VLAN_CTRL1(port),
- mask, ctrl);
- if (res)
- return res;
-
- res = yt921x_reg_toggle_bits(priv, YT921X_VLAN_IGR_FILTER,
- YT921X_VLAN_IGR_FILTER_PORTn(port),
- vlan_filtering);
- if (res)
- return res;
-
- res = yt921x_vlan_aware_set(priv, port, vlan_filtering);
- if (res)
- return res;
-
- return 0;
-}
-
-static int yt921x_vlan_del(struct yt921x_priv *priv, int port, u16 vid)
-{
- u32 masks[2];
-
- masks[0] = YT921X_VLAN_CTRLa_PORTn(port);
- masks[1] = YT921X_VLAN_CTRLb_UNTAG_PORTn(port);
-
- return yt921x_reg64_clear_bits(priv, YT921X_VLANn_CTRL(vid), masks);
-}
-
-static int
-yt921x_vlan_add(struct yt921x_priv *priv, int port, u16 vid, bool untagged)
-{
- u32 masks[2];
- u32 ctrls[2];
-
- masks[0] = YT921X_VLAN_CTRLa_PORTn(port) |
- YT921X_VLAN_CTRLa_PORTS(priv->cpu_ports_mask);
- ctrls[0] = masks[0];
-
- masks[1] = YT921X_VLAN_CTRLb_UNTAG_PORTn(port);
- ctrls[1] = untagged ? masks[1] : 0;
-
- return yt921x_reg64_update_bits(priv, YT921X_VLANn_CTRL(vid),
- masks, ctrls);
-}
-
-static int
-yt921x_pvid_clear(struct yt921x_priv *priv, int port)
-{
- struct dsa_port *dp = dsa_to_port(&priv->ds, port);
- bool vlan_filtering;
- u32 mask;
- int res;
-
- vlan_filtering = dsa_port_is_vlan_filtering(dp);
-
- res = yt921x_port_set_pvid(priv, port,
- vlan_filtering ? 0 : YT921X_VID_UNWARE);
- if (res)
- return res;
-
- if (vlan_filtering) {
- mask = YT921X_PORT_VLAN_CTRL1_CVLAN_DROP_UNTAGGED;
- res = yt921x_reg_set_bits(priv, YT921X_PORTn_VLAN_CTRL1(port),
- mask);
- if (res)
- return res;
- }
-
- return 0;
-}
-
-static int
-yt921x_pvid_set(struct yt921x_priv *priv, int port, u16 vid)
-{
- struct dsa_port *dp = dsa_to_port(&priv->ds, port);
- bool vlan_filtering;
- u32 mask;
- int res;
-
- vlan_filtering = dsa_port_is_vlan_filtering(dp);
-
- if (vlan_filtering) {
- res = yt921x_port_set_pvid(priv, port, vid);
- if (res)
- return res;
- }
-
- mask = YT921X_PORT_VLAN_CTRL1_CVLAN_DROP_UNTAGGED;
- res = yt921x_reg_clear_bits(priv, YT921X_PORTn_VLAN_CTRL1(port), mask);
- if (res)
- return res;
-
- return 0;
-}
-
-static int
-yt921x_dsa_port_vlan_filtering(struct dsa_switch *ds, int port,
- bool vlan_filtering,
- struct netlink_ext_ack *extack)
-{
- struct yt921x_priv *priv = to_yt921x_priv(ds);
- int res;
-
- if (dsa_is_cpu_port(ds, port))
- return 0;
-
- mutex_lock(&priv->reg_lock);
- res = yt921x_vlan_filtering(priv, port, vlan_filtering);
- mutex_unlock(&priv->reg_lock);
-
- return res;
-}
-
-static int
-yt921x_dsa_port_vlan_del(struct dsa_switch *ds, int port,
- const struct switchdev_obj_port_vlan *vlan)
-{
- struct yt921x_priv *priv = to_yt921x_priv(ds);
- u16 vid = vlan->vid;
- u16 pvid;
- int res;
-
- if (dsa_is_cpu_port(ds, port))
- return 0;
-
- mutex_lock(&priv->reg_lock);
- do {
- struct dsa_port *dp = dsa_to_port(ds, port);
- struct net_device *bdev;
-
- res = yt921x_vlan_del(priv, port, vid);
- if (res)
- break;
-
- bdev = dsa_port_bridge_dev_get(dp);
- if (bdev) {
- br_vlan_get_pvid(bdev, &pvid);
- if (pvid == vid)
- res = yt921x_pvid_clear(priv, port);
- }
- } while (0);
- mutex_unlock(&priv->reg_lock);
-
- return res;
-}
-
-static int
-yt921x_dsa_port_vlan_add(struct dsa_switch *ds, int port,
- const struct switchdev_obj_port_vlan *vlan,
- struct netlink_ext_ack *extack)
-{
- struct yt921x_priv *priv = to_yt921x_priv(ds);
- u16 vid = vlan->vid;
- u16 pvid;
- int res;
-
- /* CPU port is supposed to be a member of every VLAN; see
- * yt921x_vlan_add() and yt921x_port_setup()
- */
- if (dsa_is_cpu_port(ds, port))
- return 0;
-
- mutex_lock(&priv->reg_lock);
- do {
- struct dsa_port *dp = dsa_to_port(ds, port);
- struct net_device *bdev;
-
- res = yt921x_vlan_add(priv, port, vid,
- vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED);
- if (res)
- break;
-
- bdev = dsa_port_bridge_dev_get(dp);
- if (bdev) {
- if (vlan->flags & BRIDGE_VLAN_INFO_PVID) {
- res = yt921x_pvid_set(priv, port, vid);
- } else {
- br_vlan_get_pvid(bdev, &pvid);
- if (pvid == vid)
- res = yt921x_pvid_clear(priv, port);
- }
- }
- } while (0);
- mutex_unlock(&priv->reg_lock);
-
- return res;
-}
-
-static int yt921x_userport_standalone(struct yt921x_priv *priv, int port)
-{
- u32 mask;
- u32 ctrl;
- int res;
-
- ctrl = ~priv->cpu_ports_mask;
- res = yt921x_reg_write(priv, YT921X_PORTn_ISOLATION(port), ctrl);
- if (res)
- return res;
-
- /* Turn off FDB learning to prevent FDB pollution */
- mask = YT921X_PORT_LEARN_DIS;
- res = yt921x_reg_set_bits(priv, YT921X_PORTn_LEARN(port), mask);
- if (res)
- return res;
-
- /* Turn off VLAN awareness */
- res = yt921x_vlan_aware_set(priv, port, false);
- if (res)
- return res;
-
- /* Unrelated since learning is off and all packets are trapped;
- * set it anyway
- */
- res = yt921x_port_set_pvid(priv, port, YT921X_VID_UNWARE);
- if (res)
- return res;
-
- return 0;
-}
-
-static int yt921x_userport_bridge(struct yt921x_priv *priv, int port)
-{
- u32 mask;
- int res;
-
- mask = YT921X_PORT_LEARN_DIS;
- res = yt921x_reg_clear_bits(priv, YT921X_PORTn_LEARN(port), mask);
- if (res)
- return res;
-
- return 0;
-}
-
-static int yt921x_isolate(struct yt921x_priv *priv, int port)
-{
- u32 mask;
- int res;
-
- mask = BIT(port);
- for (int i = 0; i < YT921X_PORT_NUM; i++) {
- if ((BIT(i) & priv->cpu_ports_mask) || i == port)
- continue;
-
- res = yt921x_reg_set_bits(priv, YT921X_PORTn_ISOLATION(i),
- mask);
- if (res)
- return res;
- }
-
- return 0;
-}
-
-/* Make sure to include the CPU port in ports_mask, or your bridge will
- * not have it.
- */
-static int yt921x_bridge(struct yt921x_priv *priv, u16 ports_mask)
-{
- unsigned long targets_mask = ports_mask & ~priv->cpu_ports_mask;
- u32 isolated_mask;
- u32 ctrl;
- int port;
- int res;
-
- isolated_mask = 0;
- for_each_set_bit(port, &targets_mask, YT921X_PORT_NUM) {
- struct yt921x_port *pp = &priv->ports[port];
-
- if (pp->isolated)
- isolated_mask |= BIT(port);
- }
-
- /* Block from non-cpu bridge ports ... */
- for_each_set_bit(port, &targets_mask, YT921X_PORT_NUM) {
- struct yt921x_port *pp = &priv->ports[port];
-
- /* to non-bridge ports */
- ctrl = ~ports_mask;
- /* to isolated ports when isolated */
- if (pp->isolated)
- ctrl |= isolated_mask;
- /* to itself when non-hairpin */
- if (!pp->hairpin)
- ctrl |= BIT(port);
- else
- ctrl &= ~BIT(port);
-
- res = yt921x_reg_write(priv, YT921X_PORTn_ISOLATION(port),
- ctrl);
- if (res)
- return res;
- }
-
- return 0;
-}
-
-static int yt921x_bridge_leave(struct yt921x_priv *priv, int port)
-{
- int res;
-
- res = yt921x_userport_standalone(priv, port);
- if (res)
- return res;
-
- res = yt921x_isolate(priv, port);
- if (res)
- return res;
-
- return 0;
-}
-
-static int
-yt921x_bridge_join(struct yt921x_priv *priv, int port, u16 ports_mask)
-{
- int res;
-
- res = yt921x_userport_bridge(priv, port);
- if (res)
- return res;
-
- res = yt921x_bridge(priv, ports_mask);
- if (res)
- return res;
-
- return 0;
-}
-
-static int
-yt921x_bridge_flags(struct yt921x_priv *priv, int port,
- struct switchdev_brport_flags flags)
-{
- struct yt921x_port *pp = &priv->ports[port];
- bool do_flush;
- u32 mask;
- int res;
-
- if (flags.mask & BR_LEARNING) {
- bool learning = flags.val & BR_LEARNING;
-
- mask = YT921X_PORT_LEARN_DIS;
- res = yt921x_reg_toggle_bits(priv, YT921X_PORTn_LEARN(port),
- mask, !learning);
- if (res)
- return res;
- }
-
- /* BR_FLOOD, BR_MCAST_FLOOD: see the comment where ACT_UNK_ACTn_TRAP
- * is set
- */
-
- /* BR_BCAST_FLOOD: we can filter bcast, but cannot trap them */
-
- do_flush = false;
- if (flags.mask & BR_HAIRPIN_MODE) {
- pp->hairpin = flags.val & BR_HAIRPIN_MODE;
- do_flush = true;
- }
- if (flags.mask & BR_ISOLATED) {
- pp->isolated = flags.val & BR_ISOLATED;
- do_flush = true;
- }
- if (do_flush) {
- struct dsa_switch *ds = &priv->ds;
- struct dsa_port *dp = dsa_to_port(ds, port);
- struct net_device *bdev;
-
- bdev = dsa_port_bridge_dev_get(dp);
- if (bdev) {
- u32 ports_mask;
-
- ports_mask = dsa_bridge_ports(ds, bdev);
- ports_mask |= priv->cpu_ports_mask;
- res = yt921x_bridge(priv, ports_mask);
- if (res)
- return res;
- }
- }
-
- return 0;
-}
-
-static int
-yt921x_dsa_port_pre_bridge_flags(struct dsa_switch *ds, int port,
- struct switchdev_brport_flags flags,
- struct netlink_ext_ack *extack)
-{
- if (flags.mask & ~(BR_HAIRPIN_MODE | BR_LEARNING | BR_FLOOD |
- BR_MCAST_FLOOD | BR_ISOLATED))
- return -EINVAL;
- return 0;
-}
-
-static int
-yt921x_dsa_port_bridge_flags(struct dsa_switch *ds, int port,
- struct switchdev_brport_flags flags,
- struct netlink_ext_ack *extack)
-{
- struct yt921x_priv *priv = to_yt921x_priv(ds);
- int res;
-
- if (dsa_is_cpu_port(ds, port))
- return 0;
-
- mutex_lock(&priv->reg_lock);
- res = yt921x_bridge_flags(priv, port, flags);
- mutex_unlock(&priv->reg_lock);
-
- return res;
-}
-
-static void
-yt921x_dsa_port_bridge_leave(struct dsa_switch *ds, int port,
- struct dsa_bridge bridge)
-{
- struct yt921x_priv *priv = to_yt921x_priv(ds);
- struct device *dev = to_device(priv);
- int res;
-
- if (dsa_is_cpu_port(ds, port))
- return;
-
- mutex_lock(&priv->reg_lock);
- res = yt921x_bridge_leave(priv, port);
- mutex_unlock(&priv->reg_lock);
-
- if (res)
- dev_err(dev, "Failed to %s port %d: %i\n", "unbridge",
- port, res);
-}
-
-static int
-yt921x_dsa_port_bridge_join(struct dsa_switch *ds, int port,
- struct dsa_bridge bridge, bool *tx_fwd_offload,
- struct netlink_ext_ack *extack)
-{
- struct yt921x_priv *priv = to_yt921x_priv(ds);
- u16 ports_mask;
- int res;
-
- if (dsa_is_cpu_port(ds, port))
- return 0;
-
- ports_mask = dsa_bridge_ports(ds, bridge.dev);
- ports_mask |= priv->cpu_ports_mask;
-
- mutex_lock(&priv->reg_lock);
- res = yt921x_bridge_join(priv, port, ports_mask);
- mutex_unlock(&priv->reg_lock);
-
- return res;
-}
-
-static int
-yt921x_dsa_port_mst_state_set(struct dsa_switch *ds, int port,
- const struct switchdev_mst_state *st)
-{
- struct yt921x_priv *priv = to_yt921x_priv(ds);
- u32 mask;
- u32 ctrl;
- int res;
-
- mask = YT921X_STP_PORTn_M(port);
- switch (st->state) {
- case BR_STATE_DISABLED:
- ctrl = YT921X_STP_PORTn_DISABLED(port);
- break;
- case BR_STATE_LISTENING:
- case BR_STATE_LEARNING:
- ctrl = YT921X_STP_PORTn_LEARNING(port);
- break;
- case BR_STATE_FORWARDING:
- default:
- ctrl = YT921X_STP_PORTn_FORWARD(port);
- break;
- case BR_STATE_BLOCKING:
- ctrl = YT921X_STP_PORTn_BLOCKING(port);
- break;
- }
-
- mutex_lock(&priv->reg_lock);
- res = yt921x_reg_update_bits(priv, YT921X_STPn(st->msti), mask, ctrl);
- mutex_unlock(&priv->reg_lock);
-
- return res;
-}
-
-static int
-yt921x_dsa_vlan_msti_set(struct dsa_switch *ds, struct dsa_bridge bridge,
- const struct switchdev_vlan_msti *msti)
-{
- struct yt921x_priv *priv = to_yt921x_priv(ds);
- u32 masks[2];
- u32 ctrls[2];
- int res;
-
- if (!msti->vid)
- return -EINVAL;
- if (!msti->msti || msti->msti >= YT921X_MSTI_NUM)
- return -EINVAL;
-
- masks[0] = 0;
- ctrls[0] = 0;
- masks[1] = YT921X_VLAN_CTRLb_STP_ID_M;
- ctrls[1] = YT921X_VLAN_CTRLb_STP_ID(msti->msti);
-
- mutex_lock(&priv->reg_lock);
- res = yt921x_reg64_update_bits(priv, YT921X_VLANn_CTRL(msti->vid),
- masks, ctrls);
- mutex_unlock(&priv->reg_lock);
-
- return res;
-}
-
-static void
-yt921x_dsa_port_stp_state_set(struct dsa_switch *ds, int port, u8 state)
-{
- struct yt921x_priv *priv = to_yt921x_priv(ds);
- struct dsa_port *dp = dsa_to_port(ds, port);
- struct device *dev = to_device(priv);
- bool learning;
- u32 mask;
- u32 ctrl;
- int res;
-
- mask = YT921X_STP_PORTn_M(port);
- learning = false;
- switch (state) {
- case BR_STATE_DISABLED:
- ctrl = YT921X_STP_PORTn_DISABLED(port);
- break;
- case BR_STATE_LISTENING:
- ctrl = YT921X_STP_PORTn_LEARNING(port);
- break;
- case BR_STATE_LEARNING:
- ctrl = YT921X_STP_PORTn_LEARNING(port);
- learning = dp->learning;
- break;
- case BR_STATE_FORWARDING:
- default:
- ctrl = YT921X_STP_PORTn_FORWARD(port);
- learning = dp->learning;
- break;
- case BR_STATE_BLOCKING:
- ctrl = YT921X_STP_PORTn_BLOCKING(port);
- break;
- }
-
- mutex_lock(&priv->reg_lock);
- do {
- res = yt921x_reg_update_bits(priv, YT921X_STPn(0), mask, ctrl);
- if (res)
- break;
-
- mask = YT921X_PORT_LEARN_DIS;
- ctrl = !learning ? YT921X_PORT_LEARN_DIS : 0;
- res = yt921x_reg_update_bits(priv, YT921X_PORTn_LEARN(port),
- mask, ctrl);
- } while (0);
- mutex_unlock(&priv->reg_lock);
-
- if (res)
- dev_err(dev, "Failed to %s port %d: %i\n", "set STP state for",
- port, res);
-}
-
-static int __maybe_unused
-yt921x_dsa_port_get_default_prio(struct dsa_switch *ds, int port)
-{
- struct yt921x_priv *priv = to_yt921x_priv(ds);
- u32 val;
- int res;
-
- mutex_lock(&priv->reg_lock);
- res = yt921x_reg_read(priv, YT921X_PORTn_QOS(port), &val);
- mutex_unlock(&priv->reg_lock);
-
- if (res)
- return res;
-
- return FIELD_GET(YT921X_PORT_QOS_PRIO_M, val);
-}
-
-static int __maybe_unused
-yt921x_dsa_port_set_default_prio(struct dsa_switch *ds, int port, u8 prio)
-{
- struct yt921x_priv *priv = to_yt921x_priv(ds);
- u32 mask;
- u32 ctrl;
- int res;
-
- if (prio >= YT921X_PRIO_NUM)
- return -EINVAL;
-
- mutex_lock(&priv->reg_lock);
- mask = YT921X_PORT_QOS_PRIO_M | YT921X_PORT_QOS_PRIO_EN;
- ctrl = YT921X_PORT_QOS_PRIO(prio) | YT921X_PORT_QOS_PRIO_EN;
- res = yt921x_reg_update_bits(priv, YT921X_PORTn_QOS(port), mask, ctrl);
- mutex_unlock(&priv->reg_lock);
-
- return res;
-}
-
-static int __maybe_unused appprios_cmp(const void *a, const void *b)
-{
- return ((const u8 *)b)[1] - ((const u8 *)a)[1];
-}
-
-static int __maybe_unused
-yt921x_dsa_port_get_apptrust(struct dsa_switch *ds, int port, u8 *sel,
- int *nselp)
-{
- struct yt921x_priv *priv = to_yt921x_priv(ds);
- u8 appprios[2][2] = {};
- int nsel;
- u32 val;
- int res;
-
- mutex_lock(&priv->reg_lock);
- res = yt921x_reg_read(priv, YT921X_PORTn_PRIO_ORD(port), &val);
- mutex_unlock(&priv->reg_lock);
-
- if (res)
- return res;
-
- appprios[0][0] = IEEE_8021QAZ_APP_SEL_DSCP;
- appprios[0][1] = (val >> (3 * YT921X_APP_SEL_DSCP)) & 7;
- appprios[1][0] = DCB_APP_SEL_PCP;
- appprios[1][1] = (val >> (3 * YT921X_APP_SEL_CVLAN_PCP)) & 7;
- sort(appprios, ARRAY_SIZE(appprios), sizeof(appprios[0]), appprios_cmp,
- NULL);
-
- nsel = 0;
- for (int i = 0; i < ARRAY_SIZE(appprios) && appprios[i][1]; i++) {
- sel[nsel] = appprios[i][0];
- nsel++;
- }
- *nselp = nsel;
-
- return 0;
-}
-
-static int __maybe_unused
-yt921x_dsa_port_set_apptrust(struct dsa_switch *ds, int port, const u8 *sel,
- int nsel)
-{
- struct yt921x_priv *priv = to_yt921x_priv(ds);
- struct device *dev = to_device(priv);
- u32 ctrl;
- int res;
-
- if (nsel > YT921X_APP_SEL_NUM)
- return -EINVAL;
-
- ctrl = 0;
- for (int i = 0; i < nsel; i++) {
- switch (sel[i]) {
- case IEEE_8021QAZ_APP_SEL_DSCP:
- ctrl |= YT921X_PORT_PRIO_ORD_APPm(YT921X_APP_SEL_DSCP,
- 7 - i);
- break;
- case DCB_APP_SEL_PCP:
- ctrl |= YT921X_PORT_PRIO_ORD_APPm(YT921X_APP_SEL_CVLAN_PCP,
- 7 - i);
- ctrl |= YT921X_PORT_PRIO_ORD_APPm(YT921X_APP_SEL_SVLAN_PCP,
- 7 - i);
- break;
- default:
- dev_err(dev,
- "Invalid apptrust selector (at %d-th). Supported: dscp, pcp\n",
- i + 1);
- return -EOPNOTSUPP;
- }
- }
-
- mutex_lock(&priv->reg_lock);
- res = yt921x_reg_write(priv, YT921X_PORTn_PRIO_ORD(port), ctrl);
- mutex_unlock(&priv->reg_lock);
-
- return res;
-}
-
-static int yt921x_port_down(struct yt921x_priv *priv, int port)
-{
- u32 mask;
- int res;
-
- mask = YT921X_PORT_LINK | YT921X_PORT_RX_MAC_EN | YT921X_PORT_TX_MAC_EN;
- res = yt921x_reg_clear_bits(priv, YT921X_PORTn_CTRL(port), mask);
- if (res)
- return res;
-
- if (yt921x_port_is_external(port)) {
- mask = YT921X_SERDES_LINK;
- res = yt921x_reg_clear_bits(priv, YT921X_SERDESn(port), mask);
- if (res)
- return res;
-
- mask = YT921X_XMII_LINK;
- res = yt921x_reg_clear_bits(priv, YT921X_XMIIn(port), mask);
- if (res)
- return res;
- }
-
- return 0;
-}
-
-static int
-yt921x_port_up(struct yt921x_priv *priv, int port, unsigned int mode,
- phy_interface_t interface, int speed, int duplex,
- bool tx_pause, bool rx_pause)
-{
- u32 mask;
- u32 ctrl;
- int res;
-
- switch (speed) {
- case SPEED_10:
- ctrl = YT921X_PORT_SPEED_10;
- break;
- case SPEED_100:
- ctrl = YT921X_PORT_SPEED_100;
- break;
- case SPEED_1000:
- ctrl = YT921X_PORT_SPEED_1000;
- break;
- case SPEED_2500:
- ctrl = YT921X_PORT_SPEED_2500;
- break;
- case SPEED_10000:
- ctrl = YT921X_PORT_SPEED_10000;
- break;
- default:
- return -EINVAL;
- }
- if (duplex == DUPLEX_FULL)
- ctrl |= YT921X_PORT_DUPLEX_FULL;
- if (tx_pause)
- ctrl |= YT921X_PORT_TX_PAUSE;
- if (rx_pause)
- ctrl |= YT921X_PORT_RX_PAUSE;
- ctrl |= YT921X_PORT_RX_MAC_EN | YT921X_PORT_TX_MAC_EN;
- res = yt921x_reg_write(priv, YT921X_PORTn_CTRL(port), ctrl);
- if (res)
- return res;
-
- if (yt921x_port_is_external(port)) {
- mask = YT921X_SERDES_SPEED_M;
- switch (speed) {
- case SPEED_10:
- ctrl = YT921X_SERDES_SPEED_10;
- break;
- case SPEED_100:
- ctrl = YT921X_SERDES_SPEED_100;
- break;
- case SPEED_1000:
- ctrl = YT921X_SERDES_SPEED_1000;
- break;
- case SPEED_2500:
- ctrl = YT921X_SERDES_SPEED_2500;
- break;
- case SPEED_10000:
- ctrl = YT921X_SERDES_SPEED_10000;
- break;
- default:
- return -EINVAL;
- }
- mask |= YT921X_SERDES_DUPLEX_FULL;
- if (duplex == DUPLEX_FULL)
- ctrl |= YT921X_SERDES_DUPLEX_FULL;
- mask |= YT921X_SERDES_TX_PAUSE;
- if (tx_pause)
- ctrl |= YT921X_SERDES_TX_PAUSE;
- mask |= YT921X_SERDES_RX_PAUSE;
- if (rx_pause)
- ctrl |= YT921X_SERDES_RX_PAUSE;
- mask |= YT921X_SERDES_LINK;
- ctrl |= YT921X_SERDES_LINK;
- res = yt921x_reg_update_bits(priv, YT921X_SERDESn(port),
- mask, ctrl);
- if (res)
- return res;
-
- mask = YT921X_XMII_LINK;
- res = yt921x_reg_set_bits(priv, YT921X_XMIIn(port), mask);
- if (res)
- return res;
-
- switch (speed) {
- case SPEED_10:
- ctrl = YT921X_MDIO_POLLING_SPEED_10;
- break;
- case SPEED_100:
- ctrl = YT921X_MDIO_POLLING_SPEED_100;
- break;
- case SPEED_1000:
- ctrl = YT921X_MDIO_POLLING_SPEED_1000;
- break;
- case SPEED_2500:
- ctrl = YT921X_MDIO_POLLING_SPEED_2500;
- break;
- case SPEED_10000:
- ctrl = YT921X_MDIO_POLLING_SPEED_10000;
- break;
- default:
- return -EINVAL;
- }
- if (duplex == DUPLEX_FULL)
- ctrl |= YT921X_MDIO_POLLING_DUPLEX_FULL;
- ctrl |= YT921X_MDIO_POLLING_LINK;
- res = yt921x_reg_write(priv, YT921X_MDIO_POLLINGn(port), ctrl);
- if (res)
- return res;
- }
-
- return 0;
-}
-
-static int
-yt921x_port_config(struct yt921x_priv *priv, int port, unsigned int mode,
- phy_interface_t interface)
-{
- struct device *dev = to_device(priv);
- u32 mask;
- u32 ctrl;
- int res;
-
- if (!yt921x_port_is_external(port)) {
- if (interface != PHY_INTERFACE_MODE_INTERNAL) {
- dev_err(dev, "Wrong mode %d on port %d\n",
- interface, port);
- return -EINVAL;
- }
- return 0;
- }
-
- switch (interface) {
- /* SERDES */
- case PHY_INTERFACE_MODE_SGMII:
- case PHY_INTERFACE_MODE_100BASEX:
- case PHY_INTERFACE_MODE_1000BASEX:
- case PHY_INTERFACE_MODE_2500BASEX:
- mask = YT921X_SERDES_CTRL_PORTn(port);
- res = yt921x_reg_set_bits(priv, YT921X_SERDES_CTRL, mask);
- if (res)
- return res;
-
- mask = YT921X_XMII_CTRL_PORTn(port);
- res = yt921x_reg_clear_bits(priv, YT921X_XMII_CTRL, mask);
- if (res)
- return res;
-
- mask = YT921X_SERDES_MODE_M;
- switch (interface) {
- case PHY_INTERFACE_MODE_SGMII:
- ctrl = YT921X_SERDES_MODE_SGMII;
- break;
- case PHY_INTERFACE_MODE_100BASEX:
- ctrl = YT921X_SERDES_MODE_100BASEX;
- break;
- case PHY_INTERFACE_MODE_1000BASEX:
- ctrl = YT921X_SERDES_MODE_1000BASEX;
- break;
- case PHY_INTERFACE_MODE_2500BASEX:
- ctrl = YT921X_SERDES_MODE_2500BASEX;
- break;
- default:
- return -EINVAL;
- }
- res = yt921x_reg_update_bits(priv, YT921X_SERDESn(port),
- mask, ctrl);
- if (res)
- return res;
-
- break;
- /* add XMII support here */
- default:
- return -EINVAL;
- }
-
- return 0;
-}
-
-static void
-yt921x_phylink_mac_link_down(struct phylink_config *config, unsigned int mode,
- phy_interface_t interface)
-{
- struct dsa_port *dp = dsa_phylink_to_port(config);
- struct yt921x_priv *priv = to_yt921x_priv(dp->ds);
- int port = dp->index;
- int res;
-
- /* No need to sync; port control block is hold until device remove */
- cancel_delayed_work(&priv->ports[port].mib_read);
-
- mutex_lock(&priv->reg_lock);
- res = yt921x_port_down(priv, port);
- mutex_unlock(&priv->reg_lock);
-
- if (res)
- dev_err(dp->ds->dev, "Failed to %s port %d: %i\n", "bring down",
- port, res);
-}
-
-static void
-yt921x_phylink_mac_link_up(struct phylink_config *config,
- struct phy_device *phydev, unsigned int mode,
- phy_interface_t interface, int speed, int duplex,
- bool tx_pause, bool rx_pause)
-{
- struct dsa_port *dp = dsa_phylink_to_port(config);
- struct yt921x_priv *priv = to_yt921x_priv(dp->ds);
- int port = dp->index;
- int res;
-
- mutex_lock(&priv->reg_lock);
- res = yt921x_port_up(priv, port, mode, interface, speed, duplex,
- tx_pause, rx_pause);
- mutex_unlock(&priv->reg_lock);
-
- if (res)
- dev_err(dp->ds->dev, "Failed to %s port %d: %i\n", "bring up",
- port, res);
-
- schedule_delayed_work(&priv->ports[port].mib_read, 0);
-}
-
-static void
-yt921x_phylink_mac_config(struct phylink_config *config, unsigned int mode,
- const struct phylink_link_state *state)
-{
- struct dsa_port *dp = dsa_phylink_to_port(config);
- struct yt921x_priv *priv = to_yt921x_priv(dp->ds);
- int port = dp->index;
- int res;
-
- mutex_lock(&priv->reg_lock);
- res = yt921x_port_config(priv, port, mode, state->interface);
- mutex_unlock(&priv->reg_lock);
-
- if (res)
- dev_err(dp->ds->dev, "Failed to %s port %d: %i\n", "config",
- port, res);
-}
-
-static void
-yt921x_dsa_phylink_get_caps(struct dsa_switch *ds, int port,
- struct phylink_config *config)
-{
- struct yt921x_priv *priv = to_yt921x_priv(ds);
- const struct yt921x_info *info = priv->info;
-
- config->mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE |
- MAC_10 | MAC_100 | MAC_1000;
-
- if (info->internal_mask & BIT(port)) {
- /* Port 10 for MCU should probably go here too. But since that
- * is untested yet, turn it down for the moment by letting it
- * fall to the default branch.
- */
- __set_bit(PHY_INTERFACE_MODE_INTERNAL,
- config->supported_interfaces);
- } else if (info->external_mask & BIT(port)) {
- /* TODO: external ports may support SERDES only, XMII only, or
- * SERDES + XMII depending on the chip. However, we can't get
- * the accurate config table due to lack of document, thus
- * we simply declare SERDES + XMII and rely on the correctness
- * of devicetree for now.
- */
-
- /* SERDES */
- __set_bit(PHY_INTERFACE_MODE_SGMII,
- config->supported_interfaces);
- /* REVSGMII (SGMII in PHY role) should go here, once
- * PHY_INTERFACE_MODE_REVSGMII is introduced.
- */
- __set_bit(PHY_INTERFACE_MODE_100BASEX,
- config->supported_interfaces);
- __set_bit(PHY_INTERFACE_MODE_1000BASEX,
- config->supported_interfaces);
- __set_bit(PHY_INTERFACE_MODE_2500BASEX,
- config->supported_interfaces);
- config->mac_capabilities |= MAC_2500FD;
-
- /* XMII */
-
- /* Not tested. To add support for XMII:
- * - Add proper interface modes below
- * - Handle them in yt921x_port_config()
- */
- }
- /* no such port: empty supported_interfaces causes phylink to turn it
- * down
- */
-}
-
-static int yt921x_port_setup(struct yt921x_priv *priv, int port)
-{
- struct dsa_switch *ds = &priv->ds;
- u32 ctrl;
- int res;
-
- res = yt921x_userport_standalone(priv, port);
- if (res)
- return res;
-
- /* Clear prio order (even if DCB is not enabled) to avoid unsolicited
- * priorities
- */
- res = yt921x_reg_write(priv, YT921X_PORTn_PRIO_ORD(port), 0);
- if (res)
- return res;
-
- if (dsa_is_cpu_port(ds, port)) {
- /* Egress of CPU port is supposed to be completely controlled
- * via tagging, so set to oneway isolated (drop all packets
- * without tag).
- */
- ctrl = ~(u32)0;
- res = yt921x_reg_write(priv, YT921X_PORTn_ISOLATION(port),
- ctrl);
- if (res)
- return res;
-
- /* To simplify FDB "isolation" simulation, we also disable
- * learning on the CPU port, and let software identify packets
- * towarding CPU (either trapped or a static FDB entry is
- * matched, no matter which bridge that entry is for), which is
- * already done by yt921x_userport_standalone(). As a result,
- * VLAN-awareness becomes unrelated on the CPU port (set to
- * VLAN-unaware by the way).
- */
- }
-
- return 0;
-}
-
-static enum dsa_tag_protocol
-yt921x_dsa_get_tag_protocol(struct dsa_switch *ds, int port,
- enum dsa_tag_protocol m)
-{
- return DSA_TAG_PROTO_YT921X;
-}
-
-static int yt921x_dsa_port_setup(struct dsa_switch *ds, int port)
-{
- struct yt921x_priv *priv = to_yt921x_priv(ds);
- int res;
-
- mutex_lock(&priv->reg_lock);
- res = yt921x_port_setup(priv, port);
- mutex_unlock(&priv->reg_lock);
-
- return res;
-}
-
-/* Not "port" - DSCP mapping is global */
-static int __maybe_unused
-yt921x_dsa_port_get_dscp_prio(struct dsa_switch *ds, int port, u8 dscp)
-{
- struct yt921x_priv *priv = to_yt921x_priv(ds);
- u32 val;
- int res;
-
- mutex_lock(&priv->reg_lock);
- res = yt921x_reg_read(priv, YT921X_IPM_DSCPn(dscp), &val);
- mutex_unlock(&priv->reg_lock);
-
- if (res)
- return res;
-
- return FIELD_GET(YT921X_IPM_PRIO_M, val);
-}
-
-static int __maybe_unused
-yt921x_dsa_port_del_dscp_prio(struct dsa_switch *ds, int port, u8 dscp, u8 prio)
-{
- struct yt921x_priv *priv = to_yt921x_priv(ds);
- u32 val;
- int res;
-
- mutex_lock(&priv->reg_lock);
- /* During a "dcb app replace" command, the new app table entry will be
- * added first, then the old one will be deleted. But the hardware only
- * supports one QoS class per DSCP value (duh), so if we blindly delete
- * the app table entry for this DSCP value, we end up deleting the
- * entry with the new priority. Avoid that by checking whether user
- * space wants to delete the priority which is currently configured, or
- * something else which is no longer current.
- */
- res = yt921x_reg_read(priv, YT921X_IPM_DSCPn(dscp), &val);
- if (!res && FIELD_GET(YT921X_IPM_PRIO_M, val) == prio)
- res = yt921x_reg_write(priv, YT921X_IPM_DSCPn(dscp),
- YT921X_IPM_PRIO(IEEE8021Q_TT_BK));
- mutex_unlock(&priv->reg_lock);
-
- return res;
-}
-
-static int __maybe_unused
-yt921x_dsa_port_add_dscp_prio(struct dsa_switch *ds, int port, u8 dscp, u8 prio)
-{
- struct yt921x_priv *priv = to_yt921x_priv(ds);
- int res;
-
- if (prio >= YT921X_PRIO_NUM)
- return -EINVAL;
-
- mutex_lock(&priv->reg_lock);
- res = yt921x_reg_write(priv, YT921X_IPM_DSCPn(dscp),
- YT921X_IPM_PRIO(prio));
- mutex_unlock(&priv->reg_lock);
-
- return res;
-}
-
-static int yt921x_edata_wait(struct yt921x_priv *priv, u32 *valp)
-{
- u32 val = YT921X_EDATA_DATA_IDLE;
- int res;
-
- res = yt921x_reg_wait(priv, YT921X_EDATA_DATA,
- YT921X_EDATA_DATA_STATUS_M, &val);
- if (res)
- return res;
-
- *valp = val;
- return 0;
-}
-
-static int
-yt921x_edata_read_cont(struct yt921x_priv *priv, u8 addr, u8 *valp)
-{
- u32 ctrl;
- u32 val;
- int res;
-
- ctrl = YT921X_EDATA_CTRL_ADDR(addr) | YT921X_EDATA_CTRL_READ;
- res = yt921x_reg_write(priv, YT921X_EDATA_CTRL, ctrl);
- if (res)
- return res;
- res = yt921x_edata_wait(priv, &val);
- if (res)
- return res;
-
- *valp = FIELD_GET(YT921X_EDATA_DATA_DATA_M, val);
- return 0;
-}
-
-static int yt921x_edata_read(struct yt921x_priv *priv, u8 addr, u8 *valp)
-{
- u32 val;
- int res;
-
- res = yt921x_edata_wait(priv, &val);
- if (res)
- return res;
- return yt921x_edata_read_cont(priv, addr, valp);
-}
-
-static int yt921x_chip_detect(struct yt921x_priv *priv)
-{
- struct device *dev = to_device(priv);
- const struct yt921x_info *info;
- u8 extmode;
- u32 chipid;
- u32 major;
- u32 mode;
- u32 val;
- int res;
-
- res = yt921x_reg_read(priv, YT921X_CHIP_ID, &chipid);
- if (res)
- return res;
-
- major = FIELD_GET(YT921X_CHIP_ID_MAJOR, chipid);
-
- for (info = yt921x_infos; info->name; info++)
- if (info->major == major)
- break;
- if (!info->name) {
- dev_err(dev, "Unexpected chipid 0x%x\n", chipid);
- return -ENODEV;
- }
-
- res = yt921x_reg_read(priv, YT921X_CHIP_MODE, &mode);
- if (res)
- return res;
- res = yt921x_edata_read(priv, YT921X_EDATA_EXTMODE, &extmode);
- if (res)
- return res;
-
- for (; info->name; info++)
- if (info->major == major && info->mode == mode &&
- info->extmode == extmode)
- break;
- if (!info->name) {
- dev_err(dev,
- "Unsupported chipid 0x%x with chipmode 0x%x 0x%x\n",
- chipid, mode, extmode);
- return -ENODEV;
- }
-
- res = yt921x_reg_read(priv, YT921X_SYS_CLK, &val);
- if (res)
- return res;
- switch (FIELD_GET(YT921X_SYS_CLK_SEL_M, val)) {
- case 0:
- priv->cycle_ns = info->major == YT9215_MAJOR ? 8 : 6;
- break;
- case YT921X_SYS_CLK_143M:
- priv->cycle_ns = 7;
- break;
- default:
- priv->cycle_ns = 8;
- }
-
- /* Print chipid here since we are interested in lower 16 bits */
- dev_info(dev,
- "Motorcomm %s ethernet switch, chipid: 0x%x, chipmode: 0x%x 0x%x\n",
- info->name, chipid, mode, extmode);
-
- priv->info = info;
-
- return 0;
-}
-
-static int yt921x_chip_reset(struct yt921x_priv *priv)
-{
- struct device *dev = to_device(priv);
- u16 eth_p_tag;
- u32 val;
- int res;
-
- res = yt921x_chip_detect(priv);
- if (res)
- return res;
-
- /* Reset */
- res = yt921x_reg_write(priv, YT921X_RST, YT921X_RST_HW);
- if (res)
- return res;
-
- /* RST_HW is almost same as GPIO hard reset, so we need this delay. */
- fsleep(YT921X_RST_DELAY_US);
-
- val = 0;
- res = yt921x_reg_wait(priv, YT921X_RST, ~0, &val);
- if (res)
- return res;
-
- /* Check for tag EtherType; do it after reset in case you messed it up
- * before.
- */
- res = yt921x_reg_read(priv, YT921X_CPU_TAG_TPID, &val);
- if (res)
- return res;
- eth_p_tag = FIELD_GET(YT921X_CPU_TAG_TPID_TPID_M, val);
- if (eth_p_tag != ETH_P_YT921X) {
- dev_err(dev, "Tag type 0x%x != 0x%x\n", eth_p_tag,
- ETH_P_YT921X);
- /* Despite being possible, we choose not to set CPU_TAG_TPID,
- * since there is no way it can be different unless you have the
- * wrong chip.
- */
- return -EINVAL;
- }
-
- return 0;
-}
-
-static int yt921x_chip_setup_dsa(struct yt921x_priv *priv)
-{
- struct dsa_switch *ds = &priv->ds;
- unsigned long cpu_ports_mask;
- u32 ctrls[2];
- u32 ctrl;
- int port;
- int res;
-
- /* Enable DSA */
- priv->cpu_ports_mask = dsa_cpu_ports(ds);
-
- ctrl = YT921X_EXT_CPU_PORT_TAG_EN | YT921X_EXT_CPU_PORT_PORT_EN |
- YT921X_EXT_CPU_PORT_PORT(__ffs(priv->cpu_ports_mask));
- res = yt921x_reg_write(priv, YT921X_EXT_CPU_PORT, ctrl);
- if (res)
- return res;
-
- /* Setup software switch */
- ctrl = YT921X_CPU_COPY_TO_EXT_CPU;
- res = yt921x_reg_write(priv, YT921X_CPU_COPY, ctrl);
- if (res)
- return res;
-
- ctrl = GENMASK(10, 0);
- res = yt921x_reg_write(priv, YT921X_FILTER_UNK_UCAST, ctrl);
- if (res)
- return res;
- res = yt921x_reg_write(priv, YT921X_FILTER_UNK_MCAST, ctrl);
- if (res)
- return res;
-
- /* YT921x does not support native DSA port bridging, so we use port
- * isolation to emulate it. However, be especially careful that port
- * isolation takes _after_ FDB lookups, i.e. if an FDB entry (from
- * another bridge) is matched and the destination port (in another
- * bridge) is blocked, the packet will be dropped instead of flooding to
- * the "bridged" ports, thus we need to trap and handle those packets by
- * software.
- *
- * If there is no more than one bridge, we might be able to drop them
- * directly given some conditions are met, but we trap them in all cases
- * for now.
- */
- ctrl = 0;
- for (int i = 0; i < YT921X_PORT_NUM; i++)
- ctrl |= YT921X_ACT_UNK_ACTn_TRAP(i);
- /* Except for CPU ports, if any packets are sent via CPU ports without
- * tag, they should be dropped.
- */
- cpu_ports_mask = priv->cpu_ports_mask;
- for_each_set_bit(port, &cpu_ports_mask, YT921X_PORT_NUM) {
- ctrl &= ~YT921X_ACT_UNK_ACTn_M(port);
- ctrl |= YT921X_ACT_UNK_ACTn_DROP(port);
- }
- res = yt921x_reg_write(priv, YT921X_ACT_UNK_UCAST, ctrl);
- if (res)
- return res;
- res = yt921x_reg_write(priv, YT921X_ACT_UNK_MCAST, ctrl);
- if (res)
- return res;
-
- /* Tagged VID 0 should be treated as untagged, which confuses the
- * hardware a lot
- */
- ctrls[0] = YT921X_VLAN_CTRLa_LEARN_DIS | YT921X_VLAN_CTRLa_PORTS_M;
- ctrls[1] = 0;
- res = yt921x_reg64_write(priv, YT921X_VLANn_CTRL(0), ctrls);
- if (res)
- return res;
-
- return 0;
-}
-
-static int yt921x_chip_setup_tc(struct yt921x_priv *priv)
-{
- unsigned int op_ns;
- u32 ctrl;
- int res;
-
- op_ns = 8 * priv->cycle_ns;
-
- ctrl = max(priv->meter_slot_ns / op_ns, YT921X_METER_SLOT_MIN);
- res = yt921x_reg_write(priv, YT921X_METER_SLOT, ctrl);
- if (res)
- return res;
- priv->meter_slot_ns = ctrl * op_ns;
-
- ctrl = max(priv->port_shape_slot_ns / op_ns,
- YT921X_PORT_SHAPE_SLOT_MIN);
- res = yt921x_reg_write(priv, YT921X_PORT_SHAPE_SLOT, ctrl);
- if (res)
- return res;
- priv->port_shape_slot_ns = ctrl * op_ns;
-
- return 0;
-}
-
-static int yt921x_chip_setup_acl(struct yt921x_priv *priv)
-{
- u32 ctrl;
- int res;
-
- ctrl = YT921X_ACL_PERMIT_UNMATCH_PORTS_M;
- res = yt921x_reg_write(priv, YT921X_ACL_PERMIT_UNMATCH, ctrl);
- if (res)
- return res;
-
- ctrl = YT921X_ACL_PORT_PORTS_M;
- res = yt921x_reg_write(priv, YT921X_ACL_PORT, ctrl);
- if (res)
- return res;
-
- return 0;
-}
-
-static int __maybe_unused yt921x_chip_setup_qos(struct yt921x_priv *priv)
-{
- u32 ctrl;
- int res;
-
- /* DSCP to internal priorities */
- for (u8 dscp = 0; dscp < DSCP_MAX; dscp++) {
- int prio = ietf_dscp_to_ieee8021q_tt(dscp);
-
- if (prio < 0)
- return prio;
-
- res = yt921x_reg_write(priv, YT921X_IPM_DSCPn(dscp),
- YT921X_IPM_PRIO(prio));
- if (res)
- return res;
- }
-
- /* 802.1Q QoS to internal priorities */
- for (u8 pcp = 0; pcp < 8; pcp++)
- for (u8 dei = 0; dei < 2; dei++) {
- ctrl = YT921X_IPM_PRIO(pcp);
- if (dei)
- /* "Red" almost means drop, so it's not that
- * useful. Note that tc police does not support
- * Three-Color very well
- */
- ctrl |= YT921X_IPM_COLOR_YELLOW;
-
- for (u8 svlan = 0; svlan < 2; svlan++) {
- u32 reg = YT921X_IPM_PCPn(svlan, dei, pcp);
-
- res = yt921x_reg_write(priv, reg, ctrl);
- if (res)
- return res;
- }
- }
-
- return 0;
-}
-
-static int yt921x_chip_setup(struct yt921x_priv *priv)
-{
- u32 ctrl;
- int res;
-
- ctrl = YT921X_FUNC_MIB | YT921X_FUNC_ACL | YT921X_FUNC_METER;
- res = yt921x_reg_set_bits(priv, YT921X_FUNC, ctrl);
- if (res)
- return res;
-
- res = yt921x_chip_setup_dsa(priv);
- if (res)
- return res;
-
- res = yt921x_chip_setup_tc(priv);
- if (res)
- return res;
-
- res = yt921x_chip_setup_acl(priv);
- if (res)
- return res;
-
-#if IS_ENABLED(CONFIG_DCB)
- res = yt921x_chip_setup_qos(priv);
- if (res)
- return res;
-#endif
-
- /* Clear MIB */
- ctrl = YT921X_MIB_CTRL_CLEAN | YT921X_MIB_CTRL_ALL_PORT;
- res = yt921x_reg_write(priv, YT921X_MIB_CTRL, ctrl);
- if (res)
- return res;
-
- /* Miscellaneous */
- res = yt921x_reg_set_bits(priv, YT921X_SENSOR, YT921X_SENSOR_TEMP);
- if (res)
- return res;
-
- return 0;
-}
-
-static int yt921x_dsa_setup(struct dsa_switch *ds)
-{
- struct yt921x_priv *priv = to_yt921x_priv(ds);
- struct device *dev = to_device(priv);
- struct device_node *np = dev->of_node;
- struct device_node *child;
- int res;
-
- mutex_lock(&priv->reg_lock);
- res = yt921x_chip_reset(priv);
- mutex_unlock(&priv->reg_lock);
-
- if (res)
- return res;
-
- /* Register the internal mdio bus. Nodes for internal ports should have
- * proper phy-handle pointing to their PHYs. Not enabling the internal
- * bus is possible, though pretty wired, if internal ports are not used.
- */
- child = of_get_child_by_name(np, "mdio");
- if (child) {
- res = yt921x_mbus_int_init(priv, child);
- of_node_put(child);
- if (res)
- return res;
- }
-
- /* External mdio bus is optional */
- child = of_get_child_by_name(np, "mdio-external");
- if (child) {
- res = yt921x_mbus_ext_init(priv, child);
- of_node_put(child);
- if (res)
- return res;
-
- dev_err(dev, "Untested external mdio bus\n");
- return -ENODEV;
- }
-
- mutex_lock(&priv->reg_lock);
- res = yt921x_chip_setup(priv);
- mutex_unlock(&priv->reg_lock);
-
- if (res)
- return res;
-
- return 0;
-}
-
-static const struct phylink_mac_ops yt921x_phylink_mac_ops = {
- .mac_link_down = yt921x_phylink_mac_link_down,
- .mac_link_up = yt921x_phylink_mac_link_up,
- .mac_config = yt921x_phylink_mac_config,
-};
-
-static const struct dsa_switch_ops yt921x_dsa_switch_ops = {
- /* mib */
- .get_strings = yt921x_dsa_get_strings,
- .get_ethtool_stats = yt921x_dsa_get_ethtool_stats,
- .get_sset_count = yt921x_dsa_get_sset_count,
- .get_eth_mac_stats = yt921x_dsa_get_eth_mac_stats,
- .get_eth_ctrl_stats = yt921x_dsa_get_eth_ctrl_stats,
- .get_rmon_stats = yt921x_dsa_get_rmon_stats,
- .get_stats64 = yt921x_dsa_get_stats64,
- .get_pause_stats = yt921x_dsa_get_pause_stats,
- /* eee */
- .support_eee = dsa_supports_eee,
- .set_mac_eee = yt921x_dsa_set_mac_eee,
- /* mtu */
- .port_change_mtu = yt921x_dsa_port_change_mtu,
- .port_max_mtu = yt921x_dsa_port_max_mtu,
- /* rate */
- .port_policer_del = yt921x_dsa_port_policer_del,
- .port_policer_add = yt921x_dsa_port_policer_add,
- .port_setup_tc = yt921x_dsa_port_setup_tc,
- /* acl */
- .cls_flower_del = yt921x_dsa_cls_flower_del,
- .cls_flower_add = yt921x_dsa_cls_flower_add,
- /* hsr */
- .port_hsr_leave = dsa_port_simple_hsr_leave,
- .port_hsr_join = dsa_port_simple_hsr_join,
- /* mirror */
- .port_mirror_del = yt921x_dsa_port_mirror_del,
- .port_mirror_add = yt921x_dsa_port_mirror_add,
- /* lag */
- .port_lag_leave = yt921x_dsa_port_lag_leave,
- .port_lag_join = yt921x_dsa_port_lag_join,
- /* fdb */
- .port_fdb_dump = yt921x_dsa_port_fdb_dump,
- .port_fast_age = yt921x_dsa_port_fast_age,
- .set_ageing_time = yt921x_dsa_set_ageing_time,
- .port_fdb_del = yt921x_dsa_port_fdb_del,
- .port_fdb_add = yt921x_dsa_port_fdb_add,
- .port_mdb_del = yt921x_dsa_port_mdb_del,
- .port_mdb_add = yt921x_dsa_port_mdb_add,
- /* vlan */
- .port_vlan_filtering = yt921x_dsa_port_vlan_filtering,
- .port_vlan_del = yt921x_dsa_port_vlan_del,
- .port_vlan_add = yt921x_dsa_port_vlan_add,
- /* bridge */
- .port_pre_bridge_flags = yt921x_dsa_port_pre_bridge_flags,
- .port_bridge_flags = yt921x_dsa_port_bridge_flags,
- .port_bridge_leave = yt921x_dsa_port_bridge_leave,
- .port_bridge_join = yt921x_dsa_port_bridge_join,
- /* mst */
- .port_mst_state_set = yt921x_dsa_port_mst_state_set,
- .vlan_msti_set = yt921x_dsa_vlan_msti_set,
- .port_stp_state_set = yt921x_dsa_port_stp_state_set,
-#if IS_ENABLED(CONFIG_DCB)
- /* dcb */
- .port_get_default_prio = yt921x_dsa_port_get_default_prio,
- .port_set_default_prio = yt921x_dsa_port_set_default_prio,
- .port_get_apptrust = yt921x_dsa_port_get_apptrust,
- .port_set_apptrust = yt921x_dsa_port_set_apptrust,
-#endif
- /* port */
- .get_tag_protocol = yt921x_dsa_get_tag_protocol,
- .phylink_get_caps = yt921x_dsa_phylink_get_caps,
- .port_setup = yt921x_dsa_port_setup,
-#if IS_ENABLED(CONFIG_DCB)
- /* dscp */
- .port_get_dscp_prio = yt921x_dsa_port_get_dscp_prio,
- .port_del_dscp_prio = yt921x_dsa_port_del_dscp_prio,
- .port_add_dscp_prio = yt921x_dsa_port_add_dscp_prio,
-#endif
- /* chip */
- .setup = yt921x_dsa_setup,
-};
-
-static void yt921x_mdio_shutdown(struct mdio_device *mdiodev)
-{
- struct yt921x_priv *priv = mdiodev_get_drvdata(mdiodev);
-
- if (!priv)
- return;
-
- dsa_switch_shutdown(&priv->ds);
-}
-
-static void yt921x_mdio_remove(struct mdio_device *mdiodev)
-{
- struct yt921x_priv *priv = mdiodev_get_drvdata(mdiodev);
-
- if (!priv)
- return;
-
- for (size_t i = ARRAY_SIZE(priv->ports); i-- > 0; ) {
- struct yt921x_port *pp = &priv->ports[i];
-
- disable_delayed_work_sync(&pp->mib_read);
- }
-
- dsa_unregister_switch(&priv->ds);
-
- for (unsigned int i = 0; i < ARRAY_SIZE(priv->acl_blks); i++) {
- struct yt921x_acl_blk *aclblk = priv->acl_blks[i];
-
- if (!aclblk)
- continue;
- for (unsigned int j = 0; j < ARRAY_SIZE(aclblk->rules); j++) {
- struct yt921x_acl_rule *aclrule = aclblk->rules[j];
-
- if (!aclrule)
- continue;
- kvfree(aclrule);
- }
- kvfree(aclblk);
- }
- mutex_destroy(&priv->reg_lock);
-}
-
-static int yt921x_mdio_probe(struct mdio_device *mdiodev)
-{
- struct device *dev = &mdiodev->dev;
- struct yt921x_reg_mdio *mdio;
- struct yt921x_priv *priv;
- struct dsa_switch *ds;
-
- priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
- if (!priv)
- return -ENOMEM;
-
- mdio = devm_kzalloc(dev, sizeof(*mdio), GFP_KERNEL);
- if (!mdio)
- return -ENOMEM;
-
- mdio->bus = mdiodev->bus;
- mdio->addr = mdiodev->addr;
- mdio->switchid = 0;
-
- mutex_init(&priv->reg_lock);
-
- priv->reg_ops = &yt921x_reg_ops_mdio;
- priv->reg_ctx = mdio;
-
- for (size_t i = 0; i < ARRAY_SIZE(priv->ports); i++) {
- struct yt921x_port *pp = &priv->ports[i];
-
- pp->index = i;
- INIT_DELAYED_WORK(&pp->mib_read, yt921x_poll_mib);
- }
-
- ds = &priv->ds;
- ds->dev = dev;
- ds->assisted_learning_on_cpu_port = true;
- ds->dscp_prio_mapping_is_global = true;
- ds->priv = priv;
- ds->ops = &yt921x_dsa_switch_ops;
- ds->ageing_time_min = 1 * 5000;
- ds->ageing_time_max = U16_MAX * 5000;
- ds->phylink_mac_ops = &yt921x_phylink_mac_ops;
- ds->num_lag_ids = YT921X_LAG_NUM;
- ds->num_ports = YT921X_PORT_NUM;
-
- mdiodev_set_drvdata(mdiodev, priv);
-
- return dsa_register_switch(ds);
-}
-
-static const struct of_device_id yt921x_of_match[] = {
- { .compatible = "motorcomm,yt9215" },
- {}
-};
-MODULE_DEVICE_TABLE(of, yt921x_of_match);
-
-static struct mdio_driver yt921x_mdio_driver = {
- .probe = yt921x_mdio_probe,
- .remove = yt921x_mdio_remove,
- .shutdown = yt921x_mdio_shutdown,
- .mdiodrv.driver = {
- .name = YT921X_NAME,
- .of_match_table = yt921x_of_match,
- },
-};
-
-mdio_module_driver(yt921x_mdio_driver);
-
-MODULE_AUTHOR("David Yang <mmyangfl@gmail.com>");
-MODULE_DESCRIPTION("Driver for Motorcomm YT921x Switch");
-MODULE_LICENSE("GPL");
diff --git a/drivers/net/dsa/yt921x.h b/drivers/net/dsa/yt921x.h
deleted file mode 100644
index 5f3b99e189c4..000000000000
--- a/drivers/net/dsa/yt921x.h
+++ /dev/null
@@ -1,977 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (c) 2025 David Yang
- */
-
-#ifndef __YT921X_H
-#define __YT921X_H
-
-#include <net/dsa.h>
-
-#define YT921X_SMI_SWITCHID_M GENMASK(3, 2)
-#define YT921X_SMI_SWITCHID(x) FIELD_PREP(YT921X_SMI_SWITCHID_M, (x))
-#define YT921X_SMI_AD BIT(1)
-#define YT921X_SMI_ADDR 0
-#define YT921X_SMI_DATA YT921X_SMI_AD
-#define YT921X_SMI_RW BIT(0)
-#define YT921X_SMI_WRITE 0
-#define YT921X_SMI_READ YT921X_SMI_RW
-
-#define YT921X_SWITCHID_NUM 4
-
-#define YT921X_RST 0x80000
-#define YT921X_RST_HW BIT(31)
-#define YT921X_RST_SW BIT(1)
-#define YT921X_FUNC 0x80004
-#define YT921X_FUNC_METER BIT(4)
-#define YT921X_FUNC_ACL BIT(2)
-#define YT921X_FUNC_MIB BIT(1)
-#define YT921X_CHIP_ID 0x80008
-#define YT921X_CHIP_ID_MAJOR GENMASK(31, 16)
-#define YT921X_EXT_CPU_PORT 0x8000c
-#define YT921X_EXT_CPU_PORT_TAG_EN BIT(15)
-#define YT921X_EXT_CPU_PORT_PORT_EN BIT(14)
-#define YT921X_EXT_CPU_PORT_PORT_M GENMASK(3, 0)
-#define YT921X_EXT_CPU_PORT_PORT(x) FIELD_PREP(YT921X_EXT_CPU_PORT_PORT_M, (x))
-#define YT921X_CPU_TAG_TPID 0x80010
-#define YT921X_CPU_TAG_TPID_TPID_M GENMASK(15, 0)
-/* Same as ETH_P_YT921X, but this represents the true HW default, while the
- * former is a local convention chosen by us.
- */
-#define YT921X_CPU_TAG_TPID_TPID_DEFAULT 0x9988
-#define YT921X_PVID_SEL 0x80014
-#define YT921X_PVID_SEL_SVID_PORTn(port) BIT(port)
-#define YT921X_SERDES_CTRL 0x80028
-#define YT921X_SERDES_CTRL_PORTn_TEST(port) BIT((port) - 3)
-#define YT921X_SERDES_CTRL_PORTn(port) BIT((port) - 8)
-#define YT921X_IO_LEVEL 0x80030
-#define YT9215_IO_LEVEL_NORMAL_M GENMASK(5, 4)
-#define YT9215_IO_LEVEL_NORMAL(x) FIELD_PREP(YT9215_IO_LEVEL_NORMAL_M, (x))
-#define YT9215_IO_LEVEL_NORMAL_3V3 YT9215_IO_LEVEL_NORMAL(0)
-#define YT9215_IO_LEVEL_NORMAL_1V8 YT9215_IO_LEVEL_NORMAL(3)
-#define YT9215_IO_LEVEL_RGMII1_M GENMASK(3, 2)
-#define YT9215_IO_LEVEL_RGMII1(x) FIELD_PREP(YT9215_IO_LEVEL_RGMII1_M, (x))
-#define YT9215_IO_LEVEL_RGMII1_3V3 YT9215_IO_LEVEL_RGMII1(0)
-#define YT9215_IO_LEVEL_RGMII1_2V5 YT9215_IO_LEVEL_RGMII1(1)
-#define YT9215_IO_LEVEL_RGMII1_1V8 YT9215_IO_LEVEL_RGMII1(2)
-#define YT9215_IO_LEVEL_RGMII0_M GENMASK(1, 0)
-#define YT9215_IO_LEVEL_RGMII0(x) FIELD_PREP(YT9215_IO_LEVEL_RGMII0_M, (x))
-#define YT9215_IO_LEVEL_RGMII0_3V3 YT9215_IO_LEVEL_RGMII0(0)
-#define YT9215_IO_LEVEL_RGMII0_2V5 YT9215_IO_LEVEL_RGMII0(1)
-#define YT9215_IO_LEVEL_RGMII0_1V8 YT9215_IO_LEVEL_RGMII0(2)
-#define YT9218_IO_LEVEL_RGMII1_M GENMASK(5, 4)
-#define YT9218_IO_LEVEL_RGMII1(x) FIELD_PREP(YT9218_IO_LEVEL_RGMII1_M, (x))
-#define YT9218_IO_LEVEL_RGMII1_3V3 YT9218_IO_LEVEL_RGMII1(0)
-#define YT9218_IO_LEVEL_RGMII1_2V5 YT9218_IO_LEVEL_RGMII1(1)
-#define YT9218_IO_LEVEL_RGMII1_1V8 YT9218_IO_LEVEL_RGMII1(2)
-#define YT9218_IO_LEVEL_RGMII0_M GENMASK(3, 2)
-#define YT9218_IO_LEVEL_RGMII0(x) FIELD_PREP(YT9218_IO_LEVEL_RGMII0_M, (x))
-#define YT9218_IO_LEVEL_RGMII0_3V3 YT9218_IO_LEVEL_RGMII0(0)
-#define YT9218_IO_LEVEL_RGMII0_2V5 YT9218_IO_LEVEL_RGMII0(1)
-#define YT9218_IO_LEVEL_RGMII0_1V8 YT9218_IO_LEVEL_RGMII0(2)
-#define YT9218_IO_LEVEL_NORMAL_M GENMASK(1, 0)
-#define YT9218_IO_LEVEL_NORMAL(x) FIELD_PREP(YT9218_IO_LEVEL_NORMAL_M, (x))
-#define YT9218_IO_LEVEL_NORMAL_3V3 YT9218_IO_LEVEL_NORMAL(0)
-#define YT9218_IO_LEVEL_NORMAL_1V8 YT9218_IO_LEVEL_NORMAL(3)
-#define YT921X_MAC_ADDR_HI2 0x80080
-#define YT921X_MAC_ADDR_LO4 0x80084
-#define YT921X_SERDESn(port) (0x8008c + 4 * ((port) - 8))
-#define YT921X_SERDES_MODE_M GENMASK(9, 7)
-#define YT921X_SERDES_MODE(x) FIELD_PREP(YT921X_SERDES_MODE_M, (x))
-#define YT921X_SERDES_MODE_SGMII YT921X_SERDES_MODE(0)
-#define YT921X_SERDES_MODE_REVSGMII YT921X_SERDES_MODE(1)
-#define YT921X_SERDES_MODE_1000BASEX YT921X_SERDES_MODE(2)
-#define YT921X_SERDES_MODE_100BASEX YT921X_SERDES_MODE(3)
-#define YT921X_SERDES_MODE_2500BASEX YT921X_SERDES_MODE(4)
-#define YT921X_SERDES_RX_PAUSE BIT(6)
-#define YT921X_SERDES_TX_PAUSE BIT(5)
-#define YT921X_SERDES_LINK BIT(4) /* force link */
-#define YT921X_SERDES_DUPLEX_FULL BIT(3)
-#define YT921X_SERDES_SPEED_M GENMASK(2, 0)
-#define YT921X_SERDES_SPEED(x) FIELD_PREP(YT921X_SERDES_SPEED_M, (x))
-#define YT921X_SERDES_SPEED_10 YT921X_SERDES_SPEED(0)
-#define YT921X_SERDES_SPEED_100 YT921X_SERDES_SPEED(1)
-#define YT921X_SERDES_SPEED_1000 YT921X_SERDES_SPEED(2)
-#define YT921X_SERDES_SPEED_10000 YT921X_SERDES_SPEED(3)
-#define YT921X_SERDES_SPEED_2500 YT921X_SERDES_SPEED(4)
-#define YT921X_PORTn_CTRL(port) (0x80100 + 4 * (port))
-#define YT921X_PORT_CTRL_PAUSE_AN BIT(10)
-#define YT921X_PORTn_STATUS(port) (0x80200 + 4 * (port))
-#define YT921X_PORT_LINK BIT(9) /* CTRL: auto negotiation */
-#define YT921X_PORT_HALF_PAUSE BIT(8) /* Half-duplex back pressure mode */
-#define YT921X_PORT_DUPLEX_FULL BIT(7)
-#define YT921X_PORT_RX_PAUSE BIT(6)
-#define YT921X_PORT_TX_PAUSE BIT(5)
-#define YT921X_PORT_RX_MAC_EN BIT(4)
-#define YT921X_PORT_TX_MAC_EN BIT(3)
-#define YT921X_PORT_SPEED_M GENMASK(2, 0)
-#define YT921X_PORT_SPEED(x) FIELD_PREP(YT921X_PORT_SPEED_M, (x))
-#define YT921X_PORT_SPEED_10 YT921X_PORT_SPEED(0)
-#define YT921X_PORT_SPEED_100 YT921X_PORT_SPEED(1)
-#define YT921X_PORT_SPEED_1000 YT921X_PORT_SPEED(2)
-#define YT921X_PORT_SPEED_10000 YT921X_PORT_SPEED(3)
-#define YT921X_PORT_SPEED_2500 YT921X_PORT_SPEED(4)
-#define YT921X_PON_STRAP_FUNC 0x80320
-#define YT921X_PON_STRAP_VAL 0x80324
-#define YT921X_PON_STRAP_CAP 0x80328
-#define YT921X_PON_STRAP_EEE BIT(16)
-#define YT921X_PON_STRAP_LOOP_DETECT BIT(7)
-#define YT921X_MDIO_POLLINGn(port) (0x80364 + 4 * ((port) - 8))
-#define YT921X_MDIO_POLLING_DUPLEX_FULL BIT(4)
-#define YT921X_MDIO_POLLING_LINK BIT(3)
-#define YT921X_MDIO_POLLING_SPEED_M GENMASK(2, 0)
-#define YT921X_MDIO_POLLING_SPEED(x) FIELD_PREP(YT921X_MDIO_POLLING_SPEED_M, (x))
-#define YT921X_MDIO_POLLING_SPEED_10 YT921X_MDIO_POLLING_SPEED(0)
-#define YT921X_MDIO_POLLING_SPEED_100 YT921X_MDIO_POLLING_SPEED(1)
-#define YT921X_MDIO_POLLING_SPEED_1000 YT921X_MDIO_POLLING_SPEED(2)
-#define YT921X_MDIO_POLLING_SPEED_10000 YT921X_MDIO_POLLING_SPEED(3)
-#define YT921X_MDIO_POLLING_SPEED_2500 YT921X_MDIO_POLLING_SPEED(4)
-#define YT921X_SENSOR 0x8036c
-#define YT921X_SENSOR_TEMP BIT(18)
-#define YT921X_TEMP 0x80374
-#define YT921X_CHIP_MODE 0x80388
-#define YT921X_CHIP_MODE_MODE GENMASK(1, 0)
-#define YT921X_XMII_CTRL 0x80394
-#define YT921X_XMII_CTRL_PORTn(port) BIT(9 - (port)) /* Yes, it's reversed */
-#define YT921X_XMIIn(port) (0x80400 + 8 * ((port) - 8))
-#define YT921X_XMII_MODE_M GENMASK(31, 29)
-#define YT921X_XMII_MODE(x) FIELD_PREP(YT921X_XMII_MODE_M, (x))
-#define YT921X_XMII_MODE_MII YT921X_XMII_MODE(0)
-#define YT921X_XMII_MODE_REVMII YT921X_XMII_MODE(1)
-#define YT921X_XMII_MODE_RMII YT921X_XMII_MODE(2)
-#define YT921X_XMII_MODE_REVRMII YT921X_XMII_MODE(3)
-#define YT921X_XMII_MODE_RGMII YT921X_XMII_MODE(4)
-#define YT921X_XMII_MODE_DISABLE YT921X_XMII_MODE(5)
-#define YT921X_XMII_LINK BIT(19) /* force link */
-#define YT921X_XMII_EN BIT(18)
-#define YT921X_XMII_SOFT_RST BIT(17)
-#define YT921X_XMII_RGMII_TX_DELAY_150PS_M GENMASK(16, 13)
-#define YT921X_XMII_RGMII_TX_DELAY_150PS(x) FIELD_PREP(YT921X_XMII_RGMII_TX_DELAY_150PS_M, (x))
-#define YT921X_XMII_TX_CLK_IN BIT(11)
-#define YT921X_XMII_RX_CLK_IN BIT(10)
-#define YT921X_XMII_RGMII_TX_DELAY_2NS BIT(8)
-#define YT921X_XMII_RGMII_TX_CLK_OUT BIT(7)
-#define YT921X_XMII_RGMII_RX_DELAY_150PS_M GENMASK(6, 3)
-#define YT921X_XMII_RGMII_RX_DELAY_150PS(x) FIELD_PREP(YT921X_XMII_RGMII_RX_DELAY_150PS_M, (x))
-#define YT921X_XMII_RMII_PHY_TX_CLK_OUT BIT(2)
-#define YT921X_XMII_REVMII_TX_CLK_OUT BIT(1)
-#define YT921X_XMII_REVMII_RX_CLK_OUT BIT(0)
-
-#define YT921X_MACn_FRAME(port) (0x81008 + 0x1000 * (port))
-#define YT921X_MAC_FRAME_SIZE_M GENMASK(21, 8)
-#define YT921X_MAC_FRAME_SIZE(x) FIELD_PREP(YT921X_MAC_FRAME_SIZE_M, (x))
-
-#define YT921X_EEEn_VAL(port) (0xa0000 + 0x40 * (port))
-#define YT921X_EEE_VAL_DATA BIT(1)
-
-#define YT921X_EEE_CTRL 0xb0000
-#define YT921X_EEE_CTRL_ENn(port) BIT(port)
-
-#define YT921X_MIB_CTRL 0xc0004
-#define YT921X_MIB_CTRL_CLEAN BIT(30)
-#define YT921X_MIB_CTRL_PORT_M GENMASK(6, 3)
-#define YT921X_MIB_CTRL_PORT(x) FIELD_PREP(YT921X_MIB_CTRL_PORT_M, (x))
-#define YT921X_MIB_CTRL_ONE_PORT BIT(1)
-#define YT921X_MIB_CTRL_ALL_PORT BIT(0)
-#define YT921X_MIBn_DATA0(port) (0xc0100 + 0x100 * (port))
-#define YT921X_MIBn_DATAm(port, x) (YT921X_MIBn_DATA0(port) + 4 * (x))
-#define YT921X_MIB_DATA_RX_BROADCAST 0x00
-#define YT921X_MIB_DATA_RX_PAUSE 0x04
-#define YT921X_MIB_DATA_RX_MULTICAST 0x08
-#define YT921X_MIB_DATA_RX_CRC_ERR 0x0c
-
-#define YT921X_MIB_DATA_RX_ALIGN_ERR 0x10
-#define YT921X_MIB_DATA_RX_UNDERSIZE_ERR 0x14
-#define YT921X_MIB_DATA_RX_FRAG_ERR 0x18
-#define YT921X_MIB_DATA_RX_PKT_SZ_64 0x1c
-
-#define YT921X_MIB_DATA_RX_PKT_SZ_65_TO_127 0x20
-#define YT921X_MIB_DATA_RX_PKT_SZ_128_TO_255 0x24
-#define YT921X_MIB_DATA_RX_PKT_SZ_256_TO_511 0x28
-#define YT921X_MIB_DATA_RX_PKT_SZ_512_TO_1023 0x2c
-
-#define YT921X_MIB_DATA_RX_PKT_SZ_1024_TO_1518 0x30
-#define YT921X_MIB_DATA_RX_PKT_SZ_1519_TO_MAX 0x34
-/* 0x38: unused */
-#define YT921X_MIB_DATA_RX_GOOD_BYTES 0x3c
-
-/* 0x40: 64 bytes */
-#define YT921X_MIB_DATA_RX_BAD_BYTES 0x44
-/* 0x48: 64 bytes */
-#define YT921X_MIB_DATA_RX_OVERSIZE_ERR 0x4c
-
-#define YT921X_MIB_DATA_RX_DROPPED 0x50
-#define YT921X_MIB_DATA_TX_BROADCAST 0x54
-#define YT921X_MIB_DATA_TX_PAUSE 0x58
-#define YT921X_MIB_DATA_TX_MULTICAST 0x5c
-
-#define YT921X_MIB_DATA_TX_UNDERSIZE_ERR 0x60
-#define YT921X_MIB_DATA_TX_PKT_SZ_64 0x64
-#define YT921X_MIB_DATA_TX_PKT_SZ_65_TO_127 0x68
-#define YT921X_MIB_DATA_TX_PKT_SZ_128_TO_255 0x6c
-
-#define YT921X_MIB_DATA_TX_PKT_SZ_256_TO_511 0x70
-#define YT921X_MIB_DATA_TX_PKT_SZ_512_TO_1023 0x74
-#define YT921X_MIB_DATA_TX_PKT_SZ_1024_TO_1518 0x78
-#define YT921X_MIB_DATA_TX_PKT_SZ_1519_TO_MAX 0x7c
-
-/* 0x80: unused */
-#define YT921X_MIB_DATA_TX_GOOD_BYTES 0x84
-/* 0x88: 64 bytes */
-#define YT921X_MIB_DATA_TX_COLLISION 0x8c
-
-#define YT921X_MIB_DATA_TX_EXCESSIVE_COLLISION 0x90
-#define YT921X_MIB_DATA_TX_MULTIPLE_COLLISION 0x94
-#define YT921X_MIB_DATA_TX_SINGLE_COLLISION 0x98
-#define YT921X_MIB_DATA_TX_PKT 0x9c
-
-#define YT921X_MIB_DATA_TX_DEFERRED 0xa0
-#define YT921X_MIB_DATA_TX_LATE_COLLISION 0xa4
-#define YT921X_MIB_DATA_RX_OAM 0xa8
-#define YT921X_MIB_DATA_TX_OAM 0xac
-
-#define YT921X_EDATA_CTRL 0xe0000
-#define YT921X_EDATA_CTRL_ADDR_M GENMASK(15, 8)
-#define YT921X_EDATA_CTRL_ADDR(x) FIELD_PREP(YT921X_EDATA_CTRL_ADDR_M, (x))
-#define YT921X_EDATA_CTRL_OP_M GENMASK(3, 0)
-#define YT921X_EDATA_CTRL_OP(x) FIELD_PREP(YT921X_EDATA_CTRL_OP_M, (x))
-#define YT921X_EDATA_CTRL_READ YT921X_EDATA_CTRL_OP(5)
-#define YT921X_EDATA_DATA 0xe0004
-#define YT921X_EDATA_DATA_DATA_M GENMASK(31, 24)
-#define YT921X_EDATA_DATA_STATUS_M GENMASK(3, 0)
-#define YT921X_EDATA_DATA_STATUS(x) FIELD_PREP(YT921X_EDATA_DATA_STATUS_M, (x))
-#define YT921X_EDATA_DATA_IDLE YT921X_EDATA_DATA_STATUS(3)
-#define YT921X_SYS_CLK 0xe0040
-#define YT921X_SYS_CLK_SEL_M GENMASK(1, 0) /* unknown: 167M */
-#define YT9215_SYS_CLK_125M 0
-#define YT9218_SYS_CLK_167M 0
-#define YT921X_SYS_CLK_143M 1
-
-#define YT921X_EXT_MBUS_OP 0x6a000
-#define YT921X_INT_MBUS_OP 0xf0000
-#define YT921X_MBUS_OP_START BIT(0)
-#define YT921X_EXT_MBUS_CTRL 0x6a004
-#define YT921X_INT_MBUS_CTRL 0xf0004
-#define YT921X_MBUS_CTRL_PORT_M GENMASK(25, 21)
-#define YT921X_MBUS_CTRL_PORT(x) FIELD_PREP(YT921X_MBUS_CTRL_PORT_M, (x))
-#define YT921X_MBUS_CTRL_REG_M GENMASK(20, 16)
-#define YT921X_MBUS_CTRL_REG(x) FIELD_PREP(YT921X_MBUS_CTRL_REG_M, (x))
-#define YT921X_MBUS_CTRL_TYPE_M GENMASK(11, 8) /* wild guess */
-#define YT921X_MBUS_CTRL_TYPE(x) FIELD_PREP(YT921X_MBUS_CTRL_TYPE_M, (x))
-#define YT921X_MBUS_CTRL_TYPE_C22 YT921X_MBUS_CTRL_TYPE(4)
-#define YT921X_MBUS_CTRL_OP_M GENMASK(3, 2) /* wild guess */
-#define YT921X_MBUS_CTRL_OP(x) FIELD_PREP(YT921X_MBUS_CTRL_OP_M, (x))
-#define YT921X_MBUS_CTRL_WRITE YT921X_MBUS_CTRL_OP(1)
-#define YT921X_MBUS_CTRL_READ YT921X_MBUS_CTRL_OP(2)
-#define YT921X_EXT_MBUS_DOUT 0x6a008
-#define YT921X_INT_MBUS_DOUT 0xf0008
-#define YT921X_EXT_MBUS_DIN 0x6a00c
-#define YT921X_INT_MBUS_DIN 0xf000c
-
-#define YT921X_PORTn_EGR(port) (0x100000 + 4 * (port))
-#define YT921X_PORT_EGR_TPID_CTAG_M GENMASK(5, 4)
-#define YT921X_PORT_EGR_TPID_CTAG(x) FIELD_PREP(YT921X_PORT_EGR_TPID_CTAG_M, (x))
-#define YT921X_PORT_EGR_TPID_STAG_M GENMASK(3, 2)
-#define YT921X_PORT_EGR_TPID_STAG(x) FIELD_PREP(YT921X_PORT_EGR_TPID_STAG_M, (x))
-#define YT921X_TPID_EGRn(x) (0x100300 + 4 * (x)) /* [0, 3] */
-#define YT921X_TPID_EGR_TPID_M GENMASK(15, 0)
-
-#define YT921X_IPM_DSCPn(n) (0x180000 + 4 * (n)) /* Internal Priority Map */
-#define YT921X_IPM_PCPn(map, dei, pcp) (0x180100 + 4 * (16 * (map) + 8 * (dei) + (pcp)))
-#define YT921X_IPM_PRIO_M GENMASK(4, 2)
-#define YT921X_IPM_PRIO(x) FIELD_PREP(YT921X_IPM_PRIO_M, (x))
-#define YT921X_IPM_COLOR_M GENMASK(1, 0)
-#define YT921X_IPM_COLOR(x) FIELD_PREP(YT921X_IPM_COLOR_M, (x))
-#define YT921X_IPM_COLOR_GREEN YT921X_IPM_COLOR(0)
-#define YT921X_IPM_COLOR_YELLOW YT921X_IPM_COLOR(1)
-#define YT921X_IPM_COLOR_RED YT921X_IPM_COLOR(2)
-#define YT921X_PORTn_QOS(port) (0x180180 + 4 * (port))
-#define YT921X_PORT_QOS_CVLAN_PRIO_MAP_ID BIT(5)
-#define YT921X_PORT_QOS_SVLAN_PRIO_MAP_ID BIT(4)
-#define YT921X_PORT_QOS_PRIO_M GENMASK(3, 1)
-#define YT921X_PORT_QOS_PRIO(x) FIELD_PREP(YT921X_PORT_QOS_PRIO_M, (x))
-#define YT921X_PORT_QOS_PRIO_EN BIT(0)
-#define YT921X_PORTn_PRIO_ORD(port) (0x180200 + 4 * (port))
-#define YT921X_PORT_PRIO_ORD_APPm_M(m) GENMASK(3 * (m) + 2, 3 * (m))
-#define YT921X_PORT_PRIO_ORD_APPm(m, x) ((x) << (3 * (m))) /* 0: disabled, except PORT_QOS_PRIO */
-
-enum yt921x_app_selector {
- YT921X_APP_SEL_MAC_SA,
- YT921X_APP_SEL_MAC_DA,
- YT921X_APP_SEL_VID,
- YT921X_APP_SEL_ACL,
- YT921X_APP_SEL_DSCP,
- YT921X_APP_SEL_CVLAN_PCP,
- YT921X_APP_SEL_SVLAN_PCP,
- /* The physical port, i.e. YT921X_PORT_QOS_PRIO */
- YT921X_APP_SEL_PORT,
- YT921X_APP_SEL_NUM
-};
-
-#define YT921X_VLAN_IGR_FILTER 0x180280
-#define YT921X_VLAN_IGR_FILTER_PORTn_BYPASS_IGMP(port) BIT((port) + 11)
-#define YT921X_VLAN_IGR_FILTER_PORTn(port) BIT(port)
-#define YT921X_PORTn_ISOLATION(port) (0x180294 + 4 * (port))
-#define YT921X_PORT_ISOLATION_BLOCKn(port) BIT(port)
-#define YT921X_STPn(n) (0x18038c + 4 * (n))
-#define YT921X_STP_PORTn_M(port) GENMASK(2 * (port) + 1, 2 * (port))
-#define YT921X_STP_PORTn(port, x) ((x) << (2 * (port)))
-#define YT921X_STP_PORTn_DISABLED(port) YT921X_STP_PORTn(port, 0)
-#define YT921X_STP_PORTn_LEARNING(port) YT921X_STP_PORTn(port, 1)
-#define YT921X_STP_PORTn_BLOCKING(port) YT921X_STP_PORTn(port, 2)
-#define YT921X_STP_PORTn_FORWARD(port) YT921X_STP_PORTn(port, 3)
-#define YT921X_PORTn_LEARN(port) (0x1803d0 + 4 * (port))
-#define YT921X_PORT_LEARN_VID_LEARN_MULTI_EN BIT(22)
-#define YT921X_PORT_LEARN_VID_LEARN_MODE BIT(21)
-#define YT921X_PORT_LEARN_VID_LEARN_EN BIT(20)
-#define YT921X_PORT_LEARN_SUSPEND_COPY_EN BIT(19)
-#define YT921X_PORT_LEARN_SUSPEND_DROP_EN BIT(18)
-#define YT921X_PORT_LEARN_DIS BIT(17)
-#define YT921X_PORT_LEARN_LIMIT_EN BIT(16)
-#define YT921X_PORT_LEARN_LIMIT_M GENMASK(15, 8)
-#define YT921X_PORT_LEARN_LIMIT(x) FIELD_PREP(YT921X_PORT_LEARN_LIMIT_M, (x))
-#define YT921X_PORT_LEARN_DROP_ON_EXCEEDED BIT(2)
-#define YT921X_PORT_LEARN_MODE_M GENMASK(1, 0)
-#define YT921X_PORT_LEARN_MODE(x) FIELD_PREP(YT921X_PORT_LEARN_MODE_M, (x))
-#define YT921X_PORT_LEARN_MODE_AUTO YT921X_PORT_LEARN_MODE(0)
-#define YT921X_PORT_LEARN_MODE_AUTO_AND_COPY YT921X_PORT_LEARN_MODE(1)
-#define YT921X_PORT_LEARN_MODE_CPU_CONTROL YT921X_PORT_LEARN_MODE(2)
-#define YT921X_AGEING 0x180440
-#define YT921X_AGEING_INTERVAL_M GENMASK(15, 0)
-#define YT921X_FDB_IN0 0x180454
-#define YT921X_FDB_IN1 0x180458
-#define YT921X_FDB_IN2 0x18045c
-#define YT921X_FDB_OP 0x180460
-#define YT921X_FDB_OP_INDEX_M GENMASK(22, 11)
-#define YT921X_FDB_OP_INDEX(x) FIELD_PREP(YT921X_FDB_OP_INDEX_M, (x))
-#define YT921X_FDB_OP_MODE_INDEX BIT(10) /* mac+fid / index */
-#define YT921X_FDB_OP_FLUSH_MCAST BIT(9) /* ucast / mcast */
-#define YT921X_FDB_OP_FLUSH_M GENMASK(8, 7)
-#define YT921X_FDB_OP_FLUSH(x) FIELD_PREP(YT921X_FDB_OP_FLUSH_M, (x))
-#define YT921X_FDB_OP_FLUSH_ALL YT921X_FDB_OP_FLUSH(0)
-#define YT921X_FDB_OP_FLUSH_PORT YT921X_FDB_OP_FLUSH(1)
-#define YT921X_FDB_OP_FLUSH_PORT_VID YT921X_FDB_OP_FLUSH(2)
-#define YT921X_FDB_OP_FLUSH_VID YT921X_FDB_OP_FLUSH(3)
-#define YT921X_FDB_OP_FLUSH_STATIC BIT(6)
-#define YT921X_FDB_OP_NEXT_TYPE_M GENMASK(5, 4)
-#define YT921X_FDB_OP_NEXT_TYPE(x) FIELD_PREP(YT921X_FDB_OP_NEXT_TYPE_M, (x))
-#define YT921X_FDB_OP_NEXT_TYPE_UCAST_PORT YT921X_FDB_OP_NEXT_TYPE(0)
-#define YT921X_FDB_OP_NEXT_TYPE_UCAST_VID YT921X_FDB_OP_NEXT_TYPE(1)
-#define YT921X_FDB_OP_NEXT_TYPE_UCAST YT921X_FDB_OP_NEXT_TYPE(2)
-#define YT921X_FDB_OP_NEXT_TYPE_MCAST YT921X_FDB_OP_NEXT_TYPE(3)
-#define YT921X_FDB_OP_OP_M GENMASK(3, 1)
-#define YT921X_FDB_OP_OP(x) FIELD_PREP(YT921X_FDB_OP_OP_M, (x))
-#define YT921X_FDB_OP_OP_ADD YT921X_FDB_OP_OP(0)
-#define YT921X_FDB_OP_OP_DEL YT921X_FDB_OP_OP(1)
-#define YT921X_FDB_OP_OP_GET_ONE YT921X_FDB_OP_OP(2)
-#define YT921X_FDB_OP_OP_GET_NEXT YT921X_FDB_OP_OP(3)
-#define YT921X_FDB_OP_OP_FLUSH YT921X_FDB_OP_OP(4)
-#define YT921X_FDB_OP_START BIT(0)
-#define YT921X_FDB_RESULT 0x180464
-#define YT921X_FDB_RESULT_DONE BIT(15)
-#define YT921X_FDB_RESULT_NOTFOUND BIT(14)
-#define YT921X_FDB_RESULT_OVERWRITED BIT(13)
-#define YT921X_FDB_RESULT_INDEX_M GENMASK(11, 0)
-#define YT921X_FDB_RESULT_INDEX(x) FIELD_PREP(YT921X_FDB_RESULT_INDEX_M, (x))
-#define YT921X_FDB_OUT0 0x1804b0
-#define YT921X_FDB_IO0_ADDR_HI4_M GENMASK(31, 0)
-#define YT921X_FDB_OUT1 0x1804b4
-#define YT921X_FDB_IO1_EGR_PRIO_EN BIT(31)
-#define YT921X_FDB_IO1_STATUS_M GENMASK(30, 28)
-#define YT921X_FDB_IO1_STATUS(x) FIELD_PREP(YT921X_FDB_IO1_STATUS_M, (x))
-#define YT921X_FDB_IO1_STATUS_INVALID YT921X_FDB_IO1_STATUS(0)
-#define YT921X_FDB_IO1_STATUS_MIN_TIME YT921X_FDB_IO1_STATUS(1)
-#define YT921X_FDB_IO1_STATUS_MOVE_AGING_MAX_TIME YT921X_FDB_IO1_STATUS(3)
-#define YT921X_FDB_IO1_STATUS_MAX_TIME YT921X_FDB_IO1_STATUS(5)
-#define YT921X_FDB_IO1_STATUS_PENDING YT921X_FDB_IO1_STATUS(6)
-#define YT921X_FDB_IO1_STATUS_STATIC YT921X_FDB_IO1_STATUS(7)
-#define YT921X_FDB_IO1_FID_M GENMASK(27, 16) /* filtering ID (VID) */
-#define YT921X_FDB_IO1_FID(x) FIELD_PREP(YT921X_FDB_IO1_FID_M, (x))
-#define YT921X_FDB_IO1_ADDR_LO2_M GENMASK(15, 0)
-#define YT921X_FDB_OUT2 0x1804b8
-#define YT921X_FDB_IO2_MOVE_AGING_STATUS_M GENMASK(31, 30)
-#define YT921X_FDB_IO2_IGR_DROP BIT(29)
-#define YT921X_FDB_IO2_EGR_PORTS_M GENMASK(28, 18)
-#define YT921X_FDB_IO2_EGR_PORTS(x) FIELD_PREP(YT921X_FDB_IO2_EGR_PORTS_M, (x))
-#define YT921X_FDB_IO2_EGR_DROP BIT(17)
-#define YT921X_FDB_IO2_COPY_TO_CPU BIT(16)
-#define YT921X_FDB_IO2_IGR_PRIO_EN BIT(15)
-#define YT921X_FDB_IO2_PRIO_M GENMASK(14, 12)
-#define YT921X_FDB_IO2_PRIO(x) FIELD_PREP(YT921X_FDB_IO2_PRIO_M, (x))
-#define YT921X_FDB_IO2_NEW_VID_M GENMASK(11, 0)
-#define YT921X_FDB_IO2_NEW_VID(x) FIELD_PREP(YT921X_FDB_IO2_NEW_VID_M, (x))
-#define YT921X_FILTER_UNK_UCAST 0x180508
-#define YT921X_FILTER_UNK_MCAST 0x18050c
-#define YT921X_FILTER_MCAST 0x180510
-#define YT921X_FILTER_BCAST 0x180514
-#define YT921X_FILTER_PORTS_M GENMASK(10, 0)
-#define YT921X_FILTER_PORTS(x) FIELD_PREP(YT921X_FILTER_PORTS_M, (x))
-#define YT921X_FILTER_PORTn(port) BIT(port)
-#define YT921X_VLAN_EGR_FILTER 0x180598
-#define YT921X_VLAN_EGR_FILTER_PORTn(port) BIT(port)
-#define YT921X_LAG_GROUPn(n) (0x1805a8 + 4 * (n))
-#define YT921X_LAG_GROUP_PORTS_M GENMASK(13, 3)
-#define YT921X_LAG_GROUP_PORTS(x) FIELD_PREP(YT921X_LAG_GROUP_PORTS_M, (x))
-#define YT921X_LAG_GROUP_MEMBER_NUM_M GENMASK(2, 0)
-#define YT921X_LAG_GROUP_MEMBER_NUM(x) FIELD_PREP(YT921X_LAG_GROUP_MEMBER_NUM_M, (x))
-#define YT921X_LAG_MEMBERnm(n, m) (0x1805b0 + 4 * (4 * (n) + (m)))
-#define YT921X_LAG_MEMBER_PORT_M GENMASK(3, 0)
-#define YT921X_LAG_MEMBER_PORT(x) FIELD_PREP(YT921X_LAG_MEMBER_PORT_M, (x))
-#define YT921X_CPU_COPY 0x180690
-#define YT921X_CPU_COPY_FORCE_INT_PORT BIT(2)
-#define YT921X_CPU_COPY_TO_INT_CPU BIT(1)
-#define YT921X_CPU_COPY_TO_EXT_CPU BIT(0)
-#define YT921X_ACL_PERMIT_UNMATCH 0x1806a0
-#define YT921X_ACL_PERMIT_UNMATCH_PORTS_M GENMASK(10, 0)
-#define YT921X_ACL_PERMIT_UNMATCH_PORTS(x) FIELD_PREP(YT921X_ACL_PERMIT_UNMATCH_PORTS_M, (x))
-#define YT921X_ACL_PERMIT_UNMATCH_PORTn(port) BIT(port)
-#define YT921X_ACT_UNK_UCAST 0x180734
-#define YT921X_ACT_UNK_MCAST 0x180738
-#define YT921X_ACT_UNK_MCAST_BYPASS_DROP_RMA BIT(23)
-#define YT921X_ACT_UNK_MCAST_BYPASS_DROP_IGMP BIT(22)
-#define YT921X_ACT_UNK_ACTn_M(port) GENMASK(2 * (port) + 1, 2 * (port))
-#define YT921X_ACT_UNK_ACTn(port, x) ((x) << (2 * (port)))
-#define YT921X_ACT_UNK_ACTn_FORWARD(port) YT921X_ACT_UNK_ACTn(port, 0) /* flood */
-#define YT921X_ACT_UNK_ACTn_TRAP(port) YT921X_ACT_UNK_ACTn(port, 1) /* steer to CPU */
-#define YT921X_ACT_UNK_ACTn_DROP(port) YT921X_ACT_UNK_ACTn(port, 2) /* discard */
-/* NEVER use this action; see comments in the tag driver */
-#define YT921X_ACT_UNK_ACTn_COPY(port) YT921X_ACT_UNK_ACTn(port, 3) /* flood and copy */
-#define YT921X_FDB_HW_FLUSH 0x180958
-#define YT921X_FDB_HW_FLUSH_ON_LINKDOWN BIT(0)
-
-#define YT921X_VLANn_CTRL(vlan) (0x188000 + 8 * (vlan))
-#define YT921X_VLAN_CTRLb_UNTAG_PORTS_M GENMASK(18, 8)
-#define YT921X_VLAN_CTRLb_UNTAG_PORTS(x) FIELD_PREP(YT921X_VLAN_CTRLb_UNTAG_PORTS_M, (x))
-#define YT921X_VLAN_CTRLb_UNTAG_PORTn(port) BIT((port) + 8)
-#define YT921X_VLAN_CTRLb_STP_ID_M GENMASK(7, 4)
-#define YT921X_VLAN_CTRLb_STP_ID(x) FIELD_PREP(YT921X_VLAN_CTRLb_STP_ID_M, (x))
-#define YT921X_VLAN_CTRLb_SVLAN_EN BIT(3)
-#define YT921X_VLAN_CTRLab_FID_M GENMASK_ULL(34, 23)
-#define YT921X_VLAN_CTRLab_FID(x) FIELD_PREP(YT921X_VLAN_CTRLab_FID_M, (x))
-#define YT921X_VLAN_CTRLa_LEARN_DIS BIT(22)
-#define YT921X_VLAN_CTRLa_PRIO_EN BIT(21)
-#define YT921X_VLAN_CTRLa_PRIO_M GENMASK(20, 18)
-#define YT921X_VLAN_CTRLa_PRIO(x) FIELD_PREP(YT921X_VLAN_CTRLa_PRIO_M, (x))
-#define YT921X_VLAN_CTRLa_PORTS_M GENMASK(17, 7)
-#define YT921X_VLAN_CTRLa_PORTS(x) FIELD_PREP(YT921X_VLAN_CTRLa_PORTS_M, (x))
-#define YT921X_VLAN_CTRLa_PORTn(port) BIT((port) + 7)
-#define YT921X_VLAN_CTRLa_BYPASS_1X_AC BIT(6)
-#define YT921X_VLAN_CTRLa_METER_EN BIT(5)
-#define YT921X_VLAN_CTRLa_METER_ID_M GENMASK(4, 0)
-
-#define YT921X_ACLn_ACT(n) (0x1c0000 + 0x10 * (n))
-#define YT921X_ACL_ACTc_STAG_M GENMASK(26, 25)
-#define YT921X_ACL_ACTc_STAG(x) FIELD_PREP(YT921X_ACL_ACTc_STAG_M, (x))
-#define YT921X_ACL_ACTc_STAG_DONTCARE YT921X_ACL_ACTc_STAG(0)
-#define YT921X_ACL_ACTc_STAG_UNTAG YT921X_ACL_ACTc_STAG(1)
-#define YT921X_ACL_ACTc_STAG_TAG YT921X_ACL_ACTc_STAG(2)
-#define YT921X_ACL_ACTc_STAG_KEEP YT921X_ACL_ACTc_STAG(3)
-#define YT921X_ACL_ACTc_CTAG_M GENMASK(24, 23)
-#define YT921X_ACL_ACTc_CTAG(x) FIELD_PREP(YT921X_ACL_ACTc_CTAG_M, (x))
-#define YT921X_ACL_ACTc_CTAG_DONTCARE YT921X_ACL_ACTc_CTAG(0)
-#define YT921X_ACL_ACTc_CTAG_UNTAG YT921X_ACL_ACTc_CTAG(1)
-#define YT921X_ACL_ACTc_CTAG_TAG YT921X_ACL_ACTc_CTAG(2)
-#define YT921X_ACL_ACTc_CTAG_KEEP YT921X_ACL_ACTc_CTAG(3)
-#define YT921X_ACL_ACTc_FWD_M GENMASK(22, 21)
-#define YT921X_ACL_ACTc_FWD(x) FIELD_PREP(YT921X_ACL_ACTc_FWD_M, (x))
-#define YT921X_ACL_ACTc_FWD_FWD YT921X_ACL_ACTc_FWD(0)
-#define YT921X_ACL_ACTc_FWD_COPY YT921X_ACL_ACTc_FWD(1)
-#define YT921X_ACL_ACTc_FWD_REDIR YT921X_ACL_ACTc_FWD(2)
-#define YT921X_ACL_ACTc_FWD_TRAP YT921X_ACL_ACTc_FWD(3)
-#define YT921X_ACL_ACTc_FWD_REDIR_DPORTS_M GENMASK(20, 10)
-#define YT921X_ACL_ACTc_FWD_REDIR_DPORTS(x) FIELD_PREP(YT921X_ACL_ACTc_FWD_REDIR_DPORTS_M, (x))
-#define YT921X_ACL_ACTc_FWD_REDIR_DPORTn(port) BIT((port) + 10)
-#define YT921X_ACL_ACTc_FWD_EN BIT(9)
-#define YT921X_ACL_ACTc_SDEI BIT(8)
-#define YT921X_ACL_ACTc_SDEI_REPLACE BIT(7)
-#define YT921X_ACL_ACTc_SPRI_M GENMASK(6, 4)
-#define YT921X_ACL_ACTc_SPRI(x) FIELD_PREP(YT921X_ACL_ACTc_SPRI_M, (x))
-#define YT921X_ACL_ACTc_SPRI_REPLACE BIT(3)
-#define YT921X_ACL_ACTbc_SVID_M GENMASK_ULL(34, 23)
-#define YT921X_ACL_ACTbc_SVID(x) FIELD_PREP(YT921X_ACL_ACTbc_SVID_M, (x))
-#define YT921X_ACL_ACTb_SVID_REPLACE BIT(22)
-#define YT921X_ACL_ACTb_CDEI BIT(21)
-#define YT921X_ACL_ACTb_CDEI_REPLACE BIT(20)
-#define YT921X_ACL_ACTb_CPRI_M GENMASK(19, 17)
-#define YT921X_ACL_ACTb_CPRI(x) FIELD_PREP(YT921X_ACL_ACTb_CPRI_M, (x))
-#define YT921X_ACL_ACTb_CPRI_REPLACE BIT(16)
-#define YT921X_ACL_ACTb_CVID_M GENMASK(15, 4)
-#define YT921X_ACL_ACTb_CVID(x) FIELD_PREP(YT921X_ACL_ACTb_CVID_M, (x))
-#define YT921X_ACL_ACTb_CVID_REPLACE BIT(3)
-#define YT921X_ACL_ACTb_PRIO_M GENMASK(2, 0)
-#define YT921X_ACL_ACTb_PRIO(x) FIELD_PREP(YT921X_ACL_ACTb_PRIO_M, (x))
-#define YT921X_ACL_ACTa_PRIO_EN BIT(31)
-#define YT921X_ACL_ACTa_COLOR_M GENMASK(30, 29)
-#define YT921X_ACL_ACTa_COLOR(x) FIELD_PREP(YT921X_ACL_ACTa_COLOR_M, (x))
-#define YT921X_ACL_ACTa_COLOR_GREEN YT921X_ACL_ACTa_COLOR(0)
-#define YT921X_ACL_ACTa_COLOR_YELLOW YT921X_ACL_ACTa_COLOR(1)
-#define YT921X_ACL_ACTa_COLOR_RED YT921X_ACL_ACTa_COLOR(2)
-#define YT921X_ACL_ACTa_COLOR_EN BIT(28)
-#define YT921X_ACL_ACTa_DSCP_M GENMASK(27, 22)
-#define YT921X_ACL_ACTa_DSCP(x) FIELD_PREP(YT921X_ACL_ACTa_DSCP_M, (x))
-#define YT921X_ACL_ACTa_DSCP_REPLACE BIT(21)
-#define YT921X_ACL_ACTa_METER_ID_M GENMASK(20, 15)
-#define YT921X_ACL_ACTa_METER_ID(x) FIELD_PREP(YT921X_ACL_ACTa_METER_ID_M, (x))
-#define YT921X_ACL_ACTa_METER_EN BIT(14)
-#define YT921X_ACL_ACTa_MIRROR_EN BIT(13)
-#define YT921X_ACL_ACTa_FLOWSTAT_EN BIT(12)
-#define YT921X_ACL_ACTa_FLOWSTAT_ID_M GENMASK(11, 6)
-#define YT921X_ACL_ACTa_FLOWSTAT_ID(x) FIELD_PREP(YT921X_ACL_ACTa_FLOWSTAT_ID_M, (x))
-#define YT921X_ACL_ACTa_GPIO_EN BIT(5)
-#define YT921X_ACL_ACTa_GPIO_PIN_M GENMASK(4, 1)
-#define YT921X_ACL_ACTa_GPIO_PIN(x) FIELD_PREP(YT921X_ACL_ACTa_GPIO_PIN_M, (x))
-#define YT921X_ACL_ACTa_INTR_EN BIT(0)
-#define YT921X_ACL_BLK_KEEP 0x201000
-#define YT921X_ACL_BLK_KEEP_GRPIDn_M(bin) (7 << (4 * (bin) + 1))
-#define YT921X_ACL_BLK_KEEP_GRPIDn(bin, x) ((x) << (4 * (bin) + 1))
-#define YT921X_ACL_BLK_KEEP_KEEPn(bin) BIT(4 * (bin))
-#define YT921X_ACL_PORT 0x202000
-#define YT921X_ACL_PORT_PORTS_M GENMASK(10, 0)
-#define YT921X_ACL_PORT_PORTS(x) FIELD_PREP(YT921X_ACL_PORT_PORTS_M, (x))
-#define YT921X_ACL_PORT_PORTn(port) BIT(port)
-#define YT921X_ACL_BLK_CMD 0x202004
-#define YT921X_ACL_BLK_CMD_BLKID_M GENMASK(6, 1)
-#define YT921X_ACL_BLK_CMD_BLKID(x) FIELD_PREP(YT921X_ACL_BLK_CMD_BLKID_M, (x))
-#define YT921X_ACL_BLK_CMD_MODIFY BIT(0)
-#define YT921X_ACLn_ENTRY(blk) (0x203000 + 4 * (blk))
-#define YT921X_ACL_ENTRY_GRPIDm_M(bin) (7 << (4 * (bin) + 1))
-#define YT921X_ACL_ENTRY_GRPIDm(bin, x) ((x) << (4 * (bin) + 1))
-#define YT921X_ACL_ENTRY_ENm(bin) BIT(4 * (bin))
-#define YT921X_ACLn_KEYm(blk, bin) (0x204000 + 0x200 * (bin) + 8 * (blk))
-#define YT921X_ACL_KEYb_ORD_M GENMASK(29, 21)
-#define YT921X_ACL_KEYb_ORD(x) FIELD_PREP(YT921X_ACL_KEYb_ORD_M, (x))
-#define YT921X_ACL_KEYb_SPORTS_M GENMASK(20, 10)
-#define YT921X_ACL_KEYb_SPORTS(x) FIELD_PREP(YT921X_ACL_KEYb_SPORTS_M, (x))
-#define YT921X_ACL_KEYb_SPORTn(port) BIT((port) + 10)
-#define YT921X_ACL_KEYb_REVERSE BIT(9) /* reverse match */
-#define YT921X_ACL_KEYb_TYPE_M GENMASK(8, 4)
-#define YT921X_ACL_KEYb_TYPE(x) FIELD_PREP(YT921X_ACL_KEYb_TYPE_M, (x))
-/* KEY_* fields need no masks */
-#define YT921X_ACLn_MASKm(blk, bin) (0x205000 + 0x200 * (bin) + 8 * (blk))
-
-enum yt921x_acl_type {
- YT921X_ACL_TYPE_NA,
- YT921X_ACL_TYPE_MAC_DA0,
- YT921X_ACL_TYPE_MAC_SA0,
- YT921X_ACL_TYPE_MAC_DA1_SA1,
- YT921X_ACL_TYPE_VLAN,
- YT921X_ACL_TYPE_VTAG,
- YT921X_ACL_TYPE_IPV4_DA,
- YT921X_ACL_TYPE_IPV4_SA,
- YT921X_ACL_TYPE_IPV6_DA0,
- YT921X_ACL_TYPE_IPV6_DA1,
- YT921X_ACL_TYPE_IPV6_DA2,
- YT921X_ACL_TYPE_IPV6_DA3,
- YT921X_ACL_TYPE_IPV6_SA0,
- YT921X_ACL_TYPE_IPV6_SA1,
- YT921X_ACL_TYPE_IPV6_SA2,
- YT921X_ACL_TYPE_IPV6_SA3,
- YT921X_ACL_TYPE_MISC,
- YT921X_ACL_TYPE_L4,
- YT921X_ACL_TYPE_UDF0,
- YT921X_ACL_TYPE_UDF1,
- YT921X_ACL_TYPE_UDF2,
- YT921X_ACL_TYPE_UDF3,
- YT921X_ACL_TYPE_UDF4,
- YT921X_ACL_TYPE_UDF5,
- YT921X_ACL_TYPE_UDF6,
- YT921X_ACL_TYPE_UDF7,
- YT921X_ACL_TYPE_ETHERTYPE,
- YT921X_ACL_TYPE_NUM
-};
-
-/* Range: turn KEY:MASK into MIN:MAX */
-
-#define YT921X_ACL_BINb_MAC_xA0_L3_TYPE_M GENMASK(3, 0)
-#define YT921X_ACL_BINb_MAC_xA0_L3_TYPE(x) FIELD_PREP(YT921X_ACL_BINb_MAC_xA0_L3_TYPE_M, (x))
-#define YT921X_ACL_BINa_MAC_xA0_MAC_xA0_M GENMASK(31, 0)
-
-#define YT921X_ACL_BINb_MAC_DA1_SA1_L2_TYPE_M GENMASK(2, 0)
-#define YT921X_ACL_BINb_MAC_DA1_SA1_L2_TYPE(x) FIELD_PREP(YT921X_ACL_BINb_MAC_DA1_SA1_L2_TYPE_M, (x))
-#define YT921X_ACL_BINa_MAC_DA1_SA1_MAC_DA1_M GENMASK(31, 16)
-#define YT921X_ACL_BINa_MAC_DA1_SA1_MAC_SA1_M GENMASK(15, 0)
-
-#define YT921X_ACL_KEYb_VLAN_SVID_RANGE_EN BIT(31)
-#define YT921X_ACL_KEYb_VLAN_CVID_RANGE_EN BIT(30)
-#define YT921X_ACL_BINb_VLAN_CDEI BIT(3)
-#define YT921X_ACL_BINb_VLAN_CPRI_M GENMASK(2, 0)
-#define YT921X_ACL_BINb_VLAN_CPRI(x) FIELD_PREP(YT921X_ACL_BINb_VLAN_CPRI_M, (x))
-#define YT921X_ACL_BINa_VLAN_CTAG_FMT_M GENMASK(31, 30)
-#define YT921X_ACL_BINa_VLAN_CTAG_FMT(x) FIELD_PREP(YT921X_ACL_BINa_VLAN_CTAG_FMT_M, (x))
-#define YT921X_ACL_BINa_VLAN_SDEI BIT(29)
-#define YT921X_ACL_BINa_VLAN_SPRI_M GENMASK(28, 26)
-#define YT921X_ACL_BINa_VLAN_SPRI(x) FIELD_PREP(YT921X_ACL_BINa_VLAN_SPRI_M, (x))
-#define YT921X_ACL_BINa_VLAN_STAG_FMT_M GENMASK(25, 24)
-#define YT921X_ACL_BINa_VLAN_STAG_FMT(x) FIELD_PREP(YT921X_ACL_BINa_VLAN_STAG_FMT_M, (x))
-#define YT921X_ACL_BINa_VLAN_SVID_M GENMASK(23, 12)
-#define YT921X_ACL_BINa_VLAN_SVID(x) FIELD_PREP(YT921X_ACL_BINa_VLAN_SVID_M, (x))
-#define YT921X_ACL_BINa_VLAN_CVID_M GENMASK(11, 0)
-#define YT921X_ACL_BINa_VLAN_CVID(x) FIELD_PREP(YT921X_ACL_BINa_VLAN_CVID_M, (x))
-
-#define YT921X_ACL_KEYb_VTAG_SVID_RANGE_EN BIT(31)
-#define YT921X_ACL_KEYb_VTAG_CVID_RANGE_EN BIT(30)
-#define YT921X_ACL_BINa_VTAG_CDEI BIT(31)
-#define YT921X_ACL_BINa_VTAG_CPRI_M GENMASK(30, 28)
-#define YT921X_ACL_BINa_VTAG_CPRI(x) FIELD_PREP(YT921X_ACL_BINa_VTAG_CPRI_M, (x))
-#define YT921X_ACL_BINa_VTAG_SDEI BIT(27)
-#define YT921X_ACL_BINa_VTAG_SPRI_M GENMASK(26, 24)
-#define YT921X_ACL_BINa_VTAG_SPRI(x) FIELD_PREP(YT921X_ACL_BINa_VTAG_SPRI_M, (x))
-#define YT921X_ACL_BINa_VTAG_SVID_M GENMASK(23, 12)
-#define YT921X_ACL_BINa_VTAG_SVID(x) FIELD_PREP(YT921X_ACL_BINa_VTAG_SVID_M, (x))
-#define YT921X_ACL_BINa_VTAG_CVID_M GENMASK(11, 0)
-#define YT921X_ACL_BINa_VTAG_CVID(x) FIELD_PREP(YT921X_ACL_BINa_VTAG_CVID_M, (x))
-
-#define YT921X_ACL_KEYb_IPV4_ADDR_RANGE_EN BIT(30)
-#define YT921X_ACL_BINb_IPV4_FRAG BIT(3)
-#define YT921X_ACL_BINb_IPV4_L4_TYPE_M GENMASK(2, 0)
-#define YT921X_ACL_BINb_IPV4_L4_TYPE(x) FIELD_PREP(YT921X_ACL_BINb_IPV4_L4_TYPE_M, (x))
-#define YT921X_ACL_BINa_IPV4_ADDR_M GENMASK(31, 0)
-
-#define YT921X_ACL_BINb_IPV6_L4_TYPE_M GENMASK(2, 0)
-#define YT921X_ACL_BINb_IPV6_L4_TYPE(x) FIELD_PREP(YT921X_ACL_BINb_IPV6_L4_TYPE_M, (x))
-#define YT921X_ACL_BINa_IPV6_ADDRx_M GENMASK(31, 0)
-
-#define YT921X_ACL_BINb_IPV6_xA1_IP_OPTION BIT(3)
-
-#define YT921X_ACL_BINb_IPV6_xA2_FIRST_FRAG BIT(3)
-
-#define YT921X_ACL_KEYb_IPV6_xA3_ADDR_RANGE_EN BIT(30)
-#define YT921X_ACL_BINb_IPV6_xA3_FRAG BIT(3)
-
-#define YT921X_ACL_BINb_MISC_FRAG BIT(3)
-#define YT921X_ACL_BINb_MISC_L4_TYPE_M GENMASK(2, 0)
-#define YT921X_ACL_BINb_MISC_L4_TYPE(x) FIELD_PREP(YT921X_ACL_BINb_MISC_L4_TYPE_M, (x))
-#define YT921X_ACL_BINa_MISC_PPPOE_FLAG BIT(30)
-#define YT921X_ACL_BINa_MISC_FIRST_FRAG BIT(29)
-#define YT921X_ACL_BINa_MISC_IP_OPTION BIT(28)
-#define YT921X_ACL_BINa_MISC_TCP_FLAGS_M GENMASK(27, 20)
-#define YT921X_ACL_BINa_MISC_TCP_FLAGS(x) FIELD_PREP(YT921X_ACL_BINa_MISC_TCP_FLAGS_M, (x))
-#define YT921X_ACL_BINa_MISC_IP_PROTO_M GENMASK(19, 12)
-#define YT921X_ACL_BINa_MISC_IP_PROTO(x) FIELD_PREP(YT921X_ACL_BINa_MISC_IP_PROTO_M, (x))
-#define YT921X_ACL_BINa_MISC_TOS_M GENMASK(11, 4)
-#define YT921X_ACL_BINa_MISC_TOS(x) FIELD_PREP(YT921X_ACL_BINa_MISC_TOS_M, (x))
-#define YT921X_ACL_BINa_MISC_L3_TYPE_M GENMASK(3, 0)
-#define YT921X_ACL_BINa_MISC_L3_TYPE(x) FIELD_PREP(YT921X_ACL_BINa_MISC_L3_TYPE_M, (x))
-
-#define YT921X_ACL_KEYb_L4_DPORT_RANGE_EN BIT(31)
-#define YT921X_ACL_KEYb_L4_SPORT_RANGE_EN BIT(30)
-#define YT921X_ACL_BINb_L4_FRAG BIT(3)
-#define YT921X_ACL_BINb_L4_TYPE_M GENMASK(2, 0)
-#define YT921X_ACL_BINb_L4_TYPE(x) FIELD_PREP(YT921X_ACL_BINb_L4_TYPE_M, (x))
-#define YT921X_ACL_BINa_L4_DPORT_M GENMASK(31, 16)
-#define YT921X_ACL_BINa_L4_SPORT_M GENMASK(15, 0)
-
-#define YT921X_ACL_BINb_UDF_IS_IGMP BIT(0)
-#define YT921X_ACL_BINa_UDF_UDF0_M GENMASK(31, 16)
-#define YT921X_ACL_BINa_UDF_UDF0(x) FIELD_PREP(YT921X_ACL_BINa_UDF_UDF0_M, (x))
-#define YT921X_ACL_BINa_UDF_UDF1_M GENMASK(15, 0)
-#define YT921X_ACL_BINa_UDF_UDF1(x) FIELD_PREP(YT921X_ACL_BINa_UDF_UDF1_M, (x))
-
-#define YT921X_ACL_KEYb_ETHERTYPE_ETHERTYPE_RANGE_EN BIT(30)
-#define YT921X_ACL_BINb_ETHERTYPE_L4_TYPE_M GENMASK(2, 0)
-#define YT921X_ACL_BINb_ETHERTYPE_L4_TYPE(x) FIELD_PREP(YT921X_ACL_BINb_ETHERTYPE_L4_TYPE_M, (x))
-#define YT921X_ACL_BINa_ETHERTYPE_ETHERTYPE_M GENMASK(15, 0)
-#define YT921X_ACL_BINa_ETHERTYPE_ETHERTYPE(x) FIELD_PREP(YT921X_ACL_BINa_ETHERTYPE_ETHERTYPE_M, (x))
-
-enum yt921x_l2_type {
- YT921X_L2_TYPE_ETH,
- YT921X_L2_TYPE_ETHV2,
- YT921X_L2_TYPE_ETHSAP,
- YT921X_L2_TYPE_ETHSNAP,
-};
-
-enum yt921x_l3_type {
- YT921X_L3_TYPE_OTHER,
- YT921X_L3_TYPE_IPV4,
- YT921X_L3_TYPE_IPV6,
- YT921X_L3_TYPE_ARP,
- YT921X_L3_TYPE_LLDP,
- YT921X_L3_TYPE_PAE,
- YT921X_L3_TYPE_ERP,
- YT921X_L3_TYPE_SLOW_PROTOCOL,
-};
-
-enum yt921x_l4_type {
- YT921X_L4_TYPE_OTHER,
- YT921X_L4_TYPE_TCP,
- YT921X_L4_TYPE_UDP,
- YT921X_L4_TYPE_UDPLITE,
- YT921X_L4_TYPE_ICMP,
- YT921X_L4_TYPE_IGMP,
- YT921X_L4_TYPE_MLD,
- YT921X_L4_TYPE_ND,
-};
-
-#define YT921X_TPID_IGRn(x) (0x210000 + 4 * (x)) /* [0, 3] */
-#define YT921X_TPID_IGR_TPID_M GENMASK(15, 0)
-#define YT921X_PORTn_IGR_TPID(port) (0x210010 + 4 * (port))
-#define YT921X_PORT_IGR_TPIDn_STAG_M GENMASK(7, 4)
-#define YT921X_PORT_IGR_TPIDn_STAG(x) BIT((x) + 4)
-#define YT921X_PORT_IGR_TPIDn_CTAG_M GENMASK(3, 0)
-#define YT921X_PORT_IGR_TPIDn_CTAG(x) BIT(x)
-#define YT921X_LAG_HASH 0x210090
-#define YT921X_LAG_HASH_L4_SPORT BIT(7)
-#define YT921X_LAG_HASH_L4_DPORT BIT(6)
-#define YT921X_LAG_HASH_IP_PROTO BIT(5)
-#define YT921X_LAG_HASH_IP_SRC BIT(4)
-#define YT921X_LAG_HASH_IP_DST BIT(3)
-#define YT921X_LAG_HASH_MAC_SA BIT(2)
-#define YT921X_LAG_HASH_MAC_DA BIT(1)
-#define YT921X_LAG_HASH_SRC_PORT BIT(0)
-#define YT921X_UDFn_CTRL(x) (0x210094 + 4 * (x))
-#define YT921X_UDF_CTRL_UDF_TYPE_M GENMASK(8, 7)
-#define YT921X_UDF_CTRL_UDF_TYPE(x) FIELD_PREP(YT921X_UDF_CTRL_UDF_TYPE_M, (x))
-#define YT921X_UDF_CTRL_UDF_TYPE_ETH YT921X_UDF_CTRL_UDF_TYPE(0)
-#define YT921X_UDF_CTRL_UDF_TYPE_L3 YT921X_UDF_CTRL_UDF_TYPE(1)
-#define YT921X_UDF_CTRL_UDF_TYPE_L4 YT921X_UDF_CTRL_UDF_TYPE(2)
-#define YT921X_UDF_CTRL_UDF_OFFSET_M GENMASK(6, 0)
-#define YT921X_UDF_CTRL_UDF_OFFSET(x) FIELD_PREP(YT921X_UDF_CTRL_UDF_OFFSET_M, (x))
-
-#define YT921X_PORTn_RATE(port) (0x220000 + 4 * (port))
-#define YT921X_PORT_RATE_GAP_VALUE GENMASK(4, 0) /* default 20 */
-#define YT921X_METER_SLOT 0x220104
-#define YT921X_METER_SLOT_SLOT_M GENMASK(11, 0)
-#define YT921X_PORTn_METER(port) (0x220108 + 4 * (port))
-#define YT921X_PORT_METER_EN BIT(4)
-#define YT921X_PORT_METER_ID_M GENMASK(3, 0)
-#define YT921X_PORT_METER_ID(x) FIELD_PREP(YT921X_PORT_METER_ID_M, (x))
-#define YT921X_METERn_CTRL(x) (0x220800 + 0x10 * (x))
-#define YT921X_METER_CTRLc_METER_EN BIT(14)
-#define YT921X_METER_CTRLc_TOKEN_OVERFLOW_EN BIT(13) /* RFC4115: yellow use unused green bw */
-#define YT921X_METER_CTRLc_DROP_M GENMASK(12, 11)
-#define YT921X_METER_CTRLc_DROP(x) FIELD_PREP(YT921X_METER_CTRLc_DROP_M, (x))
-#define YT921X_METER_CTRLc_DROP_GYR YT921X_METER_CTRLc_DROP(0)
-#define YT921X_METER_CTRLc_DROP_YR YT921X_METER_CTRLc_DROP(1)
-#define YT921X_METER_CTRLc_DROP_R YT921X_METER_CTRLc_DROP(2)
-#define YT921X_METER_CTRLc_DROP_NONE YT921X_METER_CTRLc_DROP(3)
-#define YT921X_METER_CTRLc_COLOR_BLIND BIT(10)
-#define YT921X_METER_CTRLc_UNIT_M GENMASK(9, 7)
-#define YT921X_METER_CTRLc_UNIT(x) FIELD_PREP(YT921X_METER_CTRLc_UNIT_M, (x))
-#define YT921X_METER_CTRLc_BYTE_MODE_INCLUDE_GAP BIT(6) /* +GAP_VALUE bytes each packet */
-#define YT921X_METER_CTRLc_PKT_MODE BIT(5) /* 0: byte rate mode */
-#define YT921X_METER_CTRLc_RFC2698 BIT(4) /* 0: RFC4115 */
-#define YT921X_METER_CTRLbc_CBS_M GENMASK_ULL(35, 20)
-#define YT921X_METER_CTRLbc_CBS(x) FIELD_PREP(YT921X_METER_CTRLbc_CBS_M, (x))
-#define YT921X_METER_CTRLb_CIR_M GENMASK(19, 2)
-#define YT921X_METER_CTRLb_CIR(x) FIELD_PREP(YT921X_METER_CTRLb_CIR_M, (x))
-#define YT921X_METER_CTRLab_EBS_M GENMASK_ULL(33, 18)
-#define YT921X_METER_CTRLab_EBS(x) FIELD_PREP(YT921X_METER_CTRLab_EBS_M, (x))
-#define YT921X_METER_CTRLa_EIR_M GENMASK(17, 0)
-#define YT921X_METER_CTRLa_EIR(x) FIELD_PREP(YT921X_METER_CTRLa_EIR_M, (x))
-#define YT921X_METERn_STAT(x) (0x221000 + 8 * (x))
-
-#define YT921X_PORTn_VLAN_CTRL(port) (0x230010 + 4 * (port))
-#define YT921X_PORT_VLAN_CTRL_SVLAN_PRIO_EN BIT(31)
-#define YT921X_PORT_VLAN_CTRL_CVLAN_PRIO_EN BIT(30)
-#define YT921X_PORT_VLAN_CTRL_SVID_M GENMASK(29, 18)
-#define YT921X_PORT_VLAN_CTRL_SVID(x) FIELD_PREP(YT921X_PORT_VLAN_CTRL_SVID_M, (x))
-#define YT921X_PORT_VLAN_CTRL_CVID_M GENMASK(17, 6)
-#define YT921X_PORT_VLAN_CTRL_CVID(x) FIELD_PREP(YT921X_PORT_VLAN_CTRL_CVID_M, (x))
-#define YT921X_PORT_VLAN_CTRL_SVLAN_PRIO_M GENMASK(5, 3)
-#define YT921X_PORT_VLAN_CTRL_CVLAN_PRIO_M GENMASK(2, 0)
-#define YT921X_PORTn_VLAN_CTRL1(port) (0x230080 + 4 * (port))
-#define YT921X_PORT_VLAN_CTRL1_VLAN_RANGE_EN BIT(8)
-#define YT921X_PORT_VLAN_CTRL1_VLAN_RANGE_PROFILE_ID_M GENMASK(7, 4)
-#define YT921X_PORT_VLAN_CTRL1_SVLAN_DROP_TAGGED BIT(3)
-#define YT921X_PORT_VLAN_CTRL1_SVLAN_DROP_UNTAGGED BIT(2)
-#define YT921X_PORT_VLAN_CTRL1_CVLAN_DROP_TAGGED BIT(1)
-#define YT921X_PORT_VLAN_CTRL1_CVLAN_DROP_UNTAGGED BIT(0)
-
-#define YT921X_MIRROR 0x300300
-#define YT921X_MIRROR_IGR_PORTS_M GENMASK(26, 16)
-#define YT921X_MIRROR_IGR_PORTS(x) FIELD_PREP(YT921X_MIRROR_IGR_PORTS_M, (x))
-#define YT921X_MIRROR_IGR_PORTn(port) BIT((port) + 16)
-#define YT921X_MIRROR_EGR_PORTS_M GENMASK(14, 4)
-#define YT921X_MIRROR_EGR_PORTS(x) FIELD_PREP(YT921X_MIRROR_EGR_PORTS_M, (x))
-#define YT921X_MIRROR_EGR_PORTn(port) BIT((port) + 4)
-#define YT921X_MIRROR_PORT_M GENMASK(3, 0)
-#define YT921X_MIRROR_PORT(x) FIELD_PREP(YT921X_MIRROR_PORT_M, (x))
-
-#define YT921X_PORT_SHAPE_SLOT 0x34000c
-#define YT921X_PORT_SHAPE_SLOT_SLOT_M GENMASK(11, 0)
-#define YT921X_PORTn_SHAPE_CTRL(port) (0x354000 + 8 * (port))
-#define YT921X_PORT_SHAPE_CTRLb_EN BIT(4)
-#define YT921X_PORT_SHAPE_CTRLb_PKT_MODE BIT(3) /* 0: byte rate mode */
-#define YT921X_PORT_SHAPE_CTRLb_UNIT_M GENMASK(2, 0)
-#define YT921X_PORT_SHAPE_CTRLb_UNIT(x) FIELD_PREP(YT921X_PORT_SHAPE_CTRLb_UNIT_M, (x))
-#define YT921X_PORT_SHAPE_CTRLa_CBS_M GENMASK(31, 18)
-#define YT921X_PORT_SHAPE_CTRLa_CBS(x) FIELD_PREP(YT921X_PORT_SHAPE_CTRLa_CBS_M, (x))
-#define YT921X_PORT_SHAPE_CTRLa_CIR_M GENMASK(17, 0)
-#define YT921X_PORT_SHAPE_CTRLa_CIR(x) FIELD_PREP(YT921X_PORT_SHAPE_CTRLa_CIR_M, (x))
-#define YT921X_PORTn_SHAPE_STAT(port) (0x356000 + 4 * (port))
-
-#define YT921X_EDATA_EXTMODE 0xfb
-#define YT921X_EDATA_LEN 0x100
-
-#define YT921X_FDB_NUM 4096
-
-enum yt921x_fdb_entry_status {
- YT921X_FDB_ENTRY_STATUS_INVALID = 0,
- YT921X_FDB_ENTRY_STATUS_MIN_TIME = 1,
- YT921X_FDB_ENTRY_STATUS_MOVE_AGING_MAX_TIME = 3,
- YT921X_FDB_ENTRY_STATUS_MAX_TIME = 5,
- YT921X_FDB_ENTRY_STATUS_PENDING = 6,
- YT921X_FDB_ENTRY_STATUS_STATIC = 7,
-};
-
-#define YT921X_MSTI_NUM 16
-
-#define YT921X_TOKEN_BYTE_C 1 /* 1 token = 2^1 byte */
-#define YT921X_TOKEN_PKT_C -6 /* 1 token = 2^-6 packets */
-#define YT921X_TOKEN_RATE_C -15
-/* Custom meters only, not including dedicated port meters (11) */
-#define YT921X_METER_NUM 64
-#define YT921X_METER_SLOT_MIN 80
-#define YT921X_METER_UNIT_MAX ((1 << 3) - 1)
-#define YT921X_METER_CIR_MAX ((1 << 18) - 1)
-#define YT921X_METER_CBS_MAX ((1 << 16) - 1)
-#define YT921X_PORT_SHAPE_SLOT_MIN 80
-#define YT921X_SHAPE_UNIT_MAX ((1 << 3) - 1)
-#define YT921X_SHAPE_CIR_MAX ((1 << 18) - 1)
-#define YT921X_SHAPE_CBS_MAX ((1 << 14) - 1)
-
-#define YT921X_LAG_NUM 2
-#define YT921X_LAG_PORT_NUM 4
-
-#define YT921X_PRIO_NUM 8
-
-#define YT9215_MAJOR 0x9002
-#define YT9218_MAJOR 0x9001
-
-/* required for a hard reset */
-#define YT921X_RST_DELAY_US 10000
-
-#define YT921X_FRAME_SIZE_MAX 0x2400 /* 9216 */
-
-#define YT921X_TAG_LEN 8
-
-#define YT921X_ACL_BLK_NUM 48
-#define YT921X_ACL_ENT_PER_BLK 8
-#define YT921X_ACL_NUM (YT921X_ACL_BLK_NUM * YT921X_ACL_ENT_PER_BLK)
-#define YT921X_UDF_NUM 8
-
-/* 8 internal + 2 external + 1 mcu */
-#define YT921X_PORT_NUM 11
-
-#define yt921x_port_is_internal(port) ((port) < 8)
-#define yt921x_port_is_external(port) ((port) == 8 || (port) == 9)
-
-struct yt921x_mib {
- u64 rx_broadcast;
- u64 rx_pause;
- u64 rx_multicast;
- u64 rx_crc_errors;
-
- u64 rx_alignment_errors;
- u64 rx_undersize_errors;
- u64 rx_fragment_errors;
- u64 rx_64byte;
-
- u64 rx_65_127byte;
- u64 rx_128_255byte;
- u64 rx_256_511byte;
- u64 rx_512_1023byte;
-
- u64 rx_1024_1518byte;
- u64 rx_jumbo;
- u64 rx_good_bytes;
-
- u64 rx_bad_bytes;
- u64 rx_oversize_errors;
-
- u64 rx_dropped;
- u64 tx_broadcast;
- u64 tx_pause;
- u64 tx_multicast;
-
- u64 tx_undersize_errors;
- u64 tx_64byte;
- u64 tx_65_127byte;
- u64 tx_128_255byte;
-
- u64 tx_256_511byte;
- u64 tx_512_1023byte;
- u64 tx_1024_1518byte;
- u64 tx_jumbo;
-
- u64 tx_good_bytes;
- u64 tx_collisions;
-
- u64 tx_aborted_errors;
- u64 tx_multiple_collisions;
- u64 tx_single_collisions;
- u64 tx_good;
-
- u64 tx_deferred;
- u64 tx_late_collisions;
- u64 rx_oam;
- u64 tx_oam;
-};
-
-struct yt921x_acl_entry {
- u32 key[2];
- u32 mask[2];
-};
-
-struct yt921x_acl_rule {
- unsigned long tag;
- enum tc_setup_type type;
-
- u32 action[3];
- bool sw_assisted;
-
- u8 mask;
- struct yt921x_acl_entry entries[YT921X_ACL_ENT_PER_BLK];
-};
-
-struct yt921x_acl_blk {
- struct yt921x_acl_rule *rules[YT921X_ACL_ENT_PER_BLK];
-};
-
-struct yt921x_port {
- unsigned char index;
-
- bool hairpin;
- bool isolated;
-
- struct delayed_work mib_read;
- struct yt921x_mib mib;
- u64 rx_frames;
- u64 tx_frames;
-};
-
-struct yt921x_reg_ops {
- int (*read)(void *context, u32 reg, u32 *valp);
- int (*write)(void *context, u32 reg, u32 val);
-};
-
-struct yt921x_priv {
- struct dsa_switch ds;
-
- const struct yt921x_info *info;
- unsigned int meter_slot_ns;
- unsigned int port_shape_slot_ns;
- /* cache of dsa_cpu_ports(ds) */
- u16 cpu_ports_mask;
- unsigned char cycle_ns;
-
- /* protect the access to the switch registers */
- struct mutex reg_lock;
- const struct yt921x_reg_ops *reg_ops;
- void *reg_ctx;
-
- /* mdio master bus */
- struct mii_bus *mbus_int;
- struct mii_bus *mbus_ext;
-
- struct yt921x_port ports[YT921X_PORT_NUM];
-
- u16 eee_ports_mask;
-
- DECLARE_BITMAP(meters_map, YT921X_METER_NUM);
-
- u8 acl_masks[YT921X_ACL_BLK_NUM];
- struct yt921x_acl_blk *acl_blks[YT921X_ACL_BLK_NUM];
-};
-
-#endif