xref: /openbsd-src/sys/arch/arm64/dev/aplcpu.c (revision 5b1ba0c506e1039151dc590e0013fd3696a8678a)
1*5b1ba0c5Sjsg /*	$OpenBSD: aplcpu.c,v 1.9 2024/09/29 09:25:37 jsg Exp $	*/
242e2ec98Skettenis /*
342e2ec98Skettenis  * Copyright (c) 2022 Mark Kettenis <kettenis@openbsd.org>
442e2ec98Skettenis  *
542e2ec98Skettenis  * Permission to use, copy, modify, and distribute this software for any
642e2ec98Skettenis  * purpose with or without fee is hereby granted, provided that the above
742e2ec98Skettenis  * copyright notice and this permission notice appear in all copies.
842e2ec98Skettenis  *
942e2ec98Skettenis  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
1042e2ec98Skettenis  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1142e2ec98Skettenis  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1242e2ec98Skettenis  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1342e2ec98Skettenis  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1442e2ec98Skettenis  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1542e2ec98Skettenis  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1642e2ec98Skettenis  */
1742e2ec98Skettenis 
1842e2ec98Skettenis #include <sys/param.h>
1942e2ec98Skettenis #include <sys/systm.h>
2042e2ec98Skettenis #include <sys/device.h>
2142e2ec98Skettenis #include <sys/malloc.h>
22cf0fe25bSkettenis #include <sys/sensors.h>
2342e2ec98Skettenis #include <sys/sysctl.h>
2442e2ec98Skettenis 
2542e2ec98Skettenis #include <machine/bus.h>
2642e2ec98Skettenis #include <machine/fdt.h>
2742e2ec98Skettenis 
2842e2ec98Skettenis #include <dev/ofw/openfirm.h>
2942e2ec98Skettenis #include <dev/ofw/fdt.h>
3042e2ec98Skettenis 
31cf0fe25bSkettenis #define DVFS_CMD			0x0020
32cf0fe25bSkettenis #define DVFS_CMD_BUSY			(1U << 31)
33cf0fe25bSkettenis #define DVFS_CMD_SET			(1 << 25)
34b8e1a447Skettenis #define DVFS_CMD_PS2_MASK		(0x1f << 12)
35cf0fe25bSkettenis #define DVFS_CMD_PS2_SHIFT		12
36b8e1a447Skettenis #define DVFS_CMD_PS1_MASK		(0x1f << 0)
37cf0fe25bSkettenis #define DVFS_CMD_PS1_SHIFT		0
3842e2ec98Skettenis 
39cf0fe25bSkettenis #define DVFS_STATUS			0x50
40b8e1a447Skettenis #define DVFS_T8103_STATUS_CUR_PS_MASK	(0xf << 4)
41b8e1a447Skettenis #define DVFS_T8103_STATUS_CUR_PS_SHIFT	4
42b8e1a447Skettenis #define DVFS_T8112_STATUS_CUR_PS_MASK	(0x1f << 5)
43b8e1a447Skettenis #define DVFS_T8112_STATUS_CUR_PS_SHIFT	5
4442e2ec98Skettenis 
45c4936e80Skettenis #define APLCPU_DEEP_WFI_LATENCY		10 /* microseconds */
46c4936e80Skettenis 
4742e2ec98Skettenis struct opp {
4842e2ec98Skettenis 	uint64_t opp_hz;
4942e2ec98Skettenis 	uint32_t opp_level;
5042e2ec98Skettenis };
5142e2ec98Skettenis 
5242e2ec98Skettenis struct opp_table {
5342e2ec98Skettenis 	LIST_ENTRY(opp_table) ot_list;
5442e2ec98Skettenis 	uint32_t ot_phandle;
5542e2ec98Skettenis 
5642e2ec98Skettenis 	struct opp *ot_opp;
5742e2ec98Skettenis 	u_int ot_nopp;
5842e2ec98Skettenis 	uint64_t ot_opp_hz_min;
5942e2ec98Skettenis 	uint64_t ot_opp_hz_max;
6042e2ec98Skettenis };
6142e2ec98Skettenis 
6242e2ec98Skettenis #define APLCPU_MAX_CLUSTERS	8
6342e2ec98Skettenis 
6442e2ec98Skettenis struct aplcpu_softc {
6542e2ec98Skettenis 	struct device		sc_dev;
6642e2ec98Skettenis 	bus_space_tag_t		sc_iot;
6742e2ec98Skettenis 	bus_space_handle_t	sc_ioh[APLCPU_MAX_CLUSTERS];
6842e2ec98Skettenis 	bus_size_t		sc_ios[APLCPU_MAX_CLUSTERS];
6942e2ec98Skettenis 
7042e2ec98Skettenis 	int			sc_node;
7142e2ec98Skettenis 	u_int			sc_nclusters;
7242e2ec98Skettenis 	int			sc_perflevel;
7342e2ec98Skettenis 
74b8e1a447Skettenis 	uint32_t		sc_cur_ps_mask;
75b8e1a447Skettenis 	u_int			sc_cur_ps_shift;
76b8e1a447Skettenis 
7742e2ec98Skettenis 	LIST_HEAD(, opp_table)	sc_opp_tables;
7842e2ec98Skettenis 	struct opp_table	*sc_opp_table[APLCPU_MAX_CLUSTERS];
7942e2ec98Skettenis 	uint64_t		sc_opp_hz_min;
8042e2ec98Skettenis 	uint64_t		sc_opp_hz_max;
81cf0fe25bSkettenis 
82cf0fe25bSkettenis 	struct ksensordev	sc_sensordev;
83cf0fe25bSkettenis 	struct ksensor		sc_sensor[APLCPU_MAX_CLUSTERS];
8442e2ec98Skettenis };
8542e2ec98Skettenis 
8642e2ec98Skettenis int	aplcpu_match(struct device *, void *, void *);
8742e2ec98Skettenis void	aplcpu_attach(struct device *, struct device *, void *);
8842e2ec98Skettenis 
8942e2ec98Skettenis const struct cfattach aplcpu_ca = {
9042e2ec98Skettenis 	sizeof (struct aplcpu_softc), aplcpu_match, aplcpu_attach
9142e2ec98Skettenis };
9242e2ec98Skettenis 
9342e2ec98Skettenis struct cfdriver aplcpu_cd = {
9442e2ec98Skettenis 	NULL, "aplcpu", DV_DULL
9542e2ec98Skettenis };
9642e2ec98Skettenis 
9742e2ec98Skettenis void	aplcpu_opp_init(struct aplcpu_softc *, int);
98b8e1a447Skettenis uint32_t aplcpu_opp_level(struct aplcpu_softc *, int);
9942e2ec98Skettenis int	aplcpu_clockspeed(int *);
10042e2ec98Skettenis void	aplcpu_setperf(int level);
101cf0fe25bSkettenis void	aplcpu_refresh_sensors(void *);
102*5b1ba0c5Sjsg void	aplcpu_idle_cycle(void);
103c4936e80Skettenis void	aplcpu_deep_wfi(void);
10442e2ec98Skettenis 
10542e2ec98Skettenis int
10642e2ec98Skettenis aplcpu_match(struct device *parent, void *match, void *aux)
10742e2ec98Skettenis {
10842e2ec98Skettenis 	struct fdt_attach_args *faa = aux;
10942e2ec98Skettenis 
11099901a0dSkettenis 	return OF_is_compatible(faa->fa_node, "apple,soc-cpufreq") ||
11199901a0dSkettenis 	    OF_is_compatible(faa->fa_node, "apple,cluster-cpufreq");
11242e2ec98Skettenis }
11342e2ec98Skettenis 
11442e2ec98Skettenis void
11542e2ec98Skettenis aplcpu_attach(struct device *parent, struct device *self, void *aux)
11642e2ec98Skettenis {
11742e2ec98Skettenis 	struct aplcpu_softc *sc = (struct aplcpu_softc *)self;
11842e2ec98Skettenis 	struct fdt_attach_args *faa = aux;
11942e2ec98Skettenis 	struct cpu_info *ci;
12042e2ec98Skettenis 	CPU_INFO_ITERATOR cii;
12142e2ec98Skettenis 	int i;
12242e2ec98Skettenis 
12342e2ec98Skettenis 	if (faa->fa_nreg < 1) {
12442e2ec98Skettenis 		printf(": no registers\n");
12542e2ec98Skettenis 		return;
12642e2ec98Skettenis 	}
12742e2ec98Skettenis 
12842e2ec98Skettenis 	if (faa->fa_nreg > APLCPU_MAX_CLUSTERS) {
12942e2ec98Skettenis 		printf(": too many registers\n");
13042e2ec98Skettenis 		return;
13142e2ec98Skettenis 	}
13242e2ec98Skettenis 
13342e2ec98Skettenis 	sc->sc_iot = faa->fa_iot;
13442e2ec98Skettenis 	for (i = 0; i < faa->fa_nreg; i++) {
13542e2ec98Skettenis 		if (bus_space_map(sc->sc_iot, faa->fa_reg[i].addr,
13642e2ec98Skettenis 		    faa->fa_reg[i].size, 0, &sc->sc_ioh[i])) {
13742e2ec98Skettenis 			printf(": can't map registers\n");
13842e2ec98Skettenis 			goto unmap;
13942e2ec98Skettenis 		}
14042e2ec98Skettenis 		sc->sc_ios[i] = faa->fa_reg[i].size;
14142e2ec98Skettenis 	}
14242e2ec98Skettenis 
14342e2ec98Skettenis 	printf("\n");
14442e2ec98Skettenis 
14542e2ec98Skettenis 	sc->sc_node = faa->fa_node;
14642e2ec98Skettenis 	sc->sc_nclusters = faa->fa_nreg;
14742e2ec98Skettenis 
14899901a0dSkettenis 	if (OF_is_compatible(sc->sc_node, "apple,t8103-soc-cpufreq") ||
14999901a0dSkettenis 	    OF_is_compatible(sc->sc_node, "apple,t8103-cluster-cpufreq")) {
150b8e1a447Skettenis 		sc->sc_cur_ps_mask = DVFS_T8103_STATUS_CUR_PS_MASK;
151b8e1a447Skettenis 		sc->sc_cur_ps_shift = DVFS_T8103_STATUS_CUR_PS_SHIFT;
15299901a0dSkettenis 	} else if (OF_is_compatible(sc->sc_node, "apple,t8112-soc-cpufreq") ||
15399901a0dSkettenis 	    OF_is_compatible(sc->sc_node, "apple,t8112-cluster-cpufreq")) {
154b8e1a447Skettenis 		sc->sc_cur_ps_mask = DVFS_T8112_STATUS_CUR_PS_MASK;
155b8e1a447Skettenis 		sc->sc_cur_ps_shift = DVFS_T8112_STATUS_CUR_PS_SHIFT;
156b8e1a447Skettenis 	}
157b8e1a447Skettenis 
15842e2ec98Skettenis 	sc->sc_opp_hz_min = UINT64_MAX;
15942e2ec98Skettenis 	sc->sc_opp_hz_max = 0;
16042e2ec98Skettenis 
16142e2ec98Skettenis 	LIST_INIT(&sc->sc_opp_tables);
16242e2ec98Skettenis 	CPU_INFO_FOREACH(cii, ci) {
16342e2ec98Skettenis 		aplcpu_opp_init(sc, ci->ci_node);
16442e2ec98Skettenis 	}
16542e2ec98Skettenis 
166cf0fe25bSkettenis 	for (i = 0; i < sc->sc_nclusters; i++) {
167cf0fe25bSkettenis 		sc->sc_sensor[i].type = SENSOR_FREQ;
168cf0fe25bSkettenis 		sensor_attach(&sc->sc_sensordev, &sc->sc_sensor[i]);
169cf0fe25bSkettenis 	}
170cf0fe25bSkettenis 
171cf0fe25bSkettenis 	aplcpu_refresh_sensors(sc);
172cf0fe25bSkettenis 
173cf0fe25bSkettenis 	strlcpy(sc->sc_sensordev.xname, sc->sc_dev.dv_xname,
174cf0fe25bSkettenis 	    sizeof(sc->sc_sensordev.xname));
175cf0fe25bSkettenis 	sensordev_install(&sc->sc_sensordev);
176cf0fe25bSkettenis 	sensor_task_register(sc, aplcpu_refresh_sensors, 1);
177cf0fe25bSkettenis 
178c4936e80Skettenis 	cpu_idle_cycle_fcn = aplcpu_idle_cycle;
179c4936e80Skettenis 	cpu_suspend_cycle_fcn = aplcpu_deep_wfi;
18042e2ec98Skettenis 	cpu_cpuspeed = aplcpu_clockspeed;
18142e2ec98Skettenis 	cpu_setperf = aplcpu_setperf;
18242e2ec98Skettenis 	return;
18342e2ec98Skettenis 
18442e2ec98Skettenis unmap:
18542e2ec98Skettenis 	for (i = 0; i < faa->fa_nreg; i++) {
18642e2ec98Skettenis 		if (sc->sc_ios[i] == 0)
18742e2ec98Skettenis 			continue;
18842e2ec98Skettenis 		bus_space_unmap(sc->sc_iot, sc->sc_ioh[i], sc->sc_ios[i]);
18942e2ec98Skettenis 	}
19042e2ec98Skettenis }
19142e2ec98Skettenis 
19242e2ec98Skettenis void
19342e2ec98Skettenis aplcpu_opp_init(struct aplcpu_softc *sc, int node)
19442e2ec98Skettenis {
19542e2ec98Skettenis 	struct opp_table *ot;
19642e2ec98Skettenis 	int count, child;
19742e2ec98Skettenis 	uint32_t freq_domain[2], phandle;
19842e2ec98Skettenis 	uint32_t opp_hz, opp_level;
19942e2ec98Skettenis 	int i, j;
20042e2ec98Skettenis 
20199901a0dSkettenis 	freq_domain[0] = OF_getpropint(node, "performance-domains", 0);
20299901a0dSkettenis 	freq_domain[1] = 0;
20399901a0dSkettenis 	if (freq_domain[0] == 0) {
20442e2ec98Skettenis 		if (OF_getpropintarray(node, "apple,freq-domain", freq_domain,
20542e2ec98Skettenis 		    sizeof(freq_domain)) != sizeof(freq_domain))
20642e2ec98Skettenis 			return;
20742e2ec98Skettenis 		if (freq_domain[1] > APLCPU_MAX_CLUSTERS)
20842e2ec98Skettenis 			return;
20999901a0dSkettenis 	}
21099901a0dSkettenis 	if (freq_domain[0] != OF_getpropint(sc->sc_node, "phandle", 0))
21199901a0dSkettenis 		return;
21242e2ec98Skettenis 
21342e2ec98Skettenis 	phandle = OF_getpropint(node, "operating-points-v2", 0);
21442e2ec98Skettenis 	if (phandle == 0)
21542e2ec98Skettenis 		return;
21642e2ec98Skettenis 
21742e2ec98Skettenis 	LIST_FOREACH(ot, &sc->sc_opp_tables, ot_list) {
21842e2ec98Skettenis 		if (ot->ot_phandle == phandle) {
21942e2ec98Skettenis 			sc->sc_opp_table[freq_domain[1]] = ot;
22042e2ec98Skettenis 			return;
22142e2ec98Skettenis 		}
22242e2ec98Skettenis 	}
22342e2ec98Skettenis 
22442e2ec98Skettenis 	node = OF_getnodebyphandle(phandle);
22542e2ec98Skettenis 	if (node == 0)
22642e2ec98Skettenis 		return;
22742e2ec98Skettenis 
22842e2ec98Skettenis 	if (!OF_is_compatible(node, "operating-points-v2"))
22942e2ec98Skettenis 		return;
23042e2ec98Skettenis 
23142e2ec98Skettenis 	count = 0;
232c4936e80Skettenis 	for (child = OF_child(node); child != 0; child = OF_peer(child))
23342e2ec98Skettenis 		count++;
23442e2ec98Skettenis 	if (count == 0)
23542e2ec98Skettenis 		return;
23642e2ec98Skettenis 
23742e2ec98Skettenis 	ot = malloc(sizeof(struct opp_table), M_DEVBUF, M_ZERO | M_WAITOK);
23842e2ec98Skettenis 	ot->ot_phandle = phandle;
23942e2ec98Skettenis 	ot->ot_opp = mallocarray(count, sizeof(struct opp),
24042e2ec98Skettenis 	    M_DEVBUF, M_ZERO | M_WAITOK);
24142e2ec98Skettenis 	ot->ot_nopp = count;
24242e2ec98Skettenis 
24342e2ec98Skettenis 	count = 0;
24442e2ec98Skettenis 	for (child = OF_child(node); child != 0; child = OF_peer(child)) {
24542e2ec98Skettenis 		opp_hz = OF_getpropint64(child, "opp-hz", 0);
24642e2ec98Skettenis 		opp_level = OF_getpropint(child, "opp-level", 0);
24742e2ec98Skettenis 
24842e2ec98Skettenis 		/* Insert into the array, keeping things sorted. */
24942e2ec98Skettenis 		for (i = 0; i < count; i++) {
25042e2ec98Skettenis 			if (opp_hz < ot->ot_opp[i].opp_hz)
25142e2ec98Skettenis 				break;
25242e2ec98Skettenis 		}
25342e2ec98Skettenis 		for (j = count; j > i; j--)
25442e2ec98Skettenis 			ot->ot_opp[j] = ot->ot_opp[j - 1];
25542e2ec98Skettenis 		ot->ot_opp[i].opp_hz = opp_hz;
25642e2ec98Skettenis 		ot->ot_opp[i].opp_level = opp_level;
25742e2ec98Skettenis 		count++;
25842e2ec98Skettenis 	}
25942e2ec98Skettenis 
26042e2ec98Skettenis 	ot->ot_opp_hz_min = ot->ot_opp[0].opp_hz;
26142e2ec98Skettenis 	ot->ot_opp_hz_max = ot->ot_opp[count - 1].opp_hz;
26242e2ec98Skettenis 
26342e2ec98Skettenis 	LIST_INSERT_HEAD(&sc->sc_opp_tables, ot, ot_list);
26442e2ec98Skettenis 	sc->sc_opp_table[freq_domain[1]] = ot;
26542e2ec98Skettenis 
26642e2ec98Skettenis 	/* Keep track of overall min/max frequency. */
26742e2ec98Skettenis 	if (sc->sc_opp_hz_min > ot->ot_opp_hz_min)
26842e2ec98Skettenis 		sc->sc_opp_hz_min = ot->ot_opp_hz_min;
26942e2ec98Skettenis 	if (sc->sc_opp_hz_max < ot->ot_opp_hz_max)
27042e2ec98Skettenis 		sc->sc_opp_hz_max = ot->ot_opp_hz_max;
27142e2ec98Skettenis }
27242e2ec98Skettenis 
273b8e1a447Skettenis uint32_t
274b8e1a447Skettenis aplcpu_opp_level(struct aplcpu_softc *sc, int cluster)
275b8e1a447Skettenis {
276b8e1a447Skettenis 	uint32_t opp_level;
277b8e1a447Skettenis 	uint64_t pstate;
278b8e1a447Skettenis 
279b8e1a447Skettenis 	if (sc->sc_cur_ps_mask) {
280b8e1a447Skettenis 		pstate = bus_space_read_8(sc->sc_iot, sc->sc_ioh[cluster],
281b8e1a447Skettenis 		    DVFS_STATUS);
282b8e1a447Skettenis 		opp_level = (pstate & sc->sc_cur_ps_mask);
283b8e1a447Skettenis 		opp_level >>= sc->sc_cur_ps_shift;
284b8e1a447Skettenis 	} else {
285b8e1a447Skettenis 		pstate = bus_space_read_8(sc->sc_iot, sc->sc_ioh[cluster],
286b8e1a447Skettenis 		    DVFS_CMD);
287b8e1a447Skettenis 		opp_level = (pstate & DVFS_CMD_PS1_MASK);
288b8e1a447Skettenis 		opp_level >>= DVFS_CMD_PS1_SHIFT;
289b8e1a447Skettenis 	}
290b8e1a447Skettenis 
291b8e1a447Skettenis 	return opp_level;
292b8e1a447Skettenis }
293b8e1a447Skettenis 
29442e2ec98Skettenis int
29542e2ec98Skettenis aplcpu_clockspeed(int *freq)
29642e2ec98Skettenis {
29799901a0dSkettenis 	struct aplcpu_softc *sc;
29842e2ec98Skettenis 	struct opp_table *ot;
29942e2ec98Skettenis 	uint32_t opp_hz = 0, opp_level;
30099901a0dSkettenis 	int i, j, k;
30142e2ec98Skettenis 
30242e2ec98Skettenis 	/*
30342e2ec98Skettenis 	 * Clusters can run at different frequencies.  We report the
30442e2ec98Skettenis 	 * highest frequency among all clusters.
30542e2ec98Skettenis 	 */
30642e2ec98Skettenis 
30799901a0dSkettenis 	for (i = 0; i < aplcpu_cd.cd_ndevs; i++) {
30899901a0dSkettenis 		sc = aplcpu_cd.cd_devs[i];
30999901a0dSkettenis 		if (sc == NULL)
31042e2ec98Skettenis 			continue;
31142e2ec98Skettenis 
31299901a0dSkettenis 		for (j = 0; j < sc->sc_nclusters; j++) {
31399901a0dSkettenis 			if (sc->sc_opp_table[j] == NULL)
31499901a0dSkettenis 				continue;
31599901a0dSkettenis 
31699901a0dSkettenis 			opp_level = aplcpu_opp_level(sc, j);
31742e2ec98Skettenis 
31842e2ec98Skettenis 			/* Translate P-state to frequency. */
31999901a0dSkettenis 			ot = sc->sc_opp_table[j];
32099901a0dSkettenis 			for (k = 0; k < ot->ot_nopp; k++) {
32199901a0dSkettenis 				if (ot->ot_opp[k].opp_level != opp_level)
32299901a0dSkettenis 					continue;
32399901a0dSkettenis 				opp_hz = MAX(opp_hz, ot->ot_opp[k].opp_hz);
32442e2ec98Skettenis 			}
32542e2ec98Skettenis 		}
32699901a0dSkettenis 	}
32799901a0dSkettenis 
32842e2ec98Skettenis 	if (opp_hz == 0)
32942e2ec98Skettenis 		return EINVAL;
33042e2ec98Skettenis 
33142e2ec98Skettenis 	*freq = opp_hz / 1000000;
33242e2ec98Skettenis 	return 0;
33342e2ec98Skettenis }
33442e2ec98Skettenis 
33542e2ec98Skettenis void
33642e2ec98Skettenis aplcpu_setperf(int level)
33742e2ec98Skettenis {
33899901a0dSkettenis 	struct aplcpu_softc *sc;
33942e2ec98Skettenis 	struct opp_table *ot;
34042e2ec98Skettenis 	uint64_t min, max;
34142e2ec98Skettenis 	uint64_t level_hz;
34242e2ec98Skettenis 	uint32_t opp_level;
34342e2ec98Skettenis 	uint64_t reg;
34499901a0dSkettenis 	int i, j, k, timo;
34542e2ec98Skettenis 
34642e2ec98Skettenis 	/*
34742e2ec98Skettenis 	 * We let the CPU performance level span the entire range
34842e2ec98Skettenis 	 * between the lowest frequency on any of the clusters and the
34942e2ec98Skettenis 	 * highest frequency on any of the clusters.  We pick a
35042e2ec98Skettenis 	 * frequency within that range based on the performance level
35142e2ec98Skettenis 	 * and set all the clusters to the frequency that is closest
35242e2ec98Skettenis 	 * to but less than that frequency.  This isn't a particularly
35342e2ec98Skettenis 	 * sensible method but it is easy to implement and it is hard
35442e2ec98Skettenis 	 * to come up with something more sensible given the
35542e2ec98Skettenis 	 * constraints of the hw.setperf sysctl interface.
35642e2ec98Skettenis 	 */
35799901a0dSkettenis 	for (i = 0; i < aplcpu_cd.cd_ndevs; i++) {
35899901a0dSkettenis 		sc = aplcpu_cd.cd_devs[i];
35999901a0dSkettenis 		if (sc == NULL)
36099901a0dSkettenis 			continue;
36199901a0dSkettenis 
36242e2ec98Skettenis 		min = sc->sc_opp_hz_min;
36342e2ec98Skettenis 		max = sc->sc_opp_hz_max;
36442e2ec98Skettenis 		level_hz = min + (level * (max - min)) / 100;
36599901a0dSkettenis 	}
36642e2ec98Skettenis 
36799901a0dSkettenis 	for (i = 0; i < aplcpu_cd.cd_ndevs; i++) {
36899901a0dSkettenis 		sc = aplcpu_cd.cd_devs[i];
36999901a0dSkettenis 		if (sc == NULL)
37099901a0dSkettenis 			continue;
37199901a0dSkettenis 		if (sc->sc_perflevel == level)
37299901a0dSkettenis 			continue;
37399901a0dSkettenis 
37499901a0dSkettenis 		for (j = 0; j < sc->sc_nclusters; j++) {
37599901a0dSkettenis 			if (sc->sc_opp_table[j] == NULL)
37642e2ec98Skettenis 				continue;
37742e2ec98Skettenis 
37842e2ec98Skettenis 			/* Translate performance level to a P-state. */
37999901a0dSkettenis 			ot = sc->sc_opp_table[j];
380d206605eSkettenis 			opp_level = ot->ot_opp[0].opp_level;
38199901a0dSkettenis 			for (k = 0; k < ot->ot_nopp; k++) {
38299901a0dSkettenis 				if (ot->ot_opp[k].opp_hz <= level_hz &&
38399901a0dSkettenis 				    ot->ot_opp[k].opp_level >= opp_level)
38499901a0dSkettenis 					opp_level = ot->ot_opp[k].opp_level;
38542e2ec98Skettenis 			}
38642e2ec98Skettenis 
38742e2ec98Skettenis 			/* Wait until P-state logic isn't busy. */
38842e2ec98Skettenis 			for (timo = 100; timo > 0; timo--) {
38999901a0dSkettenis 				reg = bus_space_read_8(sc->sc_iot,
39099901a0dSkettenis 				    sc->sc_ioh[j], DVFS_CMD);
391cf0fe25bSkettenis 				if ((reg & DVFS_CMD_BUSY) == 0)
39242e2ec98Skettenis 					break;
39342e2ec98Skettenis 				delay(1);
39442e2ec98Skettenis 			}
395cf0fe25bSkettenis 			if (reg & DVFS_CMD_BUSY)
39642e2ec98Skettenis 				continue;
39742e2ec98Skettenis 
39842e2ec98Skettenis 			/* Set desired P-state. */
399cf0fe25bSkettenis 			reg &= ~DVFS_CMD_PS1_MASK;
400cf0fe25bSkettenis 			reg |= (opp_level << DVFS_CMD_PS1_SHIFT);
401cf0fe25bSkettenis 			reg |= DVFS_CMD_SET;
40299901a0dSkettenis 			bus_space_write_8(sc->sc_iot, sc->sc_ioh[j],
40399901a0dSkettenis 			    DVFS_CMD, reg);
40442e2ec98Skettenis 		}
40542e2ec98Skettenis 
40642e2ec98Skettenis 		sc->sc_perflevel = level;
40742e2ec98Skettenis 	}
40899901a0dSkettenis }
409cf0fe25bSkettenis 
410cf0fe25bSkettenis void
411cf0fe25bSkettenis aplcpu_refresh_sensors(void *arg)
412cf0fe25bSkettenis {
413cf0fe25bSkettenis 	struct aplcpu_softc *sc = arg;
414cf0fe25bSkettenis 	struct opp_table *ot;
415cf0fe25bSkettenis 	uint32_t opp_level;
416cf0fe25bSkettenis 	int i, j;
417cf0fe25bSkettenis 
418cf0fe25bSkettenis 	for (i = 0; i < sc->sc_nclusters; i++) {
419cf0fe25bSkettenis 		if (sc->sc_opp_table[i] == NULL)
420cf0fe25bSkettenis 			continue;
421cf0fe25bSkettenis 
422b8e1a447Skettenis 		opp_level = aplcpu_opp_level(sc, i);
423cf0fe25bSkettenis 
424cf0fe25bSkettenis 		/* Translate P-state to frequency. */
425cf0fe25bSkettenis 		ot = sc->sc_opp_table[i];
426cf0fe25bSkettenis 		for (j = 0; j < ot->ot_nopp; j++) {
427cf0fe25bSkettenis 			if (ot->ot_opp[j].opp_level == opp_level) {
428cf0fe25bSkettenis 				sc->sc_sensor[i].value = ot->ot_opp[j].opp_hz;
429cf0fe25bSkettenis 				break;
430cf0fe25bSkettenis 			}
431cf0fe25bSkettenis 		}
432cf0fe25bSkettenis 	}
433cf0fe25bSkettenis }
434c4936e80Skettenis 
435c4936e80Skettenis void
436c4936e80Skettenis aplcpu_idle_cycle(void)
437c4936e80Skettenis {
438c4936e80Skettenis 	struct cpu_info *ci = curcpu();
439c4936e80Skettenis 	struct timeval start, stop;
440c4936e80Skettenis 	u_long itime;
441c4936e80Skettenis 
442c4936e80Skettenis 	microuptime(&start);
443c4936e80Skettenis 
444c4936e80Skettenis 	if (ci->ci_prev_sleep > 3 * APLCPU_DEEP_WFI_LATENCY)
445c4936e80Skettenis 		aplcpu_deep_wfi();
446c4936e80Skettenis 	else
447c4936e80Skettenis 		cpu_wfi();
448c4936e80Skettenis 
449c4936e80Skettenis 	microuptime(&stop);
450c4936e80Skettenis 	timersub(&stop, &start, &stop);
451c4936e80Skettenis 	itime = stop.tv_sec * 1000000 + stop.tv_usec;
452c4936e80Skettenis 
453c4936e80Skettenis 	ci->ci_last_itime = itime;
454c4936e80Skettenis 	itime >>= 1;
455c4936e80Skettenis 	ci->ci_prev_sleep = (ci->ci_prev_sleep + (ci->ci_prev_sleep >> 1)
456c4936e80Skettenis 	    + itime) >> 1;
457c4936e80Skettenis }
458