aboutsummaryrefslogtreecommitdiffstats
path: root/target/linux/realtek/patches-5.15
diff options
context:
space:
mode:
Diffstat (limited to 'target/linux/realtek/patches-5.15')
-rw-r--r--target/linux/realtek/patches-5.15/001-5.13-dt-bindings-gpio-binding-for-realtek-otto-gpio.patch103
-rw-r--r--target/linux/realtek/patches-5.15/002-5.13-gpio-add-realtek-otto-gpio-support.patch394
-rw-r--r--target/linux/realtek/patches-5.15/003-5.12-spi-realtek-rtl838x-rtl839x-spi-controller.patch57
-rw-r--r--target/linux/realtek/patches-5.15/004-5.12-spi-realtek-rtl-add-support-for-realtek-rtl838x-rtl839x-spi-controllers.patch248
-rw-r--r--target/linux/realtek/patches-5.15/005-5.12-dt-bindings-interrupt-controller-add-realtek-rtl838x-rtl839x-support.patch78
-rw-r--r--target/linux/realtek/patches-5.15/006-5.12-irqchip-add-support-for-realtek-rtl838x-rtl839x-interrupt-controller.patch211
-rw-r--r--target/linux/realtek/patches-5.15/007-5.16-gpio-realtek-realtek-otto-fix-gpio-line-irq-offset.patch22
-rw-r--r--target/linux/realtek/patches-5.15/008-5.17-watchdog-add-realtek-otto-watchdog-timer.patch467
-rw-r--r--target/linux/realtek/patches-5.15/020-v5.17-net-mdio-add-helpers-to-extract-clause-45-regad-and-.patch53
-rw-r--r--target/linux/realtek/patches-5.15/021-v5.19-02-gpio-realtek-otto-Support-reversed-port-layouts.patch123
-rw-r--r--target/linux/realtek/patches-5.15/021-v5.19-03-gpio-realtek-otto-Support-per-cpu-interrupts.patch153
-rw-r--r--target/linux/realtek/patches-5.15/021-v5.19-04-gpio-realtek-otto-Add-RTL930x-support.patch29
-rw-r--r--target/linux/realtek/patches-5.15/021-v5.19-06-gpio-realtek-otto-Add-RTL931x-support.patch30
-rw-r--r--target/linux/realtek/patches-5.15/300-mips-add-rtl838x-platform.patch96
-rw-r--r--target/linux/realtek/patches-5.15/301-gpio-add-rtl8231-driver.patch50
-rw-r--r--target/linux/realtek/patches-5.15/302-clocksource-add-otto-driver.patch40
-rw-r--r--target/linux/realtek/patches-5.15/303-gpio-update-dependencies-for-gpio-realtek-otto.patch26
-rw-r--r--target/linux/realtek/patches-5.15/304-spi-update-dependency-for-spi-realtek-rtl.patch25
-rw-r--r--target/linux/realtek/patches-5.15/305-irqchip-update-dependency-for-irq-realtek-rtl.patch22
-rw-r--r--target/linux/realtek/patches-5.15/307-wdt-update-dependency-for-realtek-otto-wdt.patch32
-rw-r--r--target/linux/realtek/patches-5.15/308-otto-wdt-fix-missing-math-header.patch28
-rw-r--r--target/linux/realtek/patches-5.15/309-cevt-rtl9300-support.patch54
-rw-r--r--target/linux/realtek/patches-5.15/310-add-i2c-rtl9300-support.patch46
-rw-r--r--target/linux/realtek/patches-5.15/311-add-i2c-mux-rtl9300-support.patch46
-rw-r--r--target/linux/realtek/patches-5.15/312-rt9313-support.patch76
-rw-r--r--target/linux/realtek/patches-5.15/315-irqchip-irq-realtek-rtl-add-VPE-support.patch407
-rw-r--r--target/linux/realtek/patches-5.15/316-otto-gpio-uniprocessor-irq-mask.patch51
-rw-r--r--target/linux/realtek/patches-5.15/317-gpio-realtek-otto-switch-to-32-bit-I-O.patch373
-rw-r--r--target/linux/realtek/patches-5.15/318-add-rtl83xx-clk-support.patch33
-rw-r--r--target/linux/realtek/patches-5.15/319-irqchip-irq-realtek-rtl-fix-VPE-affinity.patch159
-rw-r--r--target/linux/realtek/patches-5.15/700-net-dsa-add-support-for-rtl838x-switch.patch40
-rw-r--r--target/linux/realtek/patches-5.15/701-net-dsa-add-rtl838x-support-for-tag-trailer.patch61
-rw-r--r--target/linux/realtek/patches-5.15/702-net-dsa-increase-dsa-max-ports-for-rtl838x.patch32
-rw-r--r--target/linux/realtek/patches-5.15/702-net-ethernet-add-support-for-rtl838x-ethernet.patch48
-rw-r--r--target/linux/realtek/patches-5.15/703-include-linux-add-phy-ops-for-rtl838x.patch34
-rw-r--r--target/linux/realtek/patches-5.15/704-drivers-net-phy-eee-support-for-rtl838x.patch61
-rw-r--r--target/linux/realtek/patches-5.15/704-include-linux-add-phy-hsgmii-mode.patch33
-rw-r--r--target/linux/realtek/patches-5.15/705-add-rtl-phy.patch39
-rw-r--r--target/linux/realtek/patches-5.15/705-include-linux-phy-increase-phy-address-number-for-rtl839x.patch32
-rw-r--r--target/linux/realtek/patches-5.15/708-brflood-api.patch162
-rw-r--r--target/linux/realtek/patches-5.15/709-lag-offloading.patch781
-rw-r--r--target/linux/realtek/patches-5.15/710-net-phy-sfp-re-probe-modules-on-DEV_UP-event.patch26
-rw-r--r--target/linux/realtek/patches-5.15/711-net-phy-add-an-MDIO-SMBus-library.patch168
-rw-r--r--target/linux/realtek/patches-5.15/712-net-phy-sfp-add-support-for-SMBus.patch99
-rw-r--r--target/linux/realtek/patches-5.15/713-v5.12-net-dsa-configure-better-brport-flags-when-ports-lea.patch148
-rw-r--r--target/linux/realtek/patches-5.15/800-net-mdio-support-hardware-assisted-indirect-access.patch840
46 files changed, 6136 insertions, 0 deletions
diff --git a/target/linux/realtek/patches-5.15/001-5.13-dt-bindings-gpio-binding-for-realtek-otto-gpio.patch b/target/linux/realtek/patches-5.15/001-5.13-dt-bindings-gpio-binding-for-realtek-otto-gpio.patch
new file mode 100644
index 0000000000..80b806d89f
--- /dev/null
+++ b/target/linux/realtek/patches-5.15/001-5.13-dt-bindings-gpio-binding-for-realtek-otto-gpio.patch
@@ -0,0 +1,103 @@
+From a362c0ce64866939c3daa17c76943cfed555b065 Mon Sep 17 00:00:00 2001
+From: Sander Vanheule <sander@svanheule.net>
+Date: Tue, 30 Mar 2021 19:48:42 +0200
+Subject: dt-bindings: gpio: Binding for Realtek Otto GPIO
+
+Add a binding description for Realtek's GPIO controller found on several
+of their MIPS-based SoCs (codenamed Otto), such as the RTL838x and
+RTL839x series of switch SoCs.
+
+A fallback binding 'realtek,otto-gpio' is provided for cases where the
+actual port ordering is not known yet, and enabling the interrupt
+controller may result in uncaught interrupts.
+
+Signed-off-by: Sander Vanheule <sander@svanheule.net>
+Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
+Reviewed-by: Rob Herring <robh@kernel.org>
+Signed-off-by: Bartosz Golaszewski <bgolaszewski@baylibre.com>
+---
+ .../bindings/gpio/realtek,otto-gpio.yaml | 78 ++++++++++++++++++++++
+ 1 file changed, 78 insertions(+)
+ create mode 100644 Documentation/devicetree/bindings/gpio/realtek,otto-gpio.yaml
+
+--- /dev/null
++++ b/Documentation/devicetree/bindings/gpio/realtek,otto-gpio.yaml
+@@ -0,0 +1,78 @@
++# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
++%YAML 1.2
++---
++$id: http://devicetree.org/schemas/gpio/realtek,otto-gpio.yaml#
++$schema: http://devicetree.org/meta-schemas/core.yaml#
++
++title: Realtek Otto GPIO controller
++
++maintainers:
++ - Sander Vanheule <sander@svanheule.net>
++ - Bert Vermeulen <bert@biot.com>
++
++description: |
++ Realtek's GPIO controller on their MIPS switch SoCs (Otto platform) consists
++ of two banks of 32 GPIOs. These GPIOs can generate edge-triggered interrupts.
++ Each bank's interrupts are cascased into one interrupt line on the parent
++ interrupt controller, if provided.
++ This binding allows defining a single bank in the devicetree. The interrupt
++ controller is not supported on the fallback compatible name, which only
++ allows for GPIO port use.
++
++properties:
++ $nodename:
++ pattern: "^gpio@[0-9a-f]+$"
++
++ compatible:
++ items:
++ - enum:
++ - realtek,rtl8380-gpio
++ - realtek,rtl8390-gpio
++ - const: realtek,otto-gpio
++
++ reg:
++ maxItems: 1
++
++ "#gpio-cells":
++ const: 2
++
++ gpio-controller: true
++
++ ngpios:
++ minimum: 1
++ maximum: 32
++
++ interrupt-controller: true
++
++ "#interrupt-cells":
++ const: 2
++
++ interrupts:
++ maxItems: 1
++
++required:
++ - compatible
++ - reg
++ - "#gpio-cells"
++ - gpio-controller
++
++additionalProperties: false
++
++dependencies:
++ interrupt-controller: [ interrupts ]
++
++examples:
++ - |
++ gpio@3500 {
++ compatible = "realtek,rtl8380-gpio", "realtek,otto-gpio";
++ reg = <0x3500 0x1c>;
++ gpio-controller;
++ #gpio-cells = <2>;
++ ngpios = <24>;
++ interrupt-controller;
++ #interrupt-cells = <2>;
++ interrupt-parent = <&rtlintc>;
++ interrupts = <23>;
++ };
++
++...
diff --git a/target/linux/realtek/patches-5.15/002-5.13-gpio-add-realtek-otto-gpio-support.patch b/target/linux/realtek/patches-5.15/002-5.13-gpio-add-realtek-otto-gpio-support.patch
new file mode 100644
index 0000000000..62e4338cbb
--- /dev/null
+++ b/target/linux/realtek/patches-5.15/002-5.13-gpio-add-realtek-otto-gpio-support.patch
@@ -0,0 +1,394 @@
+From f0f7d662e8514169c90d3d84cd6df773b2983088 Mon Sep 17 00:00:00 2001
+From: Sander Vanheule <sander@svanheule.net>
+Date: Tue, 30 Mar 2021 19:48:43 +0200
+Subject: gpio: Add Realtek Otto GPIO support
+
+Realtek MIPS SoCs (platform name Otto) have GPIO controllers with up to
+64 GPIOs, divided over two banks. Each bank has a set of registers for
+32 GPIOs, with support for edge-triggered interrupts.
+
+Each GPIO bank consists of four 8-bit GPIO ports (ABCD and EFGH). Most
+registers pack one bit per GPIO, except for the IMR register, which
+packs two bits per GPIO (AB-CD).
+
+Although the byte order is currently assumed to have port A..D at offset
+0x0..0x3, this has been observed to be reversed on other, Lexra-based,
+SoCs (e.g. RTL8196E/97D/97F).
+
+Interrupt support is disabled for the fallback devicetree-compatible
+'realtek,otto-gpio'. This allows for quick support of GPIO banks in
+which the byte order would be unknown. In this case, the port ordering
+in the IMR registers may not match the reversed order in the other
+registers (DCBA, and BA-DC or DC-BA).
+
+Signed-off-by: Sander Vanheule <sander@svanheule.net>
+Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
+Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>
+Signed-off-by: Bartosz Golaszewski <bgolaszewski@baylibre.com>
+---
+ drivers/gpio/Kconfig | 13 ++
+ drivers/gpio/Makefile | 1 +
+ drivers/gpio/gpio-realtek-otto.c | 325 +++++++++++++++++++++++++++++++++++++++
+ 3 files changed, 339 insertions(+)
+ create mode 100644 drivers/gpio/gpio-realtek-otto.c
+
+--- a/drivers/gpio/Kconfig
++++ b/drivers/gpio/Kconfig
+@@ -489,6 +489,19 @@ config GPIO_RDA
+ help
+ Say Y here to support RDA Micro GPIO controller.
+
++config GPIO_REALTEK_OTTO
++ tristate "Realtek Otto GPIO support"
++ depends on MACH_REALTEK_RTL
++ default MACH_REALTEK_RTL
++ select GPIO_GENERIC
++ select GPIOLIB_IRQCHIP
++ help
++ The GPIO controller on the Otto MIPS platform supports up to two
++ banks of 32 GPIOs, with edge triggered interrupts. The 32 GPIOs
++ are grouped in four 8-bit wide ports.
++
++ When built as a module, the module will be called realtek_otto_gpio.
++
+ config GPIO_REG
+ bool
+ help
+--- a/drivers/gpio/Makefile
++++ b/drivers/gpio/Makefile
+@@ -125,6 +125,7 @@ obj-$(CONFIG_GPIO_RC5T583) += gpio-rc5t
+ obj-$(CONFIG_GPIO_RCAR) += gpio-rcar.o
+ obj-$(CONFIG_GPIO_RDA) += gpio-rda.o
+ obj-$(CONFIG_GPIO_RDC321X) += gpio-rdc321x.o
++obj-$(CONFIG_GPIO_REALTEK_OTTO) += gpio-realtek-otto.o
+ obj-$(CONFIG_GPIO_REG) += gpio-reg.o
+ obj-$(CONFIG_ARCH_SA1100) += gpio-sa1100.o
+ obj-$(CONFIG_GPIO_SAMA5D2_PIOBU) += gpio-sama5d2-piobu.o
+--- /dev/null
++++ b/drivers/gpio/gpio-realtek-otto.c
+@@ -0,0 +1,325 @@
++// SPDX-License-Identifier: GPL-2.0-only
++
++#include <linux/gpio/driver.h>
++#include <linux/irq.h>
++#include <linux/minmax.h>
++#include <linux/mod_devicetable.h>
++#include <linux/module.h>
++#include <linux/platform_device.h>
++#include <linux/property.h>
++
++/*
++ * Total register block size is 0x1C for one bank of four ports (A, B, C, D).
++ * An optional second bank, with ports E, F, G, and H, may be present, starting
++ * at register offset 0x1C.
++ */
++
++/*
++ * Pin select: (0) "normal", (1) "dedicate peripheral"
++ * Not used on RTL8380/RTL8390, peripheral selection is managed by control bits
++ * in the peripheral registers.
++ */
++#define REALTEK_GPIO_REG_CNR 0x00
++/* Clear bit (0) for input, set bit (1) for output */
++#define REALTEK_GPIO_REG_DIR 0x08
++#define REALTEK_GPIO_REG_DATA 0x0C
++/* Read bit for IRQ status, write 1 to clear IRQ */
++#define REALTEK_GPIO_REG_ISR 0x10
++/* Two bits per GPIO in IMR registers */
++#define REALTEK_GPIO_REG_IMR 0x14
++#define REALTEK_GPIO_REG_IMR_AB 0x14
++#define REALTEK_GPIO_REG_IMR_CD 0x18
++#define REALTEK_GPIO_IMR_LINE_MASK GENMASK(1, 0)
++#define REALTEK_GPIO_IRQ_EDGE_FALLING 1
++#define REALTEK_GPIO_IRQ_EDGE_RISING 2
++#define REALTEK_GPIO_IRQ_EDGE_BOTH 3
++
++#define REALTEK_GPIO_MAX 32
++#define REALTEK_GPIO_PORTS_PER_BANK 4
++
++/**
++ * realtek_gpio_ctrl - Realtek Otto GPIO driver data
++ *
++ * @gc: Associated gpio_chip instance
++ * @base: Base address of the register block for a GPIO bank
++ * @lock: Lock for accessing the IRQ registers and values
++ * @intr_mask: Mask for interrupts lines
++ * @intr_type: Interrupt type selection
++ *
++ * Because the interrupt mask register (IMR) combines the function of IRQ type
++ * selection and masking, two extra values are stored. @intr_mask is used to
++ * mask/unmask the interrupts for a GPIO port, and @intr_type is used to store
++ * the selected interrupt types. The logical AND of these values is written to
++ * IMR on changes.
++ */
++struct realtek_gpio_ctrl {
++ struct gpio_chip gc;
++ void __iomem *base;
++ raw_spinlock_t lock;
++ u16 intr_mask[REALTEK_GPIO_PORTS_PER_BANK];
++ u16 intr_type[REALTEK_GPIO_PORTS_PER_BANK];
++};
++
++/* Expand with more flags as devices with other quirks are added */
++enum realtek_gpio_flags {
++ /*
++ * Allow disabling interrupts, for cases where the port order is
++ * unknown. This may result in a port mismatch between ISR and IMR.
++ * An interrupt would appear to come from a different line than the
++ * line the IRQ handler was assigned to, causing uncaught interrupts.
++ */
++ GPIO_INTERRUPTS_DISABLED = BIT(0),
++};
++
++static struct realtek_gpio_ctrl *irq_data_to_ctrl(struct irq_data *data)
++{
++ struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
++
++ return container_of(gc, struct realtek_gpio_ctrl, gc);
++}
++
++/*
++ * Normal port order register access
++ *
++ * Port information is stored with the first port at offset 0, followed by the
++ * second, etc. Most registers store one bit per GPIO and use a u8 value per
++ * port. The two interrupt mask registers store two bits per GPIO, so use u16
++ * values.
++ */
++static void realtek_gpio_write_imr(struct realtek_gpio_ctrl *ctrl,
++ unsigned int port, u16 irq_type, u16 irq_mask)
++{
++ iowrite16(irq_type & irq_mask, ctrl->base + REALTEK_GPIO_REG_IMR + 2 * port);
++}
++
++static void realtek_gpio_clear_isr(struct realtek_gpio_ctrl *ctrl,
++ unsigned int port, u8 mask)
++{
++ iowrite8(mask, ctrl->base + REALTEK_GPIO_REG_ISR + port);
++}
++
++static u8 realtek_gpio_read_isr(struct realtek_gpio_ctrl *ctrl, unsigned int port)
++{
++ return ioread8(ctrl->base + REALTEK_GPIO_REG_ISR + port);
++}
++
++/* Set the rising and falling edge mask bits for a GPIO port pin */
++static u16 realtek_gpio_imr_bits(unsigned int pin, u16 value)
++{
++ return (value & REALTEK_GPIO_IMR_LINE_MASK) << 2 * pin;
++}
++
++static void realtek_gpio_irq_ack(struct irq_data *data)
++{
++ struct realtek_gpio_ctrl *ctrl = irq_data_to_ctrl(data);
++ irq_hw_number_t line = irqd_to_hwirq(data);
++ unsigned int port = line / 8;
++ unsigned int port_pin = line % 8;
++
++ realtek_gpio_clear_isr(ctrl, port, BIT(port_pin));
++}
++
++static void realtek_gpio_irq_unmask(struct irq_data *data)
++{
++ struct realtek_gpio_ctrl *ctrl = irq_data_to_ctrl(data);
++ unsigned int line = irqd_to_hwirq(data);
++ unsigned int port = line / 8;
++ unsigned int port_pin = line % 8;
++ unsigned long flags;
++ u16 m;
++
++ raw_spin_lock_irqsave(&ctrl->lock, flags);
++ m = ctrl->intr_mask[port];
++ m |= realtek_gpio_imr_bits(port_pin, REALTEK_GPIO_IMR_LINE_MASK);
++ ctrl->intr_mask[port] = m;
++ realtek_gpio_write_imr(ctrl, port, ctrl->intr_type[port], m);
++ raw_spin_unlock_irqrestore(&ctrl->lock, flags);
++}
++
++static void realtek_gpio_irq_mask(struct irq_data *data)
++{
++ struct realtek_gpio_ctrl *ctrl = irq_data_to_ctrl(data);
++ unsigned int line = irqd_to_hwirq(data);
++ unsigned int port = line / 8;
++ unsigned int port_pin = line % 8;
++ unsigned long flags;
++ u16 m;
++
++ raw_spin_lock_irqsave(&ctrl->lock, flags);
++ m = ctrl->intr_mask[port];
++ m &= ~realtek_gpio_imr_bits(port_pin, REALTEK_GPIO_IMR_LINE_MASK);
++ ctrl->intr_mask[port] = m;
++ realtek_gpio_write_imr(ctrl, port, ctrl->intr_type[port], m);
++ raw_spin_unlock_irqrestore(&ctrl->lock, flags);
++}
++
++static int realtek_gpio_irq_set_type(struct irq_data *data, unsigned int flow_type)
++{
++ struct realtek_gpio_ctrl *ctrl = irq_data_to_ctrl(data);
++ unsigned int line = irqd_to_hwirq(data);
++ unsigned int port = line / 8;
++ unsigned int port_pin = line % 8;
++ unsigned long flags;
++ u16 type, t;
++
++ switch (flow_type & IRQ_TYPE_SENSE_MASK) {
++ case IRQ_TYPE_EDGE_FALLING:
++ type = REALTEK_GPIO_IRQ_EDGE_FALLING;
++ break;
++ case IRQ_TYPE_EDGE_RISING:
++ type = REALTEK_GPIO_IRQ_EDGE_RISING;
++ break;
++ case IRQ_TYPE_EDGE_BOTH:
++ type = REALTEK_GPIO_IRQ_EDGE_BOTH;
++ break;
++ default:
++ return -EINVAL;
++ }
++
++ irq_set_handler_locked(data, handle_edge_irq);
++
++ raw_spin_lock_irqsave(&ctrl->lock, flags);
++ t = ctrl->intr_type[port];
++ t &= ~realtek_gpio_imr_bits(port_pin, REALTEK_GPIO_IMR_LINE_MASK);
++ t |= realtek_gpio_imr_bits(port_pin, type);
++ ctrl->intr_type[port] = t;
++ realtek_gpio_write_imr(ctrl, port, t, ctrl->intr_mask[port]);
++ raw_spin_unlock_irqrestore(&ctrl->lock, flags);
++
++ return 0;
++}
++
++static void realtek_gpio_irq_handler(struct irq_desc *desc)
++{
++ struct gpio_chip *gc = irq_desc_get_handler_data(desc);
++ struct realtek_gpio_ctrl *ctrl = gpiochip_get_data(gc);
++ struct irq_chip *irq_chip = irq_desc_get_chip(desc);
++ unsigned int lines_done;
++ unsigned int port_pin_count;
++ unsigned int irq;
++ unsigned long status;
++ int offset;
++
++ chained_irq_enter(irq_chip, desc);
++
++ for (lines_done = 0; lines_done < gc->ngpio; lines_done += 8) {
++ status = realtek_gpio_read_isr(ctrl, lines_done / 8);
++ port_pin_count = min(gc->ngpio - lines_done, 8U);
++ for_each_set_bit(offset, &status, port_pin_count) {
++ irq = irq_find_mapping(gc->irq.domain, offset);
++ generic_handle_irq(irq);
++ }
++ }
++
++ chained_irq_exit(irq_chip, desc);
++}
++
++static int realtek_gpio_irq_init(struct gpio_chip *gc)
++{
++ struct realtek_gpio_ctrl *ctrl = gpiochip_get_data(gc);
++ unsigned int port;
++
++ for (port = 0; (port * 8) < gc->ngpio; port++) {
++ realtek_gpio_write_imr(ctrl, port, 0, 0);
++ realtek_gpio_clear_isr(ctrl, port, GENMASK(7, 0));
++ }
++
++ return 0;
++}
++
++static struct irq_chip realtek_gpio_irq_chip = {
++ .name = "realtek-otto-gpio",
++ .irq_ack = realtek_gpio_irq_ack,
++ .irq_mask = realtek_gpio_irq_mask,
++ .irq_unmask = realtek_gpio_irq_unmask,
++ .irq_set_type = realtek_gpio_irq_set_type,
++};
++
++static const struct of_device_id realtek_gpio_of_match[] = {
++ {
++ .compatible = "realtek,otto-gpio",
++ .data = (void *)GPIO_INTERRUPTS_DISABLED,
++ },
++ {
++ .compatible = "realtek,rtl8380-gpio",
++ },
++ {
++ .compatible = "realtek,rtl8390-gpio",
++ },
++ {}
++};
++MODULE_DEVICE_TABLE(of, realtek_gpio_of_match);
++
++static int realtek_gpio_probe(struct platform_device *pdev)
++{
++ struct device *dev = &pdev->dev;
++ unsigned int dev_flags;
++ struct gpio_irq_chip *girq;
++ struct realtek_gpio_ctrl *ctrl;
++ u32 ngpios;
++ int err, irq;
++
++ ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL);
++ if (!ctrl)
++ return -ENOMEM;
++
++ dev_flags = (unsigned int) device_get_match_data(dev);
++
++ ngpios = REALTEK_GPIO_MAX;
++ device_property_read_u32(dev, "ngpios", &ngpios);
++
++ if (ngpios > REALTEK_GPIO_MAX) {
++ dev_err(&pdev->dev, "invalid ngpios (max. %d)\n",
++ REALTEK_GPIO_MAX);
++ return -EINVAL;
++ }
++
++ ctrl->base = devm_platform_ioremap_resource(pdev, 0);
++ if (IS_ERR(ctrl->base))
++ return PTR_ERR(ctrl->base);
++
++ raw_spin_lock_init(&ctrl->lock);
++
++ err = bgpio_init(&ctrl->gc, dev, 4,
++ ctrl->base + REALTEK_GPIO_REG_DATA, NULL, NULL,
++ ctrl->base + REALTEK_GPIO_REG_DIR, NULL,
++ BGPIOF_BIG_ENDIAN_BYTE_ORDER);
++ if (err) {
++ dev_err(dev, "unable to init generic GPIO");
++ return err;
++ }
++
++ ctrl->gc.ngpio = ngpios;
++ ctrl->gc.owner = THIS_MODULE;
++
++ irq = platform_get_irq_optional(pdev, 0);
++ if (!(dev_flags & GPIO_INTERRUPTS_DISABLED) && irq > 0) {
++ girq = &ctrl->gc.irq;
++ girq->chip = &realtek_gpio_irq_chip;
++ girq->default_type = IRQ_TYPE_NONE;
++ girq->handler = handle_bad_irq;
++ girq->parent_handler = realtek_gpio_irq_handler;
++ girq->num_parents = 1;
++ girq->parents = devm_kcalloc(dev, girq->num_parents,
++ sizeof(*girq->parents), GFP_KERNEL);
++ if (!girq->parents)
++ return -ENOMEM;
++ girq->parents[0] = irq;
++ girq->init_hw = realtek_gpio_irq_init;
++ }
++
++ return devm_gpiochip_add_data(dev, &ctrl->gc, ctrl);
++}
++
++static struct platform_driver realtek_gpio_driver = {
++ .driver = {
++ .name = "realtek-otto-gpio",
++ .of_match_table = realtek_gpio_of_match,
++ },
++ .probe = realtek_gpio_probe,
++};
++module_platform_driver(realtek_gpio_driver);
++
++MODULE_DESCRIPTION("Realtek Otto GPIO support");
++MODULE_AUTHOR("Sander Vanheule <sander@svanheule.net>");
++MODULE_LICENSE("GPL v2");
diff --git a/target/linux/realtek/patches-5.15/003-5.12-spi-realtek-rtl838x-rtl839x-spi-controller.patch b/target/linux/realtek/patches-5.15/003-5.12-spi-realtek-rtl838x-rtl839x-spi-controller.patch
new file mode 100644
index 0000000000..325e904994
--- /dev/null
+++ b/target/linux/realtek/patches-5.15/003-5.12-spi-realtek-rtl838x-rtl839x-spi-controller.patch
@@ -0,0 +1,57 @@
+From 6acbd614c2c8d3b8de5fb7605d6e24b9b3a8a17b Mon Sep 17 00:00:00 2001
+From: Bert Vermeulen <bert@biot.com>
+Date: Wed, 20 Jan 2021 14:59:27 +0100
+Subject: spi: Realtek RTL838x/RTL839x SPI controller
+
+Signed-off-by: Bert Vermeulen <bert@biot.com>
+Link: https://lore.kernel.org/r/20210120135928.246054-2-bert@biot.com
+Signed-off-by: Mark Brown <broonie@kernel.org>
+---
+ .../devicetree/bindings/spi/realtek,rtl-spi.yaml | 41 ++++++++++++++++++++++
+ 1 file changed, 41 insertions(+)
+ create mode 100644 Documentation/devicetree/bindings/spi/realtek,rtl-spi.yaml
+
+--- /dev/null
++++ b/Documentation/devicetree/bindings/spi/realtek,rtl-spi.yaml
+@@ -0,0 +1,41 @@
++# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
++%YAML 1.2
++---
++$id: http://devicetree.org/schemas/spi/realtek,rtl-spi.yaml#
++$schema: http://devicetree.org/meta-schemas/core.yaml#
++
++title: Realtek RTL838x/RTL839x SPI controller
++
++maintainers:
++ - Bert Vermeulen <bert@biot.com>
++ - Birger Koblitz <mail@birger-koblitz.de>
++
++allOf:
++ - $ref: "spi-controller.yaml#"
++
++properties:
++ compatible:
++ oneOf:
++ - const: realtek,rtl8380-spi
++ - const: realtek,rtl8382-spi
++ - const: realtek,rtl8391-spi
++ - const: realtek,rtl8392-spi
++ - const: realtek,rtl8393-spi
++
++ reg:
++ maxItems: 1
++
++required:
++ - compatible
++ - reg
++
++unevaluatedProperties: false
++
++examples:
++ - |
++ spi: spi@1200 {
++ compatible = "realtek,rtl8382-spi";
++ reg = <0x1200 0x100>;
++ #address-cells = <1>;
++ #size-cells = <0>;
++ };
diff --git a/target/linux/realtek/patches-5.15/004-5.12-spi-realtek-rtl-add-support-for-realtek-rtl838x-rtl839x-spi-controllers.patch b/target/linux/realtek/patches-5.15/004-5.12-spi-realtek-rtl-add-support-for-realtek-rtl838x-rtl839x-spi-controllers.patch
new file mode 100644
index 0000000000..090847baad
--- /dev/null
+++ b/target/linux/realtek/patches-5.15/004-5.12-spi-realtek-rtl-add-support-for-realtek-rtl838x-rtl839x-spi-controllers.patch
@@ -0,0 +1,248 @@
+From a8af5cc2ff1e804694629a8ef320935629dd15ba Mon Sep 17 00:00:00 2001
+From: Bert Vermeulen <bert@biot.com>
+Date: Wed, 20 Jan 2021 14:59:28 +0100
+Subject: spi: realtek-rtl: Add support for Realtek RTL838x/RTL839x SPI
+ controllers
+
+This driver likely also supports earlier (RTL8196) and later (RTL93xx)
+SoCs.
+
+The SPI hardware in these SoCs is specifically intended for connecting NOR
+bootflash chips, and only used for that in dozens of examined devices.
+However boiled down to basics, it's really just a half-duplex SPI
+controller.
+
+The hardware appears to have a vestigial second chip-select control, but
+it hasn't been seen in the wild and is thus not supported.
+
+Signed-off-by: Bert Vermeulen <bert@biot.com>
+Link: https://lore.kernel.org/r/20210120135928.246054-3-bert@biot.com
+Signed-off-by: Mark Brown <broonie@kernel.org>
+---
+ drivers/spi/Makefile | 1 +
+ drivers/spi/spi-realtek-rtl.c | 209 ++++++++++++++++++++++++++++++++++++++++++
+ 2 files changed, 210 insertions(+)
+ create mode 100644 drivers/spi/spi-realtek-rtl.c
+
+--- a/drivers/spi/Makefile
++++ b/drivers/spi/Makefile
+@@ -94,6 +94,7 @@ obj-$(CONFIG_SPI_QCOM_QSPI) += spi-qcom
+ obj-$(CONFIG_SPI_QUP) += spi-qup.o
+ obj-$(CONFIG_SPI_ROCKCHIP) += spi-rockchip.o
+ obj-$(CONFIG_SPI_RB4XX) += spi-rb4xx.o
++obj-$(CONFIG_MACH_REALTEK_RTL) += spi-realtek-rtl.o
+ obj-$(CONFIG_SPI_RPCIF) += spi-rpc-if.o
+ obj-$(CONFIG_SPI_RSPI) += spi-rspi.o
+ obj-$(CONFIG_SPI_S3C24XX) += spi-s3c24xx-hw.o
+--- /dev/null
++++ b/drivers/spi/spi-realtek-rtl.c
+@@ -0,0 +1,209 @@
++// SPDX-License-Identifier: GPL-2.0-only
++
++#include <linux/module.h>
++#include <linux/platform_device.h>
++#include <linux/mod_devicetable.h>
++#include <linux/spi/spi.h>
++
++struct rtspi {
++ void __iomem *base;
++};
++
++/* SPI Flash Configuration Register */
++#define RTL_SPI_SFCR 0x00
++#define RTL_SPI_SFCR_RBO BIT(28)
++#define RTL_SPI_SFCR_WBO BIT(27)
++
++/* SPI Flash Control and Status Register */
++#define RTL_SPI_SFCSR 0x08
++#define RTL_SPI_SFCSR_CSB0 BIT(31)
++#define RTL_SPI_SFCSR_CSB1 BIT(30)
++#define RTL_SPI_SFCSR_RDY BIT(27)
++#define RTL_SPI_SFCSR_CS BIT(24)
++#define RTL_SPI_SFCSR_LEN_MASK ~(0x03 << 28)
++#define RTL_SPI_SFCSR_LEN1 (0x00 << 28)
++#define RTL_SPI_SFCSR_LEN4 (0x03 << 28)
++
++/* SPI Flash Data Register */
++#define RTL_SPI_SFDR 0x0c
++
++#define REG(x) (rtspi->base + x)
++
++
++static void rt_set_cs(struct spi_device *spi, bool active)
++{
++ struct rtspi *rtspi = spi_controller_get_devdata(spi->controller);
++ u32 value;
++
++ /* CS0 bit is active low */
++ value = readl(REG(RTL_SPI_SFCSR));
++ if (active)
++ value |= RTL_SPI_SFCSR_CSB0;
++ else
++ value &= ~RTL_SPI_SFCSR_CSB0;
++ writel(value, REG(RTL_SPI_SFCSR));
++}
++
++static void set_size(struct rtspi *rtspi, int size)
++{
++ u32 value;
++
++ value = readl(REG(RTL_SPI_SFCSR));
++ value &= RTL_SPI_SFCSR_LEN_MASK;
++ if (size == 4)
++ value |= RTL_SPI_SFCSR_LEN4;
++ else if (size == 1)
++ value |= RTL_SPI_SFCSR_LEN1;
++ writel(value, REG(RTL_SPI_SFCSR));
++}
++
++static inline void wait_ready(struct rtspi *rtspi)
++{
++ while (!(readl(REG(RTL_SPI_SFCSR)) & RTL_SPI_SFCSR_RDY))
++ cpu_relax();
++}
++static void send4(struct rtspi *rtspi, const u32 *buf)
++{
++ wait_ready(rtspi);
++ set_size(rtspi, 4);
++ writel(*buf, REG(RTL_SPI_SFDR));
++}
++
++static void send1(struct rtspi *rtspi, const u8 *buf)
++{
++ wait_ready(rtspi);
++ set_size(rtspi, 1);
++ writel(buf[0] << 24, REG(RTL_SPI_SFDR));
++}
++
++static void rcv4(struct rtspi *rtspi, u32 *buf)
++{
++ wait_ready(rtspi);
++ set_size(rtspi, 4);
++ *buf = readl(REG(RTL_SPI_SFDR));
++}
++
++static void rcv1(struct rtspi *rtspi, u8 *buf)
++{
++ wait_ready(rtspi);
++ set_size(rtspi, 1);
++ *buf = readl(REG(RTL_SPI_SFDR)) >> 24;
++}
++
++static int transfer_one(struct spi_controller *ctrl, struct spi_device *spi,
++ struct spi_transfer *xfer)
++{
++ struct rtspi *rtspi = spi_controller_get_devdata(ctrl);
++ void *rx_buf;
++ const void *tx_buf;
++ int cnt;
++
++ tx_buf = xfer->tx_buf;
++ rx_buf = xfer->rx_buf;
++ cnt = xfer->len;
++ if (tx_buf) {
++ while (cnt >= 4) {
++ send4(rtspi, tx_buf);
++ tx_buf += 4;
++ cnt -= 4;
++ }
++ while (cnt) {
++ send1(rtspi, tx_buf);
++ tx_buf++;
++ cnt--;
++ }
++ } else if (rx_buf) {
++ while (cnt >= 4) {
++ rcv4(rtspi, rx_buf);
++ rx_buf += 4;
++ cnt -= 4;
++ }
++ while (cnt) {
++ rcv1(rtspi, rx_buf);
++ rx_buf++;
++ cnt--;
++ }
++ }
++
++ spi_finalize_current_transfer(ctrl);
++
++ return 0;
++}
++
++static void init_hw(struct rtspi *rtspi)
++{
++ u32 value;
++
++ /* Turn on big-endian byte ordering */
++ value = readl(REG(RTL_SPI_SFCR));
++ value |= RTL_SPI_SFCR_RBO | RTL_SPI_SFCR_WBO;
++ writel(value, REG(RTL_SPI_SFCR));
++
++ value = readl(REG(RTL_SPI_SFCSR));
++ /* Permanently disable CS1, since it's never used */
++ value |= RTL_SPI_SFCSR_CSB1;
++ /* Select CS0 for use */
++ value &= RTL_SPI_SFCSR_CS;
++ writel(value, REG(RTL_SPI_SFCSR));
++}
++
++static int realtek_rtl_spi_probe(struct platform_device *pdev)
++{
++ struct spi_controller *ctrl;
++ struct rtspi *rtspi;
++ int err;
++
++ ctrl = devm_spi_alloc_master(&pdev->dev, sizeof(*rtspi));
++ if (!ctrl) {
++ dev_err(&pdev->dev, "Error allocating SPI controller\n");
++ return -ENOMEM;
++ }
++ platform_set_drvdata(pdev, ctrl);
++ rtspi = spi_controller_get_devdata(ctrl);
++
++ rtspi->base = devm_platform_get_and_ioremap_resource(pdev, 0, NULL);
++ if (IS_ERR(rtspi->base)) {
++ dev_err(&pdev->dev, "Could not map SPI register address");
++ return -ENOMEM;
++ }
++
++ init_hw(rtspi);
++
++ ctrl->dev.of_node = pdev->dev.of_node;
++ ctrl->flags = SPI_CONTROLLER_HALF_DUPLEX;
++ ctrl->set_cs = rt_set_cs;
++ ctrl->transfer_one = transfer_one;
++
++ err = devm_spi_register_controller(&pdev->dev, ctrl);
++ if (err) {
++ dev_err(&pdev->dev, "Could not register SPI controller\n");
++ return -ENODEV;
++ }
++
++ return 0;
++}
++
++
++static const struct of_device_id realtek_rtl_spi_of_ids[] = {
++ { .compatible = "realtek,rtl8380-spi" },
++ { .compatible = "realtek,rtl8382-spi" },
++ { .compatible = "realtek,rtl8391-spi" },
++ { .compatible = "realtek,rtl8392-spi" },
++ { .compatible = "realtek,rtl8393-spi" },
++ { /* sentinel */ }
++};
++MODULE_DEVICE_TABLE(of, realtek_rtl_spi_of_ids);
++
++static struct platform_driver realtek_rtl_spi_driver = {
++ .probe = realtek_rtl_spi_probe,
++ .driver = {
++ .name = "realtek-rtl-spi",
++ .of_match_table = realtek_rtl_spi_of_ids,
++ },
++};
++
++module_platform_driver(realtek_rtl_spi_driver);
++
++MODULE_LICENSE("GPL v2");
++MODULE_AUTHOR("Bert Vermeulen <bert@biot.com>");
++MODULE_DESCRIPTION("Realtek RTL SPI driver");
diff --git a/target/linux/realtek/patches-5.15/005-5.12-dt-bindings-interrupt-controller-add-realtek-rtl838x-rtl839x-support.patch b/target/linux/realtek/patches-5.15/005-5.12-dt-bindings-interrupt-controller-add-realtek-rtl838x-rtl839x-support.patch
new file mode 100644
index 0000000000..165545a220
--- /dev/null
+++ b/target/linux/realtek/patches-5.15/005-5.12-dt-bindings-interrupt-controller-add-realtek-rtl838x-rtl839x-support.patch
@@ -0,0 +1,78 @@
+From 4a2b92a5d3519fc2c1edda4d4aa0e05bff41e8de Mon Sep 17 00:00:00 2001
+From: Bert Vermeulen <bert@biot.com>
+Date: Fri, 22 Jan 2021 21:42:23 +0100
+Subject: dt-bindings: interrupt-controller: Add Realtek RTL838x/RTL839x
+ support
+
+Document the binding for the Realtek RTL838x/RTL839x interrupt controller.
+
+Reviewed-by: Rob Herring <robh@kernel.org>
+Signed-off-by: Bert Vermeulen <bert@biot.com>
+[maz: Add a commit message, as the author couldn't be bothered...]
+Signed-off-by: Marc Zyngier <maz@kernel.org>
+Link: https://lore.kernel.org/r/20210122204224.509124-2-bert@biot.com
+---
+ .../interrupt-controller/realtek,rtl-intc.yaml | 57 ++++++++++++++++++++++
+ 1 file changed, 57 insertions(+)
+ create mode 100644 Documentation/devicetree/bindings/interrupt-controller/realtek,rtl-intc.yaml
+
+--- /dev/null
++++ b/Documentation/devicetree/bindings/interrupt-controller/realtek,rtl-intc.yaml
+@@ -0,0 +1,57 @@
++# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
++%YAML 1.2
++---
++$id: http://devicetree.org/schemas/interrupt-controller/realtek,rtl-intc.yaml#
++$schema: http://devicetree.org/meta-schemas/core.yaml#
++
++title: Realtek RTL SoC interrupt controller devicetree bindings
++
++maintainers:
++ - Birger Koblitz <mail@birger-koblitz.de>
++ - Bert Vermeulen <bert@biot.com>
++ - John Crispin <john@phrozen.org>
++
++properties:
++ compatible:
++ const: realtek,rtl-intc
++
++ "#interrupt-cells":
++ const: 1
++
++ reg:
++ maxItems: 1
++
++ interrupts:
++ maxItems: 1
++
++ interrupt-controller: true
++
++ "#address-cells":
++ const: 0
++
++ interrupt-map:
++ description: Describes mapping from SoC interrupts to CPU interrupts
++
++required:
++ - compatible
++ - reg
++ - "#interrupt-cells"
++ - interrupt-controller
++ - "#address-cells"
++ - interrupt-map
++
++additionalProperties: false
++
++examples:
++ - |
++ intc: interrupt-controller@3000 {
++ compatible = "realtek,rtl-intc";
++ #interrupt-cells = <1>;
++ interrupt-controller;
++ reg = <0x3000 0x20>;
++ #address-cells = <0>;
++ interrupt-map =
++ <31 &cpuintc 2>,
++ <30 &cpuintc 1>,
++ <29 &cpuintc 5>;
++ };
diff --git a/target/linux/realtek/patches-5.15/006-5.12-irqchip-add-support-for-realtek-rtl838x-rtl839x-interrupt-controller.patch b/target/linux/realtek/patches-5.15/006-5.12-irqchip-add-support-for-realtek-rtl838x-rtl839x-interrupt-controller.patch
new file mode 100644
index 0000000000..753c41eb2f
--- /dev/null
+++ b/target/linux/realtek/patches-5.15/006-5.12-irqchip-add-support-for-realtek-rtl838x-rtl839x-interrupt-controller.patch
@@ -0,0 +1,211 @@
+From 9f3a0f34b84ad1b9a8f2bdae44b66f16685b2143 Mon Sep 17 00:00:00 2001
+From: Bert Vermeulen <bert@biot.com>
+Date: Fri, 22 Jan 2021 21:42:24 +0100
+Subject: irqchip: Add support for Realtek RTL838x/RTL839x interrupt controller
+
+This is a standard IRQ driver with only status and mask registers.
+
+The mapping from SoC interrupts (18-31) to MIPS core interrupts is
+done via an interrupt-map in device tree.
+
+Signed-off-by: Bert Vermeulen <bert@biot.com>
+Signed-off-by: Birger Koblitz <mail@birger-koblitz.de>
+Acked-by: John Crispin <john@phrozen.org>
+Signed-off-by: Marc Zyngier <maz@kernel.org>
+Link: https://lore.kernel.org/r/20210122204224.509124-3-bert@biot.com
+---
+ drivers/irqchip/Makefile | 1 +
+ drivers/irqchip/irq-realtek-rtl.c | 180 ++++++++++++++++++++++++++++++++++++++
+ 2 files changed, 181 insertions(+)
+ create mode 100644 drivers/irqchip/irq-realtek-rtl.c
+
+--- a/drivers/irqchip/Makefile
++++ b/drivers/irqchip/Makefile
+@@ -114,3 +114,4 @@ obj-$(CONFIG_LOONGSON_PCH_PIC) += irq-l
+ obj-$(CONFIG_LOONGSON_PCH_MSI) += irq-loongson-pch-msi.o
+ obj-$(CONFIG_MST_IRQ) += irq-mst-intc.o
+ obj-$(CONFIG_SL28CPLD_INTC) += irq-sl28cpld.o
++obj-$(CONFIG_MACH_REALTEK_RTL) += irq-realtek-rtl.o
+--- /dev/null
++++ b/drivers/irqchip/irq-realtek-rtl.c
+@@ -0,0 +1,180 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/*
++ * Copyright (C) 2020 Birger Koblitz <mail@birger-koblitz.de>
++ * Copyright (C) 2020 Bert Vermeulen <bert@biot.com>
++ * Copyright (C) 2020 John Crispin <john@phrozen.org>
++ */
++
++#include <linux/of_irq.h>
++#include <linux/irqchip.h>
++#include <linux/spinlock.h>
++#include <linux/of_address.h>
++#include <linux/irqchip/chained_irq.h>
++
++/* Global Interrupt Mask Register */
++#define RTL_ICTL_GIMR 0x00
++/* Global Interrupt Status Register */
++#define RTL_ICTL_GISR 0x04
++/* Interrupt Routing Registers */
++#define RTL_ICTL_IRR0 0x08
++#define RTL_ICTL_IRR1 0x0c
++#define RTL_ICTL_IRR2 0x10
++#define RTL_ICTL_IRR3 0x14
++
++#define REG(x) (realtek_ictl_base + x)
++
++static DEFINE_RAW_SPINLOCK(irq_lock);
++static void __iomem *realtek_ictl_base;
++
++static void realtek_ictl_unmask_irq(struct irq_data *i)
++{
++ unsigned long flags;
++ u32 value;
++
++ raw_spin_lock_irqsave(&irq_lock, flags);
++
++ value = readl(REG(RTL_ICTL_GIMR));
++ value |= BIT(i->hwirq);
++ writel(value, REG(RTL_ICTL_GIMR));
++
++ raw_spin_unlock_irqrestore(&irq_lock, flags);
++}
++
++static void realtek_ictl_mask_irq(struct irq_data *i)
++{
++ unsigned long flags;
++ u32 value;
++
++ raw_spin_lock_irqsave(&irq_lock, flags);
++
++ value = readl(REG(RTL_ICTL_GIMR));
++ value &= ~BIT(i->hwirq);
++ writel(value, REG(RTL_ICTL_GIMR));
++
++ raw_spin_unlock_irqrestore(&irq_lock, flags);
++}
++
++static struct irq_chip realtek_ictl_irq = {
++ .name = "realtek-rtl-intc",
++ .irq_mask = realtek_ictl_mask_irq,
++ .irq_unmask = realtek_ictl_unmask_irq,
++};
++
++static int intc_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hw)
++{
++ irq_set_chip_and_handler(hw, &realtek_ictl_irq, handle_level_irq);
++
++ return 0;
++}
++
++static const struct irq_domain_ops irq_domain_ops = {
++ .map = intc_map,
++ .xlate = irq_domain_xlate_onecell,
++};
++
++static void realtek_irq_dispatch(struct irq_desc *desc)
++{
++ struct irq_chip *chip = irq_desc_get_chip(desc);
++ struct irq_domain *domain;
++ unsigned int pending;
++
++ chained_irq_enter(chip, desc);
++ pending = readl(REG(RTL_ICTL_GIMR)) & readl(REG(RTL_ICTL_GISR));
++ if (unlikely(!pending)) {
++ spurious_interrupt();
++ goto out;
++ }
++ domain = irq_desc_get_handler_data(desc);
++ generic_handle_irq(irq_find_mapping(domain, __ffs(pending)));
++
++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.
++ */
++static int __init map_interrupts(struct device_node *node, struct irq_domain *domain)
++{
++ struct device_node *cpu_ictl;
++ const __be32 *imap;
++ u32 imaplen, soc_int, cpu_int, tmp, regs[4];
++ int ret, i, irr_regs[] = {
++ RTL_ICTL_IRR3,
++ RTL_ICTL_IRR2,
++ RTL_ICTL_IRR1,
++ RTL_ICTL_IRR0,
++ };
++ u8 mips_irqs_set;
++
++ ret = of_property_read_u32(node, "#address-cells", &tmp);
++ if (ret || tmp)
++ return -EINVAL;
++
++ imap = of_get_property(node, "interrupt-map", &imaplen);
++ if (!imap || imaplen % 3)
++ return -EINVAL;
++
++ mips_irqs_set = 0;
++ memset(regs, 0, sizeof(regs));
++ for (i = 0; i < imaplen; i += 3 * sizeof(u32)) {
++ soc_int = be32_to_cpup(imap);
++ if (soc_int > 31)
++ return -EINVAL;
++
++ cpu_ictl = of_find_node_by_phandle(be32_to_cpup(imap + 1));
++ if (!cpu_ictl)
++ return -EINVAL;
++ ret = of_property_read_u32(cpu_ictl, "#interrupt-cells", &tmp);
++ if (ret || tmp != 1)
++ return -EINVAL;
++ of_node_put(cpu_ictl);
++
++ cpu_int = be32_to_cpup(imap + 2);
++ if (cpu_int > 7)
++ return -EINVAL;
++
++ if (!(mips_irqs_set & BIT(cpu_int))) {
++ irq_set_chained_handler_and_data(cpu_int, realtek_irq_dispatch,
++ domain);
++ mips_irqs_set |= BIT(cpu_int);
++ }
++
++ regs[(soc_int * 4) / 32] |= cpu_int << (soc_int * 4) % 32;
++ imap += 3;
++ }
++
++ for (i = 0; i < 4; i++)
++ writel(regs[i], REG(irr_regs[i]));
++
++ return 0;
++}
++
++static int __init realtek_rtl_of_init(struct device_node *node, struct device_node *parent)
++{
++ struct irq_domain *domain;
++ int ret;
++
++ realtek_ictl_base = of_iomap(node, 0);
++ if (!realtek_ictl_base)
++ return -ENXIO;
++
++ /* Disable all cascaded interrupts */
++ writel(0, REG(RTL_ICTL_GIMR));
++
++ domain = irq_domain_add_simple(node, 32, 0,
++ &irq_domain_ops, NULL);
++
++ ret = map_interrupts(node, domain);
++ if (ret) {
++ pr_err("invalid interrupt map\n");
++ return ret;
++ }
++
++ return 0;
++}
++
++IRQCHIP_DECLARE(realtek_rtl_intc, "realtek,rtl-intc", realtek_rtl_of_init);
diff --git a/target/linux/realtek/patches-5.15/007-5.16-gpio-realtek-realtek-otto-fix-gpio-line-irq-offset.patch b/target/linux/realtek/patches-5.15/007-5.16-gpio-realtek-realtek-otto-fix-gpio-line-irq-offset.patch
new file mode 100644
index 0000000000..9935c57257
--- /dev/null
+++ b/target/linux/realtek/patches-5.15/007-5.16-gpio-realtek-realtek-otto-fix-gpio-line-irq-offset.patch
@@ -0,0 +1,22 @@
+From: Sander Vanheule <sander@svanheule.net>
+Subject: gpio: realtek-otto: fix GPIO line IRQ offset
+
+The irqchip uses one domain for all GPIO lines, so th line offset should be
+determined w.r.t. the first line of the first port, not the first line of the
+triggered port.
+
+Fixes: 0d82fb1127fb ("gpio: Add Realtek Otto GPIO support")
+Signed-off-by: Sander Vanheule <sander@svanheule.net>
+Link: https://lore.kernel.org/linux-gpio/20211028085243.34360-1-sander@svanheule.net/
+
+--- a/drivers/gpio/gpio-realtek-otto.c
++++ b/drivers/gpio/gpio-realtek-otto.c
+@@ -206,7 +206,7 @@ static void realtek_gpio_irq_handler(str
+ status = realtek_gpio_read_isr(ctrl, lines_done / 8);
+ port_pin_count = min(gc->ngpio - lines_done, 8U);
+ for_each_set_bit(offset, &status, port_pin_count) {
+- irq = irq_find_mapping(gc->irq.domain, offset);
++ irq = irq_find_mapping(gc->irq.domain, offset + lines_done);
+ generic_handle_irq(irq);
+ }
+ }
diff --git a/target/linux/realtek/patches-5.15/008-5.17-watchdog-add-realtek-otto-watchdog-timer.patch b/target/linux/realtek/patches-5.15/008-5.17-watchdog-add-realtek-otto-watchdog-timer.patch
new file mode 100644
index 0000000000..845441b425
--- /dev/null
+++ b/target/linux/realtek/patches-5.15/008-5.17-watchdog-add-realtek-otto-watchdog-timer.patch
@@ -0,0 +1,467 @@
+From 293903b9dfe43520f01374dc1661be11d6838c49 Mon Sep 17 00:00:00 2001
+From: Sander Vanheule <sander@svanheule.net>
+Date: Thu, 18 Nov 2021 17:29:52 +0100
+Subject: watchdog: Add Realtek Otto watchdog timer
+
+Realtek MIPS SoCs (platform name Otto) have a watchdog timer with
+pretimeout notifitication support. The WDT can (partially) hard reset,
+or soft reset the SoC.
+
+This driver implements all features as described in the devicetree
+binding, except the phase2 interrupt, and also functions as a restart
+handler. The cpu reset mode is considered to be a "warm" restart, since
+this mode does not reset all peripherals. Being an embedded system
+though, the "cpu" and "software" modes will still cause the bootloader
+to run on restart.
+
+It is not known how a forced system reset can be disabled on the
+supported platforms. This means that the phase2 interrupt will only fire
+at the same time as reset, so implementing phase2 is of little use.
+
+Signed-off-by: Sander Vanheule <sander@svanheule.net>
+Reviewed-by: Guenter Roeck <linux@roeck-us.net>
+Link: https://lore.kernel.org/r/6d060bccbdcc709cfa79203485db85aad3c3beb5.1637252610.git.sander@svanheule.net
+Signed-off-by: Guenter Roeck <linux@roeck-us.net>
+---
+ MAINTAINERS | 7 +
+ drivers/watchdog/Kconfig | 13 ++
+ drivers/watchdog/Makefile | 1 +
+ drivers/watchdog/realtek_otto_wdt.c | 384 ++++++++++++++++++++++++++++++++++++
+ 4 files changed, 405 insertions(+)
+ create mode 100644 drivers/watchdog/realtek_otto_wdt.c
+
+--- a/MAINTAINERS
++++ b/MAINTAINERS
+@@ -14816,6 +14816,13 @@ S: Maintained
+ F: include/sound/rt*.h
+ F: sound/soc/codecs/rt*
+
++REALTEK OTTO WATCHDOG
++M: Sander Vanheule <sander@svanheule.net>
++L: linux-watchdog@vger.kernel.org
++S: Maintained
++F: Documentation/devicetree/bindings/watchdog/realtek,otto-wdt.yaml
++F: driver/watchdog/realtek_otto_wdt.c
++
+ REALTEK RTL83xx SMI DSA ROUTER CHIPS
+ M: Linus Walleij <linus.walleij@linaro.org>
+ S: Maintained
+--- a/drivers/watchdog/Kconfig
++++ b/drivers/watchdog/Kconfig
+@@ -995,6 +995,19 @@ config RTD119X_WATCHDOG
+ Say Y here to include support for the watchdog timer in
+ Realtek RTD1295 SoCs.
+
++config REALTEK_OTTO_WDT
++ tristate "Realtek Otto MIPS watchdog support"
++ depends on MACH_REALTEK_RTL || COMPILE_TEST
++ depends on COMMON_CLK
++ select WATCHDOG_CORE
++ default MACH_REALTEK_RTL
++ help
++ Say Y here to include support for the watchdog timer on Realtek
++ RTL838x, RTL839x, RTL930x SoCs. This watchdog has pretimeout
++ notifications and system reset on timeout.
++
++ When built as a module this will be called realtek_otto_wdt.
++
+ config SPRD_WATCHDOG
+ tristate "Spreadtrum watchdog support"
+ depends on ARCH_SPRD || COMPILE_TEST
+--- a/drivers/watchdog/Makefile
++++ b/drivers/watchdog/Makefile
+@@ -174,6 +174,7 @@ obj-$(CONFIG_IMGPDC_WDT) += imgpdc_wdt.o
+ obj-$(CONFIG_MT7621_WDT) += mt7621_wdt.o
+ obj-$(CONFIG_PIC32_WDT) += pic32-wdt.o
+ obj-$(CONFIG_PIC32_DMT) += pic32-dmt.o
++obj-$(CONFIG_REALTEK_OTTO_WDT) += realtek_otto_wdt.o
+
+ # PARISC Architecture
+
+--- /dev/null
++++ b/drivers/watchdog/realtek_otto_wdt.c
+@@ -0,0 +1,384 @@
++// SPDX-License-Identifier: GPL-2.0-only
++
++/*
++ * Realtek Otto MIPS platform watchdog
++ *
++ * Watchdog timer that will reset the system after timeout, using the selected
++ * reset mode.
++ *
++ * Counter scaling and timeouts:
++ * - Base prescale of (2 << 25), providing tick duration T_0: 168ms @ 200MHz
++ * - PRESCALE: logarithmic prescaler adding a factor of {1, 2, 4, 8}
++ * - Phase 1: Times out after (PHASE1 + 1) × PRESCALE × T_0
++ * Generates an interrupt, WDT cannot be stopped after phase 1
++ * - Phase 2: starts after phase 1, times out after (PHASE2 + 1) × PRESCALE × T_0
++ * Resets the system according to RST_MODE
++ */
++
++#include <linux/bits.h>
++#include <linux/bitfield.h>
++#include <linux/clk.h>
++#include <linux/delay.h>
++#include <linux/interrupt.h>
++#include <linux/io.h>
++#include <linux/math.h>
++#include <linux/minmax.h>
++#include <linux/module.h>
++#include <linux/mod_devicetable.h>
++#include <linux/platform_device.h>
++#include <linux/property.h>
++#include <linux/reboot.h>
++#include <linux/watchdog.h>
++
++#define OTTO_WDT_REG_CNTR 0x0
++#define OTTO_WDT_CNTR_PING BIT(31)
++
++#define OTTO_WDT_REG_INTR 0x4
++#define OTTO_WDT_INTR_PHASE_1 BIT(31)
++#define OTTO_WDT_INTR_PHASE_2 BIT(30)
++
++#define OTTO_WDT_REG_CTRL 0x8
++#define OTTO_WDT_CTRL_ENABLE BIT(31)
++#define OTTO_WDT_CTRL_PRESCALE GENMASK(30, 29)
++#define OTTO_WDT_CTRL_PHASE1 GENMASK(26, 22)
++#define OTTO_WDT_CTRL_PHASE2 GENMASK(19, 15)
++#define OTTO_WDT_CTRL_RST_MODE GENMASK(1, 0)
++#define OTTO_WDT_MODE_SOC 0
++#define OTTO_WDT_MODE_CPU 1
++#define OTTO_WDT_MODE_SOFTWARE 2
++#define OTTO_WDT_CTRL_DEFAULT OTTO_WDT_MODE_CPU
++
++#define OTTO_WDT_PRESCALE_MAX 3
++
++/*
++ * One higher than the max values contained in PHASE{1,2}, since a value of 0
++ * corresponds to one tick.
++ */
++#define OTTO_WDT_PHASE_TICKS_MAX 32
++
++/*
++ * The maximum reset delay is actually 2×32 ticks, but that would require large
++ * pretimeout values for timeouts longer than 32 ticks. Limit the maximum timeout
++ * to 32 + 1 to ensure small pretimeout values can be configured as expected.
++ */
++#define OTTO_WDT_TIMEOUT_TICKS_MAX (OTTO_WDT_PHASE_TICKS_MAX + 1)
++
++struct otto_wdt_ctrl {
++ struct watchdog_device wdev;
++ struct device *dev;
++ void __iomem *base;
++ unsigned int clk_rate_khz;
++ int irq_phase1;
++};
++
++static int otto_wdt_start(struct watchdog_device *wdev)
++{
++ struct otto_wdt_ctrl *ctrl = watchdog_get_drvdata(wdev);
++ u32 v;
++
++ v = ioread32(ctrl->base + OTTO_WDT_REG_CTRL);
++ v |= OTTO_WDT_CTRL_ENABLE;
++ iowrite32(v, ctrl->base + OTTO_WDT_REG_CTRL);
++
++ return 0;
++}
++
++static int otto_wdt_stop(struct watchdog_device *wdev)
++{
++ struct otto_wdt_ctrl *ctrl = watchdog_get_drvdata(wdev);
++ u32 v;
++
++ v = ioread32(ctrl->base + OTTO_WDT_REG_CTRL);
++ v &= ~OTTO_WDT_CTRL_ENABLE;
++ iowrite32(v, ctrl->base + OTTO_WDT_REG_CTRL);
++
++ return 0;
++}
++
++static int otto_wdt_ping(struct watchdog_device *wdev)
++{
++ struct otto_wdt_ctrl *ctrl = watchdog_get_drvdata(wdev);
++
++ iowrite32(OTTO_WDT_CNTR_PING, ctrl->base + OTTO_WDT_REG_CNTR);
++
++ return 0;
++}
++
++static int otto_wdt_tick_ms(struct otto_wdt_ctrl *ctrl, int prescale)
++{
++ return DIV_ROUND_CLOSEST(1 << (25 + prescale), ctrl->clk_rate_khz);
++}
++
++/*
++ * The timer asserts the PHASE1/PHASE2 IRQs when the number of ticks exceeds
++ * the value stored in those fields. This means each phase will run for at least
++ * one tick, so small values need to be clamped to correctly reflect the timeout.
++ */
++static inline unsigned int div_round_ticks(unsigned int val, unsigned int tick_duration,
++ unsigned int min_ticks)
++{
++ return max(min_ticks, DIV_ROUND_UP(val, tick_duration));
++}
++
++static int otto_wdt_determine_timeouts(struct watchdog_device *wdev, unsigned int timeout,
++ unsigned int pretimeout)
++{
++ struct otto_wdt_ctrl *ctrl = watchdog_get_drvdata(wdev);
++ unsigned int pretimeout_ms = pretimeout * 1000;
++ unsigned int timeout_ms = timeout * 1000;
++ unsigned int prescale_next = 0;
++ unsigned int phase1_ticks;
++ unsigned int phase2_ticks;
++ unsigned int total_ticks;
++ unsigned int prescale;
++ unsigned int tick_ms;
++ u32 v;
++
++ do {
++ prescale = prescale_next;
++ if (prescale > OTTO_WDT_PRESCALE_MAX)
++ return -EINVAL;
++
++ tick_ms = otto_wdt_tick_ms(ctrl, prescale);
++ total_ticks = div_round_ticks(timeout_ms, tick_ms, 2);
++ phase1_ticks = div_round_ticks(timeout_ms - pretimeout_ms, tick_ms, 1);
++ phase2_ticks = total_ticks - phase1_ticks;
++
++ prescale_next++;
++ } while (phase1_ticks > OTTO_WDT_PHASE_TICKS_MAX
++ || phase2_ticks > OTTO_WDT_PHASE_TICKS_MAX);
++
++ v = ioread32(ctrl->base + OTTO_WDT_REG_CTRL);
++
++ v &= ~(OTTO_WDT_CTRL_PRESCALE | OTTO_WDT_CTRL_PHASE1 | OTTO_WDT_CTRL_PHASE2);
++ v |= FIELD_PREP(OTTO_WDT_CTRL_PHASE1, phase1_ticks - 1);
++ v |= FIELD_PREP(OTTO_WDT_CTRL_PHASE2, phase2_ticks - 1);
++ v |= FIELD_PREP(OTTO_WDT_CTRL_PRESCALE, prescale);
++
++ iowrite32(v, ctrl->base + OTTO_WDT_REG_CTRL);
++
++ timeout_ms = total_ticks * tick_ms;
++ ctrl->wdev.timeout = timeout_ms / 1000;
++
++ pretimeout_ms = phase2_ticks * tick_ms;
++ ctrl->wdev.pretimeout = pretimeout_ms / 1000;
++
++ return 0;
++}
++
++static int otto_wdt_set_timeout(struct watchdog_device *wdev, unsigned int val)
++{
++ return otto_wdt_determine_timeouts(wdev, val, min(wdev->pretimeout, val - 1));
++}
++
++static int otto_wdt_set_pretimeout(struct watchdog_device *wdev, unsigned int val)
++{
++ return otto_wdt_determine_timeouts(wdev, wdev->timeout, val);
++}
++
++static int otto_wdt_restart(struct watchdog_device *wdev, unsigned long reboot_mode,
++ void *data)
++{
++ struct otto_wdt_ctrl *ctrl = watchdog_get_drvdata(wdev);
++ u32 reset_mode;
++ u32 v;
++
++ disable_irq(ctrl->irq_phase1);
++
++ switch (reboot_mode) {
++ case REBOOT_SOFT:
++ reset_mode = OTTO_WDT_MODE_SOFTWARE;
++ break;
++ case REBOOT_WARM:
++ reset_mode = OTTO_WDT_MODE_CPU;
++ break;
++ default:
++ reset_mode = OTTO_WDT_MODE_SOC;
++ break;
++ }
++
++ /* Configure for shortest timeout and wait for reset to occur */
++ v = FIELD_PREP(OTTO_WDT_CTRL_RST_MODE, reset_mode) | OTTO_WDT_CTRL_ENABLE;
++ iowrite32(v, ctrl->base + OTTO_WDT_REG_CTRL);
++
++ mdelay(3 * otto_wdt_tick_ms(ctrl, 0));
++
++ return 0;
++}
++
++static irqreturn_t otto_wdt_phase1_isr(int irq, void *dev_id)
++{
++ struct otto_wdt_ctrl *ctrl = dev_id;
++
++ iowrite32(OTTO_WDT_INTR_PHASE_1, ctrl->base + OTTO_WDT_REG_INTR);
++ dev_crit(ctrl->dev, "phase 1 timeout\n");
++ watchdog_notify_pretimeout(&ctrl->wdev);
++
++ return IRQ_HANDLED;
++}
++
++static const struct watchdog_ops otto_wdt_ops = {
++ .owner = THIS_MODULE,
++ .start = otto_wdt_start,
++ .stop = otto_wdt_stop,
++ .ping = otto_wdt_ping,
++ .set_timeout = otto_wdt_set_timeout,
++ .set_pretimeout = otto_wdt_set_pretimeout,
++ .restart = otto_wdt_restart,
++};
++
++static const struct watchdog_info otto_wdt_info = {
++ .identity = "Realtek Otto watchdog timer",
++ .options = WDIOF_KEEPALIVEPING |
++ WDIOF_MAGICCLOSE |
++ WDIOF_SETTIMEOUT |
++ WDIOF_PRETIMEOUT,
++};
++
++static void otto_wdt_clock_action(void *data)
++{
++ clk_disable_unprepare(data);
++}
++
++static int otto_wdt_probe_clk(struct otto_wdt_ctrl *ctrl)
++{
++ struct clk *clk = devm_clk_get(ctrl->dev, NULL);
++ int ret;
++
++ if (IS_ERR(clk))
++ return dev_err_probe(ctrl->dev, PTR_ERR(clk), "Failed to get clock\n");
++
++ ret = clk_prepare_enable(clk);
++ if (ret)
++ return dev_err_probe(ctrl->dev, ret, "Failed to enable clock\n");
++
++ ret = devm_add_action_or_reset(ctrl->dev, otto_wdt_clock_action, clk);
++ if (ret)
++ return ret;
++
++ ctrl->clk_rate_khz = clk_get_rate(clk) / 1000;
++ if (ctrl->clk_rate_khz == 0)
++ return dev_err_probe(ctrl->dev, -ENXIO, "Failed to get clock rate\n");
++
++ return 0;
++}
++
++static int otto_wdt_probe_reset_mode(struct otto_wdt_ctrl *ctrl)
++{
++ static const char *mode_property = "realtek,reset-mode";
++ const struct fwnode_handle *node = ctrl->dev->fwnode;
++ int mode_count;
++ u32 mode;
++ u32 v;
++
++ if (!node)
++ return -ENXIO;
++
++ mode_count = fwnode_property_string_array_count(node, mode_property);
++ if (mode_count < 0)
++ return mode_count;
++ else if (mode_count == 0)
++ return 0;
++ else if (mode_count != 1)
++ return -EINVAL;
++
++ if (fwnode_property_match_string(node, mode_property, "soc") == 0)
++ mode = OTTO_WDT_MODE_SOC;
++ else if (fwnode_property_match_string(node, mode_property, "cpu") == 0)
++ mode = OTTO_WDT_MODE_CPU;
++ else if (fwnode_property_match_string(node, mode_property, "software") == 0)
++ mode = OTTO_WDT_MODE_SOFTWARE;
++ else
++ return -EINVAL;
++
++ v = ioread32(ctrl->base + OTTO_WDT_REG_CTRL);
++ v &= ~OTTO_WDT_CTRL_RST_MODE;
++ v |= FIELD_PREP(OTTO_WDT_CTRL_RST_MODE, mode);
++ iowrite32(v, ctrl->base + OTTO_WDT_REG_CTRL);
++
++ return 0;
++}
++
++static int otto_wdt_probe(struct platform_device *pdev)
++{
++ struct device *dev = &pdev->dev;
++ struct otto_wdt_ctrl *ctrl;
++ unsigned int max_tick_ms;
++ int ret;
++
++ ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL);
++ if (!ctrl)
++ return -ENOMEM;
++
++ ctrl->dev = dev;
++ ctrl->base = devm_platform_ioremap_resource(pdev, 0);
++ if (IS_ERR(ctrl->base))
++ return PTR_ERR(ctrl->base);
++
++ /* Clear any old interrupts and reset initial state */
++ iowrite32(OTTO_WDT_INTR_PHASE_1 | OTTO_WDT_INTR_PHASE_2,
++ ctrl->base + OTTO_WDT_REG_INTR);
++ iowrite32(OTTO_WDT_CTRL_DEFAULT, ctrl->base + OTTO_WDT_REG_CTRL);
++
++ ret = otto_wdt_probe_clk(ctrl);
++ if (ret)
++ return ret;
++
++ ctrl->irq_phase1 = platform_get_irq_byname(pdev, "phase1");
++ if (ctrl->irq_phase1 < 0)
++ return ctrl->irq_phase1;
++
++ ret = devm_request_irq(dev, ctrl->irq_phase1, otto_wdt_phase1_isr, 0,
++ "realtek-otto-wdt", ctrl);
++ if (ret)
++ return dev_err_probe(dev, ret, "Failed to get IRQ for phase1\n");
++
++ ret = otto_wdt_probe_reset_mode(ctrl);
++ if (ret)
++ return dev_err_probe(dev, ret, "Invalid reset mode specified\n");
++
++ ctrl->wdev.parent = dev;
++ ctrl->wdev.info = &otto_wdt_info;
++ ctrl->wdev.ops = &otto_wdt_ops;
++
++ /*
++ * Since pretimeout cannot be disabled, min. timeout is twice the
++ * subsystem resolution. Max. timeout is ca. 43s at a bus clock of 200MHz.
++ */
++ ctrl->wdev.min_timeout = 2;
++ max_tick_ms = otto_wdt_tick_ms(ctrl, OTTO_WDT_PRESCALE_MAX);
++ ctrl->wdev.max_hw_heartbeat_ms = max_tick_ms * OTTO_WDT_TIMEOUT_TICKS_MAX;
++ ctrl->wdev.timeout = min(30U, ctrl->wdev.max_hw_heartbeat_ms / 1000);
++
++ watchdog_set_drvdata(&ctrl->wdev, ctrl);
++ watchdog_init_timeout(&ctrl->wdev, 0, dev);
++ watchdog_stop_on_reboot(&ctrl->wdev);
++ watchdog_set_restart_priority(&ctrl->wdev, 128);
++
++ ret = otto_wdt_determine_timeouts(&ctrl->wdev, ctrl->wdev.timeout, 1);
++ if (ret)
++ return dev_err_probe(dev, ret, "Failed to set timeout\n");
++
++ return devm_watchdog_register_device(dev, &ctrl->wdev);
++}
++
++static const struct of_device_id otto_wdt_ids[] = {
++ { .compatible = "realtek,rtl8380-wdt" },
++ { .compatible = "realtek,rtl8390-wdt" },
++ { .compatible = "realtek,rtl9300-wdt" },
++ { }
++};
++MODULE_DEVICE_TABLE(of, otto_wdt_ids);
++
++static struct platform_driver otto_wdt_driver = {
++ .probe = otto_wdt_probe,
++ .driver = {
++ .name = "realtek-otto-watchdog",
++ .of_match_table = otto_wdt_ids,
++ },
++};
++module_platform_driver(otto_wdt_driver);
++
++MODULE_LICENSE("GPL v2");
++MODULE_AUTHOR("Sander Vanheule <sander@svanheule.net>");
++MODULE_DESCRIPTION("Realtek Otto watchdog timer driver");
diff --git a/target/linux/realtek/patches-5.15/020-v5.17-net-mdio-add-helpers-to-extract-clause-45-regad-and-.patch b/target/linux/realtek/patches-5.15/020-v5.17-net-mdio-add-helpers-to-extract-clause-45-regad-and-.patch
new file mode 100644
index 0000000000..ef2e225717
--- /dev/null
+++ b/target/linux/realtek/patches-5.15/020-v5.17-net-mdio-add-helpers-to-extract-clause-45-regad-and-.patch
@@ -0,0 +1,53 @@
+From c6af53f038aa32cec12e8a305ba07c7ef168f1b0 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Tue, 4 Jan 2022 12:07:00 +0000
+Subject: [PATCH 2/3] net: mdio: add helpers to extract clause 45 regad and
+ devad fields
+
+Add a couple of helpers and definitions to extract the clause 45 regad
+and devad fields from the regnum passed into MDIO drivers.
+
+Tested-by: Daniel Golle <daniel@makrotopia.org>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ include/linux/mdio.h | 12 ++++++++++++
+ 1 file changed, 12 insertions(+)
+
+--- a/include/linux/mdio.h
++++ b/include/linux/mdio.h
+@@ -7,6 +7,7 @@
+ #define __LINUX_MDIO_H__
+
+ #include <uapi/linux/mdio.h>
++#include <linux/bitfield.h>
+ #include <linux/mod_devicetable.h>
+
+ /* Or MII_ADDR_C45 into regnum for read/write on mii_bus to enable the 21 bit
+@@ -14,6 +15,7 @@
+ */
+ #define MII_ADDR_C45 (1<<30)
+ #define MII_DEVADDR_C45_SHIFT 16
++#define MII_DEVADDR_C45_MASK GENMASK(20, 16)
+ #define MII_REGADDR_C45_MASK GENMASK(15, 0)
+
+ struct gpio_desc;
+@@ -342,6 +344,16 @@ static inline u32 mdiobus_c45_addr(int d
+ return MII_ADDR_C45 | devad << MII_DEVADDR_C45_SHIFT | regnum;
+ }
+
++static inline u16 mdiobus_c45_regad(u32 regnum)
++{
++ return FIELD_GET(MII_REGADDR_C45_MASK, regnum);
++}
++
++static inline u16 mdiobus_c45_devad(u32 regnum)
++{
++ return FIELD_GET(MII_DEVADDR_C45_MASK, regnum);
++}
++
+ static inline int __mdiobus_c45_read(struct mii_bus *bus, int prtad, int devad,
+ u16 regnum)
+ {
diff --git a/target/linux/realtek/patches-5.15/021-v5.19-02-gpio-realtek-otto-Support-reversed-port-layouts.patch b/target/linux/realtek/patches-5.15/021-v5.19-02-gpio-realtek-otto-Support-reversed-port-layouts.patch
new file mode 100644
index 0000000000..8f35351438
--- /dev/null
+++ b/target/linux/realtek/patches-5.15/021-v5.19-02-gpio-realtek-otto-Support-reversed-port-layouts.patch
@@ -0,0 +1,123 @@
+From 512c5be35223d9baa2629efa1084cf5210eaee80 Mon Sep 17 00:00:00 2001
+From: Sander Vanheule <sander@svanheule.net>
+Date: Sat, 9 Apr 2022 21:55:47 +0200
+Subject: [PATCH 2/6] gpio: realtek-otto: Support reversed port layouts
+
+The GPIO port layout on the RTL930x SoC series is reversed compared to
+the RTL838x and RTL839x SoC series. Add new port offset calculator
+functions to ensure the correct order is used when reading port IRQ
+data, and ensure bgpio uses the right byte ordering.
+
+Signed-off-by: Sander Vanheule <sander@svanheule.net>
+Signed-off-by: Bartosz Golaszewski <brgl@bgdev.pl>
+---
+ drivers/gpio/gpio-realtek-otto.c | 55 +++++++++++++++++++++++++++++---
+ 1 file changed, 51 insertions(+), 4 deletions(-)
+
+--- a/drivers/gpio/gpio-realtek-otto.c
++++ b/drivers/gpio/gpio-realtek-otto.c
+@@ -58,6 +58,8 @@ struct realtek_gpio_ctrl {
+ raw_spinlock_t lock;
+ u16 intr_mask[REALTEK_GPIO_PORTS_PER_BANK];
+ u16 intr_type[REALTEK_GPIO_PORTS_PER_BANK];
++ unsigned int (*port_offset_u8)(unsigned int port);
++ unsigned int (*port_offset_u16)(unsigned int port);
+ };
+
+ /* Expand with more flags as devices with other quirks are added */
+@@ -69,6 +71,11 @@ enum realtek_gpio_flags {
+ * line the IRQ handler was assigned to, causing uncaught interrupts.
+ */
+ GPIO_INTERRUPTS_DISABLED = BIT(0),
++ /*
++ * Port order is reversed, meaning DCBA register layout for 1-bit
++ * fields, and [BA, DC] for 2-bit fields.
++ */
++ GPIO_PORTS_REVERSED = BIT(1),
+ };
+
+ static struct realtek_gpio_ctrl *irq_data_to_ctrl(struct irq_data *data)
+@@ -86,21 +93,50 @@ static struct realtek_gpio_ctrl *irq_dat
+ * port. The two interrupt mask registers store two bits per GPIO, so use u16
+ * values.
+ */
++static unsigned int realtek_gpio_port_offset_u8(unsigned int port)
++{
++ return port;
++}
++
++static unsigned int realtek_gpio_port_offset_u16(unsigned int port)
++{
++ return 2 * port;
++}
++
++/*
++ * Reversed port order register access
++ *
++ * For registers with one bit per GPIO, all ports are stored as u8-s in one
++ * register in reversed order. The two interrupt mask registers store two bits
++ * per GPIO, so use u16 values. The first register contains ports 1 and 0, the
++ * second ports 3 and 2.
++ */
++static unsigned int realtek_gpio_port_offset_u8_rev(unsigned int port)
++{
++ return 3 - port;
++}
++
++static unsigned int realtek_gpio_port_offset_u16_rev(unsigned int port)
++{
++ return 2 * (port ^ 1);
++}
++
+ static void realtek_gpio_write_imr(struct realtek_gpio_ctrl *ctrl,
+ unsigned int port, u16 irq_type, u16 irq_mask)
+ {
+- iowrite16(irq_type & irq_mask, ctrl->base + REALTEK_GPIO_REG_IMR + 2 * port);
++ iowrite16(irq_type & irq_mask,
++ ctrl->base + REALTEK_GPIO_REG_IMR + ctrl->port_offset_u16(port));
+ }
+
+ static void realtek_gpio_clear_isr(struct realtek_gpio_ctrl *ctrl,
+ unsigned int port, u8 mask)
+ {
+- iowrite8(mask, ctrl->base + REALTEK_GPIO_REG_ISR + port);
++ iowrite8(mask, ctrl->base + REALTEK_GPIO_REG_ISR + ctrl->port_offset_u8(port));
+ }
+
+ static u8 realtek_gpio_read_isr(struct realtek_gpio_ctrl *ctrl, unsigned int port)
+ {
+- return ioread8(ctrl->base + REALTEK_GPIO_REG_ISR + port);
++ return ioread8(ctrl->base + REALTEK_GPIO_REG_ISR + ctrl->port_offset_u8(port));
+ }
+
+ /* Set the rising and falling edge mask bits for a GPIO port pin */
+@@ -253,6 +289,7 @@ MODULE_DEVICE_TABLE(of, realtek_gpio_of_
+ static int realtek_gpio_probe(struct platform_device *pdev)
+ {
+ struct device *dev = &pdev->dev;
++ unsigned long bgpio_flags;
+ unsigned int dev_flags;
+ struct gpio_irq_chip *girq;
+ struct realtek_gpio_ctrl *ctrl;
+@@ -280,10 +317,20 @@ static int realtek_gpio_probe(struct pla
+
+ raw_spin_lock_init(&ctrl->lock);
+
++ if (dev_flags & GPIO_PORTS_REVERSED) {
++ bgpio_flags = 0;
++ ctrl->port_offset_u8 = realtek_gpio_port_offset_u8_rev;
++ ctrl->port_offset_u16 = realtek_gpio_port_offset_u16_rev;
++ } else {
++ bgpio_flags = BGPIOF_BIG_ENDIAN_BYTE_ORDER;
++ ctrl->port_offset_u8 = realtek_gpio_port_offset_u8;
++ ctrl->port_offset_u16 = realtek_gpio_port_offset_u16;
++ }
++
+ err = bgpio_init(&ctrl->gc, dev, 4,
+ ctrl->base + REALTEK_GPIO_REG_DATA, NULL, NULL,
+ ctrl->base + REALTEK_GPIO_REG_DIR, NULL,
+- BGPIOF_BIG_ENDIAN_BYTE_ORDER);
++ bgpio_flags);
+ if (err) {
+ dev_err(dev, "unable to init generic GPIO");
+ return err;
diff --git a/target/linux/realtek/patches-5.15/021-v5.19-03-gpio-realtek-otto-Support-per-cpu-interrupts.patch b/target/linux/realtek/patches-5.15/021-v5.19-03-gpio-realtek-otto-Support-per-cpu-interrupts.patch
new file mode 100644
index 0000000000..8f5d571ea2
--- /dev/null
+++ b/target/linux/realtek/patches-5.15/021-v5.19-03-gpio-realtek-otto-Support-per-cpu-interrupts.patch
@@ -0,0 +1,153 @@
+From 95fa6dbe58f286a8f87cb37b7516232eb678de2d Mon Sep 17 00:00:00 2001
+From: Sander Vanheule <sander@svanheule.net>
+Date: Sat, 9 Apr 2022 21:55:48 +0200
+Subject: [PATCH 3/6] gpio: realtek-otto: Support per-cpu interrupts
+
+On SoCs with multiple cores, it is possible that the GPIO interrupt
+controller supports assigning specific pins to one or more cores.
+
+IRQ balancing can be performed on a line-by-line basis if the parent
+interrupt is routed to all available cores, which is the default upon
+initialisation.
+
+Signed-off-by: Sander Vanheule <sander@svanheule.net>
+Signed-off-by: Bartosz Golaszewski <brgl@bgdev.pl>
+---
+ drivers/gpio/gpio-realtek-otto.c | 75 +++++++++++++++++++++++++++++++-
+ 1 file changed, 74 insertions(+), 1 deletion(-)
+
+--- a/drivers/gpio/gpio-realtek-otto.c
++++ b/drivers/gpio/gpio-realtek-otto.c
+@@ -1,6 +1,7 @@
+ // SPDX-License-Identifier: GPL-2.0-only
+
+ #include <linux/gpio/driver.h>
++#include <linux/cpumask.h>
+ #include <linux/irq.h>
+ #include <linux/minmax.h>
+ #include <linux/mod_devicetable.h>
+@@ -55,6 +56,8 @@
+ struct realtek_gpio_ctrl {
+ struct gpio_chip gc;
+ void __iomem *base;
++ void __iomem *cpumask_base;
++ struct cpumask cpu_irq_maskable;
+ raw_spinlock_t lock;
+ u16 intr_mask[REALTEK_GPIO_PORTS_PER_BANK];
+ u16 intr_type[REALTEK_GPIO_PORTS_PER_BANK];
+@@ -76,6 +79,11 @@ enum realtek_gpio_flags {
+ * fields, and [BA, DC] for 2-bit fields.
+ */
+ GPIO_PORTS_REVERSED = BIT(1),
++ /*
++ * Interrupts can be enabled per cpu. This requires a secondary IO
++ * range, where the per-cpu enable masks are located.
++ */
++ GPIO_INTERRUPTS_PER_CPU = BIT(2),
+ };
+
+ static struct realtek_gpio_ctrl *irq_data_to_ctrl(struct irq_data *data)
+@@ -250,14 +258,61 @@ static void realtek_gpio_irq_handler(str
+ chained_irq_exit(irq_chip, desc);
+ }
+
++static inline void __iomem *realtek_gpio_irq_cpu_mask(struct realtek_gpio_ctrl *ctrl,
++ unsigned int port, int cpu)
++{
++ return ctrl->cpumask_base + ctrl->port_offset_u8(port) +
++ REALTEK_GPIO_PORTS_PER_BANK * cpu;
++}
++
++static int realtek_gpio_irq_set_affinity(struct irq_data *data,
++ const struct cpumask *dest, bool force)
++{
++ struct realtek_gpio_ctrl *ctrl = irq_data_to_ctrl(data);
++ unsigned int line = irqd_to_hwirq(data);
++ unsigned int port = line / 8;
++ unsigned int port_pin = line % 8;
++ void __iomem *irq_cpu_mask;
++ unsigned long flags;
++ int cpu;
++ u8 v;
++
++ if (!ctrl->cpumask_base)
++ return -ENXIO;
++
++ raw_spin_lock_irqsave(&ctrl->lock, flags);
++
++ for_each_cpu(cpu, &ctrl->cpu_irq_maskable) {
++ irq_cpu_mask = realtek_gpio_irq_cpu_mask(ctrl, port, cpu);
++ v = ioread8(irq_cpu_mask);
++
++ if (cpumask_test_cpu(cpu, dest))
++ v |= BIT(port_pin);
++ else
++ v &= ~BIT(port_pin);
++
++ iowrite8(v, irq_cpu_mask);
++ }
++
++ raw_spin_unlock_irqrestore(&ctrl->lock, flags);
++
++ irq_data_update_effective_affinity(data, dest);
++
++ return 0;
++}
++
+ static int realtek_gpio_irq_init(struct gpio_chip *gc)
+ {
+ struct realtek_gpio_ctrl *ctrl = gpiochip_get_data(gc);
+ unsigned int port;
++ int cpu;
+
+ for (port = 0; (port * 8) < gc->ngpio; port++) {
+ realtek_gpio_write_imr(ctrl, port, 0, 0);
+ realtek_gpio_clear_isr(ctrl, port, GENMASK(7, 0));
++
++ for_each_cpu(cpu, &ctrl->cpu_irq_maskable)
++ iowrite8(GENMASK(7, 0), realtek_gpio_irq_cpu_mask(ctrl, port, cpu));
+ }
+
+ return 0;
+@@ -269,6 +324,7 @@ static struct irq_chip realtek_gpio_irq_
+ .irq_mask = realtek_gpio_irq_mask,
+ .irq_unmask = realtek_gpio_irq_unmask,
+ .irq_set_type = realtek_gpio_irq_set_type,
++ .irq_set_affinity = realtek_gpio_irq_set_affinity,
+ };
+
+ static const struct of_device_id realtek_gpio_of_match[] = {
+@@ -293,8 +349,10 @@ static int realtek_gpio_probe(struct pla
+ unsigned int dev_flags;
+ struct gpio_irq_chip *girq;
+ struct realtek_gpio_ctrl *ctrl;
++ struct resource *res;
+ u32 ngpios;
+- int err, irq;
++ unsigned int nr_cpus;
++ int cpu, err, irq;
+
+ ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL);
+ if (!ctrl)
+@@ -355,6 +413,21 @@ static int realtek_gpio_probe(struct pla
+ girq->init_hw = realtek_gpio_irq_init;
+ }
+
++ cpumask_clear(&ctrl->cpu_irq_maskable);
++
++ if ((dev_flags & GPIO_INTERRUPTS_PER_CPU) && irq > 0) {
++ ctrl->cpumask_base = devm_platform_get_and_ioremap_resource(pdev, 1, &res);
++ if (IS_ERR(ctrl->cpumask_base))
++ return dev_err_probe(dev, PTR_ERR(ctrl->cpumask_base),
++ "missing CPU IRQ mask registers");
++
++ nr_cpus = resource_size(res) / REALTEK_GPIO_PORTS_PER_BANK;
++ nr_cpus = min(nr_cpus, num_present_cpus());
++
++ for (cpu = 0; cpu < nr_cpus; cpu++)
++ cpumask_set_cpu(cpu, &ctrl->cpu_irq_maskable);
++ }
++
+ return devm_gpiochip_add_data(dev, &ctrl->gc, ctrl);
+ }
+
diff --git a/target/linux/realtek/patches-5.15/021-v5.19-04-gpio-realtek-otto-Add-RTL930x-support.patch b/target/linux/realtek/patches-5.15/021-v5.19-04-gpio-realtek-otto-Add-RTL930x-support.patch
new file mode 100644
index 0000000000..536c85740a
--- /dev/null
+++ b/target/linux/realtek/patches-5.15/021-v5.19-04-gpio-realtek-otto-Add-RTL930x-support.patch
@@ -0,0 +1,29 @@
+From deaf1cecdeb052cdb5e92fd642016198724b44a4 Mon Sep 17 00:00:00 2001
+From: Sander Vanheule <sander@svanheule.net>
+Date: Sat, 9 Apr 2022 21:55:49 +0200
+Subject: [PATCH 4/6] gpio: realtek-otto: Add RTL930x support
+
+The RTL930x SoC series has support for 24 GPIOs, with the port order
+reversed compared to RTL838x and RTL839x. The RTL930x series also has
+two CPUs (VPEs) and can distribute individual GPIO interrupts between
+them.
+
+Signed-off-by: Sander Vanheule <sander@svanheule.net>
+Signed-off-by: Bartosz Golaszewski <brgl@bgdev.pl>
+---
+ drivers/gpio/gpio-realtek-otto.c | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+--- a/drivers/gpio/gpio-realtek-otto.c
++++ b/drivers/gpio/gpio-realtek-otto.c
+@@ -338,6 +338,10 @@ static const struct of_device_id realtek
+ {
+ .compatible = "realtek,rtl8390-gpio",
+ },
++ {
++ .compatible = "realtek,rtl9300-gpio",
++ .data = (void *)(GPIO_PORTS_REVERSED | GPIO_INTERRUPTS_PER_CPU)
++ },
+ {}
+ };
+ MODULE_DEVICE_TABLE(of, realtek_gpio_of_match);
diff --git a/target/linux/realtek/patches-5.15/021-v5.19-06-gpio-realtek-otto-Add-RTL931x-support.patch b/target/linux/realtek/patches-5.15/021-v5.19-06-gpio-realtek-otto-Add-RTL931x-support.patch
new file mode 100644
index 0000000000..16990ce053
--- /dev/null
+++ b/target/linux/realtek/patches-5.15/021-v5.19-06-gpio-realtek-otto-Add-RTL931x-support.patch
@@ -0,0 +1,30 @@
+From d3bf3dc4bbbf6109bd9b4bd60089d36205ec4a37 Mon Sep 17 00:00:00 2001
+From: Sander Vanheule <sander@svanheule.net>
+Date: Sat, 9 Apr 2022 21:55:51 +0200
+Subject: [PATCH 6/6] gpio: realtek-otto: Add RTL931x support
+
+The RTL931x SoC series has support for 32 GPIOs, although not all lines
+may be broken out to a physical pad.
+
+The GPIO bank's parent interrupt can be routed to either or both of the
+SoC's CPU cores by the GIC. Line-by-line IRQ balancing is not possible
+on these SoCs.
+
+Signed-off-by: Sander Vanheule <sander@svanheule.net>
+Signed-off-by: Bartosz Golaszewski <brgl@bgdev.pl>
+---
+ drivers/gpio/gpio-realtek-otto.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+--- a/drivers/gpio/gpio-realtek-otto.c
++++ b/drivers/gpio/gpio-realtek-otto.c
+@@ -342,6 +342,9 @@ static const struct of_device_id realtek
+ .compatible = "realtek,rtl9300-gpio",
+ .data = (void *)(GPIO_PORTS_REVERSED | GPIO_INTERRUPTS_PER_CPU)
+ },
++ {
++ .compatible = "realtek,rtl9310-gpio",
++ },
+ {}
+ };
+ MODULE_DEVICE_TABLE(of, realtek_gpio_of_match);
diff --git a/target/linux/realtek/patches-5.15/300-mips-add-rtl838x-platform.patch b/target/linux/realtek/patches-5.15/300-mips-add-rtl838x-platform.patch
new file mode 100644
index 0000000000..3834ba7c61
--- /dev/null
+++ b/target/linux/realtek/patches-5.15/300-mips-add-rtl838x-platform.patch
@@ -0,0 +1,96 @@
+From fce11f68491b46b93df69de0630cd9edb90bc772 Mon Sep 17 00:00:00 2001
+From: Birger Koblitz <git@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@birger-koblitz.de>
+---
+ arch/mips/Kbuild.platforms | 1 +
+ arch/mips/Kconfig | 57 ++++++++++++++
+ 2 files changed, 58 insertions(+)
+
+--- a/arch/mips/Kbuild.platforms
++++ b/arch/mips/Kbuild.platforms
+@@ -23,6 +23,7 @@ platform-$(CONFIG_PIC32MZDA) += pic32/
+ platform-$(CONFIG_MACH_PISTACHIO) += pistachio/
+ platform-$(CONFIG_RALINK) += ralink/
+ platform-$(CONFIG_MIKROTIK_RB532) += rb532/
++platform-$(CONFIG_RTL83XX) += 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
+@@ -1037,8 +1037,58 @@ config NLM_XLP_BOARD
+ This board is based on Netlogic XLP Processor.
+ Say Y here if you have a XLP based board.
+
++config RTL83XX
++ bool "Realtek based platforms"
++ select DMA_NONCOHERENT
++ select IRQ_MIPS_CPU
++ select SYS_HAS_CPU_MIPS32_R1
++ select SYS_HAS_CPU_MIPS32_R2
++ select SYS_SUPPORTS_BIG_ENDIAN
++ select SYS_SUPPORTS_HIGHMEM
++ select SYS_SUPPORTS_32BIT_KERNEL
++ select SYS_SUPPORTS_MIPS16
++ select SYS_HAS_EARLY_PRINTK
++ select SYS_HAS_EARLY_PRINTK_8250
++ select USE_GENERIC_EARLY_PRINTK_8250
++ select BOOT_RAW
++ select PINCTRL
++ select ARCH_HAS_RESET_CONTROLLER
++ select RESET_CONTROLLER
++ select USE_OF
++
+ endchoice
+
++config RTL838X
++ bool "Realtek RTL838X based platforms"
++ depends on RTL83XX
++ select CPU_SUPPORTS_CPUFREQ
++ select MIPS_EXTERNAL_TIMER
++
++config RTL839X
++ bool "Realtek RTL839X based platforms"
++ depends on RTL83XX
++ select CPU_SUPPORTS_CPUFREQ
++ select MIPS_EXTERNAL_TIMER
++ select SYS_SUPPORTS_MULTITHREADING
++
++config RTL930X
++ bool "Realtek RTL930X based platforms"
++ depends on RTL83XX
++ select MIPS_CPU_SCACHE
++ select CSRC_R4K
++ select CEVT_RTL9300
++ 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"
+@@ -1097,6 +1147,9 @@ config CEVT_GT641XX
+ config CEVT_R4K
+ bool
+
++config CEVT_RTL9300
++ bool
++
+ config CEVT_SB1250
+ bool
+
diff --git a/target/linux/realtek/patches-5.15/301-gpio-add-rtl8231-driver.patch b/target/linux/realtek/patches-5.15/301-gpio-add-rtl8231-driver.patch
new file mode 100644
index 0000000000..2725651243
--- /dev/null
+++ b/target/linux/realtek/patches-5.15/301-gpio-add-rtl8231-driver.patch
@@ -0,0 +1,50 @@
+From 2b88563ee5aafd9571d965b7f2093a0f58d98a31 Mon Sep 17 00:00:00 2001
+From: John Crispin <john@phrozen.org>
+Date: Thu, 26 Nov 2020 12:02:21 +0100
+Subject: [PATCH] realtek: update the tree to the latest refactored version
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+* 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@biot.com>
+Submitted-by: Birger Koblitz <mail@birger-koblitz.de>
+Submitted-by: Sander Vanheule <sander@svanheule.net>
+Submitted-by: Bjørn Mork <bjorn@mork.no>
+Submitted-by: John Crispin <john@phrozen.org>
+---
+ drivers/gpio/Kconfig | 6 ++++++
+ drivers/gpio/Makefile | 1 +
+ 2 files changed, 7 insertions(+)
+
+--- a/drivers/gpio/Kconfig
++++ b/drivers/gpio/Kconfig
+@@ -508,6 +508,12 @@ config GPIO_REG
+ A 32-bit single register GPIO fixed in/out implementation. This
+ can be used to represent any register as a set of GPIO signals.
+
++config GPIO_RTL8231
++ tristate "RTL8231 GPIO"
++ depends on RTL83XX
++ help
++ Say yes here to support Realtek RTL8231 GPIO expansion chips.
++
+ config GPIO_SAMA5D2_PIOBU
+ tristate "SAMA5D2 PIOBU GPIO support"
+ depends on MFD_SYSCON
+--- a/drivers/gpio/Makefile
++++ b/drivers/gpio/Makefile
+@@ -127,6 +127,7 @@ obj-$(CONFIG_GPIO_RDA) += gpio-rda.o
+ obj-$(CONFIG_GPIO_RDC321X) += gpio-rdc321x.o
+ obj-$(CONFIG_GPIO_REALTEK_OTTO) += gpio-realtek-otto.o
+ obj-$(CONFIG_GPIO_REG) += gpio-reg.o
++obj-$(CONFIG_GPIO_RTL8231) += gpio-rtl8231.o
+ obj-$(CONFIG_ARCH_SA1100) += gpio-sa1100.o
+ obj-$(CONFIG_GPIO_SAMA5D2_PIOBU) += gpio-sama5d2-piobu.o
+ obj-$(CONFIG_GPIO_SCH311X) += gpio-sch311x.o
diff --git a/target/linux/realtek/patches-5.15/302-clocksource-add-otto-driver.patch b/target/linux/realtek/patches-5.15/302-clocksource-add-otto-driver.patch
new file mode 100644
index 0000000000..8b17ddaef0
--- /dev/null
+++ b/target/linux/realtek/patches-5.15/302-clocksource-add-otto-driver.patch
@@ -0,0 +1,40 @@
+--- a/drivers/clocksource/Kconfig
++++ b/drivers/clocksource/Kconfig
+@@ -127,6 +127,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
+@@ -63,6 +63,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
+@@ -133,6 +133,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-5.15/303-gpio-update-dependencies-for-gpio-realtek-otto.patch b/target/linux/realtek/patches-5.15/303-gpio-update-dependencies-for-gpio-realtek-otto.patch
new file mode 100644
index 0000000000..5f0986b905
--- /dev/null
+++ b/target/linux/realtek/patches-5.15/303-gpio-update-dependencies-for-gpio-realtek-otto.patch
@@ -0,0 +1,26 @@
+From 9bac1c20b8f39f2e0e342b087add5093b94feaed Mon Sep 17 00:00:00 2001
+From: INAGAKI Hiroshi <musashino.open@gmail.com>
+Date: Wed, 5 May 2021 22:05:39 +0900
+Subject: realtek: backport gpio-realtek-otto driver from 5.13 to 5.10
+
+This patch backports "gpio-realtek-otto" driver to Kernel 5.10.
+"MACH_REALTEK_RTL" is used as a platform name in upstream, but "RTL838X"
+is used in OpenWrt, so update the dependency by the additional patch.
+
+Submitted-by: INAGAKI Hiroshi <musashino.open@gmail.com>
+---
+ drivers/gpio/Kconfig | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+--- a/drivers/gpio/Kconfig
++++ b/drivers/gpio/Kconfig
+@@ -491,8 +491,8 @@ config GPIO_RDA
+
+ config GPIO_REALTEK_OTTO
+ tristate "Realtek Otto GPIO support"
+- depends on MACH_REALTEK_RTL
+- default MACH_REALTEK_RTL
++ depends on RTL83XX
++ default RTL838X
+ select GPIO_GENERIC
+ select GPIOLIB_IRQCHIP
+ help
diff --git a/target/linux/realtek/patches-5.15/304-spi-update-dependency-for-spi-realtek-rtl.patch b/target/linux/realtek/patches-5.15/304-spi-update-dependency-for-spi-realtek-rtl.patch
new file mode 100644
index 0000000000..23c5448aa2
--- /dev/null
+++ b/target/linux/realtek/patches-5.15/304-spi-update-dependency-for-spi-realtek-rtl.patch
@@ -0,0 +1,25 @@
+From 0b000cbfe0aa0323bffa855ef8449c0687a4c071 Mon Sep 17 00:00:00 2001
+From: INAGAKI Hiroshi <musashino.open@gmail.com>
+Date: Thu, 6 May 2021 19:30:58 +0900
+Subject: realtek: backport spi-realtek-rtl driver from 5.12 to 5.10
+
+This patch backports "spi-realtek-rtl" driver to Kernel 5.10 from 5.12.
+"MACH_REALTEK_RTL" is used as a platform name in upstream, but "RTL838X"
+is used in OpenWrt, so update the dependency by the additional patch.
+
+Submitted-by: INAGAKI Hiroshi <musashino.open@gmail.com>
+---
+ drivers/spi/Makefile | 2 +-
+ 1 files changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/spi/Makefile
++++ b/drivers/spi/Makefile
+@@ -94,7 +94,7 @@ obj-$(CONFIG_SPI_QCOM_QSPI) += spi-qcom
+ obj-$(CONFIG_SPI_QUP) += spi-qup.o
+ obj-$(CONFIG_SPI_ROCKCHIP) += spi-rockchip.o
+ obj-$(CONFIG_SPI_RB4XX) += spi-rb4xx.o
+-obj-$(CONFIG_MACH_REALTEK_RTL) += spi-realtek-rtl.o
++obj-$(CONFIG_RTL83XX) += spi-realtek-rtl.o
+ obj-$(CONFIG_SPI_RPCIF) += spi-rpc-if.o
+ obj-$(CONFIG_SPI_RSPI) += spi-rspi.o
+ obj-$(CONFIG_SPI_S3C24XX) += spi-s3c24xx-hw.o
diff --git a/target/linux/realtek/patches-5.15/305-irqchip-update-dependency-for-irq-realtek-rtl.patch b/target/linux/realtek/patches-5.15/305-irqchip-update-dependency-for-irq-realtek-rtl.patch
new file mode 100644
index 0000000000..0c1cc4235e
--- /dev/null
+++ b/target/linux/realtek/patches-5.15/305-irqchip-update-dependency-for-irq-realtek-rtl.patch
@@ -0,0 +1,22 @@
+From 2cd00b51470a30198b048a5fca48a04db77e29cc Mon Sep 17 00:00:00 2001
+From: INAGAKI Hiroshi <musashino.open@gmail.com>
+Date: Fri, 21 May 2021 23:16:37 +0900
+Subject: [PATCH] realtek: backport irq-realtek-rtl driver from 5.12 to 5.10
+
+This patch backports "irq-realtek-rtl" driver to Kernel 5.10 from 5.12.
+"MACH_REALTEK_RTL" is used as a platform name in upstream, but "RTL838X"
+is used in OpenWrt, so update the dependency by the additional patch.
+
+Submitted-by: INAGAKI Hiroshi <musashino.open@gmail.com>
+---
+ drivers/irqchip/Makefile | 2 +-
+ 1 files changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/irqchip/Makefile
++++ b/drivers/irqchip/Makefile
+@@ -114,4 +114,4 @@ obj-$(CONFIG_LOONGSON_PCH_PIC) += irq-l
+ obj-$(CONFIG_LOONGSON_PCH_MSI) += irq-loongson-pch-msi.o
+ obj-$(CONFIG_MST_IRQ) += irq-mst-intc.o
+ obj-$(CONFIG_SL28CPLD_INTC) += irq-sl28cpld.o
+-obj-$(CONFIG_MACH_REALTEK_RTL) += irq-realtek-rtl.o
++obj-$(CONFIG_RTL83XX) += irq-realtek-rtl.o
diff --git a/target/linux/realtek/patches-5.15/307-wdt-update-dependency-for-realtek-otto-wdt.patch b/target/linux/realtek/patches-5.15/307-wdt-update-dependency-for-realtek-otto-wdt.patch
new file mode 100644
index 0000000000..f92981340b
--- /dev/null
+++ b/target/linux/realtek/patches-5.15/307-wdt-update-dependency-for-realtek-otto-wdt.patch
@@ -0,0 +1,32 @@
+From b8fc5eecdc5d33cf261986436597b5482ab856da Mon Sep 17 00:00:00 2001
+From: Sander Vanheule <sander@svanheule.net>
+Date: Sun, 14 Nov 2021 19:45:32 +0100
+Subject: [PATCH] realtek: Backport Realtek Otto WDT driver
+
+Add patch submitted upstream to linux-watchdog and replace the MIPS
+architecture symbols. Requires one extra patch for the DIV_ROUND_*
+macros, which have moved to a different header since 5.10.
+
+Submitted-by: Sander Vanheule <sander@svanheule.net>
+Tested-by: Stijn Segers <foss@volatilesystems.org>
+Tested-by: Paul Fertser <fercerpav@gmail.com>
+Tested-by: Stijn Tintel <stijn@linux-ipv6.be>
+---
+ drivers/watchdog/Kconfig | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+--- a/drivers/watchdog/Kconfig
++++ b/drivers/watchdog/Kconfig
+@@ -997,10 +997,10 @@ config RTD119X_WATCHDOG
+
+ config REALTEK_OTTO_WDT
+ tristate "Realtek Otto MIPS watchdog support"
+- depends on MACH_REALTEK_RTL || COMPILE_TEST
++ depends on RTL83XX
+ depends on COMMON_CLK
+ select WATCHDOG_CORE
+- default MACH_REALTEK_RTL
++ default RTL83XX
+ help
+ Say Y here to include support for the watchdog timer on Realtek
+ RTL838x, RTL839x, RTL930x SoCs. This watchdog has pretimeout
diff --git a/target/linux/realtek/patches-5.15/308-otto-wdt-fix-missing-math-header.patch b/target/linux/realtek/patches-5.15/308-otto-wdt-fix-missing-math-header.patch
new file mode 100644
index 0000000000..78b145617f
--- /dev/null
+++ b/target/linux/realtek/patches-5.15/308-otto-wdt-fix-missing-math-header.patch
@@ -0,0 +1,28 @@
+From b8fc5eecdc5d33cf261986436597b5482ab856da Mon Sep 17 00:00:00 2001
+From: Sander Vanheule <sander@svanheule.net>
+Date: Sun, 14 Nov 2021 19:45:32 +0100
+Subject: [PATCH] realtek: Backport Realtek Otto WDT driver
+
+Add patch submitted upstream to linux-watchdog and replace the MIPS
+architecture symbols. Requires one extra patch for the DIV_ROUND_*
+macros, which have moved to a different header since 5.10.
+
+Submitted-by: Sander Vanheule <sander@svanheule.net>
+Tested-by: Stijn Segers <foss@volatilesystems.org>
+Tested-by: Paul Fertser <fercerpav@gmail.com>
+Tested-by: Stijn Tintel <stijn@linux-ipv6.be>
+---
+ drivers/watchdog/realtek_otto_wdt.c | 2 +-
+ 1 files changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/watchdog/realtek_otto_wdt.c
++++ b/drivers/watchdog/realtek_otto_wdt.c
+@@ -21,7 +21,7 @@
+ #include <linux/delay.h>
+ #include <linux/interrupt.h>
+ #include <linux/io.h>
+-#include <linux/math.h>
++#include <linux/kernel.h>
+ #include <linux/minmax.h>
+ #include <linux/module.h>
+ #include <linux/mod_devicetable.h>
diff --git a/target/linux/realtek/patches-5.15/309-cevt-rtl9300-support.patch b/target/linux/realtek/patches-5.15/309-cevt-rtl9300-support.patch
new file mode 100644
index 0000000000..93d69c07cf
--- /dev/null
+++ b/target/linux/realtek/patches-5.15/309-cevt-rtl9300-support.patch
@@ -0,0 +1,54 @@
+From 775d903216a08c2a8009863d2f9c33f62657ba94 Mon Sep 17 00:00:00 2001
+From: Birger Koblitz <git@birger-koblitz.de>
+Date: Thu, 6 Jan 2022 20:27:01 +0100
+Subject: [PATCH] realtek: Replace the RTL9300 generic timer with a CEVT timer
+
+The RTL9300 has a broken R4K MIPS timer interrupt, however, the
+R4K clocksource works. We replace the RTL9300 timer with a
+Clock Event Timer (CEVT), which is VSMP aware and can be instantiated
+as part of brining a VSMTP cpu up instead of the R4K CEVT source.
+For this we place the RTL9300 CEVT timer in arch/mips/kernel
+together with other MIPS CEVT timers, initialize the SoC IRQs
+from a modified smp-mt.c and instantiate each timer as part
+of the MIPS time setup in arch/mips/include/asm/time.h instead
+of the R4K CEVT, similarly as is done by other MIPS CEVT timers.
+
+Submitted-by: Birger Koblitz <git@birger-koblitz.de>
+---
+ arch/mips/kernel/Makefile | 1 +
+ arch/mips/include/asm/time.h | 7 +++++++
+ 2 files changed, 8 insertions(+)
+
+--- a/arch/mips/kernel/Makefile
++++ b/arch/mips/kernel/Makefile
+@@ -27,6 +27,7 @@ obj-$(CONFIG_CEVT_BCM1480) += cevt-bcm14
+ obj-$(CONFIG_CEVT_R4K) += cevt-r4k.o
+ obj-$(CONFIG_CEVT_DS1287) += cevt-ds1287.o
+ obj-$(CONFIG_CEVT_GT641XX) += cevt-gt641xx.o
++obj-$(CONFIG_CEVT_RTL9300) += cevt-rtl9300.o
+ obj-$(CONFIG_CEVT_SB1250) += cevt-sb1250.o
+ obj-$(CONFIG_CEVT_TXX9) += cevt-txx9.o
+ obj-$(CONFIG_CSRC_BCM1480) += csrc-bcm1480.o
+--- a/arch/mips/include/asm/time.h
++++ b/arch/mips/include/asm/time.h
+@@ -15,6 +15,8 @@
+ #include <linux/clockchips.h>
+ #include <linux/clocksource.h>
+
++extern void rtl9300_clockevent_init(void);
++
+ extern spinlock_t rtc_lock;
+
+ /*
+@@ -43,6 +45,11 @@ extern int r4k_clockevent_init(void);
+
+ static inline int mips_clockevent_init(void)
+ {
++#ifdef CONFIG_CEVT_RTL9300
++ rtl9300_clockevent_init();
++ return 0;
++#endif
++
+ #ifdef CONFIG_CEVT_R4K
+ return r4k_clockevent_init();
+ #else
diff --git a/target/linux/realtek/patches-5.15/310-add-i2c-rtl9300-support.patch b/target/linux/realtek/patches-5.15/310-add-i2c-rtl9300-support.patch
new file mode 100644
index 0000000000..4df9bf043b
--- /dev/null
+++ b/target/linux/realtek/patches-5.15/310-add-i2c-rtl9300-support.patch
@@ -0,0 +1,46 @@
+From 63a0a4d85bc900464c5b046b13808a582345f8c8 Mon Sep 17 00:00:00 2001
+From: Birger Koblitz <git@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@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
+@@ -954,6 +954,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 HAVE_S3C2410_I2C
+ bool
+ help
+--- a/drivers/i2c/busses/Makefile
++++ b/drivers/i2c/busses/Makefile
+@@ -95,6 +95,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_S3C2410) += i2c-s3c2410.o
+ obj-$(CONFIG_I2C_SH7760) += i2c-sh7760.o
+ obj-$(CONFIG_I2C_SH_MOBILE) += i2c-sh_mobile.o
diff --git a/target/linux/realtek/patches-5.15/311-add-i2c-mux-rtl9300-support.patch b/target/linux/realtek/patches-5.15/311-add-i2c-mux-rtl9300-support.patch
new file mode 100644
index 0000000000..d0bfba4538
--- /dev/null
+++ b/target/linux/realtek/patches-5.15/311-add-i2c-mux-rtl9300-support.patch
@@ -0,0 +1,46 @@
+From f4bdb7fdccdfe3fa382abe77f72a16c2f2e6add0 Mon Sep 17 00:00:00 2001
+From: Birger Koblitz <git@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@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-5.15/312-rt9313-support.patch b/target/linux/realtek/patches-5.15/312-rt9313-support.patch
new file mode 100644
index 0000000000..516bca2b6e
--- /dev/null
+++ b/target/linux/realtek/patches-5.15/312-rt9313-support.patch
@@ -0,0 +1,76 @@
+From 0b8dfe085180b58b81d2657c76b080168e3bc8df Mon Sep 17 00:00:00 2001
+From: Birger Koblitz <git@birger-koblitz.de>
+Date: Wed, 19 Jan 2022 18:14:02 +0100
+Subject: [PATCH] realtek: Add RTL931X sub-target
+
+We add the RTL931X sub-target with kernel configuration for
+a dual core MIPS InterAptive CPU.
+
+Submitted-by: Sebastian Gottschall <s.gottschall@dd-wrt.com>
+Submitted-by: Birger Koblitz <git@birger-koblitz.de>
+---
+ arch/mips/Makefile | 10 +++++++++++++--
+ arch/mips/kernel/head.S | 4 ++++
+ aarch/mips/kernel/vmlinux.lds.S | 4 ++++
+ 3 files changed, 18 insertions(+), 2 deletions(-)
+
+--- a/arch/mips/Makefile
++++ b/arch/mips/Makefile
+@@ -307,14 +307,24 @@ endif
+
+ KBUILD_AFLAGS += $(cflags-y)
+ KBUILD_CFLAGS += $(cflags-y)
++ifdef CONFIG_931X
++KBUILD_CPPFLAGS += -DVMLINUX_LOAD_ADDRESS=$(load-y)
++bootvars-y = VMLINUX_LOAD_ADDRESS=$(load-y) \
++ VMLINUX_ENTRY_ADDRESS=$(entry-y) \
++ PLATFORM="$(platform-y)" \
++ ITS_INPUTS="$(its-y)"
++else
+ KBUILD_CPPFLAGS += -DVMLINUX_LOAD_ADDRESS=$(load-y) -DLINKER_LOAD_ADDRESS=$(load-ld)
+-KBUILD_CPPFLAGS += -DDATAOFFSET=$(if $(dataoffset-y),$(dataoffset-y),0)
+-
+ bootvars-y = VMLINUX_LOAD_ADDRESS=$(load-y) \
+ LINKER_LOAD_ADDRESS=$(load-ld) \
+ VMLINUX_ENTRY_ADDRESS=$(entry-y) \
+ PLATFORM="$(platform-y)" \
+ ITS_INPUTS="$(its-y)"
++endif
++KBUILD_CPPFLAGS += -DDATAOFFSET=$(if $(dataoffset-y),$(dataoffset-y),0)
++
++
++
+ ifdef CONFIG_32BIT
+ bootvars-y += ADDR_BITS=32
+ endif
+--- a/arch/mips/kernel/head.S
++++ b/arch/mips/kernel/head.S
+@@ -60,12 +60,14 @@
+ .endm
+
+ #ifndef CONFIG_NO_EXCEPT_FILL
++#ifndef CONFIG_RTL931X
+ /*
+ * Reserved space for exception handlers.
+ * Necessary for machines which link their kernels at KSEG0.
+ */
+ .fill 0x400
+ #endif
++#endif
+
+ EXPORT(_stext)
+
+--- a/arch/mips/kernel/vmlinux.lds.S
++++ b/arch/mips/kernel/vmlinux.lds.S
+@@ -55,7 +55,11 @@ SECTIONS
+ /* . = 0xa800000000300000; */
+ . = 0xffffffff80300000;
+ #endif
++#ifdef CONFIG_RTL931X
++ . = 0x80220000;
++#else
+ . = LINKER_LOAD_ADDRESS;
++#endif
+ /* read-only */
+ _text = .; /* Text and read-only data */
+ .text : {
diff --git a/target/linux/realtek/patches-5.15/315-irqchip-irq-realtek-rtl-add-VPE-support.patch b/target/linux/realtek/patches-5.15/315-irqchip-irq-realtek-rtl-add-VPE-support.patch
new file mode 100644
index 0000000000..26c36302c2
--- /dev/null
+++ b/target/linux/realtek/patches-5.15/315-irqchip-irq-realtek-rtl-add-VPE-support.patch
@@ -0,0 +1,407 @@
+From 6c18e9c491959ac0674ebe36b09f9ddc3f2c9bce Mon Sep 17 00:00:00 2001
+From: Birger Koblitz <git@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.
+
+Submitted-by: Birger Koblitz <git@birger-koblitz.de>
+---
+ drivers/irqchip/irq-realtek-rtl.c | 152 +++++++++++++++---
+ 1 file changed, 73 insertions(+), 76 deletions(-)
+
+--- a/drivers/irqchip/irq-realtek-rtl.c
++++ b/drivers/irqchip/irq-realtek-rtl.c
+@@ -21,21 +21,63 @@
+ #define RTL_ICTL_IRR2 0x10
+ #define RTL_ICTL_IRR3 0x14
+
+-#define REG(x) (realtek_ictl_base + x)
++#define RTL_ICTL_NUM_INPUTS 32
++#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 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}.
++ */
++#define IRR_OFFSET(idx) (4 * (3 - (idx * 4) / 32))
++#define IRR_SHIFT(idx) ((idx * 4) % 32)
++
++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);
++ u32 irr;
++
++ irr = readl(irr0 + offset) & ~(0xf << shift);
++ irr |= (value & 0xf) << shift;
++ writel(irr, irr0 + offset);
++}
+
+ 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) {
++ value = readl(REG(RTL_ICTL_GIMR, cpu));
++ value |= BIT(i->hwirq);
++ writel(value, REG(RTL_ICTL_GIMR, cpu));
++ }
+
+ raw_spin_unlock_irqrestore(&irq_lock, flags);
+ }
+@@ -44,137 +86,247 @@ static void realtek_ictl_mask_irq(struct
+ {
+ 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) {
++ value = readl(REG(RTL_ICTL_GIMR, cpu));
++ value &= ~BIT(i->hwirq);
++ writel(value, REG(RTL_ICTL_GIMR, 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);
++
++ for_each_cpu(cpu, &cpu_enable)
++ write_irr(REG(RTL_ICTL_IRR0, cpu), i->hwirq, output->output_index + 1);
++
++ 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)
+ {
+- irq_set_chip_and_handler(hw, &realtek_ictl_irq, handle_level_irq);
++ 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);
++
++ output->child_mask |= BIT(hw);
++ write_irr(REG(RTL_ICTL_IRR0, 0), hw, output->output_index + 1);
++
++ 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;
+- unsigned int pending;
++ 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);
+- generic_handle_irq(irq_find_mapping(domain, __ffs(pending)));
++
++ for_each_set_bit(soc_int, &pending, RTL_ICTL_NUM_INPUTS)
++ generic_handle_irq(irq_find_mapping(output->domain, soc_int));
++// 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.
+- */
+-static int __init map_interrupts(struct device_node *node, struct irq_domain *domain)
++static int __init setup_parent_interrupts(struct device_node *node, int *parents,
++ unsigned int num_parents)
+ {
+- struct device_node *cpu_ictl;
+- const __be32 *imap;
+- u32 imaplen, soc_int, cpu_int, tmp, regs[4];
+- int ret, i, irr_regs[] = {
+- RTL_ICTL_IRR3,
+- RTL_ICTL_IRR2,
+- RTL_ICTL_IRR1,
+- RTL_ICTL_IRR0,
+- };
+- u8 mips_irqs_set;
++ struct realtek_ictl_output *outputs;
++ struct realtek_ictl_output *output;
++ struct irq_domain *domain;
++ unsigned int p;
+
+- ret = of_property_read_u32(node, "#address-cells", &tmp);
+- if (ret || tmp)
+- return -EINVAL;
++ outputs = kcalloc(num_parents, sizeof(*outputs), GFP_KERNEL);
++ if (!outputs)
++ return -ENOMEM;
+
+- imap = of_get_property(node, "interrupt-map", &imaplen);
+- if (!imap || imaplen % 3)
+- return -EINVAL;
++ for (p = 0; p < num_parents; p++) {
++ output = outputs + p;
+
+- mips_irqs_set = 0;
+- memset(regs, 0, sizeof(regs));
+- for (i = 0; i < imaplen; i += 3 * sizeof(u32)) {
+- soc_int = be32_to_cpup(imap);
+- if (soc_int > 31)
+- return -EINVAL;
+-
+- cpu_ictl = of_find_node_by_phandle(be32_to_cpup(imap + 1));
+- if (!cpu_ictl)
+- return -EINVAL;
+- ret = of_property_read_u32(cpu_ictl, "#interrupt-cells", &tmp);
+- if (ret || tmp != 1)
+- return -EINVAL;
+- of_node_put(cpu_ictl);
+-
+- cpu_int = be32_to_cpup(imap + 2);
+- if (cpu_int > 7)
+- return -EINVAL;
+-
+- if (!(mips_irqs_set & BIT(cpu_int))) {
+- irq_set_chained_handler_and_data(cpu_int, realtek_irq_dispatch,
+- domain);
+- mips_irqs_set |= BIT(cpu_int);
+- }
++ domain = irq_domain_add_linear(node, RTL_ICTL_NUM_INPUTS, &irq_domain_ops, output);
++ if (!domain)
++ goto domain_err;
+
+- regs[(soc_int * 4) / 32] |= cpu_int << (soc_int * 4) % 32;
+- imap += 3;
+- }
++ output->fwnode = of_node_to_fwnode(node);
++ output->output_index = p;
++ output->domain = domain;
+
+- for (i = 0; i < 4; i++)
+- writel(regs[i], REG(irr_regs[i]));
++ 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)
+ {
+- struct irq_domain *domain;
+- int ret;
++ int parent_irqs[RTL_ICTL_NUM_OUTPUTS];
++ struct of_phandle_args oirq;
++ unsigned int num_parents;
++ unsigned int soc_irq;
++ unsigned int p;
++ int cpu;
++
++ cpumask_clear(&realtek_ictl_cpu_configurable);
++
++ 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 */
++ writel(0, REG(RTL_ICTL_GIMR, cpu));
++ for (soc_irq = 0; soc_irq < RTL_ICTL_NUM_INPUTS; soc_irq++)
++ write_irr(REG(RTL_ICTL_IRR0, cpu), soc_irq, 0);
++ }
++ }
+
+- realtek_ictl_base = of_iomap(node, 0);
+- if (!realtek_ictl_base)
++ if (cpumask_empty(&realtek_ictl_cpu_configurable))
+ return -ENXIO;
+
+- /* Disable all cascaded interrupts */
+- writel(0, REG(RTL_ICTL_GIMR));
++ num_parents = of_irq_count(node);
++ if (num_parents > RTL_ICTL_NUM_OUTPUTS) {
++ pr_err("too many parent interrupts\n");
++ return -EINVAL;
++ }
+
+- domain = irq_domain_add_simple(node, 32, 0,
+- &irq_domain_ops, NULL);
++ for (p = 0; p < num_parents; p++)
++ parent_irqs[p] = of_irq_get(node, p);
+
+- ret = map_interrupts(node, domain);
+- if (ret) {
+- pr_err("invalid interrupt map\n");
+- return ret;
++ 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");
++ 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);
+ }
+
+- return 0;
++ /* 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 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-5.15/316-otto-gpio-uniprocessor-irq-mask.patch b/target/linux/realtek/patches-5.15/316-otto-gpio-uniprocessor-irq-mask.patch
new file mode 100644
index 0000000000..3980875dd4
--- /dev/null
+++ b/target/linux/realtek/patches-5.15/316-otto-gpio-uniprocessor-irq-mask.patch
@@ -0,0 +1,51 @@
+From bde6311569ef25a00c3beaeabfd6b78b19651872 Mon Sep 17 00:00:00 2001
+From: Sander Vanheule <sander@svanheule.net>
+Date: Sun, 29 May 2022 19:38:09 +0200
+Subject: [PATCH] realtek: don't unmask non-maskable GPIO IRQs
+
+On uniprocessor builds, for_each_cpu(cpu, mask) will assume 'mask'
+always contains exactly one CPU, and ignore the actual mask contents.
+This causes the loop to run, even when it shouldn't on an empty mask,
+and tries to access an uninitialised pointer.
+
+Fix this by wrapping the loop in a cpumask_empty() check, to ensure it
+will not run on uniprocessor builds if the CPU mask is empty.
+
+Fixes: af6cd37f42f3 ("realtek: replace RTL93xx GPIO patches")
+Reported-by: INAGAKI Hiroshi <musashino.open@gmail.com>
+Reported-by: Robert Marko <robimarko@gmail.com>
+Tested-by: Robert Marko <robimarko@gmail.com>
+Submitted-by: Sander Vanheule <sander@svanheule.net>
+---
+ drivers/gpio/gpio-realtek-otto.c | 9 +++++++++++--
+ 1 file changed, 11 insertions(+), 2 deletions(-)
+
+--- a/drivers/gpio/gpio-realtek-otto.c
++++ b/drivers/gpio/gpio-realtek-otto.c
+@@ -304,6 +304,7 @@ static int realtek_gpio_irq_set_affinity
+ static int realtek_gpio_irq_init(struct gpio_chip *gc)
+ {
+ struct realtek_gpio_ctrl *ctrl = gpiochip_get_data(gc);
++ void __iomem *irq_cpu_mask;
+ unsigned int port;
+ int cpu;
+
+@@ -311,8 +312,16 @@ static int realtek_gpio_irq_init(struct
+ realtek_gpio_write_imr(ctrl, port, 0, 0);
+ realtek_gpio_clear_isr(ctrl, port, GENMASK(7, 0));
+
+- for_each_cpu(cpu, &ctrl->cpu_irq_maskable)
+- iowrite8(GENMASK(7, 0), realtek_gpio_irq_cpu_mask(ctrl, port, cpu));
++ /*
++ * Uniprocessor builds assume a mask always contains one CPU,
++ * so only start the loop if we have at least one maskable CPU.
++ */
++ if(!cpumask_empty(&ctrl->cpu_irq_maskable)) {
++ for_each_cpu(cpu, &ctrl->cpu_irq_maskable) {
++ irq_cpu_mask = realtek_gpio_irq_cpu_mask(ctrl, port, cpu);
++ iowrite8(GENMASK(7, 0), irq_cpu_mask);
++ }
++ }
+ }
+
+ return 0;
diff --git a/target/linux/realtek/patches-5.15/317-gpio-realtek-otto-switch-to-32-bit-I-O.patch b/target/linux/realtek/patches-5.15/317-gpio-realtek-otto-switch-to-32-bit-I-O.patch
new file mode 100644
index 0000000000..9c043b7126
--- /dev/null
+++ b/target/linux/realtek/patches-5.15/317-gpio-realtek-otto-switch-to-32-bit-I-O.patch
@@ -0,0 +1,373 @@
+From ee0175b3b44288c74d5292c2a9c2c154f6c0317e Mon Sep 17 00:00:00 2001
+From: Sander Vanheule <sander@svanheule.net>
+Date: Sun, 7 Aug 2022 21:21:15 +0200
+Subject: [PATCH] gpio: realtek-otto: switch to 32-bit I/O
+
+By using 16-bit I/O on the GPIO peripheral, which is apparently not safe
+on MIPS, the IMR can end up containing garbage. This then results in
+interrupt triggers for lines that don't have an interrupt handler
+associated. The irq_desc lookup fails, and the ISR will not be cleared,
+keeping the CPU busy until reboot, or until another IMR operation
+restores the correct value. This situation appears to happen very
+rarely, for < 0.5% of IMR writes.
+
+Instead of using 8-bit or 16-bit I/O operations on the 32-bit memory
+mapped peripheral registers, switch to using 32-bit I/O only, operating
+on the entire bank for all single bit line settings. For 2-bit line
+settings, with 16-bit port values, stick to manual (un)packing.
+
+This issue has been seen on RTL8382M (HPE 1920-16G), RTL8391M (Netgear
+GS728TP v2), and RTL8393M (D-Link DGS-1210-52 F3, Zyxel GS1900-48).
+
+Reported-by: Luiz Angelo Daros de Luca <luizluca@gmail.com> # DGS-1210-52
+Reported-by: Birger Koblitz <mail@birger-koblitz.de> # GS728TP
+Reported-by: Jan Hoffmann <jan@3e8.eu> # 1920-16G
+Fixes: 0d82fb1127fb ("gpio: Add Realtek Otto GPIO support")
+Signed-off-by: Sander Vanheule <sander@svanheule.net>
+Cc: Paul Cercueil <paul@crapouillou.net>
+Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
+Signed-off-by: Bartosz Golaszewski <brgl@bgdev.pl>
+
+Update patch for missing upstream changes:
+ - commit a01a40e33499 ("gpio: realtek-otto: Make the irqchip immutable")
+ - commit dbd1c54fc820 ("gpio: Bulk conversion to generic_handle_domain_irq()")
+Signed-off-by: Sander Vanheule <sander@svanheule.net>
+
+---
+ drivers/gpio/gpio-realtek-otto.c | 166 ++++++++++++++++---------------
+ 1 file changed, 85 insertions(+), 81 deletions(-)
+
+--- a/drivers/gpio/gpio-realtek-otto.c
++++ b/drivers/gpio/gpio-realtek-otto.c
+@@ -46,10 +46,20 @@
+ * @lock: Lock for accessing the IRQ registers and values
+ * @intr_mask: Mask for interrupts lines
+ * @intr_type: Interrupt type selection
++ * @bank_read: Read a bank setting as a single 32-bit value
++ * @bank_write: Write a bank setting as a single 32-bit value
++ * @imr_line_pos: Bit shift of an IRQ line's IMR value.
++ *
++ * The DIR, DATA, and ISR registers consist of four 8-bit port values, packed
++ * into a single 32-bit register. Use @bank_read (@bank_write) to get (assign)
++ * a value from (to) these registers. The IMR register consists of four 16-bit
++ * port values, packed into two 32-bit registers. Use @imr_line_pos to get the
++ * bit shift of the 2-bit field for a line's IMR settings. Shifts larger than
++ * 32 overflow into the second register.
+ *
+ * Because the interrupt mask register (IMR) combines the function of IRQ type
+ * selection and masking, two extra values are stored. @intr_mask is used to
+- * mask/unmask the interrupts for a GPIO port, and @intr_type is used to store
++ * mask/unmask the interrupts for a GPIO line, and @intr_type is used to store
+ * the selected interrupt types. The logical AND of these values is written to
+ * IMR on changes.
+ */
+@@ -59,10 +69,11 @@ struct realtek_gpio_ctrl {
+ void __iomem *cpumask_base;
+ struct cpumask cpu_irq_maskable;
+ raw_spinlock_t lock;
+- u16 intr_mask[REALTEK_GPIO_PORTS_PER_BANK];
+- u16 intr_type[REALTEK_GPIO_PORTS_PER_BANK];
+- unsigned int (*port_offset_u8)(unsigned int port);
+- unsigned int (*port_offset_u16)(unsigned int port);
++ u8 intr_mask[REALTEK_GPIO_MAX];
++ u8 intr_type[REALTEK_GPIO_MAX];
++ u32 (*bank_read)(void __iomem *reg);
++ void (*bank_write)(void __iomem *reg, u32 value);
++ unsigned int (*line_imr_pos)(unsigned int line);
+ };
+
+ /* Expand with more flags as devices with other quirks are added */
+@@ -101,14 +112,22 @@ static struct realtek_gpio_ctrl *irq_dat
+ * port. The two interrupt mask registers store two bits per GPIO, so use u16
+ * values.
+ */
+-static unsigned int realtek_gpio_port_offset_u8(unsigned int port)
++static u32 realtek_gpio_bank_read_swapped(void __iomem *reg)
++{
++ return ioread32be(reg);
++}
++
++static void realtek_gpio_bank_write_swapped(void __iomem *reg, u32 value)
+ {
+- return port;
++ iowrite32be(value, reg);
+ }
+
+-static unsigned int realtek_gpio_port_offset_u16(unsigned int port)
++static unsigned int realtek_gpio_line_imr_pos_swapped(unsigned int line)
+ {
+- return 2 * port;
++ unsigned int port_pin = line % 8;
++ unsigned int port = line / 8;
++
++ return 2 * (8 * (port ^ 1) + port_pin);
+ }
+
+ /*
+@@ -119,64 +138,65 @@ static unsigned int realtek_gpio_port_of
+ * per GPIO, so use u16 values. The first register contains ports 1 and 0, the
+ * second ports 3 and 2.
+ */
+-static unsigned int realtek_gpio_port_offset_u8_rev(unsigned int port)
++static u32 realtek_gpio_bank_read(void __iomem *reg)
+ {
+- return 3 - port;
++ return ioread32(reg);
+ }
+
+-static unsigned int realtek_gpio_port_offset_u16_rev(unsigned int port)
++static void realtek_gpio_bank_write(void __iomem *reg, u32 value)
+ {
+- return 2 * (port ^ 1);
++ iowrite32(value, reg);
+ }
+
+-static void realtek_gpio_write_imr(struct realtek_gpio_ctrl *ctrl,
+- unsigned int port, u16 irq_type, u16 irq_mask)
++static unsigned int realtek_gpio_line_imr_pos(unsigned int line)
+ {
+- iowrite16(irq_type & irq_mask,
+- ctrl->base + REALTEK_GPIO_REG_IMR + ctrl->port_offset_u16(port));
++ return 2 * line;
+ }
+
+-static void realtek_gpio_clear_isr(struct realtek_gpio_ctrl *ctrl,
+- unsigned int port, u8 mask)
++static void realtek_gpio_clear_isr(struct realtek_gpio_ctrl *ctrl, u32 mask)
+ {
+- iowrite8(mask, ctrl->base + REALTEK_GPIO_REG_ISR + ctrl->port_offset_u8(port));
++ ctrl->bank_write(ctrl->base + REALTEK_GPIO_REG_ISR, mask);
+ }
+
+-static u8 realtek_gpio_read_isr(struct realtek_gpio_ctrl *ctrl, unsigned int port)
++static u32 realtek_gpio_read_isr(struct realtek_gpio_ctrl *ctrl)
+ {
+- return ioread8(ctrl->base + REALTEK_GPIO_REG_ISR + ctrl->port_offset_u8(port));
++ return ctrl->bank_read(ctrl->base + REALTEK_GPIO_REG_ISR);
+ }
+
+-/* Set the rising and falling edge mask bits for a GPIO port pin */
+-static u16 realtek_gpio_imr_bits(unsigned int pin, u16 value)
++/* Set the rising and falling edge mask bits for a GPIO pin */
++static void realtek_gpio_update_line_imr(struct realtek_gpio_ctrl *ctrl, unsigned int line)
+ {
+- return (value & REALTEK_GPIO_IMR_LINE_MASK) << 2 * pin;
++ void __iomem *reg = ctrl->base + REALTEK_GPIO_REG_IMR;
++ unsigned int line_shift = ctrl->line_imr_pos(line);
++ unsigned int shift = line_shift % 32;
++ u32 irq_type = ctrl->intr_type[line];
++ u32 irq_mask = ctrl->intr_mask[line];
++ u32 reg_val;
++
++ reg += 4 * (line_shift / 32);
++ reg_val = ioread32(reg);
++ reg_val &= ~(REALTEK_GPIO_IMR_LINE_MASK << shift);
++ reg_val |= (irq_type & irq_mask & REALTEK_GPIO_IMR_LINE_MASK) << shift;
++ iowrite32(reg_val, reg);
+ }
+
+ static void realtek_gpio_irq_ack(struct irq_data *data)
+ {
+ struct realtek_gpio_ctrl *ctrl = irq_data_to_ctrl(data);
+ irq_hw_number_t line = irqd_to_hwirq(data);
+- unsigned int port = line / 8;
+- unsigned int port_pin = line % 8;
+
+- realtek_gpio_clear_isr(ctrl, port, BIT(port_pin));
++ realtek_gpio_clear_isr(ctrl, BIT(line));
+ }
+
+ static void realtek_gpio_irq_unmask(struct irq_data *data)
+ {
+ struct realtek_gpio_ctrl *ctrl = irq_data_to_ctrl(data);
+ unsigned int line = irqd_to_hwirq(data);
+- unsigned int port = line / 8;
+- unsigned int port_pin = line % 8;
+ unsigned long flags;
+- u16 m;
+
+ raw_spin_lock_irqsave(&ctrl->lock, flags);
+- m = ctrl->intr_mask[port];
+- m |= realtek_gpio_imr_bits(port_pin, REALTEK_GPIO_IMR_LINE_MASK);
+- ctrl->intr_mask[port] = m;
+- realtek_gpio_write_imr(ctrl, port, ctrl->intr_type[port], m);
++ ctrl->intr_mask[line] = REALTEK_GPIO_IMR_LINE_MASK;
++ realtek_gpio_update_line_imr(ctrl, line);
+ raw_spin_unlock_irqrestore(&ctrl->lock, flags);
+ }
+
+@@ -184,16 +204,11 @@ static void realtek_gpio_irq_mask(struct
+ {
+ struct realtek_gpio_ctrl *ctrl = irq_data_to_ctrl(data);
+ unsigned int line = irqd_to_hwirq(data);
+- unsigned int port = line / 8;
+- unsigned int port_pin = line % 8;
+ unsigned long flags;
+- u16 m;
+
+ raw_spin_lock_irqsave(&ctrl->lock, flags);
+- m = ctrl->intr_mask[port];
+- m &= ~realtek_gpio_imr_bits(port_pin, REALTEK_GPIO_IMR_LINE_MASK);
+- ctrl->intr_mask[port] = m;
+- realtek_gpio_write_imr(ctrl, port, ctrl->intr_type[port], m);
++ ctrl->intr_mask[line] = 0;
++ realtek_gpio_update_line_imr(ctrl, line);
+ raw_spin_unlock_irqrestore(&ctrl->lock, flags);
+ }
+
+@@ -201,10 +216,8 @@ static int realtek_gpio_irq_set_type(str
+ {
+ struct realtek_gpio_ctrl *ctrl = irq_data_to_ctrl(data);
+ unsigned int line = irqd_to_hwirq(data);
+- unsigned int port = line / 8;
+- unsigned int port_pin = line % 8;
+ unsigned long flags;
+- u16 type, t;
++ u8 type;
+
+ switch (flow_type & IRQ_TYPE_SENSE_MASK) {
+ case IRQ_TYPE_EDGE_FALLING:
+@@ -223,11 +236,8 @@ static int realtek_gpio_irq_set_type(str
+ irq_set_handler_locked(data, handle_edge_irq);
+
+ raw_spin_lock_irqsave(&ctrl->lock, flags);
+- t = ctrl->intr_type[port];
+- t &= ~realtek_gpio_imr_bits(port_pin, REALTEK_GPIO_IMR_LINE_MASK);
+- t |= realtek_gpio_imr_bits(port_pin, type);
+- ctrl->intr_type[port] = t;
+- realtek_gpio_write_imr(ctrl, port, t, ctrl->intr_mask[port]);
++ ctrl->intr_type[line] = type;
++ realtek_gpio_update_line_imr(ctrl, line);
+ raw_spin_unlock_irqrestore(&ctrl->lock, flags);
+
+ return 0;
+@@ -238,31 +248,24 @@ static void realtek_gpio_irq_handler(str
+ struct gpio_chip *gc = irq_desc_get_handler_data(desc);
+ struct realtek_gpio_ctrl *ctrl = gpiochip_get_data(gc);
+ struct irq_chip *irq_chip = irq_desc_get_chip(desc);
+- unsigned int lines_done;
+- unsigned int port_pin_count;
+ unsigned int irq;
+ unsigned long status;
+ int offset;
+
+ chained_irq_enter(irq_chip, desc);
+
+- for (lines_done = 0; lines_done < gc->ngpio; lines_done += 8) {
+- status = realtek_gpio_read_isr(ctrl, lines_done / 8);
+- port_pin_count = min(gc->ngpio - lines_done, 8U);
+- for_each_set_bit(offset, &status, port_pin_count) {
+- irq = irq_find_mapping(gc->irq.domain, offset + lines_done);
+- generic_handle_irq(irq);
+- }
++ status = realtek_gpio_read_isr(ctrl);
++ for_each_set_bit(offset, &status, gc->ngpio) {
++ irq = irq_find_mapping(gc->irq.domain, offset);
++ generic_handle_irq(irq);
+ }
+
+ chained_irq_exit(irq_chip, desc);
+ }
+
+-static inline void __iomem *realtek_gpio_irq_cpu_mask(struct realtek_gpio_ctrl *ctrl,
+- unsigned int port, int cpu)
++static inline void __iomem *realtek_gpio_irq_cpu_mask(struct realtek_gpio_ctrl *ctrl, int cpu)
+ {
+- return ctrl->cpumask_base + ctrl->port_offset_u8(port) +
+- REALTEK_GPIO_PORTS_PER_BANK * cpu;
++ return ctrl->cpumask_base + REALTEK_GPIO_PORTS_PER_BANK * cpu;
+ }
+
+ static int realtek_gpio_irq_set_affinity(struct irq_data *data,
+@@ -270,12 +273,10 @@ static int realtek_gpio_irq_set_affinity
+ {
+ struct realtek_gpio_ctrl *ctrl = irq_data_to_ctrl(data);
+ unsigned int line = irqd_to_hwirq(data);
+- unsigned int port = line / 8;
+- unsigned int port_pin = line % 8;
+ void __iomem *irq_cpu_mask;
+ unsigned long flags;
+ int cpu;
+- u8 v;
++ u32 v;
+
+ if (!ctrl->cpumask_base)
+ return -ENXIO;
+@@ -283,15 +284,15 @@ static int realtek_gpio_irq_set_affinity
+ raw_spin_lock_irqsave(&ctrl->lock, flags);
+
+ for_each_cpu(cpu, &ctrl->cpu_irq_maskable) {
+- irq_cpu_mask = realtek_gpio_irq_cpu_mask(ctrl, port, cpu);
+- v = ioread8(irq_cpu_mask);
++ irq_cpu_mask = realtek_gpio_irq_cpu_mask(ctrl, cpu);
++ v = ctrl->bank_read(irq_cpu_mask);
+
+ if (cpumask_test_cpu(cpu, dest))
+- v |= BIT(port_pin);
++ v |= BIT(line);
+ else
+- v &= ~BIT(port_pin);
++ v &= ~BIT(line);
+
+- iowrite8(v, irq_cpu_mask);
++ ctrl->bank_write(irq_cpu_mask, v);
+ }
+
+ raw_spin_unlock_irqrestore(&ctrl->lock, flags);
+@@ -305,22 +306,23 @@ static int realtek_gpio_irq_init(struct
+ {
+ struct realtek_gpio_ctrl *ctrl = gpiochip_get_data(gc);
+ void __iomem *irq_cpu_mask;
+- unsigned int port;
++ u32 mask_all = GENMASK(gc->ngpio - 1, 0);
++ unsigned int line;
+ int cpu;
+
+- for (port = 0; (port * 8) < gc->ngpio; port++) {
+- realtek_gpio_write_imr(ctrl, port, 0, 0);
+- realtek_gpio_clear_isr(ctrl, port, GENMASK(7, 0));
+-
+- /*
+- * Uniprocessor builds assume a mask always contains one CPU,
+- * so only start the loop if we have at least one maskable CPU.
+- */
+- if(!cpumask_empty(&ctrl->cpu_irq_maskable)) {
+- for_each_cpu(cpu, &ctrl->cpu_irq_maskable) {
+- irq_cpu_mask = realtek_gpio_irq_cpu_mask(ctrl, port, cpu);
+- iowrite8(GENMASK(7, 0), irq_cpu_mask);
+- }
++ for (line = 0; line < gc->ngpio; line++)
++ realtek_gpio_update_line_imr(ctrl, line);
++
++ realtek_gpio_clear_isr(ctrl, mask_all);
++
++ /*
++ * Uniprocessor builds assume a mask always contains one CPU,
++ * so only start the loop if we have at least one maskable CPU.
++ */
++ if(!cpumask_empty(&ctrl->cpu_irq_maskable)) {
++ for_each_cpu(cpu, &ctrl->cpu_irq_maskable) {
++ irq_cpu_mask = realtek_gpio_irq_cpu_mask(ctrl, cpu);
++ ctrl->bank_write(irq_cpu_mask, mask_all);
+ }
+ }
+
+@@ -393,12 +395,14 @@ static int realtek_gpio_probe(struct pla
+
+ if (dev_flags & GPIO_PORTS_REVERSED) {
+ bgpio_flags = 0;
+- ctrl->port_offset_u8 = realtek_gpio_port_offset_u8_rev;
+- ctrl->port_offset_u16 = realtek_gpio_port_offset_u16_rev;
++ ctrl->bank_read = realtek_gpio_bank_read;
++ ctrl->bank_write = realtek_gpio_bank_write;
++ ctrl->line_imr_pos = realtek_gpio_line_imr_pos;
+ } else {
+ bgpio_flags = BGPIOF_BIG_ENDIAN_BYTE_ORDER;
+- ctrl->port_offset_u8 = realtek_gpio_port_offset_u8;
+- ctrl->port_offset_u16 = realtek_gpio_port_offset_u16;
++ ctrl->bank_read = realtek_gpio_bank_read_swapped;
++ ctrl->bank_write = realtek_gpio_bank_write_swapped;
++ ctrl->line_imr_pos = realtek_gpio_line_imr_pos_swapped;
+ }
+
+ err = bgpio_init(&ctrl->gc, dev, 4,
diff --git a/target/linux/realtek/patches-5.15/318-add-rtl83xx-clk-support.patch b/target/linux/realtek/patches-5.15/318-add-rtl83xx-clk-support.patch
new file mode 100644
index 0000000000..94c4230835
--- /dev/null
+++ b/target/linux/realtek/patches-5.15/318-add-rtl83xx-clk-support.patch
@@ -0,0 +1,33 @@
+From 800d5fb3c6a16661932c932bacd660e38d06b727 Mon Sep 17 00:00:00 2001
+From: Markus Stockhausen <markus.stockhausen@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@gmx.de>
+---
+ drivers/clk/Kconfig | 1 +
+ drivers/clk/Makefile | 1 +
+ 2 files changed, 2 insertions(+)
+
+--- a/drivers/clk/Kconfig
++++ b/drivers/clk/Kconfig
+@@ -372,6 +372,7 @@ source "drivers/clk/mediatek/Kconfig"
+ source "drivers/clk/meson/Kconfig"
+ source "drivers/clk/mvebu/Kconfig"
+ source "drivers/clk/qcom/Kconfig"
++source "drivers/clk/realtek/Kconfig"
+ source "drivers/clk/renesas/Kconfig"
+ source "drivers/clk/rockchip/Kconfig"
+ source "drivers/clk/samsung/Kconfig"
+--- a/drivers/clk/Makefile
++++ b/drivers/clk/Makefile
+@@ -100,6 +100,7 @@ obj-$(CONFIG_COMMON_CLK_NXP) += nxp/
+ obj-$(CONFIG_MACH_PISTACHIO) += pistachio/
+ obj-$(CONFIG_COMMON_CLK_PXA) += pxa/
+ obj-$(CONFIG_COMMON_CLK_QCOM) += qcom/
++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-5.15/319-irqchip-irq-realtek-rtl-fix-VPE-affinity.patch b/target/linux/realtek/patches-5.15/319-irqchip-irq-realtek-rtl-fix-VPE-affinity.patch
new file mode 100644
index 0000000000..bb95e7103a
--- /dev/null
+++ b/target/linux/realtek/patches-5.15/319-irqchip-irq-realtek-rtl-fix-VPE-affinity.patch
@@ -0,0 +1,159 @@
+From 2cd00b51470a30198b048a5fca48a04db77e29cc Mon Sep 17 00:00:00 2001
+From: INAGAKI Hiroshi <musashino.open@gmail.com>
+Date: Fri, 21 May 2021 23:16:37 +0900
+Subject: [PATCH] realtek: backport irq-realtek-rtl driver from 5.12 to 5.10
+
+This patch backports "irq-realtek-rtl" driver to Kernel 5.10 from 5.12.
+"MACH_REALTEK_RTL" is used as a platform name in upstream, but "RTL838X"
+is used in OpenWrt, so update the dependency by the additional patch.
+
+Submitted-by: INAGAKI Hiroshi <musashino.open@gmail.com>
+---
+ drivers/irqchip/irq-realtek-rtl.c | 38 +++++++++++------
+ 1 files changed, 58 insertions(+), 20 deletions(-)
+
+--- a/drivers/irqchip/irq-realtek-rtl.c
++++ b/drivers/irqchip/irq-realtek-rtl.c
+@@ -28,6 +28,7 @@ static DEFINE_RAW_SPINLOCK(irq_lock);
+
+ #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;
+
+@@ -41,11 +42,29 @@ struct realtek_ictl_output {
+ };
+
+ /*
+- * 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)
+
+@@ -65,19 +84,33 @@ static inline void write_irr(void __iome
+ 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);
+
+- for_each_cpu(cpu, &realtek_ictl_cpu_configurable) {
+- value = readl(REG(RTL_ICTL_GIMR, cpu));
+- value |= BIT(i->hwirq);
+- writel(value, REG(RTL_ICTL_GIMR, cpu));
+- }
++ for_each_cpu(cpu, &realtek_ictl_cpu_configurable)
++ enable_gimr(i->hwirq, cpu);
+
+ raw_spin_unlock_irqrestore(&irq_lock, flags);
+ }
+@@ -85,16 +118,12 @@ 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);
+
+- for_each_cpu(cpu, &realtek_ictl_cpu_configurable) {
+- value = readl(REG(RTL_ICTL_GIMR, cpu));
+- value &= ~BIT(i->hwirq);
+- writel(value, REG(RTL_ICTL_GIMR, cpu));
+- }
++ for_each_cpu(cpu, &realtek_ictl_cpu_configurable)
++ disable_gimr(i->hwirq, cpu);
+
+ raw_spin_unlock_irqrestore(&irq_lock, flags);
+ }
+@@ -116,11 +145,17 @@ static int __maybe_unused realtek_ictl_i
+ cpumask_and(&cpu_enable, &cpu_configure, dest);
+ cpumask_andnot(&cpu_disable, &cpu_configure, dest);
+
+- for_each_cpu(cpu, &cpu_disable)
++ 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)
++ 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);
+
+@@ -149,6 +184,7 @@ static int intc_map(struct irq_domain *d
+
+ 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);
+
+@@ -279,9 +315,11 @@ static int __init realtek_rtl_of_init(st
+ cpumask_set_cpu(cpu, &realtek_ictl_cpu_configurable);
+
+ /* Disable all cascaded interrupts and clear routing */
+- writel(0, REG(RTL_ICTL_GIMR, cpu));
+- for (soc_irq = 0; soc_irq < RTL_ICTL_NUM_INPUTS; soc_irq++)
++ 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);
++ }
+ }
+ }
+
diff --git a/target/linux/realtek/patches-5.15/700-net-dsa-add-support-for-rtl838x-switch.patch b/target/linux/realtek/patches-5.15/700-net-dsa-add-support-for-rtl838x-switch.patch
new file mode 100644
index 0000000000..f97ea94d9b
--- /dev/null
+++ b/target/linux/realtek/patches-5.15/700-net-dsa-add-support-for-rtl838x-switch.patch
@@ -0,0 +1,40 @@
+From 2b88563ee5aafd9571d965b7f2093a0f58d98a31 Mon Sep 17 00:00:00 2001
+From: John Crispin <john@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@biot.com>
+Submitted-by: Birger Koblitz <mail@birger-koblitz.de>
+Submitted-by: Sander Vanheule <sander@svanheule.net>
+Submitted-by: Bjørn Mork <bjorn@mork.no>
+Submitted-by: John Crispin <john@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
+@@ -68,6 +68,8 @@ config NET_DSA_QCA8K
+ This enables support for the Qualcomm Atheros QCA8K Ethernet
+ switch chips.
+
++source "drivers/net/dsa/rtl83xx/Kconfig"
++
+ config NET_DSA_REALTEK_SMI
+ tristate "Realtek SMI Ethernet switch family support"
+ depends on NET_DSA
+--- a/drivers/net/dsa/Makefile
++++ b/drivers/net/dsa/Makefile
+@@ -23,3 +23,4 @@ obj-y += mv88e6xxx/
+ obj-y += ocelot/
+ obj-y += qca/
+ obj-y += sja1105/
++obj-y += rtl83xx/
diff --git a/target/linux/realtek/patches-5.15/701-net-dsa-add-rtl838x-support-for-tag-trailer.patch b/target/linux/realtek/patches-5.15/701-net-dsa-add-rtl838x-support-for-tag-trailer.patch
new file mode 100644
index 0000000000..27299f17de
--- /dev/null
+++ b/target/linux/realtek/patches-5.15/701-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@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@biot.com>
+Submitted-by: Birger Koblitz <mail@birger-koblitz.de>
+Submitted-by: Sander Vanheule <sander@svanheule.net>
+Submitted-by: Bjørn Mork <bjorn@mork.no>
+Submitted-by: John Crispin <john@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
+@@ -17,7 +17,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;
+
+@@ -34,12 +39,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-5.15/702-net-dsa-increase-dsa-max-ports-for-rtl838x.patch b/target/linux/realtek/patches-5.15/702-net-dsa-increase-dsa-max-ports-for-rtl838x.patch
new file mode 100644
index 0000000000..63991d373c
--- /dev/null
+++ b/target/linux/realtek/patches-5.15/702-net-dsa-increase-dsa-max-ports-for-rtl838x.patch
@@ -0,0 +1,32 @@
+From 2b88563ee5aafd9571d965b7f2093a0f58d98a31 Mon Sep 17 00:00:00 2001
+From: John Crispin <john@phrozen.org>
+Date: Thu, 26 Nov 2020 12:02:21 +0100
+Subject: net: dsa: Increase max ports 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@biot.com>
+Submitted-by: Birger Koblitz <mail@birger-koblitz.de>
+Submitted-by: Sander Vanheule <sander@svanheule.net>
+Submitted-by: Bjørn Mork <bjorn@mork.no>
+Submitted-by: John Crispin <john@phrozen.org>
+---
+ include/linux/platform_data/dsa.h | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- 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 {
diff --git a/target/linux/realtek/patches-5.15/702-net-ethernet-add-support-for-rtl838x-ethernet.patch b/target/linux/realtek/patches-5.15/702-net-ethernet-add-support-for-rtl838x-ethernet.patch
new file mode 100644
index 0000000000..9f50e8b564
--- /dev/null
+++ b/target/linux/realtek/patches-5.15/702-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@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@biot.com>
+Submitted-by: Birger Koblitz <mail@birger-koblitz.de>
+Submitted-by: Sander Vanheule <sander@svanheule.net>
+Submitted-by: Bjørn Mork <bjorn@mork.no>
+Submitted-by: John Crispin <john@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
+@@ -163,6 +163,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 RTL83XX
++ 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/sfc/Kconfig"
+--- a/drivers/net/ethernet/Makefile
++++ b/drivers/net/ethernet/Makefile
+@@ -75,6 +75,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-5.15/703-include-linux-add-phy-ops-for-rtl838x.patch b/target/linux/realtek/patches-5.15/703-include-linux-add-phy-ops-for-rtl838x.patch
new file mode 100644
index 0000000000..8f68dd4e88
--- /dev/null
+++ b/target/linux/realtek/patches-5.15/703-include-linux-add-phy-ops-for-rtl838x.patch
@@ -0,0 +1,34 @@
+From 2b88563ee5aafd9571d965b7f2093a0f58d98a31 Mon Sep 17 00:00:00 2001
+From: John Crispin <john@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@biot.com>
+Submitted-by: Birger Koblitz <mail@birger-koblitz.de>
+Submitted-by: Sander Vanheule <sander@svanheule.net>
+Submitted-by: Bjørn Mork <bjorn@mork.no>
+Submitted-by: John Crispin <john@phrozen.org>
+---
+ include/linux/phy.h | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -885,6 +885,10 @@ struct phy_driver {
+ int (*get_sqi)(struct phy_device *dev);
+ /** @get_sqi_max: Get the maximum signal quality indication */
+ int (*get_sqi_max)(struct phy_device *dev);
++ int (*get_port)(struct phy_device *dev);
++ int (*set_port)(struct phy_device *dev, int port);
++ 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-5.15/704-drivers-net-phy-eee-support-for-rtl838x.patch b/target/linux/realtek/patches-5.15/704-drivers-net-phy-eee-support-for-rtl838x.patch
new file mode 100644
index 0000000000..c46be91fe0
--- /dev/null
+++ b/target/linux/realtek/patches-5.15/704-drivers-net-phy-eee-support-for-rtl838x.patch
@@ -0,0 +1,61 @@
+From 2b88563ee5aafd9571d965b7f2093a0f58d98a31 Mon Sep 17 00:00:00 2001
+From: John Crispin <john@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@biot.com>
+Submitted-by: Birger Koblitz <mail@birger-koblitz.de>
+Submitted-by: Sander Vanheule <sander@svanheule.net>
+Submitted-by: Bjørn Mork <bjorn@mork.no>
+Submitted-by: John Crispin <john@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
+@@ -1449,6 +1449,11 @@ int phylink_ethtool_ksettings_set(struct
+ * the presence of a PHY, this should not be changed as that
+ * should be determined from the media side advertisement.
+ */
++ if (pl->phydev->drv->get_port && pl->phydev->drv->set_port) {
++ if(pl->phydev->drv->get_port(pl->phydev) != kset->base.port) {
++ pl->phydev->drv->set_port(pl->phydev, kset->base.port);
++ }
++ }
+ return phy_ethtool_ksettings_set(pl->phydev, kset);
+ }
+
+@@ -1750,8 +1755,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;
+ }
+@@ -1768,8 +1776,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-5.15/704-include-linux-add-phy-hsgmii-mode.patch b/target/linux/realtek/patches-5.15/704-include-linux-add-phy-hsgmii-mode.patch
new file mode 100644
index 0000000000..442bff82fa
--- /dev/null
+++ b/target/linux/realtek/patches-5.15/704-include-linux-add-phy-hsgmii-mode.patch
@@ -0,0 +1,33 @@
+From 9d9bf16aa8d966834ac1280f96c37d22552c33d1 Mon Sep 17 00:00:00 2001
+From: Birger Koblitz <git@birger-koblitz.de>
+Date: Wed, 8 Sep 2021 16:13:18 +0200
+Subject: 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 HISGMI
+protocol.
+
+Submitted-by: Birger Koblitz <git@birger-koblitz.de>
+---
+ include/linux/phy.h | 3 +++
+ 1 file changed, 3 insertions(+)
+
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -134,6 +134,7 @@ typedef enum {
+ PHY_INTERFACE_MODE_XGMII,
+ PHY_INTERFACE_MODE_XLGMII,
+ PHY_INTERFACE_MODE_MOCA,
++ PHY_INTERFACE_MODE_HSGMII,
+ PHY_INTERFACE_MODE_QSGMII,
+ PHY_INTERFACE_MODE_TRGMII,
+ PHY_INTERFACE_MODE_100BASEX,
+@@ -201,6 +202,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_QSGMII:
+ return "qsgmii";
+ case PHY_INTERFACE_MODE_TRGMII:
diff --git a/target/linux/realtek/patches-5.15/705-add-rtl-phy.patch b/target/linux/realtek/patches-5.15/705-add-rtl-phy.patch
new file mode 100644
index 0000000000..47041262fc
--- /dev/null
+++ b/target/linux/realtek/patches-5.15/705-add-rtl-phy.patch
@@ -0,0 +1,39 @@
+From 89f71ebb355c624320c2b0ace8ae9488ff53cbeb Mon Sep 17 00:00:00 2001
+From: Birger Koblitz <mail@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@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
+@@ -330,6 +330,12 @@ config REALTEK_PHY
+ help
+ Supports the Realtek 821x PHY.
+
++config REALTEK_SOC_PHY
++ tristate "Realtek SoC PHYs"
++ depends on RTL83XX
++ 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
+@@ -89,6 +89,7 @@ obj-$(CONFIG_NATIONAL_PHY) += national.o
+ obj-$(CONFIG_NXP_TJA11XX_PHY) += nxp-tja11xx.o
+ obj-$(CONFIG_QSEMI_PHY) += qsemi.o
+ obj-$(CONFIG_REALTEK_PHY) += realtek.o
++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-5.15/705-include-linux-phy-increase-phy-address-number-for-rtl839x.patch b/target/linux/realtek/patches-5.15/705-include-linux-phy-increase-phy-address-number-for-rtl839x.patch
new file mode 100644
index 0000000000..bba6fd7182
--- /dev/null
+++ b/target/linux/realtek/patches-5.15/705-include-linux-phy-increase-phy-address-number-for-rtl839x.patch
@@ -0,0 +1,32 @@
+From 2b88563ee5aafd9571d965b7f2093a0f58d98a31 Mon Sep 17 00:00:00 2001
+From: John Crispin <john@phrozen.org>
+Date: Thu, 26 Nov 2020 12:02:21 +0100
+Subject: PHY: Increase max PHY adddress number
+
+* 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@biot.com>
+Submitted-by: Birger Koblitz <mail@birger-koblitz.de>
+Submitted-by: Sander Vanheule <sander@svanheule.net>
+Submitted-by: Bjørn Mork <bjorn@mork.no>
+Submitted-by: John Crispin <john@phrozen.org>
+---
+ include/linux/phy.h | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -233,7 +233,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"
diff --git a/target/linux/realtek/patches-5.15/708-brflood-api.patch b/target/linux/realtek/patches-5.15/708-brflood-api.patch
new file mode 100644
index 0000000000..2716933ae7
--- /dev/null
+++ b/target/linux/realtek/patches-5.15/708-brflood-api.patch
@@ -0,0 +1,162 @@
+From afa3ab54c03d5126b14651f367b38165fab5b3cc Mon Sep 17 00:00:00 2001
+From: Birger Koblitz <git@birger-koblitz.de>
+Date: Tue, 18 Jan 2022 17:18:43 +0100
+Subject: net: brflood API
+
+Adds the DSA API for bridge configuration (flooding, L2 learning,
+and aging) offload as found in Linux 5.12 so that we can implement
+it in our drivver.
+
+Submitted-by: Sebastian Gottschall <s.gottschall@dd-wrt.com>
+Submitted-by: Birger Koblitz <git@birger-koblitz.de>
+---
+ include/net/dsa.h | 6 +++++++--
+ net/dsa/dsa_priv.h | 6 +++---
+ net/dsa/port.c | 28 ++++++++----
+ net/dsa/slave.c | 6 +++---
+ 4 file changed, 29 insertions(+), 13 deletions(-)
+
+--- a/include/net/dsa.h
++++ b/include/net/dsa.h
+@@ -552,8 +552,14 @@ struct dsa_switch_ops {
+ void (*port_stp_state_set)(struct dsa_switch *ds, int port,
+ u8 state);
+ void (*port_fast_age)(struct dsa_switch *ds, int port);
+- int (*port_egress_floods)(struct dsa_switch *ds, int port,
+- bool unicast, bool multicast);
++ int (*port_pre_bridge_flags)(struct dsa_switch *ds, int port,
++ unsigned long flags,
++ struct netlink_ext_ack *extack);
++ int (*port_bridge_flags)(struct dsa_switch *ds, int port,
++ unsigned long flags,
++ struct netlink_ext_ack *extack);
++ int (*port_set_mrouter)(struct dsa_switch *ds, int port, bool mrouter,
++ struct netlink_ext_ack *extack);
+
+ /*
+ * VLAN support
+--- a/net/dsa/dsa_priv.h
++++ b/net/dsa/dsa_priv.h
+@@ -167,11 +167,11 @@ int dsa_port_mdb_add(const struct dsa_po
+ int dsa_port_mdb_del(const struct dsa_port *dp,
+ const struct switchdev_obj_port_mdb *mdb);
+ int dsa_port_pre_bridge_flags(const struct dsa_port *dp, unsigned long flags,
+- struct switchdev_trans *trans);
++ struct switchdev_trans *trans, struct netlink_ext_ack *extack);
+ int dsa_port_bridge_flags(const struct dsa_port *dp, unsigned long flags,
+- struct switchdev_trans *trans);
++ struct switchdev_trans *trans, struct netlink_ext_ack *extack);
+ int dsa_port_mrouter(struct dsa_port *dp, bool mrouter,
+- struct switchdev_trans *trans);
++ struct switchdev_trans *trans, struct netlink_ext_ack *extack);
+ int dsa_port_vlan_add(struct dsa_port *dp,
+ const struct switchdev_obj_port_vlan *vlan,
+ struct switchdev_trans *trans);
+--- a/net/dsa/port.c
++++ b/net/dsa/port.c
+@@ -145,7 +145,7 @@ int dsa_port_bridge_join(struct dsa_port
+ int err;
+
+ /* Set the flooding mode before joining the port in the switch */
+- err = dsa_port_bridge_flags(dp, BR_FLOOD | BR_MCAST_FLOOD, NULL);
++ err = dsa_port_bridge_flags(dp, BR_FLOOD | BR_MCAST_FLOOD, NULL, NULL);
+ if (err)
+ return err;
+
+@@ -158,7 +158,7 @@ int dsa_port_bridge_join(struct dsa_port
+
+ /* The bridging is rolled back on error */
+ if (err) {
+- dsa_port_bridge_flags(dp, 0, NULL);
++ dsa_port_bridge_flags(dp, 0, NULL, NULL);
+ dp->bridge_dev = NULL;
+ }
+
+@@ -185,7 +185,7 @@ void dsa_port_bridge_leave(struct dsa_po
+ pr_err("DSA: failed to notify DSA_NOTIFIER_BRIDGE_LEAVE\n");
+
+ /* Port is leaving the bridge, disable flooding */
+- dsa_port_bridge_flags(dp, 0, NULL);
++ dsa_port_bridge_flags(dp, 0, NULL, NULL);
+
+ /* Port left the bridge, put in BR_STATE_DISABLED by the bridge layer,
+ * so allow it to be in BR_STATE_FORWARDING to be kept functional
+@@ -333,44 +333,44 @@ int dsa_port_ageing_time(struct dsa_port
+ }
+
+ int dsa_port_pre_bridge_flags(const struct dsa_port *dp, unsigned long flags,
+- struct switchdev_trans *trans)
++ struct switchdev_trans *trans, struct netlink_ext_ack *extack)
+ {
+ struct dsa_switch *ds = dp->ds;
+
+- if (!ds->ops->port_egress_floods ||
+- (flags & ~(BR_FLOOD | BR_MCAST_FLOOD)))
++ if (!ds->ops->port_pre_bridge_flags)
+ return -EINVAL;
+
+- return 0;
++ return ds->ops->port_pre_bridge_flags(ds, dp->index, flags, extack);
+ }
+
+ int dsa_port_bridge_flags(const struct dsa_port *dp, unsigned long flags,
+- struct switchdev_trans *trans)
++ struct switchdev_trans *trans, struct netlink_ext_ack *extack)
+ {
+ struct dsa_switch *ds = dp->ds;
+- int port = dp->index;
+- int err = 0;
+
+ if (switchdev_trans_ph_prepare(trans))
+ return 0;
+
+- if (ds->ops->port_egress_floods)
+- err = ds->ops->port_egress_floods(ds, port, flags & BR_FLOOD,
+- flags & BR_MCAST_FLOOD);
++ if (!ds->ops->port_bridge_flags)
++ return -EINVAL;
++
++ return ds->ops->port_bridge_flags(ds, dp->index, flags, extack);
+
+- return err;
+ }
+
+ int dsa_port_mrouter(struct dsa_port *dp, bool mrouter,
+- struct switchdev_trans *trans)
++ struct switchdev_trans *trans,
++ struct netlink_ext_ack *extack)
+ {
+ struct dsa_switch *ds = dp->ds;
+- int port = dp->index;
+
+ if (switchdev_trans_ph_prepare(trans))
+- return ds->ops->port_egress_floods ? 0 : -EOPNOTSUPP;
++ return ds->ops->port_set_mrouter ? 0 : -EOPNOTSUPP;
++
++ if (!ds->ops->port_set_mrouter)
++ return -EOPNOTSUPP;
+
+- return ds->ops->port_egress_floods(ds, port, true, mrouter);
++ return ds->ops->port_set_mrouter(ds, dp->index, mrouter, extack);
+ }
+
+ int dsa_port_mtu_change(struct dsa_port *dp, int new_mtu,
+--- a/net/dsa/slave.c
++++ b/net/dsa/slave.c
+@@ -290,13 +290,13 @@ static int dsa_slave_port_attr_set(struc
+ break;
+ case SWITCHDEV_ATTR_ID_PORT_PRE_BRIDGE_FLAGS:
+ ret = dsa_port_pre_bridge_flags(dp, attr->u.brport_flags,
+- trans);
++ trans, NULL);
+ break;
+ case SWITCHDEV_ATTR_ID_PORT_BRIDGE_FLAGS:
+- ret = dsa_port_bridge_flags(dp, attr->u.brport_flags, trans);
++ ret = dsa_port_bridge_flags(dp, attr->u.brport_flags, trans, NULL);
+ break;
+ case SWITCHDEV_ATTR_ID_BRIDGE_MROUTER:
+- ret = dsa_port_mrouter(dp->cpu_dp, attr->u.mrouter, trans);
++ ret = dsa_port_mrouter(dp->cpu_dp, attr->u.mrouter, trans, NULL);
+ break;
+ default:
+ ret = -EOPNOTSUPP;
diff --git a/target/linux/realtek/patches-5.15/709-lag-offloading.patch b/target/linux/realtek/patches-5.15/709-lag-offloading.patch
new file mode 100644
index 0000000000..f84687ff4a
--- /dev/null
+++ b/target/linux/realtek/patches-5.15/709-lag-offloading.patch
@@ -0,0 +1,781 @@
+From afa3ab54c03d5126b14651f367b38165fab5b3cc Mon Sep 17 00:00:00 2001
+From: Birger Koblitz <git@birger-koblitz.de>
+Date: Tue, 18 Jan 2022 17:18:43 +0100
+Subject: [PATCH] realtek: Backport bridge configuration for DSA
+
+Adds the DSA API for bridge configuration (flooding, L2 learning,
+and aging) offload as found in Linux 5.12 so that we can implement
+it in our drivver.
+
+Submitted-by: Sebastian Gottschall <s.gottschall@dd-wrt.com>
+Submitted-by: Birger Koblitz <git@birger-koblitz.de>
+---
+ drivers/net/bonding/bond_main.c | 2 ++
+ include/net/dsa.h | 79 ++++++++++++++++-
+ net/dsa/dsa2.c | 88 +++++++++++++++++++
+ net/dsa/dsa_priv.h | 74 ++++++++++++++
+ net/dsa/port.c | 92 ++++++++++++++++++++
+ net/dsa/slave.c | 88 ++++++++++++++++---
+ net/dsa/switch.c | 49 ++++++++++
+ net/sda/tag_dsa.c | 13 +++++-
+ 8 file changed, 460 insertions(+), 25 deletions(-)
+
+--- a/drivers/net/bonding/bond_main.c
++++ b/drivers/net/bonding/bond_main.c
+@@ -2045,6 +2045,8 @@ int bond_enslave(struct net_device *bond
+ goto err_unregister;
+ }
+
++ bond_lower_state_changed(new_slave);
++
+ res = bond_sysfs_slave_add(new_slave);
+ if (res) {
+ slave_dbg(bond_dev, slave_dev, "Error %d calling bond_sysfs_slave_add\n", res);
+--- a/include/net/dsa.h
++++ b/include/net/dsa.h
+@@ -149,8 +149,41 @@ struct dsa_switch_tree {
+
+ /* List of DSA links composing the routing table */
+ struct list_head rtable;
++
++ /* Maps offloaded LAG netdevs to a zero-based linear ID for
++ * drivers that need it.
++ */
++ struct net_device **lags;
++ unsigned int lags_len;
+ };
+
++#define dsa_lags_foreach_id(_id, _dst) \
++ for ((_id) = 0; (_id) < (_dst)->lags_len; (_id)++) \
++ if ((_dst)->lags[(_id)])
++
++#define dsa_lag_foreach_port(_dp, _dst, _lag) \
++ list_for_each_entry((_dp), &(_dst)->ports, list) \
++ if ((_dp)->lag_dev == (_lag))
++
++static inline struct net_device *dsa_lag_dev(struct dsa_switch_tree *dst,
++ unsigned int id)
++{
++ return dst->lags[id];
++}
++
++static inline int dsa_lag_id(struct dsa_switch_tree *dst,
++ struct net_device *lag)
++{
++ unsigned int id;
++
++ dsa_lags_foreach_id(id, dst) {
++ if (dsa_lag_dev(dst, id) == lag)
++ return id;
++ }
++
++ return -ENODEV;
++}
++
+ /* TC matchall action types */
+ enum dsa_port_mall_action_type {
+ DSA_PORT_MALL_MIRROR,
+@@ -220,6 +253,8 @@ struct dsa_port {
+ bool devlink_port_setup;
+ struct phylink *pl;
+ struct phylink_config pl_config;
++ struct net_device *lag_dev;
++ bool lag_tx_enabled;
+
+ struct list_head list;
+
+@@ -340,6 +375,14 @@ struct dsa_switch {
+ */
+ bool mtu_enforcement_ingress;
+
++ /* Drivers that benefit from having an ID associated with each
++ * offloaded LAG should set this to the maximum number of
++ * supported IDs. DSA will then maintain a mapping of _at
++ * least_ these many IDs, accessible to drivers via
++ * dsa_lag_id().
++ */
++ unsigned int num_lag_ids;
++
+ size_t num_ports;
+ };
+
+@@ -432,6 +475,18 @@ static inline bool dsa_port_is_vlan_filt
+ return dp->vlan_filtering;
+ }
+
++static inline
++struct net_device *dsa_port_to_bridge_port(const struct dsa_port *dp)
++{
++ if (!dp->bridge_dev)
++ return NULL;
++
++ if (dp->lag_dev)
++ return dp->lag_dev;
++
++ return dp->slave;
++}
++
+ typedef int dsa_fdb_dump_cb_t(const unsigned char *addr, u16 vid,
+ bool is_static, void *data);
+ struct dsa_switch_ops {
+@@ -629,6 +684,13 @@ struct dsa_switch_ops {
+ void (*crosschip_bridge_leave)(struct dsa_switch *ds, int tree_index,
+ int sw_index, int port,
+ struct net_device *br);
++ int (*crosschip_lag_change)(struct dsa_switch *ds, int sw_index,
++ int port);
++ int (*crosschip_lag_join)(struct dsa_switch *ds, int sw_index,
++ int port, struct net_device *lag,
++ struct netdev_lag_upper_info *info);
++ int (*crosschip_lag_leave)(struct dsa_switch *ds, int sw_index,
++ int port, struct net_device *lag);
+
+ /*
+ * PTP functionality
+@@ -660,6 +722,16 @@ struct dsa_switch_ops {
+ int (*port_change_mtu)(struct dsa_switch *ds, int port,
+ int new_mtu);
+ int (*port_max_mtu)(struct dsa_switch *ds, int port);
++
++ /*
++ * LAG integration
++ */
++ int (*port_lag_change)(struct dsa_switch *ds, int port);
++ int (*port_lag_join)(struct dsa_switch *ds, int port,
++ struct net_device *lag,
++ struct netdev_lag_upper_info *info);
++ int (*port_lag_leave)(struct dsa_switch *ds, int port,
++ struct net_device *lag);
+ };
+
+ #define DSA_DEVLINK_PARAM_DRIVER(_id, _name, _type, _cmodes) \
+--- a/net/dsa/dsa.c
++++ b/net/dsa/dsa.c
+@@ -220,11 +220,21 @@ static int dsa_switch_rcv(struct sk_buff
+ }
+
+ skb = nskb;
+- p = netdev_priv(skb->dev);
+ skb_push(skb, ETH_HLEN);
+ skb->pkt_type = PACKET_HOST;
+ skb->protocol = eth_type_trans(skb, skb->dev);
+
++ if (unlikely(!dsa_slave_dev_check(skb->dev))) {
++ /* Packet is to be injected directly on an upper
++ * device, e.g. a team/bond, so skip all DSA-port
++ * specific actions.
++ */
++ netif_rx(skb);
++ return 0;
++ }
++
++ p = netdev_priv(skb->dev);
++
+ if (unlikely(cpu_dp->ds->untag_bridge_pvid)) {
+ nskb = dsa_untag_bridge_pvid(skb);
+ if (!nskb) {
+--- a/net/dsa/dsa2.c
++++ b/net/dsa/dsa2.c
+@@ -21,6 +21,65 @@
+ static DEFINE_MUTEX(dsa2_mutex);
+ LIST_HEAD(dsa_tree_list);
+
++/**
++ * dsa_lag_map() - Map LAG netdev to a linear LAG ID
++ * @dst: Tree in which to record the mapping.
++ * @lag: Netdev that is to be mapped to an ID.
++ *
++ * dsa_lag_id/dsa_lag_dev can then be used to translate between the
++ * two spaces. The size of the mapping space is determined by the
++ * driver by setting ds->num_lag_ids. It is perfectly legal to leave
++ * it unset if it is not needed, in which case these functions become
++ * no-ops.
++ */
++void dsa_lag_map(struct dsa_switch_tree *dst, struct net_device *lag)
++{
++ unsigned int id;
++
++ if (dsa_lag_id(dst, lag) >= 0)
++ /* Already mapped */
++ return;
++
++ for (id = 0; id < dst->lags_len; id++) {
++ if (!dsa_lag_dev(dst, id)) {
++ dst->lags[id] = lag;
++ return;
++ }
++ }
++
++ /* No IDs left, which is OK. Some drivers do not need it. The
++ * ones that do, e.g. mv88e6xxx, will discover that dsa_lag_id
++ * returns an error for this device when joining the LAG. The
++ * driver can then return -EOPNOTSUPP back to DSA, which will
++ * fall back to a software LAG.
++ */
++}
++
++/**
++ * dsa_lag_unmap() - Remove a LAG ID mapping
++ * @dst: Tree in which the mapping is recorded.
++ * @lag: Netdev that was mapped.
++ *
++ * As there may be multiple users of the mapping, it is only removed
++ * if there are no other references to it.
++ */
++void dsa_lag_unmap(struct dsa_switch_tree *dst, struct net_device *lag)
++{
++ struct dsa_port *dp;
++ unsigned int id;
++
++ dsa_lag_foreach_port(dp, dst, lag)
++ /* There are remaining users of this mapping */
++ return;
++
++ dsa_lags_foreach_id(id, dst) {
++ if (dsa_lag_dev(dst, id) == lag) {
++ dst->lags[id] = NULL;
++ break;
++ }
++ }
++}
++
+ struct dsa_switch *dsa_switch_find(int tree_index, int sw_index)
+ {
+ struct dsa_switch_tree *dst;
+@@ -597,6 +656,32 @@ static void dsa_tree_teardown_master(str
+ dsa_master_teardown(dp->master);
+ }
+
++static int dsa_tree_setup_lags(struct dsa_switch_tree *dst)
++{
++ unsigned int len = 0;
++ struct dsa_port *dp;
++
++ list_for_each_entry(dp, &dst->ports, list) {
++ if (dp->ds->num_lag_ids > len)
++ len = dp->ds->num_lag_ids;
++ }
++
++ if (!len)
++ return 0;
++
++ dst->lags = kcalloc(len, sizeof(*dst->lags), GFP_KERNEL);
++ if (!dst->lags)
++ return -ENOMEM;
++
++ dst->lags_len = len;
++ return 0;
++}
++
++static void dsa_tree_teardown_lags(struct dsa_switch_tree *dst)
++{
++ kfree(dst->lags);
++}
++
+ static int dsa_tree_setup(struct dsa_switch_tree *dst)
+ {
+ bool complete;
+@@ -624,12 +709,18 @@ static int dsa_tree_setup(struct dsa_swi
+ if (err)
+ goto teardown_switches;
+
++ err = dsa_tree_setup_lags(dst);
++ if (err)
++ goto teardown_master;
++
+ dst->setup = true;
+
+ pr_info("DSA: tree %d setup\n", dst->index);
+
+ return 0;
+
++teardown_master:
++ dsa_tree_teardown_master(dst);
+ teardown_switches:
+ dsa_tree_teardown_switches(dst);
+ teardown_default_cpu:
+@@ -645,6 +736,8 @@ static void dsa_tree_teardown(struct dsa
+ if (!dst->setup)
+ return;
+
++ dsa_tree_teardown_lags(dst);
++
+ dsa_tree_teardown_master(dst);
+
+ dsa_tree_teardown_switches(dst);
+--- a/net/dsa/dsa_priv.h
++++ b/net/dsa/dsa_priv.h
+@@ -20,6 +20,9 @@ enum {
+ DSA_NOTIFIER_BRIDGE_LEAVE,
+ DSA_NOTIFIER_FDB_ADD,
+ DSA_NOTIFIER_FDB_DEL,
++ DSA_NOTIFIER_LAG_CHANGE,
++ DSA_NOTIFIER_LAG_JOIN,
++ DSA_NOTIFIER_LAG_LEAVE,
+ DSA_NOTIFIER_MDB_ADD,
+ DSA_NOTIFIER_MDB_DEL,
+ DSA_NOTIFIER_VLAN_ADD,
+@@ -57,6 +60,15 @@ struct dsa_notifier_mdb_info {
+ int port;
+ };
+
++/* DSA_NOTIFIER_LAG_* */
++struct dsa_notifier_lag_info {
++ struct net_device *lag;
++ int sw_index;
++ int port;
++
++ struct netdev_lag_upper_info *info;
++};
++
+ /* DSA_NOTIFIER_VLAN_* */
+ struct dsa_notifier_vlan_info {
+ const struct switchdev_obj_port_vlan *vlan;
+@@ -149,6 +161,11 @@ void dsa_port_disable_rt(struct dsa_port
+ void dsa_port_disable(struct dsa_port *dp);
+ int dsa_port_bridge_join(struct dsa_port *dp, struct net_device *br);
+ void dsa_port_bridge_leave(struct dsa_port *dp, struct net_device *br);
++int dsa_port_lag_change(struct dsa_port *dp,
++ struct netdev_lag_lower_state_info *linfo);
++int dsa_port_lag_join(struct dsa_port *dp, struct net_device *lag_dev,
++ struct netdev_lag_upper_info *uinfo);
++void dsa_port_lag_leave(struct dsa_port *dp, struct net_device *lag_dev);
+ int dsa_port_vlan_filtering(struct dsa_port *dp, bool vlan_filtering,
+ struct switchdev_trans *trans);
+ bool dsa_port_skip_vlan_configuration(struct dsa_port *dp);
+@@ -181,6 +198,71 @@ int dsa_port_link_register_of(struct dsa
+ void dsa_port_link_unregister_of(struct dsa_port *dp);
+ extern const struct phylink_mac_ops dsa_port_phylink_mac_ops;
+
++static inline bool dsa_port_offloads_netdev(struct dsa_port *dp,
++ struct net_device *dev)
++{
++ /* Switchdev offloading can be configured on: */
++
++ if (dev == dp->slave)
++ /* DSA ports directly connected to a bridge, and event
++ * was emitted for the ports themselves.
++ */
++ return true;
++
++ if (dp->bridge_dev == dev)
++ /* DSA ports connected to a bridge, and event was emitted
++ * for the bridge.
++ */
++ return true;
++
++ if (dp->lag_dev == dev)
++ /* DSA ports connected to a bridge via a LAG */
++ return true;
++
++ return false;
++}
++
++static inline bool dsa_port_offloads_bridge_port(struct dsa_port *dp,
++ struct net_device *dev)
++{
++ return dsa_port_to_bridge_port(dp) == dev;
++}
++
++static inline bool dsa_port_offloads_bridge(struct dsa_port *dp,
++ struct net_device *bridge_dev)
++{
++ /* DSA ports connected to a bridge, and event was emitted
++ * for the bridge.
++ */
++ return dp->bridge_dev == bridge_dev;
++}
++
++/* Returns true if any port of this tree offloads the given net_device */
++static inline bool dsa_tree_offloads_bridge_port(struct dsa_switch_tree *dst,
++ struct net_device *dev)
++{
++ struct dsa_port *dp;
++
++ list_for_each_entry(dp, &dst->ports, list)
++ if (dsa_port_offloads_bridge_port(dp, dev))
++ return true;
++
++ return false;
++}
++
++/* Returns true if any port of this tree offloads the given net_device */
++static inline bool dsa_tree_offloads_netdev(struct dsa_switch_tree *dst,
++ struct net_device *dev)
++{
++ struct dsa_port *dp;
++
++ list_for_each_entry(dp, &dst->ports, list)
++ if (dsa_port_offloads_netdev(dp, dev))
++ return true;
++
++ return false;
++}
++
+ /* slave.c */
+ extern const struct dsa_device_ops notag_netdev_ops;
+ void dsa_slave_mii_bus_init(struct dsa_switch *ds);
+@@ -285,6 +367,9 @@ int dsa_switch_register_notifier(struct
+ void dsa_switch_unregister_notifier(struct dsa_switch *ds);
+
+ /* dsa2.c */
++void dsa_lag_map(struct dsa_switch_tree *dst, struct net_device *lag);
++void dsa_lag_unmap(struct dsa_switch_tree *dst, struct net_device *lag);
++
+ extern struct list_head dsa_tree_list;
+
+ #endif
+--- a/net/dsa/port.c
++++ b/net/dsa/port.c
+@@ -193,6 +193,99 @@ void dsa_port_bridge_leave(struct dsa_po
+ dsa_port_set_state_now(dp, BR_STATE_FORWARDING);
+ }
+
++int dsa_port_lag_change(struct dsa_port *dp,
++ struct netdev_lag_lower_state_info *linfo)
++{
++ struct dsa_notifier_lag_info info = {
++ .sw_index = dp->ds->index,
++ .port = dp->index,
++ };
++ bool tx_enabled;
++
++ if (!dp->lag_dev)
++ return 0;
++
++ /* On statically configured aggregates (e.g. loadbalance
++ * without LACP) ports will always be tx_enabled, even if the
++ * link is down. Thus we require both link_up and tx_enabled
++ * in order to include it in the tx set.
++ */
++ tx_enabled = linfo->link_up && linfo->tx_enabled;
++
++ if (tx_enabled == dp->lag_tx_enabled)
++ return 0;
++
++ dp->lag_tx_enabled = tx_enabled;
++
++ return dsa_port_notify(dp, DSA_NOTIFIER_LAG_CHANGE, &info);
++}
++
++int dsa_port_lag_join(struct dsa_port *dp, struct net_device *lag,
++ struct netdev_lag_upper_info *uinfo)
++{
++ struct dsa_notifier_lag_info info = {
++ .sw_index = dp->ds->index,
++ .port = dp->index,
++ .lag = lag,
++ .info = uinfo,
++ };
++ struct net_device *bridge_dev;
++ int err;
++
++ dsa_lag_map(dp->ds->dst, lag);
++ dp->lag_dev = lag;
++
++ err = dsa_port_notify(dp, DSA_NOTIFIER_LAG_JOIN, &info);
++ if (err)
++ goto err_lag_join;
++
++ bridge_dev = netdev_master_upper_dev_get(lag);
++ if (!bridge_dev || !netif_is_bridge_master(bridge_dev))
++ return 0;
++
++ err = dsa_port_bridge_join(dp, bridge_dev);
++ if (err)
++ goto err_bridge_join;
++
++ return 0;
++
++err_bridge_join:
++ dsa_port_notify(dp, DSA_NOTIFIER_LAG_LEAVE, &info);
++err_lag_join:
++ dp->lag_dev = NULL;
++ dsa_lag_unmap(dp->ds->dst, lag);
++ return err;
++}
++
++void dsa_port_lag_leave(struct dsa_port *dp, struct net_device *lag)
++{
++ struct dsa_notifier_lag_info info = {
++ .sw_index = dp->ds->index,
++ .port = dp->index,
++ .lag = lag,
++ };
++ int err;
++
++ if (!dp->lag_dev)
++ return;
++
++ /* Port might have been part of a LAG that in turn was
++ * attached to a bridge.
++ */
++ if (dp->bridge_dev)
++ dsa_port_bridge_leave(dp, dp->bridge_dev);
++
++ dp->lag_tx_enabled = false;
++ dp->lag_dev = NULL;
++
++ err = dsa_port_notify(dp, DSA_NOTIFIER_LAG_LEAVE, &info);
++ if (err)
++ pr_err("DSA: failed to notify DSA_NOTIFIER_LAG_LEAVE: %d\n",
++ err);
++
++ dsa_lag_unmap(dp->ds->dst, lag);
++}
++
+ /* Must be called under rcu_read_lock() */
+ static bool dsa_port_can_apply_vlan_filtering(struct dsa_port *dp,
+ bool vlan_filtering)
+--- a/net/dsa/slave.c
++++ b/net/dsa/slave.c
+@@ -337,9 +337,6 @@ static int dsa_slave_vlan_add(struct net
+ struct switchdev_obj_port_vlan vlan;
+ int vid, err;
+
+- if (obj->orig_dev != dev)
+- return -EOPNOTSUPP;
+-
+ if (dsa_port_skip_vlan_configuration(dp))
+ return 0;
+
+@@ -394,11 +391,13 @@ static int dsa_slave_port_obj_add(struct
+
+ switch (obj->id) {
+ case SWITCHDEV_OBJ_ID_PORT_MDB:
+- if (obj->orig_dev != dev)
++ if (!dsa_port_offloads_bridge_port(dp, obj->orig_dev))
+ return -EOPNOTSUPP;
+ err = dsa_port_mdb_add(dp, SWITCHDEV_OBJ_PORT_MDB(obj), trans);
+ break;
+ case SWITCHDEV_OBJ_ID_HOST_MDB:
++ if (!dsa_port_offloads_bridge(dp, obj->orig_dev))
++ return -EOPNOTSUPP;
+ /* DSA can directly translate this to a normal MDB add,
+ * but on the CPU port.
+ */
+@@ -406,6 +405,9 @@ static int dsa_slave_port_obj_add(struct
+ trans);
+ break;
+ case SWITCHDEV_OBJ_ID_PORT_VLAN:
++ if (!dsa_port_offloads_bridge_port(dp, obj->orig_dev))
++ return -EOPNOTSUPP;
++
+ err = dsa_slave_vlan_add(dev, obj, trans);
+ break;
+ default:
+@@ -424,9 +426,6 @@ static int dsa_slave_vlan_del(struct net
+ struct switchdev_obj_port_vlan *vlan;
+ int vid, err;
+
+- if (obj->orig_dev != dev)
+- return -EOPNOTSUPP;
+-
+ if (dsa_port_skip_vlan_configuration(dp))
+ return 0;
+
+@@ -453,17 +452,22 @@ static int dsa_slave_port_obj_del(struct
+
+ switch (obj->id) {
+ case SWITCHDEV_OBJ_ID_PORT_MDB:
+- if (obj->orig_dev != dev)
++ if (!dsa_port_offloads_bridge_port(dp, obj->orig_dev))
+ return -EOPNOTSUPP;
+ err = dsa_port_mdb_del(dp, SWITCHDEV_OBJ_PORT_MDB(obj));
+ break;
+ case SWITCHDEV_OBJ_ID_HOST_MDB:
++ if (!dsa_port_offloads_bridge(dp, obj->orig_dev))
++ return -EOPNOTSUPP;
+ /* DSA can directly translate this to a normal MDB add,
+ * but on the CPU port.
+ */
+ err = dsa_port_mdb_del(dp->cpu_dp, SWITCHDEV_OBJ_PORT_MDB(obj));
+ break;
+ case SWITCHDEV_OBJ_ID_PORT_VLAN:
++ if (!dsa_port_offloads_bridge_port(dp, obj->orig_dev))
++ return -EOPNOTSUPP;
++
+ err = dsa_slave_vlan_del(dev, obj);
+ break;
+ default:
+@@ -1993,6 +1997,46 @@ static int dsa_slave_changeupper(struct
+ dsa_port_bridge_leave(dp, info->upper_dev);
+ err = NOTIFY_OK;
+ }
++ } else if (netif_is_lag_master(info->upper_dev)) {
++ if (info->linking) {
++ err = dsa_port_lag_join(dp, info->upper_dev,
++ info->upper_info);
++ if (err == -EOPNOTSUPP) {
++ NL_SET_ERR_MSG_MOD(info->info.extack,
++ "Offloading not supported");
++ err = 0;
++ }
++ err = notifier_from_errno(err);
++ } else {
++ dsa_port_lag_leave(dp, info->upper_dev);
++ err = NOTIFY_OK;
++ }
++ }
++
++ return err;
++}
++
++static int
++dsa_slave_lag_changeupper(struct net_device *dev,
++ struct netdev_notifier_changeupper_info *info)
++{
++ struct net_device *lower;
++ struct list_head *iter;
++ int err = NOTIFY_DONE;
++ struct dsa_port *dp;
++
++ netdev_for_each_lower_dev(dev, lower, iter) {
++ if (!dsa_slave_dev_check(lower))
++ continue;
++
++ dp = dsa_slave_to_port(lower);
++ if (!dp->lag_dev)
++ /* Software LAG */
++ continue;
++
++ err = dsa_slave_changeupper(lower, info);
++ if (notifier_to_errno(err))
++ break;
+ }
+
+ return err;
+@@ -2078,10 +2122,26 @@ static int dsa_slave_netdevice_event(str
+ break;
+ }
+ case NETDEV_CHANGEUPPER:
++ if (dsa_slave_dev_check(dev))
++ return dsa_slave_changeupper(dev, ptr);
++
++ if (netif_is_lag_master(dev))
++ return dsa_slave_lag_changeupper(dev, ptr);
++
++ break;
++ case NETDEV_CHANGELOWERSTATE: {
++ struct netdev_notifier_changelowerstate_info *info = ptr;
++ struct dsa_port *dp;
++ int err;
++
+ if (!dsa_slave_dev_check(dev))
+- return NOTIFY_DONE;
++ break;
+
+- return dsa_slave_changeupper(dev, ptr);
++ dp = dsa_slave_to_port(dev);
++
++ err = dsa_port_lag_change(dp, info->lower_state_info);
++ return notifier_from_errno(err);
++ }
+ }
+
+ return NOTIFY_DONE;
+@@ -2229,6 +2289,15 @@ static int dsa_slave_switchdev_event(str
+ if (!fdb_info->added_by_user &&
+ !dp->ds->assisted_learning_on_cpu_port)
+ return NOTIFY_DONE;
++
++ /* When the bridge learns an address on an offloaded
++ * LAG we don't want to send traffic to the CPU, the
++ * other ports bridged with the LAG should be able to
++ * autonomously forward towards it.
++ */
++ if (dsa_tree_offloads_netdev(dp->ds->dst, dev))
++ return NOTIFY_DONE;
++
+ }
+
+ if (!dp->ds->ops->port_fdb_add || !dp->ds->ops->port_fdb_del)
+--- a/net/dsa/switch.c
++++ b/net/dsa/switch.c
+@@ -193,6 +193,47 @@ static int dsa_switch_fdb_del(struct dsa
+ return ds->ops->port_fdb_del(ds, port, info->addr, info->vid);
+ }
+
++static int dsa_switch_lag_change(struct dsa_switch *ds,
++ struct dsa_notifier_lag_info *info)
++{
++ if (ds->index == info->sw_index && ds->ops->port_lag_change)
++ return ds->ops->port_lag_change(ds, info->port);
++
++ if (ds->index != info->sw_index && ds->ops->crosschip_lag_change)
++ return ds->ops->crosschip_lag_change(ds, info->sw_index,
++ info->port);
++
++ return 0;
++}
++
++static int dsa_switch_lag_join(struct dsa_switch *ds,
++ struct dsa_notifier_lag_info *info)
++{
++ if (ds->index == info->sw_index && ds->ops->port_lag_join)
++ return ds->ops->port_lag_join(ds, info->port, info->lag,
++ info->info);
++
++ if (ds->index != info->sw_index && ds->ops->crosschip_lag_join)
++ return ds->ops->crosschip_lag_join(ds, info->sw_index,
++ info->port, info->lag,
++ info->info);
++
++ return -EOPNOTSUPP;
++}
++
++static int dsa_switch_lag_leave(struct dsa_switch *ds,
++ struct dsa_notifier_lag_info *info)
++{
++ if (ds->index == info->sw_index && ds->ops->port_lag_leave)
++ return ds->ops->port_lag_leave(ds, info->port, info->lag);
++
++ if (ds->index != info->sw_index && ds->ops->crosschip_lag_leave)
++ return ds->ops->crosschip_lag_leave(ds, info->sw_index,
++ info->port, info->lag);
++
++ return -EOPNOTSUPP;
++}
++
+ static bool dsa_switch_mdb_match(struct dsa_switch *ds, int port,
+ struct dsa_notifier_mdb_info *info)
+ {
+@@ -340,6 +381,15 @@ static int dsa_switch_event(struct notif
+ case DSA_NOTIFIER_FDB_DEL:
+ err = dsa_switch_fdb_del(ds, info);
+ break;
++ case DSA_NOTIFIER_LAG_CHANGE:
++ err = dsa_switch_lag_change(ds, info);
++ break;
++ case DSA_NOTIFIER_LAG_JOIN:
++ err = dsa_switch_lag_join(ds, info);
++ break;
++ case DSA_NOTIFIER_LAG_LEAVE:
++ err = dsa_switch_lag_leave(ds, info);
++ break;
+ case DSA_NOTIFIER_MDB_ADD:
+ err = dsa_switch_mdb_add(ds, info);
+ break;
+--- a/net/dsa/tag_dsa.c
++++ b/net/dsa/tag_dsa.c
+@@ -82,7 +82,19 @@ static struct sk_buff *dsa_rcv(struct sk
+ source_device = dsa_header[0] & 0x1f;
+ source_port = (dsa_header[1] >> 3) & 0x1f;
+
+- skb->dev = dsa_master_find_slave(dev, source_device, source_port);
++ if (trunk) {
++ struct dsa_port *cpu_dp = dev->dsa_ptr;
++
++ /* The exact source port is not available in the tag,
++ * so we inject the frame directly on the upper
++ * team/bond.
++ */
++ skb->dev = dsa_lag_dev(cpu_dp->dst, source_port);
++ } else {
++ skb->dev = dsa_master_find_slave(dev, source_device,
++ source_port);
++ }
++
+ if (!skb->dev)
+ return NULL;
+
diff --git a/target/linux/realtek/patches-5.15/710-net-phy-sfp-re-probe-modules-on-DEV_UP-event.patch b/target/linux/realtek/patches-5.15/710-net-phy-sfp-re-probe-modules-on-DEV_UP-event.patch
new file mode 100644
index 0000000000..248abee39b
--- /dev/null
+++ b/target/linux/realtek/patches-5.15/710-net-phy-sfp-re-probe-modules-on-DEV_UP-event.patch
@@ -0,0 +1,26 @@
+From a381ac0aa281fdb0b41a39d8a2bc08fd88f6db92 Mon Sep 17 00:00:00 2001
+From: Antoine Tenart <antoine.tenart@bootlin.com>
+Date: Tue, 25 Feb 2020 16:32:37 +0100
+Subject: [PATCH 1/3] net: phy: sfp: re-probe modules on DEV_UP event
+
+Signed-off-by: Antoine Tenart <antoine.tenart@bootlin.com>
+---
+ drivers/net/phy/sfp.c | 7 +++++++
+ 1 file changed, 7 insertions(+)
+
+--- a/drivers/net/phy/sfp.c
++++ b/drivers/net/phy/sfp.c
+@@ -1966,6 +1966,13 @@ static void sfp_sm_module(struct sfp *sf
+ return;
+ }
+
++ /* Re-probe the SFP modules when an interface is brought up, as the MAC
++ * do not report its link status (This means Phylink wouldn't be
++ * triggered if the PHY had a link before a MAC is brought up).
++ */
++ if (event == SFP_E_DEV_UP && sfp->sm_mod_state == SFP_MOD_PRESENT)
++ sfp_sm_mod_next(sfp, SFP_MOD_PROBE, T_SERIAL);
++
+ switch (sfp->sm_mod_state) {
+ default:
+ if (event == SFP_E_INSERT) {
diff --git a/target/linux/realtek/patches-5.15/711-net-phy-add-an-MDIO-SMBus-library.patch b/target/linux/realtek/patches-5.15/711-net-phy-add-an-MDIO-SMBus-library.patch
new file mode 100644
index 0000000000..5e1053ca6c
--- /dev/null
+++ b/target/linux/realtek/patches-5.15/711-net-phy-add-an-MDIO-SMBus-library.patch
@@ -0,0 +1,168 @@
+From d585c55b9f70cf9e8c66820d7efe7130c683f19e Mon Sep 17 00:00:00 2001
+From: Antoine Tenart <antoine.tenart@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@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
+@@ -40,6 +40,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
+@@ -17,6 +17,7 @@ obj-$(CONFIG_MDIO_MOXART) += mdio-moxar
+ obj-$(CONFIG_MDIO_MSCC_MIIM) += mdio-mscc-miim.o
+ obj-$(CONFIG_MDIO_MVUSB) += mdio-mvusb.o
+ obj-$(CONFIG_MDIO_OCTEON) += mdio-octeon.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,62 @@
++// SPDX-License-Identifier: GPL-2.0-or-later
++/*
++ * MDIO SMBus bridge
++ *
++ * Copyright (C) 2020 Antoine Tenart
++ *
++ * 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>
++
++static int smbus_mii_read(struct mii_bus *mii, int phy_id, int reg)
++{
++ struct i2c_adapter *i2c = mii->priv;
++ union i2c_smbus_data data;
++ int ret;
++
++ ret = i2c_smbus_xfer(i2c, i2c_mii_phy_addr(phy_id), 0, I2C_SMBUS_READ,
++ reg, I2C_SMBUS_BYTE_DATA, &data);
++ if (ret < 0)
++ return 0xff;
++
++ return data.byte;
++}
++
++static int smbus_mii_write(struct mii_bus *mii, int phy_id, int reg, u16 val)
++{
++ struct i2c_adapter *i2c = mii->priv;
++ union i2c_smbus_data data;
++ int ret;
++
++ data.byte = val;
++
++ ret = i2c_smbus_xfer(i2c, i2c_mii_phy_addr(phy_id), 0, I2C_SMBUS_WRITE,
++ reg, I2C_SMBUS_BYTE_DATA, &data);
++ return ret < 0 ? ret : 0;
++}
++
++struct mii_bus *mdio_smbus_alloc(struct device *parent, struct i2c_adapter *i2c)
++{
++ struct mii_bus *mii;
++
++ 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->read = smbus_mii_read;
++ mii->write = smbus_mii_write;
++ mii->priv = i2c;
++
++ 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
+@@ -60,6 +60,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
+@@ -12,5 +12,21 @@ struct i2c_adapter;
+ struct mii_bus;
+
+ struct mii_bus *mdio_i2c_alloc(struct device *parent, struct i2c_adapter *i2c);
++struct mii_bus *mdio_smbus_alloc(struct device *parent, struct i2c_adapter *i2c);
++
++/*
++ * I2C bus addresses 0x50 and 0x51 are normally an EEPROM, which is
++ * 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)
++{
++ return phy_id != 0x10 && phy_id != 0x11;
++}
++
++static unsigned int i2c_mii_phy_addr(int phy_id)
++{
++ return phy_id + 0x40;
++}
+
+ #endif
+--- a/drivers/net/mdio/mdio-i2c.c
++++ b/drivers/net/mdio/mdio-i2c.c
+@@ -13,21 +13,6 @@
+ #include <linux/mdio/mdio-i2c.h>
+ #include <linux/phy.h>
+
+-/*
+- * I2C bus addresses 0x50 and 0x51 are normally an EEPROM, which is
+- * 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)
+-{
+- return phy_id != 0x10 && phy_id != 0x11;
+-}
+-
+-static unsigned int i2c_mii_phy_addr(int phy_id)
+-{
+- return phy_id + 0x40;
+-}
+-
+ static int i2c_mii_read(struct mii_bus *bus, int phy_id, int reg)
+ {
+ struct i2c_adapter *i2c = bus->priv;
diff --git a/target/linux/realtek/patches-5.15/712-net-phy-sfp-add-support-for-SMBus.patch b/target/linux/realtek/patches-5.15/712-net-phy-sfp-add-support-for-SMBus.patch
new file mode 100644
index 0000000000..c4bacc0ea9
--- /dev/null
+++ b/target/linux/realtek/patches-5.15/712-net-phy-sfp-add-support-for-SMBus.patch
@@ -0,0 +1,99 @@
+From 3cb0bde365d913c484d20224367a54a0eac780a7 Mon Sep 17 00:00:00 2001
+From: Antoine Tenart <antoine.tenart@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@bootlin.com>
+---
+ drivers/net/phy/sfp.c | 68 ++++++++++++++++++++++++++++++++++---------
+ 1 file changed, 54 insertions(+), 14 deletions(-)
+
+--- a/drivers/net/phy/sfp.c
++++ b/drivers/net/phy/sfp.c
+@@ -413,32 +413,72 @@ 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;
++
++ bus_addr -= 0x40;
++
++ while (len > 0) {
++ *val = sfp->i2c_mii->read(sfp->i2c_mii, bus_addr, dev_addr);
++
++ val++;
++ 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;
++ u16 val;
++
++ memcpy(&val, buf, len);
++
++ return sfp->i2c_mii->write(sfp->i2c_mii, bus_addr, dev_addr, val);
++}
++
+ static int sfp_i2c_configure(struct sfp *sfp, struct i2c_adapter *i2c)
+ {
+- struct mii_bus *i2c_mii;
++ struct mii_bus *mii;
+ int ret;
+
+- if (!i2c_check_functionality(i2c, I2C_FUNC_I2C))
+- return -EINVAL;
+-
+ sfp->i2c = i2c;
+- sfp->read = sfp_i2c_read;
+- sfp->write = sfp_i2c_write;
+
+- i2c_mii = mdio_i2c_alloc(sfp->dev, i2c);
+- if (IS_ERR(i2c_mii))
+- return PTR_ERR(i2c_mii);
++ if (i2c_check_functionality(i2c, I2C_FUNC_I2C)) {
++ sfp->read = sfp_i2c_read;
++ sfp->write = sfp_i2c_write;
++
++ mii = mdio_i2c_alloc(sfp->dev, i2c);
++ if (IS_ERR(mii))
++ return PTR_ERR(mii);
++
++ mii->name = "SFP I2C Bus";
++ } else if (i2c_check_functionality(i2c, I2C_FUNC_SMBUS_BYTE_DATA)) {
++ sfp->read = sfp_smbus_read;
++ sfp->write = sfp_smbus_write;
++
++ mii = mdio_smbus_alloc(sfp->dev, i2c);
++ if (IS_ERR(mii))
++ return PTR_ERR(mii);
+
+- i2c_mii->name = "SFP I2C Bus";
+- i2c_mii->phy_mask = ~0;
++ mii->name = "SFP SMBus";
++ } else {
++ return -EINVAL;
++ }
+
+- ret = mdiobus_register(i2c_mii);
++ mii->phy_mask = ~0;
++ ret = mdiobus_register(mii);
+ if (ret < 0) {
+- mdiobus_free(i2c_mii);
++ mdiobus_free(mii);
+ return ret;
+ }
+
+- sfp->i2c_mii = i2c_mii;
++ sfp->i2c_mii = mii;
+
+ return 0;
+ }
diff --git a/target/linux/realtek/patches-5.15/713-v5.12-net-dsa-configure-better-brport-flags-when-ports-lea.patch b/target/linux/realtek/patches-5.15/713-v5.12-net-dsa-configure-better-brport-flags-when-ports-lea.patch
new file mode 100644
index 0000000000..a3bfec59ab
--- /dev/null
+++ b/target/linux/realtek/patches-5.15/713-v5.12-net-dsa-configure-better-brport-flags-when-ports-lea.patch
@@ -0,0 +1,148 @@
+From: Vladimir Oltean <vladimir.oltean@nxp.com>
+Date: Fri, 12 Feb 2021 17:15:54 +0200
+Subject: [PATCH] net: dsa: configure better brport flags when ports leave the
+ bridge
+
+Bugfixed version of upstream commit 5e38c15856e9 ("net: dsa: configure
+better brport flags when ports leave the bridge")
+
+For a DSA switch port operating in standalone mode, address learning
+doesn't make much sense since that is a bridge function. In fact,
+address learning even breaks setups such as this one:
+
+ +---------------------------------------------+
+ | |
+ | +-------------------+ |
+ | | br0 | send receive |
+ | +--------+-+--------+ +--------+ +--------+ |
+ | | | | | | | | | |
+ | | swp0 | | swp1 | | swp2 | | swp3 | |
+ | | | | | | | | | |
+ +-+--------+-+--------+-+--------+-+--------+-+
+ | ^ | ^
+ | | | |
+ | +-----------+ |
+ | |
+ +--------------------------------+
+
+because if the switch has a single FDB (can offload a single bridge)
+then source address learning on swp3 can "steal" the source MAC address
+of swp2 from br0's FDB, because learning frames coming from swp2 will be
+done twice: first on the swp1 ingress port, second on the swp3 ingress
+port. So the hardware FDB will become out of sync with the software
+bridge, and when swp2 tries to send one more packet towards swp1, the
+ASIC will attempt to short-circuit the forwarding path and send it
+directly to swp3 (since that's the last port it learned that address on),
+which it obviously can't, because swp3 operates in standalone mode.
+
+So DSA drivers operating in standalone mode should still configure a
+list of bridge port flags even when they are standalone. Currently DSA
+attempts to call dsa_port_bridge_flags with 0, which disables egress
+flooding of unknown unicast and multicast, something which doesn't make
+much sense. For the switches that implement .port_egress_floods - b53
+and mv88e6xxx, it probably doesn't matter too much either, since they
+can possibly inject traffic from the CPU into a standalone port,
+regardless of MAC DA, even if egress flooding is turned off for that
+port, but certainly not all DSA switches can do that - sja1105, for
+example, can't. So it makes sense to use a better common default there,
+such as "flood everything".
+
+It should also be noted that what DSA calls "dsa_port_bridge_flags()"
+is a degenerate name for just calling .port_egress_floods(), since
+nothing else is implemented - not learning, in particular. But disabling
+address learning, something that this driver is also coding up for, will
+be supported by individual drivers once .port_egress_floods is replaced
+with a more generic .port_bridge_flags.
+
+Previous attempts to code up this logic have been in the common bridge
+layer, but as pointed out by Ido Schimmel, there are corner cases that
+are missed when doing that:
+https://patchwork.kernel.org/project/netdevbpf/patch/20210209151936.97382-5-olteanv@gmail.com/
+
+So, at least for now, let's leave DSA in charge of setting port flags
+before and after the bridge join and leave.
+
+Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+[ backport and bugfix: break dsa_port_bridge_flags() out of loop ]
+Signed-off-by: Bjørn Mork <bjorn@mork.no>
+---
+ net/dsa/port.c | 45 ++++++++++++++++++++++++++++++++++++++-------
+ 1 file changed, 38 insertions(+), 7 deletions(-)
+
+--- a/net/dsa/port.c
++++ b/net/dsa/port.c
+@@ -134,6 +134,27 @@ void dsa_port_disable(struct dsa_port *d
+ rtnl_unlock();
+ }
+
++static void dsa_port_change_brport_flags(struct dsa_port *dp,
++ bool bridge_offload)
++{
++ unsigned long mask, flags;
++ int flag, err;
++
++ mask = BR_LEARNING | BR_FLOOD | BR_MCAST_FLOOD | BR_BCAST_FLOOD;
++ if (bridge_offload)
++ flags = mask;
++ else
++ flags = mask & ~BR_LEARNING;
++
++ for_each_set_bit(flag, &mask, 32) {
++ err = dsa_port_pre_bridge_flags(dp, BIT(flag), NULL, NULL);
++ if (err)
++ flags &= ~BIT(flag);
++ }
++
++ dsa_port_bridge_flags(dp, flags, NULL, NULL);
++}
++
+ int dsa_port_bridge_join(struct dsa_port *dp, struct net_device *br)
+ {
+ struct dsa_notifier_bridge_info info = {
+@@ -144,10 +165,10 @@ int dsa_port_bridge_join(struct dsa_port
+ };
+ int err;
+
+- /* Set the flooding mode before joining the port in the switch */
+- err = dsa_port_bridge_flags(dp, BR_FLOOD | BR_MCAST_FLOOD, NULL, NULL);
+- if (err)
+- return err;
++ /* Notify the port driver to set its configurable flags in a way that
++ * matches the initial settings of a bridge port.
++ */
++ dsa_port_change_brport_flags(dp, true);
+
+ /* Here the interface is already bridged. Reflect the current
+ * configuration so that drivers can program their chips accordingly.
+@@ -158,7 +179,7 @@ int dsa_port_bridge_join(struct dsa_port
+
+ /* The bridging is rolled back on error */
+ if (err) {
+- dsa_port_bridge_flags(dp, 0, NULL, NULL);
++ dsa_port_change_brport_flags(dp, false);
+ dp->bridge_dev = NULL;
+ }
+
+@@ -184,8 +205,18 @@ void dsa_port_bridge_leave(struct dsa_po
+ if (err)
+ pr_err("DSA: failed to notify DSA_NOTIFIER_BRIDGE_LEAVE\n");
+
+- /* Port is leaving the bridge, disable flooding */
+- dsa_port_bridge_flags(dp, 0, NULL, NULL);
++ /* Configure the port for standalone mode (no address learning,
++ * flood everything).
++ * The bridge only emits SWITCHDEV_ATTR_ID_PORT_BRIDGE_FLAGS events
++ * when the user requests it through netlink or sysfs, but not
++ * automatically at port join or leave, so we need to handle resetting
++ * the brport flags ourselves. But we even prefer it that way, because
++ * otherwise, some setups might never get the notification they need,
++ * for example, when a port leaves a LAG that offloads the bridge,
++ * it becomes standalone, but as far as the bridge is concerned, no
++ * port ever left.
++ */
++ dsa_port_change_brport_flags(dp, false);
+
+ /* Port left the bridge, put in BR_STATE_DISABLED by the bridge layer,
+ * so allow it to be in BR_STATE_FORWARDING to be kept functional
diff --git a/target/linux/realtek/patches-5.15/800-net-mdio-support-hardware-assisted-indirect-access.patch b/target/linux/realtek/patches-5.15/800-net-mdio-support-hardware-assisted-indirect-access.patch
new file mode 100644
index 0000000000..eeb26fa199
--- /dev/null
+++ b/target/linux/realtek/patches-5.15/800-net-mdio-support-hardware-assisted-indirect-access.patch
@@ -0,0 +1,840 @@
+From 5d84f16b0036b33487b94abef15ad3c224c81ee9 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Thu, 3 Feb 2022 16:38:50 +0000
+Subject: [PATCH] net: mdio: support hardware-assisted indirect access
+
+MDIO controllers found in Switch-SoCs can offload some MDIO operations
+to the hardware:
+ * MMD register access via Clause-22
+ Instead of using multiple operations to access MMD registers via
+ MII register MII_MMD_CTRL and MII_MMD_DATA some controllers
+ allow transparent access to MMD PHY registers.
+
+ * paged MII register access
+ Some PHYs (namely RealTek and Vitesse) use vendor-defined MII
+ register 0x1f for paged access. Some MDIO host controllers support
+ transparent paged access when used with such PHYs.
+
+ * add convenience accessors to fully support paged access also on
+ multi-PHY packages (like the embedded PHYs in RTL83xx):
+ phy_package_read_paged and phy_package_write_paged
+ phy_package_port_read and phy_package_port_write
+ phy_package_port_read_paged and phy_package_port_write_paged
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ drivers/net/phy/mdio_bus.c | 335 ++++++++++++++++++++++++++++++++++++-
+ drivers/net/phy/phy-core.c | 66 +++++++-
+ include/linux/mdio.h | 59 +++++++
+ include/linux/phy.h | 129 ++++++++++++++
+ include/uapi/linux/mii.h | 1 +
+ 5 files changed, 580 insertions(+), 10 deletions(-)
+
+--- a/drivers/net/phy/mdio_bus.c
++++ b/drivers/net/phy/mdio_bus.c
+@@ -734,6 +734,32 @@ out:
+ }
+
+ /**
++ * __mdiobus_select_page - Unlocked version of the mdiobus_select_page function
++ * @bus: the mii_bus struct
++ * @addr: the phy address
++ * @page: register page to select
++ *
++ * Selects a MDIO bus register page. Caller must hold the mdio bus lock.
++ *
++ * NOTE: MUST NOT be called from interrupt context.
++ */
++int __mdiobus_select_page(struct mii_bus *bus, int addr, u16 page)
++{
++ lockdep_assert_held_once(&bus->mdio_lock);
++
++ if (bus->selected_page[addr] == page)
++ return 0;
++
++ bus->selected_page[addr] = page;
++ if (bus->read_paged)
++ return 0;
++
++ return bus->write(bus, addr, MII_MAINPAGE, page);
++
++}
++EXPORT_SYMBOL(__mdiobus_select_page);
++
++/**
+ * __mdiobus_read - Unlocked version of the mdiobus_read function
+ * @bus: the mii_bus struct
+ * @addr: the phy address
+@@ -749,7 +775,10 @@ int __mdiobus_read(struct mii_bus *bus,
+
+ WARN_ON_ONCE(!mutex_is_locked(&bus->mdio_lock));
+
+- retval = bus->read(bus, addr, regnum);
++ if (bus->read_paged)
++ retval = bus->read_paged(bus, addr, bus->selected_page[addr], regnum);
++ else
++ retval = bus->read(bus, addr, regnum);
+
+ trace_mdio_access(bus, 1, addr, regnum, retval, retval);
+ mdiobus_stats_acct(&bus->stats[addr], true, retval);
+@@ -759,6 +788,40 @@ int __mdiobus_read(struct mii_bus *bus,
+ EXPORT_SYMBOL(__mdiobus_read);
+
+ /**
++ * __mdiobus_read_paged - Unlocked version of the mdiobus_read_paged function
++ * @bus: the mii_bus struct
++ * @addr: the phy address
++ * @page: the register page to access
++ * @regnum: register number to read
++ *
++ * Read a MDIO bus register. Caller must hold the mdio bus lock.
++ *
++ * NOTE: MUST NOT be called from interrupt context.
++ */
++int __mdiobus_read_paged(struct mii_bus *bus, int addr, u16 page, u32 regnum)
++{
++ int retval;
++ int oldpage;
++
++ lockdep_assert_held_once(&bus->mdio_lock);
++
++ if (bus->read_paged) {
++ retval = bus->read_paged(bus, addr, page, regnum);
++ } else {
++ oldpage = bus->selected_page[addr];
++ __mdiobus_select_page(bus, addr, page);
++ retval = bus->read(bus, addr, regnum);
++ __mdiobus_select_page(bus, addr, oldpage);
++ }
++
++ trace_mdio_access(bus, 1, addr, regnum, retval, retval);
++ mdiobus_stats_acct(&bus->stats[addr], true, retval);
++
++ return retval;
++}
++EXPORT_SYMBOL(__mdiobus_read_paged);
++
++/**
+ * __mdiobus_write - Unlocked version of the mdiobus_write function
+ * @bus: the mii_bus struct
+ * @addr: the phy address
+@@ -775,7 +838,10 @@ int __mdiobus_write(struct mii_bus *bus,
+
+ WARN_ON_ONCE(!mutex_is_locked(&bus->mdio_lock));
+
+- err = bus->write(bus, addr, regnum, val);
++ if (bus->write_paged)
++ err = bus->write_paged(bus, addr, bus->selected_page[addr], regnum, val);
++ else
++ err = bus->write(bus, addr, regnum, val);
+
+ trace_mdio_access(bus, 0, addr, regnum, val, err);
+ mdiobus_stats_acct(&bus->stats[addr], false, err);
+@@ -785,6 +851,39 @@ int __mdiobus_write(struct mii_bus *bus,
+ EXPORT_SYMBOL(__mdiobus_write);
+
+ /**
++ * __mdiobus_write_paged - Unlocked version of the mdiobus_write_paged function
++ * @bus: the mii_bus struct
++ * @addr: the phy address
++ * @page: the register page to access
++ * @regnum: register number to write
++ * @val: value to write to @regnum
++ *
++ * Write a MDIO bus register. Caller must hold the mdio bus lock.
++ *
++ * NOTE: MUST NOT be called from interrupt context.
++ */
++int __mdiobus_write_paged(struct mii_bus *bus, int addr, u16 page, u32 regnum, u16 val)
++{
++ int err, oldpage;
++
++ lockdep_assert_held_once(&bus->mdio_lock);
++
++ if (bus->write_paged) {
++ err = bus->write_paged(bus, addr, page, regnum, val);
++ } else {
++ oldpage = bus->selected_page[addr];
++ __mdiobus_select_page(bus, addr, page);
++ err = bus->write(bus, addr, regnum, val);
++ __mdiobus_select_page(bus, addr, oldpage);
++ }
++ trace_mdio_access(bus, 0, addr, regnum, val, err);
++ mdiobus_stats_acct(&bus->stats[addr], false, err);
++ return err;
++}
++EXPORT_SYMBOL(__mdiobus_write_paged);
++
++
++/**
+ * __mdiobus_modify_changed - Unlocked version of the mdiobus_modify function
+ * @bus: the mii_bus struct
+ * @addr: the phy address
+@@ -817,6 +916,43 @@ int __mdiobus_modify_changed(struct mii_
+ EXPORT_SYMBOL_GPL(__mdiobus_modify_changed);
+
+ /**
++ * __mdiobus_modify_changed_paged - Unlocked version of the mdiobus_modify_paged function
++ * @bus: the mii_bus struct
++ * @addr: the phy address
++ * @regnum: register number to modify
++ * @mask: bit mask of bits to clear
++ * @set: bit mask of bits to set
++ *
++ * Read, modify, and if any change, write the register value back to the
++ * device. Any error returns a negative number.
++ *
++ * NOTE: MUST NOT be called from interrupt context.
++ */
++int __mdiobus_modify_changed_paged(struct mii_bus *bus, int addr, u32 regnum, u16 page,
++ u16 mask, u16 set)
++{
++ int new, ret, oldpage;
++
++ oldpage = bus->selected_page[addr];
++ __mdiobus_select_page(bus, addr, page);
++
++ ret = __mdiobus_read_paged(bus, addr, page, regnum);
++ if (ret < 0)
++ return ret;
++
++ new = (ret & ~mask) | set;
++ if (new == ret)
++ return 0;
++
++ ret = __mdiobus_write_paged(bus, addr, page, regnum, new);
++
++ __mdiobus_select_page(bus, addr, oldpage);
++
++ return ret < 0 ? ret : 1;
++}
++EXPORT_SYMBOL_GPL(__mdiobus_modify_changed_paged);
++
++/**
+ * mdiobus_read_nested - Nested version of the mdiobus_read function
+ * @bus: the mii_bus struct
+ * @addr: the phy address
+@@ -842,6 +978,79 @@ int mdiobus_read_nested(struct mii_bus *
+ EXPORT_SYMBOL(mdiobus_read_nested);
+
+ /**
++ * mdiobus_select_page_nested - Nested version of the mdiobus_select_page function
++ * @bus: the mii_bus struct
++ * @addr: the phy address
++ * @page: register page to access
++ *
++ * In case of nested MDIO bus access avoid lockdep false positives by
++ * using mutex_lock_nested().
++ *
++ * NOTE: MUST NOT be called from interrupt context,
++ * because the bus read/write functions may wait for an interrupt
++ * to conclude the operation.
++ */
++int mdiobus_select_page_nested(struct mii_bus *bus, int addr, u16 page)
++{
++ int retval;
++
++ mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
++ retval = __mdiobus_select_page(bus, addr, page);
++ mutex_unlock(&bus->mdio_lock);
++
++ return retval;
++}
++EXPORT_SYMBOL(mdiobus_select_page_nested);
++
++/**
++ * mdiobus_read_paged_nested - Nested version of the mdiobus_read_paged function
++ * @bus: the mii_bus struct
++ * @addr: the phy address
++ * @page: register page to access
++ * @regnum: register number to read
++ *
++ * In case of nested MDIO bus access avoid lockdep false positives by
++ * using mutex_lock_nested().
++ *
++ * NOTE: MUST NOT be called from interrupt context,
++ * because the bus read/write functions may wait for an interrupt
++ * to conclude the operation.
++ */
++int mdiobus_read_paged_nested(struct mii_bus *bus, int addr, u16 page, u32 regnum)
++{
++ int retval;
++
++ mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
++ retval = __mdiobus_read_paged(bus, addr, page, regnum);
++ mutex_unlock(&bus->mdio_lock);
++
++ return retval;
++}
++EXPORT_SYMBOL(mdiobus_read_paged_nested);
++
++/**
++ * mdiobus_select_page - Convenience function for setting the MII register page
++ * @bus: the mii_bus struct
++ * @addr: the phy address
++ * @page: the register page to set
++ *
++ * NOTE: MUST NOT be called from interrupt context,
++ * because the bus read/write functions may wait for an interrupt
++ * to conclude the operation.
++ */
++int mdiobus_select_page(struct mii_bus *bus, int addr, u16 page)
++{
++ int retval;
++
++ mutex_lock(&bus->mdio_lock);
++ retval = __mdiobus_select_page(bus, addr, page);
++ mutex_unlock(&bus->mdio_lock);
++
++ return retval;
++}
++EXPORT_SYMBOL(mdiobus_select_page);
++
++/**
+ * mdiobus_read - Convenience function for reading a given MII mgmt register
+ * @bus: the mii_bus struct
+ * @addr: the phy address
+@@ -864,6 +1073,29 @@ int mdiobus_read(struct mii_bus *bus, in
+ EXPORT_SYMBOL(mdiobus_read);
+
+ /**
++ * mdiobus_read_paged - Convenience function for reading a given paged MII mgmt register
++ * @bus: the mii_bus struct
++ * @addr: the phy address
++ * @page: register page to access
++ * @regnum: register number to read
++ *
++ * NOTE: MUST NOT be called from interrupt context,
++ * because the bus read/write functions may wait for an interrupt
++ * to conclude the operation.
++ */
++int mdiobus_read_paged(struct mii_bus *bus, int addr, u16 page, u32 regnum)
++{
++ int retval;
++
++ mutex_lock(&bus->mdio_lock);
++ retval = __mdiobus_read_paged(bus, addr, page, regnum);
++ mutex_unlock(&bus->mdio_lock);
++
++ return retval;
++}
++EXPORT_SYMBOL(mdiobus_read_paged);
++
++/**
+ * mdiobus_write_nested - Nested version of the mdiobus_write function
+ * @bus: the mii_bus struct
+ * @addr: the phy address
+@@ -890,6 +1122,33 @@ int mdiobus_write_nested(struct mii_bus
+ EXPORT_SYMBOL(mdiobus_write_nested);
+
+ /**
++ * mdiobus_write_paged_nested - Nested version of the mdiobus_write_aged function
++ * @bus: the mii_bus struct
++ * @addr: the phy address
++ * @page: the register page to access
++ * @regnum: register number to write
++ * @val: value to write to @regnum
++ *
++ * In case of nested MDIO bus access avoid lockdep false positives by
++ * using mutex_lock_nested().
++ *
++ * NOTE: MUST NOT be called from interrupt context,
++ * because the bus read/write functions may wait for an interrupt
++ * to conclude the operation.
++ */
++int mdiobus_write_paged_nested(struct mii_bus *bus, int addr, u16 page, u32 regnum, u16 val)
++{
++ int err;
++
++ mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
++ err = __mdiobus_write_paged(bus, addr, page, regnum, val);
++ mutex_unlock(&bus->mdio_lock);
++
++ return err;
++}
++EXPORT_SYMBOL(mdiobus_write_paged_nested);
++
++/**
+ * mdiobus_write - Convenience function for writing a given MII mgmt register
+ * @bus: the mii_bus struct
+ * @addr: the phy address
+@@ -913,6 +1172,30 @@ int mdiobus_write(struct mii_bus *bus, i
+ EXPORT_SYMBOL(mdiobus_write);
+
+ /**
++ * mdiobus_write_paged - Convenience function for writing a given paged MII mgmt register
++ * @bus: the mii_bus struct
++ * @addr: the phy address
++ * @page: the register page to access
++ * @regnum: register number to write
++ * @val: value to write to @regnum
++ *
++ * NOTE: MUST NOT be called from interrupt context,
++ * because the bus read/write functions may wait for an interrupt
++ * to conclude the operation.
++ */
++int mdiobus_write_paged(struct mii_bus *bus, int addr, u16 page, u32 regnum, u16 val)
++{
++ int err;
++
++ mutex_lock(&bus->mdio_lock);
++ err = __mdiobus_write_paged(bus, addr, page, regnum, val);
++ mutex_unlock(&bus->mdio_lock);
++
++ return err;
++}
++EXPORT_SYMBOL(mdiobus_write_paged);
++
++/**
+ * mdiobus_modify - Convenience function for modifying a given mdio device
+ * register
+ * @bus: the mii_bus struct
+@@ -934,6 +1217,51 @@ int mdiobus_modify(struct mii_bus *bus,
+ EXPORT_SYMBOL_GPL(mdiobus_modify);
+
+ /**
++ * mdiobus_modify_paged - Convenience function for modifying a given mdio device
++ * register
++ * @bus: the mii_bus struct
++ * @addr: the phy address
++ * @page: the register page to access
++ * @regnum: register number to write
++ * @mask: bit mask of bits to clear
++ * @set: bit mask of bits to set
++ */
++int mdiobus_modify_paged(struct mii_bus *bus, int addr, u16 page, u32 regnum, u16 mask, u16 set)
++{
++ int err;
++
++ mutex_lock(&bus->mdio_lock);
++ err = __mdiobus_modify_changed_paged(bus, addr, page, regnum, mask, set);
++ mutex_unlock(&bus->mdio_lock);
++
++ return err < 0 ? err : 0;
++}
++EXPORT_SYMBOL_GPL(mdiobus_modify_paged);
++
++/**
++ * mdiobus_modify_changed_paged - Convenience function for modifying a given paged
++ * mdio device register and returning if it changed
++ * @bus: the mii_bus struct
++ * @addr: the phy address
++ * @page: the register page to access
++ * @regnum: register number to write
++ * @mask: bit mask of bits to clear
++ * @set: bit mask of bits to set
++ */
++int mdiobus_modify_changed_paged(struct mii_bus *bus, int addr, u16 page, u32 regnum,
++ u16 mask, u16 set)
++{
++ int err;
++
++ mutex_lock(&bus->mdio_lock);
++ err = __mdiobus_modify_changed_paged(bus, addr, page, regnum, mask, set);
++ mutex_unlock(&bus->mdio_lock);
++
++ return err;
++}
++EXPORT_SYMBOL_GPL(mdiobus_modify_changed_paged);
++
++/**
+ * mdio_bus_match - determine if given MDIO driver supports the given
+ * MDIO device
+ * @dev: target MDIO device
+--- a/drivers/net/phy/phy-core.c
++++ b/drivers/net/phy/phy-core.c
+@@ -481,10 +481,16 @@ int __phy_read_mmd(struct phy_device *ph
+ struct mii_bus *bus = phydev->mdio.bus;
+ int phy_addr = phydev->mdio.addr;
+
+- mmd_phy_indirect(bus, phy_addr, devad, regnum);
+-
+- /* Read the content of the MMD's selected register */
+- val = __mdiobus_read(bus, phy_addr, MII_MMD_DATA);
++ if (bus->access_capabilities & MDIOBUS_ACCESS_C22_MMD) {
++ val = __mdiobus_c22_mmd_read(phydev->mdio.bus,
++ phydev->mdio.addr,
++ devad, regnum);
++ } else {
++ mmd_phy_indirect(bus, phy_addr, devad, regnum);
++
++ /* Read the content of the MMD's selected register */
++ val = __mdiobus_read(bus, phy_addr, MII_MMD_DATA);
++ }
+ }
+ return val;
+ }
+@@ -537,12 +543,18 @@ int __phy_write_mmd(struct phy_device *p
+ struct mii_bus *bus = phydev->mdio.bus;
+ int phy_addr = phydev->mdio.addr;
+
+- mmd_phy_indirect(bus, phy_addr, devad, regnum);
++ if (bus->access_capabilities & MDIOBUS_ACCESS_C22_MMD) {
++ ret = __mdiobus_c22_mmd_write(phydev->mdio.bus,
++ phydev->mdio.addr,
++ devad, regnum, val);
++ } else {
++ mmd_phy_indirect(bus, phy_addr, devad, regnum);
+
+- /* Write the data into MMD's selected register */
+- __mdiobus_write(bus, phy_addr, MII_MMD_DATA, val);
++ /* Write the data into MMD's selected register */
++ __mdiobus_write(bus, phy_addr, MII_MMD_DATA, val);
+
+- ret = 0;
++ ret = 0;
++ }
+ }
+ return ret;
+ }
+@@ -748,6 +760,13 @@ EXPORT_SYMBOL_GPL(phy_modify_mmd);
+
+ static int __phy_read_page(struct phy_device *phydev)
+ {
++ if (phydev->drv && phydev->drv->flags & PHY_HAS_REALTEK_PAGES) {
++ struct mii_bus *bus = phydev->mdio.bus;
++ int phy_addr = phydev->mdio.addr;
++
++ return bus->selected_page[phy_addr];
++ }
++
+ if (WARN_ONCE(!phydev->drv->read_page, "read_page callback not available, PHY driver not loaded?\n"))
+ return -EOPNOTSUPP;
+
+@@ -756,6 +775,13 @@ static int __phy_read_page(struct phy_de
+
+ static int __phy_write_page(struct phy_device *phydev, int page)
+ {
++ if (phydev->drv && phydev->drv->flags & PHY_HAS_REALTEK_PAGES) {
++ struct mii_bus *bus = phydev->mdio.bus;
++ int phy_addr = phydev->mdio.addr;
++
++ return __mdiobus_select_page(bus, phy_addr, page);
++ }
++
+ if (WARN_ONCE(!phydev->drv->write_page, "write_page callback not available, PHY driver not loaded?\n"))
+ return -EOPNOTSUPP;
+
+@@ -857,6 +883,18 @@ int phy_read_paged(struct phy_device *ph
+ {
+ int ret = 0, oldpage;
+
++ if (phydev->drv && phydev->drv->flags & PHY_HAS_REALTEK_PAGES) {
++ struct mii_bus *bus = phydev->mdio.bus;
++ int phy_addr = phydev->mdio.addr;
++
++ if (bus->read_paged) {
++ phy_lock_mdio_bus(phydev);
++ ret = bus->read_paged(bus, phy_addr, page, regnum);
++ phy_unlock_mdio_bus(phydev);
++ return ret;
++ }
++ }
++
+ oldpage = phy_select_page(phydev, page);
+ if (oldpage >= 0)
+ ret = __phy_read(phydev, regnum);
+@@ -878,6 +916,18 @@ int phy_write_paged(struct phy_device *p
+ {
+ int ret = 0, oldpage;
+
++ if (phydev->drv && phydev->drv->flags & PHY_HAS_REALTEK_PAGES) {
++ struct mii_bus *bus = phydev->mdio.bus;
++ int phy_addr = phydev->mdio.addr;
++
++ if (bus->write_paged) {
++ phy_lock_mdio_bus(phydev);
++ ret = bus->write_paged(bus, phy_addr, page, regnum, val);
++ phy_unlock_mdio_bus(phydev);
++ return ret;
++ }
++ }
++
+ oldpage = phy_select_page(phydev, page);
+ if (oldpage >= 0)
+ ret = __phy_write(phydev, regnum, val);
+--- a/include/linux/mdio.h
++++ b/include/linux/mdio.h
+@@ -14,6 +14,7 @@
+ * IEEE 802.3ae clause 45 addressing mode used by 10GIGE phy chips.
+ */
+ #define MII_ADDR_C45 (1<<30)
++#define MII_ADDR_C22_MMD (1<<29)
+ #define MII_DEVADDR_C45_SHIFT 16
+ #define MII_DEVADDR_C45_MASK GENMASK(20, 16)
+ #define MII_REGADDR_C45_MASK GENMASK(15, 0)
+@@ -327,11 +328,19 @@ static inline void mii_10gbt_stat_mod_li
+ advertising, lpa & MDIO_AN_10GBT_STAT_LP10G);
+ }
+
++int __mdiobus_select_page(struct mii_bus *bus, int addr, u16 page);
+ int __mdiobus_read(struct mii_bus *bus, int addr, u32 regnum);
+ int __mdiobus_write(struct mii_bus *bus, int addr, u32 regnum, u16 val);
+ int __mdiobus_modify_changed(struct mii_bus *bus, int addr, u32 regnum,
+ u16 mask, u16 set);
+
++int __mdiobus_read_paged(struct mii_bus *bus, int addr, u16 page, u32 regnum);
++int __mdiobus_write_paged(struct mii_bus *bus, int addr, u16 page, u32 regnum, u16 val);
++int __mdiobus_modify_changed_paged(struct mii_bus *bus, int addr, u32 regnum, u16 page,
++ u16 mask, u16 set);
++
++int mdiobus_select_page(struct mii_bus *bus, int addr, u16 page);
++int mdiobus_select_page_nested(struct mii_bus *bus, int addr, u16 page);
+ int mdiobus_read(struct mii_bus *bus, int addr, u32 regnum);
+ int mdiobus_read_nested(struct mii_bus *bus, int addr, u32 regnum);
+ int mdiobus_write(struct mii_bus *bus, int addr, u32 regnum, u16 val);
+@@ -339,11 +348,51 @@ int mdiobus_write_nested(struct mii_bus
+ int mdiobus_modify(struct mii_bus *bus, int addr, u32 regnum, u16 mask,
+ u16 set);
+
++int mdiobus_read_paged(struct mii_bus *bus, int addr, u16 page, u32 regnum);
++int mdiobus_read_nested_paged(struct mii_bus *bus, int addr, u16 page, u32 regnum);
++int mdiobus_write_paged(struct mii_bus *bus, int addr, u16 page, u32 regnum, u16 val);
++int mdiobus_write_nested_paged(struct mii_bus *bus, int addr, u16 page, u32 regnum, u16 val);
++int mdiobus_modify_paged(struct mii_bus *bus, int addr, u16 page, u32 regnum, u16 mask,
++ u16 set);
++int mdiobus_modify_changed_paged(struct mii_bus *bus, int addr, u16 page, u32 regnum,
++ u16 mask, u16 set);
++
++static inline int mdiodev_read_paged(struct mdio_device *mdiodev, u16 page,
++ u32 regnum)
++{
++ return mdiobus_read_paged(mdiodev->bus, mdiodev->addr, page, regnum);
++}
++
++static inline int mdiodev_write_paged(struct mdio_device *mdiodev, u16 page,
++ u32 regnum, u16 val)
++{
++ return mdiobus_write_paged(mdiodev->bus, mdiodev->addr, page, regnum, val);
++}
++
++static inline int mdiodev_modify_paged(struct mdio_device *mdiodev, u16 page,
++ u32 regnum, u16 mask, u16 set)
++{
++ return mdiobus_modify_paged(mdiodev->bus, mdiodev->addr, page, regnum,
++ mask, set);
++}
++
++static inline int mdiodev_modify_changed_paged(struct mdio_device *mdiodev, u16 page,
++ u32 regnum, u16 mask, u16 set)
++{
++ return mdiobus_modify_changed_paged(mdiodev->bus, mdiodev->addr, page, regnum,
++ mask, set);
++}
++
+ static inline u32 mdiobus_c45_addr(int devad, u16 regnum)
+ {
+ return MII_ADDR_C45 | devad << MII_DEVADDR_C45_SHIFT | regnum;
+ }
+
++static inline u32 mdiobus_c22_mmd_addr(int devad, u16 regnum)
++{
++ return MII_ADDR_C22_MMD | devad << MII_DEVADDR_C45_SHIFT | regnum;
++}
++
+ static inline u16 mdiobus_c45_regad(u32 regnum)
+ {
+ return FIELD_GET(MII_REGADDR_C45_MASK, regnum);
+@@ -367,6 +416,19 @@ static inline int __mdiobus_c45_write(st
+ val);
+ }
+
++static inline int __mdiobus_c22_mmd_read(struct mii_bus *bus, int prtad,
++ int devad, u16 regnum)
++{
++ return __mdiobus_read(bus, prtad, mdiobus_c22_mmd_addr(devad, regnum));
++}
++
++static inline int __mdiobus_c22_mmd_write(struct mii_bus *bus, int prtad,
++ int devad, u16 regnum, u16 val)
++{
++ return __mdiobus_write(bus, prtad, mdiobus_c22_mmd_addr(devad, regnum),
++ val);
++}
++
+ static inline int mdiobus_c45_read(struct mii_bus *bus, int prtad, int devad,
+ u16 regnum)
+ {
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -80,6 +80,7 @@ extern const int phy_10gbit_features_arr
+ #define PHY_IS_INTERNAL 0x00000001
+ #define PHY_RST_AFTER_CLK_EN 0x00000002
+ #define PHY_POLL_CABLE_TEST 0x00000004
++#define PHY_HAS_REALTEK_PAGES 0x00000010
+ #define MDIO_DEVICE_IS_PHY 0x80000000
+
+ /**
+@@ -374,6 +375,22 @@ struct mii_bus {
+
+ /** @shared: shared state across different PHYs */
+ struct phy_package_shared *shared[PHY_MAX_ADDR];
++
++ /** @access_capabilities: hardware-assisted access capabilties */
++ enum {
++ MDIOBUS_ACCESS_SOFTWARE_ONLY = 0,
++ MDIOBUS_ACCESS_C22_MMD = 0x1,
++ } access_capabilities;
++
++ /** @read: Perform a read transfer on the bus, offloading page access */
++ int (*read_paged)(struct mii_bus *bus, int addr, u16 page, int regnum);
++ /** @write: Perform a write transfer on the bus, offloading page access */
++ int (*write_paged)(struct mii_bus *bus, int addr, u16 page, int regnum, u16 val);
++ /** currently selected page when page access is offloaded
++ * array should be PHY_MAX_ADDR+1size, but current design of the MDIO driver
++ * uses port addresses as phy addresses and they are up to 6 bit.
++ */
++ u16 selected_page[64];
+ };
+ #define to_mii_bus(d) container_of(d, struct mii_bus, dev)
+
+@@ -1651,6 +1668,66 @@ static inline int __phy_package_read(str
+ return __mdiobus_read(phydev->mdio.bus, shared->addr, regnum);
+ }
+
++static inline int phy_package_read_port(struct phy_device *phydev, u16 port, u32 regnum)
++{
++ struct phy_package_shared *shared = phydev->shared;
++
++ if (!shared)
++ return -EIO;
++
++ return mdiobus_read(phydev->mdio.bus, shared->addr + port, regnum);
++}
++
++static inline int __phy_package_read_port(struct phy_device *phydev, u16 port, u32 regnum)
++{
++ struct phy_package_shared *shared = phydev->shared;
++
++ if (!shared)
++ return -EIO;
++
++ return __mdiobus_read(phydev->mdio.bus, shared->addr + port, regnum);
++}
++
++static inline int phy_package_read_paged(struct phy_device *phydev, u16 page, u32 regnum)
++{
++ struct phy_package_shared *shared = phydev->shared;
++
++ if (!shared)
++ return -EIO;
++
++ return mdiobus_read_paged(phydev->mdio.bus, shared->addr, page, regnum);
++}
++
++static inline int __phy_package_read_paged(struct phy_device *phydev, u16 page, u32 regnum)
++{
++ struct phy_package_shared *shared = phydev->shared;
++
++ if (!shared)
++ return -EIO;
++
++ return __mdiobus_read_paged(phydev->mdio.bus, shared->addr, page, regnum);
++}
++
++static inline int phy_package_port_read_paged(struct phy_device *phydev, u16 port, u16 page, u32 regnum)
++{
++ struct phy_package_shared *shared = phydev->shared;
++
++ if (!shared)
++ return -EIO;
++
++ return mdiobus_read_paged(phydev->mdio.bus, shared->addr + port, page, regnum);
++}
++
++static inline int __phy_package_port_read_paged(struct phy_device *phydev, u16 port, u16 page, u32 regnum)
++{
++ struct phy_package_shared *shared = phydev->shared;
++
++ if (!shared)
++ return -EIO;
++
++ return __mdiobus_read_paged(phydev->mdio.bus, shared->addr + port, page, regnum);
++}
++
+ static inline int phy_package_write(struct phy_device *phydev,
+ u32 regnum, u16 val)
+ {
+@@ -1673,6 +1750,72 @@ static inline int __phy_package_write(st
+ return __mdiobus_write(phydev->mdio.bus, shared->addr, regnum, val);
+ }
+
++static inline int phy_package_port_write(struct phy_device *phydev,
++ u16 port, u32 regnum, u16 val)
++{
++ struct phy_package_shared *shared = phydev->shared;
++
++ if (!shared)
++ return -EIO;
++
++ return mdiobus_write(phydev->mdio.bus, shared->addr + port, regnum, val);
++}
++
++static inline int __phy_package_port_write(struct phy_device *phydev,
++ u16 port, u32 regnum, u16 val)
++{
++ struct phy_package_shared *shared = phydev->shared;
++
++ if (!shared)
++ return -EIO;
++
++ return __mdiobus_write(phydev->mdio.bus, shared->addr + port, regnum, val);
++}
++
++static inline int phy_package_port_write_paged(struct phy_device *phydev,
++ u16 port, u16 page, u32 regnum, u16 val)
++{
++ struct phy_package_shared *shared = phydev->shared;
++
++ if (!shared)
++ return -EIO;
++
++ return mdiobus_write_paged(phydev->mdio.bus, shared->addr + port, page, regnum, val);
++}
++
++static inline int __phy_package_port_write_paged(struct phy_device *phydev,
++ u16 port, u16 page, u32 regnum, u16 val)
++{
++ struct phy_package_shared *shared = phydev->shared;
++
++ if (!shared)
++ return -EIO;
++
++ return __mdiobus_write_paged(phydev->mdio.bus, shared->addr + port, page, regnum, val);
++}
++
++static inline int phy_package_write_paged(struct phy_device *phydev,
++ u16 page, u32 regnum, u16 val)
++{
++ struct phy_package_shared *shared = phydev->shared;
++
++ if (!shared)
++ return -EIO;
++
++ return mdiobus_write_paged(phydev->mdio.bus, shared->addr, page, regnum, val);
++}
++
++static inline int __phy_package_write_paged(struct phy_device *phydev,
++ u16 page, u32 regnum, u16 val)
++{
++ struct phy_package_shared *shared = phydev->shared;
++
++ if (!shared)
++ return -EIO;
++
++ return __mdiobus_write_paged(phydev->mdio.bus, shared->addr, page, regnum, val);
++}
++
+ static inline bool __phy_package_set_once(struct phy_device *phydev,
+ unsigned int b)
+ {
+--- a/include/uapi/linux/mii.h
++++ b/include/uapi/linux/mii.h
+@@ -36,6 +36,7 @@
+ #define MII_RESV2 0x1a /* Reserved... */
+ #define MII_TPISTATUS 0x1b /* TPI status for 10mbps */
+ #define MII_NCONFIG 0x1c /* Network interface config */
++#define MII_MAINPAGE 0x1f /* Page register */
+
+ /* Basic mode control register. */
+ #define BMCR_RESV 0x003f /* Unused... */