linux-arm-kernel.lists.infradead.org archive mirror
 help / color / mirror / Atom feed
* [RFC] AT91 cpufreq support
@ 2009-08-26 19:33 Albin Tonnerre
  2009-09-30 15:22 ` Nicolas Ferre
  0 siblings, 1 reply; 4+ messages in thread
From: Albin Tonnerre @ 2009-08-26 19:33 UTC (permalink / raw)
  To: linux-arm-kernel

Hi there,

Here is an updated version of the patch that brings cpufreq support on AT91.
This one is more generic that the previous one: instead of using fixed values,
it gets the current prescaler value and builds the frequency table accordingly.

It fixes a couple mistakes from the previous iteration, making it work more
reliably, at least there. I also added a cpufreq notifier to atmel_serial, so
that we don't lose the console over a frequency change (I'll move it to a
separate patch in a further update, and will add such support for the macb
driver too).
As there's no cpufreq notifier in the macb driver (yet), using cpufreq with a
NFS root filesystem will fail (obviously, other uses of the network will
probably fail too, but at least they don't make the system unusable)

On a Calao USB-A9263 board, switching from 180MHz to 11MHz reduces the current
consumption by roughly 65mA

Testing/feedback would be very much welcome

Regards,
Albin

---
 arch/arm/Kconfig                  |   10 ++-
 arch/arm/mach-at91/Makefile       |    1 +
 arch/arm/mach-at91/cpufreq-at91.c |  197 +++++++++++++++++++++++++++++++++++++
 drivers/serial/atmel_serial.c     |   81 +++++++++++++++
 4 files changed, 288 insertions(+), 1 deletions(-)
 create mode 100644 arch/arm/mach-at91/cpufreq-at91.c

diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
index aef63c8..d47f8ea 100644
--- a/arch/arm/Kconfig
+++ b/arch/arm/Kconfig
@@ -1241,7 +1241,7 @@ endmenu
 
 menu "CPU Power Management"
 
-if (ARCH_SA1100 || ARCH_INTEGRATOR || ARCH_OMAP || ARCH_PXA || ARCH_S3C64XX)
+if (ARCH_SA1100 || ARCH_INTEGRATOR || ARCH_OMAP || ARCH_PXA || ARCH_S3C64XX || ARCH_AT91)
 
 source "drivers/cpufreq/Kconfig"
 
@@ -1276,6 +1276,14 @@ config CPU_FREQ_S3C64XX
 	bool "CPUfreq support for Samsung S3C64XX CPUs"
 	depends on CPU_FREQ && CPU_S3C6410
 
+config CPU_FREQ_AT91
+	bool "CPUfreq driver from ATMEL AT91 ARM CPUs (EXPERIMENTAL)"
+	depends on CPU_FREQ && ARCH_AT91 && EXPERIMENTAL
+	help
+	  This enables the CPUfreq driver for ARM AT91 CPUs.
+
+	  If in doubt, say N.
+
 endif
 
 source "drivers/cpuidle/Kconfig"
diff --git a/arch/arm/mach-at91/Makefile b/arch/arm/mach-at91/Makefile
index c69ff23..d4b4e16 100644
--- a/arch/arm/mach-at91/Makefile
+++ b/arch/arm/mach-at91/Makefile
@@ -67,6 +67,7 @@ obj-y				+= leds.o
 # Power Management
 obj-$(CONFIG_PM)		+= pm.o
 obj-$(CONFIG_AT91_SLOW_CLOCK)	+= pm_slowclock.o
+obj-$(CONFIG_CPU_FREQ_AT91) += cpufreq-at91.o
 
 ifeq ($(CONFIG_PM_DEBUG),y)
 CFLAGS_pm.o += -DDEBUG
diff --git a/arch/arm/mach-at91/cpufreq-at91.c b/arch/arm/mach-at91/cpufreq-at91.c
new file mode 100644
index 0000000..32e955b
--- /dev/null
+++ b/arch/arm/mach-at91/cpufreq-at91.c
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2009
+ * Albin Tonnerre, Free Electrons <albin.tonnerre@free-electrons.com>
+ *
+ * Based on linux/arch/arm/mach-pxa/cpufreq-pxa3xx.c
+ * Copyright (C) 2008 Marvell International Ltd.
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/errno.h>
+#include <linux/io.h>
+#include <linux/cpufreq.h>
+#include <linux/clk.h>
+
+#include <mach/hardware.h>
+#include <mach/at91_pmc.h>
+#include <mach/cpu.h>
+#include <linux/delay.h>
+#include "clock.h"
+
+struct at91_freq_info {
+	unsigned int cpufreq_khz;
+	u32          mckr_pres;
+};
+
+static struct at91_freq_info *at91_freqs;
+static struct cpufreq_frequency_table *at91_freqs_table;
+
+static int setup_freqs_table(struct cpufreq_policy *policy,
+			     struct at91_freq_info *freqs, int num)
+{
+	struct cpufreq_frequency_table *table;
+	int i;
+
+	table = kzalloc((num + 1) * sizeof(*table), GFP_KERNEL);
+	if (table == NULL)
+		return -ENOMEM;
+
+	for (i = 0; i < num; i++) {
+		table[i].index = i;
+		table[i].frequency = freqs[i].cpufreq_khz;
+	}
+	table[num].frequency = i;
+	table[num].frequency = CPUFREQ_TABLE_END;
+
+	at91_freqs = freqs;
+	at91_freqs_table = table;
+
+	return cpufreq_frequency_table_cpuinfo(policy, table);
+}
+
+static void __update_core_freq(struct clk *clk, struct at91_freq_info *info)
+{
+	u32 mckr, cur_mdiv;
+
+	cur_mdiv = at91_sys_read(AT91_PMC_MCKR) & AT91_PMC_MDIV;
+	mckr = info->mckr_pres | cur_mdiv | AT91_PMC_CSS_PLLA;
+
+	at91_sys_write(AT91_PMC_MCKR, mckr);
+	while(!(at91_sys_read(AT91_PMC_SR) & AT91_PMC_MCKRDY))
+		cpu_relax();
+
+	clk->rate_hz = (info->cpufreq_khz * 1000) >> (cur_mdiv >> 8);
+}
+
+static int at91_cpufreq_verify(struct cpufreq_policy *policy)
+{
+	return cpufreq_frequency_table_verify(policy, at91_freqs_table);
+}
+
+static unsigned int at91_cpufreq_get(unsigned int cpu)
+{
+	u32 mdiv;
+
+	mdiv = 1 << ((at91_sys_read(AT91_PMC_MCKR) & AT91_PMC_MDIV) >> 8);
+	return mdiv * clk_get_rate(clk_get(NULL, "mck")) / 1000;
+}
+
+static int at91_cpufreq_set(struct cpufreq_policy *policy,
+			      unsigned int target_freq,
+			      unsigned int relation)
+{
+	struct at91_freq_info *next;
+	struct cpufreq_freqs freqs;
+	unsigned long flags;
+	int idx;
+	struct clk *clk;
+
+	if (policy->cpu != 0)
+		return -EINVAL;
+
+	/* don't do anything if the master clock source is not PLLA */
+	clk = clk_get(NULL, "mck");
+	if (strcmp(clk->parent->name, "plla"))
+		return -EINVAL;
+
+	/* Lookup the next frequency */
+	if (cpufreq_frequency_table_target(policy, at91_freqs_table,
+				target_freq, relation, &idx))
+		return -EINVAL;
+
+	next = &at91_freqs[idx];
+
+	freqs.old = policy->cur;
+	freqs.new = next->cpufreq_khz;
+	freqs.cpu = policy->cpu;
+
+	printk("CPU frequency from %d MHz to %d MHz%s\n",
+			freqs.old / 1000, freqs.new / 1000,
+			(freqs.old == freqs.new) ? " (skipped)" : "");
+
+	if (freqs.old == target_freq)
+		return 0;
+
+	cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);
+
+	local_irq_save(flags);
+	__update_core_freq(clk, next);
+	local_irq_restore(flags);
+
+	cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
+
+	return 0;
+}
+
+static __init int at91_cpufreq_init(struct cpufreq_policy *policy)
+{
+	int ret = -EINVAL, nfreqs, i;
+	u32 pres;
+	struct clk *clk;
+	struct at91_freq_info *at91_freqs;
+
+	/* don't do anything if the master clock source is not PLLA */
+	clk = clk_get(NULL, "mck");
+	if (strcmp(clk->parent->name, "plla"))
+		return -EINVAL;
+
+	/* set default policy and cpuinfo */
+	policy->cpuinfo.min_freq = (clk->rate_hz / 1000) / AT91_PMC_PRES_16;
+	policy->cpuinfo.max_freq = clk->rate_hz / 1000;
+	policy->cpuinfo.transition_latency = 1000000; /* FIXME: 1 ms, assumed */
+	policy->cur = policy->min = policy->max = at91_cpufreq_get(policy->cpu);
+
+	pres = at91_sys_read(AT91_PMC_MCKR) & AT91_PMC_PRES;
+	nfreqs = (AT91_PMC_PRES_16 - pres) >> 2;
+	nfreqs = (nfreqs > 0) ? nfreqs + 1 : 1;
+	at91_freqs = kmalloc(sizeof(struct at91_freq_info) * nfreqs, GFP_KERNEL);
+
+	for(i = 0; i < nfreqs; i++)
+	{
+		at91_freqs[i].cpufreq_khz = policy->cur >> (pres >> 2);
+		at91_freqs[i].mckr_pres = pres;
+		pres += 1 << 2;
+	}
+
+	ret = setup_freqs_table(policy, at91_freqs, nfreqs);
+
+	if (ret) {
+		pr_err("failed to setup frequency table\n");
+		kfree(at91_freqs);
+		return ret;
+	}
+
+	pr_info("CPUFREQ support for AT91 initialized\n");
+	return 0;
+}
+
+static struct cpufreq_driver at91_cpufreq_driver = {
+	.verify		= at91_cpufreq_verify,
+	.target		= at91_cpufreq_set,
+	.init		= at91_cpufreq_init,
+	.get		= at91_cpufreq_get,
+	.name		= "at91-cpufreq",
+};
+
+static int __init cpufreq_init(void)
+{
+	return cpufreq_register_driver(&at91_cpufreq_driver);
+}
+module_init(cpufreq_init);
+
+static void __exit cpufreq_exit(void)
+{
+	kfree(at91_freqs);
+	cpufreq_unregister_driver(&at91_cpufreq_driver);
+}
+module_exit(cpufreq_exit);
+
+MODULE_DESCRIPTION("CPU frequency scaling driver for AT91");
+MODULE_LICENSE("GPL");
diff --git a/drivers/serial/atmel_serial.c b/drivers/serial/atmel_serial.c
index 607d43a..5f38b90 100644
--- a/drivers/serial/atmel_serial.c
+++ b/drivers/serial/atmel_serial.c
@@ -38,6 +38,7 @@
 #include <linux/dma-mapping.h>
 #include <linux/atmel_pdc.h>
 #include <linux/atmel_serial.h>
+#include <linux/cpufreq.h>
 
 #include <asm/io.h>
 
@@ -147,6 +148,9 @@ struct atmel_uart_port {
 	unsigned int		irq_status_prev;
 
 	struct circ_buf		rx_ring;
+#ifdef CONFIG_CPU_FREQ
+	struct notifier_block		freq_transition;
+#endif
 };
 
 static struct atmel_uart_port atmel_ports[ATMEL_MAX_UART];
@@ -1525,6 +1529,79 @@ static int atmel_serial_resume(struct platform_device *pdev)
 #define atmel_serial_resume NULL
 #endif
 
+#ifdef CONFIG_CPU_FREQ
+static int atmel_serial_cpufreq_transition(struct notifier_block *nb,
+					     unsigned long val, void *data)
+{
+	struct atmel_uart_port *port;
+	struct uart_port *uport;
+
+	port = container_of(nb, struct atmel_uart_port, freq_transition);
+	uport = &port->uart;
+
+	/* try and work out if the baudrate is changing, we can detect
+	 * a change in rate, but we do not have support for detecting
+	 * a disturbance in the clock-rate over the change.
+	 */
+
+	if (IS_ERR(port->clk))
+		goto exit;
+
+	if (val == CPUFREQ_PRECHANGE) {
+		/* we should really shut the port down whilst the
+		 * frequency change is in progress. */
+
+	} else if (val == CPUFREQ_POSTCHANGE) {
+		struct ktermios *termios;
+		struct tty_struct *tty;
+
+		if (uport->info == NULL)
+			goto exit;
+
+		tty = uport->info->port.tty;
+
+		if (tty == NULL)
+			goto exit;
+
+		termios = tty->termios;
+
+		if (termios == NULL) {
+			printk(KERN_WARNING "%s: no termios?\n", __func__);
+			goto exit;
+		}
+		uport->uartclk = clk_get_rate(port->clk);
+		atmel_set_termios(uport, termios, NULL);
+	}
+	return NOTIFY_OK;
+
+ exit:
+	return NOTIFY_BAD;
+}
+
+static inline int atmel_serial_cpufreq_register(struct atmel_uart_port *port)
+{
+	port->freq_transition.notifier_call = atmel_serial_cpufreq_transition;
+
+	return cpufreq_register_notifier(&port->freq_transition,
+					 CPUFREQ_TRANSITION_NOTIFIER);
+}
+
+static inline void atmel_serial_cpufreq_deregister(struct atmel_uart_port *port)
+{
+	cpufreq_unregister_notifier(&port->freq_transition,
+				    CPUFREQ_TRANSITION_NOTIFIER);
+}
+#else
+static inline int atmel_serial_cpufreq_register(struct atmel_uart_port *port)
+{
+	return 0;
+}
+
+static inline void atmel_serial_cpufreq_deregister(struct atmel_uart_port *port)
+{
+}
+#endif
+
 static int __devinit atmel_serial_probe(struct platform_device *pdev)
 {
 	struct atmel_uart_port *port;
@@ -1561,6 +1638,9 @@ static int __devinit atmel_serial_probe(struct platform_device *pdev)
 		clk_disable(port->clk);
 	}
 #endif
+	ret = atmel_serial_cpufreq_register(port);
+	if (ret < 0)
+		dev_err(&pdev->dev, "failed to add cpufreq notifier\n");
 
 	device_init_wakeup(&pdev->dev, 1);
 	platform_set_drvdata(pdev, port);
@@ -1589,6 +1669,7 @@ static int __devexit atmel_serial_remove(struct platform_device *pdev)
 	platform_set_drvdata(pdev, NULL);
 
 	ret = uart_remove_one_port(&atmel_uart, port);
+	atmel_serial_cpufreq_deregister(atmel_port);
 
 	tasklet_kill(&atmel_port->tasklet);
 	kfree(atmel_port->rx_ring.buf);
-- 
Albin Tonnerre, Free Electrons
Kernel, drivers and embedded Linux development,
consulting, training and support.
http://free-electrons.com

^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [RFC] AT91 cpufreq support
  2009-08-26 19:33 [RFC] AT91 cpufreq support Albin Tonnerre
@ 2009-09-30 15:22 ` Nicolas Ferre
  2009-09-30 16:28   ` Eric Bénard
  0 siblings, 1 reply; 4+ messages in thread
From: Nicolas Ferre @ 2009-09-30 15:22 UTC (permalink / raw)
  To: linux-arm-kernel

Albin Tonnerre :
> Hi there,
> 
> Here is an updated version of the patch that brings cpufreq support on AT91.
> This one is more generic that the previous one: instead of using fixed values,
> it gets the current prescaler value and builds the frequency table accordingly.
> 
> It fixes a couple mistakes from the previous iteration, making it work more
> reliably, at least there. I also added a cpufreq notifier to atmel_serial, so
> that we don't lose the console over a frequency change (I'll move it to a
> separate patch in a further update, and will add such support for the macb
> driver too).
> As there's no cpufreq notifier in the macb driver (yet), using cpufreq with a
> NFS root filesystem will fail (obviously, other uses of the network will
> probably fail too, but at least they don't make the system unusable)
> 
> On a Calao USB-A9263 board, switching from 180MHz to 11MHz reduces the current
> consumption by roughly 65mA
> 
> Testing/feedback would be very much welcome

I am testing your cpufreq patch on at91sam9263ek board.

Indeed it is working quite well. But, I it is true that we will have to
add notifiers for several drivers: my LCD screen gets messy while
reducing frequency ;-)

Anyway, as I see that there is a notifier for serial driver, I wonder if
we can keep serial console connexion during frequency switching ? I
assume that we will have to change the baud rate configuration of the
terminal emulator but do you have a clue to give us (I tried without
success) ?

On my test case: at91sam9263ek without LCD backlight I have those
values: 1,63W @ 199MHz and 1,28W @ 12MHz => 21% gain: good !

Bye-bye,
-- 
Nicolas Ferre

^ permalink raw reply	[flat|nested] 4+ messages in thread

* [RFC] AT91 cpufreq support
  2009-09-30 15:22 ` Nicolas Ferre
@ 2009-09-30 16:28   ` Eric Bénard
  2009-10-03 12:18     ` Albin Tonnerre
  0 siblings, 1 reply; 4+ messages in thread
From: Eric Bénard @ 2009-09-30 16:28 UTC (permalink / raw)
  To: linux-arm-kernel

Nicolas Ferre wrote :
> Anyway, as I see that there is a notifier for serial driver, I wonder if
> we can keep serial console connexion during frequency switching ? I
> assume that we will have to change the baud rate configuration of the
> terminal emulator but do you have a clue to give us (I tried without
> success) ?
> 
once called after the frequency change the notifier function in the 
serial driver updates uartclk and calls atmel_set_termios which should 
update the baudrate generator to the new master clock so you shouldn't 
need to change the baudrate.
To be clean, the serial port may need to be turned off before the 
frequency change in order to prevent garbage chars to be sent on the 
serial port as some USB serial adapters hang when receiving such garbage 
which may explain your problem.

Eric

^ permalink raw reply	[flat|nested] 4+ messages in thread

* [RFC] AT91 cpufreq support
  2009-09-30 16:28   ` Eric Bénard
@ 2009-10-03 12:18     ` Albin Tonnerre
  0 siblings, 0 replies; 4+ messages in thread
From: Albin Tonnerre @ 2009-10-03 12:18 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, 30 Sep 2009 18:28 +0200, Eric B?nard wrote :
> Nicolas Ferre wrote :
> >Anyway, as I see that there is a notifier for serial driver, I wonder if
> >we can keep serial console connexion during frequency switching ? I
> >assume that we will have to change the baud rate configuration of the
> >terminal emulator but do you have a clue to give us (I tried without
> >success) ?

> once called after the frequency change the notifier function in the
> serial driver updates uartclk and calls atmel_set_termios which
> should update the baudrate generator to the new master clock so you
> shouldn't need to change the baudrate.

Unless the clock is too low to reliably generate the baudrate you're asking for.
In particular in asynchronous mode and depending on the value of OVER in US_MR,
you might run into trouble trying to get a 115200 baudrate out of a 12MHz clock.
Limiting the lowest frequency to 24MHz might help there.

> To be clean, the serial port may need to be turned off before the
> frequency change in order to prevent garbage chars to be sent on the
> serial port as some USB serial adapters hang when receiving such
> garbage which may explain your problem.

That might happen, but I don't think it's the problem here - the at91sam9263-ek
doesn't use a usb-serial converter, does it ?

Cheers,
-- 
Albin Tonnerre
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 836 bytes
Desc: Digital signature
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20091003/5a81b874/attachment.sig>

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2009-10-03 12:18 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2009-08-26 19:33 [RFC] AT91 cpufreq support Albin Tonnerre
2009-09-30 15:22 ` Nicolas Ferre
2009-09-30 16:28   ` Eric Bénard
2009-10-03 12:18     ` Albin Tonnerre

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).