[openwrt/openwrt] realtek: 6.12: restore patches-6.6

LEDE Commits lede-commits at lists.infradead.org
Wed Jun 11 13:27:31 PDT 2025


robimarko pushed a commit to openwrt/openwrt.git, branch main:
https://git.openwrt.org/8da7a1bfa9ed95552ecebd4ce63af5b107d331ba

commit 8da7a1bfa9ed95552ecebd4ce63af5b107d331ba
Author: Markus Stockhausen <markus.stockhausen at gmx.de>
AuthorDate: Tue May 27 09:11:03 2025 -0400

    realtek: 6.12: restore patches-6.6
    
    Automatically generated commit.
    
    Signed-off-by: Markus Stockhausen <markus.stockhausen at gmx.de>
    Link: https://github.com/openwrt/openwrt/pull/18935
    Signed-off-by: Robert Marko <robimarko at gmail.com>
---
 .../300-mips-add-rtl838x-platform.patch            |  89 ++++
 .../302-clocksource-add-otto-driver.patch          |  93 ++++
 .../patches-6.6/310-add-i2c-rtl9300-support.patch  |  46 ++
 .../311-add-i2c-mux-rtl9300-support.patch          |  46 ++
 ...4-irqchip-irq-realtek-rtl-add-VPE-support.patch | 427 +++++++++++++++
 .../patches-6.6/318-add-rtl83xx-clk-support.patch  |  33 ++
 .../330-add-realtek-thernal-driver.patch           |  21 +
 ...io-increase-max-ports-for-rtl839x-rtl931x.patch | 132 +++++
 .../702-include-linux-add-phy-hsgmii-mode.patch    |  79 +++
 ...706-include-linux-add-phy-ops-for-rtl838x.patch |  32 ++
 ...8-drivers-net-phy-eee-support-for-rtl838x.patch |  49 ++
 .../712-net-phy-add-an-MDIO-SMBus-library.patch    | 428 +++++++++++++++
 .../714-net-phy-sfp-add-support-for-SMBus.patch    | 126 +++++
 ...ethernet-add-support-for-rtl838x-ethernet.patch |  48 ++
 ...18-net-dsa-add-support-for-rtl838x-switch.patch |  42 ++
 .../realtek/patches-6.6/720-add-rtl-phy.patch      |  39 ++
 ...t-dsa-add-rtl838x-support-for-tag-trailer.patch |  61 +++
 ...dio-Add-Realtek-Otto-auxiliary-controller.patch | 227 ++++++++
 ...-regmap-Bypass-cache-for-shadowed-outputs.patch |  56 ++
 .../802-mfd-Add-RTL8231-core-device.patch          | 330 ++++++++++++
 ...-Add-RTL8231-pin-control-and-GPIO-support.patch | 581 +++++++++++++++++++++
 ...s-Add-support-for-RTL8231-LED-scan-matrix.patch | 338 ++++++++++++
 22 files changed, 3323 insertions(+)

diff --git a/target/linux/realtek/patches-6.6/300-mips-add-rtl838x-platform.patch b/target/linux/realtek/patches-6.6/300-mips-add-rtl838x-platform.patch
new file mode 100644
index 0000000000..bb89a7864f
--- /dev/null
+++ b/target/linux/realtek/patches-6.6/300-mips-add-rtl838x-platform.patch
@@ -0,0 +1,89 @@
+From fce11f68491b46b93df69de0630cd9edb90bc772 Mon Sep 17 00:00:00 2001
+From: Birger Koblitz <git at birger-koblitz.de>
+Date: Wed, 29 Dec 2021 21:54:21 +0100
+Subject: [PATCH] realtek: Create 4 different Realtek Platforms
+
+Creates RTL83XX as a basic kernel config parameter for the
+RTL838X, RTL839x, RTL930X and RTL931X platforms with respective
+configurations for the SoCs, which are introduced in addition.
+
+Submitted-by: Birger Koblitz <git at birger-koblitz.de>
+---
+--- a/arch/mips/Kbuild.platforms
++++ b/arch/mips/Kbuild.platforms
+@@ -22,6 +22,7 @@ platform-$(CONFIG_MACH_NINTENDO64)	+= n6
+ platform-$(CONFIG_PIC32MZDA)		+= pic32/
+ platform-$(CONFIG_RALINK)		+= ralink/
+ platform-$(CONFIG_MIKROTIK_RB532)	+= rb532/
++platform-$(CONFIG_MACH_REALTEK_RTL)	+= rtl838x/
+ platform-$(CONFIG_SGI_IP22)		+= sgi-ip22/
+ platform-$(CONFIG_SGI_IP27)		+= sgi-ip27/
+ platform-$(CONFIG_SGI_IP28)		+= sgi-ip22/
+--- a/arch/mips/Kconfig
++++ b/arch/mips/Kconfig
+@@ -620,21 +620,23 @@ config RALINK
+ 
+ config MACH_REALTEK_RTL
+ 	bool "Realtek RTL838x/RTL839x based machines"
+-	select MIPS_GENERIC
+ 	select DMA_NONCOHERENT
+ 	select IRQ_MIPS_CPU
+-	select CSRC_R4K
+-	select CEVT_R4K
+ 	select SYS_HAS_CPU_MIPS32_R1
+ 	select SYS_HAS_CPU_MIPS32_R2
+ 	select SYS_SUPPORTS_BIG_ENDIAN
+ 	select SYS_SUPPORTS_32BIT_KERNEL
+ 	select SYS_SUPPORTS_MIPS16
+-	select SYS_SUPPORTS_MULTITHREADING
+-	select SYS_SUPPORTS_VPE_LOADER
+ 	select BOOT_RAW
+ 	select PINCTRL
+ 	select USE_OF
++	select NO_EXCEPT_FILL
++	select SYS_SUPPORTS_HIGHMEM
++	select SYS_HAS_EARLY_PRINTK
++	select SYS_HAS_EARLY_PRINTK_8250
++	select USE_GENERIC_EARLY_PRINTK_8250
++	select ARCH_HAS_RESET_CONTROLLER
++	select RESET_CONTROLLER
+ 
+ config SGI_IP22
+ 	bool "SGI IP22 (Indy/Indigo2)"
+@@ -970,6 +972,36 @@ config CAVIUM_OCTEON_SOC
+ 
+ endchoice
+ 
++config RTL838X
++	bool "Realtek RTL838X based platforms"
++	depends on MACH_REALTEK_RTL
++	select CPU_SUPPORTS_CPUFREQ
++	select MIPS_EXTERNAL_TIMER
++
++config RTL839X
++	bool "Realtek RTL839X based platforms"
++	depends on MACH_REALTEK_RTL
++	select CPU_SUPPORTS_CPUFREQ
++	select MIPS_EXTERNAL_TIMER
++	select SYS_SUPPORTS_MULTITHREADING
++
++config RTL930X
++	bool "Realtek RTL930X based platforms"
++	depends on MACH_REALTEK_RTL
++	select MIPS_CPU_SCACHE
++	select MIPS_EXTERNAL_TIMER
++	select SYS_SUPPORTS_MULTITHREADING
++
++config RTL931X
++	bool "Realtek RTL931X based platforms"
++	depends on RTL930X
++	select MIPS_GIC
++	select COMMON_CLK
++	select CLKSRC_MIPS_GIC
++	select SYS_SUPPORTS_VPE_LOADER
++	select SYS_SUPPORTS_SMP
++	select SYS_SUPPORTS_MIPS_CPS
++
+ source "arch/mips/alchemy/Kconfig"
+ source "arch/mips/ath25/Kconfig"
+ source "arch/mips/ath79/Kconfig"
diff --git a/target/linux/realtek/patches-6.6/302-clocksource-add-otto-driver.patch b/target/linux/realtek/patches-6.6/302-clocksource-add-otto-driver.patch
new file mode 100644
index 0000000000..791ad0edb7
--- /dev/null
+++ b/target/linux/realtek/patches-6.6/302-clocksource-add-otto-driver.patch
@@ -0,0 +1,93 @@
+From 3cc8011171186d906c547bc6f0c1f8e350edc7cf Mon Sep 17 00:00:00 2001
+From: Markus Stockhausen <markus.stockhausen at gmx.de>
+Date: Mon, 3 Oct 2022 14:45:21 +0200
+Subject: [PATCH] realtek: resurrect timer driver
+
+Now that we provide a clock driver for the Reltek SOCs the CPU frequency might
+change on demand. This has direct visible effects during operation
+
+- the CEVT 4K timer is no longer a stable clocksource
+- after CPU frequencies changes time calculation works wrong
+- sched_clock falls back to kernel default interval (100 Hz)
+- timestamps in dmesg have only 2 digits left
+
+[    0.000000] sched_clock: 32 bits at 100 Hz, resolution 10000000ns, wraps ...
+[    0.060000] pid_max: default: 32768 minimum: 301
+[    0.070000] Mount-cache hash table entries: 1024 (order: 0, 4096 bytes, linear)
+[    0.070000] Mountpoint-cache hash table entries: 1024 (order: 0, 4096 bytes, linear)
+[    0.080000] dyndbg: Ignore empty _ddebug table in a CONFIG_DYNAMIC_DEBUG_CORE build
+[    0.090000] clocksource: jiffies: mask: 0xffffffff max_cycles: 0xffffffff, ...
+
+Looking around where we can start the CEVT timer for RTL930X is a good basis.
+Initially it was developed as a clocksource driver for the broken timer in that
+specific SOC series. Afterwards it was shifted around to the CEVT location,
+got SMP enablement and lost its clocksource feature. So we at least have
+something to copy from. As the timers on these devices are well understood
+the implementation follows this way:
+
+- leave the RTL930X implementation as is
+- provide a new driver for RTL83XX devices only
+- swap RTL930X driver at a later time
+
+Like the clock driver this patch contains a self contained module that is SOC
+independet and already provides full support for the RTL838X, RTL839X and
+RTL930X devices. Some of the new (or reestablished) features are:
+
+- simplified initialization routines
+- SMP setup with CPU hotplug framework
+- derived from LXB clock speed
+- supplied clocksource
+- dedicated register functions for better readability
+- documentation about some caveats
+
+Signed-off-by: Markus Stockhausen <markus.stockhausen at gmx.de>
+[remove unused header includes, remove old CONFIG_MIPS dependency, add
+REALTEK_ prefix to driver symbol]
+Signed-off-by: Sander Vanheule <sander at svanheule.net>
+
+---
+ drivers/clocksource/Kconfig                   | 12 +++
+ drivers/clocksource/Makefile                  |  1 +
+ include/linux/cpuhotplug.h                    |  1 +
+ 3 files changed, 14 insertions(+)
+
+--- a/drivers/clocksource/Kconfig
++++ b/drivers/clocksource/Kconfig
+@@ -134,6 +134,17 @@ config RDA_TIMER
+ 	help
+ 	  Enables the support for the RDA Micro timer driver.
+ 
++config REALTEK_OTTO_TIMER
++	bool "Clocksource/timer for the Realtek Otto platform"
++	select COMMON_CLK
++	select TIMER_OF
++	help
++	  This driver adds support for the timers found in the Realtek RTL83xx
++	  and RTL93xx SoCs series. This includes chips such as RTL8380, RTL8381
++	  and RTL832, as well as chips from the RTL839x series, such as RTL8390
++	  RT8391, RTL8392, RTL8393 and RTL8396 and chips of the RTL930x series
++	  such as RTL9301, RTL9302 or RTL9303.
++
+ config SUN4I_TIMER
+ 	bool "Sun4i timer driver" if COMPILE_TEST
+ 	depends on HAS_IOMEM
+--- a/drivers/clocksource/Makefile
++++ b/drivers/clocksource/Makefile
+@@ -59,6 +59,7 @@ obj-$(CONFIG_MILBEAUT_TIMER)	+= timer-mi
+ obj-$(CONFIG_SPRD_TIMER)	+= timer-sprd.o
+ obj-$(CONFIG_NPCM7XX_TIMER)	+= timer-npcm7xx.o
+ obj-$(CONFIG_RDA_TIMER)		+= timer-rda.o
++obj-$(CONFIG_REALTEK_OTTO_TIMER)	+= timer-rtl-otto.o
+ 
+ obj-$(CONFIG_ARC_TIMERS)		+= arc_timer.o
+ obj-$(CONFIG_ARM_ARCH_TIMER)		+= arm_arch_timer.o
+--- a/include/linux/cpuhotplug.h
++++ b/include/linux/cpuhotplug.h
+@@ -181,6 +181,7 @@ enum cpuhp_state {
+ 	CPUHP_AP_MARCO_TIMER_STARTING,
+ 	CPUHP_AP_MIPS_GIC_TIMER_STARTING,
+ 	CPUHP_AP_ARC_TIMER_STARTING,
++	CPUHP_AP_REALTEK_TIMER_STARTING,
+ 	CPUHP_AP_RISCV_TIMER_STARTING,
+ 	CPUHP_AP_CLINT_TIMER_STARTING,
+ 	CPUHP_AP_CSKY_TIMER_STARTING,
diff --git a/target/linux/realtek/patches-6.6/310-add-i2c-rtl9300-support.patch b/target/linux/realtek/patches-6.6/310-add-i2c-rtl9300-support.patch
new file mode 100644
index 0000000000..9eaed95d0d
--- /dev/null
+++ b/target/linux/realtek/patches-6.6/310-add-i2c-rtl9300-support.patch
@@ -0,0 +1,46 @@
+From 63a0a4d85bc900464c5b046b13808a582345f8c8 Mon Sep 17 00:00:00 2001
+From: Birger Koblitz <git at birger-koblitz.de>
+Date: Sat, 11 Dec 2021 20:14:47 +0100
+Subject: [PATCH] realtek: Add support for RTL9300/RTL9310 I2C controller
+
+This adds support for the RTL9300 and RTL9310 I2C controller.
+The controller implements the SMBus protocol for SMBus transfers
+over an I2C bus. The driver supports selecting one of the 2 possible
+SCL pins and any of the 8 possible SDA pins. Bus speeds of
+100kHz (standard speed) and 400kHz (high speed I2C) are supported.
+
+Submitted-by: Birger Koblitz <git at birger-koblitz.de>
+---
+ drivers/i2c/busses/Kconfig                    | 10 +++++++++
+ drivers/i2c/busses/Makefile                   |  1 +
+ 2 files changed, 11 insertions(+)
+
+--- a/drivers/i2c/busses/Kconfig
++++ b/drivers/i2c/busses/Kconfig
+@@ -1023,6 +1023,16 @@ config I2C_RK3X
+ 	  This driver can also be built as a module. If so, the module will
+ 	  be called i2c-rk3x.
+ 
++config I2C_RTL9300
++	tristate "Realtek RTL9300 I2C adapter"
++	depends on OF
++	help
++	  Say Y here to include support for the I2C adapter in Realtek RTL9300
++	  and RTL9310 SoCs.
++
++	  This driver can also be built as a module. If so, the module will
++	  be called i2c-rtl9300.
++
+ config I2C_RZV2M
+ 	tristate "Renesas RZ/V2M adapter"
+ 	depends on ARCH_RENESAS || COMPILE_TEST
+--- a/drivers/i2c/busses/Makefile
++++ b/drivers/i2c/busses/Makefile
+@@ -101,6 +101,7 @@ obj-$(CONFIG_I2C_QCOM_GENI)	+= i2c-qcom-
+ obj-$(CONFIG_I2C_QUP)		+= i2c-qup.o
+ obj-$(CONFIG_I2C_RIIC)		+= i2c-riic.o
+ obj-$(CONFIG_I2C_RK3X)		+= i2c-rk3x.o
++obj-$(CONFIG_I2C_RTL9300)	+= i2c-rtl9300.o
+ obj-$(CONFIG_I2C_RZV2M)		+= i2c-rzv2m.o
+ obj-$(CONFIG_I2C_S3C2410)	+= i2c-s3c2410.o
+ obj-$(CONFIG_I2C_SH7760)	+= i2c-sh7760.o
diff --git a/target/linux/realtek/patches-6.6/311-add-i2c-mux-rtl9300-support.patch b/target/linux/realtek/patches-6.6/311-add-i2c-mux-rtl9300-support.patch
new file mode 100644
index 0000000000..d0bfba4538
--- /dev/null
+++ b/target/linux/realtek/patches-6.6/311-add-i2c-mux-rtl9300-support.patch
@@ -0,0 +1,46 @@
+From f4bdb7fdccdfe3fa382abe77f72a16c2f2e6add0 Mon Sep 17 00:00:00 2001
+From: Birger Koblitz <git at birger-koblitz.de>
+Date: Sat, 11 Dec 2021 20:25:37 +0100
+Subject: [PATCH] realtek: Add support for RTL9300/RTL9310 I2C multiplexing
+
+The RTL9300/RTL9310 I2C controllers have support for 2 independent I2C
+masters, each with a fixed SCL pin, that cannot be changed. Each of these
+masters can use 8 (RTL9300) or 16 (RTL9310) different pins for SDA.
+This multiplexer directly controls the two masters and their shared
+IO configuration registers to allow multiplexing between any of these
+busses. The two masters cannot be used in parallel as the multiplex
+is protected by a standard multiplex lock.
+
+Submitted-by: Birger Koblitz <git at birger-koblitz.de>
+---
+ drivers/i2c/muxes/Kconfig                     |  9 +++++++
+ drivers/i2c/muxes/Makefile                    |  1 +
+ 2 files changed, 10 insertions(+)
+
+--- a/drivers/i2c/muxes/Kconfig
++++ b/drivers/i2c/muxes/Kconfig
+@@ -99,6 +99,15 @@ config I2C_MUX_REG
+ 	  This driver can also be built as a module.  If so, the module
+ 	  will be called i2c-mux-reg.
+ 
++config I2C_MUX_RTL9300
++	tristate "RTL9300 based I2C multiplexer"
++	help
++	  If you say yes to this option, support will be included for a
++	  RTL9300 based I2C multiplexer.
++
++	  This driver can also be built as a module.  If so, the module
++	  will be called i2c-mux-reg.
++
+ config I2C_DEMUX_PINCTRL
+ 	tristate "pinctrl-based I2C demultiplexer"
+ 	depends on PINCTRL && OF
+--- a/drivers/i2c/muxes/Makefile
++++ b/drivers/i2c/muxes/Makefile
+@@ -14,5 +14,6 @@ obj-$(CONFIG_I2C_MUX_PCA9541)	+= i2c-mux
+ obj-$(CONFIG_I2C_MUX_PCA954x)	+= i2c-mux-pca954x.o
+ obj-$(CONFIG_I2C_MUX_PINCTRL)	+= i2c-mux-pinctrl.o
+ obj-$(CONFIG_I2C_MUX_REG)	+= i2c-mux-reg.o
++obj-$(CONFIG_I2C_MUX_RTL9300)	+= i2c-mux-rtl9300.o
+ 
+ ccflags-$(CONFIG_I2C_DEBUG_BUS) := -DDEBUG
diff --git a/target/linux/realtek/patches-6.6/314-irqchip-irq-realtek-rtl-add-VPE-support.patch b/target/linux/realtek/patches-6.6/314-irqchip-irq-realtek-rtl-add-VPE-support.patch
new file mode 100644
index 0000000000..4610ca8c19
--- /dev/null
+++ b/target/linux/realtek/patches-6.6/314-irqchip-irq-realtek-rtl-add-VPE-support.patch
@@ -0,0 +1,427 @@
+From 6c18e9c491959ac0674ebe36b09f9ddc3f2c9bce Mon Sep 17 00:00:00 2001
+From: Birger Koblitz <git at birger-koblitz.de>
+Date: Fri, 31 Dec 2021 11:56:49 +0100
+Subject: [PATCH] realtek: Add VPE support for the IRQ driver
+
+In order to support VSMP, enable support for both VPEs of the RTL839X
+and RTL930X SoCs in the irq-realtek-rtl driver. Add support for IRQ
+affinity setting.
+
+Up to kernel 5.15 this patch was divided into two parts
+
+315-irqchip-irq-realtek-rtl-add-VPE-support.patch
+319-irqchip-irq-realtek-rtl-fix-VPE-affinity.patch
+
+As both parts will only work in combination they have been merged into
+one patch.
+
+Submitted-by: Birger Koblitz <git at birger-koblitz.de>
+Submitted-by: INAGAKI Hiroshi <musashino.open at gmail.com>
+Signed-off-by: Markus Stockhausen <markus.stockhausen at gmx.de>
+---
+ drivers/irqchip/irq-realtek-rtl.c | 296 +++++++++++++++++++++++++-----
+ 1 file changed, 249 insertions(+), 47 deletions(-)
+
+--- a/drivers/irqchip/irq-realtek-rtl.c
++++ b/drivers/irqchip/irq-realtek-rtl.c
+@@ -22,22 +22,58 @@
+ #define RTL_ICTL_IRR3		0x14
+ 
+ #define RTL_ICTL_NUM_INPUTS	32
+-
+-#define REG(x)		(realtek_ictl_base + x)
++#define RTL_ICTL_NUM_OUTPUTS	15
+ 
+ static DEFINE_RAW_SPINLOCK(irq_lock);
+-static void __iomem *realtek_ictl_base;
++
++#define REG(offset, cpu)	(realtek_ictl_base[cpu] + offset)
++
++static u32 realtek_ictl_unmask[NR_CPUS];
++static void __iomem *realtek_ictl_base[NR_CPUS];
++static cpumask_t realtek_ictl_cpu_configurable;
++
++struct realtek_ictl_output {
++	/* IRQ controller data */
++	struct fwnode_handle *fwnode;
++	/* Output specific data */
++	unsigned int output_index;
++	struct irq_domain *domain;
++	u32 child_mask;
++};
+ 
+ /*
+- * IRR0-IRR3 store 4 bits per interrupt, but Realtek uses inverted numbering,
+- * placing IRQ 31 in the first four bits. A routing value of '0' means the
+- * interrupt is left disconnected. Routing values {1..15} connect to output
+- * lines {0..14}.
++ * Per CPU we have a set of 5 registers that determine interrupt handling for
++ * 32 external interrupts. GIMR (enable/disable interrupt) plus IRR0-IRR3 that
++ * contain "routing" or "priority" values. GIMR uses one bit for each interrupt
++ * and IRRx store 4 bits per interrupt. Realtek uses inverted numbering,
++ * placing IRQ 31 in the first four bits. The register combinations give the
++ * following results for a single interrupt in the wild:
++ *
++ * a) GIMR = 0 / IRRx > 0 -> no interrupts
++ * b) GIMR = 0 / IRRx = 0 -> no interrupts
++ * c) GIMR = 1 / IRRx > 0 -> interrupts
++ * d) GIMR = 1 / IRRx = 0 -> rare interrupts in SMP environment
++ *
++ * Combination d) seems to trigger interrupts only on a VPE if the other VPE
++ * has GIMR = 0 and IRRx > 0. E.g. busy without interrupts allowed. To provide
++ * IRQ balancing features in SMP this driver will handle the registers as
++ * follows:
++ *
++ * 1) set IRRx > 0 for VPE where the interrupt is desired
++ * 2) set IRRx = 0 for VPE where the interrupt is not desired
++ * 3) set both GIMR = 0 to mask (disabled) interrupt
++ * 4) set GIMR = 1 to unmask (enable) interrupt but only for VPE where IRRx > 0
+  */
++
+ #define IRR_OFFSET(idx)		(4 * (3 - (idx * 4) / 32))
+ #define IRR_SHIFT(idx)		((idx * 4) % 32)
+ 
+-static void write_irr(void __iomem *irr0, int idx, u32 value)
++static inline u32 read_irr(void __iomem *irr0, int idx)
++{
++	return (readl(irr0 + IRR_OFFSET(idx)) >> IRR_SHIFT(idx)) & 0xf;
++}
++
++static inline void write_irr(void __iomem *irr0, int idx, u32 value)
+ {
+ 	unsigned int offset = IRR_OFFSET(idx);
+ 	unsigned int shift = IRR_SHIFT(idx);
+@@ -48,16 +84,33 @@ static void write_irr(void __iomem *irr0
+ 	writel(irr, irr0 + offset);
+ }
+ 
++static inline void enable_gimr(int hwirq, int cpu)
++{
++	u32 value;
++
++	value = readl(REG(RTL_ICTL_GIMR, cpu));
++	value |= (BIT(hwirq) & realtek_ictl_unmask[cpu]);
++	writel(value, REG(RTL_ICTL_GIMR, cpu));
++}
++
++static inline void disable_gimr(int hwirq, int cpu)
++{
++	u32 value;
++
++	value = readl(REG(RTL_ICTL_GIMR, cpu));
++	value &= ~BIT(hwirq);
++	writel(value, REG(RTL_ICTL_GIMR, cpu));
++}
++
+ static void realtek_ictl_unmask_irq(struct irq_data *i)
+ {
+ 	unsigned long flags;
+-	u32 value;
++	int cpu;
+ 
+ 	raw_spin_lock_irqsave(&irq_lock, flags);
+ 
+-	value = readl(REG(RTL_ICTL_GIMR));
+-	value |= BIT(i->hwirq);
+-	writel(value, REG(RTL_ICTL_GIMR));
++	for_each_cpu(cpu, &realtek_ictl_cpu_configurable)
++		enable_gimr(i->hwirq, cpu);
+ 
+ 	raw_spin_unlock_irqrestore(&irq_lock, flags);
+ }
+@@ -65,110 +118,259 @@ static void realtek_ictl_unmask_irq(stru
+ static void realtek_ictl_mask_irq(struct irq_data *i)
+ {
+ 	unsigned long flags;
+-	u32 value;
++	int cpu;
+ 
+ 	raw_spin_lock_irqsave(&irq_lock, flags);
+ 
+-	value = readl(REG(RTL_ICTL_GIMR));
+-	value &= ~BIT(i->hwirq);
+-	writel(value, REG(RTL_ICTL_GIMR));
++	for_each_cpu(cpu, &realtek_ictl_cpu_configurable)
++		disable_gimr(i->hwirq, cpu);
+ 
+ 	raw_spin_unlock_irqrestore(&irq_lock, flags);
+ }
+ 
++static int __maybe_unused realtek_ictl_irq_affinity(struct irq_data *i,
++	const struct cpumask *dest, bool force)
++{
++	struct realtek_ictl_output *output = i->domain->host_data;
++	cpumask_t cpu_configure;
++	cpumask_t cpu_disable;
++	cpumask_t cpu_enable;
++	unsigned long flags;
++	int cpu;
++
++	raw_spin_lock_irqsave(&irq_lock, flags);
++
++	cpumask_and(&cpu_configure, cpu_present_mask, &realtek_ictl_cpu_configurable);
++
++	cpumask_and(&cpu_enable, &cpu_configure, dest);
++	cpumask_andnot(&cpu_disable, &cpu_configure, dest);
++
++	for_each_cpu(cpu, &cpu_disable) {
++		write_irr(REG(RTL_ICTL_IRR0, cpu), i->hwirq, 0);
++		realtek_ictl_unmask[cpu] &= ~BIT(i->hwirq);
++		disable_gimr(i->hwirq, cpu);
++	}
++
++	for_each_cpu(cpu, &cpu_enable) {
++		write_irr(REG(RTL_ICTL_IRR0, cpu), i->hwirq, output->output_index + 1);
++		realtek_ictl_unmask[cpu] |= BIT(i->hwirq);
++		enable_gimr(i->hwirq, cpu);
++	}
++
++	irq_data_update_effective_affinity(i, &cpu_enable);
++
++	raw_spin_unlock_irqrestore(&irq_lock, flags);
++
++	return IRQ_SET_MASK_OK;
++}
++
+ static struct irq_chip realtek_ictl_irq = {
+ 	.name = "realtek-rtl-intc",
+ 	.irq_mask = realtek_ictl_mask_irq,
+ 	.irq_unmask = realtek_ictl_unmask_irq,
++#ifdef CONFIG_SMP
++	.irq_set_affinity = realtek_ictl_irq_affinity,
++#endif
+ };
+ 
+ static int intc_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hw)
+ {
++	struct realtek_ictl_output *output = d->host_data;
+ 	unsigned long flags;
+ 
+ 	irq_set_chip_and_handler(irq, &realtek_ictl_irq, handle_level_irq);
+ 
+ 	raw_spin_lock_irqsave(&irq_lock, flags);
+-	write_irr(REG(RTL_ICTL_IRR0), hw, 1);
++
++	output->child_mask |= BIT(hw);
++	write_irr(REG(RTL_ICTL_IRR0, 0), hw, output->output_index + 1);
++	realtek_ictl_unmask[0] |= BIT(hw);
++
+ 	raw_spin_unlock_irqrestore(&irq_lock, flags);
+ 
+ 	return 0;
+ }
+ 
++static int intc_select(struct irq_domain *d, struct irq_fwspec *fwspec,
++	enum irq_domain_bus_token bus_token)
++{
++	struct realtek_ictl_output *output = d->host_data;
++	bool routed_elsewhere;
++	unsigned long flags;
++	u32 routing_old;
++	int cpu;
++
++	if (fwspec->fwnode != output->fwnode)
++		return false;
++
++	/* Original specifiers had only one parameter */
++	if (fwspec->param_count < 2)
++		return true;
++
++	raw_spin_lock_irqsave(&irq_lock, flags);
++
++	/*
++	 * Inputs can only be routed to one output, so they shouldn't be
++	 * allowed to end up in multiple domains.
++	 */
++	for_each_cpu(cpu, &realtek_ictl_cpu_configurable) {
++		routing_old = read_irr(REG(RTL_ICTL_IRR0, cpu), fwspec->param[0]);
++		routed_elsewhere = routing_old && fwspec->param[1] != routing_old - 1;
++		if (routed_elsewhere) {
++			pr_warn("soc int %d already routed to output %d\n",
++				fwspec->param[0], routing_old - 1);
++			break;
++		}
++	}
++
++	raw_spin_unlock_irqrestore(&irq_lock, flags);
++
++	return !routed_elsewhere && fwspec->param[1] == output->output_index;
++}
++
+ static const struct irq_domain_ops irq_domain_ops = {
+ 	.map = intc_map,
++	.select = intc_select,
+ 	.xlate = irq_domain_xlate_onecell,
+ };
+ 
+ static void realtek_irq_dispatch(struct irq_desc *desc)
+ {
++	struct realtek_ictl_output *output = irq_desc_get_handler_data(desc);
+ 	struct irq_chip *chip = irq_desc_get_chip(desc);
+-	struct irq_domain *domain;
++	int cpu = smp_processor_id();
+ 	unsigned long pending;
+ 	unsigned int soc_int;
+ 
+ 	chained_irq_enter(chip, desc);
+-	pending = readl(REG(RTL_ICTL_GIMR)) & readl(REG(RTL_ICTL_GISR));
++	pending = readl(REG(RTL_ICTL_GIMR, cpu)) & readl(REG(RTL_ICTL_GISR, cpu))
++		& output->child_mask;
+ 
+ 	if (unlikely(!pending)) {
+ 		spurious_interrupt();
+ 		goto out;
+ 	}
+ 
+-	domain = irq_desc_get_handler_data(desc);
+-	for_each_set_bit(soc_int, &pending, 32)
+-		generic_handle_domain_irq(domain, soc_int);
++	for_each_set_bit(soc_int, &pending, RTL_ICTL_NUM_INPUTS)
++		generic_handle_domain_irq(output->domain, soc_int);
+ 
+ out:
+ 	chained_irq_exit(chip, desc);
+ }
+ 
++/*
++ * SoC interrupts are cascaded to MIPS CPU interrupts according to the
++ * interrupt-map in the device tree. Each SoC interrupt gets 4 bits for
++ * the CPU interrupt in an Interrupt Routing Register. Max 32 SoC interrupts
++ * thus go into 4 IRRs. A routing value of '0' means the interrupt is left
++ * disconnected. Routing values {1..15} connect to output lines {0..14}.
++ */
++static int __init setup_parent_interrupts(struct device_node *node, int *parents,
++	unsigned int num_parents)
++{
++	struct realtek_ictl_output *outputs;
++	struct realtek_ictl_output *output;
++	struct irq_domain *domain;
++	unsigned int p;
++
++	outputs = kcalloc(num_parents, sizeof(*outputs), GFP_KERNEL);
++	if (!outputs)
++		return -ENOMEM;
++
++	for (p = 0; p < num_parents; p++) {
++		output = outputs + p;
++
++		domain = irq_domain_add_linear(node, RTL_ICTL_NUM_INPUTS, &irq_domain_ops, output);
++		if (!domain)
++			goto domain_err;
++
++		output->fwnode = of_node_to_fwnode(node);
++		output->output_index = p;
++		output->domain = domain;
++
++		irq_set_chained_handler_and_data(parents[p], realtek_irq_dispatch, output);
++	}
++
++	return 0;
++
++domain_err:
++	while (p--) {
++		irq_set_chained_handler_and_data(parents[p], NULL, NULL);
++		irq_domain_remove(outputs[p].domain);
++	}
++
++	kfree(outputs);
++
++	return -ENOMEM;
++}
++
+ static int __init realtek_rtl_of_init(struct device_node *node, struct device_node *parent)
+ {
++	int parent_irqs[RTL_ICTL_NUM_OUTPUTS];
+ 	struct of_phandle_args oirq;
+-	struct irq_domain *domain;
++	unsigned int num_parents;
+ 	unsigned int soc_irq;
+-	int parent_irq;
++	unsigned int p;
++	int cpu;
++
++	cpumask_clear(&realtek_ictl_cpu_configurable);
+ 
+-	realtek_ictl_base = of_iomap(node, 0);
+-	if (!realtek_ictl_base)
++	for (cpu = 0; cpu < NR_CPUS; cpu++) {
++		realtek_ictl_base[cpu] = of_iomap(node, cpu);
++		if (realtek_ictl_base[cpu]) {
++			cpumask_set_cpu(cpu, &realtek_ictl_cpu_configurable);
++
++			/* Disable all cascaded interrupts and clear routing */
++			for (soc_irq = 0; soc_irq < RTL_ICTL_NUM_INPUTS; soc_irq++) {
++				write_irr(REG(RTL_ICTL_IRR0, cpu), soc_irq, 0);
++				realtek_ictl_unmask[cpu] &= ~BIT(soc_irq);
++				disable_gimr(soc_irq, cpu);
++			}
++		}
++	}
++
++	if (cpumask_empty(&realtek_ictl_cpu_configurable))
+ 		return -ENXIO;
+ 
+-	/* Disable all cascaded interrupts and clear routing */
+-	writel(0, REG(RTL_ICTL_GIMR));
+-	for (soc_irq = 0; soc_irq < RTL_ICTL_NUM_INPUTS; soc_irq++)
+-		write_irr(REG(RTL_ICTL_IRR0), soc_irq, 0);
++	num_parents = of_irq_count(node);
++	if (num_parents > RTL_ICTL_NUM_OUTPUTS) {
++		pr_err("too many parent interrupts\n");
++		return -EINVAL;
++	}
+ 
+-	if (WARN_ON(!of_irq_count(node))) {
++	for (p = 0; p < num_parents; p++)
++		parent_irqs[p] = of_irq_get(node, p);
++
++	if (WARN_ON(!num_parents)) {
+ 		/*
+ 		 * If DT contains no parent interrupts, assume MIPS CPU IRQ 2
+ 		 * (HW0) is connected to the first output. This is the case for
+ 		 * all known hardware anyway. "interrupt-map" is deprecated, so
+ 		 * don't bother trying to parse that.
++		 * Since this is to account for old devicetrees with one-cell
++		 * interrupt specifiers, only one output domain is needed.
+ 		 */
+ 		oirq.np = of_find_compatible_node(NULL, NULL, "mti,cpu-interrupt-controller");
+-		oirq.args_count = 1;
+-		oirq.args[0] = 2;
+-
+-		parent_irq = irq_create_of_mapping(&oirq);
++		if (oirq.np) {
++			oirq.args_count = 1;
++			oirq.args[0] = 2;
++
++			parent_irqs[0] = irq_create_of_mapping(&oirq);
++			num_parents = 1;
++		}
+ 
+ 		of_node_put(oirq.np);
+-	} else {
+-		parent_irq = of_irq_get(node, 0);
+ 	}
+ 
+-	if (parent_irq < 0)
+-		return parent_irq;
+-	else if (!parent_irq)
+-		return -ENODEV;
+-
+-	domain = irq_domain_add_linear(node, RTL_ICTL_NUM_INPUTS, &irq_domain_ops, NULL);
+-	if (!domain)
+-		return -ENOMEM;
+-
+-	irq_set_chained_handler_and_data(parent_irq, realtek_irq_dispatch, domain);
++	/* Ensure we haven't collected any errors before proceeding */
++	for (p = 0; p < num_parents; p++) {
++		if (parent_irqs[p] < 0)
++			return parent_irqs[p];
++		if (!parent_irqs[p])
++			return -ENODEV;
++	}
+ 
+-	return 0;
++	return setup_parent_interrupts(node, &parent_irqs[0], num_parents);
+ }
+ 
+ IRQCHIP_DECLARE(realtek_rtl_intc, "realtek,rtl-intc", realtek_rtl_of_init);
diff --git a/target/linux/realtek/patches-6.6/318-add-rtl83xx-clk-support.patch b/target/linux/realtek/patches-6.6/318-add-rtl83xx-clk-support.patch
new file mode 100644
index 0000000000..e75260a638
--- /dev/null
+++ b/target/linux/realtek/patches-6.6/318-add-rtl83xx-clk-support.patch
@@ -0,0 +1,33 @@
+From 800d5fb3c6a16661932c932bacd660e38d06b727 Mon Sep 17 00:00:00 2001
+From: Markus Stockhausen <markus.stockhausen at gmx.de>
+Date: Thu, 25 Aug 2022 08:22:36 +0200
+Subject: [PATCH] realtek: add patch to enable new clock driver in kernel
+
+Allow building the clock driver with kernel config options.
+
+Submitted-by: Markus Stockhausen <markus.stockhausen at gmx.de>
+---
+ drivers/clk/Kconfig                           | 1 +
+ drivers/clk/Makefile                          | 1 +
+ 2 files changed, 2 insertions(+)
+
+--- a/drivers/clk/Kconfig
++++ b/drivers/clk/Kconfig
+@@ -484,6 +484,7 @@ source "drivers/clk/mvebu/Kconfig"
+ source "drivers/clk/nuvoton/Kconfig"
+ source "drivers/clk/pistachio/Kconfig"
+ source "drivers/clk/qcom/Kconfig"
++source "drivers/clk/realtek/Kconfig"
+ source "drivers/clk/ralink/Kconfig"
+ source "drivers/clk/renesas/Kconfig"
+ source "drivers/clk/rockchip/Kconfig"
+--- a/drivers/clk/Makefile
++++ b/drivers/clk/Makefile
+@@ -112,6 +112,7 @@ obj-$(CONFIG_COMMON_CLK_PISTACHIO)	+= pi
+ obj-$(CONFIG_COMMON_CLK_PXA)		+= pxa/
+ obj-$(CONFIG_COMMON_CLK_QCOM)		+= qcom/
+ obj-y					+= ralink/
++obj-$(CONFIG_COMMON_CLK_REALTEK)	+= realtek/
+ obj-y					+= renesas/
+ obj-$(CONFIG_ARCH_ROCKCHIP)		+= rockchip/
+ obj-$(CONFIG_COMMON_CLK_SAMSUNG)	+= samsung/
diff --git a/target/linux/realtek/patches-6.6/330-add-realtek-thernal-driver.patch b/target/linux/realtek/patches-6.6/330-add-realtek-thernal-driver.patch
new file mode 100644
index 0000000000..af550a8c20
--- /dev/null
+++ b/target/linux/realtek/patches-6.6/330-add-realtek-thernal-driver.patch
@@ -0,0 +1,21 @@
+--- a/drivers/thermal/Kconfig
++++ b/drivers/thermal/Kconfig
+@@ -522,4 +522,11 @@ config LOONGSON2_THERMAL
+ 	  is higher than the high temperature threshold or lower than the low
+ 	  temperature threshold, the interrupt will occur.
+ 
++config REALTEK_THERMAL
++	tristate "Realtek RTL838x and RTL930x thermal sensor support"
++	depends on RTL838X || RTL839X || RTL930X || COMPILE_TEST
++	depends on THERMAL_OF
++	help
++	  Support thermal sensor in Realtek RTL838x, RTL839x and RTL930x SoCs
++
+ endif
+--- a/drivers/thermal/Makefile
++++ b/drivers/thermal/Makefile
+@@ -64,3 +64,4 @@ obj-$(CONFIG_AMLOGIC_THERMAL)     += aml
+ obj-$(CONFIG_SPRD_THERMAL)	+= sprd_thermal.o
+ obj-$(CONFIG_KHADAS_MCU_FAN_THERMAL)	+= khadas_mcu_fan.o
+ obj-$(CONFIG_LOONGSON2_THERMAL)	+= loongson2_thermal.o
++obj-$(CONFIG_REALTEK_THERMAL)	+= realtek-thermal.o
diff --git a/target/linux/realtek/patches-6.6/700-dsa-mdio-increase-max-ports-for-rtl839x-rtl931x.patch b/target/linux/realtek/patches-6.6/700-dsa-mdio-increase-max-ports-for-rtl839x-rtl931x.patch
new file mode 100644
index 0000000000..147e2c587b
--- /dev/null
+++ b/target/linux/realtek/patches-6.6/700-dsa-mdio-increase-max-ports-for-rtl839x-rtl931x.patch
@@ -0,0 +1,132 @@
+From 2b88563ee5aafd9571d965b7f2093a0f58d98a31 Mon Sep 17 00:00:00 2001
+From: John Crispin <john at phrozen.org>
+Date: Thu, 26 Nov 2020 12:02:21 +0100
+Subject: realtek dsa/phy: Increase max ports for RTL839X/RTL931X
+
+Linux standard can only support up to 32 devices per mdio bus and up to
+12 ports per DSA switch. This is not enough for the large RTL839X and
+RTL931X devices. Increase the max values accordingly. Additionally take
+care about the functions that work on bit masks.
+
+Submitted-by: Bert Vermeulen <bert at biot.com>
+Submitted-by: Birger Koblitz <mail at birger-koblitz.de>
+Submitted-by: Sander Vanheule <sander at svanheule.net>
+Submitted-by: Bjørn Mork <bjorn at mork.no>
+Submitted-by: John Crispin <john at phrozen.org>
+Signed-off-by: Markus Stockhausen <markus.stockhausen at gmx.de>
+---
+ drivers/net/mdio/fwnode_mdio.c    |  2 +-
+ include/linux/phy.h               |  6 +++---
+ include/linux/platform_data/dsa.h |  2 +-
+ include/net/dsa.h                 | 14 +++++++-------
+ net/dsa/slave.c                   |  4 ++--
+ 5 files changed, 14 insertions(+), 14 deletions(-)
+
+--- a/drivers/net/mdio/fwnode_mdio.c
++++ b/drivers/net/mdio/fwnode_mdio.c
+@@ -87,7 +87,7 @@ int fwnode_mdiobus_phy_device_register(s
+ 	}
+ 
+ 	if (fwnode_property_read_bool(child, "broken-turn-around"))
+-		mdio->phy_ignore_ta_mask |= 1 << addr;
++		mdio->phy_ignore_ta_mask |= BIT_ULL(addr);
+ 
+ 	fwnode_property_read_u32(child, "reset-assert-us",
+ 				 &phy->mdio.reset_assert_delay);
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -294,7 +294,7 @@ static inline const char *phy_modes(phy_
+ #define PHY_INIT_TIMEOUT	100000
+ #define PHY_FORCE_TIMEOUT	10
+ 
+-#define PHY_MAX_ADDR	32
++#define PHY_MAX_ADDR	64
+ 
+ /* Used when trying to connect to a specific phy (mii bus id:phy device id) */
+ #define PHY_ID_FMT "%s:%02x"
+@@ -414,10 +414,10 @@ struct mii_bus {
+ 	struct mdio_device *mdio_map[PHY_MAX_ADDR];
+ 
+ 	/** @phy_mask: PHY addresses to be ignored when probing */
+-	u32 phy_mask;
++	u64 phy_mask;
+ 
+ 	/** @phy_ignore_ta_mask: PHY addresses to ignore the TA/read failure */
+-	u32 phy_ignore_ta_mask;
++	u64 phy_ignore_ta_mask;
+ 
+ 	/**
+ 	 * @irq: An array of interrupts, each PHY's interrupt at the index
+--- a/include/linux/platform_data/dsa.h
++++ b/include/linux/platform_data/dsa.h
+@@ -6,7 +6,7 @@ struct device;
+ struct net_device;
+ 
+ #define DSA_MAX_SWITCHES	4
+-#define DSA_MAX_PORTS		12
++#define DSA_MAX_PORTS		54
+ #define DSA_RTABLE_NONE		-1
+ 
+ struct dsa_chip_data {
+--- a/include/net/dsa.h
++++ b/include/net/dsa.h
+@@ -465,7 +465,7 @@ struct dsa_switch {
+ 	/*
+ 	 * Slave mii_bus and devices for the individual ports.
+ 	 */
+-	u32			phys_mii_mask;
++	u64			phys_mii_mask;
+ 	struct mii_bus		*slave_mii_bus;
+ 
+ 	/* Ageing Time limits in msecs */
+@@ -597,24 +597,24 @@ static inline bool dsa_is_user_port(stru
+ 	dsa_switch_for_each_port_continue_reverse((_dp), (_ds)) \
+ 		if (dsa_port_is_cpu((_dp)))
+ 
+-static inline u32 dsa_user_ports(struct dsa_switch *ds)
++static inline u64 dsa_user_ports(struct dsa_switch *ds)
+ {
+ 	struct dsa_port *dp;
+-	u32 mask = 0;
++	u64 mask = 0;
+ 
+ 	dsa_switch_for_each_user_port(dp, ds)
+-		mask |= BIT(dp->index);
++		mask |= BIT_ULL(dp->index);
+ 
+ 	return mask;
+ }
+ 
+-static inline u32 dsa_cpu_ports(struct dsa_switch *ds)
++static inline u64 dsa_cpu_ports(struct dsa_switch *ds)
+ {
+ 	struct dsa_port *cpu_dp;
+-	u32 mask = 0;
++	u64 mask = 0;
+ 
+ 	dsa_switch_for_each_cpu_port(cpu_dp, ds)
+-		mask |= BIT(cpu_dp->index);
++		mask |= BIT_ULL(cpu_dp->index);
+ 
+ 	return mask;
+ }
+--- a/net/dsa/slave.c
++++ b/net/dsa/slave.c
+@@ -320,7 +320,7 @@ static int dsa_slave_phy_read(struct mii
+ {
+ 	struct dsa_switch *ds = bus->priv;
+ 
+-	if (ds->phys_mii_mask & (1 << addr))
++	if (ds->phys_mii_mask & BIT_ULL(addr))
+ 		return ds->ops->phy_read(ds, addr, reg);
+ 
+ 	return 0xffff;
+@@ -330,7 +330,7 @@ static int dsa_slave_phy_write(struct mi
+ {
+ 	struct dsa_switch *ds = bus->priv;
+ 
+-	if (ds->phys_mii_mask & (1 << addr))
++	if (ds->phys_mii_mask & BIT_ULL(addr))
+ 		return ds->ops->phy_write(ds, addr, reg, val);
+ 
+ 	return 0;
diff --git a/target/linux/realtek/patches-6.6/702-include-linux-add-phy-hsgmii-mode.patch b/target/linux/realtek/patches-6.6/702-include-linux-add-phy-hsgmii-mode.patch
new file mode 100644
index 0000000000..a43b49c1ff
--- /dev/null
+++ b/target/linux/realtek/patches-6.6/702-include-linux-add-phy-hsgmii-mode.patch
@@ -0,0 +1,79 @@
+From 9d9bf16aa8d966834ac1280f96c37d22552c33d1 Mon Sep 17 00:00:00 2001
+From: Birger Koblitz <git at birger-koblitz.de>
+Date: Wed, 8 Sep 2021 16:13:18 +0200
+Subject: realtek phy: Add PHY hsgmii mode
+
+This adds RTL93xx-specific MAC configuration routines that allow also configuration
+of 10GBit links for phylink. There is support for the Realtek-specific HSGMII
+protocol.
+
+Submitted-by: Birger Koblitz <git at birger-koblitz.de>
+---
+ drivers/net/phy/phy-core.c | 1 +
+ drivers/net/phy/phylink.c  | 4 ++++
+ include/linux/phy.h        | 3 +++
+ 3 files changed, 8 insertions(+)
+
+--- a/drivers/net/phy/phy-core.c
++++ b/drivers/net/phy/phy-core.c
+@@ -126,6 +126,7 @@ int phy_interface_num_ports(phy_interfac
+ 	case PHY_INTERFACE_MODE_MOCA:
+ 	case PHY_INTERFACE_MODE_TRGMII:
+ 	case PHY_INTERFACE_MODE_USXGMII:
++	case PHY_INTERFACE_MODE_HSGMII:
+ 	case PHY_INTERFACE_MODE_SGMII:
+ 	case PHY_INTERFACE_MODE_SMII:
+ 	case PHY_INTERFACE_MODE_1000BASEX:
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -124,6 +124,7 @@ do {									\
+ static const phy_interface_t phylink_sfp_interface_preference[] = {
+ 	PHY_INTERFACE_MODE_25GBASER,
+ 	PHY_INTERFACE_MODE_USXGMII,
++	PHY_INTERFACE_MODE_HSGMII,
+ 	PHY_INTERFACE_MODE_10GBASER,
+ 	PHY_INTERFACE_MODE_5GBASER,
+ 	PHY_INTERFACE_MODE_2500BASEX,
+@@ -238,6 +239,7 @@ static int phylink_interface_max_speed(p
+ 
+ 	case PHY_INTERFACE_MODE_XGMII:
+ 	case PHY_INTERFACE_MODE_RXAUI:
++	case PHY_INTERFACE_MODE_HSGMII:
+ 	case PHY_INTERFACE_MODE_XAUI:
+ 	case PHY_INTERFACE_MODE_10GBASER:
+ 	case PHY_INTERFACE_MODE_10GKR:
+@@ -547,6 +549,7 @@ unsigned long phylink_get_capabilities(p
+ 		break;
+ 
+ 	case PHY_INTERFACE_MODE_XGMII:
++	case PHY_INTERFACE_MODE_HSGMII:
+ 	case PHY_INTERFACE_MODE_RXAUI:
+ 	case PHY_INTERFACE_MODE_XAUI:
+ 	case PHY_INTERFACE_MODE_10GBASER:
+@@ -957,6 +960,7 @@ static int phylink_parse_mode(struct phy
+ 			fallthrough;
+ 		case PHY_INTERFACE_MODE_USXGMII:
+ 		case PHY_INTERFACE_MODE_10GKR:
++		case PHY_INTERFACE_MODE_HSGMII:
+ 		case PHY_INTERFACE_MODE_10GBASER:
+ 			phylink_set(pl->supported, 10baseT_Half);
+ 			phylink_set(pl->supported, 10baseT_Full);
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -148,6 +148,7 @@ typedef enum {
+ 	PHY_INTERFACE_MODE_XGMII,
+ 	PHY_INTERFACE_MODE_XLGMII,
+ 	PHY_INTERFACE_MODE_MOCA,
++	PHY_INTERFACE_MODE_HSGMII,
+ 	PHY_INTERFACE_MODE_PSGMII,
+ 	PHY_INTERFACE_MODE_QSGMII,
+ 	PHY_INTERFACE_MODE_TRGMII,
+@@ -256,6 +257,8 @@ static inline const char *phy_modes(phy_
+ 		return "xlgmii";
+ 	case PHY_INTERFACE_MODE_MOCA:
+ 		return "moca";
++	case PHY_INTERFACE_MODE_HSGMII:
++		return "hsgmii";
+ 	case PHY_INTERFACE_MODE_PSGMII:
+ 		return "psgmii";
+ 	case PHY_INTERFACE_MODE_QSGMII:
diff --git a/target/linux/realtek/patches-6.6/706-include-linux-add-phy-ops-for-rtl838x.patch b/target/linux/realtek/patches-6.6/706-include-linux-add-phy-ops-for-rtl838x.patch
new file mode 100644
index 0000000000..3404d55ed3
--- /dev/null
+++ b/target/linux/realtek/patches-6.6/706-include-linux-add-phy-ops-for-rtl838x.patch
@@ -0,0 +1,32 @@
+From 2b88563ee5aafd9571d965b7f2093a0f58d98a31 Mon Sep 17 00:00:00 2001
+From: John Crispin <john at phrozen.org>
+Date: Thu, 26 Nov 2020 12:02:21 +0100
+Subject: phy: Add PHY ops for rtl838x EEE
+
+* rename the target to realtek
+* add refactored DSA driver
+* add latest gpio driver
+* lots of arch cleanups
+* new irq driver
+* additional boards
+
+Submitted-by: Bert Vermeulen <bert at biot.com>
+Submitted-by: Birger Koblitz <mail at birger-koblitz.de>
+Submitted-by: Sander Vanheule <sander at svanheule.net>
+Submitted-by: Bjørn Mork <bjorn at mork.no>
+Submitted-by: John Crispin <john at phrozen.org>
+---
+ include/linux/phy.h                           | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -1181,6 +1181,8 @@ struct phy_driver {
+ 	 */
+ 	int (*led_polarity_set)(struct phy_device *dev, int index,
+ 				unsigned long modes);
++	int (*get_eee)(struct phy_device *dev, struct ethtool_eee *e);
++	int (*set_eee)(struct phy_device *dev, struct ethtool_eee *e);
+ };
+ #define to_phy_driver(d) container_of(to_mdio_common_driver(d),		\
+ 				      struct phy_driver, mdiodrv)
diff --git a/target/linux/realtek/patches-6.6/708-drivers-net-phy-eee-support-for-rtl838x.patch b/target/linux/realtek/patches-6.6/708-drivers-net-phy-eee-support-for-rtl838x.patch
new file mode 100644
index 0000000000..44461992b1
--- /dev/null
+++ b/target/linux/realtek/patches-6.6/708-drivers-net-phy-eee-support-for-rtl838x.patch
@@ -0,0 +1,49 @@
+From 2b88563ee5aafd9571d965b7f2093a0f58d98a31 Mon Sep 17 00:00:00 2001
+From: John Crispin <john at phrozen.org>
+Date: Thu, 26 Nov 2020 12:02:21 +0100
+Subject: net: phy: EEE support for rtl838x
+
+* rename the target to realtek
+* add refactored DSA driver
+* add latest gpio driver
+* lots of arch cleanups
+* new irq driver
+* additional boards
+
+Submitted-by: Bert Vermeulen <bert at biot.com>
+Submitted-by: Birger Koblitz <mail at birger-koblitz.de>
+Submitted-by: Sander Vanheule <sander at svanheule.net>
+Submitted-by: Bjørn Mork <bjorn at mork.no>
+Submitted-by: John Crispin <john at phrozen.org>
+---
+ drivers/net/phy/phylink.                      | 14 +++++++++++--
+ 1 file changed, 12 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -2805,8 +2805,11 @@ int phylink_ethtool_get_eee(struct phyli
+ 
+ 	ASSERT_RTNL();
+ 
+-	if (pl->phydev)
++	if (pl->phydev) {
++		if (pl->phydev->drv->get_eee)
++			return pl->phydev->drv->get_eee(pl->phydev, eee);
+ 		ret = phy_ethtool_get_eee(pl->phydev, eee);
++	}
+ 
+ 	return ret;
+ }
+@@ -2823,8 +2826,11 @@ int phylink_ethtool_set_eee(struct phyli
+ 
+ 	ASSERT_RTNL();
+ 
+-	if (pl->phydev)
++	if (pl->phydev) {
++		if (pl->phydev->drv->set_eee)
++			return pl->phydev->drv->set_eee(pl->phydev, eee);
+ 		ret = phy_ethtool_set_eee(pl->phydev, eee);
++	}
+ 
+ 	return ret;
+ }
diff --git a/target/linux/realtek/patches-6.6/712-net-phy-add-an-MDIO-SMBus-library.patch b/target/linux/realtek/patches-6.6/712-net-phy-add-an-MDIO-SMBus-library.patch
new file mode 100644
index 0000000000..229e2612ea
--- /dev/null
+++ b/target/linux/realtek/patches-6.6/712-net-phy-add-an-MDIO-SMBus-library.patch
@@ -0,0 +1,428 @@
+From d585c55b9f70cf9e8c66820d7efe7130c683f19e Mon Sep 17 00:00:00 2001
+From: Antoine Tenart <antoine.tenart at bootlin.com>
+Date: Fri, 21 Feb 2020 11:51:27 +0100
+Subject: [PATCH 2/3] net: phy: add an MDIO SMBus library
+
+Signed-off-by: Antoine Tenart <antoine.tenart at bootlin.com>
+---
+ drivers/net/mdio/Kconfig      | 11 +++++++
+ drivers/net/mdio/Makefile     |  1 +
+ drivers/net/mdio/mdio-smbus.c | 62 +++++++++++++++++++++++++++++++++++
+ drivers/net/phy/Kconfig       |  1 +
+ include/linux/mdio/mdio-i2c.h | 16 +++++++++
+ 5 files changed, 91 insertions(+)
+ create mode 100644 drivers/net/mdio/mdio-smbus.c
+
+--- a/drivers/net/mdio/Kconfig
++++ b/drivers/net/mdio/Kconfig
+@@ -54,6 +54,17 @@ config MDIO_SUN4I
+ 	  interface units of the Allwinner SoC that have an EMAC (A10,
+ 	  A12, A10s, etc.)
+ 
++config MDIO_SMBUS
++	tristate
++	depends on I2C_SMBUS
++	help
++	  Support SMBus based PHYs. This provides a MDIO bus bridged
++	  to SMBus to allow PHYs connected in SMBus mode to be accessed
++	  using the existing infrastructure.
++
++	  This is library mode.
++
++
+ config MDIO_XGENE
+ 	tristate "APM X-Gene SoC MDIO bus controller"
+ 	depends on ARCH_XGENE || COMPILE_TEST
+--- a/drivers/net/mdio/Makefile
++++ b/drivers/net/mdio/Makefile
+@@ -20,6 +20,7 @@ obj-$(CONFIG_MDIO_MSCC_MIIM)		+= mdio-ms
+ obj-$(CONFIG_MDIO_MVUSB)		+= mdio-mvusb.o
+ obj-$(CONFIG_MDIO_OCTEON)		+= mdio-octeon.o
+ obj-$(CONFIG_MDIO_REGMAP)		+= mdio-regmap.o
++obj-$(CONFIG_MDIO_SMBUS)		+= mdio-smbus.o
+ obj-$(CONFIG_MDIO_SUN4I)		+= mdio-sun4i.o
+ obj-$(CONFIG_MDIO_THUNDER)		+= mdio-thunder.o
+ obj-$(CONFIG_MDIO_XGENE)		+= mdio-xgene.o
+--- /dev/null
++++ b/drivers/net/mdio/mdio-smbus.c
+@@ -0,0 +1,341 @@
++// SPDX-License-Identifier: GPL-2.0-or-later
++/*
++ * MDIO SMBus bridge
++ *
++ * Copyright (C) 2020 Antoine Tenart
++ * Copyright (C) 2025 Bjørn Mork <bjorn at mork.no>
++ *
++ * Network PHYs can appear on SMBus when they are part of SFP modules.
++ */
++#include <linux/i2c.h>
++#include <linux/phy.h>
++#include <linux/mdio/mdio-i2c.h>
++#include <linux/sfp.h>
++
++static int smbus_mii_read_c45(struct mii_bus *mii, int phy_id, int devad, int reg)
++{
++	u16 bus_addr = i2c_mii_phy_addr(phy_id);
++	struct i2c_adapter *i2c = mii->priv;
++	union i2c_smbus_data data;
++	size_t addrlen;
++	u8 buf[5], *p;
++	int i, ret;
++
++	if (!i2c_mii_valid_phy_id(phy_id))
++		return 0xffff;
++
++	p = buf;
++	if (devad >= 0) {
++		*p++ = 0x20 | devad;
++		*p++ = reg >> 8;
++	}
++	*p++ = reg;
++	addrlen = p - buf;
++
++	i2c_lock_bus(i2c, I2C_LOCK_SEGMENT);
++	if (addrlen > 1) {
++		for (i = 1; i < addrlen; i++) {
++			data.byte = buf[i];
++			ret = __i2c_smbus_xfer(i2c, bus_addr, 0, I2C_SMBUS_WRITE, buf[0], I2C_SMBUS_BYTE_DATA, &data);
++			if (ret < 0)
++				goto unlock;
++		}
++	}
++
++	for (i = addrlen; i < addrlen + 2; i++) {
++		ret = __i2c_smbus_xfer(i2c, bus_addr, 0, I2C_SMBUS_READ, buf[0], I2C_SMBUS_BYTE_DATA, &data);
++		if (ret < 0)
++			goto unlock;
++		buf[i] = data.byte;
++	}
++
++unlock:
++	i2c_unlock_bus(i2c, I2C_LOCK_SEGMENT);
++	if (ret < 0)
++		return 0xffff;
++	return buf[addrlen] << 8 | buf[addrlen + 1];
++}
++
++static int smbus_mii_write_c45(struct mii_bus *mii, int phy_id, int devad, int reg, u16 val)
++{
++	u16 bus_addr = i2c_mii_phy_addr(phy_id);
++	struct i2c_adapter *i2c = mii->priv;
++	union i2c_smbus_data data;
++	size_t buflen;
++	u8 buf[5], *p;
++	int i, ret;
++
++	if (!i2c_mii_valid_phy_id(phy_id))
++		return 0;
++
++	p = buf;
++	if (devad >= 0) {
++		*p++ = devad;
++		*p++ = reg >> 8;
++	}
++	*p++ = reg;
++	*p++ = val >> 8;
++	*p++ = val;
++	buflen = p - buf;
++
++	i2c_lock_bus(i2c, I2C_LOCK_SEGMENT);
++	for (i = 1; i < buflen; i++) {
++		data.byte = buf[i];
++		ret = __i2c_smbus_xfer(i2c, bus_addr, 0, I2C_SMBUS_WRITE, buf[0], I2C_SMBUS_BYTE_DATA, &data);
++		if (ret < 0)
++			goto unlock;
++	}
++unlock:
++	i2c_unlock_bus(i2c, I2C_LOCK_SEGMENT);
++	return ret < 0 ? ret : 0;
++}
++
++static int smbus_mii_read_c22(struct mii_bus *bus, int phy_id, int reg)
++{
++	return smbus_mii_read_c45(bus, phy_id, -1, reg);
++}
++
++static int smbus_mii_write_c22(struct mii_bus *bus, int phy_id, int reg, u16 val)
++{
++	return smbus_mii_write_c45(bus, phy_id, -1, reg, val);
++}
++
++/* From mdio-i2c.c:
++ *
++ * RollBall SFPs do not access internal PHY via I2C address 0x56, but
++ * instead via address 0x51, when SFP page is set to 0x03 and password to
++ * 0xffffffff.
++ *
++ * address  size  contents  description
++ * -------  ----  --------  -----------
++ * 0x80     1     CMD       0x01/0x02/0x04 for write/read/done
++ * 0x81     1     DEV       Clause 45 device
++ * 0x82     2     REG       Clause 45 register
++ * 0x84     2     VAL       Register value
++ */
++#define ROLLBALL_PHY_I2C_ADDR		0x51
++
++#define ROLLBALL_PASSWORD		(SFP_VSL + 3)
++
++#define ROLLBALL_CMD_ADDR		0x80
++#define ROLLBALL_DATA_ADDR		0x81
++
++#define ROLLBALL_CMD_WRITE		0x01
++#define ROLLBALL_CMD_READ		0x02
++#define ROLLBALL_CMD_DONE		0x04
++
++#define SFP_PAGE_ROLLBALL_MDIO		3
++
++static int smbus_set_sfp_page_lock(struct i2c_adapter *i2c, int bus_addr, u8 page)
++{
++	union i2c_smbus_data data;
++	u8 oldpage;
++	int ret;
++
++	i2c_lock_bus(i2c, I2C_LOCK_SEGMENT);
++
++	/* read current page */
++	ret = __i2c_smbus_xfer(i2c, bus_addr, 0, I2C_SMBUS_READ, SFP_PAGE, I2C_SMBUS_BYTE_DATA, &data);
++	if (ret < 0)
++		goto unlock;
++
++	oldpage = data.byte;
++	data.byte = page;
++	ret = __i2c_smbus_xfer(i2c, bus_addr, 0, I2C_SMBUS_WRITE, SFP_PAGE, I2C_SMBUS_BYTE_DATA, &data);
++	if (ret == 0)
++		return oldpage;
++
++unlock:
++	i2c_unlock_bus(i2c, I2C_LOCK_SEGMENT);
++
++	return ret;
++}
++
++static int __smbus_set_sfp_page_unlock(struct i2c_adapter *i2c, int bus_addr, u8 page)
++{
++	union i2c_smbus_data data;
++	int ret;
++
++	data.byte = page;
++	ret = __i2c_smbus_xfer(i2c, bus_addr, 0, I2C_SMBUS_WRITE, SFP_PAGE, I2C_SMBUS_BYTE_DATA, &data);
++	i2c_unlock_bus(i2c, I2C_LOCK_SEGMENT);
++
++	return ret;
++}
++
++/* Wait for the ROLLBALL_CMD_ADDR register to read ROLLBALL_CMD_DONE,
++ * indicating that the previous command has completed.
++ *
++ * Quoting from the mdio-i2c.c implementation:
++ *
++ * 	By experiment it takes up to 70 ms to access a register for these
++ * 	SFPs. Sleep 20ms between iterations and try 10 times.
++ */
++static int __smbus_rollball_mii_poll(struct i2c_adapter *i2c , int bus_addr)
++{
++	union i2c_smbus_data data;
++	int i, ret;
++
++	i = 10;
++	do {
++		msleep(20);
++
++		ret = __i2c_smbus_xfer(i2c, bus_addr, 0, I2C_SMBUS_READ, ROLLBALL_CMD_ADDR, I2C_SMBUS_BYTE_DATA, &data);
++		if (ret < 0)
++			return ret;
++
++		if (data.byte == ROLLBALL_CMD_DONE)
++			return 0;
++	} while (i-- > 0);
++	dev_dbg(&i2c->dev, "poll timed out\n");
++	return -ETIMEDOUT;
++}
++
++static int smbus_mii_read_rollball(struct mii_bus *bus, int phy_id, int devad, int reg)
++{
++	struct i2c_adapter *i2c = bus->priv;
++	union i2c_smbus_data data;
++	int i, bus_addr, old, ret;
++	u8 buf[6];
++
++	bus_addr = i2c_mii_phy_addr(phy_id);
++	if (bus_addr != ROLLBALL_PHY_I2C_ADDR)
++ 		return 0xffff;
++
++	old = smbus_set_sfp_page_lock(i2c, bus_addr, SFP_PAGE_ROLLBALL_MDIO);
++	if (old < 0)
++		return 0xffff;
++
++	/* set address */
++	buf[0] = ROLLBALL_CMD_READ;
++	buf[1] = devad;
++	buf[2] = reg >> 8;
++	buf[3] = reg & 0xff;
++
++	/* send address */
++	for (i = 0; i < 4; i++) {
++		data.byte = buf[i];
++		ret = __i2c_smbus_xfer(i2c, bus_addr, 0, I2C_SMBUS_WRITE, ROLLBALL_CMD_ADDR + i, I2C_SMBUS_BYTE_DATA, &data);
++		if (ret < 0)
++			goto unlock;
++	}
++
++	/* wait for command to complete */
++	ret = __smbus_rollball_mii_poll(i2c, bus_addr);
++	if (ret)
++		goto unlock;
++
++	/* read result */
++	for (i = 4; i < 6; i++) {
++		ret = __i2c_smbus_xfer(i2c, bus_addr, 0, I2C_SMBUS_READ, ROLLBALL_CMD_ADDR + i, I2C_SMBUS_BYTE_DATA, &data);
++		if (ret < 0)
++			goto unlock;
++		buf[i] = data.byte;
++	}
++
++unlock:
++	__smbus_set_sfp_page_unlock(i2c, bus_addr, old);
++	if (ret < 0)
++		return 0xffff;
++	return buf[4] << 8 | buf[5];
++}
++
++static int smbus_mii_write_rollball(struct mii_bus *bus, int phy_id, int devad, int reg, u16 val)
++{
++	struct i2c_adapter *i2c = bus->priv;
++	union i2c_smbus_data data;
++	int i, bus_addr, old, ret;
++	u8 buf[6];
++
++	bus_addr = i2c_mii_phy_addr(phy_id);
++	if (bus_addr != ROLLBALL_PHY_I2C_ADDR)
++		return 0;
++
++	old = smbus_set_sfp_page_lock(i2c, bus_addr, SFP_PAGE_ROLLBALL_MDIO);
++	if (old < 0)
++		return old;
++
++	/* set address */
++	buf[0] = ROLLBALL_CMD_WRITE;
++	buf[1] = devad;
++	buf[2] = reg >> 8;
++	buf[3] = reg & 0xff;
++	buf[4] = val >> 8;
++	buf[5] = val & 0xff;
++
++	/* send address and value */
++	for (i = 0; i < 6; i++) {
++		data.byte = buf[i];
++		ret = __i2c_smbus_xfer(i2c, bus_addr, 0, I2C_SMBUS_WRITE, ROLLBALL_CMD_ADDR + i, I2C_SMBUS_BYTE_DATA, &data);
++		if (ret < 0)
++			goto unlock;
++	}
++
++	/* wait for command to complete */
++	ret = __smbus_rollball_mii_poll(i2c, bus_addr);
++
++unlock:
++	__smbus_set_sfp_page_unlock(i2c, bus_addr, old);
++	return ret;
++}
++
++/* write "password" - four 0xff bytes - to the ROLLBALL_PASSWORD register */
++static int smbus_mii_init_rollball(struct i2c_adapter *i2c)
++{
++	union i2c_smbus_data data;
++	int i, ret;
++
++	data.byte = 0xff;
++	for (i = 0; i < 4; i++) {
++		ret = i2c_smbus_xfer(i2c, ROLLBALL_PHY_I2C_ADDR, 0, I2C_SMBUS_WRITE, ROLLBALL_PASSWORD + i, I2C_SMBUS_BYTE_DATA, &data);
++		if (ret < 0)
++			return ret;
++	}
++	return 0;
++}
++
++struct mii_bus *mdio_smbus_alloc(struct device *parent, struct i2c_adapter *i2c,
++				 enum mdio_i2c_proto protocol)
++{
++	struct mii_bus *mii;
++	int ret;
++
++	if (!i2c_check_functionality(i2c, I2C_FUNC_SMBUS_BYTE_DATA))
++		return ERR_PTR(-EINVAL);
++
++	mii = mdiobus_alloc();
++	if (!mii)
++		return ERR_PTR(-ENOMEM);
++
++	snprintf(mii->id, MII_BUS_ID_SIZE, "smbus:%s", dev_name(parent));
++	mii->parent = parent;
++	mii->priv = i2c;
++
++	switch (protocol) {
++	case MDIO_I2C_ROLLBALL:
++		ret = smbus_mii_init_rollball(i2c);
++		if (ret < 0) {
++			dev_err(parent,
++				"Cannot initialize RollBall MDIO protocol on SMBus: %d\n",
++				ret);
++			mdiobus_free(mii);
++			return ERR_PTR(ret);
++		}
++
++		mii->read_c45 = smbus_mii_read_rollball;
++		mii->write_c45 = smbus_mii_write_rollball;
++		break;
++	default:
++		mii->read = smbus_mii_read_c22;
++		mii->write = smbus_mii_write_c22;
++		mii->read_c45 = smbus_mii_read_c45;
++		mii->write_c45 = smbus_mii_write_c45;
++		break;
++	}
++
++	return mii;
++}
++
++MODULE_AUTHOR("Antoine Tenart");
++MODULE_DESCRIPTION("MDIO SMBus bridge library");
++MODULE_LICENSE("GPL");
+--- a/drivers/net/phy/Kconfig
++++ b/drivers/net/phy/Kconfig
+@@ -65,6 +65,7 @@ config SFP
+ 	depends on I2C && PHYLINK
+ 	depends on HWMON || HWMON=n
+ 	select MDIO_I2C
++	select MDIO_SMBUS
+ 
+ comment "Switch configuration API + drivers"
+ 
+--- a/include/linux/mdio/mdio-i2c.h
++++ b/include/linux/mdio/mdio-i2c.h
+@@ -20,5 +20,9 @@ enum mdio_i2c_proto {
+ 
+ struct mii_bus *mdio_i2c_alloc(struct device *parent, struct i2c_adapter *i2c,
+ 			       enum mdio_i2c_proto protocol);
++struct mii_bus *mdio_smbus_alloc(struct device *parent, struct i2c_adapter *i2c,
++				 enum mdio_i2c_proto protocol);
++bool i2c_mii_valid_phy_id(int phy_id);
++unsigned int i2c_mii_phy_addr(int phy_id);
+ 
+ #endif
+--- a/drivers/net/mdio/mdio-i2c.c
++++ b/drivers/net/mdio/mdio-i2c.c
+@@ -20,12 +20,12 @@
+  * specified to be present in SFP modules.  These correspond with PHY
+  * addresses 16 and 17.  Disallow access to these "phy" addresses.
+  */
+-static bool i2c_mii_valid_phy_id(int phy_id)
++bool i2c_mii_valid_phy_id(int phy_id)
+ {
+ 	return phy_id != 0x10 && phy_id != 0x11;
+ }
+ 
+-static unsigned int i2c_mii_phy_addr(int phy_id)
++unsigned int i2c_mii_phy_addr(int phy_id)
+ {
+ 	return phy_id + 0x40;
+ }
diff --git a/target/linux/realtek/patches-6.6/714-net-phy-sfp-add-support-for-SMBus.patch b/target/linux/realtek/patches-6.6/714-net-phy-sfp-add-support-for-SMBus.patch
new file mode 100644
index 0000000000..4fb1e7f293
--- /dev/null
+++ b/target/linux/realtek/patches-6.6/714-net-phy-sfp-add-support-for-SMBus.patch
@@ -0,0 +1,126 @@
+From 3cb0bde365d913c484d20224367a54a0eac780a7 Mon Sep 17 00:00:00 2001
+From: Antoine Tenart <antoine.tenart at bootlin.com>
+Date: Fri, 21 Feb 2020 11:55:29 +0100
+Subject: [PATCH 3/3] net: phy: sfp: add support for SMBus
+
+Signed-off-by: Antoine Tenart <antoine.tenart at bootlin.com>
+---
+ drivers/net/phy/sfp.c | 92 +++++++++++++++++++++++++++++++++++++++++--
+ 1 file changed, 88 insertions(+), 4 deletions(-)
+
+--- a/drivers/net/phy/sfp.c
++++ b/drivers/net/phy/sfp.c
+@@ -675,10 +675,64 @@ static int sfp_i2c_write(struct sfp *sfp
+ 	return ret == ARRAY_SIZE(msgs) ? len : 0;
+ }
+ 
++static int sfp_smbus_read(struct sfp *sfp, bool a2, u8 dev_addr, void *buf,
++			  size_t len)
++{
++	u8 bus_addr = a2 ? 0x51 : 0x50, *val = buf;
++	union i2c_smbus_data data;
++	int ret;
++
++	bus_addr -= 0x40;
++
++	while (len > 0) {
++		ret = i2c_smbus_xfer(sfp->i2c, i2c_mii_phy_addr(bus_addr), 0,
++				     I2C_SMBUS_READ, dev_addr,
++				     I2C_SMBUS_BYTE_DATA, &data);
++		if (ret)
++			return ret;
++		*val++ = data.byte;
++		dev_addr++;
++		len--;
++	}
++
++	return val - (u8 *)buf;
++}
++
++static int sfp_smbus_write(struct sfp *sfp, bool a2, u8 dev_addr, void *buf,
++			  size_t len)
++{
++	u8 bus_addr = a2 ? 0x51 : 0x50, *val = buf;
++	union i2c_smbus_data data;
++	int ret;
++
++	bus_addr -= 0x40;
++
++	while (len > 0) {
++		data.byte = *val++;
++		ret = i2c_smbus_xfer(sfp->i2c, i2c_mii_phy_addr(bus_addr), 0,
++				     I2C_SMBUS_WRITE, dev_addr,
++				     I2C_SMBUS_BYTE_DATA, &data);
++		if (ret)
++			return ret;
++		dev_addr++;
++		len--;
++	}
++
++	return val - (u8 *)buf;
++}
++
+ static int sfp_i2c_configure(struct sfp *sfp, struct i2c_adapter *i2c)
+ {
+-	if (!i2c_check_functionality(i2c, I2C_FUNC_I2C))
+-		return -EINVAL;
++	if (!i2c_check_functionality(i2c, I2C_FUNC_I2C)) {
++		if (i2c_check_functionality(i2c, I2C_FUNC_SMBUS_BYTE_DATA)) {
++			sfp->i2c = i2c;
++			sfp->read = sfp_smbus_read;
++			sfp->write = sfp_smbus_write;
++
++			return 0;
++		} else
++			return -EINVAL;
++	}
+ 
+ 	sfp->i2c = i2c;
+ 	sfp->read = sfp_i2c_read;
+@@ -710,6 +764,29 @@ static int sfp_i2c_mdiobus_create(struct
+ 	return 0;
+ }
+ 
++static int sfp_sm_mdiobus_create(struct sfp *sfp)
++{
++	struct mii_bus *sm_mii;
++	int ret;
++
++	sm_mii = mdio_smbus_alloc(sfp->dev, sfp->i2c, sfp->mdio_protocol);
++	if (IS_ERR(sm_mii))
++		return PTR_ERR(sm_mii);
++
++	sm_mii->name = "SFP SMBus";
++	sm_mii->phy_mask = ~0;
++
++	ret = mdiobus_register(sm_mii);
++	if (ret < 0) {
++		mdiobus_free(sm_mii);
++		return ret;
++	}
++
++	sfp->i2c_mii = sm_mii;
++
++	return 0;
++}
++
+ static void sfp_i2c_mdiobus_destroy(struct sfp *sfp)
+ {
+ 	mdiobus_unregister(sfp->i2c_mii);
+@@ -1884,9 +1961,15 @@ static void sfp_sm_fault(struct sfp *sfp
+ 
+ static int sfp_sm_add_mdio_bus(struct sfp *sfp)
+ {
+-	if (sfp->mdio_protocol != MDIO_I2C_NONE)
++	if (sfp->mdio_protocol == MDIO_I2C_NONE)
++		return 0;
++
++	if (i2c_check_functionality(sfp->i2c, I2C_FUNC_I2C))
+ 		return sfp_i2c_mdiobus_create(sfp);
+ 
++	if (i2c_check_functionality(sfp->i2c, I2C_FUNC_SMBUS_BYTE_DATA))
++		return sfp_sm_mdiobus_create(sfp);
++
+ 	return 0;
+ }
+ 
diff --git a/target/linux/realtek/patches-6.6/716-net-ethernet-add-support-for-rtl838x-ethernet.patch b/target/linux/realtek/patches-6.6/716-net-ethernet-add-support-for-rtl838x-ethernet.patch
new file mode 100644
index 0000000000..e0c10bca63
--- /dev/null
+++ b/target/linux/realtek/patches-6.6/716-net-ethernet-add-support-for-rtl838x-ethernet.patch
@@ -0,0 +1,48 @@
+From 2b88563ee5aafd9571d965b7f2093a0f58d98a31 Mon Sep 17 00:00:00 2001
+From: John Crispin <john at phrozen.org>
+Date: Thu, 26 Nov 2020 12:02:21 +0100
+Subject: net: ethernet: Add support for RTL838x ethernet
+
+* rename the target to realtek
+* add refactored DSA driver
+* add latest gpio driver
+* lots of arch cleanups
+* new irq driver
+* additional boards
+
+Submitted-by: Bert Vermeulen <bert at biot.com>
+Submitted-by: Birger Koblitz <mail at birger-koblitz.de>
+Submitted-by: Sander Vanheule <sander at svanheule.net>
+Submitted-by: Bjørn Mork <bjorn at mork.no>
+Submitted-by: John Crispin <john at phrozen.org>
+---
+ drivers/net/ethernet/Kconfig                  | 7 +-
+ drivers/net/ethernet/Makefile                 | 1 +
+ 2 files changed, 8 insertions(+)
+
+--- a/drivers/net/ethernet/Kconfig
++++ b/drivers/net/ethernet/Kconfig
+@@ -170,6 +170,13 @@ source "drivers/net/ethernet/rdc/Kconfig
+ source "drivers/net/ethernet/realtek/Kconfig"
+ source "drivers/net/ethernet/renesas/Kconfig"
+ source "drivers/net/ethernet/rocker/Kconfig"
++
++config NET_RTL838X
++	tristate "Realtek rtl838x Ethernet MAC support"
++	depends on MACH_REALTEK_RTL
++	help
++	  Say Y here if you want to use the Realtek rtl838x Gbps Ethernet MAC.
++
+ source "drivers/net/ethernet/samsung/Kconfig"
+ source "drivers/net/ethernet/seeq/Kconfig"
+ source "drivers/net/ethernet/sgi/Kconfig"
+--- a/drivers/net/ethernet/Makefile
++++ b/drivers/net/ethernet/Makefile
+@@ -81,6 +81,7 @@ obj-$(CONFIG_NET_VENDOR_REALTEK) += real
+ obj-$(CONFIG_NET_VENDOR_RENESAS) += renesas/
+ obj-$(CONFIG_NET_VENDOR_RDC) += rdc/
+ obj-$(CONFIG_NET_VENDOR_ROCKER) += rocker/
++obj-$(CONFIG_NET_RTL838X) += rtl838x_eth.o
+ obj-$(CONFIG_NET_VENDOR_SAMSUNG) += samsung/
+ obj-$(CONFIG_NET_VENDOR_SEEQ) += seeq/
+ obj-$(CONFIG_NET_VENDOR_SILAN) += silan/
diff --git a/target/linux/realtek/patches-6.6/718-net-dsa-add-support-for-rtl838x-switch.patch b/target/linux/realtek/patches-6.6/718-net-dsa-add-support-for-rtl838x-switch.patch
new file mode 100644
index 0000000000..786fc37091
--- /dev/null
+++ b/target/linux/realtek/patches-6.6/718-net-dsa-add-support-for-rtl838x-switch.patch
@@ -0,0 +1,42 @@
+From 2b88563ee5aafd9571d965b7f2093a0f58d98a31 Mon Sep 17 00:00:00 2001
+From: John Crispin <john at phrozen.org>
+Date: Thu, 26 Nov 2020 12:02:21 +0100
+Subject: net: dsa: Add support for rtl838x switch
+
+* rename the target to realtek
+* add refactored DSA driver
+* add latest gpio driver
+* lots of arch cleanups
+* new irq driver
+* additional boards
+
+Submitted-by: Bert Vermeulen <bert at biot.com>
+Submitted-by: Birger Koblitz <mail at birger-koblitz.de>
+Submitted-by: Sander Vanheule <sander at svanheule.net>
+Submitted-by: Bjørn Mork <bjorn at mork.no>
+Submitted-by: John Crispin <john at phrozen.org>
+---
+ drivers/net/dsa/rtl83xx/Kconfig               | 2 ++
+ drivers/net/dsa/rtl83xx/Makefile              | 1 +
+ 2 files changed, 3 insertions(+)
+
+--- a/drivers/net/dsa/Kconfig
++++ b/drivers/net/dsa/Kconfig
+@@ -89,6 +89,8 @@ source "drivers/net/dsa/xrs700x/Kconfig"
+ 
+ source "drivers/net/dsa/realtek/Kconfig"
+ 
++source "drivers/net/dsa/rtl83xx/Kconfig"
++
+ config NET_DSA_RZN1_A5PSW
+ 	tristate "Renesas RZ/N1 A5PSW Ethernet switch support"
+ 	depends on OF && ARCH_RZN1
+--- a/drivers/net/dsa/Makefile
++++ b/drivers/net/dsa/Makefile
+@@ -24,5 +24,6 @@ obj-y				+= mv88e6xxx/
+ obj-y				+= ocelot/
+ obj-y				+= qca/
+ obj-y				+= realtek/
++obj-y				+= rtl83xx/
+ obj-y				+= sja1105/
+ obj-y				+= xrs700x/
diff --git a/target/linux/realtek/patches-6.6/720-add-rtl-phy.patch b/target/linux/realtek/patches-6.6/720-add-rtl-phy.patch
new file mode 100644
index 0000000000..c7078cea4e
--- /dev/null
+++ b/target/linux/realtek/patches-6.6/720-add-rtl-phy.patch
@@ -0,0 +1,39 @@
+From 89f71ebb355c624320c2b0ace8ae9488ff53cbeb Mon Sep 17 00:00:00 2001
+From: Birger Koblitz <mail at birger-koblitz.de>
+Date: Tue, 5 Jan 2021 20:40:52 +0100
+Subject: PHY: Add realtek PHY
+
+This fixes the build problems for the REALTEK target by adding a proper
+configuration option for the phy module.
+
+Submitted-by: Birger Koblitz <mail at birger-koblitz.de>
+---
+ drivers/net/phy/Kconfig                       | 6 ++++++
+ drivers/net/phy/Makefile                      | 1 +
+ 2 files changed, 7 insertions(+)
+
+--- a/drivers/net/phy/Kconfig
++++ b/drivers/net/phy/Kconfig
+@@ -409,6 +409,12 @@ source "drivers/net/phy/realtek/Kconfig"
+ 
+ source "drivers/net/phy/rtl8261n/Kconfig"
+ 
++config REALTEK_SOC_PHY
++	tristate "Realtek SoC PHYs"
++	depends on MACH_REALTEK_RTL
++	help
++	  Supports the PHYs found in combination with Realtek Switch SoCs
++
+ config RENESAS_PHY
+ 	tristate "Renesas PHYs"
+ 	help
+--- a/drivers/net/phy/Makefile
++++ b/drivers/net/phy/Makefile
+@@ -101,6 +101,7 @@ obj-y				+= qcom/
+ obj-$(CONFIG_QSEMI_PHY)		+= qsemi.o
+ obj-$(CONFIG_REALTEK_PHY)	+= realtek/
+ obj-y				+= rtl8261n/
++obj-$(CONFIG_REALTEK_SOC_PHY)	+= rtl83xx-phy.o
+ obj-$(CONFIG_RENESAS_PHY)	+= uPD60620.o
+ obj-$(CONFIG_ROCKCHIP_PHY)	+= rockchip.o
+ obj-$(CONFIG_SMSC_PHY)		+= smsc.o
diff --git a/target/linux/realtek/patches-6.6/722-net-dsa-add-rtl838x-support-for-tag-trailer.patch b/target/linux/realtek/patches-6.6/722-net-dsa-add-rtl838x-support-for-tag-trailer.patch
new file mode 100644
index 0000000000..348c59092a
--- /dev/null
+++ b/target/linux/realtek/patches-6.6/722-net-dsa-add-rtl838x-support-for-tag-trailer.patch
@@ -0,0 +1,61 @@
+From 2b88563ee5aafd9571d965b7f2093a0f58d98a31 Mon Sep 17 00:00:00 2001
+From: John Crispin <john at phrozen.org>
+Date: Thu, 26 Nov 2020 12:02:21 +0100
+Subject: net: dsa: Add rtl838x support for tag trailer
+
+* rename the target to realtek
+* add refactored DSA driver
+* add latest gpio driver
+* lots of arch cleanups
+* new irq driver
+* additional boards
+
+Submitted-by: Bert Vermeulen <bert at biot.com>
+Submitted-by: Birger Koblitz <mail at birger-koblitz.de>
+Submitted-by: Sander Vanheule <sander at svanheule.net>
+Submitted-by: Bjørn Mork <bjorn at mork.no>
+Submitted-by: John Crispin <john at phrozen.org>
+---
+ net/dsa/tag_trailer.c                         | 16 +++++++++++++-
+ 1 file changed, 17 insertions(+), 1 deletion(-)
+
+--- a/net/dsa/tag_trailer.c
++++ b/net/dsa/tag_trailer.c
+@@ -19,7 +19,12 @@ static struct sk_buff *trailer_xmit(stru
+ 
+ 	trailer = skb_put(skb, 4);
+ 	trailer[0] = 0x80;
++
++#ifdef CONFIG_NET_DSA_RTL83XX
++	trailer[1] = dp->index;
++#else
+ 	trailer[1] = 1 << dp->index;
++#endif /* CONFIG_NET_DSA_RTL838X */
+ 	trailer[2] = 0x10;
+ 	trailer[3] = 0x00;
+ 
+@@ -35,12 +40,23 @@ static struct sk_buff *trailer_rcv(struc
+ 		return NULL;
+ 
+ 	trailer = skb_tail_pointer(skb) - 4;
++
++#ifdef CONFIG_NET_DSA_RTL83XX
++	if (trailer[0] != 0x80 || (trailer[1] & 0x80) != 0x00 ||
++	    (trailer[2] & 0xef) != 0x00 || trailer[3] != 0x00)
++		return NULL;
++
++	if (trailer[1] & 0x40)
++		skb->offload_fwd_mark = 1;
++
++	source_port = trailer[1] & 0x3f;
++#else
+ 	if (trailer[0] != 0x80 || (trailer[1] & 0xf8) != 0x00 ||
+ 	    (trailer[2] & 0xef) != 0x00 || trailer[3] != 0x00)
+ 		return NULL;
+ 
+ 	source_port = trailer[1] & 7;
+-
++#endif
+ 	skb->dev = dsa_master_find_slave(dev, 0, source_port);
+ 	if (!skb->dev)
+ 		return NULL;
diff --git a/target/linux/realtek/patches-6.6/723-net-mdio-Add-Realtek-Otto-auxiliary-controller.patch b/target/linux/realtek/patches-6.6/723-net-mdio-Add-Realtek-Otto-auxiliary-controller.patch
new file mode 100644
index 0000000000..7315899b3f
--- /dev/null
+++ b/target/linux/realtek/patches-6.6/723-net-mdio-Add-Realtek-Otto-auxiliary-controller.patch
@@ -0,0 +1,227 @@
+From ffb7da9aa25765b2115e7ff3ee4f6dafa60f5421 Mon Sep 17 00:00:00 2001
+From: Sander Vanheule <sander at svanheule.net>
+Date: Fri, 27 Dec 2024 14:55:31 +0100
+Subject: [PATCH] net: mdio: Add Realtek Otto auxiliary controller
+
+SoCs in Realtek's Otto platform such as the RTL8380, RTL8391, and
+RTL9302 have a simple auxiliary MDIO controller that is commonly used to
+manage RTL8231 GPIO expanders on switch devices.
+
+Add a new MDIO controller driver supporting the RTL838x (maple), RTL839x
+(cypress), and RTL930x (longan) SoCs.
+
+Signed-off-by: Sander Vanheule <sander at svanheule.net>
+---
+ drivers/net/mdio/Kconfig                 |  10 ++
+ drivers/net/mdio/Makefile                |   1 +
+ drivers/net/mdio/mdio-realtek-otto-aux.c | 175 +++++++++++++++++++++++
+ 3 files changed, 186 insertions(+)
+ create mode 100644 drivers/net/mdio/mdio-realtek-otto-aux.c
+
+--- a/drivers/net/mdio/Kconfig
++++ b/drivers/net/mdio/Kconfig
+@@ -207,6 +207,16 @@ config MDIO_REGMAP
+ 	  regmap. Users willing to use this driver must explicitly select
+ 	  REGMAP.
+ 
++config MDIO_REALTEK_OTTO_AUX
++	tristate "Realtek Otto auxiliary MDIO interface support"
++	default MACH_REALTEK_RTL
++	depends on MACH_REALTEK_RTL
++	depends on MFD_SYSCON
++	select MDIO_DEVRES
++	help
++	  This driver supports the auxilairy MDIO bus on RTL838x SoCs. This bus
++	  is typically used to attach RTL8231 GPIO extenders.
++
+ config MDIO_THUNDER
+ 	tristate "ThunderX SOCs MDIO buses"
+ 	depends on 64BIT
+--- a/drivers/net/mdio/Makefile
++++ b/drivers/net/mdio/Makefile
+@@ -20,6 +20,7 @@ obj-$(CONFIG_MDIO_MSCC_MIIM)		+= mdio-ms
+ obj-$(CONFIG_MDIO_MVUSB)		+= mdio-mvusb.o
+ obj-$(CONFIG_MDIO_OCTEON)		+= mdio-octeon.o
+ obj-$(CONFIG_MDIO_REGMAP)		+= mdio-regmap.o
++obj-$(CONFIG_MDIO_REALTEK_OTTO_AUX)	+= mdio-realtek-otto-aux.o
+ obj-$(CONFIG_MDIO_SMBUS)		+= mdio-smbus.o
+ obj-$(CONFIG_MDIO_SUN4I)		+= mdio-sun4i.o
+ obj-$(CONFIG_MDIO_THUNDER)		+= mdio-thunder.o
+--- /dev/null
++++ b/drivers/net/mdio/mdio-realtek-otto-aux.c
+@@ -0,0 +1,175 @@
++// SPDX-License-Identifier: GPL-2.0-or-later
++
++#include <linux/mfd/core.h>
++#include <linux/mfd/syscon.h>
++#include <linux/module.h>
++#include <linux/of.h>
++#include <linux/of_mdio.h>
++#include <linux/of_platform.h>
++#include <linux/phy.h>
++#include <linux/platform_device.h>
++#include <linux/regmap.h>
++
++#define RTL8380_EXT_GPIO_INDIRECT_ACCESS	0xA09C
++#define RTL8390_EXT_GPIO_INDIRECT_ACCESS	0x0224
++#define RTL9300_EXT_GPIO_INDIRECT_ACCESS	0xC620
++
++#define RTL83XX_AUX_MDIO_DATA_OFFSET		16
++#define RTL83XX_AUX_MDIO_RCMD_FAIL		0
++
++#define RTL93XX_AUX_MDIO_DATA_OFFSET		12
++#define RTL93XX_AUX_MDIO_RCMD_FAIL		BIT(28)
++
++#define REALTEK_AUX_MDIO_REG			GENMASK(11, 7)
++#define REALTEK_AUX_MDIO_PHY_ADDR		GENMASK(6, 2)
++#define REALTEK_AUX_MDIO_WRITE			BIT(1)
++#define REALTEK_AUX_MDIO_READ			0
++#define REALTEK_AUX_MDIO_EXEC			BIT(0)
++
++struct realtek_aux_mdio_info {
++	unsigned int cmd_reg;
++	unsigned int data_offset;
++	unsigned int rcmd_fail_mask;
++	unsigned int timeout_us;
++};
++
++static const struct realtek_aux_mdio_info info_rtl838x = {
++	.cmd_reg = RTL8380_EXT_GPIO_INDIRECT_ACCESS,
++	.data_offset = RTL83XX_AUX_MDIO_DATA_OFFSET,
++	.rcmd_fail_mask = RTL83XX_AUX_MDIO_RCMD_FAIL,
++	.timeout_us = 1700,
++};
++
++static const struct realtek_aux_mdio_info info_rtl839x = {
++	.cmd_reg = RTL8390_EXT_GPIO_INDIRECT_ACCESS,
++	.data_offset = RTL83XX_AUX_MDIO_DATA_OFFSET,
++	.rcmd_fail_mask = RTL83XX_AUX_MDIO_RCMD_FAIL,
++	.timeout_us = 4120,
++};
++
++static const struct realtek_aux_mdio_info info_rtl930x = {
++	.cmd_reg = RTL9300_EXT_GPIO_INDIRECT_ACCESS,
++	.data_offset = RTL93XX_AUX_MDIO_DATA_OFFSET,
++	.rcmd_fail_mask = RTL93XX_AUX_MDIO_RCMD_FAIL,
++	.timeout_us = 19000,
++};
++
++struct realtek_aux_mdio_ctrl {
++	struct device *dev;
++	struct regmap *map;
++	const struct realtek_aux_mdio_info *info;
++};
++
++#define mii_bus_to_ctrl(bus)	((struct realtek_aux_mdio_ctrl *) bus->priv)
++
++static int realtek_aux_mdio_cmd(struct realtek_aux_mdio_ctrl *ctrl, int addr, int regnum,
++		u32 rw_bit, u16 *data)
++{
++	unsigned int cmd;
++	int err;
++
++	cmd = rw_bit | REALTEK_AUX_MDIO_EXEC;
++	cmd |= FIELD_PREP(REALTEK_AUX_MDIO_PHY_ADDR, addr);
++	cmd |= FIELD_PREP(REALTEK_AUX_MDIO_REG, regnum);
++
++	if (rw_bit == REALTEK_AUX_MDIO_WRITE)
++		cmd |= *data << ctrl->info->data_offset;
++
++	err = regmap_write(ctrl->map, ctrl->info->cmd_reg, cmd);
++	if (err)
++		return err;
++
++	err = regmap_read_poll_timeout_atomic(ctrl->map, ctrl->info->cmd_reg, cmd,
++			!(cmd & REALTEK_AUX_MDIO_EXEC), 3, ctrl->info->timeout_us);
++	if (err)
++		return err;
++
++	if (rw_bit == REALTEK_AUX_MDIO_READ) {
++		if (cmd & ctrl->info->rcmd_fail_mask)
++			return -EIO;
++
++		*data = (cmd >> ctrl->info->data_offset) & GENMASK(15, 0);
++	}
++
++	return 0;
++}
++
++static int realtek_aux_mdio_read(struct mii_bus *bus, int addr, int regnum)
++{
++	struct realtek_aux_mdio_ctrl *ctrl = mii_bus_to_ctrl(bus);
++	u16 data;
++	int err;
++
++	err = realtek_aux_mdio_cmd(ctrl, addr, regnum, REALTEK_AUX_MDIO_READ, &data);
++
++	if (err)
++		return err;
++	else
++		return data;
++}
++
++static int realtek_aux_mdio_write(struct mii_bus *bus, int addr, int regnum, u16 val)
++{
++	struct realtek_aux_mdio_ctrl *ctrl = mii_bus_to_ctrl(bus);
++
++	return realtek_aux_mdio_cmd(ctrl, addr, regnum, REALTEK_AUX_MDIO_WRITE, &val);
++}
++
++static int realtek_aux_mdio_probe(struct platform_device *pdev)
++{
++	struct device_node *np = pdev->dev.of_node;
++	struct realtek_aux_mdio_ctrl *ctrl;
++	struct mii_bus *bus;
++
++	bus = devm_mdiobus_alloc_size(&pdev->dev, sizeof(*ctrl));
++	if (!bus)
++		return -ENOMEM;
++
++	ctrl = bus->priv;
++	ctrl->dev = &pdev->dev;
++	ctrl->info = (const struct realtek_aux_mdio_info *) device_get_match_data(ctrl->dev);
++	ctrl->map = syscon_node_to_regmap(np->parent);
++	if (IS_ERR(ctrl->map))
++		return PTR_ERR(ctrl->map);
++
++	bus->name = "Realtek auxiliary MDIO bus";
++	snprintf(bus->id, MII_BUS_ID_SIZE, "realtek-aux-mdio") ;
++	bus->parent = ctrl->dev;
++	bus->read = realtek_aux_mdio_read;
++	bus->write = realtek_aux_mdio_write;
++	/* Don't have interrupts */
++	for (unsigned int i = 0; i < PHY_MAX_ADDR; i++)
++		bus->irq[i] = PHY_POLL;
++
++	return devm_of_mdiobus_register(ctrl->dev, bus, np);
++}
++
++static const struct of_device_id realtek_aux_mdio_of_match[] = {
++	{
++		.compatible = "realtek,rtl8380-aux-mdio",
++		.data = &info_rtl838x,
++	},
++	{
++		.compatible = "realtek,rtl8390-aux-mdio",
++		.data = &info_rtl839x,
++	},
++	{
++		.compatible = "realtek,rtl9300-aux-mdio",
++		.data = &info_rtl930x,
++	},
++	{ /* sentinel */ }
++};
++MODULE_DEVICE_TABLE(of, realtek_aux_mdio_of_match);
++
++static struct platform_driver realtek_aux_mdio_driver = {
++	.driver = {
++		.name = "realtek-otto-aux-mdio",
++		.of_match_table = realtek_aux_mdio_of_match
++	},
++	.probe = realtek_aux_mdio_probe,
++};
++module_platform_driver(realtek_aux_mdio_driver);
++
++MODULE_AUTHOR("Sander Vanheule <sander at svanheule.net>");
++MODULE_DESCRIPTION("Realtek otto auxiliary MDIO bus");
++MODULE_LICENSE("GPL v2");
diff --git a/target/linux/realtek/patches-6.6/800-gpio-regmap-Bypass-cache-for-shadowed-outputs.patch b/target/linux/realtek/patches-6.6/800-gpio-regmap-Bypass-cache-for-shadowed-outputs.patch
new file mode 100644
index 0000000000..b4dbf320b2
--- /dev/null
+++ b/target/linux/realtek/patches-6.6/800-gpio-regmap-Bypass-cache-for-shadowed-outputs.patch
@@ -0,0 +1,56 @@
+From b3f79468c90d8770f007d628a1e32b2d5d44a5c2 Mon Sep 17 00:00:00 2001
+From: Sander Vanheule <sander at svanheule.net>
+Date: Sat, 15 May 2021 11:57:32 +0200
+Subject: [PATCH] gpio: regmap: Bypass cache for shadowed outputs
+
+Some chips have the read-only input and write-only output data registers
+aliased to the same offset, but do not perform direction multiplexing on
+writes. Upon writing the register, this then always updates the output
+value, even when the pin is configured as input. As a result it is not
+safe to perform read-modify-writes on output pins, when other pins are
+still configured as input.
+
+For example, on a bit-banged I2C bus, where the lines are switched
+between out-low and in (with external pull-up)
+
+    OUT(L) IN     OUT(H)
+SCK ....../''''''|''''''
+
+SDA '''''''''\..........
+             ^   ^- SCK switches to direction to OUT, but now has a high
+             |      value, breaking the clock.
+             |
+             \- Perform RMW to update SDA. This reads the current input
+                value for SCK, updates the SDA value and writes back a 1
+                for SCK as well.
+
+If a register is used for both the data input and data output (and is
+not marked as volatile) the driver should ensure the cache is not
+updated on register reads. This ensures proper functioning of writing
+the output register with regmap_update_bits(), which will then use and
+update the cache only on register writes.
+
+Signed-off-by: Sander Vanheule <sander at svanheule.net>
+---
+ drivers/gpio/gpio-regmap.c | 10 +++++++++-
+ 1 file changed, 9 insertions(+), 1 deletion(-)
+
+--- a/drivers/gpio/gpio-regmap.c
++++ b/drivers/gpio/gpio-regmap.c
+@@ -74,7 +74,15 @@ static int gpio_regmap_get(struct gpio_c
+ 	if (ret)
+ 		return ret;
+ 
+-	ret = regmap_read(gpio->regmap, reg, &val);
++	/*
++	 * Ensure we don't spoil the register cache with pin input values and
++	 * perform a bypassed read. This way the cache (if any) is only used and
++	 * updated on register writes.
++	 */
++	if (gpio->reg_dat_base == gpio->reg_set_base)
++		ret = regmap_read_bypassed(gpio->regmap, reg, &val);
++	else
++		ret = regmap_read(gpio->regmap, reg, &val);
+ 	if (ret)
+ 		return ret;
+ 
diff --git a/target/linux/realtek/patches-6.6/802-mfd-Add-RTL8231-core-device.patch b/target/linux/realtek/patches-6.6/802-mfd-Add-RTL8231-core-device.patch
new file mode 100644
index 0000000000..df4d4aa143
--- /dev/null
+++ b/target/linux/realtek/patches-6.6/802-mfd-Add-RTL8231-core-device.patch
@@ -0,0 +1,330 @@
+From 4e3455e058d40eb2a7326016494e3c81dc506c33 Mon Sep 17 00:00:00 2001
+From: Sander Vanheule <sander at svanheule.net>
+Date: Mon, 10 May 2021 18:33:01 +0200
+Subject: [PATCH] mfd: Add RTL8231 core device
+
+The RTL8231 is implemented as an MDIO device, and provides a regmap
+interface for register access by the core and child devices.
+
+The chip can also be a device on an SMI bus, an I2C-like bus by Realtek.
+Since kernel support for SMI is limited, and no real-world SMI
+implementations have been encountered for this device, this is currently
+unimplemented. The use of the regmap interface should make any future
+support relatively straightforward.
+
+After reset, all pins are muxed to GPIO inputs before the pin drivers
+are enabled. This is done to prevent accidental system resets, when a
+pin is connected to the parent SoC's reset line.
+
+To provide different read and write semantics for the GPIO data
+registers, a secondary virtual register range is used to enable separate
+caching properties of pin input and output values.
+
+Signed-off-by: Sander Vanheule <sander at svanheule.net>
+---
+ drivers/mfd/Kconfig         |   9 ++
+ drivers/mfd/Makefile        |   1 +
+ drivers/mfd/rtl8231.c       | 193 ++++++++++++++++++++++++++++++++++++
+ include/linux/mfd/rtl8231.h |  71 +++++++++++++
+ 4 files changed, 274 insertions(+)
+ create mode 100644 drivers/mfd/rtl8231.c
+ create mode 100644 include/linux/mfd/rtl8231.h
+
+--- a/drivers/mfd/Kconfig
++++ b/drivers/mfd/Kconfig
+@@ -1171,6 +1171,15 @@ config MFD_RDC321X
+ 	  southbridge which provides access to GPIOs and Watchdog using the
+ 	  southbridge PCI device configuration space.
+ 
++config MFD_RTL8231
++	tristate "Realtek RTL8231 GPIO and LED expander"
++	select MFD_CORE
++	select REGMAP_MDIO
++	help
++	  Support for the Realtek RTL8231 GPIO and LED expander.
++	  Provides up to 37 GPIOs, 88 LEDs, and one PWM output.
++	  When built as a module, this module will be named rtl8231.
++
+ config MFD_RT4831
+ 	tristate "Richtek RT4831 four channel WLED and Display Bias Voltage"
+ 	depends on I2C
+--- a/drivers/mfd/Makefile
++++ b/drivers/mfd/Makefile
+@@ -240,6 +240,7 @@ obj-$(CONFIG_MFD_HI6421_PMIC)	+= hi6421-
+ obj-$(CONFIG_MFD_HI6421_SPMI)	+= hi6421-spmi-pmic.o
+ obj-$(CONFIG_MFD_HI655X_PMIC)   += hi655x-pmic.o
+ obj-$(CONFIG_MFD_DLN2)		+= dln2.o
++obj-$(CONFIG_MFD_RTL8231)	+= rtl8231.o
+ obj-$(CONFIG_MFD_RT4831)	+= rt4831.o
+ obj-$(CONFIG_MFD_RT5033)	+= rt5033.o
+ obj-$(CONFIG_MFD_RT5120)	+= rt5120.o
+--- /dev/null
++++ b/drivers/mfd/rtl8231.c
+@@ -0,0 +1,193 @@
++// SPDX-License-Identifier: GPL-2.0-only
++
++#include <linux/bits.h>
++#include <linux/bitfield.h>
++#include <linux/delay.h>
++#include <linux/gpio/consumer.h>
++#include <linux/mfd/core.h>
++#include <linux/mdio.h>
++#include <linux/module.h>
++#include <linux/platform_device.h>
++#include <linux/property.h>
++#include <linux/regmap.h>
++
++#include <linux/mfd/rtl8231.h>
++
++static bool rtl8231_volatile_reg(struct device *dev, unsigned int reg)
++{
++	switch (reg) {
++	/*
++	 * Registers with self-clearing bits, strapping pin values.
++	 * Don't mark the data registers as volatile, since we need
++	 * caching for the output values.
++	 */
++	case RTL8231_REG_FUNC0:
++	case RTL8231_REG_FUNC1:
++	case RTL8231_REG_PIN_HI_CFG:
++	case RTL8231_REG_LED_END:
++		return true;
++	default:
++		return false;
++	}
++}
++
++static const struct reg_field RTL8231_FIELD_LED_START = REG_FIELD(RTL8231_REG_FUNC0, 1, 1);
++
++static const struct mfd_cell rtl8231_cells[] = {
++	{
++		.name = "rtl8231-pinctrl",
++	},
++	{
++		.name = "rtl8231-leds",
++		.of_compatible = "realtek,rtl8231-leds",
++	},
++};
++
++static int rtl8231_soft_reset(struct regmap *map)
++{
++	const unsigned int all_pins_mask = GENMASK(RTL8231_BITS_VAL - 1, 0);
++	unsigned int val;
++	int err;
++
++	/* SOFT_RESET bit self-clears when done */
++	regmap_write_bits(map, RTL8231_REG_PIN_HI_CFG,
++		RTL8231_PIN_HI_CFG_SOFT_RESET, RTL8231_PIN_HI_CFG_SOFT_RESET);
++	err = regmap_read_poll_timeout(map, RTL8231_REG_PIN_HI_CFG, val,
++		!(val & RTL8231_PIN_HI_CFG_SOFT_RESET), 50, 1000);
++	if (err)
++		return err;
++
++	regcache_mark_dirty(map);
++
++	/*
++	 * Chip reset results in a pin configuration that is a mix of LED and GPIO outputs.
++	 * Select GPI functionality for all pins before enabling pin outputs.
++	 */
++	regmap_write(map, RTL8231_REG_PIN_MODE0, all_pins_mask);
++	regmap_write(map, RTL8231_REG_GPIO_DIR0, all_pins_mask);
++	regmap_write(map, RTL8231_REG_PIN_MODE1, all_pins_mask);
++	regmap_write(map, RTL8231_REG_GPIO_DIR1, all_pins_mask);
++	regmap_write(map, RTL8231_REG_PIN_HI_CFG,
++		RTL8231_PIN_HI_CFG_MODE_MASK | RTL8231_PIN_HI_CFG_DIR_MASK);
++
++	return 0;
++}
++
++static int rtl8231_init(struct device *dev, struct regmap *map)
++{
++	struct regmap_field *led_start;
++	unsigned int started;
++	unsigned int val;
++	int err;
++
++	err = regmap_read(map, RTL8231_REG_FUNC1, &val);
++	if (err) {
++		dev_err(dev, "failed to read READY_CODE\n");
++		return err;
++	}
++
++	val = FIELD_GET(RTL8231_FUNC1_READY_CODE_MASK, val);
++	if (val != RTL8231_FUNC1_READY_CODE_VALUE) {
++		dev_err(dev, "RTL8231 not present or ready 0x%x != 0x%x\n",
++			val, RTL8231_FUNC1_READY_CODE_VALUE);
++		return -ENODEV;
++	}
++
++	led_start = dev_get_drvdata(dev);
++	err = regmap_field_read(led_start, &started);
++	if (err)
++		return err;
++
++	if (!started) {
++		err = rtl8231_soft_reset(map);
++		if (err)
++			return err;
++		/* LED_START enables power to output pins, and starts the LED engine */
++		err = regmap_field_force_write(led_start, 1);
++	}
++
++	return err;
++}
++
++static const struct regmap_config rtl8231_mdio_regmap_config = {
++	.val_bits = RTL8231_BITS_VAL,
++	.reg_bits = RTL8231_BITS_REG,
++	.volatile_reg = rtl8231_volatile_reg,
++	.max_register = RTL8231_REG_COUNT - 1,
++	.use_single_read = true,
++	.use_single_write = true,
++	.reg_format_endian = REGMAP_ENDIAN_BIG,
++	.val_format_endian = REGMAP_ENDIAN_BIG,
++	/* Cannot use REGCACHE_FLAT because it's not smart enough about cache invalidation  */
++	.cache_type = REGCACHE_RBTREE,
++};
++
++static int rtl8231_mdio_probe(struct mdio_device *mdiodev)
++{
++	struct device *dev = &mdiodev->dev;
++	struct regmap_field *led_start;
++	struct regmap *map;
++	int err;
++
++	map = devm_regmap_init_mdio(mdiodev, &rtl8231_mdio_regmap_config);
++	if (IS_ERR(map)) {
++		dev_err(dev, "failed to init regmap\n");
++		return PTR_ERR(map);
++	}
++
++	led_start = devm_regmap_field_alloc(dev, map, RTL8231_FIELD_LED_START);
++	if (IS_ERR(led_start))
++		return PTR_ERR(led_start);
++
++	dev_set_drvdata(dev, led_start);
++
++	mdiodev->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
++	if (IS_ERR(mdiodev->reset_gpio))
++		return PTR_ERR(mdiodev->reset_gpio);
++
++	device_property_read_u32(dev, "reset-assert-delay", &mdiodev->reset_assert_delay);
++	device_property_read_u32(dev, "reset-deassert-delay", &mdiodev->reset_deassert_delay);
++
++	err = rtl8231_init(dev, map);
++	if (err)
++		return err;
++
++	return devm_mfd_add_devices(dev, PLATFORM_DEVID_AUTO, rtl8231_cells,
++		ARRAY_SIZE(rtl8231_cells), NULL, 0, NULL);
++}
++
++__maybe_unused static int rtl8231_suspend(struct device *dev)
++{
++	struct regmap_field *led_start = dev_get_drvdata(dev);
++
++	return regmap_field_force_write(led_start, 0);
++}
++
++__maybe_unused static int rtl8231_resume(struct device *dev)
++{
++	struct regmap_field *led_start = dev_get_drvdata(dev);
++
++	return regmap_field_force_write(led_start, 1);
++}
++
++static SIMPLE_DEV_PM_OPS(rtl8231_pm_ops, rtl8231_suspend, rtl8231_resume);
++
++static const struct of_device_id rtl8231_of_match[] = {
++	{ .compatible = "realtek,rtl8231" },
++	{}
++};
++MODULE_DEVICE_TABLE(of, rtl8231_of_match);
++
++static struct mdio_driver rtl8231_mdio_driver = {
++	.mdiodrv.driver = {
++		.name = "rtl8231-expander",
++		.of_match_table	= rtl8231_of_match,
++		.pm = pm_ptr(&rtl8231_pm_ops),
++	},
++	.probe = rtl8231_mdio_probe,
++};
++mdio_module_driver(rtl8231_mdio_driver);
++
++MODULE_AUTHOR("Sander Vanheule <sander at svanheule.net>");
++MODULE_DESCRIPTION("Realtek RTL8231 GPIO and LED expander");
++MODULE_LICENSE("GPL");
+--- /dev/null
++++ b/include/linux/mfd/rtl8231.h
+@@ -0,0 +1,71 @@
++/* SPDX-License-Identifier: GPL-2.0-or-later */
++/*
++ * Register definitions the RTL8231 GPIO and LED expander chip
++ */
++
++#ifndef __LINUX_MFD_RTL8231_H
++#define __LINUX_MFD_RTL8231_H
++
++#include <linux/bits.h>
++
++/*
++ * Registers addresses are 5 bit, values are 16 bit
++ * Also define a duplicated range of virtual addresses, to enable
++ * different read/write behaviour on the GPIO data registers
++ */
++#define RTL8231_BITS_VAL		16
++#define RTL8231_BITS_REG		5
++
++/* Chip control */
++#define RTL8231_REG_FUNC0		0x00
++#define RTL8231_FUNC0_SCAN_MODE		BIT(0)
++#define RTL8231_FUNC0_SCAN_SINGLE	0
++#define RTL8231_FUNC0_SCAN_BICOLOR	BIT(0)
++
++#define RTL8231_REG_FUNC1		0x01
++#define RTL8231_FUNC1_READY_CODE_VALUE	0x37
++#define RTL8231_FUNC1_READY_CODE_MASK	GENMASK(9, 4)
++#define RTL8231_FUNC1_DEBOUNCE_MASK	GENMASK(15, 10)
++
++/* Pin control */
++#define RTL8231_REG_PIN_MODE0		0x02
++#define RTL8231_REG_PIN_MODE1		0x03
++
++#define RTL8231_PIN_MODE_LED		0
++#define RTL8231_PIN_MODE_GPIO		1
++
++/* Pin high config: pin and GPIO control for pins 32-26 */
++#define RTL8231_REG_PIN_HI_CFG		0x04
++#define RTL8231_PIN_HI_CFG_MODE_MASK	GENMASK(4, 0)
++#define RTL8231_PIN_HI_CFG_DIR_MASK	GENMASK(9, 5)
++#define RTL8231_PIN_HI_CFG_INV_MASK	GENMASK(14, 10)
++#define RTL8231_PIN_HI_CFG_SOFT_RESET	BIT(15)
++
++/* GPIO control registers */
++#define RTL8231_REG_GPIO_DIR0		0x05
++#define RTL8231_REG_GPIO_DIR1		0x06
++#define RTL8231_REG_GPIO_INVERT0	0x07
++#define RTL8231_REG_GPIO_INVERT1	0x08
++
++#define RTL8231_GPIO_DIR_IN		1
++#define RTL8231_GPIO_DIR_OUT		0
++
++/*
++ * GPIO data registers
++ * Only the output data can be written to these registers, and only the input
++ * data can be read.
++ */
++#define RTL8231_REG_GPIO_DATA0		0x1c
++#define RTL8231_REG_GPIO_DATA1		0x1d
++#define RTL8231_REG_GPIO_DATA2		0x1e
++#define RTL8231_PIN_HI_DATA_MASK	GENMASK(4, 0)
++
++/* LED control base registers */
++#define RTL8231_REG_LED0_BASE		0x09
++#define RTL8231_REG_LED1_BASE		0x10
++#define RTL8231_REG_LED2_BASE		0x17
++#define RTL8231_REG_LED_END		0x1b
++
++#define RTL8231_REG_COUNT		0x1f
++
++#endif /* __LINUX_MFD_RTL8231_H */
diff --git a/target/linux/realtek/patches-6.6/803-pinctrl-Add-RTL8231-pin-control-and-GPIO-support.patch b/target/linux/realtek/patches-6.6/803-pinctrl-Add-RTL8231-pin-control-and-GPIO-support.patch
new file mode 100644
index 0000000000..de0f5ee868
--- /dev/null
+++ b/target/linux/realtek/patches-6.6/803-pinctrl-Add-RTL8231-pin-control-and-GPIO-support.patch
@@ -0,0 +1,581 @@
+From 098324288a63a6dcc44e96cc381aef3d5c48d89e Mon Sep 17 00:00:00 2001
+From: Sander Vanheule <sander at svanheule.net>
+Date: Mon, 10 May 2021 22:15:31 +0200
+Subject: [PATCH] pinctrl: Add RTL8231 pin control and GPIO support
+
+This driver implements the GPIO and pin muxing features provided by the
+RTL8231. The device should be instantiated as an MFD child, where the
+parent device has already configured the regmap used for register
+access.
+
+Debouncing is only available for the six highest GPIOs, and must be
+emulated when other pins are used for (button) inputs. Although
+described in the bindings, drive strength selection is currently not
+implemented.
+
+Signed-off-by: Sander Vanheule <sander at svanheule.net>
+---
+ drivers/pinctrl/Kconfig           |  11 +
+ drivers/pinctrl/Makefile          |   1 +
+ drivers/pinctrl/pinctrl-rtl8231.c | 521 ++++++++++++++++++++++++++++++
+ 3 files changed, 533 insertions(+)
+ create mode 100644 drivers/pinctrl/pinctrl-rtl8231.c
+
+--- a/drivers/pinctrl/Kconfig
++++ b/drivers/pinctrl/Kconfig
+@@ -417,6 +417,17 @@ config PINCTRL_ROCKCHIP
+ 	help
+           This support pinctrl and GPIO driver for Rockchip SoCs.
+ 
++config PINCTRL_RTL8231
++	tristate "Realtek RTL8231 GPIO expander's pin controller"
++	depends on MFD_RTL8231
++	default MFD_RTL8231
++	select GPIO_REGMAP
++	select GENERIC_PINCONF
++	select GENERIC_PINMUX_FUNCTIONS
++	help
++	  Support for RTL8231 expander's GPIOs and pin controller.
++	  When built as a module, the module will be called pinctrl-rtl8231.
++
+ config PINCTRL_SINGLE
+ 	tristate "One-register-per-pin type device tree based pinctrl driver"
+ 	depends on OF
+--- a/drivers/pinctrl/Makefile
++++ b/drivers/pinctrl/Makefile
+@@ -43,6 +43,7 @@ obj-$(CONFIG_PINCTRL_PIC32)	+= pinctrl-p
+ obj-$(CONFIG_PINCTRL_PISTACHIO)	+= pinctrl-pistachio.o
+ obj-$(CONFIG_PINCTRL_RK805)	+= pinctrl-rk805.o
+ obj-$(CONFIG_PINCTRL_ROCKCHIP)	+= pinctrl-rockchip.o
++obj-$(CONFIG_PINCTRL_RTL8231)	+= pinctrl-rtl8231.o
+ obj-$(CONFIG_PINCTRL_SINGLE)	+= pinctrl-single.o
+ obj-$(CONFIG_PINCTRL_ST) 	+= pinctrl-st.o
+ obj-$(CONFIG_PINCTRL_STMFX) 	+= pinctrl-stmfx.o
+--- /dev/null
++++ b/drivers/pinctrl/pinctrl-rtl8231.c
+@@ -0,0 +1,525 @@
++// SPDX-License-Identifier: GPL-2.0-only
++
++#include <linux/bitfield.h>
++#include <linux/gpio/driver.h>
++#include <linux/gpio/regmap.h>
++#include <linux/module.h>
++#include <linux/pinctrl/pinconf.h>
++#include <linux/pinctrl/pinctrl.h>
++#include <linux/pinctrl/pinmux.h>
++#include <linux/platform_device.h>
++#include <linux/regmap.h>
++
++#include "core.h"
++#include "pinmux.h"
++#include <linux/mfd/rtl8231.h>
++
++#define RTL8231_NUM_GPIOS		37
++#define RTL8231_DEBOUNCE_USEC		100000
++#define RTL8231_DEBOUNCE_MIN_OFFSET	31
++
++struct rtl8231_pin_ctrl {
++	struct pinctrl_desc pctl_desc;
++	struct regmap *map;
++};
++
++/*
++ * Pin controller functionality
++ */
++static const char * const rtl8231_pin_function_names[] = {
++	"gpio",
++	"led",
++	"pwm",
++};
++
++enum rtl8231_pin_function {
++	RTL8231_PIN_FUNCTION_GPIO = BIT(0),
++	RTL8231_PIN_FUNCTION_LED = BIT(1),
++	RTL8231_PIN_FUNCTION_PWM = BIT(2),
++};
++
++struct rtl8231_pin_desc {
++	const enum rtl8231_pin_function functions;
++	const u8 reg;
++	const u8 offset;
++	const u8 gpio_function_value;
++};
++
++#define RTL8231_PIN_DESC(_num, _func, _reg, _fld, _val)		\
++	[_num] = {						\
++		.functions = RTL8231_PIN_FUNCTION_GPIO | _func,	\
++		.reg = _reg,					\
++		.offset = _fld,					\
++		.gpio_function_value = _val,			\
++	}
++#define RTL8231_GPIO_PIN_DESC(_num, _reg, _fld)			\
++	RTL8231_PIN_DESC(_num, 0, _reg, _fld, RTL8231_PIN_MODE_GPIO)
++#define RTL8231_LED_PIN_DESC(_num, _reg, _fld)			\
++	RTL8231_PIN_DESC(_num, RTL8231_PIN_FUNCTION_LED, _reg, _fld, RTL8231_PIN_MODE_GPIO)
++#define RTL8231_PWM_PIN_DESC(_num, _reg, _fld)			\
++	RTL8231_PIN_DESC(_num, RTL8231_PIN_FUNCTION_PWM, _reg, _fld, 0)
++
++/*
++ * All pins have a GPIO/LED mux bit, but the bits for pins 35/36 are read-only. Use this bit
++ * for the GPIO-only pin instead of a placeholder, so the rest of the logic can stay generic.
++ */
++static struct rtl8231_pin_desc rtl8231_pin_data[RTL8231_NUM_GPIOS] = {
++	RTL8231_LED_PIN_DESC(0, RTL8231_REG_PIN_MODE0, 0),
++	RTL8231_LED_PIN_DESC(1, RTL8231_REG_PIN_MODE0, 1),
++	RTL8231_LED_PIN_DESC(2, RTL8231_REG_PIN_MODE0, 2),
++	RTL8231_LED_PIN_DESC(3, RTL8231_REG_PIN_MODE0, 3),
++	RTL8231_LED_PIN_DESC(4, RTL8231_REG_PIN_MODE0, 4),
++	RTL8231_LED_PIN_DESC(5, RTL8231_REG_PIN_MODE0, 5),
++	RTL8231_LED_PIN_DESC(6, RTL8231_REG_PIN_MODE0, 6),
++	RTL8231_LED_PIN_DESC(7, RTL8231_REG_PIN_MODE0, 7),
++	RTL8231_LED_PIN_DESC(8, RTL8231_REG_PIN_MODE0, 8),
++	RTL8231_LED_PIN_DESC(9, RTL8231_REG_PIN_MODE0, 9),
++	RTL8231_LED_PIN_DESC(10, RTL8231_REG_PIN_MODE0, 10),
++	RTL8231_LED_PIN_DESC(11, RTL8231_REG_PIN_MODE0, 11),
++	RTL8231_LED_PIN_DESC(12, RTL8231_REG_PIN_MODE0, 12),
++	RTL8231_LED_PIN_DESC(13, RTL8231_REG_PIN_MODE0, 13),
++	RTL8231_LED_PIN_DESC(14, RTL8231_REG_PIN_MODE0, 14),
++	RTL8231_LED_PIN_DESC(15, RTL8231_REG_PIN_MODE0, 15),
++	RTL8231_LED_PIN_DESC(16, RTL8231_REG_PIN_MODE1, 0),
++	RTL8231_LED_PIN_DESC(17, RTL8231_REG_PIN_MODE1, 1),
++	RTL8231_LED_PIN_DESC(18, RTL8231_REG_PIN_MODE1, 2),
++	RTL8231_LED_PIN_DESC(19, RTL8231_REG_PIN_MODE1, 3),
++	RTL8231_LED_PIN_DESC(20, RTL8231_REG_PIN_MODE1, 4),
++	RTL8231_LED_PIN_DESC(21, RTL8231_REG_PIN_MODE1, 5),
++	RTL8231_LED_PIN_DESC(22, RTL8231_REG_PIN_MODE1, 6),
++	RTL8231_LED_PIN_DESC(23, RTL8231_REG_PIN_MODE1, 7),
++	RTL8231_LED_PIN_DESC(24, RTL8231_REG_PIN_MODE1, 8),
++	RTL8231_LED_PIN_DESC(25, RTL8231_REG_PIN_MODE1, 9),
++	RTL8231_LED_PIN_DESC(26, RTL8231_REG_PIN_MODE1, 10),
++	RTL8231_LED_PIN_DESC(27, RTL8231_REG_PIN_MODE1, 11),
++	RTL8231_LED_PIN_DESC(28, RTL8231_REG_PIN_MODE1, 12),
++	RTL8231_LED_PIN_DESC(29, RTL8231_REG_PIN_MODE1, 13),
++	RTL8231_LED_PIN_DESC(30, RTL8231_REG_PIN_MODE1, 14),
++	RTL8231_LED_PIN_DESC(31, RTL8231_REG_PIN_MODE1, 15),
++	RTL8231_LED_PIN_DESC(32, RTL8231_REG_PIN_HI_CFG, 0),
++	RTL8231_LED_PIN_DESC(33, RTL8231_REG_PIN_HI_CFG, 1),
++	RTL8231_LED_PIN_DESC(34, RTL8231_REG_PIN_HI_CFG, 2),
++	RTL8231_PWM_PIN_DESC(35, RTL8231_REG_FUNC1, 3),
++	RTL8231_GPIO_PIN_DESC(36, RTL8231_REG_PIN_HI_CFG, 4),
++};
++
++#define RTL8231_PIN(_num)				\
++	{						\
++		.number = _num,				\
++		.name = "gpio" #_num,			\
++		.drv_data = &rtl8231_pin_data[_num]	\
++	}
++
++static const struct pinctrl_pin_desc rtl8231_pins[RTL8231_NUM_GPIOS] = {
++	RTL8231_PIN(0),
++	RTL8231_PIN(1),
++	RTL8231_PIN(2),
++	RTL8231_PIN(3),
++	RTL8231_PIN(4),
++	RTL8231_PIN(5),
++	RTL8231_PIN(6),
++	RTL8231_PIN(7),
++	RTL8231_PIN(8),
++	RTL8231_PIN(9),
++	RTL8231_PIN(10),
++	RTL8231_PIN(11),
++	RTL8231_PIN(12),
++	RTL8231_PIN(13),
++	RTL8231_PIN(14),
++	RTL8231_PIN(15),
++	RTL8231_PIN(16),
++	RTL8231_PIN(17),
++	RTL8231_PIN(18),
++	RTL8231_PIN(19),
++	RTL8231_PIN(20),
++	RTL8231_PIN(21),
++	RTL8231_PIN(22),
++	RTL8231_PIN(23),
++	RTL8231_PIN(24),
++	RTL8231_PIN(25),
++	RTL8231_PIN(26),
++	RTL8231_PIN(27),
++	RTL8231_PIN(28),
++	RTL8231_PIN(29),
++	RTL8231_PIN(30),
++	RTL8231_PIN(31),
++	RTL8231_PIN(32),
++	RTL8231_PIN(33),
++	RTL8231_PIN(34),
++	RTL8231_PIN(35),
++	RTL8231_PIN(36),
++};
++
++static int rtl8231_get_groups_count(struct pinctrl_dev *pctldev)
++{
++	return ARRAY_SIZE(rtl8231_pins);
++}
++
++static const char *rtl8231_get_group_name(struct pinctrl_dev *pctldev, unsigned int selector)
++{
++	return rtl8231_pins[selector].name;
++}
++
++static int rtl8231_get_group_pins(struct pinctrl_dev *pctldev, unsigned int selector,
++	const unsigned int **pins, unsigned int *num_pins)
++{
++	if (selector >= ARRAY_SIZE(rtl8231_pins))
++		return -EINVAL;
++
++	*pins = &rtl8231_pins[selector].number;
++	*num_pins = 1;
++
++	return 0;
++}
++
++static const struct pinctrl_ops rtl8231_pinctrl_ops = {
++	.get_groups_count = rtl8231_get_groups_count,
++	.get_group_name = rtl8231_get_group_name,
++	.get_group_pins = rtl8231_get_group_pins,
++	.dt_node_to_map = pinconf_generic_dt_node_to_map_all,
++	.dt_free_map = pinconf_generic_dt_free_map,
++};
++
++static int rtl8231_set_mux(struct pinctrl_dev *pctldev, unsigned int func_selector,
++	unsigned int group_selector)
++{
++	const struct function_desc *func = pinmux_generic_get_function(pctldev, func_selector);
++	const struct rtl8231_pin_desc *desc = rtl8231_pins[group_selector].drv_data;
++	const struct rtl8231_pin_ctrl *ctrl = pinctrl_dev_get_drvdata(pctldev);
++	unsigned int func_flag = (uintptr_t) func->data;
++	unsigned int function_mask;
++	unsigned int gpio_function;
++
++	if (!(desc->functions & func_flag))
++		return -EINVAL;
++
++	function_mask = BIT(desc->offset);
++	gpio_function = desc->gpio_function_value << desc->offset;
++
++	if (func_flag == RTL8231_PIN_FUNCTION_GPIO)
++		return regmap_update_bits(ctrl->map, desc->reg, function_mask, gpio_function);
++	else
++		return regmap_update_bits(ctrl->map, desc->reg, function_mask, ~gpio_function);
++}
++
++static int rtl8231_gpio_request_enable(struct pinctrl_dev *pctldev,
++	struct pinctrl_gpio_range *range, unsigned int offset)
++{
++	const struct rtl8231_pin_desc *desc = rtl8231_pins[offset].drv_data;
++	struct rtl8231_pin_ctrl *ctrl = pinctrl_dev_get_drvdata(pctldev);
++	unsigned int function_mask;
++	unsigned int gpio_function;
++
++	function_mask = BIT(desc->offset);
++	gpio_function = desc->gpio_function_value << desc->offset;
++
++	return regmap_update_bits(ctrl->map, desc->reg, function_mask, gpio_function);
++}
++
++static const struct pinmux_ops rtl8231_pinmux_ops = {
++	.get_functions_count = pinmux_generic_get_function_count,
++	.get_function_name = pinmux_generic_get_function_name,
++	.get_function_groups = pinmux_generic_get_function_groups,
++	.set_mux = rtl8231_set_mux,
++	.gpio_request_enable = rtl8231_gpio_request_enable,
++	.strict = true,
++};
++
++static int rtl8231_pin_config_get(struct pinctrl_dev *pctldev, unsigned int offset,
++	unsigned long *config)
++{
++	struct rtl8231_pin_ctrl *ctrl = pinctrl_dev_get_drvdata(pctldev);
++	unsigned int param = pinconf_to_config_param(*config);
++	unsigned int arg;
++	int err;
++	int v;
++
++	switch (param) {
++	case PIN_CONFIG_INPUT_DEBOUNCE:
++		if (offset < RTL8231_DEBOUNCE_MIN_OFFSET)
++			return -EINVAL;
++
++		err = regmap_read(ctrl->map, RTL8231_REG_FUNC1, &v);
++		if (err)
++			return err;
++
++		v = FIELD_GET(RTL8231_FUNC1_DEBOUNCE_MASK, v);
++		if (v & BIT(offset - RTL8231_DEBOUNCE_MIN_OFFSET))
++			arg = RTL8231_DEBOUNCE_USEC;
++		else
++			arg = 0;
++		break;
++	default:
++		return -ENOTSUPP;
++	}
++
++	*config = pinconf_to_config_packed(param, arg);
++
++	return 0;
++}
++
++static int rtl8231_pin_config_set(struct pinctrl_dev *pctldev, unsigned int offset,
++	unsigned long *configs, unsigned int num_configs)
++{
++	struct rtl8231_pin_ctrl *ctrl = pinctrl_dev_get_drvdata(pctldev);
++	unsigned int param, arg;
++	unsigned int pin_mask;
++	int err;
++	int i;
++
++	for (i = 0; i < num_configs; i++) {
++		param = pinconf_to_config_param(configs[i]);
++		arg = pinconf_to_config_argument(configs[i]);
++
++		switch (param) {
++		case PIN_CONFIG_INPUT_DEBOUNCE:
++			if (offset < RTL8231_DEBOUNCE_MIN_OFFSET)
++				return -EINVAL;
++
++			pin_mask = FIELD_PREP(RTL8231_FUNC1_DEBOUNCE_MASK,
++				BIT(offset - RTL8231_DEBOUNCE_MIN_OFFSET));
++
++			switch (arg) {
++			case 0:
++				err = regmap_update_bits(ctrl->map, RTL8231_REG_FUNC1,
++					pin_mask, 0);
++				break;
++			case RTL8231_DEBOUNCE_USEC:
++				err = regmap_update_bits(ctrl->map, RTL8231_REG_FUNC1,
++					pin_mask, pin_mask);
++				break;
++			default:
++				return -EINVAL;
++			}
++
++			break;
++		default:
++			return -ENOTSUPP;
++		}
++	}
++
++	return err;
++}
++
++static const struct pinconf_ops rtl8231_pinconf_ops = {
++	.is_generic = true,
++	.pin_config_get = rtl8231_pin_config_get,
++	.pin_config_set = rtl8231_pin_config_set,
++};
++
++static int rtl8231_pinctrl_init_functions(struct pinctrl_dev *pctl, struct rtl8231_pin_ctrl *ctrl)
++{
++	const char *function_name;
++	const char **groups;
++	unsigned int f_idx;
++	unsigned int pin;
++	int num_groups;
++	int err;
++
++	for (f_idx = 0; f_idx < ARRAY_SIZE(rtl8231_pin_function_names); f_idx++) {
++		function_name = rtl8231_pin_function_names[f_idx];
++
++		for (pin = 0, num_groups = 0; pin < ctrl->pctl_desc.npins; pin++)
++			if (rtl8231_pin_data[pin].functions & BIT(f_idx))
++				num_groups++;
++
++		groups = devm_kcalloc(pctl->dev, num_groups, sizeof(*groups), GFP_KERNEL);
++		if (!groups)
++			return -ENOMEM;
++
++		for (pin = 0, num_groups = 0; pin < ctrl->pctl_desc.npins; pin++)
++			if (rtl8231_pin_data[pin].functions & BIT(f_idx))
++				groups[num_groups++] = rtl8231_pins[pin].name;
++
++		err = pinmux_generic_add_function(pctl, function_name, groups, num_groups,
++			(void *) BIT(f_idx));
++		if (err < 0)
++			return err;
++	}
++
++	return 0;
++}
++
++struct pin_field_info {
++	const struct reg_field gpio_data;
++	const struct reg_field gpio_dir;
++	const struct reg_field mode;
++};
++
++static const struct pin_field_info pin_fields[] = {
++	{
++		.gpio_data = REG_FIELD(RTL8231_REG_GPIO_DATA0, 0, 15),
++		.gpio_dir = REG_FIELD(RTL8231_REG_GPIO_DIR0, 0, 15),
++		.mode = REG_FIELD(RTL8231_REG_PIN_MODE0, 0, 15),
++	},
++	{
++		.gpio_data = REG_FIELD(RTL8231_REG_GPIO_DATA1, 0, 15),
++		.gpio_dir = REG_FIELD(RTL8231_REG_GPIO_DIR1, 0, 15),
++		.mode = REG_FIELD(RTL8231_REG_PIN_MODE1, 0, 15),
++	},
++	{
++		.gpio_data = REG_FIELD(RTL8231_REG_GPIO_DATA2, 0, 4),
++		.gpio_dir = REG_FIELD(RTL8231_REG_PIN_HI_CFG, 5, 9),
++		.mode = REG_FIELD(RTL8231_REG_PIN_HI_CFG, 0, 4),
++	},
++};
++
++static int rtl8231_configure_safe(struct device *dev, struct regmap *map)
++{
++	struct regmap_field *field_data;
++	struct regmap_field *field_mode;
++	struct regmap_field *field_dir;
++	unsigned int is_output;
++	unsigned int is_gpio;
++	unsigned int data;
++	unsigned int mode;
++	unsigned int dir;
++	int err;
++
++	for (unsigned int i = 0; i < ARRAY_SIZE(pin_fields); i++) {
++		field_data = devm_regmap_field_alloc(dev, map, pin_fields[i].gpio_data);
++		if (IS_ERR(field_data))
++			return PTR_ERR(field_data);
++
++		field_dir = devm_regmap_field_alloc(dev, map, pin_fields[i].gpio_dir);
++		if (IS_ERR(field_dir))
++			return PTR_ERR(field_dir);
++
++		field_mode = devm_regmap_field_alloc(dev, map, pin_fields[i].mode);
++		if (IS_ERR(field_mode))
++			return PTR_ERR(field_mode);
++
++		/* The register cache is invalid at start-up, so this should read from HW */
++		err = regmap_field_read(field_data, &data);
++		if (err)
++			return err;
++
++		err = regmap_field_read(field_dir, &dir);
++		if (err)
++			return err;
++
++		err = regmap_field_read(field_mode, &mode);
++		if (err)
++			return err;
++
++		/* Write back only the GPIO-out values to fix the cache */
++		data &= ~dir;
++		regmap_field_write(field_data, data);
++
++		/*
++		 * Set every pin that is configured as gpio-output but muxed for the alternative
++		 * (LED) function to gpio-in. That way the pin will be high impedance when it is
++		 * muxed to GPIO, preventing unwanted glitches.
++		 * The pin muxes are left as-is, so there are no signal changes.
++		 */
++		is_gpio = mode;
++		is_output = ~dir;
++		regmap_field_write(field_dir, dir | (~is_gpio & is_output));
++
++		devm_regmap_field_free(dev, field_data);
++		devm_regmap_field_free(dev, field_dir);
++		devm_regmap_field_free(dev, field_mode);
++	}
++
++	return 0;
++}
++
++static int rtl8231_pinctrl_init(struct device *dev, struct rtl8231_pin_ctrl *ctrl)
++{
++	struct pinctrl_dev *pctldev;
++	int err;
++
++	ctrl->pctl_desc.name = "rtl8231-pinctrl";
++	ctrl->pctl_desc.owner = THIS_MODULE;
++	ctrl->pctl_desc.confops = &rtl8231_pinconf_ops;
++	ctrl->pctl_desc.pctlops = &rtl8231_pinctrl_ops;
++	ctrl->pctl_desc.pmxops = &rtl8231_pinmux_ops;
++	ctrl->pctl_desc.npins = ARRAY_SIZE(rtl8231_pins);
++	ctrl->pctl_desc.pins = rtl8231_pins;
++
++	err = devm_pinctrl_register_and_init(dev->parent, &ctrl->pctl_desc, ctrl, &pctldev);
++	if (err) {
++		dev_err(dev, "failed to register pin controller\n");
++		return err;
++	}
++
++	err = rtl8231_pinctrl_init_functions(pctldev, ctrl);
++	if (err)
++		return err;
++
++	err = pinctrl_enable(pctldev);
++	if (err)
++		dev_err(dev, "failed to enable pin controller\n");
++
++	return err;
++}
++
++/*
++ * GPIO controller functionality
++ */
++static int rtl8231_gpio_reg_mask_xlate(struct gpio_regmap *gpio, unsigned int base,
++	unsigned int offset, unsigned int *reg, unsigned int *mask)
++{
++	unsigned int pin_mask = BIT(offset % RTL8231_BITS_VAL);
++
++	if (base == RTL8231_REG_GPIO_DATA0 || offset < 32) {
++		*reg = base + offset / RTL8231_BITS_VAL;
++		*mask = pin_mask;
++	} else if (base == RTL8231_REG_GPIO_DIR0) {
++		*reg = RTL8231_REG_PIN_HI_CFG;
++		*mask = FIELD_PREP(RTL8231_PIN_HI_CFG_DIR_MASK, pin_mask);
++	} else {
++		return -EINVAL;
++	}
++
++	return 0;
++}
++
++static int rtl8231_pinctrl_probe(struct platform_device *pdev)
++{
++	struct device *dev = &pdev->dev;
++	struct rtl8231_pin_ctrl *ctrl;
++	struct gpio_regmap_config gpio_cfg = {};
++	int err;
++
++	ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL);
++	if (!ctrl)
++		return -ENOMEM;
++
++	ctrl->map = dev_get_regmap(dev->parent, NULL);
++	if (!ctrl->map)
++		return -ENODEV;
++
++	err = rtl8231_configure_safe(dev, ctrl->map);
++	if (err)
++		return err;
++
++	err = rtl8231_pinctrl_init(dev, ctrl);
++	if (err)
++		return err;
++
++	gpio_cfg.regmap = ctrl->map;
++	gpio_cfg.parent = dev->parent;
++	gpio_cfg.ngpio = RTL8231_NUM_GPIOS;
++	gpio_cfg.ngpio_per_reg = RTL8231_BITS_VAL;
++
++	gpio_cfg.reg_dat_base = GPIO_REGMAP_ADDR(RTL8231_REG_GPIO_DATA0);
++	gpio_cfg.reg_set_base = GPIO_REGMAP_ADDR(RTL8231_REG_GPIO_DATA0);
++	gpio_cfg.reg_dir_in_base = GPIO_REGMAP_ADDR(RTL8231_REG_GPIO_DIR0);
++
++	gpio_cfg.reg_mask_xlate = rtl8231_gpio_reg_mask_xlate;
++
++	return PTR_ERR_OR_ZERO(devm_gpio_regmap_register(dev, &gpio_cfg));
++}
++
++static struct platform_driver rtl8231_pinctrl_driver = {
++	.driver = {
++		.name = "rtl8231-pinctrl",
++	},
++	.probe = rtl8231_pinctrl_probe,
++};
++module_platform_driver(rtl8231_pinctrl_driver);
++
++MODULE_AUTHOR("Sander Vanheule <sander at svanheule.net>");
++MODULE_DESCRIPTION("Realtek RTL8231 pin control and GPIO support");
++MODULE_LICENSE("GPL");
diff --git a/target/linux/realtek/patches-6.6/804-leds-Add-support-for-RTL8231-LED-scan-matrix.patch b/target/linux/realtek/patches-6.6/804-leds-Add-support-for-RTL8231-LED-scan-matrix.patch
new file mode 100644
index 0000000000..37542b8230
--- /dev/null
+++ b/target/linux/realtek/patches-6.6/804-leds-Add-support-for-RTL8231-LED-scan-matrix.patch
@@ -0,0 +1,338 @@
+From 6b797a97c007e46d6081fc6f4b41ce8407078605 Mon Sep 17 00:00:00 2001
+From: Sander Vanheule <sander at svanheule.net>
+Date: Mon, 10 May 2021 22:16:11 +0200
+Subject: [PATCH] leds: Add support for RTL8231 LED scan matrix
+
+Both single and bi-color scanning modes are supported. The driver will
+verify that the addresses are valid for the current mode, before
+registering the LEDs. LEDs can be turned on, off, or toggled at one of
+six predefined rates from 40ms to 1280ms.
+
+Implements a platform device for use as a child device with RTL8231 MFD,
+and uses the parent regmap to access the required registers.
+
+Signed-off-by: Sander Vanheule <sander at svanheule.net>
+---
+ drivers/leds/Kconfig        |  10 ++
+ drivers/leds/Makefile       |   1 +
+ drivers/leds/leds-rtl8231.c | 291 ++++++++++++++++++++++++++++++++++++
+ 3 files changed, 302 insertions(+)
+ create mode 100644 drivers/leds/leds-rtl8231.c
+
+--- a/drivers/leds/Kconfig
++++ b/drivers/leds/Kconfig
+@@ -586,6 +586,16 @@ config LEDS_REGULATOR
+ 	help
+ 	  This option enables support for regulator driven LEDs.
+ 
++config LEDS_RTL8231
++	tristate "RTL8231 LED matrix support"
++	depends on LEDS_CLASS
++	depends on MFD_RTL8231
++	default MFD_RTL8231
++	help
++	  This option enables support for using the LED scanning matrix output
++	  of the RTL8231 GPIO and LED expander chip.
++	  When built as a module, this module will be named leds-rtl8231.
++
+ config LEDS_BD2606MVV
+ 	tristate "LED driver for BD2606MVV"
+ 	depends on LEDS_CLASS
+--- a/drivers/leds/Makefile
++++ b/drivers/leds/Makefile
+@@ -77,6 +77,7 @@ obj-$(CONFIG_LEDS_PM8058)		+= leds-pm805
+ obj-$(CONFIG_LEDS_POWERNV)		+= leds-powernv.o
+ obj-$(CONFIG_LEDS_PWM)			+= leds-pwm.o
+ obj-$(CONFIG_LEDS_REGULATOR)		+= leds-regulator.o
++obj-$(CONFIG_LEDS_RTL8231)		+= leds-rtl8231.o
+ obj-$(CONFIG_LEDS_SC27XX_BLTC)		+= leds-sc27xx-bltc.o
+ obj-$(CONFIG_LEDS_ST1202)		+= leds-st1202.o
+ obj-$(CONFIG_LEDS_SUNFIRE)		+= leds-sunfire.o
+--- /dev/null
++++ b/drivers/leds/leds-rtl8231.c
+@@ -0,0 +1,285 @@
++// SPDX-License-Identifier: GPL-2.0-only
++
++#include <linux/device.h>
++#include <linux/leds.h>
++#include <linux/mod_devicetable.h>
++#include <linux/module.h>
++#include <linux/platform_device.h>
++#include <linux/property.h>
++#include <linux/regmap.h>
++
++#include <linux/mfd/rtl8231.h>
++
++/**
++ * struct led_toggle_rate - description of an LED blinking mode
++ * @interval_ms:	LED toggle rate in milliseconds
++ * @mode:		Register field value used to activate this mode
++ *
++ * For LED hardware accelerated blinking, with equal on and off delay.
++ * Both delays are given by @interval, so the interval at which the LED blinks
++ * (i.e. turn on and off once) is double this value.
++ */
++struct led_toggle_rate {
++	u16 interval_ms;
++	u8 mode;
++};
++
++/**
++ * struct led_modes - description of all LED modes
++ * @toggle_rates:	Array of led_toggle_rate values, sorted by ascending interval
++ * @num_toggle_rates:	Number of elements in @led_toggle_rate
++ * @off:		Register field value to turn LED off
++ * @on:			Register field value to turn LED on
++ */
++struct led_modes {
++	const struct led_toggle_rate *toggle_rates;
++	unsigned int num_toggle_rates;
++	u8 off;
++	u8 on;
++};
++
++struct rtl8231_led {
++	struct led_classdev led;
++	const struct led_modes *modes;
++	struct regmap_field *reg_field;
++};
++#define to_rtl8231_led(_cdev) container_of(_cdev, struct rtl8231_led, led)
++
++#define RTL8231_NUM_LEDS	3
++#define RTL8231_LED_PER_REG	5
++#define RTL8231_BITS_PER_LED	3
++
++static const unsigned int rtl8231_led_port_counts_single[RTL8231_NUM_LEDS] = {32, 32, 24};
++static const unsigned int rtl8231_led_port_counts_bicolor[RTL8231_NUM_LEDS] = {24, 24, 24};
++
++static const unsigned int rtl8231_led_base[RTL8231_NUM_LEDS] = {
++	RTL8231_REG_LED0_BASE,
++	RTL8231_REG_LED1_BASE,
++	RTL8231_REG_LED2_BASE,
++};
++
++#define RTL8231_DEFAULT_TOGGLE_INTERVAL_MS	500
++
++static const struct led_toggle_rate rtl8231_toggle_rates[] = {
++	{  40, 1},
++	{  80, 2},
++	{ 160, 3},
++	{ 320, 4},
++	{ 640, 5},
++	{1280, 6},
++};
++
++static const struct led_modes rtl8231_led_modes = {
++	.off = 0,
++	.on = 7,
++	.num_toggle_rates = ARRAY_SIZE(rtl8231_toggle_rates),
++	.toggle_rates = rtl8231_toggle_rates,
++};
++
++static void rtl8231_led_brightness_set(struct led_classdev *led_cdev,
++	enum led_brightness brightness)
++{
++	struct rtl8231_led *pled = to_rtl8231_led(led_cdev);
++
++	if (brightness)
++		regmap_field_write(pled->reg_field, pled->modes->on);
++	else
++		regmap_field_write(pled->reg_field, pled->modes->off);
++}
++
++static enum led_brightness rtl8231_led_brightness_get(struct led_classdev *led_cdev)
++{
++	struct rtl8231_led *pled = to_rtl8231_led(led_cdev);
++	u32 current_mode = pled->modes->off;
++
++	regmap_field_read(pled->reg_field, &current_mode);
++
++	if (current_mode == pled->modes->off)
++		return LED_OFF;
++	else
++		return LED_ON;
++}
++
++static unsigned int rtl8231_led_current_interval(struct rtl8231_led *pled)
++{
++	unsigned int mode;
++	unsigned int i;
++
++	if (regmap_field_read(pled->reg_field, &mode))
++		return 0;
++
++	for (i = 0; i < pled->modes->num_toggle_rates; i++)
++		if (mode == pled->modes->toggle_rates[i].mode)
++			return pled->modes->toggle_rates[i].interval_ms;
++
++	return 0;
++}
++
++static int rtl8231_led_blink_set(struct led_classdev *led_cdev, unsigned long *delay_on,
++	unsigned long *delay_off)
++{
++	struct rtl8231_led *pled = to_rtl8231_led(led_cdev);
++	const struct led_toggle_rate *rates = pled->modes->toggle_rates;
++	unsigned int num_rates = pled->modes->num_toggle_rates;
++	unsigned int interval_ms;
++	unsigned int i;
++	int err;
++
++	if (*delay_on == 0 && *delay_off == 0) {
++		interval_ms = RTL8231_DEFAULT_TOGGLE_INTERVAL_MS;
++	} else {
++		/*
++		 * If the current mode is blinking, choose the delay that (likely) changed.
++		 * Otherwise, choose the interval that would have the same total delay.
++		 */
++		interval_ms = rtl8231_led_current_interval(pled);
++		if (interval_ms > 0 && interval_ms == *delay_off)
++			interval_ms = *delay_on;
++		else if (interval_ms > 0 && interval_ms == *delay_on)
++			interval_ms = *delay_off;
++		else
++			interval_ms = (*delay_on + *delay_off) / 2;
++	}
++
++	/* Find clamped toggle interval */
++	for (i = 0; i < (num_rates - 1); i++)
++		if (interval_ms > rates[i].interval_ms)
++			break;
++
++	interval_ms = rates[i].interval_ms;
++
++	err = regmap_field_write(pled->reg_field, rates[i].mode);
++	if (err)
++		return err;
++
++	*delay_on = interval_ms;
++	*delay_off = interval_ms;
++
++	return 0;
++}
++
++static int rtl8231_led_read_address(struct fwnode_handle *fwnode, unsigned int *addr_port,
++	unsigned int *addr_led)
++{
++	u32 addr[2];
++	int err;
++
++	err = fwnode_property_count_u32(fwnode, "reg");
++	if (err < 0)
++		return err;
++	if (err != ARRAY_SIZE(addr))
++		return -EINVAL;
++
++	err = fwnode_property_read_u32_array(fwnode, "reg", addr, ARRAY_SIZE(addr));
++	if (err)
++		return err;
++
++	*addr_port = addr[0];
++	*addr_led = addr[1];
++
++	return 0;
++}
++
++static const struct regmap_field *rtl8231_led_get_field(struct device *dev, struct regmap *map,
++		unsigned int port_index, unsigned int led_index)
++{
++	unsigned int offset = port_index / RTL8231_LED_PER_REG;
++	unsigned int shift = (port_index % RTL8231_LED_PER_REG) * RTL8231_BITS_PER_LED;
++	const struct reg_field field = REG_FIELD(rtl8231_led_base[led_index] + offset, shift,
++			shift + RTL8231_BITS_PER_LED - 1);
++
++	return devm_regmap_field_alloc(dev, map, field);
++}
++
++static int rtl8231_led_probe_single(struct device *dev, struct regmap *map,
++	const unsigned int *port_counts, struct fwnode_handle *fwnode)
++{
++	struct led_init_data init_data = {};
++	struct rtl8231_led *pled;
++	unsigned int port_index;
++	unsigned int led_index;
++	int err;
++
++	pled = devm_kzalloc(dev, sizeof(*pled), GFP_KERNEL);
++	if (!pled)
++		return -ENOMEM;
++
++	err = rtl8231_led_read_address(fwnode, &port_index, &led_index);
++	if (err) {
++		dev_err(dev, "LED address invalid");
++		return err;
++	}
++
++	if (led_index >= RTL8231_NUM_LEDS || port_index >= port_counts[led_index]) {
++		dev_err(dev, "LED address (%d.%d) invalid", port_index, led_index);
++		return -EINVAL;
++	}
++
++	pled->reg_field = rtl8231_led_get_field(dev, map, port_index, led_index);
++	if (IS_ERR(pled->reg_field))
++		return PTR_ERR(pled->reg_field);
++
++	pled->modes = &rtl8231_led_modes;
++
++	pled->led.max_brightness = 1;
++	pled->led.brightness_get = rtl8231_led_brightness_get;
++	pled->led.brightness_set = rtl8231_led_brightness_set;
++	pled->led.blink_set = rtl8231_led_blink_set;
++
++	init_data.fwnode = fwnode;
++
++	return devm_led_classdev_register_ext(dev, &pled->led, &init_data);
++}
++
++static int rtl8231_led_probe(struct platform_device *pdev)
++{
++	struct device *dev = &pdev->dev;
++	const unsigned int *port_counts;
++	struct fwnode_handle *child;
++	struct regmap *map;
++	int err;
++
++	map = dev_get_regmap(dev->parent, NULL);
++	if (!map)
++		return -ENODEV;
++
++	if (device_property_match_string(dev, "realtek,led-scan-mode", "single-color") >= 0) {
++		port_counts = rtl8231_led_port_counts_single;
++		regmap_update_bits(map, RTL8231_REG_FUNC0,
++			RTL8231_FUNC0_SCAN_MODE, RTL8231_FUNC0_SCAN_SINGLE);
++	} else if (device_property_match_string(dev, "realtek,led-scan-mode", "bi-color") >= 0) {
++		port_counts = rtl8231_led_port_counts_bicolor;
++		regmap_update_bits(map, RTL8231_REG_FUNC0,
++			RTL8231_FUNC0_SCAN_MODE, RTL8231_FUNC0_SCAN_BICOLOR);
++	} else {
++		dev_err(dev, "scan mode missing or invalid");
++		return -EINVAL;
++	}
++
++	fwnode_for_each_available_child_node(dev->fwnode, child) {
++		err = rtl8231_led_probe_single(dev, map, port_counts, child);
++		if (err)
++			dev_warn(dev, "failed to register LED %pfwP", child);
++	}
++
++	return 0;
++}
++
++static const struct of_device_id of_rtl8231_led_match[] = {
++	{ .compatible = "realtek,rtl8231-leds" },
++	{}
++};
++MODULE_DEVICE_TABLE(of, of_rtl8231_led_match);
++
++static struct platform_driver rtl8231_led_driver = {
++	.driver = {
++		.name = "rtl8231-leds",
++		.of_match_table = of_rtl8231_led_match,
++	},
++	.probe = rtl8231_led_probe,
++};
++module_platform_driver(rtl8231_led_driver);
++
++MODULE_AUTHOR("Sander Vanheule <sander at svanheule.net>");
++MODULE_DESCRIPTION("Realtek RTL8231 LED support");
++MODULE_LICENSE("GPL");




More information about the lede-commits mailing list