/*- * Copyright (c) 2019 - 2020 Soren Schmidt * All rights reseresed. * * 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, * without modification, immediately at the beginning of the file. * 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 __FBSDID("$Id$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cpufreq_if.h" #include "syscon_if.h" // Clock management "NB" North Bridge register set #define A37X0_NB_L0L1 0x18 #define A37X0_NB_L2L3 0x1C #define A37X0_NB_DYN_MOD 0x24 #define A37X0_NB_CLK_SEL_EN (1<<26) #define A37X0_NB_TBG_EN (1<<28) #define A37X0_NB_DIV_EN (1<<29) #define A37X0_NB_VDD_EN (1<<30) #define A37X0_NB_DFS_EN (1<<31) #define A37X0_NB_CPU_LOAD 0x30 #define A37X0_NB_CPU_LOAD_MASK 0x3 #define A37X0_NB_CPU_LOAD_CNT 4 // Voltage "AVS" register set #define A37X0_AVS_CTL0 0x0 #define A37X0_AVS_ENABLE (1<<30) #define A37X0_AVS_HIGH_VDD 16 #define A37X0_AVS_LOW_VDD 22 #define A37X0_AVS_VDD_MASK 0x3F #define A37X0_AVS_CTL2 0x8 #define A37X0_AVS_LOW_VDD_EN (1<<6) #define A37X0_AVS_VSET(x) (0x1C + 4 * (x)) struct a372x_cpufreq_softc { device_t dev; phandle_t node_syscon; struct syscon *sc_syscon; //phandle_t node_avs; //struct syscon *sc_avs; }; struct a372x_cpufreq_freq { int load; int freq; int mvolt; uint16_t config; }; static struct a372x_cpufreq_freq freq_table[] = { { 0, 1000, 1400, 0x2800 }, { 1, 500, 1200, 0x4840 }, { 2, 250, 1000, 0x8880 }, { 3, 200, 900, 0xa8c0 } }; static void a372x_cpufreq_identify(driver_t *driver, device_t parent) { phandle_t node; // SOS XXX do some real identifying job here if (!(node = OF_finddevice("/soc/internal-regs/syscon"))) return; if (device_find_child(parent, "a372x_cpufreq", -1)) return; if (BUS_ADD_CHILD(parent, 0, "a372x_cpufreq", -1) == NULL) device_printf(parent, "add child failed\n"); } static int a372x_cpufreq_probe(device_t dev) { struct a372x_cpufreq_softc *sc = device_get_softc(dev); if (device_get_unit(dev)) { if (bootverbose) device_printf(dev, "a372x_cpufreq_probe device already exists\n"); return ENXIO; } sc->dev = dev; sc->node_syscon = OF_finddevice("/soc/internal-regs/syscon"); sc->sc_syscon = syscon_find_by_ofw_node(sc->node_syscon); //sc->node_avs = OF_finddevice("/soc/internal-regs/avs"); //sc->sc_avs = syscon_find_by_ofw_node(sc->node_avs); device_set_desc(dev, "CPU Frequency Control"); return BUS_PROBE_DEFAULT; } static int a372x_cpufreq_attach(device_t dev) { struct a372x_cpufreq_softc *sc = device_get_softc(dev); // freq multipliers: 1, 2, 4, 5 // CPUmax = 1000Mhz: 1000Mhz, 500Mhz, 250Mhz, 200Mhz SYSCON_WRITE_4(sc->sc_syscon, A37X0_NB_DYN_MOD, 0x0); SYSCON_WRITE_4(sc->sc_syscon, A37X0_NB_L0L1, 0x28004840); SYSCON_WRITE_4(sc->sc_syscon, A37X0_NB_L2L3, 0x8880a8c0); SYSCON_WRITE_4(sc->sc_syscon, A37X0_NB_CPU_LOAD, 0); SYSCON_WRITE_4(sc->sc_syscon, A37X0_NB_DYN_MOD, A37X0_NB_DIV_EN | A37X0_NB_VDD_EN | A37X0_NB_DFS_EN); // this device is controlled by cpufreq(4) cpufreq_register(dev); return 0; } static int a372x_cpufreq_detach(device_t dev) { return cpufreq_unregister(dev); } static int a372x_cpufreq_set(device_t dev, const struct cf_setting *cf) { struct a372x_cpufreq_softc *sc = device_get_softc(dev); int load; if (!cf || cf->freq < 0) return EINVAL; // find load value for given frequency for (load = 0; load < A37X0_NB_CPU_LOAD_CNT; load++) { if (cf->freq >= freq_table[load].freq) break; } // write load value to hardware SYSCON_WRITE_4(sc->sc_syscon, A37X0_NB_DYN_MOD, 0x0); SYSCON_WRITE_4(sc->sc_syscon, A37X0_NB_CPU_LOAD, load); SYSCON_WRITE_4(sc->sc_syscon, A37X0_NB_DYN_MOD, A37X0_NB_DIV_EN | A37X0_NB_VDD_EN | A37X0_NB_DFS_EN); return 0; } static int a372x_cpufreq_get(device_t dev, struct cf_setting *cf) { struct a372x_cpufreq_softc *sc = device_get_softc(dev); int load; memset(cf, CPUFREQ_VAL_UNKNOWN, sizeof(*cf)); // convert load value to frequency & volt load = SYSCON_READ_4(sc->sc_syscon, A37X0_NB_CPU_LOAD); cf->freq = freq_table[load & A37X0_NB_CPU_LOAD_MASK].freq; //cf->volts = freq_table[load & A37X0_NB_CPU_LOAD_MASK].mvolt; cf->lat = 1000; cf->dev = dev; return 0; } static int a372x_cpufreq_settings(device_t dev, struct cf_setting *sets, int *count) { int i; if (sets == NULL || count == NULL) return (EINVAL); // fill data with unknown value memset(sets, CPUFREQ_VAL_UNKNOWN, sizeof(*sets) * (*count)); // fill in our data for (i = 0; i < A37X0_NB_CPU_LOAD_CNT; i++) { sets[i].freq = freq_table[i].freq; //sets[i].volts = freq_table[i].mvolt; sets[i].lat = 1000; sets[i].dev = dev; } *count = A37X0_NB_CPU_LOAD_CNT; return 0; } static int a372x_cpufreq_type(device_t dev, int *type) { if (!type) return (EINVAL); *type = CPUFREQ_TYPE_ABSOLUTE; return 0; } static device_method_t a372x_cpufreq_methods[] = { // Device interface DEVMETHOD(device_identify, a372x_cpufreq_identify), DEVMETHOD(device_probe, a372x_cpufreq_probe), DEVMETHOD(device_attach, a372x_cpufreq_attach), DEVMETHOD(device_detach, a372x_cpufreq_detach), // cpufreq interface DEVMETHOD(cpufreq_drv_set, a372x_cpufreq_set), DEVMETHOD(cpufreq_drv_get, a372x_cpufreq_get), DEVMETHOD(cpufreq_drv_settings, a372x_cpufreq_settings), DEVMETHOD(cpufreq_drv_type, a372x_cpufreq_type), DEVMETHOD_END }; static devclass_t a372x_cpufreq_devclass; static driver_t a372x_cpufreq_driver = { "a372x_cpufreq", a372x_cpufreq_methods, sizeof(struct a372x_cpufreq_softc), }; DRIVER_MODULE(a372x_cpufreq, cpu, a372x_cpufreq_driver, a372x_cpufreq_devclass, 0, 0);