diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2017-11-14 17:23:44 -0800 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2017-11-14 17:23:44 -0800 |
commit | 6aa2f9441f1ef21f10c41f45e6453b135e9cd736 (patch) | |
tree | 334e67c4693eddff47a098b9afad63ca2ccfcd55 /drivers/gpio/gpio-brcmstb.c | |
parent | e37e0ee0190034a059c9faea8adfb4982fb24ddd (diff) | |
parent | 24f0966c3e3f52a96e888504d60810d9df5b2d42 (diff) |
Merge tag 'gpio-v4.15-1' of ssh://gitolite.kernel.org/pub/scm/linux/kernel/git/linusw/linux-gpio
Pull GPIO updates from Linus Walleij:
"This is the bulk of GPIO changes for the v4.15 kernel cycle:
Core:
- Fix the semantics of raw GPIO to actually be raw. No inversion
semantics as before, but also no open draining, and allow the raw
operations to affect lines used for interrupts as the caller
supposedly knows what they are doing if they are getting the big
hammer.
- Rewrote the __inner_function() notation calls to names that make
more sense. I just find this kind of code disturbing.
- Drop the .irq_base() field from the gpiochip since now all IRQs are
mapped dynamically. This is nice.
- Support for .get_multiple() in the core driver API. This allows us
to read several GPIO lines with a single register read. This has
high value for some usecases: it can be used to create
oscilloscopes and signal analyzers and other things that rely on
reading several lines at exactly the same instant. Also a generally
nice optimization. This uses the new assign_bit() macro from the
bitops lib that was ACKed by Andrew Morton and is implemented for
two drivers, one of them being the generic MMIO driver so everyone
using that will be able to benefit from this.
- Do not allow requests of Open Drain and Open Source setting of a
GPIO line simultaneously. If the hardware actually supports
enabling both at the same time the electrical result would be
disastrous.
- A new interrupt chip core helper. This will be helpful to deal with
"banked" GPIOs, which means GPIO controllers with several logical
blocks of GPIO inside them. This is several gpiochips per device in
the device model, in contrast to the case when there is a 1-to-1
relationship between a device and a gpiochip.
New drivers:
- Maxim MAX3191x industrial serializer, a very interesting piece of
professional I/O hardware.
- Uniphier GPIO driver. This is the GPIO block from the recent
Socionext (ex Fujitsu and Panasonic) platform.
- Tegra 186 driver. This is based on the new banked GPIO
infrastructure.
Other improvements:
- Some documentation improvements.
- Wakeup support for the DesignWare DWAPB GPIO controller.
- Reset line support on the DesignWare DWAPB GPIO controller.
- Several non-critical bug fixes and improvements for the Broadcom
BRCMSTB driver.
- Misc non-critical bug fixes like exotic errorpaths, removal of dead
code etc.
- Explicit comments on fall-through switch() statements"
* tag 'gpio-v4.15-1' of ssh://gitolite.kernel.org/pub/scm/linux/kernel/git/linusw/linux-gpio: (65 commits)
gpio: tegra186: Remove tegra186_gpio_lock_class
gpio: rcar: Add r8a77995 (R-Car D3) support
pinctrl: bcm2835: Fix some merge fallout
gpio: Fix undefined lock_dep_class
gpio: Automatically add lockdep keys
gpio: Introduce struct gpio_irq_chip.first
gpio: Disambiguate struct gpio_irq_chip.nested
gpio: Add Tegra186 support
gpio: Export gpiochip_irq_{map,unmap}()
gpio: Implement tighter IRQ chip integration
gpio: Move lock_key into struct gpio_irq_chip
gpio: Move irq_valid_mask into struct gpio_irq_chip
gpio: Move irq_nested into struct gpio_irq_chip
gpio: Move irq_chained_parent to struct gpio_irq_chip
gpio: Move irq_default_type to struct gpio_irq_chip
gpio: Move irq_handler to struct gpio_irq_chip
gpio: Move irqdomain into struct gpio_irq_chip
gpio: Move irqchip into struct gpio_irq_chip
gpio: Introduce struct gpio_irq_chip
pinctrl: armada-37xx: remove unused variable
...
Diffstat (limited to 'drivers/gpio/gpio-brcmstb.c')
-rw-r--r-- | drivers/gpio/gpio-brcmstb.c | 422 |
1 files changed, 323 insertions, 99 deletions
diff --git a/drivers/gpio/gpio-brcmstb.c b/drivers/gpio/gpio-brcmstb.c index dd0308cc8bb0..545d43a587b7 100644 --- a/drivers/gpio/gpio-brcmstb.c +++ b/drivers/gpio/gpio-brcmstb.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Broadcom Corporation + * Copyright (C) 2015-2017 Broadcom * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -19,17 +19,30 @@ #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) -#define GIO_DATA(bank) (((bank) * GIO_BANK_SIZE) + 0x04) -#define GIO_IODIR(bank) (((bank) * GIO_BANK_SIZE) + 0x08) -#define GIO_EC(bank) (((bank) * GIO_BANK_SIZE) + 0x0c) -#define GIO_EI(bank) (((bank) * GIO_BANK_SIZE) + 0x10) -#define GIO_MASK(bank) (((bank) * GIO_BANK_SIZE) + 0x14) -#define GIO_LEVEL(bank) (((bank) * GIO_BANK_SIZE) + 0x18) -#define GIO_STAT(bank) (((bank) * GIO_BANK_SIZE) + 0x1c) +#include <linux/bitops.h> + +enum gio_reg_index { + GIO_REG_ODEN = 0, + GIO_REG_DATA, + GIO_REG_IODIR, + GIO_REG_EC, + GIO_REG_EI, + GIO_REG_MASK, + GIO_REG_LEVEL, + GIO_REG_STAT, + NUMBER_OF_GIO_REGISTERS +}; + +#define GIO_BANK_SIZE (NUMBER_OF_GIO_REGISTERS * sizeof(u32)) +#define GIO_BANK_OFF(bank, off) (((bank) * GIO_BANK_SIZE) + (off * sizeof(u32))) +#define GIO_ODEN(bank) GIO_BANK_OFF(bank, GIO_REG_ODEN) +#define GIO_DATA(bank) GIO_BANK_OFF(bank, GIO_REG_DATA) +#define GIO_IODIR(bank) GIO_BANK_OFF(bank, GIO_REG_IODIR) +#define GIO_EC(bank) GIO_BANK_OFF(bank, GIO_REG_EC) +#define GIO_EI(bank) GIO_BANK_OFF(bank, GIO_REG_EI) +#define GIO_MASK(bank) GIO_BANK_OFF(bank, GIO_REG_MASK) +#define GIO_LEVEL(bank) GIO_BANK_OFF(bank, GIO_REG_LEVEL) +#define GIO_STAT(bank) GIO_BANK_OFF(bank, GIO_REG_STAT) struct brcmstb_gpio_bank { struct list_head node; @@ -37,21 +50,23 @@ struct brcmstb_gpio_bank { struct gpio_chip gc; struct brcmstb_gpio_priv *parent_priv; u32 width; - struct irq_chip irq_chip; + u32 wake_active; + u32 saved_regs[GIO_REG_STAT]; /* Don't save and restore GIO_REG_STAT */ }; struct brcmstb_gpio_priv { struct list_head bank_list; void __iomem *reg_base; struct platform_device *pdev; + struct irq_domain *irq_domain; + struct irq_chip irq_chip; int parent_irq; int gpio_base; - bool can_wake; + int num_gpios; int parent_wake_irq; - struct notifier_block reboot_notifier; }; -#define MAX_GPIO_PER_BANK 32 +#define MAX_GPIO_PER_BANK 32 #define GPIO_BANK(gpio) ((gpio) >> 5) /* assumes MAX_GPIO_PER_BANK is a multiple of 2 */ #define GPIO_BIT(gpio) ((gpio) & (MAX_GPIO_PER_BANK - 1)) @@ -63,12 +78,40 @@ brcmstb_gpio_gc_to_priv(struct gpio_chip *gc) return bank->parent_priv; } +static unsigned long +__brcmstb_gpio_get_active_irqs(struct brcmstb_gpio_bank *bank) +{ + void __iomem *reg_base = bank->parent_priv->reg_base; + + return bank->gc.read_reg(reg_base + GIO_STAT(bank->id)) & + bank->gc.read_reg(reg_base + GIO_MASK(bank->id)); +} + +static unsigned long +brcmstb_gpio_get_active_irqs(struct brcmstb_gpio_bank *bank) +{ + unsigned long status; + unsigned long flags; + + spin_lock_irqsave(&bank->gc.bgpio_lock, flags); + status = __brcmstb_gpio_get_active_irqs(bank); + spin_unlock_irqrestore(&bank->gc.bgpio_lock, flags); + + return status; +} + +static int brcmstb_gpio_hwirq_to_offset(irq_hw_number_t hwirq, + struct brcmstb_gpio_bank *bank) +{ + return hwirq - (bank->gc.base - bank->parent_priv->gpio_base); +} + static void brcmstb_gpio_set_imask(struct brcmstb_gpio_bank *bank, - unsigned int offset, bool enable) + unsigned int hwirq, bool enable) { struct gpio_chip *gc = &bank->gc; struct brcmstb_gpio_priv *priv = bank->parent_priv; - u32 mask = gc->pin2mask(gc, offset); + u32 mask = BIT(brcmstb_gpio_hwirq_to_offset(hwirq, bank)); u32 imask; unsigned long flags; @@ -82,6 +125,17 @@ static void brcmstb_gpio_set_imask(struct brcmstb_gpio_bank *bank, spin_unlock_irqrestore(&gc->bgpio_lock, flags); } +static int brcmstb_gpio_to_irq(struct gpio_chip *gc, unsigned offset) +{ + struct brcmstb_gpio_priv *priv = brcmstb_gpio_gc_to_priv(gc); + /* gc_offset is relative to this gpio_chip; want real offset */ + int hwirq = offset + (gc->base - priv->gpio_base); + + if (hwirq >= priv->num_gpios) + return -ENXIO; + return irq_create_mapping(priv->irq_domain, hwirq); +} + /* -------------------- IRQ chip functions -------------------- */ static void brcmstb_gpio_irq_mask(struct irq_data *d) @@ -100,12 +154,22 @@ static void brcmstb_gpio_irq_unmask(struct irq_data *d) brcmstb_gpio_set_imask(bank, d->hwirq, true); } +static void brcmstb_gpio_irq_ack(struct irq_data *d) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct brcmstb_gpio_bank *bank = gpiochip_get_data(gc); + struct brcmstb_gpio_priv *priv = bank->parent_priv; + u32 mask = BIT(brcmstb_gpio_hwirq_to_offset(d->hwirq, bank)); + + gc->write_reg(priv->reg_base + GIO_STAT(bank->id), mask); +} + 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 = gpiochip_get_data(gc); struct brcmstb_gpio_priv *priv = bank->parent_priv; - u32 mask = BIT(d->hwirq); + u32 mask = BIT(brcmstb_gpio_hwirq_to_offset(d->hwirq, bank)); u32 edge_insensitive, iedge_insensitive; u32 edge_config, iedge_config; u32 level, ilevel; @@ -113,13 +177,13 @@ static int brcmstb_gpio_irq_set_type(struct irq_data *d, unsigned int type) switch (type) { case IRQ_TYPE_LEVEL_LOW: - level = 0; + level = mask; edge_config = 0; edge_insensitive = 0; break; case IRQ_TYPE_LEVEL_HIGH: level = mask; - edge_config = 0; + edge_config = mask; edge_insensitive = 0; break; case IRQ_TYPE_EDGE_FALLING: @@ -166,11 +230,6 @@ static int brcmstb_gpio_priv_set_wake(struct brcmstb_gpio_priv *priv, { 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 @@ -184,7 +243,18 @@ static int brcmstb_gpio_priv_set_wake(struct brcmstb_gpio_priv *priv, 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); + struct brcmstb_gpio_bank *bank = gpiochip_get_data(gc); + struct brcmstb_gpio_priv *priv = bank->parent_priv; + u32 mask = BIT(brcmstb_gpio_hwirq_to_offset(d->hwirq, bank)); + + /* + * Do not do anything specific for now, suspend/resume callbacks will + * configure the interrupt mask appropriately + */ + if (enable) + bank->wake_active |= mask; + else + bank->wake_active &= ~mask; return brcmstb_gpio_priv_set_wake(priv, enable); } @@ -195,43 +265,36 @@ static irqreturn_t brcmstb_gpio_wake_irq_handler(int irq, void *data) if (!priv || irq != priv->parent_wake_irq) return IRQ_NONE; - pm_wakeup_event(&priv->pdev->dev, 0); + + /* Nothing to do */ 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->gc.irqdomain; - void __iomem *reg_base = priv->reg_base; + struct irq_domain *domain = priv->irq_domain; + int hwbase = bank->gc.base - priv->gpio_base; unsigned long status; - unsigned long flags; - spin_lock_irqsave(&bank->gc.bgpio_lock, flags); - while ((status = bank->gc.read_reg(reg_base + GIO_STAT(bank->id)) & - bank->gc.read_reg(reg_base + GIO_MASK(bank->id)))) { - int bit; - - for_each_set_bit(bit, &status, 32) { - u32 stat = bank->gc.read_reg(reg_base + - GIO_STAT(bank->id)); - if (bit >= bank->width) + while ((status = brcmstb_gpio_get_active_irqs(bank))) { + unsigned int irq, offset; + + for_each_set_bit(offset, &status, 32) { + if (offset >= bank->width) dev_warn(&priv->pdev->dev, "IRQ for invalid GPIO (bank=%d, offset=%d)\n", - bank->id, bit); - bank->gc.write_reg(reg_base + GIO_STAT(bank->id), - stat | BIT(bit)); - generic_handle_irq(irq_find_mapping(irq_domain, bit)); + bank->id, offset); + irq = irq_linear_revmap(domain, hwbase + offset); + generic_handle_irq(irq); } } - spin_unlock_irqrestore(&bank->gc.bgpio_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 brcmstb_gpio_priv *priv = irq_desc_get_handler_data(desc); struct irq_chip *chip = irq_desc_get_chip(desc); struct brcmstb_gpio_bank *bank; @@ -244,19 +307,63 @@ static void brcmstb_gpio_irq_handler(struct irq_desc *desc) chained_irq_exit(chip, desc); } -static int brcmstb_gpio_reboot(struct notifier_block *nb, - unsigned long action, void *data) +static struct brcmstb_gpio_bank *brcmstb_gpio_hwirq_to_bank( + struct brcmstb_gpio_priv *priv, irq_hw_number_t hwirq) { - struct brcmstb_gpio_priv *priv = - container_of(nb, struct brcmstb_gpio_priv, reboot_notifier); + struct brcmstb_gpio_bank *bank; + int i = 0; - /* Enable GPIO for S5 cold boot */ - if (action == SYS_POWER_OFF) - brcmstb_gpio_priv_set_wake(priv, 1); + /* banks are in descending order */ + list_for_each_entry_reverse(bank, &priv->bank_list, node) { + i += bank->gc.ngpio; + if (hwirq < i) + return bank; + } + return NULL; +} + +/* + * This lock class tells lockdep that GPIO irqs are in a different + * category than their parents, so it won't report false recursion. + */ +static struct lock_class_key brcmstb_gpio_irq_lock_class; - return NOTIFY_DONE; + +static int brcmstb_gpio_irq_map(struct irq_domain *d, unsigned int irq, + irq_hw_number_t hwirq) +{ + struct brcmstb_gpio_priv *priv = d->host_data; + struct brcmstb_gpio_bank *bank = + brcmstb_gpio_hwirq_to_bank(priv, hwirq); + struct platform_device *pdev = priv->pdev; + int ret; + + if (!bank) + return -EINVAL; + + dev_dbg(&pdev->dev, "Mapping irq %d for gpio line %d (bank %d)\n", + irq, (int)hwirq, bank->id); + ret = irq_set_chip_data(irq, &bank->gc); + if (ret < 0) + return ret; + irq_set_lockdep_class(irq, &brcmstb_gpio_irq_lock_class); + irq_set_chip_and_handler(irq, &priv->irq_chip, handle_level_irq); + irq_set_noprobe(irq); + return 0; } +static void brcmstb_gpio_irq_unmap(struct irq_domain *d, unsigned int irq) +{ + irq_set_chip_and_handler(irq, NULL, NULL); + irq_set_chip_data(irq, NULL); +} + +static const struct irq_domain_ops brcmstb_gpio_irq_domain_ops = { + .map = brcmstb_gpio_irq_map, + .unmap = brcmstb_gpio_irq_unmap, + .xlate = irq_domain_xlate_twocell, +}; + /* 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) @@ -278,13 +385,25 @@ static int brcmstb_gpio_remove(struct platform_device *pdev) { struct brcmstb_gpio_priv *priv = platform_get_drvdata(pdev); struct brcmstb_gpio_bank *bank; - int ret = 0; + int offset, ret = 0, virq; if (!priv) { dev_err(&pdev->dev, "called %s without drvdata!\n", __func__); return -EFAULT; } + if (priv->parent_irq > 0) + irq_set_chained_handler_and_data(priv->parent_irq, NULL, NULL); + + /* Remove all IRQ mappings and delete the domain */ + if (priv->irq_domain) { + for (offset = 0; offset < priv->num_gpios; offset++) { + virq = irq_find_mapping(priv->irq_domain, offset); + irq_dispose_mapping(virq); + } + irq_domain_remove(priv->irq_domain); + } + /* * You can lose return values below, but we report all errors, and it's * more important to actually perform all of the steps. @@ -292,12 +411,6 @@ static int brcmstb_gpio_remove(struct platform_device *pdev) list_for_each_entry(bank, &priv->bank_list, node) gpiochip_remove(&bank->gc); - 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; } @@ -332,66 +445,163 @@ static int brcmstb_gpio_of_xlate(struct gpio_chip *gc, return offset; } -/* Before calling, must have bank->parent_irq set and gpiochip registered */ +/* priv->parent_irq and priv->num_gpios must be set before calling */ static int brcmstb_gpio_irq_setup(struct platform_device *pdev, - struct brcmstb_gpio_bank *bank) + struct brcmstb_gpio_priv *priv) { - struct brcmstb_gpio_priv *priv = bank->parent_priv; struct device *dev = &pdev->dev; struct device_node *np = dev->of_node; int err; - 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; + priv->irq_domain = + irq_domain_add_linear(np, priv->num_gpios, + &brcmstb_gpio_irq_domain_ops, + priv); + if (!priv->irq_domain) { + dev_err(dev, "Couldn't allocate IRQ domain\n"); + return -ENXIO; + } - if (IS_ENABLED(CONFIG_PM_SLEEP) && !priv->can_wake && - of_property_read_bool(np, "wakeup-source")) { + if (of_property_read_bool(np, "wakeup-source")) { priv->parent_wake_irq = platform_get_irq(pdev, 1); if (priv->parent_wake_irq < 0) { + priv->parent_wake_irq = 0; dev_warn(dev, "Couldn't get wake IRQ - GPIOs will not be able to wake from sleep"); } else { /* - * Set wakeup capability before requesting wakeup - * interrupt, so we can process boot-time "wakeups" - * (e.g., from S5 cold boot) + * Set wakeup capability 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); + brcmstb_gpio_wake_irq_handler, + IRQF_SHARED, + "brcmstb-gpio-wake", priv); if (err < 0) { dev_err(dev, "Couldn't request wake IRQ"); - return err; + goto out_free_domain; } - - 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; + priv->irq_chip.name = dev_name(dev); + priv->irq_chip.irq_disable = brcmstb_gpio_irq_mask; + priv->irq_chip.irq_mask = brcmstb_gpio_irq_mask; + priv->irq_chip.irq_unmask = brcmstb_gpio_irq_unmask; + priv->irq_chip.irq_ack = brcmstb_gpio_irq_ack; + priv->irq_chip.irq_set_type = brcmstb_gpio_irq_set_type; + + if (priv->parent_wake_irq) + priv->irq_chip.irq_set_wake = brcmstb_gpio_irq_set_wake; + + irq_set_chained_handler_and_data(priv->parent_irq, + brcmstb_gpio_irq_handler, priv); + irq_set_status_flags(priv->parent_irq, IRQ_DISABLE_UNLAZY); + + return 0; + +out_free_domain: + irq_domain_remove(priv->irq_domain); + + return err; +} + +static void brcmstb_gpio_bank_save(struct brcmstb_gpio_priv *priv, + struct brcmstb_gpio_bank *bank) +{ + struct gpio_chip *gc = &bank->gc; + unsigned int i; + + for (i = 0; i < GIO_REG_STAT; i++) + bank->saved_regs[i] = gc->read_reg(priv->reg_base + + GIO_BANK_OFF(bank->id, i)); +} + +static void brcmstb_gpio_quiesce(struct device *dev, bool save) +{ + struct brcmstb_gpio_priv *priv = dev_get_drvdata(dev); + struct brcmstb_gpio_bank *bank; + struct gpio_chip *gc; + u32 imask; + + /* disable non-wake interrupt */ + if (priv->parent_irq >= 0) + disable_irq(priv->parent_irq); + + list_for_each_entry(bank, &priv->bank_list, node) { + gc = &bank->gc; + + if (save) + brcmstb_gpio_bank_save(priv, bank); + + /* Unmask GPIOs which have been flagged as wake-up sources */ + if (priv->parent_wake_irq) + imask = bank->wake_active; + else + imask = 0; + gc->write_reg(priv->reg_base + GIO_MASK(bank->id), + imask); + } +} + +static void brcmstb_gpio_shutdown(struct platform_device *pdev) +{ + /* Enable GPIO for S5 cold boot */ + brcmstb_gpio_quiesce(&pdev->dev, false); +} + +#ifdef CONFIG_PM_SLEEP +static void brcmstb_gpio_bank_restore(struct brcmstb_gpio_priv *priv, + struct brcmstb_gpio_bank *bank) +{ + struct gpio_chip *gc = &bank->gc; + unsigned int i; + + for (i = 0; i < GIO_REG_STAT; i++) + gc->write_reg(priv->reg_base + GIO_BANK_OFF(bank->id, i), + bank->saved_regs[i]); +} + +static int brcmstb_gpio_suspend(struct device *dev) +{ + brcmstb_gpio_quiesce(dev, true); + return 0; +} + +static int brcmstb_gpio_resume(struct device *dev) +{ + struct brcmstb_gpio_priv *priv = dev_get_drvdata(dev); + struct brcmstb_gpio_bank *bank; + bool need_wakeup_event = false; + + list_for_each_entry(bank, &priv->bank_list, node) { + need_wakeup_event |= !!__brcmstb_gpio_get_active_irqs(bank); + brcmstb_gpio_bank_restore(priv, bank); + } + + if (priv->parent_wake_irq && need_wakeup_event) + pm_wakeup_event(dev, 0); - err = gpiochip_irqchip_add(&bank->gc, &bank->irq_chip, 0, - handle_simple_irq, IRQ_TYPE_NONE); - if (err) - return err; - gpiochip_set_chained_irqchip(&bank->gc, &bank->irq_chip, - priv->parent_irq, brcmstb_gpio_irq_handler); + /* enable non-wake interrupt */ + if (priv->parent_irq >= 0) + enable_irq(priv->parent_irq); return 0; } +#else +#define brcmstb_gpio_suspend NULL +#define brcmstb_gpio_resume NULL +#endif /* CONFIG_PM_SLEEP */ + +static const struct dev_pm_ops brcmstb_gpio_pm_ops = { + .suspend_noirq = brcmstb_gpio_suspend, + .resume_noirq = brcmstb_gpio_resume, +}; + static int brcmstb_gpio_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -406,6 +616,7 @@ static int brcmstb_gpio_probe(struct platform_device *pdev) int err; static int gpio_base; unsigned long flags = 0; + bool need_wakeup_event = false; priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) @@ -485,16 +696,23 @@ static int brcmstb_gpio_probe(struct platform_device *pdev) gc->of_node = np; gc->owner = THIS_MODULE; gc->label = devm_kasprintf(dev, GFP_KERNEL, "%pOF", dev->of_node); + if (!gc->label) { + err = -ENOMEM; + goto fail; + } gc->base = gpio_base; gc->of_gpio_n_cells = 2; gc->of_xlate = brcmstb_gpio_of_xlate; /* not all ngpio lines are valid, will use bank width later */ gc->ngpio = MAX_GPIO_PER_BANK; + if (priv->parent_irq > 0) + gc->to_irq = brcmstb_gpio_to_irq; /* * Mask all interrupts by default, since wakeup interrupts may * be retained from S5 cold boot */ + need_wakeup_event |= !!__brcmstb_gpio_get_active_irqs(bank); gc->write_reg(reg_base + GIO_MASK(bank->id), 0); err = gpiochip_add_data(gc, bank); @@ -505,12 +723,6 @@ static int brcmstb_gpio_probe(struct platform_device *pdev) } 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); @@ -520,9 +732,19 @@ static int brcmstb_gpio_probe(struct platform_device *pdev) num_banks++; } + priv->num_gpios = gpio_base - priv->gpio_base; + if (priv->parent_irq > 0) { + err = brcmstb_gpio_irq_setup(pdev, priv); + if (err) + goto fail; + } + dev_info(dev, "Registered %d banks (GPIO(s): %d-%d)\n", num_banks, priv->gpio_base, gpio_base - 1); + if (priv->parent_wake_irq && need_wakeup_event) + pm_wakeup_event(dev, 0); + return 0; fail: @@ -541,9 +763,11 @@ static struct platform_driver brcmstb_gpio_driver = { .driver = { .name = "brcmstb-gpio", .of_match_table = brcmstb_gpio_of_match, + .pm = &brcmstb_gpio_pm_ops, }, .probe = brcmstb_gpio_probe, .remove = brcmstb_gpio_remove, + .shutdown = brcmstb_gpio_shutdown, }; module_platform_driver(brcmstb_gpio_driver); |