pinctrl: pinctrl-microchip-sgpio: Add irq support (for sparx5)
authorLars Povlsen <lars.povlsen@microchip.com>
Wed, 9 Dec 2020 14:27:51 +0000 (15:27 +0100)
committerLinus Walleij <linus.walleij@linaro.org>
Fri, 11 Dec 2020 22:48:52 +0000 (23:48 +0100)
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@microchip.com>
Link: https://lore.kernel.org/r/20201209142753.683208-2-lars.povlsen@microchip.com
[Select GPIOLIB_IRQCHIP in Kconfig]
Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
drivers/pinctrl/Kconfig
drivers/pinctrl/pinctrl-microchip-sgpio.c

index 5946f8077e718208b79f6858bbfdb880596e5803..67352375036f9effe5f3519d7e02c70a7658a38c 100644 (file)
@@ -380,6 +380,7 @@ config PINCTRL_MICROCHIP_SGPIO
        depends on OF
        depends on HAS_IOMEM
        select GPIOLIB
+       select GPIOLIB_IRQCHIP
        select GENERIC_PINCONF
        select GENERIC_PINCTRL_GROUPS
        select GENERIC_PINMUX_FUNCTIONS
index e1824197318e80a8c12c86325cc643cd14bbc996..f35edb0eac40503ccf9d5d0b02c8b6bea576aef9 100644 (file)
@@ -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);