realtek: 6.12: restore patches-6.6

Automatically generated commit.

Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
Link: https://github.com/openwrt/openwrt/pull/18935
Signed-off-by: Robert Marko <robimarko@gmail.com>
This commit is contained in:
Markus Stockhausen 2025-05-27 09:11:03 -04:00 committed by Robert Marko
parent b943c746d2
commit 8da7a1bfa9
22 changed files with 3323 additions and 0 deletions

View file

@ -0,0 +1,89 @@
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>
---
--- a/arch/mips/Kbuild.platforms
+++ b/arch/mips/Kbuild.platforms
@@ -22,6 +22,7 @@ platform-$(CONFIG_MACH_NINTENDO64) += n6
platform-$(CONFIG_PIC32MZDA) += pic32/
platform-$(CONFIG_RALINK) += ralink/
platform-$(CONFIG_MIKROTIK_RB532) += rb532/
+platform-$(CONFIG_MACH_REALTEK_RTL) += rtl838x/
platform-$(CONFIG_SGI_IP22) += sgi-ip22/
platform-$(CONFIG_SGI_IP27) += sgi-ip27/
platform-$(CONFIG_SGI_IP28) += sgi-ip22/
--- a/arch/mips/Kconfig
+++ b/arch/mips/Kconfig
@@ -620,21 +620,23 @@ config RALINK
config MACH_REALTEK_RTL
bool "Realtek RTL838x/RTL839x based machines"
- select MIPS_GENERIC
select DMA_NONCOHERENT
select IRQ_MIPS_CPU
- select CSRC_R4K
- select CEVT_R4K
select SYS_HAS_CPU_MIPS32_R1
select SYS_HAS_CPU_MIPS32_R2
select SYS_SUPPORTS_BIG_ENDIAN
select SYS_SUPPORTS_32BIT_KERNEL
select SYS_SUPPORTS_MIPS16
- select SYS_SUPPORTS_MULTITHREADING
- select SYS_SUPPORTS_VPE_LOADER
select BOOT_RAW
select PINCTRL
select USE_OF
+ select NO_EXCEPT_FILL
+ select SYS_SUPPORTS_HIGHMEM
+ select SYS_HAS_EARLY_PRINTK
+ select SYS_HAS_EARLY_PRINTK_8250
+ select USE_GENERIC_EARLY_PRINTK_8250
+ select ARCH_HAS_RESET_CONTROLLER
+ select RESET_CONTROLLER
config SGI_IP22
bool "SGI IP22 (Indy/Indigo2)"
@@ -970,6 +972,36 @@ config CAVIUM_OCTEON_SOC
endchoice
+config RTL838X
+ bool "Realtek RTL838X based platforms"
+ depends on MACH_REALTEK_RTL
+ select CPU_SUPPORTS_CPUFREQ
+ select MIPS_EXTERNAL_TIMER
+
+config RTL839X
+ bool "Realtek RTL839X based platforms"
+ depends on MACH_REALTEK_RTL
+ select CPU_SUPPORTS_CPUFREQ
+ select MIPS_EXTERNAL_TIMER
+ select SYS_SUPPORTS_MULTITHREADING
+
+config RTL930X
+ bool "Realtek RTL930X based platforms"
+ depends on MACH_REALTEK_RTL
+ select MIPS_CPU_SCACHE
+ select MIPS_EXTERNAL_TIMER
+ select SYS_SUPPORTS_MULTITHREADING
+
+config RTL931X
+ bool "Realtek RTL931X based platforms"
+ depends on RTL930X
+ select MIPS_GIC
+ select COMMON_CLK
+ select CLKSRC_MIPS_GIC
+ select SYS_SUPPORTS_VPE_LOADER
+ select SYS_SUPPORTS_SMP
+ select SYS_SUPPORTS_MIPS_CPS
+
source "arch/mips/alchemy/Kconfig"
source "arch/mips/ath25/Kconfig"
source "arch/mips/ath79/Kconfig"

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
@@ -134,6 +134,17 @@ config RDA_TIMER
help
Enables the support for the RDA Micro timer driver.
+config REALTEK_OTTO_TIMER
+ bool "Clocksource/timer for the Realtek Otto platform"
+ select COMMON_CLK
+ select TIMER_OF
+ help
+ This driver adds support for the timers found in the Realtek RTL83xx
+ and RTL93xx SoCs series. This includes chips such as RTL8380, RTL8381
+ and RTL832, as well as chips from the RTL839x series, such as RTL8390
+ RT8391, RTL8392, RTL8393 and RTL8396 and chips of the RTL930x series
+ such as RTL9301, RTL9302 or RTL9303.
+
config SUN4I_TIMER
bool "Sun4i timer driver" if COMPILE_TEST
depends on HAS_IOMEM
--- a/drivers/clocksource/Makefile
+++ b/drivers/clocksource/Makefile
@@ -59,6 +59,7 @@ obj-$(CONFIG_MILBEAUT_TIMER) += timer-mi
obj-$(CONFIG_SPRD_TIMER) += timer-sprd.o
obj-$(CONFIG_NPCM7XX_TIMER) += timer-npcm7xx.o
obj-$(CONFIG_RDA_TIMER) += timer-rda.o
+obj-$(CONFIG_REALTEK_OTTO_TIMER) += timer-rtl-otto.o
obj-$(CONFIG_ARC_TIMERS) += arc_timer.o
obj-$(CONFIG_ARM_ARCH_TIMER) += arm_arch_timer.o
--- a/include/linux/cpuhotplug.h
+++ b/include/linux/cpuhotplug.h
@@ -181,6 +181,7 @@ enum cpuhp_state {
CPUHP_AP_MARCO_TIMER_STARTING,
CPUHP_AP_MIPS_GIC_TIMER_STARTING,
CPUHP_AP_ARC_TIMER_STARTING,
+ CPUHP_AP_REALTEK_TIMER_STARTING,
CPUHP_AP_RISCV_TIMER_STARTING,
CPUHP_AP_CLINT_TIMER_STARTING,
CPUHP_AP_CSKY_TIMER_STARTING,

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
@@ -1023,6 +1023,16 @@ config I2C_RK3X
This driver can also be built as a module. If so, the module will
be called i2c-rk3x.
+config I2C_RTL9300
+ tristate "Realtek RTL9300 I2C adapter"
+ depends on OF
+ help
+ Say Y here to include support for the I2C adapter in Realtek RTL9300
+ and RTL9310 SoCs.
+
+ This driver can also be built as a module. If so, the module will
+ be called i2c-rtl9300.
+
config I2C_RZV2M
tristate "Renesas RZ/V2M adapter"
depends on ARCH_RENESAS || COMPILE_TEST
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -101,6 +101,7 @@ obj-$(CONFIG_I2C_QCOM_GENI) += i2c-qcom-
obj-$(CONFIG_I2C_QUP) += i2c-qup.o
obj-$(CONFIG_I2C_RIIC) += i2c-riic.o
obj-$(CONFIG_I2C_RK3X) += i2c-rk3x.o
+obj-$(CONFIG_I2C_RTL9300) += i2c-rtl9300.o
obj-$(CONFIG_I2C_RZV2M) += i2c-rzv2m.o
obj-$(CONFIG_I2C_S3C2410) += i2c-s3c2410.o
obj-$(CONFIG_I2C_SH7760) += i2c-sh7760.o

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,427 @@
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.
Up to kernel 5.15 this patch was divided into two parts
315-irqchip-irq-realtek-rtl-add-VPE-support.patch
319-irqchip-irq-realtek-rtl-fix-VPE-affinity.patch
As both parts will only work in combination they have been merged into
one patch.
Submitted-by: Birger Koblitz <git@birger-koblitz.de>
Submitted-by: INAGAKI Hiroshi <musashino.open@gmail.com>
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
---
drivers/irqchip/irq-realtek-rtl.c | 296 +++++++++++++++++++++++++-----
1 file changed, 249 insertions(+), 47 deletions(-)
--- a/drivers/irqchip/irq-realtek-rtl.c
+++ b/drivers/irqchip/irq-realtek-rtl.c
@@ -22,22 +22,58 @@
#define RTL_ICTL_IRR3 0x14
#define RTL_ICTL_NUM_INPUTS 32
-
-#define REG(x) (realtek_ictl_base + x)
+#define RTL_ICTL_NUM_OUTPUTS 15
static DEFINE_RAW_SPINLOCK(irq_lock);
-static void __iomem *realtek_ictl_base;
+
+#define REG(offset, cpu) (realtek_ictl_base[cpu] + offset)
+
+static u32 realtek_ictl_unmask[NR_CPUS];
+static void __iomem *realtek_ictl_base[NR_CPUS];
+static cpumask_t realtek_ictl_cpu_configurable;
+
+struct realtek_ictl_output {
+ /* IRQ controller data */
+ struct fwnode_handle *fwnode;
+ /* Output specific data */
+ unsigned int output_index;
+ struct irq_domain *domain;
+ u32 child_mask;
+};
/*
- * IRR0-IRR3 store 4 bits per interrupt, but Realtek uses inverted numbering,
- * placing IRQ 31 in the first four bits. A routing value of '0' means the
- * interrupt is left disconnected. Routing values {1..15} connect to output
- * lines {0..14}.
+ * Per CPU we have a set of 5 registers that determine interrupt handling for
+ * 32 external interrupts. GIMR (enable/disable interrupt) plus IRR0-IRR3 that
+ * contain "routing" or "priority" values. GIMR uses one bit for each interrupt
+ * and IRRx store 4 bits per interrupt. Realtek uses inverted numbering,
+ * placing IRQ 31 in the first four bits. The register combinations give the
+ * following results for a single interrupt in the wild:
+ *
+ * a) GIMR = 0 / IRRx > 0 -> no interrupts
+ * b) GIMR = 0 / IRRx = 0 -> no interrupts
+ * c) GIMR = 1 / IRRx > 0 -> interrupts
+ * d) GIMR = 1 / IRRx = 0 -> rare interrupts in SMP environment
+ *
+ * Combination d) seems to trigger interrupts only on a VPE if the other VPE
+ * has GIMR = 0 and IRRx > 0. E.g. busy without interrupts allowed. To provide
+ * IRQ balancing features in SMP this driver will handle the registers as
+ * follows:
+ *
+ * 1) set IRRx > 0 for VPE where the interrupt is desired
+ * 2) set IRRx = 0 for VPE where the interrupt is not desired
+ * 3) set both GIMR = 0 to mask (disabled) interrupt
+ * 4) set GIMR = 1 to unmask (enable) interrupt but only for VPE where IRRx > 0
*/
+
#define IRR_OFFSET(idx) (4 * (3 - (idx * 4) / 32))
#define IRR_SHIFT(idx) ((idx * 4) % 32)
-static void write_irr(void __iomem *irr0, int idx, u32 value)
+static inline u32 read_irr(void __iomem *irr0, int idx)
+{
+ return (readl(irr0 + IRR_OFFSET(idx)) >> IRR_SHIFT(idx)) & 0xf;
+}
+
+static inline void write_irr(void __iomem *irr0, int idx, u32 value)
{
unsigned int offset = IRR_OFFSET(idx);
unsigned int shift = IRR_SHIFT(idx);
@@ -48,16 +84,33 @@ static void write_irr(void __iomem *irr0
writel(irr, irr0 + offset);
}
+static inline void enable_gimr(int hwirq, int cpu)
+{
+ u32 value;
+
+ value = readl(REG(RTL_ICTL_GIMR, cpu));
+ value |= (BIT(hwirq) & realtek_ictl_unmask[cpu]);
+ writel(value, REG(RTL_ICTL_GIMR, cpu));
+}
+
+static inline void disable_gimr(int hwirq, int cpu)
+{
+ u32 value;
+
+ value = readl(REG(RTL_ICTL_GIMR, cpu));
+ value &= ~BIT(hwirq);
+ writel(value, REG(RTL_ICTL_GIMR, cpu));
+}
+
static void realtek_ictl_unmask_irq(struct irq_data *i)
{
unsigned long flags;
- u32 value;
+ int cpu;
raw_spin_lock_irqsave(&irq_lock, flags);
- value = readl(REG(RTL_ICTL_GIMR));
- value |= BIT(i->hwirq);
- writel(value, REG(RTL_ICTL_GIMR));
+ for_each_cpu(cpu, &realtek_ictl_cpu_configurable)
+ enable_gimr(i->hwirq, cpu);
raw_spin_unlock_irqrestore(&irq_lock, flags);
}
@@ -65,110 +118,259 @@ static void realtek_ictl_unmask_irq(stru
static void realtek_ictl_mask_irq(struct irq_data *i)
{
unsigned long flags;
- u32 value;
+ int cpu;
raw_spin_lock_irqsave(&irq_lock, flags);
- value = readl(REG(RTL_ICTL_GIMR));
- value &= ~BIT(i->hwirq);
- writel(value, REG(RTL_ICTL_GIMR));
+ for_each_cpu(cpu, &realtek_ictl_cpu_configurable)
+ disable_gimr(i->hwirq, cpu);
raw_spin_unlock_irqrestore(&irq_lock, flags);
}
+static int __maybe_unused realtek_ictl_irq_affinity(struct irq_data *i,
+ const struct cpumask *dest, bool force)
+{
+ struct realtek_ictl_output *output = i->domain->host_data;
+ cpumask_t cpu_configure;
+ cpumask_t cpu_disable;
+ cpumask_t cpu_enable;
+ unsigned long flags;
+ int cpu;
+
+ raw_spin_lock_irqsave(&irq_lock, flags);
+
+ cpumask_and(&cpu_configure, cpu_present_mask, &realtek_ictl_cpu_configurable);
+
+ cpumask_and(&cpu_enable, &cpu_configure, dest);
+ cpumask_andnot(&cpu_disable, &cpu_configure, dest);
+
+ for_each_cpu(cpu, &cpu_disable) {
+ write_irr(REG(RTL_ICTL_IRR0, cpu), i->hwirq, 0);
+ realtek_ictl_unmask[cpu] &= ~BIT(i->hwirq);
+ disable_gimr(i->hwirq, cpu);
+ }
+
+ for_each_cpu(cpu, &cpu_enable) {
+ write_irr(REG(RTL_ICTL_IRR0, cpu), i->hwirq, output->output_index + 1);
+ realtek_ictl_unmask[cpu] |= BIT(i->hwirq);
+ enable_gimr(i->hwirq, cpu);
+ }
+
+ irq_data_update_effective_affinity(i, &cpu_enable);
+
+ raw_spin_unlock_irqrestore(&irq_lock, flags);
+
+ return IRQ_SET_MASK_OK;
+}
+
static struct irq_chip realtek_ictl_irq = {
.name = "realtek-rtl-intc",
.irq_mask = realtek_ictl_mask_irq,
.irq_unmask = realtek_ictl_unmask_irq,
+#ifdef CONFIG_SMP
+ .irq_set_affinity = realtek_ictl_irq_affinity,
+#endif
};
static int intc_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hw)
{
+ struct realtek_ictl_output *output = d->host_data;
unsigned long flags;
irq_set_chip_and_handler(irq, &realtek_ictl_irq, handle_level_irq);
raw_spin_lock_irqsave(&irq_lock, flags);
- write_irr(REG(RTL_ICTL_IRR0), hw, 1);
+
+ output->child_mask |= BIT(hw);
+ write_irr(REG(RTL_ICTL_IRR0, 0), hw, output->output_index + 1);
+ realtek_ictl_unmask[0] |= BIT(hw);
+
raw_spin_unlock_irqrestore(&irq_lock, flags);
return 0;
}
+static int intc_select(struct irq_domain *d, struct irq_fwspec *fwspec,
+ enum irq_domain_bus_token bus_token)
+{
+ struct realtek_ictl_output *output = d->host_data;
+ bool routed_elsewhere;
+ unsigned long flags;
+ u32 routing_old;
+ int cpu;
+
+ if (fwspec->fwnode != output->fwnode)
+ return false;
+
+ /* Original specifiers had only one parameter */
+ if (fwspec->param_count < 2)
+ return true;
+
+ raw_spin_lock_irqsave(&irq_lock, flags);
+
+ /*
+ * Inputs can only be routed to one output, so they shouldn't be
+ * allowed to end up in multiple domains.
+ */
+ for_each_cpu(cpu, &realtek_ictl_cpu_configurable) {
+ routing_old = read_irr(REG(RTL_ICTL_IRR0, cpu), fwspec->param[0]);
+ routed_elsewhere = routing_old && fwspec->param[1] != routing_old - 1;
+ if (routed_elsewhere) {
+ pr_warn("soc int %d already routed to output %d\n",
+ fwspec->param[0], routing_old - 1);
+ break;
+ }
+ }
+
+ raw_spin_unlock_irqrestore(&irq_lock, flags);
+
+ return !routed_elsewhere && fwspec->param[1] == output->output_index;
+}
+
static const struct irq_domain_ops irq_domain_ops = {
.map = intc_map,
+ .select = intc_select,
.xlate = irq_domain_xlate_onecell,
};
static void realtek_irq_dispatch(struct irq_desc *desc)
{
+ struct realtek_ictl_output *output = irq_desc_get_handler_data(desc);
struct irq_chip *chip = irq_desc_get_chip(desc);
- struct irq_domain *domain;
+ int cpu = smp_processor_id();
unsigned long pending;
unsigned int soc_int;
chained_irq_enter(chip, desc);
- pending = readl(REG(RTL_ICTL_GIMR)) & readl(REG(RTL_ICTL_GISR));
+ pending = readl(REG(RTL_ICTL_GIMR, cpu)) & readl(REG(RTL_ICTL_GISR, cpu))
+ & output->child_mask;
if (unlikely(!pending)) {
spurious_interrupt();
goto out;
}
- domain = irq_desc_get_handler_data(desc);
- for_each_set_bit(soc_int, &pending, 32)
- generic_handle_domain_irq(domain, soc_int);
+ for_each_set_bit(soc_int, &pending, RTL_ICTL_NUM_INPUTS)
+ generic_handle_domain_irq(output->domain, soc_int);
out:
chained_irq_exit(chip, desc);
}
+/*
+ * SoC interrupts are cascaded to MIPS CPU interrupts according to the
+ * interrupt-map in the device tree. Each SoC interrupt gets 4 bits for
+ * the CPU interrupt in an Interrupt Routing Register. Max 32 SoC interrupts
+ * thus go into 4 IRRs. A routing value of '0' means the interrupt is left
+ * disconnected. Routing values {1..15} connect to output lines {0..14}.
+ */
+static int __init setup_parent_interrupts(struct device_node *node, int *parents,
+ unsigned int num_parents)
+{
+ struct realtek_ictl_output *outputs;
+ struct realtek_ictl_output *output;
+ struct irq_domain *domain;
+ unsigned int p;
+
+ outputs = kcalloc(num_parents, sizeof(*outputs), GFP_KERNEL);
+ if (!outputs)
+ return -ENOMEM;
+
+ for (p = 0; p < num_parents; p++) {
+ output = outputs + p;
+
+ domain = irq_domain_add_linear(node, RTL_ICTL_NUM_INPUTS, &irq_domain_ops, output);
+ if (!domain)
+ goto domain_err;
+
+ output->fwnode = of_node_to_fwnode(node);
+ output->output_index = p;
+ output->domain = domain;
+
+ irq_set_chained_handler_and_data(parents[p], realtek_irq_dispatch, output);
+ }
+
+ return 0;
+
+domain_err:
+ while (p--) {
+ irq_set_chained_handler_and_data(parents[p], NULL, NULL);
+ irq_domain_remove(outputs[p].domain);
+ }
+
+ kfree(outputs);
+
+ return -ENOMEM;
+}
+
static int __init realtek_rtl_of_init(struct device_node *node, struct device_node *parent)
{
+ int parent_irqs[RTL_ICTL_NUM_OUTPUTS];
struct of_phandle_args oirq;
- struct irq_domain *domain;
+ unsigned int num_parents;
unsigned int soc_irq;
- int parent_irq;
+ unsigned int p;
+ int cpu;
+
+ cpumask_clear(&realtek_ictl_cpu_configurable);
- realtek_ictl_base = of_iomap(node, 0);
- if (!realtek_ictl_base)
+ for (cpu = 0; cpu < NR_CPUS; cpu++) {
+ realtek_ictl_base[cpu] = of_iomap(node, cpu);
+ if (realtek_ictl_base[cpu]) {
+ cpumask_set_cpu(cpu, &realtek_ictl_cpu_configurable);
+
+ /* Disable all cascaded interrupts and clear routing */
+ for (soc_irq = 0; soc_irq < RTL_ICTL_NUM_INPUTS; soc_irq++) {
+ write_irr(REG(RTL_ICTL_IRR0, cpu), soc_irq, 0);
+ realtek_ictl_unmask[cpu] &= ~BIT(soc_irq);
+ disable_gimr(soc_irq, cpu);
+ }
+ }
+ }
+
+ if (cpumask_empty(&realtek_ictl_cpu_configurable))
return -ENXIO;
- /* Disable all cascaded interrupts and clear routing */
- writel(0, REG(RTL_ICTL_GIMR));
- for (soc_irq = 0; soc_irq < RTL_ICTL_NUM_INPUTS; soc_irq++)
- write_irr(REG(RTL_ICTL_IRR0), soc_irq, 0);
+ num_parents = of_irq_count(node);
+ if (num_parents > RTL_ICTL_NUM_OUTPUTS) {
+ pr_err("too many parent interrupts\n");
+ return -EINVAL;
+ }
- if (WARN_ON(!of_irq_count(node))) {
+ for (p = 0; p < num_parents; p++)
+ parent_irqs[p] = of_irq_get(node, p);
+
+ if (WARN_ON(!num_parents)) {
/*
* If DT contains no parent interrupts, assume MIPS CPU IRQ 2
* (HW0) is connected to the first output. This is the case for
* all known hardware anyway. "interrupt-map" is deprecated, so
* don't bother trying to parse that.
+ * Since this is to account for old devicetrees with one-cell
+ * interrupt specifiers, only one output domain is needed.
*/
oirq.np = of_find_compatible_node(NULL, NULL, "mti,cpu-interrupt-controller");
- oirq.args_count = 1;
- oirq.args[0] = 2;
-
- parent_irq = irq_create_of_mapping(&oirq);
+ if (oirq.np) {
+ oirq.args_count = 1;
+ oirq.args[0] = 2;
+
+ parent_irqs[0] = irq_create_of_mapping(&oirq);
+ num_parents = 1;
+ }
of_node_put(oirq.np);
- } else {
- parent_irq = of_irq_get(node, 0);
}
- if (parent_irq < 0)
- return parent_irq;
- else if (!parent_irq)
- return -ENODEV;
-
- domain = irq_domain_add_linear(node, RTL_ICTL_NUM_INPUTS, &irq_domain_ops, NULL);
- if (!domain)
- return -ENOMEM;
-
- irq_set_chained_handler_and_data(parent_irq, realtek_irq_dispatch, domain);
+ /* Ensure we haven't collected any errors before proceeding */
+ for (p = 0; p < num_parents; p++) {
+ if (parent_irqs[p] < 0)
+ return parent_irqs[p];
+ if (!parent_irqs[p])
+ return -ENODEV;
+ }
- return 0;
+ return setup_parent_interrupts(node, &parent_irqs[0], num_parents);
}
IRQCHIP_DECLARE(realtek_rtl_intc, "realtek,rtl-intc", realtek_rtl_of_init);

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

View file

@ -0,0 +1,21 @@
--- a/drivers/thermal/Kconfig
+++ b/drivers/thermal/Kconfig
@@ -522,4 +522,11 @@ config LOONGSON2_THERMAL
is higher than the high temperature threshold or lower than the low
temperature threshold, the interrupt will occur.
+config REALTEK_THERMAL
+ tristate "Realtek RTL838x and RTL930x thermal sensor support"
+ depends on RTL838X || RTL839X || RTL930X || COMPILE_TEST
+ depends on THERMAL_OF
+ help
+ Support thermal sensor in Realtek RTL838x, RTL839x and RTL930x SoCs
+
endif
--- a/drivers/thermal/Makefile
+++ b/drivers/thermal/Makefile
@@ -64,3 +64,4 @@ obj-$(CONFIG_AMLOGIC_THERMAL) += aml
obj-$(CONFIG_SPRD_THERMAL) += sprd_thermal.o
obj-$(CONFIG_KHADAS_MCU_FAN_THERMAL) += khadas_mcu_fan.o
obj-$(CONFIG_LOONGSON2_THERMAL) += loongson2_thermal.o
+obj-$(CONFIG_REALTEK_THERMAL) += realtek-thermal.o

View file

@ -0,0 +1,132 @@
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: realtek dsa/phy: Increase max ports for RTL839X/RTL931X
Linux standard can only support up to 32 devices per mdio bus and up to
12 ports per DSA switch. This is not enough for the large RTL839X and
RTL931X devices. Increase the max values accordingly. Additionally take
care about the functions that work on bit masks.
Submitted-by: Bert Vermeulen <bert@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>
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
---
drivers/net/mdio/fwnode_mdio.c | 2 +-
include/linux/phy.h | 6 +++---
include/linux/platform_data/dsa.h | 2 +-
include/net/dsa.h | 14 +++++++-------
net/dsa/slave.c | 4 ++--
5 files changed, 14 insertions(+), 14 deletions(-)
--- a/drivers/net/mdio/fwnode_mdio.c
+++ b/drivers/net/mdio/fwnode_mdio.c
@@ -87,7 +87,7 @@ int fwnode_mdiobus_phy_device_register(s
}
if (fwnode_property_read_bool(child, "broken-turn-around"))
- mdio->phy_ignore_ta_mask |= 1 << addr;
+ mdio->phy_ignore_ta_mask |= BIT_ULL(addr);
fwnode_property_read_u32(child, "reset-assert-us",
&phy->mdio.reset_assert_delay);
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
@@ -294,7 +294,7 @@ static inline const char *phy_modes(phy_
#define PHY_INIT_TIMEOUT 100000
#define PHY_FORCE_TIMEOUT 10
-#define PHY_MAX_ADDR 32
+#define PHY_MAX_ADDR 64
/* Used when trying to connect to a specific phy (mii bus id:phy device id) */
#define PHY_ID_FMT "%s:%02x"
@@ -414,10 +414,10 @@ struct mii_bus {
struct mdio_device *mdio_map[PHY_MAX_ADDR];
/** @phy_mask: PHY addresses to be ignored when probing */
- u32 phy_mask;
+ u64 phy_mask;
/** @phy_ignore_ta_mask: PHY addresses to ignore the TA/read failure */
- u32 phy_ignore_ta_mask;
+ u64 phy_ignore_ta_mask;
/**
* @irq: An array of interrupts, each PHY's interrupt at the index
--- a/include/linux/platform_data/dsa.h
+++ b/include/linux/platform_data/dsa.h
@@ -6,7 +6,7 @@ struct device;
struct net_device;
#define DSA_MAX_SWITCHES 4
-#define DSA_MAX_PORTS 12
+#define DSA_MAX_PORTS 54
#define DSA_RTABLE_NONE -1
struct dsa_chip_data {
--- a/include/net/dsa.h
+++ b/include/net/dsa.h
@@ -465,7 +465,7 @@ struct dsa_switch {
/*
* Slave mii_bus and devices for the individual ports.
*/
- u32 phys_mii_mask;
+ u64 phys_mii_mask;
struct mii_bus *slave_mii_bus;
/* Ageing Time limits in msecs */
@@ -597,24 +597,24 @@ static inline bool dsa_is_user_port(stru
dsa_switch_for_each_port_continue_reverse((_dp), (_ds)) \
if (dsa_port_is_cpu((_dp)))
-static inline u32 dsa_user_ports(struct dsa_switch *ds)
+static inline u64 dsa_user_ports(struct dsa_switch *ds)
{
struct dsa_port *dp;
- u32 mask = 0;
+ u64 mask = 0;
dsa_switch_for_each_user_port(dp, ds)
- mask |= BIT(dp->index);
+ mask |= BIT_ULL(dp->index);
return mask;
}
-static inline u32 dsa_cpu_ports(struct dsa_switch *ds)
+static inline u64 dsa_cpu_ports(struct dsa_switch *ds)
{
struct dsa_port *cpu_dp;
- u32 mask = 0;
+ u64 mask = 0;
dsa_switch_for_each_cpu_port(cpu_dp, ds)
- mask |= BIT(cpu_dp->index);
+ mask |= BIT_ULL(cpu_dp->index);
return mask;
}
--- a/net/dsa/slave.c
+++ b/net/dsa/slave.c
@@ -320,7 +320,7 @@ static int dsa_slave_phy_read(struct mii
{
struct dsa_switch *ds = bus->priv;
- if (ds->phys_mii_mask & (1 << addr))
+ if (ds->phys_mii_mask & BIT_ULL(addr))
return ds->ops->phy_read(ds, addr, reg);
return 0xffff;
@@ -330,7 +330,7 @@ static int dsa_slave_phy_write(struct mi
{
struct dsa_switch *ds = bus->priv;
- if (ds->phys_mii_mask & (1 << addr))
+ if (ds->phys_mii_mask & BIT_ULL(addr))
return ds->ops->phy_write(ds, addr, reg, val);
return 0;

View file

@ -0,0 +1,79 @@
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: realtek phy: Add PHY hsgmii mode
This adds RTL93xx-specific MAC configuration routines that allow also configuration
of 10GBit links for phylink. There is support for the Realtek-specific HSGMII
protocol.
Submitted-by: Birger Koblitz <git@birger-koblitz.de>
---
drivers/net/phy/phy-core.c | 1 +
drivers/net/phy/phylink.c | 4 ++++
include/linux/phy.h | 3 +++
3 files changed, 8 insertions(+)
--- a/drivers/net/phy/phy-core.c
+++ b/drivers/net/phy/phy-core.c
@@ -126,6 +126,7 @@ int phy_interface_num_ports(phy_interfac
case PHY_INTERFACE_MODE_MOCA:
case PHY_INTERFACE_MODE_TRGMII:
case PHY_INTERFACE_MODE_USXGMII:
+ case PHY_INTERFACE_MODE_HSGMII:
case PHY_INTERFACE_MODE_SGMII:
case PHY_INTERFACE_MODE_SMII:
case PHY_INTERFACE_MODE_1000BASEX:
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -124,6 +124,7 @@ do { \
static const phy_interface_t phylink_sfp_interface_preference[] = {
PHY_INTERFACE_MODE_25GBASER,
PHY_INTERFACE_MODE_USXGMII,
+ PHY_INTERFACE_MODE_HSGMII,
PHY_INTERFACE_MODE_10GBASER,
PHY_INTERFACE_MODE_5GBASER,
PHY_INTERFACE_MODE_2500BASEX,
@@ -238,6 +239,7 @@ static int phylink_interface_max_speed(p
case PHY_INTERFACE_MODE_XGMII:
case PHY_INTERFACE_MODE_RXAUI:
+ case PHY_INTERFACE_MODE_HSGMII:
case PHY_INTERFACE_MODE_XAUI:
case PHY_INTERFACE_MODE_10GBASER:
case PHY_INTERFACE_MODE_10GKR:
@@ -547,6 +549,7 @@ unsigned long phylink_get_capabilities(p
break;
case PHY_INTERFACE_MODE_XGMII:
+ case PHY_INTERFACE_MODE_HSGMII:
case PHY_INTERFACE_MODE_RXAUI:
case PHY_INTERFACE_MODE_XAUI:
case PHY_INTERFACE_MODE_10GBASER:
@@ -957,6 +960,7 @@ static int phylink_parse_mode(struct phy
fallthrough;
case PHY_INTERFACE_MODE_USXGMII:
case PHY_INTERFACE_MODE_10GKR:
+ case PHY_INTERFACE_MODE_HSGMII:
case PHY_INTERFACE_MODE_10GBASER:
phylink_set(pl->supported, 10baseT_Half);
phylink_set(pl->supported, 10baseT_Full);
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
@@ -148,6 +148,7 @@ typedef enum {
PHY_INTERFACE_MODE_XGMII,
PHY_INTERFACE_MODE_XLGMII,
PHY_INTERFACE_MODE_MOCA,
+ PHY_INTERFACE_MODE_HSGMII,
PHY_INTERFACE_MODE_PSGMII,
PHY_INTERFACE_MODE_QSGMII,
PHY_INTERFACE_MODE_TRGMII,
@@ -256,6 +257,8 @@ static inline const char *phy_modes(phy_
return "xlgmii";
case PHY_INTERFACE_MODE_MOCA:
return "moca";
+ case PHY_INTERFACE_MODE_HSGMII:
+ return "hsgmii";
case PHY_INTERFACE_MODE_PSGMII:
return "psgmii";
case PHY_INTERFACE_MODE_QSGMII:

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: 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
@@ -1181,6 +1181,8 @@ struct phy_driver {
*/
int (*led_polarity_set)(struct phy_device *dev, int index,
unsigned long modes);
+ int (*get_eee)(struct phy_device *dev, struct ethtool_eee *e);
+ int (*set_eee)(struct phy_device *dev, struct ethtool_eee *e);
};
#define to_phy_driver(d) container_of(to_mdio_common_driver(d), \
struct phy_driver, mdiodrv)

View file

@ -0,0 +1,49 @@
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
@@ -2805,8 +2805,11 @@ int phylink_ethtool_get_eee(struct phyli
ASSERT_RTNL();
- if (pl->phydev)
+ if (pl->phydev) {
+ if (pl->phydev->drv->get_eee)
+ return pl->phydev->drv->get_eee(pl->phydev, eee);
ret = phy_ethtool_get_eee(pl->phydev, eee);
+ }
return ret;
}
@@ -2823,8 +2826,11 @@ int phylink_ethtool_set_eee(struct phyli
ASSERT_RTNL();
- if (pl->phydev)
+ if (pl->phydev) {
+ if (pl->phydev->drv->set_eee)
+ return pl->phydev->drv->set_eee(pl->phydev, eee);
ret = phy_ethtool_set_eee(pl->phydev, eee);
+ }
return ret;
}

View file

@ -0,0 +1,428 @@
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
@@ -20,6 +20,7 @@ obj-$(CONFIG_MDIO_MSCC_MIIM) += mdio-ms
obj-$(CONFIG_MDIO_MVUSB) += mdio-mvusb.o
obj-$(CONFIG_MDIO_OCTEON) += mdio-octeon.o
obj-$(CONFIG_MDIO_REGMAP) += mdio-regmap.o
+obj-$(CONFIG_MDIO_SMBUS) += mdio-smbus.o
obj-$(CONFIG_MDIO_SUN4I) += mdio-sun4i.o
obj-$(CONFIG_MDIO_THUNDER) += mdio-thunder.o
obj-$(CONFIG_MDIO_XGENE) += mdio-xgene.o
--- /dev/null
+++ b/drivers/net/mdio/mdio-smbus.c
@@ -0,0 +1,341 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * MDIO SMBus bridge
+ *
+ * Copyright (C) 2020 Antoine Tenart
+ * Copyright (C) 2025 Bjørn Mork <bjorn@mork.no>
+ *
+ * Network PHYs can appear on SMBus when they are part of SFP modules.
+ */
+#include <linux/i2c.h>
+#include <linux/phy.h>
+#include <linux/mdio/mdio-i2c.h>
+#include <linux/sfp.h>
+
+static int smbus_mii_read_c45(struct mii_bus *mii, int phy_id, int devad, int reg)
+{
+ u16 bus_addr = i2c_mii_phy_addr(phy_id);
+ struct i2c_adapter *i2c = mii->priv;
+ union i2c_smbus_data data;
+ size_t addrlen;
+ u8 buf[5], *p;
+ int i, ret;
+
+ if (!i2c_mii_valid_phy_id(phy_id))
+ return 0xffff;
+
+ p = buf;
+ if (devad >= 0) {
+ *p++ = 0x20 | devad;
+ *p++ = reg >> 8;
+ }
+ *p++ = reg;
+ addrlen = p - buf;
+
+ i2c_lock_bus(i2c, I2C_LOCK_SEGMENT);
+ if (addrlen > 1) {
+ for (i = 1; i < addrlen; i++) {
+ data.byte = buf[i];
+ ret = __i2c_smbus_xfer(i2c, bus_addr, 0, I2C_SMBUS_WRITE, buf[0], I2C_SMBUS_BYTE_DATA, &data);
+ if (ret < 0)
+ goto unlock;
+ }
+ }
+
+ for (i = addrlen; i < addrlen + 2; i++) {
+ ret = __i2c_smbus_xfer(i2c, bus_addr, 0, I2C_SMBUS_READ, buf[0], I2C_SMBUS_BYTE_DATA, &data);
+ if (ret < 0)
+ goto unlock;
+ buf[i] = data.byte;
+ }
+
+unlock:
+ i2c_unlock_bus(i2c, I2C_LOCK_SEGMENT);
+ if (ret < 0)
+ return 0xffff;
+ return buf[addrlen] << 8 | buf[addrlen + 1];
+}
+
+static int smbus_mii_write_c45(struct mii_bus *mii, int phy_id, int devad, int reg, u16 val)
+{
+ u16 bus_addr = i2c_mii_phy_addr(phy_id);
+ struct i2c_adapter *i2c = mii->priv;
+ union i2c_smbus_data data;
+ size_t buflen;
+ u8 buf[5], *p;
+ int i, ret;
+
+ if (!i2c_mii_valid_phy_id(phy_id))
+ return 0;
+
+ p = buf;
+ if (devad >= 0) {
+ *p++ = devad;
+ *p++ = reg >> 8;
+ }
+ *p++ = reg;
+ *p++ = val >> 8;
+ *p++ = val;
+ buflen = p - buf;
+
+ i2c_lock_bus(i2c, I2C_LOCK_SEGMENT);
+ for (i = 1; i < buflen; i++) {
+ data.byte = buf[i];
+ ret = __i2c_smbus_xfer(i2c, bus_addr, 0, I2C_SMBUS_WRITE, buf[0], I2C_SMBUS_BYTE_DATA, &data);
+ if (ret < 0)
+ goto unlock;
+ }
+unlock:
+ i2c_unlock_bus(i2c, I2C_LOCK_SEGMENT);
+ return ret < 0 ? ret : 0;
+}
+
+static int smbus_mii_read_c22(struct mii_bus *bus, int phy_id, int reg)
+{
+ return smbus_mii_read_c45(bus, phy_id, -1, reg);
+}
+
+static int smbus_mii_write_c22(struct mii_bus *bus, int phy_id, int reg, u16 val)
+{
+ return smbus_mii_write_c45(bus, phy_id, -1, reg, val);
+}
+
+/* From mdio-i2c.c:
+ *
+ * RollBall SFPs do not access internal PHY via I2C address 0x56, but
+ * instead via address 0x51, when SFP page is set to 0x03 and password to
+ * 0xffffffff.
+ *
+ * address size contents description
+ * ------- ---- -------- -----------
+ * 0x80 1 CMD 0x01/0x02/0x04 for write/read/done
+ * 0x81 1 DEV Clause 45 device
+ * 0x82 2 REG Clause 45 register
+ * 0x84 2 VAL Register value
+ */
+#define ROLLBALL_PHY_I2C_ADDR 0x51
+
+#define ROLLBALL_PASSWORD (SFP_VSL + 3)
+
+#define ROLLBALL_CMD_ADDR 0x80
+#define ROLLBALL_DATA_ADDR 0x81
+
+#define ROLLBALL_CMD_WRITE 0x01
+#define ROLLBALL_CMD_READ 0x02
+#define ROLLBALL_CMD_DONE 0x04
+
+#define SFP_PAGE_ROLLBALL_MDIO 3
+
+static int smbus_set_sfp_page_lock(struct i2c_adapter *i2c, int bus_addr, u8 page)
+{
+ union i2c_smbus_data data;
+ u8 oldpage;
+ int ret;
+
+ i2c_lock_bus(i2c, I2C_LOCK_SEGMENT);
+
+ /* read current page */
+ ret = __i2c_smbus_xfer(i2c, bus_addr, 0, I2C_SMBUS_READ, SFP_PAGE, I2C_SMBUS_BYTE_DATA, &data);
+ if (ret < 0)
+ goto unlock;
+
+ oldpage = data.byte;
+ data.byte = page;
+ ret = __i2c_smbus_xfer(i2c, bus_addr, 0, I2C_SMBUS_WRITE, SFP_PAGE, I2C_SMBUS_BYTE_DATA, &data);
+ if (ret == 0)
+ return oldpage;
+
+unlock:
+ i2c_unlock_bus(i2c, I2C_LOCK_SEGMENT);
+
+ return ret;
+}
+
+static int __smbus_set_sfp_page_unlock(struct i2c_adapter *i2c, int bus_addr, u8 page)
+{
+ union i2c_smbus_data data;
+ int ret;
+
+ data.byte = page;
+ ret = __i2c_smbus_xfer(i2c, bus_addr, 0, I2C_SMBUS_WRITE, SFP_PAGE, I2C_SMBUS_BYTE_DATA, &data);
+ i2c_unlock_bus(i2c, I2C_LOCK_SEGMENT);
+
+ return ret;
+}
+
+/* Wait for the ROLLBALL_CMD_ADDR register to read ROLLBALL_CMD_DONE,
+ * indicating that the previous command has completed.
+ *
+ * Quoting from the mdio-i2c.c implementation:
+ *
+ * By experiment it takes up to 70 ms to access a register for these
+ * SFPs. Sleep 20ms between iterations and try 10 times.
+ */
+static int __smbus_rollball_mii_poll(struct i2c_adapter *i2c , int bus_addr)
+{
+ union i2c_smbus_data data;
+ int i, ret;
+
+ i = 10;
+ do {
+ msleep(20);
+
+ ret = __i2c_smbus_xfer(i2c, bus_addr, 0, I2C_SMBUS_READ, ROLLBALL_CMD_ADDR, I2C_SMBUS_BYTE_DATA, &data);
+ if (ret < 0)
+ return ret;
+
+ if (data.byte == ROLLBALL_CMD_DONE)
+ return 0;
+ } while (i-- > 0);
+ dev_dbg(&i2c->dev, "poll timed out\n");
+ return -ETIMEDOUT;
+}
+
+static int smbus_mii_read_rollball(struct mii_bus *bus, int phy_id, int devad, int reg)
+{
+ struct i2c_adapter *i2c = bus->priv;
+ union i2c_smbus_data data;
+ int i, bus_addr, old, ret;
+ u8 buf[6];
+
+ bus_addr = i2c_mii_phy_addr(phy_id);
+ if (bus_addr != ROLLBALL_PHY_I2C_ADDR)
+ return 0xffff;
+
+ old = smbus_set_sfp_page_lock(i2c, bus_addr, SFP_PAGE_ROLLBALL_MDIO);
+ if (old < 0)
+ return 0xffff;
+
+ /* set address */
+ buf[0] = ROLLBALL_CMD_READ;
+ buf[1] = devad;
+ buf[2] = reg >> 8;
+ buf[3] = reg & 0xff;
+
+ /* send address */
+ for (i = 0; i < 4; i++) {
+ data.byte = buf[i];
+ ret = __i2c_smbus_xfer(i2c, bus_addr, 0, I2C_SMBUS_WRITE, ROLLBALL_CMD_ADDR + i, I2C_SMBUS_BYTE_DATA, &data);
+ if (ret < 0)
+ goto unlock;
+ }
+
+ /* wait for command to complete */
+ ret = __smbus_rollball_mii_poll(i2c, bus_addr);
+ if (ret)
+ goto unlock;
+
+ /* read result */
+ for (i = 4; i < 6; i++) {
+ ret = __i2c_smbus_xfer(i2c, bus_addr, 0, I2C_SMBUS_READ, ROLLBALL_CMD_ADDR + i, I2C_SMBUS_BYTE_DATA, &data);
+ if (ret < 0)
+ goto unlock;
+ buf[i] = data.byte;
+ }
+
+unlock:
+ __smbus_set_sfp_page_unlock(i2c, bus_addr, old);
+ if (ret < 0)
+ return 0xffff;
+ return buf[4] << 8 | buf[5];
+}
+
+static int smbus_mii_write_rollball(struct mii_bus *bus, int phy_id, int devad, int reg, u16 val)
+{
+ struct i2c_adapter *i2c = bus->priv;
+ union i2c_smbus_data data;
+ int i, bus_addr, old, ret;
+ u8 buf[6];
+
+ bus_addr = i2c_mii_phy_addr(phy_id);
+ if (bus_addr != ROLLBALL_PHY_I2C_ADDR)
+ return 0;
+
+ old = smbus_set_sfp_page_lock(i2c, bus_addr, SFP_PAGE_ROLLBALL_MDIO);
+ if (old < 0)
+ return old;
+
+ /* set address */
+ buf[0] = ROLLBALL_CMD_WRITE;
+ buf[1] = devad;
+ buf[2] = reg >> 8;
+ buf[3] = reg & 0xff;
+ buf[4] = val >> 8;
+ buf[5] = val & 0xff;
+
+ /* send address and value */
+ for (i = 0; i < 6; i++) {
+ data.byte = buf[i];
+ ret = __i2c_smbus_xfer(i2c, bus_addr, 0, I2C_SMBUS_WRITE, ROLLBALL_CMD_ADDR + i, I2C_SMBUS_BYTE_DATA, &data);
+ if (ret < 0)
+ goto unlock;
+ }
+
+ /* wait for command to complete */
+ ret = __smbus_rollball_mii_poll(i2c, bus_addr);
+
+unlock:
+ __smbus_set_sfp_page_unlock(i2c, bus_addr, old);
+ return ret;
+}
+
+/* write "password" - four 0xff bytes - to the ROLLBALL_PASSWORD register */
+static int smbus_mii_init_rollball(struct i2c_adapter *i2c)
+{
+ union i2c_smbus_data data;
+ int i, ret;
+
+ data.byte = 0xff;
+ for (i = 0; i < 4; i++) {
+ ret = i2c_smbus_xfer(i2c, ROLLBALL_PHY_I2C_ADDR, 0, I2C_SMBUS_WRITE, ROLLBALL_PASSWORD + i, I2C_SMBUS_BYTE_DATA, &data);
+ if (ret < 0)
+ return ret;
+ }
+ return 0;
+}
+
+struct mii_bus *mdio_smbus_alloc(struct device *parent, struct i2c_adapter *i2c,
+ enum mdio_i2c_proto protocol)
+{
+ struct mii_bus *mii;
+ int ret;
+
+ if (!i2c_check_functionality(i2c, I2C_FUNC_SMBUS_BYTE_DATA))
+ return ERR_PTR(-EINVAL);
+
+ mii = mdiobus_alloc();
+ if (!mii)
+ return ERR_PTR(-ENOMEM);
+
+ snprintf(mii->id, MII_BUS_ID_SIZE, "smbus:%s", dev_name(parent));
+ mii->parent = parent;
+ mii->priv = i2c;
+
+ switch (protocol) {
+ case MDIO_I2C_ROLLBALL:
+ ret = smbus_mii_init_rollball(i2c);
+ if (ret < 0) {
+ dev_err(parent,
+ "Cannot initialize RollBall MDIO protocol on SMBus: %d\n",
+ ret);
+ mdiobus_free(mii);
+ return ERR_PTR(ret);
+ }
+
+ mii->read_c45 = smbus_mii_read_rollball;
+ mii->write_c45 = smbus_mii_write_rollball;
+ break;
+ default:
+ mii->read = smbus_mii_read_c22;
+ mii->write = smbus_mii_write_c22;
+ mii->read_c45 = smbus_mii_read_c45;
+ mii->write_c45 = smbus_mii_write_c45;
+ break;
+ }
+
+ return mii;
+}
+
+MODULE_AUTHOR("Antoine Tenart");
+MODULE_DESCRIPTION("MDIO SMBus bridge library");
+MODULE_LICENSE("GPL");
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -65,6 +65,7 @@ config SFP
depends on I2C && PHYLINK
depends on HWMON || HWMON=n
select MDIO_I2C
+ select MDIO_SMBUS
comment "Switch configuration API + drivers"
--- a/include/linux/mdio/mdio-i2c.h
+++ b/include/linux/mdio/mdio-i2c.h
@@ -20,5 +20,9 @@ enum mdio_i2c_proto {
struct mii_bus *mdio_i2c_alloc(struct device *parent, struct i2c_adapter *i2c,
enum mdio_i2c_proto protocol);
+struct mii_bus *mdio_smbus_alloc(struct device *parent, struct i2c_adapter *i2c,
+ enum mdio_i2c_proto protocol);
+bool i2c_mii_valid_phy_id(int phy_id);
+unsigned int i2c_mii_phy_addr(int phy_id);
#endif
--- a/drivers/net/mdio/mdio-i2c.c
+++ b/drivers/net/mdio/mdio-i2c.c
@@ -20,12 +20,12 @@
* specified to be present in SFP modules. These correspond with PHY
* addresses 16 and 17. Disallow access to these "phy" addresses.
*/
-static bool i2c_mii_valid_phy_id(int phy_id)
+bool i2c_mii_valid_phy_id(int phy_id)
{
return phy_id != 0x10 && phy_id != 0x11;
}
-static unsigned int i2c_mii_phy_addr(int phy_id)
+unsigned int i2c_mii_phy_addr(int phy_id)
{
return phy_id + 0x40;
}

View file

@ -0,0 +1,126 @@
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 | 92 +++++++++++++++++++++++++++++++++++++++++--
1 file changed, 88 insertions(+), 4 deletions(-)
--- a/drivers/net/phy/sfp.c
+++ b/drivers/net/phy/sfp.c
@@ -675,10 +675,64 @@ static int sfp_i2c_write(struct sfp *sfp
return ret == ARRAY_SIZE(msgs) ? len : 0;
}
+static int sfp_smbus_read(struct sfp *sfp, bool a2, u8 dev_addr, void *buf,
+ size_t len)
+{
+ u8 bus_addr = a2 ? 0x51 : 0x50, *val = buf;
+ union i2c_smbus_data data;
+ int ret;
+
+ bus_addr -= 0x40;
+
+ while (len > 0) {
+ ret = i2c_smbus_xfer(sfp->i2c, i2c_mii_phy_addr(bus_addr), 0,
+ I2C_SMBUS_READ, dev_addr,
+ I2C_SMBUS_BYTE_DATA, &data);
+ if (ret)
+ return ret;
+ *val++ = data.byte;
+ dev_addr++;
+ len--;
+ }
+
+ return val - (u8 *)buf;
+}
+
+static int sfp_smbus_write(struct sfp *sfp, bool a2, u8 dev_addr, void *buf,
+ size_t len)
+{
+ u8 bus_addr = a2 ? 0x51 : 0x50, *val = buf;
+ union i2c_smbus_data data;
+ int ret;
+
+ bus_addr -= 0x40;
+
+ while (len > 0) {
+ data.byte = *val++;
+ ret = i2c_smbus_xfer(sfp->i2c, i2c_mii_phy_addr(bus_addr), 0,
+ I2C_SMBUS_WRITE, dev_addr,
+ I2C_SMBUS_BYTE_DATA, &data);
+ if (ret)
+ return ret;
+ dev_addr++;
+ len--;
+ }
+
+ return val - (u8 *)buf;
+}
+
static int sfp_i2c_configure(struct sfp *sfp, struct i2c_adapter *i2c)
{
- if (!i2c_check_functionality(i2c, I2C_FUNC_I2C))
- return -EINVAL;
+ if (!i2c_check_functionality(i2c, I2C_FUNC_I2C)) {
+ if (i2c_check_functionality(i2c, I2C_FUNC_SMBUS_BYTE_DATA)) {
+ sfp->i2c = i2c;
+ sfp->read = sfp_smbus_read;
+ sfp->write = sfp_smbus_write;
+
+ return 0;
+ } else
+ return -EINVAL;
+ }
sfp->i2c = i2c;
sfp->read = sfp_i2c_read;
@@ -710,6 +764,29 @@ static int sfp_i2c_mdiobus_create(struct
return 0;
}
+static int sfp_sm_mdiobus_create(struct sfp *sfp)
+{
+ struct mii_bus *sm_mii;
+ int ret;
+
+ sm_mii = mdio_smbus_alloc(sfp->dev, sfp->i2c, sfp->mdio_protocol);
+ if (IS_ERR(sm_mii))
+ return PTR_ERR(sm_mii);
+
+ sm_mii->name = "SFP SMBus";
+ sm_mii->phy_mask = ~0;
+
+ ret = mdiobus_register(sm_mii);
+ if (ret < 0) {
+ mdiobus_free(sm_mii);
+ return ret;
+ }
+
+ sfp->i2c_mii = sm_mii;
+
+ return 0;
+}
+
static void sfp_i2c_mdiobus_destroy(struct sfp *sfp)
{
mdiobus_unregister(sfp->i2c_mii);
@@ -1884,9 +1961,15 @@ static void sfp_sm_fault(struct sfp *sfp
static int sfp_sm_add_mdio_bus(struct sfp *sfp)
{
- if (sfp->mdio_protocol != MDIO_I2C_NONE)
+ if (sfp->mdio_protocol == MDIO_I2C_NONE)
+ return 0;
+
+ if (i2c_check_functionality(sfp->i2c, I2C_FUNC_I2C))
return sfp_i2c_mdiobus_create(sfp);
+ if (i2c_check_functionality(sfp->i2c, I2C_FUNC_SMBUS_BYTE_DATA))
+ return sfp_sm_mdiobus_create(sfp);
+
return 0;
}

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
@@ -170,6 +170,13 @@ source "drivers/net/ethernet/rdc/Kconfig
source "drivers/net/ethernet/realtek/Kconfig"
source "drivers/net/ethernet/renesas/Kconfig"
source "drivers/net/ethernet/rocker/Kconfig"
+
+config NET_RTL838X
+ tristate "Realtek rtl838x Ethernet MAC support"
+ depends on MACH_REALTEK_RTL
+ help
+ Say Y here if you want to use the Realtek rtl838x Gbps Ethernet MAC.
+
source "drivers/net/ethernet/samsung/Kconfig"
source "drivers/net/ethernet/seeq/Kconfig"
source "drivers/net/ethernet/sgi/Kconfig"
--- a/drivers/net/ethernet/Makefile
+++ b/drivers/net/ethernet/Makefile
@@ -81,6 +81,7 @@ obj-$(CONFIG_NET_VENDOR_REALTEK) += real
obj-$(CONFIG_NET_VENDOR_RENESAS) += renesas/
obj-$(CONFIG_NET_VENDOR_RDC) += rdc/
obj-$(CONFIG_NET_VENDOR_ROCKER) += rocker/
+obj-$(CONFIG_NET_RTL838X) += rtl838x_eth.o
obj-$(CONFIG_NET_VENDOR_SAMSUNG) += samsung/
obj-$(CONFIG_NET_VENDOR_SEEQ) += seeq/
obj-$(CONFIG_NET_VENDOR_SILAN) += silan/

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
@@ -89,6 +89,8 @@ source "drivers/net/dsa/xrs700x/Kconfig"
source "drivers/net/dsa/realtek/Kconfig"
+source "drivers/net/dsa/rtl83xx/Kconfig"
+
config NET_DSA_RZN1_A5PSW
tristate "Renesas RZ/N1 A5PSW Ethernet switch support"
depends on OF && ARCH_RZN1
--- a/drivers/net/dsa/Makefile
+++ b/drivers/net/dsa/Makefile
@@ -24,5 +24,6 @@ obj-y += mv88e6xxx/
obj-y += ocelot/
obj-y += qca/
obj-y += realtek/
+obj-y += rtl83xx/
obj-y += sja1105/
obj-y += xrs700x/

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
@@ -409,6 +409,12 @@ source "drivers/net/phy/realtek/Kconfig"
source "drivers/net/phy/rtl8261n/Kconfig"
+config REALTEK_SOC_PHY
+ tristate "Realtek SoC PHYs"
+ depends on MACH_REALTEK_RTL
+ help
+ Supports the PHYs found in combination with Realtek Switch SoCs
+
config RENESAS_PHY
tristate "Renesas PHYs"
help
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile
@@ -101,6 +101,7 @@ obj-y += qcom/
obj-$(CONFIG_QSEMI_PHY) += qsemi.o
obj-$(CONFIG_REALTEK_PHY) += realtek/
obj-y += rtl8261n/
+obj-$(CONFIG_REALTEK_SOC_PHY) += rtl83xx-phy.o
obj-$(CONFIG_RENESAS_PHY) += uPD60620.o
obj-$(CONFIG_ROCKCHIP_PHY) += rockchip.o
obj-$(CONFIG_SMSC_PHY) += smsc.o

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
@@ -19,7 +19,12 @@ static struct sk_buff *trailer_xmit(stru
trailer = skb_put(skb, 4);
trailer[0] = 0x80;
+
+#ifdef CONFIG_NET_DSA_RTL83XX
+ trailer[1] = dp->index;
+#else
trailer[1] = 1 << dp->index;
+#endif /* CONFIG_NET_DSA_RTL838X */
trailer[2] = 0x10;
trailer[3] = 0x00;
@@ -35,12 +40,23 @@ static struct sk_buff *trailer_rcv(struc
return NULL;
trailer = skb_tail_pointer(skb) - 4;
+
+#ifdef CONFIG_NET_DSA_RTL83XX
+ if (trailer[0] != 0x80 || (trailer[1] & 0x80) != 0x00 ||
+ (trailer[2] & 0xef) != 0x00 || trailer[3] != 0x00)
+ return NULL;
+
+ if (trailer[1] & 0x40)
+ skb->offload_fwd_mark = 1;
+
+ source_port = trailer[1] & 0x3f;
+#else
if (trailer[0] != 0x80 || (trailer[1] & 0xf8) != 0x00 ||
(trailer[2] & 0xef) != 0x00 || trailer[3] != 0x00)
return NULL;
source_port = trailer[1] & 7;
-
+#endif
skb->dev = dsa_master_find_slave(dev, 0, source_port);
if (!skb->dev)
return NULL;

View file

@ -0,0 +1,227 @@
From ffb7da9aa25765b2115e7ff3ee4f6dafa60f5421 Mon Sep 17 00:00:00 2001
From: Sander Vanheule <sander@svanheule.net>
Date: Fri, 27 Dec 2024 14:55:31 +0100
Subject: [PATCH] net: mdio: Add Realtek Otto auxiliary controller
SoCs in Realtek's Otto platform such as the RTL8380, RTL8391, and
RTL9302 have a simple auxiliary MDIO controller that is commonly used to
manage RTL8231 GPIO expanders on switch devices.
Add a new MDIO controller driver supporting the RTL838x (maple), RTL839x
(cypress), and RTL930x (longan) SoCs.
Signed-off-by: Sander Vanheule <sander@svanheule.net>
---
drivers/net/mdio/Kconfig | 10 ++
drivers/net/mdio/Makefile | 1 +
drivers/net/mdio/mdio-realtek-otto-aux.c | 175 +++++++++++++++++++++++
3 files changed, 186 insertions(+)
create mode 100644 drivers/net/mdio/mdio-realtek-otto-aux.c
--- a/drivers/net/mdio/Kconfig
+++ b/drivers/net/mdio/Kconfig
@@ -207,6 +207,16 @@ config MDIO_REGMAP
regmap. Users willing to use this driver must explicitly select
REGMAP.
+config MDIO_REALTEK_OTTO_AUX
+ tristate "Realtek Otto auxiliary MDIO interface support"
+ default MACH_REALTEK_RTL
+ depends on MACH_REALTEK_RTL
+ depends on MFD_SYSCON
+ select MDIO_DEVRES
+ help
+ This driver supports the auxilairy MDIO bus on RTL838x SoCs. This bus
+ is typically used to attach RTL8231 GPIO extenders.
+
config MDIO_THUNDER
tristate "ThunderX SOCs MDIO buses"
depends on 64BIT
--- a/drivers/net/mdio/Makefile
+++ b/drivers/net/mdio/Makefile
@@ -20,6 +20,7 @@ obj-$(CONFIG_MDIO_MSCC_MIIM) += mdio-ms
obj-$(CONFIG_MDIO_MVUSB) += mdio-mvusb.o
obj-$(CONFIG_MDIO_OCTEON) += mdio-octeon.o
obj-$(CONFIG_MDIO_REGMAP) += mdio-regmap.o
+obj-$(CONFIG_MDIO_REALTEK_OTTO_AUX) += mdio-realtek-otto-aux.o
obj-$(CONFIG_MDIO_SMBUS) += mdio-smbus.o
obj-$(CONFIG_MDIO_SUN4I) += mdio-sun4i.o
obj-$(CONFIG_MDIO_THUNDER) += mdio-thunder.o
--- /dev/null
+++ b/drivers/net/mdio/mdio-realtek-otto-aux.c
@@ -0,0 +1,175 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <linux/mfd/core.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_mdio.h>
+#include <linux/of_platform.h>
+#include <linux/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#define RTL8380_EXT_GPIO_INDIRECT_ACCESS 0xA09C
+#define RTL8390_EXT_GPIO_INDIRECT_ACCESS 0x0224
+#define RTL9300_EXT_GPIO_INDIRECT_ACCESS 0xC620
+
+#define RTL83XX_AUX_MDIO_DATA_OFFSET 16
+#define RTL83XX_AUX_MDIO_RCMD_FAIL 0
+
+#define RTL93XX_AUX_MDIO_DATA_OFFSET 12
+#define RTL93XX_AUX_MDIO_RCMD_FAIL BIT(28)
+
+#define REALTEK_AUX_MDIO_REG GENMASK(11, 7)
+#define REALTEK_AUX_MDIO_PHY_ADDR GENMASK(6, 2)
+#define REALTEK_AUX_MDIO_WRITE BIT(1)
+#define REALTEK_AUX_MDIO_READ 0
+#define REALTEK_AUX_MDIO_EXEC BIT(0)
+
+struct realtek_aux_mdio_info {
+ unsigned int cmd_reg;
+ unsigned int data_offset;
+ unsigned int rcmd_fail_mask;
+ unsigned int timeout_us;
+};
+
+static const struct realtek_aux_mdio_info info_rtl838x = {
+ .cmd_reg = RTL8380_EXT_GPIO_INDIRECT_ACCESS,
+ .data_offset = RTL83XX_AUX_MDIO_DATA_OFFSET,
+ .rcmd_fail_mask = RTL83XX_AUX_MDIO_RCMD_FAIL,
+ .timeout_us = 1700,
+};
+
+static const struct realtek_aux_mdio_info info_rtl839x = {
+ .cmd_reg = RTL8390_EXT_GPIO_INDIRECT_ACCESS,
+ .data_offset = RTL83XX_AUX_MDIO_DATA_OFFSET,
+ .rcmd_fail_mask = RTL83XX_AUX_MDIO_RCMD_FAIL,
+ .timeout_us = 4120,
+};
+
+static const struct realtek_aux_mdio_info info_rtl930x = {
+ .cmd_reg = RTL9300_EXT_GPIO_INDIRECT_ACCESS,
+ .data_offset = RTL93XX_AUX_MDIO_DATA_OFFSET,
+ .rcmd_fail_mask = RTL93XX_AUX_MDIO_RCMD_FAIL,
+ .timeout_us = 19000,
+};
+
+struct realtek_aux_mdio_ctrl {
+ struct device *dev;
+ struct regmap *map;
+ const struct realtek_aux_mdio_info *info;
+};
+
+#define mii_bus_to_ctrl(bus) ((struct realtek_aux_mdio_ctrl *) bus->priv)
+
+static int realtek_aux_mdio_cmd(struct realtek_aux_mdio_ctrl *ctrl, int addr, int regnum,
+ u32 rw_bit, u16 *data)
+{
+ unsigned int cmd;
+ int err;
+
+ cmd = rw_bit | REALTEK_AUX_MDIO_EXEC;
+ cmd |= FIELD_PREP(REALTEK_AUX_MDIO_PHY_ADDR, addr);
+ cmd |= FIELD_PREP(REALTEK_AUX_MDIO_REG, regnum);
+
+ if (rw_bit == REALTEK_AUX_MDIO_WRITE)
+ cmd |= *data << ctrl->info->data_offset;
+
+ err = regmap_write(ctrl->map, ctrl->info->cmd_reg, cmd);
+ if (err)
+ return err;
+
+ err = regmap_read_poll_timeout_atomic(ctrl->map, ctrl->info->cmd_reg, cmd,
+ !(cmd & REALTEK_AUX_MDIO_EXEC), 3, ctrl->info->timeout_us);
+ if (err)
+ return err;
+
+ if (rw_bit == REALTEK_AUX_MDIO_READ) {
+ if (cmd & ctrl->info->rcmd_fail_mask)
+ return -EIO;
+
+ *data = (cmd >> ctrl->info->data_offset) & GENMASK(15, 0);
+ }
+
+ return 0;
+}
+
+static int realtek_aux_mdio_read(struct mii_bus *bus, int addr, int regnum)
+{
+ struct realtek_aux_mdio_ctrl *ctrl = mii_bus_to_ctrl(bus);
+ u16 data;
+ int err;
+
+ err = realtek_aux_mdio_cmd(ctrl, addr, regnum, REALTEK_AUX_MDIO_READ, &data);
+
+ if (err)
+ return err;
+ else
+ return data;
+}
+
+static int realtek_aux_mdio_write(struct mii_bus *bus, int addr, int regnum, u16 val)
+{
+ struct realtek_aux_mdio_ctrl *ctrl = mii_bus_to_ctrl(bus);
+
+ return realtek_aux_mdio_cmd(ctrl, addr, regnum, REALTEK_AUX_MDIO_WRITE, &val);
+}
+
+static int realtek_aux_mdio_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct realtek_aux_mdio_ctrl *ctrl;
+ struct mii_bus *bus;
+
+ bus = devm_mdiobus_alloc_size(&pdev->dev, sizeof(*ctrl));
+ if (!bus)
+ return -ENOMEM;
+
+ ctrl = bus->priv;
+ ctrl->dev = &pdev->dev;
+ ctrl->info = (const struct realtek_aux_mdio_info *) device_get_match_data(ctrl->dev);
+ ctrl->map = syscon_node_to_regmap(np->parent);
+ if (IS_ERR(ctrl->map))
+ return PTR_ERR(ctrl->map);
+
+ bus->name = "Realtek auxiliary MDIO bus";
+ snprintf(bus->id, MII_BUS_ID_SIZE, "realtek-aux-mdio") ;
+ bus->parent = ctrl->dev;
+ bus->read = realtek_aux_mdio_read;
+ bus->write = realtek_aux_mdio_write;
+ /* Don't have interrupts */
+ for (unsigned int i = 0; i < PHY_MAX_ADDR; i++)
+ bus->irq[i] = PHY_POLL;
+
+ return devm_of_mdiobus_register(ctrl->dev, bus, np);
+}
+
+static const struct of_device_id realtek_aux_mdio_of_match[] = {
+ {
+ .compatible = "realtek,rtl8380-aux-mdio",
+ .data = &info_rtl838x,
+ },
+ {
+ .compatible = "realtek,rtl8390-aux-mdio",
+ .data = &info_rtl839x,
+ },
+ {
+ .compatible = "realtek,rtl9300-aux-mdio",
+ .data = &info_rtl930x,
+ },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, realtek_aux_mdio_of_match);
+
+static struct platform_driver realtek_aux_mdio_driver = {
+ .driver = {
+ .name = "realtek-otto-aux-mdio",
+ .of_match_table = realtek_aux_mdio_of_match
+ },
+ .probe = realtek_aux_mdio_probe,
+};
+module_platform_driver(realtek_aux_mdio_driver);
+
+MODULE_AUTHOR("Sander Vanheule <sander@svanheule.net>");
+MODULE_DESCRIPTION("Realtek otto auxiliary MDIO bus");
+MODULE_LICENSE("GPL v2");

View file

@ -0,0 +1,56 @@
From b3f79468c90d8770f007d628a1e32b2d5d44a5c2 Mon Sep 17 00:00:00 2001
From: Sander Vanheule <sander@svanheule.net>
Date: Sat, 15 May 2021 11:57:32 +0200
Subject: [PATCH] gpio: regmap: Bypass cache for shadowed outputs
Some chips have the read-only input and write-only output data registers
aliased to the same offset, but do not perform direction multiplexing on
writes. Upon writing the register, this then always updates the output
value, even when the pin is configured as input. As a result it is not
safe to perform read-modify-writes on output pins, when other pins are
still configured as input.
For example, on a bit-banged I2C bus, where the lines are switched
between out-low and in (with external pull-up)
OUT(L) IN OUT(H)
SCK ....../''''''|''''''
SDA '''''''''\..........
^ ^- SCK switches to direction to OUT, but now has a high
| value, breaking the clock.
|
\- Perform RMW to update SDA. This reads the current input
value for SCK, updates the SDA value and writes back a 1
for SCK as well.
If a register is used for both the data input and data output (and is
not marked as volatile) the driver should ensure the cache is not
updated on register reads. This ensures proper functioning of writing
the output register with regmap_update_bits(), which will then use and
update the cache only on register writes.
Signed-off-by: Sander Vanheule <sander@svanheule.net>
---
drivers/gpio/gpio-regmap.c | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
--- a/drivers/gpio/gpio-regmap.c
+++ b/drivers/gpio/gpio-regmap.c
@@ -74,7 +74,15 @@ static int gpio_regmap_get(struct gpio_c
if (ret)
return ret;
- ret = regmap_read(gpio->regmap, reg, &val);
+ /*
+ * Ensure we don't spoil the register cache with pin input values and
+ * perform a bypassed read. This way the cache (if any) is only used and
+ * updated on register writes.
+ */
+ if (gpio->reg_dat_base == gpio->reg_set_base)
+ ret = regmap_read_bypassed(gpio->regmap, reg, &val);
+ else
+ ret = regmap_read(gpio->regmap, reg, &val);
if (ret)
return ret;

View file

@ -0,0 +1,330 @@
From 4e3455e058d40eb2a7326016494e3c81dc506c33 Mon Sep 17 00:00:00 2001
From: Sander Vanheule <sander@svanheule.net>
Date: Mon, 10 May 2021 18:33:01 +0200
Subject: [PATCH] mfd: Add RTL8231 core device
The RTL8231 is implemented as an MDIO device, and provides a regmap
interface for register access by the core and child devices.
The chip can also be a device on an SMI bus, an I2C-like bus by Realtek.
Since kernel support for SMI is limited, and no real-world SMI
implementations have been encountered for this device, this is currently
unimplemented. The use of the regmap interface should make any future
support relatively straightforward.
After reset, all pins are muxed to GPIO inputs before the pin drivers
are enabled. This is done to prevent accidental system resets, when a
pin is connected to the parent SoC's reset line.
To provide different read and write semantics for the GPIO data
registers, a secondary virtual register range is used to enable separate
caching properties of pin input and output values.
Signed-off-by: Sander Vanheule <sander@svanheule.net>
---
drivers/mfd/Kconfig | 9 ++
drivers/mfd/Makefile | 1 +
drivers/mfd/rtl8231.c | 193 ++++++++++++++++++++++++++++++++++++
include/linux/mfd/rtl8231.h | 71 +++++++++++++
4 files changed, 274 insertions(+)
create mode 100644 drivers/mfd/rtl8231.c
create mode 100644 include/linux/mfd/rtl8231.h
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -1171,6 +1171,15 @@ config MFD_RDC321X
southbridge which provides access to GPIOs and Watchdog using the
southbridge PCI device configuration space.
+config MFD_RTL8231
+ tristate "Realtek RTL8231 GPIO and LED expander"
+ select MFD_CORE
+ select REGMAP_MDIO
+ help
+ Support for the Realtek RTL8231 GPIO and LED expander.
+ Provides up to 37 GPIOs, 88 LEDs, and one PWM output.
+ When built as a module, this module will be named rtl8231.
+
config MFD_RT4831
tristate "Richtek RT4831 four channel WLED and Display Bias Voltage"
depends on I2C
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -240,6 +240,7 @@ obj-$(CONFIG_MFD_HI6421_PMIC) += hi6421-
obj-$(CONFIG_MFD_HI6421_SPMI) += hi6421-spmi-pmic.o
obj-$(CONFIG_MFD_HI655X_PMIC) += hi655x-pmic.o
obj-$(CONFIG_MFD_DLN2) += dln2.o
+obj-$(CONFIG_MFD_RTL8231) += rtl8231.o
obj-$(CONFIG_MFD_RT4831) += rt4831.o
obj-$(CONFIG_MFD_RT5033) += rt5033.o
obj-$(CONFIG_MFD_RT5120) += rt5120.o
--- /dev/null
+++ b/drivers/mfd/rtl8231.c
@@ -0,0 +1,193 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/bits.h>
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/mfd/core.h>
+#include <linux/mdio.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+
+#include <linux/mfd/rtl8231.h>
+
+static bool rtl8231_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ /*
+ * Registers with self-clearing bits, strapping pin values.
+ * Don't mark the data registers as volatile, since we need
+ * caching for the output values.
+ */
+ case RTL8231_REG_FUNC0:
+ case RTL8231_REG_FUNC1:
+ case RTL8231_REG_PIN_HI_CFG:
+ case RTL8231_REG_LED_END:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct reg_field RTL8231_FIELD_LED_START = REG_FIELD(RTL8231_REG_FUNC0, 1, 1);
+
+static const struct mfd_cell rtl8231_cells[] = {
+ {
+ .name = "rtl8231-pinctrl",
+ },
+ {
+ .name = "rtl8231-leds",
+ .of_compatible = "realtek,rtl8231-leds",
+ },
+};
+
+static int rtl8231_soft_reset(struct regmap *map)
+{
+ const unsigned int all_pins_mask = GENMASK(RTL8231_BITS_VAL - 1, 0);
+ unsigned int val;
+ int err;
+
+ /* SOFT_RESET bit self-clears when done */
+ regmap_write_bits(map, RTL8231_REG_PIN_HI_CFG,
+ RTL8231_PIN_HI_CFG_SOFT_RESET, RTL8231_PIN_HI_CFG_SOFT_RESET);
+ err = regmap_read_poll_timeout(map, RTL8231_REG_PIN_HI_CFG, val,
+ !(val & RTL8231_PIN_HI_CFG_SOFT_RESET), 50, 1000);
+ if (err)
+ return err;
+
+ regcache_mark_dirty(map);
+
+ /*
+ * Chip reset results in a pin configuration that is a mix of LED and GPIO outputs.
+ * Select GPI functionality for all pins before enabling pin outputs.
+ */
+ regmap_write(map, RTL8231_REG_PIN_MODE0, all_pins_mask);
+ regmap_write(map, RTL8231_REG_GPIO_DIR0, all_pins_mask);
+ regmap_write(map, RTL8231_REG_PIN_MODE1, all_pins_mask);
+ regmap_write(map, RTL8231_REG_GPIO_DIR1, all_pins_mask);
+ regmap_write(map, RTL8231_REG_PIN_HI_CFG,
+ RTL8231_PIN_HI_CFG_MODE_MASK | RTL8231_PIN_HI_CFG_DIR_MASK);
+
+ return 0;
+}
+
+static int rtl8231_init(struct device *dev, struct regmap *map)
+{
+ struct regmap_field *led_start;
+ unsigned int started;
+ unsigned int val;
+ int err;
+
+ err = regmap_read(map, RTL8231_REG_FUNC1, &val);
+ if (err) {
+ dev_err(dev, "failed to read READY_CODE\n");
+ return err;
+ }
+
+ val = FIELD_GET(RTL8231_FUNC1_READY_CODE_MASK, val);
+ if (val != RTL8231_FUNC1_READY_CODE_VALUE) {
+ dev_err(dev, "RTL8231 not present or ready 0x%x != 0x%x\n",
+ val, RTL8231_FUNC1_READY_CODE_VALUE);
+ return -ENODEV;
+ }
+
+ led_start = dev_get_drvdata(dev);
+ err = regmap_field_read(led_start, &started);
+ if (err)
+ return err;
+
+ if (!started) {
+ err = rtl8231_soft_reset(map);
+ if (err)
+ return err;
+ /* LED_START enables power to output pins, and starts the LED engine */
+ err = regmap_field_force_write(led_start, 1);
+ }
+
+ return err;
+}
+
+static const struct regmap_config rtl8231_mdio_regmap_config = {
+ .val_bits = RTL8231_BITS_VAL,
+ .reg_bits = RTL8231_BITS_REG,
+ .volatile_reg = rtl8231_volatile_reg,
+ .max_register = RTL8231_REG_COUNT - 1,
+ .use_single_read = true,
+ .use_single_write = true,
+ .reg_format_endian = REGMAP_ENDIAN_BIG,
+ .val_format_endian = REGMAP_ENDIAN_BIG,
+ /* Cannot use REGCACHE_FLAT because it's not smart enough about cache invalidation */
+ .cache_type = REGCACHE_RBTREE,
+};
+
+static int rtl8231_mdio_probe(struct mdio_device *mdiodev)
+{
+ struct device *dev = &mdiodev->dev;
+ struct regmap_field *led_start;
+ struct regmap *map;
+ int err;
+
+ map = devm_regmap_init_mdio(mdiodev, &rtl8231_mdio_regmap_config);
+ if (IS_ERR(map)) {
+ dev_err(dev, "failed to init regmap\n");
+ return PTR_ERR(map);
+ }
+
+ led_start = devm_regmap_field_alloc(dev, map, RTL8231_FIELD_LED_START);
+ if (IS_ERR(led_start))
+ return PTR_ERR(led_start);
+
+ dev_set_drvdata(dev, led_start);
+
+ mdiodev->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
+ if (IS_ERR(mdiodev->reset_gpio))
+ return PTR_ERR(mdiodev->reset_gpio);
+
+ device_property_read_u32(dev, "reset-assert-delay", &mdiodev->reset_assert_delay);
+ device_property_read_u32(dev, "reset-deassert-delay", &mdiodev->reset_deassert_delay);
+
+ err = rtl8231_init(dev, map);
+ if (err)
+ return err;
+
+ return devm_mfd_add_devices(dev, PLATFORM_DEVID_AUTO, rtl8231_cells,
+ ARRAY_SIZE(rtl8231_cells), NULL, 0, NULL);
+}
+
+__maybe_unused static int rtl8231_suspend(struct device *dev)
+{
+ struct regmap_field *led_start = dev_get_drvdata(dev);
+
+ return regmap_field_force_write(led_start, 0);
+}
+
+__maybe_unused static int rtl8231_resume(struct device *dev)
+{
+ struct regmap_field *led_start = dev_get_drvdata(dev);
+
+ return regmap_field_force_write(led_start, 1);
+}
+
+static SIMPLE_DEV_PM_OPS(rtl8231_pm_ops, rtl8231_suspend, rtl8231_resume);
+
+static const struct of_device_id rtl8231_of_match[] = {
+ { .compatible = "realtek,rtl8231" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, rtl8231_of_match);
+
+static struct mdio_driver rtl8231_mdio_driver = {
+ .mdiodrv.driver = {
+ .name = "rtl8231-expander",
+ .of_match_table = rtl8231_of_match,
+ .pm = pm_ptr(&rtl8231_pm_ops),
+ },
+ .probe = rtl8231_mdio_probe,
+};
+mdio_module_driver(rtl8231_mdio_driver);
+
+MODULE_AUTHOR("Sander Vanheule <sander@svanheule.net>");
+MODULE_DESCRIPTION("Realtek RTL8231 GPIO and LED expander");
+MODULE_LICENSE("GPL");
--- /dev/null
+++ b/include/linux/mfd/rtl8231.h
@@ -0,0 +1,71 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Register definitions the RTL8231 GPIO and LED expander chip
+ */
+
+#ifndef __LINUX_MFD_RTL8231_H
+#define __LINUX_MFD_RTL8231_H
+
+#include <linux/bits.h>
+
+/*
+ * Registers addresses are 5 bit, values are 16 bit
+ * Also define a duplicated range of virtual addresses, to enable
+ * different read/write behaviour on the GPIO data registers
+ */
+#define RTL8231_BITS_VAL 16
+#define RTL8231_BITS_REG 5
+
+/* Chip control */
+#define RTL8231_REG_FUNC0 0x00
+#define RTL8231_FUNC0_SCAN_MODE BIT(0)
+#define RTL8231_FUNC0_SCAN_SINGLE 0
+#define RTL8231_FUNC0_SCAN_BICOLOR BIT(0)
+
+#define RTL8231_REG_FUNC1 0x01
+#define RTL8231_FUNC1_READY_CODE_VALUE 0x37
+#define RTL8231_FUNC1_READY_CODE_MASK GENMASK(9, 4)
+#define RTL8231_FUNC1_DEBOUNCE_MASK GENMASK(15, 10)
+
+/* Pin control */
+#define RTL8231_REG_PIN_MODE0 0x02
+#define RTL8231_REG_PIN_MODE1 0x03
+
+#define RTL8231_PIN_MODE_LED 0
+#define RTL8231_PIN_MODE_GPIO 1
+
+/* Pin high config: pin and GPIO control for pins 32-26 */
+#define RTL8231_REG_PIN_HI_CFG 0x04
+#define RTL8231_PIN_HI_CFG_MODE_MASK GENMASK(4, 0)
+#define RTL8231_PIN_HI_CFG_DIR_MASK GENMASK(9, 5)
+#define RTL8231_PIN_HI_CFG_INV_MASK GENMASK(14, 10)
+#define RTL8231_PIN_HI_CFG_SOFT_RESET BIT(15)
+
+/* GPIO control registers */
+#define RTL8231_REG_GPIO_DIR0 0x05
+#define RTL8231_REG_GPIO_DIR1 0x06
+#define RTL8231_REG_GPIO_INVERT0 0x07
+#define RTL8231_REG_GPIO_INVERT1 0x08
+
+#define RTL8231_GPIO_DIR_IN 1
+#define RTL8231_GPIO_DIR_OUT 0
+
+/*
+ * GPIO data registers
+ * Only the output data can be written to these registers, and only the input
+ * data can be read.
+ */
+#define RTL8231_REG_GPIO_DATA0 0x1c
+#define RTL8231_REG_GPIO_DATA1 0x1d
+#define RTL8231_REG_GPIO_DATA2 0x1e
+#define RTL8231_PIN_HI_DATA_MASK GENMASK(4, 0)
+
+/* LED control base registers */
+#define RTL8231_REG_LED0_BASE 0x09
+#define RTL8231_REG_LED1_BASE 0x10
+#define RTL8231_REG_LED2_BASE 0x17
+#define RTL8231_REG_LED_END 0x1b
+
+#define RTL8231_REG_COUNT 0x1f
+
+#endif /* __LINUX_MFD_RTL8231_H */

View file

@ -0,0 +1,581 @@
From 098324288a63a6dcc44e96cc381aef3d5c48d89e Mon Sep 17 00:00:00 2001
From: Sander Vanheule <sander@svanheule.net>
Date: Mon, 10 May 2021 22:15:31 +0200
Subject: [PATCH] pinctrl: Add RTL8231 pin control and GPIO support
This driver implements the GPIO and pin muxing features provided by the
RTL8231. The device should be instantiated as an MFD child, where the
parent device has already configured the regmap used for register
access.
Debouncing is only available for the six highest GPIOs, and must be
emulated when other pins are used for (button) inputs. Although
described in the bindings, drive strength selection is currently not
implemented.
Signed-off-by: Sander Vanheule <sander@svanheule.net>
---
drivers/pinctrl/Kconfig | 11 +
drivers/pinctrl/Makefile | 1 +
drivers/pinctrl/pinctrl-rtl8231.c | 521 ++++++++++++++++++++++++++++++
3 files changed, 533 insertions(+)
create mode 100644 drivers/pinctrl/pinctrl-rtl8231.c
--- a/drivers/pinctrl/Kconfig
+++ b/drivers/pinctrl/Kconfig
@@ -417,6 +417,17 @@ config PINCTRL_ROCKCHIP
help
This support pinctrl and GPIO driver for Rockchip SoCs.
+config PINCTRL_RTL8231
+ tristate "Realtek RTL8231 GPIO expander's pin controller"
+ depends on MFD_RTL8231
+ default MFD_RTL8231
+ select GPIO_REGMAP
+ select GENERIC_PINCONF
+ select GENERIC_PINMUX_FUNCTIONS
+ help
+ Support for RTL8231 expander's GPIOs and pin controller.
+ When built as a module, the module will be called pinctrl-rtl8231.
+
config PINCTRL_SINGLE
tristate "One-register-per-pin type device tree based pinctrl driver"
depends on OF
--- a/drivers/pinctrl/Makefile
+++ b/drivers/pinctrl/Makefile
@@ -43,6 +43,7 @@ obj-$(CONFIG_PINCTRL_PIC32) += pinctrl-p
obj-$(CONFIG_PINCTRL_PISTACHIO) += pinctrl-pistachio.o
obj-$(CONFIG_PINCTRL_RK805) += pinctrl-rk805.o
obj-$(CONFIG_PINCTRL_ROCKCHIP) += pinctrl-rockchip.o
+obj-$(CONFIG_PINCTRL_RTL8231) += pinctrl-rtl8231.o
obj-$(CONFIG_PINCTRL_SINGLE) += pinctrl-single.o
obj-$(CONFIG_PINCTRL_ST) += pinctrl-st.o
obj-$(CONFIG_PINCTRL_STMFX) += pinctrl-stmfx.o
--- /dev/null
+++ b/drivers/pinctrl/pinctrl-rtl8231.c
@@ -0,0 +1,525 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/bitfield.h>
+#include <linux/gpio/driver.h>
+#include <linux/gpio/regmap.h>
+#include <linux/module.h>
+#include <linux/pinctrl/pinconf.h>
+#include <linux/pinctrl/pinctrl.h>
+#include <linux/pinctrl/pinmux.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#include "core.h"
+#include "pinmux.h"
+#include <linux/mfd/rtl8231.h>
+
+#define RTL8231_NUM_GPIOS 37
+#define RTL8231_DEBOUNCE_USEC 100000
+#define RTL8231_DEBOUNCE_MIN_OFFSET 31
+
+struct rtl8231_pin_ctrl {
+ struct pinctrl_desc pctl_desc;
+ struct regmap *map;
+};
+
+/*
+ * Pin controller functionality
+ */
+static const char * const rtl8231_pin_function_names[] = {
+ "gpio",
+ "led",
+ "pwm",
+};
+
+enum rtl8231_pin_function {
+ RTL8231_PIN_FUNCTION_GPIO = BIT(0),
+ RTL8231_PIN_FUNCTION_LED = BIT(1),
+ RTL8231_PIN_FUNCTION_PWM = BIT(2),
+};
+
+struct rtl8231_pin_desc {
+ const enum rtl8231_pin_function functions;
+ const u8 reg;
+ const u8 offset;
+ const u8 gpio_function_value;
+};
+
+#define RTL8231_PIN_DESC(_num, _func, _reg, _fld, _val) \
+ [_num] = { \
+ .functions = RTL8231_PIN_FUNCTION_GPIO | _func, \
+ .reg = _reg, \
+ .offset = _fld, \
+ .gpio_function_value = _val, \
+ }
+#define RTL8231_GPIO_PIN_DESC(_num, _reg, _fld) \
+ RTL8231_PIN_DESC(_num, 0, _reg, _fld, RTL8231_PIN_MODE_GPIO)
+#define RTL8231_LED_PIN_DESC(_num, _reg, _fld) \
+ RTL8231_PIN_DESC(_num, RTL8231_PIN_FUNCTION_LED, _reg, _fld, RTL8231_PIN_MODE_GPIO)
+#define RTL8231_PWM_PIN_DESC(_num, _reg, _fld) \
+ RTL8231_PIN_DESC(_num, RTL8231_PIN_FUNCTION_PWM, _reg, _fld, 0)
+
+/*
+ * All pins have a GPIO/LED mux bit, but the bits for pins 35/36 are read-only. Use this bit
+ * for the GPIO-only pin instead of a placeholder, so the rest of the logic can stay generic.
+ */
+static struct rtl8231_pin_desc rtl8231_pin_data[RTL8231_NUM_GPIOS] = {
+ RTL8231_LED_PIN_DESC(0, RTL8231_REG_PIN_MODE0, 0),
+ RTL8231_LED_PIN_DESC(1, RTL8231_REG_PIN_MODE0, 1),
+ RTL8231_LED_PIN_DESC(2, RTL8231_REG_PIN_MODE0, 2),
+ RTL8231_LED_PIN_DESC(3, RTL8231_REG_PIN_MODE0, 3),
+ RTL8231_LED_PIN_DESC(4, RTL8231_REG_PIN_MODE0, 4),
+ RTL8231_LED_PIN_DESC(5, RTL8231_REG_PIN_MODE0, 5),
+ RTL8231_LED_PIN_DESC(6, RTL8231_REG_PIN_MODE0, 6),
+ RTL8231_LED_PIN_DESC(7, RTL8231_REG_PIN_MODE0, 7),
+ RTL8231_LED_PIN_DESC(8, RTL8231_REG_PIN_MODE0, 8),
+ RTL8231_LED_PIN_DESC(9, RTL8231_REG_PIN_MODE0, 9),
+ RTL8231_LED_PIN_DESC(10, RTL8231_REG_PIN_MODE0, 10),
+ RTL8231_LED_PIN_DESC(11, RTL8231_REG_PIN_MODE0, 11),
+ RTL8231_LED_PIN_DESC(12, RTL8231_REG_PIN_MODE0, 12),
+ RTL8231_LED_PIN_DESC(13, RTL8231_REG_PIN_MODE0, 13),
+ RTL8231_LED_PIN_DESC(14, RTL8231_REG_PIN_MODE0, 14),
+ RTL8231_LED_PIN_DESC(15, RTL8231_REG_PIN_MODE0, 15),
+ RTL8231_LED_PIN_DESC(16, RTL8231_REG_PIN_MODE1, 0),
+ RTL8231_LED_PIN_DESC(17, RTL8231_REG_PIN_MODE1, 1),
+ RTL8231_LED_PIN_DESC(18, RTL8231_REG_PIN_MODE1, 2),
+ RTL8231_LED_PIN_DESC(19, RTL8231_REG_PIN_MODE1, 3),
+ RTL8231_LED_PIN_DESC(20, RTL8231_REG_PIN_MODE1, 4),
+ RTL8231_LED_PIN_DESC(21, RTL8231_REG_PIN_MODE1, 5),
+ RTL8231_LED_PIN_DESC(22, RTL8231_REG_PIN_MODE1, 6),
+ RTL8231_LED_PIN_DESC(23, RTL8231_REG_PIN_MODE1, 7),
+ RTL8231_LED_PIN_DESC(24, RTL8231_REG_PIN_MODE1, 8),
+ RTL8231_LED_PIN_DESC(25, RTL8231_REG_PIN_MODE1, 9),
+ RTL8231_LED_PIN_DESC(26, RTL8231_REG_PIN_MODE1, 10),
+ RTL8231_LED_PIN_DESC(27, RTL8231_REG_PIN_MODE1, 11),
+ RTL8231_LED_PIN_DESC(28, RTL8231_REG_PIN_MODE1, 12),
+ RTL8231_LED_PIN_DESC(29, RTL8231_REG_PIN_MODE1, 13),
+ RTL8231_LED_PIN_DESC(30, RTL8231_REG_PIN_MODE1, 14),
+ RTL8231_LED_PIN_DESC(31, RTL8231_REG_PIN_MODE1, 15),
+ RTL8231_LED_PIN_DESC(32, RTL8231_REG_PIN_HI_CFG, 0),
+ RTL8231_LED_PIN_DESC(33, RTL8231_REG_PIN_HI_CFG, 1),
+ RTL8231_LED_PIN_DESC(34, RTL8231_REG_PIN_HI_CFG, 2),
+ RTL8231_PWM_PIN_DESC(35, RTL8231_REG_FUNC1, 3),
+ RTL8231_GPIO_PIN_DESC(36, RTL8231_REG_PIN_HI_CFG, 4),
+};
+
+#define RTL8231_PIN(_num) \
+ { \
+ .number = _num, \
+ .name = "gpio" #_num, \
+ .drv_data = &rtl8231_pin_data[_num] \
+ }
+
+static const struct pinctrl_pin_desc rtl8231_pins[RTL8231_NUM_GPIOS] = {
+ RTL8231_PIN(0),
+ RTL8231_PIN(1),
+ RTL8231_PIN(2),
+ RTL8231_PIN(3),
+ RTL8231_PIN(4),
+ RTL8231_PIN(5),
+ RTL8231_PIN(6),
+ RTL8231_PIN(7),
+ RTL8231_PIN(8),
+ RTL8231_PIN(9),
+ RTL8231_PIN(10),
+ RTL8231_PIN(11),
+ RTL8231_PIN(12),
+ RTL8231_PIN(13),
+ RTL8231_PIN(14),
+ RTL8231_PIN(15),
+ RTL8231_PIN(16),
+ RTL8231_PIN(17),
+ RTL8231_PIN(18),
+ RTL8231_PIN(19),
+ RTL8231_PIN(20),
+ RTL8231_PIN(21),
+ RTL8231_PIN(22),
+ RTL8231_PIN(23),
+ RTL8231_PIN(24),
+ RTL8231_PIN(25),
+ RTL8231_PIN(26),
+ RTL8231_PIN(27),
+ RTL8231_PIN(28),
+ RTL8231_PIN(29),
+ RTL8231_PIN(30),
+ RTL8231_PIN(31),
+ RTL8231_PIN(32),
+ RTL8231_PIN(33),
+ RTL8231_PIN(34),
+ RTL8231_PIN(35),
+ RTL8231_PIN(36),
+};
+
+static int rtl8231_get_groups_count(struct pinctrl_dev *pctldev)
+{
+ return ARRAY_SIZE(rtl8231_pins);
+}
+
+static const char *rtl8231_get_group_name(struct pinctrl_dev *pctldev, unsigned int selector)
+{
+ return rtl8231_pins[selector].name;
+}
+
+static int rtl8231_get_group_pins(struct pinctrl_dev *pctldev, unsigned int selector,
+ const unsigned int **pins, unsigned int *num_pins)
+{
+ if (selector >= ARRAY_SIZE(rtl8231_pins))
+ return -EINVAL;
+
+ *pins = &rtl8231_pins[selector].number;
+ *num_pins = 1;
+
+ return 0;
+}
+
+static const struct pinctrl_ops rtl8231_pinctrl_ops = {
+ .get_groups_count = rtl8231_get_groups_count,
+ .get_group_name = rtl8231_get_group_name,
+ .get_group_pins = rtl8231_get_group_pins,
+ .dt_node_to_map = pinconf_generic_dt_node_to_map_all,
+ .dt_free_map = pinconf_generic_dt_free_map,
+};
+
+static int rtl8231_set_mux(struct pinctrl_dev *pctldev, unsigned int func_selector,
+ unsigned int group_selector)
+{
+ const struct function_desc *func = pinmux_generic_get_function(pctldev, func_selector);
+ const struct rtl8231_pin_desc *desc = rtl8231_pins[group_selector].drv_data;
+ const struct rtl8231_pin_ctrl *ctrl = pinctrl_dev_get_drvdata(pctldev);
+ unsigned int func_flag = (uintptr_t) func->data;
+ unsigned int function_mask;
+ unsigned int gpio_function;
+
+ if (!(desc->functions & func_flag))
+ return -EINVAL;
+
+ function_mask = BIT(desc->offset);
+ gpio_function = desc->gpio_function_value << desc->offset;
+
+ if (func_flag == RTL8231_PIN_FUNCTION_GPIO)
+ return regmap_update_bits(ctrl->map, desc->reg, function_mask, gpio_function);
+ else
+ return regmap_update_bits(ctrl->map, desc->reg, function_mask, ~gpio_function);
+}
+
+static int rtl8231_gpio_request_enable(struct pinctrl_dev *pctldev,
+ struct pinctrl_gpio_range *range, unsigned int offset)
+{
+ const struct rtl8231_pin_desc *desc = rtl8231_pins[offset].drv_data;
+ struct rtl8231_pin_ctrl *ctrl = pinctrl_dev_get_drvdata(pctldev);
+ unsigned int function_mask;
+ unsigned int gpio_function;
+
+ function_mask = BIT(desc->offset);
+ gpio_function = desc->gpio_function_value << desc->offset;
+
+ return regmap_update_bits(ctrl->map, desc->reg, function_mask, gpio_function);
+}
+
+static const struct pinmux_ops rtl8231_pinmux_ops = {
+ .get_functions_count = pinmux_generic_get_function_count,
+ .get_function_name = pinmux_generic_get_function_name,
+ .get_function_groups = pinmux_generic_get_function_groups,
+ .set_mux = rtl8231_set_mux,
+ .gpio_request_enable = rtl8231_gpio_request_enable,
+ .strict = true,
+};
+
+static int rtl8231_pin_config_get(struct pinctrl_dev *pctldev, unsigned int offset,
+ unsigned long *config)
+{
+ struct rtl8231_pin_ctrl *ctrl = pinctrl_dev_get_drvdata(pctldev);
+ unsigned int param = pinconf_to_config_param(*config);
+ unsigned int arg;
+ int err;
+ int v;
+
+ switch (param) {
+ case PIN_CONFIG_INPUT_DEBOUNCE:
+ if (offset < RTL8231_DEBOUNCE_MIN_OFFSET)
+ return -EINVAL;
+
+ err = regmap_read(ctrl->map, RTL8231_REG_FUNC1, &v);
+ if (err)
+ return err;
+
+ v = FIELD_GET(RTL8231_FUNC1_DEBOUNCE_MASK, v);
+ if (v & BIT(offset - RTL8231_DEBOUNCE_MIN_OFFSET))
+ arg = RTL8231_DEBOUNCE_USEC;
+ else
+ arg = 0;
+ break;
+ default:
+ return -ENOTSUPP;
+ }
+
+ *config = pinconf_to_config_packed(param, arg);
+
+ return 0;
+}
+
+static int rtl8231_pin_config_set(struct pinctrl_dev *pctldev, unsigned int offset,
+ unsigned long *configs, unsigned int num_configs)
+{
+ struct rtl8231_pin_ctrl *ctrl = pinctrl_dev_get_drvdata(pctldev);
+ unsigned int param, arg;
+ unsigned int pin_mask;
+ int err;
+ int i;
+
+ for (i = 0; i < num_configs; i++) {
+ param = pinconf_to_config_param(configs[i]);
+ arg = pinconf_to_config_argument(configs[i]);
+
+ switch (param) {
+ case PIN_CONFIG_INPUT_DEBOUNCE:
+ if (offset < RTL8231_DEBOUNCE_MIN_OFFSET)
+ return -EINVAL;
+
+ pin_mask = FIELD_PREP(RTL8231_FUNC1_DEBOUNCE_MASK,
+ BIT(offset - RTL8231_DEBOUNCE_MIN_OFFSET));
+
+ switch (arg) {
+ case 0:
+ err = regmap_update_bits(ctrl->map, RTL8231_REG_FUNC1,
+ pin_mask, 0);
+ break;
+ case RTL8231_DEBOUNCE_USEC:
+ err = regmap_update_bits(ctrl->map, RTL8231_REG_FUNC1,
+ pin_mask, pin_mask);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ break;
+ default:
+ return -ENOTSUPP;
+ }
+ }
+
+ return err;
+}
+
+static const struct pinconf_ops rtl8231_pinconf_ops = {
+ .is_generic = true,
+ .pin_config_get = rtl8231_pin_config_get,
+ .pin_config_set = rtl8231_pin_config_set,
+};
+
+static int rtl8231_pinctrl_init_functions(struct pinctrl_dev *pctl, struct rtl8231_pin_ctrl *ctrl)
+{
+ const char *function_name;
+ const char **groups;
+ unsigned int f_idx;
+ unsigned int pin;
+ int num_groups;
+ int err;
+
+ for (f_idx = 0; f_idx < ARRAY_SIZE(rtl8231_pin_function_names); f_idx++) {
+ function_name = rtl8231_pin_function_names[f_idx];
+
+ for (pin = 0, num_groups = 0; pin < ctrl->pctl_desc.npins; pin++)
+ if (rtl8231_pin_data[pin].functions & BIT(f_idx))
+ num_groups++;
+
+ groups = devm_kcalloc(pctl->dev, num_groups, sizeof(*groups), GFP_KERNEL);
+ if (!groups)
+ return -ENOMEM;
+
+ for (pin = 0, num_groups = 0; pin < ctrl->pctl_desc.npins; pin++)
+ if (rtl8231_pin_data[pin].functions & BIT(f_idx))
+ groups[num_groups++] = rtl8231_pins[pin].name;
+
+ err = pinmux_generic_add_function(pctl, function_name, groups, num_groups,
+ (void *) BIT(f_idx));
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+struct pin_field_info {
+ const struct reg_field gpio_data;
+ const struct reg_field gpio_dir;
+ const struct reg_field mode;
+};
+
+static const struct pin_field_info pin_fields[] = {
+ {
+ .gpio_data = REG_FIELD(RTL8231_REG_GPIO_DATA0, 0, 15),
+ .gpio_dir = REG_FIELD(RTL8231_REG_GPIO_DIR0, 0, 15),
+ .mode = REG_FIELD(RTL8231_REG_PIN_MODE0, 0, 15),
+ },
+ {
+ .gpio_data = REG_FIELD(RTL8231_REG_GPIO_DATA1, 0, 15),
+ .gpio_dir = REG_FIELD(RTL8231_REG_GPIO_DIR1, 0, 15),
+ .mode = REG_FIELD(RTL8231_REG_PIN_MODE1, 0, 15),
+ },
+ {
+ .gpio_data = REG_FIELD(RTL8231_REG_GPIO_DATA2, 0, 4),
+ .gpio_dir = REG_FIELD(RTL8231_REG_PIN_HI_CFG, 5, 9),
+ .mode = REG_FIELD(RTL8231_REG_PIN_HI_CFG, 0, 4),
+ },
+};
+
+static int rtl8231_configure_safe(struct device *dev, struct regmap *map)
+{
+ struct regmap_field *field_data;
+ struct regmap_field *field_mode;
+ struct regmap_field *field_dir;
+ unsigned int is_output;
+ unsigned int is_gpio;
+ unsigned int data;
+ unsigned int mode;
+ unsigned int dir;
+ int err;
+
+ for (unsigned int i = 0; i < ARRAY_SIZE(pin_fields); i++) {
+ field_data = devm_regmap_field_alloc(dev, map, pin_fields[i].gpio_data);
+ if (IS_ERR(field_data))
+ return PTR_ERR(field_data);
+
+ field_dir = devm_regmap_field_alloc(dev, map, pin_fields[i].gpio_dir);
+ if (IS_ERR(field_dir))
+ return PTR_ERR(field_dir);
+
+ field_mode = devm_regmap_field_alloc(dev, map, pin_fields[i].mode);
+ if (IS_ERR(field_mode))
+ return PTR_ERR(field_mode);
+
+ /* The register cache is invalid at start-up, so this should read from HW */
+ err = regmap_field_read(field_data, &data);
+ if (err)
+ return err;
+
+ err = regmap_field_read(field_dir, &dir);
+ if (err)
+ return err;
+
+ err = regmap_field_read(field_mode, &mode);
+ if (err)
+ return err;
+
+ /* Write back only the GPIO-out values to fix the cache */
+ data &= ~dir;
+ regmap_field_write(field_data, data);
+
+ /*
+ * Set every pin that is configured as gpio-output but muxed for the alternative
+ * (LED) function to gpio-in. That way the pin will be high impedance when it is
+ * muxed to GPIO, preventing unwanted glitches.
+ * The pin muxes are left as-is, so there are no signal changes.
+ */
+ is_gpio = mode;
+ is_output = ~dir;
+ regmap_field_write(field_dir, dir | (~is_gpio & is_output));
+
+ devm_regmap_field_free(dev, field_data);
+ devm_regmap_field_free(dev, field_dir);
+ devm_regmap_field_free(dev, field_mode);
+ }
+
+ return 0;
+}
+
+static int rtl8231_pinctrl_init(struct device *dev, struct rtl8231_pin_ctrl *ctrl)
+{
+ struct pinctrl_dev *pctldev;
+ int err;
+
+ ctrl->pctl_desc.name = "rtl8231-pinctrl";
+ ctrl->pctl_desc.owner = THIS_MODULE;
+ ctrl->pctl_desc.confops = &rtl8231_pinconf_ops;
+ ctrl->pctl_desc.pctlops = &rtl8231_pinctrl_ops;
+ ctrl->pctl_desc.pmxops = &rtl8231_pinmux_ops;
+ ctrl->pctl_desc.npins = ARRAY_SIZE(rtl8231_pins);
+ ctrl->pctl_desc.pins = rtl8231_pins;
+
+ err = devm_pinctrl_register_and_init(dev->parent, &ctrl->pctl_desc, ctrl, &pctldev);
+ if (err) {
+ dev_err(dev, "failed to register pin controller\n");
+ return err;
+ }
+
+ err = rtl8231_pinctrl_init_functions(pctldev, ctrl);
+ if (err)
+ return err;
+
+ err = pinctrl_enable(pctldev);
+ if (err)
+ dev_err(dev, "failed to enable pin controller\n");
+
+ return err;
+}
+
+/*
+ * GPIO controller functionality
+ */
+static int rtl8231_gpio_reg_mask_xlate(struct gpio_regmap *gpio, unsigned int base,
+ unsigned int offset, unsigned int *reg, unsigned int *mask)
+{
+ unsigned int pin_mask = BIT(offset % RTL8231_BITS_VAL);
+
+ if (base == RTL8231_REG_GPIO_DATA0 || offset < 32) {
+ *reg = base + offset / RTL8231_BITS_VAL;
+ *mask = pin_mask;
+ } else if (base == RTL8231_REG_GPIO_DIR0) {
+ *reg = RTL8231_REG_PIN_HI_CFG;
+ *mask = FIELD_PREP(RTL8231_PIN_HI_CFG_DIR_MASK, pin_mask);
+ } else {
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int rtl8231_pinctrl_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct rtl8231_pin_ctrl *ctrl;
+ struct gpio_regmap_config gpio_cfg = {};
+ int err;
+
+ ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL);
+ if (!ctrl)
+ return -ENOMEM;
+
+ ctrl->map = dev_get_regmap(dev->parent, NULL);
+ if (!ctrl->map)
+ return -ENODEV;
+
+ err = rtl8231_configure_safe(dev, ctrl->map);
+ if (err)
+ return err;
+
+ err = rtl8231_pinctrl_init(dev, ctrl);
+ if (err)
+ return err;
+
+ gpio_cfg.regmap = ctrl->map;
+ gpio_cfg.parent = dev->parent;
+ gpio_cfg.ngpio = RTL8231_NUM_GPIOS;
+ gpio_cfg.ngpio_per_reg = RTL8231_BITS_VAL;
+
+ gpio_cfg.reg_dat_base = GPIO_REGMAP_ADDR(RTL8231_REG_GPIO_DATA0);
+ gpio_cfg.reg_set_base = GPIO_REGMAP_ADDR(RTL8231_REG_GPIO_DATA0);
+ gpio_cfg.reg_dir_in_base = GPIO_REGMAP_ADDR(RTL8231_REG_GPIO_DIR0);
+
+ gpio_cfg.reg_mask_xlate = rtl8231_gpio_reg_mask_xlate;
+
+ return PTR_ERR_OR_ZERO(devm_gpio_regmap_register(dev, &gpio_cfg));
+}
+
+static struct platform_driver rtl8231_pinctrl_driver = {
+ .driver = {
+ .name = "rtl8231-pinctrl",
+ },
+ .probe = rtl8231_pinctrl_probe,
+};
+module_platform_driver(rtl8231_pinctrl_driver);
+
+MODULE_AUTHOR("Sander Vanheule <sander@svanheule.net>");
+MODULE_DESCRIPTION("Realtek RTL8231 pin control and GPIO support");
+MODULE_LICENSE("GPL");

View file

@ -0,0 +1,338 @@
From 6b797a97c007e46d6081fc6f4b41ce8407078605 Mon Sep 17 00:00:00 2001
From: Sander Vanheule <sander@svanheule.net>
Date: Mon, 10 May 2021 22:16:11 +0200
Subject: [PATCH] leds: Add support for RTL8231 LED scan matrix
Both single and bi-color scanning modes are supported. The driver will
verify that the addresses are valid for the current mode, before
registering the LEDs. LEDs can be turned on, off, or toggled at one of
six predefined rates from 40ms to 1280ms.
Implements a platform device for use as a child device with RTL8231 MFD,
and uses the parent regmap to access the required registers.
Signed-off-by: Sander Vanheule <sander@svanheule.net>
---
drivers/leds/Kconfig | 10 ++
drivers/leds/Makefile | 1 +
drivers/leds/leds-rtl8231.c | 291 ++++++++++++++++++++++++++++++++++++
3 files changed, 302 insertions(+)
create mode 100644 drivers/leds/leds-rtl8231.c
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -586,6 +586,16 @@ config LEDS_REGULATOR
help
This option enables support for regulator driven LEDs.
+config LEDS_RTL8231
+ tristate "RTL8231 LED matrix support"
+ depends on LEDS_CLASS
+ depends on MFD_RTL8231
+ default MFD_RTL8231
+ help
+ This option enables support for using the LED scanning matrix output
+ of the RTL8231 GPIO and LED expander chip.
+ When built as a module, this module will be named leds-rtl8231.
+
config LEDS_BD2606MVV
tristate "LED driver for BD2606MVV"
depends on LEDS_CLASS
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -77,6 +77,7 @@ obj-$(CONFIG_LEDS_PM8058) += leds-pm805
obj-$(CONFIG_LEDS_POWERNV) += leds-powernv.o
obj-$(CONFIG_LEDS_PWM) += leds-pwm.o
obj-$(CONFIG_LEDS_REGULATOR) += leds-regulator.o
+obj-$(CONFIG_LEDS_RTL8231) += leds-rtl8231.o
obj-$(CONFIG_LEDS_SC27XX_BLTC) += leds-sc27xx-bltc.o
obj-$(CONFIG_LEDS_ST1202) += leds-st1202.o
obj-$(CONFIG_LEDS_SUNFIRE) += leds-sunfire.o
--- /dev/null
+++ b/drivers/leds/leds-rtl8231.c
@@ -0,0 +1,285 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/device.h>
+#include <linux/leds.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+
+#include <linux/mfd/rtl8231.h>
+
+/**
+ * struct led_toggle_rate - description of an LED blinking mode
+ * @interval_ms: LED toggle rate in milliseconds
+ * @mode: Register field value used to activate this mode
+ *
+ * For LED hardware accelerated blinking, with equal on and off delay.
+ * Both delays are given by @interval, so the interval at which the LED blinks
+ * (i.e. turn on and off once) is double this value.
+ */
+struct led_toggle_rate {
+ u16 interval_ms;
+ u8 mode;
+};
+
+/**
+ * struct led_modes - description of all LED modes
+ * @toggle_rates: Array of led_toggle_rate values, sorted by ascending interval
+ * @num_toggle_rates: Number of elements in @led_toggle_rate
+ * @off: Register field value to turn LED off
+ * @on: Register field value to turn LED on
+ */
+struct led_modes {
+ const struct led_toggle_rate *toggle_rates;
+ unsigned int num_toggle_rates;
+ u8 off;
+ u8 on;
+};
+
+struct rtl8231_led {
+ struct led_classdev led;
+ const struct led_modes *modes;
+ struct regmap_field *reg_field;
+};
+#define to_rtl8231_led(_cdev) container_of(_cdev, struct rtl8231_led, led)
+
+#define RTL8231_NUM_LEDS 3
+#define RTL8231_LED_PER_REG 5
+#define RTL8231_BITS_PER_LED 3
+
+static const unsigned int rtl8231_led_port_counts_single[RTL8231_NUM_LEDS] = {32, 32, 24};
+static const unsigned int rtl8231_led_port_counts_bicolor[RTL8231_NUM_LEDS] = {24, 24, 24};
+
+static const unsigned int rtl8231_led_base[RTL8231_NUM_LEDS] = {
+ RTL8231_REG_LED0_BASE,
+ RTL8231_REG_LED1_BASE,
+ RTL8231_REG_LED2_BASE,
+};
+
+#define RTL8231_DEFAULT_TOGGLE_INTERVAL_MS 500
+
+static const struct led_toggle_rate rtl8231_toggle_rates[] = {
+ { 40, 1},
+ { 80, 2},
+ { 160, 3},
+ { 320, 4},
+ { 640, 5},
+ {1280, 6},
+};
+
+static const struct led_modes rtl8231_led_modes = {
+ .off = 0,
+ .on = 7,
+ .num_toggle_rates = ARRAY_SIZE(rtl8231_toggle_rates),
+ .toggle_rates = rtl8231_toggle_rates,
+};
+
+static void rtl8231_led_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct rtl8231_led *pled = to_rtl8231_led(led_cdev);
+
+ if (brightness)
+ regmap_field_write(pled->reg_field, pled->modes->on);
+ else
+ regmap_field_write(pled->reg_field, pled->modes->off);
+}
+
+static enum led_brightness rtl8231_led_brightness_get(struct led_classdev *led_cdev)
+{
+ struct rtl8231_led *pled = to_rtl8231_led(led_cdev);
+ u32 current_mode = pled->modes->off;
+
+ regmap_field_read(pled->reg_field, &current_mode);
+
+ if (current_mode == pled->modes->off)
+ return LED_OFF;
+ else
+ return LED_ON;
+}
+
+static unsigned int rtl8231_led_current_interval(struct rtl8231_led *pled)
+{
+ unsigned int mode;
+ unsigned int i;
+
+ if (regmap_field_read(pled->reg_field, &mode))
+ return 0;
+
+ for (i = 0; i < pled->modes->num_toggle_rates; i++)
+ if (mode == pled->modes->toggle_rates[i].mode)
+ return pled->modes->toggle_rates[i].interval_ms;
+
+ return 0;
+}
+
+static int rtl8231_led_blink_set(struct led_classdev *led_cdev, unsigned long *delay_on,
+ unsigned long *delay_off)
+{
+ struct rtl8231_led *pled = to_rtl8231_led(led_cdev);
+ const struct led_toggle_rate *rates = pled->modes->toggle_rates;
+ unsigned int num_rates = pled->modes->num_toggle_rates;
+ unsigned int interval_ms;
+ unsigned int i;
+ int err;
+
+ if (*delay_on == 0 && *delay_off == 0) {
+ interval_ms = RTL8231_DEFAULT_TOGGLE_INTERVAL_MS;
+ } else {
+ /*
+ * If the current mode is blinking, choose the delay that (likely) changed.
+ * Otherwise, choose the interval that would have the same total delay.
+ */
+ interval_ms = rtl8231_led_current_interval(pled);
+ if (interval_ms > 0 && interval_ms == *delay_off)
+ interval_ms = *delay_on;
+ else if (interval_ms > 0 && interval_ms == *delay_on)
+ interval_ms = *delay_off;
+ else
+ interval_ms = (*delay_on + *delay_off) / 2;
+ }
+
+ /* Find clamped toggle interval */
+ for (i = 0; i < (num_rates - 1); i++)
+ if (interval_ms > rates[i].interval_ms)
+ break;
+
+ interval_ms = rates[i].interval_ms;
+
+ err = regmap_field_write(pled->reg_field, rates[i].mode);
+ if (err)
+ return err;
+
+ *delay_on = interval_ms;
+ *delay_off = interval_ms;
+
+ return 0;
+}
+
+static int rtl8231_led_read_address(struct fwnode_handle *fwnode, unsigned int *addr_port,
+ unsigned int *addr_led)
+{
+ u32 addr[2];
+ int err;
+
+ err = fwnode_property_count_u32(fwnode, "reg");
+ if (err < 0)
+ return err;
+ if (err != ARRAY_SIZE(addr))
+ return -EINVAL;
+
+ err = fwnode_property_read_u32_array(fwnode, "reg", addr, ARRAY_SIZE(addr));
+ if (err)
+ return err;
+
+ *addr_port = addr[0];
+ *addr_led = addr[1];
+
+ return 0;
+}
+
+static const struct regmap_field *rtl8231_led_get_field(struct device *dev, struct regmap *map,
+ unsigned int port_index, unsigned int led_index)
+{
+ unsigned int offset = port_index / RTL8231_LED_PER_REG;
+ unsigned int shift = (port_index % RTL8231_LED_PER_REG) * RTL8231_BITS_PER_LED;
+ const struct reg_field field = REG_FIELD(rtl8231_led_base[led_index] + offset, shift,
+ shift + RTL8231_BITS_PER_LED - 1);
+
+ return devm_regmap_field_alloc(dev, map, field);
+}
+
+static int rtl8231_led_probe_single(struct device *dev, struct regmap *map,
+ const unsigned int *port_counts, struct fwnode_handle *fwnode)
+{
+ struct led_init_data init_data = {};
+ struct rtl8231_led *pled;
+ unsigned int port_index;
+ unsigned int led_index;
+ int err;
+
+ pled = devm_kzalloc(dev, sizeof(*pled), GFP_KERNEL);
+ if (!pled)
+ return -ENOMEM;
+
+ err = rtl8231_led_read_address(fwnode, &port_index, &led_index);
+ if (err) {
+ dev_err(dev, "LED address invalid");
+ return err;
+ }
+
+ if (led_index >= RTL8231_NUM_LEDS || port_index >= port_counts[led_index]) {
+ dev_err(dev, "LED address (%d.%d) invalid", port_index, led_index);
+ return -EINVAL;
+ }
+
+ pled->reg_field = rtl8231_led_get_field(dev, map, port_index, led_index);
+ if (IS_ERR(pled->reg_field))
+ return PTR_ERR(pled->reg_field);
+
+ pled->modes = &rtl8231_led_modes;
+
+ pled->led.max_brightness = 1;
+ pled->led.brightness_get = rtl8231_led_brightness_get;
+ pled->led.brightness_set = rtl8231_led_brightness_set;
+ pled->led.blink_set = rtl8231_led_blink_set;
+
+ init_data.fwnode = fwnode;
+
+ return devm_led_classdev_register_ext(dev, &pled->led, &init_data);
+}
+
+static int rtl8231_led_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ const unsigned int *port_counts;
+ struct fwnode_handle *child;
+ struct regmap *map;
+ int err;
+
+ map = dev_get_regmap(dev->parent, NULL);
+ if (!map)
+ return -ENODEV;
+
+ if (device_property_match_string(dev, "realtek,led-scan-mode", "single-color") >= 0) {
+ port_counts = rtl8231_led_port_counts_single;
+ regmap_update_bits(map, RTL8231_REG_FUNC0,
+ RTL8231_FUNC0_SCAN_MODE, RTL8231_FUNC0_SCAN_SINGLE);
+ } else if (device_property_match_string(dev, "realtek,led-scan-mode", "bi-color") >= 0) {
+ port_counts = rtl8231_led_port_counts_bicolor;
+ regmap_update_bits(map, RTL8231_REG_FUNC0,
+ RTL8231_FUNC0_SCAN_MODE, RTL8231_FUNC0_SCAN_BICOLOR);
+ } else {
+ dev_err(dev, "scan mode missing or invalid");
+ return -EINVAL;
+ }
+
+ fwnode_for_each_available_child_node(dev->fwnode, child) {
+ err = rtl8231_led_probe_single(dev, map, port_counts, child);
+ if (err)
+ dev_warn(dev, "failed to register LED %pfwP", child);
+ }
+
+ return 0;
+}
+
+static const struct of_device_id of_rtl8231_led_match[] = {
+ { .compatible = "realtek,rtl8231-leds" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, of_rtl8231_led_match);
+
+static struct platform_driver rtl8231_led_driver = {
+ .driver = {
+ .name = "rtl8231-leds",
+ .of_match_table = of_rtl8231_led_match,
+ },
+ .probe = rtl8231_led_probe,
+};
+module_platform_driver(rtl8231_led_driver);
+
+MODULE_AUTHOR("Sander Vanheule <sander@svanheule.net>");
+MODULE_DESCRIPTION("Realtek RTL8231 LED support");
+MODULE_LICENSE("GPL");