K230 GSDMA includes SDMA (System Direct Memory Access) and GDMA (Graphic Direct 
Memory Access).
In this patch, add SDMA for k230 board. The following features have not been 
implemented:
1. axi protocol related
2. channel arbitration
3. lower power mode

SDMA supports transfer data between memory, in which case SDMA is the 
controller.
It also supports transfer data to decomp_gzip and caching it through SRAM. 
In this case, decomp_gzip is the controller, which controls SDMA through 
handshake signals.

According to "K230 Technical Reference Manual v0.3.1" section 2.5.2.
https://download.kendryte.com/developer/k230/HDK/K230%E7%A1%AC%E4%BB%B6%E6%96%87%E6%A1%A3/K230_Technical_Reference_Manual_V0.3.1_20241118.pdf

This commit includes:
- K230 SDMA (System Direct Memory Access) model (k230_gsdma.c, k230_gsdma.h)
- GSDMA trace log (trace-events)
- Kconfig and meson.build

Signed-off-by: Tao Ding <[email protected]>
---
 hw/dma/Kconfig              |   3 +
 hw/dma/k230_gsdma.c         | 532 ++++++++++++++++++++++++++++++++++++
 hw/dma/meson.build          |   1 +
 hw/dma/trace-events         |   6 +
 include/hw/dma/k230_gsdma.h | 128 +++++++++
 5 files changed, 670 insertions(+)
 create mode 100644 hw/dma/k230_gsdma.c
 create mode 100644 include/hw/dma/k230_gsdma.h

diff --git a/hw/dma/Kconfig b/hw/dma/Kconfig
index 98fbb1bb04..0a23e7d7b8 100644
--- a/hw/dma/Kconfig
+++ b/hw/dma/Kconfig
@@ -30,3 +30,6 @@ config SIFIVE_PDMA
 config XLNX_CSU_DMA
     bool
     select REGISTER
+
+config K230_GSDMA
+    bool
\ No newline at end of file
diff --git a/hw/dma/k230_gsdma.c b/hw/dma/k230_gsdma.c
new file mode 100644
index 0000000000..1f76f62fa0
--- /dev/null
+++ b/hw/dma/k230_gsdma.c
@@ -0,0 +1,532 @@
+/*
+ * Kendryte K230 GSDMA
+ *
+ * Copyright (c) 2026 Tao Ding <[email protected]>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * GSDMA includes SDMA (System Direct Memory Access, K230 TRM section 10.2) and
+ * GDMA (Graphic Direct Memory Access, K230 TRM section 2.5.2).
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/bitops.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "migration/vmstate.h"
+#include "system/address-spaces.h"
+#include "hw/dma/k230_gsdma.h"
+#include "hw/core/irq.h"
+#include "trace.h"
+
+static void k230_gsdma_update_irq(K230GSDMAState *s)
+{
+    qemu_set_irq(s->irq, !!(s->dma_int_stat & ~s->dma_int_mask));
+}
+
+static bool k230_gsdma_decomp_enabled(K230GSDMAState *s)
+{
+    /* Only sdma channel 0 has decomp enable flag */
+    return !!(s->channels[0].cfg & K230_GSDMA_CH0_CFG_DECOMP_CTRL_EN);
+}
+
+static bool k230_gsdma_read_sdma_llt(hwaddr addr, K230GSDMALLT *llt)
+{
+    bool ok = address_space_read(&address_space_memory, addr,
+                                 MEMTXATTRS_UNSPECIFIED, llt,
+                                 sizeof(*llt)) == MEMTX_OK;
+
+    if (ok) {
+        trace_k230_gsdma_read_sdma_llt(addr, le32_to_cpu(llt->cfg),
+                                       le32_to_cpu(llt->src_addr),
+                                       le32_to_cpu(llt->line_size),
+                                       le32_to_cpu(llt->line_cfg),
+                                       le32_to_cpu(llt->dst_addr),
+                                       le32_to_cpu(llt->next_llt_addr));
+    } else {
+        trace_k230_gsdma_read_sdma_llt_failed(addr);
+    }
+
+    return ok;
+}
+
+static unsigned int k230_gsdma_usr_data_size(uint32_t cfg)
+{
+    /* decode usr_dat_size in CH_CFG register */
+    switch (extract32(cfg, K230_GSDMA_CH_CFG_USR_DATA_SIZE_SHIFT, 2)) {
+    case 0:
+        return 1;
+    case 1:
+        return 2;
+    default:
+        return 4;
+    }
+}
+
+static bool k230_gsdma_transfer_llt(K230GSDMAChannel *c, const K230GSDMALLT 
*llt)
+{
+    uint32_t llt_cfg = le32_to_cpu(llt->cfg);
+    hwaddr src = le32_to_cpu(llt->src_addr);
+    hwaddr dst = le32_to_cpu(llt->dst_addr);
+    uint32_t line_size = le32_to_cpu(llt->line_size);
+    uint32_t line_cfg = le32_to_cpu(llt->line_cfg);
+    /* Number of lines required for 2d mode */
+    uint32_t line_num =
+            (llt_cfg & K230_GSDMA_LLT_2D_MODE) ? extract32(line_cfg, 0, 16) : 
1;
+    /* Stride line size required for 2d mode */
+    uint32_t line_space = extract32(line_cfg, 16, 16);
+
+    bool dat_mode = c->cfg & K230_GSDMA_CH_CFG_DAT_MODE;
+    bool src_fixed = c->cfg & K230_GSDMA_CH_CFG_SRC_FIXED;
+    bool dst_fixed = c->cfg & K230_GSDMA_CH_CFG_DST_FIXED;
+
+    uint8_t buf[8]; /* K230 sdma axi data width is 64-bit */
+    uint8_t fill[4];    /* Usr data is 32-bit*/
+    unsigned int fill_len = k230_gsdma_usr_data_size(c->cfg);
+    uint32_t i;
+
+    memcpy(fill, &c->usr_data, sizeof(fill));
+    if (dat_mode) {
+        for (i = 0; i < (uint32_t)sizeof(buf); i++) {
+            buf[i] = fill[i % fill_len];
+        }
+    }
+
+    for (i = 0; i < line_num; i++) {
+        uint32_t remaining = line_size;
+        hwaddr line_src = src;
+        hwaddr line_dst = dst;
+
+        while (remaining) {
+            uint32_t chunk = MIN(remaining, (uint32_t)sizeof(buf));
+
+            if (!dat_mode && address_space_read(&address_space_memory, 
line_src,
+                                          MEMTXATTRS_UNSPECIFIED, buf,
+                                          chunk) != MEMTX_OK) {
+                return false;
+            }
+
+            if (address_space_write(&address_space_memory, line_dst,
+                                    MEMTXATTRS_UNSPECIFIED, buf,
+                                    chunk) != MEMTX_OK) {
+                return false;
+            }
+
+            if (!dat_mode && !src_fixed) {
+                line_src += chunk;
+            }
+            if (!dst_fixed) {
+                line_dst += chunk;
+            }
+            remaining -= chunk;
+        }
+
+        if (!dat_mode) {
+            if (llt_cfg & K230_GSDMA_LLT_2D_MODE) { /*2d mode*/
+                src += line_size + line_space;
+            } else if (!src_fixed) {
+                src += line_size;
+            }
+        }
+
+        if (!dst_fixed) {
+            dst += line_size;
+        }
+    }
+
+    return true;
+}
+
+static void k230_gsdma_sdma_set_idle(K230GSDMAChannel *c)
+{
+    c->status &= ~(K230_GSDMA_SDMA_STATUS_BUSY | K230_GSDMA_SDMA_STATUS_PAUSE);
+    c->next_llt = 0;
+}
+
+static void k230_gsdma_sdma_set_paused(K230GSDMAChannel *c, hwaddr next_llt)
+{
+    c->status &= ~K230_GSDMA_SDMA_STATUS_BUSY;
+    c->status |= K230_GSDMA_SDMA_STATUS_PAUSE;
+    c->next_llt = next_llt;
+}
+
+static void k230_gsdma_run_sdma(K230GSDMAState *s, unsigned int ch, hwaddr 
addr)
+{
+    K230GSDMAChannel *c = &s->channels[ch];
+
+    c->status &= ~K230_GSDMA_SDMA_STATUS_PAUSE;
+    c->status |= K230_GSDMA_SDMA_STATUS_BUSY;
+    c->next_llt = 0;
+    c->current_llt = addr;
+
+    while (addr) {
+        K230GSDMALLT llt;
+        uint32_t cfg;
+        hwaddr next;
+
+        if (!k230_gsdma_read_sdma_llt(addr, &llt)) {
+            k230_gsdma_sdma_set_idle(c);
+            return;
+        }
+
+        cfg = le32_to_cpu(llt.cfg);
+        next = le32_to_cpu(llt.next_llt_addr);
+        c->current_llt = addr;
+
+        if (!k230_gsdma_transfer_llt(c, &llt)) {
+            k230_gsdma_sdma_set_idle(c);
+            return;
+        }
+
+        if (cfg & K230_GSDMA_LLT_NODE_INTR) {
+            s->dma_int_stat |= K230_GSDMA_SDMA_ITEM_INT(ch);
+        }
+
+        if (cfg & K230_GSDMA_LLT_PAUSE) {
+            s->dma_int_stat |= K230_GSDMA_SDMA_PAUSE_INT(ch);
+            k230_gsdma_sdma_set_paused(c, next);
+            k230_gsdma_update_irq(s);
+            return;
+        }
+
+        addr = next;
+    }
+
+    s->dma_int_stat |= K230_GSDMA_SDMA_DONE_INT(ch);
+    k230_gsdma_sdma_set_idle(c);
+    k230_gsdma_update_irq(s);
+}
+
+/* Decomp gzip request step sdma */
+static void k230_gsdma_step_sdma(K230GSDMAState *s, unsigned int ch, hwaddr 
addr)
+{
+    K230GSDMAChannel *c = &s->channels[ch];
+    K230GSDMALLT llt;
+    uint32_t cfg;
+    hwaddr next;
+
+    if (!addr || !k230_gsdma_read_sdma_llt(addr, &llt)) {
+        k230_gsdma_sdma_set_idle(c);
+        return;
+    }
+
+    cfg = le32_to_cpu(llt.cfg);
+    next = le32_to_cpu(llt.next_llt_addr);
+
+    c->status |= K230_GSDMA_SDMA_STATUS_BUSY;
+    c->status &= ~K230_GSDMA_SDMA_STATUS_PAUSE;
+    c->current_llt = addr;
+
+    if (!k230_gsdma_transfer_llt(c, &llt)) {
+        k230_gsdma_sdma_set_idle(c);
+        return;
+    }
+
+    if (cfg & K230_GSDMA_LLT_NODE_INTR) {
+        s->dma_int_stat |= K230_GSDMA_SDMA_ITEM_INT(ch);
+    }
+
+    c->status &= ~K230_GSDMA_SDMA_STATUS_BUSY;
+    c->next_llt = next;
+    if (next == 0) {
+        s->dma_int_stat |= K230_GSDMA_SDMA_DONE_INT(ch);
+    }
+
+    k230_gsdma_update_irq(s);
+}
+
+static const VMStateDescription vmstate_k230_gsdma_channel = {
+    .name = "k230.gsdma.channel",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (const VMStateField[]) {
+        VMSTATE_UINT32(ctl, K230GSDMAChannel),
+        VMSTATE_UINT32(status, K230GSDMAChannel),
+        VMSTATE_UINT32(cfg, K230GSDMAChannel),
+        VMSTATE_UINT32(usr_data, K230GSDMAChannel),
+        VMSTATE_UINT32(llt_saddr, K230GSDMAChannel),
+        VMSTATE_UINT32(current_llt, K230GSDMAChannel),
+        VMSTATE_UINT32(next_llt, K230GSDMAChannel),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static const VMStateDescription vmstate_k230_gsdma = {
+    .name = "k230.gsdma",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (const VMStateField[]) {
+        VMSTATE_UINT32(dma_ch_en, K230GSDMAState),
+        VMSTATE_UINT32(dma_int_mask, K230GSDMAState),
+        VMSTATE_UINT32(dma_int_stat, K230GSDMAState),
+        VMSTATE_UINT32(dma_cfg, K230GSDMAState),
+        VMSTATE_UINT32(dma_weight, K230GSDMAState),
+        VMSTATE_STRUCT_ARRAY(channels, K230GSDMAState,
+                             K230_GSDMA_NUM_SDMA_CHANNELS, 2,
+                             vmstate_k230_gsdma_channel, K230GSDMAChannel),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static uint64_t k230_gsdma_read(void *opaque, hwaddr addr, unsigned int size)
+{
+    K230GSDMAState *s = K230_GSDMA(opaque);
+    unsigned int ch;
+    hwaddr ch_off;
+    uint64_t ret = 0;
+
+    switch (addr) {
+    case K230_GSDMA_DMA_CH_EN:
+        ret = s->dma_ch_en;
+        break;
+    case K230_GSDMA_DMA_INT_MASK:
+        ret = s->dma_int_mask;
+        break;
+    case K230_GSDMA_DMA_INT_STAT:
+        ret = s->dma_int_stat;
+        break;
+    case K230_GSDMA_DMA_CFG:
+        ret = s->dma_cfg;
+        break;
+    case K230_GSDMA_GDMA_CTRL:
+    case K230_GSDMA_GDMA_LLI_BASE:
+    case K230_GSDMA_GDMA_CURRENT_LLT:
+    case K230_GSDMA_GDMA_CH_CNT0 ... K230_GSDMA_GDMA_CH_CNT_LAST:
+        break;
+    case K230_GSDMA_DMA_WEIGHT:
+        ret = s->dma_weight;
+        break;
+    default:
+        break;
+    }
+
+    ch = (addr - K230_GSDMA_CH_BASE) / K230_GSDMA_CH_STRIDE;
+    ch_off = (addr - K230_GSDMA_CH_BASE) % K230_GSDMA_CH_STRIDE;
+    if (ch < K230_GSDMA_NUM_SDMA_CHANNELS) {
+        switch (ch_off) {
+        case K230_GSDMA_CH_CTL:
+            break;
+        case K230_GSDMA_CH_STATUS:
+            ret = s->channels[ch].status;
+            break;
+        case K230_GSDMA_CH_CFG:
+            ret = s->channels[ch].cfg;
+            break;
+        case K230_GSDMA_CH_USR_DATA:
+            ret = s->channels[ch].usr_data;
+            break;
+        case K230_GSDMA_CH_LLT_SADDR:
+            ret = s->channels[ch].llt_saddr;
+            break;
+        case K230_GSDMA_CH_CURRENT_LLT:
+            ret = s->channels[ch].current_llt;
+            break;
+        default:
+            break;
+        }
+    }
+
+    trace_k230_gsdma_read(addr, size, ret);
+    return ret;
+}
+
+static void k230_gsdma_write_gdma(K230GSDMAState *s, hwaddr addr, uint32_t 
value)
+{
+    switch (addr) {
+    case K230_GSDMA_GDMA_CTRL:
+    case K230_GSDMA_GDMA_LLI_BASE:
+    case K230_GSDMA_GDMA_CURRENT_LLT:
+    case K230_GSDMA_GDMA_CH_CNT0 ... K230_GSDMA_GDMA_CH_CNT_LAST:
+        return;
+    default:
+        return;
+    }
+}
+
+static void k230_gsdma_write_channel(K230GSDMAState *s, unsigned int ch,
+                                     hwaddr ch_off, uint32_t value)
+{
+    K230GSDMAChannel *c = &s->channels[ch];
+    bool handshake_mode = ch < 2 && k230_gsdma_decomp_enabled(s);
+
+    switch (ch_off) {
+    case K230_GSDMA_CH_CTL:
+        c->ctl = value;
+        if ((value & K230_GSDMA_CTL_STOP) != 0) {
+            c->started = false;
+            k230_gsdma_sdma_set_idle(c);
+            break;
+        }
+        if ((value & K230_GSDMA_CTL_RESUME) != 0 &&
+            (c->status & K230_GSDMA_SDMA_STATUS_PAUSE) && c->next_llt != 0) {
+            k230_gsdma_run_sdma(s, ch, c->next_llt);
+            break;
+        }
+        if (handshake_mode && (value & K230_GSDMA_CTL_START) != 0) {
+            c->started = true;
+            c->status &= ~(K230_GSDMA_SDMA_STATUS_BUSY |
+                           K230_GSDMA_SDMA_STATUS_PAUSE);
+            c->current_llt = 0;
+            c->next_llt = 0;
+            break;
+        }
+        if ((value & K230_GSDMA_CTL_START) != 0 &&
+            (s->dma_ch_en & BIT(ch)) && c->llt_saddr != 0) {
+            c->started = true;
+            k230_gsdma_run_sdma(s, ch, c->llt_saddr);
+        }
+        break;
+    case K230_GSDMA_CH_CFG:
+        c->cfg = value;
+        break;
+    case K230_GSDMA_CH_USR_DATA:
+        c->usr_data = value;
+        break;
+    case K230_GSDMA_CH_LLT_SADDR:
+        c->llt_saddr = value;
+        break;
+    case K230_GSDMA_CH_CURRENT_LLT:
+        break;
+    default:
+        break;
+    }
+}
+
+static void k230_gsdma_write(void *opaque, hwaddr addr,
+                             uint64_t value, unsigned int size)
+{
+    K230GSDMAState *s = K230_GSDMA(opaque);
+    unsigned int ch;
+    hwaddr ch_off;
+    uint32_t v = value;
+
+    trace_k230_gsdma_write(addr, size, value);
+
+    switch (addr) {
+    case K230_GSDMA_DMA_CH_EN:
+        s->dma_ch_en = v & K230_GSDMA_DMA_CH_EN_MASK;
+        return;
+    case K230_GSDMA_DMA_INT_MASK:
+        s->dma_int_mask = v;
+        k230_gsdma_update_irq(s);
+        return;
+    case K230_GSDMA_DMA_INT_STAT:
+        s->dma_int_stat &= ~v;
+        k230_gsdma_update_irq(s);
+        return;
+    case K230_GSDMA_DMA_CFG:
+        s->dma_cfg = v;
+        return;
+    case K230_GSDMA_DMA_WEIGHT:
+        s->dma_weight = v & 0x00ffffff;
+        return;
+    default:
+        break;
+    }
+
+    if (addr < K230_GSDMA_CH_BASE) {
+        k230_gsdma_write_gdma(s, addr, v);
+        return;
+    }
+
+    ch = (addr - K230_GSDMA_CH_BASE) / K230_GSDMA_CH_STRIDE;
+    ch_off = (addr - K230_GSDMA_CH_BASE) % K230_GSDMA_CH_STRIDE;
+    if (ch >= K230_GSDMA_NUM_SDMA_CHANNELS) {
+        return;
+    }
+
+    k230_gsdma_write_channel(s, ch, ch_off, v);
+}
+
+static void k230_gsdma_handle_signal(void *opaque, int n, int level)
+{
+    K230GSDMAState *s = opaque;
+    K230GSDMAChannel *c;
+    hwaddr addr;
+    unsigned int ch;
+
+    if (!level || n == K230_GSDMA_GPIO_DECOMP_CTRL_EN) {
+        return;
+    }
+
+    ch = n - 1;
+
+    c = &s->channels[ch];
+    if (!k230_gsdma_decomp_enabled(s) || !(s->dma_ch_en & BIT(ch)) || 
!c->started) {
+        return;
+    }
+
+    if (c->current_llt == 0) {
+        addr = c->llt_saddr;
+    } else {
+        addr = c->next_llt;
+    }
+    if (!addr) {
+        return;
+    }
+
+    k230_gsdma_step_sdma(s, ch, addr);
+    qemu_set_irq(s->handshake_out[n - 1], 1);
+    qemu_set_irq(s->handshake_out[n - 1], 0);
+}
+
+static const MemoryRegionOps k230_gsdma_ops = {
+    .read = k230_gsdma_read,
+    .write = k230_gsdma_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .impl.min_access_size = 4,
+    .impl.max_access_size = 4,
+    .valid.min_access_size = 4,
+    .valid.max_access_size = 4,
+};
+
+static void k230_gsdma_reset(DeviceState *dev)
+{
+    K230GSDMAState *s = K230_GSDMA(dev);
+
+    memset(s->channels, 0, sizeof(s->channels));
+    s->dma_ch_en = 0;
+    s->dma_int_mask = 0;
+    s->dma_int_stat = 0;
+    s->dma_cfg = K230_GSDMA_DMA_CFG_RESET;
+    s->dma_weight = 0;
+}
+
+static void k230_gsdma_realize(DeviceState *dev, Error **errp)
+{
+    K230GSDMAState *s = K230_GSDMA(dev);
+    SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+
+    memory_region_init_io(&s->iomem, OBJECT(dev), &k230_gsdma_ops, s,
+                          TYPE_K230_GSDMA, K230_GSDMA_MMIO_SIZE);
+    sysbus_init_mmio(sbd, &s->iomem);
+    sysbus_init_irq(sbd, &s->irq);
+    qdev_init_gpio_in(DEVICE(dev), k230_gsdma_handle_signal,
+                      K230_GSDMA_NUM_GPIOS_IN);
+    qdev_init_gpio_out(DEVICE(dev), s->handshake_out,
+                       K230_GSDMA_NUM_GPIOS_OUT);
+}
+
+static void k230_gsdma_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    dc->realize = k230_gsdma_realize;
+    device_class_set_legacy_reset(dc, k230_gsdma_reset);
+    dc->vmsd = &vmstate_k230_gsdma;
+    dc->desc = "Kendryte K230 GSDMA";
+}
+
+static const TypeInfo k230_gsdma_info = {
+    .name = TYPE_K230_GSDMA,
+    .parent = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(K230GSDMAState),
+    .class_init = k230_gsdma_class_init,
+};
+
+static void k230_gsdma_register_types(void)
+{
+    type_register_static(&k230_gsdma_info);
+}
+
+type_init(k230_gsdma_register_types)
diff --git a/hw/dma/meson.build b/hw/dma/meson.build
index cc7810beb8..45db5e7357 100644
--- a/hw/dma/meson.build
+++ b/hw/dma/meson.build
@@ -12,3 +12,4 @@ system_ss.add(when: 'CONFIG_OMAP', if_true: 
files('omap_dma.c', 'soc_dma.c'))
 system_ss.add(when: 'CONFIG_RASPI', if_true: files('bcm2835_dma.c'))
 system_ss.add(when: 'CONFIG_SIFIVE_PDMA', if_true: files('sifive_pdma.c'))
 system_ss.add(when: 'CONFIG_XLNX_CSU_DMA', if_true: files('xlnx_csu_dma.c'))
+system_ss.add(when: 'CONFIG_K230_GSDMA', if_true: files('k230_gsdma.c'))
diff --git a/hw/dma/trace-events b/hw/dma/trace-events
index 4c09790f9a..a494755b5a 100644
--- a/hw/dma/trace-events
+++ b/hw/dma/trace-events
@@ -47,3 +47,9 @@ pl330_iomem_read(uint32_t addr, uint32_t data) "addr: 
0x%08"PRIx32" data: 0x%08"
 
 # xilinx_axidma.c
 xilinx_axidma_loading_desc_fail(uint32_t res) "error:%u"
+
+# k230_gsdma.c
+k230_gsdma_read(uint64_t addr, unsigned int size, uint64_t data) "K230 GSDMA 
read: [0x%"PRIx64"] size %u -> 0x%"PRIx64
+k230_gsdma_write(uint64_t addr, unsigned int size, uint64_t data) "K230 GSDMA 
write: [0x%"PRIx64"] size %u <- 0x%"PRIx64
+k230_gsdma_read_sdma_llt(uint64_t addr, uint32_t cfg, uint32_t src_addr, 
uint32_t line_size, uint32_t line_cfg, uint32_t dst_addr, uint32_t 
next_llt_addr) "K230 GSDMA read SDMA LLT: addr=0x%"PRIx64" cfg=0x%"PRIx32" 
src=0x%"PRIx32" line_size=0x%"PRIx32" line_cfg=0x%"PRIx32" dst=0x%"PRIx32" 
next=0x%"PRIx32
+k230_gsdma_read_sdma_llt_failed(uint64_t addr) "K230 GSDMA read SDMA LLT 
failed: addr=0x%"PRIx64
diff --git a/include/hw/dma/k230_gsdma.h b/include/hw/dma/k230_gsdma.h
new file mode 100644
index 0000000000..58aaa88674
--- /dev/null
+++ b/include/hw/dma/k230_gsdma.h
@@ -0,0 +1,128 @@
+/*
+ * K230 GSDMA
+ *
+ * Copyright (c) 2026 Tao Ding <[email protected]>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_DMA_K230_GSDMA_H
+#define HW_DMA_K230_GSDMA_H
+
+#include "hw/core/sysbus.h"
+
+#define TYPE_K230_GSDMA "riscv.k230.gsdma"
+OBJECT_DECLARE_SIMPLE_TYPE(K230GSDMAState, K230_GSDMA)
+
+#define K230_GSDMA_MMIO_SIZE          0x4000
+#define K230_GSDMA_NUM_SDMA_CHANNELS  4
+
+#define K230_GSDMA_CH_STRIDE          0x30
+#define K230_GSDMA_CH_BASE            0x50
+
+/* K230 SDMA request input */
+enum {
+    K230_GSDMA_GPIO_DECOMP_CTRL_EN,
+    K230_GSDMA_GPIO_DMA_WRITE_REQ,
+    K230_GSDMA_GPIO_DMA_READ_REQ,
+    K230_GSDMA_NUM_GPIOS_IN,
+};
+
+/* K230 SDMA ACK output */
+enum {
+    K230_GSDMA_GPIO_DMA_WRITE_ACK,
+    K230_GSDMA_GPIO_DMA_READ_ACK,
+    K230_GSDMA_NUM_GPIOS_OUT,
+};
+
+/* K230 GSDMA Registers Map */
+#define K230_GSDMA_DMA_CH_EN          0x00
+#define K230_GSDMA_DMA_INT_MASK       0x04
+#define K230_GSDMA_DMA_INT_STAT       0x08
+#define K230_GSDMA_DMA_CFG            0x0c
+#define K230_GSDMA_GDMA_CTRL          0x10
+#define K230_GSDMA_GDMA_LLI_BASE      0x14
+#define K230_GSDMA_GDMA_CH_CNT0       0x18
+#define K230_GSDMA_GDMA_CH_CNT_LAST   0x34
+#define K230_GSDMA_GDMA_CURRENT_LLT   0x38
+#define K230_GSDMA_DMA_WEIGHT         0x48
+
+/* K230 GSDMA SDMA Channel Register Map */
+#define K230_GSDMA_CH_CTL             0x00
+#define K230_GSDMA_CH_STATUS          0x04
+#define K230_GSDMA_CH_CFG             0x08
+#define K230_GSDMA_CH_USR_DATA        0x0c
+#define K230_GSDMA_CH_LLT_SADDR       0x10
+#define K230_GSDMA_CH_CURRENT_LLT     0x14
+
+/* K230 SDMA Control bit */
+#define K230_GSDMA_CTL_START          BIT(0)
+#define K230_GSDMA_CTL_STOP           BIT(1)
+#define K230_GSDMA_CTL_RESUME         BIT(2)
+
+/* K230 SDMA States bit */
+#define K230_GSDMA_SDMA_STATUS_BUSY   BIT(0)
+#define K230_GSDMA_SDMA_STATUS_PAUSE  BIT(1)
+
+/* K230 SDMA Interrupt bit */
+#define K230_GSDMA_SDMA_DONE_INT(ch)  BIT(ch)
+#define K230_GSDMA_SDMA_ITEM_INT(ch)  BIT((ch) + 4)
+#define K230_GSDMA_SDMA_PAUSE_INT(ch) BIT((ch) + 8)
+
+
+#define K230_GSDMA_DMA_CH_EN_MASK     MAKE_64BIT_MASK(0, 5)
+#define K230_GSDMA_DMA_CFG_RESET      0x000007ff
+
+/* K230 SDMA Linked List bit */
+#define K230_GSDMA_LLT_2D_MODE        BIT(28)
+#define K230_GSDMA_LLT_PAUSE          BIT(29)
+#define K230_GSDMA_LLT_NODE_INTR      BIT(30)
+#define K230_GSDMA_GDMA_LLT_GCH_SHIFT 16
+#define K230_GSDMA_GDMA_LLT_GCH_MASK  0x7
+#define K230_GSDMA_CH_CFG_DAT_MODE    BIT(0)
+#define K230_GSDMA_CH_CFG_USR_DATA_SIZE_SHIFT 1
+#define K230_GSDMA_CH_CFG_USR_DATA_SIZE_MASK  0x3
+#define K230_GSDMA_CH_CFG_SRC_FIXED   BIT(8)
+#define K230_GSDMA_CH_CFG_DST_FIXED   BIT(9)
+#define K230_GSDMA_CH0_CFG_DECOMP_CTRL_EN BIT(10)
+
+/* K230 SDMA channel state */
+typedef struct K230GSDMAChannel {
+    uint32_t ctl;
+    uint32_t status;
+    uint32_t cfg;
+    uint32_t usr_data;
+    uint32_t llt_saddr;
+    uint32_t current_llt;
+    uint32_t next_llt;
+    bool started;
+} K230GSDMAChannel;
+
+/* K230 SDMA Linked List */
+typedef struct K230GSDMALLT {
+    uint32_t cfg;
+    uint32_t src_addr;
+    uint32_t line_size;
+    uint32_t line_cfg;
+    uint32_t dst_addr;
+    uint32_t next_llt_addr;
+} K230GSDMALLT;
+
+/* K230 GSDMA state */
+struct K230GSDMAState {
+    SysBusDevice parent_obj;
+
+    MemoryRegion iomem;
+    qemu_irq irq;
+    qemu_irq handshake_out[K230_GSDMA_NUM_GPIOS_OUT];
+
+    uint32_t dma_ch_en;
+    uint32_t dma_int_mask;
+    uint32_t dma_int_stat;
+    uint32_t dma_cfg;
+    uint32_t dma_weight;
+
+    K230GSDMAChannel channels[K230_GSDMA_NUM_SDMA_CHANNELS];
+};
+
+#endif
-- 
2.43.0


Reply via email to