-
Notifications
You must be signed in to change notification settings - Fork 6.5k
/
dma_nxp_sof_host_dma.c
309 lines (256 loc) · 7.8 KB
/
dma_nxp_sof_host_dma.c
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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
/*
* Copyright 2023 NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/drivers/dma.h>
#include <zephyr/logging/log.h>
#include <zephyr/cache.h>
/* used for driver binding */
#define DT_DRV_COMPAT nxp_sof_host_dma
/* macros used to parse DTS properties */
#define IDENTITY_VARGS(V, ...) IDENTITY(V)
#define _SOF_HOST_DMA_CHANNEL_INDEX_ARRAY(inst)\
LISTIFY(DT_INST_PROP_OR(inst, dma_channels, 0), IDENTITY_VARGS, (,))
#define _SOF_HOST_DMA_CHANNEL_DECLARE(idx) {}
#define SOF_HOST_DMA_CHANNELS_DECLARE(inst)\
FOR_EACH(_SOF_HOST_DMA_CHANNEL_DECLARE,\
(,), _SOF_HOST_DMA_CHANNEL_INDEX_ARRAY(inst))
LOG_MODULE_REGISTER(nxp_sof_host_dma);
/* note: This driver doesn't attempt to provide
* a generic software-based DMA engine implementation.
* As its name suggests, its only usage is in SOF
* (Sound Open Firmware) for NXP plaforms which are
* able to access the host memory directly from the
* core on which the firmware is running.
*/
enum channel_state {
CHAN_STATE_INIT = 0,
CHAN_STATE_CONFIGURED,
};
struct sof_host_dma_channel {
uint32_t src;
uint32_t dest;
uint32_t size;
uint32_t direction;
enum channel_state state;
};
struct sof_host_dma_data {
/* this needs to be first */
struct dma_context ctx;
atomic_t channel_flags;
struct sof_host_dma_channel *channels;
};
static int channel_change_state(struct sof_host_dma_channel *chan,
enum channel_state next)
{
enum channel_state prev = chan->state;
/* validate transition */
switch (prev) {
case CHAN_STATE_INIT:
case CHAN_STATE_CONFIGURED:
if (next != CHAN_STATE_CONFIGURED) {
return -EPERM;
}
break;
default:
LOG_ERR("invalid channel previous state: %d", prev);
return -EINVAL;
}
chan->state = next;
return 0;
}
static int sof_host_dma_reload(const struct device *dev, uint32_t chan_id,
uint32_t src, uint32_t dst, size_t size)
{
ARG_UNUSED(src);
ARG_UNUSED(dst);
ARG_UNUSED(size);
struct sof_host_dma_data *data;
struct sof_host_dma_channel *chan;
int ret;
data = dev->data;
if (chan_id >= data->ctx.dma_channels) {
LOG_ERR("channel %d is not a valid channel ID", chan_id);
return -EINVAL;
}
/* fetch channel data */
chan = &data->channels[chan_id];
/* validate state */
if (chan->state != CHAN_STATE_CONFIGURED) {
LOG_ERR("attempting to reload unconfigured DMA channel %d", chan_id);
return -EINVAL;
}
if (chan->direction == HOST_TO_MEMORY) {
/* the host may have modified the region we're about to copy
* to local memory. In this case, the data cache holds stale
* data so invalidate it to force a read from the main memory.
*/
ret = sys_cache_data_invd_range(UINT_TO_POINTER(chan->src),
chan->size);
if (ret < 0) {
LOG_ERR("failed to invalidate data cache range");
return ret;
}
}
memcpy(UINT_TO_POINTER(chan->dest), UINT_TO_POINTER(chan->src), chan->size);
/*
* MEMORY_TO_HOST transfer: force range to main memory so that
* the host doesn't read any stale data.
*
* HOST_TO_MEMORY transfer:
* SOF assumes that data is copied from host to local memory via
* DMA, which is not the case for imx platforms. For these
* platforms, the DSP is in charge of copying the data from host to
* local memory.
*
* Additionally, because of the aforementioned assumption,
* SOF performs a cache invalidation on the destination
* memory chunk before data is copied further down the
* pipeline.
*
* If the destination memory chunk is cacheable what seems
* to happen is that the invalidation operation forces the
* DSP to fetch the data from RAM instead of the cache.
* Since a writeback was never performed on the destination
* memory chunk, the RAM will contain stale data.
*
* With this in mind, the writeback should also be
* performed in HOST_TO_MEMORY transfers (aka playback)
* to keep the cache and RAM in sync. This way, the DSP
* will read the correct data from RAM (when forced to do
* so by the cache invalidation operation).
*
* TODO: this is NOT optimal since we perform two unneeded
* cache management operations and should be addressed in
* SOF at some point.
*/
ret = sys_cache_data_flush_range(UINT_TO_POINTER(chan->dest), chan->size);
if (ret < 0) {
LOG_ERR("failed to flush data cache range");
return ret;
}
return 0;
}
static int sof_host_dma_config(const struct device *dev, uint32_t chan_id,
struct dma_config *config)
{
struct sof_host_dma_data *data;
struct sof_host_dma_channel *chan;
int ret;
data = dev->data;
if (chan_id >= data->ctx.dma_channels) {
LOG_ERR("channel %d is not a valid channel ID", chan_id);
return -EINVAL;
}
/* fetch channel data */
chan = &data->channels[chan_id];
/* attempt a state transition */
ret = channel_change_state(chan, CHAN_STATE_CONFIGURED);
if (ret < 0) {
LOG_ERR("failed to change channel %d's state to CONFIGURED", chan_id);
return ret;
}
/* SG configurations are not currently supported */
if (config->block_count != 1) {
LOG_ERR("invalid number of blocks: %d", config->block_count);
return -EINVAL;
}
if (!config->head_block->source_address) {
LOG_ERR("got NULL source address");
return -EINVAL;
}
if (!config->head_block->dest_address) {
LOG_ERR("got NULL destination address");
return -EINVAL;
}
if (!config->head_block->block_size) {
LOG_ERR("got 0 bytes to copy");
return -EINVAL;
}
/* for now, only H2M and M2H transfers are supported */
if (config->channel_direction != HOST_TO_MEMORY &&
config->channel_direction != MEMORY_TO_HOST) {
LOG_ERR("invalid channel direction: %d",
config->channel_direction);
return -EINVAL;
}
/* latch onto the passed configuration */
chan->src = config->head_block->source_address;
chan->dest = config->head_block->dest_address;
chan->size = config->head_block->block_size;
chan->direction = config->channel_direction;
LOG_DBG("configured channel %d with SRC 0x%x DST 0x%x SIZE 0x%x",
chan_id, chan->src, chan->dest, chan->size);
return 0;
}
static int sof_host_dma_start(const struct device *dev, uint32_t chan_id)
{
/* nothing to be done here */
return 0;
}
static int sof_host_dma_stop(const struct device *dev, uint32_t chan_id)
{
/* nothing to be done here */
return 0;
}
static int sof_host_dma_suspend(const struct device *dev, uint32_t chan_id)
{
/* nothing to be done here */
return 0;
}
static int sof_host_dma_resume(const struct device *dev, uint32_t chan_id)
{
/* nothing to be done here */
return 0;
}
static int sof_host_dma_get_status(const struct device *dev,
uint32_t chan_id, struct dma_status *stat)
{
/* nothing to be done here */
return 0;
}
static int sof_host_dma_get_attribute(const struct device *dev, uint32_t type, uint32_t *val)
{
switch (type) {
case DMA_ATTR_COPY_ALIGNMENT:
case DMA_ATTR_BUFFER_SIZE_ALIGNMENT:
case DMA_ATTR_BUFFER_ADDRESS_ALIGNMENT:
*val = CONFIG_DMA_NXP_SOF_HOST_DMA_ALIGN;
break;
default:
LOG_ERR("invalid attribute type: %d", type);
return -EINVAL;
}
return 0;
}
static const struct dma_driver_api sof_host_dma_api = {
.reload = sof_host_dma_reload,
.config = sof_host_dma_config,
.start = sof_host_dma_start,
.stop = sof_host_dma_stop,
.suspend = sof_host_dma_suspend,
.resume = sof_host_dma_resume,
.get_status = sof_host_dma_get_status,
.get_attribute = sof_host_dma_get_attribute,
};
static int sof_host_dma_init(const struct device *dev)
{
struct sof_host_dma_data *data = dev->data;
data->channel_flags = ATOMIC_INIT(0);
data->ctx.atomic = &data->channel_flags;
return 0;
}
static struct sof_host_dma_channel channels[] = {
SOF_HOST_DMA_CHANNELS_DECLARE(0),
};
static struct sof_host_dma_data sof_host_dma_data = {
.ctx.magic = DMA_MAGIC,
.ctx.dma_channels = ARRAY_SIZE(channels),
.channels = channels,
};
/* assumption: only 1 SOF_HOST_DMA instance */
DEVICE_DT_INST_DEFINE(0, sof_host_dma_init, NULL,
&sof_host_dma_data, NULL,
PRE_KERNEL_1, CONFIG_DMA_INIT_PRIORITY,
&sof_host_dma_api);