/*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright 2020 Michal Meloun * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cpufreq_if.h" /* CPU voltage table entry */ struct speedo_entry { uint64_t freq; /* Frequency point */ int c0; /* Coeeficient values for */ int c1; /* quadratic equation: */ int c2; /* c2 * speedo^2 + c1 * speedo + c0 */ }; struct cpu_volt_def { int min_uvolt; /* Min allowed CPU voltage */ int max_uvolt; /* Max allowed CPU voltage */ int step_uvolt; /* Step of CPU voltage */ int speedo_scale; /* Scaling factor for cvt */ int speedo_nitems; /* Size of speedo table */ struct speedo_entry *speedo_tbl; /* CPU voltage table */ }; struct cpu_speed_point { uint64_t freq; /* Frequecy */ int uvolt; /* Requested voltage */ }; static struct speedo_entry tegra210_speedo_tbl[] = { {204000000UL, 1007452, -23865, 370}, {306000000UL, 1052709, -24875, 370}, {408000000UL, 1099069, -25895, 370}, {510000000UL, 1146534, -26905, 370}, {612000000UL, 1195102, -27915, 370}, {714000000UL, 1244773, -28925, 370}, {816000000UL, 1295549, -29935, 370}, {918000000UL, 1347428, -30955, 370}, {1020000000UL, 1400411, -31965, 370}, {1122000000UL, 1454497, -32975, 370}, {1224000000UL, 1509687, -33985, 370}, {1326000000UL, 1565981, -35005, 370}, {1428000000UL, 1623379, -36015, 370}, {1530000000UL, 1681880, -37025, 370}, {1632000000UL, 1741485, -38035, 370}, {1734000000UL, 1802194, -39055, 370}, {1836000000UL, 1864006, -40065, 370}, {1912500000UL, 1910780, -40815, 370}, {2014500000UL, 1227000, 0, 0}, {2218500000UL, 1227000, 0, 0}, }; static struct cpu_volt_def tegra210_cpu_volt_def = { .min_uvolt = 900000, /* 0.9 V */ .max_uvolt = 1227000, /* 1.227 */ .step_uvolt = 10000, /* 10 mV */ .speedo_scale = 100, .speedo_nitems = nitems(tegra210_speedo_tbl), .speedo_tbl = tegra210_speedo_tbl, }; static uint64_t cpu_max_freq[] = { 1912500000UL, 1912500000UL, 2218500000UL, 1785000000UL, 1632000000UL, 1912500000UL, 2014500000UL, 1734000000UL, 1683000000UL, 1555500000UL, 1504500000UL, }; static uint64_t cpu_freq_tbl[] = { 204000000UL, 306000000UL, 408000000UL, 510000000UL, 612000000UL, 714000000UL, 816000000UL, 918000000UL, 1020000000UL, 1122000000UL, 1224000000UL, 1326000000UL, 1428000000UL, 1530000000UL, 1632000000UL, 1734000000UL, 1836000000UL, 1912500000UL, 2014500000UL, 2218500000UL, }; struct tegra210_cpufreq_softc { device_t dev; phandle_t node; clk_t clk_cpu_g; clk_t clk_pll_x; clk_t clk_pll_p; clk_t clk_dfll; int process_id; int speedo_id; int speedo_value; uint64_t cpu_max_freq; struct cpu_volt_def *cpu_def; struct cpu_speed_point *speed_points; int nspeed_points; struct cpu_speed_point *act_speed_point; int latency; }; static int cpufreq_lowest_freq = 1; TUNABLE_INT("hw.tegra210.cpufreq.lowest_freq", &cpufreq_lowest_freq); #define DIV_ROUND_CLOSEST(val, div) (((val) + ((div) / 2)) / (div)) #define ROUND_UP(val, div) roundup(val, div) #define ROUND_DOWN(val, div) rounddown(val, div) /* * Compute requesetd voltage for given frequency and SoC process variations, * - compute base voltage from speedo value using speedo table * - round up voltage to next regulator step * - clamp it to regulator limits */ static int freq_to_voltage(struct tegra210_cpufreq_softc *sc, uint64_t freq) { int uv, scale, min_uvolt, max_uvolt, step_uvolt; struct speedo_entry *ent; int i; /* Get speedo entry with higher frequency */ ent = NULL; for (i = 0; i < sc->cpu_def->speedo_nitems; i++) { if (sc->cpu_def->speedo_tbl[i].freq >= freq) { ent = &sc->cpu_def->speedo_tbl[i]; break; } } if (ent == NULL) ent = &sc->cpu_def->speedo_tbl[sc->cpu_def->speedo_nitems - 1]; scale = sc->cpu_def->speedo_scale; /* uV = (c2 * speedo / scale + c1) * speedo / scale + c0) */ uv = DIV_ROUND_CLOSEST(ent->c2 * sc->speedo_value, scale); uv = DIV_ROUND_CLOSEST((uv + ent->c1) * sc->speedo_value, scale) + ent->c0; step_uvolt = sc->cpu_def->step_uvolt; /* Round up it to next regulator step */ uv = ROUND_UP(uv, step_uvolt); /* Clamp result */ min_uvolt = ROUND_UP(sc->cpu_def->min_uvolt, step_uvolt); max_uvolt = ROUND_DOWN(sc->cpu_def->max_uvolt, step_uvolt); if (uv < min_uvolt) uv = min_uvolt; if (uv > max_uvolt) uv = max_uvolt; return (uv); } static void build_speed_points(struct tegra210_cpufreq_softc *sc) { int i; sc->nspeed_points = nitems(cpu_freq_tbl); sc->speed_points = malloc(sizeof(struct cpu_speed_point) * sc->nspeed_points, M_DEVBUF, M_NOWAIT); for (i = 0; i < sc->nspeed_points; i++) { sc->speed_points[i].freq = cpu_freq_tbl[i]; sc->speed_points[i].uvolt = freq_to_voltage(sc, cpu_freq_tbl[i]); } } static struct cpu_speed_point * get_speed_point(struct tegra210_cpufreq_softc *sc, uint64_t freq) { int i; if (sc->speed_points[0].freq >= freq) return (sc->speed_points + 0); for (i = 0; i < sc->nspeed_points - 1; i++) { if (sc->speed_points[i + 1].freq > freq) return (sc->speed_points + i); } return (sc->speed_points + sc->nspeed_points - 1); } static int tegra210_cpufreq_settings(device_t dev, struct cf_setting *sets, int *count) { struct tegra210_cpufreq_softc *sc; int i, j; if (sets == NULL || count == NULL) return (EINVAL); sc = device_get_softc(dev); memset(sets, CPUFREQ_VAL_UNKNOWN, sizeof(*sets) * (*count)); for (i = 0, j = sc->nspeed_points - 1; j >= 0; j--) { if (sc->cpu_max_freq < sc->speed_points[j].freq) continue; sets[i].freq = sc->speed_points[j].freq / 1000000; sets[i].volts = sc->speed_points[j].uvolt / 1000; sets[i].lat = sc->latency; sets[i].dev = dev; i++; } *count = i; return (0); } static int set_cpu_freq(struct tegra210_cpufreq_softc *sc, uint64_t freq) { struct cpu_speed_point *point; int rv; point = get_speed_point(sc, freq); /* Set PLLX frequency */ rv = clk_set_freq(sc->clk_pll_x, point->freq, CLK_SET_ROUND_DOWN); if (rv != 0) { device_printf(sc->dev, "Can't set CPU clock frequency\n"); return (rv); } sc->act_speed_point = point; return (0); } static int tegra210_cpufreq_set(device_t dev, const struct cf_setting *cf) { struct tegra210_cpufreq_softc *sc; uint64_t freq; int rv; if (cf == NULL || cf->freq < 0) return (EINVAL); sc = device_get_softc(dev); freq = cf->freq; if (freq < cpufreq_lowest_freq) freq = cpufreq_lowest_freq; freq *= 1000000; if (freq >= sc->cpu_max_freq) freq = sc->cpu_max_freq; rv = set_cpu_freq(sc, freq); return (rv); } static int tegra210_cpufreq_get(device_t dev, struct cf_setting *cf) { struct tegra210_cpufreq_softc *sc; if (cf == NULL) return (EINVAL); sc = device_get_softc(dev); memset(cf, CPUFREQ_VAL_UNKNOWN, sizeof(*cf)); cf->dev = NULL; cf->freq = sc->act_speed_point->freq / 1000000; cf->volts = sc->act_speed_point->uvolt / 1000; /* Transition latency in us. */ cf->lat = sc->latency; /* Driver providing this setting. */ cf->dev = dev; return (0); } static int tegra210_cpufreq_type(device_t dev, int *type) { if (type == NULL) return (EINVAL); *type = CPUFREQ_TYPE_ABSOLUTE; return (0); } static int get_fdt_resources(struct tegra210_cpufreq_softc *sc, phandle_t node) { int rv; device_t parent_dev; parent_dev = device_get_parent(sc->dev); rv = clk_get_by_ofw_name(parent_dev, 0, "cpu_g", &sc->clk_cpu_g); if (rv != 0) { device_printf(sc->dev, "Cannot get 'cpu_g' clock: %d\n", rv); return (ENXIO); } rv = clk_get_by_ofw_name(parent_dev, 0, "pll_x", &sc->clk_pll_x); if (rv != 0) { device_printf(sc->dev, "Cannot get 'pll_x' clock\n"); return (ENXIO); } rv = clk_get_by_ofw_name(parent_dev, 0, "pll_p", &sc->clk_pll_p); if (rv != 0) { device_printf(parent_dev, "Cannot get 'pll_p' clock\n"); return (ENXIO); } rv = clk_get_by_ofw_name(parent_dev, 0, "dfll", &sc->clk_dfll); /* XXX DPLL is not implemented yet */ #if 0 if (rv != 0) { device_printf(sc->dev, "Cannot get 'dfll' clock\n"); return (ENXIO); } #endif return (0); } static void tegra210_cpufreq_identify(driver_t *driver, device_t parent) { phandle_t root; root = OF_finddevice("/"); if (!ofw_bus_node_is_compatible(root, "nvidia,tegra210")) return; if (device_get_unit(parent) != 0) return; if (device_find_child(parent, "tegra210_cpufreq", -1) != NULL) return; if (BUS_ADD_CHILD(parent, 0, "tegra210_cpufreq", -1) == NULL) device_printf(parent, "add child failed\n"); } static int tegra210_cpufreq_probe(device_t dev) { device_set_desc(dev, "CPU Frequency Control"); return (0); } static int tegra210_cpufreq_attach(device_t dev) { struct tegra210_cpufreq_softc *sc; uint64_t freq; int rv; sc = device_get_softc(dev); sc->dev = dev; sc->node = ofw_bus_get_node(device_get_parent(dev)); sc->process_id = tegra_sku_info.cpu_process_id; sc->speedo_id = tegra_sku_info.cpu_speedo_id; sc->speedo_value = tegra_sku_info.cpu_speedo_value; sc->cpu_def = &tegra210_cpu_volt_def; rv = get_fdt_resources(sc, sc->node); if (rv != 0) { return (rv); } build_speed_points(sc); rv = clk_get_freq(sc->clk_cpu_g, &freq); if (rv != 0) { device_printf(dev, "Can't get CPU clock frequency\n"); return (rv); } if (sc->speedo_id < nitems(cpu_max_freq)) sc->cpu_max_freq = cpu_max_freq[sc->speedo_id]; else sc->cpu_max_freq = cpu_max_freq[0]; sc->act_speed_point = get_speed_point(sc, freq); /* Set safe startup CPU frequency. */ rv = set_cpu_freq(sc, 1632000000); if (rv != 0) { device_printf(dev, "Can't set initial CPU clock frequency\n"); return (rv); } /* This device is controlled by cpufreq(4). */ cpufreq_register(dev); return (0); } static int tegra210_cpufreq_detach(device_t dev) { struct tegra210_cpufreq_softc *sc; sc = device_get_softc(dev); cpufreq_unregister(dev); if (sc->clk_cpu_g != NULL) clk_release(sc->clk_cpu_g); if (sc->clk_pll_x != NULL) clk_release(sc->clk_pll_x); if (sc->clk_pll_p != NULL) clk_release(sc->clk_pll_p); if (sc->clk_dfll != NULL) clk_release(sc->clk_dfll); return (0); } static device_method_t tegra210_cpufreq_methods[] = { /* Device interface */ DEVMETHOD(device_identify, tegra210_cpufreq_identify), DEVMETHOD(device_probe, tegra210_cpufreq_probe), DEVMETHOD(device_attach, tegra210_cpufreq_attach), DEVMETHOD(device_detach, tegra210_cpufreq_detach), /* cpufreq interface */ DEVMETHOD(cpufreq_drv_set, tegra210_cpufreq_set), DEVMETHOD(cpufreq_drv_get, tegra210_cpufreq_get), DEVMETHOD(cpufreq_drv_settings, tegra210_cpufreq_settings), DEVMETHOD(cpufreq_drv_type, tegra210_cpufreq_type), DEVMETHOD_END }; static DEFINE_CLASS_0(tegra210_cpufreq, tegra210_cpufreq_driver, tegra210_cpufreq_methods, sizeof(struct tegra210_cpufreq_softc)); DRIVER_MODULE(tegra210_cpufreq, cpu, tegra210_cpufreq_driver, NULL, NULL);