162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * (C) 2001-2004 Dave Jones. 462306a36Sopenharmony_ci * (C) 2002 Padraig Brady. <padraig@antefacto.com> 562306a36Sopenharmony_ci * 662306a36Sopenharmony_ci * Based upon datasheets & sample CPUs kindly provided by VIA. 762306a36Sopenharmony_ci * 862306a36Sopenharmony_ci * VIA have currently 3 different versions of Longhaul. 962306a36Sopenharmony_ci * Version 1 (Longhaul) uses the BCR2 MSR at 0x1147. 1062306a36Sopenharmony_ci * It is present only in Samuel 1 (C5A), Samuel 2 (C5B) stepping 0. 1162306a36Sopenharmony_ci * Version 2 of longhaul is backward compatible with v1, but adds 1262306a36Sopenharmony_ci * LONGHAUL MSR for purpose of both frequency and voltage scaling. 1362306a36Sopenharmony_ci * Present in Samuel 2 (steppings 1-7 only) (C5B), and Ezra (C5C). 1462306a36Sopenharmony_ci * Version 3 of longhaul got renamed to Powersaver and redesigned 1562306a36Sopenharmony_ci * to use only the POWERSAVER MSR at 0x110a. 1662306a36Sopenharmony_ci * It is present in Ezra-T (C5M), Nehemiah (C5X) and above. 1762306a36Sopenharmony_ci * It's pretty much the same feature wise to longhaul v2, though 1862306a36Sopenharmony_ci * there is provision for scaling FSB too, but this doesn't work 1962306a36Sopenharmony_ci * too well in practice so we don't even try to use this. 2062306a36Sopenharmony_ci * 2162306a36Sopenharmony_ci * BIG FAT DISCLAIMER: Work in progress code. Possibly *dangerous* 2262306a36Sopenharmony_ci */ 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci#include <linux/kernel.h> 2762306a36Sopenharmony_ci#include <linux/module.h> 2862306a36Sopenharmony_ci#include <linux/moduleparam.h> 2962306a36Sopenharmony_ci#include <linux/init.h> 3062306a36Sopenharmony_ci#include <linux/cpufreq.h> 3162306a36Sopenharmony_ci#include <linux/pci.h> 3262306a36Sopenharmony_ci#include <linux/slab.h> 3362306a36Sopenharmony_ci#include <linux/string.h> 3462306a36Sopenharmony_ci#include <linux/delay.h> 3562306a36Sopenharmony_ci#include <linux/timex.h> 3662306a36Sopenharmony_ci#include <linux/io.h> 3762306a36Sopenharmony_ci#include <linux/acpi.h> 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci#include <asm/msr.h> 4062306a36Sopenharmony_ci#include <asm/cpu_device_id.h> 4162306a36Sopenharmony_ci#include <acpi/processor.h> 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_ci#include "longhaul.h" 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci#define TYPE_LONGHAUL_V1 1 4662306a36Sopenharmony_ci#define TYPE_LONGHAUL_V2 2 4762306a36Sopenharmony_ci#define TYPE_POWERSAVER 3 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci#define CPU_SAMUEL 1 5062306a36Sopenharmony_ci#define CPU_SAMUEL2 2 5162306a36Sopenharmony_ci#define CPU_EZRA 3 5262306a36Sopenharmony_ci#define CPU_EZRA_T 4 5362306a36Sopenharmony_ci#define CPU_NEHEMIAH 5 5462306a36Sopenharmony_ci#define CPU_NEHEMIAH_C 6 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_ci/* Flags */ 5762306a36Sopenharmony_ci#define USE_ACPI_C3 (1 << 1) 5862306a36Sopenharmony_ci#define USE_NORTHBRIDGE (1 << 2) 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_cistatic int cpu_model; 6162306a36Sopenharmony_cistatic unsigned int numscales = 16; 6262306a36Sopenharmony_cistatic unsigned int fsb; 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_cistatic const struct mV_pos *vrm_mV_table; 6562306a36Sopenharmony_cistatic const unsigned char *mV_vrm_table; 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_cistatic unsigned int highest_speed, lowest_speed; /* kHz */ 6862306a36Sopenharmony_cistatic unsigned int minmult, maxmult; 6962306a36Sopenharmony_cistatic int can_scale_voltage; 7062306a36Sopenharmony_cistatic struct acpi_processor *pr; 7162306a36Sopenharmony_cistatic struct acpi_processor_cx *cx; 7262306a36Sopenharmony_cistatic u32 acpi_regs_addr; 7362306a36Sopenharmony_cistatic u8 longhaul_flags; 7462306a36Sopenharmony_cistatic unsigned int longhaul_index; 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci/* Module parameters */ 7762306a36Sopenharmony_cistatic int scale_voltage; 7862306a36Sopenharmony_cistatic int disable_acpi_c3; 7962306a36Sopenharmony_cistatic int revid_errata; 8062306a36Sopenharmony_cistatic int enable; 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci/* Clock ratios multiplied by 10 */ 8362306a36Sopenharmony_cistatic int mults[32]; 8462306a36Sopenharmony_cistatic int eblcr[32]; 8562306a36Sopenharmony_cistatic int longhaul_version; 8662306a36Sopenharmony_cistatic struct cpufreq_frequency_table *longhaul_table; 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_cistatic char speedbuffer[8]; 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_cistatic char *print_speed(int speed) 9162306a36Sopenharmony_ci{ 9262306a36Sopenharmony_ci if (speed < 1000) { 9362306a36Sopenharmony_ci snprintf(speedbuffer, sizeof(speedbuffer), "%dMHz", speed); 9462306a36Sopenharmony_ci return speedbuffer; 9562306a36Sopenharmony_ci } 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci if (speed%1000 == 0) 9862306a36Sopenharmony_ci snprintf(speedbuffer, sizeof(speedbuffer), 9962306a36Sopenharmony_ci "%dGHz", speed/1000); 10062306a36Sopenharmony_ci else 10162306a36Sopenharmony_ci snprintf(speedbuffer, sizeof(speedbuffer), 10262306a36Sopenharmony_ci "%d.%dGHz", speed/1000, (speed%1000)/100); 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci return speedbuffer; 10562306a36Sopenharmony_ci} 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_cistatic unsigned int calc_speed(int mult) 10962306a36Sopenharmony_ci{ 11062306a36Sopenharmony_ci int khz; 11162306a36Sopenharmony_ci khz = (mult/10)*fsb; 11262306a36Sopenharmony_ci if (mult%10) 11362306a36Sopenharmony_ci khz += fsb/2; 11462306a36Sopenharmony_ci khz *= 1000; 11562306a36Sopenharmony_ci return khz; 11662306a36Sopenharmony_ci} 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_cistatic int longhaul_get_cpu_mult(void) 12062306a36Sopenharmony_ci{ 12162306a36Sopenharmony_ci unsigned long invalue = 0, lo, hi; 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci rdmsr(MSR_IA32_EBL_CR_POWERON, lo, hi); 12462306a36Sopenharmony_ci invalue = (lo & (1<<22|1<<23|1<<24|1<<25))>>22; 12562306a36Sopenharmony_ci if (longhaul_version == TYPE_LONGHAUL_V2 || 12662306a36Sopenharmony_ci longhaul_version == TYPE_POWERSAVER) { 12762306a36Sopenharmony_ci if (lo & (1<<27)) 12862306a36Sopenharmony_ci invalue += 16; 12962306a36Sopenharmony_ci } 13062306a36Sopenharmony_ci return eblcr[invalue]; 13162306a36Sopenharmony_ci} 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci/* For processor with BCR2 MSR */ 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_cistatic void do_longhaul1(unsigned int mults_index) 13662306a36Sopenharmony_ci{ 13762306a36Sopenharmony_ci union msr_bcr2 bcr2; 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci rdmsrl(MSR_VIA_BCR2, bcr2.val); 14062306a36Sopenharmony_ci /* Enable software clock multiplier */ 14162306a36Sopenharmony_ci bcr2.bits.ESOFTBF = 1; 14262306a36Sopenharmony_ci bcr2.bits.CLOCKMUL = mults_index & 0xff; 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci /* Sync to timer tick */ 14562306a36Sopenharmony_ci safe_halt(); 14662306a36Sopenharmony_ci /* Change frequency on next halt or sleep */ 14762306a36Sopenharmony_ci wrmsrl(MSR_VIA_BCR2, bcr2.val); 14862306a36Sopenharmony_ci /* Invoke transition */ 14962306a36Sopenharmony_ci ACPI_FLUSH_CPU_CACHE(); 15062306a36Sopenharmony_ci halt(); 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_ci /* Disable software clock multiplier */ 15362306a36Sopenharmony_ci local_irq_disable(); 15462306a36Sopenharmony_ci rdmsrl(MSR_VIA_BCR2, bcr2.val); 15562306a36Sopenharmony_ci bcr2.bits.ESOFTBF = 0; 15662306a36Sopenharmony_ci wrmsrl(MSR_VIA_BCR2, bcr2.val); 15762306a36Sopenharmony_ci} 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci/* For processor with Longhaul MSR */ 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_cistatic void do_powersaver(int cx_address, unsigned int mults_index, 16262306a36Sopenharmony_ci unsigned int dir) 16362306a36Sopenharmony_ci{ 16462306a36Sopenharmony_ci union msr_longhaul longhaul; 16562306a36Sopenharmony_ci u32 t; 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci rdmsrl(MSR_VIA_LONGHAUL, longhaul.val); 16862306a36Sopenharmony_ci /* Setup new frequency */ 16962306a36Sopenharmony_ci if (!revid_errata) 17062306a36Sopenharmony_ci longhaul.bits.RevisionKey = longhaul.bits.RevisionID; 17162306a36Sopenharmony_ci else 17262306a36Sopenharmony_ci longhaul.bits.RevisionKey = 0; 17362306a36Sopenharmony_ci longhaul.bits.SoftBusRatio = mults_index & 0xf; 17462306a36Sopenharmony_ci longhaul.bits.SoftBusRatio4 = (mults_index & 0x10) >> 4; 17562306a36Sopenharmony_ci /* Setup new voltage */ 17662306a36Sopenharmony_ci if (can_scale_voltage) 17762306a36Sopenharmony_ci longhaul.bits.SoftVID = (mults_index >> 8) & 0x1f; 17862306a36Sopenharmony_ci /* Sync to timer tick */ 17962306a36Sopenharmony_ci safe_halt(); 18062306a36Sopenharmony_ci /* Raise voltage if necessary */ 18162306a36Sopenharmony_ci if (can_scale_voltage && dir) { 18262306a36Sopenharmony_ci longhaul.bits.EnableSoftVID = 1; 18362306a36Sopenharmony_ci wrmsrl(MSR_VIA_LONGHAUL, longhaul.val); 18462306a36Sopenharmony_ci /* Change voltage */ 18562306a36Sopenharmony_ci if (!cx_address) { 18662306a36Sopenharmony_ci ACPI_FLUSH_CPU_CACHE(); 18762306a36Sopenharmony_ci halt(); 18862306a36Sopenharmony_ci } else { 18962306a36Sopenharmony_ci ACPI_FLUSH_CPU_CACHE(); 19062306a36Sopenharmony_ci /* Invoke C3 */ 19162306a36Sopenharmony_ci inb(cx_address); 19262306a36Sopenharmony_ci /* Dummy op - must do something useless after P_LVL3 19362306a36Sopenharmony_ci * read */ 19462306a36Sopenharmony_ci t = inl(acpi_gbl_FADT.xpm_timer_block.address); 19562306a36Sopenharmony_ci } 19662306a36Sopenharmony_ci longhaul.bits.EnableSoftVID = 0; 19762306a36Sopenharmony_ci wrmsrl(MSR_VIA_LONGHAUL, longhaul.val); 19862306a36Sopenharmony_ci } 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_ci /* Change frequency on next halt or sleep */ 20162306a36Sopenharmony_ci longhaul.bits.EnableSoftBusRatio = 1; 20262306a36Sopenharmony_ci wrmsrl(MSR_VIA_LONGHAUL, longhaul.val); 20362306a36Sopenharmony_ci if (!cx_address) { 20462306a36Sopenharmony_ci ACPI_FLUSH_CPU_CACHE(); 20562306a36Sopenharmony_ci halt(); 20662306a36Sopenharmony_ci } else { 20762306a36Sopenharmony_ci ACPI_FLUSH_CPU_CACHE(); 20862306a36Sopenharmony_ci /* Invoke C3 */ 20962306a36Sopenharmony_ci inb(cx_address); 21062306a36Sopenharmony_ci /* Dummy op - must do something useless after P_LVL3 read */ 21162306a36Sopenharmony_ci t = inl(acpi_gbl_FADT.xpm_timer_block.address); 21262306a36Sopenharmony_ci } 21362306a36Sopenharmony_ci /* Disable bus ratio bit */ 21462306a36Sopenharmony_ci longhaul.bits.EnableSoftBusRatio = 0; 21562306a36Sopenharmony_ci wrmsrl(MSR_VIA_LONGHAUL, longhaul.val); 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci /* Reduce voltage if necessary */ 21862306a36Sopenharmony_ci if (can_scale_voltage && !dir) { 21962306a36Sopenharmony_ci longhaul.bits.EnableSoftVID = 1; 22062306a36Sopenharmony_ci wrmsrl(MSR_VIA_LONGHAUL, longhaul.val); 22162306a36Sopenharmony_ci /* Change voltage */ 22262306a36Sopenharmony_ci if (!cx_address) { 22362306a36Sopenharmony_ci ACPI_FLUSH_CPU_CACHE(); 22462306a36Sopenharmony_ci halt(); 22562306a36Sopenharmony_ci } else { 22662306a36Sopenharmony_ci ACPI_FLUSH_CPU_CACHE(); 22762306a36Sopenharmony_ci /* Invoke C3 */ 22862306a36Sopenharmony_ci inb(cx_address); 22962306a36Sopenharmony_ci /* Dummy op - must do something useless after P_LVL3 23062306a36Sopenharmony_ci * read */ 23162306a36Sopenharmony_ci t = inl(acpi_gbl_FADT.xpm_timer_block.address); 23262306a36Sopenharmony_ci } 23362306a36Sopenharmony_ci longhaul.bits.EnableSoftVID = 0; 23462306a36Sopenharmony_ci wrmsrl(MSR_VIA_LONGHAUL, longhaul.val); 23562306a36Sopenharmony_ci } 23662306a36Sopenharmony_ci} 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_ci/** 23962306a36Sopenharmony_ci * longhaul_set_cpu_frequency() 24062306a36Sopenharmony_ci * @mults_index : bitpattern of the new multiplier. 24162306a36Sopenharmony_ci * 24262306a36Sopenharmony_ci * Sets a new clock ratio. 24362306a36Sopenharmony_ci */ 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_cistatic int longhaul_setstate(struct cpufreq_policy *policy, 24662306a36Sopenharmony_ci unsigned int table_index) 24762306a36Sopenharmony_ci{ 24862306a36Sopenharmony_ci unsigned int mults_index; 24962306a36Sopenharmony_ci int speed, mult; 25062306a36Sopenharmony_ci struct cpufreq_freqs freqs; 25162306a36Sopenharmony_ci unsigned long flags; 25262306a36Sopenharmony_ci unsigned int pic1_mask, pic2_mask; 25362306a36Sopenharmony_ci u16 bm_status = 0; 25462306a36Sopenharmony_ci u32 bm_timeout = 1000; 25562306a36Sopenharmony_ci unsigned int dir = 0; 25662306a36Sopenharmony_ci 25762306a36Sopenharmony_ci mults_index = longhaul_table[table_index].driver_data; 25862306a36Sopenharmony_ci /* Safety precautions */ 25962306a36Sopenharmony_ci mult = mults[mults_index & 0x1f]; 26062306a36Sopenharmony_ci if (mult == -1) 26162306a36Sopenharmony_ci return -EINVAL; 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_ci speed = calc_speed(mult); 26462306a36Sopenharmony_ci if ((speed > highest_speed) || (speed < lowest_speed)) 26562306a36Sopenharmony_ci return -EINVAL; 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_ci /* Voltage transition before frequency transition? */ 26862306a36Sopenharmony_ci if (can_scale_voltage && longhaul_index < table_index) 26962306a36Sopenharmony_ci dir = 1; 27062306a36Sopenharmony_ci 27162306a36Sopenharmony_ci freqs.old = calc_speed(longhaul_get_cpu_mult()); 27262306a36Sopenharmony_ci freqs.new = speed; 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_ci pr_debug("Setting to FSB:%dMHz Mult:%d.%dx (%s)\n", 27562306a36Sopenharmony_ci fsb, mult/10, mult%10, print_speed(speed/1000)); 27662306a36Sopenharmony_ciretry_loop: 27762306a36Sopenharmony_ci preempt_disable(); 27862306a36Sopenharmony_ci local_irq_save(flags); 27962306a36Sopenharmony_ci 28062306a36Sopenharmony_ci pic2_mask = inb(0xA1); 28162306a36Sopenharmony_ci pic1_mask = inb(0x21); /* works on C3. save mask. */ 28262306a36Sopenharmony_ci outb(0xFF, 0xA1); /* Overkill */ 28362306a36Sopenharmony_ci outb(0xFE, 0x21); /* TMR0 only */ 28462306a36Sopenharmony_ci 28562306a36Sopenharmony_ci /* Wait while PCI bus is busy. */ 28662306a36Sopenharmony_ci if (acpi_regs_addr && (longhaul_flags & USE_NORTHBRIDGE 28762306a36Sopenharmony_ci || ((pr != NULL) && pr->flags.bm_control))) { 28862306a36Sopenharmony_ci bm_status = inw(acpi_regs_addr); 28962306a36Sopenharmony_ci bm_status &= 1 << 4; 29062306a36Sopenharmony_ci while (bm_status && bm_timeout) { 29162306a36Sopenharmony_ci outw(1 << 4, acpi_regs_addr); 29262306a36Sopenharmony_ci bm_timeout--; 29362306a36Sopenharmony_ci bm_status = inw(acpi_regs_addr); 29462306a36Sopenharmony_ci bm_status &= 1 << 4; 29562306a36Sopenharmony_ci } 29662306a36Sopenharmony_ci } 29762306a36Sopenharmony_ci 29862306a36Sopenharmony_ci if (longhaul_flags & USE_NORTHBRIDGE) { 29962306a36Sopenharmony_ci /* Disable AGP and PCI arbiters */ 30062306a36Sopenharmony_ci outb(3, 0x22); 30162306a36Sopenharmony_ci } else if ((pr != NULL) && pr->flags.bm_control) { 30262306a36Sopenharmony_ci /* Disable bus master arbitration */ 30362306a36Sopenharmony_ci acpi_write_bit_register(ACPI_BITREG_ARB_DISABLE, 1); 30462306a36Sopenharmony_ci } 30562306a36Sopenharmony_ci switch (longhaul_version) { 30662306a36Sopenharmony_ci 30762306a36Sopenharmony_ci /* 30862306a36Sopenharmony_ci * Longhaul v1. (Samuel[C5A] and Samuel2 stepping 0[C5B]) 30962306a36Sopenharmony_ci * Software controlled multipliers only. 31062306a36Sopenharmony_ci */ 31162306a36Sopenharmony_ci case TYPE_LONGHAUL_V1: 31262306a36Sopenharmony_ci do_longhaul1(mults_index); 31362306a36Sopenharmony_ci break; 31462306a36Sopenharmony_ci 31562306a36Sopenharmony_ci /* 31662306a36Sopenharmony_ci * Longhaul v2 appears in Samuel2 Steppings 1->7 [C5B] and Ezra [C5C] 31762306a36Sopenharmony_ci * 31862306a36Sopenharmony_ci * Longhaul v3 (aka Powersaver). (Ezra-T [C5M] & Nehemiah [C5N]) 31962306a36Sopenharmony_ci * Nehemiah can do FSB scaling too, but this has never been proven 32062306a36Sopenharmony_ci * to work in practice. 32162306a36Sopenharmony_ci */ 32262306a36Sopenharmony_ci case TYPE_LONGHAUL_V2: 32362306a36Sopenharmony_ci case TYPE_POWERSAVER: 32462306a36Sopenharmony_ci if (longhaul_flags & USE_ACPI_C3) { 32562306a36Sopenharmony_ci /* Don't allow wakeup */ 32662306a36Sopenharmony_ci acpi_write_bit_register(ACPI_BITREG_BUS_MASTER_RLD, 0); 32762306a36Sopenharmony_ci do_powersaver(cx->address, mults_index, dir); 32862306a36Sopenharmony_ci } else { 32962306a36Sopenharmony_ci do_powersaver(0, mults_index, dir); 33062306a36Sopenharmony_ci } 33162306a36Sopenharmony_ci break; 33262306a36Sopenharmony_ci } 33362306a36Sopenharmony_ci 33462306a36Sopenharmony_ci if (longhaul_flags & USE_NORTHBRIDGE) { 33562306a36Sopenharmony_ci /* Enable arbiters */ 33662306a36Sopenharmony_ci outb(0, 0x22); 33762306a36Sopenharmony_ci } else if ((pr != NULL) && pr->flags.bm_control) { 33862306a36Sopenharmony_ci /* Enable bus master arbitration */ 33962306a36Sopenharmony_ci acpi_write_bit_register(ACPI_BITREG_ARB_DISABLE, 0); 34062306a36Sopenharmony_ci } 34162306a36Sopenharmony_ci outb(pic2_mask, 0xA1); /* restore mask */ 34262306a36Sopenharmony_ci outb(pic1_mask, 0x21); 34362306a36Sopenharmony_ci 34462306a36Sopenharmony_ci local_irq_restore(flags); 34562306a36Sopenharmony_ci preempt_enable(); 34662306a36Sopenharmony_ci 34762306a36Sopenharmony_ci freqs.new = calc_speed(longhaul_get_cpu_mult()); 34862306a36Sopenharmony_ci /* Check if requested frequency is set. */ 34962306a36Sopenharmony_ci if (unlikely(freqs.new != speed)) { 35062306a36Sopenharmony_ci pr_info("Failed to set requested frequency!\n"); 35162306a36Sopenharmony_ci /* Revision ID = 1 but processor is expecting revision key 35262306a36Sopenharmony_ci * equal to 0. Jumpers at the bottom of processor will change 35362306a36Sopenharmony_ci * multiplier and FSB, but will not change bits in Longhaul 35462306a36Sopenharmony_ci * MSR nor enable voltage scaling. */ 35562306a36Sopenharmony_ci if (!revid_errata) { 35662306a36Sopenharmony_ci pr_info("Enabling \"Ignore Revision ID\" option\n"); 35762306a36Sopenharmony_ci revid_errata = 1; 35862306a36Sopenharmony_ci msleep(200); 35962306a36Sopenharmony_ci goto retry_loop; 36062306a36Sopenharmony_ci } 36162306a36Sopenharmony_ci /* Why ACPI C3 sometimes doesn't work is a mystery for me. 36262306a36Sopenharmony_ci * But it does happen. Processor is entering ACPI C3 state, 36362306a36Sopenharmony_ci * but it doesn't change frequency. I tried poking various 36462306a36Sopenharmony_ci * bits in northbridge registers, but without success. */ 36562306a36Sopenharmony_ci if (longhaul_flags & USE_ACPI_C3) { 36662306a36Sopenharmony_ci pr_info("Disabling ACPI C3 support\n"); 36762306a36Sopenharmony_ci longhaul_flags &= ~USE_ACPI_C3; 36862306a36Sopenharmony_ci if (revid_errata) { 36962306a36Sopenharmony_ci pr_info("Disabling \"Ignore Revision ID\" option\n"); 37062306a36Sopenharmony_ci revid_errata = 0; 37162306a36Sopenharmony_ci } 37262306a36Sopenharmony_ci msleep(200); 37362306a36Sopenharmony_ci goto retry_loop; 37462306a36Sopenharmony_ci } 37562306a36Sopenharmony_ci /* This shouldn't happen. Longhaul ver. 2 was reported not 37662306a36Sopenharmony_ci * working on processors without voltage scaling, but with 37762306a36Sopenharmony_ci * RevID = 1. RevID errata will make things right. Just 37862306a36Sopenharmony_ci * to be 100% sure. */ 37962306a36Sopenharmony_ci if (longhaul_version == TYPE_LONGHAUL_V2) { 38062306a36Sopenharmony_ci pr_info("Switching to Longhaul ver. 1\n"); 38162306a36Sopenharmony_ci longhaul_version = TYPE_LONGHAUL_V1; 38262306a36Sopenharmony_ci msleep(200); 38362306a36Sopenharmony_ci goto retry_loop; 38462306a36Sopenharmony_ci } 38562306a36Sopenharmony_ci } 38662306a36Sopenharmony_ci 38762306a36Sopenharmony_ci if (!bm_timeout) { 38862306a36Sopenharmony_ci pr_info("Warning: Timeout while waiting for idle PCI bus\n"); 38962306a36Sopenharmony_ci return -EBUSY; 39062306a36Sopenharmony_ci } 39162306a36Sopenharmony_ci 39262306a36Sopenharmony_ci return 0; 39362306a36Sopenharmony_ci} 39462306a36Sopenharmony_ci 39562306a36Sopenharmony_ci/* 39662306a36Sopenharmony_ci * Centaur decided to make life a little more tricky. 39762306a36Sopenharmony_ci * Only longhaul v1 is allowed to read EBLCR BSEL[0:1]. 39862306a36Sopenharmony_ci * Samuel2 and above have to try and guess what the FSB is. 39962306a36Sopenharmony_ci * We do this by assuming we booted at maximum multiplier, and interpolate 40062306a36Sopenharmony_ci * between that value multiplied by possible FSBs and cpu_mhz which 40162306a36Sopenharmony_ci * was calculated at boot time. Really ugly, but no other way to do this. 40262306a36Sopenharmony_ci */ 40362306a36Sopenharmony_ci 40462306a36Sopenharmony_ci#define ROUNDING 0xf 40562306a36Sopenharmony_ci 40662306a36Sopenharmony_cistatic int guess_fsb(int mult) 40762306a36Sopenharmony_ci{ 40862306a36Sopenharmony_ci int speed = cpu_khz / 1000; 40962306a36Sopenharmony_ci int i; 41062306a36Sopenharmony_ci static const int speeds[] = { 666, 1000, 1333, 2000 }; 41162306a36Sopenharmony_ci int f_max, f_min; 41262306a36Sopenharmony_ci 41362306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(speeds); i++) { 41462306a36Sopenharmony_ci f_max = ((speeds[i] * mult) + 50) / 100; 41562306a36Sopenharmony_ci f_max += (ROUNDING / 2); 41662306a36Sopenharmony_ci f_min = f_max - ROUNDING; 41762306a36Sopenharmony_ci if ((speed <= f_max) && (speed >= f_min)) 41862306a36Sopenharmony_ci return speeds[i] / 10; 41962306a36Sopenharmony_ci } 42062306a36Sopenharmony_ci return 0; 42162306a36Sopenharmony_ci} 42262306a36Sopenharmony_ci 42362306a36Sopenharmony_ci 42462306a36Sopenharmony_cistatic int longhaul_get_ranges(void) 42562306a36Sopenharmony_ci{ 42662306a36Sopenharmony_ci unsigned int i, j, k = 0; 42762306a36Sopenharmony_ci unsigned int ratio; 42862306a36Sopenharmony_ci int mult; 42962306a36Sopenharmony_ci 43062306a36Sopenharmony_ci /* Get current frequency */ 43162306a36Sopenharmony_ci mult = longhaul_get_cpu_mult(); 43262306a36Sopenharmony_ci if (mult == -1) { 43362306a36Sopenharmony_ci pr_info("Invalid (reserved) multiplier!\n"); 43462306a36Sopenharmony_ci return -EINVAL; 43562306a36Sopenharmony_ci } 43662306a36Sopenharmony_ci fsb = guess_fsb(mult); 43762306a36Sopenharmony_ci if (fsb == 0) { 43862306a36Sopenharmony_ci pr_info("Invalid (reserved) FSB!\n"); 43962306a36Sopenharmony_ci return -EINVAL; 44062306a36Sopenharmony_ci } 44162306a36Sopenharmony_ci /* Get max multiplier - as we always did. 44262306a36Sopenharmony_ci * Longhaul MSR is useful only when voltage scaling is enabled. 44362306a36Sopenharmony_ci * C3 is booting at max anyway. */ 44462306a36Sopenharmony_ci maxmult = mult; 44562306a36Sopenharmony_ci /* Get min multiplier */ 44662306a36Sopenharmony_ci switch (cpu_model) { 44762306a36Sopenharmony_ci case CPU_NEHEMIAH: 44862306a36Sopenharmony_ci minmult = 50; 44962306a36Sopenharmony_ci break; 45062306a36Sopenharmony_ci case CPU_NEHEMIAH_C: 45162306a36Sopenharmony_ci minmult = 40; 45262306a36Sopenharmony_ci break; 45362306a36Sopenharmony_ci default: 45462306a36Sopenharmony_ci minmult = 30; 45562306a36Sopenharmony_ci break; 45662306a36Sopenharmony_ci } 45762306a36Sopenharmony_ci 45862306a36Sopenharmony_ci pr_debug("MinMult:%d.%dx MaxMult:%d.%dx\n", 45962306a36Sopenharmony_ci minmult/10, minmult%10, maxmult/10, maxmult%10); 46062306a36Sopenharmony_ci 46162306a36Sopenharmony_ci highest_speed = calc_speed(maxmult); 46262306a36Sopenharmony_ci lowest_speed = calc_speed(minmult); 46362306a36Sopenharmony_ci pr_debug("FSB:%dMHz Lowest speed: %s Highest speed:%s\n", fsb, 46462306a36Sopenharmony_ci print_speed(lowest_speed/1000), 46562306a36Sopenharmony_ci print_speed(highest_speed/1000)); 46662306a36Sopenharmony_ci 46762306a36Sopenharmony_ci if (lowest_speed == highest_speed) { 46862306a36Sopenharmony_ci pr_info("highestspeed == lowest, aborting\n"); 46962306a36Sopenharmony_ci return -EINVAL; 47062306a36Sopenharmony_ci } 47162306a36Sopenharmony_ci if (lowest_speed > highest_speed) { 47262306a36Sopenharmony_ci pr_info("nonsense! lowest (%d > %d) !\n", 47362306a36Sopenharmony_ci lowest_speed, highest_speed); 47462306a36Sopenharmony_ci return -EINVAL; 47562306a36Sopenharmony_ci } 47662306a36Sopenharmony_ci 47762306a36Sopenharmony_ci longhaul_table = kcalloc(numscales + 1, sizeof(*longhaul_table), 47862306a36Sopenharmony_ci GFP_KERNEL); 47962306a36Sopenharmony_ci if (!longhaul_table) 48062306a36Sopenharmony_ci return -ENOMEM; 48162306a36Sopenharmony_ci 48262306a36Sopenharmony_ci for (j = 0; j < numscales; j++) { 48362306a36Sopenharmony_ci ratio = mults[j]; 48462306a36Sopenharmony_ci if (ratio == -1) 48562306a36Sopenharmony_ci continue; 48662306a36Sopenharmony_ci if (ratio > maxmult || ratio < minmult) 48762306a36Sopenharmony_ci continue; 48862306a36Sopenharmony_ci longhaul_table[k].frequency = calc_speed(ratio); 48962306a36Sopenharmony_ci longhaul_table[k].driver_data = j; 49062306a36Sopenharmony_ci k++; 49162306a36Sopenharmony_ci } 49262306a36Sopenharmony_ci if (k <= 1) { 49362306a36Sopenharmony_ci kfree(longhaul_table); 49462306a36Sopenharmony_ci return -ENODEV; 49562306a36Sopenharmony_ci } 49662306a36Sopenharmony_ci /* Sort */ 49762306a36Sopenharmony_ci for (j = 0; j < k - 1; j++) { 49862306a36Sopenharmony_ci unsigned int min_f, min_i; 49962306a36Sopenharmony_ci min_f = longhaul_table[j].frequency; 50062306a36Sopenharmony_ci min_i = j; 50162306a36Sopenharmony_ci for (i = j + 1; i < k; i++) { 50262306a36Sopenharmony_ci if (longhaul_table[i].frequency < min_f) { 50362306a36Sopenharmony_ci min_f = longhaul_table[i].frequency; 50462306a36Sopenharmony_ci min_i = i; 50562306a36Sopenharmony_ci } 50662306a36Sopenharmony_ci } 50762306a36Sopenharmony_ci if (min_i != j) { 50862306a36Sopenharmony_ci swap(longhaul_table[j].frequency, 50962306a36Sopenharmony_ci longhaul_table[min_i].frequency); 51062306a36Sopenharmony_ci swap(longhaul_table[j].driver_data, 51162306a36Sopenharmony_ci longhaul_table[min_i].driver_data); 51262306a36Sopenharmony_ci } 51362306a36Sopenharmony_ci } 51462306a36Sopenharmony_ci 51562306a36Sopenharmony_ci longhaul_table[k].frequency = CPUFREQ_TABLE_END; 51662306a36Sopenharmony_ci 51762306a36Sopenharmony_ci /* Find index we are running on */ 51862306a36Sopenharmony_ci for (j = 0; j < k; j++) { 51962306a36Sopenharmony_ci if (mults[longhaul_table[j].driver_data & 0x1f] == mult) { 52062306a36Sopenharmony_ci longhaul_index = j; 52162306a36Sopenharmony_ci break; 52262306a36Sopenharmony_ci } 52362306a36Sopenharmony_ci } 52462306a36Sopenharmony_ci return 0; 52562306a36Sopenharmony_ci} 52662306a36Sopenharmony_ci 52762306a36Sopenharmony_ci 52862306a36Sopenharmony_cistatic void longhaul_setup_voltagescaling(void) 52962306a36Sopenharmony_ci{ 53062306a36Sopenharmony_ci struct cpufreq_frequency_table *freq_pos; 53162306a36Sopenharmony_ci union msr_longhaul longhaul; 53262306a36Sopenharmony_ci struct mV_pos minvid, maxvid, vid; 53362306a36Sopenharmony_ci unsigned int j, speed, pos, kHz_step, numvscales; 53462306a36Sopenharmony_ci int min_vid_speed; 53562306a36Sopenharmony_ci 53662306a36Sopenharmony_ci rdmsrl(MSR_VIA_LONGHAUL, longhaul.val); 53762306a36Sopenharmony_ci if (!(longhaul.bits.RevisionID & 1)) { 53862306a36Sopenharmony_ci pr_info("Voltage scaling not supported by CPU\n"); 53962306a36Sopenharmony_ci return; 54062306a36Sopenharmony_ci } 54162306a36Sopenharmony_ci 54262306a36Sopenharmony_ci if (!longhaul.bits.VRMRev) { 54362306a36Sopenharmony_ci pr_info("VRM 8.5\n"); 54462306a36Sopenharmony_ci vrm_mV_table = &vrm85_mV[0]; 54562306a36Sopenharmony_ci mV_vrm_table = &mV_vrm85[0]; 54662306a36Sopenharmony_ci } else { 54762306a36Sopenharmony_ci pr_info("Mobile VRM\n"); 54862306a36Sopenharmony_ci if (cpu_model < CPU_NEHEMIAH) 54962306a36Sopenharmony_ci return; 55062306a36Sopenharmony_ci vrm_mV_table = &mobilevrm_mV[0]; 55162306a36Sopenharmony_ci mV_vrm_table = &mV_mobilevrm[0]; 55262306a36Sopenharmony_ci } 55362306a36Sopenharmony_ci 55462306a36Sopenharmony_ci minvid = vrm_mV_table[longhaul.bits.MinimumVID]; 55562306a36Sopenharmony_ci maxvid = vrm_mV_table[longhaul.bits.MaximumVID]; 55662306a36Sopenharmony_ci 55762306a36Sopenharmony_ci if (minvid.mV == 0 || maxvid.mV == 0 || minvid.mV > maxvid.mV) { 55862306a36Sopenharmony_ci pr_info("Bogus values Min:%d.%03d Max:%d.%03d - Voltage scaling disabled\n", 55962306a36Sopenharmony_ci minvid.mV/1000, minvid.mV%1000, 56062306a36Sopenharmony_ci maxvid.mV/1000, maxvid.mV%1000); 56162306a36Sopenharmony_ci return; 56262306a36Sopenharmony_ci } 56362306a36Sopenharmony_ci 56462306a36Sopenharmony_ci if (minvid.mV == maxvid.mV) { 56562306a36Sopenharmony_ci pr_info("Claims to support voltage scaling but min & max are both %d.%03d - Voltage scaling disabled\n", 56662306a36Sopenharmony_ci maxvid.mV/1000, maxvid.mV%1000); 56762306a36Sopenharmony_ci return; 56862306a36Sopenharmony_ci } 56962306a36Sopenharmony_ci 57062306a36Sopenharmony_ci /* How many voltage steps*/ 57162306a36Sopenharmony_ci numvscales = maxvid.pos - minvid.pos + 1; 57262306a36Sopenharmony_ci pr_info("Max VID=%d.%03d Min VID=%d.%03d, %d possible voltage scales\n", 57362306a36Sopenharmony_ci maxvid.mV/1000, maxvid.mV%1000, 57462306a36Sopenharmony_ci minvid.mV/1000, minvid.mV%1000, 57562306a36Sopenharmony_ci numvscales); 57662306a36Sopenharmony_ci 57762306a36Sopenharmony_ci /* Calculate max frequency at min voltage */ 57862306a36Sopenharmony_ci j = longhaul.bits.MinMHzBR; 57962306a36Sopenharmony_ci if (longhaul.bits.MinMHzBR4) 58062306a36Sopenharmony_ci j += 16; 58162306a36Sopenharmony_ci min_vid_speed = eblcr[j]; 58262306a36Sopenharmony_ci if (min_vid_speed == -1) 58362306a36Sopenharmony_ci return; 58462306a36Sopenharmony_ci switch (longhaul.bits.MinMHzFSB) { 58562306a36Sopenharmony_ci case 0: 58662306a36Sopenharmony_ci min_vid_speed *= 13333; 58762306a36Sopenharmony_ci break; 58862306a36Sopenharmony_ci case 1: 58962306a36Sopenharmony_ci min_vid_speed *= 10000; 59062306a36Sopenharmony_ci break; 59162306a36Sopenharmony_ci case 3: 59262306a36Sopenharmony_ci min_vid_speed *= 6666; 59362306a36Sopenharmony_ci break; 59462306a36Sopenharmony_ci default: 59562306a36Sopenharmony_ci return; 59662306a36Sopenharmony_ci } 59762306a36Sopenharmony_ci if (min_vid_speed >= highest_speed) 59862306a36Sopenharmony_ci return; 59962306a36Sopenharmony_ci /* Calculate kHz for one voltage step */ 60062306a36Sopenharmony_ci kHz_step = (highest_speed - min_vid_speed) / numvscales; 60162306a36Sopenharmony_ci 60262306a36Sopenharmony_ci cpufreq_for_each_entry_idx(freq_pos, longhaul_table, j) { 60362306a36Sopenharmony_ci speed = freq_pos->frequency; 60462306a36Sopenharmony_ci if (speed > min_vid_speed) 60562306a36Sopenharmony_ci pos = (speed - min_vid_speed) / kHz_step + minvid.pos; 60662306a36Sopenharmony_ci else 60762306a36Sopenharmony_ci pos = minvid.pos; 60862306a36Sopenharmony_ci freq_pos->driver_data |= mV_vrm_table[pos] << 8; 60962306a36Sopenharmony_ci vid = vrm_mV_table[mV_vrm_table[pos]]; 61062306a36Sopenharmony_ci pr_info("f: %d kHz, index: %d, vid: %d mV\n", 61162306a36Sopenharmony_ci speed, j, vid.mV); 61262306a36Sopenharmony_ci } 61362306a36Sopenharmony_ci 61462306a36Sopenharmony_ci can_scale_voltage = 1; 61562306a36Sopenharmony_ci pr_info("Voltage scaling enabled\n"); 61662306a36Sopenharmony_ci} 61762306a36Sopenharmony_ci 61862306a36Sopenharmony_ci 61962306a36Sopenharmony_cistatic int longhaul_target(struct cpufreq_policy *policy, 62062306a36Sopenharmony_ci unsigned int table_index) 62162306a36Sopenharmony_ci{ 62262306a36Sopenharmony_ci unsigned int i; 62362306a36Sopenharmony_ci unsigned int dir = 0; 62462306a36Sopenharmony_ci u8 vid, current_vid; 62562306a36Sopenharmony_ci int retval = 0; 62662306a36Sopenharmony_ci 62762306a36Sopenharmony_ci if (!can_scale_voltage) 62862306a36Sopenharmony_ci retval = longhaul_setstate(policy, table_index); 62962306a36Sopenharmony_ci else { 63062306a36Sopenharmony_ci /* On test system voltage transitions exceeding single 63162306a36Sopenharmony_ci * step up or down were turning motherboard off. Both 63262306a36Sopenharmony_ci * "ondemand" and "userspace" are unsafe. C7 is doing 63362306a36Sopenharmony_ci * this in hardware, C3 is old and we need to do this 63462306a36Sopenharmony_ci * in software. */ 63562306a36Sopenharmony_ci i = longhaul_index; 63662306a36Sopenharmony_ci current_vid = (longhaul_table[longhaul_index].driver_data >> 8); 63762306a36Sopenharmony_ci current_vid &= 0x1f; 63862306a36Sopenharmony_ci if (table_index > longhaul_index) 63962306a36Sopenharmony_ci dir = 1; 64062306a36Sopenharmony_ci while (i != table_index) { 64162306a36Sopenharmony_ci vid = (longhaul_table[i].driver_data >> 8) & 0x1f; 64262306a36Sopenharmony_ci if (vid != current_vid) { 64362306a36Sopenharmony_ci retval = longhaul_setstate(policy, i); 64462306a36Sopenharmony_ci current_vid = vid; 64562306a36Sopenharmony_ci msleep(200); 64662306a36Sopenharmony_ci } 64762306a36Sopenharmony_ci if (dir) 64862306a36Sopenharmony_ci i++; 64962306a36Sopenharmony_ci else 65062306a36Sopenharmony_ci i--; 65162306a36Sopenharmony_ci } 65262306a36Sopenharmony_ci retval = longhaul_setstate(policy, table_index); 65362306a36Sopenharmony_ci } 65462306a36Sopenharmony_ci 65562306a36Sopenharmony_ci longhaul_index = table_index; 65662306a36Sopenharmony_ci return retval; 65762306a36Sopenharmony_ci} 65862306a36Sopenharmony_ci 65962306a36Sopenharmony_ci 66062306a36Sopenharmony_cistatic unsigned int longhaul_get(unsigned int cpu) 66162306a36Sopenharmony_ci{ 66262306a36Sopenharmony_ci if (cpu) 66362306a36Sopenharmony_ci return 0; 66462306a36Sopenharmony_ci return calc_speed(longhaul_get_cpu_mult()); 66562306a36Sopenharmony_ci} 66662306a36Sopenharmony_ci 66762306a36Sopenharmony_cistatic acpi_status longhaul_walk_callback(acpi_handle obj_handle, 66862306a36Sopenharmony_ci u32 nesting_level, 66962306a36Sopenharmony_ci void *context, void **return_value) 67062306a36Sopenharmony_ci{ 67162306a36Sopenharmony_ci struct acpi_device *d = acpi_fetch_acpi_dev(obj_handle); 67262306a36Sopenharmony_ci 67362306a36Sopenharmony_ci if (!d) 67462306a36Sopenharmony_ci return 0; 67562306a36Sopenharmony_ci 67662306a36Sopenharmony_ci *return_value = acpi_driver_data(d); 67762306a36Sopenharmony_ci return 1; 67862306a36Sopenharmony_ci} 67962306a36Sopenharmony_ci 68062306a36Sopenharmony_ci/* VIA don't support PM2 reg, but have something similar */ 68162306a36Sopenharmony_cistatic int enable_arbiter_disable(void) 68262306a36Sopenharmony_ci{ 68362306a36Sopenharmony_ci struct pci_dev *dev; 68462306a36Sopenharmony_ci int status = 1; 68562306a36Sopenharmony_ci int reg; 68662306a36Sopenharmony_ci u8 pci_cmd; 68762306a36Sopenharmony_ci 68862306a36Sopenharmony_ci /* Find PLE133 host bridge */ 68962306a36Sopenharmony_ci reg = 0x78; 69062306a36Sopenharmony_ci dev = pci_get_device(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_8601_0, 69162306a36Sopenharmony_ci NULL); 69262306a36Sopenharmony_ci /* Find PM133/VT8605 host bridge */ 69362306a36Sopenharmony_ci if (dev == NULL) 69462306a36Sopenharmony_ci dev = pci_get_device(PCI_VENDOR_ID_VIA, 69562306a36Sopenharmony_ci PCI_DEVICE_ID_VIA_8605_0, NULL); 69662306a36Sopenharmony_ci /* Find CLE266 host bridge */ 69762306a36Sopenharmony_ci if (dev == NULL) { 69862306a36Sopenharmony_ci reg = 0x76; 69962306a36Sopenharmony_ci dev = pci_get_device(PCI_VENDOR_ID_VIA, 70062306a36Sopenharmony_ci PCI_DEVICE_ID_VIA_862X_0, NULL); 70162306a36Sopenharmony_ci /* Find CN400 V-Link host bridge */ 70262306a36Sopenharmony_ci if (dev == NULL) 70362306a36Sopenharmony_ci dev = pci_get_device(PCI_VENDOR_ID_VIA, 0x7259, NULL); 70462306a36Sopenharmony_ci } 70562306a36Sopenharmony_ci if (dev != NULL) { 70662306a36Sopenharmony_ci /* Enable access to port 0x22 */ 70762306a36Sopenharmony_ci pci_read_config_byte(dev, reg, &pci_cmd); 70862306a36Sopenharmony_ci if (!(pci_cmd & 1<<7)) { 70962306a36Sopenharmony_ci pci_cmd |= 1<<7; 71062306a36Sopenharmony_ci pci_write_config_byte(dev, reg, pci_cmd); 71162306a36Sopenharmony_ci pci_read_config_byte(dev, reg, &pci_cmd); 71262306a36Sopenharmony_ci if (!(pci_cmd & 1<<7)) { 71362306a36Sopenharmony_ci pr_err("Can't enable access to port 0x22\n"); 71462306a36Sopenharmony_ci status = 0; 71562306a36Sopenharmony_ci } 71662306a36Sopenharmony_ci } 71762306a36Sopenharmony_ci pci_dev_put(dev); 71862306a36Sopenharmony_ci return status; 71962306a36Sopenharmony_ci } 72062306a36Sopenharmony_ci return 0; 72162306a36Sopenharmony_ci} 72262306a36Sopenharmony_ci 72362306a36Sopenharmony_cistatic int longhaul_setup_southbridge(void) 72462306a36Sopenharmony_ci{ 72562306a36Sopenharmony_ci struct pci_dev *dev; 72662306a36Sopenharmony_ci u8 pci_cmd; 72762306a36Sopenharmony_ci 72862306a36Sopenharmony_ci /* Find VT8235 southbridge */ 72962306a36Sopenharmony_ci dev = pci_get_device(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_8235, NULL); 73062306a36Sopenharmony_ci if (dev == NULL) 73162306a36Sopenharmony_ci /* Find VT8237 southbridge */ 73262306a36Sopenharmony_ci dev = pci_get_device(PCI_VENDOR_ID_VIA, 73362306a36Sopenharmony_ci PCI_DEVICE_ID_VIA_8237, NULL); 73462306a36Sopenharmony_ci if (dev != NULL) { 73562306a36Sopenharmony_ci /* Set transition time to max */ 73662306a36Sopenharmony_ci pci_read_config_byte(dev, 0xec, &pci_cmd); 73762306a36Sopenharmony_ci pci_cmd &= ~(1 << 2); 73862306a36Sopenharmony_ci pci_write_config_byte(dev, 0xec, pci_cmd); 73962306a36Sopenharmony_ci pci_read_config_byte(dev, 0xe4, &pci_cmd); 74062306a36Sopenharmony_ci pci_cmd &= ~(1 << 7); 74162306a36Sopenharmony_ci pci_write_config_byte(dev, 0xe4, pci_cmd); 74262306a36Sopenharmony_ci pci_read_config_byte(dev, 0xe5, &pci_cmd); 74362306a36Sopenharmony_ci pci_cmd |= 1 << 7; 74462306a36Sopenharmony_ci pci_write_config_byte(dev, 0xe5, pci_cmd); 74562306a36Sopenharmony_ci /* Get address of ACPI registers block*/ 74662306a36Sopenharmony_ci pci_read_config_byte(dev, 0x81, &pci_cmd); 74762306a36Sopenharmony_ci if (pci_cmd & 1 << 7) { 74862306a36Sopenharmony_ci pci_read_config_dword(dev, 0x88, &acpi_regs_addr); 74962306a36Sopenharmony_ci acpi_regs_addr &= 0xff00; 75062306a36Sopenharmony_ci pr_info("ACPI I/O at 0x%x\n", acpi_regs_addr); 75162306a36Sopenharmony_ci } 75262306a36Sopenharmony_ci 75362306a36Sopenharmony_ci pci_dev_put(dev); 75462306a36Sopenharmony_ci return 1; 75562306a36Sopenharmony_ci } 75662306a36Sopenharmony_ci return 0; 75762306a36Sopenharmony_ci} 75862306a36Sopenharmony_ci 75962306a36Sopenharmony_cistatic int longhaul_cpu_init(struct cpufreq_policy *policy) 76062306a36Sopenharmony_ci{ 76162306a36Sopenharmony_ci struct cpuinfo_x86 *c = &cpu_data(0); 76262306a36Sopenharmony_ci char *cpuname = NULL; 76362306a36Sopenharmony_ci int ret; 76462306a36Sopenharmony_ci u32 lo, hi; 76562306a36Sopenharmony_ci 76662306a36Sopenharmony_ci /* Check what we have on this motherboard */ 76762306a36Sopenharmony_ci switch (c->x86_model) { 76862306a36Sopenharmony_ci case 6: 76962306a36Sopenharmony_ci cpu_model = CPU_SAMUEL; 77062306a36Sopenharmony_ci cpuname = "C3 'Samuel' [C5A]"; 77162306a36Sopenharmony_ci longhaul_version = TYPE_LONGHAUL_V1; 77262306a36Sopenharmony_ci memcpy(mults, samuel1_mults, sizeof(samuel1_mults)); 77362306a36Sopenharmony_ci memcpy(eblcr, samuel1_eblcr, sizeof(samuel1_eblcr)); 77462306a36Sopenharmony_ci break; 77562306a36Sopenharmony_ci 77662306a36Sopenharmony_ci case 7: 77762306a36Sopenharmony_ci switch (c->x86_stepping) { 77862306a36Sopenharmony_ci case 0: 77962306a36Sopenharmony_ci longhaul_version = TYPE_LONGHAUL_V1; 78062306a36Sopenharmony_ci cpu_model = CPU_SAMUEL2; 78162306a36Sopenharmony_ci cpuname = "C3 'Samuel 2' [C5B]"; 78262306a36Sopenharmony_ci /* Note, this is not a typo, early Samuel2's had 78362306a36Sopenharmony_ci * Samuel1 ratios. */ 78462306a36Sopenharmony_ci memcpy(mults, samuel1_mults, sizeof(samuel1_mults)); 78562306a36Sopenharmony_ci memcpy(eblcr, samuel2_eblcr, sizeof(samuel2_eblcr)); 78662306a36Sopenharmony_ci break; 78762306a36Sopenharmony_ci case 1 ... 15: 78862306a36Sopenharmony_ci longhaul_version = TYPE_LONGHAUL_V2; 78962306a36Sopenharmony_ci if (c->x86_stepping < 8) { 79062306a36Sopenharmony_ci cpu_model = CPU_SAMUEL2; 79162306a36Sopenharmony_ci cpuname = "C3 'Samuel 2' [C5B]"; 79262306a36Sopenharmony_ci } else { 79362306a36Sopenharmony_ci cpu_model = CPU_EZRA; 79462306a36Sopenharmony_ci cpuname = "C3 'Ezra' [C5C]"; 79562306a36Sopenharmony_ci } 79662306a36Sopenharmony_ci memcpy(mults, ezra_mults, sizeof(ezra_mults)); 79762306a36Sopenharmony_ci memcpy(eblcr, ezra_eblcr, sizeof(ezra_eblcr)); 79862306a36Sopenharmony_ci break; 79962306a36Sopenharmony_ci } 80062306a36Sopenharmony_ci break; 80162306a36Sopenharmony_ci 80262306a36Sopenharmony_ci case 8: 80362306a36Sopenharmony_ci cpu_model = CPU_EZRA_T; 80462306a36Sopenharmony_ci cpuname = "C3 'Ezra-T' [C5M]"; 80562306a36Sopenharmony_ci longhaul_version = TYPE_POWERSAVER; 80662306a36Sopenharmony_ci numscales = 32; 80762306a36Sopenharmony_ci memcpy(mults, ezrat_mults, sizeof(ezrat_mults)); 80862306a36Sopenharmony_ci memcpy(eblcr, ezrat_eblcr, sizeof(ezrat_eblcr)); 80962306a36Sopenharmony_ci break; 81062306a36Sopenharmony_ci 81162306a36Sopenharmony_ci case 9: 81262306a36Sopenharmony_ci longhaul_version = TYPE_POWERSAVER; 81362306a36Sopenharmony_ci numscales = 32; 81462306a36Sopenharmony_ci memcpy(mults, nehemiah_mults, sizeof(nehemiah_mults)); 81562306a36Sopenharmony_ci memcpy(eblcr, nehemiah_eblcr, sizeof(nehemiah_eblcr)); 81662306a36Sopenharmony_ci switch (c->x86_stepping) { 81762306a36Sopenharmony_ci case 0 ... 1: 81862306a36Sopenharmony_ci cpu_model = CPU_NEHEMIAH; 81962306a36Sopenharmony_ci cpuname = "C3 'Nehemiah A' [C5XLOE]"; 82062306a36Sopenharmony_ci break; 82162306a36Sopenharmony_ci case 2 ... 4: 82262306a36Sopenharmony_ci cpu_model = CPU_NEHEMIAH; 82362306a36Sopenharmony_ci cpuname = "C3 'Nehemiah B' [C5XLOH]"; 82462306a36Sopenharmony_ci break; 82562306a36Sopenharmony_ci case 5 ... 15: 82662306a36Sopenharmony_ci cpu_model = CPU_NEHEMIAH_C; 82762306a36Sopenharmony_ci cpuname = "C3 'Nehemiah C' [C5P]"; 82862306a36Sopenharmony_ci break; 82962306a36Sopenharmony_ci } 83062306a36Sopenharmony_ci break; 83162306a36Sopenharmony_ci 83262306a36Sopenharmony_ci default: 83362306a36Sopenharmony_ci cpuname = "Unknown"; 83462306a36Sopenharmony_ci break; 83562306a36Sopenharmony_ci } 83662306a36Sopenharmony_ci /* Check Longhaul ver. 2 */ 83762306a36Sopenharmony_ci if (longhaul_version == TYPE_LONGHAUL_V2) { 83862306a36Sopenharmony_ci rdmsr(MSR_VIA_LONGHAUL, lo, hi); 83962306a36Sopenharmony_ci if (lo == 0 && hi == 0) 84062306a36Sopenharmony_ci /* Looks like MSR isn't present */ 84162306a36Sopenharmony_ci longhaul_version = TYPE_LONGHAUL_V1; 84262306a36Sopenharmony_ci } 84362306a36Sopenharmony_ci 84462306a36Sopenharmony_ci pr_info("VIA %s CPU detected. ", cpuname); 84562306a36Sopenharmony_ci switch (longhaul_version) { 84662306a36Sopenharmony_ci case TYPE_LONGHAUL_V1: 84762306a36Sopenharmony_ci case TYPE_LONGHAUL_V2: 84862306a36Sopenharmony_ci pr_cont("Longhaul v%d supported\n", longhaul_version); 84962306a36Sopenharmony_ci break; 85062306a36Sopenharmony_ci case TYPE_POWERSAVER: 85162306a36Sopenharmony_ci pr_cont("Powersaver supported\n"); 85262306a36Sopenharmony_ci break; 85362306a36Sopenharmony_ci } 85462306a36Sopenharmony_ci 85562306a36Sopenharmony_ci /* Doesn't hurt */ 85662306a36Sopenharmony_ci longhaul_setup_southbridge(); 85762306a36Sopenharmony_ci 85862306a36Sopenharmony_ci /* Find ACPI data for processor */ 85962306a36Sopenharmony_ci acpi_walk_namespace(ACPI_TYPE_PROCESSOR, ACPI_ROOT_OBJECT, 86062306a36Sopenharmony_ci ACPI_UINT32_MAX, &longhaul_walk_callback, NULL, 86162306a36Sopenharmony_ci NULL, (void *)&pr); 86262306a36Sopenharmony_ci 86362306a36Sopenharmony_ci /* Check ACPI support for C3 state */ 86462306a36Sopenharmony_ci if (pr != NULL && longhaul_version == TYPE_POWERSAVER) { 86562306a36Sopenharmony_ci cx = &pr->power.states[ACPI_STATE_C3]; 86662306a36Sopenharmony_ci if (cx->address > 0 && cx->latency <= 1000) 86762306a36Sopenharmony_ci longhaul_flags |= USE_ACPI_C3; 86862306a36Sopenharmony_ci } 86962306a36Sopenharmony_ci /* Disable if it isn't working */ 87062306a36Sopenharmony_ci if (disable_acpi_c3) 87162306a36Sopenharmony_ci longhaul_flags &= ~USE_ACPI_C3; 87262306a36Sopenharmony_ci /* Check if northbridge is friendly */ 87362306a36Sopenharmony_ci if (enable_arbiter_disable()) 87462306a36Sopenharmony_ci longhaul_flags |= USE_NORTHBRIDGE; 87562306a36Sopenharmony_ci 87662306a36Sopenharmony_ci /* Check ACPI support for bus master arbiter disable */ 87762306a36Sopenharmony_ci if (!(longhaul_flags & USE_ACPI_C3 87862306a36Sopenharmony_ci || longhaul_flags & USE_NORTHBRIDGE) 87962306a36Sopenharmony_ci && ((pr == NULL) || !(pr->flags.bm_control))) { 88062306a36Sopenharmony_ci pr_err("No ACPI support: Unsupported northbridge\n"); 88162306a36Sopenharmony_ci return -ENODEV; 88262306a36Sopenharmony_ci } 88362306a36Sopenharmony_ci 88462306a36Sopenharmony_ci if (longhaul_flags & USE_NORTHBRIDGE) 88562306a36Sopenharmony_ci pr_info("Using northbridge support\n"); 88662306a36Sopenharmony_ci if (longhaul_flags & USE_ACPI_C3) 88762306a36Sopenharmony_ci pr_info("Using ACPI support\n"); 88862306a36Sopenharmony_ci 88962306a36Sopenharmony_ci ret = longhaul_get_ranges(); 89062306a36Sopenharmony_ci if (ret != 0) 89162306a36Sopenharmony_ci return ret; 89262306a36Sopenharmony_ci 89362306a36Sopenharmony_ci if ((longhaul_version != TYPE_LONGHAUL_V1) && (scale_voltage != 0)) 89462306a36Sopenharmony_ci longhaul_setup_voltagescaling(); 89562306a36Sopenharmony_ci 89662306a36Sopenharmony_ci policy->transition_delay_us = 200000; /* usec */ 89762306a36Sopenharmony_ci policy->freq_table = longhaul_table; 89862306a36Sopenharmony_ci 89962306a36Sopenharmony_ci return 0; 90062306a36Sopenharmony_ci} 90162306a36Sopenharmony_ci 90262306a36Sopenharmony_cistatic struct cpufreq_driver longhaul_driver = { 90362306a36Sopenharmony_ci .verify = cpufreq_generic_frequency_table_verify, 90462306a36Sopenharmony_ci .target_index = longhaul_target, 90562306a36Sopenharmony_ci .get = longhaul_get, 90662306a36Sopenharmony_ci .init = longhaul_cpu_init, 90762306a36Sopenharmony_ci .name = "longhaul", 90862306a36Sopenharmony_ci .attr = cpufreq_generic_attr, 90962306a36Sopenharmony_ci}; 91062306a36Sopenharmony_ci 91162306a36Sopenharmony_cistatic const struct x86_cpu_id longhaul_id[] = { 91262306a36Sopenharmony_ci X86_MATCH_VENDOR_FAM(CENTAUR, 6, NULL), 91362306a36Sopenharmony_ci {} 91462306a36Sopenharmony_ci}; 91562306a36Sopenharmony_ciMODULE_DEVICE_TABLE(x86cpu, longhaul_id); 91662306a36Sopenharmony_ci 91762306a36Sopenharmony_cistatic int __init longhaul_init(void) 91862306a36Sopenharmony_ci{ 91962306a36Sopenharmony_ci struct cpuinfo_x86 *c = &cpu_data(0); 92062306a36Sopenharmony_ci 92162306a36Sopenharmony_ci if (!x86_match_cpu(longhaul_id)) 92262306a36Sopenharmony_ci return -ENODEV; 92362306a36Sopenharmony_ci 92462306a36Sopenharmony_ci if (!enable) { 92562306a36Sopenharmony_ci pr_err("Option \"enable\" not set - Aborting\n"); 92662306a36Sopenharmony_ci return -ENODEV; 92762306a36Sopenharmony_ci } 92862306a36Sopenharmony_ci#ifdef CONFIG_SMP 92962306a36Sopenharmony_ci if (num_online_cpus() > 1) { 93062306a36Sopenharmony_ci pr_err("More than 1 CPU detected, longhaul disabled\n"); 93162306a36Sopenharmony_ci return -ENODEV; 93262306a36Sopenharmony_ci } 93362306a36Sopenharmony_ci#endif 93462306a36Sopenharmony_ci#ifdef CONFIG_X86_IO_APIC 93562306a36Sopenharmony_ci if (boot_cpu_has(X86_FEATURE_APIC)) { 93662306a36Sopenharmony_ci pr_err("APIC detected. Longhaul is currently broken in this configuration.\n"); 93762306a36Sopenharmony_ci return -ENODEV; 93862306a36Sopenharmony_ci } 93962306a36Sopenharmony_ci#endif 94062306a36Sopenharmony_ci switch (c->x86_model) { 94162306a36Sopenharmony_ci case 6 ... 9: 94262306a36Sopenharmony_ci return cpufreq_register_driver(&longhaul_driver); 94362306a36Sopenharmony_ci case 10: 94462306a36Sopenharmony_ci pr_err("Use acpi-cpufreq driver for VIA C7\n"); 94562306a36Sopenharmony_ci } 94662306a36Sopenharmony_ci 94762306a36Sopenharmony_ci return -ENODEV; 94862306a36Sopenharmony_ci} 94962306a36Sopenharmony_ci 95062306a36Sopenharmony_ci 95162306a36Sopenharmony_cistatic void __exit longhaul_exit(void) 95262306a36Sopenharmony_ci{ 95362306a36Sopenharmony_ci struct cpufreq_policy *policy = cpufreq_cpu_get(0); 95462306a36Sopenharmony_ci int i; 95562306a36Sopenharmony_ci 95662306a36Sopenharmony_ci for (i = 0; i < numscales; i++) { 95762306a36Sopenharmony_ci if (mults[i] == maxmult) { 95862306a36Sopenharmony_ci struct cpufreq_freqs freqs; 95962306a36Sopenharmony_ci 96062306a36Sopenharmony_ci freqs.old = policy->cur; 96162306a36Sopenharmony_ci freqs.new = longhaul_table[i].frequency; 96262306a36Sopenharmony_ci freqs.flags = 0; 96362306a36Sopenharmony_ci 96462306a36Sopenharmony_ci cpufreq_freq_transition_begin(policy, &freqs); 96562306a36Sopenharmony_ci longhaul_setstate(policy, i); 96662306a36Sopenharmony_ci cpufreq_freq_transition_end(policy, &freqs, 0); 96762306a36Sopenharmony_ci break; 96862306a36Sopenharmony_ci } 96962306a36Sopenharmony_ci } 97062306a36Sopenharmony_ci 97162306a36Sopenharmony_ci cpufreq_cpu_put(policy); 97262306a36Sopenharmony_ci cpufreq_unregister_driver(&longhaul_driver); 97362306a36Sopenharmony_ci kfree(longhaul_table); 97462306a36Sopenharmony_ci} 97562306a36Sopenharmony_ci 97662306a36Sopenharmony_ci/* Even if BIOS is exporting ACPI C3 state, and it is used 97762306a36Sopenharmony_ci * with success when CPU is idle, this state doesn't 97862306a36Sopenharmony_ci * trigger frequency transition in some cases. */ 97962306a36Sopenharmony_cimodule_param(disable_acpi_c3, int, 0644); 98062306a36Sopenharmony_ciMODULE_PARM_DESC(disable_acpi_c3, "Don't use ACPI C3 support"); 98162306a36Sopenharmony_ci/* Change CPU voltage with frequency. Very useful to save 98262306a36Sopenharmony_ci * power, but most VIA C3 processors aren't supporting it. */ 98362306a36Sopenharmony_cimodule_param(scale_voltage, int, 0644); 98462306a36Sopenharmony_ciMODULE_PARM_DESC(scale_voltage, "Scale voltage of processor"); 98562306a36Sopenharmony_ci/* Force revision key to 0 for processors which doesn't 98662306a36Sopenharmony_ci * support voltage scaling, but are introducing itself as 98762306a36Sopenharmony_ci * such. */ 98862306a36Sopenharmony_cimodule_param(revid_errata, int, 0644); 98962306a36Sopenharmony_ciMODULE_PARM_DESC(revid_errata, "Ignore CPU Revision ID"); 99062306a36Sopenharmony_ci/* By default driver is disabled to prevent incompatible 99162306a36Sopenharmony_ci * system freeze. */ 99262306a36Sopenharmony_cimodule_param(enable, int, 0644); 99362306a36Sopenharmony_ciMODULE_PARM_DESC(enable, "Enable driver"); 99462306a36Sopenharmony_ci 99562306a36Sopenharmony_ciMODULE_AUTHOR("Dave Jones"); 99662306a36Sopenharmony_ciMODULE_DESCRIPTION("Longhaul driver for VIA Cyrix processors."); 99762306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 99862306a36Sopenharmony_ci 99962306a36Sopenharmony_cilate_initcall(longhaul_init); 100062306a36Sopenharmony_cimodule_exit(longhaul_exit); 1001