xref: /freebsd-src/sys/dev/acpica/acpi_perf.c (revision 685dc743dc3b5645e34836464128e1c0558b404b)
16cb2040bSNate Lawson /*-
26cb2040bSNate Lawson  * Copyright (c) 2003-2005 Nate Lawson (SDG)
36cb2040bSNate Lawson  * All rights reserved.
46cb2040bSNate Lawson  *
56cb2040bSNate Lawson  * Redistribution and use in source and binary forms, with or without
66cb2040bSNate Lawson  * modification, are permitted provided that the following conditions
76cb2040bSNate Lawson  * are met:
86cb2040bSNate Lawson  * 1. Redistributions of source code must retain the above copyright
96cb2040bSNate Lawson  *    notice, this list of conditions and the following disclaimer.
106cb2040bSNate Lawson  * 2. Redistributions in binary form must reproduce the above copyright
116cb2040bSNate Lawson  *    notice, this list of conditions and the following disclaimer in the
126cb2040bSNate Lawson  *    documentation and/or other materials provided with the distribution.
136cb2040bSNate Lawson  *
146cb2040bSNate Lawson  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
156cb2040bSNate Lawson  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
166cb2040bSNate Lawson  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
176cb2040bSNate Lawson  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
186cb2040bSNate Lawson  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
196cb2040bSNate Lawson  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
206cb2040bSNate Lawson  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
216cb2040bSNate Lawson  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
226cb2040bSNate Lawson  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
236cb2040bSNate Lawson  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
246cb2040bSNate Lawson  * SUCH DAMAGE.
256cb2040bSNate Lawson  */
266cb2040bSNate Lawson 
276cb2040bSNate Lawson #include <sys/cdefs.h>
286cb2040bSNate Lawson #include "opt_acpi.h"
296cb2040bSNate Lawson #include <sys/param.h>
306cb2040bSNate Lawson #include <sys/kernel.h>
316cb2040bSNate Lawson #include <sys/proc.h>
326cb2040bSNate Lawson #include <sys/sched.h>
336cb2040bSNate Lawson #include <sys/bus.h>
346cb2040bSNate Lawson #include <sys/cpu.h>
356cb2040bSNate Lawson #include <sys/power.h>
366cb2040bSNate Lawson #include <sys/malloc.h>
376cb2040bSNate Lawson #include <sys/module.h>
386cb2040bSNate Lawson #include <sys/sbuf.h>
396cb2040bSNate Lawson #include <sys/pcpu.h>
406cb2040bSNate Lawson 
416cb2040bSNate Lawson #include <machine/bus.h>
426cb2040bSNate Lawson #include <machine/resource.h>
436cb2040bSNate Lawson #include <sys/rman.h>
446cb2040bSNate Lawson 
45129d3046SJung-uk Kim #include <contrib/dev/acpica/include/acpi.h>
46129d3046SJung-uk Kim 
476cb2040bSNate Lawson #include <dev/acpica/acpivar.h>
486cb2040bSNate Lawson 
496cb2040bSNate Lawson #include "cpufreq_if.h"
506cb2040bSNate Lawson 
516cb2040bSNate Lawson /*
526cb2040bSNate Lawson  * Support for ACPI processor performance states (Px) according to
53e22cd41cSNate Lawson  * section 8.3.3 of the ACPI 2.0c specification.
546cb2040bSNate Lawson  */
556cb2040bSNate Lawson 
566cb2040bSNate Lawson struct acpi_px {
576cb2040bSNate Lawson 	uint32_t	 core_freq;
586cb2040bSNate Lawson 	uint32_t	 power;
596cb2040bSNate Lawson 	uint32_t	 trans_lat;
606cb2040bSNate Lawson 	uint32_t	 bm_lat;
616cb2040bSNate Lawson 	uint32_t	 ctrl_val;
626cb2040bSNate Lawson 	uint32_t	 sts_val;
636cb2040bSNate Lawson };
646cb2040bSNate Lawson 
6528d7170fSNate Lawson /* Offsets in struct cf_setting array for storing driver-specific values. */
6628d7170fSNate Lawson #define PX_SPEC_CONTROL	0
6728d7170fSNate Lawson #define PX_SPEC_STATUS	1
6828d7170fSNate Lawson 
696cb2040bSNate Lawson #define MAX_PX_STATES	16
706cb2040bSNate Lawson 
716cb2040bSNate Lawson struct acpi_perf_softc {
726cb2040bSNate Lawson 	device_t	 dev;
736cb2040bSNate Lawson 	ACPI_HANDLE	 handle;
746cb2040bSNate Lawson 	struct resource	*perf_ctrl;	/* Set new performance state. */
7576ce4cc4SNate Lawson 	int		 perf_ctrl_type; /* Resource type for perf_ctrl. */
766cb2040bSNate Lawson 	struct resource	*perf_status;	/* Check that transition succeeded. */
7776ce4cc4SNate Lawson 	int		 perf_sts_type;	/* Resource type for perf_status. */
786cb2040bSNate Lawson 	struct acpi_px	*px_states;	/* ACPI perf states. */
796cb2040bSNate Lawson 	uint32_t	 px_count;	/* Total number of perf states. */
806cb2040bSNate Lawson 	uint32_t	 px_max_avail;	/* Lowest index state available. */
816cb2040bSNate Lawson 	int		 px_curr_state;	/* Active state index. */
826cb2040bSNate Lawson 	int		 px_rid;
83e22cd41cSNate Lawson 	int		 info_only;	/* Can we set new states? */
846cb2040bSNate Lawson };
856cb2040bSNate Lawson 
866cb2040bSNate Lawson #define PX_GET_REG(reg) 				\
876cb2040bSNate Lawson 	(bus_space_read_4(rman_get_bustag((reg)), 	\
886cb2040bSNate Lawson 	    rman_get_bushandle((reg)), 0))
896cb2040bSNate Lawson #define PX_SET_REG(reg, val)				\
906cb2040bSNate Lawson 	(bus_space_write_4(rman_get_bustag((reg)), 	\
916cb2040bSNate Lawson 	    rman_get_bushandle((reg)), 0, (val)))
926cb2040bSNate Lawson 
938b888c66SNate Lawson #define ACPI_NOTIFY_PERF_STATES		0x80	/* _PSS changed. */
948b888c66SNate Lawson 
956cb2040bSNate Lawson static void	acpi_perf_identify(driver_t *driver, device_t parent);
966cb2040bSNate Lawson static int	acpi_perf_probe(device_t dev);
976cb2040bSNate Lawson static int	acpi_perf_attach(device_t dev);
986cb2040bSNate Lawson static int	acpi_perf_detach(device_t dev);
996cb2040bSNate Lawson static int	acpi_perf_evaluate(device_t dev);
1006cb2040bSNate Lawson static int	acpi_px_to_set(device_t dev, struct acpi_px *px,
1016cb2040bSNate Lawson 		    struct cf_setting *set);
1026cb2040bSNate Lawson static void	acpi_px_available(struct acpi_perf_softc *sc);
1033cc2f176SNate Lawson static void	acpi_px_startup(void *arg);
1046cb2040bSNate Lawson static void	acpi_px_notify(ACPI_HANDLE h, UINT32 notify, void *context);
1056cb2040bSNate Lawson static int	acpi_px_settings(device_t dev, struct cf_setting *sets,
106e94a0c1aSNate Lawson 		    int *count);
1076cb2040bSNate Lawson static int	acpi_px_set(device_t dev, const struct cf_setting *set);
1086cb2040bSNate Lawson static int	acpi_px_get(device_t dev, struct cf_setting *set);
109e94a0c1aSNate Lawson static int	acpi_px_type(device_t dev, int *type);
1106cb2040bSNate Lawson 
1116cb2040bSNate Lawson static device_method_t acpi_perf_methods[] = {
1126cb2040bSNate Lawson 	/* Device interface */
1136cb2040bSNate Lawson 	DEVMETHOD(device_identify,	acpi_perf_identify),
1146cb2040bSNate Lawson 	DEVMETHOD(device_probe,		acpi_perf_probe),
1156cb2040bSNate Lawson 	DEVMETHOD(device_attach,	acpi_perf_attach),
1166cb2040bSNate Lawson 	DEVMETHOD(device_detach,	acpi_perf_detach),
1176cb2040bSNate Lawson 
1186cb2040bSNate Lawson 	/* cpufreq interface */
1196cb2040bSNate Lawson 	DEVMETHOD(cpufreq_drv_set,	acpi_px_set),
1206cb2040bSNate Lawson 	DEVMETHOD(cpufreq_drv_get,	acpi_px_get),
121e94a0c1aSNate Lawson 	DEVMETHOD(cpufreq_drv_type,	acpi_px_type),
1226cb2040bSNate Lawson 	DEVMETHOD(cpufreq_drv_settings,	acpi_px_settings),
12361bfd867SSofian Brabez 
12461bfd867SSofian Brabez 	DEVMETHOD_END
1256cb2040bSNate Lawson };
1266cb2040bSNate Lawson 
1276cb2040bSNate Lawson static driver_t acpi_perf_driver = {
1286cb2040bSNate Lawson 	"acpi_perf",
1296cb2040bSNate Lawson 	acpi_perf_methods,
1306cb2040bSNate Lawson 	sizeof(struct acpi_perf_softc),
1316cb2040bSNate Lawson };
1326cb2040bSNate Lawson 
133*916a5d8aSJohn Baldwin DRIVER_MODULE(acpi_perf, cpu, acpi_perf_driver, 0, 0);
1346cb2040bSNate Lawson MODULE_DEPEND(acpi_perf, acpi, 1, 1, 1);
1356cb2040bSNate Lawson 
136d745c852SEd Schouten static MALLOC_DEFINE(M_ACPIPERF, "acpi_perf", "ACPI Performance states");
1376cb2040bSNate Lawson 
1386cb2040bSNate Lawson static void
acpi_perf_identify(driver_t * driver,device_t parent)1396cb2040bSNate Lawson acpi_perf_identify(driver_t *driver, device_t parent)
1406cb2040bSNate Lawson {
1416cb2040bSNate Lawson 	ACPI_HANDLE handle;
1426638c61aSNate Lawson 	device_t dev;
1436cb2040bSNate Lawson 
1446cb2040bSNate Lawson 	/* Make sure we're not being doubly invoked. */
1458b39b8ceSNate Lawson 	if (device_find_child(parent, "acpi_perf", -1) != NULL)
1466cb2040bSNate Lawson 		return;
1476cb2040bSNate Lawson 
1486cb2040bSNate Lawson 	/* Get the handle for the Processor object and check for perf states. */
1496cb2040bSNate Lawson 	handle = acpi_get_handle(parent);
1506cb2040bSNate Lawson 	if (handle == NULL)
1516cb2040bSNate Lawson 		return;
1526cb2040bSNate Lawson 	if (ACPI_FAILURE(AcpiEvaluateObject(handle, "_PSS", NULL, NULL)))
1536cb2040bSNate Lawson 		return;
15497d31723SNate Lawson 
15597d31723SNate Lawson 	/*
15697d31723SNate Lawson 	 * Add a child to every CPU that has the right methods.  In future
15797d31723SNate Lawson 	 * versions of the ACPI spec, CPUs can have different settings.
1586638c61aSNate Lawson 	 * We probe this child now so that other devices that depend
1596638c61aSNate Lawson 	 * on it (i.e., for info about supported states) will see it.
16097d31723SNate Lawson 	 */
161d3a8f98aSAlexander Motin 	if ((dev = BUS_ADD_CHILD(parent, 0, "acpi_perf",
162d3a8f98aSAlexander Motin 	    device_get_unit(parent))) != NULL)
1636638c61aSNate Lawson 		device_probe_and_attach(dev);
1646638c61aSNate Lawson 	else
1658b888c66SNate Lawson 		device_printf(parent, "add acpi_perf child failed\n");
1666cb2040bSNate Lawson }
1676cb2040bSNate Lawson 
1686cb2040bSNate Lawson static int
acpi_perf_probe(device_t dev)1696cb2040bSNate Lawson acpi_perf_probe(device_t dev)
1706cb2040bSNate Lawson {
17176ce4cc4SNate Lawson 	ACPI_HANDLE handle;
17276ce4cc4SNate Lawson 	ACPI_OBJECT *pkg;
17376ce4cc4SNate Lawson 	struct resource *res;
17476ce4cc4SNate Lawson 	ACPI_BUFFER buf;
17576ce4cc4SNate Lawson 	int error, rid, type;
1766cb2040bSNate Lawson 
177a8de37b0SEitan Adler 	if (resource_disabled("acpi_perf", 0))
178a8de37b0SEitan Adler 		return (ENXIO);
179a8de37b0SEitan Adler 
18076ce4cc4SNate Lawson 	/*
18176ce4cc4SNate Lawson 	 * Check the performance state registers.  If they are of type
182e22cd41cSNate Lawson 	 * "functional fixed hardware", we attach quietly since we will
183e22cd41cSNate Lawson 	 * only be providing information on settings to other drivers.
18476ce4cc4SNate Lawson 	 */
18576ce4cc4SNate Lawson 	error = ENXIO;
18676ce4cc4SNate Lawson 	handle = acpi_get_handle(dev);
18776ce4cc4SNate Lawson 	buf.Pointer = NULL;
18876ce4cc4SNate Lawson 	buf.Length = ACPI_ALLOCATE_BUFFER;
18976ce4cc4SNate Lawson 	if (ACPI_FAILURE(AcpiEvaluateObject(handle, "_PCT", NULL, &buf)))
19076ce4cc4SNate Lawson 		return (error);
19176ce4cc4SNate Lawson 	pkg = (ACPI_OBJECT *)buf.Pointer;
192e22cd41cSNate Lawson 	if (ACPI_PKG_VALID(pkg, 2)) {
19376ce4cc4SNate Lawson 		rid = 0;
194907b6777SNate Lawson 		error = acpi_PkgGas(dev, pkg, 0, &type, &rid, &res, 0);
195e22cd41cSNate Lawson 		switch (error) {
196e22cd41cSNate Lawson 		case 0:
19776ce4cc4SNate Lawson 			bus_release_resource(dev, type, rid, res);
198ca2c69c8SNate Lawson 			bus_delete_resource(dev, type, rid);
1996cb2040bSNate Lawson 			device_set_desc(dev, "ACPI CPU Frequency Control");
200e22cd41cSNate Lawson 			break;
201e22cd41cSNate Lawson 		case EOPNOTSUPP:
202e22cd41cSNate Lawson 			device_quiet(dev);
203e22cd41cSNate Lawson 			error = 0;
204e22cd41cSNate Lawson 			break;
205e22cd41cSNate Lawson 		}
20676ce4cc4SNate Lawson 	}
20776ce4cc4SNate Lawson 	AcpiOsFree(buf.Pointer);
20876ce4cc4SNate Lawson 
20976ce4cc4SNate Lawson 	return (error);
2106cb2040bSNate Lawson }
2116cb2040bSNate Lawson 
2126cb2040bSNate Lawson static int
acpi_perf_attach(device_t dev)2136cb2040bSNate Lawson acpi_perf_attach(device_t dev)
2146cb2040bSNate Lawson {
2156cb2040bSNate Lawson 	struct acpi_perf_softc *sc;
2166cb2040bSNate Lawson 
2176cb2040bSNate Lawson 	sc = device_get_softc(dev);
2186cb2040bSNate Lawson 	sc->dev = dev;
2196cb2040bSNate Lawson 	sc->handle = acpi_get_handle(dev);
2206cb2040bSNate Lawson 	sc->px_max_avail = 0;
2216cb2040bSNate Lawson 	sc->px_curr_state = CPUFREQ_VAL_UNKNOWN;
2226cb2040bSNate Lawson 	if (acpi_perf_evaluate(dev) != 0)
2236cb2040bSNate Lawson 		return (ENXIO);
2242be4e471SJung-uk Kim 	AcpiOsExecute(OSL_NOTIFY_HANDLER, acpi_px_startup, NULL);
225f81de92fSNate Lawson 	if (!sc->info_only)
226f81de92fSNate Lawson 		cpufreq_register(dev);
2276cb2040bSNate Lawson 
2286cb2040bSNate Lawson 	return (0);
2296cb2040bSNate Lawson }
2306cb2040bSNate Lawson 
2316cb2040bSNate Lawson static int
acpi_perf_detach(device_t dev)2326cb2040bSNate Lawson acpi_perf_detach(device_t dev)
2336cb2040bSNate Lawson {
2346cb2040bSNate Lawson 	/* TODO: teardown registers, remove notify handler. */
2356cb2040bSNate Lawson 	return (ENXIO);
2366cb2040bSNate Lawson }
2376cb2040bSNate Lawson 
2386cb2040bSNate Lawson /* Probe and setup any valid performance states (Px). */
2396cb2040bSNate Lawson static int
acpi_perf_evaluate(device_t dev)2406cb2040bSNate Lawson acpi_perf_evaluate(device_t dev)
2416cb2040bSNate Lawson {
2426cb2040bSNate Lawson 	struct acpi_perf_softc *sc;
2436cb2040bSNate Lawson 	ACPI_BUFFER buf;
2446cb2040bSNate Lawson 	ACPI_OBJECT *pkg, *res;
2456cb2040bSNate Lawson 	ACPI_STATUS status;
24655fa5feaSNate Lawson 	int count, error, i, j;
247f9ead0fcSBruno Ducrot 	static int once = 1;
2486cb2040bSNate Lawson 	uint32_t *p;
2496cb2040bSNate Lawson 
2506cb2040bSNate Lawson 	/* Get the control values and parameters for each state. */
25126d5f7dfSNate Lawson 	error = ENXIO;
2526cb2040bSNate Lawson 	sc = device_get_softc(dev);
2536cb2040bSNate Lawson 	buf.Pointer = NULL;
2546cb2040bSNate Lawson 	buf.Length = ACPI_ALLOCATE_BUFFER;
2556cb2040bSNate Lawson 	status = AcpiEvaluateObject(sc->handle, "_PSS", NULL, &buf);
2566cb2040bSNate Lawson 	if (ACPI_FAILURE(status))
2576cb2040bSNate Lawson 		return (ENXIO);
2586cb2040bSNate Lawson 
2596cb2040bSNate Lawson 	pkg = (ACPI_OBJECT *)buf.Pointer;
2606cb2040bSNate Lawson 	if (!ACPI_PKG_VALID(pkg, 1)) {
2616cb2040bSNate Lawson 		device_printf(dev, "invalid top level _PSS package\n");
26226d5f7dfSNate Lawson 		goto out;
2636cb2040bSNate Lawson 	}
2646cb2040bSNate Lawson 	sc->px_count = pkg->Package.Count;
2656cb2040bSNate Lawson 
2666cb2040bSNate Lawson 	sc->px_states = malloc(sc->px_count * sizeof(struct acpi_px),
2676cb2040bSNate Lawson 	    M_ACPIPERF, M_WAITOK | M_ZERO);
2686cb2040bSNate Lawson 
2696cb2040bSNate Lawson 	/*
2706cb2040bSNate Lawson 	 * Each state is a package of {CoreFreq, Power, TransitionLatency,
2716cb2040bSNate Lawson 	 * BusMasterLatency, ControlVal, StatusVal}, sorted from highest
2726cb2040bSNate Lawson 	 * performance to lowest.
2736cb2040bSNate Lawson 	 */
27455fa5feaSNate Lawson 	count = 0;
2756cb2040bSNate Lawson 	for (i = 0; i < sc->px_count; i++) {
2766cb2040bSNate Lawson 		res = &pkg->Package.Elements[i];
2776cb2040bSNate Lawson 		if (!ACPI_PKG_VALID(res, 6)) {
278f9ead0fcSBruno Ducrot 			if (once) {
279f9ead0fcSBruno Ducrot 				once = 0;
2806cb2040bSNate Lawson 				device_printf(dev, "invalid _PSS package\n");
281f9ead0fcSBruno Ducrot 			}
2826cb2040bSNate Lawson 			continue;
2836cb2040bSNate Lawson 		}
2840716d65cSNate Lawson 
2850716d65cSNate Lawson 		/* Parse the rest of the package into the struct. */
28655fa5feaSNate Lawson 		p = &sc->px_states[count].core_freq;
2876cb2040bSNate Lawson 		for (j = 0; j < 6; j++, p++)
2886cb2040bSNate Lawson 			acpi_PkgInt32(res, j, p);
28955fa5feaSNate Lawson 
29055fa5feaSNate Lawson 		/*
29155fa5feaSNate Lawson 		 * Check for some impossible frequencies that some systems
29255fa5feaSNate Lawson 		 * use to indicate they don't actually support this Px state.
29355fa5feaSNate Lawson 		 */
29455fa5feaSNate Lawson 		if (sc->px_states[count].core_freq == 0 ||
29555fa5feaSNate Lawson 		    sc->px_states[count].core_freq == 9999 ||
29655fa5feaSNate Lawson 		    sc->px_states[count].core_freq == 0x9999 ||
29755fa5feaSNate Lawson 		    sc->px_states[count].core_freq >= 0xffff)
29855fa5feaSNate Lawson 			continue;
29955fa5feaSNate Lawson 
3002880646dSBruno Ducrot 		/* Check for duplicate entries */
3012880646dSBruno Ducrot 		if (count > 0 &&
3022880646dSBruno Ducrot 		    sc->px_states[count - 1].core_freq ==
3032880646dSBruno Ducrot 			sc->px_states[count].core_freq)
3042880646dSBruno Ducrot 			continue;
3052880646dSBruno Ducrot 
30655fa5feaSNate Lawson 		count++;
3076cb2040bSNate Lawson 	}
308cb3f3e5cSNate Lawson 	sc->px_count = count;
30955fa5feaSNate Lawson 
310cb3f3e5cSNate Lawson 	/* No valid Px state found so give up. */
31155fa5feaSNate Lawson 	if (count == 0)
31255fa5feaSNate Lawson 		goto out;
313fbe648c9SJohn Baldwin 	AcpiOsFree(buf.Pointer);
3146cb2040bSNate Lawson 
3156cb2040bSNate Lawson 	/* Get the control and status registers (one of each). */
3166cb2040bSNate Lawson 	buf.Pointer = NULL;
3176cb2040bSNate Lawson 	buf.Length = ACPI_ALLOCATE_BUFFER;
3186cb2040bSNate Lawson 	status = AcpiEvaluateObject(sc->handle, "_PCT", NULL, &buf);
31926d5f7dfSNate Lawson 	if (ACPI_FAILURE(status))
32026d5f7dfSNate Lawson 		goto out;
3216cb2040bSNate Lawson 
3226cb2040bSNate Lawson 	/* Check the package of two registers, each a Buffer in GAS format. */
3236cb2040bSNate Lawson 	pkg = (ACPI_OBJECT *)buf.Pointer;
3246cb2040bSNate Lawson 	if (!ACPI_PKG_VALID(pkg, 2)) {
3256cb2040bSNate Lawson 		device_printf(dev, "invalid perf register package\n");
32626d5f7dfSNate Lawson 		goto out;
3276cb2040bSNate Lawson 	}
3286cb2040bSNate Lawson 
32976ce4cc4SNate Lawson 	error = acpi_PkgGas(sc->dev, pkg, 0, &sc->perf_ctrl_type, &sc->px_rid,
330907b6777SNate Lawson 	    &sc->perf_ctrl, 0);
33176ce4cc4SNate Lawson 	if (error) {
332e22cd41cSNate Lawson 		/*
333e22cd41cSNate Lawson 		 * If the register is of type FFixedHW, we can only return
334e22cd41cSNate Lawson 		 * info, we can't get or set new settings.
335e22cd41cSNate Lawson 		 */
336e22cd41cSNate Lawson 		if (error == EOPNOTSUPP) {
337e22cd41cSNate Lawson 			sc->info_only = TRUE;
338e22cd41cSNate Lawson 			error = 0;
339e22cd41cSNate Lawson 		} else
34026d5f7dfSNate Lawson 			device_printf(dev, "failed in PERF_CTL attach\n");
34126d5f7dfSNate Lawson 		goto out;
3426cb2040bSNate Lawson 	}
3436cb2040bSNate Lawson 	sc->px_rid++;
3446cb2040bSNate Lawson 
34576ce4cc4SNate Lawson 	error = acpi_PkgGas(sc->dev, pkg, 1, &sc->perf_sts_type, &sc->px_rid,
346907b6777SNate Lawson 	    &sc->perf_status, 0);
34776ce4cc4SNate Lawson 	if (error) {
348e22cd41cSNate Lawson 		if (error == EOPNOTSUPP) {
349e22cd41cSNate Lawson 			sc->info_only = TRUE;
350e22cd41cSNate Lawson 			error = 0;
351e22cd41cSNate Lawson 		} else
35226d5f7dfSNate Lawson 			device_printf(dev, "failed in PERF_STATUS attach\n");
35326d5f7dfSNate Lawson 		goto out;
3546cb2040bSNate Lawson 	}
3556cb2040bSNate Lawson 	sc->px_rid++;
3566cb2040bSNate Lawson 
3576cb2040bSNate Lawson 	/* Get our current limit and register for notifies. */
3586cb2040bSNate Lawson 	acpi_px_available(sc);
3596cb2040bSNate Lawson 	AcpiInstallNotifyHandler(sc->handle, ACPI_DEVICE_NOTIFY,
3606cb2040bSNate Lawson 	    acpi_px_notify, sc);
36126d5f7dfSNate Lawson 	error = 0;
3626cb2040bSNate Lawson 
36326d5f7dfSNate Lawson out:
36426d5f7dfSNate Lawson 	if (error) {
365ca2c69c8SNate Lawson 		if (sc->px_states) {
36626d5f7dfSNate Lawson 			free(sc->px_states, M_ACPIPERF);
367ca2c69c8SNate Lawson 			sc->px_states = NULL;
368ca2c69c8SNate Lawson 		}
369ca2c69c8SNate Lawson 		if (sc->perf_ctrl) {
370ca2c69c8SNate Lawson 			bus_release_resource(sc->dev, sc->perf_ctrl_type, 0,
371ca2c69c8SNate Lawson 			    sc->perf_ctrl);
372ca2c69c8SNate Lawson 			bus_delete_resource(sc->dev, sc->perf_ctrl_type, 0);
373ca2c69c8SNate Lawson 			sc->perf_ctrl = NULL;
374ca2c69c8SNate Lawson 		}
375ca2c69c8SNate Lawson 		if (sc->perf_status) {
376ca2c69c8SNate Lawson 			bus_release_resource(sc->dev, sc->perf_sts_type, 1,
377ca2c69c8SNate Lawson 			    sc->perf_status);
378ca2c69c8SNate Lawson 			bus_delete_resource(sc->dev, sc->perf_sts_type, 1);
379ca2c69c8SNate Lawson 			sc->perf_status = NULL;
380ca2c69c8SNate Lawson 		}
381ca2c69c8SNate Lawson 		sc->px_rid = 0;
38226d5f7dfSNate Lawson 		sc->px_count = 0;
38326d5f7dfSNate Lawson 	}
38426d5f7dfSNate Lawson 	if (buf.Pointer)
38526d5f7dfSNate Lawson 		AcpiOsFree(buf.Pointer);
38626d5f7dfSNate Lawson 	return (error);
3876cb2040bSNate Lawson }
3886cb2040bSNate Lawson 
3896cb2040bSNate Lawson static void
acpi_px_startup(void * arg)3903cc2f176SNate Lawson acpi_px_startup(void *arg)
3913cc2f176SNate Lawson {
3923cc2f176SNate Lawson 
3933cc2f176SNate Lawson 	/* Signal to the platform that we are taking over CPU control. */
3942be4e471SJung-uk Kim 	if (AcpiGbl_FADT.PstateControl == 0)
3953cc2f176SNate Lawson 		return;
3963cc2f176SNate Lawson 	ACPI_LOCK(acpi);
3972be4e471SJung-uk Kim 	AcpiOsWritePort(AcpiGbl_FADT.SmiCommand, AcpiGbl_FADT.PstateControl, 8);
3983cc2f176SNate Lawson 	ACPI_UNLOCK(acpi);
3993cc2f176SNate Lawson }
4003cc2f176SNate Lawson 
4013cc2f176SNate Lawson static void
acpi_px_notify(ACPI_HANDLE h,UINT32 notify,void * context)4026cb2040bSNate Lawson acpi_px_notify(ACPI_HANDLE h, UINT32 notify, void *context)
4036cb2040bSNate Lawson {
4046cb2040bSNate Lawson 	struct acpi_perf_softc *sc;
4056cb2040bSNate Lawson 
4066cb2040bSNate Lawson 	sc = context;
4078b888c66SNate Lawson 	if (notify != ACPI_NOTIFY_PERF_STATES)
4088b888c66SNate Lawson 		return;
4098b888c66SNate Lawson 
4106cb2040bSNate Lawson 	acpi_px_available(sc);
4116cb2040bSNate Lawson 
4126cb2040bSNate Lawson 	/* TODO: Implement notification when frequency changes. */
4136cb2040bSNate Lawson }
4146cb2040bSNate Lawson 
4156cb2040bSNate Lawson /*
4166cb2040bSNate Lawson  * Find the highest currently-supported performance state.
4176cb2040bSNate Lawson  * This can be called at runtime (e.g., due to a docking event) at
4186cb2040bSNate Lawson  * the request of a Notify on the processor object.
4196cb2040bSNate Lawson  */
4206cb2040bSNate Lawson static void
acpi_px_available(struct acpi_perf_softc * sc)4216cb2040bSNate Lawson acpi_px_available(struct acpi_perf_softc *sc)
4226cb2040bSNate Lawson {
4236cb2040bSNate Lawson 	ACPI_STATUS status;
4246cb2040bSNate Lawson 	struct cf_setting set;
4256cb2040bSNate Lawson 
4266cb2040bSNate Lawson 	status = acpi_GetInteger(sc->handle, "_PPC", &sc->px_max_avail);
4276cb2040bSNate Lawson 
4286cb2040bSNate Lawson 	/* If the old state is too high, set current state to the new max. */
4296cb2040bSNate Lawson 	if (ACPI_SUCCESS(status)) {
4306cb2040bSNate Lawson 		if (sc->px_curr_state != CPUFREQ_VAL_UNKNOWN &&
4316cb2040bSNate Lawson 		    sc->px_curr_state > sc->px_max_avail) {
4326cb2040bSNate Lawson 			acpi_px_to_set(sc->dev,
4336cb2040bSNate Lawson 			    &sc->px_states[sc->px_max_avail], &set);
4346cb2040bSNate Lawson 			acpi_px_set(sc->dev, &set);
4356cb2040bSNate Lawson 		}
4366cb2040bSNate Lawson 	} else
4376cb2040bSNate Lawson 		sc->px_max_avail = 0;
4386cb2040bSNate Lawson }
4396cb2040bSNate Lawson 
4406cb2040bSNate Lawson static int
acpi_px_to_set(device_t dev,struct acpi_px * px,struct cf_setting * set)4416cb2040bSNate Lawson acpi_px_to_set(device_t dev, struct acpi_px *px, struct cf_setting *set)
4426cb2040bSNate Lawson {
4436cb2040bSNate Lawson 
4446cb2040bSNate Lawson 	if (px == NULL || set == NULL)
4456cb2040bSNate Lawson 		return (EINVAL);
4466cb2040bSNate Lawson 
4476cb2040bSNate Lawson 	set->freq = px->core_freq;
4486cb2040bSNate Lawson 	set->power = px->power;
4496cb2040bSNate Lawson 	/* XXX Include BM latency too? */
4506cb2040bSNate Lawson 	set->lat = px->trans_lat;
4516cb2040bSNate Lawson 	set->volts = CPUFREQ_VAL_UNKNOWN;
4526cb2040bSNate Lawson 	set->dev = dev;
45328d7170fSNate Lawson 	set->spec[PX_SPEC_CONTROL] = px->ctrl_val;
45428d7170fSNate Lawson 	set->spec[PX_SPEC_STATUS] = px->sts_val;
4556cb2040bSNate Lawson 
4566cb2040bSNate Lawson 	return (0);
4576cb2040bSNate Lawson }
4586cb2040bSNate Lawson 
4596cb2040bSNate Lawson static int
acpi_px_settings(device_t dev,struct cf_setting * sets,int * count)460e94a0c1aSNate Lawson acpi_px_settings(device_t dev, struct cf_setting *sets, int *count)
4616cb2040bSNate Lawson {
4626cb2040bSNate Lawson 	struct acpi_perf_softc *sc;
4636cb2040bSNate Lawson 	int x, y;
4646cb2040bSNate Lawson 
4656cb2040bSNate Lawson 	sc = device_get_softc(dev);
4666cb2040bSNate Lawson 	if (sets == NULL || count == NULL)
4676cb2040bSNate Lawson 		return (EINVAL);
4686cb2040bSNate Lawson 	if (*count < sc->px_count - sc->px_max_avail)
469ededc31dSNate Lawson 		return (E2BIG);
4706cb2040bSNate Lawson 
4716cb2040bSNate Lawson 	/* Return a list of settings that are currently valid. */
4726cb2040bSNate Lawson 	y = 0;
4736cb2040bSNate Lawson 	for (x = sc->px_max_avail; x < sc->px_count; x++, y++)
4746cb2040bSNate Lawson 		acpi_px_to_set(dev, &sc->px_states[x], &sets[y]);
4756cb2040bSNate Lawson 	*count = sc->px_count - sc->px_max_avail;
4766cb2040bSNate Lawson 
4776cb2040bSNate Lawson 	return (0);
4786cb2040bSNate Lawson }
4796cb2040bSNate Lawson 
4806cb2040bSNate Lawson static int
acpi_px_set(device_t dev,const struct cf_setting * set)4816cb2040bSNate Lawson acpi_px_set(device_t dev, const struct cf_setting *set)
4826cb2040bSNate Lawson {
4836cb2040bSNate Lawson 	struct acpi_perf_softc *sc;
4846cb2040bSNate Lawson 	int i, status, sts_val, tries;
4856cb2040bSNate Lawson 
4866cb2040bSNate Lawson 	if (set == NULL)
4876cb2040bSNate Lawson 		return (EINVAL);
4886cb2040bSNate Lawson 	sc = device_get_softc(dev);
4896cb2040bSNate Lawson 
490e22cd41cSNate Lawson 	/* If we can't set new states, return immediately. */
491e22cd41cSNate Lawson 	if (sc->info_only)
492e22cd41cSNate Lawson 		return (ENXIO);
493e22cd41cSNate Lawson 
4946cb2040bSNate Lawson 	/* Look up appropriate state, based on frequency. */
4956cb2040bSNate Lawson 	for (i = sc->px_max_avail; i < sc->px_count; i++) {
4966cb2040bSNate Lawson 		if (CPUFREQ_CMP(set->freq, sc->px_states[i].core_freq))
4976cb2040bSNate Lawson 			break;
4986cb2040bSNate Lawson 	}
4996cb2040bSNate Lawson 	if (i == sc->px_count)
5006cb2040bSNate Lawson 		return (EINVAL);
5016cb2040bSNate Lawson 
5026cb2040bSNate Lawson 	/* Write the appropriate value to the register. */
5036cb2040bSNate Lawson 	PX_SET_REG(sc->perf_ctrl, sc->px_states[i].ctrl_val);
5046cb2040bSNate Lawson 
5050db8fa89SNate Lawson 	/*
5060db8fa89SNate Lawson 	 * Try for up to 10 ms to verify the desired state was selected.
5070db8fa89SNate Lawson 	 * This is longer than the standard says (1 ms) but in some modes,
5080db8fa89SNate Lawson 	 * systems may take longer to respond.
5090db8fa89SNate Lawson 	 */
5106cb2040bSNate Lawson 	sts_val = sc->px_states[i].sts_val;
5110db8fa89SNate Lawson 	for (tries = 0; tries < 1000; tries++) {
5126cb2040bSNate Lawson 		status = PX_GET_REG(sc->perf_status);
5130db8fa89SNate Lawson 
5140db8fa89SNate Lawson 		/*
5150db8fa89SNate Lawson 		 * If we match the status or the desired status is 8 bits
5160db8fa89SNate Lawson 		 * and matches the relevant bits, assume we succeeded.  It
5170db8fa89SNate Lawson 		 * appears some systems (IBM R32) expect byte-wide access
5180db8fa89SNate Lawson 		 * even though the standard says the register is 32-bit.
5190db8fa89SNate Lawson 		 */
5200db8fa89SNate Lawson 		if (status == sts_val ||
5210db8fa89SNate Lawson 		    ((sts_val & ~0xff) == 0 && (status & 0xff) == sts_val))
5226cb2040bSNate Lawson 			break;
5236cb2040bSNate Lawson 		DELAY(10);
5246cb2040bSNate Lawson 	}
5250db8fa89SNate Lawson 	if (tries == 1000) {
5266cb2040bSNate Lawson 		device_printf(dev, "Px transition to %d failed\n",
5276cb2040bSNate Lawson 		    sc->px_states[i].core_freq);
5286cb2040bSNate Lawson 		return (ENXIO);
5296cb2040bSNate Lawson 	}
5306cb2040bSNate Lawson 	sc->px_curr_state = i;
5316cb2040bSNate Lawson 
5326cb2040bSNate Lawson 	return (0);
5336cb2040bSNate Lawson }
5346cb2040bSNate Lawson 
5356cb2040bSNate Lawson static int
acpi_px_get(device_t dev,struct cf_setting * set)5366cb2040bSNate Lawson acpi_px_get(device_t dev, struct cf_setting *set)
5376cb2040bSNate Lawson {
5386cb2040bSNate Lawson 	struct acpi_perf_softc *sc;
5396cb2040bSNate Lawson 	uint64_t rate;
5406cb2040bSNate Lawson 	int i;
5416cb2040bSNate Lawson 	struct pcpu *pc;
5426cb2040bSNate Lawson 
5436cb2040bSNate Lawson 	if (set == NULL)
5446cb2040bSNate Lawson 		return (EINVAL);
5456cb2040bSNate Lawson 	sc = device_get_softc(dev);
5466cb2040bSNate Lawson 
547e22cd41cSNate Lawson 	/* If we can't get new states, return immediately. */
548e22cd41cSNate Lawson 	if (sc->info_only)
549e22cd41cSNate Lawson 		return (ENXIO);
550e22cd41cSNate Lawson 
5516cb2040bSNate Lawson 	/* If we've set the rate before, use the cached value. */
5526cb2040bSNate Lawson 	if (sc->px_curr_state != CPUFREQ_VAL_UNKNOWN) {
5536cb2040bSNate Lawson 		acpi_px_to_set(dev, &sc->px_states[sc->px_curr_state], set);
5546cb2040bSNate Lawson 		return (0);
5556cb2040bSNate Lawson 	}
5566cb2040bSNate Lawson 
5576cb2040bSNate Lawson 	/* Otherwise, estimate and try to match against our settings. */
5586cb2040bSNate Lawson 	pc = cpu_get_pcpu(dev);
5596cb2040bSNate Lawson 	if (pc == NULL)
5606cb2040bSNate Lawson 		return (ENXIO);
5616cb2040bSNate Lawson 	cpu_est_clockrate(pc->pc_cpuid, &rate);
5626cb2040bSNate Lawson 	rate /= 1000000;
5636cb2040bSNate Lawson 	for (i = 0; i < sc->px_count; i++) {
5646cb2040bSNate Lawson 		if (CPUFREQ_CMP(sc->px_states[i].core_freq, rate)) {
5656cb2040bSNate Lawson 			sc->px_curr_state = i;
5666cb2040bSNate Lawson 			acpi_px_to_set(dev, &sc->px_states[i], set);
5676cb2040bSNate Lawson 			break;
5686cb2040bSNate Lawson 		}
5696cb2040bSNate Lawson 	}
5706cb2040bSNate Lawson 
5716cb2040bSNate Lawson 	/* No match, give up. */
5726cb2040bSNate Lawson 	if (i == sc->px_count) {
5736cb2040bSNate Lawson 		sc->px_curr_state = CPUFREQ_VAL_UNKNOWN;
5746cb2040bSNate Lawson 		set->freq = CPUFREQ_VAL_UNKNOWN;
5756cb2040bSNate Lawson 	}
5766cb2040bSNate Lawson 
5776cb2040bSNate Lawson 	return (0);
5786cb2040bSNate Lawson }
579e94a0c1aSNate Lawson 
580e94a0c1aSNate Lawson static int
acpi_px_type(device_t dev,int * type)581e94a0c1aSNate Lawson acpi_px_type(device_t dev, int *type)
582e94a0c1aSNate Lawson {
583e94a0c1aSNate Lawson 	struct acpi_perf_softc *sc;
584e94a0c1aSNate Lawson 
585e94a0c1aSNate Lawson 	if (type == NULL)
586e94a0c1aSNate Lawson 		return (EINVAL);
587e94a0c1aSNate Lawson 	sc = device_get_softc(dev);
588e94a0c1aSNate Lawson 
589e94a0c1aSNate Lawson 	*type = CPUFREQ_TYPE_ABSOLUTE;
590e94a0c1aSNate Lawson 	if (sc->info_only)
591e94a0c1aSNate Lawson 		*type |= CPUFREQ_FLAG_INFO_ONLY;
592e94a0c1aSNate Lawson 	return (0);
593e94a0c1aSNate Lawson }
594