diff mbox series

[v3,10/12] riscv: Add K210 clock support

Message ID 6dfd39a4-0721-eee4-7dbc-6ea2a7d7abf1@gmail.com
State New
Headers show
Series riscv: Add Sipeed Maix support | expand

Commit Message

Sean Anderson Feb. 2, 2020, 8:07 p.m. UTC
Due to the large number of clocks, I decided to use the CCF. The overall
structure is modeled after the imx code. A common pattern is to create a
composite clock composed of several component clocks. For these component
clocks, the clk_register_* functions are not used, since they will be registered
as part of the composite clock. To create these component clocks, several helper
k210_clk_comp_* functions are used. This functionality seems like it would be
useful to other drivers also creating composite clocks, so perhaps some general
versions should be created. I am not particularly attached to the naming
convention, suggestions are welcome.

Signed-off-by: Sean Anderson <seanga2 at gmail.com>
---
  Changes for v3:
  - Removed sysctl struct, replacing it with defines. This is to have the same
    interface to sysctl from C as from the device tree.
  - Fixed clocks having the same id
  - Fixed clocks not using the correct register/bits
  - Aligned the defines in headers
  Changes for v2:
  - Add clk.o to obj-y
  - Don't probe before relocation

 drivers/clk/kendryte/Kconfig            |   2 +-
 drivers/clk/kendryte/Makefile           |   2 +-
 drivers/clk/kendryte/clk.c              | 390 ++++++++++++++++++++++++
 drivers/clk/kendryte/clk.h              |  27 ++
 include/dt-bindings/clock/k210-sysctl.h |  53 ++++
 include/dt-bindings/mfd/k210-sysctl.h   |  38 +++
 6 files changed, 510 insertions(+), 2 deletions(-)
 create mode 100644 drivers/clk/kendryte/clk.c
 create mode 100644 drivers/clk/kendryte/clk.h
 create mode 100644 include/dt-bindings/clock/k210-sysctl.h
 create mode 100644 include/dt-bindings/mfd/k210-sysctl.h

Comments

Lukasz Majewski Feb. 6, 2020, 9:51 p.m. UTC | #1
Hi Sean,

> Due to the large number of clocks, I decided to use the CCF. The
> overall structure is modeled after the imx code. A common pattern is
> to create a composite clock composed of several component clocks. For
> these component clocks, the clk_register_* functions are not used,
> since they will be registered as part of the composite clock. To
> create these component clocks, several helper k210_clk_comp_*
> functions are used. This functionality seems like it would be useful
> to other drivers also creating composite clocks, so perhaps some
> general versions should be created. I am not particularly attached to
> the naming convention, suggestions are welcome.
> 
> Signed-off-by: Sean Anderson <seanga2 at gmail.com>

Reviewed-by: Lukasz Majewski <lukma at denx.de>

> ---
>   Changes for v3:
>   - Removed sysctl struct, replacing it with defines. This is to have
> the same interface to sysctl from C as from the device tree.
>   - Fixed clocks having the same id
>   - Fixed clocks not using the correct register/bits
>   - Aligned the defines in headers
>   Changes for v2:
>   - Add clk.o to obj-y
>   - Don't probe before relocation
> 
>  drivers/clk/kendryte/Kconfig            |   2 +-
>  drivers/clk/kendryte/Makefile           |   2 +-
>  drivers/clk/kendryte/clk.c              | 390
> ++++++++++++++++++++++++ drivers/clk/kendryte/clk.h              |
> 27 ++ include/dt-bindings/clock/k210-sysctl.h |  53 ++++
>  include/dt-bindings/mfd/k210-sysctl.h   |  38 +++
>  6 files changed, 510 insertions(+), 2 deletions(-)
>  create mode 100644 drivers/clk/kendryte/clk.c
>  create mode 100644 drivers/clk/kendryte/clk.h
>  create mode 100644 include/dt-bindings/clock/k210-sysctl.h
>  create mode 100644 include/dt-bindings/mfd/k210-sysctl.h
> 
> diff --git a/drivers/clk/kendryte/Kconfig
> b/drivers/clk/kendryte/Kconfig index 7b69c8afaf..073fca0781 100644
> --- a/drivers/clk/kendryte/Kconfig
> +++ b/drivers/clk/kendryte/Kconfig
> @@ -1,6 +1,6 @@
>  config CLK_K210
>  	bool "Clock support for Kendryte K210"
> -	depends on CLK && CLK_CCF
> +	depends on CLK && CLK_CCF && CLK_COMPOSITE_CCF
>  	help
>  	  This enables support clock driver for Kendryte K210
> platforms. 
> diff --git a/drivers/clk/kendryte/Makefile
> b/drivers/clk/kendryte/Makefile index c56d93ea1c..d26bce954f 100644
> --- a/drivers/clk/kendryte/Makefile
> +++ b/drivers/clk/kendryte/Makefile
> @@ -1 +1 @@
> -obj-y += pll.o
> +obj-y += clk.o pll.o
> diff --git a/drivers/clk/kendryte/clk.c b/drivers/clk/kendryte/clk.c
> new file mode 100644
> index 0000000000..aaa7420c84
> --- /dev/null
> +++ b/drivers/clk/kendryte/clk.c
> @@ -0,0 +1,390 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright (C) 2019 Sean Anderson <seanga2 at gmail.com>
> + */
> +#include "clk.h"
> +
> +#include <asm/io.h>
> +#include <dt-bindings/clock/k210-sysctl.h>
> +#include <dt-bindings/mfd/k210-sysctl.h>
> +#include <dm.h>
> +#include <log.h>
> +#include <mapmem.h>
> +
> +#include "pll.h"
> +
> +static ulong k210_clk_get_rate(struct clk *clk)
> +{
> +	struct clk *c;
> +	int err = clk_get_by_id(clk->id, &c);
> +
> +	if (err)
> +		return err;
> +	return clk_get_rate(c);
> +}
> +
> +static ulong k210_clk_set_rate(struct clk *clk, unsigned long rate)
> +{
> +	struct clk *c;
> +	int err = clk_get_by_id(clk->id, &c);
> +
> +	if (err)
> +		return err;
> +	return clk_set_rate(c, rate);
> +}
> +
> +static int k210_clk_set_parent(struct clk *clk, struct clk *parent)
> +{
> +	struct clk *c, *p;
> +	int err = clk_get_by_id(clk->id, &c);
> +
> +	if (err)
> +		return err;
> +	
> +	err = clk_get_by_id(parent->id, &p);
> +	if (err)
> +		return err;
> +
> +	return clk_set_parent(c, p);
> +}
> +
> +static int k210_clk_endisable(struct clk *clk, bool enable)
> +{
> +	struct clk *c;
> +	int err = clk_get_by_id(clk->id, &c);
> +
> +	if (err)
> +		return err;
> +	return enable ? clk_enable(c) : clk_disable(c);
> +}
> +
> +static int k210_clk_enable(struct clk *clk)
> +{
> +	return k210_clk_endisable(clk, true);
> +}
> +
> +static int k210_clk_disable(struct clk *clk)
> +{
> +	return k210_clk_endisable(clk, false);
> +}
> +
> +static const struct clk_ops k210_clk_ops = {
> +	.set_rate = k210_clk_set_rate,
> +	.get_rate = k210_clk_get_rate,
> +	.set_parent = k210_clk_set_parent,
> +	.enable = k210_clk_enable,
> +	.disable = k210_clk_disable,
> +};
> +
> +/* The first clock is in0, which is filled in by k210_clk_probe */
> +static const char *generic_sels[] = { NULL, "pll0", };
> +static const char *aclk_sels[] = { "in0_half", "pll0_half", };
> +static const char *pll2_sels[] = { NULL, "pll0", "pll1", };
> +
> +static struct clk_divider *k210_clk_comp_div_flags(void __iomem
> *reg, u8 shift,
> +						   u8 width, u8
> flags) +{
> +	struct clk_divider *div;
> +
> +	div = kzalloc(sizeof(*div), GFP_KERNEL);
> +	if (!div)
> +		return div;
> +	div->reg = reg;
> +	div->shift = shift;
> +	div->width = width;
> +	div->flags = flags;
> +	return div;
> +}
> +
> +static inline struct clk_divider *k210_clk_comp_div(void __iomem
> *reg, u8 shift,
> +						    u8 width)
> +{
> +	return k210_clk_comp_div_flags(reg, shift, width, 0);
> +}
> +
> +static struct clk_gate *k210_clk_comp_gate(void __iomem *reg, u8
> bit_idx) +{
> +	struct clk_gate *gate;
> +
> +	gate = kzalloc(sizeof(*gate), GFP_KERNEL);
> +	if (!gate)
> +		return gate;
> +	gate->reg = reg;
> +	gate->bit_idx = bit_idx;
> +	return gate;
> +}
> +
> +static struct clk_mux *k210_clk_comp_mux(const char *parent_names[],
> +				         u8 num_parents, void
> __iomem *reg,
> +				         u8 shift, u8 width)
> +{
> +	struct clk_mux *mux;
> +
> +	mux = kzalloc(sizeof(*mux), GFP_KERNEL);
> +	if (!mux)
> +		return mux;
> +	mux->reg = reg;
> +	mux->mask = BIT(width) - 1;
> +	mux->shift = shift;
> +	mux->parent_names = parent_names;
> +	mux->num_parents = num_parents;
> +	return mux;
> +}
> +
> +static struct clk *k210_clk_comp_nomux(const char *name, const char
> *parent,
> +					 struct clk_divider *div,
> +					 struct clk_gate *gate)
> +{
> +	if (!div || !gate) {
> +		kfree(div);
> +		kfree(gate);
> +		return ERR_PTR(-ENOMEM);
> +	}
> +	return clk_register_composite(NULL, name, &parent, 1,
> +				      NULL, NULL,
> +				      &div->clk, &clk_divider_ops,
> +				      &gate->clk, &clk_gate_ops, 0);
> +}
> +
> +static struct clk *k210_clk_comp(const char *name, struct
> clk_divider *div,
> +				 struct clk_gate *gate, struct
> clk_mux *mux) +{
> +	if (!div || !gate || !mux) {
> +		kfree(div);
> +		kfree(gate);
> +		kfree(mux);
> +		return ERR_PTR(-ENOMEM);
> +	}
> +	return clk_register_composite(NULL, name, generic_sels,
> +				      ARRAY_SIZE(generic_sels),
> +				      &mux->clk, &clk_mux_ops,
> +				      &div->clk, &clk_divider_ops,
> +				      &gate->clk, &clk_gate_ops, 0);
> +}
> +
> +static int k210_clk_probe(struct udevice *dev)
> +{
> +	int err;
> +	const char *in0;
> +	struct clk in0_clk;
> +	struct clk_divider *div;
> +	struct clk_gate *gate;
> +	struct clk_mux *mux;
> +	struct k210_pll *pll;
> +	void *base;
> +
> +	base = dev_read_addr_ptr(dev_get_parent(dev));
> +	if (!base)
> +		return -EINVAL;
> +
> +	err = clk_get_by_index(dev, 0, &in0_clk);
> +	if (err)
> +		goto cleanup_base;
> +	in0 = in0_clk.dev->name;
> +	generic_sels[0] = in0;
> +	pll2_sels[0] = in0;
> +
> +	/* PLLs */
> +	clk_dm(K210_CLK_PLL0, k210_clk_pll("pll0", in0, base +
> K210_SYSCTL_PLL0,
> +					   base +
> K210_SYSCTL_PLL_LOCK, 0, 2));
> +	clk_dm(K210_CLK_PLL1, k210_clk_pll("pll1", in0, base +
> K210_SYSCTL_PLL1,
> +					   base +
> K210_SYSCTL_PLL_LOCK, 8, 1));
> +	/* PLL2 is muxed, so set up a composite clock */
> +	mux = k210_clk_comp_mux(pll2_sels, ARRAY_SIZE(pll2_sels),
> +				base + K210_SYSCTL_PLL2, 26, 2);
> +	pll = k210_clk_comp_pll(base + K210_SYSCTL_PLL2,
> +				base + K210_SYSCTL_PLL_LOCK, 16, 1);
> +	if (!mux || !pll) {
> +		kfree(mux);
> +		kfree(pll);
> +	} else {
> +		clk_dm(K210_CLK_PLL2,
> +		       clk_register_composite(NULL, "pll2",
> pll2_sels,
> +					      ARRAY_SIZE(pll2_sels),
> +					      &mux->clk,
> &clk_mux_ops,
> +					      &pll->clk,
> &k210_pll_ops,
> +					      &pll->clk,
> &k210_pll_ops, 0));
> +	}
> +
> +	/* Half-frequency clocks for "even" dividers */
> +	k210_clk_half("in0_half", in0);
> +	k210_clk_half("pll0_half", "pll0");
> +	k210_clk_half("pll2_half", "pll2");
> +
> +	/* Muxed clocks */
> +	div = k210_clk_comp_div_flags(base + K210_SYSCTL_SEL0, 1, 2,
> +				      CLK_DIVIDER_POWER_OF_TWO);
> +	mux = k210_clk_comp_mux(aclk_sels, ARRAY_SIZE(aclk_sels),
> +				base + K210_SYSCTL_SEL0, 0, 1);
> +	if (!div || !mux) {
> +		kfree(div);
> +		kfree(mux);
> +	} else {
> +		clk_dm(K210_CLK_ACLK,
> +		       clk_register_composite(NULL, "aclk",
> aclk_sels,
> +					      ARRAY_SIZE(aclk_sels),
> +					      &mux->clk,
> &clk_mux_ops,
> +					      &div->clk,
> &clk_divider_ops,
> +					      NULL, NULL, 0));
> +	}
> +
> +	div = k210_clk_comp_div(base + K210_SYSCTL_SEL0, 1, 2);
> +	gate = k210_clk_comp_gate(base + K210_SYSCTL_EN_PERI, 9);
> +	mux = k210_clk_comp_mux(generic_sels,
> ARRAY_SIZE(generic_sels),
> +				base + K210_SYSCTL_SEL0, 12, 1);
> +	clk_dm(K210_CLK_SPI3, k210_clk_comp("spi3", div, gate, mux));
> +	
> +	div = k210_clk_comp_div(base + K210_SYSCTL_THR2, 8, 0);
> +	gate = k210_clk_comp_gate(base + K210_SYSCTL_EN_PERI, 21);
> +	mux = k210_clk_comp_mux(generic_sels,
> ARRAY_SIZE(generic_sels),
> +				base + K210_SYSCTL_SEL0, 13, 1);
> +	clk_dm(K210_CLK_TIMER0, k210_clk_comp("timer0", div, gate,
> mux)); +
> +	div = k210_clk_comp_div(base + K210_SYSCTL_THR2, 8, 8);
> +	gate = k210_clk_comp_gate(base + K210_SYSCTL_EN_PERI, 22);
> +	mux = k210_clk_comp_mux(generic_sels,
> ARRAY_SIZE(generic_sels),
> +				base + K210_SYSCTL_SEL0, 14, 1);
> +	clk_dm(K210_CLK_TIMER1, k210_clk_comp("timer1", div, gate,
> mux));
> +	
> +	div = k210_clk_comp_div(base + K210_SYSCTL_THR2, 8, 16);
> +	gate = k210_clk_comp_gate(base + K210_SYSCTL_EN_PERI, 23);
> +	mux = k210_clk_comp_mux(generic_sels,
> ARRAY_SIZE(generic_sels),
> +				base + K210_SYSCTL_SEL0, 15, 1);
> +	clk_dm(K210_CLK_TIMER2, k210_clk_comp("timer2", div, gate,
> mux)); +
> +
> +	/* Dividing clocks, no mux */
> +	div = k210_clk_comp_div(base + K210_SYSCTL_THR0, 0, 4);
> +	gate = k210_clk_comp_gate(base + K210_SYSCTL_EN_CENT, 1);
> +	clk_dm(K210_CLK_SRAM0, k210_clk_comp_nomux("sram0", "aclk",
> div, gate)); +
> +	div = k210_clk_comp_div(base + K210_SYSCTL_THR0, 4, 4);
> +	gate = k210_clk_comp_gate(base + K210_SYSCTL_EN_CENT, 2);
> +	clk_dm(K210_CLK_SRAM1, k210_clk_comp_nomux("sram1", "aclk",
> div, gate)); +
> +	div = k210_clk_comp_div(base + K210_SYSCTL_THR0, 16, 4);
> +	gate = k210_clk_comp_gate(base + K210_SYSCTL_EN_PERI, 0);
> +	clk_dm(K210_CLK_ROM, k210_clk_comp_nomux("rom", "aclk", div,
> gate)); +
> +	div = k210_clk_comp_div(base + K210_SYSCTL_THR0, 12, 4);
> +	gate = k210_clk_comp_gate(base + K210_SYSCTL_EN_PERI, 3);
> +	clk_dm(K210_CLK_DVP, k210_clk_comp_nomux("dvp", "aclk", div,
> gate)); +
> +	/*
> +	 * XXX: the next three clocks may be using an even divider
> +	 * c.f.
> <https://github.com/kendryte/kendryte-standalone-sdk/issues/99>
> +	 */
> +	div = k210_clk_comp_div(base + K210_SYSCTL_SEL0, 3, 3);
> +	gate = k210_clk_comp_gate(base + K210_SYSCTL_EN_CENT, 3);
> +	clk_dm(K210_CLK_APB0, k210_clk_comp_nomux("apb0", "aclk",
> div, gate)); +
> +	div = k210_clk_comp_div(base + K210_SYSCTL_SEL0, 6, 3);
> +	gate = k210_clk_comp_gate(base + K210_SYSCTL_EN_CENT, 4);
> +	clk_dm(K210_CLK_APB1, k210_clk_comp_nomux("apb1", "aclk",
> div, gate)); +
> +	div = k210_clk_comp_div(base + K210_SYSCTL_SEL0, 9, 3);
> +	gate = k210_clk_comp_gate(base + K210_SYSCTL_EN_CENT, 5);
> +	clk_dm(K210_CLK_APB2, k210_clk_comp_nomux("apb2", "aclk",
> div, gate)); +
> +	div = k210_clk_comp_div(base + K210_SYSCTL_THR0, 8, 4);
> +	gate = k210_clk_comp_gate(base + K210_SYSCTL_EN_PERI, 2);
> +	clk_dm(K210_CLK_AI, k210_clk_comp_nomux("ai", "pll1", div,
> gate)); +
> +	div = k210_clk_comp_div(base + K210_SYSCTL_THR3, 0, 16);
> +	gate = k210_clk_comp_gate(base + K210_SYSCTL_EN_PERI, 10);
> +	clk_dm(K210_CLK_I2S0,
> +	       k210_clk_comp_nomux("i2s0", "pll2_half", div, gate));
> +	
> +	div = k210_clk_comp_div(base + K210_SYSCTL_THR3, 16, 16);
> +	gate = k210_clk_comp_gate(base + K210_SYSCTL_EN_PERI, 11);
> +	clk_dm(K210_CLK_I2S1,
> +	       k210_clk_comp_nomux("i2s1", "pll2_half", div, gate));
> +
> +	div = k210_clk_comp_div(base + K210_SYSCTL_THR4, 0, 16);
> +	gate = k210_clk_comp_gate(base + K210_SYSCTL_EN_PERI, 12);
> +	clk_dm(K210_CLK_I2S2,
> +	       k210_clk_comp_nomux("i2s2", "pll2_half", div, gate));
> +
> +	div = k210_clk_comp_div(base + K210_SYSCTL_THR6, 0, 8);
> +	gate = k210_clk_comp_gate(base + K210_SYSCTL_EN_PERI, 24);
> +	clk_dm(K210_CLK_WDT0,
> +	       k210_clk_comp_nomux("wdt0", "in0_half", div, gate));
> +
> +	div = k210_clk_comp_div(base + K210_SYSCTL_THR6, 8, 8);
> +	gate = k210_clk_comp_gate(base + K210_SYSCTL_EN_PERI, 25);
> +	clk_dm(K210_CLK_WDT1,
> +	       k210_clk_comp_nomux("wdt1", "in0_half", div, gate));
> +
> +	div = k210_clk_comp_div(base + K210_SYSCTL_THR1, 0, 8);
> +	gate = k210_clk_comp_gate(base + K210_SYSCTL_EN_PERI, 6);
> +	clk_dm(K210_CLK_SPI0,
> +	       k210_clk_comp_nomux("spi0", "pll0_half", div, gate));
> +
> +	div = k210_clk_comp_div(base + K210_SYSCTL_THR1, 8, 8);
> +	gate = k210_clk_comp_gate(base + K210_SYSCTL_EN_PERI, 7);
> +	clk_dm(K210_CLK_SPI1,
> +	       k210_clk_comp_nomux("spi1", "pll0_half", div, gate));
> +
> +	div = k210_clk_comp_div(base + K210_SYSCTL_THR1, 16, 8);
> +	gate = k210_clk_comp_gate(base + K210_SYSCTL_EN_PERI, 8);
> +	clk_dm(K210_CLK_SPI2,
> +	       k210_clk_comp_nomux("spi2", "pll0_half", div, gate));
> +
> +	div = k210_clk_comp_div(base + K210_SYSCTL_THR5, 8, 8);
> +	gate = k210_clk_comp_gate(base + K210_SYSCTL_EN_PERI, 13);
> +	clk_dm(K210_CLK_I2C0,
> +	       k210_clk_comp_nomux("i2c0", "pll0_half", div, gate));
> +	
> +	div = k210_clk_comp_div(base + K210_SYSCTL_THR5, 16, 8);
> +	gate = k210_clk_comp_gate(base + K210_SYSCTL_EN_PERI, 14);
> +	clk_dm(K210_CLK_I2C1,
> +	       k210_clk_comp_nomux("i2c1", "pll0_half", div, gate));
> +
> +	div = k210_clk_comp_div(base + K210_SYSCTL_THR5, 24, 8);
> +	gate = k210_clk_comp_gate(base + K210_SYSCTL_EN_PERI, 15);
> +	clk_dm(K210_CLK_I2C2,
> +	       k210_clk_comp_nomux("i2c2", "pll0_half", div, gate));
> +
> +	/* Gated clocks */
> +	clk_dm(K210_CLK_CPU,
> +	       k210_clk_gate("cpu", "aclk", base +
> K210_SYSCTL_EN_CENT, 0));
> +	clk_dm(K210_CLK_DMA,
> +	       k210_clk_gate("dma", "aclk", base +
> K210_SYSCTL_EN_PERI, 1));
> +	clk_dm(K210_CLK_FFT,
> +	       k210_clk_gate("fft", "aclk", base +
> K210_SYSCTL_EN_PERI, 4));
> +	clk_dm(K210_CLK_GPIO,
> +	       k210_clk_gate("gpio", "apb0", base +
> K210_SYSCTL_EN_PERI, 5));
> +	clk_dm(K210_CLK_UART1,
> +	       k210_clk_gate("uart1", "apb0", base +
> K210_SYSCTL_EN_PERI, 16));
> +	clk_dm(K210_CLK_UART2,
> +	       k210_clk_gate("uart2", "apb0", base +
> K210_SYSCTL_EN_PERI, 17));
> +	clk_dm(K210_CLK_UART3,
> +	       k210_clk_gate("uart3", "apb0", base +
> K210_SYSCTL_EN_PERI, 18));
> +	clk_dm(K210_CLK_FPIOA,
> +	       k210_clk_gate("fpioa", "apb0", base +
> K210_SYSCTL_EN_PERI, 20));
> +	clk_dm(K210_CLK_SHA,
> +	       k210_clk_gate("sha", "apb0", base +
> K210_SYSCTL_EN_PERI, 26));
> +	clk_dm(K210_CLK_AES,
> +	       k210_clk_gate("aes", "apb1", base +
> K210_SYSCTL_EN_PERI, 19));
> +	clk_dm(K210_CLK_OTP,
> +	       k210_clk_gate("otp", "apb1", base +
> K210_SYSCTL_EN_PERI, 27));
> +	clk_dm(K210_CLK_RTC,
> +	       k210_clk_gate("rtc", in0, base + K210_SYSCTL_EN_PERI,
> 29)); +
> +cleanup_base:
> +	unmap_sysmem(base);
> +	return err;
> +}
> +
> +static const struct udevice_id k210_clk_ids[] = {
> +	{ .compatible = "kendryte,k210-clk" },
> +	{ },
> +};
> +
> +U_BOOT_DRIVER(k210_clk) = {
> +	.name = "k210_clk",
> +	.id = UCLASS_CLK,
> +	.of_match = k210_clk_ids,
> +	.ops = &k210_clk_ops,
> +	.probe = k210_clk_probe,
> +};
> diff --git a/drivers/clk/kendryte/clk.h b/drivers/clk/kendryte/clk.h
> new file mode 100644
> index 0000000000..17d0f5de1b
> --- /dev/null
> +++ b/drivers/clk/kendryte/clk.h
> @@ -0,0 +1,27 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + * Copyright (C) 2019 Sean Anderson <seanga2 at gmail.com>
> + */
> +
> +#ifndef K210_CLK_H
> +#define K210_CLK_H
> +
> +#define LOG_CATEGORY UCLASS_CLK
> +#include <linux/types.h>
> +#include <linux/clk-provider.h>
> +
> +static inline struct clk *k210_clk_gate(const char *name,
> +					const char *parent_name,
> +					void __iomem *reg, u8
> bit_idx) +{
> +	return clk_register_gate(NULL, name, parent_name, 0, reg,
> bit_idx, 0,
> +				 NULL);
> +}
> +
> +static inline struct clk *k210_clk_half(const char *name,
> +				        const char *parent_name)
> +{
> +	return clk_register_fixed_factor(NULL, name, parent_name, 0,
> 1, 2); +}
> +
> +#endif /* K210_CLK_H */
> diff --git a/include/dt-bindings/clock/k210-sysctl.h
> b/include/dt-bindings/clock/k210-sysctl.h new file mode 100644
> index 0000000000..7c471f9893
> --- /dev/null
> +++ b/include/dt-bindings/clock/k210-sysctl.h
> @@ -0,0 +1,53 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + * Copyright (C) 2019 Sean Anderson <seanga2 at gmail.com>
> + */
> +
> +#ifndef CLOCK_K210_SYSCTL_H
> +#define CLOCK_K210_SYSCTL_H
> +
> +/*
> + * Arbitrary identifiers for clocks. 0 is unused since clk_enable
> thinks it
> + * means "no clock".
> + */
> +#define K210_CLK_PLL0   1
> +#define K210_CLK_PLL1   2
> +#define K210_CLK_PLL2   3
> +#define K210_CLK_CPU    4
> +#define K210_CLK_SRAM0  5
> +#define K210_CLK_SRAM1  6
> +#define K210_CLK_APB0   7
> +#define K210_CLK_APB1   8
> +#define K210_CLK_APB2   9
> +#define K210_CLK_ROM    10
> +#define K210_CLK_DMA    11
> +#define K210_CLK_AI     12
> +#define K210_CLK_DVP    13
> +#define K210_CLK_FFT    14
> +#define K210_CLK_GPIO   15
> +#define K210_CLK_SPI0   16
> +#define K210_CLK_SPI1   17
> +#define K210_CLK_SPI2   18
> +#define K210_CLK_SPI3   19
> +#define K210_CLK_I2S0   20
> +#define K210_CLK_I2S1   21
> +#define K210_CLK_I2S2   22
> +#define K210_CLK_I2C0   23
> +#define K210_CLK_I2C1   24
> +#define K210_CLK_I2C2   25
> +#define K210_CLK_UART1  26
> +#define K210_CLK_UART2  27
> +#define K210_CLK_UART3  28
> +#define K210_CLK_AES    29
> +#define K210_CLK_FPIOA  30
> +#define K210_CLK_TIMER0 31
> +#define K210_CLK_TIMER1 32
> +#define K210_CLK_TIMER2 33
> +#define K210_CLK_WDT0   34
> +#define K210_CLK_WDT1   35
> +#define K210_CLK_SHA    36
> +#define K210_CLK_OTP    37
> +#define K210_CLK_RTC    40
> +#define K210_CLK_ACLK   41
> +
> +#endif /* CLOCK_K210_SYSCTL_H */
> diff --git a/include/dt-bindings/mfd/k210-sysctl.h
> b/include/dt-bindings/mfd/k210-sysctl.h new file mode 100644
> index 0000000000..3457240f03
> --- /dev/null
> +++ b/include/dt-bindings/mfd/k210-sysctl.h
> @@ -0,0 +1,38 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + * Copyright (C) 2019 Sean Anderson <seanga2 at gmail.com>
> + */
> +
> +#ifndef K210_SYSCTL_H
> +#define K210_SYSCTL_H
> + 
> +/* Taken from kendryte-standalone-sdk/lib/drivers/include/sysctl.h */
> +#define K210_SYSCTL_GIT_ID     0x00 /* Git short commit id */
> +#define K210_SYSCTL_CLK_FREQ   0x04 /* System clock base frequency */
> +#define K210_SYSCTL_PLL0       0x08 /* PLL0 controller */
> +#define K210_SYSCTL_PLL1       0x0C /* PLL1 controller */
> +#define K210_SYSCTL_PLL2       0x10 /* PLL2 controller */
> +#define K210_SYSCTL_PLL_LOCK   0x18 /* PLL lock tester */
> +#define K210_SYSCTL_ROM_ERROR  0x1C /* AXI ROM detector */
> +#define K210_SYSCTL_SEL0       0x20 /* Clock select controller0 */
> +#define K210_SYSCTL_SEL1       0x24 /* Clock select controller1 */
> +#define K210_SYSCTL_EN_CENT    0x28 /* Central clock enable */
> +#define K210_SYSCTL_EN_PERI    0x2C /* Peripheral clock enable */
> +#define K210_SYSCTL_SOFT_RESET 0x30 /* Soft reset ctrl */
> +#define K210_SYSCTL_PERI_RESET 0x34 /* Peripheral reset controller */
> +#define K210_SYSCTL_THR0       0x38 /* Clock threshold controller 0
> */ +#define K210_SYSCTL_THR1       0x3C /* Clock threshold controller
> 1 */ +#define K210_SYSCTL_THR2       0x40 /* Clock threshold
> controller 2 */ +#define K210_SYSCTL_THR3       0x44 /* Clock
> threshold controller 3 */ +#define K210_SYSCTL_THR4       0x48 /*
> Clock threshold controller 4 */ +#define K210_SYSCTL_THR5       0x4C
> /* Clock threshold controller 5 */ +#define K210_SYSCTL_THR6
> 0x50 /* Clock threshold controller 6 */ +#define K210_SYSCTL_MISC
>   0x54 /* Miscellaneous controller */ +#define K210_SYSCTL_PERI
> 0x58 /* Peripheral controller */ +#define K210_SYSCTL_SPI_SLEEP  0x5C
> /* SPI sleep controller */ +#define K210_SYSCTL_RESET_STAT 0x60 /*
> Reset source status */ +#define K210_SYSCTL_DMA_SEL0   0x64 /* DMA
> handshake selector */ +#define K210_SYSCTL_DMA_SEL1   0x68 /* DMA
> handshake selector */ +#define K210_SYSCTL_POWER_SEL  0x6C /* IO
> Power Mode Select controller */ +
> +#endif /* K210_SYSCTL_H */




Best regards,

Lukasz Majewski

--

DENX Software Engineering GmbH,      Managing Director: Wolfgang Denk
HRB 165235 Munich, Office: Kirchenstr.5, D-82194 Groebenzell, Germany
Phone: (+49)-8142-66989-59 Fax: (+49)-8142-66989-80 Email: lukma at denx.de
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 488 bytes
Desc: OpenPGP digital signature
URL: <https://lists.denx.de/pipermail/u-boot/attachments/20200206/649d4bee/attachment.sig>
diff mbox series

Patch

diff --git a/drivers/clk/kendryte/Kconfig b/drivers/clk/kendryte/Kconfig
index 7b69c8afaf..073fca0781 100644
--- a/drivers/clk/kendryte/Kconfig
+++ b/drivers/clk/kendryte/Kconfig
@@ -1,6 +1,6 @@ 
 config CLK_K210
 	bool "Clock support for Kendryte K210"
-	depends on CLK && CLK_CCF
+	depends on CLK && CLK_CCF && CLK_COMPOSITE_CCF
 	help
 	  This enables support clock driver for Kendryte K210 platforms.
 
diff --git a/drivers/clk/kendryte/Makefile b/drivers/clk/kendryte/Makefile
index c56d93ea1c..d26bce954f 100644
--- a/drivers/clk/kendryte/Makefile
+++ b/drivers/clk/kendryte/Makefile
@@ -1 +1 @@ 
-obj-y += pll.o
+obj-y += clk.o pll.o
diff --git a/drivers/clk/kendryte/clk.c b/drivers/clk/kendryte/clk.c
new file mode 100644
index 0000000000..aaa7420c84
--- /dev/null
+++ b/drivers/clk/kendryte/clk.c
@@ -0,0 +1,390 @@ 
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2019 Sean Anderson <seanga2 at gmail.com>
+ */
+#include "clk.h"
+
+#include <asm/io.h>
+#include <dt-bindings/clock/k210-sysctl.h>
+#include <dt-bindings/mfd/k210-sysctl.h>
+#include <dm.h>
+#include <log.h>
+#include <mapmem.h>
+
+#include "pll.h"
+
+static ulong k210_clk_get_rate(struct clk *clk)
+{
+	struct clk *c;
+	int err = clk_get_by_id(clk->id, &c);
+
+	if (err)
+		return err;
+	return clk_get_rate(c);
+}
+
+static ulong k210_clk_set_rate(struct clk *clk, unsigned long rate)
+{
+	struct clk *c;
+	int err = clk_get_by_id(clk->id, &c);
+
+	if (err)
+		return err;
+	return clk_set_rate(c, rate);
+}
+
+static int k210_clk_set_parent(struct clk *clk, struct clk *parent)
+{
+	struct clk *c, *p;
+	int err = clk_get_by_id(clk->id, &c);
+
+	if (err)
+		return err;
+	
+	err = clk_get_by_id(parent->id, &p);
+	if (err)
+		return err;
+
+	return clk_set_parent(c, p);
+}
+
+static int k210_clk_endisable(struct clk *clk, bool enable)
+{
+	struct clk *c;
+	int err = clk_get_by_id(clk->id, &c);
+
+	if (err)
+		return err;
+	return enable ? clk_enable(c) : clk_disable(c);
+}
+
+static int k210_clk_enable(struct clk *clk)
+{
+	return k210_clk_endisable(clk, true);
+}
+
+static int k210_clk_disable(struct clk *clk)
+{
+	return k210_clk_endisable(clk, false);
+}
+
+static const struct clk_ops k210_clk_ops = {
+	.set_rate = k210_clk_set_rate,
+	.get_rate = k210_clk_get_rate,
+	.set_parent = k210_clk_set_parent,
+	.enable = k210_clk_enable,
+	.disable = k210_clk_disable,
+};
+
+/* The first clock is in0, which is filled in by k210_clk_probe */
+static const char *generic_sels[] = { NULL, "pll0", };
+static const char *aclk_sels[] = { "in0_half", "pll0_half", };
+static const char *pll2_sels[] = { NULL, "pll0", "pll1", };
+
+static struct clk_divider *k210_clk_comp_div_flags(void __iomem *reg, u8 shift,
+						   u8 width, u8 flags)
+{
+	struct clk_divider *div;
+
+	div = kzalloc(sizeof(*div), GFP_KERNEL);
+	if (!div)
+		return div;
+	div->reg = reg;
+	div->shift = shift;
+	div->width = width;
+	div->flags = flags;
+	return div;
+}
+
+static inline struct clk_divider *k210_clk_comp_div(void __iomem *reg, u8 shift,
+						    u8 width)
+{
+	return k210_clk_comp_div_flags(reg, shift, width, 0);
+}
+
+static struct clk_gate *k210_clk_comp_gate(void __iomem *reg, u8 bit_idx)
+{
+	struct clk_gate *gate;
+
+	gate = kzalloc(sizeof(*gate), GFP_KERNEL);
+	if (!gate)
+		return gate;
+	gate->reg = reg;
+	gate->bit_idx = bit_idx;
+	return gate;
+}
+
+static struct clk_mux *k210_clk_comp_mux(const char *parent_names[],
+				         u8 num_parents, void __iomem *reg,
+				         u8 shift, u8 width)
+{
+	struct clk_mux *mux;
+
+	mux = kzalloc(sizeof(*mux), GFP_KERNEL);
+	if (!mux)
+		return mux;
+	mux->reg = reg;
+	mux->mask = BIT(width) - 1;
+	mux->shift = shift;
+	mux->parent_names = parent_names;
+	mux->num_parents = num_parents;
+	return mux;
+}
+
+static struct clk *k210_clk_comp_nomux(const char *name, const char *parent,
+					 struct clk_divider *div,
+					 struct clk_gate *gate)
+{
+	if (!div || !gate) {
+		kfree(div);
+		kfree(gate);
+		return ERR_PTR(-ENOMEM);
+	}
+	return clk_register_composite(NULL, name, &parent, 1,
+				      NULL, NULL,
+				      &div->clk, &clk_divider_ops,
+				      &gate->clk, &clk_gate_ops, 0);
+}
+
+static struct clk *k210_clk_comp(const char *name, struct clk_divider *div,
+				 struct clk_gate *gate, struct clk_mux *mux)
+{
+	if (!div || !gate || !mux) {
+		kfree(div);
+		kfree(gate);
+		kfree(mux);
+		return ERR_PTR(-ENOMEM);
+	}
+	return clk_register_composite(NULL, name, generic_sels,
+				      ARRAY_SIZE(generic_sels),
+				      &mux->clk, &clk_mux_ops,
+				      &div->clk, &clk_divider_ops,
+				      &gate->clk, &clk_gate_ops, 0);
+}
+
+static int k210_clk_probe(struct udevice *dev)
+{
+	int err;
+	const char *in0;
+	struct clk in0_clk;
+	struct clk_divider *div;
+	struct clk_gate *gate;
+	struct clk_mux *mux;
+	struct k210_pll *pll;
+	void *base;
+
+	base = dev_read_addr_ptr(dev_get_parent(dev));
+	if (!base)
+		return -EINVAL;
+
+	err = clk_get_by_index(dev, 0, &in0_clk);
+	if (err)
+		goto cleanup_base;
+	in0 = in0_clk.dev->name;
+	generic_sels[0] = in0;
+	pll2_sels[0] = in0;
+
+	/* PLLs */
+	clk_dm(K210_CLK_PLL0, k210_clk_pll("pll0", in0, base + K210_SYSCTL_PLL0,
+					   base + K210_SYSCTL_PLL_LOCK, 0, 2));
+	clk_dm(K210_CLK_PLL1, k210_clk_pll("pll1", in0, base + K210_SYSCTL_PLL1,
+					   base + K210_SYSCTL_PLL_LOCK, 8, 1));
+	/* PLL2 is muxed, so set up a composite clock */
+	mux = k210_clk_comp_mux(pll2_sels, ARRAY_SIZE(pll2_sels),
+				base + K210_SYSCTL_PLL2, 26, 2);
+	pll = k210_clk_comp_pll(base + K210_SYSCTL_PLL2,
+				base + K210_SYSCTL_PLL_LOCK, 16, 1);
+	if (!mux || !pll) {
+		kfree(mux);
+		kfree(pll);
+	} else {
+		clk_dm(K210_CLK_PLL2,
+		       clk_register_composite(NULL, "pll2", pll2_sels,
+					      ARRAY_SIZE(pll2_sels),
+					      &mux->clk, &clk_mux_ops,
+					      &pll->clk, &k210_pll_ops,
+					      &pll->clk, &k210_pll_ops, 0));
+	}
+
+	/* Half-frequency clocks for "even" dividers */
+	k210_clk_half("in0_half", in0);
+	k210_clk_half("pll0_half", "pll0");
+	k210_clk_half("pll2_half", "pll2");
+
+	/* Muxed clocks */
+	div = k210_clk_comp_div_flags(base + K210_SYSCTL_SEL0, 1, 2,
+				      CLK_DIVIDER_POWER_OF_TWO);
+	mux = k210_clk_comp_mux(aclk_sels, ARRAY_SIZE(aclk_sels),
+				base + K210_SYSCTL_SEL0, 0, 1);
+	if (!div || !mux) {
+		kfree(div);
+		kfree(mux);
+	} else {
+		clk_dm(K210_CLK_ACLK,
+		       clk_register_composite(NULL, "aclk", aclk_sels,
+					      ARRAY_SIZE(aclk_sels),
+					      &mux->clk, &clk_mux_ops,
+					      &div->clk, &clk_divider_ops,
+					      NULL, NULL, 0));
+	}
+
+	div = k210_clk_comp_div(base + K210_SYSCTL_SEL0, 1, 2);
+	gate = k210_clk_comp_gate(base + K210_SYSCTL_EN_PERI, 9);
+	mux = k210_clk_comp_mux(generic_sels, ARRAY_SIZE(generic_sels),
+				base + K210_SYSCTL_SEL0, 12, 1);
+	clk_dm(K210_CLK_SPI3, k210_clk_comp("spi3", div, gate, mux));
+	
+	div = k210_clk_comp_div(base + K210_SYSCTL_THR2, 8, 0);
+	gate = k210_clk_comp_gate(base + K210_SYSCTL_EN_PERI, 21);
+	mux = k210_clk_comp_mux(generic_sels, ARRAY_SIZE(generic_sels),
+				base + K210_SYSCTL_SEL0, 13, 1);
+	clk_dm(K210_CLK_TIMER0, k210_clk_comp("timer0", div, gate, mux));
+
+	div = k210_clk_comp_div(base + K210_SYSCTL_THR2, 8, 8);
+	gate = k210_clk_comp_gate(base + K210_SYSCTL_EN_PERI, 22);
+	mux = k210_clk_comp_mux(generic_sels, ARRAY_SIZE(generic_sels),
+				base + K210_SYSCTL_SEL0, 14, 1);
+	clk_dm(K210_CLK_TIMER1, k210_clk_comp("timer1", div, gate, mux));
+	
+	div = k210_clk_comp_div(base + K210_SYSCTL_THR2, 8, 16);
+	gate = k210_clk_comp_gate(base + K210_SYSCTL_EN_PERI, 23);
+	mux = k210_clk_comp_mux(generic_sels, ARRAY_SIZE(generic_sels),
+				base + K210_SYSCTL_SEL0, 15, 1);
+	clk_dm(K210_CLK_TIMER2, k210_clk_comp("timer2", div, gate, mux));
+
+
+	/* Dividing clocks, no mux */
+	div = k210_clk_comp_div(base + K210_SYSCTL_THR0, 0, 4);
+	gate = k210_clk_comp_gate(base + K210_SYSCTL_EN_CENT, 1);
+	clk_dm(K210_CLK_SRAM0, k210_clk_comp_nomux("sram0", "aclk", div, gate));
+
+	div = k210_clk_comp_div(base + K210_SYSCTL_THR0, 4, 4);
+	gate = k210_clk_comp_gate(base + K210_SYSCTL_EN_CENT, 2);
+	clk_dm(K210_CLK_SRAM1, k210_clk_comp_nomux("sram1", "aclk", div, gate));
+
+	div = k210_clk_comp_div(base + K210_SYSCTL_THR0, 16, 4);
+	gate = k210_clk_comp_gate(base + K210_SYSCTL_EN_PERI, 0);
+	clk_dm(K210_CLK_ROM, k210_clk_comp_nomux("rom", "aclk", div, gate));
+
+	div = k210_clk_comp_div(base + K210_SYSCTL_THR0, 12, 4);
+	gate = k210_clk_comp_gate(base + K210_SYSCTL_EN_PERI, 3);
+	clk_dm(K210_CLK_DVP, k210_clk_comp_nomux("dvp", "aclk", div, gate));
+
+	/*
+	 * XXX: the next three clocks may be using an even divider
+	 * c.f. <https://github.com/kendryte/kendryte-standalone-sdk/issues/99>
+	 */
+	div = k210_clk_comp_div(base + K210_SYSCTL_SEL0, 3, 3);
+	gate = k210_clk_comp_gate(base + K210_SYSCTL_EN_CENT, 3);
+	clk_dm(K210_CLK_APB0, k210_clk_comp_nomux("apb0", "aclk", div, gate));
+
+	div = k210_clk_comp_div(base + K210_SYSCTL_SEL0, 6, 3);
+	gate = k210_clk_comp_gate(base + K210_SYSCTL_EN_CENT, 4);
+	clk_dm(K210_CLK_APB1, k210_clk_comp_nomux("apb1", "aclk", div, gate));
+
+	div = k210_clk_comp_div(base + K210_SYSCTL_SEL0, 9, 3);
+	gate = k210_clk_comp_gate(base + K210_SYSCTL_EN_CENT, 5);
+	clk_dm(K210_CLK_APB2, k210_clk_comp_nomux("apb2", "aclk", div, gate));
+
+	div = k210_clk_comp_div(base + K210_SYSCTL_THR0, 8, 4);
+	gate = k210_clk_comp_gate(base + K210_SYSCTL_EN_PERI, 2);
+	clk_dm(K210_CLK_AI, k210_clk_comp_nomux("ai", "pll1", div, gate));
+
+	div = k210_clk_comp_div(base + K210_SYSCTL_THR3, 0, 16);
+	gate = k210_clk_comp_gate(base + K210_SYSCTL_EN_PERI, 10);
+	clk_dm(K210_CLK_I2S0,
+	       k210_clk_comp_nomux("i2s0", "pll2_half", div, gate));
+	
+	div = k210_clk_comp_div(base + K210_SYSCTL_THR3, 16, 16);
+	gate = k210_clk_comp_gate(base + K210_SYSCTL_EN_PERI, 11);
+	clk_dm(K210_CLK_I2S1,
+	       k210_clk_comp_nomux("i2s1", "pll2_half", div, gate));
+
+	div = k210_clk_comp_div(base + K210_SYSCTL_THR4, 0, 16);
+	gate = k210_clk_comp_gate(base + K210_SYSCTL_EN_PERI, 12);
+	clk_dm(K210_CLK_I2S2,
+	       k210_clk_comp_nomux("i2s2", "pll2_half", div, gate));
+
+	div = k210_clk_comp_div(base + K210_SYSCTL_THR6, 0, 8);
+	gate = k210_clk_comp_gate(base + K210_SYSCTL_EN_PERI, 24);
+	clk_dm(K210_CLK_WDT0,
+	       k210_clk_comp_nomux("wdt0", "in0_half", div, gate));
+
+	div = k210_clk_comp_div(base + K210_SYSCTL_THR6, 8, 8);
+	gate = k210_clk_comp_gate(base + K210_SYSCTL_EN_PERI, 25);
+	clk_dm(K210_CLK_WDT1,
+	       k210_clk_comp_nomux("wdt1", "in0_half", div, gate));
+
+	div = k210_clk_comp_div(base + K210_SYSCTL_THR1, 0, 8);
+	gate = k210_clk_comp_gate(base + K210_SYSCTL_EN_PERI, 6);
+	clk_dm(K210_CLK_SPI0,
+	       k210_clk_comp_nomux("spi0", "pll0_half", div, gate));
+
+	div = k210_clk_comp_div(base + K210_SYSCTL_THR1, 8, 8);
+	gate = k210_clk_comp_gate(base + K210_SYSCTL_EN_PERI, 7);
+	clk_dm(K210_CLK_SPI1,
+	       k210_clk_comp_nomux("spi1", "pll0_half", div, gate));
+
+	div = k210_clk_comp_div(base + K210_SYSCTL_THR1, 16, 8);
+	gate = k210_clk_comp_gate(base + K210_SYSCTL_EN_PERI, 8);
+	clk_dm(K210_CLK_SPI2,
+	       k210_clk_comp_nomux("spi2", "pll0_half", div, gate));
+
+	div = k210_clk_comp_div(base + K210_SYSCTL_THR5, 8, 8);
+	gate = k210_clk_comp_gate(base + K210_SYSCTL_EN_PERI, 13);
+	clk_dm(K210_CLK_I2C0,
+	       k210_clk_comp_nomux("i2c0", "pll0_half", div, gate));
+	
+	div = k210_clk_comp_div(base + K210_SYSCTL_THR5, 16, 8);
+	gate = k210_clk_comp_gate(base + K210_SYSCTL_EN_PERI, 14);
+	clk_dm(K210_CLK_I2C1,
+	       k210_clk_comp_nomux("i2c1", "pll0_half", div, gate));
+
+	div = k210_clk_comp_div(base + K210_SYSCTL_THR5, 24, 8);
+	gate = k210_clk_comp_gate(base + K210_SYSCTL_EN_PERI, 15);
+	clk_dm(K210_CLK_I2C2,
+	       k210_clk_comp_nomux("i2c2", "pll0_half", div, gate));
+
+	/* Gated clocks */
+	clk_dm(K210_CLK_CPU,
+	       k210_clk_gate("cpu", "aclk", base + K210_SYSCTL_EN_CENT, 0));
+	clk_dm(K210_CLK_DMA,
+	       k210_clk_gate("dma", "aclk", base + K210_SYSCTL_EN_PERI, 1));
+	clk_dm(K210_CLK_FFT,
+	       k210_clk_gate("fft", "aclk", base + K210_SYSCTL_EN_PERI, 4));
+	clk_dm(K210_CLK_GPIO,
+	       k210_clk_gate("gpio", "apb0", base + K210_SYSCTL_EN_PERI, 5));
+	clk_dm(K210_CLK_UART1,
+	       k210_clk_gate("uart1", "apb0", base + K210_SYSCTL_EN_PERI, 16));
+	clk_dm(K210_CLK_UART2,
+	       k210_clk_gate("uart2", "apb0", base + K210_SYSCTL_EN_PERI, 17));
+	clk_dm(K210_CLK_UART3,
+	       k210_clk_gate("uart3", "apb0", base + K210_SYSCTL_EN_PERI, 18));
+	clk_dm(K210_CLK_FPIOA,
+	       k210_clk_gate("fpioa", "apb0", base + K210_SYSCTL_EN_PERI, 20));
+	clk_dm(K210_CLK_SHA,
+	       k210_clk_gate("sha", "apb0", base + K210_SYSCTL_EN_PERI, 26));
+	clk_dm(K210_CLK_AES,
+	       k210_clk_gate("aes", "apb1", base + K210_SYSCTL_EN_PERI, 19));
+	clk_dm(K210_CLK_OTP,
+	       k210_clk_gate("otp", "apb1", base + K210_SYSCTL_EN_PERI, 27));
+	clk_dm(K210_CLK_RTC,
+	       k210_clk_gate("rtc", in0, base + K210_SYSCTL_EN_PERI, 29));
+
+cleanup_base:
+	unmap_sysmem(base);
+	return err;
+}
+
+static const struct udevice_id k210_clk_ids[] = {
+	{ .compatible = "kendryte,k210-clk" },
+	{ },
+};
+
+U_BOOT_DRIVER(k210_clk) = {
+	.name = "k210_clk",
+	.id = UCLASS_CLK,
+	.of_match = k210_clk_ids,
+	.ops = &k210_clk_ops,
+	.probe = k210_clk_probe,
+};
diff --git a/drivers/clk/kendryte/clk.h b/drivers/clk/kendryte/clk.h
new file mode 100644
index 0000000000..17d0f5de1b
--- /dev/null
+++ b/drivers/clk/kendryte/clk.h
@@ -0,0 +1,27 @@ 
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (C) 2019 Sean Anderson <seanga2 at gmail.com>
+ */
+
+#ifndef K210_CLK_H
+#define K210_CLK_H
+
+#define LOG_CATEGORY UCLASS_CLK
+#include <linux/types.h>
+#include <linux/clk-provider.h>
+
+static inline struct clk *k210_clk_gate(const char *name,
+					const char *parent_name,
+					void __iomem *reg, u8 bit_idx)
+{
+	return clk_register_gate(NULL, name, parent_name, 0, reg, bit_idx, 0,
+				 NULL);
+}
+
+static inline struct clk *k210_clk_half(const char *name,
+				        const char *parent_name)
+{
+	return clk_register_fixed_factor(NULL, name, parent_name, 0, 1, 2);
+}
+
+#endif /* K210_CLK_H */
diff --git a/include/dt-bindings/clock/k210-sysctl.h b/include/dt-bindings/clock/k210-sysctl.h
new file mode 100644
index 0000000000..7c471f9893
--- /dev/null
+++ b/include/dt-bindings/clock/k210-sysctl.h
@@ -0,0 +1,53 @@ 
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (C) 2019 Sean Anderson <seanga2 at gmail.com>
+ */
+
+#ifndef CLOCK_K210_SYSCTL_H
+#define CLOCK_K210_SYSCTL_H
+
+/*
+ * Arbitrary identifiers for clocks. 0 is unused since clk_enable thinks it
+ * means "no clock".
+ */
+#define K210_CLK_PLL0   1
+#define K210_CLK_PLL1   2
+#define K210_CLK_PLL2   3
+#define K210_CLK_CPU    4
+#define K210_CLK_SRAM0  5
+#define K210_CLK_SRAM1  6
+#define K210_CLK_APB0   7
+#define K210_CLK_APB1   8
+#define K210_CLK_APB2   9
+#define K210_CLK_ROM    10
+#define K210_CLK_DMA    11
+#define K210_CLK_AI     12
+#define K210_CLK_DVP    13
+#define K210_CLK_FFT    14
+#define K210_CLK_GPIO   15
+#define K210_CLK_SPI0   16
+#define K210_CLK_SPI1   17
+#define K210_CLK_SPI2   18
+#define K210_CLK_SPI3   19
+#define K210_CLK_I2S0   20
+#define K210_CLK_I2S1   21
+#define K210_CLK_I2S2   22
+#define K210_CLK_I2C0   23
+#define K210_CLK_I2C1   24
+#define K210_CLK_I2C2   25
+#define K210_CLK_UART1  26
+#define K210_CLK_UART2  27
+#define K210_CLK_UART3  28
+#define K210_CLK_AES    29
+#define K210_CLK_FPIOA  30
+#define K210_CLK_TIMER0 31
+#define K210_CLK_TIMER1 32
+#define K210_CLK_TIMER2 33
+#define K210_CLK_WDT0   34
+#define K210_CLK_WDT1   35
+#define K210_CLK_SHA    36
+#define K210_CLK_OTP    37
+#define K210_CLK_RTC    40
+#define K210_CLK_ACLK   41
+
+#endif /* CLOCK_K210_SYSCTL_H */
diff --git a/include/dt-bindings/mfd/k210-sysctl.h b/include/dt-bindings/mfd/k210-sysctl.h
new file mode 100644
index 0000000000..3457240f03
--- /dev/null
+++ b/include/dt-bindings/mfd/k210-sysctl.h
@@ -0,0 +1,38 @@ 
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (C) 2019 Sean Anderson <seanga2 at gmail.com>
+ */
+
+#ifndef K210_SYSCTL_H
+#define K210_SYSCTL_H
+ 
+/* Taken from kendryte-standalone-sdk/lib/drivers/include/sysctl.h */
+#define K210_SYSCTL_GIT_ID     0x00 /* Git short commit id */
+#define K210_SYSCTL_CLK_FREQ   0x04 /* System clock base frequency */
+#define K210_SYSCTL_PLL0       0x08 /* PLL0 controller */
+#define K210_SYSCTL_PLL1       0x0C /* PLL1 controller */
+#define K210_SYSCTL_PLL2       0x10 /* PLL2 controller */
+#define K210_SYSCTL_PLL_LOCK   0x18 /* PLL lock tester */
+#define K210_SYSCTL_ROM_ERROR  0x1C /* AXI ROM detector */
+#define K210_SYSCTL_SEL0       0x20 /* Clock select controller0 */
+#define K210_SYSCTL_SEL1       0x24 /* Clock select controller1 */
+#define K210_SYSCTL_EN_CENT    0x28 /* Central clock enable */
+#define K210_SYSCTL_EN_PERI    0x2C /* Peripheral clock enable */
+#define K210_SYSCTL_SOFT_RESET 0x30 /* Soft reset ctrl */
+#define K210_SYSCTL_PERI_RESET 0x34 /* Peripheral reset controller */
+#define K210_SYSCTL_THR0       0x38 /* Clock threshold controller 0 */
+#define K210_SYSCTL_THR1       0x3C /* Clock threshold controller 1 */
+#define K210_SYSCTL_THR2       0x40 /* Clock threshold controller 2 */
+#define K210_SYSCTL_THR3       0x44 /* Clock threshold controller 3 */
+#define K210_SYSCTL_THR4       0x48 /* Clock threshold controller 4 */
+#define K210_SYSCTL_THR5       0x4C /* Clock threshold controller 5 */
+#define K210_SYSCTL_THR6       0x50 /* Clock threshold controller 6 */
+#define K210_SYSCTL_MISC       0x54 /* Miscellaneous controller */
+#define K210_SYSCTL_PERI       0x58 /* Peripheral controller */
+#define K210_SYSCTL_SPI_SLEEP  0x5C /* SPI sleep controller */
+#define K210_SYSCTL_RESET_STAT 0x60 /* Reset source status */
+#define K210_SYSCTL_DMA_SEL0   0x64 /* DMA handshake selector */
+#define K210_SYSCTL_DMA_SEL1   0x68 /* DMA handshake selector */
+#define K210_SYSCTL_POWER_SEL  0x6C /* IO Power Mode Select controller */
+
+#endif /* K210_SYSCTL_H */