[alsa-devel] [PATCH v3 00/11] Add STM32 DFSDM support
Hello,
I kept it as RFC as i reworked the global design (but also because not full validated...). Regarding Mark and Jonathan's comments, I decided to try an implementation with DMA support in IIO instead of handling it in ASOC. Solution seems more straightforward but as DMA cyclic mode and associated buffer block API is not part of IIO today, I still need some hacks... To be honest, I tried to integrate DMA cyclic management and associated buffer block Consumer API. but no enough expertise on IIO to success... :(
Advantage vs V2: - No more data processing need in ASoC , done in IIO - ASoC uses IIO consumer interface ( except for buffer callback) Drawback : - Need to create a "SPI bus" trigger to allo to use Buffer consumer API => Need to find a solution to set this trigger by default in IIO audio driver - No standard buffer management - Implement DMA cyclic in DFSDM IIO driver. - Define specific API to handle buffer callback per period instead of callback per samples generated by iio_push_to_buffers_with_timestamp.
V2-V3 delta: - New patches to support ASoC DMA codec in DT - ASoC: Add Bindins for DMIC codec driver. - ASoC: codec: add DT support in dmic codec. - New patches to allow in-kernel set of IIO buffer size and watermark - IIO: consumer: allow to set buffer sizes. - IIO DFSDM drivers - Split audio and ADC support in 2 drivers. - Implement DMA cyclic mode. - Add SPI bus Trigger for buffer management.
- IIO sigma delta adc drivers - Suppress "simple and rename driver.
- ASoC driver - Suppress DMA engine. - Suppress copy ops. - Use IIO consmumer interface to enable/Disable DFSDM.
Regards,
Arnaud
Arnaud Pouliquen (11): iio: Add hardware consumer support 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 support for Sigma delta ADC IIO: ADC: add stm32 DFSDM support for PDM microphone IIO: consumer: allow to set buffer sizes ASoC: Add Bindins for DMIC codec driver ASoC: codec: add DT support in dmic codec ASoC: add bindings for stm32 DFSDM filter ASoC: stm32: add DFSDM DAI support
.../bindings/iio/adc/sigma-delta-modulator.txt | 13 + .../bindings/iio/adc/st,stm32-dfsdm-adc.txt | 120 ++++ Documentation/devicetree/bindings/sound/dmic.txt | 11 + .../devicetree/bindings/sound/st,stm32-adfsdm.txt | 41 ++ drivers/iio/Kconfig | 6 + drivers/iio/Makefile | 1 + drivers/iio/adc/Kconfig | 50 ++ drivers/iio/adc/Makefile | 4 + drivers/iio/adc/sd_adc_modulator.c | 98 +++ drivers/iio/adc/stm32-dfsdm-adc.c | 419 ++++++++++++ drivers/iio/adc/stm32-dfsdm-audio.c | 720 +++++++++++++++++++++ drivers/iio/adc/stm32-dfsdm-core.c | 658 +++++++++++++++++++ drivers/iio/adc/stm32-dfsdm.h | 372 +++++++++++ drivers/iio/buffer/industrialio-buffer-cb.c | 12 + drivers/iio/hw_consumer.c | 156 +++++ include/linux/iio/adc/stm32-dfsdm-audio.h | 41 ++ include/linux/iio/consumer.h | 13 + include/linux/iio/hw_consumer.h | 12 + sound/soc/Kconfig | 1 + sound/soc/Makefile | 1 + sound/soc/codecs/dmic.c | 8 + sound/soc/stm/Kconfig | 10 + sound/soc/stm/Makefile | 2 + sound/soc/stm/stm32_adfsdm.c | 362 +++++++++++ 24 files changed, 3131 insertions(+) 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/dmic.txt create mode 100644 Documentation/devicetree/bindings/sound/st,stm32-adfsdm.txt 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-audio.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/hw_consumer.c create mode 100644 include/linux/iio/adc/stm32-dfsdm-audio.h create mode 100644 include/linux/iio/hw_consumer.h create mode 100644 sound/soc/stm/Kconfig create mode 100644 sound/soc/stm/Makefile create mode 100644 sound/soc/stm/stm32_adfsdm.c
V2:
Patch-set associated to this RFC proposes an implementation of the DFSDM features shared between ASoC and IIO frameworks.
Patch-set is only a Skeleton of the drivers, so a base to discuss and validate a design. It contains minimum code to allow probing (with DT) and to expose the ASoC and IIO ABI. Hope that is sufficent in a first step to allow to focus on APIs.
In this patch-set there are two new APIs used: - IIO in-kern API: based on hw_customer API proposed by Lars - ASOC <-> IIO API inspired by API defined for hdmi-codec for ASoC/DRM interconnect. API is dedicated to DFSDM only.
Notice also that this design is based on following assumption: - Audio stream ABI interface is ASoC, no access to data through IIO ABI for PDM. - ASoC DMA should be used for audio transfer as designed for real time stream. - Need some runtime parameters exchange between ASoC and IIO due to the correlation between the sample frequency, the DFSDM decimation factor and the associated scaling.
- "ASoC: dmaengine_pcm: add copy support" patch: I added a patch in ASoC that allows to implement a copy function to process data after DMA transfer. Requested, as DFSDM samples captured contain channel ID on 8-LSB bits and need also a potential rescale to present DAT on 24-bits.
- "IIO: ADC: add sigma delta modulator support" patch: Simple dummy driver created to support external Sigma delta modulator. It is binded to DFSDM driver through hw_customer API.
- 1.9.1
From: Lars-Peter Clausen lars@metafoo.de
Hardware consumer's can be used when one IIO device has a direct connection to another device in hardware.
Signed-off-by: Lars-Peter Clausen lars@metafoo.de Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com --- V2 -> V3: Just Adding sign off. No update but Jonathan comment. Need to be taken into account (in agreement with Lars) when transforming rfc in patch. --- drivers/iio/Kconfig | 6 ++ drivers/iio/Makefile | 1 + drivers/iio/hw_consumer.c | 156 ++++++++++++++++++++++++++++++++++++++++ include/linux/iio/hw_consumer.h | 12 ++++ 4 files changed, 175 insertions(+) create mode 100644 drivers/iio/hw_consumer.c create mode 100644 include/linux/iio/hw_consumer.h
diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig index 6743b18..956dd18 100644 --- a/drivers/iio/Kconfig +++ b/drivers/iio/Kconfig @@ -30,6 +30,12 @@ config IIO_CONFIGFS (e.g. software triggers). For more info see Documentation/iio/iio_configfs.txt.
+config IIO_HW_CONSUMER + tristate + help + Hardware consumer buffer + + config IIO_TRIGGER bool "Enable triggered sampling support" help diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile index 87e4c43..8472b97 100644 --- a/drivers/iio/Makefile +++ b/drivers/iio/Makefile @@ -6,6 +6,7 @@ obj-$(CONFIG_IIO) += industrialio.o industrialio-y := industrialio-core.o industrialio-event.o inkern.o industrialio-$(CONFIG_IIO_BUFFER) += industrialio-buffer.o industrialio-$(CONFIG_IIO_TRIGGER) += industrialio-trigger.o +obj-$(CONFIG_IIO_HW_CONSUMER) += hw_consumer.o
obj-$(CONFIG_IIO_CONFIGFS) += industrialio-configfs.o obj-$(CONFIG_IIO_SW_DEVICE) += industrialio-sw-device.o diff --git a/drivers/iio/hw_consumer.c b/drivers/iio/hw_consumer.c new file mode 100644 index 0000000..66f0732 --- /dev/null +++ b/drivers/iio/hw_consumer.c @@ -0,0 +1,156 @@ +#include <linux/err.h> +#include <linux/export.h> +#include <linux/slab.h> +#include <linux/mutex.h> +#include <linux/of.h> + +#include <linux/iio/iio.h> +#include "iio_core.h" +#include <linux/iio/machine.h> +#include <linux/iio/driver.h> +#include <linux/iio/consumer.h> +#include <linux/iio/hw_consumer.h> +#include <linux/iio/buffer.h> + +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; +}; + +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->buffer.scan_mask); + 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) +{ + 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), GFP_KERNEL); + if (!buf) + return NULL; + + buf->buffer.access = &iio_hw_buf_access; + buf->indio_dev = indio_dev; + buf->buffer.scan_mask = kcalloc(BITS_TO_LONGS(indio_dev->masklength), + sizeof(long), GFP_KERNEL); + if (!buf->buffer.scan_mask) + goto err_free_buf; + + iio_buffer_init(&buf->buffer); + list_add_tail(&buf->head, &hwc->buffers); + + return buf; + +err_free_buf: + kfree(buf); + return NULL; +} + +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 == NULL) { + 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); + +void iio_hw_consumer_free(struct iio_hw_consumer *hwc) +{ + struct hw_consumer_buffer *buf; + + iio_channel_release_all(hwc->channels); + list_for_each_entry(buf, &hwc->buffers, head) + iio_buffer_put(&buf->buffer); + kfree(hwc); +} +EXPORT_SYMBOL_GPL(iio_hw_consumer_free); + +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); + +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); diff --git a/include/linux/iio/hw_consumer.h b/include/linux/iio/hw_consumer.h new file mode 100644 index 0000000..f12653d --- /dev/null +++ b/include/linux/iio/hw_consumer.h @@ -0,0 +1,12 @@ +#ifndef LINUX_IIO_HW_CONSUMER_BUFFER_H +#define LINUX_IIO_HW_CONSUMER_BUFFER_H + +struct device; +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
Add documentation of device tree bindings to support sigma delta modulator in IIO framework.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com --- V2 -> V3 : -Rename to suppress "simple" - add "ads1201" compatibility
.../devicetree/bindings/iio/adc/sigma-delta-modulator.txt | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/adc/sigma-delta-modulator.txt
diff --git a/Documentation/devicetree/bindings/iio/adc/sigma-delta-modulator.txt b/Documentation/devicetree/bindings/iio/adc/sigma-delta-modulator.txt new file mode 100644 index 0000000..27f04a3 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/sigma-delta-modulator.txt @@ -0,0 +1,13 @@ +Device-Tree bindings for sigma delta modulator + +Required properties: +- compatible: should be "ads1201", "sd-modulator". "sd-modulator" can be use + as a generic SD modulator if modulator not specified in compatible list. +- #io-channel-cells = <1>: See the IIO bindings section "IIO consumers". + +Example node: + + ads1202: simple_sd_adc@0 { + compatible = "sd-modulator"; + #io-channel-cells = <1>; + };
Add generic driver to support sigma delta modulators.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com --- V2 -> V3 : -Rename to suppress "simple" - add "ads1201" compatibility
drivers/iio/adc/Kconfig | 11 +++++ drivers/iio/adc/Makefile | 1 + drivers/iio/adc/sd_adc_modulator.c | 98 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 110 insertions(+) create mode 100644 drivers/iio/adc/sd_adc_modulator.c
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index e0b3c09..d411d66 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -419,6 +419,17 @@ config ROCKCHIP_SARADC To compile this driver as a module, choose M here: the module will be called rockchip_saradc.
+config SD_ADC_MODULATOR + tristate "Basic sigma delta modulator" + depends on OF + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Select this option to enables generic sigma delta modulator. + + This driver can also be built as a module. If so, the module + will be called simple-sd-adc. + 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 8e02a94..c68819c 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -57,3 +57,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..4a25642 --- /dev/null +++ b/drivers/iio/adc/sd_adc_modulator.c @@ -0,0 +1,98 @@ +/* + * Basic sigma delta modulator driver + * + * Copyright (C) 2016, 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/module.h> +#include <linux/of_device.h> + +#include <linux/iio/triggered_buffer.h> + +static int iio_sd_mod_of_xlate(struct iio_dev *iio, + const struct of_phandle_args *iiospec) +{ + dev_dbg(&iio->dev, "%s:\n", __func__); + if (iiospec->args[0] != 0) { + dev_err(&iio->dev, "Only one channel supported\n"); + return -EINVAL; + } + + return 0; +} + +static const struct iio_info iio_sd_mod_iio_info = { + .of_xlate = iio_sd_mod_of_xlate, +}; + +static const struct iio_chan_spec stm32_dfsdm_ch = { + .type = IIO_VOLTAGE, + .indexed = 1, + .scan_index = 0, + .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; + + dev_dbg(&pdev->dev, "%s:\n", __func__); + 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 = &stm32_dfsdm_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, adc081c_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");
Add bindings that describes Digital Filter for Sigma Delta Modulators. DFSDM allows to connect sigma delta modulators.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com --- V2->V3: Fixes based on V2 comments
.../bindings/iio/adc/st,stm32-dfsdm-adc.txt | 120 +++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/adc/st,stm32-dfsdm-adc.txt
diff --git a/Documentation/devicetree/bindings/iio/adc/st,stm32-dfsdm-adc.txt b/Documentation/devicetree/bindings/iio/adc/st,stm32-dfsdm-adc.txt new file mode 100644 index 0000000..d7828bd --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/st,stm32-dfsdm-adc.txt @@ -0,0 +1,120 @@ +STMicroelectronics STM32 DFSDM ADC device driver + + +STM32 DFSDM ADC is a sigma delta analog-to-digital converter dedicated to +interface external sigma delta modulators to STM32 micro controllers. +It is mainly targeted for: +- Sigma delta modulators (motor control, metering...) +- PDM microphones (audio digital microphone) + +It features up to 8 serial digital interfaces (SPI or Manchester) and +up to 4 filters. + +Each instance of the sub-drivers uses one filter instance. + +Contents of a STM32 DFSDM root node: +------------------------------------ +Required properties: +- compatible: Should be "st,stm32-dfsdm". +- reg: Offset and length of the DFSDM block register set. +- clocks: IP and serial interfaces clocking. Should be set according + to rcc clock ID and "clock-names". +- clock-names: Input clock name "dfsdm" must be defined, + "audio" is optional. If defined CLKOUT is based on the audio + clock, else "dfsdm" is used. + +Optional properties: +- spi-max-frequency: Requested only for SPI master mode. + SPI clock OUT frequency (Hz). This clock must be set according + to "clock" property. Frequency must be a multiple of the rcc + clock frequency. If not, SPI CLKOUT frequency will not be + accurate. + +Contents of a STM32 DFSDM child nodes: +-------------------------------------- + +Required properties: +- compatible: Must be: + "st,stm32-dfsdm-adc" for sigma delta ADCs + "st,stm32-dfsdm-audio" for audio digital microphone. +- reg: Specifies the DFSDM filter instance used. +- interrupts: IRQ lines connected to each DFSDM filter instance. +- st,adc-channels: List of single-ended channels muxed for this ADC. +- st,adc-channel-names: List of single-ended channel names. +- st,filter-order: SinC filter order from 0 to 5. + 0: FastSinC + [1-5]: order 1 to 5. + For audio purpose it is recommended to use order 3 to 5. +- #io-channel-cells = <1>: See the IIO bindings section "IIO consumers". + +Required properties for "st,stm32-dfsdm-adc" compatibility: +- io-channels: From common IIO binding. Used to pipe external sigma delta + modulator or internal ADC output to DFSDM channel. + This is not required for "st,stm32-dfsdm-pdm" compatibility as + PDM microphone is binded in Audio DT node. + +Required properties for "st,stm32-dfsdm-pdm" compatibility: +- #sound-dai-cells: Must be set to 0. +- dma: DMA controller phandle and DMA request line associated to the + filter instance (specified by the field "reg") +- dma-names: Must be "rx" + +Optional properties: +- st,adc-channel-types: Single-ended channel input type. + - "SPI_R": SPI with data on rising edge (default) + - "SPI_F": SPI with data on falling edge + - "MANCH_R": manchester codec, rising edge = logic 0 + - "MANCH_F": manchester codec, falling edge = logic 1 +- st,adc-channel-clk-src: Conversion clock source. + - "CLKIN": external SPI clock (CLKIN x) + - "CLKOUT": internal SPI clock (CLKOUT) (default) + - "CLKOUT_F": internal SPI clock divided by 2 (falling edge). + - "CLKOUT_R": internal SPI clock divided by 2 (rising edge). + +- st,adc-alt-channel: Must be defined if two sigma delta modulator are + connected on same SPI input. + If not set, channel n is connected to SPI input n. + If set, channel n is connected to SPI input n + 1. + +- st,filter0-sync: Set to 1 to synchronize with DFSDM filter instance 0. + Used for multi microphones synchronization. + +Example of a sigma delta adc connected on DFSDM SPI port 0 +and a pdm microphone connected on DFSDM SPI port 1: + + ads1202: simple_sd_adc@0 { + compatible = "ads1202"; + #io-channel-cells = <1>; + }; + + dfsdm: dfsdm@40017000 { + compatible = "st,stm32h7-dfsdm"; + reg = <0x40017000 0x400>; + clocks = <&timer_clk>; + clock-names = "dfsdm"; + + dfsdm_adc0: dfsdm-adc0@0 { + compatible = "st,stm32-dfsdm-adc"; + #io-channel-cells = <1>; + reg = <0>; + interrupts = <110>; + st,adc-channels = <0>; + st,adc-channel-names = "sd_adc0"; + st,adc-channel-types = "SPI_F"; + st,adc-channel-clk-src = "CLKOUT"; + io-channels = <&ads1202 0>; + st,filter-order = <3>; + + dfsdm_pdm1: dfsdm-pdm@0 { + compatible = "st,stm32-dfsdm-pdm"; + reg = <1>; + interrupts = <111>; + dmas = <&dmamux1 102 0x400 0x00>; + dma-names = "rx"; + st,adc-channels = <1>; + st,adc-channel-names = "pdm1"; + st,adc-channel-types = "SPI_R"; + st,adc-channel-clk-src = "CLKOUT"; + st,filter-order = <5>; + }; + }
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. stm32-dfsdm-adc driver allows to handle sigma delta ADC.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com --- V2 -> V3 : - Split audio and ADC support in 2 drivers - Implement DMA cyclic mode - Add SPI bus Trigger for buffer management
drivers/iio/adc/Kconfig | 26 ++ drivers/iio/adc/Makefile | 2 + drivers/iio/adc/stm32-dfsdm-adc.c | 419 +++++++++++++++++++++++ drivers/iio/adc/stm32-dfsdm-core.c | 658 +++++++++++++++++++++++++++++++++++++ drivers/iio/adc/stm32-dfsdm.h | 372 +++++++++++++++++++++ 5 files changed, 1477 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 d411d66..3e0eb11 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -452,6 +452,32 @@ config STM32_ADC This driver can also be built as a module. If so, the module will be called stm32-adc.
+config STM32_DFSDM_CORE + tristate "STMicroelectronics STM32 dfsdm core" + depends on (ARCH_STM32 && OF) || COMPILE_TEST + select REGMAP + select REGMAP_MMIO + help + Select this option to enable the driver 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-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_DMAENGINE + select IIO_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 X86 && ISA_BUS_API diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index c68819c..161f271 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -43,6 +43,8 @@ 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 +obj-$(CONFIG_STM32_DFSDM_CORE) += 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..ebcb3b4 --- /dev/null +++ b/drivers/iio/adc/stm32-dfsdm-adc.c @@ -0,0 +1,419 @@ +/* + * This file is the ADC part of of the STM32 DFSDM 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/interrupt.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/slab.h> + +#include <linux/iio/hw_consumer.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#include "stm32-dfsdm.h" + +#define DFSDM_TIMEOUT_US 100000 +#define DFSDM_TIMEOUT (msecs_to_jiffies(DFSDM_TIMEOUT_US / 1000)) + +struct stm32_dfsdm_adc { + struct stm32_dfsdm *dfsdm; + unsigned int fl_id; + unsigned int ch_id; + + unsigned int oversamp; + + struct completion completion; + + u32 *buffer; + + /* Hardware consumer structure for Front End IIO */ + struct iio_hw_consumer *hwc; +}; + +static int stm32_dfsdm_start_conv(struct stm32_dfsdm_adc *adc) +{ + int ret; + + ret = stm32_dfsdm_start_dfsdm(adc->dfsdm); + if (ret < 0) + return ret; + + ret = stm32_dfsdm_start_channel(adc->dfsdm, adc->ch_id); + if (ret < 0) + goto stop_dfsdm; + + 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: + stm32_dfsdm_stop_channel(adc->dfsdm, adc->ch_id); +stop_dfsdm: + stm32_dfsdm_stop_dfsdm(adc->dfsdm); + + return ret; +} + +static void stm32_dfsdm_stop_conv(struct stm32_dfsdm_adc *adc) +{ + stm32_dfsdm_stop_filter(adc->dfsdm, adc->fl_id); + + stm32_dfsdm_stop_channel(adc->dfsdm, adc->ch_id); + + stm32_dfsdm_stop_dfsdm(adc->dfsdm); +} + +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; + + /* Unmask IRQ for regular conversion achievement*/ + ret = regmap_update_bits(adc->dfsdm->regmap, DFSDM_CR2(adc->fl_id), + DFSDM_CR2_REOCIE_MASK, DFSDM_CR2_REOCIE(1)); + if (ret < 0) + return ret; + + ret = stm32_dfsdm_start_conv(adc); + if (ret < 0) + return ret; + + 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) { + dev_warn(&indio_dev->dev, "Conversion timed out!\n"); + ret = -ETIMEDOUT; + } else if (timeout < 0) { + ret = timeout; + } else { + dev_dbg(&indio_dev->dev, "Converted val %#x\n", *res); + ret = IIO_VAL_INT; + } + + /* 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)); + + stm32_dfsdm_stop_conv(adc); + + 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); + if (ret < 0) { + dev_err(&indio_dev->dev, + "%s: Conversion failed (channel %d)\n", + __func__, chan->channel); + return ret; + } + + iio_hw_consumer_disable(adc->hwc); + + 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, + .driver_module = THIS_MODULE, +}; + +static irqreturn_t stm32_dfsdm_irq(int irq, void *arg) +{ + struct stm32_dfsdm_adc *adc = arg; + struct regmap *regmap = adc->dfsdm->regmap; + unsigned int status; + + regmap_read(regmap, DFSDM_ISR(adc->fl_id), &status); + + 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) { + regmap_update_bits(regmap, DFSDM_ICR(adc->fl_id), + DFSDM_ICR_CLRROVRF_MASK, + DFSDM_ICR_CLRROVRF_MASK); + } + + return IRQ_HANDLED; +} + +static int stm32_dfsdm_postenable(struct iio_dev *indio_dev) +{ + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + + return stm32_dfsdm_start_conv(adc); +} + +static int stm32_dfsdm_predisable(struct iio_dev *indio_dev) +{ + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + + stm32_dfsdm_stop_conv(adc); + return 0; +} + +static const struct iio_buffer_setup_ops stm32_dfsdm_buffer_setup_ops = { + .postenable = &stm32_dfsdm_postenable, + .predisable = &stm32_dfsdm_predisable, +}; + +static int stm32_dfsdm_adc_chan_init_one(struct iio_dev *indio_dev, + struct iio_chan_spec *chan, + int ch_idx) +{ + struct iio_chan_spec *ch = &chan[ch_idx]; + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + int ret; + + ret = stm32_dfsdm_channel_parse_of(adc->dfsdm, indio_dev, chan, ch_idx); + + ch->type = IIO_VOLTAGE; + ch->indexed = 1; + ch->scan_index = ch_idx; + + /* + * 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) | + 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_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->dfsdm->num_chs) { + dev_err(&indio_dev->dev, "Bad st,adc-channels?\n"); + return num_ch < 0 ? num_ch : -EINVAL; + } + + /* + * Number of channel per filter is temporary limited to 1. + * Restriction should be cleaned with scan mode + */ + if (num_ch > 1) { + dev_err(&indio_dev->dev, "Multi channel not yet supported\n"); + return -EINVAL; + } + + /* Bind to SD modulator IIO device */ + adc->hwc = iio_hw_consumer_alloc(&indio_dev->dev); + if (IS_ERR(adc->hwc)) + return -EPROBE_DEFER; + + channels = devm_kcalloc(&indio_dev->dev, num_ch, sizeof(*channels), + GFP_KERNEL); + if (!channels) + return -ENOMEM; + + 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: + iio_hw_consumer_free(adc->hwc); + return ret; +} + +static const struct of_device_id stm32_dfsdm_adc_match[] = { + { .compatible = "st,stm32-dfsdm-adc"}, + {} +}; + +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; + struct iio_dev *iio; + char *name; + int ret, irq, val; + + 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->info = &stm32_dfsdm_info_adc; + 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 = kzalloc(sizeof("dfsdm-adc0"), GFP_KERNEL); + if (!name) + return -ENOMEM; + 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; + + ret = stm32_dfsdm_adc_chan_init(iio); + if (ret < 0) + return ret; + + init_completion(&adc->completion); + + 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 *iio = iio_priv_to_dev(adc); + + iio_device_unregister(iio); + + 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, +}; +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"); diff --git a/drivers/iio/adc/stm32-dfsdm-core.c b/drivers/iio/adc/stm32-dfsdm-core.c new file mode 100644 index 0000000..488e456 --- /dev/null +++ b/drivers/iio/adc/stm32-dfsdm-core.c @@ -0,0 +1,658 @@ +/* + * This file is part the core part STM32 DFSDM driver + * + * Copyright (C) 2017, STMicroelectronics - All Rights Reserved + * Author(s): Arnaud Pouliquen arnaud.pouliquen@st.com for STMicroelectronics. + * + * 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. + */ + +#include <linux/clk.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/regmap.h> +#include <linux/slab.h> + +#include <linux/iio/trigger.h> +#include <linux/iio/sysfs.h> + +#include "stm32-dfsdm.h" + +struct stm32_dfsdm_dev_data { + unsigned int num_filters; + unsigned int num_channels; + const struct regmap_config *regmap_cfg; +}; + +#define STM32H7_DFSDM_NUM_FILTERS 4 +#define STM32H7_DFSDM_NUM_CHANNELS 8 + +#define DFSDM_MAX_INT_OVERSAMPLING 256 + +#define DFSDM_MAX_FL_OVERSAMPLING 1024 + +#define DFSDM_MAX_RES BIT(31) +#define DFSDM_DATA_RES BIT(23) + +static bool stm32_dfsdm_volatile_reg(struct device *dev, unsigned int reg) +{ + if (reg < DFSDM_FILTER_BASE_ADR) + return false; + + /* + * Mask is done on register to avoid to list registers of all them + * filter instances. + */ + switch (reg & DFSDM_FILTER_REG_MASK) { + case DFSDM_CR1(0) & DFSDM_FILTER_REG_MASK: + case DFSDM_ISR(0) & DFSDM_FILTER_REG_MASK: + case DFSDM_JDATAR(0) & DFSDM_FILTER_REG_MASK: + case DFSDM_RDATAR(0) & DFSDM_FILTER_REG_MASK: + return true; + } + + return false; +} + +static const struct regmap_config stm32h7_dfsdm_regmap_cfg = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = sizeof(u32), + .max_register = 0x2B8, + .volatile_reg = stm32_dfsdm_volatile_reg, + .fast_io = true, +}; + +static const struct stm32_dfsdm_dev_data stm32h7_dfsdm_data = { + .num_filters = STM32H7_DFSDM_NUM_FILTERS, + .num_channels = STM32H7_DFSDM_NUM_CHANNELS, + .regmap_cfg = &stm32h7_dfsdm_regmap_cfg, +}; + +struct dfsdm_priv { + struct platform_device *pdev; /* platform device*/ + + struct stm32_dfsdm dfsdm; /* common data exported for all instances */ + + unsigned int spi_clk_out_div; /* SPI clkout divider value */ + atomic_t n_active_ch; /* number of current active channels */ + + /* Clock */ + struct clk *clk; /* DFSDM clock */ + struct clk *aclk; /* audio clock */ +}; + +/** + * stm32_dfsdm_set_osrs - compute filter parameters. + * + * Enable interface if n_active_ch is not null. + * @dfsdm: Handle used to retrieve dfsdm context. + * @fast: Fast mode enabled or disabled + * @oversamp: Expected oversampling between filtered sample and SD input stream + */ +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; + } + + /* + * Looks for filter and integrator oversampling ratios which allows + * to reach 24 bits data output resolution. + * Leave at once 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; +} + +/** + * stm32_dfsdm_start_dfsdm - start global dfsdm IP interface. + * + * Enable interface if n_active_ch is not null. + * @dfsdm: Handle used to retrieve dfsdm context. + */ +int stm32_dfsdm_start_dfsdm(struct stm32_dfsdm *dfsdm) +{ + struct dfsdm_priv *priv = container_of(dfsdm, struct dfsdm_priv, dfsdm); + struct device *dev = &priv->pdev->dev; + unsigned int clk_div = priv->spi_clk_out_div; + int ret; + + if (atomic_inc_return(&priv->n_active_ch) == 1) { + /* Enable clocks */ + ret = clk_prepare_enable(priv->clk); + if (ret < 0) { + dev_err(dev, "Failed to start clock\n"); + return ret; + } + if (priv->aclk) { + ret = clk_prepare_enable(priv->aclk); + if (ret < 0) { + dev_err(dev, "Failed to start audio clock\n"); + goto disable_clk; + } + } + + /* Output the SPI CLKOUT (if clk_div == 0 clock if OFF) */ + ret = regmap_update_bits(dfsdm->regmap, DFSDM_CHCFGR1(0), + DFSDM_CHCFGR1_CKOUTDIV_MASK, + DFSDM_CHCFGR1_CKOUTDIV(clk_div)); + if (ret < 0) + goto disable_aclk; + + /* Global enable of DFSDM interface */ + ret = regmap_update_bits(dfsdm->regmap, DFSDM_CHCFGR1(0), + DFSDM_CHCFGR1_DFSDMEN_MASK, + DFSDM_CHCFGR1_DFSDMEN(1)); + if (ret < 0) + goto disable_aclk; + } + + dev_dbg(dev, "%s: n_active_ch %d\n", __func__, + atomic_read(&priv->n_active_ch)); + + return 0; + +disable_aclk: + clk_disable_unprepare(priv->aclk); +disable_clk: + clk_disable_unprepare(priv->clk); + + return ret; +} + +/** + * stm32_dfsdm_stop_dfsdm - stop global DFSDM IP interface. + * + * Disable interface if n_active_ch is null + * @dfsdm: Handle used to retrieve dfsdm context. + */ +int stm32_dfsdm_stop_dfsdm(struct stm32_dfsdm *dfsdm) +{ + struct dfsdm_priv *priv = container_of(dfsdm, struct dfsdm_priv, dfsdm); + int ret; + + if (atomic_dec_and_test(&priv->n_active_ch)) { + /* Global disable of DFSDM interface */ + ret = regmap_update_bits(dfsdm->regmap, DFSDM_CHCFGR1(0), + DFSDM_CHCFGR1_DFSDMEN_MASK, + DFSDM_CHCFGR1_DFSDMEN(0)); + if (ret < 0) + return ret; + + /* Stop SPI CLKOUT */ + ret = regmap_update_bits(dfsdm->regmap, DFSDM_CHCFGR1(0), + DFSDM_CHCFGR1_CKOUTDIV_MASK, + DFSDM_CHCFGR1_CKOUTDIV(0)); + if (ret < 0) + return ret; + + /* Disable clocks */ + clk_disable_unprepare(priv->clk); + if (priv->aclk) + clk_disable_unprepare(priv->aclk); + } + dev_dbg(&priv->pdev->dev, "%s: n_active_ch %d\n", __func__, + atomic_read(&priv->n_active_ch)); + + return 0; +} + +/** + * stm32_dfsdm_start_channel + * Start DFSDM IP channels and associated interface. + * + * @dfsdm: Handle used to retrieve dfsdm context. + * @ch_id: Channel index. + */ +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)); +} + +/** + * stm32_dfsdm_stop_channel + * Stop DFSDM IP channels and associated interface. + * + * @dfsdm: Handle used to retrieve dfsdm context. + * @ch_id: Channel index. + */ +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)); +} + +/** + * stm32_dfsdm_chan_configure + * Configure DFSDM IP channels and associated interface. + * + * @dfsdm: Handle used to retrieve dfsdm context. + * @ch_id: channel index. + */ +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)); +} + +/** + * stm32_dfsdm_start_filter - Start DFSDM IP filter conversion. + * + * @dfsdm: Handle used to retrieve dfsdm context. + * @fl_id: Filter index. + */ +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)); +} + +/** + * stm32_dfsdm_stop_filter - Stop DFSDM IP filter conversion. + * + * @dfsdm: Handle used to retrieve dfsdm context. + * @fl_id: Filter index. + */ +void stm32_dfsdm_stop_filter(struct stm32_dfsdm *dfsdm, unsigned int fl_id) +{ + /* Mask IRQ for regular conversion achievement*/ + regmap_update_bits(dfsdm->regmap, DFSDM_CR2(fl_id), + DFSDM_CR2_REOCIE_MASK, DFSDM_CR2_REOCIE(0)); + /* Disable conversion */ + regmap_update_bits(dfsdm->regmap, DFSDM_CR1(fl_id), + DFSDM_CR1_DFEN_MASK, DFSDM_CR1_DFEN(0)); +} + +/** + * stm32_dfsdm_filter_configure - Configure DFSDM IP filter and associate it + * to channel. + * + * @dfsdm: Handle used to retrieve dfsdm context. + * @fl_id: channel index. + * @fl_id: Filter index. + */ +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)); + + /* Filter order and Oversampling */ + if (!ret) + ret = regmap_update_bits(regmap, DFSDM_FCR(fl_id), + DFSDM_FCR_FOSR_MASK, + DFSDM_FCR_FOSR(fl->fosr)); + + if (!ret) + ret = regmap_update_bits(regmap, DFSDM_FCR(fl_id), + DFSDM_FCR_FORD_MASK, + DFSDM_FCR_FORD(fl->ford)); + + /* If only one channel 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)); + + return regmap_update_bits(regmap, DFSDM_CR1(fl_id), + DFSDM_CR1_RSYNC_MASK, + DFSDM_CR1_RSYNC(fl->sync_mode)); +} + +static const struct iio_trigger_ops dfsdm_trigger_ops = { + .owner = THIS_MODULE, +}; + +static int stm32_dfsdm_setup_spi_trigger(struct platform_device *pdev, + struct stm32_dfsdm *dfsdm) +{ + /* + * To be able to use buffer consumer interface a trigger is needed. + * As conversion are trigged by PDM samples from SPI bus, that makes + * sense to define the serial interface ( SPI or manchester) as + * trigger source. + */ + + struct iio_trigger *trig; + int ret; + + trig = devm_iio_trigger_alloc(&pdev->dev, DFSDM_SPI_TRIGGER_NAME); + if (!trig) + return -ENOMEM; + + trig->dev.parent = pdev->dev.parent; + trig->ops = &dfsdm_trigger_ops; + + iio_trigger_set_drvdata(trig, dfsdm); + + ret = devm_iio_trigger_register(&pdev->dev, trig); + if (ret) + return ret; + + return 0; +} + +int stm32_dfsdm_channel_parse_of(struct stm32_dfsdm *dfsdm, + struct iio_dev *indio_dev, + struct iio_chan_spec *chan, int chan_idx) +{ + struct iio_chan_spec *ch = &chan[chan_idx]; + struct stm32_dfsdm_channel *df_ch; + const char *of_str; + 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; + } + + 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); + val = stm32_dfsdm_str2val(of_str, stm32_dfsdm_chan_type); + if (ret < 0 || val < 0) + df_ch->type = 0; + else + df_ch->type = val; + + ret = of_property_read_string_index(indio_dev->dev.of_node, + "st,adc-channel-clk-src", chan_idx, + &of_str); + val = stm32_dfsdm_str2val(of_str, stm32_dfsdm_chan_src); + if (ret < 0 || val < 0) + df_ch->src = 0; + else + 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_parse_of(struct platform_device *pdev, + struct dfsdm_priv *priv) +{ + struct device_node *node = pdev->dev.of_node; + struct resource *res; + unsigned long clk_freq; + unsigned int spi_freq, rem; + int ret; + + if (!node) + return -EINVAL; + + /* Get resources */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "Failed to get memory resource\n"); + return -ENODEV; + } + priv->dfsdm.phys_base = res->start; + priv->dfsdm.base = devm_ioremap_resource(&pdev->dev, res); + + /* Source clock */ + priv->clk = devm_clk_get(&pdev->dev, "dfsdm"); + if (IS_ERR(priv->clk)) { + dev_err(&pdev->dev, "No stm32_dfsdm_clk clock found\n"); + return -EINVAL; + } + + priv->aclk = devm_clk_get(&pdev->dev, "audio"); + if (IS_ERR(priv->aclk)) + priv->aclk = NULL; + + if (priv->aclk) + clk_freq = clk_get_rate(priv->aclk); + else + clk_freq = clk_get_rate(priv->clk); + + /* SPI clock freq */ + ret = of_property_read_u32(pdev->dev.of_node, "spi-max-frequency", + &spi_freq); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to get spi-max-frequency\n"); + return ret; + } + + priv->spi_clk_out_div = div_u64_rem(clk_freq, spi_freq, &rem) - 1; + priv->dfsdm.spi_master_freq = spi_freq; + + if (rem) { + dev_warn(&pdev->dev, "SPI clock not accurate\n"); + dev_warn(&pdev->dev, "%ld = %d * %d + %d\n", + clk_freq, spi_freq, priv->spi_clk_out_div + 1, rem); + } + + return 0; +}; + +static const struct of_device_id stm32_dfsdm_of_match[] = { + { + .compatible = "st,stm32h7-dfsdm", + .data = &stm32h7_dfsdm_data, + }, + {} +}; +MODULE_DEVICE_TABLE(of, stm32_dfsdm_of_match); + +static int stm32_dfsdm_remove(struct platform_device *pdev) +{ + of_platform_depopulate(&pdev->dev); + + return 0; +} + +static int stm32_dfsdm_probe(struct platform_device *pdev) +{ + struct dfsdm_priv *priv; + struct device_node *pnode = pdev->dev.of_node; + const struct of_device_id *of_id; + const struct stm32_dfsdm_dev_data *dev_data; + struct stm32_dfsdm *dfsdm; + int ret, i; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->pdev = pdev; + + /* Populate data structure depending on compatibility */ + of_id = of_match_node(stm32_dfsdm_of_match, pnode); + 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; + dfsdm = &priv->dfsdm; + dfsdm->fl_list = devm_kcalloc(&pdev->dev, dev_data->num_filters, + sizeof(*dfsdm->fl_list), GFP_KERNEL); + if (!dfsdm->fl_list) + return -ENOMEM; + + dfsdm->num_fls = dev_data->num_filters; + dfsdm->ch_list = devm_kcalloc(&pdev->dev, dev_data->num_channels, + sizeof(*dfsdm->ch_list), + GFP_KERNEL); + if (!dfsdm->ch_list) + return -ENOMEM; + dfsdm->num_chs = dev_data->num_channels; + + ret = stm32_dfsdm_parse_of(pdev, priv); + if (ret < 0) + return ret; + + dfsdm->regmap = devm_regmap_init_mmio(&pdev->dev, dfsdm->base, + &stm32h7_dfsdm_regmap_cfg); + if (IS_ERR(dfsdm->regmap)) { + ret = PTR_ERR(dfsdm->regmap); + dev_err(&pdev->dev, "%s: Failed to allocate regmap: %d\n", + __func__, ret); + return ret; + } + + for (i = 0; i < STM32H7_DFSDM_NUM_FILTERS; i++) { + struct stm32_dfsdm_filter *fl = &dfsdm->fl_list[i]; + + fl->id = i; + } + + platform_set_drvdata(pdev, dfsdm); + + ret = stm32_dfsdm_setup_spi_trigger(pdev, dfsdm); + if (ret < 0) + return ret; + + return of_platform_populate(pnode, NULL, NULL, &pdev->dev); +} + +static struct platform_driver stm32_dfsdm_driver = { + .probe = stm32_dfsdm_probe, + .remove = stm32_dfsdm_remove, + .driver = { + .name = "stm32-dfsdm", + .of_match_table = stm32_dfsdm_of_match, + }, +}; + +module_platform_driver(stm32_dfsdm_driver); + +MODULE_AUTHOR("Arnaud Pouliquen arnaud.pouliquen@st.com"); +MODULE_DESCRIPTION("STMicroelectronics STM32 dfsdm driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/stm32-dfsdm.h b/drivers/iio/adc/stm32-dfsdm.h new file mode 100644 index 0000000..bb7d74f --- /dev/null +++ b/drivers/iio/adc/stm32-dfsdm.h @@ -0,0 +1,371 @@ +/* + * This file is part of STM32 DFSDM driver + * + * Copyright (C) 2017, STMicroelectronics - All Rights Reserved + * Author(s): Arnaud Pouliquen arnaud.pouliquen@st.com. + * + * License terms: GPL V2.0. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + */ +#ifndef MDF_STM32_DFSDM__H +#define MDF_STM32_DFSDM__H + +#include <linux/bitfield.h> + +#include <linux/iio/iio.h> +/* + * STM32 DFSDM - global register map + * ________________________________________________________ + * | Offset | Registers block | + * -------------------------------------------------------- + * | 0x000 | CHANNEL 0 + COMMON CHANNEL FIELDS | + * -------------------------------------------------------- + * | 0x020 | CHANNEL 1 | + * -------------------------------------------------------- + * | ... | ..... | + * -------------------------------------------------------- + * | 0x0E0 | CHANNEL 7 | + * -------------------------------------------------------- + * | 0x100 | FILTER 0 + COMMON FILTER FIELDs | + * -------------------------------------------------------- + * | 0x200 | FILTER 1 | + * -------------------------------------------------------- + * | 0x300 | FILTER 2 | + * -------------------------------------------------------- + * | 0x400 | FILTER 3 | + * -------------------------------------------------------- + */ + +/* + * Channels register definitions + */ +#define DFSDM_CHCFGR1(y) ((y) * 0x20 + 0x00) +#define DFSDM_CHCFGR2(y) ((y) * 0x20 + 0x04) +#define DFSDM_AWSCDR(y) ((y) * 0x20 + 0x08) +#define DFSDM_CHWDATR(y) ((y) * 0x20 + 0x0C) +#define DFSDM_CHDATINR(y) ((y) * 0x20 + 0x10) + +/* CHCFGR1: Channel configuration register 1 */ +#define DFSDM_CHCFGR1_SITP_MASK GENMASK(1, 0) +#define DFSDM_CHCFGR1_SITP(v) FIELD_PREP(DFSDM_CHCFGR1_SITP_MASK, v) +#define DFSDM_CHCFGR1_SPICKSEL_MASK GENMASK(3, 2) +#define DFSDM_CHCFGR1_SPICKSEL(v) FIELD_PREP(DFSDM_CHCFGR1_SPICKSEL_MASK, v) +#define DFSDM_CHCFGR1_SCDEN_MASK BIT(5) +#define DFSDM_CHCFGR1_SCDEN(v) FIELD_PREP(DFSDM_CHCFGR1_SCDEN_MASK, v) +#define DFSDM_CHCFGR1_CKABEN_MASK BIT(6) +#define DFSDM_CHCFGR1_CKABEN(v) FIELD_PREP(DFSDM_CHCFGR1_CKABEN_MASK, v) +#define DFSDM_CHCFGR1_CHEN_MASK BIT(7) +#define DFSDM_CHCFGR1_CHEN(v) FIELD_PREP(DFSDM_CHCFGR1_CHEN_MASK, v) +#define DFSDM_CHCFGR1_CHINSEL_MASK BIT(8) +#define DFSDM_CHCFGR1_CHINSEL(v) FIELD_PREP(DFSDM_CHCFGR1_CHINSEL_MASK, v) +#define DFSDM_CHCFGR1_DATMPX_MASK GENMASK(13, 12) +#define DFSDM_CHCFGR1_DATMPX(v) FIELD_PREP(DFSDM_CHCFGR1_DATMPX_MASK, v) +#define DFSDM_CHCFGR1_DATPACK_MASK GENMASK(15, 14) +#define DFSDM_CHCFGR1_DATPACK(v) FIELD_PREP(DFSDM_CHCFGR1_DATPACK_MASK, v) +#define DFSDM_CHCFGR1_CKOUTDIV_MASK GENMASK(23, 16) +#define DFSDM_CHCFGR1_CKOUTDIV(v) FIELD_PREP(DFSDM_CHCFGR1_CKOUTDIV_MASK, v) +#define DFSDM_CHCFGR1_CKOUTSRC_MASK BIT(30) +#define DFSDM_CHCFGR1_CKOUTSRC(v) FIELD_PREP(DFSDM_CHCFGR1_CKOUTSRC_MASK, v) +#define DFSDM_CHCFGR1_DFSDMEN_MASK BIT(31) +#define DFSDM_CHCFGR1_DFSDMEN(v) FIELD_PREP(DFSDM_CHCFGR1_DFSDMEN_MASK, v) + +/* CHCFGR2: Channel configuration register 2 */ +#define DFSDM_CHCFGR2_DTRBS_MASK GENMASK(7, 3) +#define DFSDM_CHCFGR2_DTRBS(v) FIELD_PREP(DFSDM_CHCFGR2_DTRBS_MASK, v) +#define DFSDM_CHCFGR2_OFFSET_MASK GENMASK(31, 8) +#define DFSDM_CHCFGR2_OFFSET(v) FIELD_PREP(DFSDM_CHCFGR2_OFFSET_MASK, v) + +/* AWSCDR: Channel analog watchdog and short circuit detector */ +#define DFSDM_AWSCDR_SCDT_MASK GENMASK(7, 0) +#define DFSDM_AWSCDR_SCDT(v) FIELD_PREP(DFSDM_AWSCDR_SCDT_MASK, v) +#define DFSDM_AWSCDR_BKSCD_MASK GENMASK(15, 12) +#define DFSDM_AWSCDR_BKSCD(v) FIELD_PREP(DFSDM_AWSCDR_BKSCD_MASK, v) +#define DFSDM_AWSCDR_AWFOSR_MASK GENMASK(20, 16) +#define DFSDM_AWSCDR_AWFOSR(v) FIELD_PREP(DFSDM_AWSCDR_AWFOSR_MASK, v) +#define DFSDM_AWSCDR_AWFORD_MASK GENMASK(23, 22) +#define DFSDM_AWSCDR_AWFORD(v) FIELD_PREP(DFSDM_AWSCDR_AWFORD_MASK, v) + +/* + * Filters register definitions + */ +#define DFSDM_FILTER_BASE_ADR 0x100 +#define DFSDM_FILTER_REG_MASK 0x7F +#define DFSDM_FILTER_X_BASE_ADR(x) ((x) * 0x80 + DFSDM_FILTER_BASE_ADR) + +#define DFSDM_CR1(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x00) +#define DFSDM_CR2(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x04) +#define DFSDM_ISR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x08) +#define DFSDM_ICR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x0C) +#define DFSDM_JCHGR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x10) +#define DFSDM_FCR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x14) +#define DFSDM_JDATAR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x18) +#define DFSDM_RDATAR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x1C) +#define DFSDM_AWHTR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x20) +#define DFSDM_AWLTR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x24) +#define DFSDM_AWSR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x28) +#define DFSDM_AWCFR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x2C) +#define DFSDM_EXMAX(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x30) +#define DFSDM_EXMIN(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x34) +#define DFSDM_CNVTIMR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x38) + +/* CR1 Control register 1 */ +#define DFSDM_CR1_DFEN_MASK BIT(0) +#define DFSDM_CR1_DFEN(v) FIELD_PREP(DFSDM_CR1_DFEN_MASK, v) +#define DFSDM_CR1_JSWSTART_MASK BIT(1) +#define DFSDM_CR1_JSWSTART(v) FIELD_PREP(DFSDM_CR1_JSWSTART_MASK, v) +#define DFSDM_CR1_JSYNC_MASK BIT(3) +#define DFSDM_CR1_JSYNC(v) FIELD_PREP(DFSDM_CR1_JSYNC_MASK, v) +#define DFSDM_CR1_JSCAN_MASK BIT(4) +#define DFSDM_CR1_JSCAN(v) FIELD_PREP(DFSDM_CR1_JSCAN_MASK, v) +#define DFSDM_CR1_JDMAEN_MASK BIT(5) +#define DFSDM_CR1_JDMAEN(v) FIELD_PREP(DFSDM_CR1_JDMAEN_MASK, v) +#define DFSDM_CR1_JEXTSEL_MASK GENMASK(12, 8) +#define DFSDM_CR1_JEXTSEL(v) FIELD_PREP(DFSDM_CR1_JEXTSEL_MASK, v) +#define DFSDM_CR1_JEXTEN_MASK GENMASK(14, 13) +#define DFSDM_CR1_JEXTEN(v) FIELD_PREP(DFSDM_CR1_JEXTEN_MASK, v) +#define DFSDM_CR1_RSWSTART_MASK BIT(17) +#define DFSDM_CR1_RSWSTART(v) FIELD_PREP(DFSDM_CR1_RSWSTART_MASK, v) +#define DFSDM_CR1_RCONT_MASK BIT(18) +#define DFSDM_CR1_RCONT(v) FIELD_PREP(DFSDM_CR1_RCONT_MASK, v) +#define DFSDM_CR1_RSYNC_MASK BIT(19) +#define DFSDM_CR1_RSYNC(v) FIELD_PREP(DFSDM_CR1_RSYNC_MASK, v) +#define DFSDM_CR1_RDMAEN_MASK BIT(21) +#define DFSDM_CR1_RDMAEN(v) FIELD_PREP(DFSDM_CR1_RDMAEN_MASK, v) +#define DFSDM_CR1_RCH_MASK GENMASK(26, 24) +#define DFSDM_CR1_RCH(v) FIELD_PREP(DFSDM_CR1_RCH_MASK, v) +#define DFSDM_CR1_FAST_MASK BIT(29) +#define DFSDM_CR1_FAST(v) FIELD_PREP(DFSDM_CR1_FAST_MASK, v) +#define DFSDM_CR1_AWFSEL_MASK BIT(30) +#define DFSDM_CR1_AWFSEL(v) FIELD_PREP(DFSDM_CR1_AWFSEL_MASK, v) + +/* CR2: Control register 2 */ +#define DFSDM_CR2_IE_MASK GENMASK(6, 0) +#define DFSDM_CR2_IE(v) FIELD_PREP(DFSDM_CR2_IE_MASK, v) +#define DFSDM_CR2_JEOCIE_MASK BIT(0) +#define DFSDM_CR2_JEOCIE(v) FIELD_PREP(DFSDM_CR2_JEOCIE_MASK, v) +#define DFSDM_CR2_REOCIE_MASK BIT(1) +#define DFSDM_CR2_REOCIE(v) FIELD_PREP(DFSDM_CR2_REOCIE_MASK, v) +#define DFSDM_CR2_JOVRIE_MASK BIT(2) +#define DFSDM_CR2_JOVRIE(v) FIELD_PREP(DFSDM_CR2_JOVRIE_MASK, v) +#define DFSDM_CR2_ROVRIE_MASK BIT(3) +#define DFSDM_CR2_ROVRIE(v) FIELD_PREP(DFSDM_CR2_ROVRIE_MASK, v) +#define DFSDM_CR2_AWDIE_MASK BIT(4) +#define DFSDM_CR2_AWDIE(v) FIELD_PREP(DFSDM_CR2_AWDIE_MASK, v) +#define DFSDM_CR2_SCDIE_MASK BIT(5) +#define DFSDM_CR2_SCDIE(v) FIELD_PREP(DFSDM_CR2_SCDIE_MASK, v) +#define DFSDM_CR2_CKABIE_MASK BIT(6) +#define DFSDM_CR2_CKABIE(v) FIELD_PREP(DFSDM_CR2_CKABIE_MASK, v) +#define DFSDM_CR2_EXCH_MASK GENMASK(15, 8) +#define DFSDM_CR2_EXCH(v) FIELD_PREP(DFSDM_CR2_EXCH_MASK, v) +#define DFSDM_CR2_AWDCH_MASK GENMASK(23, 16) +#define DFSDM_CR2_AWDCH(v) FIELD_PREP(DFSDM_CR2_AWDCH_MASK, v) + +/* ISR: Interrupt status register */ +#define DFSDM_ISR_JEOCF_MASK BIT(0) +#define DFSDM_ISR_JEOCF(v) FIELD_PREP(DFSDM_ISR_JEOCF_MASK, v) +#define DFSDM_ISR_REOCF_MASK BIT(1) +#define DFSDM_ISR_REOCF(v) FIELD_PREP(DFSDM_ISR_REOCF_MASK, v) +#define DFSDM_ISR_JOVRF_MASK BIT(2) +#define DFSDM_ISR_JOVRF(v) FIELD_PREP(DFSDM_ISR_JOVRF_MASK, v) +#define DFSDM_ISR_ROVRF_MASK BIT(3) +#define DFSDM_ISR_ROVRF(v) FIELD_PREP(DFSDM_ISR_ROVRF_MASK, v) +#define DFSDM_ISR_AWDF_MASK BIT(4) +#define DFSDM_ISR_AWDF(v) FIELD_PREP(DFSDM_ISR_AWDF_MASK, v) +#define DFSDM_ISR_JCIP_MASK BIT(13) +#define DFSDM_ISR_JCIP(v) FIELD_PREP(DFSDM_ISR_JCIP_MASK, v) +#define DFSDM_ISR_RCIP_MASK BIT(14) +#define DFSDM_ISR_RCIP(v) FIELD_PREP(DFSDM_ISR_RCIP, v) +#define DFSDM_ISR_CKABF_MASK GENMASK(23, 16) +#define DFSDM_ISR_CKABF(v) FIELD_PREP(DFSDM_ISR_CKABF_MASK, v) +#define DFSDM_ISR_SCDF_MASK GENMASK(31, 24) +#define DFSDM_ISR_SCDF(v) FIELD_PREP(DFSDM_ISR_SCDF_MASK, v) + +/* ICR: Interrupt flag clear register */ +#define DFSDM_ICR_CLRJOVRF_MASK BIT(2) +#define DFSDM_ICR_CLRJOVRF(v) FIELD_PREP(DFSDM_ICR_CLRJOVRF_MASK, v) +#define DFSDM_ICR_CLRROVRF_MASK BIT(3) +#define DFSDM_ICR_CLRROVRF(v) FIELD_PREP(DFSDM_ICR_CLRROVRF_MASK, v) +#define DFSDM_ICR_CLRCKABF_MASK GENMASK(23, 16) +#define DFSDM_ICR_CLRCKABF(v) FIELD_PREP(DFSDM_ICR_CLRCKABF_MASK, v) +#define DFSDM_ICR_CLRCKABF_CH_MASK(y) BIT(16 + (y)) +#define DFSDM_ICR_CLRCKABF_CH(v, y) \ + (((v) << (16 + (y))) & DFSDM_ICR_CLRCKABF_CH_MASK(y)) +#define DFSDM_ICR_CLRSCDF_MASK GENMASK(31, 24) +#define DFSDM_ICR_CLRSCDF(v) FIELD_PREP(DFSDM_ICR_CLRSCDF_MASK, v) +#define DFSDM_ICR_CLRSCDF_CH_MASK(y) BIT(24 + (y)) +#define DFSDM_ICR_CLRSCDF_CH(v, y) \ + (((v) << (24 + (y))) & DFSDM_ICR_CLRSCDF_MASK(y)) + +/* FCR: Filter control register */ +#define DFSDM_FCR_IOSR_MASK GENMASK(7, 0) +#define DFSDM_FCR_IOSR(v) FIELD_PREP(DFSDM_FCR_IOSR_MASK, v) +#define DFSDM_FCR_FOSR_MASK GENMASK(25, 16) +#define DFSDM_FCR_FOSR(v) FIELD_PREP(DFSDM_FCR_FOSR_MASK, v) +#define DFSDM_FCR_FORD_MASK GENMASK(31, 29) +#define DFSDM_FCR_FORD(v) FIELD_PREP(DFSDM_FCR_FORD_MASK, v) + +/* RDATAR: Filter data register for regular channel */ +#define DFSDM_DATAR_CH_MASK GENMASK(2, 0) +#define DFSDM_DATAR_DATA_OFFSET 8 +#define DFSDM_DATAR_DATA_MASK GENMASK(31, DFSDM_DATAR_DATA_OFFSET) + +/* AWLTR: Filter analog watchdog low threshold register */ +#define DFSDM_AWLTR_BKAWL_MASK GENMASK(3, 0) +#define DFSDM_AWLTR_BKAWL(v) FIELD_PREP(DFSDM_AWLTR_BKAWL_MASK, v) +#define DFSDM_AWLTR_AWLT_MASK GENMASK(31, 8) +#define DFSDM_AWLTR_AWLT(v) FIELD_PREP(DFSDM_AWLTR_AWLT_MASK, v) + +/* AWHTR: Filter analog watchdog low threshold register */ +#define DFSDM_AWHTR_BKAWH_MASK GENMASK(3, 0) +#define DFSDM_AWHTR_BKAWH(v) FIELD_PREP(DFSDM_AWHTR_BKAWH_MASK, v) +#define DFSDM_AWHTR_AWHT_MASK GENMASK(31, 8) +#define DFSDM_AWHTR_AWHT(v) FIELD_PREP(DFSDM_AWHTR_AWHT_MASK, v) + +/* AWSR: Filter watchdog status register */ +#define DFSDM_AWSR_AWLTF_MASK GENMASK(7, 0) +#define DFSDM_AWSR_AWLTF(v) FIELD_PREP(DFSDM_AWSR_AWLTF_MASK, v) +#define DFSDM_AWSR_AWHTF_MASK GENMASK(15, 8) +#define DFSDM_AWSR_AWHTF(v) FIELD_PREP(DFSDM_AWSR_AWHTF_MASK, v) + +/* AWCFR: Filter watchdog status register */ +#define DFSDM_AWCFR_AWLTF_MASK GENMASK(7, 0) +#define DFSDM_AWCFR_AWLTF(v) FIELD_PREP(DFSDM_AWCFR_AWLTF_MASK, v) +#define DFSDM_AWCFR_AWHTF_MASK GENMASK(15, 8) +#define DFSDM_AWCFR_AWHTF(v) FIELD_PREP(DFSDM_AWCFR_AWHTF_MASK, v) + +/* DFSDM filter order */ +enum stm32_dfsdm_sinc_order { + DFSDM_FASTSINC_ORDER, /* FastSinc filter type */ + DFSDM_SINC1_ORDER, /* Sinc 1 filter type */ + DFSDM_SINC2_ORDER, /* Sinc 2 filter type */ + DFSDM_SINC3_ORDER, /* Sinc 3 filter type */ + DFSDM_SINC4_ORDER, /* Sinc 4 filter type (N.A. for watchdog) */ + DFSDM_SINC5_ORDER, /* Sinc 5 filter type (N.A. for watchdog) */ + DFSDM_NB_SINC_ORDER, +}; + +/** + * struct stm32_dfsdm_filter - structure relative to stm32 FDSDM filter + * TODO: complete structure. + * @id: filetr ID, + */ +struct stm32_dfsdm_filter { + unsigned int id; + unsigned int iosr; /* integrator oversampling */ + unsigned int fosr; /* filter oversampling */ + enum stm32_dfsdm_sinc_order ford; + u64 res; /* output sample resolution */ + unsigned int sync_mode; /* filter suynchronized with filter0 */ + unsigned int fast; /* filter fast mode */ +}; + +/** + * struct stm32_dfsdm_channel - structure relative to stm32 FDSDM channel + * TODO: complete structure. + * @id: filetr ID, + */ +struct stm32_dfsdm_channel { + unsigned int id; /* id of the channel */ + unsigned int type; /* interface type linked to stm32_dfsdm_chan_type */ + unsigned int src; /* interface type linked to stm32_dfsdm_chan_src */ + unsigned int alt_si; /* use alternative serial input interface */ +}; + +/** + * struct stm32_dfsdm - stm32 FDSDM driver common data (for all instances) + * @base: control registers base cpu addr + * @phys_base: DFSDM IP register physical address. + * @fl_list: filter resources list + * @num_fl: number of filter resources available + * @ch_list: channel resources list + * @num_chs: number of channel resources available + */ +struct stm32_dfsdm { + void __iomem *base; + phys_addr_t phys_base; + struct regmap *regmap; + struct stm32_dfsdm_filter *fl_list; + unsigned int num_fls; + struct stm32_dfsdm_channel *ch_list; + unsigned int num_chs; + unsigned int spi_master_freq; +}; + +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 */ + { 0, 0}, +}; + +/* DFSDM channel serial spi clock source */ +enum stm32_dfsdm_spi_clk_src { + DFSDM_CHANNEL_SPI_CLOCK_EXTERNAL, + DFSDM_CHANNEL_SPI_CLOCK_INTERNAL, + DFSDM_CHANNEL_SPI_CLOCK_INTERNAL_DIV2_FALLING, + DFSDM_CHANNEL_SPI_CLOCK_INTERNAL_DIV2_RISING +}; + +/* 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 }, + { 0, 0 }, +}; + +/* DFSDM Serial interface trigger name */ +#define DFSDM_SPI_TRIGGER_NAME "DFSDM_SERIAL_IN" + +static inline 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; +} + +int stm32_dfsdm_set_osrs(struct stm32_dfsdm_filter *fl, unsigned int fast, + unsigned int oversamp); +int stm32_dfsdm_start_dfsdm(struct stm32_dfsdm *dfsdm); +int stm32_dfsdm_stop_dfsdm(struct stm32_dfsdm *dfsdm); + +int stm32_dfsdm_filter_configure(struct stm32_dfsdm *dfsdm, unsigned int fl_id, + unsigned int ch_id); +int stm32_dfsdm_start_filter(struct stm32_dfsdm *dfsdm, unsigned int fl_id); +void stm32_dfsdm_stop_filter(struct stm32_dfsdm *dfsdm, unsigned int fl_id); + +int stm32_dfsdm_chan_configure(struct stm32_dfsdm *dfsdm, + struct stm32_dfsdm_channel *ch); +int stm32_dfsdm_start_channel(struct stm32_dfsdm *dfsdm, unsigned int ch_id); +void stm32_dfsdm_stop_channel(struct stm32_dfsdm *dfsdm, unsigned int ch_id); + +int stm32_dfsdm_channel_parse_of(struct stm32_dfsdm *dfsdm, + struct iio_dev *indio_dev, + struct iio_chan_spec *chan, int chan_idx); + +#endif
Add DFSDM driver to handle PDM audio microphones.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com --- drivers/iio/adc/Kconfig | 13 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/stm32-dfsdm-audio.c | 720 ++++++++++++++++++++++++++++++ include/linux/iio/adc/stm32-dfsdm-audio.h | 41 ++ 4 files changed, 775 insertions(+) create mode 100644 drivers/iio/adc/stm32-dfsdm-audio.c create mode 100644 include/linux/iio/adc/stm32-dfsdm-audio.h
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 3e0eb11..c108933 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -478,6 +478,19 @@ config STM32_DFSDM_ADC This driver can also be built as a module. If so, the module will be called stm32-dfsdm-adc.
+config STM32_DFSDM_AUDIO + tristate "STMicroelectronics STM32 dfsdm audio + depends on (ARCH_STM32 && OF) || COMPILE_TEST + select STM32_DFSDM_CORE + select REGMAP_MMIO + select IIO_BUFFER_DMAENGINE + help + Select this option to support Audio PDM micophone 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-audio. + 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 161f271..79f975d 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -44,6 +44,7 @@ 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 +obj-$(CONFIG_STM32_DFSDM_AUDIO) += stm32-dfsdm-audio.o obj-$(CONFIG_STM32_DFSDM_CORE) += stm32-dfsdm-core.o obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o obj-$(CONFIG_TI_ADC0832) += ti-adc0832.o diff --git a/drivers/iio/adc/stm32-dfsdm-audio.c b/drivers/iio/adc/stm32-dfsdm-audio.c new file mode 100644 index 0000000..115ef8f --- /dev/null +++ b/drivers/iio/adc/stm32-dfsdm-audio.c @@ -0,0 +1,720 @@ +/* + * This file is the ADC part of of the STM32 DFSDM 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/dmaengine.h> +#include <linux/dma-mapping.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/slab.h> + +#include <linux/iio/buffer.h> +#include <linux/iio/hw_consumer.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/trigger.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> + +#include "stm32-dfsdm.h" + +#define DFSDM_DMA_BUFFER_SIZE (4 * PAGE_SIZE) + +struct stm32_dfsdm_audio { + struct stm32_dfsdm *dfsdm; + unsigned int fl_id; + unsigned int ch_id; + unsigned int spi_freq; /* SPI bus clock frequency */ + unsigned int sample_freq; /* Sample frequency after filter decimation */ + + u8 *rx_buf; + unsigned int bufi; /* Buffer current position */ + unsigned int buf_sz; /* Buffer size */ + + struct dma_chan *dma_chan; + dma_addr_t dma_buf; + + int (*cb)(const void *data, size_t size, void *cb_priv); + void *cb_priv; +}; + +const char *stm32_dfsdm_spi_trigger = DFSDM_SPI_TRIGGER_NAME; + +static ssize_t dfsdm_audio_get_rate(struct iio_dev *indio_dev, uintptr_t priv, + const struct iio_chan_spec *chan, char *buf) +{ + struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev); + + return snprintf(buf, PAGE_SIZE, "%d\n", pdmc->sample_freq); +} + +static ssize_t dfsdm_audio_set_rate(struct iio_dev *indio_dev, uintptr_t priv, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev); + struct stm32_dfsdm_filter *fl = &pdmc->dfsdm->fl_list[pdmc->fl_id]; + struct stm32_dfsdm_channel *ch = &pdmc->dfsdm->ch_list[pdmc->ch_id]; + unsigned int spi_freq = pdmc->spi_freq; + unsigned int sample_freq; + int ret; + + ret = kstrtoint(buf, 0, &sample_freq); + if (ret) + return ret; + + dev_dbg(&indio_dev->dev, "Requested sample_freq :%d\n", sample_freq); + if (!sample_freq) + return -EINVAL; + + if (ch->src != DFSDM_CHANNEL_SPI_CLOCK_EXTERNAL) + spi_freq = pdmc->dfsdm->spi_master_freq; + + if (spi_freq % sample_freq) + dev_warn(&indio_dev->dev, "Sampling rate not accurate (%d)\n", + spi_freq / (spi_freq / sample_freq)); + + ret = stm32_dfsdm_set_osrs(fl, 0, (spi_freq / sample_freq)); + if (ret < 0) { + dev_err(&indio_dev->dev, + "Not able to find filter parameter that match!\n"); + return ret; + } + pdmc->sample_freq = sample_freq; + + return len; +} + +static ssize_t dfsdm_audio_get_spiclk(struct iio_dev *indio_dev, uintptr_t priv, + const struct iio_chan_spec *chan, + char *buf) +{ + struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev); + + return snprintf(buf, PAGE_SIZE, "%d\n", pdmc->spi_freq); +} + +static ssize_t dfsdm_audio_set_spiclk(struct iio_dev *indio_dev, uintptr_t priv, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev); + struct stm32_dfsdm_filter *fl = &pdmc->dfsdm->fl_list[pdmc->fl_id]; + struct stm32_dfsdm_channel *ch = &pdmc->dfsdm->ch_list[pdmc->ch_id]; + unsigned int sample_freq = pdmc->sample_freq; + unsigned int spi_freq; + int ret; + + /* If DFSDM is master on SPI, SPI freq can not be updated */ + if (ch->src != DFSDM_CHANNEL_SPI_CLOCK_EXTERNAL) + return -EPERM; + + ret = kstrtoint(buf, 0, &spi_freq); + if (ret) + return ret; + + dev_dbg(&indio_dev->dev, "Requested frequency :%d\n", spi_freq); + if (!spi_freq) + return -EINVAL; + + if (sample_freq) { + if (spi_freq % sample_freq) + dev_warn(&indio_dev->dev, + "Sampling rate not accurate (%d)\n", + spi_freq / (spi_freq / sample_freq)); + + ret = stm32_dfsdm_set_osrs(fl, 0, (spi_freq / sample_freq)); + if (ret < 0) { + dev_err(&indio_dev->dev, + "No filter parameters that match!\n"); + return ret; + } + } + pdmc->spi_freq = spi_freq; + + return len; +} + +/* + * Define external info for SPI Frequency and audio sampling rate that can be + * configured by ASoC driver through consumer.h API + */ +static const struct iio_chan_spec_ext_info dfsdm_adc_ext_info[] = { + /* filter oversampling: Post filter oversampling ratio */ + { + .name = "audio_sampling_rate", + .shared = IIO_SHARED_BY_TYPE, + .read = dfsdm_audio_get_rate, + .write = dfsdm_audio_set_rate, + }, + /* data_right_bit_shift : Filter output data shifting */ + { + .name = "spi_clk_freq", + .shared = IIO_SHARED_BY_TYPE, + .read = dfsdm_audio_get_spiclk, + .write = dfsdm_audio_set_spiclk, + }, + {}, +}; + +static int stm32_dfsdm_start_conv(struct stm32_dfsdm_audio *pdmc, bool single) +{ + struct regmap *regmap = pdmc->dfsdm->regmap; + int ret; + + ret = stm32_dfsdm_start_dfsdm(pdmc->dfsdm); + if (ret < 0) + return ret; + + ret = stm32_dfsdm_start_channel(pdmc->dfsdm, pdmc->ch_id); + if (ret < 0) + goto stop_dfsdm; + + ret = stm32_dfsdm_filter_configure(pdmc->dfsdm, pdmc->fl_id, + pdmc->ch_id); + if (ret < 0) + goto stop_channels; + + /* Enable DMA transfer*/ + ret = regmap_update_bits(regmap, DFSDM_CR1(pdmc->fl_id), + DFSDM_CR1_RDMAEN_MASK, DFSDM_CR1_RDMAEN(1)); + if (ret < 0) + return ret; + + /* Enable conversion triggered by SPI clock*/ + ret = regmap_update_bits(regmap, DFSDM_CR1(pdmc->fl_id), + DFSDM_CR1_RCONT_MASK, DFSDM_CR1_RCONT(1)); + if (ret < 0) + return ret; + + ret = stm32_dfsdm_start_filter(pdmc->dfsdm, pdmc->fl_id); + if (ret < 0) + goto stop_channels; + + return 0; + +stop_channels: + stm32_dfsdm_stop_channel(pdmc->dfsdm, pdmc->fl_id); +stop_dfsdm: + stm32_dfsdm_stop_dfsdm(pdmc->dfsdm); + + return ret; +} + +static void stm32_dfsdm_stop_conv(struct stm32_dfsdm_audio *pdmc) +{ + stm32_dfsdm_stop_filter(pdmc->dfsdm, pdmc->fl_id); + + stm32_dfsdm_stop_channel(pdmc->dfsdm, pdmc->ch_id); + + stm32_dfsdm_stop_dfsdm(pdmc->dfsdm); +} + +static int stm32_dfsdm_set_watermark(struct iio_dev *indio_dev, + unsigned int val) +{ + struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev); + unsigned int watermark = DFSDM_DMA_BUFFER_SIZE / 2; + + /* + * DMA cyclic transfers are used, buffer is split into two periods. + * There should be : + * - always one buffer (period) DMA is working on + * - one buffer (period) driver pushed to ASoC side (). + */ + watermark = min(watermark, val * (unsigned int)(sizeof(u32))); + pdmc->buf_sz = watermark * 2; + + return 0; +} + +int stm32_dfsdm_validate_trigger(struct iio_dev *indio_dev, + struct iio_trigger *trig) +{ + if (!strcmp(stm32_dfsdm_spi_trigger, trig->name)) + return 0; + + return -EINVAL; +} + +static const struct iio_info stm32_dfsdm_info_pdmc = { + .hwfifo_set_watermark = stm32_dfsdm_set_watermark, + .driver_module = THIS_MODULE, + .validate_trigger = stm32_dfsdm_validate_trigger, +}; + +static irqreturn_t stm32_dfsdm_irq(int irq, void *arg) +{ + struct stm32_dfsdm_audio *pdmc = arg; + struct iio_dev *indio_dev = iio_priv_to_dev(pdmc); + struct regmap *regmap = pdmc->dfsdm->regmap; + unsigned int status; + + regmap_read(regmap, DFSDM_ISR(pdmc->fl_id), &status); + + if (status & DFSDM_ISR_ROVRF_MASK) { + dev_err(&indio_dev->dev, "Unexpected Conversion overflow\n"); + regmap_update_bits(regmap, DFSDM_ICR(pdmc->fl_id), + DFSDM_ICR_CLRROVRF_MASK, + DFSDM_ICR_CLRROVRF_MASK); + } + + return IRQ_HANDLED; +} + +static unsigned int stm32_dfsdm_audio_avail_data(struct stm32_dfsdm_audio *pdmc) +{ + struct dma_tx_state state; + enum dma_status status; + + status = dmaengine_tx_status(pdmc->dma_chan, + pdmc->dma_chan->cookie, + &state); + if (status == DMA_IN_PROGRESS) { + /* Residue is size in bytes from end of buffer */ + unsigned int i = pdmc->buf_sz - state.residue; + unsigned int size; + + /* Return available bytes */ + if (i >= pdmc->bufi) + size = i - pdmc->bufi; + else + size = pdmc->buf_sz + i - pdmc->bufi; + + return size; + } + + return 0; +} + +static void stm32_dfsdm_audio_dma_buffer_done(void *data) +{ + struct iio_dev *indio_dev = data; + + iio_trigger_poll_chained(indio_dev->trig); +} + +static int stm32_dfsdm_audio_dma_start(struct iio_dev *indio_dev) +{ + struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev); + struct dma_async_tx_descriptor *desc; + dma_cookie_t cookie; + int ret; + + if (!pdmc->dma_chan) + return -EINVAL; + + dev_dbg(&indio_dev->dev, "%s size=%d watermark=%d\n", __func__, + pdmc->buf_sz, pdmc->buf_sz / 2); + + /* Prepare a DMA cyclic transaction */ + desc = dmaengine_prep_dma_cyclic(pdmc->dma_chan, + pdmc->dma_buf, + pdmc->buf_sz, pdmc->buf_sz / 2, + DMA_DEV_TO_MEM, + DMA_PREP_INTERRUPT); + if (!desc) + return -EBUSY; + + desc->callback = stm32_dfsdm_audio_dma_buffer_done; + desc->callback_param = indio_dev; + + cookie = dmaengine_submit(desc); + ret = dma_submit_error(cookie); + if (ret) { + dmaengine_terminate_all(pdmc->dma_chan); + return ret; + } + + /* Issue pending DMA requests */ + dma_async_issue_pending(pdmc->dma_chan); + + return 0; +} + +static int stm32_dfsdm_postenable(struct iio_dev *indio_dev) +{ + struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev); + int ret; + + dev_dbg(&indio_dev->dev, "%s\n", __func__); + /* Reset pdmc buffer index */ + pdmc->bufi = 0; + + ret = stm32_dfsdm_start_conv(pdmc, false); + if (ret) { + dev_err(&indio_dev->dev, "Can't start conversion\n"); + return ret; + } + + ret = stm32_dfsdm_audio_dma_start(indio_dev); + if (ret) { + dev_err(&indio_dev->dev, "Can't start DMA\n"); + goto err_stop_conv; + } + + ret = iio_triggered_buffer_postenable(indio_dev); + if (ret < 0) { + dev_err(&indio_dev->dev, "%s :%d\n", __func__, __LINE__); + goto err_stop_dma; + } + + return 0; + +err_stop_dma: + if (pdmc->dma_chan) + dmaengine_terminate_all(pdmc->dma_chan); +err_stop_conv: + stm32_dfsdm_stop_conv(pdmc); + + return ret; +} + +static int stm32_dfsdm_predisable(struct iio_dev *indio_dev) +{ + struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev); + int ret; + + dev_dbg(&indio_dev->dev, "%s\n", __func__); + ret = iio_triggered_buffer_predisable(indio_dev); + if (ret < 0) + dev_err(&indio_dev->dev, "Predisable failed\n"); + + if (pdmc->dma_chan) + dmaengine_terminate_all(pdmc->dma_chan); + + stm32_dfsdm_stop_conv(pdmc); + + return 0; +} + +static const struct iio_buffer_setup_ops stm32_dfsdm_buffer_setup_ops = { + .postenable = &stm32_dfsdm_postenable, + .predisable = &stm32_dfsdm_predisable, +}; + +static irqreturn_t stm32_dfsdm_audio_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev); + size_t old_pos; + int available = stm32_dfsdm_audio_avail_data(pdmc); + + /* + * Buffer interface is not support cyclic DMA buffer,and offer only + * an interface to push data samples per samples. + * For this reason iio_push_to_buffers_with_timestamp in not used + * and interface is hacked using a private callback registered by ASoC. + * This should be a temporary solution waiting a cyclic DMA engine + * support in IIO. + */ + + dev_dbg(&indio_dev->dev, "%s: pos = %d, available = %d\n", __func__, + pdmc->bufi, available); + old_pos = pdmc->bufi; + while (available >= indio_dev->scan_bytes) { + u32 *buffer = (u32 *)&pdmc->rx_buf[pdmc->bufi]; + + /* Mask 8 LSB that contains the channel ID */ + *buffer &= 0xFFFFFF00; + available -= indio_dev->scan_bytes; + pdmc->bufi += indio_dev->scan_bytes; + if (pdmc->bufi >= pdmc->buf_sz) { + if (pdmc->cb) + pdmc->cb(&pdmc->rx_buf[old_pos], + pdmc->buf_sz - old_pos, pdmc->cb_priv); + pdmc->bufi = 0; + old_pos = 0; + } + } + if (pdmc->cb) + pdmc->cb(&pdmc->rx_buf[old_pos], pdmc->bufi - old_pos, + pdmc->cb_priv); + + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +/** + * stm32_dfsdm_get_buff_cb - register a callback + * that will be called when DMA transfer period is achieved. + * + * @iio_dev: Handle to IIO device. + * @cb: pointer to callback function. + * @data: pointer to data buffer + * @size: size in byte of the data buffer + * @private: pointer to consumer private structure + * @private: pointer to consumer private structure + */ +int stm32_dfsdm_get_buff_cb(struct iio_dev *iio_dev, + int (*cb)(const void *data, size_t size, + void *private), + void *private) +{ + struct stm32_dfsdm_audio *pdmc; + + if (!iio_dev) + return -EINVAL; + pdmc = iio_priv(iio_dev); + + if (iio_dev != iio_priv_to_dev(pdmc)) + return -EINVAL; + + pdmc->cb = cb; + pdmc->cb_priv = private; + + return 0; +} +EXPORT_SYMBOL_GPL(stm32_dfsdm_get_buff_cb); + +/** + * stm32_dfsdm_release_buff_cb - unregister buffer callback + * + * @iio_dev: Handle to IIO device. + */ +int stm32_dfsdm_release_buff_cb(struct iio_dev *iio_dev) +{ + struct stm32_dfsdm_audio *pdmc; + + if (!iio_dev) + return -EINVAL; + pdmc = iio_priv(iio_dev); + + if (iio_dev != iio_priv_to_dev(pdmc)) + return -EINVAL; + pdmc->cb = NULL; + pdmc->cb_priv = NULL; + + return 0; +} +EXPORT_SYMBOL_GPL(stm32_dfsdm_release_buff_cb); + +static int stm32_dfsdm_audio_chan_init(struct iio_dev *indio_dev) +{ + struct iio_chan_spec *ch; + struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev); + int ret; + + ch = devm_kzalloc(&indio_dev->dev, sizeof(*ch), GFP_KERNEL); + if (!ch) + return -ENOMEM; + + ret = stm32_dfsdm_channel_parse_of(pdmc->dfsdm, indio_dev, ch, 0); + if (ret < 0) + return ret; + + ch->type = IIO_VOLTAGE; + ch->indexed = 1; + ch->scan_index = 0; + ch->ext_info = dfsdm_adc_ext_info; + + ch->scan_type.sign = 's'; + ch->scan_type.realbits = 24; + ch->scan_type.storagebits = 32; + + pdmc->ch_id = ch->channel; + ret = stm32_dfsdm_chan_configure(pdmc->dfsdm, + &pdmc->dfsdm->ch_list[ch->channel]); + + indio_dev->num_channels = 1; + indio_dev->channels = ch; + + return ret; +} + +static const struct of_device_id stm32_dfsdm_audio_match[] = { + { .compatible = "st,stm32-dfsdm-audio"}, + {} +}; + +static int stm32_dfsdm_audio_dma_request(struct iio_dev *indio_dev) +{ + struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev); + struct dma_slave_config config; + int ret; + + pdmc->dma_chan = dma_request_slave_channel(&indio_dev->dev, "rx"); + if (!pdmc->dma_chan) + return -EINVAL; + + pdmc->rx_buf = dma_alloc_coherent(pdmc->dma_chan->device->dev, + DFSDM_DMA_BUFFER_SIZE, + &pdmc->dma_buf, GFP_KERNEL); + if (!pdmc->rx_buf) { + ret = -ENOMEM; + goto err_release; + } + + /* Configure DMA channel to read data register */ + memset(&config, 0, sizeof(config)); + config.src_addr = (dma_addr_t)pdmc->dfsdm->phys_base; + config.src_addr += DFSDM_RDATAR(pdmc->fl_id); + config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + + ret = dmaengine_slave_config(pdmc->dma_chan, &config); + if (ret) + goto err_free; + + return 0; + +err_free: + dma_free_coherent(pdmc->dma_chan->device->dev, DFSDM_DMA_BUFFER_SIZE, + pdmc->rx_buf, pdmc->dma_buf); +err_release: + dma_release_channel(pdmc->dma_chan); + + return ret; +} + +static int stm32_dfsdm_audio_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct stm32_dfsdm_audio *pdmc; + struct device_node *np = dev->of_node; + struct iio_dev *iio; + char *name; + int ret, irq, val; + + iio = devm_iio_device_alloc(dev, sizeof(*pdmc)); + if (IS_ERR(iio)) { + dev_err(dev, "%s: Failed to allocate IIO\n", __func__); + return PTR_ERR(iio); + } + + pdmc = iio_priv(iio); + if (IS_ERR(pdmc)) { + dev_err(dev, "%s: Failed to allocate ADC\n", __func__); + return PTR_ERR(pdmc); + } + pdmc->dfsdm = dev_get_drvdata(dev->parent); + + iio->name = np->name; + iio->dev.parent = dev; + iio->dev.of_node = np; + iio->info = &stm32_dfsdm_info_pdmc; + iio->modes = INDIO_DIRECT_MODE; + + platform_set_drvdata(pdev, pdmc); + + ret = of_property_read_u32(dev->of_node, "reg", &pdmc->fl_id); + if (ret != 0) { + dev_err(dev, "Missing reg property\n"); + return -EINVAL; + } + + name = kzalloc(sizeof("dfsdm-pdm0"), GFP_KERNEL); + if (!name) + return -ENOMEM; + snprintf(name, sizeof("dfsdm-pdm0"), "dfsdm-pdm%d", pdmc->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, pdmc); + 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; + } + pdmc->dfsdm->fl_list[pdmc->fl_id].ford = val; + + ret = of_property_read_u32(dev->of_node, "st,filter0-sync", &val); + if (!ret) + pdmc->dfsdm->fl_list[pdmc->fl_id].sync_mode = val; + + ret = stm32_dfsdm_audio_chan_init(iio); + if (ret < 0) + return ret; + + ret = stm32_dfsdm_audio_dma_request(iio); + if (ret) { + dev_err(&pdev->dev, "DMA request failed\n"); + return ret; + } + + iio->modes |= INDIO_BUFFER_SOFTWARE; + + ret = iio_triggered_buffer_setup(iio, + &iio_pollfunc_store_time, + &stm32_dfsdm_audio_trigger_handler, + &stm32_dfsdm_buffer_setup_ops); + if (ret) { + dev_err(&pdev->dev, "Buffer setup failed\n"); + goto err_dma_disable; + } + + ret = iio_device_register(iio); + if (ret) { + dev_err(&pdev->dev, "IIO dev register failed\n"); + goto err_buffer_cleanup; + } + + return 0; + +err_buffer_cleanup: + iio_triggered_buffer_cleanup(iio); + +err_dma_disable: + if (pdmc->dma_chan) { + dma_free_coherent(pdmc->dma_chan->device->dev, + DFSDM_DMA_BUFFER_SIZE, + pdmc->rx_buf, pdmc->dma_buf); + dma_release_channel(pdmc->dma_chan); + } + + return ret; +} + +static int stm32_dfsdm_audio_remove(struct platform_device *pdev) +{ + struct stm32_dfsdm_audio *pdmc = platform_get_drvdata(pdev); + struct iio_dev *iio = iio_priv_to_dev(pdmc); + + iio_device_unregister(iio); + + return 0; +} + +static struct platform_driver stm32_dfsdm_audio_driver = { + .driver = { + .name = "stm32-dfsdm-audio", + .of_match_table = stm32_dfsdm_audio_match, + }, + .probe = stm32_dfsdm_audio_probe, + .remove = stm32_dfsdm_audio_remove, +}; +module_platform_driver(stm32_dfsdm_audio_driver); + +MODULE_DESCRIPTION("STM32 sigma delta converter for PDM microphone"); +MODULE_AUTHOR("Arnaud Pouliquen arnaud.pouliquen@st.com"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/iio/adc/stm32-dfsdm-audio.h b/include/linux/iio/adc/stm32-dfsdm-audio.h new file mode 100644 index 0000000..9b41b28 --- /dev/null +++ b/include/linux/iio/adc/stm32-dfsdm-audio.h @@ -0,0 +1,41 @@ +/* + * This file discribe the STM32 DFSDM IIO driver API for audio part + * + * Copyright (C) 2017, STMicroelectronics - All Rights Reserved + * Author(s): Arnaud Pouliquen arnaud.pouliquen@st.com. + * + * License terms: GPL V2.0. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + */ +#ifndef STM32_DFSDM_AUDIO_H +#define STM32_DFSDM_AUDIO_H + +/** + * stm32_dfsdm_get_buff_cb() - register callback for capture buffer period. + * @dev: Pointer to client device. + * @indio_dev: Device on which the channel exists. + * @cb: Callback function. + * @data: pointer to data buffer + * @size: size of the data buffer in bytes + * @private: Private data passed to callback. + * + */ +int stm32_dfsdm_get_buff_cb(struct iio_dev *iio_dev, + int (*cb)(const void *data, size_t size, + void *private), + void *private); +/** + * stm32_dfsdm_get_buff_cb() - release callback for capture buffer period. + * @indio_dev: Device on which the channel exists. + */ +int stm32_dfsdm_release_buff_cb(struct iio_dev *iio_dev); + +#endif
Add iio consumer API to set buffer size and watermark according to sysfs API.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com --- drivers/iio/buffer/industrialio-buffer-cb.c | 12 ++++++++++++ include/linux/iio/consumer.h | 13 +++++++++++++ 2 files changed, 25 insertions(+)
diff --git a/drivers/iio/buffer/industrialio-buffer-cb.c b/drivers/iio/buffer/industrialio-buffer-cb.c index b8f550e..43c066a 100644 --- a/drivers/iio/buffer/industrialio-buffer-cb.c +++ b/drivers/iio/buffer/industrialio-buffer-cb.c @@ -103,6 +103,18 @@ struct iio_cb_buffer *iio_channel_get_all_cb(struct device *dev, } EXPORT_SYMBOL_GPL(iio_channel_get_all_cb);
+int iio_channel_cb_set_buffer_params(struct iio_cb_buffer *cb_buff, + size_t length, size_t watermark) +{ + if (!length || length < watermark) + return -EINVAL; + cb_buff->buffer.watermark = watermark; + cb_buff->buffer.length = length; + + return 0; +} +EXPORT_SYMBOL_GPL(iio_channel_start_all_cb); + int iio_channel_start_all_cb(struct iio_cb_buffer *cb_buff) { return iio_update_buffers(cb_buff->indio_dev, &cb_buff->buffer, diff --git a/include/linux/iio/consumer.h b/include/linux/iio/consumer.h index cb44771..0f6e94d 100644 --- a/include/linux/iio/consumer.h +++ b/include/linux/iio/consumer.h @@ -134,6 +134,19 @@ struct iio_cb_buffer *iio_channel_get_all_cb(struct device *dev, void *private), void *private); /** + * iio_channel_cb_set_buffer_size() - set the buffer length. + * @cb_buffer: The callback buffer from whom we want the channel + * information. + * @length: buffer length in bytes + * @watermark: buffer watermark in bytes + * + * This function allows to configure the buffer length. The watermark if + * forced to half of the buffer. + */ +int iio_channel_cb_set_buffer_params(struct iio_cb_buffer *cb_buffer, + size_t length, size_t watermark); + +/** * iio_channel_release_all_cb() - release and unregister the callback. * @cb_buffer: The callback buffer that was allocated. */
This patch adds documentation of device tree bindings for audio DMIC codec.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com --- Documentation/devicetree/bindings/sound/dmic.txt | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/dmic.txt
diff --git a/Documentation/devicetree/bindings/sound/dmic.txt b/Documentation/devicetree/bindings/sound/dmic.txt new file mode 100644 index 0000000..eb2a9d4 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/dmic.txt @@ -0,0 +1,11 @@ +Device-Tree bindings for dmic codec + +Required properties: + - compatible: should be "dmic-codec". + +Example node: + + dmic_audio: dmic_audio@0 { + compatible = "dmic-codec"; + status = "okay"; + };
Add of_table to allows DT probing.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com --- sound/soc/codecs/dmic.c | 8 ++++++++ 1 file changed, 8 insertions(+)
diff --git a/sound/soc/codecs/dmic.c b/sound/soc/codecs/dmic.c index c82b9dc..d04cac9 100644 --- a/sound/soc/codecs/dmic.c +++ b/sound/soc/codecs/dmic.c @@ -73,9 +73,17 @@ static int dmic_dev_remove(struct platform_device *pdev)
MODULE_ALIAS("platform:dmic-codec");
+static const struct of_device_id dmic_dev_match[] = { + { + .compatible = "dmic-codec", + }, + {}, +}; + static struct platform_driver dmic_driver = { .driver = { .name = "dmic-codec", + .of_match_table = dmic_dev_match, }, .probe = dmic_dev_probe, .remove = dmic_dev_remove,
Add bindings that describes audio settings to support Digital Filter for pulse density modulation(PDM) microphone.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com --- V2->V3: Fixes based on V2 comments
.../devicetree/bindings/sound/st,stm32-adfsdm.txt | 41 ++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/st,stm32-adfsdm.txt
diff --git a/Documentation/devicetree/bindings/sound/st,stm32-adfsdm.txt b/Documentation/devicetree/bindings/sound/st,stm32-adfsdm.txt new file mode 100644 index 0000000..ab610bc --- /dev/null +++ b/Documentation/devicetree/bindings/sound/st,stm32-adfsdm.txt @@ -0,0 +1,40 @@ +STMicroelectronics audio DFSDM DT bindings + +This driver supports audio PDM microphone capture through Digital Filter format +Sigma Delta modulators (DFSDM). + +Required properties: + - compatible: "st,stm32h7-adfsdm". + + - #sound-dai-cells : Must be equal to 0 + + - io-channels : phandle to iio dfsdm instance node. + + +Example of a simple sound card using audio DFSDM node. + + dmic0: dmic_@0 { + compatible = "dmic-codec"; + #sound-dai-cells = <0>; + }; + + asoc-pdm@0 { + compatible = "st,stm32h7-adfsdm"; + #sound-dai-cells = <0>; + io-channels = <&dfsdm_adc0 0>; + }; + + sound_dfsdm_pdm { + compatible = "simple-audio-card"; + simple-audio-card,name = "dfsdm_pdm"; + + dfsdm0_mic0: simple-audio-card,dai-link@0 { + format = "pdm"; + cpu { + sound-dai = <&asoc_pdm1>; + }; + dmic0_codec: codec { + sound-dai = <&dmic0>; + }; + }; + }; \ No newline at end of file
Add driver to handle DAI interface for PDM microphones connected to Digital Filter for Sigma Delta mModulators IP.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com --- V2->V3: - Suppress DMA engine - Suppress copy ops - Use IIO consmumer interface to enable/Disable DFSDM --- sound/soc/Kconfig | 1 + sound/soc/Makefile | 1 + sound/soc/stm/Kconfig | 10 ++ sound/soc/stm/Makefile | 2 + sound/soc/stm/stm32_adfsdm.c | 362 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 376 insertions(+) create mode 100644 sound/soc/stm/Kconfig create mode 100644 sound/soc/stm/Makefile create mode 100644 sound/soc/stm/stm32_adfsdm.c
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index 182d92e..3836ebe 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -63,6 +63,7 @@ source "sound/soc/sh/Kconfig" source "sound/soc/sirf/Kconfig" source "sound/soc/spear/Kconfig" source "sound/soc/sti/Kconfig" +source "sound/soc/stm/Kconfig" source "sound/soc/sunxi/Kconfig" source "sound/soc/tegra/Kconfig" source "sound/soc/txx9/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 9a30f21..5440cf7 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -43,6 +43,7 @@ obj-$(CONFIG_SND_SOC) += sh/ obj-$(CONFIG_SND_SOC) += sirf/ obj-$(CONFIG_SND_SOC) += spear/ obj-$(CONFIG_SND_SOC) += sti/ +obj-$(CONFIG_SND_SOC) += stm/ obj-$(CONFIG_SND_SOC) += sunxi/ obj-$(CONFIG_SND_SOC) += tegra/ obj-$(CONFIG_SND_SOC) += txx9/ diff --git a/sound/soc/stm/Kconfig b/sound/soc/stm/Kconfig new file mode 100644 index 0000000..041ddb9 --- /dev/null +++ b/sound/soc/stm/Kconfig @@ -0,0 +1,10 @@ +menuconfig SND_SOC_STM32_DFSDM + tristate "SoC Audio support for STM32 DFSDM" + depends on (ARCH_STM32 && OF && STM32_DFSDM_ADC) || COMPILE_TEST + depends on SND_SOC + select SND_SOC_GENERIC_DMAENGINE_PCM + select SND_SOC_DMIC + help + Select this option to enable the STM32 Digital Filter + for Sigma Delta Modulators (DFSDM) driver used + in various STM32 series for digital microphone capture. \ No newline at end of file diff --git a/sound/soc/stm/Makefile b/sound/soc/stm/Makefile new file mode 100644 index 0000000..ea90240 --- /dev/null +++ b/sound/soc/stm/Makefile @@ -0,0 +1,2 @@ +#DFSDM +obj-$(CONFIG_SND_SOC_STM32_DFSDM) += stm32_adfsdm.o diff --git a/sound/soc/stm/stm32_adfsdm.c b/sound/soc/stm/stm32_adfsdm.c new file mode 100644 index 0000000..05c4186 --- /dev/null +++ b/sound/soc/stm/stm32_adfsdm.c @@ -0,0 +1,362 @@ +/* + * This file is part of STM32 DFSDM ASoC DAI driver + * + * Copyright (C) 2017, STMicroelectronics - All Rights Reserved + * Authors: Arnaud Pouliquen arnaud.pouliquen@st.com + * Olivier Moysan olivier.moysan@st.com + * + * License type: GPLv2 + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see http://www.gnu.org/licenses/. + */ + +#include <linux/clk.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#include <linux/iio/iio.h> +#include <linux/iio/consumer.h> +#include <linux/iio/adc/stm32-dfsdm-audio.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; + + /* IIO */ + struct iio_channel *iio_ch; + struct iio_cb_buffer *iio_cb; + bool iio_active; + + /* PCM buffer */ + unsigned char *pcm_buff; + unsigned int pos; +}; + +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_min = 1, + .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; + } +} + +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); + char str_rate[10]; + ssize_t size; + int ret = 0; + + dev_dbg(dai->dev, "%s: rate :%d\n", __func__, + substream->runtime->rate); + + snprintf(str_rate, sizeof(str_rate), "%d\n", substream->runtime->rate); + size = iio_write_channel_ext_info(priv->iio_ch, "audio_sampling_rate", + str_rate, sizeof(str_rate)); + if (size != sizeof(str_rate)) { + dev_err(dai->dev, "%s: Failed to set %s sampling rate\n", + __func__, str_rate); + return -EINVAL; + } + + 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: Failed to start IIO channel (%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 = %d\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); + unsigned int period_bytes; + int ret; + + period_bytes = params_buffer_bytes(params) / params_periods(params); + 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_params(priv->iio_cb, + params_buffer_size(params), + 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_card *card = rtd->card->snd_card; + struct snd_pcm *pcm = rtd->pcm; + unsigned int size = DFSDM_MAX_PERIODS * DFSDM_MAX_PERIOD_SIZE; + + return snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + card->dev, size, size); +} + +static struct snd_soc_platform_driver stm32_adfsdm_soc_platform = { + .ops = &stm32_adfsdm_pcm_ops, + .pcm_new = stm32_adfsdm_pcm_new, +}; + +static const struct of_device_id stm32_adfsdm_of_match[] = { + {.compatible = "st,stm32h7-adfsdm"}, + {} +}; +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->dai_drv = stm32_adfsdm_dai; + priv->dai_drv.name = pdev->dev.of_node->name; + priv->dai_drv.capture.stream_name = pdev->dev.of_node->name; + + dev_set_drvdata(&pdev->dev, priv); + + ret = devm_snd_soc_register_component(&pdev->dev, + &stm32_adfsdm_dai_component, + &priv->dai_drv, 1); + if (ret < 0) + return ret; + + /* Associate iio channel */ + priv->iio_ch = devm_iio_channel_get_all(&pdev->dev); + if (IS_ERR(priv->iio_ch)) + return -ENODEV; + + priv->iio_cb = iio_channel_get_all_cb(&pdev->dev, NULL, NULL); + if (IS_ERR(priv->iio_cb)) + return -ENODEV; + + 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 0; +} + +static struct platform_driver stm32_adfsdm_driver = { + .probe = stm32_adfsdm_probe, + .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);
Oops stupid guy i am...I have forgotten to tag the series as [RFC] instead of [PATCH] in subjects. Apologize for any inconvenience caused.
Regards Arnaud
On 03/17/2017 03:08 PM, Arnaud POULIQUEN wrote:
Hello,
I kept it as RFC as i reworked the global design (but also because not full validated...). Regarding Mark and Jonathan's comments, I decided to try an implementation with DMA support in IIO instead of handling it in ASOC. Solution seems more straightforward but as DMA cyclic mode and associated buffer block API is not part of IIO today, I still need some hacks... To be honest, I tried to integrate DMA cyclic management and associated buffer block Consumer API. but no enough expertise on IIO to success... :(
Advantage vs V2:
- No more data processing need in ASoC , done in IIO
- ASoC uses IIO consumer interface ( except for buffer callback)
Drawback :
- Need to create a "SPI bus" trigger to allo to use Buffer consumer API => Need to find a solution to set this trigger by default in IIO audio driver
- No standard buffer management
- Implement DMA cyclic in DFSDM IIO driver. - Define specific API to handle buffer callback per period instead of callback per samples generated by iio_push_to_buffers_with_timestamp.
V2-V3 delta:
New patches to support ASoC DMA codec in DT
- ASoC: Add Bindins for DMIC codec driver.
- ASoC: codec: add DT support in dmic codec.
- New patches to allow in-kernel set of IIO buffer size and watermark
- IIO: consumer: allow to set buffer sizes.
IIO DFSDM drivers - Split audio and ADC support in 2 drivers. - Implement DMA cyclic mode. - Add SPI bus Trigger for buffer management.
IIO sigma delta adc drivers - Suppress "simple and rename driver.
- ASoC driver - Suppress DMA engine. - Suppress copy ops. - Use IIO consmumer interface to enable/Disable DFSDM.
Regards,
Arnaud
Arnaud Pouliquen (11): iio: Add hardware consumer support 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 support for Sigma delta ADC IIO: ADC: add stm32 DFSDM support for PDM microphone IIO: consumer: allow to set buffer sizes ASoC: Add Bindins for DMIC codec driver ASoC: codec: add DT support in dmic codec ASoC: add bindings for stm32 DFSDM filter ASoC: stm32: add DFSDM DAI support
.../bindings/iio/adc/sigma-delta-modulator.txt | 13 + .../bindings/iio/adc/st,stm32-dfsdm-adc.txt | 120 ++++ Documentation/devicetree/bindings/sound/dmic.txt | 11 + .../devicetree/bindings/sound/st,stm32-adfsdm.txt | 41 ++ drivers/iio/Kconfig | 6 + drivers/iio/Makefile | 1 + drivers/iio/adc/Kconfig | 50 ++ drivers/iio/adc/Makefile | 4 + drivers/iio/adc/sd_adc_modulator.c | 98 +++ drivers/iio/adc/stm32-dfsdm-adc.c | 419 ++++++++++++ drivers/iio/adc/stm32-dfsdm-audio.c | 720 +++++++++++++++++++++ drivers/iio/adc/stm32-dfsdm-core.c | 658 +++++++++++++++++++ drivers/iio/adc/stm32-dfsdm.h | 372 +++++++++++ drivers/iio/buffer/industrialio-buffer-cb.c | 12 + drivers/iio/hw_consumer.c | 156 +++++ include/linux/iio/adc/stm32-dfsdm-audio.h | 41 ++ include/linux/iio/consumer.h | 13 + include/linux/iio/hw_consumer.h | 12 + sound/soc/Kconfig | 1 + sound/soc/Makefile | 1 + sound/soc/codecs/dmic.c | 8 + sound/soc/stm/Kconfig | 10 + sound/soc/stm/Makefile | 2 + sound/soc/stm/stm32_adfsdm.c | 362 +++++++++++ 24 files changed, 3131 insertions(+) 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/dmic.txt create mode 100644 Documentation/devicetree/bindings/sound/st,stm32-adfsdm.txt 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-audio.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/hw_consumer.c create mode 100644 include/linux/iio/adc/stm32-dfsdm-audio.h create mode 100644 include/linux/iio/hw_consumer.h create mode 100644 sound/soc/stm/Kconfig create mode 100644 sound/soc/stm/Makefile create mode 100644 sound/soc/stm/stm32_adfsdm.c
V2:
Patch-set associated to this RFC proposes an implementation of the DFSDM features shared between ASoC and IIO frameworks.
Patch-set is only a Skeleton of the drivers, so a base to discuss and validate a design. It contains minimum code to allow probing (with DT) and to expose the ASoC and IIO ABI. Hope that is sufficent in a first step to allow to focus on APIs.
In this patch-set there are two new APIs used:
- IIO in-kern API: based on hw_customer API proposed by Lars
- ASOC <-> IIO API inspired by API defined for hdmi-codec for ASoC/DRM interconnect. API is dedicated to DFSDM only.
Notice also that this design is based on following assumption:
Audio stream ABI interface is ASoC, no access to data through IIO ABI for PDM.
ASoC DMA should be used for audio transfer as designed for real time stream.
Need some runtime parameters exchange between ASoC and IIO due to the correlation between the sample frequency, the DFSDM decimation factor and the associated scaling.
"ASoC: dmaengine_pcm: add copy support" patch:
I added a patch in ASoC that allows to implement a copy function to process data after DMA transfer. Requested, as DFSDM samples captured contain channel ID on 8-LSB bits and need also a potential rescale to present DAT on 24-bits.
- "IIO: ADC: add sigma delta modulator support" patch:
Simple dummy driver created to support external Sigma delta modulator. It is binded to DFSDM driver through hw_customer API.
1.9.1
On 17/03/17 14:08, 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. stm32-dfsdm-adc driver allows to handle sigma delta ADC.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com
Various minor bits inline.
I'm mostly liking this. I do slightly wondering if semantically it should be the front end that has the channels rather than the backend. Would be fiddly to do though and probably not worth the hassle.
Would love to see it running in a continuous mode in IIO, but I guess that can follow along later.
The comment about the trigger has me confused - perhaps you could elaborate further on that?
Jonathan
V2 -> V3 :
- Split audio and ADC support in 2 drivers
- Implement DMA cyclic mode
- Add SPI bus Trigger for buffer management
drivers/iio/adc/Kconfig | 26 ++ drivers/iio/adc/Makefile | 2 + drivers/iio/adc/stm32-dfsdm-adc.c | 419 +++++++++++++++++++++++ drivers/iio/adc/stm32-dfsdm-core.c | 658 +++++++++++++++++++++++++++++++++++++ drivers/iio/adc/stm32-dfsdm.h | 372 +++++++++++++++++++++ 5 files changed, 1477 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 d411d66..3e0eb11 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -452,6 +452,32 @@ config STM32_ADC This driver can also be built as a module. If so, the module will be called stm32-adc.
+config STM32_DFSDM_CORE
- tristate "STMicroelectronics STM32 dfsdm core"
- depends on (ARCH_STM32 && OF) || COMPILE_TEST
- select REGMAP
- select REGMAP_MMIO
- help
Select this option to enable the driver 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-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_DMAENGINE
- select IIO_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 X86 && ISA_BUS_API diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index c68819c..161f271 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -43,6 +43,8 @@ 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 +obj-$(CONFIG_STM32_DFSDM_CORE) += 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..ebcb3b4 --- /dev/null +++ b/drivers/iio/adc/stm32-dfsdm-adc.c @@ -0,0 +1,419 @@ +/*
- This file is the ADC part of of the STM32 DFSDM 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/interrupt.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/slab.h>
+#include <linux/iio/hw_consumer.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h>
+#include "stm32-dfsdm.h"
+#define DFSDM_TIMEOUT_US 100000 +#define DFSDM_TIMEOUT (msecs_to_jiffies(DFSDM_TIMEOUT_US / 1000))
+struct stm32_dfsdm_adc {
- struct stm32_dfsdm *dfsdm;
- unsigned int fl_id;
- unsigned int ch_id;
- unsigned int oversamp;
- struct completion completion;
- u32 *buffer;
- /* Hardware consumer structure for Front End IIO */
- struct iio_hw_consumer *hwc;
+};
+static int stm32_dfsdm_start_conv(struct stm32_dfsdm_adc *adc) +{
- int ret;
- ret = stm32_dfsdm_start_dfsdm(adc->dfsdm);
- if (ret < 0)
return ret;
- ret = stm32_dfsdm_start_channel(adc->dfsdm, adc->ch_id);
- if (ret < 0)
goto stop_dfsdm;
- 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:
- stm32_dfsdm_stop_channel(adc->dfsdm, adc->ch_id);
+stop_dfsdm:
- stm32_dfsdm_stop_dfsdm(adc->dfsdm);
- return ret;
+}
+static void stm32_dfsdm_stop_conv(struct stm32_dfsdm_adc *adc) +{
- stm32_dfsdm_stop_filter(adc->dfsdm, adc->fl_id);
- stm32_dfsdm_stop_channel(adc->dfsdm, adc->ch_id);
- stm32_dfsdm_stop_dfsdm(adc->dfsdm);
+}
+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;
- /* Unmask IRQ for regular conversion achievement*/
- ret = regmap_update_bits(adc->dfsdm->regmap, DFSDM_CR2(adc->fl_id),
DFSDM_CR2_REOCIE_MASK, DFSDM_CR2_REOCIE(1));
- if (ret < 0)
return ret;
- ret = stm32_dfsdm_start_conv(adc);
- if (ret < 0)
return ret;
- timeout = wait_for_completion_interruptible_timeout(&adc->completion,
DFSDM_TIMEOUT);
blank line perhaps.
- /* 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) {
dev_warn(&indio_dev->dev, "Conversion timed out!\n");
ret = -ETIMEDOUT;
- } else if (timeout < 0) {
ret = timeout;
- } else {
dev_dbg(&indio_dev->dev, "Converted val %#x\n", *res);
ret = IIO_VAL_INT;
- }
- /* 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));
- stm32_dfsdm_stop_conv(adc);
- 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;
- }
blank line here.
- 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);
if (ret < 0) {
dev_err(&indio_dev->dev,
"%s: Conversion failed (channel %d)\n",
__func__, chan->channel);
return ret;
}
iio_hw_consumer_disable(adc->hwc);
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,
- .driver_module = THIS_MODULE,
+};
+static irqreturn_t stm32_dfsdm_irq(int irq, void *arg) +{
- struct stm32_dfsdm_adc *adc = arg;
- struct regmap *regmap = adc->dfsdm->regmap;
- unsigned int status;
- regmap_read(regmap, DFSDM_ISR(adc->fl_id), &status);
- 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) {
What's this one? Might want a comment given it's an irq you basically eat.
regmap_update_bits(regmap, DFSDM_ICR(adc->fl_id),
DFSDM_ICR_CLRROVRF_MASK,
DFSDM_ICR_CLRROVRF_MASK);
- }
- return IRQ_HANDLED;
+}
+static int stm32_dfsdm_postenable(struct iio_dev *indio_dev) +{
- struct stm32_dfsdm_adc *adc = iio_priv(indio_dev);
- return stm32_dfsdm_start_conv(adc);
+}
+static int stm32_dfsdm_predisable(struct iio_dev *indio_dev) +{
- struct stm32_dfsdm_adc *adc = iio_priv(indio_dev);
- stm32_dfsdm_stop_conv(adc);
blank line.
- return 0;
+}
+static const struct iio_buffer_setup_ops stm32_dfsdm_buffer_setup_ops = {
- .postenable = &stm32_dfsdm_postenable,
- .predisable = &stm32_dfsdm_predisable,
+};
+static int stm32_dfsdm_adc_chan_init_one(struct iio_dev *indio_dev,
struct iio_chan_spec *chan,
int ch_idx)
+{
- struct iio_chan_spec *ch = &chan[ch_idx];
- struct stm32_dfsdm_adc *adc = iio_priv(indio_dev);
- int ret;
- ret = stm32_dfsdm_channel_parse_of(adc->dfsdm, indio_dev, chan, ch_idx);
- ch->type = IIO_VOLTAGE;
- ch->indexed = 1;
- ch->scan_index = ch_idx;
- /*
* 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) |
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_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->dfsdm->num_chs) {
dev_err(&indio_dev->dev, "Bad st,adc-channels?\n");
return num_ch < 0 ? num_ch : -EINVAL;
- }
- /*
* Number of channel per filter is temporary limited to 1.
* Restriction should be cleaned with scan mode
*/
- if (num_ch > 1) {
dev_err(&indio_dev->dev, "Multi channel not yet supported\n");
return -EINVAL;
- }
- /* Bind to SD modulator IIO device */
- adc->hwc = iio_hw_consumer_alloc(&indio_dev->dev);
- if (IS_ERR(adc->hwc))
return -EPROBE_DEFER;
- channels = devm_kcalloc(&indio_dev->dev, num_ch, sizeof(*channels),
GFP_KERNEL);
- if (!channels)
return -ENOMEM;
- 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:
- iio_hw_consumer_free(adc->hwc);
Given you have to free this in the error path, I would imagine you will need a free somewhere in the main remove path? Or just create a devm version of iio_hw_consumer_alloc. It will be useful in the long run.
- return ret;
+}
+static const struct of_device_id stm32_dfsdm_adc_match[] = {
- { .compatible = "st,stm32-dfsdm-adc"},
- {}
+};
+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;
- struct iio_dev *iio;
- char *name;
- int ret, irq, val;
- 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->info = &stm32_dfsdm_info_adc;
- 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 = kzalloc(sizeof("dfsdm-adc0"), GFP_KERNEL);
not freed. Maybe devm_kzalloc
- if (!name)
return -ENOMEM;
- 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;
- ret = stm32_dfsdm_adc_chan_init(iio);
- if (ret < 0)
return ret;
- init_completion(&adc->completion);
- 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 *iio = iio_priv_to_dev(adc);
- iio_device_unregister(iio);
If all you have is this in remove, you can probably get away with devm_iio_device_register and get rid of the remove entirely.
- 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,
+}; +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"); diff --git a/drivers/iio/adc/stm32-dfsdm-core.c b/drivers/iio/adc/stm32-dfsdm-core.c new file mode 100644 index 0000000..488e456 --- /dev/null +++ b/drivers/iio/adc/stm32-dfsdm-core.c @@ -0,0 +1,658 @@ +/*
- This file is part the core part STM32 DFSDM driver
- Copyright (C) 2017, STMicroelectronics - All Rights Reserved
- Author(s): Arnaud Pouliquen arnaud.pouliquen@st.com for STMicroelectronics.
- 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.
- */
+#include <linux/clk.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/regmap.h> +#include <linux/slab.h>
+#include <linux/iio/trigger.h> +#include <linux/iio/sysfs.h>
+#include "stm32-dfsdm.h"
+struct stm32_dfsdm_dev_data {
- unsigned int num_filters;
- unsigned int num_channels;
- const struct regmap_config *regmap_cfg;
+};
+#define STM32H7_DFSDM_NUM_FILTERS 4 +#define STM32H7_DFSDM_NUM_CHANNELS 8
+#define DFSDM_MAX_INT_OVERSAMPLING 256
+#define DFSDM_MAX_FL_OVERSAMPLING 1024
+#define DFSDM_MAX_RES BIT(31) +#define DFSDM_DATA_RES BIT(23)
+static bool stm32_dfsdm_volatile_reg(struct device *dev, unsigned int reg) +{
- if (reg < DFSDM_FILTER_BASE_ADR)
return false;
- /*
* Mask is done on register to avoid to list registers of all them
* filter instances.
*/
- switch (reg & DFSDM_FILTER_REG_MASK) {
- case DFSDM_CR1(0) & DFSDM_FILTER_REG_MASK:
- case DFSDM_ISR(0) & DFSDM_FILTER_REG_MASK:
- case DFSDM_JDATAR(0) & DFSDM_FILTER_REG_MASK:
- case DFSDM_RDATAR(0) & DFSDM_FILTER_REG_MASK:
return true;
- }
- return false;
+}
+static const struct regmap_config stm32h7_dfsdm_regmap_cfg = {
- .reg_bits = 32,
- .val_bits = 32,
- .reg_stride = sizeof(u32),
- .max_register = 0x2B8,
- .volatile_reg = stm32_dfsdm_volatile_reg,
- .fast_io = true,
+};
+static const struct stm32_dfsdm_dev_data stm32h7_dfsdm_data = {
- .num_filters = STM32H7_DFSDM_NUM_FILTERS,
- .num_channels = STM32H7_DFSDM_NUM_CHANNELS,
- .regmap_cfg = &stm32h7_dfsdm_regmap_cfg,
+};
+struct dfsdm_priv {
- struct platform_device *pdev; /* platform device*/
- struct stm32_dfsdm dfsdm; /* common data exported for all instances */
- unsigned int spi_clk_out_div; /* SPI clkout divider value */
- atomic_t n_active_ch; /* number of current active channels */
- /* Clock */
- struct clk *clk; /* DFSDM clock */
- struct clk *aclk; /* audio clock */
+};
+/**
- stm32_dfsdm_set_osrs - compute filter parameters.
Naming would suggest it's more specific than this. Setting over sampling ratios?
- Enable interface if n_active_ch is not null.
- @dfsdm: Handle used to retrieve dfsdm context.
- @fast: Fast mode enabled or disabled
- @oversamp: Expected oversampling between filtered sample and SD input stream
- */
+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;
- }
- /*
* Looks for filter and integrator oversampling ratios which allows
* to reach 24 bits data output resolution.
* Leave at once 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;
+}
+/**
- stm32_dfsdm_start_dfsdm - start global dfsdm IP interface.
- Enable interface if n_active_ch is not null.
- @dfsdm: Handle used to retrieve dfsdm context.
- */
+int stm32_dfsdm_start_dfsdm(struct stm32_dfsdm *dfsdm) +{
- struct dfsdm_priv *priv = container_of(dfsdm, struct dfsdm_priv, dfsdm);
- struct device *dev = &priv->pdev->dev;
- unsigned int clk_div = priv->spi_clk_out_div;
- int ret;
- if (atomic_inc_return(&priv->n_active_ch) == 1) {
/* Enable clocks */
ret = clk_prepare_enable(priv->clk);
if (ret < 0) {
dev_err(dev, "Failed to start clock\n");
return ret;
}
if (priv->aclk) {
ret = clk_prepare_enable(priv->aclk);
if (ret < 0) {
dev_err(dev, "Failed to start audio clock\n");
goto disable_clk;
}
}
/* Output the SPI CLKOUT (if clk_div == 0 clock if OFF) */
ret = regmap_update_bits(dfsdm->regmap, DFSDM_CHCFGR1(0),
DFSDM_CHCFGR1_CKOUTDIV_MASK,
DFSDM_CHCFGR1_CKOUTDIV(clk_div));
if (ret < 0)
goto disable_aclk;
/* Global enable of DFSDM interface */
ret = regmap_update_bits(dfsdm->regmap, DFSDM_CHCFGR1(0),
DFSDM_CHCFGR1_DFSDMEN_MASK,
DFSDM_CHCFGR1_DFSDMEN(1));
if (ret < 0)
goto disable_aclk;
- }
- dev_dbg(dev, "%s: n_active_ch %d\n", __func__,
atomic_read(&priv->n_active_ch));
- return 0;
+disable_aclk:
- clk_disable_unprepare(priv->aclk);
+disable_clk:
- clk_disable_unprepare(priv->clk);
- return ret;
+}
+/**
- stm32_dfsdm_stop_dfsdm - stop global DFSDM IP interface.
- Disable interface if n_active_ch is null
- @dfsdm: Handle used to retrieve dfsdm context.
- */
+int stm32_dfsdm_stop_dfsdm(struct stm32_dfsdm *dfsdm) +{
- struct dfsdm_priv *priv = container_of(dfsdm, struct dfsdm_priv, dfsdm);
- int ret;
- if (atomic_dec_and_test(&priv->n_active_ch)) {
/* Global disable of DFSDM interface */
ret = regmap_update_bits(dfsdm->regmap, DFSDM_CHCFGR1(0),
DFSDM_CHCFGR1_DFSDMEN_MASK,
DFSDM_CHCFGR1_DFSDMEN(0));
if (ret < 0)
return ret;
/* Stop SPI CLKOUT */
ret = regmap_update_bits(dfsdm->regmap, DFSDM_CHCFGR1(0),
DFSDM_CHCFGR1_CKOUTDIV_MASK,
DFSDM_CHCFGR1_CKOUTDIV(0));
if (ret < 0)
return ret;
/* Disable clocks */
clk_disable_unprepare(priv->clk);
if (priv->aclk)
clk_disable_unprepare(priv->aclk);
- }
- dev_dbg(&priv->pdev->dev, "%s: n_active_ch %d\n", __func__,
atomic_read(&priv->n_active_ch));
- return 0;
+}
+/**
- stm32_dfsdm_start_channel
- Start DFSDM IP channels and associated interface.
- @dfsdm: Handle used to retrieve dfsdm context.
- @ch_id: Channel index.
- */
+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));
+}
+/**
- stm32_dfsdm_stop_channel
- Stop DFSDM IP channels and associated interface.
- @dfsdm: Handle used to retrieve dfsdm context.
- @ch_id: Channel index.
- */
+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));
+}
+/**
- stm32_dfsdm_chan_configure
- Configure DFSDM IP channels and associated interface.
- @dfsdm: Handle used to retrieve dfsdm context.
- @ch_id: channel index.
- */
+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;
Blank line here and in similar places makes it easier for my eyes to parse at least... I'd also like to see some docs in here, not all of these are self explanatory.
- 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));
+}
+/**
- stm32_dfsdm_start_filter - Start DFSDM IP filter conversion.
- @dfsdm: Handle used to retrieve dfsdm context.
- @fl_id: Filter index.
- */
+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));
+}
+/**
- stm32_dfsdm_stop_filter - Stop DFSDM IP filter conversion.
- @dfsdm: Handle used to retrieve dfsdm context.
- @fl_id: Filter index.
- */
+void stm32_dfsdm_stop_filter(struct stm32_dfsdm *dfsdm, unsigned int fl_id) +{
- /* Mask IRQ for regular conversion achievement*/
- regmap_update_bits(dfsdm->regmap, DFSDM_CR2(fl_id),
DFSDM_CR2_REOCIE_MASK, DFSDM_CR2_REOCIE(0));
- /* Disable conversion */
- regmap_update_bits(dfsdm->regmap, DFSDM_CR1(fl_id),
DFSDM_CR1_DFEN_MASK, DFSDM_CR1_DFEN(0));
+}
+/**
- stm32_dfsdm_filter_configure - Configure DFSDM IP filter and associate it
- to channel.
- @dfsdm: Handle used to retrieve dfsdm context.
- @fl_id: channel index.
- @fl_id: Filter index.
- */
+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));
- /* Filter order and Oversampling */
Please handle each error properly as it happens rather than mudling onwards. If there is reason for this odd construction, then document it clearly.
- if (!ret)
ret = regmap_update_bits(regmap, DFSDM_FCR(fl_id),
DFSDM_FCR_FOSR_MASK,
DFSDM_FCR_FOSR(fl->fosr));
- if (!ret)
ret = regmap_update_bits(regmap, DFSDM_FCR(fl_id),
DFSDM_FCR_FORD_MASK,
DFSDM_FCR_FORD(fl->ford));
- /* If only one channel 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));
- return regmap_update_bits(regmap, DFSDM_CR1(fl_id),
DFSDM_CR1_RSYNC_MASK,
DFSDM_CR1_RSYNC(fl->sync_mode));
+}
+static const struct iio_trigger_ops dfsdm_trigger_ops = {
- .owner = THIS_MODULE,
+};
+static int stm32_dfsdm_setup_spi_trigger(struct platform_device *pdev,
struct stm32_dfsdm *dfsdm)
+{
- /*
* To be able to use buffer consumer interface a trigger is needed.
* As conversion are trigged by PDM samples from SPI bus, that makes
* sense to define the serial interface ( SPI or manchester) as
* trigger source.
It's not actually the case that you have to have a triggrer. There are plenty of drivers (particularly ones with hardware buffering) where there is no trigger envolved. That's not to say it doesn't make sense here.
I'm not entirely sure yet if it's needed... Given it has no ops, I doubt it somewhat...
*/
- struct iio_trigger *trig;
- int ret;
- trig = devm_iio_trigger_alloc(&pdev->dev, DFSDM_SPI_TRIGGER_NAME);
- if (!trig)
return -ENOMEM;
- trig->dev.parent = pdev->dev.parent;
- trig->ops = &dfsdm_trigger_ops;
- iio_trigger_set_drvdata(trig, dfsdm);
- ret = devm_iio_trigger_register(&pdev->dev, trig);
- if (ret)
return ret;
Just return ret in all cases.
- return 0;
+}
+int stm32_dfsdm_channel_parse_of(struct stm32_dfsdm *dfsdm,
struct iio_dev *indio_dev,
struct iio_chan_spec *chan, int chan_idx)
+{
- struct iio_chan_spec *ch = &chan[chan_idx];
- struct stm32_dfsdm_channel *df_ch;
- const char *of_str;
- 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;
- }
- 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];
Extra space on the line above.
- df_ch->id = ch->channel;
- ret = of_property_read_string_index(indio_dev->dev.of_node,
"st,adc-channel-types", chan_idx,
&of_str);
- val = stm32_dfsdm_str2val(of_str, stm32_dfsdm_chan_type);
- if (ret < 0 || val < 0)
df_ch->type = 0;
- else
df_ch->type = val;
- ret = of_property_read_string_index(indio_dev->dev.of_node,
"st,adc-channel-clk-src", chan_idx,
&of_str);
- val = stm32_dfsdm_str2val(of_str, stm32_dfsdm_chan_src);
- if (ret < 0 || val < 0)
df_ch->src = 0;
- else
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_parse_of(struct platform_device *pdev,
struct dfsdm_priv *priv)
+{
- struct device_node *node = pdev->dev.of_node;
- struct resource *res;
- unsigned long clk_freq;
- unsigned int spi_freq, rem;
- int ret;
- if (!node)
return -EINVAL;
- /* Get resources */
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- if (!res) {
dev_err(&pdev->dev, "Failed to get memory resource\n");
return -ENODEV;
- }
- priv->dfsdm.phys_base = res->start;
- priv->dfsdm.base = devm_ioremap_resource(&pdev->dev, res);
- /* Source clock */
- priv->clk = devm_clk_get(&pdev->dev, "dfsdm");
- if (IS_ERR(priv->clk)) {
dev_err(&pdev->dev, "No stm32_dfsdm_clk clock found\n");
return -EINVAL;
- }
- priv->aclk = devm_clk_get(&pdev->dev, "audio");
- if (IS_ERR(priv->aclk))
priv->aclk = NULL;
- if (priv->aclk)
clk_freq = clk_get_rate(priv->aclk);
- else
clk_freq = clk_get_rate(priv->clk);
- /* SPI clock freq */
- ret = of_property_read_u32(pdev->dev.of_node, "spi-max-frequency",
&spi_freq);
- if (ret < 0) {
dev_err(&pdev->dev, "Failed to get spi-max-frequency\n");
return ret;
- }
- priv->spi_clk_out_div = div_u64_rem(clk_freq, spi_freq, &rem) - 1;
- priv->dfsdm.spi_master_freq = spi_freq;
- if (rem) {
dev_warn(&pdev->dev, "SPI clock not accurate\n");
dev_warn(&pdev->dev, "%ld = %d * %d + %d\n",
clk_freq, spi_freq, priv->spi_clk_out_div + 1, rem);
- }
- return 0;
+};
+static const struct of_device_id stm32_dfsdm_of_match[] = {
- {
.compatible = "st,stm32h7-dfsdm",
.data = &stm32h7_dfsdm_data,
- },
- {}
+}; +MODULE_DEVICE_TABLE(of, stm32_dfsdm_of_match);
+static int stm32_dfsdm_remove(struct platform_device *pdev) +{
- of_platform_depopulate(&pdev->dev);
- return 0;
+}
+static int stm32_dfsdm_probe(struct platform_device *pdev) +{
- struct dfsdm_priv *priv;
- struct device_node *pnode = pdev->dev.of_node;
- const struct of_device_id *of_id;
- const struct stm32_dfsdm_dev_data *dev_data;
- struct stm32_dfsdm *dfsdm;
- int ret, i;
- priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
- if (!priv)
return -ENOMEM;
- priv->pdev = pdev;
- /* Populate data structure depending on compatibility */
- of_id = of_match_node(stm32_dfsdm_of_match, pnode);
- 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;
- dfsdm = &priv->dfsdm;
- dfsdm->fl_list = devm_kcalloc(&pdev->dev, dev_data->num_filters,
sizeof(*dfsdm->fl_list), GFP_KERNEL);
- if (!dfsdm->fl_list)
return -ENOMEM;
- dfsdm->num_fls = dev_data->num_filters;
- dfsdm->ch_list = devm_kcalloc(&pdev->dev, dev_data->num_channels,
sizeof(*dfsdm->ch_list),
GFP_KERNEL);
- if (!dfsdm->ch_list)
return -ENOMEM;
- dfsdm->num_chs = dev_data->num_channels;
- ret = stm32_dfsdm_parse_of(pdev, priv);
- if (ret < 0)
return ret;
- dfsdm->regmap = devm_regmap_init_mmio(&pdev->dev, dfsdm->base,
&stm32h7_dfsdm_regmap_cfg);
- if (IS_ERR(dfsdm->regmap)) {
ret = PTR_ERR(dfsdm->regmap);
dev_err(&pdev->dev, "%s: Failed to allocate regmap: %d\n",
__func__, ret);
return ret;
- }
- for (i = 0; i < STM32H7_DFSDM_NUM_FILTERS; i++) {
struct stm32_dfsdm_filter *fl = &dfsdm->fl_list[i];
fl->id = i;
I'd like a comment on why this is needed...
- }
- platform_set_drvdata(pdev, dfsdm);
- ret = stm32_dfsdm_setup_spi_trigger(pdev, dfsdm);
- if (ret < 0)
return ret;
- return of_platform_populate(pnode, NULL, NULL, &pdev->dev);
+}
+static struct platform_driver stm32_dfsdm_driver = {
- .probe = stm32_dfsdm_probe,
- .remove = stm32_dfsdm_remove,
- .driver = {
.name = "stm32-dfsdm",
.of_match_table = stm32_dfsdm_of_match,
- },
+};
+module_platform_driver(stm32_dfsdm_driver);
+MODULE_AUTHOR("Arnaud Pouliquen arnaud.pouliquen@st.com"); +MODULE_DESCRIPTION("STMicroelectronics STM32 dfsdm driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/stm32-dfsdm.h b/drivers/iio/adc/stm32-dfsdm.h new file mode 100644 index 0000000..bb7d74f --- /dev/null +++ b/drivers/iio/adc/stm32-dfsdm.h @@ -0,0 +1,371 @@ +/*
- This file is part of STM32 DFSDM driver
- Copyright (C) 2017, STMicroelectronics - All Rights Reserved
- Author(s): Arnaud Pouliquen arnaud.pouliquen@st.com.
- License terms: GPL V2.0.
- This program is free software; you can redistribute it and/or modify it
- under the terms of the GNU General Public License version 2 as published by
- the Free Software Foundation.
- This program is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
- details.
- */
+#ifndef MDF_STM32_DFSDM__H +#define MDF_STM32_DFSDM__H
+#include <linux/bitfield.h>
+#include <linux/iio/iio.h> +/*
- STM32 DFSDM - global register map
- | Offset | Registers block |
- | 0x000 | CHANNEL 0 + COMMON CHANNEL FIELDS |
- | 0x020 | CHANNEL 1 |
- | ... | ..... |
- | 0x0E0 | CHANNEL 7 |
- | 0x100 | FILTER 0 + COMMON FILTER FIELDs |
- | 0x200 | FILTER 1 |
- | 0x300 | FILTER 2 |
- | 0x400 | FILTER 3 |
- */
+/*
- Channels register definitions
- */
+#define DFSDM_CHCFGR1(y) ((y) * 0x20 + 0x00) +#define DFSDM_CHCFGR2(y) ((y) * 0x20 + 0x04) +#define DFSDM_AWSCDR(y) ((y) * 0x20 + 0x08) +#define DFSDM_CHWDATR(y) ((y) * 0x20 + 0x0C) +#define DFSDM_CHDATINR(y) ((y) * 0x20 + 0x10)
+/* CHCFGR1: Channel configuration register 1 */ +#define DFSDM_CHCFGR1_SITP_MASK GENMASK(1, 0) +#define DFSDM_CHCFGR1_SITP(v) FIELD_PREP(DFSDM_CHCFGR1_SITP_MASK, v) +#define DFSDM_CHCFGR1_SPICKSEL_MASK GENMASK(3, 2) +#define DFSDM_CHCFGR1_SPICKSEL(v) FIELD_PREP(DFSDM_CHCFGR1_SPICKSEL_MASK, v) +#define DFSDM_CHCFGR1_SCDEN_MASK BIT(5) +#define DFSDM_CHCFGR1_SCDEN(v) FIELD_PREP(DFSDM_CHCFGR1_SCDEN_MASK, v) +#define DFSDM_CHCFGR1_CKABEN_MASK BIT(6) +#define DFSDM_CHCFGR1_CKABEN(v) FIELD_PREP(DFSDM_CHCFGR1_CKABEN_MASK, v) +#define DFSDM_CHCFGR1_CHEN_MASK BIT(7) +#define DFSDM_CHCFGR1_CHEN(v) FIELD_PREP(DFSDM_CHCFGR1_CHEN_MASK, v) +#define DFSDM_CHCFGR1_CHINSEL_MASK BIT(8) +#define DFSDM_CHCFGR1_CHINSEL(v) FIELD_PREP(DFSDM_CHCFGR1_CHINSEL_MASK, v) +#define DFSDM_CHCFGR1_DATMPX_MASK GENMASK(13, 12) +#define DFSDM_CHCFGR1_DATMPX(v) FIELD_PREP(DFSDM_CHCFGR1_DATMPX_MASK, v) +#define DFSDM_CHCFGR1_DATPACK_MASK GENMASK(15, 14) +#define DFSDM_CHCFGR1_DATPACK(v) FIELD_PREP(DFSDM_CHCFGR1_DATPACK_MASK, v) +#define DFSDM_CHCFGR1_CKOUTDIV_MASK GENMASK(23, 16) +#define DFSDM_CHCFGR1_CKOUTDIV(v) FIELD_PREP(DFSDM_CHCFGR1_CKOUTDIV_MASK, v) +#define DFSDM_CHCFGR1_CKOUTSRC_MASK BIT(30) +#define DFSDM_CHCFGR1_CKOUTSRC(v) FIELD_PREP(DFSDM_CHCFGR1_CKOUTSRC_MASK, v) +#define DFSDM_CHCFGR1_DFSDMEN_MASK BIT(31) +#define DFSDM_CHCFGR1_DFSDMEN(v) FIELD_PREP(DFSDM_CHCFGR1_DFSDMEN_MASK, v)
+/* CHCFGR2: Channel configuration register 2 */ +#define DFSDM_CHCFGR2_DTRBS_MASK GENMASK(7, 3) +#define DFSDM_CHCFGR2_DTRBS(v) FIELD_PREP(DFSDM_CHCFGR2_DTRBS_MASK, v) +#define DFSDM_CHCFGR2_OFFSET_MASK GENMASK(31, 8) +#define DFSDM_CHCFGR2_OFFSET(v) FIELD_PREP(DFSDM_CHCFGR2_OFFSET_MASK, v)
+/* AWSCDR: Channel analog watchdog and short circuit detector */ +#define DFSDM_AWSCDR_SCDT_MASK GENMASK(7, 0) +#define DFSDM_AWSCDR_SCDT(v) FIELD_PREP(DFSDM_AWSCDR_SCDT_MASK, v) +#define DFSDM_AWSCDR_BKSCD_MASK GENMASK(15, 12) +#define DFSDM_AWSCDR_BKSCD(v) FIELD_PREP(DFSDM_AWSCDR_BKSCD_MASK, v) +#define DFSDM_AWSCDR_AWFOSR_MASK GENMASK(20, 16) +#define DFSDM_AWSCDR_AWFOSR(v) FIELD_PREP(DFSDM_AWSCDR_AWFOSR_MASK, v) +#define DFSDM_AWSCDR_AWFORD_MASK GENMASK(23, 22) +#define DFSDM_AWSCDR_AWFORD(v) FIELD_PREP(DFSDM_AWSCDR_AWFORD_MASK, v)
+/*
- Filters register definitions
- */
+#define DFSDM_FILTER_BASE_ADR 0x100 +#define DFSDM_FILTER_REG_MASK 0x7F +#define DFSDM_FILTER_X_BASE_ADR(x) ((x) * 0x80 + DFSDM_FILTER_BASE_ADR)
+#define DFSDM_CR1(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x00) +#define DFSDM_CR2(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x04) +#define DFSDM_ISR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x08) +#define DFSDM_ICR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x0C) +#define DFSDM_JCHGR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x10) +#define DFSDM_FCR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x14) +#define DFSDM_JDATAR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x18) +#define DFSDM_RDATAR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x1C) +#define DFSDM_AWHTR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x20) +#define DFSDM_AWLTR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x24) +#define DFSDM_AWSR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x28) +#define DFSDM_AWCFR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x2C) +#define DFSDM_EXMAX(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x30) +#define DFSDM_EXMIN(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x34) +#define DFSDM_CNVTIMR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x38)
+/* CR1 Control register 1 */ +#define DFSDM_CR1_DFEN_MASK BIT(0) +#define DFSDM_CR1_DFEN(v) FIELD_PREP(DFSDM_CR1_DFEN_MASK, v) +#define DFSDM_CR1_JSWSTART_MASK BIT(1) +#define DFSDM_CR1_JSWSTART(v) FIELD_PREP(DFSDM_CR1_JSWSTART_MASK, v) +#define DFSDM_CR1_JSYNC_MASK BIT(3) +#define DFSDM_CR1_JSYNC(v) FIELD_PREP(DFSDM_CR1_JSYNC_MASK, v) +#define DFSDM_CR1_JSCAN_MASK BIT(4) +#define DFSDM_CR1_JSCAN(v) FIELD_PREP(DFSDM_CR1_JSCAN_MASK, v) +#define DFSDM_CR1_JDMAEN_MASK BIT(5) +#define DFSDM_CR1_JDMAEN(v) FIELD_PREP(DFSDM_CR1_JDMAEN_MASK, v) +#define DFSDM_CR1_JEXTSEL_MASK GENMASK(12, 8) +#define DFSDM_CR1_JEXTSEL(v) FIELD_PREP(DFSDM_CR1_JEXTSEL_MASK, v) +#define DFSDM_CR1_JEXTEN_MASK GENMASK(14, 13) +#define DFSDM_CR1_JEXTEN(v) FIELD_PREP(DFSDM_CR1_JEXTEN_MASK, v) +#define DFSDM_CR1_RSWSTART_MASK BIT(17) +#define DFSDM_CR1_RSWSTART(v) FIELD_PREP(DFSDM_CR1_RSWSTART_MASK, v) +#define DFSDM_CR1_RCONT_MASK BIT(18) +#define DFSDM_CR1_RCONT(v) FIELD_PREP(DFSDM_CR1_RCONT_MASK, v) +#define DFSDM_CR1_RSYNC_MASK BIT(19) +#define DFSDM_CR1_RSYNC(v) FIELD_PREP(DFSDM_CR1_RSYNC_MASK, v) +#define DFSDM_CR1_RDMAEN_MASK BIT(21) +#define DFSDM_CR1_RDMAEN(v) FIELD_PREP(DFSDM_CR1_RDMAEN_MASK, v) +#define DFSDM_CR1_RCH_MASK GENMASK(26, 24) +#define DFSDM_CR1_RCH(v) FIELD_PREP(DFSDM_CR1_RCH_MASK, v) +#define DFSDM_CR1_FAST_MASK BIT(29) +#define DFSDM_CR1_FAST(v) FIELD_PREP(DFSDM_CR1_FAST_MASK, v) +#define DFSDM_CR1_AWFSEL_MASK BIT(30) +#define DFSDM_CR1_AWFSEL(v) FIELD_PREP(DFSDM_CR1_AWFSEL_MASK, v)
+/* CR2: Control register 2 */ +#define DFSDM_CR2_IE_MASK GENMASK(6, 0) +#define DFSDM_CR2_IE(v) FIELD_PREP(DFSDM_CR2_IE_MASK, v) +#define DFSDM_CR2_JEOCIE_MASK BIT(0) +#define DFSDM_CR2_JEOCIE(v) FIELD_PREP(DFSDM_CR2_JEOCIE_MASK, v) +#define DFSDM_CR2_REOCIE_MASK BIT(1) +#define DFSDM_CR2_REOCIE(v) FIELD_PREP(DFSDM_CR2_REOCIE_MASK, v) +#define DFSDM_CR2_JOVRIE_MASK BIT(2) +#define DFSDM_CR2_JOVRIE(v) FIELD_PREP(DFSDM_CR2_JOVRIE_MASK, v) +#define DFSDM_CR2_ROVRIE_MASK BIT(3) +#define DFSDM_CR2_ROVRIE(v) FIELD_PREP(DFSDM_CR2_ROVRIE_MASK, v) +#define DFSDM_CR2_AWDIE_MASK BIT(4) +#define DFSDM_CR2_AWDIE(v) FIELD_PREP(DFSDM_CR2_AWDIE_MASK, v) +#define DFSDM_CR2_SCDIE_MASK BIT(5) +#define DFSDM_CR2_SCDIE(v) FIELD_PREP(DFSDM_CR2_SCDIE_MASK, v) +#define DFSDM_CR2_CKABIE_MASK BIT(6) +#define DFSDM_CR2_CKABIE(v) FIELD_PREP(DFSDM_CR2_CKABIE_MASK, v) +#define DFSDM_CR2_EXCH_MASK GENMASK(15, 8) +#define DFSDM_CR2_EXCH(v) FIELD_PREP(DFSDM_CR2_EXCH_MASK, v) +#define DFSDM_CR2_AWDCH_MASK GENMASK(23, 16) +#define DFSDM_CR2_AWDCH(v) FIELD_PREP(DFSDM_CR2_AWDCH_MASK, v)
+/* ISR: Interrupt status register */ +#define DFSDM_ISR_JEOCF_MASK BIT(0) +#define DFSDM_ISR_JEOCF(v) FIELD_PREP(DFSDM_ISR_JEOCF_MASK, v) +#define DFSDM_ISR_REOCF_MASK BIT(1) +#define DFSDM_ISR_REOCF(v) FIELD_PREP(DFSDM_ISR_REOCF_MASK, v) +#define DFSDM_ISR_JOVRF_MASK BIT(2) +#define DFSDM_ISR_JOVRF(v) FIELD_PREP(DFSDM_ISR_JOVRF_MASK, v) +#define DFSDM_ISR_ROVRF_MASK BIT(3) +#define DFSDM_ISR_ROVRF(v) FIELD_PREP(DFSDM_ISR_ROVRF_MASK, v) +#define DFSDM_ISR_AWDF_MASK BIT(4) +#define DFSDM_ISR_AWDF(v) FIELD_PREP(DFSDM_ISR_AWDF_MASK, v) +#define DFSDM_ISR_JCIP_MASK BIT(13) +#define DFSDM_ISR_JCIP(v) FIELD_PREP(DFSDM_ISR_JCIP_MASK, v) +#define DFSDM_ISR_RCIP_MASK BIT(14) +#define DFSDM_ISR_RCIP(v) FIELD_PREP(DFSDM_ISR_RCIP, v) +#define DFSDM_ISR_CKABF_MASK GENMASK(23, 16) +#define DFSDM_ISR_CKABF(v) FIELD_PREP(DFSDM_ISR_CKABF_MASK, v) +#define DFSDM_ISR_SCDF_MASK GENMASK(31, 24) +#define DFSDM_ISR_SCDF(v) FIELD_PREP(DFSDM_ISR_SCDF_MASK, v)
+/* ICR: Interrupt flag clear register */ +#define DFSDM_ICR_CLRJOVRF_MASK BIT(2) +#define DFSDM_ICR_CLRJOVRF(v) FIELD_PREP(DFSDM_ICR_CLRJOVRF_MASK, v) +#define DFSDM_ICR_CLRROVRF_MASK BIT(3) +#define DFSDM_ICR_CLRROVRF(v) FIELD_PREP(DFSDM_ICR_CLRROVRF_MASK, v) +#define DFSDM_ICR_CLRCKABF_MASK GENMASK(23, 16) +#define DFSDM_ICR_CLRCKABF(v) FIELD_PREP(DFSDM_ICR_CLRCKABF_MASK, v) +#define DFSDM_ICR_CLRCKABF_CH_MASK(y) BIT(16 + (y)) +#define DFSDM_ICR_CLRCKABF_CH(v, y) \
(((v) << (16 + (y))) & DFSDM_ICR_CLRCKABF_CH_MASK(y))
+#define DFSDM_ICR_CLRSCDF_MASK GENMASK(31, 24) +#define DFSDM_ICR_CLRSCDF(v) FIELD_PREP(DFSDM_ICR_CLRSCDF_MASK, v) +#define DFSDM_ICR_CLRSCDF_CH_MASK(y) BIT(24 + (y)) +#define DFSDM_ICR_CLRSCDF_CH(v, y) \
(((v) << (24 + (y))) & DFSDM_ICR_CLRSCDF_MASK(y))
+/* FCR: Filter control register */ +#define DFSDM_FCR_IOSR_MASK GENMASK(7, 0) +#define DFSDM_FCR_IOSR(v) FIELD_PREP(DFSDM_FCR_IOSR_MASK, v) +#define DFSDM_FCR_FOSR_MASK GENMASK(25, 16) +#define DFSDM_FCR_FOSR(v) FIELD_PREP(DFSDM_FCR_FOSR_MASK, v) +#define DFSDM_FCR_FORD_MASK GENMASK(31, 29) +#define DFSDM_FCR_FORD(v) FIELD_PREP(DFSDM_FCR_FORD_MASK, v)
+/* RDATAR: Filter data register for regular channel */ +#define DFSDM_DATAR_CH_MASK GENMASK(2, 0) +#define DFSDM_DATAR_DATA_OFFSET 8 +#define DFSDM_DATAR_DATA_MASK GENMASK(31, DFSDM_DATAR_DATA_OFFSET)
+/* AWLTR: Filter analog watchdog low threshold register */ +#define DFSDM_AWLTR_BKAWL_MASK GENMASK(3, 0) +#define DFSDM_AWLTR_BKAWL(v) FIELD_PREP(DFSDM_AWLTR_BKAWL_MASK, v) +#define DFSDM_AWLTR_AWLT_MASK GENMASK(31, 8) +#define DFSDM_AWLTR_AWLT(v) FIELD_PREP(DFSDM_AWLTR_AWLT_MASK, v)
+/* AWHTR: Filter analog watchdog low threshold register */ +#define DFSDM_AWHTR_BKAWH_MASK GENMASK(3, 0) +#define DFSDM_AWHTR_BKAWH(v) FIELD_PREP(DFSDM_AWHTR_BKAWH_MASK, v) +#define DFSDM_AWHTR_AWHT_MASK GENMASK(31, 8) +#define DFSDM_AWHTR_AWHT(v) FIELD_PREP(DFSDM_AWHTR_AWHT_MASK, v)
+/* AWSR: Filter watchdog status register */ +#define DFSDM_AWSR_AWLTF_MASK GENMASK(7, 0) +#define DFSDM_AWSR_AWLTF(v) FIELD_PREP(DFSDM_AWSR_AWLTF_MASK, v) +#define DFSDM_AWSR_AWHTF_MASK GENMASK(15, 8) +#define DFSDM_AWSR_AWHTF(v) FIELD_PREP(DFSDM_AWSR_AWHTF_MASK, v)
+/* AWCFR: Filter watchdog status register */ +#define DFSDM_AWCFR_AWLTF_MASK GENMASK(7, 0) +#define DFSDM_AWCFR_AWLTF(v) FIELD_PREP(DFSDM_AWCFR_AWLTF_MASK, v) +#define DFSDM_AWCFR_AWHTF_MASK GENMASK(15, 8) +#define DFSDM_AWCFR_AWHTF(v) FIELD_PREP(DFSDM_AWCFR_AWHTF_MASK, v)
+/* DFSDM filter order */ +enum stm32_dfsdm_sinc_order {
- DFSDM_FASTSINC_ORDER, /* FastSinc filter type */
- DFSDM_SINC1_ORDER, /* Sinc 1 filter type */
- DFSDM_SINC2_ORDER, /* Sinc 2 filter type */
- DFSDM_SINC3_ORDER, /* Sinc 3 filter type */
- DFSDM_SINC4_ORDER, /* Sinc 4 filter type (N.A. for watchdog) */
- DFSDM_SINC5_ORDER, /* Sinc 5 filter type (N.A. for watchdog) */
- DFSDM_NB_SINC_ORDER,
+};
+/**
- struct stm32_dfsdm_filter - structure relative to stm32 FDSDM filter
- TODO: complete structure.
nice :) RFC I guess :)
- @id: filetr ID,
- */
+struct stm32_dfsdm_filter {
- unsigned int id;
- unsigned int iosr; /* integrator oversampling */
- unsigned int fosr; /* filter oversampling */
- enum stm32_dfsdm_sinc_order ford;
- u64 res; /* output sample resolution */
- unsigned int sync_mode; /* filter suynchronized with filter0 */
- unsigned int fast; /* filter fast mode */
+};
+/**
- struct stm32_dfsdm_channel - structure relative to stm32 FDSDM channel
- TODO: complete structure.
- @id: filetr ID,
filter
- */
+struct stm32_dfsdm_channel {
- unsigned int id; /* id of the channel */
- unsigned int type; /* interface type linked to stm32_dfsdm_chan_type */
- unsigned int src; /* interface type linked to stm32_dfsdm_chan_src */
- unsigned int alt_si; /* use alternative serial input interface */
+};
+/**
- struct stm32_dfsdm - stm32 FDSDM driver common data (for all instances)
- @base: control registers base cpu addr
- @phys_base: DFSDM IP register physical address.
- @fl_list: filter resources list
- @num_fl: number of filter resources available
- @ch_list: channel resources list
- @num_chs: number of channel resources available
- */
+struct stm32_dfsdm {
- void __iomem *base;
- phys_addr_t phys_base;
- struct regmap *regmap;
- struct stm32_dfsdm_filter *fl_list;
- unsigned int num_fls;
- struct stm32_dfsdm_channel *ch_list;
- unsigned int num_chs;
- unsigned int spi_master_freq;
+};
+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 */
- { 0, 0},
+};
+/* DFSDM channel serial spi clock source */ +enum stm32_dfsdm_spi_clk_src {
- DFSDM_CHANNEL_SPI_CLOCK_EXTERNAL,
- DFSDM_CHANNEL_SPI_CLOCK_INTERNAL,
- DFSDM_CHANNEL_SPI_CLOCK_INTERNAL_DIV2_FALLING,
- DFSDM_CHANNEL_SPI_CLOCK_INTERNAL_DIV2_RISING
+};
+/* 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 },
- { 0, 0 },
+};
+/* DFSDM Serial interface trigger name */ +#define DFSDM_SPI_TRIGGER_NAME "DFSDM_SERIAL_IN"
+static inline 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;
+}
+int stm32_dfsdm_set_osrs(struct stm32_dfsdm_filter *fl, unsigned int fast,
unsigned int oversamp);
+int stm32_dfsdm_start_dfsdm(struct stm32_dfsdm *dfsdm); +int stm32_dfsdm_stop_dfsdm(struct stm32_dfsdm *dfsdm);
+int stm32_dfsdm_filter_configure(struct stm32_dfsdm *dfsdm, unsigned int fl_id,
unsigned int ch_id);
+int stm32_dfsdm_start_filter(struct stm32_dfsdm *dfsdm, unsigned int fl_id); +void stm32_dfsdm_stop_filter(struct stm32_dfsdm *dfsdm, unsigned int fl_id);
+int stm32_dfsdm_chan_configure(struct stm32_dfsdm *dfsdm,
struct stm32_dfsdm_channel *ch);
+int stm32_dfsdm_start_channel(struct stm32_dfsdm *dfsdm, unsigned int ch_id); +void stm32_dfsdm_stop_channel(struct stm32_dfsdm *dfsdm, unsigned int ch_id);
+int stm32_dfsdm_channel_parse_of(struct stm32_dfsdm *dfsdm,
struct iio_dev *indio_dev,
struct iio_chan_spec *chan, int chan_idx);
+#endif
On 17/03/17 14:08, Arnaud Pouliquen wrote:
Add DFSDM driver to handle PDM audio microphones.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com
So key element here is that we really need to have a proper way of doing DMA buffer consumers from IIO (as you said in your cover letter).
Not entirely obvious how to do this. Would like some input from Lars if possible. I'm afraid I don't have an suitable hardware to try anything out on (or the time really!). This will obviously be quite different from the single scan stuff you have seen is there at the moment.
Whether this whole approach makes sense vs doing the dma in the alsa driver isn't clear to me.
On the plus side, presumably we'll get love dma ADC support for free as part of doing it this way ;)
It's been a while since I looked at the IIO dma buffer stuff at all. Will try and refresh my memory sometime this week.
Jonathan
drivers/iio/adc/Kconfig | 13 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/stm32-dfsdm-audio.c | 720 ++++++++++++++++++++++++++++++ include/linux/iio/adc/stm32-dfsdm-audio.h | 41 ++ 4 files changed, 775 insertions(+) create mode 100644 drivers/iio/adc/stm32-dfsdm-audio.c create mode 100644 include/linux/iio/adc/stm32-dfsdm-audio.h
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 3e0eb11..c108933 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -478,6 +478,19 @@ config STM32_DFSDM_ADC This driver can also be built as a module. If so, the module will be called stm32-dfsdm-adc.
+config STM32_DFSDM_AUDIO
- tristate "STMicroelectronics STM32 dfsdm audio
- depends on (ARCH_STM32 && OF) || COMPILE_TEST
- select STM32_DFSDM_CORE
- select REGMAP_MMIO
- select IIO_BUFFER_DMAENGINE
- help
Select this option to support Audio PDM micophone 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-audio.
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 161f271..79f975d 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -44,6 +44,7 @@ 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 +obj-$(CONFIG_STM32_DFSDM_AUDIO) += stm32-dfsdm-audio.o obj-$(CONFIG_STM32_DFSDM_CORE) += stm32-dfsdm-core.o obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o obj-$(CONFIG_TI_ADC0832) += ti-adc0832.o diff --git a/drivers/iio/adc/stm32-dfsdm-audio.c b/drivers/iio/adc/stm32-dfsdm-audio.c new file mode 100644 index 0000000..115ef8f --- /dev/null +++ b/drivers/iio/adc/stm32-dfsdm-audio.c @@ -0,0 +1,720 @@ +/*
- This file is the ADC part of of the STM32 DFSDM 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/dmaengine.h> +#include <linux/dma-mapping.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/slab.h>
+#include <linux/iio/buffer.h> +#include <linux/iio/hw_consumer.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/trigger.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h>
+#include "stm32-dfsdm.h"
+#define DFSDM_DMA_BUFFER_SIZE (4 * PAGE_SIZE)
+struct stm32_dfsdm_audio {
- struct stm32_dfsdm *dfsdm;
- unsigned int fl_id;
- unsigned int ch_id;
- unsigned int spi_freq; /* SPI bus clock frequency */
- unsigned int sample_freq; /* Sample frequency after filter decimation */
- u8 *rx_buf;
- unsigned int bufi; /* Buffer current position */
- unsigned int buf_sz; /* Buffer size */
- struct dma_chan *dma_chan;
- dma_addr_t dma_buf;
- int (*cb)(const void *data, size_t size, void *cb_priv);
- void *cb_priv;
+};
+const char *stm32_dfsdm_spi_trigger = DFSDM_SPI_TRIGGER_NAME;
+static ssize_t dfsdm_audio_get_rate(struct iio_dev *indio_dev, uintptr_t priv,
const struct iio_chan_spec *chan, char *buf)
+{
- struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev);
- return snprintf(buf, PAGE_SIZE, "%d\n", pdmc->sample_freq);
+}
+static ssize_t dfsdm_audio_set_rate(struct iio_dev *indio_dev, uintptr_t priv,
const struct iio_chan_spec *chan,
const char *buf, size_t len)
+{
- struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev);
- struct stm32_dfsdm_filter *fl = &pdmc->dfsdm->fl_list[pdmc->fl_id];
- struct stm32_dfsdm_channel *ch = &pdmc->dfsdm->ch_list[pdmc->ch_id];
- unsigned int spi_freq = pdmc->spi_freq;
- unsigned int sample_freq;
- int ret;
- ret = kstrtoint(buf, 0, &sample_freq);
- if (ret)
return ret;
- dev_dbg(&indio_dev->dev, "Requested sample_freq :%d\n", sample_freq);
- if (!sample_freq)
return -EINVAL;
- if (ch->src != DFSDM_CHANNEL_SPI_CLOCK_EXTERNAL)
spi_freq = pdmc->dfsdm->spi_master_freq;
- if (spi_freq % sample_freq)
dev_warn(&indio_dev->dev, "Sampling rate not accurate (%d)\n",
spi_freq / (spi_freq / sample_freq));
- ret = stm32_dfsdm_set_osrs(fl, 0, (spi_freq / sample_freq));
- if (ret < 0) {
dev_err(&indio_dev->dev,
"Not able to find filter parameter that match!\n");
return ret;
- }
- pdmc->sample_freq = sample_freq;
- return len;
+}
+static ssize_t dfsdm_audio_get_spiclk(struct iio_dev *indio_dev, uintptr_t priv,
const struct iio_chan_spec *chan,
char *buf)
+{
- struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev);
- return snprintf(buf, PAGE_SIZE, "%d\n", pdmc->spi_freq);
+}
+static ssize_t dfsdm_audio_set_spiclk(struct iio_dev *indio_dev, uintptr_t priv,
const struct iio_chan_spec *chan,
const char *buf, size_t len)
+{
- struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev);
- struct stm32_dfsdm_filter *fl = &pdmc->dfsdm->fl_list[pdmc->fl_id];
- struct stm32_dfsdm_channel *ch = &pdmc->dfsdm->ch_list[pdmc->ch_id];
- unsigned int sample_freq = pdmc->sample_freq;
- unsigned int spi_freq;
- int ret;
- /* If DFSDM is master on SPI, SPI freq can not be updated */
- if (ch->src != DFSDM_CHANNEL_SPI_CLOCK_EXTERNAL)
return -EPERM;
- ret = kstrtoint(buf, 0, &spi_freq);
- if (ret)
return ret;
- dev_dbg(&indio_dev->dev, "Requested frequency :%d\n", spi_freq);
- if (!spi_freq)
return -EINVAL;
- if (sample_freq) {
if (spi_freq % sample_freq)
dev_warn(&indio_dev->dev,
"Sampling rate not accurate (%d)\n",
spi_freq / (spi_freq / sample_freq));
ret = stm32_dfsdm_set_osrs(fl, 0, (spi_freq / sample_freq));
if (ret < 0) {
dev_err(&indio_dev->dev,
"No filter parameters that match!\n");
return ret;
}
- }
- pdmc->spi_freq = spi_freq;
- return len;
+}
+/*
- Define external info for SPI Frequency and audio sampling rate that can be
- configured by ASoC driver through consumer.h API
- */
+static const struct iio_chan_spec_ext_info dfsdm_adc_ext_info[] = {
- /* filter oversampling: Post filter oversampling ratio */
- {
.name = "audio_sampling_rate",
.shared = IIO_SHARED_BY_TYPE,
.read = dfsdm_audio_get_rate,
.write = dfsdm_audio_set_rate,
- },
- /* data_right_bit_shift : Filter output data shifting */
- {
.name = "spi_clk_freq",
.shared = IIO_SHARED_BY_TYPE,
.read = dfsdm_audio_get_spiclk,
.write = dfsdm_audio_set_spiclk,
- },
- {},
+};
+static int stm32_dfsdm_start_conv(struct stm32_dfsdm_audio *pdmc, bool single) +{
- struct regmap *regmap = pdmc->dfsdm->regmap;
- int ret;
- ret = stm32_dfsdm_start_dfsdm(pdmc->dfsdm);
- if (ret < 0)
return ret;
- ret = stm32_dfsdm_start_channel(pdmc->dfsdm, pdmc->ch_id);
- if (ret < 0)
goto stop_dfsdm;
- ret = stm32_dfsdm_filter_configure(pdmc->dfsdm, pdmc->fl_id,
pdmc->ch_id);
- if (ret < 0)
goto stop_channels;
- /* Enable DMA transfer*/
- ret = regmap_update_bits(regmap, DFSDM_CR1(pdmc->fl_id),
DFSDM_CR1_RDMAEN_MASK, DFSDM_CR1_RDMAEN(1));
- if (ret < 0)
return ret;
- /* Enable conversion triggered by SPI clock*/
- ret = regmap_update_bits(regmap, DFSDM_CR1(pdmc->fl_id),
DFSDM_CR1_RCONT_MASK, DFSDM_CR1_RCONT(1));
- if (ret < 0)
return ret;
- ret = stm32_dfsdm_start_filter(pdmc->dfsdm, pdmc->fl_id);
- if (ret < 0)
goto stop_channels;
- return 0;
+stop_channels:
- stm32_dfsdm_stop_channel(pdmc->dfsdm, pdmc->fl_id);
+stop_dfsdm:
- stm32_dfsdm_stop_dfsdm(pdmc->dfsdm);
- return ret;
+}
+static void stm32_dfsdm_stop_conv(struct stm32_dfsdm_audio *pdmc) +{
- stm32_dfsdm_stop_filter(pdmc->dfsdm, pdmc->fl_id);
- stm32_dfsdm_stop_channel(pdmc->dfsdm, pdmc->ch_id);
- stm32_dfsdm_stop_dfsdm(pdmc->dfsdm);
+}
+static int stm32_dfsdm_set_watermark(struct iio_dev *indio_dev,
unsigned int val)
+{
- struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev);
- unsigned int watermark = DFSDM_DMA_BUFFER_SIZE / 2;
- /*
* DMA cyclic transfers are used, buffer is split into two periods.
* There should be :
* - always one buffer (period) DMA is working on
* - one buffer (period) driver pushed to ASoC side ().
*/
- watermark = min(watermark, val * (unsigned int)(sizeof(u32)));
- pdmc->buf_sz = watermark * 2;
- return 0;
+}
+int stm32_dfsdm_validate_trigger(struct iio_dev *indio_dev,
struct iio_trigger *trig)
+{
- if (!strcmp(stm32_dfsdm_spi_trigger, trig->name))
return 0;
- return -EINVAL;
+}
+static const struct iio_info stm32_dfsdm_info_pdmc = {
- .hwfifo_set_watermark = stm32_dfsdm_set_watermark,
- .driver_module = THIS_MODULE,
- .validate_trigger = stm32_dfsdm_validate_trigger,
+};
+static irqreturn_t stm32_dfsdm_irq(int irq, void *arg) +{
- struct stm32_dfsdm_audio *pdmc = arg;
- struct iio_dev *indio_dev = iio_priv_to_dev(pdmc);
- struct regmap *regmap = pdmc->dfsdm->regmap;
- unsigned int status;
- regmap_read(regmap, DFSDM_ISR(pdmc->fl_id), &status);
- if (status & DFSDM_ISR_ROVRF_MASK) {
dev_err(&indio_dev->dev, "Unexpected Conversion overflow\n");
regmap_update_bits(regmap, DFSDM_ICR(pdmc->fl_id),
DFSDM_ICR_CLRROVRF_MASK,
DFSDM_ICR_CLRROVRF_MASK);
- }
- return IRQ_HANDLED;
+}
+static unsigned int stm32_dfsdm_audio_avail_data(struct stm32_dfsdm_audio *pdmc) +{
- struct dma_tx_state state;
- enum dma_status status;
- status = dmaengine_tx_status(pdmc->dma_chan,
pdmc->dma_chan->cookie,
&state);
- if (status == DMA_IN_PROGRESS) {
/* Residue is size in bytes from end of buffer */
unsigned int i = pdmc->buf_sz - state.residue;
unsigned int size;
/* Return available bytes */
if (i >= pdmc->bufi)
size = i - pdmc->bufi;
else
size = pdmc->buf_sz + i - pdmc->bufi;
return size;
- }
- return 0;
+}
+static void stm32_dfsdm_audio_dma_buffer_done(void *data) +{
- struct iio_dev *indio_dev = data;
- iio_trigger_poll_chained(indio_dev->trig);
This shouldn't be going through the trigger infrastructure at all really. I'm not 100% sure what doing so is gaining you...
+}
+static int stm32_dfsdm_audio_dma_start(struct iio_dev *indio_dev) +{
- struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev);
- struct dma_async_tx_descriptor *desc;
- dma_cookie_t cookie;
- int ret;
- if (!pdmc->dma_chan)
return -EINVAL;
- dev_dbg(&indio_dev->dev, "%s size=%d watermark=%d\n", __func__,
pdmc->buf_sz, pdmc->buf_sz / 2);
- /* Prepare a DMA cyclic transaction */
- desc = dmaengine_prep_dma_cyclic(pdmc->dma_chan,
pdmc->dma_buf,
pdmc->buf_sz, pdmc->buf_sz / 2,
DMA_DEV_TO_MEM,
DMA_PREP_INTERRUPT);
- if (!desc)
return -EBUSY;
- desc->callback = stm32_dfsdm_audio_dma_buffer_done;
- desc->callback_param = indio_dev;
- cookie = dmaengine_submit(desc);
- ret = dma_submit_error(cookie);
- if (ret) {
dmaengine_terminate_all(pdmc->dma_chan);
return ret;
- }
- /* Issue pending DMA requests */
- dma_async_issue_pending(pdmc->dma_chan);
- return 0;
+}
+static int stm32_dfsdm_postenable(struct iio_dev *indio_dev) +{
- struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev);
- int ret;
- dev_dbg(&indio_dev->dev, "%s\n", __func__);
- /* Reset pdmc buffer index */
- pdmc->bufi = 0;
- ret = stm32_dfsdm_start_conv(pdmc, false);
- if (ret) {
dev_err(&indio_dev->dev, "Can't start conversion\n");
return ret;
- }
- ret = stm32_dfsdm_audio_dma_start(indio_dev);
- if (ret) {
dev_err(&indio_dev->dev, "Can't start DMA\n");
goto err_stop_conv;
- }
- ret = iio_triggered_buffer_postenable(indio_dev);
- if (ret < 0) {
dev_err(&indio_dev->dev, "%s :%d\n", __func__, __LINE__);
goto err_stop_dma;
- }
- return 0;
+err_stop_dma:
- if (pdmc->dma_chan)
dmaengine_terminate_all(pdmc->dma_chan);
+err_stop_conv:
- stm32_dfsdm_stop_conv(pdmc);
- return ret;
+}
+static int stm32_dfsdm_predisable(struct iio_dev *indio_dev) +{
- struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev);
- int ret;
- dev_dbg(&indio_dev->dev, "%s\n", __func__);
- ret = iio_triggered_buffer_predisable(indio_dev);
- if (ret < 0)
dev_err(&indio_dev->dev, "Predisable failed\n");
- if (pdmc->dma_chan)
dmaengine_terminate_all(pdmc->dma_chan);
- stm32_dfsdm_stop_conv(pdmc);
- return 0;
+}
+static const struct iio_buffer_setup_ops stm32_dfsdm_buffer_setup_ops = {
- .postenable = &stm32_dfsdm_postenable,
- .predisable = &stm32_dfsdm_predisable,
+};
+static irqreturn_t stm32_dfsdm_audio_trigger_handler(int irq, void *p) +{
OK. So now I kind of understand what the trigger provided in the adc driver was about. hmm. Triggers are supposed to be per sample so this is indeed a hack. We need fullblown DMA buffer consumers to do this right.
Probably best person to comment on that is Lars.
- struct iio_poll_func *pf = p;
- struct iio_dev *indio_dev = pf->indio_dev;
- struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev);
- size_t old_pos;
- int available = stm32_dfsdm_audio_avail_data(pdmc);
- /*
* Buffer interface is not support cyclic DMA buffer,and offer only
* an interface to push data samples per samples.
* For this reason iio_push_to_buffers_with_timestamp in not used
* and interface is hacked using a private callback registered by ASoC.
* This should be a temporary solution waiting a cyclic DMA engine
* support in IIO.
*/
- dev_dbg(&indio_dev->dev, "%s: pos = %d, available = %d\n", __func__,
pdmc->bufi, available);
- old_pos = pdmc->bufi;
- while (available >= indio_dev->scan_bytes) {
u32 *buffer = (u32 *)&pdmc->rx_buf[pdmc->bufi];
/* Mask 8 LSB that contains the channel ID */
*buffer &= 0xFFFFFF00;
available -= indio_dev->scan_bytes;
pdmc->bufi += indio_dev->scan_bytes;
if (pdmc->bufi >= pdmc->buf_sz) {
if (pdmc->cb)
pdmc->cb(&pdmc->rx_buf[old_pos],
pdmc->buf_sz - old_pos, pdmc->cb_priv);
pdmc->bufi = 0;
old_pos = 0;
}
- }
- if (pdmc->cb)
pdmc->cb(&pdmc->rx_buf[old_pos], pdmc->bufi - old_pos,
pdmc->cb_priv);
- iio_trigger_notify_done(indio_dev->trig);
- return IRQ_HANDLED;
+}
+/**
- stm32_dfsdm_get_buff_cb - register a callback
- that will be called when DMA transfer period is achieved.
- @iio_dev: Handle to IIO device.
- @cb: pointer to callback function.
- @data: pointer to data buffer
- @size: size in byte of the data buffer
- @private: pointer to consumer private structure
- @private: pointer to consumer private structure
- */
+int stm32_dfsdm_get_buff_cb(struct iio_dev *iio_dev,
int (*cb)(const void *data, size_t size,
void *private),
void *private)
+{
- struct stm32_dfsdm_audio *pdmc;
- if (!iio_dev)
return -EINVAL;
- pdmc = iio_priv(iio_dev);
- if (iio_dev != iio_priv_to_dev(pdmc))
return -EINVAL;
- pdmc->cb = cb;
- pdmc->cb_priv = private;
- return 0;
+} +EXPORT_SYMBOL_GPL(stm32_dfsdm_get_buff_cb);
+/**
- stm32_dfsdm_release_buff_cb - unregister buffer callback
- @iio_dev: Handle to IIO device.
- */
+int stm32_dfsdm_release_buff_cb(struct iio_dev *iio_dev) +{
- struct stm32_dfsdm_audio *pdmc;
- if (!iio_dev)
return -EINVAL;
- pdmc = iio_priv(iio_dev);
- if (iio_dev != iio_priv_to_dev(pdmc))
return -EINVAL;
- pdmc->cb = NULL;
- pdmc->cb_priv = NULL;
- return 0;
+} +EXPORT_SYMBOL_GPL(stm32_dfsdm_release_buff_cb);
+static int stm32_dfsdm_audio_chan_init(struct iio_dev *indio_dev) +{
- struct iio_chan_spec *ch;
- struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev);
- int ret;
- ch = devm_kzalloc(&indio_dev->dev, sizeof(*ch), GFP_KERNEL);
- if (!ch)
return -ENOMEM;
- ret = stm32_dfsdm_channel_parse_of(pdmc->dfsdm, indio_dev, ch, 0);
- if (ret < 0)
return ret;
- ch->type = IIO_VOLTAGE;
- ch->indexed = 1;
- ch->scan_index = 0;
- ch->ext_info = dfsdm_adc_ext_info;
- ch->scan_type.sign = 's';
- ch->scan_type.realbits = 24;
- ch->scan_type.storagebits = 32;
- pdmc->ch_id = ch->channel;
- ret = stm32_dfsdm_chan_configure(pdmc->dfsdm,
&pdmc->dfsdm->ch_list[ch->channel]);
- indio_dev->num_channels = 1;
- indio_dev->channels = ch;
- return ret;
+}
+static const struct of_device_id stm32_dfsdm_audio_match[] = {
- { .compatible = "st,stm32-dfsdm-audio"},
- {}
+};
+static int stm32_dfsdm_audio_dma_request(struct iio_dev *indio_dev) +{
- struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev);
- struct dma_slave_config config;
- int ret;
- pdmc->dma_chan = dma_request_slave_channel(&indio_dev->dev, "rx");
- if (!pdmc->dma_chan)
return -EINVAL;
- pdmc->rx_buf = dma_alloc_coherent(pdmc->dma_chan->device->dev,
DFSDM_DMA_BUFFER_SIZE,
&pdmc->dma_buf, GFP_KERNEL);
- if (!pdmc->rx_buf) {
ret = -ENOMEM;
goto err_release;
- }
- /* Configure DMA channel to read data register */
- memset(&config, 0, sizeof(config));
- config.src_addr = (dma_addr_t)pdmc->dfsdm->phys_base;
- config.src_addr += DFSDM_RDATAR(pdmc->fl_id);
- config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
- ret = dmaengine_slave_config(pdmc->dma_chan, &config);
- if (ret)
goto err_free;
- return 0;
+err_free:
- dma_free_coherent(pdmc->dma_chan->device->dev, DFSDM_DMA_BUFFER_SIZE,
pdmc->rx_buf, pdmc->dma_buf);
+err_release:
- dma_release_channel(pdmc->dma_chan);
- return ret;
+}
+static int stm32_dfsdm_audio_probe(struct platform_device *pdev) +{
- struct device *dev = &pdev->dev;
- struct stm32_dfsdm_audio *pdmc;
- struct device_node *np = dev->of_node;
- struct iio_dev *iio;
- char *name;
- int ret, irq, val;
- iio = devm_iio_device_alloc(dev, sizeof(*pdmc));
- if (IS_ERR(iio)) {
dev_err(dev, "%s: Failed to allocate IIO\n", __func__);
return PTR_ERR(iio);
- }
- pdmc = iio_priv(iio);
- if (IS_ERR(pdmc)) {
dev_err(dev, "%s: Failed to allocate ADC\n", __func__);
return PTR_ERR(pdmc);
- }
- pdmc->dfsdm = dev_get_drvdata(dev->parent);
- iio->name = np->name;
- iio->dev.parent = dev;
- iio->dev.of_node = np;
- iio->info = &stm32_dfsdm_info_pdmc;
- iio->modes = INDIO_DIRECT_MODE;
- platform_set_drvdata(pdev, pdmc);
- ret = of_property_read_u32(dev->of_node, "reg", &pdmc->fl_id);
- if (ret != 0) {
dev_err(dev, "Missing reg property\n");
return -EINVAL;
- }
- name = kzalloc(sizeof("dfsdm-pdm0"), GFP_KERNEL);
- if (!name)
return -ENOMEM;
- snprintf(name, sizeof("dfsdm-pdm0"), "dfsdm-pdm%d", pdmc->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, pdmc);
- 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;
- }
- pdmc->dfsdm->fl_list[pdmc->fl_id].ford = val;
- ret = of_property_read_u32(dev->of_node, "st,filter0-sync", &val);
- if (!ret)
pdmc->dfsdm->fl_list[pdmc->fl_id].sync_mode = val;
- ret = stm32_dfsdm_audio_chan_init(iio);
- if (ret < 0)
return ret;
- ret = stm32_dfsdm_audio_dma_request(iio);
- if (ret) {
dev_err(&pdev->dev, "DMA request failed\n");
return ret;
- }
- iio->modes |= INDIO_BUFFER_SOFTWARE;
- ret = iio_triggered_buffer_setup(iio,
&iio_pollfunc_store_time,
&stm32_dfsdm_audio_trigger_handler,
&stm32_dfsdm_buffer_setup_ops);
- if (ret) {
dev_err(&pdev->dev, "Buffer setup failed\n");
goto err_dma_disable;
- }
- ret = iio_device_register(iio);
- if (ret) {
dev_err(&pdev->dev, "IIO dev register failed\n");
goto err_buffer_cleanup;
- }
- return 0;
+err_buffer_cleanup:
- iio_triggered_buffer_cleanup(iio);
+err_dma_disable:
- if (pdmc->dma_chan) {
dma_free_coherent(pdmc->dma_chan->device->dev,
DFSDM_DMA_BUFFER_SIZE,
pdmc->rx_buf, pdmc->dma_buf);
dma_release_channel(pdmc->dma_chan);
- }
- return ret;
+}
+static int stm32_dfsdm_audio_remove(struct platform_device *pdev) +{
- struct stm32_dfsdm_audio *pdmc = platform_get_drvdata(pdev);
- struct iio_dev *iio = iio_priv_to_dev(pdmc);
- iio_device_unregister(iio);
Same as in the other driver. Can just use the devm_iio_device_register version and drop remove.
- return 0;
+}
+static struct platform_driver stm32_dfsdm_audio_driver = {
- .driver = {
.name = "stm32-dfsdm-audio",
.of_match_table = stm32_dfsdm_audio_match,
- },
- .probe = stm32_dfsdm_audio_probe,
- .remove = stm32_dfsdm_audio_remove,
+}; +module_platform_driver(stm32_dfsdm_audio_driver);
+MODULE_DESCRIPTION("STM32 sigma delta converter for PDM microphone"); +MODULE_AUTHOR("Arnaud Pouliquen arnaud.pouliquen@st.com"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/iio/adc/stm32-dfsdm-audio.h b/include/linux/iio/adc/stm32-dfsdm-audio.h new file mode 100644 index 0000000..9b41b28 --- /dev/null +++ b/include/linux/iio/adc/stm32-dfsdm-audio.h @@ -0,0 +1,41 @@ +/*
- This file discribe the STM32 DFSDM IIO driver API for audio part
- Copyright (C) 2017, STMicroelectronics - All Rights Reserved
- Author(s): Arnaud Pouliquen arnaud.pouliquen@st.com.
- License terms: GPL V2.0.
- This program is free software; you can redistribute it and/or modify it
- under the terms of the GNU General Public License version 2 as published by
- the Free Software Foundation.
- This program is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
- details.
- */
+#ifndef STM32_DFSDM_AUDIO_H +#define STM32_DFSDM_AUDIO_H
+/**
- stm32_dfsdm_get_buff_cb() - register callback for capture buffer period.
- @dev: Pointer to client device.
- @indio_dev: Device on which the channel exists.
- @cb: Callback function.
@data: pointer to data buffer
@size: size of the data buffer in bytes
- @private: Private data passed to callback.
- */
+int stm32_dfsdm_get_buff_cb(struct iio_dev *iio_dev,
int (*cb)(const void *data, size_t size,
void *private),
void *private);
+/**
- stm32_dfsdm_get_buff_cb() - release callback for capture buffer period.
- @indio_dev: Device on which the channel exists.
- */
+int stm32_dfsdm_release_buff_cb(struct iio_dev *iio_dev);
+#endif
On 17/03/17 14:08, Arnaud Pouliquen wrote:
Add iio consumer API to set buffer size and watermark according to sysfs API.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com
Hmm. Not keen on the length one. Setting a requested watermark is fair enough. There is no actually buffer in these cases though so setting it's length is downright odd..
Guess this is part of the hacks we need to clean up by doing the dma buffer consumer stuff right...
Jonathan
drivers/iio/buffer/industrialio-buffer-cb.c | 12 ++++++++++++ include/linux/iio/consumer.h | 13 +++++++++++++ 2 files changed, 25 insertions(+)
diff --git a/drivers/iio/buffer/industrialio-buffer-cb.c b/drivers/iio/buffer/industrialio-buffer-cb.c index b8f550e..43c066a 100644 --- a/drivers/iio/buffer/industrialio-buffer-cb.c +++ b/drivers/iio/buffer/industrialio-buffer-cb.c @@ -103,6 +103,18 @@ struct iio_cb_buffer *iio_channel_get_all_cb(struct device *dev, } EXPORT_SYMBOL_GPL(iio_channel_get_all_cb);
+int iio_channel_cb_set_buffer_params(struct iio_cb_buffer *cb_buff,
size_t length, size_t watermark)
+{
- if (!length || length < watermark)
return -EINVAL;
- cb_buff->buffer.watermark = watermark;
- cb_buff->buffer.length = length;
- return 0;
+} +EXPORT_SYMBOL_GPL(iio_channel_start_all_cb);
int iio_channel_start_all_cb(struct iio_cb_buffer *cb_buff) { return iio_update_buffers(cb_buff->indio_dev, &cb_buff->buffer, diff --git a/include/linux/iio/consumer.h b/include/linux/iio/consumer.h index cb44771..0f6e94d 100644 --- a/include/linux/iio/consumer.h +++ b/include/linux/iio/consumer.h @@ -134,6 +134,19 @@ struct iio_cb_buffer *iio_channel_get_all_cb(struct device *dev, void *private), void *private); /**
- iio_channel_cb_set_buffer_size() - set the buffer length.
- @cb_buffer: The callback buffer from whom we want the channel
information.
- @length: buffer length in bytes
- @watermark: buffer watermark in bytes
- This function allows to configure the buffer length. The watermark if
- forced to half of the buffer.
- */
+int iio_channel_cb_set_buffer_params(struct iio_cb_buffer *cb_buffer,
size_t length, size_t watermark);
+/**
- iio_channel_release_all_cb() - release and unregister the callback.
- @cb_buffer: The callback buffer that was allocated.
*/
Hi Arnaud,
[auto build test ERROR on asoc/for-next] [also build test ERROR on v4.11-rc3] [cannot apply to iio/togreg next-20170310] [if your patch is applied to the wrong git tree, please drop us a note to help improve the system]
url: https://github.com/0day-ci/linux/commits/Arnaud-Pouliquen/Add-STM32-DFSDM-su... base: https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git for-next config: i386-randconfig-x073-201712 (attached as .config) compiler: gcc-6 (Debian 6.2.0-3) 6.2.0 20160901 reproduce: # save the attached .config to linux build tree make ARCH=i386
All error/warnings (new ones prefixed by >>):
In file included from include/linux/linkage.h:6:0, from include/linux/kernel.h:6, from drivers/iio/buffer/industrialio-buffer-cb.c:8:
include/linux/export.h:67:20: error: redefinition of '__kstrtab_iio_channel_start_all_cb'
static const char __kstrtab_##sym[] \ ^ include/linux/export.h:100:25: note: in expansion of macro '___EXPORT_SYMBOL' #define __EXPORT_SYMBOL ___EXPORT_SYMBOL ^~~~~~~~~~~~~~~~ include/linux/export.h:107:2: note: in expansion of macro '__EXPORT_SYMBOL' __EXPORT_SYMBOL(sym, "_gpl") ^~~~~~~~~~~~~~~
drivers/iio/buffer/industrialio-buffer-cb.c:124:1: note: in expansion of macro 'EXPORT_SYMBOL_GPL'
EXPORT_SYMBOL_GPL(iio_channel_start_all_cb); ^~~~~~~~~~~~~~~~~ include/linux/export.h:67:20: note: previous definition of '__kstrtab_iio_channel_start_all_cb' was here static const char __kstrtab_##sym[] \ ^ include/linux/export.h:100:25: note: in expansion of macro '___EXPORT_SYMBOL' #define __EXPORT_SYMBOL ___EXPORT_SYMBOL ^~~~~~~~~~~~~~~~ include/linux/export.h:107:2: note: in expansion of macro '__EXPORT_SYMBOL' __EXPORT_SYMBOL(sym, "_gpl") ^~~~~~~~~~~~~~~ drivers/iio/buffer/industrialio-buffer-cb.c:117:1: note: in expansion of macro 'EXPORT_SYMBOL_GPL' EXPORT_SYMBOL_GPL(iio_channel_start_all_cb); ^~~~~~~~~~~~~~~~~ include/linux/export.h:70:36: error: redefinition of '__ksymtab_iio_channel_start_all_cb' static const struct kernel_symbol __ksymtab_##sym \ ^ include/linux/export.h:100:25: note: in expansion of macro '___EXPORT_SYMBOL' #define __EXPORT_SYMBOL ___EXPORT_SYMBOL ^~~~~~~~~~~~~~~~ include/linux/export.h:107:2: note: in expansion of macro '__EXPORT_SYMBOL' __EXPORT_SYMBOL(sym, "_gpl") ^~~~~~~~~~~~~~~
drivers/iio/buffer/industrialio-buffer-cb.c:124:1: note: in expansion of macro 'EXPORT_SYMBOL_GPL'
EXPORT_SYMBOL_GPL(iio_channel_start_all_cb); ^~~~~~~~~~~~~~~~~ include/linux/export.h:70:36: note: previous definition of '__ksymtab_iio_channel_start_all_cb' was here static const struct kernel_symbol __ksymtab_##sym \ ^ include/linux/export.h:100:25: note: in expansion of macro '___EXPORT_SYMBOL' #define __EXPORT_SYMBOL ___EXPORT_SYMBOL ^~~~~~~~~~~~~~~~ include/linux/export.h:107:2: note: in expansion of macro '__EXPORT_SYMBOL' __EXPORT_SYMBOL(sym, "_gpl") ^~~~~~~~~~~~~~~ drivers/iio/buffer/industrialio-buffer-cb.c:117:1: note: in expansion of macro 'EXPORT_SYMBOL_GPL' EXPORT_SYMBOL_GPL(iio_channel_start_all_cb); ^~~~~~~~~~~~~~~~~
vim +/__kstrtab_iio_channel_start_all_cb +67 include/linux/export.h
f5016932 Paul Gortmaker 2011-05-23 61 #endif f5016932 Paul Gortmaker 2011-05-23 62 f5016932 Paul Gortmaker 2011-05-23 63 /* For every exported symbol, place a struct in the __ksymtab section */ f2355416 Nicolas Pitre 2016-01-22 64 #define ___EXPORT_SYMBOL(sym, sec) \ f5016932 Paul Gortmaker 2011-05-23 65 extern typeof(sym) sym; \ f5016932 Paul Gortmaker 2011-05-23 66 __CRC_SYMBOL(sym, sec) \ f5016932 Paul Gortmaker 2011-05-23 @67 static const char __kstrtab_##sym[] \ f5016932 Paul Gortmaker 2011-05-23 68 __attribute__((section("__ksymtab_strings"), aligned(1))) \ b92021b0 Rusty Russell 2013-03-15 69 = VMLINUX_SYMBOL_STR(sym); \ b67067f1 Nicholas Piggin 2016-08-24 70 static const struct kernel_symbol __ksymtab_##sym \
:::::: The code at line 67 was first introduced by commit :::::: f50169324df4ad942e544386d136216c8617636a module.h: split out the EXPORT_SYMBOL into export.h
:::::: TO: Paul Gortmaker paul.gortmaker@windriver.com :::::: CC: Paul Gortmaker paul.gortmaker@windriver.com
--- 0-DAY kernel test infrastructure Open Source Technology Center https://lists.01.org/pipermail/kbuild-all Intel Corporation
Hi Arnaud,
[auto build test ERROR on asoc/for-next] [also build test ERROR on v4.11-rc3] [cannot apply to iio/togreg next-20170310] [if your patch is applied to the wrong git tree, please drop us a note to help improve the system]
url: https://github.com/0day-ci/linux/commits/Arnaud-Pouliquen/Add-STM32-DFSDM-su... base: https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git for-next config: tile-allmodconfig (attached as .config) compiler: tilegx-linux-gcc (GCC) 4.6.2 reproduce: wget https://raw.githubusercontent.com/01org/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross chmod +x ~/bin/make.cross # save the attached .config to linux build tree make.cross ARCH=tile
All errors (new ones prefixed by >>):
drivers/iio/adc/sd_adc_modulator.c:84:1: error: 'adc081c_of_match' undeclared here (not in a function)
drivers/iio/adc/sd_adc_modulator.c:84:1: error: '__mod_of__adc081c_of_match_device_table' aliased to undefined symbol 'adc081c_of_match'
vim +84 drivers/iio/adc/sd_adc_modulator.c
78 79 static const struct of_device_id sd_adc_of_match[] = { 80 { .compatible = "sd-modulator" }, 81 { .compatible = "ads1201" }, 82 { } 83 };
84 MODULE_DEVICE_TABLE(of, adc081c_of_match);
85 86 static struct platform_driver iio_sd_mod_adc = { 87 .driver = {
--- 0-DAY kernel test infrastructure Open Source Technology Center https://lists.01.org/pipermail/kbuild-all Intel Corporation
Hi Arnaud,
[auto build test WARNING on asoc/for-next] [also build test WARNING on v4.11-rc3] [cannot apply to iio/togreg next-20170310] [if your patch is applied to the wrong git tree, please drop us a note to help improve the system]
url: https://github.com/0day-ci/linux/commits/Arnaud-Pouliquen/Add-STM32-DFSDM-su... base: https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git for-next config: x86_64-allmodconfig (attached as .config) compiler: gcc-6 (Debian 6.2.0-3) 6.2.0 20160901 reproduce: # save the attached .config to linux build tree make ARCH=x86_64
All warnings (new ones prefixed by >>):
In file included from drivers/iio/adc/sd_adc_modulator.c:23:0: drivers/iio/adc/sd_adc_modulator.c:84:25: error: 'adc081c_of_match' undeclared here (not in a function) MODULE_DEVICE_TABLE(of, adc081c_of_match); ^ include/linux/module.h:212:21: note: in definition of macro 'MODULE_DEVICE_TABLE' extern const typeof(name) __mod_##type##__##name##_device_table \ ^~~~ include/linux/module.h:212:27: error: '__mod_of__adc081c_of_match_device_table' aliased to undefined symbol 'adc081c_of_match' extern const typeof(name) __mod_##type##__##name##_device_table \ ^
drivers/iio/adc/sd_adc_modulator.c:84:1: note: in expansion of macro 'MODULE_DEVICE_TABLE'
MODULE_DEVICE_TABLE(of, adc081c_of_match); ^~~~~~~~~~~~~~~~~~~
vim +/MODULE_DEVICE_TABLE +84 drivers/iio/adc/sd_adc_modulator.c
68 iio->info = &iio_sd_mod_iio_info; 69 iio->modes = INDIO_BUFFER_HARDWARE; 70 71 iio->num_channels = 1; 72 iio->channels = &stm32_dfsdm_ch; 73 74 platform_set_drvdata(pdev, iio); 75 76 return devm_iio_device_register(&pdev->dev, iio); 77 } 78 79 static const struct of_device_id sd_adc_of_match[] = { 80 { .compatible = "sd-modulator" }, 81 { .compatible = "ads1201" }, 82 { } 83 };
84 MODULE_DEVICE_TABLE(of, adc081c_of_match);
85 86 static struct platform_driver iio_sd_mod_adc = { 87 .driver = { 88 .name = "iio_sd_adc_mod", 89 .of_match_table = of_match_ptr(sd_adc_of_match), 90 }, 91 .probe = iio_sd_mod_probe, 92 };
--- 0-DAY kernel test infrastructure Open Source Technology Center https://lists.01.org/pipermail/kbuild-all Intel Corporation
Hi Arnaud,
[auto build test WARNING on asoc/for-next] [also build test WARNING on v4.11-rc3] [cannot apply to iio/togreg next-20170310] [if your patch is applied to the wrong git tree, please drop us a note to help improve the system]
url: https://github.com/0day-ci/linux/commits/Arnaud-Pouliquen/Add-STM32-DFSDM-su... base: https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git for-next config: x86_64-allmodconfig (attached as .config) compiler: gcc-6 (Debian 6.2.0-3) 6.2.0 20160901 reproduce: # save the attached .config to linux build tree make ARCH=x86_64
All warnings (new ones prefixed by >>):
drivers/iio/hw_consumer.c:23:20: error: field 'buffer' has incomplete type struct iio_buffer buffer; ^~~~~~ In file included from include/asm-generic/bug.h:13:0, from arch/x86/include/asm/bug.h:35, from include/linux/bug.h:4, from include/linux/mmdebug.h:4, from include/linux/gfp.h:4, from include/linux/slab.h:14, from drivers/iio/hw_consumer.c:3: drivers/iio/hw_consumer.c: In function 'iio_buffer_to_hw_consumer_buffer': include/linux/kernel.h:852:48: error: initialization from incompatible pointer type [-Werror=incompatible-pointer-types] const typeof( ((type *)0)->member ) *__mptr = (ptr); \ ^
drivers/iio/hw_consumer.c:29:9: note: in expansion of macro 'container_of'
return container_of(buffer, struct hw_consumer_buffer, buffer); ^~~~~~~~~~~~ drivers/iio/hw_consumer.c: At top level: drivers/iio/hw_consumer.c:40:21: error: variable 'iio_hw_buf_access' has initializer but incomplete type static const struct iio_buffer_access_funcs iio_hw_buf_access = { ^~~~~~~~~~~~~~~~~~~~~~~ drivers/iio/hw_consumer.c:41:2: error: unknown field 'release' specified in initializer .release = &iio_hw_buf_release, ^
drivers/iio/hw_consumer.c:41:13: warning: excess elements in struct initializer
.release = &iio_hw_buf_release, ^ drivers/iio/hw_consumer.c:41:13: note: (near initialization for 'iio_hw_buf_access') drivers/iio/hw_consumer.c:42:2: error: unknown field 'modes' specified in initializer .modes = INDIO_BUFFER_HARDWARE, ^ In file included from drivers/iio/hw_consumer.c:7:0:
include/linux/iio/iio.h:353:32: warning: excess elements in struct initializer
#define INDIO_BUFFER_HARDWARE 0x08 ^
drivers/iio/hw_consumer.c:42:11: note: in expansion of macro 'INDIO_BUFFER_HARDWARE'
.modes = INDIO_BUFFER_HARDWARE, ^~~~~~~~~~~~~~~~~~~~~ include/linux/iio/iio.h:353:32: note: (near initialization for 'iio_hw_buf_access') #define INDIO_BUFFER_HARDWARE 0x08 ^
drivers/iio/hw_consumer.c:42:11: note: in expansion of macro 'INDIO_BUFFER_HARDWARE'
.modes = INDIO_BUFFER_HARDWARE, ^~~~~~~~~~~~~~~~~~~~~ drivers/iio/hw_consumer.c: In function 'iio_hw_consumer_get_buffer': drivers/iio/hw_consumer.c:66:2: error: implicit declaration of function 'iio_buffer_init' [-Werror=implicit-function-declaration] iio_buffer_init(&buf->buffer); ^~~~~~~~~~~~~~~ drivers/iio/hw_consumer.c: In function 'iio_hw_consumer_alloc': drivers/iio/hw_consumer.c:110:3: error: implicit declaration of function 'iio_buffer_put' [-Werror=implicit-function-declaration] iio_buffer_put(&buf->buffer); ^~~~~~~~~~~~~~ drivers/iio/hw_consumer.c: In function 'iio_hw_consumer_enable': drivers/iio/hw_consumer.c:135:9: error: implicit declaration of function 'iio_update_buffers' [-Werror=implicit-function-declaration] ret = iio_update_buffers(buf->indio_dev, &buf->buffer, NULL); ^~~~~~~~~~~~~~~~~~ drivers/iio/hw_consumer.c: At top level: drivers/iio/hw_consumer.c:40:45: error: storage size of 'iio_hw_buf_access' isn't known static const struct iio_buffer_access_funcs iio_hw_buf_access = { ^~~~~~~~~~~~~~~~~ cc1: some warnings being treated as errors -- In file included from drivers/iio/buffer/industrialio-buffer-dma.c:17:0: include/linux/iio/buffer-dma.h:107:20: error: field 'buffer' has incomplete type struct iio_buffer buffer; ^~~~~~ drivers/iio/buffer/industrialio-buffer-dma.c: In function 'iio_buffer_block_release': drivers/iio/buffer/industrialio-buffer-dma.c:104:2: error: implicit declaration of function 'iio_buffer_put' [-Werror=implicit-function-declaration] iio_buffer_put(&block->queue->buffer); ^~~~~~~~~~~~~~ In file included from include/asm-generic/bug.h:13:0, from arch/x86/include/asm/bug.h:35, from include/linux/bug.h:4, from include/linux/mmdebug.h:4, from include/linux/gfp.h:4, from include/linux/slab.h:14, from drivers/iio/buffer/industrialio-buffer-dma.c:8: drivers/iio/buffer/industrialio-buffer-dma.c: In function 'iio_buffer_to_queue': include/linux/kernel.h:852:48: error: initialization from incompatible pointer type [-Werror=incompatible-pointer-types] const typeof( ((type *)0)->member ) *__mptr = (ptr); \ ^
drivers/iio/buffer/industrialio-buffer-dma.c:163:9: note: in expansion of macro 'container_of'
return container_of(buf, struct iio_dma_buffer_queue, buffer); ^~~~~~~~~~~~ drivers/iio/buffer/industrialio-buffer-dma.c: In function 'iio_dma_buffer_alloc_block': drivers/iio/buffer/industrialio-buffer-dma.c:188:2: error: implicit declaration of function 'iio_buffer_get' [-Werror=implicit-function-declaration] iio_buffer_get(&queue->buffer); ^~~~~~~~~~~~~~ drivers/iio/buffer/industrialio-buffer-dma.c: In function 'iio_dma_buffer_read': drivers/iio/buffer/industrialio-buffer-dma.c:489:16: error: dereferencing pointer to incomplete type 'struct iio_buffer' if (n < buffer->bytes_per_datum) ^~ drivers/iio/buffer/industrialio-buffer-dma.c: In function 'iio_dma_buffer_init': drivers/iio/buffer/industrialio-buffer-dma.c:614:2: error: implicit declaration of function 'iio_buffer_init' [-Werror=implicit-function-declaration] iio_buffer_init(&queue->buffer); ^~~~~~~~~~~~~~~ drivers/iio/buffer/industrialio-buffer-dma.c: In function 'iio_buffer_to_queue':
drivers/iio/buffer/industrialio-buffer-dma.c:164:1: warning: control reaches end of non-void function [-Wreturn-type]
} ^ cc1: some warnings being treated as errors -- In file included from drivers/iio/buffer/industrialio-buffer-dmaengine.c:17:0: include/linux/iio/buffer-dma.h:107:20: error: field 'buffer' has incomplete type struct iio_buffer buffer; ^~~~~~ In file included from include/asm-generic/bug.h:13:0, from arch/x86/include/asm/bug.h:35, from include/linux/bug.h:4, from include/linux/mmdebug.h:4, from include/linux/gfp.h:4, from include/linux/slab.h:14, from drivers/iio/buffer/industrialio-buffer-dmaengine.c:8: drivers/iio/buffer/industrialio-buffer-dmaengine.c: In function 'iio_buffer_to_dmaengine_buffer': include/linux/kernel.h:852:48: error: initialization from incompatible pointer type [-Werror=incompatible-pointer-types] const typeof( ((type *)0)->member ) *__mptr = (ptr); \ ^
drivers/iio/buffer/industrialio-buffer-dmaengine.c:43:9: note: in expansion of macro 'container_of'
return container_of(buffer, struct dmaengine_buffer, queue.buffer); ^~~~~~~~~~~~ drivers/iio/buffer/industrialio-buffer-dmaengine.c: At top level: drivers/iio/buffer/industrialio-buffer-dmaengine.c:109:21: error: variable 'iio_dmaengine_buffer_ops' has initializer but incomplete type static const struct iio_buffer_access_funcs iio_dmaengine_buffer_ops = { ^~~~~~~~~~~~~~~~~~~~~~~ drivers/iio/buffer/industrialio-buffer-dmaengine.c:110:2: error: unknown field 'read_first_n' specified in initializer .read_first_n = iio_dma_buffer_read, ^
drivers/iio/buffer/industrialio-buffer-dmaengine.c:110:18: warning: excess elements in struct initializer
.read_first_n = iio_dma_buffer_read, ^~~~~~~~~~~~~~~~~~~ drivers/iio/buffer/industrialio-buffer-dmaengine.c:110:18: note: (near initialization for 'iio_dmaengine_buffer_ops') drivers/iio/buffer/industrialio-buffer-dmaengine.c:111:2: error: unknown field 'set_bytes_per_datum' specified in initializer .set_bytes_per_datum = iio_dma_buffer_set_bytes_per_datum, ^ drivers/iio/buffer/industrialio-buffer-dmaengine.c:111:25: warning: excess elements in struct initializer .set_bytes_per_datum = iio_dma_buffer_set_bytes_per_datum, ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ drivers/iio/buffer/industrialio-buffer-dmaengine.c:111:25: note: (near initialization for 'iio_dmaengine_buffer_ops') drivers/iio/buffer/industrialio-buffer-dmaengine.c:112:2: error: unknown field 'set_length' specified in initializer .set_length = iio_dma_buffer_set_length, ^ drivers/iio/buffer/industrialio-buffer-dmaengine.c:112:16: warning: excess elements in struct initializer .set_length = iio_dma_buffer_set_length, ^~~~~~~~~~~~~~~~~~~~~~~~~ drivers/iio/buffer/industrialio-buffer-dmaengine.c:112:16: note: (near initialization for 'iio_dmaengine_buffer_ops') drivers/iio/buffer/industrialio-buffer-dmaengine.c:113:2: error: unknown field 'request_update' specified in initializer .request_update = iio_dma_buffer_request_update, ^ drivers/iio/buffer/industrialio-buffer-dmaengine.c:113:20: warning: excess elements in struct initializer .request_update = iio_dma_buffer_request_update, ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~ drivers/iio/buffer/industrialio-buffer-dmaengine.c:113:20: note: (near initialization for 'iio_dmaengine_buffer_ops') drivers/iio/buffer/industrialio-buffer-dmaengine.c:114:2: error: unknown field 'enable' specified in initializer .enable = iio_dma_buffer_enable, ^ drivers/iio/buffer/industrialio-buffer-dmaengine.c:114:12: warning: excess elements in struct initializer .enable = iio_dma_buffer_enable, ^~~~~~~~~~~~~~~~~~~~~ drivers/iio/buffer/industrialio-buffer-dmaengine.c:114:12: note: (near initialization for 'iio_dmaengine_buffer_ops') drivers/iio/buffer/industrialio-buffer-dmaengine.c:115:2: error: unknown field 'disable' specified in initializer .disable = iio_dma_buffer_disable, ^ drivers/iio/buffer/industrialio-buffer-dmaengine.c:115:13: warning: excess elements in struct initializer .disable = iio_dma_buffer_disable, ^~~~~~~~~~~~~~~~~~~~~~ drivers/iio/buffer/industrialio-buffer-dmaengine.c:115:13: note: (near initialization for 'iio_dmaengine_buffer_ops') drivers/iio/buffer/industrialio-buffer-dmaengine.c:116:2: error: unknown field 'data_available' specified in initializer .data_available = iio_dma_buffer_data_available, ^ drivers/iio/buffer/industrialio-buffer-dmaengine.c:116:20: warning: excess elements in struct initializer .data_available = iio_dma_buffer_data_available, ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~ drivers/iio/buffer/industrialio-buffer-dmaengine.c:116:20: note: (near initialization for 'iio_dmaengine_buffer_ops') drivers/iio/buffer/industrialio-buffer-dmaengine.c:117:2: error: unknown field 'release' specified in initializer .release = iio_dmaengine_buffer_release, ^ drivers/iio/buffer/industrialio-buffer-dmaengine.c:117:13: warning: excess elements in struct initializer .release = iio_dmaengine_buffer_release, ^~~~~~~~~~~~~~~~~~~~~~~~~~~~ drivers/iio/buffer/industrialio-buffer-dmaengine.c:117:13: note: (near initialization for 'iio_dmaengine_buffer_ops') drivers/iio/buffer/industrialio-buffer-dmaengine.c:119:2: error: unknown field 'modes' specified in initializer .modes = INDIO_BUFFER_HARDWARE, ^ In file included from drivers/iio/buffer/industrialio-buffer-dmaengine.c:15:0:
include/linux/iio/iio.h:353:32: warning: excess elements in struct initializer
#define INDIO_BUFFER_HARDWARE 0x08 ^
drivers/iio/buffer/industrialio-buffer-dmaengine.c:119:11: note: in expansion of macro 'INDIO_BUFFER_HARDWARE'
.modes = INDIO_BUFFER_HARDWARE, ^~~~~~~~~~~~~~~~~~~~~ include/linux/iio/iio.h:353:32: note: (near initialization for 'iio_dmaengine_buffer_ops') #define INDIO_BUFFER_HARDWARE 0x08 ^
drivers/iio/buffer/industrialio-buffer-dmaengine.c:119:11: note: in expansion of macro 'INDIO_BUFFER_HARDWARE'
.modes = INDIO_BUFFER_HARDWARE, ^~~~~~~~~~~~~~~~~~~~~ drivers/iio/buffer/industrialio-buffer-dmaengine.c:120:2: error: unknown field 'flags' specified in initializer .flags = INDIO_BUFFER_FLAG_FIXED_WATERMARK, ^ drivers/iio/buffer/industrialio-buffer-dmaengine.c:120:11: error: 'INDIO_BUFFER_FLAG_FIXED_WATERMARK' undeclared here (not in a function) .flags = INDIO_BUFFER_FLAG_FIXED_WATERMARK, ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ drivers/iio/buffer/industrialio-buffer-dmaengine.c:120:11: warning: excess elements in struct initializer drivers/iio/buffer/industrialio-buffer-dmaengine.c:120:11: note: (near initialization for 'iio_dmaengine_buffer_ops') drivers/iio/buffer/industrialio-buffer-dmaengine.c: In function 'iio_dmaengine_buffer_free': drivers/iio/buffer/industrialio-buffer-dmaengine.c:206:2: error: implicit declaration of function 'iio_buffer_put' [-Werror=implicit-function-declaration] iio_buffer_put(buffer); ^~~~~~~~~~~~~~ drivers/iio/buffer/industrialio-buffer-dmaengine.c: At top level: drivers/iio/buffer/industrialio-buffer-dmaengine.c:109:45: error: storage size of 'iio_dmaengine_buffer_ops' isn't known static const struct iio_buffer_access_funcs iio_dmaengine_buffer_ops = { ^~~~~~~~~~~~~~~~~~~~~~~~ drivers/iio/buffer/industrialio-buffer-dmaengine.c: In function 'iio_buffer_to_dmaengine_buffer':
drivers/iio/buffer/industrialio-buffer-dmaengine.c:44:1: warning: control reaches end of non-void function [-Wreturn-type]
} ^ cc1: some warnings being treated as errors -- In file included from drivers/iio//buffer/industrialio-buffer-dmaengine.c:17:0: include/linux/iio/buffer-dma.h:107:20: error: field 'buffer' has incomplete type struct iio_buffer buffer; ^~~~~~ In file included from include/asm-generic/bug.h:13:0, from arch/x86/include/asm/bug.h:35, from include/linux/bug.h:4, from include/linux/mmdebug.h:4, from include/linux/gfp.h:4, from include/linux/slab.h:14, from drivers/iio//buffer/industrialio-buffer-dmaengine.c:8: drivers/iio//buffer/industrialio-buffer-dmaengine.c: In function 'iio_buffer_to_dmaengine_buffer': include/linux/kernel.h:852:48: error: initialization from incompatible pointer type [-Werror=incompatible-pointer-types] const typeof( ((type *)0)->member ) *__mptr = (ptr); \ ^ drivers/iio//buffer/industrialio-buffer-dmaengine.c:43:9: note: in expansion of macro 'container_of' return container_of(buffer, struct dmaengine_buffer, queue.buffer); ^~~~~~~~~~~~ drivers/iio//buffer/industrialio-buffer-dmaengine.c: At top level: drivers/iio//buffer/industrialio-buffer-dmaengine.c:109:21: error: variable 'iio_dmaengine_buffer_ops' has initializer but incomplete type static const struct iio_buffer_access_funcs iio_dmaengine_buffer_ops = { ^~~~~~~~~~~~~~~~~~~~~~~ drivers/iio//buffer/industrialio-buffer-dmaengine.c:110:2: error: unknown field 'read_first_n' specified in initializer .read_first_n = iio_dma_buffer_read, ^ drivers/iio//buffer/industrialio-buffer-dmaengine.c:110:18: warning: excess elements in struct initializer .read_first_n = iio_dma_buffer_read, ^~~~~~~~~~~~~~~~~~~ drivers/iio//buffer/industrialio-buffer-dmaengine.c:110:18: note: (near initialization for 'iio_dmaengine_buffer_ops') drivers/iio//buffer/industrialio-buffer-dmaengine.c:111:2: error: unknown field 'set_bytes_per_datum' specified in initializer .set_bytes_per_datum = iio_dma_buffer_set_bytes_per_datum, ^ drivers/iio//buffer/industrialio-buffer-dmaengine.c:111:25: warning: excess elements in struct initializer .set_bytes_per_datum = iio_dma_buffer_set_bytes_per_datum, ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ drivers/iio//buffer/industrialio-buffer-dmaengine.c:111:25: note: (near initialization for 'iio_dmaengine_buffer_ops') drivers/iio//buffer/industrialio-buffer-dmaengine.c:112:2: error: unknown field 'set_length' specified in initializer .set_length = iio_dma_buffer_set_length, ^ drivers/iio//buffer/industrialio-buffer-dmaengine.c:112:16: warning: excess elements in struct initializer .set_length = iio_dma_buffer_set_length, ^~~~~~~~~~~~~~~~~~~~~~~~~ drivers/iio//buffer/industrialio-buffer-dmaengine.c:112:16: note: (near initialization for 'iio_dmaengine_buffer_ops') drivers/iio//buffer/industrialio-buffer-dmaengine.c:113:2: error: unknown field 'request_update' specified in initializer .request_update = iio_dma_buffer_request_update, ^ drivers/iio//buffer/industrialio-buffer-dmaengine.c:113:20: warning: excess elements in struct initializer .request_update = iio_dma_buffer_request_update, ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~ drivers/iio//buffer/industrialio-buffer-dmaengine.c:113:20: note: (near initialization for 'iio_dmaengine_buffer_ops') drivers/iio//buffer/industrialio-buffer-dmaengine.c:114:2: error: unknown field 'enable' specified in initializer .enable = iio_dma_buffer_enable, ^ drivers/iio//buffer/industrialio-buffer-dmaengine.c:114:12: warning: excess elements in struct initializer .enable = iio_dma_buffer_enable, ^~~~~~~~~~~~~~~~~~~~~ drivers/iio//buffer/industrialio-buffer-dmaengine.c:114:12: note: (near initialization for 'iio_dmaengine_buffer_ops') drivers/iio//buffer/industrialio-buffer-dmaengine.c:115:2: error: unknown field 'disable' specified in initializer .disable = iio_dma_buffer_disable, ^ drivers/iio//buffer/industrialio-buffer-dmaengine.c:115:13: warning: excess elements in struct initializer .disable = iio_dma_buffer_disable, ^~~~~~~~~~~~~~~~~~~~~~ drivers/iio//buffer/industrialio-buffer-dmaengine.c:115:13: note: (near initialization for 'iio_dmaengine_buffer_ops') drivers/iio//buffer/industrialio-buffer-dmaengine.c:116:2: error: unknown field 'data_available' specified in initializer .data_available = iio_dma_buffer_data_available, ^ drivers/iio//buffer/industrialio-buffer-dmaengine.c:116:20: warning: excess elements in struct initializer .data_available = iio_dma_buffer_data_available, ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~ drivers/iio//buffer/industrialio-buffer-dmaengine.c:116:20: note: (near initialization for 'iio_dmaengine_buffer_ops') drivers/iio//buffer/industrialio-buffer-dmaengine.c:117:2: error: unknown field 'release' specified in initializer .release = iio_dmaengine_buffer_release, ^ drivers/iio//buffer/industrialio-buffer-dmaengine.c:117:13: warning: excess elements in struct initializer .release = iio_dmaengine_buffer_release, ^~~~~~~~~~~~~~~~~~~~~~~~~~~~ drivers/iio//buffer/industrialio-buffer-dmaengine.c:117:13: note: (near initialization for 'iio_dmaengine_buffer_ops') drivers/iio//buffer/industrialio-buffer-dmaengine.c:119:2: error: unknown field 'modes' specified in initializer .modes = INDIO_BUFFER_HARDWARE, ^ In file included from drivers/iio//buffer/industrialio-buffer-dmaengine.c:15:0:
include/linux/iio/iio.h:353:32: warning: excess elements in struct initializer
#define INDIO_BUFFER_HARDWARE 0x08 ^ drivers/iio//buffer/industrialio-buffer-dmaengine.c:119:11: note: in expansion of macro 'INDIO_BUFFER_HARDWARE' .modes = INDIO_BUFFER_HARDWARE, ^~~~~~~~~~~~~~~~~~~~~ include/linux/iio/iio.h:353:32: note: (near initialization for 'iio_dmaengine_buffer_ops') #define INDIO_BUFFER_HARDWARE 0x08 ^ drivers/iio//buffer/industrialio-buffer-dmaengine.c:119:11: note: in expansion of macro 'INDIO_BUFFER_HARDWARE' .modes = INDIO_BUFFER_HARDWARE, ^~~~~~~~~~~~~~~~~~~~~ drivers/iio//buffer/industrialio-buffer-dmaengine.c:120:2: error: unknown field 'flags' specified in initializer .flags = INDIO_BUFFER_FLAG_FIXED_WATERMARK, ^ drivers/iio//buffer/industrialio-buffer-dmaengine.c:120:11: error: 'INDIO_BUFFER_FLAG_FIXED_WATERMARK' undeclared here (not in a function) .flags = INDIO_BUFFER_FLAG_FIXED_WATERMARK, ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ drivers/iio//buffer/industrialio-buffer-dmaengine.c:120:11: warning: excess elements in struct initializer drivers/iio//buffer/industrialio-buffer-dmaengine.c:120:11: note: (near initialization for 'iio_dmaengine_buffer_ops') drivers/iio//buffer/industrialio-buffer-dmaengine.c: In function 'iio_dmaengine_buffer_free': drivers/iio//buffer/industrialio-buffer-dmaengine.c:206:2: error: implicit declaration of function 'iio_buffer_put' [-Werror=implicit-function-declaration] iio_buffer_put(buffer); ^~~~~~~~~~~~~~ drivers/iio//buffer/industrialio-buffer-dmaengine.c: At top level: drivers/iio//buffer/industrialio-buffer-dmaengine.c:109:45: error: storage size of 'iio_dmaengine_buffer_ops' isn't known static const struct iio_buffer_access_funcs iio_dmaengine_buffer_ops = { ^~~~~~~~~~~~~~~~~~~~~~~~ drivers/iio//buffer/industrialio-buffer-dmaengine.c: In function 'iio_buffer_to_dmaengine_buffer': drivers/iio//buffer/industrialio-buffer-dmaengine.c:44:1: warning: control reaches end of non-void function [-Wreturn-type] } ^ cc1: some warnings being treated as errors
vim +/container_of +29 drivers/iio/hw_consumer.c
d5ca88eb Lars-Peter Clausen 2017-03-17 1 #include <linux/err.h> d5ca88eb Lars-Peter Clausen 2017-03-17 2 #include <linux/export.h> d5ca88eb Lars-Peter Clausen 2017-03-17 @3 #include <linux/slab.h> d5ca88eb Lars-Peter Clausen 2017-03-17 4 #include <linux/mutex.h> d5ca88eb Lars-Peter Clausen 2017-03-17 5 #include <linux/of.h> d5ca88eb Lars-Peter Clausen 2017-03-17 6 d5ca88eb Lars-Peter Clausen 2017-03-17 7 #include <linux/iio/iio.h> d5ca88eb Lars-Peter Clausen 2017-03-17 8 #include "iio_core.h" d5ca88eb Lars-Peter Clausen 2017-03-17 9 #include <linux/iio/machine.h> d5ca88eb Lars-Peter Clausen 2017-03-17 10 #include <linux/iio/driver.h> d5ca88eb Lars-Peter Clausen 2017-03-17 11 #include <linux/iio/consumer.h> d5ca88eb Lars-Peter Clausen 2017-03-17 12 #include <linux/iio/hw_consumer.h> d5ca88eb Lars-Peter Clausen 2017-03-17 13 #include <linux/iio/buffer.h> d5ca88eb Lars-Peter Clausen 2017-03-17 14 d5ca88eb Lars-Peter Clausen 2017-03-17 15 struct iio_hw_consumer { d5ca88eb Lars-Peter Clausen 2017-03-17 16 struct list_head buffers; d5ca88eb Lars-Peter Clausen 2017-03-17 17 struct iio_channel *channels; d5ca88eb Lars-Peter Clausen 2017-03-17 18 }; d5ca88eb Lars-Peter Clausen 2017-03-17 19 d5ca88eb Lars-Peter Clausen 2017-03-17 20 struct hw_consumer_buffer { d5ca88eb Lars-Peter Clausen 2017-03-17 21 struct list_head head; d5ca88eb Lars-Peter Clausen 2017-03-17 22 struct iio_dev *indio_dev; d5ca88eb Lars-Peter Clausen 2017-03-17 23 struct iio_buffer buffer; d5ca88eb Lars-Peter Clausen 2017-03-17 24 }; d5ca88eb Lars-Peter Clausen 2017-03-17 25 d5ca88eb Lars-Peter Clausen 2017-03-17 26 static struct hw_consumer_buffer *iio_buffer_to_hw_consumer_buffer( d5ca88eb Lars-Peter Clausen 2017-03-17 27 struct iio_buffer *buffer) d5ca88eb Lars-Peter Clausen 2017-03-17 28 { d5ca88eb Lars-Peter Clausen 2017-03-17 @29 return container_of(buffer, struct hw_consumer_buffer, buffer); d5ca88eb Lars-Peter Clausen 2017-03-17 30 } d5ca88eb Lars-Peter Clausen 2017-03-17 31 d5ca88eb Lars-Peter Clausen 2017-03-17 32 static void iio_hw_buf_release(struct iio_buffer *buffer) d5ca88eb Lars-Peter Clausen 2017-03-17 33 { d5ca88eb Lars-Peter Clausen 2017-03-17 34 struct hw_consumer_buffer *hw_buf = d5ca88eb Lars-Peter Clausen 2017-03-17 35 iio_buffer_to_hw_consumer_buffer(buffer); d5ca88eb Lars-Peter Clausen 2017-03-17 36 kfree(hw_buf->buffer.scan_mask); d5ca88eb Lars-Peter Clausen 2017-03-17 37 kfree(hw_buf); d5ca88eb Lars-Peter Clausen 2017-03-17 38 } d5ca88eb Lars-Peter Clausen 2017-03-17 39 d5ca88eb Lars-Peter Clausen 2017-03-17 @40 static const struct iio_buffer_access_funcs iio_hw_buf_access = { d5ca88eb Lars-Peter Clausen 2017-03-17 @41 .release = &iio_hw_buf_release, d5ca88eb Lars-Peter Clausen 2017-03-17 @42 .modes = INDIO_BUFFER_HARDWARE, d5ca88eb Lars-Peter Clausen 2017-03-17 43 }; d5ca88eb Lars-Peter Clausen 2017-03-17 44 d5ca88eb Lars-Peter Clausen 2017-03-17 45 static struct hw_consumer_buffer *iio_hw_consumer_get_buffer(
:::::: The code at line 29 was first introduced by commit :::::: d5ca88ebd75256ba43b57c82dfa9a3cbeb3cacf7 iio: Add hardware consumer support
:::::: TO: Lars-Peter Clausen lars@metafoo.de :::::: CC: 0day robot fengguang.wu@intel.com
--- 0-DAY kernel test infrastructure Open Source Technology Center https://lists.01.org/pipermail/kbuild-all Intel Corporation
Hi Arnaud,
[auto build test ERROR on asoc/for-next] [also build test ERROR on v4.11-rc3] [cannot apply to iio/togreg next-20170310] [if your patch is applied to the wrong git tree, please drop us a note to help improve the system]
url: https://github.com/0day-ci/linux/commits/Arnaud-Pouliquen/Add-STM32-DFSDM-su... base: https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git for-next config: i386-allyesconfig (attached as .config) compiler: gcc-6 (Debian 6.2.0-3) 6.2.0 20160901 reproduce: # save the attached .config to linux build tree make ARCH=i386
All errors (new ones prefixed by >>):
drivers/iio/hw_consumer.c:23:20: error: field 'buffer' has incomplete type struct iio_buffer buffer; ^~~~~~ drivers/iio/hw_consumer.c: In function 'iio_buffer_to_hw_consumer_buffer':
drivers/iio/hw_consumer.c:29:79: error: initialization from incompatible pointer type [-Werror=incompatible-pointer-types]
return container_of(buffer, struct hw_consumer_buffer, buffer); ^ drivers/iio/hw_consumer.c: At top level: drivers/iio/hw_consumer.c:40:21: error: variable 'iio_hw_buf_access' has initializer but incomplete type static const struct iio_buffer_access_funcs iio_hw_buf_access = { ^~~~~~~~~~~~~~~~~~~~~~~ drivers/iio/hw_consumer.c:41:2: error: unknown field 'release' specified in initializer .release = &iio_hw_buf_release, ^ drivers/iio/hw_consumer.c:41:13: warning: excess elements in struct initializer .release = &iio_hw_buf_release, ^ drivers/iio/hw_consumer.c:41:13: note: (near initialization for 'iio_hw_buf_access') drivers/iio/hw_consumer.c:42:2: error: unknown field 'modes' specified in initializer .modes = INDIO_BUFFER_HARDWARE, ^ drivers/iio/hw_consumer.c:42:11: warning: excess elements in struct initializer .modes = INDIO_BUFFER_HARDWARE, ^~~~ drivers/iio/hw_consumer.c:42:11: note: (near initialization for 'iio_hw_buf_access') drivers/iio/hw_consumer.c: In function 'iio_hw_consumer_get_buffer': drivers/iio/hw_consumer.c:66:2: error: implicit declaration of function 'iio_buffer_init' [-Werror=implicit-function-declaration] iio_buffer_init(&buf->buffer); ^~~~~~~~~~~~~~~ drivers/iio/hw_consumer.c: In function 'iio_hw_consumer_alloc': drivers/iio/hw_consumer.c:110:3: error: implicit declaration of function 'iio_buffer_put' [-Werror=implicit-function-declaration] iio_buffer_put(&buf->buffer); ^~~~~~~~~~~~~~ drivers/iio/hw_consumer.c: In function 'iio_hw_consumer_enable': drivers/iio/hw_consumer.c:135:9: error: implicit declaration of function 'iio_update_buffers' [-Werror=implicit-function-declaration] ret = iio_update_buffers(buf->indio_dev, &buf->buffer, NULL); ^~~~~~~~~~~~~~~~~~ drivers/iio/hw_consumer.c: At top level: drivers/iio/hw_consumer.c:40:45: error: storage size of 'iio_hw_buf_access' isn't known static const struct iio_buffer_access_funcs iio_hw_buf_access = { ^~~~~~~~~~~~~~~~~ cc1: some warnings being treated as errors -- In file included from drivers/iio/buffer/industrialio-buffer-dma.c:17:0: include/linux/iio/buffer-dma.h:107:20: error: field 'buffer' has incomplete type struct iio_buffer buffer; ^~~~~~ drivers/iio/buffer/industrialio-buffer-dma.c: In function 'iio_buffer_block_release': drivers/iio/buffer/industrialio-buffer-dma.c:104:2: error: implicit declaration of function 'iio_buffer_put' [-Werror=implicit-function-declaration] iio_buffer_put(&block->queue->buffer); ^~~~~~~~~~~~~~ drivers/iio/buffer/industrialio-buffer-dma.c: In function 'iio_buffer_to_queue':
drivers/iio/buffer/industrialio-buffer-dma.c:163:81: error: initialization from incompatible pointer type [-Werror=incompatible-pointer-types]
return container_of(buf, struct iio_dma_buffer_queue, buffer); ^ drivers/iio/buffer/industrialio-buffer-dma.c: In function 'iio_dma_buffer_alloc_block': drivers/iio/buffer/industrialio-buffer-dma.c:188:2: error: implicit declaration of function 'iio_buffer_get' [-Werror=implicit-function-declaration] iio_buffer_get(&queue->buffer); ^~~~~~~~~~~~~~ drivers/iio/buffer/industrialio-buffer-dma.c: In function 'iio_dma_buffer_read': drivers/iio/buffer/industrialio-buffer-dma.c:489:16: error: dereferencing pointer to incomplete type 'struct iio_buffer' if (n < buffer->bytes_per_datum) ^~ drivers/iio/buffer/industrialio-buffer-dma.c: In function 'iio_dma_buffer_init': drivers/iio/buffer/industrialio-buffer-dma.c:614:2: error: implicit declaration of function 'iio_buffer_init' [-Werror=implicit-function-declaration] iio_buffer_init(&queue->buffer); ^~~~~~~~~~~~~~~ drivers/iio/buffer/industrialio-buffer-dma.c: In function 'iio_buffer_to_queue': drivers/iio/buffer/industrialio-buffer-dma.c:164:1: warning: control reaches end of non-void function [-Wreturn-type] } ^ cc1: some warnings being treated as errors -- In file included from drivers/iio/buffer/industrialio-buffer-dmaengine.c:17:0: include/linux/iio/buffer-dma.h:107:20: error: field 'buffer' has incomplete type struct iio_buffer buffer; ^~~~~~ drivers/iio/buffer/industrialio-buffer-dmaengine.c: In function 'iio_buffer_to_dmaengine_buffer':
drivers/iio/buffer/industrialio-buffer-dmaengine.c:43:83: error: initialization from incompatible pointer type [-Werror=incompatible-pointer-types]
return container_of(buffer, struct dmaengine_buffer, queue.buffer); ^ drivers/iio/buffer/industrialio-buffer-dmaengine.c: At top level: drivers/iio/buffer/industrialio-buffer-dmaengine.c:109:21: error: variable 'iio_dmaengine_buffer_ops' has initializer but incomplete type static const struct iio_buffer_access_funcs iio_dmaengine_buffer_ops = { ^~~~~~~~~~~~~~~~~~~~~~~ drivers/iio/buffer/industrialio-buffer-dmaengine.c:110:2: error: unknown field 'read_first_n' specified in initializer .read_first_n = iio_dma_buffer_read, ^ drivers/iio/buffer/industrialio-buffer-dmaengine.c:110:18: warning: excess elements in struct initializer .read_first_n = iio_dma_buffer_read, ^~~~~~~~~~~~~~~~~~~ drivers/iio/buffer/industrialio-buffer-dmaengine.c:110:18: note: (near initialization for 'iio_dmaengine_buffer_ops') drivers/iio/buffer/industrialio-buffer-dmaengine.c:111:2: error: unknown field 'set_bytes_per_datum' specified in initializer .set_bytes_per_datum = iio_dma_buffer_set_bytes_per_datum, ^ drivers/iio/buffer/industrialio-buffer-dmaengine.c:111:25: warning: excess elements in struct initializer .set_bytes_per_datum = iio_dma_buffer_set_bytes_per_datum, ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ drivers/iio/buffer/industrialio-buffer-dmaengine.c:111:25: note: (near initialization for 'iio_dmaengine_buffer_ops') drivers/iio/buffer/industrialio-buffer-dmaengine.c:112:2: error: unknown field 'set_length' specified in initializer .set_length = iio_dma_buffer_set_length, ^ drivers/iio/buffer/industrialio-buffer-dmaengine.c:112:16: warning: excess elements in struct initializer .set_length = iio_dma_buffer_set_length, ^~~~~~~~~~~~~~~~~~~~~~~~~ drivers/iio/buffer/industrialio-buffer-dmaengine.c:112:16: note: (near initialization for 'iio_dmaengine_buffer_ops') drivers/iio/buffer/industrialio-buffer-dmaengine.c:113:2: error: unknown field 'request_update' specified in initializer .request_update = iio_dma_buffer_request_update, ^ drivers/iio/buffer/industrialio-buffer-dmaengine.c:113:20: warning: excess elements in struct initializer .request_update = iio_dma_buffer_request_update, ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~ drivers/iio/buffer/industrialio-buffer-dmaengine.c:113:20: note: (near initialization for 'iio_dmaengine_buffer_ops') drivers/iio/buffer/industrialio-buffer-dmaengine.c:114:2: error: unknown field 'enable' specified in initializer .enable = iio_dma_buffer_enable, ^ drivers/iio/buffer/industrialio-buffer-dmaengine.c:114:12: warning: excess elements in struct initializer .enable = iio_dma_buffer_enable, ^~~~~~~~~~~~~~~~~~~~~ drivers/iio/buffer/industrialio-buffer-dmaengine.c:114:12: note: (near initialization for 'iio_dmaengine_buffer_ops') drivers/iio/buffer/industrialio-buffer-dmaengine.c:115:2: error: unknown field 'disable' specified in initializer .disable = iio_dma_buffer_disable, ^ drivers/iio/buffer/industrialio-buffer-dmaengine.c:115:13: warning: excess elements in struct initializer .disable = iio_dma_buffer_disable, ^~~~~~~~~~~~~~~~~~~~~~ drivers/iio/buffer/industrialio-buffer-dmaengine.c:115:13: note: (near initialization for 'iio_dmaengine_buffer_ops') drivers/iio/buffer/industrialio-buffer-dmaengine.c:116:2: error: unknown field 'data_available' specified in initializer .data_available = iio_dma_buffer_data_available, ^ drivers/iio/buffer/industrialio-buffer-dmaengine.c:116:20: warning: excess elements in struct initializer .data_available = iio_dma_buffer_data_available, ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~ drivers/iio/buffer/industrialio-buffer-dmaengine.c:116:20: note: (near initialization for 'iio_dmaengine_buffer_ops') drivers/iio/buffer/industrialio-buffer-dmaengine.c:117:2: error: unknown field 'release' specified in initializer .release = iio_dmaengine_buffer_release, ^ drivers/iio/buffer/industrialio-buffer-dmaengine.c:117:13: warning: excess elements in struct initializer .release = iio_dmaengine_buffer_release, ^~~~~~~~~~~~~~~~~~~~~~~~~~~~ drivers/iio/buffer/industrialio-buffer-dmaengine.c:117:13: note: (near initialization for 'iio_dmaengine_buffer_ops') drivers/iio/buffer/industrialio-buffer-dmaengine.c:119:2: error: unknown field 'modes' specified in initializer .modes = INDIO_BUFFER_HARDWARE, ^ drivers/iio/buffer/industrialio-buffer-dmaengine.c:119:11: warning: excess elements in struct initializer .modes = INDIO_BUFFER_HARDWARE, ^~~~ drivers/iio/buffer/industrialio-buffer-dmaengine.c:119:11: note: (near initialization for 'iio_dmaengine_buffer_ops') drivers/iio/buffer/industrialio-buffer-dmaengine.c:120:2: error: unknown field 'flags' specified in initializer .flags = INDIO_BUFFER_FLAG_FIXED_WATERMARK, ^ drivers/iio/buffer/industrialio-buffer-dmaengine.c:120:11: error: 'INDIO_BUFFER_FLAG_FIXED_WATERMARK' undeclared here (not in a function) .flags = INDIO_BUFFER_FLAG_FIXED_WATERMARK, ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ drivers/iio/buffer/industrialio-buffer-dmaengine.c:120:11: warning: excess elements in struct initializer drivers/iio/buffer/industrialio-buffer-dmaengine.c:120:11: note: (near initialization for 'iio_dmaengine_buffer_ops') drivers/iio/buffer/industrialio-buffer-dmaengine.c: In function 'iio_dmaengine_buffer_free': drivers/iio/buffer/industrialio-buffer-dmaengine.c:206:2: error: implicit declaration of function 'iio_buffer_put' [-Werror=implicit-function-declaration] iio_buffer_put(buffer); ^~~~~~~~~~~~~~ drivers/iio/buffer/industrialio-buffer-dmaengine.c: At top level: drivers/iio/buffer/industrialio-buffer-dmaengine.c:109:45: error: storage size of 'iio_dmaengine_buffer_ops' isn't known static const struct iio_buffer_access_funcs iio_dmaengine_buffer_ops = { ^~~~~~~~~~~~~~~~~~~~~~~~ drivers/iio/buffer/industrialio-buffer-dmaengine.c: In function 'iio_buffer_to_dmaengine_buffer': drivers/iio/buffer/industrialio-buffer-dmaengine.c:44:1: warning: control reaches end of non-void function [-Wreturn-type] } ^ cc1: some warnings being treated as errors
vim +29 drivers/iio/hw_consumer.c
d5ca88eb Lars-Peter Clausen 2017-03-17 17 struct iio_channel *channels; d5ca88eb Lars-Peter Clausen 2017-03-17 18 }; d5ca88eb Lars-Peter Clausen 2017-03-17 19 d5ca88eb Lars-Peter Clausen 2017-03-17 20 struct hw_consumer_buffer { d5ca88eb Lars-Peter Clausen 2017-03-17 21 struct list_head head; d5ca88eb Lars-Peter Clausen 2017-03-17 22 struct iio_dev *indio_dev; d5ca88eb Lars-Peter Clausen 2017-03-17 @23 struct iio_buffer buffer; d5ca88eb Lars-Peter Clausen 2017-03-17 24 }; d5ca88eb Lars-Peter Clausen 2017-03-17 25 d5ca88eb Lars-Peter Clausen 2017-03-17 26 static struct hw_consumer_buffer *iio_buffer_to_hw_consumer_buffer( d5ca88eb Lars-Peter Clausen 2017-03-17 27 struct iio_buffer *buffer) d5ca88eb Lars-Peter Clausen 2017-03-17 28 { d5ca88eb Lars-Peter Clausen 2017-03-17 @29 return container_of(buffer, struct hw_consumer_buffer, buffer); d5ca88eb Lars-Peter Clausen 2017-03-17 30 } d5ca88eb Lars-Peter Clausen 2017-03-17 31 d5ca88eb Lars-Peter Clausen 2017-03-17 32 static void iio_hw_buf_release(struct iio_buffer *buffer)
:::::: The code at line 29 was first introduced by commit :::::: d5ca88ebd75256ba43b57c82dfa9a3cbeb3cacf7 iio: Add hardware consumer support
:::::: TO: Lars-Peter Clausen lars@metafoo.de :::::: CC: 0day robot fengguang.wu@intel.com
--- 0-DAY kernel test infrastructure Open Source Technology Center https://lists.01.org/pipermail/kbuild-all Intel Corporation
Hello Jonathan
Thanks for your comments Few answers in-line.
Regards Arnaud
On 03/19/2017 11:25 PM, Jonathan Cameron wrote:
On 17/03/17 14:08, 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. stm32-dfsdm-adc driver allows to handle sigma delta ADC.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com
Various minor bits inline.
I'm mostly liking this. I do slightly wondering if semantically it should be the front end that has the channels rather than the backend. Would be fiddly to do though and probably not worth the hassle.
DFSDM support the scan mode, so several front ends can be connected to One filter. In this case not possible to expose channel FE.
Would love to see it running in a continuous mode in IIO, but I guess that can follow along later.
Yes for the rest of the management it should be quite close to the stm32-adc driver.
The comment about the trigger has me confused
- perhaps you could elaborate further on that?
Code associated to the trigger should be part of the [PATCH v3 06/11] IIO: ADC: add stm32 DFSDM support for PDM microphone, as it concern the audio part... I did not found a way to use consumer.h interface to enable DFSDM IIO, without defining triggered buffer. that's why i defined a trigger and use it. But i just saw that my reasoning is wrong. I'm linked to trigger in stm32-dfsdm-audio.c because i use iio_triggered_buffer_postenable and iio_triggered_buffer_predisable. As i don't use the callback for buffer no need to call it...i can call the ASoC callback directly in DMA IRQ. Still a hack but more logic...
Jonathan
V2 -> V3 :
- Split audio and ADC support in 2 drivers
- Implement DMA cyclic mode
- Add SPI bus Trigger for buffer management
drivers/iio/adc/Kconfig | 26 ++ drivers/iio/adc/Makefile | 2 + drivers/iio/adc/stm32-dfsdm-adc.c | 419 +++++++++++++++++++++++ drivers/iio/adc/stm32-dfsdm-core.c | 658 +++++++++++++++++++++++++++++++++++++ drivers/iio/adc/stm32-dfsdm.h | 372 +++++++++++++++++++++ 5 files changed, 1477 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 d411d66..3e0eb11 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -452,6 +452,32 @@ config STM32_ADC This driver can also be built as a module. If so, the module will be called stm32-adc.
+config STM32_DFSDM_CORE
- tristate "STMicroelectronics STM32 dfsdm core"
- depends on (ARCH_STM32 && OF) || COMPILE_TEST
- select REGMAP
- select REGMAP_MMIO
- help
Select this option to enable the driver 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-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_DMAENGINE
- select IIO_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 X86 && ISA_BUS_API diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index c68819c..161f271 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -43,6 +43,8 @@ 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 +obj-$(CONFIG_STM32_DFSDM_CORE) += 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..ebcb3b4 --- /dev/null +++ b/drivers/iio/adc/stm32-dfsdm-adc.c @@ -0,0 +1,419 @@ +/*
- This file is the ADC part of of the STM32 DFSDM 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/interrupt.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/slab.h>
+#include <linux/iio/hw_consumer.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h>
+#include "stm32-dfsdm.h"
+#define DFSDM_TIMEOUT_US 100000 +#define DFSDM_TIMEOUT (msecs_to_jiffies(DFSDM_TIMEOUT_US / 1000))
+struct stm32_dfsdm_adc {
- struct stm32_dfsdm *dfsdm;
- unsigned int fl_id;
- unsigned int ch_id;
- unsigned int oversamp;
- struct completion completion;
- u32 *buffer;
- /* Hardware consumer structure for Front End IIO */
- struct iio_hw_consumer *hwc;
+};
+static int stm32_dfsdm_start_conv(struct stm32_dfsdm_adc *adc) +{
- int ret;
- ret = stm32_dfsdm_start_dfsdm(adc->dfsdm);
- if (ret < 0)
return ret;
- ret = stm32_dfsdm_start_channel(adc->dfsdm, adc->ch_id);
- if (ret < 0)
goto stop_dfsdm;
- 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:
- stm32_dfsdm_stop_channel(adc->dfsdm, adc->ch_id);
+stop_dfsdm:
- stm32_dfsdm_stop_dfsdm(adc->dfsdm);
- return ret;
+}
+static void stm32_dfsdm_stop_conv(struct stm32_dfsdm_adc *adc) +{
- stm32_dfsdm_stop_filter(adc->dfsdm, adc->fl_id);
- stm32_dfsdm_stop_channel(adc->dfsdm, adc->ch_id);
- stm32_dfsdm_stop_dfsdm(adc->dfsdm);
+}
+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;
- /* Unmask IRQ for regular conversion achievement*/
- ret = regmap_update_bits(adc->dfsdm->regmap, DFSDM_CR2(adc->fl_id),
DFSDM_CR2_REOCIE_MASK, DFSDM_CR2_REOCIE(1));
- if (ret < 0)
return ret;
- ret = stm32_dfsdm_start_conv(adc);
- if (ret < 0)
return ret;
- timeout = wait_for_completion_interruptible_timeout(&adc->completion,
DFSDM_TIMEOUT);
blank line perhaps.
- /* 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) {
dev_warn(&indio_dev->dev, "Conversion timed out!\n");
ret = -ETIMEDOUT;
- } else if (timeout < 0) {
ret = timeout;
- } else {
dev_dbg(&indio_dev->dev, "Converted val %#x\n", *res);
ret = IIO_VAL_INT;
- }
- /* 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));
- stm32_dfsdm_stop_conv(adc);
- 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;
- }
blank line here.
- 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);
if (ret < 0) {
dev_err(&indio_dev->dev,
"%s: Conversion failed (channel %d)\n",
__func__, chan->channel);
return ret;
}
iio_hw_consumer_disable(adc->hwc);
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,
- .driver_module = THIS_MODULE,
+};
+static irqreturn_t stm32_dfsdm_irq(int irq, void *arg) +{
- struct stm32_dfsdm_adc *adc = arg;
- struct regmap *regmap = adc->dfsdm->regmap;
- unsigned int status;
- regmap_read(regmap, DFSDM_ISR(adc->fl_id), &status);
- 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) {
What's this one? Might want a comment given it's an irq you basically eat.
Yes at least an error message that to inform on an overrun.
regmap_update_bits(regmap, DFSDM_ICR(adc->fl_id),
DFSDM_ICR_CLRROVRF_MASK,
DFSDM_ICR_CLRROVRF_MASK);
- }
- return IRQ_HANDLED;
+}
+static int stm32_dfsdm_postenable(struct iio_dev *indio_dev) +{
- struct stm32_dfsdm_adc *adc = iio_priv(indio_dev);
- return stm32_dfsdm_start_conv(adc);
+}
+static int stm32_dfsdm_predisable(struct iio_dev *indio_dev) +{
- struct stm32_dfsdm_adc *adc = iio_priv(indio_dev);
- stm32_dfsdm_stop_conv(adc);
blank line.
- return 0;
+}
+static const struct iio_buffer_setup_ops stm32_dfsdm_buffer_setup_ops = {
- .postenable = &stm32_dfsdm_postenable,
- .predisable = &stm32_dfsdm_predisable,
+};
+static int stm32_dfsdm_adc_chan_init_one(struct iio_dev *indio_dev,
struct iio_chan_spec *chan,
int ch_idx)
+{
- struct iio_chan_spec *ch = &chan[ch_idx];
- struct stm32_dfsdm_adc *adc = iio_priv(indio_dev);
- int ret;
- ret = stm32_dfsdm_channel_parse_of(adc->dfsdm, indio_dev, chan, ch_idx);
- ch->type = IIO_VOLTAGE;
- ch->indexed = 1;
- ch->scan_index = ch_idx;
- /*
* 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) |
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_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->dfsdm->num_chs) {
dev_err(&indio_dev->dev, "Bad st,adc-channels?\n");
return num_ch < 0 ? num_ch : -EINVAL;
- }
- /*
* Number of channel per filter is temporary limited to 1.
* Restriction should be cleaned with scan mode
*/
- if (num_ch > 1) {
dev_err(&indio_dev->dev, "Multi channel not yet supported\n");
return -EINVAL;
- }
- /* Bind to SD modulator IIO device */
- adc->hwc = iio_hw_consumer_alloc(&indio_dev->dev);
- if (IS_ERR(adc->hwc))
return -EPROBE_DEFER;
- channels = devm_kcalloc(&indio_dev->dev, num_ch, sizeof(*channels),
GFP_KERNEL);
- if (!channels)
return -ENOMEM;
- 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:
- iio_hw_consumer_free(adc->hwc);
Given you have to free this in the error path, I would imagine you will need a free somewhere in the main remove path? Or just create a devm version of iio_hw_consumer_alloc. It will be useful in the long run.
- return ret;
+}
+static const struct of_device_id stm32_dfsdm_adc_match[] = {
- { .compatible = "st,stm32-dfsdm-adc"},
- {}
+};
+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;
- struct iio_dev *iio;
- char *name;
- int ret, irq, val;
- 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->info = &stm32_dfsdm_info_adc;
- 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 = kzalloc(sizeof("dfsdm-adc0"), GFP_KERNEL);
not freed. Maybe devm_kzalloc
- if (!name)
return -ENOMEM;
- 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;
- ret = stm32_dfsdm_adc_chan_init(iio);
- if (ret < 0)
return ret;
- init_completion(&adc->completion);
- 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 *iio = iio_priv_to_dev(adc);
- iio_device_unregister(iio);
If all you have is this in remove, you can probably get away with devm_iio_device_register and get rid of the remove entirely.
- 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,
+}; +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"); diff --git a/drivers/iio/adc/stm32-dfsdm-core.c b/drivers/iio/adc/stm32-dfsdm-core.c new file mode 100644 index 0000000..488e456 --- /dev/null +++ b/drivers/iio/adc/stm32-dfsdm-core.c @@ -0,0 +1,658 @@ +/*
- This file is part the core part STM32 DFSDM driver
- Copyright (C) 2017, STMicroelectronics - All Rights Reserved
- Author(s): Arnaud Pouliquen arnaud.pouliquen@st.com for STMicroelectronics.
- 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.
- */
+#include <linux/clk.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/regmap.h> +#include <linux/slab.h>
+#include <linux/iio/trigger.h> +#include <linux/iio/sysfs.h>
+#include "stm32-dfsdm.h"
+struct stm32_dfsdm_dev_data {
- unsigned int num_filters;
- unsigned int num_channels;
- const struct regmap_config *regmap_cfg;
+};
+#define STM32H7_DFSDM_NUM_FILTERS 4 +#define STM32H7_DFSDM_NUM_CHANNELS 8
+#define DFSDM_MAX_INT_OVERSAMPLING 256
+#define DFSDM_MAX_FL_OVERSAMPLING 1024
+#define DFSDM_MAX_RES BIT(31) +#define DFSDM_DATA_RES BIT(23)
+static bool stm32_dfsdm_volatile_reg(struct device *dev, unsigned int reg) +{
- if (reg < DFSDM_FILTER_BASE_ADR)
return false;
- /*
* Mask is done on register to avoid to list registers of all them
* filter instances.
*/
- switch (reg & DFSDM_FILTER_REG_MASK) {
- case DFSDM_CR1(0) & DFSDM_FILTER_REG_MASK:
- case DFSDM_ISR(0) & DFSDM_FILTER_REG_MASK:
- case DFSDM_JDATAR(0) & DFSDM_FILTER_REG_MASK:
- case DFSDM_RDATAR(0) & DFSDM_FILTER_REG_MASK:
return true;
- }
- return false;
+}
+static const struct regmap_config stm32h7_dfsdm_regmap_cfg = {
- .reg_bits = 32,
- .val_bits = 32,
- .reg_stride = sizeof(u32),
- .max_register = 0x2B8,
- .volatile_reg = stm32_dfsdm_volatile_reg,
- .fast_io = true,
+};
+static const struct stm32_dfsdm_dev_data stm32h7_dfsdm_data = {
- .num_filters = STM32H7_DFSDM_NUM_FILTERS,
- .num_channels = STM32H7_DFSDM_NUM_CHANNELS,
- .regmap_cfg = &stm32h7_dfsdm_regmap_cfg,
+};
+struct dfsdm_priv {
- struct platform_device *pdev; /* platform device*/
- struct stm32_dfsdm dfsdm; /* common data exported for all instances */
- unsigned int spi_clk_out_div; /* SPI clkout divider value */
- atomic_t n_active_ch; /* number of current active channels */
- /* Clock */
- struct clk *clk; /* DFSDM clock */
- struct clk *aclk; /* audio clock */
+};
+/**
- stm32_dfsdm_set_osrs - compute filter parameters.
Naming would suggest it's more specific than this. Setting over sampling ratios?
Right, it is a computation not a set.
- Enable interface if n_active_ch is not null.
- @dfsdm: Handle used to retrieve dfsdm context.
- @fast: Fast mode enabled or disabled
- @oversamp: Expected oversampling between filtered sample and SD input stream
- */
+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;
- }
- /*
* Looks for filter and integrator oversampling ratios which allows
* to reach 24 bits data output resolution.
* Leave at once 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;
+}
+/**
- stm32_dfsdm_start_dfsdm - start global dfsdm IP interface.
- Enable interface if n_active_ch is not null.
- @dfsdm: Handle used to retrieve dfsdm context.
- */
+int stm32_dfsdm_start_dfsdm(struct stm32_dfsdm *dfsdm) +{
- struct dfsdm_priv *priv = container_of(dfsdm, struct dfsdm_priv, dfsdm);
- struct device *dev = &priv->pdev->dev;
- unsigned int clk_div = priv->spi_clk_out_div;
- int ret;
- if (atomic_inc_return(&priv->n_active_ch) == 1) {
/* Enable clocks */
ret = clk_prepare_enable(priv->clk);
if (ret < 0) {
dev_err(dev, "Failed to start clock\n");
return ret;
}
if (priv->aclk) {
ret = clk_prepare_enable(priv->aclk);
if (ret < 0) {
dev_err(dev, "Failed to start audio clock\n");
goto disable_clk;
}
}
/* Output the SPI CLKOUT (if clk_div == 0 clock if OFF) */
ret = regmap_update_bits(dfsdm->regmap, DFSDM_CHCFGR1(0),
DFSDM_CHCFGR1_CKOUTDIV_MASK,
DFSDM_CHCFGR1_CKOUTDIV(clk_div));
if (ret < 0)
goto disable_aclk;
/* Global enable of DFSDM interface */
ret = regmap_update_bits(dfsdm->regmap, DFSDM_CHCFGR1(0),
DFSDM_CHCFGR1_DFSDMEN_MASK,
DFSDM_CHCFGR1_DFSDMEN(1));
if (ret < 0)
goto disable_aclk;
- }
- dev_dbg(dev, "%s: n_active_ch %d\n", __func__,
atomic_read(&priv->n_active_ch));
- return 0;
+disable_aclk:
- clk_disable_unprepare(priv->aclk);
+disable_clk:
- clk_disable_unprepare(priv->clk);
- return ret;
+}
+/**
- stm32_dfsdm_stop_dfsdm - stop global DFSDM IP interface.
- Disable interface if n_active_ch is null
- @dfsdm: Handle used to retrieve dfsdm context.
- */
+int stm32_dfsdm_stop_dfsdm(struct stm32_dfsdm *dfsdm) +{
- struct dfsdm_priv *priv = container_of(dfsdm, struct dfsdm_priv, dfsdm);
- int ret;
- if (atomic_dec_and_test(&priv->n_active_ch)) {
/* Global disable of DFSDM interface */
ret = regmap_update_bits(dfsdm->regmap, DFSDM_CHCFGR1(0),
DFSDM_CHCFGR1_DFSDMEN_MASK,
DFSDM_CHCFGR1_DFSDMEN(0));
if (ret < 0)
return ret;
/* Stop SPI CLKOUT */
ret = regmap_update_bits(dfsdm->regmap, DFSDM_CHCFGR1(0),
DFSDM_CHCFGR1_CKOUTDIV_MASK,
DFSDM_CHCFGR1_CKOUTDIV(0));
if (ret < 0)
return ret;
/* Disable clocks */
clk_disable_unprepare(priv->clk);
if (priv->aclk)
clk_disable_unprepare(priv->aclk);
- }
- dev_dbg(&priv->pdev->dev, "%s: n_active_ch %d\n", __func__,
atomic_read(&priv->n_active_ch));
- return 0;
+}
+/**
- stm32_dfsdm_start_channel
- Start DFSDM IP channels and associated interface.
- @dfsdm: Handle used to retrieve dfsdm context.
- @ch_id: Channel index.
- */
+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));
+}
+/**
- stm32_dfsdm_stop_channel
- Stop DFSDM IP channels and associated interface.
- @dfsdm: Handle used to retrieve dfsdm context.
- @ch_id: Channel index.
- */
+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));
+}
+/**
- stm32_dfsdm_chan_configure
- Configure DFSDM IP channels and associated interface.
- @dfsdm: Handle used to retrieve dfsdm context.
- @ch_id: channel index.
- */
+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;
Blank line here and in similar places makes it easier for my eyes to parse at least... I'd also like to see some docs in here, not all of these are self explanatory.
I will apply recommendation in my whole code for next time
- 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));
+}
+/**
- stm32_dfsdm_start_filter - Start DFSDM IP filter conversion.
- @dfsdm: Handle used to retrieve dfsdm context.
- @fl_id: Filter index.
- */
+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));
+}
+/**
- stm32_dfsdm_stop_filter - Stop DFSDM IP filter conversion.
- @dfsdm: Handle used to retrieve dfsdm context.
- @fl_id: Filter index.
- */
+void stm32_dfsdm_stop_filter(struct stm32_dfsdm *dfsdm, unsigned int fl_id) +{
- /* Mask IRQ for regular conversion achievement*/
- regmap_update_bits(dfsdm->regmap, DFSDM_CR2(fl_id),
DFSDM_CR2_REOCIE_MASK, DFSDM_CR2_REOCIE(0));
- /* Disable conversion */
- regmap_update_bits(dfsdm->regmap, DFSDM_CR1(fl_id),
DFSDM_CR1_DFEN_MASK, DFSDM_CR1_DFEN(0));
+}
+/**
- stm32_dfsdm_filter_configure - Configure DFSDM IP filter and associate it
- to channel.
- @dfsdm: Handle used to retrieve dfsdm context.
- @fl_id: channel index.
- @fl_id: Filter index.
- */
+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));
- /* Filter order and Oversampling */
Please handle each error properly as it happens rather than mudling onwards. If there is reason for this odd construction, then document it clearly.
If you mention the checks on ret value that are missing at end of functions, yes dirty code to fix.
- if (!ret)
ret = regmap_update_bits(regmap, DFSDM_FCR(fl_id),
DFSDM_FCR_FOSR_MASK,
DFSDM_FCR_FOSR(fl->fosr));
- if (!ret)
ret = regmap_update_bits(regmap, DFSDM_FCR(fl_id),
DFSDM_FCR_FORD_MASK,
DFSDM_FCR_FORD(fl->ford));
- /* If only one channel 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));
- return regmap_update_bits(regmap, DFSDM_CR1(fl_id),
DFSDM_CR1_RSYNC_MASK,
DFSDM_CR1_RSYNC(fl->sync_mode));
+}
+static const struct iio_trigger_ops dfsdm_trigger_ops = {
- .owner = THIS_MODULE,
+};
+static int stm32_dfsdm_setup_spi_trigger(struct platform_device *pdev,
struct stm32_dfsdm *dfsdm)
+{
- /*
* To be able to use buffer consumer interface a trigger is needed.
* As conversion are trigged by PDM samples from SPI bus, that makes
* sense to define the serial interface ( SPI or manchester) as
* trigger source.
It's not actually the case that you have to have a triggrer. There are plenty of drivers (particularly ones with hardware buffering) where there is no trigger envolved. That's not to say it doesn't make sense here.
I'm not entirely sure yet if it's needed... Given it has no ops, I doubt it somewhat...
*/
- struct iio_trigger *trig;
- int ret;
- trig = devm_iio_trigger_alloc(&pdev->dev, DFSDM_SPI_TRIGGER_NAME);
- if (!trig)
return -ENOMEM;
- trig->dev.parent = pdev->dev.parent;
- trig->ops = &dfsdm_trigger_ops;
- iio_trigger_set_drvdata(trig, dfsdm);
- ret = devm_iio_trigger_register(&pdev->dev, trig);
- if (ret)
return ret;
Just return ret in all cases.
- return 0;
+}
+int stm32_dfsdm_channel_parse_of(struct stm32_dfsdm *dfsdm,
struct iio_dev *indio_dev,
struct iio_chan_spec *chan, int chan_idx)
+{
- struct iio_chan_spec *ch = &chan[chan_idx];
- struct stm32_dfsdm_channel *df_ch;
- const char *of_str;
- 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;
- }
- 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];
Extra space on the line above.
- df_ch->id = ch->channel;
- ret = of_property_read_string_index(indio_dev->dev.of_node,
"st,adc-channel-types", chan_idx,
&of_str);
- val = stm32_dfsdm_str2val(of_str, stm32_dfsdm_chan_type);
- if (ret < 0 || val < 0)
df_ch->type = 0;
- else
df_ch->type = val;
- ret = of_property_read_string_index(indio_dev->dev.of_node,
"st,adc-channel-clk-src", chan_idx,
&of_str);
- val = stm32_dfsdm_str2val(of_str, stm32_dfsdm_chan_src);
- if (ret < 0 || val < 0)
df_ch->src = 0;
- else
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_parse_of(struct platform_device *pdev,
struct dfsdm_priv *priv)
+{
- struct device_node *node = pdev->dev.of_node;
- struct resource *res;
- unsigned long clk_freq;
- unsigned int spi_freq, rem;
- int ret;
- if (!node)
return -EINVAL;
- /* Get resources */
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- if (!res) {
dev_err(&pdev->dev, "Failed to get memory resource\n");
return -ENODEV;
- }
- priv->dfsdm.phys_base = res->start;
- priv->dfsdm.base = devm_ioremap_resource(&pdev->dev, res);
- /* Source clock */
- priv->clk = devm_clk_get(&pdev->dev, "dfsdm");
- if (IS_ERR(priv->clk)) {
dev_err(&pdev->dev, "No stm32_dfsdm_clk clock found\n");
return -EINVAL;
- }
- priv->aclk = devm_clk_get(&pdev->dev, "audio");
- if (IS_ERR(priv->aclk))
priv->aclk = NULL;
- if (priv->aclk)
clk_freq = clk_get_rate(priv->aclk);
- else
clk_freq = clk_get_rate(priv->clk);
- /* SPI clock freq */
- ret = of_property_read_u32(pdev->dev.of_node, "spi-max-frequency",
&spi_freq);
- if (ret < 0) {
dev_err(&pdev->dev, "Failed to get spi-max-frequency\n");
return ret;
- }
- priv->spi_clk_out_div = div_u64_rem(clk_freq, spi_freq, &rem) - 1;
- priv->dfsdm.spi_master_freq = spi_freq;
- if (rem) {
dev_warn(&pdev->dev, "SPI clock not accurate\n");
dev_warn(&pdev->dev, "%ld = %d * %d + %d\n",
clk_freq, spi_freq, priv->spi_clk_out_div + 1, rem);
- }
- return 0;
+};
+static const struct of_device_id stm32_dfsdm_of_match[] = {
- {
.compatible = "st,stm32h7-dfsdm",
.data = &stm32h7_dfsdm_data,
- },
- {}
+}; +MODULE_DEVICE_TABLE(of, stm32_dfsdm_of_match);
+static int stm32_dfsdm_remove(struct platform_device *pdev) +{
- of_platform_depopulate(&pdev->dev);
- return 0;
+}
+static int stm32_dfsdm_probe(struct platform_device *pdev) +{
- struct dfsdm_priv *priv;
- struct device_node *pnode = pdev->dev.of_node;
- const struct of_device_id *of_id;
- const struct stm32_dfsdm_dev_data *dev_data;
- struct stm32_dfsdm *dfsdm;
- int ret, i;
- priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
- if (!priv)
return -ENOMEM;
- priv->pdev = pdev;
- /* Populate data structure depending on compatibility */
- of_id = of_match_node(stm32_dfsdm_of_match, pnode);
- 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;
- dfsdm = &priv->dfsdm;
- dfsdm->fl_list = devm_kcalloc(&pdev->dev, dev_data->num_filters,
sizeof(*dfsdm->fl_list), GFP_KERNEL);
- if (!dfsdm->fl_list)
return -ENOMEM;
- dfsdm->num_fls = dev_data->num_filters;
- dfsdm->ch_list = devm_kcalloc(&pdev->dev, dev_data->num_channels,
sizeof(*dfsdm->ch_list),
GFP_KERNEL);
- if (!dfsdm->ch_list)
return -ENOMEM;
- dfsdm->num_chs = dev_data->num_channels;
- ret = stm32_dfsdm_parse_of(pdev, priv);
- if (ret < 0)
return ret;
- dfsdm->regmap = devm_regmap_init_mmio(&pdev->dev, dfsdm->base,
&stm32h7_dfsdm_regmap_cfg);
- if (IS_ERR(dfsdm->regmap)) {
ret = PTR_ERR(dfsdm->regmap);
dev_err(&pdev->dev, "%s: Failed to allocate regmap: %d\n",
__func__, ret);
return ret;
- }
- for (i = 0; i < STM32H7_DFSDM_NUM_FILTERS; i++) {
struct stm32_dfsdm_filter *fl = &dfsdm->fl_list[i];
fl->id = i;
I'd like a comment on why this is needed...
to be cleaned.
- }
- platform_set_drvdata(pdev, dfsdm);
- ret = stm32_dfsdm_setup_spi_trigger(pdev, dfsdm);
- if (ret < 0)
return ret;
- return of_platform_populate(pnode, NULL, NULL, &pdev->dev);
+}
+static struct platform_driver stm32_dfsdm_driver = {
- .probe = stm32_dfsdm_probe,
- .remove = stm32_dfsdm_remove,
- .driver = {
.name = "stm32-dfsdm",
.of_match_table = stm32_dfsdm_of_match,
- },
+};
+module_platform_driver(stm32_dfsdm_driver);
+MODULE_AUTHOR("Arnaud Pouliquen arnaud.pouliquen@st.com"); +MODULE_DESCRIPTION("STMicroelectronics STM32 dfsdm driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/stm32-dfsdm.h b/drivers/iio/adc/stm32-dfsdm.h new file mode 100644 index 0000000..bb7d74f --- /dev/null +++ b/drivers/iio/adc/stm32-dfsdm.h @@ -0,0 +1,371 @@ +/*
- This file is part of STM32 DFSDM driver
- Copyright (C) 2017, STMicroelectronics - All Rights Reserved
- Author(s): Arnaud Pouliquen arnaud.pouliquen@st.com.
- License terms: GPL V2.0.
- This program is free software; you can redistribute it and/or modify it
- under the terms of the GNU General Public License version 2 as published by
- the Free Software Foundation.
- This program is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
- details.
- */
+#ifndef MDF_STM32_DFSDM__H +#define MDF_STM32_DFSDM__H
+#include <linux/bitfield.h>
+#include <linux/iio/iio.h> +/*
- STM32 DFSDM - global register map
- | Offset | Registers block |
- | 0x000 | CHANNEL 0 + COMMON CHANNEL FIELDS |
- | 0x020 | CHANNEL 1 |
- | ... | ..... |
- | 0x0E0 | CHANNEL 7 |
- | 0x100 | FILTER 0 + COMMON FILTER FIELDs |
- | 0x200 | FILTER 1 |
- | 0x300 | FILTER 2 |
- | 0x400 | FILTER 3 |
- */
+/*
- Channels register definitions
- */
+#define DFSDM_CHCFGR1(y) ((y) * 0x20 + 0x00) +#define DFSDM_CHCFGR2(y) ((y) * 0x20 + 0x04) +#define DFSDM_AWSCDR(y) ((y) * 0x20 + 0x08) +#define DFSDM_CHWDATR(y) ((y) * 0x20 + 0x0C) +#define DFSDM_CHDATINR(y) ((y) * 0x20 + 0x10)
+/* CHCFGR1: Channel configuration register 1 */ +#define DFSDM_CHCFGR1_SITP_MASK GENMASK(1, 0) +#define DFSDM_CHCFGR1_SITP(v) FIELD_PREP(DFSDM_CHCFGR1_SITP_MASK, v) +#define DFSDM_CHCFGR1_SPICKSEL_MASK GENMASK(3, 2) +#define DFSDM_CHCFGR1_SPICKSEL(v) FIELD_PREP(DFSDM_CHCFGR1_SPICKSEL_MASK, v) +#define DFSDM_CHCFGR1_SCDEN_MASK BIT(5) +#define DFSDM_CHCFGR1_SCDEN(v) FIELD_PREP(DFSDM_CHCFGR1_SCDEN_MASK, v) +#define DFSDM_CHCFGR1_CKABEN_MASK BIT(6) +#define DFSDM_CHCFGR1_CKABEN(v) FIELD_PREP(DFSDM_CHCFGR1_CKABEN_MASK, v) +#define DFSDM_CHCFGR1_CHEN_MASK BIT(7) +#define DFSDM_CHCFGR1_CHEN(v) FIELD_PREP(DFSDM_CHCFGR1_CHEN_MASK, v) +#define DFSDM_CHCFGR1_CHINSEL_MASK BIT(8) +#define DFSDM_CHCFGR1_CHINSEL(v) FIELD_PREP(DFSDM_CHCFGR1_CHINSEL_MASK, v) +#define DFSDM_CHCFGR1_DATMPX_MASK GENMASK(13, 12) +#define DFSDM_CHCFGR1_DATMPX(v) FIELD_PREP(DFSDM_CHCFGR1_DATMPX_MASK, v) +#define DFSDM_CHCFGR1_DATPACK_MASK GENMASK(15, 14) +#define DFSDM_CHCFGR1_DATPACK(v) FIELD_PREP(DFSDM_CHCFGR1_DATPACK_MASK, v) +#define DFSDM_CHCFGR1_CKOUTDIV_MASK GENMASK(23, 16) +#define DFSDM_CHCFGR1_CKOUTDIV(v) FIELD_PREP(DFSDM_CHCFGR1_CKOUTDIV_MASK, v) +#define DFSDM_CHCFGR1_CKOUTSRC_MASK BIT(30) +#define DFSDM_CHCFGR1_CKOUTSRC(v) FIELD_PREP(DFSDM_CHCFGR1_CKOUTSRC_MASK, v) +#define DFSDM_CHCFGR1_DFSDMEN_MASK BIT(31) +#define DFSDM_CHCFGR1_DFSDMEN(v) FIELD_PREP(DFSDM_CHCFGR1_DFSDMEN_MASK, v)
+/* CHCFGR2: Channel configuration register 2 */ +#define DFSDM_CHCFGR2_DTRBS_MASK GENMASK(7, 3) +#define DFSDM_CHCFGR2_DTRBS(v) FIELD_PREP(DFSDM_CHCFGR2_DTRBS_MASK, v) +#define DFSDM_CHCFGR2_OFFSET_MASK GENMASK(31, 8) +#define DFSDM_CHCFGR2_OFFSET(v) FIELD_PREP(DFSDM_CHCFGR2_OFFSET_MASK, v)
+/* AWSCDR: Channel analog watchdog and short circuit detector */ +#define DFSDM_AWSCDR_SCDT_MASK GENMASK(7, 0) +#define DFSDM_AWSCDR_SCDT(v) FIELD_PREP(DFSDM_AWSCDR_SCDT_MASK, v) +#define DFSDM_AWSCDR_BKSCD_MASK GENMASK(15, 12) +#define DFSDM_AWSCDR_BKSCD(v) FIELD_PREP(DFSDM_AWSCDR_BKSCD_MASK, v) +#define DFSDM_AWSCDR_AWFOSR_MASK GENMASK(20, 16) +#define DFSDM_AWSCDR_AWFOSR(v) FIELD_PREP(DFSDM_AWSCDR_AWFOSR_MASK, v) +#define DFSDM_AWSCDR_AWFORD_MASK GENMASK(23, 22) +#define DFSDM_AWSCDR_AWFORD(v) FIELD_PREP(DFSDM_AWSCDR_AWFORD_MASK, v)
+/*
- Filters register definitions
- */
+#define DFSDM_FILTER_BASE_ADR 0x100 +#define DFSDM_FILTER_REG_MASK 0x7F +#define DFSDM_FILTER_X_BASE_ADR(x) ((x) * 0x80 + DFSDM_FILTER_BASE_ADR)
+#define DFSDM_CR1(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x00) +#define DFSDM_CR2(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x04) +#define DFSDM_ISR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x08) +#define DFSDM_ICR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x0C) +#define DFSDM_JCHGR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x10) +#define DFSDM_FCR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x14) +#define DFSDM_JDATAR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x18) +#define DFSDM_RDATAR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x1C) +#define DFSDM_AWHTR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x20) +#define DFSDM_AWLTR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x24) +#define DFSDM_AWSR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x28) +#define DFSDM_AWCFR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x2C) +#define DFSDM_EXMAX(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x30) +#define DFSDM_EXMIN(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x34) +#define DFSDM_CNVTIMR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x38)
+/* CR1 Control register 1 */ +#define DFSDM_CR1_DFEN_MASK BIT(0) +#define DFSDM_CR1_DFEN(v) FIELD_PREP(DFSDM_CR1_DFEN_MASK, v) +#define DFSDM_CR1_JSWSTART_MASK BIT(1) +#define DFSDM_CR1_JSWSTART(v) FIELD_PREP(DFSDM_CR1_JSWSTART_MASK, v) +#define DFSDM_CR1_JSYNC_MASK BIT(3) +#define DFSDM_CR1_JSYNC(v) FIELD_PREP(DFSDM_CR1_JSYNC_MASK, v) +#define DFSDM_CR1_JSCAN_MASK BIT(4) +#define DFSDM_CR1_JSCAN(v) FIELD_PREP(DFSDM_CR1_JSCAN_MASK, v) +#define DFSDM_CR1_JDMAEN_MASK BIT(5) +#define DFSDM_CR1_JDMAEN(v) FIELD_PREP(DFSDM_CR1_JDMAEN_MASK, v) +#define DFSDM_CR1_JEXTSEL_MASK GENMASK(12, 8) +#define DFSDM_CR1_JEXTSEL(v) FIELD_PREP(DFSDM_CR1_JEXTSEL_MASK, v) +#define DFSDM_CR1_JEXTEN_MASK GENMASK(14, 13) +#define DFSDM_CR1_JEXTEN(v) FIELD_PREP(DFSDM_CR1_JEXTEN_MASK, v) +#define DFSDM_CR1_RSWSTART_MASK BIT(17) +#define DFSDM_CR1_RSWSTART(v) FIELD_PREP(DFSDM_CR1_RSWSTART_MASK, v) +#define DFSDM_CR1_RCONT_MASK BIT(18) +#define DFSDM_CR1_RCONT(v) FIELD_PREP(DFSDM_CR1_RCONT_MASK, v) +#define DFSDM_CR1_RSYNC_MASK BIT(19) +#define DFSDM_CR1_RSYNC(v) FIELD_PREP(DFSDM_CR1_RSYNC_MASK, v) +#define DFSDM_CR1_RDMAEN_MASK BIT(21) +#define DFSDM_CR1_RDMAEN(v) FIELD_PREP(DFSDM_CR1_RDMAEN_MASK, v) +#define DFSDM_CR1_RCH_MASK GENMASK(26, 24) +#define DFSDM_CR1_RCH(v) FIELD_PREP(DFSDM_CR1_RCH_MASK, v) +#define DFSDM_CR1_FAST_MASK BIT(29) +#define DFSDM_CR1_FAST(v) FIELD_PREP(DFSDM_CR1_FAST_MASK, v) +#define DFSDM_CR1_AWFSEL_MASK BIT(30) +#define DFSDM_CR1_AWFSEL(v) FIELD_PREP(DFSDM_CR1_AWFSEL_MASK, v)
+/* CR2: Control register 2 */ +#define DFSDM_CR2_IE_MASK GENMASK(6, 0) +#define DFSDM_CR2_IE(v) FIELD_PREP(DFSDM_CR2_IE_MASK, v) +#define DFSDM_CR2_JEOCIE_MASK BIT(0) +#define DFSDM_CR2_JEOCIE(v) FIELD_PREP(DFSDM_CR2_JEOCIE_MASK, v) +#define DFSDM_CR2_REOCIE_MASK BIT(1) +#define DFSDM_CR2_REOCIE(v) FIELD_PREP(DFSDM_CR2_REOCIE_MASK, v) +#define DFSDM_CR2_JOVRIE_MASK BIT(2) +#define DFSDM_CR2_JOVRIE(v) FIELD_PREP(DFSDM_CR2_JOVRIE_MASK, v) +#define DFSDM_CR2_ROVRIE_MASK BIT(3) +#define DFSDM_CR2_ROVRIE(v) FIELD_PREP(DFSDM_CR2_ROVRIE_MASK, v) +#define DFSDM_CR2_AWDIE_MASK BIT(4) +#define DFSDM_CR2_AWDIE(v) FIELD_PREP(DFSDM_CR2_AWDIE_MASK, v) +#define DFSDM_CR2_SCDIE_MASK BIT(5) +#define DFSDM_CR2_SCDIE(v) FIELD_PREP(DFSDM_CR2_SCDIE_MASK, v) +#define DFSDM_CR2_CKABIE_MASK BIT(6) +#define DFSDM_CR2_CKABIE(v) FIELD_PREP(DFSDM_CR2_CKABIE_MASK, v) +#define DFSDM_CR2_EXCH_MASK GENMASK(15, 8) +#define DFSDM_CR2_EXCH(v) FIELD_PREP(DFSDM_CR2_EXCH_MASK, v) +#define DFSDM_CR2_AWDCH_MASK GENMASK(23, 16) +#define DFSDM_CR2_AWDCH(v) FIELD_PREP(DFSDM_CR2_AWDCH_MASK, v)
+/* ISR: Interrupt status register */ +#define DFSDM_ISR_JEOCF_MASK BIT(0) +#define DFSDM_ISR_JEOCF(v) FIELD_PREP(DFSDM_ISR_JEOCF_MASK, v) +#define DFSDM_ISR_REOCF_MASK BIT(1) +#define DFSDM_ISR_REOCF(v) FIELD_PREP(DFSDM_ISR_REOCF_MASK, v) +#define DFSDM_ISR_JOVRF_MASK BIT(2) +#define DFSDM_ISR_JOVRF(v) FIELD_PREP(DFSDM_ISR_JOVRF_MASK, v) +#define DFSDM_ISR_ROVRF_MASK BIT(3) +#define DFSDM_ISR_ROVRF(v) FIELD_PREP(DFSDM_ISR_ROVRF_MASK, v) +#define DFSDM_ISR_AWDF_MASK BIT(4) +#define DFSDM_ISR_AWDF(v) FIELD_PREP(DFSDM_ISR_AWDF_MASK, v) +#define DFSDM_ISR_JCIP_MASK BIT(13) +#define DFSDM_ISR_JCIP(v) FIELD_PREP(DFSDM_ISR_JCIP_MASK, v) +#define DFSDM_ISR_RCIP_MASK BIT(14) +#define DFSDM_ISR_RCIP(v) FIELD_PREP(DFSDM_ISR_RCIP, v) +#define DFSDM_ISR_CKABF_MASK GENMASK(23, 16) +#define DFSDM_ISR_CKABF(v) FIELD_PREP(DFSDM_ISR_CKABF_MASK, v) +#define DFSDM_ISR_SCDF_MASK GENMASK(31, 24) +#define DFSDM_ISR_SCDF(v) FIELD_PREP(DFSDM_ISR_SCDF_MASK, v)
+/* ICR: Interrupt flag clear register */ +#define DFSDM_ICR_CLRJOVRF_MASK BIT(2) +#define DFSDM_ICR_CLRJOVRF(v) FIELD_PREP(DFSDM_ICR_CLRJOVRF_MASK, v) +#define DFSDM_ICR_CLRROVRF_MASK BIT(3) +#define DFSDM_ICR_CLRROVRF(v) FIELD_PREP(DFSDM_ICR_CLRROVRF_MASK, v) +#define DFSDM_ICR_CLRCKABF_MASK GENMASK(23, 16) +#define DFSDM_ICR_CLRCKABF(v) FIELD_PREP(DFSDM_ICR_CLRCKABF_MASK, v) +#define DFSDM_ICR_CLRCKABF_CH_MASK(y) BIT(16 + (y)) +#define DFSDM_ICR_CLRCKABF_CH(v, y) \
(((v) << (16 + (y))) & DFSDM_ICR_CLRCKABF_CH_MASK(y))
+#define DFSDM_ICR_CLRSCDF_MASK GENMASK(31, 24) +#define DFSDM_ICR_CLRSCDF(v) FIELD_PREP(DFSDM_ICR_CLRSCDF_MASK, v) +#define DFSDM_ICR_CLRSCDF_CH_MASK(y) BIT(24 + (y)) +#define DFSDM_ICR_CLRSCDF_CH(v, y) \
(((v) << (24 + (y))) & DFSDM_ICR_CLRSCDF_MASK(y))
+/* FCR: Filter control register */ +#define DFSDM_FCR_IOSR_MASK GENMASK(7, 0) +#define DFSDM_FCR_IOSR(v) FIELD_PREP(DFSDM_FCR_IOSR_MASK, v) +#define DFSDM_FCR_FOSR_MASK GENMASK(25, 16) +#define DFSDM_FCR_FOSR(v) FIELD_PREP(DFSDM_FCR_FOSR_MASK, v) +#define DFSDM_FCR_FORD_MASK GENMASK(31, 29) +#define DFSDM_FCR_FORD(v) FIELD_PREP(DFSDM_FCR_FORD_MASK, v)
+/* RDATAR: Filter data register for regular channel */ +#define DFSDM_DATAR_CH_MASK GENMASK(2, 0) +#define DFSDM_DATAR_DATA_OFFSET 8 +#define DFSDM_DATAR_DATA_MASK GENMASK(31, DFSDM_DATAR_DATA_OFFSET)
+/* AWLTR: Filter analog watchdog low threshold register */ +#define DFSDM_AWLTR_BKAWL_MASK GENMASK(3, 0) +#define DFSDM_AWLTR_BKAWL(v) FIELD_PREP(DFSDM_AWLTR_BKAWL_MASK, v) +#define DFSDM_AWLTR_AWLT_MASK GENMASK(31, 8) +#define DFSDM_AWLTR_AWLT(v) FIELD_PREP(DFSDM_AWLTR_AWLT_MASK, v)
+/* AWHTR: Filter analog watchdog low threshold register */ +#define DFSDM_AWHTR_BKAWH_MASK GENMASK(3, 0) +#define DFSDM_AWHTR_BKAWH(v) FIELD_PREP(DFSDM_AWHTR_BKAWH_MASK, v) +#define DFSDM_AWHTR_AWHT_MASK GENMASK(31, 8) +#define DFSDM_AWHTR_AWHT(v) FIELD_PREP(DFSDM_AWHTR_AWHT_MASK, v)
+/* AWSR: Filter watchdog status register */ +#define DFSDM_AWSR_AWLTF_MASK GENMASK(7, 0) +#define DFSDM_AWSR_AWLTF(v) FIELD_PREP(DFSDM_AWSR_AWLTF_MASK, v) +#define DFSDM_AWSR_AWHTF_MASK GENMASK(15, 8) +#define DFSDM_AWSR_AWHTF(v) FIELD_PREP(DFSDM_AWSR_AWHTF_MASK, v)
+/* AWCFR: Filter watchdog status register */ +#define DFSDM_AWCFR_AWLTF_MASK GENMASK(7, 0) +#define DFSDM_AWCFR_AWLTF(v) FIELD_PREP(DFSDM_AWCFR_AWLTF_MASK, v) +#define DFSDM_AWCFR_AWHTF_MASK GENMASK(15, 8) +#define DFSDM_AWCFR_AWHTF(v) FIELD_PREP(DFSDM_AWCFR_AWHTF_MASK, v)
+/* DFSDM filter order */ +enum stm32_dfsdm_sinc_order {
- DFSDM_FASTSINC_ORDER, /* FastSinc filter type */
- DFSDM_SINC1_ORDER, /* Sinc 1 filter type */
- DFSDM_SINC2_ORDER, /* Sinc 2 filter type */
- DFSDM_SINC3_ORDER, /* Sinc 3 filter type */
- DFSDM_SINC4_ORDER, /* Sinc 4 filter type (N.A. for watchdog) */
- DFSDM_SINC5_ORDER, /* Sinc 5 filter type (N.A. for watchdog) */
- DFSDM_NB_SINC_ORDER,
+};
+/**
- struct stm32_dfsdm_filter - structure relative to stm32 FDSDM filter
- TODO: complete structure.
nice :) RFC I guess :)
- @id: filetr ID,
- */
+struct stm32_dfsdm_filter {
- unsigned int id;
- unsigned int iosr; /* integrator oversampling */
- unsigned int fosr; /* filter oversampling */
- enum stm32_dfsdm_sinc_order ford;
- u64 res; /* output sample resolution */
- unsigned int sync_mode; /* filter suynchronized with filter0 */
- unsigned int fast; /* filter fast mode */
+};
+/**
- struct stm32_dfsdm_channel - structure relative to stm32 FDSDM channel
- TODO: complete structure.
- @id: filetr ID,
filter
- */
+struct stm32_dfsdm_channel {
- unsigned int id; /* id of the channel */
- unsigned int type; /* interface type linked to stm32_dfsdm_chan_type */
- unsigned int src; /* interface type linked to stm32_dfsdm_chan_src */
- unsigned int alt_si; /* use alternative serial input interface */
+};
+/**
- struct stm32_dfsdm - stm32 FDSDM driver common data (for all instances)
- @base: control registers base cpu addr
- @phys_base: DFSDM IP register physical address.
- @fl_list: filter resources list
- @num_fl: number of filter resources available
- @ch_list: channel resources list
- @num_chs: number of channel resources available
- */
+struct stm32_dfsdm {
- void __iomem *base;
- phys_addr_t phys_base;
- struct regmap *regmap;
- struct stm32_dfsdm_filter *fl_list;
- unsigned int num_fls;
- struct stm32_dfsdm_channel *ch_list;
- unsigned int num_chs;
- unsigned int spi_master_freq;
+};
+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 */
- { 0, 0},
+};
+/* DFSDM channel serial spi clock source */ +enum stm32_dfsdm_spi_clk_src {
- DFSDM_CHANNEL_SPI_CLOCK_EXTERNAL,
- DFSDM_CHANNEL_SPI_CLOCK_INTERNAL,
- DFSDM_CHANNEL_SPI_CLOCK_INTERNAL_DIV2_FALLING,
- DFSDM_CHANNEL_SPI_CLOCK_INTERNAL_DIV2_RISING
+};
+/* 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 },
- { 0, 0 },
+};
+/* DFSDM Serial interface trigger name */ +#define DFSDM_SPI_TRIGGER_NAME "DFSDM_SERIAL_IN"
+static inline 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;
+}
+int stm32_dfsdm_set_osrs(struct stm32_dfsdm_filter *fl, unsigned int fast,
unsigned int oversamp);
+int stm32_dfsdm_start_dfsdm(struct stm32_dfsdm *dfsdm); +int stm32_dfsdm_stop_dfsdm(struct stm32_dfsdm *dfsdm);
+int stm32_dfsdm_filter_configure(struct stm32_dfsdm *dfsdm, unsigned int fl_id,
unsigned int ch_id);
+int stm32_dfsdm_start_filter(struct stm32_dfsdm *dfsdm, unsigned int fl_id); +void stm32_dfsdm_stop_filter(struct stm32_dfsdm *dfsdm, unsigned int fl_id);
+int stm32_dfsdm_chan_configure(struct stm32_dfsdm *dfsdm,
struct stm32_dfsdm_channel *ch);
+int stm32_dfsdm_start_channel(struct stm32_dfsdm *dfsdm, unsigned int ch_id); +void stm32_dfsdm_stop_channel(struct stm32_dfsdm *dfsdm, unsigned int ch_id);
+int stm32_dfsdm_channel_parse_of(struct stm32_dfsdm *dfsdm,
struct iio_dev *indio_dev,
struct iio_chan_spec *chan, int chan_idx);
+#endif
-- To unsubscribe from this list: send the line "unsubscribe linux-iio" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
Please find my comments in-line
Thanks and Regards, Arnaud
On 03/19/2017 11:38 PM, Jonathan Cameron wrote:
On 17/03/17 14:08, Arnaud Pouliquen wrote:
Add DFSDM driver to handle PDM audio microphones.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com
So key element here is that we really need to have a proper way of doing DMA buffer consumers from IIO (as you said in your cover letter).
Not entirely obvious how to do this. Would like some input from Lars if possible. I'm afraid I don't have an suitable hardware to try anything out on (or the time really!). This will obviously be quite different from the single scan stuff you have seen is there at the moment.
I saw 2 other drivers ti_am335x_adc.c and stm32_adc.c) that use cyclic DMA. I suppose that problematics are similar. Perhaps the extra constrains in DFSDM is the in-kernel API used to get the data.
Whether this whole approach makes sense vs doing the dma in the alsa driver isn't clear to me.
On the plus side, presumably we'll get love dma ADC support for free as part of doing it this way ;)
It's been a while since I looked at the IIO dma buffer stuff at all. Will try and refresh my memory sometime this week.
Ok, i will wait your feedback (and Lars's one) before updating my code for next version.
Jonathan
drivers/iio/adc/Kconfig | 13 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/stm32-dfsdm-audio.c | 720 ++++++++++++++++++++++++++++++ include/linux/iio/adc/stm32-dfsdm-audio.h | 41 ++ 4 files changed, 775 insertions(+) create mode 100644 drivers/iio/adc/stm32-dfsdm-audio.c create mode 100644 include/linux/iio/adc/stm32-dfsdm-audio.h
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 3e0eb11..c108933 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -478,6 +478,19 @@ config STM32_DFSDM_ADC This driver can also be built as a module. If so, the module will be called stm32-dfsdm-adc.
+config STM32_DFSDM_AUDIO
- tristate "STMicroelectronics STM32 dfsdm audio
- depends on (ARCH_STM32 && OF) || COMPILE_TEST
- select STM32_DFSDM_CORE
- select REGMAP_MMIO
- select IIO_BUFFER_DMAENGINE
- help
Select this option to support Audio PDM micophone 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-audio.
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 161f271..79f975d 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -44,6 +44,7 @@ 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 +obj-$(CONFIG_STM32_DFSDM_AUDIO) += stm32-dfsdm-audio.o obj-$(CONFIG_STM32_DFSDM_CORE) += stm32-dfsdm-core.o obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o obj-$(CONFIG_TI_ADC0832) += ti-adc0832.o diff --git a/drivers/iio/adc/stm32-dfsdm-audio.c b/drivers/iio/adc/stm32-dfsdm-audio.c new file mode 100644 index 0000000..115ef8f --- /dev/null +++ b/drivers/iio/adc/stm32-dfsdm-audio.c @@ -0,0 +1,720 @@ +/*
- This file is the ADC part of of the STM32 DFSDM 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/dmaengine.h> +#include <linux/dma-mapping.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/slab.h>
+#include <linux/iio/buffer.h> +#include <linux/iio/hw_consumer.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/trigger.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h>
+#include "stm32-dfsdm.h"
+#define DFSDM_DMA_BUFFER_SIZE (4 * PAGE_SIZE)
+struct stm32_dfsdm_audio {
- struct stm32_dfsdm *dfsdm;
- unsigned int fl_id;
- unsigned int ch_id;
- unsigned int spi_freq; /* SPI bus clock frequency */
- unsigned int sample_freq; /* Sample frequency after filter decimation */
- u8 *rx_buf;
- unsigned int bufi; /* Buffer current position */
- unsigned int buf_sz; /* Buffer size */
- struct dma_chan *dma_chan;
- dma_addr_t dma_buf;
- int (*cb)(const void *data, size_t size, void *cb_priv);
- void *cb_priv;
+};
+const char *stm32_dfsdm_spi_trigger = DFSDM_SPI_TRIGGER_NAME;
+static ssize_t dfsdm_audio_get_rate(struct iio_dev *indio_dev, uintptr_t priv,
const struct iio_chan_spec *chan, char *buf)
+{
- struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev);
- return snprintf(buf, PAGE_SIZE, "%d\n", pdmc->sample_freq);
+}
+static ssize_t dfsdm_audio_set_rate(struct iio_dev *indio_dev, uintptr_t priv,
const struct iio_chan_spec *chan,
const char *buf, size_t len)
+{
- struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev);
- struct stm32_dfsdm_filter *fl = &pdmc->dfsdm->fl_list[pdmc->fl_id];
- struct stm32_dfsdm_channel *ch = &pdmc->dfsdm->ch_list[pdmc->ch_id];
- unsigned int spi_freq = pdmc->spi_freq;
- unsigned int sample_freq;
- int ret;
- ret = kstrtoint(buf, 0, &sample_freq);
- if (ret)
return ret;
- dev_dbg(&indio_dev->dev, "Requested sample_freq :%d\n", sample_freq);
- if (!sample_freq)
return -EINVAL;
- if (ch->src != DFSDM_CHANNEL_SPI_CLOCK_EXTERNAL)
spi_freq = pdmc->dfsdm->spi_master_freq;
- if (spi_freq % sample_freq)
dev_warn(&indio_dev->dev, "Sampling rate not accurate (%d)\n",
spi_freq / (spi_freq / sample_freq));
- ret = stm32_dfsdm_set_osrs(fl, 0, (spi_freq / sample_freq));
- if (ret < 0) {
dev_err(&indio_dev->dev,
"Not able to find filter parameter that match!\n");
return ret;
- }
- pdmc->sample_freq = sample_freq;
- return len;
+}
+static ssize_t dfsdm_audio_get_spiclk(struct iio_dev *indio_dev, uintptr_t priv,
const struct iio_chan_spec *chan,
char *buf)
+{
- struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev);
- return snprintf(buf, PAGE_SIZE, "%d\n", pdmc->spi_freq);
+}
+static ssize_t dfsdm_audio_set_spiclk(struct iio_dev *indio_dev, uintptr_t priv,
const struct iio_chan_spec *chan,
const char *buf, size_t len)
+{
- struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev);
- struct stm32_dfsdm_filter *fl = &pdmc->dfsdm->fl_list[pdmc->fl_id];
- struct stm32_dfsdm_channel *ch = &pdmc->dfsdm->ch_list[pdmc->ch_id];
- unsigned int sample_freq = pdmc->sample_freq;
- unsigned int spi_freq;
- int ret;
- /* If DFSDM is master on SPI, SPI freq can not be updated */
- if (ch->src != DFSDM_CHANNEL_SPI_CLOCK_EXTERNAL)
return -EPERM;
- ret = kstrtoint(buf, 0, &spi_freq);
- if (ret)
return ret;
- dev_dbg(&indio_dev->dev, "Requested frequency :%d\n", spi_freq);
- if (!spi_freq)
return -EINVAL;
- if (sample_freq) {
if (spi_freq % sample_freq)
dev_warn(&indio_dev->dev,
"Sampling rate not accurate (%d)\n",
spi_freq / (spi_freq / sample_freq));
ret = stm32_dfsdm_set_osrs(fl, 0, (spi_freq / sample_freq));
if (ret < 0) {
dev_err(&indio_dev->dev,
"No filter parameters that match!\n");
return ret;
}
- }
- pdmc->spi_freq = spi_freq;
- return len;
+}
+/*
- Define external info for SPI Frequency and audio sampling rate that can be
- configured by ASoC driver through consumer.h API
- */
+static const struct iio_chan_spec_ext_info dfsdm_adc_ext_info[] = {
- /* filter oversampling: Post filter oversampling ratio */
- {
.name = "audio_sampling_rate",
.shared = IIO_SHARED_BY_TYPE,
.read = dfsdm_audio_get_rate,
.write = dfsdm_audio_set_rate,
- },
- /* data_right_bit_shift : Filter output data shifting */
- {
.name = "spi_clk_freq",
.shared = IIO_SHARED_BY_TYPE,
.read = dfsdm_audio_get_spiclk,
.write = dfsdm_audio_set_spiclk,
- },
- {},
+};
+static int stm32_dfsdm_start_conv(struct stm32_dfsdm_audio *pdmc, bool single) +{
- struct regmap *regmap = pdmc->dfsdm->regmap;
- int ret;
- ret = stm32_dfsdm_start_dfsdm(pdmc->dfsdm);
- if (ret < 0)
return ret;
- ret = stm32_dfsdm_start_channel(pdmc->dfsdm, pdmc->ch_id);
- if (ret < 0)
goto stop_dfsdm;
- ret = stm32_dfsdm_filter_configure(pdmc->dfsdm, pdmc->fl_id,
pdmc->ch_id);
- if (ret < 0)
goto stop_channels;
- /* Enable DMA transfer*/
- ret = regmap_update_bits(regmap, DFSDM_CR1(pdmc->fl_id),
DFSDM_CR1_RDMAEN_MASK, DFSDM_CR1_RDMAEN(1));
- if (ret < 0)
return ret;
- /* Enable conversion triggered by SPI clock*/
- ret = regmap_update_bits(regmap, DFSDM_CR1(pdmc->fl_id),
DFSDM_CR1_RCONT_MASK, DFSDM_CR1_RCONT(1));
- if (ret < 0)
return ret;
- ret = stm32_dfsdm_start_filter(pdmc->dfsdm, pdmc->fl_id);
- if (ret < 0)
goto stop_channels;
- return 0;
+stop_channels:
- stm32_dfsdm_stop_channel(pdmc->dfsdm, pdmc->fl_id);
+stop_dfsdm:
- stm32_dfsdm_stop_dfsdm(pdmc->dfsdm);
- return ret;
+}
+static void stm32_dfsdm_stop_conv(struct stm32_dfsdm_audio *pdmc) +{
- stm32_dfsdm_stop_filter(pdmc->dfsdm, pdmc->fl_id);
- stm32_dfsdm_stop_channel(pdmc->dfsdm, pdmc->ch_id);
- stm32_dfsdm_stop_dfsdm(pdmc->dfsdm);
+}
+static int stm32_dfsdm_set_watermark(struct iio_dev *indio_dev,
unsigned int val)
+{
- struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev);
- unsigned int watermark = DFSDM_DMA_BUFFER_SIZE / 2;
- /*
* DMA cyclic transfers are used, buffer is split into two periods.
* There should be :
* - always one buffer (period) DMA is working on
* - one buffer (period) driver pushed to ASoC side ().
*/
- watermark = min(watermark, val * (unsigned int)(sizeof(u32)));
- pdmc->buf_sz = watermark * 2;
- return 0;
+}
+int stm32_dfsdm_validate_trigger(struct iio_dev *indio_dev,
struct iio_trigger *trig)
+{
- if (!strcmp(stm32_dfsdm_spi_trigger, trig->name))
return 0;
- return -EINVAL;
+}
+static const struct iio_info stm32_dfsdm_info_pdmc = {
- .hwfifo_set_watermark = stm32_dfsdm_set_watermark,
- .driver_module = THIS_MODULE,
- .validate_trigger = stm32_dfsdm_validate_trigger,
+};
+static irqreturn_t stm32_dfsdm_irq(int irq, void *arg) +{
- struct stm32_dfsdm_audio *pdmc = arg;
- struct iio_dev *indio_dev = iio_priv_to_dev(pdmc);
- struct regmap *regmap = pdmc->dfsdm->regmap;
- unsigned int status;
- regmap_read(regmap, DFSDM_ISR(pdmc->fl_id), &status);
- if (status & DFSDM_ISR_ROVRF_MASK) {
dev_err(&indio_dev->dev, "Unexpected Conversion overflow\n");
regmap_update_bits(regmap, DFSDM_ICR(pdmc->fl_id),
DFSDM_ICR_CLRROVRF_MASK,
DFSDM_ICR_CLRROVRF_MASK);
- }
- return IRQ_HANDLED;
+}
+static unsigned int stm32_dfsdm_audio_avail_data(struct stm32_dfsdm_audio *pdmc) +{
- struct dma_tx_state state;
- enum dma_status status;
- status = dmaengine_tx_status(pdmc->dma_chan,
pdmc->dma_chan->cookie,
&state);
- if (status == DMA_IN_PROGRESS) {
/* Residue is size in bytes from end of buffer */
unsigned int i = pdmc->buf_sz - state.residue;
unsigned int size;
/* Return available bytes */
if (i >= pdmc->bufi)
size = i - pdmc->bufi;
else
size = pdmc->buf_sz + i - pdmc->bufi;
return size;
- }
- return 0;
+}
+static void stm32_dfsdm_audio_dma_buffer_done(void *data) +{
- struct iio_dev *indio_dev = data;
- iio_trigger_poll_chained(indio_dev->trig);
This shouldn't be going through the trigger infrastructure at all really. I'm not 100% sure what doing so is gaining you...
Yes i will clean trigger part and call directly the ASoC callback here.
+}
+static int stm32_dfsdm_audio_dma_start(struct iio_dev *indio_dev) +{
- struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev);
- struct dma_async_tx_descriptor *desc;
- dma_cookie_t cookie;
- int ret;
- if (!pdmc->dma_chan)
return -EINVAL;
- dev_dbg(&indio_dev->dev, "%s size=%d watermark=%d\n", __func__,
pdmc->buf_sz, pdmc->buf_sz / 2);
- /* Prepare a DMA cyclic transaction */
- desc = dmaengine_prep_dma_cyclic(pdmc->dma_chan,
pdmc->dma_buf,
pdmc->buf_sz, pdmc->buf_sz / 2,
DMA_DEV_TO_MEM,
DMA_PREP_INTERRUPT);
- if (!desc)
return -EBUSY;
- desc->callback = stm32_dfsdm_audio_dma_buffer_done;
- desc->callback_param = indio_dev;
- cookie = dmaengine_submit(desc);
- ret = dma_submit_error(cookie);
- if (ret) {
dmaengine_terminate_all(pdmc->dma_chan);
return ret;
- }
- /* Issue pending DMA requests */
- dma_async_issue_pending(pdmc->dma_chan);
- return 0;
+}
+static int stm32_dfsdm_postenable(struct iio_dev *indio_dev) +{
- struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev);
- int ret;
- dev_dbg(&indio_dev->dev, "%s\n", __func__);
- /* Reset pdmc buffer index */
- pdmc->bufi = 0;
- ret = stm32_dfsdm_start_conv(pdmc, false);
- if (ret) {
dev_err(&indio_dev->dev, "Can't start conversion\n");
return ret;
- }
- ret = stm32_dfsdm_audio_dma_start(indio_dev);
- if (ret) {
dev_err(&indio_dev->dev, "Can't start DMA\n");
goto err_stop_conv;
- }
- ret = iio_triggered_buffer_postenable(indio_dev);
- if (ret < 0) {
dev_err(&indio_dev->dev, "%s :%d\n", __func__, __LINE__);
goto err_stop_dma;
- }
- return 0;
+err_stop_dma:
- if (pdmc->dma_chan)
dmaengine_terminate_all(pdmc->dma_chan);
+err_stop_conv:
- stm32_dfsdm_stop_conv(pdmc);
- return ret;
+}
+static int stm32_dfsdm_predisable(struct iio_dev *indio_dev) +{
- struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev);
- int ret;
- dev_dbg(&indio_dev->dev, "%s\n", __func__);
- ret = iio_triggered_buffer_predisable(indio_dev);
- if (ret < 0)
dev_err(&indio_dev->dev, "Predisable failed\n");
- if (pdmc->dma_chan)
dmaengine_terminate_all(pdmc->dma_chan);
- stm32_dfsdm_stop_conv(pdmc);
- return 0;
+}
+static const struct iio_buffer_setup_ops stm32_dfsdm_buffer_setup_ops = {
- .postenable = &stm32_dfsdm_postenable,
- .predisable = &stm32_dfsdm_predisable,
+};
+static irqreturn_t stm32_dfsdm_audio_trigger_handler(int irq, void *p) +{
OK. So now I kind of understand what the trigger provided in the adc driver was about. hmm. Triggers are supposed to be per sample so this is indeed a hack. We need fullblown DMA buffer consumers to do this right.
Yes exactly.
Probably best person to comment on that is Lars.
- struct iio_poll_func *pf = p;
- struct iio_dev *indio_dev = pf->indio_dev;
- struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev);
- size_t old_pos;
- int available = stm32_dfsdm_audio_avail_data(pdmc);
- /*
* Buffer interface is not support cyclic DMA buffer,and offer only
* an interface to push data samples per samples.
* For this reason iio_push_to_buffers_with_timestamp in not used
* and interface is hacked using a private callback registered by ASoC.
* This should be a temporary solution waiting a cyclic DMA engine
* support in IIO.
*/
- dev_dbg(&indio_dev->dev, "%s: pos = %d, available = %d\n", __func__,
pdmc->bufi, available);
- old_pos = pdmc->bufi;
- while (available >= indio_dev->scan_bytes) {
u32 *buffer = (u32 *)&pdmc->rx_buf[pdmc->bufi];
/* Mask 8 LSB that contains the channel ID */
*buffer &= 0xFFFFFF00;
available -= indio_dev->scan_bytes;
pdmc->bufi += indio_dev->scan_bytes;
if (pdmc->bufi >= pdmc->buf_sz) {
if (pdmc->cb)
pdmc->cb(&pdmc->rx_buf[old_pos],
pdmc->buf_sz - old_pos, pdmc->cb_priv);
pdmc->bufi = 0;
old_pos = 0;
}
- }
- if (pdmc->cb)
pdmc->cb(&pdmc->rx_buf[old_pos], pdmc->bufi - old_pos,
pdmc->cb_priv);
- iio_trigger_notify_done(indio_dev->trig);
- return IRQ_HANDLED;
+}
+/**
- stm32_dfsdm_get_buff_cb - register a callback
- that will be called when DMA transfer period is achieved.
- @iio_dev: Handle to IIO device.
- @cb: pointer to callback function.
- @data: pointer to data buffer
- @size: size in byte of the data buffer
- @private: pointer to consumer private structure
- @private: pointer to consumer private structure
- */
+int stm32_dfsdm_get_buff_cb(struct iio_dev *iio_dev,
int (*cb)(const void *data, size_t size,
void *private),
void *private)
+{
- struct stm32_dfsdm_audio *pdmc;
- if (!iio_dev)
return -EINVAL;
- pdmc = iio_priv(iio_dev);
- if (iio_dev != iio_priv_to_dev(pdmc))
return -EINVAL;
- pdmc->cb = cb;
- pdmc->cb_priv = private;
- return 0;
+} +EXPORT_SYMBOL_GPL(stm32_dfsdm_get_buff_cb);
+/**
- stm32_dfsdm_release_buff_cb - unregister buffer callback
- @iio_dev: Handle to IIO device.
- */
+int stm32_dfsdm_release_buff_cb(struct iio_dev *iio_dev) +{
- struct stm32_dfsdm_audio *pdmc;
- if (!iio_dev)
return -EINVAL;
- pdmc = iio_priv(iio_dev);
- if (iio_dev != iio_priv_to_dev(pdmc))
return -EINVAL;
- pdmc->cb = NULL;
- pdmc->cb_priv = NULL;
- return 0;
+} +EXPORT_SYMBOL_GPL(stm32_dfsdm_release_buff_cb);
+static int stm32_dfsdm_audio_chan_init(struct iio_dev *indio_dev) +{
- struct iio_chan_spec *ch;
- struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev);
- int ret;
- ch = devm_kzalloc(&indio_dev->dev, sizeof(*ch), GFP_KERNEL);
- if (!ch)
return -ENOMEM;
- ret = stm32_dfsdm_channel_parse_of(pdmc->dfsdm, indio_dev, ch, 0);
- if (ret < 0)
return ret;
- ch->type = IIO_VOLTAGE;
- ch->indexed = 1;
- ch->scan_index = 0;
- ch->ext_info = dfsdm_adc_ext_info;
- ch->scan_type.sign = 's';
- ch->scan_type.realbits = 24;
- ch->scan_type.storagebits = 32;
- pdmc->ch_id = ch->channel;
- ret = stm32_dfsdm_chan_configure(pdmc->dfsdm,
&pdmc->dfsdm->ch_list[ch->channel]);
- indio_dev->num_channels = 1;
- indio_dev->channels = ch;
- return ret;
+}
+static const struct of_device_id stm32_dfsdm_audio_match[] = {
- { .compatible = "st,stm32-dfsdm-audio"},
- {}
+};
+static int stm32_dfsdm_audio_dma_request(struct iio_dev *indio_dev) +{
- struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev);
- struct dma_slave_config config;
- int ret;
- pdmc->dma_chan = dma_request_slave_channel(&indio_dev->dev, "rx");
- if (!pdmc->dma_chan)
return -EINVAL;
- pdmc->rx_buf = dma_alloc_coherent(pdmc->dma_chan->device->dev,
DFSDM_DMA_BUFFER_SIZE,
&pdmc->dma_buf, GFP_KERNEL);
- if (!pdmc->rx_buf) {
ret = -ENOMEM;
goto err_release;
- }
- /* Configure DMA channel to read data register */
- memset(&config, 0, sizeof(config));
- config.src_addr = (dma_addr_t)pdmc->dfsdm->phys_base;
- config.src_addr += DFSDM_RDATAR(pdmc->fl_id);
- config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
- ret = dmaengine_slave_config(pdmc->dma_chan, &config);
- if (ret)
goto err_free;
- return 0;
+err_free:
- dma_free_coherent(pdmc->dma_chan->device->dev, DFSDM_DMA_BUFFER_SIZE,
pdmc->rx_buf, pdmc->dma_buf);
+err_release:
- dma_release_channel(pdmc->dma_chan);
- return ret;
+}
+static int stm32_dfsdm_audio_probe(struct platform_device *pdev) +{
- struct device *dev = &pdev->dev;
- struct stm32_dfsdm_audio *pdmc;
- struct device_node *np = dev->of_node;
- struct iio_dev *iio;
- char *name;
- int ret, irq, val;
- iio = devm_iio_device_alloc(dev, sizeof(*pdmc));
- if (IS_ERR(iio)) {
dev_err(dev, "%s: Failed to allocate IIO\n", __func__);
return PTR_ERR(iio);
- }
- pdmc = iio_priv(iio);
- if (IS_ERR(pdmc)) {
dev_err(dev, "%s: Failed to allocate ADC\n", __func__);
return PTR_ERR(pdmc);
- }
- pdmc->dfsdm = dev_get_drvdata(dev->parent);
- iio->name = np->name;
- iio->dev.parent = dev;
- iio->dev.of_node = np;
- iio->info = &stm32_dfsdm_info_pdmc;
- iio->modes = INDIO_DIRECT_MODE;
- platform_set_drvdata(pdev, pdmc);
- ret = of_property_read_u32(dev->of_node, "reg", &pdmc->fl_id);
- if (ret != 0) {
dev_err(dev, "Missing reg property\n");
return -EINVAL;
- }
- name = kzalloc(sizeof("dfsdm-pdm0"), GFP_KERNEL);
- if (!name)
return -ENOMEM;
- snprintf(name, sizeof("dfsdm-pdm0"), "dfsdm-pdm%d", pdmc->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, pdmc);
- 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;
- }
- pdmc->dfsdm->fl_list[pdmc->fl_id].ford = val;
- ret = of_property_read_u32(dev->of_node, "st,filter0-sync", &val);
- if (!ret)
pdmc->dfsdm->fl_list[pdmc->fl_id].sync_mode = val;
- ret = stm32_dfsdm_audio_chan_init(iio);
- if (ret < 0)
return ret;
- ret = stm32_dfsdm_audio_dma_request(iio);
- if (ret) {
dev_err(&pdev->dev, "DMA request failed\n");
return ret;
- }
- iio->modes |= INDIO_BUFFER_SOFTWARE;
- ret = iio_triggered_buffer_setup(iio,
&iio_pollfunc_store_time,
&stm32_dfsdm_audio_trigger_handler,
&stm32_dfsdm_buffer_setup_ops);
- if (ret) {
dev_err(&pdev->dev, "Buffer setup failed\n");
goto err_dma_disable;
- }
- ret = iio_device_register(iio);
- if (ret) {
dev_err(&pdev->dev, "IIO dev register failed\n");
goto err_buffer_cleanup;
- }
- return 0;
+err_buffer_cleanup:
- iio_triggered_buffer_cleanup(iio);
+err_dma_disable:
- if (pdmc->dma_chan) {
dma_free_coherent(pdmc->dma_chan->device->dev,
DFSDM_DMA_BUFFER_SIZE,
pdmc->rx_buf, pdmc->dma_buf);
dma_release_channel(pdmc->dma_chan);
- }
- return ret;
+}
+static int stm32_dfsdm_audio_remove(struct platform_device *pdev) +{
- struct stm32_dfsdm_audio *pdmc = platform_get_drvdata(pdev);
- struct iio_dev *iio = iio_priv_to_dev(pdmc);
- iio_device_unregister(iio);
Same as in the other driver. Can just use the devm_iio_device_register version and drop remove.
oops missing the previous comment... sorry.
- return 0;
+}
+static struct platform_driver stm32_dfsdm_audio_driver = {
- .driver = {
.name = "stm32-dfsdm-audio",
.of_match_table = stm32_dfsdm_audio_match,
- },
- .probe = stm32_dfsdm_audio_probe,
- .remove = stm32_dfsdm_audio_remove,
+}; +module_platform_driver(stm32_dfsdm_audio_driver);
+MODULE_DESCRIPTION("STM32 sigma delta converter for PDM microphone"); +MODULE_AUTHOR("Arnaud Pouliquen arnaud.pouliquen@st.com"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/iio/adc/stm32-dfsdm-audio.h b/include/linux/iio/adc/stm32-dfsdm-audio.h new file mode 100644 index 0000000..9b41b28 --- /dev/null +++ b/include/linux/iio/adc/stm32-dfsdm-audio.h @@ -0,0 +1,41 @@ +/*
- This file discribe the STM32 DFSDM IIO driver API for audio part
- Copyright (C) 2017, STMicroelectronics - All Rights Reserved
- Author(s): Arnaud Pouliquen arnaud.pouliquen@st.com.
- License terms: GPL V2.0.
- This program is free software; you can redistribute it and/or modify it
- under the terms of the GNU General Public License version 2 as published by
- the Free Software Foundation.
- This program is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
- details.
- */
+#ifndef STM32_DFSDM_AUDIO_H +#define STM32_DFSDM_AUDIO_H
+/**
- stm32_dfsdm_get_buff_cb() - register callback for capture buffer period.
- @dev: Pointer to client device.
- @indio_dev: Device on which the channel exists.
- @cb: Callback function.
@data: pointer to data buffer
@size: size of the data buffer in bytes
- @private: Private data passed to callback.
- */
+int stm32_dfsdm_get_buff_cb(struct iio_dev *iio_dev,
int (*cb)(const void *data, size_t size,
void *private),
void *private);
+/**
- stm32_dfsdm_get_buff_cb() - release callback for capture buffer period.
- @indio_dev: Device on which the channel exists.
- */
+int stm32_dfsdm_release_buff_cb(struct iio_dev *iio_dev);
+#endif
On 03/19/2017 11:44 PM, Jonathan Cameron wrote:
On 17/03/17 14:08, Arnaud Pouliquen wrote:
Add iio consumer API to set buffer size and watermark according to sysfs API.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com
Hmm. Not keen on the length one. Setting a requested watermark is fair enough. There is no actually buffer in these cases though so setting it's length is downright odd..
Length and watermark are configurable from user land through sysfs. Seems to me logic to also propose it in inkern API... But I can clean length , no problem.
Guess this is part of the hacks we need to clean up by doing the dma buffer consumer stuff right...
Yes all is linked :-).
Jonathan
drivers/iio/buffer/industrialio-buffer-cb.c | 12 ++++++++++++ include/linux/iio/consumer.h | 13 +++++++++++++ 2 files changed, 25 insertions(+)
diff --git a/drivers/iio/buffer/industrialio-buffer-cb.c b/drivers/iio/buffer/industrialio-buffer-cb.c index b8f550e..43c066a 100644 --- a/drivers/iio/buffer/industrialio-buffer-cb.c +++ b/drivers/iio/buffer/industrialio-buffer-cb.c @@ -103,6 +103,18 @@ struct iio_cb_buffer *iio_channel_get_all_cb(struct device *dev, } EXPORT_SYMBOL_GPL(iio_channel_get_all_cb);
+int iio_channel_cb_set_buffer_params(struct iio_cb_buffer *cb_buff,
size_t length, size_t watermark)
+{
- if (!length || length < watermark)
return -EINVAL;
- cb_buff->buffer.watermark = watermark;
- cb_buff->buffer.length = length;
- return 0;
+} +EXPORT_SYMBOL_GPL(iio_channel_start_all_cb);
int iio_channel_start_all_cb(struct iio_cb_buffer *cb_buff) { return iio_update_buffers(cb_buff->indio_dev, &cb_buff->buffer, diff --git a/include/linux/iio/consumer.h b/include/linux/iio/consumer.h index cb44771..0f6e94d 100644 --- a/include/linux/iio/consumer.h +++ b/include/linux/iio/consumer.h @@ -134,6 +134,19 @@ struct iio_cb_buffer *iio_channel_get_all_cb(struct device *dev, void *private), void *private); /**
- iio_channel_cb_set_buffer_size() - set the buffer length.
- @cb_buffer: The callback buffer from whom we want the channel
information.
- @length: buffer length in bytes
- @watermark: buffer watermark in bytes
- This function allows to configure the buffer length. The watermark if
- forced to half of the buffer.
- */
+int iio_channel_cb_set_buffer_params(struct iio_cb_buffer *cb_buffer,
size_t length, size_t watermark);
+/**
- iio_channel_release_all_cb() - release and unregister the callback.
- @cb_buffer: The callback buffer that was allocated.
*/
-- To unsubscribe from this list: send the line "unsubscribe linux-iio" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
On Fri, Mar 17, 2017 at 03:08:15PM +0100, Arnaud Pouliquen wrote:
Add documentation of device tree bindings to support sigma delta modulator in IIO framework.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com
V2 -> V3 : -Rename to suppress "simple" - add "ads1201" compatibility
.../devicetree/bindings/iio/adc/sigma-delta-modulator.txt | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/adc/sigma-delta-modulator.txt
diff --git a/Documentation/devicetree/bindings/iio/adc/sigma-delta-modulator.txt b/Documentation/devicetree/bindings/iio/adc/sigma-delta-modulator.txt new file mode 100644 index 0000000..27f04a3 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/sigma-delta-modulator.txt @@ -0,0 +1,13 @@ +Device-Tree bindings for sigma delta modulator
+Required properties: +- compatible: should be "ads1201", "sd-modulator". "sd-modulator" can be use
- as a generic SD modulator if modulator not specified in compatible list.
+- #io-channel-cells = <1>: See the IIO bindings section "IIO consumers".
+Example node:
- ads1202: simple_sd_adc@0 {
adc@...
With that,
Acked-by: Rob Herring robh@kernel.org
compatible = "sd-modulator";
#io-channel-cells = <1>;
- };
-- 1.9.1
On Fri, Mar 17, 2017 at 03:08:17PM +0100, Arnaud Pouliquen wrote:
Add bindings that describes Digital Filter for Sigma Delta Modulators. DFSDM allows to connect sigma delta modulators.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com
V2->V3: Fixes based on V2 comments
.../bindings/iio/adc/st,stm32-dfsdm-adc.txt | 120 +++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/adc/st,stm32-dfsdm-adc.txt
Acked-by: Rob Herring robh@kernel.org
On Fri, Mar 17, 2017 at 03:08:21PM +0100, Arnaud Pouliquen wrote:
This patch adds documentation of device tree bindings for audio DMIC codec.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com
Documentation/devicetree/bindings/sound/dmic.txt | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/dmic.txt
diff --git a/Documentation/devicetree/bindings/sound/dmic.txt b/Documentation/devicetree/bindings/sound/dmic.txt new file mode 100644 index 0000000..eb2a9d4 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/dmic.txt @@ -0,0 +1,11 @@ +Device-Tree bindings for dmic codec
Please define what is and isn't a DMIC here. What's the interface?
+Required properties:
- compatible: should be "dmic-codec".
DMICs don't have part numbers?
+Example node:
- dmic_audio: dmic_audio@0 {
Don't use '_' and there's no reg property, so there should be no unit address.
compatible = "dmic-codec";
status = "okay";
Don't show status in examples.
- };
-- 1.9.1
On Fri, Mar 17, 2017 at 03:08:23PM +0100, Arnaud Pouliquen wrote:
Add bindings that describes audio settings to support Digital Filter for pulse density modulation(PDM) microphone.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com
V2->V3: Fixes based on V2 comments
.../devicetree/bindings/sound/st,stm32-adfsdm.txt | 41 ++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/st,stm32-adfsdm.txt
diff --git a/Documentation/devicetree/bindings/sound/st,stm32-adfsdm.txt b/Documentation/devicetree/bindings/sound/st,stm32-adfsdm.txt new file mode 100644 index 0000000..ab610bc --- /dev/null +++ b/Documentation/devicetree/bindings/sound/st,stm32-adfsdm.txt @@ -0,0 +1,40 @@ +STMicroelectronics audio DFSDM DT bindings
+This driver supports audio PDM microphone capture through Digital Filter format +Sigma Delta modulators (DFSDM).
+Required properties:
- compatible: "st,stm32h7-adfsdm".
- #sound-dai-cells : Must be equal to 0
- io-channels : phandle to iio dfsdm instance node.
+Example of a simple sound card using audio DFSDM node.
- dmic0: dmic_@0 {
Drop the '_' and unit address.
compatible = "dmic-codec";
#sound-dai-cells = <0>;
- };
- asoc-pdm@0 {
asoc is a Linux term. Drop the unit address.
compatible = "st,stm32h7-adfsdm";
Is this a separate block from the ADC? A drawing of the h/w blocks and connections would help.
#sound-dai-cells = <0>;
io-channels = <&dfsdm_adc0 0>;
- };
- sound_dfsdm_pdm {
sound-card {
compatible = "simple-audio-card";
simple-audio-card,name = "dfsdm_pdm";
dfsdm0_mic0: simple-audio-card,dai-link@0 {
I'd suggest moving to the graph card.
format = "pdm";
cpu {
sound-dai = <&asoc_pdm1>;
This phandle doesn't point to anything.
};
dmic0_codec: codec {
sound-dai = <&dmic0>;
};
};
- };
\ No newline at end of file
^^^
-- 1.9.1
On 20/03/17 11:24, Arnaud Pouliquen wrote:
Hello Jonathan
Thanks for your comments Few answers in-line.
Regards Arnaud
On 03/19/2017 11:25 PM, Jonathan Cameron wrote:
On 17/03/17 14:08, 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. stm32-dfsdm-adc driver allows to handle sigma delta ADC.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com
Various minor bits inline.
I'm mostly liking this. I do slightly wondering if semantically it should be the front end that has the channels rather than the backend. Would be fiddly to do though and probably not worth the hassle.
DFSDM support the scan mode, so several front ends can be connected to One filter. In this case not possible to expose channel FE.
It still could but would admittedly get really fiddly and require demuxing the scan...
Would love to see it running in a continuous mode in IIO, but I guess that can follow along later.
Yes for the rest of the management it should be quite close to the stm32-adc driver.
The comment about the trigger has me confused
- perhaps you could elaborate further on that?
Code associated to the trigger should be part of the [PATCH v3 06/11] IIO: ADC: add stm32 DFSDM support for PDM microphone, as it concern the audio part... I did not found a way to use consumer.h interface to enable DFSDM IIO, without defining triggered buffer. that's why i defined a trigger and use it. But i just saw that my reasoning is wrong. I'm linked to trigger in stm32-dfsdm-audio.c because i use iio_triggered_buffer_postenable and iio_triggered_buffer_predisable.
This used to be more obvious until we put those boiler plate functions in to avoid lots of replication. Pretty much everything should be optional.
As i don't use the callback for buffer no need to call it...i can call the ASoC callback directly in DMA IRQ. Still a hack but more logic...
Cool. We definitely need to clean this up long term, but perhaps not as part of this initially at least!
I hate holding drivers up for internal stuff that we can change in our own good time!
Jonathan
V2 -> V3 :
- Split audio and ADC support in 2 drivers
- Implement DMA cyclic mode
- Add SPI bus Trigger for buffer management
drivers/iio/adc/Kconfig | 26 ++ drivers/iio/adc/Makefile | 2 + drivers/iio/adc/stm32-dfsdm-adc.c | 419 +++++++++++++++++++++++ drivers/iio/adc/stm32-dfsdm-core.c | 658 +++++++++++++++++++++++++++++++++++++ drivers/iio/adc/stm32-dfsdm.h | 372 +++++++++++++++++++++ 5 files changed, 1477 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 d411d66..3e0eb11 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -452,6 +452,32 @@ config STM32_ADC This driver can also be built as a module. If so, the module will be called stm32-adc.
+config STM32_DFSDM_CORE
- tristate "STMicroelectronics STM32 dfsdm core"
- depends on (ARCH_STM32 && OF) || COMPILE_TEST
- select REGMAP
- select REGMAP_MMIO
- help
Select this option to enable the driver 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-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_DMAENGINE
- select IIO_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 X86 && ISA_BUS_API diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index c68819c..161f271 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -43,6 +43,8 @@ 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 +obj-$(CONFIG_STM32_DFSDM_CORE) += 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..ebcb3b4 --- /dev/null +++ b/drivers/iio/adc/stm32-dfsdm-adc.c @@ -0,0 +1,419 @@ +/*
- This file is the ADC part of of the STM32 DFSDM 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/interrupt.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/slab.h>
+#include <linux/iio/hw_consumer.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h>
+#include "stm32-dfsdm.h"
+#define DFSDM_TIMEOUT_US 100000 +#define DFSDM_TIMEOUT (msecs_to_jiffies(DFSDM_TIMEOUT_US / 1000))
+struct stm32_dfsdm_adc {
- struct stm32_dfsdm *dfsdm;
- unsigned int fl_id;
- unsigned int ch_id;
- unsigned int oversamp;
- struct completion completion;
- u32 *buffer;
- /* Hardware consumer structure for Front End IIO */
- struct iio_hw_consumer *hwc;
+};
+static int stm32_dfsdm_start_conv(struct stm32_dfsdm_adc *adc) +{
- int ret;
- ret = stm32_dfsdm_start_dfsdm(adc->dfsdm);
- if (ret < 0)
return ret;
- ret = stm32_dfsdm_start_channel(adc->dfsdm, adc->ch_id);
- if (ret < 0)
goto stop_dfsdm;
- 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:
- stm32_dfsdm_stop_channel(adc->dfsdm, adc->ch_id);
+stop_dfsdm:
- stm32_dfsdm_stop_dfsdm(adc->dfsdm);
- return ret;
+}
+static void stm32_dfsdm_stop_conv(struct stm32_dfsdm_adc *adc) +{
- stm32_dfsdm_stop_filter(adc->dfsdm, adc->fl_id);
- stm32_dfsdm_stop_channel(adc->dfsdm, adc->ch_id);
- stm32_dfsdm_stop_dfsdm(adc->dfsdm);
+}
+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;
- /* Unmask IRQ for regular conversion achievement*/
- ret = regmap_update_bits(adc->dfsdm->regmap, DFSDM_CR2(adc->fl_id),
DFSDM_CR2_REOCIE_MASK, DFSDM_CR2_REOCIE(1));
- if (ret < 0)
return ret;
- ret = stm32_dfsdm_start_conv(adc);
- if (ret < 0)
return ret;
- timeout = wait_for_completion_interruptible_timeout(&adc->completion,
DFSDM_TIMEOUT);
blank line perhaps.
- /* 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) {
dev_warn(&indio_dev->dev, "Conversion timed out!\n");
ret = -ETIMEDOUT;
- } else if (timeout < 0) {
ret = timeout;
- } else {
dev_dbg(&indio_dev->dev, "Converted val %#x\n", *res);
ret = IIO_VAL_INT;
- }
- /* 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));
- stm32_dfsdm_stop_conv(adc);
- 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;
- }
blank line here.
- 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);
if (ret < 0) {
dev_err(&indio_dev->dev,
"%s: Conversion failed (channel %d)\n",
__func__, chan->channel);
return ret;
}
iio_hw_consumer_disable(adc->hwc);
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,
- .driver_module = THIS_MODULE,
+};
+static irqreturn_t stm32_dfsdm_irq(int irq, void *arg) +{
- struct stm32_dfsdm_adc *adc = arg;
- struct regmap *regmap = adc->dfsdm->regmap;
- unsigned int status;
- regmap_read(regmap, DFSDM_ISR(adc->fl_id), &status);
- 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) {
What's this one? Might want a comment given it's an irq you basically eat.
Yes at least an error message that to inform on an overrun.
regmap_update_bits(regmap, DFSDM_ICR(adc->fl_id),
DFSDM_ICR_CLRROVRF_MASK,
DFSDM_ICR_CLRROVRF_MASK);
- }
- return IRQ_HANDLED;
+}
+static int stm32_dfsdm_postenable(struct iio_dev *indio_dev) +{
- struct stm32_dfsdm_adc *adc = iio_priv(indio_dev);
- return stm32_dfsdm_start_conv(adc);
+}
+static int stm32_dfsdm_predisable(struct iio_dev *indio_dev) +{
- struct stm32_dfsdm_adc *adc = iio_priv(indio_dev);
- stm32_dfsdm_stop_conv(adc);
blank line.
- return 0;
+}
+static const struct iio_buffer_setup_ops stm32_dfsdm_buffer_setup_ops = {
- .postenable = &stm32_dfsdm_postenable,
- .predisable = &stm32_dfsdm_predisable,
+};
+static int stm32_dfsdm_adc_chan_init_one(struct iio_dev *indio_dev,
struct iio_chan_spec *chan,
int ch_idx)
+{
- struct iio_chan_spec *ch = &chan[ch_idx];
- struct stm32_dfsdm_adc *adc = iio_priv(indio_dev);
- int ret;
- ret = stm32_dfsdm_channel_parse_of(adc->dfsdm, indio_dev, chan, ch_idx);
- ch->type = IIO_VOLTAGE;
- ch->indexed = 1;
- ch->scan_index = ch_idx;
- /*
* 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) |
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_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->dfsdm->num_chs) {
dev_err(&indio_dev->dev, "Bad st,adc-channels?\n");
return num_ch < 0 ? num_ch : -EINVAL;
- }
- /*
* Number of channel per filter is temporary limited to 1.
* Restriction should be cleaned with scan mode
*/
- if (num_ch > 1) {
dev_err(&indio_dev->dev, "Multi channel not yet supported\n");
return -EINVAL;
- }
- /* Bind to SD modulator IIO device */
- adc->hwc = iio_hw_consumer_alloc(&indio_dev->dev);
- if (IS_ERR(adc->hwc))
return -EPROBE_DEFER;
- channels = devm_kcalloc(&indio_dev->dev, num_ch, sizeof(*channels),
GFP_KERNEL);
- if (!channels)
return -ENOMEM;
- 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:
- iio_hw_consumer_free(adc->hwc);
Given you have to free this in the error path, I would imagine you will need a free somewhere in the main remove path? Or just create a devm version of iio_hw_consumer_alloc. It will be useful in the long run.
- return ret;
+}
+static const struct of_device_id stm32_dfsdm_adc_match[] = {
- { .compatible = "st,stm32-dfsdm-adc"},
- {}
+};
+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;
- struct iio_dev *iio;
- char *name;
- int ret, irq, val;
- 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->info = &stm32_dfsdm_info_adc;
- 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 = kzalloc(sizeof("dfsdm-adc0"), GFP_KERNEL);
not freed. Maybe devm_kzalloc
- if (!name)
return -ENOMEM;
- 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;
- ret = stm32_dfsdm_adc_chan_init(iio);
- if (ret < 0)
return ret;
- init_completion(&adc->completion);
- 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 *iio = iio_priv_to_dev(adc);
- iio_device_unregister(iio);
If all you have is this in remove, you can probably get away with devm_iio_device_register and get rid of the remove entirely.
- 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,
+}; +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"); diff --git a/drivers/iio/adc/stm32-dfsdm-core.c b/drivers/iio/adc/stm32-dfsdm-core.c new file mode 100644 index 0000000..488e456 --- /dev/null +++ b/drivers/iio/adc/stm32-dfsdm-core.c @@ -0,0 +1,658 @@ +/*
- This file is part the core part STM32 DFSDM driver
- Copyright (C) 2017, STMicroelectronics - All Rights Reserved
- Author(s): Arnaud Pouliquen arnaud.pouliquen@st.com for STMicroelectronics.
- 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.
- */
+#include <linux/clk.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/regmap.h> +#include <linux/slab.h>
+#include <linux/iio/trigger.h> +#include <linux/iio/sysfs.h>
+#include "stm32-dfsdm.h"
+struct stm32_dfsdm_dev_data {
- unsigned int num_filters;
- unsigned int num_channels;
- const struct regmap_config *regmap_cfg;
+};
+#define STM32H7_DFSDM_NUM_FILTERS 4 +#define STM32H7_DFSDM_NUM_CHANNELS 8
+#define DFSDM_MAX_INT_OVERSAMPLING 256
+#define DFSDM_MAX_FL_OVERSAMPLING 1024
+#define DFSDM_MAX_RES BIT(31) +#define DFSDM_DATA_RES BIT(23)
+static bool stm32_dfsdm_volatile_reg(struct device *dev, unsigned int reg) +{
- if (reg < DFSDM_FILTER_BASE_ADR)
return false;
- /*
* Mask is done on register to avoid to list registers of all them
* filter instances.
*/
- switch (reg & DFSDM_FILTER_REG_MASK) {
- case DFSDM_CR1(0) & DFSDM_FILTER_REG_MASK:
- case DFSDM_ISR(0) & DFSDM_FILTER_REG_MASK:
- case DFSDM_JDATAR(0) & DFSDM_FILTER_REG_MASK:
- case DFSDM_RDATAR(0) & DFSDM_FILTER_REG_MASK:
return true;
- }
- return false;
+}
+static const struct regmap_config stm32h7_dfsdm_regmap_cfg = {
- .reg_bits = 32,
- .val_bits = 32,
- .reg_stride = sizeof(u32),
- .max_register = 0x2B8,
- .volatile_reg = stm32_dfsdm_volatile_reg,
- .fast_io = true,
+};
+static const struct stm32_dfsdm_dev_data stm32h7_dfsdm_data = {
- .num_filters = STM32H7_DFSDM_NUM_FILTERS,
- .num_channels = STM32H7_DFSDM_NUM_CHANNELS,
- .regmap_cfg = &stm32h7_dfsdm_regmap_cfg,
+};
+struct dfsdm_priv {
- struct platform_device *pdev; /* platform device*/
- struct stm32_dfsdm dfsdm; /* common data exported for all instances */
- unsigned int spi_clk_out_div; /* SPI clkout divider value */
- atomic_t n_active_ch; /* number of current active channels */
- /* Clock */
- struct clk *clk; /* DFSDM clock */
- struct clk *aclk; /* audio clock */
+};
+/**
- stm32_dfsdm_set_osrs - compute filter parameters.
Naming would suggest it's more specific than this. Setting over sampling ratios?
Right, it is a computation not a set.
- Enable interface if n_active_ch is not null.
- @dfsdm: Handle used to retrieve dfsdm context.
- @fast: Fast mode enabled or disabled
- @oversamp: Expected oversampling between filtered sample and SD input stream
- */
+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;
- }
- /*
* Looks for filter and integrator oversampling ratios which allows
* to reach 24 bits data output resolution.
* Leave at once 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;
+}
+/**
- stm32_dfsdm_start_dfsdm - start global dfsdm IP interface.
- Enable interface if n_active_ch is not null.
- @dfsdm: Handle used to retrieve dfsdm context.
- */
+int stm32_dfsdm_start_dfsdm(struct stm32_dfsdm *dfsdm) +{
- struct dfsdm_priv *priv = container_of(dfsdm, struct dfsdm_priv, dfsdm);
- struct device *dev = &priv->pdev->dev;
- unsigned int clk_div = priv->spi_clk_out_div;
- int ret;
- if (atomic_inc_return(&priv->n_active_ch) == 1) {
/* Enable clocks */
ret = clk_prepare_enable(priv->clk);
if (ret < 0) {
dev_err(dev, "Failed to start clock\n");
return ret;
}
if (priv->aclk) {
ret = clk_prepare_enable(priv->aclk);
if (ret < 0) {
dev_err(dev, "Failed to start audio clock\n");
goto disable_clk;
}
}
/* Output the SPI CLKOUT (if clk_div == 0 clock if OFF) */
ret = regmap_update_bits(dfsdm->regmap, DFSDM_CHCFGR1(0),
DFSDM_CHCFGR1_CKOUTDIV_MASK,
DFSDM_CHCFGR1_CKOUTDIV(clk_div));
if (ret < 0)
goto disable_aclk;
/* Global enable of DFSDM interface */
ret = regmap_update_bits(dfsdm->regmap, DFSDM_CHCFGR1(0),
DFSDM_CHCFGR1_DFSDMEN_MASK,
DFSDM_CHCFGR1_DFSDMEN(1));
if (ret < 0)
goto disable_aclk;
- }
- dev_dbg(dev, "%s: n_active_ch %d\n", __func__,
atomic_read(&priv->n_active_ch));
- return 0;
+disable_aclk:
- clk_disable_unprepare(priv->aclk);
+disable_clk:
- clk_disable_unprepare(priv->clk);
- return ret;
+}
+/**
- stm32_dfsdm_stop_dfsdm - stop global DFSDM IP interface.
- Disable interface if n_active_ch is null
- @dfsdm: Handle used to retrieve dfsdm context.
- */
+int stm32_dfsdm_stop_dfsdm(struct stm32_dfsdm *dfsdm) +{
- struct dfsdm_priv *priv = container_of(dfsdm, struct dfsdm_priv, dfsdm);
- int ret;
- if (atomic_dec_and_test(&priv->n_active_ch)) {
/* Global disable of DFSDM interface */
ret = regmap_update_bits(dfsdm->regmap, DFSDM_CHCFGR1(0),
DFSDM_CHCFGR1_DFSDMEN_MASK,
DFSDM_CHCFGR1_DFSDMEN(0));
if (ret < 0)
return ret;
/* Stop SPI CLKOUT */
ret = regmap_update_bits(dfsdm->regmap, DFSDM_CHCFGR1(0),
DFSDM_CHCFGR1_CKOUTDIV_MASK,
DFSDM_CHCFGR1_CKOUTDIV(0));
if (ret < 0)
return ret;
/* Disable clocks */
clk_disable_unprepare(priv->clk);
if (priv->aclk)
clk_disable_unprepare(priv->aclk);
- }
- dev_dbg(&priv->pdev->dev, "%s: n_active_ch %d\n", __func__,
atomic_read(&priv->n_active_ch));
- return 0;
+}
+/**
- stm32_dfsdm_start_channel
- Start DFSDM IP channels and associated interface.
- @dfsdm: Handle used to retrieve dfsdm context.
- @ch_id: Channel index.
- */
+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));
+}
+/**
- stm32_dfsdm_stop_channel
- Stop DFSDM IP channels and associated interface.
- @dfsdm: Handle used to retrieve dfsdm context.
- @ch_id: Channel index.
- */
+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));
+}
+/**
- stm32_dfsdm_chan_configure
- Configure DFSDM IP channels and associated interface.
- @dfsdm: Handle used to retrieve dfsdm context.
- @ch_id: channel index.
- */
+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;
Blank line here and in similar places makes it easier for my eyes to parse at least... I'd also like to see some docs in here, not all of these are self explanatory.
I will apply recommendation in my whole code for next time
- 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));
+}
+/**
- stm32_dfsdm_start_filter - Start DFSDM IP filter conversion.
- @dfsdm: Handle used to retrieve dfsdm context.
- @fl_id: Filter index.
- */
+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));
+}
+/**
- stm32_dfsdm_stop_filter - Stop DFSDM IP filter conversion.
- @dfsdm: Handle used to retrieve dfsdm context.
- @fl_id: Filter index.
- */
+void stm32_dfsdm_stop_filter(struct stm32_dfsdm *dfsdm, unsigned int fl_id) +{
- /* Mask IRQ for regular conversion achievement*/
- regmap_update_bits(dfsdm->regmap, DFSDM_CR2(fl_id),
DFSDM_CR2_REOCIE_MASK, DFSDM_CR2_REOCIE(0));
- /* Disable conversion */
- regmap_update_bits(dfsdm->regmap, DFSDM_CR1(fl_id),
DFSDM_CR1_DFEN_MASK, DFSDM_CR1_DFEN(0));
+}
+/**
- stm32_dfsdm_filter_configure - Configure DFSDM IP filter and associate it
- to channel.
- @dfsdm: Handle used to retrieve dfsdm context.
- @fl_id: channel index.
- @fl_id: Filter index.
- */
+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));
- /* Filter order and Oversampling */
Please handle each error properly as it happens rather than mudling onwards. If there is reason for this odd construction, then document it clearly.
If you mention the checks on ret value that are missing at end of functions, yes dirty code to fix.
- if (!ret)
ret = regmap_update_bits(regmap, DFSDM_FCR(fl_id),
DFSDM_FCR_FOSR_MASK,
DFSDM_FCR_FOSR(fl->fosr));
- if (!ret)
ret = regmap_update_bits(regmap, DFSDM_FCR(fl_id),
DFSDM_FCR_FORD_MASK,
DFSDM_FCR_FORD(fl->ford));
- /* If only one channel 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));
- return regmap_update_bits(regmap, DFSDM_CR1(fl_id),
DFSDM_CR1_RSYNC_MASK,
DFSDM_CR1_RSYNC(fl->sync_mode));
+}
+static const struct iio_trigger_ops dfsdm_trigger_ops = {
- .owner = THIS_MODULE,
+};
+static int stm32_dfsdm_setup_spi_trigger(struct platform_device *pdev,
struct stm32_dfsdm *dfsdm)
+{
- /*
* To be able to use buffer consumer interface a trigger is needed.
* As conversion are trigged by PDM samples from SPI bus, that makes
* sense to define the serial interface ( SPI or manchester) as
* trigger source.
It's not actually the case that you have to have a triggrer. There are plenty of drivers (particularly ones with hardware buffering) where there is no trigger envolved. That's not to say it doesn't make sense here.
I'm not entirely sure yet if it's needed... Given it has no ops, I doubt it somewhat...
*/
- struct iio_trigger *trig;
- int ret;
- trig = devm_iio_trigger_alloc(&pdev->dev, DFSDM_SPI_TRIGGER_NAME);
- if (!trig)
return -ENOMEM;
- trig->dev.parent = pdev->dev.parent;
- trig->ops = &dfsdm_trigger_ops;
- iio_trigger_set_drvdata(trig, dfsdm);
- ret = devm_iio_trigger_register(&pdev->dev, trig);
- if (ret)
return ret;
Just return ret in all cases.
- return 0;
+}
+int stm32_dfsdm_channel_parse_of(struct stm32_dfsdm *dfsdm,
struct iio_dev *indio_dev,
struct iio_chan_spec *chan, int chan_idx)
+{
- struct iio_chan_spec *ch = &chan[chan_idx];
- struct stm32_dfsdm_channel *df_ch;
- const char *of_str;
- 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;
- }
- 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];
Extra space on the line above.
- df_ch->id = ch->channel;
- ret = of_property_read_string_index(indio_dev->dev.of_node,
"st,adc-channel-types", chan_idx,
&of_str);
- val = stm32_dfsdm_str2val(of_str, stm32_dfsdm_chan_type);
- if (ret < 0 || val < 0)
df_ch->type = 0;
- else
df_ch->type = val;
- ret = of_property_read_string_index(indio_dev->dev.of_node,
"st,adc-channel-clk-src", chan_idx,
&of_str);
- val = stm32_dfsdm_str2val(of_str, stm32_dfsdm_chan_src);
- if (ret < 0 || val < 0)
df_ch->src = 0;
- else
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_parse_of(struct platform_device *pdev,
struct dfsdm_priv *priv)
+{
- struct device_node *node = pdev->dev.of_node;
- struct resource *res;
- unsigned long clk_freq;
- unsigned int spi_freq, rem;
- int ret;
- if (!node)
return -EINVAL;
- /* Get resources */
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- if (!res) {
dev_err(&pdev->dev, "Failed to get memory resource\n");
return -ENODEV;
- }
- priv->dfsdm.phys_base = res->start;
- priv->dfsdm.base = devm_ioremap_resource(&pdev->dev, res);
- /* Source clock */
- priv->clk = devm_clk_get(&pdev->dev, "dfsdm");
- if (IS_ERR(priv->clk)) {
dev_err(&pdev->dev, "No stm32_dfsdm_clk clock found\n");
return -EINVAL;
- }
- priv->aclk = devm_clk_get(&pdev->dev, "audio");
- if (IS_ERR(priv->aclk))
priv->aclk = NULL;
- if (priv->aclk)
clk_freq = clk_get_rate(priv->aclk);
- else
clk_freq = clk_get_rate(priv->clk);
- /* SPI clock freq */
- ret = of_property_read_u32(pdev->dev.of_node, "spi-max-frequency",
&spi_freq);
- if (ret < 0) {
dev_err(&pdev->dev, "Failed to get spi-max-frequency\n");
return ret;
- }
- priv->spi_clk_out_div = div_u64_rem(clk_freq, spi_freq, &rem) - 1;
- priv->dfsdm.spi_master_freq = spi_freq;
- if (rem) {
dev_warn(&pdev->dev, "SPI clock not accurate\n");
dev_warn(&pdev->dev, "%ld = %d * %d + %d\n",
clk_freq, spi_freq, priv->spi_clk_out_div + 1, rem);
- }
- return 0;
+};
+static const struct of_device_id stm32_dfsdm_of_match[] = {
- {
.compatible = "st,stm32h7-dfsdm",
.data = &stm32h7_dfsdm_data,
- },
- {}
+}; +MODULE_DEVICE_TABLE(of, stm32_dfsdm_of_match);
+static int stm32_dfsdm_remove(struct platform_device *pdev) +{
- of_platform_depopulate(&pdev->dev);
- return 0;
+}
+static int stm32_dfsdm_probe(struct platform_device *pdev) +{
- struct dfsdm_priv *priv;
- struct device_node *pnode = pdev->dev.of_node;
- const struct of_device_id *of_id;
- const struct stm32_dfsdm_dev_data *dev_data;
- struct stm32_dfsdm *dfsdm;
- int ret, i;
- priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
- if (!priv)
return -ENOMEM;
- priv->pdev = pdev;
- /* Populate data structure depending on compatibility */
- of_id = of_match_node(stm32_dfsdm_of_match, pnode);
- 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;
- dfsdm = &priv->dfsdm;
- dfsdm->fl_list = devm_kcalloc(&pdev->dev, dev_data->num_filters,
sizeof(*dfsdm->fl_list), GFP_KERNEL);
- if (!dfsdm->fl_list)
return -ENOMEM;
- dfsdm->num_fls = dev_data->num_filters;
- dfsdm->ch_list = devm_kcalloc(&pdev->dev, dev_data->num_channels,
sizeof(*dfsdm->ch_list),
GFP_KERNEL);
- if (!dfsdm->ch_list)
return -ENOMEM;
- dfsdm->num_chs = dev_data->num_channels;
- ret = stm32_dfsdm_parse_of(pdev, priv);
- if (ret < 0)
return ret;
- dfsdm->regmap = devm_regmap_init_mmio(&pdev->dev, dfsdm->base,
&stm32h7_dfsdm_regmap_cfg);
- if (IS_ERR(dfsdm->regmap)) {
ret = PTR_ERR(dfsdm->regmap);
dev_err(&pdev->dev, "%s: Failed to allocate regmap: %d\n",
__func__, ret);
return ret;
- }
- for (i = 0; i < STM32H7_DFSDM_NUM_FILTERS; i++) {
struct stm32_dfsdm_filter *fl = &dfsdm->fl_list[i];
fl->id = i;
I'd like a comment on why this is needed...
to be cleaned.
- }
- platform_set_drvdata(pdev, dfsdm);
- ret = stm32_dfsdm_setup_spi_trigger(pdev, dfsdm);
- if (ret < 0)
return ret;
- return of_platform_populate(pnode, NULL, NULL, &pdev->dev);
+}
+static struct platform_driver stm32_dfsdm_driver = {
- .probe = stm32_dfsdm_probe,
- .remove = stm32_dfsdm_remove,
- .driver = {
.name = "stm32-dfsdm",
.of_match_table = stm32_dfsdm_of_match,
- },
+};
+module_platform_driver(stm32_dfsdm_driver);
+MODULE_AUTHOR("Arnaud Pouliquen arnaud.pouliquen@st.com"); +MODULE_DESCRIPTION("STMicroelectronics STM32 dfsdm driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/stm32-dfsdm.h b/drivers/iio/adc/stm32-dfsdm.h new file mode 100644 index 0000000..bb7d74f --- /dev/null +++ b/drivers/iio/adc/stm32-dfsdm.h @@ -0,0 +1,371 @@ +/*
- This file is part of STM32 DFSDM driver
- Copyright (C) 2017, STMicroelectronics - All Rights Reserved
- Author(s): Arnaud Pouliquen arnaud.pouliquen@st.com.
- License terms: GPL V2.0.
- This program is free software; you can redistribute it and/or modify it
- under the terms of the GNU General Public License version 2 as published by
- the Free Software Foundation.
- This program is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
- details.
- */
+#ifndef MDF_STM32_DFSDM__H +#define MDF_STM32_DFSDM__H
+#include <linux/bitfield.h>
+#include <linux/iio/iio.h> +/*
- STM32 DFSDM - global register map
- | Offset | Registers block |
- | 0x000 | CHANNEL 0 + COMMON CHANNEL FIELDS |
- | 0x020 | CHANNEL 1 |
- | ... | ..... |
- | 0x0E0 | CHANNEL 7 |
- | 0x100 | FILTER 0 + COMMON FILTER FIELDs |
- | 0x200 | FILTER 1 |
- | 0x300 | FILTER 2 |
- | 0x400 | FILTER 3 |
- */
+/*
- Channels register definitions
- */
+#define DFSDM_CHCFGR1(y) ((y) * 0x20 + 0x00) +#define DFSDM_CHCFGR2(y) ((y) * 0x20 + 0x04) +#define DFSDM_AWSCDR(y) ((y) * 0x20 + 0x08) +#define DFSDM_CHWDATR(y) ((y) * 0x20 + 0x0C) +#define DFSDM_CHDATINR(y) ((y) * 0x20 + 0x10)
+/* CHCFGR1: Channel configuration register 1 */ +#define DFSDM_CHCFGR1_SITP_MASK GENMASK(1, 0) +#define DFSDM_CHCFGR1_SITP(v) FIELD_PREP(DFSDM_CHCFGR1_SITP_MASK, v) +#define DFSDM_CHCFGR1_SPICKSEL_MASK GENMASK(3, 2) +#define DFSDM_CHCFGR1_SPICKSEL(v) FIELD_PREP(DFSDM_CHCFGR1_SPICKSEL_MASK, v) +#define DFSDM_CHCFGR1_SCDEN_MASK BIT(5) +#define DFSDM_CHCFGR1_SCDEN(v) FIELD_PREP(DFSDM_CHCFGR1_SCDEN_MASK, v) +#define DFSDM_CHCFGR1_CKABEN_MASK BIT(6) +#define DFSDM_CHCFGR1_CKABEN(v) FIELD_PREP(DFSDM_CHCFGR1_CKABEN_MASK, v) +#define DFSDM_CHCFGR1_CHEN_MASK BIT(7) +#define DFSDM_CHCFGR1_CHEN(v) FIELD_PREP(DFSDM_CHCFGR1_CHEN_MASK, v) +#define DFSDM_CHCFGR1_CHINSEL_MASK BIT(8) +#define DFSDM_CHCFGR1_CHINSEL(v) FIELD_PREP(DFSDM_CHCFGR1_CHINSEL_MASK, v) +#define DFSDM_CHCFGR1_DATMPX_MASK GENMASK(13, 12) +#define DFSDM_CHCFGR1_DATMPX(v) FIELD_PREP(DFSDM_CHCFGR1_DATMPX_MASK, v) +#define DFSDM_CHCFGR1_DATPACK_MASK GENMASK(15, 14) +#define DFSDM_CHCFGR1_DATPACK(v) FIELD_PREP(DFSDM_CHCFGR1_DATPACK_MASK, v) +#define DFSDM_CHCFGR1_CKOUTDIV_MASK GENMASK(23, 16) +#define DFSDM_CHCFGR1_CKOUTDIV(v) FIELD_PREP(DFSDM_CHCFGR1_CKOUTDIV_MASK, v) +#define DFSDM_CHCFGR1_CKOUTSRC_MASK BIT(30) +#define DFSDM_CHCFGR1_CKOUTSRC(v) FIELD_PREP(DFSDM_CHCFGR1_CKOUTSRC_MASK, v) +#define DFSDM_CHCFGR1_DFSDMEN_MASK BIT(31) +#define DFSDM_CHCFGR1_DFSDMEN(v) FIELD_PREP(DFSDM_CHCFGR1_DFSDMEN_MASK, v)
+/* CHCFGR2: Channel configuration register 2 */ +#define DFSDM_CHCFGR2_DTRBS_MASK GENMASK(7, 3) +#define DFSDM_CHCFGR2_DTRBS(v) FIELD_PREP(DFSDM_CHCFGR2_DTRBS_MASK, v) +#define DFSDM_CHCFGR2_OFFSET_MASK GENMASK(31, 8) +#define DFSDM_CHCFGR2_OFFSET(v) FIELD_PREP(DFSDM_CHCFGR2_OFFSET_MASK, v)
+/* AWSCDR: Channel analog watchdog and short circuit detector */ +#define DFSDM_AWSCDR_SCDT_MASK GENMASK(7, 0) +#define DFSDM_AWSCDR_SCDT(v) FIELD_PREP(DFSDM_AWSCDR_SCDT_MASK, v) +#define DFSDM_AWSCDR_BKSCD_MASK GENMASK(15, 12) +#define DFSDM_AWSCDR_BKSCD(v) FIELD_PREP(DFSDM_AWSCDR_BKSCD_MASK, v) +#define DFSDM_AWSCDR_AWFOSR_MASK GENMASK(20, 16) +#define DFSDM_AWSCDR_AWFOSR(v) FIELD_PREP(DFSDM_AWSCDR_AWFOSR_MASK, v) +#define DFSDM_AWSCDR_AWFORD_MASK GENMASK(23, 22) +#define DFSDM_AWSCDR_AWFORD(v) FIELD_PREP(DFSDM_AWSCDR_AWFORD_MASK, v)
+/*
- Filters register definitions
- */
+#define DFSDM_FILTER_BASE_ADR 0x100 +#define DFSDM_FILTER_REG_MASK 0x7F +#define DFSDM_FILTER_X_BASE_ADR(x) ((x) * 0x80 + DFSDM_FILTER_BASE_ADR)
+#define DFSDM_CR1(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x00) +#define DFSDM_CR2(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x04) +#define DFSDM_ISR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x08) +#define DFSDM_ICR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x0C) +#define DFSDM_JCHGR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x10) +#define DFSDM_FCR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x14) +#define DFSDM_JDATAR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x18) +#define DFSDM_RDATAR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x1C) +#define DFSDM_AWHTR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x20) +#define DFSDM_AWLTR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x24) +#define DFSDM_AWSR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x28) +#define DFSDM_AWCFR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x2C) +#define DFSDM_EXMAX(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x30) +#define DFSDM_EXMIN(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x34) +#define DFSDM_CNVTIMR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x38)
+/* CR1 Control register 1 */ +#define DFSDM_CR1_DFEN_MASK BIT(0) +#define DFSDM_CR1_DFEN(v) FIELD_PREP(DFSDM_CR1_DFEN_MASK, v) +#define DFSDM_CR1_JSWSTART_MASK BIT(1) +#define DFSDM_CR1_JSWSTART(v) FIELD_PREP(DFSDM_CR1_JSWSTART_MASK, v) +#define DFSDM_CR1_JSYNC_MASK BIT(3) +#define DFSDM_CR1_JSYNC(v) FIELD_PREP(DFSDM_CR1_JSYNC_MASK, v) +#define DFSDM_CR1_JSCAN_MASK BIT(4) +#define DFSDM_CR1_JSCAN(v) FIELD_PREP(DFSDM_CR1_JSCAN_MASK, v) +#define DFSDM_CR1_JDMAEN_MASK BIT(5) +#define DFSDM_CR1_JDMAEN(v) FIELD_PREP(DFSDM_CR1_JDMAEN_MASK, v) +#define DFSDM_CR1_JEXTSEL_MASK GENMASK(12, 8) +#define DFSDM_CR1_JEXTSEL(v) FIELD_PREP(DFSDM_CR1_JEXTSEL_MASK, v) +#define DFSDM_CR1_JEXTEN_MASK GENMASK(14, 13) +#define DFSDM_CR1_JEXTEN(v) FIELD_PREP(DFSDM_CR1_JEXTEN_MASK, v) +#define DFSDM_CR1_RSWSTART_MASK BIT(17) +#define DFSDM_CR1_RSWSTART(v) FIELD_PREP(DFSDM_CR1_RSWSTART_MASK, v) +#define DFSDM_CR1_RCONT_MASK BIT(18) +#define DFSDM_CR1_RCONT(v) FIELD_PREP(DFSDM_CR1_RCONT_MASK, v) +#define DFSDM_CR1_RSYNC_MASK BIT(19) +#define DFSDM_CR1_RSYNC(v) FIELD_PREP(DFSDM_CR1_RSYNC_MASK, v) +#define DFSDM_CR1_RDMAEN_MASK BIT(21) +#define DFSDM_CR1_RDMAEN(v) FIELD_PREP(DFSDM_CR1_RDMAEN_MASK, v) +#define DFSDM_CR1_RCH_MASK GENMASK(26, 24) +#define DFSDM_CR1_RCH(v) FIELD_PREP(DFSDM_CR1_RCH_MASK, v) +#define DFSDM_CR1_FAST_MASK BIT(29) +#define DFSDM_CR1_FAST(v) FIELD_PREP(DFSDM_CR1_FAST_MASK, v) +#define DFSDM_CR1_AWFSEL_MASK BIT(30) +#define DFSDM_CR1_AWFSEL(v) FIELD_PREP(DFSDM_CR1_AWFSEL_MASK, v)
+/* CR2: Control register 2 */ +#define DFSDM_CR2_IE_MASK GENMASK(6, 0) +#define DFSDM_CR2_IE(v) FIELD_PREP(DFSDM_CR2_IE_MASK, v) +#define DFSDM_CR2_JEOCIE_MASK BIT(0) +#define DFSDM_CR2_JEOCIE(v) FIELD_PREP(DFSDM_CR2_JEOCIE_MASK, v) +#define DFSDM_CR2_REOCIE_MASK BIT(1) +#define DFSDM_CR2_REOCIE(v) FIELD_PREP(DFSDM_CR2_REOCIE_MASK, v) +#define DFSDM_CR2_JOVRIE_MASK BIT(2) +#define DFSDM_CR2_JOVRIE(v) FIELD_PREP(DFSDM_CR2_JOVRIE_MASK, v) +#define DFSDM_CR2_ROVRIE_MASK BIT(3) +#define DFSDM_CR2_ROVRIE(v) FIELD_PREP(DFSDM_CR2_ROVRIE_MASK, v) +#define DFSDM_CR2_AWDIE_MASK BIT(4) +#define DFSDM_CR2_AWDIE(v) FIELD_PREP(DFSDM_CR2_AWDIE_MASK, v) +#define DFSDM_CR2_SCDIE_MASK BIT(5) +#define DFSDM_CR2_SCDIE(v) FIELD_PREP(DFSDM_CR2_SCDIE_MASK, v) +#define DFSDM_CR2_CKABIE_MASK BIT(6) +#define DFSDM_CR2_CKABIE(v) FIELD_PREP(DFSDM_CR2_CKABIE_MASK, v) +#define DFSDM_CR2_EXCH_MASK GENMASK(15, 8) +#define DFSDM_CR2_EXCH(v) FIELD_PREP(DFSDM_CR2_EXCH_MASK, v) +#define DFSDM_CR2_AWDCH_MASK GENMASK(23, 16) +#define DFSDM_CR2_AWDCH(v) FIELD_PREP(DFSDM_CR2_AWDCH_MASK, v)
+/* ISR: Interrupt status register */ +#define DFSDM_ISR_JEOCF_MASK BIT(0) +#define DFSDM_ISR_JEOCF(v) FIELD_PREP(DFSDM_ISR_JEOCF_MASK, v) +#define DFSDM_ISR_REOCF_MASK BIT(1) +#define DFSDM_ISR_REOCF(v) FIELD_PREP(DFSDM_ISR_REOCF_MASK, v) +#define DFSDM_ISR_JOVRF_MASK BIT(2) +#define DFSDM_ISR_JOVRF(v) FIELD_PREP(DFSDM_ISR_JOVRF_MASK, v) +#define DFSDM_ISR_ROVRF_MASK BIT(3) +#define DFSDM_ISR_ROVRF(v) FIELD_PREP(DFSDM_ISR_ROVRF_MASK, v) +#define DFSDM_ISR_AWDF_MASK BIT(4) +#define DFSDM_ISR_AWDF(v) FIELD_PREP(DFSDM_ISR_AWDF_MASK, v) +#define DFSDM_ISR_JCIP_MASK BIT(13) +#define DFSDM_ISR_JCIP(v) FIELD_PREP(DFSDM_ISR_JCIP_MASK, v) +#define DFSDM_ISR_RCIP_MASK BIT(14) +#define DFSDM_ISR_RCIP(v) FIELD_PREP(DFSDM_ISR_RCIP, v) +#define DFSDM_ISR_CKABF_MASK GENMASK(23, 16) +#define DFSDM_ISR_CKABF(v) FIELD_PREP(DFSDM_ISR_CKABF_MASK, v) +#define DFSDM_ISR_SCDF_MASK GENMASK(31, 24) +#define DFSDM_ISR_SCDF(v) FIELD_PREP(DFSDM_ISR_SCDF_MASK, v)
+/* ICR: Interrupt flag clear register */ +#define DFSDM_ICR_CLRJOVRF_MASK BIT(2) +#define DFSDM_ICR_CLRJOVRF(v) FIELD_PREP(DFSDM_ICR_CLRJOVRF_MASK, v) +#define DFSDM_ICR_CLRROVRF_MASK BIT(3) +#define DFSDM_ICR_CLRROVRF(v) FIELD_PREP(DFSDM_ICR_CLRROVRF_MASK, v) +#define DFSDM_ICR_CLRCKABF_MASK GENMASK(23, 16) +#define DFSDM_ICR_CLRCKABF(v) FIELD_PREP(DFSDM_ICR_CLRCKABF_MASK, v) +#define DFSDM_ICR_CLRCKABF_CH_MASK(y) BIT(16 + (y)) +#define DFSDM_ICR_CLRCKABF_CH(v, y) \
(((v) << (16 + (y))) & DFSDM_ICR_CLRCKABF_CH_MASK(y))
+#define DFSDM_ICR_CLRSCDF_MASK GENMASK(31, 24) +#define DFSDM_ICR_CLRSCDF(v) FIELD_PREP(DFSDM_ICR_CLRSCDF_MASK, v) +#define DFSDM_ICR_CLRSCDF_CH_MASK(y) BIT(24 + (y)) +#define DFSDM_ICR_CLRSCDF_CH(v, y) \
(((v) << (24 + (y))) & DFSDM_ICR_CLRSCDF_MASK(y))
+/* FCR: Filter control register */ +#define DFSDM_FCR_IOSR_MASK GENMASK(7, 0) +#define DFSDM_FCR_IOSR(v) FIELD_PREP(DFSDM_FCR_IOSR_MASK, v) +#define DFSDM_FCR_FOSR_MASK GENMASK(25, 16) +#define DFSDM_FCR_FOSR(v) FIELD_PREP(DFSDM_FCR_FOSR_MASK, v) +#define DFSDM_FCR_FORD_MASK GENMASK(31, 29) +#define DFSDM_FCR_FORD(v) FIELD_PREP(DFSDM_FCR_FORD_MASK, v)
+/* RDATAR: Filter data register for regular channel */ +#define DFSDM_DATAR_CH_MASK GENMASK(2, 0) +#define DFSDM_DATAR_DATA_OFFSET 8 +#define DFSDM_DATAR_DATA_MASK GENMASK(31, DFSDM_DATAR_DATA_OFFSET)
+/* AWLTR: Filter analog watchdog low threshold register */ +#define DFSDM_AWLTR_BKAWL_MASK GENMASK(3, 0) +#define DFSDM_AWLTR_BKAWL(v) FIELD_PREP(DFSDM_AWLTR_BKAWL_MASK, v) +#define DFSDM_AWLTR_AWLT_MASK GENMASK(31, 8) +#define DFSDM_AWLTR_AWLT(v) FIELD_PREP(DFSDM_AWLTR_AWLT_MASK, v)
+/* AWHTR: Filter analog watchdog low threshold register */ +#define DFSDM_AWHTR_BKAWH_MASK GENMASK(3, 0) +#define DFSDM_AWHTR_BKAWH(v) FIELD_PREP(DFSDM_AWHTR_BKAWH_MASK, v) +#define DFSDM_AWHTR_AWHT_MASK GENMASK(31, 8) +#define DFSDM_AWHTR_AWHT(v) FIELD_PREP(DFSDM_AWHTR_AWHT_MASK, v)
+/* AWSR: Filter watchdog status register */ +#define DFSDM_AWSR_AWLTF_MASK GENMASK(7, 0) +#define DFSDM_AWSR_AWLTF(v) FIELD_PREP(DFSDM_AWSR_AWLTF_MASK, v) +#define DFSDM_AWSR_AWHTF_MASK GENMASK(15, 8) +#define DFSDM_AWSR_AWHTF(v) FIELD_PREP(DFSDM_AWSR_AWHTF_MASK, v)
+/* AWCFR: Filter watchdog status register */ +#define DFSDM_AWCFR_AWLTF_MASK GENMASK(7, 0) +#define DFSDM_AWCFR_AWLTF(v) FIELD_PREP(DFSDM_AWCFR_AWLTF_MASK, v) +#define DFSDM_AWCFR_AWHTF_MASK GENMASK(15, 8) +#define DFSDM_AWCFR_AWHTF(v) FIELD_PREP(DFSDM_AWCFR_AWHTF_MASK, v)
+/* DFSDM filter order */ +enum stm32_dfsdm_sinc_order {
- DFSDM_FASTSINC_ORDER, /* FastSinc filter type */
- DFSDM_SINC1_ORDER, /* Sinc 1 filter type */
- DFSDM_SINC2_ORDER, /* Sinc 2 filter type */
- DFSDM_SINC3_ORDER, /* Sinc 3 filter type */
- DFSDM_SINC4_ORDER, /* Sinc 4 filter type (N.A. for watchdog) */
- DFSDM_SINC5_ORDER, /* Sinc 5 filter type (N.A. for watchdog) */
- DFSDM_NB_SINC_ORDER,
+};
+/**
- struct stm32_dfsdm_filter - structure relative to stm32 FDSDM filter
- TODO: complete structure.
nice :) RFC I guess :)
- @id: filetr ID,
- */
+struct stm32_dfsdm_filter {
- unsigned int id;
- unsigned int iosr; /* integrator oversampling */
- unsigned int fosr; /* filter oversampling */
- enum stm32_dfsdm_sinc_order ford;
- u64 res; /* output sample resolution */
- unsigned int sync_mode; /* filter suynchronized with filter0 */
- unsigned int fast; /* filter fast mode */
+};
+/**
- struct stm32_dfsdm_channel - structure relative to stm32 FDSDM channel
- TODO: complete structure.
- @id: filetr ID,
filter
- */
+struct stm32_dfsdm_channel {
- unsigned int id; /* id of the channel */
- unsigned int type; /* interface type linked to stm32_dfsdm_chan_type */
- unsigned int src; /* interface type linked to stm32_dfsdm_chan_src */
- unsigned int alt_si; /* use alternative serial input interface */
+};
+/**
- struct stm32_dfsdm - stm32 FDSDM driver common data (for all instances)
- @base: control registers base cpu addr
- @phys_base: DFSDM IP register physical address.
- @fl_list: filter resources list
- @num_fl: number of filter resources available
- @ch_list: channel resources list
- @num_chs: number of channel resources available
- */
+struct stm32_dfsdm {
- void __iomem *base;
- phys_addr_t phys_base;
- struct regmap *regmap;
- struct stm32_dfsdm_filter *fl_list;
- unsigned int num_fls;
- struct stm32_dfsdm_channel *ch_list;
- unsigned int num_chs;
- unsigned int spi_master_freq;
+};
+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 */
- { 0, 0},
+};
+/* DFSDM channel serial spi clock source */ +enum stm32_dfsdm_spi_clk_src {
- DFSDM_CHANNEL_SPI_CLOCK_EXTERNAL,
- DFSDM_CHANNEL_SPI_CLOCK_INTERNAL,
- DFSDM_CHANNEL_SPI_CLOCK_INTERNAL_DIV2_FALLING,
- DFSDM_CHANNEL_SPI_CLOCK_INTERNAL_DIV2_RISING
+};
+/* 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 },
- { 0, 0 },
+};
+/* DFSDM Serial interface trigger name */ +#define DFSDM_SPI_TRIGGER_NAME "DFSDM_SERIAL_IN"
+static inline 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;
+}
+int stm32_dfsdm_set_osrs(struct stm32_dfsdm_filter *fl, unsigned int fast,
unsigned int oversamp);
+int stm32_dfsdm_start_dfsdm(struct stm32_dfsdm *dfsdm); +int stm32_dfsdm_stop_dfsdm(struct stm32_dfsdm *dfsdm);
+int stm32_dfsdm_filter_configure(struct stm32_dfsdm *dfsdm, unsigned int fl_id,
unsigned int ch_id);
+int stm32_dfsdm_start_filter(struct stm32_dfsdm *dfsdm, unsigned int fl_id); +void stm32_dfsdm_stop_filter(struct stm32_dfsdm *dfsdm, unsigned int fl_id);
+int stm32_dfsdm_chan_configure(struct stm32_dfsdm *dfsdm,
struct stm32_dfsdm_channel *ch);
+int stm32_dfsdm_start_channel(struct stm32_dfsdm *dfsdm, unsigned int ch_id); +void stm32_dfsdm_stop_channel(struct stm32_dfsdm *dfsdm, unsigned int ch_id);
+int stm32_dfsdm_channel_parse_of(struct stm32_dfsdm *dfsdm,
struct iio_dev *indio_dev,
struct iio_chan_spec *chan, int chan_idx);
+#endif
-- To unsubscribe from this list: send the line "unsubscribe linux-iio" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
-- To unsubscribe from this list: send the line "unsubscribe linux-iio" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
On 20/03/17 11:29, Arnaud Pouliquen wrote:
Please find my comments in-line
Thanks and Regards, Arnaud
On 03/19/2017 11:38 PM, Jonathan Cameron wrote:
On 17/03/17 14:08, Arnaud Pouliquen wrote:
Add DFSDM driver to handle PDM audio microphones.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com
So key element here is that we really need to have a proper way of doing DMA buffer consumers from IIO (as you said in your cover letter).
Not entirely obvious how to do this. Would like some input from Lars if possible. I'm afraid I don't have an suitable hardware to try anything out on (or the time really!). This will obviously be quite different from the single scan stuff you have seen is there at the moment.
I saw 2 other drivers ti_am335x_adc.c and stm32_adc.c) that use cyclic DMA. I suppose that problematics are similar. Perhaps the extra constrains in DFSDM is the in-kernel API used to get the data.
Absolutely. Cyclic dma is becoming more and more common on SoC ADCs. There are a few more fpga based ones in Analog devices tree as well - most of those actually do the dma buffer route rather than pushing through the normal buffer interface (kfifo effectively). Right now we just don't have any means of accessing these dma buffers in kernel.
Unlike the normal consumer interface, I doubt we'll want to do demuxing of the stream for this sort of usecase - feels like exclusive use is more likely.
Whether this whole approach makes sense vs doing the dma in the alsa driver isn't clear to me.
On the plus side, presumably we'll get love dma ADC support for free as part of doing it this way ;)
It's been a while since I looked at the IIO dma buffer stuff at all. Will try and refresh my memory sometime this week.
Ok, i will wait your feedback (and Lars's one) before updating my code for next version.
It may well be the case that it will take us sometime (when I say us, I mean you and Lars ;) to pin down how to do dma buffer consumers, so it may be a case of taking the driver in a state close to the current one with the intent to switch to a generic way of handling it later.
As long as bindings and userspace don't change we can of course mess with anything we like.
Jonathan
Jonathan
drivers/iio/adc/Kconfig | 13 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/stm32-dfsdm-audio.c | 720 ++++++++++++++++++++++++++++++ include/linux/iio/adc/stm32-dfsdm-audio.h | 41 ++ 4 files changed, 775 insertions(+) create mode 100644 drivers/iio/adc/stm32-dfsdm-audio.c create mode 100644 include/linux/iio/adc/stm32-dfsdm-audio.h
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 3e0eb11..c108933 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -478,6 +478,19 @@ config STM32_DFSDM_ADC This driver can also be built as a module. If so, the module will be called stm32-dfsdm-adc.
+config STM32_DFSDM_AUDIO
- tristate "STMicroelectronics STM32 dfsdm audio
- depends on (ARCH_STM32 && OF) || COMPILE_TEST
- select STM32_DFSDM_CORE
- select REGMAP_MMIO
- select IIO_BUFFER_DMAENGINE
- help
Select this option to support Audio PDM micophone 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-audio.
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 161f271..79f975d 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -44,6 +44,7 @@ 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 +obj-$(CONFIG_STM32_DFSDM_AUDIO) += stm32-dfsdm-audio.o obj-$(CONFIG_STM32_DFSDM_CORE) += stm32-dfsdm-core.o obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o obj-$(CONFIG_TI_ADC0832) += ti-adc0832.o diff --git a/drivers/iio/adc/stm32-dfsdm-audio.c b/drivers/iio/adc/stm32-dfsdm-audio.c new file mode 100644 index 0000000..115ef8f --- /dev/null +++ b/drivers/iio/adc/stm32-dfsdm-audio.c @@ -0,0 +1,720 @@ +/*
- This file is the ADC part of of the STM32 DFSDM 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/dmaengine.h> +#include <linux/dma-mapping.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/slab.h>
+#include <linux/iio/buffer.h> +#include <linux/iio/hw_consumer.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/trigger.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h>
+#include "stm32-dfsdm.h"
+#define DFSDM_DMA_BUFFER_SIZE (4 * PAGE_SIZE)
+struct stm32_dfsdm_audio {
- struct stm32_dfsdm *dfsdm;
- unsigned int fl_id;
- unsigned int ch_id;
- unsigned int spi_freq; /* SPI bus clock frequency */
- unsigned int sample_freq; /* Sample frequency after filter decimation */
- u8 *rx_buf;
- unsigned int bufi; /* Buffer current position */
- unsigned int buf_sz; /* Buffer size */
- struct dma_chan *dma_chan;
- dma_addr_t dma_buf;
- int (*cb)(const void *data, size_t size, void *cb_priv);
- void *cb_priv;
+};
+const char *stm32_dfsdm_spi_trigger = DFSDM_SPI_TRIGGER_NAME;
+static ssize_t dfsdm_audio_get_rate(struct iio_dev *indio_dev, uintptr_t priv,
const struct iio_chan_spec *chan, char *buf)
+{
- struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev);
- return snprintf(buf, PAGE_SIZE, "%d\n", pdmc->sample_freq);
+}
+static ssize_t dfsdm_audio_set_rate(struct iio_dev *indio_dev, uintptr_t priv,
const struct iio_chan_spec *chan,
const char *buf, size_t len)
+{
- struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev);
- struct stm32_dfsdm_filter *fl = &pdmc->dfsdm->fl_list[pdmc->fl_id];
- struct stm32_dfsdm_channel *ch = &pdmc->dfsdm->ch_list[pdmc->ch_id];
- unsigned int spi_freq = pdmc->spi_freq;
- unsigned int sample_freq;
- int ret;
- ret = kstrtoint(buf, 0, &sample_freq);
- if (ret)
return ret;
- dev_dbg(&indio_dev->dev, "Requested sample_freq :%d\n", sample_freq);
- if (!sample_freq)
return -EINVAL;
- if (ch->src != DFSDM_CHANNEL_SPI_CLOCK_EXTERNAL)
spi_freq = pdmc->dfsdm->spi_master_freq;
- if (spi_freq % sample_freq)
dev_warn(&indio_dev->dev, "Sampling rate not accurate (%d)\n",
spi_freq / (spi_freq / sample_freq));
- ret = stm32_dfsdm_set_osrs(fl, 0, (spi_freq / sample_freq));
- if (ret < 0) {
dev_err(&indio_dev->dev,
"Not able to find filter parameter that match!\n");
return ret;
- }
- pdmc->sample_freq = sample_freq;
- return len;
+}
+static ssize_t dfsdm_audio_get_spiclk(struct iio_dev *indio_dev, uintptr_t priv,
const struct iio_chan_spec *chan,
char *buf)
+{
- struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev);
- return snprintf(buf, PAGE_SIZE, "%d\n", pdmc->spi_freq);
+}
+static ssize_t dfsdm_audio_set_spiclk(struct iio_dev *indio_dev, uintptr_t priv,
const struct iio_chan_spec *chan,
const char *buf, size_t len)
+{
- struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev);
- struct stm32_dfsdm_filter *fl = &pdmc->dfsdm->fl_list[pdmc->fl_id];
- struct stm32_dfsdm_channel *ch = &pdmc->dfsdm->ch_list[pdmc->ch_id];
- unsigned int sample_freq = pdmc->sample_freq;
- unsigned int spi_freq;
- int ret;
- /* If DFSDM is master on SPI, SPI freq can not be updated */
- if (ch->src != DFSDM_CHANNEL_SPI_CLOCK_EXTERNAL)
return -EPERM;
- ret = kstrtoint(buf, 0, &spi_freq);
- if (ret)
return ret;
- dev_dbg(&indio_dev->dev, "Requested frequency :%d\n", spi_freq);
- if (!spi_freq)
return -EINVAL;
- if (sample_freq) {
if (spi_freq % sample_freq)
dev_warn(&indio_dev->dev,
"Sampling rate not accurate (%d)\n",
spi_freq / (spi_freq / sample_freq));
ret = stm32_dfsdm_set_osrs(fl, 0, (spi_freq / sample_freq));
if (ret < 0) {
dev_err(&indio_dev->dev,
"No filter parameters that match!\n");
return ret;
}
- }
- pdmc->spi_freq = spi_freq;
- return len;
+}
+/*
- Define external info for SPI Frequency and audio sampling rate that can be
- configured by ASoC driver through consumer.h API
- */
+static const struct iio_chan_spec_ext_info dfsdm_adc_ext_info[] = {
- /* filter oversampling: Post filter oversampling ratio */
- {
.name = "audio_sampling_rate",
.shared = IIO_SHARED_BY_TYPE,
.read = dfsdm_audio_get_rate,
.write = dfsdm_audio_set_rate,
- },
- /* data_right_bit_shift : Filter output data shifting */
- {
.name = "spi_clk_freq",
.shared = IIO_SHARED_BY_TYPE,
.read = dfsdm_audio_get_spiclk,
.write = dfsdm_audio_set_spiclk,
- },
- {},
+};
+static int stm32_dfsdm_start_conv(struct stm32_dfsdm_audio *pdmc, bool single) +{
- struct regmap *regmap = pdmc->dfsdm->regmap;
- int ret;
- ret = stm32_dfsdm_start_dfsdm(pdmc->dfsdm);
- if (ret < 0)
return ret;
- ret = stm32_dfsdm_start_channel(pdmc->dfsdm, pdmc->ch_id);
- if (ret < 0)
goto stop_dfsdm;
- ret = stm32_dfsdm_filter_configure(pdmc->dfsdm, pdmc->fl_id,
pdmc->ch_id);
- if (ret < 0)
goto stop_channels;
- /* Enable DMA transfer*/
- ret = regmap_update_bits(regmap, DFSDM_CR1(pdmc->fl_id),
DFSDM_CR1_RDMAEN_MASK, DFSDM_CR1_RDMAEN(1));
- if (ret < 0)
return ret;
- /* Enable conversion triggered by SPI clock*/
- ret = regmap_update_bits(regmap, DFSDM_CR1(pdmc->fl_id),
DFSDM_CR1_RCONT_MASK, DFSDM_CR1_RCONT(1));
- if (ret < 0)
return ret;
- ret = stm32_dfsdm_start_filter(pdmc->dfsdm, pdmc->fl_id);
- if (ret < 0)
goto stop_channels;
- return 0;
+stop_channels:
- stm32_dfsdm_stop_channel(pdmc->dfsdm, pdmc->fl_id);
+stop_dfsdm:
- stm32_dfsdm_stop_dfsdm(pdmc->dfsdm);
- return ret;
+}
+static void stm32_dfsdm_stop_conv(struct stm32_dfsdm_audio *pdmc) +{
- stm32_dfsdm_stop_filter(pdmc->dfsdm, pdmc->fl_id);
- stm32_dfsdm_stop_channel(pdmc->dfsdm, pdmc->ch_id);
- stm32_dfsdm_stop_dfsdm(pdmc->dfsdm);
+}
+static int stm32_dfsdm_set_watermark(struct iio_dev *indio_dev,
unsigned int val)
+{
- struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev);
- unsigned int watermark = DFSDM_DMA_BUFFER_SIZE / 2;
- /*
* DMA cyclic transfers are used, buffer is split into two periods.
* There should be :
* - always one buffer (period) DMA is working on
* - one buffer (period) driver pushed to ASoC side ().
*/
- watermark = min(watermark, val * (unsigned int)(sizeof(u32)));
- pdmc->buf_sz = watermark * 2;
- return 0;
+}
+int stm32_dfsdm_validate_trigger(struct iio_dev *indio_dev,
struct iio_trigger *trig)
+{
- if (!strcmp(stm32_dfsdm_spi_trigger, trig->name))
return 0;
- return -EINVAL;
+}
+static const struct iio_info stm32_dfsdm_info_pdmc = {
- .hwfifo_set_watermark = stm32_dfsdm_set_watermark,
- .driver_module = THIS_MODULE,
- .validate_trigger = stm32_dfsdm_validate_trigger,
+};
+static irqreturn_t stm32_dfsdm_irq(int irq, void *arg) +{
- struct stm32_dfsdm_audio *pdmc = arg;
- struct iio_dev *indio_dev = iio_priv_to_dev(pdmc);
- struct regmap *regmap = pdmc->dfsdm->regmap;
- unsigned int status;
- regmap_read(regmap, DFSDM_ISR(pdmc->fl_id), &status);
- if (status & DFSDM_ISR_ROVRF_MASK) {
dev_err(&indio_dev->dev, "Unexpected Conversion overflow\n");
regmap_update_bits(regmap, DFSDM_ICR(pdmc->fl_id),
DFSDM_ICR_CLRROVRF_MASK,
DFSDM_ICR_CLRROVRF_MASK);
- }
- return IRQ_HANDLED;
+}
+static unsigned int stm32_dfsdm_audio_avail_data(struct stm32_dfsdm_audio *pdmc) +{
- struct dma_tx_state state;
- enum dma_status status;
- status = dmaengine_tx_status(pdmc->dma_chan,
pdmc->dma_chan->cookie,
&state);
- if (status == DMA_IN_PROGRESS) {
/* Residue is size in bytes from end of buffer */
unsigned int i = pdmc->buf_sz - state.residue;
unsigned int size;
/* Return available bytes */
if (i >= pdmc->bufi)
size = i - pdmc->bufi;
else
size = pdmc->buf_sz + i - pdmc->bufi;
return size;
- }
- return 0;
+}
+static void stm32_dfsdm_audio_dma_buffer_done(void *data) +{
- struct iio_dev *indio_dev = data;
- iio_trigger_poll_chained(indio_dev->trig);
This shouldn't be going through the trigger infrastructure at all really. I'm not 100% sure what doing so is gaining you...
Yes i will clean trigger part and call directly the ASoC callback here.
+}
+static int stm32_dfsdm_audio_dma_start(struct iio_dev *indio_dev) +{
- struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev);
- struct dma_async_tx_descriptor *desc;
- dma_cookie_t cookie;
- int ret;
- if (!pdmc->dma_chan)
return -EINVAL;
- dev_dbg(&indio_dev->dev, "%s size=%d watermark=%d\n", __func__,
pdmc->buf_sz, pdmc->buf_sz / 2);
- /* Prepare a DMA cyclic transaction */
- desc = dmaengine_prep_dma_cyclic(pdmc->dma_chan,
pdmc->dma_buf,
pdmc->buf_sz, pdmc->buf_sz / 2,
DMA_DEV_TO_MEM,
DMA_PREP_INTERRUPT);
- if (!desc)
return -EBUSY;
- desc->callback = stm32_dfsdm_audio_dma_buffer_done;
- desc->callback_param = indio_dev;
- cookie = dmaengine_submit(desc);
- ret = dma_submit_error(cookie);
- if (ret) {
dmaengine_terminate_all(pdmc->dma_chan);
return ret;
- }
- /* Issue pending DMA requests */
- dma_async_issue_pending(pdmc->dma_chan);
- return 0;
+}
+static int stm32_dfsdm_postenable(struct iio_dev *indio_dev) +{
- struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev);
- int ret;
- dev_dbg(&indio_dev->dev, "%s\n", __func__);
- /* Reset pdmc buffer index */
- pdmc->bufi = 0;
- ret = stm32_dfsdm_start_conv(pdmc, false);
- if (ret) {
dev_err(&indio_dev->dev, "Can't start conversion\n");
return ret;
- }
- ret = stm32_dfsdm_audio_dma_start(indio_dev);
- if (ret) {
dev_err(&indio_dev->dev, "Can't start DMA\n");
goto err_stop_conv;
- }
- ret = iio_triggered_buffer_postenable(indio_dev);
- if (ret < 0) {
dev_err(&indio_dev->dev, "%s :%d\n", __func__, __LINE__);
goto err_stop_dma;
- }
- return 0;
+err_stop_dma:
- if (pdmc->dma_chan)
dmaengine_terminate_all(pdmc->dma_chan);
+err_stop_conv:
- stm32_dfsdm_stop_conv(pdmc);
- return ret;
+}
+static int stm32_dfsdm_predisable(struct iio_dev *indio_dev) +{
- struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev);
- int ret;
- dev_dbg(&indio_dev->dev, "%s\n", __func__);
- ret = iio_triggered_buffer_predisable(indio_dev);
- if (ret < 0)
dev_err(&indio_dev->dev, "Predisable failed\n");
- if (pdmc->dma_chan)
dmaengine_terminate_all(pdmc->dma_chan);
- stm32_dfsdm_stop_conv(pdmc);
- return 0;
+}
+static const struct iio_buffer_setup_ops stm32_dfsdm_buffer_setup_ops = {
- .postenable = &stm32_dfsdm_postenable,
- .predisable = &stm32_dfsdm_predisable,
+};
+static irqreturn_t stm32_dfsdm_audio_trigger_handler(int irq, void *p) +{
OK. So now I kind of understand what the trigger provided in the adc driver was about. hmm. Triggers are supposed to be per sample so this is indeed a hack. We need fullblown DMA buffer consumers to do this right.
Yes exactly.
Probably best person to comment on that is Lars.
- struct iio_poll_func *pf = p;
- struct iio_dev *indio_dev = pf->indio_dev;
- struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev);
- size_t old_pos;
- int available = stm32_dfsdm_audio_avail_data(pdmc);
- /*
* Buffer interface is not support cyclic DMA buffer,and offer only
* an interface to push data samples per samples.
* For this reason iio_push_to_buffers_with_timestamp in not used
* and interface is hacked using a private callback registered by ASoC.
* This should be a temporary solution waiting a cyclic DMA engine
* support in IIO.
*/
- dev_dbg(&indio_dev->dev, "%s: pos = %d, available = %d\n", __func__,
pdmc->bufi, available);
- old_pos = pdmc->bufi;
- while (available >= indio_dev->scan_bytes) {
u32 *buffer = (u32 *)&pdmc->rx_buf[pdmc->bufi];
/* Mask 8 LSB that contains the channel ID */
*buffer &= 0xFFFFFF00;
available -= indio_dev->scan_bytes;
pdmc->bufi += indio_dev->scan_bytes;
if (pdmc->bufi >= pdmc->buf_sz) {
if (pdmc->cb)
pdmc->cb(&pdmc->rx_buf[old_pos],
pdmc->buf_sz - old_pos, pdmc->cb_priv);
pdmc->bufi = 0;
old_pos = 0;
}
- }
- if (pdmc->cb)
pdmc->cb(&pdmc->rx_buf[old_pos], pdmc->bufi - old_pos,
pdmc->cb_priv);
- iio_trigger_notify_done(indio_dev->trig);
- return IRQ_HANDLED;
+}
+/**
- stm32_dfsdm_get_buff_cb - register a callback
- that will be called when DMA transfer period is achieved.
- @iio_dev: Handle to IIO device.
- @cb: pointer to callback function.
- @data: pointer to data buffer
- @size: size in byte of the data buffer
- @private: pointer to consumer private structure
- @private: pointer to consumer private structure
- */
+int stm32_dfsdm_get_buff_cb(struct iio_dev *iio_dev,
int (*cb)(const void *data, size_t size,
void *private),
void *private)
+{
- struct stm32_dfsdm_audio *pdmc;
- if (!iio_dev)
return -EINVAL;
- pdmc = iio_priv(iio_dev);
- if (iio_dev != iio_priv_to_dev(pdmc))
return -EINVAL;
- pdmc->cb = cb;
- pdmc->cb_priv = private;
- return 0;
+} +EXPORT_SYMBOL_GPL(stm32_dfsdm_get_buff_cb);
+/**
- stm32_dfsdm_release_buff_cb - unregister buffer callback
- @iio_dev: Handle to IIO device.
- */
+int stm32_dfsdm_release_buff_cb(struct iio_dev *iio_dev) +{
- struct stm32_dfsdm_audio *pdmc;
- if (!iio_dev)
return -EINVAL;
- pdmc = iio_priv(iio_dev);
- if (iio_dev != iio_priv_to_dev(pdmc))
return -EINVAL;
- pdmc->cb = NULL;
- pdmc->cb_priv = NULL;
- return 0;
+} +EXPORT_SYMBOL_GPL(stm32_dfsdm_release_buff_cb);
+static int stm32_dfsdm_audio_chan_init(struct iio_dev *indio_dev) +{
- struct iio_chan_spec *ch;
- struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev);
- int ret;
- ch = devm_kzalloc(&indio_dev->dev, sizeof(*ch), GFP_KERNEL);
- if (!ch)
return -ENOMEM;
- ret = stm32_dfsdm_channel_parse_of(pdmc->dfsdm, indio_dev, ch, 0);
- if (ret < 0)
return ret;
- ch->type = IIO_VOLTAGE;
- ch->indexed = 1;
- ch->scan_index = 0;
- ch->ext_info = dfsdm_adc_ext_info;
- ch->scan_type.sign = 's';
- ch->scan_type.realbits = 24;
- ch->scan_type.storagebits = 32;
- pdmc->ch_id = ch->channel;
- ret = stm32_dfsdm_chan_configure(pdmc->dfsdm,
&pdmc->dfsdm->ch_list[ch->channel]);
- indio_dev->num_channels = 1;
- indio_dev->channels = ch;
- return ret;
+}
+static const struct of_device_id stm32_dfsdm_audio_match[] = {
- { .compatible = "st,stm32-dfsdm-audio"},
- {}
+};
+static int stm32_dfsdm_audio_dma_request(struct iio_dev *indio_dev) +{
- struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev);
- struct dma_slave_config config;
- int ret;
- pdmc->dma_chan = dma_request_slave_channel(&indio_dev->dev, "rx");
- if (!pdmc->dma_chan)
return -EINVAL;
- pdmc->rx_buf = dma_alloc_coherent(pdmc->dma_chan->device->dev,
DFSDM_DMA_BUFFER_SIZE,
&pdmc->dma_buf, GFP_KERNEL);
- if (!pdmc->rx_buf) {
ret = -ENOMEM;
goto err_release;
- }
- /* Configure DMA channel to read data register */
- memset(&config, 0, sizeof(config));
- config.src_addr = (dma_addr_t)pdmc->dfsdm->phys_base;
- config.src_addr += DFSDM_RDATAR(pdmc->fl_id);
- config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
- ret = dmaengine_slave_config(pdmc->dma_chan, &config);
- if (ret)
goto err_free;
- return 0;
+err_free:
- dma_free_coherent(pdmc->dma_chan->device->dev, DFSDM_DMA_BUFFER_SIZE,
pdmc->rx_buf, pdmc->dma_buf);
+err_release:
- dma_release_channel(pdmc->dma_chan);
- return ret;
+}
+static int stm32_dfsdm_audio_probe(struct platform_device *pdev) +{
- struct device *dev = &pdev->dev;
- struct stm32_dfsdm_audio *pdmc;
- struct device_node *np = dev->of_node;
- struct iio_dev *iio;
- char *name;
- int ret, irq, val;
- iio = devm_iio_device_alloc(dev, sizeof(*pdmc));
- if (IS_ERR(iio)) {
dev_err(dev, "%s: Failed to allocate IIO\n", __func__);
return PTR_ERR(iio);
- }
- pdmc = iio_priv(iio);
- if (IS_ERR(pdmc)) {
dev_err(dev, "%s: Failed to allocate ADC\n", __func__);
return PTR_ERR(pdmc);
- }
- pdmc->dfsdm = dev_get_drvdata(dev->parent);
- iio->name = np->name;
- iio->dev.parent = dev;
- iio->dev.of_node = np;
- iio->info = &stm32_dfsdm_info_pdmc;
- iio->modes = INDIO_DIRECT_MODE;
- platform_set_drvdata(pdev, pdmc);
- ret = of_property_read_u32(dev->of_node, "reg", &pdmc->fl_id);
- if (ret != 0) {
dev_err(dev, "Missing reg property\n");
return -EINVAL;
- }
- name = kzalloc(sizeof("dfsdm-pdm0"), GFP_KERNEL);
- if (!name)
return -ENOMEM;
- snprintf(name, sizeof("dfsdm-pdm0"), "dfsdm-pdm%d", pdmc->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, pdmc);
- 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;
- }
- pdmc->dfsdm->fl_list[pdmc->fl_id].ford = val;
- ret = of_property_read_u32(dev->of_node, "st,filter0-sync", &val);
- if (!ret)
pdmc->dfsdm->fl_list[pdmc->fl_id].sync_mode = val;
- ret = stm32_dfsdm_audio_chan_init(iio);
- if (ret < 0)
return ret;
- ret = stm32_dfsdm_audio_dma_request(iio);
- if (ret) {
dev_err(&pdev->dev, "DMA request failed\n");
return ret;
- }
- iio->modes |= INDIO_BUFFER_SOFTWARE;
- ret = iio_triggered_buffer_setup(iio,
&iio_pollfunc_store_time,
&stm32_dfsdm_audio_trigger_handler,
&stm32_dfsdm_buffer_setup_ops);
- if (ret) {
dev_err(&pdev->dev, "Buffer setup failed\n");
goto err_dma_disable;
- }
- ret = iio_device_register(iio);
- if (ret) {
dev_err(&pdev->dev, "IIO dev register failed\n");
goto err_buffer_cleanup;
- }
- return 0;
+err_buffer_cleanup:
- iio_triggered_buffer_cleanup(iio);
+err_dma_disable:
- if (pdmc->dma_chan) {
dma_free_coherent(pdmc->dma_chan->device->dev,
DFSDM_DMA_BUFFER_SIZE,
pdmc->rx_buf, pdmc->dma_buf);
dma_release_channel(pdmc->dma_chan);
- }
- return ret;
+}
+static int stm32_dfsdm_audio_remove(struct platform_device *pdev) +{
- struct stm32_dfsdm_audio *pdmc = platform_get_drvdata(pdev);
- struct iio_dev *iio = iio_priv_to_dev(pdmc);
- iio_device_unregister(iio);
Same as in the other driver. Can just use the devm_iio_device_register version and drop remove.
oops missing the previous comment... sorry.
- return 0;
+}
+static struct platform_driver stm32_dfsdm_audio_driver = {
- .driver = {
.name = "stm32-dfsdm-audio",
.of_match_table = stm32_dfsdm_audio_match,
- },
- .probe = stm32_dfsdm_audio_probe,
- .remove = stm32_dfsdm_audio_remove,
+}; +module_platform_driver(stm32_dfsdm_audio_driver);
+MODULE_DESCRIPTION("STM32 sigma delta converter for PDM microphone"); +MODULE_AUTHOR("Arnaud Pouliquen arnaud.pouliquen@st.com"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/iio/adc/stm32-dfsdm-audio.h b/include/linux/iio/adc/stm32-dfsdm-audio.h new file mode 100644 index 0000000..9b41b28 --- /dev/null +++ b/include/linux/iio/adc/stm32-dfsdm-audio.h @@ -0,0 +1,41 @@ +/*
- This file discribe the STM32 DFSDM IIO driver API for audio part
- Copyright (C) 2017, STMicroelectronics - All Rights Reserved
- Author(s): Arnaud Pouliquen arnaud.pouliquen@st.com.
- License terms: GPL V2.0.
- This program is free software; you can redistribute it and/or modify it
- under the terms of the GNU General Public License version 2 as published by
- the Free Software Foundation.
- This program is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
- details.
- */
+#ifndef STM32_DFSDM_AUDIO_H +#define STM32_DFSDM_AUDIO_H
+/**
- stm32_dfsdm_get_buff_cb() - register callback for capture buffer period.
- @dev: Pointer to client device.
- @indio_dev: Device on which the channel exists.
- @cb: Callback function.
@data: pointer to data buffer
@size: size of the data buffer in bytes
- @private: Private data passed to callback.
- */
+int stm32_dfsdm_get_buff_cb(struct iio_dev *iio_dev,
int (*cb)(const void *data, size_t size,
void *private),
void *private);
+/**
- stm32_dfsdm_get_buff_cb() - release callback for capture buffer period.
- @indio_dev: Device on which the channel exists.
- */
+int stm32_dfsdm_release_buff_cb(struct iio_dev *iio_dev);
+#endif
-- To unsubscribe from this list: send the line "unsubscribe linux-iio" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
On 20/03/17 11:30, Arnaud Pouliquen wrote:
On 03/19/2017 11:44 PM, Jonathan Cameron wrote:
On 17/03/17 14:08, Arnaud Pouliquen wrote:
Add iio consumer API to set buffer size and watermark according to sysfs API.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com
Hmm. Not keen on the length one. Setting a requested watermark is fair enough. There is no actually buffer in these cases though so setting it's length is downright odd..
Length and watermark are configurable from user land through sysfs. Seems to me logic to also propose it in inkern API... But I can clean length , no problem.
The concept of length for the consumer interface doesn't currently make sense as it refers to an actual buffer. The consumer interface currently passes one entry at a time...
Guess this is part of the hacks we need to clean up by doing the dma buffer consumer stuff right...
Yes all is linked :-).
Jonathan
drivers/iio/buffer/industrialio-buffer-cb.c | 12 ++++++++++++ include/linux/iio/consumer.h | 13 +++++++++++++ 2 files changed, 25 insertions(+)
diff --git a/drivers/iio/buffer/industrialio-buffer-cb.c b/drivers/iio/buffer/industrialio-buffer-cb.c index b8f550e..43c066a 100644 --- a/drivers/iio/buffer/industrialio-buffer-cb.c +++ b/drivers/iio/buffer/industrialio-buffer-cb.c @@ -103,6 +103,18 @@ struct iio_cb_buffer *iio_channel_get_all_cb(struct device *dev, } EXPORT_SYMBOL_GPL(iio_channel_get_all_cb);
+int iio_channel_cb_set_buffer_params(struct iio_cb_buffer *cb_buff,
size_t length, size_t watermark)
+{
- if (!length || length < watermark)
return -EINVAL;
- cb_buff->buffer.watermark = watermark;
- cb_buff->buffer.length = length;
- return 0;
+} +EXPORT_SYMBOL_GPL(iio_channel_start_all_cb);
int iio_channel_start_all_cb(struct iio_cb_buffer *cb_buff) { return iio_update_buffers(cb_buff->indio_dev, &cb_buff->buffer, diff --git a/include/linux/iio/consumer.h b/include/linux/iio/consumer.h index cb44771..0f6e94d 100644 --- a/include/linux/iio/consumer.h +++ b/include/linux/iio/consumer.h @@ -134,6 +134,19 @@ struct iio_cb_buffer *iio_channel_get_all_cb(struct device *dev, void *private), void *private); /**
- iio_channel_cb_set_buffer_size() - set the buffer length.
- @cb_buffer: The callback buffer from whom we want the channel
information.
- @length: buffer length in bytes
- @watermark: buffer watermark in bytes
- This function allows to configure the buffer length. The watermark if
- forced to half of the buffer.
- */
+int iio_channel_cb_set_buffer_params(struct iio_cb_buffer *cb_buffer,
size_t length, size_t watermark);
+/**
- iio_channel_release_all_cb() - release and unregister the callback.
- @cb_buffer: The callback buffer that was allocated.
*/
-- To unsubscribe from this list: send the line "unsubscribe linux-iio" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
-- To unsubscribe from this list: send the line "unsubscribe linux-iio" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
On Fri, Mar 24, 2017 at 09:46:03AM -0500, Rob Herring wrote:
On Fri, Mar 17, 2017 at 03:08:21PM +0100, Arnaud Pouliquen wrote:
+Device-Tree bindings for dmic codec
Please define what is and isn't a DMIC here. What's the interface?
Digital microphones are a well understood concept for anyone familiar with the problem domain, they all provide a PDM output.
+Required properties:
- compatible: should be "dmic-codec".
DMICs don't have part numbers?
Not meaningfully in this context any more than analogue microphones do - they have no software visible interface.
On 03/25/2017 04:59 PM, Jonathan Cameron wrote:
On 20/03/17 11:29, Arnaud Pouliquen wrote:
Please find my comments in-line
Thanks and Regards, Arnaud
On 03/19/2017 11:38 PM, Jonathan Cameron wrote:
On 17/03/17 14:08, Arnaud Pouliquen wrote:
Add DFSDM driver to handle PDM audio microphones.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com
So key element here is that we really need to have a proper way of doing DMA buffer consumers from IIO (as you said in your cover letter).
Not entirely obvious how to do this. Would like some input from Lars if possible. I'm afraid I don't have an suitable hardware to try anything out on (or the time really!). This will obviously be quite different from the single scan stuff you have seen is there at the moment.
I saw 2 other drivers ti_am335x_adc.c and stm32_adc.c) that use cyclic DMA. I suppose that problematics are similar. Perhaps the extra constrains in DFSDM is the in-kernel API used to get the data.
Absolutely. Cyclic dma is becoming more and more common on SoC ADCs. There are a few more fpga based ones in Analog devices tree as well - most of those actually do the dma buffer route rather than pushing through the normal buffer interface (kfifo effectively). Right now we just don't have any means of accessing these dma buffers in kernel.
Unlike the normal consumer interface, I doubt we'll want to do demuxing of the stream for this sort of usecase - feels like exclusive use is more likely.
Whether this whole approach makes sense vs doing the dma in the alsa driver isn't clear to me.
On the plus side, presumably we'll get love dma ADC support for free as part of doing it this way ;)
It's been a while since I looked at the IIO dma buffer stuff at all. Will try and refresh my memory sometime this week.
Ok, i will wait your feedback (and Lars's one) before updating my code for next version.
It may well be the case that it will take us sometime (when I say us, I mean you and Lars ;) to pin down how to do dma buffer consumers, so it may be a case of taking the driver in a state close to the current one with the intent to switch to a generic way of handling it later.
I had a short discussion with Lars on IIO IRC, seems that he also does not see a straightforward solution for this IP. The Fact that audio part is in a separate file should help to rework it in a generic way. So i will propose a V4 for upstream, integrating discussed fixes.
Thanks Arnaud
As long as bindings and userspace don't change we can of course mess with anything we like.
Jonathan
Jonathan
drivers/iio/adc/Kconfig | 13 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/stm32-dfsdm-audio.c | 720 ++++++++++++++++++++++++++++++ include/linux/iio/adc/stm32-dfsdm-audio.h | 41 ++ 4 files changed, 775 insertions(+) create mode 100644 drivers/iio/adc/stm32-dfsdm-audio.c create mode 100644 include/linux/iio/adc/stm32-dfsdm-audio.h
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 3e0eb11..c108933 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -478,6 +478,19 @@ config STM32_DFSDM_ADC This driver can also be built as a module. If so, the module will be called stm32-dfsdm-adc.
+config STM32_DFSDM_AUDIO
- tristate "STMicroelectronics STM32 dfsdm audio
- depends on (ARCH_STM32 && OF) || COMPILE_TEST
- select STM32_DFSDM_CORE
- select REGMAP_MMIO
- select IIO_BUFFER_DMAENGINE
- help
Select this option to support Audio PDM micophone 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-audio.
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 161f271..79f975d 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -44,6 +44,7 @@ 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 +obj-$(CONFIG_STM32_DFSDM_AUDIO) += stm32-dfsdm-audio.o obj-$(CONFIG_STM32_DFSDM_CORE) += stm32-dfsdm-core.o obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o obj-$(CONFIG_TI_ADC0832) += ti-adc0832.o diff --git a/drivers/iio/adc/stm32-dfsdm-audio.c b/drivers/iio/adc/stm32-dfsdm-audio.c new file mode 100644 index 0000000..115ef8f --- /dev/null +++ b/drivers/iio/adc/stm32-dfsdm-audio.c @@ -0,0 +1,720 @@ +/*
- This file is the ADC part of of the STM32 DFSDM 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/dmaengine.h> +#include <linux/dma-mapping.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/slab.h>
+#include <linux/iio/buffer.h> +#include <linux/iio/hw_consumer.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/trigger.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h>
+#include "stm32-dfsdm.h"
+#define DFSDM_DMA_BUFFER_SIZE (4 * PAGE_SIZE)
+struct stm32_dfsdm_audio {
- struct stm32_dfsdm *dfsdm;
- unsigned int fl_id;
- unsigned int ch_id;
- unsigned int spi_freq; /* SPI bus clock frequency */
- unsigned int sample_freq; /* Sample frequency after filter decimation */
- u8 *rx_buf;
- unsigned int bufi; /* Buffer current position */
- unsigned int buf_sz; /* Buffer size */
- struct dma_chan *dma_chan;
- dma_addr_t dma_buf;
- int (*cb)(const void *data, size_t size, void *cb_priv);
- void *cb_priv;
+};
+const char *stm32_dfsdm_spi_trigger = DFSDM_SPI_TRIGGER_NAME;
+static ssize_t dfsdm_audio_get_rate(struct iio_dev *indio_dev, uintptr_t priv,
const struct iio_chan_spec *chan, char *buf)
+{
- struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev);
- return snprintf(buf, PAGE_SIZE, "%d\n", pdmc->sample_freq);
+}
+static ssize_t dfsdm_audio_set_rate(struct iio_dev *indio_dev, uintptr_t priv,
const struct iio_chan_spec *chan,
const char *buf, size_t len)
+{
- struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev);
- struct stm32_dfsdm_filter *fl = &pdmc->dfsdm->fl_list[pdmc->fl_id];
- struct stm32_dfsdm_channel *ch = &pdmc->dfsdm->ch_list[pdmc->ch_id];
- unsigned int spi_freq = pdmc->spi_freq;
- unsigned int sample_freq;
- int ret;
- ret = kstrtoint(buf, 0, &sample_freq);
- if (ret)
return ret;
- dev_dbg(&indio_dev->dev, "Requested sample_freq :%d\n", sample_freq);
- if (!sample_freq)
return -EINVAL;
- if (ch->src != DFSDM_CHANNEL_SPI_CLOCK_EXTERNAL)
spi_freq = pdmc->dfsdm->spi_master_freq;
- if (spi_freq % sample_freq)
dev_warn(&indio_dev->dev, "Sampling rate not accurate (%d)\n",
spi_freq / (spi_freq / sample_freq));
- ret = stm32_dfsdm_set_osrs(fl, 0, (spi_freq / sample_freq));
- if (ret < 0) {
dev_err(&indio_dev->dev,
"Not able to find filter parameter that match!\n");
return ret;
- }
- pdmc->sample_freq = sample_freq;
- return len;
+}
+static ssize_t dfsdm_audio_get_spiclk(struct iio_dev *indio_dev, uintptr_t priv,
const struct iio_chan_spec *chan,
char *buf)
+{
- struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev);
- return snprintf(buf, PAGE_SIZE, "%d\n", pdmc->spi_freq);
+}
+static ssize_t dfsdm_audio_set_spiclk(struct iio_dev *indio_dev, uintptr_t priv,
const struct iio_chan_spec *chan,
const char *buf, size_t len)
+{
- struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev);
- struct stm32_dfsdm_filter *fl = &pdmc->dfsdm->fl_list[pdmc->fl_id];
- struct stm32_dfsdm_channel *ch = &pdmc->dfsdm->ch_list[pdmc->ch_id];
- unsigned int sample_freq = pdmc->sample_freq;
- unsigned int spi_freq;
- int ret;
- /* If DFSDM is master on SPI, SPI freq can not be updated */
- if (ch->src != DFSDM_CHANNEL_SPI_CLOCK_EXTERNAL)
return -EPERM;
- ret = kstrtoint(buf, 0, &spi_freq);
- if (ret)
return ret;
- dev_dbg(&indio_dev->dev, "Requested frequency :%d\n", spi_freq);
- if (!spi_freq)
return -EINVAL;
- if (sample_freq) {
if (spi_freq % sample_freq)
dev_warn(&indio_dev->dev,
"Sampling rate not accurate (%d)\n",
spi_freq / (spi_freq / sample_freq));
ret = stm32_dfsdm_set_osrs(fl, 0, (spi_freq / sample_freq));
if (ret < 0) {
dev_err(&indio_dev->dev,
"No filter parameters that match!\n");
return ret;
}
- }
- pdmc->spi_freq = spi_freq;
- return len;
+}
+/*
- Define external info for SPI Frequency and audio sampling rate that can be
- configured by ASoC driver through consumer.h API
- */
+static const struct iio_chan_spec_ext_info dfsdm_adc_ext_info[] = {
- /* filter oversampling: Post filter oversampling ratio */
- {
.name = "audio_sampling_rate",
.shared = IIO_SHARED_BY_TYPE,
.read = dfsdm_audio_get_rate,
.write = dfsdm_audio_set_rate,
- },
- /* data_right_bit_shift : Filter output data shifting */
- {
.name = "spi_clk_freq",
.shared = IIO_SHARED_BY_TYPE,
.read = dfsdm_audio_get_spiclk,
.write = dfsdm_audio_set_spiclk,
- },
- {},
+};
+static int stm32_dfsdm_start_conv(struct stm32_dfsdm_audio *pdmc, bool single) +{
- struct regmap *regmap = pdmc->dfsdm->regmap;
- int ret;
- ret = stm32_dfsdm_start_dfsdm(pdmc->dfsdm);
- if (ret < 0)
return ret;
- ret = stm32_dfsdm_start_channel(pdmc->dfsdm, pdmc->ch_id);
- if (ret < 0)
goto stop_dfsdm;
- ret = stm32_dfsdm_filter_configure(pdmc->dfsdm, pdmc->fl_id,
pdmc->ch_id);
- if (ret < 0)
goto stop_channels;
- /* Enable DMA transfer*/
- ret = regmap_update_bits(regmap, DFSDM_CR1(pdmc->fl_id),
DFSDM_CR1_RDMAEN_MASK, DFSDM_CR1_RDMAEN(1));
- if (ret < 0)
return ret;
- /* Enable conversion triggered by SPI clock*/
- ret = regmap_update_bits(regmap, DFSDM_CR1(pdmc->fl_id),
DFSDM_CR1_RCONT_MASK, DFSDM_CR1_RCONT(1));
- if (ret < 0)
return ret;
- ret = stm32_dfsdm_start_filter(pdmc->dfsdm, pdmc->fl_id);
- if (ret < 0)
goto stop_channels;
- return 0;
+stop_channels:
- stm32_dfsdm_stop_channel(pdmc->dfsdm, pdmc->fl_id);
+stop_dfsdm:
- stm32_dfsdm_stop_dfsdm(pdmc->dfsdm);
- return ret;
+}
+static void stm32_dfsdm_stop_conv(struct stm32_dfsdm_audio *pdmc) +{
- stm32_dfsdm_stop_filter(pdmc->dfsdm, pdmc->fl_id);
- stm32_dfsdm_stop_channel(pdmc->dfsdm, pdmc->ch_id);
- stm32_dfsdm_stop_dfsdm(pdmc->dfsdm);
+}
+static int stm32_dfsdm_set_watermark(struct iio_dev *indio_dev,
unsigned int val)
+{
- struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev);
- unsigned int watermark = DFSDM_DMA_BUFFER_SIZE / 2;
- /*
* DMA cyclic transfers are used, buffer is split into two periods.
* There should be :
* - always one buffer (period) DMA is working on
* - one buffer (period) driver pushed to ASoC side ().
*/
- watermark = min(watermark, val * (unsigned int)(sizeof(u32)));
- pdmc->buf_sz = watermark * 2;
- return 0;
+}
+int stm32_dfsdm_validate_trigger(struct iio_dev *indio_dev,
struct iio_trigger *trig)
+{
- if (!strcmp(stm32_dfsdm_spi_trigger, trig->name))
return 0;
- return -EINVAL;
+}
+static const struct iio_info stm32_dfsdm_info_pdmc = {
- .hwfifo_set_watermark = stm32_dfsdm_set_watermark,
- .driver_module = THIS_MODULE,
- .validate_trigger = stm32_dfsdm_validate_trigger,
+};
+static irqreturn_t stm32_dfsdm_irq(int irq, void *arg) +{
- struct stm32_dfsdm_audio *pdmc = arg;
- struct iio_dev *indio_dev = iio_priv_to_dev(pdmc);
- struct regmap *regmap = pdmc->dfsdm->regmap;
- unsigned int status;
- regmap_read(regmap, DFSDM_ISR(pdmc->fl_id), &status);
- if (status & DFSDM_ISR_ROVRF_MASK) {
dev_err(&indio_dev->dev, "Unexpected Conversion overflow\n");
regmap_update_bits(regmap, DFSDM_ICR(pdmc->fl_id),
DFSDM_ICR_CLRROVRF_MASK,
DFSDM_ICR_CLRROVRF_MASK);
- }
- return IRQ_HANDLED;
+}
+static unsigned int stm32_dfsdm_audio_avail_data(struct stm32_dfsdm_audio *pdmc) +{
- struct dma_tx_state state;
- enum dma_status status;
- status = dmaengine_tx_status(pdmc->dma_chan,
pdmc->dma_chan->cookie,
&state);
- if (status == DMA_IN_PROGRESS) {
/* Residue is size in bytes from end of buffer */
unsigned int i = pdmc->buf_sz - state.residue;
unsigned int size;
/* Return available bytes */
if (i >= pdmc->bufi)
size = i - pdmc->bufi;
else
size = pdmc->buf_sz + i - pdmc->bufi;
return size;
- }
- return 0;
+}
+static void stm32_dfsdm_audio_dma_buffer_done(void *data) +{
- struct iio_dev *indio_dev = data;
- iio_trigger_poll_chained(indio_dev->trig);
This shouldn't be going through the trigger infrastructure at all really. I'm not 100% sure what doing so is gaining you...
Yes i will clean trigger part and call directly the ASoC callback here.
+}
+static int stm32_dfsdm_audio_dma_start(struct iio_dev *indio_dev) +{
- struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev);
- struct dma_async_tx_descriptor *desc;
- dma_cookie_t cookie;
- int ret;
- if (!pdmc->dma_chan)
return -EINVAL;
- dev_dbg(&indio_dev->dev, "%s size=%d watermark=%d\n", __func__,
pdmc->buf_sz, pdmc->buf_sz / 2);
- /* Prepare a DMA cyclic transaction */
- desc = dmaengine_prep_dma_cyclic(pdmc->dma_chan,
pdmc->dma_buf,
pdmc->buf_sz, pdmc->buf_sz / 2,
DMA_DEV_TO_MEM,
DMA_PREP_INTERRUPT);
- if (!desc)
return -EBUSY;
- desc->callback = stm32_dfsdm_audio_dma_buffer_done;
- desc->callback_param = indio_dev;
- cookie = dmaengine_submit(desc);
- ret = dma_submit_error(cookie);
- if (ret) {
dmaengine_terminate_all(pdmc->dma_chan);
return ret;
- }
- /* Issue pending DMA requests */
- dma_async_issue_pending(pdmc->dma_chan);
- return 0;
+}
+static int stm32_dfsdm_postenable(struct iio_dev *indio_dev) +{
- struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev);
- int ret;
- dev_dbg(&indio_dev->dev, "%s\n", __func__);
- /* Reset pdmc buffer index */
- pdmc->bufi = 0;
- ret = stm32_dfsdm_start_conv(pdmc, false);
- if (ret) {
dev_err(&indio_dev->dev, "Can't start conversion\n");
return ret;
- }
- ret = stm32_dfsdm_audio_dma_start(indio_dev);
- if (ret) {
dev_err(&indio_dev->dev, "Can't start DMA\n");
goto err_stop_conv;
- }
- ret = iio_triggered_buffer_postenable(indio_dev);
- if (ret < 0) {
dev_err(&indio_dev->dev, "%s :%d\n", __func__, __LINE__);
goto err_stop_dma;
- }
- return 0;
+err_stop_dma:
- if (pdmc->dma_chan)
dmaengine_terminate_all(pdmc->dma_chan);
+err_stop_conv:
- stm32_dfsdm_stop_conv(pdmc);
- return ret;
+}
+static int stm32_dfsdm_predisable(struct iio_dev *indio_dev) +{
- struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev);
- int ret;
- dev_dbg(&indio_dev->dev, "%s\n", __func__);
- ret = iio_triggered_buffer_predisable(indio_dev);
- if (ret < 0)
dev_err(&indio_dev->dev, "Predisable failed\n");
- if (pdmc->dma_chan)
dmaengine_terminate_all(pdmc->dma_chan);
- stm32_dfsdm_stop_conv(pdmc);
- return 0;
+}
+static const struct iio_buffer_setup_ops stm32_dfsdm_buffer_setup_ops = {
- .postenable = &stm32_dfsdm_postenable,
- .predisable = &stm32_dfsdm_predisable,
+};
+static irqreturn_t stm32_dfsdm_audio_trigger_handler(int irq, void *p) +{
OK. So now I kind of understand what the trigger provided in the adc driver was about. hmm. Triggers are supposed to be per sample so this is indeed a hack. We need fullblown DMA buffer consumers to do this right.
Yes exactly.
Probably best person to comment on that is Lars.
- struct iio_poll_func *pf = p;
- struct iio_dev *indio_dev = pf->indio_dev;
- struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev);
- size_t old_pos;
- int available = stm32_dfsdm_audio_avail_data(pdmc);
- /*
* Buffer interface is not support cyclic DMA buffer,and offer only
* an interface to push data samples per samples.
* For this reason iio_push_to_buffers_with_timestamp in not used
* and interface is hacked using a private callback registered by ASoC.
* This should be a temporary solution waiting a cyclic DMA engine
* support in IIO.
*/
- dev_dbg(&indio_dev->dev, "%s: pos = %d, available = %d\n", __func__,
pdmc->bufi, available);
- old_pos = pdmc->bufi;
- while (available >= indio_dev->scan_bytes) {
u32 *buffer = (u32 *)&pdmc->rx_buf[pdmc->bufi];
/* Mask 8 LSB that contains the channel ID */
*buffer &= 0xFFFFFF00;
available -= indio_dev->scan_bytes;
pdmc->bufi += indio_dev->scan_bytes;
if (pdmc->bufi >= pdmc->buf_sz) {
if (pdmc->cb)
pdmc->cb(&pdmc->rx_buf[old_pos],
pdmc->buf_sz - old_pos, pdmc->cb_priv);
pdmc->bufi = 0;
old_pos = 0;
}
- }
- if (pdmc->cb)
pdmc->cb(&pdmc->rx_buf[old_pos], pdmc->bufi - old_pos,
pdmc->cb_priv);
- iio_trigger_notify_done(indio_dev->trig);
- return IRQ_HANDLED;
+}
+/**
- stm32_dfsdm_get_buff_cb - register a callback
- that will be called when DMA transfer period is achieved.
- @iio_dev: Handle to IIO device.
- @cb: pointer to callback function.
- @data: pointer to data buffer
- @size: size in byte of the data buffer
- @private: pointer to consumer private structure
- @private: pointer to consumer private structure
- */
+int stm32_dfsdm_get_buff_cb(struct iio_dev *iio_dev,
int (*cb)(const void *data, size_t size,
void *private),
void *private)
+{
- struct stm32_dfsdm_audio *pdmc;
- if (!iio_dev)
return -EINVAL;
- pdmc = iio_priv(iio_dev);
- if (iio_dev != iio_priv_to_dev(pdmc))
return -EINVAL;
- pdmc->cb = cb;
- pdmc->cb_priv = private;
- return 0;
+} +EXPORT_SYMBOL_GPL(stm32_dfsdm_get_buff_cb);
+/**
- stm32_dfsdm_release_buff_cb - unregister buffer callback
- @iio_dev: Handle to IIO device.
- */
+int stm32_dfsdm_release_buff_cb(struct iio_dev *iio_dev) +{
- struct stm32_dfsdm_audio *pdmc;
- if (!iio_dev)
return -EINVAL;
- pdmc = iio_priv(iio_dev);
- if (iio_dev != iio_priv_to_dev(pdmc))
return -EINVAL;
- pdmc->cb = NULL;
- pdmc->cb_priv = NULL;
- return 0;
+} +EXPORT_SYMBOL_GPL(stm32_dfsdm_release_buff_cb);
+static int stm32_dfsdm_audio_chan_init(struct iio_dev *indio_dev) +{
- struct iio_chan_spec *ch;
- struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev);
- int ret;
- ch = devm_kzalloc(&indio_dev->dev, sizeof(*ch), GFP_KERNEL);
- if (!ch)
return -ENOMEM;
- ret = stm32_dfsdm_channel_parse_of(pdmc->dfsdm, indio_dev, ch, 0);
- if (ret < 0)
return ret;
- ch->type = IIO_VOLTAGE;
- ch->indexed = 1;
- ch->scan_index = 0;
- ch->ext_info = dfsdm_adc_ext_info;
- ch->scan_type.sign = 's';
- ch->scan_type.realbits = 24;
- ch->scan_type.storagebits = 32;
- pdmc->ch_id = ch->channel;
- ret = stm32_dfsdm_chan_configure(pdmc->dfsdm,
&pdmc->dfsdm->ch_list[ch->channel]);
- indio_dev->num_channels = 1;
- indio_dev->channels = ch;
- return ret;
+}
+static const struct of_device_id stm32_dfsdm_audio_match[] = {
- { .compatible = "st,stm32-dfsdm-audio"},
- {}
+};
+static int stm32_dfsdm_audio_dma_request(struct iio_dev *indio_dev) +{
- struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev);
- struct dma_slave_config config;
- int ret;
- pdmc->dma_chan = dma_request_slave_channel(&indio_dev->dev, "rx");
- if (!pdmc->dma_chan)
return -EINVAL;
- pdmc->rx_buf = dma_alloc_coherent(pdmc->dma_chan->device->dev,
DFSDM_DMA_BUFFER_SIZE,
&pdmc->dma_buf, GFP_KERNEL);
- if (!pdmc->rx_buf) {
ret = -ENOMEM;
goto err_release;
- }
- /* Configure DMA channel to read data register */
- memset(&config, 0, sizeof(config));
- config.src_addr = (dma_addr_t)pdmc->dfsdm->phys_base;
- config.src_addr += DFSDM_RDATAR(pdmc->fl_id);
- config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
- ret = dmaengine_slave_config(pdmc->dma_chan, &config);
- if (ret)
goto err_free;
- return 0;
+err_free:
- dma_free_coherent(pdmc->dma_chan->device->dev, DFSDM_DMA_BUFFER_SIZE,
pdmc->rx_buf, pdmc->dma_buf);
+err_release:
- dma_release_channel(pdmc->dma_chan);
- return ret;
+}
+static int stm32_dfsdm_audio_probe(struct platform_device *pdev) +{
- struct device *dev = &pdev->dev;
- struct stm32_dfsdm_audio *pdmc;
- struct device_node *np = dev->of_node;
- struct iio_dev *iio;
- char *name;
- int ret, irq, val;
- iio = devm_iio_device_alloc(dev, sizeof(*pdmc));
- if (IS_ERR(iio)) {
dev_err(dev, "%s: Failed to allocate IIO\n", __func__);
return PTR_ERR(iio);
- }
- pdmc = iio_priv(iio);
- if (IS_ERR(pdmc)) {
dev_err(dev, "%s: Failed to allocate ADC\n", __func__);
return PTR_ERR(pdmc);
- }
- pdmc->dfsdm = dev_get_drvdata(dev->parent);
- iio->name = np->name;
- iio->dev.parent = dev;
- iio->dev.of_node = np;
- iio->info = &stm32_dfsdm_info_pdmc;
- iio->modes = INDIO_DIRECT_MODE;
- platform_set_drvdata(pdev, pdmc);
- ret = of_property_read_u32(dev->of_node, "reg", &pdmc->fl_id);
- if (ret != 0) {
dev_err(dev, "Missing reg property\n");
return -EINVAL;
- }
- name = kzalloc(sizeof("dfsdm-pdm0"), GFP_KERNEL);
- if (!name)
return -ENOMEM;
- snprintf(name, sizeof("dfsdm-pdm0"), "dfsdm-pdm%d", pdmc->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, pdmc);
- 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;
- }
- pdmc->dfsdm->fl_list[pdmc->fl_id].ford = val;
- ret = of_property_read_u32(dev->of_node, "st,filter0-sync", &val);
- if (!ret)
pdmc->dfsdm->fl_list[pdmc->fl_id].sync_mode = val;
- ret = stm32_dfsdm_audio_chan_init(iio);
- if (ret < 0)
return ret;
- ret = stm32_dfsdm_audio_dma_request(iio);
- if (ret) {
dev_err(&pdev->dev, "DMA request failed\n");
return ret;
- }
- iio->modes |= INDIO_BUFFER_SOFTWARE;
- ret = iio_triggered_buffer_setup(iio,
&iio_pollfunc_store_time,
&stm32_dfsdm_audio_trigger_handler,
&stm32_dfsdm_buffer_setup_ops);
- if (ret) {
dev_err(&pdev->dev, "Buffer setup failed\n");
goto err_dma_disable;
- }
- ret = iio_device_register(iio);
- if (ret) {
dev_err(&pdev->dev, "IIO dev register failed\n");
goto err_buffer_cleanup;
- }
- return 0;
+err_buffer_cleanup:
- iio_triggered_buffer_cleanup(iio);
+err_dma_disable:
- if (pdmc->dma_chan) {
dma_free_coherent(pdmc->dma_chan->device->dev,
DFSDM_DMA_BUFFER_SIZE,
pdmc->rx_buf, pdmc->dma_buf);
dma_release_channel(pdmc->dma_chan);
- }
- return ret;
+}
+static int stm32_dfsdm_audio_remove(struct platform_device *pdev) +{
- struct stm32_dfsdm_audio *pdmc = platform_get_drvdata(pdev);
- struct iio_dev *iio = iio_priv_to_dev(pdmc);
- iio_device_unregister(iio);
Same as in the other driver. Can just use the devm_iio_device_register version and drop remove.
oops missing the previous comment... sorry.
- return 0;
+}
+static struct platform_driver stm32_dfsdm_audio_driver = {
- .driver = {
.name = "stm32-dfsdm-audio",
.of_match_table = stm32_dfsdm_audio_match,
- },
- .probe = stm32_dfsdm_audio_probe,
- .remove = stm32_dfsdm_audio_remove,
+}; +module_platform_driver(stm32_dfsdm_audio_driver);
+MODULE_DESCRIPTION("STM32 sigma delta converter for PDM microphone"); +MODULE_AUTHOR("Arnaud Pouliquen arnaud.pouliquen@st.com"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/iio/adc/stm32-dfsdm-audio.h b/include/linux/iio/adc/stm32-dfsdm-audio.h new file mode 100644 index 0000000..9b41b28 --- /dev/null +++ b/include/linux/iio/adc/stm32-dfsdm-audio.h @@ -0,0 +1,41 @@ +/*
- This file discribe the STM32 DFSDM IIO driver API for audio part
- Copyright (C) 2017, STMicroelectronics - All Rights Reserved
- Author(s): Arnaud Pouliquen arnaud.pouliquen@st.com.
- License terms: GPL V2.0.
- This program is free software; you can redistribute it and/or modify it
- under the terms of the GNU General Public License version 2 as published by
- the Free Software Foundation.
- This program is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
- details.
- */
+#ifndef STM32_DFSDM_AUDIO_H +#define STM32_DFSDM_AUDIO_H
+/**
- stm32_dfsdm_get_buff_cb() - register callback for capture buffer period.
- @dev: Pointer to client device.
- @indio_dev: Device on which the channel exists.
- @cb: Callback function.
@data: pointer to data buffer
@size: size of the data buffer in bytes
- @private: Private data passed to callback.
- */
+int stm32_dfsdm_get_buff_cb(struct iio_dev *iio_dev,
int (*cb)(const void *data, size_t size,
void *private),
void *private);
+/**
- stm32_dfsdm_get_buff_cb() - release callback for capture buffer period.
- @indio_dev: Device on which the channel exists.
- */
+int stm32_dfsdm_release_buff_cb(struct iio_dev *iio_dev);
+#endif
-- To unsubscribe from this list: send the line "unsubscribe linux-iio" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
Hello Rob,
Sorry for this dirty/crappy patch... Please find answers below
Thanks
Arnaud
On 03/24/2017 03:52 PM, Rob Herring wrote:
On Fri, Mar 17, 2017 at 03:08:23PM +0100, Arnaud Pouliquen wrote:
Add bindings that describes audio settings to support Digital Filter for pulse density modulation(PDM) microphone.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com
V2->V3: Fixes based on V2 comments
.../devicetree/bindings/sound/st,stm32-adfsdm.txt | 41 ++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/st,stm32-adfsdm.txt
diff --git a/Documentation/devicetree/bindings/sound/st,stm32-adfsdm.txt b/Documentation/devicetree/bindings/sound/st,stm32-adfsdm.txt new file mode 100644 index 0000000..ab610bc --- /dev/null +++ b/Documentation/devicetree/bindings/sound/st,stm32-adfsdm.txt @@ -0,0 +1,40 @@ +STMicroelectronics audio DFSDM DT bindings
+This driver supports audio PDM microphone capture through Digital Filter format +Sigma Delta modulators (DFSDM).
+Required properties:
- compatible: "st,stm32h7-adfsdm".
- #sound-dai-cells : Must be equal to 0
- io-channels : phandle to iio dfsdm instance node.
+Example of a simple sound card using audio DFSDM node.
- dmic0: dmic_@0 {
Drop the '_' and unit address.
compatible = "dmic-codec";
#sound-dai-cells = <0>;
- };
- asoc-pdm@0 {
asoc is a Linux term. Drop the unit address.
compatible = "st,stm32h7-adfsdm";
Is this a separate block from the ADC? A drawing of the h/w blocks and connections would help.
I don't know how to explain it...So don't hesitate is still not clear. On DFSDM we can connect 2 ADC types: - Generic Sigma delta(SD) ADC for analog to digital conversion - Digital microphones for audio capture.
In both cases, an SPI or Manchester bus is used to transfer 1-bit stream to DFSDM. 1) In case of SD ADC, The link is described in IIO declaration using "io-channels" property ( [04/11] IIO: add DT bindings for stm32 DFSDM filter) 2) For audio purpose, the link is done by the simple or the graph card So this device exposes the Digital audio interface (DAI) associated to the DFSDM ADC. Link between the DFSDM DAI and the DFSDM IIO device is done through the "io-channels" property.
#sound-dai-cells = <0>;
io-channels = <&dfsdm_adc0 0>;
- };
- sound_dfsdm_pdm {
sound-card {
compatible = "simple-audio-card";
simple-audio-card,name = "dfsdm_pdm";
dfsdm0_mic0: simple-audio-card,dai-link@0 {
I'd suggest moving to the graph card.
yes should be in V4
format = "pdm";
cpu {
sound-dai = <&asoc_pdm1>;
This phandle doesn't point to anything.
};
dmic0_codec: codec {
sound-dai = <&dmic0>;
};
};
- };
\ No newline at end of file
^^^
-- 1.9.1
Hello Lars,
Any news on the upstream of your HW consumer interface, prerequisite for my STM32 DFSDM upstream? Don't hesitate if i can help...
Regards Arnaud
On 03/17/2017 03:08 PM, Arnaud POULIQUEN wrote:
From: Lars-Peter Clausen lars@metafoo.de
Hardware consumer's can be used when one IIO device has a direct connection to another device in hardware.
Signed-off-by: Lars-Peter Clausen lars@metafoo.de Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com
V2 -> V3: Just Adding sign off. No update but Jonathan comment. Need to be taken into account (in agreement with Lars) when transforming rfc in patch.
drivers/iio/Kconfig | 6 ++ drivers/iio/Makefile | 1 + drivers/iio/hw_consumer.c | 156 ++++++++++++++++++++++++++++++++++++++++ include/linux/iio/hw_consumer.h | 12 ++++ 4 files changed, 175 insertions(+) create mode 100644 drivers/iio/hw_consumer.c create mode 100644 include/linux/iio/hw_consumer.h
diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig index 6743b18..956dd18 100644 --- a/drivers/iio/Kconfig +++ b/drivers/iio/Kconfig @@ -30,6 +30,12 @@ config IIO_CONFIGFS (e.g. software triggers). For more info see Documentation/iio/iio_configfs.txt.
+config IIO_HW_CONSUMER
- tristate
- help
Hardware consumer buffer
config IIO_TRIGGER bool "Enable triggered sampling support" help diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile index 87e4c43..8472b97 100644 --- a/drivers/iio/Makefile +++ b/drivers/iio/Makefile @@ -6,6 +6,7 @@ obj-$(CONFIG_IIO) += industrialio.o industrialio-y := industrialio-core.o industrialio-event.o inkern.o industrialio-$(CONFIG_IIO_BUFFER) += industrialio-buffer.o industrialio-$(CONFIG_IIO_TRIGGER) += industrialio-trigger.o +obj-$(CONFIG_IIO_HW_CONSUMER) += hw_consumer.o
obj-$(CONFIG_IIO_CONFIGFS) += industrialio-configfs.o obj-$(CONFIG_IIO_SW_DEVICE) += industrialio-sw-device.o diff --git a/drivers/iio/hw_consumer.c b/drivers/iio/hw_consumer.c new file mode 100644 index 0000000..66f0732 --- /dev/null +++ b/drivers/iio/hw_consumer.c @@ -0,0 +1,156 @@ +#include <linux/err.h> +#include <linux/export.h> +#include <linux/slab.h> +#include <linux/mutex.h> +#include <linux/of.h>
+#include <linux/iio/iio.h> +#include "iio_core.h" +#include <linux/iio/machine.h> +#include <linux/iio/driver.h> +#include <linux/iio/consumer.h> +#include <linux/iio/hw_consumer.h> +#include <linux/iio/buffer.h>
+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;
+};
+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->buffer.scan_mask);
- 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)
+{
- 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), GFP_KERNEL);
- if (!buf)
return NULL;
- buf->buffer.access = &iio_hw_buf_access;
- buf->indio_dev = indio_dev;
- buf->buffer.scan_mask = kcalloc(BITS_TO_LONGS(indio_dev->masklength),
sizeof(long), GFP_KERNEL);
- if (!buf->buffer.scan_mask)
goto err_free_buf;
- iio_buffer_init(&buf->buffer);
- list_add_tail(&buf->head, &hwc->buffers);
- return buf;
+err_free_buf:
- kfree(buf);
- return NULL;
+}
+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 == NULL) {
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);
+void iio_hw_consumer_free(struct iio_hw_consumer *hwc) +{
- struct hw_consumer_buffer *buf;
- iio_channel_release_all(hwc->channels);
- list_for_each_entry(buf, &hwc->buffers, head)
iio_buffer_put(&buf->buffer);
- kfree(hwc);
+} +EXPORT_SYMBOL_GPL(iio_hw_consumer_free);
+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);
+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); diff --git a/include/linux/iio/hw_consumer.h b/include/linux/iio/hw_consumer.h new file mode 100644 index 0000000..f12653d --- /dev/null +++ b/include/linux/iio/hw_consumer.h @@ -0,0 +1,12 @@ +#ifndef LINUX_IIO_HW_CONSUMER_BUFFER_H +#define LINUX_IIO_HW_CONSUMER_BUFFER_H
+struct device; +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
Hello Lars,
New gentlemen Reminder.
I suppose that you have to treat a lot of subjects with more priority than this one, but on my side I'm starting to have a lot of pressure from project, as STM32 DFSDM upstream is blocked since close to 6 months.
As you proposed the interface, I would be fair you could push it for upstream this month.
If you don't have time for this, another solution could be that I push it for you. Please tell me your preference.
Regards, Arnaud
On 06/06/2017 12:15 PM, Arnaud Pouliquen wrote:
Hello Lars,
Any news on the upstream of your HW consumer interface, prerequisite for my STM32 DFSDM upstream? Don't hesitate if i can help...
Regards Arnaud
On 03/17/2017 03:08 PM, Arnaud POULIQUEN wrote:
From: Lars-Peter Clausen lars@metafoo.de
Hardware consumer's can be used when one IIO device has a direct connection to another device in hardware.
Signed-off-by: Lars-Peter Clausen lars@metafoo.de Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com
V2 -> V3: Just Adding sign off. No update but Jonathan comment. Need to be taken into account (in agreement with Lars) when transforming rfc in patch.
drivers/iio/Kconfig | 6 ++ drivers/iio/Makefile | 1 + drivers/iio/hw_consumer.c | 156 ++++++++++++++++++++++++++++++++++++++++ include/linux/iio/hw_consumer.h | 12 ++++ 4 files changed, 175 insertions(+) create mode 100644 drivers/iio/hw_consumer.c create mode 100644 include/linux/iio/hw_consumer.h
diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig index 6743b18..956dd18 100644 --- a/drivers/iio/Kconfig +++ b/drivers/iio/Kconfig @@ -30,6 +30,12 @@ config IIO_CONFIGFS (e.g. software triggers). For more info see Documentation/iio/iio_configfs.txt.
+config IIO_HW_CONSUMER
- tristate
- help
Hardware consumer buffer
config IIO_TRIGGER bool "Enable triggered sampling support" help diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile index 87e4c43..8472b97 100644 --- a/drivers/iio/Makefile +++ b/drivers/iio/Makefile @@ -6,6 +6,7 @@ obj-$(CONFIG_IIO) += industrialio.o industrialio-y := industrialio-core.o industrialio-event.o inkern.o industrialio-$(CONFIG_IIO_BUFFER) += industrialio-buffer.o industrialio-$(CONFIG_IIO_TRIGGER) += industrialio-trigger.o +obj-$(CONFIG_IIO_HW_CONSUMER) += hw_consumer.o
obj-$(CONFIG_IIO_CONFIGFS) += industrialio-configfs.o obj-$(CONFIG_IIO_SW_DEVICE) += industrialio-sw-device.o diff --git a/drivers/iio/hw_consumer.c b/drivers/iio/hw_consumer.c new file mode 100644 index 0000000..66f0732 --- /dev/null +++ b/drivers/iio/hw_consumer.c @@ -0,0 +1,156 @@ +#include <linux/err.h> +#include <linux/export.h> +#include <linux/slab.h> +#include <linux/mutex.h> +#include <linux/of.h>
+#include <linux/iio/iio.h> +#include "iio_core.h" +#include <linux/iio/machine.h> +#include <linux/iio/driver.h> +#include <linux/iio/consumer.h> +#include <linux/iio/hw_consumer.h> +#include <linux/iio/buffer.h>
+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;
+};
+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->buffer.scan_mask);
- 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)
+{
- 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), GFP_KERNEL);
- if (!buf)
return NULL;
- buf->buffer.access = &iio_hw_buf_access;
- buf->indio_dev = indio_dev;
- buf->buffer.scan_mask = kcalloc(BITS_TO_LONGS(indio_dev->masklength),
sizeof(long), GFP_KERNEL);
- if (!buf->buffer.scan_mask)
goto err_free_buf;
- iio_buffer_init(&buf->buffer);
- list_add_tail(&buf->head, &hwc->buffers);
- return buf;
+err_free_buf:
- kfree(buf);
- return NULL;
+}
+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 == NULL) {
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);
+void iio_hw_consumer_free(struct iio_hw_consumer *hwc) +{
- struct hw_consumer_buffer *buf;
- iio_channel_release_all(hwc->channels);
- list_for_each_entry(buf, &hwc->buffers, head)
iio_buffer_put(&buf->buffer);
- kfree(hwc);
+} +EXPORT_SYMBOL_GPL(iio_hw_consumer_free);
+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);
+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); diff --git a/include/linux/iio/hw_consumer.h b/include/linux/iio/hw_consumer.h new file mode 100644 index 0000000..f12653d --- /dev/null +++ b/include/linux/iio/hw_consumer.h @@ -0,0 +1,12 @@ +#ifndef LINUX_IIO_HW_CONSUMER_BUFFER_H +#define LINUX_IIO_HW_CONSUMER_BUFFER_H
+struct device; +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
participants (5)
-
Arnaud Pouliquen
-
Jonathan Cameron
-
kbuild test robot
-
Mark Brown
-
Rob Herring