Initial commit
Some checks failed
Build Kernel / Build all affected Kernels (push) Has been cancelled
Build all core packages / Build all core packages for selected target (push) Has been cancelled
Build and Push prebuilt tools container / Build and Push all prebuilt containers (push) Has been cancelled
Build Toolchains / Build Toolchains for each target (push) Has been cancelled
Build host tools / Build host tools for linux and macos based systems (push) Has been cancelled
Coverity scan build / Coverity x86/64 build (push) Has been cancelled

This commit is contained in:
domenico
2025-06-24 12:51:15 +02:00
commit 27c9d80f51
10493 changed files with 1885777 additions and 0 deletions

View File

@@ -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
@@ -15903,6 +15903,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
@@ -954,6 +954,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
@@ -171,6 +171,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");

View File

@@ -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;
@@ -355,6 +357,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)
{

View File

@@ -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 */
@@ -250,6 +286,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;
@@ -277,10 +314,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;

View File

@@ -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)
@@ -247,14 +255,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;
@@ -266,6 +321,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[] = {
@@ -290,8 +346,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)
@@ -352,6 +410,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);
}

View File

@@ -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
@@ -335,6 +335,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);

View File

@@ -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
@@ -339,6 +339,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);

View File

@@ -0,0 +1,86 @@
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_NLM_COMMON) += netlog
platform-$(CONFIG_PIC32MZDA) += pic32/
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
@@ -1056,8 +1056,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 NO_EXCEPT_FILL
+ 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 MIPS_EXTERNAL_TIMER
+ select SYS_SUPPORTS_MULTITHREADING
+
+config RTL931X
+ bool "Realtek RTL931X based platforms"
+ depends on RTL930X
+ select MIPS_GIC
+ select COMMON_CLK
+ select CLKSRC_MIPS_GIC
+ select SYS_SUPPORTS_VPE_LOADER
+ select SYS_SUPPORTS_SMP
+ select SYS_SUPPORTS_MIPS_CPS
+
source "arch/mips/alchemy/Kconfig"
source "arch/mips/ath25/Kconfig"
source "arch/mips/ath79/Kconfig"

View File

@@ -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
@@ -529,6 +529,12 @@ config GPIO_ROCKCHIP
help
Say yes here to support GPIO on Rockchip SoCs.
+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
@@ -129,6 +129,7 @@ obj-$(CONFIG_GPIO_RDC321X) += gpio-rdc3
obj-$(CONFIG_GPIO_REALTEK_OTTO) += gpio-realtek-otto.o
obj-$(CONFIG_GPIO_REG) += gpio-reg.o
obj-$(CONFIG_GPIO_ROCKCHIP) += gpio-rockchip.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

View File

@@ -0,0 +1,93 @@
From 3cc8011171186d906c547bc6f0c1f8e350edc7cf Mon Sep 17 00:00:00 2001
From: Markus Stockhausen <markus.stockhausen@gmx.de>
Date: Mon, 3 Oct 2022 14:45:21 +0200
Subject: [PATCH] realtek: resurrect timer driver
Now that we provide a clock driver for the Reltek SOCs the CPU frequency might
change on demand. This has direct visible effects during operation
- the CEVT 4K timer is no longer a stable clocksource
- after CPU frequencies changes time calculation works wrong
- sched_clock falls back to kernel default interval (100 Hz)
- timestamps in dmesg have only 2 digits left
[ 0.000000] sched_clock: 32 bits at 100 Hz, resolution 10000000ns, wraps ...
[ 0.060000] pid_max: default: 32768 minimum: 301
[ 0.070000] Mount-cache hash table entries: 1024 (order: 0, 4096 bytes, linear)
[ 0.070000] Mountpoint-cache hash table entries: 1024 (order: 0, 4096 bytes, linear)
[ 0.080000] dyndbg: Ignore empty _ddebug table in a CONFIG_DYNAMIC_DEBUG_CORE build
[ 0.090000] clocksource: jiffies: mask: 0xffffffff max_cycles: 0xffffffff, ...
Looking around where we can start the CEVT timer for RTL930X is a good basis.
Initially it was developed as a clocksource driver for the broken timer in that
specific SOC series. Afterwards it was shifted around to the CEVT location,
got SMP enablement and lost its clocksource feature. So we at least have
something to copy from. As the timers on these devices are well understood
the implementation follows this way:
- leave the RTL930X implementation as is
- provide a new driver for RTL83XX devices only
- swap RTL930X driver at a later time
Like the clock driver this patch contains a self contained module that is SOC
independet and already provides full support for the RTL838X, RTL839X and
RTL930X devices. Some of the new (or reestablished) features are:
- simplified initialization routines
- SMP setup with CPU hotplug framework
- derived from LXB clock speed
- supplied clocksource
- dedicated register functions for better readability
- documentation about some caveats
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
[remove unused header includes, remove old CONFIG_MIPS dependency, add
REALTEK_ prefix to driver symbol]
Signed-off-by: Sander Vanheule <sander@svanheule.net>
---
drivers/clocksource/Kconfig | 12 +++
drivers/clocksource/Makefile | 1 +
include/linux/cpuhotplug.h | 1 +
3 files changed, 14 insertions(+)
--- a/drivers/clocksource/Kconfig
+++ b/drivers/clocksource/Kconfig
@@ -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
@@ -58,6 +58,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
@@ -176,6 +176,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,

View File

@@ -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
@@ -503,8 +503,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

View File

@@ -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
@@ -97,7 +97,7 @@ obj-$(CONFIG_SPI_QUP) += spi-qup.o
obj-$(CONFIG_SPI_ROCKCHIP) += spi-rockchip.o
obj-$(CONFIG_SPI_ROCKCHIP_SFC) += spi-rockchip-sfc.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

View File

@@ -0,0 +1,25 @@
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
@@ -112,7 +112,7 @@ 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
obj-$(CONFIG_WPCM450_AIC) += irq-wpcm450-aic.o
obj-$(CONFIG_IRQ_IDT3243X) += irq-idt3243x.o
obj-$(CONFIG_APPLE_AIC) += irq-apple-aic.o

View File

@@ -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
@@ -956,10 +956,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

View File

@@ -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>

View File

@@ -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
@@ -949,6 +949,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
@@ -94,6 +94,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

View File

@@ -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

View File

@@ -0,0 +1,402 @@
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,52 +86,137 @@ 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)
{
+ 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;
+ int cpu = smp_processor_id();
unsigned long pending;
unsigned int soc_int;
chained_irq_enter(chip, desc);
- pending = readl(REG(RTL_ICTL_GIMR)) & readl(REG(RTL_ICTL_GISR));
+ pending = readl(REG(RTL_ICTL_GIMR, cpu)) & readl(REG(RTL_ICTL_GISR, cpu))
+ & output->child_mask;
if (unlikely(!pending)) {
spurious_interrupt();
goto out;
}
- domain = irq_desc_get_handler_data(desc);
- for_each_set_bit(soc_int, &pending, 32)
- generic_handle_domain_irq(domain, soc_int);
+ for_each_set_bit(soc_int, &pending, RTL_ICTL_NUM_INPUTS)
+ generic_handle_domain_irq(output->domain, soc_int);
out:
chained_irq_exit(chip, desc);
@@ -102,85 +229,110 @@ out:
* thus go into 4 IRRs. A routing value of '0' means the interrupt is left
* disconnected. Routing values {1..15} connect to output lines {0..14}.
*/
-static int __init 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);
- of_node_put(cpu_ictl);
- if (ret || tmp != 1)
- return -EINVAL;
-
- cpu_int = be32_to_cpup(imap + 2);
- if (cpu_int > 7 || cpu_int < 2)
- 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;
- /* Use routing values (1..6) for CPU interrupts (2..7) */
- regs[(soc_int * 4) / 32] |= (cpu_int - 1) << (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);

View File

@@ -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
@@ -301,6 +301,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;
@@ -308,8 +309,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;

View File

@@ -0,0 +1,368 @@
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")
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,28 +248,21 @@ 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 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)
- generic_handle_domain_irq(gc->irq.domain, offset + lines_done);
- }
+ status = realtek_gpio_read_isr(ctrl);
+ for_each_set_bit(offset, &status, gc->ngpio)
+ generic_handle_domain_irq(gc->irq.domain, offset);
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,
@@ -267,12 +270,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;
@@ -280,15 +281,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);
@@ -302,22 +303,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);
}
}
@@ -390,12 +392,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,

View File

@@ -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
@@ -406,6 +406,7 @@ source "drivers/clk/mstar/Kconfig"
source "drivers/clk/mvebu/Kconfig"
source "drivers/clk/pistachio/Kconfig"
source "drivers/clk/qcom/Kconfig"
+source "drivers/clk/realtek/Kconfig"
source "drivers/clk/ralink/Kconfig"
source "drivers/clk/renesas/Kconfig"
source "drivers/clk/rockchip/Kconfig"
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -101,6 +101,7 @@ obj-$(CONFIG_COMMON_CLK_PISTACHIO) += pi
obj-$(CONFIG_COMMON_CLK_PXA) += pxa/
obj-$(CONFIG_COMMON_CLK_QCOM) += qcom/
obj-y += ralink/
+obj-$(CONFIG_COMMON_CLK_REALTEK) += realtek/
obj-y += renesas/
obj-$(CONFIG_ARCH_ROCKCHIP) += rockchip/
obj-$(CONFIG_COMMON_CLK_SAMSUNG) += samsung/

View File

@@ -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);
@@ -285,9 +321,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);
+ }
}
}

View File

@@ -0,0 +1,42 @@
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
@@ -85,6 +85,8 @@ source "drivers/net/dsa/sja1105/Kconfig"
source "drivers/net/dsa/xrs700x/Kconfig"
+source "drivers/net/dsa/rtl83xx/Kconfig"
+
config NET_DSA_REALTEK_SMI
tristate "Realtek SMI Ethernet switch family support"
select NET_DSA_TAG_RTL4_A
--- a/drivers/net/dsa/Makefile
+++ b/drivers/net/dsa/Makefile
@@ -24,5 +24,6 @@ obj-y += microchip/
obj-y += mv88e6xxx/
obj-y += ocelot/
obj-y += qca/
+obj-y += rtl83xx/
obj-y += sja1105/
obj-y += xrs700x/

View File

@@ -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;
@@ -33,12 +38,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;

View File

@@ -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 {

View File

@@ -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
@@ -166,6 +166,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/sgi/Kconfig"
--- a/drivers/net/ethernet/Makefile
+++ b/drivers/net/ethernet/Makefile
@@ -77,6 +77,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/

View File

@@ -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
@@ -1009,6 +1009,10 @@ struct phy_driver {
int (*led_blink_set)(struct phy_device *dev, u8 index,
unsigned long *delay_on,
unsigned long *delay_off);
+ 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)

View File

@@ -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
@@ -1994,6 +1994,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);
}
@@ -2297,8 +2302,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;
}
@@ -2315,8 +2323,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;
}

View File

@@ -0,0 +1,62 @@
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>
---
drivers/net/phy/phylink.c | 2 ++
include/linux/phy.h | 3 +++
2 file changed, 5 insertions(+)
--- a/drivers/net/phy/phy-core.c
+++ b/drivers/net/phy/phy-core.c
@@ -124,6 +124,7 @@ int phy_interface_num_ports(phy_interfac
case PHY_INTERFACE_MODE_MOCA:
case PHY_INTERFACE_MODE_TRGMII:
case PHY_INTERFACE_MODE_USXGMII:
+ case PHY_INTERFACE_MODE_HSGMII:
case PHY_INTERFACE_MODE_SGMII:
case PHY_INTERFACE_MODE_SMII:
case PHY_INTERFACE_MODE_1000BASEX:
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -410,6 +410,7 @@ void phylink_get_linkmodes(unsigned long
case PHY_INTERFACE_MODE_XGMII:
case PHY_INTERFACE_MODE_RXAUI:
+ case PHY_INTERFACE_MODE_HSGMII:
case PHY_INTERFACE_MODE_XAUI:
case PHY_INTERFACE_MODE_10GBASER:
case PHY_INTERFACE_MODE_10GKR:
@@ -665,6 +666,7 @@ static int phylink_parse_mode(struct phy
fallthrough;
case PHY_INTERFACE_MODE_USXGMII:
case PHY_INTERFACE_MODE_10GKR:
+ case PHY_INTERFACE_MODE_HSGMII:
case PHY_INTERFACE_MODE_10GBASER:
phylink_set(pl->supported, 10baseT_Half);
phylink_set(pl->supported, 10baseT_Full);
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
@@ -141,6 +141,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,
@@ -248,6 +249,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:

View File

@@ -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
@@ -354,6 +354,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_NXP_C45_TJA11XX_PHY) += nxp
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

View File

@@ -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
@@ -287,7 +287,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"

View File

@@ -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
@@ -2160,6 +2160,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) {

View File

@@ -0,0 +1,148 @@
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
@@ -54,6 +54,17 @@ config MDIO_SUN4I
interface units of the Allwinner SoC that have an EMAC (A10,
A12, A10s, etc.)
+config MDIO_SMBUS
+ tristate
+ depends on I2C_SMBUS
+ help
+ Support SMBus based PHYs. This provides a MDIO bus bridged
+ to SMBus to allow PHYs connected in SMBus mode to be accessed
+ using the existing infrastructure.
+
+ This is library mode.
+
+
config MDIO_XGENE
tristate "APM X-Gene SoC MDIO bus controller"
depends on ARCH_XGENE || COMPILE_TEST
--- a/drivers/net/mdio/Makefile
+++ b/drivers/net/mdio/Makefile
@@ -19,6 +19,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
@@ -61,6 +61,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,8 @@ 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);
+bool i2c_mii_valid_phy_id(int phy_id);
+unsigned int i2c_mii_phy_addr(int phy_id);
#endif
--- a/drivers/net/mdio/mdio-i2c.c
+++ b/drivers/net/mdio/mdio-i2c.c
@@ -18,12 +18,12 @@
* specified to be present in SFP modules. These correspond with PHY
* addresses 16 and 17. Disallow access to these "phy" addresses.
*/
-static bool i2c_mii_valid_phy_id(int phy_id)
+bool i2c_mii_valid_phy_id(int phy_id)
{
return phy_id != 0x10 && phy_id != 0x11;
}
-static unsigned int i2c_mii_phy_addr(int phy_id)
+unsigned int i2c_mii_phy_addr(int phy_id)
{
return phy_id + 0x40;
}

View File

@@ -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
@@ -556,32 +556,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;
}

View File

@@ -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
@@ -742,6 +742,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
@@ -757,7 +783,10 @@ int __mdiobus_read(struct mii_bus *bus,
lockdep_assert_held_once(&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);
@@ -767,6 +796,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
@@ -783,7 +846,10 @@ int __mdiobus_write(struct mii_bus *bus,
lockdep_assert_held_once(&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);
@@ -793,6 +859,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
@@ -825,6 +924,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
@@ -850,6 +986,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
@@ -872,6 +1081,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
@@ -898,6 +1130,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
@@ -921,6 +1180,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
@@ -942,6 +1225,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
@@ -557,10 +557,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;
}
@@ -613,12 +619,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;
}
@@ -824,6 +836,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;
@@ -832,6 +851,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;
@@ -933,6 +959,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);
@@ -954,6 +992,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)
@@ -340,11 +341,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);
@@ -352,11 +361,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);
@@ -380,6 +429,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
@@ -81,6 +81,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
/**
@@ -428,6 +429,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)
@@ -1825,6 +1842,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)
{
@@ -1847,6 +1924,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... */