]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sys/dev/cpufreq/cpufreq_dt.c
Copy libevent sources to contrib
[FreeBSD/FreeBSD.git] / sys / dev / cpufreq / cpufreq_dt.c
1 /*-
2  * Copyright (c) 2018 Emmanuel Vadot <manu@FreeBSD.Org>
3  * Copyright (c) 2016 Jared McNeill <jmcneill@invisible.ca>
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
16  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
19  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
20  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
22  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  *
27  * $FreeBSD$
28  */
29
30 /*
31  * Generic DT based cpufreq driver
32  */
33
34 #include <sys/cdefs.h>
35 __FBSDID("$FreeBSD$");
36
37 #include <sys/param.h>
38 #include <sys/systm.h>
39 #include <sys/bus.h>
40 #include <sys/rman.h>
41 #include <sys/kernel.h>
42 #include <sys/module.h>
43 #include <sys/cpu.h>
44 #include <sys/cpuset.h>
45 #include <sys/smp.h>
46
47 #include <dev/ofw/ofw_bus.h>
48 #include <dev/ofw/ofw_bus_subr.h>
49
50 #include <dev/extres/clk/clk.h>
51 #include <dev/extres/regulator/regulator.h>
52
53 #include "cpufreq_if.h"
54
55 #if 0
56 #define DEBUG(dev, msg...) device_printf(dev, "cpufreq_dt: " msg);
57 #else
58 #define DEBUG(dev, msg...)
59 #endif
60
61 enum opp_version {
62         OPP_V1 = 1,
63         OPP_V2,
64 };
65
66 struct cpufreq_dt_opp {
67         uint64_t        freq;
68         uint32_t        uvolt_target;
69         uint32_t        uvolt_min;
70         uint32_t        uvolt_max;
71         uint32_t        uamps;
72         uint32_t        clk_latency;
73         bool            turbo_mode;
74         bool            opp_suspend;
75 };
76
77 struct cpufreq_dt_softc {
78         device_t dev;
79         clk_t clk;
80         regulator_t reg;
81
82         struct cpufreq_dt_opp *opp;
83         ssize_t nopp;
84
85         cpuset_t cpus;
86 };
87
88 static void
89 cpufreq_dt_notify(device_t dev, uint64_t freq)
90 {
91         struct cpufreq_dt_softc *sc;
92         struct pcpu *pc;
93         int cpu;
94
95         sc = device_get_softc(dev);
96
97         CPU_FOREACH(cpu) {
98                 if (CPU_ISSET(cpu, &sc->cpus)) {
99                         pc = pcpu_find(cpu);
100                         pc->pc_clock = freq;
101                 }
102         }
103 }
104
105 static const struct cpufreq_dt_opp *
106 cpufreq_dt_find_opp(device_t dev, uint64_t freq)
107 {
108         struct cpufreq_dt_softc *sc;
109         ssize_t n;
110
111         sc = device_get_softc(dev);
112
113         DEBUG(dev, "Looking for freq %ju\n", freq);
114         for (n = 0; n < sc->nopp; n++)
115                 if (CPUFREQ_CMP(sc->opp[n].freq, freq))
116                         return (&sc->opp[n]);
117
118         DEBUG(dev, "Couldn't find one\n");
119         return (NULL);
120 }
121
122 static void
123 cpufreq_dt_opp_to_setting(device_t dev, const struct cpufreq_dt_opp *opp,
124     struct cf_setting *set)
125 {
126         struct cpufreq_dt_softc *sc;
127
128         sc = device_get_softc(dev);
129
130         memset(set, 0, sizeof(*set));
131         set->freq = opp->freq / 1000000;
132         set->volts = opp->uvolt_target / 1000;
133         set->power = CPUFREQ_VAL_UNKNOWN;
134         set->lat = opp->clk_latency;
135         set->dev = dev;
136 }
137
138 static int
139 cpufreq_dt_get(device_t dev, struct cf_setting *set)
140 {
141         struct cpufreq_dt_softc *sc;
142         const struct cpufreq_dt_opp *opp;
143         uint64_t freq;
144
145         sc = device_get_softc(dev);
146
147         DEBUG(dev, "cpufreq_dt_get\n");
148         if (clk_get_freq(sc->clk, &freq) != 0)
149                 return (ENXIO);
150
151         opp = cpufreq_dt_find_opp(dev, freq);
152         if (opp == NULL) {
153                 device_printf(dev, "Can't find the current freq in opp\n");
154                 return (ENOENT);
155         }
156
157         cpufreq_dt_opp_to_setting(dev, opp, set);
158
159         DEBUG(dev, "Current freq %dMhz\n", set->freq);
160         return (0);
161 }
162
163 static int
164 cpufreq_dt_set(device_t dev, const struct cf_setting *set)
165 {
166         struct cpufreq_dt_softc *sc;
167         const struct cpufreq_dt_opp *opp, *copp;
168         uint64_t freq;
169         int error = 0;
170
171         sc = device_get_softc(dev);
172
173         if (clk_get_freq(sc->clk, &freq) != 0) {
174                 device_printf(dev, "Can't get current clk freq\n");
175                 return (ENXIO);
176         }
177
178         DEBUG(sc->dev, "Current freq %ju\n", freq);
179         DEBUG(sc->dev, "Target freq %ju\n", (uint64_t)set->freq * 1000000);
180         copp = cpufreq_dt_find_opp(sc->dev, freq);
181         if (copp == NULL) {
182                 device_printf(dev, "Can't find the current freq in opp\n");
183                 return (ENOENT);
184         }
185         opp = cpufreq_dt_find_opp(sc->dev, set->freq * 1000000);
186         if (opp == NULL) {
187                 device_printf(dev, "Couldn't find an opp for this freq\n");
188                 return (EINVAL);
189         }
190
191         if (copp->uvolt_target < opp->uvolt_target) {
192                 DEBUG(dev, "Changing regulator from %u to %u\n",
193                     copp->uvolt_target, opp->uvolt_target);
194                 error = regulator_set_voltage(sc->reg,
195                     opp->uvolt_min,
196                     opp->uvolt_max);
197                 if (error != 0) {
198                         DEBUG(dev, "Failed, backout\n");
199                         return (ENXIO);
200                 }
201         }
202
203         DEBUG(dev, "Setting clk to %ju\n", opp->freq);
204         error = clk_set_freq(sc->clk, opp->freq, 0);
205         if (error != 0) {
206                 DEBUG(dev, "Failed, backout\n");
207                 /* Restore previous voltage (best effort) */
208                 error = regulator_set_voltage(sc->reg,
209                     copp->uvolt_min,
210                     copp->uvolt_max);
211                 return (ENXIO);
212         }
213
214         if (copp->uvolt_target > opp->uvolt_target) {
215                 error = regulator_set_voltage(sc->reg,
216                     opp->uvolt_min,
217                     opp->uvolt_max);
218                 if (error != 0) {
219                         DEBUG(dev, "Failed to switch regulator to %d\n",
220                             opp->uvolt_target);
221                         /* Restore previous CPU frequency (best effort) */
222                         (void)clk_set_freq(sc->clk,
223                             copp->freq, 0);
224                         return (ENXIO);
225                 }
226         }
227
228         if (clk_get_freq(sc->clk, &freq) == 0)
229                 cpufreq_dt_notify(dev, freq);
230
231         return (0);
232 }
233
234
235 static int
236 cpufreq_dt_type(device_t dev, int *type)
237 {
238         if (type == NULL)
239                 return (EINVAL);
240
241         *type = CPUFREQ_TYPE_ABSOLUTE;
242         return (0);
243 }
244
245 static int
246 cpufreq_dt_settings(device_t dev, struct cf_setting *sets, int *count)
247 {
248         struct cpufreq_dt_softc *sc;
249         ssize_t n;
250
251         DEBUG(dev, "cpufreq_dt_settings\n");
252         if (sets == NULL || count == NULL)
253                 return (EINVAL);
254
255         sc = device_get_softc(dev);
256
257         if (*count < sc->nopp) {
258                 *count = (int)sc->nopp;
259                 return (E2BIG);
260         }
261
262         for (n = 0; n < sc->nopp; n++)
263                 cpufreq_dt_opp_to_setting(dev, &sc->opp[n], &sets[n]);
264
265         *count = (int)sc->nopp;
266
267         return (0);
268 }
269
270 static void
271 cpufreq_dt_identify(driver_t *driver, device_t parent)
272 {
273         phandle_t node;
274
275         /* Properties must be listed under node /cpus/cpu@0 */
276         node = ofw_bus_get_node(parent);
277
278         /* The cpu@0 node must have the following properties */
279         if (!OF_hasprop(node, "clocks") ||
280             !OF_hasprop(node, "cpu-supply"))
281                 return;
282
283         if (!OF_hasprop(node, "operating-points") &&
284             !OF_hasprop(node, "operating-points-v2"))
285                 return;
286
287         if (device_find_child(parent, "cpufreq_dt", -1) != NULL)
288                 return;
289
290         if (BUS_ADD_CHILD(parent, 0, "cpufreq_dt", -1) == NULL)
291                 device_printf(parent, "add cpufreq_dt child failed\n");
292 }
293
294 static int
295 cpufreq_dt_probe(device_t dev)
296 {
297         phandle_t node;
298
299         node = ofw_bus_get_node(device_get_parent(dev));
300
301         if (!OF_hasprop(node, "clocks") ||
302             !OF_hasprop(node, "cpu-supply"))
303                 return (ENXIO);
304
305         if (!OF_hasprop(node, "operating-points") &&
306           !OF_hasprop(node, "operating-points-v2"))
307                 return (ENXIO);
308
309         device_set_desc(dev, "Generic cpufreq driver");
310         return (BUS_PROBE_GENERIC);
311 }
312
313 static int
314 cpufreq_dt_oppv1_parse(struct cpufreq_dt_softc *sc, phandle_t node)
315 {
316         uint32_t *opp, lat;
317         ssize_t n;
318
319         sc->nopp = OF_getencprop_alloc_multi(node, "operating-points",
320             sizeof(uint32_t) * 2, (void **)&opp);
321         if (sc->nopp == -1)
322                 return (ENXIO);
323
324         if (OF_getencprop(node, "clock-latency", &lat, sizeof(lat)) == -1)
325                 lat = CPUFREQ_VAL_UNKNOWN;
326
327         sc->opp = malloc(sizeof(*sc->opp) * sc->nopp, M_DEVBUF, M_WAITOK);
328
329         for (n = 0; n < sc->nopp; n++) {
330                 sc->opp[n].freq = opp[n * 2 + 0] * 1000;
331                 sc->opp[n].uvolt_min = opp[n * 2 + 1];
332                 sc->opp[n].uvolt_max = sc->opp[n].uvolt_min;
333                 sc->opp[n].uvolt_target = sc->opp[n].uvolt_min;
334                 sc->opp[n].clk_latency = lat;
335
336                 if (bootverbose)
337                         device_printf(sc->dev, "%ju.%03ju MHz, %u uV\n",
338                             sc->opp[n].freq / 1000000,
339                             sc->opp[n].freq % 1000000,
340                             sc->opp[n].uvolt_target);
341         }
342         free(opp, M_OFWPROP);
343
344         return (0);
345 }
346
347 static int
348 cpufreq_dt_oppv2_parse(struct cpufreq_dt_softc *sc, phandle_t node)
349 {
350         phandle_t opp, opp_table, opp_xref;
351         pcell_t cell[2];
352         uint32_t *volts, lat;
353         int nvolt, i;
354
355         if (OF_getencprop(node, "operating-points-v2", &opp_xref,
356             sizeof(opp_xref)) == -1) {
357                 device_printf(sc->dev, "Cannot get xref to oppv2 table\n");
358                 return (ENXIO);
359         }
360
361         opp_table = OF_node_from_xref(opp_xref);
362         if (opp_table == opp_xref)
363                 return (ENXIO);
364
365         if (!OF_hasprop(opp_table, "opp-shared")) {
366                 device_printf(sc->dev, "Only opp-shared is supported\n");
367                 return (ENXIO);
368         }
369
370         for (opp = OF_child(opp_table); opp > 0; opp = OF_peer(opp))
371                 sc->nopp += 1;
372
373         sc->opp = malloc(sizeof(*sc->opp) * sc->nopp, M_DEVBUF, M_WAITOK);
374
375         for (i = 0, opp_table = OF_child(opp_table); opp_table > 0;
376              opp_table = OF_peer(opp_table), i++) {
377                 /* opp-hz is a required property */
378                 if (OF_getencprop(opp_table, "opp-hz", cell,
379                     sizeof(cell)) == -1)
380                         continue;
381
382                 sc->opp[i].freq = cell[0];
383                 sc->opp[i].freq <<= 32;
384                 sc->opp[i].freq |= cell[1];
385
386                 if (OF_getencprop(opp_table, "clock-latency", &lat,
387                     sizeof(lat)) == -1)
388                         sc->opp[i].clk_latency = CPUFREQ_VAL_UNKNOWN;
389                 else
390                         sc->opp[i].clk_latency = (int)lat;
391
392                 if (OF_hasprop(opp_table, "turbo-mode"))
393                         sc->opp[i].turbo_mode = true;
394                 if (OF_hasprop(opp_table, "opp-suspend"))
395                         sc->opp[i].opp_suspend = true;
396
397                 nvolt = OF_getencprop_alloc_multi(opp_table, "opp-microvolt",
398                   sizeof(*volts), (void **)&volts);
399                 if (nvolt == 1) {
400                         sc->opp[i].uvolt_target = volts[0];
401                         sc->opp[i].uvolt_min = volts[0];
402                         sc->opp[i].uvolt_max = volts[0];
403                 } else if (nvolt == 3) {
404                         sc->opp[i].uvolt_target = volts[0];
405                         sc->opp[i].uvolt_min = volts[1];
406                         sc->opp[i].uvolt_max = volts[2];
407                 } else {
408                         device_printf(sc->dev,
409                             "Wrong count of opp-microvolt property\n");
410                         OF_prop_free(volts);
411                         free(sc->opp, M_DEVBUF);
412                         return (ENXIO);
413                 }
414                 OF_prop_free(volts);
415
416                 if (bootverbose)
417                         device_printf(sc->dev, "%ju.%03ju Mhz (%u uV)\n",
418                             sc->opp[i].freq / 1000000,
419                             sc->opp[i].freq % 1000000,
420                             sc->opp[i].uvolt_target);
421         }
422         return (0);
423 }
424
425 static int
426 cpufreq_dt_attach(device_t dev)
427 {
428         struct cpufreq_dt_softc *sc;
429         phandle_t node;
430         phandle_t cnode, opp, copp;
431         int cpu;
432         uint64_t freq;
433         int rv = 0;
434         enum opp_version version;
435
436         sc = device_get_softc(dev);
437         sc->dev = dev;
438         node = ofw_bus_get_node(device_get_parent(dev));
439
440         if (regulator_get_by_ofw_property(dev, node,
441             "cpu-supply", &sc->reg) != 0) {
442                 device_printf(dev, "no regulator for %s\n",
443                     ofw_bus_get_name(device_get_parent(dev)));
444                 return (ENXIO);
445         }
446
447         if (clk_get_by_ofw_index(dev, node, 0, &sc->clk) != 0) {
448                 device_printf(dev, "no clock for %s\n",
449                     ofw_bus_get_name(device_get_parent(dev)));
450                 regulator_release(sc->reg);
451                 return (ENXIO);
452         }
453
454         if (OF_hasprop(node, "operating-points")) {
455                 version = OPP_V1;
456                 rv = cpufreq_dt_oppv1_parse(sc, node);
457                 if (rv != 0) {
458                         device_printf(dev, "Failed to parse opp-v1 table\n");
459                         return (rv);
460                 }
461                 OF_getencprop(node, "operating-points", &opp,
462                     sizeof(opp));
463         } else {
464                 version = OPP_V2;
465                 rv = cpufreq_dt_oppv2_parse(sc, node);
466                 if (rv != 0) {
467                         device_printf(dev, "Failed to parse opp-v2 table\n");
468                         return (rv);
469                 }
470                 OF_getencprop(node, "operating-points-v2", &opp,
471                     sizeof(opp));
472         }
473
474         /*
475          * Find all CPUs that share the same opp table
476          */
477         CPU_ZERO(&sc->cpus);
478         cpu = device_get_unit(device_get_parent(dev));
479         for (cnode = node; cnode > 0; cnode = OF_peer(cnode), cpu++) {
480                 copp = -1;
481                 if (version == OPP_V1)
482                         OF_getencprop(cnode, "operating-points", &copp,
483                             sizeof(copp));
484                 else if (version == OPP_V2)
485                         OF_getencprop(cnode, "operating-points-v2",
486                             &copp, sizeof(copp));
487                 if (opp == copp)
488                         CPU_SET(cpu, &sc->cpus);
489         }
490
491         if (clk_get_freq(sc->clk, &freq) == 0)
492                 cpufreq_dt_notify(dev, freq);
493
494         cpufreq_register(dev);
495
496         return (0);
497 }
498
499
500 static device_method_t cpufreq_dt_methods[] = {
501         /* Device interface */
502         DEVMETHOD(device_identify,      cpufreq_dt_identify),
503         DEVMETHOD(device_probe,         cpufreq_dt_probe),
504         DEVMETHOD(device_attach,        cpufreq_dt_attach),
505
506         /* cpufreq interface */
507         DEVMETHOD(cpufreq_drv_get,      cpufreq_dt_get),
508         DEVMETHOD(cpufreq_drv_set,      cpufreq_dt_set),
509         DEVMETHOD(cpufreq_drv_type,     cpufreq_dt_type),
510         DEVMETHOD(cpufreq_drv_settings, cpufreq_dt_settings),
511
512         DEVMETHOD_END
513 };
514
515 static driver_t cpufreq_dt_driver = {
516         "cpufreq_dt",
517         cpufreq_dt_methods,
518         sizeof(struct cpufreq_dt_softc),
519 };
520
521 static devclass_t cpufreq_dt_devclass;
522
523 DRIVER_MODULE(cpufreq_dt, cpu, cpufreq_dt_driver, cpufreq_dt_devclass, 0, 0);
524 MODULE_VERSION(cpufreq_dt, 1);