1
+/*
2
+ * Kendryte K230 GSDMA
3
+ *
4
+ * Copyright (c) 2026 Tao Ding <dingtao0430@163.com>
5
+ *
6
+ * SPDX-License-Identifier: GPL-2.0-or-later
7
+ *
8
+ * GSDMA includes SDMA (System Direct Memory Access, K230 TRM section 10.2) and
9
+ * GDMA (Graphic Direct Memory Access, K230 TRM section 2.5.2).
10
+ */
11
+
12
+#include "qemu/osdep.h"
13
+#include "qemu/bitops.h"
14
+#include "qemu/log.h"
15
+#include "qemu/module.h"
16
+#include "migration/vmstate.h"
17
+#include "system/address-spaces.h"
18
+#include "hw/dma/k230_gsdma.h"
19
+#include "hw/core/irq.h"
20
+#include "trace.h"
21
+
22
+static void k230_gsdma_update_irq(K230GSDMAState *s)
23
+{
24
+ qemu_set_irq(s->irq, !!(s->dma_int_stat & ~s->dma_int_mask));
25
+}
26
+
27
+static bool k230_gsdma_decomp_enabled(K230GSDMAState *s)
28
+{
29
+ /* Only sdma channel 0 has decomp enable flag */
30
+ return !!(s->channels[0].cfg & K230_GSDMA_CH0_CFG_DECOMP_CTRL_EN);
31
+}
32
+
33
+static bool k230_gsdma_read_sdma_llt(hwaddr addr, K230GSDMALLT *llt)
34
+{
35
+ bool ok = address_space_read(&address_space_memory, addr,
36
+ MEMTXATTRS_UNSPECIFIED, llt,
37
+ sizeof(*llt)) == MEMTX_OK;
38
+
39
+ if (ok) {
40
+ trace_k230_gsdma_read_sdma_llt(addr, le32_to_cpu(llt->cfg),
41
+ le32_to_cpu(llt->src_addr),
42
+ le32_to_cpu(llt->line_size),
43
+ le32_to_cpu(llt->line_cfg),
44
+ le32_to_cpu(llt->dst_addr),
45
+ le32_to_cpu(llt->next_llt_addr));
46
+ } else {
47
+ trace_k230_gsdma_read_sdma_llt_failed(addr);
48
+ }
49
+
50
+ return ok;
51
+}
52
+
53
+static unsigned int k230_gsdma_usr_data_size(uint32_t cfg)
54
+{
55
+ /* decode usr_dat_size in CH_CFG register */
56
+ switch (extract32(cfg, K230_GSDMA_CH_CFG_USR_DATA_SIZE_SHIFT, 2)) {
57
+ case 0:
58
+ return 1;
59
+ case 1:
60
+ return 2;
61
+ default:
62
+ return 4;
63
+ }
64
+}
65
+
66
+static void k230_gsdma_convert_endian(uint8_t *buf, uint32_t len, uint32_t cfg)
67
+{
68
+ unsigned int mode = extract32(cfg,
69
+ K230_GSDMA_CH_CFG_DAT_ENDIAN_SHIFT, 2);
70
+ unsigned int unit_size;
71
+ unsigned int offset;
72
+
73
+ if (mode == 0) {
74
+ return;
75
+ }
76
+
77
+ unit_size = 1U << mode;
78
+ for (offset = 0; offset + unit_size <= len; offset += unit_size) {
79
+ unsigned int i;
80
+
81
+ for (i = 0; i < unit_size / 2; i++) {
82
+ uint8_t tmp = buf[offset + i];
83
+
84
+ buf[offset + i] = buf[offset + unit_size - i - 1];
85
+ buf[offset + unit_size - i - 1] = tmp;
86
+ }
87
+ }
88
+}
89
+
90
+static bool k230_gsdma_transfer_llt(K230GSDMAChannel *c, const K230GSDMALLT *llt)
91
+{
92
+ uint32_t llt_cfg = le32_to_cpu(llt->cfg);
93
+ hwaddr src = le32_to_cpu(llt->src_addr);
94
+ hwaddr dst = le32_to_cpu(llt->dst_addr);
95
+ uint32_t line_size = le32_to_cpu(llt->line_size);
96
+ uint32_t line_cfg = le32_to_cpu(llt->line_cfg);
97
+ /* Number of lines required for 2d mode */
98
+ uint32_t line_num =
99
+ (llt_cfg & K230_GSDMA_LLT_2D_MODE) ? extract32(line_cfg, 0, 16) : 1;
100
+ /* Stride line size required for 2d mode */
101
+ uint32_t line_space = extract32(line_cfg, 16, 16);
102
+
103
+ bool dat_mode = c->cfg & K230_GSDMA_CH_CFG_DAT_MODE;
104
+ bool src_fixed = c->cfg & K230_GSDMA_CH_CFG_SRC_FIXED;
105
+ bool dst_fixed = c->cfg & K230_GSDMA_CH_CFG_DST_FIXED;
106
+
107
+ uint8_t buf[8]; /* K230 sdma axi data width is 64-bit */
108
+ uint8_t fill[4]; /* Usr data is 32-bit*/
109
+ unsigned int fill_len = k230_gsdma_usr_data_size(c->cfg);
110
+ uint32_t i;
111
+
112
+ memcpy(fill, &c->usr_data, sizeof(fill));
113
+
114
+ for (i = 0; i < line_num; i++) {
115
+ uint32_t remaining = line_size;
116
+ hwaddr line_src = src;
117
+ hwaddr line_dst = dst;
118
+
119
+ while (remaining) {
120
+ uint32_t chunk = MIN(remaining, (uint32_t)sizeof(buf));
121
+
122
+ if (dat_mode) {
123
+ uint32_t j;
124
+
125
+ for (j = 0; j < chunk; j++) {
126
+ buf[j] = fill[j % fill_len];
127
+ }
128
+ } else if (address_space_read(&address_space_memory, line_src,
129
+ MEMTXATTRS_UNSPECIFIED, buf,
130
+ chunk) != MEMTX_OK) {
131
+ return false;
132
+ }
133
+
134
+ k230_gsdma_convert_endian(buf, chunk, c->cfg);
135
+
136
+ if (address_space_write(&address_space_memory, line_dst,
137
+ MEMTXATTRS_UNSPECIFIED, buf,
138
+ chunk) != MEMTX_OK) {
139
+ return false;
140
+ }
141
+
142
+ if (!dat_mode && !src_fixed) {
143
+ line_src += chunk;
144
+ }
145
+ if (!dst_fixed) {
146
+ line_dst += chunk;
147
+ }
148
+ remaining -= chunk;
149
+ }
150
+
151
+ if (!dat_mode && !src_fixed) {
152
+ if (llt_cfg & K230_GSDMA_LLT_2D_MODE) { /* 2d mode */
153
+ src += line_size + line_space;
154
+ } else {
155
+ src += line_size;
156
+ }
157
+ }
158
+
159
+ if (!dst_fixed) {
160
+ dst += line_size;
161
+ }
162
+ }
163
+
164
+ return true;
165
+}
166
+
167
+static void k230_gsdma_sdma_set_idle(K230GSDMAChannel *c)
168
+{
169
+ c->status &= ~(K230_GSDMA_SDMA_STATUS_BUSY | K230_GSDMA_SDMA_STATUS_PAUSE);
170
+ c->next_llt = 0;
171
+}
172
+
173
+static void k230_gsdma_sdma_set_paused(K230GSDMAChannel *c, hwaddr next_llt)
174
+{
175
+ c->status &= ~K230_GSDMA_SDMA_STATUS_BUSY;
176
+ c->status |= K230_GSDMA_SDMA_STATUS_PAUSE;
177
+ c->next_llt = next_llt;
178
+}
179
+
180
+static void k230_gsdma_run_sdma(K230GSDMAState *s, unsigned int ch, hwaddr addr)
181
+{
182
+ K230GSDMAChannel *c = &s->channels[ch];
183
+
184
+ c->status &= ~K230_GSDMA_SDMA_STATUS_PAUSE;
185
+ c->status |= K230_GSDMA_SDMA_STATUS_BUSY;
186
+ c->next_llt = 0;
187
+ c->current_llt = addr;
188
+
189
+ while (addr) {
190
+ K230GSDMALLT llt;
191
+ uint32_t cfg;
192
+ hwaddr next;
193
+
194
+ if (!k230_gsdma_read_sdma_llt(addr, &llt)) {
195
+ k230_gsdma_sdma_set_idle(c);
196
+ return;
197
+ }
198
+
199
+ cfg = le32_to_cpu(llt.cfg);
200
+ next = le32_to_cpu(llt.next_llt_addr);
201
+ c->current_llt = addr;
202
+
203
+ if (!k230_gsdma_transfer_llt(c, &llt)) {
204
+ k230_gsdma_sdma_set_idle(c);
205
+ return;
206
+ }
207
+
208
+ if (cfg & K230_GSDMA_LLT_NODE_INTR) {
209
+ s->dma_int_stat |= K230_GSDMA_SDMA_ITEM_INT(ch);
210
+ }
211
+
212
+ if (cfg & K230_GSDMA_LLT_PAUSE) {
213
+ s->dma_int_stat |= K230_GSDMA_SDMA_PAUSE_INT(ch);
214
+ k230_gsdma_sdma_set_paused(c, next);
215
+ k230_gsdma_update_irq(s);
216
+ return;
217
+ }
218
+
219
+ addr = next;
220
+ }
221
+
222
+ s->dma_int_stat |= K230_GSDMA_SDMA_DONE_INT(ch);
223
+ s->dma_ch_en &= ~BIT(ch);
224
+ k230_gsdma_sdma_set_idle(c);
225
+ k230_gsdma_update_irq(s);
226
+}
227
+
228
+/* Decomp gzip request step sdma */
229
+static void k230_gsdma_step_sdma(K230GSDMAState *s, unsigned int ch, hwaddr addr)
230
+{
231
+ K230GSDMAChannel *c = &s->channels[ch];
232
+ K230GSDMALLT llt;
233
+ uint32_t cfg;
234
+ hwaddr next;
235
+
236
+ if (!addr || !k230_gsdma_read_sdma_llt(addr, &llt)) {
237
+ k230_gsdma_sdma_set_idle(c);
238
+ return;
239
+ }
240
+
241
+ cfg = le32_to_cpu(llt.cfg);
242
+ next = le32_to_cpu(llt.next_llt_addr);
243
+
244
+ c->status |= K230_GSDMA_SDMA_STATUS_BUSY;
245
+ c->status &= ~K230_GSDMA_SDMA_STATUS_PAUSE;
246
+ c->current_llt = addr;
247
+
248
+ if (!k230_gsdma_transfer_llt(c, &llt)) {
249
+ k230_gsdma_sdma_set_idle(c);
250
+ return;
251
+ }
252
+
253
+ if (cfg & K230_GSDMA_LLT_NODE_INTR) {
254
+ s->dma_int_stat |= K230_GSDMA_SDMA_ITEM_INT(ch);
255
+ }
256
+
257
+ c->status &= ~K230_GSDMA_SDMA_STATUS_BUSY;
258
+ c->next_llt = next;
259
+ if (next == 0) {
260
+ s->dma_int_stat |= K230_GSDMA_SDMA_DONE_INT(ch);
261
+ s->dma_ch_en &= ~BIT(ch);
262
+ }
263
+
264
+ k230_gsdma_update_irq(s);
265
+}
266
+
267
+static const VMStateDescription vmstate_k230_gsdma_channel = {
268
+ .name = "k230.gsdma.channel",
269
+ .version_id = 1,
270
+ .minimum_version_id = 1,
271
+ .fields = (const VMStateField[]) {
272
+ VMSTATE_UINT32(ctl, K230GSDMAChannel),
273
+ VMSTATE_UINT32(status, K230GSDMAChannel),
274
+ VMSTATE_UINT32(cfg, K230GSDMAChannel),
275
+ VMSTATE_UINT32(usr_data, K230GSDMAChannel),
276
+ VMSTATE_UINT32(llt_saddr, K230GSDMAChannel),
277
+ VMSTATE_UINT32(current_llt, K230GSDMAChannel),
278
+ VMSTATE_UINT32(next_llt, K230GSDMAChannel),
279
+ VMSTATE_END_OF_LIST()
280
+ }
281
+};
282
+
283
+static const VMStateDescription vmstate_k230_gsdma = {
284
+ .name = "k230.gsdma",
285
+ .version_id = 1,
286
+ .minimum_version_id = 1,
287
+ .fields = (const VMStateField[]) {
288
+ VMSTATE_UINT32(dma_ch_en, K230GSDMAState),
289
+ VMSTATE_UINT32(dma_int_mask, K230GSDMAState),
290
+ VMSTATE_UINT32(dma_int_stat, K230GSDMAState),
291
+ VMSTATE_UINT32(dma_cfg, K230GSDMAState),
292
+ VMSTATE_UINT32(dma_weight, K230GSDMAState),
293
+ VMSTATE_STRUCT_ARRAY(channels, K230GSDMAState,
294
+ K230_GSDMA_NUM_SDMA_CHANNELS, 2,
295
+ vmstate_k230_gsdma_channel, K230GSDMAChannel),
296
+ VMSTATE_END_OF_LIST()
297
+ }
298
+};
299
+
300
+static uint64_t k230_gsdma_read(void *opaque, hwaddr addr, unsigned int size)
301
+{
302
+ K230GSDMAState *s = K230_GSDMA(opaque);
303
+ unsigned int ch;
304
+ hwaddr ch_off;
305
+ uint64_t ret = 0;
306
+
307
+ switch (addr) {
308
+ case K230_GSDMA_DMA_CH_EN:
309
+ ret = s->dma_ch_en;
310
+ break;
311
+ case K230_GSDMA_DMA_INT_MASK:
312
+ ret = s->dma_int_mask;
313
+ break;
314
+ case K230_GSDMA_DMA_INT_STAT:
315
+ ret = s->dma_int_stat;
316
+ break;
317
+ case K230_GSDMA_DMA_CFG:
318
+ ret = s->dma_cfg;
319
+ break;
320
+ case K230_GSDMA_DMA_WEIGHT:
321
+ ret = s->dma_weight;
322
+ break;
323
+ default:
324
+ if (addr < K230_GSDMA_CH_BASE) {
325
+ qemu_log_mask(LOG_GUEST_ERROR,
326
+ "%s: not implement gdma at offset 0x%" HWADDR_PRIx
327
+ "\n", __func__, addr);
328
+ }
329
+ break;
330
+ }
331
+
332
+ ch = (addr - K230_GSDMA_CH_BASE) / K230_GSDMA_CH_STRIDE;
333
+ ch_off = (addr - K230_GSDMA_CH_BASE) % K230_GSDMA_CH_STRIDE;
334
+ if (ch < K230_GSDMA_NUM_SDMA_CHANNELS) {
335
+ switch (ch_off) {
336
+ case K230_GSDMA_CH_CTL:
337
+ break;
338
+ case K230_GSDMA_CH_STATUS:
339
+ ret = s->channels[ch].status;
340
+ break;
341
+ case K230_GSDMA_CH_CFG:
342
+ ret = s->channels[ch].cfg;
343
+ break;
344
+ case K230_GSDMA_CH_USR_DATA:
345
+ ret = s->channels[ch].usr_data;
346
+ break;
347
+ case K230_GSDMA_CH_LLT_SADDR:
348
+ ret = s->channels[ch].llt_saddr;
349
+ break;
350
+ case K230_GSDMA_CH_CURRENT_LLT:
351
+ ret = s->channels[ch].current_llt;
352
+ break;
353
+ default:
354
+ break;
355
+ }
356
+ }
357
+
358
+ trace_k230_gsdma_read(addr, size, ret);
359
+ return ret;
360
+}
361
+
362
+static void k230_gsdma_write_channel(K230GSDMAState *s, unsigned int ch,
363
+ hwaddr ch_off, uint32_t value)
364
+{
365
+ K230GSDMAChannel *c = &s->channels[ch];
366
+ bool handshake_mode = ch < 2 && k230_gsdma_decomp_enabled(s);
367
+
368
+ switch (ch_off) {
369
+ case K230_GSDMA_CH_CTL:
370
+ c->ctl = value;
371
+ if ((value & K230_GSDMA_CTL_STOP) != 0) {
372
+ c->started = false;
373
+ k230_gsdma_sdma_set_idle(c);
374
+ break;
375
+ }
376
+ if ((value & K230_GSDMA_CTL_RESUME) != 0 &&
377
+ (c->status & K230_GSDMA_SDMA_STATUS_PAUSE) && c->next_llt != 0) {
378
+ k230_gsdma_run_sdma(s, ch, c->next_llt);
379
+ break;
380
+ }
381
+ if (handshake_mode && (value & K230_GSDMA_CTL_START) != 0) {
382
+ c->started = true;
383
+ c->status &= ~(K230_GSDMA_SDMA_STATUS_BUSY |
384
+ K230_GSDMA_SDMA_STATUS_PAUSE);
385
+ c->current_llt = 0;
386
+ c->next_llt = 0;
387
+ break;
388
+ }
389
+ if ((value & K230_GSDMA_CTL_START) != 0 &&
390
+ (s->dma_ch_en & BIT(ch)) && c->llt_saddr != 0) {
391
+ c->started = true;
392
+ k230_gsdma_run_sdma(s, ch, c->llt_saddr);
393
+ }
394
+ break;
395
+ case K230_GSDMA_CH_CFG:
396
+ c->cfg = value;
397
+ break;
398
+ case K230_GSDMA_CH_USR_DATA:
399
+ c->usr_data = value;
400
+ break;
401
+ case K230_GSDMA_CH_LLT_SADDR:
402
+ c->llt_saddr = value;
403
+ break;
404
+ case K230_GSDMA_CH_CURRENT_LLT:
405
+ break;
406
+ default:
407
+ break;
408
+ }
409
+}
410
+
411
+static void k230_gsdma_write(void *opaque, hwaddr addr,
412
+ uint64_t value, unsigned int size)
413
+{
414
+ K230GSDMAState *s = K230_GSDMA(opaque);
415
+ unsigned int ch;
416
+ hwaddr ch_off;
417
+ uint32_t v = value;
418
+
419
+ trace_k230_gsdma_write(addr, size, value);
420
+
421
+ switch (addr) {
422
+ case K230_GSDMA_DMA_CH_EN:
423
+ s->dma_ch_en = v & K230_GSDMA_DMA_CH_EN_MASK;
424
+ return;
425
+ case K230_GSDMA_DMA_INT_MASK:
426
+ s->dma_int_mask = v;
427
+ k230_gsdma_update_irq(s);
428
+ return;
429
+ case K230_GSDMA_DMA_INT_STAT:
430
+ s->dma_int_stat &= ~v;
431
+ k230_gsdma_update_irq(s);
432
+ return;
433
+ case K230_GSDMA_DMA_CFG:
434
+ s->dma_cfg = v;
435
+ return;
436
+ case K230_GSDMA_DMA_WEIGHT:
437
+ s->dma_weight = v & 0x00ffffff;
438
+ return;
439
+ default:
440
+ break;
441
+ }
442
+
443
+ if (addr < K230_GSDMA_CH_BASE) {
444
+ qemu_log_mask(LOG_GUEST_ERROR,
445
+ "%s: not implement gdma at offset 0x%" HWADDR_PRIx
446
+ "\n", __func__, addr);
447
+ return;
448
+ }
449
+
450
+ ch = (addr - K230_GSDMA_CH_BASE) / K230_GSDMA_CH_STRIDE;
451
+ ch_off = (addr - K230_GSDMA_CH_BASE) % K230_GSDMA_CH_STRIDE;
452
+ if (ch >= K230_GSDMA_NUM_SDMA_CHANNELS) {
453
+ return;
454
+ }
455
+
456
+ k230_gsdma_write_channel(s, ch, ch_off, v);
457
+}
458
+
459
+static void k230_gsdma_handle_signal(void *opaque, int n, int level)
460
+{
461
+ K230GSDMAState *s = opaque;
462
+ K230GSDMAChannel *c;
463
+ hwaddr addr;
464
+ unsigned int ch;
465
+
466
+ if (!level || n == K230_GSDMA_GPIO_DECOMP_CTRL_EN) {
467
+ return;
468
+ }
469
+
470
+ ch = n - 1;
471
+
472
+ c = &s->channels[ch];
473
+ if (!k230_gsdma_decomp_enabled(s) || !(s->dma_ch_en & BIT(ch)) || !c->started) {
474
+ return;
475
+ }
476
+
477
+ if (c->current_llt == 0) {
478
+ addr = c->llt_saddr;
479
+ } else {
480
+ addr = c->next_llt;
481
+ }
482
+ if (!addr) {
483
+ return;
484
+ }
485
+
486
+ k230_gsdma_step_sdma(s, ch, addr);
487
+ qemu_set_irq(s->handshake_out[n - 1], 1);
488
+ qemu_set_irq(s->handshake_out[n - 1], 0);
489
+}
490
+
491
+static const MemoryRegionOps k230_gsdma_ops = {
492
+ .read = k230_gsdma_read,
493
+ .write = k230_gsdma_write,
494
+ .endianness = DEVICE_LITTLE_ENDIAN,
495
+ .impl.min_access_size = 4,
496
+ .impl.max_access_size = 4,
497
+ .valid.min_access_size = 4,
498
+ .valid.max_access_size = 4,
499
+};
500
+
501
+static void k230_gsdma_reset_hold(Object *obj, ResetType type)
502
+{
503
+ K230GSDMAState *s = K230_GSDMA(obj);
504
+
505
+ memset(s->channels, 0, sizeof(s->channels));
506
+ s->dma_ch_en = 0;
507
+ s->dma_int_mask = 0;
508
+ s->dma_int_stat = 0;
509
+ s->dma_cfg = K230_GSDMA_DMA_CFG_RESET;
510
+ s->dma_weight = 0;
511
+}
512
+
513
+static void k230_gsdma_realize(DeviceState *dev, Error **errp)
514
+{
515
+ K230GSDMAState *s = K230_GSDMA(dev);
516
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
517
+
518
+ memory_region_init_io(&s->iomem, OBJECT(dev), &k230_gsdma_ops, s,
519
+ TYPE_K230_GSDMA, K230_GSDMA_MMIO_SIZE);
520
+ sysbus_init_mmio(sbd, &s->iomem);
521
+ sysbus_init_irq(sbd, &s->irq);
522
+ qdev_init_gpio_in(DEVICE(dev), k230_gsdma_handle_signal,
523
+ K230_GSDMA_NUM_GPIOS_IN);
524
+ qdev_init_gpio_out(DEVICE(dev), s->handshake_out,
525
+ K230_GSDMA_NUM_GPIOS_OUT);
526
+}
527
+
528
+static void k230_gsdma_class_init(ObjectClass *klass, const void *data)
529
+{
530
+ DeviceClass *dc = DEVICE_CLASS(klass);
531
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
532
+
533
+ dc->realize = k230_gsdma_realize;
534
+ rc->phases.hold = k230_gsdma_reset_hold;
535
+ dc->vmsd = &vmstate_k230_gsdma;
536
+ dc->desc = "Kendryte K230 GSDMA";
537
+}
538
+
539
+static const TypeInfo k230_gsdma_info = {
540
+ .name = TYPE_K230_GSDMA,
541
+ .parent = TYPE_SYS_BUS_DEVICE,
542
+ .instance_size = sizeof(K230GSDMAState),
543
+ .class_init = k230_gsdma_class_init,
544
+};
545
+
546
+static void k230_gsdma_register_types(void)
547
+{
548
+ type_register_static(&k230_gsdma_info);
549
+}
550
+
551
+type_init(k230_gsdma_register_types)