/****************************************************************************** * * Name: hwcpu32.c - CPU support for IA32 (Throttling, Cx_states) * $Revision: 33 $ * *****************************************************************************/ /* * Copyright (C) 2000 R. Byron Moore * * 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 "acpi.h" #include "acnamesp.h" #include "achware.h" #define _COMPONENT HARDWARE MODULE_NAME ("Hwcpu32") #define BIT_4 0x10 /* TBD: [investigate] is this correct? */ /**************************************************************************** * * FUNCTION: Acpi_hw_enter_c1 * * PARAMETERS: Pblk_address - Address of the processor control block * Pm_timer_ticks - Number of PM timer ticks elapsed while asleep * * RETURN: Function status. * * DESCRIPTION: Set C1 state on IA32 processor (halt) * ****************************************************************************/ ACPI_STATUS acpi_hw_enter_c1( ACPI_IO_ADDRESS pblk_address, u32 *pm_timer_ticks) { u32 timer = 0; if (!pm_timer_ticks) { /* * Enter C1: * --------- */ enable(); halt(); *pm_timer_ticks = ACPI_UINT32_MAX; } else { timer = acpi_hw_pmt_ticks (); /* * Enter C1: * --------- */ enable (); halt (); /* * Compute Time in C1: * ------------------- */ timer = acpi_hw_pmt_ticks () - timer; *pm_timer_ticks = timer; } return (AE_OK); } /**************************************************************************** * * FUNCTION: Acpi_hw_enter_c2 * * PARAMETERS: Pblk_address - Address of the processor control block * Pm_timer_ticks - Number of PM timer ticks elapsed while asleep * * RETURN: * * DESCRIPTION: Set C2 state on IA32 processor * ****************************************************************************/ ACPI_STATUS acpi_hw_enter_c2( ACPI_IO_ADDRESS pblk_address, u32 *pm_timer_ticks) { u32 timer = 0; if (!pblk_address || !pm_timer_ticks) { return (AE_BAD_PARAMETER); } /* * Disable interrupts before all C2/C3 transitions. */ disable (); timer = acpi_hw_pmt_ticks (); /* * Enter C2: * --------- * Read from the P_LVL2 (P_BLK+4) register to invoke a C2 transition. */ acpi_os_in8 ((ACPI_IO_ADDRESS) (pblk_address + 4)); /* * Perform Dummy Op: * ----------------- * We have to do something useless after reading LVL2 because chipsets * cannot guarantee that STPCLK# gets asserted in time to freeze execution. */ acpi_os_in8 ((ACPI_IO_ADDRESS) acpi_gbl_FACP->pm2_cnt_blk); /* * Compute Time in C2: * ------------------- */ timer = acpi_hw_pmt_ticks () - timer; *pm_timer_ticks = timer; /* * Re-enable interrupts after coming out of C2/C3. */ enable (); return (AE_OK); } /**************************************************************************** * * FUNCTION: Acpi_hw_enter_c3 * * PARAMETERS: Pblk_address - Address of the processor control block * Pm_timer_ticks - Number of PM timer ticks elapsed while asleep * * RETURN: Status of function * * DESCRIPTION: Set C3 state on IA32 processor (UP only, cache coherency via * disabling bus mastering) * ****************************************************************************/ ACPI_STATUS acpi_hw_enter_c3( ACPI_IO_ADDRESS pblk_address, u32 *pm_timer_ticks) { u32 timer = 0; u8 pm2_cnt_blk = 0; u32 bus_master_status = 0; if (!pblk_address || !pm_timer_ticks) { return (AE_BAD_PARAMETER); } /* * Check the BM_STS bit, if it is set, do not enter C3 * but clear the bit (with a write) and exit, telling * the calling module that we spent zero time in C3. * If bus mastering continues, this action should * eventually cause a demotion to C2 */ if (1 == (bus_master_status = acpi_hw_register_access (ACPI_READ, ACPI_MTX_LOCK, BM_STS))) { /* * Clear the BM_STS bit by setting it. */ acpi_hw_register_access (ACPI_WRITE, ACPI_MTX_LOCK, BM_STS, 1); *pm_timer_ticks = 0; return (AE_OK); } /* * Disable interrupts before all C2/C3 transitions. */ disable(); /* * Disable Bus Mastering: * ---------------------- * Set the PM2_CNT.ARB_DIS bit (bit #0), preserving all other bits. */ pm2_cnt_blk = acpi_os_in8 ((ACPI_IO_ADDRESS) acpi_gbl_FACP->pm2_cnt_blk); pm2_cnt_blk |= 0x01; acpi_os_out8 ((ACPI_IO_ADDRESS) acpi_gbl_FACP->pm2_cnt_blk, pm2_cnt_blk); /* * Get the timer base before entering C state */ timer = acpi_hw_pmt_ticks (); /* * Enter C3: * --------- * Read from the P_LVL3 (P_BLK+5) register to invoke a C3 transition. */ acpi_os_in8 ((ACPI_IO_ADDRESS)(pblk_address + 5)); /* * Perform Dummy Op: * ----------------- * We have to do something useless after reading LVL3 because chipsets * cannot guarantee that STPCLK# gets asserted in time to freeze execution. */ acpi_os_in8 ((ACPI_IO_ADDRESS) acpi_gbl_FACP->pm2_cnt_blk); /* * Immediately compute the time in the C state */ timer = acpi_hw_pmt_ticks() - timer; /* * Re-Enable Bus Mastering: * ------------------------ * Clear the PM2_CNT.ARB_DIS bit (bit #0), preserving all other bits. */ pm2_cnt_blk = acpi_os_in8 ((ACPI_IO_ADDRESS) acpi_gbl_FACP->pm2_cnt_blk); pm2_cnt_blk &= 0xFE; acpi_os_out8 ((ACPI_IO_ADDRESS) acpi_gbl_FACP->pm2_cnt_blk, pm2_cnt_blk); /* TBD: [Unhandled]: Support 24-bit timers (this algorithm assumes 32-bit) */ *pm_timer_ticks = timer; /* * Re-enable interrupts after coming out of C2/C3. */ enable(); return (AE_OK); } /**************************************************************************** * * FUNCTION: Acpi_hw_enter_cx * * PARAMETERS: Processor_handle - handle of the processor * * RETURN: Status of function * * DESCRIPTION: Invoke the currently active processor Cx handler to put this * processor to sleep. * ****************************************************************************/ ACPI_STATUS acpi_hw_enter_cx ( ACPI_IO_ADDRESS pblk_address, u32 *pm_timer_ticks) { if (!acpi_hw_cx_handlers[acpi_hw_active_cx_state]) { return (AE_SUPPORT); } return (acpi_hw_cx_handlers[acpi_hw_active_cx_state] (pblk_address, pm_timer_ticks)); } /**************************************************************************** * * FUNCTION: Acpi_hw_set_cx * * PARAMETERS: State - value (1-3) of the Cx state to 'make active' * * RETURN: Function status. * * DESCRIPTION: Sets the state to use during calls to Acpi_hw_enter_cx(). * ****************************************************************************/ ACPI_STATUS acpi_hw_set_cx ( u32 cx_state) { /* * Supported State? * ---------------- */ if ((cx_state < 1) || (cx_state > 3)) { return (AE_BAD_PARAMETER); } if (!acpi_hw_cx_handlers[cx_state]) { return (AE_SUPPORT); } /* * New Cx State? * ------------- * We only care when moving from one state to another... */ if (acpi_hw_active_cx_state == cx_state) { return (AE_OK); } /* * Prepare to Use New State: * ------------------------- * If the new Cx_state is C3, the BM_RLD bit must be set to allow * the generation of a bus master requets to cause the processor * in the C3 state to transition to the C0 state. */ switch (cx_state) { case 3: acpi_hw_register_access (ACPI_WRITE, ACPI_MTX_LOCK, BM_RLD, 1); break; } /* * Clean up from Old State: * ------------------------ * If the old Cx_state was C3, the BM_RLD bit is reset. When the * bit is reset, the generation of a bus master request does not * effect any processor in the C3 state. */ switch (acpi_hw_active_cx_state) { case 3: acpi_hw_register_access (ACPI_WRITE, ACPI_MTX_LOCK, BM_RLD, 0); break; } /* * Enable: * ------- */ acpi_hw_active_cx_state = cx_state; return (AE_OK); } /**************************************************************************** * * FUNCTION: Acpi_hw_get_cx_info * * PARAMETERS: Cx_states - Information (latencies) on all Cx states * * RETURN: Status of function * * DESCRIPTION: This function is called both to initialize Cx handling * and retrieve the current Cx information (latency values). * ****************************************************************************/ ACPI_STATUS acpi_hw_get_cx_info ( u32 cx_states[]) { u8 SMP_system = FALSE; if (!cx_states) { return(AE_BAD_PARAMETER); } /* * TBD: [Unhandled] need to init SMP_system using info from the MAPIC * table. */ /* * Set Defaults: * ------------- * C0 and C1 support is implied (but what about that PROC_C1 register * in the FADT?!?!). Set C2/C3 to max. latency (not supported until * proven otherwise). */ cx_states[0] = 0; cx_states[1] = 0; cx_states[2] = MAX_CX_STATE_LATENCY; cx_states[3] = MAX_CX_STATE_LATENCY; /* * C2 Supported? * ------------- * We're only supporting C2 when the latency is <= 100 microseconds, * and on SMP systems when P_LVL2_UP (which indicates C2 only on UP) * is not set. */ if (acpi_gbl_FACP->plvl2_lat <= 100) { if (!SMP_system) { acpi_hw_cx_handlers[2] = acpi_hw_enter_c2; cx_states[2] = acpi_gbl_FACP->plvl2_lat; } else if (!acpi_gbl_FACP->plvl2_up) { acpi_hw_cx_handlers[2] = acpi_hw_enter_c2; cx_states[2] = acpi_gbl_FACP->plvl2_lat; } } /* * C3 Supported? * ------------- * We're only supporting C3 on UP systems when the latency is * <= 1000 microseconds and that include the ability to disable * Bus Mastering while in C3 (ARB_DIS) but allows Bus Mastering * requests to wake the system from C3 (BM_RLD). Note his method * of maintaining cache coherency (disabling of bus mastering) * cannot be used on SMP systems, and flushing caches (e.g. WBINVD) * is simply too costly (at this time). */ if (acpi_gbl_FACP->plvl3_lat <= 1000) { if (!SMP_system && (acpi_gbl_FACP->pm2_cnt_blk && acpi_gbl_FACP->pm2_cnt_len)) { acpi_hw_cx_handlers[3] = acpi_hw_enter_c3; cx_states[3] = acpi_gbl_FACP->plvl3_lat; } } return(AE_OK); } /**************************************************************************** * * FUNCTION: Acpi_hw_get_cx_handler * * PARAMETERS: State - the Cx state * Handler - pointer to location for the returned handler * * RETURN: Status of function * * DESCRIPTION: This function is called to get an installed Cx state handler. * ****************************************************************************/ ACPI_STATUS acpi_hw_get_cx_handler ( u32 cx_state, ACPI_C_STATE_HANDLER *handler) { if ((cx_state == 0) || (cx_state >= MAX_CX_STATES) || !handler) { return(AE_BAD_PARAMETER); } *handler = acpi_hw_cx_handlers[cx_state]; return(AE_OK); } /**************************************************************************** * * FUNCTION: Acpi_hw_set_cx_handler * * PARAMETERS: Cx_state - the Cx state * Handler - new Cx state handler * * RETURN: Status of function * * DESCRIPTION: This function is called to install a new Cx state handler. * ****************************************************************************/ ACPI_STATUS acpi_hw_set_cx_handler ( u32 cx_state, ACPI_C_STATE_HANDLER handler) { if ((cx_state == 0) || (cx_state >= MAX_CX_STATES) || !handler) { return(AE_BAD_PARAMETER); } acpi_hw_cx_handlers[cx_state] = handler; return(AE_OK); } /************************************************************************** * * FUNCTION: Acpi_hw_local_pow * * PARAMETERS: x,y operands * * RETURN: result * * DESCRIPTION: Compute x ^ y * *************************************************************************/ NATIVE_UINT acpi_hw_local_pow ( NATIVE_UINT x, NATIVE_UINT y) { NATIVE_UINT i; NATIVE_UINT result = 1; for (i = 0; i < y; i++) { result = result * x; } return (result); } /************************************************************************** * * FUNCTION: Acpi_hw_enable_throttling * * PARAMETERS: Pblk_address - Address of Pcnt (Processor Control) * register * * RETURN: none * * DESCRIPTION: Enable throttling by setting the THT_EN bit. * *************************************************************************/ void acpi_hw_enable_throttling ( ACPI_IO_ADDRESS pblk_address) { u32 pblk_value; pblk_value = acpi_os_in32 (pblk_address); pblk_value = pblk_value | BIT_4; acpi_os_out32 (pblk_address, pblk_value); return; } /************************************************************************** * * FUNCTION: Acpi_hw_disable_throttling * * PARAMETERS: Pblk_address - Address of Pcnt (Processor Control) * register * * RETURN: none * * DESCRIPTION:Disable throttling by clearing the THT_EN bit * *************************************************************************/ void acpi_hw_disable_throttling ( ACPI_IO_ADDRESS pblk_address) { u32 pblk_value; pblk_value = acpi_os_in32 (pblk_address); pblk_value = pblk_value & (~(u32)BIT_4); acpi_os_out32 (pblk_address, pblk_value); return; } /************************************************************************** * * FUNCTION: Acpi_hw_get_duty_cycle * * PARAMETERS: Duty_offset Pcnt register duty cycle field offset * Pblk_address Pcnt register address in chipset * Num_throttle_states # of CPU throttle states this system * supports * * RETURN: none * * DESCRIPTION: Get the duty cycle from the chipset * *************************************************************************/ u32 acpi_hw_get_duty_cycle ( u8 duty_offset, ACPI_IO_ADDRESS pblk_address, u32 num_throttle_states) { NATIVE_UINT index; u32 duty32_value; u32 pcnt_mask_off_duty_field; /* * Use Num_throttle_states - 1 as mask [ex. 8 - 1 = 7 (Fh)] * and then shift it into the right position */ pcnt_mask_off_duty_field = num_throttle_states - 1; /* * Read in the current value from the port */ duty32_value = acpi_os_in32 ((ACPI_IO_ADDRESS) pblk_address); /* * Shift the the value to LSB */ for (index = 0; index < (NATIVE_UINT) duty_offset; index++) { duty32_value = duty32_value >> 1; } /* * Get the duty field only */ duty32_value = duty32_value & pcnt_mask_off_duty_field; return ((u32) duty32_value); } /************************************************************************** * * FUNCTION: Acpi_hw_program_duty_cycle * * PARAMETERS: Duty_offset Pcnt register duty cycle field offset * Duty_cycle duty cycle to program into chipset * Pblk_address Pcnt register address in chipset * Num_throttle_states # of CPU throttle states this system * supports * * RETURN: none * * DESCRIPTION: Program chipset with specified duty cycle by bit-shifting the * duty cycle bits to the appropriate offset, reading the duty * cycle register, OR-ing in the duty cycle, and writing it to * the Pcnt register. * *************************************************************************/ void acpi_hw_program_duty_cycle ( u8 duty_offset, u32 duty_cycle, ACPI_IO_ADDRESS pblk_address, u32 num_throttle_states) { NATIVE_UINT index; u32 duty32_value; u32 pcnt_mask_off_duty_field; u32 port_value; /* * valid Duty_cycle passed */ duty32_value = duty_cycle; /* * use Num_throttle_states - 1 as mask [ex. 8 - 1 = 7 (Fh)] * and then shift it into the right position */ pcnt_mask_off_duty_field = num_throttle_states - 1; /* * Shift the mask */ for (index = 0; index < (NATIVE_UINT) duty_offset; index++) { pcnt_mask_off_duty_field = pcnt_mask_off_duty_field << 1; duty32_value = duty32_value << 1; } /* * Read in the current value from the port */ port_value = acpi_os_in32 ((ACPI_IO_ADDRESS) pblk_address); /* * Mask off the duty field so we don't OR in junk! */ port_value = port_value & (~pcnt_mask_off_duty_field); /* * OR in the bits we want to write out to the port */ port_value = (port_value | duty32_value) & (~(u32)BIT_4); /* * write it to the port */ acpi_os_out32 ((ACPI_IO_ADDRESS) pblk_address, port_value); return; }