[PATCH v3 05/11] IIO: ADC: add stm32 DFSDM support for Sigma delta ADC
Jonathan Cameron
jic23 at kernel.org
Sun Mar 19 15:25:47 PDT 2017
On 17/03/17 14:08, Arnaud Pouliquen wrote:
> Add driver for stm32 DFSDM IP. This IP converts a sigma delta stream
> in n bit samples through a low pass filter and an integrator.
> stm32-dfsdm-adc driver allows to handle sigma delta ADC.
>
> Signed-off-by: Arnaud Pouliquen <arnaud.pouliquen at st.com>
Various minor bits inline.
I'm mostly liking this. I do slightly wondering if semantically it
should be the front end that has the channels rather than the
backend. Would be fiddly to do though and probably not worth the
hassle.
Would love to see it running in a continuous mode in IIO, but
I guess that can follow along later.
The comment about the trigger has me confused
- perhaps you could elaborate further on that?
Jonathan
> ---
> V2 -> V3 :
> - Split audio and ADC support in 2 drivers
> - Implement DMA cyclic mode
> - Add SPI bus Trigger for buffer management
>
> drivers/iio/adc/Kconfig | 26 ++
> drivers/iio/adc/Makefile | 2 +
> drivers/iio/adc/stm32-dfsdm-adc.c | 419 +++++++++++++++++++++++
> drivers/iio/adc/stm32-dfsdm-core.c | 658 +++++++++++++++++++++++++++++++++++++
> drivers/iio/adc/stm32-dfsdm.h | 372 +++++++++++++++++++++
> 5 files changed, 1477 insertions(+)
> create mode 100644 drivers/iio/adc/stm32-dfsdm-adc.c
> create mode 100644 drivers/iio/adc/stm32-dfsdm-core.c
> create mode 100644 drivers/iio/adc/stm32-dfsdm.h
>
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index d411d66..3e0eb11 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -452,6 +452,32 @@ config STM32_ADC
> This driver can also be built as a module. If so, the module
> will be called stm32-adc.
>
> +config STM32_DFSDM_CORE
> + tristate "STMicroelectronics STM32 dfsdm core"
> + depends on (ARCH_STM32 && OF) || COMPILE_TEST
> + select REGMAP
> + select REGMAP_MMIO
> + help
> + Select this option to enable the driver for STMicroelectronics
> + STM32 digital filter for sigma delta converter.
> +
> + This driver can also be built as a module. If so, the module
> + will be called stm32-dfsdm-core.
> +
> +config STM32_DFSDM_ADC
> + tristate "STMicroelectronics STM32 dfsdm adc"
> + depends on (ARCH_STM32 && OF) || COMPILE_TEST
> + select STM32_DFSDM_CORE
> + select REGMAP_MMIO
> + select IIO_BUFFER_DMAENGINE
> + select IIO_HW_CONSUMER
> + help
> + Select this option to support ADCSigma delta modulator for
> + STMicroelectronics STM32 digital filter for sigma delta converter.
> +
> + This driver can also be built as a module. If so, the module
> + will be called stm32-dfsdm-adc.
> +
> config STX104
> tristate "Apex Embedded Systems STX104 driver"
> depends on X86 && ISA_BUS_API
> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
> index c68819c..161f271 100644
> --- a/drivers/iio/adc/Makefile
> +++ b/drivers/iio/adc/Makefile
> @@ -43,6 +43,8 @@ obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o
> obj-$(CONFIG_STX104) += stx104.o
> obj-$(CONFIG_STM32_ADC_CORE) += stm32-adc-core.o
> obj-$(CONFIG_STM32_ADC) += stm32-adc.o
> +obj-$(CONFIG_STM32_DFSDM_ADC) += stm32-dfsdm-adc.o
> +obj-$(CONFIG_STM32_DFSDM_CORE) += stm32-dfsdm-core.o
> obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o
> obj-$(CONFIG_TI_ADC0832) += ti-adc0832.o
> obj-$(CONFIG_TI_ADC12138) += ti-adc12138.o
> diff --git a/drivers/iio/adc/stm32-dfsdm-adc.c b/drivers/iio/adc/stm32-dfsdm-adc.c
> new file mode 100644
> index 0000000..ebcb3b4
> --- /dev/null
> +++ b/drivers/iio/adc/stm32-dfsdm-adc.c
> @@ -0,0 +1,419 @@
> +/*
> + * This file is the ADC part of of the STM32 DFSDM driver
> + *
> + * Copyright (C) 2017, STMicroelectronics - All Rights Reserved
> + * Author: Arnaud Pouliquen <arnaud.pouliquen at st.com>.
> + *
> + * License type: GPLv2
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms of the GNU General Public License version 2 as published by
> + * the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful, but
> + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
> + * or FITNESS FOR A PARTICULAR PURPOSE.
> + * See the GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License along with
> + * this program. If not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#include <linux/interrupt.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/regmap.h>
> +#include <linux/slab.h>
> +
> +#include <linux/iio/hw_consumer.h>
> +#include <linux/iio/iio.h>
> +#include <linux/iio/sysfs.h>
> +
> +#include "stm32-dfsdm.h"
> +
> +#define DFSDM_TIMEOUT_US 100000
> +#define DFSDM_TIMEOUT (msecs_to_jiffies(DFSDM_TIMEOUT_US / 1000))
> +
> +struct stm32_dfsdm_adc {
> + struct stm32_dfsdm *dfsdm;
> + unsigned int fl_id;
> + unsigned int ch_id;
> +
> + unsigned int oversamp;
> +
> + struct completion completion;
> +
> + u32 *buffer;
> +
> + /* Hardware consumer structure for Front End IIO */
> + struct iio_hw_consumer *hwc;
> +};
> +
> +static int stm32_dfsdm_start_conv(struct stm32_dfsdm_adc *adc)
> +{
> + int ret;
> +
> + ret = stm32_dfsdm_start_dfsdm(adc->dfsdm);
> + if (ret < 0)
> + return ret;
> +
> + ret = stm32_dfsdm_start_channel(adc->dfsdm, adc->ch_id);
> + if (ret < 0)
> + goto stop_dfsdm;
> +
> + ret = stm32_dfsdm_filter_configure(adc->dfsdm, adc->fl_id, adc->ch_id);
> + if (ret < 0)
> + goto stop_channels;
> +
> + ret = stm32_dfsdm_start_filter(adc->dfsdm, adc->fl_id);
> + if (ret < 0)
> + goto stop_channels;
> +
> + return 0;
> +
> +stop_channels:
> + stm32_dfsdm_stop_channel(adc->dfsdm, adc->ch_id);
> +stop_dfsdm:
> + stm32_dfsdm_stop_dfsdm(adc->dfsdm);
> +
> + return ret;
> +}
> +
> +static void stm32_dfsdm_stop_conv(struct stm32_dfsdm_adc *adc)
> +{
> + stm32_dfsdm_stop_filter(adc->dfsdm, adc->fl_id);
> +
> + stm32_dfsdm_stop_channel(adc->dfsdm, adc->ch_id);
> +
> + stm32_dfsdm_stop_dfsdm(adc->dfsdm);
> +}
> +
> +static int stm32_dfsdm_single_conv(struct iio_dev *indio_dev,
> + const struct iio_chan_spec *chan, int *res)
> +{
> + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev);
> + long timeout;
> + int ret;
> +
> + reinit_completion(&adc->completion);
> +
> + adc->buffer = res;
> +
> + /* Unmask IRQ for regular conversion achievement*/
> + ret = regmap_update_bits(adc->dfsdm->regmap, DFSDM_CR2(adc->fl_id),
> + DFSDM_CR2_REOCIE_MASK, DFSDM_CR2_REOCIE(1));
> + if (ret < 0)
> + return ret;
> +
> + ret = stm32_dfsdm_start_conv(adc);
> + if (ret < 0)
> + return ret;
> +
> + timeout = wait_for_completion_interruptible_timeout(&adc->completion,
> + DFSDM_TIMEOUT);
blank line perhaps.
> + /* Mask IRQ for regular conversion achievement*/
> + regmap_update_bits(adc->dfsdm->regmap, DFSDM_CR2(adc->fl_id),
> + DFSDM_CR2_REOCIE_MASK, DFSDM_CR2_REOCIE(0));
> +
> + if (timeout == 0) {
> + dev_warn(&indio_dev->dev, "Conversion timed out!\n");
> + ret = -ETIMEDOUT;
> + } else if (timeout < 0) {
> + ret = timeout;
> + } else {
> + dev_dbg(&indio_dev->dev, "Converted val %#x\n", *res);
> + ret = IIO_VAL_INT;
> + }
> +
> + /* Mask IRQ for regular conversion achievement*/
> + regmap_update_bits(adc->dfsdm->regmap, DFSDM_CR2(adc->fl_id),
> + DFSDM_CR2_REOCIE_MASK, DFSDM_CR2_REOCIE(0));
> +
> + stm32_dfsdm_stop_conv(adc);
> +
> + return ret;
> +}
> +
> +static int stm32_dfsdm_write_raw(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan,
> + int val, int val2, long mask)
> +{
> + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev);
> + struct stm32_dfsdm_filter *fl = &adc->dfsdm->fl_list[adc->fl_id];
> + int ret = -EINVAL;
> +
> + if (mask == IIO_CHAN_INFO_OVERSAMPLING_RATIO) {
> + ret = stm32_dfsdm_set_osrs(fl, 0, val);
> + if (!ret)
> + adc->oversamp = val;
> + }
blank line here.
> + return ret;
> +}
> +
> +static int stm32_dfsdm_read_raw(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan, int *val,
> + int *val2, long mask)
> +{
> + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev);
> + int ret;
> +
> + switch (mask) {
> + case IIO_CHAN_INFO_RAW:
> + ret = iio_hw_consumer_enable(adc->hwc);
> + if (ret < 0) {
> + dev_err(&indio_dev->dev,
> + "%s: IIO enable failed (channel %d)\n",
> + __func__, chan->channel);
> + return ret;
> + }
> + ret = stm32_dfsdm_single_conv(indio_dev, chan, val);
> + if (ret < 0) {
> + dev_err(&indio_dev->dev,
> + "%s: Conversion failed (channel %d)\n",
> + __func__, chan->channel);
> + return ret;
> + }
> +
> + iio_hw_consumer_disable(adc->hwc);
> +
> + return IIO_VAL_INT;
> +
> + case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
> + *val = adc->oversamp;
> +
> + return IIO_VAL_INT;
> + }
> +
> + return -EINVAL;
> +}
> +
> +static const struct iio_info stm32_dfsdm_info_adc = {
> + .read_raw = stm32_dfsdm_read_raw,
> + .write_raw = stm32_dfsdm_write_raw,
> + .driver_module = THIS_MODULE,
> +};
> +
> +static irqreturn_t stm32_dfsdm_irq(int irq, void *arg)
> +{
> + struct stm32_dfsdm_adc *adc = arg;
> + struct regmap *regmap = adc->dfsdm->regmap;
> + unsigned int status;
> +
> + regmap_read(regmap, DFSDM_ISR(adc->fl_id), &status);
> +
> + if (status & DFSDM_ISR_REOCF_MASK) {
> + /* read the data register clean the IRQ status */
> + regmap_read(regmap, DFSDM_RDATAR(adc->fl_id), adc->buffer);
> + complete(&adc->completion);
> + }
> + if (status & DFSDM_ISR_ROVRF_MASK) {
What's this one? Might want a comment given it's an irq you basically eat.
> + regmap_update_bits(regmap, DFSDM_ICR(adc->fl_id),
> + DFSDM_ICR_CLRROVRF_MASK,
> + DFSDM_ICR_CLRROVRF_MASK);
> + }
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int stm32_dfsdm_postenable(struct iio_dev *indio_dev)
> +{
> + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev);
> +
> + return stm32_dfsdm_start_conv(adc);
> +}
> +
> +static int stm32_dfsdm_predisable(struct iio_dev *indio_dev)
> +{
> + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev);
> +
> + stm32_dfsdm_stop_conv(adc);
blank line.
> + return 0;
> +}
> +
> +static const struct iio_buffer_setup_ops stm32_dfsdm_buffer_setup_ops = {
> + .postenable = &stm32_dfsdm_postenable,
> + .predisable = &stm32_dfsdm_predisable,
> +};
> +
> +static int stm32_dfsdm_adc_chan_init_one(struct iio_dev *indio_dev,
> + struct iio_chan_spec *chan,
> + int ch_idx)
> +{
> + struct iio_chan_spec *ch = &chan[ch_idx];
> + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev);
> + int ret;
> +
> + ret = stm32_dfsdm_channel_parse_of(adc->dfsdm, indio_dev, chan, ch_idx);
> +
> + ch->type = IIO_VOLTAGE;
> + ch->indexed = 1;
> + ch->scan_index = ch_idx;
> +
> + /*
> + * IIO_CHAN_INFO_RAW: used to compute regular conversion
> + * IIO_CHAN_INFO_OVERSAMPLING_RATIO: used to set oversampling
> + */
> + ch->info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
> + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO);
> +
> + ch->scan_type.sign = 'u';
> + ch->scan_type.realbits = 24;
> + ch->scan_type.storagebits = 32;
> + adc->ch_id = ch->channel;
> +
> + return stm32_dfsdm_chan_configure(adc->dfsdm,
> + &adc->dfsdm->ch_list[ch->channel]);
> +}
> +
> +static int stm32_dfsdm_adc_chan_init(struct iio_dev *indio_dev)
> +{
> + struct iio_chan_spec *channels;
> + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev);
> + unsigned int num_ch;
> + int ret, chan_idx;
> +
> + num_ch = of_property_count_u32_elems(indio_dev->dev.of_node,
> + "st,adc-channels");
> + if (num_ch < 0 || num_ch >= adc->dfsdm->num_chs) {
> + dev_err(&indio_dev->dev, "Bad st,adc-channels?\n");
> + return num_ch < 0 ? num_ch : -EINVAL;
> + }
> +
> + /*
> + * Number of channel per filter is temporary limited to 1.
> + * Restriction should be cleaned with scan mode
> + */
> + if (num_ch > 1) {
> + dev_err(&indio_dev->dev, "Multi channel not yet supported\n");
> + return -EINVAL;
> + }
> +
> + /* Bind to SD modulator IIO device */
> + adc->hwc = iio_hw_consumer_alloc(&indio_dev->dev);
> + if (IS_ERR(adc->hwc))
> + return -EPROBE_DEFER;
> +
> + channels = devm_kcalloc(&indio_dev->dev, num_ch, sizeof(*channels),
> + GFP_KERNEL);
> + if (!channels)
> + return -ENOMEM;
> +
> + for (chan_idx = 0; chan_idx < num_ch; chan_idx++) {
> + ret = stm32_dfsdm_adc_chan_init_one(indio_dev, channels,
> + chan_idx);
> + if (ret < 0)
> + goto free_hwc;
> + }
> +
> + indio_dev->num_channels = num_ch;
> + indio_dev->channels = channels;
> +
> + return 0;
> +
> +free_hwc:
> + iio_hw_consumer_free(adc->hwc);
Given you have to free this in the error path, I would imagine you will
need a free somewhere in the main remove path? Or just create a devm
version of iio_hw_consumer_alloc. It will be useful in the long run.
> + return ret;
> +}
> +
> +static const struct of_device_id stm32_dfsdm_adc_match[] = {
> + { .compatible = "st,stm32-dfsdm-adc"},
> + {}
> +};
> +
> +static int stm32_dfsdm_adc_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct stm32_dfsdm_adc *adc;
> + struct device_node *np = dev->of_node;
> + struct iio_dev *iio;
> + char *name;
> + int ret, irq, val;
> +
> + iio = devm_iio_device_alloc(dev, sizeof(*adc));
> + if (IS_ERR(iio)) {
> + dev_err(dev, "%s: Failed to allocate IIO\n", __func__);
> + return PTR_ERR(iio);
> + }
> +
> + adc = iio_priv(iio);
> + if (IS_ERR(adc)) {
> + dev_err(dev, "%s: Failed to allocate ADC\n", __func__);
> + return PTR_ERR(adc);
> + }
> + adc->dfsdm = dev_get_drvdata(dev->parent);
> +
> + iio->dev.parent = dev;
> + iio->dev.of_node = np;
> + iio->info = &stm32_dfsdm_info_adc;
> + iio->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_SOFTWARE;
> +
> + platform_set_drvdata(pdev, adc);
> +
> + ret = of_property_read_u32(dev->of_node, "reg", &adc->fl_id);
> + if (ret != 0) {
> + dev_err(dev, "Missing reg property\n");
> + return -EINVAL;
> + }
> +
> + name = kzalloc(sizeof("dfsdm-adc0"), GFP_KERNEL);
not freed. Maybe devm_kzalloc
> + if (!name)
> + return -ENOMEM;
> + snprintf(name, sizeof("dfsdm-adc0"), "dfsdm-adc%d", adc->fl_id);
> + iio->name = name;
> +
> + /*
> + * In a first step IRQs generated for channels are not treated.
> + * So IRQ associated to filter instance 0 is dedicated to the Filter 0.
> + */
> + irq = platform_get_irq(pdev, 0);
> + ret = devm_request_irq(dev, irq, stm32_dfsdm_irq,
> + 0, pdev->name, adc);
> + if (ret < 0) {
> + dev_err(dev, "Failed to request IRQ\n");
> + return ret;
> + }
> +
> + ret = of_property_read_u32(dev->of_node, "st,filter-order", &val);
> + if (ret < 0) {
> + dev_err(dev, "Failed to set filter order\n");
> + return ret;
> + }
> + adc->dfsdm->fl_list[adc->fl_id].ford = val;
> +
> + ret = of_property_read_u32(dev->of_node, "st,filter0-sync", &val);
> + if (!ret)
> + adc->dfsdm->fl_list[adc->fl_id].sync_mode = val;
> +
> + ret = stm32_dfsdm_adc_chan_init(iio);
> + if (ret < 0)
> + return ret;
> +
> + init_completion(&adc->completion);
> +
> + return iio_device_register(iio);
> +}
> +
> +static int stm32_dfsdm_adc_remove(struct platform_device *pdev)
> +{
> + struct stm32_dfsdm_adc *adc = platform_get_drvdata(pdev);
> + struct iio_dev *iio = iio_priv_to_dev(adc);
> +
> + iio_device_unregister(iio);
If all you have is this in remove, you can probably get away with
devm_iio_device_register and get rid of the remove entirely.
> +
> + return 0;
> +}
> +
> +static struct platform_driver stm32_dfsdm_adc_driver = {
> + .driver = {
> + .name = "stm32-dfsdm-adc",
> + .of_match_table = stm32_dfsdm_adc_match,
> + },
> + .probe = stm32_dfsdm_adc_probe,
> + .remove = stm32_dfsdm_adc_remove,
> +};
> +module_platform_driver(stm32_dfsdm_adc_driver);
> +
> +MODULE_DESCRIPTION("STM32 sigma delta ADC");
> +MODULE_AUTHOR("Arnaud Pouliquen <arnaud.pouliquen at st.com>");
> +MODULE_LICENSE("GPL v2");
> diff --git a/drivers/iio/adc/stm32-dfsdm-core.c b/drivers/iio/adc/stm32-dfsdm-core.c
> new file mode 100644
> index 0000000..488e456
> --- /dev/null
> +++ b/drivers/iio/adc/stm32-dfsdm-core.c
> @@ -0,0 +1,658 @@
> +/*
> + * This file is part the core part STM32 DFSDM driver
> + *
> + * Copyright (C) 2017, STMicroelectronics - All Rights Reserved
> + * Author(s): Arnaud Pouliquen <arnaud.pouliquen at st.com> for STMicroelectronics.
> + *
> + * License terms: GPL V2.0.
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms of the GNU General Public License version 2 as published by
> + * the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful, but
> + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
> + * details.
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/interrupt.h>
> +#include <linux/module.h>
> +#include <linux/of_device.h>
> +#include <linux/regmap.h>
> +#include <linux/slab.h>
> +
> +#include <linux/iio/trigger.h>
> +#include <linux/iio/sysfs.h>
> +
> +#include "stm32-dfsdm.h"
> +
> +struct stm32_dfsdm_dev_data {
> + unsigned int num_filters;
> + unsigned int num_channels;
> + const struct regmap_config *regmap_cfg;
> +};
> +
> +#define STM32H7_DFSDM_NUM_FILTERS 4
> +#define STM32H7_DFSDM_NUM_CHANNELS 8
> +
> +#define DFSDM_MAX_INT_OVERSAMPLING 256
> +
> +#define DFSDM_MAX_FL_OVERSAMPLING 1024
> +
> +#define DFSDM_MAX_RES BIT(31)
> +#define DFSDM_DATA_RES BIT(23)
> +
> +static bool stm32_dfsdm_volatile_reg(struct device *dev, unsigned int reg)
> +{
> + if (reg < DFSDM_FILTER_BASE_ADR)
> + return false;
> +
> + /*
> + * Mask is done on register to avoid to list registers of all them
> + * filter instances.
> + */
> + switch (reg & DFSDM_FILTER_REG_MASK) {
> + case DFSDM_CR1(0) & DFSDM_FILTER_REG_MASK:
> + case DFSDM_ISR(0) & DFSDM_FILTER_REG_MASK:
> + case DFSDM_JDATAR(0) & DFSDM_FILTER_REG_MASK:
> + case DFSDM_RDATAR(0) & DFSDM_FILTER_REG_MASK:
> + return true;
> + }
> +
> + return false;
> +}
> +
> +static const struct regmap_config stm32h7_dfsdm_regmap_cfg = {
> + .reg_bits = 32,
> + .val_bits = 32,
> + .reg_stride = sizeof(u32),
> + .max_register = 0x2B8,
> + .volatile_reg = stm32_dfsdm_volatile_reg,
> + .fast_io = true,
> +};
> +
> +static const struct stm32_dfsdm_dev_data stm32h7_dfsdm_data = {
> + .num_filters = STM32H7_DFSDM_NUM_FILTERS,
> + .num_channels = STM32H7_DFSDM_NUM_CHANNELS,
> + .regmap_cfg = &stm32h7_dfsdm_regmap_cfg,
> +};
> +
> +struct dfsdm_priv {
> + struct platform_device *pdev; /* platform device*/
> +
> + struct stm32_dfsdm dfsdm; /* common data exported for all instances */
> +
> + unsigned int spi_clk_out_div; /* SPI clkout divider value */
> + atomic_t n_active_ch; /* number of current active channels */
> +
> + /* Clock */
> + struct clk *clk; /* DFSDM clock */
> + struct clk *aclk; /* audio clock */
> +};
> +
> +/**
> + * stm32_dfsdm_set_osrs - compute filter parameters.
Naming would suggest it's more specific than this.
Setting over sampling ratios?
> + *
> + * Enable interface if n_active_ch is not null.
> + * @dfsdm: Handle used to retrieve dfsdm context.
> + * @fast: Fast mode enabled or disabled
> + * @oversamp: Expected oversampling between filtered sample and SD input stream
> + */
> +int stm32_dfsdm_set_osrs(struct stm32_dfsdm_filter *fl, unsigned int fast,
> + unsigned int oversamp)
> +{
> + unsigned int i, d, fosr, iosr;
> + u64 res;
> + s64 delta;
> + unsigned int m = 1; /* multiplication factor */
> + unsigned int p = fl->ford; /* filter order (ford) */
> +
> + pr_debug("%s: Requested oversampling: %d\n", __func__, oversamp);
> + /*
> + * This function tries to compute filter oversampling and integrator
> + * oversampling, base on oversampling ratio requested by user.
> + *
> + * Decimation d depends on the filter order and the oversampling ratios.
> + * ford: filter order
> + * fosr: filter over sampling ratio
> + * iosr: integrator over sampling ratio
> + */
> + if (fl->ford == DFSDM_FASTSINC_ORDER) {
> + m = 2;
> + p = 2;
> + }
> +
> + /*
> + * Looks for filter and integrator oversampling ratios which allows
> + * to reach 24 bits data output resolution.
> + * Leave at once if exact resolution if reached.
> + * Otherwise the higher resolution below 32 bits is kept.
> + */
> + for (fosr = 1; fosr <= DFSDM_MAX_FL_OVERSAMPLING; fosr++) {
> + for (iosr = 1; iosr <= DFSDM_MAX_INT_OVERSAMPLING; iosr++) {
> + if (fast)
> + d = fosr * iosr;
> + else if (fl->ford == DFSDM_FASTSINC_ORDER)
> + d = fosr * (iosr + 3) + 2;
> + else
> + d = fosr * (iosr - 1 + p) + p;
> +
> + if (d > oversamp)
> + break;
> + else if (d != oversamp)
> + continue;
> + /*
> + * Check resolution (limited to signed 32 bits)
> + * res <= 2^31
> + * Sincx filters:
> + * res = m * fosr^p x iosr (with m=1, p=ford)
> + * FastSinc filter
> + * res = m * fosr^p x iosr (with m=2, p=2)
> + */
> + res = fosr;
> + for (i = p - 1; i > 0; i--) {
> + res = res * (u64)fosr;
> + if (res > DFSDM_MAX_RES)
> + break;
> + }
> + if (res > DFSDM_MAX_RES)
> + continue;
> + res = res * (u64)m * (u64)iosr;
> + if (res > DFSDM_MAX_RES)
> + continue;
> +
> + delta = res - DFSDM_DATA_RES;
> +
> + if (res >= fl->res) {
> + fl->res = res;
> + fl->fosr = fosr;
> + fl->iosr = iosr;
> + fl->fast = fast;
> + pr_debug("%s: fosr = %d, iosr = %d\n",
> + __func__, fl->fosr, fl->iosr);
> + }
> +
> + if (!delta)
> + return 0;
> + }
> + }
> +
> + if (!fl->fosr)
> + return -EINVAL;
> +
> + return 0;
> +}
> +
> +/**
> + * stm32_dfsdm_start_dfsdm - start global dfsdm IP interface.
> + *
> + * Enable interface if n_active_ch is not null.
> + * @dfsdm: Handle used to retrieve dfsdm context.
> + */
> +int stm32_dfsdm_start_dfsdm(struct stm32_dfsdm *dfsdm)
> +{
> + struct dfsdm_priv *priv = container_of(dfsdm, struct dfsdm_priv, dfsdm);
> + struct device *dev = &priv->pdev->dev;
> + unsigned int clk_div = priv->spi_clk_out_div;
> + int ret;
> +
> + if (atomic_inc_return(&priv->n_active_ch) == 1) {
> + /* Enable clocks */
> + ret = clk_prepare_enable(priv->clk);
> + if (ret < 0) {
> + dev_err(dev, "Failed to start clock\n");
> + return ret;
> + }
> + if (priv->aclk) {
> + ret = clk_prepare_enable(priv->aclk);
> + if (ret < 0) {
> + dev_err(dev, "Failed to start audio clock\n");
> + goto disable_clk;
> + }
> + }
> +
> + /* Output the SPI CLKOUT (if clk_div == 0 clock if OFF) */
> + ret = regmap_update_bits(dfsdm->regmap, DFSDM_CHCFGR1(0),
> + DFSDM_CHCFGR1_CKOUTDIV_MASK,
> + DFSDM_CHCFGR1_CKOUTDIV(clk_div));
> + if (ret < 0)
> + goto disable_aclk;
> +
> + /* Global enable of DFSDM interface */
> + ret = regmap_update_bits(dfsdm->regmap, DFSDM_CHCFGR1(0),
> + DFSDM_CHCFGR1_DFSDMEN_MASK,
> + DFSDM_CHCFGR1_DFSDMEN(1));
> + if (ret < 0)
> + goto disable_aclk;
> + }
> +
> + dev_dbg(dev, "%s: n_active_ch %d\n", __func__,
> + atomic_read(&priv->n_active_ch));
> +
> + return 0;
> +
> +disable_aclk:
> + clk_disable_unprepare(priv->aclk);
> +disable_clk:
> + clk_disable_unprepare(priv->clk);
> +
> + return ret;
> +}
> +
> +/**
> + * stm32_dfsdm_stop_dfsdm - stop global DFSDM IP interface.
> + *
> + * Disable interface if n_active_ch is null
> + * @dfsdm: Handle used to retrieve dfsdm context.
> + */
> +int stm32_dfsdm_stop_dfsdm(struct stm32_dfsdm *dfsdm)
> +{
> + struct dfsdm_priv *priv = container_of(dfsdm, struct dfsdm_priv, dfsdm);
> + int ret;
> +
> + if (atomic_dec_and_test(&priv->n_active_ch)) {
> + /* Global disable of DFSDM interface */
> + ret = regmap_update_bits(dfsdm->regmap, DFSDM_CHCFGR1(0),
> + DFSDM_CHCFGR1_DFSDMEN_MASK,
> + DFSDM_CHCFGR1_DFSDMEN(0));
> + if (ret < 0)
> + return ret;
> +
> + /* Stop SPI CLKOUT */
> + ret = regmap_update_bits(dfsdm->regmap, DFSDM_CHCFGR1(0),
> + DFSDM_CHCFGR1_CKOUTDIV_MASK,
> + DFSDM_CHCFGR1_CKOUTDIV(0));
> + if (ret < 0)
> + return ret;
> +
> + /* Disable clocks */
> + clk_disable_unprepare(priv->clk);
> + if (priv->aclk)
> + clk_disable_unprepare(priv->aclk);
> + }
> + dev_dbg(&priv->pdev->dev, "%s: n_active_ch %d\n", __func__,
> + atomic_read(&priv->n_active_ch));
> +
> + return 0;
> +}
> +
> +/**
> + * stm32_dfsdm_start_channel
> + * Start DFSDM IP channels and associated interface.
> + *
> + * @dfsdm: Handle used to retrieve dfsdm context.
> + * @ch_id: Channel index.
> + */
> +int stm32_dfsdm_start_channel(struct stm32_dfsdm *dfsdm, unsigned int ch_id)
> +{
> + return regmap_update_bits(dfsdm->regmap, DFSDM_CHCFGR1(ch_id),
> + DFSDM_CHCFGR1_CHEN_MASK,
> + DFSDM_CHCFGR1_CHEN(1));
> +}
> +
> +/**
> + * stm32_dfsdm_stop_channel
> + * Stop DFSDM IP channels and associated interface.
> + *
> + * @dfsdm: Handle used to retrieve dfsdm context.
> + * @ch_id: Channel index.
> + */
> +void stm32_dfsdm_stop_channel(struct stm32_dfsdm *dfsdm, unsigned int ch_id)
> +{
> + regmap_update_bits(dfsdm->regmap, DFSDM_CHCFGR1(ch_id),
> + DFSDM_CHCFGR1_CHEN_MASK,
> + DFSDM_CHCFGR1_CHEN(0));
> +}
> +
> +/**
> + * stm32_dfsdm_chan_configure
> + * Configure DFSDM IP channels and associated interface.
> + *
> + * @dfsdm: Handle used to retrieve dfsdm context.
> + * @ch_id: channel index.
> + */
> +int stm32_dfsdm_chan_configure(struct stm32_dfsdm *dfsdm,
> + struct stm32_dfsdm_channel *ch)
> +{
> + unsigned int id = ch->id;
> + struct regmap *regmap = dfsdm->regmap;
> + int ret;
> +
> + ret = regmap_update_bits(regmap, DFSDM_CHCFGR1(id),
> + DFSDM_CHCFGR1_SITP_MASK,
> + DFSDM_CHCFGR1_SITP(ch->type));
> + if (ret < 0)
> + return ret;
Blank line here and in similar places makes it easier for my
eyes to parse at least...
I'd also like to see some docs in here, not all of these
are self explanatory.
> + ret = regmap_update_bits(regmap, DFSDM_CHCFGR1(id),
> + DFSDM_CHCFGR1_SPICKSEL_MASK,
> + DFSDM_CHCFGR1_SPICKSEL(ch->src));
> + if (ret < 0)
> + return ret;
> + return regmap_update_bits(regmap, DFSDM_CHCFGR1(id),
> + DFSDM_CHCFGR1_CHINSEL_MASK,
> + DFSDM_CHCFGR1_CHINSEL(ch->alt_si));
> +}
> +
> +/**
> + * stm32_dfsdm_start_filter - Start DFSDM IP filter conversion.
> + *
> + * @dfsdm: Handle used to retrieve dfsdm context.
> + * @fl_id: Filter index.
> + */
> +int stm32_dfsdm_start_filter(struct stm32_dfsdm *dfsdm, unsigned int fl_id)
> +{
> + int ret;
> +
> + /* Enable filter */
> + ret = regmap_update_bits(dfsdm->regmap, DFSDM_CR1(fl_id),
> + DFSDM_CR1_DFEN_MASK, DFSDM_CR1_DFEN(1));
> + if (ret < 0)
> + return ret;
> +
> + /* Start conversion */
> + return regmap_update_bits(dfsdm->regmap, DFSDM_CR1(fl_id),
> + DFSDM_CR1_RSWSTART_MASK,
> + DFSDM_CR1_RSWSTART(1));
> +}
> +
> +/**
> + * stm32_dfsdm_stop_filter - Stop DFSDM IP filter conversion.
> + *
> + * @dfsdm: Handle used to retrieve dfsdm context.
> + * @fl_id: Filter index.
> + */
> +void stm32_dfsdm_stop_filter(struct stm32_dfsdm *dfsdm, unsigned int fl_id)
> +{
> + /* Mask IRQ for regular conversion achievement*/
> + regmap_update_bits(dfsdm->regmap, DFSDM_CR2(fl_id),
> + DFSDM_CR2_REOCIE_MASK, DFSDM_CR2_REOCIE(0));
> + /* Disable conversion */
> + regmap_update_bits(dfsdm->regmap, DFSDM_CR1(fl_id),
> + DFSDM_CR1_DFEN_MASK, DFSDM_CR1_DFEN(0));
> +}
> +
> +/**
> + * stm32_dfsdm_filter_configure - Configure DFSDM IP filter and associate it
> + * to channel.
> + *
> + * @dfsdm: Handle used to retrieve dfsdm context.
> + * @fl_id: channel index.
> + * @fl_id: Filter index.
> + */
> +int stm32_dfsdm_filter_configure(struct stm32_dfsdm *dfsdm, unsigned int fl_id,
> + unsigned int ch_id)
> +{
> + struct regmap *regmap = dfsdm->regmap;
> + struct stm32_dfsdm_filter *fl = &dfsdm->fl_list[fl_id];
> + int ret;
> +
> + /* Average integrator oversampling */
> + ret = regmap_update_bits(regmap, DFSDM_FCR(fl_id), DFSDM_FCR_IOSR_MASK,
> + DFSDM_FCR_IOSR(fl->iosr));
> +
> + /* Filter order and Oversampling */
Please handle each error properly as it happens rather than mudling onwards.
If there is reason for this odd construction, then document it clearly.
> + if (!ret)
> + ret = regmap_update_bits(regmap, DFSDM_FCR(fl_id),
> + DFSDM_FCR_FOSR_MASK,
> + DFSDM_FCR_FOSR(fl->fosr));
> +
> + if (!ret)
> + ret = regmap_update_bits(regmap, DFSDM_FCR(fl_id),
> + DFSDM_FCR_FORD_MASK,
> + DFSDM_FCR_FORD(fl->ford));
> +
> + /* If only one channel no scan mode supported for the moment */
> + ret = regmap_update_bits(regmap, DFSDM_CR1(fl_id),
> + DFSDM_CR1_RCH_MASK,
> + DFSDM_CR1_RCH(ch_id));
> +
> + return regmap_update_bits(regmap, DFSDM_CR1(fl_id),
> + DFSDM_CR1_RSYNC_MASK,
> + DFSDM_CR1_RSYNC(fl->sync_mode));
> +}
> +
> +static const struct iio_trigger_ops dfsdm_trigger_ops = {
> + .owner = THIS_MODULE,
> +};
> +
> +static int stm32_dfsdm_setup_spi_trigger(struct platform_device *pdev,
> + struct stm32_dfsdm *dfsdm)
> +{
> + /*
> + * To be able to use buffer consumer interface a trigger is needed.
> + * As conversion are trigged by PDM samples from SPI bus, that makes
> + * sense to define the serial interface ( SPI or manchester) as
> + * trigger source.
It's not actually the case that you have to have a triggrer.
There are plenty of drivers (particularly ones with hardware buffering)
where there is no trigger envolved. That's not to say it doesn't make sense
here.
I'm not entirely sure yet if it's needed... Given it has no ops, I doubt it
somewhat...
> + */
> +
> + struct iio_trigger *trig;
> + int ret;
> +
> + trig = devm_iio_trigger_alloc(&pdev->dev, DFSDM_SPI_TRIGGER_NAME);
> + if (!trig)
> + return -ENOMEM;
> +
> + trig->dev.parent = pdev->dev.parent;
> + trig->ops = &dfsdm_trigger_ops;
> +
> + iio_trigger_set_drvdata(trig, dfsdm);
> +
> + ret = devm_iio_trigger_register(&pdev->dev, trig);
> + if (ret)
> + return ret;
Just return ret in all cases.
> +
> + return 0;
> +}
> +
> +int stm32_dfsdm_channel_parse_of(struct stm32_dfsdm *dfsdm,
> + struct iio_dev *indio_dev,
> + struct iio_chan_spec *chan, int chan_idx)
> +{
> + struct iio_chan_spec *ch = &chan[chan_idx];
> + struct stm32_dfsdm_channel *df_ch;
> + const char *of_str;
> + int ret, val;
> +
> + ret = of_property_read_u32_index(indio_dev->dev.of_node,
> + "st,adc-channels", chan_idx,
> + &ch->channel);
> + if (ret < 0) {
> + dev_err(&indio_dev->dev,
> + " Error parsing 'st,adc-channels' for idx %d\n",
> + chan_idx);
> + return ret;
> + }
> +
> + ret = of_property_read_string_index(indio_dev->dev.of_node,
> + "st,adc-channel-names", chan_idx,
> + &ch->datasheet_name);
> + if (ret < 0) {
> + dev_err(&indio_dev->dev,
> + " Error parsing 'st,adc-channel-names' for idx %d\n",
> + chan_idx);
> + return ret;
> + }
> +
> + df_ch = &dfsdm->ch_list[ch->channel];
Extra space on the line above.
> + df_ch->id = ch->channel;
> + ret = of_property_read_string_index(indio_dev->dev.of_node,
> + "st,adc-channel-types", chan_idx,
> + &of_str);
> + val = stm32_dfsdm_str2val(of_str, stm32_dfsdm_chan_type);
> + if (ret < 0 || val < 0)
> + df_ch->type = 0;
> + else
> + df_ch->type = val;
> +
> + ret = of_property_read_string_index(indio_dev->dev.of_node,
> + "st,adc-channel-clk-src", chan_idx,
> + &of_str);
> + val = stm32_dfsdm_str2val(of_str, stm32_dfsdm_chan_src);
> + if (ret < 0 || val < 0)
> + df_ch->src = 0;
> + else
> + df_ch->src = val;
> +
> + ret = of_property_read_u32_index(indio_dev->dev.of_node,
> + "st,adc-alt-channel", chan_idx,
> + &df_ch->alt_si);
> + if (ret < 0)
> + df_ch->alt_si = 0;
> +
> + return 0;
> +}
> +
> +static int stm32_dfsdm_parse_of(struct platform_device *pdev,
> + struct dfsdm_priv *priv)
> +{
> + struct device_node *node = pdev->dev.of_node;
> + struct resource *res;
> + unsigned long clk_freq;
> + unsigned int spi_freq, rem;
> + int ret;
> +
> + if (!node)
> + return -EINVAL;
> +
> + /* Get resources */
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + if (!res) {
> + dev_err(&pdev->dev, "Failed to get memory resource\n");
> + return -ENODEV;
> + }
> + priv->dfsdm.phys_base = res->start;
> + priv->dfsdm.base = devm_ioremap_resource(&pdev->dev, res);
> +
> + /* Source clock */
> + priv->clk = devm_clk_get(&pdev->dev, "dfsdm");
> + if (IS_ERR(priv->clk)) {
> + dev_err(&pdev->dev, "No stm32_dfsdm_clk clock found\n");
> + return -EINVAL;
> + }
> +
> + priv->aclk = devm_clk_get(&pdev->dev, "audio");
> + if (IS_ERR(priv->aclk))
> + priv->aclk = NULL;
> +
> + if (priv->aclk)
> + clk_freq = clk_get_rate(priv->aclk);
> + else
> + clk_freq = clk_get_rate(priv->clk);
> +
> + /* SPI clock freq */
> + ret = of_property_read_u32(pdev->dev.of_node, "spi-max-frequency",
> + &spi_freq);
> + if (ret < 0) {
> + dev_err(&pdev->dev, "Failed to get spi-max-frequency\n");
> + return ret;
> + }
> +
> + priv->spi_clk_out_div = div_u64_rem(clk_freq, spi_freq, &rem) - 1;
> + priv->dfsdm.spi_master_freq = spi_freq;
> +
> + if (rem) {
> + dev_warn(&pdev->dev, "SPI clock not accurate\n");
> + dev_warn(&pdev->dev, "%ld = %d * %d + %d\n",
> + clk_freq, spi_freq, priv->spi_clk_out_div + 1, rem);
> + }
> +
> + return 0;
> +};
> +
> +static const struct of_device_id stm32_dfsdm_of_match[] = {
> + {
> + .compatible = "st,stm32h7-dfsdm",
> + .data = &stm32h7_dfsdm_data,
> + },
> + {}
> +};
> +MODULE_DEVICE_TABLE(of, stm32_dfsdm_of_match);
> +
> +static int stm32_dfsdm_remove(struct platform_device *pdev)
> +{
> + of_platform_depopulate(&pdev->dev);
> +
> + return 0;
> +}
> +
> +static int stm32_dfsdm_probe(struct platform_device *pdev)
> +{
> + struct dfsdm_priv *priv;
> + struct device_node *pnode = pdev->dev.of_node;
> + const struct of_device_id *of_id;
> + const struct stm32_dfsdm_dev_data *dev_data;
> + struct stm32_dfsdm *dfsdm;
> + int ret, i;
> +
> + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
> + if (!priv)
> + return -ENOMEM;
> +
> + priv->pdev = pdev;
> +
> + /* Populate data structure depending on compatibility */
> + of_id = of_match_node(stm32_dfsdm_of_match, pnode);
> + if (!of_id->data) {
> + dev_err(&pdev->dev, "Data associated to device is missing\n");
> + return -EINVAL;
> + }
> +
> + dev_data = (const struct stm32_dfsdm_dev_data *)of_id->data;
> + dfsdm = &priv->dfsdm;
> + dfsdm->fl_list = devm_kcalloc(&pdev->dev, dev_data->num_filters,
> + sizeof(*dfsdm->fl_list), GFP_KERNEL);
> + if (!dfsdm->fl_list)
> + return -ENOMEM;
> +
> + dfsdm->num_fls = dev_data->num_filters;
> + dfsdm->ch_list = devm_kcalloc(&pdev->dev, dev_data->num_channels,
> + sizeof(*dfsdm->ch_list),
> + GFP_KERNEL);
> + if (!dfsdm->ch_list)
> + return -ENOMEM;
> + dfsdm->num_chs = dev_data->num_channels;
> +
> + ret = stm32_dfsdm_parse_of(pdev, priv);
> + if (ret < 0)
> + return ret;
> +
> + dfsdm->regmap = devm_regmap_init_mmio(&pdev->dev, dfsdm->base,
> + &stm32h7_dfsdm_regmap_cfg);
> + if (IS_ERR(dfsdm->regmap)) {
> + ret = PTR_ERR(dfsdm->regmap);
> + dev_err(&pdev->dev, "%s: Failed to allocate regmap: %d\n",
> + __func__, ret);
> + return ret;
> + }
> +
> + for (i = 0; i < STM32H7_DFSDM_NUM_FILTERS; i++) {
> + struct stm32_dfsdm_filter *fl = &dfsdm->fl_list[i];
> +
> + fl->id = i;
I'd like a comment on why this is needed...
> + }
> +
> + platform_set_drvdata(pdev, dfsdm);
> +
> + ret = stm32_dfsdm_setup_spi_trigger(pdev, dfsdm);
> + if (ret < 0)
> + return ret;
> +
> + return of_platform_populate(pnode, NULL, NULL, &pdev->dev);
> +}
> +
> +static struct platform_driver stm32_dfsdm_driver = {
> + .probe = stm32_dfsdm_probe,
> + .remove = stm32_dfsdm_remove,
> + .driver = {
> + .name = "stm32-dfsdm",
> + .of_match_table = stm32_dfsdm_of_match,
> + },
> +};
> +
> +module_platform_driver(stm32_dfsdm_driver);
> +
> +MODULE_AUTHOR("Arnaud Pouliquen <arnaud.pouliquen at st.com>");
> +MODULE_DESCRIPTION("STMicroelectronics STM32 dfsdm driver");
> +MODULE_LICENSE("GPL v2");
> diff --git a/drivers/iio/adc/stm32-dfsdm.h b/drivers/iio/adc/stm32-dfsdm.h
> new file mode 100644
> index 0000000..bb7d74f
> --- /dev/null
> +++ b/drivers/iio/adc/stm32-dfsdm.h
> @@ -0,0 +1,371 @@
> +/*
> + * This file is part of STM32 DFSDM driver
> + *
> + * Copyright (C) 2017, STMicroelectronics - All Rights Reserved
> + * Author(s): Arnaud Pouliquen <arnaud.pouliquen at st.com>.
> + *
> + * License terms: GPL V2.0.
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms of the GNU General Public License version 2 as published by
> + * the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful, but
> + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
> + * details.
> + */
> +#ifndef MDF_STM32_DFSDM__H
> +#define MDF_STM32_DFSDM__H
> +
> +#include <linux/bitfield.h>
> +
> +#include <linux/iio/iio.h>
> +/*
> + * STM32 DFSDM - global register map
> + * ________________________________________________________
> + * | Offset | Registers block |
> + * --------------------------------------------------------
> + * | 0x000 | CHANNEL 0 + COMMON CHANNEL FIELDS |
> + * --------------------------------------------------------
> + * | 0x020 | CHANNEL 1 |
> + * --------------------------------------------------------
> + * | ... | ..... |
> + * --------------------------------------------------------
> + * | 0x0E0 | CHANNEL 7 |
> + * --------------------------------------------------------
> + * | 0x100 | FILTER 0 + COMMON FILTER FIELDs |
> + * --------------------------------------------------------
> + * | 0x200 | FILTER 1 |
> + * --------------------------------------------------------
> + * | 0x300 | FILTER 2 |
> + * --------------------------------------------------------
> + * | 0x400 | FILTER 3 |
> + * --------------------------------------------------------
> + */
> +
> +/*
> + * Channels register definitions
> + */
> +#define DFSDM_CHCFGR1(y) ((y) * 0x20 + 0x00)
> +#define DFSDM_CHCFGR2(y) ((y) * 0x20 + 0x04)
> +#define DFSDM_AWSCDR(y) ((y) * 0x20 + 0x08)
> +#define DFSDM_CHWDATR(y) ((y) * 0x20 + 0x0C)
> +#define DFSDM_CHDATINR(y) ((y) * 0x20 + 0x10)
> +
> +/* CHCFGR1: Channel configuration register 1 */
> +#define DFSDM_CHCFGR1_SITP_MASK GENMASK(1, 0)
> +#define DFSDM_CHCFGR1_SITP(v) FIELD_PREP(DFSDM_CHCFGR1_SITP_MASK, v)
> +#define DFSDM_CHCFGR1_SPICKSEL_MASK GENMASK(3, 2)
> +#define DFSDM_CHCFGR1_SPICKSEL(v) FIELD_PREP(DFSDM_CHCFGR1_SPICKSEL_MASK, v)
> +#define DFSDM_CHCFGR1_SCDEN_MASK BIT(5)
> +#define DFSDM_CHCFGR1_SCDEN(v) FIELD_PREP(DFSDM_CHCFGR1_SCDEN_MASK, v)
> +#define DFSDM_CHCFGR1_CKABEN_MASK BIT(6)
> +#define DFSDM_CHCFGR1_CKABEN(v) FIELD_PREP(DFSDM_CHCFGR1_CKABEN_MASK, v)
> +#define DFSDM_CHCFGR1_CHEN_MASK BIT(7)
> +#define DFSDM_CHCFGR1_CHEN(v) FIELD_PREP(DFSDM_CHCFGR1_CHEN_MASK, v)
> +#define DFSDM_CHCFGR1_CHINSEL_MASK BIT(8)
> +#define DFSDM_CHCFGR1_CHINSEL(v) FIELD_PREP(DFSDM_CHCFGR1_CHINSEL_MASK, v)
> +#define DFSDM_CHCFGR1_DATMPX_MASK GENMASK(13, 12)
> +#define DFSDM_CHCFGR1_DATMPX(v) FIELD_PREP(DFSDM_CHCFGR1_DATMPX_MASK, v)
> +#define DFSDM_CHCFGR1_DATPACK_MASK GENMASK(15, 14)
> +#define DFSDM_CHCFGR1_DATPACK(v) FIELD_PREP(DFSDM_CHCFGR1_DATPACK_MASK, v)
> +#define DFSDM_CHCFGR1_CKOUTDIV_MASK GENMASK(23, 16)
> +#define DFSDM_CHCFGR1_CKOUTDIV(v) FIELD_PREP(DFSDM_CHCFGR1_CKOUTDIV_MASK, v)
> +#define DFSDM_CHCFGR1_CKOUTSRC_MASK BIT(30)
> +#define DFSDM_CHCFGR1_CKOUTSRC(v) FIELD_PREP(DFSDM_CHCFGR1_CKOUTSRC_MASK, v)
> +#define DFSDM_CHCFGR1_DFSDMEN_MASK BIT(31)
> +#define DFSDM_CHCFGR1_DFSDMEN(v) FIELD_PREP(DFSDM_CHCFGR1_DFSDMEN_MASK, v)
> +
> +/* CHCFGR2: Channel configuration register 2 */
> +#define DFSDM_CHCFGR2_DTRBS_MASK GENMASK(7, 3)
> +#define DFSDM_CHCFGR2_DTRBS(v) FIELD_PREP(DFSDM_CHCFGR2_DTRBS_MASK, v)
> +#define DFSDM_CHCFGR2_OFFSET_MASK GENMASK(31, 8)
> +#define DFSDM_CHCFGR2_OFFSET(v) FIELD_PREP(DFSDM_CHCFGR2_OFFSET_MASK, v)
> +
> +/* AWSCDR: Channel analog watchdog and short circuit detector */
> +#define DFSDM_AWSCDR_SCDT_MASK GENMASK(7, 0)
> +#define DFSDM_AWSCDR_SCDT(v) FIELD_PREP(DFSDM_AWSCDR_SCDT_MASK, v)
> +#define DFSDM_AWSCDR_BKSCD_MASK GENMASK(15, 12)
> +#define DFSDM_AWSCDR_BKSCD(v) FIELD_PREP(DFSDM_AWSCDR_BKSCD_MASK, v)
> +#define DFSDM_AWSCDR_AWFOSR_MASK GENMASK(20, 16)
> +#define DFSDM_AWSCDR_AWFOSR(v) FIELD_PREP(DFSDM_AWSCDR_AWFOSR_MASK, v)
> +#define DFSDM_AWSCDR_AWFORD_MASK GENMASK(23, 22)
> +#define DFSDM_AWSCDR_AWFORD(v) FIELD_PREP(DFSDM_AWSCDR_AWFORD_MASK, v)
> +
> +/*
> + * Filters register definitions
> + */
> +#define DFSDM_FILTER_BASE_ADR 0x100
> +#define DFSDM_FILTER_REG_MASK 0x7F
> +#define DFSDM_FILTER_X_BASE_ADR(x) ((x) * 0x80 + DFSDM_FILTER_BASE_ADR)
> +
> +#define DFSDM_CR1(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x00)
> +#define DFSDM_CR2(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x04)
> +#define DFSDM_ISR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x08)
> +#define DFSDM_ICR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x0C)
> +#define DFSDM_JCHGR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x10)
> +#define DFSDM_FCR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x14)
> +#define DFSDM_JDATAR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x18)
> +#define DFSDM_RDATAR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x1C)
> +#define DFSDM_AWHTR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x20)
> +#define DFSDM_AWLTR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x24)
> +#define DFSDM_AWSR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x28)
> +#define DFSDM_AWCFR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x2C)
> +#define DFSDM_EXMAX(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x30)
> +#define DFSDM_EXMIN(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x34)
> +#define DFSDM_CNVTIMR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x38)
> +
> +/* CR1 Control register 1 */
> +#define DFSDM_CR1_DFEN_MASK BIT(0)
> +#define DFSDM_CR1_DFEN(v) FIELD_PREP(DFSDM_CR1_DFEN_MASK, v)
> +#define DFSDM_CR1_JSWSTART_MASK BIT(1)
> +#define DFSDM_CR1_JSWSTART(v) FIELD_PREP(DFSDM_CR1_JSWSTART_MASK, v)
> +#define DFSDM_CR1_JSYNC_MASK BIT(3)
> +#define DFSDM_CR1_JSYNC(v) FIELD_PREP(DFSDM_CR1_JSYNC_MASK, v)
> +#define DFSDM_CR1_JSCAN_MASK BIT(4)
> +#define DFSDM_CR1_JSCAN(v) FIELD_PREP(DFSDM_CR1_JSCAN_MASK, v)
> +#define DFSDM_CR1_JDMAEN_MASK BIT(5)
> +#define DFSDM_CR1_JDMAEN(v) FIELD_PREP(DFSDM_CR1_JDMAEN_MASK, v)
> +#define DFSDM_CR1_JEXTSEL_MASK GENMASK(12, 8)
> +#define DFSDM_CR1_JEXTSEL(v) FIELD_PREP(DFSDM_CR1_JEXTSEL_MASK, v)
> +#define DFSDM_CR1_JEXTEN_MASK GENMASK(14, 13)
> +#define DFSDM_CR1_JEXTEN(v) FIELD_PREP(DFSDM_CR1_JEXTEN_MASK, v)
> +#define DFSDM_CR1_RSWSTART_MASK BIT(17)
> +#define DFSDM_CR1_RSWSTART(v) FIELD_PREP(DFSDM_CR1_RSWSTART_MASK, v)
> +#define DFSDM_CR1_RCONT_MASK BIT(18)
> +#define DFSDM_CR1_RCONT(v) FIELD_PREP(DFSDM_CR1_RCONT_MASK, v)
> +#define DFSDM_CR1_RSYNC_MASK BIT(19)
> +#define DFSDM_CR1_RSYNC(v) FIELD_PREP(DFSDM_CR1_RSYNC_MASK, v)
> +#define DFSDM_CR1_RDMAEN_MASK BIT(21)
> +#define DFSDM_CR1_RDMAEN(v) FIELD_PREP(DFSDM_CR1_RDMAEN_MASK, v)
> +#define DFSDM_CR1_RCH_MASK GENMASK(26, 24)
> +#define DFSDM_CR1_RCH(v) FIELD_PREP(DFSDM_CR1_RCH_MASK, v)
> +#define DFSDM_CR1_FAST_MASK BIT(29)
> +#define DFSDM_CR1_FAST(v) FIELD_PREP(DFSDM_CR1_FAST_MASK, v)
> +#define DFSDM_CR1_AWFSEL_MASK BIT(30)
> +#define DFSDM_CR1_AWFSEL(v) FIELD_PREP(DFSDM_CR1_AWFSEL_MASK, v)
> +
> +/* CR2: Control register 2 */
> +#define DFSDM_CR2_IE_MASK GENMASK(6, 0)
> +#define DFSDM_CR2_IE(v) FIELD_PREP(DFSDM_CR2_IE_MASK, v)
> +#define DFSDM_CR2_JEOCIE_MASK BIT(0)
> +#define DFSDM_CR2_JEOCIE(v) FIELD_PREP(DFSDM_CR2_JEOCIE_MASK, v)
> +#define DFSDM_CR2_REOCIE_MASK BIT(1)
> +#define DFSDM_CR2_REOCIE(v) FIELD_PREP(DFSDM_CR2_REOCIE_MASK, v)
> +#define DFSDM_CR2_JOVRIE_MASK BIT(2)
> +#define DFSDM_CR2_JOVRIE(v) FIELD_PREP(DFSDM_CR2_JOVRIE_MASK, v)
> +#define DFSDM_CR2_ROVRIE_MASK BIT(3)
> +#define DFSDM_CR2_ROVRIE(v) FIELD_PREP(DFSDM_CR2_ROVRIE_MASK, v)
> +#define DFSDM_CR2_AWDIE_MASK BIT(4)
> +#define DFSDM_CR2_AWDIE(v) FIELD_PREP(DFSDM_CR2_AWDIE_MASK, v)
> +#define DFSDM_CR2_SCDIE_MASK BIT(5)
> +#define DFSDM_CR2_SCDIE(v) FIELD_PREP(DFSDM_CR2_SCDIE_MASK, v)
> +#define DFSDM_CR2_CKABIE_MASK BIT(6)
> +#define DFSDM_CR2_CKABIE(v) FIELD_PREP(DFSDM_CR2_CKABIE_MASK, v)
> +#define DFSDM_CR2_EXCH_MASK GENMASK(15, 8)
> +#define DFSDM_CR2_EXCH(v) FIELD_PREP(DFSDM_CR2_EXCH_MASK, v)
> +#define DFSDM_CR2_AWDCH_MASK GENMASK(23, 16)
> +#define DFSDM_CR2_AWDCH(v) FIELD_PREP(DFSDM_CR2_AWDCH_MASK, v)
> +
> +/* ISR: Interrupt status register */
> +#define DFSDM_ISR_JEOCF_MASK BIT(0)
> +#define DFSDM_ISR_JEOCF(v) FIELD_PREP(DFSDM_ISR_JEOCF_MASK, v)
> +#define DFSDM_ISR_REOCF_MASK BIT(1)
> +#define DFSDM_ISR_REOCF(v) FIELD_PREP(DFSDM_ISR_REOCF_MASK, v)
> +#define DFSDM_ISR_JOVRF_MASK BIT(2)
> +#define DFSDM_ISR_JOVRF(v) FIELD_PREP(DFSDM_ISR_JOVRF_MASK, v)
> +#define DFSDM_ISR_ROVRF_MASK BIT(3)
> +#define DFSDM_ISR_ROVRF(v) FIELD_PREP(DFSDM_ISR_ROVRF_MASK, v)
> +#define DFSDM_ISR_AWDF_MASK BIT(4)
> +#define DFSDM_ISR_AWDF(v) FIELD_PREP(DFSDM_ISR_AWDF_MASK, v)
> +#define DFSDM_ISR_JCIP_MASK BIT(13)
> +#define DFSDM_ISR_JCIP(v) FIELD_PREP(DFSDM_ISR_JCIP_MASK, v)
> +#define DFSDM_ISR_RCIP_MASK BIT(14)
> +#define DFSDM_ISR_RCIP(v) FIELD_PREP(DFSDM_ISR_RCIP, v)
> +#define DFSDM_ISR_CKABF_MASK GENMASK(23, 16)
> +#define DFSDM_ISR_CKABF(v) FIELD_PREP(DFSDM_ISR_CKABF_MASK, v)
> +#define DFSDM_ISR_SCDF_MASK GENMASK(31, 24)
> +#define DFSDM_ISR_SCDF(v) FIELD_PREP(DFSDM_ISR_SCDF_MASK, v)
> +
> +/* ICR: Interrupt flag clear register */
> +#define DFSDM_ICR_CLRJOVRF_MASK BIT(2)
> +#define DFSDM_ICR_CLRJOVRF(v) FIELD_PREP(DFSDM_ICR_CLRJOVRF_MASK, v)
> +#define DFSDM_ICR_CLRROVRF_MASK BIT(3)
> +#define DFSDM_ICR_CLRROVRF(v) FIELD_PREP(DFSDM_ICR_CLRROVRF_MASK, v)
> +#define DFSDM_ICR_CLRCKABF_MASK GENMASK(23, 16)
> +#define DFSDM_ICR_CLRCKABF(v) FIELD_PREP(DFSDM_ICR_CLRCKABF_MASK, v)
> +#define DFSDM_ICR_CLRCKABF_CH_MASK(y) BIT(16 + (y))
> +#define DFSDM_ICR_CLRCKABF_CH(v, y) \
> + (((v) << (16 + (y))) & DFSDM_ICR_CLRCKABF_CH_MASK(y))
> +#define DFSDM_ICR_CLRSCDF_MASK GENMASK(31, 24)
> +#define DFSDM_ICR_CLRSCDF(v) FIELD_PREP(DFSDM_ICR_CLRSCDF_MASK, v)
> +#define DFSDM_ICR_CLRSCDF_CH_MASK(y) BIT(24 + (y))
> +#define DFSDM_ICR_CLRSCDF_CH(v, y) \
> + (((v) << (24 + (y))) & DFSDM_ICR_CLRSCDF_MASK(y))
> +
> +/* FCR: Filter control register */
> +#define DFSDM_FCR_IOSR_MASK GENMASK(7, 0)
> +#define DFSDM_FCR_IOSR(v) FIELD_PREP(DFSDM_FCR_IOSR_MASK, v)
> +#define DFSDM_FCR_FOSR_MASK GENMASK(25, 16)
> +#define DFSDM_FCR_FOSR(v) FIELD_PREP(DFSDM_FCR_FOSR_MASK, v)
> +#define DFSDM_FCR_FORD_MASK GENMASK(31, 29)
> +#define DFSDM_FCR_FORD(v) FIELD_PREP(DFSDM_FCR_FORD_MASK, v)
> +
> +/* RDATAR: Filter data register for regular channel */
> +#define DFSDM_DATAR_CH_MASK GENMASK(2, 0)
> +#define DFSDM_DATAR_DATA_OFFSET 8
> +#define DFSDM_DATAR_DATA_MASK GENMASK(31, DFSDM_DATAR_DATA_OFFSET)
> +
> +/* AWLTR: Filter analog watchdog low threshold register */
> +#define DFSDM_AWLTR_BKAWL_MASK GENMASK(3, 0)
> +#define DFSDM_AWLTR_BKAWL(v) FIELD_PREP(DFSDM_AWLTR_BKAWL_MASK, v)
> +#define DFSDM_AWLTR_AWLT_MASK GENMASK(31, 8)
> +#define DFSDM_AWLTR_AWLT(v) FIELD_PREP(DFSDM_AWLTR_AWLT_MASK, v)
> +
> +/* AWHTR: Filter analog watchdog low threshold register */
> +#define DFSDM_AWHTR_BKAWH_MASK GENMASK(3, 0)
> +#define DFSDM_AWHTR_BKAWH(v) FIELD_PREP(DFSDM_AWHTR_BKAWH_MASK, v)
> +#define DFSDM_AWHTR_AWHT_MASK GENMASK(31, 8)
> +#define DFSDM_AWHTR_AWHT(v) FIELD_PREP(DFSDM_AWHTR_AWHT_MASK, v)
> +
> +/* AWSR: Filter watchdog status register */
> +#define DFSDM_AWSR_AWLTF_MASK GENMASK(7, 0)
> +#define DFSDM_AWSR_AWLTF(v) FIELD_PREP(DFSDM_AWSR_AWLTF_MASK, v)
> +#define DFSDM_AWSR_AWHTF_MASK GENMASK(15, 8)
> +#define DFSDM_AWSR_AWHTF(v) FIELD_PREP(DFSDM_AWSR_AWHTF_MASK, v)
> +
> +/* AWCFR: Filter watchdog status register */
> +#define DFSDM_AWCFR_AWLTF_MASK GENMASK(7, 0)
> +#define DFSDM_AWCFR_AWLTF(v) FIELD_PREP(DFSDM_AWCFR_AWLTF_MASK, v)
> +#define DFSDM_AWCFR_AWHTF_MASK GENMASK(15, 8)
> +#define DFSDM_AWCFR_AWHTF(v) FIELD_PREP(DFSDM_AWCFR_AWHTF_MASK, v)
> +
> +/* DFSDM filter order */
> +enum stm32_dfsdm_sinc_order {
> + DFSDM_FASTSINC_ORDER, /* FastSinc filter type */
> + DFSDM_SINC1_ORDER, /* Sinc 1 filter type */
> + DFSDM_SINC2_ORDER, /* Sinc 2 filter type */
> + DFSDM_SINC3_ORDER, /* Sinc 3 filter type */
> + DFSDM_SINC4_ORDER, /* Sinc 4 filter type (N.A. for watchdog) */
> + DFSDM_SINC5_ORDER, /* Sinc 5 filter type (N.A. for watchdog) */
> + DFSDM_NB_SINC_ORDER,
> +};
> +
> +/**
> + * struct stm32_dfsdm_filter - structure relative to stm32 FDSDM filter
> + * TODO: complete structure.
nice :) RFC I guess :)
> + * @id: filetr ID,
> + */
> +struct stm32_dfsdm_filter {
> + unsigned int id;
> + unsigned int iosr; /* integrator oversampling */
> + unsigned int fosr; /* filter oversampling */
> + enum stm32_dfsdm_sinc_order ford;
> + u64 res; /* output sample resolution */
> + unsigned int sync_mode; /* filter suynchronized with filter0 */
> + unsigned int fast; /* filter fast mode */
> +};
> +
> +/**
> + * struct stm32_dfsdm_channel - structure relative to stm32 FDSDM channel
> + * TODO: complete structure.
> + * @id: filetr ID,
filter
> + */
> +struct stm32_dfsdm_channel {
> + unsigned int id; /* id of the channel */
> + unsigned int type; /* interface type linked to stm32_dfsdm_chan_type */
> + unsigned int src; /* interface type linked to stm32_dfsdm_chan_src */
> + unsigned int alt_si; /* use alternative serial input interface */
> +};
> +
> +/**
> + * struct stm32_dfsdm - stm32 FDSDM driver common data (for all instances)
> + * @base: control registers base cpu addr
> + * @phys_base: DFSDM IP register physical address.
> + * @fl_list: filter resources list
> + * @num_fl: number of filter resources available
> + * @ch_list: channel resources list
> + * @num_chs: number of channel resources available
> + */
> +struct stm32_dfsdm {
> + void __iomem *base;
> + phys_addr_t phys_base;
> + struct regmap *regmap;
> + struct stm32_dfsdm_filter *fl_list;
> + unsigned int num_fls;
> + struct stm32_dfsdm_channel *ch_list;
> + unsigned int num_chs;
> + unsigned int spi_master_freq;
> +};
> +
> +struct stm32_dfsdm_str2field {
> + const char *name;
> + unsigned int val;
> +};
> +
> +/* DFSDM channel serial interface type */
> +static const struct stm32_dfsdm_str2field stm32_dfsdm_chan_type[] = {
> + { "SPI_R", 0 }, /* SPI with data on rising edge */
> + { "SPI_F", 1 }, /* SPI with data on falling edge */
> + { "MANCH_R", 2 }, /* Manchester codec, rising edge = logic 0 */
> + { "MANCH_F", 3 }, /* Manchester codec, falling edge = logic 1 */
> + { 0, 0},
> +};
> +
> +/* DFSDM channel serial spi clock source */
> +enum stm32_dfsdm_spi_clk_src {
> + DFSDM_CHANNEL_SPI_CLOCK_EXTERNAL,
> + DFSDM_CHANNEL_SPI_CLOCK_INTERNAL,
> + DFSDM_CHANNEL_SPI_CLOCK_INTERNAL_DIV2_FALLING,
> + DFSDM_CHANNEL_SPI_CLOCK_INTERNAL_DIV2_RISING
> +};
> +
> +/* DFSDM channel clock source */
> +static const struct stm32_dfsdm_str2field stm32_dfsdm_chan_src[] = {
> + /* External SPI clock (CLKIN x) */
> + { "CLKIN", DFSDM_CHANNEL_SPI_CLOCK_EXTERNAL },
> + /* Internal SPI clock (CLKOUT) */
> + { "CLKOUT", DFSDM_CHANNEL_SPI_CLOCK_INTERNAL },
> + /* Internal SPI clock divided by 2 (falling edge) */
> + { "CLKOUT_F", DFSDM_CHANNEL_SPI_CLOCK_INTERNAL_DIV2_FALLING },
> + /* Internal SPI clock divided by 2 (falling edge) */
> + { "CLKOUT_R", DFSDM_CHANNEL_SPI_CLOCK_INTERNAL_DIV2_RISING },
> + { 0, 0 },
> +};
> +
> +/* DFSDM Serial interface trigger name */
> +#define DFSDM_SPI_TRIGGER_NAME "DFSDM_SERIAL_IN"
> +
> +static inline int stm32_dfsdm_str2val(const char *str,
> + const struct stm32_dfsdm_str2field *list)
> +{
> + const struct stm32_dfsdm_str2field *p = list;
> +
> + for (p = list; p && p->name; p++) {
> + if (!strcmp(p->name, str))
> + return p->val;
> + }
> + return -EINVAL;
> +}
> +
> +int stm32_dfsdm_set_osrs(struct stm32_dfsdm_filter *fl, unsigned int fast,
> + unsigned int oversamp);
> +int stm32_dfsdm_start_dfsdm(struct stm32_dfsdm *dfsdm);
> +int stm32_dfsdm_stop_dfsdm(struct stm32_dfsdm *dfsdm);
> +
> +int stm32_dfsdm_filter_configure(struct stm32_dfsdm *dfsdm, unsigned int fl_id,
> + unsigned int ch_id);
> +int stm32_dfsdm_start_filter(struct stm32_dfsdm *dfsdm, unsigned int fl_id);
> +void stm32_dfsdm_stop_filter(struct stm32_dfsdm *dfsdm, unsigned int fl_id);
> +
> +int stm32_dfsdm_chan_configure(struct stm32_dfsdm *dfsdm,
> + struct stm32_dfsdm_channel *ch);
> +int stm32_dfsdm_start_channel(struct stm32_dfsdm *dfsdm, unsigned int ch_id);
> +void stm32_dfsdm_stop_channel(struct stm32_dfsdm *dfsdm, unsigned int ch_id);
> +
> +int stm32_dfsdm_channel_parse_of(struct stm32_dfsdm *dfsdm,
> + struct iio_dev *indio_dev,
> + struct iio_chan_spec *chan, int chan_idx);
> +
> +#endif
>
More information about the linux-arm-kernel
mailing list