xref: /freebsd/sys/kern/kern_cpu.c (revision 5f0afa04155e5d4d1ba47d114134f191d4ad9540)
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>
430325089dSNate Lawson #include <sys/timetc.h>
4473347b07SNate Lawson 
4573347b07SNate Lawson #include "cpufreq_if.h"
4673347b07SNate Lawson 
4773347b07SNate Lawson /*
4873347b07SNate Lawson  * Common CPU frequency glue code.  Drivers for specific hardware can
4973347b07SNate Lawson  * attach this interface to allow users to get/set the CPU frequency.
5073347b07SNate Lawson  */
5173347b07SNate Lawson 
5273347b07SNate Lawson /*
5373347b07SNate Lawson  * Number of levels we can handle.  Levels are synthesized from settings
5473347b07SNate Lawson  * so for N settings there may be N^2 levels.
5573347b07SNate Lawson  */
5673347b07SNate Lawson #define CF_MAX_LEVELS	32
5773347b07SNate Lawson 
5873347b07SNate Lawson struct cpufreq_softc {
5973347b07SNate Lawson 	struct cf_level			curr_level;
605f0afa04SNate Lawson 	int				curr_priority;
615f0afa04SNate Lawson 	struct cf_level			saved_level;
625f0afa04SNate Lawson 	int				saved_priority;
6373347b07SNate Lawson 	struct cf_level_lst		all_levels;
645f0afa04SNate Lawson 	int				all_count;
6573347b07SNate Lawson 	device_t			dev;
6673347b07SNate Lawson 	struct sysctl_ctx_list		sysctl_ctx;
6773347b07SNate Lawson };
6873347b07SNate Lawson 
6973347b07SNate Lawson struct cf_setting_array {
7073347b07SNate Lawson 	struct cf_setting		sets[MAX_SETTINGS];
7173347b07SNate Lawson 	int				count;
7273347b07SNate Lawson 	TAILQ_ENTRY(cf_setting_array)	link;
7373347b07SNate Lawson };
7473347b07SNate Lawson 
7573347b07SNate Lawson TAILQ_HEAD(cf_setting_lst, cf_setting_array);
7673347b07SNate Lawson 
7773347b07SNate Lawson static int	cpufreq_attach(device_t dev);
7873347b07SNate Lawson static int	cpufreq_detach(device_t dev);
7973347b07SNate Lawson static void	cpufreq_evaluate(void *arg);
8073347b07SNate Lawson static int	cf_set_method(device_t dev, const struct cf_level *level,
8173347b07SNate Lawson 		    int priority);
8273347b07SNate Lawson static int	cf_get_method(device_t dev, struct cf_level *level);
8373347b07SNate Lawson static int	cf_levels_method(device_t dev, struct cf_level *levels,
8473347b07SNate Lawson 		    int *count);
8588c9b54cSNate Lawson static int	cpufreq_insert_abs(struct cpufreq_softc *sc,
8673347b07SNate Lawson 		    struct cf_setting *sets, int count);
8788c9b54cSNate Lawson static int	cpufreq_expand_set(struct cpufreq_softc *sc,
8888c9b54cSNate Lawson 		    struct cf_setting_array *set_arr);
8988c9b54cSNate Lawson static struct cf_level *cpufreq_dup_set(struct cpufreq_softc *sc,
9088c9b54cSNate Lawson 		    struct cf_level *dup, struct cf_setting *set);
9173347b07SNate Lawson static int	cpufreq_curr_sysctl(SYSCTL_HANDLER_ARGS);
9273347b07SNate Lawson static int	cpufreq_levels_sysctl(SYSCTL_HANDLER_ARGS);
9373347b07SNate Lawson 
9473347b07SNate Lawson static device_method_t cpufreq_methods[] = {
9573347b07SNate Lawson 	DEVMETHOD(device_probe,		bus_generic_probe),
9673347b07SNate Lawson 	DEVMETHOD(device_attach,	cpufreq_attach),
9773347b07SNate Lawson 	DEVMETHOD(device_detach,	cpufreq_detach),
9873347b07SNate Lawson 
9973347b07SNate Lawson         DEVMETHOD(cpufreq_set,		cf_set_method),
10073347b07SNate Lawson         DEVMETHOD(cpufreq_get,		cf_get_method),
10173347b07SNate Lawson         DEVMETHOD(cpufreq_levels,	cf_levels_method),
10273347b07SNate Lawson 	{0, 0}
10373347b07SNate Lawson };
10473347b07SNate Lawson static driver_t cpufreq_driver = {
10573347b07SNate Lawson 	"cpufreq", cpufreq_methods, sizeof(struct cpufreq_softc)
10673347b07SNate Lawson };
10773347b07SNate Lawson static devclass_t cpufreq_dc;
10873347b07SNate Lawson DRIVER_MODULE(cpufreq, cpu, cpufreq_driver, cpufreq_dc, 0, 0);
10973347b07SNate Lawson 
11073347b07SNate Lawson static eventhandler_tag cf_ev_tag;
11173347b07SNate Lawson 
11273347b07SNate Lawson static int
11373347b07SNate Lawson cpufreq_attach(device_t dev)
11473347b07SNate Lawson {
11573347b07SNate Lawson 	struct cpufreq_softc *sc;
11673347b07SNate Lawson 	device_t parent;
11773347b07SNate Lawson 	int numdevs;
11873347b07SNate Lawson 
11973347b07SNate Lawson 	sc = device_get_softc(dev);
12073347b07SNate Lawson 	parent = device_get_parent(dev);
12173347b07SNate Lawson 	sc->dev = dev;
12273347b07SNate Lawson 	sysctl_ctx_init(&sc->sysctl_ctx);
12373347b07SNate Lawson 	TAILQ_INIT(&sc->all_levels);
12473347b07SNate Lawson 	sc->curr_level.total_set.freq = CPUFREQ_VAL_UNKNOWN;
1255f0afa04SNate Lawson 	sc->saved_level.total_set.freq = CPUFREQ_VAL_UNKNOWN;
12673347b07SNate Lawson 
12773347b07SNate Lawson 	/*
12873347b07SNate Lawson 	 * Only initialize one set of sysctls for all CPUs.  In the future,
12973347b07SNate Lawson 	 * if multiple CPUs can have different settings, we can move these
13073347b07SNate Lawson 	 * sysctls to be under every CPU instead of just the first one.
13173347b07SNate Lawson 	 */
13273347b07SNate Lawson 	numdevs = devclass_get_count(cpufreq_dc);
13373347b07SNate Lawson 	if (numdevs > 1)
13473347b07SNate Lawson 		return (0);
13573347b07SNate Lawson 
13673347b07SNate Lawson 	SYSCTL_ADD_PROC(&sc->sysctl_ctx,
13773347b07SNate Lawson 	    SYSCTL_CHILDREN(device_get_sysctl_tree(parent)),
13873347b07SNate Lawson 	    OID_AUTO, "freq", CTLTYPE_INT | CTLFLAG_RW, sc, 0,
13973347b07SNate Lawson 	    cpufreq_curr_sysctl, "I", "Current CPU frequency");
14073347b07SNate Lawson 	SYSCTL_ADD_PROC(&sc->sysctl_ctx,
14173347b07SNate Lawson 	    SYSCTL_CHILDREN(device_get_sysctl_tree(parent)),
14273347b07SNate Lawson 	    OID_AUTO, "freq_levels", CTLTYPE_STRING | CTLFLAG_RD, sc, 0,
14373347b07SNate Lawson 	    cpufreq_levels_sysctl, "A", "CPU frequency levels");
14473347b07SNate Lawson 	cf_ev_tag = EVENTHANDLER_REGISTER(cpufreq_changed, cpufreq_evaluate,
14573347b07SNate Lawson 	    NULL, EVENTHANDLER_PRI_ANY);
14673347b07SNate Lawson 
14773347b07SNate Lawson 	return (0);
14873347b07SNate Lawson }
14973347b07SNate Lawson 
15073347b07SNate Lawson static int
15173347b07SNate Lawson cpufreq_detach(device_t dev)
15273347b07SNate Lawson {
15373347b07SNate Lawson 	struct cpufreq_softc *sc;
15473347b07SNate Lawson 	int numdevs;
15573347b07SNate Lawson 
15673347b07SNate Lawson 	sc = device_get_softc(dev);
15773347b07SNate Lawson 	sysctl_ctx_free(&sc->sysctl_ctx);
15873347b07SNate Lawson 
15973347b07SNate Lawson 	/* Only clean up these resources when the last device is detaching. */
16073347b07SNate Lawson 	numdevs = devclass_get_count(cpufreq_dc);
16173347b07SNate Lawson 	if (numdevs == 1)
16273347b07SNate Lawson 		EVENTHANDLER_DEREGISTER(cpufreq_changed, cf_ev_tag);
16373347b07SNate Lawson 
16473347b07SNate Lawson 	return (0);
16573347b07SNate Lawson }
16673347b07SNate Lawson 
16773347b07SNate Lawson static void
16873347b07SNate Lawson cpufreq_evaluate(void *arg)
16973347b07SNate Lawson {
17073347b07SNate Lawson 	/* TODO: Re-evaluate when notified of changes to drivers. */
17173347b07SNate Lawson }
17273347b07SNate Lawson 
17373347b07SNate Lawson static int
17473347b07SNate Lawson cf_set_method(device_t dev, const struct cf_level *level, int priority)
17573347b07SNate Lawson {
17673347b07SNate Lawson 	struct cpufreq_softc *sc;
17773347b07SNate Lawson 	const struct cf_setting *set;
1780325089dSNate Lawson 	struct pcpu *pc;
1790325089dSNate Lawson 	int cpu_id, error, i;
18073347b07SNate Lawson 
18173347b07SNate Lawson 	sc = device_get_softc(dev);
18273347b07SNate Lawson 
1830325089dSNate Lawson 	/*
1840325089dSNate Lawson 	 * Check that the TSC isn't being used as a timecounter.
1850325089dSNate Lawson 	 * If it is, then return EBUSY and refuse to change the
1860325089dSNate Lawson 	 * clock speed.
1870325089dSNate Lawson 	 */
1880325089dSNate Lawson 	if (strcmp(timecounter->tc_name, "TSC") == 0)
1890325089dSNate Lawson 		return (EBUSY);
1900325089dSNate Lawson 
1915f0afa04SNate Lawson 	/*
1925f0afa04SNate Lawson 	 * If the caller didn't specify a level and one is saved, prepare to
1935f0afa04SNate Lawson 	 * restore the saved level.  If none has been saved, return an error.
1945f0afa04SNate Lawson 	 * If they did specify one, but the requested level has a lower
1955f0afa04SNate Lawson 	 * priority, don't allow the new level right now.
1965f0afa04SNate Lawson 	 */
1975f0afa04SNate Lawson 	if (level == NULL) {
1985f0afa04SNate Lawson 		if (sc->saved_level.total_set.freq != CPUFREQ_VAL_UNKNOWN) {
1995f0afa04SNate Lawson 			level = &sc->saved_level;
2005f0afa04SNate Lawson 			priority = sc->saved_priority;
2015f0afa04SNate Lawson 		} else
2025f0afa04SNate Lawson 			return (ENXIO);
2035f0afa04SNate Lawson 	} else if (priority < sc->curr_priority)
2045f0afa04SNate Lawson 		return (EPERM);
2055f0afa04SNate Lawson 
20673347b07SNate Lawson 	/* If already at this level, just return. */
20773347b07SNate Lawson 	if (CPUFREQ_CMP(sc->curr_level.total_set.freq, level->total_set.freq))
20873347b07SNate Lawson 		return (0);
20973347b07SNate Lawson 
2100325089dSNate Lawson 	/* If the setting is for a different CPU, switch to it. */
2110325089dSNate Lawson 	cpu_id = PCPU_GET(cpuid);
2120325089dSNate Lawson 	pc = cpu_get_pcpu(dev);
2130325089dSNate Lawson 	KASSERT(pc, ("NULL pcpu for dev %p", dev));
2140325089dSNate Lawson 	if (cpu_id != pc->pc_cpuid) {
2150325089dSNate Lawson 		mtx_lock_spin(&sched_lock);
2160325089dSNate Lawson 		sched_bind(curthread, pc->pc_cpuid);
2170325089dSNate Lawson 		mtx_unlock_spin(&sched_lock);
2180325089dSNate Lawson 	}
2190325089dSNate Lawson 
22073347b07SNate Lawson 	/* First, set the absolute frequency via its driver. */
22173347b07SNate Lawson 	set = &level->abs_set;
22273347b07SNate Lawson 	if (set->dev) {
22373347b07SNate Lawson 		if (!device_is_attached(set->dev)) {
22473347b07SNate Lawson 			error = ENXIO;
22573347b07SNate Lawson 			goto out;
22673347b07SNate Lawson 		}
22773347b07SNate Lawson 		error = CPUFREQ_DRV_SET(set->dev, set);
22873347b07SNate Lawson 		if (error) {
22973347b07SNate Lawson 			goto out;
23073347b07SNate Lawson 		}
23173347b07SNate Lawson 	}
23273347b07SNate Lawson 
23388c9b54cSNate Lawson 	/* Next, set any/all relative frequencies via their drivers. */
23488c9b54cSNate Lawson 	for (i = 0; i < level->rel_count; i++) {
23588c9b54cSNate Lawson 		set = &level->rel_set[i];
23688c9b54cSNate Lawson 		if (!device_is_attached(set->dev)) {
23788c9b54cSNate Lawson 			error = ENXIO;
23888c9b54cSNate Lawson 			goto out;
23988c9b54cSNate Lawson 		}
24088c9b54cSNate Lawson 		error = CPUFREQ_DRV_SET(set->dev, set);
24188c9b54cSNate Lawson 		if (error) {
24288c9b54cSNate Lawson 			/* XXX Back out any successful setting? */
24388c9b54cSNate Lawson 			goto out;
24488c9b54cSNate Lawson 		}
24588c9b54cSNate Lawson 	}
24673347b07SNate Lawson 
2475f0afa04SNate Lawson 	/* If we were restoring a saved state, reset it to "unused". */
2485f0afa04SNate Lawson 	if (level == &sc->saved_level) {
2495f0afa04SNate Lawson 		sc->saved_level.total_set.freq = CPUFREQ_VAL_UNKNOWN;
2505f0afa04SNate Lawson 		sc->saved_priority = 0;
2515f0afa04SNate Lawson 	}
2525f0afa04SNate Lawson 
2535f0afa04SNate Lawson 	/*
2545f0afa04SNate Lawson 	 * Before recording the current level, check if we're going to a
2555f0afa04SNate Lawson 	 * higher priority and have not saved a level yet.  If so, save the
2565f0afa04SNate Lawson 	 * previous level and priority.
2575f0afa04SNate Lawson 	 */
2585f0afa04SNate Lawson 	if (sc->curr_level.total_set.freq != CPUFREQ_VAL_UNKNOWN &&
2595f0afa04SNate Lawson 	    sc->saved_level.total_set.freq == CPUFREQ_VAL_UNKNOWN &&
2605f0afa04SNate Lawson 	    priority > sc->curr_priority) {
2615f0afa04SNate Lawson 		sc->saved_level = sc->curr_level;
2625f0afa04SNate Lawson 		sc->saved_priority = sc->curr_priority;
2635f0afa04SNate Lawson 	}
26473347b07SNate Lawson 	sc->curr_level = *level;
2655f0afa04SNate Lawson 	sc->curr_priority = priority;
26673347b07SNate Lawson 	error = 0;
26773347b07SNate Lawson 
26873347b07SNate Lawson out:
2690325089dSNate Lawson 	/* If we switched to another CPU, switch back before exiting. */
2700325089dSNate Lawson 	if (cpu_id != pc->pc_cpuid) {
2710325089dSNate Lawson 		mtx_lock_spin(&sched_lock);
2720325089dSNate Lawson 		sched_unbind(curthread);
2730325089dSNate Lawson 		mtx_unlock_spin(&sched_lock);
2740325089dSNate Lawson 	}
27573347b07SNate Lawson 	if (error)
27673347b07SNate Lawson 		device_printf(set->dev, "set freq failed, err %d\n", error);
27773347b07SNate Lawson 	return (error);
27873347b07SNate Lawson }
27973347b07SNate Lawson 
28073347b07SNate Lawson static int
28173347b07SNate Lawson cf_get_method(device_t dev, struct cf_level *level)
28273347b07SNate Lawson {
28373347b07SNate Lawson 	struct cpufreq_softc *sc;
28473347b07SNate Lawson 	struct cf_level *levels;
28573347b07SNate Lawson 	struct cf_setting *curr_set, set;
28673347b07SNate Lawson 	struct pcpu *pc;
28773347b07SNate Lawson 	device_t *devs;
28873347b07SNate Lawson 	int count, error, i, numdevs;
28973347b07SNate Lawson 	uint64_t rate;
29073347b07SNate Lawson 
29173347b07SNate Lawson 	sc = device_get_softc(dev);
29273347b07SNate Lawson 	curr_set = &sc->curr_level.total_set;
29373347b07SNate Lawson 	levels = NULL;
29473347b07SNate Lawson 
29573347b07SNate Lawson 	/* If we already know the current frequency, we're done. */
29673347b07SNate Lawson 	if (curr_set->freq != CPUFREQ_VAL_UNKNOWN)
29773347b07SNate Lawson 		goto out;
29873347b07SNate Lawson 
29973347b07SNate Lawson 	/*
30073347b07SNate Lawson 	 * We need to figure out the current level.  Loop through every
30173347b07SNate Lawson 	 * driver, getting the current setting.  Then, attempt to get a best
30273347b07SNate Lawson 	 * match of settings against each level.
30373347b07SNate Lawson 	 */
30473347b07SNate Lawson 	count = CF_MAX_LEVELS;
30573347b07SNate Lawson 	levels = malloc(count * sizeof(*levels), M_TEMP, M_NOWAIT);
30673347b07SNate Lawson 	if (levels == NULL)
30773347b07SNate Lawson 		return (ENOMEM);
30873347b07SNate Lawson 	error = CPUFREQ_LEVELS(sc->dev, levels, &count);
30973347b07SNate Lawson 	if (error)
31073347b07SNate Lawson 		goto out;
31173347b07SNate Lawson 	error = device_get_children(device_get_parent(dev), &devs, &numdevs);
31273347b07SNate Lawson 	if (error)
31373347b07SNate Lawson 		goto out;
31473347b07SNate Lawson 	for (i = 0; i < numdevs && curr_set->freq == CPUFREQ_VAL_UNKNOWN; i++) {
31573347b07SNate Lawson 		if (!device_is_attached(devs[i]))
31673347b07SNate Lawson 			continue;
31773347b07SNate Lawson 		error = CPUFREQ_DRV_GET(devs[i], &set);
31873347b07SNate Lawson 		if (error)
31973347b07SNate Lawson 			continue;
32073347b07SNate Lawson 		for (i = 0; i < count; i++) {
32188c9b54cSNate Lawson 			if (CPUFREQ_CMP(set.freq, levels[i].total_set.freq)) {
32273347b07SNate Lawson 				sc->curr_level = levels[i];
32373347b07SNate Lawson 				break;
32473347b07SNate Lawson 			}
32573347b07SNate Lawson 		}
32673347b07SNate Lawson 	}
32773347b07SNate Lawson 	free(devs, M_TEMP);
32873347b07SNate Lawson 	if (curr_set->freq != CPUFREQ_VAL_UNKNOWN)
32973347b07SNate Lawson 		goto out;
33073347b07SNate Lawson 
33173347b07SNate Lawson 	/*
33273347b07SNate Lawson 	 * We couldn't find an exact match, so attempt to estimate and then
33373347b07SNate Lawson 	 * match against a level.
33473347b07SNate Lawson 	 */
33573347b07SNate Lawson 	pc = cpu_get_pcpu(dev);
33673347b07SNate Lawson 	if (pc == NULL) {
33773347b07SNate Lawson 		error = ENXIO;
33873347b07SNate Lawson 		goto out;
33973347b07SNate Lawson 	}
34073347b07SNate Lawson 	cpu_est_clockrate(pc->pc_cpuid, &rate);
34173347b07SNate Lawson 	rate /= 1000000;
34273347b07SNate Lawson 	for (i = 0; i < count; i++) {
34373347b07SNate Lawson 		if (CPUFREQ_CMP(rate, levels[i].total_set.freq)) {
34473347b07SNate Lawson 			sc->curr_level = levels[i];
34573347b07SNate Lawson 			break;
34673347b07SNate Lawson 		}
34773347b07SNate Lawson 	}
34873347b07SNate Lawson 
34973347b07SNate Lawson out:
35073347b07SNate Lawson 	if (levels)
35173347b07SNate Lawson 		free(levels, M_TEMP);
35273347b07SNate Lawson 	*level = sc->curr_level;
35373347b07SNate Lawson 	return (0);
35473347b07SNate Lawson }
35573347b07SNate Lawson 
35673347b07SNate Lawson static int
35773347b07SNate Lawson cf_levels_method(device_t dev, struct cf_level *levels, int *count)
35873347b07SNate Lawson {
35988c9b54cSNate Lawson 	struct cf_setting_array *set_arr;
36073347b07SNate Lawson 	struct cf_setting_lst rel_sets;
36173347b07SNate Lawson 	struct cpufreq_softc *sc;
36273347b07SNate Lawson 	struct cf_level *lev;
36373347b07SNate Lawson 	struct cf_setting *sets;
36473347b07SNate Lawson 	struct pcpu *pc;
36573347b07SNate Lawson 	device_t *devs;
36688c9b54cSNate Lawson 	int error, i, numdevs, set_count, type;
36773347b07SNate Lawson 	uint64_t rate;
36873347b07SNate Lawson 
36973347b07SNate Lawson 	if (levels == NULL || count == NULL)
37073347b07SNate Lawson 		return (EINVAL);
37173347b07SNate Lawson 
37273347b07SNate Lawson 	TAILQ_INIT(&rel_sets);
37373347b07SNate Lawson 	sc = device_get_softc(dev);
37473347b07SNate Lawson 	error = device_get_children(device_get_parent(dev), &devs, &numdevs);
37573347b07SNate Lawson 	if (error)
37673347b07SNate Lawson 		return (error);
37773347b07SNate Lawson 	sets = malloc(MAX_SETTINGS * sizeof(*sets), M_TEMP, M_NOWAIT);
37873347b07SNate Lawson 	if (sets == NULL) {
37973347b07SNate Lawson 		free(devs, M_TEMP);
38073347b07SNate Lawson 		return (ENOMEM);
38173347b07SNate Lawson 	}
38273347b07SNate Lawson 
38373347b07SNate Lawson 	/* Get settings from all cpufreq drivers. */
38473347b07SNate Lawson 	for (i = 0; i < numdevs; i++) {
385e22cd41cSNate Lawson 		/* Skip devices that aren't ready. */
38673347b07SNate Lawson 		if (!device_is_attached(devs[i]))
38773347b07SNate Lawson 			continue;
388e22cd41cSNate Lawson 
389e22cd41cSNate Lawson 		/*
390e22cd41cSNate Lawson 		 * Get settings, skipping drivers that offer no settings or
391e22cd41cSNate Lawson 		 * provide settings for informational purposes only.
392e22cd41cSNate Lawson 		 */
39373347b07SNate Lawson 		set_count = MAX_SETTINGS;
39473347b07SNate Lawson 		error = CPUFREQ_DRV_SETTINGS(devs[i], sets, &set_count, &type);
395e22cd41cSNate Lawson 		if (error || set_count == 0 || (type & CPUFREQ_FLAG_INFO_ONLY))
39673347b07SNate Lawson 			continue;
39773347b07SNate Lawson 
398e22cd41cSNate Lawson 		/* Add the settings to our absolute/relative lists. */
3990325089dSNate Lawson 		switch (type & CPUFREQ_TYPE_MASK) {
40088c9b54cSNate Lawson 		case CPUFREQ_TYPE_ABSOLUTE:
40188c9b54cSNate Lawson 			error = cpufreq_insert_abs(sc, sets, set_count);
40288c9b54cSNate Lawson 			break;
40388c9b54cSNate Lawson 		case CPUFREQ_TYPE_RELATIVE:
40488c9b54cSNate Lawson 			set_arr = malloc(sizeof(*set_arr), M_TEMP, M_NOWAIT);
40588c9b54cSNate Lawson 			if (set_arr == NULL) {
40688c9b54cSNate Lawson 				error = ENOMEM;
40788c9b54cSNate Lawson 				goto out;
40888c9b54cSNate Lawson 			}
40988c9b54cSNate Lawson 			bcopy(sets, set_arr->sets, set_count * sizeof(*sets));
41088c9b54cSNate Lawson 			set_arr->count = set_count;
41188c9b54cSNate Lawson 			TAILQ_INSERT_TAIL(&rel_sets, set_arr, link);
41288c9b54cSNate Lawson 			break;
41388c9b54cSNate Lawson 		default:
41488c9b54cSNate Lawson 			error = EINVAL;
41588c9b54cSNate Lawson 			break;
41688c9b54cSNate Lawson 		}
41788c9b54cSNate Lawson 		if (error)
41873347b07SNate Lawson 			goto out;
41973347b07SNate Lawson 	}
42073347b07SNate Lawson 
42173347b07SNate Lawson 	/* If there are no absolute levels, create a fake one at 100%. */
42273347b07SNate Lawson 	if (TAILQ_EMPTY(&sc->all_levels)) {
42373347b07SNate Lawson 		bzero(&sets[0], sizeof(*sets));
42473347b07SNate Lawson 		pc = cpu_get_pcpu(dev);
42573347b07SNate Lawson 		if (pc == NULL) {
42673347b07SNate Lawson 			error = ENXIO;
42773347b07SNate Lawson 			goto out;
42873347b07SNate Lawson 		}
42973347b07SNate Lawson 		cpu_est_clockrate(pc->pc_cpuid, &rate);
43073347b07SNate Lawson 		sets[0].freq = rate / 1000000;
43188c9b54cSNate Lawson 		error = cpufreq_insert_abs(sc, sets, 1);
43273347b07SNate Lawson 		if (error)
43373347b07SNate Lawson 			goto out;
43473347b07SNate Lawson 	}
43573347b07SNate Lawson 
43688c9b54cSNate Lawson 	/* Create a combined list of absolute + relative levels. */
43788c9b54cSNate Lawson 	TAILQ_FOREACH(set_arr, &rel_sets, link)
43888c9b54cSNate Lawson 		cpufreq_expand_set(sc, set_arr);
43988c9b54cSNate Lawson 
44088c9b54cSNate Lawson 	/* If the caller doesn't have enough space, return the actual count. */
44188c9b54cSNate Lawson 	if (sc->all_count > *count) {
44288c9b54cSNate Lawson 		*count = sc->all_count;
44388c9b54cSNate Lawson 		error = E2BIG;
44488c9b54cSNate Lawson 		goto out;
44588c9b54cSNate Lawson 	}
44688c9b54cSNate Lawson 
44788c9b54cSNate Lawson 	/* Finally, output the list of levels. */
44873347b07SNate Lawson 	i = 0;
44973347b07SNate Lawson 	TAILQ_FOREACH(lev, &sc->all_levels, link) {
45073347b07SNate Lawson 		levels[i] = *lev;
45173347b07SNate Lawson 		i++;
45273347b07SNate Lawson 	}
45388c9b54cSNate Lawson 	*count = sc->all_count;
45473347b07SNate Lawson 	error = 0;
45573347b07SNate Lawson 
45673347b07SNate Lawson out:
45773347b07SNate Lawson 	/* Clear all levels since we regenerate them each time. */
45873347b07SNate Lawson 	while ((lev = TAILQ_FIRST(&sc->all_levels)) != NULL) {
45973347b07SNate Lawson 		TAILQ_REMOVE(&sc->all_levels, lev, link);
46073347b07SNate Lawson 		free(lev, M_TEMP);
46173347b07SNate Lawson 	}
46288c9b54cSNate Lawson 	while ((set_arr = TAILQ_FIRST(&rel_sets)) != NULL) {
46388c9b54cSNate Lawson 		TAILQ_REMOVE(&rel_sets, set_arr, link);
46488c9b54cSNate Lawson 		free(set_arr, M_TEMP);
46588c9b54cSNate Lawson 	}
46688c9b54cSNate Lawson 	sc->all_count = 0;
46773347b07SNate Lawson 	free(devs, M_TEMP);
46873347b07SNate Lawson 	free(sets, M_TEMP);
46973347b07SNate Lawson 	return (error);
47073347b07SNate Lawson }
47173347b07SNate Lawson 
47273347b07SNate Lawson /*
47373347b07SNate Lawson  * Create levels for an array of absolute settings and insert them in
47473347b07SNate Lawson  * sorted order in the specified list.
47573347b07SNate Lawson  */
47673347b07SNate Lawson static int
47788c9b54cSNate Lawson cpufreq_insert_abs(struct cpufreq_softc *sc, struct cf_setting *sets,
47873347b07SNate Lawson     int count)
47973347b07SNate Lawson {
48088c9b54cSNate Lawson 	struct cf_level_lst *list;
48173347b07SNate Lawson 	struct cf_level *level, *search;
48273347b07SNate Lawson 	int i;
48373347b07SNate Lawson 
48488c9b54cSNate Lawson 	list = &sc->all_levels;
48573347b07SNate Lawson 	for (i = 0; i < count; i++) {
48673347b07SNate Lawson 		level = malloc(sizeof(*level), M_TEMP, M_NOWAIT | M_ZERO);
48773347b07SNate Lawson 		if (level == NULL)
48873347b07SNate Lawson 			return (ENOMEM);
48973347b07SNate Lawson 		level->abs_set = sets[i];
49088c9b54cSNate Lawson 		level->total_set = sets[i];
49188c9b54cSNate Lawson 		level->total_set.dev = NULL;
49288c9b54cSNate Lawson 		sc->all_count++;
49373347b07SNate Lawson 
49473347b07SNate Lawson 		if (TAILQ_EMPTY(list)) {
49573347b07SNate Lawson 			TAILQ_INSERT_HEAD(list, level, link);
49673347b07SNate Lawson 			continue;
49773347b07SNate Lawson 		}
49873347b07SNate Lawson 
49973347b07SNate Lawson 		TAILQ_FOREACH_REVERSE(search, list, cf_level_lst, link) {
50088c9b54cSNate Lawson 			if (sets[i].freq <= search->total_set.freq) {
50173347b07SNate Lawson 				TAILQ_INSERT_AFTER(list, search, level, link);
50273347b07SNate Lawson 				break;
50373347b07SNate Lawson 			}
50473347b07SNate Lawson 		}
50573347b07SNate Lawson 	}
50673347b07SNate Lawson 	return (0);
50773347b07SNate Lawson }
50873347b07SNate Lawson 
50988c9b54cSNate Lawson /*
51088c9b54cSNate Lawson  * Expand a group of relative settings, creating derived levels from them.
51188c9b54cSNate Lawson  */
51288c9b54cSNate Lawson static int
51388c9b54cSNate Lawson cpufreq_expand_set(struct cpufreq_softc *sc, struct cf_setting_array *set_arr)
51488c9b54cSNate Lawson {
51588c9b54cSNate Lawson 	struct cf_level *fill, *search;
51688c9b54cSNate Lawson 	struct cf_setting *set;
51788c9b54cSNate Lawson 	int i;
51888c9b54cSNate Lawson 
51988c9b54cSNate Lawson 	TAILQ_FOREACH(search, &sc->all_levels, link) {
52088c9b54cSNate Lawson 		/* Skip this level if we've already modified it. */
52188c9b54cSNate Lawson 		for (i = 0; i < search->rel_count; i++) {
52288c9b54cSNate Lawson 			if (search->rel_set[i].dev == set_arr->sets[0].dev)
52388c9b54cSNate Lawson 				break;
52488c9b54cSNate Lawson 		}
52588c9b54cSNate Lawson 		if (i != search->rel_count)
52688c9b54cSNate Lawson 			continue;
52788c9b54cSNate Lawson 
52888c9b54cSNate Lawson 		/* Add each setting to the level, duplicating if necessary. */
52988c9b54cSNate Lawson 		for (i = 0; i < set_arr->count; i++) {
53088c9b54cSNate Lawson 			set = &set_arr->sets[i];
53188c9b54cSNate Lawson 
53288c9b54cSNate Lawson 			/*
53388c9b54cSNate Lawson 			 * If this setting is less than 100%, split the level
53488c9b54cSNate Lawson 			 * into two and add this setting to the new level.
53588c9b54cSNate Lawson 			 */
53688c9b54cSNate Lawson 			fill = search;
53788c9b54cSNate Lawson 			if (set->freq < 10000)
53888c9b54cSNate Lawson 				fill = cpufreq_dup_set(sc, search, set);
53988c9b54cSNate Lawson 
54088c9b54cSNate Lawson 			/*
54188c9b54cSNate Lawson 			 * The new level was a duplicate of an existing level
54288c9b54cSNate Lawson 			 * so we freed it.  Go to the next setting.
54388c9b54cSNate Lawson 			 */
54488c9b54cSNate Lawson 			if (fill == NULL)
54588c9b54cSNate Lawson 				continue;
54688c9b54cSNate Lawson 
54788c9b54cSNate Lawson 			/* Add this setting to the existing or new level. */
54888c9b54cSNate Lawson 			KASSERT(fill->rel_count < MAX_SETTINGS,
54988c9b54cSNate Lawson 			    ("cpufreq: too many relative drivers (%d)",
55088c9b54cSNate Lawson 			    MAX_SETTINGS));
55188c9b54cSNate Lawson 			fill->rel_set[fill->rel_count] = *set;
55288c9b54cSNate Lawson 			fill->rel_count++;
55388c9b54cSNate Lawson 		}
55488c9b54cSNate Lawson 	}
55588c9b54cSNate Lawson 
55688c9b54cSNate Lawson 	return (0);
55788c9b54cSNate Lawson }
55888c9b54cSNate Lawson 
55988c9b54cSNate Lawson static struct cf_level *
56088c9b54cSNate Lawson cpufreq_dup_set(struct cpufreq_softc *sc, struct cf_level *dup,
56188c9b54cSNate Lawson     struct cf_setting *set)
56288c9b54cSNate Lawson {
56388c9b54cSNate Lawson 	struct cf_level_lst *list;
56488c9b54cSNate Lawson 	struct cf_level *fill, *itr;
56588c9b54cSNate Lawson 	struct cf_setting *fill_set, *itr_set;
56688c9b54cSNate Lawson 	int i;
56788c9b54cSNate Lawson 
56888c9b54cSNate Lawson 	/*
56988c9b54cSNate Lawson 	 * Create a new level, copy it from the old one, and update the
57088c9b54cSNate Lawson 	 * total frequency and power by the percentage specified in the
57188c9b54cSNate Lawson 	 * relative setting.
57288c9b54cSNate Lawson 	 */
57388c9b54cSNate Lawson 	fill = malloc(sizeof(*fill), M_TEMP, M_NOWAIT);
57488c9b54cSNate Lawson 	if (fill == NULL)
57588c9b54cSNate Lawson 		return (NULL);
57688c9b54cSNate Lawson 	*fill = *dup;
57788c9b54cSNate Lawson 	fill_set = &fill->total_set;
57888c9b54cSNate Lawson 	fill_set->freq =
57988c9b54cSNate Lawson 	    ((uint64_t)fill_set->freq * set->freq) / 10000;
58088c9b54cSNate Lawson 	if (fill_set->power != CPUFREQ_VAL_UNKNOWN) {
58188c9b54cSNate Lawson 		fill_set->power = ((uint64_t)fill_set->power * set->freq)
58288c9b54cSNate Lawson 		    / 10000;
58388c9b54cSNate Lawson 	}
58488c9b54cSNate Lawson 	if (set->lat != CPUFREQ_VAL_UNKNOWN) {
58588c9b54cSNate Lawson 		if (fill_set->lat != CPUFREQ_VAL_UNKNOWN)
58688c9b54cSNate Lawson 			fill_set->lat += set->lat;
58788c9b54cSNate Lawson 		else
58888c9b54cSNate Lawson 			fill_set->lat = set->lat;
58988c9b54cSNate Lawson 	}
59088c9b54cSNate Lawson 
59188c9b54cSNate Lawson 	/*
59288c9b54cSNate Lawson 	 * If we copied an old level that we already modified (say, at 100%),
59388c9b54cSNate Lawson 	 * we need to remove that setting before adding this one.  Since we
59488c9b54cSNate Lawson 	 * process each setting array in order, we know any settings for this
59588c9b54cSNate Lawson 	 * driver will be found at the end.
59688c9b54cSNate Lawson 	 */
59788c9b54cSNate Lawson 	for (i = fill->rel_count; i != 0; i--) {
59888c9b54cSNate Lawson 		if (fill->rel_set[i - 1].dev != set->dev)
59988c9b54cSNate Lawson 			break;
60088c9b54cSNate Lawson 		fill->rel_count--;
60188c9b54cSNate Lawson 	}
60288c9b54cSNate Lawson 
60388c9b54cSNate Lawson 	/*
60488c9b54cSNate Lawson 	 * Insert the new level in sorted order.  If we find a duplicate,
60588c9b54cSNate Lawson 	 * free the new level.  We can do this since any existing level will
60688c9b54cSNate Lawson 	 * be guaranteed to have the same or less settings and thus consume
60788c9b54cSNate Lawson 	 * less power.  For example, a level with one absolute setting of
60888c9b54cSNate Lawson 	 * 800 Mhz uses less power than one composed of an absolute setting
60988c9b54cSNate Lawson 	 * of 1600 Mhz and a relative setting at 50%.
61088c9b54cSNate Lawson 	 */
61188c9b54cSNate Lawson 	list = &sc->all_levels;
61288c9b54cSNate Lawson 	if (TAILQ_EMPTY(list)) {
61388c9b54cSNate Lawson 		TAILQ_INSERT_HEAD(list, fill, link);
61488c9b54cSNate Lawson 	} else {
61588c9b54cSNate Lawson 		TAILQ_FOREACH_REVERSE(itr, list, cf_level_lst, link) {
61688c9b54cSNate Lawson 			itr_set = &itr->total_set;
61788c9b54cSNate Lawson 			if (CPUFREQ_CMP(fill_set->freq, itr_set->freq)) {
61888c9b54cSNate Lawson 				free(fill, M_TEMP);
61988c9b54cSNate Lawson 				fill = NULL;
62088c9b54cSNate Lawson 				break;
62188c9b54cSNate Lawson 			} else if (fill_set->freq < itr_set->freq) {
62288c9b54cSNate Lawson 				TAILQ_INSERT_AFTER(list, itr, fill, link);
62388c9b54cSNate Lawson 				sc->all_count++;
62488c9b54cSNate Lawson 				break;
62588c9b54cSNate Lawson 			}
62688c9b54cSNate Lawson 		}
62788c9b54cSNate Lawson 	}
62888c9b54cSNate Lawson 
62988c9b54cSNate Lawson 	return (fill);
63088c9b54cSNate Lawson }
63188c9b54cSNate Lawson 
63273347b07SNate Lawson static int
63373347b07SNate Lawson cpufreq_curr_sysctl(SYSCTL_HANDLER_ARGS)
63473347b07SNate Lawson {
63573347b07SNate Lawson 	struct cpufreq_softc *sc;
63673347b07SNate Lawson 	struct cf_level *levels;
6370325089dSNate Lawson 	int count, devcount, error, freq, i, n;
6380325089dSNate Lawson 	device_t *devs;
63973347b07SNate Lawson 
6400325089dSNate Lawson 	devs = NULL;
64173347b07SNate Lawson 	sc = oidp->oid_arg1;
6420325089dSNate Lawson 	levels = malloc(CF_MAX_LEVELS * sizeof(*levels), M_TEMP, M_NOWAIT);
64373347b07SNate Lawson 	if (levels == NULL)
64473347b07SNate Lawson 		return (ENOMEM);
64573347b07SNate Lawson 
64673347b07SNate Lawson 	error = CPUFREQ_GET(sc->dev, &levels[0]);
64773347b07SNate Lawson 	if (error)
64873347b07SNate Lawson 		goto out;
64973347b07SNate Lawson 	freq = levels[0].total_set.freq;
65073347b07SNate Lawson 	error = sysctl_handle_int(oidp, &freq, 0, req);
65173347b07SNate Lawson 	if (error != 0 || req->newptr == NULL)
65273347b07SNate Lawson 		goto out;
65373347b07SNate Lawson 
6540325089dSNate Lawson 	/*
6550325089dSNate Lawson 	 * While we only call cpufreq_get() on one device (assuming all
6560325089dSNate Lawson 	 * CPUs have equal levels), we call cpufreq_set() on all CPUs.
6570325089dSNate Lawson 	 * This is needed for some MP systems.
6580325089dSNate Lawson 	 */
6590325089dSNate Lawson 	error = devclass_get_devices(cpufreq_dc, &devs, &devcount);
66073347b07SNate Lawson 	if (error)
66173347b07SNate Lawson 		goto out;
6620325089dSNate Lawson 	for (n = 0; n < devcount; n++) {
6630325089dSNate Lawson 		count = CF_MAX_LEVELS;
6640325089dSNate Lawson 		error = CPUFREQ_LEVELS(devs[n], levels, &count);
6650325089dSNate Lawson 		if (error)
6660325089dSNate Lawson 			break;
66773347b07SNate Lawson 		for (i = 0; i < count; i++) {
66873347b07SNate Lawson 			if (CPUFREQ_CMP(levels[i].total_set.freq, freq)) {
6690325089dSNate Lawson 				error = CPUFREQ_SET(devs[n], &levels[i],
67073347b07SNate Lawson 				    CPUFREQ_PRIO_USER);
67173347b07SNate Lawson 				break;
67273347b07SNate Lawson 			}
67373347b07SNate Lawson 		}
6740325089dSNate Lawson 		if (i == count) {
67573347b07SNate Lawson 			error = EINVAL;
6760325089dSNate Lawson 			break;
6770325089dSNate Lawson 		}
6780325089dSNate Lawson 	}
67973347b07SNate Lawson 
68073347b07SNate Lawson out:
6810325089dSNate Lawson 	if (devs)
6820325089dSNate Lawson 		free(devs, M_TEMP);
68373347b07SNate Lawson 	if (levels)
68473347b07SNate Lawson 		free(levels, M_TEMP);
68573347b07SNate Lawson 	return (error);
68673347b07SNate Lawson }
68773347b07SNate Lawson 
68873347b07SNate Lawson static int
68973347b07SNate Lawson cpufreq_levels_sysctl(SYSCTL_HANDLER_ARGS)
69073347b07SNate Lawson {
69173347b07SNate Lawson 	struct cpufreq_softc *sc;
69273347b07SNate Lawson 	struct cf_level *levels;
69373347b07SNate Lawson 	struct cf_setting *set;
69473347b07SNate Lawson 	struct sbuf sb;
69573347b07SNate Lawson 	int count, error, i;
69673347b07SNate Lawson 
69773347b07SNate Lawson 	sc = oidp->oid_arg1;
69873347b07SNate Lawson 	sbuf_new(&sb, NULL, 128, SBUF_AUTOEXTEND);
69973347b07SNate Lawson 
70073347b07SNate Lawson 	/* Get settings from the device and generate the output string. */
70173347b07SNate Lawson 	count = CF_MAX_LEVELS;
70273347b07SNate Lawson 	levels = malloc(count * sizeof(*levels), M_TEMP, M_NOWAIT);
70373347b07SNate Lawson 	if (levels == NULL)
70473347b07SNate Lawson 		return (ENOMEM);
70573347b07SNate Lawson 	error = CPUFREQ_LEVELS(sc->dev, levels, &count);
70673347b07SNate Lawson 	if (error)
70773347b07SNate Lawson 		goto out;
70873347b07SNate Lawson 	if (count) {
70973347b07SNate Lawson 		for (i = 0; i < count; i++) {
71073347b07SNate Lawson 			set = &levels[i].total_set;
71173347b07SNate Lawson 			sbuf_printf(&sb, "%d/%d ", set->freq, set->power);
71273347b07SNate Lawson 		}
71373347b07SNate Lawson 	} else
71473347b07SNate Lawson 		sbuf_cpy(&sb, "0");
71573347b07SNate Lawson 	sbuf_trim(&sb);
71673347b07SNate Lawson 	sbuf_finish(&sb);
71773347b07SNate Lawson 	error = sysctl_handle_string(oidp, sbuf_data(&sb), sbuf_len(&sb), req);
71873347b07SNate Lawson 
71973347b07SNate Lawson out:
72073347b07SNate Lawson 	free(levels, M_TEMP);
72173347b07SNate Lawson 	sbuf_delete(&sb);
72273347b07SNate Lawson 	return (error);
72373347b07SNate Lawson }
72473347b07SNate Lawson 
72573347b07SNate Lawson int
72673347b07SNate Lawson cpufreq_register(device_t dev)
72773347b07SNate Lawson {
72873347b07SNate Lawson 	device_t cf_dev, cpu_dev;
72973347b07SNate Lawson 
73073347b07SNate Lawson 	/*
7310325089dSNate Lawson 	 * Add only one cpufreq device to each CPU.  Currently, all CPUs
7320325089dSNate Lawson 	 * must offer the same levels and be switched at the same time.
73373347b07SNate Lawson 	 */
7340325089dSNate Lawson 	cpu_dev = device_get_parent(dev);
7350325089dSNate Lawson 	KASSERT(cpu_dev != NULL, ("no parent for %p", dev));
7360325089dSNate Lawson 	if (device_find_child(cpu_dev, "cpufreq", -1))
73773347b07SNate Lawson 		return (0);
73873347b07SNate Lawson 
7390325089dSNate Lawson 	/* Add the child device and possibly sysctls. */
7400325089dSNate Lawson 	cf_dev = BUS_ADD_CHILD(cpu_dev, 0, "cpufreq", -1);
74173347b07SNate Lawson 	if (cf_dev == NULL)
74273347b07SNate Lawson 		return (ENOMEM);
74373347b07SNate Lawson 	device_quiet(cf_dev);
74473347b07SNate Lawson 
74573347b07SNate Lawson 	return (device_probe_and_attach(cf_dev));
74673347b07SNate Lawson }
74773347b07SNate Lawson 
74873347b07SNate Lawson int
74973347b07SNate Lawson cpufreq_unregister(device_t dev)
75073347b07SNate Lawson {
75173347b07SNate Lawson 	device_t cf_dev, *devs;
75273347b07SNate Lawson 	int cfcount, count, devcount, error, i, type;
75373347b07SNate Lawson 	struct cf_setting set;
75473347b07SNate Lawson 
75573347b07SNate Lawson 	/*
75673347b07SNate Lawson 	 * If this is the last cpufreq child device, remove the control
75773347b07SNate Lawson 	 * device as well.  We identify cpufreq children by calling a method
75873347b07SNate Lawson 	 * they support.
75973347b07SNate Lawson 	 */
76073347b07SNate Lawson 	error = device_get_children(device_get_parent(dev), &devs, &devcount);
76173347b07SNate Lawson 	if (error)
76273347b07SNate Lawson 		return (error);
76373347b07SNate Lawson 	cf_dev = devclass_get_device(cpufreq_dc, 0);
76473347b07SNate Lawson 	KASSERT(cf_dev != NULL, ("unregister with no cpufreq dev"));
76573347b07SNate Lawson 	cfcount = 0;
76673347b07SNate Lawson 	for (i = 0; i < devcount; i++) {
76773347b07SNate Lawson 		if (!device_is_attached(devs[i]))
76873347b07SNate Lawson 			continue;
76973347b07SNate Lawson 		count = 1;
77073347b07SNate Lawson 		if (CPUFREQ_DRV_SETTINGS(devs[i], &set, &count, &type) == 0)
77173347b07SNate Lawson 			cfcount++;
77273347b07SNate Lawson 	}
7730325089dSNate Lawson 	if (cfcount <= 1)
77473347b07SNate Lawson 		device_delete_child(device_get_parent(cf_dev), cf_dev);
77573347b07SNate Lawson 	free(devs, M_TEMP);
77673347b07SNate Lawson 
77773347b07SNate Lawson 	return (0);
77873347b07SNate Lawson }
779