Commit bd156147 authored by Paul Mundt's avatar Paul Mundt
Browse files

sh: dyntick infrastructure.



This adds basic NO_IDLE_HZ support to the SH timer API so timers
are able to wire it up. Taken from the ARM version, as it fit in
to our API with very few changes needed.

Signed-off-by: default avatarPaul Mundt <lethal@linux-sh.org>
parent 1d118562
Loading
Loading
Loading
Loading
+18 −0
Original line number Diff line number Diff line
@@ -423,6 +423,24 @@ config SH_TIMER_IRQ
	default "140" if CPU_SUBTYPE_SH7206
	default "16"

config NO_IDLE_HZ
	bool "Dynamic tick timer"
	help
	  Select this option if you want to disable continuous timer ticks
	  and have them programmed to occur as required. This option saves
	  power as the system can remain in idle state for longer.

	  By default dynamic tick is disabled during the boot, and can be
	  manually enabled with:

	    echo 1 > /sys/devices/system/timer/timer0/dyn_tick

	  Alternatively, if you want dynamic tick automatically enabled
	  during boot, pass "dyntick=enable" via the kernel command string.

	  Please note that dynamic tick may affect the accuracy of
	  timekeeping on some platforms depending on the implementation.

config SH_PCLK_FREQ
	int "Peripheral clock frequency (in Hz)"
	default "27000000" if CPU_SUBTYPE_SH73180 || CPU_SUBTYPE_SH7343
+123 −1
Original line number Diff line number Diff line
@@ -174,6 +174,108 @@ static struct sysdev_class timer_sysclass = {
	.resume	 = timer_resume,
};

#ifdef CONFIG_NO_IDLE_HZ
static int timer_dyn_tick_enable(void)
{
	struct dyn_tick_timer *dyn_tick = sys_timer->dyn_tick;
	unsigned long flags;
	int ret = -ENODEV;

	if (dyn_tick) {
		spin_lock_irqsave(&dyn_tick->lock, flags);
		ret = 0;
		if (!(dyn_tick->state & DYN_TICK_ENABLED)) {
			ret = dyn_tick->enable();

			if (ret == 0)
				dyn_tick->state |= DYN_TICK_ENABLED;
		}
		spin_unlock_irqrestore(&dyn_tick->lock, flags);
	}

	return ret;
}

static int timer_dyn_tick_disable(void)
{
	struct dyn_tick_timer *dyn_tick = sys_timer->dyn_tick;
	unsigned long flags;
	int ret = -ENODEV;

	if (dyn_tick) {
		spin_lock_irqsave(&dyn_tick->lock, flags);
		ret = 0;
		if (dyn_tick->state & DYN_TICK_ENABLED) {
			ret = dyn_tick->disable();

			if (ret == 0)
				dyn_tick->state &= ~DYN_TICK_ENABLED;
		}
		spin_unlock_irqrestore(&dyn_tick->lock, flags);
	}

	return ret;
}

/*
 * Reprogram the system timer for at least the calculated time interval.
 * This function should be called from the idle thread with IRQs disabled,
 * immediately before sleeping.
 */
void timer_dyn_reprogram(void)
{
	struct dyn_tick_timer *dyn_tick = sys_timer->dyn_tick;
	unsigned long next, seq, flags;

	if (!dyn_tick)
		return;

	spin_lock_irqsave(&dyn_tick->lock, flags);
	if (dyn_tick->state & DYN_TICK_ENABLED) {
		next = next_timer_interrupt();
		do {
			seq = read_seqbegin(&xtime_lock);
			dyn_tick->reprogram(next - jiffies);
		} while (read_seqretry(&xtime_lock, seq));
	}
	spin_unlock_irqrestore(&dyn_tick->lock, flags);
}

static ssize_t timer_show_dyn_tick(struct sys_device *dev, char *buf)
{
	return sprintf(buf, "%i\n",
		       (sys_timer->dyn_tick->state & DYN_TICK_ENABLED) >> 1);
}

static ssize_t timer_set_dyn_tick(struct sys_device *dev, const char *buf,
				  size_t count)
{
	unsigned int enable = simple_strtoul(buf, NULL, 2);

	if (enable)
		timer_dyn_tick_enable();
	else
		timer_dyn_tick_disable();

	return count;
}
static SYSDEV_ATTR(dyn_tick, 0644, timer_show_dyn_tick, timer_set_dyn_tick);

/*
 * dyntick=enable|disable
 */
static char dyntick_str[4] __initdata = "";

static int __init dyntick_setup(char *str)
{
	if (str)
		strlcpy(dyntick_str, str, sizeof(dyntick_str));
	return 1;
}

__setup("dyntick=", dyntick_setup);
#endif

static int __init timer_init_sysfs(void)
{
	int ret = sysdev_class_register(&timer_sysclass);
@@ -181,7 +283,22 @@ static int __init timer_init_sysfs(void)
		return ret;

	sys_timer->dev.cls = &timer_sysclass;
	return sysdev_register(&sys_timer->dev);
	ret = sysdev_register(&sys_timer->dev);

#ifdef CONFIG_NO_IDLE_HZ
	if (ret == 0 && sys_timer->dyn_tick) {
		ret = sysdev_create_file(&sys_timer->dev, &attr_dyn_tick);

		/*
		 * Turn on dynamic tick after calibrate delay
		 * for correct bogomips
		 */
		if (ret == 0 && dyntick_str[0] == 'e')
			ret = timer_dyn_tick_enable();
	}
#endif

	return ret;
}
device_initcall(timer_init_sysfs);

@@ -205,6 +322,11 @@ void __init time_init(void)
	sys_timer = get_sys_timer();
	printk(KERN_INFO "Using %s for system timer\n", sys_timer->name);

#ifdef CONFIG_NO_IDLE_HZ
	if (sys_timer->dyn_tick)
		spin_lock_init(&sys_timer->dyn_tick->lock);
#endif

#if defined(CONFIG_SH_KGDB)
	/*
	 * Set up kgdb as requested. We do it here because the serial
+21 −0
Original line number Diff line number Diff line
@@ -18,8 +18,29 @@ struct sys_timer {

	struct sys_device	dev;
	struct sys_timer_ops	*ops;

#ifdef CONFIG_NO_IDLE_HZ
	struct dyn_tick_timer	*dyn_tick;
#endif
};

#ifdef CONFIG_NO_IDLE_HZ
#define DYN_TICK_ENABLED	(1 << 1)

struct dyn_tick_timer {
	spinlock_t	lock;
	unsigned int	state;			/* Current state */
	int		(*enable)(void);	/* Enables dynamic tick */
	int		(*disable)(void);	/* Disables dynamic tick */
	void		(*reprogram)(unsigned long); /* Reprograms the timer */
	int		(*handler)(int, void *);
};

void timer_dyn_reprogram(void);
#else
#define timer_dyn_reprogram()	do { } while (0)
#endif

#define TICK_SIZE (tick_nsec / 1000)

extern struct sys_timer tmu_timer, cmt_timer, mtu2_timer;