⚠️ [dma] remove firq-triggered auto mode

This commit is contained in:
stnolting 2025-03-02 09:51:12 +01:00
parent ecb185b9b6
commit 35d0fefbf0
4 changed files with 123 additions and 223 deletions

View file

@ -16,17 +16,20 @@
**Overview**
The NEORV32 DMA provides a small-scale scatter/gather direct memory access controller that allows to transfer and
modify data independently of the CPU. A single read/write transfer channel is implemented that is configured via
memory-mapped registers. a configured transfer can either be triggered manually or by a programmable CPU FIRQ interrupt
(see <<_neorv32_specific_fast_interrupt_requests>>).
The NEORV32 DMA provides a lightweight direct memory access controller that allows to transfer and
modify data independently of the CPU. A single read/write channel is implemented that is configured via
memory-mapped registers.
The DMA is connected to the central processor-internal bus system (see section <<_address_space>>) and can access the same
address space as the CPU core. It uses _interleaving mode_ accessing the central processor bus only if the CPU does not
currently request and bus access.
currently request and bus access. The controller can handle different data quantities (e.g. read bytes and write them
back as sign-extend words) and can also change the Endianness of data while transferring.
The controller can handle different data quantities (e.g. read bytes and write them back as sign-extend words) and can
also change the Endianness of data while transferring.
.DMA Access Privilege Level
[WARNING]
Transactions performed by the DMA are executed as bus transactions with elevated **machine-mode** privilege level.
Note that any physical memory protection rules (<<_smpmp_isa_extension>>) are not applied to DMA transfers.
.DMA Demo Program
[TIP]
@ -40,39 +43,32 @@ configuring the actual DMA transfer. The base address of the source data is prog
Vice versa, the base address of the destination data is programmed via the `DST_BASE`. The third configuration register
`TTYPE` is use to configure the actual transfer type and the number of elements to transfer.
The DMA is enabled by setting the `DMA_CTRL_EN` bit of the control register. Manual trigger mode (i.e. the DMA transfer is
triggered by writing to the `TTYPE` register) is selected if `DMA_CTRL_AUTO` is cleared. Alternatively, the DMA transfer can
be triggered by a processor internal FIRQ signal if `DMA_CTRL_AUTO` is set (see section below).
The DMA is enabled by setting the `DMA_CTRL_EN` bit of the control register. A programmed DMA transfer is initiated
by setting the control register's `DMA_CTRL_START` bit.
The DMA uses a load-modify-write data transfer process. Data is read from the bus system, internally modified and then written
back to the bus system. This combination is implemented as an atomic progress, so canceling the current transfer by clearing the
`DMA_CTRL_EN` bit will stop the DMA right after the current load-modify-write operation.
`DMA_CTRL_EN` bit will stop the DMA after the current load-modify-write operation.
If the DMA controller detects a bus error during operation, it will set either the `DMA_CTRL_ERROR_RD` (error during
last read access) or `DMA_CTRL_ERROR_WR` (error during last write access) and will terminate the current transfer.
Software can read the `SRC_BASE` or `DST_BASE` register to retrieve the address that caused the according error.
Alternatively, software can read back the `NUM` bits of the control register to determine the index of the element
that caused the error. The error bits are automatically cleared when starting a new transfer.
The error bits are automatically cleared when starting a new transfer. The error flags auto-clear when starting a new
DMA transfer.
When the `DMA_CTRL_DONE` flag is set the DMA has actually executed a transfer. However, the `DMA_CTRL_ERROR_*` flags
should also be checked to verify that the executed transfer completed without errors. The `DMA_CTRL_DONE` flag is
automatically cleared when writing the `CTRL` register.
.DMA Access Privilege Level
[WARNING]
Transactions performed by the DMA are executed as bus transactions with elevated **machine-mode** privilege level.
Note that any physical memory protection rules (<<_smpmp_isa_extension>>) are not applied to DMA transfers.
**Transfer Configuration**
If the DMA is set to **manual trigger mode** (`DMA_CTRL_AUTO` = 0) writing the `TTRIG` register will start the
programmed DMA transfer. Once started, the DMA will read one data quantity from the source address, processes it internally
Once started, the DMA will read one data quantity from the source address, processes it internally
and then will write it back to the destination address. The `DMA_TTYPE_NUM` bits of the `TTYPE` register define how many
times this process is repeated by specifying the number of elements to transfer.
Optionally, the source and/or destination addresses can be increments according to the data quantities
automatically by setting the according `DMA_TTYPE_SRC_INC` and/or `DMA_TTYPE_DST_INC` bit.
Optionally, the source and/or destination addresses can be automatically increments according to the data quantities
by setting the according `DMA_TTYPE_SRC_INC` and/or `DMA_TTYPE_DST_INC` bit.
Four different transfer quantities are available, which are configured via the `DMA_TTYPE_QSEL` bits:
@ -89,34 +85,11 @@ bit is set.
Make sure to align the source and destination base addresses to the according transfer data quantities. For instance,
word-to-word transfers require that the two LSB of `SRC_BASE` and `DST_BASE` are cleared.
.Writing to IO Device
.Accessing IO Device
[IMPORTANT]
When writing data to IO / peripheral devices (for example to the <<_cyclic_redundancy_check_crc>>) the destination
data quantity has to be set to **word** (32-bit) since all IO registers can only be written in full 32-bit word mode.
**Automatic Trigger**
As an alternative to the manual trigger mode, the DMA can be set to **automatic trigger mode** starting a pre-configured
transfer if a specific processor-internal peripheral issues a FIRQ interrupt request. The automatic trigger mode is enabled by
setting the `CTRL` register's `DMA_CTRL_AUTO` bit. In this configuration _no_ transfer is started when writing to the DMA's
`TTYPE` register.
The actually triggering FIRQ channel is configured via the control register's `DMA_CTRL_FIRQ_SEL` bits. Writing a 0 will
select FIRQ channel 0, writing a 1 will select FIRQ channel 1, and so on. See section <<_processor_interrupts>>
for a list of all FIRQ channels and their according sources.
The FIRQ trigger can operate in two trigger mode configured via the `DMA_CTRL_FIRQ_TYPE` flag:
* `DMA_CTRL_FIRQ_TYPE = 0`: trigger the automatic DMA transfer on a rising-edge of the selected FIRQ channel (e.g. trigger
DMA transfer only once)
* `DMA_CTRL_FIRQ_TYPE = 1`: trigger the automatic DMA transfer when the selected FIRQ channel is active (e.g. trigger
DMA transfer again and again)
.FIRQ Trigger
[NOTE]
The DMA transfer will start if a **rising edge** is detected on the configured FIRQ channel. Hence, the DMA is triggered only
once even if the selected FIRQ channel keeps pending.
In contrast, read accesses to IO / peripheral devices can also be executed on a byte granule.
**DMA Interrupt**
@ -134,23 +107,19 @@ register).
[options="header",grid="all"]
|=======================
| Address | Name [C] | Bit(s), Name [C] | R/W | Function
.11+<| `0xffed0000` .11+<| `CTRL` <|`0` `DMA_CTRL_EN` ^| r/w <| DMA module enable
<|`1` `DMA_CTRL_AUTO` ^| r/w <| Enable automatic mode (FIRQ-triggered)
<|`7:2` _reserved_ ^| r/- <| reserved, read as zero
<|`8` `DMA_CTRL_ERROR_RD` ^| r/- <| Error during read access, clears when starting a new transfer
<|`9` `DMA_CTRL_ERROR_WR` ^| r/- <| Error during write access, clears when starting a new transfer
<|`10` `DMA_CTRL_BUSY` ^| r/- <| DMA transfer in progress
<|`11` `DMA_CTRL_DONE` ^| r/c <| Set if a transfer was executed; auto-clears on write-access
<|`14:12` _reserved_ ^| r/- <| reserved, read as zero
<|`15` `DMA_CTRL_FIRQ_TYPE` ^| r/w <| Trigger on rising-edge (`0`) or high-level (`1`) or selected FIRQ channel
<|`19:16` `DMA_CTRL_FIRQ_SEL_MSB : DMA_CTRL_FIRQ_SEL_LSB` ^| r/w <| FIRQ trigger select (FIRQ0=0 ... FIRQ15=15)
<|`31:20` _reserved_ ^| r/- <| reserved, read as zero
.7+<| `0xffed0000` .7+<| `CTRL` <|`0` `DMA_CTRL_EN` ^| r/w <| DMA module enable
<|`1` `DMA_CTRL_START` ^| r/s <| Start programmed DMA transfer (reads as zero)
<|`7:27` _reserved_ ^| r/- <| reserved, read as zero
<|`28` `DMA_CTRL_ERROR_RD` ^| r/- <| Error during read access, clears when starting a new transfer
<|`29` `DMA_CTRL_ERROR_WR` ^| r/- <| Error during write access, clears when starting a new transfer
<|`30` `DMA_CTRL_DONE` ^| r/c <| Set if a transfer was executed; auto-clears on write-access
<|`31` `DMA_CTRL_BUSY` ^| r/- <| DMA transfer in progress
| `0xffed0004` | `SRC_BASE` |`31:0` | r/w | Source base address (shows the last-accessed source address when read)
| `0xffed0008` | `DST_BASE` |`31:0` | r/w | Destination base address (shows the last-accessed destination address when read)
.6+<| `0xffed000c` .6+<| `TTYPE` <|`23:0` `DMA_TTYPE_NUM_MSB : DMA_TTYPE_NUM_LSB` ^| r/w <| Number of elements to transfer (shows the last-transferred element index when read)
<|`26:24` _reserved_ ^| r/- <| reserved, read as zero
<|`28:27` `DMA_TTYPE_QSEL_MSB : DMA_TTYPE_QSEL_LSB` ^| r/w <| Quantity select (`00` = byte -> byte, `01` = byte -> zero-extended-word, `10` = byte -> sign-extended-word, `11` = word -> word)
<|`28:27` `DMA_TTYPE_QSEL_MSB : DMA_TTYPE_QSEL_LSB` ^| r/w <| Transfer type (`00` = byte -> byte, `01` = byte -> zero-extended-word, `10` = byte -> sign-extended-word, `11` = word -> word)
<|`29` `DMA_TTYPE_SRC_INC` ^| r/w <| Constant (`0`) or incrementing (`1`) source address
<|`30` `DMA_TTYPE_DST_INC` ^| r/w <| Constant (`0`) or incrementing (`1`) destination address
<|`31` `DMA_TTYPE_ENDIAN` ^| r/w <| Swap Endianness when set
<|`31` `DMA_TTYPE_ENDIAN` ^| r/w <| Convert Endianness when set
|=======================

View file

@ -23,7 +23,6 @@ entity neorv32_dma is
bus_rsp_o : out bus_rsp_t; -- bus response
dma_req_o : out bus_req_t; -- DMA request
dma_rsp_i : in bus_rsp_t; -- DMA response
firq_i : in std_ulogic_vector(15 downto 0); -- CPU FIRQ channels
irq_o : out std_ulogic -- transfer done interrupt
);
end neorv32_dma;
@ -41,17 +40,13 @@ architecture neorv32_dma_rtl of neorv32_dma is
constant type_endian_c : natural := 31; -- r/w: Convert Endianness when set
-- control and status register bits --
constant ctrl_en_c : natural := 0; -- r/w: DMA enable
constant ctrl_auto_c : natural := 1; -- r/w: enable FIRQ-triggered transfer
constant ctrl_en_c : natural := 0; -- r/w: DMA enable
constant ctrl_start_c : natural := 1; -- -/s: start DMA operation
--
constant ctrl_error_rd_c : natural := 8; -- r/-: error during read transfer
constant ctrl_error_wr_c : natural := 9; -- r/-: error during write transfer
constant ctrl_busy_c : natural := 10; -- r/-: DMA transfer in progress
constant ctrl_done_c : natural := 11; -- r/c: a DMA transfer was executed/attempted
--
constant ctrl_firq_type_c : natural := 15; -- r/w: trigger on FIRQ rising-edge or on high-level
constant ctrl_firq_sel_lsb_c : natural := 16; -- r/w: FIRQ trigger select LSB
constant ctrl_firq_sel_msb_c : natural := 19; -- r/w: FIRQ trigger select MSB
constant ctrl_error_rd_c : natural := 28; -- r/-: error during read transfer
constant ctrl_error_wr_c : natural := 29; -- r/-: error during write transfer
constant ctrl_done_c : natural := 30; -- r/c: transfer has completed
constant ctrl_busy_c : natural := 31; -- r/-: DMA transfer in progress
-- transfer quantities --
constant qsel_b2b_c : std_ulogic_vector(1 downto 0) := "00"; -- byte to byte
@ -61,19 +56,16 @@ architecture neorv32_dma_rtl of neorv32_dma is
-- configuration registers --
type cfg_t is record
enable : std_ulogic; -- DMA enabled when set
auto : std_ulogic; -- FIRQ-driven auto transfer
firq_sel : std_ulogic_vector(3 downto 0); -- FIRQ trigger select
firq_type : std_ulogic; -- trigger on FIRQ rising-edge (0) or high-level (1)
src_base : std_ulogic_vector(31 downto 0); -- source base address
dst_base : std_ulogic_vector(31 downto 0); -- destination base address
num : std_ulogic_vector(23 downto 0); -- number of elements
qsel : std_ulogic_vector(1 downto 0); -- data quantity select
src_inc : std_ulogic; -- constant (0) or incrementing (1) source address
dst_inc : std_ulogic; -- constant (0) or incrementing (1) destination address
endian : std_ulogic; -- convert endianness when set
start : std_ulogic; -- transfer start trigger
done : std_ulogic; -- transfer was executed (but might have failed)
enable : std_ulogic; -- DMA enabled when set
start : std_ulogic; -- transfer start trigger
done : std_ulogic; -- transfer was executed (but might have failed)
src_base : std_ulogic_vector(31 downto 0); -- source base address
dst_base : std_ulogic_vector(31 downto 0); -- destination base address
num : std_ulogic_vector(23 downto 0); -- number of elements
qsel : std_ulogic_vector(1 downto 0); -- data quantity select
src_inc : std_ulogic; -- constant (0) or incrementing (1) source address
dst_inc : std_ulogic; -- constant (0) or incrementing (1) destination address
endian : std_ulogic; -- convert endianness when set
end record;
signal cfg : cfg_t;
@ -81,6 +73,8 @@ architecture neorv32_dma_rtl of neorv32_dma is
type state_t is (S_IDLE, S_READ, S_WRITE, S_NEXT);
type engine_t is record
state : state_t;
stb : std_ulogic;
rw : std_ulogic;
src_addr : std_ulogic_vector(31 downto 0);
dst_addr : std_ulogic_vector(31 downto 0);
num : std_ulogic_vector(23 downto 0);
@ -94,14 +88,7 @@ architecture neorv32_dma_rtl of neorv32_dma is
signal engine : engine_t;
-- data alignment --
signal align_buf : std_ulogic_vector(31 downto 0);
signal align_end : std_ulogic_vector(31 downto 0);
-- FIRQ trigger --
signal firq_buf : std_ulogic_vector(15 downto 0);
signal match : std_ulogic;
signal match_ff : std_ulogic;
signal atrigger : std_ulogic;
signal align_buf, align_end : std_ulogic_vector(31 downto 0);
begin
@ -110,20 +97,17 @@ begin
bus_access: process(rstn_i, clk_i)
begin
if (rstn_i = '0') then
bus_rsp_o <= rsp_terminate_c;
cfg.enable <= '0';
cfg.auto <= '0';
cfg.firq_sel <= (others => '0');
cfg.firq_type <= '0';
cfg.src_base <= (others => '0');
cfg.dst_base <= (others => '0');
cfg.num <= (others => '0');
cfg.qsel <= (others => '0');
cfg.src_inc <= '0';
cfg.dst_inc <= '0';
cfg.endian <= '0';
cfg.start <= '0';
cfg.done <= '0';
bus_rsp_o <= rsp_terminate_c;
cfg.enable <= '0';
cfg.start <= '0';
cfg.done <= '0';
cfg.src_base <= (others => '0');
cfg.dst_base <= (others => '0');
cfg.num <= (others => '0');
cfg.qsel <= (others => '0');
cfg.src_inc <= '0';
cfg.dst_inc <= '0';
cfg.endian <= '0';
elsif rising_edge(clk_i) then
-- bus handshake --
bus_rsp_o.ack <= bus_req_i.stb;
@ -134,14 +118,13 @@ begin
cfg.start <= '0'; -- default
cfg.done <= cfg.enable and (cfg.done or engine.done); -- set if enabled and transfer done
-- bus access --
if (bus_req_i.stb = '1') then
if (bus_req_i.rw = '1') then -- write access
if (bus_req_i.addr(3 downto 2) = "00") then -- control and status register
cfg.enable <= bus_req_i.data(ctrl_en_c);
cfg.auto <= bus_req_i.data(ctrl_auto_c);
cfg.done <= '0'; -- clear on write access
cfg.firq_type <= bus_req_i.data(ctrl_firq_type_c);
cfg.firq_sel <= bus_req_i.data(ctrl_firq_sel_msb_c downto ctrl_firq_sel_lsb_c);
cfg.enable <= bus_req_i.data(ctrl_en_c);
cfg.start <= bus_req_i.data(ctrl_start_c); -- start transfer
cfg.done <= '0'; -- clear on write access
end if;
if (bus_req_i.addr(3 downto 2) = "01") then -- source base address
cfg.src_base <= bus_req_i.data;
@ -155,19 +138,15 @@ begin
cfg.src_inc <= bus_req_i.data(type_src_inc_c);
cfg.dst_inc <= bus_req_i.data(type_dst_inc_c);
cfg.endian <= bus_req_i.data(type_endian_c);
cfg.start <= '1'; -- trigger DMA operation
end if;
else -- read access
case bus_req_i.addr(3 downto 2) is
when "00" => -- control and status register
bus_rsp_o.data(ctrl_en_c) <= cfg.enable;
bus_rsp_o.data(ctrl_auto_c) <= cfg.auto;
bus_rsp_o.data(ctrl_error_rd_c) <= engine.err_rd;
bus_rsp_o.data(ctrl_error_wr_c) <= engine.err_wr;
bus_rsp_o.data(ctrl_busy_c) <= engine.busy;
bus_rsp_o.data(ctrl_done_c) <= cfg.done;
bus_rsp_o.data(ctrl_firq_type_c) <= cfg.firq_type;
bus_rsp_o.data(ctrl_firq_sel_msb_c downto ctrl_firq_sel_lsb_c) <= cfg.firq_sel;
bus_rsp_o.data(ctrl_en_c) <= cfg.enable;
bus_rsp_o.data(ctrl_error_rd_c) <= engine.err_rd;
bus_rsp_o.data(ctrl_error_wr_c) <= engine.err_wr;
bus_rsp_o.data(ctrl_done_c) <= cfg.done;
bus_rsp_o.data(ctrl_busy_c) <= engine.busy;
when "01" => -- address of last read access
bus_rsp_o.data <= engine.src_addr;
when "10" => -- address of last write access
@ -188,63 +167,40 @@ begin
irq_o <= cfg.done;
-- Automatic Trigger ----------------------------------------------------------------------
-- -------------------------------------------------------------------------------------------
automatic_trigger: process(rstn_i, clk_i)
begin
if (rstn_i = '0') then
firq_buf <= (others => '0');
match_ff <= '0';
atrigger <= '0';
elsif rising_edge(clk_i) then
firq_buf <= firq_i;
match_ff <= match;
if (cfg.firq_type = '0') then -- auto-trigger on rising-edge of FIRQ
atrigger <= match and (not match_ff);
else -- auto-trigger on high-level of FIRQ
atrigger <= match;
end if;
end if;
end process automatic_trigger;
-- select a single FIRQ --
match <= firq_buf(to_integer(unsigned(cfg.firq_sel)));
-- Bus Access Engine ----------------------------------------------------------------------
-- -------------------------------------------------------------------------------------------
bus_engine: process(rstn_i, clk_i)
begin
if (rstn_i = '0') then
engine.state <= S_IDLE;
engine.stb <= '0';
engine.rw <= '0';
engine.src_addr <= (others => '0');
engine.dst_addr <= (others => '0');
engine.num <= (others => '0');
engine.err_rd <= '0';
engine.err_wr <= '0';
engine.done <= '0';
dma_req_o.rw <= '0';
dma_req_o.stb <= '0';
elsif rising_edge(clk_i) then
-- defaults --
engine.done <= '0';
dma_req_o.stb <= '0';
engine.done <= '0';
engine.stb <= '0';
-- state machine --
case engine.state is
when S_IDLE => -- idle, waiting for start trigger
when S_IDLE => -- idle, waiting for trigger
-- ------------------------------------------------------------
engine.src_addr <= cfg.src_base;
engine.dst_addr <= cfg.dst_base;
engine.num <= cfg.num;
if (cfg.enable = '1') and
(((cfg.auto = '0') and (cfg.start = '1')) or -- manual trigger
((cfg.auto = '1') and (atrigger = '1'))) then -- automatic trigger
engine.rw <= '0';
if (cfg.enable = '0') and (cfg.start = '1') then -- disabled or start
engine.err_rd <= '0';
engine.err_wr <= '0';
dma_req_o.rw <= '0'; -- read
dma_req_o.stb <= '1'; -- issue read request
end if;
if (cfg.enable = '1') and (cfg.start = '1') then -- start
engine.stb <= '1';
engine.state <= S_READ;
end if;
@ -255,9 +211,9 @@ begin
engine.err_rd <= '1';
engine.state <= S_IDLE;
elsif (dma_rsp_i.ack = '1') then
dma_req_o.rw <= '1'; -- write
dma_req_o.stb <= '1'; -- issue write request
engine.state <= S_WRITE;
engine.rw <= '1'; -- write
engine.stb <= '1'; -- issue write request
engine.state <= S_WRITE;
end if;
when S_WRITE => -- pending write access
@ -283,9 +239,9 @@ begin
if (cfg.dst_inc = '1') then -- incrementing destination address
engine.dst_addr <= std_ulogic_vector(unsigned(engine.dst_addr) + engine.dst_add);
end if;
dma_req_o.rw <= '0'; -- read
dma_req_o.stb <= '1'; -- issue read request
engine.state <= S_READ;
engine.rw <= '0';
engine.stb <= '1'; -- issue read request
engine.state <= S_READ;
end if;
when others => -- undefined
@ -300,7 +256,9 @@ begin
engine.busy <= '0' when (engine.state = S_IDLE) else '1';
-- bus output --
dma_req_o.addr <= engine.src_addr when (engine.state = S_READ) else engine.dst_addr;
dma_req_o.stb <= engine.stb;
dma_req_o.rw <= engine.rw;
dma_req_o.addr <= engine.dst_addr when (engine.state = S_WRITE) else engine.src_addr;
dma_req_o.src <= '0'; -- source = data access
dma_req_o.priv <= priv_mode_m_c; -- DMA accesses are always privileged
dma_req_o.debug <= '0'; -- can never ever be in debug mode
@ -312,9 +270,15 @@ begin
address_inc: process(cfg.qsel)
begin
case cfg.qsel is
when qsel_b2b_c => engine.src_add <= to_unsigned(1, 32); engine.dst_add <= to_unsigned(1, 32); -- byte -> byte
when qsel_w2w_c => engine.src_add <= to_unsigned(4, 32); engine.dst_add <= to_unsigned(4, 32); -- word -> word
when others => engine.src_add <= to_unsigned(1, 32); engine.dst_add <= to_unsigned(4, 32); -- byte -> word
when qsel_b2b_c => -- byte -> byte
engine.src_add <= to_unsigned(1, 32);
engine.dst_add <= to_unsigned(1, 32);
when qsel_w2w_c => -- word -> word
engine.src_add <= to_unsigned(4, 32);
engine.dst_add <= to_unsigned(4, 32);
when others => -- byte -> word
engine.src_add <= to_unsigned(1, 32);
engine.dst_add <= to_unsigned(4, 32);
end case;
end process address_inc;

View file

@ -35,16 +35,12 @@ typedef volatile struct __attribute__((packed,aligned(4))) {
/** DMA control and status register bits */
enum NEORV32_DMA_CTRL_enum {
DMA_CTRL_EN = 0, /**< DMA control register(0) (r/w): DMA enable */
DMA_CTRL_AUTO = 1, /**< DMA control register(1) (r/w): Automatic trigger mode enable */
DMA_CTRL_START = 1, /**< DMA control register(1) (-/s): Start configured DMA transfer */
DMA_CTRL_ERROR_RD = 8, /**< DMA control register(8) (r/-): Error during read access; SRC_BASE shows the faulting address */
DMA_CTRL_ERROR_WR = 9, /**< DMA control register(9) (r/-): Error during write access; DST_BASE shows the faulting address */
DMA_CTRL_BUSY = 10, /**< DMA control register(10) (r/-): DMA busy / transfer in progress */
DMA_CTRL_DONE = 11, /**< DMA control register(11) (r/c): A transfer was executed when set */
DMA_CTRL_FIRQ_TYPE = 15, /**< DMA control register(15) (r/w): Trigger on FIRQ rising-edge (0) or high-level (1) */
DMA_CTRL_FIRQ_SEL_LSB = 16, /**< DMA control register(16) (r/w): FIRQ trigger select LSB */
DMA_CTRL_FIRQ_SEL_MSB = 19 /**< DMA control register(19) (r/w): FIRQ trigger select MSB */
DMA_CTRL_ERROR_RD = 28, /**< DMA control register(28) (r/-): Error during read access; SRC_BASE shows the faulting address */
DMA_CTRL_ERROR_WR = 29, /**< DMA control register(29) (r/-): Error during write access; DST_BASE shows the faulting address */
DMA_CTRL_DONE = 30, /**< DMA control register(30) (r/c): A transfer has been executed when set */
DMA_CTRL_BUSY = 31 /**< DMA control register(32) (r/-): DMA busy / transfer in progress */
};
/** DMA transfer type bits */
@ -87,10 +83,22 @@ enum NEORV32_DMA_STATUS_enum {
DMA_STATUS_ERR_WR = -2, /**< write access error during last transfer (-2) */
DMA_STATUS_ERR_RD = -1, /**< read access error during last transfer (-1) */
DMA_STATUS_IDLE = 0, /**< DMA idle (0) */
DMA_STATUS_BUSY = 1 /**< DMA busy (1) */
DMA_STATUS_BUSY = 1, /**< DMA busy (1) */
DMA_STATUS_DONE = 2 /**< transfer done (2) */
};
/**********************************************************************//**
* DMA transfer descriptor
**************************************************************************/
typedef struct __attribute__((packed,aligned(4))) {
uint32_t src; /**< 32-bit source base address */
uint32_t dst; /**< 32-bit destination base address */
uint32_t num; /**< 24-bit (LSB-aligned) number of elements to transfer */
uint32_t cmd; /**< transfer type */
} neorv32_dma_desc_t;
/**********************************************************************//**
* @name Prototypes
**************************************************************************/
@ -98,10 +106,8 @@ enum NEORV32_DMA_STATUS_enum {
int neorv32_dma_available(void);
void neorv32_dma_enable(void);
void neorv32_dma_disable(void);
void neorv32_dma_transfer(uint32_t base_src, uint32_t base_dst, uint32_t num, uint32_t config);
void neorv32_dma_transfer_auto(uint32_t base_src, uint32_t base_dst, uint32_t num, uint32_t config, int firq_sel, int firq_type);
void neorv32_dma_transfer(neorv32_dma_desc_t *desc);
int neorv32_dma_status(void);
int neorv32_dma_done(void);
/**@}*/

View file

@ -56,37 +56,12 @@ void neorv32_dma_disable(void) {
* @param[in] num Number of elements to transfer (24-bit).
* @param[in] config Transfer type configuration/commands.
**************************************************************************/
void neorv32_dma_transfer(uint32_t base_src, uint32_t base_dst, uint32_t num, uint32_t config) {
void neorv32_dma_transfer(neorv32_dma_desc_t *desc) {
NEORV32_DMA->CTRL &= ~((uint32_t)(1 << DMA_CTRL_AUTO)); // manual transfer trigger
NEORV32_DMA->SRC_BASE = base_src;
NEORV32_DMA->DST_BASE = base_dst;
NEORV32_DMA->TTYPE = (num & 0x00ffffffUL) | (config & 0xff000000UL); // trigger transfer
}
/**********************************************************************//**
* Configure automatic DMA transfer (triggered by CPU FIRQ).
*
* @param[in] base_src Source base address (has to be aligned to source data type!).
* @param[in] base_dst Destination base address (has to be aligned to destination data type!).
* @param[in] num Number of elements to transfer (24-bit).
* @param[in] config Transfer type configuration/commands.
* @param[in] firq_sel FIRQ trigger select (#NEORV32_CSR_MIP_enum); only FIRQ0..FIRQ15 = 16..31.
* @param[in] firq_type Trigger on rising-edge (0) or high-level (1) of FIRQ channel.
**************************************************************************/
void neorv32_dma_transfer_auto(uint32_t base_src, uint32_t base_dst, uint32_t num, uint32_t config, int firq_sel, int firq_type) {
uint32_t tmp = NEORV32_DMA->CTRL;
tmp |= (uint32_t)(1 << DMA_CTRL_AUTO); // automatic transfer trigger
tmp &= ~(0xf << DMA_CTRL_FIRQ_SEL_LSB); // clear current FIRQ select
tmp |= (uint32_t)((firq_sel & 0xf) << DMA_CTRL_FIRQ_SEL_LSB); // set new FIRQ select
tmp |= (uint32_t)((firq_type & 1) << DMA_CTRL_FIRQ_TYPE); // FIRQ trigger type
NEORV32_DMA->CTRL = tmp;
NEORV32_DMA->SRC_BASE = base_src;
NEORV32_DMA->DST_BASE = base_dst;
NEORV32_DMA->TTYPE = (num & 0x00ffffffUL) | (config & 0xff000000UL);
NEORV32_DMA->SRC_BASE = desc->src;
NEORV32_DMA->DST_BASE = desc->dst;
NEORV32_DMA->TTYPE = (desc->num & 0x00ffffffUL) | (desc->cmd & 0xff000000UL);
NEORV32_DMA->CTRL |= 1<<DMA_CTRL_START;
}
@ -108,24 +83,10 @@ int neorv32_dma_status(void) {
else if (tmp & (1 << DMA_CTRL_BUSY)) {
return DMA_STATUS_BUSY; // transfer in progress
}
else if (tmp & (1 << DMA_CTRL_DONE)) {
return DMA_STATUS_DONE; // transfer done
}
else {
return DMA_STATUS_IDLE; // idle
}
}
/**********************************************************************//**
* Check if a transfer has actually been executed.
*
* @return 0 if no transfer was executed, 1 if a transfer has actually been executed.
* Use neorv32_dma_status(void) to check if there was an error during that transfer.
**************************************************************************/
int neorv32_dma_done(void) {
if (NEORV32_DMA->CTRL & (1 << DMA_CTRL_DONE)) {
return 1; // there was a transfer
}
else {
return 0; // no transfer executed
}
}