xref: /freebsd/sys/dev/cpufreq/cpufreq_dt.c (revision d6b92ffa)
1 /*-
2  * Copyright (c) 2016 Jared McNeill <jmcneill@invisible.ca>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
15  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
18  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
19  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
21  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  *
26  * $FreeBSD$
27  */
28 
29 /*
30  * Generic DT based cpufreq driver
31  */
32 
33 #include <sys/cdefs.h>
34 __FBSDID("$FreeBSD$");
35 
36 #include <sys/param.h>
37 #include <sys/systm.h>
38 #include <sys/bus.h>
39 #include <sys/rman.h>
40 #include <sys/kernel.h>
41 #include <sys/module.h>
42 #include <sys/cpu.h>
43 #include <sys/cpuset.h>
44 #include <sys/smp.h>
45 
46 #include <dev/ofw/ofw_bus.h>
47 #include <dev/ofw/ofw_bus_subr.h>
48 
49 #include <dev/extres/clk/clk.h>
50 #include <dev/extres/regulator/regulator.h>
51 
52 #include "cpufreq_if.h"
53 
54 struct cpufreq_dt_opp {
55 	uint32_t	freq_khz;
56 	uint32_t	voltage_uv;
57 };
58 
59 struct cpufreq_dt_softc {
60 	clk_t clk;
61 	regulator_t reg;
62 
63 	struct cpufreq_dt_opp *opp;
64 	ssize_t nopp;
65 	int clk_latency;
66 
67 	cpuset_t cpus;
68 };
69 
70 static void
71 cpufreq_dt_notify(device_t dev, uint64_t freq)
72 {
73 #ifdef __aarch64__
74 	struct cpufreq_dt_softc *sc;
75 	struct pcpu *pc;
76 	int cpu;
77 
78 	sc = device_get_softc(dev);
79 
80 	CPU_FOREACH(cpu) {
81 		if (CPU_ISSET(cpu, &sc->cpus)) {
82 			pc = pcpu_find(cpu);
83 			pc->pc_clock = freq;
84 		}
85 	}
86 #endif
87 }
88 
89 static const struct cpufreq_dt_opp *
90 cpufreq_dt_find_opp(device_t dev, uint32_t freq_mhz)
91 {
92 	struct cpufreq_dt_softc *sc;
93 	ssize_t n;
94 
95 	sc = device_get_softc(dev);
96 
97 	for (n = 0; n < sc->nopp; n++)
98 		if (CPUFREQ_CMP(sc->opp[n].freq_khz / 1000, freq_mhz))
99 			return (&sc->opp[n]);
100 
101 	return (NULL);
102 }
103 
104 static void
105 cpufreq_dt_opp_to_setting(device_t dev, const struct cpufreq_dt_opp *opp,
106     struct cf_setting *set)
107 {
108 	struct cpufreq_dt_softc *sc;
109 
110 	sc = device_get_softc(dev);
111 
112 	memset(set, 0, sizeof(*set));
113 	set->freq = opp->freq_khz / 1000;
114 	set->volts = opp->voltage_uv / 1000;
115 	set->power = CPUFREQ_VAL_UNKNOWN;
116 	set->lat = sc->clk_latency;
117 	set->dev = dev;
118 }
119 
120 static int
121 cpufreq_dt_get(device_t dev, struct cf_setting *set)
122 {
123 	struct cpufreq_dt_softc *sc;
124 	const struct cpufreq_dt_opp *opp;
125 	uint64_t freq;
126 
127 	sc = device_get_softc(dev);
128 
129 	if (clk_get_freq(sc->clk, &freq) != 0)
130 		return (ENXIO);
131 
132 	opp = cpufreq_dt_find_opp(dev, freq / 1000000);
133 	if (opp == NULL)
134 		return (ENOENT);
135 
136 	cpufreq_dt_opp_to_setting(dev, opp, set);
137 
138 	return (0);
139 }
140 
141 static int
142 cpufreq_dt_set(device_t dev, const struct cf_setting *set)
143 {
144 	struct cpufreq_dt_softc *sc;
145 	const struct cpufreq_dt_opp *opp, *copp;
146 	uint64_t freq;
147 	int error;
148 
149 	sc = device_get_softc(dev);
150 
151 	if (clk_get_freq(sc->clk, &freq) != 0)
152 		return (ENXIO);
153 
154 	copp = cpufreq_dt_find_opp(dev, freq / 1000000);
155 	if (copp == NULL)
156 		return (ENOENT);
157 	opp = cpufreq_dt_find_opp(dev, set->freq);
158 	if (opp == NULL)
159 		return (EINVAL);
160 
161 	if (copp->voltage_uv < opp->voltage_uv) {
162 		error = regulator_set_voltage(sc->reg, opp->voltage_uv,
163 		    opp->voltage_uv);
164 		if (error != 0)
165 			return (ENXIO);
166 	}
167 
168 	error = clk_set_freq(sc->clk, (uint64_t)opp->freq_khz * 1000, 0);
169 	if (error != 0) {
170 		/* Restore previous voltage (best effort) */
171 		(void)regulator_set_voltage(sc->reg, copp->voltage_uv,
172 		    copp->voltage_uv);
173 		return (ENXIO);
174 	}
175 
176 	if (copp->voltage_uv > opp->voltage_uv) {
177 		error = regulator_set_voltage(sc->reg, opp->voltage_uv,
178 		    opp->voltage_uv);
179 		if (error != 0) {
180 			/* Restore previous CPU frequency (best effort) */
181 			(void)clk_set_freq(sc->clk,
182 			    (uint64_t)copp->freq_khz * 1000, 0);
183 			return (ENXIO);
184 		}
185 	}
186 
187 	if (clk_get_freq(sc->clk, &freq) == 0)
188 		cpufreq_dt_notify(dev, freq);
189 
190 	return (0);
191 }
192 
193 
194 static int
195 cpufreq_dt_type(device_t dev, int *type)
196 {
197 	if (type == NULL)
198 		return (EINVAL);
199 
200 	*type = CPUFREQ_TYPE_ABSOLUTE;
201 	return (0);
202 }
203 
204 static int
205 cpufreq_dt_settings(device_t dev, struct cf_setting *sets, int *count)
206 {
207 	struct cpufreq_dt_softc *sc;
208 	ssize_t n;
209 
210 	if (sets == NULL || count == NULL)
211 		return (EINVAL);
212 
213 	sc = device_get_softc(dev);
214 
215 	if (*count < sc->nopp) {
216 		*count = (int)sc->nopp;
217 		return (E2BIG);
218 	}
219 
220 	for (n = 0; n < sc->nopp; n++)
221 		cpufreq_dt_opp_to_setting(dev, &sc->opp[n], &sets[n]);
222 
223 	*count = (int)sc->nopp;
224 
225 	return (0);
226 }
227 
228 static void
229 cpufreq_dt_identify(driver_t *driver, device_t parent)
230 {
231 	phandle_t node;
232 
233 	/* Properties must be listed under node /cpus/cpu@0 */
234 	node = ofw_bus_get_node(parent);
235 
236 	/* The cpu@0 node must have the following properties */
237 	if (!OF_hasprop(node, "operating-points") ||
238 	    !OF_hasprop(node, "clocks") ||
239 	    !OF_hasprop(node, "cpu-supply"))
240 		return;
241 
242 	if (device_find_child(parent, "cpufreq_dt", -1) != NULL)
243 		return;
244 
245 	if (BUS_ADD_CHILD(parent, 0, "cpufreq_dt", -1) == NULL)
246 		device_printf(parent, "add cpufreq_dt child failed\n");
247 }
248 
249 static int
250 cpufreq_dt_probe(device_t dev)
251 {
252 	phandle_t node;
253 
254 	node = ofw_bus_get_node(device_get_parent(dev));
255 
256 	if (!OF_hasprop(node, "operating-points") ||
257 	    !OF_hasprop(node, "clocks") ||
258 	    !OF_hasprop(node, "cpu-supply"))
259 		return (ENXIO);
260 
261 	device_set_desc(dev, "Generic cpufreq driver");
262 	return (BUS_PROBE_GENERIC);
263 }
264 
265 static int
266 cpufreq_dt_attach(device_t dev)
267 {
268 	struct cpufreq_dt_softc *sc;
269 	uint32_t *opp, lat;
270 	phandle_t node, cnode;
271 	uint64_t freq;
272 	ssize_t n;
273 	int cpu;
274 
275 	sc = device_get_softc(dev);
276 	node = ofw_bus_get_node(device_get_parent(dev));
277 
278 	if (regulator_get_by_ofw_property(dev, node,
279 	    "cpu-supply", &sc->reg) != 0) {
280 		device_printf(dev, "no regulator for %s\n",
281 		    ofw_bus_get_name(device_get_parent(dev)));
282 		return (ENXIO);
283 	}
284 
285 	if (clk_get_by_ofw_index(dev, node, 0, &sc->clk) != 0) {
286 		device_printf(dev, "no clock for %s\n",
287 		    ofw_bus_get_name(device_get_parent(dev)));
288 		regulator_release(sc->reg);
289 		return (ENXIO);
290 	}
291 
292 	sc->nopp = OF_getencprop_alloc(node, "operating-points",
293 	    sizeof(*sc->opp), (void **)&opp);
294 	if (sc->nopp == -1)
295 		return (ENXIO);
296 	sc->opp = malloc(sizeof(*sc->opp) * sc->nopp, M_DEVBUF, M_WAITOK);
297 	for (n = 0; n < sc->nopp; n++) {
298 		sc->opp[n].freq_khz = opp[n * 2 + 0];
299 		sc->opp[n].voltage_uv = opp[n * 2 + 1];
300 
301 		if (bootverbose)
302 			device_printf(dev, "%u.%03u MHz, %u uV\n",
303 			    sc->opp[n].freq_khz / 1000,
304 			    sc->opp[n].freq_khz % 1000,
305 			    sc->opp[n].voltage_uv);
306 	}
307 	free(opp, M_OFWPROP);
308 
309 	if (OF_getencprop(node, "clock-latency", &lat, sizeof(lat)) == -1)
310 		sc->clk_latency = CPUFREQ_VAL_UNKNOWN;
311 	else
312 		sc->clk_latency = (int)lat;
313 
314 	/*
315 	 * Find all CPUs that share the same voltage and CPU frequency
316 	 * controls. Start with the current node and move forward until
317 	 * the end is reached or a peer has an "operating-points" property.
318 	 */
319 	CPU_ZERO(&sc->cpus);
320 	cpu = device_get_unit(device_get_parent(dev));
321 	for (cnode = node; cnode > 0; cnode = OF_peer(cnode), cpu++) {
322 		if (cnode != node && OF_hasprop(cnode, "operating-points"))
323 			break;
324 		CPU_SET(cpu, &sc->cpus);
325 	}
326 
327 	if (clk_get_freq(sc->clk, &freq) == 0)
328 		cpufreq_dt_notify(dev, freq);
329 
330 	cpufreq_register(dev);
331 
332 	return (0);
333 }
334 
335 
336 static device_method_t cpufreq_dt_methods[] = {
337 	/* Device interface */
338 	DEVMETHOD(device_identify,	cpufreq_dt_identify),
339 	DEVMETHOD(device_probe,		cpufreq_dt_probe),
340 	DEVMETHOD(device_attach,	cpufreq_dt_attach),
341 
342 	/* cpufreq interface */
343 	DEVMETHOD(cpufreq_drv_get,	cpufreq_dt_get),
344 	DEVMETHOD(cpufreq_drv_set,	cpufreq_dt_set),
345 	DEVMETHOD(cpufreq_drv_type,	cpufreq_dt_type),
346 	DEVMETHOD(cpufreq_drv_settings,	cpufreq_dt_settings),
347 
348 	DEVMETHOD_END
349 };
350 
351 static driver_t cpufreq_dt_driver = {
352 	"cpufreq_dt",
353 	cpufreq_dt_methods,
354 	sizeof(struct cpufreq_dt_softc),
355 };
356 
357 static devclass_t cpufreq_dt_devclass;
358 
359 DRIVER_MODULE(cpufreq_dt, cpu, cpufreq_dt_driver, cpufreq_dt_devclass, 0, 0);
360 MODULE_VERSION(cpufreq_dt, 1);
361