1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
|
/*
* Copyright (c) 2011-2012, The Linux Foundation. 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/module.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 <linux/platform_device.h>
#include <asm/cpu.h>
#include <mach/board.h>
#include <mach/msm_iomap.h>
#include <mach/msm_bus.h>
#include <mach/msm_bus_board.h>
#include <mach/rpm-regulator.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)
#define MAX_VDD_MEM 1150000
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_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;
unsigned int vdd_cpu;
unsigned int vdd_mem;
unsigned int bw_level;
};
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 },
};
/* Instantaneous bandwidth requests in MB/s. */
#define BW_MBPS(_bw) \
{ \
.vectors = &(struct msm_bus_vectors){ \
.src = MSM_BUS_MASTER_AMPSS_M0, \
.dst = MSM_BUS_SLAVE_EBI_CH0, \
.ib = (_bw) * 1000000UL, \
.ab = 0, \
}, \
.num_paths = 1, \
}
static struct msm_bus_paths bw_level_tbl[] = {
[0] = BW_MBPS(152), /* At least 19 MHz on bus. */
[1] = BW_MBPS(368), /* At least 46 MHz on bus. */
[2] = BW_MBPS(552), /* At least 69 MHz on bus. */
[3] = BW_MBPS(736), /* At least 92 MHz on bus. */
[4] = BW_MBPS(1064), /* At least 133 MHz on bus. */
[5] = BW_MBPS(1536), /* At least 192 MHz on bus. */
};
static struct msm_bus_scale_pdata bus_client_pdata = {
.usecase = bw_level_tbl,
.num_usecases = ARRAY_SIZE(bw_level_tbl),
.active_only = 1,
.name = "acpuclock",
};
static uint32_t bus_perf_client;
static struct clkctl_acpu_speed acpu_freq_tbl[] = {
{ 0, 19200, SRC_CXO, 0, 0, RPM_VREG_CORNER_LOW, 1050000, 0 },
{ 1, 138000, SRC_PLL0, 6, 1, RPM_VREG_CORNER_LOW, 1050000, 2 },
{ 1, 276000, SRC_PLL0, 6, 0, RPM_VREG_CORNER_NOMINAL, 1050000, 2 },
{ 1, 384000, SRC_PLL8, 3, 0, RPM_VREG_CORNER_HIGH, 1150000, 4 },
/* The row below may be changed at runtime depending on hw rev. */
{ 1, 440000, SRC_PLL9, 2, 0, RPM_VREG_CORNER_HIGH, 1150000, 4 },
{ 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);
}
/* Update the bus bandwidth request. */
static void set_bus_bw(unsigned int bw)
{
int ret;
/* Bounds check. */
if (bw >= ARRAY_SIZE(bw_level_tbl)) {
pr_err("invalid bandwidth request (%d)\n", bw);
return;
}
/* Update bandwidth if request has changed. This may sleep. */
ret = msm_bus_scale_client_update_request(bus_perf_client, bw);
if (ret)
pr_err("bandwidth request failed (%d)\n", ret);
return;
}
/* Apply any per-cpu voltage increases. */
static int increase_vdd(unsigned int vdd_cpu, unsigned int vdd_mem)
{
int rc = 0;
/*
* Increase vdd_mem active-set before vdd_cpu.
* vdd_mem should be >= vdd_cpu.
*/
rc = rpm_vreg_set_voltage(RPM_VREG_ID_PM8018_L9, RPM_VREG_VOTER1,
vdd_mem, MAX_VDD_MEM, 0);
if (rc) {
pr_err("vdd_mem increase failed (%d)\n", rc);
return rc;
}
rc = rpm_vreg_set_voltage(RPM_VREG_ID_PM8018_VDD_DIG_CORNER,
RPM_VREG_VOTER1, vdd_cpu, RPM_VREG_CORNER_HIGH, 0);
if (rc)
pr_err("vdd_cpu increase failed (%d)\n", rc);
return rc;
}
/* Apply any per-cpu voltage decreases. */
static void decrease_vdd(unsigned int vdd_cpu, unsigned int vdd_mem)
{
int ret;
/* Update CPU voltage. */
ret = rpm_vreg_set_voltage(RPM_VREG_ID_PM8018_VDD_DIG_CORNER,
RPM_VREG_VOTER1, vdd_cpu, RPM_VREG_CORNER_HIGH, 0);
if (ret) {
pr_err("vdd_cpu decrease failed (%d)\n", ret);
return;
}
/*
* Decrease vdd_mem active-set after vdd_cpu.
* vdd_mem should be >= vdd_cpu.
*/
ret = rpm_vreg_set_voltage(RPM_VREG_ID_PM8018_L9, RPM_VREG_VOTER1,
vdd_mem, MAX_VDD_MEM, 0);
if (ret)
pr_err("vdd_mem decrease failed (%d)\n", ret);
}
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;
}
/* Increase VDD levels if needed. */
if ((reason == SETRATE_CPUFREQ || reason == SETRATE_INIT)
&& (tgt_s->khz > strt_s->khz)) {
rc = increase_vdd(tgt_s->vdd_cpu, tgt_s->vdd_mem);
if (rc)
goto out;
}
pr_debug("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_debug("CPU speed change complete\n");
/* Nothing else to do for SWFI or power-collapse. */
if (reason == SETRATE_SWFI || reason == SETRATE_PC)
goto out;
/* Update bus bandwith request. */
set_bus_bw(tgt_s->bw_level);
/* Drop VDD levels if we can. */
if (tgt_s->khz < strt_s->khz)
decrease_vdd(tgt_s->vdd_cpu, tgt_s->vdd_mem);
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_probe(struct platform_device *pdev)
{
unsigned long max_cpu_khz = 0;
int i;
mutex_init(&drv_state.lock);
bus_perf_client = msm_bus_scale_register_client(&bus_client_pdata);
if (!bus_perf_client) {
pr_err("Unable to register bus client\n");
BUG();
}
for (i = 0; i < NUM_SRC; i++) {
if (clocks[i].name) {
clocks[i].clk = clk_get_sys("acpu", clocks[i].name);
BUG_ON(IS_ERR(clocks[i].clk));
/*
* Prepare the PLLs because we enable/disable them
* in atomic context during power collapse/restore.
*/
BUG_ON(clk_prepare(clocks[i].clk));
}
}
/* Determine the rate of PLL9 and fixup tables accordingly */
if (clk_get_rate(clocks[SRC_PLL9].clk) == 550000000) {
for (i = 0; i < ARRAY_SIZE(acpu_freq_tbl); i++)
if (acpu_freq_tbl[i].src == SRC_PLL9) {
acpu_freq_tbl[i].khz = 550000;
acpu_freq_tbl[i].bw_level = 5;
}
}
/* 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;
}
static struct platform_driver acpuclk_9615_driver = {
.driver = {
.name = "acpuclk-9615",
.owner = THIS_MODULE,
},
};
static int __init acpuclk_9615_init(void)
{
return platform_driver_probe(&acpuclk_9615_driver, acpuclk_9615_probe);
}
device_initcall(acpuclk_9615_init);
|