linux/drivers/cpufreq/sparc-us2e-cpufreq.c
<<
>>
Prefs
   1/* us2e_cpufreq.c: UltraSPARC-IIe cpu frequency support
   2 *
   3 * Copyright (C) 2003 David S. Miller (davem@redhat.com)
   4 *
   5 * Many thanks to Dominik Brodowski for fixing up the cpufreq
   6 * infrastructure in order to make this driver easier to implement.
   7 */
   8
   9#include <linux/kernel.h>
  10#include <linux/module.h>
  11#include <linux/sched.h>
  12#include <linux/smp.h>
  13#include <linux/cpufreq.h>
  14#include <linux/threads.h>
  15#include <linux/slab.h>
  16#include <linux/delay.h>
  17#include <linux/init.h>
  18
  19#include <asm/asi.h>
  20#include <asm/timer.h>
  21
  22static struct cpufreq_driver *cpufreq_us2e_driver;
  23
  24struct us2e_freq_percpu_info {
  25        struct cpufreq_frequency_table table[6];
  26};
  27
  28/* Indexed by cpu number. */
  29static struct us2e_freq_percpu_info *us2e_freq_table;
  30
  31#define HBIRD_MEM_CNTL0_ADDR    0x1fe0000f010UL
  32#define HBIRD_ESTAR_MODE_ADDR   0x1fe0000f080UL
  33
  34/* UltraSPARC-IIe has five dividers: 1, 2, 4, 6, and 8.  These are controlled
  35 * in the ESTAR mode control register.
  36 */
  37#define ESTAR_MODE_DIV_1        0x0000000000000000UL
  38#define ESTAR_MODE_DIV_2        0x0000000000000001UL
  39#define ESTAR_MODE_DIV_4        0x0000000000000003UL
  40#define ESTAR_MODE_DIV_6        0x0000000000000002UL
  41#define ESTAR_MODE_DIV_8        0x0000000000000004UL
  42#define ESTAR_MODE_DIV_MASK     0x0000000000000007UL
  43
  44#define MCTRL0_SREFRESH_ENAB    0x0000000000010000UL
  45#define MCTRL0_REFR_COUNT_MASK  0x0000000000007f00UL
  46#define MCTRL0_REFR_COUNT_SHIFT 8
  47#define MCTRL0_REFR_INTERVAL    7800
  48#define MCTRL0_REFR_CLKS_P_CNT  64
  49
  50static unsigned long read_hbreg(unsigned long addr)
  51{
  52        unsigned long ret;
  53
  54        __asm__ __volatile__("ldxa      [%1] %2, %0"
  55                             : "=&r" (ret)
  56                             : "r" (addr), "i" (ASI_PHYS_BYPASS_EC_E));
  57        return ret;
  58}
  59
  60static void write_hbreg(unsigned long addr, unsigned long val)
  61{
  62        __asm__ __volatile__("stxa      %0, [%1] %2\n\t"
  63                             "membar    #Sync"
  64                             : /* no outputs */
  65                             : "r" (val), "r" (addr), "i" (ASI_PHYS_BYPASS_EC_E)
  66                             : "memory");
  67        if (addr == HBIRD_ESTAR_MODE_ADDR) {
  68                /* Need to wait 16 clock cycles for the PLL to lock.  */
  69                udelay(1);
  70        }
  71}
  72
  73static void self_refresh_ctl(int enable)
  74{
  75        unsigned long mctrl = read_hbreg(HBIRD_MEM_CNTL0_ADDR);
  76
  77        if (enable)
  78                mctrl |= MCTRL0_SREFRESH_ENAB;
  79        else
  80                mctrl &= ~MCTRL0_SREFRESH_ENAB;
  81        write_hbreg(HBIRD_MEM_CNTL0_ADDR, mctrl);
  82        (void) read_hbreg(HBIRD_MEM_CNTL0_ADDR);
  83}
  84
  85static void frob_mem_refresh(int cpu_slowing_down,
  86                             unsigned long clock_tick,
  87                             unsigned long old_divisor, unsigned long divisor)
  88{
  89        unsigned long old_refr_count, refr_count, mctrl;
  90
  91        refr_count  = (clock_tick * MCTRL0_REFR_INTERVAL);
  92        refr_count /= (MCTRL0_REFR_CLKS_P_CNT * divisor * 1000000000UL);
  93
  94        mctrl = read_hbreg(HBIRD_MEM_CNTL0_ADDR);
  95        old_refr_count = (mctrl & MCTRL0_REFR_COUNT_MASK)
  96                >> MCTRL0_REFR_COUNT_SHIFT;
  97
  98        mctrl &= ~MCTRL0_REFR_COUNT_MASK;
  99        mctrl |= refr_count << MCTRL0_REFR_COUNT_SHIFT;
 100        write_hbreg(HBIRD_MEM_CNTL0_ADDR, mctrl);
 101        mctrl = read_hbreg(HBIRD_MEM_CNTL0_ADDR);
 102
 103        if (cpu_slowing_down && !(mctrl & MCTRL0_SREFRESH_ENAB)) {
 104                unsigned long usecs;
 105
 106                /* We have to wait for both refresh counts (old
 107                 * and new) to go to zero.
 108                 */
 109                usecs = (MCTRL0_REFR_CLKS_P_CNT *
 110                         (refr_count + old_refr_count) *
 111                         1000000UL *
 112                         old_divisor) / clock_tick;
 113                udelay(usecs + 1UL);
 114        }
 115}
 116
 117static void us2e_transition(unsigned long estar, unsigned long new_bits,
 118                            unsigned long clock_tick,
 119                            unsigned long old_divisor, unsigned long divisor)
 120{
 121        unsigned long flags;
 122
 123        local_irq_save(flags);
 124
 125        estar &= ~ESTAR_MODE_DIV_MASK;
 126
 127        /* This is based upon the state transition diagram in the IIe manual.  */
 128        if (old_divisor == 2 && divisor == 1) {
 129                self_refresh_ctl(0);
 130                write_hbreg(HBIRD_ESTAR_MODE_ADDR, estar | new_bits);
 131                frob_mem_refresh(0, clock_tick, old_divisor, divisor);
 132        } else if (old_divisor == 1 && divisor == 2) {
 133                frob_mem_refresh(1, clock_tick, old_divisor, divisor);
 134                write_hbreg(HBIRD_ESTAR_MODE_ADDR, estar | new_bits);
 135                self_refresh_ctl(1);
 136        } else if (old_divisor == 1 && divisor > 2) {
 137                us2e_transition(estar, ESTAR_MODE_DIV_2, clock_tick,
 138                                1, 2);
 139                us2e_transition(estar, new_bits, clock_tick,
 140                                2, divisor);
 141        } else if (old_divisor > 2 && divisor == 1) {
 142                us2e_transition(estar, ESTAR_MODE_DIV_2, clock_tick,
 143                                old_divisor, 2);
 144                us2e_transition(estar, new_bits, clock_tick,
 145                                2, divisor);
 146        } else if (old_divisor < divisor) {
 147                frob_mem_refresh(0, clock_tick, old_divisor, divisor);
 148                write_hbreg(HBIRD_ESTAR_MODE_ADDR, estar | new_bits);
 149        } else if (old_divisor > divisor) {
 150                write_hbreg(HBIRD_ESTAR_MODE_ADDR, estar | new_bits);
 151                frob_mem_refresh(1, clock_tick, old_divisor, divisor);
 152        } else {
 153                BUG();
 154        }
 155
 156        local_irq_restore(flags);
 157}
 158
 159static unsigned long index_to_estar_mode(unsigned int index)
 160{
 161        switch (index) {
 162        case 0:
 163                return ESTAR_MODE_DIV_1;
 164
 165        case 1:
 166                return ESTAR_MODE_DIV_2;
 167
 168        case 2:
 169                return ESTAR_MODE_DIV_4;
 170
 171        case 3:
 172                return ESTAR_MODE_DIV_6;
 173
 174        case 4:
 175                return ESTAR_MODE_DIV_8;
 176
 177        default:
 178                BUG();
 179        }
 180}
 181
 182static unsigned long index_to_divisor(unsigned int index)
 183{
 184        switch (index) {
 185        case 0:
 186                return 1;
 187
 188        case 1:
 189                return 2;
 190
 191        case 2:
 192                return 4;
 193
 194        case 3:
 195                return 6;
 196
 197        case 4:
 198                return 8;
 199
 200        default:
 201                BUG();
 202        }
 203}
 204
 205static unsigned long estar_to_divisor(unsigned long estar)
 206{
 207        unsigned long ret;
 208
 209        switch (estar & ESTAR_MODE_DIV_MASK) {
 210        case ESTAR_MODE_DIV_1:
 211                ret = 1;
 212                break;
 213        case ESTAR_MODE_DIV_2:
 214                ret = 2;
 215                break;
 216        case ESTAR_MODE_DIV_4:
 217                ret = 4;
 218                break;
 219        case ESTAR_MODE_DIV_6:
 220                ret = 6;
 221                break;
 222        case ESTAR_MODE_DIV_8:
 223                ret = 8;
 224                break;
 225        default:
 226                BUG();
 227        }
 228
 229        return ret;
 230}
 231
 232static unsigned int us2e_freq_get(unsigned int cpu)
 233{
 234        cpumask_t cpus_allowed;
 235        unsigned long clock_tick, estar;
 236
 237        cpumask_copy(&cpus_allowed, tsk_cpus_allowed(current));
 238        set_cpus_allowed_ptr(current, cpumask_of(cpu));
 239
 240        clock_tick = sparc64_get_clock_tick(cpu) / 1000;
 241        estar = read_hbreg(HBIRD_ESTAR_MODE_ADDR);
 242
 243        set_cpus_allowed_ptr(current, &cpus_allowed);
 244
 245        return clock_tick / estar_to_divisor(estar);
 246}
 247
 248static void us2e_set_cpu_divider_index(struct cpufreq_policy *policy,
 249                unsigned int index)
 250{
 251        unsigned int cpu = policy->cpu;
 252        unsigned long new_bits, new_freq;
 253        unsigned long clock_tick, divisor, old_divisor, estar;
 254        cpumask_t cpus_allowed;
 255        struct cpufreq_freqs freqs;
 256
 257        cpumask_copy(&cpus_allowed, tsk_cpus_allowed(current));
 258        set_cpus_allowed_ptr(current, cpumask_of(cpu));
 259
 260        new_freq = clock_tick = sparc64_get_clock_tick(cpu) / 1000;
 261        new_bits = index_to_estar_mode(index);
 262        divisor = index_to_divisor(index);
 263        new_freq /= divisor;
 264
 265        estar = read_hbreg(HBIRD_ESTAR_MODE_ADDR);
 266
 267        old_divisor = estar_to_divisor(estar);
 268
 269        freqs.old = clock_tick / old_divisor;
 270        freqs.new = new_freq;
 271        cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE);
 272
 273        if (old_divisor != divisor)
 274                us2e_transition(estar, new_bits, clock_tick * 1000,
 275                                old_divisor, divisor);
 276
 277        cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE);
 278
 279        set_cpus_allowed_ptr(current, &cpus_allowed);
 280}
 281
 282static int us2e_freq_target(struct cpufreq_policy *policy,
 283                          unsigned int target_freq,
 284                          unsigned int relation)
 285{
 286        unsigned int new_index = 0;
 287
 288        if (cpufreq_frequency_table_target(policy,
 289                                           &us2e_freq_table[policy->cpu].table[0],
 290                                           target_freq, relation, &new_index))
 291                return -EINVAL;
 292
 293        us2e_set_cpu_divider_index(policy, new_index);
 294
 295        return 0;
 296}
 297
 298static int us2e_freq_verify(struct cpufreq_policy *policy)
 299{
 300        return cpufreq_frequency_table_verify(policy,
 301                                              &us2e_freq_table[policy->cpu].table[0]);
 302}
 303
 304static int __init us2e_freq_cpu_init(struct cpufreq_policy *policy)
 305{
 306        unsigned int cpu = policy->cpu;
 307        unsigned long clock_tick = sparc64_get_clock_tick(cpu) / 1000;
 308        struct cpufreq_frequency_table *table =
 309                &us2e_freq_table[cpu].table[0];
 310
 311        table[0].index = 0;
 312        table[0].frequency = clock_tick / 1;
 313        table[1].index = 1;
 314        table[1].frequency = clock_tick / 2;
 315        table[2].index = 2;
 316        table[2].frequency = clock_tick / 4;
 317        table[2].index = 3;
 318        table[2].frequency = clock_tick / 6;
 319        table[2].index = 4;
 320        table[2].frequency = clock_tick / 8;
 321        table[2].index = 5;
 322        table[3].frequency = CPUFREQ_TABLE_END;
 323
 324        policy->cpuinfo.transition_latency = 0;
 325        policy->cur = clock_tick;
 326
 327        return cpufreq_frequency_table_cpuinfo(policy, table);
 328}
 329
 330static int us2e_freq_cpu_exit(struct cpufreq_policy *policy)
 331{
 332        if (cpufreq_us2e_driver)
 333                us2e_set_cpu_divider_index(policy, 0);
 334
 335        return 0;
 336}
 337
 338static int __init us2e_freq_init(void)
 339{
 340        unsigned long manuf, impl, ver;
 341        int ret;
 342
 343        if (tlb_type != spitfire)
 344                return -ENODEV;
 345
 346        __asm__("rdpr %%ver, %0" : "=r" (ver));
 347        manuf = ((ver >> 48) & 0xffff);
 348        impl  = ((ver >> 32) & 0xffff);
 349
 350        if (manuf == 0x17 && impl == 0x13) {
 351                struct cpufreq_driver *driver;
 352
 353                ret = -ENOMEM;
 354                driver = kzalloc(sizeof(struct cpufreq_driver), GFP_KERNEL);
 355                if (!driver)
 356                        goto err_out;
 357
 358                us2e_freq_table = kzalloc(
 359                        (NR_CPUS * sizeof(struct us2e_freq_percpu_info)),
 360                        GFP_KERNEL);
 361                if (!us2e_freq_table)
 362                        goto err_out;
 363
 364                driver->init = us2e_freq_cpu_init;
 365                driver->verify = us2e_freq_verify;
 366                driver->target = us2e_freq_target;
 367                driver->get = us2e_freq_get;
 368                driver->exit = us2e_freq_cpu_exit;
 369                driver->owner = THIS_MODULE,
 370                strcpy(driver->name, "UltraSPARC-IIe");
 371
 372                cpufreq_us2e_driver = driver;
 373                ret = cpufreq_register_driver(driver);
 374                if (ret)
 375                        goto err_out;
 376
 377                return 0;
 378
 379err_out:
 380                if (driver) {
 381                        kfree(driver);
 382                        cpufreq_us2e_driver = NULL;
 383                }
 384                kfree(us2e_freq_table);
 385                us2e_freq_table = NULL;
 386                return ret;
 387        }
 388
 389        return -ENODEV;
 390}
 391
 392static void __exit us2e_freq_exit(void)
 393{
 394        if (cpufreq_us2e_driver) {
 395                cpufreq_unregister_driver(cpufreq_us2e_driver);
 396                kfree(cpufreq_us2e_driver);
 397                cpufreq_us2e_driver = NULL;
 398                kfree(us2e_freq_table);
 399                us2e_freq_table = NULL;
 400        }
 401}
 402
 403MODULE_AUTHOR("David S. Miller <davem@redhat.com>");
 404MODULE_DESCRIPTION("cpufreq driver for UltraSPARC-IIe");
 405MODULE_LICENSE("GPL");
 406
 407module_init(us2e_freq_init);
 408module_exit(us2e_freq_exit);
 409
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.