blob: 5ae5c529fca7c6cc1a72ab54be4e611bc53715da [file] [log] [blame]
Kukjin Kimf7d77072011-06-01 14:18:22 -07001/*
Kukjin Kim7d30e8b2011-02-14 16:33:10 +09002 * Copyright (c) 2010-2011 Samsung Electronics Co., Ltd.
Sunyoung Kangf40f91f2010-09-16 17:59:21 +09003 * http://www.samsung.com
4 *
Jaecheol Leea125a172012-01-07 20:18:35 +09005 * EXYNOS4210 - CPU frequency scaling support
Sunyoung Kangf40f91f2010-09-16 17:59:21 +09006 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License version 2 as
9 * published by the Free Software Foundation.
10*/
11
Jaecheol Lee6c523c62012-01-07 20:18:39 +090012#include <linux/module.h>
Sunyoung Kangf40f91f2010-09-16 17:59:21 +090013#include <linux/kernel.h>
14#include <linux/err.h>
15#include <linux/clk.h>
16#include <linux/io.h>
17#include <linux/slab.h>
Sunyoung Kangf40f91f2010-09-16 17:59:21 +090018#include <linux/cpufreq.h>
19
Sunyoung Kangf40f91f2010-09-16 17:59:21 +090020#include <mach/regs-clock.h>
Jaecheol Leea125a172012-01-07 20:18:35 +090021#include <mach/cpufreq.h>
Sunyoung Kangf40f91f2010-09-16 17:59:21 +090022
Jaecheol Leea125a172012-01-07 20:18:35 +090023#define CPUFREQ_LEVEL_END L5
24
Sunyoung Kangf40f91f2010-09-16 17:59:21 +090025static struct clk *cpu_clk;
26static struct clk *moutcore;
27static struct clk *mout_mpll;
28static struct clk *mout_apll;
29
Jaecheol Lee27f805d2011-12-07 11:44:09 +090030struct cpufreq_clkdiv {
Jaecheol Leea125a172012-01-07 20:18:35 +090031 unsigned int index;
Jaecheol Lee27f805d2011-12-07 11:44:09 +090032 unsigned int clkdiv;
33};
34
Jaecheol Leea125a172012-01-07 20:18:35 +090035static unsigned int exynos4210_volt_table[CPUFREQ_LEVEL_END] = {
36 1250000, 1150000, 1050000, 975000, 950000,
Sunyoung Kangf40f91f2010-09-16 17:59:21 +090037};
38
Jaecheol Lee27f805d2011-12-07 11:44:09 +090039
Jaecheol Leea125a172012-01-07 20:18:35 +090040static struct cpufreq_clkdiv exynos4210_clkdiv_table[CPUFREQ_LEVEL_END];
41
42static struct cpufreq_frequency_table exynos4210_freq_table[] = {
Jaecheol Leeba9d7802011-12-07 11:43:56 +090043 {L0, 1200*1000},
44 {L1, 1000*1000},
45 {L2, 800*1000},
46 {L3, 500*1000},
47 {L4, 200*1000},
Sunyoung Kangf40f91f2010-09-16 17:59:21 +090048 {0, CPUFREQ_TABLE_END},
49};
50
Sangwook Jubf5ce052010-12-22 16:49:32 +090051static unsigned int clkdiv_cpu0[CPUFREQ_LEVEL_END][7] = {
Sunyoung Kangf40f91f2010-09-16 17:59:21 +090052 /*
53 * Clock divider value for following
54 * { DIVCORE, DIVCOREM0, DIVCOREM1, DIVPERIPH,
55 * DIVATB, DIVPCLK_DBG, DIVAPLL }
56 */
57
Jaecheol Leeba9d7802011-12-07 11:43:56 +090058 /* ARM L0: 1200MHz */
59 { 0, 3, 7, 3, 4, 1, 7 },
Sunyoung Kangf40f91f2010-09-16 17:59:21 +090060
Jaecheol Leeba9d7802011-12-07 11:43:56 +090061 /* ARM L1: 1000MHz */
62 { 0, 3, 7, 3, 4, 1, 7 },
Sunyoung Kangf40f91f2010-09-16 17:59:21 +090063
Jaecheol Leeba9d7802011-12-07 11:43:56 +090064 /* ARM L2: 800MHz */
65 { 0, 3, 7, 3, 3, 1, 7 },
Sunyoung Kangf40f91f2010-09-16 17:59:21 +090066
Jaecheol Leeba9d7802011-12-07 11:43:56 +090067 /* ARM L3: 500MHz */
68 { 0, 3, 7, 3, 3, 1, 7 },
69
70 /* ARM L4: 200MHz */
71 { 0, 1, 3, 1, 3, 1, 0 },
Sunyoung Kangf40f91f2010-09-16 17:59:21 +090072};
73
Sangwook Jubf5ce052010-12-22 16:49:32 +090074static unsigned int clkdiv_cpu1[CPUFREQ_LEVEL_END][2] = {
75 /*
76 * Clock divider value for following
77 * { DIVCOPY, DIVHPM }
78 */
79
Jaecheol Leeba9d7802011-12-07 11:43:56 +090080 /* ARM L0: 1200MHz */
81 { 5, 0 },
82
83 /* ARM L1: 1000MHz */
84 { 4, 0 },
85
86 /* ARM L2: 800MHz */
Sangwook Jubf5ce052010-12-22 16:49:32 +090087 { 3, 0 },
88
Jaecheol Leeba9d7802011-12-07 11:43:56 +090089 /* ARM L3: 500MHz */
Sangwook Jubf5ce052010-12-22 16:49:32 +090090 { 3, 0 },
91
Jaecheol Leeba9d7802011-12-07 11:43:56 +090092 /* ARM L4: 200MHz */
Sangwook Jubf5ce052010-12-22 16:49:32 +090093 { 3, 0 },
94};
95
Jaecheol Leea125a172012-01-07 20:18:35 +090096static unsigned int exynos4210_apll_pms_table[CPUFREQ_LEVEL_END] = {
Jaecheol Leeba9d7802011-12-07 11:43:56 +090097 /* APLL FOUT L0: 1200MHz */
98 ((150 << 16) | (3 << 8) | 1),
99
100 /* APLL FOUT L1: 1000MHz */
Sangwook Jubf5ce052010-12-22 16:49:32 +0900101 ((250 << 16) | (6 << 8) | 1),
102
Jaecheol Leeba9d7802011-12-07 11:43:56 +0900103 /* APLL FOUT L2: 800MHz */
Sangwook Jubf5ce052010-12-22 16:49:32 +0900104 ((200 << 16) | (6 << 8) | 1),
105
Jaecheol Leeba9d7802011-12-07 11:43:56 +0900106 /* APLL FOUT L3: 500MHz */
107 ((250 << 16) | (6 << 8) | 2),
Sangwook Jubf5ce052010-12-22 16:49:32 +0900108
Jaecheol Leeba9d7802011-12-07 11:43:56 +0900109 /* APLL FOUT L4: 200MHz */
110 ((200 << 16) | (6 << 8) | 3),
Sangwook Jubf5ce052010-12-22 16:49:32 +0900111};
112
Jaecheol Leea125a172012-01-07 20:18:35 +0900113static void exynos4210_set_clkdiv(unsigned int div_index)
Sunyoung Kangf40f91f2010-09-16 17:59:21 +0900114{
115 unsigned int tmp;
116
117 /* Change Divider - CPU0 */
118
Jaecheol Leea125a172012-01-07 20:18:35 +0900119 tmp = exynos4210_clkdiv_table[div_index].clkdiv;
Sunyoung Kangf40f91f2010-09-16 17:59:21 +0900120
Kukjin Kim09cee1a2012-01-31 13:49:24 +0900121 __raw_writel(tmp, EXYNOS4_CLKDIV_CPU);
Sunyoung Kangf40f91f2010-09-16 17:59:21 +0900122
123 do {
Kukjin Kim09cee1a2012-01-31 13:49:24 +0900124 tmp = __raw_readl(EXYNOS4_CLKDIV_STATCPU);
Sunyoung Kangf40f91f2010-09-16 17:59:21 +0900125 } while (tmp & 0x1111111);
126
Sangwook Jubf5ce052010-12-22 16:49:32 +0900127 /* Change Divider - CPU1 */
128
Kukjin Kim09cee1a2012-01-31 13:49:24 +0900129 tmp = __raw_readl(EXYNOS4_CLKDIV_CPU1);
Sangwook Jubf5ce052010-12-22 16:49:32 +0900130
131 tmp &= ~((0x7 << 4) | 0x7);
132
133 tmp |= ((clkdiv_cpu1[div_index][0] << 4) |
134 (clkdiv_cpu1[div_index][1] << 0));
135
Kukjin Kim09cee1a2012-01-31 13:49:24 +0900136 __raw_writel(tmp, EXYNOS4_CLKDIV_CPU1);
Sangwook Jubf5ce052010-12-22 16:49:32 +0900137
138 do {
Kukjin Kim09cee1a2012-01-31 13:49:24 +0900139 tmp = __raw_readl(EXYNOS4_CLKDIV_STATCPU1);
Sangwook Jubf5ce052010-12-22 16:49:32 +0900140 } while (tmp & 0x11);
Sunyoung Kangf40f91f2010-09-16 17:59:21 +0900141}
142
Jaecheol Leea125a172012-01-07 20:18:35 +0900143static void exynos4210_set_apll(unsigned int index)
Sangwook Jubf5ce052010-12-22 16:49:32 +0900144{
145 unsigned int tmp;
146
147 /* 1. MUX_CORE_SEL = MPLL, ARMCLK uses MPLL for lock time */
148 clk_set_parent(moutcore, mout_mpll);
149
150 do {
Kukjin Kim09cee1a2012-01-31 13:49:24 +0900151 tmp = (__raw_readl(EXYNOS4_CLKMUX_STATCPU)
152 >> EXYNOS4_CLKSRC_CPU_MUXCORE_SHIFT);
Sangwook Jubf5ce052010-12-22 16:49:32 +0900153 tmp &= 0x7;
154 } while (tmp != 0x2);
155
156 /* 2. Set APLL Lock time */
Kukjin Kim09cee1a2012-01-31 13:49:24 +0900157 __raw_writel(EXYNOS4_APLL_LOCKTIME, EXYNOS4_APLL_LOCK);
Sangwook Jubf5ce052010-12-22 16:49:32 +0900158
159 /* 3. Change PLL PMS values */
Kukjin Kim09cee1a2012-01-31 13:49:24 +0900160 tmp = __raw_readl(EXYNOS4_APLL_CON0);
Sangwook Jubf5ce052010-12-22 16:49:32 +0900161 tmp &= ~((0x3ff << 16) | (0x3f << 8) | (0x7 << 0));
Jaecheol Leea125a172012-01-07 20:18:35 +0900162 tmp |= exynos4210_apll_pms_table[index];
Kukjin Kim09cee1a2012-01-31 13:49:24 +0900163 __raw_writel(tmp, EXYNOS4_APLL_CON0);
Sangwook Jubf5ce052010-12-22 16:49:32 +0900164
165 /* 4. wait_lock_time */
166 do {
Kukjin Kim09cee1a2012-01-31 13:49:24 +0900167 tmp = __raw_readl(EXYNOS4_APLL_CON0);
168 } while (!(tmp & (0x1 << EXYNOS4_APLLCON0_LOCKED_SHIFT)));
Sangwook Jubf5ce052010-12-22 16:49:32 +0900169
170 /* 5. MUX_CORE_SEL = APLL */
171 clk_set_parent(moutcore, mout_apll);
172
173 do {
Kukjin Kim09cee1a2012-01-31 13:49:24 +0900174 tmp = __raw_readl(EXYNOS4_CLKMUX_STATCPU);
175 tmp &= EXYNOS4_CLKMUX_STATCPU_MUXCORE_MASK;
176 } while (tmp != (0x1 << EXYNOS4_CLKSRC_CPU_MUXCORE_SHIFT));
Sangwook Jubf5ce052010-12-22 16:49:32 +0900177}
178
Jaecheol Leea125a172012-01-07 20:18:35 +0900179bool exynos4210_pms_change(unsigned int old_index, unsigned int new_index)
180{
181 unsigned int old_pm = (exynos4210_apll_pms_table[old_index] >> 8);
182 unsigned int new_pm = (exynos4210_apll_pms_table[new_index] >> 8);
183
184 return (old_pm == new_pm) ? 0 : 1;
185}
186
187static void exynos4210_set_frequency(unsigned int old_index,
188 unsigned int new_index)
Sangwook Jubf5ce052010-12-22 16:49:32 +0900189{
190 unsigned int tmp;
191
192 if (old_index > new_index) {
Jaecheol Leea125a172012-01-07 20:18:35 +0900193 if (!exynos4210_pms_change(old_index, new_index)) {
Sangwook Jubf5ce052010-12-22 16:49:32 +0900194 /* 1. Change the system clock divider values */
Jaecheol Leea125a172012-01-07 20:18:35 +0900195 exynos4210_set_clkdiv(new_index);
Sangwook Jubf5ce052010-12-22 16:49:32 +0900196
197 /* 2. Change just s value in apll m,p,s value */
Kukjin Kim09cee1a2012-01-31 13:49:24 +0900198 tmp = __raw_readl(EXYNOS4_APLL_CON0);
Sangwook Jubf5ce052010-12-22 16:49:32 +0900199 tmp &= ~(0x7 << 0);
Jaecheol Leea125a172012-01-07 20:18:35 +0900200 tmp |= (exynos4210_apll_pms_table[new_index] & 0x7);
Kukjin Kim09cee1a2012-01-31 13:49:24 +0900201 __raw_writel(tmp, EXYNOS4_APLL_CON0);
Sangwook Jubf5ce052010-12-22 16:49:32 +0900202 } else {
Jaecheol Lee27f805d2011-12-07 11:44:09 +0900203 /* Clock Configuration Procedure */
204 /* 1. Change the system clock divider values */
Jaecheol Leea125a172012-01-07 20:18:35 +0900205 exynos4210_set_clkdiv(new_index);
Jaecheol Lee27f805d2011-12-07 11:44:09 +0900206 /* 2. Change the apll m,p,s value */
Jaecheol Leea125a172012-01-07 20:18:35 +0900207 exynos4210_set_apll(new_index);
Jaecheol Lee27f805d2011-12-07 11:44:09 +0900208 }
209 } else if (old_index < new_index) {
Jaecheol Leea125a172012-01-07 20:18:35 +0900210 if (!exynos4210_pms_change(old_index, new_index)) {
Sangwook Jubf5ce052010-12-22 16:49:32 +0900211 /* 1. Change just s value in apll m,p,s value */
Kukjin Kim09cee1a2012-01-31 13:49:24 +0900212 tmp = __raw_readl(EXYNOS4_APLL_CON0);
Sangwook Jubf5ce052010-12-22 16:49:32 +0900213 tmp &= ~(0x7 << 0);
Jaecheol Leea125a172012-01-07 20:18:35 +0900214 tmp |= (exynos4210_apll_pms_table[new_index] & 0x7);
Kukjin Kim09cee1a2012-01-31 13:49:24 +0900215 __raw_writel(tmp, EXYNOS4_APLL_CON0);
Sangwook Jubf5ce052010-12-22 16:49:32 +0900216
217 /* 2. Change the system clock divider values */
Jaecheol Leea125a172012-01-07 20:18:35 +0900218 exynos4210_set_clkdiv(new_index);
Jaecheol Lee27f805d2011-12-07 11:44:09 +0900219 } else {
220 /* Clock Configuration Procedure */
221 /* 1. Change the apll m,p,s value */
Jaecheol Leea125a172012-01-07 20:18:35 +0900222 exynos4210_set_apll(new_index);
Jaecheol Lee27f805d2011-12-07 11:44:09 +0900223 /* 2. Change the system clock divider values */
Jaecheol Leea125a172012-01-07 20:18:35 +0900224 exynos4210_set_clkdiv(new_index);
Sangwook Jubf5ce052010-12-22 16:49:32 +0900225 }
226 }
227}
228
Jaecheol Leea125a172012-01-07 20:18:35 +0900229int exynos4210_cpufreq_init(struct exynos_dvfs_info *info)
Sunyoung Kangf40f91f2010-09-16 17:59:21 +0900230{
Jaecheol Lee27f805d2011-12-07 11:44:09 +0900231 int i;
232 unsigned int tmp;
Jaecheol Leea125a172012-01-07 20:18:35 +0900233 unsigned long rate;
Jaecheol Lee27f805d2011-12-07 11:44:09 +0900234
Sunyoung Kangf40f91f2010-09-16 17:59:21 +0900235 cpu_clk = clk_get(NULL, "armclk");
236 if (IS_ERR(cpu_clk))
237 return PTR_ERR(cpu_clk);
238
239 moutcore = clk_get(NULL, "moutcore");
240 if (IS_ERR(moutcore))
Jaecheol Leea125a172012-01-07 20:18:35 +0900241 goto err_moutcore;
Sunyoung Kangf40f91f2010-09-16 17:59:21 +0900242
243 mout_mpll = clk_get(NULL, "mout_mpll");
244 if (IS_ERR(mout_mpll))
Jaecheol Leea125a172012-01-07 20:18:35 +0900245 goto err_mout_mpll;
246
247 rate = clk_get_rate(mout_mpll) / 1000;
Sunyoung Kangf40f91f2010-09-16 17:59:21 +0900248
249 mout_apll = clk_get(NULL, "mout_apll");
250 if (IS_ERR(mout_apll))
Jaecheol Leea125a172012-01-07 20:18:35 +0900251 goto err_mout_apll;
MyungJoo Ham0073f532011-08-18 19:45:16 +0900252
Kukjin Kim09cee1a2012-01-31 13:49:24 +0900253 tmp = __raw_readl(EXYNOS4_CLKDIV_CPU);
Jaecheol Lee27f805d2011-12-07 11:44:09 +0900254
255 for (i = L0; i < CPUFREQ_LEVEL_END; i++) {
Kukjin Kim09cee1a2012-01-31 13:49:24 +0900256 tmp &= ~(EXYNOS4_CLKDIV_CPU0_CORE_MASK |
257 EXYNOS4_CLKDIV_CPU0_COREM0_MASK |
258 EXYNOS4_CLKDIV_CPU0_COREM1_MASK |
259 EXYNOS4_CLKDIV_CPU0_PERIPH_MASK |
260 EXYNOS4_CLKDIV_CPU0_ATB_MASK |
261 EXYNOS4_CLKDIV_CPU0_PCLKDBG_MASK |
262 EXYNOS4_CLKDIV_CPU0_APLL_MASK);
Jaecheol Lee27f805d2011-12-07 11:44:09 +0900263
Kukjin Kim09cee1a2012-01-31 13:49:24 +0900264 tmp |= ((clkdiv_cpu0[i][0] << EXYNOS4_CLKDIV_CPU0_CORE_SHIFT) |
265 (clkdiv_cpu0[i][1] << EXYNOS4_CLKDIV_CPU0_COREM0_SHIFT) |
266 (clkdiv_cpu0[i][2] << EXYNOS4_CLKDIV_CPU0_COREM1_SHIFT) |
267 (clkdiv_cpu0[i][3] << EXYNOS4_CLKDIV_CPU0_PERIPH_SHIFT) |
268 (clkdiv_cpu0[i][4] << EXYNOS4_CLKDIV_CPU0_ATB_SHIFT) |
269 (clkdiv_cpu0[i][5] << EXYNOS4_CLKDIV_CPU0_PCLKDBG_SHIFT) |
270 (clkdiv_cpu0[i][6] << EXYNOS4_CLKDIV_CPU0_APLL_SHIFT));
Jaecheol Lee27f805d2011-12-07 11:44:09 +0900271
Jaecheol Leea125a172012-01-07 20:18:35 +0900272 exynos4210_clkdiv_table[i].clkdiv = tmp;
Jaecheol Lee27f805d2011-12-07 11:44:09 +0900273 }
274
Jaecheol Leea125a172012-01-07 20:18:35 +0900275 info->mpll_freq_khz = rate;
Jaecheol Leea125a172012-01-07 20:18:35 +0900276 info->pll_safe_idx = L2;
Jaecheol Leea125a172012-01-07 20:18:35 +0900277 info->cpu_clk = cpu_clk;
278 info->volt_table = exynos4210_volt_table;
279 info->freq_table = exynos4210_freq_table;
280 info->set_freq = exynos4210_set_frequency;
281 info->need_apll_change = exynos4210_pms_change;
Sunyoung Kangf40f91f2010-09-16 17:59:21 +0900282
Jaecheol Leea125a172012-01-07 20:18:35 +0900283 return 0;
284
285err_mout_apll:
Jonghwan Choi184cddd2012-12-23 15:51:40 -0800286 clk_put(mout_mpll);
Jaecheol Leea125a172012-01-07 20:18:35 +0900287err_mout_mpll:
Jonghwan Choi184cddd2012-12-23 15:51:40 -0800288 clk_put(moutcore);
Jaecheol Leea125a172012-01-07 20:18:35 +0900289err_moutcore:
Jonghwan Choi184cddd2012-12-23 15:51:40 -0800290 clk_put(cpu_clk);
Sunyoung Kangf40f91f2010-09-16 17:59:21 +0900291
Jaecheol Leea125a172012-01-07 20:18:35 +0900292 pr_debug("%s: failed initialization\n", __func__);
Sunyoung Kangf40f91f2010-09-16 17:59:21 +0900293 return -EINVAL;
294}
Jaecheol Leea125a172012-01-07 20:18:35 +0900295EXPORT_SYMBOL(exynos4210_cpufreq_init);