1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Hi6220 stub clock driver |
4 | * |
5 | * Copyright (c) 2015 Hisilicon Limited. |
6 | * Copyright (c) 2015 Linaro Limited. |
7 | * |
8 | * Author: Leo Yan <leo.yan@linaro.org> |
9 | */ |
10 | |
11 | #include <linux/clk-provider.h> |
12 | #include <linux/err.h> |
13 | #include <linux/kernel.h> |
14 | #include <linux/mfd/syscon.h> |
15 | #include <linux/mailbox_client.h> |
16 | #include <linux/of.h> |
17 | #include <linux/platform_device.h> |
18 | #include <linux/regmap.h> |
19 | |
20 | /* Stub clocks id */ |
21 | #define HI6220_STUB_ACPU0 0 |
22 | #define HI6220_STUB_ACPU1 1 |
23 | #define HI6220_STUB_GPU 2 |
24 | #define HI6220_STUB_DDR 5 |
25 | |
26 | /* Mailbox message */ |
27 | #define HI6220_MBOX_MSG_LEN 8 |
28 | |
29 | #define HI6220_MBOX_FREQ 0xA |
30 | #define HI6220_MBOX_CMD_SET 0x3 |
31 | #define HI6220_MBOX_OBJ_AP 0x0 |
32 | |
33 | /* CPU dynamic frequency scaling */ |
34 | #define ACPU_DFS_FREQ_MAX 0x1724 |
35 | #define ACPU_DFS_CUR_FREQ 0x17CC |
36 | #define ACPU_DFS_FLAG 0x1B30 |
37 | #define ACPU_DFS_FREQ_REQ 0x1B34 |
38 | #define ACPU_DFS_FREQ_LMT 0x1B38 |
39 | #define ACPU_DFS_LOCK_FLAG 0xAEAEAEAE |
40 | |
41 | #define to_stub_clk(hw) container_of(hw, struct hi6220_stub_clk, hw) |
42 | |
43 | struct hi6220_stub_clk { |
44 | u32 id; |
45 | |
46 | struct device *dev; |
47 | struct clk_hw hw; |
48 | |
49 | struct regmap *dfs_map; |
50 | struct mbox_client cl; |
51 | struct mbox_chan *mbox; |
52 | }; |
53 | |
54 | struct hi6220_mbox_msg { |
55 | unsigned char type; |
56 | unsigned char cmd; |
57 | unsigned char obj; |
58 | unsigned char src; |
59 | unsigned char para[4]; |
60 | }; |
61 | |
62 | union hi6220_mbox_data { |
63 | unsigned int data[HI6220_MBOX_MSG_LEN]; |
64 | struct hi6220_mbox_msg msg; |
65 | }; |
66 | |
67 | static unsigned int hi6220_acpu_get_freq(struct hi6220_stub_clk *stub_clk) |
68 | { |
69 | unsigned int freq; |
70 | |
71 | regmap_read(map: stub_clk->dfs_map, ACPU_DFS_CUR_FREQ, val: &freq); |
72 | return freq; |
73 | } |
74 | |
75 | static int hi6220_acpu_set_freq(struct hi6220_stub_clk *stub_clk, |
76 | unsigned int freq) |
77 | { |
78 | union hi6220_mbox_data data; |
79 | |
80 | /* set the frequency in sram */ |
81 | regmap_write(map: stub_clk->dfs_map, ACPU_DFS_FREQ_REQ, val: freq); |
82 | |
83 | /* compound mailbox message */ |
84 | data.msg.type = HI6220_MBOX_FREQ; |
85 | data.msg.cmd = HI6220_MBOX_CMD_SET; |
86 | data.msg.obj = HI6220_MBOX_OBJ_AP; |
87 | data.msg.src = HI6220_MBOX_OBJ_AP; |
88 | |
89 | mbox_send_message(chan: stub_clk->mbox, mssg: &data); |
90 | return 0; |
91 | } |
92 | |
93 | static int hi6220_acpu_round_freq(struct hi6220_stub_clk *stub_clk, |
94 | unsigned int freq) |
95 | { |
96 | unsigned int limit_flag, limit_freq = UINT_MAX; |
97 | unsigned int max_freq; |
98 | |
99 | /* check the constrained frequency */ |
100 | regmap_read(map: stub_clk->dfs_map, ACPU_DFS_FLAG, val: &limit_flag); |
101 | if (limit_flag == ACPU_DFS_LOCK_FLAG) |
102 | regmap_read(map: stub_clk->dfs_map, ACPU_DFS_FREQ_LMT, val: &limit_freq); |
103 | |
104 | /* check the supported maximum frequency */ |
105 | regmap_read(map: stub_clk->dfs_map, ACPU_DFS_FREQ_MAX, val: &max_freq); |
106 | |
107 | /* calculate the real maximum frequency */ |
108 | max_freq = min(max_freq, limit_freq); |
109 | |
110 | if (WARN_ON(freq > max_freq)) |
111 | freq = max_freq; |
112 | |
113 | return freq; |
114 | } |
115 | |
116 | static unsigned long hi6220_stub_clk_recalc_rate(struct clk_hw *hw, |
117 | unsigned long parent_rate) |
118 | { |
119 | u32 rate = 0; |
120 | struct hi6220_stub_clk *stub_clk = to_stub_clk(hw); |
121 | |
122 | switch (stub_clk->id) { |
123 | case HI6220_STUB_ACPU0: |
124 | rate = hi6220_acpu_get_freq(stub_clk); |
125 | |
126 | /* convert from kHz to Hz */ |
127 | rate *= 1000; |
128 | break; |
129 | |
130 | default: |
131 | dev_err(stub_clk->dev, "%s: un-supported clock id %d\n" , |
132 | __func__, stub_clk->id); |
133 | break; |
134 | } |
135 | |
136 | return rate; |
137 | } |
138 | |
139 | static int hi6220_stub_clk_set_rate(struct clk_hw *hw, unsigned long rate, |
140 | unsigned long parent_rate) |
141 | { |
142 | struct hi6220_stub_clk *stub_clk = to_stub_clk(hw); |
143 | unsigned long new_rate = rate / 1000; /* kHz */ |
144 | int ret = 0; |
145 | |
146 | switch (stub_clk->id) { |
147 | case HI6220_STUB_ACPU0: |
148 | ret = hi6220_acpu_set_freq(stub_clk, freq: new_rate); |
149 | if (ret < 0) |
150 | return ret; |
151 | |
152 | break; |
153 | |
154 | default: |
155 | dev_err(stub_clk->dev, "%s: un-supported clock id %d\n" , |
156 | __func__, stub_clk->id); |
157 | break; |
158 | } |
159 | |
160 | pr_debug("%s: set rate=%ldkHz\n" , __func__, new_rate); |
161 | return ret; |
162 | } |
163 | |
164 | static long hi6220_stub_clk_round_rate(struct clk_hw *hw, unsigned long rate, |
165 | unsigned long *parent_rate) |
166 | { |
167 | struct hi6220_stub_clk *stub_clk = to_stub_clk(hw); |
168 | unsigned long new_rate = rate / 1000; /* kHz */ |
169 | |
170 | switch (stub_clk->id) { |
171 | case HI6220_STUB_ACPU0: |
172 | new_rate = hi6220_acpu_round_freq(stub_clk, freq: new_rate); |
173 | |
174 | /* convert from kHz to Hz */ |
175 | new_rate *= 1000; |
176 | break; |
177 | |
178 | default: |
179 | dev_err(stub_clk->dev, "%s: un-supported clock id %d\n" , |
180 | __func__, stub_clk->id); |
181 | break; |
182 | } |
183 | |
184 | return new_rate; |
185 | } |
186 | |
187 | static const struct clk_ops hi6220_stub_clk_ops = { |
188 | .recalc_rate = hi6220_stub_clk_recalc_rate, |
189 | .round_rate = hi6220_stub_clk_round_rate, |
190 | .set_rate = hi6220_stub_clk_set_rate, |
191 | }; |
192 | |
193 | static int hi6220_stub_clk_probe(struct platform_device *pdev) |
194 | { |
195 | struct device *dev = &pdev->dev; |
196 | struct clk_init_data init; |
197 | struct hi6220_stub_clk *stub_clk; |
198 | struct clk *clk; |
199 | struct device_node *np = pdev->dev.of_node; |
200 | int ret; |
201 | |
202 | stub_clk = devm_kzalloc(dev, size: sizeof(*stub_clk), GFP_KERNEL); |
203 | if (!stub_clk) |
204 | return -ENOMEM; |
205 | |
206 | stub_clk->dfs_map = syscon_regmap_lookup_by_phandle(np, |
207 | property: "hisilicon,hi6220-clk-sram" ); |
208 | if (IS_ERR(ptr: stub_clk->dfs_map)) { |
209 | dev_err(dev, "failed to get sram regmap\n" ); |
210 | return PTR_ERR(ptr: stub_clk->dfs_map); |
211 | } |
212 | |
213 | stub_clk->hw.init = &init; |
214 | stub_clk->dev = dev; |
215 | stub_clk->id = HI6220_STUB_ACPU0; |
216 | |
217 | /* Use mailbox client with blocking mode */ |
218 | stub_clk->cl.dev = dev; |
219 | stub_clk->cl.tx_done = NULL; |
220 | stub_clk->cl.tx_block = true; |
221 | stub_clk->cl.tx_tout = 500; |
222 | stub_clk->cl.knows_txdone = false; |
223 | |
224 | /* Allocate mailbox channel */ |
225 | stub_clk->mbox = mbox_request_channel(cl: &stub_clk->cl, index: 0); |
226 | if (IS_ERR(ptr: stub_clk->mbox)) { |
227 | dev_err(dev, "failed get mailbox channel\n" ); |
228 | return PTR_ERR(ptr: stub_clk->mbox); |
229 | } |
230 | |
231 | init.name = "acpu0" ; |
232 | init.ops = &hi6220_stub_clk_ops; |
233 | init.num_parents = 0; |
234 | init.flags = 0; |
235 | |
236 | clk = devm_clk_register(dev, hw: &stub_clk->hw); |
237 | if (IS_ERR(ptr: clk)) |
238 | return PTR_ERR(ptr: clk); |
239 | |
240 | ret = of_clk_add_provider(np, clk_src_get: of_clk_src_simple_get, data: clk); |
241 | if (ret) { |
242 | dev_err(dev, "failed to register OF clock provider\n" ); |
243 | return ret; |
244 | } |
245 | |
246 | /* initialize buffer to zero */ |
247 | regmap_write(map: stub_clk->dfs_map, ACPU_DFS_FLAG, val: 0x0); |
248 | regmap_write(map: stub_clk->dfs_map, ACPU_DFS_FREQ_REQ, val: 0x0); |
249 | regmap_write(map: stub_clk->dfs_map, ACPU_DFS_FREQ_LMT, val: 0x0); |
250 | |
251 | dev_dbg(dev, "Registered clock '%s'\n" , init.name); |
252 | return 0; |
253 | } |
254 | |
255 | static const struct of_device_id hi6220_stub_clk_of_match[] = { |
256 | { .compatible = "hisilicon,hi6220-stub-clk" , }, |
257 | {} |
258 | }; |
259 | |
260 | static struct platform_driver hi6220_stub_clk_driver = { |
261 | .driver = { |
262 | .name = "hi6220-stub-clk" , |
263 | .of_match_table = hi6220_stub_clk_of_match, |
264 | }, |
265 | .probe = hi6220_stub_clk_probe, |
266 | }; |
267 | |
268 | static int __init hi6220_stub_clk_init(void) |
269 | { |
270 | return platform_driver_register(&hi6220_stub_clk_driver); |
271 | } |
272 | subsys_initcall(hi6220_stub_clk_init); |
273 | |