summaryrefslogtreecommitdiff
path: root/libhw_cr/rp2040_dma.h
blob: c7f5a8fbb248610775e626a401d983c417b280af (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
/* libhw_cr/rp2040_dma.h - Utilities for using DMA on the RP2040 (replaces <hardware/dma.h>)
 *
 * Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
 * SPDX-License-Identifier: BSD-3-Clause
 *
 * Copyright (C) 2025  Luke T. Shumaker <lukeshu@lukeshu.com>
 * SPDX-License-Identifier: AGPL-3.0-or-later
 */

#ifndef _LIBHW_CR_RP2040_DMA_H_
#define _LIBHW_CR_RP2040_DMA_H_

#include <assert.h>
#include <stddef.h> /* for offsetof() */
#include <stdint.h> /* for uint32_t */

#include <hardware/regs/dreq.h>   /* for DREQ_* for use with DMA_CTRL_TREQ_SEL() */
#include <hardware/structs/dma.h> /* for dma_hw, dma_channel_hw_t, DMA_NUM_CHANNELS */

#include <libmisc/macro.h> /* for LM_FLOORLOG2() */

/* Borrowed from <hardware/dma.h> *********************************************/

static inline dma_channel_hw_t *dma_channel_hw_addr(uint channel) {
	assert(channel < NUM_DMA_CHANNELS);
	return &dma_hw->ch[channel];
}

enum dma_channel_transfer_size {
    DMA_SIZE_8 = 0,    ///< Byte transfer (8 bits)
    DMA_SIZE_16 = 1,   ///< Half word transfer (16 bits)
    DMA_SIZE_32 = 2    ///< Word transfer (32 bits)
};

/* Our own code ***************************************************************/

enum dmairq {
	DMAIRQ_0 = DMA_IRQ_0,
	DMAIRQ_1 = DMA_IRQ_1,
};

typedef void (*dmairq_handler_t)(void *arg, enum dmairq irq, uint channel);

/**
 * Register `fn(arg, ...)` to be called when `channel` completes or
 * has a NULL trigger (depending on the channel's configuration).
 *
 * Your handler does not need to acknowledge the IRQ; that will be
 * done for you after your handler is called.
 *
 * It is illegal to enable the same channel on more than one IRQ.
 */
void dmairq_set_and_enable_exclusive_handler(enum dmairq irq, uint channel, dmairq_handler_t fn, void *arg);

#define DMA_CTRL_ENABLE          (1<<0)
#define DMA_CTRL_HI_PRIO         (1<<1)
#define DMA_CTRL_DATA_SIZE(sz)   ((sz)<<2)
#define DMA_CTRL_INCR_READ       (1<<4)
#define DMA_CTRL_INCR_WRITE      (1<<5)
#define _DMA_CTRL_RING_BITS(b)   ((b)<<6)
#define _DMA_CTRL_RING_RD        (0)
#define _DMA_CTRL_RING_WR        (1<<10)
#define DMA_CTRL_RING(rdwr, bits) (_DMA_CTRL_RING_##rdwr | _DMA_CTRL_RING_BITS(bits))
#define DMA_CTRL_CHAIN_TO(ch)    ((ch)<<11)
#define DMA_CTRL_TREQ_SEL(dreq)  ((dreq)<<15)
#define DMA_CTRL_IRQ_QUIET       (1<<21)
#define DMA_CTRL_BSWAP           (1<<22)
#define DMA_CTRL_SNIFF_EN        (1<<23)

/*                    |          elem         |     val    |  name */
#define READ_ADDR   /*|*/volatile const void/*|*/    *   /*|*/read_addr
#define WRITE_ADDR  /*|*/volatile       void/*|*/    *   /*|*/write_addr
#define TRANS_COUNT /*|*/                   /*|*/uint32_t/*|*/trans_count
#define CTRL        /*|*/                   /*|*/uint32_t/*|*/ctrl

/*                { +0x0      ; +0x4        ; +0x8        ; +0xC (Trigger) */
struct dma_alias0 { READ_ADDR ; WRITE_ADDR  ; TRANS_COUNT ; CTRL        ; };
struct dma_alias1 { CTRL      ; READ_ADDR   ; WRITE_ADDR  ; TRANS_COUNT ; };
struct dma_alias2 { CTRL      ; TRANS_COUNT ; READ_ADDR   ; WRITE_ADDR  ; };
struct dma_alias3 { CTRL      ; WRITE_ADDR  ; TRANS_COUNT ; READ_ADDR   ; };
struct dma_alias0_short2                    { TRANS_COUNT ; CTRL        ; };
struct dma_alias1_short2                    { WRITE_ADDR  ; TRANS_COUNT ; };
struct dma_alias2_short2                    { READ_ADDR   ; WRITE_ADDR  ; };
struct dma_alias3_short2                    { TRANS_COUNT ; READ_ADDR   ; };
struct dma_alias0_short3                                  { CTRL        ; };
struct dma_alias1_short3                                  { TRANS_COUNT ; };
struct dma_alias2_short3                                  { WRITE_ADDR  ; };
struct dma_alias3_short3                                  { READ_ADDR   ; };

#undef CTRL
#undef TRANS_COUNT
#undef WRITE_ADDR
#undef READ_ADDR

#define DMA_CHAN_ADDR(CH, TYP) ((TYP *volatile)_Generic((TYP){},                     \
	struct dma_alias0:        &dma_channel_hw_addr(CH)->read_addr,               \
	struct dma_alias1:        &dma_channel_hw_addr(CH)->al1_ctrl,                \
	struct dma_alias2:        &dma_channel_hw_addr(CH)->al2_ctrl,                \
	struct dma_alias3:        &dma_channel_hw_addr(CH)->al3_ctrl,                \
	struct dma_alias0_short2: &dma_channel_hw_addr(CH)->transfer_count,          \
	struct dma_alias1_short2: &dma_channel_hw_addr(CH)->al1_write_addr,          \
	struct dma_alias2_short2: &dma_channel_hw_addr(CH)->al2_read_addr,           \
	struct dma_alias3_short2: &dma_channel_hw_addr(CH)->al3_transfer_count,      \
	struct dma_alias0_short3: &dma_channel_hw_addr(CH)->ctrl_trig,               \
	struct dma_alias1_short3: &dma_channel_hw_addr(CH)->al1_transfer_count_trig, \
	struct dma_alias2_short3: &dma_channel_hw_addr(CH)->al2_write_addr_trig,     \
	struct dma_alias3_short3: &dma_channel_hw_addr(CH)->al3_read_addr_trig))

#define DMA_IS_TRIGGER(TYP, FIELD) (offsetof(TYP, FIELD) == 0xC)

#define DMA_CHAN_WR_TRANS_COUNT(TYP) \
	(sizeof(TYP)/4)

#define DMA_CHAN_WR_CTRL(TYP) ( DMA_CTRL_DATA_SIZE(DMA_SIZE_32) \
                              | DMA_CTRL_INCR_WRITE \
                              | DMA_CTRL_RING(WR, LM_FLOORLOG2(sizeof(TYP))) \
                              )

#define DMA_NONTRIGGER(CH, FIELD) (DMA_CHAN_ADDR(CH, _DMA_NONTRIGGER_##FIELD)->FIELD)
#define _DMA_NONTRIGGER_read_addr   struct dma_alias0
#define _DMA_NONTRIGGER_write_addr  struct dma_alias0
#define _DMA_NONTRIGGER_trans_count struct dma_alias0
#define _DMA_NONTRIGGER_ctrl        struct dma_alias1

#define DMA_TRIGGER(CH, FIELD)    (DMA_CHAN_ADDR(CH, _DMA_TRIGGER_##FIELD)->FIELD)
#define _DMA_TRIGGER_read_addr      struct dma_alias3
#define _DMA_TRIGGER_write_addr     struct dma_alias2
#define _DMA_TRIGGER_trans_count    struct dma_alias1
#define _DMA_TRIGGER_ctrl           struct dma_alias0

#endif /* _LIBHW_CR_RP2040_DMA_H_ */