mbox series

[v6,00/13] Add STM32 DFSDM support

Message ID 1512150020-20335-1-git-send-email-arnaud.pouliquen@st.com
Headers show
Series Add STM32 DFSDM support | expand

Message

Arnaud POULIQUEN Dec. 1, 2017, 5:40 p.m. UTC
Hello,

Here is a new version to fix kbuild test warnings

Main deltas V6 vs V5:
- Fix warning reported by kbuild test in :
   include/linux/iio/consumer.h
   sound/soc/stm/stm32_adfsdm.c

Main deltas V5 vs V4:
- Integrate ASOC DAI as a subnode of the DFSDM.
- Add in kernel consumer interface to allow to manipulate attribute.

Context reminder:
-----------------

DFSDM peripheral is a peripheral that allows to connect some sigma delta ADCs
or PDM microphones via a SPI or Manchester bus.
DFSDM integrates digital filters to offer up to 24 bits final resolution.

In term of SW architecture. 2 use-cases have to be supported:

1) Sigma delta ADC conversion through IIO framework.
Sigma delta ADC is handled by generic sigma delta modulator driver. 
DFSDM peripheral is binded to a SD modulator ADC using the IIO HW consumer interface. 
Please notice that IIO HW consumer interface has be proposed by Lars, but is
part of this patchset with Lars's agreement.
User interface is IIO one.
Notice that this patch-set propose only a raw conversion, to simplify review.
Buffer and trigger management will be added in next patch-sets.

2) PDM microphone record through ALSA framework.
PDM microphone is handled by ASOC Generic DMIC codec driver.
ADFSDM ASOC DAI driver is binded to IIO driver using the IIO consumer interface
ADFSDM ASOC DAI driver is binded to a PDM microphone ASOC component using ASOC Of_graph.
User interface is ALSA one.
As IIO DMA management is not adapted to an audio realtime stream. A specific DMA 
management has been implemented in IIO driver for audio purposes.

Regards,
Arnaud



Arnaud Pouliquen (12):
  docs: driver-api: add iio hw consumer section
  IIO: hw_consumer: add devm_iio_hw_consumer_alloc
  IIO: inkern: API for manipulating channel attributes
  IIO: Add DT bindings for sigma delta adc modulator
  IIO: ADC: add sigma delta modulator support
  IIO: add DT bindings for stm32 DFSDM filter
  IIO: ADC: add stm32 DFSDM core support
  IIO: ADC: add STM32 DFSDM sigma delta ADC support
  IIO: ADC: add stm32 DFSDM support for PDM microphone
  IIO: consumer: allow to set buffer sizes
  ASoC: add bindings for stm32 DFSDM filter
  ASoC: stm32: add DFSDM DAI support

Lars-Peter Clausen (1):
  iio: Add hardware consumer buffer support

 .../ABI/testing/sysfs-bus-iio-dfsdm-adc-stm32      |   16 +
 .../bindings/iio/adc/sigma-delta-modulator.txt     |   13 +
 .../bindings/iio/adc/st,stm32-dfsdm-adc.txt        |  127 ++
 .../devicetree/bindings/sound/st,stm32-adfsdm.txt  |   62 +
 Documentation/driver-api/iio/hw-consumer.rst       |   51 +
 Documentation/driver-api/iio/index.rst             |    1 +
 drivers/iio/adc/Kconfig                            |   37 +
 drivers/iio/adc/Makefile                           |    3 +
 drivers/iio/adc/sd_adc_modulator.c                 |   81 ++
 drivers/iio/adc/stm32-dfsdm-adc.c                  | 1232 ++++++++++++++++++++
 drivers/iio/adc/stm32-dfsdm-core.c                 |  318 +++++
 drivers/iio/adc/stm32-dfsdm.h                      |  319 +++++
 drivers/iio/buffer/Kconfig                         |   10 +
 drivers/iio/buffer/Makefile                        |    1 +
 drivers/iio/buffer/industrialio-buffer-cb.c        |   11 +
 drivers/iio/buffer/industrialio-hw-consumer.c      |  248 ++++
 drivers/iio/inkern.c                               |   18 +-
 include/linux/iio/adc/stm32-dfsdm-adc.h            |   28 +
 include/linux/iio/consumer.h                       |   39 +-
 include/linux/iio/hw-consumer.h                    |   22 +
 sound/soc/stm/Kconfig                              |   11 +
 sound/soc/stm/Makefile                             |    3 +
 sound/soc/stm/stm32_adfsdm.c                       |  386 ++++++
 23 files changed, 3031 insertions(+), 6 deletions(-)
 create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-dfsdm-adc-stm32
 create mode 100644 Documentation/devicetree/bindings/iio/adc/sigma-delta-modulator.txt
 create mode 100644 Documentation/devicetree/bindings/iio/adc/st,stm32-dfsdm-adc.txt
 create mode 100644 Documentation/devicetree/bindings/sound/st,stm32-adfsdm.txt
 create mode 100644 Documentation/driver-api/iio/hw-consumer.rst
 create mode 100644 drivers/iio/adc/sd_adc_modulator.c
 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
 create mode 100644 drivers/iio/buffer/industrialio-hw-consumer.c
 create mode 100644 include/linux/iio/adc/stm32-dfsdm-adc.h
 create mode 100644 include/linux/iio/hw-consumer.h
 create mode 100644 sound/soc/stm/stm32_adfsdm.c

Comments

Jonathan Cameron Dec. 2, 2017, 2:46 p.m. UTC | #1
On Fri, 1 Dec 2017 18:40:11 +0100
Arnaud Pouliquen <arnaud.pouliquen@st.com> wrote:

> Extend the inkern API with functions for reading and writing
> attribute of iio channels.
> 
> Signed-off-by: Arnaud Pouliquen <arnaud.pouliquen@st.com>
> ---
> V5 to V6 update:
>  Replace include type.h with iio.h to fix build warning

Except don't do that.   Consumers should not be able to see the internals
of iio devices..  Hence instead, move the required enum to types.h please.

One other passing comment inline (not one I'm expecting you to do anything
about!).  Otherwise this is fine with me.

Thanks,

Jonathan

> 
>  drivers/iio/inkern.c         | 18 +++++++++++++-----
>  include/linux/iio/consumer.h | 28 +++++++++++++++++++++++++++-
>  2 files changed, 40 insertions(+), 6 deletions(-)
> 
> diff --git a/drivers/iio/inkern.c b/drivers/iio/inkern.c
> index 069defc..f2e7824 100644
> --- a/drivers/iio/inkern.c
> +++ b/drivers/iio/inkern.c
> @@ -664,9 +664,8 @@ int iio_convert_raw_to_processed(struct iio_channel *chan, int raw,
>  }
>  EXPORT_SYMBOL_GPL(iio_convert_raw_to_processed);
>  
> -static int iio_read_channel_attribute(struct iio_channel *chan,
> -				      int *val, int *val2,
> -				      enum iio_chan_info_enum attribute)
> +int iio_read_channel_attribute(struct iio_channel *chan, int *val, int *val2,
> +			       enum iio_chan_info_enum attribute)
>  {
>  	int ret;
>  
> @@ -682,6 +681,8 @@ static int iio_read_channel_attribute(struct iio_channel *chan,
>  
>  	return ret;
>  }
> +EXPORT_SYMBOL_GPL(iio_read_channel_attribute);
> +
>  
>  int iio_read_channel_offset(struct iio_channel *chan, int *val, int *val2)
>  {
> @@ -850,7 +851,8 @@ static int iio_channel_write(struct iio_channel *chan, int val, int val2,
>  						chan->channel, val, val2, info);
>  }
>  
> -int iio_write_channel_raw(struct iio_channel *chan, int val)
> +int iio_write_channel_attribute(struct iio_channel *chan, int val, int val2,
> +				enum iio_chan_info_enum attribute)
>  {
>  	int ret;
>  
> @@ -860,12 +862,18 @@ int iio_write_channel_raw(struct iio_channel *chan, int val)
>  		goto err_unlock;
>  	}
>  
> -	ret = iio_channel_write(chan, val, 0, IIO_CHAN_INFO_RAW);
> +	ret = iio_channel_write(chan, val, val2, attribute);
>  err_unlock:
>  	mutex_unlock(&chan->indio_dev->info_exist_lock);
>  
>  	return ret;
>  }
> +EXPORT_SYMBOL_GPL(iio_write_channel_attribute);
> +
> +int iio_write_channel_raw(struct iio_channel *chan, int val)
> +{
> +	return iio_write_channel_attribute(chan, val, 0, IIO_CHAN_INFO_RAW);
> +}
>  EXPORT_SYMBOL_GPL(iio_write_channel_raw);
>  
>  unsigned int iio_get_channel_ext_info_count(struct iio_channel *chan)
> diff --git a/include/linux/iio/consumer.h b/include/linux/iio/consumer.h
> index 5e347a9..70658b1 100644
> --- a/include/linux/iio/consumer.h
> +++ b/include/linux/iio/consumer.h
> @@ -11,7 +11,7 @@
>  #define _IIO_INKERN_CONSUMER_H_
>  
>  #include <linux/types.h>
> -#include <linux/iio/types.h>
> +#include <linux/iio/iio.h>

Hmm. Not keen on this include as it just exposed the internals
to IIO consumers which we really don't want to do.

This is to allow access to the enum iio_chan_info_enum?
Move it to types.h please.

>  
>  struct iio_dev;
>  struct iio_chan_spec;
> @@ -216,6 +216,32 @@ int iio_read_channel_average_raw(struct iio_channel *chan, int *val);
>  int iio_read_channel_processed(struct iio_channel *chan, int *val);
>  
>  /**
> + * iio_write_channel_attribute() - Write values to the device attribute.
> + * @chan:	The channel being queried.
> + * @val:	Value being written.
> + * @val2:	Value being written.val2 use depends on attribute type.
> + * @attribute:	info attribute to be read.
> + *
> + * Returns an error code or 0.
> + */
> +int iio_write_channel_attribute(struct iio_channel *chan, int val,
> +				int val2, enum iio_chan_info_enum attribute);
> +
> +/**
> + * iio_read_channel_attribute() - Read values from the device attribute.
> + * @chan:	The channel being queried.
> + * @val:	Value being written.
> + * @val2:	Value being written.Val2 use depends on attribute type.

This pretty much highlights that we are still missing a function to allow us
to query what form write data should be presented in...

Here we have tightly coupled hardware so we know the answer, but in general
we don't.

Still can be a follow up patch when someone needs it.

> + * @attribute:	info attribute to be written.
> + *
> + * Returns an error code if failed. Else returns a description of what is in val
> + * and val2, such as IIO_VAL_INT_PLUS_MICRO telling us we have a value of val
> + * + val2/1e6
> + */
> +int iio_read_channel_attribute(struct iio_channel *chan, int *val,
> +			       int *val2, enum iio_chan_info_enum attribute);
> +
> +/**
>   * iio_write_channel_raw() - write to a given channel
>   * @chan:		The channel being queried.
>   * @val:		Value being written.

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Jonathan Cameron Dec. 2, 2017, 2:50 p.m. UTC | #2
On Fri, 1 Dec 2017 18:40:08 +0100
Arnaud Pouliquen <arnaud.pouliquen@st.com> wrote:

> From: Lars-Peter Clausen <lars@metafoo.de>
> 
> Hardware consumer interface can be used when one IIO device has
> a direct connection to another device in hardware.

Given I'm not totally sure who will do the immutable branch I'm going
to starting adding tags to make it clear what I'm happy with.
(might rip them off again if I end up handling the final merge)
> 
> Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
> Signed-off-by: Arnaud Pouliquen <arnaud.pouliquen@st.com>

Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>

> ---
>  drivers/iio/buffer/Kconfig                    |  10 ++
>  drivers/iio/buffer/Makefile                   |   1 +
>  drivers/iio/buffer/industrialio-hw-consumer.c | 182 ++++++++++++++++++++++++++
>  include/linux/iio/hw-consumer.h               |  20 +++
>  4 files changed, 213 insertions(+)
>  create mode 100644 drivers/iio/buffer/industrialio-hw-consumer.c
>  create mode 100644 include/linux/iio/hw-consumer.h
> 
> diff --git a/drivers/iio/buffer/Kconfig b/drivers/iio/buffer/Kconfig
> index 4ffd3db..338774c 100644
> --- a/drivers/iio/buffer/Kconfig
> +++ b/drivers/iio/buffer/Kconfig
> @@ -29,6 +29,16 @@ config IIO_BUFFER_DMAENGINE
>  
>  	  Should be selected by drivers that want to use this functionality.
>  
> +config IIO_BUFFER_HW_CONSUMER
> +	tristate "Industrial I/O HW buffering"
> +	help
> +	  Provides a way to bonding when an IIO device has a direct connection
> +	  to another device in hardware. In this case buffers for data transfers
> +	  are handled by hardware.
> +
> +	  Should be selected by drivers that want to use the generic Hw consumer
> +	  interface.
> +
>  config IIO_KFIFO_BUF
>  	tristate "Industrial I/O buffering based on kfifo"
>  	help
> diff --git a/drivers/iio/buffer/Makefile b/drivers/iio/buffer/Makefile
> index 85beaae..324a36b 100644
> --- a/drivers/iio/buffer/Makefile
> +++ b/drivers/iio/buffer/Makefile
> @@ -6,5 +6,6 @@
>  obj-$(CONFIG_IIO_BUFFER_CB) += industrialio-buffer-cb.o
>  obj-$(CONFIG_IIO_BUFFER_DMA) += industrialio-buffer-dma.o
>  obj-$(CONFIG_IIO_BUFFER_DMAENGINE) += industrialio-buffer-dmaengine.o
> +obj-$(CONFIG_IIO_BUFFER_HW_CONSUMER) += industrialio-hw-consumer.o
>  obj-$(CONFIG_IIO_TRIGGERED_BUFFER) += industrialio-triggered-buffer.o
>  obj-$(CONFIG_IIO_KFIFO_BUF) += kfifo_buf.o
> diff --git a/drivers/iio/buffer/industrialio-hw-consumer.c b/drivers/iio/buffer/industrialio-hw-consumer.c
> new file mode 100644
> index 0000000..0253be1
> --- /dev/null
> +++ b/drivers/iio/buffer/industrialio-hw-consumer.c
> @@ -0,0 +1,182 @@
> +/*
> + * Copyright 2017 Analog Devices Inc.
> + *  Author: Lars-Peter Clausen <lars@metafoo.de>
> + *
> + * Licensed under the GPL-2 or later.
> + */
> +
> +#include <linux/err.h>
> +#include <linux/export.h>
> +#include <linux/slab.h>
> +#include <linux/module.h>
> +
> +#include <linux/iio/iio.h>
> +#include <linux/iio/consumer.h>
> +#include <linux/iio/hw-consumer.h>
> +#include <linux/iio/buffer_impl.h>
> +
> +/**
> + * struct iio_hw_consumer - IIO hw consumer block
> + * @buffers: hardware buffers list head.
> + * @channels: IIO provider channels.
> + */
> +struct iio_hw_consumer {
> +	struct list_head buffers;
> +	struct iio_channel *channels;
> +};
> +
> +struct hw_consumer_buffer {
> +	struct list_head head;
> +	struct iio_dev *indio_dev;
> +	struct iio_buffer buffer;
> +	long scan_mask[];
> +};
> +
> +static struct hw_consumer_buffer *iio_buffer_to_hw_consumer_buffer(
> +	struct iio_buffer *buffer)
> +{
> +	return container_of(buffer, struct hw_consumer_buffer, buffer);
> +}
> +
> +static void iio_hw_buf_release(struct iio_buffer *buffer)
> +{
> +	struct hw_consumer_buffer *hw_buf =
> +		iio_buffer_to_hw_consumer_buffer(buffer);
> +	kfree(hw_buf);
> +}
> +
> +static const struct iio_buffer_access_funcs iio_hw_buf_access = {
> +	.release = &iio_hw_buf_release,
> +	.modes = INDIO_BUFFER_HARDWARE,
> +};
> +
> +static struct hw_consumer_buffer *iio_hw_consumer_get_buffer(
> +	struct iio_hw_consumer *hwc, struct iio_dev *indio_dev)
> +{
> +	size_t mask_size = BITS_TO_LONGS(indio_dev->masklength) * sizeof(long);
> +	struct hw_consumer_buffer *buf;
> +
> +	list_for_each_entry(buf, &hwc->buffers, head) {
> +		if (buf->indio_dev == indio_dev)
> +			return buf;
> +	}
> +
> +	buf = kzalloc(sizeof(*buf) + mask_size, GFP_KERNEL);
> +	if (!buf)
> +		return NULL;
> +
> +	buf->buffer.access = &iio_hw_buf_access;
> +	buf->indio_dev = indio_dev;
> +	buf->buffer.scan_mask = buf->scan_mask;
> +
> +	iio_buffer_init(&buf->buffer);
> +	list_add_tail(&buf->head, &hwc->buffers);
> +
> +	return buf;
> +}
> +
> +/**
> + * iio_hw_consumer_alloc() - Allocate IIO hardware consumer
> + * @dev: Pointer to consumer device.
> + *
> + * Returns a valid iio_hw_consumer on success or a ERR_PTR() on failure.
> + */
> +struct iio_hw_consumer *iio_hw_consumer_alloc(struct device *dev)
> +{
> +	struct hw_consumer_buffer *buf;
> +	struct iio_hw_consumer *hwc;
> +	struct iio_channel *chan;
> +	int ret;
> +
> +	hwc = kzalloc(sizeof(*hwc), GFP_KERNEL);
> +	if (!hwc)
> +		return ERR_PTR(-ENOMEM);
> +
> +	INIT_LIST_HEAD(&hwc->buffers);
> +
> +	hwc->channels = iio_channel_get_all(dev);
> +	if (IS_ERR(hwc->channels)) {
> +		ret = PTR_ERR(hwc->channels);
> +		goto err_free_hwc;
> +	}
> +
> +	chan = &hwc->channels[0];
> +	while (chan->indio_dev) {
> +		buf = iio_hw_consumer_get_buffer(hwc, chan->indio_dev);
> +		if (!buf) {
> +			ret = -ENOMEM;
> +			goto err_put_buffers;
> +		}
> +		set_bit(chan->channel->scan_index, buf->buffer.scan_mask);
> +		chan++;
> +	}
> +
> +	return hwc;
> +
> +err_put_buffers:
> +	list_for_each_entry(buf, &hwc->buffers, head)
> +		iio_buffer_put(&buf->buffer);
> +	iio_channel_release_all(hwc->channels);
> +err_free_hwc:
> +	kfree(hwc);
> +	return ERR_PTR(ret);
> +}
> +EXPORT_SYMBOL_GPL(iio_hw_consumer_alloc);
> +
> +/**
> + * iio_hw_consumer_free() - Free IIO hardware consumer
> + * @hwc: hw consumer to free.
> + */
> +void iio_hw_consumer_free(struct iio_hw_consumer *hwc)
> +{
> +	struct hw_consumer_buffer *buf, *n;
> +
> +	iio_channel_release_all(hwc->channels);
> +	list_for_each_entry_safe(buf, n, &hwc->buffers, head)
> +		iio_buffer_put(&buf->buffer);
> +	kfree(hwc);
> +}
> +EXPORT_SYMBOL_GPL(iio_hw_consumer_free);
> +
> +/**
> + * iio_hw_consumer_enable() - Enable IIO hardware consumer
> + * @hwc: iio_hw_consumer to enable.
> + *
> + * Returns 0 on success.
> + */
> +int iio_hw_consumer_enable(struct iio_hw_consumer *hwc)
> +{
> +	struct hw_consumer_buffer *buf;
> +	int ret;
> +
> +	list_for_each_entry(buf, &hwc->buffers, head) {
> +		ret = iio_update_buffers(buf->indio_dev, &buf->buffer, NULL);
> +		if (ret)
> +			goto err_disable_buffers;
> +	}
> +
> +	return 0;
> +
> +err_disable_buffers:
> +	list_for_each_entry_continue_reverse(buf, &hwc->buffers, head)
> +		iio_update_buffers(buf->indio_dev, NULL, &buf->buffer);
> +	return ret;
> +}
> +EXPORT_SYMBOL_GPL(iio_hw_consumer_enable);
> +
> +/**
> + * iio_hw_consumer_disable() - Disable IIO hardware consumer
> + * @hwc: iio_hw_consumer to disable.
> + */
> +void iio_hw_consumer_disable(struct iio_hw_consumer *hwc)
> +{
> +	struct hw_consumer_buffer *buf;
> +
> +	list_for_each_entry(buf, &hwc->buffers, head)
> +		iio_update_buffers(buf->indio_dev, NULL, &buf->buffer);
> +}
> +EXPORT_SYMBOL_GPL(iio_hw_consumer_disable);
> +
> +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
> +MODULE_DESCRIPTION("Hardware consumer buffer the IIO framework");
> +MODULE_LICENSE("GPL v2");
> diff --git a/include/linux/iio/hw-consumer.h b/include/linux/iio/hw-consumer.h
> new file mode 100644
> index 0000000..f16791b
> --- /dev/null
> +++ b/include/linux/iio/hw-consumer.h
> @@ -0,0 +1,20 @@
> +/*
> + * Industrial I/O in kernel hardware consumer interface
> + *
> + * Copyright 2017 Analog Devices Inc.
> + *  Author: Lars-Peter Clausen <lars@metafoo.de>
> + *
> + * Licensed under the GPL-2 or later.
> + */
> +
> +#ifndef LINUX_IIO_HW_CONSUMER_H
> +#define LINUX_IIO_HW_CONSUMER_H
> +
> +struct iio_hw_consumer;
> +
> +struct iio_hw_consumer *iio_hw_consumer_alloc(struct device *dev);
> +void iio_hw_consumer_free(struct iio_hw_consumer *hwc);
> +int iio_hw_consumer_enable(struct iio_hw_consumer *hwc);
> +void iio_hw_consumer_disable(struct iio_hw_consumer *hwc);
> +
> +#endif

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Jonathan Cameron Dec. 2, 2017, 2:51 p.m. UTC | #3
On Fri, 1 Dec 2017 18:40:09 +0100
Arnaud Pouliquen <arnaud.pouliquen@st.com> wrote:

> This adds a section about the Hardware consumer
> API of the IIO subsystem to the driver API
> documentation.
> 
> Signed-off-by: Arnaud Pouliquen <arnaud.pouliquen@st.com>

Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>

> ---
>  Documentation/driver-api/iio/hw-consumer.rst | 51 ++++++++++++++++++++++++++++
>  Documentation/driver-api/iio/index.rst       |  1 +
>  2 files changed, 52 insertions(+)
>  create mode 100644 Documentation/driver-api/iio/hw-consumer.rst
> 
> diff --git a/Documentation/driver-api/iio/hw-consumer.rst b/Documentation/driver-api/iio/hw-consumer.rst
> new file mode 100644
> index 0000000..8facce6
> --- /dev/null
> +++ b/Documentation/driver-api/iio/hw-consumer.rst
> @@ -0,0 +1,51 @@
> +===========
> +HW consumer
> +===========
> +An IIO device can be directly connected to another device in hardware. in this
> +case the buffers between IIO provider and IIO consumer are handled by hardware.
> +The Industrial I/O HW consumer offers a way to bond these IIO devices without
> +software buffer for data. The implementation can be found under
> +:file:`drivers/iio/buffer/hw-consumer.c`
> +
> +
> +* struct :c:type:`iio_hw_consumer` — Hardware consumer structure
> +* :c:func:`iio_hw_consumer_alloc` — Allocate IIO hardware consumer
> +* :c:func:`iio_hw_consumer_free` — Free IIO hardware consumer
> +* :c:func:`iio_hw_consumer_enable` — Enable IIO hardware consumer
> +* :c:func:`iio_hw_consumer_disable` — Disable IIO hardware consumer
> +
> +
> +HW consumer setup
> +=================
> +
> +As standard IIO device the implementation is based on IIO provider/consumer.
> +A typical IIO HW consumer setup looks like this::
> +
> +	static struct iio_hw_consumer *hwc;
> +
> +	static const struct iio_info adc_info = {
> +		.read_raw = adc_read_raw,
> +	};
> +
> +	static int adc_read_raw(struct iio_dev *indio_dev,
> +				struct iio_chan_spec const *chan, int *val,
> +				int *val2, long mask)
> +	{
> +		ret = iio_hw_consumer_enable(hwc);
> +
> +		/* Acquire data */
> +
> +		ret = iio_hw_consumer_disable(hwc);
> +	}
> +
> +	static int adc_probe(struct platform_device *pdev)
> +	{
> +		hwc = devm_iio_hw_consumer_alloc(&iio->dev);
> +	}
> +
> +More details
> +============
> +.. kernel-doc:: include/linux/iio/hw-consumer.h
> +.. kernel-doc:: drivers/iio/buffer/industrialio-hw-consumer.c
> +   :export:
> +
> diff --git a/Documentation/driver-api/iio/index.rst b/Documentation/driver-api/iio/index.rst
> index e5c3922..7fba341 100644
> --- a/Documentation/driver-api/iio/index.rst
> +++ b/Documentation/driver-api/iio/index.rst
> @@ -15,3 +15,4 @@ Contents:
>     buffers
>     triggers
>     triggered-buffers
> +   hw-consumer

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Jonathan Cameron Dec. 2, 2017, 2:52 p.m. UTC | #4
On Fri, 1 Dec 2017 18:40:10 +0100
Arnaud Pouliquen <arnaud.pouliquen@st.com> wrote:

> Add devm_iio_hw_consumer_alloc function that calls iio_hw_consumer_free
> when the device is unbound from the bus.
> 
> Signed-off-by: Arnaud Pouliquen <arnaud.pouliquen@st.com>

Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>

> ---
>  drivers/iio/buffer/industrialio-hw-consumer.c | 66 +++++++++++++++++++++++++++
>  include/linux/iio/hw-consumer.h               |  2 +
>  2 files changed, 68 insertions(+)
> 
> diff --git a/drivers/iio/buffer/industrialio-hw-consumer.c b/drivers/iio/buffer/industrialio-hw-consumer.c
> index 0253be1..e980a79 100644
> --- a/drivers/iio/buffer/industrialio-hw-consumer.c
> +++ b/drivers/iio/buffer/industrialio-hw-consumer.c
> @@ -138,6 +138,72 @@ void iio_hw_consumer_free(struct iio_hw_consumer *hwc)
>  }
>  EXPORT_SYMBOL_GPL(iio_hw_consumer_free);
>  
> +static void devm_iio_hw_consumer_release(struct device *dev, void *res)
> +{
> +	iio_hw_consumer_free(*(struct iio_hw_consumer **)res);
> +}
> +
> +static int devm_iio_hw_consumer_match(struct device *dev, void *res, void *data)
> +{
> +	struct iio_hw_consumer **r = res;
> +
> +	if (!r || !*r) {
> +		WARN_ON(!r || !*r);
> +		return 0;
> +	}
> +	return *r == data;
> +}
> +
> +/**
> + * devm_iio_hw_consumer_alloc - Resource-managed iio_hw_consumer_alloc()
> + * @dev: Pointer to consumer device.
> + *
> + * Managed iio_hw_consumer_alloc. iio_hw_consumer allocated with this function
> + * is automatically freed on driver detach.
> + *
> + * If an iio_hw_consumer allocated with this function needs to be freed
> + * separately, devm_iio_hw_consumer_free() must be used.
> + *
> + * returns pointer to allocated iio_hw_consumer on success, NULL on failure.
> + */
> +struct iio_hw_consumer *devm_iio_hw_consumer_alloc(struct device *dev)
> +{
> +	struct iio_hw_consumer **ptr, *iio_hwc;
> +
> +	ptr = devres_alloc(devm_iio_hw_consumer_release, sizeof(*ptr),
> +			   GFP_KERNEL);
> +	if (!ptr)
> +		return NULL;
> +
> +	iio_hwc = iio_hw_consumer_alloc(dev);
> +	if (IS_ERR(iio_hwc)) {
> +		devres_free(ptr);
> +	} else {
> +		*ptr = iio_hwc;
> +		devres_add(dev, ptr);
> +	}
> +
> +	return iio_hwc;
> +}
> +EXPORT_SYMBOL_GPL(devm_iio_hw_consumer_alloc);
> +
> +/**
> + * devm_iio_hw_consumer_free - Resource-managed iio_hw_consumer_free()
> + * @dev: Pointer to consumer device.
> + * @hwc: iio_hw_consumer to free.
> + *
> + * Free iio_hw_consumer allocated with devm_iio_hw_consumer_alloc().
> + */
> +void devm_iio_hw_consumer_free(struct device *dev, struct iio_hw_consumer *hwc)
> +{
> +	int rc;
> +
> +	rc = devres_release(dev, devm_iio_hw_consumer_release,
> +			    devm_iio_hw_consumer_match, hwc);
> +	WARN_ON(rc);
> +}
> +EXPORT_SYMBOL_GPL(devm_iio_hw_consumer_free);
> +
>  /**
>   * iio_hw_consumer_enable() - Enable IIO hardware consumer
>   * @hwc: iio_hw_consumer to enable.
> diff --git a/include/linux/iio/hw-consumer.h b/include/linux/iio/hw-consumer.h
> index f16791b..90ecfce 100644
> --- a/include/linux/iio/hw-consumer.h
> +++ b/include/linux/iio/hw-consumer.h
> @@ -14,6 +14,8 @@ struct iio_hw_consumer;
>  
>  struct iio_hw_consumer *iio_hw_consumer_alloc(struct device *dev);
>  void iio_hw_consumer_free(struct iio_hw_consumer *hwc);
> +struct iio_hw_consumer *devm_iio_hw_consumer_alloc(struct device *dev);
> +void devm_iio_hw_consumer_free(struct device *dev, struct iio_hw_consumer *hwc);
>  int iio_hw_consumer_enable(struct iio_hw_consumer *hwc);
>  void iio_hw_consumer_disable(struct iio_hw_consumer *hwc);
>  

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Jonathan Cameron Dec. 2, 2017, 2:54 p.m. UTC | #5
On Fri, 1 Dec 2017 18:40:13 +0100
Arnaud Pouliquen <arnaud.pouliquen@st.com> wrote:

> Add generic driver to support sigma delta modulators.
> Typically, this device is hardware connected to
> an IIO device in charge of the conversion. Devices are
> bonded through the hardware consumer API.
> 
> Signed-off-by: Arnaud Pouliquen <arnaud.pouliquen@st.com>
I love this little one ;) Smallest IIO driver yet!
Doesn't technically 'do' anything but that's not the point.

Acked-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>

> ---
>  drivers/iio/adc/Kconfig            | 12 ++++++
>  drivers/iio/adc/Makefile           |  1 +
>  drivers/iio/adc/sd_adc_modulator.c | 81 ++++++++++++++++++++++++++++++++++++++
>  3 files changed, 94 insertions(+)
>  create mode 100644 drivers/iio/adc/sd_adc_modulator.c
> 
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index 5762565..c5db62f 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -626,6 +626,18 @@ config SPEAR_ADC
>  	  To compile this driver as a module, choose M here: the
>  	  module will be called spear_adc.
>  
> +config SD_ADC_MODULATOR
> +	tristate "Generic sigma delta modulator"
> +	depends on OF
> +	select IIO_BUFFER
> +	select IIO_TRIGGERED_BUFFER
> +	help
> +	  Select this option to enables sigma delta modulator. This driver can
> +	  support generic sigma delta modulators.
> +
> +	  This driver can also be built as a module.  If so, the module
> +	  will be called sd_adc_modulator.
> +
>  config STM32_ADC_CORE
>  	tristate "STMicroelectronics STM32 adc core"
>  	depends on ARCH_STM32 || COMPILE_TEST
> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
> index 9874e05..d800325 100644
> --- a/drivers/iio/adc/Makefile
> +++ b/drivers/iio/adc/Makefile
> @@ -81,3 +81,4 @@ obj-$(CONFIG_VF610_ADC) += vf610_adc.o
>  obj-$(CONFIG_VIPERBOARD_ADC) += viperboard_adc.o
>  xilinx-xadc-y := xilinx-xadc-core.o xilinx-xadc-events.o
>  obj-$(CONFIG_XILINX_XADC) += xilinx-xadc.o
> +obj-$(CONFIG_SD_ADC_MODULATOR) += sd_adc_modulator.o
> diff --git a/drivers/iio/adc/sd_adc_modulator.c b/drivers/iio/adc/sd_adc_modulator.c
> new file mode 100644
> index 0000000..08bd7b6
> --- /dev/null
> +++ b/drivers/iio/adc/sd_adc_modulator.c
> @@ -0,0 +1,81 @@
> +/*
> + * Generic sigma delta modulator driver
> + *
> + * Copyright (C) 2017, STMicroelectronics - All Rights Reserved
> + * Author: Arnaud Pouliquen <arnaud.pouliquen@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/iio/iio.h>
> +#include <linux/iio/triggered_buffer.h>
> +#include <linux/module.h>
> +#include <linux/of_device.h>
> +
> +static const struct iio_info iio_sd_mod_iio_info;
> +
> +static const struct iio_chan_spec iio_sd_mod_ch = {
> +	.type = IIO_VOLTAGE,
> +	.indexed = 1,
> +	.scan_type = {
> +		.sign = 'u',
> +		.realbits = 1,
> +		.shift = 0,
> +	},
> +};
> +
> +static int iio_sd_mod_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct iio_dev *iio;
> +
> +	iio = devm_iio_device_alloc(dev, 0);
> +	if (!iio)
> +		return -ENOMEM;
> +
> +	iio->dev.parent = dev;
> +	iio->dev.of_node = dev->of_node;
> +	iio->name = dev_name(dev);
> +	iio->info = &iio_sd_mod_iio_info;
> +	iio->modes = INDIO_BUFFER_HARDWARE;
> +
> +	iio->num_channels = 1;
> +	iio->channels = &iio_sd_mod_ch;
> +
> +	platform_set_drvdata(pdev, iio);
> +
> +	return devm_iio_device_register(&pdev->dev, iio);
> +}
> +
> +static const struct of_device_id sd_adc_of_match[] = {
> +	{ .compatible = "sd-modulator" },
> +	{ .compatible = "ads1201" },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, sd_adc_of_match);
> +
> +static struct platform_driver iio_sd_mod_adc = {
> +	.driver = {
> +		.name = "iio_sd_adc_mod",
> +		.of_match_table = of_match_ptr(sd_adc_of_match),
> +	},
> +	.probe = iio_sd_mod_probe,
> +};
> +
> +module_platform_driver(iio_sd_mod_adc);
> +
> +MODULE_DESCRIPTION("Basic sigma delta modulator");
> +MODULE_AUTHOR("Arnaud Pouliquen <arnaud.pouliquen@st.com>");
> +MODULE_LICENSE("GPL v2");

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Jonathan Cameron Dec. 2, 2017, 2:58 p.m. UTC | #6
On Fri, 1 Dec 2017 18:40:16 +0100
Arnaud Pouliquen <arnaud.pouliquen@st.com> wrote:

> Add DFSDM driver to handle sigma delta ADC.
> 
> Signed-off-by: Arnaud Pouliquen <arnaud.pouliquen@st.com>

A couple of really small nitpicks if you are respinning.

Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>

> ---
>  drivers/iio/adc/Kconfig           |  13 +
>  drivers/iio/adc/Makefile          |   1 +
>  drivers/iio/adc/stm32-dfsdm-adc.c | 741 ++++++++++++++++++++++++++++++++++++++
>  3 files changed, 755 insertions(+)
>  create mode 100644 drivers/iio/adc/stm32-dfsdm-adc.c
> 
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index b729ae0..98ca30b 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -677,6 +677,19 @@ config STM32_DFSDM_CORE
>  	  This driver can also be built as a module.  If so, the module
>  	  will be called stm32-dfsdm-core.
>  
> +config STM32_DFSDM_ADC
> +	tristate "STMicroelectronics STM32 dfsdm adc"
> +	depends on (ARCH_STM32 && OF) || COMPILE_TEST
> +	select STM32_DFSDM_CORE
> +	select REGMAP_MMIO
> +	select IIO_BUFFER_HW_CONSUMER
> +	help
> +	  Select this option to support ADCSigma delta modulator for
> +	  STMicroelectronics STM32 digital filter for sigma delta converter.
> +
> +	  This driver can also be built as a module.  If so, the module
> +	  will be called stm32-dfsdm-adc.
> +
>  config STX104
>  	tristate "Apex Embedded Systems STX104 driver"
>  	depends on PC104 && X86 && ISA_BUS_API
> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
> index b52d0a0..c4f5d15 100644
> --- a/drivers/iio/adc/Makefile
> +++ b/drivers/iio/adc/Makefile
> @@ -64,6 +64,7 @@ obj-$(CONFIG_SUN4I_GPADC) += sun4i-gpadc-iio.o
>  obj-$(CONFIG_STM32_ADC_CORE) += stm32-adc-core.o
>  obj-$(CONFIG_STM32_ADC) += stm32-adc.o
>  obj-$(CONFIG_STM32_DFSDM_CORE) += stm32-dfsdm-core.o
> +obj-$(CONFIG_STM32_DFSDM_ADC) += stm32-dfsdm-adc.o
>  obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o
>  obj-$(CONFIG_TI_ADC0832) += ti-adc0832.o
>  obj-$(CONFIG_TI_ADC084S021) += ti-adc084s021.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..f9419ab
> --- /dev/null
> +++ b/drivers/iio/adc/stm32-dfsdm-adc.c
> @@ -0,0 +1,741 @@
> +/*
> + * This file is the ADC part of the STM32 DFSDM driver
> + *
> + * Copyright (C) 2017, STMicroelectronics - All Rights Reserved
> + * Author: Arnaud Pouliquen <arnaud.pouliquen@st.com>.
> + *
> + * License type: 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/interrupt.h>
> +#include <linux/iio/buffer.h>
> +#include <linux/iio/hw-consumer.h>
> +#include <linux/iio/iio.h>
> +#include <linux/iio/sysfs.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/regmap.h>
> +#include <linux/slab.h>
> +
> +#include "stm32-dfsdm.h"
> +
> +/* Conversion timeout */
> +#define DFSDM_TIMEOUT_US 100000
> +#define DFSDM_TIMEOUT (msecs_to_jiffies(DFSDM_TIMEOUT_US / 1000))
> +
> +/* Oversampling attribute default */
> +#define DFSDM_DEFAULT_OVERSAMPLING  100
> +
> +/* Oversampling max values */
> +#define DFSDM_MAX_INT_OVERSAMPLING 256
> +#define DFSDM_MAX_FL_OVERSAMPLING 1024
> +
> +/* Max sample resolutions */
> +#define DFSDM_MAX_RES BIT(31)
> +#define DFSDM_DATA_RES BIT(23)
> +
> +enum sd_converter_type {
> +	DFSDM_AUDIO,
> +	DFSDM_IIO,
> +};
> +
> +struct stm32_dfsdm_dev_data {
> +	int type;
> +	int (*init)(struct iio_dev *indio_dev);
> +	unsigned int num_channels;
> +	const struct regmap_config *regmap_cfg;
> +};
> +
> +struct stm32_dfsdm_adc {
> +	struct stm32_dfsdm *dfsdm;
> +	const struct stm32_dfsdm_dev_data *dev_data;
> +	unsigned int fl_id;
> +	unsigned int ch_id;
> +
> +	/* ADC specific */
> +	unsigned int oversamp;
> +	struct iio_hw_consumer *hwc;
> +	struct completion completion;
> +	u32 *buffer;
> +
> +};
> +
> +struct stm32_dfsdm_str2field {
> +	const char	*name;
> +	unsigned int	val;
> +};
> +
> +/* DFSDM channel serial interface type */
> +static const struct stm32_dfsdm_str2field stm32_dfsdm_chan_type[] = {
> +	{ "SPI_R", 0 }, /* SPI with data on rising edge */
> +	{ "SPI_F", 1 }, /* SPI with data on falling edge */
> +	{ "MANCH_R", 2 }, /* Manchester codec, rising edge = logic 0 */
> +	{ "MANCH_F", 3 }, /* Manchester codec, falling edge = logic 1 */
> +	{},
> +};
> +
> +/* DFSDM channel clock source */
> +static const struct stm32_dfsdm_str2field stm32_dfsdm_chan_src[] = {
> +	/* External SPI clock (CLKIN x) */
> +	{ "CLKIN", DFSDM_CHANNEL_SPI_CLOCK_EXTERNAL },
> +	/* Internal SPI clock (CLKOUT) */
> +	{ "CLKOUT", DFSDM_CHANNEL_SPI_CLOCK_INTERNAL },
> +	/* Internal SPI clock divided by 2 (falling edge) */
> +	{ "CLKOUT_F", DFSDM_CHANNEL_SPI_CLOCK_INTERNAL_DIV2_FALLING },
> +	/* Internal SPI clock divided by 2 (falling edge) */
> +	{ "CLKOUT_R", DFSDM_CHANNEL_SPI_CLOCK_INTERNAL_DIV2_RISING },
> +	{},
> +};
> +
> +static int stm32_dfsdm_str2val(const char *str,
> +			       const struct stm32_dfsdm_str2field *list)
> +{
> +	const struct stm32_dfsdm_str2field *p = list;
> +
> +	for (p = list; p && p->name; p++)
> +		if (!strcmp(p->name, str))
> +			return p->val;
> +
> +	return -EINVAL;
> +}
> +
> +static int stm32_dfsdm_set_osrs(struct stm32_dfsdm_filter *fl,
> +				unsigned int fast, unsigned int oversamp)
> +{
> +	unsigned int i, d, fosr, iosr;
> +	u64 res;
> +	s64 delta;
> +	unsigned int m = 1;	/* multiplication factor */
> +	unsigned int p = fl->ford;	/* filter order (ford) */
> +
> +	pr_debug("%s: Requested oversampling: %d\n",  __func__, oversamp);
> +	/*
> +	 * This function tries to compute filter oversampling and integrator
> +	 * oversampling, base on oversampling ratio requested by user.
> +	 *
> +	 * Decimation d depends on the filter order and the oversampling ratios.
> +	 * ford: filter order
> +	 * fosr: filter over sampling ratio
> +	 * iosr: integrator over sampling ratio
> +	 */
> +	if (fl->ford == DFSDM_FASTSINC_ORDER) {
> +		m = 2;
> +		p = 2;
> +	}
> +
> +	/*
> +	 * Look for filter and integrator oversampling ratios which allows
> +	 * to reach 24 bits data output resolution.
> +	 * Leave as soon as if exact resolution if reached.
> +	 * Otherwise the higher resolution below 32 bits is kept.
> +	 */
> +	for (fosr = 1; fosr <= DFSDM_MAX_FL_OVERSAMPLING; fosr++) {
> +		for (iosr = 1; iosr <= DFSDM_MAX_INT_OVERSAMPLING; iosr++) {
> +			if (fast)
> +				d = fosr * iosr;
> +			else if (fl->ford == DFSDM_FASTSINC_ORDER)
> +				d = fosr * (iosr + 3) + 2;
> +			else
> +				d = fosr * (iosr - 1 + p) + p;
> +
> +			if (d > oversamp)
> +				break;
> +			else if (d != oversamp)
> +				continue;
> +			/*
> +			 * Check resolution (limited to signed 32 bits)
> +			 *   res <= 2^31
> +			 * Sincx filters:
> +			 *   res = m * fosr^p x iosr (with m=1, p=ford)
> +			 * FastSinc filter
> +			 *   res = m * fosr^p x iosr (with m=2, p=2)
> +			 */
> +			res = fosr;
> +			for (i = p - 1; i > 0; i--) {
> +				res = res * (u64)fosr;
> +				if (res > DFSDM_MAX_RES)
> +					break;
> +			}
> +			if (res > DFSDM_MAX_RES)
> +				continue;
> +			res = res * (u64)m * (u64)iosr;
> +			if (res > DFSDM_MAX_RES)
> +				continue;
> +
> +			delta = res - DFSDM_DATA_RES;
> +
> +			if (res >= fl->res) {
> +				fl->res = res;
> +				fl->fosr = fosr;
> +				fl->iosr = iosr;
> +				fl->fast = fast;
> +				pr_debug("%s: fosr = %d, iosr = %d\n",
> +					 __func__, fl->fosr, fl->iosr);
> +			}
> +
> +			if (!delta)
> +				return 0;
> +		}
> +	}
> +
> +	if (!fl->fosr)
> +		return -EINVAL;
> +
> +	return 0;
> +}
> +
> +static int stm32_dfsdm_start_channel(struct stm32_dfsdm *dfsdm,
> +				     unsigned int ch_id)
> +{
> +	return regmap_update_bits(dfsdm->regmap, DFSDM_CHCFGR1(ch_id),
> +				  DFSDM_CHCFGR1_CHEN_MASK,
> +				  DFSDM_CHCFGR1_CHEN(1));
> +}
> +
> +static void stm32_dfsdm_stop_channel(struct stm32_dfsdm *dfsdm,
> +				     unsigned int ch_id)
> +{
> +	regmap_update_bits(dfsdm->regmap, DFSDM_CHCFGR1(ch_id),
> +			   DFSDM_CHCFGR1_CHEN_MASK, DFSDM_CHCFGR1_CHEN(0));
> +}
> +
> +static int stm32_dfsdm_chan_configure(struct stm32_dfsdm *dfsdm,
> +				      struct stm32_dfsdm_channel *ch)
> +{
> +	unsigned int id = ch->id;
> +	struct regmap *regmap = dfsdm->regmap;
> +	int ret;
> +
> +	ret = regmap_update_bits(regmap, DFSDM_CHCFGR1(id),
> +				 DFSDM_CHCFGR1_SITP_MASK,
> +				 DFSDM_CHCFGR1_SITP(ch->type));
> +	if (ret < 0)
> +		return ret;
> +	ret = regmap_update_bits(regmap, DFSDM_CHCFGR1(id),
> +				 DFSDM_CHCFGR1_SPICKSEL_MASK,
> +				 DFSDM_CHCFGR1_SPICKSEL(ch->src));
> +	if (ret < 0)
> +		return ret;
> +	return regmap_update_bits(regmap, DFSDM_CHCFGR1(id),
> +				  DFSDM_CHCFGR1_CHINSEL_MASK,
> +				  DFSDM_CHCFGR1_CHINSEL(ch->alt_si));
> +}
> +
> +static int stm32_dfsdm_start_filter(struct stm32_dfsdm *dfsdm,
> +				    unsigned int fl_id)
> +{
> +	int ret;
> +
> +	/* Enable filter */
> +	ret = regmap_update_bits(dfsdm->regmap, DFSDM_CR1(fl_id),
> +				 DFSDM_CR1_DFEN_MASK, DFSDM_CR1_DFEN(1));
> +	if (ret < 0)
> +		return ret;
> +
> +	/* Start conversion */
> +	return regmap_update_bits(dfsdm->regmap, DFSDM_CR1(fl_id),
> +				  DFSDM_CR1_RSWSTART_MASK,
> +				  DFSDM_CR1_RSWSTART(1));
> +}
> +
> +void stm32_dfsdm_stop_filter(struct stm32_dfsdm *dfsdm, unsigned int fl_id)
> +{
> +	/* Disable conversion */
> +	regmap_update_bits(dfsdm->regmap, DFSDM_CR1(fl_id),
> +			   DFSDM_CR1_DFEN_MASK, DFSDM_CR1_DFEN(0));
> +}
> +
> +static int stm32_dfsdm_filter_configure(struct stm32_dfsdm *dfsdm,
> +					unsigned int fl_id, unsigned int ch_id)
> +{
> +	struct regmap *regmap = dfsdm->regmap;
> +	struct stm32_dfsdm_filter *fl = &dfsdm->fl_list[fl_id];
> +	int ret;
> +
> +	/* Average integrator oversampling */
> +	ret = regmap_update_bits(regmap, DFSDM_FCR(fl_id), DFSDM_FCR_IOSR_MASK,
> +				 DFSDM_FCR_IOSR(fl->iosr - 1));
> +	if (ret)
> +		return ret;
> +
> +	/* Filter order and Oversampling */
> +	ret = regmap_update_bits(regmap, DFSDM_FCR(fl_id), DFSDM_FCR_FOSR_MASK,
> +				 DFSDM_FCR_FOSR(fl->fosr - 1));
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_update_bits(regmap, DFSDM_FCR(fl_id), DFSDM_FCR_FORD_MASK,
> +				 DFSDM_FCR_FORD(fl->ford));
> +	if (ret)
> +		return ret;
> +
> +	/* No scan mode supported for the moment */
> +	ret = regmap_update_bits(regmap, DFSDM_CR1(fl_id), DFSDM_CR1_RCH_MASK,
> +				 DFSDM_CR1_RCH(ch_id));
> +	if (ret)
> +		return ret;
> +
> +	return regmap_update_bits(regmap, DFSDM_CR1(fl_id),
> +				  DFSDM_CR1_RSYNC_MASK,
> +				  DFSDM_CR1_RSYNC(fl->sync_mode));
> +}
> +
> +int stm32_dfsdm_channel_parse_of(struct stm32_dfsdm *dfsdm,
> +				 struct iio_dev *indio_dev,
> +				 struct iio_chan_spec *ch)
> +{
> +	struct stm32_dfsdm_channel *df_ch;
> +	const char *of_str;
> +	int chan_idx = ch->scan_index;
> +	int ret, val;
> +
> +	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;
> +	}
> +	if (ch->channel >= dfsdm->num_chs) {
> +		dev_err(&indio_dev->dev,
> +			" Error bad channel number %d (max = %d)\n",
> +			ch->channel, dfsdm->num_chs);
> +		return -EINVAL;
> +	}
> +
> +	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;
> +	}
> +
> +	df_ch =  &dfsdm->ch_list[ch->channel];
> +	df_ch->id = ch->channel;
> +
> +	ret = of_property_read_string_index(indio_dev->dev.of_node,
> +					    "st,adc-channel-types", chan_idx,
> +					    &of_str);
> +	if (!ret) {
> +		val  = stm32_dfsdm_str2val(of_str, stm32_dfsdm_chan_type);
> +		if (val < 0)
> +			return val;
> +	} else {
> +		val = 0;
> +	}
> +	df_ch->type = val;
> +
> +	ret = of_property_read_string_index(indio_dev->dev.of_node,
> +					    "st,adc-channel-clk-src", chan_idx,
> +					    &of_str);
> +	if (!ret) {
> +		val  = stm32_dfsdm_str2val(of_str, stm32_dfsdm_chan_src);
> +		if (val < 0)
> +			return val;
> +	} else {
> +		val = 0;
> +	}
> +	df_ch->src = val;
> +
> +	ret = of_property_read_u32_index(indio_dev->dev.of_node,
> +					 "st,adc-alt-channel", chan_idx,
> +					 &df_ch->alt_si);
> +	if (ret < 0)
> +		df_ch->alt_si = 0;
> +
> +	return 0;
> +}
> +
> +static int stm32_dfsdm_start_conv(struct stm32_dfsdm_adc *adc, bool dma)
> +{
> +	struct regmap *regmap = adc->dfsdm->regmap;
> +	int ret;
> +
> +	ret = stm32_dfsdm_start_channel(adc->dfsdm, adc->ch_id);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = stm32_dfsdm_filter_configure(adc->dfsdm, adc->fl_id,
> +					   adc->ch_id);
> +	if (ret < 0)
> +		goto stop_channels;
> +
> +	ret = stm32_dfsdm_start_filter(adc->dfsdm, adc->fl_id);
> +	if (ret < 0)
> +		goto stop_channels;
> +
> +	return 0;
> +
> +stop_channels:
> +	regmap_update_bits(regmap, DFSDM_CR1(adc->fl_id),
> +			   DFSDM_CR1_RDMAEN_MASK, 0);
> +
> +	regmap_update_bits(regmap, DFSDM_CR1(adc->fl_id),
> +			   DFSDM_CR1_RCONT_MASK, 0);
> +	stm32_dfsdm_stop_channel(adc->dfsdm, adc->fl_id);
> +
> +	return ret;
> +}
> +
> +static void stm32_dfsdm_stop_conv(struct stm32_dfsdm_adc *adc)
> +{
> +	struct regmap *regmap = adc->dfsdm->regmap;
> +
> +	stm32_dfsdm_stop_filter(adc->dfsdm, adc->fl_id);
> +
> +	/* Clean conversion options */
> +	regmap_update_bits(regmap, DFSDM_CR1(adc->fl_id),
> +			   DFSDM_CR1_RDMAEN_MASK, 0);
> +
> +	regmap_update_bits(regmap, DFSDM_CR1(adc->fl_id),
> +			   DFSDM_CR1_RCONT_MASK, 0);
> +
> +	stm32_dfsdm_stop_channel(adc->dfsdm, adc->ch_id);
> +}
> +
> +static int stm32_dfsdm_single_conv(struct iio_dev *indio_dev,
> +				   const struct iio_chan_spec *chan, int *res)
> +{
> +	struct stm32_dfsdm_adc *adc = iio_priv(indio_dev);
> +	long timeout;
> +	int ret;
> +
> +	reinit_completion(&adc->completion);
> +
> +	adc->buffer = res;
> +
> +	ret = stm32_dfsdm_start_dfsdm(adc->dfsdm);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = regmap_update_bits(adc->dfsdm->regmap, DFSDM_CR2(adc->fl_id),
> +				 DFSDM_CR2_REOCIE_MASK, DFSDM_CR2_REOCIE(1));
> +	if (ret < 0)
> +		goto stop_dfsdm;
> +
> +	ret = stm32_dfsdm_start_conv(adc, false);
> +	if (ret < 0) {
> +		regmap_update_bits(adc->dfsdm->regmap, DFSDM_CR2(adc->fl_id),
> +				   DFSDM_CR2_REOCIE_MASK, DFSDM_CR2_REOCIE(0));
> +		goto stop_dfsdm;
> +	}
> +
> +	timeout = wait_for_completion_interruptible_timeout(&adc->completion,
> +							    DFSDM_TIMEOUT);
> +
> +	/* Mask IRQ for regular conversion achievement*/
> +	regmap_update_bits(adc->dfsdm->regmap, DFSDM_CR2(adc->fl_id),
> +			   DFSDM_CR2_REOCIE_MASK, DFSDM_CR2_REOCIE(0));
> +
> +	if (timeout == 0)
> +		ret = -ETIMEDOUT;
> +	else if (timeout < 0)
> +		ret = timeout;
> +	else
> +		ret = IIO_VAL_INT;
> +
> +	stm32_dfsdm_stop_conv(adc);
> +
> +stop_dfsdm:
> +	stm32_dfsdm_stop_dfsdm(adc->dfsdm);
> +
> +	return ret;
> +}
> +
> +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);
> +	struct stm32_dfsdm_filter *fl = &adc->dfsdm->fl_list[adc->fl_id];
> +	int ret = -EINVAL;
> +
> +	if (mask == IIO_CHAN_INFO_OVERSAMPLING_RATIO) {
> +		ret = stm32_dfsdm_set_osrs(fl, 0, val);
> +		if (!ret)
> +			adc->oversamp = val;
> +	}
> +
> +	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;
> +
> +	switch (mask) {
> +	case IIO_CHAN_INFO_RAW:
> +		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;
> +		}
> +		ret = stm32_dfsdm_single_conv(indio_dev, chan, val);
> +		iio_hw_consumer_disable(adc->hwc);
> +		if (ret < 0) {
> +			dev_err(&indio_dev->dev,
> +				"%s: Conversion failed (channel %d)\n",
> +				__func__, chan->channel);
> +			return ret;
> +		}
> +		return IIO_VAL_INT;
> +
> +	case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
> +		*val = 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,
> +};
> +
> +static irqreturn_t stm32_dfsdm_irq(int irq, void *arg)
> +{
> +	struct stm32_dfsdm_adc *adc = arg;
> +	struct iio_dev *indio_dev = iio_priv_to_dev(adc);
> +	struct regmap *regmap = adc->dfsdm->regmap;
> +	unsigned int status, int_en;
> +
> +	regmap_read(regmap, DFSDM_ISR(adc->fl_id), &status);
> +	regmap_read(regmap, DFSDM_CR2(adc->fl_id), &int_en);
> +
> +	if (status & DFSDM_ISR_REOCF_MASK) {
> +		/* Read the data register clean the IRQ status */
> +		regmap_read(regmap, DFSDM_RDATAR(adc->fl_id), adc->buffer);
> +		complete(&adc->completion);
> +	}
> +
> +	if (status & DFSDM_ISR_ROVRF_MASK) {
> +		if (int_en & DFSDM_CR2_ROVRIE_MASK)
> +			dev_warn(&indio_dev->dev, "Overrun detected\n");
> +		regmap_update_bits(regmap, DFSDM_ICR(adc->fl_id),
> +				   DFSDM_ICR_CLRROVRF_MASK,
> +				   DFSDM_ICR_CLRROVRF_MASK);
> +	}
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int stm32_dfsdm_adc_chan_init_one(struct iio_dev *indio_dev,
> +					 struct iio_chan_spec *ch)
> +{
> +	struct stm32_dfsdm_adc *adc = iio_priv(indio_dev);
> +	int ret;
> +
> +	ret = stm32_dfsdm_channel_parse_of(adc->dfsdm, indio_dev, ch);
> +	if (ret < 0)
> +		return ret;
> +
> +	ch->type = IIO_VOLTAGE;
> +	ch->indexed = 1;
> +
> +	/*
> +	 * IIO_CHAN_INFO_RAW: used to compute regular conversion
> +	 * IIO_CHAN_INFO_OVERSAMPLING_RATIO: used to set oversampling
> +	 */
> +	ch->info_mask_separate = BIT(IIO_CHAN_INFO_RAW);
> +	ch->info_mask_shared_by_all = BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO);
> +
> +	ch->scan_type.sign = 'u';
> +	ch->scan_type.realbits = 24;
> +	ch->scan_type.storagebits = 32;
> +	adc->ch_id = ch->channel;
> +
> +	return stm32_dfsdm_chan_configure(adc->dfsdm,
> +					  &adc->dfsdm->ch_list[ch->channel]);
> +}
> +
> +static int stm32_dfsdm_adc_init(struct iio_dev *indio_dev)
> +{
> +	struct iio_chan_spec *ch;
> +	struct stm32_dfsdm_adc *adc = iio_priv(indio_dev);
> +	int num_ch;
> +	int ret, chan_idx;
> +
> +	adc->oversamp = DFSDM_DEFAULT_OVERSAMPLING;
> +	ret = stm32_dfsdm_set_osrs(&adc->dfsdm->fl_list[adc->fl_id], 0,
> +				   adc->oversamp);
> +	if (ret < 0)
> +		return ret;
> +
> +	num_ch = of_property_count_u32_elems(indio_dev->dev.of_node,
> +					     "st,adc-channels");
> +	if (num_ch < 0 || num_ch > adc->dfsdm->num_chs) {
> +		dev_err(&indio_dev->dev, "Bad st,adc-channels\n");
> +		return num_ch < 0 ? num_ch : -EINVAL;
> +	}
> +
> +	/* Bind to SD modulator IIO device */
> +	adc->hwc = devm_iio_hw_consumer_alloc(&indio_dev->dev);
> +	if (IS_ERR(adc->hwc))
> +		return -EPROBE_DEFER;
> +
> +	ch = devm_kcalloc(&indio_dev->dev, num_ch, sizeof(*ch),
> +			  GFP_KERNEL);
> +	if (!ch)
> +		return -ENOMEM;
> +
> +	for (chan_idx = 0; chan_idx < num_ch; chan_idx++) {
> +		ch->scan_index = chan_idx;
> +		ret = stm32_dfsdm_adc_chan_init_one(indio_dev, ch);
> +		if (ret < 0) {
> +			dev_err(&indio_dev->dev, "Channels init failed\n");
> +			return ret;
> +		}
> +	}
> +
> +	indio_dev->num_channels = num_ch;
> +	indio_dev->channels = ch;
> +
> +	init_completion(&adc->completion);
> +
> +	return 0;
> +}
> +
> +static const struct stm32_dfsdm_dev_data stm32h7_dfsdm_adc_data = {
> +	.type = DFSDM_IIO,
> +	.init = stm32_dfsdm_adc_init,
> +};
> +
> +static const struct of_device_id stm32_dfsdm_adc_match[] = {
> +	{ .compatible = "st,stm32-dfsdm-adc",
Slightly odd alignment, I'd drop .compatible onto a new line as well..

> +		.data = &stm32h7_dfsdm_adc_data,
> +	},
> +	{}
> +};
> +
> +static int stm32_dfsdm_adc_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct stm32_dfsdm_adc *adc;
> +	struct device_node *np = dev->of_node;
> +	const struct stm32_dfsdm_dev_data *dev_data;
> +	struct iio_dev *iio;
> +	const struct of_device_id *of_id;
> +	char *name;
> +	int ret, irq, val;
> +
> +	of_id = of_match_node(stm32_dfsdm_adc_match, np);
> +	if (!of_id->data) {
> +		dev_err(&pdev->dev, "Data associated to device is missing\n");
> +		return -EINVAL;
> +	}
> +
> +	dev_data = (const struct stm32_dfsdm_dev_data *)of_id->data;
> +
> +	iio = devm_iio_device_alloc(dev, sizeof(*adc));
> +	if (IS_ERR(iio)) {
> +		dev_err(dev, "%s: Failed to allocate IIO\n", __func__);
> +		return PTR_ERR(iio);
> +	}
> +
> +	adc = iio_priv(iio);
> +	if (IS_ERR(adc)) {
> +		dev_err(dev, "%s: Failed to allocate ADC\n", __func__);
> +		return PTR_ERR(adc);
> +	}
> +	adc->dfsdm = dev_get_drvdata(dev->parent);
> +
> +	iio->dev.parent = dev;
> +	iio->dev.of_node = np;
> +	iio->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_SOFTWARE;
> +
> +	platform_set_drvdata(pdev, adc);
> +
> +	ret = of_property_read_u32(dev->of_node, "reg", &adc->fl_id);
> +	if (ret != 0) {
> +		dev_err(dev, "Missing reg property\n");
> +		return -EINVAL;
> +	}
> +
> +	name = devm_kzalloc(dev, sizeof("dfsdm-adc0"), GFP_KERNEL);
> +	if (!name)
> +		return -ENOMEM;
> +	iio->info = &stm32_dfsdm_info_adc;
> +	snprintf(name, sizeof("dfsdm-adc0"), "dfsdm-adc%d", adc->fl_id);
> +	iio->name = name;
> +
> +	/*
> +	 * In a first step IRQs generated for channels are not treated.
> +	 * So IRQ associated to filter instance 0 is dedicated to the Filter 0.
> +	 */
> +	irq = platform_get_irq(pdev, 0);
> +	ret = devm_request_irq(dev, irq, stm32_dfsdm_irq,
> +			       0, pdev->name, adc);
> +	if (ret < 0) {
> +		dev_err(dev, "Failed to request IRQ\n");
> +		return ret;
> +	}
> +
> +	ret = of_property_read_u32(dev->of_node, "st,filter-order", &val);
> +	if (ret < 0) {
> +		dev_err(dev, "Failed to set filter order\n");
> +		return ret;
> +	}
> +
> +	adc->dfsdm->fl_list[adc->fl_id].ford = val;
> +
> +	ret = of_property_read_u32(dev->of_node, "st,filter0-sync", &val);
> +	if (!ret)
> +		adc->dfsdm->fl_list[adc->fl_id].sync_mode = val;
> +
> +	adc->dev_data = dev_data;
> +	ret = dev_data->init(iio);
> +	if (ret < 0)
> +		return ret;
> +
> +	return iio_device_register(iio);
> +}
> +
> +static int stm32_dfsdm_adc_remove(struct platform_device *pdev)
> +{
> +	struct stm32_dfsdm_adc *adc = platform_get_drvdata(pdev);
> +	struct iio_dev *indio_dev = iio_priv_to_dev(adc);
> +
> +	iio_device_unregister(indio_dev);
> +
> +	return 0;
> +}
> +
> +static struct platform_driver stm32_dfsdm_adc_driver = {
> +	.driver = {
> +		.name = "stm32-dfsdm-adc",
> +		.of_match_table = stm32_dfsdm_adc_match,
> +	},
> +	.probe = stm32_dfsdm_adc_probe,
> +	.remove = stm32_dfsdm_adc_remove,
> +

Real nitpick but no blank line here!

> +};
> +module_platform_driver(stm32_dfsdm_adc_driver);
> +
> +MODULE_DESCRIPTION("STM32 sigma delta ADC");
> +MODULE_AUTHOR("Arnaud Pouliquen <arnaud.pouliquen@st.com>");
> +MODULE_LICENSE("GPL v2");

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Jonathan Cameron Dec. 2, 2017, 3:09 p.m. UTC | #7
On Fri, 1 Dec 2017 18:40:20 +0100
Arnaud Pouliquen <arnaud.pouliquen@st.com> 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@st.com>

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 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!

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@st.com>
> + *          Olivier Moysan <olivier.moysan@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.
> + *
> + * 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@st.com>");
> +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@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Arnaud POULIQUEN Dec. 4, 2017, 8:58 a.m. UTC | #8
On 12/02/2017 04:09 PM, Jonathan Cameron wrote:
> On Fri, 1 Dec 2017 18:40:20 +0100
> Arnaud Pouliquen <arnaud.pouliquen@st.com> 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@st.com>
> 
> 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@st.com>
>> + *          Olivier Moysan <olivier.moysan@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.
>> + *
>> + * 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@st.com>");
>> +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@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Mark Brown Dec. 4, 2017, 4:58 p.m. UTC | #9
On Mon, Dec 04, 2017 at 09:58:42AM +0100, Arnaud Pouliquen wrote:
> On 12/02/2017 04:09 PM, Jonathan Cameron wrote:

> > 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."

Yup, I figure that a shared branch with the IIO changes in will be the
easiest thing (Morimoto-san is donig a bunch of subsystem wide ASoC
cleanups ATM).