[PATCH 7/8] ARM: pxa: add 32KHz timer as clock source
Eric Miao
eric.y.miao at gmail.com
Mon Nov 29 02:46:35 EST 2010
On Wed, Nov 24, 2010 at 11:54 AM, Haojian Zhuang
<haojian.zhuang at marvell.com> wrote:
> PXA silicons are using 3MHz timer as default clock source. But it'll be stopped
> counting in low power mode. Although 32KHz timer is slow, it can continue
> counting in low power mode. While low power mode is implemented as idle,
> stopped timer can't be accepted.
I believe this is true on pxa95x. While on pxa93x/pxa3xx/pxa2xx, the 13MHz
timer can just work fine.
My idea would be to have a separate timer-32khz.c and have at least a
config option for this. The ideal case would be to seamlessly use the
32KHz timer in low power mode, with minimum impact to existing
platforms.
>
> Add 32KHz timer as an alternative clock source. User can switch clock source
> on demand.
>
> Signed-off-by: Haojian Zhuang <haojian.zhuang at marvell.com>
> Cc: Eric Miao <eric.y.miao at gmail.com>
> Cc: Nicolas Pitre <nico at fluxnic.net>
> ---
> arch/arm/mach-pxa/include/mach/regs-ost.h | 2 +
> arch/arm/mach-pxa/time.c | 202 +++++++++++++++++++++++------
> 2 files changed, 161 insertions(+), 43 deletions(-)
>
> diff --git a/arch/arm/mach-pxa/include/mach/regs-ost.h b/arch/arm/mach-pxa/include/mach/regs-ost.h
> index a3e5f86..ac01d5c 100644
> --- a/arch/arm/mach-pxa/include/mach/regs-ost.h
> +++ b/arch/arm/mach-pxa/include/mach/regs-ost.h
> @@ -19,6 +19,7 @@
> #define OWER __REG(0x40A00018) /* OS Timer Watchdog Enable Register */
> #define OIER __REG(0x40A0001C) /* OS Timer Interrupt Enable Register */
>
> +#define OSSR_M4 (1 << 4) /* Match status channel 4 */
> #define OSSR_M3 (1 << 3) /* Match status channel 3 */
> #define OSSR_M2 (1 << 2) /* Match status channel 2 */
> #define OSSR_M1 (1 << 1) /* Match status channel 1 */
> @@ -26,6 +27,7 @@
>
> #define OWER_WME (1 << 0) /* Watchdog Match Enable */
>
> +#define OIER_E4 (1 << 4) /* Interrupt enable channel 4 */
> #define OIER_E3 (1 << 3) /* Interrupt enable channel 3 */
> #define OIER_E2 (1 << 2) /* Interrupt enable channel 2 */
> #define OIER_E1 (1 << 1) /* Interrupt enable channel 1 */
> diff --git a/arch/arm/mach-pxa/time.c b/arch/arm/mach-pxa/time.c
> index dd96128..668bd7d 100644
> --- a/arch/arm/mach-pxa/time.c
> +++ b/arch/arm/mach-pxa/time.c
> @@ -7,6 +7,10 @@
> * Derived from Nicolas Pitre's PXA timer handler Copyright (c) 2001
> * by MontaVista Software, Inc. (Nico, your code rocks!)
> *
> + * Support 32KHz timer as alternative timer.
> + * Copyright (c) 2010 by Marvell International Inc.
> + * Haojian Zhuang <haojian.zhuang at marvell.com>
> + *
> * 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.
> @@ -18,12 +22,21 @@
> #include <linux/clockchips.h>
> #include <linux/sched.h>
> #include <linux/cnt32_to_63.h>
> +#include <linux/spinlock.h>
> +#include <linux/slab.h>
>
> #include <asm/div64.h>
> #include <asm/mach/irq.h>
> #include <asm/mach/time.h>
> #include <mach/regs-ost.h>
>
> +#define OSCR2NS_SCALE_FACTOR 10
> +#define MIN_OSCR_DELTA 16
> +#define PXA_CLOCK_32K 32768
> +
> +static struct clock_event_device ckevt_pxa;
> +static atomic_t active_oscr0, active_32k;
> +
> /*
> * This is PXA's sched_clock implementation. This has a resolution
> * of at least 308 ns and a maximum value of 208 days.
> @@ -33,8 +46,6 @@
> * calls to sched_clock() which should always be the case in practice.
> */
>
> -#define OSCR2NS_SCALE_FACTOR 10
> -
> static unsigned long oscr2ns_scale;
>
> static void __init set_oscr2ns_scale(unsigned long oscr_rate)
> @@ -57,11 +68,12 @@ unsigned long long sched_clock(void)
> return (v * oscr2ns_scale) >> OSCR2NS_SCALE_FACTOR;
> }
>
> +static inline int cpu_has_32khz_timer(void)
> +{
> + return !cpu_is_pxa25x();
> +}
>
> -#define MIN_OSCR_DELTA 16
> -
> -static irqreturn_t
> -pxa_ost0_interrupt(int irq, void *dev_id)
> +static irqreturn_t pxa_ost0_interrupt(int irq, void *dev_id)
> {
> struct clock_event_device *c = dev_id;
>
> @@ -73,33 +85,81 @@ pxa_ost0_interrupt(int irq, void *dev_id)
> return IRQ_HANDLED;
> }
>
> -static int
> -pxa_osmr0_set_next_event(unsigned long delta, struct clock_event_device *dev)
> +static struct irqaction pxa_ost0_irq = {
> + .name = "ost0",
> + .flags = IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL,
> + .handler = pxa_ost0_interrupt,
> + .dev_id = &ckevt_pxa,
> +};
> +
> +static irqreturn_t pxa_32k_interrupt(int irq, void *dev_id)
> {
> - unsigned long next, oscr;
> + struct clock_event_device *c = dev_id;
>
> - OIER |= OIER_E0;
> - next = OSCR + delta;
> - OSMR0 = next;
> - oscr = OSCR;
> + OIER &= ~OIER_E4;
> + OSSR = OSSR_M4;
> + c->event_handler(c);
> + return IRQ_HANDLED;
> +}
> +
> +static struct irqaction pxa_ost4_irq = {
> + .name = "ost4",
> + .flags = IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL,
> + .handler = pxa_32k_interrupt,
> + .dev_id = &ckevt_pxa,
> +};
>
> - return (signed)(next - oscr) <= MIN_OSCR_DELTA ? -ETIME : 0;
> +static int pxa_set_next_event(unsigned long delta,
> + struct clock_event_device *dev)
> +{
> + unsigned long next, oscr, oscr4;
> +
> + if (atomic_read(&active_oscr0) && atomic_read(&active_32k)) {
> + /* In the time of changing clock source */
> + OIER |= OIER_E0;
> + next = OSCR + (MIN_OSCR_DELTA << 1);
> + OSMR0 = next;
> + oscr = OSCR;
> + return (signed)(next - oscr) <= MIN_OSCR_DELTA ? -ETIME : 0;
> + } else if (atomic_read(&active_32k)) {
> + OIER |= OIER_E4;
> + next = OSCR4 + delta;
> + OSMR4 = next;
> + oscr4 = OSCR4;
> + return (signed)(next - oscr4) <= MIN_OSCR_DELTA ? -ETIME : 0;
> + } else {
> + OIER |= OIER_E0;
> + next = OSCR + delta;
> + OSMR0 = next;
> + oscr = OSCR;
> + return (signed)(next - oscr) <= MIN_OSCR_DELTA ? -ETIME : 0;
> + }
> }
>
> -static void
> -pxa_osmr0_set_mode(enum clock_event_mode mode, struct clock_event_device *dev)
> +static void pxa_set_mode(enum clock_event_mode mode,
> + struct clock_event_device *dev)
> {
> switch (mode) {
> case CLOCK_EVT_MODE_ONESHOT:
> - OIER &= ~OIER_E0;
> - OSSR = OSSR_M0;
> + if (likely(atomic_read(&active_oscr0))) {
> + OIER &= ~OIER_E0;
> + OSSR = OSSR_M0;
> + } else {
> + OIER &= ~OIER_E4;
> + OSSR = OSSR_M4;
> + }
> break;
>
> case CLOCK_EVT_MODE_UNUSED:
> case CLOCK_EVT_MODE_SHUTDOWN:
> /* initializing, released, or preparing for suspend */
> - OIER &= ~OIER_E0;
> - OSSR = OSSR_M0;
> + if (likely(atomic_read(&active_oscr0))) {
> + OIER &= ~OIER_E0;
> + OSSR = OSSR_M0;
> + } else {
> + OIER &= ~OIER_E4;
> + OSSR = OSSR_M4;
> + }
> break;
>
> case CLOCK_EVT_MODE_RESUME:
> @@ -108,12 +168,12 @@ pxa_osmr0_set_mode(enum clock_event_mode mode, struct clock_event_device *dev)
> }
> }
>
> -static struct clock_event_device ckevt_pxa_osmr0 = {
> - .name = "osmr0",
> +static struct clock_event_device ckevt_pxa = {
> + .name = "osmr",
> .features = CLOCK_EVT_FEAT_ONESHOT,
> .rating = 200,
> - .set_next_event = pxa_osmr0_set_next_event,
> - .set_mode = pxa_osmr0_set_mode,
> + .set_next_event = pxa_set_next_event,
> + .set_mode = pxa_set_mode,
> };
>
> static cycle_t pxa_read_oscr(struct clocksource *cs)
> @@ -121,42 +181,98 @@ static cycle_t pxa_read_oscr(struct clocksource *cs)
> return OSCR;
> }
>
> +static int pxa_ost0_enable(struct clocksource *cs)
> +{
> + atomic_set(&active_oscr0, 1);
> + clockevents_calc_mult_shift(&ckevt_pxa, get_clock_tick_rate(), 4);
> + return 0;
> +}
> +
> +static void pxa_ost0_disable(struct clocksource *cs)
> +{
> + atomic_dec(&active_oscr0);
> +}
> +
> static struct clocksource cksrc_pxa_oscr0 = {
> - .name = "oscr0",
> - .rating = 200,
> - .read = pxa_read_oscr,
> - .mask = CLOCKSOURCE_MASK(32),
> + .name = "accurate",
> + .rating = 200,
> + .read = pxa_read_oscr,
> + .enable = pxa_ost0_enable,
> + .disable = pxa_ost0_disable,
> + .mask = CLOCKSOURCE_MASK(32),
> .flags = CLOCK_SOURCE_IS_CONTINUOUS,
> };
>
> -static struct irqaction pxa_ost0_irq = {
> - .name = "ost0",
> - .flags = IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL,
> - .handler = pxa_ost0_interrupt,
> - .dev_id = &ckevt_pxa_osmr0,
> +static cycle_t pxa_32k_read(struct clocksource *cs)
> +{
> + return OSCR4;
> +}
> +
> +static int pxa_32k_enable(struct clocksource *cs)
> +{
> + atomic_set(&active_32k, 1);
> + clockevents_calc_mult_shift(&ckevt_pxa, PXA_CLOCK_32K, 4);
> + return 0;
> +}
> +
> +static void pxa_32k_disable(struct clocksource *cs)
> +{
> + atomic_dec(&active_32k);
> +}
> +
> +static struct clocksource cksrc_pxa_32k = {
> + .name = "32k",
> + .rating = 150,
> + .read = pxa_32k_read,
> + .enable = pxa_32k_enable,
> + .disable = pxa_32k_disable,
> + .mask = CLOCKSOURCE_MASK(32),
> + .flags = CLOCK_SOURCE_IS_CONTINUOUS,
> };
>
> +/*
> + * The rating of OSCR0 _must_ be large than the rating of OSCR4.
> + * So default timer is OSCR0.
> + *
> + * While PXA SoC enters into low power mode, OSCR0 will stop. So using OSCR4
> + * as timer is alternative choice.
> + *
> + * While PXA SoC needs accurate timer, OSCR0 is the best choice.
> + */
> static void __init pxa_timer_init(void)
> {
> unsigned long clock_tick_rate = get_clock_tick_rate();
>
> OIER = 0;
> - OSSR = OSSR_M0 | OSSR_M1 | OSSR_M2 | OSSR_M3;
> + OSSR = OSSR_M0 | OSSR_M1 | OSSR_M2 | OSSR_M3 | OSSR_M4;
>
> set_oscr2ns_scale(clock_tick_rate);
>
> - clocksource_calc_mult_shift(&cksrc_pxa_oscr0, clock_tick_rate, 4);
> - clockevents_calc_mult_shift(&ckevt_pxa_osmr0, clock_tick_rate, 4);
> - ckevt_pxa_osmr0.max_delta_ns =
> - clockevent_delta2ns(0x7fffffff, &ckevt_pxa_osmr0);
> - ckevt_pxa_osmr0.min_delta_ns =
> - clockevent_delta2ns(MIN_OSCR_DELTA * 2, &ckevt_pxa_osmr0) + 1;
> - ckevt_pxa_osmr0.cpumask = cpumask_of(0);
> + atomic_set(&active_oscr0, 0);
> + atomic_set(&active_32k, 0);
>
> - setup_irq(IRQ_OST0, &pxa_ost0_irq);
> + if (cpu_has_32khz_timer()) {
> + OMCR4 = 0xc1; /* match OSCR4 & periodic timer */
> + OSMR4 = 0;
> + OSCR4 = 1;
> +
> + clocksource_calc_mult_shift(&cksrc_pxa_32k, PXA_CLOCK_32K, 4);
> + setup_irq(IRQ_OST_4_11, &pxa_ost4_irq);
> + clocksource_register(&cksrc_pxa_32k);
> + }
>
> + clocksource_calc_mult_shift(&cksrc_pxa_oscr0, clock_tick_rate, 4);
> + setup_irq(IRQ_OST0, &pxa_ost0_irq);
> clocksource_register(&cksrc_pxa_oscr0);
> - clockevents_register_device(&ckevt_pxa_osmr0);
> +
> + clockevents_calc_mult_shift(&ckevt_pxa, clock_tick_rate, 4);
> + ckevt_pxa.max_delta_ns =
> + clockevent_delta2ns(0x7fffffff, &ckevt_pxa);
> + ckevt_pxa.min_delta_ns =
> + clockevent_delta2ns(MIN_OSCR_DELTA * 2, &ckevt_pxa) + 1;
> + ckevt_pxa.cpumask = cpumask_of(0);
> +
> + clockevents_register_device(&ckevt_pxa);
> }
>
> #ifdef CONFIG_PM
> --
> 1.5.6.5
>
>
More information about the linux-arm-kernel
mailing list