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        estar &= ~ESTAR_MODE_DIV_MASK;
 122
 123        /* This is based upon the state transition diagram in the IIe manual.  */
 124        if (old_divisor == 2 && divisor == 1) {
 125                self_refresh_ctl(0);
 126                write_hbreg(HBIRD_ESTAR_MODE_ADDR, estar | new_bits);
 127                frob_mem_refresh(0, clock_tick, old_divisor, divisor);
 128        } else if (old_divisor == 1 && divisor == 2) {
 129                frob_mem_refresh(1, clock_tick, old_divisor, divisor);
 130                write_hbreg(HBIRD_ESTAR_MODE_ADDR, estar | new_bits);
 131                self_refresh_ctl(1);
 132        } else if (old_divisor == 1 && divisor > 2) {
 133                us2e_transition(estar, ESTAR_MODE_DIV_2, clock_tick,
 134                                1, 2);
 135                us2e_transition(estar, new_bits, clock_tick,
 136                                2, divisor);
 137        } else if (old_divisor > 2 && divisor == 1) {
 138                us2e_transition(estar, ESTAR_MODE_DIV_2, clock_tick,
 139                                old_divisor, 2);
 140                us2e_transition(estar, new_bits, clock_tick,
 141                                2, divisor);
 142        } else if (old_divisor < divisor) {
 143                frob_mem_refresh(0, clock_tick, old_divisor, divisor);
 144                write_hbreg(HBIRD_ESTAR_MODE_ADDR, estar | new_bits);
 145        } else if (old_divisor > divisor) {
 146                write_hbreg(HBIRD_ESTAR_MODE_ADDR, estar | new_bits);
 147                frob_mem_refresh(1, clock_tick, old_divisor, divisor);
 148        } else {
 149                BUG();
 150        }
 151}
 152
 153static unsigned long index_to_estar_mode(unsigned int index)
 154{
 155        switch (index) {
 156        case 0:
 157                return ESTAR_MODE_DIV_1;
 158
 159        case 1:
 160                return ESTAR_MODE_DIV_2;
 161
 162        case 2:
 163                return ESTAR_MODE_DIV_4;
 164
 165        case 3:
 166                return ESTAR_MODE_DIV_6;
 167
 168        case 4:
 169                return ESTAR_MODE_DIV_8;
 170
 171        default:
 172                BUG();
 173        }
 174}
 175
 176static unsigned long index_to_divisor(unsigned int index)
 177{
 178        switch (index) {
 179        case 0:
 180                return 1;
 181
 182        case 1:
 183                return 2;
 184
 185        case 2:
 186                return 4;
 187
 188        case 3:
 189                return 6;
 190
 191        case 4:
 192                return 8;
 193
 194        default:
 195                BUG();
 196        }
 197}
 198
 199static unsigned long estar_to_divisor(unsigned long estar)
 200{
 201        unsigned long ret;
 202
 203        switch (estar & ESTAR_MODE_DIV_MASK) {
 204        case ESTAR_MODE_DIV_1:
 205                ret = 1;
 206                break;
 207        case ESTAR_MODE_DIV_2:
 208                ret = 2;
 209                break;
 210        case ESTAR_MODE_DIV_4:
 211                ret = 4;
 212                break;
 213        case ESTAR_MODE_DIV_6:
 214                ret = 6;
 215                break;
 216        case ESTAR_MODE_DIV_8:
 217                ret = 8;
 218                break;
 219        default:
 220                BUG();
 221        }
 222
 223        return ret;
 224}
 225
 226static void __us2e_freq_get(void *arg)
 227{
 228        unsigned long *estar = arg;
 229
 230        *estar = read_hbreg(HBIRD_ESTAR_MODE_ADDR);
 231}
 232
 233static unsigned int us2e_freq_get(unsigned int cpu)
 234{
 235        unsigned long clock_tick, estar;
 236
 237        clock_tick = sparc64_get_clock_tick(cpu) / 1000;
 238        if (smp_call_function_single(cpu, __us2e_freq_get, &estar, 1))
 239                return 0;
 240
 241        return clock_tick / estar_to_divisor(estar);
 242}
 243
 244static void __us2e_freq_target(void *arg)
 245{
 246        unsigned int cpu = smp_processor_id();
 247        unsigned int *index = arg;
 248        unsigned long new_bits, new_freq;
 249        unsigned long clock_tick, divisor, old_divisor, estar;
 250
 251        new_freq = clock_tick = sparc64_get_clock_tick(cpu) / 1000;
 252        new_bits = index_to_estar_mode(*index);
 253        divisor = index_to_divisor(*index);
 254        new_freq /= divisor;
 255
 256        estar = read_hbreg(HBIRD_ESTAR_MODE_ADDR);
 257
 258        old_divisor = estar_to_divisor(estar);
 259
 260        if (old_divisor != divisor) {
 261                us2e_transition(estar, new_bits, clock_tick * 1000,
 262                                old_divisor, divisor);
 263        }
 264}
 265
 266static int us2e_freq_target(struct cpufreq_policy *policy, unsigned int index)
 267{
 268        unsigned int cpu = policy->cpu;
 269
 270        return smp_call_function_single(cpu, __us2e_freq_target, &index, 1);
 271}
 272
 273static int __init us2e_freq_cpu_init(struct cpufreq_policy *policy)
 274{
 275        unsigned int cpu = policy->cpu;
 276        unsigned long clock_tick = sparc64_get_clock_tick(cpu) / 1000;
 277        struct cpufreq_frequency_table *table =
 278                &us2e_freq_table[cpu].table[0];
 279
 280        table[0].driver_data = 0;
 281        table[0].frequency = clock_tick / 1;
 282        table[1].driver_data = 1;
 283        table[1].frequency = clock_tick / 2;
 284        table[2].driver_data = 2;
 285        table[2].frequency = clock_tick / 4;
 286        table[2].driver_data = 3;
 287        table[2].frequency = clock_tick / 6;
 288        table[2].driver_data = 4;
 289        table[2].frequency = clock_tick / 8;
 290        table[2].driver_data = 5;
 291        table[3].frequency = CPUFREQ_TABLE_END;
 292
 293        policy->cpuinfo.transition_latency = 0;
 294        policy->cur = clock_tick;
 295        policy->freq_table = table;
 296
 297        return 0;
 298}
 299
 300static int us2e_freq_cpu_exit(struct cpufreq_policy *policy)
 301{
 302        if (cpufreq_us2e_driver)
 303                us2e_freq_target(policy, 0);
 304
 305        return 0;
 306}
 307
 308static int __init us2e_freq_init(void)
 309{
 310        unsigned long manuf, impl, ver;
 311        int ret;
 312
 313        if (tlb_type != spitfire)
 314                return -ENODEV;
 315
 316        __asm__("rdpr %%ver, %0" : "=r" (ver));
 317        manuf = ((ver >> 48) & 0xffff);
 318        impl  = ((ver >> 32) & 0xffff);
 319
 320        if (manuf == 0x17 && impl == 0x13) {
 321                struct cpufreq_driver *driver;
 322
 323                ret = -ENOMEM;
 324                driver = kzalloc(sizeof(*driver), GFP_KERNEL);
 325                if (!driver)
 326                        goto err_out;
 327
 328                us2e_freq_table = kzalloc((NR_CPUS * sizeof(*us2e_freq_table)),
 329                        GFP_KERNEL);
 330                if (!us2e_freq_table)
 331                        goto err_out;
 332
 333                driver->init = us2e_freq_cpu_init;
 334                driver->verify = cpufreq_generic_frequency_table_verify;
 335                driver->target_index = us2e_freq_target;
 336                driver->get = us2e_freq_get;
 337                driver->exit = us2e_freq_cpu_exit;
 338                strcpy(driver->name, "UltraSPARC-IIe");
 339
 340                cpufreq_us2e_driver = driver;
 341                ret = cpufreq_register_driver(driver);
 342                if (ret)
 343                        goto err_out;
 344
 345                return 0;
 346
 347err_out:
 348                if (driver) {
 349                        kfree(driver);
 350                        cpufreq_us2e_driver = NULL;
 351                }
 352                kfree(us2e_freq_table);
 353                us2e_freq_table = NULL;
 354                return ret;
 355        }
 356
 357        return -ENODEV;
 358}
 359
 360static void __exit us2e_freq_exit(void)
 361{
 362        if (cpufreq_us2e_driver) {
 363                cpufreq_unregister_driver(cpufreq_us2e_driver);
 364                kfree(cpufreq_us2e_driver);
 365                cpufreq_us2e_driver = NULL;
 366                kfree(us2e_freq_table);
 367                us2e_freq_table = NULL;
 368        }
 369}
 370
 371MODULE_AUTHOR("David S. Miller <davem@redhat.com>");
 372MODULE_DESCRIPTION("cpufreq driver for UltraSPARC-IIe");
 373MODULE_LICENSE("GPL");
 374
 375module_init(us2e_freq_init);
 376module_exit(us2e_freq_exit);
 377