[PATCH -next 1/3] pinctrl: pinctrl-microchip-sgpio: Add irq support (for sparx5)

From: Lars Povlsen
Date: Wed Dec 09 2020 - 09:29:50 EST


This adds 'interrupt-controller' features for the signals available on
the Microchip SGPIO controller, however only for controller versions
on the Sparx5 platform (or later).

Signed-off-by: Lars Povlsen <lars.povlsen@xxxxxxxxxxxxx>
---
drivers/pinctrl/pinctrl-microchip-sgpio.c | 187 +++++++++++++++++++++-
1 file changed, 185 insertions(+), 2 deletions(-)

diff --git a/drivers/pinctrl/pinctrl-microchip-sgpio.c b/drivers/pinctrl/pinctrl-microchip-sgpio.c
index e1824197318e..f35edb0eac40 100644
--- a/drivers/pinctrl/pinctrl-microchip-sgpio.c
+++ b/drivers/pinctrl/pinctrl-microchip-sgpio.c
@@ -31,6 +31,11 @@ enum {
REG_PORT_ENABLE,
REG_SIO_CONFIG,
REG_SIO_CLOCK,
+ REG_INT_POLARITY,
+ REG_INT_TRIGGER,
+ REG_INT_ACK,
+ REG_INT_ENABLE,
+ REG_INT_IDENT,
MAXREG
};

@@ -40,8 +45,13 @@ enum {
SGPIO_ARCH_SPARX5,
};

+enum {
+ SGPIO_FLAGS_HAS_IRQ = BIT(0),
+};
+
struct sgpio_properties {
int arch;
+ int flags;
u8 regoff[MAXREG];
};

@@ -60,6 +70,16 @@ struct sgpio_properties {
#define SGPIO_SPARX5_CLK_FREQ GENMASK(19, 8)
#define SGPIO_SPARX5_BIT_SOURCE GENMASK(23, 12)

+#define SGPIO_MASTER_INTR_ENA BIT(0)
+
+#define SGPIO_INT_TRG_LEVEL 0
+#define SGPIO_INT_TRG_EDGE 1
+#define SGPIO_INT_TRG_EDGE_FALL 2
+#define SGPIO_INT_TRG_EDGE_RISE 3
+
+#define SGPIO_TRG_LEVEL_HIGH 0
+#define SGPIO_TRG_LEVEL_LOW 1
+
static const struct sgpio_properties properties_luton = {
.arch = SGPIO_ARCH_LUTON,
.regoff = { 0x00, 0x09, 0x29, 0x2a, 0x2b },
@@ -72,7 +92,8 @@ static const struct sgpio_properties properties_ocelot = {

static const struct sgpio_properties properties_sparx5 = {
.arch = SGPIO_ARCH_SPARX5,
- .regoff = { 0x00, 0x06, 0x26, 0x04, 0x05 },
+ .flags = SGPIO_FLAGS_HAS_IRQ,
+ .regoff = { 0x00, 0x06, 0x26, 0x04, 0x05, 0x2a, 0x32, 0x3a, 0x3e, 0x42 },
};

static const char * const functions[] = { "gpio" };
@@ -107,6 +128,11 @@ static inline void sgpio_pin_to_addr(struct sgpio_priv *priv, int pin,
addr->bit = pin % priv->bitcount;
}

+static inline int sgpio_addr_to_pin(struct sgpio_priv *priv, int port, int bit)
+{
+ return bit + port * priv->bitcount;
+}
+
static inline u32 sgpio_readl(struct sgpio_priv *priv, u32 rno, u32 off)
{
u32 __iomem *reg = &priv->regs[priv->properties->regoff[rno] + off];
@@ -478,7 +504,7 @@ static int microchip_sgpio_of_xlate(struct gpio_chip *gc,
gpiospec->args[1] > priv->bitcount)
return -EINVAL;

- pin = gpiospec->args[1] + gpiospec->args[0] * priv->bitcount;
+ pin = sgpio_addr_to_pin(priv, gpiospec->args[0], gpiospec->args[1]);

if (pin > gc->ngpio)
return -EINVAL;
@@ -527,6 +553,133 @@ static int microchip_sgpio_get_ports(struct sgpio_priv *priv)
return 0;
}

+static void microchip_sgpio_irq_settype(struct irq_data *data,
+ int type,
+ int polarity)
+{
+ 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;
+ u32 ena;
+
+ sgpio_pin_to_addr(bank->priv, gpio, &addr);
+
+ /* 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);
+
+ /* Type value spread over 2 registers sets: low, high bit */
+ sgpio_clrsetbits(bank->priv, REG_INT_TRIGGER, addr.bit,
+ BIT(addr.port), (!!(type & 0x1)) << addr.port);
+ sgpio_clrsetbits(bank->priv, REG_INT_TRIGGER + SGPIO_MAX_BITS, addr.bit,
+ BIT(addr.port), (!!(type & 0x2)) << addr.port);
+
+ if (type == SGPIO_INT_TRG_LEVEL)
+ sgpio_clrsetbits(bank->priv, REG_INT_POLARITY, addr.bit,
+ BIT(addr.port), polarity << addr.port);
+
+ /* Possibly re-enable interrupts */
+ sgpio_writel(bank->priv, ena, REG_INT_ENABLE, addr.bit);
+}
+
+static void microchip_sgpio_irq_setreg(struct irq_data *data,
+ int reg,
+ bool clear)
+{
+ 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);
+
+ 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));
+}
+
+static void microchip_sgpio_irq_mask(struct irq_data *data)
+{
+ microchip_sgpio_irq_setreg(data, REG_INT_ENABLE, true);
+}
+
+static void microchip_sgpio_irq_unmask(struct irq_data *data)
+{
+ microchip_sgpio_irq_setreg(data, REG_INT_ENABLE, false);
+}
+
+static void microchip_sgpio_irq_ack(struct irq_data *data)
+{
+ microchip_sgpio_irq_setreg(data, REG_INT_ACK, false);
+}
+
+static int microchip_sgpio_irq_set_type(struct irq_data *data, unsigned int type)
+{
+ type &= IRQ_TYPE_SENSE_MASK;
+
+ switch (type) {
+ case IRQ_TYPE_EDGE_BOTH:
+ irq_set_handler_locked(data, handle_edge_irq);
+ microchip_sgpio_irq_settype(data, SGPIO_INT_TRG_EDGE, 0);
+ break;
+ case IRQ_TYPE_EDGE_RISING:
+ irq_set_handler_locked(data, handle_edge_irq);
+ microchip_sgpio_irq_settype(data, SGPIO_INT_TRG_EDGE_RISE, 0);
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ irq_set_handler_locked(data, handle_edge_irq);
+ microchip_sgpio_irq_settype(data, SGPIO_INT_TRG_EDGE_FALL, 0);
+ break;
+ case IRQ_TYPE_LEVEL_HIGH:
+ irq_set_handler_locked(data, handle_level_irq);
+ microchip_sgpio_irq_settype(data, SGPIO_INT_TRG_LEVEL, SGPIO_TRG_LEVEL_HIGH);
+ break;
+ case IRQ_TYPE_LEVEL_LOW:
+ irq_set_handler_locked(data, handle_level_irq);
+ microchip_sgpio_irq_settype(data, SGPIO_INT_TRG_LEVEL, SGPIO_TRG_LEVEL_LOW);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const struct irq_chip microchip_sgpio_irqchip = {
+ .name = "gpio",
+ .irq_mask = microchip_sgpio_irq_mask,
+ .irq_ack = microchip_sgpio_irq_ack,
+ .irq_unmask = microchip_sgpio_irq_unmask,
+ .irq_set_type = microchip_sgpio_irq_set_type,
+};
+
+static void sgpio_irq_handler(struct irq_desc *desc)
+{
+ struct irq_chip *parent_chip = irq_desc_get_chip(desc);
+ struct gpio_chip *chip = irq_desc_get_handler_data(desc);
+ struct sgpio_bank *bank = gpiochip_get_data(chip);
+ struct sgpio_priv *priv = bank->priv;
+ int bit, port, gpio;
+ long val;
+
+ for (bit = 0; bit < priv->bitcount; bit++) {
+ val = sgpio_readl(priv, REG_INT_IDENT, bit);
+ if (!val)
+ continue;
+
+ chained_irq_enter(parent_chip, desc);
+
+ for_each_set_bit(port, &val, SGPIO_BITS_PER_WORD) {
+ gpio = sgpio_addr_to_pin(priv, port, bit);
+ generic_handle_irq(irq_linear_revmap(chip->irq.domain, gpio));
+ }
+
+ chained_irq_exit(parent_chip, desc);
+ }
+}
+
static int microchip_sgpio_register_bank(struct device *dev,
struct sgpio_priv *priv,
struct fwnode_handle *fwnode,
@@ -608,6 +761,36 @@ static int microchip_sgpio_register_bank(struct device *dev,
gc->base = -1;
gc->ngpio = ngpios;

+ if (bank->is_input && priv->properties->flags & SGPIO_FLAGS_HAS_IRQ) {
+ int irq = fwnode_irq_get(fwnode, 0);
+
+ if (irq) {
+ struct gpio_irq_chip *girq = &gc->irq;
+
+ girq->chip = devm_kmemdup(dev, &microchip_sgpio_irqchip,
+ sizeof(microchip_sgpio_irqchip),
+ GFP_KERNEL);
+ if (!girq->chip)
+ return -ENOMEM;
+ girq->parent_handler = sgpio_irq_handler;
+ girq->num_parents = 1;
+ girq->parents = devm_kcalloc(dev, 1,
+ sizeof(*girq->parents),
+ GFP_KERNEL);
+ if (!girq->parents)
+ return -ENOMEM;
+ girq->parents[0] = irq;
+ girq->default_type = IRQ_TYPE_NONE;
+ girq->handler = handle_bad_irq;
+
+ /* Disable all individual pins */
+ for (i = 0; i < SGPIO_MAX_BITS; i++)
+ sgpio_writel(priv, 0, REG_INT_ENABLE, i);
+ /* Master enable */
+ sgpio_clrsetbits(priv, REG_SIO_CONFIG, 0, 0, SGPIO_MASTER_INTR_ENA);
+ }
+ }
+
ret = devm_gpiochip_add_data(dev, gc, bank);
if (ret)
dev_err(dev, "Failed to register: ret %d\n", ret);
--
2.25.1