[RFC v2 7/7] IIO: ADC: add stm32 DFSDM support

Arnaud Pouliquen arnaud.pouliquen at st.com
Mon Feb 27 02:09:55 PST 2017


Hello Jonathan,

Late answer... sorry for this.

I will integrate your remark in V2.
Please find my answers in-line

Regards
Arnaud

On 02/19/2017 03:46 PM, Jonathan Cameron wrote:
> On 13/02/17 16:38, Arnaud Pouliquen wrote:
>> Add driver for stm32 DFSDM IP. This IP converts a sigma delta stream in
>> n bit samples through a low pass filter and an integrator.
>>
>> Signed-off-by: Arnaud Pouliquen <arnaud.pouliquen at st.com>
> I think this fits together rather nicely.  As before, various comments that
> are irrelevant to an RFC (I just couldn't stop myself ;) and a few more
> relevant ones.
> 
> So as far as I'm concerned: Looking forward to the full version!
> (as long as Mark and others are happy of course)
> 
> I definitely want to ultimately see buffered and dma support on the
> ADC driver side of things as well but that can come later.
Ok, I suppose that DMA in cyclic mode should also be required for some
other IIO driver...
> 
> Jonathan
>> ---
>>  drivers/iio/adc/Kconfig            |  13 +
>>  drivers/iio/adc/Makefile           |   1 +
>>  drivers/iio/adc/stm32-dfsdm-adc.c  | 483 +++++++++++++++++++++++++++++++++++++
>>  drivers/iio/adc/stm32-dfsdm-core.c | 273 +++++++++++++++++++++
>>  drivers/iio/adc/stm32-dfsdm.h      | 141 +++++++++++
>>  5 files changed, 911 insertions(+)
>>  create mode 100644 drivers/iio/adc/stm32-dfsdm-adc.c
>>  create mode 100644 drivers/iio/adc/stm32-dfsdm-core.c
>>  create mode 100644 drivers/iio/adc/stm32-dfsdm.h
>>
>> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
>> index d4366ac..ab917b6 100644
>> --- a/drivers/iio/adc/Kconfig
>> +++ b/drivers/iio/adc/Kconfig
>> @@ -452,6 +452,19 @@ config STM32_ADC
>>  	  This driver can also be built as a module.  If so, the module
>>  	  will be called stm32-adc.
>>  
>> +config STM32_DFSDM_ADC
>> +	tristate "STMicroelectronics STM32 dfsdm adc"
>> +	depends on (ARCH_STM32 && OF) || COMPILE_TEST
>> +	select REGMAP
>> +	select REGMAP_MMIO
>> +	select IIO_HW_CONSUMER
>> +	help
>> +	  Select this option to enable the  driver for STMicroelectronics
>> +	  STM32 digital filter for sigma delta converter (ADC).
>> +
>> +	  This driver can also be built as a module.  If so, the module
>> +	  will be called stm32-adc-dfsdm-adc.
>> +
>>  config STX104
>>  	tristate "Apex Embedded Systems STX104 driver"
>>  	depends on X86 && ISA_BUS_API
>> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
>> index bd67144..5bcad23 100644
>> --- a/drivers/iio/adc/Makefile
>> +++ b/drivers/iio/adc/Makefile
>> @@ -43,6 +43,7 @@ obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o
>>  obj-$(CONFIG_STX104) += stx104.o
>>  obj-$(CONFIG_STM32_ADC_CORE) += stm32-adc-core.o
>>  obj-$(CONFIG_STM32_ADC) += stm32-adc.o
>> +obj-$(CONFIG_STM32_DFSDM_ADC) += stm32-dfsdm-adc.o stm32-dfsdm-core.o
>>  obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o
>>  obj-$(CONFIG_TI_ADC0832) += ti-adc0832.o
>>  obj-$(CONFIG_TI_ADC12138) += ti-adc12138.o
>> diff --git a/drivers/iio/adc/stm32-dfsdm-adc.c b/drivers/iio/adc/stm32-dfsdm-adc.c
>> new file mode 100644
>> index 0000000..8f9c3263
>> --- /dev/null
>> +++ b/drivers/iio/adc/stm32-dfsdm-adc.c
>> @@ -0,0 +1,483 @@
>> +/*
>> + * This file is part of STM32 DFSDM ADC driver
>> + *
>> + * Copyright (C) 2016, STMicroelectronics - All Rights Reserved
>> + * Author: Arnaud Pouliquen <arnaud.pouliquen at 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/interrupt.h>
>> +#include <linux/io.h>
>> +#include <linux/module.h>
>> +#include <linux/of.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/slab.h>
>> +
>> +#include <linux/iio/hw_consumer.h>
>> +#include <linux/iio/iio.h>
>> +#include <linux/iio/sysfs.h>
>> +
>> +#include <sound/stm32-adfsdm.h>
>> +
>> +#include "stm32-dfsdm.h"
>> +
>> +enum stm32_dfsdm_mode {
>> +	DFSDM_ADC, /* ADC mode, access through IIO ABI */
>> +	DFSDM_AUDIO /* Audio mode, access through ASoC ABI */
>> +};
>> +
>> +struct stm32_dfsdm_adc {
>> +	struct stm32_dfsdm *common;
>> +
>> +	unsigned int fl_id;
>> +	unsigned int oversamp;
>> +	unsigned int clk_freq;
>> +
>> +	enum stm32_dfsdm_mode mode;
>> +	struct platform_device *audio_pdev;
>> +
>> +	void (*overrun_cb)(void *context);
>> +	void *cb_context;
>> +
>> +	/* Hardware consumer structure for Front End iio */
> IIO
>> +	struct iio_hw_consumer *hwc;
>> +};
>> +
>> +static const enum stm32_dfsdm_mode stm32_dfsdm_data_adc = DFSDM_ADC;
>> +static const enum stm32_dfsdm_mode stm32_dfsdm_data_audio = DFSDM_AUDIO;
>> +
>> +struct stm32_dfsdm_adc_devdata {
>> +	enum stm32_dfsdm_mode mode;
>> +	const struct iio_info *info;
>> +};
>> +
>> +static int stm32_dfsdm_set_osrs(struct stm32_dfsdm_adc *adc, bool fast,
>> +				unsigned int oversamp)
>> +{
>> +	/*
>> +	 * TODO
>> +	 * This function tries to compute filter oversampling and integrator
>> +	 * oversampling, base on oversampling ratio requested by user.
>> +	 */
>> +
>> +	return 0;
>> +};
>> +
>> +static int stm32_dfsdm_single_conv(struct iio_dev *indio_dev,
>> +				   const struct iio_chan_spec *chan, int *res)
>> +{
>> +	/* TODO: Perform conversion instead of sending fake value */
>> +	dev_dbg(&indio_dev->dev, "%s\n", __func__);
> :( Definitely an RFC
>> +
>> +	*res = chan->channel + 0xFFFF00;
>> +	return 0;
>> +}
>> +
>> +static int stm32_dfsdm_write_raw(struct iio_dev *indio_dev,
>> +				 struct iio_chan_spec const *chan,
>> +				 int val, int val2, long mask)
>> +{
>> +	struct stm32_dfsdm_adc *adc = iio_priv(indio_dev);
>> +	int ret;
>> +
>> +	switch (mask) {
>> +	case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
>> +		ret = stm32_dfsdm_set_osrs(adc, 0, val);
>> +		if (!ret)
>> +			adc->oversamp = val;
> If no reason to carry on,(i.e. nothing to unwind) return directly from within
> the switch statement.
>> +		break;
>> +	case IIO_CHAN_INFO_SAMP_FREQ:
>> +		if (adc->mode == DFSDM_AUDIO)
>> +			ret = stm32_dfsdm_set_osrs(adc, 0, val);
>> +		else
>> +			ret = -EINVAL;
>> +		break;
>> +
>> +	default:
>> +		ret = -EINVAL;
>> +	}
>> +
>> +	return ret;
>> +}
>> +
>> +static int stm32_dfsdm_read_raw(struct iio_dev *indio_dev,
>> +				struct iio_chan_spec const *chan, int *val,
>> +				int *val2, long mask)
>> +{
>> +	struct stm32_dfsdm_adc *adc = iio_priv(indio_dev);
>> +	int ret;
>> +
>> +	dev_dbg(&indio_dev->dev, "%s\n", __func__);
>> +	switch (mask) {
>> +	case IIO_CHAN_INFO_RAW:
>> +		if (adc->hwc) {
>> +			ret = iio_hw_consumer_enable(adc->hwc);
>> +			if (ret < 0) {
>> +				dev_err(&indio_dev->dev,
>> +					"%s: iio enable failed (channel %d)\n",
>> +					__func__, chan->channel);
>> +				return ret;
>> +			}
> Not an error if hwc not available?
ASoC framework requests to handle a codec driver. So in case of PDM
usecase, no IIO SD modulator device is used, instead an ASoC generic
dmic-codec device is probed. In other words the link between the DFSDM
and the external SD-modulator has to be done in ASOC for PDMs and in
IIO for ADCs.

>> +		}
>> +		ret = stm32_dfsdm_single_conv(indio_dev, chan, val);
>> +		if (ret < 0) {
>> +			dev_err(&indio_dev->dev,
>> +				"%s: conversion failed (channel %d)\n",
>> +				__func__, chan->channel);
>> +			return ret;
>> +		}
>> +
>> +		if (adc->hwc)
>> +			iio_hw_consumer_disable(adc->hwc);
>> +
>> +		return IIO_VAL_INT;
>> +
>> +	case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
>> +		*val = adc->oversamp;
>> +
>> +		return IIO_VAL_INT;
>> +
>> +	case IIO_CHAN_INFO_SAMP_FREQ:
>> +		*val = DIV_ROUND_CLOSEST(adc->clk_freq, adc->oversamp);
>> +
>> +		return IIO_VAL_INT;
>> +	}
>> +
>> +	return -EINVAL;
>> +}
>> +
>> +static const struct iio_info stm32_dfsdm_info_adc = {
>> +	.read_raw = stm32_dfsdm_read_raw,
>> +	.write_raw = stm32_dfsdm_write_raw,
>> +	.driver_module = THIS_MODULE,
>> +};
>> +
>> +static const struct iio_info stm32_dfsdm_info_audio = {
>> +	.read_raw = stm32_dfsdm_read_raw,
>> +	.write_raw = stm32_dfsdm_write_raw,
>> +	.driver_module = THIS_MODULE,
>> +};
> Hohum. These two are the same, why two copies?  Mind you for the audio
> you can't write or read anything so you could drop the callbacks.
yes to rework.
>> +
>> +const struct stm32_dfsdm_adc_devdata stm32_dfsdm_devdata_adc = {
>> +	.mode = DFSDM_ADC,
>> +	.info = &stm32_dfsdm_info_adc,
>> +};
>> +
>> +const struct stm32_dfsdm_adc_devdata stm32_dfsdm_devdata_audio = {
>> +	.mode = DFSDM_AUDIO,
>> +	.info = &stm32_dfsdm_info_audio,
>> +};
>> +
>> +static irqreturn_t stm32_dfsdm_irq(int irq, void *arg)
>> +{
>> +	/* TODO */
>> +	return IRQ_HANDLED;
>> +}
>> +
>> +static void stm32_dfsdm_set_sysclk(struct stm32_dfsdm_adc *adc,
>> +				   unsigned int freq)
>> +{
>> +	struct iio_dev *iio = iio_priv_to_dev(adc);
>> +
>> +	dev_dbg(&iio->dev, "%s:\n", __func__);
>> +
>> +	adc->clk_freq = freq;
>> +};
>> +
>> +	/* Set expected audio sampling rate */
>> +static int stm32_dfsdm_set_hwparam(struct stm32_dfsdm_adc *adc,
>> +				   struct stm32_dfsdm_hw_param *params)
>> +{
>> +	struct iio_dev *iio = iio_priv_to_dev(adc);
>> +
>> +	dev_dbg(&iio->dev, "%s for rate %d\n", __func__, params->rate);
>> +
>> +	return stm32_dfsdm_set_osrs(adc, 0, params->rate);
>> +};
>> +
>> +	/* Called when ASoC starts an audio stream setup. */
>> +static int stm32_dfsdm_audio_startup(struct stm32_dfsdm_adc *adc)
>> +{
>> +	struct iio_dev *iio = iio_priv_to_dev(adc);
>> +
>> +	dev_dbg(&iio->dev, "%s\n", __func__);
>> +
>> +	return 0;
>> +};
>> +
>> +	/* Shuts down the audio stream. */
> Odd indenting.
>> +static void stm32_dfsdm_audio_shutdown(struct stm32_dfsdm_adc *adc)
>> +{
>> +	struct iio_dev *iio = iio_priv_to_dev(adc);
>> +
>> +	dev_dbg(&iio->dev, "%s\n", __func__);
>> +};
>> +
>> +	/*
>> +	 * Provides DMA source physicla addr to allow ALsa to handle DMA
>> +	 * transfers.
> physical - please run a spell checker over the comments.
>> +	 */
>> +static dma_addr_t stm32_dfsdm_get_dma_source(struct stm32_dfsdm_adc *adc)
>> +{
>> +	struct iio_dev *iio = iio_priv_to_dev(adc);
>> +
>> +	dev_dbg(&iio->dev, "%s\n", __func__);
>> +
>> +	return (dma_addr_t)(adc->common->phys_base + DFSDM_RDATAR(adc->fl_id));
>> +};
>> +
>> +/* Register callback to treat underrun and overrun issues */
>> +static void stm32_dfsdm_register_xrun_cb(struct stm32_dfsdm_adc *adc,
>> +					 void (*overrun_cb)(void *context),
>> +					 void *context)
>> +{
>> +	struct iio_dev *iio = iio_priv_to_dev(adc);
>> +
>> +	dev_dbg(&iio->dev, "%s\n", __func__);
>> +	adc->overrun_cb = overrun_cb;
>> +	adc->cb_context = context;
>> +};
>> +
>> +const struct stm32_adfsdm_codec_ops stm32_dfsdm_audio_ops = {
>> +	.set_sysclk = stm32_dfsdm_set_sysclk,
>> +	.set_hwparam = stm32_dfsdm_set_hwparam,
>> +	.audio_startup = stm32_dfsdm_audio_startup,
>> +	.audio_shutdown = stm32_dfsdm_audio_shutdown,
>> +	.register_xrun_cb = stm32_dfsdm_register_xrun_cb,
>> +	.get_dma_source = stm32_dfsdm_get_dma_source
>> +};
> Hmm. I'm wondering if it might make sense to farm the audio stuff off
> to a separate file.  Potentially we'll have systems that are built with
> no audio support at all, so would be odd to have this stuff still provided.
Ok, this make sense if the API become generic.
> 
> What do you think?  Also provides an obvious clean bit to be Mark's problem
> (even if it's in the IIO directory ;)
I can't answer instead of Mark,( and perhaps i misunderstood...) but
regarding HDMI story (drivers shared between ASoC and DRM), not sure
that Mark accepts to have an ASoC drivers in IIO directory. So need a
part in ASoC that is customer of IIO driver...

>> +
>> +static int stm32_dfsdm_adc_chan_init_one(struct iio_dev *indio_dev,
>> +					 struct iio_chan_spec *chan,
>> +					 int chan_idx)
>> +{
>> +	struct iio_chan_spec *ch = &chan[chan_idx];
>> +	struct stm32_dfsdm_adc *adc = iio_priv(indio_dev);
>> +	int ret;
>> +
>> +	dev_dbg(&indio_dev->dev, "%s:\n", __func__);
>> +	ret = of_property_read_u32_index(indio_dev->dev.of_node,
>> +					 "st,adc-channels", chan_idx,
>> +					 &ch->channel);
>> +	if (ret < 0) {
>> +		dev_err(&indio_dev->dev,
>> +			" error parsing 'st,adc-channels' for idx %d\n",
>> +			chan_idx);
>> +		return ret;
>> +	}
>> +
>> +	ret = of_property_read_string_index(indio_dev->dev.of_node,
>> +					    "st,adc-channel-names", chan_idx,
>> +					    &ch->datasheet_name);
>> +	if (ret < 0) {
>> +		dev_err(&indio_dev->dev,
>> +			" error parsing 'st,adc-channel-names' for idx %d\n",
>> +			chan_idx);
>> +		return ret;
>> +	}
>> +
>> +	ch->type = IIO_VOLTAGE;
>> +	ch->indexed = 1;
>> +	ch->scan_index = chan_idx;
>> +	if (adc->mode == DFSDM_ADC) {
>> +		/*
>> +		 * IIO_CHAN_INFO_RAW: used to compute regular conversion
>> +		 * IIO_CHAN_INFO_SAMP_FREQ: used to indicate sampling frequency
>> +		 * IIO_CHAN_INFO_OVERSAMPLING_RATIO: used set oversampling
>> +		 */
>> +		ch->info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
>> +					 BIT(IIO_CHAN_INFO_SAMP_FREQ) |
>> +					 BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO);
> Very nice.  I was just thinking you should do this before I got here.
> Channels with no properties but still with an existence from the point of
> view of consumers using the buffered interface.  Potentially you 'could'
> provide some of the info as read only but why bother...
>> +	}
>> +
>> +	ch->scan_type.sign = 'u';
>> +	ch->scan_type.realbits = 24;
>> +	ch->scan_type.storagebits = 32;
>> +
>> +	return 0;
>> +}
>> +
>> +static int stm32_dfsdm_adc_chan_init(struct iio_dev *indio_dev)
>> +{
>> +	struct iio_chan_spec *channels;
>> +	struct stm32_dfsdm_adc *adc = iio_priv(indio_dev);
>> +	unsigned int num_ch;
>> +	int ret, chan_idx;
>> +
>> +	num_ch = of_property_count_u32_elems(indio_dev->dev.of_node,
>> +					     "st,adc-channels");
>> +	if (num_ch < 0 || num_ch >= adc->common->num_chs) {
>> +		dev_err(&indio_dev->dev, "Bad st,adc-channels?\n");
>> +		return num_ch < 0 ? num_ch : -EINVAL;
>> +	}
>> +
>> +	channels = devm_kcalloc(&indio_dev->dev, num_ch, sizeof(*channels),
>> +				GFP_KERNEL);
>> +	if (!channels)
>> +		return -ENOMEM;
>> +
>> +	if (adc->mode == DFSDM_ADC) {
>> +		/*
>> +		 * Bind to sd modulator iio device for ADC only.
>> +		 * For Audio the PDM microphone will be handled by ASoC
>> +		 */
>> +		adc->hwc = iio_hw_consumer_alloc(&indio_dev->dev);
>> +		if (IS_ERR(adc->hwc)) {
>> +			dev_err(&indio_dev->dev, "no backend found\n");
> Deferred probing a possibility? I'm not quite sure and you know what is going
> on here better than me ;)
Reading your comment i have a doubt... i will cross check
Idea here is that there is a customer-provider relationchip with the
sd-modulator driver. So need that sd-modulator driver is probed first.

>> +			return PTR_ERR(adc->hwc);
>> +		}
>> +	}
>> +
>> +	for (chan_idx = 0; chan_idx < num_ch; chan_idx++) {
>> +		ret = stm32_dfsdm_adc_chan_init_one(indio_dev, channels,
>> +						    chan_idx);
>> +		if (ret < 0)
>> +			goto free_hwc;
>> +	}
>> +
>> +	indio_dev->num_channels = num_ch;
>> +	indio_dev->channels = channels;
>> +
>> +	return 0;
>> +
>> +free_hwc:
>> +	if (adc->hwc)
>> +		iio_hw_consumer_free(adc->hwc);
>> +	return ret;
>> +}
>> +
[...]



More information about the linux-arm-kernel mailing list