* [PATCH 4/8] sh: timer rewrite V2, cmt driver
@ 2008-11-25 13:00 Magnus Damm
0 siblings, 0 replies; only message in thread
From: Magnus Damm @ 2008-11-25 13:00 UTC (permalink / raw)
To: linux-sh
From: Magnus Damm <damm@igel.co.jp>
CMT driver for the SuperH timer base code implementation V2.
Both 16-bit and 32-bit CMT versions are supported, but only 32-bit
is tested. This driver only contains hardware specific bits, the
logic handling clock events and clock sources is performed by the
timer_inc helper code.
Works fine as clock source and/or event in periodic or oneshot mode.
Tested on sh7722 and sh7723, but should work with any cpu/architecture.
Signed-off-by: Magnus Damm <damm@igel.co.jp>
---
Changes since V1 include:
- Reworked private data handling - no more union in sh_timer.h
- Made spinlock static
- Use setup_irq() to request early interrupt
- Removed request_mem_region()/free_mem_region()
- Removed sysdev code
arch/sh/Kconfig | 7 +
drivers/clocksource/Makefile | 1
drivers/clocksource/sh_cmt.c | 270 ++++++++++++++++++++++++++++++++++++++++
drivers/clocksource/sh_timer.c | 3
include/linux/sh_timer.h | 5
5 files changed, 285 insertions(+), 1 deletion(-)
--- 0006/arch/sh/Kconfig
+++ work/arch/sh/Kconfig 2008-11-25 20:37:29.000000000 +0900
@@ -397,6 +397,13 @@ config SH_TIMER_TMU
help
This enables build of the TMU system timer driver.
+config SH_TIMER_CMT
+ def_bool y
+ prompt "CMT timer support"
+ depends on SH_TIMER
+ help
+ This enables build of the CMT system timer driver.
+
config SH_TMU
def_bool y
prompt "TMU timer support"
--- 0005/drivers/clocksource/Makefile
+++ work/drivers/clocksource/Makefile 2008-11-25 20:37:29.000000000 +0900
@@ -4,3 +4,4 @@ obj-$(CONFIG_X86_PM_TIMER) += acpi_pm.o
obj-$(CONFIG_SCx200HR_TIMER) += scx200_hrt.o
obj-$(CONFIG_SH_TIMER) += sh_timer.o
obj-$(CONFIG_SH_TIMER_TMU) += sh_tmu.o
+obj-$(CONFIG_SH_TIMER_CMT) += sh_cmt.o timer_inc.o
--- /dev/null
+++ work/drivers/clocksource/sh_cmt.c 2008-11-25 20:40:58.000000000 +0900
@@ -0,0 +1,270 @@
+/*
+ * SuperH Timer Support - CMT
+ *
+ * Copyright (C) 2008 Magnus Damm
+ *
+ * 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
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/init.h>
+#include <linux/clocksource.h>
+#include <linux/clockchips.h>
+#include <linux/spinlock.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/ioport.h>
+#include <linux/io.h>
+
+#include <linux/sh_timer.h>
+#include <linux/timer_inc.h>
+
+struct sh_cmt_priv {
+ void __iomem *mapbase;
+ struct clk *clk;
+ struct timer_inc ti;
+ unsigned long ch_size;
+ unsigned long overflow_bit;
+ unsigned long clear_bits;
+ struct irqaction irqaction;
+ struct sh_timer_config cfg;
+};
+
+static DEFINE_SPINLOCK(sh_cmt_lock);
+
+/* The CMT is driven by the 32KHz r_clk, and the best resolution we
+ * can get is 4096 ticks per second by configuring the CMT to divide by 8.
+ */
+
+#define CMT_CLOCKRATE (32768 / 8)
+
+#define CMSTR -1 /* shared register */
+#define CMCSR 0 /* channel register */
+#define CMCNT 1 /* channel register */
+#define CMCOR 2 /* channel register */
+
+static inline unsigned long sh_cmt_read(struct sh_cmt_priv *p, int reg_nr)
+{
+ void __iomem *base = p->mapbase;
+ unsigned long offs;
+
+ if (reg_nr = CMSTR) {
+ offs = 0;
+ base -= p->cfg.channel_offset;
+ } else
+ offs = reg_nr;
+
+ if (p->cfg.flags & SH_TIMER_FLAGS_CMT_16BIT)
+ offs <<= 1;
+ else {
+ offs <<= 2;
+ if ((reg_nr = CMCNT) || (reg_nr = CMCOR))
+ return ioread32(base + offs);
+ }
+
+ return ioread16(base + offs);
+}
+
+static inline void sh_cmt_write(struct sh_cmt_priv *p, int reg_nr,
+ unsigned long value)
+{
+ void __iomem *base = p->mapbase;
+ unsigned long offs;
+
+ if (reg_nr = CMSTR) {
+ offs = 0;
+ base -= p->cfg.channel_offset;
+ } else
+ offs = reg_nr;
+
+ if (p->cfg.flags & SH_TIMER_FLAGS_CMT_16BIT)
+ offs <<= 1;
+ else {
+ offs <<= 2;
+ if ((reg_nr = CMCNT) || (reg_nr = CMCOR)) {
+ iowrite32(value, base + offs);
+ return;
+ }
+ }
+
+ iowrite16(value, base + offs);
+}
+
+static void sh_cmt_set_match(void *priv, unsigned long value)
+{
+ sh_cmt_write(priv, CMCOR, value);
+}
+
+static unsigned long sh_cmt_get_counter(void *priv, int *has_wrapped)
+{
+ struct sh_cmt_priv *p = priv;
+ unsigned long v1, v2, v3;
+
+ /* Make sure the timer value is stable. Stolen from acpi_pm.c */
+ do {
+ v1 = sh_cmt_read(p, CMCNT);
+ v2 = sh_cmt_read(p, CMCNT);
+ v3 = sh_cmt_read(p, CMCNT);
+ } while (unlikely((v1 > v2 && v1 < v3) || (v2 > v3 && v2 < v1)
+ || (v3 > v1 && v3 < v2)));
+
+ *has_wrapped = sh_cmt_read(p, CMCSR) & p->overflow_bit;
+ return v2;
+}
+
+static irqreturn_t sh_cmt_interrupt(int irq, void *dev_id)
+{
+ struct sh_cmt_priv *p = dev_id;
+ struct timer_inc *tip = &p->ti;
+
+ /* clear flags */
+ sh_cmt_write(p, CMCSR, sh_cmt_read(p, CMCSR) & p->clear_bits);
+
+ timer_inc_interrupt(tip);
+ return IRQ_HANDLED;
+}
+
+static void sh_cmt_start_stop_ch(struct sh_cmt_priv *p, int start)
+{
+ unsigned long flags, value;
+
+ /* start stop register shared by multiple timer channels */
+ spin_lock_irqsave(&sh_cmt_lock, flags);
+ value = sh_cmt_read(p, CMSTR);
+
+ if (start)
+ value |= 1 << p->cfg.timer_bit;
+ else
+ value &= ~(1 << p->cfg.timer_bit);
+
+ sh_cmt_write(p, CMSTR, value);
+ spin_unlock_irqrestore(&sh_cmt_lock, flags);
+}
+
+static int sh_cmt_start(void *priv)
+{
+ struct sh_cmt_priv *p = priv;
+ unsigned long n = p->ch_size;
+ int ret;
+
+ /* map memory */
+ p->mapbase = ioremap_nocache(p->cfg.base, p->cfg.channel_offset + n);
+ if (p->mapbase = NULL) {
+ pr_err("sh_cmt: failed to remap I/O memory\n");
+ ret = -ENXIO;
+ goto err0;
+ }
+
+ /* let mapbase point to our channel */
+ p->mapbase += p->cfg.channel_offset;
+
+ /* request irq using setup_irq() (too early for request_irq()) */
+ p->irqaction.name = p->cfg.name;
+ p->irqaction.handler = sh_cmt_interrupt;
+ p->irqaction.dev_id = p;
+ p->irqaction.flags = IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL;
+ p->irqaction.mask = CPU_MASK_NONE;
+ ret = setup_irq(p->cfg.irq, &p->irqaction);
+ if (ret) {
+ pr_err("sh_cmt: failed to request irq %d\n", p->cfg.irq);
+ goto err1;
+ }
+
+ /* enable clocks */
+ p->clk = clk_get(NULL, p->cfg.clk);
+ if (IS_ERR(p->clk)) {
+ pr_err("sh_cmt: cannot get clock \"%s\"\n", p->cfg.clk);
+ ret = PTR_ERR(p->clk);
+ goto err2;
+ }
+ clk_enable(p->clk);
+
+ /* make sure channel is disabled */
+ sh_cmt_start_stop_ch(p, 0);
+
+ /* configure channel, periodic mode and maximum timeout */
+ if (p->cfg.flags & SH_TIMER_FLAGS_CMT_16BIT)
+ sh_cmt_write(p, CMCSR, 0);
+ else
+ sh_cmt_write(p, CMCSR, 0x01a4);
+
+ sh_cmt_write(p, CMCOR, 0xffffffff);
+ sh_cmt_write(p, CMCNT, 0);
+
+ /* enable channel */
+ sh_cmt_start_stop_ch(p, 1);
+ return 0;
+
+ err2:
+ free_irq(p->cfg.irq, p);
+ err1:
+ iounmap(p->mapbase - p->cfg.channel_offset);
+ err0:
+ return ret;
+}
+
+static void sh_cmt_stop(void *priv)
+{
+ struct sh_cmt_priv *p = priv;
+
+ /* disable channel */
+ sh_cmt_start_stop_ch(p, 0);
+
+ /* let go of clocks */
+ clk_disable(p->clk);
+ clk_put(p->clk);
+
+ /* free up interrupt and memory map resources */
+ free_irq(p->cfg.irq, p);
+ iounmap(p->mapbase - p->cfg.channel_offset);
+}
+
+static struct timer_inc_ops sh_cmt_ops = {
+ .set_match = sh_cmt_set_match,
+ .get_counter = sh_cmt_get_counter,
+ .start = sh_cmt_start,
+ .stop = sh_cmt_stop,
+};
+
+static int sh_cmt_setup(void *priv, void *config)
+{
+ struct sh_cmt_priv *p = priv;
+ int width;
+
+ memset(p, 0, sizeof(*p));
+ memcpy(&p->cfg, config, sizeof(p->cfg));
+
+ if (p->cfg.flags & SH_TIMER_FLAGS_CMT_16BIT) {
+ width = 16;
+ p->ch_size = 6;
+ p->overflow_bit = 0x80;
+ p->clear_bits = ~0xc0;
+ } else {
+ width = 32;
+ p->ch_size = 12;
+ p->overflow_bit = 0x8000;
+ p->clear_bits = ~0xc000;
+ }
+
+ return timer_inc_register(&p->ti, p->cfg.name, p, width,
+ CMT_CLOCKRATE,
+ &sh_cmt_ops, 1, 1);
+}
+
+struct early_timer sh_cmt = {
+ .priv_size = sizeof(struct sh_cmt_priv),
+ .setup = sh_cmt_setup,
+};
--- 0005/drivers/clocksource/sh_timer.c
+++ work/drivers/clocksource/sh_timer.c 2008-11-25 20:38:57.000000000 +0900
@@ -25,6 +25,9 @@ struct early_timer *sh_timers_early[] =
#ifdef CONFIG_SH_TIMER_TMU
[SH_TIMER_TYPE_TMU] = &sh_tmu,
#endif
+#ifdef CONFIG_SH_TIMER_CMT
+ [SH_TIMER_TYPE_CMT] = &sh_cmt,
+#endif
};
static struct sh_timer_config *sh_timer_timers;
--- 0005/include/linux/sh_timer.h
+++ work/include/linux/sh_timer.h 2008-11-25 20:40:35.000000000 +0900
@@ -14,9 +14,12 @@ struct early_timer {
int (*setup)(void *priv, void *config);
};
-enum sh_timer_type { SH_TIMER_TYPE_TMU, SH_TIMER_TYPE_NR };
+enum sh_timer_type { SH_TIMER_TYPE_TMU, SH_TIMER_TYPE_CMT, SH_TIMER_TYPE_NR };
extern struct early_timer sh_tmu;
+extern struct early_timer sh_cmt;
+
+#define SH_TIMER_FLAGS_CMT_16BIT (1 << 0)
struct sh_timer_config {
enum sh_timer_type type;
^ permalink raw reply [flat|nested] only message in thread
only message in thread, other threads:[~2008-11-25 13:00 UTC | newest]
Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2008-11-25 13:00 [PATCH 4/8] sh: timer rewrite V2, cmt driver Magnus Damm
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).