diff options
Diffstat (limited to 'sys/arm/nvidia/tegra124/tegra124_cpufreq.c')
-rw-r--r-- | sys/arm/nvidia/tegra124/tegra124_cpufreq.c | 583 |
1 files changed, 583 insertions, 0 deletions
diff --git a/sys/arm/nvidia/tegra124/tegra124_cpufreq.c b/sys/arm/nvidia/tegra124/tegra124_cpufreq.c new file mode 100644 index 0000000..1227c81 --- /dev/null +++ b/sys/arm/nvidia/tegra124/tegra124_cpufreq.c @@ -0,0 +1,583 @@ +/*- + * Copyright (c) 2016 Michal Meloun <mmel@FreeBSD.org> + * All rights reserved. + * + * 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 <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/bus.h> +#include <sys/cpu.h> +#include <sys/kernel.h> +#include <sys/lock.h> +#include <sys/malloc.h> +#include <sys/module.h> + +#include <machine/bus.h> +#include <machine/cpu.h> + +#include <dev/extres/clk/clk.h> +#include <dev/extres/regulator/regulator.h> +#include <dev/ofw/ofw_bus_subr.h> + +#include <arm/nvidia/tegra_efuse.h> + +#include "cpufreq_if.h" + +#define XXX + +/* 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 tegra124_speedo_dpll_tbl[] = +{ + { 204000000ULL, 1112619, -29295, 402}, + { 306000000ULL, 1150460, -30585, 402}, + { 408000000ULL, 1190122, -31865, 402}, + { 510000000ULL, 1231606, -33155, 402}, + { 612000000ULL, 1274912, -34435, 402}, + { 714000000ULL, 1320040, -35725, 402}, + { 816000000ULL, 1366990, -37005, 402}, + { 918000000ULL, 1415762, -38295, 402}, + {1020000000ULL, 1466355, -39575, 402}, + {1122000000ULL, 1518771, -40865, 402}, + {1224000000ULL, 1573009, -42145, 402}, + {1326000000ULL, 1629068, -43435, 402}, + {1428000000ULL, 1686950, -44715, 402}, + {1530000000ULL, 1746653, -46005, 402}, + {1632000000ULL, 1808179, -47285, 402}, + {1734000000ULL, 1871526, -48575, 402}, + {1836000000ULL, 1936696, -49855, 402}, + {1938000000ULL, 2003687, -51145, 402}, + {2014500000ULL, 2054787, -52095, 402}, + {2116500000ULL, 2124957, -53385, 402}, + {2218500000ULL, 2196950, -54665, 402}, + {2320500000ULL, 2270765, -55955, 402}, + {2320500000ULL, 2270765, -55955, 402}, + {2422500000ULL, 2346401, -57235, 402}, + {2524500000ULL, 2437299, -58535, 402}, +}; + +static struct cpu_volt_def tegra124_cpu_volt_dpll_def = +{ + .min_uvolt = 900000, /* 0.9 V */ + .max_uvolt = 1260000, /* 1.26 */ + .step_uvolt = 10000, /* 10 mV */ + .speedo_scale = 100, + .speedo_nitems = nitems(tegra124_speedo_dpll_tbl), + .speedo_tbl = tegra124_speedo_dpll_tbl, +}; + +static struct speedo_entry tegra124_speedo_pllx_tbl[] = +{ + { 204000000ULL, 800000, 0, 0}, + { 306000000ULL, 800000, 0, 0}, + { 408000000ULL, 800000, 0, 0}, + { 510000000ULL, 800000, 0, 0}, + { 612000000ULL, 800000, 0, 0}, + { 714000000ULL, 800000, 0, 0}, + { 816000000ULL, 820000, 0, 0}, + { 918000000ULL, 840000, 0, 0}, + {1020000000ULL, 880000, 0, 0}, + {1122000000ULL, 900000, 0, 0}, + {1224000000ULL, 930000, 0, 0}, + {1326000000ULL, 960000, 0, 0}, + {1428000000ULL, 990000, 0, 0}, + {1530000000ULL, 1020000, 0, 0}, + {1632000000ULL, 1070000, 0, 0}, + {1734000000ULL, 1100000, 0, 0}, + {1836000000ULL, 1140000, 0, 0}, + {1938000000ULL, 1180000, 0, 0}, + {2014500000ULL, 1220000, 0, 0}, + {2116500000ULL, 1260000, 0, 0}, + {2218500000ULL, 1310000, 0, 0}, + {2320500000ULL, 1360000, 0, 0}, + {2397000000ULL, 1400000, 0, 0}, + {2499000000ULL, 1400000, 0, 0}, +}; + + +static struct cpu_volt_def tegra124_cpu_volt_pllx_def = +{ + .min_uvolt = 900000, /* 0.9 V */ + .max_uvolt = 1260000, /* 1.26 */ + .step_uvolt = 10000, /* 10 mV */ + .speedo_scale = 100, + .speedo_nitems = nitems(tegra124_speedo_pllx_tbl), + .speedo_tbl = tegra124_speedo_pllx_tbl, +}; + +static uint64_t cpu_freq_tbl[] = { + 204000000ULL, + 306000000ULL, + 408000000ULL, + 510000000ULL, + 612000000ULL, + 714000000ULL, + 816000000ULL, + 918000000ULL, + 1020000000ULL, + 1122000000ULL, + 1224000000ULL, + 1326000000ULL, + 1428000000ULL, + 1530000000ULL, + 1632000000ULL, + 1734000000ULL, + 1836000000ULL, + 1938000000ULL, + 2014000000ULL, + 2116000000ULL, + 2218000000ULL, + 2320000000ULL, + 2320000000ULL, + 2422000000ULL, + 2524000000ULL, +}; + +static uint64_t cpu_max_freq[] = { + 2014500000ULL, + 2320500000ULL, + 2116500000ULL, + 2524500000ULL, +}; + +struct tegra124_cpufreq_softc { + device_t dev; + phandle_t node; + + regulator_t supply_vdd_cpu; + clk_t clk_cpu_g; + clk_t clk_cpu_lp; + 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.tegra124.cpufreq.lowest_freq", &cpufreq_lowest_freq); + +#define DIV_ROUND_CLOSEST(val, div) (((val) + ((div) / 2)) / (div)) + +#define ROUND_UP(val, div) ((((val) + ((div) - 1)) / (div)) * (div)) +#define ROUND_DOWN(val, div) (((val) / (div)) * (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 tegra124_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 tegra124_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 tegra124_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 +tegra124_cpufreq_settings(device_t dev, struct cf_setting *sets, int *count) +{ + struct tegra124_cpufreq_softc *sc; + int i, j, max_cnt; + + if (sets == NULL || count == NULL) + return (EINVAL); + + sc = device_get_softc(dev); + memset(sets, CPUFREQ_VAL_UNKNOWN, sizeof(*sets) * (*count)); + + max_cnt = min(sc->nspeed_points, *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 tegra124_cpufreq_softc *sc, uint64_t freq) +{ + struct cpu_speed_point *point; + int rv; + + point = get_speed_point(sc, freq); + + if (sc->act_speed_point->uvolt < point->uvolt) { + /* set cpu voltage */ + rv = regulator_set_voltage(sc->supply_vdd_cpu, + point->uvolt, point->uvolt); + DELAY(10000); + if (rv != 0) + return (rv); + } + rv = clk_set_freq(sc->clk_cpu_g, point->freq, CLK_SET_ROUND_DOWN); + if (rv != 0) { + device_printf(sc->dev, "Can't set CPU clock frequency\n"); + return (rv); + } + + if (sc->act_speed_point->uvolt > point->uvolt) { + /* set cpu voltage */ + rv = regulator_set_voltage(sc->supply_vdd_cpu, + point->uvolt, point->uvolt); + if (rv != 0) + return (rv); + } + + sc->act_speed_point = point; + + return (0); +} + +static int +tegra124_cpufreq_set(device_t dev, const struct cf_setting *cf) +{ + struct tegra124_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 +tegra124_cpufreq_get(device_t dev, struct cf_setting *cf) +{ + struct tegra124_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 +tegra124_cpufreq_type(device_t dev, int *type) +{ + + if (type == NULL) + return (EINVAL); + *type = CPUFREQ_TYPE_ABSOLUTE; + + return (0); +} + +static int +get_fdt_resources(struct tegra124_cpufreq_softc *sc, phandle_t node) +{ + int rv; + device_t parent_dev; + + parent_dev = device_get_parent(sc->dev); + rv = regulator_get_by_ofw_property(parent_dev, "vdd-cpu-supply", + &sc->supply_vdd_cpu); + if (rv != 0) { + device_printf(sc->dev, "Cannot get 'vdd-cpu' regulator\n"); + return (rv); + } + + rv = clk_get_by_ofw_name(parent_dev, "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, "cpu_lp", &sc->clk_cpu_lp); + if (rv != 0) { + device_printf(sc->dev, "Cannot get 'cpu_lp' clock\n"); + return (ENXIO); + } + + rv = clk_get_by_ofw_name(parent_dev, "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, "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, "dfll", &sc->clk_dfll); + if (rv != 0) { + /* XXX DPLL is not implemented yet */ +/* + device_printf(sc->dev, "Cannot get 'dfll' clock\n"); + return (ENXIO); +*/ + } + return (0); +} + +static void +tegra124_cpufreq_identify(driver_t *driver, device_t parent) +{ + + if (device_find_child(parent, "tegra124_cpufreq", -1) != NULL) + return; + if (BUS_ADD_CHILD(parent, 0, "tegra124_cpufreq", -1) == NULL) + device_printf(parent, "add child failed\n"); +} + +static int +tegra124_cpufreq_probe(device_t dev) +{ + + if (device_get_unit(dev) != 0) + return (ENXIO); + device_set_desc(dev, "CPU Frequency Control"); + + return (0); +} + +static int +tegra124_cpufreq_attach(device_t dev) +{ + struct tegra124_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; + + /* Tegra 124 */ + /* XXX DPLL is not implemented yet */ + if (1) + sc->cpu_def = &tegra124_cpu_volt_pllx_def; + else + sc->cpu_def = &tegra124_cpu_volt_dpll_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 +tegra124_cpufreq_detach(device_t dev) +{ + struct tegra124_cpufreq_softc *sc; + + sc = device_get_softc(dev); + cpufreq_unregister(dev); + + if (sc->supply_vdd_cpu != NULL) + regulator_release(sc->supply_vdd_cpu); + + if (sc->clk_cpu_g != NULL) + clk_release(sc->clk_cpu_g); + if (sc->clk_cpu_lp != NULL) + clk_release(sc->clk_cpu_lp); + 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 tegra124_cpufreq_methods[] = { + /* Device interface */ + DEVMETHOD(device_identify, tegra124_cpufreq_identify), + DEVMETHOD(device_probe, tegra124_cpufreq_probe), + DEVMETHOD(device_attach, tegra124_cpufreq_attach), + DEVMETHOD(device_detach, tegra124_cpufreq_detach), + + /* cpufreq interface */ + DEVMETHOD(cpufreq_drv_set, tegra124_cpufreq_set), + DEVMETHOD(cpufreq_drv_get, tegra124_cpufreq_get), + DEVMETHOD(cpufreq_drv_settings, tegra124_cpufreq_settings), + DEVMETHOD(cpufreq_drv_type, tegra124_cpufreq_type), + + DEVMETHOD_END +}; + +static devclass_t tegra124_cpufreq_devclass; +static driver_t tegra124_cpufreq_driver = { + "tegra124_cpufreq", + tegra124_cpufreq_methods, + sizeof(struct tegra124_cpufreq_softc), +}; + +DRIVER_MODULE(tegra124_cpufreq, cpu, tegra124_cpufreq_driver, + tegra124_cpufreq_devclass, 0, 0); |