Initial Contribution

msm-2.6.38: tag AU_LINUX_ANDROID_GINGERBREAD.02.03.04.00.142

Signed-off-by: Bryan Huntsman <bryanh@codeaurora.org>
diff --git a/arch/arm/mach-msm/avs.c b/arch/arm/mach-msm/avs.c
new file mode 100644
index 0000000..827adab
--- /dev/null
+++ b/arch/arm/mach-msm/avs.c
@@ -0,0 +1,280 @@
+/*
+ * 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);
+}
+
+