aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorArnaud Pouliquen <arnaud.pouliquen@st.com>2018-01-10 05:13:15 -0500
committerMark Brown <broonie@kernel.org>2018-01-10 05:52:05 -0500
commit55da094824c4ef1d50bc591733d79448d00265bb (patch)
tree03200b7774191dff4e8cb9097a47621d5834110a
parenta71615792d0b341500a9d1d7b374d19e3d443a12 (diff)
ASoC: stm32: add DFSDM DAI support
Add driver to handle DAI interface for PDM microphones connected to Digital Filter for Sigma Delta Modulators IP. Signed-off-by: Arnaud Pouliquen <arnaud.pouliquen@st.com> Signed-off-by: Mark Brown <broonie@kernel.org>
-rw-r--r--sound/soc/stm/Kconfig11
-rw-r--r--sound/soc/stm/Makefile3
-rw-r--r--sound/soc/stm/stm32_adfsdm.c347
3 files changed, 361 insertions, 0 deletions
diff --git a/sound/soc/stm/Kconfig b/sound/soc/stm/Kconfig
index 3398e6c57f37..a78f7700d489 100644
--- a/sound/soc/stm/Kconfig
+++ b/sound/soc/stm/Kconfig
@@ -28,4 +28,15 @@ config SND_SOC_STM32_SPDIFRX
28 help 28 help
29 Say Y if you want to enable S/PDIF capture for STM32 29 Say Y if you want to enable S/PDIF capture for STM32
30 30
31config SND_SOC_STM32_DFSDM
32 tristate "SoC Audio support for STM32 DFSDM"
33 depends on (ARCH_STM32 && OF && STM32_DFSDM_ADC) || COMPILE_TEST
34 depends on SND_SOC
35 select SND_SOC_GENERIC_DMAENGINE_PCM
36 select SND_SOC_DMIC
37 select IIO_BUFFER_CB
38 help
39 Select this option to enable the STM32 Digital Filter
40 for Sigma Delta Modulators (DFSDM) driver used
41 in various STM32 series for digital microphone capture.
31endmenu 42endmenu
diff --git a/sound/soc/stm/Makefile b/sound/soc/stm/Makefile
index 5b7f0fab0bd6..3143c0b47042 100644
--- a/sound/soc/stm/Makefile
+++ b/sound/soc/stm/Makefile
@@ -13,3 +13,6 @@ obj-$(CONFIG_SND_SOC_STM32_I2S) += snd-soc-stm32-i2s.o
13# SPDIFRX 13# SPDIFRX
14snd-soc-stm32-spdifrx-objs := stm32_spdifrx.o 14snd-soc-stm32-spdifrx-objs := stm32_spdifrx.o
15obj-$(CONFIG_SND_SOC_STM32_SPDIFRX) += snd-soc-stm32-spdifrx.o 15obj-$(CONFIG_SND_SOC_STM32_SPDIFRX) += snd-soc-stm32-spdifrx.o
16
17#DFSDM
18obj-$(CONFIG_SND_SOC_STM32_DFSDM) += stm32_adfsdm.o
diff --git a/sound/soc/stm/stm32_adfsdm.c b/sound/soc/stm/stm32_adfsdm.c
new file mode 100644
index 000000000000..af50891983c6
--- /dev/null
+++ b/sound/soc/stm/stm32_adfsdm.c
@@ -0,0 +1,347 @@
1// SPDX-License-Identifier: GPL-2.0
2/*
3 * This file is part of STM32 DFSDM ASoC DAI driver
4 *
5 * Copyright (C) 2017, STMicroelectronics - All Rights Reserved
6 * Authors: Arnaud Pouliquen <arnaud.pouliquen@st.com>
7 * Olivier Moysan <olivier.moysan@st.com>
8 */
9
10#include <linux/clk.h>
11#include <linux/module.h>
12#include <linux/platform_device.h>
13#include <linux/slab.h>
14
15#include <linux/iio/iio.h>
16#include <linux/iio/consumer.h>
17#include <linux/iio/adc/stm32-dfsdm-adc.h>
18
19#include <sound/pcm.h>
20#include <sound/soc.h>
21
22#define STM32_ADFSDM_DRV_NAME "stm32-adfsdm"
23
24#define DFSDM_MAX_PERIOD_SIZE (PAGE_SIZE / 2)
25#define DFSDM_MAX_PERIODS 6
26
27struct stm32_adfsdm_priv {
28 struct snd_soc_dai_driver dai_drv;
29 struct snd_pcm_substream *substream;
30 struct device *dev;
31
32 /* IIO */
33 struct iio_channel *iio_ch;
34 struct iio_cb_buffer *iio_cb;
35 bool iio_active;
36
37 /* PCM buffer */
38 unsigned char *pcm_buff;
39 unsigned int pos;
40};
41
42static const struct snd_pcm_hardware stm32_adfsdm_pcm_hw = {
43 .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
44 SNDRV_PCM_INFO_PAUSE,
45 .formats = SNDRV_PCM_FMTBIT_S32_LE,
46
47 .rate_min = 8000,
48 .rate_max = 32000,
49
50 .channels_min = 1,
51 .channels_max = 1,
52
53 .periods_min = 2,
54 .periods_max = DFSDM_MAX_PERIODS,
55
56 .period_bytes_max = DFSDM_MAX_PERIOD_SIZE,
57 .buffer_bytes_max = DFSDM_MAX_PERIODS * DFSDM_MAX_PERIOD_SIZE
58};
59
60static void stm32_adfsdm_shutdown(struct snd_pcm_substream *substream,
61 struct snd_soc_dai *dai)
62{
63 struct stm32_adfsdm_priv *priv = snd_soc_dai_get_drvdata(dai);
64
65 if (priv->iio_active) {
66 iio_channel_stop_all_cb(priv->iio_cb);
67 priv->iio_active = false;
68 }
69}
70
71static int stm32_adfsdm_dai_prepare(struct snd_pcm_substream *substream,
72 struct snd_soc_dai *dai)
73{
74 struct stm32_adfsdm_priv *priv = snd_soc_dai_get_drvdata(dai);
75 int ret;
76
77 ret = iio_write_channel_attribute(priv->iio_ch,
78 substream->runtime->rate, 0,
79 IIO_CHAN_INFO_SAMP_FREQ);
80 if (ret < 0) {
81 dev_err(dai->dev, "%s: Failed to set %d sampling rate\n",
82 __func__, substream->runtime->rate);
83 return ret;
84 }
85
86 if (!priv->iio_active) {
87 ret = iio_channel_start_all_cb(priv->iio_cb);
88 if (!ret)
89 priv->iio_active = true;
90 else
91 dev_err(dai->dev, "%s: IIO channel start failed (%d)\n",
92 __func__, ret);
93 }
94
95 return ret;
96}
97
98static int stm32_adfsdm_set_sysclk(struct snd_soc_dai *dai, int clk_id,
99 unsigned int freq, int dir)
100{
101 struct stm32_adfsdm_priv *priv = snd_soc_dai_get_drvdata(dai);
102 ssize_t size;
103 char str_freq[10];
104
105 dev_dbg(dai->dev, "%s: Enter for freq %d\n", __func__, freq);
106
107 /* Set IIO frequency if CODEC is master as clock comes from SPI_IN */
108
109 snprintf(str_freq, sizeof(str_freq), "%d\n", freq);
110 size = iio_write_channel_ext_info(priv->iio_ch, "spi_clk_freq",
111 str_freq, sizeof(str_freq));
112 if (size != sizeof(str_freq)) {
113 dev_err(dai->dev, "%s: Failed to set SPI clock\n",
114 __func__);
115 return -EINVAL;
116 }
117 return 0;
118}
119
120static const struct snd_soc_dai_ops stm32_adfsdm_dai_ops = {
121 .shutdown = stm32_adfsdm_shutdown,
122 .prepare = stm32_adfsdm_dai_prepare,
123 .set_sysclk = stm32_adfsdm_set_sysclk,
124};
125
126static const struct snd_soc_dai_driver stm32_adfsdm_dai = {
127 .capture = {
128 .channels_min = 1,
129 .channels_max = 1,
130 .formats = SNDRV_PCM_FMTBIT_S32_LE,
131 .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
132 SNDRV_PCM_RATE_32000),
133 },
134 .ops = &stm32_adfsdm_dai_ops,
135};
136
137static const struct snd_soc_component_driver stm32_adfsdm_dai_component = {
138 .name = "stm32_dfsdm_audio",
139};
140
141static int stm32_afsdm_pcm_cb(const void *data, size_t size, void *private)
142{
143 struct stm32_adfsdm_priv *priv = private;
144 struct snd_soc_pcm_runtime *rtd = priv->substream->private_data;
145 u8 *pcm_buff = priv->pcm_buff;
146 u8 *src_buff = (u8 *)data;
147 unsigned int buff_size = snd_pcm_lib_buffer_bytes(priv->substream);
148 unsigned int period_size = snd_pcm_lib_period_bytes(priv->substream);
149 unsigned int old_pos = priv->pos;
150 unsigned int cur_size = size;
151
152 dev_dbg(rtd->dev, "%s: buff_add :%p, pos = %d, size = %zu\n",
153 __func__, &pcm_buff[priv->pos], priv->pos, size);
154
155 if ((priv->pos + size) > buff_size) {
156 memcpy(&pcm_buff[priv->pos], src_buff, buff_size - priv->pos);
157 cur_size -= buff_size - priv->pos;
158 priv->pos = 0;
159 }
160
161 memcpy(&pcm_buff[priv->pos], &src_buff[size - cur_size], cur_size);
162 priv->pos = (priv->pos + cur_size) % buff_size;
163
164 if (cur_size != size || (old_pos && (old_pos % period_size < size)))
165 snd_pcm_period_elapsed(priv->substream);
166
167 return 0;
168}
169
170static int stm32_adfsdm_trigger(struct snd_pcm_substream *substream, int cmd)
171{
172 struct snd_soc_pcm_runtime *rtd = substream->private_data;
173 struct stm32_adfsdm_priv *priv =
174 snd_soc_dai_get_drvdata(rtd->cpu_dai);
175
176 switch (cmd) {
177 case SNDRV_PCM_TRIGGER_START:
178 case SNDRV_PCM_TRIGGER_RESUME:
179 priv->pos = 0;
180 return stm32_dfsdm_get_buff_cb(priv->iio_ch->indio_dev,
181 stm32_afsdm_pcm_cb, priv);
182 case SNDRV_PCM_TRIGGER_SUSPEND:
183 case SNDRV_PCM_TRIGGER_STOP:
184 return stm32_dfsdm_release_buff_cb(priv->iio_ch->indio_dev);
185 }
186
187 return -EINVAL;
188}
189
190static int stm32_adfsdm_pcm_open(struct snd_pcm_substream *substream)
191{
192 struct snd_soc_pcm_runtime *rtd = substream->private_data;
193 struct stm32_adfsdm_priv *priv = snd_soc_dai_get_drvdata(rtd->cpu_dai);
194 int ret;
195
196 ret = snd_soc_set_runtime_hwparams(substream, &stm32_adfsdm_pcm_hw);
197 if (!ret)
198 priv->substream = substream;
199
200 return ret;
201}
202
203static int stm32_adfsdm_pcm_close(struct snd_pcm_substream *substream)
204{
205 struct snd_soc_pcm_runtime *rtd = substream->private_data;
206 struct stm32_adfsdm_priv *priv =
207 snd_soc_dai_get_drvdata(rtd->cpu_dai);
208
209 snd_pcm_lib_free_pages(substream);
210 priv->substream = NULL;
211
212 return 0;
213}
214
215static snd_pcm_uframes_t stm32_adfsdm_pcm_pointer(
216 struct snd_pcm_substream *substream)
217{
218 struct snd_soc_pcm_runtime *rtd = substream->private_data;
219 struct stm32_adfsdm_priv *priv =
220 snd_soc_dai_get_drvdata(rtd->cpu_dai);
221
222 return bytes_to_frames(substream->runtime, priv->pos);
223}
224
225static int stm32_adfsdm_pcm_hw_params(struct snd_pcm_substream *substream,
226 struct snd_pcm_hw_params *params)
227{
228 struct snd_soc_pcm_runtime *rtd = substream->private_data;
229 struct stm32_adfsdm_priv *priv =
230 snd_soc_dai_get_drvdata(rtd->cpu_dai);
231 int ret;
232
233 ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
234 if (ret < 0)
235 return ret;
236 priv->pcm_buff = substream->runtime->dma_area;
237
238 return iio_channel_cb_set_buffer_watermark(priv->iio_cb,
239 params_period_size(params));
240}
241
242static int stm32_adfsdm_pcm_hw_free(struct snd_pcm_substream *substream)
243{
244 snd_pcm_lib_free_pages(substream);
245
246 return 0;
247}
248
249static struct snd_pcm_ops stm32_adfsdm_pcm_ops = {
250 .open = stm32_adfsdm_pcm_open,
251 .close = stm32_adfsdm_pcm_close,
252 .hw_params = stm32_adfsdm_pcm_hw_params,
253 .hw_free = stm32_adfsdm_pcm_hw_free,
254 .trigger = stm32_adfsdm_trigger,
255 .pointer = stm32_adfsdm_pcm_pointer,
256};
257
258static int stm32_adfsdm_pcm_new(struct snd_soc_pcm_runtime *rtd)
259{
260 struct snd_pcm *pcm = rtd->pcm;
261 struct stm32_adfsdm_priv *priv =
262 snd_soc_dai_get_drvdata(rtd->cpu_dai);
263 unsigned int size = DFSDM_MAX_PERIODS * DFSDM_MAX_PERIOD_SIZE;
264
265 return snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
266 priv->dev, size, size);
267}
268
269static void stm32_adfsdm_pcm_free(struct snd_pcm *pcm)
270{
271 struct snd_pcm_substream *substream;
272 struct snd_soc_pcm_runtime *rtd;
273 struct stm32_adfsdm_priv *priv;
274
275 substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream;
276 if (substream) {
277 rtd = substream->private_data;
278 priv = snd_soc_dai_get_drvdata(rtd->cpu_dai);
279
280 snd_pcm_lib_preallocate_free_for_all(pcm);
281 }
282}
283
284static struct snd_soc_platform_driver stm32_adfsdm_soc_platform = {
285 .ops = &stm32_adfsdm_pcm_ops,
286 .pcm_new = stm32_adfsdm_pcm_new,
287 .pcm_free = stm32_adfsdm_pcm_free,
288};
289
290static const struct of_device_id stm32_adfsdm_of_match[] = {
291 {.compatible = "st,stm32h7-dfsdm-dai"},
292 {}
293};
294MODULE_DEVICE_TABLE(of, stm32_adfsdm_of_match);
295
296static int stm32_adfsdm_probe(struct platform_device *pdev)
297{
298 struct stm32_adfsdm_priv *priv;
299 int ret;
300
301 priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
302 if (!priv)
303 return -ENOMEM;
304
305 priv->dev = &pdev->dev;
306 priv->dai_drv = stm32_adfsdm_dai;
307
308 dev_set_drvdata(&pdev->dev, priv);
309
310 ret = devm_snd_soc_register_component(&pdev->dev,
311 &stm32_adfsdm_dai_component,
312 &priv->dai_drv, 1);
313 if (ret < 0)
314 return ret;
315
316 /* Associate iio channel */
317 priv->iio_ch = devm_iio_channel_get_all(&pdev->dev);
318 if (IS_ERR(priv->iio_ch))
319 return PTR_ERR(priv->iio_ch);
320
321 priv->iio_cb = iio_channel_get_all_cb(&pdev->dev, NULL, NULL);
322 if (IS_ERR(priv->iio_cb))
323 return PTR_ERR(priv->iio_ch);
324
325 ret = devm_snd_soc_register_platform(&pdev->dev,
326 &stm32_adfsdm_soc_platform);
327 if (ret < 0)
328 dev_err(&pdev->dev, "%s: Failed to register PCM platform\n",
329 __func__);
330
331 return ret;
332}
333
334static struct platform_driver stm32_adfsdm_driver = {
335 .driver = {
336 .name = STM32_ADFSDM_DRV_NAME,
337 .of_match_table = stm32_adfsdm_of_match,
338 },
339 .probe = stm32_adfsdm_probe,
340};
341
342module_platform_driver(stm32_adfsdm_driver);
343
344MODULE_DESCRIPTION("stm32 DFSDM DAI driver");
345MODULE_AUTHOR("Arnaud Pouliquen <arnaud.pouliquen@st.com>");
346MODULE_LICENSE("GPL v2");
347MODULE_ALIAS("platform:" STM32_ADFSDM_DRV_NAME);