blob: e12caeba505eb7529adfbe8328140754a32a191e [file] [log] [blame]
/*
* 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,
};