The implementation in this patch still requires the irq_setup to be done
properly, and also lacks the supported interrupts. These will be added
in separate patches.

Signed-off-by: Tero Kristo <[email protected]>
---
 drivers/power/omap_prm.c       |  214 +++++++++++++++++++++++++++++++++++++++-
 include/linux/power/omap_prm.h |   19 ++++
 2 files changed, 231 insertions(+), 2 deletions(-)
 create mode 100644 include/linux/power/omap_prm.h

diff --git a/drivers/power/omap_prm.c b/drivers/power/omap_prm.c
index dfc0920..745a4bc 100644
--- a/drivers/power/omap_prm.c
+++ b/drivers/power/omap_prm.c
@@ -15,15 +15,32 @@
 #include <linux/ctype.h>
 #include <linux/module.h>
 #include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
 #include <linux/slab.h>
 #include <linux/init.h>
 #include <linux/err.h>
 #include <linux/platform_device.h>
 
+#include <plat/irqs.h>
+
 #define DRIVER_NAME "omap-prm"
+#define OMAP_PRCM_MAX_NR_PENDING_REG 2
+
+struct omap_prcm_irq_setup {
+       u32 ack;
+       u32 mask;
+       int irq;
+       int io_irq;
+       int base_irq;
+       int nr_regs;
+       int nr_irqs;
+};
 
 struct omap_prm_device {
        struct platform_device          pdev;
+       struct omap_prcm_irq_setup      irq_setup;
+       struct irq_chip_generic         **irq_chips;
 };
 
 static struct omap_prm_device prm_dev = {
@@ -33,20 +50,213 @@ static struct omap_prm_device prm_dev = {
        },
 };
 
-static int __init omap_prm_probe(struct platform_device *pdev)
+static void prm_pending_events(unsigned long *events)
+{
+       u32 ena, st;
+       int i;
+
+       memset(events, 0, prm_dev.irq_setup.nr_regs * 8);
+
+       for (i = 0; i < prm_dev.irq_setup.nr_regs; i++) {
+               ena = readl(prm_dev.irq_setup.mask);
+               st = readl(prm_dev.irq_setup.ack);
+               events[i] = ena & st;
+       }
+}
+
+/*
+ * PRCM Interrupt Handler
+ *
+ * The PRM_IRQSTATUS_MPU register indicates if there are any pending
+ * interrupts from the PRCM for the MPU. These bits must be cleared in
+ * order to clear the PRCM interrupt. The PRCM interrupt handler is
+ * implemented to simply clear the PRM_IRQSTATUS_MPU in order to clear
+ * the PRCM interrupt. Please note that bit 0 of the PRM_IRQSTATUS_MPU
+ * register indicates that a wake-up event is pending for the MPU and
+ * this bit can only be cleared if the all the wake-up events latched
+ * in the various PM_WKST_x registers have been cleared. The interrupt
+ * handler is implemented using a do-while loop so that if a wake-up
+ * event occurred during the processing of the prcm interrupt handler
+ * (setting a bit in the corresponding PM_WKST_x register and thus
+ * preventing us from clearing bit 0 of the PRM_IRQSTATUS_MPU register)
+ * this would be handled.
+ */
+static void prcm_irq_handler(unsigned int irq, struct irq_desc *desc)
+{
+       unsigned long pending[OMAP_PRCM_MAX_NR_PENDING_REG];
+       struct irq_chip *chip = irq_desc_get_chip(desc);
+       unsigned int virtirq;
+       int nr_irqs = prm_dev.irq_setup.nr_irqs;
+
+       /*
+        * Loop until all pending irqs are handled, since
+        * generic_handle_irq() can cause new irqs to come
+        */
+       while (1) {
+               chip->irq_ack(&desc->irq_data);
+
+               prm_pending_events(pending);
+
+               /* No bit set, then all IRQs are handled */
+               if (find_first_bit(pending, nr_irqs) >= nr_irqs) {
+                       chip->irq_unmask(&desc->irq_data);
+                       break;
+               }
+
+               /*
+                * Loop on all currently pending irqs so that new irqs
+                * cannot starve previously pending irqs
+                */
+               for_each_set_bit(virtirq, pending, nr_irqs)
+                       generic_handle_irq(prm_dev.irq_setup.base_irq +
+                               virtirq);
+
+               chip->irq_unmask(&desc->irq_data);
+       }
+}
+
+/*
+ * Given a PRCM event name, returns the corresponding IRQ on which the
+ * handler should be registered.
+ */
+int omap_prcm_event_to_irq(const char *name)
+{
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(omap_prcm_irqs); i++)
+               if (!strcmp(omap_prcm_irqs[i].name, name))
+                       return prm_dev.irq_setup.base_irq +
+                               omap_prcm_irqs[i].offset;
+
+       return -ENOENT;
+}
+
+/*
+ * Reverses memory allocated and other setups done by
+ * omap_prcm_irq_init().
+ */
+void omap_prcm_irq_cleanup(void)
 {
+       int i;
+
+       if (prm_dev.irq_chips) {
+               for (i = 0; i < prm_dev.irq_setup.nr_regs; i++) {
+                       if (prm_dev.irq_chips[i])
+                               irq_remove_generic_chip(prm_dev.irq_chips[i],
+                                       0xffffffff, 0, 0);
+                       prm_dev.irq_chips[i] = NULL;
+               }
+               kfree(prm_dev.irq_chips);
+               prm_dev.irq_chips = NULL;
+       }
+
+       irq_set_chained_handler(prm_dev.irq_setup.irq, NULL);
+
+       if (prm_dev.irq_setup.base_irq > 0)
+               irq_free_descs(prm_dev.irq_setup.base_irq,
+                               prm_dev.irq_setup.nr_irqs);
+       prm_dev.irq_setup.base_irq = 0;
+}
+
+/*
+ * Prepare the array of PRCM events corresponding to the current SoC,
+ * and set-up the chained interrupt handler mechanism.
+ */
+static int __init omap_prcm_irq_init(void)
+{
+       int i;
+       struct irq_chip_generic *gc;
+       struct irq_chip_type *ct;
+       int offset;
+       int max_irq = 64;
+
+       /* XXX: supported irqs should be setup here */
+
+       irq_set_chained_handler(prm_dev.irq_setup.irq, prcm_irq_handler);
+
+       prm_dev.irq_setup.base_irq =
+               irq_alloc_descs(-1, 0, prm_dev.irq_setup.nr_irqs, 0);
+
+       if (prm_dev.irq_setup.base_irq < 0) {
+               pr_err("PRCM: failed to allocate irq descs\n");
+               goto err;
+       }
+
+       prm_dev.irq_chips = kzalloc(sizeof(void *) * prm_dev.irq_setup.nr_regs,
+               GFP_KERNEL);
+
+       if (!prm_dev.irq_chips) {
+               pr_err("PRCM: kzalloc failed\n");
+               goto err;
+       }
+
+       for (i = 0; i <= max_irq / 32; i++) {
+               gc = irq_alloc_generic_chip("PRCM", 1,
+                       prm_dev.irq_setup.base_irq + i * 32, NULL,
+                       handle_level_irq);
+
+               if (!gc) {
+                       pr_err("PRCM: failed to allocate generic chip\n");
+                       goto err;
+               }
+               ct = gc->chip_types;
+               ct->chip.irq_ack = irq_gc_ack_set_bit;
+               ct->chip.irq_mask = irq_gc_mask_clr_bit;
+               ct->chip.irq_unmask = irq_gc_mask_set_bit;
+
+               ct->regs.ack = prm_dev.irq_setup.ack + (i << 2);
+               ct->regs.mask = prm_dev.irq_setup.mask + (i << 2);
+
+               irq_setup_generic_chip(gc, 0xffffffff, 0, IRQ_NOREQUEST, 0);
+               prm_dev.irq_chips[i] = gc;
+       }
+
        return 0;
+
+err:
+       omap_prcm_irq_cleanup();
+       return -ENOMEM;
 }
 
-static int __devexit omap_prm_remove(struct platform_device *pdev)
+static int omap_prm_prepare(struct device *kdev)
 {
+       disable_irq(prm_dev.irq_setup.io_irq);
        return 0;
 }
 
+static void omap_prm_complete(struct device *kdev)
+{
+       enable_irq(prm_dev.irq_setup.io_irq);
+}
+
+static int __devexit omap_prm_remove(struct platform_device *pdev)
+{
+       return 0;
+}
+
+static int __init omap_prm_probe(struct platform_device *pdev)
+{
+       /* XXX: prm_dev.irq_setup should be populated here */
+
+       /* XXX: following calls should be enabled once irq_setup is done */
+#if 0
+       omap_prcm_irq_init();
+
+       prm_dev.irq_setup.io_irq = omap_prcm_event_to_irq("io");
+#endif
+       return 0;
+}
+
+static const struct dev_pm_ops prm_pm_ops = {
+       .prepare = omap_prm_prepare,
+       .complete = omap_prm_complete,
+};
+
 static struct platform_driver prm_driver = {
        .remove         = __exit_p(omap_prm_remove),
        .driver         = {
                .name   = DRIVER_NAME,
+               .pm     = &prm_pm_ops,
        },
 };
 
diff --git a/include/linux/power/omap_prm.h b/include/linux/power/omap_prm.h
new file mode 100644
index 0000000..9b161b5
--- /dev/null
+++ b/include/linux/power/omap_prm.h
@@ -0,0 +1,19 @@
+/*
+ * OMAP Power and Reset Management (PRM) driver
+ *
+ * Copyright (C) 2011 Texas Instruments, Inc.
+ *
+ * Author: Tero Kristo <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifndef __LINUX_POWER_OMAP_PRM_H__
+#define __LINUX_POWER_OMAP_PRM_H__
+
+int omap_prcm_event_to_irq(const char *name);
+
+#endif
-- 
1.7.4.1


Texas Instruments Oy, Tekniikantie 12, 02150 Espoo. Y-tunnus: 0115040-6. 
Kotipaikka: Helsinki
 

--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to