root/drivers/cpufreq/longhaul.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 *  (C) 2001-2004  Dave Jones.
 *  (C) 2002  Padraig Brady. <padraig@antefacto.com>
 *
 *  Based upon datasheets & sample CPUs kindly provided by VIA.
 *
 *  VIA have currently 3 different versions of Longhaul.
 *  Version 1 (Longhaul) uses the BCR2 MSR at 0x1147.
 *   It is present only in Samuel 1 (C5A), Samuel 2 (C5B) stepping 0.
 *  Version 2 of longhaul is backward compatible with v1, but adds
 *   LONGHAUL MSR for purpose of both frequency and voltage scaling.
 *   Present in Samuel 2 (steppings 1-7 only) (C5B), and Ezra (C5C).
 *  Version 3 of longhaul got renamed to Powersaver and redesigned
 *   to use only the POWERSAVER MSR at 0x110a.
 *   It is present in Ezra-T (C5M), Nehemiah (C5X) and above.
 *   It's pretty much the same feature wise to longhaul v2, though
 *   there is provision for scaling FSB too, but this doesn't work
 *   too well in practice so we don't even try to use this.
 *
 *  BIG FAT DISCLAIMER: Work in progress code. Possibly *dangerous*
 */

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/cpufreq.h>
#include <linux/pci.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/delay.h>
#include <linux/timex.h>
#include <linux/io.h>
#include <linux/acpi.h>

#include <asm/msr.h>
#include <asm/cpu_device_id.h>
#include <acpi/processor.h>

#include "longhaul.h"

#define TYPE_LONGHAUL_V1        1
#define TYPE_LONGHAUL_V2        2
#define TYPE_POWERSAVER         3

#define CPU_SAMUEL      1
#define CPU_SAMUEL2     2
#define CPU_EZRA        3
#define CPU_EZRA_T      4
#define CPU_NEHEMIAH    5
#define CPU_NEHEMIAH_C  6

/* Flags */
#define USE_ACPI_C3             (1 << 1)
#define USE_NORTHBRIDGE         (1 << 2)

static int cpu_model;
static unsigned int numscales = 16;
static unsigned int fsb;

static const struct mV_pos *vrm_mV_table;
static const unsigned char *mV_vrm_table;

static unsigned int highest_speed, lowest_speed; /* kHz */
static unsigned int minmult, maxmult;
static int can_scale_voltage;
static struct acpi_processor *pr;
static struct acpi_processor_cx *cx;
static u32 acpi_regs_addr;
static u8 longhaul_flags;
static unsigned int longhaul_index;

/* Module parameters */
static int scale_voltage;
static int disable_acpi_c3;
static int revid_errata;
static int enable;

/* Clock ratios multiplied by 10 */
static int mults[32];
static int eblcr[32];
static int longhaul_version;
static struct cpufreq_frequency_table *longhaul_table;

static char speedbuffer[8];

static char *print_speed(int speed)
{
        if (speed < 1000) {
                snprintf(speedbuffer, sizeof(speedbuffer), "%dMHz", speed);
                return speedbuffer;
        }

        if (speed%1000 == 0)
                snprintf(speedbuffer, sizeof(speedbuffer),
                        "%dGHz", speed/1000);
        else
                snprintf(speedbuffer, sizeof(speedbuffer),
                        "%d.%dGHz", speed/1000, (speed%1000)/100);

        return speedbuffer;
}


static unsigned int calc_speed(int mult)
{
        int khz;
        khz = (mult/10)*fsb;
        if (mult%10)
                khz += fsb/2;
        khz *= 1000;
        return khz;
}


static int longhaul_get_cpu_mult(void)
{
        unsigned long invalue = 0, lo, hi;

        rdmsr(MSR_IA32_EBL_CR_POWERON, lo, hi);
        invalue = (lo & (1<<22|1<<23|1<<24|1<<25))>>22;
        if (longhaul_version == TYPE_LONGHAUL_V2 ||
            longhaul_version == TYPE_POWERSAVER) {
                if (lo & (1<<27))
                        invalue += 16;
        }
        return eblcr[invalue];
}

/* For processor with BCR2 MSR */

static void do_longhaul1(unsigned int mults_index)
{
        union msr_bcr2 bcr2;

        rdmsrq(MSR_VIA_BCR2, bcr2.val);
        /* Enable software clock multiplier */
        bcr2.bits.ESOFTBF = 1;
        bcr2.bits.CLOCKMUL = mults_index & 0xff;

        /* Sync to timer tick */
        safe_halt();
        /* Change frequency on next halt or sleep */
        wrmsrq(MSR_VIA_BCR2, bcr2.val);
        /* Invoke transition */
        ACPI_FLUSH_CPU_CACHE();
        halt();

        /* Disable software clock multiplier */
        local_irq_disable();
        rdmsrq(MSR_VIA_BCR2, bcr2.val);
        bcr2.bits.ESOFTBF = 0;
        wrmsrq(MSR_VIA_BCR2, bcr2.val);
}

/* For processor with Longhaul MSR */

static void do_powersaver(int cx_address, unsigned int mults_index,
                          unsigned int dir)
{
        union msr_longhaul longhaul;
        u32 t;

        rdmsrq(MSR_VIA_LONGHAUL, longhaul.val);
        /* Setup new frequency */
        if (!revid_errata)
                longhaul.bits.RevisionKey = longhaul.bits.RevisionID;
        else
                longhaul.bits.RevisionKey = 0;
        longhaul.bits.SoftBusRatio = mults_index & 0xf;
        longhaul.bits.SoftBusRatio4 = (mults_index & 0x10) >> 4;
        /* Setup new voltage */
        if (can_scale_voltage)
                longhaul.bits.SoftVID = (mults_index >> 8) & 0x1f;
        /* Sync to timer tick */
        safe_halt();
        /* Raise voltage if necessary */
        if (can_scale_voltage && dir) {
                longhaul.bits.EnableSoftVID = 1;
                wrmsrq(MSR_VIA_LONGHAUL, longhaul.val);
                /* Change voltage */
                if (!cx_address) {
                        ACPI_FLUSH_CPU_CACHE();
                        halt();
                } else {
                        ACPI_FLUSH_CPU_CACHE();
                        /* Invoke C3 */
                        inb(cx_address);
                        /* Dummy op - must do something useless after P_LVL3
                         * read */
                        t = inl(acpi_gbl_FADT.xpm_timer_block.address);
                }
                longhaul.bits.EnableSoftVID = 0;
                wrmsrq(MSR_VIA_LONGHAUL, longhaul.val);
        }

        /* Change frequency on next halt or sleep */
        longhaul.bits.EnableSoftBusRatio = 1;
        wrmsrq(MSR_VIA_LONGHAUL, longhaul.val);
        if (!cx_address) {
                ACPI_FLUSH_CPU_CACHE();
                halt();
        } else {
                ACPI_FLUSH_CPU_CACHE();
                /* Invoke C3 */
                inb(cx_address);
                /* Dummy op - must do something useless after P_LVL3 read */
                t = inl(acpi_gbl_FADT.xpm_timer_block.address);
        }
        /* Disable bus ratio bit */
        longhaul.bits.EnableSoftBusRatio = 0;
        wrmsrq(MSR_VIA_LONGHAUL, longhaul.val);

        /* Reduce voltage if necessary */
        if (can_scale_voltage && !dir) {
                longhaul.bits.EnableSoftVID = 1;
                wrmsrq(MSR_VIA_LONGHAUL, longhaul.val);
                /* Change voltage */
                if (!cx_address) {
                        ACPI_FLUSH_CPU_CACHE();
                        halt();
                } else {
                        ACPI_FLUSH_CPU_CACHE();
                        /* Invoke C3 */
                        inb(cx_address);
                        /* Dummy op - must do something useless after P_LVL3
                         * read */
                        t = inl(acpi_gbl_FADT.xpm_timer_block.address);
                }
                longhaul.bits.EnableSoftVID = 0;
                wrmsrq(MSR_VIA_LONGHAUL, longhaul.val);
        }
}

/**
 * longhaul_setstate()
 * @policy: cpufreq_policy structure containing the current policy.
 * @table_index: index of the frequency within the cpufreq_frequency_table.
 *
 * Sets a new clock ratio.
 */

static int longhaul_setstate(struct cpufreq_policy *policy,
                unsigned int table_index)
{
        unsigned int mults_index;
        int speed, mult;
        struct cpufreq_freqs freqs;
        unsigned long flags;
        unsigned int pic1_mask, pic2_mask;
        u16 bm_status = 0;
        u32 bm_timeout = 1000;
        unsigned int dir = 0;

        mults_index = longhaul_table[table_index].driver_data;
        /* Safety precautions */
        mult = mults[mults_index & 0x1f];
        if (mult == -1)
                return -EINVAL;

        speed = calc_speed(mult);
        if ((speed > highest_speed) || (speed < lowest_speed))
                return -EINVAL;

        /* Voltage transition before frequency transition? */
        if (can_scale_voltage && longhaul_index < table_index)
                dir = 1;

        freqs.old = calc_speed(longhaul_get_cpu_mult());
        freqs.new = speed;

        pr_debug("Setting to FSB:%dMHz Mult:%d.%dx (%s)\n",
                        fsb, mult/10, mult%10, print_speed(speed/1000));
retry_loop:
        preempt_disable();
        local_irq_save(flags);

        pic2_mask = inb(0xA1);
        pic1_mask = inb(0x21);  /* works on C3. save mask. */
        outb(0xFF, 0xA1);       /* Overkill */
        outb(0xFE, 0x21);       /* TMR0 only */

        /* Wait while PCI bus is busy. */
        if (acpi_regs_addr && (longhaul_flags & USE_NORTHBRIDGE
            || ((pr != NULL) && pr->flags.bm_control))) {
                bm_status = inw(acpi_regs_addr);
                bm_status &= 1 << 4;
                while (bm_status && bm_timeout) {
                        outw(1 << 4, acpi_regs_addr);
                        bm_timeout--;
                        bm_status = inw(acpi_regs_addr);
                        bm_status &= 1 << 4;
                }
        }

        if (longhaul_flags & USE_NORTHBRIDGE) {
                /* Disable AGP and PCI arbiters */
                outb(3, 0x22);
        } else if ((pr != NULL) && pr->flags.bm_control) {
                /* Disable bus master arbitration */
                acpi_write_bit_register(ACPI_BITREG_ARB_DISABLE, 1);
        }
        switch (longhaul_version) {

        /*
         * Longhaul v1. (Samuel[C5A] and Samuel2 stepping 0[C5B])
         * Software controlled multipliers only.
         */
        case TYPE_LONGHAUL_V1:
                do_longhaul1(mults_index);
                break;

        /*
         * Longhaul v2 appears in Samuel2 Steppings 1->7 [C5B] and Ezra [C5C]
         *
         * Longhaul v3 (aka Powersaver). (Ezra-T [C5M] & Nehemiah [C5N])
         * Nehemiah can do FSB scaling too, but this has never been proven
         * to work in practice.
         */
        case TYPE_LONGHAUL_V2:
        case TYPE_POWERSAVER:
                if (longhaul_flags & USE_ACPI_C3) {
                        /* Don't allow wakeup */
                        acpi_write_bit_register(ACPI_BITREG_BUS_MASTER_RLD, 0);
                        do_powersaver(cx->address, mults_index, dir);
                } else {
                        do_powersaver(0, mults_index, dir);
                }
                break;
        }

        if (longhaul_flags & USE_NORTHBRIDGE) {
                /* Enable arbiters */
                outb(0, 0x22);
        } else if ((pr != NULL) && pr->flags.bm_control) {
                /* Enable bus master arbitration */
                acpi_write_bit_register(ACPI_BITREG_ARB_DISABLE, 0);
        }
        outb(pic2_mask, 0xA1);  /* restore mask */
        outb(pic1_mask, 0x21);

        local_irq_restore(flags);
        preempt_enable();

        freqs.new = calc_speed(longhaul_get_cpu_mult());
        /* Check if requested frequency is set. */
        if (unlikely(freqs.new != speed)) {
                pr_info("Failed to set requested frequency!\n");
                /* Revision ID = 1 but processor is expecting revision key
                 * equal to 0. Jumpers at the bottom of processor will change
                 * multiplier and FSB, but will not change bits in Longhaul
                 * MSR nor enable voltage scaling. */
                if (!revid_errata) {
                        pr_info("Enabling \"Ignore Revision ID\" option\n");
                        revid_errata = 1;
                        msleep(200);
                        goto retry_loop;
                }
                /* Why ACPI C3 sometimes doesn't work is a mystery for me.
                 * But it does happen. Processor is entering ACPI C3 state,
                 * but it doesn't change frequency. I tried poking various
                 * bits in northbridge registers, but without success. */
                if (longhaul_flags & USE_ACPI_C3) {
                        pr_info("Disabling ACPI C3 support\n");
                        longhaul_flags &= ~USE_ACPI_C3;
                        if (revid_errata) {
                                pr_info("Disabling \"Ignore Revision ID\" option\n");
                                revid_errata = 0;
                        }
                        msleep(200);
                        goto retry_loop;
                }
                /* This shouldn't happen. Longhaul ver. 2 was reported not
                 * working on processors without voltage scaling, but with
                 * RevID = 1. RevID errata will make things right. Just
                 * to be 100% sure. */
                if (longhaul_version == TYPE_LONGHAUL_V2) {
                        pr_info("Switching to Longhaul ver. 1\n");
                        longhaul_version = TYPE_LONGHAUL_V1;
                        msleep(200);
                        goto retry_loop;
                }
        }

        if (!bm_timeout) {
                pr_info("Warning: Timeout while waiting for idle PCI bus\n");
                return -EBUSY;
        }

        return 0;
}

/*
 * Centaur decided to make life a little more tricky.
 * Only longhaul v1 is allowed to read EBLCR BSEL[0:1].
 * Samuel2 and above have to try and guess what the FSB is.
 * We do this by assuming we booted at maximum multiplier, and interpolate
 * between that value multiplied by possible FSBs and cpu_mhz which
 * was calculated at boot time. Really ugly, but no other way to do this.
 */

#define ROUNDING        0xf

static int guess_fsb(int mult)
{
        int speed = cpu_khz / 1000;
        int i;
        static const int speeds[] = { 666, 1000, 1333, 2000 };
        int f_max, f_min;

        for (i = 0; i < ARRAY_SIZE(speeds); i++) {
                f_max = ((speeds[i] * mult) + 50) / 100;
                f_max += (ROUNDING / 2);
                f_min = f_max - ROUNDING;
                if ((speed <= f_max) && (speed >= f_min))
                        return speeds[i] / 10;
        }
        return 0;
}


static int longhaul_get_ranges(void)
{
        unsigned int i, j, k = 0;
        unsigned int ratio;
        int mult;

        /* Get current frequency */
        mult = longhaul_get_cpu_mult();
        if (mult == -1) {
                pr_info("Invalid (reserved) multiplier!\n");
                return -EINVAL;
        }
        fsb = guess_fsb(mult);
        if (fsb == 0) {
                pr_info("Invalid (reserved) FSB!\n");
                return -EINVAL;
        }
        /* Get max multiplier - as we always did.
         * Longhaul MSR is useful only when voltage scaling is enabled.
         * C3 is booting at max anyway. */
        maxmult = mult;
        /* Get min multiplier */
        switch (cpu_model) {
        case CPU_NEHEMIAH:
                minmult = 50;
                break;
        case CPU_NEHEMIAH_C:
                minmult = 40;
                break;
        default:
                minmult = 30;
                break;
        }

        pr_debug("MinMult:%d.%dx MaxMult:%d.%dx\n",
                 minmult/10, minmult%10, maxmult/10, maxmult%10);

        highest_speed = calc_speed(maxmult);
        lowest_speed = calc_speed(minmult);
        pr_debug("FSB:%dMHz  Lowest speed: %s   Highest speed:%s\n", fsb,
                 print_speed(lowest_speed/1000),
                 print_speed(highest_speed/1000));

        if (lowest_speed == highest_speed) {
                pr_info("highestspeed == lowest, aborting\n");
                return -EINVAL;
        }
        if (lowest_speed > highest_speed) {
                pr_info("nonsense! lowest (%d > %d) !\n",
                        lowest_speed, highest_speed);
                return -EINVAL;
        }

        longhaul_table = kzalloc_objs(*longhaul_table, numscales + 1);
        if (!longhaul_table)
                return -ENOMEM;

        for (j = 0; j < numscales; j++) {
                ratio = mults[j];
                if (ratio == -1)
                        continue;
                if (ratio > maxmult || ratio < minmult)
                        continue;
                longhaul_table[k].frequency = calc_speed(ratio);
                longhaul_table[k].driver_data   = j;
                k++;
        }
        if (k <= 1) {
                kfree(longhaul_table);
                return -ENODEV;
        }
        /* Sort */
        for (j = 0; j < k - 1; j++) {
                unsigned int min_f, min_i;
                min_f = longhaul_table[j].frequency;
                min_i = j;
                for (i = j + 1; i < k; i++) {
                        if (longhaul_table[i].frequency < min_f) {
                                min_f = longhaul_table[i].frequency;
                                min_i = i;
                        }
                }
                if (min_i != j) {
                        swap(longhaul_table[j].frequency,
                             longhaul_table[min_i].frequency);
                        swap(longhaul_table[j].driver_data,
                             longhaul_table[min_i].driver_data);
                }
        }

        longhaul_table[k].frequency = CPUFREQ_TABLE_END;

        /* Find index we are running on */
        for (j = 0; j < k; j++) {
                if (mults[longhaul_table[j].driver_data & 0x1f] == mult) {
                        longhaul_index = j;
                        break;
                }
        }
        return 0;
}


static void longhaul_setup_voltagescaling(void)
{
        struct cpufreq_frequency_table *freq_pos;
        union msr_longhaul longhaul;
        struct mV_pos minvid, maxvid, vid;
        unsigned int j, speed, pos, kHz_step, numvscales;
        int min_vid_speed;

        rdmsrq(MSR_VIA_LONGHAUL, longhaul.val);
        if (!(longhaul.bits.RevisionID & 1)) {
                pr_info("Voltage scaling not supported by CPU\n");
                return;
        }

        if (!longhaul.bits.VRMRev) {
                pr_info("VRM 8.5\n");
                vrm_mV_table = &vrm85_mV[0];
                mV_vrm_table = &mV_vrm85[0];
        } else {
                pr_info("Mobile VRM\n");
                if (cpu_model < CPU_NEHEMIAH)
                        return;
                vrm_mV_table = &mobilevrm_mV[0];
                mV_vrm_table = &mV_mobilevrm[0];
        }

        minvid = vrm_mV_table[longhaul.bits.MinimumVID];
        maxvid = vrm_mV_table[longhaul.bits.MaximumVID];

        if (minvid.mV == 0 || maxvid.mV == 0 || minvid.mV > maxvid.mV) {
                pr_info("Bogus values Min:%d.%03d Max:%d.%03d - Voltage scaling disabled\n",
                        minvid.mV/1000, minvid.mV%1000,
                        maxvid.mV/1000, maxvid.mV%1000);
                return;
        }

        if (minvid.mV == maxvid.mV) {
                pr_info("Claims to support voltage scaling but min & max are both %d.%03d - Voltage scaling disabled\n",
                        maxvid.mV/1000, maxvid.mV%1000);
                return;
        }

        /* How many voltage steps*/
        numvscales = maxvid.pos - minvid.pos + 1;
        pr_info("Max VID=%d.%03d  Min VID=%d.%03d, %d possible voltage scales\n",
                maxvid.mV/1000, maxvid.mV%1000,
                minvid.mV/1000, minvid.mV%1000,
                numvscales);

        /* Calculate max frequency at min voltage */
        j = longhaul.bits.MinMHzBR;
        if (longhaul.bits.MinMHzBR4)
                j += 16;
        min_vid_speed = eblcr[j];
        if (min_vid_speed == -1)
                return;
        switch (longhaul.bits.MinMHzFSB) {
        case 0:
                min_vid_speed *= 13333;
                break;
        case 1:
                min_vid_speed *= 10000;
                break;
        case 3:
                min_vid_speed *= 6666;
                break;
        default:
                return;
        }
        if (min_vid_speed >= highest_speed)
                return;
        /* Calculate kHz for one voltage step */
        kHz_step = (highest_speed - min_vid_speed) / numvscales;

        cpufreq_for_each_entry_idx(freq_pos, longhaul_table, j) {
                speed = freq_pos->frequency;
                if (speed > min_vid_speed)
                        pos = (speed - min_vid_speed) / kHz_step + minvid.pos;
                else
                        pos = minvid.pos;
                freq_pos->driver_data |= mV_vrm_table[pos] << 8;
                vid = vrm_mV_table[mV_vrm_table[pos]];
                pr_info("f: %d kHz, index: %d, vid: %d mV\n",
                        speed, j, vid.mV);
        }

        can_scale_voltage = 1;
        pr_info("Voltage scaling enabled\n");
}


static int longhaul_target(struct cpufreq_policy *policy,
                            unsigned int table_index)
{
        unsigned int i;
        unsigned int dir = 0;
        u8 vid, current_vid;
        int retval = 0;

        if (!can_scale_voltage)
                retval = longhaul_setstate(policy, table_index);
        else {
                /* On test system voltage transitions exceeding single
                 * step up or down were turning motherboard off. Both
                 * "ondemand" and "userspace" are unsafe. C7 is doing
                 * this in hardware, C3 is old and we need to do this
                 * in software. */
                i = longhaul_index;
                current_vid = (longhaul_table[longhaul_index].driver_data >> 8);
                current_vid &= 0x1f;
                if (table_index > longhaul_index)
                        dir = 1;
                while (i != table_index) {
                        vid = (longhaul_table[i].driver_data >> 8) & 0x1f;
                        if (vid != current_vid) {
                                retval = longhaul_setstate(policy, i);
                                current_vid = vid;
                                msleep(200);
                        }
                        if (dir)
                                i++;
                        else
                                i--;
                }
                retval = longhaul_setstate(policy, table_index);
        }

        longhaul_index = table_index;
        return retval;
}


static unsigned int longhaul_get(unsigned int cpu)
{
        if (cpu)
                return 0;
        return calc_speed(longhaul_get_cpu_mult());
}

static acpi_status longhaul_walk_callback(acpi_handle obj_handle,
                                          u32 nesting_level,
                                          void *context, void **return_value)
{
        struct acpi_device *d = acpi_fetch_acpi_dev(obj_handle);

        if (!d)
                return 0;

        *return_value = acpi_driver_data(d);
        return 1;
}

/* VIA don't support PM2 reg, but have something similar */
static int enable_arbiter_disable(void)
{
        struct pci_dev *dev;
        int status = 1;
        int reg;
        u8 pci_cmd;

        /* Find PLE133 host bridge */
        reg = 0x78;
        dev = pci_get_device(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_8601_0,
                             NULL);
        /* Find PM133/VT8605 host bridge */
        if (dev == NULL)
                dev = pci_get_device(PCI_VENDOR_ID_VIA,
                                     PCI_DEVICE_ID_VIA_8605_0, NULL);
        /* Find CLE266 host bridge */
        if (dev == NULL) {
                reg = 0x76;
                dev = pci_get_device(PCI_VENDOR_ID_VIA,
                                     PCI_DEVICE_ID_VIA_862X_0, NULL);
                /* Find CN400 V-Link host bridge */
                if (dev == NULL)
                        dev = pci_get_device(PCI_VENDOR_ID_VIA, 0x7259, NULL);
        }
        if (dev != NULL) {
                /* Enable access to port 0x22 */
                pci_read_config_byte(dev, reg, &pci_cmd);
                if (!(pci_cmd & 1<<7)) {
                        pci_cmd |= 1<<7;
                        pci_write_config_byte(dev, reg, pci_cmd);
                        pci_read_config_byte(dev, reg, &pci_cmd);
                        if (!(pci_cmd & 1<<7)) {
                                pr_err("Can't enable access to port 0x22\n");
                                status = 0;
                        }
                }
                pci_dev_put(dev);
                return status;
        }
        return 0;
}

static int longhaul_setup_southbridge(void)
{
        struct pci_dev *dev;
        u8 pci_cmd;

        /* Find VT8235 southbridge */
        dev = pci_get_device(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_8235, NULL);
        if (dev == NULL)
                /* Find VT8237 southbridge */
                dev = pci_get_device(PCI_VENDOR_ID_VIA,
                                     PCI_DEVICE_ID_VIA_8237, NULL);
        if (dev != NULL) {
                /* Set transition time to max */
                pci_read_config_byte(dev, 0xec, &pci_cmd);
                pci_cmd &= ~(1 << 2);
                pci_write_config_byte(dev, 0xec, pci_cmd);
                pci_read_config_byte(dev, 0xe4, &pci_cmd);
                pci_cmd &= ~(1 << 7);
                pci_write_config_byte(dev, 0xe4, pci_cmd);
                pci_read_config_byte(dev, 0xe5, &pci_cmd);
                pci_cmd |= 1 << 7;
                pci_write_config_byte(dev, 0xe5, pci_cmd);
                /* Get address of ACPI registers block*/
                pci_read_config_byte(dev, 0x81, &pci_cmd);
                if (pci_cmd & 1 << 7) {
                        pci_read_config_dword(dev, 0x88, &acpi_regs_addr);
                        acpi_regs_addr &= 0xff00;
                        pr_info("ACPI I/O at 0x%x\n", acpi_regs_addr);
                }

                pci_dev_put(dev);
                return 1;
        }
        return 0;
}

static int longhaul_cpu_init(struct cpufreq_policy *policy)
{
        struct cpuinfo_x86 *c = &cpu_data(0);
        char *cpuname = NULL;
        int ret;
        u32 lo, hi;

        /* Check what we have on this motherboard */
        switch (c->x86_model) {
        case 6:
                cpu_model = CPU_SAMUEL;
                cpuname = "C3 'Samuel' [C5A]";
                longhaul_version = TYPE_LONGHAUL_V1;
                memcpy(mults, samuel1_mults, sizeof(samuel1_mults));
                memcpy(eblcr, samuel1_eblcr, sizeof(samuel1_eblcr));
                break;

        case 7:
                switch (c->x86_stepping) {
                case 0:
                        longhaul_version = TYPE_LONGHAUL_V1;
                        cpu_model = CPU_SAMUEL2;
                        cpuname = "C3 'Samuel 2' [C5B]";
                        /* Note, this is not a typo, early Samuel2's had
                         * Samuel1 ratios. */
                        memcpy(mults, samuel1_mults, sizeof(samuel1_mults));
                        memcpy(eblcr, samuel2_eblcr, sizeof(samuel2_eblcr));
                        break;
                case 1 ... 15:
                        longhaul_version = TYPE_LONGHAUL_V2;
                        if (c->x86_stepping < 8) {
                                cpu_model = CPU_SAMUEL2;
                                cpuname = "C3 'Samuel 2' [C5B]";
                        } else {
                                cpu_model = CPU_EZRA;
                                cpuname = "C3 'Ezra' [C5C]";
                        }
                        memcpy(mults, ezra_mults, sizeof(ezra_mults));
                        memcpy(eblcr, ezra_eblcr, sizeof(ezra_eblcr));
                        break;
                }
                break;

        case 8:
                cpu_model = CPU_EZRA_T;
                cpuname = "C3 'Ezra-T' [C5M]";
                longhaul_version = TYPE_POWERSAVER;
                numscales = 32;
                memcpy(mults, ezrat_mults, sizeof(ezrat_mults));
                memcpy(eblcr, ezrat_eblcr, sizeof(ezrat_eblcr));
                break;

        case 9:
                longhaul_version = TYPE_POWERSAVER;
                numscales = 32;
                memcpy(mults, nehemiah_mults, sizeof(nehemiah_mults));
                memcpy(eblcr, nehemiah_eblcr, sizeof(nehemiah_eblcr));
                switch (c->x86_stepping) {
                case 0 ... 1:
                        cpu_model = CPU_NEHEMIAH;
                        cpuname = "C3 'Nehemiah A' [C5XLOE]";
                        break;
                case 2 ... 4:
                        cpu_model = CPU_NEHEMIAH;
                        cpuname = "C3 'Nehemiah B' [C5XLOH]";
                        break;
                case 5 ... 15:
                        cpu_model = CPU_NEHEMIAH_C;
                        cpuname = "C3 'Nehemiah C' [C5P]";
                        break;
                }
                break;

        default:
                cpuname = "Unknown";
                break;
        }
        /* Check Longhaul ver. 2 */
        if (longhaul_version == TYPE_LONGHAUL_V2) {
                rdmsr(MSR_VIA_LONGHAUL, lo, hi);
                if (lo == 0 && hi == 0)
                        /* Looks like MSR isn't present */
                        longhaul_version = TYPE_LONGHAUL_V1;
        }

        pr_info("VIA %s CPU detected.  ", cpuname);
        switch (longhaul_version) {
        case TYPE_LONGHAUL_V1:
        case TYPE_LONGHAUL_V2:
                pr_cont("Longhaul v%d supported\n", longhaul_version);
                break;
        case TYPE_POWERSAVER:
                pr_cont("Powersaver supported\n");
                break;
        }

        /* Doesn't hurt */
        longhaul_setup_southbridge();

        /* Find ACPI data for processor */
        acpi_walk_namespace(ACPI_TYPE_PROCESSOR, ACPI_ROOT_OBJECT,
                                ACPI_UINT32_MAX, &longhaul_walk_callback, NULL,
                                NULL, (void *)&pr);

        /* Check ACPI support for C3 state */
        if (pr != NULL && longhaul_version == TYPE_POWERSAVER) {
                cx = &pr->power.states[ACPI_STATE_C3];
                if (cx->address > 0 && cx->latency <= 1000)
                        longhaul_flags |= USE_ACPI_C3;
        }
        /* Disable if it isn't working */
        if (disable_acpi_c3)
                longhaul_flags &= ~USE_ACPI_C3;
        /* Check if northbridge is friendly */
        if (enable_arbiter_disable())
                longhaul_flags |= USE_NORTHBRIDGE;

        /* Check ACPI support for bus master arbiter disable */
        if (!(longhaul_flags & USE_ACPI_C3
             || longhaul_flags & USE_NORTHBRIDGE)
            && ((pr == NULL) || !(pr->flags.bm_control))) {
                pr_err("No ACPI support: Unsupported northbridge\n");
                return -ENODEV;
        }

        if (longhaul_flags & USE_NORTHBRIDGE)
                pr_info("Using northbridge support\n");
        if (longhaul_flags & USE_ACPI_C3)
                pr_info("Using ACPI support\n");

        ret = longhaul_get_ranges();
        if (ret != 0)
                return ret;

        if ((longhaul_version != TYPE_LONGHAUL_V1) && (scale_voltage != 0))
                longhaul_setup_voltagescaling();

        policy->transition_delay_us = 200000;   /* usec */
        policy->freq_table = longhaul_table;

        return 0;
}

static struct cpufreq_driver longhaul_driver = {
        .verify = cpufreq_generic_frequency_table_verify,
        .target_index = longhaul_target,
        .get    = longhaul_get,
        .init   = longhaul_cpu_init,
        .name   = "longhaul",
};

static const struct x86_cpu_id longhaul_id[] = {
        X86_MATCH_VENDOR_FAM(CENTAUR, 6, NULL),
        {}
};
MODULE_DEVICE_TABLE(x86cpu, longhaul_id);

static int __init longhaul_init(void)
{
        struct cpuinfo_x86 *c = &cpu_data(0);

        if (!x86_match_cpu(longhaul_id))
                return -ENODEV;

        if (!enable) {
                pr_err("Option \"enable\" not set - Aborting\n");
                return -ENODEV;
        }
#ifdef CONFIG_SMP
        if (num_online_cpus() > 1) {
                pr_err("More than 1 CPU detected, longhaul disabled\n");
                return -ENODEV;
        }
#endif
#ifdef CONFIG_X86_IO_APIC
        if (boot_cpu_has(X86_FEATURE_APIC)) {
                pr_err("APIC detected. Longhaul is currently broken in this configuration.\n");
                return -ENODEV;
        }
#endif
        switch (c->x86_model) {
        case 6 ... 9:
                return cpufreq_register_driver(&longhaul_driver);
        case 10:
                pr_err("Use acpi-cpufreq driver for VIA C7\n");
        }

        return -ENODEV;
}


static void __exit longhaul_exit(void)
{
        struct cpufreq_policy *policy = cpufreq_cpu_get(0);
        int i;

        if (unlikely(!policy))
                return;

        for (i = 0; i < numscales; i++) {
                if (mults[i] == maxmult) {
                        struct cpufreq_freqs freqs;

                        freqs.old = policy->cur;
                        freqs.new = longhaul_table[i].frequency;
                        freqs.flags = 0;

                        cpufreq_freq_transition_begin(policy, &freqs);
                        longhaul_setstate(policy, i);
                        cpufreq_freq_transition_end(policy, &freqs, 0);
                        break;
                }
        }

        cpufreq_cpu_put(policy);
        cpufreq_unregister_driver(&longhaul_driver);
        kfree(longhaul_table);
}

/* Even if BIOS is exporting ACPI C3 state, and it is used
 * with success when CPU is idle, this state doesn't
 * trigger frequency transition in some cases. */
module_param(disable_acpi_c3, int, 0644);
MODULE_PARM_DESC(disable_acpi_c3, "Don't use ACPI C3 support");
/* Change CPU voltage with frequency. Very useful to save
 * power, but most VIA C3 processors aren't supporting it. */
module_param(scale_voltage, int, 0644);
MODULE_PARM_DESC(scale_voltage, "Scale voltage of processor");
/* Force revision key to 0 for processors which doesn't
 * support voltage scaling, but are introducing itself as
 * such. */
module_param(revid_errata, int, 0644);
MODULE_PARM_DESC(revid_errata, "Ignore CPU Revision ID");
/* By default driver is disabled to prevent incompatible
 * system freeze. */
module_param(enable, int, 0644);
MODULE_PARM_DESC(enable, "Enable driver");

MODULE_AUTHOR("Dave Jones");
MODULE_DESCRIPTION("Longhaul driver for VIA Cyrix processors.");
MODULE_LICENSE("GPL");

late_initcall(longhaul_init);
module_exit(longhaul_exit);