at v3.6 191 lines 4.7 kB view raw
1/* 2 * linux/arch/arm/common/timer-sp.c 3 * 4 * Copyright (C) 1999 - 2003 ARM Limited 5 * Copyright (C) 2000 Deep Blue Solutions Ltd 6 * 7 * This program is free software; you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License as published by 9 * the Free Software Foundation; either version 2 of the License, or 10 * (at your option) any later version. 11 * 12 * This program is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU General Public License for more details. 16 * 17 * You should have received a copy of the GNU General Public License 18 * along with this program; if not, write to the Free Software 19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 */ 21#include <linux/clk.h> 22#include <linux/clocksource.h> 23#include <linux/clockchips.h> 24#include <linux/err.h> 25#include <linux/interrupt.h> 26#include <linux/irq.h> 27#include <linux/io.h> 28 29#include <asm/sched_clock.h> 30#include <asm/hardware/arm_timer.h> 31 32static long __init sp804_get_clock_rate(const char *name) 33{ 34 struct clk *clk; 35 long rate; 36 int err; 37 38 clk = clk_get_sys("sp804", name); 39 if (IS_ERR(clk)) { 40 pr_err("sp804: %s clock not found: %d\n", name, 41 (int)PTR_ERR(clk)); 42 return PTR_ERR(clk); 43 } 44 45 err = clk_prepare(clk); 46 if (err) { 47 pr_err("sp804: %s clock failed to prepare: %d\n", name, err); 48 clk_put(clk); 49 return err; 50 } 51 52 err = clk_enable(clk); 53 if (err) { 54 pr_err("sp804: %s clock failed to enable: %d\n", name, err); 55 clk_unprepare(clk); 56 clk_put(clk); 57 return err; 58 } 59 60 rate = clk_get_rate(clk); 61 if (rate < 0) { 62 pr_err("sp804: %s clock failed to get rate: %ld\n", name, rate); 63 clk_disable(clk); 64 clk_unprepare(clk); 65 clk_put(clk); 66 } 67 68 return rate; 69} 70 71static void __iomem *sched_clock_base; 72 73static u32 sp804_read(void) 74{ 75 return ~readl_relaxed(sched_clock_base + TIMER_VALUE); 76} 77 78void __init __sp804_clocksource_and_sched_clock_init(void __iomem *base, 79 const char *name, 80 int use_sched_clock) 81{ 82 long rate = sp804_get_clock_rate(name); 83 84 if (rate < 0) 85 return; 86 87 /* setup timer 0 as free-running clocksource */ 88 writel(0, base + TIMER_CTRL); 89 writel(0xffffffff, base + TIMER_LOAD); 90 writel(0xffffffff, base + TIMER_VALUE); 91 writel(TIMER_CTRL_32BIT | TIMER_CTRL_ENABLE | TIMER_CTRL_PERIODIC, 92 base + TIMER_CTRL); 93 94 clocksource_mmio_init(base + TIMER_VALUE, name, 95 rate, 200, 32, clocksource_mmio_readl_down); 96 97 if (use_sched_clock) { 98 sched_clock_base = base; 99 setup_sched_clock(sp804_read, 32, rate); 100 } 101} 102 103 104static void __iomem *clkevt_base; 105static unsigned long clkevt_reload; 106 107/* 108 * IRQ handler for the timer 109 */ 110static irqreturn_t sp804_timer_interrupt(int irq, void *dev_id) 111{ 112 struct clock_event_device *evt = dev_id; 113 114 /* clear the interrupt */ 115 writel(1, clkevt_base + TIMER_INTCLR); 116 117 evt->event_handler(evt); 118 119 return IRQ_HANDLED; 120} 121 122static void sp804_set_mode(enum clock_event_mode mode, 123 struct clock_event_device *evt) 124{ 125 unsigned long ctrl = TIMER_CTRL_32BIT | TIMER_CTRL_IE; 126 127 writel(ctrl, clkevt_base + TIMER_CTRL); 128 129 switch (mode) { 130 case CLOCK_EVT_MODE_PERIODIC: 131 writel(clkevt_reload, clkevt_base + TIMER_LOAD); 132 ctrl |= TIMER_CTRL_PERIODIC | TIMER_CTRL_ENABLE; 133 break; 134 135 case CLOCK_EVT_MODE_ONESHOT: 136 /* period set, and timer enabled in 'next_event' hook */ 137 ctrl |= TIMER_CTRL_ONESHOT; 138 break; 139 140 case CLOCK_EVT_MODE_UNUSED: 141 case CLOCK_EVT_MODE_SHUTDOWN: 142 default: 143 break; 144 } 145 146 writel(ctrl, clkevt_base + TIMER_CTRL); 147} 148 149static int sp804_set_next_event(unsigned long next, 150 struct clock_event_device *evt) 151{ 152 unsigned long ctrl = readl(clkevt_base + TIMER_CTRL); 153 154 writel(next, clkevt_base + TIMER_LOAD); 155 writel(ctrl | TIMER_CTRL_ENABLE, clkevt_base + TIMER_CTRL); 156 157 return 0; 158} 159 160static struct clock_event_device sp804_clockevent = { 161 .features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT, 162 .set_mode = sp804_set_mode, 163 .set_next_event = sp804_set_next_event, 164 .rating = 300, 165 .cpumask = cpu_all_mask, 166}; 167 168static struct irqaction sp804_timer_irq = { 169 .name = "timer", 170 .flags = IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL, 171 .handler = sp804_timer_interrupt, 172 .dev_id = &sp804_clockevent, 173}; 174 175void __init sp804_clockevents_init(void __iomem *base, unsigned int irq, 176 const char *name) 177{ 178 struct clock_event_device *evt = &sp804_clockevent; 179 long rate = sp804_get_clock_rate(name); 180 181 if (rate < 0) 182 return; 183 184 clkevt_base = base; 185 clkevt_reload = DIV_ROUND_CLOSEST(rate, HZ); 186 evt->name = name; 187 evt->irq = irq; 188 189 setup_irq(irq, &sp804_timer_irq); 190 clockevents_config_and_register(evt, rate, 0xf, 0xffffffff); 191}