ppm.c revision e7147e44195a7880e2fcbb05656dfa2407339b0a
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2005 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* Platform Power Management master pseudo driver -
* - attaches only when ppm.conf file is present, indicating a
* workstation (since Excalibur era ) that is designed to
* be MOU-3 EPA compliant and which uses platform-specific
* hardware to do so;
* - this pseudo driver uses a set of simple satellite
* device drivers responsible for accessing platform
* specific devices to modify the registers they own.
* ppm drivers tells these satellite drivers what to do
* according to using command values taken from ppm.conf.
*/
#include <sys/conf.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/open.h>
#include <sys/callb.h>
#include <sys/va_list.h>
#include <sys/errno.h>
#include <sys/modctl.h>
#include <sys/sysmacros.h>
#include <sys/ddi_impldefs.h>
#include <sys/promif.h>
#include <sys/epm.h>
#include <sys/sunpm.h>
#include <sys/ppmio.h>
#include <sys/sunldi.h>
#include <sys/ppmvar.h>
#include <sys/i2c/clients/i2c_gpio.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
/*
* Note: When pm_power() is called (directly or indirectly) to change the
* power level of a device and the call returns failure, DO NOT assume the
* level is unchanged. Doublecheck it against ppmd->level.
*/
/*
* cb_ops
*/
static int ppm_open(dev_t *, int, int, cred_t *);
static int ppm_close(dev_t, int, int, cred_t *);
static int ppm_ioctl(dev_t, int, intptr_t, int, cred_t *, int *);
static struct cb_ops ppm_cb_ops = {
ppm_open, /* open */
ppm_close, /* close */
nodev, /* strategy */
nodev, /* print */
nodev, /* dump */
nodev, /* read */
nodev, /* write */
ppm_ioctl, /* ioctl */
nodev, /* devmap */
nodev, /* mmap */
nodev, /* segmap */
nochpoll, /* poll */
ddi_prop_op, /* prop_op */
NULL, /* streamtab */
D_MP | D_NEW, /* driver compatibility flag */
CB_REV, /* cb_ops revision */
nodev, /* async read */
nodev /* async write */
};
/*
* bus_ops
*/
static int ppm_ctlops(dev_info_t *, dev_info_t *, ddi_ctl_enum_t, void *,
void *);
static struct bus_ops ppm_bus_ops = {
BUSO_REV, /* busops_rev */
0, /* bus_map */
0, /* bus_get_intrspec */
0, /* bus_add_intrspec */
0, /* bus_remove_intrspec */
0, /* bus_map_fault */
ddi_no_dma_map, /* bus_dma_map */
ddi_no_dma_allochdl, /* bus_dma_allochdl */
NULL, /* bus_dma_freehdl */
NULL, /* bus_dma_bindhdl */
NULL, /* bus_dma_unbindhdl */
NULL, /* bus_dma_flush */
NULL, /* bus_dma_win */
NULL, /* bus_dma_ctl */
ppm_ctlops, /* bus_ctl */
0, /* bus_prop_op */
0, /* bus_get_eventcookie */
0, /* bus_add_eventcall */
0, /* bus_remove_eventcall */
0, /* bus_post_event */
0 /* bus_intr_ctl */
};
/*
* dev_ops
*/
static int ppm_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **);
static int ppm_attach(dev_info_t *, ddi_attach_cmd_t);
static int ppm_detach(dev_info_t *, ddi_detach_cmd_t);
static struct dev_ops ppm_ops = {
DEVO_REV, /* devo_rev */
0, /* refcnt */
ppm_getinfo, /* info */
nulldev, /* identify */
nulldev, /* probe */
ppm_attach, /* attach */
ppm_detach, /* detach */
nodev, /* reset */
&ppm_cb_ops, /* cb_ops */
&ppm_bus_ops, /* bus_ops */
nulldev /* power */
};
extern struct mod_ops mod_driverops;
static struct modldrv modldrv = {
&mod_driverops,
"platform pm driver v%I%",
&ppm_ops
};
static struct modlinkage modlinkage = {
MODREV_1,
&modldrv,
NULL
};
/*
* Global data structure and variables
*/
int ppm_inst = -1;
void *ppm_statep;
ppm_domain_t *ppm_domain_p;
callb_id_t *ppm_cprcb_id;
static kmutex_t ppm_cpr_window_lock; /* guard ppm_cpr_window_flag */
static boolean_t ppm_cpr_window_flag; /* set indicating chpt-resume period */
/* LED actions */
#define PPM_LED_SOLIDON 0
#define PPM_LED_BLINKING 1
/*
* Debug
*/
#ifdef DEBUG
uint_t ppm_debug = 0;
#endif
/*
* This flag disables vcore/vid feature by default.
*/
uint_t ppm_do_vcore = 0;
/*
* Local function prototypes and data
*/
static boolean_t ppm_cpr_callb(void *, int);
static int ppm_fetset(ppm_domain_t *, uint8_t);
static int ppm_fetget(ppm_domain_t *, uint8_t *);
static int ppm_gpioset(ppm_domain_t *, int);
static int ppm_manage_cpus(dev_info_t *, power_req_t *, int *);
static int ppm_change_cpu_power(ppm_dev_t *, int);
static int ppm_revert_cpu_power(ppm_dev_t *, int);
static int ppm_manage_pci(dev_info_t *, power_req_t *, int *);
static int ppm_manage_pcie(dev_info_t *, power_req_t *, int *);
static int ppm_manage_fet(dev_info_t *, power_req_t *, int *);
static void ppm_manage_led(int);
static void ppm_set_led(ppm_domain_t *, int);
static void ppm_blink_led(void *);
static void ppm_svc_resume_ctlop(dev_info_t *, power_req_t *);
static int ppm_set_level(ppm_dev_t *, int, int, boolean_t);
static int ppm_change_power_level(ppm_dev_t *, int, int);
static int ppm_record_level_change(ppm_dev_t *, int, int);
static int ppm_switch_clock(ppm_domain_t *, int);
static int ppm_pcie_pwr(ppm_domain_t *, int);
static int ppm_power_up_domain(dev_info_t *dip);
static int ppm_power_down_domain(dev_info_t *dip);
int
_init(void)
{
if (ddi_soft_state_init(
&ppm_statep, sizeof (ppm_unit_t), 1) != DDI_SUCCESS)
return (DDI_FAILURE);
if (mod_install(&modlinkage) != DDI_SUCCESS) {
ddi_soft_state_fini(&ppm_statep);
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
int
_fini(void)
{
return (mod_remove(&modlinkage));
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
/* ARGSUSED */
int
ppm_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **resultp)
{
struct ppm_unit *unitp;
dev_t dev;
int instance;
int rval;
if (ppm_inst == -1)
return (DDI_FAILURE);
switch (cmd) {
case DDI_INFO_DEVT2DEVINFO:
if (unitp = ddi_get_soft_state(ppm_statep, (dev_t)arg)) {
*resultp = unitp->dip;
rval = DDI_SUCCESS;
} else
rval = DDI_FAILURE;
return (rval);
case DDI_INFO_DEVT2INSTANCE:
dev = (dev_t)arg;
instance = getminor(dev);
*resultp = (void *)(uintptr_t)instance;
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
}
/*
* attach(9E)
*/
static int
ppm_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
ppm_unit_t *unitp;
int ret;
#ifdef DEBUG
char *str = "ppm_attach";
#endif
switch (cmd) {
case DDI_ATTACH:
PPMD(D_ATTACH, ("%s: attaching ...\n", str))
break;
case DDI_RESUME:
PPMD(D_ATTACH, ("%s: Resuming ...\n", str))
unitp = ddi_get_soft_state(ppm_statep, ppm_inst);
mutex_enter(&unitp->lock);
unitp->states &= ~PPM_STATE_SUSPENDED;
mutex_exit(&unitp->lock);
return (DDI_SUCCESS);
default:
cmn_err(CE_WARN, "ppm_attach: unknown command %d, dip(0x%p)",
cmd, (void *)dip);
return (DDI_FAILURE);
}
if (ppm_inst != -1) {
PPMD(D_ATTACH, ("%s: Already attached !", str))
return (DDI_FAILURE);
}
ppm_inst = ddi_get_instance(dip);
if (ddi_soft_state_zalloc(ppm_statep, ppm_inst) != DDI_SUCCESS) {
PPMD(D_ATTACH, ("%s: soft states alloc error!\n", str))
return (DDI_FAILURE);
}
unitp = ddi_get_soft_state(ppm_statep, ppm_inst);
ret = ddi_create_minor_node(dip, "ppm", S_IFCHR, ppm_inst,
"ddi_ppm", 0);
if (ret != DDI_SUCCESS) {
PPMD(D_ATTACH, ("%s: can't create minor node!\n", str))
goto fail1;
}
unitp->dip = dip;
mutex_init(&unitp->lock, NULL, MUTEX_DRIVER, NULL);
/*
* read ppm.conf, construct ppm_domain data structure and
* their sub data structure.
*/
if ((ret = ppm_create_db(dip)) != DDI_SUCCESS)
goto fail2;
/*
* walk down ppm domain control from each domain, initialize
* domain control orthogonal function call handle
*/
ppm_init_cb(dip);
if ((ret = pm_register_ppm(ppm_claim_dev, dip)) != DDI_SUCCESS) {
cmn_err(CE_WARN, "ppm_attach: can't register ppm handler!");
goto fail2;
}
mutex_init(&ppm_cpr_window_lock, NULL, MUTEX_DRIVER, NULL);
ppm_cpr_window_flag = B_FALSE;
ppm_cprcb_id = callb_add(ppm_cpr_callb, (void *)NULL,
CB_CL_CPR_PM, "ppm_cpr");
ddi_report_dev(dip);
return (DDI_SUCCESS);
fail2:
ddi_remove_minor_node(dip, "ddi_ppm");
mutex_destroy(&unitp->lock);
fail1:
ddi_soft_state_free(ppm_statep, ppm_inst);
ppm_inst = -1;
return (DDI_FAILURE);
}
/* ARGSUSED */
static int
ppm_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
ppm_unit_t *unitp;
#ifdef DEBUG
char *str = "ppm_detach";
#endif
switch (cmd) {
case DDI_DETACH:
PPMD(D_DETACH, ("%s: detach not allowed.\n", str))
return (DDI_FAILURE);
case DDI_SUSPEND:
PPMD(D_DETACH, ("%s: suspending ...\n", str))
unitp = ddi_get_soft_state(ppm_statep, ppm_inst);
mutex_enter(&unitp->lock);
unitp->states |= PPM_STATE_SUSPENDED;
mutex_exit(&unitp->lock);
/*
* Suspend requires that timeout callouts to be canceled.
* Turning off the LED blinking will cancel the timeout.
*/
ppm_manage_led(PPM_LED_SOLIDON);
return (DDI_SUCCESS);
default:
cmn_err(CE_WARN, "ppm_detach: unsupported command %d, dip(%p)",
cmd, (void *)dip);
return (DDI_FAILURE);
}
}
/* ARGSUSED */
int
ppm_open(dev_t *devp, int flag, int otyp, cred_t *cred_p)
{
if (otyp != OTYP_CHR)
return (EINVAL);
PPMD(D_OPEN, ("ppm_open: devp 0x%p, flag 0x%x, otyp %d\n",
(void *)devp, flag, otyp))
return (0);
}
/* ARGSUSED */
int
ppm_close(dev_t dev, int flag, int otyp, cred_t *credp)
{
PPMD(D_CLOSE, ("ppm_close: dev 0x%lx, flag 0x%x, otyp %d\n",
dev, flag, otyp))
return (0);
}
/* ARGSUSED */
int
ppm_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *cred_p,
int *rval_p)
{
#ifdef DEBUG
char *str = "ppm_ioctl";
#endif
ppm_domain_t *domp = NULL;
uint8_t level, lvl;
int ret = 0;
PPMD(D_IOCTL, ("%s: dev 0x%lx, cmd 0x%x, mode 0x%x\n",
str, dev, cmd, mode))
switch (cmd) {
case PPMGET_DPWR:
{
STRUCT_DECL(ppm_dpwr, dpwr);
struct ppm_unit *unitp;
char *domain;
STRUCT_INIT(dpwr, mode);
ret = ddi_copyin((caddr_t)arg, STRUCT_BUF(dpwr),
STRUCT_SIZE(dpwr), mode);
if (ret != 0)
return (EFAULT);
/* copyin domain name */
domain = kmem_zalloc(MAXNAMELEN, KM_SLEEP);
ret = copyinstr(
STRUCT_FGETP(dpwr, domain), domain, MAXNAMELEN, NULL);
if (ret != 0) {
PPMD(D_IOCTL, ("%s: can't copyin domain, line(%d)\n",
str, __LINE__))
ret = EFAULT;
goto err_dpwr;
}
/* locate domain */
if ((domp = ppm_lookup_domain(domain)) == NULL) {
PPMD(D_IOCTL, ("%s: no such domain %s\n", str, domain))
ret = ENODEV;
goto err_dpwr;
}
switch (domp->model) {
case PPMD_FET: /* report power fet ON or OFF */
if ((ret = ppm_fetget(domp, &lvl)) != 0) {
ret = EIO;
goto err_dpwr;
}
level = (lvl == PPMD_ON) ?
PPMIO_POWER_ON : PPMIO_POWER_OFF;
break;
case PPMD_PCI: /* report pci slot clock ON or OFF */
case PPMD_PCI_PROP:
case PPMD_PCIE:
level = (domp->status == PPMD_ON) ?
PPMIO_POWER_ON : PPMIO_POWER_OFF;
break;
case PPMD_LED: /* report LED blinking or solid on */
unitp = ddi_get_soft_state(ppm_statep, ppm_inst);
if (unitp->led_tid == 0)
level = PPMIO_LED_SOLIDON;
else
level = PPMIO_LED_BLINKING;
break;
case PPMD_CPU: /* report cpu speed divisor */
level = domp->devlist->level;
break;
default:
ret = EINVAL;
goto err_dpwr;
}
STRUCT_FSET(dpwr, level, level);
ret = ddi_copyout(STRUCT_BUF(dpwr), (caddr_t)arg,
STRUCT_SIZE(dpwr), mode);
if (ret != 0) {
PPMD(D_IOCTL, ("%s: can't copyout, line(%d)\n",
str, __LINE__))
ret = EFAULT;
}
err_dpwr:
kmem_free(domain, MAXNAMELEN);
break;
}
case PPMGET_DOMBYDEV:
{
STRUCT_DECL(ppm_bydev, bydev);
char *path = NULL;
size_t size, l;
STRUCT_INIT(bydev, mode);
ret = ddi_copyin((caddr_t)arg, STRUCT_BUF(bydev),
STRUCT_SIZE(bydev), mode);
if (ret != 0)
return (EFAULT);
/* copyin .path */
path = kmem_zalloc(MAXPATHLEN, KM_SLEEP);
ret = copyinstr(
STRUCT_FGETP(bydev, path), path, MAXPATHLEN, NULL);
if (ret != 0) {
PPMD(D_IOCTL, ("%s: can't copyin path, line(%d)\n",
str, __LINE__))
kmem_free(path, MAXPATHLEN);
return (EFAULT);
}
/* so far we have up to one domain for a given device */
size = STRUCT_FGET(bydev, size);
domp = ppm_get_domain_by_dev(path);
kmem_free(path, MAXPATHLEN);
if (domp != NULL) {
l = strlen(domp->name) + 1;
if (l > size) {
PPMD(D_IOCTL, ("%s: buffer too small\n", str))
return ((size == 0) ? EINVAL : EFAULT);
}
} else /* no domain found to be associated with given device */
return (ENODEV);
ret = copyoutstr(
domp->name, STRUCT_FGETP(bydev, domlist), l, &l);
if (ret != 0) {
PPMD(D_IOCTL, ("%s: can't copyout domlist, line(%d)"
" \n", str, __LINE__))
return (EFAULT);
}
break;
}
case PPMGET_DEVBYDOM:
{
STRUCT_DECL(ppm_bydom, bydom);
char *domain = NULL;
char *devlist = NULL;
ppm_dev_t *ppmd;
dev_info_t *odip = NULL;
char *s, *d;
size_t size, l;
STRUCT_INIT(bydom, mode);
ret = ddi_copyin((caddr_t)arg, STRUCT_BUF(bydom),
STRUCT_SIZE(bydom), mode);
if (ret != 0)
return (EFAULT);
/* copyin .domain */
domain = kmem_zalloc(MAXNAMELEN, KM_SLEEP);
ret = copyinstr(STRUCT_FGETP(bydom, domain), domain,
MAXNAMELEN, NULL);
if (ret != 0) {
PPMD(D_IOCTL, ("%s: can't copyin domain, line(%d)\n",
str, __LINE__))
ret = EFAULT;
goto err_bydom;
}
/* locate domain */
if ((domp = ppm_lookup_domain(domain)) == NULL) {
ret = ENODEV;
goto err_bydom;
}
l = 0;
if ((size = STRUCT_FGET(bydom, size)) == 0)
ret = EINVAL;
else
if ((d = devlist = kmem_zalloc(size, KM_SLEEP)) == NULL)
ret = EFAULT;
if (ret != 0)
goto err_bydom;
for (ppmd = domp->devlist; ppmd;
odip = ppmd->dip, ppmd = ppmd->next) {
if (ppmd->dip == odip)
continue;
if (ppmd != domp->devlist)
*d++ = ' ';
l += strlen(ppmd->path) + 1;
if (l > size) {
PPMD(D_IOCTL, ("%s: buffer overflow\n", str))
ret = EFAULT;
goto err_bydom;
}
for (s = ppmd->path; *s != 0; )
*d++ = *s++;
}
*d = 0;
if (*devlist == 0)
goto err_bydom;
ret = copyoutstr(
devlist, STRUCT_FGETP(bydom, devlist), l, &l);
if (ret != 0) {
PPMD(D_IOCTL, ("%s: can't copyout devlist, line(%d)"
" \n", str, __LINE__))
ret = EFAULT;
}
err_bydom:
if (devlist)
kmem_free(devlist, size);
if (domain)
kmem_free(domain, MAXNAMELEN);
break;
}
default:
PPMD(D_IOCTL, ("%s: unsupported ioctl command(%d)\n", str, cmd))
return (EINVAL);
}
return (ret);
}
/*
* interface between pm framework and ppm driver
*/
/* ARGSUSED */
static int
ppm_ctlops(dev_info_t *dip, dev_info_t *rdip,
ddi_ctl_enum_t ctlop, void *arg, void *result)
{
power_req_t *reqp = (power_req_t *)arg;
ppm_unit_t *unitp;
ppm_domain_t *domp;
ppm_dev_t *ppmd;
char path[MAXNAMELEN];
ppm_owned_t *owned;
int mode;
int ret = DDI_SUCCESS;
#ifdef DEBUG
char *str = "ppm_ctlops";
int mask = ppm_debug & (D_CTLOPS1 | D_CTLOPS2);
char *ctlstr = ppm_get_ctlstr(reqp->request_type, mask);
if (mask && ctlstr)
PPMD(mask, ("%s: %s, %s\n",
str, ddi_binding_name(rdip), ctlstr))
#endif
if (ctlop != DDI_CTLOPS_POWER)
return (DDI_FAILURE);
unitp = (ppm_unit_t *)ddi_get_soft_state(ppm_statep, ppm_inst);
switch (reqp->request_type) {
/* attempt to blink led if indeed all at lowest */
case PMR_PPM_ALL_LOWEST:
mode = (reqp->req.ppm_all_lowest_req.mode == PM_ALL_LOWEST);
if (!(unitp->states & PPM_STATE_SUSPENDED) && mode)
ppm_manage_led(PPM_LED_BLINKING);
else
ppm_manage_led(PPM_LED_SOLIDON);
PPMD(D_LOWEST, ("%s: %sall devices are at lowest power \n",
str, mode ? "" : "not "))
return (DDI_SUCCESS);
/* undo the claiming of 'rdip' at attach time */
case PMR_PPM_POST_DETACH:
ASSERT(reqp->req.ppm_set_power_req.who == rdip);
mutex_enter(&unitp->lock);
if (reqp->req.ppm_config_req.result != DDI_SUCCESS ||
(PPM_GET_PRIVATE(rdip) == NULL)) {
mutex_exit(&unitp->lock);
return (DDI_FAILURE);
}
mutex_exit(&unitp->lock);
ppm_rem_dev(rdip);
return (DDI_SUCCESS);
/* chance to adjust pwr_cnt if resume is about to power up rdip */
case PMR_PPM_PRE_RESUME:
ppm_svc_resume_ctlop(rdip, reqp);
return (DDI_SUCCESS);
/*
* synchronizing, so that only the owner of the power lock is
* permitted to change device and component's power level.
*/
case PMR_PPM_UNLOCK_POWER:
case PMR_PPM_TRY_LOCK_POWER:
case PMR_PPM_LOCK_POWER:
ppmd = PPM_GET_PRIVATE(rdip);
if (ppmd)
domp = ppmd->domp;
else if (reqp->request_type != PMR_PPM_UNLOCK_POWER) {
domp = ppm_lookup_dev(rdip);
ASSERT(domp);
ppmd = ppm_get_dev(rdip, domp);
}
PPMD(D_LOCKS, ("ppm_lock_%s: %s, %s\n",
(domp->dflags & PPMD_LOCK_ALL) ? "all" : "one",
ppmd->path, ppm_get_ctlstr(reqp->request_type, D_LOCKS)))
if (domp->dflags & PPMD_LOCK_ALL)
ppm_lock_all(domp, reqp, result);
else
ppm_lock_one(ppmd, reqp, result);
return (DDI_SUCCESS);
case PMR_PPM_POWER_LOCK_OWNER:
ASSERT(reqp->req.ppm_power_lock_owner_req.who == rdip);
ppmd = PPM_GET_PRIVATE(rdip);
if (ppmd)
domp = ppmd->domp;
else {
domp = ppm_lookup_dev(rdip);
ASSERT(domp);
ppmd = ppm_get_dev(rdip, domp);
}
/*
* In case of LOCK_ALL, effective owner of the power lock
* is the owner of the domain lock. otherwise, it is the owner
* of the power lock.
*/
if (domp->dflags & PPMD_LOCK_ALL)
reqp->req.ppm_power_lock_owner_req.owner =
mutex_owner(&domp->lock);
else {
reqp->req.ppm_power_lock_owner_req.owner =
DEVI(rdip)->devi_busy_thread;
}
return (DDI_SUCCESS);
case PMR_PPM_INIT_CHILD:
ASSERT(reqp->req.ppm_lock_power_req.who == rdip);
if ((domp = ppm_lookup_dev(rdip)) == NULL)
return (DDI_SUCCESS);
/*
* We keep track of power-manageable devices starting with
* initialization process. The initializing flag remains
* set until it is cleared by ppm_add_dev(). Power management
* policy for some domains are affected even during device
* initialization. For example, PCI domains should leave
* their clock running meanwhile a device in that domain
* is initializing.
*/
mutex_enter(&domp->lock);
owned = ppm_add_owned(rdip, domp);
ASSERT(owned->initializing == 0);
owned->initializing = 1;
if (PPMD_IS_PCI(domp->model) && domp->status == PPMD_OFF) {
ret = ppm_switch_clock(domp, PPMD_ON);
if (ret == DDI_SUCCESS)
domp->dflags |= PPMD_INITCHILD_CLKON;
}
mutex_exit(&domp->lock);
return (ret);
case PMR_PPM_POST_ATTACH:
ASSERT(reqp->req.ppm_config_req.who == rdip);
domp = ppm_lookup_dev(rdip);
ASSERT(domp);
ASSERT(domp->status == PPMD_ON);
if (reqp->req.ppm_config_req.result == DDI_SUCCESS) {
/*
* call ppm_get_dev, which will increment the
* domain power count by the right number.
* Undo the power count increment, done in PRE_PROBE.
*/
if (PM_GET_PM_INFO(rdip))
ppmd = ppm_get_dev(rdip, domp);
mutex_enter(&domp->lock);
ASSERT(domp->pwr_cnt > 0);
domp->pwr_cnt--;
mutex_exit(&domp->lock);
return (DDI_SUCCESS);
}
ret = ppm_power_down_domain(rdip);
/* FALLTHROUGH */
case PMR_PPM_UNINIT_CHILD:
ASSERT(reqp->req.ppm_lock_power_req.who == rdip);
if ((domp = ppm_lookup_dev(rdip)) == NULL)
return (DDI_SUCCESS);
(void) ddi_pathname(rdip, path);
mutex_enter(&domp->lock);
for (owned = domp->owned; owned; owned = owned->next)
if (strcmp(owned->path, path) == 0)
break;
/*
* In case we didn't go through a complete attach and detach,
* the initializing flag will still be set, so clear it.
*/
if ((owned != NULL) && (owned->initializing))
owned->initializing = 0;
if (PPMD_IS_PCI(domp->model) &&
domp->status == PPMD_ON && domp->pwr_cnt == 0 &&
(domp->dflags & PPMD_INITCHILD_CLKON) &&
ppm_none_else_holds_power(domp)) {
ret = ppm_switch_clock(domp, PPMD_OFF);
if (ret == DDI_SUCCESS)
domp->dflags &= ~PPMD_INITCHILD_CLKON;
}
mutex_exit(&domp->lock);
return (ret);
/* place holders */
case PMR_PPM_UNMANAGE:
case PMR_PPM_PRE_DETACH:
return (DDI_SUCCESS);
case PMR_PPM_PRE_PROBE:
ASSERT(reqp->req.ppm_config_req.who == rdip);
return (ppm_power_up_domain(rdip));
case PMR_PPM_POST_PROBE:
ASSERT(reqp->req.ppm_config_req.who == rdip);
if (reqp->req.ppm_config_req.result == DDI_PROBE_SUCCESS ||
reqp->req.ppm_config_req.result == DDI_PROBE_DONTCARE)
return (DDI_SUCCESS);
/* Probe failed */
PPMD(D_CTLOPS1 | D_CTLOPS2, ("%s: probe failed for %s@%s "
"rv %d\n", str, PM_NAME(rdip), PM_ADDR(rdip),
reqp->req.ppm_config_req.result))
return (ppm_power_down_domain(rdip));
case PMR_PPM_PRE_ATTACH:
ASSERT(reqp->req.ppm_config_req.who == rdip);
/* Domain has already been powered up in PRE_PROBE */
domp = ppm_lookup_dev(rdip);
ASSERT(domp);
ASSERT(domp->status == PPMD_ON);
return (DDI_SUCCESS);
/* ppm intercepts power change process to the claimed devices */
case PMR_PPM_SET_POWER:
case PMR_PPM_POWER_CHANGE_NOTIFY:
if ((ppmd = PPM_GET_PRIVATE(rdip)) == NULL) {
domp = ppm_lookup_dev(rdip);
ASSERT(domp);
ppmd = ppm_get_dev(rdip, domp);
}
switch (ppmd->domp->model) {
case PPMD_CPU:
return (ppm_manage_cpus(rdip, reqp, result));
case PPMD_FET:
return (ppm_manage_fet(rdip, reqp, result));
case PPMD_PCI:
case PPMD_PCI_PROP:
return (ppm_manage_pci(rdip, reqp, result));
case PPMD_PCIE:
return (ppm_manage_pcie(rdip, reqp, result));
default:
cmn_err(CE_WARN, "ppm_ctlops: domain model %d does"
" not support PMR_PPM_SET_POWER ctlop",
ppmd->domp->model);
return (DDI_FAILURE);
}
default:
cmn_err(CE_WARN, "ppm_ctlops: unrecognized ctlops req(%d)",
reqp->request_type);
return (DDI_FAILURE);
}
}
/*
* PPMDC_CPU_NEXT operation
*/
int
ppm_cpu_next(ppm_domain_t *domp, int level)
{
#ifdef DEBUG
char *str = "ppm_cpu_next";
#endif
ppm_dc_t *dc;
int index = level - 1;
int ret = 0;
dc = ppm_lookup_dc(domp, PPMDC_CPU_NEXT);
for (; dc && (dc->cmd == PPMDC_CPU_NEXT); dc = dc->next) {
switch (dc->method) {
case PPMDC_CPUSPEEDKIO:
ret = ldi_ioctl(dc->lh, dc->m_un.cpu.iowr,
(intptr_t)index, FWRITE | FKIOCTL, kcred, NULL);
if (ret)
return (ret);
break;
default:
PPMD(D_CPU, ("%s: unsupported method(0x%x)\n",
str, dc->method))
return (-1);
}
}
return (ret);
}
/*
* PPMDC_PRE_CHNG operation
*/
int
ppm_cpu_pre_chng(ppm_domain_t *domp, int oldl, int speedup)
{
#ifdef DEBUG
char *str = "ppm_cpu_pre_chng";
#endif
ppm_dc_t *dc;
int lowest;
int ret = 0;
dc = ppm_lookup_dc(domp, PPMDC_PRE_CHNG);
for (; dc && (dc->cmd == PPMDC_PRE_CHNG); dc = dc->next) {
switch (dc->method) {
case PPMDC_VCORE:
lowest = domp->devlist->lowest;
if ((oldl != lowest) || (speedup != 1))
break;
/* raise core voltage */
if (ppm_do_vcore > 0) {
ret = ldi_ioctl(dc->lh,
dc->m_un.cpu.iowr,
(intptr_t)&dc->m_un.cpu.val,
FWRITE | FKIOCTL, kcred, NULL);
if (ret != 0)
return (ret);
if (dc->m_un.cpu.delay > 0)
drv_usecwait(dc->m_un.cpu.delay);
}
break;
default:
PPMD(D_CPU, ("%s: unsupported method(0x%x)\n",
str, dc->method))
return (-1);
}
}
return (ret);
}
/*
* PPMDC_CPU_GO operation
*/
/* ARGSUSED */
int
ppm_cpu_go(ppm_domain_t *domp, int level)
{
ppm_dc_t *dc;
int ret = 0;
dc = ppm_lookup_dc(domp, PPMDC_CPU_GO);
ASSERT(dc);
switch (dc->method) {
case PPMDC_KIO:
ret = ldi_ioctl(dc->lh, dc->m_un.kio.iowr,
(intptr_t)dc->m_un.kio.val, FWRITE | FKIOCTL,
kcred, NULL);
break;
default:
return (-1);
}
return (ret);
}
/*
* PPMDC_POST_CHNG operation
*/
int
ppm_cpu_post_chng(ppm_domain_t *domp, int newl, int speedup)
{
#ifdef DEBUG
char *str = "ppm_cpu_post_chng";
#endif
ppm_dc_t *dc;
int lowest;
int ret = 0;
dc = ppm_lookup_dc(domp, PPMDC_POST_CHNG);
for (; dc && (dc->cmd == PPMDC_POST_CHNG); dc = dc->next) {
switch (dc->method) {
case PPMDC_VCORE:
lowest = domp->devlist->lowest;
if ((newl != lowest) || (speedup != 0))
break;
/* lower core voltage */
if (ppm_do_vcore > 0) {
ret = ldi_ioctl(dc->lh,
dc->m_un.cpu.iowr,
(intptr_t)&dc->m_un.cpu.val,
FWRITE | FKIOCTL, kcred, NULL);
if (ret != 0)
return (ret);
if (dc->m_un.cpu.delay > 0)
drv_usecwait(dc->m_un.cpu.delay);
}
break;
default:
PPMD(D_CPU, ("%s: unsupported method(0x%x)\n",
str, dc->method))
return (-1);
}
}
return (ret);
}
/*
* The effective cpu estar model is: program all cpus to be ready to go
* the same next(or new) speed level, program all other system bus resident
* devices to the same next speed level. At last, pull the trigger to
* initiate the speed change for all system bus resident devices
* simultaneously.
*
* On Excalibur, the Safari bus resident devices are Cheetah/Cheetah+ and
* Schizo. On Enchilada, the JBus resident devides are Jalapeno(s) and
* Tomatillo(s).
*/
static int
ppm_change_cpu_power(ppm_dev_t *ppmd, int newlevel)
{
#ifdef DEBUG
char *str = "ppm_change_cpu_power";
#endif
ppm_unit_t *unitp;
ppm_domain_t *domp;
ppm_dev_t *cpup;
dev_info_t *dip;
int level, oldlevel;
int speedup, incr, lowest, highest;
char *chstr;
int ret;
unitp = ddi_get_soft_state(ppm_statep, ppm_inst);
ASSERT(unitp);
domp = ppmd->domp;
cpup = domp->devlist;
lowest = cpup->lowest;
highest = cpup->highest;
/*
* Not all cpus may have transitioned to a known level by this time
*/
oldlevel = (cpup->level == PM_LEVEL_UNKNOWN) ? highest : cpup->level;
dip = cpup->dip;
ASSERT(dip);
PPMD(D_CPU, ("%s: old %d, new %d, highest %d, lowest %d\n",
str, oldlevel, newlevel, highest, lowest))
if (newlevel > oldlevel) {
chstr = "UP";
speedup = 1;
incr = 1;
} else if (newlevel < oldlevel) {
chstr = "DOWN";
speedup = 0;
incr = -1;
} else
return (DDI_SUCCESS);
/*
* This loop will execute 1x or 2x depending on
* number of times we need to change clock rates
*/
for (level = oldlevel+incr; level != newlevel+incr; level += incr) {
/* bring each cpu to next level */
for (; cpup; cpup = cpup->next) {
if (cpup->level == level)
continue;
ret = pm_power(cpup->dip, 0, level);
PPMD(D_CPU, ("%s: \"%s\", %s to level %d, ret %d\n",
str, cpup->path, chstr, level, ret))
if (ret == DDI_SUCCESS) {
cpup->level = level;
cpup->rplvl = PM_LEVEL_UNKNOWN;
continue;
}
/*
* if the driver was unable to lower cpu speed,
* the cpu probably got busy; set the previous
* cpus back to the original level
*/
if (speedup == 0)
ret = ppm_revert_cpu_power(cpup, level + 1);
return (ret);
}
cpup = domp->devlist;
/*
* set bus resident devices at next speed level
*/
ret = ppm_cpu_next(domp, level);
if (ret != 0) {
(void) ppm_revert_cpu_power(cpup, level - incr);
return (ret);
}
/*
* platform dependent various operations before
* initiating cpu speed change
*/
ret = ppm_cpu_pre_chng(domp, level - incr, speedup);
if (ret != 0) {
(void) ppm_revert_cpu_power(cpup, level - incr);
(void) ppm_cpu_next(domp, level - incr);
return (ret);
}
/*
* the following 1us delay is actually required for us3i only.
* on us3i system, entering estar mode from full requires
* to set mcu to single fsm state followed by 1us delay
* before trigger actual transition. The mcu part is
* handled in us_drv, the delay is here.
*/
if ((oldlevel == highest) && (speedup == 0))
drv_usecwait(1);
/*
* initiate cpu speed change
*/
ret = ppm_cpu_go(domp, level);
if (ret != 0) {
(void) ppm_revert_cpu_power(cpup, level - incr);
(void) ppm_cpu_next(domp, level - incr);
return (ret);
}
/*
* platform dependent operations post cpu speed change
*/
ret = ppm_cpu_post_chng(domp, level, speedup);
if (ret != 0)
return (ret);
} /* end of looping each level */
return (DDI_SUCCESS);
}
/*
* Raise the power level of a subrange of cpus. Used when cpu driver
* failed an attempt to lower the power of a cpu (probably because
* it got busy). Need to revert the ones we already changed.
*
* ecpup = the ppm_dev_t for the cpu which failed to lower power
* level = power level to reset prior cpus to
*/
static int
ppm_revert_cpu_power(ppm_dev_t *ecpup, int level)
{
ppm_dev_t *cpup;
int ret = DDI_SUCCESS;
for (cpup = ecpup->domp->devlist; cpup != ecpup; cpup = cpup->next) {
PPMD(D_CPU, ("ppm_revert_cpu_power: \"%s\", revert to "
"level %d\n", cpup->path, level))
ret = pm_power(cpup->dip, 0, level);
if (ret == DDI_SUCCESS) {
cpup->level = level;
cpup->rplvl = PM_LEVEL_UNKNOWN;
}
}
return (ret);
}
/*
* ppm_manage_cpus - Process a request to change the power level of a cpu.
* If not all cpus want to be at the same level, OR if we are currently
* refusing slowdown requests due to thermal stress, we cache the request.
* Otherwise, set all cpus to the new power level.
*/
/* ARGSUSED */
static int
ppm_manage_cpus(dev_info_t *dip, power_req_t *reqp, int *result)
{
#ifdef DEBUG
char *str = "ppm_manage_cpus";
#endif
int old, new, ret, kmflag;
ppm_dev_t *ppmd, *cpup;
int change_notify = 0;
pm_ppm_devlist_t *devlist = NULL, *p;
int do_rescan = 0;
dev_info_t *rescan_dip;
*result = DDI_SUCCESS;
switch (reqp->request_type) {
case PMR_PPM_SET_POWER:
break;
case PMR_PPM_POWER_CHANGE_NOTIFY:
change_notify = 1;
break;
default:
return (DDI_FAILURE);
}
ppmd = PPM_GET_PRIVATE(dip);
ASSERT(MUTEX_HELD(&ppmd->domp->lock));
old = reqp->req.ppm_set_power_req.old_level;
new = reqp->req.ppm_set_power_req.new_level;
if (change_notify) {
ppmd->level = new;
ppmd->rplvl = PM_LEVEL_UNKNOWN;
PPMD(D_CPU, ("%s: Notify cpu dip %p power level has changed "
"from %d to %d", str, (void *)dip, old, new))
return (DDI_SUCCESS);
}
/*
* This handles the power-on case where cpu power level is
* PM_LEVEL_UNKNOWN. Per agreement with OBP, cpus always
* boot up at full speed. In fact, we must not making calls
* into tomtppm or schppm to trigger cpu speed change to a
* different level at early boot time since some cpu may not
* be ready, causing xc_one() to fail silently.
*
* Here we simply call pm_power() to get the power level updated
* in pm and ppm. Had xc_one() failed silently inside us_power()
* at this time we're unaffected.
*/
if (ppmd->level == PM_LEVEL_UNKNOWN && new == ppmd->highest) {
ret = pm_power(dip, 0, new);
if (ret != DDI_SUCCESS) {
PPMD(D_CPU, ("%s: pm_power() failed to change power "
"level to %d", str, new))
} else {
ppmd->level = new;
ppmd->rplvl = PM_LEVEL_UNKNOWN;
}
*result = ret;
return (ret);
}
if (new == ppmd->level) {
PPMD(D_CPU, ("%s: already at power level %d\n", str, new))
return (DDI_SUCCESS);
}
/*
* A request from lower to higher level transition is granted and
* made effective on both cpus. For more than two cpu platform model,
* the following code needs to be modified to remember the rest of
* the unsoliciting cpus to be rescan'ed.
* A request from higher to lower must be agreed by all cpus.
*/
ppmd->rplvl = new;
for (cpup = ppmd->domp->devlist; cpup; cpup = cpup->next) {
if (cpup->rplvl == new)
continue;
if (new < old) {
PPMD(D_SOME, ("%s: not all cpus wants to be at new "
"level %d yet.\n", str, new))
return (DDI_SUCCESS);
}
/*
* If a single cpu requests power up, honor the request by
* powering up both cpus.
*/
if (new > old) {
PPMD(D_SOME, ("%s: powering up device(%s@%s, %p) "
"because of request from dip(%s@%s, %p), "
"need pm_rescan\n", str, PM_NAME(cpup->dip),
PM_ADDR(cpup->dip), (void *)cpup->dip,
PM_NAME(dip), PM_ADDR(dip), (void *)dip))
do_rescan++;
rescan_dip = cpup->dip;
break;
}
}
PPMD(D_SETLVL, ("%s: \"%s\" set power level old %d, new %d \n",
str, ppmd->path, ppmd->level, new))
ret = ppm_change_cpu_power(ppmd, new);
*result = ret;
if (ret == DDI_SUCCESS) {
if (reqp->req.ppm_set_power_req.canblock == PM_CANBLOCK_BLOCK)
kmflag = KM_SLEEP;
else
kmflag = KM_NOSLEEP;
for (cpup = ppmd->domp->devlist; cpup; cpup = cpup->next) {
if (cpup->dip == dip)
continue;
if ((p = kmem_zalloc(sizeof (pm_ppm_devlist_t),
kmflag)) == NULL) {
break;
}
p->ppd_who = cpup->dip;
p->ppd_cmpt = cpup->cmpt;
p->ppd_old_level = old;
p->ppd_new_level = new;
p->ppd_next = devlist;
PPMD(D_SETLVL, ("%s: devlist entry[\"%s\"] %d -> %d\n",
str, cpup->path, old, new))
devlist = p;
}
reqp->req.ppm_set_power_req.cookie = (void *) devlist;
if (do_rescan > 0)
pm_rescan(rescan_dip);
}
return (ret);
}
/*
* ppm_svc_resume_ctlop - this is a small bookkeeping ppm does -
* increments its FET domain power count, in anticipation of that
* the indicated device(dip) would be powered up by its driver as
* a result of cpr resuming.
*/
/* ARGSUSED */
static void
ppm_svc_resume_ctlop(dev_info_t *dip, power_req_t *reqp)
{
ppm_domain_t *domp;
ppm_dev_t *ppmd;
int powered; /* power up count per dip */
ppmd = PPM_GET_PRIVATE(dip);
if (ppmd == NULL)
return;
/*
* Maintain correct powered count for domain which cares
*/
powered = 0;
domp = ppmd->domp;
mutex_enter(&domp->lock);
if ((domp->model == PPMD_FET) || PPMD_IS_PCI(domp->model)) {
for (ppmd = domp->devlist; ppmd; ppmd = ppmd->next) {
if (ppmd->dip == dip && ppmd->level)
powered++;
}
/*
* All fets and clocks are held on during suspend -
* resume window regardless their domain devices' power
* level.
*/
ASSERT(domp->status == PPMD_ON);
/*
* The difference indicates the number of components
* being off prior to suspend operation, that is the
* amount needs to be compensated in order to sync up
* bookkeeping with reality, for PROM reset would have
* brought up all devices.
*/
if (powered < PM_NUMCMPTS(dip))
domp->pwr_cnt += PM_NUMCMPTS(dip) - powered;
}
for (ppmd = domp->devlist; ppmd; ppmd = ppmd->next) {
if (ppmd->dip == dip)
ppmd->level = ppmd->rplvl = PM_LEVEL_UNKNOWN;
}
mutex_exit(&domp->lock);
}
#ifdef DEBUG
static int ppmbringup = 0;
#endif
int
ppm_bringup_domains()
{
#ifdef DEBUG
char *str = "ppm_bringup_domains";
#endif
ppm_domain_t *domp;
int ret = DDI_SUCCESS;
PPMD(D_CPR, ("%s[%d]: enter\n", str, ++ppmbringup))
for (domp = ppm_domain_p; domp; domp = domp->next) {
if ((!PPMD_IS_PCI(domp->model) && (domp->model != PPMD_FET)) ||
(domp->devlist == NULL))
continue;
mutex_enter(&domp->lock);
if (domp->status == PPMD_ON) {
mutex_exit(&domp->lock);
continue;
}
if (domp->model == PPMD_FET)
ret = ppm_fetset(domp, PPMD_ON);
else /* PPMD_PCI or PPMD_PCI_PROP */
ret = ppm_switch_clock(domp, PPMD_ON);
mutex_exit(&domp->lock);
}
PPMD(D_CPR, ("%s[%d]: exit\n", str, ppmbringup))
return (ret);
}
#ifdef DEBUG
static int ppmsyncbp = 0;
#endif
int
ppm_sync_bookkeeping()
{
#ifdef DEBUG
char *str = "ppm_sync_bookkeeping";
#endif
ppm_domain_t *domp;
int ret = DDI_SUCCESS;
PPMD(D_CPR, ("%s[%d]: enter\n", str, ++ppmsyncbp))
for (domp = ppm_domain_p; domp; domp = domp->next) {
if ((!PPMD_IS_PCI(domp->model) && (domp->model != PPMD_FET)) ||
(domp->devlist == NULL))
continue;
mutex_enter(&domp->lock);
if ((domp->pwr_cnt != 0) || !ppm_none_else_holds_power(domp)) {
mutex_exit(&domp->lock);
continue;
}
/*
* skip NULL .devlist slot, for some may host pci device
* that can not tolerate clock off or not even participate
* in PM.
*/
if (domp->devlist == NULL)
continue;
if (domp->model == PPMD_FET)
ret = ppm_fetset(domp, PPMD_OFF);
else /* PPMD_PCI or PPMD_PCI_PROP */
ret = ppm_switch_clock(domp, PPMD_OFF);
mutex_exit(&domp->lock);
}
PPMD(D_CPR, ("%s[%d]: exit\n", str, ppmsyncbp))
return (ret);
}
/*
* pre-suspend window;
*
* power up every FET and PCI clock that are off;
*
* set ppm_cpr_window global flag to indicate
* that even though all pm_scan requested power transitions
* will be honored as usual but that until we're out
* of this window, no FET or clock will be turned off
* for domains with pwr_cnt decremented down to 0.
* Such is to avoid accessing the orthogonal drivers that own
* the FET and clock registers that may not be resumed yet.
*
* at post-resume window, walk through each FET and PCI domains,
* bring pwr_cnt and domp->status to sense: if pwr-cnt == 0,
* and noinvol check okays, power down the FET or PCI. At last,
* clear the global flag ppm_cpr_window.
*
* ASSERT case 1, during cpr window, checks pwr_cnt against power
* transitions;
* ASSERT case 2, out of cpr window, checks four things:
* pwr_cnt <> power transition in/out of 0
* <> status <> record of noinvol device detached
*
*/
/* ARGSUSED */
static boolean_t
ppm_cpr_callb(void *arg, int code)
{
int ret;
switch (code) {
case CB_CODE_CPR_CHKPT:
/* pre-suspend: start of cpr window */
mutex_enter(&ppm_cpr_window_lock);
ASSERT(ppm_cpr_window_flag == B_FALSE);
ppm_cpr_window_flag = B_TRUE;
mutex_exit(&ppm_cpr_window_lock);
ret = ppm_bringup_domains();
break;
case CB_CODE_CPR_RESUME:
/* post-resume: end of cpr window */
ret = ppm_sync_bookkeeping();
mutex_enter(&ppm_cpr_window_lock);
ASSERT(ppm_cpr_window_flag == B_TRUE);
ppm_cpr_window_flag = B_FALSE;
mutex_exit(&ppm_cpr_window_lock);
break;
}
return (ret == DDI_SUCCESS);
}
/*
* Initialize our private version of real power level
* as well as lowest and highest levels the device supports;
* relate to ppm_add_dev
*/
void
ppm_dev_init(ppm_dev_t *ppmd)
{
struct pm_component *dcomps;
struct pm_comp *pm_comp;
dev_info_t *dip;
int maxi, i;
ASSERT(MUTEX_HELD(&ppmd->domp->lock));
ppmd->level = PM_LEVEL_UNKNOWN;
ppmd->rplvl = PM_LEVEL_UNKNOWN;
/* increment pwr_cnt per component */
if ((ppmd->domp->model == PPMD_FET) ||
PPMD_IS_PCI(ppmd->domp->model) ||
(ppmd->domp->model == PPMD_PCIE))
ppmd->domp->pwr_cnt++;
dip = ppmd->dip;
dcomps = DEVI(dip)->devi_pm_components;
pm_comp = &dcomps[ppmd->cmpt].pmc_comp;
ppmd->lowest = pm_comp->pmc_lvals[0];
ASSERT(ppmd->lowest >= 0);
maxi = pm_comp->pmc_numlevels - 1;
ppmd->highest = pm_comp->pmc_lvals[maxi];
/*
* If 66mhz PCI device on pci 66mhz bus supports D2 state
* (config reg PMC bit 10 set), ppm could turn off its bus
* clock once it is at D3hot.
*/
if (ppmd->domp->dflags & PPMD_PCI66MHZ) {
for (i = 0; i < maxi; i++)
if (pm_comp->pmc_lvals[i] == PM_LEVEL_D2) {
ppmd->flags |= PPMDEV_PCI66_D2;
break;
}
}
/*
* If device is in PCI_PROP domain and has exported the
* property listed in ppm.conf, its clock will be turned
* off when all pm'able devices in that domain are at D3.
*/
if ((ppmd->domp->model == PPMD_PCI_PROP) &&
(ppmd->domp->propname != NULL) &&
ddi_prop_exists(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
ppmd->domp->propname))
ppmd->flags |= PPMDEV_PCI_PROP_CLKPM;
}
/*
* relate to ppm_rem_dev
*/
void
ppm_dev_fini(ppm_dev_t *ppmd)
{
ASSERT(MUTEX_HELD(&ppmd->domp->lock));
/* decrement pwr_cnt per component */
if ((ppmd->domp->model == PPMD_FET) ||
PPMD_IS_PCI(ppmd->domp->model) ||
(ppmd->domp->model == PPMD_PCIE))
if (ppmd->level != ppmd->lowest)
ppmd->domp->pwr_cnt--;
}
/*
* Each power fet controls the power of one or more platform
* device(s) within their domain. Hence domain devices' power
* level change has been monitored, such that once all devices
* are powered off, the fet is turned off to save more power.
*
* To power on any domain device, the domain power fet
* needs to be turned on first. always one fet per domain.
*/
static int
ppm_manage_fet(dev_info_t *dip, power_req_t *reqp, int *result)
{
#ifdef DEBUG
char *str = "ppm_manage_fet";
#endif
int (*pwr_func)(ppm_dev_t *, int, int);
int new, old, cmpt;
ppm_dev_t *ppmd;
ppm_domain_t *domp;
int incr = 0;
int dummy_ret;
*result = DDI_SUCCESS;
switch (reqp->request_type) {
case PMR_PPM_SET_POWER:
pwr_func = ppm_change_power_level;
old = reqp->req.ppm_set_power_req.old_level;
new = reqp->req.ppm_set_power_req.new_level;
cmpt = reqp->req.ppm_set_power_req.cmpt;
break;
case PMR_PPM_POWER_CHANGE_NOTIFY:
pwr_func = ppm_record_level_change;
old = reqp->req.ppm_notify_level_req.old_level;
new = reqp->req.ppm_notify_level_req.new_level;
cmpt = reqp->req.ppm_notify_level_req.cmpt;
break;
default:
*result = DDI_FAILURE;
PPMD(D_FET, ("%s: unknown request type %d for %s@%s\n",
str, reqp->request_type, PM_NAME(dip), PM_ADDR(dip)))
return (DDI_FAILURE);
}
for (ppmd = PPM_GET_PRIVATE(dip); ppmd; ppmd = ppmd->next)
if (cmpt == ppmd->cmpt)
break;
if (!ppmd) {
PPMD(D_FET, ("%s: dip(%p): old(%d)->new(%d): no ppm_dev"
" found for cmpt(%d)", str, (void *)dip, old, new, cmpt))
*result = DDI_FAILURE;
return (DDI_FAILURE);
}
domp = ppmd->domp;
PPMD(D_FET, ("%s: %s@%s %s old %d, new %d, c%d, level %d, "
"status %s\n", str, PM_NAME(dip), PM_ADDR(dip),
ppm_get_ctlstr(reqp->request_type, ~0), old, new, cmpt,
ppmd->level, (domp->status == PPMD_OFF ? "off" : "on")))
ASSERT(old == ppmd->level);
if (new == ppmd->level) {
PPMD(D_FET, ("nop\n"))
return (DDI_SUCCESS);
}
PPM_LOCK_DOMAIN(domp);
/*
* In general, a device's published lowest power level does not
* have to be 0 if power-off is not tolerated. i.e. a device
* instance may export its lowest level > 0. It is reasonable to
* assume that level 0 indicates off state, positive level values
* indicate power states above off, include full power state.
*/
if (new > 0) { /* device powering up or to different positive level */
if (domp->status == PPMD_OFF) {
/* can not be in (chpt, resume) window */
ASSERT(ppm_cpr_window_flag == B_FALSE);
ASSERT(old == 0 && domp->pwr_cnt == 0);
PPMD(D_FET, ("About to turn fet on for %s@%s c%d\n",
PM_NAME(dip), PM_ADDR(dip), cmpt))
*result = ppm_fetset(domp, PPMD_ON);
if (*result != DDI_SUCCESS) {
PPMD(D_FET, ("\tCan't turn on power FET: "
"ret(%d)\n", *result))
PPM_UNLOCK_DOMAIN(domp);
return (DDI_FAILURE);
}
}
/*
* If powering up, pre-increment the count before
* calling pwr_func, because we are going to release
* the domain lock and another thread might turn off
* domain power otherwise.
*/
if (old == 0) {
domp->pwr_cnt++;
incr = 1;
}
PPMD(D_FET, ("\t%s domain power count: %d\n",
domp->name, domp->pwr_cnt))
}
PPM_UNLOCK_DOMAIN(domp);
ASSERT(domp->pwr_cnt > 0);
if ((*result = (*pwr_func)(ppmd, cmpt, new)) != DDI_SUCCESS) {
PPMD(D_FET, ("\t%s power change failed: ret(%d)\n",
ppmd->path, *result))
}
PPM_LOCK_DOMAIN(domp);
/*
* Decr the power count in two cases:
*
* 1) request was to power device down and was successful
* 2) request was to power up (we pre-incremented count), but failed.
*/
if ((*result == DDI_SUCCESS && ppmd->level == 0) ||
(*result != DDI_SUCCESS && incr)) {
ASSERT(domp->pwr_cnt > 0);
domp->pwr_cnt--;
}
PPMD(D_FET, ("\t%s domain power count: %d\n",
domp->name, domp->pwr_cnt))
/*
* call to pwr_func will update ppm data structures, if it
* succeeds. ppm should return whatever is the return value
* from call to pwr_func. This way pm and ppm data structures
* always in sync. Use dummy_ret from here for any further
* return values.
*/
if ((domp->pwr_cnt == 0) &&
(ppm_cpr_window_flag == B_FALSE) &&
ppm_none_else_holds_power(domp)) {
PPMD(D_FET, ("About to turn FET off for %s@%s c%d\n",
PM_NAME(dip), PM_ADDR(dip), cmpt))
dummy_ret = ppm_fetset(domp, PPMD_OFF);
if (dummy_ret != DDI_SUCCESS) {
PPMD(D_FET, ("\tCan't turn off FET: ret(%d)\n",
dummy_ret))
}
}
PPM_UNLOCK_DOMAIN(domp);
ASSERT(domp->pwr_cnt >= 0);
return (*result);
}
/*
* the actual code that turn on or off domain power fet and
* update domain status
*/
static int
ppm_fetset(ppm_domain_t *domp, uint8_t value)
{
char *str = "ppm_fetset";
int key;
ppm_dc_t *dc;
i2c_gpio_t i2c_req;
int ret;
clock_t temp;
clock_t delay = 0;
key = (value == PPMD_ON) ? PPMDC_FET_ON : PPMDC_FET_OFF;
for (dc = domp->dc; dc; dc = dc->next)
if (dc->cmd == key)
break;
if (!dc || !dc->lh) {
PPMD(D_FET, ("%s: %s domain: NULL ppm_dc handle\n",
str, domp->name))
return (DDI_FAILURE);
}
if (key == PPMDC_FET_ON) {
if (dc->method == PPMDC_I2CKIO)
delay = dc->m_un.i2c.delay;
else if (dc->method == PPMDC_KIO)
delay = dc->m_un.kio.delay;
if (delay > 0 && domp->last_off_time > 0) {
/*
* provide any delay required before turning on.
* some devices e.g. Samsung DVD require minimum
* of 1 sec between OFF->ON. no delay is required
* for the first time.
*/
temp = ddi_get_lbolt();
temp -= domp->last_off_time;
temp = drv_hztousec(temp);
if (temp < delay) {
/*
* busy wait untill we meet the
* required delay. Since we maintain
* time stamps in terms of clock ticks
* we might wait for longer than required
*/
PPMD(D_FET, ("%s : waiting %lu micro seconds "
"before on\n", domp->name,
delay - temp))
drv_usecwait(delay - temp);
}
}
}
switch (dc->method) {
case PPMDC_I2CKIO:
i2c_req.reg_mask = dc->m_un.i2c.mask;
i2c_req.reg_val = dc->m_un.i2c.val;
ret = ldi_ioctl(dc->lh, dc->m_un.i2c.iowr,
(intptr_t)&i2c_req, FWRITE | FKIOCTL, kcred, NULL);
break;
case PPMDC_KIO:
ret = ldi_ioctl(dc->lh, dc->m_un.kio.iowr,
(intptr_t)&(dc->m_un.kio.val), FWRITE | FKIOCTL, kcred,
NULL);
break;
default:
PPMD(D_FET, ("\t%s: unsupported domain control method %d\n",
str, domp->dc->method))
return (DDI_FAILURE);
}
PPMD(D_FET, ("%s: %s domain(%s) FET from %s to %s\n", str,
(ret == 0) ? "turned" : "failed to turn",
domp->name,
(domp->status == PPMD_ON) ? "ON" : "OFF",
(value == PPMD_ON) ? "ON" : "OFF"))
if (ret == DDI_SUCCESS) {
domp->status = value;
if (key == PPMDC_FET_OFF)
/*
* record the time, when it is off. time is recorded
* in clock ticks
*/
domp->last_off_time = ddi_get_lbolt();
/* implement any post op delay. */
if (key == PPMDC_FET_ON) {
if (dc->method == PPMDC_I2CKIO)
delay = dc->m_un.i2c.post_delay;
else if (dc->method == PPMDC_KIO)
delay = dc->m_un.kio.post_delay;
PPMD(D_FET, ("%s : waiting %lu micro seconds "
"after on\n", domp->name, delay))
if (delay > 0)
drv_usecwait(delay);
}
}
return (ret);
}
/*
* read power fet status
*/
static int
ppm_fetget(ppm_domain_t *domp, uint8_t *lvl)
{
char *str = "ppm_fetget";
ppm_dc_t *dc = domp->dc;
i2c_gpio_t i2c_req;
uint_t kio_val;
int off_val;
int ret;
if (!dc->lh) {
PPMD(D_FET, ("%s: %s domain NULL ppm_dc layered handle\n",
str, domp->name))
return (DDI_FAILURE);
}
if (!dc->next) {
cmn_err(CE_WARN, "%s: expect both fet on and fet off ops "
"defined, found only one in domain(%s)", str, domp->name);
return (DDI_FAILURE);
}
switch (dc->method) {
case PPMDC_I2CKIO:
i2c_req.reg_mask = dc->m_un.i2c.mask;
ret = ldi_ioctl(dc->lh, dc->m_un.i2c.iord,
(intptr_t)&i2c_req, FWRITE | FKIOCTL, kcred, NULL);
if (ret) {
PPMD(D_FET, ("%s: PPMDC_I2CKIO failed: ret(%d)\n",
str, ret))
return (ret);
}
off_val = (dc->cmd == PPMDC_FET_OFF) ? dc->m_un.i2c.val :
dc->next->m_un.i2c.val;
*lvl = (i2c_req.reg_val == off_val) ? PPMD_OFF : PPMD_ON;
PPMD(D_FET, ("%s: %s domain FET %s\n", str, domp->name,
(i2c_req.reg_val == off_val) ? "OFF" : "ON"))
break;
case PPMDC_KIO:
ret = ldi_ioctl(dc->lh, dc->m_un.kio.iord,
(intptr_t)&kio_val, FWRITE | FKIOCTL, kcred, NULL);
if (ret) {
PPMD(D_FET, ("%s: PPMDC_KIO failed: ret(%d)\n",
str, ret))
return (ret);
}
off_val = (dc->cmd == PPMDC_FET_OFF) ? dc->m_un.kio.val :
dc->next->m_un.kio.val;
*lvl = (kio_val == off_val) ? PPMD_OFF : PPMD_ON;
PPMD(D_FET, ("%s: %s domain FET %s\n", str, domp->name,
(kio_val == off_val) ? "OFF" : "ON"))
break;
default:
PPMD(D_FET, ("%s: unsupported domain control method %d\n",
str, domp->dc->method))
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
/*
* the actual code that switches pci clock and update domain status
*/
static int
ppm_switch_clock(ppm_domain_t *domp, int onoff)
{
#ifdef DEBUG
char *str = "ppm_switch_clock";
#endif
int cmd, pio_save;
ppm_dc_t *dc;
int ret;
extern int do_polled_io;
extern uint_t cfb_inuse;
ppm_dev_t *pdev;
cmd = (onoff == PPMD_ON) ? PPMDC_CLK_ON : PPMDC_CLK_OFF;
dc = ppm_lookup_dc(domp, cmd);
if (!dc) {
PPMD(D_PCI, ("%s: no ppm_dc found for domain (%s)\n",
str, domp->name))
return (DDI_FAILURE);
}
switch (dc->method) {
case PPMDC_KIO:
/*
* If we're powering up cfb on a Stop-A, we only
* want to do polled i/o to turn ON the clock
*/
pio_save = do_polled_io;
if ((cfb_inuse) && (cmd == PPMDC_CLK_ON)) {
for (pdev = domp->devlist; pdev; pdev = pdev->next) {
if (pm_is_cfb(pdev->dip)) {
do_polled_io = 1;
break;
}
}
}
ret = ldi_ioctl(dc->lh, dc->m_un.kio.iowr,
(intptr_t)&(dc->m_un.kio.val), FWRITE | FKIOCTL,
kcred, NULL);
do_polled_io = pio_save;
if (ret == 0) {
if (cmd == PPMDC_CLK_ON) {
domp->status = PPMD_ON;
/*
* PCI PM spec requires 50ms delay
*/
drv_usecwait(50000);
} else
domp->status = PPMD_OFF;
}
PPMD(D_PCI, ("%s: %s pci clock %s for domain (%s)\n", str,
(ret == 0) ? "turned" : "failed to turn",
(cmd == PPMDC_CLK_OFF) ? "OFF" : "ON",
domp->name))
break;
default:
PPMD(D_PCI, ("%s: unsupported domain control method %d\n",
str, dc->method))
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
/*
* pci slot domain is formed of pci device(s) reside in a pci slot.
* This function monitors domain device's power level change, such
* that,
* when all domain power count has gone to 0, it attempts to turn off
* the pci slot's clock;
* if any domain device is powering up, it'll turn on the pci slot's
* clock as the first thing.
*/
/* ARGUSED */
static int
ppm_manage_pci(dev_info_t *dip, power_req_t *reqp, int *result)
{
#ifdef DEBUG
char *str = "ppm_manage_pci";
#endif
int (*pwr_func)(ppm_dev_t *, int, int);
int old, new, cmpt;
ppm_dev_t *ppmd;
ppm_domain_t *domp;
int incr = 0;
int dummy_ret;
*result = DDI_SUCCESS;
switch (reqp->request_type) {
case PMR_PPM_SET_POWER:
pwr_func = ppm_change_power_level;
old = reqp->req.ppm_set_power_req.old_level;
new = reqp->req.ppm_set_power_req.new_level;
cmpt = reqp->req.ppm_set_power_req.cmpt;
break;
case PMR_PPM_POWER_CHANGE_NOTIFY:
pwr_func = ppm_record_level_change;
old = reqp->req.ppm_notify_level_req.old_level;
new = reqp->req.ppm_notify_level_req.new_level;
cmpt = reqp->req.ppm_notify_level_req.cmpt;
break;
default:
*result = DDI_FAILURE;
return (DDI_FAILURE);
}
for (ppmd = PPM_GET_PRIVATE(dip); ppmd; ppmd = ppmd->next)
if (cmpt == ppmd->cmpt)
break;
if (!ppmd) {
PPMD(D_PCI, ("%s: dip(%p): old(%d), new(%d): no ppm_dev"
" found for cmpt(%d)", str, (void *)dip, old, new, cmpt))
*result = DDI_FAILURE;
return (DDI_FAILURE);
}
domp = ppmd->domp;
PPMD(D_PCI, ("%s: %s, dev(%s), c%d, old %d, new %d\n", str,
ppm_get_ctlstr(reqp->request_type, ~0),
ppmd->path, cmpt, old, new))
ASSERT(old == ppmd->level);
if (new == ppmd->level)
return (DDI_SUCCESS);
PPM_LOCK_DOMAIN(domp);
if (new > 0) { /* device powering up */
if (domp->status == PPMD_OFF) {
/* cannot be off during (chpt, resume) window */
ASSERT(ppm_cpr_window_flag == B_FALSE);
/* either both OFF or both ON */
ASSERT(!((old == 0) ^ (domp->pwr_cnt == 0)));
PPMD(D_PCI, ("About to turn clock on for %s@%s c%d\n",
PM_NAME(dip), PM_ADDR(dip), cmpt))
*result = ppm_switch_clock(domp, PPMD_ON);
if (*result != DDI_SUCCESS) {
PPMD(D_PCI, ("\tcan't switch on pci clock: "
"ret(%d)\n", *result))
PPM_UNLOCK_DOMAIN(domp);
return (DDI_FAILURE);
}
}
if (old == 0) {
domp->pwr_cnt++;
incr = 1;
}
PPMD(D_PCI, ("\t%s domain power count: %d\n",
domp->name, domp->pwr_cnt))
}
PPM_UNLOCK_DOMAIN(domp);
ASSERT(domp->pwr_cnt > 0);
if ((*result = (*pwr_func)(ppmd, cmpt, new)) != DDI_SUCCESS) {
PPMD(D_PCI, ("\t%s power change failed: ret(%d)\n",
ppmd->path, *result))
}
PPM_LOCK_DOMAIN(domp);
/*
* Decr the power count in two cases:
*
* 1) request was to power device down and was successful
* 2) request was to power up (we pre-incremented count), but failed.
*/
if ((*result == DDI_SUCCESS && ppmd->level == 0) ||
(*result != DDI_SUCCESS && incr)) {
ASSERT(domp->pwr_cnt > 0);
domp->pwr_cnt--;
}
PPMD(D_PCI, ("\t%s domain power count: %d\n",
domp->name, domp->pwr_cnt))
/*
* call to pwr_func will update ppm data structures, if it
* succeeds. ppm should return whatever is the return value
* from call to pwr_func. This way pm and ppm data structures
* always in sync. Use dummy_ret from here for any further
* return values.
*/
if ((domp->pwr_cnt == 0) &&
(ppm_cpr_window_flag == B_FALSE) &&
ppm_none_else_holds_power(domp)) {
PPMD(D_PCI, ("About to turn clock off for %s@%s c%d\n",
PM_NAME(dip), PM_ADDR(dip), cmpt))
dummy_ret = ppm_switch_clock(domp, PPMD_OFF);
if (dummy_ret != DDI_SUCCESS) {
PPMD(D_PCI, ("\tCan't switch clock off: "
"ret(%d)\n", dummy_ret))
}
}
PPM_UNLOCK_DOMAIN(domp);
ASSERT(domp->pwr_cnt >= 0);
return (*result);
}
/*
* When the driver for the primary PCI-Express child has set the device to
* lowest power (D3hot), we come here to save even more power by transitioning
* the slot to D3cold. Similarly, if the slot is in D3cold and we need to
* power up the child, we come here first to power up the slot.
*/
/* ARGUSED */
static int
ppm_manage_pcie(dev_info_t *dip, power_req_t *reqp, int *result)
{
#ifdef DEBUG
char *str = "ppm_manage_pcie";
#endif
int (*pwr_func)(ppm_dev_t *, int, int);
int old, new, cmpt;
ppm_dev_t *ppmd;
ppm_domain_t *domp;
int incr = 0;
int dummy_ret;
*result = DDI_SUCCESS;
switch (reqp->request_type) {
case PMR_PPM_SET_POWER:
pwr_func = ppm_change_power_level;
old = reqp->req.ppm_set_power_req.old_level;
new = reqp->req.ppm_set_power_req.new_level;
cmpt = reqp->req.ppm_set_power_req.cmpt;
break;
case PMR_PPM_POWER_CHANGE_NOTIFY:
pwr_func = ppm_record_level_change;
old = reqp->req.ppm_notify_level_req.old_level;
new = reqp->req.ppm_notify_level_req.new_level;
cmpt = reqp->req.ppm_notify_level_req.cmpt;
break;
default:
*result = DDI_FAILURE;
return (DDI_FAILURE);
}
for (ppmd = PPM_GET_PRIVATE(dip); ppmd; ppmd = ppmd->next)
if (cmpt == ppmd->cmpt)
break;
if (!ppmd) {
PPMD(D_PCI, ("%s: dip(%p): old(%d), new(%d): no ppm_dev"
" found for cmpt(%d)", str, (void *)dip, old, new, cmpt))
*result = DDI_FAILURE;
return (DDI_FAILURE);
}
domp = ppmd->domp;
PPMD(D_PCI, ("%s: %s, dev(%s), c%d, old %d, new %d\n", str,
ppm_get_ctlstr(reqp->request_type, ~0),
ppmd->path, cmpt, old, new))
ASSERT(old == ppmd->level);
if (new == ppmd->level)
return (DDI_SUCCESS);
PPM_LOCK_DOMAIN(domp);
if (new > 0) { /* device powering up */
if (domp->status == PPMD_OFF) {
/* cannot be off during (chpt, resume) window */
ASSERT(ppm_cpr_window_flag == B_FALSE);
/* either both OFF or both ON */
ASSERT(!((old == 0) ^ (domp->pwr_cnt == 0)));
PPMD(D_PCI, ("About to turn on pcie slot for "
"%s@%s c%d\n", PM_NAME(dip), PM_ADDR(dip), cmpt))
*result = ppm_pcie_pwr(domp, PPMD_ON);
if (*result != DDI_SUCCESS) {
PPMD(D_PCI, ("\tcan't switch on pcie slot: "
"ret(%d)\n", *result))
PPM_UNLOCK_DOMAIN(domp);
return (DDI_FAILURE);
}
}
if (old == 0) {
domp->pwr_cnt++;
incr = 1;
}
PPMD(D_PCI, ("\t%s domain power count: %d\n",
domp->name, domp->pwr_cnt))
}
PPM_UNLOCK_DOMAIN(domp);
ASSERT(domp->pwr_cnt > 0);
if ((*result = (*pwr_func)(ppmd, cmpt, new)) != DDI_SUCCESS) {
PPMD(D_PCI, ("\t%s power change failed: ret(%d)\n",
ppmd->path, *result))
}
PPM_LOCK_DOMAIN(domp);
/*
* Decr the power count in two cases:
*
* 1) request was to power device down and was successful
* 2) request was to power up (we pre-incremented count), but failed.
*/
if ((*result == DDI_SUCCESS && ppmd->level == 0) ||
(*result != DDI_SUCCESS && incr)) {
ASSERT(domp->pwr_cnt > 0);
domp->pwr_cnt--;
}
PPMD(D_PCI, ("\t%s domain power count: %d\n",
domp->name, domp->pwr_cnt))
/*
* call to pwr_func will update ppm data structures, if it
* succeeds. ppm should return whatever is the return value
* from call to pwr_func. This way pm and ppm data structures
* always in sync. Use dummy_ret from here for any further
* return values.
*/
if ((domp->pwr_cnt == 0) &&
(ppm_cpr_window_flag == B_FALSE) &&
ppm_none_else_holds_power(domp)) {
PPMD(D_PCI, ("About to turn off pcie slot for %s@%s c%d\n",
PM_NAME(dip), PM_ADDR(dip), cmpt))
dummy_ret = ppm_pcie_pwr(domp, PPMD_OFF);
if (dummy_ret != DDI_SUCCESS) {
PPMD(D_PCI, ("\tCan't switch pcie slot off: "
"ret(%d)\n", dummy_ret))
}
}
PPM_UNLOCK_DOMAIN(domp);
ASSERT(domp->pwr_cnt >= 0);
return (*result);
}
/*
* Set or clear a bit on a GPIO device. These bits are used for various device-
* specific purposes.
*/
static int
ppm_gpioset(ppm_domain_t *domp, int key)
{
#ifdef DEBUG
char *str = "ppm_gpioset";
#endif
ppm_dc_t *dc;
i2c_gpio_t i2c_req;
int ret;
clock_t delay = 0;
for (dc = domp->dc; dc; dc = dc->next)
if (dc->cmd == key)
break;
if (!dc || !dc->lh) {
PPMD(D_GPIO, ("%s: %s domain: NULL ppm_dc handle\n",
str, domp->name))
return (DDI_FAILURE);
}
if (dc->method == PPMDC_I2CKIO)
delay = dc->m_un.i2c.delay;
else if (dc->method == PPMDC_KIO)
delay = dc->m_un.kio.delay;
if (delay > 0) {
PPMD(D_GPIO, ("%s : waiting %lu micro seconds "
"before change\n", domp->name, delay))
drv_usecwait(delay);
}
switch (dc->method) {
case PPMDC_I2CKIO:
i2c_req.reg_mask = dc->m_un.i2c.mask;
i2c_req.reg_val = dc->m_un.i2c.val;
ret = ldi_ioctl(dc->lh, dc->m_un.i2c.iowr,
(intptr_t)&i2c_req, FWRITE | FKIOCTL, kcred, NULL);
PPMD(D_GPIO, ("%s: %s domain(%s) from %s by writing %x "
"to gpio\n",
str, (ret == 0) ? "turned" : "FAILed to turn",
domp->name,
(domp->status == PPMD_ON) ? "ON" : "OFF",
dc->m_un.i2c.val))
break;
case PPMDC_KIO:
ret = ldi_ioctl(dc->lh, dc->m_un.kio.iowr,
(intptr_t)&(dc->m_un.kio.val), FWRITE | FKIOCTL, kcred,
NULL);
PPMD(D_GPIO, ("%s: %s domain(%s) from %s by writing %x "
"to gpio\n",
str, (ret == 0) ? "turned" : "FAILed to turn",
domp->name,
(domp->status == PPMD_ON) ? "ON" : "OFF",
dc->m_un.kio.val))
break;
default:
PPMD(D_GPIO, ("\t%s: unsupported domain control method %d\n",
str, domp->dc->method))
return (DDI_FAILURE);
}
/* implement any post op delay. */
if (dc->method == PPMDC_I2CKIO)
delay = dc->m_un.i2c.post_delay;
else if (dc->method == PPMDC_KIO)
delay = dc->m_un.kio.post_delay;
if (delay > 0) {
PPMD(D_GPIO, ("%s : waiting %lu micro seconds "
"after change\n", domp->name, delay))
drv_usecwait(delay);
}
return (ret);
}
static int
ppm_pcie_pwr(ppm_domain_t *domp, int onoff)
{
#ifdef DEBUG
char *str = "ppm_pcie_pwr";
#endif
int ret = DDI_FAILURE;
ppm_dc_t *dc;
clock_t delay;
ASSERT(onoff == PPMD_OFF || onoff == PPMD_ON);
dc = ppm_lookup_dc(domp,
onoff == PPMD_ON ? PPMDC_PRE_PWR_ON : PPMDC_PRE_PWR_OFF);
if (dc) {
/*
* Invoke layered ioctl for pcie root complex nexus to
* transition the link
*/
ASSERT(dc->method == PPMDC_KIO);
delay = dc->m_un.kio.delay;
if (delay > 0) {
PPMD(D_GPIO, ("%s : waiting %lu micro seconds "
"before change\n", domp->name, delay))
drv_usecwait(delay);
}
ret = ldi_ioctl(dc->lh, dc->m_un.kio.iowr,
(intptr_t)&(dc->m_un.kio.val),
FWRITE | FKIOCTL, kcred, NULL);
if (ret == DDI_SUCCESS) {
delay = dc->m_un.kio.post_delay;
if (delay > 0) {
PPMD(D_GPIO, ("%s : waiting %lu micro seconds "
"after change\n", domp->name, delay))
drv_usecwait(delay);
}
} else {
PPMD(D_PCI, ("%s: ldi_ioctl FAILED for domain(%s)\n",
str, domp->name))
return (ret);
}
}
switch (onoff) {
case PPMD_OFF:
/* Turn off the clock for this slot. */
if ((ret = ppm_gpioset(domp, PPMDC_CLK_OFF)) != DDI_SUCCESS) {
PPMD(D_GPIO,
("%s: failed to turn off domain(%s) clock\n",
str, domp->name))
return (ret);
}
/* Turn off the power to this slot */
if ((ret = ppm_gpioset(domp, PPMDC_PWR_OFF)) != DDI_SUCCESS) {
PPMD(D_GPIO,
("%s: failed to turn off domain(%s) power\n",
str, domp->name))
return (ret);
}
break;
case PPMD_ON:
/* Assert RESET for this slot. */
if ((ret = ppm_gpioset(domp, PPMDC_RESET_ON)) != DDI_SUCCESS) {
PPMD(D_GPIO,
("%s: failed to assert reset for domain(%s)\n",
str, domp->name))
return (ret);
}
/* Turn on the power to this slot */
if ((ret = ppm_gpioset(domp, PPMDC_PWR_ON)) != DDI_SUCCESS) {
PPMD(D_GPIO,
("%s: failed to turn on domain(%s) power\n",
str, domp->name))
return (ret);
}
/* Turn on the clock for this slot */
if ((ret = ppm_gpioset(domp, PPMDC_CLK_ON)) != DDI_SUCCESS) {
PPMD(D_GPIO,
("%s: failed to turn on domain(%s) clock\n",
str, domp->name))
return (ret);
}
/* De-assert RESET for this slot. */
if ((ret = ppm_gpioset(domp, PPMDC_RESET_OFF)) != DDI_SUCCESS) {
PPMD(D_GPIO,
("%s: failed to de-assert reset for domain(%s)\n",
str, domp->name))
return (ret);
}
dc = ppm_lookup_dc(domp, PPMDC_POST_PWR_ON);
if (dc) {
/*
* Invoke layered ioctl to PCIe root complex nexus
* to transition the link.
*/
ASSERT(dc->method == PPMDC_KIO);
delay = dc->m_un.kio.delay;
if (delay > 0) {
PPMD(D_GPIO, ("%s: waiting %lu micro seconds "
"before change\n", domp->name, delay))
drv_usecwait(delay);
}
ret = ldi_ioctl(dc->lh, dc->m_un.kio.iowr,
(intptr_t)&(dc->m_un.kio.val),
FWRITE | FKIOCTL, kcred, NULL);
if (ret != DDI_SUCCESS) {
PPMD(D_PCI, ("%s: layered ioctl to PCIe"
"root complex nexus FAILed\n", str))
return (ret);
}
delay = dc->m_un.kio.post_delay;
if (delay > 0) {
PPMD(D_GPIO, ("%s: waiting %lu micro "
"seconds after change\n",
domp->name, delay))
drv_usecwait(delay);
}
}
break;
default:
ASSERT(0);
}
PPMD(D_PCI, ("%s: turned domain(%s) PCIe slot power from %s to %s\n",
str, domp->name, (domp->status == PPMD_ON) ? "ON" : "OFF",
onoff == PPMD_ON ? "ON" : "OFF"))
domp->status = onoff;
return (ret);
}
/*
* Change the power level for a component of a device. If the change
* arg is true, we call the framework to actually change the device's
* power; otherwise, we just update our own copy of the power level.
*/
static int
ppm_set_level(ppm_dev_t *ppmd, int cmpt, int level, boolean_t change)
{
#ifdef DEBUG
char *str = "ppm_set_level";
#endif
int ret;
ret = DDI_SUCCESS;
if (change)
ret = pm_power(ppmd->dip, cmpt, level);
PPMD(D_SETLVL, ("%s: %s change=%d, old %d, new %d, ret %d\n",
str, ppmd->path, change, ppmd->level, level, ret))
if (ret == DDI_SUCCESS) {
ppmd->level = level;
ppmd->rplvl = PM_LEVEL_UNKNOWN;
}
return (ret);
}
static int
ppm_change_power_level(ppm_dev_t *ppmd, int cmpt, int level)
{
return (ppm_set_level(ppmd, cmpt, level, B_TRUE));
}
static int
ppm_record_level_change(ppm_dev_t *ppmd, int cmpt, int level)
{
return (ppm_set_level(ppmd, cmpt, level, B_FALSE));
}
static void
ppm_manage_led(int action)
{
ppm_domain_t *domp;
ppm_unit_t *unitp;
timeout_id_t tid;
PPMD(D_LED, ("ppm_manage_led: action: %s\n",
(action == PPM_LED_BLINKING) ? "PPM_LED_BLINKING" :
"PPM_LED_SOLIDON"))
/*
* test whether led operation is practically supported,
* if not, we waive without pressing for reasons
*/
if (!ppm_lookup_dc(NULL, PPMDC_LED_ON))
return;
unitp = ddi_get_soft_state(ppm_statep, ppm_inst);
for (domp = ppm_domain_p; (domp && (domp->model != PPMD_LED)); )
domp = domp->next;
mutex_enter(&unitp->lock);
if (action == PPM_LED_BLINKING) {
ppm_set_led(domp, PPMD_OFF);
unitp->led_tid = timeout(
ppm_blink_led, domp, PPM_LEDOFF_INTERVAL);
} else { /* PPM_LED_SOLIDON */
ASSERT(action == PPM_LED_SOLIDON);
tid = unitp->led_tid;
unitp->led_tid = 0;
mutex_exit(&unitp->lock);
(void) untimeout(tid);
mutex_enter(&unitp->lock);
ppm_set_led(domp, PPMD_ON);
}
mutex_exit(&unitp->lock);
}
static void
ppm_set_led(ppm_domain_t *domp, int val)
{
int ret;
ret = ppm_gpioset(domp,
(val == PPMD_ON) ? PPMDC_LED_ON : PPMDC_LED_OFF);
PPMD(D_LED, ("ppm_set_led: %s LED from %s\n",
(ret == 0) ? "turned" : "FAILed to turn",
(domp->status == PPMD_ON) ? "ON to OFF" : "OFF to ON"))
if (ret == DDI_SUCCESS)
domp->status = val;
}
static void
ppm_blink_led(void *arg)
{
ppm_unit_t *unitp;
clock_t intvl;
ppm_domain_t *domp = arg;
unitp = ddi_get_soft_state(ppm_statep, ppm_inst);
mutex_enter(&unitp->lock);
if (unitp->led_tid == 0) {
mutex_exit(&unitp->lock);
return;
}
if (domp->status == PPMD_ON) {
ppm_set_led(domp, PPMD_OFF);
intvl = PPM_LEDOFF_INTERVAL;
} else {
ppm_set_led(domp, PPMD_ON);
intvl = PPM_LEDON_INTERVAL;
}
unitp->led_tid = timeout(ppm_blink_led, domp, intvl);
mutex_exit(&unitp->lock);
}
/*
* Function to power up a domain, if required. It also increments the
* domain pwr_cnt to prevent it from going down.
*/
static int
ppm_power_up_domain(dev_info_t *dip)
{
int ret = DDI_SUCCESS;
ppm_domain_t *domp;
char *str = "ppm_power_up_domain";
domp = ppm_lookup_dev(dip);
ASSERT(domp);
mutex_enter(&domp->lock);
switch (domp->model) {
case PPMD_FET:
if (domp->status == PPMD_OFF) {
if ((ret = ppm_fetset(domp, PPMD_ON)) ==
DDI_SUCCESS) {
PPMD(D_FET, ("%s: turned on fet for %s@%s\n",
str, PM_NAME(dip), PM_ADDR(dip)))
} else {
PPMD(D_FET, ("%s: couldn't turned on fet "
"for %s@%s\n", str, PM_NAME(dip),
PM_ADDR(dip)))
}
}
break;
case PPMD_PCI:
case PPMD_PCI_PROP:
if (domp->status == PPMD_OFF) {
if ((ret = ppm_switch_clock(domp, PPMD_ON)) ==
DDI_SUCCESS) {
PPMD(D_PCI, ("%s: turned on clock for "
"%s@%s\n", str, PM_NAME(dip),
PM_ADDR(dip)))
} else {
PPMD(D_PCI, ("%s: couldn't turned on clock "
"for %s@%s\n", str, PM_NAME(dip),
PM_ADDR(dip)))
}
}
break;
default:
break;
}
if (ret == DDI_SUCCESS)
domp->pwr_cnt++;
mutex_exit(&domp->lock);
return (ret);
}
/*
* Decrements the domain pwr_cnt. if conditions to power down the domain
* are met, powers down the domain,.
*/
static int
ppm_power_down_domain(dev_info_t *dip)
{
int ret = DDI_SUCCESS;
char *str = "ppm_power_down_domain";
ppm_domain_t *domp;
domp = ppm_lookup_dev(dip);
ASSERT(domp);
mutex_enter(&domp->lock);
ASSERT(domp->pwr_cnt > 0);
domp->pwr_cnt--;
switch (domp->model) {
case PPMD_FET:
if ((domp->pwr_cnt == 0) &&
(ppm_cpr_window_flag == B_FALSE) &&
ppm_none_else_holds_power(domp)) {
if ((ret = ppm_fetset(domp, PPMD_OFF)) ==
DDI_SUCCESS) {
PPMD(D_FET, ("%s: turned off FET for %s@%s \n",
str, PM_NAME(dip), PM_ADDR(dip)))
} else {
PPMD(D_FET, ("%s: couldn't turn off FET for "
" %s@%s\n", str, PM_NAME(dip),
PM_ADDR(dip)))
}
}
break;
case PPMD_PCI:
case PPMD_PCI_PROP:
if ((domp->pwr_cnt == 0) &&
(ppm_cpr_window_flag == B_FALSE) &&
ppm_none_else_holds_power(domp)) {
if ((ret = ppm_switch_clock(domp, PPMD_OFF)) ==
DDI_SUCCESS) {
PPMD(D_PCI, ("%s: turned off clock for %s@%s\n",
str, PM_NAME(dip), PM_ADDR(dip)))
} else {
PPMD(D_PCI, ("%s: couldn't turn off clock "
"for %s@%s\n", str, PM_NAME(dip),
PM_ADDR(dip)))
}
}
break;
default:
break;
}
mutex_exit(&domp->lock);
return (ret);
}