Merge tag 'random_for_linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tytso...
[firefly-linux-kernel-4.4.55.git] / drivers / gpio / gpiolib.c
index 584d2b465f84f02ddb15223321047b1b94533347..761013f8b82f5a3d7c534f201a45f2c5902cd188 100644 (file)
@@ -1254,6 +1254,9 @@ fail:
 }
 EXPORT_SYMBOL_GPL(gpiochip_add);
 
+/* Forward-declaration */
+static void gpiochip_irqchip_remove(struct gpio_chip *gpiochip);
+
 /**
  * gpiochip_remove() - unregister a gpio_chip
  * @chip: the chip to unregister
@@ -1270,6 +1273,7 @@ int gpiochip_remove(struct gpio_chip *chip)
 
        spin_lock_irqsave(&gpio_lock, flags);
 
+       gpiochip_irqchip_remove(chip);
        gpiochip_remove_pin_ranges(chip);
        of_gpiochip_remove(chip);
 
@@ -1339,6 +1343,215 @@ static struct gpio_chip *find_chip_by_name(const char *name)
        return gpiochip_find((void *)name, gpiochip_match_name);
 }
 
+#ifdef CONFIG_GPIOLIB_IRQCHIP
+
+/*
+ * The following is irqchip helper code for gpiochips.
+ */
+
+/**
+ * gpiochip_add_chained_irqchip() - adds a chained irqchip to a gpiochip
+ * @gpiochip: the gpiochip to add the irqchip to
+ * @irqchip: the irqchip to add to the gpiochip
+ * @parent_irq: the irq number corresponding to the parent IRQ for this
+ * chained irqchip
+ * @parent_handler: the parent interrupt handler for the accumulated IRQ
+ * coming out of the gpiochip
+ */
+void gpiochip_set_chained_irqchip(struct gpio_chip *gpiochip,
+                                 struct irq_chip *irqchip,
+                                 int parent_irq,
+                                 irq_flow_handler_t parent_handler)
+{
+       irq_set_chained_handler(parent_irq, parent_handler);
+       /*
+        * The parent irqchip is already using the chip_data for this
+        * irqchip, so our callbacks simply use the handler_data.
+        */
+       irq_set_handler_data(parent_irq, gpiochip);
+}
+EXPORT_SYMBOL_GPL(gpiochip_set_chained_irqchip);
+
+/**
+ * gpiochip_irq_map() - maps an IRQ into a GPIO irqchip
+ * @d: the irqdomain used by this irqchip
+ * @irq: the global irq number used by this GPIO irqchip irq
+ * @hwirq: the local IRQ/GPIO line offset on this gpiochip
+ *
+ * This function will set up the mapping for a certain IRQ line on a
+ * gpiochip by assigning the gpiochip as chip data, and using the irqchip
+ * stored inside the gpiochip.
+ */
+static int gpiochip_irq_map(struct irq_domain *d, unsigned int irq,
+                           irq_hw_number_t hwirq)
+{
+       struct gpio_chip *chip = d->host_data;
+
+       irq_set_chip_and_handler(irq, chip->irqchip, chip->irq_handler);
+       irq_set_chip_data(irq, chip);
+#ifdef CONFIG_ARM
+       set_irq_flags(irq, IRQF_VALID);
+#else
+       irq_set_noprobe(irq);
+#endif
+       irq_set_irq_type(irq, chip->irq_default_type);
+
+       return 0;
+}
+
+static void gpiochip_irq_unmap(struct irq_domain *d, unsigned int irq)
+{
+#ifdef CONFIG_ARM
+       set_irq_flags(irq, 0);
+#endif
+       irq_set_chip_and_handler(irq, NULL, NULL);
+       irq_set_chip_data(irq, NULL);
+}
+
+static const struct irq_domain_ops gpiochip_domain_ops = {
+       .map    = gpiochip_irq_map,
+       .unmap  = gpiochip_irq_unmap,
+       /* Virtually all GPIO irqchips are twocell:ed */
+       .xlate  = irq_domain_xlate_twocell,
+};
+
+static int gpiochip_irq_reqres(struct irq_data *d)
+{
+       struct gpio_chip *chip = irq_data_get_irq_chip_data(d);
+
+       if (gpio_lock_as_irq(chip, d->hwirq)) {
+               chip_err(chip,
+                       "unable to lock HW IRQ %lu for IRQ\n",
+                       d->hwirq);
+               return -EINVAL;
+       }
+       return 0;
+}
+
+static void gpiochip_irq_relres(struct irq_data *d)
+{
+       struct gpio_chip *chip = irq_data_get_irq_chip_data(d);
+
+       gpio_unlock_as_irq(chip, d->hwirq);
+}
+
+static int gpiochip_to_irq(struct gpio_chip *chip, unsigned offset)
+{
+       return irq_find_mapping(chip->irqdomain, offset);
+}
+
+/**
+ * gpiochip_irqchip_remove() - removes an irqchip added to a gpiochip
+ * @gpiochip: the gpiochip to remove the irqchip from
+ *
+ * This is called only from gpiochip_remove()
+ */
+static void gpiochip_irqchip_remove(struct gpio_chip *gpiochip)
+{
+       unsigned int offset;
+
+       /* Remove all IRQ mappings and delete the domain */
+       if (gpiochip->irqdomain) {
+               for (offset = 0; offset < gpiochip->ngpio; offset++)
+                       irq_dispose_mapping(gpiochip->irq_base + offset);
+               irq_domain_remove(gpiochip->irqdomain);
+       }
+
+       if (gpiochip->irqchip) {
+               gpiochip->irqchip->irq_request_resources = NULL;
+               gpiochip->irqchip->irq_release_resources = NULL;
+               gpiochip->irqchip = NULL;
+       }
+}
+
+/**
+ * gpiochip_irqchip_add() - adds an irqchip to a gpiochip
+ * @gpiochip: the gpiochip to add the irqchip to
+ * @irqchip: the irqchip to add to the gpiochip
+ * @first_irq: if not dynamically assigned, the base (first) IRQ to
+ * allocate gpiochip irqs from
+ * @handler: the irq handler to use (often a predefined irq core function)
+ * @type: the default type for IRQs on this irqchip
+ *
+ * This function closely associates a certain irqchip with a certain
+ * gpiochip, providing an irq domain to translate the local IRQs to
+ * global irqs in the gpiolib core, and making sure that the gpiochip
+ * is passed as chip data to all related functions. Driver callbacks
+ * need to use container_of() to get their local state containers back
+ * from the gpiochip passed as chip data. An irqdomain will be stored
+ * in the gpiochip that shall be used by the driver to handle IRQ number
+ * translation. The gpiochip will need to be initialized and registered
+ * before calling this function.
+ *
+ * This function will handle two cell:ed simple IRQs and assumes all
+ * the pins on the gpiochip can generate a unique IRQ. Everything else
+ * need to be open coded.
+ */
+int gpiochip_irqchip_add(struct gpio_chip *gpiochip,
+                        struct irq_chip *irqchip,
+                        unsigned int first_irq,
+                        irq_flow_handler_t handler,
+                        unsigned int type)
+{
+       struct device_node *of_node;
+       unsigned int offset;
+       unsigned irq_base = 0;
+
+       if (!gpiochip || !irqchip)
+               return -EINVAL;
+
+       if (!gpiochip->dev) {
+               pr_err("missing gpiochip .dev parent pointer\n");
+               return -EINVAL;
+       }
+       of_node = gpiochip->dev->of_node;
+#ifdef CONFIG_OF_GPIO
+       /*
+        * If the gpiochip has an assigned OF node this takes precendence
+        * FIXME: get rid of this and use gpiochip->dev->of_node everywhere
+        */
+       if (gpiochip->of_node)
+               of_node = gpiochip->of_node;
+#endif
+       gpiochip->irqchip = irqchip;
+       gpiochip->irq_handler = handler;
+       gpiochip->irq_default_type = type;
+       gpiochip->to_irq = gpiochip_to_irq;
+       gpiochip->irqdomain = irq_domain_add_simple(of_node,
+                                       gpiochip->ngpio, first_irq,
+                                       &gpiochip_domain_ops, gpiochip);
+       if (!gpiochip->irqdomain) {
+               gpiochip->irqchip = NULL;
+               return -EINVAL;
+       }
+       irqchip->irq_request_resources = gpiochip_irq_reqres;
+       irqchip->irq_release_resources = gpiochip_irq_relres;
+
+       /*
+        * Prepare the mapping since the irqchip shall be orthogonal to
+        * any gpiochip calls. If the first_irq was zero, this is
+        * necessary to allocate descriptors for all IRQs.
+        */
+       for (offset = 0; offset < gpiochip->ngpio; offset++) {
+               irq_base = irq_create_mapping(gpiochip->irqdomain, offset);
+               if (offset == 0)
+                       /*
+                        * Store the base into the gpiochip to be used when
+                        * unmapping the irqs.
+                        */
+                       gpiochip->irq_base = irq_base;
+       }
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(gpiochip_irqchip_add);
+
+#else /* CONFIG_GPIOLIB_IRQCHIP */
+
+static void gpiochip_irqchip_remove(struct gpio_chip *gpiochip) {}
+
+#endif /* CONFIG_GPIOLIB_IRQCHIP */
+
 #ifdef CONFIG_PINCTRL
 
 /**