Skip to content

Commit cd4d306

Browse files
Elaine Zhangrkhuangtao
authored andcommitted
clk: rockchip: add support for half divider
The new Rockchip socs have optional half divider, so we use "branch_half_divider" + "COMPOSITE_NOMUX_HALFDIV \ DIV_HALF \ COMPOSITE_HALFDIV \ CMPOSITE_NOGATE_HALFDIV" to hook that special divider clock-type into our clock-tree. Change-Id: I79e3f0e8265ccb6a9839cd83a7a3ee0ca825a020 Signed-off-by: Elaine Zhang <zhangqing@rock-chips.com>
1 parent e6dda0b commit cd4d306

4 files changed

Lines changed: 326 additions & 0 deletions

File tree

drivers/clk/rockchip/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ obj-y += clk-inverter.o
1010
obj-y += clk-mmc-phase.o
1111
obj-y += clk-muxgrf.o
1212
obj-y += clk-ddr.o
13+
obj-y += clk-half-divider.o
1314
obj-$(CONFIG_RESET_CONTROLLER) += softrst.o
1415

1516
obj-$(CONFIG_CPU_PX30) += clk-px30.o
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/*
3+
* Copyright (c) 2018 Fuzhou Rockchip Electronics Co., Ltd
4+
*/
5+
6+
#include <linux/slab.h>
7+
#include <linux/bitops.h>
8+
#include <linux/regmap.h>
9+
#include <linux/clk.h>
10+
#include <linux/clk-provider.h>
11+
#include "clk.h"
12+
13+
#define div_mask(width) ((1 << (width)) - 1)
14+
15+
static bool _is_best_half_div(unsigned long rate, unsigned long now,
16+
unsigned long best, unsigned long flags)
17+
{
18+
if (flags & CLK_DIVIDER_ROUND_CLOSEST)
19+
return abs(rate - now) < abs(rate - best);
20+
21+
return now <= rate && now > best;
22+
}
23+
24+
static unsigned long clk_half_divider_recalc_rate(struct clk_hw *hw,
25+
unsigned long parent_rate)
26+
{
27+
struct clk_divider *divider = to_clk_divider(hw);
28+
unsigned int val;
29+
30+
val = clk_readl(divider->reg) >> divider->shift;
31+
val &= div_mask(divider->width);
32+
val = val * 2 + 3;
33+
34+
return DIV_ROUND_UP_ULL(((u64)parent_rate * 2), val);
35+
}
36+
37+
static int clk_half_divider_bestdiv(struct clk_hw *hw, unsigned long rate,
38+
unsigned long *best_parent_rate, u8 width,
39+
unsigned long flags)
40+
{
41+
unsigned int i, bestdiv = 0;
42+
unsigned long parent_rate, best = 0, now, maxdiv;
43+
unsigned long parent_rate_saved = *best_parent_rate;
44+
45+
if (!rate)
46+
rate = 1;
47+
48+
maxdiv = div_mask(width);
49+
50+
if (!(clk_hw_get_flags(hw) & CLK_SET_RATE_PARENT)) {
51+
parent_rate = *best_parent_rate;
52+
bestdiv = DIV_ROUND_UP_ULL(((u64)parent_rate * 2), rate);
53+
if (bestdiv < 3)
54+
bestdiv = 0;
55+
else
56+
bestdiv = (bestdiv - 3) / 2;
57+
bestdiv = bestdiv > maxdiv ? maxdiv : bestdiv;
58+
return bestdiv;
59+
}
60+
61+
/*
62+
* The maximum divider we can use without overflowing
63+
* unsigned long in rate * i below
64+
*/
65+
maxdiv = min(ULONG_MAX / rate, maxdiv);
66+
67+
for (i = 0; i <= maxdiv; i++) {
68+
if (((u64)rate * (i * 2 + 3)) == ((u64)parent_rate_saved * 2)) {
69+
/*
70+
* It's the most ideal case if the requested rate can be
71+
* divided from parent clock without needing to change
72+
* parent rate, so return the divider immediately.
73+
*/
74+
*best_parent_rate = parent_rate_saved;
75+
return i;
76+
}
77+
parent_rate = clk_hw_round_rate(clk_hw_get_parent(hw),
78+
((u64)rate * (i * 2 + 3)) / 2);
79+
now = DIV_ROUND_UP_ULL(((u64)parent_rate * 2),
80+
(i * 2 + 3));
81+
82+
if (_is_best_half_div(rate, now, best, flags)) {
83+
bestdiv = i;
84+
best = now;
85+
*best_parent_rate = parent_rate;
86+
}
87+
}
88+
89+
if (!bestdiv) {
90+
bestdiv = div_mask(width);
91+
*best_parent_rate = clk_hw_round_rate(clk_hw_get_parent(hw), 1);
92+
}
93+
94+
return bestdiv;
95+
}
96+
97+
static long clk_half_divider_round_rate(struct clk_hw *hw, unsigned long rate,
98+
unsigned long *prate)
99+
{
100+
struct clk_divider *divider = to_clk_divider(hw);
101+
int div;
102+
103+
div = clk_half_divider_bestdiv(hw, rate, prate,
104+
divider->width,
105+
divider->flags);
106+
107+
return DIV_ROUND_UP_ULL(((u64)*prate * 2), div * 2 + 3);
108+
}
109+
110+
static int clk_half_divider_set_rate(struct clk_hw *hw, unsigned long rate,
111+
unsigned long parent_rate)
112+
{
113+
struct clk_divider *divider = to_clk_divider(hw);
114+
unsigned int value;
115+
unsigned long flags = 0;
116+
u32 val;
117+
118+
value = DIV_ROUND_UP_ULL(((u64)parent_rate * 2), rate);
119+
value = (value - 3) / 2;
120+
value = min_t(unsigned int, value, div_mask(divider->width));
121+
122+
if (divider->lock)
123+
spin_lock_irqsave(divider->lock, flags);
124+
else
125+
__acquire(divider->lock);
126+
127+
if (divider->flags & CLK_DIVIDER_HIWORD_MASK) {
128+
val = div_mask(divider->width) << (divider->shift + 16);
129+
} else {
130+
val = clk_readl(divider->reg);
131+
val &= ~(div_mask(divider->width) << divider->shift);
132+
}
133+
val |= value << divider->shift;
134+
clk_writel(val, divider->reg);
135+
136+
if (divider->lock)
137+
spin_unlock_irqrestore(divider->lock, flags);
138+
else
139+
__release(divider->lock);
140+
141+
return 0;
142+
}
143+
144+
const struct clk_ops clk_half_divider_ops = {
145+
.recalc_rate = clk_half_divider_recalc_rate,
146+
.round_rate = clk_half_divider_round_rate,
147+
.set_rate = clk_half_divider_set_rate,
148+
};
149+
EXPORT_SYMBOL_GPL(clk_half_divider_ops);
150+
151+
/**
152+
* Register a clock branch.
153+
* Most clock branches have a form like
154+
*
155+
* src1 --|--\
156+
* |M |--[GATE]-[DIV]-
157+
* src2 --|--/
158+
*
159+
* sometimes without one of those components.
160+
*/
161+
struct clk *rockchip_clk_register_halfdiv(const char *name,
162+
const char *const *parent_names,
163+
u8 num_parents, void __iomem *base,
164+
int muxdiv_offset, u8 mux_shift,
165+
u8 mux_width, u8 mux_flags,
166+
u8 div_shift, u8 div_width,
167+
u8 div_flags, int gate_offset,
168+
u8 gate_shift, u8 gate_flags,
169+
unsigned long flags,
170+
spinlock_t *lock)
171+
{
172+
struct clk *clk;
173+
struct clk_mux *mux = NULL;
174+
struct clk_gate *gate = NULL;
175+
struct clk_divider *div = NULL;
176+
const struct clk_ops *mux_ops = NULL, *div_ops = NULL,
177+
*gate_ops = NULL;
178+
179+
if (num_parents > 1) {
180+
mux = kzalloc(sizeof(*mux), GFP_KERNEL);
181+
if (!mux)
182+
return ERR_PTR(-ENOMEM);
183+
184+
mux->reg = base + muxdiv_offset;
185+
mux->shift = mux_shift;
186+
mux->mask = BIT(mux_width) - 1;
187+
mux->flags = mux_flags;
188+
mux->lock = lock;
189+
mux_ops = (mux_flags & CLK_MUX_READ_ONLY) ? &clk_mux_ro_ops
190+
: &clk_mux_ops;
191+
}
192+
193+
if (gate_offset >= 0) {
194+
gate = kzalloc(sizeof(*gate), GFP_KERNEL);
195+
if (!gate)
196+
goto err_gate;
197+
198+
gate->flags = gate_flags;
199+
gate->reg = base + gate_offset;
200+
gate->bit_idx = gate_shift;
201+
gate->lock = lock;
202+
gate_ops = &clk_gate_ops;
203+
}
204+
205+
if (div_width > 0) {
206+
div = kzalloc(sizeof(*div), GFP_KERNEL);
207+
if (!div)
208+
goto err_div;
209+
210+
div->flags = div_flags;
211+
div->reg = base + muxdiv_offset;
212+
div->shift = div_shift;
213+
div->width = div_width;
214+
div->lock = lock;
215+
div_ops = &clk_half_divider_ops;
216+
}
217+
218+
clk = clk_register_composite(NULL, name, parent_names, num_parents,
219+
mux ? &mux->hw : NULL, mux_ops,
220+
div ? &div->hw : NULL, div_ops,
221+
gate ? &gate->hw : NULL, gate_ops,
222+
flags);
223+
224+
return clk;
225+
err_div:
226+
kfree(gate);
227+
err_gate:
228+
kfree(mux);
229+
return ERR_PTR(-ENOMEM);
230+
}

drivers/clk/rockchip/clk.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,16 @@ void __init rockchip_clk_register_branches(
515515
list->gate_flags, flags, list->child,
516516
list->max_prate, &ctx->lock);
517517
break;
518+
case branch_half_divider:
519+
clk = rockchip_clk_register_halfdiv(list->name,
520+
list->parent_names, list->num_parents,
521+
ctx->reg_base, list->muxdiv_offset,
522+
list->mux_shift, list->mux_width,
523+
list->mux_flags, list->div_shift,
524+
list->div_width, list->div_flags,
525+
list->gate_offset, list->gate_shift,
526+
list->gate_flags, flags, &ctx->lock);
527+
break;
518528
case branch_gate:
519529
flags |= CLK_SET_RATE_PARENT;
520530

drivers/clk/rockchip/clk.h

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,7 @@ enum rockchip_clk_branch_type {
426426
branch_inverter,
427427
branch_factor,
428428
branch_ddrc,
429+
branch_half_divider,
429430
};
430431

431432
struct rockchip_clk_branch {
@@ -760,6 +761,79 @@ struct rockchip_clk_branch {
760761
.gate_flags = gf, \
761762
}
762763

764+
#define COMPOSITE_HALFDIV(_id, cname, pnames, f, mo, ms, mw, mf, ds, dw,\
765+
df, go, gs, gf) \
766+
{ \
767+
.id = _id, \
768+
.branch_type = branch_half_divider, \
769+
.name = cname, \
770+
.parent_names = pnames, \
771+
.num_parents = ARRAY_SIZE(pnames), \
772+
.flags = f, \
773+
.muxdiv_offset = mo, \
774+
.mux_shift = ms, \
775+
.mux_width = mw, \
776+
.mux_flags = mf, \
777+
.div_shift = ds, \
778+
.div_width = dw, \
779+
.div_flags = df, \
780+
.gate_offset = go, \
781+
.gate_shift = gs, \
782+
.gate_flags = gf, \
783+
}
784+
785+
#define COMPOSITE_NOGATE_HALFDIV(_id, cname, pnames, f, mo, ms, mw, mf, \
786+
ds, dw, df) \
787+
{ \
788+
.id = _id, \
789+
.branch_type = branch_half_divider, \
790+
.name = cname, \
791+
.parent_names = pnames, \
792+
.num_parents = ARRAY_SIZE(pnames), \
793+
.flags = f, \
794+
.muxdiv_offset = mo, \
795+
.mux_shift = ms, \
796+
.mux_width = mw, \
797+
.mux_flags = mf, \
798+
.div_shift = ds, \
799+
.div_width = dw, \
800+
.div_flags = df, \
801+
.gate_offset = -1, \
802+
}
803+
804+
#define COMPOSITE_NOMUX_HALFDIV(_id, cname, pname, f, mo, ds, dw, df, \
805+
go, gs, gf) \
806+
{ \
807+
.id = _id, \
808+
.branch_type = branch_half_divider, \
809+
.name = cname, \
810+
.parent_names = (const char *[]){ pname }, \
811+
.num_parents = 1, \
812+
.flags = f, \
813+
.muxdiv_offset = mo, \
814+
.div_shift = ds, \
815+
.div_width = dw, \
816+
.div_flags = df, \
817+
.gate_offset = go, \
818+
.gate_shift = gs, \
819+
.gate_flags = gf, \
820+
}
821+
822+
#define DIV_HALF(_id, cname, pname, f, o, s, w, df) \
823+
{ \
824+
.id = _id, \
825+
.branch_type = branch_half_divider, \
826+
.name = cname, \
827+
.parent_names = (const char *[]){ pname }, \
828+
.num_parents = 1, \
829+
.flags = f, \
830+
.muxdiv_offset = o, \
831+
.div_shift = s, \
832+
.div_width = w, \
833+
.div_flags = df, \
834+
.gate_offset = -1, \
835+
}
836+
763837
struct rockchip_clk_provider *rockchip_clk_init(struct device_node *np,
764838
void __iomem *base, unsigned long nr_clks);
765839
void rockchip_clk_of_add_provider(struct device_node *np,
@@ -788,6 +862,17 @@ void rockchip_register_restart_notifier(struct rockchip_clk_provider *ctx,
788862

789863
#define ROCKCHIP_SOFTRST_HIWORD_MASK BIT(0)
790864

865+
struct clk *rockchip_clk_register_halfdiv(const char *name,
866+
const char *const *parent_names,
867+
u8 num_parents, void __iomem *base,
868+
int muxdiv_offset, u8 mux_shift,
869+
u8 mux_width, u8 mux_flags,
870+
u8 div_shift, u8 div_width,
871+
u8 div_flags, int gate_offset,
872+
u8 gate_shift, u8 gate_flags,
873+
unsigned long flags,
874+
spinlock_t *lock);
875+
791876
#ifdef CONFIG_RESET_CONTROLLER
792877
void rockchip_register_softrst(struct device_node *np,
793878
unsigned int num_regs,

0 commit comments

Comments
 (0)