[PATCH v4 12/17] riscv: Add a bypass clock for K210
Rick Chen
rickchen36 at gmail.com
Tue Feb 18 07:35:59 CET 2020
Hi Sean
This patch is relative about clock driver.
It shall be named as clk instead of riscv
Thanks
Rick
> This is a small driver to do a software bypass of a clock if hardware
> bypass is not working. I have tried to write this in a generic fashion, so
> that it could be potentially broken out of the kendryte code at some future
> date. For the K210, it is used to have aclk bypass pll0 and use in0 instead
> so that the CPU keeps on working.
>
> Signed-off-by: Sean Anderson <seanga2 at gmail.com>
> ---
>
> Changes in v4:
> - New
>
> drivers/clk/kendryte/Makefile | 2 +-
> drivers/clk/kendryte/bypass.c | 268 ++++++++++++++++++++++++++++++++++
> include/kendryte/bypass.h | 28 ++++
> 3 files changed, 297 insertions(+), 1 deletion(-)
> create mode 100644 drivers/clk/kendryte/bypass.c
> create mode 100644 include/kendryte/bypass.h
>
> diff --git a/drivers/clk/kendryte/Makefile b/drivers/clk/kendryte/Makefile
> index c56d93ea1c..47f682fce3 100644
> --- a/drivers/clk/kendryte/Makefile
> +++ b/drivers/clk/kendryte/Makefile
> @@ -1 +1 @@
> -obj-y += pll.o
> +obj-y += bypass.o pll.o
> diff --git a/drivers/clk/kendryte/bypass.c b/drivers/clk/kendryte/bypass.c
> new file mode 100644
> index 0000000000..5276591bfd
> --- /dev/null
> +++ b/drivers/clk/kendryte/bypass.c
> @@ -0,0 +1,268 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright (C) 2020 Sean Anderson <seanga2 at gmail.com>
> + */
> +
> +#include <kendryte/bypass.h>
> +
> +#include <clk-uclass.h>
> +#include <linux/clk-provider.h>
> +#include <linux/err.h>
> +#define LOG_CATEGORY UCLASS_CLK
> +#include <log.h>
> +#include <serial.h>
> +
> +#define CLK_K210_BYPASS "k210_clk_bypass"
> +
> +/*
> + * This is a small driver to do a software bypass of a clock if hardware bypass
> + * is not working. I have tried to write this in a generic fashion, so that it
> + * could be potentially broken out of the kendryte code at some future date.
> + *
> + * Say you have the following clock configuration
> + *
> + * +---+ +---+
> + * |osc| |pll|
> + * +---+ +---+
> + * ^
> + * /|
> + * / |
> + * / |
> + * / |
> + * / |
> + * +---+ +---+
> + * |clk| |clk|
> + * +---+ +---+
> + *
> + * But the pll does not have a bypass, so when you configure the pll, the
> + * configuration needs to change to look like
> + *
> + * +---+ +---+
> + * |osc| |pll|
> + * +---+ +---+
> + * ^
> + * |\
> + * | \
> + * | \
> + * | \
> + * | \
> + * +---+ +---+
> + * |clk| |clk|
> + * +---+ +---+
> + *
> + * To set this up, create a bypass clock with bypassee=pll and alt=osc. When
> + * creating the child clocks, set their parent to the bypass clock. After
> + * creating all the children, call k210_bypass_setchildren().
> + */
> +
> +static int k210_bypass_dobypass(struct k210_bypass *bypass)
> +{
> + int ret, i;
> +
> + /*
> + * If we already have saved parents, then the children are already
> + * bypassed
> + */
> + if (bypass->child_count && bypass->saved_parents[0])
> + return 0;
> +
> + for (i = 0; i < bypass->child_count; i++) {
> + struct clk *child = bypass->children[i];
> + struct clk *parent = clk_get_parent(child);
> +
> + if (IS_ERR(parent)) {
> + for (; i; i--)
> + bypass->saved_parents[i] = NULL;
> + return PTR_ERR(parent);
> + }
> + bypass->saved_parents[i] = parent;
> + }
> +
> + for (i = 0; i < bypass->child_count; i++) {
> + struct clk *child = bypass->children[i];
> +
> + ret = clk_set_parent(child, bypass->alt);
> + if (ret) {
> + for (; i; i--)
> + clk_set_parent(bypass->children[i],
> + bypass->saved_parents[i]);
> + for (i = 0; i < bypass->child_count; i++)
> + bypass->saved_parents[i] = NULL;
> + return ret;
> + }
> + }
> +
> + return 0;
> +}
> +
> +static int k210_bypass_unbypass(struct k210_bypass *bypass)
> +{
> + int err, ret, i;
> +
> + if (!bypass->child_count && !bypass->saved_parents[0]) {
> + log_warning("Cannot unbypass children; dobypass not called first\n");
> + return 0;
> + }
> +
> + ret = 0;
> + for (i = 0; i < bypass->child_count; i++) {
> + err = clk_set_parent(bypass->children[i],
> + bypass->saved_parents[i]);
> + if (err)
> + ret = err;
> + bypass->saved_parents[i] = NULL;
> + }
> + return ret;
> +}
> +
> +static ulong k210_bypass_get_rate(struct clk *clk)
> +{
> + struct k210_bypass *bypass = to_k210_bypass(clk);
> + const struct clk_ops *ops = bypass->bypassee_ops;
> +
> + if (ops->get_rate)
> + return ops->get_rate(bypass->bypassee);
> + else
> + return clk_get_parent_rate(bypass->bypassee);
> +}
> +
> +static ulong k210_bypass_set_rate(struct clk *clk, unsigned long rate)
> +{
> + int ret;
> + struct k210_bypass *bypass = to_k210_bypass(clk);
> + const struct clk_ops *ops = bypass->bypassee_ops;
> +
> + /* Don't bother bypassing if we aren't going to set the rate */
> + if (!ops->set_rate)
> + return k210_bypass_get_rate(clk);
> +
> + ret = k210_bypass_dobypass(bypass);
> + if (ret)
> + return ret;
> +
> + ret = ops->set_rate(bypass->bypassee, rate);
> + if (ret < 0)
> + return ret;
> +
> + return k210_bypass_unbypass(bypass);
> +}
> +
> +static int k210_bypass_set_parent(struct clk *clk, struct clk *parent)
> +{
> + struct k210_bypass *bypass = to_k210_bypass(clk);
> + const struct clk_ops *ops = bypass->bypassee_ops;
> +
> + if (ops->set_parent)
> + return ops->set_parent(bypass->bypassee, parent);
> + else
> + return -ENOTSUPP;
> +}
> +
> +/*
> + * For these next two functions, do the bypassing even if there is no
> + * en-/-disable function, since the bypassing itself can be observed in between
> + * calls.
> + */
> +static int k210_bypass_enable(struct clk *clk)
> +{
> + int ret;
> + struct k210_bypass *bypass = to_k210_bypass(clk);
> + const struct clk_ops *ops = bypass->bypassee_ops;
> +
> + ret = k210_bypass_dobypass(bypass);
> + if (ret)
> + return ret;
> +
> + if (ops->enable)
> + ret = ops->enable(bypass->bypassee);
> + else
> + ret = 0;
> + if (ret)
> + return ret;
> +
> + return k210_bypass_unbypass(bypass);
> +}
> +
> +static int k210_bypass_disable(struct clk *clk)
> +{
> + int ret;
> + struct k210_bypass *bypass = to_k210_bypass(clk);
> + const struct clk_ops *ops = bypass->bypassee_ops;
> +
> + ret = k210_bypass_dobypass(bypass);
> + if (ret)
> + return ret;
> +
> + if (ops->disable)
> + return ops->disable(bypass->bypassee);
> + else
> + return 0;
> +}
> +
> +static const struct clk_ops k210_bypass_ops = {
> + .get_rate = k210_bypass_get_rate,
> + .set_rate = k210_bypass_set_rate,
> + .set_parent = k210_bypass_set_parent,
> + .enable = k210_bypass_enable,
> + .disable = k210_bypass_disable,
> +};
> +
> +int k210_bypass_set_children(struct clk *clk, struct clk **children,
> + size_t child_count)
> +{
> + struct k210_bypass *bypass = to_k210_bypass(clk);
> +
> + kfree(bypass->saved_parents);
> + if (child_count) {
> + bypass->saved_parents =
> + kcalloc(child_count, sizeof(struct clk *), GFP_KERNEL);
> + if (!bypass->saved_parents)
> + return -ENOMEM;
> + }
> + bypass->child_count = child_count;
> + bypass->children = children;
> +
> + return 0;
> +}
> +
> +static struct k210_bypass *k210_clk_comp_bypass(struct clk *bypassee,
> + const struct clk_ops *bypassee_ops,
> + struct clk *alt)
> +{
> + struct k210_bypass *bypass;
> +
> + bypass = kzalloc(sizeof(*bypass), GFP_KERNEL);
> + if (!bypass)
> + return bypass;
> +
> + bypass->bypassee = bypassee;
> + bypass->bypassee_ops = bypassee_ops;
> + bypass->alt = alt;
> + return bypass;
> +}
> +
> +struct clk *k210_clk_bypass(const char *name, const char *parent_name,
> + struct clk *bypassee,
> + const struct clk_ops *bypassee_ops, struct clk *alt)
> +{
> + int err;
> + struct k210_bypass *bypass;
> +
> + bypass = k210_clk_comp_bypass(bypassee, bypassee_ops, alt);
> + if (!bypass)
> + return ERR_PTR(-ENOMEM);
> +
> + err = clk_register(&bypass->clk, CLK_K210_BYPASS, name, parent_name);
> + if (err) {
> + kfree(bypass);
> + return ERR_PTR(err);
> + }
> + bypassee->dev = bypass->clk.dev;
> + return &bypass->clk;
> +}
> +
> +U_BOOT_DRIVER(k210_bypass) = {
> + .name = CLK_K210_BYPASS,
> + .id = UCLASS_CLK,
> + .ops = &k210_bypass_ops,
> +};
> diff --git a/include/kendryte/bypass.h b/include/kendryte/bypass.h
> new file mode 100644
> index 0000000000..3093057324
> --- /dev/null
> +++ b/include/kendryte/bypass.h
> @@ -0,0 +1,28 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + * Copyright (C) 2020 Sean Anderson <seanga2 at gmail.com>
> + */
> +#ifndef K210_BYPASS_H
> +#define K210_BYPASS_H
> +
> +#include <clk.h>
> +
> +struct k210_bypass {
> + struct clk clk;
> + struct clk **children; /* Clocks to reparent */
> + struct clk **saved_parents; /* Parents saved over en-/dis-able */
> + struct clk *bypassee; /* Clock to bypass */
> + const struct clk_ops *bypassee_ops; /* Ops of the bypass clock */
> + struct clk *alt; /* Clock to set children to when bypassing */
> + size_t child_count;
> +};
> +
> +#define to_k210_bypass(_clk) container_of(_clk, struct k210_bypass, clk)
> +
> +int k210_bypass_set_children(struct clk *clk, struct clk **children,
> + size_t child_count);
> +struct clk *k210_clk_bypass(const char *name, const char *parent_name,
> + struct clk *bypassee,
> + const struct clk_ops *bypassee_ops,
> + struct clk *alt);
> +#endif /* K210_BYPASS_H */
> --
> 2.25.0
>
More information about the U-Boot
mailing list