genirq: Remove irq argument from irq flow handlers
[linux-drm-fsl-dcu.git] / drivers / gpio / gpio-brcmstb.c
index 4630a8133ea6b94726a84c48486ff75eb4075bb2..4c64627c6bb5db745a3efff6d64eef9d8416e331 100644 (file)
 #include <linux/of_irq.h>
 #include <linux/module.h>
 #include <linux/basic_mmio_gpio.h>
+#include <linux/irqdomain.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/interrupt.h>
+#include <linux/reboot.h>
 
 #define GIO_BANK_SIZE           0x20
 #define GIO_ODEN(bank)          (((bank) * GIO_BANK_SIZE) + 0x00)
@@ -34,14 +38,18 @@ struct brcmstb_gpio_bank {
        struct bgpio_chip bgc;
        struct brcmstb_gpio_priv *parent_priv;
        u32 width;
+       struct irq_chip irq_chip;
 };
 
 struct brcmstb_gpio_priv {
        struct list_head bank_list;
        void __iomem *reg_base;
-       int num_banks;
        struct platform_device *pdev;
+       int parent_irq;
        int gpio_base;
+       bool can_wake;
+       int parent_wake_irq;
+       struct notifier_block reboot_notifier;
 };
 
 #define MAX_GPIO_PER_BANK           32
@@ -63,6 +71,203 @@ brcmstb_gpio_gc_to_priv(struct gpio_chip *gc)
        return bank->parent_priv;
 }
 
+static void brcmstb_gpio_set_imask(struct brcmstb_gpio_bank *bank,
+               unsigned int offset, bool enable)
+{
+       struct bgpio_chip *bgc = &bank->bgc;
+       struct brcmstb_gpio_priv *priv = bank->parent_priv;
+       u32 mask = bgc->pin2mask(bgc, offset);
+       u32 imask;
+       unsigned long flags;
+
+       spin_lock_irqsave(&bgc->lock, flags);
+       imask = bgc->read_reg(priv->reg_base + GIO_MASK(bank->id));
+       if (enable)
+               imask |= mask;
+       else
+               imask &= ~mask;
+       bgc->write_reg(priv->reg_base + GIO_MASK(bank->id), imask);
+       spin_unlock_irqrestore(&bgc->lock, flags);
+}
+
+/* -------------------- IRQ chip functions -------------------- */
+
+static void brcmstb_gpio_irq_mask(struct irq_data *d)
+{
+       struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+       struct brcmstb_gpio_bank *bank = brcmstb_gpio_gc_to_bank(gc);
+
+       brcmstb_gpio_set_imask(bank, d->hwirq, false);
+}
+
+static void brcmstb_gpio_irq_unmask(struct irq_data *d)
+{
+       struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+       struct brcmstb_gpio_bank *bank = brcmstb_gpio_gc_to_bank(gc);
+
+       brcmstb_gpio_set_imask(bank, d->hwirq, true);
+}
+
+static int brcmstb_gpio_irq_set_type(struct irq_data *d, unsigned int type)
+{
+       struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+       struct brcmstb_gpio_bank *bank = brcmstb_gpio_gc_to_bank(gc);
+       struct brcmstb_gpio_priv *priv = bank->parent_priv;
+       u32 mask = BIT(d->hwirq);
+       u32 edge_insensitive, iedge_insensitive;
+       u32 edge_config, iedge_config;
+       u32 level, ilevel;
+       unsigned long flags;
+
+       switch (type) {
+       case IRQ_TYPE_LEVEL_LOW:
+               level = 0;
+               edge_config = 0;
+               edge_insensitive = 0;
+               break;
+       case IRQ_TYPE_LEVEL_HIGH:
+               level = mask;
+               edge_config = 0;
+               edge_insensitive = 0;
+               break;
+       case IRQ_TYPE_EDGE_FALLING:
+               level = 0;
+               edge_config = 0;
+               edge_insensitive = 0;
+               break;
+       case IRQ_TYPE_EDGE_RISING:
+               level = 0;
+               edge_config = mask;
+               edge_insensitive = 0;
+               break;
+       case IRQ_TYPE_EDGE_BOTH:
+               level = 0;
+               edge_config = 0;  /* don't care, but want known value */
+               edge_insensitive = mask;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       spin_lock_irqsave(&bank->bgc.lock, flags);
+
+       iedge_config = bank->bgc.read_reg(priv->reg_base +
+                       GIO_EC(bank->id)) & ~mask;
+       iedge_insensitive = bank->bgc.read_reg(priv->reg_base +
+                       GIO_EI(bank->id)) & ~mask;
+       ilevel = bank->bgc.read_reg(priv->reg_base +
+                       GIO_LEVEL(bank->id)) & ~mask;
+
+       bank->bgc.write_reg(priv->reg_base + GIO_EC(bank->id),
+                       iedge_config | edge_config);
+       bank->bgc.write_reg(priv->reg_base + GIO_EI(bank->id),
+                       iedge_insensitive | edge_insensitive);
+       bank->bgc.write_reg(priv->reg_base + GIO_LEVEL(bank->id),
+                       ilevel | level);
+
+       spin_unlock_irqrestore(&bank->bgc.lock, flags);
+       return 0;
+}
+
+static int brcmstb_gpio_priv_set_wake(struct brcmstb_gpio_priv *priv,
+               unsigned int enable)
+{
+       int ret = 0;
+
+       /*
+        * Only enable wake IRQ once for however many hwirqs can wake
+        * since they all use the same wake IRQ.  Mask will be set
+        * up appropriately thanks to IRQCHIP_MASK_ON_SUSPEND flag.
+        */
+       if (enable)
+               ret = enable_irq_wake(priv->parent_wake_irq);
+       else
+               ret = disable_irq_wake(priv->parent_wake_irq);
+       if (ret)
+               dev_err(&priv->pdev->dev, "failed to %s wake-up interrupt\n",
+                               enable ? "enable" : "disable");
+       return ret;
+}
+
+static int brcmstb_gpio_irq_set_wake(struct irq_data *d, unsigned int enable)
+{
+       struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+       struct brcmstb_gpio_priv *priv = brcmstb_gpio_gc_to_priv(gc);
+
+       return brcmstb_gpio_priv_set_wake(priv, enable);
+}
+
+static irqreturn_t brcmstb_gpio_wake_irq_handler(int irq, void *data)
+{
+       struct brcmstb_gpio_priv *priv = data;
+
+       if (!priv || irq != priv->parent_wake_irq)
+               return IRQ_NONE;
+       pm_wakeup_event(&priv->pdev->dev, 0);
+       return IRQ_HANDLED;
+}
+
+static void brcmstb_gpio_irq_bank_handler(struct brcmstb_gpio_bank *bank)
+{
+       struct brcmstb_gpio_priv *priv = bank->parent_priv;
+       struct irq_domain *irq_domain = bank->bgc.gc.irqdomain;
+       void __iomem *reg_base = priv->reg_base;
+       unsigned long status;
+       unsigned long flags;
+
+       spin_lock_irqsave(&bank->bgc.lock, flags);
+       while ((status = bank->bgc.read_reg(reg_base + GIO_STAT(bank->id)) &
+                        bank->bgc.read_reg(reg_base + GIO_MASK(bank->id)))) {
+               int bit;
+
+               for_each_set_bit(bit, &status, 32) {
+                       u32 stat = bank->bgc.read_reg(reg_base +
+                                                     GIO_STAT(bank->id));
+                       if (bit >= bank->width)
+                               dev_warn(&priv->pdev->dev,
+                                        "IRQ for invalid GPIO (bank=%d, offset=%d)\n",
+                                        bank->id, bit);
+                       bank->bgc.write_reg(reg_base + GIO_STAT(bank->id),
+                                           stat | BIT(bit));
+                       generic_handle_irq(irq_find_mapping(irq_domain, bit));
+               }
+       }
+       spin_unlock_irqrestore(&bank->bgc.lock, flags);
+}
+
+/* Each UPG GIO block has one IRQ for all banks */
+static void brcmstb_gpio_irq_handler(struct irq_desc *desc)
+{
+       struct gpio_chip *gc = irq_desc_get_handler_data(desc);
+       struct brcmstb_gpio_priv *priv = brcmstb_gpio_gc_to_priv(gc);
+       struct irq_chip *chip = irq_desc_get_chip(desc);
+       struct list_head *pos;
+
+       /* Interrupts weren't properly cleared during probe */
+       BUG_ON(!priv || !chip);
+
+       chained_irq_enter(chip, desc);
+       list_for_each(pos, &priv->bank_list) {
+               struct brcmstb_gpio_bank *bank =
+                       list_entry(pos, struct brcmstb_gpio_bank, node);
+               brcmstb_gpio_irq_bank_handler(bank);
+       }
+       chained_irq_exit(chip, desc);
+}
+
+static int brcmstb_gpio_reboot(struct notifier_block *nb,
+               unsigned long action, void *data)
+{
+       struct brcmstb_gpio_priv *priv =
+               container_of(nb, struct brcmstb_gpio_priv, reboot_notifier);
+
+       /* Enable GPIO for S5 cold boot */
+       if (action == SYS_POWER_OFF)
+               brcmstb_gpio_priv_set_wake(priv, 1);
+
+       return NOTIFY_DONE;
+}
+
 /* Make sure that the number of banks matches up between properties */
 static int brcmstb_gpio_sanity_check_banks(struct device *dev,
                struct device_node *np, struct resource *res)
@@ -100,7 +305,13 @@ static int brcmstb_gpio_remove(struct platform_device *pdev)
                bank = list_entry(pos, struct brcmstb_gpio_bank, node);
                ret = bgpio_remove(&bank->bgc);
                if (ret)
-                       dev_err(&pdev->dev, "gpiochip_remove fail in cleanup");
+                       dev_err(&pdev->dev, "gpiochip_remove fail in cleanup\n");
+       }
+       if (priv->reboot_notifier.notifier_call) {
+               ret = unregister_reboot_notifier(&priv->reboot_notifier);
+               if (ret)
+                       dev_err(&pdev->dev,
+                               "failed to unregister reboot notifier\n");
        }
        return ret;
 }
@@ -121,7 +332,7 @@ static int brcmstb_gpio_of_xlate(struct gpio_chip *gc,
                return -EINVAL;
 
        offset = gpiospec->args[0] - (gc->base - priv->gpio_base);
-       if (offset >= gc->ngpio)
+       if (offset >= gc->ngpio || offset < 0)
                return -EINVAL;
 
        if (unlikely(offset >= bank->width)) {
@@ -136,6 +347,65 @@ static int brcmstb_gpio_of_xlate(struct gpio_chip *gc,
        return offset;
 }
 
+/* Before calling, must have bank->parent_irq set and gpiochip registered */
+static int brcmstb_gpio_irq_setup(struct platform_device *pdev,
+               struct brcmstb_gpio_bank *bank)
+{
+       struct brcmstb_gpio_priv *priv = bank->parent_priv;
+       struct device *dev = &pdev->dev;
+       struct device_node *np = dev->of_node;
+
+       bank->irq_chip.name = dev_name(dev);
+       bank->irq_chip.irq_mask = brcmstb_gpio_irq_mask;
+       bank->irq_chip.irq_unmask = brcmstb_gpio_irq_unmask;
+       bank->irq_chip.irq_set_type = brcmstb_gpio_irq_set_type;
+
+       /* Ensures that all non-wakeup IRQs are disabled at suspend */
+       bank->irq_chip.flags = IRQCHIP_MASK_ON_SUSPEND;
+
+       if (IS_ENABLED(CONFIG_PM_SLEEP) && !priv->can_wake &&
+                       of_property_read_bool(np, "wakeup-source")) {
+               priv->parent_wake_irq = platform_get_irq(pdev, 1);
+               if (priv->parent_wake_irq < 0) {
+                       dev_warn(dev,
+                               "Couldn't get wake IRQ - GPIOs will not be able to wake from sleep");
+               } else {
+                       int err;
+
+                       /*
+                        * Set wakeup capability before requesting wakeup
+                        * interrupt, so we can process boot-time "wakeups"
+                        * (e.g., from S5 cold boot)
+                        */
+                       device_set_wakeup_capable(dev, true);
+                       device_wakeup_enable(dev);
+                       err = devm_request_irq(dev, priv->parent_wake_irq,
+                                       brcmstb_gpio_wake_irq_handler, 0,
+                                       "brcmstb-gpio-wake", priv);
+
+                       if (err < 0) {
+                               dev_err(dev, "Couldn't request wake IRQ");
+                               return err;
+                       }
+
+                       priv->reboot_notifier.notifier_call =
+                               brcmstb_gpio_reboot;
+                       register_reboot_notifier(&priv->reboot_notifier);
+                       priv->can_wake = true;
+               }
+       }
+
+       if (priv->can_wake)
+               bank->irq_chip.irq_set_wake = brcmstb_gpio_irq_set_wake;
+
+       gpiochip_irqchip_add(&bank->bgc.gc, &bank->irq_chip, 0,
+                       handle_simple_irq, IRQ_TYPE_NONE);
+       gpiochip_set_chained_irqchip(&bank->bgc.gc, &bank->irq_chip,
+                       priv->parent_irq, brcmstb_gpio_irq_handler);
+
+       return 0;
+}
+
 static int brcmstb_gpio_probe(struct platform_device *pdev)
 {
        struct device *dev = &pdev->dev;
@@ -146,6 +416,7 @@ static int brcmstb_gpio_probe(struct platform_device *pdev)
        struct property *prop;
        const __be32 *p;
        u32 bank_width;
+       int num_banks = 0;
        int err;
        static int gpio_base;
 
@@ -164,6 +435,16 @@ static int brcmstb_gpio_probe(struct platform_device *pdev)
        priv->reg_base = reg_base;
        priv->pdev = pdev;
 
+       if (of_property_read_bool(np, "interrupt-controller")) {
+               priv->parent_irq = platform_get_irq(pdev, 0);
+               if (priv->parent_irq <= 0) {
+                       dev_err(dev, "Couldn't get IRQ");
+                       return -ENOENT;
+               }
+       } else {
+               priv->parent_irq = -ENOENT;
+       }
+
        if (brcmstb_gpio_sanity_check_banks(dev, np, res))
                return -EINVAL;
 
@@ -180,7 +461,7 @@ static int brcmstb_gpio_probe(struct platform_device *pdev)
                }
 
                bank->parent_priv = priv;
-               bank->id = priv->num_banks;
+               bank->id = num_banks;
                if (bank_width <= 0 || bank_width > MAX_GPIO_PER_BANK) {
                        dev_err(dev, "Invalid bank width %d\n", bank_width);
                        goto fail;
@@ -212,6 +493,12 @@ static int brcmstb_gpio_probe(struct platform_device *pdev)
                /* not all ngpio lines are valid, will use bank width later */
                gc->ngpio = MAX_GPIO_PER_BANK;
 
+               /*
+                * Mask all interrupts by default, since wakeup interrupts may
+                * be retained from S5 cold boot
+                */
+               bank->bgc.write_reg(reg_base + GIO_MASK(bank->id), 0);
+
                err = gpiochip_add(gc);
                if (err) {
                        dev_err(dev, "Could not add gpiochip for bank %d\n",
@@ -219,17 +506,24 @@ static int brcmstb_gpio_probe(struct platform_device *pdev)
                        goto fail;
                }
                gpio_base += gc->ngpio;
+
+               if (priv->parent_irq > 0) {
+                       err = brcmstb_gpio_irq_setup(pdev, bank);
+                       if (err)
+                               goto fail;
+               }
+
                dev_dbg(dev, "bank=%d, base=%d, ngpio=%d, width=%d\n", bank->id,
                        gc->base, gc->ngpio, bank->width);
 
                /* Everything looks good, so add bank to list */
                list_add(&bank->node, &priv->bank_list);
 
-               priv->num_banks++;
+               num_banks++;
        }
 
        dev_info(dev, "Registered %d banks (GPIO(s): %d-%d)\n",
-                       priv->num_banks, priv->gpio_base, gpio_base - 1);
+                       num_banks, priv->gpio_base, gpio_base - 1);
 
        return 0;