root/sys/dev/clk/clk.c
/*-
 * Copyright 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>
#include "opt_platform.h"
#include <sys/param.h>
#include <sys/conf.h>
#include <sys/bus.h>
#include <sys/kernel.h>
#include <sys/queue.h>
#include <sys/kobj.h>
#include <sys/malloc.h>
#include <sys/mutex.h>
#include <sys/limits.h>
#include <sys/lock.h>
#include <sys/sbuf.h>
#include <sys/sysctl.h>
#include <sys/systm.h>
#include <sys/sx.h>

#ifdef FDT
#include <dev/fdt/fdt_common.h>
#include <dev/ofw/ofw_bus.h>
#include <dev/ofw/ofw_bus_subr.h>
#endif
#include <dev/clk/clk.h>

SYSCTL_NODE(_hw, OID_AUTO, clock, CTLFLAG_RD | CTLFLAG_MPSAFE, NULL,
    "Clocks");

MALLOC_DEFINE(M_CLOCK, "clocks", "Clock framework");

/* Forward declarations. */
struct clk;
struct clknodenode;
struct clkdom;

typedef TAILQ_HEAD(clknode_list, clknode) clknode_list_t;
typedef TAILQ_HEAD(clkdom_list, clkdom) clkdom_list_t;

/* Default clock methods. */
static int clknode_method_init(struct clknode *clk, device_t dev);
static int clknode_method_recalc_freq(struct clknode *clk, uint64_t *freq);
static int clknode_method_set_freq(struct clknode *clk, uint64_t fin,
    uint64_t *fout, int flags, int *stop);
static int clknode_method_set_gate(struct clknode *clk, bool enable);
static int clknode_method_set_mux(struct clknode *clk, int idx);

/*
 * Clock controller methods.
 */
static clknode_method_t clknode_methods[] = {
        CLKNODEMETHOD(clknode_init,             clknode_method_init),
        CLKNODEMETHOD(clknode_recalc_freq,      clknode_method_recalc_freq),
        CLKNODEMETHOD(clknode_set_freq,         clknode_method_set_freq),
        CLKNODEMETHOD(clknode_set_gate,         clknode_method_set_gate),
        CLKNODEMETHOD(clknode_set_mux,          clknode_method_set_mux),

        CLKNODEMETHOD_END
};
DEFINE_CLASS_0(clknode, clknode_class, clknode_methods, 0);

/*
 * Clock node - basic element for modeling SOC clock graph.  It holds the clock
 * provider's data about the clock, and the links for the clock's membership in
 * various lists.
 */
struct clknode {
        KOBJ_FIELDS;

        /* Clock nodes topology. */
        struct clkdom           *clkdom;        /* Owning clock domain */
        TAILQ_ENTRY(clknode)    clkdom_link;    /* Domain list entry */
        TAILQ_ENTRY(clknode)    clklist_link;   /* Global list entry */

        /* String based parent list. */
        const char              **parent_names; /* Array of parent names */
        int                     parent_cnt;     /* Number of parents */
        int                     parent_idx;     /* Parent index or -1 */

        /* Cache for already resolved names. */
        struct clknode          **parents;      /* Array of potential parents */
        struct clknode          *parent;        /* Current parent */

        /* Parent/child relationship links. */
        clknode_list_t          children;       /* List of our children */
        TAILQ_ENTRY(clknode)    sibling_link;   /* Our entry in parent's list */

        /* Details of this device. */
        void                    *softc;         /* Instance softc */
        const char              *name;          /* Globally unique name */
        intptr_t                id;             /* Per domain unique id */
        int                     flags;          /* CLK_FLAG_*  */
        struct sx               lock;           /* Lock for this clock */
        int                     ref_cnt;        /* Reference counter */
        int                     enable_cnt;     /* Enabled counter */

        /* Cached values. */
        uint64_t                freq;           /* Actual frequency */

        struct sysctl_ctx_list  sysctl_ctx;
};

/*
 *  Per consumer data, information about how a consumer is using a clock node.
 *  A pointer to this structure is used as a handle in the consumer interface.
 */
struct clk {
        device_t                dev;
        struct clknode          *clknode;
        int                     enable_cnt;
};

/*
 * Clock domain - a group of clocks provided by one clock device.
 */
struct clkdom {
        device_t                dev;    /* Link to provider device */
        TAILQ_ENTRY(clkdom)     link;           /* Global domain list entry */
        clknode_list_t          clknode_list;   /* All clocks in the domain */

#ifdef FDT
        clknode_ofw_mapper_func *ofw_mapper;    /* Find clock using FDT xref */
#endif
};

/*
 * The system-wide list of clock domains.
 */
static clkdom_list_t clkdom_list = TAILQ_HEAD_INITIALIZER(clkdom_list);

/*
 * Each clock node is linked on a system-wide list and can be searched by name.
 */
static clknode_list_t clknode_list = TAILQ_HEAD_INITIALIZER(clknode_list);

/*
 * Locking - we use three levels of locking:
 * - First, topology lock is taken.  This one protect all lists.
 * - Second level is per clknode lock.  It protects clknode data.
 * - Third level is outside of this file, it protect clock device registers.
 * First two levels use sleepable locks; clock device can use mutex or sx lock.
 */
static struct sx                clk_topo_lock;
SX_SYSINIT(clock_topology, &clk_topo_lock, "Clock topology lock");

#define CLK_TOPO_SLOCK()        sx_slock(&clk_topo_lock)
#define CLK_TOPO_XLOCK()        sx_xlock(&clk_topo_lock)
#define CLK_TOPO_UNLOCK()       sx_unlock(&clk_topo_lock)
#define CLK_TOPO_ASSERT()       sx_assert(&clk_topo_lock, SA_LOCKED)
#define CLK_TOPO_XASSERT()      sx_assert(&clk_topo_lock, SA_XLOCKED)

#define CLKNODE_SLOCK(_sc)      sx_slock(&((_sc)->lock))
#define CLKNODE_XLOCK(_sc)      sx_xlock(&((_sc)->lock))
#define CLKNODE_UNLOCK(_sc)     sx_unlock(&((_sc)->lock))

static void clknode_adjust_parent(struct clknode *clknode, int idx);

enum clknode_sysctl_type {
        CLKNODE_SYSCTL_PARENT,
        CLKNODE_SYSCTL_PARENTS_LIST,
        CLKNODE_SYSCTL_CHILDREN_LIST,
        CLKNODE_SYSCTL_FREQUENCY,
        CLKNODE_SYSCTL_GATE,
};

static int clknode_sysctl(SYSCTL_HANDLER_ARGS);
static int clkdom_sysctl(SYSCTL_HANDLER_ARGS);

static void clknode_finish(void *dummy);
SYSINIT(clknode_finish, SI_SUB_LAST, SI_ORDER_ANY, clknode_finish, NULL);

/*
 * Default clock methods for base class.
 */
static int
clknode_method_init(struct clknode *clknode, device_t dev)
{

        return (0);
}

static int
clknode_method_recalc_freq(struct clknode *clknode, uint64_t *freq)
{

        return (0);
}

static int
clknode_method_set_freq(struct clknode *clknode, uint64_t fin,  uint64_t *fout,
   int flags, int *stop)
{

        *stop = 0;
        return (0);
}

static int
clknode_method_set_gate(struct clknode *clk, bool enable)
{

        return (0);
}

static int
clknode_method_set_mux(struct clknode *clk, int idx)
{

        return (0);
}

/*
 * Internal functions.
 */

/*
 * Duplicate an array of parent names.
 *
 * Compute total size and allocate a single block which holds both the array of
 * pointers to strings and the copied strings themselves.  Returns a pointer to
 * the start of the block where the array of copied string pointers lives.
 *
 * XXX Revisit this, no need for the DECONST stuff.
 */
static const char **
strdup_list(const char **names, int num)
{
        size_t len, slen;
        const char **outptr, *ptr;
        int i;

        len = sizeof(char *) * num;
        for (i = 0; i < num; i++) {
                if (names[i] == NULL)
                        continue;
                slen = strlen(names[i]);
                if (slen == 0)
                        panic("Clock parent names array have empty string");
                len += slen + 1;
        }
        outptr = malloc(len, M_CLOCK, M_WAITOK | M_ZERO);
        ptr = (char *)(outptr + num);
        for (i = 0; i < num; i++) {
                if (names[i] == NULL)
                        continue;
                outptr[i] = ptr;
                slen = strlen(names[i]) + 1;
                bcopy(names[i], __DECONST(void *, outptr[i]), slen);
                ptr += slen;
        }
        return (outptr);
}

/*
 * Recompute the cached frequency for this node and all its children.
 */
static int
clknode_refresh_cache(struct clknode *clknode, uint64_t freq)
{
        int rv;
        struct clknode *entry;

        CLK_TOPO_XASSERT();

        /* Compute generated frequency. */
        rv = CLKNODE_RECALC_FREQ(clknode, &freq);
        if (rv != 0) {
                 /* XXX If an error happens while refreshing children
                  * this leaves the world in a  partially-updated state.
                  * Panic for now.
                  */
                panic("clknode_refresh_cache failed for '%s'\n",
                    clknode->name);
                return (rv);
        }
        /* Refresh cache for this node. */
        clknode->freq = freq;

        /* Refresh cache for all children. */
        TAILQ_FOREACH(entry, &(clknode->children), sibling_link) {
                rv = clknode_refresh_cache(entry, freq);
                if (rv != 0)
                        return (rv);
        }
        return (0);
}

/*
 * Public interface.
 */

struct clknode *
clknode_find_by_name(const char *name)
{
        struct clknode *entry;

        CLK_TOPO_ASSERT();

        TAILQ_FOREACH(entry, &clknode_list, clklist_link) {
                if (strcmp(entry->name, name) == 0)
                        return (entry);
        }
        return (NULL);
}

struct clknode *
clknode_find_by_id(struct clkdom *clkdom, intptr_t id)
{
        struct clknode *entry;

        CLK_TOPO_ASSERT();

        TAILQ_FOREACH(entry, &clkdom->clknode_list, clkdom_link) {
                if (entry->id ==  id)
                        return (entry);
        }

        return (NULL);
}

/* -------------------------------------------------------------------------- */
/*
 * Clock domain functions
 */

/* Find clock domain associated to device in global list. */
struct clkdom *
clkdom_get_by_dev(const device_t dev)
{
        struct clkdom *entry;

        CLK_TOPO_ASSERT();

        TAILQ_FOREACH(entry, &clkdom_list, link) {
                if (entry->dev == dev)
                        return (entry);
        }
        return (NULL);
}


#ifdef FDT
/* Default DT mapper. */
static int
clknode_default_ofw_map(struct clkdom *clkdom, uint32_t ncells,
    phandle_t *cells, struct clknode **clk)
{

        CLK_TOPO_ASSERT();

        if (ncells == 0)
                *clk = clknode_find_by_id(clkdom, 1);
        else if (ncells == 1)
                *clk = clknode_find_by_id(clkdom, cells[0]);
        else
                return  (ERANGE);

        if (*clk == NULL)
                return (ENXIO);
        return (0);
}
#endif

/*
 * Create a clock domain.  Returns with the topo lock held.
 */
struct clkdom *
clkdom_create(device_t dev)
{
        struct clkdom *clkdom;

        clkdom = malloc(sizeof(struct clkdom), M_CLOCK, M_WAITOK | M_ZERO);
        clkdom->dev = dev;
        TAILQ_INIT(&clkdom->clknode_list);
#ifdef FDT
        clkdom->ofw_mapper = clknode_default_ofw_map;
#endif

        SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev),
            SYSCTL_CHILDREN(device_get_sysctl_tree(dev)),
            OID_AUTO, "clocks",
            CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE,
            clkdom, 0, clkdom_sysctl, "A",
            "Clock list for the domain");

        return (clkdom);
}

void
clkdom_unlock(struct clkdom *clkdom)
{

        CLK_TOPO_UNLOCK();
}

void
clkdom_xlock(struct clkdom *clkdom)
{

        CLK_TOPO_XLOCK();
}

/*
 * Finalize initialization of clock domain.  Releases topo lock.
 *
 * XXX Revisit failure handling.
 */
int
clkdom_finit(struct clkdom *clkdom)
{
        struct clknode *clknode;
        int i, rv;
#ifdef FDT
        phandle_t node;


        if ((node = ofw_bus_get_node(clkdom->dev)) == -1) {
                device_printf(clkdom->dev,
                    "%s called on not ofw based device\n", __func__);
                return (ENXIO);
        }
#endif
        rv = 0;

        /* Make clock domain globally visible. */
        CLK_TOPO_XLOCK();
        TAILQ_INSERT_TAIL(&clkdom_list, clkdom, link);
#ifdef FDT
        OF_device_register_xref(OF_xref_from_node(node), clkdom->dev);
#endif

        /* Register all clock names into global list. */
        TAILQ_FOREACH(clknode, &clkdom->clknode_list, clkdom_link) {
                TAILQ_INSERT_TAIL(&clknode_list, clknode, clklist_link);
        }
        /*
         * At this point all domain nodes must be registered and all
         * parents must be valid.
         */
        TAILQ_FOREACH(clknode, &clkdom->clknode_list, clkdom_link) {
                if (clknode->parent_cnt == 0)
                        continue;
                for (i = 0; i < clknode->parent_cnt; i++) {
                        if (clknode->parents[i] != NULL)
                                continue;
                        if (clknode->parent_names[i] == NULL)
                                continue;
                        clknode->parents[i] = clknode_find_by_name(
                            clknode->parent_names[i]);
                        if (clknode->parents[i] == NULL) {
                                device_printf(clkdom->dev,
                                    "Clock %s have unknown parent: %s\n",
                                    clknode->name, clknode->parent_names[i]);
                                rv = ENODEV;
                        }
                }

                /* If parent index is not set yet... */
                if (clknode->parent_idx == CLKNODE_IDX_NONE) {
                        device_printf(clkdom->dev,
                            "Clock %s have not set parent idx\n",
                            clknode->name);
                        rv = ENXIO;
                        continue;
                }
                if (clknode->parents[clknode->parent_idx] == NULL) {
                        device_printf(clkdom->dev,
                            "Clock %s have unknown parent(idx %d): %s\n",
                            clknode->name, clknode->parent_idx,
                            clknode->parent_names[clknode->parent_idx]);
                        rv = ENXIO;
                        continue;
                }
                clknode_adjust_parent(clknode, clknode->parent_idx);
        }
        CLK_TOPO_UNLOCK();
        return (rv);
}

/* Dump clock domain. */
void
clkdom_dump(struct clkdom * clkdom)
{
        struct clknode *clknode;
        int rv;
        uint64_t freq;

        CLK_TOPO_SLOCK();
        TAILQ_FOREACH(clknode, &clkdom->clknode_list, clkdom_link) {
                rv = clknode_get_freq(clknode, &freq);
                if (rv != 0) {
                        printf("Clock: %s, error getting frequency: %d\n",
                            clknode->name, rv);
                        continue;
                }

                if (clknode->parent != NULL) {
                        printf("Clock: %s, parent: %s(%d), freq: %ju\n",
                            clknode->name, clknode->parent->name,
                            clknode->parent_idx, (uintmax_t)freq);
                } else {
                        printf("Clock: %s, parent: none, freq: %ju\n",
                            clknode->name, (uintmax_t)freq);
                }
        }
        CLK_TOPO_UNLOCK();
}

/*
 * Create and initialize clock object, but do not register it.
 */
struct clknode *
clknode_create(struct clkdom * clkdom, clknode_class_t clknode_class,
    const struct clknode_init_def *def)
{
        struct clknode *clknode;
        struct sysctl_oid *clknode_oid;
        bool replaced;
        kobjop_desc_t kobj_desc;
        kobj_method_t *kobj_method;

        KASSERT(def->name != NULL, ("clock name is NULL"));
        KASSERT(def->name[0] != '\0', ("clock name is empty"));
        if (def->flags & CLK_NODE_LINKED) {
                KASSERT(def->parent_cnt == 0,
                 ("Linked clock must not have parents"));
                KASSERT(clknode_class->size== 0,
                 ("Linked clock cannot have own softc"));
        }

        /* Process duplicated clocks */
        CLK_TOPO_SLOCK();
        clknode = clknode_find_by_name(def->name);
        CLK_TOPO_UNLOCK();
        if (clknode !=  NULL) {
                if (!(clknode->flags & CLK_NODE_LINKED) &&
                    def->flags & CLK_NODE_LINKED) {
                        /*
                         * New clock is linked and real already exists.
                         * Do nothing and return real node. It is in right
                         * domain, enqueued in right lists and fully initialized.
                         */
                        return (clknode);
                } else if (clknode->flags & CLK_NODE_LINKED &&
                   !(def->flags & CLK_NODE_LINKED)) {
                        /*
                         * New clock is real but linked already exists.
                         * Remove old linked node from originating domain
                         * (real clock must be owned by another) and from
                         * global names link (it will be added back into it
                         * again in following clknode_register()). Then reuse
                         * original clknode structure and reinitialize it
                         * with new dat. By this, all lists containing this
                         * node remains valid, but the new node virtually
                         * replace the linked one.
                         */
                        KASSERT(clkdom != clknode->clkdom,
                            ("linked clock must be from another "
                            "domain that real one"));
                        TAILQ_REMOVE(&clkdom->clknode_list, clknode,
                            clkdom_link);
                        TAILQ_REMOVE(&clknode_list, clknode, clklist_link);
                        replaced = true;
                } else if (clknode->flags & CLK_NODE_LINKED &&
                   def->flags & CLK_NODE_LINKED) {
                        /*
                         * Both clocks are linked.
                         * Return old one, so we hold only one copy od link.
                         */
                        return (clknode);
                } else {
                        /* Both clocks are real */
                        panic("Duplicated clock registration: %s\n", def->name);
                }
        } else {
                /* Create clknode object and initialize it. */
                clknode = malloc(sizeof(struct clknode), M_CLOCK,
                    M_WAITOK | M_ZERO);
                sx_init(&clknode->lock, "Clocknode lock");
                TAILQ_INIT(&clknode->children);
                replaced = false;
        }

        kobj_init((kobj_t)clknode, (kobj_class_t)clknode_class);

        /* Allocate softc if required. */
        if (clknode_class->size > 0) {
                clknode->softc = malloc(clknode_class->size,
                    M_CLOCK, M_WAITOK | M_ZERO);
        }

        /* Prepare array for ptrs to parent clocks. */
        clknode->parents = malloc(sizeof(struct clknode *) * def->parent_cnt,
            M_CLOCK, M_WAITOK | M_ZERO);

        /* Copy all strings unless they're flagged as static. */
        if (def->flags & CLK_NODE_STATIC_STRINGS) {
                clknode->name = def->name;
                clknode->parent_names = def->parent_names;
        } else {
                clknode->name = strdup(def->name, M_CLOCK);
                clknode->parent_names =
                    strdup_list(def->parent_names, def->parent_cnt);
        }

        /* Rest of init. */
        clknode->id = def->id;
        clknode->clkdom = clkdom;
        clknode->flags = def->flags;
        clknode->parent_cnt = def->parent_cnt;
        clknode->parent = NULL;
        clknode->parent_idx = CLKNODE_IDX_NONE;

        if (replaced)
                return (clknode);

        sysctl_ctx_init(&clknode->sysctl_ctx);
        clknode_oid = SYSCTL_ADD_NODE(&clknode->sysctl_ctx,
            SYSCTL_STATIC_CHILDREN(_hw_clock),
            OID_AUTO, clknode->name,
            CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "A clock node");

        SYSCTL_ADD_PROC(&clknode->sysctl_ctx,
            SYSCTL_CHILDREN(clknode_oid),
            OID_AUTO, "frequency",
            CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE,
            clknode, CLKNODE_SYSCTL_FREQUENCY, clknode_sysctl,
            "A",
            "The clock frequency");

        /* Install gate handler only if clknode have 'set_gate' method */
        kobj_desc = &clknode_set_gate_desc;
        kobj_method = kobj_lookup_method(((kobj_t)clknode)->ops->cls, NULL,
            kobj_desc);
        if (kobj_method != &kobj_desc->deflt &&
            kobj_method->func != (kobjop_t)clknode_method_set_gate) {
                SYSCTL_ADD_PROC(&clknode->sysctl_ctx,
                    SYSCTL_CHILDREN(clknode_oid),
                    OID_AUTO, "gate",
                    CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE,
                    clknode, CLKNODE_SYSCTL_GATE, clknode_sysctl,
                    "A",
                    "The clock gate status");
        }

        SYSCTL_ADD_PROC(&clknode->sysctl_ctx,
            SYSCTL_CHILDREN(clknode_oid),
            OID_AUTO, "parent",
            CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE,
            clknode, CLKNODE_SYSCTL_PARENT, clknode_sysctl,
            "A",
            "The clock parent");
        SYSCTL_ADD_PROC(&clknode->sysctl_ctx,
            SYSCTL_CHILDREN(clknode_oid),
            OID_AUTO, "parents",
            CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE,
            clknode, CLKNODE_SYSCTL_PARENTS_LIST, clknode_sysctl,
            "A",
            "The clock parents list");
        SYSCTL_ADD_PROC(&clknode->sysctl_ctx,
            SYSCTL_CHILDREN(clknode_oid),
            OID_AUTO, "childrens",
            CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE,
            clknode, CLKNODE_SYSCTL_CHILDREN_LIST, clknode_sysctl,
            "A",
            "The clock childrens list");
        SYSCTL_ADD_INT(&clknode->sysctl_ctx,
            SYSCTL_CHILDREN(clknode_oid),
            OID_AUTO, "enable_cnt",
            CTLFLAG_RD, &clknode->enable_cnt, 0, "The clock enable counter");

        return (clknode);
}

/*
 * Register clock object into clock domain hierarchy.
 */
struct clknode *
clknode_register(struct clkdom * clkdom, struct clknode *clknode)
{
        int rv;

        /* Skip already registered linked node */
        if (clknode->flags & CLK_NODE_REGISTERED)
                return(clknode);

        rv = CLKNODE_INIT(clknode, clknode_get_device(clknode));
        if (rv != 0) {
                printf(" CLKNODE_INIT failed: %d\n", rv);
                return (NULL);
        }

        TAILQ_INSERT_TAIL(&clkdom->clknode_list, clknode, clkdom_link);
        clknode->flags |= CLK_NODE_REGISTERED;
        return (clknode);
}


static void
clknode_finish(void *dummy)
{
        struct clknode *clknode;

        CLK_TOPO_SLOCK();
        TAILQ_FOREACH(clknode, &clknode_list, clklist_link) {
                if (clknode->flags & CLK_NODE_LINKED)
                        printf("Unresolved linked clock found: %s\n",
                            clknode->name);
        }
        CLK_TOPO_UNLOCK();
}
/*
 * Clock providers interface.
 */

/*
 * Reparent clock node.
 */
static void
clknode_adjust_parent(struct clknode *clknode, int idx)
{

        CLK_TOPO_XASSERT();

        if (clknode->parent_cnt == 0)
                return;
        if ((idx == CLKNODE_IDX_NONE) || (idx >= clknode->parent_cnt))
                panic("%s: Invalid parent index %d for clock %s",
                    __func__, idx, clknode->name);

        if (clknode->parents[idx] == NULL)
                panic("%s: Invalid parent index %d for clock %s",
                    __func__, idx, clknode->name);

        /* Remove me from old children list. */
        if (clknode->parent != NULL) {
                TAILQ_REMOVE(&clknode->parent->children, clknode, sibling_link);
        }

        /* Insert into children list of new parent. */
        clknode->parent_idx = idx;
        clknode->parent = clknode->parents[idx];
        TAILQ_INSERT_TAIL(&clknode->parent->children, clknode, sibling_link);
}

/*
 * Set parent index - init function.
 */
void
clknode_init_parent_idx(struct clknode *clknode, int idx)
{

        if (clknode->parent_cnt == 0) {
                clknode->parent_idx = CLKNODE_IDX_NONE;
                clknode->parent = NULL;
                return;
        }
        if ((idx == CLKNODE_IDX_NONE) ||
            (idx >= clknode->parent_cnt) ||
            (clknode->parent_names[idx] == NULL))
                panic("%s: Invalid parent index %d for clock %s",
                    __func__, idx, clknode->name);
        clknode->parent_idx = idx;
}

int
clknode_set_parent_by_idx(struct clknode *clknode, int idx)
{
        int rv;
        uint64_t freq;
        int  oldidx;

        /* We have exclusive topology lock, node lock is not needed. */
        CLK_TOPO_XASSERT();

        if (clknode->parent_cnt == 0)
                return (0);

        if (clknode->parent_idx == idx)
                return (0);

        oldidx = clknode->parent_idx;
        clknode_adjust_parent(clknode, idx);
        rv = CLKNODE_SET_MUX(clknode, idx);
        if (rv != 0) {
                clknode_adjust_parent(clknode, oldidx);
                return (rv);
        }
        rv = clknode_get_freq(clknode->parent, &freq);
        if (rv != 0)
                return (rv);
        rv = clknode_refresh_cache(clknode, freq);
        return (rv);
}

int
clknode_set_parent_by_name(struct clknode *clknode, const char *name)
{
        int rv;
        uint64_t freq;
        int  oldidx, idx;

        /* We have exclusive topology lock, node lock is not needed. */
        CLK_TOPO_XASSERT();

        if (clknode->parent_cnt == 0)
                return (0);

        /*
         * If this node doesnt have mux, then passthrough request to parent.
         * This feature is used in clock domain initialization and allows us to
         * set clock source and target frequency on the tail node of the clock
         * chain.
         */
        if (clknode->parent_cnt == 1) {
                rv = clknode_set_parent_by_name(clknode->parent, name);
                return (rv);
        }

        for (idx = 0; idx < clknode->parent_cnt; idx++) {
                if (clknode->parent_names[idx] == NULL)
                        continue;
                if (strcmp(clknode->parent_names[idx], name) == 0)
                        break;
        }
        if (idx >= clknode->parent_cnt) {
                return (ENXIO);
        }
        if (clknode->parent_idx == idx)
                return (0);

        oldidx = clknode->parent_idx;
        clknode_adjust_parent(clknode, idx);
        rv = CLKNODE_SET_MUX(clknode, idx);
        if (rv != 0) {
                clknode_adjust_parent(clknode, oldidx);
                CLKNODE_UNLOCK(clknode);
                return (rv);
        }
        rv = clknode_get_freq(clknode->parent, &freq);
        if (rv != 0)
                return (rv);
        rv = clknode_refresh_cache(clknode, freq);
        return (rv);
}

struct clknode *
clknode_get_parent(struct clknode *clknode)
{

        return (clknode->parent);
}

const char *
clknode_get_name(struct clknode *clknode)
{

        return (clknode->name);
}

const char **
clknode_get_parent_names(struct clknode *clknode)
{

        return (clknode->parent_names);
}

int
clknode_get_parents_num(struct clknode *clknode)
{

        return (clknode->parent_cnt);
}

int
clknode_get_parent_idx(struct clknode *clknode)
{

        return (clknode->parent_idx);
}

int
clknode_get_flags(struct clknode *clknode)
{

        return (clknode->flags);
}


void *
clknode_get_softc(struct clknode *clknode)
{

        return (clknode->softc);
}

device_t
clknode_get_device(struct clknode *clknode)
{

        return (clknode->clkdom->dev);
}

#ifdef FDT
void
clkdom_set_ofw_mapper(struct clkdom * clkdom, clknode_ofw_mapper_func *map)
{

        clkdom->ofw_mapper = map;
}
#endif

/*
 * Real consumers executive
 */
int
clknode_get_freq(struct clknode *clknode, uint64_t *freq)
{
        int rv;

        CLK_TOPO_ASSERT();

        /* Use cached value, if it exists. */
        *freq  = clknode->freq;
        if (*freq != 0)
                return (0);

        /* Get frequency from parent, if the clock has a parent. */
        if (clknode->parent_cnt > 0) {
                rv = clknode_get_freq(clknode->parent, freq);
                if (rv != 0) {
                        return (rv);
                }
        }

        /* And recalculate my output frequency. */
        CLKNODE_XLOCK(clknode);
        rv = CLKNODE_RECALC_FREQ(clknode, freq);
        if (rv != 0) {
                CLKNODE_UNLOCK(clknode);
                printf("Cannot get frequency for clk: %s, error: %d\n",
                    clknode->name, rv);
                return (rv);
        }

        /* Save new frequency to cache. */
        clknode->freq = *freq;
        CLKNODE_UNLOCK(clknode);
        return (0);
}

static int
_clknode_set_freq(struct clknode *clknode, uint64_t *freq, int flags,
    int enablecnt)
{
        int rv, done;
        uint64_t parent_freq;

        /* We have exclusive topology lock, node lock is not needed. */
        CLK_TOPO_XASSERT();

        /* Check for no change */
        if (clknode->freq == *freq)
                return (0);

        parent_freq = 0;

        /*
         * We can set frequency only if
         *   clock is disabled
         * OR
         *   clock is glitch free and is enabled by calling consumer only
         */
        if ((flags & CLK_SET_DRYRUN) == 0 &&
            clknode->enable_cnt > 1 &&
            clknode->enable_cnt > enablecnt &&
            (clknode->flags & CLK_NODE_GLITCH_FREE) == 0) {
                return (EBUSY);
        }

        /* Get frequency from parent, if the clock has a parent. */
        if (clknode->parent_cnt > 0) {
                rv = clknode_get_freq(clknode->parent, &parent_freq);
                if (rv != 0) {
                        return (rv);
                }
        }

        /* Set frequency for this clock. */
        rv = CLKNODE_SET_FREQ(clknode, parent_freq, freq, flags, &done);
        if (rv != 0) {
                printf("Cannot set frequency for clk: %s, error: %d\n",
                    clknode->name, rv);
                if ((flags & CLK_SET_DRYRUN) == 0)
                        clknode_refresh_cache(clknode, parent_freq);
                return (rv);
        }

        if (done) {
                /* Success - invalidate frequency cache for all children. */
                if ((flags & CLK_SET_DRYRUN) == 0) {
                        clknode->freq = *freq;
                        /* Clock might have reparent during set_freq */
                        if (clknode->parent_cnt > 0) {
                                rv = clknode_get_freq(clknode->parent,
                                    &parent_freq);
                                if (rv != 0) {
                                        return (rv);
                                }
                        }
                        clknode_refresh_cache(clknode, parent_freq);
                }
        } else if (clknode->parent != NULL) {
                /* Nothing changed, pass request to parent. */
                rv = _clknode_set_freq(clknode->parent, freq, flags,
                    enablecnt);
        } else {
                /* End of chain without action. */
                printf("Cannot set frequency for clk: %s, end of chain\n",
                    clknode->name);
                rv = ENXIO;
        }

        return (rv);
}

int
clknode_set_freq(struct clknode *clknode, uint64_t freq, int flags,
    int enablecnt)
{

        return (_clknode_set_freq(clknode, &freq, flags, enablecnt));
}

int
clknode_test_freq(struct clknode *clknode, uint64_t freq, int flags,
    int enablecnt, uint64_t *out_freq)
{
        int rv;

        rv = _clknode_set_freq(clknode, &freq, flags | CLK_SET_DRYRUN,
            enablecnt);
        if (out_freq != NULL)
                *out_freq = freq;

        return (rv);
}

int
clknode_enable(struct clknode *clknode)
{
        int rv;

        CLK_TOPO_ASSERT();

        /* Enable clock for each node in chain, starting from source. */
        if (clknode->parent_cnt > 0) {
                rv = clknode_enable(clknode->parent);
                if (rv != 0) {
                        return (rv);
                }
        }

        /* Handle this node */
        CLKNODE_XLOCK(clknode);
        if (clknode->enable_cnt == 0) {
                rv = CLKNODE_SET_GATE(clknode, 1);
                if (rv != 0) {
                        CLKNODE_UNLOCK(clknode);
                        return (rv);
                }
        }
        clknode->enable_cnt++;
        CLKNODE_UNLOCK(clknode);
        return (0);
}

int
clknode_disable(struct clknode *clknode)
{
        int rv;

        CLK_TOPO_ASSERT();
        rv = 0;

        CLKNODE_XLOCK(clknode);
        /* Disable clock for each node in chain, starting from consumer. */
        if ((clknode->enable_cnt == 1) &&
            ((clknode->flags & CLK_NODE_CANNOT_STOP) == 0)) {
                rv = CLKNODE_SET_GATE(clknode, 0);
                if (rv != 0) {
                        CLKNODE_UNLOCK(clknode);
                        return (rv);
                }
        }
        clknode->enable_cnt--;
        CLKNODE_UNLOCK(clknode);

        if (clknode->parent_cnt > 0) {
                rv = clknode_disable(clknode->parent);
        }
        return (rv);
}

int
clknode_stop(struct clknode *clknode, int depth)
{
        int rv;

        CLK_TOPO_ASSERT();
        rv = 0;

        CLKNODE_XLOCK(clknode);
        /* The first node cannot be enabled. */
        if ((clknode->enable_cnt != 0) && (depth == 0)) {
                CLKNODE_UNLOCK(clknode);
                return (EBUSY);
        }
        /* Stop clock for each node in chain, starting from consumer. */
        if ((clknode->enable_cnt == 0) &&
            ((clknode->flags & CLK_NODE_CANNOT_STOP) == 0)) {
                rv = CLKNODE_SET_GATE(clknode, 0);
                if (rv != 0) {
                        CLKNODE_UNLOCK(clknode);
                        return (rv);
                }
        }
        CLKNODE_UNLOCK(clknode);

        if (clknode->parent_cnt > 0)
                rv = clknode_stop(clknode->parent, depth + 1);
        return (rv);
}

/* --------------------------------------------------------------------------
 *
 * Clock consumers interface.
 *
 */
/* Helper function for clk_get*() */
static clk_t
clk_create(struct clknode *clknode, device_t dev)
{
        struct clk *clk;

        CLK_TOPO_ASSERT();

        clk =  malloc(sizeof(struct clk), M_CLOCK, M_WAITOK);
        clk->dev = dev;
        clk->clknode = clknode;
        clk->enable_cnt = 0;
        clknode->ref_cnt++;

        return (clk);
}

int
clk_get_freq(clk_t clk, uint64_t *freq)
{
        int rv;
        struct clknode *clknode;

        clknode = clk->clknode;
        KASSERT(clknode->ref_cnt > 0,
           ("Attempt to access unreferenced clock: %s\n", clknode->name));

        CLK_TOPO_SLOCK();
        rv = clknode_get_freq(clknode, freq);
        CLK_TOPO_UNLOCK();
        return (rv);
}

int
clk_set_freq(clk_t clk, uint64_t freq, int flags)
{
        int rv;
        struct clknode *clknode;

        flags &= CLK_SET_USER_MASK;
        clknode = clk->clknode;
        KASSERT(clknode->ref_cnt > 0,
           ("Attempt to access unreferenced clock: %s\n", clknode->name));

        CLK_TOPO_XLOCK();
        rv = clknode_set_freq(clknode, freq, flags, clk->enable_cnt);
        CLK_TOPO_UNLOCK();
        return (rv);
}

int
clk_test_freq(clk_t clk, uint64_t freq, int flags)
{
        int rv;
        struct clknode *clknode;

        flags &= CLK_SET_USER_MASK;
        clknode = clk->clknode;
        KASSERT(clknode->ref_cnt > 0,
           ("Attempt to access unreferenced clock: %s\n", clknode->name));

        CLK_TOPO_XLOCK();
        rv = clknode_set_freq(clknode, freq, flags | CLK_SET_DRYRUN, 0);
        CLK_TOPO_UNLOCK();
        return (rv);
}

int
clk_get_parent(clk_t clk, clk_t *parent)
{
        struct clknode *clknode;
        struct clknode *parentnode;

        clknode = clk->clknode;
        KASSERT(clknode->ref_cnt > 0,
           ("Attempt to access unreferenced clock: %s\n", clknode->name));

        CLK_TOPO_SLOCK();
        parentnode = clknode_get_parent(clknode);
        if (parentnode == NULL) {
                CLK_TOPO_UNLOCK();
                return (ENODEV);
        }
        *parent = clk_create(parentnode, clk->dev);
        CLK_TOPO_UNLOCK();
        return (0);
}

int
clk_set_parent_by_clk(clk_t clk, clk_t parent)
{
        int rv;
        struct clknode *clknode;
        struct clknode *parentnode;

        clknode = clk->clknode;
        parentnode = parent->clknode;
        KASSERT(clknode->ref_cnt > 0,
           ("Attempt to access unreferenced clock: %s\n", clknode->name));
        KASSERT(parentnode->ref_cnt > 0,
           ("Attempt to access unreferenced clock: %s\n", clknode->name));
        CLK_TOPO_XLOCK();
        rv = clknode_set_parent_by_name(clknode, parentnode->name);
        CLK_TOPO_UNLOCK();
        return (rv);
}

int
clk_enable(clk_t clk)
{
        int rv;
        struct clknode *clknode;

        clknode = clk->clknode;
        KASSERT(clknode->ref_cnt > 0,
           ("Attempt to access unreferenced clock: %s\n", clknode->name));
        CLK_TOPO_SLOCK();
        rv = clknode_enable(clknode);
        if (rv == 0)
                clk->enable_cnt++;
        CLK_TOPO_UNLOCK();
        return (rv);
}

int
clk_disable(clk_t clk)
{
        int rv;
        struct clknode *clknode;

        clknode = clk->clknode;
        KASSERT(clknode->ref_cnt > 0,
           ("Attempt to access unreferenced clock: %s\n", clknode->name));
        KASSERT(clk->enable_cnt > 0,
           ("Attempt to disable already disabled clock: %s\n", clknode->name));
        CLK_TOPO_SLOCK();
        rv = clknode_disable(clknode);
        if (rv == 0)
                clk->enable_cnt--;
        CLK_TOPO_UNLOCK();
        return (rv);
}

int
clk_stop(clk_t clk)
{
        int rv;
        struct clknode *clknode;

        clknode = clk->clknode;
        KASSERT(clknode->ref_cnt > 0,
           ("Attempt to access unreferenced clock: %s\n", clknode->name));
        KASSERT(clk->enable_cnt == 0,
           ("Attempt to stop already enabled clock: %s\n", clknode->name));

        CLK_TOPO_SLOCK();
        rv = clknode_stop(clknode, 0);
        CLK_TOPO_UNLOCK();
        return (rv);
}

int
clk_release(clk_t clk)
{
        struct clknode *clknode;

        clknode = clk->clknode;
        KASSERT(clknode->ref_cnt > 0,
           ("Attempt to access unreferenced clock: %s\n", clknode->name));
        CLK_TOPO_SLOCK();
        while (clk->enable_cnt > 0) {
                clknode_disable(clknode);
                clk->enable_cnt--;
        }
        CLKNODE_XLOCK(clknode);
        clknode->ref_cnt--;
        CLKNODE_UNLOCK(clknode);
        CLK_TOPO_UNLOCK();

        free(clk, M_CLOCK);
        return (0);
}

const char *
clk_get_name(clk_t clk)
{
        const char *name;
        struct clknode *clknode;

        clknode = clk->clknode;
        KASSERT(clknode->ref_cnt > 0,
           ("Attempt to access unreferenced clock: %s\n", clknode->name));
        name = clknode_get_name(clknode);
        return (name);
}

int
clk_get_by_name(device_t dev, const char *name, clk_t *clk)
{
        struct clknode *clknode;

        CLK_TOPO_SLOCK();
        clknode = clknode_find_by_name(name);
        if (clknode == NULL) {
                CLK_TOPO_UNLOCK();
                return (ENODEV);
        }
        *clk = clk_create(clknode, dev);
        CLK_TOPO_UNLOCK();
        return (0);
}

int
clk_get_by_id(device_t dev, struct clkdom *clkdom, intptr_t id, clk_t *clk)
{
        struct clknode *clknode;

        CLK_TOPO_SLOCK();

        clknode = clknode_find_by_id(clkdom, id);
        if (clknode == NULL) {
                CLK_TOPO_UNLOCK();
                return (ENODEV);
        }
        *clk = clk_create(clknode, dev);
        CLK_TOPO_UNLOCK();

        return (0);
}

#ifdef FDT

static void
clk_set_assigned_parent(device_t dev, clk_t clk, int idx)
{
        clk_t parent;
        const char *pname;
        int rv;

        rv = clk_get_by_ofw_index_prop(dev, 0,
            "assigned-clock-parents", idx, &parent);
        if (rv != 0) {
                device_printf(dev,
                    "cannot get parent at idx %d\n", idx);
                return;
        }

        pname = clk_get_name(parent);
        rv = clk_set_parent_by_clk(clk, parent);
        if (rv != 0)
                device_printf(dev,
                    "Cannot set parent %s for clock %s\n",
                    pname, clk_get_name(clk));
        else if (bootverbose)
                device_printf(dev, "Set %s as the parent of %s\n",
                    pname, clk_get_name(clk));
        clk_release(parent);
}

static void
clk_set_assigned_rates(device_t dev, clk_t clk, uint32_t freq)
{
        int rv;

        rv = clk_set_freq(clk, freq, CLK_SET_ROUND_DOWN | CLK_SET_ROUND_UP);
        if (rv != 0) {
                device_printf(dev, "Failed to set %s to a frequency of %u\n",
                    clk_get_name(clk), freq);
                return;
        }
        if (bootverbose)
                device_printf(dev, "Set %s to %u\n",
                    clk_get_name(clk), freq);
}

int
clk_set_assigned(device_t dev, phandle_t node)
{
        clk_t clk;
        uint32_t *rates;
        int rv, nclocks, nrates, nparents, i;

        rv = ofw_bus_parse_xref_list_get_length(node,
            "assigned-clocks", "#clock-cells", &nclocks);

        if (rv != 0) {
                if (rv != ENOENT)
                        device_printf(dev,
                            "cannot parse assigned-clock property\n");
                return (rv);
        }

        nrates = OF_getencprop_alloc_multi(node, "assigned-clock-rates",
            sizeof(*rates), (void **)&rates);
        if (nrates <= 0)
                nrates = 0;

        if (ofw_bus_parse_xref_list_get_length(node,
            "assigned-clock-parents", "#clock-cells", &nparents) != 0)
                nparents = -1;
        for (i = 0; i < nclocks; i++) {
                /* First get the clock we are supposed to modify */
                rv = clk_get_by_ofw_index_prop(dev, 0, "assigned-clocks",
                    i, &clk);
                if (rv != 0) {
                        if (bootverbose)
                                device_printf(dev,
                                    "cannot get assigned clock at idx %d\n",
                                    i);
                        continue;
                }

                /* First set it's parent if needed */
                if (i < nparents)
                        clk_set_assigned_parent(dev, clk, i);

                /* Then set a new frequency */
                if (i < nrates && rates[i] != 0)
                        clk_set_assigned_rates(dev, clk, rates[i]);

                clk_release(clk);
        }
        if (rates != NULL)
                OF_prop_free(rates);

        return (0);
}

int
clk_get_by_ofw_index_prop(device_t dev, phandle_t cnode, const char *prop, int idx, clk_t *clk)
{
        phandle_t parent, *cells;
        device_t clockdev;
        int ncells, rv;
        struct clkdom *clkdom;
        struct clknode *clknode;

        *clk = NULL;
        if (cnode <= 0)
                cnode = ofw_bus_get_node(dev);
        if (cnode <= 0) {
                device_printf(dev, "%s called on not ofw based device\n",
                 __func__);
                return (ENXIO);
        }


        rv = ofw_bus_parse_xref_list_alloc(cnode, prop, "#clock-cells", idx,
            &parent, &ncells, &cells);
        if (rv != 0) {
                return (rv);
        }

        clockdev = OF_device_from_xref(parent);
        if (clockdev == NULL) {
                rv = ENODEV;
                goto done;
        }

        CLK_TOPO_SLOCK();
        clkdom = clkdom_get_by_dev(clockdev);
        if (clkdom == NULL){
                CLK_TOPO_UNLOCK();
                rv = ENXIO;
                goto done;
        }

        rv = clkdom->ofw_mapper(clkdom, ncells, cells, &clknode);
        if (rv == 0) {
                *clk = clk_create(clknode, dev);
        }
        CLK_TOPO_UNLOCK();

done:
        if (cells != NULL)
                OF_prop_free(cells);
        return (rv);
}

int
clk_get_by_ofw_index(device_t dev, phandle_t cnode, int idx, clk_t *clk)
{
        return (clk_get_by_ofw_index_prop(dev, cnode, "clocks", idx, clk));
}

int
clk_get_by_ofw_name(device_t dev, phandle_t cnode, const char *name, clk_t *clk)
{
        int rv, idx;

        if (cnode <= 0)
                cnode = ofw_bus_get_node(dev);
        if (cnode <= 0) {
                device_printf(dev, "%s called on not ofw based device\n",
                 __func__);
                return (ENXIO);
        }
        rv = ofw_bus_find_string_index(cnode, "clock-names", name, &idx);
        if (rv != 0)
                return (rv);
        return (clk_get_by_ofw_index(dev, cnode, idx, clk));
}

/* --------------------------------------------------------------------------
 *
 * Support functions for parsing various clock related OFW things.
 */

/*
 * Get "clock-output-names" and  (optional) "clock-indices" lists.
 * Both lists are allocated using M_OFWPROP specifier.
 *
 * Returns number of items or 0.
 */
int
clk_parse_ofw_out_names(device_t dev, phandle_t node, const char ***out_names,
        uint32_t **indices)
{
        int name_items, rv;

        *out_names = NULL;
        *indices = NULL;
        if (!OF_hasprop(node, "clock-output-names"))
                return (0);
        rv = ofw_bus_string_list_to_array(node, "clock-output-names",
            out_names);
        if (rv <= 0)
                return (0);
        name_items = rv;

        if (!OF_hasprop(node, "clock-indices"))
                return (name_items);
        rv = OF_getencprop_alloc_multi(node, "clock-indices", sizeof (uint32_t),
            (void **)indices);
        if (rv != name_items) {
                device_printf(dev, " Size of 'clock-output-names' and "
                    "'clock-indices' differs\n");
                OF_prop_free(*out_names);
                OF_prop_free(*indices);
                return (0);
        }
        return (name_items);
}

/*
 * Get output clock name for single output clock node.
 */
int
clk_parse_ofw_clk_name(device_t dev, phandle_t node, const char **name)
{
        const char **out_names;
        const char  *tmp_name;
        int rv;

        *name = NULL;
        if (!OF_hasprop(node, "clock-output-names")) {
                tmp_name  = ofw_bus_get_name(dev);
                if (tmp_name == NULL)
                        return (ENXIO);
                *name = strdup(tmp_name, M_OFWPROP);
                return (0);
        }
        rv = ofw_bus_string_list_to_array(node, "clock-output-names",
            &out_names);
        if (rv != 1) {
                OF_prop_free(out_names);
                device_printf(dev, "Malformed 'clock-output-names' property\n");
                return (ENXIO);
        }
        *name = strdup(out_names[0], M_OFWPROP);
        OF_prop_free(out_names);
        return (0);
}
#endif

static int
clkdom_sysctl(SYSCTL_HANDLER_ARGS)
{
        struct clkdom *clkdom = arg1;
        struct clknode *clknode;
        struct sbuf *sb;
        int ret;

        sb = sbuf_new_for_sysctl(NULL, NULL, 4096, req);
        if (sb == NULL)
                return (ENOMEM);

        CLK_TOPO_SLOCK();
        TAILQ_FOREACH(clknode, &clkdom->clknode_list, clkdom_link) {
                sbuf_printf(sb, "%s ", clknode->name);
        }
        CLK_TOPO_UNLOCK();

        ret = sbuf_finish(sb);
        sbuf_delete(sb);
        return (ret);
}

static int
clknode_sysctl(SYSCTL_HANDLER_ARGS)
{
        struct clknode *clknode, *children;
        enum clknode_sysctl_type type = arg2;
        struct sbuf *sb;
        const char **parent_names;
        uint64_t freq;
        bool enable;
        int ret, i;

        clknode = arg1;
        sb = sbuf_new_for_sysctl(NULL, NULL, 512, req);
        if (sb == NULL)
                return (ENOMEM);

        CLK_TOPO_SLOCK();
        switch (type) {
        case CLKNODE_SYSCTL_PARENT:
                if (clknode->parent)
                        sbuf_printf(sb, "%s", clknode->parent->name);
                break;
        case CLKNODE_SYSCTL_PARENTS_LIST:
                parent_names = clknode_get_parent_names(clknode);
                for (i = 0; i < clknode->parent_cnt; i++)
                        sbuf_printf(sb, "%s ", parent_names[i]);
                break;
        case CLKNODE_SYSCTL_CHILDREN_LIST:
                TAILQ_FOREACH(children, &(clknode->children), sibling_link) {
                        sbuf_printf(sb, "%s ", children->name);
                }
                break;
        case CLKNODE_SYSCTL_FREQUENCY:
                ret = clknode_get_freq(clknode, &freq);
                if (ret == 0)
                        sbuf_printf(sb, "%ju ", (uintmax_t)freq);
                else
                        sbuf_printf(sb, "Error: %d ", ret);
                break;
        case CLKNODE_SYSCTL_GATE:
                ret = CLKNODE_GET_GATE(clknode, &enable);
                if (ret == 0)
                        sbuf_printf(sb, enable ? "enabled": "disabled");
                else if (ret == ENXIO)
                        sbuf_printf(sb, "unimplemented");
                else if (ret == ENOENT)
                        sbuf_printf(sb, "unreadable");
                else
                        sbuf_printf(sb, "Error: %d ", ret);
                break;
        }
        CLK_TOPO_UNLOCK();

        ret = sbuf_finish(sb);
        sbuf_delete(sb);
        return (ret);
}