root/sys/arch/arm/altera/cycv_clkmgr.c
/* $NetBSD: cycv_clkmgr.c,v 1.8 2021/01/27 03:10:18 thorpej Exp $ */

/* This file is in the public domain. */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: cycv_clkmgr.c,v 1.8 2021/01/27 03:10:18 thorpej 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 <dev/clk/clk_backend.h>

#include <arm/altera/cycv_reg.h>
#include <arm/altera/cycv_var.h>

#include <dev/fdt/fdtvar.h>

#define CYCV_CLOCK_OSC1         25000000

static int cycv_clkmgr_match(device_t, cfdata_t, void *);
static void cycv_clkmgr_attach(device_t, device_t, void *);

static struct clk *cycv_clkmgr_clock_decode(device_t, int, const void *,
                                            size_t);

static const struct fdtbus_clock_controller_func cycv_clkmgr_fdtclock_funcs = {
        .decode = cycv_clkmgr_clock_decode
};

static struct clk *cycv_clkmgr_clock_get(void *, const char *);
static void cycv_clkmgr_clock_put(void *, struct clk *);
static u_int cycv_clkmgr_clock_get_rate(void *, struct clk *);
static int cycv_clkmgr_clock_set_rate(void *, struct clk *, u_int);
static int cycv_clkmgr_clock_enable(void *, struct clk *);
static int cycv_clkmgr_clock_disable(void *, struct clk *);
static int cycv_clkmgr_clock_set_parent(void *, struct clk *, struct clk *);
static struct clk *cycv_clkmgr_clock_get_parent(void *, struct clk *);

static const struct clk_funcs cycv_clkmgr_clock_funcs = {
        .get = cycv_clkmgr_clock_get,
        .put = cycv_clkmgr_clock_put,
        .get_rate = cycv_clkmgr_clock_get_rate,
        .set_rate = cycv_clkmgr_clock_set_rate,
        .enable = cycv_clkmgr_clock_enable,
        .disable = cycv_clkmgr_clock_disable,
        .get_parent = cycv_clkmgr_clock_get_parent,
        .set_parent = cycv_clkmgr_clock_set_parent,
};

struct cycv_clk {
        struct clk base;

        int id;
        u_int refcnt;

        struct cycv_clk *parent;        /* cached and valid if not NULL */
        /* parent_id is not zero and filled with dtb if only one parent clock */
        int parent_id;

        int type;
#define CYCV_CLK_TYPE_PERIP     0xffff  /* pseudo-type */
#define CYCV_CLK_TYPE_PLL       0x0001
#define CYCV_CLK_TYPE_FIXED     0x0002
#define CYCV_CLK_TYPE_FIXED_DIV 0x0003
#define CYCV_CLK_TYPE_DIV       0x0004

        int flags;
#define CYCV_CLK_FLAG_HAVE_GATE 0x0001
#define CYCV_CLK_FLAG_IS_AVAIL  0x0002

        union {
                bus_addr_t pll_addr;
                uint32_t fixed_freq;
                uint32_t fixed_div;
                struct {
                        bus_addr_t addr;
                        uint32_t mask;
                        int shift;
                } div;
        } u;

        bus_addr_t gate_addr;
        int gate_shift;
};

struct cycv_clkmgr_softc {
        device_t sc_dev;
        struct clk_domain sc_clkdom;

        bus_space_tag_t sc_bst;
        bus_space_handle_t sc_bsh;

        struct cycv_clk *sc_clocks;
        u_int sc_nclocks;
};

static void cycv_clkmgr_init(struct cycv_clkmgr_softc *, int);
static void cycv_clkmgr_clock_parse(struct cycv_clkmgr_softc *, int, u_int);
static u_int cycv_clkmgr_clocks_traverse(struct cycv_clkmgr_softc *, int,
        void (*)(struct cycv_clkmgr_softc *, int, u_int), u_int);
static struct cycv_clk_mux_info *cycv_clkmgr_get_mux_info(const char *);
static void cycv_clkmgr_clock_print(struct cycv_clkmgr_softc *,
        struct cycv_clk *);

CFATTACH_DECL_NEW(cycvclkmgr, sizeof (struct cycv_clkmgr_softc),
        cycv_clkmgr_match, cycv_clkmgr_attach, NULL, NULL);

static const struct device_compatible_entry compat_data[] = {
        { .compat = "altr,clk-mgr" },
        DEVICE_COMPAT_EOL
};

static int
cycv_clkmgr_match(device_t parent, cfdata_t cf, void *aux)
{
        struct fdt_attach_args *faa = aux;

        return of_compatible_match(faa->faa_phandle, compat_data);
}

static void
cycv_clkmgr_attach(device_t parent, device_t self, void *aux)
{
        struct cycv_clkmgr_softc *sc = device_private(self);
        struct fdt_attach_args *faa = aux;
        int phandle = faa->faa_phandle;
        bus_addr_t addr;
        bus_size_t size;
        int error;

        if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) {
                aprint_error(": couldn't get registers\n");
                return;
        }

        sc->sc_dev = self;
        sc->sc_bst = faa->faa_bst;
        error = bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh);
        if (error) {
                aprint_error(": couldn't map %#" PRIxBUSADDR ": %d",
                             addr, error);
                return;
        }

        aprint_normal(": clock manager\n");

        sc->sc_clkdom.funcs = &cycv_clkmgr_clock_funcs;
        sc->sc_clkdom.priv = sc;

        cycv_clkmgr_init(sc, phandle);
}

static void
cycv_clkmgr_init(struct cycv_clkmgr_softc *sc, int clkmgr_handle)
{
        int clocks_handle;

        clocks_handle = of_find_firstchild_byname(clkmgr_handle, "clocks");
        if (clocks_handle == -1) {
                aprint_error_dev(sc->sc_dev, "no clocks property\n");
                return;
        }

        sc->sc_nclocks = cycv_clkmgr_clocks_traverse(sc, clocks_handle, NULL,
                                                     0);

        sc->sc_clocks = kmem_zalloc(sc->sc_nclocks * sizeof *sc->sc_clocks,
                                    KM_SLEEP);
        cycv_clkmgr_clocks_traverse(sc, clocks_handle, cycv_clkmgr_clock_parse,
                                    0);

#if 1
        for (int i = 0; i < sc->sc_nclocks; i++)
                cycv_clkmgr_clock_print(sc, &sc->sc_clocks[i]);
#else
        (void) cycv_clkmgr_clock_print;
#endif
}

#define CYCV_CLK_MAX_PARENTS 3

static struct cycv_clk_mux_info {
        const char *name;
        const char *parents[CYCV_CLK_MAX_PARENTS];
        int nparents;
        bus_addr_t addr;
        uint32_t mask;
} cycv_clk_mux_tree[] = {
        { "periph_pll", { "osc1", "osc2", "f2s_periph_ref_clk" }, 3,
                0x80, 0x00c00000 },
        { "sdram_pll", { "osc1", "osc2", "f2s_sdram_ref_clk" }, 3,
                0xc0, 0x00c00000 },
        { "l4_mp_clk", { "mainclk", "per_base_clk" }, 2, 0x70, 0x00000001 },
        { "l4_sp_clk", { "mainclk", "per_base_clk" }, 2, 0x70, 0x00000002 },
        { "sdmmc_clk",
            { "f2s_periph_ref_clk", "main_nand_sdmmc_clk", "per_nand_mmc_clk" },
            3, 0xac, 0x00000003 },
        { "nand_x_clk",
            { "f2s_periph_ref_clk", "main_nand_sdmmc_clk", "per_nand_mmc_clk" },
            3, 0xac, 0x0000000c },
        { "qspi_clk", { "f2s_periph_ref_clk", "main_qspi_clk", "per_qsi_clk" },
            3, 0xac, 0x00000030 },

        /* Don't special case bypass */
        { "dbg_base_clk", { "main_pll" }, 1, 0, 0 },
        /* Bug in dtb */
        { "nand_clk", { "nand_x_clk" }, 1, 0, 0 },
};

static const struct device_compatible_entry clock_types[] = {
        { .compat = "fixed-clock",              .value = CYCV_CLK_TYPE_FIXED },
        { .compat = "altr,socfpga-pll-clock",   .value = CYCV_CLK_TYPE_PLL },
        { .compat = "altr,socfpga-perip-clk",   .value = CYCV_CLK_TYPE_PERIP },
        { .compat = "altr,socfpga-gate-clk",    .value = CYCV_CLK_TYPE_PERIP },
        DEVICE_COMPAT_EOL
};

static void
cycv_clkmgr_clock_parse(struct cycv_clkmgr_softc *sc, int handle, u_int clkno)
{
        struct cycv_clk *clk = &sc->sc_clocks[clkno];
        int flags = 0;
        const uint8_t *buf;
        int len;

        clk->base.domain = &sc->sc_clkdom;
        clk->base.name = fdtbus_get_string(handle, "name");
        clk->base.flags = 0;

        clk->id = handle;
        clk->parent = NULL;
        clk->parent_id = 0;
        clk->refcnt = 0;

        const struct device_compatible_entry * const dce =
            of_compatible_lookup(handle, clock_types);
        if (dce == NULL)
                goto err;
        clk->type = dce->value;

        if (clk->type == CYCV_CLK_TYPE_FIXED) {
                if (of_getprop_uint32(handle, "clock-frequency",
                                      &clk->u.fixed_freq) == 0) {
                        flags |= CYCV_CLK_FLAG_IS_AVAIL;
                }
        } else if (clk->type == CYCV_CLK_TYPE_PLL) {
                if (fdtbus_get_reg(handle, 0, &clk->u.pll_addr, NULL) != 0)
                        goto err;
                flags |= CYCV_CLK_FLAG_IS_AVAIL;
        } else if (clk->type == CYCV_CLK_TYPE_PERIP) {
                if (of_getprop_uint32(handle, "fixed-divider",
                                      &clk->u.fixed_div) == 0) {
                        clk->type = CYCV_CLK_TYPE_FIXED_DIV;
                } else if (fdtbus_get_reg(handle, 0, &clk->u.div.addr, NULL) ==
                                0) {
                        clk->type = CYCV_CLK_TYPE_DIV;
                        clk->u.div.shift = 0;
                        clk->u.div.mask = 0xff;
                } else if ((buf = fdtbus_get_prop(handle, "div-reg", &len)) !=
                                NULL) {
                        if (len != 3 * 4)
                                goto err;

                        clk->type = CYCV_CLK_TYPE_DIV;
                        clk->u.div.addr = of_decode_int(buf);
                        clk->u.div.shift = of_decode_int(buf + 4);
                        clk->u.div.mask = ((1 << of_decode_int(buf + 8)) - 1) <<
                                clk->u.div.shift;
                } else {
                        /* Simply a gate and/or a mux */
                        clk->type = CYCV_CLK_TYPE_FIXED_DIV;
                        clk->u.fixed_div = 1;
                }
                flags |= CYCV_CLK_FLAG_IS_AVAIL;
        } else
                goto err;

        if ((buf = fdtbus_get_prop(handle, "clk-gate", &len)) != NULL) {
                clk->gate_addr = of_decode_int(buf);
                clk->gate_shift = of_decode_int(buf + 4);
                flags |= CYCV_CLK_FLAG_HAVE_GATE;
        }

        buf = fdtbus_get_prop(handle, "clocks", &len);
        if (buf != NULL && len == sizeof (uint32_t)) {
                clk->parent_id =
                        fdtbus_get_phandle_from_native(of_decode_int(buf));
        }

        clk->flags = flags;

        fdtbus_register_clock_controller(sc->sc_dev, handle,
                &cycv_clkmgr_fdtclock_funcs);

        return;
err:
        aprint_debug_dev(sc->sc_dev, "(%s) error parsing phandle %d\n",
                clk->base.name, handle);
}

static u_int
cycv_clkmgr_clocks_traverse(struct cycv_clkmgr_softc *sc, int clocks_handle,
                            void func(struct cycv_clkmgr_softc *, int, u_int),
                            u_int nclocks)
{
        int clk_handle;

        for (clk_handle = OF_child(clocks_handle); clk_handle != 0;
                                        clk_handle = OF_peer(clk_handle)) {
                if (func != NULL)
                        func(sc, clk_handle, nclocks);
                nclocks++;

                nclocks = cycv_clkmgr_clocks_traverse(sc, clk_handle, func,
                                                      nclocks);
        }

        return nclocks;
}

static struct cycv_clk_mux_info *
cycv_clkmgr_get_mux_info(const char *name)
{
        size_t i;

        for (i = 0; i < __arraycount(cycv_clk_mux_tree); i++) {
                struct cycv_clk_mux_info *cand = &cycv_clk_mux_tree[i];
                if (strncmp(name, cand->name, strlen(cand->name)) == 0)
                        return cand;
        }

        return NULL;
}

static struct cycv_clk *
cycv_clkmgr_clock_lookup_by_id(struct cycv_clkmgr_softc *sc, int id)
{
        size_t i;

        for (i = 0; i < sc->sc_nclocks; i++) {
                if (sc->sc_clocks[i].id == id)
                        return &sc->sc_clocks[i];
        }

        return NULL;
}

static struct cycv_clk *
cycv_clkmgr_clock_lookup_by_name(struct cycv_clkmgr_softc *sc, const char *name)
{
        size_t i;

        for (i = 0; i < sc->sc_nclocks; i++) {
                struct cycv_clk *cand = &sc->sc_clocks[i];
                if (strncmp(cand->base.name, name, strlen(name)) == 0)
                        return cand;
        }

        return NULL;
}

static void
cycv_clkmgr_clock_print(struct cycv_clkmgr_softc *sc, struct cycv_clk *clk)
{
        uint32_t numer;
        uint32_t denom;
        uint32_t tmp;

        aprint_debug("clock %s, id %d, frequency %uHz:\n", clk->base.name,
                     clk->id, cycv_clkmgr_clock_get_rate(sc, &clk->base));
        if (clk->parent != NULL)
                aprint_debug("parent: %s", clk->parent->base.name);
        else
                aprint_debug("parent_id: %d", clk->parent_id);
        aprint_debug(", flags: %d\n", clk->flags);
        switch (clk->type) {
        case CYCV_CLK_TYPE_PLL:
                tmp = bus_space_read_4(sc->sc_bst, sc->sc_bsh, clk->u.pll_addr);
                numer = __SHIFTOUT(tmp, CYCV_CLKMGR_PLL_VCO_NUMER) + 1;
                denom = __SHIFTOUT(tmp, CYCV_CLKMGR_PLL_VCO_DENOM) + 1;
                aprint_debug(" PLL num = %u, den = %u\n", numer, denom);
                break;
        case CYCV_CLK_TYPE_FIXED:
                aprint_debug(" Fixed frequency = %u\n", clk->u.fixed_freq);
                break;
        case CYCV_CLK_TYPE_FIXED_DIV:
                aprint_debug(" Fixed divisor = %u\n", clk->u.fixed_div);
                break;
        case CYCV_CLK_TYPE_DIV:
                tmp = bus_space_read_4(sc->sc_bst, sc->sc_bsh, clk->u.div.addr);
                tmp = (tmp & clk->u.div.mask) >> clk->u.div.shift;
                if (__SHIFTOUT_MASK(clk->u.div.mask) > 0xf)
                        tmp += 1;
                else
                        tmp = (1 << tmp);
                aprint_debug(" Divisor = %u\n", tmp);
                break;
        default:
                aprint_debug(" Unknown!!!\n");
                break;
        }

        if (clk->flags & CYCV_CLK_FLAG_HAVE_GATE) {
                tmp = bus_space_read_4(sc->sc_bst, sc->sc_bsh, clk->gate_addr);
                tmp &= (1 << clk->gate_shift);
                aprint_debug(" Gate %s\n", tmp? "on" : "off");
        }


}

static struct clk *
cycv_clkmgr_clock_decode(device_t dev, int cc_phandle, const void *data,
                         size_t len)
{
        struct cycv_clkmgr_softc *sc = device_private(dev);
        struct cycv_clk *clk;

        if (data != NULL || len != 0) {
                aprint_debug_dev(dev, "can't decode clock entry\n");
                return NULL;
        }

        clk = cycv_clkmgr_clock_lookup_by_id(sc, cc_phandle);

        return clk == NULL? NULL : &clk->base;
}

static struct clk *
cycv_clkmgr_clock_get(void *priv, const char *name)
{
        aprint_debug("%s: called and not implemented\n", __func__);
        (void) cycv_clkmgr_get_mux_info;
        (void) cycv_clkmgr_clock_lookup_by_name;

        return NULL;
}

static void
cycv_clkmgr_clock_put(void *priv, struct clk *clk)
{
        aprint_debug("%s: called and not implemented\n", __func__);
}

static u_int
cycv_clkmgr_clock_get_rate(void *priv, struct clk *base_clk)
{
        struct cycv_clkmgr_softc *sc = priv;
        struct cycv_clk *clk = (struct cycv_clk *) base_clk;
        struct cycv_clk *parent;
        uint32_t parent_rate = 0;
        uint32_t divisor = 0;
        uint32_t numer;
        uint32_t tmp;

        if (clk->type == CYCV_CLK_TYPE_FIXED)
                return clk->u.fixed_freq;

        parent = (struct cycv_clk *)
                cycv_clkmgr_clock_get_parent(priv, base_clk);
        if (parent == NULL) {
                aprint_debug_dev(sc->sc_dev, "can't get parent of clock %s\n",
                                 clk->base.name);
                return 0;
        }
        parent_rate = cycv_clkmgr_clock_get_rate(priv, &parent->base);

        if (strncmp(clk->base.name, "mpuclk@", strlen("mpuclk@")) == 0)
                parent_rate /= 2;
        else if (strncmp(clk->base.name, "mainclk@", strlen("mainclk@")) == 0)
                parent_rate /= 4;
        else if (strncmp(clk->base.name, "dbgatclk@", strlen("dbgatclk@")) == 0)
                parent_rate /= 4;

        switch (clk->type) {
        case CYCV_CLK_TYPE_FIXED_DIV:
                return parent_rate / clk->u.fixed_div;

        case CYCV_CLK_TYPE_DIV:
                divisor = bus_space_read_4(sc->sc_bst, sc->sc_bsh,
                                           clk->u.div.addr);
                divisor = (divisor & clk->u.div.mask) >> clk->u.div.shift;
                if (__SHIFTOUT_MASK(clk->u.div.mask) > 0xf)
                        divisor += 1;
                else
                        divisor = (1 << divisor);

                return parent_rate / divisor;

        case CYCV_CLK_TYPE_PLL:
                tmp = bus_space_read_4(sc->sc_bst, sc->sc_bsh, clk->u.pll_addr);
                numer = __SHIFTOUT(tmp, CYCV_CLKMGR_PLL_VCO_NUMER) + 1;
                divisor = __SHIFTOUT(tmp, CYCV_CLKMGR_PLL_VCO_DENOM) + 1;

                return (uint64_t) parent_rate * numer / divisor;
        }

        aprint_debug_dev(sc->sc_dev, "unknown clock type %d\n", clk->type);

        return 0;
}

static int
cycv_clkmgr_clock_set_rate(void *priv, struct clk *clk, u_int rate)
{
        aprint_debug("%s: called and not implemented\n", __func__);
        return EINVAL;
}

static int
cycv_clkmgr_clock_set(void *priv, struct clk *base_clk, int val)
{
        struct cycv_clkmgr_softc *sc = priv;
        struct cycv_clk *clk = (struct cycv_clk *) base_clk;

        if (clk->flags & CYCV_CLK_FLAG_HAVE_GATE) {
                uint32_t tmp = bus_space_read_4(sc->sc_bst, sc->sc_bsh,
                        clk->gate_addr);
                tmp &= ~(1 << clk->gate_shift);
                tmp |= val << clk->gate_shift;
                bus_space_write_4(sc->sc_bst, sc->sc_bsh, clk->gate_addr, tmp);
        } else
                /* XXX should iterate to the root of the clock domain */
                return 0;

        return 0;
}

static int
cycv_clkmgr_clock_enable(void *priv, struct clk *clk)
{
        return cycv_clkmgr_clock_set(priv, clk, 1);
}

static int
cycv_clkmgr_clock_disable(void *priv, struct clk *clk)
{
        return cycv_clkmgr_clock_set(priv, clk, 0);
}

static int
cycv_clkmgr_clock_set_parent(void *priv, struct clk *clk,
    struct clk *clk_parent)
{
        /* lookup clk in muxinfo table */
        /* if not found, parent is not settable */
        /* check if clk_parent can be a parent (by name) */
        /* beware of special case where there is only one parent in mux */
        /* enact reparenting h/w wise, update clk->parent */
        aprint_debug("%s: called and not implemented\n", __func__);
        return EINVAL;
}

static struct clk *
cycv_clkmgr_clock_get_parent(void *priv, struct clk *base_clk)
{
        struct cycv_clkmgr_softc *sc = priv;
        struct cycv_clk *clk = (struct cycv_clk *) base_clk;
        struct cycv_clk_mux_info *mux;
        struct cycv_clk *parent = clk->parent;
        int parent_index;
        uint32_t tmp;

        if (parent != NULL)
                goto update;

        if (clk->parent_id != 0) {
                parent = cycv_clkmgr_clock_lookup_by_id(sc, clk->parent_id);
                goto update;
        }

        mux = cycv_clkmgr_get_mux_info(clk->base.name);

        if (mux == NULL)
                goto update;

        if (mux->nparents == 1)
                parent_index = 0;
        else {
                tmp = bus_space_read_4(sc->sc_bst, sc->sc_bsh, mux->addr);
                parent_index = __SHIFTOUT(tmp, mux->mask);
        }

        if (parent_index >= mux->nparents) {
                aprint_error_dev(sc->sc_dev,
                                 "clock %s parent has non existent index %d\n",
                                 clk->base.name, parent_index);
                goto update;
        }

        parent = cycv_clkmgr_clock_lookup_by_name(sc,
                                                  mux->parents[parent_index]);

update:
        clk->parent = parent;
        return &parent->base;
}

/*
 * Functions called during early startup, possibly before autoconfiguration.
 */

uint32_t
cycv_clkmgr_early_get_mpu_clk(void)
{
        bus_space_tag_t bst = &armv7_generic_bs_tag;
        bus_space_handle_t bsh;
        uint32_t tmp;
        uint64_t vco;

        bus_space_map(bst, CYCV_CLKMGR_BASE, CYCV_CLKMGR_SIZE, 0, &bsh);

        tmp = bus_space_read_4(bst, bsh, CYCV_CLKMGR_MAIN_PLL_VCO);
        vco = (uint64_t) CYCV_CLOCK_OSC1 *
                (__SHIFTOUT(tmp, CYCV_CLKMGR_PLL_VCO_NUMER) + 1) /
                (__SHIFTOUT(tmp, CYCV_CLKMGR_PLL_VCO_DENOM) + 1);
        tmp = bus_space_read_4(bst, bsh, CYCV_CLKMGR_MAIN_PLL_MPUCLK);

        return vco / 2 / (__SHIFTOUT(tmp, CYCV_CLKMGR_MAIN_PLL_MPUCLK_CNT) + 1);
}

uint32_t
cycv_clkmgr_early_get_l4_sp_clk(void)
{
        bus_space_tag_t bst = &armv7_generic_bs_tag;
        bus_space_handle_t bsh;
        uint32_t tmp;
        uint32_t res;
        uint64_t vco;

        bus_space_map(bst, CYCV_CLKMGR_BASE, CYCV_CLKMGR_SIZE, 0, &bsh);

        tmp = bus_space_read_4(bst, bsh, CYCV_CLKMGR_MAIN_PLL_L4SRC);
        if (__SHIFTOUT(tmp, CYCV_CLKMGR_MAIN_PLL_L4SRC_L4SP) == 0) {
                /* L4 SP clock driven by main clock */
                vco = (uint64_t) CYCV_CLOCK_OSC1 *
                        (__SHIFTOUT(tmp, CYCV_CLKMGR_PLL_VCO_NUMER) + 1) /
                        (__SHIFTOUT(tmp, CYCV_CLKMGR_PLL_VCO_DENOM) + 1);
                tmp = bus_space_read_4(bst, bsh, CYCV_CLKMGR_MAIN_PLL_MAINCLK);
                tmp = __SHIFTOUT(tmp, CYCV_CLKMGR_MAIN_PLL_MAINCLK_CNT);

                res = vco / 4 / (tmp + 1);
        } else {
                /* L4 SP clock driven by periph clock */
                vco = (uint64_t) CYCV_CLOCK_OSC1 *
                        (__SHIFTOUT(tmp, CYCV_CLKMGR_PLL_VCO_NUMER) + 1) /
                        (__SHIFTOUT(tmp, CYCV_CLKMGR_PLL_VCO_DENOM) + 1);
                tmp = bus_space_read_4(bst, bsh,
                        CYCV_CLKMGR_PERI_PLL_PERBASECLK);
                tmp = __SHIFTOUT(tmp, CYCV_CLKMGR_PERI_PLL_PERBASECLK_CNT);

                res = vco / (tmp + 1);
        }

        tmp = bus_space_read_4(bst, bsh, CYCV_CLKMGR_MAIN_PLL_MAINDIV);
        tmp = __SHIFTOUT(tmp, CYCV_CLKMGR_MAIN_PLL_MAINDIV_L4SP);

        printf("%s: returning %u\n", __func__, (uint32_t)
                (res / (1 << tmp)));
        return res / (1 << tmp);
}