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