diff mbox

[1/2] davinci: Add cpufreq support

Message ID 1246886001-2285-1-git-send-email-nsekhar@ti.com (mailing list archive)
State Superseded
Headers show

Commit Message

Sekhar Nori July 6, 2009, 1:13 p.m. UTC
This patch adds minimal cpufreq support for DaVinci devices.

The patch implements cpufreq driver, support to change PLL output rate and
recalculation of the rates of PLL derived clocks

Tested using OMAP-L138 EVM.

Signed-off-by: Sekhar Nori <nsekhar@ti.com>
---
This patch set depends on the two patch set posted by Sudhakar adding
DA850/OMAP-L138 support.

 arch/arm/Kconfig                            |    2 +-
 arch/arm/mach-davinci/Makefile              |    3 +
 arch/arm/mach-davinci/clock.c               |  105 +++++++++++++++-
 arch/arm/mach-davinci/clock.h               |    9 ++
 arch/arm/mach-davinci/cpu-davinci.c         |  179 +++++++++++++++++++++++++++
 arch/arm/mach-davinci/include/mach/common.h |    3 +
 6 files changed, 297 insertions(+), 4 deletions(-)
 create mode 100644 arch/arm/mach-davinci/cpu-davinci.c

Comments

David Brownell July 6, 2009, 7:04 p.m. UTC | #1
On Monday 06 July 2009, Sekhar Nori wrote:
> The patch implements cpufreq driver, support to change PLL
> output rate and recalculation of the rates of PLL derived clocks

It seems to me this is missing some sanity checks.

First, that the DRAM isn't being clocked through this PLL...
since changing the PLL would imply recalculating all of its
timings, and might require running the re-clock from SRAM.

Second, similar issues crop up with every clock derived
from that same PLL.  If you change the clock going into
the MMC controller, that can require recalculating the
dividers used to clock the MMC/SD card it's talking to.

Now, those are issues the clock framework handles poorly.
So I don't think there are likely to be easy fixes for
those PLL recalc problems ... unless I missed something.

It might be simpler to just restrict a first pass of
this to changing dividers for the ARM's clock.


Also, this patch is doing two separate things.  One is
adding clk_set_rate() support for PLLs.  The other is
matching $SUBJECT.  Better to split those two.

(And notice how your patch [2/2] hit that second issue
already, with the UART2 clock getting goofed ...)

- Dave
Sekhar Nori July 7, 2009, 6:28 a.m. UTC | #2
On Tue, Jul 07, 2009 at 00:34:23, David Brownell wrote:
> On Monday 06 July 2009, Sekhar Nori wrote:
> > The patch implements cpufreq driver, support to change PLL
> > output rate and recalculation of the rates of PLL derived clocks
>
> It seems to me this is missing some sanity checks.
>
> First, that the DRAM isn't being clocked through this PLL...
> since changing the PLL would imply recalculating all of its
> timings, and might require running the re-clock from SRAM.
>
> Second, similar issues crop up with every clock derived
> from that same PLL.  If you change the clock going into
> the MMC controller, that can require recalculating the
> dividers used to clock the MMC/SD card it's talking to.
>
> Now, those are issues the clock framework handles poorly.
> So I don't think there are likely to be easy fixes for
> those PLL recalc problems ... unless I missed something.

All of these are valid side effects of changing the
PLL frequency runtime. But, this patch just implements
the functionality of changing the rate of a given PLL
without worrying too much about the side effects.

I think the responsibility of taking care of the
side effects should lie with the particular <soc>.c
file or other caller which is attempting to change
the rate.

The side affects are different for different SoCs.

For OMAP-L138, changing the PLL0 rate doesn't affect
the mDDR as that clock is derived from PLL1. But there
are a bunch of other peripherals which will get affected.

I think peripheral rate changes are taken care of by
the CPUFreq pre- and post- rate change notification
mechanism (which I must admit I am not on top of right
now).

>
> It might be simpler to just restrict a first pass of
> this to changing dividers for the ARM's clock.
>

This is not straightforward either; most of the SYSCLKs have
fixed ratios with other SYSCLKs - so changing one affects
others. Example on OMAP-L138, SYSCLK6 (ARM clock) and SYSCLK2
(MMC/SD0 etc) are tied 1:2.

>
> Also, this patch is doing two separate things.  One is
> adding clk_set_rate() support for PLLs.  The other is
> matching $SUBJECT.  Better to split those two.

Okay sure.

>
> (And notice how your patch [2/2] hit that second issue
> already, with the UART2 clock getting goofed ...)
>

Yes, and da850.c takes care of it in its own specific
way, by moving UART2 to a separate clock domain.

I agree code to take care of all frequency change
side-effects is not there yet. I submitted the clock
rate change code early to get some feedback (and
hopefully acceptance :) going. As we add more
peripheral capability, we will also need support
to get it working correctly with CPUFreq.

Thanks,
Sekhar
diff mbox

Patch

diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
index 915b393..79411ce 100644
--- a/arch/arm/Kconfig
+++ b/arch/arm/Kconfig
@@ -1189,7 +1189,7 @@  endmenu
 
 menu "CPU Power Management"
 
-if (ARCH_SA1100 || ARCH_INTEGRATOR || ARCH_OMAP || ARCH_IMX || ARCH_PXA)
+if (ARCH_SA1100 || ARCH_INTEGRATOR || ARCH_OMAP || ARCH_IMX || ARCH_PXA || ARCH_DAVINCI)
 
 source "drivers/cpufreq/Kconfig"
 
diff --git a/arch/arm/mach-davinci/Makefile b/arch/arm/mach-davinci/Makefile
index 2e11e84..14b9527 100644
--- a/arch/arm/mach-davinci/Makefile
+++ b/arch/arm/mach-davinci/Makefile
@@ -29,3 +29,6 @@  obj-$(CONFIG_MACH_DAVINCI_DM6467_EVM)	+= board-dm646x-evm.o
 obj-$(CONFIG_MACH_DAVINCI_DM365_EVM)	+= board-dm365-evm.o
 obj-$(CONFIG_MACH_DAVINCI_DA830_EVM)	+= board-da830-evm.o
 obj-$(CONFIG_MACH_DAVINCI_DA850_EVM)	+= board-da850-evm.o
+
+# Power Management
+obj-$(CONFIG_CPU_FREQ)			+= cpu-davinci.o
diff --git a/arch/arm/mach-davinci/clock.c b/arch/arm/mach-davinci/clock.c
index 83d54d5..c284a17 100644
--- a/arch/arm/mach-davinci/clock.c
+++ b/arch/arm/mach-davinci/clock.c
@@ -19,6 +19,7 @@ 
 #include <linux/mutex.h>
 #include <linux/platform_device.h>
 #include <linux/io.h>
+#include <linux/delay.h>
 
 #include <mach/hardware.h>
 
@@ -99,17 +100,27 @@  long clk_round_rate(struct clk *clk, unsigned long rate)
 	if (clk == NULL || IS_ERR(clk))
 		return -EINVAL;
 
+	if (clk->round_rate)
+		return clk->round_rate(clk, rate);
+
 	return clk->rate;
 }
 EXPORT_SYMBOL(clk_round_rate);
 
 int clk_set_rate(struct clk *clk, unsigned long rate)
 {
+	unsigned long flags;
+	int ret = -EINVAL;
+
 	if (clk == NULL || IS_ERR(clk))
-		return -EINVAL;
+		return ret;
 
-	/* changing the clk rate is not supported */
-	return -EINVAL;
+	spin_lock_irqsave(&clockfw_lock, flags);
+	if (clk->set_rate)
+		ret = clk->set_rate(clk, rate);
+	spin_unlock_irqrestore(&clockfw_lock, flags);
+
+	return ret;
 }
 EXPORT_SYMBOL(clk_set_rate);
 
@@ -273,6 +284,94 @@  static void __init clk_pll_init(struct clk *clk)
 	pr_debug("] --> %lu MHz output.\n", clk->rate / 1000000);
 }
 
+/**
+ * davinci_set_pllrate - set the output rate of a given PLL
+ *
+ * @pll: pll whose rate needs to be changed.
+ * @prediv: prediv value to be programmed. Passing 0 disables prediv.
+ * @pllm: pllm value to be programmed. Passing 0 leads to multiply-by-one.
+ * @postdiv: postdiv value to be programmed. Passing 0 disables postdiv.
+ */
+int davinci_set_pllrate(struct pll_data *pll, unsigned int prediv,
+					unsigned int mult, unsigned int postdiv)
+{
+	u32 ctrl;
+
+	BUG_ON(pll->base == NULL);
+
+	if (prediv)
+		prediv = (prediv - 1) | PLLDIV_EN;
+	if (postdiv)
+		postdiv = (postdiv - 1) | PLLDIV_EN;
+	if (mult)
+		mult = mult - 1;
+
+	ctrl = __raw_readl(pll->base + PLLCTL);
+
+	/* Switch the PLL to bypass mode */
+	ctrl &= ~(PLLCTL_PLLENSRC | PLLCTL_PLLEN);
+	__raw_writel(ctrl, pll->base + PLLCTL);
+
+	/*
+	 * Wait for 4 OSCIN/CLKIN cycles to ensure that the PLLC has switched
+	 * to bypass mode = 4/25 us ~= 1 us
+	 */
+	udelay(1);
+
+	/* Reset and enable PLL */
+	ctrl &= ~(PLLCTL_PLLRST | PLLCTL_PLLDIS);
+	__raw_writel(ctrl, pll->base + PLLCTL);
+
+	if (pll->flags & PLL_HAS_PREDIV)
+		__raw_writel(prediv, pll->base + PREDIV);
+
+	__raw_writel(mult, pll->base + PLLM);
+
+	if (pll->flags & PLL_HAS_POSTDIV)
+		__raw_writel(postdiv, pll->base + POSTDIV);
+
+	/* Bring PLL out of reset */
+	ctrl |= PLLCTL_PLLRST;
+	__raw_writel(ctrl, pll->base + PLLCTL);
+
+	/*
+	 * Wait for PLL to lock. FIXME: justify this value. TRM remains
+	 * ambiguous as well
+	 */
+	udelay(100);
+
+	/* Remove PLL from bypass mode */
+	ctrl |= PLLCTL_PLLEN;
+	__raw_writel(ctrl, pll->base + PLLCTL);
+
+	return 0;
+}
+EXPORT_SYMBOL(davinci_set_pllrate);
+
+/**
+ * davinci_clk_recal_rates - recalculate rates of the davinci clock tree
+ *
+ * @clocks: pointer to the clock tree
+ */
+int davinci_clk_recalc_rates(struct davinci_clk *clocks)
+{
+	struct davinci_clk *c;
+	struct clk *clk;
+
+	for (c = clocks; c->lk.clk; c++) {
+		clk = c->lk.clk;
+
+		/* Re-calculate rates for PLL-derived clocks */
+		if (!clk->pll_data && clk->flags & CLK_PLL)
+			clk_sysclk_recalc(clk);
+		else if (clk->flags & CLK_PSC)
+			clk->rate = clk->parent->rate;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(davinci_clk_recalc_rates);
+
 int __init davinci_clk_init(struct davinci_clk *clocks)
   {
 	struct davinci_clk *c;
diff --git a/arch/arm/mach-davinci/clock.h b/arch/arm/mach-davinci/clock.h
index 27233cb..f772e6e 100644
--- a/arch/arm/mach-davinci/clock.h
+++ b/arch/arm/mach-davinci/clock.h
@@ -22,6 +22,10 @@ 
 /* PLL/Reset register offsets */
 #define PLLCTL          0x100
 #define PLLCTL_PLLEN    BIT(0)
+#define PLLCTL_PLLPWRDN	BIT(1)
+#define PLLCTL_PLLRST	BIT(3)
+#define PLLCTL_PLLDIS	BIT(4)
+#define PLLCTL_PLLENSRC	BIT(5)
 #define PLLCTL_CLKMODE  BIT(8)
 
 #define PLLM		0x110
@@ -71,6 +75,8 @@  struct clk {
 	struct clk              *parent;
 	struct pll_data         *pll_data;
 	u32                     div_reg;
+	int (*set_rate) (struct clk *clk, unsigned long rate);
+	int (*round_rate) (struct clk *clk, unsigned long rate);
 };
 
 /* Clock flags */
@@ -94,6 +100,9 @@  struct davinci_clk {
 	}
 
 int davinci_clk_init(struct davinci_clk *clocks);
+int davinci_clk_recalc_rates(struct davinci_clk *clocks);
+int davinci_set_pllrate(struct pll_data *pll, unsigned int prediv,
+				unsigned int mult, unsigned int postdiv);
 
 extern struct platform_device davinci_wdt_device;
 
diff --git a/arch/arm/mach-davinci/cpu-davinci.c b/arch/arm/mach-davinci/cpu-davinci.c
new file mode 100644
index 0000000..52527f1
--- /dev/null
+++ b/arch/arm/mach-davinci/cpu-davinci.c
@@ -0,0 +1,179 @@ 
+/*
+ * CPU frequency scaling for DaVinci
+ *
+ * Copyright (C) 2009 Texas Instruments Incorporated - http://www.ti.com/
+ *
+ * Based on linux/arch/arm/plat-omap/cpu-omap.c. Original Copyright follows:
+ *
+ *  Copyright (C) 2005 Nokia Corporation
+ *  Written by Tony Lindgren <tony@atomide.com>
+ *
+ *  Based on cpu-sa1110.c, Copyright (C) 2001 Russell King
+ *
+ * Copyright (C) 2007-2008 Texas Instruments, Inc.
+ * Updated to support OMAP3
+ * Rajendra Nayak <rnayak@ti.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.
+ */
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/cpufreq.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+
+#include <mach/hardware.h>
+#include <asm/system.h>
+#include <mach/clock.h>
+#include <mach/common.h>
+
+#include "clock.h"
+
+#define VERY_HI_RATE	900000000
+
+static struct cpufreq_frequency_table *freq_table;
+static struct clk *armclk;
+
+static int davinci_verify_speed(struct cpufreq_policy *policy)
+{
+	if (freq_table)
+		return cpufreq_frequency_table_verify(policy, freq_table);
+
+	if (policy->cpu)
+		return -EINVAL;
+
+	cpufreq_verify_within_limits(policy, policy->cpuinfo.min_freq,
+				     policy->cpuinfo.max_freq);
+
+	policy->min = clk_round_rate(armclk, policy->min * 1000) / 1000;
+	policy->max = clk_round_rate(armclk, policy->max * 1000) / 1000;
+	cpufreq_verify_within_limits(policy, policy->cpuinfo.min_freq,
+				     policy->cpuinfo.max_freq);
+	return 0;
+}
+
+static unsigned int davinci_getspeed(unsigned int cpu)
+{
+	unsigned long rate;
+
+	if (cpu)
+		return 0;
+
+	rate = clk_get_rate(armclk) / 1000;
+
+	return rate;
+}
+
+static int davinci_target(struct cpufreq_policy *policy,
+		       unsigned int target_freq,
+		       unsigned int relation)
+{
+	struct cpufreq_freqs freqs;
+	int ret = 0;
+
+	/* Ensure desired rate is within allowed range.  Some govenors
+	 * (ondemand) will just pass target_freq=0 to get the minimum. */
+	if (target_freq < policy->cpuinfo.min_freq)
+		target_freq = policy->cpuinfo.min_freq;
+	if (target_freq > policy->cpuinfo.max_freq)
+		target_freq = policy->cpuinfo.max_freq;
+
+	freqs.old = davinci_getspeed(0);
+	freqs.new = clk_round_rate(armclk, target_freq * 1000) / 1000;
+	freqs.cpu = 0;
+
+	if (freqs.old == freqs.new)
+		return ret;
+	cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);
+#ifdef CONFIG_CPU_FREQ_DEBUG
+	printk(KERN_DEBUG "cpufreq-davinci: transition: %u --> %u\n",
+	       freqs.old, freqs.new);
+#endif
+	ret = clk_set_rate(armclk, freqs.new * 1000);
+	cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
+
+	return ret;
+}
+
+static int __init davinci_cpu_init(struct cpufreq_policy *policy)
+{
+	int result = 0;
+
+	armclk = clk_get(NULL, "arm");
+	if (IS_ERR(armclk)) {
+		printk(KERN_ERR "cpufreq-davinci: Unable to get ARM clock\n");
+		return PTR_ERR(armclk);
+	}
+
+	if (policy->cpu != 0) {
+		clk_put(armclk);
+		return -EINVAL;
+	}
+
+	policy->cur = policy->min = policy->max = davinci_getspeed(0);
+
+	davinci_soc_info.init_cpufreq_table(&freq_table);
+	if (freq_table) {
+		result = cpufreq_frequency_table_cpuinfo(policy, freq_table);
+		if (!result)
+			cpufreq_frequency_table_get_attr(freq_table,
+							policy->cpu);
+	} else {
+		policy->cpuinfo.min_freq = clk_round_rate(armclk, 0) / 1000;
+		policy->cpuinfo.max_freq = clk_round_rate(armclk,
+							VERY_HI_RATE) / 1000;
+	}
+
+	clk_set_rate(armclk, policy->cpuinfo.max_freq * 1000);
+
+	policy->min = policy->cpuinfo.min_freq;
+	policy->max = policy->cpuinfo.max_freq;
+	policy->cur = davinci_getspeed(0);
+
+	/* FIXME: what's the actual transition time? */
+	policy->cpuinfo.transition_latency = 1 * 1000 * 1000;
+	return 0;
+}
+
+static int davinci_cpu_exit(struct cpufreq_policy *policy)
+{
+	clk_put(armclk);
+	return 0;
+}
+
+static struct freq_attr *davinci_cpufreq_attr[] = {
+	&cpufreq_freq_attr_scaling_available_freqs,
+	NULL,
+};
+
+static struct cpufreq_driver davinci_driver = {
+	.flags		= CPUFREQ_STICKY,
+	.verify		= davinci_verify_speed,
+	.target		= davinci_target,
+	.get		= davinci_getspeed,
+	.init		= davinci_cpu_init,
+	.exit		= davinci_cpu_exit,
+	.name		= "davinci",
+	.attr		= davinci_cpufreq_attr,
+};
+
+static int __init davinci_cpufreq_init(void)
+{
+	return cpufreq_register_driver(&davinci_driver);
+}
+
+late_initcall(davinci_cpufreq_init);
+
+/*
+ * if ever we want to remove this, upon cleanup call:
+ *
+ * cpufreq_unregister_driver()
+ * cpufreq_frequency_table_put_attr()
+ */
+
diff --git a/arch/arm/mach-davinci/include/mach/common.h b/arch/arm/mach-davinci/include/mach/common.h
index 1fd3917..1487a57 100644
--- a/arch/arm/mach-davinci/include/mach/common.h
+++ b/arch/arm/mach-davinci/include/mach/common.h
@@ -12,6 +12,8 @@ 
 #ifndef __ARCH_ARM_MACH_DAVINCI_COMMON_H
 #define __ARCH_ARM_MACH_DAVINCI_COMMON_H
 
+#include <linux/cpufreq.h>
+
 struct sys_timer;
 
 extern struct sys_timer davinci_timer;
@@ -68,6 +70,7 @@  struct davinci_soc_info {
 	struct emac_platform_data	*emac_pdata;
 	dma_addr_t			sram_dma;
 	unsigned			sram_len;
+	void (*init_cpufreq_table) (struct cpufreq_frequency_table **);
 };
 
 extern struct davinci_soc_info davinci_soc_info;