[PATCH 3/7] msm: qsd8x50: add acpuclock code

Daniel Walker dwalker at codeaurora.org
Thu Jan 20 15:32:41 EST 2011


This adds acpuclock-qsd8x50.c from Google. This provides control
over the cpu frequency for qsd8x50 SoCs.

Signed-off-by: Daniel Walker <dwalker at codeaurora.org>
---
 arch/arm/mach-msm/Makefile             |    3 +-
 arch/arm/mach-msm/acpuclock-qsd8x50.c  |  457 ++++++++++++++++++++++++++++++++
 arch/arm/mach-msm/board-mahimahi.c     |   24 ++
 arch/arm/mach-msm/include/mach/board.h |    1 +
 4 files changed, 484 insertions(+), 1 deletions(-)
 create mode 100644 arch/arm/mach-msm/acpuclock-qsd8x50.c

diff --git a/arch/arm/mach-msm/Makefile b/arch/arm/mach-msm/Makefile
index 94195c1..9fd3a35 100644
--- a/arch/arm/mach-msm/Makefile
+++ b/arch/arm/mach-msm/Makefile
@@ -1,6 +1,5 @@
 obj-y += io.o idle.o timer.o
 ifndef CONFIG_ARCH_MSM8X60
-obj-y += acpuclock-arm11.o
 obj-y += dma.o
 endif
 
@@ -11,6 +10,8 @@ ifndef CONFIG_ARCH_MSM8X60
 obj-y += irq.o
 endif
 endif
+obj-$(CONFIG_ARCH_MSM_ARM11) += acpuclock-arm11.o
+obj-$(CONFIG_ARCH_QSD8X50) += acpuclock-qsd8x50.o
 
 obj-$(CONFIG_ARCH_MSM8X60) += clock-dummy.o iommu.o iommu_dev.o devices-msm8x60-iommu.o
 obj-$(CONFIG_MSM_PROC_COMM) += proc_comm.o clock-pcom.o vreg.o
diff --git a/arch/arm/mach-msm/acpuclock-qsd8x50.c b/arch/arm/mach-msm/acpuclock-qsd8x50.c
new file mode 100644
index 0000000..c726b54
--- /dev/null
+++ b/arch/arm/mach-msm/acpuclock-qsd8x50.c
@@ -0,0 +1,457 @@
+/*
+ * Copyright (c) 2009 Google, Inc.
+ * Copyright (c) 2008 QUALCOMM Incorporated.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/mutex.h>
+#include <linux/errno.h>
+#include <linux/cpufreq.h>
+#include <linux/regulator/consumer.h>
+
+#include <mach/board.h>
+#include <mach/msm_iomap.h>
+
+#include "acpuclock.h"
+#include "proc_comm.h"
+
+#define SHOT_SWITCH	4
+#define HOP_SWITCH	5
+#define SIMPLE_SLEW	6
+#define COMPLEX_SLEW	7
+
+#define SPSS_CLK_CNTL_ADDR	(MSM_CSR_BASE + 0x100)
+#define SPSS_CLK_SEL_ADDR	(MSM_CSR_BASE + 0x104)
+
+/* Scorpion PLL registers */
+#define SCPLL_CTL_ADDR		(MSM_SCPLL_BASE + 0x4)
+#define SCPLL_STATUS_ADDR	(MSM_SCPLL_BASE + 0x18)
+#define SCPLL_FSM_CTL_EXT_ADDR	(MSM_SCPLL_BASE + 0x10)
+
+struct clkctl_acpu_speed {
+	unsigned acpu_khz;
+	unsigned clk_cfg;
+	unsigned clk_sel;
+	unsigned sc_l_value;
+	unsigned lpj;
+	int      vdd;
+};
+
+/* clock sources */
+#define CLK_TCXO	0 /* 19.2 MHz */
+#define CLK_GLOBAL_PLL	1 /* 768 MHz */
+#define CLK_MODEM_PLL	4 /* 245 MHz (UMTS) or 235.93 MHz (CDMA) */
+
+#define CCTL(src, div) (((src) << 4) | (div - 1))
+
+/* core sources */
+#define SRC_RAW		0 /* clock from SPSS_CLK_CNTL */
+#define SRC_SCPLL	1 /* output of scpll 128-998 MHZ */
+#define SRC_AXI		2 /* 128 MHz */
+#define SRC_PLL1	3 /* 768 MHz */
+
+struct clkctl_acpu_speed acpu_freq_tbl[] = {
+	{  19200, CCTL(CLK_TCXO, 1),		SRC_RAW, 0, 0, 1050 },
+	{ 128000, CCTL(CLK_TCXO, 1),		SRC_AXI, 0, 0, 1050 },
+	{ 245000, CCTL(CLK_MODEM_PLL, 1),	SRC_RAW, 0, 0, 1050 },
+	{ 256000, CCTL(CLK_GLOBAL_PLL, 3),	SRC_RAW, 0, 0, 1050 },
+	{ 384000, CCTL(CLK_TCXO, 1),		SRC_SCPLL, 0x0A, 0, 1050 },
+	{ 422400, CCTL(CLK_TCXO, 1),		SRC_SCPLL, 0x0B, 0, 1050 },
+	{ 460800, CCTL(CLK_TCXO, 1),		SRC_SCPLL, 0x0C, 0, 1050 },
+	{ 499200, CCTL(CLK_TCXO, 1),		SRC_SCPLL, 0x0D, 0, 1075 },
+	{ 537600, CCTL(CLK_TCXO, 1),		SRC_SCPLL, 0x0E, 0, 1100 },
+	{ 576000, CCTL(CLK_TCXO, 1),		SRC_SCPLL, 0x0F, 0, 1100 },
+	{ 614400, CCTL(CLK_TCXO, 1),		SRC_SCPLL, 0x10, 0, 1125 },
+	{ 652800, CCTL(CLK_TCXO, 1),		SRC_SCPLL, 0x11, 0, 1150 },
+	{ 691200, CCTL(CLK_TCXO, 1),		SRC_SCPLL, 0x12, 0, 1175 },
+	{ 729600, CCTL(CLK_TCXO, 1),		SRC_SCPLL, 0x13, 0, 1200 },
+	{ 768000, CCTL(CLK_TCXO, 1),		SRC_SCPLL, 0x14, 0, 1200 },
+	{ 806400, CCTL(CLK_TCXO, 1),		SRC_SCPLL, 0x15, 0, 1225 },
+	{ 844800, CCTL(CLK_TCXO, 1),		SRC_SCPLL, 0x16, 0, 1250 },
+	{ 883200, CCTL(CLK_TCXO, 1),		SRC_SCPLL, 0x17, 0, 1275 },
+	{ 921600, CCTL(CLK_TCXO, 1),		SRC_SCPLL, 0x18, 0, 1275 },
+	{ 960000, CCTL(CLK_TCXO, 1),		SRC_SCPLL, 0x19, 0, 1275 },
+	{ 998400, CCTL(CLK_TCXO, 1),		SRC_SCPLL, 0x1A, 0, 1275 },
+	{ 0 },
+};
+
+/* select the standby clock that is used when switching scpll
+ * frequencies
+ *
+ * Currently: MPLL
+ */
+struct clkctl_acpu_speed *acpu_stby = &acpu_freq_tbl[2];
+#define IS_ACPU_STANDBY(x)	(((x)->clk_cfg == acpu_stby->clk_cfg) && \
+				 ((x)->clk_sel == acpu_stby->clk_sel))
+
+struct clkctl_acpu_speed *acpu_mpll = &acpu_freq_tbl[2];
+
+#ifdef CONFIG_CPU_FREQ_TABLE
+static struct cpufreq_frequency_table freq_table[ARRAY_SIZE(acpu_freq_tbl)];
+
+static void __init acpuclk_init_cpufreq_table(void)
+{
+	int i;
+	int vdd;
+	for (i = 0; acpu_freq_tbl[i].acpu_khz; i++) {
+		freq_table[i].index = i;
+		freq_table[i].frequency = CPUFREQ_ENTRY_INVALID;
+
+		/* Skip speeds using the global pll */
+		if (acpu_freq_tbl[i].acpu_khz == 256000 ||
+				acpu_freq_tbl[i].acpu_khz == 19200)
+			continue;
+
+		vdd = acpu_freq_tbl[i].vdd;
+		/* Allow mpll and the first scpll speeds */
+		if (acpu_freq_tbl[i].acpu_khz == acpu_mpll->acpu_khz ||
+				acpu_freq_tbl[i].acpu_khz == 384000) {
+			freq_table[i].frequency = acpu_freq_tbl[i].acpu_khz;
+			continue;
+		}
+
+		/* Take the fastest speed available at the specified VDD level */
+		if (vdd != acpu_freq_tbl[i + 1].vdd)
+			freq_table[i].frequency = acpu_freq_tbl[i].acpu_khz;
+	}
+
+	freq_table[i].index = i;
+	freq_table[i].frequency = CPUFREQ_TABLE_END;
+
+	cpufreq_frequency_table_get_attr(freq_table, smp_processor_id());
+}
+#else
+#define acpuclk_init_cpufreq_table() do {} while (0);
+#endif
+
+struct clock_state {
+	struct clkctl_acpu_speed	*current_speed;
+	struct mutex			lock;
+	uint32_t			acpu_switch_time_us;
+	uint32_t			max_speed_delta_khz;
+	uint32_t			vdd_switch_time_us;
+	unsigned long			power_collapse_khz;
+	unsigned long			wait_for_irq_khz;
+	struct regulator                *regulator;
+};
+
+static struct clock_state drv_state = { 0 };
+
+static DEFINE_SPINLOCK(acpu_lock);
+
+#define PLLMODE_POWERDOWN	0
+#define PLLMODE_BYPASS		1
+#define PLLMODE_STANDBY		2
+#define PLLMODE_FULL_CAL	4
+#define PLLMODE_HALF_CAL	5
+#define PLLMODE_STEP_CAL	6
+#define PLLMODE_NORMAL		7
+#define PLLMODE_MASK		7
+
+static void scpll_power_down(void)
+{
+	uint32_t val;
+
+	/* Wait for any frequency switches to finish. */
+	while (readl(SCPLL_STATUS_ADDR) & 0x1)
+		;
+
+	/* put the pll in standby mode */
+	val = readl(SCPLL_CTL_ADDR);
+	val = (val & (~PLLMODE_MASK)) | PLLMODE_STANDBY;
+	writel(val, SCPLL_CTL_ADDR);
+	dmb();
+
+	/* wait to stabilize in standby mode */
+	udelay(10);
+
+	val = (val & (~PLLMODE_MASK)) | PLLMODE_POWERDOWN;
+	writel(val, SCPLL_CTL_ADDR);
+	dmb();
+}
+
+static void scpll_set_freq(uint32_t lval)
+{
+	uint32_t val, ctl;
+
+	if (lval > 33)
+		lval = 33;
+	if (lval < 10)
+		lval = 10;
+
+	/* wait for any calibrations or frequency switches to finish */
+	while (readl(SCPLL_STATUS_ADDR) & 0x3)
+		;
+
+	ctl = readl(SCPLL_CTL_ADDR);
+
+	if ((ctl & PLLMODE_MASK) != PLLMODE_NORMAL) {
+		/* put the pll in standby mode */
+		writel((ctl & (~PLLMODE_MASK)) | PLLMODE_STANDBY, SCPLL_CTL_ADDR);
+		dmb();
+
+		/* wait to stabilize in standby mode */
+		udelay(10);
+
+		/* switch to 384 MHz */
+		val = readl(SCPLL_FSM_CTL_EXT_ADDR);
+		val = (val & (~0x1FF)) | (0x0A << 3) | SHOT_SWITCH;
+		writel(val, SCPLL_FSM_CTL_EXT_ADDR);
+		dmb();
+
+		ctl = readl(SCPLL_CTL_ADDR);
+		writel(ctl | PLLMODE_NORMAL, SCPLL_CTL_ADDR);
+		dmb();
+
+		/* wait for frequency switch to finish */
+		while (readl(SCPLL_STATUS_ADDR) & 0x1)
+			;
+
+		/* completion bit is not reliable for SHOT switch */
+		udelay(25);
+	}
+
+	/* write the new L val and switch mode */
+	val = readl(SCPLL_FSM_CTL_EXT_ADDR);
+	val = (val & (~0x1FF)) | (lval << 3) | HOP_SWITCH;
+	writel(val, SCPLL_FSM_CTL_EXT_ADDR);
+	dmb();
+
+	ctl = readl(SCPLL_CTL_ADDR);
+	writel(ctl | PLLMODE_NORMAL, SCPLL_CTL_ADDR);
+	dmb();
+
+	/* wait for frequency switch to finish */
+	while (readl(SCPLL_STATUS_ADDR) & 0x1)
+		;
+}
+
+/* this is still a bit weird... */
+static void select_clock(unsigned src, unsigned config)
+{
+	uint32_t val;
+
+	if (src == SRC_RAW) {
+		uint32_t sel = readl(SPSS_CLK_SEL_ADDR);
+		unsigned shift = (sel & 1) ? 8 : 0;
+
+		/* set other clock source to the new configuration */
+		val = readl(SPSS_CLK_CNTL_ADDR);
+		val = (val & (~(0x7F << shift))) | (config << shift);
+		writel(val, SPSS_CLK_CNTL_ADDR);
+
+		/* switch to other clock source */
+		writel(sel ^ 1, SPSS_CLK_SEL_ADDR);
+
+		dmb(); /* necessary? */
+	}
+
+	/* switch to new source */
+	val = readl(SPSS_CLK_SEL_ADDR) & (~6);
+	writel(val | ((src & 3) << 1), SPSS_CLK_SEL_ADDR);
+}
+
+static int acpuclk_set_vdd_level(int vdd)
+{
+	if (!drv_state.regulator || IS_ERR(drv_state.regulator)) {
+		drv_state.regulator = regulator_get(NULL, "acpu_vcore");
+		if (IS_ERR(drv_state.regulator)) {
+			pr_info("acpuclk_set_vdd_level %d no regulator\n", vdd);
+			/* Assume that the PMIC supports scaling the processor
+			 * to its maximum frequency at its default voltage.
+			 */
+			return 0;
+		}
+		pr_info("acpuclk_set_vdd_level got regulator\n");
+	}
+	vdd *= 1000; /* mV -> uV */
+	return regulator_set_voltage(drv_state.regulator, vdd, vdd);
+}
+
+int acpuclk_set_rate(unsigned long rate, int for_power_collapse)
+{
+	struct clkctl_acpu_speed *cur, *next;
+	unsigned long flags;
+
+	cur = drv_state.current_speed;
+
+	/* convert to KHz */
+	rate /= 1000;
+
+	pr_debug("acpuclk_set_rate(%d,%d)\n", (int) rate, for_power_collapse);
+
+	if (rate == 0 || rate == cur->acpu_khz)
+		return 0;
+
+	next = acpu_freq_tbl;
+	for (;;) {
+		if (next->acpu_khz == rate)
+			break;
+		if (next->acpu_khz == 0)
+			return -EINVAL;
+		next++;
+	}
+
+	if (!for_power_collapse) {
+		mutex_lock(&drv_state.lock);
+		/* Increase VDD if needed. */
+		if (next->vdd > cur->vdd) {
+			if (acpuclk_set_vdd_level(next->vdd)) {
+				pr_err("acpuclock: Unable to increase ACPU VDD.\n");
+				mutex_unlock(&drv_state.lock);
+				return -EINVAL;
+			}
+		}
+	}
+
+	spin_lock_irqsave(&acpu_lock, flags);
+
+	pr_debug("sel=%d cfg=%02x lv=%02x -> sel=%d, cfg=%02x lv=%02x\n",
+	      cur->clk_sel, cur->clk_cfg, cur->sc_l_value,
+	      next->clk_sel, next->clk_cfg, next->sc_l_value);
+
+	if (next->clk_sel == SRC_SCPLL) {
+		if (!IS_ACPU_STANDBY(cur))
+			select_clock(acpu_stby->clk_sel, acpu_stby->clk_cfg);
+		loops_per_jiffy = next->lpj;
+		scpll_set_freq(next->sc_l_value);
+		select_clock(SRC_SCPLL, 0);
+	} else {
+		loops_per_jiffy = next->lpj;
+		if (cur->clk_sel == SRC_SCPLL) {
+			select_clock(acpu_stby->clk_sel, acpu_stby->clk_cfg);
+			select_clock(next->clk_sel, next->clk_cfg);
+			scpll_power_down();
+		} else {
+			select_clock(next->clk_sel, next->clk_cfg);
+		}
+	}
+
+	drv_state.current_speed = next;
+
+	spin_unlock_irqrestore(&acpu_lock, flags);
+	if (!for_power_collapse) {
+		/* Drop VDD level if we can. */
+		if (next->vdd < cur->vdd) {
+			if (acpuclk_set_vdd_level(next->vdd))
+				pr_err("acpuclock: Unable to drop ACPU VDD.\n");
+		}
+		mutex_unlock(&drv_state.lock);
+	}
+
+	return 0;
+}
+
+static unsigned __init acpuclk_find_speed(void)
+{
+	uint32_t sel, val;
+
+	sel = readl(SPSS_CLK_SEL_ADDR);
+	switch ((sel & 6) >> 1) {
+	case 1:
+		val = readl(SCPLL_FSM_CTL_EXT_ADDR);
+		val = (val >> 3) & 0x3f;
+		return val * 38400;
+	case 2:
+		return 128000;
+	default:
+		pr_err("acpu_find_speed: failed\n");
+		BUG();
+		return 0;
+	}
+}
+
+#define PCOM_MODEM_PLL	0
+static int pll_request(unsigned id, unsigned on)
+{
+	on = !!on;
+	return msm_proc_comm(PCOM_CLKCTL_RPC_PLL_REQUEST, &id, &on);
+}
+
+static void __init acpuclk_init(void)
+{
+	struct clkctl_acpu_speed *speed;
+	unsigned init_khz;
+
+	init_khz = acpuclk_find_speed();
+
+	/* request the modem pll, and then drop it. We don't want to keep a
+	 * ref to it, but we do want to make sure that it is initialized at
+	 * this point. The ARM9 will ensure that the MPLL is always on
+	 * once it is fully booted, but it may not be up by the time we get
+	 * to here. So, our pll_request for it will block until the mpll is
+	 * actually up. We want it up because we will want to use it as a
+	 * temporary step during frequency scaling. */
+	pll_request(PCOM_MODEM_PLL, 1);
+	pll_request(PCOM_MODEM_PLL, 0);
+
+	if (!(readl(MSM_CLK_CTL_BASE + 0x300) & 1)) {
+		pr_err("%s: MPLL IS NOT ON!!! RUN AWAY!!\n", __func__);
+		BUG();
+	}
+
+	/* Move to 768MHz for boot, which is a safe frequency
+	 * for all versions of Scorpion at the moment.
+	 */
+	speed = acpu_freq_tbl;
+	for (;;) {
+		if (speed->acpu_khz == 768000)
+			break;
+		if (speed->acpu_khz == 0) {
+			pr_err("acpuclk_init: cannot find 768MHz\n");
+			BUG();
+		}
+		speed++;
+	}
+
+	if (init_khz != speed->acpu_khz) {
+		/* Bootloader needs to have SCPLL operating, but we're
+		 * going to step over to the standby clock and make sure
+		 * we select the right frequency on SCPLL and then
+		 * step back to it, to make sure we're sane here.
+		 */
+		select_clock(acpu_stby->clk_sel, acpu_stby->clk_cfg);
+		scpll_power_down();
+		scpll_set_freq(speed->sc_l_value);
+		select_clock(SRC_SCPLL, 0);
+	}
+	drv_state.current_speed = speed;
+
+	for (speed = acpu_freq_tbl; speed->acpu_khz; speed++)
+		speed->lpj = cpufreq_scale(loops_per_jiffy,
+					   init_khz, speed->acpu_khz);
+
+	loops_per_jiffy = drv_state.current_speed->lpj;
+}
+
+void __init msm_acpu_clock_init(struct msm_acpu_clock_platform_data *clkdata)
+{
+	spin_lock_init(&acpu_lock);
+	mutex_init(&drv_state.lock);
+
+	drv_state.acpu_switch_time_us = clkdata->acpu_switch_time_us;
+	drv_state.max_speed_delta_khz = clkdata->max_speed_delta_khz;
+	drv_state.vdd_switch_time_us = clkdata->vdd_switch_time_us;
+	drv_state.power_collapse_khz = clkdata->power_collapse_khz;
+	drv_state.wait_for_irq_khz = clkdata->wait_for_irq_khz;
+
+	if (clkdata->mpll_khz)
+		acpu_mpll->acpu_khz = clkdata->mpll_khz;
+
+	acpuclk_init();
+	acpuclk_init_cpufreq_table();
+}
diff --git a/arch/arm/mach-msm/board-mahimahi.c b/arch/arm/mach-msm/board-mahimahi.c
index ef3ebf2..aedb136 100644
--- a/arch/arm/mach-msm/board-mahimahi.c
+++ b/arch/arm/mach-msm/board-mahimahi.c
@@ -48,8 +48,32 @@ static struct platform_device *devices[] __initdata = {
 	&msm_device_nand,
 };
 
+static struct msm_acpu_clock_platform_data mahimahi_clock_data = {
+	.acpu_switch_time_us	= 20,
+	.max_speed_delta_khz	= 256000,
+	.vdd_switch_time_us	= 62,
+	.power_collapse_khz	= 245000,
+	.wait_for_irq_khz	= 245000,
+	.mpll_khz		= 245000
+};
+
+static struct msm_acpu_clock_platform_data mahimahi_cdma_clock_data = {
+	.acpu_switch_time_us	= 20,
+	.max_speed_delta_khz	= 256000,
+	.vdd_switch_time_us	= 62,
+	.power_collapse_khz	= 235930,
+	.wait_for_irq_khz	= 235930,
+	.mpll_khz		= 235930
+};
+
+
 static void __init mahimahi_init(void)
 {
+	if (is_cdma_version(system_rev))
+		msm_acpu_clock_init(&mahimahi_cdma_clock_data);
+	else
+		msm_acpu_clock_init(&mahimahi_clock_data);
+
 	platform_add_devices(devices, ARRAY_SIZE(devices));
 }
 
diff --git a/arch/arm/mach-msm/include/mach/board.h b/arch/arm/mach-msm/include/mach/board.h
index 6abf4a6..44efaf5 100644
--- a/arch/arm/mach-msm/include/mach/board.h
+++ b/arch/arm/mach-msm/include/mach/board.h
@@ -27,6 +27,7 @@ struct msm_acpu_clock_platform_data
 	uint32_t acpu_switch_time_us;
 	uint32_t max_speed_delta_khz;
 	uint32_t vdd_switch_time_us;
+	unsigned long mpll_khz;
 	unsigned long power_collapse_khz;
 	unsigned long wait_for_irq_khz;
 };
-- 
1.7.0.4

-- 
Sent by a consultant of the Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum.



More information about the linux-arm-kernel mailing list