Add driver to handle DAI interface for PDM microphones connected to Digital Filter for Sigma Delta mModulators IP. 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 + */ +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 + * 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 + * 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; + + /* + * 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 + * 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__); + /* 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); -- 1.9.1 -- To unsubscribe from this list: send the line "unsubscribe linux-iio" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html