The Design and Implementation of the FreeBSD Operating System, Second Edition
Now available: The Design and Implementation of the FreeBSD Operating System (Second Edition)


[ source navigation ] [ diff markup ] [ identifier search ] [ freetext search ] [ file search ] [ list types ] [ track identifier ]

FreeBSD/Linux Kernel Cross Reference
sys/arm64/nvidia/tegra210/tegra210_cpufreq.c

Version: -  FREEBSD  -  FREEBSD-13-STABLE  -  FREEBSD-13-0  -  FREEBSD-12-STABLE  -  FREEBSD-12-0  -  FREEBSD-11-STABLE  -  FREEBSD-11-0  -  FREEBSD-10-STABLE  -  FREEBSD-10-0  -  FREEBSD-9-STABLE  -  FREEBSD-9-0  -  FREEBSD-8-STABLE  -  FREEBSD-8-0  -  FREEBSD-7-STABLE  -  FREEBSD-7-0  -  FREEBSD-6-STABLE  -  FREEBSD-6-0  -  FREEBSD-5-STABLE  -  FREEBSD-5-0  -  FREEBSD-4-STABLE  -  FREEBSD-3-STABLE  -  FREEBSD22  -  l41  -  OPENBSD  -  linux-2.6  -  MK84  -  PLAN9  -  xnu-8792 
SearchContext: -  none  -  3  -  10 

    1 /*-
    2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
    3  *
    4  * Copyright 2020 Michal Meloun <mmel@FreeBSD.org>
    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 AND CONTRIBUTORS ``AS IS'' AND
   16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   18  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
   19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
   21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
   22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   23  * LIABILITY, 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 
   28 #include <sys/cdefs.h>
   29 __FBSDID("$FreeBSD$");
   30 
   31 #include <sys/param.h>
   32 #include <sys/systm.h>
   33 #include <sys/bus.h>
   34 #include <sys/cpu.h>
   35 #include <sys/kernel.h>
   36 #include <sys/lock.h>
   37 #include <sys/malloc.h>
   38 #include <sys/module.h>
   39 
   40 #include <machine/bus.h>
   41 #include <machine/cpu.h>
   42 
   43 #include <dev/extres/clk/clk.h>
   44 #include <dev/extres/regulator/regulator.h>
   45 #include <dev/ofw/ofw_bus_subr.h>
   46 
   47 #include <arm/nvidia/tegra_efuse.h>
   48 
   49 #include "cpufreq_if.h"
   50 
   51 /* CPU voltage table entry */
   52 struct speedo_entry {
   53         uint64_t                freq;   /* Frequency point */
   54         int                     c0;     /* Coeeficient values for */
   55         int                     c1;     /* quadratic equation: */
   56         int                     c2;     /* c2 * speedo^2 + c1 * speedo + c0 */
   57 };
   58 
   59 struct cpu_volt_def {
   60         int                     min_uvolt;      /* Min allowed CPU voltage */
   61         int                     max_uvolt;      /* Max allowed CPU voltage */
   62         int                     step_uvolt;     /* Step of CPU voltage */
   63         int                     speedo_scale;   /* Scaling factor for cvt */
   64         int                     speedo_nitems;  /* Size of speedo table */
   65         struct speedo_entry     *speedo_tbl;    /* CPU voltage table */
   66 };
   67 
   68 struct cpu_speed_point {
   69         uint64_t                freq;           /* Frequecy */
   70         int                     uvolt;          /* Requested voltage */
   71 };
   72 
   73 static struct speedo_entry tegra210_speedo_tbl[] =
   74 {
   75         {204000000UL,   1007452, -23865, 370},
   76         {306000000UL,   1052709, -24875, 370},
   77         {408000000UL,   1099069, -25895, 370},
   78         {510000000UL,   1146534, -26905, 370},
   79         {612000000UL,   1195102, -27915, 370},
   80         {714000000UL,   1244773, -28925, 370},
   81         {816000000UL,   1295549, -29935, 370},
   82         {918000000UL,   1347428, -30955, 370},
   83         {1020000000UL,  1400411, -31965, 370},
   84         {1122000000UL,  1454497, -32975, 370},
   85         {1224000000UL,  1509687, -33985, 370},
   86         {1326000000UL,  1565981, -35005, 370},
   87         {1428000000UL,  1623379, -36015, 370},
   88         {1530000000UL,  1681880, -37025, 370},
   89         {1632000000UL,  1741485, -38035, 370},
   90         {1734000000UL,  1802194, -39055, 370},
   91         {1836000000UL,  1864006, -40065, 370},
   92         {1912500000UL,  1910780, -40815, 370},
   93         {2014500000UL,  1227000,      0,   0},
   94         {2218500000UL,  1227000,      0,   0},
   95 };
   96 
   97 static struct cpu_volt_def tegra210_cpu_volt_def =
   98 {
   99         .min_uvolt = 900000,            /* 0.9 V */
  100         .max_uvolt = 1227000,           /* 1.227 */
  101         .step_uvolt =  10000,           /* 10 mV */
  102         .speedo_scale = 100,
  103         .speedo_nitems = nitems(tegra210_speedo_tbl),
  104         .speedo_tbl = tegra210_speedo_tbl,
  105 };
  106 
  107 static uint64_t cpu_max_freq[] = {
  108         1912500000UL,
  109         1912500000UL,
  110         2218500000UL,
  111         1785000000UL,
  112         1632000000UL,
  113         1912500000UL,
  114         2014500000UL,
  115         1734000000UL,
  116         1683000000UL,
  117         1555500000UL,
  118         1504500000UL,
  119 };
  120 
  121 static uint64_t cpu_freq_tbl[] = {
  122          204000000UL,
  123          306000000UL,
  124          408000000UL,
  125          510000000UL,
  126          612000000UL,
  127          714000000UL,
  128          816000000UL,
  129          918000000UL,
  130         1020000000UL,
  131         1122000000UL,
  132         1224000000UL,
  133         1326000000UL,
  134         1428000000UL,
  135         1530000000UL,
  136         1632000000UL,
  137         1734000000UL,
  138         1836000000UL,
  139         1912500000UL,
  140         2014500000UL,
  141         2218500000UL,
  142 };
  143 
  144 struct tegra210_cpufreq_softc {
  145         device_t                dev;
  146         phandle_t               node;
  147 
  148         clk_t                   clk_cpu_g;
  149         clk_t                   clk_pll_x;
  150         clk_t                   clk_pll_p;
  151         clk_t                   clk_dfll;
  152 
  153         int                     process_id;
  154         int                     speedo_id;
  155         int                     speedo_value;
  156 
  157         uint64_t                cpu_max_freq;
  158         struct cpu_volt_def     *cpu_def;
  159         struct cpu_speed_point  *speed_points;
  160         int                     nspeed_points;
  161 
  162         struct cpu_speed_point  *act_speed_point;
  163 
  164         int                     latency;
  165 };
  166 
  167 static int cpufreq_lowest_freq = 1;
  168 TUNABLE_INT("hw.tegra210.cpufreq.lowest_freq", &cpufreq_lowest_freq);
  169 
  170 #define DIV_ROUND_CLOSEST(val, div)     (((val) + ((div) / 2)) / (div))
  171 
  172 #define ROUND_UP(val, div)      roundup(val, div)
  173 #define ROUND_DOWN(val, div)    rounddown(val, div)
  174 
  175 /*
  176  * Compute requesetd voltage for given frequency and SoC process variations,
  177  * - compute base voltage from speedo value using speedo table
  178  * - round up voltage to next regulator step
  179  * - clamp it to regulator limits
  180  */
  181 static int
  182 freq_to_voltage(struct tegra210_cpufreq_softc *sc, uint64_t freq)
  183 {
  184         int uv, scale, min_uvolt, max_uvolt, step_uvolt;
  185         struct speedo_entry *ent;
  186         int i;
  187 
  188         /* Get speedo entry with higher frequency */
  189         ent = NULL;
  190         for (i = 0; i < sc->cpu_def->speedo_nitems; i++) {
  191                 if (sc->cpu_def->speedo_tbl[i].freq >= freq) {
  192                         ent = &sc->cpu_def->speedo_tbl[i];
  193                         break;
  194                 }
  195         }
  196         if (ent == NULL)
  197                 ent = &sc->cpu_def->speedo_tbl[sc->cpu_def->speedo_nitems - 1];
  198         scale = sc->cpu_def->speedo_scale;
  199 
  200 
  201         /* uV = (c2 * speedo / scale + c1) * speedo / scale + c0) */
  202         uv = DIV_ROUND_CLOSEST(ent->c2 * sc->speedo_value, scale);
  203         uv = DIV_ROUND_CLOSEST((uv + ent->c1) * sc->speedo_value, scale) +
  204             ent->c0;
  205         step_uvolt = sc->cpu_def->step_uvolt;
  206         /* Round up it to next regulator step */
  207         uv = ROUND_UP(uv, step_uvolt);
  208 
  209         /* Clamp result */
  210         min_uvolt = ROUND_UP(sc->cpu_def->min_uvolt, step_uvolt);
  211         max_uvolt = ROUND_DOWN(sc->cpu_def->max_uvolt, step_uvolt);
  212         if (uv < min_uvolt)
  213                 uv =  min_uvolt;
  214         if (uv > max_uvolt)
  215                 uv =  max_uvolt;
  216         return (uv);
  217 
  218 }
  219 
  220 static void
  221 build_speed_points(struct tegra210_cpufreq_softc *sc) {
  222         int i;
  223 
  224         sc->nspeed_points = nitems(cpu_freq_tbl);
  225         sc->speed_points = malloc(sizeof(struct cpu_speed_point) *
  226             sc->nspeed_points, M_DEVBUF, M_NOWAIT);
  227         for (i = 0; i < sc->nspeed_points; i++) {
  228                 sc->speed_points[i].freq = cpu_freq_tbl[i];
  229                 sc->speed_points[i].uvolt = freq_to_voltage(sc,
  230                     cpu_freq_tbl[i]);
  231         }
  232 }
  233 
  234 static struct cpu_speed_point *
  235 get_speed_point(struct tegra210_cpufreq_softc *sc, uint64_t freq)
  236 {
  237         int i;
  238 
  239         if (sc->speed_points[0].freq >= freq)
  240                 return (sc->speed_points + 0);
  241 
  242         for (i = 0; i < sc->nspeed_points - 1; i++) {
  243                 if (sc->speed_points[i + 1].freq > freq)
  244                         return (sc->speed_points + i);
  245         }
  246 
  247         return (sc->speed_points + sc->nspeed_points - 1);
  248 }
  249 
  250 static int
  251 tegra210_cpufreq_settings(device_t dev, struct cf_setting *sets, int *count)
  252 {
  253         struct tegra210_cpufreq_softc *sc;
  254         int i, j;
  255 
  256         if (sets == NULL || count == NULL)
  257                 return (EINVAL);
  258 
  259         sc = device_get_softc(dev);
  260         memset(sets, CPUFREQ_VAL_UNKNOWN, sizeof(*sets) * (*count));
  261 
  262         for (i = 0, j = sc->nspeed_points - 1; j >= 0; j--) {
  263                 if (sc->cpu_max_freq < sc->speed_points[j].freq)
  264                         continue;
  265                 sets[i].freq = sc->speed_points[j].freq / 1000000;
  266                 sets[i].volts = sc->speed_points[j].uvolt / 1000;
  267                 sets[i].lat = sc->latency;
  268                 sets[i].dev = dev;
  269                 i++;
  270         }
  271         *count = i;
  272 
  273         return (0);
  274 }
  275 
  276 static int
  277 set_cpu_freq(struct tegra210_cpufreq_softc *sc, uint64_t freq)
  278 {
  279         struct cpu_speed_point *point;
  280         int rv;
  281 
  282         point = get_speed_point(sc, freq);
  283 
  284         /* Set PLLX frequency */
  285         rv = clk_set_freq(sc->clk_pll_x, point->freq, CLK_SET_ROUND_DOWN);
  286         if (rv != 0) {
  287                 device_printf(sc->dev, "Can't set CPU clock frequency\n");
  288                 return (rv);
  289         }
  290 
  291         sc->act_speed_point = point;
  292 
  293         return (0);
  294 }
  295 
  296 static int
  297 tegra210_cpufreq_set(device_t dev, const struct cf_setting *cf)
  298 {
  299         struct tegra210_cpufreq_softc *sc;
  300         uint64_t freq;
  301         int rv;
  302 
  303         if (cf == NULL || cf->freq < 0)
  304                 return (EINVAL);
  305 
  306         sc = device_get_softc(dev);
  307 
  308         freq = cf->freq;
  309         if (freq < cpufreq_lowest_freq)
  310                 freq = cpufreq_lowest_freq;
  311         freq *= 1000000;
  312         if (freq >= sc->cpu_max_freq)
  313                 freq = sc->cpu_max_freq;
  314         rv = set_cpu_freq(sc, freq);
  315 
  316         return (rv);
  317 }
  318 
  319 static int
  320 tegra210_cpufreq_get(device_t dev, struct cf_setting *cf)
  321 {
  322         struct tegra210_cpufreq_softc *sc;
  323 
  324         if (cf == NULL)
  325                 return (EINVAL);
  326 
  327         sc = device_get_softc(dev);
  328         memset(cf, CPUFREQ_VAL_UNKNOWN, sizeof(*cf));
  329         cf->dev = NULL;
  330         cf->freq = sc->act_speed_point->freq / 1000000;
  331         cf->volts = sc->act_speed_point->uvolt / 1000;
  332         /* Transition latency in us. */
  333         cf->lat = sc->latency;
  334         /* Driver providing this setting. */
  335         cf->dev = dev;
  336 
  337         return (0);
  338 }
  339 
  340 
  341 static int
  342 tegra210_cpufreq_type(device_t dev, int *type)
  343 {
  344 
  345         if (type == NULL)
  346                 return (EINVAL);
  347         *type = CPUFREQ_TYPE_ABSOLUTE;
  348 
  349         return (0);
  350 }
  351 
  352 static int
  353 get_fdt_resources(struct tegra210_cpufreq_softc *sc, phandle_t node)
  354 {
  355         int rv;
  356         device_t parent_dev;
  357 
  358         parent_dev =  device_get_parent(sc->dev);
  359 
  360         rv = clk_get_by_ofw_name(parent_dev, 0, "cpu_g", &sc->clk_cpu_g);
  361         if (rv != 0) {
  362                 device_printf(sc->dev, "Cannot get 'cpu_g' clock: %d\n", rv);
  363                 return (ENXIO);
  364         }
  365 
  366         rv = clk_get_by_ofw_name(parent_dev, 0, "pll_x", &sc->clk_pll_x);
  367         if (rv != 0) {
  368                 device_printf(sc->dev, "Cannot get 'pll_x' clock\n");
  369                 return (ENXIO);
  370         }
  371         rv = clk_get_by_ofw_name(parent_dev, 0, "pll_p", &sc->clk_pll_p);
  372         if (rv != 0) {
  373                 device_printf(parent_dev, "Cannot get 'pll_p' clock\n");
  374                 return (ENXIO);
  375         }
  376         rv = clk_get_by_ofw_name(parent_dev, 0, "dfll", &sc->clk_dfll);
  377 
  378         /* XXX DPLL is not implemented yet */
  379 #if 0
  380         if (rv != 0) {
  381                 device_printf(sc->dev, "Cannot get 'dfll' clock\n");
  382                 return (ENXIO);
  383         }
  384 #endif
  385         return (0);
  386 }
  387 
  388 static void
  389 tegra210_cpufreq_identify(driver_t *driver, device_t parent)
  390 {
  391         phandle_t root;
  392 
  393         root = OF_finddevice("/");
  394         if (!ofw_bus_node_is_compatible(root, "nvidia,tegra210"))
  395                 return;
  396 
  397         if (device_get_unit(parent) != 0)
  398                 return;
  399         if (device_find_child(parent, "tegra210_cpufreq", -1) != NULL)
  400                 return;
  401         if (BUS_ADD_CHILD(parent, 0, "tegra210_cpufreq", -1) == NULL)
  402                 device_printf(parent, "add child failed\n");
  403 }
  404 
  405 static int
  406 tegra210_cpufreq_probe(device_t dev)
  407 {
  408 
  409         device_set_desc(dev, "CPU Frequency Control");
  410 
  411         return (0);
  412 }
  413 
  414 static int
  415 tegra210_cpufreq_attach(device_t dev)
  416 {
  417         struct tegra210_cpufreq_softc *sc;
  418         uint64_t freq;
  419         int rv;
  420 
  421         sc = device_get_softc(dev);
  422         sc->dev = dev;
  423         sc->node = ofw_bus_get_node(device_get_parent(dev));
  424 
  425         sc->process_id = tegra_sku_info.cpu_process_id;
  426         sc->speedo_id = tegra_sku_info.cpu_speedo_id;
  427         sc->speedo_value = tegra_sku_info.cpu_speedo_value;
  428 
  429         sc->cpu_def = &tegra210_cpu_volt_def;
  430 
  431         rv = get_fdt_resources(sc, sc->node);
  432         if (rv !=  0) {
  433                 return (rv);
  434         }
  435 
  436         build_speed_points(sc);
  437 
  438         rv = clk_get_freq(sc->clk_cpu_g, &freq);
  439         if (rv != 0) {
  440                 device_printf(dev, "Can't get CPU clock frequency\n");
  441                 return (rv);
  442         }
  443         if (sc->speedo_id < nitems(cpu_max_freq))
  444                 sc->cpu_max_freq = cpu_max_freq[sc->speedo_id];
  445         else
  446                 sc->cpu_max_freq = cpu_max_freq[0];
  447         sc->act_speed_point = get_speed_point(sc, freq);
  448 
  449         /* Set safe startup CPU frequency. */
  450         rv = set_cpu_freq(sc, 1632000000);
  451         if (rv != 0) {
  452                 device_printf(dev, "Can't set initial CPU clock frequency\n");
  453                 return (rv);
  454         }
  455 
  456         /* This device is controlled by cpufreq(4). */
  457         cpufreq_register(dev);
  458 
  459         return (0);
  460 }
  461 
  462 static int
  463 tegra210_cpufreq_detach(device_t dev)
  464 {
  465         struct tegra210_cpufreq_softc *sc;
  466 
  467         sc = device_get_softc(dev);
  468         cpufreq_unregister(dev);
  469 
  470         if (sc->clk_cpu_g != NULL)
  471                 clk_release(sc->clk_cpu_g);
  472         if (sc->clk_pll_x != NULL)
  473                 clk_release(sc->clk_pll_x);
  474         if (sc->clk_pll_p != NULL)
  475                 clk_release(sc->clk_pll_p);
  476         if (sc->clk_dfll != NULL)
  477                 clk_release(sc->clk_dfll);
  478         return (0);
  479 }
  480 
  481 static device_method_t tegra210_cpufreq_methods[] = {
  482         /* Device interface */
  483         DEVMETHOD(device_identify,      tegra210_cpufreq_identify),
  484         DEVMETHOD(device_probe,         tegra210_cpufreq_probe),
  485         DEVMETHOD(device_attach,        tegra210_cpufreq_attach),
  486         DEVMETHOD(device_detach,        tegra210_cpufreq_detach),
  487 
  488         /* cpufreq interface */
  489         DEVMETHOD(cpufreq_drv_set,      tegra210_cpufreq_set),
  490         DEVMETHOD(cpufreq_drv_get,      tegra210_cpufreq_get),
  491         DEVMETHOD(cpufreq_drv_settings, tegra210_cpufreq_settings),
  492         DEVMETHOD(cpufreq_drv_type,     tegra210_cpufreq_type),
  493 
  494         DEVMETHOD_END
  495 };
  496 
  497 static DEFINE_CLASS_0(tegra210_cpufreq, tegra210_cpufreq_driver,
  498     tegra210_cpufreq_methods, sizeof(struct tegra210_cpufreq_softc));
  499 DRIVER_MODULE(tegra210_cpufreq, cpu, tegra210_cpufreq_driver, NULL, NULL);

Cache object: 779bf26e0a3765333d3f353f01d4d2b8


[ source navigation ] [ diff markup ] [ identifier search ] [ freetext search ] [ file search ] [ list types ] [ track identifier ]


This page is part of the FreeBSD/Linux Linux Kernel Cross-Reference, and was automatically generated using a modified version of the LXR engine.