173347b07SNate Lawson /*- 273347b07SNate Lawson * Copyright (c) 2004-2005 Nate Lawson (SDG) 373347b07SNate Lawson * All rights reserved. 473347b07SNate Lawson * 573347b07SNate Lawson * Redistribution and use in source and binary forms, with or without 673347b07SNate Lawson * modification, are permitted provided that the following conditions 773347b07SNate Lawson * are met: 873347b07SNate Lawson * 1. Redistributions of source code must retain the above copyright 973347b07SNate Lawson * notice, this list of conditions and the following disclaimer. 1073347b07SNate Lawson * 2. Redistributions in binary form must reproduce the above copyright 1173347b07SNate Lawson * notice, this list of conditions and the following disclaimer in the 1273347b07SNate Lawson * documentation and/or other materials provided with the distribution. 1373347b07SNate Lawson * 1473347b07SNate Lawson * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 1573347b07SNate Lawson * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 1673347b07SNate Lawson * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 1773347b07SNate Lawson * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 1873347b07SNate Lawson * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 1973347b07SNate Lawson * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 2073347b07SNate Lawson * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 2173347b07SNate Lawson * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 2273347b07SNate Lawson * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 2373347b07SNate Lawson * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 2473347b07SNate Lawson * SUCH DAMAGE. 2573347b07SNate Lawson */ 2673347b07SNate Lawson 2773347b07SNate Lawson #include <sys/cdefs.h> 2873347b07SNate Lawson __FBSDID("$FreeBSD$"); 2973347b07SNate Lawson 3073347b07SNate Lawson #include <sys/param.h> 3173347b07SNate Lawson #include <sys/bus.h> 3273347b07SNate Lawson #include <sys/cpu.h> 3373347b07SNate Lawson #include <sys/eventhandler.h> 3473347b07SNate Lawson #include <sys/kernel.h> 3573347b07SNate Lawson #include <sys/malloc.h> 3673347b07SNate Lawson #include <sys/module.h> 3773347b07SNate Lawson #include <sys/proc.h> 3873347b07SNate Lawson #include <sys/queue.h> 3973347b07SNate Lawson #include <sys/sched.h> 4073347b07SNate Lawson #include <sys/sysctl.h> 4173347b07SNate Lawson #include <sys/systm.h> 4273347b07SNate Lawson #include <sys/sbuf.h> 4373347b07SNate Lawson 4473347b07SNate Lawson #include "cpufreq_if.h" 4573347b07SNate Lawson 4673347b07SNate Lawson /* 4773347b07SNate Lawson * Common CPU frequency glue code. Drivers for specific hardware can 4873347b07SNate Lawson * attach this interface to allow users to get/set the CPU frequency. 4973347b07SNate Lawson */ 5073347b07SNate Lawson 5173347b07SNate Lawson /* 5273347b07SNate Lawson * Number of levels we can handle. Levels are synthesized from settings 5373347b07SNate Lawson * so for N settings there may be N^2 levels. 5473347b07SNate Lawson */ 5573347b07SNate Lawson #define CF_MAX_LEVELS 32 5673347b07SNate Lawson 5773347b07SNate Lawson struct cpufreq_softc { 5873347b07SNate Lawson struct cf_level curr_level; 5973347b07SNate Lawson int priority; 6073347b07SNate Lawson struct cf_level_lst all_levels; 6173347b07SNate Lawson device_t dev; 6273347b07SNate Lawson struct sysctl_ctx_list sysctl_ctx; 6373347b07SNate Lawson }; 6473347b07SNate Lawson 6573347b07SNate Lawson struct cf_setting_array { 6673347b07SNate Lawson struct cf_setting sets[MAX_SETTINGS]; 6773347b07SNate Lawson int count; 6873347b07SNate Lawson TAILQ_ENTRY(cf_setting_array) link; 6973347b07SNate Lawson }; 7073347b07SNate Lawson 7173347b07SNate Lawson TAILQ_HEAD(cf_setting_lst, cf_setting_array); 7273347b07SNate Lawson 7373347b07SNate Lawson static int cpufreq_attach(device_t dev); 7473347b07SNate Lawson static int cpufreq_detach(device_t dev); 7573347b07SNate Lawson static void cpufreq_evaluate(void *arg); 7673347b07SNate Lawson static int cf_set_method(device_t dev, const struct cf_level *level, 7773347b07SNate Lawson int priority); 7873347b07SNate Lawson static int cf_get_method(device_t dev, struct cf_level *level); 7973347b07SNate Lawson static int cf_levels_method(device_t dev, struct cf_level *levels, 8073347b07SNate Lawson int *count); 8173347b07SNate Lawson static int cpufreq_insert_abs(struct cf_level_lst *list, 8273347b07SNate Lawson struct cf_setting *sets, int count); 8373347b07SNate Lawson static int cpufreq_curr_sysctl(SYSCTL_HANDLER_ARGS); 8473347b07SNate Lawson static int cpufreq_levels_sysctl(SYSCTL_HANDLER_ARGS); 8573347b07SNate Lawson 8673347b07SNate Lawson static device_method_t cpufreq_methods[] = { 8773347b07SNate Lawson DEVMETHOD(device_probe, bus_generic_probe), 8873347b07SNate Lawson DEVMETHOD(device_attach, cpufreq_attach), 8973347b07SNate Lawson DEVMETHOD(device_detach, cpufreq_detach), 9073347b07SNate Lawson 9173347b07SNate Lawson DEVMETHOD(cpufreq_set, cf_set_method), 9273347b07SNate Lawson DEVMETHOD(cpufreq_get, cf_get_method), 9373347b07SNate Lawson DEVMETHOD(cpufreq_levels, cf_levels_method), 9473347b07SNate Lawson {0, 0} 9573347b07SNate Lawson }; 9673347b07SNate Lawson static driver_t cpufreq_driver = { 9773347b07SNate Lawson "cpufreq", cpufreq_methods, sizeof(struct cpufreq_softc) 9873347b07SNate Lawson }; 9973347b07SNate Lawson static devclass_t cpufreq_dc; 10073347b07SNate Lawson DRIVER_MODULE(cpufreq, cpu, cpufreq_driver, cpufreq_dc, 0, 0); 10173347b07SNate Lawson 10273347b07SNate Lawson static eventhandler_tag cf_ev_tag; 10373347b07SNate Lawson 10473347b07SNate Lawson static int 10573347b07SNate Lawson cpufreq_attach(device_t dev) 10673347b07SNate Lawson { 10773347b07SNate Lawson struct cpufreq_softc *sc; 10873347b07SNate Lawson device_t parent; 10973347b07SNate Lawson int numdevs; 11073347b07SNate Lawson 11173347b07SNate Lawson sc = device_get_softc(dev); 11273347b07SNate Lawson parent = device_get_parent(dev); 11373347b07SNate Lawson sc->dev = dev; 11473347b07SNate Lawson sysctl_ctx_init(&sc->sysctl_ctx); 11573347b07SNate Lawson TAILQ_INIT(&sc->all_levels); 11673347b07SNate Lawson sc->curr_level.total_set.freq = CPUFREQ_VAL_UNKNOWN; 11773347b07SNate Lawson 11873347b07SNate Lawson /* 11973347b07SNate Lawson * Only initialize one set of sysctls for all CPUs. In the future, 12073347b07SNate Lawson * if multiple CPUs can have different settings, we can move these 12173347b07SNate Lawson * sysctls to be under every CPU instead of just the first one. 12273347b07SNate Lawson */ 12373347b07SNate Lawson numdevs = devclass_get_count(cpufreq_dc); 12473347b07SNate Lawson if (numdevs > 1) 12573347b07SNate Lawson return (0); 12673347b07SNate Lawson 12773347b07SNate Lawson SYSCTL_ADD_PROC(&sc->sysctl_ctx, 12873347b07SNate Lawson SYSCTL_CHILDREN(device_get_sysctl_tree(parent)), 12973347b07SNate Lawson OID_AUTO, "freq", CTLTYPE_INT | CTLFLAG_RW, sc, 0, 13073347b07SNate Lawson cpufreq_curr_sysctl, "I", "Current CPU frequency"); 13173347b07SNate Lawson SYSCTL_ADD_PROC(&sc->sysctl_ctx, 13273347b07SNate Lawson SYSCTL_CHILDREN(device_get_sysctl_tree(parent)), 13373347b07SNate Lawson OID_AUTO, "freq_levels", CTLTYPE_STRING | CTLFLAG_RD, sc, 0, 13473347b07SNate Lawson cpufreq_levels_sysctl, "A", "CPU frequency levels"); 13573347b07SNate Lawson cf_ev_tag = EVENTHANDLER_REGISTER(cpufreq_changed, cpufreq_evaluate, 13673347b07SNate Lawson NULL, EVENTHANDLER_PRI_ANY); 13773347b07SNate Lawson 13873347b07SNate Lawson return (0); 13973347b07SNate Lawson } 14073347b07SNate Lawson 14173347b07SNate Lawson static int 14273347b07SNate Lawson cpufreq_detach(device_t dev) 14373347b07SNate Lawson { 14473347b07SNate Lawson struct cpufreq_softc *sc; 14573347b07SNate Lawson int numdevs; 14673347b07SNate Lawson 14773347b07SNate Lawson sc = device_get_softc(dev); 14873347b07SNate Lawson sysctl_ctx_free(&sc->sysctl_ctx); 14973347b07SNate Lawson 15073347b07SNate Lawson /* Only clean up these resources when the last device is detaching. */ 15173347b07SNate Lawson numdevs = devclass_get_count(cpufreq_dc); 15273347b07SNate Lawson if (numdevs == 1) 15373347b07SNate Lawson EVENTHANDLER_DEREGISTER(cpufreq_changed, cf_ev_tag); 15473347b07SNate Lawson 15573347b07SNate Lawson return (0); 15673347b07SNate Lawson } 15773347b07SNate Lawson 15873347b07SNate Lawson static void 15973347b07SNate Lawson cpufreq_evaluate(void *arg) 16073347b07SNate Lawson { 16173347b07SNate Lawson /* TODO: Re-evaluate when notified of changes to drivers. */ 16273347b07SNate Lawson } 16373347b07SNate Lawson 16473347b07SNate Lawson static int 16573347b07SNate Lawson cf_set_method(device_t dev, const struct cf_level *level, int priority) 16673347b07SNate Lawson { 16773347b07SNate Lawson struct cpufreq_softc *sc; 16873347b07SNate Lawson const struct cf_setting *set; 16973347b07SNate Lawson int error; 17073347b07SNate Lawson 17173347b07SNate Lawson sc = device_get_softc(dev); 17273347b07SNate Lawson 17373347b07SNate Lawson /* If already at this level, just return. */ 17473347b07SNate Lawson if (CPUFREQ_CMP(sc->curr_level.total_set.freq, level->total_set.freq)) 17573347b07SNate Lawson return (0); 17673347b07SNate Lawson 17773347b07SNate Lawson /* First, set the absolute frequency via its driver. */ 17873347b07SNate Lawson set = &level->abs_set; 17973347b07SNate Lawson if (set->dev) { 18073347b07SNate Lawson if (!device_is_attached(set->dev)) { 18173347b07SNate Lawson error = ENXIO; 18273347b07SNate Lawson goto out; 18373347b07SNate Lawson } 18473347b07SNate Lawson error = CPUFREQ_DRV_SET(set->dev, set); 18573347b07SNate Lawson if (error) { 18673347b07SNate Lawson goto out; 18773347b07SNate Lawson } 18873347b07SNate Lawson } 18973347b07SNate Lawson 19073347b07SNate Lawson /* TODO: Next, set any/all relative frequencies via their drivers. */ 19173347b07SNate Lawson 19273347b07SNate Lawson /* Record the current level. */ 19373347b07SNate Lawson sc->curr_level = *level; 19473347b07SNate Lawson sc->priority = priority; 19573347b07SNate Lawson error = 0; 19673347b07SNate Lawson 19773347b07SNate Lawson out: 19873347b07SNate Lawson if (error) 19973347b07SNate Lawson device_printf(set->dev, "set freq failed, err %d\n", error); 20073347b07SNate Lawson return (error); 20173347b07SNate Lawson } 20273347b07SNate Lawson 20373347b07SNate Lawson static int 20473347b07SNate Lawson cf_get_method(device_t dev, struct cf_level *level) 20573347b07SNate Lawson { 20673347b07SNate Lawson struct cpufreq_softc *sc; 20773347b07SNate Lawson struct cf_level *levels; 20873347b07SNate Lawson struct cf_setting *curr_set, set; 20973347b07SNate Lawson struct pcpu *pc; 21073347b07SNate Lawson device_t *devs; 21173347b07SNate Lawson int count, error, i, numdevs; 21273347b07SNate Lawson uint64_t rate; 21373347b07SNate Lawson 21473347b07SNate Lawson sc = device_get_softc(dev); 21573347b07SNate Lawson curr_set = &sc->curr_level.total_set; 21673347b07SNate Lawson levels = NULL; 21773347b07SNate Lawson 21873347b07SNate Lawson /* If we already know the current frequency, we're done. */ 21973347b07SNate Lawson if (curr_set->freq != CPUFREQ_VAL_UNKNOWN) 22073347b07SNate Lawson goto out; 22173347b07SNate Lawson 22273347b07SNate Lawson /* 22373347b07SNate Lawson * We need to figure out the current level. Loop through every 22473347b07SNate Lawson * driver, getting the current setting. Then, attempt to get a best 22573347b07SNate Lawson * match of settings against each level. 22673347b07SNate Lawson */ 22773347b07SNate Lawson count = CF_MAX_LEVELS; 22873347b07SNate Lawson levels = malloc(count * sizeof(*levels), M_TEMP, M_NOWAIT); 22973347b07SNate Lawson if (levels == NULL) 23073347b07SNate Lawson return (ENOMEM); 23173347b07SNate Lawson error = CPUFREQ_LEVELS(sc->dev, levels, &count); 23273347b07SNate Lawson if (error) 23373347b07SNate Lawson goto out; 23473347b07SNate Lawson error = device_get_children(device_get_parent(dev), &devs, &numdevs); 23573347b07SNate Lawson if (error) 23673347b07SNate Lawson goto out; 23773347b07SNate Lawson for (i = 0; i < numdevs && curr_set->freq == CPUFREQ_VAL_UNKNOWN; i++) { 23873347b07SNate Lawson if (!device_is_attached(devs[i])) 23973347b07SNate Lawson continue; 24073347b07SNate Lawson error = CPUFREQ_DRV_GET(devs[i], &set); 24173347b07SNate Lawson if (error) 24273347b07SNate Lawson continue; 24373347b07SNate Lawson for (i = 0; i < count; i++) { 24473347b07SNate Lawson if (CPUFREQ_CMP(set.freq, levels[i].abs_set.freq)) { 24573347b07SNate Lawson sc->curr_level = levels[i]; 24673347b07SNate Lawson break; 24773347b07SNate Lawson } 24873347b07SNate Lawson } 24973347b07SNate Lawson } 25073347b07SNate Lawson free(devs, M_TEMP); 25173347b07SNate Lawson if (curr_set->freq != CPUFREQ_VAL_UNKNOWN) 25273347b07SNate Lawson goto out; 25373347b07SNate Lawson 25473347b07SNate Lawson /* 25573347b07SNate Lawson * We couldn't find an exact match, so attempt to estimate and then 25673347b07SNate Lawson * match against a level. 25773347b07SNate Lawson */ 25873347b07SNate Lawson pc = cpu_get_pcpu(dev); 25973347b07SNate Lawson if (pc == NULL) { 26073347b07SNate Lawson error = ENXIO; 26173347b07SNate Lawson goto out; 26273347b07SNate Lawson } 26373347b07SNate Lawson cpu_est_clockrate(pc->pc_cpuid, &rate); 26473347b07SNate Lawson rate /= 1000000; 26573347b07SNate Lawson for (i = 0; i < count; i++) { 26673347b07SNate Lawson if (CPUFREQ_CMP(rate, levels[i].total_set.freq)) { 26773347b07SNate Lawson sc->curr_level = levels[i]; 26873347b07SNate Lawson break; 26973347b07SNate Lawson } 27073347b07SNate Lawson } 27173347b07SNate Lawson 27273347b07SNate Lawson out: 27373347b07SNate Lawson if (levels) 27473347b07SNate Lawson free(levels, M_TEMP); 27573347b07SNate Lawson *level = sc->curr_level; 27673347b07SNate Lawson return (0); 27773347b07SNate Lawson } 27873347b07SNate Lawson 27973347b07SNate Lawson static int 28073347b07SNate Lawson cf_levels_method(device_t dev, struct cf_level *levels, int *count) 28173347b07SNate Lawson { 28273347b07SNate Lawson struct cf_setting_lst rel_sets; 28373347b07SNate Lawson struct cpufreq_softc *sc; 28473347b07SNate Lawson struct cf_level *lev; 28573347b07SNate Lawson struct cf_setting *sets; 28673347b07SNate Lawson struct pcpu *pc; 28773347b07SNate Lawson device_t *devs; 28873347b07SNate Lawson int error, i, numdevs, numlevels, set_count, type; 28973347b07SNate Lawson uint64_t rate; 29073347b07SNate Lawson 29173347b07SNate Lawson if (levels == NULL || count == NULL) 29273347b07SNate Lawson return (EINVAL); 29373347b07SNate Lawson 29473347b07SNate Lawson TAILQ_INIT(&rel_sets); 29573347b07SNate Lawson sc = device_get_softc(dev); 29673347b07SNate Lawson error = device_get_children(device_get_parent(dev), &devs, &numdevs); 29773347b07SNate Lawson if (error) 29873347b07SNate Lawson return (error); 29973347b07SNate Lawson sets = malloc(MAX_SETTINGS * sizeof(*sets), M_TEMP, M_NOWAIT); 30073347b07SNate Lawson if (sets == NULL) { 30173347b07SNate Lawson free(devs, M_TEMP); 30273347b07SNate Lawson return (ENOMEM); 30373347b07SNate Lawson } 30473347b07SNate Lawson 30573347b07SNate Lawson /* Get settings from all cpufreq drivers. */ 30673347b07SNate Lawson numlevels = 0; 30773347b07SNate Lawson for (i = 0; i < numdevs; i++) { 30873347b07SNate Lawson if (!device_is_attached(devs[i])) 30973347b07SNate Lawson continue; 31073347b07SNate Lawson set_count = MAX_SETTINGS; 31173347b07SNate Lawson error = CPUFREQ_DRV_SETTINGS(devs[i], sets, &set_count, &type); 31273347b07SNate Lawson if (error || set_count == 0) 31373347b07SNate Lawson continue; 31473347b07SNate Lawson error = cpufreq_insert_abs(&sc->all_levels, sets, set_count); 31573347b07SNate Lawson if (error) 31673347b07SNate Lawson goto out; 31773347b07SNate Lawson numlevels += set_count; 31873347b07SNate Lawson } 31973347b07SNate Lawson 32073347b07SNate Lawson /* If the caller doesn't have enough space, return the actual count. */ 32173347b07SNate Lawson if (numlevels > *count) { 32273347b07SNate Lawson *count = numlevels; 32373347b07SNate Lawson error = E2BIG; 32473347b07SNate Lawson goto out; 32573347b07SNate Lawson } 32673347b07SNate Lawson 32773347b07SNate Lawson /* If there are no absolute levels, create a fake one at 100%. */ 32873347b07SNate Lawson if (TAILQ_EMPTY(&sc->all_levels)) { 32973347b07SNate Lawson bzero(&sets[0], sizeof(*sets)); 33073347b07SNate Lawson pc = cpu_get_pcpu(dev); 33173347b07SNate Lawson if (pc == NULL) { 33273347b07SNate Lawson error = ENXIO; 33373347b07SNate Lawson goto out; 33473347b07SNate Lawson } 33573347b07SNate Lawson cpu_est_clockrate(pc->pc_cpuid, &rate); 33673347b07SNate Lawson sets[0].freq = rate / 1000000; 33773347b07SNate Lawson error = cpufreq_insert_abs(&sc->all_levels, sets, 1); 33873347b07SNate Lawson if (error) 33973347b07SNate Lawson goto out; 34073347b07SNate Lawson } 34173347b07SNate Lawson 34273347b07SNate Lawson /* TODO: Create a combined list of absolute + relative levels. */ 34373347b07SNate Lawson i = 0; 34473347b07SNate Lawson TAILQ_FOREACH(lev, &sc->all_levels, link) { 34573347b07SNate Lawson /* For now, just assume total freq equals absolute freq. */ 34673347b07SNate Lawson lev->total_set = lev->abs_set; 34773347b07SNate Lawson lev->total_set.dev = NULL; 34873347b07SNate Lawson levels[i] = *lev; 34973347b07SNate Lawson i++; 35073347b07SNate Lawson } 35173347b07SNate Lawson *count = i; 35273347b07SNate Lawson error = 0; 35373347b07SNate Lawson 35473347b07SNate Lawson out: 35573347b07SNate Lawson /* Clear all levels since we regenerate them each time. */ 35673347b07SNate Lawson while ((lev = TAILQ_FIRST(&sc->all_levels)) != NULL) { 35773347b07SNate Lawson TAILQ_REMOVE(&sc->all_levels, lev, link); 35873347b07SNate Lawson free(lev, M_TEMP); 35973347b07SNate Lawson } 36073347b07SNate Lawson free(devs, M_TEMP); 36173347b07SNate Lawson free(sets, M_TEMP); 36273347b07SNate Lawson return (error); 36373347b07SNate Lawson } 36473347b07SNate Lawson 36573347b07SNate Lawson /* 36673347b07SNate Lawson * Create levels for an array of absolute settings and insert them in 36773347b07SNate Lawson * sorted order in the specified list. 36873347b07SNate Lawson */ 36973347b07SNate Lawson static int 37073347b07SNate Lawson cpufreq_insert_abs(struct cf_level_lst *list, struct cf_setting *sets, 37173347b07SNate Lawson int count) 37273347b07SNate Lawson { 37373347b07SNate Lawson struct cf_level *level, *search; 37473347b07SNate Lawson int i; 37573347b07SNate Lawson 37673347b07SNate Lawson for (i = 0; i < count; i++) { 37773347b07SNate Lawson level = malloc(sizeof(*level), M_TEMP, M_NOWAIT | M_ZERO); 37873347b07SNate Lawson if (level == NULL) 37973347b07SNate Lawson return (ENOMEM); 38073347b07SNate Lawson level->abs_set = sets[i]; 38173347b07SNate Lawson 38273347b07SNate Lawson if (TAILQ_EMPTY(list)) { 38373347b07SNate Lawson TAILQ_INSERT_HEAD(list, level, link); 38473347b07SNate Lawson continue; 38573347b07SNate Lawson } 38673347b07SNate Lawson 38773347b07SNate Lawson TAILQ_FOREACH_REVERSE(search, list, cf_level_lst, link) { 38873347b07SNate Lawson if (sets[i].freq <= search->abs_set.freq) { 38973347b07SNate Lawson TAILQ_INSERT_AFTER(list, search, level, link); 39073347b07SNate Lawson break; 39173347b07SNate Lawson } 39273347b07SNate Lawson } 39373347b07SNate Lawson } 39473347b07SNate Lawson return (0); 39573347b07SNate Lawson } 39673347b07SNate Lawson 39773347b07SNate Lawson static int 39873347b07SNate Lawson cpufreq_curr_sysctl(SYSCTL_HANDLER_ARGS) 39973347b07SNate Lawson { 40073347b07SNate Lawson struct cpufreq_softc *sc; 40173347b07SNate Lawson struct cf_level *levels; 40273347b07SNate Lawson int count, error, freq, i; 40373347b07SNate Lawson 40473347b07SNate Lawson sc = oidp->oid_arg1; 40573347b07SNate Lawson count = CF_MAX_LEVELS; 40673347b07SNate Lawson levels = malloc(count * sizeof(*levels), M_TEMP, M_NOWAIT); 40773347b07SNate Lawson if (levels == NULL) 40873347b07SNate Lawson return (ENOMEM); 40973347b07SNate Lawson 41073347b07SNate Lawson error = CPUFREQ_GET(sc->dev, &levels[0]); 41173347b07SNate Lawson if (error) 41273347b07SNate Lawson goto out; 41373347b07SNate Lawson freq = levels[0].total_set.freq; 41473347b07SNate Lawson error = sysctl_handle_int(oidp, &freq, 0, req); 41573347b07SNate Lawson if (error != 0 || req->newptr == NULL) 41673347b07SNate Lawson goto out; 41773347b07SNate Lawson 41873347b07SNate Lawson error = CPUFREQ_LEVELS(sc->dev, levels, &count); 41973347b07SNate Lawson if (error) 42073347b07SNate Lawson goto out; 42173347b07SNate Lawson for (i = 0; i < count; i++) { 42273347b07SNate Lawson if (CPUFREQ_CMP(levels[i].total_set.freq, freq)) { 42373347b07SNate Lawson error = CPUFREQ_SET(sc->dev, &levels[i], 42473347b07SNate Lawson CPUFREQ_PRIO_USER); 42573347b07SNate Lawson break; 42673347b07SNate Lawson } 42773347b07SNate Lawson } 42873347b07SNate Lawson if (i == count) 42973347b07SNate Lawson error = EINVAL; 43073347b07SNate Lawson 43173347b07SNate Lawson out: 43273347b07SNate Lawson if (levels) 43373347b07SNate Lawson free(levels, M_TEMP); 43473347b07SNate Lawson return (error); 43573347b07SNate Lawson } 43673347b07SNate Lawson 43773347b07SNate Lawson static int 43873347b07SNate Lawson cpufreq_levels_sysctl(SYSCTL_HANDLER_ARGS) 43973347b07SNate Lawson { 44073347b07SNate Lawson struct cpufreq_softc *sc; 44173347b07SNate Lawson struct cf_level *levels; 44273347b07SNate Lawson struct cf_setting *set; 44373347b07SNate Lawson struct sbuf sb; 44473347b07SNate Lawson int count, error, i; 44573347b07SNate Lawson 44673347b07SNate Lawson sc = oidp->oid_arg1; 44773347b07SNate Lawson sbuf_new(&sb, NULL, 128, SBUF_AUTOEXTEND); 44873347b07SNate Lawson 44973347b07SNate Lawson /* Get settings from the device and generate the output string. */ 45073347b07SNate Lawson count = CF_MAX_LEVELS; 45173347b07SNate Lawson levels = malloc(count * sizeof(*levels), M_TEMP, M_NOWAIT); 45273347b07SNate Lawson if (levels == NULL) 45373347b07SNate Lawson return (ENOMEM); 45473347b07SNate Lawson error = CPUFREQ_LEVELS(sc->dev, levels, &count); 45573347b07SNate Lawson if (error) 45673347b07SNate Lawson goto out; 45773347b07SNate Lawson if (count) { 45873347b07SNate Lawson for (i = 0; i < count; i++) { 45973347b07SNate Lawson set = &levels[i].total_set; 46073347b07SNate Lawson sbuf_printf(&sb, "%d/%d ", set->freq, set->power); 46173347b07SNate Lawson } 46273347b07SNate Lawson } else 46373347b07SNate Lawson sbuf_cpy(&sb, "0"); 46473347b07SNate Lawson sbuf_trim(&sb); 46573347b07SNate Lawson sbuf_finish(&sb); 46673347b07SNate Lawson error = sysctl_handle_string(oidp, sbuf_data(&sb), sbuf_len(&sb), req); 46773347b07SNate Lawson 46873347b07SNate Lawson out: 46973347b07SNate Lawson free(levels, M_TEMP); 47073347b07SNate Lawson sbuf_delete(&sb); 47173347b07SNate Lawson return (error); 47273347b07SNate Lawson } 47373347b07SNate Lawson 47473347b07SNate Lawson int 47573347b07SNate Lawson cpufreq_register(device_t dev) 47673347b07SNate Lawson { 47773347b07SNate Lawson device_t cf_dev, cpu_dev; 47873347b07SNate Lawson 47973347b07SNate Lawson /* 48073347b07SNate Lawson * Only add one cpufreq device (on cpu0) for all control. Once 48173347b07SNate Lawson * independent multi-cpu control appears, we can assign one cpufreq 48273347b07SNate Lawson * device per cpu. 48373347b07SNate Lawson */ 48473347b07SNate Lawson cf_dev = devclass_get_device(cpufreq_dc, 0); 48573347b07SNate Lawson if (cf_dev) { 48673347b07SNate Lawson device_printf(dev, 48773347b07SNate Lawson "warning: only one cpufreq device at a time supported\n"); 48873347b07SNate Lawson return (0); 48973347b07SNate Lawson } 49073347b07SNate Lawson 49173347b07SNate Lawson /* Add the child device and sysctls. */ 49273347b07SNate Lawson cpu_dev = devclass_get_device(devclass_find("cpu"), 0); 49373347b07SNate Lawson cf_dev = BUS_ADD_CHILD(cpu_dev, 0, "cpufreq", 0); 49473347b07SNate Lawson if (cf_dev == NULL) 49573347b07SNate Lawson return (ENOMEM); 49673347b07SNate Lawson device_quiet(cf_dev); 49773347b07SNate Lawson 49873347b07SNate Lawson return (device_probe_and_attach(cf_dev)); 49973347b07SNate Lawson } 50073347b07SNate Lawson 50173347b07SNate Lawson int 50273347b07SNate Lawson cpufreq_unregister(device_t dev) 50373347b07SNate Lawson { 50473347b07SNate Lawson device_t cf_dev, *devs; 50573347b07SNate Lawson int cfcount, count, devcount, error, i, type; 50673347b07SNate Lawson struct cf_setting set; 50773347b07SNate Lawson 50873347b07SNate Lawson /* 50973347b07SNate Lawson * If this is the last cpufreq child device, remove the control 51073347b07SNate Lawson * device as well. We identify cpufreq children by calling a method 51173347b07SNate Lawson * they support. 51273347b07SNate Lawson */ 51373347b07SNate Lawson error = device_get_children(device_get_parent(dev), &devs, &devcount); 51473347b07SNate Lawson if (error) 51573347b07SNate Lawson return (error); 51673347b07SNate Lawson cf_dev = devclass_get_device(cpufreq_dc, 0); 51773347b07SNate Lawson KASSERT(cf_dev != NULL, ("unregister with no cpufreq dev")); 51873347b07SNate Lawson cfcount = 0; 51973347b07SNate Lawson for (i = 0; i < devcount; i++) { 52073347b07SNate Lawson if (!device_is_attached(devs[i])) 52173347b07SNate Lawson continue; 52273347b07SNate Lawson count = 1; 52373347b07SNate Lawson if (CPUFREQ_DRV_SETTINGS(devs[i], &set, &count, &type) == 0) 52473347b07SNate Lawson cfcount++; 52573347b07SNate Lawson } 52673347b07SNate Lawson if (cfcount <= 1) { 52773347b07SNate Lawson device_delete_child(device_get_parent(cf_dev), cf_dev); 52873347b07SNate Lawson } 52973347b07SNate Lawson free(devs, M_TEMP); 53073347b07SNate Lawson 53173347b07SNate Lawson return (0); 53273347b07SNate Lawson } 533