blob: 167c9f7473e94cbf67d60fe92d66c6a0c662c757 [file] [log] [blame]
/*
* arch/arm/plat-ambarella/generic/timer.c
*
* Author: Anthony Ginger <hfjiang@ambarella.com>
*
* Copyright (C) 2004-2012, Ambarella, Inc.
*
* 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, or
* (at your option) any later version.
*
* 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
*
* Default clock is from APB.
*/
#include <linux/semaphore.h>
#include <linux/interrupt.h>
#include <linux/clockchips.h>
#include <linux/clk.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <asm/mach/time.h>
#include <asm/smp_twd.h>
#include <asm/sched_clock.h>
#include <asm/localtimer.h>
#include <plat/timer.h>
#include <plat/event.h>
static void __iomem *ce_base = NULL;
static void __iomem *ce_ctrl_reg = NULL;
static u32 ce_ctrl_offset = -1;
static void __iomem *cs_base = NULL;
static void __iomem *cs_ctrl_reg = NULL;
static u32 cs_ctrl_offset = -1;
#define AMBARELLA_TIMER_FREQ clk_get_rate(clk_get(NULL, "gclk_apb"))
#define AMBARELLA_TIMER_RATING (300)
static struct clock_event_device ambarella_clkevt;
static struct clocksource ambarella_clksrc;
static u32 ambarella_read_sched_clock(void);
/* ==========================================================================*/
struct amb_timer_pm_reg {
u32 clk_rate;
u32 ctrl_reg;
u32 status_reg;
u32 reload_reg;
u32 match1_reg;
u32 match2_reg;
};
static struct amb_timer_pm_reg amb_timer_ce_pm;
static struct amb_timer_pm_reg amb_timer_cs_pm;
static void ambarella_timer_suspend(u32 is_ce)
{
struct amb_timer_pm_reg *amb_timer_pm;
void __iomem *regbase;
void __iomem *ctrl_reg;
u32 ctrl_offset;
amb_timer_pm = is_ce ? &amb_timer_ce_pm : &amb_timer_cs_pm;
regbase = is_ce ? ce_base : cs_base;
ctrl_reg = is_ce ? ce_ctrl_reg : cs_ctrl_reg;
ctrl_offset = is_ce ? ce_ctrl_offset : cs_ctrl_offset;
amb_timer_pm->clk_rate = AMBARELLA_TIMER_FREQ;
amb_timer_pm->ctrl_reg = (amba_readl(ctrl_reg) >> ctrl_offset) & 0xf;
amb_timer_pm->status_reg = amba_readl(regbase + TIMER_STATUS_OFFSET);
amb_timer_pm->reload_reg = amba_readl(regbase + TIMER_RELOAD_OFFSET);
amb_timer_pm->match1_reg = amba_readl(regbase + TIMER_MATCH1_OFFSET);
amb_timer_pm->match2_reg = amba_readl(regbase + TIMER_MATCH2_OFFSET);
amba_clrbitsl(ctrl_reg, 0xf << ctrl_offset);
}
static void ambarella_timer_resume(u32 is_ce)
{
struct clock_event_device *clkevt = &ambarella_clkevt;
struct clocksource *clksrc = &ambarella_clksrc;
struct amb_timer_pm_reg *amb_timer_pm;
void __iomem *regbase;
void __iomem *ctrl_reg;
u32 ctrl_offset, clk_rate;
amb_timer_pm = is_ce ? &amb_timer_ce_pm : &amb_timer_cs_pm;
regbase = is_ce ? ce_base : cs_base;
ctrl_reg = is_ce ? ce_ctrl_reg : cs_ctrl_reg;
ctrl_offset = is_ce ? ce_ctrl_offset : cs_ctrl_offset;
amba_clrbitsl(ctrl_reg, 0xf << ctrl_offset);
amba_writel(regbase + TIMER_STATUS_OFFSET, amb_timer_pm->status_reg);
amba_writel(regbase + TIMER_RELOAD_OFFSET, amb_timer_pm->reload_reg);
amba_writel(regbase + TIMER_MATCH1_OFFSET, amb_timer_pm->match1_reg);
amba_writel(regbase + TIMER_MATCH2_OFFSET, amb_timer_pm->match2_reg);
clk_rate = AMBARELLA_TIMER_FREQ;
if (amb_timer_pm->clk_rate == clk_rate)
goto resume_exit;
amb_timer_pm->clk_rate = clk_rate;
if (is_ce) {
clockevents_update_freq(clkevt, clk_rate);
} else {
clocksource_change_rating(clksrc, 0);
__clocksource_updatefreq_hz(clksrc, clk_rate);
clocksource_change_rating(clksrc, AMBARELLA_TIMER_RATING);
}
resume_exit:
amba_setbitsl(ctrl_reg, amb_timer_pm->ctrl_reg << ctrl_offset);
}
/* ==========================================================================*/
static inline void ambarella_ce_timer_disable(void __iomem *ctrl_reg, u32 offs)
{
amba_clrbitsl(ctrl_reg, TIMER_CTRL_EN << offs);
}
static inline void ambarella_ce_timer_enable(void __iomem *ctrl_reg, u32 offs)
{
amba_setbitsl(ctrl_reg, TIMER_CTRL_EN << offs);
}
static inline void ambarella_ce_timer_misc(void __iomem *ctrl_reg, u32 offs)
{
amba_setbitsl(ctrl_reg, TIMER_CTRL_OF << offs);
amba_clrbitsl(ctrl_reg, TIMER_CTRL_CSL << offs);
}
static inline void ambarella_ce_timer_set_periodic
(void __iomem *base_reg, void __iomem *ctrl_reg, u32 offs)
{
u32 cnt = AMBARELLA_TIMER_FREQ / HZ;
amba_writel(base_reg + TIMER_STATUS_OFFSET, cnt);
amba_writel(base_reg + TIMER_RELOAD_OFFSET, cnt);
amba_writel(base_reg + TIMER_MATCH1_OFFSET, 0x0);
amba_writel(base_reg + TIMER_MATCH2_OFFSET, 0x0);
ambarella_ce_timer_misc(ctrl_reg, offs);
}
static inline void ambarella_ce_timer_set_oneshot
(void __iomem *base_reg, void __iomem *ctrl_reg, u32 offs)
{
amba_writel(base_reg + TIMER_STATUS_OFFSET, 0x0);
amba_writel(base_reg + TIMER_RELOAD_OFFSET, 0xffffffff);
amba_writel(base_reg + TIMER_MATCH1_OFFSET, 0x0);
amba_writel(base_reg + TIMER_MATCH2_OFFSET, 0x0);
ambarella_ce_timer_misc(ctrl_reg, offs);
}
static void inline ambarella_ce_set_mode(
void __iomem *base_reg, void __iomem *ctrl_reg, u32 ctrl_offset,
enum clock_event_mode mode, struct clock_event_device *clkevt)
{
switch (mode) {
case CLOCK_EVT_MODE_PERIODIC:
ambarella_ce_timer_disable(ctrl_reg, ctrl_offset);
ambarella_ce_timer_set_periodic(base_reg, ctrl_reg, ctrl_offset);
ambarella_ce_timer_enable(ctrl_reg, ctrl_offset);
break;
case CLOCK_EVT_MODE_ONESHOT:
ambarella_ce_timer_disable(ctrl_reg, ctrl_offset);
ambarella_ce_timer_set_oneshot(base_reg, ctrl_reg, ctrl_offset);
ambarella_ce_timer_enable(ctrl_reg, ctrl_offset);
break;
case CLOCK_EVT_MODE_UNUSED:
case CLOCK_EVT_MODE_SHUTDOWN:
ambarella_ce_timer_disable(ctrl_reg, ctrl_offset);
break;
case CLOCK_EVT_MODE_RESUME:
break;
}
pr_debug("%s:%d\n", __func__, mode);
}
static void inline ambarella_ce_set_next_event(void __iomem *base_reg,
unsigned long delta, struct clock_event_device *clkevt)
{
amba_writel(base_reg + TIMER_STATUS_OFFSET, delta);
}
/* ==========================================================================*/
static void ambarella_global_ce_set_mode(enum clock_event_mode mode,
struct clock_event_device *clkevt)
{
ambarella_ce_set_mode(ce_base, ce_ctrl_reg, ce_ctrl_offset, mode, clkevt);
}
static int ambarella_global_ce_set_next_event(unsigned long delta,
struct clock_event_device *clkevt)
{
ambarella_ce_set_next_event(ce_base, delta, clkevt);
return 0;
}
static void ambarella_global_ce_suspend(struct clock_event_device *dev)
{
ambarella_timer_suspend(1);
}
static void ambarella_global_ce_resume(struct clock_event_device *dev)
{
ambarella_timer_resume(1);
}
static struct clock_event_device ambarella_clkevt = {
.name = "ambarella-clkevt",
.features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
.rating = AMBARELLA_TIMER_RATING,
.set_next_event = ambarella_global_ce_set_next_event,
.set_mode = ambarella_global_ce_set_mode,
.mode = CLOCK_EVT_MODE_UNUSED,
.suspend = ambarella_global_ce_suspend,
.resume = ambarella_global_ce_resume,
};
static irqreturn_t ambarella_global_ce_interrupt(int irq, void *dev_id)
{
struct clock_event_device *clkevt = &ambarella_clkevt;
clkevt->event_handler(clkevt);
return IRQ_HANDLED;
}
static struct irqaction ambarella_ce_timer_irq = {
.name = "ambarella-ce-timer",
.flags = IRQF_TIMER | IRQF_TRIGGER_RISING,
.handler = ambarella_global_ce_interrupt,
};
/* ==========================================================================*/
static inline void ambarella_cs_timer_init(void)
{
amba_clrbitsl(cs_ctrl_reg, TIMER_CTRL_EN << cs_ctrl_offset);
amba_clrbitsl(cs_ctrl_reg, TIMER_CTRL_OF << cs_ctrl_offset);
amba_clrbitsl(cs_ctrl_reg, TIMER_CTRL_CSL << cs_ctrl_offset);
amba_writel(cs_base + TIMER_STATUS_OFFSET, 0xffffffff);
amba_writel(cs_base + TIMER_RELOAD_OFFSET, 0xffffffff);
amba_writel(cs_base + TIMER_MATCH1_OFFSET, 0x0);
amba_writel(cs_base + TIMER_MATCH2_OFFSET, 0x0);
amba_setbitsl(cs_ctrl_reg, TIMER_CTRL_EN << cs_ctrl_offset);
}
static cycle_t ambarella_cs_timer_read(struct clocksource *cs)
{
return (-(u32)amba_readl(cs_base + TIMER_STATUS_OFFSET));
}
static void ambarella_cs_timer_suspend(struct clocksource *dev)
{
ambarella_timer_suspend(0);
}
static void ambarella_cs_timer_resume(struct clocksource *dev)
{
ambarella_timer_resume(0);
}
static struct clocksource ambarella_clksrc = {
.name = "ambarella-cs-timer",
.rating = AMBARELLA_TIMER_RATING,
.read = ambarella_cs_timer_read,
.mask = CLOCKSOURCE_MASK(32),
.flags = CLOCK_SOURCE_IS_CONTINUOUS,
.suspend = ambarella_cs_timer_suspend,
.resume = ambarella_cs_timer_resume,
};
static u32 notrace ambarella_read_sched_clock(void)
{
return (-(u32)amba_readl(cs_base + TIMER_STATUS_OFFSET));
}
/* ==========================================================================*/
/*define a event struct to update timer*/
struct ambarella_timer_notifier {
struct notifier_block system_event;
struct semaphore system_event_sem;
} amba_timer_notifier;
static int ambarella_timer_system_event(struct notifier_block *nb,
unsigned long val, void *data)
{
unsigned long flags;
switch (val) {
case AMBA_EVENT_PRE_CPUFREQ:
down(&(amba_timer_notifier.system_event_sem));
break;
case AMBA_EVENT_POST_CPUFREQ:
local_irq_save(flags);
clockevents_update_freq(&ambarella_clkevt, AMBARELLA_TIMER_FREQ);
clocksource_change_rating(&ambarella_clksrc, 0);
__clocksource_updatefreq_hz(&ambarella_clksrc, AMBARELLA_TIMER_FREQ);
clocksource_change_rating(&ambarella_clksrc, AMBARELLA_TIMER_RATING);
local_irq_restore(flags);
up(&(amba_timer_notifier.system_event_sem));
break;
default:
break;
}
return NOTIFY_OK;
}
/* ==========================================================================*/
static const struct of_device_id clock_event_match[] __initconst = {
{ .compatible = "ambarella,clock-event" },
{ },
};
static const struct of_device_id clock_source_match[] __initconst = {
{ .compatible = "ambarella,clock-source" },
{ },
};
static void __init ambarella_clockevent_init(void)
{
struct device_node *np;
struct clock_event_device *clkevt;
int rval, irq;
np = of_find_matching_node(NULL, clock_event_match);
if (!np) {
pr_err("Can't find clock event node\n");
return;
}
ce_base = of_iomap(np, 0);
if (!ce_base) {
pr_err("%s: Failed to map event base\n", __func__);
return;
}
ce_ctrl_reg = of_iomap(np, 1);
if (!ce_ctrl_reg) {
pr_err("%s: Failed to map timer-ctrl base\n", __func__);
return;
}
irq = irq_of_parse_and_map(np, 0);
if (irq <= 0) {
pr_err("%s: Can't get irq\n", __func__);
return;
}
rval = of_property_read_u32(np, "ctrl-offset", &ce_ctrl_offset);
if (rval < 0) {
pr_err("%s: Can't get ctrl offset\n", __func__);
return;
}
of_node_put(np);
clkevt = &ambarella_clkevt;
clkevt->cpumask = cpumask_of(0);
clkevt->irq = irq;
rval = setup_irq(clkevt->irq, &ambarella_ce_timer_irq);
if (rval) {
printk(KERN_ERR "Failed to register timer IRQ: %d\n", rval);
BUG();
}
clockevents_config_and_register(clkevt, AMBARELLA_TIMER_FREQ, 1, 0xffffffff);
}
static void __init ambarella_clocksource_init(void)
{
struct device_node *np;
struct clocksource *clksrc;
int rval;
np = of_find_matching_node(NULL, clock_source_match);
if (!np) {
pr_err("Can't find clock source node\n");
return;
}
cs_base = of_iomap(np, 0);
if (!cs_base) {
pr_err("%s: Failed to map source base\n", __func__);
return;
}
cs_ctrl_reg = of_iomap(np, 1);
if (!cs_ctrl_reg) {
pr_err("%s: Failed to map timer-ctrl base\n", __func__);
return;
}
rval = of_property_read_u32(np, "ctrl-offset", &cs_ctrl_offset);
if (rval < 0) {
pr_err("%s: Can't get ctrl offset\n", __func__);
return;
}
of_node_put(np);
clksrc = &ambarella_clksrc;
ambarella_cs_timer_init();
clocksource_register_hz(clksrc, AMBARELLA_TIMER_FREQ);
pr_debug("%s: mult = %u, shift = %u\n",
clksrc->name, clksrc->mult, clksrc->shift);
setup_sched_clock(ambarella_read_sched_clock, 32, AMBARELLA_TIMER_FREQ);
/*add notifier to update the timer when the cpu frequency is changed*/
sema_init(&amba_timer_notifier.system_event_sem, 1);
amba_timer_notifier.system_event.notifier_call = ambarella_timer_system_event;
ambarella_register_event_notifier(&amba_timer_notifier.system_event);
}
#ifdef CONFIG_HAVE_ARM_TWD
static DEFINE_TWD_LOCAL_TIMER(twd_local_timer, AMBARELLA_VA_PT_WD_BASE, IRQ_LOCALTIMER);
static void __init ambarella_smp_twd_init(void)
{
int err = twd_local_timer_register(&twd_local_timer);
if (err)
pr_err("twd_local_timer_register failed %d\n", err);
}
#endif
#ifdef CONFIG_PLAT_AMBARELLA_LOCAL_TIMERS
struct ambarella_local_clkevt {
struct clock_event_device *evt;
void __iomem *base;
void __iomem *ctrl_reg;
u32 ctrl_offset;
char name[16];
};
static DEFINE_PER_CPU(struct ambarella_local_clkevt, percpu_clkevt);
static void __iomem *local_clkevt_base[NR_CPUS];
static void __iomem *local_clkevt_ctrl_reg[NR_CPUS];
static int local_clkevt_irq[NR_CPUS];
static u32 local_clkevt_ctrl_offset[NR_CPUS];
static struct irqaction local_clkevt_irqaction[NR_CPUS];
/* ==========================================================================*/
static void ambarella_local_ce_set_mode(enum clock_event_mode mode,
struct clock_event_device *clkevt)
{
struct ambarella_local_clkevt *local_clkevt;
void __iomem *base_reg;
void __iomem *ctrl_reg;
u32 ctrl_offset;
local_clkevt = this_cpu_ptr(&percpu_clkevt);
base_reg = local_clkevt->base;
ctrl_reg = local_clkevt->ctrl_reg;
ctrl_offset = local_clkevt->ctrl_offset;
ambarella_ce_set_mode(base_reg, ctrl_reg, ctrl_offset, mode, clkevt);
}
static int ambarella_local_ce_set_next_event(unsigned long delta,
struct clock_event_device *clkevt)
{
struct ambarella_local_clkevt *local_clkevt;
local_clkevt = this_cpu_ptr(&percpu_clkevt);
ambarella_ce_set_next_event(local_clkevt->base, delta, clkevt);
return 0;
}
static irqreturn_t ambarella_local_ce_interrupt(int irq, void *dev_id)
{
struct clock_event_device *clkevt = dev_id;
clkevt->event_handler(clkevt);
return IRQ_HANDLED;
}
static int __cpuinit ambarella_local_timer_setup(struct clock_event_device *evt)
{
struct ambarella_local_clkevt *local_clkevt;
unsigned int cpu = smp_processor_id();
int rval;
local_clkevt = this_cpu_ptr(&percpu_clkevt);
local_clkevt->evt = evt;
local_clkevt->base = local_clkevt_base[cpu];
local_clkevt->ctrl_reg = local_clkevt_ctrl_reg[cpu];
local_clkevt->ctrl_offset = local_clkevt_ctrl_offset[cpu];
sprintf(local_clkevt->name, "local_clkevt%d", cpu);
evt->name = local_clkevt->name;
evt->cpumask = cpumask_of(cpu);
evt->set_next_event = ambarella_local_ce_set_next_event;
evt->set_mode = ambarella_local_ce_set_mode;
evt->features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT;
evt->rating = AMBARELLA_TIMER_RATING + 30;
clockevents_config_and_register(evt, AMBARELLA_TIMER_FREQ, 1, 0xffffffff);
evt->irq = local_clkevt_irq[cpu];
local_clkevt_irqaction[cpu].name = local_clkevt->name;
local_clkevt_irqaction[cpu].flags =
IRQF_TIMER | IRQF_TRIGGER_RISING | IRQF_NOBALANCING;
local_clkevt_irqaction[cpu].handler = ambarella_local_ce_interrupt;
local_clkevt_irqaction[cpu].dev_id = evt;
irq_set_affinity(evt->irq, cpumask_of(cpu));
rval = setup_irq(evt->irq, &local_clkevt_irqaction[cpu]);
if (rval) {
printk(KERN_ERR "Failed to register local timer IRQ: %d\n", rval);
BUG();
}
return 0;
}
static void ambarella_local_timer_stop(struct clock_event_device *evt)
{
unsigned int cpu = smp_processor_id();
evt->set_mode(CLOCK_EVT_MODE_UNUSED, evt);
remove_irq(evt->irq, &local_clkevt_irqaction[cpu]);
}
static struct local_timer_ops ambarella_local_timer_ops __cpuinitdata = {
.setup = ambarella_local_timer_setup,
.stop = ambarella_local_timer_stop,
};
/* ==========================================================================*/
static const struct of_device_id local_clkevt_match[] __initconst = {
{ .compatible = "ambarella,local-clock-event" },
{ },
};
static void __init ambarella_local_clockevent_init(void)
{
struct device_node *np;
int i, rval;
np = of_find_matching_node(NULL, local_clkevt_match);
if (!np) {
pr_err("Can't find local clock event node\n");
return;
}
for (i = 0; i < NR_CPUS; i++) {
local_clkevt_base[i] = of_iomap(np, i * 2);
if (!local_clkevt_base[i]) {
pr_err("%s: Failed to map event base[%d]\n", __func__, i);
return;
}
local_clkevt_ctrl_reg[i] = of_iomap(np, i * 2 + 1);
if (!local_clkevt_ctrl_reg[i]) {
pr_err("%s: Failed to map event base[%d]\n", __func__, i);
return;
}
local_clkevt_irq[i] = irq_of_parse_and_map(np, i);
if (local_clkevt_irq[i] <= 0) {
pr_err("%s: Can't get irq\n", __func__);
return;
}
}
rval = of_property_read_u32_array(np, "ctrl-offset",
local_clkevt_ctrl_offset, NR_CPUS);
if (rval < 0) {
pr_err("%s: Can't get local ctrl offset\n", __func__);
return;
}
of_node_put(np);
local_timer_register(&ambarella_local_timer_ops);
}
#endif
void __init ambarella_timer_init(void)
{
ambarella_clockevent_init();
ambarella_clocksource_init();
#ifdef CONFIG_HAVE_ARM_TWD
ambarella_smp_twd_init();
#endif
#ifdef CONFIG_PLAT_AMBARELLA_LOCAL_TIMERS
ambarella_local_clockevent_init();
#endif
}