#if defined(CONFIG_BCM_KF_ARM_BCM963XX) /* <:copyright-BRCM:2013:DUAL/GPL:standard Copyright (c) 2013 Broadcom 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 (the "GPL"). 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. A copy of the GPL is available at http://www.broadcom.com/licenses/GPLv2.php, or by writing to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. :> */ #include #include #include #include #include #include #include #include #include #include #include /* * The ARM9 MPCORE Global Timer is a continously-running 64-bit timer, * which is used both as a "clock source" and as a "clock event" - * there is a banked per-cpu compare and reload registers that are * used to generated either one-shot or periodic interrupts on the cpu * that calls the mode_set function. * * NOTE: This code does not support dynamic change of the source clock * frequency. The interrupt interval is only calculated once during * initialization. */ /* * Global Timer Registers */ #define GTIMER_COUNT_LO 0x00 /* Lower 32 of 64 bits counter */ #define GTIMER_COUNT_HI 0x04 /* Higher 32 of 64 bits counter */ #define GTIMER_CTRL 0x08 /* Control (partially banked) */ #define GTIMER_CTRL_EN (1<<0) /* Timer enable bit */ #define GTIMER_CTRL_CMP_EN (1<<1) /* Comparator enable */ #define GTIMER_CTRL_IRQ_EN (1<<2) /* Interrupt enable */ #define GTIMER_CTRL_AUTO_EN (1<<3) /* Auto-increment enable */ #define GTIMER_INT_STAT 0x0C /* Interrupt Status (banked) */ #define GTIMER_COMP_LO 0x10 /* Lower half comparator (banked) */ #define GTIMER_COMP_HI 0x14 /* Upper half comparator (banked) */ #define GTIMER_RELOAD 0x18 /* Auto-increment (banked) */ #define GTIMER_MIN_RANGE 30 /* Minimum wrap-around time in sec */ #define GTIMER_VIRT_ADDR (IO_ADDRESS(SCU_PHYS_BASE) + CA9MP_GTIMER_OFF) #define LTIMER_PHY_ADDR (SCU_PHYS_BASE + CA9MP_LTIMER_OFF) /* Gobal variables */ static u32 ticks_per_jiffy; static cycle_t gptimer_count_read(struct clocksource *cs) { u32 count_hi, count_ho, count_lo; u64 count; /* Avoid unexpected rollover with double-read of upper half */ do { count_hi = readl_relaxed(GTIMER_VIRT_ADDR + GTIMER_COUNT_HI); count_lo = readl_relaxed(GTIMER_VIRT_ADDR + GTIMER_COUNT_LO); count_ho = readl_relaxed(GTIMER_VIRT_ADDR + GTIMER_COUNT_HI); } while (count_hi != count_ho); count = (u64)count_hi << 32 | count_lo; return count; } static struct clocksource clocksource_gptimer = { .name = "ca9mp_gtimer", .rating = 300, .read = gptimer_count_read, .mask = CLOCKSOURCE_MASK(64), // .shift = 20, .flags = CLOCK_SOURCE_IS_CONTINUOUS, }; static notrace u32 brcm_sched_clock_read(void) { return clocksource_gptimer.read(&clocksource_gptimer); } /* * IRQ handler for the global timer * This interrupt is banked per CPU so is handled identically */ static irqreturn_t gtimer_interrupt(int irq, void *dev_id) { struct clock_event_device *evt = *(struct clock_event_device **)dev_id; if (evt->mode == CLOCK_EVT_MODE_ONESHOT) { u32 ctrl = readl_relaxed(GTIMER_VIRT_ADDR + GTIMER_CTRL); ctrl &= ~GTIMER_CTRL_EN; writel_relaxed(ctrl, GTIMER_VIRT_ADDR + GTIMER_CTRL); } /* clear the interrupt */ writel_relaxed(1, GTIMER_VIRT_ADDR + GTIMER_INT_STAT); evt->event_handler(evt); return IRQ_HANDLED; } static void gtimer_set_mode(enum clock_event_mode mode, struct clock_event_device *evt) { u32 ctrl = 0, period; u64 count; /* By default, when we enter this function, we can just stop * the timer completely, once a mode is selected, then we * can start the timer at that point. */ switch (mode) { case CLOCK_EVT_MODE_PERIODIC: period = ticks_per_jiffy; count = gptimer_count_read(NULL); count += period; writel_relaxed(ctrl, GTIMER_VIRT_ADDR + GTIMER_CTRL); writel_relaxed(count & 0xffffffffUL, GTIMER_VIRT_ADDR + GTIMER_COMP_LO); writel_relaxed(count >> 32, GTIMER_VIRT_ADDR + GTIMER_COMP_HI); writel_relaxed(period, GTIMER_VIRT_ADDR + GTIMER_RELOAD); ctrl = GTIMER_CTRL_EN | GTIMER_CTRL_CMP_EN | GTIMER_CTRL_IRQ_EN | GTIMER_CTRL_AUTO_EN; break; case CLOCK_EVT_MODE_ONESHOT: /* period set, and timer enabled in 'next_event' hook */ break; case CLOCK_EVT_MODE_UNUSED: case CLOCK_EVT_MODE_SHUTDOWN: default: break; } /* Apply the new mode */ writel_relaxed(ctrl, GTIMER_VIRT_ADDR + GTIMER_CTRL); } static int gtimer_set_next_event(unsigned long next, struct clock_event_device *evt) { u32 ctrl = readl_relaxed(GTIMER_VIRT_ADDR + GTIMER_CTRL); u64 count = gptimer_count_read(NULL); ctrl &= ~GTIMER_CTRL_CMP_EN; writel_relaxed(ctrl, GTIMER_VIRT_ADDR + GTIMER_CTRL); count += next; writel_relaxed(count & 0xffffffffUL, GTIMER_VIRT_ADDR + GTIMER_COMP_LO); writel_relaxed(count >> 32, GTIMER_VIRT_ADDR + GTIMER_COMP_HI); /* enable IRQ for the same cpu that loaded comparator */ ctrl |= GTIMER_CTRL_EN | GTIMER_CTRL_CMP_EN | GTIMER_CTRL_IRQ_EN; writel_relaxed(ctrl, GTIMER_VIRT_ADDR + GTIMER_CTRL); return 0; } static struct clock_event_device gtimer_clockevent = { .name = "ca9mp_gtimer", .shift = 20, .features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT, .set_mode = gtimer_set_mode, .set_next_event = gtimer_set_next_event, .rating = 300, }; static union { struct clock_event_device *evt; struct clock_event_device __percpu **percpu_evt; } brcm_evt; static void __init gtimer_clockevents_init(u32 rate) { struct clock_event_device *evt = >imer_clockevent; int res; evt->irq = CA9MP_IRQ_GLOBALTIMER; evt->cpumask = cpumask_of(0); #ifdef CONFIG_BCM63138_SIM ticks_per_jiffy = DIV_ROUND_CLOSEST(rate, HZ) / 20; #else ticks_per_jiffy = DIV_ROUND_CLOSEST(rate, HZ); #endif clockevents_calc_mult_shift(evt, rate, GTIMER_MIN_RANGE); evt->max_delta_ns = clockevent_delta2ns(0xffffffff, evt); evt->min_delta_ns = clockevent_delta2ns(0xf, evt); /* Register the device to install handler before enabing IRQ */ clockevents_register_device(evt); brcm_evt.percpu_evt = alloc_percpu(struct clock_event_device *); if (!brcm_evt.percpu_evt) { pr_err("alloc_percpu failed for %s\n", evt->name); } *__this_cpu_ptr(brcm_evt.percpu_evt) = evt; res = request_percpu_irq(evt->irq, gtimer_interrupt, evt->name, brcm_evt.percpu_evt); if (!res) { pr_err("request_percpu_irq succeeds for %s\n", evt->name); enable_percpu_irq(evt->irq, 0); } else pr_err("request_percpu_irq fails! for %s\n", evt->name); } static void inline gtimer_clockevents_updatefreq_hz(u32 rate) { struct clock_event_device *evt = >imer_clockevent; /* there is an API called clockevents_update_freq which does * almost identical task as what we do here */ #ifdef CONFIG_BCM63138_SIM ticks_per_jiffy = DIV_ROUND_CLOSEST(rate, HZ) / 20; #else ticks_per_jiffy = DIV_ROUND_CLOSEST(rate, HZ); #endif clockevents_calc_mult_shift(evt, rate, GTIMER_MIN_RANGE); evt->max_delta_ns = clockevent_delta2ns(0xffffffff, evt); evt->min_delta_ns = clockevent_delta2ns(0xf, evt); } /* * MPCORE Global Timer initialization function */ static void __init ca9mp_gtimer_init(unsigned long rate) { u64 count; int res; printk(KERN_INFO "MPCORE Global Timer Clock %luHz\n", rate); /* Register as system timer */ gtimer_clockevents_init(rate); /* Self-test the timer is running */ count = gptimer_count_read(NULL); /* Register as time source */ res = clocksource_register_hz(&clocksource_gptimer, rate); if (res) printk("%s:clocksource_register failed!\n", __func__); setup_sched_clock(brcm_sched_clock_read, 32, rate); count = gptimer_count_read(NULL) - count; if (count == 0) printk(KERN_CRIT "MPCORE Global Timer Dead!!\n"); } #ifdef CONFIG_HAVE_ARM_TWD static DEFINE_TWD_LOCAL_TIMER(twd_local_timer, LTIMER_PHY_ADDR, CA9MP_IRQ_LOCALTIMER); static void __init ca9mp_twd_init(void) { int err = twd_local_timer_register(&twd_local_timer); if (err) pr_err("twd_local_timer_register failed %d\n", err); } #else #define ca9mp_twd_init() do {} while(0) #endif void ca9mp_timer_update_freq(unsigned long rate) { printk(KERN_INFO "MPCORE Global Timer Clock update to %luHz\n", rate); gtimer_clockevents_updatefreq_hz(rate); __clocksource_updatefreq_hz(&clocksource_gptimer, rate); } void __init ca9mp_timer_init(unsigned long rate) { /* init global timer */ ca9mp_gtimer_init(rate); /* init TWD / local timer */ ca9mp_twd_init(); } #endif /* CONFIG_BCM_KF_ARM_BCM963XX */