--- a/arch/arm/mach-msm/Kconfig
+++ b/arch/arm/mach-msm/Kconfig
@@ -702,6 +702,15 @@ config MSM_CPU_FREQ_SCREEN_ON
endif # MSM_CPU_FREQ_SCREEN
+config MSM_CPU_AVS
+ bool "Enable Adaptive Voltage Scaling (AVS)"
+ depends on (ARCH_MSM_SCORPION)
+ default n
+ help
+ This enables the Adaptive Voltage Scaling feature of
+ Qualcomm ARMv7 CPUs. It adjusts the voltage for each frequency
+ based on feedback from three ring oscillators in the CPU.
+
config MSM_HW3D
tristate "MSM Hardware 3D Register Driver"
depends on EARLYSUSPEND
--- a/arch/arm/mach-msm/Makefile
+++ b/arch/arm/mach-msm/Makefile
@@ -6,6 +6,7 @@ obj-y += vreg.o
obj-y += pmic.o
obj-y += remote_spinlock.o
obj-$(CONFIG_ARCH_MSM_ARM11) += acpuclock-arm11.o idle.o
+obj-$(CONFIG_MSM_CPU_AVS) += avs.o avs_hw.o
obj-$(CONFIG_ARCH_QSD8X50) += arch-init-scorpion.o acpuclock-scorpion.o
obj-$(CONFIG_ARCH_MSM7X30) += acpuclock-7x30.o internal_power_rail.o
obj-$(CONFIG_ARCH_MSM7X30) += clock-7x30.o arch-init-7x30.o socinfo.o
--- a/arch/arm/mach-msm/acpuclock-scorpion.c
+++ b/arch/arm/mach-msm/acpuclock-scorpion.c
@@ -27,6 +27,9 @@
#include <mach/msm_iomap.h>
#include "acpuclock.h"
+#ifdef CONFIG_MSM_CPU_AVS
+#include "avs.h"
+#endif
#include "proc_comm.h"
#include "clock.h"
@@ -76,8 +79,6 @@ struct clkctl_acpu_speed acpu_freq_tbl[]
{ 19200, CCTL(CLK_TCXO, 1), SRC_RAW, 0, 0, 925, 14000},
{ 128000, CCTL(CLK_TCXO, 1), SRC_AXI, 0, 0, 925, 14000 },
{ 245000, CCTL(CLK_MODEM_PLL, 1), SRC_RAW, 0, 0, 925, 29000 },
- /* Work arround for acpu resume hung, GPLL is turn off by arm9 */
- /*{ 256000, CCTL(CLK_GLOBAL_PLL, 3), SRC_RAW, 0, 0, 1050, 29000 },*/
{ 384000, CCTL(CLK_TCXO, 1), SRC_SCPLL, 0x0A, 0, 975, 58000 },
{ 422400, CCTL(CLK_TCXO, 1), SRC_SCPLL, 0x0B, 0, 975, 117000 },
{ 460800, CCTL(CLK_TCXO, 1), SRC_SCPLL, 0x0C, 0, 1000, 117000 },
@@ -155,14 +156,15 @@ static void __init acpuclk_init_cpufreq_
struct clock_state {
struct clkctl_acpu_speed *current_speed;
- struct mutex lock;
+ 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;
+ unsigned long power_collapse_khz;
+ unsigned long wait_for_irq_khz;
struct clk* clk_ebi1;
- struct regulator *regulator;
+ struct regulator *regulator;
+ int (*acpu_set_vdd) (int mvolts);
};
static struct clock_state drv_state = { 0 };
@@ -286,27 +288,41 @@ static void select_clock(unsigned src, u
writel(val | ((src & 3) << 1), SPSS_CLK_SEL_ADDR);
}
-static int acpuclk_set_vdd_level(int vdd)
+static int acpu_set_vdd(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);
+ pr_info("acpu_set_vdd %d no regulator\n", vdd);
/* Assume that the PMIC supports scaling the processor
* to its maximum frequency at its default voltage.
*/
- return 0;
+ return -ENODEV;
}
- pr_info("acpuclk_set_vdd_level got regulator\n");
+ pr_info("acpu_set_vdd got regulator\n");
}
vdd *= 1000; /* mV -> uV */
return regulator_set_voltage(drv_state.regulator, vdd, vdd);
}
+static int acpuclk_set_vdd_level(int vdd)
+{
+ if (drv_state.acpu_set_vdd)
+ return drv_state.acpu_set_vdd(vdd);
+ else {
+ /* Assume that the PMIC supports scaling the processor
+ * to its maximum frequency at its default voltage.
+ */
+ return 0;
+ }
+}
+
int acpuclk_set_rate(unsigned long rate, enum setrate_reason reason)
{
struct clkctl_acpu_speed *cur, *next;
unsigned long flags;
+ int rc = 0;
+ int freq_index = 0;
cur = drv_state.current_speed;
@@ -325,16 +341,29 @@ int acpuclk_set_rate(unsigned long rate,
if (next->acpu_khz == 0)
return -EINVAL;
next++;
+ freq_index++;
}
if (reason == SETRATE_CPUFREQ) {
mutex_lock(&drv_state.lock);
+#ifdef CONFIG_MSM_CPU_AVS
+ /* Notify avs before changing frequency */
+ rc = avs_adjust_freq(freq_index, 1);
+ if (rc) {
+ printk(KERN_ERR
+ "acpuclock: Unable to increase ACPU "
+ "vdd: %d.\n", (int) rate);
+ mutex_unlock(&drv_state.lock);
+ return rc;
+ }
+#endif
/* 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");
+ rc = acpuclk_set_vdd_level(next->vdd);
+ if (rc) {
+ pr_err("acpuclock: Unable to increase ACPU VDD from %d to %d setting rate to %d.\n", cur->vdd, next->vdd, (int) rate);
mutex_unlock(&drv_state.lock);
- return -EINVAL;
+ return rc;
}
}
}
@@ -367,24 +396,28 @@ int acpuclk_set_rate(unsigned long rate,
spin_unlock_irqrestore(&acpu_lock, flags);
-#ifndef CONFIG_AXI_SCREEN_POLICY
if (reason == SETRATE_CPUFREQ || reason == SETRATE_PC) {
if (cur->axiclk_khz != next->axiclk_khz)
clk_set_rate(drv_state.clk_ebi1, next->axiclk_khz * 1000);
- DEBUG("acpuclk_set_rate switch axi to %d\n",
- clk_get_rate(drv_state.clk_ebi1));
}
-#endif
+
if (reason == SETRATE_CPUFREQ) {
+#ifdef CONFIG_MSM_CPU_AVS
+ /* notify avs after changing frequency */
+ rc = avs_adjust_freq(freq_index, 0);
+ if (rc)
+ printk(KERN_ERR "acpuclock: Unable to drop ACPU vdd: %d.\n", (int) rate);
+#endif
/* 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");
+ rc = acpuclk_set_vdd_level(next->vdd);
+ if (rc)
+ pr_err("acpuclock: Unable to drop ACPU VDD from %d to %d setting rate to %d.\n", cur->vdd, next->vdd, (int) rate);
}
mutex_unlock(&drv_state.lock);
}
- return 0;
+ return rc;
}
static unsigned __init acpuclk_find_speed(void)
@@ -435,15 +468,15 @@ static void __init acpuclk_init(void)
BUG();
}
- /* Move to 768MHz for boot, which is a safe frequency
+ /* Move to 998MHz 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)
+ if (speed->acpu_khz == 998400)
break;
if (speed->acpu_khz == 0) {
- pr_err("acpuclk_init: cannot find 768MHz\n");
+ pr_err("acpuclk_init: cannot find 998MHz\n");
BUG();
}
speed++;
@@ -501,6 +534,23 @@ unsigned long acpuclk_wait_for_irq(void)
return ret * 1000;
}
+#ifdef CONFIG_MSM_CPU_AVS
+static int __init acpu_avs_init(int (*set_vdd) (int), int khz)
+{
+ int i;
+ int freq_count = 0;
+ int freq_index = -1;
+
+ for (i = 0; acpu_freq_tbl[i].acpu_khz; i++) {
+ freq_count++;
+ if (acpu_freq_tbl[i].acpu_khz == khz)
+ freq_index = i;
+ }
+
+ return avs_init(set_vdd, freq_count, freq_index);
+}
+#endif
+
void __init msm_acpu_clock_init(struct msm_acpu_clock_platform_data *clkdata)
{
spin_lock_init(&acpu_lock);
@@ -511,20 +561,27 @@ void __init msm_acpu_clock_init(struct m
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;
-
+ drv_state.acpu_set_vdd = acpu_set_vdd;
+
if (clkdata->mpll_khz)
acpu_mpll->acpu_khz = clkdata->mpll_khz;
acpuclk_init();
acpuclk_init_cpufreq_table();
drv_state.clk_ebi1 = clk_get(NULL,"ebi1_clk");
-#ifndef CONFIG_AXI_SCREEN_POLICY
clk_set_rate(drv_state.clk_ebi1, drv_state.current_speed->axiclk_khz * 1000);
+
+#ifdef CONFIG_MSM_CPU_AVS
+ if (!acpu_avs_init(drv_state.acpu_set_vdd,
+ drv_state.current_speed->acpu_khz)) {
+ /* avs init successful. avs will handle voltage changes */
+ drv_state.acpu_set_vdd = NULL;
+ }
#endif
}
#ifdef CONFIG_CPU_FREQ_VDD_LEVELS
-
+#ifndef CONFIG_MSM_CPU_AVS
ssize_t acpuclk_get_vdd_levels_str(char *buf)
{
int i, len = 0;
@@ -541,22 +598,22 @@ ssize_t acpuclk_get_vdd_levels_str(char
return len;
}
-void acpuclk_set_vdd(unsigned acpu_khz, int vdd)
+void acpuclk_set_vdd(unsigned acpu_khz, int max_vdd)
{
int i;
- vdd = vdd / 25 * 25; //! regulator only accepts multiples of 25 (mV)
+ max_vdd = max_vdd / 25 * 25; //! regulator only accepts multiples of 25 (mV)
mutex_lock(&drv_state.lock);
for (i = 0; acpu_freq_tbl[i].acpu_khz; i++)
{
if (freq_table[i].frequency != CPUFREQ_ENTRY_INVALID)
{
if (acpu_khz == 0)
- acpu_freq_tbl[i].vdd = min(max((acpu_freq_tbl[i].vdd + vdd), CONFIG_CPU_FREQ_VDD_LEVELS_MIN), CONFIG_CPU_FREQ_VDD_LEVELS_MAX);
+ acpu_freq_tbl[i].vdd = min(max((acpu_freq_tbl[i].vdd + max_vdd), CONFIG_CPU_FREQ_VDD_LEVELS_MIN), CONFIG_CPU_FREQ_VDD_LEVELS_MAX);
else if (acpu_freq_tbl[i].acpu_khz == acpu_khz)
- acpu_freq_tbl[i].vdd = min(max(vdd, CONFIG_CPU_FREQ_VDD_LEVELS_MIN), CONFIG_CPU_FREQ_VDD_LEVELS_MAX);
+ acpu_freq_tbl[i].vdd = min(max(max_vdd, CONFIG_CPU_FREQ_VDD_LEVELS_MIN), CONFIG_CPU_FREQ_VDD_LEVELS_MAX);
}
}
mutex_unlock(&drv_state.lock);
}
-
+#endif
#endif
--- /dev/null
+++ b/arch/arm/mach-msm/avs.c
@@ -0,0 +1,410 @@
+/*
+ * Copyright (c) 2009, Code Aurora Forum. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Code Aurora Forum nor
+ * the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * Alternatively, provided that this notice is retained in full, this software
+ * may be relicensed by the recipient under the terms of the GNU General Public
+ * License version 2 ("GPL") and only version 2, in which case the provisions of
+ * the GPL apply INSTEAD OF those given above. If the recipient relicenses the
+ * software under the GPL, then the identification text in the MODULE_LICENSE
+ * macro must be changed to reflect "GPLv2" instead of "Dual BSD/GPL". Once a
+ * recipient changes the license terms to the GPL, subsequent recipients shall
+ * not relicense under alternate licensing terms, including the BSD or dual
+ * BSD/GPL terms. In addition, the following license statement immediately
+ * below and between the words START and END shall also then apply when this
+ * software is relicensed under the GPL:
+ *
+ * START
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * END
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/kernel_stat.h>
+#include <linux/workqueue.h>
+#include <linux/slab.h>
+
+#include "avs.h"
+
+#define AVSDSCR_INPUT 0x01004860 /* magic # from circuit designer */
+#define TSCSR_INPUT 0x00000001 /* enable temperature sense */
+
+#define TEMPRS 16 /* total number of temperature regions */
+#define GET_TEMPR() (avs_get_tscsr() >> 28) /* scale TSCSR[CTEMP] to regions */
+
+struct mutex avs_lock;
+
+static struct avs_state_s
+{
+ u32 freq_cnt; /* Frequencies supported list */
+ short *avs_v; /* Dyanmically allocated storage for
+ * 2D table of voltages over temp &
+ * freq. Used as a set of 1D tables.
+ * Each table is for a single temp.
+ * For usage see avs_get_voltage
+ */
+ int (*set_vdd) (int); /* Function Ptr for setting voltage */
+ int changing; /* Clock frequency is changing */
+ u32 freq_idx; /* Current frequency index */
+ int vdd; /* Current ACPU voltage */
+} avs_state;
+
+struct clkctl_acpu_speed {
+ unsigned acpu_khz;
+ int min_vdd;
+ int max_vdd;
+};
+
+
+struct clkctl_acpu_speed acpu_vdd_tbl[] = {
+ { 19200, 950, 950 },
+ { 128000, 900, 950 },
+ { 245000, 900, 950 },
+ { 384000, 950, 1000 },
+ { 422400, 950, 1000 },
+ { 460800, 975, 1025 },
+ { 499200, 1000, 1050 },
+ { 537600, 1000, 1050 },
+ { 576000, 1025, 1075 },
+ { 614400, 1050, 1100 },
+ { 652800, 1075, 1125 },
+ { 691200, 1100, 1150 },
+ { 729600, 1125, 1175 },
+ { 768000, 1150, 1200 },
+ { 806400, 1175, 1225 },
+ { 844800, 1200, 1250 },
+ { 883200, 1200, 1250 },
+ { 921600, 1225, 1275 },
+ { 960000, 1225, 1275 },
+ { 998400, 1225, 1275 },
+ { 1036800, 1275, 1275 },
+ { 1075200, 1275, 1275 },
+ { 1113600, 1275, 1275 },
+ { 1152000, 1350, 1350 },
+ { 1190400, 1350, 1350 },
+ { 1228800, 1400, 1400 },
+ { 0 },
+};
+
+/*
+ * Update the AVS voltage vs frequency table, for current temperature
+ * Adjust based on the AVS delay circuit hardware status
+ */
+static void avs_update_voltage_table(short *vdd_table)
+{
+ u32 avscsr;
+ int cpu;
+ int vu;
+ int l2;
+ int i;
+ u32 cur_freq_idx;
+ short cur_voltage;
+
+ cur_freq_idx = avs_state.freq_idx;
+ cur_voltage = avs_state.vdd;
+
+ avscsr = avs_test_delays();
+ AVSDEBUG("avscsr=%x, avsdscr=%x\n", avscsr, avs_get_avsdscr());
+
+ /*
+ * Read the results for the various unit's AVS delay circuits
+ * 2=> up, 1=>down, 0=>no-change
+ */
+ cpu = ((avscsr >> 23) & 2) + ((avscsr >> 16) & 1);
+ vu = ((avscsr >> 28) & 2) + ((avscsr >> 21) & 1);
+ l2 = ((avscsr >> 29) & 2) + ((avscsr >> 22) & 1);
+
+ if ((cpu == 3) || (vu == 3) || (l2 == 3)) {
+ printk(KERN_ERR "AVS: Dly Synth O/P error\n");
+ } else if ((cpu == 2) || (l2 == 2) || (vu == 2)) {
+ /*
+ * even if one oscillator asks for up, increase the voltage,
+ * as its an indication we are running outside the
+ * critical acceptable range of v-f combination.
+ */
+ AVSDEBUG("cpu=%d l2=%d vu=%d\n", cpu, l2, vu);
+ AVSDEBUG("Voltage up at %d\n", cur_freq_idx);
+
+ if (cur_voltage >= VOLTAGE_MAX || cur_voltage >= acpu_vdd_tbl[cur_freq_idx].max_vdd)
+ printk(KERN_ERR
+ "AVS: Voltage can not get high enough!\n");
+
+ /* Raise the voltage for all frequencies */
+ for (i = 0; i < avs_state.freq_cnt; i++) {
+ vdd_table[i] = cur_voltage + VOLTAGE_STEP;
+ if (vdd_table[i] > VOLTAGE_MAX)
+ vdd_table[i] = VOLTAGE_MAX;
+ else if (vdd_table[i] > acpu_vdd_tbl[i].max_vdd)
+ vdd_table[i] = acpu_vdd_tbl[i].max_vdd;
+ }
+ } else if ((cpu == 1) && (l2 == 1) && (vu == 1)) {
+ if ((cur_voltage - VOLTAGE_STEP >= VOLTAGE_MIN) &&
+ (cur_voltage - VOLTAGE_STEP >= acpu_vdd_tbl[cur_freq_idx].min_vdd) &&
+ (cur_voltage <= vdd_table[cur_freq_idx])) {
+ vdd_table[cur_freq_idx] = cur_voltage - VOLTAGE_STEP;
+ AVSDEBUG("Voltage down for %d and lower levels\n",
+ cur_freq_idx);
+
+ /* clamp to this voltage for all lower levels */
+ for (i = 0; i < cur_freq_idx; i++) {
+ if (vdd_table[i] > vdd_table[cur_freq_idx])
+ vdd_table[i] = vdd_table[cur_freq_idx];
+ }
+ }
+ }
+}
+
+/*
+ * Return the voltage for the target performance freq_idx and optionally
+ * use AVS hardware to check the present voltage freq_idx
+ */
+static short avs_get_target_voltage(int freq_idx, bool update_table)
+{
+ unsigned cur_tempr = GET_TEMPR();
+ unsigned temp_index = cur_tempr*avs_state.freq_cnt;
+
+ /* Table of voltages vs frequencies for this temp */
+ short *vdd_table = avs_state.avs_v + temp_index;
+
+ if (update_table)
+ avs_update_voltage_table(vdd_table);
+
+ if (vdd_table[freq_idx] > acpu_vdd_tbl[freq_idx].max_vdd) {
+ pr_info("%dmV too high for %d.\n", vdd_table[freq_idx], acpu_vdd_tbl[freq_idx].acpu_khz);
+ vdd_table[freq_idx] = acpu_vdd_tbl[freq_idx].max_vdd;
+ }
+ if (vdd_table[freq_idx] < acpu_vdd_tbl[freq_idx].min_vdd) {
+ pr_info("%dmV too low for %d.\n", vdd_table[freq_idx], acpu_vdd_tbl[freq_idx].acpu_khz);
+ vdd_table[freq_idx] = acpu_vdd_tbl[freq_idx].min_vdd;
+ }
+
+ return vdd_table[freq_idx];
+}
+
+
+/*
+ * Set the voltage for the freq_idx and optionally
+ * use AVS hardware to update the voltage
+ */
+static int avs_set_target_voltage(int freq_idx, bool update_table)
+{
+ int rc = 0, new_voltage;
+
+ if (freq_idx < 0 || freq_idx >= avs_state.freq_cnt) {
+ AVSDEBUG("Out of range :%d\n", freq_idx);
+ return -EINVAL;
+ }
+
+ new_voltage = avs_get_target_voltage(freq_idx, update_table);
+ if (avs_state.vdd != new_voltage) {
+ AVSDEBUG("AVS setting V to %d mV @%d MHz\n",
+ new_voltage, acpu_vdd_tbl[freq_idx].acpu_khz / 1000);
+ rc = avs_state.set_vdd(new_voltage);
+ if (rc)
+ return rc;
+ avs_state.vdd = new_voltage;
+ }
+ return rc;
+}
+
+/*
+ * Notify avs of clk frquency transition begin & end
+ */
+int avs_adjust_freq(u32 freq_idx, int begin)
+{
+ int rc = 0;
+
+ if (!avs_state.set_vdd) {
+ /* AVS not initialized */
+ return 0;
+ }
+
+ if (freq_idx < 0 || freq_idx >= avs_state.freq_cnt) {
+ AVSDEBUG("Out of range :%d\n", freq_idx);
+ return -EINVAL;
+ }
+
+ mutex_lock(&avs_lock);
+ if ((begin && (freq_idx > avs_state.freq_idx)) ||
+ (!begin && (freq_idx < avs_state.freq_idx))) {
+ /* Update voltage before increasing frequency &
+ * after decreasing frequency
+ */
+ rc = avs_set_target_voltage(freq_idx, 0);
+ if (rc)
+ goto aaf_out;
+
+ avs_state.freq_idx = freq_idx;
+ }
+ avs_state.changing = begin;
+aaf_out:
+ mutex_unlock(&avs_lock);
+
+ return rc;
+}
+
+
+static struct delayed_work avs_work;
+static struct workqueue_struct *kavs_wq;
+#define AVS_DELAY ((CONFIG_HZ * 50 + 999) / 1000)
+
+static void do_avs_timer(struct work_struct *work)
+{
+ int cur_freq_idx;
+
+ mutex_lock(&avs_lock);
+ if (!avs_state.changing) {
+ /* Only adjust the voltage if clk is stable */
+ cur_freq_idx = avs_state.freq_idx;
+ avs_set_target_voltage(cur_freq_idx, 1);
+ }
+ mutex_unlock(&avs_lock);
+ queue_delayed_work_on(0, kavs_wq, &avs_work, AVS_DELAY);
+}
+
+
+static void __init avs_timer_init(void)
+{
+ INIT_DELAYED_WORK_DEFERRABLE(&avs_work, do_avs_timer);
+ queue_delayed_work_on(0, kavs_wq, &avs_work, AVS_DELAY);
+}
+
+static void __exit avs_timer_exit(void)
+{
+ cancel_delayed_work(&avs_work);
+}
+
+static int __init avs_work_init(void)
+{
+ kavs_wq = create_workqueue("avs");
+ if (!kavs_wq) {
+ printk(KERN_ERR "AVS initialization failed\n");
+ return -EFAULT;
+ }
+ avs_timer_init();
+
+ return 1;
+}
+
+static void __exit avs_work_exit(void)
+{
+ avs_timer_exit();
+ destroy_workqueue(kavs_wq);
+}
+
+int __init avs_init(int (*set_vdd)(int), u32 freq_cnt, u32 freq_idx)
+{
+ int i;
+
+ mutex_init(&avs_lock);
+
+ if (freq_cnt == 0)
+ return -EINVAL;
+
+ avs_state.freq_cnt = freq_cnt;
+
+ if (freq_idx >= avs_state.freq_cnt)
+ return -EINVAL;
+
+ avs_state.avs_v = kmalloc(TEMPRS * avs_state.freq_cnt *
+ sizeof(avs_state.avs_v[0]), GFP_KERNEL);
+
+ if (avs_state.avs_v == 0)
+ return -ENOMEM;
+
+ for (i = 0; i < TEMPRS*avs_state.freq_cnt; i++)
+ avs_state.avs_v[i] = VOLTAGE_MAX;
+
+ avs_reset_delays(AVSDSCR_INPUT);
+ avs_set_tscsr(TSCSR_INPUT);
+
+ avs_state.set_vdd = set_vdd;
+ avs_state.changing = 0;
+ avs_state.freq_idx = -1;
+ avs_state.vdd = -1;
+ avs_adjust_freq(freq_idx, 0);
+
+ avs_work_init();
+
+ return 0;
+}
+
+void __exit avs_exit()
+{
+ avs_work_exit();
+
+ kfree(avs_state.avs_v);
+}
+
+#ifdef CONFIG_CPU_FREQ_VDD_LEVELS
+ssize_t acpuclk_get_vdd_levels_str(char *buf)
+{
+ int i, len = 0;
+ if (buf)
+ {
+ for (i = 0; acpu_vdd_tbl[i].acpu_khz; i++)
+ {
+ if(acpu_vdd_tbl[i].acpu_khz != 19200)
+ len += sprintf(buf + len, "%8u: %4d-%4d\n", acpu_vdd_tbl[i].acpu_khz, acpu_vdd_tbl[i].min_vdd, acpu_vdd_tbl[i].max_vdd);
+ }
+ }
+ return len;
+}
+
+void acpuclk_set_vdd(unsigned acpu_khz, int min_vdd, int max_vdd)
+{
+ int i;
+ min_vdd = min_vdd / 25 * 25; //! regulator only accepts multiples of 25 (mV)
+ max_vdd = max_vdd / 25 * 25; //! regulator only accepts multiples of 25 (mV)
+ for (i = 0; acpu_vdd_tbl[i].acpu_khz; i++)
+ {
+ if (acpu_khz == 0) {
+ acpu_vdd_tbl[i].min_vdd = min(max((acpu_vdd_tbl[i].min_vdd + min_vdd), CONFIG_CPU_FREQ_VDD_LEVELS_MIN), CONFIG_CPU_FREQ_VDD_LEVELS_MAX);
+ acpu_vdd_tbl[i].max_vdd = min(max((acpu_vdd_tbl[i].max_vdd + min_vdd), CONFIG_CPU_FREQ_VDD_LEVELS_MIN), CONFIG_CPU_FREQ_VDD_LEVELS_MAX);
+ } else if (acpu_vdd_tbl[i].acpu_khz == acpu_khz) {
+ acpu_vdd_tbl[i].min_vdd = min(max(min_vdd, CONFIG_CPU_FREQ_VDD_LEVELS_MIN), CONFIG_CPU_FREQ_VDD_LEVELS_MAX);
+ acpu_vdd_tbl[i].max_vdd = min(max(max_vdd, CONFIG_CPU_FREQ_VDD_LEVELS_MIN), CONFIG_CPU_FREQ_VDD_LEVELS_MAX);
+ }
+ }
+}
+#endif
--- /dev/null
+++ b/arch/arm/mach-msm/avs.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2009, Code Aurora Forum. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Code Aurora nor
+ * the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef AVS_H
+#define AVS_H
+
+#define VOLTAGE_MIN CONFIG_CPU_FREQ_VDD_LEVELS_MIN /* mV */
+#define VOLTAGE_MAX CONFIG_CPU_FREQ_VDD_LEVELS_MAX
+#define VOLTAGE_STEP 25
+
+int __init avs_init(int (*set_vdd)(int), u32 freq_cnt, u32 freq_idx);
+void __exit avs_exit(void);
+
+int avs_adjust_freq(u32 freq_index, int begin);
+
+/* Routines exported from avs_hw.S */
+u32 avs_test_delays(void);
+u32 avs_reset_delays(u32 avsdscr);
+u32 avs_get_avscsr(void);
+u32 avs_get_avsdscr(void);
+u32 avs_get_tscsr(void);
+void avs_set_tscsr(u32 to_tscsr);
+
+#ifdef CONFIG_MSM_CPU_AVS_DEBUG
+#define AVSDEBUG(x...) pr_info("AVS: " x);
+#else
+#define AVSDEBUG(...)
+#endif
+
+#endif /* AVS_H */
--- /dev/null
+++ b/arch/arm/mach-msm/avs_hw.S
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2009, 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+ .text
+
+ .global avs_test_delays
+avs_test_delays:
+
+/* Read r1=CPMR and enable Never Sleep for VSLPDLY */
+ mrc p15, 7, r1, c15, c0, 5
+ orr r12, r1, #3, 24
+ mcr p15, 7, r12, c15, c0, 5
+
+/* Read r2=CPACR and enable full access to CP10 and CP11 space */
+ mrc p15, 0, r2, c1, c0, 2
+ orr r12, r2, #(0xf << 20)
+ mcr p15, 0, r12, c1, c0, 2
+ isb
+
+/* Read r3=FPEXC and or in FP enable, VFP/ASE enable = FPEXC[30]; */
+ fmrx r3, fpexc
+ orr r12, r3, #1, 2
+ fmxr fpexc, r12
+
+/*
+ * Do floating-point operations to prime the VFP pipeline. Use
+ * fcpyd d0, d0 as a floating point nop. This avoids changing VFP
+ * state.
+ */
+ fcpyd d0, d0
+ fcpyd d0, d0
+ fcpyd d0, d0
+
+/* Read r0=AVSCSR to get status from CPU, VFP, and L2 ring oscillators */
+ mrc p15, 7, r0, c15, c1, 7
+
+/* Restore FPEXC */
+ fmxr fpexc, r3
+
+/* Restore CPACR */
+ MCR p15, 0, r2, c1, c0, 2
+
+/* Restore CPMR */
+ mcr p15, 7, r1, c15, c0, 5
+ isb
+
+ bx lr
+
+
+
+
+ .global avs_get_avscsr
+/* Read r0=AVSCSR to get status from CPU, VFP, and L2 ring oscillators */
+
+avs_get_avscsr:
+ mrc p15, 7, r0, c15, c1, 7
+ bx lr
+
+ .global avs_get_avsdscr
+/* Read r0=AVSDSCR to get the AVS Delay Synthesizer control settings */
+
+avs_get_avsdscr:
+ mrc p15, 7, r0, c15, c0, 6
+ bx lr
+
+
+
+
+ .global avs_get_tscsr
+/* Read r0=TSCSR to get temperature sensor control and status */
+
+avs_get_tscsr:
+ mrc p15, 7, r0, c15, c1, 0
+ bx lr
+
+ .global avs_set_tscsr
+/* Write TSCSR=r0 to set temperature sensor control and status */
+
+avs_set_tscsr:
+ mcr p15, 7, r0, c15, c1, 0
+ bx lr
+
+
+
+
+
+ .global avs_reset_delays
+avs_reset_delays:
+
+/* AVSCSR(0x61) to enable CPU, V and L2 AVS module */
+ mov r3, #0x61
+ mcr p15, 7, r3, c15, c1, 7
+
+/* AVSDSCR(dly) to program delay */
+ mcr p15, 7, r0, c15, c0, 6
+
+/* Read r0=AVSDSCR */
+ mrc p15, 7, r0, c15, c0, 6
+
+ bx lr
+
+ .end
+
+
--- a/arch/arm/mach-msm/idle-v7.S
+++ b/arch/arm/mach-msm/idle-v7.S
@@ -42,6 +42,18 @@ ENTRY(msm_pm_collapse)
mrc p15, 0, ip, c13, c0, 1 /* context ID */
stmia r0!, {r1-r9, ip}
+#ifdef CONFIG_MSM_CPU_AVS
+ mrc p15, 7, r1, c15, c1, 7 /* AVSCSR is the Adaptive Voltage Scaling
+ * Control and Status Register */
+ mrc p15, 7, r2, c15, c0, 6 /* AVSDSCR is the Adaptive Voltage
+ * Scaling Delay Synthesizer Control
+ * Register */
+ mrc p15, 7, r3, c15, c1, 0 /* TSCSR is the Temperature Status and
+ * Control Register
+ */
+ stmia r0!, {r1-r3}
+#endif
+
#ifdef CONFIG_VFP
VFPFSTMIA r0, r1 /* Save VFP working registers */
fmrx r1, fpexc
@@ -108,6 +120,12 @@ ENTRY(msm_pm_collapse_exit)
adr r3, msm_pm_collapse_exit
add r1, r1, r3
sub r1, r1, r2
+#ifdef CONFIG_MSM_CPU_AVS
+ ldmdb r1!, {r2-r4}
+ mcr p15, 7, r4, c15, c1, 0 /* TSCSR */
+ mcr p15, 7, r3, c15, c0, 6 /* AVSDSCR */
+ mcr p15, 7, r2, c15, c1, 7 /* AVSCSR */
+#endif
#ifdef CONFIG_VFP
mrc p15, 0, r2, c1, c0, 2 /* Read CP Access Control Register */
orr r2, r2, #0x00F00000 /* Enable full access for p10,11 */
@@ -191,6 +209,9 @@ msm_pm_pa_to_va:
saved_state:
.space 4 * 11 /* r4-14 */
.space 4 * 10 /* cp15 */
+#ifdef CONFIG_MSM_CPU_AVS
+ .space 4 * 3 /* AVS control registers */
+#endif
#ifdef CONFIG_VFP
.space 8 * 32 /* VFP working registers */
.space 4 * 2 /* VFP state registers */
--- a/drivers/cpufreq/cpufreq.c
+++ b/drivers/cpufreq/cpufreq.c
@@ -650,16 +650,25 @@ static ssize_t show_scaling_setspeed(str
#ifdef CONFIG_CPU_FREQ_VDD_LEVELS
extern ssize_t acpuclk_get_vdd_levels_str(char *buf);
+#ifdef CONFIG_MSM_CPU_AVS
+static ssize_t show_vdd_levels_havs(struct cpufreq_policy *policy, char *buf)
+#else
static ssize_t show_vdd_levels(struct cpufreq_policy *policy, char *buf)
+#endif
{
return acpuclk_get_vdd_levels_str(buf);
}
-extern void acpuclk_set_vdd(unsigned acpu_khz, int vdd);
+#ifdef CONFIG_MSM_CPU_AVS
+extern void acpuclk_set_vdd(unsigned acpu_khz, int min_vdd, int max_vdd);
+static ssize_t store_vdd_levels_havs(struct cpufreq_policy *policy, const char *buf, size_t count)
+#else
+extern void acpuclk_set_vdd(unsigned acpu_khz, int max_vdd);
static ssize_t store_vdd_levels(struct cpufreq_policy *policy, const char *buf, size_t count)
+#endif
{
int i = 0, j;
- int pair[2] = { 0, 0 };
+ int pair[3] = { 0, 0, 0 };
int sign = 0;
if (count < 1)
@@ -689,7 +698,11 @@ static ssize_t store_vdd_levels(struct c
if (pair[j] != 0)
{
j++;
+#ifndef CONFIG_MSM_CPU_AVS
if ((sign != 0) || (j > 1))
+#else
+ if ((sign != 0) || (j > 2))
+#endif
break;
}
}
@@ -700,12 +713,21 @@ static ssize_t store_vdd_levels(struct c
if (sign != 0)
{
if (pair[0] > 0)
+#ifndef CONFIG_MSM_CPU_AVS
acpuclk_set_vdd(0, sign * pair[0]);
+#else
+ acpuclk_set_vdd(0, sign * pair[0], 0);
+#endif
}
else
{
+#ifndef CONFIG_MSM_CPU_AVS
if ((pair[0] > 0) && (pair[1] > 0))
acpuclk_set_vdd((unsigned)pair[0], pair[1]);
+#else
+ if ((pair[0] > 0) && (pair[1] > 0) && (pair[2] > 0))
+ acpuclk_set_vdd((unsigned)pair[0], pair[1], pair[2]);
+#endif
else
return -EINVAL;
}
@@ -741,8 +763,12 @@ define_one_rw(scaling_max_freq);
define_one_rw(scaling_governor);
define_one_rw(scaling_setspeed);
#ifdef CONFIG_CPU_FREQ_VDD_LEVELS
+#ifdef CONFIG_MSM_CPU_AVS
+define_one_rw(vdd_levels_havs);
+#else
define_one_rw(vdd_levels);
#endif
+#endif
static struct attribute *default_attrs[] = {
&cpuinfo_min_freq.attr,
@@ -757,8 +783,12 @@ static struct attribute *default_attrs[]
&scaling_available_governors.attr,
&scaling_setspeed.attr,
#ifdef CONFIG_CPU_FREQ_VDD_LEVELS
+#ifdef CONFIG_MSM_CPU_AVS
+ &vdd_levels_havs.attr,
+#else
&vdd_levels.attr,
#endif
+#endif
NULL
};