Commit | Line | Data |
---|---|---|
576f1b4b HW |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | // | |
3 | // Copyright (c) 2018 MediaTek Inc. | |
4 | ||
5 | #include <linux/completion.h> | |
6 | #include <linux/errno.h> | |
7 | #include <linux/dma-mapping.h> | |
8 | #include <linux/module.h> | |
9 | #include <linux/mailbox_controller.h> | |
d01e0aec | 10 | #include <linux/of.h> |
576f1b4b HW |
11 | #include <linux/soc/mediatek/mtk-cmdq.h> |
12 | ||
576f1b4b | 13 | #define CMDQ_WRITE_ENABLE_MASK BIT(0) |
b2ff2356 | 14 | #define CMDQ_POLL_ENABLE_MASK BIT(0) |
576f1b4b | 15 | #define CMDQ_EOC_IRQ_EN BIT(0) |
613c2e2c | 16 | #define CMDQ_REG_TYPE 1 |
ed4d5ab1 CKH |
17 | #define CMDQ_JUMP_RELATIVE 0 |
18 | #define CMDQ_JUMP_ABSOLUTE 1 | |
576f1b4b | 19 | |
5c8b718c BH |
20 | struct cmdq_instruction { |
21 | union { | |
22 | u32 value; | |
23 | u32 mask; | |
5f6e560c DYH |
24 | struct { |
25 | u16 arg_c; | |
26 | u16 src_reg; | |
27 | }; | |
5c8b718c BH |
28 | }; |
29 | union { | |
30 | u16 offset; | |
31 | u16 event; | |
613c2e2c DYH |
32 | u16 reg_dst; |
33 | }; | |
34 | union { | |
35 | u8 subsys; | |
36 | struct { | |
37 | u8 sop:5; | |
38 | u8 arg_c_t:1; | |
39 | u8 src_t:1; | |
40 | u8 dst_t:1; | |
41 | }; | |
5c8b718c | 42 | }; |
5c8b718c BH |
43 | u8 op; |
44 | }; | |
45 | ||
d412f18c BH |
46 | int cmdq_dev_get_client_reg(struct device *dev, |
47 | struct cmdq_client_reg *client_reg, int idx) | |
48 | { | |
49 | struct of_phandle_args spec; | |
50 | int err; | |
51 | ||
52 | if (!client_reg) | |
53 | return -ENOENT; | |
54 | ||
55 | err = of_parse_phandle_with_fixed_args(dev->of_node, | |
56 | "mediatek,gce-client-reg", | |
57 | 3, idx, &spec); | |
58 | if (err < 0) { | |
59 | dev_err(dev, | |
60 | "error %d can't parse gce-client-reg property (%d)", | |
61 | err, idx); | |
62 | ||
63 | return err; | |
64 | } | |
65 | ||
66 | client_reg->subsys = (u8)spec.args[0]; | |
67 | client_reg->offset = (u16)spec.args[1]; | |
68 | client_reg->size = (u16)spec.args[2]; | |
69 | of_node_put(spec.np); | |
70 | ||
71 | return 0; | |
72 | } | |
73 | EXPORT_SYMBOL(cmdq_dev_get_client_reg); | |
74 | ||
a69dcdfc | 75 | struct cmdq_client *cmdq_mbox_create(struct device *dev, int index) |
576f1b4b HW |
76 | { |
77 | struct cmdq_client *client; | |
78 | ||
79 | client = kzalloc(sizeof(*client), GFP_KERNEL); | |
80 | if (!client) | |
81 | return (struct cmdq_client *)-ENOMEM; | |
82 | ||
576f1b4b HW |
83 | client->client.dev = dev; |
84 | client->client.tx_block = false; | |
ce35e21d | 85 | client->client.knows_txdone = true; |
576f1b4b HW |
86 | client->chan = mbox_request_channel(&client->client, index); |
87 | ||
88 | if (IS_ERR(client->chan)) { | |
89 | long err; | |
90 | ||
91 | dev_err(dev, "failed to request channel\n"); | |
92 | err = PTR_ERR(client->chan); | |
93 | kfree(client); | |
94 | ||
95 | return ERR_PTR(err); | |
96 | } | |
97 | ||
98 | return client; | |
99 | } | |
100 | EXPORT_SYMBOL(cmdq_mbox_create); | |
101 | ||
102 | void cmdq_mbox_destroy(struct cmdq_client *client) | |
103 | { | |
576f1b4b HW |
104 | mbox_free_channel(client->chan); |
105 | kfree(client); | |
106 | } | |
107 | EXPORT_SYMBOL(cmdq_mbox_destroy); | |
108 | ||
109 | struct cmdq_pkt *cmdq_pkt_create(struct cmdq_client *client, size_t size) | |
110 | { | |
111 | struct cmdq_pkt *pkt; | |
112 | struct device *dev; | |
113 | dma_addr_t dma_addr; | |
114 | ||
115 | pkt = kzalloc(sizeof(*pkt), GFP_KERNEL); | |
116 | if (!pkt) | |
117 | return ERR_PTR(-ENOMEM); | |
118 | pkt->va_base = kzalloc(size, GFP_KERNEL); | |
119 | if (!pkt->va_base) { | |
120 | kfree(pkt); | |
121 | return ERR_PTR(-ENOMEM); | |
122 | } | |
123 | pkt->buf_size = size; | |
124 | pkt->cl = (void *)client; | |
125 | ||
126 | dev = client->chan->mbox->dev; | |
127 | dma_addr = dma_map_single(dev, pkt->va_base, pkt->buf_size, | |
128 | DMA_TO_DEVICE); | |
129 | if (dma_mapping_error(dev, dma_addr)) { | |
130 | dev_err(dev, "dma map failed, size=%u\n", (u32)(u64)size); | |
131 | kfree(pkt->va_base); | |
132 | kfree(pkt); | |
133 | return ERR_PTR(-ENOMEM); | |
134 | } | |
135 | ||
136 | pkt->pa_base = dma_addr; | |
137 | ||
138 | return pkt; | |
139 | } | |
140 | EXPORT_SYMBOL(cmdq_pkt_create); | |
141 | ||
142 | void cmdq_pkt_destroy(struct cmdq_pkt *pkt) | |
143 | { | |
144 | struct cmdq_client *client = (struct cmdq_client *)pkt->cl; | |
145 | ||
146 | dma_unmap_single(client->chan->mbox->dev, pkt->pa_base, pkt->buf_size, | |
147 | DMA_TO_DEVICE); | |
148 | kfree(pkt->va_base); | |
149 | kfree(pkt); | |
150 | } | |
151 | EXPORT_SYMBOL(cmdq_pkt_destroy); | |
152 | ||
5c8b718c BH |
153 | static int cmdq_pkt_append_command(struct cmdq_pkt *pkt, |
154 | struct cmdq_instruction inst) | |
576f1b4b | 155 | { |
5c8b718c | 156 | struct cmdq_instruction *cmd_ptr; |
576f1b4b HW |
157 | |
158 | if (unlikely(pkt->cmd_buf_size + CMDQ_INST_SIZE > pkt->buf_size)) { | |
159 | /* | |
160 | * In the case of allocated buffer size (pkt->buf_size) is used | |
161 | * up, the real required size (pkt->cmdq_buf_size) is still | |
162 | * increased, so that the user knows how much memory should be | |
163 | * ultimately allocated after appending all commands and | |
164 | * flushing the command packet. Therefor, the user can call | |
165 | * cmdq_pkt_create() again with the real required buffer size. | |
166 | */ | |
167 | pkt->cmd_buf_size += CMDQ_INST_SIZE; | |
168 | WARN_ONCE(1, "%s: buffer size %u is too small !\n", | |
169 | __func__, (u32)pkt->buf_size); | |
170 | return -ENOMEM; | |
171 | } | |
5c8b718c | 172 | |
576f1b4b | 173 | cmd_ptr = pkt->va_base + pkt->cmd_buf_size; |
5c8b718c | 174 | *cmd_ptr = inst; |
576f1b4b HW |
175 | pkt->cmd_buf_size += CMDQ_INST_SIZE; |
176 | ||
177 | return 0; | |
178 | } | |
179 | ||
556030f0 | 180 | int cmdq_pkt_write(struct cmdq_pkt *pkt, u8 subsys, u16 offset, u32 value) |
576f1b4b | 181 | { |
5c8b718c | 182 | struct cmdq_instruction inst; |
576f1b4b | 183 | |
5c8b718c BH |
184 | inst.op = CMDQ_CODE_WRITE; |
185 | inst.value = value; | |
186 | inst.offset = offset; | |
187 | inst.subsys = subsys; | |
576f1b4b | 188 | |
5c8b718c | 189 | return cmdq_pkt_append_command(pkt, inst); |
576f1b4b HW |
190 | } |
191 | EXPORT_SYMBOL(cmdq_pkt_write); | |
192 | ||
556030f0 BH |
193 | int cmdq_pkt_write_mask(struct cmdq_pkt *pkt, u8 subsys, |
194 | u16 offset, u32 value, u32 mask) | |
576f1b4b | 195 | { |
5c8b718c BH |
196 | struct cmdq_instruction inst = { {0} }; |
197 | u16 offset_mask = offset; | |
01d1b408 | 198 | int err; |
576f1b4b HW |
199 | |
200 | if (mask != 0xffffffff) { | |
5c8b718c BH |
201 | inst.op = CMDQ_CODE_MASK; |
202 | inst.mask = ~mask; | |
203 | err = cmdq_pkt_append_command(pkt, inst); | |
01d1b408 BH |
204 | if (err < 0) |
205 | return err; | |
206 | ||
576f1b4b HW |
207 | offset_mask |= CMDQ_WRITE_ENABLE_MASK; |
208 | } | |
01d1b408 | 209 | err = cmdq_pkt_write(pkt, subsys, offset_mask, value); |
576f1b4b HW |
210 | |
211 | return err; | |
212 | } | |
213 | EXPORT_SYMBOL(cmdq_pkt_write_mask); | |
214 | ||
d3b04aab DYH |
215 | int cmdq_pkt_read_s(struct cmdq_pkt *pkt, u16 high_addr_reg_idx, u16 addr_low, |
216 | u16 reg_idx) | |
217 | { | |
218 | struct cmdq_instruction inst = {}; | |
219 | ||
220 | inst.op = CMDQ_CODE_READ_S; | |
221 | inst.dst_t = CMDQ_REG_TYPE; | |
222 | inst.sop = high_addr_reg_idx; | |
223 | inst.reg_dst = reg_idx; | |
224 | inst.src_reg = addr_low; | |
225 | ||
226 | return cmdq_pkt_append_command(pkt, inst); | |
227 | } | |
228 | EXPORT_SYMBOL(cmdq_pkt_read_s); | |
229 | ||
5f6e560c DYH |
230 | int cmdq_pkt_write_s(struct cmdq_pkt *pkt, u16 high_addr_reg_idx, |
231 | u16 addr_low, u16 src_reg_idx) | |
232 | { | |
233 | struct cmdq_instruction inst = {}; | |
234 | ||
235 | inst.op = CMDQ_CODE_WRITE_S; | |
236 | inst.src_t = CMDQ_REG_TYPE; | |
237 | inst.sop = high_addr_reg_idx; | |
238 | inst.offset = addr_low; | |
239 | inst.src_reg = src_reg_idx; | |
240 | ||
241 | return cmdq_pkt_append_command(pkt, inst); | |
242 | } | |
243 | EXPORT_SYMBOL(cmdq_pkt_write_s); | |
244 | ||
11c7842d DYH |
245 | int cmdq_pkt_write_s_mask(struct cmdq_pkt *pkt, u16 high_addr_reg_idx, |
246 | u16 addr_low, u16 src_reg_idx, u32 mask) | |
247 | { | |
248 | struct cmdq_instruction inst = {}; | |
249 | int err; | |
250 | ||
251 | inst.op = CMDQ_CODE_MASK; | |
252 | inst.mask = ~mask; | |
253 | err = cmdq_pkt_append_command(pkt, inst); | |
254 | if (err < 0) | |
255 | return err; | |
256 | ||
257 | inst.mask = 0; | |
258 | inst.op = CMDQ_CODE_WRITE_S_MASK; | |
259 | inst.src_t = CMDQ_REG_TYPE; | |
260 | inst.sop = high_addr_reg_idx; | |
261 | inst.offset = addr_low; | |
262 | inst.src_reg = src_reg_idx; | |
263 | ||
264 | return cmdq_pkt_append_command(pkt, inst); | |
265 | } | |
266 | EXPORT_SYMBOL(cmdq_pkt_write_s_mask); | |
267 | ||
1af43fce DYH |
268 | int cmdq_pkt_write_s_value(struct cmdq_pkt *pkt, u8 high_addr_reg_idx, |
269 | u16 addr_low, u32 value) | |
270 | { | |
271 | struct cmdq_instruction inst = {}; | |
272 | ||
273 | inst.op = CMDQ_CODE_WRITE_S; | |
274 | inst.sop = high_addr_reg_idx; | |
275 | inst.offset = addr_low; | |
276 | inst.value = value; | |
277 | ||
278 | return cmdq_pkt_append_command(pkt, inst); | |
279 | } | |
280 | EXPORT_SYMBOL(cmdq_pkt_write_s_value); | |
281 | ||
88a2ffc4 DYH |
282 | int cmdq_pkt_write_s_mask_value(struct cmdq_pkt *pkt, u8 high_addr_reg_idx, |
283 | u16 addr_low, u32 value, u32 mask) | |
284 | { | |
285 | struct cmdq_instruction inst = {}; | |
286 | int err; | |
287 | ||
288 | inst.op = CMDQ_CODE_MASK; | |
289 | inst.mask = ~mask; | |
290 | err = cmdq_pkt_append_command(pkt, inst); | |
291 | if (err < 0) | |
292 | return err; | |
293 | ||
294 | inst.op = CMDQ_CODE_WRITE_S_MASK; | |
295 | inst.sop = high_addr_reg_idx; | |
296 | inst.offset = addr_low; | |
297 | inst.value = value; | |
298 | ||
299 | return cmdq_pkt_append_command(pkt, inst); | |
300 | } | |
301 | EXPORT_SYMBOL(cmdq_pkt_write_s_mask_value); | |
302 | ||
23c22299 | 303 | int cmdq_pkt_wfe(struct cmdq_pkt *pkt, u16 event, bool clear) |
576f1b4b | 304 | { |
5c8b718c | 305 | struct cmdq_instruction inst = { {0} }; |
23c22299 | 306 | u32 clear_option = clear ? CMDQ_WFE_UPDATE : 0; |
576f1b4b HW |
307 | |
308 | if (event >= CMDQ_MAX_EVENT) | |
309 | return -EINVAL; | |
310 | ||
5c8b718c | 311 | inst.op = CMDQ_CODE_WFE; |
23c22299 | 312 | inst.value = CMDQ_WFE_OPTION | clear_option; |
5c8b718c | 313 | inst.event = event; |
576f1b4b | 314 | |
5c8b718c | 315 | return cmdq_pkt_append_command(pkt, inst); |
576f1b4b HW |
316 | } |
317 | EXPORT_SYMBOL(cmdq_pkt_wfe); | |
318 | ||
556030f0 | 319 | int cmdq_pkt_clear_event(struct cmdq_pkt *pkt, u16 event) |
576f1b4b | 320 | { |
5c8b718c BH |
321 | struct cmdq_instruction inst = { {0} }; |
322 | ||
576f1b4b HW |
323 | if (event >= CMDQ_MAX_EVENT) |
324 | return -EINVAL; | |
325 | ||
5c8b718c BH |
326 | inst.op = CMDQ_CODE_WFE; |
327 | inst.value = CMDQ_WFE_UPDATE; | |
328 | inst.event = event; | |
329 | ||
330 | return cmdq_pkt_append_command(pkt, inst); | |
576f1b4b HW |
331 | } |
332 | EXPORT_SYMBOL(cmdq_pkt_clear_event); | |
333 | ||
7de796ca DYH |
334 | int cmdq_pkt_set_event(struct cmdq_pkt *pkt, u16 event) |
335 | { | |
336 | struct cmdq_instruction inst = {}; | |
337 | ||
338 | if (event >= CMDQ_MAX_EVENT) | |
339 | return -EINVAL; | |
340 | ||
341 | inst.op = CMDQ_CODE_WFE; | |
342 | inst.value = CMDQ_WFE_UPDATE | CMDQ_WFE_UPDATE_VALUE; | |
343 | inst.event = event; | |
344 | ||
345 | return cmdq_pkt_append_command(pkt, inst); | |
346 | } | |
347 | EXPORT_SYMBOL(cmdq_pkt_set_event); | |
348 | ||
b2ff2356 BH |
349 | int cmdq_pkt_poll(struct cmdq_pkt *pkt, u8 subsys, |
350 | u16 offset, u32 value) | |
351 | { | |
352 | struct cmdq_instruction inst = { {0} }; | |
353 | int err; | |
354 | ||
355 | inst.op = CMDQ_CODE_POLL; | |
356 | inst.value = value; | |
357 | inst.offset = offset; | |
358 | inst.subsys = subsys; | |
359 | err = cmdq_pkt_append_command(pkt, inst); | |
360 | ||
361 | return err; | |
362 | } | |
363 | EXPORT_SYMBOL(cmdq_pkt_poll); | |
364 | ||
365 | int cmdq_pkt_poll_mask(struct cmdq_pkt *pkt, u8 subsys, | |
366 | u16 offset, u32 value, u32 mask) | |
367 | { | |
368 | struct cmdq_instruction inst = { {0} }; | |
369 | int err; | |
370 | ||
371 | inst.op = CMDQ_CODE_MASK; | |
372 | inst.mask = ~mask; | |
373 | err = cmdq_pkt_append_command(pkt, inst); | |
374 | if (err < 0) | |
375 | return err; | |
376 | ||
377 | offset = offset | CMDQ_POLL_ENABLE_MASK; | |
378 | err = cmdq_pkt_poll(pkt, subsys, offset, value); | |
379 | ||
380 | return err; | |
381 | } | |
382 | EXPORT_SYMBOL(cmdq_pkt_poll_mask); | |
383 | ||
613c2e2c DYH |
384 | int cmdq_pkt_assign(struct cmdq_pkt *pkt, u16 reg_idx, u32 value) |
385 | { | |
386 | struct cmdq_instruction inst = {}; | |
387 | ||
388 | inst.op = CMDQ_CODE_LOGIC; | |
389 | inst.dst_t = CMDQ_REG_TYPE; | |
390 | inst.reg_dst = reg_idx; | |
391 | inst.value = value; | |
392 | return cmdq_pkt_append_command(pkt, inst); | |
393 | } | |
394 | EXPORT_SYMBOL(cmdq_pkt_assign); | |
395 | ||
7218be3b | 396 | int cmdq_pkt_jump_abs(struct cmdq_pkt *pkt, dma_addr_t addr, u8 shift_pa) |
946f1792 DYH |
397 | { |
398 | struct cmdq_instruction inst = {}; | |
399 | ||
400 | inst.op = CMDQ_CODE_JUMP; | |
ed4d5ab1 | 401 | inst.offset = CMDQ_JUMP_ABSOLUTE; |
ade17653 | 402 | inst.value = addr >> shift_pa; |
946f1792 DYH |
403 | return cmdq_pkt_append_command(pkt, inst); |
404 | } | |
7218be3b | 405 | EXPORT_SYMBOL(cmdq_pkt_jump_abs); |
946f1792 | 406 | |
99581858 | 407 | int cmdq_pkt_finalize(struct cmdq_pkt *pkt) |
576f1b4b | 408 | { |
5c8b718c | 409 | struct cmdq_instruction inst = { {0} }; |
576f1b4b HW |
410 | int err; |
411 | ||
412 | /* insert EOC and generate IRQ for each command iteration */ | |
5c8b718c BH |
413 | inst.op = CMDQ_CODE_EOC; |
414 | inst.value = CMDQ_EOC_IRQ_EN; | |
415 | err = cmdq_pkt_append_command(pkt, inst); | |
01d1b408 BH |
416 | if (err < 0) |
417 | return err; | |
576f1b4b HW |
418 | |
419 | /* JUMP to end */ | |
5c8b718c | 420 | inst.op = CMDQ_CODE_JUMP; |
2b8cf383 DYH |
421 | inst.value = CMDQ_JUMP_PASS >> |
422 | cmdq_get_shift_pa(((struct cmdq_client *)pkt->cl)->chan); | |
5c8b718c | 423 | err = cmdq_pkt_append_command(pkt, inst); |
576f1b4b HW |
424 | |
425 | return err; | |
426 | } | |
99581858 | 427 | EXPORT_SYMBOL(cmdq_pkt_finalize); |
576f1b4b | 428 | |
5252c1c5 | 429 | int cmdq_pkt_flush_async(struct cmdq_pkt *pkt) |
576f1b4b HW |
430 | { |
431 | int err; | |
576f1b4b HW |
432 | struct cmdq_client *client = (struct cmdq_client *)pkt->cl; |
433 | ||
34c4e407 DYH |
434 | err = mbox_send_message(client->chan, pkt); |
435 | if (err < 0) | |
436 | return err; | |
576f1b4b HW |
437 | /* We can send next packet immediately, so just call txdone. */ |
438 | mbox_client_txdone(client->chan, 0); | |
439 | ||
440 | return 0; | |
441 | } | |
442 | EXPORT_SYMBOL(cmdq_pkt_flush_async); | |
443 | ||
576f1b4b | 444 | MODULE_LICENSE("GPL v2"); |