[PATCH 6/8] mpc83xx: timer driver for PM wakeup
Scott Wood
scottwood at freescale.com
Thu Dec 13 04:36:04 EST 2007
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 <scottwood at freescale.com>
---
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..7fc6048
--- /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 <linux/of_platform.h>
+
+#include <asm/io.h>
+#include <asm/irq.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) {
+ dev_err(&dev->dev, "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) {
+ dev_err(&dev->dev, "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,mpc8313-gtm",
+ },
+ {},
+};
+
+static struct of_platform_driver gtm_driver = {
+ .name = "mpc83xx-gtm",
+ .match_table = gtm_match,
+ .probe = gtm_probe,
+ .remove = __devexit_p(gtm_remove)
+};
+
+static int __init gtm_init(void)
+{
+ return of_register_platform_driver(>m_driver);
+}
+
+static void __exit gtm_exit(void)
+{
+ of_unregister_platform_driver(>m_driver);
+}
+
+module_init(gtm_init);
+module_exit(gtm_exit);
--
1.5.3.7
More information about the Linuxppc-dev
mailing list