diff mbox

[RFC,v2,5/7] ASoC: stm32: add DFSDM DAI support

Message ID 1487003909-11710-6-git-send-email-arnaud.pouliquen@st.com (mailing list archive)
State New, archived
Headers show

Commit Message

Arnaud POULIQUEN Feb. 13, 2017, 4:38 p.m. UTC
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@st.com>
---
 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

Comments

Mark Brown Feb. 14, 2017, 5:45 p.m. UTC | #1
On Mon, Feb 13, 2017 at 05:38:27PM +0100, Arnaud Pouliquen wrote:

This looks basically fine as a system specific driver but as some of the
comments in here say there's bits of it could perhaps be genericised but
I'm not sure we need to do that right now.  I'm not sure the abstraction
is exactly comfortable but having another bit of hardware doing a bridge
to IIO might be the best way to figure out something better.

> +	.period_bytes_min = 40, /* 8 khz 5 ms */
> +	.period_bytes_max = 4 * PAGE_SIZE,
> +	.buffer_bytes_max = 16 * PAGE_SIZE

What's the physical minimum period limit?  The comment makes this sound
like it's just made up.

> +	unsigned int shift = 24 -priv->max_scaling;
> +	

Missing space after -.

> +	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);

Looks like debug changes got left in?

> +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);

Since the DAI is unidirectional it doesn't matter but obviously if it
weren't then the fact that getting the supported rates involves setting
the hwparams means this could become disruptive.  If we're going to
genericise this to be a more general IIO/ASoC bridge that could matter.

> +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;
> +}

Remove empty functions, though in this case I think you want to add
something to disconnect the XRUN callback just in order to be sure it
can't be mistakenly called.
Arnaud POULIQUEN Feb. 15, 2017, 4:39 p.m. UTC | #2
On 02/14/2017 06:45 PM, Mark Brown wrote:
> On Mon, Feb 13, 2017 at 05:38:27PM +0100, Arnaud Pouliquen wrote:
> 
> This looks basically fine as a system specific driver but as some 
> of the comments in here say there's bits of it could perhaps be 
> genericised but I'm not sure we need to do that right now.  I'm not
> sure the abstraction is exactly comfortable but having another bit
> of hardware doing a bridge to IIO might be the best way to figure
> out something better.
> 
Yes this is one point to clarify. I keep it as a specific API, as i
don't know if another hardware needs to support it...
I would say that objective of this version is to highlight interactions.
Then i can rework it in a way that could be  genericised in future (
i.e. using void* for params that would depend on platforms)...

Extend IIO customer API would be also another alternative, but i did
not find a generic way to do it. Some IIO attributes could be used for
Hw params but DMA and IRQs seems tricky to handle through this IIO
interface...




>> +	.period_bytes_min = 40, /* 8 khz 5 ms */ +	.period_bytes_max =
>>  4 * PAGE_SIZE, +	.buffer_bytes_max = 16 * PAGE_SIZE
> 
> What's the physical minimum period limit?  The comment makes this 
> sound like it's just made up.
I did not find physical minimum period limit, that why i considered
this value from a scheduling point of view. I will re-check these values.

> 
>> +	unsigned int shift = 24 -priv->max_scaling; +
> 
> Missing space after -.
> 
>> +	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);
> 
> Looks like debug changes got left in?
yes exactly...
> 
>> +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);
> 
> Since the DAI is unidirectional it doesn't matter but obviously if
>  it weren't then the fact that getting the supported rates involves
>  setting the hwparams means this could become disruptive.  If we're
>  going to genericise this to be a more general IIO/ASoC bridge that
>  could matter.
I think that the driver itself can not be generic. ST DFSDM is too
specific. Only API with IIO could be.

> 
>> +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; +}
> 
> Remove empty functions, though in this case I think you want to
> add something to disconnect the XRUN callback just in order to be
> sure it can't be mistakenly called.

Yes Completely unnecessary here. Furthermore the driver should be
removed by the IIO driver.

Regards
Arnaud
Mark Brown Feb. 15, 2017, 4:53 p.m. UTC | #3
On Wed, Feb 15, 2017 at 05:39:53PM +0100, Arnaud Pouliquen wrote:
> On 02/14/2017 06:45 PM, Mark Brown wrote:

> > What's the physical minimum period limit?  The comment makes this 
> > sound like it's just made up.

> I did not find physical minimum period limit, that why i considered
> this value from a scheduling point of view. I will re-check these values.

If there's no physical limit the minimum is one byte, it's the
application's problem if it's not actually able to sustain what it
chooses.
Jonathan Cameron Feb. 19, 2017, 2:56 p.m. UTC | #4
On 14/02/17 17:45, Mark Brown wrote:
> On Mon, Feb 13, 2017 at 05:38:27PM +0100, Arnaud Pouliquen wrote:
> 
> This looks basically fine as a system specific driver but as some of the
> comments in here say there's bits of it could perhaps be genericised but
> I'm not sure we need to do that right now.  I'm not sure the abstraction
> is exactly comfortable but having another bit of hardware doing a bridge
> to IIO might be the best way to figure out something better.
Agreed.  To an extent we are fishing around in the dark at the moment.
Lets wait until we have a few more cases of similar hardware before trying
too much generalization.  This is acting as a good exploration of what
is needed.

Ideally Lars might upstream some of the other bits he has in his tree
to do with DSP processing on ADC streams and that might provide us with
more clues on generality (at least at the lowest layers)
(Apparently he's busy - though he always makes that excuse :)
Joking aside, the exploration Analog and in particular Lars does around
pushing the limits of how things interact is always useful!)

> 
>> +	.period_bytes_min = 40, /* 8 khz 5 ms */
>> +	.period_bytes_max = 4 * PAGE_SIZE,
>> +	.buffer_bytes_max = 16 * PAGE_SIZE
> 
> What's the physical minimum period limit?  The comment makes this sound
> like it's just made up.
> 
>> +	unsigned int shift = 24 -priv->max_scaling;
>> +	
> 
> Missing space after -.
> 
>> +	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);
> 
> Looks like debug changes got left in?
> 
>> +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);
> 
> Since the DAI is unidirectional it doesn't matter but obviously if it
> weren't then the fact that getting the supported rates involves setting
> the hwparams means this could become disruptive.  If we're going to
> genericise this to be a more general IIO/ASoC bridge that could matter.
> 
>> +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;
>> +}
> 
> Remove empty functions, though in this case I think you want to add
> something to disconnect the XRUN callback just in order to be sure it
> can't be mistakenly called.
>
Arnaud POULIQUEN Feb. 27, 2017, 10:31 a.m. UTC | #5
On 02/19/2017 03:56 PM, Jonathan Cameron wrote:
> On 14/02/17 17:45, Mark Brown wrote:
>> On Mon, Feb 13, 2017 at 05:38:27PM +0100, Arnaud Pouliquen wrote:
>>
>> This looks basically fine as a system specific driver but as some of the
>> comments in here say there's bits of it could perhaps be genericised but
>> I'm not sure we need to do that right now.  I'm not sure the abstraction
>> is exactly comfortable but having another bit of hardware doing a bridge
>> to IIO might be the best way to figure out something better.
> Agreed.  To an extent we are fishing around in the dark at the moment.
> Lets wait until we have a few more cases of similar hardware before trying
> too much generalization.  This is acting as a good exploration of what
> is needed.

So for now i keep like this the API between ASOC and IIO, means not
generic API, and DMA handled in ASOC?

Then when some other hardwares come with same kind of requirements, we
will re-discuss a more generic way to do it...

> 
> Ideally Lars might upstream some of the other bits he has in his tree
> to do with DSP processing on ADC streams and that might provide us with
> more clues on generality (at least at the lowest layers)
> (Apparently he's busy - though he always makes that excuse :)
> Joking aside, the exploration Analog and in particular Lars does around
> pushing the limits of how things interact is always useful!)
> 
>>
>>> +	.period_bytes_min = 40, /* 8 khz 5 ms */
>>> +	.period_bytes_max = 4 * PAGE_SIZE,
>>> +	.buffer_bytes_max = 16 * PAGE_SIZE
>>
>> What's the physical minimum period limit?  The comment makes this sound
>> like it's just made up.
>>
>>> +	unsigned int shift = 24 -priv->max_scaling;
>>> +	
>>
>> Missing space after -.
>>
>>> +	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);
>>
>> Looks like debug changes got left in?
>>
>>> +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);
>>
>> Since the DAI is unidirectional it doesn't matter but obviously if it
>> weren't then the fact that getting the supported rates involves setting
>> the hwparams means this could become disruptive.  If we're going to
>> genericise this to be a more general IIO/ASoC bridge that could matter.
>>
>>> +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;
>>> +}
>>
>> Remove empty functions, though in this case I think you want to add
>> something to disconnect the XRUN callback just in order to be sure it
>> can't be mistakenly called.
>>
>
Jonathan Cameron March 5, 2017, 10:55 a.m. UTC | #6
On 27/02/17 10:31, Arnaud Pouliquen wrote:
> 
> 
> On 02/19/2017 03:56 PM, Jonathan Cameron wrote:
>> On 14/02/17 17:45, Mark Brown wrote:
>>> On Mon, Feb 13, 2017 at 05:38:27PM +0100, Arnaud Pouliquen wrote:
>>>
>>> This looks basically fine as a system specific driver but as some of the
>>> comments in here say there's bits of it could perhaps be genericised but
>>> I'm not sure we need to do that right now.  I'm not sure the abstraction
>>> is exactly comfortable but having another bit of hardware doing a bridge
>>> to IIO might be the best way to figure out something better.
>> Agreed.  To an extent we are fishing around in the dark at the moment.
>> Lets wait until we have a few more cases of similar hardware before trying
>> too much generalization.  This is acting as a good exploration of what
>> is needed.
> 
> So for now i keep like this the API between ASOC and IIO, means not
> generic API, and DMA handled in ASOC?
> 
> Then when some other hardwares come with same kind of requirements, we
> will re-discuss a more generic way to do it...
Exactly what I was thinking.
> 
>>
>> Ideally Lars might upstream some of the other bits he has in his tree
>> to do with DSP processing on ADC streams and that might provide us with
>> more clues on generality (at least at the lowest layers)
>> (Apparently he's busy - though he always makes that excuse :)
>> Joking aside, the exploration Analog and in particular Lars does around
>> pushing the limits of how things interact is always useful!)
>>
>>>
>>>> +	.period_bytes_min = 40, /* 8 khz 5 ms */
>>>> +	.period_bytes_max = 4 * PAGE_SIZE,
>>>> +	.buffer_bytes_max = 16 * PAGE_SIZE
>>>
>>> What's the physical minimum period limit?  The comment makes this sound
>>> like it's just made up.
>>>
>>>> +	unsigned int shift = 24 -priv->max_scaling;
>>>> +	
>>>
>>> Missing space after -.
>>>
>>>> +	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);
>>>
>>> Looks like debug changes got left in?
>>>
>>>> +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);
>>>
>>> Since the DAI is unidirectional it doesn't matter but obviously if it
>>> weren't then the fact that getting the supported rates involves setting
>>> the hwparams means this could become disruptive.  If we're going to
>>> genericise this to be a more general IIO/ASoC bridge that could matter.
>>>
>>>> +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;
>>>> +}
>>>
>>> Remove empty functions, though in this case I think you want to add
>>> something to disconnect the XRUN callback just in order to be sure it
>>> can't be mistakenly called.
>>>
>>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-iio" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
>
diff mbox

Patch

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@st.com>.
+ *
+ * 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@st.com>
+ *          Olivier Moysan <olivier.moysan@st.com>
+ *
+ * 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, &params);
+		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@st.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" STM32_ADFSDM_DRV_NAME);