Commit | Line | Data |
---|---|---|
d2912cb1 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
c1628a2c LY |
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> | |
c1628a2c LY |
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> | |
a96cbb14 | 17 | #include <linux/platform_device.h> |
c1628a2c LY |
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(stub_clk->dfs_map, ACPU_DFS_CUR_FREQ, &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(stub_clk->dfs_map, ACPU_DFS_FREQ_REQ, 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(stub_clk->mbox, &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(stub_clk->dfs_map, ACPU_DFS_FLAG, &limit_flag); | |
101 | if (limit_flag == ACPU_DFS_LOCK_FLAG) | |
102 | regmap_read(stub_clk->dfs_map, ACPU_DFS_FREQ_LMT, &limit_freq); | |
103 | ||
104 | /* check the supported maximum frequency */ | |
105 | regmap_read(stub_clk->dfs_map, ACPU_DFS_FREQ_MAX, &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, 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, 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, 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 | "hisilicon,hi6220-clk-sram"); | |
208 | if (IS_ERR(stub_clk->dfs_map)) { | |
209 | dev_err(dev, "failed to get sram regmap\n"); | |
210 | return PTR_ERR(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(&stub_clk->cl, 0); | |
226 | if (IS_ERR(stub_clk->mbox)) { | |
227 | dev_err(dev, "failed get mailbox channel\n"); | |
228 | return PTR_ERR(stub_clk->mbox); | |
90c53547 | 229 | } |
c1628a2c LY |
230 | |
231 | init.name = "acpu0"; | |
232 | init.ops = &hi6220_stub_clk_ops; | |
233 | init.num_parents = 0; | |
f61990f3 | 234 | init.flags = 0; |
c1628a2c LY |
235 | |
236 | clk = devm_clk_register(dev, &stub_clk->hw); | |
237 | if (IS_ERR(clk)) | |
238 | return PTR_ERR(clk); | |
239 | ||
240 | ret = of_clk_add_provider(np, of_clk_src_simple_get, 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(stub_clk->dfs_map, ACPU_DFS_FLAG, 0x0); | |
248 | regmap_write(stub_clk->dfs_map, ACPU_DFS_FREQ_REQ, 0x0); | |
249 | regmap_write(stub_clk->dfs_map, ACPU_DFS_FREQ_LMT, 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); |