[PATCH 4/7] IIO: add STM32 DFSDM ADC support

Arnaud Pouliquen
2017-01-23T16:32:22Z

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");
--
1.9.1