| Eric Miao | 4f788bb | 2008-08-06 15:59:06 +0800 | [diff] [blame] | 1 | /* | 
|  | 2 | * linux/arch/arm/mach-pxa/cpufreq-pxa3xx.c | 
|  | 3 | * | 
|  | 4 | * Copyright (C) 2008 Marvell International Ltd. | 
|  | 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 as published by | 
|  | 8 | * the Free Software Foundation; either version 2 of the License, or | 
|  | 9 | * (at your option) any later version. | 
|  | 10 | */ | 
|  | 11 |  | 
|  | 12 | #include <linux/kernel.h> | 
|  | 13 | #include <linux/module.h> | 
|  | 14 | #include <linux/sched.h> | 
|  | 15 | #include <linux/init.h> | 
|  | 16 | #include <linux/cpufreq.h> | 
| Tejun Heo | 5a0e3ad | 2010-03-24 17:04:11 +0900 | [diff] [blame] | 17 | #include <linux/slab.h> | 
| Eric Miao | 4f788bb | 2008-08-06 15:59:06 +0800 | [diff] [blame] | 18 |  | 
| Eric Miao | 4f788bb | 2008-08-06 15:59:06 +0800 | [diff] [blame] | 19 | #include <mach/pxa3xx-regs.h> | 
|  | 20 |  | 
|  | 21 | #include "generic.h" | 
|  | 22 |  | 
|  | 23 | #define HSS_104M	(0) | 
|  | 24 | #define HSS_156M	(1) | 
|  | 25 | #define HSS_208M	(2) | 
|  | 26 | #define HSS_312M	(3) | 
|  | 27 |  | 
|  | 28 | #define SMCFS_78M	(0) | 
|  | 29 | #define SMCFS_104M	(2) | 
|  | 30 | #define SMCFS_208M	(5) | 
|  | 31 |  | 
|  | 32 | #define SFLFS_104M	(0) | 
|  | 33 | #define SFLFS_156M	(1) | 
|  | 34 | #define SFLFS_208M	(2) | 
|  | 35 | #define SFLFS_312M	(3) | 
|  | 36 |  | 
|  | 37 | #define XSPCLK_156M	(0) | 
|  | 38 | #define XSPCLK_NONE	(3) | 
|  | 39 |  | 
|  | 40 | #define DMCFS_26M	(0) | 
|  | 41 | #define DMCFS_260M	(3) | 
|  | 42 |  | 
|  | 43 | struct pxa3xx_freq_info { | 
|  | 44 | unsigned int cpufreq_mhz; | 
|  | 45 | unsigned int core_xl : 5; | 
|  | 46 | unsigned int core_xn : 3; | 
|  | 47 | unsigned int hss : 2; | 
|  | 48 | unsigned int dmcfs : 2; | 
|  | 49 | unsigned int smcfs : 3; | 
|  | 50 | unsigned int sflfs : 2; | 
|  | 51 | unsigned int df_clkdiv : 3; | 
|  | 52 |  | 
|  | 53 | int	vcc_core;	/* in mV */ | 
|  | 54 | int	vcc_sram;	/* in mV */ | 
|  | 55 | }; | 
|  | 56 |  | 
|  | 57 | #define OP(cpufreq, _xl, _xn, _hss, _dmc, _smc, _sfl, _dfi, vcore, vsram) \ | 
|  | 58 | {									\ | 
|  | 59 | .cpufreq_mhz	= cpufreq,					\ | 
|  | 60 | .core_xl	= _xl,						\ | 
|  | 61 | .core_xn	= _xn,						\ | 
|  | 62 | .hss		= HSS_##_hss##M,				\ | 
|  | 63 | .dmcfs		= DMCFS_##_dmc##M,				\ | 
|  | 64 | .smcfs		= SMCFS_##_smc##M,				\ | 
|  | 65 | .sflfs		= SFLFS_##_sfl##M,				\ | 
|  | 66 | .df_clkdiv	= _dfi,						\ | 
|  | 67 | .vcc_core	= vcore,					\ | 
|  | 68 | .vcc_sram	= vsram,					\ | 
|  | 69 | } | 
|  | 70 |  | 
|  | 71 | static struct pxa3xx_freq_info pxa300_freqs[] = { | 
|  | 72 | /*  CPU XL XN  HSS DMEM SMEM SRAM DFI VCC_CORE VCC_SRAM */ | 
|  | 73 | OP(104,  8, 1, 104, 260,  78, 104, 3, 1000, 1100), /* 104MHz */ | 
|  | 74 | OP(208, 16, 1, 104, 260, 104, 156, 2, 1000, 1100), /* 208MHz */ | 
|  | 75 | OP(416, 16, 2, 156, 260, 104, 208, 2, 1100, 1200), /* 416MHz */ | 
|  | 76 | OP(624, 24, 2, 208, 260, 208, 312, 3, 1375, 1400), /* 624MHz */ | 
|  | 77 | }; | 
|  | 78 |  | 
|  | 79 | static struct pxa3xx_freq_info pxa320_freqs[] = { | 
|  | 80 | /*  CPU XL XN  HSS DMEM SMEM SRAM DFI VCC_CORE VCC_SRAM */ | 
|  | 81 | OP(104,  8, 1, 104, 260,  78, 104, 3, 1000, 1100), /* 104MHz */ | 
|  | 82 | OP(208, 16, 1, 104, 260, 104, 156, 2, 1000, 1100), /* 208MHz */ | 
|  | 83 | OP(416, 16, 2, 156, 260, 104, 208, 2, 1100, 1200), /* 416MHz */ | 
|  | 84 | OP(624, 24, 2, 208, 260, 208, 312, 3, 1375, 1400), /* 624MHz */ | 
|  | 85 | OP(806, 31, 2, 208, 260, 208, 312, 3, 1400, 1400), /* 806MHz */ | 
|  | 86 | }; | 
|  | 87 |  | 
|  | 88 | static unsigned int pxa3xx_freqs_num; | 
|  | 89 | static struct pxa3xx_freq_info *pxa3xx_freqs; | 
|  | 90 | static struct cpufreq_frequency_table *pxa3xx_freqs_table; | 
|  | 91 |  | 
|  | 92 | static int setup_freqs_table(struct cpufreq_policy *policy, | 
|  | 93 | struct pxa3xx_freq_info *freqs, int num) | 
|  | 94 | { | 
|  | 95 | struct cpufreq_frequency_table *table; | 
|  | 96 | int i; | 
|  | 97 |  | 
|  | 98 | table = kzalloc((num + 1) * sizeof(*table), GFP_KERNEL); | 
|  | 99 | if (table == NULL) | 
|  | 100 | return -ENOMEM; | 
|  | 101 |  | 
|  | 102 | for (i = 0; i < num; i++) { | 
|  | 103 | table[i].index = i; | 
|  | 104 | table[i].frequency = freqs[i].cpufreq_mhz * 1000; | 
|  | 105 | } | 
| Daniel Mack | 68a31de | 2009-11-20 17:50:34 +0100 | [diff] [blame] | 106 | table[num].index = i; | 
| Eric Miao | 4f788bb | 2008-08-06 15:59:06 +0800 | [diff] [blame] | 107 | table[num].frequency = CPUFREQ_TABLE_END; | 
|  | 108 |  | 
|  | 109 | pxa3xx_freqs = freqs; | 
|  | 110 | pxa3xx_freqs_num = num; | 
|  | 111 | pxa3xx_freqs_table = table; | 
|  | 112 |  | 
|  | 113 | return cpufreq_frequency_table_cpuinfo(policy, table); | 
|  | 114 | } | 
|  | 115 |  | 
|  | 116 | static void __update_core_freq(struct pxa3xx_freq_info *info) | 
|  | 117 | { | 
|  | 118 | uint32_t mask = ACCR_XN_MASK | ACCR_XL_MASK; | 
|  | 119 | uint32_t accr = ACCR; | 
|  | 120 | uint32_t xclkcfg; | 
|  | 121 |  | 
|  | 122 | accr &= ~(ACCR_XN_MASK | ACCR_XL_MASK | ACCR_XSPCLK_MASK); | 
|  | 123 | accr |= ACCR_XN(info->core_xn) | ACCR_XL(info->core_xl); | 
|  | 124 |  | 
|  | 125 | /* No clock until core PLL is re-locked */ | 
|  | 126 | accr |= ACCR_XSPCLK(XSPCLK_NONE); | 
|  | 127 |  | 
|  | 128 | xclkcfg = (info->core_xn == 2) ? 0x3 : 0x2;	/* turbo bit */ | 
|  | 129 |  | 
|  | 130 | ACCR = accr; | 
|  | 131 | __asm__("mcr p14, 0, %0, c6, c0, 0\n" : : "r"(xclkcfg)); | 
|  | 132 |  | 
|  | 133 | while ((ACSR & mask) != (accr & mask)) | 
|  | 134 | cpu_relax(); | 
|  | 135 | } | 
|  | 136 |  | 
|  | 137 | static void __update_bus_freq(struct pxa3xx_freq_info *info) | 
|  | 138 | { | 
|  | 139 | uint32_t mask; | 
|  | 140 | uint32_t accr = ACCR; | 
|  | 141 |  | 
|  | 142 | mask = ACCR_SMCFS_MASK | ACCR_SFLFS_MASK | ACCR_HSS_MASK | | 
|  | 143 | ACCR_DMCFS_MASK; | 
|  | 144 |  | 
|  | 145 | accr &= ~mask; | 
|  | 146 | accr |= ACCR_SMCFS(info->smcfs) | ACCR_SFLFS(info->sflfs) | | 
|  | 147 | ACCR_HSS(info->hss) | ACCR_DMCFS(info->dmcfs); | 
|  | 148 |  | 
|  | 149 | ACCR = accr; | 
|  | 150 |  | 
|  | 151 | while ((ACSR & mask) != (accr & mask)) | 
|  | 152 | cpu_relax(); | 
|  | 153 | } | 
|  | 154 |  | 
|  | 155 | static int pxa3xx_cpufreq_verify(struct cpufreq_policy *policy) | 
|  | 156 | { | 
|  | 157 | return cpufreq_frequency_table_verify(policy, pxa3xx_freqs_table); | 
|  | 158 | } | 
|  | 159 |  | 
|  | 160 | static unsigned int pxa3xx_cpufreq_get(unsigned int cpu) | 
|  | 161 | { | 
|  | 162 | return get_clk_frequency_khz(0); | 
|  | 163 | } | 
|  | 164 |  | 
|  | 165 | static int pxa3xx_cpufreq_set(struct cpufreq_policy *policy, | 
|  | 166 | unsigned int target_freq, | 
|  | 167 | unsigned int relation) | 
|  | 168 | { | 
|  | 169 | struct pxa3xx_freq_info *next; | 
|  | 170 | struct cpufreq_freqs freqs; | 
|  | 171 | unsigned long flags; | 
|  | 172 | int idx; | 
|  | 173 |  | 
|  | 174 | if (policy->cpu != 0) | 
|  | 175 | return -EINVAL; | 
|  | 176 |  | 
|  | 177 | /* Lookup the next frequency */ | 
|  | 178 | if (cpufreq_frequency_table_target(policy, pxa3xx_freqs_table, | 
|  | 179 | target_freq, relation, &idx)) | 
|  | 180 | return -EINVAL; | 
|  | 181 |  | 
|  | 182 | next = &pxa3xx_freqs[idx]; | 
|  | 183 |  | 
|  | 184 | freqs.old = policy->cur; | 
|  | 185 | freqs.new = next->cpufreq_mhz * 1000; | 
|  | 186 | freqs.cpu = policy->cpu; | 
|  | 187 |  | 
|  | 188 | pr_debug("CPU frequency from %d MHz to %d MHz%s\n", | 
|  | 189 | freqs.old / 1000, freqs.new / 1000, | 
|  | 190 | (freqs.old == freqs.new) ? " (skipped)" : ""); | 
|  | 191 |  | 
|  | 192 | if (freqs.old == target_freq) | 
|  | 193 | return 0; | 
|  | 194 |  | 
|  | 195 | cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); | 
|  | 196 |  | 
|  | 197 | local_irq_save(flags); | 
|  | 198 | __update_core_freq(next); | 
|  | 199 | __update_bus_freq(next); | 
|  | 200 | local_irq_restore(flags); | 
|  | 201 |  | 
|  | 202 | cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); | 
|  | 203 |  | 
|  | 204 | return 0; | 
|  | 205 | } | 
|  | 206 |  | 
|  | 207 | static __init int pxa3xx_cpufreq_init(struct cpufreq_policy *policy) | 
|  | 208 | { | 
|  | 209 | int ret = -EINVAL; | 
|  | 210 |  | 
|  | 211 | /* set default policy and cpuinfo */ | 
| Eric Miao | 4f788bb | 2008-08-06 15:59:06 +0800 | [diff] [blame] | 212 | policy->cpuinfo.min_freq = 104000; | 
|  | 213 | policy->cpuinfo.max_freq = (cpu_is_pxa320()) ? 806000 : 624000; | 
|  | 214 | policy->cpuinfo.transition_latency = 1000; /* FIXME: 1 ms, assumed */ | 
|  | 215 | policy->cur = policy->min = policy->max = get_clk_frequency_khz(0); | 
|  | 216 |  | 
|  | 217 | if (cpu_is_pxa300() || cpu_is_pxa310()) | 
|  | 218 | ret = setup_freqs_table(policy, ARRAY_AND_SIZE(pxa300_freqs)); | 
|  | 219 |  | 
|  | 220 | if (cpu_is_pxa320()) | 
|  | 221 | ret = setup_freqs_table(policy, ARRAY_AND_SIZE(pxa320_freqs)); | 
|  | 222 |  | 
|  | 223 | if (ret) { | 
|  | 224 | pr_err("failed to setup frequency table\n"); | 
|  | 225 | return ret; | 
|  | 226 | } | 
|  | 227 |  | 
|  | 228 | pr_info("CPUFREQ support for PXA3xx initialized\n"); | 
|  | 229 | return 0; | 
|  | 230 | } | 
|  | 231 |  | 
|  | 232 | static struct cpufreq_driver pxa3xx_cpufreq_driver = { | 
|  | 233 | .verify		= pxa3xx_cpufreq_verify, | 
|  | 234 | .target		= pxa3xx_cpufreq_set, | 
|  | 235 | .init		= pxa3xx_cpufreq_init, | 
|  | 236 | .get		= pxa3xx_cpufreq_get, | 
|  | 237 | .name		= "pxa3xx-cpufreq", | 
|  | 238 | }; | 
|  | 239 |  | 
|  | 240 | static int __init cpufreq_init(void) | 
|  | 241 | { | 
|  | 242 | if (cpu_is_pxa3xx()) | 
|  | 243 | return cpufreq_register_driver(&pxa3xx_cpufreq_driver); | 
|  | 244 |  | 
|  | 245 | return 0; | 
|  | 246 | } | 
|  | 247 | module_init(cpufreq_init); | 
|  | 248 |  | 
|  | 249 | static void __exit cpufreq_exit(void) | 
|  | 250 | { | 
|  | 251 | cpufreq_unregister_driver(&pxa3xx_cpufreq_driver); | 
|  | 252 | } | 
|  | 253 | module_exit(cpufreq_exit); | 
|  | 254 |  | 
|  | 255 | MODULE_DESCRIPTION("CPU frequency scaling driver for PXA3xx"); | 
|  | 256 | MODULE_LICENSE("GPL"); |