public inbox for linux-sh@vger.kernel.org
 help / color / mirror / Atom feed
From: john stultz <johnstul@us.ibm.com>
To: linux-sh@vger.kernel.org
Subject: Re: [PATCH 04/10] timer_inc: timer helper code, clockevent only
Date: Mon, 01 Dec 2008 20:29:14 +0000	[thread overview]
Message-ID: <1228163354.7176.18.camel@localhost.localdomain> (raw)
In-Reply-To: <20081201103300.26620.32206.sendpatchset@rx1.opensource.se>

On Mon, 2008-12-01 at 19:33 +0900, Magnus Damm wrote:
> From: Magnus Damm <damm@igel.co.jp>
> 
> This patch adds clock source/event helper code for incrementing timers.
> 
> Normally tickless operation requires two timers, one for the clock source
> and another one for a oneshot timer event. The helper code makes it
> possible to use a single timer for both clock source and clock event,
> ie tickless with a single incrementing timer. Useful on for instance
> processors where all timers except one needs to be disabled for deep sleep.
> 
> The clock source is calculated from the running hardware timer plus a
> software controlled counter which together form a 32 bit value. High
> accuracy of the clock source is kept by never reprogramming the hardware
> counter, only the hardware match value.
> 
> Clock events are handled by reading out current hardware counter value
> and reprogramming the match register. Both periodic and oneshot modes
> are supported.
> 
> The helper code only supports incrementing timer hardware which at
> matching time resets and generates an interrupt but keeps on counting.
> Timers counting down are not supported.

Hey Magnus,
	Forgive me again for not understanding all this immediately. It just
seems like a fair amount of generic infrastructure, so I want to make
sure it really is reusable.

I'm not sure your initial assertion that tickless operation requires two
timers is correct. Can you explain why this timer_inc structure is
necessary, in comparison to just having separate clockevent and
clocksource interfaces for one bit of hardware, like what is done with
the similar count-up HPET on x86?

I just worry that we're adding extra structures and interfaces where it
might be better to just re-factor the clocksource and clockevent
structures themselves to handle the needs you have.

thanks
-john


> This patch only implements clock event code to begin with. The code
> gets updated with full clock source support later in the series.
> 
> Signed-off-by: Magnus Damm <damm@igel.co.jp>
> ---
> 
>  drivers/clocksource/timer_inc.c |  349 +++++++++++++++++++++++++++++++++++++++
>  include/linux/timer_inc.h       |   44 ++++
>  2 files changed, 393 insertions(+)
> 
> --- /dev/null
> +++ work/drivers/clocksource/timer_inc.c	2008-12-01 12:51:28.000000000 +0900
> @@ -0,0 +1,349 @@
> +/*
> + * timer_inc.c - Clock source/event helper code for incrementing timers.
> + *
> + *  Copyright (C) 2008 Magnus Damm
> + *
> + * 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
> + *
> + * 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/errno.h>
> +#include <linux/string.h>
> +#include <linux/clocksource.h>
> +#include <linux/clockchips.h>
> +#include <linux/spinlock.h>
> +#include <linux/timer_inc.h>
> +
> +/*
> + * Written for increment-match-wrap timer hardware. The timer is constantly
> + * enabled and a software cycle counter is updated at interrupt time.
> + * The hardware timer counter is never modified to allow high clock source
> + * quality. The match register is rewritten to support oneshot and periodic
> + * clock events.
> + */
> +
> +/* private flags */
> +#define FLAG_CLOCKEVENT (1 << 0)
> +#define FLAG_REPROGRAM (1 << 2)
> +#define FLAG_SKIPEVENT (1 << 3)
> +#define FLAG_IRQCONTEXT (1 << 4)
> +
> +static void timer_inc_clock_event_program_verify(struct timer_inc *tip,
> +						 int absolute)
> +{
> +	unsigned long new_match;
> +	unsigned long value = tip->next_match_value;
> +	unsigned long delay = 0;
> +	unsigned long now = 0;
> +	int has_wrapped;
> +
> +	now = tip->ops->get_counter(tip->priv, &has_wrapped);
> +	tip->flags |= FLAG_REPROGRAM; /* force reprogram */
> +
> +	if (has_wrapped) {
> +		/* we're competing with the interrupt handler.
> +		 *  -> let the interrupt handler reprogram the timer.
> +		 *  -> interrupt number two handles the event.
> +		 */
> +		tip->flags |= FLAG_SKIPEVENT;
> +		return;
> +	}
> +
> +	if (absolute)
> +		now = 0;
> +
> +	do {
> +		/* reprogram the timer hardware,
> +		 * but don't save the new match value yet.
> +		 */
> +		new_match = now + value + delay;
> +		if (new_match > tip->max_match_value)
> +			new_match = tip->max_match_value;
> +
> +		tip->ops->set_match(tip->priv, new_match);
> +
> +		now = tip->ops->get_counter(tip->priv, &has_wrapped);
> +		if (has_wrapped && (new_match > tip->match_value)) {
> +			/* we are changing to a greater match value,
> +			 * so this wrap must be caused by the counter
> +			 * matching the old value.
> +			 * -> first interrupt reprograms the timer.
> +			 * -> interrupt number two handles the event.
> +			 */
> +			tip->flags |= FLAG_SKIPEVENT;
> +			break;
> +		}
> +
> +		if (has_wrapped) {
> +			/* we are changing to a smaller match value,
> +			 * so the wrap must be caused by the counter
> +			 * matching the new value.
> +			 * -> save programmed match value.
> +			 * -> let isr handle the event.
> +			 */
> +			tip->match_value = new_match;
> +			break;
> +		}
> +
> +		/* be safe: verify hardware settings */
> +		if (now < new_match) {
> +			/* timer value is below match value, all good.
> +			 * this makes sure we won't miss any match events.
> +			 * -> save programmed match value.
> +			 * -> let isr handle the event.
> +			 */
> +			tip->match_value = new_match;
> +			break;
> +		}
> +
> +		/* the counter has reached a value greater
> +		 * than our new match value. and since the
> +		 * has_wrapped flag isn't set we must have
> +		 * programmed a too close event.
> +		 * -> increase delay and retry.
> +		 */
> +		if (delay)
> +			delay <<= 1;
> +		else
> +			delay = 1;
> +
> +		if (!delay)
> +			pr_warning("timer_inc: too long delay\n");
> +
> +	} while (delay);
> +}
> +
> +static void timer_inc_set_next(struct timer_inc *tip, unsigned long delta)
> +{
> +	unsigned long flags;
> +
> +	if (delta > tip->max_match_value)
> +		pr_warning("timer_inc: delta out of range\n");
> +
> +	spin_lock_irqsave(&tip->lock, flags);
> +	tip->next_match_value = delta;
> +	timer_inc_clock_event_program_verify(tip, 0);
> +	spin_unlock_irqrestore(&tip->lock, flags);
> +}
> +
> +void timer_inc_interrupt(struct timer_inc *tip)
> +{
> +	if (!(tip->flags & FLAG_REPROGRAM))
> +		tip->next_match_value = tip->max_match_value;
> +
> +	tip->flags |= FLAG_IRQCONTEXT;
> +
> +#ifdef CONFIG_GENERIC_CLOCKEVENTS
> +	if (tip->flags & FLAG_CLOCKEVENT) {
> +		if (!(tip->flags & FLAG_SKIPEVENT)) {
> +			if (tip->ced.mode = CLOCK_EVT_MODE_ONESHOT) {
> +				tip->next_match_value = tip->max_match_value;
> +				tip->flags |= FLAG_REPROGRAM;
> +			}
> +
> +			tip->ced.event_handler(&tip->ced);
> +		}
> +	}
> +#endif
> +	tip->flags &= ~FLAG_SKIPEVENT;
> +
> +	if (tip->flags & FLAG_REPROGRAM) {
> +		tip->flags &= ~FLAG_REPROGRAM;
> +		timer_inc_clock_event_program_verify(tip, 1);
> +
> +#ifdef CONFIG_GENERIC_CLOCKEVENTS
> +		if (tip->flags & FLAG_CLOCKEVENT)
> +			if ((tip->ced.mode = CLOCK_EVT_MODE_SHUTDOWN)
> +			    || (tip->match_value = tip->next_match_value))
> +				tip->flags &= ~FLAG_REPROGRAM;
> +#endif
> +	}
> +
> +	tip->flags &= ~FLAG_IRQCONTEXT;
> +}
> +
> +static int timer_inc_start(struct timer_inc *tip, unsigned long flag)
> +{
> +	int ret = 0;
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&tip->lock, flags);
> +
> +	if (!(tip->flags & FLAG_CLOCKEVENT))
> +		ret = tip->ops->enable(tip->priv, &tip->rate);
> +
> +	if (!ret)
> +		tip->flags |= flag;
> +
> +	spin_unlock_irqrestore(&tip->lock, flags);
> +
> +	return ret;
> +}
> +
> +static void timer_inc_stop(struct timer_inc *tip, unsigned long flag)
> +{
> +	unsigned long flags;
> +	unsigned long f;
> +
> +	spin_lock_irqsave(&tip->lock, flags);
> +
> +	f = tip->flags & FLAG_CLOCKEVENT;
> +	tip->flags &= ~flag;
> +
> +	if (f && !(tip->flags & FLAG_CLOCKEVENT))
> +		tip->ops->disable(tip->priv);
> +
> +	spin_unlock_irqrestore(&tip->lock, flags);
> +}
> +
> +
> +#ifdef CONFIG_GENERIC_CLOCKEVENTS
> +
> +static struct timer_inc *ced_to_timer_inc(struct clock_event_device *ced)
> +{
> +	return container_of(ced, struct timer_inc, ced);
> +}
> +
> +static void timer_inc_clock_event_start(struct timer_inc *tip, int periodic)
> +{
> +	struct clock_event_device *ced = &tip->ced;
> +
> +	timer_inc_start(tip, FLAG_CLOCKEVENT);
> +
> +	/* TODO: calculate good shift from rate and counter bit width */
> +
> +	ced->shift = 32;
> +	ced->mult = div_sc(tip->rate, NSEC_PER_SEC, ced->shift);
> +	ced->max_delta_ns = clockevent_delta2ns(tip->max_match_value, ced);
> +	ced->min_delta_ns = clockevent_delta2ns(0x1f, ced);
> +
> +	if (periodic)
> +		timer_inc_set_next(tip, (tip->rate + HZ/2) / HZ);
> +	else
> +		timer_inc_set_next(tip, tip->max_match_value);
> +}
> +
> +static void timer_inc_clock_event_mode(enum clock_event_mode mode,
> +				       struct clock_event_device *ced)
> +{
> +	struct timer_inc *tip = ced_to_timer_inc(ced);
> +
> +	/* deal with old setting first */
> +	switch (ced->mode) {
> +	case CLOCK_EVT_MODE_PERIODIC:
> +	case CLOCK_EVT_MODE_ONESHOT:
> +		timer_inc_stop(tip, FLAG_CLOCKEVENT);
> +		break;
> +	default:
> +		break;
> +	}
> +
> +	switch (mode) {
> +	case CLOCK_EVT_MODE_PERIODIC:
> +		pr_info("timer_inc: %s used for periodic clock events\n",
> +			ced->name);
> +		timer_inc_clock_event_start(tip, 1);
> +		break;
> +	case CLOCK_EVT_MODE_ONESHOT:
> +		pr_info("timer_inc: %s used for oneshot clock events\n",
> +			ced->name);
> +		timer_inc_clock_event_start(tip, 0);
> +		break;
> +	case CLOCK_EVT_MODE_SHUTDOWN:
> +	case CLOCK_EVT_MODE_UNUSED:
> +		timer_inc_stop(tip, FLAG_CLOCKEVENT);
> +		break;
> +	default:
> +		break;
> +	}
> +}
> +
> +static int timer_inc_clock_event_next(unsigned long delta,
> +				      struct clock_event_device *ced)
> +{
> +	struct timer_inc *tip = ced_to_timer_inc(ced);
> +
> +	BUG_ON(ced->mode != CLOCK_EVT_MODE_ONESHOT);
> +	if (likely(tip->flags & FLAG_IRQCONTEXT))
> +		tip->next_match_value = delta;
> +	else
> +		timer_inc_set_next(tip, delta);
> +
> +	return 0;
> +}
> +
> +static void timer_inc_register_clockevent(struct timer_inc *tip,
> +					  char *name,
> +					  unsigned long rating)
> +{
> +	struct clock_event_device *ced = &tip->ced;
> +
> +	memset(ced, 0, sizeof(*ced));
> +
> +	ced->name = name;
> +	ced->features = CLOCK_EVT_FEAT_PERIODIC;
> +	ced->features |= CLOCK_EVT_FEAT_ONESHOT;
> +	ced->rating = rating;
> +	ced->cpumask = CPU_MASK_CPU0;
> +	ced->set_next_event = timer_inc_clock_event_next;
> +	ced->set_mode = timer_inc_clock_event_mode;
> +
> +	pr_info("timer_inc: %s used for clock events\n", ced->name);
> +	clockevents_register_device(ced);
> +}
> +
> +static void timer_inc_unregister_clockevent(struct timer_inc *tip)
> +{
> +	struct clock_event_device *ced = &tip->ced;
> +
> +	if (ced->features)
> +		clockevents_unregister_device(ced);
> +}
> +
> +#else /* CONFIG_GENERIC_CLOCKEVENTS */
> +static inline void timer_inc_register_clockevent(struct timer_inc *tip,
> +						 char *name,
> +						 unsigned long rating) {}
> +static inline void timer_inc_unregister_clockevent(struct timer_inc *tip) {}
> +#endif /* CONFIG_GENERIC_CLOCKEVENTS */
> +
> +int timer_inc_register(struct timer_inc *tip,
> +		       char *name, void *priv,
> +		       unsigned long bits,
> +		       struct timer_inc_ops *ops,
> +		       unsigned long clockevent_rating,
> +		       unsigned long clocksource_rating)
> +{
> +	memset(tip, 0, sizeof(*tip));
> +
> +	if (bits = (sizeof(tip->max_match_value) * 8))
> +		tip->max_match_value = ~0;
> +	else
> +		tip->max_match_value = (1 << bits) - 1;
> +
> +	tip->match_value = tip->max_match_value;
> +	tip->priv = priv;
> +	tip->ops = ops;
> +	spin_lock_init(&tip->lock);
> +
> +	if (clockevent_rating)
> +		timer_inc_register_clockevent(tip, name, clockevent_rating);
> +
> +	return 0;
> +}
> +
> +void timer_inc_unregister(struct timer_inc *tip)
> +{
> +	timer_inc_unregister_clockevent(tip);
> +}
> --- /dev/null
> +++ work/include/linux/timer_inc.h	2008-12-01 12:47:28.000000000 +0900
> @@ -0,0 +1,44 @@
> +#ifndef __TIMER_INC_H__
> +#define __TIMER_INC_H__
> +
> +/* Clock source/event helper code for incrementing timers. */
> +
> +#include <linux/clocksource.h>
> +#include <linux/clockchips.h>
> +#include <linux/spinlock.h>
> +
> +struct timer_inc;
> +
> +struct timer_inc_ops {
> +	void (*set_match)(void *priv, unsigned long value);
> +	unsigned long (*get_counter)(void *priv, int *has_wrapped);
> +	int (*enable)(void *priv, unsigned long *rate);
> +	void (*disable)(void *priv);
> +};
> +
> +struct timer_inc {
> +	struct timer_inc_ops *ops;
> +	void *priv;
> +	unsigned long flags;
> +	unsigned long match_value;
> +	unsigned long next_match_value;
> +	unsigned long max_match_value;
> +	unsigned long rate;
> +	spinlock_t lock;
> +#ifdef CONFIG_GENERIC_CLOCKEVENTS
> +	struct clock_event_device ced;
> +#endif
> +};
> +
> +void timer_inc_interrupt(struct timer_inc *t);
> +
> +int timer_inc_register(struct timer_inc *tip,
> +		       char *name, void *priv,
> +		       unsigned long bits,
> +		       struct timer_inc_ops *ops,
> +		       unsigned long clockevent_rating,
> +		       unsigned long clocksource_rating);
> +
> +void timer_inc_unregister(struct timer_inc *tip);
> +
> +#endif /* __TIMER_INC_H__ */


  reply	other threads:[~2008-12-01 20:29 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2008-12-01 10:33 [PATCH 04/10] timer_inc: timer helper code, clockevent only Magnus Damm
2008-12-01 20:29 ` john stultz [this message]
2008-12-02  5:30 ` Magnus Damm
2008-12-03  4:04 ` john stultz
2008-12-03 15:30 ` Magnus Damm
2008-12-03 16:35 ` John Stultz
2008-12-04 13:52 ` Magnus Damm

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1228163354.7176.18.camel@localhost.localdomain \
    --to=johnstul@us.ibm.com \
    --cc=linux-sh@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox