On Mon, 13 Feb 2017, Arnaud Pouliquen wrote: > Add driver to handle DAI interface for PDM microphones connected > to Digital Filter for Sigma Delta mModulators IP. Modulators more typos below > Signed-off-by: Arnaud Pouliquen <arnaud.pouliquen@xxxxxx> > --- > include/sound/stm32-adfsdm.h | 80 ++++++++++ > sound/soc/Kconfig | 1 + > sound/soc/Makefile | 1 + > sound/soc/stm/Kconfig | 10 ++ > sound/soc/stm/Makefile | 2 + > sound/soc/stm/stm32_adfsdm.c | 365 +++++++++++++++++++++++++++++++++++++++++++ > 6 files changed, 459 insertions(+) > create mode 100644 include/sound/stm32-adfsdm.h > create mode 100644 sound/soc/stm/Kconfig > create mode 100644 sound/soc/stm/Makefile > create mode 100644 sound/soc/stm/stm32_adfsdm.c > > diff --git a/include/sound/stm32-adfsdm.h b/include/sound/stm32-adfsdm.h > new file mode 100644 > index 0000000..ff5899d > --- /dev/null > +++ b/include/sound/stm32-adfsdm.h > @@ -0,0 +1,80 @@ > +/* > + * This file is part of STM32 DFSDM mfd driver API > + * > + * Copyright (C) 2017, STMicroelectronics - All Rights Reserved > + * Author(s): Arnaud Pouliquen <arnaud.pouliquen@xxxxxx>. > + * > + * License terms: GPL V2.0. > + * > + * This program is free software; you can redistribute it and/or modify it > + * under the terms of the GNU General Public License version 2 as published by > + * the Free Software Foundation. > + * > + * This program is distributed in the hope that it will be useful, but > + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or > + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more > + * details. > + */ > +#ifndef STM32_ADFSDM_H > +#define STM32_ADFSDM_H > + > +struct stm32_dfsdm_adc; > + > +/** > + * struct stm32_dfsdm_hw_param - stm32 audio hardware params > + * @rate: sampling rate > + * @sample_bits: sample size in bits > + * @max_scaling: effective scaling in bit computed by iio driver bits? > + */ > +struct stm32_dfsdm_hw_param { > + unsigned int rate; > + unsigned int sample_bits; > + unsigned int *max_scaling; > +}; > + > +/* > + * Potential improvement: > + * Following structure and functions could be generic and declared in > + * an asoc-iio.h > + */ > +struct stm32_adfsdm_codec_ops { > + /* > + * Set the SPI or manchester input Frequency frequency > + * Optional: not use if DFSDM is master on SPI > + */ > + void (*set_sysclk)(struct stm32_dfsdm_adc *adc, unsigned int freq); > + > + /* > + * Set expected audio sampling rate and format. > + * Precision is returned to allow to rescale samples > + */ > + int (*set_hwparam)(struct stm32_dfsdm_adc *adc, > + struct stm32_dfsdm_hw_param *params); > + > + /* Called when ASoC starts an audio stream setup. */ > + int (*audio_startup)(struct stm32_dfsdm_adc *adc); > + > + /* Shuts down the audio stream. */ > + void (*audio_shutdown)(struct stm32_dfsdm_adc *adc); > + > + /* > + * Provides DMA source physicla addr to allow ALsa to handle DMA physical ALSA > + * transfers. > + */ > + dma_addr_t (*get_dma_source)(struct stm32_dfsdm_adc *adc); > + > + /* Register callback to treat overrun issues */ > + void (*register_xrun_cb)(struct stm32_dfsdm_adc *adc, > + void (*overrun_cb)(void *context), > + void *context); > + > +}; > + > +/* stm32 dfsdm initalization data */ > +struct stm32_adfsdm_pdata { > + const struct stm32_adfsdm_codec_ops *ops; > + struct stm32_dfsdm_adc *adc; > +}; > + > +#define STM32_ADFSDM_DRV_NAME "stm32-dfsdm-audio" > +#endif > diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig > index 182d92e..3836ebe 100644 > --- a/sound/soc/Kconfig > +++ b/sound/soc/Kconfig > @@ -63,6 +63,7 @@ source "sound/soc/sh/Kconfig" > source "sound/soc/sirf/Kconfig" > source "sound/soc/spear/Kconfig" > source "sound/soc/sti/Kconfig" > +source "sound/soc/stm/Kconfig" > source "sound/soc/sunxi/Kconfig" > source "sound/soc/tegra/Kconfig" > source "sound/soc/txx9/Kconfig" > diff --git a/sound/soc/Makefile b/sound/soc/Makefile > index 9a30f21..5440cf7 100644 > --- a/sound/soc/Makefile > +++ b/sound/soc/Makefile > @@ -43,6 +43,7 @@ obj-$(CONFIG_SND_SOC) += sh/ > obj-$(CONFIG_SND_SOC) += sirf/ > obj-$(CONFIG_SND_SOC) += spear/ > obj-$(CONFIG_SND_SOC) += sti/ > +obj-$(CONFIG_SND_SOC) += stm/ > obj-$(CONFIG_SND_SOC) += sunxi/ > obj-$(CONFIG_SND_SOC) += tegra/ > obj-$(CONFIG_SND_SOC) += txx9/ > diff --git a/sound/soc/stm/Kconfig b/sound/soc/stm/Kconfig > new file mode 100644 > index 0000000..041ddb9 > --- /dev/null > +++ b/sound/soc/stm/Kconfig > @@ -0,0 +1,10 @@ > +menuconfig SND_SOC_STM32_DFSDM > + tristate "SoC Audio support for STM32 DFSDM" > + depends on (ARCH_STM32 && OF && STM32_DFSDM_ADC) || COMPILE_TEST > + depends on SND_SOC > + select SND_SOC_GENERIC_DMAENGINE_PCM > + select SND_SOC_DMIC > + help > + Select this option to enable the STM32 Digital Filter > + for Sigma Delta Modulators (DFSDM) driver used > + in various STM32 series for digital microphone capture. > \ No newline at end of file > diff --git a/sound/soc/stm/Makefile b/sound/soc/stm/Makefile > new file mode 100644 > index 0000000..ea90240 > --- /dev/null > +++ b/sound/soc/stm/Makefile > @@ -0,0 +1,2 @@ > +#DFSDM > +obj-$(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 0000000..4488461 > --- /dev/null > +++ b/sound/soc/stm/stm32_adfsdm.c > @@ -0,0 +1,365 @@ > +/* > + * This file is part of STM32 DFSDM ASoC DAI driver > + * > + * Copyright (C) 2017, STMicroelectronics - All Rights Reserved > + * Authors: Arnaud Pouliquen <arnaud.pouliquen@xxxxxx> > + * Olivier Moysan <olivier.moysan@xxxxxx> > + * > + * License type: GPLv2 > + * > + * This program is free software; you can redistribute it and/or modify it > + * under the terms of the GNU General Public License version 2 as published by > + * the Free Software Foundation. > + * > + * This program is distributed in the hope that it will be useful, but > + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY > + * or FITNESS FOR A PARTICULAR PURPOSE. > + * See the GNU General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License along with > + * this program. If not, see <http://www.gnu.org/licenses/>. > + */ > + > +#include <linux/clk.h> > +#include <linux/module.h> > +#include <linux/platform_device.h> > +#include <linux/slab.h> > + > +#include <sound/dmaengine_pcm.h> > +#include <sound/stm32-adfsdm.h> > + > +#define STM32_ADFSDM_DATA_MASK GENMASK(31, 8) > + > +struct stm32_adfsdm_priv { > + struct snd_soc_dai_driver dai_drv; > + struct stm32_adfsdm_pdata *pdata; /* platform data set by IIO driver */ > + struct snd_dmaengine_dai_dma_data dma_data; /* dma config */ > + struct snd_pcm_substream *substream; > + struct snd_pcm_hw_constraint_list rates_const; > + unsigned long dmic_clk; /* SPI or manchester input clock frequency */ > + unsigned int fl_id; /* filter instance ID */ > + unsigned int order; /* filter order */ > + unsigned int max_scaling; /* max scaling for audio samples */ > +}; > + > +struct stm32_adfsdm_data { > + unsigned int rate; /* SNDRV_PCM_RATE value */ > + unsigned int freq; /* frequency in Hz */ > +}; > + > +static const struct stm32_adfsdm_data stm32_dfsdm_filter[] = { > + { .rate = SNDRV_PCM_RATE_8000, .freq = 8000 }, > + { .rate = SNDRV_PCM_RATE_16000, .freq = 16000 }, > + { .rate = SNDRV_PCM_RATE_32000, .freq = 32000 }, > +}; > + > +static const struct snd_pcm_hardware stm32_adfsdm_pcm_hw = { > + .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | > + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_MMAP | > + SNDRV_PCM_INFO_MMAP_VALID, > + .formats = SNDRV_PCM_FMTBIT_S24_LE, > + > + .rate_min = 8000, > + .rate_max = 32000, > + > + .channels_min = 1, > + .channels_max = 1, > + > + .periods_min = 2, > + .periods_max = 48, > + > + .period_bytes_min = 40, /* 8 khz 5 ms */ > + .period_bytes_max = 4 * PAGE_SIZE, > + .buffer_bytes_max = 16 * PAGE_SIZE > +}; > + > +static int stm32_adfsdm_get_supported_rates(struct snd_soc_dai *dai, > + unsigned int *rates) > +{ > + struct stm32_adfsdm_priv *priv = snd_soc_dai_get_drvdata(dai); > + struct stm32_adfsdm_pdata *pdata = priv->pdata; > + struct stm32_dfsdm_hw_param params; > + unsigned int max_scaling, i; > + int ret; > + > + *rates = 0; > + > + for (i = 0; i < ARRAY_SIZE(stm32_dfsdm_filter); i++) { > + /* > + * Check that clkout_freq is compatible > + * Try to find one solution for filter and integrator > + * oversampling ratio. > + */ > + > + params.rate = stm32_dfsdm_filter[i].freq; > + params.sample_bits = 24; > + params.max_scaling = &max_scaling; > + > + ret = pdata->ops->set_hwparam(pdata->adc, ¶ms); > + if (!ret) { > + *rates |= 1 << i; > + dev_err(dai->dev, "%s: %d rate supported\n", __func__, > + stm32_dfsdm_filter[i].freq); > + } > + } > + > + if (!*rates) { > + dev_err(dai->dev, "%s: no matched rate found\n", __func__); > + return -EINVAL; > + } > + > + return 0; > +} > + > +static int stm32_adfsdm_copy(struct snd_pcm_substream *substream, int channel, > + snd_pcm_uframes_t pos, > + void __user *buf, snd_pcm_uframes_t count) > +{ > + struct snd_pcm_runtime *runtime = substream->runtime; > + struct snd_soc_pcm_runtime *rtd = substream->private_data; > + struct stm32_adfsdm_priv *priv = snd_soc_dai_get_drvdata(rtd->cpu_dai); > + int *ptr = (int *)(runtime->dma_area + frames_to_bytes(runtime, pos)); > + char *hwbuf = runtime->dma_area + frames_to_bytes(runtime, pos); > + ssize_t bytes = frames_to_bytes(runtime, count); > + ssize_t sample_cnt = bytes_to_samples(runtime, bytes); > + unsigned int shift = 24 -priv->max_scaling; 24 - priv->max_scaling > + > + /* > + * Audio samples are available on 24 MSBs of the DFSDM DATAR register. > + * We need to mask 8 LSB control bits... > + * Additionnaly sample scaling depends on decimation and can need shift Additionally > + * to be aligned on 32-bit word MSB. > + */ > + if (shift > 0) { > + do { > + *ptr <<= shift & STM32_ADFSDM_DATA_MASK; > + ptr++; > + } while (--sample_cnt); > + } else { > + do { > + *ptr &= STM32_ADFSDM_DATA_MASK; > + ptr++; > + } while (--sample_cnt); > + } > + > + return copy_to_user(buf, hwbuf, bytes); > +} > + > +static void stm32_dfsdm_xrun(void *context) > +{ > + struct snd_soc_dai *dai = context; > + struct stm32_adfsdm_priv *priv = snd_soc_dai_get_drvdata(dai); > + > + snd_pcm_stream_lock(priv->substream); > + dev_dbg(dai->dev, "%s:unexpected overrun\n", __func__); newline > + /* Stop the player */ > + snd_pcm_stop(priv->substream, SNDRV_PCM_STATE_XRUN); > + snd_pcm_stream_unlock(priv->substream); > +} > + > +static int stm32_adfsdm_startup(struct snd_pcm_substream *substream, > + struct snd_soc_dai *dai) > +{ > + struct stm32_adfsdm_priv *priv = snd_soc_dai_get_drvdata(dai); > + > + priv->substream = substream; > + > + dev_dbg(dai->dev, "%s: enter\n", __func__); > + return 0; ???? > + return snd_pcm_hw_constraint_list(substream->runtime, 0, > + SNDRV_PCM_HW_PARAM_RATE, > + &priv->rates_const); > +} > + > +static void stm32_adfsdm_shutdown(struct snd_pcm_substream *substream, > + struct snd_soc_dai *dai) > +{ > + struct stm32_adfsdm_priv *priv = snd_soc_dai_get_drvdata(dai); > + > + dev_dbg(dai->dev, "%s: enter\n", __func__); > + priv->substream = NULL; > +} > + > +static int stm32_adfsdm_dai_hw_params(struct snd_pcm_substream *substream, > + struct snd_pcm_hw_params *params, > + struct snd_soc_dai *dai) > +{ > + struct snd_dmaengine_dai_dma_data *dma_data; > + struct stm32_adfsdm_priv *priv = snd_soc_dai_get_drvdata(dai); > + struct stm32_adfsdm_pdata *pdata = priv->pdata; > + struct stm32_dfsdm_hw_param df_params; > + > + dev_dbg(dai->dev, "%s: enter\n", __func__); > + dma_data = snd_soc_dai_get_dma_data(dai, substream); > + dma_data->maxburst = 1; > + > + df_params.rate = substream->runtime->rate; > + df_params.sample_bits = substream->runtime->sample_bits; > + df_params.max_scaling = &priv->max_scaling; > + > + return pdata->ops->set_hwparam(pdata->adc, &df_params); > +} > + > +static int stm32_adfsdm_trigger(struct snd_pcm_substream *substream, int cmd, > + struct snd_soc_dai *dai) > +{ > + struct stm32_adfsdm_priv *priv = snd_soc_dai_get_drvdata(dai); > + struct stm32_adfsdm_pdata *pdata = priv->pdata; > + > + dev_dbg(dai->dev, "%s: enter\n", __func__); > + > + switch (cmd) { > + case SNDRV_PCM_TRIGGER_START: > + case SNDRV_PCM_TRIGGER_RESUME: > + return pdata->ops->audio_startup(pdata->adc); > + case SNDRV_PCM_TRIGGER_SUSPEND: > + case SNDRV_PCM_TRIGGER_STOP: > + pdata->ops->audio_shutdown(pdata->adc); > + break; > + default: > + return -EINVAL; > + } > + > + return 0; > +} > + > +static int stm32_adfsdm_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) > +{ > + struct stm32_adfsdm_priv *priv = snd_soc_dai_get_drvdata(dai); > + unsigned int cb = fmt & SND_SOC_DAIFMT_MASTER_MASK; > + > + dev_dbg(dai->dev, "%s: enter\n", __func__); > + > + if ((cb == SND_SOC_DAIFMT_CBM_CFM) || (cb == SND_SOC_DAIFMT_CBM_CFS)) { > + /* Digital microphone is clocked by external clock */ > + if (!priv->dmic_clk) { > + dev_err(dai->dev, > + "system-clock-frequency not defined\n"); > + return -EINVAL; > + } > + } > + > + return 0; > +} > + > +static int stm32_adfsdm_set_sysclk(struct snd_soc_dai *dai, int clk_id, > + unsigned int freq, int dir) > +{ > + struct stm32_adfsdm_priv *priv = snd_soc_dai_get_drvdata(dai); > + struct stm32_adfsdm_pdata *pdata = priv->pdata; > + > + dev_dbg(dai->dev, "%s: enter for dai %d\n", __func__, dai->id); > + if (dir == SND_SOC_CLOCK_IN) { > + pdata->ops->set_sysclk(pdata->adc, freq); > + priv->dmic_clk = freq; > + } > + > + /* Determine supported rate which depends on SPI/manchester clock */ > + return stm32_adfsdm_get_supported_rates(dai, &priv->rates_const.mask); > +} > + > +static const struct snd_soc_dai_ops stm32_adfsdm_dai_ops = { > + .startup = stm32_adfsdm_startup, > + .shutdown = stm32_adfsdm_shutdown, > + .hw_params = stm32_adfsdm_dai_hw_params, > + .set_fmt = stm32_adfsdm_set_dai_fmt, > + .set_sysclk = stm32_adfsdm_set_sysclk, > + .trigger = stm32_adfsdm_trigger, > +}; > + > +static int stm32_adfsdm_dai_probe(struct snd_soc_dai *dai) > +{ > + struct stm32_adfsdm_priv *priv = snd_soc_dai_get_drvdata(dai); > + struct snd_dmaengine_dai_dma_data *dma = &priv->dma_data; > + struct stm32_adfsdm_pdata *pdata = priv->pdata; > + > + dev_dbg(dai->dev, "%s: enter for dai %d\n", __func__, dai->id); > + > + /* DMA settings */ > + snd_soc_dai_init_dma_data(dai, NULL, dma); > + dma->addr = pdata->ops->get_dma_source(pdata->adc); > + dma->addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; > + > + pdata->ops->register_xrun_cb(priv->pdata->adc, stm32_dfsdm_xrun, dai); > + > + return 0; > +} > + > +static int stm32_adfsdm_dai_remove(struct snd_soc_dai *dai) > +{ > + dev_dbg(dai->dev, "%s: enter for dai %d\n", __func__, dai->id); > + > + return 0; > +} > + > +static const struct snd_soc_dai_driver stm32_adfsdm_dai = { > + .capture = { > + .channels_min = 1, > + .channels_max = 1, > + .formats = SNDRV_PCM_FMTBIT_S24_LE, > + .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | > + SNDRV_PCM_RATE_32000), > + }, > + .probe = stm32_adfsdm_dai_probe, > + .remove = stm32_adfsdm_dai_remove, > + .ops = &stm32_adfsdm_dai_ops, > +}; > + > +static const struct snd_soc_component_driver stm32_adfsdm_dai_component = { > + .name = "sti_cpu_dai", > +}; > + > +static const struct snd_dmaengine_pcm_config dmaengine_pcm_config = { > + .pcm_hardware = &stm32_adfsdm_pcm_hw, > + .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config, > + .copy = stm32_adfsdm_copy, > +}; > + > +static int stm32_adfsdm_probe(struct platform_device *pdev) > +{ > + struct stm32_adfsdm_priv *priv; > + struct stm32_adfsdm_pdata *pdata = pdev->dev.platform_data; > + int ret; > + > + dev_dbg(&pdev->dev, "%s: enter for node %p\n", __func__, > + pdev->dev.parent->of_node->name); > + > + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); > + if (!priv) > + return -ENOMEM; > + > + priv->pdata = pdata; > + > + priv->dai_drv = stm32_adfsdm_dai; > + priv->dai_drv.name = pdev->dev.parent->of_node->name; > + priv->dai_drv.capture.stream_name = pdev->dev.parent->of_node->name; > + > + dev_set_drvdata(&pdev->dev, priv); > + > + ret = devm_snd_soc_register_component(&pdev->dev, > + &stm32_adfsdm_dai_component, > + &priv->dai_drv, 1); > + if (ret < 0) > + return ret; > + > + ret = devm_snd_dmaengine_pcm_register(pdev->dev.parent, > + &dmaengine_pcm_config, 0); > + if (ret < 0) > + dev_err(&pdev->dev, "failed to register dma pcm config\n"); > + > + return ret; > +} > + > +static struct platform_driver stm32_adfsdm_driver = { > + .driver = { > + .name = STM32_ADFSDM_DRV_NAME, > + }, > + .probe = stm32_adfsdm_probe, > +}; > + > +module_platform_driver(stm32_adfsdm_driver); > + > +MODULE_DESCRIPTION("stm32 DFSDM DAI driver"); > +MODULE_AUTHOR("Arnaud Pouliquen <arnaud.pouliquen@xxxxxx>"); > +MODULE_LICENSE("GPL v2"); > +MODULE_ALIAS("platform:" STM32_ADFSDM_DRV_NAME); > -- Peter Meerwald-Stadler +43-664-2444418 (mobile) -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html