msm-2.6.38: tag AU_LINUX_ANDROID_GINGERBREAD.02.03.04.00.142 Signed-off-by: Bryan Huntsman <bryanh@codeaurora.org>
281 lines
6.8 KiB
C
281 lines
6.8 KiB
C
/*
|
|
* 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.
|
|
*/
|
|
|
|
#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;
|
|
|
|
/*
|
|
* 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)
|
|
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 ((cpu == 1) && (l2 == 1) && (vu == 1)) {
|
|
if ((cur_voltage - VOLTAGE_STEP >= VOLTAGE_MIN) &&
|
|
(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);
|
|
|
|
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;
|
|
int new_voltage = avs_get_target_voltage(freq_idx, update_table);
|
|
if (avs_state.vdd != new_voltage) {
|
|
AVSDEBUG("AVS setting V to %d mV @%d\n",
|
|
new_voltage, freq_idx);
|
|
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 >= 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);
|
|
}
|
|
|
|
|