[alsa-devel] [PATCH 0/7] Add STM32 DFSDM support
This patch-set handles the Digital Filter for Sigma Delta modulators IP on STM32 platforms. DFSDM IP allows PDM microphone capture and sigma delta ADC conversion.
Those two features are mixed into the registers of the same hardware block which lead to introduce a multifunction driver on the top of them to be able to share the registers.
for This, MFD exports an API based on get/configure/start/stop/release mechanisms.
Two kind of resources are handled by the MFD driver. - The filters: - 4 instances available for L4 and H7 - composed with a SInC filter and an integrator - The transceivers or channels - 8 instances available for L4 and H7 - can be connected to serial transceivers (SPI or Manchester mode) internal parallel transceivers (from memory or internal ADC) - associated monitoring: analog watchdog, short circuit detector, extreme detector (monitoring support will be part of a specific patch-set))
Principle OF the DFSDM IP is to connect one or several channels to a filter to process Sigma delta ADC or PDM microphone data.
Two types of child devices can de declared: IIO for the management of Sigma Delta ADC conversion. ASoC for PDM microphone audio capture.
For details on IP and use case examples please refer to this document: http://www.st.com/content/ccc/resource/training/technical/product_training/9...
Remark: For audio part a patch is proposed to update core part to add copy support in soc_dmaengine_pcm. Rational is that output data register in DFSDM contains audio sample on 24 MSB + channel ID on the 8 LSB. Proposal is to allow to register a copy ops in soc_dmaengine_pcm to be able to perform post-processing. Back up if not accepted can be to create a pcm_engine in stm32 driver.
Not part of this patch-set, but should come as add-on patches: - IIO management of Analog watchdog, short-circuit detection and clock absence detector, with associated IRQs management. - IIO trigger and buffer management.
Arnaud Pouliquen (6): MFD: add bindings for STM32 DFSDM driver MFD: add STM32 DFSDM support IIO: add bindings for STM32 DFSDM ADC driver IIO: add STM32 DFSDM ADC support ASoC: add bindings for STM32 DFSDM driver ASoC: add STM32 DFSDM support
olivier moysan (1): ASoC: dmaengine_pcm: add copy support
.../bindings/iio/adc/st,stm32-dfsdm-adc.txt | 60 ++ .../devicetree/bindings/mfd/stm32-dfsdm.txt | 68 ++ .../devicetree/bindings/sound/st,sm32-adfsdm.txt | 84 ++ drivers/iio/adc/Kconfig | 9 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/stm32-dfsdm-adc.c | 676 +++++++++++++ drivers/mfd/Kconfig | 11 + drivers/mfd/Makefile | 2 + drivers/mfd/stm32-dfsdm-reg.h | 220 +++++ drivers/mfd/stm32-dfsdm.c | 1044 ++++++++++++++++++++ include/linux/mfd/stm32-dfsdm.h | 324 ++++++ include/sound/dmaengine_pcm.h | 3 + sound/soc/Kconfig | 1 + sound/soc/Makefile | 1 + sound/soc/soc-generic-dmaengine-pcm.c | 37 +- sound/soc/stm/Kconfig | 10 + sound/soc/stm/Makefile | 2 + sound/soc/stm/stm32_adfsdm.c | 686 +++++++++++++ 18 files changed, 3237 insertions(+), 2 deletions(-) create mode 100644 Documentation/devicetree/bindings/iio/adc/st,stm32-dfsdm-adc.txt create mode 100644 Documentation/devicetree/bindings/mfd/stm32-dfsdm.txt create mode 100644 Documentation/devicetree/bindings/sound/st,sm32-adfsdm.txt create mode 100644 drivers/iio/adc/stm32-dfsdm-adc.c create mode 100644 drivers/mfd/stm32-dfsdm-reg.h create mode 100644 drivers/mfd/stm32-dfsdm.c create mode 100644 include/linux/mfd/stm32-dfsdm.h create mode 100644 sound/soc/stm/Kconfig create mode 100644 sound/soc/stm/Makefile create mode 100644 sound/soc/stm/stm32_adfsdm.c
Add bindings information for STM32 Digital Filter for Sigma Delta modulators MFD driver
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com --- .../devicetree/bindings/mfd/stm32-dfsdm.txt | 68 ++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 Documentation/devicetree/bindings/mfd/stm32-dfsdm.txt
diff --git a/Documentation/devicetree/bindings/mfd/stm32-dfsdm.txt b/Documentation/devicetree/bindings/mfd/stm32-dfsdm.txt new file mode 100644 index 0000000..e0b45ee --- /dev/null +++ b/Documentation/devicetree/bindings/mfd/stm32-dfsdm.txt @@ -0,0 +1,68 @@ +STMicroelectronics STM32 Digital Filter for Sigma Delta Modulator (DFSDM) +ulti-function device. + +The STM32 DFSDM device is a multifunction device that handles the DFSDM IP. + +The DFSDM IP allows to add processing on Sigma Delta ADC based on SinC filters. +For this, a pool of m filters can be connected to a pool of n channels. +For STM32H7 : m = 4, n = 8. + +Each channel n is assigned to the SPI or Manchester interface n or n + 1. +Channels 0 to 2 can also be connected to ADC IP instance 1 to 3. +Filtering result is stored in a left aligned register, with 8 LSB reserved for +the input channel ID. + +Each filter instance supports two contexts to manage conversions, each one has +its own configurable sequence and trigger: +- regular conversion: used for single or continuous conversion. +- injected conversions: used for triggered conversion. + +Interfaces supported: +- sigma delta ADCs trough IIO framework. +- PDM microphones through ASoC framework. + +Required properties: +- compatible: Must be "st,stm32h7-dfsdm". +- reg: Specifies the DFSDM block register address and length. +- interrupts: IRQ lines connected to each DFSDM filter instance. +- clocks: IP and serial interfaces clocking. Should be set according + to rcc clock ID and "clock-names". +- clock-names: Input clock name "dfsdm_clk" must be defined, + "audio_clk" is optional. If defined CLKOUT is based on the audio + clock, else "dfsdm_clk" is used. + +Optional properties: +- st,clkout-freq: clkout clock frequency (Hz).This clock must be set according + to "clock" property. Frequency must be a multiple of the rcc + clock frequency. If not, clkout frequency will not be + accurate. +- pinctrl-names: set to "default". +- pinctrl-0: List of phandles pointing to pin configuration nodes for DFSDM + module. + For Pinctrl properties see ../pinctrl/pinctrl-bindings.txt +Example : + dfsdm: dfsdm@4400D000 { + compatible = "st,stm32h7-dfsdm"; + reg = <0x40017000 0x400>; + interrupts = <110>, <111>, <112>, <113>; + clocks = <&timer_clk>; + clock-names = "dfsdm_clk"; + pinctrl-0 = <&dfsdm_ch0 &dfsdm_ch1>; + pinctrl-names = "default"; + st,clkout-freq = <2480000>; + + iio_dfsdm0: iio-dfsdm@0 { + compatible = "st,stm32-dfsdm-adc"; + #io-channel-cells = <1>; + reg = <0>; + status = "disabled"; + }; + dai_dfsdm0: dfsdm-audio@0 { + compatible = "st,stm32-dfsdm-audio"; + #sound-dai-cells = <0>; + reg = <0>; + dmas = <&dmamux1 101 0x400 0x00>; + dma-names = "rx"; + status = "disabled"; + }; + };
On Mon, Jan 23, 2017 at 05:32:19PM +0100, Arnaud Pouliquen wrote:
Add bindings information for STM32 Digital Filter for Sigma Delta modulators MFD driver
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com
.../devicetree/bindings/mfd/stm32-dfsdm.txt | 68 ++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 Documentation/devicetree/bindings/mfd/stm32-dfsdm.txt
diff --git a/Documentation/devicetree/bindings/mfd/stm32-dfsdm.txt b/Documentation/devicetree/bindings/mfd/stm32-dfsdm.txt new file mode 100644 index 0000000..e0b45ee --- /dev/null +++ b/Documentation/devicetree/bindings/mfd/stm32-dfsdm.txt @@ -0,0 +1,68 @@ +STMicroelectronics STM32 Digital Filter for Sigma Delta Modulator (DFSDM) +ulti-function device.
+The STM32 DFSDM device is a multifunction device that handles the DFSDM IP.
+The DFSDM IP allows to add processing on Sigma Delta ADC based on SinC filters. +For this, a pool of m filters can be connected to a pool of n channels. +For STM32H7 : m = 4, n = 8.
+Each channel n is assigned to the SPI or Manchester interface n or n + 1. +Channels 0 to 2 can also be connected to ADC IP instance 1 to 3. +Filtering result is stored in a left aligned register, with 8 LSB reserved for +the input channel ID.
+Each filter instance supports two contexts to manage conversions, each one has +its own configurable sequence and trigger: +- regular conversion: used for single or continuous conversion. +- injected conversions: used for triggered conversion.
+Interfaces supported: +- sigma delta ADCs trough IIO framework. +- PDM microphones through ASoC framework.
Bindings describe h/w, not Linux subsystems.
+Required properties: +- compatible: Must be "st,stm32h7-dfsdm". +- reg: Specifies the DFSDM block register address and length. +- interrupts: IRQ lines connected to each DFSDM filter instance. +- clocks: IP and serial interfaces clocking. Should be set according
to rcc clock ID and "clock-names".
+- clock-names: Input clock name "dfsdm_clk" must be defined,
"audio_clk" is optional. If defined CLKOUT is based on the audio
clock, else "dfsdm_clk" is used.
_clk is redundant.
+Optional properties: +- st,clkout-freq: clkout clock frequency (Hz).This clock must be set according
to "clock" property. Frequency must be a multiple of the rcc
clock frequency. If not, clkout frequency will not be
accurate.
What is CLKOUT connected to and will you need to describe that in DT?
+- pinctrl-names: set to "default". +- pinctrl-0: List of phandles pointing to pin configuration nodes for DFSDM
module.
For Pinctrl properties see ../pinctrl/pinctrl-bindings.txt
+Example :
- dfsdm: dfsdm@4400D000 {
compatible = "st,stm32h7-dfsdm";
reg = <0x40017000 0x400>;
interrupts = <110>, <111>, <112>, <113>;
clocks = <&timer_clk>;
clock-names = "dfsdm_clk";
pinctrl-0 = <&dfsdm_ch0 &dfsdm_ch1>;
pinctrl-names = "default";
st,clkout-freq = <2480000>;
iio_dfsdm0: iio-dfsdm@0 {
adc {
compatible = "st,stm32-dfsdm-adc";
#io-channel-cells = <1>;
reg = <0>;
status = "disabled";
};
dai_dfsdm0: dfsdm-audio@0 {
digital-mic {
compatible = "st,stm32-dfsdm-audio";
#sound-dai-cells = <0>;
reg = <0>;
You can't have 2 children with the same unit address. Just drop reg and the unit address.
dmas = <&dmamux1 101 0x400 0x00>;
dma-names = "rx";
status = "disabled";
};
- };
-- 1.9.1
Hello Rob,
Thanks for the review, FYI This patch-set has to be abandoned to be redesigned without MFD driver. Anyway i will take into account your remarks in my redesign.
I just added a comment below to explain CLKOUT.
Regards Arnaud
On 01/27/2017 09:53 PM, Rob Herring wrote:
On Mon, Jan 23, 2017 at 05:32:19PM +0100, Arnaud Pouliquen wrote:
Add bindings information for STM32 Digital Filter for Sigma Delta modulators MFD driver
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com
.../devicetree/bindings/mfd/stm32-dfsdm.txt | 68 ++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 Documentation/devicetree/bindings/mfd/stm32-dfsdm.txt
diff --git a/Documentation/devicetree/bindings/mfd/stm32-dfsdm.txt b/Documentation/devicetree/bindings/mfd/stm32-dfsdm.txt new file mode 100644 index 0000000..e0b45ee --- /dev/null +++ b/Documentation/devicetree/bindings/mfd/stm32-dfsdm.txt @@ -0,0 +1,68 @@ +STMicroelectronics STM32 Digital Filter for Sigma Delta Modulator (DFSDM) +ulti-function device.
+The STM32 DFSDM device is a multifunction device that handles the DFSDM IP.
+The DFSDM IP allows to add processing on Sigma Delta ADC based on SinC filters. +For this, a pool of m filters can be connected to a pool of n channels. +For STM32H7 : m = 4, n = 8.
+Each channel n is assigned to the SPI or Manchester interface n or n + 1. +Channels 0 to 2 can also be connected to ADC IP instance 1 to 3. +Filtering result is stored in a left aligned register, with 8 LSB reserved for +the input channel ID.
+Each filter instance supports two contexts to manage conversions, each one has +its own configurable sequence and trigger: +- regular conversion: used for single or continuous conversion. +- injected conversions: used for triggered conversion.
+Interfaces supported: +- sigma delta ADCs trough IIO framework. +- PDM microphones through ASoC framework.
Bindings describe h/w, not Linux subsystems.
+Required properties: +- compatible: Must be "st,stm32h7-dfsdm". +- reg: Specifies the DFSDM block register address and length. +- interrupts: IRQ lines connected to each DFSDM filter instance. +- clocks: IP and serial interfaces clocking. Should be set according
to rcc clock ID and "clock-names".
+- clock-names: Input clock name "dfsdm_clk" must be defined,
"audio_clk" is optional. If defined CLKOUT is based on the audio
clock, else "dfsdm_clk" is used.
_clk is redundant.
+Optional properties: +- st,clkout-freq: clkout clock frequency (Hz).This clock must be set according
to "clock" property. Frequency must be a multiple of the rcc
clock frequency. If not, clkout frequency will not be
accurate.
What is CLKOUT connected to and will you need to describe that in DT?
CLKOUT can be outputted to clock SPI interfaces (master mode) or extern ADC. This CLKOUT is common for the 8 instances of the SPIs of the DFSDM. CLKOUT frequency is computed based on an internal division of the "dfsdm_clk" or "audio_clk".
+- pinctrl-names: set to "default". +- pinctrl-0: List of phandles pointing to pin configuration nodes for DFSDM
module.
For Pinctrl properties see ../pinctrl/pinctrl-bindings.txt
+Example :
- dfsdm: dfsdm@4400D000 {
compatible = "st,stm32h7-dfsdm";
reg = <0x40017000 0x400>;
interrupts = <110>, <111>, <112>, <113>;
clocks = <&timer_clk>;
clock-names = "dfsdm_clk";
pinctrl-0 = <&dfsdm_ch0 &dfsdm_ch1>;
pinctrl-names = "default";
st,clkout-freq = <2480000>;
iio_dfsdm0: iio-dfsdm@0 {
adc {
compatible = "st,stm32-dfsdm-adc";
#io-channel-cells = <1>;
reg = <0>;
status = "disabled";
};
dai_dfsdm0: dfsdm-audio@0 {
digital-mic {
compatible = "st,stm32-dfsdm-audio";
#sound-dai-cells = <0>;
reg = <0>;
You can't have 2 children with the same unit address. Just drop reg and the unit address.
dmas = <&dmamux1 101 0x400 0x00>;
dma-names = "rx";
status = "disabled";
};
- };
-- 1.9.1
DFSDM hardware IP can be used at the same time for ADC sigma delta conversion and audio PDM microphone. MFD driver is in charge of configuring IP registers and managing IP clocks. For this it exports an API to handles filters and channels resources.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com --- drivers/mfd/Kconfig | 11 + drivers/mfd/Makefile | 2 + drivers/mfd/stm32-dfsdm-reg.h | 220 +++++++++ drivers/mfd/stm32-dfsdm.c | 1044 +++++++++++++++++++++++++++++++++++++++ include/linux/mfd/stm32-dfsdm.h | 324 ++++++++++++ 5 files changed, 1601 insertions(+) create mode 100644 drivers/mfd/stm32-dfsdm-reg.h create mode 100644 drivers/mfd/stm32-dfsdm.c create mode 100644 include/linux/mfd/stm32-dfsdm.h
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index c6df644..4bb660b 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -1607,6 +1607,17 @@ config MFD_STW481X in various ST Microelectronics and ST-Ericsson embedded Nomadik series.
+config MFD_STM32_DFSDM + tristate "ST Microelectronics STM32 DFSDM" + depends on (ARCH_STM32 && OF) || COMPILE_TEST + select MFD_CORE + select REGMAP + select REGMAP_MMIO + help + Select this option to enable the STM32 Digital Filter + for Sigma Delta Modulators (DFSDM) driver used + in various STM32 series. + menu "Multimedia Capabilities Port drivers" depends on ARCH_SA1100
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 9834e66..1f095e5 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -211,3 +211,5 @@ obj-$(CONFIG_INTEL_SOC_PMIC) += intel-soc-pmic.o obj-$(CONFIG_MFD_MT6397) += mt6397-core.o
obj-$(CONFIG_MFD_ALTERA_A10SR) += altera-a10sr.o + +obj-$(CONFIG_MFD_STM32_DFSDM) += stm32-dfsdm.o \ No newline at end of file diff --git a/drivers/mfd/stm32-dfsdm-reg.h b/drivers/mfd/stm32-dfsdm-reg.h new file mode 100644 index 0000000..05ff702 --- /dev/null +++ b/drivers/mfd/stm32-dfsdm-reg.h @@ -0,0 +1,220 @@ +/* + * This file is part of STM32 DFSDM mfd 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_REG_H +#define MDF_STM32_DFSDM_REG_H + +#include <linux/bitfield.h> +/* + * Channels 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 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) + +#endif diff --git a/drivers/mfd/stm32-dfsdm.c b/drivers/mfd/stm32-dfsdm.c new file mode 100644 index 0000000..81ca29c --- /dev/null +++ b/drivers/mfd/stm32-dfsdm.c @@ -0,0 +1,1044 @@ +/* + * This file is part of STM32 DFSDM mfd 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/mfd/core.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/regmap.h> +#include <linux/slab.h> + +#include <linux/mfd/stm32-dfsdm.h> + +#include "stm32-dfsdm-reg.h" + +#define DFSDM_UPDATE_BITS(regm, reg, mask, val) \ + WARN_ON(regmap_update_bits(regm, reg, mask, val)) + +#define DFSDM_REG_READ(regm, reg, val) \ + WARN_ON(regmap_read(regm, reg, val)) + +#define DFSDM_REG_WRITE(regm, reg, val) \ + WARN_ON(regmap_write(regm, reg, val)) + +#define STM32H7_DFSDM_NUM_FILTERS 4 +#define STM32H7_DFSDM_NUM_INPUTS 8 + +enum dfsdm_clkout_src { + DFSDM_CLK, + AUDIO_CLK +}; + +struct stm32_dev_data { + const struct stm32_dfsdm dfsdm; + const struct regmap_config *regmap_cfg; +}; + +struct dfsdm_priv; + +struct filter_params { + unsigned int id; + int irq; + struct stm32_dfsdm_fl_event event; + u32 event_mask; + struct dfsdm_priv *priv; /* Cross ref for context */ + unsigned int ext_ch_mask; + unsigned int scan_ch; +}; + +struct ch_params { + struct stm32_dfsdm_channel ch; +}; + +struct dfsdm_priv { + struct platform_device *pdev; + struct stm32_dfsdm dfsdm; + + spinlock_t lock; /* Used for resource sharing & interrupt lock */ + + /* Filters */ + struct filter_params *filters; + unsigned int free_filter_mask; + unsigned int scd_filter_mask; + unsigned int ckab_filter_mask; + + /* Channels */ + struct stm32_dfsdm_channel *channels; + unsigned int free_channel_mask; + atomic_t n_active_ch; + + /* Clock */ + struct clk *clk; + struct clk *aclk; + unsigned int clkout_div; + unsigned int clkout_freq_req; + + /* Registers*/ + void __iomem *base; + struct regmap *regmap; + phys_addr_t phys_base; +}; + +/* + * Common + */ +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 = DFSDM_CNVTIMR(STM32H7_DFSDM_NUM_FILTERS - 1), + .volatile_reg = stm32_dfsdm_volatile_reg, + .fast_io = true, +}; + +static const struct stm32_dev_data stm32h7_data = { + .dfsdm.max_channels = STM32H7_DFSDM_NUM_INPUTS, + .dfsdm.max_filters = STM32H7_DFSDM_NUM_FILTERS, + .regmap_cfg = &stm32h7_dfsdm_regmap_cfg, +}; + +static int stm32_dfsdm_start_dfsdm(struct dfsdm_priv *priv) +{ + int ret; + struct device *dev = &priv->pdev->dev; + + if (atomic_inc_return(&priv->n_active_ch) == 1) { + 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"); + return ret; + } + } + DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CHCFGR1(0), + DFSDM_CHCFGR1_CKOUTDIV_MASK, + DFSDM_CHCFGR1_CKOUTDIV(priv->clkout_div)); + + DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CHCFGR1(0), + DFSDM_CHCFGR1_DFSDMEN_MASK, + DFSDM_CHCFGR1_DFSDMEN(1)); + } + + dev_dbg(&priv->pdev->dev, "%s: n_active_ch %d\n", __func__, + atomic_read(&priv->n_active_ch)); + + return 0; +} + +static void stm32_dfsdm_stop_dfsdm(struct dfsdm_priv *priv) +{ + if (atomic_dec_and_test(&priv->n_active_ch)) { + DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CHCFGR1(0), + DFSDM_CHCFGR1_DFSDMEN_MASK, + DFSDM_CHCFGR1_DFSDMEN(0)); + DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CHCFGR1(0), + DFSDM_CHCFGR1_CKOUTDIV_MASK, + DFSDM_CHCFGR1_CKOUTDIV(0)); + 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)); +} + +static unsigned int stm32_dfsdm_get_clkout_divider(struct dfsdm_priv *priv, + unsigned long rate) +{ + unsigned int delta, div; + + /* div = 0 disables the clockout */ + if (!priv->clkout_freq_req) + return 0; + + div = DIV_ROUND_CLOSEST(rate, priv->clkout_freq_req); + + delta = rate - (priv->clkout_freq_req * div); + if (delta) + dev_warn(&priv->pdev->dev, + "clkout not accurate. delta (Hz): %d\n", delta); + + dev_dbg(&priv->pdev->dev, "%s: clk: %lu (Hz), div %u\n", + __func__, rate, div); + + return (div - 1); +} + +/* + * Filters + */ + +static int stm32_dfsdm_clear_event(struct dfsdm_priv *priv, unsigned int fl_id, + unsigned int event, int mask) +{ + int val; + + switch (event) { + case DFSDM_EVENT_INJ_EOC: + DFSDM_REG_READ(priv->regmap, DFSDM_JDATAR(fl_id), &val); + break; + case DFSDM_EVENT_REG_EOC: + DFSDM_REG_READ(priv->regmap, DFSDM_RDATAR(fl_id), &val); + break; + case DFSDM_EVENT_INJ_XRUN: + DFSDM_UPDATE_BITS(priv->regmap, DFSDM_ICR(fl_id), + DFSDM_ICR_CLRJOVRF_MASK, + DFSDM_ICR_CLRJOVRF_MASK); + break; + case DFSDM_EVENT_REG_XRUN: + DFSDM_UPDATE_BITS(priv->regmap, DFSDM_ICR(fl_id), + DFSDM_ICR_CLRROVRF_MASK, + DFSDM_ICR_CLRROVRF_MASK); + break; + default: + return -EINVAL; + } + + return 0; +} + +static irqreturn_t stm32_dfsdm_irq(int irq, void *arg) +{ + struct filter_params *params = arg; + unsigned int status; + struct dfsdm_priv *priv = params->priv; + unsigned int event_mask = params->event_mask; + + DFSDM_REG_READ(priv->regmap, DFSDM_ISR(params->id), &status); + + if (status & DFSDM_ISR_JOVRF_MASK) { + if (event_mask & DFSDM_EVENT_INJ_XRUN) { + params->event.cb(&priv->dfsdm, params->id, + DFSDM_EVENT_INJ_XRUN, 0, + params->event.context); + } + stm32_dfsdm_clear_event(priv, params->id, DFSDM_EVENT_INJ_XRUN, + 0); + } + + if (status & DFSDM_ISR_ROVRF_MASK) { + if (event_mask & DFSDM_EVENT_REG_XRUN) { + params->event.cb(&priv->dfsdm, params->id, + DFSDM_EVENT_REG_XRUN, 0, + params->event.context); + } + stm32_dfsdm_clear_event(priv, params->id, DFSDM_EVENT_REG_XRUN, + 0); + } + + if (status & DFSDM_ISR_JEOCF_MASK) { + if (event_mask & DFSDM_EVENT_INJ_EOC) + params->event.cb(&priv->dfsdm, params->id, + DFSDM_EVENT_INJ_EOC, 0, + params->event.context); + else + stm32_dfsdm_clear_event(priv, params->id, + DFSDM_EVENT_INJ_EOC, 0); + } + + if (status & DFSDM_ISR_REOCF_MASK) { + if (event_mask & DFSDM_EVENT_REG_EOC) + params->event.cb(&priv->dfsdm, params->id, + DFSDM_EVENT_REG_EOC, 0, + params->event.context); + else + stm32_dfsdm_clear_event(priv, params->id, + DFSDM_EVENT_REG_EOC, 0); + } + + return IRQ_HANDLED; +} + +static void stm32_dfsdm_configure_reg_conv(struct dfsdm_priv *priv, + unsigned int fl_id, + struct stm32_dfsdm_regular *params) +{ + unsigned int ch_id = params->ch_src; + + DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR1(fl_id), DFSDM_CR1_RCH_MASK, + DFSDM_CR1_RCH(ch_id)); + DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR1(fl_id), DFSDM_CR1_FAST_MASK, + DFSDM_CR1_FAST(params->fast_mode)); + + DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR1(fl_id), DFSDM_CR1_RCONT_MASK, + DFSDM_CR1_RCONT(params->cont_mode)); + DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR1(fl_id), DFSDM_CR1_RDMAEN_MASK, + DFSDM_CR1_RDMAEN(params->dma_mode)); + DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR1(fl_id), DFSDM_CR1_RSYNC_MASK, + DFSDM_CR1_RSYNC(params->sync_mode)); + + priv->filters[fl_id].scan_ch = BIT(ch_id); +} + +static void stm32_dfsdm_configure_inj_conv(struct dfsdm_priv *priv, + unsigned int fl_id, + struct stm32_dfsdm_injected *params) +{ + int val; + + DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR1(fl_id), DFSDM_CR1_JSCAN_MASK, + DFSDM_CR1_JSCAN(params->scan_mode)); + DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR1(fl_id), DFSDM_CR1_JDMAEN_MASK, + DFSDM_CR1_JDMAEN(params->dma_mode)); + + val = (params->trigger == DFSDM_FILTER_EXT_TRIGGER) ? + params->trig_src : 0; + DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR1(fl_id), + DFSDM_CR1_JEXTSEL_MASK, + DFSDM_CR1_JEXTSEL(val)); + + val = (params->trigger == DFSDM_FILTER_SYNC_TRIGGER) ? 1 : 0; + DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR1(fl_id), DFSDM_CR1_JSYNC_MASK, + DFSDM_CR1_JSYNC(val)); + DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR1(fl_id), DFSDM_CR1_JEXTEN_MASK, + DFSDM_CR1_JEXTEN(params->trig_pol)); + priv->filters[fl_id].scan_ch = params->ch_group; + + DFSDM_REG_WRITE(priv->regmap, DFSDM_JCHGR(fl_id), params->ch_group); +} + +/** + * stm32_dfsdm_configure_filter - Configure filter. + * + * @dfsdm: Handle used to retrieve dfsdm context. + * @fl_id: Filter id. + * @conv: Conversion type regular or injected. + */ +int stm32_dfsdm_configure_filter(struct stm32_dfsdm *dfsdm, unsigned int fl_id, + struct stm32_dfsdm_filter *fl_cfg) +{ + struct dfsdm_priv *priv = container_of(dfsdm, struct dfsdm_priv, + dfsdm); + struct stm32_dfsdm_sinc_filter *sparams = &fl_cfg->sinc_params; + + dev_dbg(&priv->pdev->dev, "%s:config filter %d\n", __func__, fl_id); + + /* Average integrator oversampling */ + if ((!fl_cfg->int_oversampling) || + (fl_cfg->int_oversampling > DFSDM_MAX_INT_OVERSAMPLING)) { + dev_err(&priv->pdev->dev, "invalid integrator oversampling %d\n", + fl_cfg->int_oversampling); + return -EINVAL; + } + DFSDM_UPDATE_BITS(priv->regmap, DFSDM_FCR(fl_id), DFSDM_FCR_IOSR_MASK, + DFSDM_FCR_IOSR((fl_cfg->int_oversampling - 1))); + + /* Oversamplings and filter*/ + if ((!sparams->oversampling) || + (sparams->oversampling > DFSDM_MAX_FL_OVERSAMPLING)) { + dev_err(&priv->pdev->dev, "invalid oversampling %d\n", + sparams->oversampling); + return -EINVAL; + } + + if (sparams->order > DFSDM_SINC5_ORDER) { + dev_err(&priv->pdev->dev, "invalid filter order %d\n", + sparams->order); + return -EINVAL; + } + + DFSDM_UPDATE_BITS(priv->regmap, DFSDM_FCR(fl_id), DFSDM_FCR_FOSR_MASK, + DFSDM_FCR_FOSR((sparams->oversampling - 1))); + + DFSDM_UPDATE_BITS(priv->regmap, DFSDM_FCR(fl_id), DFSDM_FCR_FORD_MASK, + DFSDM_FCR_FORD(sparams->order)); + + /* Conversion */ + if (fl_cfg->inj_params) + stm32_dfsdm_configure_inj_conv(priv, fl_id, fl_cfg->inj_params); + else if (fl_cfg->reg_params) + stm32_dfsdm_configure_reg_conv(priv, fl_id, fl_cfg->reg_params); + + priv->filters[fl_id].event = fl_cfg->event; + + return 0; +} +EXPORT_SYMBOL_GPL(dfsdm_configure_filter); + +/** + * stm32_dfsdm_start_filter - Start filter conversion. + * + * @dfsdm: Handle used to retrieve dfsdm context. + * @fl_id: Filter id. + * @conv: Conversion type regular or injected. + */ +void stm32_dfsdm_start_filter(struct stm32_dfsdm *dfsdm, unsigned int fl_id, + enum stm32_dfsdm_conv_type conv) +{ + struct dfsdm_priv *priv = container_of(dfsdm, struct dfsdm_priv, dfsdm); + + dev_dbg(&priv->pdev->dev, "%s:start filter %d\n", __func__, fl_id); + + DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR1(fl_id), DFSDM_CR1_DFEN_MASK, + DFSDM_CR1_DFEN(1)); + + if (conv == DFSDM_FILTER_REG_CONV) { + DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR1(fl_id), + DFSDM_CR1_RSWSTART_MASK, + DFSDM_CR1_RSWSTART(1)); + } else if (conv == DFSDM_FILTER_SW_INJ_CONV) { + DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR1(fl_id), + DFSDM_CR1_JSWSTART_MASK, + DFSDM_CR1_JSWSTART(1)); + } +} +EXPORT_SYMBOL_GPL(dfsdm_start_filter); + +/** + * stm32_dfsdm_stop_filter - Stop filter conversion. + * + * @dfsdm: Handle used to retrieve dfsdm context. + * @fl_id: Filter id. + */ +void stm32_dfsdm_stop_filter(struct stm32_dfsdm *dfsdm, unsigned int fl_id) +{ + struct dfsdm_priv *priv = container_of(dfsdm, struct dfsdm_priv, dfsdm); + + dev_dbg(&priv->pdev->dev, "%s:stop filter %d\n", __func__, fl_id); + + DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR1(fl_id), DFSDM_CR1_DFEN_MASK, + DFSDM_CR1_DFEN(0)); + priv->filters[fl_id].scan_ch = 0; +} +EXPORT_SYMBOL_GPL(dfsdm_stop_filter); + +/** + * stm32_dfsdm_read_fl_conv - Read filter conversion. + * + * @dfsdm: Handle used to retrieve dfsdm context. + * @fl_id: Filter id. + * @type: Regular or injected conversion. + */ +void stm32_dfsdm_read_fl_conv(struct stm32_dfsdm *dfsdm, unsigned int fl_id, + u32 *val, int *ch_id, + enum stm32_dfsdm_conv_type type) +{ + struct dfsdm_priv *priv = container_of(dfsdm, struct dfsdm_priv, dfsdm); + int reg_v, offset; + + if (type == DFSDM_FILTER_REG_CONV) + offset = DFSDM_RDATAR(fl_id); + else + offset = DFSDM_JDATAR(fl_id); + + DFSDM_REG_READ(priv->regmap, offset, ®_v); + + *ch_id = reg_v & DFSDM_DATAR_CH_MASK; + *val = reg_v & DFSDM_DATAR_DATA_MASK; +} +EXPORT_SYMBOL_GPL(dfsdm_read_fl_conv); + +/** + * stm32_dfsdm_get_filter - Get filter instance. + * + * @dfsdm: Handle used to retrieve dfsdm context. + * @fl_id: Filter instance to reserve. + * + * Reserves a DFSDM filter resource. + */ +int stm32_dfsdm_get_filter(struct stm32_dfsdm *dfsdm, unsigned int fl_id) +{ + struct dfsdm_priv *priv = container_of(dfsdm, struct dfsdm_priv, + dfsdm); + struct device *dev = &priv->pdev->dev; + + spin_lock(&priv->lock); + if (!(priv->free_filter_mask & BIT(fl_id))) { + spin_unlock(&priv->lock); + dev_err(dev, "filter resource %d available\n", fl_id); + return -EBUSY; + } + priv->free_filter_mask &= ~BIT(fl_id); + + spin_unlock(&priv->lock); + + dev_dbg(dev, "%s: new mask %#x\n", __func__, priv->free_filter_mask); + + return 0; +} +EXPORT_SYMBOL_GPL(dfsdm_get_filter); + +/** + * stm32_dfsdm_release_filter - Release filter instance. + * + * @dfsdm: Handle used to retrieve dfsdm context. + * @fl_id: Filter id. + * + * Free the DFSDM filter resource. + */ +void stm32_dfsdm_release_filter(struct stm32_dfsdm *dfsdm, unsigned int fl_id) +{ + struct dfsdm_priv *priv = container_of(dfsdm, struct dfsdm_priv, dfsdm); + + spin_lock(&priv->lock); + priv->free_filter_mask |= BIT(fl_id); + spin_unlock(&priv->lock); +} +EXPORT_SYMBOL_GPL(dfsdm_release_filter); + +/** + * stm32_dfsdm_get_filter_dma_addr - Get register address for dma transfer. + * + * @dfsdm: Handle used to retrieve dfsdm context. + * @fl_id: Filter id. + * @conv: Conversion type. + */ +dma_addr_t stm32_dfsdm_get_filter_dma_phy_addr(struct stm32_dfsdm *dfsdm, + unsigned int fl_id, + enum stm32_dfsdm_conv_type conv) +{ + struct dfsdm_priv *priv = container_of(dfsdm, struct dfsdm_priv, dfsdm); + + if (conv == DFSDM_FILTER_REG_CONV) + return (dma_addr_t)(priv->phys_base + DFSDM_RDATAR(fl_id)); + else + return (dma_addr_t)(priv->phys_base + DFSDM_JDATAR(fl_id)); +} + +/** + * stm32_dfsdm_register_fl_event - Register filter event. + * + * @dfsdm: Handle used to retrieve dfsdm context. + * @fl_id: Filter id. + * @event: Event to unregister. + * @chan_mask: Mask of channels associated to filter. + * + * The function enables associated IRQ. + */ +int stm32_dfsdm_register_fl_event(struct stm32_dfsdm *dfsdm, unsigned int fl_id, + enum stm32_dfsdm_events event, + unsigned int chan_mask) +{ + struct dfsdm_priv *priv = container_of(dfsdm, struct dfsdm_priv, dfsdm); + unsigned long flags, ulmask = chan_mask; + int ret, i; + + dev_dbg(&priv->pdev->dev, "%s:for filter %d: event %#x ch_mask %#x\n", + __func__, fl_id, event, chan_mask); + + if (event > DFSDM_EVENT_CKA) + return -EINVAL; + + /* Clear interrupt before enable them */ + ret = stm32_dfsdm_clear_event(priv, fl_id, event, chan_mask); + if (ret < 0) + return ret; + + spin_lock_irqsave(&priv->lock, flags); + /* Enable interrupts */ + switch (event) { + case DFSDM_EVENT_SCD: + for_each_set_bit(i, &ulmask, priv->dfsdm.max_channels) { + DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CHCFGR1(i), + DFSDM_CHCFGR1_SCDEN_MASK, + DFSDM_CHCFGR1_SCDEN(1)); + } + if (!priv->scd_filter_mask) + DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR2(0), + DFSDM_CR2_SCDIE_MASK, + DFSDM_CR2_SCDIE(1)); + priv->scd_filter_mask |= BIT(fl_id); + break; + case DFSDM_EVENT_CKA: + for_each_set_bit(i, &ulmask, priv->dfsdm.max_channels) { + DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CHCFGR1(i), + DFSDM_CHCFGR1_CKABEN_MASK, + DFSDM_CHCFGR1_CKABEN(1)); + } + if (!priv->ckab_filter_mask) + DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR2(0), + DFSDM_CR2_CKABIE_MASK, + DFSDM_CR2_CKABIE(1)); + priv->ckab_filter_mask |= BIT(fl_id); + break; + default: + DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR2(fl_id), event, event); + } + priv->filters[fl_id].event_mask |= event; + spin_unlock_irqrestore(&priv->lock, flags); + + return 0; +} +EXPORT_SYMBOL_GPL(dfsdm_register_fl_event); + +/** + * stm32_dfsdm_unregister_fl_event - Unregister filter event. + * + * @dfsdm: Handle used to retrieve dfsdm context. + * @fl_id: Filter id. + * @event: Event to unregister. + * @chan_mask: Mask of channels associated to filter. + * + * The function disables associated IRQ. + */ +int stm32_dfsdm_unregister_fl_event(struct stm32_dfsdm *dfsdm, + unsigned int fl_id, + enum stm32_dfsdm_events event, + unsigned int chan_mask) +{ + struct dfsdm_priv *priv = container_of(dfsdm, struct dfsdm_priv, dfsdm); + unsigned long flags, ulmask = chan_mask; + int i; + + dev_dbg(&priv->pdev->dev, "%s:for filter %d: event %#x ch_mask %#x\n", + __func__, fl_id, event, chan_mask); + + if (event > DFSDM_EVENT_CKA) + return -EINVAL; + + spin_lock_irqsave(&priv->lock, flags); + /* Disable interrupts */ + switch (event) { + case DFSDM_EVENT_SCD: + for_each_set_bit(i, &ulmask, priv->dfsdm.max_channels) { + DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CHCFGR1(i), + DFSDM_CHCFGR1_SCDEN_MASK, + DFSDM_CHCFGR1_SCDEN(0)); + } + priv->scd_filter_mask &= ~BIT(fl_id); + if (!priv->scd_filter_mask) + DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR2(0), + DFSDM_CR2_SCDIE_MASK, + DFSDM_CR2_SCDIE(0)); + break; + case DFSDM_EVENT_CKA: + for_each_set_bit(i, &ulmask, priv->dfsdm.max_channels) { + DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CHCFGR1(i), + DFSDM_CHCFGR1_CKABEN_MASK, + DFSDM_CHCFGR1_CKABEN(0)); + } + priv->ckab_filter_mask &= ~BIT(fl_id); + if (!priv->ckab_filter_mask) + DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR2(0), + DFSDM_CR2_CKABIE_MASK, + DFSDM_CR2_CKABIE(0)); + break; + default: + DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR2(fl_id), event, 0); + } + + priv->filters[fl_id].event_mask &= ~event; + spin_unlock_irqrestore(&priv->lock, flags); + + return 0; +} +EXPORT_SYMBOL_GPL(dfsdm_unregister_fl_event); + +/* + * Channels + */ +static void stm32_dfsdm_init_channel(struct dfsdm_priv *priv, + struct stm32_dfsdm_channel *ch) +{ + DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CHCFGR1(ch->id), + DFSDM_CHCFGR1_DATMPX_MASK, + DFSDM_CHCFGR1_DATMPX(ch->type.source)); + if (ch->type.source == DFSDM_CHANNEL_EXTERNAL_INPUTS) { + DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CHCFGR1(ch->id), + DFSDM_CHCFGR1_SITP_MASK, + DFSDM_CHCFGR1_SITP(ch->serial_if.type)); + DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CHCFGR1(ch->id), + DFSDM_CHCFGR1_SPICKSEL_MASK, + DFSDM_CHCFGR1_SPICKSEL(ch->serial_if.spi_clk)); + } + DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CHCFGR1(ch->id), + DFSDM_CHCFGR1_DATPACK_MASK, + DFSDM_CHCFGR1_DATPACK(ch->type.DataPacking)); + DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CHCFGR1(ch->id), + DFSDM_CHCFGR1_CHINSEL_MASK, + DFSDM_CHCFGR1_CHINSEL(ch->serial_if.pins)); +} + +/** + * stm32_dfsdm_start_channel - Configure and activate DFSDM channel. + * + * @dfsdm: Handle used to retrieve dfsdm context. + * @ch: Filter id. + * @cfg: Filter configuration. + */ +int stm32_dfsdm_start_channel(struct stm32_dfsdm *dfsdm, unsigned int ch_id, + struct stm32_dfsdm_ch_cfg *cfg) +{ + struct dfsdm_priv *priv = container_of(dfsdm, struct dfsdm_priv, + dfsdm); + struct regmap *reg = priv->regmap; + int ret; + + dev_dbg(&priv->pdev->dev, "%s: for channel %d\n", __func__, ch_id); + + ret = stm32_dfsdm_start_dfsdm(priv); + if (ret < 0) + return ret; + + DFSDM_UPDATE_BITS(reg, DFSDM_CHCFGR2(ch_id), DFSDM_CHCFGR2_DTRBS_MASK, + DFSDM_CHCFGR2_DTRBS(cfg->right_bit_shift)); + DFSDM_UPDATE_BITS(reg, DFSDM_CHCFGR2(ch_id), DFSDM_CHCFGR2_OFFSET_MASK, + DFSDM_CHCFGR2_OFFSET(cfg->offset)); + + DFSDM_UPDATE_BITS(reg, DFSDM_CHCFGR1(ch_id), DFSDM_CHCFGR1_CHEN_MASK, + DFSDM_CHCFGR1_CHEN(1)); + + /* Clear absence detection IRQ */ + DFSDM_UPDATE_BITS(priv->regmap, DFSDM_ICR(0), + DFSDM_ICR_CLRCKABF_CH_MASK(ch_id), + DFSDM_ICR_CLRCKABF_CH(1, ch_id)); + + return 0; +} +EXPORT_SYMBOL_GPL(dfsdm_start_channel); + +/** + * stm32_dfsdm_stop_channel - Deactivate channel. + * + * @dfsdm: Handle used to retrieve dfsdm context. + * @ch_id: DFSDM channel identifier. + */ +void stm32_dfsdm_stop_channel(struct stm32_dfsdm *dfsdm, unsigned int ch_id) +{ + struct dfsdm_priv *priv = container_of(dfsdm, struct dfsdm_priv, dfsdm); + + dev_dbg(&priv->pdev->dev, "%s:for channel %d\n", __func__, ch_id); + + DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CHCFGR1(ch_id), + DFSDM_CHCFGR1_CHEN_MASK, + DFSDM_CHCFGR1_CHEN(0)); + + DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CHCFGR1(ch_id), + DFSDM_CHCFGR1_CKABEN_MASK, DFSDM_CHCFGR1_CKABEN(0)); + + DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CHCFGR1(ch_id), + DFSDM_CHCFGR1_SCDEN_MASK, DFSDM_CHCFGR1_SCDEN(0)); + + stm32_dfsdm_stop_dfsdm(priv); +} +EXPORT_SYMBOL_GPL(dfsdm_stop_channel); + +/** + * stm32_dfsdm_get_channel - Get channel instance. + * + * @dfsdm: handle used to retrieve dfsdm context. + * @ch: DFSDM channel hardware parameters. + * + * Reserve DFSDM channel resource. + */ +int stm32_dfsdm_get_channel(struct stm32_dfsdm *dfsdm, + struct stm32_dfsdm_channel *ch) +{ + struct dfsdm_priv *priv = container_of(dfsdm, struct dfsdm_priv, dfsdm); + unsigned int id = ch->id; + + dev_dbg(&priv->pdev->dev, "%s:get channel %d\n", __func__, id); + + if (id >= priv->dfsdm.max_channels) { + dev_err(&priv->pdev->dev, "channel (%d) is not valid\n", id); + return -EINVAL; + } + + if ((ch->type.source != DFSDM_CHANNEL_EXTERNAL_INPUTS) & + (ch->serial_if.spi_clk != DFSDM_CHANNEL_SPI_CLOCK_EXTERNAL) & + (!priv->clkout_freq_req)) { + dev_err(&priv->pdev->dev, "clkout not present\n"); + return -EINVAL; + } + + spin_lock(&priv->lock); + if (!(BIT(id) & priv->free_channel_mask)) { + spin_unlock(&priv->lock); + dev_err(&priv->pdev->dev, "channel (%d) already in use\n", id); + return -EBUSY; + } + + priv->free_channel_mask &= ~BIT(id); + priv->channels[id] = *ch; + spin_unlock(&priv->lock); + + dev_dbg(&priv->pdev->dev, "%s: new mask %#x\n", __func__, + priv->free_channel_mask); + + /** + * Check clock constrainst between clkout and either + * dfsdm/audio clock: + * - In SPI mode (clkout is used): Fclk >= 4 * Fclkout + * (e.g. CKOUTDIV >= 3) + * - In mancherster mode: Fclk >= 6 * Fclkout + */ + switch (ch->serial_if.type) { + case DFSDM_CHANNEL_SPI_RISING: + case DFSDM_CHANNEL_SPI_FALLING: + if (priv->clkout_div && priv->clkout_div < 3) + dev_warn(&priv->pdev->dev, + "Clock div should be higher than 3\n"); + break; + case DFSDM_CHANNEL_MANCHESTER_RISING: + case DFSDM_CHANNEL_MANCHESTER_FALLING: + if (priv->clkout_div && priv->clkout_div < 5) + dev_warn(&priv->pdev->dev, + "Clock div should be higher than 5\n"); + break; + } + + stm32_dfsdm_init_channel(priv, ch); + + return 0; +} +EXPORT_SYMBOL_GPL(dfsdm_get_channel); + +/** + * stm32_dfsdm_release_channel - Release channel instance. + * + * @dfsdm: Handle used to retrieve dfsdm context. + * @ch_id: DFSDM channel identifier. + * + * Free the DFSDM channel resource. + */ +void stm32_dfsdm_release_channel(struct stm32_dfsdm *dfsdm, unsigned int ch_id) +{ + struct dfsdm_priv *priv = container_of(dfsdm, struct dfsdm_priv, dfsdm); + + spin_lock(&priv->lock); + priv->free_channel_mask |= BIT(ch_id); + spin_unlock(&priv->lock); +} +EXPORT_SYMBOL_GPL(dfsdm_release_channel); + +/** + * stm32_dfsdm_get_clk_out_rate - get clkout frequency. + * + * @dfsdm: handle used to retrieve dfsdm context. + * @rate: clock out rate in Hz. + * + * Provide output frequency used for external ADC. + * return EINVAL if clockout is not used else return 0. + */ +int stm32_dfsdm_get_clk_out_rate(struct stm32_dfsdm *dfsdm, unsigned long *rate) +{ + struct dfsdm_priv *priv = container_of(dfsdm, struct dfsdm_priv, dfsdm); + unsigned long int clk_rate; + + if (!priv->clkout_div) + return -EINVAL; + + clk_rate = clk_get_rate(priv->aclk ? priv->aclk : priv->clk); + *rate = clk_rate / (priv->clkout_div + 1); + dev_dbg(&priv->pdev->dev, "%s: clkout: %ld (Hz)\n", __func__, *rate); + + return 0; +} +EXPORT_SYMBOL_GPL(dfsdm_get_clk_out_rate); + +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; + int ret, val; + + 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->phys_base = res->start; + priv->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + ret = of_property_read_u32(node, "st,clkout-freq", &val); + if (!ret) { + if (!val) { + dev_err(&priv->pdev->dev, + "st,clkout-freq cannot be 0\n"); + return -EINVAL; + } + priv->clkout_freq_req = val; + } else if (ret != -EINVAL) { + dev_err(&priv->pdev->dev, "Failed to get st,clkout-freq\n"); + return ret; + } + + /* Source clock */ + priv->clk = devm_clk_get(&pdev->dev, "dfsdm_clk"); + 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_clk"); + if (IS_ERR(priv->aclk)) + priv->aclk = NULL; + + return 0; +}; + +static const struct of_device_id stm32_dfsdm_of_match[] = { + { + .compatible = "st,stm32h7-dfsdm", + .data = &stm32h7_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_dev_data *dev_data; + enum dfsdm_clkout_src clk_src; + 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_dev_data *)of_id->data; + + ret = stm32_dfsdm_parse_of(pdev, priv); + if (ret < 0) + return ret; + + priv->regmap = devm_regmap_init_mmio(&pdev->dev, priv->base, + dev_data->regmap_cfg); + if (IS_ERR(priv->regmap)) { + ret = PTR_ERR(priv->regmap); + dev_err(&pdev->dev, "%s: Failed to allocate regmap: %d\n", + __func__, ret); + return ret; + } + + priv->dfsdm = dev_data->dfsdm; + + priv->filters = devm_kcalloc(&pdev->dev, dev_data->dfsdm.max_filters, + sizeof(*priv->filters), GFP_KERNEL); + if (IS_ERR(priv->filters)) { + ret = PTR_ERR(priv->filters); + goto probe_err; + } + + for (i = 0; i < dev_data->dfsdm.max_filters; i++) { + struct filter_params *params = &priv->filters[i]; + + params->id = i; + params->irq = platform_get_irq(pdev, i); + if (params->irq < 0) { + dev_err(&pdev->dev, "Failed to get IRQ resource\n"); + ret = params->irq; + goto probe_err; + } + + ret = devm_request_irq(&pdev->dev, params->irq, stm32_dfsdm_irq, + 0, dev_name(&pdev->dev), params); + if (ret) { + dev_err(&pdev->dev, "Failed to register interrupt\n"); + goto probe_err; + } + + params->priv = priv; + } + + priv->channels = devm_kcalloc(&pdev->dev, priv->dfsdm.max_channels, + sizeof(*priv->channels), GFP_KERNEL); + if (IS_ERR(priv->channels)) { + ret = PTR_ERR(priv->channels); + goto probe_err; + } + priv->free_filter_mask = BIT(priv->dfsdm.max_filters) - 1; + priv->free_channel_mask = BIT(priv->dfsdm.max_channels) - 1; + + platform_set_drvdata(pdev, &priv->dfsdm); + spin_lock_init(&priv->lock); + + priv->clkout_div = stm32_dfsdm_get_clkout_divider(priv, + clk_get_rate(priv->clk)); + + ret = of_platform_populate(pnode, NULL, NULL, &pdev->dev); + if (ret < 0) + goto probe_err; + + clk_src = priv->aclk ? AUDIO_CLK : DFSDM_CLK; + + DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CHCFGR1(0), + DFSDM_CHCFGR1_CKOUTSRC_MASK, + DFSDM_CHCFGR1_CKOUTSRC(clk_src)); + return 0; + +probe_err: + return ret; +} + +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/include/linux/mfd/stm32-dfsdm.h b/include/linux/mfd/stm32-dfsdm.h new file mode 100644 index 0000000..f6eb788 --- /dev/null +++ b/include/linux/mfd/stm32-dfsdm.h @@ -0,0 +1,324 @@ +/* + * This file is part of STM32 DFSDM mfd driver API + * + * Copyright (C) 2017, STMicroelectronics - All Rights Reserved + * Author(s): Arnaud Pouliquen arnaud.pouliquen@st.com. + * + * License terms: GPL V2.0. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + */ +#ifndef MDF_STM32_DFSDM_H +#define MDF_STM32_DFSDM_H + +/* + * Channel definitions + */ +#define DFSDM_CHANNEL_0 BIT(0) +#define DFSDM_CHANNEL_1 BIT(1) +#define DFSDM_CHANNEL_2 BIT(2) +#define DFSDM_CHANNEL_3 BIT(3) +#define DFSDM_CHANNEL_4 BIT(4) +#define DFSDM_CHANNEL_5 BIT(5) +#define DFSDM_CHANNEL_6 BIT(6) +#define DFSDM_CHANNEL_7 BIT(7) + +/* DFSDM channel input data packing */ +enum stm32_dfsdm_data_packing { + DFSDM_CHANNEL_STANDARD_MODE, /* Standard data packing mode */ + DFSDM_CHANNEL_INTERLEAVED_MODE, /* Interleaved data packing mode */ + DFSDM_CHANNEL_DUAL_MODE /* Dual data packing mode */ +}; + +/* DFSDM channel input multiplexer */ +enum stm32_dfsdm_input_multiplexer { + DFSDM_CHANNEL_EXTERNAL_INPUTS, /* Data taken from external inputs */ + DFSDM_CHANNEL_INTERNAL_ADC, /* Data taken from internal ADC */ + DFSDM_CHANNEL_INTERNAL_REGISTER, /* Data taken from register */ +}; + +/* DFSDM channel serial interface type */ +enum stm32_dfsdm_serial_in_type { + DFSDM_CHANNEL_SPI_RISING, /* SPI with rising edge */ + DFSDM_CHANNEL_SPI_FALLING, /* SPI with falling edge */ + DFSDM_CHANNEL_MANCHESTER_RISING, /* Manchester with rising edge */ + DFSDM_CHANNEL_MANCHESTER_FALLING, /* Manchester with falling edge */ +}; + +/* DFSDM channel serial spi clock source */ +enum stm32_dfsdm_spi_clk_src { + /* External SPI clock */ + DFSDM_CHANNEL_SPI_CLOCK_EXTERNAL, + /* Internal SPI clock */ + DFSDM_CHANNEL_SPI_CLOCK_INTERNAL, + /* Internal SPI clock divided by 2, falling edge */ + DFSDM_CHANNEL_SPI_CLOCK_INTERNAL_DIV2_FALLING, + /* Internal SPI clock divided by 2, rising edge */ + DFSDM_CHANNEL_SPI_CLOCK_INTERNAL_DIV2_RISING +}; + +/* DFSDM channel input pins */ +enum stm32_dfsdm_serial_in_select { + /* Serial input taken from pins of the same channel (y) */ + DFSDM_CHANNEL_SAME_CHANNEL_PINS, + /* Serial input taken from pins of the following channel (y + 1)*/ + DFSDM_CHANNEL_NEXT_CHANNEL_PINS, +}; + +/** + * struct stm32_dfsdm_input_type - DFSDM channel init structure definition. + * @DataPacking: Standard, interleaved or dual mode for internal register. + * @source: channel source: internal DAC, serial input or memory. + */ +struct stm32_dfsdm_input_type { + enum stm32_dfsdm_data_packing DataPacking; + enum stm32_dfsdm_input_multiplexer source; +}; + +/** + * struct stm32_dfsdm_serial_if - DFSDM serial interface parameters. + * @type: Serial interface type. + * @spi_clk: SPI clock source. + * @pins: select serial interface associated to the channel + */ +struct stm32_dfsdm_serial_if { + enum stm32_dfsdm_serial_in_type type; + enum stm32_dfsdm_spi_clk_src spi_clk; + enum stm32_dfsdm_serial_in_select pins; +}; + +/** + * struct stm32_dfsdm_channel - DFSDM channel hardware parameters. + * @id: DFSDM channel identifier. + * @type: DFSDM channel input parameters. + * @serial_if: DFSDM channel serial interface parameters. + * Mandatory for DFSDM_CHANNEL_EXTERNAL_INPUTS. + */ +struct stm32_dfsdm_channel { + unsigned int id; + struct stm32_dfsdm_input_type type; + struct stm32_dfsdm_serial_if serial_if; +}; + +/** + * struct stm32_dfsdm_ch_cfg - DFSDM channel config. + * @offset: DFSDM channel 24 bit calibration offset. + * @right_bit_shift: DFSDM channel right bit shift of the data result. + */ +struct stm32_dfsdm_ch_cfg { + unsigned int offset; + unsigned int right_bit_shift; +}; + +/* + * Filter definitions + */ + +#define DFSDM_MIN_INT_OVERSAMPLING 1 +#define DFSDM_MAX_INT_OVERSAMPLING 256 +#define DFSDM_MIN_FL_OVERSAMPLING 1 +#define DFSDM_MAX_FL_OVERSAMPLING 1024 + +enum stm32_dfsdm_events { + DFSDM_EVENT_INJ_EOC = BIT(0), /* Injected end of conversion event */ + DFSDM_EVENT_REG_EOC = BIT(1), /* Regular end of conversion event */ + DFSDM_EVENT_INJ_XRUN = BIT(2), /* Injected conversion overrun event */ + DFSDM_EVENT_REG_XRUN = BIT(3), /* Regular conversion overrun event */ + DFSDM_EVENT_AWD = BIT(4), /* Analog watchdog event */ + DFSDM_EVENT_SCD = BIT(5), /* Short circuit detector event */ + DFSDM_EVENT_CKA = BIT(6), /* Clock abscence detection event */ +}; + +#define STM32_DFSDM_EVENT_MASK 0x3F + +/* 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, +}; + +/* DFSDM filter order */ +enum stm32_dfsdm_state { + DFSDM_DISABLE, + DFSDM_ENABLE, +}; + +/** + * struct stm32_dfsdm_sinc_filter - DFSDM Sinc filter structure definition + * @order: DFSM filter order. + * @oversampling: DFSDM filter oversampling: + * post processing filter: min = 1, max = 1024. + */ +struct stm32_dfsdm_sinc_filter { + enum stm32_dfsdm_sinc_order order; + unsigned int oversampling; +}; + +/* DFSDM filter conversion trigger */ +enum stm32_dfsdm_trigger { + DFSDM_FILTER_SW_TRIGGER, /* Software trigger */ + DFSDM_FILTER_SYNC_TRIGGER, /* Synchronous with DFSDM0 */ + DFSDM_FILTER_EXT_TRIGGER, /* External trigger (only for injected) */ +}; + +/* DFSDM filter external trigger polarity */ +enum stm32_dfsdm_filter_ext_trigger_pol { + DFSDM_FILTER_EXT_TRIG_NO_TRIG, /* Trigger disable */ + DFSDM_FILTER_EXT_TRIG_RISING_EDGE, /* Rising edge */ + DFSDM_FILTER_EXT_TRIG_FALLING_EDGE, /* Falling edge */ + DFSDM_FILTER_EXT_TRIG_BOTH_EDGES, /* Rising and falling edges */ +}; + +/* DFSDM filter conversion type */ +enum stm32_dfsdm_conv_type { + DFSDM_FILTER_REG_CONV, /* Regular conversion */ + DFSDM_FILTER_SW_INJ_CONV, /* Injected conversion */ + DFSDM_FILTER_TRIG_INJ_CONV, /* Injected conversion */ +}; + +/* DFSDM filter regular synchronous mode */ +enum stm32_dfsdm_conv_rsync { + DFSDM_FILTER_RSYNC_OFF, /* regular conversion asynchronous */ + DFSDM_FILTER_RSYNC_ON, /* regular conversion synchronous with filter0*/ +}; + +/** + * struct stm32_dfsdm_regular - DFSDM filter conversion parameters structure + * @ch_src: Channel source from 0 to 7. + * @fast_mode: Enable/disable fast mode for regular conversion. + * @dma_mode: Enable/disable dma mode. + * @cont_mode Enable/disable continuous conversion. + * @sync_mode Enable/disable synchro mode. + */ +struct stm32_dfsdm_regular { + unsigned int ch_src; + bool fast_mode; + bool dma_mode; + bool cont_mode; + bool sync_mode; +}; + +/** + * struct stm32_dfsdm_injected - DFSDM filter conversion parameters structure + * @trigger: Trigger used to start injected conversion. + * @trig_src: External trigger, 0 to 30 (refer to datasheet for details). + * @trig_pol: External trigger edge: software, rising, falling or both. + * @scan_mode: Enable/disable scan mode for injected conversion. + * @ch_group: mask containing channels to scan ( set bit y to scan + * channel y). + * @dma_mode: DFSDM channel input parameters. + */ +struct stm32_dfsdm_injected { + enum stm32_dfsdm_trigger trigger; + unsigned int trig_src; + enum stm32_dfsdm_filter_ext_trigger_pol trig_pol; + bool scan_mode; + unsigned int ch_group; + bool dma_mode; +}; + +struct stm32_dfsdm; + +/** + * struct stm32_dfsdm_fl_event - DFSDM filters event + * @cb: User event callback with parameters. be carful this function + * is called under threaded IRQ context: + * struct stm32_dfsdm *dfsdm: dfsdm handle, + * unsigned int fl_id: filter id, + * num stm32_dfsdm_events flag: event, + * param: parameter associated to the event, + * void *context: user context provided on registration. + * @context: User param to retrieve context. + */ +struct stm32_dfsdm_fl_event { + void (*cb)(struct stm32_dfsdm *, int, enum stm32_dfsdm_events, + unsigned int, void *); + void *context; +}; + +/** + * struct stm32_dfsdm_filter - DFSDM filter conversion parameters structure + * @reg_params: DFSDM regular conversion parameters. + * this param is optional and not taken into account if + * @inj_params is defined. + * @inj_params: DFSDM injected conversion parameters (optional). + * @filter_params: DFSDM filter parameters. + * @event: Events callback. + * @int_oversampling: Integrator oversampling ratio for average purpose + * (range from 1 to 256). + * @ext_det_ch_mask: Extreme detector mask for channel selection + * mask generated using DFSDM_CHANNEL_0 to + * DFSDM_CHANNEL_7. If 0 feature is disable. + */ +struct stm32_dfsdm_filter { + struct stm32_dfsdm_regular *reg_params; + struct stm32_dfsdm_injected *inj_params; + struct stm32_dfsdm_sinc_filter sinc_params; + struct stm32_dfsdm_fl_event event; + unsigned int int_oversampling; +}; + +/** + * struct stm32_dfsdm - DFSDM context structure. + * + * @trig_info: Trigger name and id available last member name is null. + * @max_channels: max number of channels available. + * @max_filters: max number of filters available. + * + * Notice That structure is filled by mdf driver and must not be updated by + * user. + */ +struct stm32_dfsdm { + unsigned int max_channels; + unsigned int max_filters; +}; + +int stm32_dfsdm_get_filter(struct stm32_dfsdm *dfsdm, unsigned int fl_id); +void stm32_dfsdm_release_filter(struct stm32_dfsdm *dfsdm, unsigned int fl_id); + +dma_addr_t stm32_dfsdm_get_filter_dma_phy_addr(struct stm32_dfsdm *dfsdm, + unsigned int fl_id, + enum stm32_dfsdm_conv_type conv); + +int stm32_dfsdm_configure_filter(struct stm32_dfsdm *dfsdm, unsigned int fl_id, + struct stm32_dfsdm_filter *filter); +void stm32_dfsdm_start_filter(struct stm32_dfsdm *dfsdm, unsigned int fl_id, + enum stm32_dfsdm_conv_type conv); +void stm32_dfsdm_stop_filter(struct stm32_dfsdm *dfsdm, unsigned int fl_id); + +void stm32_dfsdm_read_fl_conv(struct stm32_dfsdm *dfsdm, unsigned int fl_id, + u32 *val, int *ch_id, + enum stm32_dfsdm_conv_type type); + +int stm32_dfsdm_unregister_fl_event(struct stm32_dfsdm *dfsdm, + unsigned int fl_id, + enum stm32_dfsdm_events event, + unsigned int ch_mask); +int stm32_dfsdm_register_fl_event(struct stm32_dfsdm *dfsdm, unsigned int fl_id, + enum stm32_dfsdm_events event, + unsigned int ch_mask); + +int stm32_dfsdm_get_channel(struct stm32_dfsdm *dfsdm, + struct stm32_dfsdm_channel *ch); +void stm32_dfsdm_release_channel(struct stm32_dfsdm *dfsdm, unsigned int ch_id); + +int stm32_dfsdm_start_channel(struct stm32_dfsdm *dfsdm, unsigned int ch_id, + struct stm32_dfsdm_ch_cfg *cfg); +void stm32_dfsdm_stop_channel(struct stm32_dfsdm *dfsdm, unsigned int ch_id); + +int stm32_dfsdm_get_clk_out_rate(struct stm32_dfsdm *dfsdm, + unsigned long *rate); + +#endif
Hi Arnaud,
[auto build test WARNING on iio/togreg] [cannot apply to asoc/for-next ljones-mfd/for-mfd-next v4.10-rc5 next-20170123] [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/jic23/iio.git togreg config: i386-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=i386
All warnings (new ones prefixed by >>):
In file included from include/linux/linkage.h:6:0, from include/linux/kernel.h:6, from include/linux/clk.h:16, from drivers/mfd/stm32-dfsdm.c:19: drivers/mfd/stm32-dfsdm.c:397:19: error: 'dfsdm_configure_filter' undeclared here (not in a function) EXPORT_SYMBOL_GPL(dfsdm_configure_filter); ^ include/linux/export.h:58:16: note: in definition of macro '___EXPORT_SYMBOL' extern typeof(sym) sym; \ ^~~
drivers/mfd/stm32-dfsdm.c:397:1: note: in expansion of macro 'EXPORT_SYMBOL_GPL'
EXPORT_SYMBOL_GPL(dfsdm_configure_filter); ^~~~~~~~~~~~~~~~~ drivers/mfd/stm32-dfsdm.c:426:19: error: 'dfsdm_start_filter' undeclared here (not in a function) EXPORT_SYMBOL_GPL(dfsdm_start_filter); ^ include/linux/export.h:58:16: note: in definition of macro '___EXPORT_SYMBOL' extern typeof(sym) sym; \ ^~~ drivers/mfd/stm32-dfsdm.c:426:1: note: in expansion of macro 'EXPORT_SYMBOL_GPL' EXPORT_SYMBOL_GPL(dfsdm_start_filter); ^~~~~~~~~~~~~~~~~ drivers/mfd/stm32-dfsdm.c:444:19: error: 'dfsdm_stop_filter' undeclared here (not in a function) EXPORT_SYMBOL_GPL(dfsdm_stop_filter); ^ include/linux/export.h:58:16: note: in definition of macro '___EXPORT_SYMBOL' extern typeof(sym) sym; \ ^~~ drivers/mfd/stm32-dfsdm.c:444:1: note: in expansion of macro 'EXPORT_SYMBOL_GPL' EXPORT_SYMBOL_GPL(dfsdm_stop_filter); ^~~~~~~~~~~~~~~~~ drivers/mfd/stm32-dfsdm.c:470:19: error: 'dfsdm_read_fl_conv' undeclared here (not in a function) EXPORT_SYMBOL_GPL(dfsdm_read_fl_conv); ^ include/linux/export.h:58:16: note: in definition of macro '___EXPORT_SYMBOL' extern typeof(sym) sym; \ ^~~ drivers/mfd/stm32-dfsdm.c:470:1: note: in expansion of macro 'EXPORT_SYMBOL_GPL' EXPORT_SYMBOL_GPL(dfsdm_read_fl_conv); ^~~~~~~~~~~~~~~~~ drivers/mfd/stm32-dfsdm.c:500:19: error: 'dfsdm_get_filter' undeclared here (not in a function) EXPORT_SYMBOL_GPL(dfsdm_get_filter); ^ include/linux/export.h:58:16: note: in definition of macro '___EXPORT_SYMBOL' extern typeof(sym) sym; \ ^~~ drivers/mfd/stm32-dfsdm.c:500:1: note: in expansion of macro 'EXPORT_SYMBOL_GPL' EXPORT_SYMBOL_GPL(dfsdm_get_filter); ^~~~~~~~~~~~~~~~~ drivers/mfd/stm32-dfsdm.c:518:19: error: 'dfsdm_release_filter' undeclared here (not in a function) EXPORT_SYMBOL_GPL(dfsdm_release_filter); ^ include/linux/export.h:58:16: note: in definition of macro '___EXPORT_SYMBOL' extern typeof(sym) sym; \ ^~~ drivers/mfd/stm32-dfsdm.c:518:1: note: in expansion of macro 'EXPORT_SYMBOL_GPL' EXPORT_SYMBOL_GPL(dfsdm_release_filter); ^~~~~~~~~~~~~~~~~ drivers/mfd/stm32-dfsdm.c:603:19: error: 'dfsdm_register_fl_event' undeclared here (not in a function) EXPORT_SYMBOL_GPL(dfsdm_register_fl_event); ^ include/linux/export.h:58:16: note: in definition of macro '___EXPORT_SYMBOL' extern typeof(sym) sym; \ ^~~ drivers/mfd/stm32-dfsdm.c:603:1: note: in expansion of macro 'EXPORT_SYMBOL_GPL' EXPORT_SYMBOL_GPL(dfsdm_register_fl_event); ^~~~~~~~~~~~~~~~~ drivers/mfd/stm32-dfsdm.c:666:19: error: 'dfsdm_unregister_fl_event' undeclared here (not in a function) EXPORT_SYMBOL_GPL(dfsdm_unregister_fl_event); ^ include/linux/export.h:58:16: note: in definition of macro '___EXPORT_SYMBOL' extern typeof(sym) sym; \ ^~~ drivers/mfd/stm32-dfsdm.c:666:1: note: in expansion of macro 'EXPORT_SYMBOL_GPL' EXPORT_SYMBOL_GPL(dfsdm_unregister_fl_event); ^~~~~~~~~~~~~~~~~ drivers/mfd/stm32-dfsdm.c:729:19: error: 'dfsdm_start_channel' undeclared here (not in a function) EXPORT_SYMBOL_GPL(dfsdm_start_channel); ^ include/linux/export.h:58:16: note: in definition of macro '___EXPORT_SYMBOL' extern typeof(sym) sym; \ ^~~ drivers/mfd/stm32-dfsdm.c:729:1: note: in expansion of macro 'EXPORT_SYMBOL_GPL' EXPORT_SYMBOL_GPL(dfsdm_start_channel); ^~~~~~~~~~~~~~~~~ drivers/mfd/stm32-dfsdm.c:755:19: error: 'dfsdm_stop_channel' undeclared here (not in a function) EXPORT_SYMBOL_GPL(dfsdm_stop_channel); ^ include/linux/export.h:58:16: note: in definition of macro '___EXPORT_SYMBOL' extern typeof(sym) sym; \ ^~~ drivers/mfd/stm32-dfsdm.c:755:1: note: in expansion of macro 'EXPORT_SYMBOL_GPL' EXPORT_SYMBOL_GPL(dfsdm_stop_channel); ^~~~~~~~~~~~~~~~~ drivers/mfd/stm32-dfsdm.c:825:19: error: 'dfsdm_get_channel' undeclared here (not in a function) EXPORT_SYMBOL_GPL(dfsdm_get_channel); ^ include/linux/export.h:58:16: note: in definition of macro '___EXPORT_SYMBOL' extern typeof(sym) sym; \ ^~~ drivers/mfd/stm32-dfsdm.c:825:1: note: in expansion of macro 'EXPORT_SYMBOL_GPL' EXPORT_SYMBOL_GPL(dfsdm_get_channel); ^~~~~~~~~~~~~~~~~ drivers/mfd/stm32-dfsdm.c:843:19: error: 'dfsdm_release_channel' undeclared here (not in a function) EXPORT_SYMBOL_GPL(dfsdm_release_channel); ^ include/linux/export.h:58:16: note: in definition of macro '___EXPORT_SYMBOL' extern typeof(sym) sym; \ ^~~ drivers/mfd/stm32-dfsdm.c:843:1: note: in expansion of macro 'EXPORT_SYMBOL_GPL' EXPORT_SYMBOL_GPL(dfsdm_release_channel);
vim +/EXPORT_SYMBOL_GPL +397 drivers/mfd/stm32-dfsdm.c
381 DFSDM_UPDATE_BITS(priv->regmap, DFSDM_FCR(fl_id), DFSDM_FCR_FOSR_MASK, 382 DFSDM_FCR_FOSR((sparams->oversampling - 1))); 383 384 DFSDM_UPDATE_BITS(priv->regmap, DFSDM_FCR(fl_id), DFSDM_FCR_FORD_MASK, 385 DFSDM_FCR_FORD(sparams->order)); 386 387 /* Conversion */ 388 if (fl_cfg->inj_params) 389 stm32_dfsdm_configure_inj_conv(priv, fl_id, fl_cfg->inj_params); 390 else if (fl_cfg->reg_params) 391 stm32_dfsdm_configure_reg_conv(priv, fl_id, fl_cfg->reg_params); 392 393 priv->filters[fl_id].event = fl_cfg->event; 394 395 return 0; 396 }
397 EXPORT_SYMBOL_GPL(dfsdm_configure_filter);
398 399 /** 400 * stm32_dfsdm_start_filter - Start filter conversion. 401 * 402 * @dfsdm: Handle used to retrieve dfsdm context. 403 * @fl_id: Filter id. 404 * @conv: Conversion type regular or injected. 405 */
--- 0-DAY kernel test infrastructure Open Source Technology Center https://lists.01.org/pipermail/kbuild-all Intel Corporation
On Mon, 23 Jan 2017, Arnaud Pouliquen wrote:
DFSDM hardware IP can be used at the same time for ADC sigma delta
Same time as what?
conversion and audio PDM microphone. MFD driver is in charge of configuring IP registers and managing IP clocks. For this it exports an API to handles filters and channels resources.
This looks like an ADC driver? What is it that makes it an MFD?
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com
drivers/mfd/Kconfig | 11 + drivers/mfd/Makefile | 2 + drivers/mfd/stm32-dfsdm-reg.h | 220 +++++++++ drivers/mfd/stm32-dfsdm.c | 1044 +++++++++++++++++++++++++++++++++++++++ include/linux/mfd/stm32-dfsdm.h | 324 ++++++++++++ 5 files changed, 1601 insertions(+) create mode 100644 drivers/mfd/stm32-dfsdm-reg.h create mode 100644 drivers/mfd/stm32-dfsdm.c create mode 100644 include/linux/mfd/stm32-dfsdm.h
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index c6df644..4bb660b 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -1607,6 +1607,17 @@ config MFD_STW481X in various ST Microelectronics and ST-Ericsson embedded Nomadik series.
+config MFD_STM32_DFSDM
- tristate "ST Microelectronics STM32 DFSDM"
- depends on (ARCH_STM32 && OF) || COMPILE_TEST
- select MFD_CORE
- select REGMAP
- select REGMAP_MMIO
- help
Select this option to enable the STM32 Digital Filter
for Sigma Delta Modulators (DFSDM) driver used
in various STM32 series.
menu "Multimedia Capabilities Port drivers" depends on ARCH_SA1100
[...]
Hello Lee,
On 01/24/2017 09:22 AM, Lee Jones wrote:
On Mon, 23 Jan 2017, Arnaud Pouliquen wrote:
DFSDM hardware IP can be used at the same time for ADC sigma delta
Same time as what?
DFSDM is used for ADC acquisition (through IIO) but also PDM microphone capture (through ASOC).
conversion and audio PDM microphone. MFD driver is in charge of configuring IP registers and managing IP clocks. For this it exports an API to handles filters and channels resources.
This looks like an ADC driver? What is it that makes it an MFD?
Yes it a kind of ADC but that supports 2 features audio and iio. So it has to support 2 features based on 2 separate Frameworks.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com
drivers/mfd/Kconfig | 11 + drivers/mfd/Makefile | 2 + drivers/mfd/stm32-dfsdm-reg.h | 220 +++++++++ drivers/mfd/stm32-dfsdm.c | 1044 +++++++++++++++++++++++++++++++++++++++ include/linux/mfd/stm32-dfsdm.h | 324 ++++++++++++ 5 files changed, 1601 insertions(+) create mode 100644 drivers/mfd/stm32-dfsdm-reg.h create mode 100644 drivers/mfd/stm32-dfsdm.c create mode 100644 include/linux/mfd/stm32-dfsdm.h
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index c6df644..4bb660b 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -1607,6 +1607,17 @@ config MFD_STW481X in various ST Microelectronics and ST-Ericsson embedded Nomadik series.
+config MFD_STM32_DFSDM
- tristate "ST Microelectronics STM32 DFSDM"
- depends on (ARCH_STM32 && OF) || COMPILE_TEST
- select MFD_CORE
- select REGMAP
- select REGMAP_MMIO
- help
Select this option to enable the STM32 Digital Filter
for Sigma Delta Modulators (DFSDM) driver used
in various STM32 series.
menu "Multimedia Capabilities Port drivers" depends on ARCH_SA1100
[...]
Regards Arnaud
On Tue, 24 Jan 2017, Arnaud Pouliquen wrote:
On 01/24/2017 09:22 AM, Lee Jones wrote:
On Mon, 23 Jan 2017, Arnaud Pouliquen wrote:
DFSDM hardware IP can be used at the same time for ADC sigma delta
Same time as what?
DFSDM is used for ADC acquisition (through IIO) but also PDM microphone capture (through ASOC).
conversion and audio PDM microphone. MFD driver is in charge of configuring IP registers and managing IP clocks. For this it exports an API to handles filters and channels resources.
This looks like an ADC driver? What is it that makes it an MFD?
Yes it a kind of ADC but that supports 2 features audio and iio. So it has to support 2 features based on 2 separate Frameworks.
I'm still unsure why it needs to live in MFD.
By the looks of it, this driver needs to move into IIO and you need to call into it from ASoC.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com
drivers/mfd/Kconfig | 11 + drivers/mfd/Makefile | 2 + drivers/mfd/stm32-dfsdm-reg.h | 220 +++++++++ drivers/mfd/stm32-dfsdm.c | 1044 +++++++++++++++++++++++++++++++++++++++ include/linux/mfd/stm32-dfsdm.h | 324 ++++++++++++ 5 files changed, 1601 insertions(+) create mode 100644 drivers/mfd/stm32-dfsdm-reg.h create mode 100644 drivers/mfd/stm32-dfsdm.c create mode 100644 include/linux/mfd/stm32-dfsdm.h
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index c6df644..4bb660b 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -1607,6 +1607,17 @@ config MFD_STW481X in various ST Microelectronics and ST-Ericsson embedded Nomadik series.
+config MFD_STM32_DFSDM
- tristate "ST Microelectronics STM32 DFSDM"
- depends on (ARCH_STM32 && OF) || COMPILE_TEST
- select MFD_CORE
- select REGMAP
- select REGMAP_MMIO
- help
Select this option to enable the STM32 Digital Filter
for Sigma Delta Modulators (DFSDM) driver used
in various STM32 series.
menu "Multimedia Capabilities Port drivers" depends on ARCH_SA1100
[...]
Regards Arnaud
On 01/24/2017 12:36 PM, Lee Jones wrote:
On Tue, 24 Jan 2017, Arnaud Pouliquen wrote:
On 01/24/2017 09:22 AM, Lee Jones wrote:
On Mon, 23 Jan 2017, Arnaud Pouliquen wrote:
DFSDM hardware IP can be used at the same time for ADC sigma delta
Same time as what?
DFSDM is used for ADC acquisition (through IIO) but also PDM microphone capture (through ASOC).
conversion and audio PDM microphone. MFD driver is in charge of configuring IP registers and managing IP clocks. For this it exports an API to handles filters and channels resources.
This looks like an ADC driver? What is it that makes it an MFD?
Yes it a kind of ADC but that supports 2 features audio and iio. So it has to support 2 features based on 2 separate Frameworks.
I'm still unsure why it needs to live in MFD.
By the looks of it, this driver needs to move into IIO and you need to call into it from ASoC.
I think i introduce confusion by speaking about ADC for audio...
1) IIO handles sigma delta ADCs that can be used as example for motor controls. the aim is to get value based on an application request or using some triggers. example: http://www.ti.com/lit/ds/symlink/ads1202.pdf
2) For audio part, we speak about Digital mems microphones that generate PDM format stream. PDM is a continuous real time stream dedicated to audio record and must be handled in ASOC ( codec Dmic part driver is /sound/soc/codec/dmic.c). DMIC example: http://www.st.com/content/ccc/resource/technical/document/datasheet/47/bd/d2...
Form my point of view it very strange to handle DMICs in IIO, as it is not designed to support audio streams.it is two separate features that are not compatible.
Now, from software point of view That would means that IIO declares ADCs that it can not expose, because DMIC is not IIO standard. But IIO inkern API needs that device is declared... So i should define a specific API in IIO for ASOC driver.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com
drivers/mfd/Kconfig | 11 + drivers/mfd/Makefile | 2 + drivers/mfd/stm32-dfsdm-reg.h | 220 +++++++++ drivers/mfd/stm32-dfsdm.c | 1044 +++++++++++++++++++++++++++++++++++++++ include/linux/mfd/stm32-dfsdm.h | 324 ++++++++++++ 5 files changed, 1601 insertions(+) create mode 100644 drivers/mfd/stm32-dfsdm-reg.h create mode 100644 drivers/mfd/stm32-dfsdm.c create mode 100644 include/linux/mfd/stm32-dfsdm.h
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index c6df644..4bb660b 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -1607,6 +1607,17 @@ config MFD_STW481X in various ST Microelectronics and ST-Ericsson embedded Nomadik series.
+config MFD_STM32_DFSDM
- tristate "ST Microelectronics STM32 DFSDM"
- depends on (ARCH_STM32 && OF) || COMPILE_TEST
- select MFD_CORE
- select REGMAP
- select REGMAP_MMIO
- help
Select this option to enable the STM32 Digital Filter
for Sigma Delta Modulators (DFSDM) driver used
in various STM32 series.
menu "Multimedia Capabilities Port drivers" depends on ARCH_SA1100
[...]
Regards Arnaud
On Tue, 24 Jan 2017, Arnaud Pouliquen wrote:
On 01/24/2017 12:36 PM, Lee Jones wrote:
On Tue, 24 Jan 2017, Arnaud Pouliquen wrote:
On 01/24/2017 09:22 AM, Lee Jones wrote:
On Mon, 23 Jan 2017, Arnaud Pouliquen wrote:
DFSDM hardware IP can be used at the same time for ADC sigma delta
Same time as what?
DFSDM is used for ADC acquisition (through IIO) but also PDM microphone capture (through ASOC).
conversion and audio PDM microphone. MFD driver is in charge of configuring IP registers and managing IP clocks. For this it exports an API to handles filters and channels resources.
This looks like an ADC driver? What is it that makes it an MFD?
Yes it a kind of ADC but that supports 2 features audio and iio. So it has to support 2 features based on 2 separate Frameworks.
I'm still unsure why it needs to live in MFD.
By the looks of it, this driver needs to move into IIO and you need to call into it from ASoC.
I think i introduce confusion by speaking about ADC for audio...
- IIO handles sigma delta ADCs that can be used as example for motor
controls. the aim is to get value based on an application request or using some triggers. example: http://www.ti.com/lit/ds/symlink/ads1202.pdf
- For audio part, we speak about Digital mems microphones that generate
PDM format stream. PDM is a continuous real time stream dedicated to audio record and must be handled in ASOC ( codec Dmic part driver is /sound/soc/codec/dmic.c). DMIC example: http://www.st.com/content/ccc/resource/technical/document/datasheet/47/bd/d2...
Form my point of view it very strange to handle DMICs in IIO, as it is not designed to support audio streams.it is two separate features that are not compatible.
Now, from software point of view That would means that IIO declares ADCs that it can not expose, because DMIC is not IIO standard. But IIO inkern API needs that device is declared
So i should define a specific API in IIO for ASOC driver.
Yes, this is what I think you should do.
MFD is not a dumping ground for devices that do not fit anywhere else.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com
drivers/mfd/Kconfig | 11 + drivers/mfd/Makefile | 2 + drivers/mfd/stm32-dfsdm-reg.h | 220 +++++++++ drivers/mfd/stm32-dfsdm.c | 1044 +++++++++++++++++++++++++++++++++++++++ include/linux/mfd/stm32-dfsdm.h | 324 ++++++++++++ 5 files changed, 1601 insertions(+) create mode 100644 drivers/mfd/stm32-dfsdm-reg.h create mode 100644 drivers/mfd/stm32-dfsdm.c create mode 100644 include/linux/mfd/stm32-dfsdm.h
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index c6df644..4bb660b 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -1607,6 +1607,17 @@ config MFD_STW481X in various ST Microelectronics and ST-Ericsson embedded Nomadik series.
+config MFD_STM32_DFSDM
- tristate "ST Microelectronics STM32 DFSDM"
- depends on (ARCH_STM32 && OF) || COMPILE_TEST
- select MFD_CORE
- select REGMAP
- select REGMAP_MMIO
- help
Select this option to enable the STM32 Digital Filter
for Sigma Delta Modulators (DFSDM) driver used
in various STM32 series.
menu "Multimedia Capabilities Port drivers" depends on ARCH_SA1100
[...]
Regards Arnaud
On 01/27/2017 11:15 AM, Lee Jones wrote:
On Tue, 24 Jan 2017, Arnaud Pouliquen wrote:
On 01/24/2017 12:36 PM, Lee Jones wrote:
On Tue, 24 Jan 2017, Arnaud Pouliquen wrote:
On 01/24/2017 09:22 AM, Lee Jones wrote:
On Mon, 23 Jan 2017, Arnaud Pouliquen wrote:
DFSDM hardware IP can be used at the same time for ADC sigma delta
Same time as what?
DFSDM is used for ADC acquisition (through IIO) but also PDM microphone capture (through ASOC).
conversion and audio PDM microphone. MFD driver is in charge of configuring IP registers and managing IP clocks. For this it exports an API to handles filters and channels resources.
This looks like an ADC driver? What is it that makes it an MFD?
Yes it a kind of ADC but that supports 2 features audio and iio. So it has to support 2 features based on 2 separate Frameworks.
I'm still unsure why it needs to live in MFD.
By the looks of it, this driver needs to move into IIO and you need to call into it from ASoC.
I think i introduce confusion by speaking about ADC for audio...
- IIO handles sigma delta ADCs that can be used as example for motor
controls. the aim is to get value based on an application request or using some triggers. example: http://www.ti.com/lit/ds/symlink/ads1202.pdf
- For audio part, we speak about Digital mems microphones that generate
PDM format stream. PDM is a continuous real time stream dedicated to audio record and must be handled in ASOC ( codec Dmic part driver is /sound/soc/codec/dmic.c). DMIC example: http://www.st.com/content/ccc/resource/technical/document/datasheet/47/bd/d2...
Form my point of view it very strange to handle DMICs in IIO, as it is not designed to support audio streams.it is two separate features that are not compatible.
Now, from software point of view That would means that IIO declares ADCs that it can not expose, because DMIC is not IIO standard. But IIO inkern API needs that device is declared
So i should define a specific API in IIO for ASOC driver.
Yes, this is what I think you should do.
MFD is not a dumping ground for devices that do not fit anywhere else.
I fully understand that you don't want to create unnecessary MFD devices. But In case of DFSDM ,it is really a device that supports 2 features. The main evidence is that "ADC acquisition" and "digital microphone" features are handled by two separate frameworks. So this corresponds to two features that share the same device resources (registers, IRQs, clocks). Seems that this match with MFD definition.
Now, if IIO and ASOC maintainers are aligned with you proposal, i will move MFD part in IIO. But in this case, i can not see another way to do it, except a MFD driver hidden in IIO?
Here is my analysis of a design in IIO:
Natural way to do this is to consider that ASOC is a customer of IIO so use the customer.h API. 1) As Digital microphone can not be handled by IIO dev interface, i can not expose them as an IIO channel, that would be visible by applications in /sys/bus/iio/devices/iio 2) ASOC needs to configure DFSDM filter to match to specific frequencies and sample formats requested by users. Not standard IIO interface to do this. (on IIO side this is done by attribute file) 3) audio stream is a real time stream. So for audio quality best is to minimize latencies and Xrun. That why transfer as done from HW to application using LLI DMA transfers with a minimum of copy. This does not match with the IIO inkern API. => So seems not possible to use IIO inkern API.
That's means that i would need to create a ST specific include file to offer an API to ASoC to handle DFSDM resources linked to the DMIC. and to probe ASOc device in IIO one. So the solution would be to create something like a sub IIO driver That probe and handle some IIO and ASOC devices. Ultimately This would correspond to a MFD driver integrated in IIO...
Jonathan, Mark, Please could you share your opinion on this topic?
Regards Arnaud
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com
drivers/mfd/Kconfig | 11 + drivers/mfd/Makefile | 2 + drivers/mfd/stm32-dfsdm-reg.h | 220 +++++++++ drivers/mfd/stm32-dfsdm.c | 1044 +++++++++++++++++++++++++++++++++++++++ include/linux/mfd/stm32-dfsdm.h | 324 ++++++++++++ 5 files changed, 1601 insertions(+) create mode 100644 drivers/mfd/stm32-dfsdm-reg.h create mode 100644 drivers/mfd/stm32-dfsdm.c create mode 100644 include/linux/mfd/stm32-dfsdm.h
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index c6df644..4bb660b 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -1607,6 +1607,17 @@ config MFD_STW481X in various ST Microelectronics and ST-Ericsson embedded Nomadik series.
+config MFD_STM32_DFSDM
- tristate "ST Microelectronics STM32 DFSDM"
- depends on (ARCH_STM32 && OF) || COMPILE_TEST
- select MFD_CORE
- select REGMAP
- select REGMAP_MMIO
- help
Select this option to enable the STM32 Digital Filter
for Sigma Delta Modulators (DFSDM) driver used
in various STM32 series.
menu "Multimedia Capabilities Port drivers" depends on ARCH_SA1100
[...]
Regards Arnaud
On Fri, 27 Jan 2017, Arnaud Pouliquen wrote:
On 01/27/2017 11:15 AM, Lee Jones wrote:
On Tue, 24 Jan 2017, Arnaud Pouliquen wrote:
On 01/24/2017 12:36 PM, Lee Jones wrote:
On Tue, 24 Jan 2017, Arnaud Pouliquen wrote:
On 01/24/2017 09:22 AM, Lee Jones wrote:
On Mon, 23 Jan 2017, Arnaud Pouliquen wrote:
> DFSDM hardware IP can be used at the same time for ADC sigma delta
Same time as what?
DFSDM is used for ADC acquisition (through IIO) but also PDM microphone capture (through ASOC).
> conversion and audio PDM microphone. > MFD driver is in charge of configuring IP registers and managing IP clocks. > For this it exports an API to handles filters and channels resources.
This looks like an ADC driver? What is it that makes it an MFD?
Yes it a kind of ADC but that supports 2 features audio and iio. So it has to support 2 features based on 2 separate Frameworks.
I'm still unsure why it needs to live in MFD.
By the looks of it, this driver needs to move into IIO and you need to call into it from ASoC.
I think i introduce confusion by speaking about ADC for audio...
- IIO handles sigma delta ADCs that can be used as example for motor
controls. the aim is to get value based on an application request or using some triggers. example: http://www.ti.com/lit/ds/symlink/ads1202.pdf
- For audio part, we speak about Digital mems microphones that generate
PDM format stream. PDM is a continuous real time stream dedicated to audio record and must be handled in ASOC ( codec Dmic part driver is /sound/soc/codec/dmic.c). DMIC example: http://www.st.com/content/ccc/resource/technical/document/datasheet/47/bd/d2...
Form my point of view it very strange to handle DMICs in IIO, as it is not designed to support audio streams.it is two separate features that are not compatible.
Now, from software point of view That would means that IIO declares ADCs that it can not expose, because DMIC is not IIO standard. But IIO inkern API needs that device is declared
So i should define a specific API in IIO for ASOC driver.
Yes, this is what I think you should do.
MFD is not a dumping ground for devices that do not fit anywhere else.
I fully understand that you don't want to create unnecessary MFD devices. But In case of DFSDM ,it is really a device that supports 2 features. The main evidence is that "ADC acquisition" and "digital microphone" features are handled by two separate frameworks. So this corresponds to two features that share the same device resources (registers, IRQs, clocks). Seems that this match with MFD definition.
Now, if IIO and ASOC maintainers are aligned with you proposal, i will move MFD part in IIO. But in this case, i can not see another way to do it, except a MFD driver hidden in IIO?
Here is my analysis of a design in IIO:
Natural way to do this is to consider that ASOC is a customer of IIO so use the customer.h API.
- As Digital microphone can not be handled by IIO dev interface, i can
not expose them as an IIO channel, that would be visible by applications in /sys/bus/iio/devices/iio 2) ASOC needs to configure DFSDM filter to match to specific frequencies and sample formats requested by users. Not standard IIO interface to do this. (on IIO side this is done by attribute file) 3) audio stream is a real time stream. So for audio quality best is to minimize latencies and Xrun. That why transfer as done from HW to application using LLI DMA transfers with a minimum of copy. This does not match with the IIO inkern API. => So seems not possible to use IIO inkern API.
That's means that i would need to create a ST specific include file to offer an API to ASoC to handle DFSDM resources linked to the DMIC. and to probe ASOc device in IIO one. So the solution would be to create something like a sub IIO driver That probe and handle some IIO and ASOC devices. Ultimately This would correspond to a MFD driver integrated in IIO...
The issue is not the creation of an MFD driver to create shared resources and probe the child devices. That's what MFD *is* for. What I do take exception to is having lots of code in MFD which should clearly live in a different subsystem.
Since this device only serves a couple of functions, I expect it to be a few hundred lines, maximum. Everything else should be farmed out. MFD knows nothing of "channels" or "filters" so anything related to them (init, get, start, stop, release, etc) needs to find another home. Whether that's in IIO is a separate discussion, but it certainly doesn't live in MFD.
Jonathan, Mark, Please could you share your opinion on this topic?
Regards Arnaud
> Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com > --- > drivers/mfd/Kconfig | 11 + > drivers/mfd/Makefile | 2 + > drivers/mfd/stm32-dfsdm-reg.h | 220 +++++++++ > drivers/mfd/stm32-dfsdm.c | 1044 +++++++++++++++++++++++++++++++++++++++ > include/linux/mfd/stm32-dfsdm.h | 324 ++++++++++++ > 5 files changed, 1601 insertions(+) > create mode 100644 drivers/mfd/stm32-dfsdm-reg.h > create mode 100644 drivers/mfd/stm32-dfsdm.c > create mode 100644 include/linux/mfd/stm32-dfsdm.h > > diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig > index c6df644..4bb660b 100644 > --- a/drivers/mfd/Kconfig > +++ b/drivers/mfd/Kconfig > @@ -1607,6 +1607,17 @@ config MFD_STW481X > in various ST Microelectronics and ST-Ericsson embedded > Nomadik series. > > +config MFD_STM32_DFSDM > + tristate "ST Microelectronics STM32 DFSDM" > + depends on (ARCH_STM32 && OF) || COMPILE_TEST > + select MFD_CORE > + select REGMAP > + select REGMAP_MMIO > + help > + Select this option to enable the STM32 Digital Filter > + for Sigma Delta Modulators (DFSDM) driver used > + in various STM32 series. > + > menu "Multimedia Capabilities Port drivers" > depends on ARCH_SA1100
[...]
Regards Arnaud
On 27/01/17 17:17, Lee Jones wrote:
On Fri, 27 Jan 2017, Arnaud Pouliquen wrote:
On 01/27/2017 11:15 AM, Lee Jones wrote:
On Tue, 24 Jan 2017, Arnaud Pouliquen wrote:
On 01/24/2017 12:36 PM, Lee Jones wrote:
On Tue, 24 Jan 2017, Arnaud Pouliquen wrote:
On 01/24/2017 09:22 AM, Lee Jones wrote: > On Mon, 23 Jan 2017, Arnaud Pouliquen wrote: > >> DFSDM hardware IP can be used at the same time for ADC sigma delta > > Same time as what? DFSDM is used for ADC acquisition (through IIO) but also PDM microphone capture (through ASOC). > >> conversion and audio PDM microphone. >> MFD driver is in charge of configuring IP registers and managing IP clocks. >> For this it exports an API to handles filters and channels resources. > > This looks like an ADC driver? What is it that makes it an MFD? Yes it a kind of ADC but that supports 2 features audio and iio. So it has to support 2 features based on 2 separate Frameworks.
I'm still unsure why it needs to live in MFD.
By the looks of it, this driver needs to move into IIO and you need to call into it from ASoC.
I think i introduce confusion by speaking about ADC for audio...
- IIO handles sigma delta ADCs that can be used as example for motor
controls. the aim is to get value based on an application request or using some triggers. example: http://www.ti.com/lit/ds/symlink/ads1202.pdf
- For audio part, we speak about Digital mems microphones that generate
PDM format stream. PDM is a continuous real time stream dedicated to audio record and must be handled in ASOC ( codec Dmic part driver is /sound/soc/codec/dmic.c). DMIC example: http://www.st.com/content/ccc/resource/technical/document/datasheet/47/bd/d2...
Form my point of view it very strange to handle DMICs in IIO, as it is not designed to support audio streams.it is two separate features that are not compatible.
Now, from software point of view That would means that IIO declares ADCs that it can not expose, because DMIC is not IIO standard. But IIO inkern API needs that device is declared
So i should define a specific API in IIO for ASOC driver.
Yes, this is what I think you should do.
MFD is not a dumping ground for devices that do not fit anywhere else.
I fully understand that you don't want to create unnecessary MFD devices. But In case of DFSDM ,it is really a device that supports 2 features. The main evidence is that "ADC acquisition" and "digital microphone" features are handled by two separate frameworks. So this corresponds to two features that share the same device resources (registers, IRQs, clocks). Seems that this match with MFD definition.
Now, if IIO and ASOC maintainers are aligned with you proposal, i will move MFD part in IIO. But in this case, i can not see another way to do it, except a MFD driver hidden in IIO?
Here is my analysis of a design in IIO:
Natural way to do this is to consider that ASOC is a customer of IIO so use the customer.h API.
- As Digital microphone can not be handled by IIO dev interface, i can
not expose them as an IIO channel, that would be visible by applications in /sys/bus/iio/devices/iio 2) ASOC needs to configure DFSDM filter to match to specific frequencies and sample formats requested by users. Not standard IIO interface to do this. (on IIO side this is done by attribute file) 3) audio stream is a real time stream. So for audio quality best is to minimize latencies and Xrun. That why transfer as done from HW to application using LLI DMA transfers with a minimum of copy. This does not match with the IIO inkern API. => So seems not possible to use IIO inkern API.
That's means that i would need to create a ST specific include file to offer an API to ASoC to handle DFSDM resources linked to the DMIC. and to probe ASOc device in IIO one. So the solution would be to create something like a sub IIO driver That probe and handle some IIO and ASOC devices. Ultimately This would correspond to a MFD driver integrated in IIO...
The issue is not the creation of an MFD driver to create shared resources and probe the child devices. That's what MFD *is* for. What I do take exception to is having lots of code in MFD which should clearly live in a different subsystem.
Since this device only serves a couple of functions, I expect it to be a few hundred lines, maximum. Everything else should be farmed out. MFD knows nothing of "channels" or "filters" so anything related to them (init, get, start, stop, release, etc) needs to find another home. Whether that's in IIO is a separate discussion, but it certainly doesn't live in MFD.
Jonathan, Mark, Please could you share your opinion on this topic?
Hmm - based on a fairly quick read through of the code (which is never ideal!). I can see that the ideal would indeed be as Lee says, to expand the IIO interfaces sufficiently to support what you need.
So, reading the code (fairly quickly I'm afraid as had a lot of reviews to catch up on this weekend). What we need: 1) DMA support in the ADC driver. This would be a good anyway! 2) DMA consumer support - I defer to Lars for comments on this. 3) Means of describing and controlling the sinc filters applied. 4) Appropriate channel support. I'm not convinced that it doesn't make sense to have IIO channels for the microphones - at least in a streaming mode. It's data - I don't really care what ;) Coarsely it's a filtered pulse per period counter which is a perfectly valid type to have a channel for.
The big question to my mind is the DMA consumer support. How would it work. It it wouldn't this is somewhat of a non starter.
To bring up another slightly ugly MFD case where it is borderline on whether an MFD makes sense (just as a reference point of something we have discussed a few times before)
ADCs with features directed at touchscreen support. These are odd as the ADC bit is generic, but the specific output and read sequences used for touchscreen reading don't correspond to anything that makes any real sense for other applications.
We have started to get hybrid drives that have an MFD underneath but do the ADC reads through IIO consumer interfaces, and the timing control from a touchscreen driver. We haven't really gotten this one right yet either.
Here however, to my mind things are different - as I read it (and feel free to point out what I'm missing), the sound usecase is just a question of setting up sampling frequencies and filters appropriate to the microphones and what ASoC expects?
That's not to say the IIO dma stuff is flexible enough (yet) to handle the data flows, but perhaps we can work towards that.
Jonathan
Regards Arnaud
>> Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com >> --- >> drivers/mfd/Kconfig | 11 + >> drivers/mfd/Makefile | 2 + >> drivers/mfd/stm32-dfsdm-reg.h | 220 +++++++++ >> drivers/mfd/stm32-dfsdm.c | 1044 +++++++++++++++++++++++++++++++++++++++ >> include/linux/mfd/stm32-dfsdm.h | 324 ++++++++++++ >> 5 files changed, 1601 insertions(+) >> create mode 100644 drivers/mfd/stm32-dfsdm-reg.h >> create mode 100644 drivers/mfd/stm32-dfsdm.c >> create mode 100644 include/linux/mfd/stm32-dfsdm.h >> >> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig >> index c6df644..4bb660b 100644 >> --- a/drivers/mfd/Kconfig >> +++ b/drivers/mfd/Kconfig >> @@ -1607,6 +1607,17 @@ config MFD_STW481X >> in various ST Microelectronics and ST-Ericsson embedded >> Nomadik series. >> >> +config MFD_STM32_DFSDM >> + tristate "ST Microelectronics STM32 DFSDM" >> + depends on (ARCH_STM32 && OF) || COMPILE_TEST >> + select MFD_CORE >> + select REGMAP >> + select REGMAP_MMIO >> + help >> + Select this option to enable the STM32 Digital Filter >> + for Sigma Delta Modulators (DFSDM) driver used >> + in various STM32 series. >> + >> menu "Multimedia Capabilities Port drivers" >> depends on ARCH_SA1100 > > [...] >
Regards Arnaud
On 01/29/2017 01:28 PM, Jonathan Cameron wrote: [...]
Jonathan, Mark, Please could you share your opinion on this topic?
Hmm - based on a fairly quick read through of the code (which is never ideal!). I can see that the ideal would indeed be as Lee says, to expand the IIO interfaces sufficiently to support what you need.
So, reading the code (fairly quickly I'm afraid as had a lot of reviews to catch up on this weekend). What we need:
- DMA support in the ADC driver. This would be a good anyway!
- DMA consumer support - I defer to Lars for comments on this.
- Means of describing and controlling the sinc filters applied.
- Appropriate channel support. I'm not convinced that it doesn't make
sense to have IIO channels for the microphones - at least in a streaming mode. It's data - I don't really care what ;) Coarsely it's a filtered pulse per period counter which is a perfectly valid type to have a channel for.
The big question to my mind is the DMA consumer support. How would it work. It it wouldn't this is somewhat of a non starter.
To bring up another slightly ugly MFD case where it is borderline on whether an MFD makes sense (just as a reference point of something we have discussed a few times before)
ADCs with features directed at touchscreen support. These are odd as the ADC bit is generic, but the specific output and read sequences used for touchscreen reading don't correspond to anything that makes any real sense for other applications.
We have started to get hybrid drives that have an MFD underneath but do the ADC reads through IIO consumer interfaces, and the timing control from a touchscreen driver. We haven't really gotten this one right yet either.
Here however, to my mind things are different - as I read it (and feel free to point out what I'm missing), the sound usecase is just a question of setting up sampling frequencies and filters appropriate to the microphones and what ASoC expects?
That's not to say the IIO dma stuff is flexible enough (yet) to handle the data flows, but perhaps we can work towards that.
Yeah, so this is a bit different, but not unexpected. And I'm sure we'll see more similar hardware in the future. I've talked about this before[1], the cost structure of creating and manufacturing new hardware drives the design in a certain direction so that we end up with general purpose hardware that suddenly has applications in multiple frameworks that were previously fully orthogonal.
This device is certainly not a multi-function-device. It only has one function, it's a sigma-delta demodulator. It is rather a multi-purpose-device. It can be used for sigma-delta demodulation in audio applications as well as more specialized data capture applications.
It's comparable to something like a GPIO that can be used to control a reset pin or turn on and off a LED. The GPIO chip is not considered multi-function-device though, even though it can be used for many different applications.
As for DMA we already have a lot of DMA infrastructure on the audio side and we probably want to reuse that rather than inserting IIO as a middle layer since audio buffer capture as different requirements from IIO buffer and we'd have to go the route of the least common denominator and loose expressibility in the process.
I've created a IIO buffer[2] that does not capture data to memory but is only used to enable/disable the data capture process. We use this in setups where the data is passed from the converter to a application specific processing chain without ever going through system memory. This buffer could probably also be used here on the audio side to control the converter state.
- Lars
[1] https://lists.linuxfoundation.org/pipermail/ksummit-discuss/2016-July/003029...
[2] https://github.com/analogdevicesinc/linux/blob/xcomm_zynq/drivers/iio/buffer...
On 01/29/2017 03:19 PM, Lars-Peter Clausen wrote:
On 01/29/2017 01:28 PM, Jonathan Cameron wrote: [...]
Jonathan, Mark, Please could you share your opinion on this topic?
Hmm - based on a fairly quick read through of the code (which is never ideal!). I can see that the ideal would indeed be as Lee says, to expand the IIO interfaces sufficiently to support what you need.
So, reading the code (fairly quickly I'm afraid as had a lot of reviews to catch up on this weekend). What we need:
- DMA support in the ADC driver. This would be a good anyway!
- DMA consumer support - I defer to Lars for comments on this.
- Means of describing and controlling the sinc filters applied.
- Appropriate channel support. I'm not convinced that it doesn't make
sense to have IIO channels for the microphones - at least in a streaming mode. It's data - I don't really care what ;) Coarsely it's a filtered pulse per period counter which is a perfectly valid type to have a channel for.
The big question to my mind is the DMA consumer support. How would it work. It it wouldn't this is somewhat of a non starter.
To bring up another slightly ugly MFD case where it is borderline on whether an MFD makes sense (just as a reference point of something we have discussed a few times before)
ADCs with features directed at touchscreen support. These are odd as the ADC bit is generic, but the specific output and read sequences used for touchscreen reading don't correspond to anything that makes any real sense for other applications.
We have started to get hybrid drives that have an MFD underneath but do the ADC reads through IIO consumer interfaces, and the timing control from a touchscreen driver. We haven't really gotten this one right yet either.
Here however, to my mind things are different - as I read it (and feel free to point out what I'm missing), the sound usecase is just a question of setting up sampling frequencies and filters appropriate to the microphones and what ASoC expects?
That's not to say the IIO dma stuff is flexible enough (yet) to handle the data flows, but perhaps we can work towards that.
Yeah, so this is a bit different, but not unexpected. And I'm sure we'll see more similar hardware in the future. I've talked about this before[1], the cost structure of creating and manufacturing new hardware drives the design in a certain direction so that we end up with general purpose hardware that suddenly has applications in multiple frameworks that were previously fully orthogonal.
This device is certainly not a multi-function-device. It only has one function, it's a sigma-delta demodulator. It is rather a multi-purpose-device. It can be used for sigma-delta demodulation in audio applications as well as more specialized data capture applications.
It's comparable to something like a GPIO that can be used to control a reset pin or turn on and off a LED. The GPIO chip is not considered multi-function-device though, even though it can be used for many different applications.
As for DMA we already have a lot of DMA infrastructure on the audio side and we probably want to reuse that rather than inserting IIO as a middle layer since audio buffer capture as different requirements from IIO buffer and we'd have to go the route of the least common denominator and loose expressibility in the process.
I've created a IIO buffer[2] that does not capture data to memory but is only used to enable/disable the data capture process. We use this in setups where the data is passed from the converter to a application specific processing chain without ever going through system memory. This buffer could probably also be used here on the audio side to control the converter state.
I forgot to mention. I think the first thing we should do is work on terminology. This is not an ADC, this is a configurable low-pass-filter.
It works in conjunction with a analog frontend (ADC) that produces a 1-bit pulse-density-modulated stream, takes that stream and converts it into N-bit PCM samples. The PCM samples are generated at a fraction of the PDM stream samplerate that corresponds to the decimation factor.
This is not an unusual device. Many audio CODEC and audio controllers contain such a core as well as most SigmaDelta converters supported by IIO. What is special about this part is that it is a dedicated core that is not embedded in some other hardware component. This creates greater flexibility, but of course also greater complexity that is required to manage all that flexibility.
We shouldn't codify anything about the kernel internal frameworks through which the device might be exposed into the devicetree. We should accurately describe the hardware (including the analog frontend) and then create a appropriate software structures to handle them.
Hello,
Thanks everyone for you feedback! My comments below.
On 01/29/2017 03:34 PM, Lars-Peter Clausen wrote:
On 01/29/2017 03:19 PM, Lars-Peter Clausen wrote:
On 01/29/2017 01:28 PM, Jonathan Cameron wrote: [...]
Jonathan, Mark, Please could you share your opinion on this topic?
Hmm - based on a fairly quick read through of the code (which is never ideal!). I can see that the ideal would indeed be as Lee says, to expand the IIO interfaces sufficiently to support what you need.
So, reading the code (fairly quickly I'm afraid as had a lot of reviews to catch up on this weekend). What we need:
- DMA support in the ADC driver. This would be a good anyway!
- DMA consumer support - I defer to Lars for comments on this.
- Means of describing and controlling the sinc filters applied.
- Appropriate channel support. I'm not convinced that it doesn't make
sense to have IIO channels for the microphones - at least in a streaming mode. It's data - I don't really care what ;) Coarsely it's a filtered pulse per period counter which is a perfectly valid type to have a channel for.
The big question to my mind is the DMA consumer support. How would it work. It it wouldn't this is somewhat of a non starter.
To bring up another slightly ugly MFD case where it is borderline on whether an MFD makes sense (just as a reference point of something we have discussed a few times before)
ADCs with features directed at touchscreen support. These are odd as the ADC bit is generic, but the specific output and read sequences used for touchscreen reading don't correspond to anything that makes any real sense for other applications.
We have started to get hybrid drives that have an MFD underneath but do the ADC reads through IIO consumer interfaces, and the timing control from a touchscreen driver. We haven't really gotten this one right yet either.
Here however, to my mind things are different - as I read it (and feel free to point out what I'm missing), the sound usecase is just a question of setting up sampling frequencies and filters appropriate to the microphones and what ASoC expects?
That's not to say the IIO dma stuff is flexible enough (yet) to handle the data flows, but perhaps we can work towards that.
Yeah, so this is a bit different, but not unexpected. And I'm sure we'll see more similar hardware in the future. I've talked about this before[1], the cost structure of creating and manufacturing new hardware drives the design in a certain direction so that we end up with general purpose hardware that suddenly has applications in multiple frameworks that were previously fully orthogonal.
This device is certainly not a multi-function-device. It only has one function, it's a sigma-delta demodulator. It is rather a multi-purpose-device. It can be used for sigma-delta demodulation in audio applications as well as more specialized data capture applications.
It's comparable to something like a GPIO that can be used to control a reset pin or turn on and off a LED. The GPIO chip is not considered multi-function-device though, even though it can be used for many different applications.
As for DMA we already have a lot of DMA infrastructure on the audio side and we probably want to reuse that rather than inserting IIO as a middle layer since audio buffer capture as different requirements from IIO buffer and we'd have to go the route of the least common denominator and loose expressibility in the process.
I've created a IIO buffer[2] that does not capture data to memory but is only used to enable/disable the data capture process. We use this in setups where the data is passed from the converter to a application specific processing chain without ever going through system memory. This buffer could probably also be used here on the audio side to control the converter state.
I forgot to mention. I think the first thing we should do is work on terminology. This is not an ADC, this is a configurable low-pass-filter.
It works in conjunction with a analog frontend (ADC) that produces a 1-bit pulse-density-modulated stream, takes that stream and converts it into N-bit PCM samples. The PCM samples are generated at a fraction of the PDM stream samplerate that corresponds to the decimation factor.
This is not an unusual device. Many audio CODEC and audio controllers contain such a core as well as most SigmaDelta converters supported by IIO. What is special about this part is that it is a dedicated core that is not embedded in some other hardware component. This creates greater flexibility, but of course also greater complexity that is required to manage all that flexibility.
We shouldn't codify anything about the kernel internal frameworks through which the device might be exposed into the devicetree. We should accurately describe the hardware (including the analog frontend) and then create a appropriate software structures to handle them.
So if everyone is aligned, i will abandon the MFD driver and try to bind an ASoC driver on IIO interface. The "challenge" is to define appropriate relation ship between ASoC and IIO...
In a first step, a lot of question to answers and points to clarify... i will reply to associated mails.
Then I see two main topics to clarify: - DFSDM integration in IIO in a generic way. Lars, if i well interpret, your proposal should be to introduce front-end and filter notions in IIO, to support this kind of hardware? - DMA engine for audio purpose. I propose to come back with RFC for both subjects.
If you want to have more detail on DFSDM: DFSDM datasheet is included in STM32F413 datasheet available here: http://www.st.com/content/ccc/resource/technical/document/reference_manual/g...
DFSDM Block diagram p 384
Regards Arnaud
On 30/01/17 11:13, Arnaud Pouliquen wrote:
Hello,
Thanks everyone for you feedback! My comments below.
On 01/29/2017 03:34 PM, Lars-Peter Clausen wrote:
On 01/29/2017 03:19 PM, Lars-Peter Clausen wrote:
On 01/29/2017 01:28 PM, Jonathan Cameron wrote: [...]
Jonathan, Mark, Please could you share your opinion on this topic?
Hmm - based on a fairly quick read through of the code (which is never ideal!). I can see that the ideal would indeed be as Lee says, to expand the IIO interfaces sufficiently to support what you need.
So, reading the code (fairly quickly I'm afraid as had a lot of reviews to catch up on this weekend). What we need:
- DMA support in the ADC driver. This would be a good anyway!
- DMA consumer support - I defer to Lars for comments on this.
- Means of describing and controlling the sinc filters applied.
- Appropriate channel support. I'm not convinced that it doesn't make
sense to have IIO channels for the microphones - at least in a streaming mode. It's data - I don't really care what ;) Coarsely it's a filtered pulse per period counter which is a perfectly valid type to have a channel for.
The big question to my mind is the DMA consumer support. How would it work. It it wouldn't this is somewhat of a non starter.
To bring up another slightly ugly MFD case where it is borderline on whether an MFD makes sense (just as a reference point of something we have discussed a few times before)
ADCs with features directed at touchscreen support. These are odd as the ADC bit is generic, but the specific output and read sequences used for touchscreen reading don't correspond to anything that makes any real sense for other applications.
We have started to get hybrid drives that have an MFD underneath but do the ADC reads through IIO consumer interfaces, and the timing control from a touchscreen driver. We haven't really gotten this one right yet either.
Here however, to my mind things are different - as I read it (and feel free to point out what I'm missing), the sound usecase is just a question of setting up sampling frequencies and filters appropriate to the microphones and what ASoC expects?
That's not to say the IIO dma stuff is flexible enough (yet) to handle the data flows, but perhaps we can work towards that.
Yeah, so this is a bit different, but not unexpected. And I'm sure we'll see more similar hardware in the future. I've talked about this before[1], the cost structure of creating and manufacturing new hardware drives the design in a certain direction so that we end up with general purpose hardware that suddenly has applications in multiple frameworks that were previously fully orthogonal.
This device is certainly not a multi-function-device. It only has one function, it's a sigma-delta demodulator. It is rather a multi-purpose-device. It can be used for sigma-delta demodulation in audio applications as well as more specialized data capture applications.
It's comparable to something like a GPIO that can be used to control a reset pin or turn on and off a LED. The GPIO chip is not considered multi-function-device though, even though it can be used for many different applications.
As for DMA we already have a lot of DMA infrastructure on the audio side and we probably want to reuse that rather than inserting IIO as a middle layer since audio buffer capture as different requirements from IIO buffer and we'd have to go the route of the least common denominator and loose expressibility in the process.
I've created a IIO buffer[2] that does not capture data to memory but is only used to enable/disable the data capture process. We use this in setups where the data is passed from the converter to a application specific processing chain without ever going through system memory. This buffer could probably also be used here on the audio side to control the converter state.
I forgot to mention. I think the first thing we should do is work on terminology. This is not an ADC, this is a configurable low-pass-filter.
It works in conjunction with a analog frontend (ADC) that produces a 1-bit pulse-density-modulated stream, takes that stream and converts it into N-bit PCM samples. The PCM samples are generated at a fraction of the PDM stream samplerate that corresponds to the decimation factor.
This is not an unusual device. Many audio CODEC and audio controllers contain such a core as well as most SigmaDelta converters supported by IIO. What is special about this part is that it is a dedicated core that is not embedded in some other hardware component. This creates greater flexibility, but of course also greater complexity that is required to manage all that flexibility.
We shouldn't codify anything about the kernel internal frameworks through which the device might be exposed into the devicetree. We should accurately describe the hardware (including the analog frontend) and then create a appropriate software structures to handle them.
So if everyone is aligned, i will abandon the MFD driver and try to bind an ASoC driver on IIO interface. The "challenge" is to define appropriate relation ship between ASoC and IIO...
In a first step, a lot of question to answers and points to clarify... i will reply to associated mails.
Then I see two main topics to clarify:
- DFSDM integration in IIO in a generic way. Lars, if i well interpret,
your proposal should be to introduce front-end and filter notions in IIO, to support this kind of hardware?
- DMA engine for audio purpose.
I propose to come back with RFC for both subjects.
If you want to have more detail on DFSDM: DFSDM datasheet is included in STM32F413 datasheet available here: http://www.st.com/content/ccc/resource/technical/document/reference_manual/g...
DFSDM Block diagram p 384
Sounds like a sensible way forward to me.
Might well go through a few more rounds before we get this right, but I agree with Lars that this looks to be something we are going to meet more and more in the future so would be excellent to get it right!
Jonathan
Regards Arnaud
-- 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 Lars,
On 01/29/2017 03:19 PM, Lars-Peter Clausen wrote:
On 01/29/2017 01:28 PM, Jonathan Cameron wrote: [...]
Jonathan, Mark, Please could you share your opinion on this topic?
Hmm - based on a fairly quick read through of the code (which is never ideal!). I can see that the ideal would indeed be as Lee says, to expand the IIO interfaces sufficiently to support what you need.
So, reading the code (fairly quickly I'm afraid as had a lot of reviews to catch up on this weekend). What we need:
- DMA support in the ADC driver. This would be a good anyway!
- DMA consumer support - I defer to Lars for comments on this.
- Means of describing and controlling the sinc filters applied.
- Appropriate channel support. I'm not convinced that it doesn't make
sense to have IIO channels for the microphones - at least in a streaming mode. It's data - I don't really care what ;) Coarsely it's a filtered pulse per period counter which is a perfectly valid type to have a channel for.
The big question to my mind is the DMA consumer support. How would it work. It it wouldn't this is somewhat of a non starter.
To bring up another slightly ugly MFD case where it is borderline on whether an MFD makes sense (just as a reference point of something we have discussed a few times before)
ADCs with features directed at touchscreen support. These are odd as the ADC bit is generic, but the specific output and read sequences used for touchscreen reading don't correspond to anything that makes any real sense for other applications.
We have started to get hybrid drives that have an MFD underneath but do the ADC reads through IIO consumer interfaces, and the timing control from a touchscreen driver. We haven't really gotten this one right yet either.
Here however, to my mind things are different - as I read it (and feel free to point out what I'm missing), the sound usecase is just a question of setting up sampling frequencies and filters appropriate to the microphones and what ASoC expects?
That's not to say the IIO dma stuff is flexible enough (yet) to handle the data flows, but perhaps we can work towards that.
Yeah, so this is a bit different, but not unexpected. And I'm sure we'll see more similar hardware in the future. I've talked about this before[1], the cost structure of creating and manufacturing new hardware drives the design in a certain direction so that we end up with general purpose hardware that suddenly has applications in multiple frameworks that were previously fully orthogonal.
This device is certainly not a multi-function-device. It only has one function, it's a sigma-delta demodulator. It is rather a multi-purpose-device. It can be used for sigma-delta demodulation in audio applications as well as more specialized data capture applications.
It's comparable to something like a GPIO that can be used to control a reset pin or turn on and off a LED. The GPIO chip is not considered multi-function-device though, even though it can be used for many different applications.
As for DMA we already have a lot of DMA infrastructure on the audio side and we probably want to reuse that rather than inserting IIO as a middle layer since audio buffer capture as different requirements from IIO buffer and we'd have to go the route of the least common denominator and loose expressibility in the process.
I'm agree with your analysis. Audio DMA engine seems a more secure solution to avoid audio runtime issue, and should minimize impact in IIO.
I've created a IIO buffer[2] that does not capture data to memory but is only used to enable/disable the data capture process. We use this in setups where the data is passed from the converter to a application specific processing chain without ever going through system memory. This buffer could probably also be used here on the audio side to control the converter state.
This remind me HDMI. For HDMI, solution integrated is a drm driver that probes "hdmi-audio-codec" driver.
Something similar could be done. An IIO device that probes a generic DAI ASoC driver. Driver data structure should be used to provide DMA config and IIO device phandles. Then DAI drivers could use your "hw_consummer" interface to control iio device...
- Lars
[1] https://lists.linuxfoundation.org/pipermail/ksummit-discuss/2016-July/003029...
[2] https://github.com/analogdevicesinc/linux/blob/xcomm_zynq/drivers/iio/buffer...
Regards Arnaud
On 01/27/2017 06:17 PM, Lee Jones wrote:
On Fri, 27 Jan 2017, Arnaud Pouliquen wrote:
On 01/27/2017 11:15 AM, Lee Jones wrote:
On Tue, 24 Jan 2017, Arnaud Pouliquen wrote:
On 01/24/2017 12:36 PM, Lee Jones wrote:
On Tue, 24 Jan 2017, Arnaud Pouliquen wrote:
On 01/24/2017 09:22 AM, Lee Jones wrote: > On Mon, 23 Jan 2017, Arnaud Pouliquen wrote: > >> DFSDM hardware IP can be used at the same time for ADC sigma delta > > Same time as what? DFSDM is used for ADC acquisition (through IIO) but also PDM microphone capture (through ASOC). > >> conversion and audio PDM microphone. >> MFD driver is in charge of configuring IP registers and managing IP clocks. >> For this it exports an API to handles filters and channels resources. > > This looks like an ADC driver? What is it that makes it an MFD? Yes it a kind of ADC but that supports 2 features audio and iio. So it has to support 2 features based on 2 separate Frameworks.
I'm still unsure why it needs to live in MFD.
By the looks of it, this driver needs to move into IIO and you need to call into it from ASoC.
I think i introduce confusion by speaking about ADC for audio...
- IIO handles sigma delta ADCs that can be used as example for motor
controls. the aim is to get value based on an application request or using some triggers. example: http://www.ti.com/lit/ds/symlink/ads1202.pdf
- For audio part, we speak about Digital mems microphones that generate
PDM format stream. PDM is a continuous real time stream dedicated to audio record and must be handled in ASOC ( codec Dmic part driver is /sound/soc/codec/dmic.c). DMIC example: http://www.st.com/content/ccc/resource/technical/document/datasheet/47/bd/d2...
Form my point of view it very strange to handle DMICs in IIO, as it is not designed to support audio streams.it is two separate features that are not compatible.
Now, from software point of view That would means that IIO declares ADCs that it can not expose, because DMIC is not IIO standard. But IIO inkern API needs that device is declared
So i should define a specific API in IIO for ASOC driver.
Yes, this is what I think you should do.
MFD is not a dumping ground for devices that do not fit anywhere else.
I fully understand that you don't want to create unnecessary MFD devices. But In case of DFSDM ,it is really a device that supports 2 features. The main evidence is that "ADC acquisition" and "digital microphone" features are handled by two separate frameworks. So this corresponds to two features that share the same device resources (registers, IRQs, clocks). Seems that this match with MFD definition.
Now, if IIO and ASOC maintainers are aligned with you proposal, i will move MFD part in IIO. But in this case, i can not see another way to do it, except a MFD driver hidden in IIO?
Here is my analysis of a design in IIO:
Natural way to do this is to consider that ASOC is a customer of IIO so use the customer.h API.
- As Digital microphone can not be handled by IIO dev interface, i can
not expose them as an IIO channel, that would be visible by applications in /sys/bus/iio/devices/iio 2) ASOC needs to configure DFSDM filter to match to specific frequencies and sample formats requested by users. Not standard IIO interface to do this. (on IIO side this is done by attribute file) 3) audio stream is a real time stream. So for audio quality best is to minimize latencies and Xrun. That why transfer as done from HW to application using LLI DMA transfers with a minimum of copy. This does not match with the IIO inkern API. => So seems not possible to use IIO inkern API.
That's means that i would need to create a ST specific include file to offer an API to ASoC to handle DFSDM resources linked to the DMIC. and to probe ASOc device in IIO one. So the solution would be to create something like a sub IIO driver That probe and handle some IIO and ASOC devices. Ultimately This would correspond to a MFD driver integrated in IIO...
The issue is not the creation of an MFD driver to create shared resources and probe the child devices. That's what MFD *is* for. What I do take exception to is having lots of code in MFD which should clearly live in a different subsystem.
Since this device only serves a couple of functions, I expect it to be a few hundred lines, maximum. Everything else should be farmed out. MFD knows nothing of "channels" or "filters" so anything related to them (init, get, start, stop, release, etc) needs to find another home. Whether that's in IIO is a separate discussion, but it certainly doesn't live in MFD.
Thanks for your time and explanations Lee. As you recommended, I will try to integrate it in IIO. And i notice for the next time that i can not expose such "high level" interface in MFD.
Regards Arnaud
On Fri, Jan 27, 2017 at 02:45:49PM +0100, Arnaud Pouliquen wrote:
Jonathan, Mark, Please could you share your opinion on this topic?
I really don't have enough visbility of what the hardware or software is doing to say much and though there's extensive backtrace in here it's not enough to follow. If it's a generic ADC that happens to have audio applications then some relationship with IIO might make sense but I don't know how separate the audio and general purpose bits of the hardware are.
Please understand that I get copied in on *lots* of threads about MFDs with drivers getting posted repeatedly for whatever revisions are needed with little relevance to me so it's very random if I even look at these things, much less read them with detail.
Hello Mark,
On 01/29/2017 06:50 PM, Mark Brown wrote:
On Fri, Jan 27, 2017 at 02:45:49PM +0100, Arnaud Pouliquen wrote:
Jonathan, Mark, Please could you share your opinion on this topic?
I really don't have enough visbility of what the hardware or software is doing to say much and though there's extensive backtrace in here it's not enough to follow. If it's a generic ADC that happens to have audio applications then some relationship with IIO might make sense but I don't know how separate the audio and general purpose bits of the hardware are.
Please understand that I get copied in on *lots* of threads about MFDs with drivers getting posted repeatedly for whatever revisions are needed with little relevance to me so it's very random if I even look at these things, much less read them with detail.
In a first step, your comment is enough from my point of view as you are not disagree with the concept of a relationship between ASOC an IIO. This confirms that the MFD driver have to be abandoned.
Then details on how to do it is another story, that should trigs some other discussions...
Thanks and Regards,
Arnaud
On 24/01/17 13:32, Arnaud Pouliquen wrote:
On 01/24/2017 12:36 PM, Lee Jones wrote:
On Tue, 24 Jan 2017, Arnaud Pouliquen wrote:
On 01/24/2017 09:22 AM, Lee Jones wrote:
On Mon, 23 Jan 2017, Arnaud Pouliquen wrote:
DFSDM hardware IP can be used at the same time for ADC sigma delta
Same time as what?
DFSDM is used for ADC acquisition (through IIO) but also PDM microphone capture (through ASOC).
conversion and audio PDM microphone. MFD driver is in charge of configuring IP registers and managing IP clocks. For this it exports an API to handles filters and channels resources.
This looks like an ADC driver? What is it that makes it an MFD?
Yes it a kind of ADC but that supports 2 features audio and iio. So it has to support 2 features based on 2 separate Frameworks.
I'm still unsure why it needs to live in MFD.
By the looks of it, this driver needs to move into IIO and you need to call into it from ASoC.
I think i introduce confusion by speaking about ADC for audio...
- IIO handles sigma delta ADCs that can be used as example for motor
controls. the aim is to get value based on an application request or using some triggers. example: http://www.ti.com/lit/ds/symlink/ads1202.pdf
- For audio part, we speak about Digital mems microphones that generate
PDM format stream. PDM is a continuous real time stream dedicated to audio record and must be handled in ASOC ( codec Dmic part driver is /sound/soc/codec/dmic.c). DMIC example: http://www.st.com/content/ccc/resource/technical/document/datasheet/47/bd/d2...
Form my point of view it very strange to handle DMICs in IIO, as it is not designed to support audio streams.it is two separate features that are not compatible.
Now, from software point of view That would means that IIO declares ADCs that it can not expose, because DMIC is not IIO standard. But IIO inkern API needs that device is declared... So i should define a specific API in IIO for ASOC driver.
IIO isn't designed specifically to support audio streams, but it does have high bandwidth continuous sampling support via the dma buffer side of things. Analog devices use this stuff for software defined radio applications.
What we don't currently have is an in kernel consumer interface for this. I'm not really sure how difficult it would be to do this, but it is certainly sounding like there is demand for it.
Probably the best person to comment is Lars who wrote the dma stuff in the first place and also has a more than passing familiarity with ASOC.
I've sent Lars a PM to highlight this thread.
Jonathan
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com
drivers/mfd/Kconfig | 11 + drivers/mfd/Makefile | 2 + drivers/mfd/stm32-dfsdm-reg.h | 220 +++++++++ drivers/mfd/stm32-dfsdm.c | 1044 +++++++++++++++++++++++++++++++++++++++ include/linux/mfd/stm32-dfsdm.h | 324 ++++++++++++ 5 files changed, 1601 insertions(+) create mode 100644 drivers/mfd/stm32-dfsdm-reg.h create mode 100644 drivers/mfd/stm32-dfsdm.c create mode 100644 include/linux/mfd/stm32-dfsdm.h
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index c6df644..4bb660b 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -1607,6 +1607,17 @@ config MFD_STW481X in various ST Microelectronics and ST-Ericsson embedded Nomadik series.
+config MFD_STM32_DFSDM
- tristate "ST Microelectronics STM32 DFSDM"
- depends on (ARCH_STM32 && OF) || COMPILE_TEST
- select MFD_CORE
- select REGMAP
- select REGMAP_MMIO
- help
Select this option to enable the STM32 Digital Filter
for Sigma Delta Modulators (DFSDM) driver used
in various STM32 series.
menu "Multimedia Capabilities Port drivers" depends on ARCH_SA1100
[...]
Regards Arnaud
-- 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 23/01/17 16:32, Arnaud Pouliquen wrote:
DFSDM hardware IP can be used at the same time for ADC sigma delta conversion and audio PDM microphone. MFD driver is in charge of configuring IP registers and managing IP clocks. For this it exports an API to handles filters and channels resources.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com
This is somewhat of a beast. I would be tempted to build it up in more bite sized chunks.
Obvious things to drop from a first version (basically to make it easier to review) would be injected supported. There may well be others but you'll have a better feel for that than me.
I really don't like the wrappers round the regmap functions though. They mean you are papering over errors if they occur and make the code slightly harder to read by implying that something else is going on.
Yes the code will be longer without them, but you will also be forced to think properly about error paths.
Anyhow, was mostly reading this to get a feel for what was going on in the whole series so not really a terribly thorough review I'm afraid. Sorry about that!
Jonathan
drivers/mfd/Kconfig | 11 + drivers/mfd/Makefile | 2 + drivers/mfd/stm32-dfsdm-reg.h | 220 +++++++++ drivers/mfd/stm32-dfsdm.c | 1044 +++++++++++++++++++++++++++++++++++++++ include/linux/mfd/stm32-dfsdm.h | 324 ++++++++++++ 5 files changed, 1601 insertions(+) create mode 100644 drivers/mfd/stm32-dfsdm-reg.h create mode 100644 drivers/mfd/stm32-dfsdm.c create mode 100644 include/linux/mfd/stm32-dfsdm.h
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index c6df644..4bb660b 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -1607,6 +1607,17 @@ config MFD_STW481X in various ST Microelectronics and ST-Ericsson embedded Nomadik series.
+config MFD_STM32_DFSDM
- tristate "ST Microelectronics STM32 DFSDM"
- depends on (ARCH_STM32 && OF) || COMPILE_TEST
- select MFD_CORE
- select REGMAP
- select REGMAP_MMIO
- help
Select this option to enable the STM32 Digital Filter
for Sigma Delta Modulators (DFSDM) driver used
in various STM32 series.
menu "Multimedia Capabilities Port drivers" depends on ARCH_SA1100
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 9834e66..1f095e5 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -211,3 +211,5 @@ obj-$(CONFIG_INTEL_SOC_PMIC) += intel-soc-pmic.o obj-$(CONFIG_MFD_MT6397) += mt6397-core.o
obj-$(CONFIG_MFD_ALTERA_A10SR) += altera-a10sr.o
+obj-$(CONFIG_MFD_STM32_DFSDM) += stm32-dfsdm.o \ No newline at end of file diff --git a/drivers/mfd/stm32-dfsdm-reg.h b/drivers/mfd/stm32-dfsdm-reg.h new file mode 100644 index 0000000..05ff702 --- /dev/null +++ b/drivers/mfd/stm32-dfsdm-reg.h @@ -0,0 +1,220 @@ +/*
- This file is part of STM32 DFSDM mfd 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_REG_H +#define MDF_STM32_DFSDM_REG_H
+#include <linux/bitfield.h> +/*
- Channels 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 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)
+#endif diff --git a/drivers/mfd/stm32-dfsdm.c b/drivers/mfd/stm32-dfsdm.c new file mode 100644 index 0000000..81ca29c --- /dev/null +++ b/drivers/mfd/stm32-dfsdm.c @@ -0,0 +1,1044 @@ +/*
- This file is part of STM32 DFSDM mfd 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/mfd/core.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/regmap.h> +#include <linux/slab.h>
+#include <linux/mfd/stm32-dfsdm.h>
+#include "stm32-dfsdm-reg.h"
+#define DFSDM_UPDATE_BITS(regm, reg, mask, val) \
WARN_ON(regmap_update_bits(regm, reg, mask, val))
Don't do these wrappers please. Handle error correctly in all cases. Reviewing as I tend to do backwards through the driver, I thought these were doing something interesting.
Effectively you have no error handling as a result of these which needs fixing.
+#define DFSDM_REG_READ(regm, reg, val) \
WARN_ON(regmap_read(regm, reg, val))
+#define DFSDM_REG_WRITE(regm, reg, val) \
WARN_ON(regmap_write(regm, reg, val))
+#define STM32H7_DFSDM_NUM_FILTERS 4 +#define STM32H7_DFSDM_NUM_INPUTS 8
+enum dfsdm_clkout_src {
- DFSDM_CLK,
- AUDIO_CLK
+};
+struct stm32_dev_data {
- const struct stm32_dfsdm dfsdm;
- const struct regmap_config *regmap_cfg;
+};
+struct dfsdm_priv;
+struct filter_params {
- unsigned int id;
- int irq;
- struct stm32_dfsdm_fl_event event;
- u32 event_mask;
- struct dfsdm_priv *priv; /* Cross ref for context */
- unsigned int ext_ch_mask;
- unsigned int scan_ch;
+};
+struct ch_params {
- struct stm32_dfsdm_channel ch;
+};
I'd like to see a lot more comments in here. Perhaps full kernel-doc as some elements are not that obvious at least to a fairly casual read.
+struct dfsdm_priv {
- struct platform_device *pdev;
- struct stm32_dfsdm dfsdm;
- spinlock_t lock; /* Used for resource sharing & interrupt lock */
- /* Filters */
- struct filter_params *filters;
- unsigned int free_filter_mask;
- unsigned int scd_filter_mask;
- unsigned int ckab_filter_mask;
- /* Channels */
- struct stm32_dfsdm_channel *channels;
- unsigned int free_channel_mask;
- atomic_t n_active_ch;
- /* Clock */
- struct clk *clk;
- struct clk *aclk;
- unsigned int clkout_div;
- unsigned int clkout_freq_req;
- /* Registers*/
- void __iomem *base;
- struct regmap *regmap;
- phys_addr_t phys_base;
+};
+/*
- Common
- */
+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 = DFSDM_CNVTIMR(STM32H7_DFSDM_NUM_FILTERS - 1),
- .volatile_reg = stm32_dfsdm_volatile_reg,
- .fast_io = true,
+};
+static const struct stm32_dev_data stm32h7_data = {
- .dfsdm.max_channels = STM32H7_DFSDM_NUM_INPUTS,
- .dfsdm.max_filters = STM32H7_DFSDM_NUM_FILTERS,
- .regmap_cfg = &stm32h7_dfsdm_regmap_cfg,
+};
+static int stm32_dfsdm_start_dfsdm(struct dfsdm_priv *priv) +{
- int ret;
- struct device *dev = &priv->pdev->dev;
- if (atomic_inc_return(&priv->n_active_ch) == 1) {
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");
return ret;
}
}
DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CHCFGR1(0),
DFSDM_CHCFGR1_CKOUTDIV_MASK,
DFSDM_CHCFGR1_CKOUTDIV(priv->clkout_div));
DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CHCFGR1(0),
DFSDM_CHCFGR1_DFSDMEN_MASK,
DFSDM_CHCFGR1_DFSDMEN(1));
- }
- dev_dbg(&priv->pdev->dev, "%s: n_active_ch %d\n", __func__,
atomic_read(&priv->n_active_ch));
- return 0;
+}
+static void stm32_dfsdm_stop_dfsdm(struct dfsdm_priv *priv) +{
- if (atomic_dec_and_test(&priv->n_active_ch)) {
DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CHCFGR1(0),
DFSDM_CHCFGR1_DFSDMEN_MASK,
DFSDM_CHCFGR1_DFSDMEN(0));
DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CHCFGR1(0),
DFSDM_CHCFGR1_CKOUTDIV_MASK,
DFSDM_CHCFGR1_CKOUTDIV(0));
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));
+}
+static unsigned int stm32_dfsdm_get_clkout_divider(struct dfsdm_priv *priv,
unsigned long rate)
+{
- unsigned int delta, div;
- /* div = 0 disables the clockout */
- if (!priv->clkout_freq_req)
return 0;
- div = DIV_ROUND_CLOSEST(rate, priv->clkout_freq_req);
- delta = rate - (priv->clkout_freq_req * div);
- if (delta)
dev_warn(&priv->pdev->dev,
"clkout not accurate. delta (Hz): %d\n", delta);
- dev_dbg(&priv->pdev->dev, "%s: clk: %lu (Hz), div %u\n",
__func__, rate, div);
- return (div - 1);
+}
+/*
- Filters
- */
+static int stm32_dfsdm_clear_event(struct dfsdm_priv *priv, unsigned int fl_id,
unsigned int event, int mask)
+{
- int val;
- switch (event) {
- case DFSDM_EVENT_INJ_EOC:
DFSDM_REG_READ(priv->regmap, DFSDM_JDATAR(fl_id), &val);
break;
- case DFSDM_EVENT_REG_EOC:
DFSDM_REG_READ(priv->regmap, DFSDM_RDATAR(fl_id), &val);
break;
- case DFSDM_EVENT_INJ_XRUN:
DFSDM_UPDATE_BITS(priv->regmap, DFSDM_ICR(fl_id),
DFSDM_ICR_CLRJOVRF_MASK,
DFSDM_ICR_CLRJOVRF_MASK);
break;
- case DFSDM_EVENT_REG_XRUN:
DFSDM_UPDATE_BITS(priv->regmap, DFSDM_ICR(fl_id),
DFSDM_ICR_CLRROVRF_MASK,
DFSDM_ICR_CLRROVRF_MASK);
break;
- default:
return -EINVAL;
- }
- return 0;
+}
+static irqreturn_t stm32_dfsdm_irq(int irq, void *arg) +{
- struct filter_params *params = arg;
- unsigned int status;
- struct dfsdm_priv *priv = params->priv;
- unsigned int event_mask = params->event_mask;
- DFSDM_REG_READ(priv->regmap, DFSDM_ISR(params->id), &status);
- if (status & DFSDM_ISR_JOVRF_MASK) {
if (event_mask & DFSDM_EVENT_INJ_XRUN) {
params->event.cb(&priv->dfsdm, params->id,
DFSDM_EVENT_INJ_XRUN, 0,
params->event.context);
}
stm32_dfsdm_clear_event(priv, params->id, DFSDM_EVENT_INJ_XRUN,
0);
- }
- if (status & DFSDM_ISR_ROVRF_MASK) {
if (event_mask & DFSDM_EVENT_REG_XRUN) {
params->event.cb(&priv->dfsdm, params->id,
DFSDM_EVENT_REG_XRUN, 0,
params->event.context);
}
stm32_dfsdm_clear_event(priv, params->id, DFSDM_EVENT_REG_XRUN,
0);
- }
- if (status & DFSDM_ISR_JEOCF_MASK) {
if (event_mask & DFSDM_EVENT_INJ_EOC)
params->event.cb(&priv->dfsdm, params->id,
DFSDM_EVENT_INJ_EOC, 0,
params->event.context);
else
stm32_dfsdm_clear_event(priv, params->id,
DFSDM_EVENT_INJ_EOC, 0);
- }
- if (status & DFSDM_ISR_REOCF_MASK) {
if (event_mask & DFSDM_EVENT_REG_EOC)
params->event.cb(&priv->dfsdm, params->id,
DFSDM_EVENT_REG_EOC, 0,
params->event.context);
else
stm32_dfsdm_clear_event(priv, params->id,
DFSDM_EVENT_REG_EOC, 0);
- }
- return IRQ_HANDLED;
+}
+static void stm32_dfsdm_configure_reg_conv(struct dfsdm_priv *priv,
unsigned int fl_id,
struct stm32_dfsdm_regular *params)
+{
- unsigned int ch_id = params->ch_src;
- DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR1(fl_id), DFSDM_CR1_RCH_MASK,
DFSDM_CR1_RCH(ch_id));
- DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR1(fl_id), DFSDM_CR1_FAST_MASK,
DFSDM_CR1_FAST(params->fast_mode));
- DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR1(fl_id), DFSDM_CR1_RCONT_MASK,
DFSDM_CR1_RCONT(params->cont_mode));
- DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR1(fl_id), DFSDM_CR1_RDMAEN_MASK,
DFSDM_CR1_RDMAEN(params->dma_mode));
- DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR1(fl_id), DFSDM_CR1_RSYNC_MASK,
DFSDM_CR1_RSYNC(params->sync_mode));
- priv->filters[fl_id].scan_ch = BIT(ch_id);
+}
+static void stm32_dfsdm_configure_inj_conv(struct dfsdm_priv *priv,
unsigned int fl_id,
struct stm32_dfsdm_injected *params)
+{
- int val;
- DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR1(fl_id), DFSDM_CR1_JSCAN_MASK,
DFSDM_CR1_JSCAN(params->scan_mode));
- DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR1(fl_id), DFSDM_CR1_JDMAEN_MASK,
DFSDM_CR1_JDMAEN(params->dma_mode));
- val = (params->trigger == DFSDM_FILTER_EXT_TRIGGER) ?
params->trig_src : 0;
- DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR1(fl_id),
DFSDM_CR1_JEXTSEL_MASK,
DFSDM_CR1_JEXTSEL(val));
- val = (params->trigger == DFSDM_FILTER_SYNC_TRIGGER) ? 1 : 0;
- DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR1(fl_id), DFSDM_CR1_JSYNC_MASK,
DFSDM_CR1_JSYNC(val));
- DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR1(fl_id), DFSDM_CR1_JEXTEN_MASK,
DFSDM_CR1_JEXTEN(params->trig_pol));
- priv->filters[fl_id].scan_ch = params->ch_group;
- DFSDM_REG_WRITE(priv->regmap, DFSDM_JCHGR(fl_id), params->ch_group);
+}
+/**
- stm32_dfsdm_configure_filter - Configure filter.
- @dfsdm: Handle used to retrieve dfsdm context.
- @fl_id: Filter id.
- @conv: Conversion type regular or injected.
- */
+int stm32_dfsdm_configure_filter(struct stm32_dfsdm *dfsdm, unsigned int fl_id,
struct stm32_dfsdm_filter *fl_cfg)
+{
- struct dfsdm_priv *priv = container_of(dfsdm, struct dfsdm_priv,
dfsdm);
- struct stm32_dfsdm_sinc_filter *sparams = &fl_cfg->sinc_params;
- dev_dbg(&priv->pdev->dev, "%s:config filter %d\n", __func__, fl_id);
- /* Average integrator oversampling */
- if ((!fl_cfg->int_oversampling) ||
(fl_cfg->int_oversampling > DFSDM_MAX_INT_OVERSAMPLING)) {
dev_err(&priv->pdev->dev, "invalid integrator oversampling %d\n",
fl_cfg->int_oversampling);
return -EINVAL;
- }
- DFSDM_UPDATE_BITS(priv->regmap, DFSDM_FCR(fl_id), DFSDM_FCR_IOSR_MASK,
DFSDM_FCR_IOSR((fl_cfg->int_oversampling - 1)));
- /* Oversamplings and filter*/
- if ((!sparams->oversampling) ||
(sparams->oversampling > DFSDM_MAX_FL_OVERSAMPLING)) {
dev_err(&priv->pdev->dev, "invalid oversampling %d\n",
sparams->oversampling);
return -EINVAL;
- }
- if (sparams->order > DFSDM_SINC5_ORDER) {
dev_err(&priv->pdev->dev, "invalid filter order %d\n",
sparams->order);
return -EINVAL;
- }
- DFSDM_UPDATE_BITS(priv->regmap, DFSDM_FCR(fl_id), DFSDM_FCR_FOSR_MASK,
DFSDM_FCR_FOSR((sparams->oversampling - 1)));
- DFSDM_UPDATE_BITS(priv->regmap, DFSDM_FCR(fl_id), DFSDM_FCR_FORD_MASK,
DFSDM_FCR_FORD(sparams->order));
- /* Conversion */
- if (fl_cfg->inj_params)
stm32_dfsdm_configure_inj_conv(priv, fl_id, fl_cfg->inj_params);
- else if (fl_cfg->reg_params)
stm32_dfsdm_configure_reg_conv(priv, fl_id, fl_cfg->reg_params);
- priv->filters[fl_id].event = fl_cfg->event;
- return 0;
+} +EXPORT_SYMBOL_GPL(dfsdm_configure_filter);
+/**
- stm32_dfsdm_start_filter - Start filter conversion.
- @dfsdm: Handle used to retrieve dfsdm context.
- @fl_id: Filter id.
- @conv: Conversion type regular or injected.
- */
+void stm32_dfsdm_start_filter(struct stm32_dfsdm *dfsdm, unsigned int fl_id,
enum stm32_dfsdm_conv_type conv)
+{
- struct dfsdm_priv *priv = container_of(dfsdm, struct dfsdm_priv, dfsdm);
- dev_dbg(&priv->pdev->dev, "%s:start filter %d\n", __func__, fl_id);
- DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR1(fl_id), DFSDM_CR1_DFEN_MASK,
DFSDM_CR1_DFEN(1));
- if (conv == DFSDM_FILTER_REG_CONV) {
DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR1(fl_id),
DFSDM_CR1_RSWSTART_MASK,
DFSDM_CR1_RSWSTART(1));
- } else if (conv == DFSDM_FILTER_SW_INJ_CONV) {
DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR1(fl_id),
DFSDM_CR1_JSWSTART_MASK,
DFSDM_CR1_JSWSTART(1));
- }
+} +EXPORT_SYMBOL_GPL(dfsdm_start_filter);
+/**
- stm32_dfsdm_stop_filter - Stop filter conversion.
- @dfsdm: Handle used to retrieve dfsdm context.
- @fl_id: Filter id.
- */
+void stm32_dfsdm_stop_filter(struct stm32_dfsdm *dfsdm, unsigned int fl_id) +{
- struct dfsdm_priv *priv = container_of(dfsdm, struct dfsdm_priv, dfsdm);
- dev_dbg(&priv->pdev->dev, "%s:stop filter %d\n", __func__, fl_id);
- DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR1(fl_id), DFSDM_CR1_DFEN_MASK,
DFSDM_CR1_DFEN(0));
- priv->filters[fl_id].scan_ch = 0;
+} +EXPORT_SYMBOL_GPL(dfsdm_stop_filter);
+/**
- stm32_dfsdm_read_fl_conv - Read filter conversion.
- @dfsdm: Handle used to retrieve dfsdm context.
- @fl_id: Filter id.
- @type: Regular or injected conversion.
- */
+void stm32_dfsdm_read_fl_conv(struct stm32_dfsdm *dfsdm, unsigned int fl_id,
u32 *val, int *ch_id,
enum stm32_dfsdm_conv_type type)
+{
- struct dfsdm_priv *priv = container_of(dfsdm, struct dfsdm_priv, dfsdm);
- int reg_v, offset;
- if (type == DFSDM_FILTER_REG_CONV)
offset = DFSDM_RDATAR(fl_id);
- else
offset = DFSDM_JDATAR(fl_id);
- DFSDM_REG_READ(priv->regmap, offset, ®_v);
- *ch_id = reg_v & DFSDM_DATAR_CH_MASK;
- *val = reg_v & DFSDM_DATAR_DATA_MASK;
+} +EXPORT_SYMBOL_GPL(dfsdm_read_fl_conv);
+/**
- stm32_dfsdm_get_filter - Get filter instance.
- @dfsdm: Handle used to retrieve dfsdm context.
- @fl_id: Filter instance to reserve.
- Reserves a DFSDM filter resource.
- */
+int stm32_dfsdm_get_filter(struct stm32_dfsdm *dfsdm, unsigned int fl_id) +{
- struct dfsdm_priv *priv = container_of(dfsdm, struct dfsdm_priv,
dfsdm);
- struct device *dev = &priv->pdev->dev;
- spin_lock(&priv->lock);
- if (!(priv->free_filter_mask & BIT(fl_id))) {
spin_unlock(&priv->lock);
dev_err(dev, "filter resource %d available\n", fl_id);
return -EBUSY;
- }
- priv->free_filter_mask &= ~BIT(fl_id);
- spin_unlock(&priv->lock);
- dev_dbg(dev, "%s: new mask %#x\n", __func__, priv->free_filter_mask);
- return 0;
+} +EXPORT_SYMBOL_GPL(dfsdm_get_filter);
+/**
- stm32_dfsdm_release_filter - Release filter instance.
- @dfsdm: Handle used to retrieve dfsdm context.
- @fl_id: Filter id.
- Free the DFSDM filter resource.
- */
+void stm32_dfsdm_release_filter(struct stm32_dfsdm *dfsdm, unsigned int fl_id) +{
- struct dfsdm_priv *priv = container_of(dfsdm, struct dfsdm_priv, dfsdm);
- spin_lock(&priv->lock);
- priv->free_filter_mask |= BIT(fl_id);
- spin_unlock(&priv->lock);
+} +EXPORT_SYMBOL_GPL(dfsdm_release_filter);
+/**
- stm32_dfsdm_get_filter_dma_addr - Get register address for dma transfer.
- @dfsdm: Handle used to retrieve dfsdm context.
- @fl_id: Filter id.
- @conv: Conversion type.
- */
+dma_addr_t stm32_dfsdm_get_filter_dma_phy_addr(struct stm32_dfsdm *dfsdm,
unsigned int fl_id,
enum stm32_dfsdm_conv_type conv)
+{
- struct dfsdm_priv *priv = container_of(dfsdm, struct dfsdm_priv, dfsdm);
- if (conv == DFSDM_FILTER_REG_CONV)
return (dma_addr_t)(priv->phys_base + DFSDM_RDATAR(fl_id));
- else
return (dma_addr_t)(priv->phys_base + DFSDM_JDATAR(fl_id));
+}
+/**
- stm32_dfsdm_register_fl_event - Register filter event.
What is a filter event? More details good on things that are very device specific like this.
- @dfsdm: Handle used to retrieve dfsdm context.
- @fl_id: Filter id.
- @event: Event to unregister.
- @chan_mask: Mask of channels associated to filter.
- The function enables associated IRQ.
- */
+int stm32_dfsdm_register_fl_event(struct stm32_dfsdm *dfsdm, unsigned int fl_id,
enum stm32_dfsdm_events event,
unsigned int chan_mask)
+{
- struct dfsdm_priv *priv = container_of(dfsdm, struct dfsdm_priv, dfsdm);
- unsigned long flags, ulmask = chan_mask;
- int ret, i;
- dev_dbg(&priv->pdev->dev, "%s:for filter %d: event %#x ch_mask %#x\n",
__func__, fl_id, event, chan_mask);
- if (event > DFSDM_EVENT_CKA)
return -EINVAL;
- /* Clear interrupt before enable them */
- ret = stm32_dfsdm_clear_event(priv, fl_id, event, chan_mask);
- if (ret < 0)
return ret;
- spin_lock_irqsave(&priv->lock, flags);
- /* Enable interrupts */
- switch (event) {
- case DFSDM_EVENT_SCD:
for_each_set_bit(i, &ulmask, priv->dfsdm.max_channels) {
DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CHCFGR1(i),
DFSDM_CHCFGR1_SCDEN_MASK,
DFSDM_CHCFGR1_SCDEN(1));
}
if (!priv->scd_filter_mask)
DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR2(0),
DFSDM_CR2_SCDIE_MASK,
DFSDM_CR2_SCDIE(1));
priv->scd_filter_mask |= BIT(fl_id);
break;
- case DFSDM_EVENT_CKA:
for_each_set_bit(i, &ulmask, priv->dfsdm.max_channels) {
DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CHCFGR1(i),
DFSDM_CHCFGR1_CKABEN_MASK,
DFSDM_CHCFGR1_CKABEN(1));
}
if (!priv->ckab_filter_mask)
DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR2(0),
DFSDM_CR2_CKABIE_MASK,
DFSDM_CR2_CKABIE(1));
priv->ckab_filter_mask |= BIT(fl_id);
break;
- default:
DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR2(fl_id), event, event);
- }
- priv->filters[fl_id].event_mask |= event;
- spin_unlock_irqrestore(&priv->lock, flags);
- return 0;
+} +EXPORT_SYMBOL_GPL(dfsdm_register_fl_event);
+/**
- stm32_dfsdm_unregister_fl_event - Unregister filter event.
- @dfsdm: Handle used to retrieve dfsdm context.
- @fl_id: Filter id.
- @event: Event to unregister.
- @chan_mask: Mask of channels associated to filter.
- The function disables associated IRQ.
- */
+int stm32_dfsdm_unregister_fl_event(struct stm32_dfsdm *dfsdm,
unsigned int fl_id,
enum stm32_dfsdm_events event,
unsigned int chan_mask)
+{
- struct dfsdm_priv *priv = container_of(dfsdm, struct dfsdm_priv, dfsdm);
- unsigned long flags, ulmask = chan_mask;
- int i;
- dev_dbg(&priv->pdev->dev, "%s:for filter %d: event %#x ch_mask %#x\n",
__func__, fl_id, event, chan_mask);
- if (event > DFSDM_EVENT_CKA)
return -EINVAL;
- spin_lock_irqsave(&priv->lock, flags);
- /* Disable interrupts */
- switch (event) {
- case DFSDM_EVENT_SCD:
for_each_set_bit(i, &ulmask, priv->dfsdm.max_channels) {
DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CHCFGR1(i),
DFSDM_CHCFGR1_SCDEN_MASK,
DFSDM_CHCFGR1_SCDEN(0));
}
priv->scd_filter_mask &= ~BIT(fl_id);
if (!priv->scd_filter_mask)
DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR2(0),
DFSDM_CR2_SCDIE_MASK,
DFSDM_CR2_SCDIE(0));
break;
- case DFSDM_EVENT_CKA:
for_each_set_bit(i, &ulmask, priv->dfsdm.max_channels) {
DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CHCFGR1(i),
DFSDM_CHCFGR1_CKABEN_MASK,
DFSDM_CHCFGR1_CKABEN(0));
}
priv->ckab_filter_mask &= ~BIT(fl_id);
if (!priv->ckab_filter_mask)
DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR2(0),
DFSDM_CR2_CKABIE_MASK,
DFSDM_CR2_CKABIE(0));
break;
- default:
DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR2(fl_id), event, 0);
- }
- priv->filters[fl_id].event_mask &= ~event;
- spin_unlock_irqrestore(&priv->lock, flags);
- return 0;
+} +EXPORT_SYMBOL_GPL(dfsdm_unregister_fl_event);
+/*
- Channels
- */
+static void stm32_dfsdm_init_channel(struct dfsdm_priv *priv,
struct stm32_dfsdm_channel *ch)
+{
Comments in here for what the various bits are doing would be great. The naming makes it just about possible to work out, but not nice to read!
- DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CHCFGR1(ch->id),
DFSDM_CHCFGR1_DATMPX_MASK,
DFSDM_CHCFGR1_DATMPX(ch->type.source));
- if (ch->type.source == DFSDM_CHANNEL_EXTERNAL_INPUTS) {
DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CHCFGR1(ch->id),
DFSDM_CHCFGR1_SITP_MASK,
DFSDM_CHCFGR1_SITP(ch->serial_if.type));
DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CHCFGR1(ch->id),
DFSDM_CHCFGR1_SPICKSEL_MASK,
DFSDM_CHCFGR1_SPICKSEL(ch->serial_if.spi_clk));
- }
- DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CHCFGR1(ch->id),
DFSDM_CHCFGR1_DATPACK_MASK,
DFSDM_CHCFGR1_DATPACK(ch->type.DataPacking));
- DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CHCFGR1(ch->id),
DFSDM_CHCFGR1_CHINSEL_MASK,
DFSDM_CHCFGR1_CHINSEL(ch->serial_if.pins));
+}
+/**
- stm32_dfsdm_start_channel - Configure and activate DFSDM channel.
- @dfsdm: Handle used to retrieve dfsdm context.
- @ch: Filter id.
- @cfg: Filter configuration.
- */
+int stm32_dfsdm_start_channel(struct stm32_dfsdm *dfsdm, unsigned int ch_id,
struct stm32_dfsdm_ch_cfg *cfg)
+{
- struct dfsdm_priv *priv = container_of(dfsdm, struct dfsdm_priv,
dfsdm);
- struct regmap *reg = priv->regmap;
- int ret;
- dev_dbg(&priv->pdev->dev, "%s: for channel %d\n", __func__, ch_id);
- ret = stm32_dfsdm_start_dfsdm(priv);
- if (ret < 0)
return ret;
- DFSDM_UPDATE_BITS(reg, DFSDM_CHCFGR2(ch_id), DFSDM_CHCFGR2_DTRBS_MASK,
DFSDM_CHCFGR2_DTRBS(cfg->right_bit_shift));
- DFSDM_UPDATE_BITS(reg, DFSDM_CHCFGR2(ch_id), DFSDM_CHCFGR2_OFFSET_MASK,
DFSDM_CHCFGR2_OFFSET(cfg->offset));
- DFSDM_UPDATE_BITS(reg, DFSDM_CHCFGR1(ch_id), DFSDM_CHCFGR1_CHEN_MASK,
DFSDM_CHCFGR1_CHEN(1));
- /* Clear absence detection IRQ */
- DFSDM_UPDATE_BITS(priv->regmap, DFSDM_ICR(0),
DFSDM_ICR_CLRCKABF_CH_MASK(ch_id),
DFSDM_ICR_CLRCKABF_CH(1, ch_id));
- return 0;
+} +EXPORT_SYMBOL_GPL(dfsdm_start_channel);
+/**
- stm32_dfsdm_stop_channel - Deactivate channel.
- @dfsdm: Handle used to retrieve dfsdm context.
- @ch_id: DFSDM channel identifier.
- */
+void stm32_dfsdm_stop_channel(struct stm32_dfsdm *dfsdm, unsigned int ch_id) +{
- struct dfsdm_priv *priv = container_of(dfsdm, struct dfsdm_priv, dfsdm);
- dev_dbg(&priv->pdev->dev, "%s:for channel %d\n", __func__, ch_id);
- DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CHCFGR1(ch_id),
DFSDM_CHCFGR1_CHEN_MASK,
DFSDM_CHCFGR1_CHEN(0));
- DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CHCFGR1(ch_id),
DFSDM_CHCFGR1_CKABEN_MASK, DFSDM_CHCFGR1_CKABEN(0));
- DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CHCFGR1(ch_id),
DFSDM_CHCFGR1_SCDEN_MASK, DFSDM_CHCFGR1_SCDEN(0));
- stm32_dfsdm_stop_dfsdm(priv);
+} +EXPORT_SYMBOL_GPL(dfsdm_stop_channel);
+/**
- stm32_dfsdm_get_channel - Get channel instance.
- @dfsdm: handle used to retrieve dfsdm context.
- @ch: DFSDM channel hardware parameters.
- Reserve DFSDM channel resource.
- */
+int stm32_dfsdm_get_channel(struct stm32_dfsdm *dfsdm,
struct stm32_dfsdm_channel *ch)
+{
- struct dfsdm_priv *priv = container_of(dfsdm, struct dfsdm_priv, dfsdm);
- unsigned int id = ch->id;
- dev_dbg(&priv->pdev->dev, "%s:get channel %d\n", __func__, id);
- if (id >= priv->dfsdm.max_channels) {
dev_err(&priv->pdev->dev, "channel (%d) is not valid\n", id);
return -EINVAL;
- }
- if ((ch->type.source != DFSDM_CHANNEL_EXTERNAL_INPUTS) &
(ch->serial_if.spi_clk != DFSDM_CHANNEL_SPI_CLOCK_EXTERNAL) &
(!priv->clkout_freq_req)) {
dev_err(&priv->pdev->dev, "clkout not present\n");
return -EINVAL;
- }
- spin_lock(&priv->lock);
- if (!(BIT(id) & priv->free_channel_mask)) {
spin_unlock(&priv->lock);
dev_err(&priv->pdev->dev, "channel (%d) already in use\n", id);
return -EBUSY;
- }
- priv->free_channel_mask &= ~BIT(id);
- priv->channels[id] = *ch;
- spin_unlock(&priv->lock);
- dev_dbg(&priv->pdev->dev, "%s: new mask %#x\n", __func__,
priv->free_channel_mask);
- /**
* Check clock constrainst between clkout and either
* dfsdm/audio clock:
* - In SPI mode (clkout is used): Fclk >= 4 * Fclkout
* (e.g. CKOUTDIV >= 3)
* - In mancherster mode: Fclk >= 6 * Fclkout
*/
- switch (ch->serial_if.type) {
- case DFSDM_CHANNEL_SPI_RISING:
- case DFSDM_CHANNEL_SPI_FALLING:
if (priv->clkout_div && priv->clkout_div < 3)
dev_warn(&priv->pdev->dev,
"Clock div should be higher than 3\n");
Really only warnings? If requirements then error out. If not, then a description of what the side effects of these would be would be great here and perhaps even in the warning message.
break;
- case DFSDM_CHANNEL_MANCHESTER_RISING:
- case DFSDM_CHANNEL_MANCHESTER_FALLING:
if (priv->clkout_div && priv->clkout_div < 5)
dev_warn(&priv->pdev->dev,
"Clock div should be higher than 5\n");
break;
- }
- stm32_dfsdm_init_channel(priv, ch);
- return 0;
+} +EXPORT_SYMBOL_GPL(dfsdm_get_channel);
+/**
- stm32_dfsdm_release_channel - Release channel instance.
- @dfsdm: Handle used to retrieve dfsdm context.
- @ch_id: DFSDM channel identifier.
- Free the DFSDM channel resource.
- */
+void stm32_dfsdm_release_channel(struct stm32_dfsdm *dfsdm, unsigned int ch_id) +{
- struct dfsdm_priv *priv = container_of(dfsdm, struct dfsdm_priv, dfsdm);
- spin_lock(&priv->lock);
- priv->free_channel_mask |= BIT(ch_id);
- spin_unlock(&priv->lock);
+} +EXPORT_SYMBOL_GPL(dfsdm_release_channel);
+/**
- stm32_dfsdm_get_clk_out_rate - get clkout frequency.
- @dfsdm: handle used to retrieve dfsdm context.
- @rate: clock out rate in Hz.
- Provide output frequency used for external ADC.
- return EINVAL if clockout is not used else return 0.
- */
+int stm32_dfsdm_get_clk_out_rate(struct stm32_dfsdm *dfsdm, unsigned long *rate) +{
- struct dfsdm_priv *priv = container_of(dfsdm, struct dfsdm_priv, dfsdm);
- unsigned long int clk_rate;
- if (!priv->clkout_div)
return -EINVAL;
- clk_rate = clk_get_rate(priv->aclk ? priv->aclk : priv->clk);
- *rate = clk_rate / (priv->clkout_div + 1);
- dev_dbg(&priv->pdev->dev, "%s: clkout: %ld (Hz)\n", __func__, *rate);
- return 0;
+} +EXPORT_SYMBOL_GPL(dfsdm_get_clk_out_rate);
+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;
- int ret, val;
- 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->phys_base = res->start;
- priv->base = devm_ioremap_resource(&pdev->dev, res);
- if (IS_ERR(priv->base))
return PTR_ERR(priv->base);
- ret = of_property_read_u32(node, "st,clkout-freq", &val);
- if (!ret) {
if (!val) {
dev_err(&priv->pdev->dev,
"st,clkout-freq cannot be 0\n");
return -EINVAL;
}
priv->clkout_freq_req = val;
- } else if (ret != -EINVAL) {
dev_err(&priv->pdev->dev, "Failed to get st,clkout-freq\n");
return ret;
- }
- /* Source clock */
- priv->clk = devm_clk_get(&pdev->dev, "dfsdm_clk");
- 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_clk");
- if (IS_ERR(priv->aclk))
priv->aclk = NULL;
- return 0;
+};
+static const struct of_device_id stm32_dfsdm_of_match[] = {
- {
.compatible = "st,stm32h7-dfsdm",
.data = &stm32h7_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_dev_data *dev_data;
- enum dfsdm_clkout_src clk_src;
- 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_dev_data *)of_id->data;
- ret = stm32_dfsdm_parse_of(pdev, priv);
- if (ret < 0)
return ret;
- priv->regmap = devm_regmap_init_mmio(&pdev->dev, priv->base,
dev_data->regmap_cfg);
- if (IS_ERR(priv->regmap)) {
ret = PTR_ERR(priv->regmap);
dev_err(&pdev->dev, "%s: Failed to allocate regmap: %d\n",
__func__, ret);
return ret;
- }
- priv->dfsdm = dev_data->dfsdm;
- priv->filters = devm_kcalloc(&pdev->dev, dev_data->dfsdm.max_filters,
sizeof(*priv->filters), GFP_KERNEL);
- if (IS_ERR(priv->filters)) {
ret = PTR_ERR(priv->filters);
goto probe_err;
- }
- for (i = 0; i < dev_data->dfsdm.max_filters; i++) {
struct filter_params *params = &priv->filters[i];
params->id = i;
params->irq = platform_get_irq(pdev, i);
if (params->irq < 0) {
dev_err(&pdev->dev, "Failed to get IRQ resource\n");
ret = params->irq;
goto probe_err;
}
ret = devm_request_irq(&pdev->dev, params->irq, stm32_dfsdm_irq,
0, dev_name(&pdev->dev), params);
if (ret) {
dev_err(&pdev->dev, "Failed to register interrupt\n");
goto probe_err;
}
params->priv = priv;
- }
- priv->channels = devm_kcalloc(&pdev->dev, priv->dfsdm.max_channels,
sizeof(*priv->channels), GFP_KERNEL);
- if (IS_ERR(priv->channels)) {
ret = PTR_ERR(priv->channels);
goto probe_err;
- }
- priv->free_filter_mask = BIT(priv->dfsdm.max_filters) - 1;
- priv->free_channel_mask = BIT(priv->dfsdm.max_channels) - 1;
- platform_set_drvdata(pdev, &priv->dfsdm);
- spin_lock_init(&priv->lock);
- priv->clkout_div = stm32_dfsdm_get_clkout_divider(priv,
clk_get_rate(priv->clk));
- ret = of_platform_populate(pnode, NULL, NULL, &pdev->dev);
- if (ret < 0)
goto probe_err;
- clk_src = priv->aclk ? AUDIO_CLK : DFSDM_CLK;
I'd like to see a comment here saying what this is doing.
- DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CHCFGR1(0),
DFSDM_CHCFGR1_CKOUTSRC_MASK,
DFSDM_CHCFGR1_CKOUTSRC(clk_src));
- return 0;
+probe_err:
- return ret;
+}
+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/include/linux/mfd/stm32-dfsdm.h b/include/linux/mfd/stm32-dfsdm.h new file mode 100644 index 0000000..f6eb788 --- /dev/null +++ b/include/linux/mfd/stm32-dfsdm.h @@ -0,0 +1,324 @@ +/*
- This file is part of STM32 DFSDM mfd driver API
- Copyright (C) 2017, STMicroelectronics - All Rights Reserved
- Author(s): Arnaud Pouliquen arnaud.pouliquen@st.com.
- License terms: GPL V2.0.
- This program is free software; you can redistribute it and/or modify it
- under the terms of the GNU General Public License version 2 as published by
- the Free Software Foundation.
- This program is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
- details.
- */
+#ifndef MDF_STM32_DFSDM_H +#define MDF_STM32_DFSDM_H
+/*
- Channel definitions
- */
+#define DFSDM_CHANNEL_0 BIT(0) +#define DFSDM_CHANNEL_1 BIT(1) +#define DFSDM_CHANNEL_2 BIT(2) +#define DFSDM_CHANNEL_3 BIT(3) +#define DFSDM_CHANNEL_4 BIT(4) +#define DFSDM_CHANNEL_5 BIT(5) +#define DFSDM_CHANNEL_6 BIT(6) +#define DFSDM_CHANNEL_7 BIT(7)
+/* DFSDM channel input data packing */ +enum stm32_dfsdm_data_packing {
- DFSDM_CHANNEL_STANDARD_MODE, /* Standard data packing mode */
- DFSDM_CHANNEL_INTERLEAVED_MODE, /* Interleaved data packing mode */
- DFSDM_CHANNEL_DUAL_MODE /* Dual data packing mode */
+};
+/* DFSDM channel input multiplexer */ +enum stm32_dfsdm_input_multiplexer {
- DFSDM_CHANNEL_EXTERNAL_INPUTS, /* Data taken from external inputs */
- DFSDM_CHANNEL_INTERNAL_ADC, /* Data taken from internal ADC */
- DFSDM_CHANNEL_INTERNAL_REGISTER, /* Data taken from register */
+};
+/* DFSDM channel serial interface type */ +enum stm32_dfsdm_serial_in_type {
- DFSDM_CHANNEL_SPI_RISING, /* SPI with rising edge */
- DFSDM_CHANNEL_SPI_FALLING, /* SPI with falling edge */
We could use standard SPI naming for these first two. That would I think describe these as clock phases. However, perhaps alongside the machester coding it doesn't make sense to do so.
- DFSDM_CHANNEL_MANCHESTER_RISING, /* Manchester with rising edge */
- DFSDM_CHANNEL_MANCHESTER_FALLING, /* Manchester with falling edge */
+};
+/* DFSDM channel serial spi clock source */ +enum stm32_dfsdm_spi_clk_src {
- /* External SPI clock */
- DFSDM_CHANNEL_SPI_CLOCK_EXTERNAL,
- /* Internal SPI clock */
- DFSDM_CHANNEL_SPI_CLOCK_INTERNAL,
- /* Internal SPI clock divided by 2, falling edge */
- DFSDM_CHANNEL_SPI_CLOCK_INTERNAL_DIV2_FALLING,
- /* Internal SPI clock divided by 2, rising edge */
- DFSDM_CHANNEL_SPI_CLOCK_INTERNAL_DIV2_RISING
+};
+/* DFSDM channel input pins */ +enum stm32_dfsdm_serial_in_select {
- /* Serial input taken from pins of the same channel (y) */
- DFSDM_CHANNEL_SAME_CHANNEL_PINS,
- /* Serial input taken from pins of the following channel (y + 1)*/
- DFSDM_CHANNEL_NEXT_CHANNEL_PINS,
+};
+/**
- struct stm32_dfsdm_input_type - DFSDM channel init structure definition.
- @DataPacking: Standard, interleaved or dual mode for internal register.
- @source: channel source: internal DAC, serial input or memory.
- */
+struct stm32_dfsdm_input_type {
- enum stm32_dfsdm_data_packing DataPacking;
- enum stm32_dfsdm_input_multiplexer source;
+};
+/**
- struct stm32_dfsdm_serial_if - DFSDM serial interface parameters.
- @type: Serial interface type.
- @spi_clk: SPI clock source.
- @pins: select serial interface associated to the channel
- */
+struct stm32_dfsdm_serial_if {
- enum stm32_dfsdm_serial_in_type type;
- enum stm32_dfsdm_spi_clk_src spi_clk;
- enum stm32_dfsdm_serial_in_select pins;
+};
+/**
- struct stm32_dfsdm_channel - DFSDM channel hardware parameters.
- @id: DFSDM channel identifier.
- @type: DFSDM channel input parameters.
- @serial_if: DFSDM channel serial interface parameters.
Mandatory for DFSDM_CHANNEL_EXTERNAL_INPUTS.
- */
+struct stm32_dfsdm_channel {
- unsigned int id;
- struct stm32_dfsdm_input_type type;
- struct stm32_dfsdm_serial_if serial_if;
+};
+/**
- struct stm32_dfsdm_ch_cfg - DFSDM channel config.
- @offset: DFSDM channel 24 bit calibration offset.
- @right_bit_shift: DFSDM channel right bit shift of the data result.
- */
+struct stm32_dfsdm_ch_cfg {
- unsigned int offset;
- unsigned int right_bit_shift;
+};
+/*
- Filter definitions
Single line comment syntax. Make sure this is correct throughout as otherwise we'll only get 'fix' patches turning up after this hits linux-next.
- */
+#define DFSDM_MIN_INT_OVERSAMPLING 1 +#define DFSDM_MAX_INT_OVERSAMPLING 256 +#define DFSDM_MIN_FL_OVERSAMPLING 1
Oversampling of 1 is effectively not oversampling so do we need this?
+#define DFSDM_MAX_FL_OVERSAMPLING 1024
+enum stm32_dfsdm_events {
Slightly clunky enum usage. I'd just use the value and put the BIT around whereever it is used.
- DFSDM_EVENT_INJ_EOC = BIT(0), /* Injected end of conversion event */
- DFSDM_EVENT_REG_EOC = BIT(1), /* Regular end of conversion event */
- DFSDM_EVENT_INJ_XRUN = BIT(2), /* Injected conversion overrun event */
- DFSDM_EVENT_REG_XRUN = BIT(3), /* Regular conversion overrun event */
- DFSDM_EVENT_AWD = BIT(4), /* Analog watchdog event */
- DFSDM_EVENT_SCD = BIT(5), /* Short circuit detector event */
- DFSDM_EVENT_CKA = BIT(6), /* Clock abscence detection event */
+};
+#define STM32_DFSDM_EVENT_MASK 0x3F
+/* 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,
+};
+/* DFSDM filter order */
Please check your comments - this one is clearly wrong.
+enum stm32_dfsdm_state {
- DFSDM_DISABLE,
- DFSDM_ENABLE,
+};
+/**
- struct stm32_dfsdm_sinc_filter - DFSDM Sinc filter structure definition
- @order: DFSM filter order.
- @oversampling: DFSDM filter oversampling:
post processing filter: min = 1, max = 1024.
- */
+struct stm32_dfsdm_sinc_filter {
- enum stm32_dfsdm_sinc_order order;
- unsigned int oversampling;
+};
+/* DFSDM filter conversion trigger */ +enum stm32_dfsdm_trigger {
- DFSDM_FILTER_SW_TRIGGER, /* Software trigger */
- DFSDM_FILTER_SYNC_TRIGGER, /* Synchronous with DFSDM0 */
- DFSDM_FILTER_EXT_TRIGGER, /* External trigger (only for injected) */
+};
+/* DFSDM filter external trigger polarity */ +enum stm32_dfsdm_filter_ext_trigger_pol {
- DFSDM_FILTER_EXT_TRIG_NO_TRIG, /* Trigger disable */
- DFSDM_FILTER_EXT_TRIG_RISING_EDGE, /* Rising edge */
- DFSDM_FILTER_EXT_TRIG_FALLING_EDGE, /* Falling edge */
- DFSDM_FILTER_EXT_TRIG_BOTH_EDGES, /* Rising and falling edges */
+};
+/* DFSDM filter conversion type */ +enum stm32_dfsdm_conv_type {
- DFSDM_FILTER_REG_CONV, /* Regular conversion */
- DFSDM_FILTER_SW_INJ_CONV, /* Injected conversion */
- DFSDM_FILTER_TRIG_INJ_CONV, /* Injected conversion */
+};
+/* DFSDM filter regular synchronous mode */ +enum stm32_dfsdm_conv_rsync {
- DFSDM_FILTER_RSYNC_OFF, /* regular conversion asynchronous */
- DFSDM_FILTER_RSYNC_ON, /* regular conversion synchronous with filter0*/
stray 0?
+};
+/**
- struct stm32_dfsdm_regular - DFSDM filter conversion parameters structure
- @ch_src: Channel source from 0 to 7.
- @fast_mode: Enable/disable fast mode for regular conversion.
- @dma_mode: Enable/disable dma mode.
- @cont_mode Enable/disable continuous conversion.
- @sync_mode Enable/disable synchro mode.
- */
+struct stm32_dfsdm_regular {
- unsigned int ch_src;
- bool fast_mode;
- bool dma_mode;
- bool cont_mode;
- bool sync_mode;
+};
+/**
- struct stm32_dfsdm_injected - DFSDM filter conversion parameters structure
- @trigger: Trigger used to start injected conversion.
- @trig_src: External trigger, 0 to 30 (refer to datasheet for details).
- @trig_pol: External trigger edge: software, rising, falling or both.
- @scan_mode: Enable/disable scan mode for injected conversion.
- @ch_group: mask containing channels to scan ( set bit y to scan
channel y).
- @dma_mode: DFSDM channel input parameters.
- */
+struct stm32_dfsdm_injected {
- enum stm32_dfsdm_trigger trigger;
- unsigned int trig_src;
- enum stm32_dfsdm_filter_ext_trigger_pol trig_pol;
- bool scan_mode;
- unsigned int ch_group;
- bool dma_mode;
+};
+struct stm32_dfsdm;
+/**
- struct stm32_dfsdm_fl_event - DFSDM filters event
- @cb: User event callback with parameters. be carful this function
is called under threaded IRQ context:
struct stm32_dfsdm *dfsdm: dfsdm handle,
unsigned int fl_id: filter id,
num stm32_dfsdm_events flag: event,
param: parameter associated to the event,
void *context: user context provided on registration.
- @context: User param to retrieve context.
- */
+struct stm32_dfsdm_fl_event {
- void (*cb)(struct stm32_dfsdm *, int, enum stm32_dfsdm_events,
unsigned int, void *);
- void *context;
+};
+/**
- struct stm32_dfsdm_filter - DFSDM filter conversion parameters structure
- @reg_params: DFSDM regular conversion parameters.
this param is optional and not taken into account if
@inj_params is defined.
- @inj_params: DFSDM injected conversion parameters (optional).
- @filter_params: DFSDM filter parameters.
- @event: Events callback.
- @int_oversampling: Integrator oversampling ratio for average purpose
(range from 1 to 256).
- @ext_det_ch_mask: Extreme detector mask for channel selection
mask generated using DFSDM_CHANNEL_0 to
DFSDM_CHANNEL_7. If 0 feature is disable.
- */
+struct stm32_dfsdm_filter {
- struct stm32_dfsdm_regular *reg_params;
- struct stm32_dfsdm_injected *inj_params;
- struct stm32_dfsdm_sinc_filter sinc_params;
- struct stm32_dfsdm_fl_event event;
- unsigned int int_oversampling;
+};
+/**
- struct stm32_dfsdm - DFSDM context structure.
- @trig_info: Trigger name and id available last member name is null.
- @max_channels: max number of channels available.
- @max_filters: max number of filters available.
- Notice That structure is filled by mdf driver and must not be updated by
mfd
- user.
- */
+struct stm32_dfsdm {
- unsigned int max_channels;
- unsigned int max_filters;
+};
+int stm32_dfsdm_get_filter(struct stm32_dfsdm *dfsdm, unsigned int fl_id); +void stm32_dfsdm_release_filter(struct stm32_dfsdm *dfsdm, unsigned int fl_id);
+dma_addr_t stm32_dfsdm_get_filter_dma_phy_addr(struct stm32_dfsdm *dfsdm,
unsigned int fl_id,
enum stm32_dfsdm_conv_type conv);
+int stm32_dfsdm_configure_filter(struct stm32_dfsdm *dfsdm, unsigned int fl_id,
struct stm32_dfsdm_filter *filter);
+void stm32_dfsdm_start_filter(struct stm32_dfsdm *dfsdm, unsigned int fl_id,
enum stm32_dfsdm_conv_type conv);
+void stm32_dfsdm_stop_filter(struct stm32_dfsdm *dfsdm, unsigned int fl_id);
+void stm32_dfsdm_read_fl_conv(struct stm32_dfsdm *dfsdm, unsigned int fl_id,
u32 *val, int *ch_id,
enum stm32_dfsdm_conv_type type);
+int stm32_dfsdm_unregister_fl_event(struct stm32_dfsdm *dfsdm,
unsigned int fl_id,
enum stm32_dfsdm_events event,
unsigned int ch_mask);
+int stm32_dfsdm_register_fl_event(struct stm32_dfsdm *dfsdm, unsigned int fl_id,
enum stm32_dfsdm_events event,
unsigned int ch_mask);
+int stm32_dfsdm_get_channel(struct stm32_dfsdm *dfsdm,
struct stm32_dfsdm_channel *ch);
+void stm32_dfsdm_release_channel(struct stm32_dfsdm *dfsdm, unsigned int ch_id);
+int stm32_dfsdm_start_channel(struct stm32_dfsdm *dfsdm, unsigned int ch_id,
struct stm32_dfsdm_ch_cfg *cfg);
+void stm32_dfsdm_stop_channel(struct stm32_dfsdm *dfsdm, unsigned int ch_id);
+int stm32_dfsdm_get_clk_out_rate(struct stm32_dfsdm *dfsdm,
unsigned long *rate);
+#endif
Hello Jonathan,
Thanks for the review. This drivers should disappear, but i will integrate you comment/remark in my redesign.
Please find some comments below, on specific points
Regards Arnaud
On 01/29/2017 12:53 PM, Jonathan Cameron wrote:
On 23/01/17 16:32, Arnaud Pouliquen wrote:
DFSDM hardware IP can be used at the same time for ADC sigma delta conversion and audio PDM microphone. MFD driver is in charge of configuring IP registers and managing IP clocks. For this it exports an API to handles filters and channels resources.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com
This is somewhat of a beast. I would be tempted to build it up in more bite sized chunks.
Obvious things to drop from a first version (basically to make it easier to review) would be injected supported. There may well be others but you'll have a better feel for that than me.
i will pay attention for next version.
I really don't like the wrappers round the regmap functions though. They mean you are papering over errors if they occur and make the code slightly harder to read by implying that something else is going on.
One aim of the wrapper was to simplify code review, seems that just the opposite... As i have around 50 regmap accesses, adding a return/goto for each error and at least an associated error messages for each function, should add 100 to 150 extra lines...
Yes the code will be longer without them, but you will also be forced to think properly about error paths.
I have a question around this. What should be the action if a register access return an error? Possible root causes: - bad address of the IP (DT) - IP not clocked or in reset (driver BUG). - IP is out of order (hardware issue) - bug in driver that access to an invalid address.
So except for the last root cause,we can suppose that every register accesses should always be OK or KO... This is also a reason of the wrapper. Detect driver bug, without adding a test on each register access return.
Perhaps a compromise could be that test is done only during some specific phase (probe, after reset deassertion, clock enabling...) and then trust access without test? Or simply add error message in regmap helper routines...
Please just tell/confirm me your preference.
Anyhow, was mostly reading this to get a feel for what was going on in the whole series so not really a terribly thorough review I'm afraid. Sorry about that!
More than enough for this first version :)
Jonathan
drivers/mfd/Kconfig | 11 + drivers/mfd/Makefile | 2 + drivers/mfd/stm32-dfsdm-reg.h | 220 +++++++++ drivers/mfd/stm32-dfsdm.c | 1044 +++++++++++++++++++++++++++++++++++++++ include/linux/mfd/stm32-dfsdm.h | 324 ++++++++++++ 5 files changed, 1601 insertions(+) create mode 100644 drivers/mfd/stm32-dfsdm-reg.h create mode 100644 drivers/mfd/stm32-dfsdm.c create mode 100644 include/linux/mfd/stm32-dfsdm.h
[...]
diff --git a/drivers/mfd/stm32-dfsdm.c b/drivers/mfd/stm32-dfsdm.c new file mode 100644 index 0000000..81ca29c --- /dev/null +++ b/drivers/mfd/stm32-dfsdm.c @@ -0,0 +1,1044 @@ +/*
- This file is part of STM32 DFSDM mfd 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/mfd/core.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/regmap.h> +#include <linux/slab.h>
+#include <linux/mfd/stm32-dfsdm.h>
+#include "stm32-dfsdm-reg.h"
+#define DFSDM_UPDATE_BITS(regm, reg, mask, val) \
WARN_ON(regmap_update_bits(regm, reg, mask, val))
Don't do these wrappers please. Handle error correctly in all cases. Reviewing as I tend to do backwards through the driver, I thought these were doing something interesting.
Effectively you have no error handling as a result of these which needs fixing.
+#define DFSDM_REG_READ(regm, reg, val) \
WARN_ON(regmap_read(regm, reg, val))
+#define DFSDM_REG_WRITE(regm, reg, val) \
WARN_ON(regmap_write(regm, reg, val))
+#define STM32H7_DFSDM_NUM_FILTERS 4 +#define STM32H7_DFSDM_NUM_INPUTS 8
+enum dfsdm_clkout_src {
- DFSDM_CLK,
- AUDIO_CLK
+};
+struct stm32_dev_data {
- const struct stm32_dfsdm dfsdm;
- const struct regmap_config *regmap_cfg;
+};
+struct dfsdm_priv;
+struct filter_params {
- unsigned int id;
- int irq;
- struct stm32_dfsdm_fl_event event;
- u32 event_mask;
- struct dfsdm_priv *priv; /* Cross ref for context */
- unsigned int ext_ch_mask;
- unsigned int scan_ch;
+};
+struct ch_params {
- struct stm32_dfsdm_channel ch;
+};
I'd like to see a lot more comments in here. Perhaps full kernel-doc as some elements are not that obvious at least to a fairly casual read.
Description in device-tree tree bindings and cover-letter is not sufficient? you would a doc in Document/arm/stm32?
+struct dfsdm_priv {
- struct platform_device *pdev;
- struct stm32_dfsdm dfsdm;
- spinlock_t lock; /* Used for resource sharing & interrupt lock */
- /* Filters */
- struct filter_params *filters;
- unsigned int free_filter_mask;
- unsigned int scd_filter_mask;
- unsigned int ckab_filter_mask;
- /* Channels */
- struct stm32_dfsdm_channel *channels;
- unsigned int free_channel_mask;
- atomic_t n_active_ch;
- /* Clock */
- struct clk *clk;
- struct clk *aclk;
- unsigned int clkout_div;
- unsigned int clkout_freq_req;
- /* Registers*/
- void __iomem *base;
- struct regmap *regmap;
- phys_addr_t phys_base;
+};
+/*
- Common
- */
+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 = DFSDM_CNVTIMR(STM32H7_DFSDM_NUM_FILTERS - 1),
- .volatile_reg = stm32_dfsdm_volatile_reg,
- .fast_io = true,
+};
+static const struct stm32_dev_data stm32h7_data = {
- .dfsdm.max_channels = STM32H7_DFSDM_NUM_INPUTS,
- .dfsdm.max_filters = STM32H7_DFSDM_NUM_FILTERS,
- .regmap_cfg = &stm32h7_dfsdm_regmap_cfg,
+};
[...]
+/**
- stm32_dfsdm_get_filter_dma_addr - Get register address for dma transfer.
- @dfsdm: Handle used to retrieve dfsdm context.
- @fl_id: Filter id.
- @conv: Conversion type.
- */
+dma_addr_t stm32_dfsdm_get_filter_dma_phy_addr(struct stm32_dfsdm *dfsdm,
unsigned int fl_id,
enum stm32_dfsdm_conv_type conv)
+{
- struct dfsdm_priv *priv = container_of(dfsdm, struct dfsdm_priv, dfsdm);
- if (conv == DFSDM_FILTER_REG_CONV)
return (dma_addr_t)(priv->phys_base + DFSDM_RDATAR(fl_id));
- else
return (dma_addr_t)(priv->phys_base + DFSDM_JDATAR(fl_id));
+}
+/**
- stm32_dfsdm_register_fl_event - Register filter event.
What is a filter event? More details good on things that are very device specific like this.
Filter events correspond to filter IRQ status, will be handled in a different way in IIO.
- @dfsdm: Handle used to retrieve dfsdm context.
- @fl_id: Filter id.
- @event: Event to unregister.
- @chan_mask: Mask of channels associated to filter.
- The function enables associated IRQ.
- */
+int stm32_dfsdm_register_fl_event(struct stm32_dfsdm *dfsdm, unsigned int fl_id,
enum stm32_dfsdm_events event,
unsigned int chan_mask)
+{
- struct dfsdm_priv *priv = container_of(dfsdm, struct dfsdm_priv, dfsdm);
- unsigned long flags, ulmask = chan_mask;
- int ret, i;
- dev_dbg(&priv->pdev->dev, "%s:for filter %d: event %#x ch_mask %#x\n",
__func__, fl_id, event, chan_mask);
- if (event > DFSDM_EVENT_CKA)
return -EINVAL;
- /* Clear interrupt before enable them */
- ret = stm32_dfsdm_clear_event(priv, fl_id, event, chan_mask);
- if (ret < 0)
return ret;
- spin_lock_irqsave(&priv->lock, flags);
- /* Enable interrupts */
- switch (event) {
- case DFSDM_EVENT_SCD:
for_each_set_bit(i, &ulmask, priv->dfsdm.max_channels) {
DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CHCFGR1(i),
DFSDM_CHCFGR1_SCDEN_MASK,
DFSDM_CHCFGR1_SCDEN(1));
}
if (!priv->scd_filter_mask)
DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR2(0),
DFSDM_CR2_SCDIE_MASK,
DFSDM_CR2_SCDIE(1));
priv->scd_filter_mask |= BIT(fl_id);
break;
- case DFSDM_EVENT_CKA:
for_each_set_bit(i, &ulmask, priv->dfsdm.max_channels) {
DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CHCFGR1(i),
DFSDM_CHCFGR1_CKABEN_MASK,
DFSDM_CHCFGR1_CKABEN(1));
}
if (!priv->ckab_filter_mask)
DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR2(0),
DFSDM_CR2_CKABIE_MASK,
DFSDM_CR2_CKABIE(1));
priv->ckab_filter_mask |= BIT(fl_id);
break;
- default:
DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR2(fl_id), event, event);
- }
- priv->filters[fl_id].event_mask |= event;
- spin_unlock_irqrestore(&priv->lock, flags);
- return 0;
+} +EXPORT_SYMBOL_GPL(dfsdm_register_fl_event);
+/**
- stm32_dfsdm_unregister_fl_event - Unregister filter event.
- @dfsdm: Handle used to retrieve dfsdm context.
- @fl_id: Filter id.
- @event: Event to unregister.
- @chan_mask: Mask of channels associated to filter.
- The function disables associated IRQ.
- */
+int stm32_dfsdm_unregister_fl_event(struct stm32_dfsdm *dfsdm,
unsigned int fl_id,
enum stm32_dfsdm_events event,
unsigned int chan_mask)
+{
- struct dfsdm_priv *priv = container_of(dfsdm, struct dfsdm_priv, dfsdm);
- unsigned long flags, ulmask = chan_mask;
- int i;
- dev_dbg(&priv->pdev->dev, "%s:for filter %d: event %#x ch_mask %#x\n",
__func__, fl_id, event, chan_mask);
- if (event > DFSDM_EVENT_CKA)
return -EINVAL;
- spin_lock_irqsave(&priv->lock, flags);
- /* Disable interrupts */
- switch (event) {
- case DFSDM_EVENT_SCD:
for_each_set_bit(i, &ulmask, priv->dfsdm.max_channels) {
DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CHCFGR1(i),
DFSDM_CHCFGR1_SCDEN_MASK,
DFSDM_CHCFGR1_SCDEN(0));
}
priv->scd_filter_mask &= ~BIT(fl_id);
if (!priv->scd_filter_mask)
DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR2(0),
DFSDM_CR2_SCDIE_MASK,
DFSDM_CR2_SCDIE(0));
break;
- case DFSDM_EVENT_CKA:
for_each_set_bit(i, &ulmask, priv->dfsdm.max_channels) {
DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CHCFGR1(i),
DFSDM_CHCFGR1_CKABEN_MASK,
DFSDM_CHCFGR1_CKABEN(0));
}
priv->ckab_filter_mask &= ~BIT(fl_id);
if (!priv->ckab_filter_mask)
DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR2(0),
DFSDM_CR2_CKABIE_MASK,
DFSDM_CR2_CKABIE(0));
break;
- default:
DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR2(fl_id), event, 0);
- }
- priv->filters[fl_id].event_mask &= ~event;
- spin_unlock_irqrestore(&priv->lock, flags);
- return 0;
+} +EXPORT_SYMBOL_GPL(dfsdm_unregister_fl_event);
[...]
+/* DFSDM filter conversion type */ +enum stm32_dfsdm_conv_type {
- DFSDM_FILTER_REG_CONV, /* Regular conversion */
- DFSDM_FILTER_SW_INJ_CONV, /* Injected conversion */
- DFSDM_FILTER_TRIG_INJ_CONV, /* Injected conversion */
+};
+/* DFSDM filter regular synchronous mode */ +enum stm32_dfsdm_conv_rsync {
- DFSDM_FILTER_RSYNC_OFF, /* regular conversion asynchronous */
- DFSDM_FILTER_RSYNC_ON, /* regular conversion synchronous with filter0*/
stray 0?
Should read "filter instance 0"... This corresponds to a specificity of the DFSDM hardware. DFSDM can offer possibility to synchronize each filter output with the filter 0 instance output. As example, this can be used to synchronize several audio microphones. Filter 0 is allocated to main microphones and the other filters for background microphones (notice that we need one filter per 1-bit PDM stream)
[...]
On 30/01/17 15:08, Arnaud Pouliquen wrote:
Hello Jonathan,
Thanks for the review. This drivers should disappear, but i will integrate you comment/remark in my redesign.
Please find some comments below, on specific points
Regards Arnaud
On 01/29/2017 12:53 PM, Jonathan Cameron wrote:
On 23/01/17 16:32, Arnaud Pouliquen wrote:
DFSDM hardware IP can be used at the same time for ADC sigma delta conversion and audio PDM microphone. MFD driver is in charge of configuring IP registers and managing IP clocks. For this it exports an API to handles filters and channels resources.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com
This is somewhat of a beast. I would be tempted to build it up in more bite sized chunks.
Obvious things to drop from a first version (basically to make it easier to review) would be injected supported. There may well be others but you'll have a better feel for that than me.
i will pay attention for next version.
I really don't like the wrappers round the regmap functions though. They mean you are papering over errors if they occur and make the code slightly harder to read by implying that something else is going on.
One aim of the wrapper was to simplify code review, seems that just the opposite... As i have around 50 regmap accesses, adding a return/goto for each error and at least an associated error messages for each function, should add 100 to 150 extra lines...
Keep error message to a minimum. Likely to never occur as you say!
Yes the code will be longer without them, but you will also be forced to think properly about error paths.
I have a question around this. What should be the action if a register access return an error? Possible root causes:
- bad address of the IP (DT)
- IP not clocked or in reset (driver BUG).
- IP is out of order (hardware issue)
- bug in driver that access to an invalid address.
So except for the last root cause,we can suppose that every register accesses should always be OK or KO... This is also a reason of the wrapper. Detect driver bug, without adding a test on each register access return.
Perhaps a compromise could be that test is done only during some specific phase (probe, after reset deassertion, clock enabling...) and then trust access without test? Or simply add error message in regmap helper routines...
Please just tell/confirm me your preference.
Always assume an error can occur anywhere and handle it as best possible (usually drop straight out of the function with an error).
I agree it doesn't always give that much information, but trying to paper over a problem and continue is usually a worse idea.
Anyhow, was mostly reading this to get a feel for what was going on in the whole series so not really a terribly thorough review I'm afraid. Sorry about that!
More than enough for this first version :)
Jonathan
drivers/mfd/Kconfig | 11 + drivers/mfd/Makefile | 2 + drivers/mfd/stm32-dfsdm-reg.h | 220 +++++++++ drivers/mfd/stm32-dfsdm.c | 1044 +++++++++++++++++++++++++++++++++++++++ include/linux/mfd/stm32-dfsdm.h | 324 ++++++++++++ 5 files changed, 1601 insertions(+) create mode 100644 drivers/mfd/stm32-dfsdm-reg.h create mode 100644 drivers/mfd/stm32-dfsdm.c create mode 100644 include/linux/mfd/stm32-dfsdm.h
[...]
diff --git a/drivers/mfd/stm32-dfsdm.c b/drivers/mfd/stm32-dfsdm.c new file mode 100644 index 0000000..81ca29c --- /dev/null +++ b/drivers/mfd/stm32-dfsdm.c @@ -0,0 +1,1044 @@ +/*
- This file is part of STM32 DFSDM mfd 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/mfd/core.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/regmap.h> +#include <linux/slab.h>
+#include <linux/mfd/stm32-dfsdm.h>
+#include "stm32-dfsdm-reg.h"
+#define DFSDM_UPDATE_BITS(regm, reg, mask, val) \
WARN_ON(regmap_update_bits(regm, reg, mask, val))
Don't do these wrappers please. Handle error correctly in all cases. Reviewing as I tend to do backwards through the driver, I thought these were doing something interesting.
Effectively you have no error handling as a result of these which needs fixing.
+#define DFSDM_REG_READ(regm, reg, val) \
WARN_ON(regmap_read(regm, reg, val))
+#define DFSDM_REG_WRITE(regm, reg, val) \
WARN_ON(regmap_write(regm, reg, val))
+#define STM32H7_DFSDM_NUM_FILTERS 4 +#define STM32H7_DFSDM_NUM_INPUTS 8
+enum dfsdm_clkout_src {
- DFSDM_CLK,
- AUDIO_CLK
+};
+struct stm32_dev_data {
- const struct stm32_dfsdm dfsdm;
- const struct regmap_config *regmap_cfg;
+};
+struct dfsdm_priv;
+struct filter_params {
- unsigned int id;
- int irq;
- struct stm32_dfsdm_fl_event event;
- u32 event_mask;
- struct dfsdm_priv *priv; /* Cross ref for context */
- unsigned int ext_ch_mask;
- unsigned int scan_ch;
+};
+struct ch_params {
- struct stm32_dfsdm_channel ch;
+};
I'd like to see a lot more comments in here. Perhaps full kernel-doc as some elements are not that obvious at least to a fairly casual read.
Description in device-tree tree bindings and cover-letter is not sufficient? you would a doc in Document/arm/stm32?
Sorry, just meant on the following structure. Internal comments would make it easier to follow what the elements of this structure are for.
+struct dfsdm_priv {
- struct platform_device *pdev;
- struct stm32_dfsdm dfsdm;
- spinlock_t lock; /* Used for resource sharing & interrupt lock */
- /* Filters */
- struct filter_params *filters;
- unsigned int free_filter_mask;
- unsigned int scd_filter_mask;
- unsigned int ckab_filter_mask;
- /* Channels */
- struct stm32_dfsdm_channel *channels;
- unsigned int free_channel_mask;
- atomic_t n_active_ch;
- /* Clock */
- struct clk *clk;
- struct clk *aclk;
- unsigned int clkout_div;
- unsigned int clkout_freq_req;
- /* Registers*/
- void __iomem *base;
- struct regmap *regmap;
- phys_addr_t phys_base;
+};
+/*
- Common
- */
+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 = DFSDM_CNVTIMR(STM32H7_DFSDM_NUM_FILTERS - 1),
- .volatile_reg = stm32_dfsdm_volatile_reg,
- .fast_io = true,
+};
+static const struct stm32_dev_data stm32h7_data = {
- .dfsdm.max_channels = STM32H7_DFSDM_NUM_INPUTS,
- .dfsdm.max_filters = STM32H7_DFSDM_NUM_FILTERS,
- .regmap_cfg = &stm32h7_dfsdm_regmap_cfg,
+};
[...]
+/**
- stm32_dfsdm_get_filter_dma_addr - Get register address for dma transfer.
- @dfsdm: Handle used to retrieve dfsdm context.
- @fl_id: Filter id.
- @conv: Conversion type.
- */
+dma_addr_t stm32_dfsdm_get_filter_dma_phy_addr(struct stm32_dfsdm *dfsdm,
unsigned int fl_id,
enum stm32_dfsdm_conv_type conv)
+{
- struct dfsdm_priv *priv = container_of(dfsdm, struct dfsdm_priv, dfsdm);
- if (conv == DFSDM_FILTER_REG_CONV)
return (dma_addr_t)(priv->phys_base + DFSDM_RDATAR(fl_id));
- else
return (dma_addr_t)(priv->phys_base + DFSDM_JDATAR(fl_id));
+}
+/**
- stm32_dfsdm_register_fl_event - Register filter event.
What is a filter event? More details good on things that are very device specific like this.
Filter events correspond to filter IRQ status, will be handled in a different way in IIO.
- @dfsdm: Handle used to retrieve dfsdm context.
- @fl_id: Filter id.
- @event: Event to unregister.
- @chan_mask: Mask of channels associated to filter.
- The function enables associated IRQ.
- */
+int stm32_dfsdm_register_fl_event(struct stm32_dfsdm *dfsdm, unsigned int fl_id,
enum stm32_dfsdm_events event,
unsigned int chan_mask)
+{
- struct dfsdm_priv *priv = container_of(dfsdm, struct dfsdm_priv, dfsdm);
- unsigned long flags, ulmask = chan_mask;
- int ret, i;
- dev_dbg(&priv->pdev->dev, "%s:for filter %d: event %#x ch_mask %#x\n",
__func__, fl_id, event, chan_mask);
- if (event > DFSDM_EVENT_CKA)
return -EINVAL;
- /* Clear interrupt before enable them */
- ret = stm32_dfsdm_clear_event(priv, fl_id, event, chan_mask);
- if (ret < 0)
return ret;
- spin_lock_irqsave(&priv->lock, flags);
- /* Enable interrupts */
- switch (event) {
- case DFSDM_EVENT_SCD:
for_each_set_bit(i, &ulmask, priv->dfsdm.max_channels) {
DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CHCFGR1(i),
DFSDM_CHCFGR1_SCDEN_MASK,
DFSDM_CHCFGR1_SCDEN(1));
}
if (!priv->scd_filter_mask)
DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR2(0),
DFSDM_CR2_SCDIE_MASK,
DFSDM_CR2_SCDIE(1));
priv->scd_filter_mask |= BIT(fl_id);
break;
- case DFSDM_EVENT_CKA:
for_each_set_bit(i, &ulmask, priv->dfsdm.max_channels) {
DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CHCFGR1(i),
DFSDM_CHCFGR1_CKABEN_MASK,
DFSDM_CHCFGR1_CKABEN(1));
}
if (!priv->ckab_filter_mask)
DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR2(0),
DFSDM_CR2_CKABIE_MASK,
DFSDM_CR2_CKABIE(1));
priv->ckab_filter_mask |= BIT(fl_id);
break;
- default:
DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR2(fl_id), event, event);
- }
- priv->filters[fl_id].event_mask |= event;
- spin_unlock_irqrestore(&priv->lock, flags);
- return 0;
+} +EXPORT_SYMBOL_GPL(dfsdm_register_fl_event);
+/**
- stm32_dfsdm_unregister_fl_event - Unregister filter event.
- @dfsdm: Handle used to retrieve dfsdm context.
- @fl_id: Filter id.
- @event: Event to unregister.
- @chan_mask: Mask of channels associated to filter.
- The function disables associated IRQ.
- */
+int stm32_dfsdm_unregister_fl_event(struct stm32_dfsdm *dfsdm,
unsigned int fl_id,
enum stm32_dfsdm_events event,
unsigned int chan_mask)
+{
- struct dfsdm_priv *priv = container_of(dfsdm, struct dfsdm_priv, dfsdm);
- unsigned long flags, ulmask = chan_mask;
- int i;
- dev_dbg(&priv->pdev->dev, "%s:for filter %d: event %#x ch_mask %#x\n",
__func__, fl_id, event, chan_mask);
- if (event > DFSDM_EVENT_CKA)
return -EINVAL;
- spin_lock_irqsave(&priv->lock, flags);
- /* Disable interrupts */
- switch (event) {
- case DFSDM_EVENT_SCD:
for_each_set_bit(i, &ulmask, priv->dfsdm.max_channels) {
DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CHCFGR1(i),
DFSDM_CHCFGR1_SCDEN_MASK,
DFSDM_CHCFGR1_SCDEN(0));
}
priv->scd_filter_mask &= ~BIT(fl_id);
if (!priv->scd_filter_mask)
DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR2(0),
DFSDM_CR2_SCDIE_MASK,
DFSDM_CR2_SCDIE(0));
break;
- case DFSDM_EVENT_CKA:
for_each_set_bit(i, &ulmask, priv->dfsdm.max_channels) {
DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CHCFGR1(i),
DFSDM_CHCFGR1_CKABEN_MASK,
DFSDM_CHCFGR1_CKABEN(0));
}
priv->ckab_filter_mask &= ~BIT(fl_id);
if (!priv->ckab_filter_mask)
DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR2(0),
DFSDM_CR2_CKABIE_MASK,
DFSDM_CR2_CKABIE(0));
break;
- default:
DFSDM_UPDATE_BITS(priv->regmap, DFSDM_CR2(fl_id), event, 0);
- }
- priv->filters[fl_id].event_mask &= ~event;
- spin_unlock_irqrestore(&priv->lock, flags);
- return 0;
+} +EXPORT_SYMBOL_GPL(dfsdm_unregister_fl_event);
[...]
+/* DFSDM filter conversion type */ +enum stm32_dfsdm_conv_type {
- DFSDM_FILTER_REG_CONV, /* Regular conversion */
- DFSDM_FILTER_SW_INJ_CONV, /* Injected conversion */
- DFSDM_FILTER_TRIG_INJ_CONV, /* Injected conversion */
+};
+/* DFSDM filter regular synchronous mode */ +enum stm32_dfsdm_conv_rsync {
- DFSDM_FILTER_RSYNC_OFF, /* regular conversion asynchronous */
- DFSDM_FILTER_RSYNC_ON, /* regular conversion synchronous with filter0*/
stray 0?
Should read "filter instance 0"...
Cool.
This corresponds to a specificity of the DFSDM hardware. DFSDM can offer possibility to synchronize each filter output with the filter 0 instance output. As example, this can be used to synchronize several audio microphones. Filter 0 is allocated to main microphones and the other filters for background microphones (notice that we need one filter per 1-bit PDM stream)
Makes sense, thanks.
[...]
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
This patch adds documentation of device tree bindings for the STM32 DFSDM ADC.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com --- .../bindings/iio/adc/st,stm32-dfsdm-adc.txt | 60 ++++++++++++++++++++++ 1 file changed, 60 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..c156bcb --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/st,stm32-dfsdm-adc.txt @@ -0,0 +1,60 @@ +STMicroelectronics STM32 DFSDM ADC device driver + +STM32 DFSDM ADC is a sigma delta analog-to-digital converter. +It has to be declared in device-tree as a subnode of the DFSDM mfd node. + +It has several multiplexed input channels. Conversions can be performed +in single, scan or discontinuous mode. Conversions can be launched in software +or using hardware triggers. +Each instance of the driver uses one filter instance handle by the DFSDM mfd +driver. + +DFSDM also offers extra features: +-The analog watchdog feature allows the application to detect if the + input voltage goes beyond the user-defined, higher or lower thresholds. +-The short circuit detection allows allows the application to detect if the + input is in CC. +-The clock absence detection allows application to detect if SPI input is clocked. + +Required properties: +- compatible: Must be "st,stm32-dfsdm-adc". +- reg: Specifies the DFSDM filter instance. +- #io-channel-cells = <1>: See the IIO bindings section "IIO consumers". +- st,adc-channels: List of single-ended channels muxed for this ADC. +- st,adc-channel-names: List of single-ended channels Name. + +Optional properties: +- st,adc-channel-types: Single-ended channel input type. Default value is 0. + - 0: SPI with data on rising edge (default) + - 1: SPI with data on falling edge + - 2: manchester codec, rising edage = logic 0 + - 3: manchester codec, rising edage = logic 1 +- st,adc-channel-clk-src: Conversion clock source. default value is 1. + - 0: External SPI clocl (CLKIN x) + - 1: internal SPI clock (CLKOUT) (default) + - 2: internal SPI clock divided by 2 (falling edge). + - 2: internal SPI clock divided by 2 (rising edge). +- st,adc-alt-channel: must be defined if Two ADCs 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. + +Example: + dfsdm: dfsdm@4400D000 { + iio_dfsdm0: iio-dfsdm@0 { + compatible = "st,stm32-dfsdm-adc"; + #io-channel-cells = <1>; + reg = <0>; + st,adc-channels = <1>; + st,adc-channel-names = "in0"; + }; + iio_dfsdm1: iio-dfsdm@1 { + compatible = "st,stm32-dfsdm-adc"; + #io-channel-cells = <1>; + reg = <1>; + st,adc-channels = <1>; + st,adc-channel-names = "in1"; + st,adc-channel-types = <1>; + st,adc-alt-channel = <1>; + }; + };
On 23/01/17 16:32, Arnaud Pouliquen wrote:
This patch adds documentation of device tree bindings for the STM32 DFSDM ADC.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com
Trivial bits inline.
.../bindings/iio/adc/st,stm32-dfsdm-adc.txt | 60 ++++++++++++++++++++++ 1 file changed, 60 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..c156bcb --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/st,stm32-dfsdm-adc.txt @@ -0,0 +1,60 @@ +STMicroelectronics STM32 DFSDM ADC device driver
+STM32 DFSDM ADC is a sigma delta analog-to-digital converter. +It has to be declared in device-tree as a subnode of the DFSDM mfd node.
+It has several multiplexed input channels. Conversions can be performed +in single, scan or discontinuous mode. Conversions can be launched in software +or using hardware triggers. +Each instance of the driver uses one filter instance handle by the DFSDM mfd +driver.
+DFSDM also offers extra features: +-The analog watchdog feature allows the application to detect if the
- input voltage goes beyond the user-defined, higher or lower thresholds.
+-The short circuit detection allows allows the application to detect if the
- input is in CC.
+-The clock absence detection allows application to detect if SPI input is clocked.
+Required properties: +- compatible: Must be "st,stm32-dfsdm-adc". +- reg: Specifies the DFSDM filter instance. +- #io-channel-cells = <1>: See the IIO bindings section "IIO consumers". +- st,adc-channels: List of single-ended channels muxed for this ADC. +- st,adc-channel-names: List of single-ended channels Name.
+Optional properties: +- st,adc-channel-types: Single-ended channel input type. Default value is 0.
- 0: SPI with data on rising edge (default)
- 1: SPI with data on falling edge
This is an element that can be described without magic numbers so I'd prefer that you do so.
Also spell check edage -> edge.
- 2: manchester codec, rising edage = logic 0
- 3: manchester codec, rising edage = logic 1
+- st,adc-channel-clk-src: Conversion clock source. default value is 1.
- 0: External SPI clocl (CLKIN x)
- 1: internal SPI clock (CLKOUT) (default)
- 2: internal SPI clock divided by 2 (falling edge).
- 2: internal SPI clock divided by 2 (rising edge).
3?
+- st,adc-alt-channel: must be defined if Two ADCs 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.
? Two data inputs with shared clock?
+Example:
- dfsdm: dfsdm@4400D000 {
iio_dfsdm0: iio-dfsdm@0 {
compatible = "st,stm32-dfsdm-adc";
#io-channel-cells = <1>;
reg = <0>;
st,adc-channels = <1>;
st,adc-channel-names = "in0";
};
iio_dfsdm1: iio-dfsdm@1 {
compatible = "st,stm32-dfsdm-adc";
#io-channel-cells = <1>;
reg = <1>;
st,adc-channels = <1>;
st,adc-channel-names = "in1";
st,adc-channel-types = <1>;
st,adc-alt-channel = <1>;
};
- };
On 29/01/17 11:58, Jonathan Cameron wrote:
On 23/01/17 16:32, Arnaud Pouliquen wrote:
This patch adds documentation of device tree bindings for the STM32 DFSDM ADC.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com
Trivial bits inline.
.../bindings/iio/adc/st,stm32-dfsdm-adc.txt | 60 ++++++++++++++++++++++ 1 file changed, 60 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..c156bcb --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/st,stm32-dfsdm-adc.txt @@ -0,0 +1,60 @@ +STMicroelectronics STM32 DFSDM ADC device driver
+STM32 DFSDM ADC is a sigma delta analog-to-digital converter. +It has to be declared in device-tree as a subnode of the DFSDM mfd node.
+It has several multiplexed input channels. Conversions can be performed +in single, scan or discontinuous mode. Conversions can be launched in software +or using hardware triggers. +Each instance of the driver uses one filter instance handle by the DFSDM mfd +driver.
+DFSDM also offers extra features: +-The analog watchdog feature allows the application to detect if the
- input voltage goes beyond the user-defined, higher or lower thresholds.
+-The short circuit detection allows allows the application to detect if the
- input is in CC.
+-The clock absence detection allows application to detect if SPI input is clocked.
+Required properties: +- compatible: Must be "st,stm32-dfsdm-adc". +- reg: Specifies the DFSDM filter instance. +- #io-channel-cells = <1>: See the IIO bindings section "IIO consumers". +- st,adc-channels: List of single-ended channels muxed for this ADC. +- st,adc-channel-names: List of single-ended channels Name.
+Optional properties: +- st,adc-channel-types: Single-ended channel input type. Default value is 0.
- 0: SPI with data on rising edge (default)
- 1: SPI with data on falling edge
This is an element that can be described without magic numbers so I'd prefer that you do so.
Also spell check edage -> edge.
- 2: manchester codec, rising edage = logic 0
- 3: manchester codec, rising edage = logic 1
+- st,adc-channel-clk-src: Conversion clock source. default value is 1.
- 0: External SPI clocl (CLKIN x)
- 1: internal SPI clock (CLKOUT) (default)
- 2: internal SPI clock divided by 2 (falling edge).
- 2: internal SPI clock divided by 2 (rising edge).
3?
+- st,adc-alt-channel: must be defined if Two ADCs 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.
? Two data inputs with shared clock?
+Example:
- dfsdm: dfsdm@4400D000 {
I'd like to actually see the ADCs on the end of these explicitly described as well. It's entirely possible we'll have something that isn't simply a dumb sigma delta modulator that will need it's own driver.
In some senses what we actually have here is a data bus so we should be able to describe things on it in that fashion rather than just describing the data that is in coming. Our boundary of knowledge isn't at the edge of this device!
A quick example google fed me would be the gain control on a maxim ds8102.
Tricky bit here will be defining this nice and generally when we currently only have the one piece of hardware to do it with ;)
That's what makes kernel development fun!
Jonathan
iio_dfsdm0: iio-dfsdm@0 {
compatible = "st,stm32-dfsdm-adc";
#io-channel-cells = <1>;
reg = <0>;
st,adc-channels = <1>;
st,adc-channel-names = "in0";
};
iio_dfsdm1: iio-dfsdm@1 {
compatible = "st,stm32-dfsdm-adc";
#io-channel-cells = <1>;
reg = <1>;
st,adc-channels = <1>;
st,adc-channel-names = "in1";
st,adc-channel-types = <1>;
st,adc-alt-channel = <1>;
};
- };
-- 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
Hi Jonathan,
On 01/29/2017 01:42 PM, Jonathan Cameron wrote:
On 29/01/17 11:58, Jonathan Cameron wrote:
On 23/01/17 16:32, Arnaud Pouliquen wrote:
This patch adds documentation of device tree bindings for the STM32 DFSDM ADC.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com
Trivial bits inline.
.../bindings/iio/adc/st,stm32-dfsdm-adc.txt | 60 ++++++++++++++++++++++ 1 file changed, 60 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..c156bcb --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/st,stm32-dfsdm-adc.txt @@ -0,0 +1,60 @@ +STMicroelectronics STM32 DFSDM ADC device driver
+STM32 DFSDM ADC is a sigma delta analog-to-digital converter. +It has to be declared in device-tree as a subnode of the DFSDM mfd node.
+It has several multiplexed input channels. Conversions can be performed +in single, scan or discontinuous mode. Conversions can be launched in software +or using hardware triggers. +Each instance of the driver uses one filter instance handle by the DFSDM mfd +driver.
+DFSDM also offers extra features: +-The analog watchdog feature allows the application to detect if the
- input voltage goes beyond the user-defined, higher or lower thresholds.
+-The short circuit detection allows allows the application to detect if the
- input is in CC.
+-The clock absence detection allows application to detect if SPI input is clocked.
+Required properties: +- compatible: Must be "st,stm32-dfsdm-adc". +- reg: Specifies the DFSDM filter instance. +- #io-channel-cells = <1>: See the IIO bindings section "IIO consumers". +- st,adc-channels: List of single-ended channels muxed for this ADC. +- st,adc-channel-names: List of single-ended channels Name.
+Optional properties: +- st,adc-channel-types: Single-ended channel input type. Default value is 0.
- 0: SPI with data on rising edge (default)
- 1: SPI with data on falling edge
This is an element that can be described without magic numbers so I'd prefer that you do so.
Also spell check edage -> edge.
- 2: manchester codec, rising edage = logic 0
- 3: manchester codec, rising edage = logic 1
+- st,adc-channel-clk-src: Conversion clock source. default value is 1.
- 0: External SPI clocl (CLKIN x)
- 1: internal SPI clock (CLKOUT) (default)
- 2: internal SPI clock divided by 2 (falling edge).
- 2: internal SPI clock divided by 2 (rising edge).
3?
+- st,adc-alt-channel: must be defined if Two ADCs 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.
? Two data inputs with shared clock?
+Example:
- dfsdm: dfsdm@4400D000 {
I'd like to actually see the ADCs on the end of these explicitly described as well. It's entirely possible we'll have something that isn't simply a dumb sigma delta modulator that will need it's own driver.
In some senses what we actually have here is a data bus so we should be able to describe things on it in that fashion rather than just describing the data that is in coming. Our boundary of knowledge isn't at the edge of this device!
A quick example google fed me would be the gain control on a maxim ds8102.
Tricky bit here will be defining this nice and generally when we currently only have the one piece of hardware to do it with ;)
That's what makes kernel development fun!
What you describe here seems similar to audio codecs device defined in ASoC. An Audio codec is an external chip that is connected to SoC through an audio bus. ASoC allows to bind this codec with the SOC DAI (digital Audio Interface).
FYI we should have this kind of feature to support on STM32 uin second step: DSFDM input can also process stm32 ADC output through an hardware pipe (stm32-adc IIO driver that has been recently integrated in kernel). Internally, to pipe the ADC with DFSDM, we use an extension of inkern interface that is very similar with the one mentioned by Lars:
https://github.com/analogdevicesinc/linux/blob/xcomm_zynq/drivers /iio/buffer/hw_consumer.c
Don't know if this is what Lars has in mind... but a solution could be to define a new type of IIO that would be something like "postprocessing" or "filter". This new iio device type could be piped to a frontend IIO device to export a new one (through hw_consumer.c interface). In this case for simple device like PDM audio microphone that does not required control a kind of dummy frontend device could be defined...
Regards Arnaud
iio_dfsdm0: iio-dfsdm@0 {
compatible = "st,stm32-dfsdm-adc";
#io-channel-cells = <1>;
reg = <0>;
st,adc-channels = <1>;
st,adc-channel-names = "in0";
};
iio_dfsdm1: iio-dfsdm@1 {
compatible = "st,stm32-dfsdm-adc";
#io-channel-cells = <1>;
reg = <1>;
st,adc-channels = <1>;
st,adc-channel-names = "in1";
st,adc-channel-types = <1>;
st,adc-alt-channel = <1>;
};
- };
-- 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 31/01/17 14:10, Arnaud Pouliquen wrote:
Hi Jonathan,
On 01/29/2017 01:42 PM, Jonathan Cameron wrote:
On 29/01/17 11:58, Jonathan Cameron wrote:
On 23/01/17 16:32, Arnaud Pouliquen wrote:
This patch adds documentation of device tree bindings for the STM32 DFSDM ADC.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com
Trivial bits inline.
.../bindings/iio/adc/st,stm32-dfsdm-adc.txt | 60 ++++++++++++++++++++++ 1 file changed, 60 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..c156bcb --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/st,stm32-dfsdm-adc.txt @@ -0,0 +1,60 @@ +STMicroelectronics STM32 DFSDM ADC device driver
+STM32 DFSDM ADC is a sigma delta analog-to-digital converter. +It has to be declared in device-tree as a subnode of the DFSDM mfd node.
+It has several multiplexed input channels. Conversions can be performed +in single, scan or discontinuous mode. Conversions can be launched in software +or using hardware triggers. +Each instance of the driver uses one filter instance handle by the DFSDM mfd +driver.
+DFSDM also offers extra features: +-The analog watchdog feature allows the application to detect if the
- input voltage goes beyond the user-defined, higher or lower thresholds.
+-The short circuit detection allows allows the application to detect if the
- input is in CC.
+-The clock absence detection allows application to detect if SPI input is clocked.
+Required properties: +- compatible: Must be "st,stm32-dfsdm-adc". +- reg: Specifies the DFSDM filter instance. +- #io-channel-cells = <1>: See the IIO bindings section "IIO consumers". +- st,adc-channels: List of single-ended channels muxed for this ADC. +- st,adc-channel-names: List of single-ended channels Name.
+Optional properties: +- st,adc-channel-types: Single-ended channel input type. Default value is 0.
- 0: SPI with data on rising edge (default)
- 1: SPI with data on falling edge
This is an element that can be described without magic numbers so I'd prefer that you do so.
Also spell check edage -> edge.
- 2: manchester codec, rising edage = logic 0
- 3: manchester codec, rising edage = logic 1
+- st,adc-channel-clk-src: Conversion clock source. default value is 1.
- 0: External SPI clocl (CLKIN x)
- 1: internal SPI clock (CLKOUT) (default)
- 2: internal SPI clock divided by 2 (falling edge).
- 2: internal SPI clock divided by 2 (rising edge).
3?
+- st,adc-alt-channel: must be defined if Two ADCs 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.
? Two data inputs with shared clock?
+Example:
- dfsdm: dfsdm@4400D000 {
I'd like to actually see the ADCs on the end of these explicitly described as well. It's entirely possible we'll have something that isn't simply a dumb sigma delta modulator that will need it's own driver.
In some senses what we actually have here is a data bus so we should be able to describe things on it in that fashion rather than just describing the data that is in coming. Our boundary of knowledge isn't at the edge of this device!
A quick example google fed me would be the gain control on a maxim ds8102.
Tricky bit here will be defining this nice and generally when we currently only have the one piece of hardware to do it with ;)
That's what makes kernel development fun!
What you describe here seems similar to audio codecs device defined in ASoC. An Audio codec is an external chip that is connected to SoC through an audio bus. ASoC allows to bind this codec with the SOC DAI (digital Audio Interface).
Yes, that was exactly what I was thinking of. Was a while back but I once mainlined a codec so had this at the back of my mind.
FYI we should have this kind of feature to support on STM32 uin second step: DSFDM input can also process stm32 ADC output through an hardware pipe (stm32-adc IIO driver that has been recently integrated in kernel). Internally, to pipe the ADC with DFSDM, we use an extension of inkern interface that is very similar with the one mentioned by Lars:
https://github.com/analogdevicesinc/linux/blob/xcomm_zynq/drivers /iio/buffer/hw_consumer.c
Don't know if this is what Lars has in mind... but a solution could be to define a new type of IIO that would be something like "postprocessing" or "filter". This new iio device type could be piped to a frontend IIO device to export a new one (through hw_consumer.c interface). In this case for simple device like PDM audio microphone that does not required control a kind of dummy frontend device could be defined...
That could work. We already have stacked IIO devices - only difference here is that we can't actually see the data flowing between the levels of stack (or pipeline).
J
Regards Arnaud
iio_dfsdm0: iio-dfsdm@0 {
compatible = "st,stm32-dfsdm-adc";
#io-channel-cells = <1>;
reg = <0>;
st,adc-channels = <1>;
st,adc-channel-names = "in0";
};
iio_dfsdm1: iio-dfsdm@1 {
compatible = "st,stm32-dfsdm-adc";
#io-channel-cells = <1>;
reg = <1>;
st,adc-channels = <1>;
st,adc-channel-names = "in1";
st,adc-channel-types = <1>;
st,adc-alt-channel = <1>;
};
- };
-- 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
Hello Jonathan,
My comments below.
Regards
Arnaud On 01/29/2017 12:58 PM, Jonathan Cameron wrote:
On 23/01/17 16:32, Arnaud Pouliquen wrote:
This patch adds documentation of device tree bindings for the STM32 DFSDM ADC.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com
Trivial bits inline.
.../bindings/iio/adc/st,stm32-dfsdm-adc.txt | 60 ++++++++++++++++++++++ 1 file changed, 60 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..c156bcb --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/st,stm32-dfsdm-adc.txt @@ -0,0 +1,60 @@ +STMicroelectronics STM32 DFSDM ADC device driver
+STM32 DFSDM ADC is a sigma delta analog-to-digital converter. +It has to be declared in device-tree as a subnode of the DFSDM mfd node.
+It has several multiplexed input channels. Conversions can be performed +in single, scan or discontinuous mode. Conversions can be launched in software +or using hardware triggers. +Each instance of the driver uses one filter instance handle by the DFSDM mfd +driver.
+DFSDM also offers extra features: +-The analog watchdog feature allows the application to detect if the
- input voltage goes beyond the user-defined, higher or lower thresholds.
+-The short circuit detection allows allows the application to detect if the
- input is in CC.
+-The clock absence detection allows application to detect if SPI input is clocked.
+Required properties: +- compatible: Must be "st,stm32-dfsdm-adc". +- reg: Specifies the DFSDM filter instance. +- #io-channel-cells = <1>: See the IIO bindings section "IIO consumers". +- st,adc-channels: List of single-ended channels muxed for this ADC. +- st,adc-channel-names: List of single-ended channels Name.
+Optional properties: +- st,adc-channel-types: Single-ended channel input type. Default value is 0.
- 0: SPI with data on rising edge (default)
- 1: SPI with data on falling edge
This is an element that can be described without magic numbers so I'd prefer that you do so.
Also spell check edage -> edge.
ok i will use strings
- 2: manchester codec, rising edage = logic 0
- 3: manchester codec, rising edage = logic 1
+- st,adc-channel-clk-src: Conversion clock source. default value is 1.
- 0: External SPI clocl (CLKIN x)
- 1: internal SPI clock (CLKOUT) (default)
- 2: internal SPI clock divided by 2 (falling edge).
- 2: internal SPI clock divided by 2 (rising edge).
3?
+- st,adc-alt-channel: must be defined if Two ADCs 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.
? Two data inputs with shared clock?
Yes you can connect 2 digital microphones on one SPI. One samples audio data on the clock rising edge, the other one samples on the falling edge. you can see an example in slide 15 of following document. http://www.st.com/content/ccc/resource/training/technical/product_training/9...
I will describe this in an RFC to explain hardware but, to summarize, there are 3 main "entities" in DFSDM: - The serial interface: SPI or Manchester bus. - The channel: it a kind of bridge than can be connected: on one side to a serial input (channel n connected to SPI interface n or n + 1) on other side to one or several filters.
The filter: low pass filter + integrator.
+Example:
- dfsdm: dfsdm@4400D000 {
iio_dfsdm0: iio-dfsdm@0 {
compatible = "st,stm32-dfsdm-adc";
#io-channel-cells = <1>;
reg = <0>;
st,adc-channels = <1>;
st,adc-channel-names = "in0";
};
iio_dfsdm1: iio-dfsdm@1 {
compatible = "st,stm32-dfsdm-adc";
#io-channel-cells = <1>;
reg = <1>;
st,adc-channels = <1>;
st,adc-channel-names = "in1";
st,adc-channel-types = <1>;
st,adc-alt-channel = <1>;
};
- };
Add driver to handle Sigma Delta ADC conversion for ADC connected to DFSDM IP.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com --- drivers/iio/adc/Kconfig | 9 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/stm32-dfsdm-adc.c | 676 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 686 insertions(+) create mode 100644 drivers/iio/adc/stm32-dfsdm-adc.c
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index e0b3c09..4b2b886 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -441,6 +441,15 @@ config STM32_ADC This driver can also be built as a module. If so, the module will be called stm32-adc.
+config STM32_DFSDM_ADC + tristate "STMicroelectronics STM32 DFSDM ADC driver" + depends on (ARCH_STM32 && OF && MFD_STM32_DFSDM) || COMPILE_TEST + help + Say yes here to build the driver for the STMicroelectronics + STM32 analog-to-digital converter with Digital filter. + 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 8e02a94..aed42c6 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -43,6 +43,7 @@ obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o obj-$(CONFIG_STX104) += stx104.o obj-$(CONFIG_STM32_ADC_CORE) += stm32-adc-core.o obj-$(CONFIG_STM32_ADC) += stm32-adc.o +obj-$(CONFIG_STM32_DFSDM_ADC) += stm32-dfsdm-adc.o 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..727d6b1 --- /dev/null +++ b/drivers/iio/adc/stm32-dfsdm-adc.c @@ -0,0 +1,676 @@ +/* + * This file is part of STM32 DFSDM ADC 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/irq_work.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#include <linux/iio/iio.h> + +#include <linux/mfd/stm32-dfsdm.h> + +#define DFSDM_ADC_MAX_RESOLUTION 24 +#define DFSDM_ADC_STORAGE_BITS 32 + +#define DFSDM_MAX_CH_OFFSET BIT(24) +#define DFSDM_MAX_CH_SHIFT 24 + +#define DFSDM_TIMEOUT_US 100000 +#define DFSDM_TIMEOUT (msecs_to_jiffies(DFSDM_TIMEOUT_US / 1000)) + +#define CH_ID_FROM_IDX(i) (adc->inputs[i].id) +#define CH_CFG_FROM_IDX(i) (&adc->inputs_cfg[i]) + +struct stm32_dfsdm_adc { + struct device *dev; + struct stm32_dfsdm *dfsdm; + struct list_head adc_list; + + /* Filter */ + unsigned int fl_id; + struct stm32_dfsdm_sinc_filter sinc; + unsigned int int_oversampling; + + /* Channels */ + struct stm32_dfsdm_channel *inputs; + struct stm32_dfsdm_ch_cfg *inputs_cfg; + + /* Raw mode*/ + struct completion completion; + struct stm32_dfsdm_regular reg_params; + u32 *buffer; +}; + +static const char * const stm32_dfsdm_adc_sinc_order[] = { + [0] = "FastSinc", + [1] = "Sinc1", + [2] = "Sinc2", + [3] = "Sinc3", + [4] = "Sinc4", + [5] = "Sinc5", +}; + +static inline const struct iio_chan_spec *get_ch_from_id( + struct iio_dev *indio_dev, int ch_id) +{ + int i; + + for (i = 0; i < indio_dev->num_channels; i++) { + if (ch_id == indio_dev->channels[i].channel) + return &indio_dev->channels[i]; + } + + return NULL; +} + +/* + * Filter attributes + */ + +static int stm32_dfsdm_adc_set_sinc(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int val) +{ + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + + dev_dbg(&indio_dev->dev, "%s: %s\n", __func__, + stm32_dfsdm_adc_sinc_order[adc->sinc.order]); + + adc->sinc.order = val; + + return 0; +} + +static int stm32_dfsdm_adc_get_sinc(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + + dev_dbg(&indio_dev->dev, "%s: %s\n", __func__, + stm32_dfsdm_adc_sinc_order[adc->sinc.order]); + + return adc->sinc.order; +} + +static const struct iio_enum stm32_dfsdm_adc_fl_sinc_order = { + .items = stm32_dfsdm_adc_sinc_order, + .num_items = ARRAY_SIZE(stm32_dfsdm_adc_sinc_order), + .get = stm32_dfsdm_adc_get_sinc, + .set = stm32_dfsdm_adc_set_sinc, +}; + +static ssize_t stm32_dfsdm_adc_get_int_os(struct iio_dev *indio_dev, + uintptr_t priv, + const struct iio_chan_spec *chan, + char *buf) +{ + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + + return snprintf(buf, PAGE_SIZE, "%d\n", adc->int_oversampling); +} + +static ssize_t stm32_dfsdm_adc_set_int_os(struct iio_dev *indio_dev, + uintptr_t priv, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + int ret, val; + + ret = kstrtoint(buf, 0, &val); + if (ret) + return ret; + + if ((!val) || (val > DFSDM_MAX_INT_OVERSAMPLING)) { + dev_err(&indio_dev->dev, "invalid oversampling (0 or > %#x)", + DFSDM_MAX_INT_OVERSAMPLING); + return -EINVAL; + } + adc->int_oversampling = val; + + return len; +} + +static ssize_t stm32_dfsdm_adc_get_fl_os(struct iio_dev *indio_dev, + uintptr_t priv, + const struct iio_chan_spec *chan, + char *buf) +{ + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + + return snprintf(buf, PAGE_SIZE, "%d\n", adc->sinc.oversampling); +} + +static ssize_t stm32_dfsdm_adc_set_fl_os(struct iio_dev *indio_dev, + uintptr_t priv, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + int ret, val; + + ret = kstrtoint(buf, 0, &val); + if (ret) + return ret; + + if ((!val) || (val > DFSDM_MAX_FL_OVERSAMPLING)) { + dev_err(&indio_dev->dev, "invalid oversampling (0 or > %#x)", + DFSDM_MAX_FL_OVERSAMPLING); + return -EINVAL; + } + adc->sinc.oversampling = val; + + return len; +} + +/* + * Data bit shifting attribute + */ +static ssize_t stm32_dfsdm_adc_get_shift(struct iio_dev *indio_dev, + uintptr_t priv, + const struct iio_chan_spec *chan, + char *buf) +{ + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + struct stm32_dfsdm_ch_cfg *ch_cfg = CH_CFG_FROM_IDX(chan->scan_index); + + return snprintf(buf, PAGE_SIZE, "%d\n", ch_cfg->right_bit_shift); +} + +static ssize_t stm32_dfsdm_adc_set_shift(struct iio_dev *indio_dev, + uintptr_t priv, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + struct stm32_dfsdm_ch_cfg *ch_cfg = CH_CFG_FROM_IDX(chan->scan_index); + int ret, val; + + ret = kstrtoint(buf, 0, &val); + if (ret) + return ret; + + if (val > DFSDM_MAX_CH_SHIFT) { + dev_err(&indio_dev->dev, "invalid shift value (> %#x)", + DFSDM_MAX_CH_SHIFT); + return -EINVAL; + } + ch_cfg->right_bit_shift = val; + + return len; +} + +static const struct iio_chan_spec_ext_info stm32_dfsdm_adc_ext_info[] = { + /* sinc_filter_order: Configure Sinc filter order */ + IIO_ENUM("sinc_filter_order", IIO_SHARED_BY_TYPE, + &stm32_dfsdm_adc_fl_sinc_order), + IIO_ENUM_AVAILABLE("sinc_filter_order", &stm32_dfsdm_adc_fl_sinc_order), + /* filter oversampling: Post filter oversampling ratio */ + { + .name = "sinc_filter_oversampling_ratio", + .shared = IIO_SHARED_BY_TYPE, + .read = stm32_dfsdm_adc_get_fl_os, + .write = stm32_dfsdm_adc_set_fl_os, + }, + /* data_right_bit_shift : Filter output data shifting */ + { + .name = "data_right_bit_shift", + .shared = IIO_SEPARATE, + .read = stm32_dfsdm_adc_get_shift, + .write = stm32_dfsdm_adc_set_shift, + }, + + /* + * averaging_length : Mean windows of data from filter. + * Defines how many filter data will be summed to one data output + */ + { + .name = "integrator_oversampling", + .shared = IIO_SHARED_BY_TYPE, + .read = stm32_dfsdm_adc_get_int_os, + .write = stm32_dfsdm_adc_set_int_os, + }, + {}, +}; + +/* + * Filter event routine called under IRQ context + */ +static void stm32_dfsdm_event_cb(struct stm32_dfsdm *dfsdm, int flt_id, + enum stm32_dfsdm_events ev, unsigned int param, + void *context) +{ + struct stm32_dfsdm_adc *adc = context; + unsigned int ch_id; + + dev_dbg(adc->dev, "%s:\n", __func__); + + switch (ev) { + case DFSDM_EVENT_REG_EOC: + stm32_dfsdm_read_fl_conv(adc->dfsdm, flt_id, adc->buffer, + &ch_id, DFSDM_FILTER_REG_CONV); + complete(&adc->completion); + break; + case DFSDM_EVENT_REG_XRUN: + dev_err(adc->dev, "%s: underrun detected for filter %d\n", + __func__, flt_id); + break; + default: + dev_err(adc->dev, "%s: event %#x not implemented\n", + __func__, ev); + break; + } +} + +static inline void stm32_dfsdm_adc_fl_config(struct stm32_dfsdm_adc *adc, + u32 channel_mask, + struct stm32_dfsdm_filter *filter) +{ + dev_dbg(adc->dev, "%s:\n", __func__); + + filter->event.cb = stm32_dfsdm_event_cb; + filter->event.context = adc; + + filter->sinc_params = adc->sinc; + + filter->int_oversampling = adc->int_oversampling; +} + +static int stm32_dfsdm_adc_start_raw_conv(struct stm32_dfsdm_adc *adc, + const struct iio_chan_spec *chan) +{ + struct stm32_dfsdm_filter filter; + struct stm32_dfsdm_ch_cfg *ch_cfg = CH_CFG_FROM_IDX(chan->scan_index); + unsigned int ch_id = CH_ID_FROM_IDX(chan->scan_index); + int ret; + + dev_dbg(adc->dev, "%s:\n", __func__); + + memset(&filter, 0, sizeof(filter)); + filter.reg_params = &adc->reg_params; + + if (!filter.reg_params) + return -ENOMEM; + + filter.reg_params->ch_src = ch_id; + + stm32_dfsdm_adc_fl_config(adc, BIT(ch_id), &filter); + + ret = stm32_dfsdm_configure_filter(adc->dfsdm, adc->fl_id, &filter); + if (ret < 0) { + dev_err(adc->dev, "Failed to configure filter\n"); + return ret; + } + + ret = stm32_dfsdm_start_channel(adc->dfsdm, ch_id, ch_cfg); + if (ret < 0) + return ret; + + stm32_dfsdm_start_filter(adc->dfsdm, adc->fl_id, DFSDM_FILTER_REG_CONV); + + return 0; +} + +static void stm32_dfsdm_adc_stop_raw_conv(struct stm32_dfsdm_adc *adc, + const struct iio_chan_spec *chan) +{ + unsigned int ch_id = CH_ID_FROM_IDX(chan->scan_index); + + dev_dbg(adc->dev, "%s:\n", __func__); + + stm32_dfsdm_stop_filter(adc->dfsdm, adc->fl_id); + stm32_dfsdm_stop_channel(adc->dfsdm, ch_id); +} + +static int stm32_dfsdm_single_conv(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + u32 *result) +{ + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + long timeout; + int ret; + + dev_dbg(&indio_dev->dev, "%s:\n", __func__); + + reinit_completion(&adc->completion); + + ret = stm32_dfsdm_register_fl_event(adc->dfsdm, adc->fl_id, + DFSDM_EVENT_REG_EOC, 0); + if (ret < 0) { + dev_err(&indio_dev->dev, "Failed to register event\n"); + return ret; + } + + adc->buffer = result; + ret = stm32_dfsdm_adc_start_raw_conv(adc, chan); + if (ret) { + dev_err(&indio_dev->dev, "Failed to start conversion\n"); + goto free_event; + } + + timeout = wait_for_completion_interruptible_timeout(&adc->completion, + DFSDM_TIMEOUT); + 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", *result); + ret = IIO_VAL_INT; + } + + stm32_dfsdm_adc_stop_raw_conv(adc, chan); + +free_event: + adc->buffer = NULL; + stm32_dfsdm_unregister_fl_event(adc->dfsdm, adc->fl_id, + DFSDM_EVENT_REG_EOC, 0); + + 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); + struct stm32_dfsdm_ch_cfg *ch_cfg = CH_CFG_FROM_IDX(chan->scan_index); + int ret = -EINVAL; + + dev_dbg(&indio_dev->dev, "%s channel %d\n", __func__, chan->channel); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = stm32_dfsdm_single_conv(indio_dev, chan, val); + if (!ret) + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_OFFSET: + *val = ch_cfg->offset; + ret = IIO_VAL_INT; + break; + } + + 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_ch_cfg *ch_cfg = CH_CFG_FROM_IDX(chan->scan_index); + + dev_dbg(&indio_dev->dev, "%s channel%d", __func__, chan->channel); + + switch (mask) { + case IIO_CHAN_INFO_OFFSET: + if (val > DFSDM_MAX_CH_OFFSET) { + dev_err(&indio_dev->dev, "invalid offset (> %#lx)", + DFSDM_MAX_CH_OFFSET); + return -EINVAL; + } + ch_cfg->offset = val; + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct iio_info stm32_dfsdm_iio_info = { + .read_raw = stm32_dfsdm_read_raw, + .write_raw = stm32_dfsdm_write_raw, + .driver_module = THIS_MODULE, +}; + +static int stm32_dfsdm_adc_chan_init_one(struct iio_dev *indio_dev, + struct iio_chan_spec *chan, + int chan_idx) +{ + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + struct stm32_dfsdm_channel *dfsdm_ch = &adc->inputs[chan_idx]; + struct iio_chan_spec *ch = &chan[chan_idx]; + int ret; + unsigned int alt_ch = 0; + + ret = of_property_read_u32_index(indio_dev->dev.of_node, + "st,adc-channels", chan_idx, + &ch->channel); + if (ret < 0) { + dev_err(&indio_dev->dev, + " error parsing 'st,adc-channels' for idx %d\n", + chan_idx); + return ret; + } + + ret = of_property_read_string_index(indio_dev->dev.of_node, + "st,adc-channel-names", chan_idx, + &ch->datasheet_name); + if (ret < 0) { + dev_err(&indio_dev->dev, + " error parsing 'st,adc-channel-names' for idx %d\n", + chan_idx); + return ret; + } + + ch->extend_name = ch->datasheet_name; + ch->type = IIO_VOLTAGE; + ch->indexed = 1; + ch->scan_index = chan_idx; + ch->info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_OFFSET); + ch->scan_type.sign = 'u'; + ch->scan_type.realbits = DFSDM_ADC_MAX_RESOLUTION; + ch->scan_type.storagebits = DFSDM_ADC_STORAGE_BITS; + ch->scan_type.shift = 8; + + ch->ext_info = stm32_dfsdm_adc_ext_info; + + of_property_read_u32_index(indio_dev->dev.of_node, "st,adc-alt-channel", + chan_idx, &alt_ch); + /* Select the previous channel if alternate field is defined*/ + if (alt_ch) { + if (!ch->channel) + ch->channel = adc->dfsdm->max_channels; + ch->channel -= 1; + dfsdm_ch->serial_if.pins = DFSDM_CHANNEL_NEXT_CHANNEL_PINS; + } else { + dfsdm_ch->serial_if.pins = DFSDM_CHANNEL_SAME_CHANNEL_PINS; + } + dfsdm_ch->id = ch->channel; + + dfsdm_ch->type.DataPacking = DFSDM_CHANNEL_STANDARD_MODE; + + dfsdm_ch->type.source = DFSDM_CHANNEL_EXTERNAL_INPUTS; + ret = of_property_read_u32_index(indio_dev->dev.of_node, + "st,adc-channel-types", + chan_idx, &dfsdm_ch->serial_if.type); + if (ret < 0) + dfsdm_ch->serial_if.type = DFSDM_CHANNEL_SPI_RISING; + + ret = of_property_read_u32_index(indio_dev->dev.of_node, + "st,adc-channel-clk-src", + chan_idx, + &dfsdm_ch->serial_if.spi_clk); + + if ((dfsdm_ch->serial_if.type == DFSDM_CHANNEL_MANCHESTER_RISING) || + (dfsdm_ch->serial_if.type == DFSDM_CHANNEL_MANCHESTER_FALLING) || + (ret < 0)) + dfsdm_ch->serial_if.spi_clk = DFSDM_CHANNEL_SPI_CLOCK_INTERNAL; + + return stm32_dfsdm_get_channel(adc->dfsdm, dfsdm_ch); +} + +static int stm32_dfsdm_adc_chan_init(struct iio_dev *indio_dev) +{ + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + unsigned int num_ch; + struct iio_chan_spec *channels; + int ret, chan_idx; + + num_ch = of_property_count_strings(indio_dev->dev.of_node, + "st,adc-channel-names"); + + channels = devm_kcalloc(&indio_dev->dev, num_ch, sizeof(*channels), + GFP_KERNEL); + if (!channels) + return -ENOMEM; + + adc->inputs = devm_kcalloc(&indio_dev->dev, num_ch, + sizeof(*adc->inputs), GFP_KERNEL); + if (!adc->inputs) + return -ENOMEM; + + adc->inputs_cfg = devm_kcalloc(&indio_dev->dev, num_ch, + sizeof(*adc->inputs_cfg), GFP_KERNEL); + if (!adc->inputs_cfg) + 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 ch_error; + } + + indio_dev->num_channels = num_ch; + indio_dev->channels = channels; + + return 0; + +ch_error: + for (chan_idx--; chan_idx >= 0; chan_idx--) + stm32_dfsdm_release_channel(adc->dfsdm, + adc->inputs[chan_idx].id); + + return ret; +} + +static int stm32_dfsdm_adc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct stm32_dfsdm_adc *adc; + struct device_node *np = pdev->dev.of_node; + struct iio_dev *indio_dev; + int ret, i; + + if (!np) + return -ENODEV; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*adc)); + if (IS_ERR(indio_dev)) { + dev_err(dev, "%s: failed to allocate iio", __func__); + return PTR_ERR(indio_dev); + } + + indio_dev->name = np->name; + indio_dev->dev.parent = dev; + indio_dev->dev.of_node = np; + indio_dev->info = &stm32_dfsdm_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + adc = iio_priv(indio_dev); + if (IS_ERR(adc)) { + dev_err(dev, "%s: failed to allocate adc", __func__); + return PTR_ERR(adc); + } + + if (of_property_read_u32(np, "reg", &adc->fl_id)) { + dev_err(&pdev->dev, "missing reg property\n"); + return -EINVAL; + } + + adc->dev = &indio_dev->dev; + adc->dfsdm = dev_get_drvdata(pdev->dev.parent); + + ret = stm32_dfsdm_adc_chan_init(indio_dev); + if (ret < 0) { + dev_err(dev, "iio channels init failed\n"); + return ret; + } + + ret = stm32_dfsdm_get_filter(adc->dfsdm, adc->fl_id); + if (ret < 0) + goto get_fl_err; + + adc->int_oversampling = DFSDM_MIN_INT_OVERSAMPLING; + adc->sinc.oversampling = DFSDM_MIN_FL_OVERSAMPLING; + + init_completion(&adc->completion); + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) { + dev_err(adc->dev, "failed to register iio device\n"); + goto register_err; + } + + platform_set_drvdata(pdev, adc); + + return 0; + +register_err: + stm32_dfsdm_release_filter(adc->dfsdm, adc->fl_id); + +get_fl_err: + for (i = 0; i < indio_dev->num_channels; i++) + stm32_dfsdm_release_channel(adc->dfsdm, adc->inputs[i].id); + + return ret; +} + +static int stm32_dfsdm_adc_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev; + struct stm32_dfsdm_adc *adc = platform_get_drvdata(pdev); + int i; + + indio_dev = iio_priv_to_dev(adc); + for (i = 0; i < indio_dev->num_channels; i++) + stm32_dfsdm_release_channel(adc->dfsdm, adc->inputs[i].id); + stm32_dfsdm_release_filter(adc->dfsdm, adc->fl_id); + + return 0; +} + +static const struct of_device_id stm32_dfsdm_adc_match[] = { + { .compatible = "st,stm32-dfsdm-adc"}, + {} +}; + +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");
On 23/01/17 16:32, Arnaud Pouliquen wrote:
Add driver to handle Sigma Delta ADC conversion for ADC connected to DFSDM IP.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com
Various comments inline. Again mostly reading this to grasp how the whole lot fits together.
At the moment, other than some new ABI, this is a fairly simple sysfs or in kernel polled ADC driver. The hardware seems to support a whole lot more but fair enough to present this as a starting point!
Jonathan
drivers/iio/adc/Kconfig | 9 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/stm32-dfsdm-adc.c | 676 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 686 insertions(+) create mode 100644 drivers/iio/adc/stm32-dfsdm-adc.c
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index e0b3c09..4b2b886 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -441,6 +441,15 @@ config STM32_ADC This driver can also be built as a module. If so, the module will be called stm32-adc.
+config STM32_DFSDM_ADC
- tristate "STMicroelectronics STM32 DFSDM ADC driver"
- depends on (ARCH_STM32 && OF && MFD_STM32_DFSDM) || COMPILE_TEST
- help
Say yes here to build the driver for the STMicroelectronics
STM32 analog-to-digital converter with Digital filter.
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 8e02a94..aed42c6 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -43,6 +43,7 @@ obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o obj-$(CONFIG_STX104) += stx104.o obj-$(CONFIG_STM32_ADC_CORE) += stm32-adc-core.o obj-$(CONFIG_STM32_ADC) += stm32-adc.o +obj-$(CONFIG_STM32_DFSDM_ADC) += stm32-dfsdm-adc.o 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..727d6b1 --- /dev/null +++ b/drivers/iio/adc/stm32-dfsdm-adc.c @@ -0,0 +1,676 @@ +/*
- This file is part of STM32 DFSDM ADC 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/irq_work.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/slab.h>
+#include <linux/iio/iio.h>
+#include <linux/mfd/stm32-dfsdm.h>
+#define DFSDM_ADC_MAX_RESOLUTION 24 +#define DFSDM_ADC_STORAGE_BITS 32
+#define DFSDM_MAX_CH_OFFSET BIT(24) +#define DFSDM_MAX_CH_SHIFT 24
+#define DFSDM_TIMEOUT_US 100000 +#define DFSDM_TIMEOUT (msecs_to_jiffies(DFSDM_TIMEOUT_US / 1000))
+#define CH_ID_FROM_IDX(i) (adc->inputs[i].id) +#define CH_CFG_FROM_IDX(i) (&adc->inputs_cfg[i])
+struct stm32_dfsdm_adc {
- struct device *dev;
- struct stm32_dfsdm *dfsdm;
- struct list_head adc_list;
- /* Filter */
- unsigned int fl_id;
- struct stm32_dfsdm_sinc_filter sinc;
- unsigned int int_oversampling;
- /* Channels */
- struct stm32_dfsdm_channel *inputs;
- struct stm32_dfsdm_ch_cfg *inputs_cfg;
- /* Raw mode*/
- struct completion completion;
- struct stm32_dfsdm_regular reg_params;
- u32 *buffer;
+};
+static const char * const stm32_dfsdm_adc_sinc_order[] = {
- [0] = "FastSinc",
- [1] = "Sinc1",
- [2] = "Sinc2",
- [3] = "Sinc3",
- [4] = "Sinc4",
- [5] = "Sinc5",
+};
+static inline const struct iio_chan_spec *get_ch_from_id(
struct iio_dev *indio_dev, int ch_id)
+{
- int i;
- for (i = 0; i < indio_dev->num_channels; i++) {
if (ch_id == indio_dev->channels[i].channel)
return &indio_dev->channels[i];
- }
- return NULL;
+}
+/*
- Filter attributes
- */
+static int stm32_dfsdm_adc_set_sinc(struct iio_dev *indio_dev,
const struct iio_chan_spec *chan,
unsigned int val)
+{
- struct stm32_dfsdm_adc *adc = iio_priv(indio_dev);
- dev_dbg(&indio_dev->dev, "%s: %s\n", __func__,
stm32_dfsdm_adc_sinc_order[adc->sinc.order]);
- adc->sinc.order = val;
- return 0;
+}
+static int stm32_dfsdm_adc_get_sinc(struct iio_dev *indio_dev,
const struct iio_chan_spec *chan)
+{
- struct stm32_dfsdm_adc *adc = iio_priv(indio_dev);
- dev_dbg(&indio_dev->dev, "%s: %s\n", __func__,
stm32_dfsdm_adc_sinc_order[adc->sinc.order]);
- return adc->sinc.order;
+}
+static const struct iio_enum stm32_dfsdm_adc_fl_sinc_order = {
- .items = stm32_dfsdm_adc_sinc_order,
- .num_items = ARRAY_SIZE(stm32_dfsdm_adc_sinc_order),
- .get = stm32_dfsdm_adc_get_sinc,
- .set = stm32_dfsdm_adc_set_sinc,
+};
+static ssize_t stm32_dfsdm_adc_get_int_os(struct iio_dev *indio_dev,
uintptr_t priv,
const struct iio_chan_spec *chan,
char *buf)
+{
- struct stm32_dfsdm_adc *adc = iio_priv(indio_dev);
- return snprintf(buf, PAGE_SIZE, "%d\n", adc->int_oversampling);
+}
+static ssize_t stm32_dfsdm_adc_set_int_os(struct iio_dev *indio_dev,
uintptr_t priv,
const struct iio_chan_spec *chan,
const char *buf, size_t len)
+{
- struct stm32_dfsdm_adc *adc = iio_priv(indio_dev);
- int ret, val;
- ret = kstrtoint(buf, 0, &val);
- if (ret)
return ret;
- if ((!val) || (val > DFSDM_MAX_INT_OVERSAMPLING)) {
dev_err(&indio_dev->dev, "invalid oversampling (0 or > %#x)",
DFSDM_MAX_INT_OVERSAMPLING);
return -EINVAL;
- }
- adc->int_oversampling = val;
- return len;
+}
+static ssize_t stm32_dfsdm_adc_get_fl_os(struct iio_dev *indio_dev,
uintptr_t priv,
const struct iio_chan_spec *chan,
char *buf)
+{
- struct stm32_dfsdm_adc *adc = iio_priv(indio_dev);
- return snprintf(buf, PAGE_SIZE, "%d\n", adc->sinc.oversampling);
+}
+static ssize_t stm32_dfsdm_adc_set_fl_os(struct iio_dev *indio_dev,
uintptr_t priv,
const struct iio_chan_spec *chan,
const char *buf, size_t len)
+{
- struct stm32_dfsdm_adc *adc = iio_priv(indio_dev);
- int ret, val;
- ret = kstrtoint(buf, 0, &val);
- if (ret)
return ret;
- if ((!val) || (val > DFSDM_MAX_FL_OVERSAMPLING)) {
dev_err(&indio_dev->dev, "invalid oversampling (0 or > %#x)",
DFSDM_MAX_FL_OVERSAMPLING);
return -EINVAL;
- }
- adc->sinc.oversampling = val;
- return len;
+}
+/*
- Data bit shifting attribute
- */
+static ssize_t stm32_dfsdm_adc_get_shift(struct iio_dev *indio_dev,
uintptr_t priv,
const struct iio_chan_spec *chan,
char *buf)
+{
- struct stm32_dfsdm_adc *adc = iio_priv(indio_dev);
- struct stm32_dfsdm_ch_cfg *ch_cfg = CH_CFG_FROM_IDX(chan->scan_index);
- return snprintf(buf, PAGE_SIZE, "%d\n", ch_cfg->right_bit_shift);
+}
+static ssize_t stm32_dfsdm_adc_set_shift(struct iio_dev *indio_dev,
uintptr_t priv,
const struct iio_chan_spec *chan,
const char *buf, size_t len)
+{
- struct stm32_dfsdm_adc *adc = iio_priv(indio_dev);
- struct stm32_dfsdm_ch_cfg *ch_cfg = CH_CFG_FROM_IDX(chan->scan_index);
- int ret, val;
- ret = kstrtoint(buf, 0, &val);
- if (ret)
return ret;
- if (val > DFSDM_MAX_CH_SHIFT) {
dev_err(&indio_dev->dev, "invalid shift value (> %#x)",
DFSDM_MAX_CH_SHIFT);
return -EINVAL;
- }
- ch_cfg->right_bit_shift = val;
- return len;
+}
+static const struct iio_chan_spec_ext_info stm32_dfsdm_adc_ext_info[] = {
- /* sinc_filter_order: Configure Sinc filter order */
- IIO_ENUM("sinc_filter_order", IIO_SHARED_BY_TYPE,
&stm32_dfsdm_adc_fl_sinc_order),
Documentation? This is new ABI. Plus I'd like to see a generic description of the filters rather than a name out of the datasheet. Docs under Documentation/ABI/testing/sysfs-bus-iio-*
- IIO_ENUM_AVAILABLE("sinc_filter_order", &stm32_dfsdm_adc_fl_sinc_order),
- /* filter oversampling: Post filter oversampling ratio */
- {
.name = "sinc_filter_oversampling_ratio",
.shared = IIO_SHARED_BY_TYPE,
.read = stm32_dfsdm_adc_get_fl_os,
.write = stm32_dfsdm_adc_set_fl_os,
- },
- /* data_right_bit_shift : Filter output data shifting */
- {
.name = "data_right_bit_shift",
Well this is a simple scaling I think? Should be supported as such.
.shared = IIO_SEPARATE,
.read = stm32_dfsdm_adc_get_shift,
.write = stm32_dfsdm_adc_set_shift,
- },
- /*
* averaging_length : Mean windows of data from filter.
* Defines how many filter data will be summed to one data output
*/
This probably corresponds to the conventional oversampling_ratio that we already have as standard ABI (be it with some magic having occured before hand).
- {
.name = "integrator_oversampling",
.shared = IIO_SHARED_BY_TYPE,
.read = stm32_dfsdm_adc_get_int_os,
.write = stm32_dfsdm_adc_set_int_os,
- },
- {},
+};
+/*
- Filter event routine called under IRQ context
Again, keep to kernel comment style please. Either do proper kernel-doc or just simple single line comments where appropriate.
- */
+static void stm32_dfsdm_event_cb(struct stm32_dfsdm *dfsdm, int flt_id,
enum stm32_dfsdm_events ev, unsigned int param,
void *context)
+{
- struct stm32_dfsdm_adc *adc = context;
- unsigned int ch_id;
- dev_dbg(adc->dev, "%s:\n", __func__);
- switch (ev) {
- case DFSDM_EVENT_REG_EOC:
stm32_dfsdm_read_fl_conv(adc->dfsdm, flt_id, adc->buffer,
&ch_id, DFSDM_FILTER_REG_CONV);
complete(&adc->completion);
break;
- case DFSDM_EVENT_REG_XRUN:
dev_err(adc->dev, "%s: underrun detected for filter %d\n",
__func__, flt_id);
break;
- default:
dev_err(adc->dev, "%s: event %#x not implemented\n",
__func__, ev);
break;
- }
+}
+static inline void stm32_dfsdm_adc_fl_config(struct stm32_dfsdm_adc *adc,
u32 channel_mask,
struct stm32_dfsdm_filter *filter)
+{
- dev_dbg(adc->dev, "%s:\n", __func__);
- filter->event.cb = stm32_dfsdm_event_cb;
- filter->event.context = adc;
- filter->sinc_params = adc->sinc;
- filter->int_oversampling = adc->int_oversampling;
+}
+static int stm32_dfsdm_adc_start_raw_conv(struct stm32_dfsdm_adc *adc,
const struct iio_chan_spec *chan)
+{
- struct stm32_dfsdm_filter filter;
- struct stm32_dfsdm_ch_cfg *ch_cfg = CH_CFG_FROM_IDX(chan->scan_index);
- unsigned int ch_id = CH_ID_FROM_IDX(chan->scan_index);
- int ret;
- dev_dbg(adc->dev, "%s:\n", __func__);
- memset(&filter, 0, sizeof(filter));
- filter.reg_params = &adc->reg_params;
- if (!filter.reg_params)
return -ENOMEM;
- filter.reg_params->ch_src = ch_id;
- stm32_dfsdm_adc_fl_config(adc, BIT(ch_id), &filter);
A lot of this seems to run every time. Can we not leave some of this stuff configured for all conversions on a given channel?
- ret = stm32_dfsdm_configure_filter(adc->dfsdm, adc->fl_id, &filter);
- if (ret < 0) {
dev_err(adc->dev, "Failed to configure filter\n");
return ret;
- }
- ret = stm32_dfsdm_start_channel(adc->dfsdm, ch_id, ch_cfg);
- if (ret < 0)
return ret;
- stm32_dfsdm_start_filter(adc->dfsdm, adc->fl_id, DFSDM_FILTER_REG_CONV);
- return 0;
+}
+static void stm32_dfsdm_adc_stop_raw_conv(struct stm32_dfsdm_adc *adc,
const struct iio_chan_spec *chan)
+{
- unsigned int ch_id = CH_ID_FROM_IDX(chan->scan_index);
- dev_dbg(adc->dev, "%s:\n", __func__);
- stm32_dfsdm_stop_filter(adc->dfsdm, adc->fl_id);
- stm32_dfsdm_stop_channel(adc->dfsdm, ch_id);
+}
+static int stm32_dfsdm_single_conv(struct iio_dev *indio_dev,
const struct iio_chan_spec *chan,
u32 *result)
+{
- struct stm32_dfsdm_adc *adc = iio_priv(indio_dev);
- long timeout;
- int ret;
- dev_dbg(&indio_dev->dev, "%s:\n", __func__);
- reinit_completion(&adc->completion);
- ret = stm32_dfsdm_register_fl_event(adc->dfsdm, adc->fl_id,
DFSDM_EVENT_REG_EOC, 0);
- if (ret < 0) {
dev_err(&indio_dev->dev, "Failed to register event\n");
return ret;
- }
- adc->buffer = result;
- ret = stm32_dfsdm_adc_start_raw_conv(adc, chan);
- if (ret) {
dev_err(&indio_dev->dev, "Failed to start conversion\n");
goto free_event;
- }
- timeout = wait_for_completion_interruptible_timeout(&adc->completion,
DFSDM_TIMEOUT);
- 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", *result);
ret = IIO_VAL_INT;
- }
- stm32_dfsdm_adc_stop_raw_conv(adc, chan);
+free_event:
- adc->buffer = NULL;
- stm32_dfsdm_unregister_fl_event(adc->dfsdm, adc->fl_id,
DFSDM_EVENT_REG_EOC, 0);
- 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);
- struct stm32_dfsdm_ch_cfg *ch_cfg = CH_CFG_FROM_IDX(chan->scan_index);
- int ret = -EINVAL;
- dev_dbg(&indio_dev->dev, "%s channel %d\n", __func__, chan->channel);
- switch (mask) {
- case IIO_CHAN_INFO_RAW:
ret = stm32_dfsdm_single_conv(indio_dev, chan, val);
if (!ret)
ret = IIO_VAL_INT;
break;
- case IIO_CHAN_INFO_OFFSET:
*val = ch_cfg->offset;
ret = IIO_VAL_INT;
break;
- }
- 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_ch_cfg *ch_cfg = CH_CFG_FROM_IDX(chan->scan_index);
- dev_dbg(&indio_dev->dev, "%s channel%d", __func__, chan->channel);
- switch (mask) {
- case IIO_CHAN_INFO_OFFSET:
if (val > DFSDM_MAX_CH_OFFSET) {
dev_err(&indio_dev->dev, "invalid offset (> %#lx)",
DFSDM_MAX_CH_OFFSET);
return -EINVAL;
}
ch_cfg->offset = val;
break;
- default:
return -EINVAL;
- }
- return 0;
+}
+static const struct iio_info stm32_dfsdm_iio_info = {
- .read_raw = stm32_dfsdm_read_raw,
- .write_raw = stm32_dfsdm_write_raw,
- .driver_module = THIS_MODULE,
+};
+static int stm32_dfsdm_adc_chan_init_one(struct iio_dev *indio_dev,
struct iio_chan_spec *chan,
int chan_idx)
+{
- struct stm32_dfsdm_adc *adc = iio_priv(indio_dev);
- struct stm32_dfsdm_channel *dfsdm_ch = &adc->inputs[chan_idx];
- struct iio_chan_spec *ch = &chan[chan_idx];
- int ret;
- unsigned int alt_ch = 0;
- ret = of_property_read_u32_index(indio_dev->dev.of_node,
"st,adc-channels", chan_idx,
&ch->channel);
- if (ret < 0) {
dev_err(&indio_dev->dev,
" error parsing 'st,adc-channels' for idx %d\n",
chan_idx);
return ret;
- }
- ret = of_property_read_string_index(indio_dev->dev.of_node,
"st,adc-channel-names", chan_idx,
&ch->datasheet_name);
- if (ret < 0) {
dev_err(&indio_dev->dev,
" error parsing 'st,adc-channel-names' for idx %d\n",
chan_idx);
return ret;
- }
- ch->extend_name = ch->datasheet_name;
This is not what extend_name is for, don't do it - it just makes it really difficult to have standard userspace code. With hindsight I should probably never have introduced it in the first place.
- ch->type = IIO_VOLTAGE;
- ch->indexed = 1;
- ch->scan_index = chan_idx;
- ch->info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
BIT(IIO_CHAN_INFO_OFFSET);
- ch->scan_type.sign = 'u';
- ch->scan_type.realbits = DFSDM_ADC_MAX_RESOLUTION;
- ch->scan_type.storagebits = DFSDM_ADC_STORAGE_BITS;
Unless these are used elsehwere, given they are 'real numbers' it would actually be easier to read the code if you just put the numbers in here directly.
- ch->scan_type.shift = 8;
- ch->ext_info = stm32_dfsdm_adc_ext_info;
- of_property_read_u32_index(indio_dev->dev.of_node, "st,adc-alt-channel",
chan_idx, &alt_ch);
- /* Select the previous channel if alternate field is defined*/
space before */ Please check throughout as patches to fix this sort of thing are really boring :)
- if (alt_ch) {
if (!ch->channel)
ch->channel = adc->dfsdm->max_channels;
ch->channel -= 1;
dfsdm_ch->serial_if.pins = DFSDM_CHANNEL_NEXT_CHANNEL_PINS;
- } else {
dfsdm_ch->serial_if.pins = DFSDM_CHANNEL_SAME_CHANNEL_PINS;
- }
- dfsdm_ch->id = ch->channel;
- dfsdm_ch->type.DataPacking = DFSDM_CHANNEL_STANDARD_MODE;
- dfsdm_ch->type.source = DFSDM_CHANNEL_EXTERNAL_INPUTS;
- ret = of_property_read_u32_index(indio_dev->dev.of_node,
"st,adc-channel-types",
chan_idx, &dfsdm_ch->serial_if.type);
- if (ret < 0)
dfsdm_ch->serial_if.type = DFSDM_CHANNEL_SPI_RISING;
- ret = of_property_read_u32_index(indio_dev->dev.of_node,
"st,adc-channel-clk-src",
chan_idx,
&dfsdm_ch->serial_if.spi_clk);
- if ((dfsdm_ch->serial_if.type == DFSDM_CHANNEL_MANCHESTER_RISING) ||
(dfsdm_ch->serial_if.type == DFSDM_CHANNEL_MANCHESTER_FALLING) ||
(ret < 0))
dfsdm_ch->serial_if.spi_clk = DFSDM_CHANNEL_SPI_CLOCK_INTERNAL;
- return stm32_dfsdm_get_channel(adc->dfsdm, dfsdm_ch);
+}
+static int stm32_dfsdm_adc_chan_init(struct iio_dev *indio_dev) +{
- struct stm32_dfsdm_adc *adc = iio_priv(indio_dev);
- unsigned int num_ch;
- struct iio_chan_spec *channels;
- int ret, chan_idx;
- num_ch = of_property_count_strings(indio_dev->dev.of_node,
"st,adc-channel-names");
- channels = devm_kcalloc(&indio_dev->dev, num_ch, sizeof(*channels),
GFP_KERNEL);
- if (!channels)
return -ENOMEM;
- adc->inputs = devm_kcalloc(&indio_dev->dev, num_ch,
sizeof(*adc->inputs), GFP_KERNEL);
- if (!adc->inputs)
return -ENOMEM;
- adc->inputs_cfg = devm_kcalloc(&indio_dev->dev, num_ch,
sizeof(*adc->inputs_cfg), GFP_KERNEL);
- if (!adc->inputs_cfg)
return -ENOMEM;
There are a lot of small allocs here. I'm not sure how many channels there can be, but it may be more efficient (and cleaner) to just put arrays of sufficient size to take all possible channels directly in your adc structure.
- 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 ch_error;
- }
- indio_dev->num_channels = num_ch;
- indio_dev->channels = channels;
- return 0;
+ch_error:
- for (chan_idx--; chan_idx >= 0; chan_idx--)
stm32_dfsdm_release_channel(adc->dfsdm,
adc->inputs[chan_idx].id);
- return ret;
+}
+static int stm32_dfsdm_adc_probe(struct platform_device *pdev) +{
- struct device *dev = &pdev->dev;
- struct stm32_dfsdm_adc *adc;
- struct device_node *np = pdev->dev.of_node;
- struct iio_dev *indio_dev;
- int ret, i;
- if (!np)
return -ENODEV;
- indio_dev = devm_iio_device_alloc(dev, sizeof(*adc));
- if (IS_ERR(indio_dev)) {
dev_err(dev, "%s: failed to allocate iio", __func__);
return PTR_ERR(indio_dev);
- }
- indio_dev->name = np->name;
- indio_dev->dev.parent = dev;
- indio_dev->dev.of_node = np;
- indio_dev->info = &stm32_dfsdm_iio_info;
- indio_dev->modes = INDIO_DIRECT_MODE;
- adc = iio_priv(indio_dev);
- if (IS_ERR(adc)) {
dev_err(dev, "%s: failed to allocate adc", __func__);
return PTR_ERR(adc);
- }
- if (of_property_read_u32(np, "reg", &adc->fl_id)) {
dev_err(&pdev->dev, "missing reg property\n");
return -EINVAL;
- }
- adc->dev = &indio_dev->dev;
- adc->dfsdm = dev_get_drvdata(pdev->dev.parent);
- ret = stm32_dfsdm_adc_chan_init(indio_dev);
- if (ret < 0) {
dev_err(dev, "iio channels init failed\n");
return ret;
- }
- ret = stm32_dfsdm_get_filter(adc->dfsdm, adc->fl_id);
- if (ret < 0)
goto get_fl_err;
- adc->int_oversampling = DFSDM_MIN_INT_OVERSAMPLING;
- adc->sinc.oversampling = DFSDM_MIN_FL_OVERSAMPLING;
- init_completion(&adc->completion);
- ret = devm_iio_device_register(dev, indio_dev);
Probe order must be reversed in remove to make it obvious there are no weird and wonderful race conditions. The moment you have anything in probe that needs unwinding before a devm call that rule is broken.
Upshot, you can't use the devm version of iio_device_register.
- if (ret) {
dev_err(adc->dev, "failed to register iio device\n");
goto register_err;
- }
- platform_set_drvdata(pdev, adc);
- return 0;
+register_err:
- stm32_dfsdm_release_filter(adc->dfsdm, adc->fl_id);
+get_fl_err:
- for (i = 0; i < indio_dev->num_channels; i++)
stm32_dfsdm_release_channel(adc->dfsdm, adc->inputs[i].id);
- return ret;
+}
+static int stm32_dfsdm_adc_remove(struct platform_device *pdev) +{
- struct iio_dev *indio_dev;
- struct stm32_dfsdm_adc *adc = platform_get_drvdata(pdev);
- int i;
- indio_dev = iio_priv_to_dev(adc);
- for (i = 0; i < indio_dev->num_channels; i++)
stm32_dfsdm_release_channel(adc->dfsdm, adc->inputs[i].id);
- stm32_dfsdm_release_filter(adc->dfsdm, adc->fl_id);
- return 0;
+}
+static const struct of_device_id stm32_dfsdm_adc_match[] = {
- { .compatible = "st,stm32-dfsdm-adc"},
- {}
+};
+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");
Hello Jonathan,
Thanks for the reviews, i will integrate your comment in next version.
On 01/29/2017 01:15 PM, Jonathan Cameron wrote:
On 23/01/17 16:32, Arnaud Pouliquen wrote:
Add driver to handle Sigma Delta ADC conversion for ADC connected to DFSDM IP.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com
Various comments inline. Again mostly reading this to grasp how the whole lot fits together.
At the moment, other than some new ABI, this is a fairly simple sysfs or in kernel polled ADC driver. The hardware seems to support a whole lot more but fair enough to present this as a starting point!
Yes, from IIO point of view, should increase driver features in future to integrate buffer, scan mode, triggers... Or perhaps i will need to introduce some of these concepts to handle ASoC requirement in IIO...
FYI, DFSDM also offers some features like Analog watchdog, short circuit and SPI/manchester bus clock absence detection detection...:)
Regards Arnaud
From: olivier moysan olivier.moysan@st.com
Add copy support in pcm damengine operations. This allows to pre/post process samples (apply shift, mask ...) for playback/capture to support hardware contrains.
Signed-off-by: olivier moysan omoysan.stm32@gmail.com Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com --- include/sound/dmaengine_pcm.h | 3 +++ sound/soc/soc-generic-dmaengine-pcm.c | 37 +++++++++++++++++++++++++++++++++-- 2 files changed, 38 insertions(+), 2 deletions(-)
diff --git a/include/sound/dmaengine_pcm.h b/include/sound/dmaengine_pcm.h index 67be244..9d7bce8 100644 --- a/include/sound/dmaengine_pcm.h +++ b/include/sound/dmaengine_pcm.h @@ -137,6 +137,9 @@ struct snd_dmaengine_pcm_config { int (*prepare_slave_config)(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct dma_slave_config *slave_config); + int (*copy)(struct snd_pcm_substream *substream, int channel, + snd_pcm_uframes_t pos, + void __user *buf, snd_pcm_uframes_t count); struct dma_chan *(*compat_request_channel)( struct snd_soc_pcm_runtime *rtd, struct snd_pcm_substream *substream); diff --git a/sound/soc/soc-generic-dmaengine-pcm.c b/sound/soc/soc-generic-dmaengine-pcm.c index 6cef397..bd8332ce 100644 --- a/sound/soc/soc-generic-dmaengine-pcm.c +++ b/sound/soc/soc-generic-dmaengine-pcm.c @@ -329,6 +329,16 @@ static snd_pcm_uframes_t dmaengine_pcm_pointer( return snd_dmaengine_pcm_pointer(substream); }
+int dmaengine_pcm_copy(struct snd_pcm_substream *substream, int channel, + snd_pcm_uframes_t pos, void __user *buf, + snd_pcm_uframes_t count) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct dmaengine_pcm *pcm = soc_platform_to_pcm(rtd->platform); + + return pcm->config->copy(substream, channel, pos, buf, count); +} + static const struct snd_pcm_ops dmaengine_pcm_ops = { .open = dmaengine_pcm_open, .close = snd_dmaengine_pcm_close, @@ -339,6 +349,17 @@ static snd_pcm_uframes_t dmaengine_pcm_pointer( .pointer = dmaengine_pcm_pointer, };
+static const struct snd_pcm_ops dmaengine_pcm_ops_with_cpy = { + .open = dmaengine_pcm_open, + .close = snd_dmaengine_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = dmaengine_pcm_hw_params, + .hw_free = snd_pcm_lib_free_pages, + .trigger = snd_dmaengine_pcm_trigger, + .pointer = dmaengine_pcm_pointer, + .copy = dmaengine_pcm_copy, +}; + static const struct snd_soc_platform_driver dmaengine_pcm_platform = { .component_driver = { .probe_order = SND_SOC_COMP_ORDER_LATE, @@ -347,6 +368,14 @@ static snd_pcm_uframes_t dmaengine_pcm_pointer( .pcm_new = dmaengine_pcm_new, };
+static const struct snd_soc_platform_driver dmaengine_pcm_platform_with_cpy = { + .component_driver = { + .probe_order = SND_SOC_COMP_ORDER_LATE, + }, + .ops = &dmaengine_pcm_ops_with_cpy, + .pcm_new = dmaengine_pcm_new, +}; + static const char * const dmaengine_pcm_dma_channel_names[] = { [SNDRV_PCM_STREAM_PLAYBACK] = "tx", [SNDRV_PCM_STREAM_CAPTURE] = "rx", @@ -439,8 +468,12 @@ int snd_dmaengine_pcm_register(struct device *dev, if (ret) goto err_free_dma;
- ret = snd_soc_add_platform(dev, &pcm->platform, - &dmaengine_pcm_platform); + if (config && config->copy) + ret = snd_soc_add_platform(dev, &pcm->platform, + &dmaengine_pcm_platform_with_cpy); + else + ret = snd_soc_add_platform(dev, &pcm->platform, + &dmaengine_pcm_platform); if (ret) goto err_free_dma;
On Mon, Jan 23, 2017 at 05:32:23PM +0100, Arnaud Pouliquen wrote:
From: olivier moysan olivier.moysan@st.com
Signed-off-by: olivier moysan omoysan.stm32@gmail.com
The signoff and author information don't match for this patch, they should match. The signoff is important for legal reasons - please see SubmittingPatches for details.
Hi olivier,
[auto build test WARNING on iio/togreg] [cannot apply to asoc/for-next ljones-mfd/for-mfd-next v4.10-rc5 next-20170123] [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/jic23/iio.git togreg reproduce: make htmldocs
All warnings (new ones prefixed by >>):
include/sound/dmaengine_pcm.h:152: warning: No description found for parameter 'copy'
include/sound/compress_driver.h:162: warning: No description found for parameter 'id[64]' include/sound/compress_driver.h:162: warning: No description found for parameter 'proc_root' include/sound/compress_driver.h:162: warning: No description found for parameter 'proc_info_entry' include/sound/core.h:323: warning: No description found for parameter '...' include/sound/core.h:334: warning: No description found for parameter '...' include/sound/core.h:387: warning: No description found for parameter '...'
vim +/copy +152 include/sound/dmaengine_pcm.h
28c4468b Lars-Peter Clausen 2013-04-15 136 struct snd_dmaengine_pcm_config { 28c4468b Lars-Peter Clausen 2013-04-15 137 int (*prepare_slave_config)(struct snd_pcm_substream *substream, 28c4468b Lars-Peter Clausen 2013-04-15 138 struct snd_pcm_hw_params *params, 28c4468b Lars-Peter Clausen 2013-04-15 139 struct dma_slave_config *slave_config); 553a8492 olivier moysan 2017-01-23 140 int (*copy)(struct snd_pcm_substream *substream, int channel, 553a8492 olivier moysan 2017-01-23 141 snd_pcm_uframes_t pos, 553a8492 olivier moysan 2017-01-23 142 void __user *buf, snd_pcm_uframes_t count); c999836d Lars-Peter Clausen 2013-04-15 143 struct dma_chan *(*compat_request_channel)( c999836d Lars-Peter Clausen 2013-04-15 144 struct snd_soc_pcm_runtime *rtd, c999836d Lars-Peter Clausen 2013-04-15 145 struct snd_pcm_substream *substream); c999836d Lars-Peter Clausen 2013-04-15 146 dma_filter_fn compat_filter_fn; 194c7dea Stephen Warren 2013-12-03 147 struct device *dma_dev; 194c7dea Stephen Warren 2013-12-03 148 const char *chan_names[SNDRV_PCM_STREAM_LAST + 1]; 28c4468b Lars-Peter Clausen 2013-04-15 149 28c4468b Lars-Peter Clausen 2013-04-15 150 const struct snd_pcm_hardware *pcm_hardware; 28c4468b Lars-Peter Clausen 2013-04-15 151 unsigned int prealloc_buffer_size; 28c4468b Lars-Peter Clausen 2013-04-15 @152 }; 28c4468b Lars-Peter Clausen 2013-04-15 153 28c4468b Lars-Peter Clausen 2013-04-15 154 int snd_dmaengine_pcm_register(struct device *dev, 28c4468b Lars-Peter Clausen 2013-04-15 155 const struct snd_dmaengine_pcm_config *config, 28c4468b Lars-Peter Clausen 2013-04-15 156 unsigned int flags); 28c4468b Lars-Peter Clausen 2013-04-15 157 void snd_dmaengine_pcm_unregister(struct device *dev); 28c4468b Lars-Peter Clausen 2013-04-15 158 21585ee8 Lars-Peter Clausen 2013-11-28 159 int devm_snd_dmaengine_pcm_register(struct device *dev, 21585ee8 Lars-Peter Clausen 2013-11-28 160 const struct snd_dmaengine_pcm_config *config,
:::::: The code at line 152 was first introduced by commit :::::: 28c4468b00a1e55e08cc20117de968f7c6275441 ASoC: Add a generic dmaengine_pcm driver
:::::: TO: Lars-Peter Clausen lars@metafoo.de :::::: CC: Mark Brown broonie@opensource.wolfsonmicro.com
--- 0-DAY kernel test infrastructure Open Source Technology Center https://lists.01.org/pipermail/kbuild-all Intel Corporation
This patch adds documentation of device tree bindings for the STM32 DFSDM ASoC driver.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com --- .../devicetree/bindings/sound/st,sm32-adfsdm.txt | 84 ++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/st,sm32-adfsdm.txt
diff --git a/Documentation/devicetree/bindings/sound/st,sm32-adfsdm.txt b/Documentation/devicetree/bindings/sound/st,sm32-adfsdm.txt new file mode 100644 index 0000000..a1d27b8 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/st,sm32-adfsdm.txt @@ -0,0 +1,84 @@ +STMicroelectronics STM32 ADFSDM ASoC DAI device driver. + +STM32 ADFSDM ASoC is a sigma delta audio interface for digital microphone. +It has to be declared in device-tree as a subnode of the DFSDM mfd node. + +It offers possibility to record several mono microphones, with an option to +synchronize all microphones on a main one (that must be piped to filter 0) +Audio interface can be exposed through the generic ASoC simple card. + +2 Dmics can be connected on one SPI interface instance n. +Convention is that the DMIC that strobes data on rising edge is connected to the +corresponding DFSDM channel n; while the Dmic that strobes data on falling edge +is connected to the channel n-1. Simple card property "bitclock-inversion = <1>" +is used to specify that microphone strobes data on falling edge. + +SPI interface allows to be scheduled by an external SPI clock. To use it +simple card properties "bitclock-master = <&codec>" and "system-clock-frequency" +have to be defined in dai-link node. + +Required properties: +- compatible: Must be "st,stm32-dfsdm-audio", +- reg: Specifies the DFSDM filter instance. +- interrupts: DFSDM filter instance interrupt line. +- dma: DMA controller phandle and DMA request line associated to the + filter instance ( specified by the field "reg") +- dma-names: must be "rx" + +- st,input-id: Id of the SPI/Manchester interface used. +- st,dai-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. + +Optional properties: + - st,dai0-synchronized: Set to 1 to synchronize DAI with DFSDM instance 0. + +Exemple of a card with 2 Dmics synchronized and connected on SPI interface 1. + + dfsdm: dfsdm@4400D000 { + dai_dfsdm0: dfsdm-audio@0 { + compatible = "st,stm32-dfsdm-audio"; + #sound-dai-cells = <0>; + reg = <0>; + dmas = <&dmamux1 101 0x400 0x00>; + dma-names = "rx"; + st,input-id = <0>; + st,dai-filter-order = <5>; + }; + dai_dfsdm1: dfsdm-audio@1 { + compatible = "st,stm32-dfsdm-audio"; + #sound-dai-cells = <0>; + reg = <0>; + dmas = <&dmamux1 102 0x400 0x00>; + dma-names = "rx"; + st,input-id = <0>; + st,dai0-synchronized = <1>; + st,dai-filter-order = <5>; + }; + }; + sound_dfsdm_pdm { + compatible = "simple-audio-card"; + simple-audio-card,name = "dfsdm_pdm"; + status = "okay"; + + dfsdm0_mic0: simple-audio-card,dai-link@0 { + format = "pdm"; + cpu { + sound-dai = <&dai_dfsdm0>; + }; + dmic0_codec: codec { + sound-dai = <&dmic0>; + }; + }; + dfsdm0_mic1: simple-audio-card,dai-link@1 { + format = "pdm"; + bitclock-inversion = <1>; + cpu { + sound-dai = <&dai_dfsdm1>; + }; + codec { + sound-dai = <&dmic1>; + }; + }; + };
On 23/01/17 16:32, Arnaud Pouliquen wrote:
This patch adds documentation of device tree bindings for the STM32 DFSDM ASoC driver.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com
.../devicetree/bindings/sound/st,sm32-adfsdm.txt | 84 ++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/st,sm32-adfsdm.txt
diff --git a/Documentation/devicetree/bindings/sound/st,sm32-adfsdm.txt b/Documentation/devicetree/bindings/sound/st,sm32-adfsdm.txt new file mode 100644 index 0000000..a1d27b8 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/st,sm32-adfsdm.txt @@ -0,0 +1,84 @@ +STMicroelectronics STM32 ADFSDM ASoC DAI device driver.
+STM32 ADFSDM ASoC is a sigma delta audio interface for digital microphone. +It has to be declared in device-tree as a subnode of the DFSDM mfd node.
+It offers possibility to record several mono microphones, with an option to +synchronize all microphones on a main one (that must be piped to filter 0) +Audio interface can be exposed through the generic ASoC simple card.
+2 Dmics can be connected on one SPI interface instance n. +Convention is that the DMIC that strobes data on rising edge is connected to the +corresponding DFSDM channel n; while the Dmic that strobes data on falling edge +is connected to the channel n-1. Simple card property "bitclock-inversion = <1>" +is used to specify that microphone strobes data on falling edge.
+SPI interface allows to be scheduled by an external SPI clock. To use it +simple card properties "bitclock-master = <&codec>" and "system-clock-frequency" +have to be defined in dai-link node.
+Required properties: +- compatible: Must be "st,stm32-dfsdm-audio", +- reg: Specifies the DFSDM filter instance. +- interrupts: DFSDM filter instance interrupt line. +- dma: DMA controller phandle and DMA request line associated to the
filter instance ( specified by the field "reg")
+- dma-names: must be "rx"
+- st,input-id: Id of the SPI/Manchester interface used. +- st,dai-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.
Interesting for audio you consider it feature of the hardware, but for ADC you consider that this should be userspace controlled. Personally I'd like to see more detail on those filters but if this is convention in Asoc then so be it.
+Optional properties:
- st,dai0-synchronized: Set to 1 to synchronize DAI with DFSDM instance 0.
+Exemple of a card with 2 Dmics synchronized and connected on SPI interface 1.
Example.
- dfsdm: dfsdm@4400D000 {
dai_dfsdm0: dfsdm-audio@0 {
compatible = "st,stm32-dfsdm-audio";
#sound-dai-cells = <0>;
reg = <0>;
dmas = <&dmamux1 101 0x400 0x00>;
dma-names = "rx";
st,input-id = <0>;
st,dai-filter-order = <5>;
};
dai_dfsdm1: dfsdm-audio@1 {
compatible = "st,stm32-dfsdm-audio";
#sound-dai-cells = <0>;
reg = <0>;
dmas = <&dmamux1 102 0x400 0x00>;
dma-names = "rx";
st,input-id = <0>;
st,dai0-synchronized = <1>;
st,dai-filter-order = <5>;
};
- };
- sound_dfsdm_pdm {
compatible = "simple-audio-card";
simple-audio-card,name = "dfsdm_pdm";
status = "okay";
dfsdm0_mic0: simple-audio-card,dai-link@0 {
format = "pdm";
cpu {
sound-dai = <&dai_dfsdm0>;
};
dmic0_codec: codec {
sound-dai = <&dmic0>;
};
};
dfsdm0_mic1: simple-audio-card,dai-link@1 {
format = "pdm";
bitclock-inversion = <1>;
cpu {
sound-dai = <&dai_dfsdm1>;
};
codec {
sound-dai = <&dmic1>;
};
};
- };
Jonathan
Hello Johan,
Please find my comments in-line.
Regards
Arnaud
On 01/29/2017 01:19 PM, Jonathan Cameron wrote:
On 23/01/17 16:32, Arnaud Pouliquen wrote:
This patch adds documentation of device tree bindings for the STM32 DFSDM ASoC driver.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com
.../devicetree/bindings/sound/st,sm32-adfsdm.txt | 84 ++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/st,sm32-adfsdm.txt
diff --git a/Documentation/devicetree/bindings/sound/st,sm32-adfsdm.txt b/Documentation/devicetree/bindings/sound/st,sm32-adfsdm.txt new file mode 100644 index 0000000..a1d27b8 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/st,sm32-adfsdm.txt @@ -0,0 +1,84 @@ +STMicroelectronics STM32 ADFSDM ASoC DAI device driver.
+STM32 ADFSDM ASoC is a sigma delta audio interface for digital microphone. +It has to be declared in device-tree as a subnode of the DFSDM mfd node.
+It offers possibility to record several mono microphones, with an option to +synchronize all microphones on a main one (that must be piped to filter 0) +Audio interface can be exposed through the generic ASoC simple card.
+2 Dmics can be connected on one SPI interface instance n. +Convention is that the DMIC that strobes data on rising edge is connected to the +corresponding DFSDM channel n; while the Dmic that strobes data on falling edge +is connected to the channel n-1. Simple card property "bitclock-inversion = <1>" +is used to specify that microphone strobes data on falling edge.
+SPI interface allows to be scheduled by an external SPI clock. To use it +simple card properties "bitclock-master = <&codec>" and "system-clock-frequency" +have to be defined in dai-link node.
+Required properties: +- compatible: Must be "st,stm32-dfsdm-audio", +- reg: Specifies the DFSDM filter instance. +- interrupts: DFSDM filter instance interrupt line. +- dma: DMA controller phandle and DMA request line associated to the
filter instance ( specified by the field "reg")
+- dma-names: must be "rx"
+- st,input-id: Id of the SPI/Manchester interface used. +- st,dai-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.
Interesting for audio you consider it feature of the hardware, but for ADC you consider that this should be userspace controlled. Personally I'd like to see more detail on those filters but if this is convention in Asoc then so be it.
Configure filter and integrator is quite tricky. Updating filter and integrator parameters have impact on sample resolution and output sample rate. Sample resolution is the peak to peak data value and depends on oversampling parameters and filter order.
Output sample rate depends on SPI input clock frequency oversampling parameters and filter order.
High filter order gives a better resolution and a better filter response but allows less dynamic to find the good rate...
So it is a compromise between resolution, rate and filtering.
For ADC use case, filter orders 1 to 3 are recommended. For audio use case, orders 3 to 5 are recommended.
On IIO side i proposed to expose tuning in ABI, to allows application fine tuning. Need to document it...
For ASOC it is another story. Application requests a rate and a sample format. So filter parameters are computed based on SPI bus clock frequency, expected audio sample rate and audio sample format. This is done in stm32_adfsdm_get_best_osr in sound/soc/stm/stm32_adfsdm.c.
Now your comment trig a good point. Perhaps a better option in IIO, could be to offer same kind of ABI than ASOC interface: - standard oversampling-ratio ABI. based on it filter and integrator oversampling ratios will be computed to maximize the resolution. - a read only resolution ABI: /sys/bus/iio/devices/iio:deviceX/in_adc_x_resolution. That give the computed resolution.
In this case filter order will be part of the DT for IIO and ASoC. This would also match with a redesign of the DFSDM driver in IIO...
+Optional properties:
- st,dai0-synchronized: Set to 1 to synchronize DAI with DFSDM instance 0.
+Exemple of a card with 2 Dmics synchronized and connected on SPI interface 1.
Example.
- dfsdm: dfsdm@4400D000 {
dai_dfsdm0: dfsdm-audio@0 {
compatible = "st,stm32-dfsdm-audio";
#sound-dai-cells = <0>;
reg = <0>;
dmas = <&dmamux1 101 0x400 0x00>;
dma-names = "rx";
st,input-id = <0>;
st,dai-filter-order = <5>;
};
dai_dfsdm1: dfsdm-audio@1 {
compatible = "st,stm32-dfsdm-audio";
#sound-dai-cells = <0>;
reg = <0>;
dmas = <&dmamux1 102 0x400 0x00>;
dma-names = "rx";
st,input-id = <0>;
st,dai0-synchronized = <1>;
st,dai-filter-order = <5>;
};
- };
- sound_dfsdm_pdm {
compatible = "simple-audio-card";
simple-audio-card,name = "dfsdm_pdm";
status = "okay";
dfsdm0_mic0: simple-audio-card,dai-link@0 {
format = "pdm";
cpu {
sound-dai = <&dai_dfsdm0>;
};
dmic0_codec: codec {
sound-dai = <&dmic0>;
};
};
dfsdm0_mic1: simple-audio-card,dai-link@1 {
format = "pdm";
bitclock-inversion = <1>;
cpu {
sound-dai = <&dai_dfsdm1>;
};
codec {
sound-dai = <&dmic1>;
};
};
- };
Jonathan
On 30/01/17 17:32, Arnaud Pouliquen wrote:
Hello Johan,
Please find my comments in-line.
Regards
Arnaud
On 01/29/2017 01:19 PM, Jonathan Cameron wrote:
On 23/01/17 16:32, Arnaud Pouliquen wrote:
This patch adds documentation of device tree bindings for the STM32 DFSDM ASoC driver.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com
.../devicetree/bindings/sound/st,sm32-adfsdm.txt | 84 ++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/st,sm32-adfsdm.txt
diff --git a/Documentation/devicetree/bindings/sound/st,sm32-adfsdm.txt b/Documentation/devicetree/bindings/sound/st,sm32-adfsdm.txt new file mode 100644 index 0000000..a1d27b8 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/st,sm32-adfsdm.txt @@ -0,0 +1,84 @@ +STMicroelectronics STM32 ADFSDM ASoC DAI device driver.
+STM32 ADFSDM ASoC is a sigma delta audio interface for digital microphone. +It has to be declared in device-tree as a subnode of the DFSDM mfd node.
+It offers possibility to record several mono microphones, with an option to +synchronize all microphones on a main one (that must be piped to filter 0) +Audio interface can be exposed through the generic ASoC simple card.
+2 Dmics can be connected on one SPI interface instance n. +Convention is that the DMIC that strobes data on rising edge is connected to the +corresponding DFSDM channel n; while the Dmic that strobes data on falling edge +is connected to the channel n-1. Simple card property "bitclock-inversion = <1>" +is used to specify that microphone strobes data on falling edge.
+SPI interface allows to be scheduled by an external SPI clock. To use it +simple card properties "bitclock-master = <&codec>" and "system-clock-frequency" +have to be defined in dai-link node.
+Required properties: +- compatible: Must be "st,stm32-dfsdm-audio", +- reg: Specifies the DFSDM filter instance. +- interrupts: DFSDM filter instance interrupt line. +- dma: DMA controller phandle and DMA request line associated to the
filter instance ( specified by the field "reg")
+- dma-names: must be "rx"
+- st,input-id: Id of the SPI/Manchester interface used. +- st,dai-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.
Interesting for audio you consider it feature of the hardware, but for ADC you consider that this should be userspace controlled. Personally I'd like to see more detail on those filters but if this is convention in Asoc then so be it.
Configure filter and integrator is quite tricky. Updating filter and integrator parameters have impact on sample resolution and output sample rate. Sample resolution is the peak to peak data value and depends on oversampling parameters and filter order.
Output sample rate depends on SPI input clock frequency oversampling parameters and filter order.
High filter order gives a better resolution and a better filter response but allows less dynamic to find the good rate...
So it is a compromise between resolution, rate and filtering.
For ADC use case, filter orders 1 to 3 are recommended. For audio use case, orders 3 to 5 are recommended.
I'm never against allowing users enough rope to hang themselves as long as the defaults are sensible :)
On IIO side i proposed to expose tuning in ABI, to allows application fine tuning. Need to document it...
For ASOC it is another story. Application requests a rate and a sample format. So filter parameters are computed based on SPI bus clock frequency, expected audio sample rate and audio sample format. This is done in stm32_adfsdm_get_best_osr in sound/soc/stm/stm32_adfsdm.c.
If there is only ever one sensible answer, fair enough on not being able to pick and choose.
Now your comment trig a good point. Perhaps a better option in IIO, could be to offer same kind of ABI than ASOC interface:
- standard oversampling-ratio ABI. based on it filter and integrator
oversampling ratios will be computed to maximize the resolution.
- a read only resolution ABI:
/sys/bus/iio/devices/iio:deviceX/in_adc_x_resolution. That give the computed resolution.
Would this be directly exposed in terms of the 'maximum value' that could come out? If so we could use the existing range description callbacks (called _available though that's somewhat missleading in this case).
In this case filter order will be part of the DT for IIO and ASoC. This would also match with a redesign of the DFSDM driver in IIO...
Sounds to me like it would be sensible to do restrict things somewhat in the first instance, and perhaps rename them as 'defaults' in the future if we decide that userspace control does make sense. Basically do it the first time someone bashes us over the head with a usecase we aren't supporting well.
+Optional properties:
- st,dai0-synchronized: Set to 1 to synchronize DAI with DFSDM instance 0.
+Exemple of a card with 2 Dmics synchronized and connected on SPI interface 1.
Example.
- dfsdm: dfsdm@4400D000 {
dai_dfsdm0: dfsdm-audio@0 {
compatible = "st,stm32-dfsdm-audio";
#sound-dai-cells = <0>;
reg = <0>;
dmas = <&dmamux1 101 0x400 0x00>;
dma-names = "rx";
st,input-id = <0>;
st,dai-filter-order = <5>;
};
dai_dfsdm1: dfsdm-audio@1 {
compatible = "st,stm32-dfsdm-audio";
#sound-dai-cells = <0>;
reg = <0>;
dmas = <&dmamux1 102 0x400 0x00>;
dma-names = "rx";
st,input-id = <0>;
st,dai0-synchronized = <1>;
st,dai-filter-order = <5>;
};
- };
- sound_dfsdm_pdm {
compatible = "simple-audio-card";
simple-audio-card,name = "dfsdm_pdm";
status = "okay";
dfsdm0_mic0: simple-audio-card,dai-link@0 {
format = "pdm";
cpu {
sound-dai = <&dai_dfsdm0>;
};
dmic0_codec: codec {
sound-dai = <&dmic0>;
};
};
dfsdm0_mic1: simple-audio-card,dai-link@1 {
format = "pdm";
bitclock-inversion = <1>;
cpu {
sound-dai = <&dai_dfsdm1>;
};
codec {
sound-dai = <&dmic1>;
};
};
- };
Jonathan
-- 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
Add driver to handle PDM microphones connected to DFSDM IP.
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com --- sound/soc/Kconfig | 1 + sound/soc/Makefile | 1 + sound/soc/stm/Kconfig | 10 + sound/soc/stm/Makefile | 2 + sound/soc/stm/stm32_adfsdm.c | 686 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 700 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..79aee4e --- /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 && MFD_STM32_DFSDM) || 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..9d34bb7 --- /dev/null +++ b/sound/soc/stm/stm32_adfsdm.c @@ -0,0 +1,686 @@ +/* + * This file is part of STM32 DFSDM ASoC DAI driver + * + * Copyright (C) 2017, STMicroelectronics - All Rights Reserved + * Authors: Arnaud Pouliquen arnaud.pouliquen@st.com + * Olivier Moysan olivier.moysan@st.com + * + * License type: GPLv2 + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see http://www.gnu.org/licenses/. + */ + +#include <linux/clk.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#include <sound/dmaengine_pcm.h> + +#include <linux/mfd/stm32-dfsdm.h> + +/* + * Set data output resolution to 23 bits max to keep 1 extra bit for sign, + * as filter output is symmetric +/-2^(n-1). + */ +#define STM32_ADFSDM_DATA_RES BIT(23) +#define STM32_ADFSDM_MAX_RES BIT(31) +#define STM32_ADFSDM_DATAR_DATA_MASK GENMASK(31, 8) + +struct stm32_adfsdm_data { + unsigned int rate; /* SNDRV_PCM_RATE value */ + unsigned int freq; /* frequency in Hz */ + unsigned int fosr; /* filter over sampling ratio */ + unsigned int iosr; /* integrator over sampling ratio */ + unsigned int fast; /* filter fast mode */ + unsigned long res; /* output data resolution */ + int shift; /* shift on data output */ + bool h_res_found; /* preferred resolution higher than expected */ +}; + +static const struct stm32_adfsdm_data stm32_dfsdm_filter[] = { + { .rate = SNDRV_PCM_RATE_8000, .freq = 8000 }, + { .rate = SNDRV_PCM_RATE_16000, .freq = 16000 }, + { .rate = SNDRV_PCM_RATE_32000, .freq = 32000 }, +}; + +static const unsigned int stm32_dfsdm_sr_val[] = { + 8000, + 16000, + 32000, +}; + +struct stm32_adfsdm_priv { + struct snd_soc_dai_driver dai; + struct snd_dmaengine_dai_dma_data dma_data; + struct snd_pcm_substream *substream; + struct stm32_dfsdm_sinc_filter fl; + struct stm32_dfsdm_channel channel; + struct stm32_dfsdm_ch_cfg ch_cfg; + struct stm32_dfsdm *dfsdm; + struct stm32_adfsdm_data *f_param; + struct device *dev; + struct snd_pcm_hw_constraint_list rates_const; + unsigned long dmic_clk; + unsigned int input_id; + unsigned int fl_id; + unsigned int order; /* filter order */ + int synchro; +}; + +static const struct snd_pcm_hardware stm32_adfsdm_pcm_hw = { + .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID, + .formats = SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE, + + .rate_min = 8000, + .rate_max = 48000, + + .channels_min = 1, + .channels_max = 1, + + .periods_min = 2, + .periods_max = 48, + + .period_bytes_min = 40, /* 8 khz 5 ms */ + .period_bytes_max = 4 * PAGE_SIZE, + .buffer_bytes_max = 16 * PAGE_SIZE +}; + +static inline void stm32_adfsdm_get_param(struct stm32_adfsdm_priv *priv, + unsigned int rate, + struct stm32_adfsdm_data **fparam) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(stm32_dfsdm_filter); i++) { + if (rate == priv->f_param[i].freq) { + *fparam = &priv->f_param[i]; + break; + } + } +} + +static int stm32_adfsdm_compute_shift(struct stm32_adfsdm_priv *priv, + struct stm32_adfsdm_data *param) +{ + int shift = 0; + u32 r = param->res; + + if (!r) { + dev_err(priv->dev, "%s: resolution undefined\n", __func__); + return -EINVAL; + } + + /* + * If filter resolution is higher than data output resolution + * compute right shift required to match data resolution. + * Otherwise compute left shift to align MSB on data resolution. + */ + if (r >= STM32_ADFSDM_DATA_RES) + while ((r >> -shift) >= STM32_ADFSDM_DATA_RES) + shift--; + else + while ((r << shift) < STM32_ADFSDM_DATA_RES) + shift++; + + param->shift = shift; + dev_dbg(priv->dev, "%s: output shift: %d\n", __func__, shift); + + return 0; +} + +static int stm32_adfsdm_get_best_osr(struct stm32_adfsdm_priv *priv, + unsigned int decim, bool fast, + struct stm32_adfsdm_data *param) +{ + unsigned int i, d, fosr, iosr; + u64 res; + s64 delta; + unsigned int m = 1; /* multiplication factor */ + unsigned int p = priv->order; /* filter order (ford) */ + + /* + * Decimation d depends on the filter order and the oversampling ratios. + * ford: filter order + * fosr: filter over sampling ratio + * iosr: integrator over sampling ratio + */ + dev_dbg(priv->dev, "%s: decim = %d fast = %d\n", __func__, decim, fast); + if (priv->order == DFSDM_FASTSINC_ORDER) { + m = 2; + p = 2; + } + + /* + * Looks for filter and integrator oversampling ratios which allow + * 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 (priv->order == DFSDM_FASTSINC_ORDER) + d = fosr * (iosr + 3) + 2; + else + d = fosr * (iosr - 1 + p) + p; + + if (d > decim) + break; + else if (d != decim) + 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 > STM32_ADFSDM_MAX_RES) + break; + } + if (res > STM32_ADFSDM_MAX_RES) + continue; + res = res * (u64)m * (u64)iosr; + if (res > STM32_ADFSDM_MAX_RES) + continue; + + delta = res - STM32_ADFSDM_DATA_RES; + + if (res >= param->res) { + param->res = res; + param->fosr = fosr; + param->iosr = iosr; + param->fast = fast; + } + + if (!delta) + return 0; + } + } + + if (!param->fosr) + return -EINVAL; + + return 0; +} + +static int stm32_adfsdm_get_supported_rates(struct stm32_adfsdm_priv *priv, + unsigned int *rates) +{ + unsigned long fs = priv->dmic_clk; + unsigned int i, decim; + int ret; + + *rates = 0; + + for (i = 0; i < ARRAY_SIZE(stm32_dfsdm_filter); i++) { + /* check that clkout_freq is compatible */ + if ((fs % priv->f_param[i].freq) != 0) + continue; + + decim = fs / priv->f_param[i].freq; + + /* + * Try to find one solution for filter and integrator + * oversampling ratio with fast mode ON or OFF. + * Fast mode on is the preferred solution. + */ + ret = stm32_adfsdm_get_best_osr(priv, decim, 0, + &priv->f_param[i]); + ret &= stm32_adfsdm_get_best_osr(priv, decim, 1, + &priv->f_param[i]); + if (!ret) { + ret = stm32_adfsdm_compute_shift(priv, + &priv->f_param[i]); + if (ret) + continue; + + *rates |= 1 << i; + dev_dbg(priv->dev, "%s: %d rate supported\n", __func__, + priv->f_param[i].freq); + } + } + + if (!*rates) { + dev_err(priv->dev, "%s: no matched rate found\n", __func__); + return -EINVAL; + } + + return 0; +} + +static void stm32_dfsdm_xrun(struct stm32_dfsdm *dfsdm, int flt_id, + enum stm32_dfsdm_events ev, unsigned int param, + void *context) +{ + struct stm32_adfsdm_priv *priv = context; + + snd_pcm_stream_lock(priv->substream); + dev_err(priv->dev, "%s:unexpected underrun\n", __func__); + /* Stop the player */ + stm32_dfsdm_unregister_fl_event(priv->dfsdm, priv->fl_id, + DFSDM_EVENT_REG_XRUN, 0); + snd_pcm_stop(priv->substream, SNDRV_PCM_STATE_XRUN); + snd_pcm_stream_unlock(priv->substream); +} + +static int stm32_adfsdm_copy(struct snd_pcm_substream *substream, int channel, + snd_pcm_uframes_t pos, + void __user *buf, snd_pcm_uframes_t count) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct stm32_adfsdm_priv *priv = snd_soc_dai_get_drvdata(rtd->cpu_dai); + struct stm32_adfsdm_data *f_param; + int *ptr = (int *)(runtime->dma_area + frames_to_bytes(runtime, pos)); + char *hwbuf = runtime->dma_area + frames_to_bytes(runtime, pos); + ssize_t bytes = frames_to_bytes(runtime, count); + ssize_t sample_cnt = bytes_to_samples(runtime, bytes); + + stm32_adfsdm_get_param(priv, runtime->rate, &f_param); + + /* + * Audio samples are available on 24 MSBs of the DFSDM DATAR register. + * We need to mask 8 LSB control bits... + * Additionnaly precision depends on decimation and can need shift + * to be aligned on 32-bit word MSB. + */ + if (f_param->shift > 0) { + do { + *ptr <<= f_param->shift & STM32_ADFSDM_DATAR_DATA_MASK; + ptr++; + } while (--sample_cnt); + } else { + do { + *ptr &= STM32_ADFSDM_DATAR_DATA_MASK; + ptr++; + } while (--sample_cnt); + } + + return copy_to_user(buf, hwbuf, bytes); +} + +static int stm32_adfsdm_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct stm32_adfsdm_priv *priv = snd_soc_dai_get_drvdata(dai); + + priv->substream = substream; + + /* Fix available rate depending on CLKOUT or CKIN value */ + return snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &priv->rates_const); +} + +static void stm32_adfsdm_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct stm32_adfsdm_priv *priv = snd_soc_dai_get_drvdata(dai); + + priv->substream = NULL; +} + +static int stm32_adfsdm_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_dmaengine_dai_dma_data *dma_data; + + dev_dbg(dai->dev, "%s: enter\n", __func__); + dma_data = snd_soc_dai_get_dma_data(dai, substream); + dma_data->maxburst = 1; + + return 0; +} + +static int stm32_adfsdm_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct stm32_adfsdm_priv *priv = snd_soc_dai_get_drvdata(dai); + struct snd_pcm_runtime *runtime = substream->runtime; + struct stm32_adfsdm_data *f_param; + struct stm32_dfsdm_filter filter; + struct stm32_dfsdm_regular params; + int ret; + + dev_dbg(dai->dev, "%s: enter\n", __func__); + + stm32_adfsdm_get_param(priv, runtime->rate, &f_param); + + memset(&filter, 0, sizeof(filter)); + memset(¶ms, 0, sizeof(params)); + + params.ch_src = priv->channel.id; + params.dma_mode = 1; + params.cont_mode = 1; + params.fast_mode = f_param->fast; + params.sync_mode = priv->synchro ? + DFSDM_FILTER_RSYNC_ON : DFSDM_FILTER_RSYNC_OFF; + filter.reg_params = ¶ms; + filter.sinc_params.order = priv->order; + filter.sinc_params.oversampling = f_param->fosr; + filter.int_oversampling = f_param->iosr; + + filter.event.cb = stm32_dfsdm_xrun; + filter.event.context = priv; + + ret = stm32_dfsdm_configure_filter(priv->dfsdm, priv->fl_id, &filter); + if (ret < 0) + return ret; + + ret = stm32_dfsdm_register_fl_event(priv->dfsdm, priv->fl_id, + DFSDM_EVENT_REG_XRUN, 0); + if (ret < 0) + dev_err(priv->dev, "Failed to register xrun event\n"); + + return ret; +} + +static int stm32_adfsdm_start(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct stm32_adfsdm_priv *priv = snd_soc_dai_get_drvdata(dai); + struct snd_pcm_runtime *runtime = substream->runtime; + struct stm32_adfsdm_data *f_param; + int ret; + + dev_dbg(dai->dev, "%s: enter\n", __func__); + + stm32_adfsdm_get_param(priv, runtime->rate, &f_param); + if (f_param->shift < 0) + priv->ch_cfg.right_bit_shift = -f_param->shift; + + ret = stm32_dfsdm_start_channel(priv->dfsdm, priv->channel.id, + &priv->ch_cfg); + if (ret < 0) + return ret; + + stm32_dfsdm_start_filter(priv->dfsdm, priv->fl_id, + DFSDM_FILTER_REG_CONV); + + return 0; +} + +static void stm32_adfsdm_stop(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct stm32_adfsdm_priv *priv = snd_soc_dai_get_drvdata(dai); + + dev_dbg(dai->dev, "%s: enter\n", __func__); + + stm32_dfsdm_unregister_fl_event(priv->dfsdm, priv->fl_id, + DFSDM_EVENT_REG_XRUN, 0); + stm32_dfsdm_stop_filter(priv->dfsdm, priv->fl_id); + stm32_dfsdm_stop_channel(priv->dfsdm, priv->channel.id); +} + +static int stm32_adfsdm_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + return stm32_adfsdm_start(substream, dai); + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + stm32_adfsdm_stop(substream, dai); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int stm32_adfsdm_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct stm32_adfsdm_priv *priv = snd_soc_dai_get_drvdata(dai); + unsigned int inv = fmt & SND_SOC_DAIFMT_INV_MASK; + unsigned int cb = fmt & SND_SOC_DAIFMT_MASTER_MASK; + int ret; + + dev_dbg(dai->dev, "%s: enter\n", __func__); + + /* DAI clock strobing */ + if ((inv == SND_SOC_DAIFMT_IB_NF) || (inv == SND_SOC_DAIFMT_IB_IF)) { + priv->channel.serial_if.type = DFSDM_CHANNEL_SPI_FALLING; + priv->channel.serial_if.pins = DFSDM_CHANNEL_NEXT_CHANNEL_PINS; + /* + * if data on falling egde SPI connected to channel n - 1. + * if data on rising egde SPI connected to channel n. + */ + if (priv->input_id) + priv->channel.id = priv->input_id - 1; + else + priv->channel.id = priv->dfsdm->max_channels - 1; + } else { + priv->channel.serial_if.type = DFSDM_CHANNEL_SPI_RISING; + priv->channel.serial_if.pins = DFSDM_CHANNEL_SAME_CHANNEL_PINS; + priv->channel.id = priv->input_id; + } + + dev_dbg(dai->dev, "%s: channel %d on input %d\n", __func__, + priv->channel.id, priv->input_id); + + if ((cb == SND_SOC_DAIFMT_CBS_CFM) || (cb == SND_SOC_DAIFMT_CBS_CFS)) { + /* Digital microphone is clocked by CLKOUT */ + stm32_dfsdm_get_clk_out_rate(priv->dfsdm, &priv->dmic_clk); + } else { + /* Digital microphone is clocked by external clock */ + if (!priv->dmic_clk) { + dev_err(priv->dev, + "system-clock-frequency not defined\n"); + return -EINVAL; + } + } + + priv->rates_const.count = ARRAY_SIZE(stm32_dfsdm_sr_val); + priv->rates_const.list = stm32_dfsdm_sr_val; + ret = stm32_adfsdm_get_supported_rates(priv, &priv->rates_const.mask); + if (ret < 0) + return ret; + + return stm32_dfsdm_get_channel(priv->dfsdm, &priv->channel); +} + +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); + + dev_dbg(dai->dev, "%s: enter for dai %d\n", __func__, dai->id); + if (dir == SND_SOC_CLOCK_IN) + priv->dmic_clk = freq; + + return 0; +} + +static const struct snd_soc_dai_ops stm32_adfsdm_dai_ops = { + .startup = stm32_adfsdm_startup, + .shutdown = stm32_adfsdm_shutdown, + .hw_params = stm32_adfsdm_dai_hw_params, + .set_fmt = stm32_adfsdm_set_dai_fmt, + .set_sysclk = stm32_adfsdm_set_sysclk, + .prepare = stm32_adfsdm_prepare, + .trigger = stm32_adfsdm_trigger, +}; + +static int stm32_adfsdm_dai_probe(struct snd_soc_dai *dai) +{ + struct stm32_adfsdm_priv *priv = snd_soc_dai_get_drvdata(dai); + struct snd_dmaengine_dai_dma_data *dma = &priv->dma_data; + int ret; + + dev_dbg(dai->dev, "%s: enter for dai %d\n", __func__, dai->id); + + /* filter settings */ + ret = stm32_dfsdm_get_filter(priv->dfsdm, priv->fl_id); + if (ret < 0) + return -EBUSY; + + /* DMA settings */ + snd_soc_dai_init_dma_data(dai, NULL, dma); + dma->addr = stm32_dfsdm_get_filter_dma_phy_addr(priv->dfsdm, + priv->fl_id, + DFSDM_FILTER_REG_CONV); + dma->addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + + return 0; +} + +static int stm32_adfsdm_dai_remove(struct snd_soc_dai *dai) +{ + struct stm32_adfsdm_priv *priv = snd_soc_dai_get_drvdata(dai); + + dev_dbg(dai->dev, "%s: enter for dai %d\n", __func__, dai->id); + + stm32_dfsdm_release_filter(priv->dfsdm, priv->fl_id); + stm32_dfsdm_release_channel(priv->dfsdm, priv->channel.id); + + return 0; +} + +static const struct snd_soc_dai_driver stm32_adfsdm_dai = { + .capture = { + .channels_min = 1, + .channels_max = 1, + .formats = SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE, + .rates = SNDRV_PCM_RATE_8000_48000, + }, + .probe = stm32_adfsdm_dai_probe, + .remove = stm32_adfsdm_dai_remove, + .ops = &stm32_adfsdm_dai_ops, +}; + +static const struct snd_soc_component_driver stm32_adfsdm_dai_component = { + .name = "sti_cpu_dai", +}; + +static const struct snd_dmaengine_pcm_config dmaengine_pcm_config = { + .pcm_hardware = &stm32_adfsdm_pcm_hw, + .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config, + .copy = stm32_adfsdm_copy, +}; + +static int stm32_adfsdm_probe(struct platform_device *pdev) +{ + struct stm32_adfsdm_priv *priv; + struct device_node *np = pdev->dev.of_node; + char *_name; + int ret; + + dev_dbg(&pdev->dev, "%s: enter for node %s\n", __func__, + np->name); + + if (!np) { + dev_err(&pdev->dev, "No DT found\n"); + return -EINVAL; + } + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = &pdev->dev; + priv->dfsdm = dev_get_drvdata(pdev->dev.parent); + + if (of_property_read_u32(np, "reg", &priv->fl_id)) { + dev_err(&pdev->dev, "missing reg property\n"); + return -EINVAL; + } + + ret = of_property_read_u32(np, "st,dai-filter-order", &priv->order); + if (ret < 0) { + dev_warn(&pdev->dev, "Default filter order selected\n"); + priv->order = DFSDM_SINC5_ORDER; + } + + ret = of_property_read_u32(np, "st,input-id", &priv->input_id); + if (ret < 0) { + dev_err(&pdev->dev, "st,input-id property missing\n"); + return ret; + } + + ret = of_property_read_u32(np, "st,dai0-synchronized", &priv->synchro); + if (ret < 0) + /* default case if property not defined */ + priv->synchro = 0; + + priv->channel.type.DataPacking = DFSDM_CHANNEL_STANDARD_MODE; + priv->channel.type.source = DFSDM_CHANNEL_EXTERNAL_INPUTS; + priv->channel.serial_if.spi_clk = DFSDM_CHANNEL_SPI_CLOCK_INTERNAL; + + /* DAI settings */ + _name = devm_kzalloc(&pdev->dev, sizeof("dfsdm_pdm_0"), GFP_KERNEL); + if (!_name) + return -ENOMEM; + + priv->dai = stm32_adfsdm_dai; + + priv->f_param = devm_kcalloc(&pdev->dev, + ARRAY_SIZE(stm32_dfsdm_filter), + sizeof(stm32_dfsdm_filter[0]), GFP_KERNEL); + if (!priv->f_param) + return -ENOMEM; + + memcpy(priv->f_param, stm32_dfsdm_filter, + ARRAY_SIZE(stm32_dfsdm_filter) * sizeof(stm32_dfsdm_filter[0])); + + snprintf(_name, sizeof("dfsdm_pdm_0"), "dfsdm_pdm_%i", priv->fl_id); + priv->dai.name = _name; + priv->dai.capture.stream_name = _name; + + dev_set_drvdata(&pdev->dev, priv); + + ret = devm_snd_soc_register_component(&pdev->dev, + &stm32_adfsdm_dai_component, + &priv->dai, 1); + if (ret < 0) + return ret; + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, + &dmaengine_pcm_config, 0); + if (ret < 0) + dev_err(&pdev->dev, "failed to register dma pcm config\n"); + + return ret; +} + +static const struct of_device_id snd_soc_dfsdm_match[] = { + {.compatible = "st,stm32-dfsdm-audio"}, + {}, +}; + +static struct platform_driver stm32_adfsdm_driver = { + .driver = { + .name = "stm32-dfsdm-audio", + .of_match_table = snd_soc_dfsdm_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");
participants (7)
-
Arnaud Pouliquen
-
Jonathan Cameron
-
kbuild test robot
-
Lars-Peter Clausen
-
Lee Jones
-
Mark Brown
-
Rob Herring