Merge branch 'master' of git://git.kernel.org/pub/scm/linux/kernel/git/linville/wirel...
[sfrench/cifs-2.6.git] / arch / powerpc / sysdev / mpc8xxx_gpio.c
index 6478eb10691aafcc85ef4290fe1780802ef0025d..83f519655fac0cd00286f3f121f9cd646f19fff2 100644 (file)
@@ -16,6 +16,7 @@
 #include <linux/of_gpio.h>
 #include <linux/gpio.h>
 #include <linux/slab.h>
+#include <linux/irq.h>
 
 #define MPC8XXX_GPIO_PINS      32
 
@@ -35,6 +36,7 @@ struct mpc8xxx_gpio_chip {
         * open drain mode safely
         */
        u32 data;
+       struct irq_host *irq;
 };
 
 static inline u32 mpc8xxx_gpio2mask(unsigned int gpio)
@@ -128,12 +130,136 @@ static int mpc8xxx_gpio_dir_out(struct gpio_chip *gc, unsigned int gpio, int val
        return 0;
 }
 
+static int mpc8xxx_gpio_to_irq(struct gpio_chip *gc, unsigned offset)
+{
+       struct of_mm_gpio_chip *mm = to_of_mm_gpio_chip(gc);
+       struct mpc8xxx_gpio_chip *mpc8xxx_gc = to_mpc8xxx_gpio_chip(mm);
+
+       if (mpc8xxx_gc->irq && offset < MPC8XXX_GPIO_PINS)
+               return irq_create_mapping(mpc8xxx_gc->irq, offset);
+       else
+               return -ENXIO;
+}
+
+static void mpc8xxx_gpio_irq_cascade(unsigned int irq, struct irq_desc *desc)
+{
+       struct mpc8xxx_gpio_chip *mpc8xxx_gc = get_irq_desc_data(desc);
+       struct of_mm_gpio_chip *mm = &mpc8xxx_gc->mm_gc;
+       unsigned int mask;
+
+       mask = in_be32(mm->regs + GPIO_IER) & in_be32(mm->regs + GPIO_IMR);
+       if (mask)
+               generic_handle_irq(irq_linear_revmap(mpc8xxx_gc->irq,
+                                                    32 - ffs(mask)));
+}
+
+static void mpc8xxx_irq_unmask(unsigned int virq)
+{
+       struct mpc8xxx_gpio_chip *mpc8xxx_gc = get_irq_chip_data(virq);
+       struct of_mm_gpio_chip *mm = &mpc8xxx_gc->mm_gc;
+       unsigned long flags;
+
+       spin_lock_irqsave(&mpc8xxx_gc->lock, flags);
+
+       setbits32(mm->regs + GPIO_IMR, mpc8xxx_gpio2mask(virq_to_hw(virq)));
+
+       spin_unlock_irqrestore(&mpc8xxx_gc->lock, flags);
+}
+
+static void mpc8xxx_irq_mask(unsigned int virq)
+{
+       struct mpc8xxx_gpio_chip *mpc8xxx_gc = get_irq_chip_data(virq);
+       struct of_mm_gpio_chip *mm = &mpc8xxx_gc->mm_gc;
+       unsigned long flags;
+
+       spin_lock_irqsave(&mpc8xxx_gc->lock, flags);
+
+       clrbits32(mm->regs + GPIO_IMR, mpc8xxx_gpio2mask(virq_to_hw(virq)));
+
+       spin_unlock_irqrestore(&mpc8xxx_gc->lock, flags);
+}
+
+static void mpc8xxx_irq_ack(unsigned int virq)
+{
+       struct mpc8xxx_gpio_chip *mpc8xxx_gc = get_irq_chip_data(virq);
+       struct of_mm_gpio_chip *mm = &mpc8xxx_gc->mm_gc;
+
+       out_be32(mm->regs + GPIO_IER, mpc8xxx_gpio2mask(virq_to_hw(virq)));
+}
+
+static int mpc8xxx_irq_set_type(unsigned int virq, unsigned int flow_type)
+{
+       struct mpc8xxx_gpio_chip *mpc8xxx_gc = get_irq_chip_data(virq);
+       struct of_mm_gpio_chip *mm = &mpc8xxx_gc->mm_gc;
+       unsigned long flags;
+
+       switch (flow_type) {
+       case IRQ_TYPE_EDGE_FALLING:
+               spin_lock_irqsave(&mpc8xxx_gc->lock, flags);
+               setbits32(mm->regs + GPIO_ICR,
+                         mpc8xxx_gpio2mask(virq_to_hw(virq)));
+               spin_unlock_irqrestore(&mpc8xxx_gc->lock, flags);
+               break;
+
+       case IRQ_TYPE_EDGE_BOTH:
+               spin_lock_irqsave(&mpc8xxx_gc->lock, flags);
+               clrbits32(mm->regs + GPIO_ICR,
+                         mpc8xxx_gpio2mask(virq_to_hw(virq)));
+               spin_unlock_irqrestore(&mpc8xxx_gc->lock, flags);
+               break;
+
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static struct irq_chip mpc8xxx_irq_chip = {
+       .name           = "mpc8xxx-gpio",
+       .unmask         = mpc8xxx_irq_unmask,
+       .mask           = mpc8xxx_irq_mask,
+       .ack            = mpc8xxx_irq_ack,
+       .set_type       = mpc8xxx_irq_set_type,
+};
+
+static int mpc8xxx_gpio_irq_map(struct irq_host *h, unsigned int virq,
+                               irq_hw_number_t hw)
+{
+       set_irq_chip_data(virq, h->host_data);
+       set_irq_chip_and_handler(virq, &mpc8xxx_irq_chip, handle_level_irq);
+       set_irq_type(virq, IRQ_TYPE_NONE);
+
+       return 0;
+}
+
+static int mpc8xxx_gpio_irq_xlate(struct irq_host *h, struct device_node *ct,
+                                 const u32 *intspec, unsigned int intsize,
+                                 irq_hw_number_t *out_hwirq,
+                                 unsigned int *out_flags)
+
+{
+       /* interrupt sense values coming from the device tree equal either
+        * EDGE_FALLING or EDGE_BOTH
+        */
+       *out_hwirq = intspec[0];
+       *out_flags = intspec[1];
+
+       return 0;
+}
+
+static struct irq_host_ops mpc8xxx_gpio_irq_ops = {
+       .map    = mpc8xxx_gpio_irq_map,
+       .xlate  = mpc8xxx_gpio_irq_xlate,
+};
+
 static void __init mpc8xxx_add_controller(struct device_node *np)
 {
        struct mpc8xxx_gpio_chip *mpc8xxx_gc;
        struct of_mm_gpio_chip *mm_gc;
        struct of_gpio_chip *of_gc;
        struct gpio_chip *gc;
+       unsigned hwirq;
        int ret;
 
        mpc8xxx_gc = kzalloc(sizeof(*mpc8xxx_gc), GFP_KERNEL);
@@ -158,11 +284,32 @@ static void __init mpc8xxx_add_controller(struct device_node *np)
        else
                gc->get = mpc8xxx_gpio_get;
        gc->set = mpc8xxx_gpio_set;
+       gc->to_irq = mpc8xxx_gpio_to_irq;
 
        ret = of_mm_gpiochip_add(np, mm_gc);
        if (ret)
                goto err;
 
+       hwirq = irq_of_parse_and_map(np, 0);
+       if (hwirq == NO_IRQ)
+               goto skip_irq;
+
+       mpc8xxx_gc->irq =
+               irq_alloc_host(np, IRQ_HOST_MAP_LINEAR, MPC8XXX_GPIO_PINS,
+                              &mpc8xxx_gpio_irq_ops, MPC8XXX_GPIO_PINS);
+       if (!mpc8xxx_gc->irq)
+               goto skip_irq;
+
+       mpc8xxx_gc->irq->host_data = mpc8xxx_gc;
+
+       /* ack and mask all irqs */
+       out_be32(mm_gc->regs + GPIO_IER, 0xffffffff);
+       out_be32(mm_gc->regs + GPIO_IMR, 0);
+
+       set_irq_data(hwirq, mpc8xxx_gc);
+       set_irq_chained_handler(hwirq, mpc8xxx_gpio_irq_cascade);
+
+skip_irq:
        return;
 
 err: