|
@@ -38,20 +38,22 @@ struct brcmstb_gpio_bank {
|
|
|
struct gpio_chip gc;
|
|
|
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;
|
|
|
struct platform_device *pdev;
|
|
|
+ struct irq_domain *irq_domain;
|
|
|
+ struct irq_chip irq_chip;
|
|
|
int parent_irq;
|
|
|
int gpio_base;
|
|
|
+ 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))
|
|
@@ -78,24 +80,42 @@ brcmstb_gpio_get_active_irqs(struct brcmstb_gpio_bank *bank)
|
|
|
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 = BIT(brcmstb_gpio_hwirq_to_offset(hwirq, bank));
|
|
|
u32 imask;
|
|
|
unsigned long flags;
|
|
|
|
|
|
spin_lock_irqsave(&gc->bgpio_lock, flags);
|
|
|
imask = gc->read_reg(priv->reg_base + GIO_MASK(bank->id));
|
|
|
if (enable)
|
|
|
- imask |= BIT(offset);
|
|
|
+ imask |= mask;
|
|
|
else
|
|
|
- imask &= ~BIT(offset);
|
|
|
+ imask &= ~mask;
|
|
|
gc->write_reg(priv->reg_base + GIO_MASK(bank->id), imask);
|
|
|
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)
|
|
@@ -119,7 +139,7 @@ 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(d->hwirq);
|
|
|
+ u32 mask = BIT(brcmstb_gpio_hwirq_to_offset(d->hwirq, bank));
|
|
|
|
|
|
gc->write_reg(priv->reg_base + GIO_STAT(bank->id), mask);
|
|
|
}
|
|
@@ -129,7 +149,7 @@ 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;
|
|
@@ -226,18 +246,20 @@ static irqreturn_t brcmstb_gpio_wake_irq_handler(int irq, void *data)
|
|
|
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;
|
|
|
+ struct irq_domain *domain = priv->irq_domain;
|
|
|
+ int hwbase = bank->gc.base - priv->gpio_base;
|
|
|
unsigned long status;
|
|
|
|
|
|
while ((status = brcmstb_gpio_get_active_irqs(bank))) {
|
|
|
- int bit;
|
|
|
+ unsigned int irq, offset;
|
|
|
|
|
|
- for_each_set_bit(bit, &status, 32) {
|
|
|
- if (bit >= bank->width)
|
|
|
+ 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);
|
|
|
- generic_handle_irq(irq_find_mapping(irq_domain, bit));
|
|
|
+ bank->id, offset);
|
|
|
+ irq = irq_linear_revmap(domain, hwbase + offset);
|
|
|
+ generic_handle_irq(irq);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
@@ -245,8 +267,7 @@ static void brcmstb_gpio_irq_bank_handler(struct brcmstb_gpio_bank *bank)
|
|
|
/* 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;
|
|
|
|
|
@@ -272,6 +293,63 @@ static int brcmstb_gpio_reboot(struct notifier_block *nb,
|
|
|
return NOTIFY_DONE;
|
|
|
}
|
|
|
|
|
|
+static struct brcmstb_gpio_bank *brcmstb_gpio_hwirq_to_bank(
|
|
|
+ struct brcmstb_gpio_priv *priv, irq_hw_number_t hwirq)
|
|
|
+{
|
|
|
+ struct brcmstb_gpio_bank *bank;
|
|
|
+ int i = 0;
|
|
|
+
|
|
|
+ /* 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;
|
|
|
+
|
|
|
+
|
|
|
+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)
|
|
@@ -293,13 +371,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.
|
|
@@ -347,26 +437,24 @@ 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_ack = brcmstb_gpio_irq_ack;
|
|
|
- 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->parent_wake_irq &&
|
|
|
- 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;
|
|
@@ -387,7 +475,7 @@ static int brcmstb_gpio_irq_setup(struct platform_device *pdev,
|
|
|
|
|
|
if (err < 0) {
|
|
|
dev_err(dev, "Couldn't request wake IRQ");
|
|
|
- return err;
|
|
|
+ goto out_free_domain;
|
|
|
}
|
|
|
|
|
|
priv->reboot_notifier.notifier_call =
|
|
@@ -396,17 +484,28 @@ static int brcmstb_gpio_irq_setup(struct platform_device *pdev,
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ 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;
|
|
|
+
|
|
|
+ /* Ensures that all non-wakeup IRQs are disabled at suspend */
|
|
|
+ priv->irq_chip.flags = IRQCHIP_MASK_ON_SUSPEND;
|
|
|
+
|
|
|
if (priv->parent_wake_irq)
|
|
|
- bank->irq_chip.irq_set_wake = brcmstb_gpio_irq_set_wake;
|
|
|
+ priv->irq_chip.irq_set_wake = brcmstb_gpio_irq_set_wake;
|
|
|
|
|
|
- err = gpiochip_irqchip_add(&bank->gc, &bank->irq_chip, 0,
|
|
|
- handle_level_irq, IRQ_TYPE_NONE);
|
|
|
- if (err)
|
|
|
- return err;
|
|
|
- gpiochip_set_chained_irqchip(&bank->gc, &bank->irq_chip,
|
|
|
- priv->parent_irq, brcmstb_gpio_irq_handler);
|
|
|
+ irq_set_chained_handler_and_data(priv->parent_irq,
|
|
|
+ brcmstb_gpio_irq_handler, priv);
|
|
|
|
|
|
return 0;
|
|
|
+
|
|
|
+out_free_domain:
|
|
|
+ irq_domain_remove(priv->irq_domain);
|
|
|
+
|
|
|
+ return err;
|
|
|
}
|
|
|
|
|
|
static int brcmstb_gpio_probe(struct platform_device *pdev)
|
|
@@ -511,6 +610,8 @@ static int brcmstb_gpio_probe(struct platform_device *pdev)
|
|
|
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
|
|
@@ -526,12 +627,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);
|
|
|
|
|
@@ -541,6 +636,13 @@ 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);
|
|
|
|