From: Dave Liu <dave...@freescale.com>

The mpic timer works as wake up source for power management,
the max timer period is 336 seconds when the CCB freq is 400MHz.

to setup timer, type
echo 30 > /sys/devices/ffe00000.soc8572/ffe41100.timer/timeout

before the system enter to sleep mode.

Signed-off-by: Dave Liu <dave...@freescale.com>
---

Posting this both as a example of timer code for Regis as well as code for
partial review.. need to clean up a number of things.

- k

 arch/powerpc/boot/dts/mpc8572ds.dts |    7 +
 arch/powerpc/include/asm/mpic.h     |    1 +
 arch/powerpc/sysdev/Makefile        |    2 +-
 arch/powerpc/sysdev/mpic.c          |   89 ++++++++++++-
 arch/powerpc/sysdev/mpic_timer.c    |  257 +++++++++++++++++++++++++++++++++++
 5 files changed, 350 insertions(+), 6 deletions(-)
 create mode 100644 arch/powerpc/sysdev/mpic_timer.c

diff --git a/arch/powerpc/boot/dts/mpc8572ds.dts 
b/arch/powerpc/boot/dts/mpc8572ds.dts
index 82a8845..f3620a6 100644
--- a/arch/powerpc/boot/dts/mpc8572ds.dts
+++ b/arch/powerpc/boot/dts/mpc8572ds.dts
@@ -464,6 +464,13 @@
                        fsl,has-rstcr;
                };

+               ti...@41100 {
+                       compatible = "fsl,mpic-global-timer";
+                       reg = <0x41100 0x204>;
+                       interrupts = <0xf7 0x2>;
+                       interrupt-parent = <&mpic>;
+               };
+
                m...@41600 {
                        compatible = "fsl,mpc8572-msi", "fsl,mpic-msi";
                        reg = <0x41600 0x80>;
diff --git a/arch/powerpc/include/asm/mpic.h b/arch/powerpc/include/asm/mpic.h
index eb685ed..a98be7d 100644
--- a/arch/powerpc/include/asm/mpic.h
+++ b/arch/powerpc/include/asm/mpic.h
@@ -254,6 +254,7 @@ struct mpic
 #ifdef CONFIG_SMP
        struct irq_chip         hc_ipi;
 #endif
+       struct irq_chip         hc_tm;
        const char              *name;
        /* Flags */
        unsigned int            flags;
diff --git a/arch/powerpc/sysdev/Makefile b/arch/powerpc/sysdev/Makefile
index a90054b..58414fa 100644
--- a/arch/powerpc/sysdev/Makefile
+++ b/arch/powerpc/sysdev/Makefile
@@ -3,7 +3,7 @@ EXTRA_CFLAGS                    += -mno-minimal-toc
 endif

 mpic-msi-obj-$(CONFIG_PCI_MSI) += mpic_msi.o mpic_u3msi.o mpic_pasemi_msi.o
-obj-$(CONFIG_MPIC)             += mpic.o $(mpic-msi-obj-y)
+obj-$(CONFIG_MPIC)             += mpic.o mpic_timer.o $(mpic-msi-obj-y)
 fsl-msi-obj-$(CONFIG_PCI_MSI)  += fsl_msi.o

 obj-$(CONFIG_PPC_MPC106)       += grackle.o
diff --git a/arch/powerpc/sysdev/mpic.c b/arch/powerpc/sysdev/mpic.c
index e1d77ab..0cd9dac 100644
--- a/arch/powerpc/sysdev/mpic.c
+++ b/arch/powerpc/sysdev/mpic.c
@@ -207,6 +207,22 @@ static inline void _mpic_ipi_write(struct mpic *mpic, 
unsigned int ipi, u32 valu
        _mpic_write(mpic->reg_type, &mpic->gregs, offset, value);
 }

+static inline u32 _mpic_tm_read(struct mpic *mpic, unsigned int tm)
+{
+       unsigned int offset = MPIC_INFO(TIMER_VECTOR_PRI) +
+                             (tm * MPIC_INFO(TIMER_STRIDE));
+
+       return _mpic_read(mpic->reg_type, &mpic->tmregs, offset);
+}
+
+static inline void _mpic_tm_write(struct mpic *mpic, unsigned int tm, u32 
value)
+{
+       unsigned int offset = MPIC_INFO(TIMER_VECTOR_PRI) +
+                             (tm * MPIC_INFO(TIMER_STRIDE));
+
+       _mpic_write(mpic->reg_type, &mpic->tmregs, offset, value);
+}
+
 static inline u32 _mpic_cpu_read(struct mpic *mpic, unsigned int reg)
 {
        unsigned int cpu = 0;
@@ -259,6 +275,8 @@ static inline void _mpic_irq_write(struct mpic *mpic, 
unsigned int src_no,
 #define mpic_write(b,r,v)      _mpic_write(mpic->reg_type,&(b),(r),(v))
 #define mpic_ipi_read(i)       _mpic_ipi_read(mpic,(i))
 #define mpic_ipi_write(i,v)    _mpic_ipi_write(mpic,(i),(v))
+#define mpic_tm_read(i)                _mpic_tm_read(mpic,(i))
+#define mpic_tm_write(i,v)     _mpic_tm_write(mpic,(i),(v))
 #define mpic_cpu_read(i)       _mpic_cpu_read(mpic,(i))
 #define mpic_cpu_write(i,v)    _mpic_cpu_write(mpic,(i),(v))
 #define mpic_irq_read(s,r)     _mpic_irq_read(mpic,(s),(r))
@@ -612,7 +630,8 @@ static int irq_choose_cpu(unsigned int virt_irq)
 #define mpic_irq_to_hw(virq)   ((unsigned int)irq_map[virq].hwirq)

 /* Find an mpic associated with a given linux interrupt */
-static struct mpic *mpic_find(unsigned int irq, unsigned int *is_ipi)
+static struct mpic *mpic_find(unsigned int irq, unsigned int *is_ipi,
+                             unsigned int *is_tm)
 {
        unsigned int src = mpic_irq_to_hw(irq);
        struct mpic *mpic;
@@ -625,6 +644,9 @@ static struct mpic *mpic_find(unsigned int irq, unsigned 
int *is_ipi)
        if (is_ipi)
                *is_ipi = (src >= mpic->ipi_vecs[0] &&
                           src <= mpic->ipi_vecs[3]);
+       if (is_tm)
+               *is_tm = (src >= mpic->timer_vecs[0] &&
+                         src <= mpic->timer_vecs[3]);

        return mpic;
 }
@@ -648,6 +670,12 @@ static inline struct mpic * mpic_from_ipi(unsigned int ipi)
 }
 #endif

+/* Get the mpic structure from the tm number */
+static inline struct mpic * mpic_from_tm(unsigned int tm)
+{
+       return irq_desc[tm].chip_data;
+}
+
 /* Get the mpic structure from the irq number */
 static inline struct mpic * mpic_from_irq(unsigned int irq)
 {
@@ -817,6 +845,32 @@ static void mpic_end_ipi(unsigned int irq)

 #endif /* CONFIG_SMP */

+static void mpic_unmask_tm(unsigned int irq)
+{
+       struct mpic *mpic = mpic_from_tm(irq);
+       unsigned int src = mpic_irq_to_hw(irq) - mpic->timer_vecs[0];
+
+       DBG("%s: enable_tm: %d (tm %d)\n", mpic->name, irq, src);
+       mpic_tm_write(src, mpic_tm_read(src) & ~MPIC_VECPRI_MASK);
+       mpic_tm_read(src);
+}
+
+static void mpic_mask_tm(unsigned int irq)
+{
+       struct mpic *mpic = mpic_from_tm(irq);
+       unsigned int src = mpic_irq_to_hw(irq) - mpic->timer_vecs[0];
+
+       mpic_tm_write(src, mpic_tm_read(src) | MPIC_VECPRI_MASK);
+       mpic_tm_read(src);
+}
+
+static void mpic_end_tm(unsigned int irq)
+{
+       struct mpic *mpic = mpic_from_tm(irq);
+
+       mpic_eoi(mpic);
+}
+
 void mpic_set_affinity(unsigned int irq, cpumask_t cpumask)
 {
        struct mpic *mpic = mpic_from_irq(irq);
@@ -930,6 +984,12 @@ static struct irq_chip mpic_ipi_chip = {
 };
 #endif /* CONFIG_SMP */

+static struct irq_chip mpic_tm_chip = {
+       .mask           = mpic_mask_tm,
+       .unmask         = mpic_unmask_tm,
+       .eoi            = mpic_end_tm,
+};
+
 #ifdef CONFIG_MPIC_U3_HT_IRQS
 static struct irq_chip mpic_irq_ht_chip = {
        .startup        = mpic_startup_ht_irq,
@@ -961,6 +1021,15 @@ static int mpic_host_map(struct irq_host *h, unsigned int 
virq,
        if (mpic->protected && test_bit(hw, mpic->protected))
                return -EINVAL;

+       else if (hw >= mpic->timer_vecs[0] && hw <= mpic->timer_vecs[3]) {
+               WARN_ON(!(mpic->flags & MPIC_PRIMARY));
+
+               DBG("mpic: mapping as timer\n");
+               set_irq_chip_data(virq, mpic);
+               set_irq_chip_and_handler(virq, &mpic->hc_tm,
+                                        handle_fasteoi_irq);
+               return 0;
+       }
 #ifdef CONFIG_SMP
        else if (hw >= mpic->ipi_vecs[0]) {
                WARN_ON(!(mpic->flags & MPIC_PRIMARY));
@@ -1090,6 +1159,9 @@ struct mpic * __init mpic_alloc(struct device_node *node,
        mpic->hc_ipi.typename = name;
 #endif /* CONFIG_SMP */

+       mpic->hc_tm = mpic_tm_chip;
+       mpic->hc_tm.typename = name;
+
        mpic->flags = flags;
        mpic->isu_size = isu_size;
        mpic->irq_count = irq_count;
@@ -1279,15 +1351,17 @@ void __init mpic_init(struct mpic *mpic)
        /* Set current processor priority to max */
        mpic_cpu_write(MPIC_INFO(CPU_CURRENT_TASK_PRI), 0xf);

-       /* Initialize timers: just disable them all */
+       /* Initialize timers to our reserved vectors and mask them for now */
        for (i = 0; i < 4; i++) {
                mpic_write(mpic->tmregs,
                           i * MPIC_INFO(TIMER_STRIDE) +
-                          MPIC_INFO(TIMER_DESTINATION), 0);
+                          MPIC_INFO(TIMER_DESTINATION),
+                          1 << hard_smp_processor_id());
                mpic_write(mpic->tmregs,
                           i * MPIC_INFO(TIMER_STRIDE) +
                           MPIC_INFO(TIMER_VECTOR_PRI),
                           MPIC_VECPRI_MASK |
+                          (9 << MPIC_VECPRI_PRIORITY_SHIFT) |
                           (mpic->timer_vecs[0] + i));
        }

@@ -1378,8 +1452,8 @@ void __init mpic_set_serial_int(struct mpic *mpic, int 
enable)

 void mpic_irq_set_priority(unsigned int irq, unsigned int pri)
 {
-       unsigned int is_ipi;
-       struct mpic *mpic = mpic_find(irq, &is_ipi);
+       unsigned int is_ipi, is_tm;
+       struct mpic *mpic = mpic_find(irq, &is_ipi, &is_tm);
        unsigned int src = mpic_irq_to_hw(irq);
        unsigned long flags;
        u32 reg;
@@ -1393,6 +1467,11 @@ void mpic_irq_set_priority(unsigned int irq, unsigned 
int pri)
                        ~MPIC_VECPRI_PRIORITY_MASK;
                mpic_ipi_write(src - mpic->ipi_vecs[0],
                               reg | (pri << MPIC_VECPRI_PRIORITY_SHIFT));
+       } else if (is_tm) {
+               reg = mpic_tm_read(src - mpic->timer_vecs[0]) &
+                       ~MPIC_VECPRI_PRIORITY_MASK;
+               mpic_tm_write(src - mpic->timer_vecs[0],
+                             reg | (pri << MPIC_VECPRI_PRIORITY_SHIFT));
        } else {
                reg = mpic_irq_read(src, MPIC_INFO(IRQ_VECTOR_PRI))
                        & ~MPIC_VECPRI_PRIORITY_MASK;
diff --git a/arch/powerpc/sysdev/mpic_timer.c b/arch/powerpc/sysdev/mpic_timer.c
new file mode 100644
index 0000000..1d39732
--- /dev/null
+++ b/arch/powerpc/sysdev/mpic_timer.c
@@ -0,0 +1,257 @@
+/*
+ * Copyright (c) 2008 Freescale Semiconductor, Inc. All rights reserved.
+ * Dave Liu <dave...@freescale.com>
+ * copy from the 83xx GTM driver and modify for MPIC global timer,
+ * implement the global timer 0 function.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published
+ * by the Free Software Foundation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/errno.h>
+#include <linux/module.h>
+#include <linux/mm.h>
+#include <linux/delay.h>
+#include <linux/fs.h>
+#include <linux/string.h>
+#include <linux/interrupt.h>
+#include <linux/sysfs.h>
+#include <linux/of_platform.h>
+
+#include <linux/io.h>
+#include <linux/irq.h>
+
+#include <sysdev/fsl_soc.h>
+
+#define MPIC_TIMER_TCR_OFFSET          0x200
+#define MPIC_TIMER_TCR_CLKDIV_64       0x00000300
+#define MPIC_TIMER_STOP                        0x80000000
+
+struct mpic_tm_regs {
+       u32     gtccr;
+       u32     res0[3];
+       u32     gtbcr;
+       u32     res1[3];
+       u32     gtvpr;
+       u32     res2[3];
+       u32     gtdr;
+       u32     res3[3];
+};
+
+struct mpic_tm_priv {
+       struct mpic_tm_regs __iomem *regs;
+       int irq;
+       int ticks_per_sec;
+       spinlock_t lock;
+};
+
+struct mpic_type {
+       int has_tcr;
+};
+
+static irqreturn_t mpic_tm_isr(int irq, void *dev_id)
+{
+       struct mpic_tm_priv *priv = dev_id;
+       unsigned long flags;
+       unsigned long temp;
+
+       spin_lock_irqsave(&priv->lock, flags);
+       temp = in_be32(&priv->regs->gtbcr);
+       temp |= MPIC_TIMER_STOP; /* counting inhibited */
+       out_be32(&priv->regs->gtbcr, temp);
+       spin_unlock_irqrestore(&priv->lock, flags);
+
+       return IRQ_HANDLED;
+}
+
+static ssize_t mpic_tm_timeout_store(struct device *dev,
+                               struct device_attribute *attr,
+                               const char *buf, size_t count)
+{
+       struct mpic_tm_priv *priv = dev_get_drvdata(dev);
+       unsigned long interval = simple_strtoul(buf, NULL, 0);
+       unsigned long temp;
+
+       if (interval > 0x7fffffff) {
+               dev_dbg(dev, "mpic_tm: interval %lu (in ns) too long\n", 
interval);
+               return -EINVAL;
+       }
+
+       interval *= priv->ticks_per_sec;
+
+       if (interval > 0x7fffffff) {
+               dev_dbg(dev, "mpic_tm: interval %lu (in ticks) too long\n",
+                       interval);
+               return -EINVAL;
+       }
+
+       spin_lock_irq(&priv->lock);
+
+       /* stop timer 0 */
+       temp = in_be32(&priv->regs->gtbcr);
+       temp |= MPIC_TIMER_STOP; /* counting inhibited */
+       out_be32(&priv->regs->gtbcr, temp);
+
+       if (interval != 0) {
+               /* start timer */
+               out_be32(&priv->regs->gtbcr, interval | MPIC_TIMER_STOP);
+               out_be32(&priv->regs->gtbcr, interval);
+       }
+
+       spin_unlock_irq(&priv->lock);
+       return count;
+}
+
+static ssize_t mpic_tm_timeout_show(struct device *dev,
+                               struct device_attribute *attr,
+                               char *buf)
+{
+       struct mpic_tm_priv *priv = dev_get_drvdata(dev);
+       int timeout = 0;
+
+       spin_lock_irq(&priv->lock);
+
+       if (!(in_be32(&priv->regs->gtbcr) & MPIC_TIMER_STOP)) {
+               timeout = in_be32(&priv->regs->gtccr);
+               timeout += priv->ticks_per_sec - 1;
+               timeout /= priv->ticks_per_sec;
+       }
+
+       spin_unlock_irq(&priv->lock);
+       return sprintf(buf, "%u\n", timeout);
+}
+
+static DEVICE_ATTR(timeout, 0660, mpic_tm_timeout_show, mpic_tm_timeout_store);
+
+static int __devinit mpic_tm_probe(struct of_device *dev,
+                               const struct of_device_id *match)
+{
+       struct device_node *np = dev->node;
+       struct resource res;
+       struct mpic_tm_priv *priv;
+       struct mpic_type *type = match->data;
+       int has_tcr = type->has_tcr;
+       u32 busfreq = fsl_get_sys_freq();
+       int ret = 0;
+
+       if (busfreq == 0) {
+               dev_err(&dev->dev, "mpic_tm: No bus frequency in device 
tree.\n");
+               return -ENODEV;
+       }
+
+       priv = kmalloc(sizeof(struct mpic_tm_priv), GFP_KERNEL);
+       if (!priv)
+               return -ENOMEM;
+
+       spin_lock_init(&priv->lock);
+       dev_set_drvdata(&dev->dev, priv);
+
+       ret = of_address_to_resource(np, 0, &res);
+       if (ret)
+               goto out;
+
+       priv->irq = irq_of_parse_and_map(np, 0);
+       if (priv->irq == NO_IRQ) {
+               dev_err(&dev->dev, "MPIC global timer0 exists in device tree "
+                               "without an IRQ.\n");
+               ret = -ENODEV;
+               goto out;
+       }
+
+       ret = request_irq(priv->irq, mpic_tm_isr, 0, "mpic timer 0", priv);
+       if (ret)
+               goto out;
+
+       priv->regs = ioremap(res.start, res.end - res.start + 1);
+       if (!priv->regs) {
+               ret = -ENOMEM;
+               goto out;
+       }
+
+       /*
+        * MPIC implementation from Freescale has the TCR register,
+        * the MPIC_TIMER_TCR_OFFSET is 0x200 from global timer base
+        * the default clock source to the MPIC timer 0 is CCB freq / 8.
+        * to extend the timer period, we divide the timer clock source
+        * as CCB freq / 64, so the max timer period is 336 seconds
+        * when the CCB frequence is 400MHz.
+        */
+       if (!has_tcr) {
+               priv->ticks_per_sec = busfreq / 8;
+       } else {
+               u32 __iomem *tcr;
+               tcr = (u32 __iomem *)((u32)priv->regs + MPIC_TIMER_TCR_OFFSET);
+               out_be32(tcr, in_be32(tcr) | MPIC_TIMER_TCR_CLKDIV_64);
+               priv->ticks_per_sec = busfreq / 64;
+       }
+
+       ret = device_create_file(&dev->dev, &dev_attr_timeout);
+       if (ret)
+               goto out;
+
+       printk("MPIC global timer init done.\n");
+
+       return 0;
+
+out:
+       kfree(priv);
+       return ret;
+}
+
+static int __devexit mpic_tm_remove(struct of_device *dev)
+{
+       struct mpic_tm_priv *priv = dev_get_drvdata(&dev->dev);
+
+       device_remove_file(&dev->dev, &dev_attr_timeout);
+       free_irq(priv->irq, priv);
+       iounmap(priv->regs);
+
+       dev_set_drvdata(&dev->dev, NULL);
+       kfree(priv);
+       return 0;
+}
+
+static struct mpic_type mpic_types[] = {
+       {
+               .has_tcr = 0,
+       },
+       {
+               .has_tcr = 1,
+       }
+};
+
+static struct of_device_id mpic_tm_match[] = {
+       {
+               .compatible = "mpic-global-timer",
+               .data = &mpic_types[0],
+       },
+       {
+               .compatible = "fsl,mpic-global-timer",
+               .data = &mpic_types[1],
+       },
+       {},
+};
+
+static struct of_platform_driver mpic_tm_driver = {
+       .name = "mpic-global-timer",
+       .match_table = mpic_tm_match,
+       .probe = mpic_tm_probe,
+       .remove = __devexit_p(mpic_tm_remove)
+};
+
+static int __init mpic_tm_init(void)
+{
+       return of_register_platform_driver(&mpic_tm_driver);
+}
+
+static void __exit mpic_tm_exit(void)
+{
+       of_unregister_platform_driver(&mpic_tm_driver);
+}
+
+module_init(mpic_tm_init);
+module_exit(mpic_tm_exit);
-- 
1.5.6.5

_______________________________________________
Linuxppc-dev mailing list
Linuxppc-dev@ozlabs.org
https://ozlabs.org/mailman/listinfo/linuxppc-dev

Reply via email to