aboutsummaryrefslogtreecommitdiff
path: root/hw/dma/sifive_pdma.c
diff options
context:
space:
mode:
Diffstat (limited to 'hw/dma/sifive_pdma.c')
-rw-r--r--hw/dma/sifive_pdma.c54
1 files changed, 43 insertions, 11 deletions
diff --git a/hw/dma/sifive_pdma.c b/hw/dma/sifive_pdma.c
index 9b2ac2017d..b4fd40573a 100644
--- a/hw/dma/sifive_pdma.c
+++ b/hw/dma/sifive_pdma.c
@@ -54,6 +54,13 @@
#define DMA_EXEC_DST 0x110
#define DMA_EXEC_SRC 0x118
+/*
+ * FU540/FU740 docs are incorrect with NextConfig.wsize/rsize reset values.
+ * The reset values tested on Unleashed/Unmatched boards are 6 instead of 0.
+ */
+#define CONFIG_WRSZ_DEFAULT 6
+#define CONFIG_RDSZ_DEFAULT 6
+
enum dma_chan_state {
DMA_CHAN_STATE_IDLE,
DMA_CHAN_STATE_STARTED,
@@ -67,13 +74,13 @@ static void sifive_pdma_run(SiFivePDMAState *s, int ch)
uint64_t dst = s->chan[ch].next_dst;
uint64_t src = s->chan[ch].next_src;
uint32_t config = s->chan[ch].next_config;
- int wsize, rsize, size;
+ int wsize, rsize, size, remainder;
uint8_t buf[64];
int n;
/* do nothing if bytes to transfer is zero */
if (!bytes) {
- goto error;
+ goto done;
}
/*
@@ -99,11 +106,7 @@ static void sifive_pdma_run(SiFivePDMAState *s, int ch)
size = 6;
}
size = 1 << size;
-
- /* the bytes to transfer should be multiple of transaction size */
- if (bytes % size) {
- goto error;
- }
+ remainder = bytes % size;
/* indicate a DMA transfer is started */
s->chan[ch].state = DMA_CHAN_STATE_STARTED;
@@ -124,10 +127,13 @@ static void sifive_pdma_run(SiFivePDMAState *s, int ch)
s->chan[ch].exec_bytes -= size;
}
- /* indicate a DMA transfer is done */
- s->chan[ch].state = DMA_CHAN_STATE_DONE;
- s->chan[ch].control &= ~CONTROL_RUN;
- s->chan[ch].control |= CONTROL_DONE;
+ if (remainder) {
+ cpu_physical_memory_read(s->chan[ch].exec_src, buf, remainder);
+ cpu_physical_memory_write(s->chan[ch].exec_dst, buf, remainder);
+ s->chan[ch].exec_src += remainder;
+ s->chan[ch].exec_dst += remainder;
+ s->chan[ch].exec_bytes -= remainder;
+ }
/* reload exec_ registers if repeat is required */
if (s->chan[ch].next_config & CONFIG_REPEAT) {
@@ -136,6 +142,11 @@ static void sifive_pdma_run(SiFivePDMAState *s, int ch)
s->chan[ch].exec_src = src;
}
+done:
+ /* indicate a DMA transfer is done */
+ s->chan[ch].state = DMA_CHAN_STATE_DONE;
+ s->chan[ch].control &= ~CONTROL_RUN;
+ s->chan[ch].control |= CONTROL_DONE;
return;
error:
@@ -221,6 +232,7 @@ static void sifive_pdma_write(void *opaque, hwaddr offset,
{
SiFivePDMAState *s = opaque;
int ch = SIFIVE_PDMA_CHAN_NO(offset);
+ bool claimed;
if (ch >= SIFIVE_PDMA_CHANS) {
qemu_log_mask(LOG_GUEST_ERROR, "%s: Invalid channel no %d\n",
@@ -231,8 +243,28 @@ static void sifive_pdma_write(void *opaque, hwaddr offset,
offset &= 0xfff;
switch (offset) {
case DMA_CONTROL:
+ claimed = !!s->chan[ch].control & CONTROL_CLAIM;
+
+ if (!claimed && (value & CONTROL_CLAIM)) {
+ /* reset Next* registers */
+ s->chan[ch].next_config = (CONFIG_RDSZ_DEFAULT << CONFIG_RDSZ_SHIFT) |
+ (CONFIG_WRSZ_DEFAULT << CONFIG_WRSZ_SHIFT);
+ s->chan[ch].next_bytes = 0;
+ s->chan[ch].next_dst = 0;
+ s->chan[ch].next_src = 0;
+ }
+
s->chan[ch].control = value;
+ /*
+ * If channel was not claimed before run bit is set,
+ * DMA won't run.
+ */
+ if (!claimed) {
+ s->chan[ch].control &= ~CONTROL_RUN;
+ return;
+ }
+
if (value & CONTROL_RUN) {
sifive_pdma_run(s, ch);
}