/drivers/clocksource/sh_cmt.c
C | 709 lines | 506 code | 131 blank | 72 comment | 90 complexity | 42cd023284d2f44e46075a0d66a98594 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.0, AGPL-1.0
- /*
- * 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/platform_device.h>
- #include <linux/spinlock.h>
- #include <linux/interrupt.h>
- #include <linux/ioport.h>
- #include <linux/io.h>
- #include <linux/clk.h>
- #include <linux/irq.h>
- #include <linux/err.h>
- #include <linux/clocksource.h>
- #include <linux/clockchips.h>
- #include <linux/sh_timer.h>
- #include <linux/slab.h>
- struct sh_cmt_priv {
- void __iomem *mapbase;
- struct clk *clk;
- unsigned long width; /* 16 or 32 bit version of hardware block */
- unsigned long overflow_bit;
- unsigned long clear_bits;
- struct irqaction irqaction;
- struct platform_device *pdev;
- unsigned long flags;
- unsigned long match_value;
- unsigned long next_match_value;
- unsigned long max_match_value;
- unsigned long rate;
- spinlock_t lock;
- struct clock_event_device ced;
- struct clocksource cs;
- unsigned long total_cycles;
- };
- static DEFINE_SPINLOCK(sh_cmt_lock);
- #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)
- {
- struct sh_timer_config *cfg = p->pdev->dev.platform_data;
- void __iomem *base = p->mapbase;
- unsigned long offs;
- if (reg_nr == CMSTR) {
- offs = 0;
- base -= cfg->channel_offset;
- } else
- offs = reg_nr;
- if (p->width == 16)
- 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)
- {
- struct sh_timer_config *cfg = p->pdev->dev.platform_data;
- void __iomem *base = p->mapbase;
- unsigned long offs;
- if (reg_nr == CMSTR) {
- offs = 0;
- base -= cfg->channel_offset;
- } else
- offs = reg_nr;
- if (p->width == 16)
- offs <<= 1;
- else {
- offs <<= 2;
- if ((reg_nr == CMCNT) || (reg_nr == CMCOR)) {
- iowrite32(value, base + offs);
- return;
- }
- }
- iowrite16(value, base + offs);
- }
- static unsigned long sh_cmt_get_counter(struct sh_cmt_priv *p,
- int *has_wrapped)
- {
- unsigned long v1, v2, v3;
- int o1, o2;
- o1 = sh_cmt_read(p, CMCSR) & p->overflow_bit;
- /* Make sure the timer value is stable. Stolen from acpi_pm.c */
- do {
- o2 = o1;
- v1 = sh_cmt_read(p, CMCNT);
- v2 = sh_cmt_read(p, CMCNT);
- v3 = sh_cmt_read(p, CMCNT);
- o1 = sh_cmt_read(p, CMCSR) & p->overflow_bit;
- } while (unlikely((o1 != o2) || (v1 > v2 && v1 < v3)
- || (v2 > v3 && v2 < v1) || (v3 > v1 && v3 < v2)));
- *has_wrapped = o1;
- return v2;
- }
- static void sh_cmt_start_stop_ch(struct sh_cmt_priv *p, int start)
- {
- struct sh_timer_config *cfg = p->pdev->dev.platform_data;
- 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 << cfg->timer_bit;
- else
- value &= ~(1 << cfg->timer_bit);
- sh_cmt_write(p, CMSTR, value);
- spin_unlock_irqrestore(&sh_cmt_lock, flags);
- }
- static int sh_cmt_enable(struct sh_cmt_priv *p, unsigned long *rate)
- {
- int ret;
- /* enable clock */
- ret = clk_enable(p->clk);
- if (ret) {
- dev_err(&p->pdev->dev, "cannot enable clock\n");
- return ret;
- }
- /* make sure channel is disabled */
- sh_cmt_start_stop_ch(p, 0);
- /* configure channel, periodic mode and maximum timeout */
- if (p->width == 16) {
- *rate = clk_get_rate(p->clk) / 512;
- sh_cmt_write(p, CMCSR, 0x43);
- } else {
- *rate = clk_get_rate(p->clk) / 8;
- 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;
- }
- static void sh_cmt_disable(struct sh_cmt_priv *p)
- {
- /* disable channel */
- sh_cmt_start_stop_ch(p, 0);
- /* disable interrupts in CMT block */
- sh_cmt_write(p, CMCSR, 0);
- /* stop clock */
- clk_disable(p->clk);
- }
- /* private flags */
- #define FLAG_CLOCKEVENT (1 << 0)
- #define FLAG_CLOCKSOURCE (1 << 1)
- #define FLAG_REPROGRAM (1 << 2)
- #define FLAG_SKIPEVENT (1 << 3)
- #define FLAG_IRQCONTEXT (1 << 4)
- static void sh_cmt_clock_event_program_verify(struct sh_cmt_priv *p,
- int absolute)
- {
- unsigned long new_match;
- unsigned long value = p->next_match_value;
- unsigned long delay = 0;
- unsigned long now = 0;
- int has_wrapped;
- now = sh_cmt_get_counter(p, &has_wrapped);
- p->flags |= FLAG_REPROGRAM; /* force reprogram */
- if (has_wrapped) {
- /* we're competing with the interrupt handler.
- * -> let the interrupt handler reprogram the timer.
- * -> interrupt number two handles the event.
- */
- p->flags |= FLAG_SKIPEVENT;
- return;
- }
- if (absolute)
- now = 0;
- do {
- /* reprogram the timer hardware,
- * but don't save the new match value yet.
- */
- new_match = now + value + delay;
- if (new_match > p->max_match_value)
- new_match = p->max_match_value;
- sh_cmt_write(p, CMCOR, new_match);
- now = sh_cmt_get_counter(p, &has_wrapped);
- if (has_wrapped && (new_match > p->match_value)) {
- /* we are changing to a greater match value,
- * so this wrap must be caused by the counter
- * matching the old value.
- * -> first interrupt reprograms the timer.
- * -> interrupt number two handles the event.
- */
- p->flags |= FLAG_SKIPEVENT;
- break;
- }
- if (has_wrapped) {
-