Date: Sun, 26 Feb 2017 16:00:20 +0000 (UTC) From: Emmanuel Vadot <manu@FreeBSD.org> To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org Subject: svn commit: r314303 - in head/sys/arm/allwinner: . clkng h3 Message-ID: <201702261600.v1QG0Krv080636@repo.freebsd.org>
next in thread | raw e-mail | index | archive | help
Author: manu Date: Sun Feb 26 16:00:20 2017 New Revision: 314303 URL: https://svnweb.freebsd.org/changeset/base/314303 Log: Add clkng driver for Allwinner SoC Since Linux 4.9-4.10 DTS doesn't have clocks under /clocks but only a ccu node. Currently only H3 is supported with almost the same state as HEAD. (video pll aren't supported for now but we don't support video). This driver and clocks will also be used for other SoC (A64, A31, H5, H2 etc ...) Reviewed by: jmcneill Differential Revision: https://reviews.freebsd.org/D9517 Added: head/sys/arm/allwinner/clkng/ head/sys/arm/allwinner/clkng/aw_ccung.c (contents, props changed) head/sys/arm/allwinner/clkng/aw_ccung.h (contents, props changed) head/sys/arm/allwinner/clkng/aw_clk.h (contents, props changed) head/sys/arm/allwinner/clkng/aw_clk_nkmp.c (contents, props changed) head/sys/arm/allwinner/clkng/aw_clk_nkmp.h (contents, props changed) head/sys/arm/allwinner/clkng/aw_clk_nm.c (contents, props changed) head/sys/arm/allwinner/clkng/aw_clk_nm.h (contents, props changed) head/sys/arm/allwinner/clkng/aw_clk_prediv_mux.c (contents, props changed) head/sys/arm/allwinner/clkng/aw_clk_prediv_mux.h (contents, props changed) head/sys/arm/allwinner/clkng/ccu_h3.c (contents, props changed) head/sys/arm/allwinner/clkng/ccu_h3.h (contents, props changed) Modified: head/sys/arm/allwinner/files.allwinner head/sys/arm/allwinner/h3/files.h3 Added: head/sys/arm/allwinner/clkng/aw_ccung.c ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/sys/arm/allwinner/clkng/aw_ccung.c Sun Feb 26 16:00:20 2017 (r314303) @@ -0,0 +1,348 @@ +/*- + * Copyright (c) 2017 Emmanuel Vadot <manu@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 ``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. + * + * $FreeBSD$ + */ + +/* + * Allwinner Clock Control Unit + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/bus.h> +#include <sys/rman.h> +#include <sys/kernel.h> +#include <sys/module.h> +#include <machine/bus.h> + +#include <dev/fdt/simplebus.h> + +#include <dev/ofw/ofw_bus.h> +#include <dev/ofw/ofw_bus_subr.h> + +#include <dev/extres/clk/clk.h> +#include <dev/extres/clk/clk_gate.h> + +#include <dev/extres/hwreset/hwreset.h> + +#include <arm/allwinner/clkng/aw_ccung.h> +#include <arm/allwinner/clkng/aw_clk.h> + +#if defined(SOC_ALLWINNER_H3) +#include <arm/allwinner/clkng/ccu_h3.h> +#endif + +#include "clkdev_if.h" +#include "hwreset_if.h" + +static struct resource_spec aw_ccung_spec[] = { + { SYS_RES_MEMORY, 0, RF_ACTIVE }, + { -1, 0 } +}; + +#if defined(SOC_ALLWINNER_H3) +#define H3_CCU 1 +#endif + +static struct ofw_compat_data compat_data[] = { +#if defined(SOC_ALLWINNER_H3) + { "allwinner,sun8i-h3-ccu", H3_CCU }, +#endif + {NULL, 0 } +}; + +#define CCU_READ4(sc, reg) bus_read_4((sc)->res, (reg)) +#define CCU_WRITE4(sc, reg, val) bus_write_4((sc)->res, (reg), (val)) + +static int +aw_ccung_write_4(device_t dev, bus_addr_t addr, uint32_t val) +{ + struct aw_ccung_softc *sc; + + sc = device_get_softc(dev); + CCU_WRITE4(sc, addr, val); + return (0); +} + +static int +aw_ccung_read_4(device_t dev, bus_addr_t addr, uint32_t *val) +{ + struct aw_ccung_softc *sc; + + sc = device_get_softc(dev); + + *val = CCU_READ4(sc, addr); + return (0); +} + +static int +aw_ccung_modify_4(device_t dev, bus_addr_t addr, uint32_t clr, uint32_t set) +{ + struct aw_ccung_softc *sc; + uint32_t reg; + + sc = device_get_softc(dev); + + reg = CCU_READ4(sc, addr); + reg &= ~clr; + reg |= set; + CCU_WRITE4(sc, addr, reg); + + return (0); +} + +static int +aw_ccung_reset_assert(device_t dev, intptr_t id, bool reset) +{ + struct aw_ccung_softc *sc; + uint32_t val; + + sc = device_get_softc(dev); + + if (id >= sc->nresets || sc->resets[id].offset == 0) + return (0); + + mtx_lock(&sc->mtx); + val = CCU_READ4(sc, sc->resets[id].offset); + if (reset) + val &= ~(1 << sc->resets[id].shift); + else + val |= 1 << sc->resets[id].shift; + CCU_WRITE4(sc, sc->resets[id].offset, val); + mtx_unlock(&sc->mtx); + + return (0); +} + +static int +aw_ccung_reset_is_asserted(device_t dev, intptr_t id, bool *reset) +{ + struct aw_ccung_softc *sc; + uint32_t val; + + sc = device_get_softc(dev); + + if (id >= sc->nresets || sc->resets[id].offset == 0) + return (0); + + mtx_lock(&sc->mtx); + val = CCU_READ4(sc, sc->resets[id].offset); + *reset = (val & (1 << sc->resets[id].shift)) != 0 ? false : true; + mtx_unlock(&sc->mtx); + + return (0); +} + +static void +aw_ccung_device_lock(device_t dev) +{ + struct aw_ccung_softc *sc; + + sc = device_get_softc(dev); + mtx_lock(&sc->mtx); +} + +static void +aw_ccung_device_unlock(device_t dev) +{ + struct aw_ccung_softc *sc; + + sc = device_get_softc(dev); + mtx_unlock(&sc->mtx); +} + +static int +aw_ccung_probe(device_t dev) +{ + + if (!ofw_bus_status_okay(dev)) + return (ENXIO); + + if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) + return (ENXIO); + + device_set_desc(dev, "Allwinner Clock Control Unit NG"); + return (BUS_PROBE_DEFAULT); +} + +static int +aw_ccung_register_gates(struct aw_ccung_softc *sc) +{ + struct clk_gate_def def; + int i; + + for (i = 0; i < sc->ngates; i++) { + if (sc->gates[i].name == NULL) + continue; + memset(&def, 0, sizeof(def)); + def.clkdef.id = i; + def.clkdef.name = sc->gates[i].name; + def.clkdef.parent_names = &sc->gates[i].parent_name; + def.clkdef.parent_cnt = 1; + def.offset = sc->gates[i].offset; + def.shift = sc->gates[i].shift; + def.mask = 1; + def.on_value = 1; + def.off_value = 0; + clknode_gate_register(sc->clkdom, &def); + } + + return (0); +} + +static void +aw_ccung_init_clocks(struct aw_ccung_softc *sc) +{ + struct clknode *clknode; + int i, error; + + for (i = 0; i < sc->n_clk_init; i++) { + clknode = clknode_find_by_name(sc->clk_init[i].name); + if (clknode == NULL) { + device_printf(sc->dev, "Cannot find clock %s\n", + sc->clk_init[i].name); + continue; + } + + if (sc->clk_init[i].parent_name != NULL) { + if (bootverbose) + device_printf(sc->dev, "Setting %s as parent for %s\n", + sc->clk_init[i].parent_name, + sc->clk_init[i].name); + error = clknode_set_parent_by_name(clknode, + sc->clk_init[i].parent_name); + if (error != 0) { + device_printf(sc->dev, + "Cannot set parent to %s for %s\n", + sc->clk_init[i].parent_name, + sc->clk_init[i].name); + continue; + } + } + if (sc->clk_init[i].default_freq != 0) { + error = clknode_set_freq(clknode, + sc->clk_init[i].default_freq, 0 , 0); + if (error != 0) { + device_printf(sc->dev, + "Cannot set frequency for %s to %llu\n", + sc->clk_init[i].name, + sc->clk_init[i].default_freq); + continue; + } + } + if (sc->clk_init[i].enable) { + error = clknode_enable(clknode); + if (error != 0) { + device_printf(sc->dev, + "Cannot enable %s\n", + sc->clk_init[i].name); + continue; + } + } + } +} + +static int +aw_ccung_attach(device_t dev) +{ + struct aw_ccung_softc *sc; + + sc = device_get_softc(dev); + sc->dev = dev; + + if (bus_alloc_resources(dev, aw_ccung_spec, &sc->res) != 0) { + device_printf(dev, "cannot allocate resources for device\n"); + return (ENXIO); + } + + mtx_init(&sc->mtx, device_get_nameunit(dev), NULL, MTX_DEF); + + sc->type = ofw_bus_search_compatible(dev, compat_data)->ocd_data; + + sc->clkdom = clkdom_create(dev); + if (sc->clkdom == NULL) + panic("Cannot create clkdom\n"); + + switch (sc->type) { +#if defined(SOC_ALLWINNER_H3) + case H3_CCU: + ccu_h3_register_clocks(sc); + break; +#endif + } + + if (sc->gates) + aw_ccung_register_gates(sc); + if (clkdom_finit(sc->clkdom) != 0) + panic("cannot finalize clkdom initialization\n"); + + clkdom_xlock(sc->clkdom); + aw_ccung_init_clocks(sc); + clkdom_unlock(sc->clkdom); + + if (bootverbose) + clkdom_dump(sc->clkdom); + + /* If we have resets, register our self as a reset provider */ + if (sc->resets) + hwreset_register_ofw_provider(dev); + + return (0); +} + +static device_method_t aw_ccung_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, aw_ccung_probe), + DEVMETHOD(device_attach, aw_ccung_attach), + + /* clkdev interface */ + DEVMETHOD(clkdev_write_4, aw_ccung_write_4), + DEVMETHOD(clkdev_read_4, aw_ccung_read_4), + DEVMETHOD(clkdev_modify_4, aw_ccung_modify_4), + DEVMETHOD(clkdev_device_lock, aw_ccung_device_lock), + DEVMETHOD(clkdev_device_unlock, aw_ccung_device_unlock), + + /* Reset interface */ + DEVMETHOD(hwreset_assert, aw_ccung_reset_assert), + DEVMETHOD(hwreset_is_asserted, aw_ccung_reset_is_asserted), + + DEVMETHOD_END +}; + +static driver_t aw_ccung_driver = { + "aw_ccung", + aw_ccung_methods, + sizeof(struct aw_ccung_softc), +}; + +static devclass_t aw_ccung_devclass; + +EARLY_DRIVER_MODULE(aw_ccung, simplebus, aw_ccung_driver, aw_ccung_devclass, + 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE); +MODULE_VERSION(aw_ccung, 1); Added: head/sys/arm/allwinner/clkng/aw_ccung.h ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/sys/arm/allwinner/clkng/aw_ccung.h Sun Feb 26 16:00:20 2017 (r314303) @@ -0,0 +1,59 @@ +/*- + * Copyright (c) 2017 Emmanuel Vadot <manu@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 ``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. + * + * $FreeBSD$ + */ + +#ifndef __CCU_NG_H__ +#define __CCU_NG_H__ + +struct aw_ccung_softc { + device_t dev; + struct resource *res; + struct clkdom *clkdom; + struct mtx mtx; + int type; + struct aw_ccung_reset *resets; + int nresets; + struct aw_ccung_gate *gates; + int ngates; + struct aw_clk_init *clk_init; + int n_clk_init; +}; + +struct aw_ccung_reset { + uint32_t offset; + uint32_t shift; +}; + +struct aw_ccung_gate { + const char *name; + const char *parent_name; + uint32_t id; + uint32_t offset; + uint32_t shift; +}; + +#endif /* __CCU_NG_H__ */ Added: head/sys/arm/allwinner/clkng/aw_clk.h ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/sys/arm/allwinner/clkng/aw_clk.h Sun Feb 26 16:00:20 2017 (r314303) @@ -0,0 +1,317 @@ +/*- + * Copyright (c) 2017 Emmanuel Vadot <manu@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 ``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. + * + * $FreeBSD$ + */ + +#ifndef __AW_CLK_H__ +#define __AW_CLK_H__ + +/* + Allwinner clocks formula : + +PLLs: + +(24MHz*N*K)/(M*P) +(24MHz*N)/(M*P) +(24MHz*N*2)/M +(24MHz*N)/M +(24MHz*N*K)/M +(24MHz*N*K/2) +(24MHz*N)/M +(24MHz*N*K/2) +(24MHz*N)/M + +Periph clocks: + +Clock Source/Divider N/Divider M +Clock Source/Divider N/Divider M/2 + + */ + +struct aw_clk_init { + const char *name; + const char *parent_name; + uint64_t default_freq; + bool enable; +}; + +#define AW_CLK_HAS_GATE 0x0001 +#define AW_CLK_HAS_LOCK 0x0002 +#define AW_CLK_HAS_MUX 0x0004 +#define AW_CLK_REPARENT 0x0008 +#define AW_CLK_SCALE_CHANGE 0x0010 + +#define AW_CLK_FACTOR_POWER_OF_TWO 0x0001 +#define AW_CLK_FACTOR_ZERO_BASED 0x0002 +#define AW_CLK_FACTOR_HAS_COND 0x0004 +#define AW_CLK_FACTOR_FIXED 0x0008 + +struct aw_clk_factor { + uint32_t shift; /* Shift bits for the factor */ + uint32_t mask; /* Mask to get the factor, will be override by the clk methods */ + uint32_t width; /* Number of bits for the factor */ + uint32_t value; /* Fixed value, depends on AW_CLK_FACTOR_FIXED */ + + uint32_t cond_shift; + uint32_t cond_mask; + uint32_t cond_width; + uint32_t cond_value; + + uint32_t flags; /* Flags */ +}; + +static inline uint32_t +aw_clk_get_factor(uint32_t val, struct aw_clk_factor *factor) +{ + uint32_t factor_val; + uint32_t cond; + + if (factor->flags & AW_CLK_FACTOR_HAS_COND) { + cond = (val & factor->cond_mask) >> factor->cond_shift; + if (cond != factor->cond_value) + return (1); + } + + if (factor->flags & AW_CLK_FACTOR_FIXED) + return (factor->value); + + factor_val = (val & factor->mask) >> factor->shift; + if (!(factor->flags & AW_CLK_FACTOR_ZERO_BASED)) + factor_val += 1; + else if (factor->flags & AW_CLK_FACTOR_POWER_OF_TWO) + factor_val = 1 << factor_val; + + return (factor_val); +} + +static inline uint32_t +aw_clk_factor_get_max(struct aw_clk_factor *factor) +{ + uint32_t max; + + if (factor->flags & AW_CLK_FACTOR_FIXED) + max = factor->value; + else if (factor->flags & AW_CLK_FACTOR_POWER_OF_TWO) + max = 1 << ((1 << factor->width) - 1); + else { + max = (1 << factor->width); + } + + return (max); +} + +static inline uint32_t +aw_clk_factor_get_min(struct aw_clk_factor *factor) +{ + uint32_t min; + + if (factor->flags & AW_CLK_FACTOR_FIXED) + min = factor->value; + else if (factor->flags & AW_CLK_FACTOR_ZERO_BASED) + min = 0; + else + min = 1; + + return (min); +} + +static inline uint32_t +aw_clk_factor_get_value(struct aw_clk_factor *factor, uint32_t raw) +{ + uint32_t val; + + if (factor->flags & AW_CLK_FACTOR_FIXED) + return (factor->value); + + if (factor->flags & AW_CLK_FACTOR_ZERO_BASED) + val = raw; + else if (factor->flags & AW_CLK_FACTOR_POWER_OF_TWO) { + for (val = 0; raw != 1; val++) + raw >>= 1; + } else + val = raw - 1; + + return (val); +} + +#define CCU_RESET(idx, o, s) \ + [idx] = { \ + .offset = o, \ + .shift = s, \ + }, + +#define CCU_GATE(idx, clkname, pname, o, s) \ + [idx] = { \ + .name = clkname, \ + .parent_name = pname, \ + .offset = o, \ + .shift = s, \ + }, + +#define NKMP_CLK(_id, _name, _pnames, \ + _offset, \ + _n_shift, _n_width, _n_value, _n_flags, \ + _k_shift, _k_width, _k_value, _k_flags, \ + _m_shift, _m_width, _m_value, _m_flags, \ + _p_shift, _p_width, _p_value, _p_flags, \ + _gate, \ + _lock, _lock_retries, \ + _flags) \ + { \ + .clkdef = { \ + .id = _id, \ + .name = _name, \ + .parent_names = _pnames, \ + .parent_cnt = nitems(_pnames), \ + }, \ + .offset = _offset, \ + .n.shift = _n_shift, \ + .n.width = _n_width, \ + .n.value = _n_value, \ + .n.flags = _n_flags, \ + .k.shift = _k_shift, \ + .k.width = _k_width, \ + .k.value = _k_value, \ + .k.flags = _k_flags, \ + .m.shift = _m_shift, \ + .m.width = _m_width, \ + .m.value = _m_value, \ + .m.flags = _m_flags, \ + .p.shift = _p_shift, \ + .p.width = _p_width, \ + .p.value = _p_value, \ + .p.flags = _p_flags, \ + .gate_shift = _gate, \ + .lock_shift = _lock, \ + .lock_retries = _lock_retries, \ + .flags = _flags, \ + }, + +#define NM_CLK(_id, _name, _pnames, \ + _offset, \ + _nshift, _nwidth, _nvalue, _nflags, \ + _mshift, _mwidth, _mvalue, _mflags, \ + _mux_shift, _mux_width, \ + _gate_shift, \ + _flags) \ + { \ + .clkdef = { \ + .id = _id, \ + .name = _name, \ + .parent_names = _pnames, \ + .parent_cnt = nitems(_pnames), \ + }, \ + .offset = _offset, \ + .n.shift = _nshift, \ + .n.width = _nwidth, \ + .n.value = _nvalue, \ + .n.flags = _nflags, \ + .mux_shift = _mux_shift, \ + .m.shift = _mshift, \ + .m.width = _mwidth, \ + .m.value = _mvalue, \ + .m.flags = _mflags, \ + .mux_width = _mux_width, \ + .flags = _flags, \ + }, + +#define PREDIV_CLK(_id, _name, _pnames, \ + _offset, \ + _mux_shift, _mux_width, \ + _div_shift, _div_width, _div_value, _div_flags, \ + _prediv_shift, _prediv_width, _prediv_value, _prediv_flags, \ + _prediv_cond_shift, _prediv_cond_width, _prediv_cond_value) \ + { \ + .clkdef = { \ + .id = _id, \ + .name = _name, \ + .parent_names = _pnames, \ + .parent_cnt = nitems(_pnames), \ + }, \ + .offset = _offset, \ + .mux_shift = _mux_shift, \ + .mux_width = _mux_width, \ + .div.shift = _div_shift, \ + .div.width = _div_width, \ + .div.value = _div_value, \ + .div.flags = _div_flags, \ + .prediv.shift = _prediv_shift, \ + .prediv.width = _prediv_width, \ + .prediv.value = _prediv_value, \ + .prediv.flags = _prediv_flags, \ + .prediv.cond_shift = _prediv_cond_shift, \ + .prediv.cond_width = _prediv_cond_width, \ + .prediv.cond_value = _prediv_cond_value, \ + }, + +#define MUX_CLK(_id, _name, _pnames, \ + _offset, _shift, _width) \ + { \ + .clkdef = { \ + .id = _id, \ + .name = _name, \ + .parent_names = _pnames, \ + .parent_cnt = nitems(_pnames) \ + }, \ + .offset = _offset, \ + .shift = _shift, \ + .width = _width, \ + }, + +#define DIV_CLK(_id, _name, _pnames, \ + _offset, \ + _i_shift, _i_width, \ + _div_flags, _div_table) \ + { \ + .clkdef = { \ + .id = _id, \ + .name = _name, \ + .parent_names = _pnames, \ + .parent_cnt = nitems(_pnames) \ + }, \ + .offset = _offset, \ + .i_shift = _i_shift, \ + .i_width = _i_width, \ + .div_flags = _div_flags, \ + .div_table = _div_table, \ + }, + +#define FIXED_CLK(_id, _name, _pnames, \ + _freq, _mult, _div, _flags) \ + { \ + .clkdef = { \ + .id = _id, \ + .name = _name, \ + .parent_names = _pnames, \ + .parent_cnt = 1, \ + }, \ + .freq = _freq, \ + .mult = _mult, \ + .div = _div, \ + .fixed_flags = _flags, \ + }, + +#endif /* __AW_CLK_H__ */ Added: head/sys/arm/allwinner/clkng/aw_clk_nkmp.c ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/sys/arm/allwinner/clkng/aw_clk_nkmp.c Sun Feb 26 16:00:20 2017 (r314303) @@ -0,0 +1,362 @@ +/*- + * Copyright (c) 2017 Emmanuel Vadot <manu@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 ``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. + * + * $FreeBSD$ + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/bus.h> + +#include <dev/extres/clk/clk.h> + +#include <arm/allwinner/clkng/aw_clk.h> +#include <arm/allwinner/clkng/aw_clk_nkmp.h> + +#include "clkdev_if.h" + +/* + * clknode for clocks matching the formula : + * + * clk = (clkin * n * k) / (m * p) + * + */ + +struct aw_clk_nkmp_sc { + uint32_t offset; + + struct aw_clk_factor n; + struct aw_clk_factor k; + struct aw_clk_factor m; + struct aw_clk_factor p; + + uint32_t gate_shift; + uint32_t lock_shift; + uint32_t lock_retries; + + uint32_t flags; +}; + +#define WRITE4(_clk, off, val) \ + CLKDEV_WRITE_4(clknode_get_device(_clk), off, val) +#define READ4(_clk, off, val) \ + CLKDEV_READ_4(clknode_get_device(_clk), off, val) +#define MODIFY4(_clk, off, clr, set ) \ + CLKDEV_MODIFY_4(clknode_get_device(_clk), off, clr, set) +#define DEVICE_LOCK(_clk) \ + CLKDEV_DEVICE_LOCK(clknode_get_device(_clk)) +#define DEVICE_UNLOCK(_clk) \ + CLKDEV_DEVICE_UNLOCK(clknode_get_device(_clk)) + +static int +aw_clk_nkmp_init(struct clknode *clk, device_t dev) +{ + clknode_init_parent_idx(clk, 0); + return (0); +} + +static int +aw_clk_nkmp_set_gate(struct clknode *clk, bool enable) +{ + struct aw_clk_nkmp_sc *sc; + uint32_t val; + + sc = clknode_get_softc(clk); + + if ((sc->flags & AW_CLK_HAS_GATE) == 0) + return (0); + + DEVICE_LOCK(clk); + READ4(clk, sc->offset, &val); + if (enable) + val |= (1 << sc->gate_shift); + else + val &= ~(1 << sc->gate_shift); + WRITE4(clk, sc->offset, val); + DEVICE_UNLOCK(clk); + + return (0); +} + +static uint64_t +aw_clk_nkmp_find_best(struct aw_clk_nkmp_sc *sc, uint64_t fparent, uint64_t *fout, + uint32_t *factor_n, uint32_t *factor_k, uint32_t *factor_m, uint32_t *factor_p) +{ + uint64_t cur, best; + uint32_t n, k, m, p; + + best = 0; + *factor_n = 0; + *factor_k = 0; + *factor_m = 0; + *factor_p = 0; + + for (n = aw_clk_factor_get_min(&sc->n); n <= aw_clk_factor_get_max(&sc->n); ) { + for (k = aw_clk_factor_get_min(&sc->k); k <= aw_clk_factor_get_max(&sc->k); ) { + for (m = aw_clk_factor_get_min(&sc->m); m <= aw_clk_factor_get_max(&sc->m); ) { + for (p = aw_clk_factor_get_min(&sc->p); p <= aw_clk_factor_get_max(&sc->p); ) { + cur = (fparent * n * k) / (m * p); + if ((*fout - cur) < (*fout - best)) { + best = cur; + *factor_n = n; + *factor_k = k; + *factor_m = m; + *factor_p = p; + } + if (best == *fout) + return (best); + if ((sc->p.flags & AW_CLK_FACTOR_POWER_OF_TWO) != 0) + p <<= 1; + else + p++; + } + if ((sc->m.flags & AW_CLK_FACTOR_POWER_OF_TWO) != 0) + m <<= 1; + else + m++; + } + if ((sc->k.flags & AW_CLK_FACTOR_POWER_OF_TWO) != 0) + k <<= 1; + else + k++; + } + if ((sc->n.flags & AW_CLK_FACTOR_POWER_OF_TWO) != 0) + n <<= 1; + else + n++; + } + + return best; +} + +static void +aw_clk_nkmp_set_freq_scale(struct clknode *clk, struct aw_clk_nkmp_sc *sc, + uint32_t factor_n, uint32_t factor_k, uint32_t factor_m, uint32_t factor_p) +{ + uint32_t val, n, k, m, p; + int retry; + + DEVICE_LOCK(clk); + READ4(clk, sc->offset, &val); + + n = aw_clk_get_factor(val, &sc->n); + k = aw_clk_get_factor(val, &sc->k); + m = aw_clk_get_factor(val, &sc->m); + p = aw_clk_get_factor(val, &sc->p); + + if (p < factor_p) { + val &= ~sc->p.mask; + val |= aw_clk_factor_get_value(&sc->p, factor_p) << sc->p.shift; + WRITE4(clk, sc->offset, val); + DELAY(2000); + } + + if (m < factor_m) { + val &= ~sc->m.mask; + val |= aw_clk_factor_get_value(&sc->m, factor_m) << sc->m.shift; + WRITE4(clk, sc->offset, val); + DELAY(2000); + } + + val &= ~sc->n.mask; + val &= ~sc->k.mask; + val |= aw_clk_factor_get_value(&sc->n, factor_n) << sc->n.shift; + val |= aw_clk_factor_get_value(&sc->k, factor_k) << sc->k.shift; + WRITE4(clk, sc->offset, val); + DELAY(2000); + + if (m > factor_m) { + val &= ~sc->m.mask; + val |= aw_clk_factor_get_value(&sc->m, factor_m) << sc->m.shift; + WRITE4(clk, sc->offset, val); + DELAY(2000); + } + + if (p > factor_p) { + val &= ~sc->p.mask; + val |= aw_clk_factor_get_value(&sc->p, factor_p) << sc->p.shift; + WRITE4(clk, sc->offset, val); + DELAY(2000); + } + + if ((sc->flags & AW_CLK_HAS_LOCK) != 0) { + for (retry = 0; retry < sc->lock_retries; retry++) { + READ4(clk, sc->offset, &val); + if ((val & (1 << sc->lock_shift)) != 0) + break; + DELAY(1000); + } + } + + DEVICE_UNLOCK(clk); +} + +static int +aw_clk_nkmp_set_freq(struct clknode *clk, uint64_t fparent, uint64_t *fout, + int flags, int *stop) +{ + struct aw_clk_nkmp_sc *sc; + uint64_t best; + uint32_t val, best_n, best_k, best_m, best_p; + int retry; + + sc = clknode_get_softc(clk); + + best = aw_clk_nkmp_find_best(sc, fparent, fout, + &best_n, &best_k, &best_m, &best_p); + if ((flags & CLK_SET_DRYRUN) != 0) { + *fout = best; + *stop = 1; + return (0); + } + + if ((best < *fout) && + ((flags & CLK_SET_ROUND_DOWN) != 0)) { + *stop = 1; + return (ERANGE); + } + if ((best > *fout) && + ((flags & CLK_SET_ROUND_UP) != 0)) { + *stop = 1; + return (ERANGE); + } + + if ((sc->flags & AW_CLK_SCALE_CHANGE) != 0) + aw_clk_nkmp_set_freq_scale(clk, sc, + best_n, best_k, best_m, best_p); + else { + DEVICE_LOCK(clk); + READ4(clk, sc->offset, &val); + val &= ~sc->n.mask; + val &= ~sc->k.mask; + val &= ~sc->m.mask; + val &= ~sc->p.mask; + val |= aw_clk_factor_get_value(&sc->n, best_n) << sc->n.shift; + val |= aw_clk_factor_get_value(&sc->k, best_k) << sc->k.shift; + val |= aw_clk_factor_get_value(&sc->m, best_m) << sc->m.shift; + val |= aw_clk_factor_get_value(&sc->p, best_p) << sc->p.shift; + WRITE4(clk, sc->offset, val); + DELAY(2000); *** DIFF OUTPUT TRUNCATED AT 1000 LINES ***
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201702261600.v1QG0Krv080636>