2022-02-26 20:45:50

by Michael Walle

[permalink] [raw]
Subject: [PATCH v2 0/5] pinctrl: microchip-sgpio: locking and synchronous output

There are boards which use the output of the SGPIO to drive I2C muxers.
SGPIO right now is broken in a way that when the software sets this bit
there is a rather large delay until that value ends up on the hardware
pin.

While digging into this, I've noticed that there is no locking at all
in this driver. Add locking for all RWM accesses.

Please note, that parts of the modification of the first patch are
removed again in a later patch. This is because the first patch is
intended to be backported to the stable trees.

This was also just tested on a LAN9668 SoC. If you have additional
hardware, please test.

Changes since v1:
- add Ocelot support

Michael Walle (5):
pinctrl: microchip-sgpio: lock RMW access
pinctrl: microchip-sgpio: don't do RMW for interrupt ack register
pinctrl: microchip-sgpio: use regmap_update_bits()
pinctrl: microchip-sgpio: return error in spgio_output_set()
pinctrl: microchip-sgpio: wait until output is actually set

drivers/pinctrl/pinctrl-microchip-sgpio.c | 112 +++++++++++++++++++---
1 file changed, 97 insertions(+), 15 deletions(-)

--
2.30.2


2022-02-26 20:45:51

by Michael Walle

[permalink] [raw]
Subject: [PATCH v2 2/5] pinctrl: microchip-sgpio: don't do RMW for interrupt ack register

The interrupt ack register has the usual "write one to clear" semantics.
No read-modify-write is required here.

This is also a preparation patch to change the sgpio_clrsetbits() to use
regmap_update_bits() which don't write the value if it is not changed.

Signed-off-by: Michael Walle <[email protected]>
---
drivers/pinctrl/pinctrl-microchip-sgpio.c | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/drivers/pinctrl/pinctrl-microchip-sgpio.c b/drivers/pinctrl/pinctrl-microchip-sgpio.c
index 666f1e3889e0..29cce0609fd9 100644
--- a/drivers/pinctrl/pinctrl-microchip-sgpio.c
+++ b/drivers/pinctrl/pinctrl-microchip-sgpio.c
@@ -640,7 +640,14 @@ static void microchip_sgpio_irq_unmask(struct irq_data *data)

static void microchip_sgpio_irq_ack(struct irq_data *data)
{
- microchip_sgpio_irq_setreg(data, REG_INT_ACK, false);
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
+ struct sgpio_bank *bank = gpiochip_get_data(chip);
+ unsigned int gpio = irqd_to_hwirq(data);
+ struct sgpio_port_addr addr;
+
+ sgpio_pin_to_addr(bank->priv, gpio, &addr);
+
+ sgpio_writel(bank->priv, BIT(addr.port), REG_INT_ACK, addr.bit);
}

static int microchip_sgpio_irq_set_type(struct irq_data *data, unsigned int type)
--
2.30.2

2022-02-26 20:45:54

by Michael Walle

[permalink] [raw]
Subject: [PATCH v2 1/5] pinctrl: microchip-sgpio: lock RMW access

Protect any RMW access to the registers by a spinlock.

Fixes: 7e5ea974e61c ("pinctrl: pinctrl-microchip-sgpio: Add pinctrl driver for Microsemi Serial GPIO")
Signed-off-by: Michael Walle <[email protected]>
---
drivers/pinctrl/pinctrl-microchip-sgpio.c | 15 +++++++++++++++
1 file changed, 15 insertions(+)

diff --git a/drivers/pinctrl/pinctrl-microchip-sgpio.c b/drivers/pinctrl/pinctrl-microchip-sgpio.c
index 639f1130e989..666f1e3889e0 100644
--- a/drivers/pinctrl/pinctrl-microchip-sgpio.c
+++ b/drivers/pinctrl/pinctrl-microchip-sgpio.c
@@ -19,6 +19,7 @@
#include <linux/property.h>
#include <linux/regmap.h>
#include <linux/reset.h>
+#include <linux/spinlock.h>

#include "core.h"
#include "pinconf.h"
@@ -116,6 +117,7 @@ struct sgpio_priv {
u32 clock;
struct regmap *regs;
const struct sgpio_properties *properties;
+ spinlock_t lock;
};

struct sgpio_port_addr {
@@ -229,6 +231,7 @@ static void sgpio_output_set(struct sgpio_priv *priv,
int value)
{
unsigned int bit = SGPIO_SRC_BITS * addr->bit;
+ unsigned long flags;
u32 clr, set;

switch (priv->properties->arch) {
@@ -247,7 +250,10 @@ static void sgpio_output_set(struct sgpio_priv *priv,
default:
return;
}
+
+ spin_lock_irqsave(&priv->lock, flags);
sgpio_clrsetbits(priv, REG_PORT_CONFIG, addr->port, clr, set);
+ spin_unlock_irqrestore(&priv->lock, flags);
}

static int sgpio_output_get(struct sgpio_priv *priv,
@@ -575,10 +581,13 @@ static void microchip_sgpio_irq_settype(struct irq_data *data,
struct sgpio_bank *bank = gpiochip_get_data(chip);
unsigned int gpio = irqd_to_hwirq(data);
struct sgpio_port_addr addr;
+ unsigned long flags;
u32 ena;

sgpio_pin_to_addr(bank->priv, gpio, &addr);

+ spin_lock_irqsave(&bank->priv->lock, flags);
+
/* Disable interrupt while changing type */
ena = sgpio_readl(bank->priv, REG_INT_ENABLE, addr.bit);
sgpio_writel(bank->priv, ena & ~BIT(addr.port), REG_INT_ENABLE, addr.bit);
@@ -595,6 +604,8 @@ static void microchip_sgpio_irq_settype(struct irq_data *data,

/* Possibly re-enable interrupts */
sgpio_writel(bank->priv, ena, REG_INT_ENABLE, addr.bit);
+
+ spin_unlock_irqrestore(&bank->priv->lock, flags);
}

static void microchip_sgpio_irq_setreg(struct irq_data *data,
@@ -605,13 +616,16 @@ static void microchip_sgpio_irq_setreg(struct irq_data *data,
struct sgpio_bank *bank = gpiochip_get_data(chip);
unsigned int gpio = irqd_to_hwirq(data);
struct sgpio_port_addr addr;
+ unsigned long flags;

sgpio_pin_to_addr(bank->priv, gpio, &addr);

+ spin_lock_irqsave(&bank->priv->lock, flags);
if (clear)
sgpio_clrsetbits(bank->priv, reg, addr.bit, BIT(addr.port), 0);
else
sgpio_clrsetbits(bank->priv, reg, addr.bit, 0, BIT(addr.port));
+ spin_unlock_irqrestore(&bank->priv->lock, flags);
}

static void microchip_sgpio_irq_mask(struct irq_data *data)
@@ -833,6 +847,7 @@ static int microchip_sgpio_probe(struct platform_device *pdev)
return -ENOMEM;

priv->dev = dev;
+ spin_lock_init(&priv->lock);

reset = devm_reset_control_get_optional_shared(&pdev->dev, "switch");
if (IS_ERR(reset))
--
2.30.2

2022-02-26 20:45:55

by Michael Walle

[permalink] [raw]
Subject: [PATCH v2 3/5] pinctrl: microchip-sgpio: use regmap_update_bits()

Convert sgpio_clrsetbits() to use regmap_update_bits() and drop the
spinlocks because regmap already takes care of the locking.

Signed-off-by: Michael Walle <[email protected]>
---
drivers/pinctrl/pinctrl-microchip-sgpio.c | 15 ++++-----------
1 file changed, 4 insertions(+), 11 deletions(-)

diff --git a/drivers/pinctrl/pinctrl-microchip-sgpio.c b/drivers/pinctrl/pinctrl-microchip-sgpio.c
index 29cce0609fd9..a6ad61aa845e 100644
--- a/drivers/pinctrl/pinctrl-microchip-sgpio.c
+++ b/drivers/pinctrl/pinctrl-microchip-sgpio.c
@@ -168,12 +168,11 @@ static void sgpio_writel(struct sgpio_priv *priv,
static inline void sgpio_clrsetbits(struct sgpio_priv *priv,
u32 rno, u32 off, u32 clear, u32 set)
{
- u32 val = sgpio_readl(priv, rno, off);
-
- val &= ~clear;
- val |= set;
+ u32 addr = sgpio_get_addr(priv, rno, off);
+ int ret;

- sgpio_writel(priv, val, rno, off);
+ ret = regmap_update_bits(priv->regs, addr, clear | set, set);
+ WARN_ONCE(ret, "error updating sgpio reg %d\n", ret);
}

static inline void sgpio_configure_bitstream(struct sgpio_priv *priv)
@@ -231,7 +230,6 @@ static void sgpio_output_set(struct sgpio_priv *priv,
int value)
{
unsigned int bit = SGPIO_SRC_BITS * addr->bit;
- unsigned long flags;
u32 clr, set;

switch (priv->properties->arch) {
@@ -251,9 +249,7 @@ static void sgpio_output_set(struct sgpio_priv *priv,
return;
}

- spin_lock_irqsave(&priv->lock, flags);
sgpio_clrsetbits(priv, REG_PORT_CONFIG, addr->port, clr, set);
- spin_unlock_irqrestore(&priv->lock, flags);
}

static int sgpio_output_get(struct sgpio_priv *priv,
@@ -616,16 +612,13 @@ static void microchip_sgpio_irq_setreg(struct irq_data *data,
struct sgpio_bank *bank = gpiochip_get_data(chip);
unsigned int gpio = irqd_to_hwirq(data);
struct sgpio_port_addr addr;
- unsigned long flags;

sgpio_pin_to_addr(bank->priv, gpio, &addr);

- spin_lock_irqsave(&bank->priv->lock, flags);
if (clear)
sgpio_clrsetbits(bank->priv, reg, addr.bit, BIT(addr.port), 0);
else
sgpio_clrsetbits(bank->priv, reg, addr.bit, 0, BIT(addr.port));
- spin_unlock_irqrestore(&bank->priv->lock, flags);
}

static void microchip_sgpio_irq_mask(struct irq_data *data)
--
2.30.2

2022-02-26 20:45:59

by Michael Walle

[permalink] [raw]
Subject: [PATCH v2 5/5] pinctrl: microchip-sgpio: wait until output is actually set

Right now, when a gpio value is set, the actual hardware pin gets set
asynchronously. When linux write the output register, it takes some time
until it is actually propagated to the output shift registers. If that
output port is connected to an I2C mux for example, the linux driver
assumes the I2C bus is already switched although it is not.

Fortunately, there is a single shot mode with a feedback: you can
trigger the single shot and the hardware will clear that bit once it has
finished the clocking and strobed the load signal of the shift
registers. This can take a considerable amount of time though.
Measuremens have shown that it takes up to a whole burst cycle gap which
is about 50ms on the largest setting. Therefore, we have to mark the
output bank as sleepable. To avoid unnecessary waiting, just trigger the
single shot if the value was actually changed.

Signed-off-by: Michael Walle <[email protected]>
---
drivers/pinctrl/pinctrl-microchip-sgpio.c | 69 ++++++++++++++++++++++-
1 file changed, 68 insertions(+), 1 deletion(-)

diff --git a/drivers/pinctrl/pinctrl-microchip-sgpio.c b/drivers/pinctrl/pinctrl-microchip-sgpio.c
index a5085a215b8b..80a8939ad0c0 100644
--- a/drivers/pinctrl/pinctrl-microchip-sgpio.c
+++ b/drivers/pinctrl/pinctrl-microchip-sgpio.c
@@ -64,11 +64,13 @@ struct sgpio_properties {
#define SGPIO_LUTON_BIT_SOURCE GENMASK(11, 0)

#define SGPIO_OCELOT_AUTO_REPEAT BIT(10)
+#define SGPIO_OCELOT_SINGLE_SHOT BIT(11)
#define SGPIO_OCELOT_PORT_WIDTH GENMASK(8, 7)
#define SGPIO_OCELOT_CLK_FREQ GENMASK(19, 8)
#define SGPIO_OCELOT_BIT_SOURCE GENMASK(23, 12)

#define SGPIO_SPARX5_AUTO_REPEAT BIT(6)
+#define SGPIO_SPARX5_SINGLE_SHOT BIT(7)
#define SGPIO_SPARX5_PORT_WIDTH GENMASK(4, 3)
#define SGPIO_SPARX5_CLK_FREQ GENMASK(19, 8)
#define SGPIO_SPARX5_BIT_SOURCE GENMASK(23, 12)
@@ -118,6 +120,8 @@ struct sgpio_priv {
struct regmap *regs;
const struct sgpio_properties *properties;
spinlock_t lock;
+ /* protects the config register and single shot mode */
+ struct mutex poll_lock;
};

struct sgpio_port_addr {
@@ -225,12 +229,64 @@ static inline void sgpio_configure_clock(struct sgpio_priv *priv, u32 clkfrq)
sgpio_clrsetbits(priv, REG_SIO_CLOCK, 0, clr, set);
}

+static int sgpio_single_shot(struct sgpio_priv *priv)
+{
+ u32 addr = sgpio_get_addr(priv, REG_SIO_CONFIG, 0);
+ int ret, ret2;
+ u32 ctrl;
+ unsigned int single_shot;
+ unsigned int auto_repeat;
+
+ switch (priv->properties->arch) {
+ case SGPIO_ARCH_LUTON:
+ /* not supported for now */
+ return 0;
+ case SGPIO_ARCH_OCELOT:
+ single_shot = SGPIO_OCELOT_SINGLE_SHOT;
+ auto_repeat = SGPIO_OCELOT_AUTO_REPEAT;
+ break;
+ case SGPIO_ARCH_SPARX5:
+ single_shot = SGPIO_SPARX5_SINGLE_SHOT;
+ auto_repeat = SGPIO_SPARX5_AUTO_REPEAT;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /*
+ * Trigger immediate burst. This only works when auto repeat is turned
+ * off. Otherwise, the single shot bit will never be cleared by the
+ * hardware. Measurements showed that an update might take as long as
+ * the burst gap. On a LAN9668 this is about 50ms for the largest
+ * setting.
+ * After the manual burst, reenable the auto repeat mode again.
+ */
+ mutex_lock(&priv->poll_lock);
+ ret = regmap_update_bits(priv->regs, addr, single_shot | auto_repeat,
+ single_shot);
+ if (ret)
+ goto out;
+
+ ret = regmap_read_poll_timeout(priv->regs, addr, ctrl,
+ !(ctrl & single_shot), 100, 60000);
+
+ /* reenable auto repeat mode even if there was an error */
+ ret2 = regmap_update_bits(priv->regs, addr, auto_repeat, auto_repeat);
+out:
+ mutex_unlock(&priv->poll_lock);
+
+ return ret ?: ret2;
+}
+
static int sgpio_output_set(struct sgpio_priv *priv,
struct sgpio_port_addr *addr,
int value)
{
unsigned int bit = SGPIO_SRC_BITS * addr->bit;
+ u32 reg = sgpio_get_addr(priv, REG_PORT_CONFIG, addr->port);
+ bool changed;
u32 clr, set;
+ int ret;

switch (priv->properties->arch) {
case SGPIO_ARCH_LUTON:
@@ -249,7 +305,16 @@ static int sgpio_output_set(struct sgpio_priv *priv,
return -EINVAL;
}

- sgpio_clrsetbits(priv, REG_PORT_CONFIG, addr->port, clr, set);
+ ret = regmap_update_bits_check(priv->regs, reg, clr | set, set,
+ &changed);
+ if (ret)
+ return ret;
+
+ if (changed) {
+ ret = sgpio_single_shot(priv);
+ if (ret)
+ return ret;
+ }

return 0;
}
@@ -788,6 +853,7 @@ static int microchip_sgpio_register_bank(struct device *dev,
gc->of_gpio_n_cells = 3;
gc->base = -1;
gc->ngpio = ngpios;
+ gc->can_sleep = !bank->is_input;

if (bank->is_input && priv->properties->flags & SGPIO_FLAGS_HAS_IRQ) {
int irq = fwnode_irq_get(fwnode, 0);
@@ -848,6 +914,7 @@ static int microchip_sgpio_probe(struct platform_device *pdev)

priv->dev = dev;
spin_lock_init(&priv->lock);
+ mutex_init(&priv->poll_lock);

reset = devm_reset_control_get_optional_shared(&pdev->dev, "switch");
if (IS_ERR(reset))
--
2.30.2

2022-02-26 20:46:01

by Michael Walle

[permalink] [raw]
Subject: [PATCH v2 4/5] pinctrl: microchip-sgpio: return error in spgio_output_set()

Make sgpio_output_set() return an error value. Don't just ignore the
return value of any regmap access but propagate it to our callers. Even
if the accesses never fail, this is a preparation patch to add single
shot mode where we need to poll a bit and thus we might get -ETIMEDOUT.

Signed-off-by: Michael Walle <[email protected]>
---
drivers/pinctrl/pinctrl-microchip-sgpio.c | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/drivers/pinctrl/pinctrl-microchip-sgpio.c b/drivers/pinctrl/pinctrl-microchip-sgpio.c
index a6ad61aa845e..a5085a215b8b 100644
--- a/drivers/pinctrl/pinctrl-microchip-sgpio.c
+++ b/drivers/pinctrl/pinctrl-microchip-sgpio.c
@@ -225,9 +225,9 @@ static inline void sgpio_configure_clock(struct sgpio_priv *priv, u32 clkfrq)
sgpio_clrsetbits(priv, REG_SIO_CLOCK, 0, clr, set);
}

-static void sgpio_output_set(struct sgpio_priv *priv,
- struct sgpio_port_addr *addr,
- int value)
+static int sgpio_output_set(struct sgpio_priv *priv,
+ struct sgpio_port_addr *addr,
+ int value)
{
unsigned int bit = SGPIO_SRC_BITS * addr->bit;
u32 clr, set;
@@ -246,10 +246,12 @@ static void sgpio_output_set(struct sgpio_priv *priv,
set = FIELD_PREP(SGPIO_SPARX5_BIT_SOURCE, value << bit);
break;
default:
- return;
+ return -EINVAL;
}

sgpio_clrsetbits(priv, REG_PORT_CONFIG, addr->port, clr, set);
+
+ return 0;
}

static int sgpio_output_get(struct sgpio_priv *priv,
@@ -335,7 +337,7 @@ static int sgpio_pinconf_set(struct pinctrl_dev *pctldev, unsigned int pin,
case PIN_CONFIG_OUTPUT:
if (bank->is_input)
return -EINVAL;
- sgpio_output_set(priv, &addr, arg);
+ err = sgpio_output_set(priv, &addr, arg);
break;

default:
@@ -475,9 +477,7 @@ static int microchip_sgpio_direction_output(struct gpio_chip *gc,

sgpio_pin_to_addr(priv, gpio, &addr);

- sgpio_output_set(priv, &addr, value);
-
- return 0;
+ return sgpio_output_set(priv, &addr, value);
}

static int microchip_sgpio_get_direction(struct gpio_chip *gc, unsigned int gpio)
--
2.30.2

2022-03-16 00:33:05

by Linus Walleij

[permalink] [raw]
Subject: Re: [PATCH v2 0/5] pinctrl: microchip-sgpio: locking and synchronous output

On Sat, Feb 26, 2022 at 9:45 PM Michael Walle <[email protected]> wrote:

> There are boards which use the output of the SGPIO to drive I2C muxers.
> SGPIO right now is broken in a way that when the software sets this bit
> there is a rather large delay until that value ends up on the hardware
> pin.
>
> While digging into this, I've noticed that there is no locking at all
> in this driver. Add locking for all RWM accesses.
>
> Please note, that parts of the modification of the first patch are
> removed again in a later patch. This is because the first patch is
> intended to be backported to the stable trees.
>
> This was also just tested on a LAN9668 SoC. If you have additional
> hardware, please test.

Nobody is protesting for three weeks or so, I just applied the patches
for v5.18.

If there are problems we can fix them in the v5.18-rc:s

Yours,
Linus Walleij