| /* |
| * arch/arm/plat-ambarella/generic/timer.c |
| * |
| * History: |
| * 2006/12/27 - [Charles Chiou] created file |
| * 2008/01/08 - [Anthony Ginger] Rewrite for 2.6.28 |
| * |
| * Copyright (C) 2004-2009, 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. |
| * Timer 3 is used as clock_event_device. |
| * Timer 2 is used as free-running clocksource |
| * if CONFIG_AMBARELLA_SUPPORT_CLOCKSOURCE is defined |
| * Timer 1 is not used. |
| */ |
| |
| #include <linux/interrupt.h> |
| #include <linux/clockchips.h> |
| #include <linux/delay.h> |
| |
| #include <asm/mach/irq.h> |
| #include <asm/mach/time.h> |
| #include <asm/localtimer.h> |
| #include <asm/sched_clock.h> |
| |
| #include <mach/hardware.h> |
| #include <plat/timer.h> |
| |
| #include <hal/hal.h> |
| |
| /* ==========================================================================*/ |
| #define AMBARELLA_TIMER_FREQ (get_apb_bus_freq_hz()) |
| #define AMBARELLA_TIMER_RATING (300) |
| |
| struct ambarella_timer_pm_info { |
| u32 timer_clk; |
| u32 timer_ctr_reg; |
| u32 timer_ce_status_reg; |
| u32 timer_ce_reload_reg; |
| u32 timer_ce_match1_reg; |
| u32 timer_ce_match2_reg; |
| #if defined(CONFIG_AMBARELLA_SUPPORT_CLOCKSOURCE) |
| u32 timer_cs_status_reg; |
| u32 timer_cs_reload_reg; |
| u32 timer_cs_match1_reg; |
| u32 timer_cs_match2_reg; |
| #endif |
| }; |
| struct ambarella_timer_pm_info ambarella_timer_pm; |
| static struct clock_event_device ambarella_clkevt; |
| static struct irqaction ambarella_ce_timer_irq; |
| |
| /* ==========================================================================*/ |
| #define AMBARELLA_CE_TIMER_STATUS_REG TIMER3_STATUS_REG |
| #define AMBARELLA_CE_TIMER_RELOAD_REG TIMER3_RELOAD_REG |
| #define AMBARELLA_CE_TIMER_MATCH1_REG TIMER3_MATCH1_REG |
| #define AMBARELLA_CE_TIMER_MATCH2_REG TIMER3_MATCH2_REG |
| #define AMBARELLA_CE_TIMER_IRQ TIMER3_IRQ |
| #define AMBARELLA_CE_TIMER_CTR_EN TIMER_CTR_EN3 |
| #define AMBARELLA_CE_TIMER_CTR_OF TIMER_CTR_OF3 |
| #define AMBARELLA_CE_TIMER_CTR_CSL TIMER_CTR_CSL3 |
| #define AMBARELLA_CE_TIMER_CTR_MASK 0x00000F00 |
| |
| static inline void ambarella_ce_timer_disable(void) |
| { |
| amba_clrbitsl(TIMER_CTR_REG, AMBARELLA_CE_TIMER_CTR_EN); |
| } |
| |
| static inline void ambarella_ce_timer_enable(void) |
| { |
| amba_setbitsl(TIMER_CTR_REG, AMBARELLA_CE_TIMER_CTR_EN); |
| } |
| |
| static inline void ambarella_ce_timer_misc(void) |
| { |
| amba_setbitsl(TIMER_CTR_REG, AMBARELLA_CE_TIMER_CTR_OF); |
| amba_clrbitsl(TIMER_CTR_REG, AMBARELLA_CE_TIMER_CTR_CSL); |
| } |
| |
| static inline void ambarella_ce_timer_set_periodic(void) |
| { |
| u32 cnt; |
| |
| cnt = AMBARELLA_TIMER_FREQ / HZ; |
| amba_writel(AMBARELLA_CE_TIMER_STATUS_REG, cnt); |
| amba_writel(AMBARELLA_CE_TIMER_RELOAD_REG, cnt); |
| amba_writel(AMBARELLA_CE_TIMER_MATCH1_REG, 0x0); |
| amba_writel(AMBARELLA_CE_TIMER_MATCH2_REG, 0x0); |
| ambarella_ce_timer_misc(); |
| } |
| |
| static inline void ambarella_ce_timer_set_oneshot(void) |
| { |
| amba_writel(AMBARELLA_CE_TIMER_STATUS_REG, 0x0); |
| amba_writel(AMBARELLA_CE_TIMER_RELOAD_REG, 0xffffffff); |
| amba_writel(AMBARELLA_CE_TIMER_MATCH1_REG, 0x0); |
| amba_writel(AMBARELLA_CE_TIMER_MATCH2_REG, 0x0); |
| ambarella_ce_timer_misc(); |
| } |
| |
| static void ambarella_ce_timer_set_mode(enum clock_event_mode mode, |
| struct clock_event_device *evt) |
| { |
| switch (mode) { |
| case CLOCK_EVT_MODE_PERIODIC: |
| ambarella_ce_timer_disable(); |
| ambarella_ce_timer_set_periodic(); |
| ambarella_ce_timer_enable(); |
| break; |
| case CLOCK_EVT_MODE_ONESHOT: |
| ambarella_ce_timer_disable(); |
| ambarella_ce_timer_set_oneshot(); |
| ambarella_ce_timer_enable(); |
| break; |
| case CLOCK_EVT_MODE_UNUSED: |
| remove_irq(ambarella_clkevt.irq, &ambarella_ce_timer_irq); |
| case CLOCK_EVT_MODE_SHUTDOWN: |
| ambarella_ce_timer_disable(); |
| break; |
| case CLOCK_EVT_MODE_RESUME: |
| break; |
| } |
| pr_debug("%s:%d\n", __func__, mode); |
| } |
| |
| static int ambarella_ce_timer_set_next_event(unsigned long delta, |
| struct clock_event_device *dev) |
| { |
| amba_writel(AMBARELLA_CE_TIMER_STATUS_REG, delta); |
| |
| return 0; |
| } |
| |
| 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_ce_timer_set_next_event, |
| .set_mode = ambarella_ce_timer_set_mode, |
| .mode = CLOCK_EVT_MODE_UNUSED, |
| .irq = AMBARELLA_CE_TIMER_IRQ, |
| }; |
| |
| static irqreturn_t ambarella_ce_timer_interrupt(int irq, void *dev_id) |
| { |
| ambarella_clkevt.event_handler(&ambarella_clkevt); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static struct irqaction ambarella_ce_timer_irq = { |
| .name = "ambarella-ce-timer", |
| .flags = IRQF_DISABLED | IRQF_TIMER | IRQF_TRIGGER_RISING, |
| .handler = ambarella_ce_timer_interrupt, |
| }; |
| |
| /* ==========================================================================*/ |
| #if defined(CONFIG_AMBARELLA_SUPPORT_CLOCKSOURCE) |
| #define AMBARELLA_CS_TIMER_STATUS_REG TIMER2_STATUS_REG |
| #define AMBARELLA_CS_TIMER_RELOAD_REG TIMER2_RELOAD_REG |
| #define AMBARELLA_CS_TIMER_MATCH1_REG TIMER2_MATCH1_REG |
| #define AMBARELLA_CS_TIMER_MATCH2_REG TIMER2_MATCH2_REG |
| #define AMBARELLA_CS_TIMER_CTR_EN TIMER_CTR_EN2 |
| #define AMBARELLA_CS_TIMER_CTR_OF TIMER_CTR_OF2 |
| #define AMBARELLA_CS_TIMER_CTR_CSL TIMER_CTR_CSL2 |
| #define AMBARELLA_CS_TIMER_CTR_MASK 0x000000F0 |
| |
| static inline void ambarella_cs_timer_init(void) |
| { |
| amba_clrbitsl(TIMER_CTR_REG, AMBARELLA_CS_TIMER_CTR_EN); |
| amba_clrbitsl(TIMER_CTR_REG, AMBARELLA_CS_TIMER_CTR_OF); |
| amba_clrbitsl(TIMER_CTR_REG, AMBARELLA_CS_TIMER_CTR_CSL); |
| amba_writel(AMBARELLA_CS_TIMER_STATUS_REG, 0xffffffff); |
| amba_writel(AMBARELLA_CS_TIMER_RELOAD_REG, 0xffffffff); |
| amba_writel(AMBARELLA_CS_TIMER_MATCH1_REG, 0x0); |
| amba_writel(AMBARELLA_CS_TIMER_MATCH2_REG, 0x0); |
| amba_setbitsl(TIMER_CTR_REG, AMBARELLA_CS_TIMER_CTR_EN); |
| } |
| |
| static cycle_t ambarella_cs_timer_read(struct clocksource *cs) |
| { |
| return (-(u32)amba_readl(AMBARELLA_CS_TIMER_STATUS_REG)); |
| } |
| |
| static struct clocksource ambarella_cs_timer_clksrc = { |
| .name = "ambarella-cs-timer", |
| .rating = AMBARELLA_TIMER_RATING, |
| .read = ambarella_cs_timer_read, |
| .mask = CLOCKSOURCE_MASK(32), |
| .mult = 2236962133u, |
| .shift = 27, |
| .flags = CLOCK_SOURCE_IS_CONTINUOUS, |
| }; |
| |
| #if defined(CONFIG_HAVE_SCHED_CLOCK) |
| static DEFINE_CLOCK_DATA(ambarella_sched_clock); |
| |
| unsigned long long notrace sched_clock(void) |
| { |
| return cyc_to_sched_clock(&ambarella_sched_clock, |
| (-(u32)amba_readl(AMBARELLA_CS_TIMER_STATUS_REG)), (u32)~0); |
| } |
| |
| static void notrace ambarella_update_sched_clock(void) |
| { |
| update_sched_clock(&ambarella_sched_clock, |
| (-(u32)amba_readl(AMBARELLA_CS_TIMER_STATUS_REG)), |
| (u32)~0); |
| } |
| #endif |
| #endif |
| |
| /* ==========================================================================*/ |
| static void __init ambarella_timer_init(void) |
| { |
| #ifdef CONFIG_LOCAL_TIMERS |
| twd_base = __io(AMBARELLA_VA_PT_WD_BASE); |
| #endif |
| |
| #if defined(CONFIG_AMBARELLA_SUPPORT_CLOCKSOURCE) |
| ambarella_cs_timer_init(); |
| clocks_calc_mult_shift(&ambarella_cs_timer_clksrc.mult, |
| &ambarella_cs_timer_clksrc.shift, |
| AMBARELLA_TIMER_FREQ, NSEC_PER_SEC, 60); |
| pr_debug("%s: mult = %u, shift = %u\n", |
| ambarella_cs_timer_clksrc.name, |
| ambarella_cs_timer_clksrc.mult, |
| ambarella_cs_timer_clksrc.shift); |
| clocksource_register(&ambarella_cs_timer_clksrc); |
| #if defined(CONFIG_HAVE_SCHED_CLOCK) |
| init_fixed_sched_clock(&ambarella_sched_clock, |
| ambarella_update_sched_clock, 32, AMBARELLA_TIMER_FREQ, |
| ambarella_cs_timer_clksrc.mult, |
| ambarella_cs_timer_clksrc.shift); |
| #endif |
| #endif |
| |
| ambarella_clkevt.cpumask = cpumask_of(0); |
| setup_irq(ambarella_clkevt.irq, &ambarella_ce_timer_irq); |
| clockevents_calc_mult_shift(&ambarella_clkevt, AMBARELLA_TIMER_FREQ, 5); |
| ambarella_clkevt.max_delta_ns = |
| clockevent_delta2ns(0xffffffff, &ambarella_clkevt); |
| ambarella_clkevt.min_delta_ns = |
| clockevent_delta2ns(1, &ambarella_clkevt); |
| clockevents_register_device(&ambarella_clkevt); |
| } |
| |
| struct sys_timer ambarella_timer = { |
| .init = ambarella_timer_init, |
| }; |
| |
| u32 ambarella_timer_suspend(u32 level) |
| { |
| u32 timer_ctr_mask; |
| |
| ambarella_timer_pm.timer_ctr_reg = amba_readl(TIMER_CTR_REG); |
| ambarella_timer_pm.timer_clk = AMBARELLA_TIMER_FREQ; |
| ambarella_timer_pm.timer_ce_status_reg = |
| amba_readl(AMBARELLA_CE_TIMER_STATUS_REG); |
| ambarella_timer_pm.timer_ce_reload_reg = |
| amba_readl(AMBARELLA_CE_TIMER_RELOAD_REG); |
| ambarella_timer_pm.timer_ce_match1_reg = |
| amba_readl(AMBARELLA_CE_TIMER_MATCH1_REG); |
| ambarella_timer_pm.timer_ce_match2_reg = |
| amba_readl(AMBARELLA_CE_TIMER_MATCH2_REG); |
| #if defined(CONFIG_AMBARELLA_SUPPORT_CLOCKSOURCE) |
| ambarella_timer_pm.timer_cs_status_reg = |
| amba_readl(AMBARELLA_CS_TIMER_STATUS_REG); |
| ambarella_timer_pm.timer_cs_reload_reg = |
| amba_readl(AMBARELLA_CS_TIMER_RELOAD_REG); |
| ambarella_timer_pm.timer_cs_match1_reg = |
| amba_readl(AMBARELLA_CS_TIMER_MATCH1_REG); |
| ambarella_timer_pm.timer_cs_match2_reg = |
| amba_readl(AMBARELLA_CS_TIMER_MATCH2_REG); |
| #endif |
| |
| if (level) { |
| disable_irq(AMBARELLA_CE_TIMER_IRQ); |
| timer_ctr_mask = AMBARELLA_CE_TIMER_CTR_MASK; |
| #if defined(CONFIG_AMBARELLA_SUPPORT_CLOCKSOURCE) |
| timer_ctr_mask |= AMBARELLA_CS_TIMER_CTR_MASK; |
| #endif |
| amba_clrbitsl(TIMER_CTR_REG, timer_ctr_mask); |
| } |
| |
| return 0; |
| } |
| |
| u32 ambarella_timer_resume(u32 level) |
| { |
| u32 timer_ctr_mask; |
| |
| timer_ctr_mask = AMBARELLA_CE_TIMER_CTR_MASK; |
| #if defined(CONFIG_AMBARELLA_SUPPORT_CLOCKSOURCE) |
| timer_ctr_mask |= AMBARELLA_CS_TIMER_CTR_MASK; |
| #endif |
| amba_clrbitsl(TIMER_CTR_REG, timer_ctr_mask); |
| #if defined(CONFIG_AMBARELLA_SUPPORT_CLOCKSOURCE) |
| amba_writel(AMBARELLA_CS_TIMER_STATUS_REG, |
| ambarella_timer_pm.timer_cs_status_reg); |
| amba_writel(AMBARELLA_CS_TIMER_RELOAD_REG, |
| ambarella_timer_pm.timer_cs_reload_reg); |
| amba_writel(AMBARELLA_CS_TIMER_MATCH1_REG, |
| ambarella_timer_pm.timer_cs_match1_reg); |
| amba_writel(AMBARELLA_CS_TIMER_MATCH2_REG, |
| ambarella_timer_pm.timer_cs_match2_reg); |
| #endif |
| if ((ambarella_timer_pm.timer_ce_status_reg == 0) && |
| (ambarella_timer_pm.timer_ce_reload_reg == 0)){ |
| amba_writel(AMBARELLA_CE_TIMER_STATUS_REG, |
| AMBARELLA_TIMER_FREQ / HZ); |
| } else { |
| amba_writel(AMBARELLA_CE_TIMER_STATUS_REG, |
| ambarella_timer_pm.timer_ce_status_reg); |
| } |
| amba_writel(AMBARELLA_CE_TIMER_RELOAD_REG, |
| ambarella_timer_pm.timer_ce_reload_reg); |
| amba_writel(AMBARELLA_CE_TIMER_MATCH1_REG, |
| ambarella_timer_pm.timer_ce_match1_reg); |
| amba_writel(AMBARELLA_CE_TIMER_MATCH2_REG, |
| ambarella_timer_pm.timer_ce_match2_reg); |
| |
| if (ambarella_timer_pm.timer_clk != AMBARELLA_TIMER_FREQ) { |
| clockevents_calc_mult_shift(&ambarella_clkevt, |
| AMBARELLA_TIMER_FREQ, 5); |
| ambarella_clkevt.max_delta_ns = |
| clockevent_delta2ns(0xffffffff, &ambarella_clkevt); |
| ambarella_clkevt.min_delta_ns = |
| clockevent_delta2ns(1, &ambarella_clkevt); |
| switch (ambarella_clkevt.mode) { |
| case CLOCK_EVT_MODE_PERIODIC: |
| ambarella_ce_timer_set_periodic(); |
| break; |
| case CLOCK_EVT_MODE_ONESHOT: |
| case CLOCK_EVT_MODE_UNUSED: |
| case CLOCK_EVT_MODE_SHUTDOWN: |
| case CLOCK_EVT_MODE_RESUME: |
| break; |
| } |
| |
| #if defined(CONFIG_AMBARELLA_SUPPORT_CLOCKSOURCE) |
| clocksource_change_rating(&ambarella_cs_timer_clksrc, 0); |
| clocks_calc_mult_shift(&ambarella_cs_timer_clksrc.mult, |
| &ambarella_cs_timer_clksrc.shift, |
| AMBARELLA_TIMER_FREQ, NSEC_PER_SEC, 60); |
| pr_debug("%s: mult = %u, shift = %u\n", |
| ambarella_cs_timer_clksrc.name, |
| ambarella_cs_timer_clksrc.mult, |
| ambarella_cs_timer_clksrc.shift); |
| clocksource_change_rating(&ambarella_cs_timer_clksrc, |
| AMBARELLA_TIMER_RATING); |
| #if defined(CONFIG_HAVE_SCHED_CLOCK) |
| clocks_calc_mult_shift(&ambarella_sched_clock.mult, |
| &ambarella_sched_clock.shift, |
| AMBARELLA_TIMER_FREQ, NSEC_PER_SEC, 0); |
| #endif |
| #endif |
| } |
| |
| timer_ctr_mask = AMBARELLA_CE_TIMER_CTR_MASK; |
| #if defined(CONFIG_AMBARELLA_SUPPORT_CLOCKSOURCE) |
| timer_ctr_mask |= AMBARELLA_CS_TIMER_CTR_MASK; |
| #endif |
| amba_setbitsl(TIMER_CTR_REG, |
| (ambarella_timer_pm.timer_ctr_reg & timer_ctr_mask)); |
| if (level) |
| enable_irq(AMBARELLA_CE_TIMER_IRQ); |
| |
| #if defined(CONFIG_SMP) |
| percpu_timer_update_rate(amb_get_axi_clock_frequency(HAL_BASE_VP)); |
| #endif |
| |
| return 0; |
| } |
| |
| int __init ambarella_init_tm(void) |
| { |
| int errCode = 0; |
| |
| return errCode; |
| } |
| |