/* * linux/arch/arm/kernel/smp_twd.c * * Copyright (C) 2002 ARM Ltd. * All Rights Reserved * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* set up by the platform code */ static void __iomem *twd_base; static struct clk *twd_clk; static unsigned long twd_timer_rate; static bool common_setup_called; static DEFINE_PER_CPU(bool, percpu_setup_called); static struct clock_event_device __percpu **twd_evt; static int twd_ppi; static void twd_set_mode(enum clock_event_mode mode, struct clock_event_device *clk) { unsigned long ctrl; switch (mode) { case CLOCK_EVT_MODE_PERIODIC: ctrl = TWD_TIMER_CONTROL_ENABLE | TWD_TIMER_CONTROL_IT_ENABLE | TWD_TIMER_CONTROL_PERIODIC; __raw_writel(DIV_ROUND_CLOSEST(twd_timer_rate, HZ), twd_base + TWD_TIMER_LOAD); break; case CLOCK_EVT_MODE_ONESHOT: /* period set, and timer enabled in 'next_event' hook */ ctrl = TWD_TIMER_CONTROL_IT_ENABLE | TWD_TIMER_CONTROL_ONESHOT; break; case CLOCK_EVT_MODE_UNUSED: case CLOCK_EVT_MODE_SHUTDOWN: default: ctrl = 0; } __raw_writel(ctrl, twd_base + TWD_TIMER_CONTROL); } static int twd_set_next_event(unsigned long evt, struct clock_event_device *unused) { unsigned long ctrl = __raw_readl(twd_base + TWD_TIMER_CONTROL); ctrl |= TWD_TIMER_CONTROL_ENABLE; __raw_writel(evt, twd_base + TWD_TIMER_COUNTER); __raw_writel(ctrl, twd_base + TWD_TIMER_CONTROL); return 0; } /* * local_timer_ack: checks for a local timer interrupt. * * If a local timer interrupt has occurred, acknowledge and return 1. * Otherwise, return 0. */ static int twd_timer_ack(void) { if (__raw_readl(twd_base + TWD_TIMER_INTSTAT)) { __raw_writel(1, twd_base + TWD_TIMER_INTSTAT); return 1; } return 0; } static void twd_timer_stop(struct clock_event_device *clk) { twd_set_mode(CLOCK_EVT_MODE_UNUSED, clk); disable_percpu_irq(clk->irq); } #ifdef CONFIG_COMMON_CLK /* * Updates clockevent frequency when the cpu frequency changes. * Called on the cpu that is changing frequency with interrupts disabled. */ static void twd_update_frequency(void *new_rate) { twd_timer_rate = *((unsigned long *) new_rate); clockevents_update_freq(*__this_cpu_ptr(twd_evt), twd_timer_rate); } static int twd_rate_change(struct notifier_block *nb, unsigned long flags, void *data) { struct clk_notifier_data *cnd = data; /* * The twd clock events must be reprogrammed to account for the new * frequency. The timer is local to a cpu, so cross-call to the * changing cpu. */ if (flags == POST_RATE_CHANGE) smp_call_function(twd_update_frequency, (void *)&cnd->new_rate, 1); return NOTIFY_OK; } static struct notifier_block twd_clk_nb = { .notifier_call = twd_rate_change, }; static int twd_clk_init(void) { if (twd_evt && *__this_cpu_ptr(twd_evt) && !IS_ERR(twd_clk)) return clk_notifier_register(twd_clk, &twd_clk_nb); return 0; } core_initcall(twd_clk_init); #elif defined (CONFIG_CPU_FREQ) #include /* * Updates clockevent frequency when the cpu frequency changes. * Called on the cpu that is changing frequency with interrupts disabled. */ static void twd_update_frequency(void *data) { twd_timer_rate = clk_get_rate(twd_clk); clockevents_update_freq(*__this_cpu_ptr(twd_evt), twd_timer_rate); } static int twd_cpufreq_transition(struct notifier_block *nb, unsigned long state, void *data) { struct cpufreq_freqs *freqs = data; /* * The twd clock events must be reprogrammed to account for the new * frequency. The timer is local to a cpu, so cross-call to the * changing cpu. */ if (state == CPUFREQ_POSTCHANGE || state == CPUFREQ_RESUMECHANGE) smp_call_function_single(freqs->cpu, twd_update_frequency, NULL, 1); return NOTIFY_OK; }