linux/drivers/cpufreq/sc520_freq.c
<<
>>
Prefs
   1/*
   2 *      sc520_freq.c: cpufreq driver for the AMD Elan sc520
   3 *
   4 *      Copyright (C) 2005 Sean Young <sean@mess.org>
   5 *
   6 *      This program is free software; you can redistribute it and/or
   7 *      modify it under the terms of the GNU General Public License
   8 *      as published by the Free Software Foundation; either version
   9 *      2 of the License, or (at your option) any later version.
  10 *
  11 *      Based on elanfreq.c
  12 *
  13 *      2005-03-30: - initial revision
  14 */
  15
  16#include <linux/kernel.h>
  17#include <linux/module.h>
  18#include <linux/init.h>
  19
  20#include <linux/delay.h>
  21#include <linux/cpufreq.h>
  22#include <linux/timex.h>
  23#include <linux/io.h>
  24
  25#include <asm/cpu_device_id.h>
  26#include <asm/msr.h>
  27
  28#define MMCR_BASE       0xfffef000      /* The default base address */
  29#define OFFS_CPUCTL     0x2   /* CPU Control Register */
  30
  31static __u8 __iomem *cpuctl;
  32
  33#define PFX "sc520_freq: "
  34
  35static struct cpufreq_frequency_table sc520_freq_table[] = {
  36        {0x01,  100000},
  37        {0x02,  133000},
  38        {0,     CPUFREQ_TABLE_END},
  39};
  40
  41static unsigned int sc520_freq_get_cpu_frequency(unsigned int cpu)
  42{
  43        u8 clockspeed_reg = *cpuctl;
  44
  45        switch (clockspeed_reg & 0x03) {
  46        default:
  47                printk(KERN_ERR PFX "error: cpuctl register has unexpected "
  48                                "value %02x\n", clockspeed_reg);
  49        case 0x01:
  50                return 100000;
  51        case 0x02:
  52                return 133000;
  53        }
  54}
  55
  56static void sc520_freq_set_cpu_state(unsigned int state)
  57{
  58
  59        struct cpufreq_freqs    freqs;
  60        u8 clockspeed_reg;
  61
  62        freqs.old = sc520_freq_get_cpu_frequency(0);
  63        freqs.new = sc520_freq_table[state].frequency;
  64        freqs.cpu = 0; /* AMD Elan is UP */
  65
  66        cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);
  67
  68        pr_debug("attempting to set frequency to %i kHz\n",
  69                        sc520_freq_table[state].frequency);
  70
  71        local_irq_disable();
  72
  73        clockspeed_reg = *cpuctl & ~0x03;
  74        *cpuctl = clockspeed_reg | sc520_freq_table[state].index;
  75
  76        local_irq_enable();
  77
  78        cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
  79};
  80
  81static int sc520_freq_verify(struct cpufreq_policy *policy)
  82{
  83        return cpufreq_frequency_table_verify(policy, &sc520_freq_table[0]);
  84}
  85
  86static int sc520_freq_target(struct cpufreq_policy *policy,
  87                            unsigned int target_freq,
  88                            unsigned int relation)
  89{
  90        unsigned int newstate = 0;
  91
  92        if (cpufreq_frequency_table_target(policy, sc520_freq_table,
  93                                target_freq, relation, &newstate))
  94                return -EINVAL;
  95
  96        sc520_freq_set_cpu_state(newstate);
  97
  98        return 0;
  99}
 100
 101
 102/*
 103 *      Module init and exit code
 104 */
 105
 106static int sc520_freq_cpu_init(struct cpufreq_policy *policy)
 107{
 108        struct cpuinfo_x86 *c = &cpu_data(0);
 109        int result;
 110
 111        /* capability check */
 112        if (c->x86_vendor != X86_VENDOR_AMD ||
 113            c->x86 != 4 || c->x86_model != 9)
 114                return -ENODEV;
 115
 116        /* cpuinfo and default policy values */
 117        policy->cpuinfo.transition_latency = 1000000; /* 1ms */
 118        policy->cur = sc520_freq_get_cpu_frequency(0);
 119
 120        result = cpufreq_frequency_table_cpuinfo(policy, sc520_freq_table);
 121        if (result)
 122                return result;
 123
 124        cpufreq_frequency_table_get_attr(sc520_freq_table, policy->cpu);
 125
 126        return 0;
 127}
 128
 129
 130static int sc520_freq_cpu_exit(struct cpufreq_policy *policy)
 131{
 132        cpufreq_frequency_table_put_attr(policy->cpu);
 133        return 0;
 134}
 135
 136
 137static struct freq_attr *sc520_freq_attr[] = {
 138        &cpufreq_freq_attr_scaling_available_freqs,
 139        NULL,
 140};
 141
 142
 143static struct cpufreq_driver sc520_freq_driver = {
 144        .get    = sc520_freq_get_cpu_frequency,
 145        .verify = sc520_freq_verify,
 146        .target = sc520_freq_target,
 147        .init   = sc520_freq_cpu_init,
 148        .exit   = sc520_freq_cpu_exit,
 149        .name   = "sc520_freq",
 150        .owner  = THIS_MODULE,
 151        .attr   = sc520_freq_attr,
 152};
 153
 154static const struct x86_cpu_id sc520_ids[] = {
 155        { X86_VENDOR_AMD, 4, 9 },
 156        {}
 157};
 158MODULE_DEVICE_TABLE(x86cpu, sc520_ids);
 159
 160static int __init sc520_freq_init(void)
 161{
 162        int err;
 163
 164        if (!x86_match_cpu(sc520_ids))
 165                return -ENODEV;
 166
 167        cpuctl = ioremap((unsigned long)(MMCR_BASE + OFFS_CPUCTL), 1);
 168        if (!cpuctl) {
 169                printk(KERN_ERR "sc520_freq: error: failed to remap memory\n");
 170                return -ENOMEM;
 171        }
 172
 173        err = cpufreq_register_driver(&sc520_freq_driver);
 174        if (err)
 175                iounmap(cpuctl);
 176
 177        return err;
 178}
 179
 180
 181static void __exit sc520_freq_exit(void)
 182{
 183        cpufreq_unregister_driver(&sc520_freq_driver);
 184        iounmap(cpuctl);
 185}
 186
 187
 188MODULE_LICENSE("GPL");
 189MODULE_AUTHOR("Sean Young <sean@mess.org>");
 190MODULE_DESCRIPTION("cpufreq driver for AMD's Elan sc520 CPU");
 191
 192module_init(sc520_freq_init);
 193module_exit(sc520_freq_exit);
 194
 195