/* * cpu.c - Processor handling * * Copyright (C) 2000 Andrew Henroid * Copyright (C) 2001 Andrew Grover * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include "acpi.h" #include "driver.h" #define _COMPONENT OS_DEPENDENT MODULE_NAME ("cpu") u32 acpi_c2_exit_latency = ACPI_INFINITE; u32 acpi_c3_exit_latency = ACPI_INFINITE; u32 acpi_c2_enter_latency = ACPI_INFINITE; u32 acpi_c3_enter_latency = ACPI_INFINITE; u32 acpi_use_idle = TRUE; u32 acpi_c1_count = 0; u32 acpi_c2_count = 0; u32 acpi_c3_count = 0; static u32 acpi_pblk = ACPI_INVALID; static int acpi_c2_tested = 0; static int acpi_c3_tested = 0; static int acpi_max_c_state = 1; static int acpi_pm_tmr_len = 24; #define CPU_POWER_STATES 3 #define MAX_C2_LATENCY 100 #define MAX_C3_LATENCY 1000 #define ACPI_STATE_C1 0 #define ACPI_STATE_C2 1 #define ACPI_STATE_C3 2 /* * Clear busmaster activity flag */ static inline void acpi_clear_bm_activity(void) { acpi_hw_register_bit_access(ACPI_WRITE, ACPI_MTX_LOCK, BM_STS, 1); } /* * Returns 1 if there has been busmaster activity */ static inline int acpi_bm_activity(void) { return acpi_hw_register_bit_access(ACPI_READ, ACPI_MTX_LOCK, BM_STS); } /* * Set system to sleep through busmaster requests */ static void acpi_sleep_on_busmaster(void) { acpi_hw_register_bit_access (ACPI_WRITE, ACPI_MTX_LOCK, BM_RLD, 1); } /* * Set system to wake on busmaster requests */ static void acpi_wake_on_busmaster(void) { acpi_hw_register_bit_access (ACPI_WRITE, ACPI_MTX_LOCK, BM_RLD, 0); } u32 acpi_read_pm_timer(void) { return acpi_hw_register_read(ACPI_MTX_LOCK, PM_TIMER); } /* * Do a compare, accounting for 24/32bit rollover */ static u32 acpi_compare_pm_timers(u32 first, u32 second) { if (first < second) { return (second - first); } else { if (acpi_pm_tmr_len == 24) return (second + (0xFFFFFF - first)); else return (second + (0xFFFFFFFF - first)); } } /* * Idle loop (uniprocessor only) */ static void acpi_idle(void) { static int sleep_level = 1; FADT_DESCRIPTOR *fadt = &acpi_fadt; /* * start from the previous sleep level. * if not initialized, we goto sleep1 */ if (sleep_level == 1 || acpi_max_c_state < 2) goto sleep1; if (sleep_level == 2 || acpi_max_c_state < 3) goto sleep2; sleep3: sleep_level = 3; if (!acpi_c3_tested) { DEBUG_PRINT(ACPI_INFO, ("C3 works\n")); acpi_c3_tested = 1; } acpi_wake_on_busmaster(); if (fadt->Xpm2_cnt_blk.address) goto sleep3_with_arbiter; for (;;) { unsigned long time; unsigned long diff; __cli(); if (current->need_resched) goto out; if (acpi_bm_activity()) goto sleep2; time = acpi_read_pm_timer(); acpi_c3_count++; inb(acpi_pblk + ACPI_P_LVL3); /* Dummy read, force synchronization with the PMU */ acpi_read_pm_timer(); diff = acpi_compare_pm_timers(time, acpi_read_pm_timer()); __sti(); if (diff < acpi_c3_exit_latency) goto sleep1; } sleep3_with_arbiter: for (;;) { unsigned long time; unsigned long diff; __cli(); if (current->need_resched) goto out; if (acpi_bm_activity()) goto sleep2; time = acpi_read_pm_timer(); /* Disable arbiter, park on CPU */ acpi_hw_register_bit_access(ACPI_WRITE, ACPI_MTX_LOCK, ARB_DIS, 1); acpi_c3_count++; inb(acpi_pblk + ACPI_P_LVL3); /* Dummy read, force synchronization with the PMU */ acpi_read_pm_timer(); diff = acpi_compare_pm_timers(time, acpi_read_pm_timer()); /* Enable arbiter again.. */ acpi_hw_register_bit_access(ACPI_WRITE, ACPI_MTX_LOCK, ARB_DIS, 0); __sti(); if (diff < acpi_c3_exit_latency) goto sleep1; } sleep2: sleep_level = 2; if (!acpi_c2_tested) { DEBUG_PRINT(ACPI_INFO, ("C2 works\n")); acpi_c2_tested = 1; } acpi_wake_on_busmaster(); /* Required to track BM activity.. */ for (;;) { unsigned long time; unsigned long diff; __cli(); if (current->need_resched) goto out; time = acpi_read_pm_timer(); acpi_c2_count++; inb(acpi_pblk + ACPI_P_LVL2); /* Dummy read, force synchronization with the PMU */ acpi_read_pm_timer(); diff = acpi_compare_pm_timers(time, acpi_read_pm_timer()); __sti(); if (diff < acpi_c2_exit_latency) goto sleep1; if (acpi_bm_activity()) { acpi_clear_bm_activity(); continue; } if (diff > acpi_c3_enter_latency && acpi_max_c_state >= 3) goto sleep3; } sleep1: sleep_level = 1; acpi_sleep_on_busmaster(); for (;;) { unsigned long time; unsigned long diff; __cli(); if (current->need_resched) goto out; time = acpi_read_pm_timer(); acpi_c1_count++; safe_halt(); diff = acpi_compare_pm_timers(time, acpi_read_pm_timer()); if (diff > acpi_c2_enter_latency && acpi_max_c_state >= 2) goto sleep2; } out: __sti(); } /* * Get processor information */ static ACPI_STATUS acpi_found_cpu(ACPI_HANDLE handle, u32 level, void *ctx, void **value) { ACPI_OBJECT obj; ACPI_BUFFER buf; buf.length = sizeof(obj); buf.pointer = &obj; if (!ACPI_SUCCESS(acpi_evaluate_object(handle, NULL, NULL, &buf))) return AE_OK; DEBUG_PRINT(ACPI_INFO, ("PBLK %d @ 0x%04x:%d\n", obj.processor.proc_id, obj.processor.pblk_address, obj.processor.pblk_length)); if (acpi_pblk != ACPI_INVALID || !obj.processor.pblk_address || obj.processor.pblk_length != 6) return AE_OK; acpi_pblk = obj.processor.pblk_address; if (acpi_fadt.plvl2_lat && acpi_fadt.plvl2_lat <= MAX_C2_LATENCY) { acpi_c2_exit_latency = ACPI_MICROSEC_TO_TMR_TICKS(acpi_fadt.plvl2_lat); acpi_c2_enter_latency = ACPI_MICROSEC_TO_TMR_TICKS(acpi_fadt.plvl2_lat * 4); acpi_max_c_state = 2; printk(KERN_INFO "ACPI: System firmware supports: C2"); if (acpi_fadt.plvl3_lat && acpi_fadt.plvl3_lat <= MAX_C3_LATENCY) { acpi_c3_exit_latency = ACPI_MICROSEC_TO_TMR_TICKS(acpi_fadt.plvl3_lat); acpi_c3_enter_latency = ACPI_MICROSEC_TO_TMR_TICKS(acpi_fadt.plvl3_lat * 12); acpi_max_c_state = 3; printk(" C3"); } printk("\n"); } printk(KERN_INFO "ACPI: plvl2lat=%d plvl3lat=%d\n", acpi_fadt.plvl2_lat, acpi_fadt.plvl3_lat); printk(KERN_INFO "ACPI: C2 enter=%d C2 exit=%d\n", acpi_c2_enter_latency, acpi_c2_exit_latency); printk(KERN_INFO "ACPI: C3 enter=%d C3 exit=%d\n", acpi_c3_enter_latency, acpi_c3_exit_latency); return AE_OK; } static int acpi_pm_timer_init(void) { FADT_DESCRIPTOR *fadt = &acpi_fadt; if (fadt->tmr_val_ext) { acpi_pm_tmr_len = 32; } else { acpi_pm_tmr_len = 24; } DEBUG_PRINT(ACPI_INFO, ("PM Timer width: %d bits\n", acpi_pm_tmr_len)); return AE_OK; } int acpi_cpu_init(void) { acpi_walk_namespace(ACPI_TYPE_PROCESSOR, ACPI_ROOT_OBJECT, ACPI_UINT32_MAX, acpi_found_cpu, NULL, NULL); acpi_pm_timer_init(); if (acpi_use_idle) { #ifdef CONFIG_SMP if (smp_num_cpus == 1) pm_idle = acpi_idle; #else pm_idle = acpi_idle; #endif printk(KERN_INFO "ACPI: Using ACPI idle\n"); printk(KERN_INFO "ACPI: If experiencing system slowness, try adding \"acpi=no-idle\" to cmdline\n"); } else { printk(KERN_INFO "ACPI: Not using ACPI idle\n"); } return 0; }