1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Copyright (c) 2016 Rockchip Electronics Co. Ltd.
4 * Author: Lin Huang <hl@rock-chips.com>
5 */
6
7 #include <linux/arm-smccc.h>
8 #include <linux/clk.h>
9 #include <linux/clk-provider.h>
10 #include <linux/io.h>
11 #include <linux/of.h>
12 #include <linux/rockchip/rockchip_sip.h>
13 #include <linux/slab.h>
14 #include <soc/rockchip/rockchip_sip.h>
15 #include <soc/rockchip/scpi.h>
16 #include <uapi/drm/drm_mode.h>
17
18 #ifdef CONFIG_ARM
19 #include <asm/psci.h>
20 #endif
21
22 #include "clk.h"
23
24 #define MHZ (1000000)
25 #define DDR_RATE 12
26 struct rockchip_ddrclk {
27 struct clk_hw hw;
28 void __iomem *reg_base;
29 int mux_offset;
30 int mux_shift;
31 int mux_width;
32 int div_shift;
33 int div_width;
34 int ddr_flag;
35 };
36
37 #define to_rockchip_ddrclk_hw(hw) container_of(hw, struct rockchip_ddrclk, hw)
38
39 struct share_params_ddrclk {
40 u32 hz;
41 u32 lcdc_type;
42 };
43
44 struct rockchip_ddrclk_data {
45 void __iomem *params;
46 int (*dmcfreq_wait_complete)(void);
47 };
48
49 static struct rockchip_ddrclk_data ddr_data = {NULL, NULL};
50
rockchip_set_ddrclk_params(void __iomem * params)51 void rockchip_set_ddrclk_params(void __iomem *params)
52 {
53 ddr_data.params = params;
54 }
55 EXPORT_SYMBOL(rockchip_set_ddrclk_params);
56
rockchip_set_ddrclk_dmcfreq_wait_complete(int (* func)(void))57 void rockchip_set_ddrclk_dmcfreq_wait_complete(int (*func)(void))
58 {
59 ddr_data.dmcfreq_wait_complete = func;
60 }
61 EXPORT_SYMBOL(rockchip_set_ddrclk_dmcfreq_wait_complete);
62
rockchip_ddrclk_sip_set_rate(struct clk_hw * hw,unsigned long drate,unsigned long prate)63 static int rockchip_ddrclk_sip_set_rate(struct clk_hw *hw, unsigned long drate, unsigned long prate)
64 {
65 struct arm_smccc_res res;
66
67 arm_smccc_smc(ROCKCHIP_SIP_DRAM_FREQ, drate, 0, ROCKCHIP_SIP_CONFIG_DRAM_SET_RATE, 0, 0, 0, 0, &res);
68
69 if (res.a0) {
70 return 0;
71 } else {
72 return -EPERM;
73 }
74 }
75
rockchip_ddrclk_sip_recalc_rate(struct clk_hw * hw,unsigned long parent_rate)76 static unsigned long rockchip_ddrclk_sip_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
77 {
78 struct arm_smccc_res res;
79
80 arm_smccc_smc(ROCKCHIP_SIP_DRAM_FREQ, 0, 0, ROCKCHIP_SIP_CONFIG_DRAM_GET_RATE, 0, 0, 0, 0, &res);
81
82 return res.a0;
83 }
84
rockchip_ddrclk_sip_round_rate(struct clk_hw * hw,unsigned long rate,unsigned long * prate)85 static long rockchip_ddrclk_sip_round_rate(struct clk_hw *hw, unsigned long rate, unsigned long *prate)
86 {
87 struct arm_smccc_res res;
88
89 arm_smccc_smc(ROCKCHIP_SIP_DRAM_FREQ, rate, 0, ROCKCHIP_SIP_CONFIG_DRAM_ROUND_RATE, 0, 0, 0, 0, &res);
90
91 return res.a0;
92 }
93
rockchip_ddrclk_get_parent(struct clk_hw * hw)94 static u8 rockchip_ddrclk_get_parent(struct clk_hw *hw)
95 {
96 struct rockchip_ddrclk *ddrclk = to_rockchip_ddrclk_hw(hw);
97 u32 val;
98
99 val = readl(ddrclk->reg_base + ddrclk->mux_offset) >> ddrclk->mux_shift;
100 val &= GENMASK(ddrclk->mux_width - 1, 0);
101
102 return val;
103 }
104
105 static const struct clk_ops rockchip_ddrclk_sip_ops = {
106 .recalc_rate = rockchip_ddrclk_sip_recalc_rate,
107 .set_rate = rockchip_ddrclk_sip_set_rate,
108 .round_rate = rockchip_ddrclk_sip_round_rate,
109 .get_parent = rockchip_ddrclk_get_parent,
110 };
111
112 static u32 ddr_clk_cached;
113
rockchip_ddrclk_scpi_set_rate(struct clk_hw * hw,unsigned long drate,unsigned long prate)114 static int rockchip_ddrclk_scpi_set_rate(struct clk_hw *hw, unsigned long drate, unsigned long prate)
115 {
116 u32 ret;
117 u32 lcdc_type = 0;
118 struct share_params_ddrclk *p;
119
120 p = (struct share_params_ddrclk *)ddr_data.params;
121 if (p) {
122 lcdc_type = p->lcdc_type;
123 }
124
125 ret = scpi_ddr_set_clk_rate(drate / MHZ, lcdc_type);
126 if (ret) {
127 ddr_clk_cached = ret;
128 ret = 0;
129 } else {
130 ddr_clk_cached = 0;
131 ret = -1;
132 }
133
134 return ret;
135 }
136
rockchip_ddrclk_scpi_recalc_rate(struct clk_hw * hw,unsigned long parent_rate)137 static unsigned long rockchip_ddrclk_scpi_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
138 {
139 if (ddr_clk_cached) {
140 return (MHZ * ddr_clk_cached);
141 } else {
142 return (MHZ * scpi_ddr_get_clk_rate());
143 }
144 }
145
rockchip_ddrclk_scpi_round_rate(struct clk_hw * hw,unsigned long rate,unsigned long * prate)146 static long rockchip_ddrclk_scpi_round_rate(struct clk_hw *hw, unsigned long rate, unsigned long *prate)
147 {
148 rate = rate / MHZ;
149 rate = (rate / DDR_RATE) * DDR_RATE;
150
151 return (rate * MHZ);
152 }
153
154 static const struct clk_ops rockchip_ddrclk_scpi_ops = {
155 .recalc_rate = rockchip_ddrclk_scpi_recalc_rate,
156 .set_rate = rockchip_ddrclk_scpi_set_rate,
157 .round_rate = rockchip_ddrclk_scpi_round_rate,
158 .get_parent = rockchip_ddrclk_get_parent,
159 };
160
rockchip_ddrclk_sip_set_rate_v2(struct clk_hw * hw,unsigned long drate,unsigned long prate)161 static int rockchip_ddrclk_sip_set_rate_v2(struct clk_hw *hw, unsigned long drate, unsigned long prate)
162 {
163 struct share_params_ddrclk *p;
164 struct arm_smccc_res res;
165
166 p = (struct share_params_ddrclk *)ddr_data.params;
167 if (p) {
168 p->hz = drate;
169 }
170
171 res = sip_smc_dram(SHARE_PAGE_TYPE_DDR, 0, ROCKCHIP_SIP_CONFIG_DRAM_SET_RATE);
172 if ((int)res.a1 == SIP_RET_SET_RATE_TIMEOUT) {
173 if (ddr_data.dmcfreq_wait_complete) {
174 ddr_data.dmcfreq_wait_complete();
175 }
176 }
177
178 return res.a0;
179 }
180
rockchip_ddrclk_sip_recalc_rate_v2(struct clk_hw * hw,unsigned long parent_rate)181 static unsigned long rockchip_ddrclk_sip_recalc_rate_v2(struct clk_hw *hw, unsigned long parent_rate)
182 {
183 struct arm_smccc_res res;
184
185 res = sip_smc_dram(SHARE_PAGE_TYPE_DDR, 0, ROCKCHIP_SIP_CONFIG_DRAM_GET_RATE);
186 if (!res.a0) {
187 return res.a1;
188 } else {
189 return 0;
190 }
191 }
192
rockchip_ddrclk_sip_round_rate_v2(struct clk_hw * hw,unsigned long rate,unsigned long * prate)193 static long rockchip_ddrclk_sip_round_rate_v2(struct clk_hw *hw, unsigned long rate, unsigned long *prate)
194 {
195 struct share_params_ddrclk *p;
196 struct arm_smccc_res res;
197
198 p = (struct share_params_ddrclk *)ddr_data.params;
199 if (p) {
200 p->hz = rate;
201 }
202
203 res = sip_smc_dram(SHARE_PAGE_TYPE_DDR, 0, ROCKCHIP_SIP_CONFIG_DRAM_ROUND_RATE);
204 if (!res.a0) {
205 return res.a1;
206 } else {
207 return 0;
208 }
209 }
210
211 static const struct clk_ops rockchip_ddrclk_sip_ops_v2 = {
212 .recalc_rate = rockchip_ddrclk_sip_recalc_rate_v2,
213 .set_rate = rockchip_ddrclk_sip_set_rate_v2,
214 .round_rate = rockchip_ddrclk_sip_round_rate_v2,
215 .get_parent = rockchip_ddrclk_get_parent,
216 };
217
rockchip_clk_register_ddrclk(const char * name,int flags,const char * const * parent_names,u8 num_parents,int mux_offset,int mux_shift,int mux_width,int div_shift,int div_width,int ddr_flag,void __iomem * reg_base)218 struct clk *rockchip_clk_register_ddrclk(const char *name, int flags, const char *const *parent_names, u8 num_parents,
219 int mux_offset, int mux_shift, int mux_width, int div_shift, int div_width,
220 int ddr_flag, void __iomem *reg_base)
221 {
222 struct rockchip_ddrclk *ddrclk;
223 struct clk_init_data init;
224 struct clk *clk;
225
226 #ifdef CONFIG_ARM
227 if (!psci_smp_available()) {
228 return NULL;
229 }
230 #endif
231
232 ddrclk = kzalloc(sizeof(*ddrclk), GFP_KERNEL);
233 if (!ddrclk) {
234 return ERR_PTR(-ENOMEM);
235 }
236
237 init.name = name;
238 init.parent_names = parent_names;
239 init.num_parents = num_parents;
240
241 init.flags = flags;
242 init.flags |= CLK_SET_RATE_NO_REPARENT;
243
244 switch (ddr_flag) {
245 case ROCKCHIP_DDRCLK_SIP:
246 init.ops = &rockchip_ddrclk_sip_ops;
247 break;
248 case ROCKCHIP_DDRCLK_SCPI:
249 init.ops = &rockchip_ddrclk_scpi_ops;
250 break;
251 case ROCKCHIP_DDRCLK_SIP_V2:
252 init.ops = &rockchip_ddrclk_sip_ops_v2;
253 break;
254 default:
255 pr_err("%s: unsupported ddrclk type %d\n", __func__, ddr_flag);
256 kfree(ddrclk);
257 return ERR_PTR(-EINVAL);
258 }
259
260 ddrclk->reg_base = reg_base;
261 ddrclk->hw.init = &init;
262 ddrclk->mux_offset = mux_offset;
263 ddrclk->mux_shift = mux_shift;
264 ddrclk->mux_width = mux_width;
265 ddrclk->div_shift = div_shift;
266 ddrclk->div_width = div_width;
267 ddrclk->ddr_flag = ddr_flag;
268
269 clk = clk_register(NULL, &ddrclk->hw);
270 if (IS_ERR(clk)) {
271 kfree(ddrclk);
272 }
273
274 return clk;
275 }
276 EXPORT_SYMBOL_GPL(rockchip_clk_register_ddrclk);
277