freebsd-dev/sys/dev/extres/clk/clk.c
Michal Meloun c0c5cf7b31 Reverse the processing order of assigned clocks property.
Linux processes these clocks in reverse order and some DT relies
on this fact. For example, the frequency setting for a given PLL
is the last in the list, preceded by the frequency setting of its
following divider or so...

MFC after:	1 week
2020-07-12 07:59:15 +00:00

1645 lines
38 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>
__FBSDID("$FreeBSD$");
#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/extres/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,
};
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_NEEDGIANT,
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);
printf("Clock: %s, parent: %s(%d), freq: %ju\n", clknode->name,
clknode->parent == NULL ? "(NULL)" : clknode->parent->name,
clknode->parent_idx,
(uintmax_t)((rv == 0) ? freq: rv));
}
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;
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_U64(&clknode->sysctl_ctx,
SYSCTL_CHILDREN(clknode_oid),
OID_AUTO, "frequency",
CTLFLAG_RD, &clknode->freq, 0, "The clock frequency");
SYSCTL_ADD_PROC(&clknode->sysctl_ctx,
SYSCTL_CHILDREN(clknode_oid),
OID_AUTO, "parent",
CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_NEEDGIANT,
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_NEEDGIANT,
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_NEEDGIANT,
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);
}
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_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 = nclocks - 1; i >= 0; 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 alocated 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;
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;
}
CLK_TOPO_UNLOCK();
ret = sbuf_finish(sb);
sbuf_delete(sb);
return (ret);
}