summaryrefslogtreecommitdiffstats
path: root/drivers/clk/rockchip/clk.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/clk/rockchip/clk.c')
-rw-r--r--drivers/clk/rockchip/clk.c531
1 files changed, 417 insertions, 114 deletions
diff --git a/drivers/clk/rockchip/clk.c b/drivers/clk/rockchip/clk.c
index 9e0cbadd57..aca107a45d 100644
--- a/drivers/clk/rockchip/clk.c
+++ b/drivers/clk/rockchip/clk.c
@@ -1,32 +1,30 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (c) 2014 MundoReader S.L.
* Author: Heiko Stuebner <heiko@sntech.de>
*
+ * Copyright (c) 2016 Rockchip Electronics Co. Ltd.
+ * Author: Xing Zheng <zhengxing@rock-chips.com>
+ *
* based on
*
* samsung/clk.c
* Copyright (c) 2013 Samsung Electronics Co., Ltd.
* Copyright (c) 2013 Linaro Ltd.
* Author: Thomas Abraham <thomas.ab@samsung.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
*/
#include <common.h>
#include <malloc.h>
#include <linux/clk.h>
+#include <linux/regmap.h>
+#include <mfd/syscon.h>
+#include <linux/spinlock.h>
+#include <linux/rational.h>
+#include <restart.h>
#include "clk.h"
-#include <init.h>
-/**
+/*
* Register a clock branch.
* Most clock branches have a form like
*
@@ -37,134 +35,369 @@
* sometimes without one of those components.
*/
static struct clk *rockchip_clk_register_branch(const char *name,
- const char **parent_names, u8 num_parents, void __iomem *base,
+ const char *const *parent_names, u8 num_parents,
+ void __iomem *base,
int muxdiv_offset, u8 mux_shift, u8 mux_width, u8 mux_flags,
- u8 div_shift, u8 div_width, u8 div_flags,
+ int div_offset, u8 div_shift, u8 div_width, u8 div_flags,
struct clk_div_table *div_table, int gate_offset,
- u8 gate_shift, u8 gate_flags, unsigned long flags
- )
+ u8 gate_shift, u8 gate_flags, unsigned long flags,
+ spinlock_t *lock)
{
struct clk *clk;
- struct clk *mux = NULL;
- struct clk *gate = NULL;
- struct clk *div = NULL;
+ struct clk_mux *mux = NULL;
+ struct clk_gate *gate = NULL;
+ struct clk_divider *div = NULL;
+ int ret;
if (num_parents > 1) {
- mux = clk_mux_alloc(name, 0, base + muxdiv_offset, mux_shift,
- mux_width, parent_names, num_parents, mux_flags);
+ mux = kzalloc(sizeof(*mux), GFP_KERNEL);
if (!mux)
return ERR_PTR(-ENOMEM);
+
+ mux->reg = base + muxdiv_offset;
+ mux->shift = mux_shift;
+ mux->width = mux_width;
+ mux->flags = mux_flags;
+ mux->lock = lock;
+ mux->hw.clk.name = basprintf("%s.mux", name);
+ mux->hw.clk.ops = (mux_flags & CLK_MUX_READ_ONLY) ? &clk_mux_ro_ops
+ : &clk_mux_ops;
}
if (gate_offset >= 0) {
- gate = clk_gate_alloc(name, *parent_names, base + gate_offset,
- gate_shift, flags, gate_flags);
- if (!gate)
- return ERR_PTR(-ENOMEM);
+ gate = kzalloc(sizeof(*gate), GFP_KERNEL);
+ if (!gate) {
+ ret = -ENOMEM;
+ goto err_gate;
+ }
+
+ gate->flags = gate_flags;
+ gate->reg = base + gate_offset;
+ gate->shift = gate_shift;
+ gate->lock = lock;
+ gate->hw.clk.name = basprintf("%s.gate", name);
+ gate->hw.clk.ops = &clk_gate_ops;
}
if (div_width > 0) {
- div = clk_divider_alloc(name, *parent_names, 0,
- base + muxdiv_offset, div_shift, div_width, div_flags);
- if (!div)
- return ERR_PTR(-ENOMEM);
+ div = kzalloc(sizeof(*div), GFP_KERNEL);
+ if (!div) {
+ ret = -ENOMEM;
+ goto err_div;
+ }
+
+ div->flags = div_flags;
+ if (div_offset)
+ div->reg = base + div_offset;
+ else
+ div->reg = base + muxdiv_offset;
+ div->shift = div_shift;
+ div->width = div_width;
+ div->lock = lock;
+ div->table = div_table;
+ div->hw.clk.name = basprintf("%s.div", name);
+ div->hw.clk.ops = (div_flags & CLK_DIVIDER_READ_ONLY)
+ ? &clk_divider_ro_ops
+ : &clk_divider_ops;
}
clk = clk_register_composite(name, parent_names, num_parents,
- mux,
- div,
- gate,
- flags);
+ mux ? &mux->hw.clk : NULL,
+ div ? &div->hw.clk : NULL,
+ gate ? &gate->hw.clk : NULL,
+ flags);
+ if (IS_ERR(clk)) {
+ kfree(div);
+ kfree(gate);
+ return ERR_CAST(clk);
+ }
return clk;
+err_div:
+ kfree(gate);
+err_gate:
+ kfree(mux);
+ return ERR_PTR(ret);
+}
+
+struct rockchip_clk_frac {
+ struct clk_fractional_divider div;
+ struct clk_gate gate;
+
+ struct clk_mux mux;
+ const struct clk_ops *mux_ops;
+ int mux_frac_idx;
+
+ bool rate_change_remuxed;
+ int rate_change_idx;
+};
+
+/*
+ * fractional divider must set that denominator is 20 times larger than
+ * numerator to generate precise clock frequency.
+ */
+static void rockchip_fractional_approximation(struct clk_hw *hw,
+ unsigned long rate, unsigned long *parent_rate,
+ unsigned long *m, unsigned long *n)
+{
+ struct clk_fractional_divider *fd = to_clk_fd(hw);
+ unsigned long p_rate, p_parent_rate;
+ struct clk_hw *p_parent;
+ unsigned long scale;
+
+ p_rate = clk_hw_get_rate(clk_hw_get_parent(hw));
+ if ((rate * 20 > p_rate) && (p_rate % rate != 0)) {
+ p_parent = clk_hw_get_parent(clk_hw_get_parent(hw));
+ p_parent_rate = clk_hw_get_rate(p_parent);
+ *parent_rate = p_parent_rate;
+ }
+
+ /*
+ * Get rate closer to *parent_rate to guarantee there is no overflow
+ * for m and n. In the result it will be the nearest rate left shifted
+ * by (scale - fd->nwidth) bits.
+ */
+ scale = fls_long(*parent_rate / rate - 1);
+ if (scale > fd->nwidth)
+ rate <<= scale - fd->nwidth;
+
+ rational_best_approximation(rate, *parent_rate,
+ GENMASK(fd->mwidth - 1, 0), GENMASK(fd->nwidth - 1, 0),
+ m, n);
}
-static struct clk *rockchip_clk_register_frac_branch(const char *name,
- const char **parent_names, u8 num_parents, void __iomem *base,
- int muxdiv_offset, u8 div_flags,
+static struct clk *rockchip_clk_register_frac_branch(
+ struct rockchip_clk_provider *ctx, const char *name,
+ const char *const *parent_names, u8 num_parents,
+ void __iomem *base, int muxdiv_offset, u8 div_flags,
int gate_offset, u8 gate_shift, u8 gate_flags,
- unsigned long flags)
+ unsigned long flags, struct rockchip_clk_branch *child,
+ spinlock_t *lock)
{
struct clk *clk;
- struct clk *gate = NULL;
- struct clk *div = NULL;
-
- if (gate_offset >= 0) {
- gate = clk_gate_alloc(name, *parent_names, base + gate_offset,
- gate_shift, flags, gate_flags);
- if (!gate)
- return ERR_PTR(-ENOMEM);
- }
+ struct rockchip_clk_frac *frac;
+ struct clk_gate *gate = NULL;
+ struct clk_fractional_divider *div = NULL;
if (muxdiv_offset < 0)
return ERR_PTR(-EINVAL);
- div = clk_fractional_divider_alloc(name, *parent_names, flags,
- base + muxdiv_offset, 16, 16, 0, 16, div_flags);
- if (!div)
+ if (child && child->branch_type != branch_mux) {
+ pr_err("%s: fractional child clock for %s can only be a mux\n",
+ __func__, name);
+ return ERR_PTR(-EINVAL);
+ }
+
+ frac = kzalloc(sizeof(*frac), GFP_KERNEL);
+ if (!frac)
return ERR_PTR(-ENOMEM);
+ if (gate_offset >= 0) {
+ gate = &frac->gate;
+ gate->flags = gate_flags;
+ gate->reg = base + gate_offset;
+ gate->shift = gate_shift;
+ gate->lock = lock;
+ gate->hw.clk.ops = &clk_gate_ops;
+ }
+
+ div = &frac->div;
+ div->flags = div_flags;
+ div->reg = base + muxdiv_offset;
+ div->mshift = 16;
+ div->mwidth = 16;
+ div->mmask = GENMASK(div->mwidth - 1, 0) << div->mshift;
+ div->nshift = 0;
+ div->nwidth = 16;
+ div->nmask = GENMASK(div->nwidth - 1, 0) << div->nshift;
+ div->lock = lock;
+ div->approximation = rockchip_fractional_approximation;
+ div->hw.clk.ops = &clk_fractional_divider_ops;
+
clk = clk_register_composite(name, parent_names, num_parents,
- NULL,
- div,
- gate,
- flags);
+ NULL,
+ &div->hw.clk,
+ gate ? &gate->hw.clk : NULL,
+ flags | CLK_SET_RATE_UNGATE);
+ if (IS_ERR(clk)) {
+ kfree(frac);
+ return ERR_CAST(clk);
+ }
+
+ if (child) {
+ struct clk_mux *frac_mux = &frac->mux;
+ struct clk_init_data init;
+ struct clk *mux_clk;
+
+ frac->mux_frac_idx = match_string(child->parent_names,
+ child->num_parents, name);
+ frac->mux_ops = &clk_mux_ops;
+
+ frac_mux->reg = base + child->muxdiv_offset;
+ frac_mux->shift = child->mux_shift;
+ frac_mux->width = child->mux_width;
+ frac_mux->flags = child->mux_flags;
+ frac_mux->lock = lock;
+ frac_mux->hw.init = &init;
+
+ init.name = child->name;
+ init.flags = child->flags | CLK_SET_RATE_PARENT;
+ init.ops = frac->mux_ops;
+ init.parent_names = child->parent_names;
+ init.num_parents = child->num_parents;
+
+ mux_clk = clk_register(NULL, &frac_mux->hw);
+ if (IS_ERR(mux_clk)) {
+ kfree(frac);
+ return mux_clk;
+ }
+
+ rockchip_clk_add_lookup(ctx, mux_clk, child->id);
+
+ /* notifier on the fraction divider to catch rate changes */
+ if (frac->mux_frac_idx >= 0) {
+ pr_debug("%s: found fractional parent in mux at pos %d\n",
+ __func__, frac->mux_frac_idx);
+ } else {
+ pr_warn("%s: could not find %s as parent of %s, rate changes may not work\n",
+ __func__, name, child->name);
+ }
+ }
return clk;
}
-static struct clk **clk_table;
-static void __iomem *reg_base;
-static struct clk_onecell_data clk_data;
-static struct device_node *cru_node;
+static struct clk *rockchip_clk_register_factor_branch(const char *name,
+ const char *const *parent_names, u8 num_parents,
+ void __iomem *base, unsigned int mult, unsigned int div,
+ int gate_offset, u8 gate_shift, u8 gate_flags,
+ unsigned long flags, spinlock_t *lock)
+{
+ struct clk *clk;
+ struct clk_gate *gate = NULL;
+ struct clk_fixed_factor *fix = NULL;
+
+ /* without gate, register a simple factor clock */
+ if (gate_offset == 0) {
+ return clk_register_fixed_factor(NULL, name,
+ parent_names[0], flags, mult,
+ div);
+ }
+
+ gate = kzalloc(sizeof(*gate), GFP_KERNEL);
+ if (!gate)
+ return ERR_PTR(-ENOMEM);
+
+ gate->flags = gate_flags;
+ gate->reg = base + gate_offset;
+ gate->shift = gate_shift;
+ gate->lock = lock;
+ gate->hw.clk.ops = &clk_gate_ops;
+
+ fix = kzalloc(sizeof(*fix), GFP_KERNEL);
+ if (!fix) {
+ kfree(gate);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ fix->mult = mult;
+ fix->div = div;
+ fix->hw.clk.ops = &clk_fixed_factor_ops;
+
+ clk = clk_register_composite(name, parent_names, num_parents,
+ NULL,
+ &fix->hw.clk,
+ &gate->hw.clk, flags);
+ if (IS_ERR(clk)) {
+ kfree(fix);
+ kfree(gate);
+ return ERR_CAST(clk);
+ }
-void __init rockchip_clk_init(struct device_node *np, void __iomem *base,
- unsigned long nr_clks)
+ return clk;
+}
+
+struct rockchip_clk_provider *rockchip_clk_init(struct device_node *np,
+ void __iomem *base,
+ unsigned long nr_clks)
{
- reg_base = base;
- cru_node = np;
+ struct rockchip_clk_provider *ctx;
+ struct clk **clk_table;
+ int i;
- clk_table = calloc(nr_clks, sizeof(struct clk *));
+ ctx = kzalloc(sizeof(struct rockchip_clk_provider), GFP_KERNEL);
+ if (!ctx)
+ return ERR_PTR(-ENOMEM);
+
+ clk_table = kcalloc(nr_clks, sizeof(struct clk *), GFP_KERNEL);
if (!clk_table)
- pr_err("%s: could not allocate clock lookup table\n", __func__);
+ goto err_free;
+
+ for (i = 0; i < nr_clks; ++i)
+ clk_table[i] = ERR_PTR(-ENOENT);
+
+ ctx->reg_base = base;
+ ctx->clk_data.clks = clk_table;
+ ctx->clk_data.clk_num = nr_clks;
+ ctx->cru_node = np;
+ spin_lock_init(&ctx->lock);
+
+ ctx->grf = syscon_regmap_lookup_by_phandle(ctx->cru_node,
+ "rockchip,grf");
+
+ return ctx;
+
+err_free:
+ kfree(ctx);
+ return ERR_PTR(-ENOMEM);
+}
+EXPORT_SYMBOL_GPL(rockchip_clk_init);
- clk_data.clks = clk_table;
- clk_data.clk_num = nr_clks;
- of_clk_add_provider(np, of_clk_src_onecell_get, &clk_data);
+void rockchip_clk_of_add_provider(struct device_node *np,
+ struct rockchip_clk_provider *ctx)
+{
+ if (of_clk_add_provider(np, of_clk_src_onecell_get,
+ &ctx->clk_data))
+ pr_err("%s: could not register clk provider\n", __func__);
}
+EXPORT_SYMBOL_GPL(rockchip_clk_of_add_provider);
-void rockchip_clk_add_lookup(struct clk *clk, unsigned int id)
+void rockchip_clk_add_lookup(struct rockchip_clk_provider *ctx,
+ struct clk *clk, unsigned int id)
{
- if (clk_table && id)
- clk_table[id] = clk;
+ if (ctx->clk_data.clks && id)
+ ctx->clk_data.clks[id] = clk;
}
+EXPORT_SYMBOL_GPL(rockchip_clk_add_lookup);
-void __init rockchip_clk_register_plls(struct rockchip_pll_clock *list,
+void rockchip_clk_register_plls(struct rockchip_clk_provider *ctx,
+ struct rockchip_pll_clock *list,
unsigned int nr_pll, int grf_lock_offset)
{
struct clk *clk;
int idx;
for (idx = 0; idx < nr_pll; idx++, list++) {
- clk = rockchip_clk_register_pll(list->type, list->name,
+ clk = rockchip_clk_register_pll(ctx, list->type, list->name,
list->parent_names, list->num_parents,
- reg_base, list->con_offset, grf_lock_offset,
+ list->con_offset, grf_lock_offset,
list->lock_shift, list->mode_offset,
list->mode_shift, list->rate_table,
- list->pll_flags);
+ list->flags, list->pll_flags);
if (IS_ERR(clk)) {
pr_err("%s: failed to register clock %s\n", __func__,
list->name);
continue;
}
- rockchip_clk_add_lookup(clk, list->id);
+ rockchip_clk_add_lookup(ctx, clk, list->id);
}
}
+EXPORT_SYMBOL_GPL(rockchip_clk_register_plls);
-void __init rockchip_clk_register_branches(
- struct rockchip_clk_branch *list,
- unsigned int nr_clk)
+void rockchip_clk_register_branches(struct rockchip_clk_provider *ctx,
+ struct rockchip_clk_branch *list,
+ unsigned int nr_clk)
{
struct clk *clk = NULL;
unsigned int idx;
@@ -176,50 +409,89 @@ void __init rockchip_clk_register_branches(
/* catch simple muxes */
switch (list->branch_type) {
case branch_mux:
- clk = clk_mux(list->name, flags,
- reg_base + list->muxdiv_offset, list->mux_shift,
- list->mux_width, list->parent_names,
- list->num_parents, list->mux_flags);
+ clk = clk_register_mux(NULL, list->name,
+ list->parent_names, list->num_parents,
+ flags, ctx->reg_base + list->muxdiv_offset,
+ list->mux_shift, list->mux_width,
+ list->mux_flags, &ctx->lock);
+ break;
+ case branch_muxgrf:
+ clk = rockchip_clk_register_muxgrf(list->name,
+ list->parent_names, list->num_parents,
+ flags, ctx->grf, list->muxdiv_offset,
+ list->mux_shift, list->mux_width,
+ list->mux_flags);
break;
case branch_divider:
if (list->div_table)
- clk = clk_divider_table(list->name,
- list->parent_names[0], flags,
- reg_base + list->muxdiv_offset,
- list->div_shift, list->div_width,
- list->div_table, list->div_flags);
+ clk = clk_register_divider_table(NULL,
+ list->name, list->parent_names[0],
+ flags,
+ ctx->reg_base + list->muxdiv_offset,
+ list->div_shift, list->div_width,
+ list->div_flags, list->div_table,
+ &ctx->lock);
else
- clk = clk_divider(list->name,
- list->parent_names[0], flags,
- reg_base + list->muxdiv_offset,
- list->div_shift, list->div_width,
- list->div_flags);
+ clk = clk_register_divider(NULL, list->name,
+ list->parent_names[0], flags,
+ ctx->reg_base + list->muxdiv_offset,
+ list->div_shift, list->div_width,
+ list->div_flags, &ctx->lock);
break;
case branch_fraction_divider:
- clk = rockchip_clk_register_frac_branch(list->name,
+ clk = rockchip_clk_register_frac_branch(ctx, list->name,
list->parent_names, list->num_parents,
- reg_base, list->muxdiv_offset, list->div_flags,
+ ctx->reg_base, list->muxdiv_offset,
+ list->div_flags,
list->gate_offset, list->gate_shift,
- list->gate_flags, flags);
+ list->gate_flags, flags, list->child,
+ &ctx->lock);
+ break;
+ case branch_half_divider:
break;
case branch_gate:
flags |= CLK_SET_RATE_PARENT;
- clk = clk_gate(list->name, list->parent_names[0],
- reg_base + list->gate_offset, list->gate_shift,
- flags, list->gate_flags);
+ clk = clk_register_gate(NULL, list->name,
+ list->parent_names[0], flags,
+ ctx->reg_base + list->gate_offset,
+ list->gate_shift, list->gate_flags, &ctx->lock);
break;
case branch_composite:
clk = rockchip_clk_register_branch(list->name,
list->parent_names, list->num_parents,
- reg_base, list->muxdiv_offset, list->mux_shift,
+ ctx->reg_base, list->muxdiv_offset,
+ list->mux_shift,
list->mux_width, list->mux_flags,
- list->div_shift, list->div_width,
+ list->div_offset, list->div_shift, list->div_width,
list->div_flags, list->div_table,
list->gate_offset, list->gate_shift,
- list->gate_flags, flags);
+ list->gate_flags, flags, &ctx->lock);
break;
case branch_mmc:
+ clk = rockchip_clk_register_mmc(
+ list->name,
+ list->parent_names, list->num_parents,
+ ctx->reg_base + list->muxdiv_offset,
+ list->div_shift
+ );
+ break;
+ case branch_inverter:
+ clk = rockchip_clk_register_inverter(
+ list->name, list->parent_names,
+ list->num_parents,
+ ctx->reg_base + list->muxdiv_offset,
+ list->div_shift, list->div_flags, &ctx->lock);
+ break;
+ case branch_factor:
+ clk = rockchip_clk_register_factor_branch(
+ list->name, list->parent_names,
+ list->num_parents, ctx->reg_base,
+ list->div_shift, list->div_width,
+ list->gate_offset, list->gate_shift,
+ list->gate_flags, flags, &ctx->lock);
+ break;
+ case branch_ddrclk:
break;
}
@@ -236,32 +508,36 @@ void __init rockchip_clk_register_branches(
continue;
}
- rockchip_clk_add_lookup(clk, list->id);
+ rockchip_clk_add_lookup(ctx, clk, list->id);
}
}
-
-void __init rockchip_clk_register_armclk(unsigned int lookup_id,
- const char *name, const char **parent_names,
- u8 num_parents,
- const struct rockchip_cpuclk_reg_data *reg_data,
- const struct rockchip_cpuclk_rate_table *rates,
- int nrates)
+EXPORT_SYMBOL_GPL(rockchip_clk_register_branches);
+
+void rockchip_clk_register_armclk(struct rockchip_clk_provider *ctx,
+ unsigned int lookup_id,
+ const char *name, const char *const *parent_names,
+ u8 num_parents,
+ const struct rockchip_cpuclk_reg_data *reg_data,
+ const struct rockchip_cpuclk_rate_table *rates,
+ int nrates)
{
struct clk *clk;
clk = rockchip_clk_register_cpuclk(name, parent_names, num_parents,
- reg_data, rates, nrates, reg_base
- );
+ reg_data, rates, nrates,
+ ctx->reg_base, &ctx->lock);
if (IS_ERR(clk)) {
pr_err("%s: failed to register clock %s: %ld\n",
__func__, name, PTR_ERR(clk));
return;
}
- rockchip_clk_add_lookup(clk, lookup_id);
+ rockchip_clk_add_lookup(ctx, clk, lookup_id);
}
+EXPORT_SYMBOL_GPL(rockchip_clk_register_armclk);
-void __init rockchip_clk_protect_critical(const char *clocks[], int nclocks)
+void rockchip_clk_protect_critical(const char *const clocks[],
+ int nclocks)
{
int i;
@@ -269,7 +545,34 @@ void __init rockchip_clk_protect_critical(const char *clocks[], int nclocks)
for (i = 0; i < nclocks; i++) {
struct clk *clk = __clk_lookup(clocks[i]);
- if (clk)
- clk_enable(clk);
+ clk_enable(clk);
}
}
+EXPORT_SYMBOL_GPL(rockchip_clk_protect_critical);
+
+static void rockchip_restart(struct restart_handler *this)
+{
+ struct rockchip_clk_provider *ctx =
+ container_of(this, struct rockchip_clk_provider, restart_handler);
+
+ writel(0xfdb9, ctx->reg_base + ctx->reg_restart);
+ mdelay(1000);
+}
+
+void rockchip_register_restart_notifier(struct rockchip_clk_provider *ctx,
+ unsigned int reg)
+{
+ int ret;
+
+ ctx->restart_handler.name = "rockchip-pmu",
+ ctx->restart_handler.restart = rockchip_restart,
+ ctx->restart_handler.priority = RESTART_DEFAULT_PRIORITY,
+
+ ctx->reg_restart = reg;
+
+ ret = restart_handler_register(&ctx->restart_handler);
+ if (ret)
+ pr_err("%s: cannot register restart handler, %d\n",
+ __func__, ret);
+}
+EXPORT_SYMBOL_GPL(rockchip_register_restart_notifier);