Implement support for (soft)linked clocks.

This kind of clock nodes represent temporary placeholder for clocks
defined later in boot process. Also, these are necessary to break
circular dependencies occasionally occurring in complex clock graphs.

MFC after: 3 weeks
This commit is contained in:
Michal Meloun 2019-11-08 18:57:41 +00:00
parent 09bc401f9d
commit 124a91ac18
5 changed files with 258 additions and 12 deletions

View File

@ -1681,6 +1681,7 @@ dev/extres/clk/clk_bus.c optional ext_resources clk fdt
dev/extres/clk/clk_div.c optional ext_resources clk fdt dev/extres/clk/clk_div.c optional ext_resources clk fdt
dev/extres/clk/clk_fixed.c optional ext_resources clk fdt dev/extres/clk/clk_fixed.c optional ext_resources clk fdt
dev/extres/clk/clk_gate.c optional ext_resources clk fdt dev/extres/clk/clk_gate.c optional ext_resources clk fdt
dev/extres/clk/clk_link.c optional ext_resources clk fdt
dev/extres/clk/clk_mux.c optional ext_resources clk fdt dev/extres/clk/clk_mux.c optional ext_resources clk fdt
dev/extres/phy/phy.c optional ext_resources phy fdt dev/extres/phy/phy.c optional ext_resources phy fdt
dev/extres/phy/phydev_if.m optional ext_resources phy fdt dev/extres/phy/phydev_if.m optional ext_resources phy fdt

View File

@ -189,6 +189,9 @@ enum clknode_sysctl_type {
static int clknode_sysctl(SYSCTL_HANDLER_ARGS); static int clknode_sysctl(SYSCTL_HANDLER_ARGS);
static int clkdom_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. * Default clock methods for base class.
*/ */
@ -526,20 +529,71 @@ clknode_create(struct clkdom * clkdom, clknode_class_t clknode_class,
{ {
struct clknode *clknode; struct clknode *clknode;
struct sysctl_oid *clknode_oid; struct sysctl_oid *clknode_oid;
bool replaced;
KASSERT(def->name != NULL, ("clock name is NULL")); KASSERT(def->name != NULL, ("clock name is NULL"));
KASSERT(def->name[0] != '\0', ("clock name is empty")); KASSERT(def->name[0] != '\0', ("clock name is empty"));
#ifdef INVARIANTS if (def->flags & CLK_NODE_LINKED) {
CLK_TOPO_SLOCK(); KASSERT(def->parent_cnt == 0,
if (clknode_find_by_name(def->name) != NULL) ("Linked clock must not have parents"));
panic("Duplicated clock registration: %s\n", def->name); KASSERT(clknode_class->size== 0,
CLK_TOPO_UNLOCK(); ("Linked clock cannot have own softc"));
#endif }
/* 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;
}
/* Create object and initialize it. */
clknode = malloc(sizeof(struct clknode), M_CLOCK, M_WAITOK | M_ZERO);
kobj_init((kobj_t)clknode, (kobj_class_t)clknode_class); kobj_init((kobj_t)clknode, (kobj_class_t)clknode_class);
sx_init(&clknode->lock, "Clocknode lock");
/* Allocate softc if required. */ /* Allocate softc if required. */
if (clknode_class->size > 0) { if (clknode_class->size > 0) {
@ -568,7 +622,9 @@ clknode_create(struct clkdom * clkdom, clknode_class_t clknode_class,
clknode->parent_cnt = def->parent_cnt; clknode->parent_cnt = def->parent_cnt;
clknode->parent = NULL; clknode->parent = NULL;
clknode->parent_idx = CLKNODE_IDX_NONE; clknode->parent_idx = CLKNODE_IDX_NONE;
TAILQ_INIT(&clknode->children);
if (replaced)
return (clknode);
sysctl_ctx_init(&clknode->sysctl_ctx); sysctl_ctx_init(&clknode->sysctl_ctx);
clknode_oid = SYSCTL_ADD_NODE(&clknode->sysctl_ctx, clknode_oid = SYSCTL_ADD_NODE(&clknode->sysctl_ctx,
@ -617,6 +673,10 @@ clknode_register(struct clkdom * clkdom, struct clknode *clknode)
{ {
int rv; int rv;
/* Skip already registered linked node */
if (clknode->flags & CLK_NODE_REGISTERED)
return(clknode);
rv = CLKNODE_INIT(clknode, clknode_get_device(clknode)); rv = CLKNODE_INIT(clknode, clknode_get_device(clknode));
if (rv != 0) { if (rv != 0) {
printf(" CLKNODE_INIT failed: %d\n", rv); printf(" CLKNODE_INIT failed: %d\n", rv);
@ -624,10 +684,24 @@ clknode_register(struct clkdom * clkdom, struct clknode *clknode)
} }
TAILQ_INSERT_TAIL(&clkdom->clknode_list, clknode, clkdom_link); TAILQ_INSERT_TAIL(&clkdom->clknode_list, clknode, clkdom_link);
clknode->flags |= CLK_NODE_REGISTERED;
return (clknode); 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. * Clock providers interface.
*/ */

View File

@ -41,7 +41,9 @@
/* clknode flags. */ /* clknode flags. */
#define CLK_NODE_STATIC_STRINGS 0x00000001 /* Static name strings */ #define CLK_NODE_STATIC_STRINGS 0x00000001 /* Static name strings */
#define CLK_NODE_GLITCH_FREE 0x00000002 /* Freq can change w/o stop */ #define CLK_NODE_GLITCH_FREE 0x00000002 /* Freq can change w/o stop */
#define CLK_NODE_CANNOT_STOP 0x00000004 /* Clock cannot be disabled */ #define CLK_NODE_CANNOT_STOP 0x00000004 /* Cannot be disabled */
#define CLK_NODE_LINKED 0x00000008 /* Is linked clock */
#define CLK_NODE_REGISTERED 0x00000020 /* Is already registered */
/* Flags passed to clk_set_freq() and clknode_set_freq(). */ /* Flags passed to clk_set_freq() and clknode_set_freq(). */
#define CLK_SET_ROUND(x) ((x) & (CLK_SET_ROUND_UP | CLK_SET_ROUND_DOWN)) #define CLK_SET_ROUND(x) ((x) & (CLK_SET_ROUND_UP | CLK_SET_ROUND_DOWN))

View File

@ -0,0 +1,122 @@
/*-
* 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 <sys/param.h>
#include <sys/conf.h>
#include <sys/bus.h>
#include <sys/kernel.h>
#include <sys/kobj.h>
#include <sys/malloc.h>
#include <sys/mutex.h>
#include <sys/module.h>
#include <sys/rman.h>
#include <sys/systm.h>
#include <machine/bus.h>
#include <dev/ofw/ofw_bus.h>
#include <dev/ofw/ofw_bus_subr.h>
#include <dev/extres/clk/clk_link.h>
static int clknode_link_init(struct clknode *clk, device_t dev);
static int clknode_link_recalc(struct clknode *clk, uint64_t *freq);
static int clknode_link_set_freq(struct clknode *clk, uint64_t fin,
uint64_t *fout, int flags, int *stop);
static int clknode_link_set_mux(struct clknode *clk, int idx);
static int clknode_link_set_gate(struct clknode *clk, bool enable);
static clknode_method_t clknode_link_methods[] = {
/* Device interface */
CLKNODEMETHOD(clknode_init, clknode_link_init),
CLKNODEMETHOD(clknode_recalc_freq, clknode_link_recalc),
CLKNODEMETHOD(clknode_set_freq, clknode_link_set_freq),
CLKNODEMETHOD(clknode_set_gate, clknode_link_set_gate),
CLKNODEMETHOD(clknode_set_mux, clknode_link_set_mux),
CLKNODEMETHOD_END
};
DEFINE_CLASS_1(clknode_link, clknode_link_class, clknode_link_methods,
0, clknode_class);
static int
clknode_link_init(struct clknode *clk, device_t dev)
{
return(0);
}
static int
clknode_link_recalc(struct clknode *clk, uint64_t *freq)
{
printf("%s: Attempt to use unresolved linked clock: %s\n", __func__,
clknode_get_name(clk));
return (EBADF);
}
static int
clknode_link_set_freq(struct clknode *clk, uint64_t fin, uint64_t *fout,
int flags, int *stop)
{
printf("%s: Attempt to use unresolved linked clock: %s\n", __func__,
clknode_get_name(clk));
return (EBADF);
}
static int
clknode_link_set_mux(struct clknode *clk, int idx)
{
printf("%s: Attempt to use unresolved linked clock: %s\n", __func__,
clknode_get_name(clk));
return (EBADF);
}
static int
clknode_link_set_gate(struct clknode *clk, bool enable)
{
printf("%s: Attempt to use unresolved linked clock: %s\n", __func__,
clknode_get_name(clk));
return (EBADF);
}
int
clknode_link_register(struct clkdom *clkdom, struct clk_link_def *clkdef)
{
struct clknode *clk;
struct clknode_init_def tmp;
tmp = clkdef->clkdef;
tmp.flags |= CLK_NODE_LINKED;
clk = clknode_create(clkdom, &clknode_link_class, &tmp);
if (clk == NULL)
return (1);
clknode_register(clkdom, clk);
return (0);
}

View File

@ -0,0 +1,47 @@
/*-
* 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.
*
* $FreeBSD$
*/
#ifndef _DEV_EXTRES_CLK_LINK_H_
#define _DEV_EXTRES_CLK_LINK_H_
#include <dev/extres/clk/clk.h>
/*
* A linked clock is used as placeholder for not yet available clock.
* It will be replaced by equally named clock from other domain, created
* in future stage of system initialization.
*/
struct clk_link_def {
struct clknode_init_def clkdef;
};
int clknode_link_register(struct clkdom *clkdom, struct clk_link_def *clkdef);
#endif /*_DEV_EXTRES_CLK_LINK_H_*/