21884a83b2
Pull timer core updates from Thomas Gleixner: "The timer changes contain: - posix timer code consolidation and fixes for odd corner cases - sched_clock implementation moved from ARM to core code to avoid duplication by other architectures - alarm timer updates - clocksource and clockevents unregistration facilities - clocksource/events support for new hardware - precise nanoseconds RTC readout (Xen feature) - generic support for Xen suspend/resume oddities - the usual lot of fixes and cleanups all over the place The parts which touch other areas (ARM/XEN) have been coordinated with the relevant maintainers. Though this results in an handful of trivial to solve merge conflicts, which we preferred over nasty cross tree merge dependencies. The patches which have been committed in the last few days are bug fixes plus the posix timer lot. The latter was in akpms queue and next for quite some time; they just got forgotten and Frederic collected them last minute." * 'timers-core-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip: (59 commits) hrtimer: Remove unused variable hrtimers: Move SMP function call to thread context clocksource: Reselect clocksource when watchdog validated high-res capability posix-cpu-timers: don't account cpu timer after stopped thread runtime accounting posix_timers: fix racy timer delta caching on task exit posix-timers: correctly get dying task time sample in posix_cpu_timer_schedule() selftests: add basic posix timers selftests posix_cpu_timers: consolidate expired timers check posix_cpu_timers: consolidate timer list cleanups posix_cpu_timer: consolidate expiry time type tick: Sanitize broadcast control logic tick: Prevent uncontrolled switch to oneshot mode tick: Make oneshot broadcast robust vs. CPU offlining x86: xen: Sync the CMOS RTC as well as the Xen wallclock x86: xen: Sync the wallclock when the system time is set timekeeping: Indicate that clock was set in the pvclock gtod notifier timekeeping: Pass flags instead of multiple bools to timekeeping_update() xen: Remove clock_was_set() call in the resume path hrtimers: Support resuming with two or more CPUs online (but stopped) timer: Fix jiffies wrap behavior of round_jiffies_common() ...
156 lines
4 KiB
C
156 lines
4 KiB
C
/*
|
|
* Copyright (C) 2012 Altera Corporation
|
|
* Copyright (c) 2011 Picochip Ltd., Jamie Iles
|
|
*
|
|
* Modified from mach-picoxcell/time.c
|
|
*
|
|
* 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.
|
|
*
|
|
* 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, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
#include <linux/dw_apb_timer.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/of_irq.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/sched_clock.h>
|
|
|
|
static void timer_get_base_and_rate(struct device_node *np,
|
|
void __iomem **base, u32 *rate)
|
|
{
|
|
struct clk *timer_clk;
|
|
struct clk *pclk;
|
|
|
|
*base = of_iomap(np, 0);
|
|
|
|
if (!*base)
|
|
panic("Unable to map regs for %s", np->name);
|
|
|
|
/*
|
|
* Not all implementations use a periphal clock, so don't panic
|
|
* if it's not present
|
|
*/
|
|
pclk = of_clk_get_by_name(np, "pclk");
|
|
if (!IS_ERR(pclk))
|
|
if (clk_prepare_enable(pclk))
|
|
pr_warn("pclk for %s is present, but could not be activated\n",
|
|
np->name);
|
|
|
|
timer_clk = of_clk_get_by_name(np, "timer");
|
|
if (IS_ERR(timer_clk))
|
|
goto try_clock_freq;
|
|
|
|
if (!clk_prepare_enable(timer_clk)) {
|
|
*rate = clk_get_rate(timer_clk);
|
|
return;
|
|
}
|
|
|
|
try_clock_freq:
|
|
if (of_property_read_u32(np, "clock-freq", rate) &&
|
|
of_property_read_u32(np, "clock-frequency", rate))
|
|
panic("No clock nor clock-frequency property for %s", np->name);
|
|
}
|
|
|
|
static void add_clockevent(struct device_node *event_timer)
|
|
{
|
|
void __iomem *iobase;
|
|
struct dw_apb_clock_event_device *ced;
|
|
u32 irq, rate;
|
|
|
|
irq = irq_of_parse_and_map(event_timer, 0);
|
|
if (irq == 0)
|
|
panic("No IRQ for clock event timer");
|
|
|
|
timer_get_base_and_rate(event_timer, &iobase, &rate);
|
|
|
|
ced = dw_apb_clockevent_init(0, event_timer->name, 300, iobase, irq,
|
|
rate);
|
|
if (!ced)
|
|
panic("Unable to initialise clockevent device");
|
|
|
|
dw_apb_clockevent_register(ced);
|
|
}
|
|
|
|
static void __iomem *sched_io_base;
|
|
static u32 sched_rate;
|
|
|
|
static void add_clocksource(struct device_node *source_timer)
|
|
{
|
|
void __iomem *iobase;
|
|
struct dw_apb_clocksource *cs;
|
|
u32 rate;
|
|
|
|
timer_get_base_and_rate(source_timer, &iobase, &rate);
|
|
|
|
cs = dw_apb_clocksource_init(300, source_timer->name, iobase, rate);
|
|
if (!cs)
|
|
panic("Unable to initialise clocksource device");
|
|
|
|
dw_apb_clocksource_start(cs);
|
|
dw_apb_clocksource_register(cs);
|
|
|
|
/*
|
|
* Fallback to use the clocksource as sched_clock if no separate
|
|
* timer is found. sched_io_base then points to the current_value
|
|
* register of the clocksource timer.
|
|
*/
|
|
sched_io_base = iobase + 0x04;
|
|
sched_rate = rate;
|
|
}
|
|
|
|
static u32 read_sched_clock(void)
|
|
{
|
|
return __raw_readl(sched_io_base);
|
|
}
|
|
|
|
static const struct of_device_id sptimer_ids[] __initconst = {
|
|
{ .compatible = "picochip,pc3x2-rtc" },
|
|
{ .compatible = "snps,dw-apb-timer-sp" },
|
|
{ /* Sentinel */ },
|
|
};
|
|
|
|
static void init_sched_clock(void)
|
|
{
|
|
struct device_node *sched_timer;
|
|
|
|
sched_timer = of_find_matching_node(NULL, sptimer_ids);
|
|
if (sched_timer) {
|
|
timer_get_base_and_rate(sched_timer, &sched_io_base,
|
|
&sched_rate);
|
|
of_node_put(sched_timer);
|
|
}
|
|
|
|
setup_sched_clock(read_sched_clock, 32, sched_rate);
|
|
}
|
|
|
|
static int num_called;
|
|
static void __init dw_apb_timer_init(struct device_node *timer)
|
|
{
|
|
switch (num_called) {
|
|
case 0:
|
|
pr_debug("%s: found clockevent timer\n", __func__);
|
|
add_clockevent(timer);
|
|
of_node_put(timer);
|
|
break;
|
|
case 1:
|
|
pr_debug("%s: found clocksource timer\n", __func__);
|
|
add_clocksource(timer);
|
|
of_node_put(timer);
|
|
init_sched_clock();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
num_called++;
|
|
}
|
|
CLOCKSOURCE_OF_DECLARE(pc3x2_timer, "picochip,pc3x2-timer", dw_apb_timer_init);
|
|
CLOCKSOURCE_OF_DECLARE(apb_timer, "snps,dw-apb-timer-osc", dw_apb_timer_init);
|