mirror of
https://github.com/openwrt/openwrt.git
synced 2024-12-25 08:21:14 +00:00
374 lines
13 KiB
Diff
374 lines
13 KiB
Diff
|
From ee0175b3b44288c74d5292c2a9c2c154f6c0317e Mon Sep 17 00:00:00 2001
|
||
|
From: Sander Vanheule <sander@svanheule.net>
|
||
|
Date: Sun, 7 Aug 2022 21:21:15 +0200
|
||
|
Subject: [PATCH] gpio: realtek-otto: switch to 32-bit I/O
|
||
|
|
||
|
By using 16-bit I/O on the GPIO peripheral, which is apparently not safe
|
||
|
on MIPS, the IMR can end up containing garbage. This then results in
|
||
|
interrupt triggers for lines that don't have an interrupt handler
|
||
|
associated. The irq_desc lookup fails, and the ISR will not be cleared,
|
||
|
keeping the CPU busy until reboot, or until another IMR operation
|
||
|
restores the correct value. This situation appears to happen very
|
||
|
rarely, for < 0.5% of IMR writes.
|
||
|
|
||
|
Instead of using 8-bit or 16-bit I/O operations on the 32-bit memory
|
||
|
mapped peripheral registers, switch to using 32-bit I/O only, operating
|
||
|
on the entire bank for all single bit line settings. For 2-bit line
|
||
|
settings, with 16-bit port values, stick to manual (un)packing.
|
||
|
|
||
|
This issue has been seen on RTL8382M (HPE 1920-16G), RTL8391M (Netgear
|
||
|
GS728TP v2), and RTL8393M (D-Link DGS-1210-52 F3, Zyxel GS1900-48).
|
||
|
|
||
|
Reported-by: Luiz Angelo Daros de Luca <luizluca@gmail.com> # DGS-1210-52
|
||
|
Reported-by: Birger Koblitz <mail@birger-koblitz.de> # GS728TP
|
||
|
Reported-by: Jan Hoffmann <jan@3e8.eu> # 1920-16G
|
||
|
Fixes: 0d82fb1127fb ("gpio: Add Realtek Otto GPIO support")
|
||
|
Signed-off-by: Sander Vanheule <sander@svanheule.net>
|
||
|
Cc: Paul Cercueil <paul@crapouillou.net>
|
||
|
Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
|
||
|
Signed-off-by: Bartosz Golaszewski <brgl@bgdev.pl>
|
||
|
|
||
|
Update patch for missing upstream changes:
|
||
|
- commit a01a40e33499 ("gpio: realtek-otto: Make the irqchip immutable")
|
||
|
- commit dbd1c54fc820 ("gpio: Bulk conversion to generic_handle_domain_irq()")
|
||
|
Signed-off-by: Sander Vanheule <sander@svanheule.net>
|
||
|
|
||
|
---
|
||
|
drivers/gpio/gpio-realtek-otto.c | 166 ++++++++++++++++---------------
|
||
|
1 file changed, 85 insertions(+), 81 deletions(-)
|
||
|
|
||
|
--- a/drivers/gpio/gpio-realtek-otto.c
|
||
|
+++ b/drivers/gpio/gpio-realtek-otto.c
|
||
|
@@ -46,10 +46,20 @@
|
||
|
* @lock: Lock for accessing the IRQ registers and values
|
||
|
* @intr_mask: Mask for interrupts lines
|
||
|
* @intr_type: Interrupt type selection
|
||
|
+ * @bank_read: Read a bank setting as a single 32-bit value
|
||
|
+ * @bank_write: Write a bank setting as a single 32-bit value
|
||
|
+ * @imr_line_pos: Bit shift of an IRQ line's IMR value.
|
||
|
+ *
|
||
|
+ * The DIR, DATA, and ISR registers consist of four 8-bit port values, packed
|
||
|
+ * into a single 32-bit register. Use @bank_read (@bank_write) to get (assign)
|
||
|
+ * a value from (to) these registers. The IMR register consists of four 16-bit
|
||
|
+ * port values, packed into two 32-bit registers. Use @imr_line_pos to get the
|
||
|
+ * bit shift of the 2-bit field for a line's IMR settings. Shifts larger than
|
||
|
+ * 32 overflow into the second register.
|
||
|
*
|
||
|
* Because the interrupt mask register (IMR) combines the function of IRQ type
|
||
|
* selection and masking, two extra values are stored. @intr_mask is used to
|
||
|
- * mask/unmask the interrupts for a GPIO port, and @intr_type is used to store
|
||
|
+ * mask/unmask the interrupts for a GPIO line, and @intr_type is used to store
|
||
|
* the selected interrupt types. The logical AND of these values is written to
|
||
|
* IMR on changes.
|
||
|
*/
|
||
|
@@ -59,10 +69,11 @@ struct realtek_gpio_ctrl {
|
||
|
void __iomem *cpumask_base;
|
||
|
struct cpumask cpu_irq_maskable;
|
||
|
raw_spinlock_t lock;
|
||
|
- u16 intr_mask[REALTEK_GPIO_PORTS_PER_BANK];
|
||
|
- u16 intr_type[REALTEK_GPIO_PORTS_PER_BANK];
|
||
|
- unsigned int (*port_offset_u8)(unsigned int port);
|
||
|
- unsigned int (*port_offset_u16)(unsigned int port);
|
||
|
+ u8 intr_mask[REALTEK_GPIO_MAX];
|
||
|
+ u8 intr_type[REALTEK_GPIO_MAX];
|
||
|
+ u32 (*bank_read)(void __iomem *reg);
|
||
|
+ void (*bank_write)(void __iomem *reg, u32 value);
|
||
|
+ unsigned int (*line_imr_pos)(unsigned int line);
|
||
|
};
|
||
|
|
||
|
/* Expand with more flags as devices with other quirks are added */
|
||
|
@@ -101,14 +112,22 @@ static struct realtek_gpio_ctrl *irq_dat
|
||
|
* port. The two interrupt mask registers store two bits per GPIO, so use u16
|
||
|
* values.
|
||
|
*/
|
||
|
-static unsigned int realtek_gpio_port_offset_u8(unsigned int port)
|
||
|
+static u32 realtek_gpio_bank_read_swapped(void __iomem *reg)
|
||
|
+{
|
||
|
+ return ioread32be(reg);
|
||
|
+}
|
||
|
+
|
||
|
+static void realtek_gpio_bank_write_swapped(void __iomem *reg, u32 value)
|
||
|
{
|
||
|
- return port;
|
||
|
+ iowrite32be(value, reg);
|
||
|
}
|
||
|
|
||
|
-static unsigned int realtek_gpio_port_offset_u16(unsigned int port)
|
||
|
+static unsigned int realtek_gpio_line_imr_pos_swapped(unsigned int line)
|
||
|
{
|
||
|
- return 2 * port;
|
||
|
+ unsigned int port_pin = line % 8;
|
||
|
+ unsigned int port = line / 8;
|
||
|
+
|
||
|
+ return 2 * (8 * (port ^ 1) + port_pin);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
@@ -119,64 +138,65 @@ static unsigned int realtek_gpio_port_of
|
||
|
* per GPIO, so use u16 values. The first register contains ports 1 and 0, the
|
||
|
* second ports 3 and 2.
|
||
|
*/
|
||
|
-static unsigned int realtek_gpio_port_offset_u8_rev(unsigned int port)
|
||
|
+static u32 realtek_gpio_bank_read(void __iomem *reg)
|
||
|
{
|
||
|
- return 3 - port;
|
||
|
+ return ioread32(reg);
|
||
|
}
|
||
|
|
||
|
-static unsigned int realtek_gpio_port_offset_u16_rev(unsigned int port)
|
||
|
+static void realtek_gpio_bank_write(void __iomem *reg, u32 value)
|
||
|
{
|
||
|
- return 2 * (port ^ 1);
|
||
|
+ iowrite32(value, reg);
|
||
|
}
|
||
|
|
||
|
-static void realtek_gpio_write_imr(struct realtek_gpio_ctrl *ctrl,
|
||
|
- unsigned int port, u16 irq_type, u16 irq_mask)
|
||
|
+static unsigned int realtek_gpio_line_imr_pos(unsigned int line)
|
||
|
{
|
||
|
- iowrite16(irq_type & irq_mask,
|
||
|
- ctrl->base + REALTEK_GPIO_REG_IMR + ctrl->port_offset_u16(port));
|
||
|
+ return 2 * line;
|
||
|
}
|
||
|
|
||
|
-static void realtek_gpio_clear_isr(struct realtek_gpio_ctrl *ctrl,
|
||
|
- unsigned int port, u8 mask)
|
||
|
+static void realtek_gpio_clear_isr(struct realtek_gpio_ctrl *ctrl, u32 mask)
|
||
|
{
|
||
|
- iowrite8(mask, ctrl->base + REALTEK_GPIO_REG_ISR + ctrl->port_offset_u8(port));
|
||
|
+ ctrl->bank_write(ctrl->base + REALTEK_GPIO_REG_ISR, mask);
|
||
|
}
|
||
|
|
||
|
-static u8 realtek_gpio_read_isr(struct realtek_gpio_ctrl *ctrl, unsigned int port)
|
||
|
+static u32 realtek_gpio_read_isr(struct realtek_gpio_ctrl *ctrl)
|
||
|
{
|
||
|
- return ioread8(ctrl->base + REALTEK_GPIO_REG_ISR + ctrl->port_offset_u8(port));
|
||
|
+ return ctrl->bank_read(ctrl->base + REALTEK_GPIO_REG_ISR);
|
||
|
}
|
||
|
|
||
|
-/* Set the rising and falling edge mask bits for a GPIO port pin */
|
||
|
-static u16 realtek_gpio_imr_bits(unsigned int pin, u16 value)
|
||
|
+/* Set the rising and falling edge mask bits for a GPIO pin */
|
||
|
+static void realtek_gpio_update_line_imr(struct realtek_gpio_ctrl *ctrl, unsigned int line)
|
||
|
{
|
||
|
- return (value & REALTEK_GPIO_IMR_LINE_MASK) << 2 * pin;
|
||
|
+ void __iomem *reg = ctrl->base + REALTEK_GPIO_REG_IMR;
|
||
|
+ unsigned int line_shift = ctrl->line_imr_pos(line);
|
||
|
+ unsigned int shift = line_shift % 32;
|
||
|
+ u32 irq_type = ctrl->intr_type[line];
|
||
|
+ u32 irq_mask = ctrl->intr_mask[line];
|
||
|
+ u32 reg_val;
|
||
|
+
|
||
|
+ reg += 4 * (line_shift / 32);
|
||
|
+ reg_val = ioread32(reg);
|
||
|
+ reg_val &= ~(REALTEK_GPIO_IMR_LINE_MASK << shift);
|
||
|
+ reg_val |= (irq_type & irq_mask & REALTEK_GPIO_IMR_LINE_MASK) << shift;
|
||
|
+ iowrite32(reg_val, reg);
|
||
|
}
|
||
|
|
||
|
static void realtek_gpio_irq_ack(struct irq_data *data)
|
||
|
{
|
||
|
struct realtek_gpio_ctrl *ctrl = irq_data_to_ctrl(data);
|
||
|
irq_hw_number_t line = irqd_to_hwirq(data);
|
||
|
- unsigned int port = line / 8;
|
||
|
- unsigned int port_pin = line % 8;
|
||
|
|
||
|
- realtek_gpio_clear_isr(ctrl, port, BIT(port_pin));
|
||
|
+ realtek_gpio_clear_isr(ctrl, BIT(line));
|
||
|
}
|
||
|
|
||
|
static void realtek_gpio_irq_unmask(struct irq_data *data)
|
||
|
{
|
||
|
struct realtek_gpio_ctrl *ctrl = irq_data_to_ctrl(data);
|
||
|
unsigned int line = irqd_to_hwirq(data);
|
||
|
- unsigned int port = line / 8;
|
||
|
- unsigned int port_pin = line % 8;
|
||
|
unsigned long flags;
|
||
|
- u16 m;
|
||
|
|
||
|
raw_spin_lock_irqsave(&ctrl->lock, flags);
|
||
|
- m = ctrl->intr_mask[port];
|
||
|
- m |= realtek_gpio_imr_bits(port_pin, REALTEK_GPIO_IMR_LINE_MASK);
|
||
|
- ctrl->intr_mask[port] = m;
|
||
|
- realtek_gpio_write_imr(ctrl, port, ctrl->intr_type[port], m);
|
||
|
+ ctrl->intr_mask[line] = REALTEK_GPIO_IMR_LINE_MASK;
|
||
|
+ realtek_gpio_update_line_imr(ctrl, line);
|
||
|
raw_spin_unlock_irqrestore(&ctrl->lock, flags);
|
||
|
}
|
||
|
|
||
|
@@ -184,16 +204,11 @@ static void realtek_gpio_irq_mask(struct
|
||
|
{
|
||
|
struct realtek_gpio_ctrl *ctrl = irq_data_to_ctrl(data);
|
||
|
unsigned int line = irqd_to_hwirq(data);
|
||
|
- unsigned int port = line / 8;
|
||
|
- unsigned int port_pin = line % 8;
|
||
|
unsigned long flags;
|
||
|
- u16 m;
|
||
|
|
||
|
raw_spin_lock_irqsave(&ctrl->lock, flags);
|
||
|
- m = ctrl->intr_mask[port];
|
||
|
- m &= ~realtek_gpio_imr_bits(port_pin, REALTEK_GPIO_IMR_LINE_MASK);
|
||
|
- ctrl->intr_mask[port] = m;
|
||
|
- realtek_gpio_write_imr(ctrl, port, ctrl->intr_type[port], m);
|
||
|
+ ctrl->intr_mask[line] = 0;
|
||
|
+ realtek_gpio_update_line_imr(ctrl, line);
|
||
|
raw_spin_unlock_irqrestore(&ctrl->lock, flags);
|
||
|
}
|
||
|
|
||
|
@@ -201,10 +216,8 @@ static int realtek_gpio_irq_set_type(str
|
||
|
{
|
||
|
struct realtek_gpio_ctrl *ctrl = irq_data_to_ctrl(data);
|
||
|
unsigned int line = irqd_to_hwirq(data);
|
||
|
- unsigned int port = line / 8;
|
||
|
- unsigned int port_pin = line % 8;
|
||
|
unsigned long flags;
|
||
|
- u16 type, t;
|
||
|
+ u8 type;
|
||
|
|
||
|
switch (flow_type & IRQ_TYPE_SENSE_MASK) {
|
||
|
case IRQ_TYPE_EDGE_FALLING:
|
||
|
@@ -223,11 +236,8 @@ static int realtek_gpio_irq_set_type(str
|
||
|
irq_set_handler_locked(data, handle_edge_irq);
|
||
|
|
||
|
raw_spin_lock_irqsave(&ctrl->lock, flags);
|
||
|
- t = ctrl->intr_type[port];
|
||
|
- t &= ~realtek_gpio_imr_bits(port_pin, REALTEK_GPIO_IMR_LINE_MASK);
|
||
|
- t |= realtek_gpio_imr_bits(port_pin, type);
|
||
|
- ctrl->intr_type[port] = t;
|
||
|
- realtek_gpio_write_imr(ctrl, port, t, ctrl->intr_mask[port]);
|
||
|
+ ctrl->intr_type[line] = type;
|
||
|
+ realtek_gpio_update_line_imr(ctrl, line);
|
||
|
raw_spin_unlock_irqrestore(&ctrl->lock, flags);
|
||
|
|
||
|
return 0;
|
||
|
@@ -238,31 +248,24 @@ static void realtek_gpio_irq_handler(str
|
||
|
struct gpio_chip *gc = irq_desc_get_handler_data(desc);
|
||
|
struct realtek_gpio_ctrl *ctrl = gpiochip_get_data(gc);
|
||
|
struct irq_chip *irq_chip = irq_desc_get_chip(desc);
|
||
|
- unsigned int lines_done;
|
||
|
- unsigned int port_pin_count;
|
||
|
unsigned int irq;
|
||
|
unsigned long status;
|
||
|
int offset;
|
||
|
|
||
|
chained_irq_enter(irq_chip, desc);
|
||
|
|
||
|
- for (lines_done = 0; lines_done < gc->ngpio; lines_done += 8) {
|
||
|
- status = realtek_gpio_read_isr(ctrl, lines_done / 8);
|
||
|
- port_pin_count = min(gc->ngpio - lines_done, 8U);
|
||
|
- for_each_set_bit(offset, &status, port_pin_count) {
|
||
|
- irq = irq_find_mapping(gc->irq.domain, offset + lines_done);
|
||
|
- generic_handle_irq(irq);
|
||
|
- }
|
||
|
+ status = realtek_gpio_read_isr(ctrl);
|
||
|
+ for_each_set_bit(offset, &status, gc->ngpio) {
|
||
|
+ irq = irq_find_mapping(gc->irq.domain, offset);
|
||
|
+ generic_handle_irq(irq);
|
||
|
}
|
||
|
|
||
|
chained_irq_exit(irq_chip, desc);
|
||
|
}
|
||
|
|
||
|
-static inline void __iomem *realtek_gpio_irq_cpu_mask(struct realtek_gpio_ctrl *ctrl,
|
||
|
- unsigned int port, int cpu)
|
||
|
+static inline void __iomem *realtek_gpio_irq_cpu_mask(struct realtek_gpio_ctrl *ctrl, int cpu)
|
||
|
{
|
||
|
- return ctrl->cpumask_base + ctrl->port_offset_u8(port) +
|
||
|
- REALTEK_GPIO_PORTS_PER_BANK * cpu;
|
||
|
+ return ctrl->cpumask_base + REALTEK_GPIO_PORTS_PER_BANK * cpu;
|
||
|
}
|
||
|
|
||
|
static int realtek_gpio_irq_set_affinity(struct irq_data *data,
|
||
|
@@ -270,12 +273,10 @@ static int realtek_gpio_irq_set_affinity
|
||
|
{
|
||
|
struct realtek_gpio_ctrl *ctrl = irq_data_to_ctrl(data);
|
||
|
unsigned int line = irqd_to_hwirq(data);
|
||
|
- unsigned int port = line / 8;
|
||
|
- unsigned int port_pin = line % 8;
|
||
|
void __iomem *irq_cpu_mask;
|
||
|
unsigned long flags;
|
||
|
int cpu;
|
||
|
- u8 v;
|
||
|
+ u32 v;
|
||
|
|
||
|
if (!ctrl->cpumask_base)
|
||
|
return -ENXIO;
|
||
|
@@ -283,15 +284,15 @@ static int realtek_gpio_irq_set_affinity
|
||
|
raw_spin_lock_irqsave(&ctrl->lock, flags);
|
||
|
|
||
|
for_each_cpu(cpu, &ctrl->cpu_irq_maskable) {
|
||
|
- irq_cpu_mask = realtek_gpio_irq_cpu_mask(ctrl, port, cpu);
|
||
|
- v = ioread8(irq_cpu_mask);
|
||
|
+ irq_cpu_mask = realtek_gpio_irq_cpu_mask(ctrl, cpu);
|
||
|
+ v = ctrl->bank_read(irq_cpu_mask);
|
||
|
|
||
|
if (cpumask_test_cpu(cpu, dest))
|
||
|
- v |= BIT(port_pin);
|
||
|
+ v |= BIT(line);
|
||
|
else
|
||
|
- v &= ~BIT(port_pin);
|
||
|
+ v &= ~BIT(line);
|
||
|
|
||
|
- iowrite8(v, irq_cpu_mask);
|
||
|
+ ctrl->bank_write(irq_cpu_mask, v);
|
||
|
}
|
||
|
|
||
|
raw_spin_unlock_irqrestore(&ctrl->lock, flags);
|
||
|
@@ -305,22 +306,23 @@ static int realtek_gpio_irq_init(struct
|
||
|
{
|
||
|
struct realtek_gpio_ctrl *ctrl = gpiochip_get_data(gc);
|
||
|
void __iomem *irq_cpu_mask;
|
||
|
- unsigned int port;
|
||
|
+ u32 mask_all = GENMASK(gc->ngpio - 1, 0);
|
||
|
+ unsigned int line;
|
||
|
int cpu;
|
||
|
|
||
|
- for (port = 0; (port * 8) < gc->ngpio; port++) {
|
||
|
- realtek_gpio_write_imr(ctrl, port, 0, 0);
|
||
|
- realtek_gpio_clear_isr(ctrl, port, GENMASK(7, 0));
|
||
|
-
|
||
|
- /*
|
||
|
- * Uniprocessor builds assume a mask always contains one CPU,
|
||
|
- * so only start the loop if we have at least one maskable CPU.
|
||
|
- */
|
||
|
- if(!cpumask_empty(&ctrl->cpu_irq_maskable)) {
|
||
|
- for_each_cpu(cpu, &ctrl->cpu_irq_maskable) {
|
||
|
- irq_cpu_mask = realtek_gpio_irq_cpu_mask(ctrl, port, cpu);
|
||
|
- iowrite8(GENMASK(7, 0), irq_cpu_mask);
|
||
|
- }
|
||
|
+ for (line = 0; line < gc->ngpio; line++)
|
||
|
+ realtek_gpio_update_line_imr(ctrl, line);
|
||
|
+
|
||
|
+ realtek_gpio_clear_isr(ctrl, mask_all);
|
||
|
+
|
||
|
+ /*
|
||
|
+ * Uniprocessor builds assume a mask always contains one CPU,
|
||
|
+ * so only start the loop if we have at least one maskable CPU.
|
||
|
+ */
|
||
|
+ if(!cpumask_empty(&ctrl->cpu_irq_maskable)) {
|
||
|
+ for_each_cpu(cpu, &ctrl->cpu_irq_maskable) {
|
||
|
+ irq_cpu_mask = realtek_gpio_irq_cpu_mask(ctrl, cpu);
|
||
|
+ ctrl->bank_write(irq_cpu_mask, mask_all);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@@ -393,12 +395,14 @@ static int realtek_gpio_probe(struct pla
|
||
|
|
||
|
if (dev_flags & GPIO_PORTS_REVERSED) {
|
||
|
bgpio_flags = 0;
|
||
|
- ctrl->port_offset_u8 = realtek_gpio_port_offset_u8_rev;
|
||
|
- ctrl->port_offset_u16 = realtek_gpio_port_offset_u16_rev;
|
||
|
+ ctrl->bank_read = realtek_gpio_bank_read;
|
||
|
+ ctrl->bank_write = realtek_gpio_bank_write;
|
||
|
+ ctrl->line_imr_pos = realtek_gpio_line_imr_pos;
|
||
|
} else {
|
||
|
bgpio_flags = BGPIOF_BIG_ENDIAN_BYTE_ORDER;
|
||
|
- ctrl->port_offset_u8 = realtek_gpio_port_offset_u8;
|
||
|
- ctrl->port_offset_u16 = realtek_gpio_port_offset_u16;
|
||
|
+ ctrl->bank_read = realtek_gpio_bank_read_swapped;
|
||
|
+ ctrl->bank_write = realtek_gpio_bank_write_swapped;
|
||
|
+ ctrl->line_imr_pos = realtek_gpio_line_imr_pos_swapped;
|
||
|
}
|
||
|
|
||
|
err = bgpio_init(&ctrl->gc, dev, 4,
|