/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (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 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright (c) 2009, Intel Corporation. * All Rights Reserved. */ /* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * 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 */ ddi_quiesce_not_needed, /* quiesce */ }; extern struct mod_ops mod_driverops; static struct modldrv modldrv = { &mod_driverops, "platform pm driver", &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 /* * 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_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) { PPMD(D_INIT, ("ppm: soft state init\n")) return (DDI_FAILURE); } if (mod_install(&modlinkage) != DDI_SUCCESS) { ddi_soft_state_fini(&ppm_statep); return (DDI_FAILURE); } return (DDI_SUCCESS); } int _fini(void) { int error; if ((error = mod_remove(&modlinkage)) == DDI_SUCCESS) ddi_soft_state_fini(&ppm_statep); return (error); } 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"); #if defined(__x86) /* * Register callback so that once CPUs have been added to * the device tree, ppm CPU domains can be allocated using ACPI * data. */ cpupm_ppm_alloc_pstate_domains = ppm_alloc_pstate_domains; cpupm_ppm_free_pstate_domains = ppm_free_pstate_domains; /* * Register callback so that whenever max speed throttle requests * are received, ppm can redefine the high power level for * all CPUs in the domain. */ cpupm_redefine_topspeed = ppm_redefine_topspeed; #endif 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; } #if defined(__x86) /* * Note that these two ioctls exist for test purposes only. * Unfortunately, there really isn't any other good way of * unit testing the dynamic redefinition of the top speed as it * usually occurs due to environmental conditions. */ case PPMGET_NORMAL: case PPMSET_NORMAL: { STRUCT_DECL(ppm_norm, norm); char *path = NULL; struct pm_component *dcomps; struct pm_comp *pm_comp; ppm_dev_t *ppmd; int i; STRUCT_INIT(norm, mode); ret = ddi_copyin((caddr_t)arg, STRUCT_BUF(norm), STRUCT_SIZE(norm), mode); if (ret != 0) return (EFAULT); /* copyin .path */ path = kmem_zalloc(MAXPATHLEN, KM_SLEEP); ret = copyinstr( STRUCT_FGETP(norm, 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); } domp = ppm_get_domain_by_dev(path); kmem_free(path, MAXPATHLEN); if (domp == NULL) return (ENODEV); ppmd = domp->devlist; if (cmd == PPMSET_NORMAL) { if (domp->model != PPMD_CPU) return (EINVAL); level = STRUCT_FGET(norm, norm); dcomps = DEVI(ppmd->dip)->devi_pm_components; pm_comp = &dcomps[ppmd->cmpt].pmc_comp; for (i = pm_comp->pmc_numlevels; i > 0; i--) { if (pm_comp->pmc_lvals[i-1] == level) break; } if (i == 0) return (EINVAL); ppm_set_topspeed(ppmd, pm_comp->pmc_numlevels - i); } level = pm_get_normal_power(ppmd->dip, 0); STRUCT_FSET(norm, norm, level); ret = ddi_copyout(STRUCT_BUF(norm), (caddr_t)arg, STRUCT_SIZE(norm), mode); if (ret != 0) { PPMD(D_IOCTL, ("%s: can't copyout, line(%d)\n", str, __LINE__)) ret = EFAULT; } break; } #endif default: PPMD(D_IOCTL, ("%s: unsupported ioctl command(%d)\n", str, cmd)) return (EINVAL); } return (ret); } static int ppm_manage_sx(s3a_t *, int); static int ppm_search_list(pm_searchargs_t *); /* * 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; int *res = (int *)result; s3a_t s3args; domp = NULL; #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); 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); } if (domp == NULL) return (DDI_FAILURE); 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); } if (domp == NULL) return (DDI_FAILURE); /* * 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); } case PMR_PPM_ENTER_SX: case PMR_PPM_EXIT_SX: s3args.s3a_state = reqp->req.ppm_power_enter_sx_req.sx_state; s3args.s3a_test_point = reqp->req.ppm_power_enter_sx_req.test_point; s3args.s3a_wakephys = reqp->req.ppm_power_enter_sx_req.wakephys; s3args.s3a_psr = reqp->req.ppm_power_enter_sx_req.psr; ret = ppm_manage_sx(&s3args, reqp->request_type == PMR_PPM_ENTER_SX); if (ret) { PPMD(D_CPR, ("ppm_manage_sx returns %d\n", ret)) return (DDI_FAILURE); } else { return (DDI_SUCCESS); } case PMR_PPM_SEARCH_LIST: ret = ppm_search_list(reqp->req.ppm_search_list_req.searchlist); reqp->req.ppm_search_list_req.result = ret; *res = ret; if (ret) { PPMD(D_CPR, ("ppm_search_list returns %d\n", ret)) return (DDI_FAILURE); } else { PPMD(D_CPR, ("ppm_search_list returns %d\n", ret)) return (DDI_SUCCESS); } default: cmn_err(CE_WARN, "ppm_ctlops: unrecognized ctlops req(%d)", reqp->request_type); return (DDI_FAILURE); } } /* * 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 */ 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; *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); } if (ppm_manage_early_cpus(dip, new, result)) return (*result); 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 all cpus. A request from higher to lower must * be agreed upon 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 * powering up all 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++; } } 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) { for (cpup = ppmd->domp->devlist; cpup; cpup = cpup->next) { if (cpup->dip == dip) continue; pm_rescan(cpup->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) || (domp->model == PPMD_PCIE)) { 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->model != PPMD_PCIE)) || (domp->devlist == NULL)) continue; mutex_enter(&domp->lock); if (domp->status == PPMD_ON) { mutex_exit(&domp->lock); continue; } switch (domp->model) { case PPMD_FET: ret = ppm_fetset(domp, PPMD_ON); break; case PPMD_PCI: case PPMD_PCI_PROP: ret = ppm_switch_clock(domp, PPMD_ON); break; case PPMD_PCIE: ret = ppm_pcie_pwr(domp, PPMD_ON); break; default: break; } 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->model != PPMD_PCIE)) || (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; switch (domp->model) { case PPMD_FET: ret = ppm_fetset(domp, PPMD_OFF); break; case PPMD_PCI: case PPMD_PCI_PROP: ret = ppm_switch_clock(domp, PPMD_OFF); break; case PPMD_PCIE: ret = ppm_pcie_pwr(domp, PPMD_OFF); break; default: break; } 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; default: ret = DDI_SUCCESS; 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; /* * ppm exists to handle power-manageable devices which require * special handling on the current platform. However, a * driver for such a device may choose not to support power * management on a particular load/attach. In this case we * we create a structure to represent a single-component device * for which "level" = PM_LEVEL_UNKNOWN and "lowest" = 0 * are effectively constant. */ if (PM_GET_PM_INFO(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; 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) { PPM_GET_IO_DELAY(dc, 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) { #ifdef sun4u case PPMDC_I2CKIO: { i2c_gpio_t i2c_req; 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; } #endif 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) { PPM_GET_IO_POST_DELAY(dc, 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; 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) { #ifdef sun4u case PPMDC_I2CKIO: { i2c_gpio_t i2c_req; 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; } #endif 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; 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); } PPM_GET_IO_DELAY(dc, delay); if (delay > 0) { PPMD(D_GPIO, ("%s : waiting %lu micro seconds " "before change\n", domp->name, delay)) drv_usecwait(delay); } switch (dc->method) { #ifdef sun4u case PPMDC_I2CKIO: { i2c_gpio_t i2c_req; ppm_dev_t *pdev; int pio_save; extern int do_polled_io; extern uint_t cfb_inuse; i2c_req.reg_mask = dc->m_un.i2c.mask; i2c_req.reg_val = dc->m_un.i2c.val; pio_save = do_polled_io; if (cfb_inuse) { for (pdev = domp->devlist; pdev; pdev = pdev->next) { if (pm_is_cfb(pdev->dip)) { do_polled_io = 1; PPMD(D_GPIO, ("%s: cfb is in use, " "i2c transaction is done in " "poll-mode.\n", str)) break; } } } ret = ldi_ioctl(dc->lh, dc->m_un.i2c.iowr, (intptr_t)&i2c_req, FWRITE | FKIOCTL, kcred, NULL); do_polled_io = pio_save; 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; } #endif 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. */ PPM_GET_IO_POST_DELAY(dc, 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 turn 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 turn on clock " "for %s@%s\n", str, PM_NAME(dip), PM_ADDR(dip))) } } break; case PPMD_PCIE: if (domp->status == PPMD_OFF) { if ((ret = ppm_pcie_pwr(domp, PPMD_ON)) == DDI_SUCCESS) { PPMD(D_PCI, ("%s: turned on link for " "%s@%s\n", str, PM_NAME(dip), PM_ADDR(dip))) } else { PPMD(D_PCI, ("%s: couldn't turn on link " "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; case PPMD_PCIE: if ((domp->pwr_cnt == 0) && (ppm_cpr_window_flag == B_FALSE) && ppm_none_else_holds_power(domp)) { if ((ret = ppm_pcie_pwr(domp, PPMD_OFF)) == DDI_SUCCESS) { PPMD(D_PCI, ("%s: turned off link for %s@%s\n", str, PM_NAME(dip), PM_ADDR(dip))) } else { PPMD(D_PCI, ("%s: couldn't turn off link " "for %s@%s\n", str, PM_NAME(dip), PM_ADDR(dip))) } } break; default: break; } mutex_exit(&domp->lock); return (ret); } static int ppm_manage_sx(s3a_t *s3ap, int enter) { ppm_domain_t *domp = ppm_lookup_domain("domain_estar"); ppm_dc_t *dc; int ret = 0; if (domp == NULL) { PPMD(D_CPR, ("ppm_manage_sx: can't find estar domain\n")) return (ENODEV); } PPMD(D_CPR, ("ppm_manage_sx %x, enter %d\n", s3ap->s3a_state, enter)) switch (s3ap->s3a_state) { case S3: if (enter) { dc = ppm_lookup_dc(domp, PPMDC_ENTER_S3); } else { dc = ppm_lookup_dc(domp, PPMDC_EXIT_S3); } ASSERT(dc && dc->method == PPMDC_KIO); PPMD(D_CPR, ("ppm_manage_sx: calling acpi driver (handle %p)" " with %x\n", (void *)dc->lh, dc->m_un.kio.iowr)) ret = ldi_ioctl(dc->lh, dc->m_un.kio.iowr, (intptr_t)s3ap, FWRITE | FKIOCTL, kcred, NULL); break; case S4: /* S4 is not supported yet */ return (EINVAL); default: ASSERT(0); } return (ret); } /* * Search enable/disable lists, which are encoded in ppm.conf as an array * of char strings. */ static int ppm_search_list(pm_searchargs_t *sl) { int i; int flags = DDI_PROP_DONTPASS; ppm_unit_t *unitp = ddi_get_soft_state(ppm_statep, ppm_inst); char **pp; char *starp; uint_t nelements; char *manuf = sl->pms_manufacturer; char *prod = sl->pms_product; if (ddi_prop_lookup_string_array(DDI_DEV_T_ANY, unitp->dip, flags, sl->pms_listname, &pp, &nelements) != DDI_PROP_SUCCESS) { PPMD(D_CPR, ("ppm_search_list prop lookup %s failed--EINVAL\n", sl->pms_listname)) return (EINVAL); } ASSERT((nelements & 1) == 0); /* must be even */ PPMD(D_CPR, ("ppm_search_list looking for %s, %s\n", manuf, prod)) for (i = 0; i < nelements; i += 2) { PPMD(D_CPR, ("checking %s, %s", pp[i], pp[i+1])) /* we support only a trailing '*' pattern match */ if ((starp = strchr(pp[i], '*')) != NULL && *(starp + 1) == 0) { /* LINTED - ptrdiff overflow */ if (strncmp(manuf, pp[i], (starp - pp[i])) != 0) { PPMD(D_CPR, (" no match %s with %s\n", manuf, pp[i + 1])) continue; } } if ((starp = strchr(pp[i + 1], '*')) != NULL && *(starp + 1) == 0) { if (strncmp(prod, /* LINTED - ptrdiff overflow */ pp[i + 1], (starp - pp[i + 1])) != 0) { PPMD(D_CPR, (" no match %s with %s\n", prod, pp[i + 1])) continue; } } if (strcmp(manuf, pp[i]) == 0 && (strcmp(prod, pp[i + 1]) == 0)) { PPMD(D_CPR, (" match\n")) ddi_prop_free(pp); return (0); } PPMD(D_CPR, (" no match %s with %s or %s with %s\n", manuf, pp[i], prod, pp[i + 1])) } ddi_prop_free(pp); return (ENODEV); }