Blame view

sound/soc/soc-dmaengine-pcm.c 11 KB
e7f73a161   Lars-Peter Clausen   ASoC: Add dmaengi...
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
  /*
   *  Copyright (C) 2012, Analog Devices Inc.
   *	Author: Lars-Peter Clausen <lars@metafoo.de>
   *
   *  Based on:
   *	imx-pcm-dma-mx2.c, Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de>
   *	mxs-pcm.c, Copyright (C) 2011 Freescale Semiconductor, Inc.
   *	ep93xx-pcm.c, Copyright (C) 2006 Lennert Buytenhek <buytenh@wantstofly.org>
   *		      Copyright (C) 2006 Applied Data Systems
   *
   *  This program is free software; you can redistribute it and/or modify it
   *  under  the terms of the GNU General  Public License as published by the
   *  Free Software Foundation;  either version 2 of the License, or (at your
   *  option) any later version.
   *
   *  You should have received a copy of the GNU General Public License along
   *  with this program; if not, write to the Free Software Foundation, Inc.,
   *  675 Mass Ave, Cambridge, MA 02139, USA.
   *
   */
  #include <linux/module.h>
  #include <linux/init.h>
  #include <linux/dmaengine.h>
  #include <linux/slab.h>
  #include <sound/pcm.h>
  #include <sound/pcm_params.h>
  #include <sound/soc.h>
  
  #include <sound/dmaengine_pcm.h>
  
  struct dmaengine_pcm_runtime_data {
  	struct dma_chan *dma_chan;
3528f27a5   Lars-Peter Clausen   ASoC: dmaengine-p...
33
  	dma_cookie_t cookie;
e7f73a161   Lars-Peter Clausen   ASoC: Add dmaengi...
34
35
  
  	unsigned int pos;
e7f73a161   Lars-Peter Clausen   ASoC: Add dmaengi...
36
37
38
39
40
41
42
  };
  
  static inline struct dmaengine_pcm_runtime_data *substream_to_prtd(
  	const struct snd_pcm_substream *substream)
  {
  	return substream->runtime->private_data;
  }
e7f73a161   Lars-Peter Clausen   ASoC: Add dmaengi...
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
  struct dma_chan *snd_dmaengine_pcm_get_chan(struct snd_pcm_substream *substream)
  {
  	struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream);
  
  	return prtd->dma_chan;
  }
  EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_get_chan);
  
  /**
   * snd_hwparams_to_dma_slave_config - Convert hw_params to dma_slave_config
   * @substream: PCM substream
   * @params: hw_params
   * @slave_config: DMA slave config
   *
   * This function can be used to initialize a dma_slave_config from a substream
   * and hw_params in a dmaengine based PCM driver implementation.
   */
  int snd_hwparams_to_dma_slave_config(const struct snd_pcm_substream *substream,
  	const struct snd_pcm_hw_params *params,
  	struct dma_slave_config *slave_config)
  {
  	enum dma_slave_buswidth buswidth;
  
  	switch (params_format(params)) {
  	case SNDRV_PCM_FORMAT_S8:
  		buswidth = DMA_SLAVE_BUSWIDTH_1_BYTE;
  		break;
  	case SNDRV_PCM_FORMAT_S16_LE:
  		buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES;
  		break;
  	case SNDRV_PCM_FORMAT_S18_3LE:
  	case SNDRV_PCM_FORMAT_S20_3LE:
  	case SNDRV_PCM_FORMAT_S24_LE:
  	case SNDRV_PCM_FORMAT_S32_LE:
  		buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES;
  		break;
  	default:
  		return -EINVAL;
  	}
  
  	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
  		slave_config->direction = DMA_MEM_TO_DEV;
  		slave_config->dst_addr_width = buswidth;
  	} else {
  		slave_config->direction = DMA_DEV_TO_MEM;
  		slave_config->src_addr_width = buswidth;
  	}
5fa70f71d   Lars-Peter Clausen   ASoC: dmaengine_p...
90
  	slave_config->device_fc = false;
e7f73a161   Lars-Peter Clausen   ASoC: Add dmaengi...
91
92
93
  	return 0;
  }
  EXPORT_SYMBOL_GPL(snd_hwparams_to_dma_slave_config);
85c9f9c5f   Lars-Peter Clausen   ASoC: dmaengine-p...
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
  /**
   * snd_dmaengine_pcm_set_config_from_dai_data() - Initializes a dma slave config
   *  using DAI DMA data.
   * @substream: PCM substream
   * @dma_data: DAI DMA data
   * @slave_config: DMA slave configuration
   *
   * Initializes the {dst,src}_addr, {dst,src}_maxburst, {dst,src}_addr_width and
   * slave_id fields of the DMA slave config from the same fields of the DAI DMA
   * data struct. The src and dst fields will be initialized depending on the
   * direction of the substream. If the substream is a playback stream the dst
   * fields will be initialized, if it is a capture stream the src fields will be
   * initialized. The {dst,src}_addr_width field will only be initialized if the
   * addr_width field of the DAI DMA data struct is not equal to
   * DMA_SLAVE_BUSWIDTH_UNDEFINED.
   */
  void snd_dmaengine_pcm_set_config_from_dai_data(
  	const struct snd_pcm_substream *substream,
  	const struct snd_dmaengine_dai_dma_data *dma_data,
  	struct dma_slave_config *slave_config)
  {
  	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
  		slave_config->dst_addr = dma_data->addr;
  		slave_config->dst_maxburst = dma_data->maxburst;
  		if (dma_data->addr_width != DMA_SLAVE_BUSWIDTH_UNDEFINED)
  			slave_config->dst_addr_width = dma_data->addr_width;
  	} else {
  		slave_config->src_addr = dma_data->addr;
  		slave_config->src_maxburst = dma_data->maxburst;
  		if (dma_data->addr_width != DMA_SLAVE_BUSWIDTH_UNDEFINED)
  			slave_config->src_addr_width = dma_data->addr_width;
  	}
  
  	slave_config->slave_id = dma_data->slave_id;
  }
  EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_set_config_from_dai_data);
e7f73a161   Lars-Peter Clausen   ASoC: Add dmaengi...
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
  static void dmaengine_pcm_dma_complete(void *arg)
  {
  	struct snd_pcm_substream *substream = arg;
  	struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream);
  
  	prtd->pos += snd_pcm_lib_period_bytes(substream);
  	if (prtd->pos >= snd_pcm_lib_buffer_bytes(substream))
  		prtd->pos = 0;
  
  	snd_pcm_period_elapsed(substream);
  }
  
  static int dmaengine_pcm_prepare_and_submit(struct snd_pcm_substream *substream)
  {
  	struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream);
  	struct dma_chan *chan = prtd->dma_chan;
  	struct dma_async_tx_descriptor *desc;
  	enum dma_transfer_direction direction;
e7736cdea   Peter Ujfalusi   dmaengine: Add fl...
148
  	unsigned long flags = DMA_CTRL_ACK;
e7f73a161   Lars-Peter Clausen   ASoC: Add dmaengi...
149
150
  
  	direction = snd_pcm_substream_to_dma_direction(substream);
e7736cdea   Peter Ujfalusi   dmaengine: Add fl...
151
152
  	if (!substream->runtime->no_period_wakeup)
  		flags |= DMA_PREP_INTERRUPT;
7a08cf702   Mika Westerberg   ASoC: dmaengine_p...
153
  	prtd->pos = 0;
41ba6b711   Vinod Koul   ASoC: dmaengine_p...
154
  	desc = dmaengine_prep_dma_cyclic(chan,
e7f73a161   Lars-Peter Clausen   ASoC: Add dmaengi...
155
156
  		substream->runtime->dma_addr,
  		snd_pcm_lib_buffer_bytes(substream),
e7736cdea   Peter Ujfalusi   dmaengine: Add fl...
157
  		snd_pcm_lib_period_bytes(substream), direction, flags);
e7f73a161   Lars-Peter Clausen   ASoC: Add dmaengi...
158
159
160
161
162
163
  
  	if (!desc)
  		return -ENOMEM;
  
  	desc->callback = dmaengine_pcm_dma_complete;
  	desc->callback_param = substream;
3528f27a5   Lars-Peter Clausen   ASoC: dmaengine-p...
164
  	prtd->cookie = dmaengine_submit(desc);
e7f73a161   Lars-Peter Clausen   ASoC: Add dmaengi...
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
  
  	return 0;
  }
  
  /**
   * snd_dmaengine_pcm_trigger - dmaengine based PCM trigger implementation
   * @substream: PCM substream
   * @cmd: Trigger command
   *
   * Returns 0 on success, a negative error code otherwise.
   *
   * This function can be used as the PCM trigger callback for dmaengine based PCM
   * driver implementations.
   */
  int snd_dmaengine_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
  {
  	struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream);
  	int ret;
  
  	switch (cmd) {
  	case SNDRV_PCM_TRIGGER_START:
  		ret = dmaengine_pcm_prepare_and_submit(substream);
  		if (ret)
  			return ret;
  		dma_async_issue_pending(prtd->dma_chan);
  		break;
  	case SNDRV_PCM_TRIGGER_RESUME:
  	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
  		dmaengine_resume(prtd->dma_chan);
  		break;
  	case SNDRV_PCM_TRIGGER_SUSPEND:
  	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
  		dmaengine_pause(prtd->dma_chan);
  		break;
  	case SNDRV_PCM_TRIGGER_STOP:
  		dmaengine_terminate_all(prtd->dma_chan);
  		break;
  	default:
  		return -EINVAL;
  	}
  
  	return 0;
  }
  EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_trigger);
  
  /**
9883ab229   Lars-Peter Clausen   ASoC: dmaengine-p...
211
   * snd_dmaengine_pcm_pointer_no_residue - dmaengine based PCM pointer implementation
e7f73a161   Lars-Peter Clausen   ASoC: Add dmaengi...
212
213
   * @substream: PCM substream
   *
9883ab229   Lars-Peter Clausen   ASoC: dmaengine-p...
214
215
   * This function is deprecated and should not be used by new drivers, as its
   * results may be unreliable.
e7f73a161   Lars-Peter Clausen   ASoC: Add dmaengi...
216
   */
9883ab229   Lars-Peter Clausen   ASoC: dmaengine-p...
217
  snd_pcm_uframes_t snd_dmaengine_pcm_pointer_no_residue(struct snd_pcm_substream *substream)
e7f73a161   Lars-Peter Clausen   ASoC: Add dmaengi...
218
219
220
221
  {
  	struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream);
  	return bytes_to_frames(substream->runtime, prtd->pos);
  }
9883ab229   Lars-Peter Clausen   ASoC: dmaengine-p...
222
  EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_pointer_no_residue);
e7f73a161   Lars-Peter Clausen   ASoC: Add dmaengi...
223

3528f27a5   Lars-Peter Clausen   ASoC: dmaengine-p...
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
  /**
   * snd_dmaengine_pcm_pointer - dmaengine based PCM pointer implementation
   * @substream: PCM substream
   *
   * This function can be used as the PCM pointer callback for dmaengine based PCM
   * driver implementations.
   */
  snd_pcm_uframes_t snd_dmaengine_pcm_pointer(struct snd_pcm_substream *substream)
  {
  	struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream);
  	struct dma_tx_state state;
  	enum dma_status status;
  	unsigned int buf_size;
  	unsigned int pos = 0;
  
  	status = dmaengine_tx_status(prtd->dma_chan, prtd->cookie, &state);
  	if (status == DMA_IN_PROGRESS || status == DMA_PAUSED) {
  		buf_size = snd_pcm_lib_buffer_bytes(substream);
  		if (state.residue > 0 && state.residue <= buf_size)
  			pos = buf_size - state.residue;
  	}
  
  	return bytes_to_frames(substream->runtime, pos);
  }
  EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_pointer);
c999836d3   Lars-Peter Clausen   ASoC: dmaengine_p...
249
250
251
252
253
254
255
256
257
258
  /**
   * snd_dmaengine_pcm_request_channel - Request channel for the dmaengine PCM
   * @filter_fn: Filter function used to request the DMA channel
   * @filter_data: Data passed to the DMA filter function
   *
   * Returns NULL or the requested DMA channel.
   *
   * This function request a DMA channel for usage with dmaengine PCM.
   */
  struct dma_chan *snd_dmaengine_pcm_request_channel(dma_filter_fn filter_fn,
7c1c1d4a7   Lars-Peter Clausen   ASoC: dmaengine-p...
259
  	void *filter_data)
e7f73a161   Lars-Peter Clausen   ASoC: Add dmaengi...
260
261
262
263
264
265
  {
  	dma_cap_mask_t mask;
  
  	dma_cap_zero(mask);
  	dma_cap_set(DMA_SLAVE, mask);
  	dma_cap_set(DMA_CYCLIC, mask);
e7f73a161   Lars-Peter Clausen   ASoC: Add dmaengi...
266

7c1c1d4a7   Lars-Peter Clausen   ASoC: dmaengine-p...
267
  	return dma_request_channel(mask, filter_fn, filter_data);
e7f73a161   Lars-Peter Clausen   ASoC: Add dmaengi...
268
  }
c999836d3   Lars-Peter Clausen   ASoC: dmaengine_p...
269
  EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_request_channel);
e7f73a161   Lars-Peter Clausen   ASoC: Add dmaengi...
270
271
272
273
  
  /**
   * snd_dmaengine_pcm_open - Open a dmaengine based PCM substream
   * @substream: PCM substream
7c1c1d4a7   Lars-Peter Clausen   ASoC: dmaengine-p...
274
   * @chan: DMA channel to use for data transfers
e7f73a161   Lars-Peter Clausen   ASoC: Add dmaengi...
275
276
277
   *
   * Returns 0 on success, a negative error code otherwise.
   *
7c1c1d4a7   Lars-Peter Clausen   ASoC: dmaengine-p...
278
279
280
   * The function should usually be called from the pcm open callback. Note that
   * this function will use private_data field of the substream's runtime. So it
   * is not availabe to your pcm driver implementation.
e7f73a161   Lars-Peter Clausen   ASoC: Add dmaengi...
281
282
   */
  int snd_dmaengine_pcm_open(struct snd_pcm_substream *substream,
7c1c1d4a7   Lars-Peter Clausen   ASoC: dmaengine-p...
283
  	struct dma_chan *chan)
e7f73a161   Lars-Peter Clausen   ASoC: Add dmaengi...
284
285
286
  {
  	struct dmaengine_pcm_runtime_data *prtd;
  	int ret;
7c1c1d4a7   Lars-Peter Clausen   ASoC: dmaengine-p...
287
288
  	if (!chan)
  		return -ENXIO;
e7f73a161   Lars-Peter Clausen   ASoC: Add dmaengi...
289
290
291
292
293
294
295
296
  	ret = snd_pcm_hw_constraint_integer(substream->runtime,
  					    SNDRV_PCM_HW_PARAM_PERIODS);
  	if (ret < 0)
  		return ret;
  
  	prtd = kzalloc(sizeof(*prtd), GFP_KERNEL);
  	if (!prtd)
  		return -ENOMEM;
7c1c1d4a7   Lars-Peter Clausen   ASoC: dmaengine-p...
297
  	prtd->dma_chan = chan;
e7f73a161   Lars-Peter Clausen   ASoC: Add dmaengi...
298
299
300
301
302
303
304
305
  
  	substream->runtime->private_data = prtd;
  
  	return 0;
  }
  EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_open);
  
  /**
7c1c1d4a7   Lars-Peter Clausen   ASoC: dmaengine-p...
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
   * snd_dmaengine_pcm_open_request_chan - Open a dmaengine based PCM substream and request channel
   * @substream: PCM substream
   * @filter_fn: Filter function used to request the DMA channel
   * @filter_data: Data passed to the DMA filter function
   *
   * Returns 0 on success, a negative error code otherwise.
   *
   * This function will request a DMA channel using the passed filter function and
   * data. The function should usually be called from the pcm open callback. Note
   * that this function will use private_data field of the substream's runtime. So
   * it is not availabe to your pcm driver implementation.
   */
  int snd_dmaengine_pcm_open_request_chan(struct snd_pcm_substream *substream,
  	dma_filter_fn filter_fn, void *filter_data)
  {
  	return snd_dmaengine_pcm_open(substream,
c999836d3   Lars-Peter Clausen   ASoC: dmaengine_p...
322
  		    snd_dmaengine_pcm_request_channel(filter_fn, filter_data));
7c1c1d4a7   Lars-Peter Clausen   ASoC: dmaengine-p...
323
324
325
326
  }
  EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_open_request_chan);
  
  /**
e7f73a161   Lars-Peter Clausen   ASoC: Add dmaengi...
327
328
329
330
331
332
   * snd_dmaengine_pcm_close - Close a dmaengine based PCM substream
   * @substream: PCM substream
   */
  int snd_dmaengine_pcm_close(struct snd_pcm_substream *substream)
  {
  	struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream);
e7f73a161   Lars-Peter Clausen   ASoC: Add dmaengi...
333
334
335
336
337
  	kfree(prtd);
  
  	return 0;
  }
  EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_close);
cba69b4dc   Lothar Waßmann   ASoC: dmaengine_p...
338

7c1c1d4a7   Lars-Peter Clausen   ASoC: dmaengine-p...
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
  /**
   * snd_dmaengine_pcm_release_chan_close - Close a dmaengine based PCM substream and release channel
   * @substream: PCM substream
   *
   * Releases the DMA channel associated with the PCM substream.
   */
  int snd_dmaengine_pcm_close_release_chan(struct snd_pcm_substream *substream)
  {
  	struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream);
  
  	dma_release_channel(prtd->dma_chan);
  
  	return snd_dmaengine_pcm_close(substream);
  }
  EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_close_release_chan);
cba69b4dc   Lothar Waßmann   ASoC: dmaengine_p...
354
  MODULE_LICENSE("GPL");