This is a driver for the mpc83xx's GTM4 timer.  It's functionality
is limited to providing a wakeup source for suspend-to-RAM.

Signed-off-by: Scott Wood <[EMAIL PROTECTED]>
---
 arch/powerpc/platforms/83xx/Kconfig  |    9 +
 arch/powerpc/platforms/83xx/Makefile |    1 +
 arch/powerpc/platforms/83xx/timer.c  |  299 ++++++++++++++++++++++++++++++++++
 3 files changed, 309 insertions(+), 0 deletions(-)
 create mode 100644 arch/powerpc/platforms/83xx/timer.c

diff --git a/arch/powerpc/platforms/83xx/Kconfig 
b/arch/powerpc/platforms/83xx/Kconfig
index 901dbaf..800f547 100644
--- a/arch/powerpc/platforms/83xx/Kconfig
+++ b/arch/powerpc/platforms/83xx/Kconfig
@@ -80,3 +80,12 @@ config PPC_83xx_SUSPEND
        bool
        default y
        depends on PPC_83xx && SUSPEND
+
+config MPC83xx_GTM
+       tristate "83xx General-Purpose Timers for PM wakeup"
+       help
+         This enables a driver for the GTM4 timer, to be used
+         as a wakeup source for suspend-to-RAM.  To arm the
+         timer, write the number of seconds until expiration
+         to the "timeout" file in the device's sysfs directory.
+         To disarm, write 0 to the "timeout" file.
diff --git a/arch/powerpc/platforms/83xx/Makefile 
b/arch/powerpc/platforms/83xx/Makefile
index 944369e..bcc1003 100644
--- a/arch/powerpc/platforms/83xx/Makefile
+++ b/arch/powerpc/platforms/83xx/Makefile
@@ -4,6 +4,7 @@
 obj-y                          := misc.o usb.o
 obj-$(CONFIG_PCI)              += pci.o
 obj-$(CONFIG_SUSPEND)          += suspend.o suspend-asm.o
+obj-$(CONFIG_MPC83xx_GTM)      += timer.o
 obj-$(CONFIG_MPC8313_RDB)      += mpc8313_rdb.o
 obj-$(CONFIG_MPC832x_RDB)      += mpc832x_rdb.o
 obj-$(CONFIG_MPC834x_MDS)      += mpc834x_mds.o
diff --git a/arch/powerpc/platforms/83xx/timer.c 
b/arch/powerpc/platforms/83xx/timer.c
new file mode 100644
index 0000000..ed7b469
--- /dev/null
+++ b/arch/powerpc/platforms/83xx/timer.c
@@ -0,0 +1,299 @@
+/*
+ * MPC83xx Global Timer4 support
+ *
+ * This driver is currently specific to timer 4 in 16-bit mode,
+ * as that is all that can be used as a wakeup source for deep sleep
+ * on the MPC8313.
+ *
+ * Copyright (c) 2007 Freescale Semiconductor, Inc.
+ *
+ * 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 <asm/io.h>
+#include <asm/irq.h>
+#include <asm/of_platform.h>
+
+#include <sysdev/fsl_soc.h>
+
+#define MDR_ICLK_DIV16 0x0004
+
+struct gtm_regs {
+       u8    cfr1; /* Timer1/2 Configuration  */
+       #define CFR1_PCAS 0x80 /* Pair Cascade mode  */
+       #define CFR1_BCM  0x40  /* Backward compatible mode  */
+       #define CFR1_STP2 0x20 /* Stop timer  */
+       #define CFR1_RST2 0x10 /* Reset timer  */
+       #define CFR1_GM2  0x08 /* Gate mode for pin 2  */
+       #define CFR1_GM1  0x04 /* Gate mode for pin 1  */
+       #define CFR1_STP1 0x02 /* Stop timer  */
+       #define CFR1_RST1 0x01 /* Reset timer  */
+       #define CFR1_RES ~(CFR1_PCAS | CFR1_STP2 | CFR1_RST2 | CFR1_GM2 |\
+               CFR1_GM1 | CFR1_STP1 | CFR1_RST1)
+
+       u8    res0[3];
+       u8    cfr2; /* Timer3/4 Configuration  */
+       #define CFR2_PCAS 0x80 /* Pair Cascade mode  */
+       #define CFR2_SCAS 0x40 /* Super Cascade mode  */
+       #define CFR2_STP4 0x20 /* Stop timer  */
+       #define CFR2_RST4 0x10 /* Reset timer  */
+       #define CFR2_GM4  0x08 /* Gate mode for pin 4  */
+       #define CFR2_GM3  0x04 /* Gate mode for pin 3  */
+       #define CFR2_STP3 0x02 /* Stop timer  */
+       #define CFR2_RST3 0x01 /* Reset timer  */
+
+       u8    res1[11];
+       u16   mdr1; /* Timer1 Mode Register  */
+       #define MDR_SPS  0xff00 /* Secondary Prescaler value (256) */
+       #define MDR_CE   0x00c0 /* Capture edge and enable interrupt  */
+       #define MDR_OM   0x0020 /* Output mode  */
+       #define MDR_ORI  0x0010 /* Output reference interrupt enable  */
+       #define MDR_FRR  0x0008 /* Free run/restart  */
+       #define MDR_ICLK 0x0006 /* Input clock source for the timer */
+       #define MDR_GE   0x0001 /* Gate enable  */
+
+       u16   mdr2; /* Timer2 Mode Register  */
+       u16   rfr1; /* Timer1 Reference Register  */
+       u16   rfr2; /* Timer2 Reference Register  */
+       u16   cpr1; /* Timer1 Capture Register  */
+       u16   cpr2; /* Timer2 Capture Register  */
+       u16   cnr1; /* Timer1 Counter Register  */
+       u16   cnr2; /* Timer2 Counter Register  */
+       u16   mdr3; /* Timer3 Mode Register  */
+       u16   mdr4; /* Timer4 Mode Register  */
+       u16   rfr3; /* Timer3 Reference Register  */
+       u16   rfr4; /* Timer4 Reference Register  */
+       u16   cpr3; /* Timer3 Capture Register  */
+       u16   cpr4; /* Timer4 Capture Register  */
+       u16   cnr3; /* Timer3 Counter Register  */
+       u16   cnr4; /* Timer4 Counter Register  */
+       u16   evr1; /* Timer1 Event Register  */
+       u16   evr2; /* Timer2 Event Register  */
+       u16   evr3; /* Timer3 Event Register  */
+       u16   evr4; /* Timer4 Event Register  */
+       #define GTEVR_REF 0x0002 /* Output reference event  */
+       #define GTEVR_CAP 0x0001 /* Counter Capture event   */
+       #define GTEVR_RES ~(EVR_CAP|EVR_REF)
+
+       u16   psr1; /* Timer1 Prescaler Register  */
+       u16   psr2; /* Timer2 Prescaler Register  */
+       u16   psr3; /* Timer3 Prescaler Register  */
+       u16   psr4; /* Timer4 Prescaler Register  */
+       #define GTPSR_PPS  0x00FF /* Primary Prescaler Bits (256). */
+       #define GTPSR_RES  ~(GTPSR_PPS)
+};
+
+struct gtm_priv {
+       struct gtm_regs __iomem *regs;
+       int irq;
+       int ticks_per_sec;
+       spinlock_t lock;
+};
+
+static irqreturn_t fsl_gtm_isr(int irq, void *dev_id)
+{
+       struct gtm_priv *priv = dev_id;
+       unsigned long flags;
+       u16 event;
+
+       spin_lock_irqsave(&priv->lock, flags);
+
+       event = in_be16(&priv->regs->evr4);
+       out_be16(&priv->regs->evr4, event);
+
+       if (event & GTEVR_REF)
+               out_8(&priv->regs->cfr2, CFR2_STP4);
+
+       spin_unlock_irqrestore(&priv->lock, flags);
+       return event ? IRQ_HANDLED : IRQ_NONE;
+}
+
+static ssize_t gtm_timeout_store(struct device *dev,
+                                 struct device_attribute *attr,
+                                 const char *buf, size_t count)
+{
+       struct gtm_priv *priv = dev_get_drvdata(dev);
+       unsigned long interval = simple_strtoul(buf, NULL, 0);
+
+       if (interval > 0xffff) {
+               dev_dbg(dev, "gtm: interval %lu (in ns) too long\n", interval);
+               return -EINVAL;
+       }
+
+       interval *= priv->ticks_per_sec;
+
+       if (interval > 0xffff) {
+               dev_dbg(dev, "gtm: interval %lu (in ticks) too long\n",
+                       interval);
+               return -EINVAL;
+       }
+
+       spin_lock_irq(&priv->lock);
+
+       /* reset timer 4 */
+       out_8(&priv->regs->cfr2, CFR2_STP3 | CFR2_STP4);
+
+       if (interval != 0) {
+               out_8(&priv->regs->cfr2, CFR2_GM4 | CFR2_RST4 | CFR2_STP4);
+               /* clear events */
+               out_be16(&priv->regs->evr4, GTEVR_REF | GTEVR_CAP);
+               /* maximum primary prescale (256) */
+               out_be16(&priv->regs->psr4, GTPSR_PPS);
+               /* clear current counter */
+               out_be16(&priv->regs->cnr4, 0x0);
+               /* set count limit */
+               out_be16(&priv->regs->rfr4, interval);
+               out_be16(&priv->regs->mdr4, MDR_SPS | MDR_ORI | MDR_FRR |
+                                           MDR_ICLK_DIV16);
+               /* start timer */
+               out_8(&priv->regs->cfr2, CFR2_GM4 | CFR2_STP3 | CFR2_RST4);
+       }
+
+       spin_unlock_irq(&priv->lock);
+       return count;
+}
+
+static ssize_t gtm_timeout_show(struct device *dev,
+                                struct device_attribute *attr,
+                                char *buf)
+{
+       struct gtm_priv *priv = dev_get_drvdata(dev);
+       int timeout = 0;
+
+       spin_lock_irq(&priv->lock);
+
+       if (!(in_8(&priv->regs->cfr2) & CFR2_STP4)) {
+               timeout = in_be16(&priv->regs->rfr4) -
+                         in_be16(&priv->regs->cnr4);
+               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, gtm_timeout_show, gtm_timeout_store);
+
+static int __devinit gtm_probe(struct of_device *dev,
+                               const struct of_device_id *match)
+{
+       struct device_node *np = dev->node;
+       struct resource res;
+       int ret = 0;
+       u32 busfreq = fsl_get_sys_freq();
+       struct gtm_priv *priv;
+
+       if (busfreq == 0) {
+               printk(KERN_ERR "gtm: No bus frequency in device tree.\n");
+               return -ENODEV;
+       }
+
+       priv = kmalloc(sizeof(struct gtm_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) {
+               printk(KERN_ERR "mpc83xx-gtm exists in device tree "
+                               "without an IRQ.\n");
+               ret = -ENODEV;
+               goto out;
+       }
+
+       ret = request_irq(priv->irq, fsl_gtm_isr, 0, "gtm timer", priv);
+       if (ret)
+               goto out;
+
+       priv->regs = ioremap(res.start, sizeof(struct gtm_regs));
+       if (!priv->regs) {
+               ret = -ENOMEM;
+               goto out;
+       }
+
+       /* Disable the unused clocks to save power. */
+       out_8(&priv->regs->cfr1, CFR1_STP1 | CFR1_STP2);
+       out_8(&priv->regs->cfr2, CFR2_STP3 | CFR2_STP4);
+
+       /*
+        * Maximum prescaling is used (input clock/16, 256 primary prescaler,
+        * 256 secondary prescaler) to maximize the timer's range.  With a
+        * bus clock of 133MHz, this yields a maximum interval of 516
+        * seconds while retaining subsecond precision.  Since only
+        * timer 4 is supported for wakeup on the 8313, and timer 4
+        * is the LSB when chained, we can't use chaining to increase
+        * the range.
+        */
+       priv->ticks_per_sec = busfreq / (16*256*256);
+
+       ret = device_create_file(&dev->dev, &dev_attr_timeout);
+       if (ret)
+               goto out;
+
+       return 0;
+
+out:
+       kfree(priv);
+       return ret;
+}
+
+static int __devexit gtm_remove(struct of_device *dev)
+{
+       struct gtm_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 of_device_id gtm_match[] = {
+       {
+               .compatible = "fsl,mpc83xx-gtm",
+       },
+       {},
+};
+
+static struct of_platform_driver gtm_driver = {
+       .name = "mpc83xx-gtm-timer",
+       .match_table = gtm_match,
+       .probe = gtm_probe,
+       .remove = __devexit_p(gtm_remove)
+};
+
+static int __init gtm_init(void)
+{
+       return of_register_platform_driver(&gtm_driver);
+}
+
+static void __exit gtm_exit(void)
+{
+       of_unregister_platform_driver(&gtm_driver);
+}
+
+module_init(gtm_init);
+module_exit(gtm_exit);
-- 
1.5.3.4

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

Reply via email to