Files
wlan-ap/backports/0008-realtek-update-to-latest-owrt-HEAD.patch
John Crispin c6f47257ee ucentral: development update
* update netifd
* update hostapd
* update mt76
* update maverick
* add dynamic vlan
* update ucentral-client
* update ucentral-schema
* update ucode
* update 5.10 patches
* fix realtek

Signed-off-by: John Crispin <john@phrozen.org>
2021-06-05 05:59:36 +02:00

6269 lines
188 KiB
Diff

From cd5d073a6469beb8e7a3a267672e34baa73cbd83 Mon Sep 17 00:00:00 2001
From: John Crispin <john@phrozen.org>
Date: Tue, 16 Mar 2021 10:46:51 +0100
Subject: [PATCH 01/40] realtek: update to latest owrt HEAD
Signed-off-by: John Crispin <john@phrozen.org>
---
package/boot/uboot-envtools/files/realtek | 7 +
.../realtek/base-files/etc/board.d/01_leds | 1 -
.../realtek/base-files/etc/board.d/02_network | 3 +
target/linux/realtek/config-5.4 | 31 +-
.../realtek/dts/rtl8380_zyxel_gs1900-10hp.dts | 20 +-
.../dts/rtl8392_edgecore_ecs4100-12ph.dts | 297 +++++++++
target/linux/realtek/dts/rtl839x.dtsi | 197 ++++++
.../include/asm/mach-rtl838x/mach-rtl83xx.h | 23 +-
.../files-5.4/arch/mips/rtl838x/setup.c | 18 -
.../files-5.4/drivers/gpio/edgecore_reboot.c | 61 ++
.../files-5.4/drivers/gpio/gpio-rtl838x.c | 3 +
.../drivers/net/dsa/rtl83xx/common.c | 39 +-
.../files-5.4/drivers/net/dsa/rtl83xx/dsa.c | 609 +++++++++++++-----
.../drivers/net/dsa/rtl83xx/rtl838x.c | 581 +++++++++++++----
.../drivers/net/dsa/rtl83xx/rtl838x.h | 56 +-
.../drivers/net/dsa/rtl83xx/rtl839x.c | 540 ++++++++++++----
.../drivers/net/dsa/rtl83xx/rtl930x.c | 506 +++++++++++++--
.../drivers/net/dsa/rtl83xx/rtl931x.c | 69 +-
.../drivers/net/ethernet/rtl838x_eth.c | 155 +++--
.../drivers/net/ethernet/rtl838x_eth.h | 1 +
.../files-5.4/drivers/net/phy/rtl83xx-phy.c | 562 +++++++++-------
target/linux/realtek/image/Makefile | 9 +
.../301-gpio-add-rtl838x-driver.patch | 2 +-
...0-gpio-Add-Realtek-Otto-GPIO-support.patch | 412 ++++++++++++
.../realtek/patches-5.4/706-sysled.patch | 294 +++++++++
.../realtek/patches-5.4/707-reboot.patch | 9 +
.../realtek/patches-5.4/708-poor-stp.patch | 16 +
.../realtek/patches-5.4/710-adt7470.patch | 22 +
.../realtek/patches-5.4/711-ec4100.patch | 41 ++
29 files changed, 3785 insertions(+), 799 deletions(-)
create mode 100644 target/linux/realtek/dts/rtl8392_edgecore_ecs4100-12ph.dts
create mode 100644 target/linux/realtek/dts/rtl839x.dtsi
create mode 100644 target/linux/realtek/files-5.4/drivers/gpio/edgecore_reboot.c
create mode 100644 target/linux/realtek/patches-5.4/500-gpio-Add-Realtek-Otto-GPIO-support.patch
create mode 100644 target/linux/realtek/patches-5.4/706-sysled.patch
create mode 100644 target/linux/realtek/patches-5.4/707-reboot.patch
create mode 100644 target/linux/realtek/patches-5.4/708-poor-stp.patch
create mode 100644 target/linux/realtek/patches-5.4/710-adt7470.patch
create mode 100644 target/linux/realtek/patches-5.4/711-ec4100.patch
diff --git a/package/boot/uboot-envtools/files/realtek b/package/boot/uboot-envtools/files/realtek
index cce0628ffc..a4b7089d62 100644
--- a/package/boot/uboot-envtools/files/realtek
+++ b/package/boot/uboot-envtools/files/realtek
@@ -11,11 +11,18 @@ case "$board" in
d-link,dgs-1210-16|\
d-link,dgs-1210-28|\
d-link,dgs-1210-10p|\
+zyxel,gs1900-8hp-v1|\
+zyxel,gs1900-8hp-v2|\
zyxel,gs1900-10hp)
idx="$(find_mtd_index u-boot-env)"
[ -n "$idx" ] && \
ubootenv_add_uci_config "/dev/mtd$idx" "0x0" "0x400" "0x10000"
;;
+edgecore,ecs4100-12ph)
+ idx="$(find_mtd_index u-boot-env)"
+ [ -n "$idx" ] && \
+ ubootenv_add_uci_config "/dev/mtd$idx" "0x0" "0x20000" "0x10000"
+ ;;
*)
idx="$(find_mtd_index u-boot-env)"
[ -n "$idx" ] && \
diff --git a/target/linux/realtek/base-files/etc/board.d/01_leds b/target/linux/realtek/base-files/etc/board.d/01_leds
index 699ab817dd..36ca01a696 100755
--- a/target/linux/realtek/base-files/etc/board.d/01_leds
+++ b/target/linux/realtek/base-files/etc/board.d/01_leds
@@ -1,5 +1,4 @@
#!/bin/sh
-
. /lib/functions/uci-defaults.sh
board=$(board_name)
diff --git a/target/linux/realtek/base-files/etc/board.d/02_network b/target/linux/realtek/base-files/etc/board.d/02_network
index 2568fd2e0e..2b82ccb0aa 100755
--- a/target/linux/realtek/base-files/etc/board.d/02_network
+++ b/target/linux/realtek/base-files/etc/board.d/02_network
@@ -49,6 +49,9 @@ done
[ -n "$label_mac" ] && ucidef_set_label_macaddr $label_mac
case $board in
+edgecore,ecs4100-12ph)
+ ucidef_set_poe 130 "lan1 lan2 lan3 lan4 lan5 lan6 lan7 lan8"
+ ;;
netgear,gs110tpp-v1)
ucidef_set_poe 130 "$lan_list"
;;
diff --git a/target/linux/realtek/config-5.4 b/target/linux/realtek/config-5.4
index 2fbd904376..3f72b911cc 100644
--- a/target/linux/realtek/config-5.4
+++ b/target/linux/realtek/config-5.4
@@ -2,17 +2,18 @@ CONFIG_ARCH_32BIT_OFF_T=y
CONFIG_ARCH_CLOCKSOURCE_DATA=y
CONFIG_ARCH_HIBERNATION_POSSIBLE=y
CONFIG_ARCH_MMAP_RND_BITS_MAX=15
+CONFIG_ARCH_MMAP_RND_COMPAT_BITS_MAX=15
CONFIG_ARCH_SUSPEND_POSSIBLE=y
CONFIG_BLK_DEV_RAM=y
CONFIG_BLK_DEV_RAM_COUNT=16
CONFIG_BLK_DEV_RAM_SIZE=4096
CONFIG_CEVT_R4K=y
-CONFIG_CLONE_BACKWARDS=y
-CONFIG_COMPAT_32BIT_TIME=y
-CONFIG_HAVE_CLK=y
CONFIG_CLKDEV_LOOKUP=y
+CONFIG_CLKSRC_MMIO=y
+CONFIG_CLONE_BACKWARDS=y
CONFIG_COMMON_CLK=y
CONFIG_COMMON_CLK_BOSTON=y
+CONFIG_COMPAT_32BIT_TIME=y
CONFIG_CONSOLE_LOGLEVEL_DEFAULT=15
CONFIG_CPU_BIG_ENDIAN=y
CONFIG_CPU_GENERIC_DUMP_TLB=y
@@ -40,14 +41,10 @@ CONFIG_DMA_NONCOHERENT_CACHE_SYNC=y
CONFIG_DTC=y
CONFIG_EARLY_PRINTK=y
CONFIG_EARLY_PRINTK_8250=y
-CONFIG_EFI_EARLYCON=y
CONFIG_ETHERNET_PACKET_MANGLE=y
CONFIG_EXTRA_FIRMWARE="rtl838x_phy/rtl838x_8214fc.fw rtl838x_phy/rtl838x_8218b.fw rtl838x_phy/rtl838x_8380.fw"
CONFIG_EXTRA_FIRMWARE_DIR="firmware"
CONFIG_FIXED_PHY=y
-CONFIG_FONT_8x16=y
-CONFIG_FONT_AUTOSELECT=y
-CONFIG_FONT_SUPPORT=y
CONFIG_FW_LOADER_PAGED_BUF=y
CONFIG_GENERIC_ATOMIC64=y
CONFIG_GENERIC_CLOCKEVENTS=y
@@ -74,7 +71,9 @@ CONFIG_GENERIC_TIME_VSYSCALL=y
CONFIG_GPIOLIB=y
CONFIG_GPIO_RTL8231=y
CONFIG_GPIO_RTL838X=y
-CONFIG_REALTEK_SOC_PHY=y
+CONFIG_GPIO_REALTEK_OTTO=y
+CONFIG_GPIO_WATCHDOG=y
+# CONFIG_GPIO_WATCHDOG_ARCH_INITCALL is not set
CONFIG_GRO_CELLS=y
CONFIG_HANDLE_DOMAIN_IRQ=y
CONFIG_HARDWARE_WATCHPOINTS=y
@@ -82,12 +81,14 @@ CONFIG_HAS_DMA=y
CONFIG_HAS_IOMEM=y
CONFIG_HAS_IOPORT_MAP=y
# CONFIG_HIGH_RES_TIMERS is not set
+CONFIG_HWMON=y
CONFIG_HZ=250
CONFIG_HZ_250=y
CONFIG_HZ_PERIODIC=y
CONFIG_I2C=y
CONFIG_I2C_ALGOBIT=y
CONFIG_I2C_BOARDINFO=y
+CONFIG_I2C_CHARDEV=y
CONFIG_I2C_GPIO=y
CONFIG_INITRAMFS_SOURCE=""
CONFIG_IRQCHIP=y
@@ -101,10 +102,13 @@ CONFIG_LEGACY_PTYS=y
CONFIG_LEGACY_PTY_COUNT=256
CONFIG_LIBFDT=y
CONFIG_LOCK_DEBUGGING_SUPPORT=y
+CONFIG_MARVELL_PHY=y
CONFIG_MDIO_BUS=y
CONFIG_MDIO_DEVICE=y
CONFIG_MDIO_I2C=y
CONFIG_MEMFD_CREATE=y
+CONFIG_MFD_CORE=y
+CONFIG_MFD_REALTEK_EIO=y
CONFIG_MFD_SYSCON=y
CONFIG_MIGRATION=y
CONFIG_MIPS=y
@@ -159,11 +163,17 @@ CONFIG_PINCTRL=y
CONFIG_POWER_RESET=y
CONFIG_POWER_RESET_SYSCON=y
CONFIG_PSB6970_PHY=y
+CONFIG_RATIONAL=y
+CONFIG_REALTEK_PHY=y
+CONFIG_REALTEK_SOC_PHY=y
CONFIG_REGMAP=y
+CONFIG_REGMAP_I2C=y
CONFIG_REGMAP_MMIO=y
CONFIG_RESET_CONTROLLER=y
CONFIG_RTL838X=y
CONFIG_RTL9300_TIMER=y
+CONFIG_SENSORS_GPIO_FAN=y
+CONFIG_SENSORS_LM75=y
CONFIG_SERIAL_MCTRL_GPIO=y
CONFIG_SERIAL_OF_PLATFORM=y
CONFIG_SFP=y
@@ -172,7 +182,7 @@ CONFIG_SPI_MASTER=y
CONFIG_SPI_MEM=y
CONFIG_SPI_RTL838X=y
CONFIG_SRCU=y
-CONFIG_SWAP_IO_SPACE=y
+CONFIG_SWCONFIG=y
CONFIG_SWPHY=y
CONFIG_SYSCTL_EXCEPTION_TRACE=y
CONFIG_SYS_HAS_CPU_MIPS32_R1=y
@@ -184,8 +194,11 @@ CONFIG_SYS_SUPPORTS_BIG_ENDIAN=y
CONFIG_SYS_SUPPORTS_MIPS16=y
CONFIG_TARGET_ISA_REV=2
CONFIG_TICK_CPU_ACCOUNTING=y
+CONFIG_TIMER_OF=y
+CONFIG_TIMER_PROBE=y
CONFIG_TINY_SRCU=y
CONFIG_USE_GENERIC_EARLY_PRINTK_8250=y
CONFIG_USE_OF=y
+CONFIG_WATCHDOG_CORE=y
CONFIG_ZLIB_DEFLATE=y
CONFIG_ZLIB_INFLATE=y
diff --git a/target/linux/realtek/dts/rtl8380_zyxel_gs1900-10hp.dts b/target/linux/realtek/dts/rtl8380_zyxel_gs1900-10hp.dts
index 92d0e25fc4..a590450055 100644
--- a/target/linux/realtek/dts/rtl8380_zyxel_gs1900-10hp.dts
+++ b/target/linux/realtek/dts/rtl8380_zyxel_gs1900-10hp.dts
@@ -55,29 +55,17 @@
port@24 {
reg = <24>;
label = "lan9";
- phy-mode = "rgmii-id";
- phy-handle = <&phy24>;
+ phy-mode = "1000base-x";
+ managed = "in-band-status";
sfp = <&sfp0>;
-
- fixed-link {
- speed = <1000>;
- full-duplex;
- pause;
- };
};
port@26 {
reg = <26>;
label = "lan10";
- phy-mode = "rgmii-id";
- phy-handle = <&phy26>;
+ phy-mode = "1000base-x";
+ managed = "in-band-status";
sfp = <&sfp1>;
-
- fixed-link {
- speed = <1000>;
- full-duplex;
- pause;
- };
};
};
};
diff --git a/target/linux/realtek/dts/rtl8392_edgecore_ecs4100-12ph.dts b/target/linux/realtek/dts/rtl8392_edgecore_ecs4100-12ph.dts
new file mode 100644
index 0000000000..303b79ac83
--- /dev/null
+++ b/target/linux/realtek/dts/rtl8392_edgecore_ecs4100-12ph.dts
@@ -0,0 +1,297 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "rtl839x.dtsi"
+
+#include <dt-bindings/input/input.h>
+#include <dt-bindings/gpio/gpio.h>
+
+/ {
+ compatible = "edgecore,ecs4100-12ph", "realtek,rtl838x-soc";
+ model = "Edgecore ECS4100-12PH Switch";
+
+ aliases {
+ led-boot = &led_sys;
+ led-failsafe = &led_sys;
+ led-running = &led_sys;
+ led-upgrade = &led_sys;
+ };
+
+ chosen {
+ bootargs = "console=ttyS0,115200";
+ };
+
+ memory@0 {
+ device_type = "memory";
+ reg = <0x0 0x10000000>;
+ };
+
+ /* i2c of the left SFP cage: port 9 */
+ i2c0: i2c-gpio-0 {
+ compatible = "i2c-gpio";
+ sda-gpios = <&gpio1 6 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
+ scl-gpios = <&gpio1 7 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
+ i2c-gpio,delay-us = <2>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ };
+
+ sfp0: sfp-p9 {
+ compatible = "sff,sfp";
+ i2c-bus = <&i2c0>;
+ los-gpio = <&gpio0 12 GPIO_ACTIVE_HIGH>;
+ mod-def0-gpio = <&gpio1 8 GPIO_ACTIVE_LOW>;
+ };
+
+ i2c1: i2c-gpio-1 {
+ compatible = "i2c-gpio";
+ sda-gpios = <&gpio1 1 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
+ scl-gpios = <&gpio1 2 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
+ i2c-gpio,delay-us = <2>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ };
+
+ sfp1: sfp-p10 {
+ compatible = "sff,sfp";
+ i2c-bus = <&i2c1>;
+ los-gpio = <&gpio0 14 GPIO_ACTIVE_HIGH>;
+ mod-def0-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
+ };
+
+ i2c2: i2c-gpio-2 {
+ compatible = "i2c-gpio";
+ sda-gpios = <&gpio1 22 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
+ scl-gpios = <&gpio1 23 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
+ i2c-gpio,delay-us = <2>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ };
+
+ sfp2: sfp-p11 {
+ compatible = "sff,sfp";
+ i2c-bus = <&i2c2>;
+ los-gpio = <&gpio0 21 GPIO_ACTIVE_HIGH>;
+ mod-def0-gpio = <&gpio1 24 GPIO_ACTIVE_LOW>;
+ };
+
+ i2c3: i2c-gpio-3 {
+ compatible = "i2c-gpio";
+ sda-gpios = <&gpio1 11 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
+ scl-gpios = <&gpio1 12 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
+ i2c-gpio,delay-us = <2>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ };
+
+ sfp3: sfp-p12 {
+ compatible = "sff,sfp";
+ i2c-bus = <&i2c3>;
+ los-gpio = <&gpio0 22 GPIO_ACTIVE_HIGH>;
+ mod-def0-gpio = <&gpio1 13 GPIO_ACTIVE_LOW>;
+ };
+
+ i2c4: i2c-gpio-4 {
+ compatible = "i2c-gpio";
+ sda-gpios = <&gpio1 29 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
+ scl-gpios = <&gpio1 30 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
+ i2c-gpio,delay-us = <2>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ adt7470@2f {
+ compatible = "adi,adt7470";
+ reg = <0x2f>;
+ };
+
+ lm75b@48 {
+ compatible = "nxp,lm75a";
+ reg = <0x48>;
+ };
+
+ eeprom@506 {
+ compatible = "atmel,24c32";
+ reg = <0x56>;
+ };
+ };
+
+ watchdog {
+ compatible = "linux,wdt-gpio";
+ gpios = <&gpio0 20 GPIO_ACTIVE_LOW>;
+ hw_algo = "toggle";
+ hw_margin_ms = <1200>;
+ };
+
+ reboot@0 {
+ compatible = "edgecore,reboot";
+ gpios = <&gpio1 26 GPIO_ACTIVE_HIGH>;
+ };
+
+ fan0: gpio-fan {
+ #cooling-cells = <2>;
+ compatible = "gpio-fan";
+ gpio-fan,speed-map = <0 0 3000 1>;
+ gpios = <&gpio1 9 GPIO_ACTIVE_LOW>;
+ status = "okay";
+ };
+};
+
+&gpio1 {
+ status = "okay";
+};
+
+&gpio0 {
+ poe_enable {
+ gpio-hog;
+ gpios = <16 GPIO_ACTIVE_HIGH>;
+ output-high;
+ };
+
+ poe_reset {
+ gpio-hog;
+ gpios = <18 GPIO_ACTIVE_HIGH>;
+ output-high;
+ };
+};
+
+&spi0 {
+ status = "okay";
+
+ flash@0 {
+ compatible = "jedec,spi-nor";
+ reg = <0>;
+ spi-max-frequency = <10000000>;
+
+ partitions {
+ compatible = "fixed-partitions";
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ partition@0 {
+ label = "u-boot";
+ reg = <0x0 0x40000>;
+ read-only;
+ };
+ partition@100000 {
+ label = "u-boot-env";
+ reg = <0x100000 0x100000>;
+ read-only;
+ };
+ partition@b260000 {
+ label = "firmware";
+ reg = <0x200000 0xe00000>;
+ compatible = "openwrt,uimage", "denx,uimage";
+ };
+ };
+ };
+};
+
+&ethernet0 {
+ mdio: mdio-bus {
+ compatible = "realtek,rtl838x-mdio";
+ regmap = <&ethernet0>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ INTERNAL_PHY(0)
+ INTERNAL_PHY(1)
+ INTERNAL_PHY(2)
+ INTERNAL_PHY(3)
+ INTERNAL_PHY(4)
+ INTERNAL_PHY(5)
+ INTERNAL_PHY(6)
+ INTERNAL_PHY(7)
+
+ phy48: ethernet-phy@48 {
+ reg = <48>;
+ compatible = "ethernet-phy-ieee802.3-c22";
+ sfp = <&sfp0>;
+ };
+
+ phy49: ethernet-phy@49 {
+ reg = <49>;
+ compatible = "ethernet-phy-ieee802.3-c22";
+ sfp = <&sfp1>;
+ };
+
+ phy50: ethernet-phy@50 {
+ reg = <50>;
+ compatible = "ethernet-phy-ieee802.3-c22";
+ sfp = <&sfp2>;
+ };
+
+ phy51: ethernet-phy@51 {
+ reg = <51>;
+ compatible = "ethernet-phy-ieee802.3-c22";
+ sfp = <&sfp3>;
+ };
+ };
+};
+
+&switch0 {
+ ext_io: ext-io@e4 {
+ compatible = "realtek,rtl8390-eio", "syscon";
+ reg = <0xe4 0x17c>;
+
+ led_sys: sys-led {
+ active-low;
+ label = "green:status";
+ linux,default-trigger = "default-on";
+ };
+ };
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ SWITCH_PORT(0, 1, internal)
+ SWITCH_PORT(1, 2, internal)
+ SWITCH_PORT(2, 3, internal)
+ SWITCH_PORT(3, 4, internal)
+ SWITCH_PORT(4, 5, internal)
+ SWITCH_PORT(5, 6, internal)
+ SWITCH_PORT(6, 7, internal)
+ SWITCH_PORT(7, 8, internal)
+
+ port@48 {
+ reg = <48>;
+ label = "lan9";
+ phy-mode = "sgmii";
+ phy-handle = <&phy48>;
+ managed = "in-band-status";
+ };
+
+ port@49 {
+ reg = <49>;
+ label = "lan10";
+ phy-mode = "sgmii";
+ phy-handle = <&phy49>;
+ managed = "in-band-status";
+ };
+
+ port@50 {
+ reg = <50>;
+ label = "lan11";
+ phy-mode = "sgmii";
+ phy-handle = <&phy50>;
+ managed = "in-band-status";
+ };
+
+ port@51 {
+ reg = <51>;
+ label = "lan12";
+ phy-mode = "sgmii";
+ phy-handle = <&phy51>;
+ managed = "in-band-status";
+ };
+
+ port@28 {
+ ethernet = <&ethernet0>;
+ reg = <28>;
+ phy-mode = "internal";
+
+ fixed-link {
+ speed = <1000>;
+ full-duplex;
+ };
+ };
+ };
+};
diff --git a/target/linux/realtek/dts/rtl839x.dtsi b/target/linux/realtek/dts/rtl839x.dtsi
new file mode 100644
index 0000000000..9816b2a09b
--- /dev/null
+++ b/target/linux/realtek/dts/rtl839x.dtsi
@@ -0,0 +1,197 @@
+// SPDX-License-Identifier: GPL-2.0-or-later OR MIT
+
+/dts-v1/;
+
+#define STRINGIZE(s) #s
+#define LAN_LABEL(p, s) STRINGIZE(p ## s)
+#define SWITCH_PORT_LABEL(n) LAN_LABEL(lan, n)
+
+#define INTERNAL_PHY(n) \
+ phy##n: ethernet-phy@##n { \
+ reg = <##n>; \
+ compatible = "ethernet-phy-ieee802.3-c22"; \
+ phy-is-integrated; \
+ };
+
+#define EXTERNAL_PHY(n) \
+ phy##n: ethernet-phy@##n { \
+ reg = <##n>; \
+ compatible = "ethernet-phy-ieee802.3-c22"; \
+ };
+
+#define EXTERNAL_SFP_PHY(n) \
+ phy##n: ethernet-phy@##n { \
+ compatible = "ethernet-phy-ieee802.3-c22"; \
+ sfp; \
+ media = "fibre"; \
+ reg = <##n>; \
+ };
+
+#define SWITCH_PORT(n, s, m) \
+ port@##n { \
+ reg = <##n>; \
+ label = SWITCH_PORT_LABEL(s) ; \
+ phy-handle = <&phy##n>; \
+ phy-mode = #m ; \
+ };
+
+#define SWITCH_SFP_PORT(n, s, m) \
+ port@##n { \
+ reg = <##n>; \
+ label = SWITCH_PORT_LABEL(s) ; \
+ phy-handle = <&phy##n>; \
+ phy-mode = #m ; \
+ fixed-link { \
+ speed = <1000>; \
+ full-duplex; \
+ }; \
+ };
+
+/ {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ compatible = "realtek,rtl838x-soc";
+
+ cpus {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ frequency = <700000000>;
+
+ cpu@0 {
+ compatible = "mips,mips34Kc";
+ reg = <0>;
+ };
+ };
+
+ memory@0 {
+ device_type = "memory";
+ reg = <0x0 0x8000000>;
+ };
+
+ chosen {
+ bootargs = "console=ttyS0,38400";
+ };
+
+
+ cpuintc: cpuintc {
+ #address-cells = <0>;
+ #interrupt-cells = <1>;
+ interrupt-controller;
+ compatible = "mti,cpu-interrupt-controller";
+ };
+
+ intc: rtlintc {
+ #address-cells = <0>;
+ #interrupt-cells = <1>;
+ interrupt-controller;
+ compatible = "realtek,rt8380-intc";
+ reg = <0xb8003000 0x20>;
+ };
+
+ spi0: spi@b8001200 {
+ status = "okay";
+
+ compatible = "realtek,rtl838x-nor";
+ reg = <0xb8001200 0x100>;
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+ };
+
+ uart0: uart@b8002000 {
+ status = "okay";
+
+ compatible = "ns16550a";
+ reg = <0xb8002000 0x100>;
+
+ clock-frequency = <200000000>;
+
+ interrupt-parent = <&intc>;
+ interrupts = <31>;
+
+ reg-io-width = <1>;
+ reg-shift = <2>;
+ fifo-size = <1>;
+ no-loopback-test;
+ };
+
+ uart1: uart@b8002100 {
+ pinctrl-names = "default";
+ pinctrl-0 = <&enable_uart1>;
+
+ status = "okay";
+
+ compatible = "ns16550a";
+ reg = <0xb8002100 0x100>;
+
+ clock-frequency = <200000000>;
+
+ interrupt-parent = <&intc>;
+ interrupts = <30>;
+
+ reg-io-width = <1>;
+ reg-shift = <2>;
+ fifo-size = <1>;
+ no-loopback-test;
+ };
+
+ gpio0: gpio-controller@b8003500 {
+ compatible = "realtek,rtl8380-gpio", "realtek,otto-gpio";
+ reg = <0xb8003500 0x20>;
+ gpio-controller;
+ #gpio-cells = <2>;
+ ngpios = <24>;
+ };
+
+ gpio1: rtl8231-gpio {
+ status = "disabled";
+ compatible = "realtek,rtl8231-gpio";
+ #gpio-cells = <2>;
+ indirect-access-bus-id = <3>;
+ gpio-controller;
+ };
+
+ pinmux: pinmux@bb001000 {
+ compatible = "pinctrl-single";
+ reg = <0xbb000004 0x4>;
+
+ pinctrl-single,bit-per-mux;
+ pinctrl-single,register-width = <32>;
+ pinctrl-single,function-mask = <0x1>;
+ #pinctrl-cells = <2>;
+
+ enable_uart1: pinmux_enable_uart1 {
+ pinctrl-single,bits = <0x0 0x01 0x03>;
+ };
+ };
+
+ ethernet0: ethernet@bb00a300 {
+ status = "okay";
+
+ compatible = "realtek,rtl838x-eth";
+ reg = <0xbb00a300 0x100>;
+ interrupt-parent = <&intc>;
+ interrupts = <24>;
+ #interrupt-cells = <1>;
+ phy-mode = "internal";
+
+ fixed-link {
+ speed = <1000>;
+ full-duplex;
+ };
+ };
+
+ switch0: switch@bb000000 {
+ status = "okay";
+
+ interrupt-parent = <&intc>;
+ interrupts = <20>;
+
+ compatible = "realtek,rtl83xx-switch", "simple-bus";
+ #address-cells = <1>;
+ #size-cells = <1>;
+ ranges = <0x0 0xbb000000 0x10000>;
+
+ };
+};
diff --git a/target/linux/realtek/files-5.4/arch/mips/include/asm/mach-rtl838x/mach-rtl83xx.h b/target/linux/realtek/files-5.4/arch/mips/include/asm/mach-rtl838x/mach-rtl83xx.h
index fc401e5481..0abfc6f4d2 100644
--- a/target/linux/realtek/files-5.4/arch/mips/include/asm/mach-rtl838x/mach-rtl83xx.h
+++ b/target/linux/realtek/files-5.4/arch/mips/include/asm/mach-rtl838x/mach-rtl83xx.h
@@ -313,8 +313,29 @@
#define RTL839X_SMI_PORT_POLLING_CTRL (0x03fc)
#define RTL839X_PHYREG_ACCESS_CTRL (0x03DC)
#define RTL839X_PHYREG_CTRL (0x03E0)
-#define RTL839X_PHYREG_PORT_CTRL(p) (0x03E4 + ((p >> 5) << 2))
+#define RTL839X_PHYREG_PORT_CTRL (0x03E4)
#define RTL839X_PHYREG_DATA_CTRL (0x03F0)
+#define RTL839X_PHYREG_MMD_CTRL (0x3F4)
+
+#define RTL930X_SMI_GLB_CTRL (0xCA00)
+#define RTL930X_SMI_POLL_CTRL (0xca90)
+#define RTL930X_SMI_PORT0_15_POLLING_SEL (0xCA08)
+#define RTL930X_SMI_PORT16_27_POLLING_SEL (0xCA0C)
+#define RTL930X_SMI_PORT0_5_ADDR (0xCB80)
+#define RTL930X_SMI_ACCESS_PHY_CTRL_0 (0xCB70)
+#define RTL930X_SMI_ACCESS_PHY_CTRL_1 (0xCB74)
+#define RTL930X_SMI_ACCESS_PHY_CTRL_2 (0xCB78)
+#define RTL930X_SMI_ACCESS_PHY_CTRL_3 (0xCB7C)
+
+#define RTL931X_SMI_GLB_CTRL1 (0x0CBC)
+#define RTL931X_SMI_GLB_CTRL0 (0x0CC0)
+#define RTL931X_SMI_PORT_POLLING_CTRL (0x0CCC)
+#define RTL931X_SMI_INDRT_ACCESS_CTRL_0 (0x0C00)
+#define RTL931X_SMI_INDRT_ACCESS_CTRL_1 (0x0C04)
+#define RTL931X_SMI_INDRT_ACCESS_CTRL_2 (0x0C08)
+#define RTL931X_SMI_INDRT_ACCESS_CTRL_3 (0x0C10)
+#define RTL931X_SMI_INDRT_ACCESS_BC_PHYID_CTRL (0x0C14)
+#define RTL931X_SMI_INDRT_ACCESS_MMD_CTRL (0xC18)
#define RTL930X_SMI_GLB_CTRL (0xCA00)
#define RTL930X_SMI_POLL_CTRL (0xca90)
diff --git a/target/linux/realtek/files-5.4/arch/mips/rtl838x/setup.c b/target/linux/realtek/files-5.4/arch/mips/rtl838x/setup.c
index ef97d485e1..ea2dd0c866 100644
--- a/target/linux/realtek/files-5.4/arch/mips/rtl838x/setup.c
+++ b/target/linux/realtek/files-5.4/arch/mips/rtl838x/setup.c
@@ -46,21 +46,6 @@ static void rtl838x_restart(char *command)
sw_w32(1, RTL838X_RST_GLB_CTRL_1);
}
-static void rtl839x_restart(char *command)
-{
- /* SoC reset vector (in flash memory): on RTL839x platform preferred way to reset */
- void (*f)(void) = (void *) 0xbfc00000;
-
- pr_info("System restart.\n");
- /* Reset SoC */
- sw_w32(0xFFFFFFFF, RTL839X_RST_GLB_CTRL);
- /* and call reset vector */
- f();
- /* If this fails, halt the CPU */
- while
- (1);
-}
-
static void rtl930x_restart(char *command)
{
pr_info("System restart.\n");
@@ -109,8 +94,6 @@ static void __init rtl838x_setup(void)
static void __init rtl839x_setup(void)
{
pr_info("Registering _machine_restart\n");
- _machine_restart = rtl839x_restart;
- _machine_halt = rtl838x_halt;
/* Setup System LED. Bit 14 of RTL839X_LED_GLB_CTRL then allows to toggle it */
sw_w32_mask(0, 3 << 15, RTL839X_LED_GLB_CTRL);
@@ -141,7 +124,6 @@ void __init plat_mem_setup(void)
void *dtb;
set_io_port_base(KSEG1);
- _machine_restart = rtl838x_restart;
if (fw_passed_dtb) /* UHI interface */
dtb = (void *)fw_passed_dtb;
diff --git a/target/linux/realtek/files-5.4/drivers/gpio/edgecore_reboot.c b/target/linux/realtek/files-5.4/drivers/gpio/edgecore_reboot.c
new file mode 100644
index 0000000000..2cafab4279
--- /dev/null
+++ b/target/linux/realtek/files-5.4/drivers/gpio/edgecore_reboot.c
@@ -0,0 +1,61 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (C) 2021 John Crispin <john@phrozen.org> */
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/notifier.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/reboot.h>
+#include <linux/module.h>
+#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
+
+static struct notifier_block edgecore_reboot_handler;
+static struct gpio_desc *gpiod;
+static int edgecore_reboot_handle(struct notifier_block *this,
+ unsigned long mode, void *cmd)
+{
+ gpiod_direction_output(gpiod, 0);
+ mdelay(1000);
+
+ pr_emerg("Unable to restart system\n");
+ return NOTIFY_DONE;
+}
+
+static int __init edgecore_reboot_probe(struct platform_device *pdev)
+{
+ int err;
+ unsigned long flags = GPIOF_IN;
+
+ gpiod = devm_gpiod_get_index(&pdev->dev, NULL, 0, flags);
+ if (!IS_ERR(gpiod))
+ gpiod_set_consumer_name(gpiod, "reboot");
+ else
+ return -EPROBE_DEFER;
+
+ edgecore_reboot_handler.notifier_call = edgecore_reboot_handle;
+ edgecore_reboot_handler.priority = 255;
+ err = register_restart_handler(&edgecore_reboot_handler);
+ if (err)
+ printk("can't register restart notifier (err=%d)\n", err);
+
+
+ return 0;
+}
+
+static const struct of_device_id edgecore_reboot_of_ids[] = {
+ { .compatible = "edgecore,reboot"},
+ { /* sentinel */ }
+};
+
+
+static struct platform_driver edgecore_reboot_driver = {
+ .probe = edgecore_reboot_probe,
+ .driver = {
+ .name = "edgecore_reboot",
+ .of_match_table = edgecore_reboot_of_ids,
+ },
+};
+
+module_platform_driver(edgecore_reboot_driver);
diff --git a/target/linux/realtek/files-5.4/drivers/gpio/gpio-rtl838x.c b/target/linux/realtek/files-5.4/drivers/gpio/gpio-rtl838x.c
index 8207e4bb73..60b6f08834 100644
--- a/target/linux/realtek/files-5.4/drivers/gpio/gpio-rtl838x.c
+++ b/target/linux/realtek/files-5.4/drivers/gpio/gpio-rtl838x.c
@@ -348,6 +348,9 @@ static int rtl838x_gpio_probe(struct platform_device *pdev)
case 0x8391:
pr_debug("Found RTL8391 GPIO\n");
break;
+ case 0x8392:
+ pr_debug("Found RTL8392 GPIO\n");
+ break;
case 0x8393:
pr_debug("Found RTL8393 GPIO\n");
break;
diff --git a/target/linux/realtek/files-5.4/drivers/net/dsa/rtl83xx/common.c b/target/linux/realtek/files-5.4/drivers/net/dsa/rtl83xx/common.c
index 698f2892ea..a380906b92 100644
--- a/target/linux/realtek/files-5.4/drivers/net/dsa/rtl83xx/common.c
+++ b/target/linux/realtek/files-5.4/drivers/net/dsa/rtl83xx/common.c
@@ -18,27 +18,6 @@ extern const struct dsa_switch_ops rtl930x_switch_ops;
DEFINE_MUTEX(smi_lock);
-// TODO: unused
-static void dump_fdb(struct rtl838x_switch_priv *priv)
-{
- struct rtl838x_l2_entry e;
- int i;
-
- mutex_lock(&priv->reg_mutex);
-
- for (i = 0; i < priv->fib_entries; i++) {
- priv->r->read_l2_entry_using_hash(i >> 2, i & 0x3, &e);
-
- if (!e.valid) /* Check for invalid entry */
- continue;
-
- pr_debug("-> port %02d: mac %pM, vid: %d, rvid: %d, MC: %d, %d\n",
- e.port, &e.mac[0], e.vid, e.rvid, e.is_ip_mc, e.is_ipv6_mc);
- }
-
- mutex_unlock(&priv->reg_mutex);
-}
-
int rtl83xx_port_get_stp_state(struct rtl838x_switch_priv *priv, int port)
{
u32 msti = 0;
@@ -144,7 +123,6 @@ void rtl_table_read(struct table_reg *r, int idx)
cmd |= BIT(r->c_bit + 1) | (r->tbl << r->t_bit) | (idx & (BIT(r->t_bit) - 1));
sw_w32(cmd, r->addr);
- pr_debug("Writing %08x to %x for read\n", cmd, r->addr);
do { } while (sw_r32(r->addr) & BIT(r->c_bit + 1));
}
@@ -156,8 +134,6 @@ void rtl_table_write(struct table_reg *r, int idx)
u32 cmd = r->rmode ? 0 : BIT(r->c_bit);
cmd |= BIT(r->c_bit + 1) | (r->tbl << r->t_bit) | (idx & (BIT(r->t_bit) - 1));
- pr_debug("Writing %08x to %x for write, value %08x\n",
- cmd, r->addr, sw_r32(0xb344));
sw_w32(cmd, r->addr);
do { } while (sw_r32(r->addr) & BIT(r->c_bit + 1));
}
@@ -368,8 +344,8 @@ static int __init rtl83xx_mdio_probe(struct rtl838x_switch_priv *priv)
/* Enable PHY control via SoC */
if (priv->family_id == RTL8380_FAMILY_ID) {
- /* Enable PHY control via SoC */
- sw_w32_mask(0, BIT(15), RTL838X_SMI_GLB_CTRL);
+ /* Enable SerDes NWAY and PHY control via SoC */
+ sw_w32_mask(BIT(7), BIT(15), RTL838X_SMI_GLB_CTRL);
} else {
/* Disable PHY polling via SoC */
sw_w32_mask(BIT(7), 0, RTL839X_SMI_GLB_CTRL);
@@ -555,7 +531,6 @@ static int __init rtl83xx_sw_probe(struct platform_device *pdev)
int err = 0, i;
struct rtl838x_switch_priv *priv;
struct device *dev = &pdev->dev;
- u64 irq_mask;
u64 bpdu_mask;
pr_debug("Probing RTL838X switch device\n");
@@ -594,6 +569,7 @@ static int __init rtl83xx_sw_probe(struct platform_device *pdev)
priv->fib_entries = 8192;
rtl8380_get_version(priv);
priv->n_lags = 8;
+ priv->l2_bucket_size = 4;
break;
case RTL8390_FAMILY_ID:
priv->ds->ops = &rtl83xx_switch_ops;
@@ -606,6 +582,7 @@ static int __init rtl83xx_sw_probe(struct platform_device *pdev)
priv->fib_entries = 16384;
rtl8390_get_version(priv);
priv->n_lags = 16;
+ priv->l2_bucket_size = 4;
break;
case RTL9300_FAMILY_ID:
priv->ds->ops = &rtl930x_switch_ops;
@@ -619,6 +596,7 @@ static int __init rtl83xx_sw_probe(struct platform_device *pdev)
priv->version = RTL8390_VERSION_A;
priv->n_lags = 16;
sw_w32(1, RTL930X_ST_CTRL);
+ priv->l2_bucket_size = 8;
break;
case RTL9310_FAMILY_ID:
priv->ds->ops = &rtl930x_switch_ops;
@@ -631,6 +609,7 @@ static int __init rtl83xx_sw_probe(struct platform_device *pdev)
priv->fib_entries = 16384;
priv->version = RTL8390_VERSION_A;
priv->n_lags = 16;
+ priv->l2_bucket_size = 8;
break;
}
pr_debug("Chip version %c\n", priv->version);
@@ -650,9 +629,9 @@ static int __init rtl83xx_sw_probe(struct platform_device *pdev)
/* Enable link and media change interrupts. Are the SERDES masks needed? */
sw_w32_mask(0, 3, priv->r->isr_glb_src);
-
- priv->r->set_port_reg_le(irq_mask, priv->r->isr_port_link_sts_chg);
- priv->r->set_port_reg_le(irq_mask, priv->r->imr_port_link_sts_chg);
+
+ priv->r->set_port_reg_le(priv->irq_mask, priv->r->isr_port_link_sts_chg);
+ priv->r->set_port_reg_le(priv->irq_mask, priv->r->imr_port_link_sts_chg);
priv->link_state_irq = platform_get_irq(pdev, 0);
pr_info("LINK state irq: %d\n", priv->link_state_irq);
diff --git a/target/linux/realtek/files-5.4/drivers/net/dsa/rtl83xx/dsa.c b/target/linux/realtek/files-5.4/drivers/net/dsa/rtl83xx/dsa.c
index e0832c42b8..c5f243c55a 100644
--- a/target/linux/realtek/files-5.4/drivers/net/dsa/rtl83xx/dsa.c
+++ b/target/linux/realtek/files-5.4/drivers/net/dsa/rtl83xx/dsa.c
@@ -26,50 +26,6 @@ static void rtl83xx_init_stats(struct rtl838x_switch_priv *priv)
mutex_unlock(&priv->reg_mutex);
}
-static void rtl83xx_write_cam(int idx, u32 *r)
-{
- u32 cmd = BIT(16) /* Execute cmd */
- | BIT(15) /* Read */
- | BIT(13) /* Table type 0b01 */
- | (idx & 0x3f);
-
- sw_w32(r[0], RTL838X_TBL_ACCESS_L2_DATA(0));
- sw_w32(r[1], RTL838X_TBL_ACCESS_L2_DATA(1));
- sw_w32(r[2], RTL838X_TBL_ACCESS_L2_DATA(2));
-
- sw_w32(cmd, RTL838X_TBL_ACCESS_L2_CTRL);
- do { } while (sw_r32(RTL838X_TBL_ACCESS_L2_CTRL) & BIT(16));
-}
-
-static u64 rtl83xx_hash_key(struct rtl838x_switch_priv *priv, u64 mac, u32 vid)
-{
- switch (priv->family_id) {
- case RTL8380_FAMILY_ID:
- return rtl838x_hash(priv, mac << 12 | vid);
- case RTL8390_FAMILY_ID:
- return rtl839x_hash(priv, mac << 12 | vid);
- case RTL9300_FAMILY_ID:
- return rtl930x_hash(priv, ((u64)vid) << 48 | mac);
- default:
- pr_err("Hash not implemented\n");
- }
- return 0;
-}
-
-static void rtl83xx_write_hash(int idx, u32 *r)
-{
- u32 cmd = BIT(16) /* Execute cmd */
- | 0 << 15 /* Write */
- | 0 << 13 /* Table type 0b00 */
- | (idx & 0x1fff);
-
- sw_w32(0, RTL838X_TBL_ACCESS_L2_DATA(0));
- sw_w32(0, RTL838X_TBL_ACCESS_L2_DATA(1));
- sw_w32(0, RTL838X_TBL_ACCESS_L2_DATA(2));
- sw_w32(cmd, RTL838X_TBL_ACCESS_L2_CTRL);
- do { } while (sw_r32(RTL838X_TBL_ACCESS_L2_CTRL) & BIT(16));
-}
-
static void rtl83xx_enable_phy_polling(struct rtl838x_switch_priv *priv)
{
int i;
@@ -79,7 +35,7 @@ static void rtl83xx_enable_phy_polling(struct rtl838x_switch_priv *priv)
/* Enable all ports with a PHY, including the SFP-ports */
for (i = 0; i < priv->cpu_port; i++) {
if (priv->ports[i].phy)
- v |= BIT(i);
+ v |= BIT_ULL(i);
}
pr_debug("%s: %16llx\n", __func__, v);
@@ -153,6 +109,36 @@ static enum dsa_tag_protocol rtl83xx_get_tag_protocol(struct dsa_switch *ds, int
return DSA_TAG_PROTO_TRAILER;
}
+/*
+ * Initialize all VLANS
+ */
+static void rtl83xx_vlan_setup(struct rtl838x_switch_priv *priv)
+{
+ struct rtl838x_vlan_info info;
+ int i;
+
+ pr_info("In %s\n", __func__);
+
+ priv->r->vlan_profile_setup(0);
+ priv->r->vlan_profile_setup(1);
+ pr_info("UNKNOWN_MC_PMASK: %016llx\n", priv->r->read_mcast_pmask(UNKNOWN_MC_PMASK));
+ priv->r->vlan_profile_dump(0);
+
+ info.fid = 0; // Default Forwarding ID / MSTI
+ info.hash_uc_fid = false; // Do not build the L2 lookup hash with FID, but VID
+ info.hash_mc_fid = false; // Do the same for Multicast packets
+ info.profile_id = 0; // Use default Vlan Profile 0
+ info.tagged_ports = 0; // Initially no port members
+
+ // Initialize all vlans 0-4095
+ for (i = 0; i < MAX_VLANS; i ++)
+ priv->r->vlan_set_tagged(i, &info);
+
+ // Set forwarding action based on inner VLAN tag
+ for (i = 0; i < priv->cpu_port; i++)
+ priv->r->vlan_fwd_on_inner(i, true);
+}
+
static int rtl83xx_setup(struct dsa_switch *ds)
{
int i;
@@ -174,7 +160,7 @@ static int rtl83xx_setup(struct dsa_switch *ds)
*/
for (i = 0; i < priv->cpu_port; i++) {
if (priv->ports[i].phy) {
- priv->r->set_port_reg_be(BIT_ULL(priv->cpu_port) | BIT(i),
+ priv->r->set_port_reg_be(BIT_ULL(priv->cpu_port) | BIT_ULL(i),
priv->r->port_iso_ctrl(i));
port_bitmap |= BIT_ULL(i);
}
@@ -188,6 +174,8 @@ static int rtl83xx_setup(struct dsa_switch *ds)
rtl83xx_init_stats(priv);
+ rtl83xx_vlan_setup(priv);
+
ds->configure_vlan_while_not_filtering = true;
/* Enable MAC Polling PHY again */
@@ -218,8 +206,8 @@ static int rtl930x_setup(struct dsa_switch *ds)
for (i = 0; i < priv->cpu_port; i++) {
if (priv->ports[i].phy) {
- priv->r->traffic_set(i, BIT(priv->cpu_port) | BIT(i));
- port_bitmap |= 1ULL << i;
+ priv->r->traffic_set(i, BIT_ULL(priv->cpu_port) | BIT_ULL(i));
+ port_bitmap |= BIT_ULL(i);
}
}
priv->r->traffic_set(priv->cpu_port, port_bitmap);
@@ -228,6 +216,8 @@ static int rtl930x_setup(struct dsa_switch *ds)
// TODO: Initialize statistics
+ rtl83xx_vlan_setup(priv);
+
ds->configure_vlan_while_not_filtering = true;
rtl83xx_enable_phy_polling(priv);
@@ -242,9 +232,10 @@ static void rtl83xx_phylink_validate(struct dsa_switch *ds, int port,
struct rtl838x_switch_priv *priv = ds->priv;
__ETHTOOL_DECLARE_LINK_MODE_MASK(mask) = { 0, };
- pr_debug("In %s port %d", __func__, port);
+ pr_debug("In %s port %d, state is %d", __func__, port, state->interface);
if (!phy_interface_mode_is_rgmii(state->interface) &&
+ state->interface != PHY_INTERFACE_MODE_NA &&
state->interface != PHY_INTERFACE_MODE_1000BASEX &&
state->interface != PHY_INTERFACE_MODE_MII &&
state->interface != PHY_INTERFACE_MODE_REVMII &&
@@ -278,6 +269,10 @@ static void rtl83xx_phylink_validate(struct dsa_switch *ds, int port,
if (port >= 24 && port <= 27 && priv->family_id == RTL8380_FAMILY_ID)
phylink_set(mask, 1000baseX_Full);
+ /* On the RTL839x family of SoCs, ports 48 to 51 are SFP ports */
+ if (port >=48 && port <= 51 && priv->family_id == RTL8390_FAMILY_ID)
+ phylink_set(mask, 1000baseX_Full);
+
phylink_set(mask, 10baseT_Half);
phylink_set(mask, 10baseT_Full);
phylink_set(mask, 100baseT_Half);
@@ -310,7 +305,7 @@ static int rtl83xx_phylink_mac_link_state(struct dsa_switch *ds, int port,
link = priv->r->get_port_reg_le(priv->r->mac_link_sts);
if (link & BIT_ULL(port))
state->link = 1;
- pr_info("%s: link state: %llx\n", __func__, link & BIT_ULL(port));
+ pr_debug("%s: link state port %d: %llx\n", __func__, port, link & BIT_ULL(port));
state->duplex = 0;
if (priv->r->get_port_reg_le(priv->r->mac_link_dup_sts) & BIT_ULL(port))
@@ -329,7 +324,8 @@ static int rtl83xx_phylink_mac_link_state(struct dsa_switch *ds, int port,
state->speed = SPEED_1000;
break;
case 3:
- if (port == 24 || port == 26) /* Internal serdes */
+ if (priv->family_id == RTL9300_FAMILY_ID
+ && (port == 24 || port == 26)) /* Internal serdes */
state->speed = SPEED_2500;
else
state->speed = SPEED_100; /* Is in fact 500Mbit */
@@ -343,6 +339,43 @@ static int rtl83xx_phylink_mac_link_state(struct dsa_switch *ds, int port,
return 1;
}
+static void rtl83xx_config_interface(int port, phy_interface_t interface)
+{
+ u32 old, int_shift, sds_shift;
+
+ switch (port) {
+ case 24:
+ int_shift = 0;
+ sds_shift = 5;
+ break;
+ case 26:
+ int_shift = 3;
+ sds_shift = 0;
+ break;
+ default:
+ return;
+ }
+
+ old = sw_r32(RTL838X_SDS_MODE_SEL);
+ switch (interface) {
+ case PHY_INTERFACE_MODE_1000BASEX:
+ if ((old >> sds_shift & 0x1f) == 4)
+ return;
+ sw_w32_mask(0x7 << int_shift, 1 << int_shift, RTL838X_INT_MODE_CTRL);
+ sw_w32_mask(0x1f << sds_shift, 4 << sds_shift, RTL838X_SDS_MODE_SEL);
+ break;
+ case PHY_INTERFACE_MODE_SGMII:
+ if ((old >> sds_shift & 0x1f) == 2)
+ return;
+ sw_w32_mask(0x7 << int_shift, 2 << int_shift, RTL838X_INT_MODE_CTRL);
+ sw_w32_mask(0x1f << sds_shift, 2 << sds_shift, RTL838X_SDS_MODE_SEL);
+ break;
+ default:
+ return;
+ }
+ pr_debug("configured port %d for interface %s\n", port, phy_modes(interface));
+}
+
static void rtl83xx_phylink_mac_config(struct dsa_switch *ds, int port,
unsigned int mode,
const struct phylink_link_state *state)
@@ -376,10 +409,11 @@ static void rtl83xx_phylink_mac_config(struct dsa_switch *ds, int port,
reg = sw_r32(priv->r->mac_force_mode_ctrl(port));
/* Auto-Negotiation does not work for MAC in RTL8390 */
if (priv->family_id == RTL8380_FAMILY_ID) {
- if (mode == MLO_AN_PHY) {
+ if (mode == MLO_AN_PHY || phylink_autoneg_inband(mode)) {
pr_debug("PHY autonegotiates\n");
reg |= BIT(2);
sw_w32(reg, priv->r->mac_force_mode_ctrl(port));
+ rtl83xx_config_interface(port, state->interface);
return;
}
}
@@ -509,8 +543,11 @@ static int rtl83xx_port_enable(struct dsa_switch *ds, int port,
v |= priv->ports[port].pm;
priv->r->traffic_set(port, v);
- sw_w32_mask(0, BIT(port), RTL930X_L2_PORT_SABLK_CTRL);
- sw_w32_mask(0, BIT(port), RTL930X_L2_PORT_DABLK_CTRL);
+ // TODO: Figure out if this is necessary
+ if (priv->family_id == RTL9300_FAMILY_ID) {
+ sw_w32_mask(0, BIT(port), RTL930X_L2_PORT_SABLK_CTRL);
+ sw_w32_mask(0, BIT(port), RTL930X_L2_PORT_DABLK_CTRL);
+ }
return 0;
}
@@ -537,58 +574,55 @@ static void rtl83xx_port_disable(struct dsa_switch *ds, int port)
priv->ports[port].enable = false;
}
+static int rtl83xx_set_mac_eee(struct dsa_switch *ds, int port,
+ struct ethtool_eee *e)
+{
+ struct rtl838x_switch_priv *priv = ds->priv;
+
+ if (e->eee_enabled && !priv->eee_enabled) {
+ pr_info("Globally enabling EEE\n");
+ priv->r->init_eee(priv, true);
+ }
+
+ priv->r->port_eee_set(priv, port, e->eee_enabled);
+
+ if (e->eee_enabled)
+ pr_info("Enabled EEE for port %d\n", port);
+ else
+ pr_info("Disabled EEE for port %d\n", port);
+ return 0;
+}
+
static int rtl83xx_get_mac_eee(struct dsa_switch *ds, int port,
struct ethtool_eee *e)
{
struct rtl838x_switch_priv *priv = ds->priv;
- pr_debug("%s: port %d", __func__, port);
e->supported = SUPPORTED_100baseT_Full | SUPPORTED_1000baseT_Full;
- if (sw_r32(priv->r->mac_force_mode_ctrl(port)) & BIT(9))
- e->advertised |= ADVERTISED_100baseT_Full;
- if (sw_r32(priv->r->mac_force_mode_ctrl(port)) & BIT(10))
- e->advertised |= ADVERTISED_1000baseT_Full;
+ priv->r->eee_port_ability(priv, e, port);
e->eee_enabled = priv->ports[port].eee_enabled;
- pr_debug("enabled: %d, active %x\n", e->eee_enabled, e->advertised);
-
- if (sw_r32(RTL838X_MAC_EEE_ABLTY) & BIT(port)) {
- e->lp_advertised = ADVERTISED_100baseT_Full;
- e->lp_advertised |= ADVERTISED_1000baseT_Full;
- }
e->eee_active = !!(e->advertised & e->lp_advertised);
- pr_debug("active: %d, lp %x\n", e->eee_active, e->lp_advertised);
return 0;
}
-static int rtl83xx_set_mac_eee(struct dsa_switch *ds, int port,
+static int rtl93xx_get_mac_eee(struct dsa_switch *ds, int port,
struct ethtool_eee *e)
{
struct rtl838x_switch_priv *priv = ds->priv;
- pr_debug("%s: port %d", __func__, port);
- if (e->eee_enabled) {
- pr_debug("Globally enabling EEE\n");
- sw_w32_mask(0x4, 0, RTL838X_SMI_GLB_CTRL);
- }
- if (e->eee_enabled) {
- pr_debug("Enabling EEE for MAC %d\n", port);
- sw_w32_mask(0, 3 << 9, priv->r->mac_force_mode_ctrl(port));
- sw_w32_mask(0, BIT(port), RTL838X_EEE_PORT_TX_EN);
- sw_w32_mask(0, BIT(port), RTL838X_EEE_PORT_RX_EN);
- priv->ports[port].eee_enabled = true;
- e->eee_enabled = true;
- } else {
- pr_debug("Disabling EEE for MAC %d\n", port);
- sw_w32_mask(3 << 9, 0, priv->r->mac_force_mode_ctrl(port));
- sw_w32_mask(BIT(port), 0, RTL838X_EEE_PORT_TX_EN);
- sw_w32_mask(BIT(port), 0, RTL838X_EEE_PORT_RX_EN);
- priv->ports[port].eee_enabled = false;
- e->eee_enabled = false;
- }
+ e->supported = SUPPORTED_100baseT_Full | SUPPORTED_1000baseT_Full
+ | SUPPORTED_2500baseX_Full;
+
+ priv->r->eee_port_ability(priv, e, port);
+
+ e->eee_enabled = priv->ports[port].eee_enabled;
+
+ e->eee_active = !!(e->advertised & e->lp_advertised);
+
return 0;
}
@@ -618,7 +652,7 @@ static int rtl83xx_port_bridge_join(struct dsa_switch *ds, int port,
struct net_device *bridge)
{
struct rtl838x_switch_priv *priv = ds->priv;
- u64 port_bitmap = 1ULL << priv->cpu_port, v;
+ u64 port_bitmap = BIT_ULL(priv->cpu_port), v;
int i;
pr_debug("%s %x: %d %llx", __func__, (u32)priv, port, port_bitmap);
@@ -634,8 +668,8 @@ static int rtl83xx_port_bridge_join(struct dsa_switch *ds, int port,
if (priv->ports[i].enable)
priv->r->traffic_enable(i, port);
- priv->ports[i].pm |= 1ULL << port;
- port_bitmap |= 1ULL << i;
+ priv->ports[i].pm |= BIT_ULL(port);
+ port_bitmap |= BIT_ULL(i);
}
}
@@ -656,7 +690,7 @@ static void rtl83xx_port_bridge_leave(struct dsa_switch *ds, int port,
struct net_device *bridge)
{
struct rtl838x_switch_priv *priv = ds->priv;
- u64 port_bitmap = 1ULL << priv->cpu_port, v;
+ u64 port_bitmap = BIT_ULL(priv->cpu_port), v;
int i;
pr_debug("%s %x: %d", __func__, (u32)priv, port);
@@ -674,7 +708,7 @@ static void rtl83xx_port_bridge_leave(struct dsa_switch *ds, int port,
if (priv->ports[i].enable)
priv->r->traffic_disable(i, port);
- priv->ports[i].pm |= 1ULL << port;
+ priv->ports[i].pm |= BIT_ULL(port);
port_bitmap &= ~BIT_ULL(i);
}
}
@@ -826,17 +860,16 @@ static int rtl83xx_vlan_prepare(struct dsa_switch *ds, int port,
struct rtl838x_vlan_info info;
struct rtl838x_switch_priv *priv = ds->priv;
- pr_info("%s: port %d\n", __func__, port);
+ priv->r->vlan_tables_read(0, &info);
- mutex_lock(&priv->reg_mutex);
+ pr_debug("VLAN 0: Tagged ports %llx, untag %llx, profile %d, MC# %d, UC# %d, FID %x\n",
+ info.tagged_ports, info.untagged_ports, info.profile_id,
+ info.hash_mc_fid, info.hash_uc_fid, info.fid);
- priv->r->vlan_profile_dump(1);
priv->r->vlan_tables_read(1, &info);
-
- pr_info("Tagged ports %llx, untag %llx, prof %x, MC# %d, UC# %d, FID %x\n",
+ pr_debug("VLAN 1: Tagged ports %llx, untag %llx, profile %d, MC# %d, UC# %d, FID %x\n",
info.tagged_ports, info.untagged_ports, info.profile_id,
info.hash_mc_fid, info.hash_uc_fid, info.fid);
-
priv->r->vlan_set_untagged(1, info.untagged_ports);
pr_debug("SET: Untagged ports, VLAN %d: %llx\n", 1, info.untagged_ports);
@@ -854,7 +887,7 @@ static void rtl83xx_vlan_add(struct dsa_switch *ds, int port,
struct rtl838x_switch_priv *priv = ds->priv;
int v;
- pr_info("%s port %d, vid_end %d, vid_end %d, flags %x\n", __func__,
+ pr_debug("%s port %d, vid_end %d, vid_end %d, flags %x\n", __func__,
port, vlan->vid_begin, vlan->vid_end, vlan->flags);
if (vlan->vid_begin > 4095 || vlan->vid_end > 4095) {
@@ -899,10 +932,10 @@ static void rtl83xx_vlan_add(struct dsa_switch *ds, int port,
info.untagged_ports |= BIT_ULL(port);
priv->r->vlan_set_untagged(v, info.untagged_ports);
- pr_info("Untagged ports, VLAN %d: %llx\n", v, info.untagged_ports);
+ pr_debug("Untagged ports, VLAN %d: %llx\n", v, info.untagged_ports);
priv->r->vlan_set_tagged(v, &info);
- pr_info("Tagged ports, VLAN %d: %llx\n", v, info.tagged_ports);
+ pr_debug("Tagged ports, VLAN %d: %llx\n", v, info.tagged_ports);
}
mutex_unlock(&priv->reg_mutex);
@@ -953,59 +986,136 @@ static int rtl83xx_vlan_del(struct dsa_switch *ds, int port,
return 0;
}
-static int rtl83xx_port_fdb_add(struct dsa_switch *ds, int port,
- const unsigned char *addr, u16 vid)
+static void dump_l2_entry(struct rtl838x_l2_entry *e)
{
- struct rtl838x_switch_priv *priv = ds->priv;
- u64 mac = ether_addr_to_u64(addr);
- u32 key = rtl83xx_hash_key(priv, mac, vid);
- struct rtl838x_l2_entry e;
- u32 r[3];
+ pr_info("MAC: %02x:%02x:%02x:%02x:%02x:%02x vid: %d, rvid: %d, port: %d, valid: %d\n",
+ e->mac[0], e->mac[1], e->mac[2], e->mac[3], e->mac[4], e->mac[5],
+ e->vid, e->rvid, e->port, e->valid);
+
+ if (e->type != L2_MULTICAST) {
+ pr_info("Type: %d, is_static: %d, is_ip_mc: %d, is_ipv6_mc: %d, block_da: %d\n",
+ e->type, e->is_static, e->is_ip_mc, e->is_ipv6_mc, e->block_da);
+ pr_info(" block_sa: %d, susp: %d, nh: %d, age: %d, is_trunk: %d, trunk: %d\n",
+ e->block_sa, e->suspended, e->next_hop, e->age, e->is_trunk, e->trunk);
+ }
+ if (e->type == L2_MULTICAST)
+ pr_info(" L2_MULTICAST mc_portmask_index: %d\n", e->mc_portmask_index);
+ if (e->is_ip_mc || e->is_ipv6_mc)
+ pr_info(" mc_portmask_index: %d, mc_gip: %d, mc_sip: %d\n",
+ e->mc_portmask_index, e->mc_gip, e->mc_sip);
+ pr_info(" stack_dev: %d\n", e->stack_dev);
+ if (e->next_hop)
+ pr_info(" nh_route_id: %d\n", e->nh_route_id);
+}
+
+static void rtl83xx_setup_l2_uc_entry(struct rtl838x_l2_entry *e, int port, int vid, u64 mac)
+{
+ e->is_ip_mc = e->is_ipv6_mc = false;
+ e->valid = true;
+ e->age = 3;
+ e->port = port,
+ e->vid = vid;
+ u64_to_ether_addr(mac, e->mac);
+}
+
+static void rtl83xx_setup_l2_mc_entry(struct rtl838x_switch_priv *priv,
+ struct rtl838x_l2_entry *e, int vid, u64 mac, int mc_group)
+{
+ e->is_ip_mc = e->is_ipv6_mc = false;
+ e->valid = true;
+ e->mc_portmask_index = mc_group;
+ e->type = L2_MULTICAST;
+ e->rvid = e->vid = vid;
+ pr_debug("%s: vid: %d, rvid: %d\n", __func__, e->vid, e->rvid);
+ u64_to_ether_addr(mac, e->mac);
+}
+
+/*
+ * Uses the seed to identify a hash bucket in the L2 using the derived hash key and then loops
+ * over the entries in the bucket until either a matching entry is found or an empty slot
+ * Returns the filled in rtl838x_l2_entry and the index in the bucket when an entry was found
+ * when an empty slot was found and must exist is false, the index of the slot is returned
+ * when no slots are available returns -1
+ */
+static int rtl83xx_find_l2_hash_entry(struct rtl838x_switch_priv *priv, u64 seed,
+ bool must_exist, struct rtl838x_l2_entry *e)
+{
+ int i, idx = -1;
+ u32 key = priv->r->l2_hash_key(priv, seed);
u64 entry;
- int idx = -1, err = 0, i;
- mutex_lock(&priv->reg_mutex);
- for (i = 0; i < 4; i++) {
- entry = priv->r->read_l2_entry_using_hash(key, i, &e);
- if (!e.valid) {
- idx = (key << 2) | i;
- break;
- }
- if ((entry & 0x0fffffffffffffffULL) == ((mac << 12) | vid)) {
- idx = (key << 2) | i;
+ pr_debug("%s: using key %x, for seed %016llx\n", __func__, key, seed);
+ // Loop over all entries in the hash-bucket and over the second block on 93xx SoCs
+ for (i = 0; i < priv->l2_bucket_size; i++) {
+ entry = priv->r->read_l2_entry_using_hash(key, i, e);
+ pr_debug("valid %d, mac %016llx\n", e->valid, ether_addr_to_u64(&e->mac[0]));
+ if (must_exist && !e->valid)
+ continue;
+ if (!e->valid || ((entry & 0x0fffffffffffffffULL) == seed)) {
+ idx = i > 3 ? ((key >> 14) & 0xffff) | i >> 1 : ((key << 2) | i) & 0xffff;
break;
}
}
- if (idx >= 0) {
- r[0] = 3 << 17 | port << 12; // Aging and port
- r[0] |= vid;
- r[1] = mac >> 16;
- r[2] = (mac & 0xffff) << 12; /* rvid = 0 */
- rtl83xx_write_hash(idx, r);
- goto out;
- }
- /* Hash buckets full, try CAM */
+ return idx;
+}
+
+/*
+ * Uses the seed to identify an entry in the CAM by looping over all its entries
+ * Returns the filled in rtl838x_l2_entry and the index in the CAM when an entry was found
+ * when an empty slot was found the index of the slot is returned
+ * when no slots are available returns -1
+ */
+static int rtl83xx_find_l2_cam_entry(struct rtl838x_switch_priv *priv, u64 seed,
+ bool must_exist, struct rtl838x_l2_entry *e)
+{
+ int i, idx = -1;
+ u64 entry;
+
for (i = 0; i < 64; i++) {
- entry = priv->r->read_cam(i, &e);
- if (!e.valid) {
+ entry = priv->r->read_cam(i, e);
+ if (!must_exist && !e->valid) {
if (idx < 0) /* First empty entry? */
idx = i;
break;
- } else if ((entry & 0x0fffffffffffffffULL) == ((mac << 12) | vid)) {
+ } else if ((entry & 0x0fffffffffffffffULL) == seed) {
pr_debug("Found entry in CAM\n");
idx = i;
break;
}
}
+ return idx;
+}
+
+static int rtl83xx_port_fdb_add(struct dsa_switch *ds, int port,
+ const unsigned char *addr, u16 vid)
+{
+ struct rtl838x_switch_priv *priv = ds->priv;
+ u64 mac = ether_addr_to_u64(addr);
+ struct rtl838x_l2_entry e;
+ int err = 0, idx;
+ u64 seed = priv->r->l2_hash_seed(mac, vid);
+
+ mutex_lock(&priv->reg_mutex);
+
+ idx = rtl83xx_find_l2_hash_entry(priv, seed, false, &e);
+
+ // Found an existing or empty entry
+ if (idx >= 0) {
+ rtl83xx_setup_l2_uc_entry(&e, port, vid, mac);
+ priv->r->write_l2_entry_using_hash(idx >> 2, idx & 0x3, &e);
+ goto out;
+ }
+
+ // Hash buckets full, try CAM
+ rtl83xx_find_l2_cam_entry(priv, seed, false, &e);
+
if (idx >= 0) {
- r[0] = 3 << 17 | port << 12; // Aging
- r[0] |= vid;
- r[1] = mac >> 16;
- r[2] = (mac & 0xffff) << 12; /* rvid = 0 */
- rtl83xx_write_cam(idx, r);
+ rtl83xx_setup_l2_uc_entry(&e, port, vid, mac);
+ priv->r->write_cam(idx, &e);
goto out;
}
+
err = -ENOTSUPP;
out:
mutex_unlock(&priv->reg_mutex);
@@ -1017,41 +1127,29 @@ static int rtl83xx_port_fdb_del(struct dsa_switch *ds, int port,
{
struct rtl838x_switch_priv *priv = ds->priv;
u64 mac = ether_addr_to_u64(addr);
- u32 key = rtl83xx_hash_key(priv, mac, vid);
struct rtl838x_l2_entry e;
- u32 r[3];
- u64 entry;
- int idx = -1, err = 0, i;
+ int err = 0, idx;
+ u64 seed = priv->r->l2_hash_seed(mac, vid);
- pr_debug("In %s, mac %llx, vid: %d, key: %x08x\n", __func__, mac, vid, key);
+ pr_info("In %s, mac %llx, vid: %d\n", __func__, mac, vid);
mutex_lock(&priv->reg_mutex);
- for (i = 0; i < 4; i++) {
- entry = priv->r->read_l2_entry_using_hash(key, i, &e);
- if (!e.valid)
- continue;
- if ((entry & 0x0fffffffffffffffULL) == ((mac << 12) | vid)) {
- idx = (key << 2) | i;
- break;
- }
- }
+ idx = rtl83xx_find_l2_hash_entry(priv, seed, true, &e);
+
+ pr_info("Found entry index %d, key %d and bucket %d\n", idx, idx >> 2, idx & 3);
if (idx >= 0) {
- r[0] = r[1] = r[2] = 0;
- rtl83xx_write_hash(idx, r);
+ e.valid = false;
+ dump_l2_entry(&e);
+ priv->r->write_l2_entry_using_hash(idx >> 2, idx & 0x3, &e);
goto out;
}
/* Check CAM for spillover from hash buckets */
- for (i = 0; i < 64; i++) {
- entry = priv->r->read_cam(i, &e);
- if ((entry & 0x0fffffffffffffffULL) == ((mac << 12) | vid)) {
- idx = i;
- break;
- }
- }
+ rtl83xx_find_l2_cam_entry(priv, seed, true, &e);
+
if (idx >= 0) {
- r[0] = r[1] = r[2] = 0;
- rtl83xx_write_cam(idx, r);
+ e.valid = false;
+ priv->r->write_cam(idx, &e);
goto out;
}
err = -ENOENT;
@@ -1066,8 +1164,7 @@ static int rtl83xx_port_fdb_dump(struct dsa_switch *ds, int port,
struct rtl838x_l2_entry e;
struct rtl838x_switch_priv *priv = ds->priv;
int i;
- u32 fid;
- u32 pkey;
+ u32 fid, pkey;
u64 mac;
mutex_lock(&priv->reg_mutex);
@@ -1079,13 +1176,25 @@ static int rtl83xx_port_fdb_dump(struct dsa_switch *ds, int port,
continue;
if (e.port == port) {
- fid = (i & 0x3ff) | (e.rvid & ~0x3ff);
+ fid = ((i >> 2) & 0x3ff) | (e.rvid & ~0x3ff);
mac = ether_addr_to_u64(&e.mac[0]);
- pkey = rtl838x_hash(priv, mac << 12 | fid);
+ pkey = priv->r->l2_hash_key(priv, priv->r->l2_hash_seed(mac, fid));
fid = (pkey & 0x3ff) | (fid & ~0x3ff);
- pr_debug("-> mac %016llx, fid: %d\n", mac, fid);
+ pr_info("-> index %d, key %x, bucket %d, dmac %016llx, fid: %x rvid: %x\n",
+ i, i >> 2, i & 0x3, mac, fid, e.rvid);
+ dump_l2_entry(&e);
+ u64 seed = priv->r->l2_hash_seed(mac, e.rvid);
+ u32 key = priv->r->l2_hash_key(priv, seed);
+ pr_info("seed: %016llx, key based on rvid: %08x\n", seed, key);
cb(e.mac, e.vid, e.is_static, data);
}
+ if (e.type == L2_MULTICAST) {
+ u64 portmask = priv->r->read_mcast_pmask(e.mc_portmask_index);
+ if (portmask & BIT_ULL(port)) {
+ dump_l2_entry(&e);
+ pr_info(" PM: %016llx\n", portmask);
+ }
+ }
}
for (i = 0; i < 64; i++) {
@@ -1102,6 +1211,164 @@ static int rtl83xx_port_fdb_dump(struct dsa_switch *ds, int port,
return 0;
}
+static int rtl83xx_port_mdb_prepare(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_mdb *mdb)
+{
+ struct rtl838x_switch_priv *priv = ds->priv;
+
+ if (priv->id >= 0x9300)
+ return -EOPNOTSUPP;
+
+ return 0;
+}
+
+static int rtl83xx_mc_group_alloc(struct rtl838x_switch_priv *priv, int port)
+{
+ int mc_group = find_first_zero_bit(priv->mc_group_bm, MAX_MC_GROUPS - 1);
+ u64 portmask;
+
+ if (mc_group >= MAX_MC_GROUPS - 1)
+ return -1;
+
+ pr_debug("Using MC group %d\n", mc_group);
+ set_bit(mc_group, priv->mc_group_bm);
+ mc_group++; // We cannot use group 0, as this is used for lookup miss flooding
+ portmask = BIT_ULL(port);
+ priv->r->write_mcast_pmask(mc_group, portmask);
+
+ return mc_group;
+}
+
+static u64 rtl83xx_mc_group_add_port(struct rtl838x_switch_priv *priv, int mc_group, int port)
+{
+ u64 portmask = priv->r->read_mcast_pmask(mc_group);
+
+ portmask |= BIT_ULL(port);
+ priv->r->write_mcast_pmask(mc_group, portmask);
+
+ return portmask;
+}
+
+static u64 rtl83xx_mc_group_del_port(struct rtl838x_switch_priv *priv, int mc_group, int port)
+{
+ u64 portmask = priv->r->read_mcast_pmask(mc_group);
+
+ portmask &= ~BIT_ULL(port);
+ priv->r->write_mcast_pmask(mc_group, portmask);
+ if (!portmask)
+ clear_bit(mc_group, priv->mc_group_bm);
+
+ return portmask;
+}
+
+static void rtl83xx_port_mdb_add(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_mdb *mdb)
+{
+ struct rtl838x_switch_priv *priv = ds->priv;
+ u64 mac = ether_addr_to_u64(mdb->addr);
+ struct rtl838x_l2_entry e;
+ int err = 0, idx;
+ int vid = mdb->vid;
+ u64 seed = priv->r->l2_hash_seed(mac, vid);
+ int mc_group;
+
+ pr_debug("In %s port %d, mac %llx, vid: %d\n", __func__, port, mac, vid);
+ mutex_lock(&priv->reg_mutex);
+
+ idx = rtl83xx_find_l2_hash_entry(priv, seed, false, &e);
+
+ // Found an existing or empty entry
+ if (idx >= 0) {
+ if (e.valid) {
+ pr_debug("Found an existing entry %016llx, mc_group %d\n",
+ ether_addr_to_u64(e.mac), e.mc_portmask_index);
+ rtl83xx_mc_group_add_port(priv, e.mc_portmask_index, port);
+ } else {
+ pr_debug("New entry for seed %016llx\n", seed);
+ mc_group = rtl83xx_mc_group_alloc(priv, port);
+ if (mc_group < 0) {
+ err = -ENOTSUPP;
+ goto out;
+ }
+ rtl83xx_setup_l2_mc_entry(priv, &e, vid, mac, mc_group);
+ priv->r->write_l2_entry_using_hash(idx >> 2, idx & 0x3, &e);
+ }
+ goto out;
+ }
+
+ // Hash buckets full, try CAM
+ rtl83xx_find_l2_cam_entry(priv, seed, false, &e);
+
+ if (idx >= 0) {
+ if (e.valid) {
+ pr_debug("Found existing CAM entry %016llx, mc_group %d\n",
+ ether_addr_to_u64(e.mac), e.mc_portmask_index);
+ rtl83xx_mc_group_add_port(priv, e.mc_portmask_index, port);
+ } else {
+ pr_debug("New entry\n");
+ mc_group = rtl83xx_mc_group_alloc(priv, port);
+ if (mc_group < 0) {
+ err = -ENOTSUPP;
+ goto out;
+ }
+ rtl83xx_setup_l2_mc_entry(priv, &e, vid, mac, mc_group);
+ priv->r->write_cam(idx, &e);
+ }
+ goto out;
+ }
+
+ err = -ENOTSUPP;
+out:
+ mutex_unlock(&priv->reg_mutex);
+ if (err)
+ dev_err(ds->dev, "failed to add MDB entry\n");
+}
+
+int rtl83xx_port_mdb_del(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_mdb *mdb)
+{
+ struct rtl838x_switch_priv *priv = ds->priv;
+ u64 mac = ether_addr_to_u64(mdb->addr);
+ struct rtl838x_l2_entry e;
+ int err = 0, idx;
+ int vid = mdb->vid;
+ u64 seed = priv->r->l2_hash_seed(mac, vid);
+ u64 portmask;
+
+ pr_debug("In %s, port %d, mac %llx, vid: %d\n", __func__, port, mac, vid);
+ mutex_lock(&priv->reg_mutex);
+
+ idx = rtl83xx_find_l2_hash_entry(priv, seed, true, &e);
+
+ pr_debug("Found entry index %d, key %d and bucket %d\n", idx, idx >> 2, idx & 3);
+ if (idx >= 0) {
+ portmask = rtl83xx_mc_group_del_port(priv, e.mc_portmask_index, port);
+ if (!portmask) {
+ e.valid = false;
+ // dump_l2_entry(&e);
+ priv->r->write_l2_entry_using_hash(idx >> 2, idx & 0x3, &e);
+ }
+ goto out;
+ }
+
+ /* Check CAM for spillover from hash buckets */
+ rtl83xx_find_l2_cam_entry(priv, seed, true, &e);
+
+ if (idx >= 0) {
+ portmask = rtl83xx_mc_group_del_port(priv, e.mc_portmask_index, port);
+ if (!portmask) {
+ e.valid = false;
+ // dump_l2_entry(&e);
+ priv->r->write_cam(idx, &e);
+ }
+ goto out;
+ }
+ // TODO: Re-enable with a newer kernel: err = -ENOENT;
+out:
+ mutex_unlock(&priv->reg_mutex);
+ return err;
+}
+
static int rtl83xx_port_mirror_add(struct dsa_switch *ds, int port,
struct dsa_mall_mirror_tc_entry *mirror,
bool ingress)
@@ -1268,6 +1535,10 @@ const struct dsa_switch_ops rtl83xx_switch_ops = {
.port_fdb_del = rtl83xx_port_fdb_del,
.port_fdb_dump = rtl83xx_port_fdb_dump,
+ .port_mdb_prepare = rtl83xx_port_mdb_prepare,
+ .port_mdb_add = rtl83xx_port_mdb_add,
+ .port_mdb_del = rtl83xx_port_mdb_del,
+
.port_mirror_add = rtl83xx_port_mirror_add,
.port_mirror_del = rtl83xx_port_mirror_del,
};
@@ -1292,6 +1563,9 @@ const struct dsa_switch_ops rtl930x_switch_ops = {
.port_enable = rtl83xx_port_enable,
.port_disable = rtl83xx_port_disable,
+ .get_mac_eee = rtl93xx_get_mac_eee,
+ .set_mac_eee = rtl83xx_set_mac_eee,
+
.set_ageing_time = rtl83xx_set_l2aging,
.port_bridge_join = rtl83xx_port_bridge_join,
.port_bridge_leave = rtl83xx_port_bridge_leave,
@@ -1306,4 +1580,9 @@ const struct dsa_switch_ops rtl930x_switch_ops = {
.port_fdb_add = rtl83xx_port_fdb_add,
.port_fdb_del = rtl83xx_port_fdb_del,
.port_fdb_dump = rtl83xx_port_fdb_dump,
+
+ .port_mdb_prepare = rtl83xx_port_mdb_prepare,
+ .port_mdb_add = rtl83xx_port_mdb_add,
+ .port_mdb_del = rtl83xx_port_mdb_del,
+
};
diff --git a/target/linux/realtek/files-5.4/drivers/net/dsa/rtl83xx/rtl838x.c b/target/linux/realtek/files-5.4/drivers/net/dsa/rtl83xx/rtl838x.c
index de9e83bb8d..5d764b6a32 100644
--- a/target/linux/realtek/files-5.4/drivers/net/dsa/rtl83xx/rtl838x.c
+++ b/target/linux/realtek/files-5.4/drivers/net/dsa/rtl83xx/rtl838x.c
@@ -12,10 +12,10 @@ void rtl838x_print_matrix(void)
ptr8 = RTL838X_SW_BASE + RTL838X_PORT_ISO_CTRL(0);
for (i = 0; i < 28; i += 8)
- pr_info("> %8x %8x %8x %8x %8x %8x %8x %8x\n",
+ pr_debug("> %8x %8x %8x %8x %8x %8x %8x %8x\n",
ptr8[i + 0], ptr8[i + 1], ptr8[i + 2], ptr8[i + 3],
ptr8[i + 4], ptr8[i + 5], ptr8[i + 6], ptr8[i + 7]);
- pr_info("CPU_PORT> %8x\n", ptr8[28]);
+ pr_debug("CPU_PORT> %8x\n", ptr8[28]);
}
static inline int rtl838x_port_iso_ctrl(int p)
@@ -42,56 +42,98 @@ static inline int rtl838x_tbl_access_data_0(int i)
static void rtl838x_vlan_tables_read(u32 vlan, struct rtl838x_vlan_info *info)
{
- u32 cmd, v;
+ u32 v;
+ // Read VLAN table (0) via register 0
+ struct table_reg *r = rtl_table_get(RTL8380_TBL_0, 0);
+
+ rtl_table_read(r, vlan);
+ info->tagged_ports = sw_r32(rtl_table_data(r, 0));
+ v = sw_r32(rtl_table_data(r, 1));
+ pr_debug("VLAN_READ %d: %016llx %08x\n", vlan, info->tagged_ports, v);
+ rtl_table_release(r);
- cmd = BIT(15) /* Execute cmd */
- | BIT(14) /* Read */
- | 0 << 12 /* Table type 0b00 */
- | (vlan & 0xfff);
- rtl838x_exec_tbl0_cmd(cmd);
- info->tagged_ports = sw_r32(RTL838X_TBL_ACCESS_DATA_0(0));
- v = sw_r32(RTL838X_TBL_ACCESS_DATA_0(1));
info->profile_id = v & 0x7;
info->hash_mc_fid = !!(v & 0x8);
info->hash_uc_fid = !!(v & 0x10);
info->fid = (v >> 5) & 0x3f;
-
- cmd = BIT(15) /* Execute cmd */
- | BIT(14) /* Read */
- | 0 << 12 /* Table type 0b00 */
- | (vlan & 0xfff);
- rtl838x_exec_tbl1_cmd(cmd);
- info->untagged_ports = sw_r32(RTL838X_TBL_ACCESS_DATA_1(0));
+ // Read UNTAG table (0) via table register 1
+ r = rtl_table_get(RTL8380_TBL_1, 0);
+ rtl_table_read(r, vlan);
+ info->untagged_ports = sw_r32(rtl_table_data(r, 0));
+ rtl_table_release(r);
}
static void rtl838x_vlan_set_tagged(u32 vlan, struct rtl838x_vlan_info *info)
{
- u32 cmd = BIT(15) /* Execute cmd */
- | 0 << 14 /* Write */
- | 0 << 12 /* Table type 0b00 */
- | (vlan & 0xfff);
u32 v;
+ // Access VLAN table (0) via register 0
+ struct table_reg *r = rtl_table_get(RTL8380_TBL_0, 0);
- sw_w32(info->tagged_ports, RTL838X_TBL_ACCESS_DATA_0(0));
+ sw_w32(info->tagged_ports, rtl_table_data(r, 0));
v = info->profile_id;
v |= info->hash_mc_fid ? 0x8 : 0;
v |= info->hash_uc_fid ? 0x10 : 0;
v |= ((u32)info->fid) << 5;
+ sw_w32(v, rtl_table_data(r, 1));
- sw_w32(v, RTL838X_TBL_ACCESS_DATA_0(1));
- rtl838x_exec_tbl0_cmd(cmd);
+ rtl_table_write(r, vlan);
+ rtl_table_release(r);
}
static void rtl838x_vlan_set_untagged(u32 vlan, u64 portmask)
{
- u32 cmd = BIT(15) /* Execute cmd */
- | 0 << 14 /* Write */
- | 0 << 12 /* Table type 0b00 */
- | (vlan & 0xfff);
- sw_w32(portmask & 0x1fffffff, RTL838X_TBL_ACCESS_DATA_1(0));
- rtl838x_exec_tbl1_cmd(cmd);
+ // Access UNTAG table (0) via register 1
+ struct table_reg *r = rtl_table_get(RTL8380_TBL_1, 0);
+
+ sw_w32(portmask & 0x1fffffff, rtl_table_data(r, 0));
+ rtl_table_write(r, vlan);
+ rtl_table_release(r);
+}
+
+/* Sets the L2 forwarding to be based on either the inner VLAN tag or the outer
+ */
+static void rtl838x_vlan_fwd_on_inner(int port, bool is_set)
+{
+ if (is_set)
+ sw_w32_mask(BIT(port), 0, RTL838X_VLAN_PORT_FWD);
+ else
+ sw_w32_mask(0, BIT(port), RTL838X_VLAN_PORT_FWD);
+}
+
+static u64 rtl838x_l2_hash_seed(u64 mac, u32 vid)
+{
+ return mac << 12 | vid;
+}
+
+/*
+ * Applies the same hash algorithm as the one used currently by the ASIC to the seed
+ * and returns a key into the L2 hash table
+ */
+static u32 rtl838x_l2_hash_key(struct rtl838x_switch_priv *priv, u64 seed)
+{
+ u32 h1, h2, h3, h;
+
+ if (sw_r32(priv->r->l2_ctrl_0) & 1) {
+ h1 = (seed >> 11) & 0x7ff;
+ h1 = ((h1 & 0x1f) << 6) | ((h1 >> 5) & 0x3f);
+
+ h2 = (seed >> 33) & 0x7ff;
+ h2 = ((h2 & 0x3f) << 5) | ((h2 >> 6) & 0x1f);
+
+ h3 = (seed >> 44) & 0x7ff;
+ h3 = ((h3 & 0x7f) << 4) | ((h3 >> 7) & 0xf);
+
+ h = h1 ^ h2 ^ h3 ^ ((seed >> 55) & 0x1ff);
+ h ^= ((seed >> 22) & 0x7ff) ^ (seed & 0x7ff);
+ } else {
+ h = ((seed >> 55) & 0x1ff) ^ ((seed >> 44) & 0x7ff)
+ ^ ((seed >> 33) & 0x7ff) ^ ((seed >> 22) & 0x7ff)
+ ^ ((seed >> 11) & 0x7ff) ^ (seed & 0x7ff);
+ }
+
+ return h;
}
static inline int rtl838x_mac_force_mode_ctrl(int p)
@@ -124,87 +166,239 @@ inline static int rtl838x_trk_mbr_ctr(int group)
return RTL838X_TRK_MBR_CTR + (group << 2);
}
-static u64 rtl838x_read_l2_entry_using_hash(u32 hash, u32 position, struct rtl838x_l2_entry *e)
+/*
+ * Fills an L2 entry structure from the SoC registers
+ */
+static void rtl838x_fill_l2_entry(u32 r[], struct rtl838x_l2_entry *e)
+{
+ /* Table contains different entry types, we need to identify the right one:
+ * Check for MC entries, first
+ * In contrast to the RTL93xx SoCs, there is no valid bit, use heuristics to
+ * identify valid entries
+ */
+ e->is_ip_mc = !!(r[0] & BIT(22));
+ e->is_ipv6_mc = !!(r[0] & BIT(21));
+ e->type = L2_INVALID;
+
+ if (!e->is_ip_mc && !e->is_ipv6_mc) {
+ e->mac[0] = (r[1] >> 20);
+ e->mac[1] = (r[1] >> 12);
+ e->mac[2] = (r[1] >> 4);
+ e->mac[3] = (r[1] & 0xf) << 4 | (r[2] >> 28);
+ e->mac[4] = (r[2] >> 20);
+ e->mac[5] = (r[2] >> 12);
+
+ e->rvid = r[2] & 0xfff;
+ e->vid = r[0] & 0xfff;
+
+ /* Is it a unicast entry? check multicast bit */
+ if (!(e->mac[0] & 1)) {
+ e->is_static = !!((r[0] >> 19) & 1);
+ e->port = (r[0] >> 12) & 0x1f;
+ e->block_da = !!(r[1] & BIT(30));
+ e->block_sa = !!(r[1] & BIT(31));
+ e->suspended = !!(r[1] & BIT(29));
+ e->next_hop = !!(r[1] & BIT(28));
+ if (e->next_hop) {
+ pr_info("Found next hop entry, need to read extra data\n");
+ e->nh_vlan_target = !!(r[0] & BIT(9));
+ e->nh_route_id = r[0] & 0x1ff;
+ }
+ e->age = (r[0] >> 17) & 0x3;
+ e->valid = true;
+
+ /* A valid entry has one of mutli-cast, aging, sa/da-blocking,
+ * next-hop or static entry bit set */
+ if (!(r[0] & 0x007c0000) && !(r[1] & 0xd0000000))
+ e->valid = false;
+ else
+ e->type = L2_UNICAST;
+ } else { // L2 multicast
+ pr_info("Got L2 MC entry: %08x %08x %08x\n", r[0], r[1], r[2]);
+ e->valid = true;
+ e->type = L2_MULTICAST;
+ e->mc_portmask_index = (r[0] >> 12) & 0x1ff;
+ }
+ } else { // IPv4 and IPv6 multicast
+ e->valid = true;
+ e->mc_portmask_index = (r[0] >> 12) & 0x1ff;
+ e->mc_gip = r[1];
+ e->mc_sip = r[2];
+ e->rvid = r[0] & 0xfff;
+ }
+ if (e->is_ip_mc)
+ e->type = IP4_MULTICAST;
+ if (e->is_ipv6_mc)
+ e->type = IP6_MULTICAST;
+}
+
+/*
+ * Fills the 3 SoC table registers r[] with the information of in the rtl838x_l2_entry
+ */
+static void rtl838x_fill_l2_row(u32 r[], struct rtl838x_l2_entry *e)
+{
+ u64 mac = ether_addr_to_u64(e->mac);
+
+ if (!e->valid) {
+ r[0] = r[1] = r[2] = 0;
+ return;
+ }
+
+ r[0] = e->is_ip_mc ? BIT(22) : 0;
+ r[0] |= e->is_ipv6_mc ? BIT(21) : 0;
+
+ if (!e->is_ip_mc && !e->is_ipv6_mc) {
+ r[1] = mac >> 20;
+ r[2] = (mac & 0xfffff) << 12;
+
+ /* Is it a unicast entry? check multicast bit */
+ if (!(e->mac[0] & 1)) {
+ r[0] |= e->is_static ? BIT(19) : 0;
+ r[0] |= (e->port & 0x3f) << 12;
+ r[0] |= e->vid;
+ r[1] |= e->block_da ? BIT(30) : 0;
+ r[1] |= e->block_sa ? BIT(31) : 0;
+ r[1] |= e->suspended ? BIT(29) : 0;
+ r[2] |= e->rvid & 0xfff;
+ if (e->next_hop) {
+ r[1] |= BIT(28);
+ r[0] |= e->nh_vlan_target ? BIT(9) : 0;
+ r[0] |= e->nh_route_id &0x1ff;
+ }
+ r[0] |= (e->age & 0x3) << 17;
+ } else { // L2 Multicast
+ r[0] |= (e->mc_portmask_index & 0x1ff) << 12;
+ r[2] |= e->rvid & 0xfff;
+ r[0] |= e->vid & 0xfff;
+ pr_info("FILL MC: %08x %08x %08x\n", r[0], r[1], r[2]);
+ }
+ } else { // IPv4 and IPv6 multicast
+ r[1] = e->mc_gip;
+ r[2] = e->mc_sip;
+ r[0] |= e->rvid;
+ }
+}
+
+/*
+ * Read an L2 UC or MC entry out of a hash bucket of the L2 forwarding table
+ * hash is the id of the bucket and pos is the position of the entry in that bucket
+ * The data read from the SoC is filled into rtl838x_l2_entry
+ */
+static u64 rtl838x_read_l2_entry_using_hash(u32 hash, u32 pos, struct rtl838x_l2_entry *e)
{
u64 entry;
u32 r[3];
+ struct table_reg *q = rtl_table_get(RTL8380_TBL_L2, 0); // Access L2 Table 0
+ u32 idx = (0 << 14) | (hash << 2) | pos; // Search SRAM, with hash and at pos in bucket
+ int i;
+
+ rtl_table_read(q, idx);
+ for (i= 0; i < 3; i++)
+ r[i] = sw_r32(rtl_table_data(q, i));
- /* Search in SRAM, with hash and at position in hash bucket (0-3) */
- u32 idx = (0 << 14) | (hash << 2) | position;
-
- u32 cmd = BIT(16) /* Execute cmd */
- | BIT(15) /* Read */
- | 0 << 13 /* Table type 0b00 */
- | (idx & 0x1fff);
-
- sw_w32(cmd, RTL838X_TBL_ACCESS_L2_CTRL);
- do { } while (sw_r32(RTL838X_TBL_ACCESS_L2_CTRL) & BIT(16));
- r[0] = sw_r32(RTL838X_TBL_ACCESS_L2_DATA(0));
- r[1] = sw_r32(RTL838X_TBL_ACCESS_L2_DATA(1));
- r[2] = sw_r32(RTL838X_TBL_ACCESS_L2_DATA(2));
-
- e->mac[0] = (r[1] >> 20);
- e->mac[1] = (r[1] >> 12);
- e->mac[2] = (r[1] >> 4);
- e->mac[3] = (r[1] & 0xf) << 4 | (r[2] >> 28);
- e->mac[4] = (r[2] >> 20);
- e->mac[5] = (r[2] >> 12);
- e->is_static = !!((r[0] >> 19) & 1);
- e->vid = r[0] & 0xfff;
- e->rvid = r[2] & 0xfff;
- e->port = (r[0] >> 12) & 0x1f;
-
- e->valid = true;
- if (!(r[0] >> 17)) /* Check for invalid entry */
- e->valid = false;
-
- if (e->valid)
- pr_debug("Found in Hash: R1 %x R2 %x R3 %x\n", r[0], r[1], r[2]);
+ rtl_table_release(q);
+
+ rtl838x_fill_l2_entry(r, e);
+ if (!e->valid)
+ return 0;
entry = (((u64) r[1]) << 32) | (r[2] & 0xfffff000) | (r[0] & 0xfff);
return entry;
}
+static void rtl838x_write_l2_entry_using_hash(u32 hash, u32 pos, struct rtl838x_l2_entry *e)
+{
+ u32 r[3];
+ struct table_reg *q = rtl_table_get(RTL8380_TBL_L2, 0);
+ int i;
+
+ u32 idx = (0 << 14) | (hash << 2) | pos; // Access SRAM, with hash and at pos in bucket
+
+ rtl838x_fill_l2_row(r, e);
+
+ for (i= 0; i < 3; i++)
+ sw_w32(r[i], rtl_table_data(q, i));
+
+ rtl_table_write(q, idx);
+ rtl_table_release(q);
+}
+
static u64 rtl838x_read_cam(int idx, struct rtl838x_l2_entry *e)
{
u64 entry;
u32 r[3];
+ struct table_reg *q = rtl_table_get(RTL8380_TBL_L2, 1); // Access L2 Table 1
+ int i;
+
+ rtl_table_read(q, idx);
+ for (i= 0; i < 3; i++)
+ r[i] = sw_r32(rtl_table_data(q, i));
- u32 cmd = BIT(16) /* Execute cmd */
- | BIT(15) /* Read */
- | BIT(13) /* Table type 0b01 */
- | (idx & 0x3f);
- sw_w32(cmd, RTL838X_TBL_ACCESS_L2_CTRL);
- do { } while (sw_r32(RTL838X_TBL_ACCESS_L2_CTRL) & BIT(16));
- r[0] = sw_r32(RTL838X_TBL_ACCESS_L2_DATA(0));
- r[1] = sw_r32(RTL838X_TBL_ACCESS_L2_DATA(1));
- r[2] = sw_r32(RTL838X_TBL_ACCESS_L2_DATA(2));
-
- e->mac[0] = (r[1] >> 20);
- e->mac[1] = (r[1] >> 12);
- e->mac[2] = (r[1] >> 4);
- e->mac[3] = (r[1] & 0xf) << 4 | (r[2] >> 28);
- e->mac[4] = (r[2] >> 20);
- e->mac[5] = (r[2] >> 12);
- e->is_static = !!((r[0] >> 19) & 1);
- e->vid = r[0] & 0xfff;
- e->rvid = r[2] & 0xfff;
- e->port = (r[0] >> 12) & 0x1f;
-
- e->valid = true;
- if (!(r[0] >> 17)) /* Check for invalid entry */
- e->valid = false;
-
- if (e->valid)
- pr_debug("Found in CAM: R1 %x R2 %x R3 %x\n", r[0], r[1], r[2]);
+ rtl_table_release(q);
+ rtl838x_fill_l2_entry(r, e);
+ if (!e->valid)
+ return 0;
+
+ pr_debug("Found in CAM: R1 %x R2 %x R3 %x\n", r[0], r[1], r[2]);
+
+ // Return MAC with concatenated VID ac concatenated ID
entry = (((u64) r[1]) << 32) | (r[2] & 0xfffff000) | (r[0] & 0xfff);
return entry;
}
-static inline int rtl838x_vlan_profile(int profile)
+static void rtl838x_write_cam(int idx, struct rtl838x_l2_entry *e)
+{
+ u32 r[3];
+ struct table_reg *q = rtl_table_get(RTL8380_TBL_L2, 1); // Access L2 Table 1
+ int i;
+
+ rtl838x_fill_l2_row(r, e);
+
+ for (i= 0; i < 3; i++)
+ sw_w32(r[i], rtl_table_data(q, i));
+
+ rtl_table_write(q, idx);
+ rtl_table_release(q);
+}
+
+static u64 rtl838x_read_mcast_pmask(int idx)
+{
+ u32 portmask;
+ // Read MC_PMSK (2) via register RTL8380_TBL_L2
+ struct table_reg *q = rtl_table_get(RTL8380_TBL_L2, 2);
+
+ rtl_table_read(q, idx);
+ portmask = sw_r32(rtl_table_data(q, 0));
+ rtl_table_release(q);
+
+ return portmask;
+}
+
+static void rtl838x_write_mcast_pmask(int idx, u64 portmask)
{
- return RTL838X_VLAN_PROFILE(profile);
+ // Access MC_PMSK (2) via register RTL8380_TBL_L2
+ struct table_reg *q = rtl_table_get(RTL8380_TBL_L2, 2);
+
+ sw_w32(((u32)portmask) & 0x1fffffff, rtl_table_data(q, 0));
+ rtl_table_write(q, idx);
+ rtl_table_release(q);
+}
+
+static void rtl838x_vlan_profile_setup(int profile)
+{
+ u32 pmask_id = UNKNOWN_MC_PMASK;
+ // Enable L2 Learning BIT 0, portmask UNKNOWN_MC_PMASK for unknown MC traffic flooding
+ u32 p = 1 | pmask_id << 1 | pmask_id << 10 | pmask_id << 19;
+
+ sw_w32(p, RTL838X_VLAN_PROFILE(profile));
+
+ /* RTL8380 and RTL8390 use an index into the portmask table to set the
+ * unknown multicast portmask, setup a default at a safe location
+ * On RTL93XX, the portmask is directly set in the profile,
+ * see e.g. rtl9300_vlan_profile_setup
+ */
+ rtl838x_write_mcast_pmask(UNKNOWN_MC_PMASK, 0x1fffffff);
}
static inline int rtl838x_vlan_port_egr_filter(int port)
@@ -263,6 +457,84 @@ void rtl838x_traffic_disable(int source, int dest)
rtl838x_mask_port_reg(BIT(dest), 0, rtl838x_port_iso_ctrl(source));
}
+/*
+ * Enables or disables the EEE/EEEP capability of a port
+ */
+static void rtl838x_port_eee_set(struct rtl838x_switch_priv *priv, int port, bool enable)
+{
+ u32 v;
+
+ // This works only for Ethernet ports, and on the RTL838X, ports from 24 are SFP
+ if (port >= 24)
+ return;
+
+ pr_debug("In %s: setting port %d to %d\n", __func__, port, enable);
+ v = enable ? 0x3 : 0x0;
+
+ // Set EEE state for 100 (bit 9) & 1000MBit (bit 10)
+ sw_w32_mask(0x3 << 9, v << 9, priv->r->mac_force_mode_ctrl(port));
+
+ // Set TX/RX EEE state
+ if (enable) {
+ sw_w32_mask(0, BIT(port), RTL838X_EEE_PORT_TX_EN);
+ sw_w32_mask(0, BIT(port), RTL838X_EEE_PORT_RX_EN);
+ } else {
+ sw_w32_mask(BIT(port), 0, RTL838X_EEE_PORT_TX_EN);
+ sw_w32_mask(BIT(port), 0, RTL838X_EEE_PORT_RX_EN);
+ }
+ priv->ports[port].eee_enabled = enable;
+}
+
+
+/*
+ * Get EEE own capabilities and negotiation result
+ */
+static int rtl838x_eee_port_ability(struct rtl838x_switch_priv *priv,
+ struct ethtool_eee *e, int port)
+{
+ u64 link;
+
+ if (port >= 24)
+ return 0;
+
+ link = rtl839x_get_port_reg_le(RTL838X_MAC_LINK_STS);
+ if (!(link & BIT(port)))
+ return 0;
+
+ if (sw_r32(rtl838x_mac_force_mode_ctrl(port)) & BIT(9))
+ e->advertised |= ADVERTISED_100baseT_Full;
+
+ if (sw_r32(rtl838x_mac_force_mode_ctrl(port)) & BIT(10))
+ e->advertised |= ADVERTISED_1000baseT_Full;
+
+ if (sw_r32(RTL838X_MAC_EEE_ABLTY) & BIT(port)) {
+ e->lp_advertised = ADVERTISED_100baseT_Full;
+ e->lp_advertised |= ADVERTISED_1000baseT_Full;
+ return 1;
+ }
+
+ return 0;
+}
+
+static void rtl838x_init_eee(struct rtl838x_switch_priv *priv, bool enable)
+{
+ int i;
+
+ pr_info("Setting up EEE, state: %d\n", enable);
+ sw_w32_mask(0x4, 0, RTL838X_SMI_GLB_CTRL);
+
+ /* Set timers for EEE */
+ sw_w32(0x5001411, RTL838X_EEE_TX_TIMER_GIGA_CTRL);
+ sw_w32(0x5001417, RTL838X_EEE_TX_TIMER_GELITE_CTRL);
+
+ // Enable EEE MAC support on ports
+ for (i = 0; i < priv->cpu_port; i++) {
+ if (priv->ports[i].phy)
+ rtl838x_port_eee_set(priv, i, enable);
+ }
+ priv->eee_enabled = enable;
+}
+
const struct rtl838x_reg rtl838x_reg = {
.mask_port_reg_be = rtl838x_mask_port_reg,
.set_port_reg_be = rtl838x_set_port_reg,
@@ -295,6 +567,8 @@ const struct rtl838x_reg rtl838x_reg = {
.vlan_set_untagged = rtl838x_vlan_set_untagged,
.mac_force_mode_ctrl = rtl838x_mac_force_mode_ctrl,
.vlan_profile_dump = rtl838x_vlan_profile_dump,
+ .vlan_profile_setup = rtl838x_vlan_profile_setup,
+ .vlan_fwd_on_inner = rtl838x_vlan_fwd_on_inner,
.stp_get = rtl838x_stp_get,
.stp_set = rtl838x_stp_set,
.mac_port_ctrl = rtl838x_mac_port_ctrl,
@@ -309,7 +583,9 @@ const struct rtl838x_reg rtl838x_reg = {
.mac_rx_pause_sts = RTL838X_MAC_RX_PAUSE_STS,
.mac_tx_pause_sts = RTL838X_MAC_TX_PAUSE_STS,
.read_l2_entry_using_hash = rtl838x_read_l2_entry_using_hash,
+ .write_l2_entry_using_hash = rtl838x_write_l2_entry_using_hash,
.read_cam = rtl838x_read_cam,
+ .write_cam = rtl838x_write_cam,
.vlan_port_egr_filter = RTL838X_VLAN_PORT_EGR_FLTR,
.vlan_port_igr_filter = RTL838X_VLAN_PORT_IGR_FLTR(0),
.vlan_port_pb = RTL838X_VLAN_PORT_PB_VLAN,
@@ -317,6 +593,13 @@ const struct rtl838x_reg rtl838x_reg = {
.trk_mbr_ctr = rtl838x_trk_mbr_ctr,
.rma_bpdu_fld_pmask = RTL838X_RMA_BPDU_FLD_PMSK,
.spcl_trap_eapol_ctrl = RTL838X_SPCL_TRAP_EAPOL_CTRL,
+ .init_eee = rtl838x_init_eee,
+ .port_eee_set = rtl838x_port_eee_set,
+ .eee_port_ability = rtl838x_eee_port_ability,
+ .l2_hash_seed = rtl838x_l2_hash_seed,
+ .l2_hash_key = rtl838x_l2_hash_key,
+ .read_mcast_pmask = rtl838x_read_mcast_pmask,
+ .write_mcast_pmask = rtl838x_write_mcast_pmask,
};
irqreturn_t rtl838x_switch_irq(int irq, void *dev_id)
@@ -432,6 +715,79 @@ timeout:
return -ETIMEDOUT;
}
+/*
+ * Read an mmd register of a PHY
+ */
+int rtl838x_read_mmd_phy(u32 port, u32 addr, u32 reg, u32 *val)
+{
+ u32 v;
+
+ mutex_lock(&smi_lock);
+
+ if (rtl838x_smi_wait_op(10000))
+ goto timeout;
+
+ sw_w32(1 << port, RTL838X_SMI_ACCESS_PHY_CTRL_0);
+ mdelay(10);
+
+ sw_w32_mask(0xffff0000, port << 16, RTL838X_SMI_ACCESS_PHY_CTRL_2);
+
+ v = addr << 16 | reg;
+ sw_w32(v, RTL838X_SMI_ACCESS_PHY_CTRL_3);
+
+ /* mmd-access | read | cmd-start */
+ v = 1 << 1 | 0 << 2 | 1;
+ sw_w32(v, RTL838X_SMI_ACCESS_PHY_CTRL_1);
+
+ if (rtl838x_smi_wait_op(10000))
+ goto timeout;
+
+ *val = sw_r32(RTL838X_SMI_ACCESS_PHY_CTRL_2) & 0xffff;
+
+ mutex_unlock(&smi_lock);
+ return 0;
+
+timeout:
+ mutex_unlock(&smi_lock);
+ return -ETIMEDOUT;
+}
+
+/*
+ * Write to an mmd register of a PHY
+ */
+int rtl838x_write_mmd_phy(u32 port, u32 addr, u32 reg, u32 val)
+{
+ u32 v;
+
+ pr_debug("MMD write: port %d, dev %d, reg %d, val %x\n", port, addr, reg, val);
+ val &= 0xffff;
+ mutex_lock(&smi_lock);
+
+ if (rtl838x_smi_wait_op(10000))
+ goto timeout;
+
+ sw_w32(1 << port, RTL838X_SMI_ACCESS_PHY_CTRL_0);
+ mdelay(10);
+
+ sw_w32_mask(0xffff0000, val << 16, RTL838X_SMI_ACCESS_PHY_CTRL_2);
+
+ sw_w32_mask(0x1f << 16, addr << 16, RTL838X_SMI_ACCESS_PHY_CTRL_3);
+ sw_w32_mask(0xffff, reg, RTL838X_SMI_ACCESS_PHY_CTRL_3);
+ /* mmd-access | write | cmd-start */
+ v = 1 << 1 | 1 << 2 | 1;
+ sw_w32(v, RTL838X_SMI_ACCESS_PHY_CTRL_1);
+
+ if (rtl838x_smi_wait_op(10000))
+ goto timeout;
+
+ mutex_unlock(&smi_lock);
+ return 0;
+
+timeout:
+ mutex_unlock(&smi_lock);
+ return -ETIMEDOUT;
+}
+
void rtl8380_get_version(struct rtl838x_switch_priv *priv)
{
u32 rw_save, info_save;
@@ -459,47 +815,18 @@ void rtl8380_get_version(struct rtl838x_switch_priv *priv)
}
}
-/*
- * Applies the same hash algorithm as the one used currently by the ASIC
- */
-u32 rtl838x_hash(struct rtl838x_switch_priv *priv, u64 seed)
-{
- u32 h1, h2, h3, h;
-
- if (sw_r32(priv->r->l2_ctrl_0) & 1) {
- h1 = (seed >> 11) & 0x7ff;
- h1 = ((h1 & 0x1f) << 6) | ((h1 >> 5) & 0x3f);
-
- h2 = (seed >> 33) & 0x7ff;
- h2 = ((h2 & 0x3f) << 5) | ((h2 >> 6) & 0x1f);
-
- h3 = (seed >> 44) & 0x7ff;
- h3 = ((h3 & 0x7f) << 4) | ((h3 >> 7) & 0xf);
-
- h = h1 ^ h2 ^ h3 ^ ((seed >> 55) & 0x1ff);
- h ^= ((seed >> 22) & 0x7ff) ^ (seed & 0x7ff);
- } else {
- h = ((seed >> 55) & 0x1ff) ^ ((seed >> 44) & 0x7ff)
- ^ ((seed >> 33) & 0x7ff) ^ ((seed >> 22) & 0x7ff)
- ^ ((seed >> 11) & 0x7ff) ^ (seed & 0x7ff);
- }
-
- return h;
-}
-
-void rtl838x_vlan_profile_dump(int index)
+void rtl838x_vlan_profile_dump(int profile)
{
- u32 profile;
+ u32 p;
- if (index < 0 || index > 7)
+ if (profile < 0 || profile > 7)
return;
- profile = sw_r32(RTL838X_VLAN_PROFILE(index));
+ p = sw_r32(RTL838X_VLAN_PROFILE(profile));
- pr_info("VLAN %d: L2 learning: %d, L2 Unknown MultiCast Field %x, \
- IPv4 Unknown MultiCast Field %x, IPv6 Unknown MultiCast Field: %x",
- index, profile & 1, (profile >> 1) & 0x1ff, (profile >> 10) & 0x1ff,
- (profile >> 19) & 0x1ff);
+ pr_info("VLAN profile %d: L2 learning: %d, UNKN L2MC FLD PMSK %d, \
+ UNKN IPMC FLD PMSK %d, UNKN IPv6MC FLD PMSK: %d",
+ profile, p & 1, (p >> 1) & 0x1ff, (p >> 10) & 0x1ff, (p >> 19) & 0x1ff);
}
void rtl8380_sds_rst(int mac)
diff --git a/target/linux/realtek/files-5.4/drivers/net/dsa/rtl83xx/rtl838x.h b/target/linux/realtek/files-5.4/drivers/net/dsa/rtl83xx/rtl838x.h
index d5ca153a10..b2097363b9 100644
--- a/target/linux/realtek/files-5.4/drivers/net/dsa/rtl83xx/rtl838x.h
+++ b/target/linux/realtek/files-5.4/drivers/net/dsa/rtl83xx/rtl838x.h
@@ -162,6 +162,16 @@
#define RTL838X_EEE_TX_TIMER_GIGA_CTRL (0xaa04)
#define RTL838X_EEE_TX_TIMER_GELITE_CTRL (0xaa08)
+#define RTL839X_EEE_TX_TIMER_GELITE_CTRL (0x042C)
+#define RTL839X_EEE_TX_TIMER_GIGA_CTRL (0x0430)
+#define RTL839X_EEE_TX_TIMER_10G_CTRL (0x0434)
+#define RTL839X_EEE_CTRL(p) (0x8008 + ((p) << 7))
+#define RTL839X_MAC_EEE_ABLTY (0x03C8)
+
+#define RTL930X_MAC_EEE_ABLTY (0xCB34)
+#define RTL930X_EEE_CTRL(p) (0x3274 + ((p) << 6))
+#define RTL930X_EEEP_PORT_CTRL(p) (0x3278 + ((p) << 6))
+
/* L2 functionality */
#define RTL838X_L2_CTRL_0 (0x3200)
#define RTL839X_L2_CTRL_0 (0x3800)
@@ -206,6 +216,12 @@
#define RTL931X_RMA_BPDU_FLD_PMSK (0x8950)
#define RTL839X_RMA_BPDU_FLD_PMSK (0x125C)
+#define RTL838X_L2_PORT_LM_ACT(p) (0x3208 + ((p) << 2))
+#define RTL838X_VLAN_PORT_FWD (0x3A78)
+#define RTL839X_VLAN_PORT_FWD (0x27AC)
+#define RTL930X_VLAN_PORT_FWD (0x834C)
+#define RTL838X_VLAN_FID_CTRL (0x3aa8)
+
/* Port Mirroring */
#define RTL838X_MIR_CTRL (0x5D00)
#define RTL838X_MIR_DPM_CTRL (0x5D20)
@@ -322,8 +338,12 @@
/* Debug features */
#define RTL930X_STAT_PRVTE_DROP_COUNTER0 (0xB5B8)
+#define MAX_VLANS 4096
#define MAX_LAGS 16
#define MAX_PRIOS 8
+#define RTL930X_PORT_IGNORE 0x3f
+#define MAX_MC_GROUPS 512
+#define UNKNOWN_MC_PMASK (MAX_MC_GROUPS - 1)
enum phy_type {
PHY_NONE = 0,
@@ -379,8 +399,27 @@ struct rtl838x_l2_entry {
bool next_hop;
int age;
u8 trunk;
- u8 stackDev;
+ bool is_trunk;
+ u8 stack_dev;
u16 mc_portmask_index;
+ u32 mc_gip;
+ u32 mc_sip;
+ u16 mc_mac_index;
+ u16 nh_route_id;
+ bool nh_vlan_target; // Only RTL83xx: VLAN used for next hop
+};
+
+struct rtl838x_nexthop {
+ u16 id; // ID in HW Nexthop table
+ u32 ip; // IP Addres of nexthop
+ u32 dev_id;
+ u16 port;
+ u16 vid;
+ u16 fid;
+ u64 mac;
+ u16 mac_id;
+ u16 l2_id; // Index of this next hop forwarding entry in L2 FIB table
+ u16 if_id;
};
struct rtl838x_switch_priv;
@@ -416,6 +455,7 @@ struct rtl838x_reg {
void (*vlan_set_tagged)(u32 vlan, struct rtl838x_vlan_info *info);
void (*vlan_set_untagged)(u32 vlan, u64 portmask);
void (*vlan_profile_dump)(int index);
+ void (*vlan_profile_setup)(int profile);
void (*stp_get)(struct rtl838x_switch_priv *priv, u16 msti, u32 port_state[]);
void (*stp_set)(struct rtl838x_switch_priv *priv, u16 msti, u32 port_state[]);
int (*mac_force_mode_ctrl)(int port);
@@ -431,7 +471,9 @@ struct rtl838x_reg {
int mac_rx_pause_sts;
int mac_tx_pause_sts;
u64 (*read_l2_entry_using_hash)(u32 hash, u32 position, struct rtl838x_l2_entry *e);
+ void (*write_l2_entry_using_hash)(u32 hash, u32 pos, struct rtl838x_l2_entry *e);
u64 (*read_cam)(int idx, struct rtl838x_l2_entry *e);
+ void (*write_cam)(int idx, struct rtl838x_l2_entry *e);
int vlan_port_egr_filter;
int vlan_port_igr_filter;
int vlan_port_pb;
@@ -440,6 +482,15 @@ struct rtl838x_reg {
int (*trk_mbr_ctr)(int group);
int rma_bpdu_fld_pmask;
int spcl_trap_eapol_ctrl;
+ void (*init_eee)(struct rtl838x_switch_priv *priv, bool enable);
+ void (*port_eee_set)(struct rtl838x_switch_priv *priv, int port, bool enable);
+ int (*eee_port_ability)(struct rtl838x_switch_priv *priv,
+ struct ethtool_eee *e, int port);
+ u64 (*l2_hash_seed)(u64 mac, u32 vid);
+ u32 (*l2_hash_key)(struct rtl838x_switch_priv *priv, u64 seed);
+ u64 (*read_mcast_pmask)(int idx);
+ void (*write_mcast_pmask)(int idx, u64 portmask);
+ void (*vlan_fwd_on_inner)(int port, bool is_set);
};
struct rtl838x_switch_priv {
@@ -460,11 +511,14 @@ struct rtl838x_switch_priv {
u8 port_width;
u64 irq_mask;
u32 fib_entries;
+ int l2_bucket_size;
struct dentry *dbgfs_dir;
int n_lags;
u64 lags_port_members[MAX_LAGS];
struct net_device *lag_devs[MAX_LAGS];
struct notifier_block nb;
+ bool eee_enabled;
+ unsigned long int mc_group_bm[MAX_MC_GROUPS >> 5];
};
void rtl838x_dbgfs_init(struct rtl838x_switch_priv *priv);
diff --git a/target/linux/realtek/files-5.4/drivers/net/dsa/rtl83xx/rtl839x.c b/target/linux/realtek/files-5.4/drivers/net/dsa/rtl83xx/rtl839x.c
index 5106bd2e9d..c62dc441c1 100644
--- a/target/linux/realtek/files-5.4/drivers/net/dsa/rtl83xx/rtl839x.c
+++ b/target/linux/realtek/files-5.4/drivers/net/dsa/rtl83xx/rtl839x.c
@@ -48,68 +48,120 @@ static inline int rtl839x_tbl_access_data_0(int i)
static void rtl839x_vlan_tables_read(u32 vlan, struct rtl838x_vlan_info *info)
{
- u32 cmd;
- u64 v;
- u32 u, w;
-
- cmd = 1 << 16 /* Execute cmd */
- | 0 << 15 /* Read */
- | 0 << 12 /* Table type 0b000 */
- | (vlan & 0xfff);
- rtl839x_exec_tbl0_cmd(cmd);
-
- v = sw_r32(RTL839X_TBL_ACCESS_DATA_0(0));
- v <<= 32;
- u = sw_r32(RTL839X_TBL_ACCESS_DATA_0(1));
- v |= u;
- info->tagged_ports = v >> 11;
-
- w = sw_r32(RTL839X_TBL_ACCESS_DATA_0(2));
-
- info->profile_id = w >> 30 | ((u & 1) << 2);
- info->hash_mc_fid = !!(u & 2);
- info->hash_uc_fid = !!(u & 4);
- info->fid = (u >> 3) & 0xff;
-
- cmd = 1 << 15 /* Execute cmd */
- | 0 << 14 /* Read */
- | 0 << 12 /* Table type 0b00 */
- | (vlan & 0xfff);
- rtl839x_exec_tbl1_cmd(cmd);
- v = sw_r32(RTL839X_TBL_ACCESS_DATA_1(0));
- v <<= 32;
- v |= sw_r32(RTL839X_TBL_ACCESS_DATA_1(1));
- info->untagged_ports = v >> 11;
+ u32 u, v, w;
+ // Read VLAN table (0) via register 0
+ struct table_reg *r = rtl_table_get(RTL8390_TBL_0, 0);
+
+ rtl_table_read(r, vlan);
+ u = sw_r32(rtl_table_data(r, 0));
+ v = sw_r32(rtl_table_data(r, 1));
+ w = sw_r32(rtl_table_data(r, 2));
+ rtl_table_release(r);
+
+ info->tagged_ports = u;
+ info->tagged_ports = (info->tagged_ports << 21) | ((v >> 11) & 0x1fffff);
+ info->profile_id = w >> 30 | ((v & 1) << 2);
+ info->hash_mc_fid = !!(w & BIT(2));
+ info->hash_uc_fid = !!(w & BIT(3));
+ info->fid = (v >> 3) & 0xff;
+
+ // Read UNTAG table (0) via table register 1
+ r = rtl_table_get(RTL8390_TBL_1, 0);
+ rtl_table_read(r, vlan);
+ u = sw_r32(rtl_table_data(r, 0));
+ v = sw_r32(rtl_table_data(r, 1));
+ rtl_table_release(r);
+
+ info->untagged_ports = u;
+ info->untagged_ports = (info->untagged_ports << 21) | ((v >> 11) & 0x1fffff);
}
static void rtl839x_vlan_set_tagged(u32 vlan, struct rtl838x_vlan_info *info)
{
- u32 cmd = BIT(16) /* Execute cmd */
- | BIT(15) /* Write */
- | 0 << 12 /* Table type 0b00 */
- | (vlan & 0xfff);
- u32 w;
- u64 v = info->tagged_ports << 11;
+ u32 u, v, w;
+ // Access VLAN table (0) via register 0
+ struct table_reg *r = rtl_table_get(RTL8390_TBL_0, 0);
- v |= info->profile_id >> 2;
- v |= info->hash_mc_fid ? 2 : 0;
- v |= info->hash_uc_fid ? 4 : 0;
+ u = info->tagged_ports >> 21;
+ v = info->tagged_ports << 11;
v |= ((u32)info->fid) << 3;
- rtl839x_set_port_reg_be(v, RTL838X_TBL_ACCESS_DATA_0(0));
+ v |= info->hash_uc_fid ? BIT(2) : 0;
+ v |= info->hash_mc_fid ? BIT(1) : 0;
+ v |= (info->profile_id & 0x4) ? 1 : 0;
+ w = ((u32)(info->profile_id & 3)) << 30;
+
+ sw_w32(u, rtl_table_data(r, 0));
+ sw_w32(v, rtl_table_data(r, 1));
+ sw_w32(w, rtl_table_data(r, 2));
- w = info->profile_id;
- sw_w32(w << 30, RTL838X_TBL_ACCESS_DATA_0(2));
- rtl839x_exec_tbl0_cmd(cmd);
+ rtl_table_write(r, vlan);
+ rtl_table_release(r);
}
static void rtl839x_vlan_set_untagged(u32 vlan, u64 portmask)
{
- u32 cmd = BIT(16) /* Execute cmd */
- | BIT(15) /* Write */
- | 0 << 12 /* Table type 0b00 */
- | (vlan & 0xfff);
- rtl839x_set_port_reg_be(portmask << 11, RTL838X_TBL_ACCESS_DATA_1(0));
- rtl839x_exec_tbl1_cmd(cmd);
+ u32 u, v;
+
+ // Access UNTAG table (0) via table register 1
+ struct table_reg *r = rtl_table_get(RTL8390_TBL_1, 0);
+
+ u = portmask >> 21;
+ v = portmask << 11;
+
+ sw_w32(u, rtl_table_data(r, 0));
+ sw_w32(v, rtl_table_data(r, 1));
+ rtl_table_write(r, vlan);
+
+ rtl_table_release(r);
+}
+
+/* Sets the L2 forwarding to be based on either the inner VLAN tag or the outer
+ */
+static void rtl839x_vlan_fwd_on_inner(int port, bool is_set)
+{
+ if (is_set)
+ rtl839x_mask_port_reg_be(BIT_ULL(port), 0ULL, RTL839X_VLAN_PORT_FWD);
+ else
+ rtl839x_mask_port_reg_be(0ULL, BIT_ULL(port), RTL839X_VLAN_PORT_FWD);
+}
+
+/*
+ * Hash seed is vid (actually rvid) concatenated with the MAC address
+ */
+static u64 rtl839x_l2_hash_seed(u64 mac, u32 vid)
+{
+ u64 v = vid;
+
+ v <<= 48;
+ v |= mac;
+
+ return v;
+}
+
+/*
+ * Applies the same hash algorithm as the one used currently by the ASIC to the seed
+ * and returns a key into the L2 hash table
+ */
+static u32 rtl839x_l2_hash_key(struct rtl838x_switch_priv *priv, u64 seed)
+{
+ u32 h1, h2, h;
+
+ if (sw_r32(priv->r->l2_ctrl_0) & 1) {
+ h1 = (u32) (((seed >> 60) & 0x3f) ^ ((seed >> 54) & 0x3f)
+ ^ ((seed >> 36) & 0x3f) ^ ((seed >> 30) & 0x3f)
+ ^ ((seed >> 12) & 0x3f) ^ ((seed >> 6) & 0x3f));
+ h2 = (u32) (((seed >> 48) & 0x3f) ^ ((seed >> 42) & 0x3f)
+ ^ ((seed >> 24) & 0x3f) ^ ((seed >> 18) & 0x3f)
+ ^ (seed & 0x3f));
+ h = (h1 << 6) | h2;
+ } else {
+ h = (seed >> 60)
+ ^ ((((seed >> 48) & 0x3f) << 6) | ((seed >> 54) & 0x3f))
+ ^ ((seed >> 36) & 0xfff) ^ ((seed >> 24) & 0xfff)
+ ^ ((seed >> 12) & 0xfff) ^ (seed & 0xfff);
+ }
+
+ return h;
}
static inline int rtl839x_mac_force_mode_ctrl(int p)
@@ -192,60 +244,174 @@ static void rtl839x_fill_l2_entry(u32 r[], struct rtl838x_l2_entry *e)
}
}
-static u64 rtl839x_read_l2_entry_using_hash(u32 hash, u32 position, struct rtl838x_l2_entry *e)
+/*
+ * Fills the 3 SoC table registers r[] with the information of in the rtl838x_l2_entry
+ */
+static void rtl839x_fill_l2_row(u32 r[], struct rtl838x_l2_entry *e)
{
- u64 entry;
- u32 r[3];
+ if (!e->valid) {
+ r[0] = r[1] = r[2] = 0;
+ return;
+ }
+
+ r[2] = e->is_ip_mc ? BIT(31) : 0;
+ r[2] |= e->is_ipv6_mc ? BIT(30) : 0;
+
+ if (!e->is_ip_mc && !e->is_ipv6_mc) {
+ r[0] = ((u32)e->mac[0]) << 12;
+ r[0] |= ((u32)e->mac[1]) << 4;
+ r[0] |= ((u32)e->mac[2]) >> 4;
+ r[1] = ((u32)e->mac[2]) << 28;
+ r[1] |= ((u32)e->mac[3]) << 20;
+ r[1] |= ((u32)e->mac[4]) << 12;
+ r[1] |= ((u32)e->mac[5]) << 4;
+
+ if (!(e->mac[0] & 1)) { // Not multicast
+ r[2] |= e->is_static ? BIT(18) : 0;
+ r[2] |= e->vid << 4;
+ r[0] |= ((u32)e->rvid) << 20;
+ r[2] |= e->port << 24;
+ r[2] |= e->block_da ? BIT(19) : 0;
+ r[2] |= e->block_sa ? BIT(20) : 0;
+ r[2] |= e->suspended ? BIT(17) : 0;
+ if (e->next_hop) {
+ r[2] |= BIT(16);
+ r[2] |= e->nh_vlan_target ? BIT(15) : 0;
+ r[2] |= (e->nh_route_id & 0x7ff) << 4;
+ }
+ r[2] |= ((u32)e->age) << 21;
+ } else { // L2 Multicast
+ r[0] |= ((u32)e->rvid) << 20;
+ r[2] |= ((u32)e->mc_portmask_index) << 6;
+ pr_debug("Write L2 MC entry: %08x %08x %08x\n", r[0], r[1], r[2]);
+ }
+ } else { // IPv4 or IPv6 MC entry
+ r[0] = ((u32)e->rvid) << 20;
+ r[2] |= ((u32)e->mc_portmask_index) << 6;
+ r[1] = e->mc_gip;
+ }
+}
- /* Search in SRAM, with hash and at position in hash bucket (0-3) */
- u32 idx = (0 << 14) | (hash << 2) | position;
+/*
+ * Read an L2 UC or MC entry out of a hash bucket of the L2 forwarding table
+ * hash is the id of the bucket and pos is the position of the entry in that bucket
+ * The data read from the SoC is filled into rtl838x_l2_entry
+ */
+static u64 rtl839x_read_l2_entry_using_hash(u32 hash, u32 pos, struct rtl838x_l2_entry *e)
+{
+ u32 r[3];
+ struct table_reg *q = rtl_table_get(RTL8390_TBL_L2, 0);
+ u32 idx = (0 << 14) | (hash << 2) | pos; // Search SRAM, with hash and at pos in bucket
+ int i;
- u32 cmd = 1 << 17 /* Execute cmd */
- | 0 << 16 /* Read */
- | 0 << 14 /* Table type 0b00 */
- | (idx & 0x3fff);
+ rtl_table_read(q, idx);
+ for (i= 0; i < 3; i++)
+ r[i] = sw_r32(rtl_table_data(q, i));
- sw_w32(cmd, RTL839X_TBL_ACCESS_L2_CTRL);
- do { } while (sw_r32(RTL839X_TBL_ACCESS_L2_CTRL) & (1 << 17));
- r[0] = sw_r32(RTL839X_TBL_ACCESS_L2_DATA(0));
- r[1] = sw_r32(RTL839X_TBL_ACCESS_L2_DATA(1));
- r[2] = sw_r32(RTL839X_TBL_ACCESS_L2_DATA(2));
+ rtl_table_release(q);
rtl839x_fill_l2_entry(r, e);
+ if (!e->valid)
+ return 0;
- entry = (((u64) r[0]) << 12) | ((r[1] & 0xfffffff0) << 12) | ((r[2] >> 4) & 0xfff);
- return entry;
+ return rtl839x_l2_hash_seed(ether_addr_to_u64(&e->mac[0]), e->rvid);
+}
+
+static void rtl839x_write_l2_entry_using_hash(u32 hash, u32 pos, struct rtl838x_l2_entry *e)
+{
+ u32 r[3];
+ struct table_reg *q = rtl_table_get(RTL8390_TBL_L2, 0);
+ int i;
+
+ u32 idx = (0 << 14) | (hash << 2) | pos; // Access SRAM, with hash and at pos in bucket
+
+ rtl839x_fill_l2_row(r, e);
+
+ for (i= 0; i < 3; i++)
+ sw_w32(r[i], rtl_table_data(q, i));
+
+ rtl_table_write(q, idx);
+ rtl_table_release(q);
}
static u64 rtl839x_read_cam(int idx, struct rtl838x_l2_entry *e)
{
- u64 entry;
u32 r[3];
+ struct table_reg *q = rtl_table_get(RTL8390_TBL_L2, 1); // Access L2 Table 1
+ int i;
- u32 cmd = 1 << 17 /* Execute cmd */
- | 0 << 16 /* Read */
- | 1 << 14 /* Table type 0b01 */
- | (idx & 0x3f);
- sw_w32(cmd, RTL839X_TBL_ACCESS_L2_CTRL);
- do { } while (sw_r32(RTL839X_TBL_ACCESS_L2_CTRL) & (1 << 17));
- r[0] = sw_r32(RTL839X_TBL_ACCESS_L2_DATA(0));
- r[1] = sw_r32(RTL839X_TBL_ACCESS_L2_DATA(1));
- r[2] = sw_r32(RTL839X_TBL_ACCESS_L2_DATA(2));
+ rtl_table_read(q, idx);
+ for (i= 0; i < 3; i++)
+ r[i] = sw_r32(rtl_table_data(q, i));
+ rtl_table_release(q);
rtl839x_fill_l2_entry(r, e);
- if (e->valid)
- pr_info("Found in CAM: R1 %x R2 %x R3 %x\n", r[0], r[1], r[2]);
- else
+ if (!e->valid)
return 0;
- entry = (((u64) r[0]) << 12) | ((r[1] & 0xfffffff0) << 12) | ((r[2] >> 4) & 0xfff);
- return entry;
+ pr_debug("Found in CAM: R1 %x R2 %x R3 %x\n", r[0], r[1], r[2]);
+
+ // Return MAC with concatenated VID ac concatenated ID
+ return rtl839x_l2_hash_seed(ether_addr_to_u64(&e->mac[0]), e->rvid);
+}
+
+static void rtl839x_write_cam(int idx, struct rtl838x_l2_entry *e)
+{
+ u32 r[3];
+ struct table_reg *q = rtl_table_get(RTL8390_TBL_L2, 1); // Access L2 Table 1
+ int i;
+
+ rtl839x_fill_l2_row(r, e);
+
+ for (i= 0; i < 3; i++)
+ sw_w32(r[i], rtl_table_data(q, i));
+
+ rtl_table_write(q, idx);
+ rtl_table_release(q);
}
-static inline int rtl839x_vlan_profile(int profile)
+static u64 rtl839x_read_mcast_pmask(int idx)
{
- return RTL839X_VLAN_PROFILE(profile);
+ u64 portmask;
+ // Read MC_PMSK (2) via register RTL8390_TBL_L2
+ struct table_reg *q = rtl_table_get(RTL8390_TBL_L2, 2);
+
+ rtl_table_read(q, idx);
+ portmask = sw_r32(rtl_table_data(q, 0));
+ portmask <<= 32;
+ portmask |= sw_r32(rtl_table_data(q, 1));
+ portmask >>= 11; // LSB is bit 11 in data registers
+ rtl_table_release(q);
+
+ return portmask;
+}
+
+static void rtl839x_write_mcast_pmask(int idx, u64 portmask)
+{
+ // Access MC_PMSK (2) via register RTL8380_TBL_L2
+ struct table_reg *q = rtl_table_get(RTL8390_TBL_L2, 2);
+
+ portmask <<= 11; // LSB is bit 11 in data registers
+ sw_w32((u32)(portmask >> 32), rtl_table_data(q, 0));
+ sw_w32((u32)((portmask & 0xfffff800)), rtl_table_data(q, 1));
+ rtl_table_write(q, idx);
+ rtl_table_release(q);
+}
+
+static void rtl839x_vlan_profile_setup(int profile)
+{
+ u32 p[2];
+ u32 pmask_id = UNKNOWN_MC_PMASK;
+
+ p[0] = pmask_id; // Use portmaks 0xfff for unknown IPv6 MC flooding
+ // Enable L2 Learning BIT 0, portmask UNKNOWN_MC_PMASK for IP/L2-MC traffic flooding
+ p[1] = 1 | pmask_id << 1 | pmask_id << 13;
+
+ sw_w32(p[0], RTL839X_VLAN_PROFILE(profile));
+ sw_w32(p[1], RTL839X_VLAN_PROFILE(profile) + 4);
+
+ rtl839x_write_mcast_pmask(UNKNOWN_MC_PMASK, 0x001fffffffffffff);
}
static inline int rtl839x_vlan_port_egr_filter(int port)
@@ -275,7 +441,7 @@ void rtl839x_traffic_enable(int source, int dest)
void rtl839x_traffic_disable(int source, int dest)
{
- rtl839x_mask_port_reg_be(BIT(dest), 0, rtl839x_port_iso_ctrl(source));
+ rtl839x_mask_port_reg_be(BIT_ULL(dest), 0, rtl839x_port_iso_ctrl(source));
}
irqreturn_t rtl839x_switch_irq(int irq, void *dev_id)
@@ -290,10 +456,10 @@ irqreturn_t rtl839x_switch_irq(int irq, void *dev_id)
rtl839x_set_port_reg_le(ports, RTL839X_ISR_PORT_LINK_STS_CHG);
pr_debug("RTL8390 Link change: status: %x, ports %llx\n", status, ports);
- for (i = 0; i < 52; i++) {
- if (ports & (1ULL << i)) {
+ for (i = 0; i < RTL839X_CPU_PORT; i++) {
+ if (ports & BIT_ULL(i)) {
link = rtl839x_get_port_reg_le(RTL839X_MAC_LINK_STS);
- if (link & (1ULL << i))
+ if (link & BIT_ULL(i))
dsa_port_phylink_mac_change(ds, i, true);
else
dsa_port_phylink_mac_change(ds, i, false);
@@ -358,10 +524,9 @@ int rtl839x_write_phy(u32 port, u32 page, u32 reg, u32 val)
return -ENOTSUPP;
mutex_lock(&smi_lock);
- /* Clear both port registers */
- sw_w32(0, RTL839X_PHYREG_PORT_CTRL(0));
- sw_w32(0, RTL839X_PHYREG_PORT_CTRL(0) + 4);
- sw_w32_mask(0, BIT(port), RTL839X_PHYREG_PORT_CTRL(port));
+
+ // Set PHY to access
+ rtl839x_set_port_reg_le(BIT_ULL(port), RTL839X_PHYREG_PORT_CTRL);
sw_w32_mask(0xffff0000, val << 16, RTL839X_PHYREG_DATA_CTRL);
@@ -383,6 +548,68 @@ int rtl839x_write_phy(u32 port, u32 page, u32 reg, u32 val)
return err;
}
+/*
+ * Read an mmd register of the PHY
+ */
+int rtl839x_read_mmd_phy(u32 port, u32 devnum, u32 regnum, u32 *val)
+{
+ int err = 0;
+ u32 v;
+
+ mutex_lock(&smi_lock);
+
+ // Set PHY to access
+ sw_w32_mask(0xffff << 16, port << 16, RTL839X_PHYREG_DATA_CTRL);
+
+ // Set MMD device number and register to write to
+ sw_w32(devnum << 16 | (regnum & 0xffff), RTL839X_PHYREG_MMD_CTRL);
+
+ v = BIT(2) | BIT(0); // MMD-access | EXEC
+ sw_w32(v, RTL839X_PHYREG_ACCESS_CTRL);
+
+ do {
+ v = sw_r32(RTL839X_PHYREG_ACCESS_CTRL);
+ } while (v & BIT(0));
+ // There is no error-checking via BIT 1 of v, as it does not seem to be set correctly
+ *val = (sw_r32(RTL839X_PHYREG_DATA_CTRL) & 0xffff);
+ pr_debug("%s: port %d, regnum: %x, val: %x (err %d)\n", __func__, port, regnum, *val, err);
+
+ mutex_unlock(&smi_lock);
+
+ return err;
+}
+
+/*
+ * Write to an mmd register of the PHY
+ */
+int rtl839x_write_mmd_phy(u32 port, u32 devnum, u32 regnum, u32 val)
+{
+ int err = 0;
+ u32 v;
+
+ mutex_lock(&smi_lock);
+
+ // Set PHY to access
+ rtl839x_set_port_reg_le(BIT_ULL(port), RTL839X_PHYREG_PORT_CTRL);
+
+ // Set data to write
+ sw_w32_mask(0xffff << 16, val << 16, RTL839X_PHYREG_DATA_CTRL);
+
+ // Set MMD device number and register to write to
+ sw_w32(devnum << 16 | (regnum & 0xffff), RTL839X_PHYREG_MMD_CTRL);
+
+ v = BIT(3) | BIT(2) | BIT(0); // WRITE | MMD-access | EXEC
+ sw_w32(v, RTL839X_PHYREG_ACCESS_CTRL);
+
+ do {
+ v = sw_r32(RTL839X_PHYREG_ACCESS_CTRL);
+ } while (v & BIT(0));
+
+ pr_debug("%s: port %d, regnum: %x, val: %x (err %d)\n", __func__, port, regnum, val, err);
+ mutex_unlock(&smi_lock);
+ return err;
+}
+
void rtl8390_get_version(struct rtl838x_switch_priv *priv)
{
u32 info;
@@ -393,42 +620,21 @@ void rtl8390_get_version(struct rtl838x_switch_priv *priv)
priv->version = RTL8390_VERSION_A;
}
-u32 rtl839x_hash(struct rtl838x_switch_priv *priv, u64 seed)
+void rtl839x_vlan_profile_dump(int profile)
{
- u32 h1, h2, h;
+ u32 p[2];
- if (sw_r32(priv->r->l2_ctrl_0) & 1) {
- h1 = (u32) (((seed >> 60) & 0x3f) ^ ((seed >> 54) & 0x3f)
- ^ ((seed >> 36) & 0x3f) ^ ((seed >> 30) & 0x3f)
- ^ ((seed >> 12) & 0x3f) ^ ((seed >> 6) & 0x3f));
- h2 = (u32) (((seed >> 48) & 0x3f) ^ ((seed >> 42) & 0x3f)
- ^ ((seed >> 24) & 0x3f) ^ ((seed >> 18) & 0x3f)
- ^ (seed & 0x3f));
- h = (h1 << 6) | h2;
- } else {
- h = (seed >> 60)
- ^ ((((seed >> 48) & 0x3f) << 6) | ((seed >> 54) & 0x3f))
- ^ ((seed >> 36) & 0xfff) ^ ((seed >> 24) & 0xfff)
- ^ ((seed >> 12) & 0xfff) ^ (seed & 0xfff);
- }
-
- return h;
-}
-
-void rtl839x_vlan_profile_dump(int index)
-{
- u32 profile, profile1;
-
- if (index < 0 || index > 7)
+ if (profile < 0 || profile > 7)
return;
- profile1 = sw_r32(RTL839X_VLAN_PROFILE(index) + 4);
- profile = sw_r32(RTL839X_VLAN_PROFILE(index));
+ p[0] = sw_r32(RTL839X_VLAN_PROFILE(profile));
+ p[1] = sw_r32(RTL839X_VLAN_PROFILE(profile) + 4);
- pr_debug("VLAN %d: L2 learning: %d, L2 Unknown MultiCast Field %x, \
- IPv4 Unknown MultiCast Field %x, IPv6 Unknown MultiCast Field: %x",
- index, profile & 1, (profile >> 1) & 0xfff, (profile >> 13) & 0xfff,
- (profile1) & 0xfff);
+ pr_info("VLAN profile %d: L2 learning: %d, UNKN L2MC FLD PMSK %d, \
+ UNKN IPMC FLD PMSK %d, UNKN IPv6MC FLD PMSK: %d",
+ profile, p[1] & 1, (p[1] >> 1) & 0xfff, (p[1] >> 13) & 0xfff,
+ (p[0]) & 0xfff);
+ pr_info("VLAN profile %d: raw %08x, %08x\n", profile, p[0], p[1]);
}
static void rtl839x_stp_get(struct rtl838x_switch_priv *priv, u16 msti, u32 port_state[])
@@ -456,6 +662,83 @@ static void rtl839x_stp_set(struct rtl838x_switch_priv *priv, u16 msti, u32 port
priv->r->exec_tbl0_cmd(cmd);
}
+/*
+ * Enables or disables the EEE/EEEP capability of a port
+ */
+void rtl839x_port_eee_set(struct rtl838x_switch_priv *priv, int port, bool enable)
+{
+ u32 v;
+
+ // This works only for Ethernet ports, and on the RTL839X, ports above 47 are SFP
+ if (port >= 48)
+ return;
+
+ enable = true;
+ pr_debug("In %s: setting port %d to %d\n", __func__, port, enable);
+ v = enable ? 0xf : 0x0;
+
+ // Set EEE for 100, 500, 1000MBit and 10GBit
+ sw_w32_mask(0xf << 8, v << 8, rtl839x_mac_force_mode_ctrl(port));
+
+ // Set TX/RX EEE state
+ v = enable ? 0x3 : 0x0;
+ sw_w32(v, RTL839X_EEE_CTRL(port));
+
+ priv->ports[port].eee_enabled = enable;
+}
+
+/*
+ * Get EEE own capabilities and negotiation result
+ */
+int rtl839x_eee_port_ability(struct rtl838x_switch_priv *priv, struct ethtool_eee *e, int port)
+{
+ u64 link, a;
+
+ if (port >= 48)
+ return 0;
+
+ link = rtl839x_get_port_reg_le(RTL839X_MAC_LINK_STS);
+ if (!(link & BIT_ULL(port)))
+ return 0;
+
+ if (sw_r32(rtl839x_mac_force_mode_ctrl(port)) & BIT(8))
+ e->advertised |= ADVERTISED_100baseT_Full;
+
+ if (sw_r32(rtl839x_mac_force_mode_ctrl(port)) & BIT(10))
+ e->advertised |= ADVERTISED_1000baseT_Full;
+
+ a = rtl839x_get_port_reg_le(RTL839X_MAC_EEE_ABLTY);
+ pr_info("Link partner: %016llx\n", a);
+ if (rtl839x_get_port_reg_le(RTL839X_MAC_EEE_ABLTY) & BIT_ULL(port)) {
+ e->lp_advertised = ADVERTISED_100baseT_Full;
+ e->lp_advertised |= ADVERTISED_1000baseT_Full;
+ return 1;
+ }
+
+ return 0;
+}
+
+static void rtl839x_init_eee(struct rtl838x_switch_priv *priv, bool enable)
+{
+ int i;
+
+ pr_info("Setting up EEE, state: %d\n", enable);
+
+ // Set wake timer for TX and pause timer both to 0x21
+ sw_w32_mask(0xff << 20| 0xff, 0x21 << 20| 0x21, RTL839X_EEE_TX_TIMER_GELITE_CTRL);
+ // Set pause wake timer for GIGA-EEE to 0x11
+ sw_w32_mask(0xff << 20, 0x11 << 20, RTL839X_EEE_TX_TIMER_GIGA_CTRL);
+ // Set pause wake timer for 10GBit ports to 0x11
+ sw_w32_mask(0xff << 20, 0x11 << 20, RTL839X_EEE_TX_TIMER_10G_CTRL);
+
+ // Setup EEE on all ports
+ for (i = 0; i < priv->cpu_port; i++) {
+ if (priv->ports[i].phy)
+ rtl839x_port_eee_set(priv, i, enable);
+ }
+ priv->eee_enabled = enable;
+}
+
const struct rtl838x_reg rtl839x_reg = {
.mask_port_reg_be = rtl839x_mask_port_reg_be,
.set_port_reg_be = rtl839x_set_port_reg_be,
@@ -487,6 +770,8 @@ const struct rtl838x_reg rtl839x_reg = {
.vlan_set_tagged = rtl839x_vlan_set_tagged,
.vlan_set_untagged = rtl839x_vlan_set_untagged,
.vlan_profile_dump = rtl839x_vlan_profile_dump,
+ .vlan_profile_setup = rtl839x_vlan_profile_setup,
+ .vlan_fwd_on_inner = rtl839x_vlan_fwd_on_inner,
.stp_get = rtl839x_stp_get,
.stp_set = rtl839x_stp_set,
.mac_force_mode_ctrl = rtl839x_mac_force_mode_ctrl,
@@ -502,7 +787,9 @@ const struct rtl838x_reg rtl839x_reg = {
.mac_rx_pause_sts = RTL839X_MAC_RX_PAUSE_STS,
.mac_tx_pause_sts = RTL839X_MAC_TX_PAUSE_STS,
.read_l2_entry_using_hash = rtl839x_read_l2_entry_using_hash,
+ .write_l2_entry_using_hash = rtl839x_write_l2_entry_using_hash,
.read_cam = rtl839x_read_cam,
+ .write_cam = rtl839x_write_cam,
.vlan_port_egr_filter = RTL839X_VLAN_PORT_EGR_FLTR(0),
.vlan_port_igr_filter = RTL839X_VLAN_PORT_IGR_FLTR(0),
.vlan_port_pb = RTL839X_VLAN_PORT_PB_VLAN,
@@ -510,4 +797,11 @@ const struct rtl838x_reg rtl839x_reg = {
.trk_mbr_ctr = rtl839x_trk_mbr_ctr,
.rma_bpdu_fld_pmask = RTL839X_RMA_BPDU_FLD_PMSK,
.spcl_trap_eapol_ctrl = RTL839X_SPCL_TRAP_EAPOL_CTRL,
+ .init_eee = rtl839x_init_eee,
+ .port_eee_set = rtl839x_port_eee_set,
+ .eee_port_ability = rtl839x_eee_port_ability,
+ .l2_hash_seed = rtl839x_l2_hash_seed,
+ .l2_hash_key = rtl839x_l2_hash_key,
+ .read_mcast_pmask = rtl839x_read_mcast_pmask,
+ .write_mcast_pmask = rtl839x_write_mcast_pmask,
};
diff --git a/target/linux/realtek/files-5.4/drivers/net/dsa/rtl83xx/rtl930x.c b/target/linux/realtek/files-5.4/drivers/net/dsa/rtl83xx/rtl930x.c
index 59c283903b..f1de39f0bc 100644
--- a/target/linux/realtek/files-5.4/drivers/net/dsa/rtl83xx/rtl930x.c
+++ b/target/linux/realtek/files-5.4/drivers/net/dsa/rtl83xx/rtl930x.c
@@ -54,7 +54,7 @@ inline static int rtl930x_trk_mbr_ctr(int group)
static void rtl930x_vlan_tables_read(u32 vlan, struct rtl838x_vlan_info *info)
{
u32 v, w;
- // Read VLAN table (0) via register 0
+ // Read VLAN table (1) via register 0
struct table_reg *r = rtl_table_get(RTL9300_TBL_0, 1);
rtl_table_read(r, vlan);
@@ -99,22 +99,28 @@ static void rtl930x_vlan_set_tagged(u32 vlan, struct rtl838x_vlan_info *info)
rtl_table_release(r);
}
-void rtl930x_vlan_profile_dump(int index)
+void rtl930x_vlan_profile_dump(int profile)
{
- u32 profile[5];
+ u32 p[5];
- if (index < 0 || index > 7)
+ if (profile < 0 || profile > 7)
return;
- profile[0] = sw_r32(RTL930X_VLAN_PROFILE_SET(index));
- profile[1] = sw_r32(RTL930X_VLAN_PROFILE_SET(index) + 4);
- profile[2] = sw_r32(RTL930X_VLAN_PROFILE_SET(index) + 8) & 0x1FFFFFFF;
- profile[3] = sw_r32(RTL930X_VLAN_PROFILE_SET(index) + 12) & 0x1FFFFFFF;
- profile[4] = sw_r32(RTL930X_VLAN_PROFILE_SET(index) + 16) & 0x1FFFFFFF;
-
- pr_debug("VLAN %d: L2 learning: %d, L2 Unknown MultiCast Field %x, \
- IPv4 Unknown MultiCast Field %x, IPv6 Unknown MultiCast Field: %x",
- index, profile[0] & (3 << 21), profile[2], profile[3], profile[4]);
+ p[0] = sw_r32(RTL930X_VLAN_PROFILE_SET(profile));
+ p[1] = sw_r32(RTL930X_VLAN_PROFILE_SET(profile) + 4);
+ p[2] = sw_r32(RTL930X_VLAN_PROFILE_SET(profile) + 8) & 0x1FFFFFFF;
+ p[3] = sw_r32(RTL930X_VLAN_PROFILE_SET(profile) + 12) & 0x1FFFFFFF;
+ p[4] = sw_r32(RTL930X_VLAN_PROFILE_SET(profile) + 16) & 0x1FFFFFFF;
+
+ pr_info("VLAN %d: L2 learn: %d; Unknown MC PMasks: L2 %0x, IPv4 %0x, IPv6: %0x",
+ profile, p[0] & (3 << 21), p[2], p[3], p[4]);
+ pr_info(" Routing enabled: IPv4 UC %c, IPv6 UC %c, IPv4 MC %c, IPv6 MC %c\n",
+ p[0] & BIT(17) ? 'y' : 'n', p[0] & BIT(16) ? 'y' : 'n',
+ p[0] & BIT(13) ? 'y' : 'n', p[0] & BIT(12) ? 'y' : 'n');
+ pr_info(" Bridge enabled: IPv4 MC %c, IPv6 MC %c,\n",
+ p[0] & BIT(15) ? 'y' : 'n', p[0] & BIT(14) ? 'y' : 'n');
+ pr_info("VLAN profile %d: raw %08x %08x %08x %08x %08x\n",
+ profile, p[0], p[1], p[2], p[3], p[4]);
}
static void rtl930x_vlan_set_untagged(u32 vlan, u64 portmask)
@@ -126,6 +132,39 @@ static void rtl930x_vlan_set_untagged(u32 vlan, u64 portmask)
rtl_table_release(r);
}
+/* Sets the L2 forwarding to be based on either the inner VLAN tag or the outer
+ */
+static void rtl930x_vlan_fwd_on_inner(int port, bool is_set)
+{
+ // Always set all tag modes to fwd based on either inner or outer tag
+ if (is_set)
+ sw_w32_mask(0, 0xf, RTL930X_VLAN_PORT_FWD + (port << 2));
+ else
+ sw_w32_mask(0xf, 0, RTL930X_VLAN_PORT_FWD + (port << 2));
+}
+
+static void rtl930x_vlan_profile_setup(int profile)
+{
+ u32 p[5];
+
+ pr_info("In %s\n", __func__);
+ p[0] = sw_r32(RTL930X_VLAN_PROFILE_SET(profile));
+ p[1] = sw_r32(RTL930X_VLAN_PROFILE_SET(profile) + 4);
+
+ // Enable routing of Ipv4/6 Unicast and IPv4/6 Multicast traffic
+ p[0] |= BIT(17) | BIT(16) | BIT(13) | BIT(12);
+ p[2] = 0x1fffffff; // L2 unknown MC flooding portmask all ports, including the CPU-port
+ p[3] = 0x1fffffff; // IPv4 unknown MC flooding portmask
+ p[4] = 0x1fffffff; // IPv6 unknown MC flooding portmask
+
+ sw_w32(p[0], RTL930X_VLAN_PROFILE_SET(profile));
+ sw_w32(p[1], RTL930X_VLAN_PROFILE_SET(profile) + 4);
+ sw_w32(p[2], RTL930X_VLAN_PROFILE_SET(profile) + 8);
+ sw_w32(p[3], RTL930X_VLAN_PROFILE_SET(profile) + 12);
+ sw_w32(p[4], RTL930X_VLAN_PROFILE_SET(profile) + 16);
+ pr_info("Leaving %s\n", __func__);
+}
+
static void rtl930x_stp_get(struct rtl838x_switch_priv *priv, u16 msti, u32 port_state[])
{
int i;
@@ -168,12 +207,74 @@ static inline int rtl930x_mac_link_spd_sts(int p)
return RTL930X_MAC_LINK_SPD_STS(p);
}
+static u64 rtl930x_l2_hash_seed(u64 mac, u32 vid)
+{
+ u64 v = vid;
+
+ v <<= 48;
+ v |= mac;
+
+ return v;
+}
+
+/*
+ * Calculate both the block 0 and the block 1 hash by applyingthe same hash
+ * algorithm as the one used currently by the ASIC to the seed, and return
+ * both hashes in the lower and higher word of the return value since only 12 bit of
+ * the hash are significant
+ */
+static u32 rtl930x_l2_hash_key(struct rtl838x_switch_priv *priv, u64 seed)
+{
+ u32 k0, k1, h1, h2, h;
+
+ k0 = (u32) (((seed >> 55) & 0x1f) ^ ((seed >> 44) & 0x7ff)
+ ^ ((seed >> 33) & 0x7ff) ^ ((seed >> 22) & 0x7ff)
+ ^ ((seed >> 11) & 0x7ff) ^ (seed & 0x7ff));
+
+ h1 = (seed >> 11) & 0x7ff;
+ h1 = ((h1 & 0x1f) << 6) | ((h1 >> 5) & 0x3f);
+
+ h2 = (seed >> 33) & 0x7ff;
+ h2 = ((h2 & 0x3f) << 5)| ((h2 >> 6) & 0x3f);
+
+ k1 = (u32) (((seed << 55) & 0x1f) ^ ((seed >> 44) & 0x7ff) ^ h2
+ ^ ((seed >> 22) & 0x7ff) ^ h1
+ ^ (seed & 0x7ff));
+
+ // Algorithm choice for block 0
+ if (sw_r32(RTL930X_L2_CTRL) & BIT(0))
+ h = k1;
+ else
+ h = k0;
+
+ /* Algorithm choice for block 1
+ * Since k0 and k1 are < 2048, adding 2048 will offset the hash into the second
+ * half of hash-space
+ * 2048 is in fact the hash-table size 16384 divided by 4 hashes per bucket
+ * divided by 2 to divide the hash space in 2
+ */
+ if (sw_r32(RTL930X_L2_CTRL) & BIT(1))
+ h |= (k1 + 2048) << 16;
+ else
+ h |= (k0 + 2048) << 16;
+
+ return h;
+}
+
+/*
+ * Fills an L2 entry structure from the SoC registers
+ */
static void rtl930x_fill_l2_entry(u32 r[], struct rtl838x_l2_entry *e)
{
+ pr_debug("In %s valid?\n", __func__);
e->valid = !!(r[2] & BIT(31));
if (!e->valid)
return;
+ pr_debug("In %s is valid\n", __func__);
+ e->is_ip_mc = false;
+ e->is_ipv6_mc = false;
+
// TODO: Is there not a function to copy directly MAC memory?
e->mac[0] = (r[0] >> 24);
e->mac[1] = (r[0] >> 16);
@@ -182,61 +283,164 @@ static void rtl930x_fill_l2_entry(u32 r[], struct rtl838x_l2_entry *e)
e->mac[4] = (r[1] >> 24);
e->mac[5] = (r[1] >> 16);
+ e->next_hop = !!(r[2] & BIT(12));
+ e->rvid = r[1] & 0xfff;
+
/* Is it a unicast entry? check multicast bit */
if (!(e->mac[0] & 1)) {
e->type = L2_UNICAST;
e->is_static = !!(r[2] & BIT(14));
- e->vid = r[2] & 0xfff;
- e->rvid = r[1] & 0xfff;
e->port = (r[2] >> 20) & 0x3ff;
// Check for trunk port
if (r[2] & BIT(30)) {
- e->stackDev = (e->port >> 9) & 1;
+ e->is_trunk = true;
+ e->stack_dev = (e->port >> 9) & 1;
e->trunk = e->port & 0x3f;
} else {
- e->stackDev = (e->port >> 6) & 0xf;
+ e->is_trunk = false;
+ e->stack_dev = (e->port >> 6) & 0xf;
e->port = e->port & 0x3f;
}
e->block_da = !!(r[2] & BIT(15));
e->block_sa = !!(r[2] & BIT(16));
e->suspended = !!(r[2] & BIT(13));
- e->next_hop = !!(r[2] & BIT(12));
e->age = (r[2] >> 17) & 3;
e->valid = true;
-
+ // the UC_VID field in hardware is used for the VID or for the route id
+ if (e->next_hop) {
+ e->nh_route_id = r[2] & 0xfff;
+ e->vid = 0;
+ } else {
+ e->vid = r[2] & 0xfff;
+ e->nh_route_id = 0;
+ }
} else {
e->valid = true;
e->type = L2_MULTICAST;
- e->mc_portmask_index = (r[2]>>6) & 0xfff;
+ e->mc_portmask_index = (r[2] >> 16) & 0x3ff;
}
}
-static u64 rtl930x_read_l2_entry_using_hash(u32 hash, u32 position, struct rtl838x_l2_entry *e)
+/*
+ * Fills the 3 SoC table registers r[] with the information of in the rtl838x_l2_entry
+ */
+static void rtl930x_fill_l2_row(u32 r[], struct rtl838x_l2_entry *e)
+{
+ u32 port;
+
+ if (!e->valid) {
+ r[0] = r[1] = r[2] = 0;
+ return;
+ }
+
+ r[2] = BIT(31); // Set valid bit
+
+ r[0] = ((u32)e->mac[0]) << 24 | ((u32)e->mac[1]) << 16
+ | ((u32)e->mac[2]) << 8 | ((u32)e->mac[3]);
+ r[1] = ((u32)e->mac[4]) << 24 | ((u32)e->mac[5]) << 16;
+
+ r[2] |= e->next_hop ? BIT(12) : 0;
+
+ if (e->type == L2_UNICAST) {
+ r[2] |= e->is_static ? BIT(14) : 0;
+ r[1] |= e->rvid & 0xfff;
+ r[2] |= (e->port & 0x3ff) << 20;
+ if (e->is_trunk) {
+ r[2] |= BIT(30);
+ port = e->stack_dev << 9 | (e->port & 0x3f);
+ } else {
+ port = (e->stack_dev & 0xf) << 6;
+ port |= e->port & 0x3f;
+ }
+ r[2] |= port << 20;
+ r[2] |= e->block_da ? BIT(15) : 0;
+ r[2] |= e->block_sa ? BIT(17) : 0;
+ r[2] |= e->suspended ? BIT(13) : 0;
+ r[2] |= (e->age & 0x3) << 17;
+ // the UC_VID field in hardware is used for the VID or for the route id
+ if (e->next_hop)
+ r[2] |= e->nh_route_id & 0xfff;
+ else
+ r[2] |= e->vid & 0xfff;
+ } else { // L2_MULTICAST
+ r[2] |= (e->mc_portmask_index & 0x3ff) << 16;
+ r[2] |= e->mc_mac_index & 0x7ff;
+ }
+}
+
+/*
+ * Read an L2 UC or MC entry out of a hash bucket of the L2 forwarding table
+ * hash is the id of the bucket and pos is the position of the entry in that bucket
+ * The data read from the SoC is filled into rtl838x_l2_entry
+ */
+static u64 rtl930x_read_l2_entry_using_hash(u32 hash, u32 pos, struct rtl838x_l2_entry *e)
{
- u64 entry;
u32 r[3];
struct table_reg *q = rtl_table_get(RTL9300_TBL_L2, 0);
- u32 idx = (0 << 14) | (hash << 2) | position;
+ u32 idx;
int i;
+ u64 mac;
+ u64 seed;
+
+ pr_debug("%s: hash %08x, pos: %d\n", __func__, hash, pos);
+
+ /* On the RTL93xx, 2 different hash algorithms are used making it a total of
+ * 8 buckets that need to be searched, 4 for each hash-half
+ * Use second hash space when bucket is between 4 and 8 */
+ if (pos >= 4) {
+ pos -= 4;
+ hash >>= 16;
+ } else {
+ hash &= 0xffff;
+ }
+
+ idx = (0 << 14) | (hash << 2) | pos; // Search SRAM, with hash and at pos in bucket
+ pr_debug("%s: NOW hash %08x, pos: %d\n", __func__, hash, pos);
rtl_table_read(q, idx);
- for (i= 0; i < 3; i++)
+ for (i = 0; i < 3; i++)
r[i] = sw_r32(rtl_table_data(q, i));
rtl_table_release(q);
rtl930x_fill_l2_entry(r, e);
+
+ pr_debug("%s: valid: %d, nh: %d\n", __func__, e->valid, e->next_hop);
if (!e->valid)
return 0;
- entry = ((u64)r[0] << 32) | (r[1] & 0xffff0000) | e->vid;
- return entry;
+ mac = ((u64)e->mac[0]) << 40 | ((u64)e->mac[1]) << 32 | ((u64)e->mac[2]) << 24
+ | ((u64)e->mac[3]) << 16 | ((u64)e->mac[4]) << 8 | ((u64)e->mac[5]);
+
+ seed = rtl930x_l2_hash_seed(mac, e->rvid);
+ pr_debug("%s: mac %016llx, seed %016llx\n", __func__, mac, seed);
+ // return vid with concatenated mac as unique id
+ return seed;
+}
+
+static void rtl930x_write_l2_entry_using_hash(u32 hash, u32 pos, struct rtl838x_l2_entry *e)
+{
+ u32 r[3];
+ struct table_reg *q = rtl_table_get(RTL9300_TBL_L2, 0);
+ u32 idx = (0 << 14) | (hash << 2) | pos; // Access SRAM, with hash and at pos in bucket
+ int i;
+
+ pr_info("%s: hash %d, pos %d\n", __func__, hash, pos);
+ pr_info("%s: index %d -> mac %02x:%02x:%02x:%02x:%02x:%02x\n", __func__, idx,
+ e->mac[0], e->mac[1], e->mac[2], e->mac[3],e->mac[4],e->mac[5]);
+
+ rtl930x_fill_l2_row(r, e);
+
+ for (i= 0; i < 3; i++)
+ sw_w32(r[i], rtl_table_data(q, i));
+
+ rtl_table_write(q, idx);
+ rtl_table_release(q);
}
static u64 rtl930x_read_cam(int idx, struct rtl838x_l2_entry *e)
{
- u64 entry;
u32 r[3];
struct table_reg *q = rtl_table_get(RTL9300_TBL_L2, 1);
int i;
@@ -251,9 +455,142 @@ static u64 rtl930x_read_cam(int idx, struct rtl838x_l2_entry *e)
if (!e->valid)
return 0;
- entry = ((u64)r[0] << 32) | (r[1] & 0xffff0000) | e->vid;
+ // return mac with concatenated vid as unique id
+ return ((u64)r[0] << 28) | ((r[1] & 0xffff0000) >> 4) | e->vid;
+}
+
+static void rtl930x_write_cam(int idx, struct rtl838x_l2_entry *e)
+{
+ u32 r[3];
+ struct table_reg *q = rtl_table_get(RTL9300_TBL_L2, 1); // Access L2 Table 1
+ int i;
+
+ rtl930x_fill_l2_row(r, e);
+
+ for (i= 0; i < 3; i++)
+ sw_w32(r[i], rtl_table_data(q, i));
+
+ rtl_table_write(q, idx);
+ rtl_table_release(q);
+}
- return entry;
+static void dump_l2_entry(struct rtl838x_l2_entry *e)
+{
+ pr_info("MAC: %02x:%02x:%02x:%02x:%02x:%02x vid: %d, rvid: %d, port: %d, valid: %d\n",
+ e->mac[0], e->mac[1], e->mac[2], e->mac[3], e->mac[4], e->mac[5],
+ e->vid, e->rvid, e->port, e->valid);
+ pr_info("Type: %d, is_static: %d, is_ip_mc: %d, is_ipv6_mc: %d, block_da: %d\n",
+ e->type, e->is_static, e->is_ip_mc, e->is_ipv6_mc, e->block_da);
+ pr_info(" block_sa: %d, suspended: %d, next_hop: %d, age: %d, is_trunk: %d, trunk: %d\n",
+ e->block_sa, e->suspended, e->next_hop, e->age, e->is_trunk, e->trunk);
+ if (e->is_ip_mc || e->is_ipv6_mc)
+ pr_info(" mc_portmask_index: %d, mc_gip: %d, mc_sip: %d\n",
+ e->mc_portmask_index, e->mc_gip, e->mc_sip);
+ pr_info(" stac_dev: %d, nh_route_id: %d, port: %d, dev_id\n",
+ e->stack_dev, e->nh_route_id, e->port);
+}
+
+/*
+ * Add an L2 nexthop entry for the L3 routing system in the SoC
+ * Use VID and MAC in rtl838x_l2_entry to identify either a free slot in the L2 hash table
+ * or mark an existing entry as a nexthop by setting it's nexthop bit
+ * Called from the L3 layer
+ * The index in the L2 hash table is filled into nh->l2_id;
+ */
+static int rtl930x_l2_nexthop_add(struct rtl838x_switch_priv *priv, struct rtl838x_nexthop *nh)
+{
+ struct rtl838x_l2_entry e;
+ u64 seed = rtl930x_l2_hash_seed(nh->mac, nh->vid);
+ u32 key = rtl930x_l2_hash_key(priv, seed);
+ int i, idx = -1;
+ u64 entry;
+
+ pr_info("%s searching for %08llx vid %d with key %d, seed: %016llx\n",
+ __func__, nh->mac, nh->vid, key, seed);
+
+ e.type = L2_UNICAST;
+ e.rvid = nh->fid; // Verify its the forwarding ID!!! l2_entry.un.unicast.fid
+ u64_to_ether_addr(nh->mac, &e.mac[0]);
+ e.port = RTL930X_PORT_IGNORE;
+
+ // Loop over all entries in the hash-bucket and over the second block on 93xx SoCs
+ for (i = 0; i < priv->l2_bucket_size; i++) {
+ entry = rtl930x_read_l2_entry_using_hash(key, i, &e);
+ pr_info("%s i: %d, entry %016llx, seed %016llx\n", __func__, i, entry, seed);
+ if (e.valid && e.next_hop)
+ continue;
+ if (!e.valid || ((entry & 0x0fffffffffffffffULL) == seed)) {
+ idx = i > 3 ? ((key >> 14) & 0xffff) | i >> 1
+ : ((key << 2) | i) & 0xffff;
+ break;
+ }
+ }
+
+ pr_info("%s: found idx %d and i %d\n", __func__, idx, i);
+
+ if (idx < 0) {
+ pr_err("%s: No more L2 forwarding entries available\n", __func__);
+ return -1;
+ }
+
+ // Found an existing or empty entry, make it a nexthop entry
+ pr_info("%s BEFORE -> key %d, pos: %d, index: %d\n", __func__, key, i, idx);
+ dump_l2_entry(&e);
+ nh->l2_id = idx;
+
+ // Found an existing (e->valid is true) or empty entry, make it a nexthop entry
+ if (e.valid) {
+ nh->port = e.port;
+ nh->fid = e.rvid;
+ nh->vid = e.vid;
+ nh->dev_id = e.stack_dev;
+ } else {
+ e.valid = true;
+ e.is_static = false;
+ e.vid = nh->vid;
+ e.rvid = nh->fid;
+ e.port = RTL930X_PORT_IGNORE;
+ u64_to_ether_addr(nh->mac, &e.mac[0]);
+ }
+ e.next_hop = true;
+ // For nexthop entries, the vid field in the table is used to denote the dest mac_id
+ e.nh_route_id = nh->mac_id;
+ pr_info("%s AFTER\n", __func__);
+ dump_l2_entry(&e);
+
+ rtl930x_write_l2_entry_using_hash(idx >> 2, idx & 0x3, &e);
+
+ // _dal_longan_l2_nexthop_add
+ return 0;
+}
+
+static u64 rtl930x_read_mcast_pmask(int idx)
+{
+ u32 portmask;
+ // Read MC_PORTMASK (2) via register RTL9300_TBL_L2
+ struct table_reg *q = rtl_table_get(RTL9300_TBL_L2, 2);
+
+ rtl_table_read(q, idx);
+ portmask = sw_r32(rtl_table_data(q, 0));
+ portmask >>= 3;
+ rtl_table_release(q);
+
+ pr_debug("%s: Index idx %d has portmask %08x\n", __func__, idx, portmask);
+ return portmask;
+}
+
+static void rtl930x_write_mcast_pmask(int idx, u64 portmask)
+{
+ u32 pm = portmask;
+
+ // Access MC_PORTMASK (2) via register RTL9300_TBL_L2
+ struct table_reg *q = rtl_table_get(RTL9300_TBL_L2, 2);
+
+ pr_debug("%s: Index idx %d has portmask %08x\n", __func__, idx, pm);
+ pm <<= 3;
+ sw_w32(pm, rtl_table_data(q, 0));
+ rtl_table_write(q, idx);
+ rtl_table_release(q);
}
u64 rtl930x_traffic_get(int source)
@@ -382,7 +719,6 @@ int rtl9300_sds_power(int mac, int val)
return 0;
}
-
int rtl930x_write_phy(u32 port, u32 page, u32 reg, u32 val)
{
u32 v;
@@ -418,7 +754,6 @@ int rtl930x_read_phy(u32 port, u32 page, u32 reg, u32 *val)
u32 v;
int err = 0;
-// pr_info("In %s\n", __func__);
if (port > 63 || page > 4095 || reg > 31)
return -ENOTSUPP;
@@ -445,7 +780,6 @@ int rtl930x_read_phy(u32 port, u32 page, u32 reg, u32 *val)
return err;
}
-
/*
* Write to an mmd register of the PHY
*/
@@ -465,12 +799,12 @@ int rtl930x_write_mmd_phy(u32 port, u32 devnum, u32 regnum, u32 val)
// Set MMD device number and register to write to
sw_w32(devnum << 16 | (regnum & 0xffff), RTL930X_SMI_ACCESS_PHY_CTRL_3);
- v = BIT(2)| BIT(1)| BIT(0); // WRITE | MMD-access | EXEC
+ v = BIT(2) | BIT(1) | BIT(0); // WRITE | MMD-access | EXEC
sw_w32(v, RTL930X_SMI_ACCESS_PHY_CTRL_1);
do {
v = sw_r32(RTL930X_SMI_ACCESS_PHY_CTRL_1);
- } while ( v & BIT(0));
+ } while (v & BIT(0));
pr_debug("%s: port %d, regnum: %x, val: %x (err %d)\n", __func__, port, regnum, val, err);
mutex_unlock(&smi_lock);
@@ -493,12 +827,12 @@ int rtl930x_read_mmd_phy(u32 port, u32 devnum, u32 regnum, u32 *val)
// Set MMD device number and register to write to
sw_w32(devnum << 16 | (regnum & 0xffff), RTL930X_SMI_ACCESS_PHY_CTRL_3);
- v = BIT(1)| BIT(0); // MMD-access | EXEC
+ v = BIT(1) | BIT(0); // MMD-access | EXEC
sw_w32(v, RTL930X_SMI_ACCESS_PHY_CTRL_1);
do {
v = sw_r32(RTL930X_SMI_ACCESS_PHY_CTRL_1);
- } while ( v & 0x1);
+ } while (v & BIT(0));
// There is no error-checking via BIT 25 of v, as it does not seem to be set correctly
*val = (sw_r32(RTL930X_SMI_ACCESS_PHY_CTRL_2) & 0xffff);
pr_debug("%s: port %d, regnum: %x, val: %x (err %d)\n", __func__, port, regnum, *val, err);
@@ -508,7 +842,6 @@ int rtl930x_read_mmd_phy(u32 port, u32 devnum, u32 regnum, u32 *val)
return err;
}
-
/*
* Calculate both the block 0 and the block 1 hash, and return in
* lower and higher word of the return value since only 12 bit of
@@ -552,6 +885,96 @@ u32 rtl930x_hash(struct rtl838x_switch_priv *priv, u64 seed)
return h;
}
+/*
+ * Enables or disables the EEE/EEEP capability of a port
+ */
+void rtl930x_port_eee_set(struct rtl838x_switch_priv *priv, int port, bool enable)
+{
+ u32 v;
+
+ // This works only for Ethernet ports, and on the RTL930X, ports from 26 are SFP
+ if (port >= 26)
+ return;
+
+ pr_debug("In %s: setting port %d to %d\n", __func__, port, enable);
+ v = enable ? 0x3f : 0x0;
+
+ // Set EEE/EEEP state for 100, 500, 1000MBit and 2.5, 5 and 10GBit
+ sw_w32_mask(0, v << 10, rtl930x_mac_force_mode_ctrl(port));
+
+ // Set TX/RX EEE state
+ v = enable ? 0x3 : 0x0;
+ sw_w32(v, RTL930X_EEE_CTRL(port));
+
+ priv->ports[port].eee_enabled = enable;
+}
+
+/*
+ * Get EEE own capabilities and negotiation result
+ */
+int rtl930x_eee_port_ability(struct rtl838x_switch_priv *priv, struct ethtool_eee *e, int port)
+{
+ u32 link, a;
+
+ if (port >= 26)
+ return -ENOTSUPP;
+
+ pr_info("In %s, port %d\n", __func__, port);
+ link = sw_r32(RTL930X_MAC_LINK_STS);
+ link = sw_r32(RTL930X_MAC_LINK_STS);
+ if (!(link & BIT(port)))
+ return 0;
+
+ pr_info("Setting advertised\n");
+ if (sw_r32(rtl930x_mac_force_mode_ctrl(port)) & BIT(10))
+ e->advertised |= ADVERTISED_100baseT_Full;
+
+ if (sw_r32(rtl930x_mac_force_mode_ctrl(port)) & BIT(12))
+ e->advertised |= ADVERTISED_1000baseT_Full;
+
+ if (priv->ports[port].is2G5 && sw_r32(rtl930x_mac_force_mode_ctrl(port)) & BIT(13)) {
+ pr_info("ADVERTISING 2.5G EEE\n");
+ e->advertised |= ADVERTISED_2500baseX_Full;
+ }
+
+ if (priv->ports[port].is10G && sw_r32(rtl930x_mac_force_mode_ctrl(port)) & BIT(15))
+ e->advertised |= ADVERTISED_10000baseT_Full;
+
+ a = sw_r32(RTL930X_MAC_EEE_ABLTY);
+ a = sw_r32(RTL930X_MAC_EEE_ABLTY);
+ pr_info("Link partner: %08x\n", a);
+ if (a & BIT(port)) {
+ e->lp_advertised = ADVERTISED_100baseT_Full;
+ e->lp_advertised |= ADVERTISED_1000baseT_Full;
+ if (priv->ports[port].is2G5)
+ e->lp_advertised |= ADVERTISED_2500baseX_Full;
+ if (priv->ports[port].is10G)
+ e->lp_advertised |= ADVERTISED_10000baseT_Full;
+ }
+
+ // Read 2x to clear latched state
+ a = sw_r32(RTL930X_EEEP_PORT_CTRL(port));
+ a = sw_r32(RTL930X_EEEP_PORT_CTRL(port));
+ pr_info("%s RTL930X_EEEP_PORT_CTRL: %08x\n", __func__, a);
+
+ return 0;
+}
+
+static void rtl930x_init_eee(struct rtl838x_switch_priv *priv, bool enable)
+{
+ int i;
+
+ pr_info("Setting up EEE, state: %d\n", enable);
+
+ // Setup EEE on all ports
+ for (i = 0; i < priv->cpu_port; i++) {
+ if (priv->ports[i].phy)
+ rtl930x_port_eee_set(priv, i, enable);
+ }
+
+ priv->eee_enabled = enable;
+}
+
const struct rtl838x_reg rtl930x_reg = {
.mask_port_reg_be = rtl838x_mask_port_reg,
.set_port_reg_be = rtl838x_set_port_reg,
@@ -582,6 +1005,8 @@ const struct rtl838x_reg rtl930x_reg = {
.vlan_set_tagged = rtl930x_vlan_set_tagged,
.vlan_set_untagged = rtl930x_vlan_set_untagged,
.vlan_profile_dump = rtl930x_vlan_profile_dump,
+ .vlan_profile_setup = rtl930x_vlan_profile_setup,
+ .vlan_fwd_on_inner = rtl930x_vlan_fwd_on_inner,
.stp_get = rtl930x_stp_get,
.stp_set = rtl930x_stp_set,
.mac_force_mode_ctrl = rtl930x_mac_force_mode_ctrl,
@@ -597,11 +1022,18 @@ const struct rtl838x_reg rtl930x_reg = {
.mac_rx_pause_sts = RTL930X_MAC_RX_PAUSE_STS,
.mac_tx_pause_sts = RTL930X_MAC_TX_PAUSE_STS,
.read_l2_entry_using_hash = rtl930x_read_l2_entry_using_hash,
+ .write_l2_entry_using_hash = rtl930x_write_l2_entry_using_hash,
.read_cam = rtl930x_read_cam,
+ .write_cam = rtl930x_write_cam,
.vlan_port_egr_filter = RTL930X_VLAN_PORT_EGR_FLTR,
.vlan_port_igr_filter = RTL930X_VLAN_PORT_IGR_FLTR(0),
.vlan_port_pb = RTL930X_VLAN_PORT_PB_VLAN,
.vlan_port_tag_sts_ctrl = RTL930X_VLAN_PORT_TAG_STS_CTRL,
.trk_mbr_ctr = rtl930x_trk_mbr_ctr,
.rma_bpdu_fld_pmask = RTL930X_RMA_BPDU_FLD_PMSK,
+ .init_eee = rtl930x_init_eee,
+ .port_eee_set = rtl930x_port_eee_set,
+ .eee_port_ability = rtl930x_eee_port_ability,
+ .read_mcast_pmask = rtl930x_read_mcast_pmask,
+ .write_mcast_pmask = rtl930x_write_mcast_pmask,
};
diff --git a/target/linux/realtek/files-5.4/drivers/net/dsa/rtl83xx/rtl931x.c b/target/linux/realtek/files-5.4/drivers/net/dsa/rtl83xx/rtl931x.c
index a33941a0eb..f98bf7df29 100644
--- a/target/linux/realtek/files-5.4/drivers/net/dsa/rtl83xx/rtl931x.c
+++ b/target/linux/realtek/files-5.4/drivers/net/dsa/rtl83xx/rtl931x.c
@@ -175,6 +175,7 @@ static u64 rtl931x_read_cam(int idx, struct rtl838x_l2_entry *e)
// TODO: Implement
return entry;
}
+
irqreturn_t rtl931x_switch_irq(int irq, void *dev_id)
{
struct dsa_switch *ds = dev_id;
@@ -199,7 +200,6 @@ irqreturn_t rtl931x_switch_irq(int irq, void *dev_id)
return IRQ_HANDLED;
}
-
int rtl931x_write_phy(u32 port, u32 page, u32 reg, u32 val)
{
u32 v;
@@ -264,6 +264,73 @@ int rtl931x_read_phy(u32 port, u32 page, u32 reg, u32 *val)
return 0;
}
+/*
+ * Read an mmd register of the PHY
+ */
+int rtl931x_read_mmd_phy(u32 port, u32 devnum, u32 regnum, u32 *val)
+{
+ int err = 0;
+ u32 v;
+ int type = 1; // TODO: For C45 PHYs need to set to 2
+
+ mutex_lock(&smi_lock);
+
+ // Set PHY to access via port-number
+ sw_w32(port << 5, RTL931X_SMI_INDRT_ACCESS_BC_PHYID_CTRL);
+
+ // Set MMD device number and register to write to
+ sw_w32(devnum << 16 | (regnum & 0xffff), RTL931X_SMI_INDRT_ACCESS_MMD_CTRL);
+
+ v = type << 2 | BIT(0); // MMD-access-type | EXEC
+ sw_w32(v, RTL931X_SMI_INDRT_ACCESS_CTRL_0);
+
+ do {
+ v = sw_r32(RTL931X_SMI_INDRT_ACCESS_CTRL_0);
+ } while (v & BIT(0));
+
+ // There is no error-checking via BIT 1 of v, as it does not seem to be set correctly
+
+ *val = (sw_r32(RTL931X_SMI_INDRT_ACCESS_CTRL_3) & 0xffff);
+
+ pr_debug("%s: port %d, regnum: %x, val: %x (err %d)\n", __func__, port, regnum, *val, err);
+
+ mutex_unlock(&smi_lock);
+
+ return err;
+}
+
+/*
+ * Write to an mmd register of the PHY
+ */
+int rtl931x_write_mmd_phy(u32 port, u32 devnum, u32 regnum, u32 val)
+{
+ int err = 0;
+ u32 v;
+ int type = 1; // TODO: For C45 PHYs need to set to 2
+
+ mutex_lock(&smi_lock);
+
+ // Set PHY to access via port-number
+ sw_w32(port << 5, RTL931X_SMI_INDRT_ACCESS_BC_PHYID_CTRL);
+
+ // Set data to write
+ sw_w32_mask(0xffff << 16, val << 16, RTL931X_SMI_INDRT_ACCESS_CTRL_3);
+
+ // Set MMD device number and register to write to
+ sw_w32(devnum << 16 | (regnum & 0xffff), RTL931X_SMI_INDRT_ACCESS_MMD_CTRL);
+
+ v = BIT(4) | type << 2 | BIT(0); // WRITE | MMD-access-type | EXEC
+ sw_w32(v, RTL931X_SMI_INDRT_ACCESS_CTRL_0);
+
+ do {
+ v = sw_r32(RTL931X_SMI_INDRT_ACCESS_CTRL_0);
+ } while (v & BIT(0));
+
+ pr_debug("%s: port %d, regnum: %x, val: %x (err %d)\n", __func__, port, regnum, val, err);
+ mutex_unlock(&smi_lock);
+ return err;
+}
+
void rtl931x_print_matrix(void)
{
volatile u64 *ptr = RTL838X_SW_BASE + RTL839X_PORT_ISO_CTRL(0);
diff --git a/target/linux/realtek/files-5.4/drivers/net/ethernet/rtl838x_eth.c b/target/linux/realtek/files-5.4/drivers/net/ethernet/rtl838x_eth.c
index 7931daff07..c5c6e3b6b7 100644
--- a/target/linux/realtek/files-5.4/drivers/net/ethernet/rtl838x_eth.c
+++ b/target/linux/realtek/files-5.4/drivers/net/ethernet/rtl838x_eth.c
@@ -234,19 +234,21 @@ struct dsa_tag {
u16 port;
u8 l2_offloaded;
u8 prio;
+ bool crc_error;
};
bool rtl838x_decode_tag(struct p_hdr *h, struct dsa_tag *t)
{
t->reason = h->cpu_tag[3] & 0xf;
- if (t->reason != 15)
- pr_debug("Reason: %d\n", t->reason);
t->queue = (h->cpu_tag[0] & 0xe0) >> 5;
+ t->port = h->cpu_tag[1] & 0x1f;
+ t->crc_error = t->reason == 13;
+
+ pr_debug("Reason: %d\n", t->reason);
if (t->reason != 4) // NIC_RX_REASON_SPECIAL_TRAP
t->l2_offloaded = 1;
else
t->l2_offloaded = 0;
- t->port = h->cpu_tag[1] & 0x1f;
return t->l2_offloaded;
}
@@ -254,37 +256,48 @@ bool rtl838x_decode_tag(struct p_hdr *h, struct dsa_tag *t)
bool rtl839x_decode_tag(struct p_hdr *h, struct dsa_tag *t)
{
t->reason = h->cpu_tag[4] & 0x1f;
- if (t->reason != 31)
+ t->queue = (h->cpu_tag[3] & 0xe000) >> 13;
+ t->port = h->cpu_tag[1] & 0x3f;
+ t->crc_error = h->cpu_tag[3] & BIT(2);
+
pr_debug("Reason: %d\n", t->reason);
- t->queue = (h->cpu_tag[3] & 0xe000) >> 13;
if ((t->reason != 7) && (t->reason != 8)) // NIC_RX_REASON_RMA_USR
t->l2_offloaded = 1;
else
t->l2_offloaded = 0;
-
- t->port = h->cpu_tag[1] & 0x3f;
return t->l2_offloaded;
}
-bool rtl931x_decode_tag(struct p_hdr *h, struct dsa_tag *t)
+bool rtl930x_decode_tag(struct p_hdr *h, struct dsa_tag *t)
{
t->reason = h->cpu_tag[7] & 0x3f;
- pr_debug("Reason %d\n", t->reason);
t->queue = (h->cpu_tag[2] >> 11) & 0x1f;
+ t->port = (h->cpu_tag[0] >> 8) & 0x1f;
+ t->crc_error = h->cpu_tag[1] & BIT(6);
+
+ pr_debug("Reason %d, port %d, queue %d\n", t->reason, t->port, t->queue);
if (t->reason >= 19 && t->reason <= 27)
t->l2_offloaded = 0;
else
t->l2_offloaded = 1;
- t->port = (h->cpu_tag[0] >> 8) & 0x3f;
return t->l2_offloaded;
}
-bool rtl930x_decode_tag(struct p_hdr *h, struct dsa_tag *t)
+bool rtl931x_decode_tag(struct p_hdr *h, struct dsa_tag *t)
{
- rtl931x_decode_tag(h, t);
- t->port &= 0x1f;
+ t->reason = h->cpu_tag[7] & 0x3f;
+ t->queue = (h->cpu_tag[2] >> 11) & 0x1f;
+ t->port = (h->cpu_tag[0] >> 8) & 0x3f;
+ t->crc_error = h->cpu_tag[1] & BIT(6);
+
+ pr_debug("Reason %d, port %d, queue %d\n", t->reason, t->port, t->queue);
+ if (t->reason >= 19 && t->reason <= 27)
+ t->l2_offloaded = 0;
+ else
+ t->l2_offloaded = 1;
+
return t->l2_offloaded;
}
@@ -731,8 +744,9 @@ static void rtl838x_hw_en_rxtx(struct rtl838x_eth_priv *priv)
* | MEDIA_SEL
*/
sw_w32(0x6192F, priv->r->mac_force_mode_ctrl + priv->cpu_port * 4);
- /* allow CRC errors on CPU-port */
- sw_w32_mask(0, 0x8, priv->r->mac_port_ctrl(priv->cpu_port));
+
+ /* Enable CRC checks on CPU-port */
+ sw_w32_mask(0, BIT(3), priv->r->mac_port_ctrl(priv->cpu_port));
}
static void rtl839x_hw_en_rxtx(struct rtl838x_eth_priv *priv)
@@ -746,8 +760,8 @@ static void rtl839x_hw_en_rxtx(struct rtl838x_eth_priv *priv)
/* Enable DMA */
sw_w32_mask(0, RX_EN | TX_EN, priv->r->dma_if_ctrl);
- /* Restart TX/RX to CPU port */
- sw_w32_mask(0x0, 0x3, priv->r->mac_port_ctrl(priv->cpu_port));
+ /* Restart TX/RX to CPU port, enable CRC checking */
+ sw_w32_mask(0x0, 0x3 | BIT(3), priv->r->mac_port_ctrl(priv->cpu_port));
/* CPU port joins Lookup Miss Flooding Portmask */
// TODO: The code below should also work for the RTL838x
@@ -784,8 +798,8 @@ static void rtl93xx_hw_en_rxtx(struct rtl838x_eth_priv *priv)
/* Enable DMA */
sw_w32_mask(0, RX_EN_93XX | TX_EN_93XX, priv->r->dma_if_ctrl);
- /* Restart TX/RX to CPU port */
- sw_w32_mask(0x0, 0x3, priv->r->mac_port_ctrl(priv->cpu_port));
+ /* Restart TX/RX to CPU port, enable CRC checking */
+ sw_w32_mask(0x0, 0x3 | BIT(4), priv->r->mac_port_ctrl(priv->cpu_port));
sw_w32_mask(0, BIT(priv->cpu_port), RTL930X_L2_UNKN_UC_FLD_PMSK);
sw_w32(0x217, priv->r->mac_force_mode_ctrl + priv->cpu_port * 4);
@@ -855,7 +869,7 @@ static int rtl838x_eth_open(struct net_device *ndev)
struct ring_b *ring = priv->membase;
int i, err;
- pr_info("%s called: RX rings %d(length %d), TX rings %d(length %d)\n",
+ pr_debug("%s called: RX rings %d(length %d), TX rings %d(length %d)\n",
__func__, priv->rxrings, priv->rxringlen, TXRINGS, TXRINGLEN);
spin_lock_irqsave(&priv->lock, flags);
@@ -883,28 +897,30 @@ static int rtl838x_eth_open(struct net_device *ndev)
switch (priv->family_id) {
case RTL8380_FAMILY_ID:
rtl838x_hw_en_rxtx(priv);
- /* Trap IGMP traffic to CPU-Port */
+ /* Trap IGMP/MLD traffic to CPU-Port */
sw_w32(0x3, RTL838X_SPCL_TRAP_IGMP_CTRL);
/* Flush learned FDB entries on link down of a port */
sw_w32_mask(0, BIT(7), RTL838X_L2_CTRL_0);
break;
+
case RTL8390_FAMILY_ID:
rtl839x_hw_en_rxtx(priv);
+ // Trap MLD and IGMP messages to CPU_PORT
sw_w32(0x3, RTL839X_SPCL_TRAP_IGMP_CTRL);
/* Flush learned FDB entries on link down of a port */
sw_w32_mask(0, BIT(7), RTL839X_L2_CTRL_0);
break;
+
case RTL9300_FAMILY_ID:
rtl93xx_hw_en_rxtx(priv);
/* Flush learned FDB entries on link down of a port */
sw_w32_mask(0, BIT(7), RTL930X_L2_CTRL);
- sw_w32_mask(BIT(28), 0, RTL930X_L2_PORT_SABLK_CTRL);
- sw_w32_mask(BIT(28), 0, RTL930X_L2_PORT_DABLK_CTRL);
+ // Trap MLD and IGMP messages to CPU_PORT
+ sw_w32((0x2 << 3) | 0x2, RTL930X_VLAN_APP_PKT_CTRL);
break;
case RTL9310_FAMILY_ID:
rtl93xx_hw_en_rxtx(priv);
-// TODO: Add trapping of IGMP frames to CPU-port
break;
}
@@ -1115,15 +1131,20 @@ static int rtl838x_eth_tx(struct sk_buff *skb, struct net_device *dev)
if (netdev_uses_dsa(dev) && skb->data[len-4] == 0x80 && skb->data[len-3] > 0
&& skb->data[len-3] < 28 && skb->data[len-2] == 0x10
&& skb->data[len-1] == 0x00) {
- /* Reuse tag space for CRC */
+ /* Reuse tag space for CRC if possible */
dest_port = skb->data[len-3];
len -= 4;
}
- if (len < ETH_ZLEN)
- len = ETH_ZLEN;
- /* ASIC expects that packet includes CRC, so we extend by 4 bytes */
- len += 4;
+ len += 4; // Add space for CRC
+
+ // On RTL8380 SoCs, the packet needs extra padding
+ if (priv->family_id == RTL8380_FAMILY_ID) {
+ if (len < ETH_ZLEN)
+ len = ETH_ZLEN; // SoC not automatically padding to ETH_ZLEN
+ else
+ len += 4;
+ }
if (skb_padto(skb, len)) {
ret = NETDEV_TX_OK;
@@ -1280,6 +1301,12 @@ static int rtl838x_hw_receive(struct net_device *dev, int r, int budget)
tag.queue, len, tag.reason, tag.port);
skb->protocol = eth_type_trans(skb, dev);
+ if (dev->features & NETIF_F_RXCSUM) {
+ if (tag.crc_error)
+ skb_checksum_none_assert(skb);
+ else
+ skb->ip_summed = CHECKSUM_UNNECESSARY;
+ }
dev->stats.rx_packets++;
dev->stats.rx_bytes += len;
@@ -1342,7 +1369,7 @@ static void rtl838x_validate(struct phylink_config *config,
{
__ETHTOOL_DECLARE_LINK_MODE_MASK(mask) = { 0, };
- pr_info("In %s\n", __func__);
+ pr_debug("In %s\n", __func__);
if (!phy_interface_mode_is_rgmii(state->interface) &&
state->interface != PHY_INTERFACE_MODE_1000BASEX &&
@@ -1404,7 +1431,7 @@ static void rtl838x_mac_an_restart(struct phylink_config *config)
if (priv->family_id != RTL8380_FAMILY_ID)
return;
- pr_info("In %s\n", __func__);
+ pr_debug("In %s\n", __func__);
/* Restart by disabling and re-enabling link */
sw_w32(0x6192D, priv->r->mac_force_mode_ctrl + priv->cpu_port * 4);
mdelay(20);
@@ -1419,7 +1446,7 @@ static int rtl838x_mac_pcs_get_state(struct phylink_config *config,
struct rtl838x_eth_priv *priv = netdev_priv(dev);
int port = priv->cpu_port;
- pr_info("In %s\n", __func__);
+ pr_debug("In %s\n", __func__);
state->link = priv->r->get_mac_link_sts(port) ? 1 : 0;
state->duplex = priv->r->get_mac_link_dup_sts(port) ? 1 : 0;
@@ -1456,7 +1483,7 @@ static void rtl838x_mac_link_down(struct phylink_config *config,
struct net_device *dev = container_of(config->dev, struct net_device, dev);
struct rtl838x_eth_priv *priv = netdev_priv(dev);
- pr_info("In %s\n", __func__);
+ pr_debug("In %s\n", __func__);
/* Stop TX/RX to port */
sw_w32_mask(0x03, 0, priv->r->mac_port_ctrl(priv->cpu_port));
}
@@ -1468,7 +1495,7 @@ static void rtl838x_mac_link_up(struct phylink_config *config, unsigned int mode
struct net_device *dev = container_of(config->dev, struct net_device, dev);
struct rtl838x_eth_priv *priv = netdev_priv(dev);
- pr_info("In %s\n", __func__);
+ pr_debug("In %s\n", __func__);
/* Restart TX/RX to port */
sw_w32_mask(0, 0x03, priv->r->mac_port_ctrl(priv->cpu_port));
}
@@ -1479,7 +1506,7 @@ static void rtl838x_set_mac_hw(struct net_device *dev, u8 *mac)
unsigned long flags;
spin_lock_irqsave(&priv->lock, flags);
- pr_info("In %s\n", __func__);
+ pr_debug("In %s\n", __func__);
sw_w32((mac[0] << 8) | mac[1], priv->r->mac);
sw_w32((mac[2] << 24) | (mac[3] << 16) | (mac[4] << 8) | mac[5], priv->r->mac + 4);
@@ -1547,7 +1574,7 @@ static int rtl838x_get_link_ksettings(struct net_device *ndev,
{
struct rtl838x_eth_priv *priv = netdev_priv(ndev);
- pr_info("%s called\n", __func__);
+ pr_debug("%s called\n", __func__);
return phylink_ethtool_ksettings_get(priv->phylink, cmd);
}
@@ -1556,7 +1583,7 @@ static int rtl838x_set_link_ksettings(struct net_device *ndev,
{
struct rtl838x_eth_priv *priv = netdev_priv(ndev);
- pr_info("%s called\n", __func__);
+ pr_debug("%s called\n", __func__);
return phylink_ethtool_ksettings_set(priv->phylink, cmd);
}
@@ -1678,7 +1705,7 @@ static int rtl931x_mdio_write(struct mii_bus *bus, int mii_id,
static int rtl838x_mdio_reset(struct mii_bus *bus)
{
- pr_info("%s called\n", __func__);
+ pr_debug("%s called\n", __func__);
/* Disable MAC polling the PHY so that we can start configuration */
sw_w32(0x00000000, RTL838X_SMI_POLL_CTRL);
@@ -1693,7 +1720,7 @@ static int rtl839x_mdio_reset(struct mii_bus *bus)
{
return 0;
- pr_info("%s called\n", __func__);
+ pr_debug("%s called\n", __func__);
/* BUG: The following does not work, but should! */
/* Disable MAC polling the PHY so that we can start configuration */
sw_w32(0x00000000, RTL839X_SMI_PORT_POLLING_CTRL);
@@ -1710,7 +1737,7 @@ static int rtl931x_mdio_reset(struct mii_bus *bus)
sw_w32(0x00000000, RTL931X_SMI_PORT_POLLING_CTRL);
sw_w32(0x00000000, RTL931X_SMI_PORT_POLLING_CTRL + 4);
- pr_info("%s called\n", __func__);
+ pr_debug("%s called\n", __func__);
return 0;
}
@@ -1767,7 +1794,7 @@ static int rtl838x_mdio_init(struct rtl838x_eth_priv *priv)
struct device_node *mii_np;
int ret;
- pr_info("%s called\n", __func__);
+ pr_debug("%s called\n", __func__);
mii_np = of_get_child_by_name(priv->pdev->dev.of_node, "mdio-bus");
if (!mii_np) {
@@ -1827,7 +1854,7 @@ err_put_node:
static int rtl838x_mdio_remove(struct rtl838x_eth_priv *priv)
{
- pr_info("%s called\n", __func__);
+ pr_debug("%s called\n", __func__);
if (!priv->mii_bus)
return 0;
@@ -1837,6 +1864,40 @@ static int rtl838x_mdio_remove(struct rtl838x_eth_priv *priv)
return 0;
}
+static netdev_features_t rtl838x_fix_features(struct net_device *dev,
+ netdev_features_t features)
+{
+ return features;
+}
+
+static int rtl83xx_set_features(struct net_device *dev, netdev_features_t features)
+{
+ struct rtl838x_eth_priv *priv = netdev_priv(dev);
+
+ if ((features ^ dev->features) & NETIF_F_RXCSUM) {
+ if (!(features & NETIF_F_RXCSUM))
+ sw_w32_mask(BIT(3), 0, priv->r->mac_port_ctrl(priv->cpu_port));
+ else
+ sw_w32_mask(0, BIT(4), priv->r->mac_port_ctrl(priv->cpu_port));
+ }
+
+ return 0;
+}
+
+static int rtl93xx_set_features(struct net_device *dev, netdev_features_t features)
+{
+ struct rtl838x_eth_priv *priv = netdev_priv(dev);
+
+ if ((features ^ dev->features) & NETIF_F_RXCSUM) {
+ if (!(features & NETIF_F_RXCSUM))
+ sw_w32_mask(BIT(4), 0, priv->r->mac_port_ctrl(priv->cpu_port));
+ else
+ sw_w32_mask(0, BIT(4), priv->r->mac_port_ctrl(priv->cpu_port));
+ }
+
+ return 0;
+}
+
static const struct net_device_ops rtl838x_eth_netdev_ops = {
.ndo_open = rtl838x_eth_open,
.ndo_stop = rtl838x_eth_stop,
@@ -1846,6 +1907,8 @@ static const struct net_device_ops rtl838x_eth_netdev_ops = {
.ndo_validate_addr = eth_validate_addr,
.ndo_set_rx_mode = rtl838x_eth_set_multicast_list,
.ndo_tx_timeout = rtl838x_eth_tx_timeout,
+ .ndo_set_features = rtl83xx_set_features,
+ .ndo_fix_features = rtl838x_fix_features,
};
static const struct net_device_ops rtl839x_eth_netdev_ops = {
@@ -1857,6 +1920,8 @@ static const struct net_device_ops rtl839x_eth_netdev_ops = {
.ndo_validate_addr = eth_validate_addr,
.ndo_set_rx_mode = rtl839x_eth_set_multicast_list,
.ndo_tx_timeout = rtl838x_eth_tx_timeout,
+ .ndo_set_features = rtl83xx_set_features,
+ .ndo_fix_features = rtl838x_fix_features,
};
static const struct net_device_ops rtl930x_eth_netdev_ops = {
@@ -1868,6 +1933,8 @@ static const struct net_device_ops rtl930x_eth_netdev_ops = {
.ndo_validate_addr = eth_validate_addr,
.ndo_set_rx_mode = rtl930x_eth_set_multicast_list,
.ndo_tx_timeout = rtl838x_eth_tx_timeout,
+ .ndo_set_features = rtl93xx_set_features,
+ .ndo_fix_features = rtl838x_fix_features,
};
static const struct net_device_ops rtl931x_eth_netdev_ops = {
@@ -1879,6 +1946,8 @@ static const struct net_device_ops rtl931x_eth_netdev_ops = {
.ndo_validate_addr = eth_validate_addr,
.ndo_set_rx_mode = rtl931x_eth_set_multicast_list,
.ndo_tx_timeout = rtl838x_eth_tx_timeout,
+ .ndo_set_features = rtl93xx_set_features,
+ .ndo_fix_features = rtl838x_fix_features,
};
static const struct phylink_mac_ops rtl838x_phylink_ops = {
@@ -1975,6 +2044,8 @@ static int __init rtl838x_eth_probe(struct platform_device *pdev)
dev->ethtool_ops = &rtl838x_ethtool_ops;
dev->min_mtu = ETH_ZLEN;
dev->max_mtu = 1536;
+ dev->features = NETIF_F_RXCSUM | NETIF_F_HW_CSUM;
+ dev->hw_features = NETIF_F_RXCSUM;
priv->id = soc_info.id;
priv->family_id = soc_info.family;
diff --git a/target/linux/realtek/files-5.4/drivers/net/ethernet/rtl838x_eth.h b/target/linux/realtek/files-5.4/drivers/net/ethernet/rtl838x_eth.h
index d7b4317cbb..c7e97057b3 100644
--- a/target/linux/realtek/files-5.4/drivers/net/ethernet/rtl838x_eth.h
+++ b/target/linux/realtek/files-5.4/drivers/net/ethernet/rtl838x_eth.h
@@ -177,6 +177,7 @@
#define RTL839X_RMA_CTRL_2 (0x1208)
#define RTL839X_RMA_CTRL_3 (0x120C)
+#define RTL930X_VLAN_APP_PKT_CTRL (0xA23C)
#define RTL930X_RMA_CTRL_0 (0x9E60)
#define RTL930X_RMA_CTRL_1 (0x9E64)
#define RTL930X_RMA_CTRL_2 (0x9E68)
diff --git a/target/linux/realtek/files-5.4/drivers/net/phy/rtl83xx-phy.c b/target/linux/realtek/files-5.4/drivers/net/phy/rtl83xx-phy.c
index 78953c6d17..cfddb0aced 100644
--- a/target/linux/realtek/files-5.4/drivers/net/phy/rtl83xx-phy.c
+++ b/target/linux/realtek/files-5.4/drivers/net/phy/rtl83xx-phy.c
@@ -18,12 +18,26 @@
extern struct rtl83xx_soc_info soc_info;
extern struct mutex smi_lock;
+/*
+ * This lock protects the state of the SoC automatically polling the PHYs over the SMI
+ * bus to detect e.g. link and media changes. For operations on the PHYs such as
+ * patching or other configuration changes such as EEE, polling needs to be disabled
+ * since otherwise these operations may fails or lead to unpredictable results.
+ */
+DEFINE_MUTEX(poll_lock);
+
static const struct firmware rtl838x_8380_fw;
static const struct firmware rtl838x_8214fc_fw;
static const struct firmware rtl838x_8218b_fw;
+int rtl838x_read_mmd_phy(u32 port, u32 devnum, u32 regnum, u32 *val);
+int rtl838x_write_mmd_phy(u32 port, u32 devnum, u32 reg, u32 val);
+int rtl839x_read_mmd_phy(u32 port, u32 devnum, u32 regnum, u32 *val);
+int rtl839x_write_mmd_phy(u32 port, u32 devnum, u32 reg, u32 val);
int rtl930x_read_mmd_phy(u32 port, u32 devnum, u32 regnum, u32 *val);
-int rtl930x_write_mmd_phy(u32 port, u32 addr, u32 reg, u32 val);
+int rtl930x_write_mmd_phy(u32 port, u32 devnum, u32 reg, u32 val);
+int rtl931x_read_mmd_phy(u32 port, u32 devnum, u32 regnum, u32 *val);
+int rtl931x_write_mmd_phy(u32 port, u32 devnum, u32 reg, u32 val);
static int read_phy(u32 port, u32 page, u32 reg, u32 *val)
{ switch (soc_info.family) {
@@ -54,6 +68,93 @@ static int write_phy(u32 port, u32 page, u32 reg, u32 val)
return -1;
}
+static int read_mmd_phy(u32 port, u32 devnum, u32 regnum, u32 *val)
+{
+ switch (soc_info.family) {
+ case RTL8380_FAMILY_ID:
+ return rtl838x_read_mmd_phy(port, devnum, regnum, val);
+ case RTL8390_FAMILY_ID:
+ return rtl839x_read_mmd_phy(port, devnum, regnum, val);
+ case RTL9300_FAMILY_ID:
+ return rtl930x_read_mmd_phy(port, devnum, regnum, val);
+ case RTL9310_FAMILY_ID:
+ return rtl931x_read_mmd_phy(port, devnum, regnum, val);
+ }
+ return -1;
+}
+
+int write_mmd_phy(u32 port, u32 devnum, u32 reg, u32 val)
+{
+ switch (soc_info.family) {
+ case RTL8380_FAMILY_ID:
+ return rtl838x_write_mmd_phy(port, devnum, reg, val);
+ case RTL8390_FAMILY_ID:
+ return rtl839x_write_mmd_phy(port, devnum, reg, val);
+ case RTL9300_FAMILY_ID:
+ return rtl930x_write_mmd_phy(port, devnum, reg, val);
+ case RTL9310_FAMILY_ID:
+ return rtl931x_write_mmd_phy(port, devnum, reg, val);
+ }
+ return -1;
+}
+
+static u64 disable_polling(int port)
+{
+ u64 saved_state;
+
+ mutex_lock(&poll_lock);
+
+ switch (soc_info.family) {
+ case RTL8380_FAMILY_ID:
+ saved_state = sw_r32(RTL838X_SMI_POLL_CTRL);
+ sw_w32_mask(BIT(port), 0, RTL838X_SMI_POLL_CTRL);
+ break;
+ case RTL8390_FAMILY_ID:
+ saved_state = sw_r32(RTL839X_SMI_PORT_POLLING_CTRL + 4);
+ saved_state <<= 32;
+ saved_state |= sw_r32(RTL839X_SMI_PORT_POLLING_CTRL);
+ sw_w32_mask(BIT(port % 32), 0,
+ RTL839X_SMI_PORT_POLLING_CTRL + ((port >> 5) << 2));
+ break;
+ case RTL9300_FAMILY_ID:
+ saved_state = sw_r32(RTL930X_SMI_POLL_CTRL);
+ sw_w32_mask(BIT(port), 0, RTL930X_SMI_POLL_CTRL);
+ break;
+ case RTL9310_FAMILY_ID:
+ pr_warn("%s not implemented for RTL931X\n", __func__);
+ break;
+ }
+
+ mutex_unlock(&poll_lock);
+
+ return saved_state;
+}
+
+static int resume_polling(u64 saved_state)
+{
+ mutex_lock(&poll_lock);
+
+ switch (soc_info.family) {
+ case RTL8380_FAMILY_ID:
+ sw_w32(saved_state, RTL838X_SMI_POLL_CTRL);
+ break;
+ case RTL8390_FAMILY_ID:
+ sw_w32(saved_state >> 32, RTL839X_SMI_PORT_POLLING_CTRL + 4);
+ sw_w32(saved_state, RTL839X_SMI_PORT_POLLING_CTRL);
+ break;
+ case RTL9300_FAMILY_ID:
+ sw_w32(saved_state, RTL930X_SMI_POLL_CTRL);
+ break;
+ case RTL9310_FAMILY_ID:
+ pr_warn("%s not implemented for RTL931X\n", __func__);
+ break;
+ }
+
+ mutex_unlock(&poll_lock);
+
+ return 0;
+}
+
static void rtl8380_int_phy_on_off(int mac, bool on)
{
u32 val;
@@ -94,18 +195,6 @@ static void rtl8380_phy_reset(int mac)
write_phy(mac, 0, 0, val | BIT(15));
}
-static void rtl8380_sds_rst(int mac)
-{
- u32 offset = (mac == 24) ? 0 : 0x100;
-
- sw_w32_mask(1 << 11, 0, RTL8380_SDS4_FIB_REG0 + offset);
- sw_w32_mask(0x3, 0, RTL838X_SDS4_REG28 + offset);
- sw_w32_mask(0x3, 0x3, RTL838X_SDS4_REG28 + offset);
- sw_w32_mask(0, 0x1 << 6, RTL838X_SDS4_DUMMY0 + offset);
- sw_w32_mask(0x1 << 6, 0, RTL838X_SDS4_DUMMY0 + offset);
- pr_info("SERDES reset: %d\n", mac);
-}
-
/*
* Reset the SerDes by powering it off and set a new operations mode
* of the SerDes. 0x1f is off. Other modes are
@@ -307,6 +396,7 @@ static int rtl8393_read_status(struct phy_device *phydev)
return err;
}
+
static int rtl8226_read_page(struct phy_device *phydev)
{
return __phy_read(phydev, 0x1f);
@@ -331,20 +421,20 @@ static int rtl8226_read_status(struct phy_device *phydev)
// Link status must be read twice
for (i = 0; i < 2; i++) {
- rtl930x_read_mmd_phy(port, MMD_VEND2, 0xA402, &val);
+ read_mmd_phy(port, MMD_VEND2, 0xA402, &val);
}
phydev->link = val & BIT(2) ? 1 : 0;
if (!phydev->link)
goto out;
// Read duplex status
- ret = rtl930x_read_mmd_phy(port, MMD_VEND2, 0xA434, &val);
+ ret = read_mmd_phy(port, MMD_VEND2, 0xA434, &val);
if (ret)
goto out;
phydev->duplex = !!(val & BIT(3));
// Read speed
- ret = rtl930x_read_mmd_phy(port, MMD_VEND2, 0xA434, &val);
+ ret = read_mmd_phy(port, MMD_VEND2, 0xA434, &val);
switch (val & 0x0630) {
case 0x0000:
phydev->speed = SPEED_10;
@@ -371,7 +461,7 @@ out:
return ret;
}
-static int rtl8266_advertise_aneg(struct phy_device *phydev)
+static int rtl8226_advertise_aneg(struct phy_device *phydev)
{
int ret = 0;
u32 v;
@@ -379,7 +469,7 @@ static int rtl8266_advertise_aneg(struct phy_device *phydev)
pr_info("In %s\n", __func__);
- ret = rtl930x_read_mmd_phy(port, MMD_AN, 16, &v);
+ ret = read_mmd_phy(port, MMD_AN, 16, &v);
if (ret)
goto out;
@@ -388,31 +478,30 @@ static int rtl8266_advertise_aneg(struct phy_device *phydev)
v |= BIT(7); // HD 100M
v |= BIT(8); // FD 100M
- ret = rtl930x_write_mmd_phy(port, MMD_AN, 16, v);
+ ret = write_mmd_phy(port, MMD_AN, 16, v);
// Allow 1GBit
- ret = rtl930x_read_mmd_phy(port, MMD_VEND2, 0xA412, &v);
+ ret = read_mmd_phy(port, MMD_VEND2, 0xA412, &v);
if (ret)
goto out;
v |= BIT(9); // FD 1000M
- ret = rtl930x_write_mmd_phy(port, MMD_VEND2, 0xA412, v);
+ ret = write_mmd_phy(port, MMD_VEND2, 0xA412, v);
if (ret)
goto out;
// Allow 2.5G
- ret = rtl930x_read_mmd_phy(port, MMD_AN, 32, &v);
+ ret = read_mmd_phy(port, MMD_AN, 32, &v);
if (ret)
goto out;
v |= BIT(7);
- ret = rtl930x_write_mmd_phy(port, MMD_AN, 32, v);
+ ret = write_mmd_phy(port, MMD_AN, 32, v);
out:
return ret;
}
-
static int rtl8226_config_aneg(struct phy_device *phydev)
{
int ret = 0;
@@ -421,26 +510,26 @@ static int rtl8226_config_aneg(struct phy_device *phydev)
pr_info("In %s\n", __func__);
if (phydev->autoneg == AUTONEG_ENABLE) {
- ret = rtl8266_advertise_aneg(phydev);
+ ret = rtl8226_advertise_aneg(phydev);
if (ret)
goto out;
// AutoNegotiationEnable
- ret = rtl930x_read_mmd_phy(port, MMD_AN, 0, &v);
+ ret = read_mmd_phy(port, MMD_AN, 0, &v);
if (ret)
goto out;
v |= BIT(12); // Enable AN
- ret = rtl930x_write_mmd_phy(port, MMD_AN, 0, v);
+ ret = write_mmd_phy(port, MMD_AN, 0, v);
if (ret)
goto out;
// RestartAutoNegotiation
- ret = rtl930x_read_mmd_phy(port, MMD_VEND2, 0xA400, &v);
+ ret = read_mmd_phy(port, MMD_VEND2, 0xA400, &v);
if (ret)
goto out;
v |= BIT(9);
- ret = rtl930x_write_mmd_phy(port, MMD_VEND2, 0xA400, v);
+ ret = write_mmd_phy(port, MMD_VEND2, 0xA400, v);
}
pr_info("%s: Ret is already: %d\n", __func__, ret);
@@ -451,6 +540,68 @@ out:
return ret;
}
+static int rtl8226_get_eee(struct phy_device *phydev,
+ struct ethtool_eee *e)
+{
+ u32 val;
+ int addr = phydev->mdio.addr;
+
+ pr_debug("In %s, port %d, was enabled: %d\n", __func__, addr, e->eee_enabled);
+
+ read_mmd_phy(addr, MMD_AN, 60, &val);
+ if (e->eee_enabled) {
+ e->eee_enabled = !!(val & BIT(1));
+ if (!e->eee_enabled) {
+ read_mmd_phy(addr, MMD_AN, 62, &val);
+ e->eee_enabled = !!(val & BIT(0));
+ }
+ }
+ pr_debug("%s: enabled: %d\n", __func__, e->eee_enabled);
+
+ return 0;
+}
+
+static int rtl8226_set_eee(struct phy_device *phydev, struct ethtool_eee *e)
+{
+ int port = phydev->mdio.addr;
+ u64 poll_state;
+ bool an_enabled;
+ u32 val;
+
+ pr_info("In %s, port %d, enabled %d\n", __func__, port, e->eee_enabled);
+
+ poll_state = disable_polling(port);
+
+ // Remember aneg state
+ read_mmd_phy(port, MMD_AN, 0, &val);
+ an_enabled = !!(val & BIT(12));
+
+ // Setup 100/1000MBit
+ read_mmd_phy(port, MMD_AN, 60, &val);
+ if (e->eee_enabled)
+ val |= 0x6;
+ else
+ val &= 0x6;
+ write_mmd_phy(port, MMD_AN, 60, val);
+
+ // Setup 2.5GBit
+ read_mmd_phy(port, MMD_AN, 62, &val);
+ if (e->eee_enabled)
+ val |= 0x1;
+ else
+ val &= 0x1;
+ write_mmd_phy(port, MMD_AN, 62, val);
+
+ // RestartAutoNegotiation
+ read_mmd_phy(port, MMD_VEND2, 0xA400, &val);
+ val |= BIT(9);
+ write_mmd_phy(port, MMD_VEND2, 0xA400, val);
+
+ resume_polling(poll_state);
+
+ return 0;
+}
+
static struct fw_header *rtl838x_request_fw(struct phy_device *phydev,
const struct firmware *fw,
const char *name)
@@ -750,79 +901,6 @@ static int rtl8218b_ext_match_phy_device(struct phy_device *phydev)
return phydev->phy_id == PHY_ID_RTL8218B_E;
}
-/*
- * Read an mmd register of the PHY
- */
-static int rtl83xx_read_mmd_phy(u32 port, u32 addr, u32 reg, u32 *val)
-{
- u32 v;
-
- mutex_lock(&smi_lock);
-
- if (rtl838x_smi_wait_op(10000))
- goto timeout;
-
- sw_w32(1 << port, RTL838X_SMI_ACCESS_PHY_CTRL_0);
- mdelay(10);
-
- sw_w32_mask(0xffff0000, port << 16, RTL838X_SMI_ACCESS_PHY_CTRL_2);
-
- v = addr << 16 | reg;
- sw_w32(v, RTL838X_SMI_ACCESS_PHY_CTRL_3);
-
- /* mmd-access | read | cmd-start */
- v = 1 << 1 | 0 << 2 | 1;
- sw_w32(v, RTL838X_SMI_ACCESS_PHY_CTRL_1);
-
- if (rtl838x_smi_wait_op(10000))
- goto timeout;
-
- *val = sw_r32(RTL838X_SMI_ACCESS_PHY_CTRL_2) & 0xffff;
-
- mutex_unlock(&smi_lock);
- return 0;
-
-timeout:
- mutex_unlock(&smi_lock);
- return -ETIMEDOUT;
-}
-
-/*
- * Write to an mmd register of the PHY
- */
-static int rtl838x_write_mmd_phy(u32 port, u32 addr, u32 reg, u32 val)
-{
- u32 v;
-
- pr_debug("MMD write: port %d, dev %d, reg %d, val %x\n", port, addr, reg, val);
- val &= 0xffff;
- mutex_lock(&smi_lock);
-
- if (rtl838x_smi_wait_op(10000))
- goto timeout;
-
- sw_w32(1 << port, RTL838X_SMI_ACCESS_PHY_CTRL_0);
- mdelay(10);
-
- sw_w32_mask(0xffff0000, val << 16, RTL838X_SMI_ACCESS_PHY_CTRL_2);
-
- sw_w32_mask(0x1f << 16, addr << 16, RTL838X_SMI_ACCESS_PHY_CTRL_3);
- sw_w32_mask(0xffff, reg, RTL838X_SMI_ACCESS_PHY_CTRL_3);
- /* mmd-access | write | cmd-start */
- v = 1 << 1 | 1 << 2 | 1;
- sw_w32(v, RTL838X_SMI_ACCESS_PHY_CTRL_1);
-
- if (rtl838x_smi_wait_op(10000))
- goto timeout;
-
- mutex_unlock(&smi_lock);
- return 0;
-
-timeout:
- mutex_unlock(&smi_lock);
- return -ETIMEDOUT;
-}
-
static int rtl8218b_read_mmd(struct phy_device *phydev,
int devnum, u16 regnum)
{
@@ -830,7 +908,7 @@ static int rtl8218b_read_mmd(struct phy_device *phydev,
u32 val;
int addr = phydev->mdio.addr;
- ret = rtl83xx_read_mmd_phy(addr, devnum, regnum, &val);
+ ret = read_mmd_phy(addr, devnum, regnum, &val);
if (ret)
return ret;
return val;
@@ -850,8 +928,7 @@ static int rtl8226_read_mmd(struct phy_device *phydev, int devnum, u16 regnum)
int err;
u32 val;
- err = rtl930x_read_mmd_phy(port, devnum, regnum, &val);
-
+ err = read_mmd_phy(port, devnum, regnum, &val);
if (err)
return err;
return val;
@@ -861,7 +938,7 @@ static int rtl8226_write_mmd(struct phy_device *phydev, int devnum, u16 regnum,
{
int port = phydev->mdio.addr; // the SoC translates port addresses to PHY addr
- return rtl930x_write_mmd_phy(port, devnum, regnum, val);
+ return write_mmd_phy(port, devnum, regnum, val);
}
static void rtl8380_rtl8214fc_media_set(int mac, bool set_fibre)
@@ -957,90 +1034,46 @@ static int rtl8214fc_get_port(struct phy_device *phydev)
return PORT_MII;
}
-static void rtl8218b_eee_set_u_boot(int port, bool enable)
-{
- u32 val;
- bool an_enabled;
-
- /* Set GPHY page to copper */
- write_phy(port, 0, 30, 0x0001);
- read_phy(port, 0, 0, &val);
- an_enabled = val & (1 << 12);
-
- if (enable) {
- /* 100/1000M EEE Capability */
- write_phy(port, 0, 13, 0x0007);
- write_phy(port, 0, 14, 0x003C);
- write_phy(port, 0, 13, 0x4007);
- write_phy(port, 0, 14, 0x0006);
-
- read_phy(port, 0x0A43, 25, &val);
- val |= 1 << 4;
- write_phy(port, 0x0A43, 25, val);
- } else {
- /* 100/1000M EEE Capability */
- write_phy(port, 0, 13, 0x0007);
- write_phy(port, 0, 14, 0x003C);
- write_phy(port, 0, 13, 0x0007);
- write_phy(port, 0, 14, 0x0000);
-
- read_phy(port, 0x0A43, 25, &val);
- val &= ~(1 << 4);
- write_phy(port, 0x0A43, 25, val);
- }
-
- /* Restart AN if enabled */
- if (an_enabled) {
- read_phy(port, 0, 0, &val);
- val |= (1 << 12) | (1 << 9);
- write_phy(port, 0, 0, val);
- }
-
- /* GPHY page back to auto*/
- write_phy(port, 0xa42, 29, 0);
-}
-
-// TODO: unused
-void rtl8380_rtl8218b_eee_set(int port, bool enable)
+/*
+ * Enable EEE on the RTL8218B PHYs
+ * The method used is not the preferred way (which would be based on the MAC-EEE state,
+ * but the only way that works since the kernel first enables EEE in the MAC
+ * and then sets up the PHY. The MAC-based approach would require the oppsite.
+ */
+void rtl8218d_eee_set(int port, bool enable)
{
u32 val;
bool an_enabled;
pr_debug("In %s %d, enable %d\n", __func__, port, enable);
/* Set GPHY page to copper */
- write_phy(port, 0xa42, 29, 0x0001);
+ write_phy(port, 0xa42, 30, 0x0001);
read_phy(port, 0, 0, &val);
- an_enabled = val & (1 << 12);
+ an_enabled = val & BIT(12);
- /* MAC based EEE */
- read_phy(port, 0xa43, 25, &val);
- val &= ~(1 << 5);
- write_phy(port, 0xa43, 25, val);
-
- /* 100M / 1000M EEE */
- if (enable)
- rtl838x_write_mmd_phy(port, 7, 60, 0x6);
- else
- rtl838x_write_mmd_phy(port, 7, 60, 0);
+ /* Enable 100M (bit 1) / 1000M (bit 2) EEE */
+ read_mmd_phy(port, 7, 60, &val);
+ val |= BIT(2) | BIT(1);
+ write_mmd_phy(port, 7, 60, enable ? 0x6 : 0);
/* 500M EEE ability */
read_phy(port, 0xa42, 20, &val);
if (enable)
- val |= 1 << 7;
+ val |= BIT(7);
else
- val &= ~(1 << 7);
+ val &= ~BIT(7);
write_phy(port, 0xa42, 20, val);
/* Restart AN if enabled */
if (an_enabled) {
read_phy(port, 0, 0, &val);
- val |= (1 << 12) | (1 << 9);
+ val |= BIT(9);
write_phy(port, 0, 0, val);
}
/* GPHY page back to auto*/
- write_phy(port, 0xa42, 29, 0);
+ write_phy(port, 0xa42, 30, 0);
}
static int rtl8218b_get_eee(struct phy_device *phydev,
@@ -1049,16 +1082,21 @@ static int rtl8218b_get_eee(struct phy_device *phydev,
u32 val;
int addr = phydev->mdio.addr;
- pr_debug("In %s, port %d\n", __func__, addr);
+ pr_debug("In %s, port %d, was enabled: %d\n", __func__, addr, e->eee_enabled);
/* Set GPHY page to copper */
write_phy(addr, 0xa42, 29, 0x0001);
- rtl83xx_read_mmd_phy(addr, 7, 60, &val);
- if (e->eee_enabled && (!!(val & (1 << 7))))
- e->eee_enabled = !!(val & (1 << 7));
- else
- e->eee_enabled = 0;
+ read_phy(addr, 7, 60, &val);
+ if (e->eee_enabled) {
+ // Verify vs MAC-based EEE
+ e->eee_enabled = !!(val & BIT(7));
+ if (!e->eee_enabled) {
+ read_phy(addr, 0x0A43, 25, &val);
+ e->eee_enabled = !!(val & BIT(4));
+ }
+ }
+ pr_debug("%s: enabled: %d\n", __func__, e->eee_enabled);
/* GPHY page to auto */
write_phy(addr, 0xa42, 29, 0x0000);
@@ -1066,49 +1104,24 @@ static int rtl8218b_get_eee(struct phy_device *phydev,
return 0;
}
-// TODO: unused
-void rtl8380_rtl8218b_green_set(int mac, bool enable)
-{
- u32 val;
-
- /* Set GPHY page to copper */
- write_phy(mac, 0xa42, 29, 0x0001);
-
- write_phy(mac, 0, 27, 0x8011);
- read_phy(mac, 0, 28, &val);
- if (enable) {
- val |= 1 << 9;
- write_phy(mac, 0, 27, 0x8011);
- write_phy(mac, 0, 28, val);
- } else {
- val &= ~(1 << 9);
- write_phy(mac, 0, 27, 0x8011);
- write_phy(mac, 0, 28, val);
- }
-
- /* GPHY page to auto */
- write_phy(mac, 0xa42, 29, 0x0000);
-}
-
-// TODO: unused
-int rtl8380_rtl8214fc_get_green(struct phy_device *phydev, struct ethtool_eee *e)
+static int rtl8218d_get_eee(struct phy_device *phydev,
+ struct ethtool_eee *e)
{
u32 val;
int addr = phydev->mdio.addr;
- pr_debug("In %s %d\n", __func__, addr);
+ pr_debug("In %s, port %d, was enabled: %d\n", __func__, addr, e->eee_enabled);
+
/* Set GPHY page to copper */
- write_phy(addr, 0xa42, 29, 0x0001);
+ write_phy(addr, 0xa42, 30, 0x0001);
- write_phy(addr, 0, 27, 0x8011);
- read_phy(addr, 0, 28, &val);
- if (e->eee_enabled && (!!(val & (1 << 9))))
- e->eee_enabled = !!(val & (1 << 9));
- else
- e->eee_enabled = 0;
+ read_phy(addr, 7, 60, &val);
+ if (e->eee_enabled)
+ e->eee_enabled = !!(val & BIT(7));
+ pr_debug("%s: enabled: %d\n", __func__, e->eee_enabled);
/* GPHY page to auto */
- write_phy(addr, 0xa42, 29, 0x0000);
+ write_phy(addr, 0xa42, 30, 0x0000);
return 0;
}
@@ -1116,20 +1129,56 @@ int rtl8380_rtl8214fc_get_green(struct phy_device *phydev, struct ethtool_eee *e
static int rtl8214fc_set_eee(struct phy_device *phydev,
struct ethtool_eee *e)
{
- u32 pollMask;
- int addr = phydev->mdio.addr;
+ u32 poll_state;
+ int port = phydev->mdio.addr;
+ bool an_enabled;
+ u32 val;
- pr_debug("In %s port %d, enabled %d\n", __func__, addr, e->eee_enabled);
+ pr_debug("In %s port %d, enabled %d\n", __func__, port, e->eee_enabled);
- if (rtl8380_rtl8214fc_media_is_fibre(addr)) {
- netdev_err(phydev->attached_dev, "Port %d configured for FIBRE", addr);
+ if (rtl8380_rtl8214fc_media_is_fibre(port)) {
+ netdev_err(phydev->attached_dev, "Port %d configured for FIBRE", port);
return -ENOTSUPP;
}
- pollMask = sw_r32(RTL838X_SMI_POLL_CTRL);
- sw_w32(0, RTL838X_SMI_POLL_CTRL);
- rtl8218b_eee_set_u_boot(addr, (bool) e->eee_enabled);
- sw_w32(pollMask, RTL838X_SMI_POLL_CTRL);
+ poll_state = disable_polling(port);
+
+ /* Set GPHY page to copper */
+ write_phy(port, 0xa42, 29, 0x0001);
+
+ // Get auto-negotiation status
+ read_phy(port, 0, 0, &val);
+ an_enabled = val & BIT(12);
+
+ pr_info("%s: aneg: %d\n", __func__, an_enabled);
+ read_phy(port, 0x0A43, 25, &val);
+ val &= ~BIT(5); // Use MAC-based EEE
+ write_phy(port, 0x0A43, 25, val);
+
+ /* Enable 100M (bit 1) / 1000M (bit 2) EEE */
+ write_phy(port, 7, 60, e->eee_enabled ? 0x6 : 0);
+
+ /* 500M EEE ability */
+ read_phy(port, 0xa42, 20, &val);
+ if (e->eee_enabled)
+ val |= BIT(7);
+ else
+ val &= ~BIT(7);
+ write_phy(port, 0xa42, 20, val);
+
+ /* Restart AN if enabled */
+ if (an_enabled) {
+ pr_info("%s: doing aneg\n", __func__);
+ read_phy(port, 0, 0, &val);
+ val |= BIT(9);
+ write_phy(port, 0, 0, val);
+ }
+
+ /* GPHY page back to auto*/
+ write_phy(port, 0xa42, 29, 0);
+
+ resume_polling(poll_state);
+
return 0;
}
@@ -1147,18 +1196,72 @@ static int rtl8214fc_get_eee(struct phy_device *phydev,
return rtl8218b_get_eee(phydev, e);
}
-static int rtl8218b_set_eee(struct phy_device *phydev,
- struct ethtool_eee *e)
+static int rtl8218b_set_eee(struct phy_device *phydev, struct ethtool_eee *e)
+{
+ int port = phydev->mdio.addr;
+ u64 poll_state;
+ u32 val;
+ bool an_enabled;
+
+ pr_info("In %s, port %d, enabled %d\n", __func__, port, e->eee_enabled);
+
+ poll_state = disable_polling(port);
+
+ /* Set GPHY page to copper */
+ write_phy(port, 0, 30, 0x0001);
+ read_phy(port, 0, 0, &val);
+ an_enabled = val & BIT(12);
+
+ if (e->eee_enabled) {
+ /* 100/1000M EEE Capability */
+ write_phy(port, 0, 13, 0x0007);
+ write_phy(port, 0, 14, 0x003C);
+ write_phy(port, 0, 13, 0x4007);
+ write_phy(port, 0, 14, 0x0006);
+
+ read_phy(port, 0x0A43, 25, &val);
+ val |= BIT(4);
+ write_phy(port, 0x0A43, 25, val);
+ } else {
+ /* 100/1000M EEE Capability */
+ write_phy(port, 0, 13, 0x0007);
+ write_phy(port, 0, 14, 0x003C);
+ write_phy(port, 0, 13, 0x0007);
+ write_phy(port, 0, 14, 0x0000);
+
+ read_phy(port, 0x0A43, 25, &val);
+ val &= ~BIT(4);
+ write_phy(port, 0x0A43, 25, val);
+ }
+
+ /* Restart AN if enabled */
+ if (an_enabled) {
+ read_phy(port, 0, 0, &val);
+ val |= BIT(9);
+ write_phy(port, 0, 0, val);
+ }
+
+ /* GPHY page back to auto*/
+ write_phy(port, 0xa42, 30, 0);
+
+ pr_info("%s done\n", __func__);
+ resume_polling(poll_state);
+
+ return 0;
+}
+
+static int rtl8218d_set_eee(struct phy_device *phydev, struct ethtool_eee *e)
{
- u32 pollMask;
int addr = phydev->mdio.addr;
+ u64 poll_state;
+
+ pr_info("In %s, port %d, enabled %d\n", __func__, addr, e->eee_enabled);
- pr_debug("In %s, port %d, enabled %d\n", __func__, addr, e->eee_enabled);
+ poll_state = disable_polling(addr);
- pollMask = sw_r32(RTL838X_SMI_POLL_CTRL);
- sw_w32(0, RTL838X_SMI_POLL_CTRL);
- rtl8218b_eee_set_u_boot(addr, (bool) e->eee_enabled);
- sw_w32(pollMask, RTL838X_SMI_POLL_CTRL);
+ rtl8218d_eee_set(addr, (bool) e->eee_enabled);
+
+ resume_polling(poll_state);
return 0;
}
@@ -1296,7 +1399,7 @@ static int rtl8380_configure_rtl8214fc(struct phy_device *phydev)
for (i = 0; i < 4; i++) {
for (l = 0; l < 100; l++) {
read_phy(mac + i, 0xb80, 0x10, &val);
- if (val & 0x40)
+ if (val & 0x80)
break;
}
if (l >= 100) {
@@ -1791,7 +1894,10 @@ static struct phy_driver rtl83xx_phy_driver[] = {
.suspend = genphy_suspend,
.resume = genphy_resume,
.set_loopback = genphy_loopback,
- }, {
+ .set_eee = rtl8218d_set_eee,
+ .get_eee = rtl8218d_get_eee,
+ },
+ {
PHY_ID_MATCH_MODEL(PHY_ID_RTL8226),
.name = "REALTEK RTL8226",
.features = PHY_GBIT_FEATURES,
@@ -1805,6 +1911,8 @@ static struct phy_driver rtl83xx_phy_driver[] = {
.write_page = rtl8226_write_page,
.read_status = rtl8226_read_status,
.config_aneg = rtl8226_config_aneg,
+ .set_eee = rtl8226_set_eee,
+ .get_eee = rtl8226_get_eee,
},
{
PHY_ID_MATCH_MODEL(PHY_ID_RTL8218B_I),
diff --git a/target/linux/realtek/image/Makefile b/target/linux/realtek/image/Makefile
index 424726c8a9..a4b2ea892c 100644
--- a/target/linux/realtek/image/Makefile
+++ b/target/linux/realtek/image/Makefile
@@ -118,4 +118,13 @@ define Device/zyxel_gs1900-8hp-v2
endef
TARGET_DEVICES += zyxel_gs1900-8hp-v2
+define Device/edgecore_ecs4100-12ph
+ SOC := rtl8392
+ IMAGE_SIZE := 14336k
+ DEVICE_VENDOR := Edgecore
+ DEVICE_MODEL := ECS4100-12PH
+ DEVICE_PACKAGES += lua-rs232
+endef
+TARGET_DEVICES += edgecore_ecs4100-12ph
+
$(eval $(call BuildImage))
diff --git a/target/linux/realtek/patches-5.4/301-gpio-add-rtl838x-driver.patch b/target/linux/realtek/patches-5.4/301-gpio-add-rtl838x-driver.patch
index 4f5901d87f..e4bb9d90d2 100644
--- a/target/linux/realtek/patches-5.4/301-gpio-add-rtl838x-driver.patch
+++ b/target/linux/realtek/patches-5.4/301-gpio-add-rtl838x-driver.patch
@@ -26,7 +26,7 @@
obj-$(CONFIG_GPIO_RDC321X) += gpio-rdc321x.o
obj-$(CONFIG_GPIO_REG) += gpio-reg.o
+obj-$(CONFIG_GPIO_RTL8231) += gpio-rtl8231.o
-+obj-$(CONFIG_GPIO_RTL838X) += gpio-rtl838x.o
++#obj-$(CONFIG_GPIO_RTL838X) += gpio-rtl838x.o
obj-$(CONFIG_ARCH_SA1100) += gpio-sa1100.o
obj-$(CONFIG_GPIO_SAMA5D2_PIOBU) += gpio-sama5d2-piobu.o
obj-$(CONFIG_GPIO_SCH311X) += gpio-sch311x.o
diff --git a/target/linux/realtek/patches-5.4/500-gpio-Add-Realtek-Otto-GPIO-support.patch b/target/linux/realtek/patches-5.4/500-gpio-Add-Realtek-Otto-GPIO-support.patch
new file mode 100644
index 0000000000..f6964c6c5f
--- /dev/null
+++ b/target/linux/realtek/patches-5.4/500-gpio-Add-Realtek-Otto-GPIO-support.patch
@@ -0,0 +1,412 @@
+From patchwork Tue Mar 30 17:48:43 2021
+Content-Type: text/plain; charset="utf-8"
+MIME-Version: 1.0
+Content-Transfer-Encoding: 7bit
+Subject: [v6,2/2] gpio: Add Realtek Otto GPIO support
+X-Patchwork-Submitter: Sander Vanheule <sander@svanheule.net>
+X-Patchwork-Id: 411993
+Message-Id: <2d00064530e88fbef4fff2e759a66bdda261cca7.1617126277.git.sander@svanheule.net>
+To: devicetree@vger.kernel.org, linux-gpio@vger.kernel.org
+Cc: andy.shevchenko@gmail.com, bert@biot.com,
+ bgolaszewski@baylibre.com, linus.walleij@linaro.org,
+ linux-kernel@vger.kernel.org, maz@kernel.org, robh+dt@kernel.org,
+ Sander Vanheule <sander@svanheule.net>
+Date: Tue, 30 Mar 2021 19:48:43 +0200
+From: Sander Vanheule <sander@svanheule.net>
+List-Id: <linux-gpio.vger.kernel.org>
+
+Realtek MIPS SoCs (platform name Otto) have GPIO controllers with up to
+64 GPIOs, divided over two banks. Each bank has a set of registers for
+32 GPIOs, with support for edge-triggered interrupts.
+
+Each GPIO bank consists of four 8-bit GPIO ports (ABCD and EFGH). Most
+registers pack one bit per GPIO, except for the IMR register, which
+packs two bits per GPIO (AB-CD).
+
+Although the byte order is currently assumed to have port A..D at offset
+0x0..0x3, this has been observed to be reversed on other, Lexra-based,
+SoCs (e.g. RTL8196E/97D/97F).
+
+Interrupt support is disabled for the fallback devicetree-compatible
+'realtek,otto-gpio'. This allows for quick support of GPIO banks in
+which the byte order would be unknown. In this case, the port ordering
+in the IMR registers may not match the reversed order in the other
+registers (DCBA, and BA-DC or DC-BA).
+
+Signed-off-by: Sander Vanheule <sander@svanheule.net>
+Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
+Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>
+---
+ drivers/gpio/Kconfig | 13 ++
+ drivers/gpio/Makefile | 1 +
+ drivers/gpio/gpio-realtek-otto.c | 325 +++++++++++++++++++++++++++++++
+ 3 files changed, 339 insertions(+)
+ create mode 100644 drivers/gpio/gpio-realtek-otto.c
+
+diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
+index e3607ec4c2e8..6fb13d6507db 100644
+--- a/drivers/gpio/Kconfig
++++ b/drivers/gpio/Kconfig
+@@ -502,6 +502,19 @@ config GPIO_RDA
+ help
+ Say Y here to support RDA Micro GPIO controller.
+
++config GPIO_REALTEK_OTTO
++ tristate "Realtek Otto GPIO support"
++ depends on RTL838X
++ default RTL838X
++ select GPIO_GENERIC
++ select GPIOLIB_IRQCHIP
++ help
++ The GPIO controller on the Otto MIPS platform supports up to two
++ banks of 32 GPIOs, with edge triggered interrupts. The 32 GPIOs
++ are grouped in four 8-bit wide ports.
++
++ When built as a module, the module will be called realtek_otto_gpio.
++
+ config GPIO_REG
+ bool
+ help
+diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
+index c58a90a3c3b1..8ace5934e3c3 100644
+--- a/drivers/gpio/Makefile
++++ b/drivers/gpio/Makefile
+@@ -124,6 +124,7 @@ obj-$(CONFIG_GPIO_RC5T583) += gpio-rc5t583.o
+ obj-$(CONFIG_GPIO_RCAR) += gpio-rcar.o
+ obj-$(CONFIG_GPIO_RDA) += gpio-rda.o
+ obj-$(CONFIG_GPIO_RDC321X) += gpio-rdc321x.o
++obj-$(CONFIG_GPIO_REALTEK_OTTO) += gpio-realtek-otto.o
+ obj-$(CONFIG_GPIO_REG) += gpio-reg.o
+ obj-$(CONFIG_ARCH_SA1100) += gpio-sa1100.o
+ obj-$(CONFIG_GPIO_SAMA5D2_PIOBU) += gpio-sama5d2-piobu.o
+diff --git a/drivers/gpio/gpio-realtek-otto.c b/drivers/gpio/gpio-realtek-otto.c
+new file mode 100644
+index 000000000000..cb64fb5a51aa
+--- /dev/null
++++ b/drivers/gpio/gpio-realtek-otto.c
+@@ -0,0 +1,325 @@
++// SPDX-License-Identifier: GPL-2.0-only
++
++#include <linux/gpio/driver.h>
++#include <linux/irq.h>
++//#include <linux/minmax.h>
++#include <linux/mod_devicetable.h>
++#include <linux/module.h>
++#include <linux/platform_device.h>
++#include <linux/property.h>
++
++/*
++ * Total register block size is 0x1C for one bank of four ports (A, B, C, D).
++ * An optional second bank, with ports E, F, G, and H, may be present, starting
++ * at register offset 0x1C.
++ */
++
++/*
++ * Pin select: (0) "normal", (1) "dedicate peripheral"
++ * Not used on RTL8380/RTL8390, peripheral selection is managed by control bits
++ * in the peripheral registers.
++ */
++#define REALTEK_GPIO_REG_CNR 0x00
++/* Clear bit (0) for input, set bit (1) for output */
++#define REALTEK_GPIO_REG_DIR 0x08
++#define REALTEK_GPIO_REG_DATA 0x0C
++/* Read bit for IRQ status, write 1 to clear IRQ */
++#define REALTEK_GPIO_REG_ISR 0x10
++/* Two bits per GPIO in IMR registers */
++#define REALTEK_GPIO_REG_IMR 0x14
++#define REALTEK_GPIO_REG_IMR_AB 0x14
++#define REALTEK_GPIO_REG_IMR_CD 0x18
++#define REALTEK_GPIO_IMR_LINE_MASK GENMASK(1, 0)
++#define REALTEK_GPIO_IRQ_EDGE_FALLING 1
++#define REALTEK_GPIO_IRQ_EDGE_RISING 2
++#define REALTEK_GPIO_IRQ_EDGE_BOTH 3
++
++#define REALTEK_GPIO_MAX 32
++#define REALTEK_GPIO_PORTS_PER_BANK 4
++
++/**
++ * realtek_gpio_ctrl - Realtek Otto GPIO driver data
++ *
++ * @gc: Associated gpio_chip instance
++ * @base: Base address of the register block for a GPIO bank
++ * @lock: Lock for accessing the IRQ registers and values
++ * @intr_mask: Mask for interrupts lines
++ * @intr_type: Interrupt type selection
++ *
++ * Because the interrupt mask register (IMR) combines the function of IRQ type
++ * selection and masking, two extra values are stored. @intr_mask is used to
++ * mask/unmask the interrupts for a GPIO port, and @intr_type is used to store
++ * the selected interrupt types. The logical AND of these values is written to
++ * IMR on changes.
++ */
++struct realtek_gpio_ctrl {
++ struct gpio_chip gc;
++ void __iomem *base;
++ raw_spinlock_t lock;
++ u16 intr_mask[REALTEK_GPIO_PORTS_PER_BANK];
++ u16 intr_type[REALTEK_GPIO_PORTS_PER_BANK];
++};
++
++/* Expand with more flags as devices with other quirks are added */
++enum realtek_gpio_flags {
++ /*
++ * Allow disabling interrupts, for cases where the port order is
++ * unknown. This may result in a port mismatch between ISR and IMR.
++ * An interrupt would appear to come from a different line than the
++ * line the IRQ handler was assigned to, causing uncaught interrupts.
++ */
++ GPIO_INTERRUPTS_DISABLED = BIT(0),
++};
++
++static struct realtek_gpio_ctrl *irq_data_to_ctrl(struct irq_data *data)
++{
++ struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
++
++ return container_of(gc, struct realtek_gpio_ctrl, gc);
++}
++
++/*
++ * Normal port order register access
++ *
++ * Port information is stored with the first port at offset 0, followed by the
++ * second, etc. Most registers store one bit per GPIO and use a u8 value per
++ * port. The two interrupt mask registers store two bits per GPIO, so use u16
++ * values.
++ */
++static void realtek_gpio_write_imr(struct realtek_gpio_ctrl *ctrl,
++ unsigned int port, u16 irq_type, u16 irq_mask)
++{
++ iowrite16(irq_type & irq_mask, ctrl->base + REALTEK_GPIO_REG_IMR + 2 * port);
++}
++
++static void realtek_gpio_clear_isr(struct realtek_gpio_ctrl *ctrl,
++ unsigned int port, u8 mask)
++{
++ iowrite8(mask, ctrl->base + REALTEK_GPIO_REG_ISR + port);
++}
++
++static u8 realtek_gpio_read_isr(struct realtek_gpio_ctrl *ctrl, unsigned int port)
++{
++ return ioread8(ctrl->base + REALTEK_GPIO_REG_ISR + port);
++}
++
++/* Set the rising and falling edge mask bits for a GPIO port pin */
++static u16 realtek_gpio_imr_bits(unsigned int pin, u16 value)
++{
++ return (value & REALTEK_GPIO_IMR_LINE_MASK) << 2 * pin;
++}
++
++static void realtek_gpio_irq_ack(struct irq_data *data)
++{
++ struct realtek_gpio_ctrl *ctrl = irq_data_to_ctrl(data);
++ irq_hw_number_t line = irqd_to_hwirq(data);
++ unsigned int port = line / 8;
++ unsigned int port_pin = line % 8;
++
++ realtek_gpio_clear_isr(ctrl, port, BIT(port_pin));
++}
++
++static void realtek_gpio_irq_unmask(struct irq_data *data)
++{
++ struct realtek_gpio_ctrl *ctrl = irq_data_to_ctrl(data);
++ unsigned int line = irqd_to_hwirq(data);
++ unsigned int port = line / 8;
++ unsigned int port_pin = line % 8;
++ unsigned long flags;
++ u16 m;
++
++ raw_spin_lock_irqsave(&ctrl->lock, flags);
++ m = ctrl->intr_mask[port];
++ m |= realtek_gpio_imr_bits(port_pin, REALTEK_GPIO_IMR_LINE_MASK);
++ ctrl->intr_mask[port] = m;
++ realtek_gpio_write_imr(ctrl, port, ctrl->intr_type[port], m);
++ raw_spin_unlock_irqrestore(&ctrl->lock, flags);
++}
++
++static void realtek_gpio_irq_mask(struct irq_data *data)
++{
++ struct realtek_gpio_ctrl *ctrl = irq_data_to_ctrl(data);
++ unsigned int line = irqd_to_hwirq(data);
++ unsigned int port = line / 8;
++ unsigned int port_pin = line % 8;
++ unsigned long flags;
++ u16 m;
++
++ raw_spin_lock_irqsave(&ctrl->lock, flags);
++ m = ctrl->intr_mask[port];
++ m &= ~realtek_gpio_imr_bits(port_pin, REALTEK_GPIO_IMR_LINE_MASK);
++ ctrl->intr_mask[port] = m;
++ realtek_gpio_write_imr(ctrl, port, ctrl->intr_type[port], m);
++ raw_spin_unlock_irqrestore(&ctrl->lock, flags);
++}
++
++static int realtek_gpio_irq_set_type(struct irq_data *data, unsigned int flow_type)
++{
++ struct realtek_gpio_ctrl *ctrl = irq_data_to_ctrl(data);
++ unsigned int line = irqd_to_hwirq(data);
++ unsigned int port = line / 8;
++ unsigned int port_pin = line % 8;
++ unsigned long flags;
++ u16 type, t;
++
++ switch (flow_type & IRQ_TYPE_SENSE_MASK) {
++ case IRQ_TYPE_EDGE_FALLING:
++ type = REALTEK_GPIO_IRQ_EDGE_FALLING;
++ break;
++ case IRQ_TYPE_EDGE_RISING:
++ type = REALTEK_GPIO_IRQ_EDGE_RISING;
++ break;
++ case IRQ_TYPE_EDGE_BOTH:
++ type = REALTEK_GPIO_IRQ_EDGE_BOTH;
++ break;
++ default:
++ return -EINVAL;
++ }
++
++ irq_set_handler_locked(data, handle_edge_irq);
++
++ raw_spin_lock_irqsave(&ctrl->lock, flags);
++ t = ctrl->intr_type[port];
++ t &= ~realtek_gpio_imr_bits(port_pin, REALTEK_GPIO_IMR_LINE_MASK);
++ t |= realtek_gpio_imr_bits(port_pin, type);
++ ctrl->intr_type[port] = t;
++ realtek_gpio_write_imr(ctrl, port, t, ctrl->intr_mask[port]);
++ raw_spin_unlock_irqrestore(&ctrl->lock, flags);
++
++ return 0;
++}
++
++static void realtek_gpio_irq_handler(struct irq_desc *desc)
++{
++ struct gpio_chip *gc = irq_desc_get_handler_data(desc);
++ struct realtek_gpio_ctrl *ctrl = gpiochip_get_data(gc);
++ struct irq_chip *irq_chip = irq_desc_get_chip(desc);
++ unsigned int lines_done;
++ unsigned int port_pin_count;
++ unsigned int irq;
++ unsigned long status;
++ int offset;
++
++ chained_irq_enter(irq_chip, desc);
++
++ for (lines_done = 0; lines_done < gc->ngpio; lines_done += 8) {
++ status = realtek_gpio_read_isr(ctrl, lines_done / 8);
++ port_pin_count = min(gc->ngpio - lines_done, 8U);
++ for_each_set_bit(offset, &status, port_pin_count) {
++ irq = irq_find_mapping(gc->irq.domain, offset);
++ generic_handle_irq(irq);
++ }
++ }
++
++ chained_irq_exit(irq_chip, desc);
++}
++
++static int realtek_gpio_irq_init(struct gpio_chip *gc)
++{
++ struct realtek_gpio_ctrl *ctrl = gpiochip_get_data(gc);
++ unsigned int port;
++
++ for (port = 0; (port * 8) < gc->ngpio; port++) {
++ realtek_gpio_write_imr(ctrl, port, 0, 0);
++ realtek_gpio_clear_isr(ctrl, port, GENMASK(7, 0));
++ }
++
++ return 0;
++}
++
++static struct irq_chip realtek_gpio_irq_chip = {
++ .name = "realtek-otto-gpio",
++ .irq_ack = realtek_gpio_irq_ack,
++ .irq_mask = realtek_gpio_irq_mask,
++ .irq_unmask = realtek_gpio_irq_unmask,
++ .irq_set_type = realtek_gpio_irq_set_type,
++};
++
++static const struct of_device_id realtek_gpio_of_match[] = {
++ {
++ .compatible = "realtek,otto-gpio",
++ .data = (void *)GPIO_INTERRUPTS_DISABLED,
++ },
++ {
++ .compatible = "realtek,rtl8380-gpio",
++ },
++ {
++ .compatible = "realtek,rtl8390-gpio",
++ },
++ {}
++};
++MODULE_DEVICE_TABLE(of, realtek_gpio_of_match);
++
++static int realtek_gpio_probe(struct platform_device *pdev)
++{
++ struct device *dev = &pdev->dev;
++ unsigned int dev_flags;
++ struct gpio_irq_chip *girq;
++ struct realtek_gpio_ctrl *ctrl;
++ u32 ngpios;
++ int err, irq;
++
++ ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL);
++ if (!ctrl)
++ return -ENOMEM;
++
++ dev_flags = (unsigned int) device_get_match_data(dev);
++
++ ngpios = REALTEK_GPIO_MAX;
++ device_property_read_u32(dev, "ngpios", &ngpios);
++
++ if (ngpios > REALTEK_GPIO_MAX) {
++ dev_err(&pdev->dev, "invalid ngpios (max. %d)\n",
++ REALTEK_GPIO_MAX);
++ return -EINVAL;
++ }
++
++ ctrl->base = devm_platform_ioremap_resource(pdev, 0);
++ if (IS_ERR(ctrl->base))
++ return PTR_ERR(ctrl->base);
++
++ raw_spin_lock_init(&ctrl->lock);
++
++ err = bgpio_init(&ctrl->gc, dev, 4,
++ ctrl->base + REALTEK_GPIO_REG_DATA, NULL, NULL,
++ ctrl->base + REALTEK_GPIO_REG_DIR, NULL,
++ BGPIOF_BIG_ENDIAN_BYTE_ORDER);
++ if (err) {
++ dev_err(dev, "unable to init generic GPIO");
++ return err;
++ }
++
++ ctrl->gc.ngpio = ngpios;
++ ctrl->gc.owner = THIS_MODULE;
++
++ irq = platform_get_irq_optional(pdev, 0);
++ if (!(dev_flags & GPIO_INTERRUPTS_DISABLED) && irq > 0) {
++ girq = &ctrl->gc.irq;
++ girq->chip = &realtek_gpio_irq_chip;
++ girq->default_type = IRQ_TYPE_NONE;
++ girq->handler = handle_bad_irq;
++ girq->parent_handler = realtek_gpio_irq_handler;
++ girq->num_parents = 1;
++ girq->parents = devm_kcalloc(dev, girq->num_parents,
++ sizeof(*girq->parents), GFP_KERNEL);
++ if (!girq->parents)
++ return -ENOMEM;
++ girq->parents[0] = irq;
++ girq->init_hw = realtek_gpio_irq_init;
++ }
++
++ return devm_gpiochip_add_data(dev, &ctrl->gc, ctrl);
++}
++
++static struct platform_driver realtek_gpio_driver = {
++ .driver = {
++ .name = "realtek-otto-gpio",
++ .of_match_table = realtek_gpio_of_match,
++ },
++ .probe = realtek_gpio_probe,
++};
++module_platform_driver(realtek_gpio_driver);
++
++MODULE_DESCRIPTION("Realtek Otto GPIO support");
++MODULE_AUTHOR("Sander Vanheule <sander@svanheule.net>");
++MODULE_LICENSE("GPL v2");
diff --git a/target/linux/realtek/patches-5.4/706-sysled.patch b/target/linux/realtek/patches-5.4/706-sysled.patch
new file mode 100644
index 0000000000..c13885d5ac
--- /dev/null
+++ b/target/linux/realtek/patches-5.4/706-sysled.patch
@@ -0,0 +1,294 @@
+From c1a89fdf22862379bb4150fc76504e2d3384cd67 Mon Sep 17 00:00:00 2001
+From: Bert Vermeulen <bert@biot.com>
+Date: Mon, 1 Mar 2021 12:41:35 +0100
+Subject: [PATCH] mfd: Add Realtek RTL838x/RTL839x sys-led driver
+
+---
+ drivers/mfd/Kconfig | 11 ++
+ drivers/mfd/Makefile | 1 +
+ drivers/mfd/realtek-eio.c | 243 ++++++++++++++++++++++++++++++++++++++
+ 3 files changed, 255 insertions(+)
+ create mode 100644 drivers/mfd/realtek-eio.c
+
+Index: linux-5.4.92/drivers/mfd/Kconfig
+===================================================================
+--- linux-5.4.92.orig/drivers/mfd/Kconfig
++++ linux-5.4.92/drivers/mfd/Kconfig
+@@ -923,6 +923,16 @@ config MFD_RETU
+ Retu and Tahvo are a multi-function devices found on Nokia
+ Internet Tablets (770, N800 and N810).
+
++config MFD_REALTEK_EIO
++ tristate "Realtek external LED and GPIO driver"
++ select MFD_CORE
++ select MFD_SYSCON
++ select GENERIC_PINCONF
++ default y
++ help
++ Say yes here if you want external LED/GPIO support for Realtek
++ switch SoCs.
++
+ config MFD_PCF50633
+ tristate "NXP PCF50633"
+ depends on I2C
+Index: linux-5.4.92/drivers/mfd/realtek-eio.c
+===================================================================
+--- /dev/null
++++ linux-5.4.92/drivers/mfd/realtek-eio.c
+@@ -0,0 +1,246 @@
++// SPDX-License-Identifier: GPL-2.0-or-later
++
++#include <linux/leds.h>
++#include <linux/mfd/core.h>
++#include <linux/mfd/syscon.h>
++#include <linux/module.h>
++#include <linux/of.h>
++#include <linux/of_address.h>
++#include <linux/of_platform.h>
++#include <linux/platform_device.h>
++#include <linux/regmap.h>
++
++#define REALTEK_EIO_GLOBAL_CTRL 0x0
++
++/*
++ * Management of external RTL8231 GPIO expanders.
++ * One RTL8231's GPIO registers can be shadowed to the internal GPIO_DIR
++ * and GPIO_DAT registers.
++ */
++#define RTL8380_EIO_GPIO_INDIRECT_ACCESS 0x9C
++#define RTL8380_EIO_GPIO_CTRL 0xE0
++#define RTL8380_EIO_GPIO_DIR(pin) (0xE4 + 4*((pin)/32))
++#define RTL8380_EIO_GPIO_DAT(pin) (0xEC + 4*((pin)/32))
++
++struct realtek_eio_ctrl;
++
++struct realtek_eio_data {
++ unsigned int sys_led_pos;
++ const struct mfd_cell *mfd_devices;
++ unsigned int mfd_device_count;
++};
++
++struct realtek_eio_ctrl {
++ struct device *dev;
++ struct regmap *map;
++ const struct realtek_eio_data *data;
++ struct led_classdev sys_led;
++ bool active_low;
++};
++
++
++#define OF_MFD_CELL(_name, _compat) \
++ { \
++ .name = (_name), \
++ .of_compatible = (_compat), \
++ }
++
++/*
++ * Realtek hardware system LED
++ *
++ * The switch SoC supports one hardware managed direct LED output
++ * to manage a system LED, with two supported blinking rates.
++ */
++enum {
++ REALTEK_SYS_LED_OFF = 0,
++ REALTEK_SYS_LED_BLINK_64MS,
++ REALTEK_SYS_LED_BLINK_1024MS,
++ REALTEK_SYS_LED_ON
++};
++
++static void realtek_sys_led_set(const struct realtek_eio_ctrl *ctrl,
++ unsigned int mode)
++{
++ regmap_update_bits(ctrl->map, REALTEK_EIO_GLOBAL_CTRL,
++ (0x3 << ctrl->data->sys_led_pos),
++ ((mode & 0x3) << ctrl->data->sys_led_pos));
++}
++
++static void realtek_sys_led_brightness_set(struct led_classdev *led_cdev,
++ enum led_brightness brightness)
++{
++ struct realtek_eio_ctrl *ctrl =
++ container_of(led_cdev, struct realtek_eio_ctrl, sys_led);
++
++ if ((!ctrl->active_low && brightness == LED_OFF) ||
++ (ctrl->active_low && brightness != LED_OFF))
++ realtek_sys_led_set(ctrl, REALTEK_SYS_LED_OFF);
++ else
++ realtek_sys_led_set(ctrl, REALTEK_SYS_LED_ON);
++}
++
++static enum led_brightness realtek_sys_led_brightness_get(
++ struct led_classdev *led_cdev)
++{
++ struct realtek_eio_ctrl *ctrl =
++ container_of(led_cdev, struct realtek_eio_ctrl, sys_led);
++ u32 val;
++
++ regmap_read(ctrl->map, REALTEK_EIO_GLOBAL_CTRL, &val);
++ val = (val >> ctrl->data->sys_led_pos) & 0x3;
++
++ if ((!ctrl->active_low && val == REALTEK_SYS_LED_OFF) ||
++ (ctrl->active_low && val == REALTEK_SYS_LED_ON))
++ return LED_OFF;
++ else
++ return LED_ON;
++}
++
++static int realtek_sys_led_blink_set(struct led_classdev *led_cdev,
++ unsigned long *delay_on, unsigned long *delay_off)
++{
++ struct realtek_eio_ctrl *ctrl =
++ container_of(led_cdev, struct realtek_eio_ctrl, sys_led);
++ u32 blink_interval = *delay_on + *delay_off;
++
++ /* Split range at geometric mean of 64 and 1024 */
++ if (blink_interval == 0 || blink_interval > 2*256) {
++ *delay_on = 1024;
++ *delay_off = 1024;
++ realtek_sys_led_set(ctrl, REALTEK_SYS_LED_BLINK_1024MS);
++ }
++ else {
++ *delay_on = 64;
++ *delay_off = 64;
++ realtek_sys_led_set(ctrl, REALTEK_SYS_LED_BLINK_64MS);
++ }
++
++ return 0;
++}
++
++static int realtek_sys_led_probe(struct realtek_eio_ctrl *ctrl,
++ struct device *parent, struct device_node *np)
++{
++ struct led_classdev *sys_led = &ctrl->sys_led;
++ struct led_init_data init_data = {};
++
++ init_data.fwnode = of_fwnode_handle(np);
++
++ ctrl->active_low = of_property_read_bool(np, "active-low");
++
++ sys_led->max_brightness = 1;
++ sys_led->brightness_set = realtek_sys_led_brightness_set;
++ sys_led->brightness_get = realtek_sys_led_brightness_get;
++ sys_led->blink_set = realtek_sys_led_blink_set;
++
++ return devm_led_classdev_register_ext(parent, sys_led, &init_data);
++}
++
++static const struct mfd_cell rtl8380_mfd_devices[] = {
++ OF_MFD_CELL("realtek-eio-port-leds", "realtek,rtl8380-eio-port-led"),
++ OF_MFD_CELL("realtek-eio-mdio", "realtek,rtl8380-eio-mdio"),
++ OF_MFD_CELL("realtek-eio-pinctrl", "realtek,rtl8380-eio-pinctrl"),
++};
++
++static const struct realtek_eio_data rtl8380_eio_data = {
++ .sys_led_pos = 16,
++ .mfd_devices = rtl8380_mfd_devices,
++ .mfd_device_count = ARRAY_SIZE(rtl8380_mfd_devices)
++};
++
++static const struct mfd_cell rtl8390_mfd_devices[] = {
++ OF_MFD_CELL("realtek-eio-port-leds", "realtek,rtl8390-eio-port-led"),
++};
++
++static struct realtek_eio_data rtl8390_eio_data = {
++ .sys_led_pos = 15,
++ .mfd_devices = rtl8390_mfd_devices,
++ .mfd_device_count = ARRAY_SIZE(rtl8390_mfd_devices)
++};
++
++static const struct of_device_id of_realtek_eio_match[] = {
++ {
++ .compatible = "realtek,rtl8380-eio",
++ .data = &rtl8380_eio_data,
++ },
++ {
++ .compatible = "realtek,rtl8390-eio",
++ .data = &rtl8390_eio_data,
++ },
++};
++
++MODULE_DEVICE_TABLE(of, of_realtek_eio_match);
++
++static int realtek_eio_probe(struct platform_device *pdev)
++{
++ struct device *dev = &pdev->dev;
++ struct device_node *np = dev->of_node;
++ struct device_node *np_sys_led;
++ const struct of_device_id *match;
++ struct realtek_eio_ctrl *ctrl;
++ int err, val;
++ unsigned r;
++
++ ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL);
++ if (!ctrl)
++ return -ENOMEM;
++
++ match = of_match_device(of_realtek_eio_match, dev);
++ if (match)
++ ctrl->data = (struct realtek_eio_data *) match->data;
++ else {
++ dev_err(dev, "no device match\n");
++ return -EINVAL;
++ }
++
++ ctrl->dev = dev;
++
++ if (!np) {
++ dev_err(dev, "no DT node found\n");
++ return -EINVAL;
++ }
++
++ ctrl->map = device_node_to_regmap(np);
++ if (!ctrl->map) {
++ dev_err(dev, "failed to get regmap\n");
++ return -EINVAL;
++ }
++
++ /* Parse optional sys-led child */
++ np_sys_led = of_get_child_by_name(np, "sys-led");
++ if (IS_ERR(np_sys_led))
++ return PTR_ERR(np_sys_led);
++
++ if (np_sys_led) {
++ err = realtek_sys_led_probe(ctrl, dev, np_sys_led);
++ if (err)
++ return err;
++ }
++
++ /* Find sub-devices */
++ if (ctrl->data->mfd_devices)
++ mfd_add_devices(dev, 0, ctrl->data->mfd_devices,
++ ctrl->data->mfd_device_count, NULL, 0, NULL);
++
++ /* Dump register values */
++ for (r = 0; r <= regmap_get_max_register(ctrl->map); r += 4) {
++ regmap_read(ctrl->map, r, &val);
++ dev_info(dev, "%02x %08x\n", r, val);
++ }
++
++ return 0;
++}
++
++static struct platform_driver realtek_eio_driver = {
++ .probe = realtek_eio_probe,
++ .driver = {
++ .name = "realtek-ext-io",
++ .of_match_table = of_realtek_eio_match
++ }
++};
++
++module_platform_driver(realtek_eio_driver);
++
++MODULE_AUTHOR("Sander Vanheule <sander@svanheule.net>");
++MODULE_DESCRIPTION("Realtek switch SoC external LED/GPIO driver");
++MODULE_LICENSE("GPL v2");
+Index: linux-5.4.92/drivers/mfd/Makefile
+===================================================================
+--- linux-5.4.92.orig/drivers/mfd/Makefile
++++ linux-5.4.92/drivers/mfd/Makefile
+@@ -255,4 +255,4 @@ obj-$(CONFIG_RAVE_SP_CORE) += rave-sp.o
+ obj-$(CONFIG_MFD_ROHM_BD70528) += rohm-bd70528.o
+ obj-$(CONFIG_MFD_ROHM_BD718XX) += rohm-bd718x7.o
+ obj-$(CONFIG_MFD_STMFX) += stmfx.o
+-
++obj-$(CONFIG_MFD_REALTEK_EIO) += realtek-eio.o
diff --git a/target/linux/realtek/patches-5.4/707-reboot.patch b/target/linux/realtek/patches-5.4/707-reboot.patch
new file mode 100644
index 0000000000..420b91d809
--- /dev/null
+++ b/target/linux/realtek/patches-5.4/707-reboot.patch
@@ -0,0 +1,9 @@
+Index: linux-5.4.92/drivers/gpio/Makefile
+===================================================================
+--- linux-5.4.92.orig/drivers/gpio/Makefile
++++ linux-5.4.92/drivers/gpio/Makefile
+@@ -171,3 +171,4 @@ obj-$(CONFIG_GPIO_XTENSA) += gpio-xtens
+ obj-$(CONFIG_GPIO_ZEVIO) += gpio-zevio.o
+ obj-$(CONFIG_GPIO_ZX) += gpio-zx.o
+ obj-$(CONFIG_GPIO_ZYNQ) += gpio-zynq.o
++obj-y += edgecore_reboot.o
diff --git a/target/linux/realtek/patches-5.4/708-poor-stp.patch b/target/linux/realtek/patches-5.4/708-poor-stp.patch
new file mode 100644
index 0000000000..1980914961
--- /dev/null
+++ b/target/linux/realtek/patches-5.4/708-poor-stp.patch
@@ -0,0 +1,16 @@
+Index: linux-5.4.102/net/bridge/br_fdb.c
+===================================================================
+--- linux-5.4.102.orig/net/bridge/br_fdb.c
++++ linux-5.4.102/net/bridge/br_fdb.c
+@@ -573,9 +573,9 @@ void br_fdb_update(struct net_bridge *br
+ if (likely(fdb)) {
+ /* attempt to update an entry for a local interface */
+ if (unlikely(fdb->is_local)) {
+- if (net_ratelimit())
+- br_warn(br, "received packet on %s with own address as source address (addr:%pM, vlan:%u)\n",
++ br_warn(br, "received packet on %s with own address as source address (addr:%pM, vlan:%u) shutting port down\n",
+ source->dev->name, addr, vid);
++ br_set_state(source, BR_STATE_BLOCKING);
+ } else {
+ unsigned long now = jiffies;
+
diff --git a/target/linux/realtek/patches-5.4/710-adt7470.patch b/target/linux/realtek/patches-5.4/710-adt7470.patch
new file mode 100644
index 0000000000..b76b0b33cf
--- /dev/null
+++ b/target/linux/realtek/patches-5.4/710-adt7470.patch
@@ -0,0 +1,22 @@
+Index: linux-5.4.92/drivers/hwmon/adt7470.c
+===================================================================
+--- linux-5.4.92.orig/drivers/hwmon/adt7470.c
++++ linux-5.4.92/drivers/hwmon/adt7470.c
+@@ -1271,10 +1271,17 @@ static const struct i2c_device_id adt747
+ };
+ MODULE_DEVICE_TABLE(i2c, adt7470_id);
+
++static const struct of_device_id __maybe_unused adt7470_of_match =
++{
++ .compatible = "adi,adt7470",
++};
++MODULE_DEVICE_TABLE(of, adt7470_of_match);
++
+ static struct i2c_driver adt7470_driver = {
+ .class = I2C_CLASS_HWMON,
+ .driver = {
+ .name = "adt7470",
++ .of_match_table = of_match_ptr(&adt7470_of_match),
+ },
+ .probe = adt7470_probe,
+ .remove = adt7470_remove,
diff --git a/target/linux/realtek/patches-5.4/711-ec4100.patch b/target/linux/realtek/patches-5.4/711-ec4100.patch
new file mode 100644
index 0000000000..f297bb2f3a
--- /dev/null
+++ b/target/linux/realtek/patches-5.4/711-ec4100.patch
@@ -0,0 +1,41 @@
+Index: linux-5.4.111/drivers/net/dsa/rtl83xx/common.c
+===================================================================
+--- linux-5.4.111.orig/drivers/net/dsa/rtl83xx/common.c
++++ linux-5.4.111/drivers/net/dsa/rtl83xx/common.c
+@@ -687,6 +687,17 @@ static int __init rtl83xx_sw_probe(struc
+ rtl838x_dbgfs_init(priv);
+ }
+
++ if (of_machine_is_compatible("edgecore,ecs4100-12ph")) {
++ sw_w32(0x000000FF, 0x110);
++ sw_w32(0x00000000, 0x114);
++ sw_w32(0x00000000, 0x118);
++ sw_w32(0x000f0000, 0x11c);
++ sw_w32(0x00000000, 0x120);
++ sw_w32(0x000f0000, 0x124);
++ sw_w32(0x3DEA, 0xec);
++ sw_w32(0x707568, 0xe4);
++ }
++
+ return err;
+ }
+
+Index: linux-5.4.111/drivers/net/phy/rtl83xx-phy.c
+===================================================================
+--- linux-5.4.111.orig/drivers/net/phy/rtl83xx-phy.c
++++ linux-5.4.111/drivers/net/phy/rtl83xx-phy.c
+@@ -1439,6 +1439,14 @@ static int rtl8380_configure_rtl8214fc(s
+ write_phy(mac + i, 0xfff, 0x1e, 0x0000);
+ }
+
++ if (of_machine_is_compatible("edgecore,ecs4100-12ph")) {
++ printk("setting edgecore specific SFP modes\n");
++ rtl8380_rtl8214fc_media_set(mac + 0, 0);
++ rtl8380_rtl8214fc_media_set(mac + 1, 0);
++ rtl8380_rtl8214fc_media_set(mac + 2, 1);
++ rtl8380_rtl8214fc_media_set(mac + 3, 1);
++ }
++
+ return 0;
+ }
+
--
2.25.1