blob: 827adab00e2a28ac90641ff11095003ce8d45b45 [file] [log] [blame]
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001/*
2 * Copyright (c) 2009, Code Aurora Forum. All rights reserved.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 and
6 * only version 2 as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 */
13
14#include <linux/kernel.h>
15#include <linux/module.h>
16#include <linux/init.h>
17#include <linux/io.h>
18#include <linux/delay.h>
19#include <linux/kernel_stat.h>
20#include <linux/workqueue.h>
21#include <linux/slab.h>
22
23#include "avs.h"
24
25#define AVSDSCR_INPUT 0x01004860 /* magic # from circuit designer */
26#define TSCSR_INPUT 0x00000001 /* enable temperature sense */
27
28#define TEMPRS 16 /* total number of temperature regions */
29#define GET_TEMPR() (avs_get_tscsr() >> 28) /* scale TSCSR[CTEMP] to regions */
30
31struct mutex avs_lock;
32
33static struct avs_state_s
34{
35 u32 freq_cnt; /* Frequencies supported list */
36 short *avs_v; /* Dyanmically allocated storage for
37 * 2D table of voltages over temp &
38 * freq. Used as a set of 1D tables.
39 * Each table is for a single temp.
40 * For usage see avs_get_voltage
41 */
42 int (*set_vdd) (int); /* Function Ptr for setting voltage */
43 int changing; /* Clock frequency is changing */
44 u32 freq_idx; /* Current frequency index */
45 int vdd; /* Current ACPU voltage */
46} avs_state;
47
48/*
49 * Update the AVS voltage vs frequency table, for current temperature
50 * Adjust based on the AVS delay circuit hardware status
51 */
52static void avs_update_voltage_table(short *vdd_table)
53{
54 u32 avscsr;
55 int cpu;
56 int vu;
57 int l2;
58 int i;
59 u32 cur_freq_idx;
60 short cur_voltage;
61
62 cur_freq_idx = avs_state.freq_idx;
63 cur_voltage = avs_state.vdd;
64
65 avscsr = avs_test_delays();
66 AVSDEBUG("avscsr=%x, avsdscr=%x\n", avscsr, avs_get_avsdscr());
67
68 /*
69 * Read the results for the various unit's AVS delay circuits
70 * 2=> up, 1=>down, 0=>no-change
71 */
72 cpu = ((avscsr >> 23) & 2) + ((avscsr >> 16) & 1);
73 vu = ((avscsr >> 28) & 2) + ((avscsr >> 21) & 1);
74 l2 = ((avscsr >> 29) & 2) + ((avscsr >> 22) & 1);
75
76 if ((cpu == 3) || (vu == 3) || (l2 == 3)) {
77 printk(KERN_ERR "AVS: Dly Synth O/P error\n");
78 } else if ((cpu == 2) || (l2 == 2) || (vu == 2)) {
79 /*
80 * even if one oscillator asks for up, increase the voltage,
81 * as its an indication we are running outside the
82 * critical acceptable range of v-f combination.
83 */
84 AVSDEBUG("cpu=%d l2=%d vu=%d\n", cpu, l2, vu);
85 AVSDEBUG("Voltage up at %d\n", cur_freq_idx);
86
87 if (cur_voltage >= VOLTAGE_MAX)
88 printk(KERN_ERR
89 "AVS: Voltage can not get high enough!\n");
90
91 /* Raise the voltage for all frequencies */
92 for (i = 0; i < avs_state.freq_cnt; i++) {
93 vdd_table[i] = cur_voltage + VOLTAGE_STEP;
94 if (vdd_table[i] > VOLTAGE_MAX)
95 vdd_table[i] = VOLTAGE_MAX;
96 }
97 } else if ((cpu == 1) && (l2 == 1) && (vu == 1)) {
98 if ((cur_voltage - VOLTAGE_STEP >= VOLTAGE_MIN) &&
99 (cur_voltage <= vdd_table[cur_freq_idx])) {
100 vdd_table[cur_freq_idx] = cur_voltage - VOLTAGE_STEP;
101 AVSDEBUG("Voltage down for %d and lower levels\n",
102 cur_freq_idx);
103
104 /* clamp to this voltage for all lower levels */
105 for (i = 0; i < cur_freq_idx; i++) {
106 if (vdd_table[i] > vdd_table[cur_freq_idx])
107 vdd_table[i] = vdd_table[cur_freq_idx];
108 }
109 }
110 }
111}
112
113/*
114 * Return the voltage for the target performance freq_idx and optionally
115 * use AVS hardware to check the present voltage freq_idx
116 */
117static short avs_get_target_voltage(int freq_idx, bool update_table)
118{
119 unsigned cur_tempr = GET_TEMPR();
120 unsigned temp_index = cur_tempr*avs_state.freq_cnt;
121
122 /* Table of voltages vs frequencies for this temp */
123 short *vdd_table = avs_state.avs_v + temp_index;
124
125 if (update_table)
126 avs_update_voltage_table(vdd_table);
127
128 return vdd_table[freq_idx];
129}
130
131
132/*
133 * Set the voltage for the freq_idx and optionally
134 * use AVS hardware to update the voltage
135 */
136static int avs_set_target_voltage(int freq_idx, bool update_table)
137{
138 int rc = 0;
139 int new_voltage = avs_get_target_voltage(freq_idx, update_table);
140 if (avs_state.vdd != new_voltage) {
141 AVSDEBUG("AVS setting V to %d mV @%d\n",
142 new_voltage, freq_idx);
143 rc = avs_state.set_vdd(new_voltage);
144 if (rc)
145 return rc;
146 avs_state.vdd = new_voltage;
147 }
148 return rc;
149}
150
151/*
152 * Notify avs of clk frquency transition begin & end
153 */
154int avs_adjust_freq(u32 freq_idx, int begin)
155{
156 int rc = 0;
157
158 if (!avs_state.set_vdd) {
159 /* AVS not initialized */
160 return 0;
161 }
162
163 if (freq_idx >= avs_state.freq_cnt) {
164 AVSDEBUG("Out of range :%d\n", freq_idx);
165 return -EINVAL;
166 }
167
168 mutex_lock(&avs_lock);
169 if ((begin && (freq_idx > avs_state.freq_idx)) ||
170 (!begin && (freq_idx < avs_state.freq_idx))) {
171 /* Update voltage before increasing frequency &
172 * after decreasing frequency
173 */
174 rc = avs_set_target_voltage(freq_idx, 0);
175 if (rc)
176 goto aaf_out;
177
178 avs_state.freq_idx = freq_idx;
179 }
180 avs_state.changing = begin;
181aaf_out:
182 mutex_unlock(&avs_lock);
183
184 return rc;
185}
186
187
188static struct delayed_work avs_work;
189static struct workqueue_struct *kavs_wq;
190#define AVS_DELAY ((CONFIG_HZ * 50 + 999) / 1000)
191
192static void do_avs_timer(struct work_struct *work)
193{
194 int cur_freq_idx;
195
196 mutex_lock(&avs_lock);
197 if (!avs_state.changing) {
198 /* Only adjust the voltage if clk is stable */
199 cur_freq_idx = avs_state.freq_idx;
200 avs_set_target_voltage(cur_freq_idx, 1);
201 }
202 mutex_unlock(&avs_lock);
203 queue_delayed_work_on(0, kavs_wq, &avs_work, AVS_DELAY);
204}
205
206
207static void __init avs_timer_init(void)
208{
209 INIT_DELAYED_WORK_DEFERRABLE(&avs_work, do_avs_timer);
210 queue_delayed_work_on(0, kavs_wq, &avs_work, AVS_DELAY);
211}
212
213static void __exit avs_timer_exit(void)
214{
215 cancel_delayed_work(&avs_work);
216}
217
218static int __init avs_work_init(void)
219{
220 kavs_wq = create_workqueue("avs");
221 if (!kavs_wq) {
222 printk(KERN_ERR "AVS initialization failed\n");
223 return -EFAULT;
224 }
225 avs_timer_init();
226
227 return 1;
228}
229
230static void __exit avs_work_exit(void)
231{
232 avs_timer_exit();
233 destroy_workqueue(kavs_wq);
234}
235
236int __init avs_init(int (*set_vdd)(int), u32 freq_cnt, u32 freq_idx)
237{
238 int i;
239
240 mutex_init(&avs_lock);
241
242 if (freq_cnt == 0)
243 return -EINVAL;
244
245 avs_state.freq_cnt = freq_cnt;
246
247 if (freq_idx >= avs_state.freq_cnt)
248 return -EINVAL;
249
250 avs_state.avs_v = kmalloc(TEMPRS * avs_state.freq_cnt *
251 sizeof(avs_state.avs_v[0]), GFP_KERNEL);
252
253 if (avs_state.avs_v == 0)
254 return -ENOMEM;
255
256 for (i = 0; i < TEMPRS*avs_state.freq_cnt; i++)
257 avs_state.avs_v[i] = VOLTAGE_MAX;
258
259 avs_reset_delays(AVSDSCR_INPUT);
260 avs_set_tscsr(TSCSR_INPUT);
261
262 avs_state.set_vdd = set_vdd;
263 avs_state.changing = 0;
264 avs_state.freq_idx = -1;
265 avs_state.vdd = -1;
266 avs_adjust_freq(freq_idx, 0);
267
268 avs_work_init();
269
270 return 0;
271}
272
273void __exit avs_exit()
274{
275 avs_work_exit();
276
277 kfree(avs_state.avs_v);
278}
279
280