msm: acpuclock-9615: Introduce the MDM9615 acpuclock driver
Only basic CPU scaling is supported at this time. Voltage
and bus scaling will be added later once prerequisite
drivers are available.
Signed-off-by: Matt Wagantall <mattw@codeaurora.org>
diff --git a/arch/arm/mach-msm/Makefile b/arch/arm/mach-msm/Makefile
index eb6f5a1..382bdd3 100644
--- a/arch/arm/mach-msm/Makefile
+++ b/arch/arm/mach-msm/Makefile
@@ -205,7 +205,7 @@
obj-$(CONFIG_ARCH_MSM8960) += bms-batterydata.o
obj-$(CONFIG_ARCH_APQ8064) += board-apq8064.o devices-8064.o board-apq8064-regulator.o
obj-$(CONFIG_ARCH_MSM9615) += board-9615.o devices-9615.o
-obj-$(CONFIG_ARCH_MSM9615) += clock-local.o clock-9615.o
+obj-$(CONFIG_ARCH_MSM9615) += clock-local.o clock-9615.o acpuclock-9615.o
obj-$(CONFIG_MACH_SAPPHIRE) += board-sapphire.o board-sapphire-gpio.o
obj-$(CONFIG_MACH_SAPPHIRE) += board-sapphire-keypad.o board-sapphire-panel.o
diff --git a/arch/arm/mach-msm/acpuclock-9615.c b/arch/arm/mach-msm/acpuclock-9615.c
new file mode 100644
index 0000000..e12caeb
--- /dev/null
+++ b/arch/arm/mach-msm/acpuclock-9615.c
@@ -0,0 +1,212 @@
+/*
+ * Copyright (c) 2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/mutex.h>
+#include <linux/spinlock.h>
+#include <linux/errno.h>
+#include <linux/cpufreq.h>
+#include <linux/clk.h>
+
+#include <asm/cpu.h>
+
+#include <mach/board.h>
+#include <mach/msm_iomap.h>
+
+#include "acpuclock.h"
+
+#define REG_CLKSEL_0 (MSM_APCS_GLB_BASE + 0x08)
+#define REG_CLKDIV_0 (MSM_APCS_GLB_BASE + 0x0C)
+#define REG_CLKSEL_1 (MSM_APCS_GLB_BASE + 0x10)
+#define REG_CLKDIV_1 (MSM_APCS_GLB_BASE + 0x14)
+#define REG_CLKOUTSEL (MSM_APCS_GLB_BASE + 0x18)
+
+enum clk_src {
+ SRC_CXO,
+ SRC_PLL0,
+ SRC_PLL8,
+ SRC_PLL9,
+ NUM_SRC,
+};
+
+struct src_clock {
+ struct clk *clk;
+ const char *name;
+};
+
+static struct src_clock clocks[NUM_SRC] = {
+ [SRC_CXO].name = "cxo",
+ [SRC_PLL0].name = "pll0",
+ [SRC_PLL8].name = "pll8",
+ [SRC_PLL9].name = "pll9",
+};
+
+struct clkctl_acpu_speed {
+ bool use_for_scaling;
+ unsigned int khz;
+ int src;
+ unsigned int src_sel;
+ unsigned int src_div;
+};
+
+struct acpuclk_state {
+ struct mutex lock;
+ struct clkctl_acpu_speed *current_speed;
+};
+
+static struct acpuclk_state drv_state = {
+ .current_speed = &(struct clkctl_acpu_speed){ 0 },
+};
+
+static struct clkctl_acpu_speed acpu_freq_tbl[] = {
+ { 0, 19200, SRC_CXO, 0, 0 },
+ { 1, 138000, SRC_PLL0, 6, 1 },
+ { 1, 276000, SRC_PLL0, 6, 0 },
+ { 1, 384000, SRC_PLL8, 3, 0 },
+ { 1, 440000, SRC_PLL9, 2, 0 },
+ { 0 }
+};
+
+static void select_clk_source_div(struct clkctl_acpu_speed *s)
+{
+ static void * __iomem const sel_reg[] = {REG_CLKSEL_0, REG_CLKSEL_1};
+ static void * __iomem const div_reg[] = {REG_CLKDIV_0, REG_CLKDIV_1};
+ uint32_t next_bank;
+
+ next_bank = !(readl_relaxed(REG_CLKOUTSEL) & 1);
+ writel_relaxed(s->src_sel, sel_reg[next_bank]);
+ writel_relaxed(s->src_div, div_reg[next_bank]);
+ writel_relaxed(next_bank, REG_CLKOUTSEL);
+
+ /* Wait for switch to complete. */
+ mb();
+ udelay(1);
+}
+
+static int acpuclk_9615_set_rate(int cpu, unsigned long rate,
+ enum setrate_reason reason)
+{
+ struct clkctl_acpu_speed *tgt_s, *strt_s;
+ int rc = 0;
+
+ if (reason == SETRATE_CPUFREQ)
+ mutex_lock(&drv_state.lock);
+
+ strt_s = drv_state.current_speed;
+
+ /* Return early if rate didn't change. */
+ if (rate == strt_s->khz)
+ goto out;
+
+ /* Find target frequency. */
+ for (tgt_s = acpu_freq_tbl; tgt_s->khz != 0; tgt_s++)
+ if (tgt_s->khz == rate)
+ break;
+ if (tgt_s->khz == 0) {
+ rc = -EINVAL;
+ goto out;
+ }
+
+ pr_info("Switching from CPU rate %u KHz -> %u KHz\n",
+ strt_s->khz, tgt_s->khz);
+
+ /* Switch CPU speed. */
+ clk_enable(clocks[tgt_s->src].clk);
+ select_clk_source_div(tgt_s);
+ clk_disable(clocks[strt_s->src].clk);
+
+ drv_state.current_speed = tgt_s;
+ pr_info("CPU speed change complete\n");
+
+out:
+ if (reason == SETRATE_CPUFREQ)
+ mutex_unlock(&drv_state.lock);
+ return rc;
+}
+
+static unsigned long acpuclk_9615_get_rate(int cpu)
+{
+ return drv_state.current_speed->khz;
+}
+
+#ifdef CONFIG_CPU_FREQ_MSM
+static struct cpufreq_frequency_table freq_table[30];
+
+static void __init cpufreq_table_init(void)
+{
+ int i, freq_cnt = 0;
+
+ /* Construct the freq_table tables from acpu_freq_tbl. */
+ for (i = 0; acpu_freq_tbl[i].khz != 0
+ && freq_cnt < ARRAY_SIZE(freq_table); i++) {
+ if (acpu_freq_tbl[i].use_for_scaling) {
+ freq_table[freq_cnt].index = freq_cnt;
+ freq_table[freq_cnt].frequency
+ = acpu_freq_tbl[i].khz;
+ freq_cnt++;
+ }
+ }
+ /* freq_table not big enough to store all usable freqs. */
+ BUG_ON(acpu_freq_tbl[i].khz != 0);
+
+ freq_table[freq_cnt].index = freq_cnt;
+ freq_table[freq_cnt].frequency = CPUFREQ_TABLE_END;
+
+ pr_info("CPU: %d scaling frequencies supported.\n", freq_cnt);
+
+ /* Register table with CPUFreq. */
+ cpufreq_frequency_table_get_attr(freq_table, smp_processor_id());
+}
+#else
+static void __init cpufreq_table_init(void) {}
+#endif
+
+static struct acpuclk_data acpuclk_9615_data = {
+ .set_rate = acpuclk_9615_set_rate,
+ .get_rate = acpuclk_9615_get_rate,
+ .power_collapse_khz = 19200,
+ .wait_for_irq_khz = 19200,
+};
+
+static int __init acpuclk_9615_init(struct acpuclk_soc_data *soc_data)
+{
+ unsigned long max_cpu_khz = 0;
+ int i;
+
+ mutex_init(&drv_state.lock);
+ for (i = 0; i < NUM_SRC; i++) {
+ if (clocks[i].name) {
+ clocks[i].clk = clk_get_sys(NULL, clocks[i].name);
+ BUG_ON(IS_ERR(clocks[i].clk));
+ }
+ }
+
+ /* Improve boot time by ramping up CPU immediately. */
+ for (i = 0; acpu_freq_tbl[i].khz != 0; i++)
+ max_cpu_khz = acpu_freq_tbl[i].khz;
+ acpuclk_9615_set_rate(smp_processor_id(), max_cpu_khz, SETRATE_INIT);
+
+ acpuclk_register(&acpuclk_9615_data);
+ cpufreq_table_init();
+
+ return 0;
+}
+
+struct acpuclk_soc_data acpuclk_9615_soc_data __initdata = {
+ .init = acpuclk_9615_init,
+};
diff --git a/arch/arm/mach-msm/acpuclock.h b/arch/arm/mach-msm/acpuclock.h
index 77b47ad..2b188d8 100644
--- a/arch/arm/mach-msm/acpuclock.h
+++ b/arch/arm/mach-msm/acpuclock.h
@@ -108,4 +108,6 @@
extern struct acpuclk_soc_data acpuclk_8x60_soc_data;
extern struct acpuclk_soc_data acpuclk_8960_soc_data;
extern struct acpuclk_soc_data acpuclk_9xxx_soc_data;
+extern struct acpuclk_soc_data acpuclk_9615_soc_data;
+
#endif
diff --git a/arch/arm/mach-msm/devices-9615.c b/arch/arm/mach-msm/devices-9615.c
index d114714..6a14ace 100644
--- a/arch/arm/mach-msm/devices-9615.c
+++ b/arch/arm/mach-msm/devices-9615.c
@@ -22,6 +22,7 @@
#include <mach/socinfo.h>
#include <asm/hardware/cache-l2x0.h>
#include "devices.h"
+#include "acpuclock.h"
/* Address of GSBI blocks */
#define MSM_GSBI1_PHYS 0x16000000
@@ -151,6 +152,7 @@
pr_err("socinfo_init() failed!\n");
msm_clock_init(&msm9615_clock_init_data);
+ acpuclk_init(&acpuclk_9615_soc_data);
}
void __init msm9615_map_io(void)