blob: 5ae39f50ed19fe5e00e64d82cbd67e8ac93b9deb [file] [log] [blame]
/*
* linux/arch/arm/mach-comcerto/time.c
*
* Copyright (C) 2012 Mindspeed Technologies, 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
*/
#include <linux/init.h>
#include <linux/smp.h>
#include <linux/irq.h>
#include <linux/export.h>
#include <linux/clocksource.h>
#include <linux/clockchips.h>
#include <asm/irq.h>
#include <asm/smp_twd.h>
#include <asm/sched_clock.h>
#include <asm/localtimer.h>
#include <asm/mach/time.h>
#include <mach/hardware.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <mach/comcerto-2000/clock.h>
/* Kernel needs a timer cadenced to 10ms */
#define COMCERTO_KERNEL_TIMER_VALUE (COMCERTO_AHBCLK / HZ)
#define machinecycles_to_usecs(ticks) (((ticks) * 10)/ (COMCERTO_AHBCLK/100000))
/*
* HARDWARE TIMER
* Can be use by any driver for its own need
*/
#define COMCERTO_TIMER_DEBUG 1
#define TIMER_STATUS_ENABLED (1 << 0)
#define TIMER_STATUS_FREE (1 << 1)
#define COMCERTO_MAX_TIMERS 6
/*
* HARDWARE TIMER
* Can be use by any driver for its own need
*/
/* List of available timers */
struct timer_hw {
u8 id;
u8 status;
struct comcerto_timer *t;
};
struct timer_hw timer_hw [COMCERTO_MAX_TIMERS] = {
{0, 0, NULL}, /* MSP timer */
{1, 0, NULL}, /* Clock event */
{2, 0, NULL}, /* CLock source + Sched clock */
{3, TIMER_STATUS_FREE, NULL},
{4, TIMER_STATUS_FREE, NULL},
{5, TIMER_STATUS_FREE, NULL}
};
static spinlock_t comcerto_timer_lock;
static unsigned long COMCERTO_AHBCLK;
static void comcerto_timer_disable(int t)
{
unsigned long flags;
spin_lock_irqsave(&comcerto_timer_lock, flags);
__comcerto_timer_disable(t);
spin_unlock_irqrestore(&comcerto_timer_lock, flags);
}
static unsigned long __comcerto_timer_get(int id)
{
if (id == 1)
{
return comcerto_timer1_get();
}
else if (id == 2)
{
return comcerto_timer2_get();
}
else if (id == 3)
{
return comcerto_timer3_get();
}
else if (id == 4)
{
return comcerto_timer4_get();
}
else if (id == 5)
{
return comcerto_timer5_get();
}
else
return 0;
}
static void __comcerto_timer_set(int id, unsigned long count)
{
if (id == 1)
{
comcerto_timer1_set(count);
}
else if (id == 2)
{
comcerto_timer2_set(0, count, 0);
}
else if (id == 3)
{
comcerto_timer3_set(0, count, 0);
}
else if (id == 4)
{
comcerto_timer4_set(count);
}
else if (id == 5)
{
comcerto_timer5_set(0, count, 0);
}
}
static void __timer_start(struct timer_hw *thw, unsigned long count)
{
thw->status |= TIMER_STATUS_ENABLED;
__comcerto_timer_set(thw->id, count);
__comcerto_timer_enable(thw->id);
}
static void timer_start(struct timer_hw *thw, unsigned long count)
{
unsigned long flags;
spin_lock_irqsave(&comcerto_timer_lock, flags);
__timer_start(thw, count);
spin_unlock_irqrestore(&comcerto_timer_lock, flags);
}
static void __timer_stop(struct timer_hw *thw)
{
__comcerto_timer_disable(thw->id);
thw->status &= ~ TIMER_STATUS_ENABLED;
}
static void timer_stop(struct timer_hw *thw)
{
unsigned long flags;
spin_lock_irqsave(&comcerto_timer_lock, flags);
__timer_stop(thw);
spin_unlock_irqrestore(&comcerto_timer_lock, flags);
}
static void __timer_free(struct timer_hw *thw)
{
thw->status |= TIMER_STATUS_FREE;
thw->t->thw = (unsigned long) NULL;
thw->t = NULL;
}
static void timer_free(struct timer_hw *thw)
{
unsigned long flags;
spin_lock_irqsave(&comcerto_timer_lock, flags);
__timer_free(thw);
spin_unlock_irqrestore(&comcerto_timer_lock, flags);
}
static struct timer_hw *__timer_alloc(struct comcerto_timer *t)
{
struct timer_hw *thw;
int i;
for (i = 0; i < COMCERTO_MAX_TIMERS; i++)
{
thw = &timer_hw[i];
if (thw->status & TIMER_STATUS_FREE)
{
thw->status &= ~ TIMER_STATUS_FREE;
t->thw = (unsigned long) thw;
thw->t = t;
goto found;
}
}
return NULL;
found:
return thw;
}
static struct timer_hw *timer_alloc(struct comcerto_timer *t)
{
struct timer_hw *thw;
unsigned long flags;
spin_lock_irqsave(&comcerto_timer_lock, flags);
thw = __timer_alloc(t);
spin_unlock_irqrestore(&comcerto_timer_lock, flags);
return thw;
}
int comcerto_timer_start(struct comcerto_timer *t)
{
struct timer_hw *thw;
thw = (struct timer_hw *) t->thw;
if (!thw) {
thw = timer_alloc(t);
if (!thw) {
printk (KERN_ERR "Comcerto timer: unable to allocate hardware timer\n");
goto err;
}
}
#ifdef COMCERTO_TIMER_DEBUG
if (thw->t != t) {
printk (KERN_ERR "Comcerto timer: timer corruption %#lx %#lx %#lx\n",
(unsigned long) thw, (unsigned long) thw->t, (unsigned long) t);
goto err;
}
#endif /* COMCERTO_TIMER_DEBUG */
/* timeout in us */
timer_start(thw, (t->timeout * (COMCERTO_AHBCLK / 100000)) / 10);
return 0;
err:
return -1;
}
int comcerto_timer_stop(struct comcerto_timer *t)
{
struct timer_hw *thw;
thw = (struct timer_hw *) t->thw;
if (!thw)
goto err;
#ifdef COMCERTO_TIMER_DEBUG
if (thw->t != t) {
printk (KERN_ERR "Comcerto timer: timer corruption %#lx %#lx %#lx\n",
(unsigned long) thw, (unsigned long) thw->t, (unsigned long) t);
goto err;
}
#endif /* COMCERTO_TIMER_DEBUG */
timer_stop(thw);
timer_free(thw);
return 0;
err:
return -1;
}
int comcerto_timer_read(struct comcerto_timer *t)
{
struct timer_hw *thw;
thw = (struct timer_hw *) t->thw;
if (!thw)
goto err;
#ifdef COMCERTO_TIMER_DEBUG
if (thw->t != t) {
printk (KERN_ERR "Comcerto timer: timer corruption %#lx %#lx %#lx\n",
(unsigned long) thw, (unsigned long) thw->t, (unsigned long) t);
goto err;
}
#endif /* COMCERTO_TIMER_DEBUG */
return ((__comcerto_timer_get(thw->id) * 10) / (COMCERTO_AHBCLK / 100000));
err:
return -1;
}
EXPORT_SYMBOL(comcerto_timer_start);
EXPORT_SYMBOL(comcerto_timer_stop);
EXPORT_SYMBOL(comcerto_timer_read);
int timer_hw_handler(u8 id)
{
struct timer_hw *thw;
unsigned long flags;
thw = &timer_hw[id];
spin_lock_irqsave(&comcerto_timer_lock, flags);
if (thw->status & TIMER_STATUS_ENABLED) {
struct comcerto_timer *t = thw->t;
if (t->flags & COMCERTO_TIMER_RUN_ONCE) {
__timer_stop(thw);
__timer_free(thw);
}
spin_unlock_irqrestore(&comcerto_timer_lock, flags);
t->func(t->data);
} else {
spin_unlock_irqrestore(&comcerto_timer_lock, flags);
goto err;
}
return 0;
err:
return -1;
}
struct comcerto_clock_device
{
int timer;
struct clock_event_device device;
};
/* This function must be called with interrupts disabled. We need to start as close from zero
* as possible - if we don't clear counter before setting high bound value we'll have innacurate
* results in ONESHOT mode.
*/
static int comcerto_clock_set_next_event(unsigned long evt, struct clock_event_device *dev)
{
struct comcerto_clock_device *clock = container_of(dev, typeof(*clock), device);
unsigned long flags;
spin_lock_irqsave(&comcerto_timer_lock, flags);
/* now write correct bound and clear interrupt status.
Writing high bound register automatically resets count to low bound value.
For very small bound values it's possible that we ack the interrupt _after_ the timer has already expired,
this is not very serious because the interrupt will be asserted again in a very short time */
__comcerto_timer_set(clock->timer, evt);
comcerto_timer_ack(clock->timer);
/* enable interrupt for ONESHOT mode */
if (dev->mode == CLOCK_EVT_MODE_ONESHOT)
__comcerto_timer_enable(clock->timer);
spin_unlock_irqrestore(&comcerto_timer_lock, flags);
return 0;
}
static void comcerto_clock_set_mode(enum clock_event_mode mode, struct clock_event_device *dev)
{
struct comcerto_clock_device *clock = container_of(dev, typeof(*clock), device);
unsigned long flags;
/* This timer is true PERIODIC in hardware, to emulate ONESHOT we need to keep it masked
* until set_next_event() call. Enable interrupt only for PERIODIC mode.
*/
spin_lock_irqsave(&comcerto_timer_lock, flags);
if (mode != CLOCK_EVT_MODE_PERIODIC)
__comcerto_timer_disable(clock->timer);
else {
__comcerto_timer_set(clock->timer, COMCERTO_KERNEL_TIMER_VALUE);
__comcerto_timer_enable(clock->timer);
}
spin_unlock_irqrestore(&comcerto_timer_lock, flags);
}
static struct comcerto_clock_device clock =
{
.timer = 1,
.device =
{
.name = "timer1",
.features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
.rating = 200,
.shift = 31,
.set_mode = comcerto_clock_set_mode,
.set_next_event = comcerto_clock_set_next_event,
},
};
static cycle_t comcerto_timer2_read(struct clocksource *cs)
{
return comcerto_timer2_get();
}
static struct clocksource clocksource =
{
.name = "timer2",
.rating = 200,
.read = comcerto_timer2_read,
.mask = CLOCKSOURCE_MASK(32),
.shift = 28,
.flags = CLOCK_SOURCE_IS_CONTINUOUS,
};
/***********************************************************
* KERNEL TIMER *
* (Functions called by the kernel) *
***********************************************************/
#define irq_to_timer(irq) (IRQ_TIMER0 - irq)
#define timer_mask(timer) (1 << timer)
#ifdef CONFIG_LOCAL_TIMERS
/*
* Setup the local clock events for a CPU.
*/
int __cpuinit local_timer_setup(struct clock_event_device *evt)
{
evt->irq = IRQ_LOCALTIMER;
twd_timer_setup(evt);
return 0;
}
#endif
/*
* Routine to catch timer interrupts
*/
static irqreturn_t comcerto_timer1_interrupt(int irq, void *dev_id)
{
u32 status;
struct comcerto_clock_device *clock = dev_id;
struct clock_event_device *dev = &clock->device;
status = __raw_readl(COMCERTO_TIMER_STATUS) & __raw_readl(COMCERTO_TIMER_IRQ_MASK);
/* timer1 expired */
if (status & timer_mask(clock->timer)) {
/* we need to disable interrupt to simulate ONESHOT mode,
do it before clearing the interrupt to avoid race */
if (dev->mode != CLOCK_EVT_MODE_PERIODIC)
comcerto_timer_disable(clock->timer);
comcerto_timer_ack(clock->timer);
dev->event_handler(dev);
return IRQ_HANDLED;
}
return IRQ_NONE;
}
irqreturn_t comcerto_timerN_interrupt(int irq, void *dev_id)
{
u32 status;
int timer = irq_to_timer(irq);
status = __raw_readl(COMCERTO_TIMER_STATUS);
if (status & timer_mask(timer)) {
comcerto_timer_ack(timer);
timer_hw_handler(timer);
return IRQ_HANDLED;
}
return IRQ_NONE;
}
static struct irqaction comcerto_timer1_irq = {
.name = "timer1",
.flags = IRQF_DISABLED | IRQF_TIMER,
.handler = comcerto_timer1_interrupt,
.dev_id = &clock,
};
static struct irqaction comcerto_timer3_irq = {
.name = "timer3",
.flags = IRQF_DISABLED | IRQF_TIMER,
.handler = comcerto_timerN_interrupt,
};
static struct irqaction comcerto_timer4_irq = {
.name = "timer4",
.flags = IRQF_DISABLED | IRQF_TIMER,
.handler = comcerto_timerN_interrupt,
};
static struct irqaction comcerto_timer5_irq = {
.name = "timer5",
.flags = IRQF_DISABLED | IRQF_TIMER,
.handler = comcerto_timerN_interrupt,
};
static DEFINE_CLOCK_DATA(cd);
unsigned long long notrace sched_clock(void)
{
u32 cyc = comcerto_timer2_get();
return cyc_to_sched_clock(&cd, cyc, (u32)~0);
}
static void notrace comcerto_update_sched_clock(void)
{
u32 cyc;
cyc = comcerto_timer2_get();
update_sched_clock(&cd, cyc, (u32)~0);
}
void comcerto_hwtimer_init(void)
{
/*
* DO NOT MODIFY THE CONFIGURATION OF TIMER0
* It is used by the MSP
*/
struct clk *clk_axi;
/* Initializing the clock structure Tree declared/defined in clock.c */
clk_init();
spin_lock_init(&comcerto_timer_lock);
/* Mask all the timers except timer0 */
comcerto_timer_disable(1);
comcerto_timer_disable(2);
comcerto_timer_disable(3);
comcerto_timer_disable(4);
comcerto_timer_disable(5);
/* Get the AXI clock , to be used for AHB clock value*/
clk_axi = clk_get(NULL,"axi");
if (IS_ERR(clk_axi)){
pr_err("%s: Unable to obtain axi clock: %ld\n",__func__,PTR_ERR(clk_axi));
/* System cannot proceed from here */
BUG();
}
/* Enable the AXI clock */
if (clk_enable(clk_axi)){
pr_err("%s: Unable to enable axi clock\n",__func__);
/* System cannot proceed from here */
BUG();
}
/* Get the AXI clock rate value , which will assigned to AHB clock value */
COMCERTO_AHBCLK = clk_get_rate(clk_axi);
__comcerto_timer_set(2, 0xffffffff);
clocksource.mult = clocksource_hz2mult(COMCERTO_AHBCLK, clocksource.shift);
clocksource_register(&clocksource);
init_sched_clock(&cd, comcerto_update_sched_clock, 32, COMCERTO_AHBCLK);
clock.device.mult = div_sc(COMCERTO_AHBCLK, NSEC_PER_SEC, clock.device.shift);
clock.device.max_delta_ns = clockevent_delta2ns(0x3fffffff, &clock.device);
clock.device.min_delta_ns = clockevent_delta2ns(1, &clock.device);
clock.device.cpumask = cpumask_of(0);
clockevents_register_device(&clock.device);
/* Clear all the timers except timer0 */
__raw_writel(COMCERTO_TIMER_CSP, COMCERTO_TIMER_STATUS);
/* Register interrupt handler for interrupt on IRQ_TIMERB*/
irq_set_irq_type(IRQ_TIMER1, IRQ_TYPE_EDGE_RISING);
irq_set_irq_type(IRQ_TIMER3, IRQ_TYPE_EDGE_RISING);
irq_set_irq_type(IRQ_TIMER4, IRQ_TYPE_EDGE_RISING);
irq_set_irq_type(IRQ_TIMER5, IRQ_TYPE_EDGE_RISING);
setup_irq(IRQ_TIMER1, &comcerto_timer1_irq);
setup_irq(IRQ_TIMER3, &comcerto_timer3_irq);
setup_irq(IRQ_TIMER4, &comcerto_timer4_irq);
setup_irq(IRQ_TIMER5, &comcerto_timer5_irq);
}
static void __init comcerto_timer_init(void)
{
#ifdef CONFIG_LOCAL_TIMERS
twd_base = (void *)COMCERTO_TWD_VADDR;
#endif
comcerto_hwtimer_init();
}
struct sys_timer comcerto_timer = {
.init = comcerto_timer_init,
};