2 * Copyright (C) 2013, Analog Devices Inc.
3 * Author: Lars-Peter Clausen <lars@metafoo.de>
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version.
10 * You should have received a copy of the GNU General Public License along
11 * with this program; if not, write to the Free Software Foundation, Inc.,
12 * 675 Mass Ave, Cambridge, MA 02139, USA.
15 #include <linux/module.h>
16 #include <linux/init.h>
17 #include <linux/dmaengine.h>
18 #include <linux/slab.h>
19 #include <sound/pcm.h>
20 #include <sound/pcm_params.h>
21 #include <sound/soc.h>
22 #include <linux/dma-mapping.h>
25 #include <sound/dmaengine_pcm.h>
27 struct dmaengine_pcm
{
28 struct dma_chan
*chan
[SNDRV_PCM_STREAM_CAPTURE
+ 1];
29 const struct snd_dmaengine_pcm_config
*config
;
30 struct snd_soc_platform platform
;
34 static struct dmaengine_pcm
*soc_platform_to_pcm(struct snd_soc_platform
*p
)
36 return container_of(p
, struct dmaengine_pcm
, platform
);
40 * snd_dmaengine_pcm_prepare_slave_config() - Generic prepare_slave_config callback
41 * @substream: PCM substream
43 * @slave_config: DMA slave config to prepare
45 * This function can be used as a generic prepare_slave_config callback for
46 * platforms which make use of the snd_dmaengine_dai_dma_data struct for their
47 * DAI DMA data. Internally the function will first call
48 * snd_hwparams_to_dma_slave_config to fill in the slave config based on the
49 * hw_params, followed by snd_dmaengine_set_config_from_dai_data to fill in the
50 * remaining fields based on the DAI DMA data.
52 int snd_dmaengine_pcm_prepare_slave_config(struct snd_pcm_substream
*substream
,
53 struct snd_pcm_hw_params
*params
, struct dma_slave_config
*slave_config
)
55 struct snd_soc_pcm_runtime
*rtd
= substream
->private_data
;
56 struct snd_dmaengine_dai_dma_data
*dma_data
;
59 dma_data
= snd_soc_dai_get_dma_data(rtd
->cpu_dai
, substream
);
61 ret
= snd_hwparams_to_dma_slave_config(substream
, params
, slave_config
);
65 snd_dmaengine_pcm_set_config_from_dai_data(substream
, dma_data
,
70 EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_prepare_slave_config
);
72 static int dmaengine_pcm_hw_params(struct snd_pcm_substream
*substream
,
73 struct snd_pcm_hw_params
*params
)
75 struct snd_soc_pcm_runtime
*rtd
= substream
->private_data
;
76 struct dmaengine_pcm
*pcm
= soc_platform_to_pcm(rtd
->platform
);
77 struct dma_chan
*chan
= snd_dmaengine_pcm_get_chan(substream
);
78 struct dma_slave_config slave_config
;
81 if (pcm
->config
->prepare_slave_config
) {
82 ret
= pcm
->config
->prepare_slave_config(substream
, params
,
87 ret
= dmaengine_slave_config(chan
, &slave_config
);
92 return snd_pcm_lib_malloc_pages(substream
, params_buffer_bytes(params
));
95 static int dmaengine_pcm_open(struct snd_pcm_substream
*substream
)
97 struct snd_soc_pcm_runtime
*rtd
= substream
->private_data
;
98 struct dmaengine_pcm
*pcm
= soc_platform_to_pcm(rtd
->platform
);
99 struct dma_chan
*chan
= pcm
->chan
[substream
->stream
];
102 ret
= snd_soc_set_runtime_hwparams(substream
,
103 pcm
->config
->pcm_hardware
);
107 return snd_dmaengine_pcm_open(substream
, chan
);
110 static struct device
*dmaengine_dma_dev(struct dmaengine_pcm
*pcm
,
111 struct snd_pcm_substream
*substream
)
113 if (!pcm
->chan
[substream
->stream
])
116 return pcm
->chan
[substream
->stream
]->device
->dev
;
119 static void dmaengine_pcm_free(struct snd_pcm
*pcm
)
121 snd_pcm_lib_preallocate_free_for_all(pcm
);
124 static struct dma_chan
*dmaengine_pcm_compat_request_channel(
125 struct snd_soc_pcm_runtime
*rtd
,
126 struct snd_pcm_substream
*substream
)
128 struct dmaengine_pcm
*pcm
= soc_platform_to_pcm(rtd
->platform
);
130 if ((pcm
->flags
& SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX
) && pcm
->chan
[0])
133 if (pcm
->config
->compat_request_channel
)
134 return pcm
->config
->compat_request_channel(rtd
, substream
);
136 return snd_dmaengine_pcm_request_channel(pcm
->config
->compat_filter_fn
,
137 snd_soc_dai_get_dma_data(rtd
->cpu_dai
, substream
));
140 static int dmaengine_pcm_new(struct snd_soc_pcm_runtime
*rtd
)
142 struct dmaengine_pcm
*pcm
= soc_platform_to_pcm(rtd
->platform
);
143 const struct snd_dmaengine_pcm_config
*config
= pcm
->config
;
144 struct snd_pcm_substream
*substream
;
148 for (i
= SNDRV_PCM_STREAM_PLAYBACK
; i
<= SNDRV_PCM_STREAM_CAPTURE
; i
++) {
149 substream
= rtd
->pcm
->streams
[i
].substream
;
153 if (!pcm
->chan
[i
] && (pcm
->flags
& SND_DMAENGINE_PCM_FLAG_COMPAT
)) {
154 pcm
->chan
[i
] = dmaengine_pcm_compat_request_channel(rtd
,
159 dev_err(rtd
->platform
->dev
,
160 "Missing dma channel for stream: %d\n", i
);
165 ret
= snd_pcm_lib_preallocate_pages(substream
,
167 dmaengine_dma_dev(pcm
, substream
),
168 config
->prealloc_buffer_size
,
169 config
->pcm_hardware
->buffer_bytes_max
);
177 dmaengine_pcm_free(rtd
->pcm
);
181 static const struct snd_pcm_ops dmaengine_pcm_ops
= {
182 .open
= dmaengine_pcm_open
,
183 .close
= snd_dmaengine_pcm_close
,
184 .ioctl
= snd_pcm_lib_ioctl
,
185 .hw_params
= dmaengine_pcm_hw_params
,
186 .hw_free
= snd_pcm_lib_free_pages
,
187 .trigger
= snd_dmaengine_pcm_trigger
,
188 .pointer
= snd_dmaengine_pcm_pointer
,
191 static const struct snd_soc_platform_driver dmaengine_pcm_platform
= {
192 .ops
= &dmaengine_pcm_ops
,
193 .pcm_new
= dmaengine_pcm_new
,
194 .pcm_free
= dmaengine_pcm_free
,
195 .probe_order
= SND_SOC_COMP_ORDER_LATE
,
198 static const struct snd_pcm_ops dmaengine_no_residue_pcm_ops
= {
199 .open
= dmaengine_pcm_open
,
200 .close
= snd_dmaengine_pcm_close
,
201 .ioctl
= snd_pcm_lib_ioctl
,
202 .hw_params
= dmaengine_pcm_hw_params
,
203 .hw_free
= snd_pcm_lib_free_pages
,
204 .trigger
= snd_dmaengine_pcm_trigger
,
205 .pointer
= snd_dmaengine_pcm_pointer_no_residue
,
208 static const struct snd_soc_platform_driver dmaengine_no_residue_pcm_platform
= {
209 .ops
= &dmaengine_no_residue_pcm_ops
,
210 .pcm_new
= dmaengine_pcm_new
,
211 .pcm_free
= dmaengine_pcm_free
,
212 .probe_order
= SND_SOC_COMP_ORDER_LATE
,
215 static const char * const dmaengine_pcm_dma_channel_names
[] = {
216 [SNDRV_PCM_STREAM_PLAYBACK
] = "tx",
217 [SNDRV_PCM_STREAM_CAPTURE
] = "rx",
220 static void dmaengine_pcm_request_chan_of(struct dmaengine_pcm
*pcm
,
225 if ((pcm
->flags
& SND_DMAENGINE_PCM_FLAG_NO_DT
) || !dev
->of_node
)
228 if (pcm
->flags
& SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX
) {
229 pcm
->chan
[0] = dma_request_slave_channel(dev
, "rx-tx");
230 pcm
->chan
[1] = pcm
->chan
[0];
232 for (i
= SNDRV_PCM_STREAM_PLAYBACK
; i
<= SNDRV_PCM_STREAM_CAPTURE
; i
++) {
233 pcm
->chan
[i
] = dma_request_slave_channel(dev
,
234 dmaengine_pcm_dma_channel_names
[i
]);
240 * snd_dmaengine_pcm_register - Register a dmaengine based PCM device
241 * @dev: The parent device for the PCM device
242 * @config: Platform specific PCM configuration
243 * @flags: Platform specific quirks
245 int snd_dmaengine_pcm_register(struct device
*dev
,
246 const struct snd_dmaengine_pcm_config
*config
, unsigned int flags
)
248 struct dmaengine_pcm
*pcm
;
250 pcm
= kzalloc(sizeof(*pcm
), GFP_KERNEL
);
254 pcm
->config
= config
;
257 dmaengine_pcm_request_chan_of(pcm
, dev
);
259 if (flags
& SND_DMAENGINE_PCM_FLAG_NO_RESIDUE
)
260 return snd_soc_add_platform(dev
, &pcm
->platform
,
261 &dmaengine_no_residue_pcm_platform
);
263 return snd_soc_add_platform(dev
, &pcm
->platform
,
264 &dmaengine_pcm_platform
);
266 EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_register
);
269 * snd_dmaengine_pcm_unregister - Removes a dmaengine based PCM device
270 * @dev: Parent device the PCM was register with
272 * Removes a dmaengine based PCM device previously registered with
273 * snd_dmaengine_pcm_register.
275 void snd_dmaengine_pcm_unregister(struct device
*dev
)
277 struct snd_soc_platform
*platform
;
278 struct dmaengine_pcm
*pcm
;
281 platform
= snd_soc_lookup_platform(dev
);
285 pcm
= soc_platform_to_pcm(platform
);
287 for (i
= SNDRV_PCM_STREAM_PLAYBACK
; i
<= SNDRV_PCM_STREAM_CAPTURE
; i
++) {
289 dma_release_channel(pcm
->chan
[i
]);
290 if (pcm
->flags
& SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX
)
295 snd_soc_remove_platform(platform
);
298 EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_unregister
);
300 MODULE_LICENSE("GPL");