On 12/02/2017 04:09 PM, Jonathan Cameron wrote: > On Fri, 1 Dec 2017 18:40:20 +0100 > Arnaud Pouliquen <arnaud.pouliquen@xxxxxx> wrote: > >> 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@xxxxxx> > > include tied up with the move of that enum in the earlier patch. > Other than that the IIO stuff looks fine to me. > > Mark, given this set is moderately invasive on the IIO side and > should just drop in cleanly on the sound side of things, either > I could take it via IIO or one of us can do an immutable branch > and we take it through both trees. Don't know if you saw the reply from Mark on V5: "This is basically fine, if someone could send me a pull request and the relevant patches when the IIO stuff is sorted out I'll give it a final check and apply then." > > Don't mind which but if I'm either taking the series or doing > an immutable branch I'll obviously be waiting on your review! > > Also need time for Rob to check the updating bindings. > > Anyhow, it's coming together reasonably nicely in the end. > Good work Arnaud! Thanks! it will be a good achievement for me, not trivial this peripheral to integrate :) I'm waiting Rob's ack or remarks on patch 12/13 to send the next version. Thanks and Regards Arnaud > > Jonathan >> --- >> V5 to V6 update: >> fix build warning >> >> sound/soc/stm/Kconfig | 11 ++ >> sound/soc/stm/Makefile | 3 + >> sound/soc/stm/stm32_adfsdm.c | 386 +++++++++++++++++++++++++++++++++++++++++++ >> 3 files changed, 400 insertions(+) >> create mode 100644 sound/soc/stm/stm32_adfsdm.c >> >> diff --git a/sound/soc/stm/Kconfig b/sound/soc/stm/Kconfig >> index 3398e6c..a78f770 100644 >> --- a/sound/soc/stm/Kconfig >> +++ b/sound/soc/stm/Kconfig >> @@ -28,4 +28,15 @@ config SND_SOC_STM32_SPDIFRX >> help >> Say Y if you want to enable S/PDIF capture for STM32 >> >> +config 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 >> + select IIO_BUFFER_CB >> + 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. >> endmenu >> diff --git a/sound/soc/stm/Makefile b/sound/soc/stm/Makefile >> index 4ed22e6..53e90e6 100644 >> --- a/sound/soc/stm/Makefile >> +++ b/sound/soc/stm/Makefile >> @@ -12,3 +12,6 @@ obj-$(CONFIG_SND_SOC_STM32_I2S) += snd-soc-stm32-i2s.o >> # SPDIFRX >> snd-soc-stm32-spdifrx-objs := stm32_spdifrx.o >> obj-$(CONFIG_SND_SOC_STM32_SPDIFRX) += snd-soc-stm32-spdifrx.o >> + >> +#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..890ec24 >> --- /dev/null >> +++ b/sound/soc/stm/stm32_adfsdm.c >> @@ -0,0 +1,386 @@ >> +/* >> + * 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 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. >> + * >> + * 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 <linux/iio/iio.h> > > I missed this before but a consumer should not need to include iio.h. > I would guess it was about that enum that I want you to move to types.h? > >> +#include <linux/iio/consumer.h> >> +#include <linux/iio/adc/stm32-dfsdm-adc.h> >> + >> +#include <sound/pcm.h> >> +#include <sound/soc.h> >> + >> +#define STM32_ADFSDM_DRV_NAME "stm32-adfsdm" >> + >> +#define DFSDM_MAX_PERIOD_SIZE (PAGE_SIZE / 2) >> +#define DFSDM_MAX_PERIODS 6 >> + >> +struct stm32_adfsdm_priv { >> + struct snd_soc_dai_driver dai_drv; >> + struct snd_pcm_substream *substream; >> + struct device *dev; >> + >> + /* IIO */ >> + struct iio_channel *iio_ch; >> + struct iio_cb_buffer *iio_cb; >> + bool iio_active; >> + >> + /* PCM buffer */ >> + unsigned char *pcm_buff; >> + unsigned int pos; >> + bool allocated; >> +}; >> + >> +struct stm32_adfsdm_data { >> + unsigned int rate; /* SNDRV_PCM_RATE value */ >> + unsigned int freq; /* frequency in Hz */ >> +}; >> + >> +static const struct snd_pcm_hardware stm32_adfsdm_pcm_hw = { >> + .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | >> + SNDRV_PCM_INFO_PAUSE, >> + .formats = SNDRV_PCM_FMTBIT_S32_LE, >> + >> + .rate_min = 8000, >> + .rate_max = 32000, >> + >> + .channels_min = 1, >> + .channels_max = 1, >> + >> + .periods_min = 2, >> + .periods_max = DFSDM_MAX_PERIODS, >> + >> + .period_bytes_max = DFSDM_MAX_PERIOD_SIZE, >> + .buffer_bytes_max = DFSDM_MAX_PERIODS * DFSDM_MAX_PERIOD_SIZE >> +}; >> + >> +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); >> + >> + if (priv->iio_active) { >> + iio_channel_stop_all_cb(priv->iio_cb); >> + priv->iio_active = false; >> + } >> +} >> + >> +static int stm32_adfsdm_dai_prepare(struct snd_pcm_substream *substream, >> + struct snd_soc_dai *dai) >> +{ >> + struct stm32_adfsdm_priv *priv = snd_soc_dai_get_drvdata(dai); >> + int ret; >> + >> + ret = iio_write_channel_attribute(priv->iio_ch, >> + substream->runtime->rate, 0, >> + IIO_CHAN_INFO_SAMP_FREQ); >> + if (ret < 0) { >> + dev_err(dai->dev, "%s: Failed to set %d sampling rate\n", >> + __func__, substream->runtime->rate); >> + return ret; >> + } >> + >> + if (!priv->iio_active) { >> + ret = iio_channel_start_all_cb(priv->iio_cb); >> + if (!ret) >> + priv->iio_active = true; >> + else >> + dev_err(dai->dev, "%s: IIO channel start failed (%d)\n", >> + __func__, ret); >> + } >> + >> + return ret; >> +} >> + >> +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); >> + ssize_t size; >> + >> + dev_dbg(dai->dev, "%s: Enter for freq %d\n", __func__, freq); >> + >> + /* Set IIO frequency if CODEC is master as clock comes from SPI_IN*/ >> + if (dir == SND_SOC_CLOCK_IN) { >> + char str_freq[10]; >> + >> + snprintf(str_freq, sizeof(str_freq), "%d\n", freq); >> + size = iio_write_channel_ext_info(priv->iio_ch, "spi_clk_freq", >> + str_freq, sizeof(str_freq)); >> + if (size != sizeof(str_freq)) { >> + dev_err(dai->dev, "%s: Failed to set SPI clock\n", >> + __func__); >> + return -EINVAL; >> + } >> + } >> + return 0; >> +} >> + >> +static const struct snd_soc_dai_ops stm32_adfsdm_dai_ops = { >> + .shutdown = stm32_adfsdm_shutdown, >> + .prepare = stm32_adfsdm_dai_prepare, >> + .set_sysclk = stm32_adfsdm_set_sysclk, >> +}; >> + >> +static const struct snd_soc_dai_driver stm32_adfsdm_dai = { >> + .capture = { >> + .channels_min = 1, >> + .channels_max = 1, >> + .formats = SNDRV_PCM_FMTBIT_S32_LE, >> + .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | >> + SNDRV_PCM_RATE_32000), >> + }, >> + .ops = &stm32_adfsdm_dai_ops, >> +}; >> + >> +static const struct snd_soc_component_driver stm32_adfsdm_dai_component = { >> + .name = "stm32_dfsdm_audio", >> +}; >> + >> +static int stm32_afsdm_pcm_cb(const void *data, size_t size, void *private) >> +{ >> + struct stm32_adfsdm_priv *priv = private; >> + struct snd_soc_pcm_runtime *rtd = priv->substream->private_data; >> + u8 *pcm_buff = priv->pcm_buff; >> + u8 *src_buff = (u8 *)data; >> + unsigned int buff_size = snd_pcm_lib_buffer_bytes(priv->substream); >> + unsigned int period_size = snd_pcm_lib_period_bytes(priv->substream); >> + unsigned int old_pos = priv->pos; >> + unsigned int cur_size = size; >> + >> + dev_dbg(rtd->dev, "%s: buff_add :%p, pos = %d, size = %lu\n", >> + __func__, &pcm_buff[priv->pos], priv->pos, size); >> + >> + if ((priv->pos + size) > buff_size) { >> + memcpy(&pcm_buff[priv->pos], src_buff, buff_size - priv->pos); >> + cur_size -= buff_size - priv->pos; >> + priv->pos = 0; >> + } >> + >> + memcpy(&pcm_buff[priv->pos], &src_buff[size - cur_size], cur_size); >> + priv->pos = (priv->pos + cur_size) % buff_size; >> + >> + if (cur_size != size || (old_pos && (old_pos % period_size < size))) >> + snd_pcm_period_elapsed(priv->substream); >> + >> + return 0; >> +} >> + >> +static int stm32_adfsdm_trigger(struct snd_pcm_substream *substream, int cmd) >> +{ >> + struct snd_soc_pcm_runtime *rtd = substream->private_data; >> + struct stm32_adfsdm_priv *priv = >> + snd_soc_dai_get_drvdata(rtd->cpu_dai); >> + >> + switch (cmd) { >> + case SNDRV_PCM_TRIGGER_START: >> + case SNDRV_PCM_TRIGGER_RESUME: >> + priv->pos = 0; >> + return stm32_dfsdm_get_buff_cb(priv->iio_ch->indio_dev, >> + stm32_afsdm_pcm_cb, priv); >> + case SNDRV_PCM_TRIGGER_SUSPEND: >> + case SNDRV_PCM_TRIGGER_STOP: >> + return stm32_dfsdm_release_buff_cb(priv->iio_ch->indio_dev); >> + default: >> + return -EINVAL; >> + } >> + >> + return 0; >> +} >> + >> +static int stm32_adfsdm_pcm_open(struct snd_pcm_substream *substream) >> +{ >> + struct snd_soc_pcm_runtime *rtd = substream->private_data; >> + struct stm32_adfsdm_priv *priv = snd_soc_dai_get_drvdata(rtd->cpu_dai); >> + int ret; >> + >> + ret = snd_soc_set_runtime_hwparams(substream, &stm32_adfsdm_pcm_hw); >> + if (!ret) >> + priv->substream = substream; >> + >> + return ret; >> +} >> + >> +static int stm32_adfsdm_pcm_close(struct snd_pcm_substream *substream) >> +{ >> + struct snd_soc_pcm_runtime *rtd = substream->private_data; >> + struct stm32_adfsdm_priv *priv = >> + snd_soc_dai_get_drvdata(rtd->cpu_dai); >> + >> + snd_pcm_lib_free_pages(substream); >> + priv->substream = NULL; >> + >> + return 0; >> +} >> + >> +static snd_pcm_uframes_t stm32_adfsdm_pcm_pointer( >> + struct snd_pcm_substream *substream) >> +{ >> + struct snd_soc_pcm_runtime *rtd = substream->private_data; >> + struct stm32_adfsdm_priv *priv = >> + snd_soc_dai_get_drvdata(rtd->cpu_dai); >> + >> + return bytes_to_frames(substream->runtime, priv->pos); >> +} >> + >> +static int stm32_adfsdm_pcm_hw_params(struct snd_pcm_substream *substream, >> + struct snd_pcm_hw_params *params) >> +{ >> + struct snd_soc_pcm_runtime *rtd = substream->private_data; >> + struct stm32_adfsdm_priv *priv = >> + snd_soc_dai_get_drvdata(rtd->cpu_dai); >> + int ret; >> + >> + ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); >> + if (ret < 0) >> + return ret; >> + priv->pcm_buff = substream->runtime->dma_area; >> + >> + return iio_channel_cb_set_buffer_watermark(priv->iio_cb, >> + params_period_size(params)); >> +} >> + >> +static int stm32_adfsdm_pcm_hw_free(struct snd_pcm_substream *substream) >> +{ >> + snd_pcm_lib_free_pages(substream); >> + >> + return 0; >> +} >> + >> +static struct snd_pcm_ops stm32_adfsdm_pcm_ops = { >> + .open = stm32_adfsdm_pcm_open, >> + .close = stm32_adfsdm_pcm_close, >> + .hw_params = stm32_adfsdm_pcm_hw_params, >> + .hw_free = stm32_adfsdm_pcm_hw_free, >> + .trigger = stm32_adfsdm_trigger, >> + .pointer = stm32_adfsdm_pcm_pointer, >> +}; >> + >> +static int stm32_adfsdm_pcm_new(struct snd_soc_pcm_runtime *rtd) >> +{ >> + struct snd_pcm *pcm = rtd->pcm; >> + struct stm32_adfsdm_priv *priv = >> + snd_soc_dai_get_drvdata(rtd->cpu_dai); >> + unsigned int size = DFSDM_MAX_PERIODS * DFSDM_MAX_PERIOD_SIZE; >> + int ret; >> + >> + /* >> + * FIXME : >> + * A platform as been registered per DAI. >> + * In soc_new_pcm function, pcm_new callback is called for each >> + * component of the sound card. So if n dai links are created this >> + * function is called n times. >> + */ >> + if (priv->allocated) >> + return 0; >> + >> + ret = snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, >> + priv->dev, size, size); >> + if (!ret) >> + priv->allocated = true; >> + >> + return ret; >> +} >> + >> +static void stm32_adfsdm_pcm_free(struct snd_pcm *pcm) >> +{ >> + struct snd_pcm_substream *substream; >> + struct snd_soc_pcm_runtime *rtd; >> + struct stm32_adfsdm_priv *priv; >> + >> + substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; >> + if (substream) { >> + rtd = substream->private_data; >> + priv = snd_soc_dai_get_drvdata(rtd->cpu_dai); >> + >> + snd_pcm_lib_preallocate_free_for_all(pcm); >> + priv->allocated = false; >> + } >> +} >> + >> +static struct snd_soc_platform_driver stm32_adfsdm_soc_platform = { >> + .ops = &stm32_adfsdm_pcm_ops, >> + .pcm_new = stm32_adfsdm_pcm_new, >> + .pcm_free = stm32_adfsdm_pcm_free, >> +}; >> + >> +static const struct of_device_id stm32_adfsdm_of_match[] = { >> + {.compatible = "st,stm32h7-dfsdm-dai"}, >> + {} >> +}; >> +MODULE_DEVICE_TABLE(of, stm32_adfsdm_of_match); >> + >> +static int stm32_adfsdm_probe(struct platform_device *pdev) >> +{ >> + struct stm32_adfsdm_priv *priv; >> + int ret; >> + >> + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); >> + if (!priv) >> + return -ENOMEM; >> + >> + priv->dev = &pdev->dev; >> + priv->dai_drv = stm32_adfsdm_dai; >> + >> + 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; >> + >> + /* Associate iio channel */ >> + priv->iio_ch = devm_iio_channel_get_all(&pdev->dev); >> + if (IS_ERR(priv->iio_ch)) >> + return PTR_ERR(priv->iio_ch); >> + >> + priv->iio_cb = iio_channel_get_all_cb(&pdev->dev, NULL, NULL); >> + if (IS_ERR(priv->iio_cb)) >> + return PTR_ERR(priv->iio_ch); >> + >> + ret = devm_snd_soc_register_platform(&pdev->dev, >> + &stm32_adfsdm_soc_platform); >> + if (ret < 0) >> + dev_err(&pdev->dev, "%s: Failed to register PCM platform\n", >> + __func__); >> + >> + return ret; >> +} >> + >> +static struct platform_driver stm32_adfsdm_driver = { >> + .driver = { >> + .name = STM32_ADFSDM_DRV_NAME, >> + .of_match_table = stm32_adfsdm_of_match, >> + }, >> + .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); > -- 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