| Mark Brown | be2de99 | 2011-05-10 15:42:08 +0200 | [diff] [blame] | 1 | /* | 
| Mark Brown | b3748dd | 2009-06-15 11:23:20 +0100 | [diff] [blame] | 2 |  * Copyright 2009 Wolfson Microelectronics plc | 
 | 3 |  * | 
 | 4 |  * S3C64xx CPUfreq Support | 
 | 5 |  * | 
 | 6 |  * This program is free software; you can redistribute it and/or modify | 
 | 7 |  * it under the terms of the GNU General Public License version 2 as | 
 | 8 |  * published by the Free Software Foundation. | 
 | 9 |  */ | 
 | 10 |  | 
| Mark Brown | a6a4341 | 2011-12-05 18:22:01 +0000 | [diff] [blame] | 11 | #define pr_fmt(fmt) "cpufreq: " fmt | 
 | 12 |  | 
| Mark Brown | b3748dd | 2009-06-15 11:23:20 +0100 | [diff] [blame] | 13 | #include <linux/kernel.h> | 
 | 14 | #include <linux/types.h> | 
 | 15 | #include <linux/init.h> | 
 | 16 | #include <linux/cpufreq.h> | 
 | 17 | #include <linux/clk.h> | 
 | 18 | #include <linux/err.h> | 
 | 19 | #include <linux/regulator/consumer.h> | 
| Mark Brown | a6ee877 | 2011-07-29 16:19:26 +0100 | [diff] [blame] | 20 | #include <linux/module.h> | 
| Mark Brown | b3748dd | 2009-06-15 11:23:20 +0100 | [diff] [blame] | 21 |  | 
 | 22 | static struct clk *armclk; | 
 | 23 | static struct regulator *vddarm; | 
| Mark Brown | 43f1069 | 2009-11-03 14:42:11 +0000 | [diff] [blame] | 24 | static unsigned long regulator_latency; | 
| Mark Brown | b3748dd | 2009-06-15 11:23:20 +0100 | [diff] [blame] | 25 |  | 
 | 26 | #ifdef CONFIG_CPU_S3C6410 | 
 | 27 | struct s3c64xx_dvfs { | 
 | 28 | 	unsigned int vddarm_min; | 
 | 29 | 	unsigned int vddarm_max; | 
 | 30 | }; | 
 | 31 |  | 
 | 32 | static struct s3c64xx_dvfs s3c64xx_dvfs_table[] = { | 
| Mark Brown | e9c08f0 | 2009-11-03 14:42:12 +0000 | [diff] [blame] | 33 | 	[0] = { 1000000, 1150000 }, | 
 | 34 | 	[1] = { 1050000, 1150000 }, | 
 | 35 | 	[2] = { 1100000, 1150000 }, | 
 | 36 | 	[3] = { 1200000, 1350000 }, | 
| Mark Brown | c6e2d68 | 2011-06-08 14:49:15 +0100 | [diff] [blame] | 37 | 	[4] = { 1300000, 1350000 }, | 
| Mark Brown | b3748dd | 2009-06-15 11:23:20 +0100 | [diff] [blame] | 38 | }; | 
 | 39 |  | 
 | 40 | static struct cpufreq_frequency_table s3c64xx_freq_table[] = { | 
 | 41 | 	{ 0,  66000 }, | 
| Mark Brown | ef993ef | 2011-06-28 20:26:49 -0700 | [diff] [blame] | 42 | 	{ 0, 100000 }, | 
| Mark Brown | b3748dd | 2009-06-15 11:23:20 +0100 | [diff] [blame] | 43 | 	{ 0, 133000 }, | 
| Mark Brown | ef993ef | 2011-06-28 20:26:49 -0700 | [diff] [blame] | 44 | 	{ 1, 200000 }, | 
| Mark Brown | b3748dd | 2009-06-15 11:23:20 +0100 | [diff] [blame] | 45 | 	{ 1, 222000 }, | 
 | 46 | 	{ 1, 266000 }, | 
 | 47 | 	{ 2, 333000 }, | 
 | 48 | 	{ 2, 400000 }, | 
| Mark Brown | e9c08f0 | 2009-11-03 14:42:12 +0000 | [diff] [blame] | 49 | 	{ 2, 532000 }, | 
 | 50 | 	{ 2, 533000 }, | 
 | 51 | 	{ 3, 667000 }, | 
| Mark Brown | c6e2d68 | 2011-06-08 14:49:15 +0100 | [diff] [blame] | 52 | 	{ 4, 800000 }, | 
| Mark Brown | b3748dd | 2009-06-15 11:23:20 +0100 | [diff] [blame] | 53 | 	{ 0, CPUFREQ_TABLE_END }, | 
 | 54 | }; | 
 | 55 | #endif | 
 | 56 |  | 
 | 57 | static int s3c64xx_cpufreq_verify_speed(struct cpufreq_policy *policy) | 
 | 58 | { | 
 | 59 | 	if (policy->cpu != 0) | 
 | 60 | 		return -EINVAL; | 
 | 61 |  | 
 | 62 | 	return cpufreq_frequency_table_verify(policy, s3c64xx_freq_table); | 
 | 63 | } | 
 | 64 |  | 
 | 65 | static unsigned int s3c64xx_cpufreq_get_speed(unsigned int cpu) | 
 | 66 | { | 
 | 67 | 	if (cpu != 0) | 
 | 68 | 		return 0; | 
 | 69 |  | 
 | 70 | 	return clk_get_rate(armclk) / 1000; | 
 | 71 | } | 
 | 72 |  | 
 | 73 | static int s3c64xx_cpufreq_set_target(struct cpufreq_policy *policy, | 
 | 74 | 				      unsigned int target_freq, | 
 | 75 | 				      unsigned int relation) | 
 | 76 | { | 
 | 77 | 	int ret; | 
 | 78 | 	unsigned int i; | 
 | 79 | 	struct cpufreq_freqs freqs; | 
 | 80 | 	struct s3c64xx_dvfs *dvfs; | 
 | 81 |  | 
 | 82 | 	ret = cpufreq_frequency_table_target(policy, s3c64xx_freq_table, | 
 | 83 | 					     target_freq, relation, &i); | 
 | 84 | 	if (ret != 0) | 
 | 85 | 		return ret; | 
 | 86 |  | 
 | 87 | 	freqs.cpu = 0; | 
 | 88 | 	freqs.old = clk_get_rate(armclk) / 1000; | 
 | 89 | 	freqs.new = s3c64xx_freq_table[i].frequency; | 
 | 90 | 	freqs.flags = 0; | 
 | 91 | 	dvfs = &s3c64xx_dvfs_table[s3c64xx_freq_table[i].index]; | 
 | 92 |  | 
 | 93 | 	if (freqs.old == freqs.new) | 
 | 94 | 		return 0; | 
 | 95 |  | 
| Mark Brown | a6a4341 | 2011-12-05 18:22:01 +0000 | [diff] [blame] | 96 | 	pr_debug("Transition %d-%dkHz\n", freqs.old, freqs.new); | 
| Mark Brown | b3748dd | 2009-06-15 11:23:20 +0100 | [diff] [blame] | 97 |  | 
 | 98 | 	cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); | 
 | 99 |  | 
 | 100 | #ifdef CONFIG_REGULATOR | 
 | 101 | 	if (vddarm && freqs.new > freqs.old) { | 
 | 102 | 		ret = regulator_set_voltage(vddarm, | 
 | 103 | 					    dvfs->vddarm_min, | 
 | 104 | 					    dvfs->vddarm_max); | 
 | 105 | 		if (ret != 0) { | 
| Mark Brown | a6a4341 | 2011-12-05 18:22:01 +0000 | [diff] [blame] | 106 | 			pr_err("Failed to set VDDARM for %dkHz: %d\n", | 
| Mark Brown | b3748dd | 2009-06-15 11:23:20 +0100 | [diff] [blame] | 107 | 			       freqs.new, ret); | 
 | 108 | 			goto err; | 
 | 109 | 		} | 
 | 110 | 	} | 
 | 111 | #endif | 
 | 112 |  | 
 | 113 | 	ret = clk_set_rate(armclk, freqs.new * 1000); | 
 | 114 | 	if (ret < 0) { | 
| Mark Brown | a6a4341 | 2011-12-05 18:22:01 +0000 | [diff] [blame] | 115 | 		pr_err("Failed to set rate %dkHz: %d\n", | 
| Mark Brown | b3748dd | 2009-06-15 11:23:20 +0100 | [diff] [blame] | 116 | 		       freqs.new, ret); | 
 | 117 | 		goto err; | 
 | 118 | 	} | 
 | 119 |  | 
| Mark Brown | fb3b1fe | 2011-06-22 15:08:56 +0100 | [diff] [blame] | 120 | 	cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); | 
 | 121 |  | 
| Mark Brown | b3748dd | 2009-06-15 11:23:20 +0100 | [diff] [blame] | 122 | #ifdef CONFIG_REGULATOR | 
 | 123 | 	if (vddarm && freqs.new < freqs.old) { | 
 | 124 | 		ret = regulator_set_voltage(vddarm, | 
 | 125 | 					    dvfs->vddarm_min, | 
 | 126 | 					    dvfs->vddarm_max); | 
 | 127 | 		if (ret != 0) { | 
| Mark Brown | a6a4341 | 2011-12-05 18:22:01 +0000 | [diff] [blame] | 128 | 			pr_err("Failed to set VDDARM for %dkHz: %d\n", | 
| Mark Brown | b3748dd | 2009-06-15 11:23:20 +0100 | [diff] [blame] | 129 | 			       freqs.new, ret); | 
 | 130 | 			goto err_clk; | 
 | 131 | 		} | 
 | 132 | 	} | 
 | 133 | #endif | 
 | 134 |  | 
| Mark Brown | a6a4341 | 2011-12-05 18:22:01 +0000 | [diff] [blame] | 135 | 	pr_debug("Set actual frequency %lukHz\n", | 
| Mark Brown | b3748dd | 2009-06-15 11:23:20 +0100 | [diff] [blame] | 136 | 		 clk_get_rate(armclk) / 1000); | 
 | 137 |  | 
 | 138 | 	return 0; | 
 | 139 |  | 
 | 140 | err_clk: | 
 | 141 | 	if (clk_set_rate(armclk, freqs.old * 1000) < 0) | 
 | 142 | 		pr_err("Failed to restore original clock rate\n"); | 
 | 143 | err: | 
 | 144 | 	cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); | 
 | 145 |  | 
 | 146 | 	return ret; | 
 | 147 | } | 
 | 148 |  | 
 | 149 | #ifdef CONFIG_REGULATOR | 
| Mark Brown | 43f1069 | 2009-11-03 14:42:11 +0000 | [diff] [blame] | 150 | static void __init s3c64xx_cpufreq_config_regulator(void) | 
| Mark Brown | b3748dd | 2009-06-15 11:23:20 +0100 | [diff] [blame] | 151 | { | 
 | 152 | 	int count, v, i, found; | 
 | 153 | 	struct cpufreq_frequency_table *freq; | 
 | 154 | 	struct s3c64xx_dvfs *dvfs; | 
 | 155 |  | 
 | 156 | 	count = regulator_count_voltages(vddarm); | 
 | 157 | 	if (count < 0) { | 
| Mark Brown | a6a4341 | 2011-12-05 18:22:01 +0000 | [diff] [blame] | 158 | 		pr_err("Unable to check supported voltages\n"); | 
| Mark Brown | b3748dd | 2009-06-15 11:23:20 +0100 | [diff] [blame] | 159 | 	} | 
 | 160 |  | 
 | 161 | 	freq = s3c64xx_freq_table; | 
| Mark Brown | 43f1069 | 2009-11-03 14:42:11 +0000 | [diff] [blame] | 162 | 	while (count > 0 && freq->frequency != CPUFREQ_TABLE_END) { | 
| Mark Brown | b3748dd | 2009-06-15 11:23:20 +0100 | [diff] [blame] | 163 | 		if (freq->frequency == CPUFREQ_ENTRY_INVALID) | 
 | 164 | 			continue; | 
 | 165 |  | 
 | 166 | 		dvfs = &s3c64xx_dvfs_table[freq->index]; | 
 | 167 | 		found = 0; | 
 | 168 |  | 
 | 169 | 		for (i = 0; i < count; i++) { | 
 | 170 | 			v = regulator_list_voltage(vddarm, i); | 
 | 171 | 			if (v >= dvfs->vddarm_min && v <= dvfs->vddarm_max) | 
 | 172 | 				found = 1; | 
 | 173 | 		} | 
 | 174 |  | 
 | 175 | 		if (!found) { | 
| Mark Brown | a6a4341 | 2011-12-05 18:22:01 +0000 | [diff] [blame] | 176 | 			pr_debug("%dkHz unsupported by regulator\n", | 
| Mark Brown | b3748dd | 2009-06-15 11:23:20 +0100 | [diff] [blame] | 177 | 				 freq->frequency); | 
 | 178 | 			freq->frequency = CPUFREQ_ENTRY_INVALID; | 
 | 179 | 		} | 
 | 180 |  | 
 | 181 | 		freq++; | 
 | 182 | 	} | 
| Mark Brown | 43f1069 | 2009-11-03 14:42:11 +0000 | [diff] [blame] | 183 |  | 
 | 184 | 	/* Guess based on having to do an I2C/SPI write; in future we | 
 | 185 | 	 * will be able to query the regulator performance here. */ | 
 | 186 | 	regulator_latency = 1 * 1000 * 1000; | 
| Mark Brown | b3748dd | 2009-06-15 11:23:20 +0100 | [diff] [blame] | 187 | } | 
 | 188 | #endif | 
 | 189 |  | 
| Mark Brown | 6d0de15 | 2011-03-11 16:10:03 +0900 | [diff] [blame] | 190 | static int s3c64xx_cpufreq_driver_init(struct cpufreq_policy *policy) | 
| Mark Brown | b3748dd | 2009-06-15 11:23:20 +0100 | [diff] [blame] | 191 | { | 
 | 192 | 	int ret; | 
 | 193 | 	struct cpufreq_frequency_table *freq; | 
 | 194 |  | 
 | 195 | 	if (policy->cpu != 0) | 
 | 196 | 		return -EINVAL; | 
 | 197 |  | 
 | 198 | 	if (s3c64xx_freq_table == NULL) { | 
| Mark Brown | a6a4341 | 2011-12-05 18:22:01 +0000 | [diff] [blame] | 199 | 		pr_err("No frequency information for this CPU\n"); | 
| Mark Brown | b3748dd | 2009-06-15 11:23:20 +0100 | [diff] [blame] | 200 | 		return -ENODEV; | 
 | 201 | 	} | 
 | 202 |  | 
 | 203 | 	armclk = clk_get(NULL, "armclk"); | 
 | 204 | 	if (IS_ERR(armclk)) { | 
| Mark Brown | a6a4341 | 2011-12-05 18:22:01 +0000 | [diff] [blame] | 205 | 		pr_err("Unable to obtain ARMCLK: %ld\n", | 
| Mark Brown | b3748dd | 2009-06-15 11:23:20 +0100 | [diff] [blame] | 206 | 		       PTR_ERR(armclk)); | 
 | 207 | 		return PTR_ERR(armclk); | 
 | 208 | 	} | 
 | 209 |  | 
 | 210 | #ifdef CONFIG_REGULATOR | 
 | 211 | 	vddarm = regulator_get(NULL, "vddarm"); | 
 | 212 | 	if (IS_ERR(vddarm)) { | 
 | 213 | 		ret = PTR_ERR(vddarm); | 
| Mark Brown | a6a4341 | 2011-12-05 18:22:01 +0000 | [diff] [blame] | 214 | 		pr_err("Failed to obtain VDDARM: %d\n", ret); | 
 | 215 | 		pr_err("Only frequency scaling available\n"); | 
| Mark Brown | b3748dd | 2009-06-15 11:23:20 +0100 | [diff] [blame] | 216 | 		vddarm = NULL; | 
 | 217 | 	} else { | 
| Mark Brown | 43f1069 | 2009-11-03 14:42:11 +0000 | [diff] [blame] | 218 | 		s3c64xx_cpufreq_config_regulator(); | 
| Mark Brown | b3748dd | 2009-06-15 11:23:20 +0100 | [diff] [blame] | 219 | 	} | 
 | 220 | #endif | 
 | 221 |  | 
 | 222 | 	freq = s3c64xx_freq_table; | 
 | 223 | 	while (freq->frequency != CPUFREQ_TABLE_END) { | 
 | 224 | 		unsigned long r; | 
 | 225 |  | 
 | 226 | 		/* Check for frequencies we can generate */ | 
 | 227 | 		r = clk_round_rate(armclk, freq->frequency * 1000); | 
 | 228 | 		r /= 1000; | 
| Mark Brown | 383af9c | 2009-11-03 14:42:07 +0000 | [diff] [blame] | 229 | 		if (r != freq->frequency) { | 
| Mark Brown | a6a4341 | 2011-12-05 18:22:01 +0000 | [diff] [blame] | 230 | 			pr_debug("%dkHz unsupported by clock\n", | 
| Mark Brown | 383af9c | 2009-11-03 14:42:07 +0000 | [diff] [blame] | 231 | 				 freq->frequency); | 
| Mark Brown | b3748dd | 2009-06-15 11:23:20 +0100 | [diff] [blame] | 232 | 			freq->frequency = CPUFREQ_ENTRY_INVALID; | 
| Mark Brown | 383af9c | 2009-11-03 14:42:07 +0000 | [diff] [blame] | 233 | 		} | 
| Mark Brown | b3748dd | 2009-06-15 11:23:20 +0100 | [diff] [blame] | 234 |  | 
 | 235 | 		/* If we have no regulator then assume startup | 
 | 236 | 		 * frequency is the maximum we can support. */ | 
 | 237 | 		if (!vddarm && freq->frequency > s3c64xx_cpufreq_get_speed(0)) | 
 | 238 | 			freq->frequency = CPUFREQ_ENTRY_INVALID; | 
 | 239 |  | 
 | 240 | 		freq++; | 
 | 241 | 	} | 
 | 242 |  | 
 | 243 | 	policy->cur = clk_get_rate(armclk) / 1000; | 
 | 244 |  | 
| Mark Brown | 43f1069 | 2009-11-03 14:42:11 +0000 | [diff] [blame] | 245 | 	/* Datasheet says PLL stabalisation time (if we were to use | 
 | 246 | 	 * the PLLs, which we don't currently) is ~300us worst case, | 
 | 247 | 	 * but add some fudge. | 
 | 248 | 	 */ | 
 | 249 | 	policy->cpuinfo.transition_latency = (500 * 1000) + regulator_latency; | 
| Mark Brown | b3748dd | 2009-06-15 11:23:20 +0100 | [diff] [blame] | 250 |  | 
 | 251 | 	ret = cpufreq_frequency_table_cpuinfo(policy, s3c64xx_freq_table); | 
 | 252 | 	if (ret != 0) { | 
| Mark Brown | a6a4341 | 2011-12-05 18:22:01 +0000 | [diff] [blame] | 253 | 		pr_err("Failed to configure frequency table: %d\n", | 
| Mark Brown | b3748dd | 2009-06-15 11:23:20 +0100 | [diff] [blame] | 254 | 		       ret); | 
 | 255 | 		regulator_put(vddarm); | 
 | 256 | 		clk_put(armclk); | 
 | 257 | 	} | 
 | 258 |  | 
 | 259 | 	return ret; | 
 | 260 | } | 
 | 261 |  | 
 | 262 | static struct cpufreq_driver s3c64xx_cpufreq_driver = { | 
 | 263 | 	.owner		= THIS_MODULE, | 
 | 264 | 	.flags          = 0, | 
 | 265 | 	.verify		= s3c64xx_cpufreq_verify_speed, | 
 | 266 | 	.target		= s3c64xx_cpufreq_set_target, | 
 | 267 | 	.get		= s3c64xx_cpufreq_get_speed, | 
 | 268 | 	.init		= s3c64xx_cpufreq_driver_init, | 
 | 269 | 	.name		= "s3c", | 
 | 270 | }; | 
 | 271 |  | 
 | 272 | static int __init s3c64xx_cpufreq_init(void) | 
 | 273 | { | 
 | 274 | 	return cpufreq_register_driver(&s3c64xx_cpufreq_driver); | 
 | 275 | } | 
 | 276 | module_init(s3c64xx_cpufreq_init); |