From 840eb062ccbff93e2286150c93b484307cb737e7 Mon Sep 17 00:00:00 2001 From: Elaine Zhang Date: Wed, 6 May 2020 09:38:43 +0800 Subject: [PATCH] clk: rockchip: add a COMPOSITE_DCLK clock-type The CLK_SET_RATE_PARENT flag make the parent clock and the child clk is 1:1. If the DCLK frequency is too low, the PLL frequency will be very low, which will affect the output waveform quality of PLL, and PLL locking may be abnormal, so add a new COMPOSITE_DCLK clock-type to handle that. Change-Id: If9bee9ebf157fcf034aed246b3aa1cff503ef9cf Signed-off-by: Elaine Zhang --- drivers/clk/rockchip/Makefile | 1 + drivers/clk/rockchip/clk-dclk-divider.c | 168 ++++++++++++++++++++++++ drivers/clk/rockchip/clk.c | 10 ++ drivers/clk/rockchip/clk.h | 38 ++++++ include/linux/clk-provider.h | 2 + 5 files changed, 219 insertions(+) create mode 100644 drivers/clk/rockchip/clk-dclk-divider.c diff --git a/drivers/clk/rockchip/Makefile b/drivers/clk/rockchip/Makefile index af5c0002ce3e..38c1194df06e 100644 --- a/drivers/clk/rockchip/Makefile +++ b/drivers/clk/rockchip/Makefile @@ -12,6 +12,7 @@ obj-y += clk-mmc-phase.o obj-y += clk-muxgrf.o obj-y += clk-ddr.o obj-y += clk-pvtm.o +obj-y += clk-dclk-divider.o obj-$(CONFIG_RESET_CONTROLLER) += softrst.o obj-$(CONFIG_CPU_PX30) += clk-px30.o diff --git a/drivers/clk/rockchip/clk-dclk-divider.c b/drivers/clk/rockchip/clk-dclk-divider.c new file mode 100644 index 000000000000..88cf7ab82a57 --- /dev/null +++ b/drivers/clk/rockchip/clk-dclk-divider.c @@ -0,0 +1,168 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2019 Fuzhou Rockchip Electronics Co., Ltd + */ + +#include +#include +#include +#include +#include +#include "clk.h" + +#define div_mask(width) ((1 << (width)) - 1) + +static unsigned long clk_dclk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct clk_divider *divider = to_clk_divider(hw); + unsigned int val; + + val = clk_readl(divider->reg) >> divider->shift; + val &= div_mask(divider->width); + + return DIV_ROUND_UP_ULL(((u64)parent_rate), val + 1); +} + +static long clk_dclk_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *prate) +{ + struct clk_divider *divider = to_clk_divider(hw); + int div, maxdiv = div_mask(divider->width) + 1; + + div = DIV_ROUND_UP_ULL(divider->max_prate, rate); + if (div % 2) + div = __rounddown_pow_of_two(div); + div = div > maxdiv ? maxdiv : div; + *prate = div * rate; + return rate; +} + +static int clk_dclk_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct clk_divider *divider = to_clk_divider(hw); + unsigned int value; + unsigned long flags = 0; + u32 val; + + value = divider_get_val(rate, parent_rate, divider->table, + divider->width, divider->flags); + + if (divider->lock) + spin_lock_irqsave(divider->lock, flags); + else + __acquire(divider->lock); + + if (divider->flags & CLK_DIVIDER_HIWORD_MASK) { + val = div_mask(divider->width) << (divider->shift + 16); + } else { + val = clk_readl(divider->reg); + val &= ~(div_mask(divider->width) << divider->shift); + } + val |= value << divider->shift; + clk_writel(val, divider->reg); + + if (divider->lock) + spin_unlock_irqrestore(divider->lock, flags); + else + __release(divider->lock); + + return 0; +} + +const struct clk_ops clk_dclk_divider_ops = { + .recalc_rate = clk_dclk_recalc_rate, + .round_rate = clk_dclk_round_rate, + .set_rate = clk_dclk_set_rate, +}; +EXPORT_SYMBOL_GPL(clk_dclk_divider_ops); + +/** + * Register a clock branch. + * Most clock branches have a form like + * + * src1 --|--\ + * |M |--[GATE]-[DIV]- + * src2 --|--/ + * + * sometimes without one of those components. + */ +struct clk *rockchip_clk_register_dclk_branch(const char *name, + const char *const *parent_names, + u8 num_parents, + void __iomem *base, + int muxdiv_offset, u8 mux_shift, + u8 mux_width, u8 mux_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, + unsigned long max_prate, + spinlock_t *lock) +{ + struct clk *clk; + struct clk_mux *mux = NULL; + struct clk_gate *gate = NULL; + struct clk_divider *div = NULL; + const struct clk_ops *mux_ops = NULL, *div_ops = NULL, + *gate_ops = NULL; + + if (num_parents > 1) { + mux = kzalloc(sizeof(*mux), GFP_KERNEL); + if (!mux) + return ERR_PTR(-ENOMEM); + + mux->reg = base + muxdiv_offset; + mux->shift = mux_shift; + mux->mask = BIT(mux_width) - 1; + mux->flags = mux_flags; + mux->lock = lock; + mux_ops = (mux_flags & CLK_MUX_READ_ONLY) ? &clk_mux_ro_ops + : &clk_mux_ops; + } + + if (gate_offset >= 0) { + gate = kzalloc(sizeof(*gate), GFP_KERNEL); + if (!gate) + goto err_gate; + + gate->flags = gate_flags; + gate->reg = base + gate_offset; + gate->bit_idx = gate_shift; + gate->lock = lock; + gate_ops = &clk_gate_ops; + } + + if (div_width > 0) { + div = kzalloc(sizeof(*div), GFP_KERNEL); + if (!div) + 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->max_prate = max_prate; + div_ops = &clk_dclk_divider_ops; + } + + clk = clk_register_composite(NULL, name, parent_names, num_parents, + mux ? &mux->hw : NULL, mux_ops, + div ? &div->hw : NULL, div_ops, + gate ? &gate->hw : NULL, gate_ops, + flags); + + return clk; +err_div: + kfree(gate); +err_gate: + kfree(mux); + return ERR_PTR(-ENOMEM); +} diff --git a/drivers/clk/rockchip/clk.c b/drivers/clk/rockchip/clk.c index 049cd405c7a2..73f049a2dd03 100644 --- a/drivers/clk/rockchip/clk.c +++ b/drivers/clk/rockchip/clk.c @@ -612,6 +612,16 @@ void __init rockchip_clk_register_branches( list->div_width, list->div_flags, ctx->reg_base); break; + case branch_dclk_divider: + clk = rockchip_clk_register_dclk_branch(list->name, + list->parent_names, list->num_parents, + ctx->reg_base, list->muxdiv_offset, list->mux_shift, + list->mux_width, list->mux_flags, + 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->max_prate, &ctx->lock); + break; } /* none of the cases above matched */ diff --git a/drivers/clk/rockchip/clk.h b/drivers/clk/rockchip/clk.h index 758db9ccd93e..5de18b9d29f0 100644 --- a/drivers/clk/rockchip/clk.h +++ b/drivers/clk/rockchip/clk.h @@ -458,6 +458,7 @@ enum rockchip_clk_branch_type { branch_factor, branch_ddrclk, branch_half_divider, + branch_dclk_divider, }; struct rockchip_clk_branch { @@ -964,6 +965,28 @@ struct rockchip_clk_branch { .gate_offset = -1, \ } +#define COMPOSITE_DCLK(_id, cname, pnames, f, mo, ms, mw, mf, ds, dw,\ + df, go, gs, gf, prate) \ + { \ + .id = _id, \ + .branch_type = branch_dclk_divider, \ + .name = cname, \ + .parent_names = pnames, \ + .num_parents = ARRAY_SIZE(pnames), \ + .flags = f, \ + .muxdiv_offset = mo, \ + .mux_shift = ms, \ + .mux_width = mw, \ + .mux_flags = mf, \ + .div_shift = ds, \ + .div_width = dw, \ + .div_flags = df, \ + .gate_offset = go, \ + .gate_shift = gs, \ + .gate_flags = gf, \ + .max_prate = prate, \ + } + /* SGRF clocks are only accessible from secure mode, so not controllable */ #define SGRF_GATE(_id, cname, pname) \ FACTOR(_id, cname, pname, 0, 1, 1) @@ -1006,6 +1029,21 @@ struct clk *rockchip_clk_register_halfdiv(const char *name, u8 gate_flags, unsigned long flags, spinlock_t *lock); +struct clk *rockchip_clk_register_dclk_branch(const char *name, + const char *const *parent_names, + u8 num_parents, + void __iomem *base, + int muxdiv_offset, u8 mux_shift, + u8 mux_width, u8 mux_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, + unsigned long max_prate, + spinlock_t *lock); + #ifdef CONFIG_RESET_CONTROLLER void rockchip_register_softrst(struct device_node *np, unsigned int num_regs, diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h index 50c8cb0ded1a..539e5e033aba 100644 --- a/include/linux/clk-provider.h +++ b/include/linux/clk-provider.h @@ -388,6 +388,7 @@ struct clk_div_table { * @reg: register containing the divider * @shift: shift to the divider bit field * @width: width of the divider bit field + * @max_prate: the maximum frequency of the parent clock * @table: array of value/divider pairs, last entry should have div = 0 * @lock: register lock * @@ -424,6 +425,7 @@ struct clk_divider { u8 shift; u8 width; u8 flags; + unsigned long max_prate; const struct clk_div_table *table; spinlock_t *lock; };