]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sys/dev/cpufreq/cpufreq_dt.c
Upgrade our copies of clang, llvm, lld, lldb, compiler-rt and libc++ to
[FreeBSD/FreeBSD.git] / sys / dev / cpufreq / cpufreq_dt.c
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);