Module Name: src Committed By: jmcneill Date: Thu Mar 5 23:43:53 UTC 2015
Modified Files: src/sys/arch/arm/amlogic: amlogic_var.h files.amlogic Added Files: src/sys/arch/arm/amlogic: amlogic_cpufreq.c Log Message: CPU frequency scaling support, enough to bump my ODROID-C1 up to the advertised 1.5GHz. To generate a diff of this commit: cvs rdiff -u -r0 -r1.1 src/sys/arch/arm/amlogic/amlogic_cpufreq.c cvs rdiff -u -r1.3 -r1.4 src/sys/arch/arm/amlogic/amlogic_var.h cvs rdiff -u -r1.4 -r1.5 src/sys/arch/arm/amlogic/files.amlogic Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.
Modified files: Index: src/sys/arch/arm/amlogic/amlogic_var.h diff -u src/sys/arch/arm/amlogic/amlogic_var.h:1.3 src/sys/arch/arm/amlogic/amlogic_var.h:1.4 --- src/sys/arch/arm/amlogic/amlogic_var.h:1.3 Wed Mar 4 12:36:12 2015 +++ src/sys/arch/arm/amlogic/amlogic_var.h Thu Mar 5 23:43:53 2015 @@ -1,4 +1,4 @@ -/* $NetBSD: amlogic_var.h,v 1.3 2015/03/04 12:36:12 jmcneill Exp $ */ +/* $NetBSD: amlogic_var.h,v 1.4 2015/03/05 23:43:53 jmcneill Exp $ */ /*- * Copyright (c) 2015 Jared D. McNeill <jmcne...@invisible.ca> @@ -56,6 +56,8 @@ extern struct arm32_bus_dma_tag amlogic_ void amlogic_bootstrap(void); +void amlogic_cpufreq_init(void); + void amlogic_usbphy_init(int); void amlogic_eth_init(void); Index: src/sys/arch/arm/amlogic/files.amlogic diff -u src/sys/arch/arm/amlogic/files.amlogic:1.4 src/sys/arch/arm/amlogic/files.amlogic:1.5 --- src/sys/arch/arm/amlogic/files.amlogic:1.4 Wed Mar 4 12:36:12 2015 +++ src/sys/arch/arm/amlogic/files.amlogic Thu Mar 5 23:43:53 2015 @@ -1,4 +1,4 @@ -# $NetBSD: files.amlogic,v 1.4 2015/03/04 12:36:12 jmcneill Exp $ +# $NetBSD: files.amlogic,v 1.5 2015/03/05 23:43:53 jmcneill Exp $ # # Configuration info for Amlogic ARM Peripherals # @@ -12,6 +12,7 @@ file arch/arm/arm32/arm32_reboot.c file arch/arm/arm32/irq_dispatch.S file arch/arm/amlogic/amlogic_board.c +file arch/arm/amlogic/amlogic_cpufreq.c file arch/arm/amlogic/amlogic_space.c file arch/arm/arm/bus_space_a4x.S Added files: Index: src/sys/arch/arm/amlogic/amlogic_cpufreq.c diff -u /dev/null src/sys/arch/arm/amlogic/amlogic_cpufreq.c:1.1 --- /dev/null Thu Mar 5 23:43:53 2015 +++ src/sys/arch/arm/amlogic/amlogic_cpufreq.c Thu Mar 5 23:43:53 2015 @@ -0,0 +1,265 @@ +/* $NetBSD: amlogic_cpufreq.c,v 1.1 2015/03/05 23:43:53 jmcneill Exp $ */ + +/*- + * Copyright (c) 2015 Jared D. McNeill <jmcne...@invisible.ca> + * 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 ``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 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 "locators.h" + +#include <sys/cdefs.h> +__KERNEL_RCSID(0, "$NetBSD: amlogic_cpufreq.c,v 1.1 2015/03/05 23:43:53 jmcneill Exp $"); + +#include <sys/param.h> +#include <sys/bus.h> +#include <sys/device.h> +#include <sys/intr.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/atomic.h> +#include <sys/kmem.h> +#include <sys/xcall.h> +#include <sys/sysctl.h> + +#include <arm/amlogic/amlogic_reg.h> +#include <arm/amlogic/amlogic_crureg.h> +#include <arm/amlogic/amlogic_var.h> + +#include <arm/cortex/a9tmr_var.h> + +static u_int cpufreq_busy; +static struct sysctllog *cpufreq_log; +static int cpufreq_node_target, cpufreq_node_current, cpufreq_node_available; +static u_int (*cpufreq_set_rate)(u_int); +static u_int (*cpufreq_get_rate)(void); +static size_t (*cpufreq_get_available)(u_int *, size_t); + +#define AMLOGIC_CPUFREQ_MAX 8 + +static void amlogic_cpufreq_cb(void *, void *); +static int amlogic_cpufreq_freq_helper(SYSCTLFN_PROTO); +static char amlogic_cpufreq_available[AMLOGIC_CPUFREQ_MAX * 5]; + +static u_int meson8b_cpu_set_rate(u_int); +static u_int meson8b_cpu_get_rate(void); +static size_t meson8b_cpu_get_available(u_int *, size_t); + +#define CBUS_READ(x) \ + bus_space_read_4(&amlogic_bs_tag, amlogic_core_bsh, \ + AMLOGIC_CBUS_OFFSET + (x)) +#define CBUS_WRITE(x, v) \ + bus_space_write_4(&amlogic_bs_tag, amlogic_core_bsh, \ + AMLOGIC_CBUS_OFFSET + (x), (v)) + +void +amlogic_cpufreq_init(void) +{ + const struct sysctlnode *node, *cpunode, *freqnode; + u_int availfreq[AMLOGIC_CPUFREQ_MAX]; + size_t nfreq; + int error; + + cpufreq_set_rate = &meson8b_cpu_set_rate; + cpufreq_get_rate = &meson8b_cpu_get_rate; + cpufreq_get_available = &meson8b_cpu_get_available; + + nfreq = cpufreq_get_available(availfreq, AMLOGIC_CPUFREQ_MAX); + if (nfreq == 0) + return; + + KASSERT(nfreq <= AMLOGIC_CPUFREQ_MAX); + + for (int i = 0; i < nfreq; i++) { + char buf[6]; + snprintf(buf, sizeof(buf), i ? " %u" : "%u", availfreq[i]); + strcat(amlogic_cpufreq_available, buf); + } + + error = sysctl_createv(&cpufreq_log, 0, NULL, &node, + CTLFLAG_PERMANENT, CTLTYPE_NODE, "machdep", NULL, + NULL, 0, NULL, 0, CTL_MACHDEP, CTL_EOL); + if (error) + goto sysctl_failed; + error = sysctl_createv(&cpufreq_log, 0, &node, &cpunode, + 0, CTLTYPE_NODE, "cpu", NULL, + NULL, 0, NULL, 0, CTL_CREATE, CTL_EOL); + if (error) + goto sysctl_failed; + error = sysctl_createv(&cpufreq_log, 0, &cpunode, &freqnode, + 0, CTLTYPE_NODE, "frequency", NULL, + NULL, 0, NULL, 0, CTL_CREATE, CTL_EOL); + if (error) + goto sysctl_failed; + + error = sysctl_createv(&cpufreq_log, 0, &freqnode, &node, + CTLFLAG_READWRITE, CTLTYPE_INT, "target", NULL, + amlogic_cpufreq_freq_helper, 0, NULL, 0, + CTL_CREATE, CTL_EOL); + if (error) + goto sysctl_failed; + cpufreq_node_target = node->sysctl_num; + + error = sysctl_createv(&cpufreq_log, 0, &freqnode, &node, + CTLFLAG_READWRITE, CTLTYPE_INT, "current", NULL, + amlogic_cpufreq_freq_helper, 0, NULL, 0, + CTL_CREATE, CTL_EOL); + if (error) + goto sysctl_failed; + cpufreq_node_current = node->sysctl_num; + + error = sysctl_createv(&cpufreq_log, 0, &freqnode, &node, + 0, CTLTYPE_STRING, "available", NULL, + NULL, 0, amlogic_cpufreq_available, 0, + CTL_CREATE, CTL_EOL); + if (error) + goto sysctl_failed; + cpufreq_node_available = node->sysctl_num; + + return; + +sysctl_failed: + aprint_error("cpufreq: couldn't create sysctl nodes (%d)\n", error); + sysctl_teardown(&cpufreq_log); +} + +static void +amlogic_cpufreq_cb(void *arg1, void *arg2) +{ + struct cpu_info *ci = curcpu(); + + ci->ci_data.cpu_cc_freq = cpufreq_get_rate() * 1000000; +} + +static int +amlogic_cpufreq_freq_helper(SYSCTLFN_ARGS) +{ + struct sysctlnode node; + int fq, oldfq = 0, error; + uint64_t xc; + + node = *rnode; + node.sysctl_data = &fq; + + fq = cpufreq_get_rate(); + if (rnode->sysctl_num == cpufreq_node_target) + oldfq = fq; + + error = sysctl_lookup(SYSCTLFN_CALL(&node)); + if (error || newp == NULL) + return error; + + if (fq == oldfq || rnode->sysctl_num != cpufreq_node_target) + return 0; + + if (atomic_cas_uint(&cpufreq_busy, 0, 1) != 0) + return EBUSY; + + error = cpufreq_set_rate(fq); + if (error == 0) { + xc = xc_broadcast(0, amlogic_cpufreq_cb, NULL, NULL); + xc_wait(xc); + + pmf_event_inject(NULL, PMFE_SPEED_CHANGED); + } + + atomic_dec_uint(&cpufreq_busy); + + return error; +} + +/* + * meson8b + */ +static const u_int meson8b_rates[] = { + 1512, 1416, 1320, 1200 +}; + +static size_t +meson8b_cpu_get_available(u_int *pavail, size_t maxavail) +{ + KASSERT(__arraycount(meson8b_rates) <= maxavail); + + memcpy(pavail, meson8b_rates, sizeof(meson8b_rates)); + + return __arraycount(meson8b_rates); +} + +static u_int +meson8b_cpu_get_rate(void) +{ + return amlogic_get_rate_a9() / 1000000; +} + +static u_int +meson8b_cpu_set_rate(u_int rate) +{ + const uint32_t xtal_rate = amlogic_get_rate_xtal(); + const u_int old_rate = meson8b_cpu_get_rate(); + u_int new_rate = 0; + + /* Pick the closest rate (nearest 100MHz increment) */ + for (int i = 0; i < __arraycount(meson8b_rates); i++) { + u_int arate = (meson8b_rates[i] + 50) / 100 * 100; + if (arate <= rate) { + new_rate = meson8b_rates[i] * 1000000; + break; + } + } + if (new_rate == 0) { + return EINVAL; + } + if (old_rate == new_rate) { + return 0; + } + + uint32_t cntl0 = CBUS_READ(HHI_SYS_CPU_CLK_CNTL0_REG); + uint32_t cntl = CBUS_READ(HHI_SYS_PLL_CNTL_REG); + + const u_int new_mul = new_rate / xtal_rate; + const u_int new_div = 1; + const u_int new_od = 0; + + /* + * XXX make some assumptions about the state of cpu clk cntl regs + */ + if ((cntl0 & HHI_SYS_CPU_CLK_CNTL0_CLKSEL) == 0) + return EIO; + if (__SHIFTOUT(cntl0, HHI_SYS_CPU_CLK_CNTL0_PLLSEL) != 1) + return EIO; + if (__SHIFTOUT(cntl0, HHI_SYS_CPU_CLK_CNTL0_SOUTSEL) != 0) + return EIO; + + cntl &= ~HHI_SYS_PLL_CNTL_MUL; + cntl |= __SHIFTIN(new_mul, HHI_SYS_PLL_CNTL_MUL); + cntl &= ~HHI_SYS_PLL_CNTL_DIV; + cntl |= __SHIFTIN(new_div, HHI_SYS_PLL_CNTL_DIV); + cntl &= ~HHI_SYS_PLL_CNTL_OD; + cntl |= __SHIFTIN(new_od, HHI_SYS_PLL_CNTL_OD); + + CBUS_WRITE(HHI_SYS_PLL_CNTL_REG, cntl); + + a9tmr_update_freq(amlogic_get_rate_a9periph()); + + return 0; +}