Skip to content

Commit ca574a5

Browse files
lumagandersson
authored andcommitted
clk: qcom: add msm8996 Core Bus Framework (CBF) support
Add CBF clock driver as a part of MSM8996 CPU clocks. Significantly based on AngeloGioacchino del Regno's work at [1]. The CBF is an interconnect between two CPU clusters, setting it up properly is required for booting the MSM8996 with all four cores enabled. [1] https://github.com/sonyxperiadev/kernel/blob/aosp/LE.UM.2.3.2.r1.4/drivers/clk/qcom/clk-cpu-8996.c Co-developed-by: Konrad Dybcio <konrad.dybcio@somainline.org> Signed-off-by: Konrad Dybcio <konrad.dybcio@somainline.org> Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org> [bjorn: Dropped partially uninitialized variable "ret" from cbf_clk_notifier_cb()] Signed-off-by: Bjorn Andersson <andersson@kernel.org> Link: https://lore.kernel.org/r/20230120061417.2623751-4-dmitry.baryshkov@linaro.org
1 parent 56c121d commit ca574a5

2 files changed

Lines changed: 316 additions & 1 deletion

File tree

drivers/clk/qcom/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ obj-$(CONFIG_MSM_MMCC_8998) += mmcc-msm8998.o
5252
obj-$(CONFIG_QCOM_A53PLL) += a53-pll.o
5353
obj-$(CONFIG_QCOM_A7PLL) += a7-pll.o
5454
obj-$(CONFIG_QCOM_CLK_APCS_MSM8916) += apcs-msm8916.o
55-
obj-$(CONFIG_QCOM_CLK_APCC_MSM8996) += apcs-msm8996.o clk-cpu-8996.o
55+
obj-$(CONFIG_QCOM_CLK_APCC_MSM8996) += apcs-msm8996.o clk-cpu-8996.o clk-cbf-8996.o
5656
obj-$(CONFIG_QCOM_CLK_APCS_SDX55) += apcs-sdx55.o
5757
obj-$(CONFIG_QCOM_CLK_RPM) += clk-rpm.o
5858
obj-$(CONFIG_QCOM_CLK_RPMH) += clk-rpmh.o

drivers/clk/qcom/clk-cbf-8996.c

Lines changed: 315 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/*
3+
* Copyright (c) 2022, 2023 Linaro Ltd.
4+
*/
5+
#include <linux/bitfield.h>
6+
#include <linux/clk.h>
7+
#include <linux/clk-provider.h>
8+
#include <linux/of.h>
9+
#include <linux/module.h>
10+
#include <linux/platform_device.h>
11+
#include <linux/regmap.h>
12+
13+
#include "clk-alpha-pll.h"
14+
#include "clk-regmap.h"
15+
16+
/* Need to match the order of clocks in DT binding */
17+
enum {
18+
DT_XO,
19+
DT_APCS_AUX,
20+
};
21+
22+
enum {
23+
CBF_XO_INDEX,
24+
CBF_PLL_INDEX,
25+
CBF_DIV_INDEX,
26+
CBF_APCS_AUX_INDEX,
27+
};
28+
29+
#define DIV_THRESHOLD 600000000
30+
31+
#define CBF_MUX_OFFSET 0x18
32+
#define CBF_MUX_PARENT_MASK GENMASK(1, 0)
33+
#define CBF_MUX_AUTO_CLK_SEL_ALWAYS_ON_MASK GENMASK(5, 4)
34+
#define CBF_MUX_AUTO_CLK_SEL_ALWAYS_ON_GPLL0_SEL \
35+
FIELD_PREP(CBF_MUX_AUTO_CLK_SEL_ALWAYS_ON_MASK, 0x03)
36+
#define CBF_MUX_AUTO_CLK_SEL_BIT BIT(6)
37+
38+
#define CBF_PLL_OFFSET 0xf000
39+
40+
static const u8 cbf_pll_regs[PLL_OFF_MAX_REGS] = {
41+
[PLL_OFF_L_VAL] = 0x08,
42+
[PLL_OFF_ALPHA_VAL] = 0x10,
43+
[PLL_OFF_USER_CTL] = 0x18,
44+
[PLL_OFF_CONFIG_CTL] = 0x20,
45+
[PLL_OFF_CONFIG_CTL_U] = 0x24,
46+
[PLL_OFF_TEST_CTL] = 0x30,
47+
[PLL_OFF_TEST_CTL_U] = 0x34,
48+
[PLL_OFF_STATUS] = 0x28,
49+
};
50+
51+
static const struct alpha_pll_config cbfpll_config = {
52+
.l = 72,
53+
.config_ctl_val = 0x200d4828,
54+
.config_ctl_hi_val = 0x006,
55+
.test_ctl_val = 0x1c000000,
56+
.test_ctl_hi_val = 0x00004000,
57+
.pre_div_mask = BIT(12),
58+
.post_div_mask = 0x3 << 8,
59+
.post_div_val = 0x1 << 8,
60+
.main_output_mask = BIT(0),
61+
.early_output_mask = BIT(3),
62+
};
63+
64+
static struct clk_alpha_pll cbf_pll = {
65+
.offset = CBF_PLL_OFFSET,
66+
.regs = cbf_pll_regs,
67+
.flags = SUPPORTS_DYNAMIC_UPDATE | SUPPORTS_FSM_MODE,
68+
.clkr.hw.init = &(struct clk_init_data){
69+
.name = "cbf_pll",
70+
.parent_data = (const struct clk_parent_data[]) {
71+
{ .index = DT_XO, },
72+
},
73+
.num_parents = 1,
74+
.ops = &clk_alpha_pll_hwfsm_ops,
75+
},
76+
};
77+
78+
static struct clk_fixed_factor cbf_pll_postdiv = {
79+
.mult = 1,
80+
.div = 2,
81+
.hw.init = &(struct clk_init_data){
82+
.name = "cbf_pll_postdiv",
83+
.parent_hws = (const struct clk_hw*[]){
84+
&cbf_pll.clkr.hw
85+
},
86+
.num_parents = 1,
87+
.ops = &clk_fixed_factor_ops,
88+
.flags = CLK_SET_RATE_PARENT,
89+
},
90+
};
91+
92+
static const struct clk_parent_data cbf_mux_parent_data[] = {
93+
{ .index = DT_XO },
94+
{ .hw = &cbf_pll.clkr.hw },
95+
{ .hw = &cbf_pll_postdiv.hw },
96+
{ .index = DT_APCS_AUX },
97+
};
98+
99+
struct clk_cbf_8996_mux {
100+
u32 reg;
101+
struct notifier_block nb;
102+
struct clk_regmap clkr;
103+
};
104+
105+
static struct clk_cbf_8996_mux *to_clk_cbf_8996_mux(struct clk_regmap *clkr)
106+
{
107+
return container_of(clkr, struct clk_cbf_8996_mux, clkr);
108+
}
109+
110+
static int cbf_clk_notifier_cb(struct notifier_block *nb, unsigned long event,
111+
void *data);
112+
113+
static u8 clk_cbf_8996_mux_get_parent(struct clk_hw *hw)
114+
{
115+
struct clk_regmap *clkr = to_clk_regmap(hw);
116+
struct clk_cbf_8996_mux *mux = to_clk_cbf_8996_mux(clkr);
117+
u32 val;
118+
119+
regmap_read(clkr->regmap, mux->reg, &val);
120+
121+
return FIELD_GET(CBF_MUX_PARENT_MASK, val);
122+
}
123+
124+
static int clk_cbf_8996_mux_set_parent(struct clk_hw *hw, u8 index)
125+
{
126+
struct clk_regmap *clkr = to_clk_regmap(hw);
127+
struct clk_cbf_8996_mux *mux = to_clk_cbf_8996_mux(clkr);
128+
u32 val;
129+
130+
val = FIELD_PREP(CBF_MUX_PARENT_MASK, index);
131+
132+
return regmap_update_bits(clkr->regmap, mux->reg, CBF_MUX_PARENT_MASK, val);
133+
}
134+
135+
static int clk_cbf_8996_mux_determine_rate(struct clk_hw *hw,
136+
struct clk_rate_request *req)
137+
{
138+
struct clk_hw *parent;
139+
140+
if (req->rate < (DIV_THRESHOLD / 2))
141+
return -EINVAL;
142+
143+
if (req->rate < DIV_THRESHOLD)
144+
parent = clk_hw_get_parent_by_index(hw, CBF_DIV_INDEX);
145+
else
146+
parent = clk_hw_get_parent_by_index(hw, CBF_PLL_INDEX);
147+
148+
if (!parent)
149+
return -EINVAL;
150+
151+
req->best_parent_rate = clk_hw_round_rate(parent, req->rate);
152+
req->best_parent_hw = parent;
153+
154+
return 0;
155+
}
156+
157+
static const struct clk_ops clk_cbf_8996_mux_ops = {
158+
.set_parent = clk_cbf_8996_mux_set_parent,
159+
.get_parent = clk_cbf_8996_mux_get_parent,
160+
.determine_rate = clk_cbf_8996_mux_determine_rate,
161+
};
162+
163+
static struct clk_cbf_8996_mux cbf_mux = {
164+
.reg = CBF_MUX_OFFSET,
165+
.nb.notifier_call = cbf_clk_notifier_cb,
166+
.clkr.hw.init = &(struct clk_init_data) {
167+
.name = "cbf_mux",
168+
.parent_data = cbf_mux_parent_data,
169+
.num_parents = ARRAY_SIZE(cbf_mux_parent_data),
170+
.ops = &clk_cbf_8996_mux_ops,
171+
/* CPU clock is critical and should never be gated */
172+
.flags = CLK_SET_RATE_PARENT | CLK_IS_CRITICAL,
173+
},
174+
};
175+
176+
static int cbf_clk_notifier_cb(struct notifier_block *nb, unsigned long event,
177+
void *data)
178+
{
179+
struct clk_notifier_data *cnd = data;
180+
181+
switch (event) {
182+
case PRE_RATE_CHANGE:
183+
/*
184+
* Avoid overvolting. clk_core_set_rate_nolock() walks from top
185+
* to bottom, so it will change the rate of the PLL before
186+
* chaging the parent of PMUX. This can result in pmux getting
187+
* clocked twice the expected rate.
188+
*
189+
* Manually switch to PLL/2 here.
190+
*/
191+
if (cnd->old_rate > DIV_THRESHOLD &&
192+
cnd->new_rate < DIV_THRESHOLD)
193+
clk_cbf_8996_mux_set_parent(&cbf_mux.clkr.hw, CBF_DIV_INDEX);
194+
break;
195+
case ABORT_RATE_CHANGE:
196+
/* Revert manual change */
197+
if (cnd->new_rate < DIV_THRESHOLD &&
198+
cnd->old_rate > DIV_THRESHOLD)
199+
clk_cbf_8996_mux_set_parent(&cbf_mux.clkr.hw, CBF_PLL_INDEX);
200+
break;
201+
default:
202+
break;
203+
}
204+
205+
return notifier_from_errno(0);
206+
};
207+
208+
static struct clk_hw *cbf_msm8996_hw_clks[] = {
209+
&cbf_pll_postdiv.hw,
210+
};
211+
212+
static struct clk_regmap *cbf_msm8996_clks[] = {
213+
&cbf_pll.clkr,
214+
&cbf_mux.clkr,
215+
};
216+
217+
static const struct regmap_config cbf_msm8996_regmap_config = {
218+
.reg_bits = 32,
219+
.reg_stride = 4,
220+
.val_bits = 32,
221+
.max_register = 0x10000,
222+
.fast_io = true,
223+
.val_format_endian = REGMAP_ENDIAN_LITTLE,
224+
};
225+
226+
static int qcom_msm8996_cbf_probe(struct platform_device *pdev)
227+
{
228+
void __iomem *base;
229+
struct regmap *regmap;
230+
struct device *dev = &pdev->dev;
231+
int i, ret;
232+
233+
base = devm_platform_ioremap_resource(pdev, 0);
234+
if (IS_ERR(base))
235+
return PTR_ERR(base);
236+
237+
regmap = devm_regmap_init_mmio(dev, base, &cbf_msm8996_regmap_config);
238+
if (IS_ERR(regmap))
239+
return PTR_ERR(regmap);
240+
241+
/* Select GPLL0 for 300MHz for the CBF clock */
242+
regmap_write(regmap, CBF_MUX_OFFSET, 0x3);
243+
244+
/* Ensure write goes through before PLLs are reconfigured */
245+
udelay(5);
246+
247+
/* Set the auto clock sel always-on source to GPLL0/2 (300MHz) */
248+
regmap_update_bits(regmap, CBF_MUX_OFFSET,
249+
CBF_MUX_AUTO_CLK_SEL_ALWAYS_ON_MASK,
250+
CBF_MUX_AUTO_CLK_SEL_ALWAYS_ON_GPLL0_SEL);
251+
252+
clk_alpha_pll_configure(&cbf_pll, regmap, &cbfpll_config);
253+
254+
/* Wait for PLL(s) to lock */
255+
udelay(50);
256+
257+
/* Enable auto clock selection for CBF */
258+
regmap_update_bits(regmap, CBF_MUX_OFFSET,
259+
CBF_MUX_AUTO_CLK_SEL_BIT,
260+
CBF_MUX_AUTO_CLK_SEL_BIT);
261+
262+
/* Ensure write goes through before muxes are switched */
263+
udelay(5);
264+
265+
/* Switch CBF to use the primary PLL */
266+
regmap_update_bits(regmap, CBF_MUX_OFFSET, CBF_MUX_PARENT_MASK, 0x1);
267+
268+
for (i = 0; i < ARRAY_SIZE(cbf_msm8996_hw_clks); i++) {
269+
ret = devm_clk_hw_register(dev, cbf_msm8996_hw_clks[i]);
270+
if (ret)
271+
return ret;
272+
}
273+
274+
for (i = 0; i < ARRAY_SIZE(cbf_msm8996_clks); i++) {
275+
ret = devm_clk_register_regmap(dev, cbf_msm8996_clks[i]);
276+
if (ret)
277+
return ret;
278+
}
279+
280+
ret = devm_clk_notifier_register(dev, cbf_mux.clkr.hw.clk, &cbf_mux.nb);
281+
if (ret)
282+
return ret;
283+
284+
return devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, &cbf_mux.clkr.hw);
285+
}
286+
287+
static const struct of_device_id qcom_msm8996_cbf_match_table[] = {
288+
{ .compatible = "qcom,msm8996-cbf" },
289+
{ /* sentinel */ },
290+
};
291+
MODULE_DEVICE_TABLE(of, qcom_msm8996_cbf_match_table);
292+
293+
static struct platform_driver qcom_msm8996_cbf_driver = {
294+
.probe = qcom_msm8996_cbf_probe,
295+
.driver = {
296+
.name = "qcom-msm8996-cbf",
297+
.of_match_table = qcom_msm8996_cbf_match_table,
298+
},
299+
};
300+
301+
/* Register early enough to fix the clock to be used for other cores */
302+
static int __init qcom_msm8996_cbf_init(void)
303+
{
304+
return platform_driver_register(&qcom_msm8996_cbf_driver);
305+
}
306+
postcore_initcall(qcom_msm8996_cbf_init);
307+
308+
static void __exit qcom_msm8996_cbf_exit(void)
309+
{
310+
platform_driver_unregister(&qcom_msm8996_cbf_driver);
311+
}
312+
module_exit(qcom_msm8996_cbf_exit);
313+
314+
MODULE_DESCRIPTION("QCOM MSM8996 CPU Bus Fabric Clock Driver");
315+
MODULE_LICENSE("GPL");

0 commit comments

Comments
 (0)