1*8906SEric.Saxe@Sun.COM /* 2*8906SEric.Saxe@Sun.COM * CDDL HEADER START 3*8906SEric.Saxe@Sun.COM * 4*8906SEric.Saxe@Sun.COM * The contents of this file are subject to the terms of the 5*8906SEric.Saxe@Sun.COM * Common Development and Distribution License (the "License"). 6*8906SEric.Saxe@Sun.COM * You may not use this file except in compliance with the License. 7*8906SEric.Saxe@Sun.COM * 8*8906SEric.Saxe@Sun.COM * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9*8906SEric.Saxe@Sun.COM * or http://www.opensolaris.org/os/licensing. 10*8906SEric.Saxe@Sun.COM * See the License for the specific language governing permissions 11*8906SEric.Saxe@Sun.COM * and limitations under the License. 12*8906SEric.Saxe@Sun.COM * 13*8906SEric.Saxe@Sun.COM * When distributing Covered Code, include this CDDL HEADER in each 14*8906SEric.Saxe@Sun.COM * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15*8906SEric.Saxe@Sun.COM * If applicable, add the following below this CDDL HEADER, with the 16*8906SEric.Saxe@Sun.COM * fields enclosed by brackets "[]" replaced with your own identifying 17*8906SEric.Saxe@Sun.COM * information: Portions Copyright [yyyy] [name of copyright owner] 18*8906SEric.Saxe@Sun.COM * 19*8906SEric.Saxe@Sun.COM * CDDL HEADER END 20*8906SEric.Saxe@Sun.COM */ 21*8906SEric.Saxe@Sun.COM /* 22*8906SEric.Saxe@Sun.COM * Copyright 2009 Sun Microsystems, Inc. All rights reserved. 23*8906SEric.Saxe@Sun.COM * Use is subject to license terms. 24*8906SEric.Saxe@Sun.COM */ 25*8906SEric.Saxe@Sun.COM 26*8906SEric.Saxe@Sun.COM /* 27*8906SEric.Saxe@Sun.COM * CPU power management driver support for i86pc. 28*8906SEric.Saxe@Sun.COM */ 29*8906SEric.Saxe@Sun.COM 30*8906SEric.Saxe@Sun.COM #include <sys/ddi.h> 31*8906SEric.Saxe@Sun.COM #include <sys/sunddi.h> 32*8906SEric.Saxe@Sun.COM #include <sys/cpupm.h> 33*8906SEric.Saxe@Sun.COM #include <sys/cpudrv_mach.h> 34*8906SEric.Saxe@Sun.COM #include <sys/machsystm.h> 35*8906SEric.Saxe@Sun.COM #include <sys/cpu_pm.h> 36*8906SEric.Saxe@Sun.COM #include <sys/cpuvar.h> 37*8906SEric.Saxe@Sun.COM #include <sys/sdt.h> 38*8906SEric.Saxe@Sun.COM #include <sys/cpu_idle.h> 39*8906SEric.Saxe@Sun.COM 40*8906SEric.Saxe@Sun.COM /* 41*8906SEric.Saxe@Sun.COM * Note that our driver numbers the power levels from lowest to 42*8906SEric.Saxe@Sun.COM * highest starting at 1 (i.e., the lowest power level is 1 and 43*8906SEric.Saxe@Sun.COM * the highest power level is cpupm->num_spd). The x86 modules get 44*8906SEric.Saxe@Sun.COM * their power levels from ACPI which numbers power levels from 45*8906SEric.Saxe@Sun.COM * highest to lowest starting at 0 (i.e., the lowest power level 46*8906SEric.Saxe@Sun.COM * is (cpupm->num_spd - 1) and the highest power level is 0). So to 47*8906SEric.Saxe@Sun.COM * map one of our driver power levels to one understood by ACPI we 48*8906SEric.Saxe@Sun.COM * simply subtract our driver power level from cpupm->num_spd. Likewise, 49*8906SEric.Saxe@Sun.COM * to map an ACPI power level to the proper driver power level, we 50*8906SEric.Saxe@Sun.COM * subtract the ACPI power level from cpupm->num_spd. 51*8906SEric.Saxe@Sun.COM */ 52*8906SEric.Saxe@Sun.COM #define PM_2_PLAT_LEVEL(cpupm, pm_level) (cpupm->num_spd - pm_level) 53*8906SEric.Saxe@Sun.COM #define PLAT_2_PM_LEVEL(cpupm, plat_level) (cpupm->num_spd - plat_level) 54*8906SEric.Saxe@Sun.COM 55*8906SEric.Saxe@Sun.COM /* 56*8906SEric.Saxe@Sun.COM * Change CPU speed using interface provided by module. 57*8906SEric.Saxe@Sun.COM */ 58*8906SEric.Saxe@Sun.COM int 59*8906SEric.Saxe@Sun.COM cpudrv_change_speed(cpudrv_devstate_t *cpudsp, cpudrv_pm_spd_t *new_spd) 60*8906SEric.Saxe@Sun.COM { 61*8906SEric.Saxe@Sun.COM cpu_t *cp = cpudsp->cp; 62*8906SEric.Saxe@Sun.COM cpupm_mach_state_t *mach_state = 63*8906SEric.Saxe@Sun.COM (cpupm_mach_state_t *)cp->cpu_m.mcpu_pm_mach_state; 64*8906SEric.Saxe@Sun.COM cpudrv_pm_t *cpupm; 65*8906SEric.Saxe@Sun.COM cpuset_t set; 66*8906SEric.Saxe@Sun.COM uint32_t plat_level; 67*8906SEric.Saxe@Sun.COM 68*8906SEric.Saxe@Sun.COM if (!(mach_state->ms_caps & CPUPM_P_STATES)) 69*8906SEric.Saxe@Sun.COM return (DDI_FAILURE); 70*8906SEric.Saxe@Sun.COM ASSERT(mach_state->ms_pstate.cma_ops != NULL); 71*8906SEric.Saxe@Sun.COM cpupm = &(cpudsp->cpudrv_pm); 72*8906SEric.Saxe@Sun.COM plat_level = PM_2_PLAT_LEVEL(cpupm, new_spd->pm_level); 73*8906SEric.Saxe@Sun.COM CPUSET_ONLY(set, cp->cpu_id); 74*8906SEric.Saxe@Sun.COM mach_state->ms_pstate.cma_ops->cpus_change(set, plat_level); 75*8906SEric.Saxe@Sun.COM 76*8906SEric.Saxe@Sun.COM return (DDI_SUCCESS); 77*8906SEric.Saxe@Sun.COM } 78*8906SEric.Saxe@Sun.COM 79*8906SEric.Saxe@Sun.COM /* 80*8906SEric.Saxe@Sun.COM * Determine the cpu_id for the CPU device. 81*8906SEric.Saxe@Sun.COM */ 82*8906SEric.Saxe@Sun.COM boolean_t 83*8906SEric.Saxe@Sun.COM cpudrv_get_cpu_id(dev_info_t *dip, processorid_t *cpu_id) 84*8906SEric.Saxe@Sun.COM { 85*8906SEric.Saxe@Sun.COM return ((*cpu_id = ddi_prop_get_int(DDI_DEV_T_ANY, dip, 86*8906SEric.Saxe@Sun.COM DDI_PROP_DONTPASS, "reg", -1)) != -1); 87*8906SEric.Saxe@Sun.COM 88*8906SEric.Saxe@Sun.COM } 89*8906SEric.Saxe@Sun.COM 90*8906SEric.Saxe@Sun.COM boolean_t 91*8906SEric.Saxe@Sun.COM cpudrv_is_enabled(cpudrv_devstate_t *cpudsp) 92*8906SEric.Saxe@Sun.COM { 93*8906SEric.Saxe@Sun.COM cpupm_mach_state_t *mach_state; 94*8906SEric.Saxe@Sun.COM 95*8906SEric.Saxe@Sun.COM if (!cpupm_is_enabled(CPUPM_P_STATES) || !cpudrv_enabled) 96*8906SEric.Saxe@Sun.COM return (B_FALSE); 97*8906SEric.Saxe@Sun.COM 98*8906SEric.Saxe@Sun.COM /* 99*8906SEric.Saxe@Sun.COM * Only check the instance specific setting it exists. 100*8906SEric.Saxe@Sun.COM */ 101*8906SEric.Saxe@Sun.COM if (cpudsp != NULL && cpudsp->cp != NULL && 102*8906SEric.Saxe@Sun.COM cpudsp->cp->cpu_m.mcpu_pm_mach_state != NULL) { 103*8906SEric.Saxe@Sun.COM mach_state = 104*8906SEric.Saxe@Sun.COM (cpupm_mach_state_t *)cpudsp->cp->cpu_m.mcpu_pm_mach_state; 105*8906SEric.Saxe@Sun.COM return (mach_state->ms_caps & CPUPM_P_STATES); 106*8906SEric.Saxe@Sun.COM } 107*8906SEric.Saxe@Sun.COM 108*8906SEric.Saxe@Sun.COM return (B_TRUE); 109*8906SEric.Saxe@Sun.COM } 110*8906SEric.Saxe@Sun.COM 111*8906SEric.Saxe@Sun.COM /* 112*8906SEric.Saxe@Sun.COM * Is the current thread the thread that is handling the 113*8906SEric.Saxe@Sun.COM * PPC change notification? 114*8906SEric.Saxe@Sun.COM */ 115*8906SEric.Saxe@Sun.COM boolean_t 116*8906SEric.Saxe@Sun.COM cpudrv_is_governor_thread(cpudrv_pm_t *cpupm) 117*8906SEric.Saxe@Sun.COM { 118*8906SEric.Saxe@Sun.COM return (curthread == cpupm->pm_governor_thread); 119*8906SEric.Saxe@Sun.COM } 120*8906SEric.Saxe@Sun.COM 121*8906SEric.Saxe@Sun.COM /* 122*8906SEric.Saxe@Sun.COM * This routine changes the top speed to which the CPUs can transition by: 123*8906SEric.Saxe@Sun.COM * 124*8906SEric.Saxe@Sun.COM * - Resetting the up_spd for all speeds lower than the new top speed 125*8906SEric.Saxe@Sun.COM * to point to the new top speed. 126*8906SEric.Saxe@Sun.COM * - Updating the framework with a new "normal" (maximum power) for this 127*8906SEric.Saxe@Sun.COM * device. 128*8906SEric.Saxe@Sun.COM */ 129*8906SEric.Saxe@Sun.COM void 130*8906SEric.Saxe@Sun.COM cpudrv_set_topspeed(void *ctx, int plat_level) 131*8906SEric.Saxe@Sun.COM { 132*8906SEric.Saxe@Sun.COM cpudrv_devstate_t *cpudsp; 133*8906SEric.Saxe@Sun.COM cpudrv_pm_t *cpupm; 134*8906SEric.Saxe@Sun.COM cpudrv_pm_spd_t *spd; 135*8906SEric.Saxe@Sun.COM cpudrv_pm_spd_t *top_spd; 136*8906SEric.Saxe@Sun.COM dev_info_t *dip; 137*8906SEric.Saxe@Sun.COM int pm_level; 138*8906SEric.Saxe@Sun.COM int instance; 139*8906SEric.Saxe@Sun.COM int i; 140*8906SEric.Saxe@Sun.COM 141*8906SEric.Saxe@Sun.COM dip = ctx; 142*8906SEric.Saxe@Sun.COM instance = ddi_get_instance(dip); 143*8906SEric.Saxe@Sun.COM cpudsp = ddi_get_soft_state(cpudrv_state, instance); 144*8906SEric.Saxe@Sun.COM ASSERT(cpudsp != NULL); 145*8906SEric.Saxe@Sun.COM 146*8906SEric.Saxe@Sun.COM mutex_enter(&cpudsp->lock); 147*8906SEric.Saxe@Sun.COM cpupm = &(cpudsp->cpudrv_pm); 148*8906SEric.Saxe@Sun.COM pm_level = PLAT_2_PM_LEVEL(cpupm, plat_level); 149*8906SEric.Saxe@Sun.COM for (i = 0, spd = cpupm->head_spd; spd; i++, spd = spd->down_spd) { 150*8906SEric.Saxe@Sun.COM /* 151*8906SEric.Saxe@Sun.COM * Don't mess with speeds that are higher than the new 152*8906SEric.Saxe@Sun.COM * top speed. They should be out of range anyway. 153*8906SEric.Saxe@Sun.COM */ 154*8906SEric.Saxe@Sun.COM if (spd->pm_level > pm_level) 155*8906SEric.Saxe@Sun.COM continue; 156*8906SEric.Saxe@Sun.COM /* 157*8906SEric.Saxe@Sun.COM * This is the new top speed. 158*8906SEric.Saxe@Sun.COM */ 159*8906SEric.Saxe@Sun.COM if (spd->pm_level == pm_level) 160*8906SEric.Saxe@Sun.COM top_spd = spd; 161*8906SEric.Saxe@Sun.COM 162*8906SEric.Saxe@Sun.COM spd->up_spd = top_spd; 163*8906SEric.Saxe@Sun.COM } 164*8906SEric.Saxe@Sun.COM cpupm->top_spd = top_spd; 165*8906SEric.Saxe@Sun.COM 166*8906SEric.Saxe@Sun.COM cpupm->pm_governor_thread = curthread; 167*8906SEric.Saxe@Sun.COM 168*8906SEric.Saxe@Sun.COM mutex_exit(&cpudsp->lock); 169*8906SEric.Saxe@Sun.COM 170*8906SEric.Saxe@Sun.COM (void) pm_update_maxpower(dip, 0, top_spd->pm_level); 171*8906SEric.Saxe@Sun.COM } 172*8906SEric.Saxe@Sun.COM 173*8906SEric.Saxe@Sun.COM /* 174*8906SEric.Saxe@Sun.COM * This routine reads the ACPI _PPC object. It's accessed as a callback 175*8906SEric.Saxe@Sun.COM * by the ppm driver whenever a _PPC change notification is received. 176*8906SEric.Saxe@Sun.COM */ 177*8906SEric.Saxe@Sun.COM int 178*8906SEric.Saxe@Sun.COM cpudrv_get_topspeed(void *ctx) 179*8906SEric.Saxe@Sun.COM { 180*8906SEric.Saxe@Sun.COM cpu_t *cp; 181*8906SEric.Saxe@Sun.COM cpudrv_devstate_t *cpudsp; 182*8906SEric.Saxe@Sun.COM dev_info_t *dip; 183*8906SEric.Saxe@Sun.COM int instance; 184*8906SEric.Saxe@Sun.COM int plat_level; 185*8906SEric.Saxe@Sun.COM 186*8906SEric.Saxe@Sun.COM dip = ctx; 187*8906SEric.Saxe@Sun.COM instance = ddi_get_instance(dip); 188*8906SEric.Saxe@Sun.COM cpudsp = ddi_get_soft_state(cpudrv_state, instance); 189*8906SEric.Saxe@Sun.COM ASSERT(cpudsp != NULL); 190*8906SEric.Saxe@Sun.COM cp = cpudsp->cp; 191*8906SEric.Saxe@Sun.COM plat_level = cpupm_get_top_speed(cp); 192*8906SEric.Saxe@Sun.COM 193*8906SEric.Saxe@Sun.COM return (plat_level); 194*8906SEric.Saxe@Sun.COM } 195*8906SEric.Saxe@Sun.COM 196*8906SEric.Saxe@Sun.COM 197*8906SEric.Saxe@Sun.COM /* 198*8906SEric.Saxe@Sun.COM * This notification handler is called whenever the ACPI _PPC 199*8906SEric.Saxe@Sun.COM * object changes. The _PPC is a sort of governor on power levels. 200*8906SEric.Saxe@Sun.COM * It sets an upper threshold on which, _PSS defined, power levels 201*8906SEric.Saxe@Sun.COM * are usuable. The _PPC value is dynamic and may change as properties 202*8906SEric.Saxe@Sun.COM * (i.e., thermal or AC source) of the system change. 203*8906SEric.Saxe@Sun.COM */ 204*8906SEric.Saxe@Sun.COM /* ARGSUSED */ 205*8906SEric.Saxe@Sun.COM static void 206*8906SEric.Saxe@Sun.COM cpudrv_notify_handler(ACPI_HANDLE obj, UINT32 val, void *ctx) 207*8906SEric.Saxe@Sun.COM { 208*8906SEric.Saxe@Sun.COM extern pm_cpupm_t cpupm; 209*8906SEric.Saxe@Sun.COM 210*8906SEric.Saxe@Sun.COM /* 211*8906SEric.Saxe@Sun.COM * We only handle _PPC change notifications. 212*8906SEric.Saxe@Sun.COM */ 213*8906SEric.Saxe@Sun.COM if (val == CPUPM_PPC_CHANGE_NOTIFICATION && !PM_EVENT_CPUPM) 214*8906SEric.Saxe@Sun.COM cpudrv_redefine_topspeed(ctx); 215*8906SEric.Saxe@Sun.COM } 216*8906SEric.Saxe@Sun.COM 217*8906SEric.Saxe@Sun.COM void 218*8906SEric.Saxe@Sun.COM cpudrv_install_notify_handler(cpudrv_devstate_t *cpudsp) 219*8906SEric.Saxe@Sun.COM { 220*8906SEric.Saxe@Sun.COM cpu_t *cp = cpudsp->cp; 221*8906SEric.Saxe@Sun.COM cpupm_add_notify_handler(cp, cpudrv_notify_handler, 222*8906SEric.Saxe@Sun.COM cpudsp->dip); 223*8906SEric.Saxe@Sun.COM } 224*8906SEric.Saxe@Sun.COM 225*8906SEric.Saxe@Sun.COM void 226*8906SEric.Saxe@Sun.COM cpudrv_redefine_topspeed(void *ctx) 227*8906SEric.Saxe@Sun.COM { 228*8906SEric.Saxe@Sun.COM /* 229*8906SEric.Saxe@Sun.COM * This should never happen, unless ppm does not get loaded. 230*8906SEric.Saxe@Sun.COM */ 231*8906SEric.Saxe@Sun.COM if (cpupm_redefine_topspeed == NULL) { 232*8906SEric.Saxe@Sun.COM cmn_err(CE_WARN, "cpudrv_redefine_topspeed: " 233*8906SEric.Saxe@Sun.COM "cpupm_redefine_topspeed has not been initialized - " 234*8906SEric.Saxe@Sun.COM "ignoring notification"); 235*8906SEric.Saxe@Sun.COM return; 236*8906SEric.Saxe@Sun.COM } 237*8906SEric.Saxe@Sun.COM 238*8906SEric.Saxe@Sun.COM /* 239*8906SEric.Saxe@Sun.COM * ppm callback needs to handle redefinition for all CPUs in 240*8906SEric.Saxe@Sun.COM * the domain. 241*8906SEric.Saxe@Sun.COM */ 242*8906SEric.Saxe@Sun.COM (*cpupm_redefine_topspeed)(ctx); 243*8906SEric.Saxe@Sun.COM } 244*8906SEric.Saxe@Sun.COM 245*8906SEric.Saxe@Sun.COM boolean_t 246*8906SEric.Saxe@Sun.COM cpudrv_mach_init(cpudrv_devstate_t *cpudsp) 247*8906SEric.Saxe@Sun.COM { 248*8906SEric.Saxe@Sun.COM cpupm_mach_state_t *mach_state; 249*8906SEric.Saxe@Sun.COM 250*8906SEric.Saxe@Sun.COM mutex_enter(&cpu_lock); 251*8906SEric.Saxe@Sun.COM cpudsp->cp = cpu_get(cpudsp->cpu_id); 252*8906SEric.Saxe@Sun.COM mutex_exit(&cpu_lock); 253*8906SEric.Saxe@Sun.COM if (cpudsp->cp == NULL) { 254*8906SEric.Saxe@Sun.COM cmn_err(CE_WARN, "cpudrv_mach_pm_init: instance %d: " 255*8906SEric.Saxe@Sun.COM "can't get cpu_t", ddi_get_instance(cpudsp->dip)); 256*8906SEric.Saxe@Sun.COM return (B_FALSE); 257*8906SEric.Saxe@Sun.COM } 258*8906SEric.Saxe@Sun.COM 259*8906SEric.Saxe@Sun.COM mach_state = (cpupm_mach_state_t *) 260*8906SEric.Saxe@Sun.COM (cpudsp->cp->cpu_m.mcpu_pm_mach_state); 261*8906SEric.Saxe@Sun.COM mach_state->ms_dip = cpudsp->dip; 262*8906SEric.Saxe@Sun.COM return (B_TRUE); 263*8906SEric.Saxe@Sun.COM } 264*8906SEric.Saxe@Sun.COM 265*8906SEric.Saxe@Sun.COM uint_t 266*8906SEric.Saxe@Sun.COM cpudrv_get_speeds(cpudrv_devstate_t *cpudsp, int **speeds) 267*8906SEric.Saxe@Sun.COM { 268*8906SEric.Saxe@Sun.COM return (cpupm_get_speeds(cpudsp->cp, speeds)); 269*8906SEric.Saxe@Sun.COM } 270*8906SEric.Saxe@Sun.COM 271*8906SEric.Saxe@Sun.COM void 272*8906SEric.Saxe@Sun.COM cpudrv_free_speeds(int *speeds, uint_t nspeeds) 273*8906SEric.Saxe@Sun.COM { 274*8906SEric.Saxe@Sun.COM cpupm_free_speeds(speeds, nspeeds); 275*8906SEric.Saxe@Sun.COM } 276*8906SEric.Saxe@Sun.COM 277*8906SEric.Saxe@Sun.COM boolean_t 278*8906SEric.Saxe@Sun.COM cpudrv_power_ready(void) 279*8906SEric.Saxe@Sun.COM { 280*8906SEric.Saxe@Sun.COM return (cpupm_power_ready()); 281*8906SEric.Saxe@Sun.COM } 282*8906SEric.Saxe@Sun.COM 283*8906SEric.Saxe@Sun.COM /* ARGSUSED */ 284*8906SEric.Saxe@Sun.COM void 285*8906SEric.Saxe@Sun.COM cpudrv_set_supp_freqs(cpudrv_devstate_t *cpudsp) 286*8906SEric.Saxe@Sun.COM { 287*8906SEric.Saxe@Sun.COM } 288