All of lore.kernel.org
 help / color / mirror / Atom feed
From: Scott Wood <scottwood@freescale.com>
To: paulus@samba.org
Cc: linuxppc-dev@ozlabs.org
Subject: [PATCH 6/8] mpc83xx: timer driver for PM wakeup
Date: Wed, 12 Dec 2007 11:36:04 -0600	[thread overview]
Message-ID: <20071212173604.GE5596@loki.buserror.net> (raw)
In-Reply-To: <20071212173519.GA5577@loki.buserror.net>

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@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(&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.7

  parent reply	other threads:[~2007-12-12 17:36 UTC|newest]

Thread overview: 12+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2007-12-12 17:35 [PATCH 1/8] Implement arch disable/enable irq hooks Scott Wood
2007-12-12 17:35 ` [PATCH 2/8] pm: Add TLF_SLEEPING hack to delay interrupt delivery when waking from sleep Scott Wood
2007-12-12 17:36 ` [PATCH 3/8] Add 6xx-style HID0_SLEEP support Scott Wood
2007-12-12 17:36 ` [PATCH 4/8] fsl_soc: Factor fsl_get_sys_freq() out of the wdt init Scott Wood
2007-12-12 23:01   ` Stephen Rothwell
2007-12-12 17:36 ` [PATCH 5/8] mpc83xx: Power Management support Scott Wood
2007-12-12 17:36 ` Scott Wood [this message]
2007-12-13  0:14   ` [PATCH 6/8] mpc83xx: timer driver for PM wakeup Stephen Rothwell
2007-12-12 17:36 ` [PATCH 7/8] gianfar: Add flags for magic packet and MDIO Scott Wood
2007-12-12 17:36 ` [PATCH 8/8] gianfar: Magic Packet and suspend/resume support Scott Wood
2007-12-14 20:24   ` Jeff Garzik
2007-12-14 20:24     ` Jeff Garzik

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20071212173604.GE5596@loki.buserror.net \
    --to=scottwood@freescale.com \
    --cc=linuxppc-dev@ozlabs.org \
    --cc=paulus@samba.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.