freebsd-src/sys/dev/clk/clk.c
Mitchell Horne 08635c51d1 clkdom_dump(): improve output text
If the call to clknode_get_freq() returns an error (unlikely), report
this, rather than printing the error code as the clock frequency.

If the clock has no parent (e.g. a fixed reference clock), print "none"
rather than "(NULL)(-1)". This is a more human-legible presentation of the
same information.

Reviewed by:	manu
MFC after:	1 week
Sponsored by:	The FreeBSD Foundation
Differential Revision:	https://reviews.freebsd.org/D44267
2024-03-08 10:09:36 -04:00

1719 lines
40 KiB
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);
}