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