Message ID | 1512150020-20335-10-git-send-email-arnaud.pouliquen@st.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
On Fri, 1 Dec 2017 18:40:16 +0100 Arnaud Pouliquen <arnaud.pouliquen@st.com> wrote: > Add DFSDM driver to handle sigma delta ADC. > > Signed-off-by: Arnaud Pouliquen <arnaud.pouliquen@st.com> A couple of really small nitpicks if you are respinning. Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com> > --- > drivers/iio/adc/Kconfig | 13 + > drivers/iio/adc/Makefile | 1 + > drivers/iio/adc/stm32-dfsdm-adc.c | 741 ++++++++++++++++++++++++++++++++++++++ > 3 files changed, 755 insertions(+) > create mode 100644 drivers/iio/adc/stm32-dfsdm-adc.c > > diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig > index b729ae0..98ca30b 100644 > --- a/drivers/iio/adc/Kconfig > +++ b/drivers/iio/adc/Kconfig > @@ -677,6 +677,19 @@ config STM32_DFSDM_CORE > This driver can also be built as a module. If so, the module > will be called stm32-dfsdm-core. > > +config STM32_DFSDM_ADC > + tristate "STMicroelectronics STM32 dfsdm adc" > + depends on (ARCH_STM32 && OF) || COMPILE_TEST > + select STM32_DFSDM_CORE > + select REGMAP_MMIO > + select IIO_BUFFER_HW_CONSUMER > + help > + Select this option to support ADCSigma delta modulator for > + STMicroelectronics STM32 digital filter for sigma delta converter. > + > + This driver can also be built as a module. If so, the module > + will be called stm32-dfsdm-adc. > + > config STX104 > tristate "Apex Embedded Systems STX104 driver" > depends on PC104 && X86 && ISA_BUS_API > diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile > index b52d0a0..c4f5d15 100644 > --- a/drivers/iio/adc/Makefile > +++ b/drivers/iio/adc/Makefile > @@ -64,6 +64,7 @@ obj-$(CONFIG_SUN4I_GPADC) += sun4i-gpadc-iio.o > obj-$(CONFIG_STM32_ADC_CORE) += stm32-adc-core.o > obj-$(CONFIG_STM32_ADC) += stm32-adc.o > obj-$(CONFIG_STM32_DFSDM_CORE) += stm32-dfsdm-core.o > +obj-$(CONFIG_STM32_DFSDM_ADC) += stm32-dfsdm-adc.o > obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o > obj-$(CONFIG_TI_ADC0832) += ti-adc0832.o > obj-$(CONFIG_TI_ADC084S021) += ti-adc084s021.o > diff --git a/drivers/iio/adc/stm32-dfsdm-adc.c b/drivers/iio/adc/stm32-dfsdm-adc.c > new file mode 100644 > index 0000000..f9419ab > --- /dev/null > +++ b/drivers/iio/adc/stm32-dfsdm-adc.c > @@ -0,0 +1,741 @@ > +/* > + * This file is the ADC part of the STM32 DFSDM driver > + * > + * Copyright (C) 2017, STMicroelectronics - All Rights Reserved > + * Author: Arnaud Pouliquen <arnaud.pouliquen@st.com>. > + * > + * License type: GPL V2.0. > + * > + * This program is free software; you can redistribute it and/or modify it > + * under the terms of the GNU General Public License version 2 as published by > + * the Free Software Foundation. > + * > + * This program is distributed in the hope that it will be useful, but > + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY > + * or FITNESS FOR A PARTICULAR PURPOSE. > + * See the GNU General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License along with > + * this program. If not, see <http://www.gnu.org/licenses/>. > + */ > + > +#include <linux/interrupt.h> > +#include <linux/iio/buffer.h> > +#include <linux/iio/hw-consumer.h> > +#include <linux/iio/iio.h> > +#include <linux/iio/sysfs.h> > +#include <linux/module.h> > +#include <linux/of.h> > +#include <linux/platform_device.h> > +#include <linux/regmap.h> > +#include <linux/slab.h> > + > +#include "stm32-dfsdm.h" > + > +/* Conversion timeout */ > +#define DFSDM_TIMEOUT_US 100000 > +#define DFSDM_TIMEOUT (msecs_to_jiffies(DFSDM_TIMEOUT_US / 1000)) > + > +/* Oversampling attribute default */ > +#define DFSDM_DEFAULT_OVERSAMPLING 100 > + > +/* Oversampling max values */ > +#define DFSDM_MAX_INT_OVERSAMPLING 256 > +#define DFSDM_MAX_FL_OVERSAMPLING 1024 > + > +/* Max sample resolutions */ > +#define DFSDM_MAX_RES BIT(31) > +#define DFSDM_DATA_RES BIT(23) > + > +enum sd_converter_type { > + DFSDM_AUDIO, > + DFSDM_IIO, > +}; > + > +struct stm32_dfsdm_dev_data { > + int type; > + int (*init)(struct iio_dev *indio_dev); > + unsigned int num_channels; > + const struct regmap_config *regmap_cfg; > +}; > + > +struct stm32_dfsdm_adc { > + struct stm32_dfsdm *dfsdm; > + const struct stm32_dfsdm_dev_data *dev_data; > + unsigned int fl_id; > + unsigned int ch_id; > + > + /* ADC specific */ > + unsigned int oversamp; > + struct iio_hw_consumer *hwc; > + struct completion completion; > + u32 *buffer; > + > +}; > + > +struct stm32_dfsdm_str2field { > + const char *name; > + unsigned int val; > +}; > + > +/* DFSDM channel serial interface type */ > +static const struct stm32_dfsdm_str2field stm32_dfsdm_chan_type[] = { > + { "SPI_R", 0 }, /* SPI with data on rising edge */ > + { "SPI_F", 1 }, /* SPI with data on falling edge */ > + { "MANCH_R", 2 }, /* Manchester codec, rising edge = logic 0 */ > + { "MANCH_F", 3 }, /* Manchester codec, falling edge = logic 1 */ > + {}, > +}; > + > +/* DFSDM channel clock source */ > +static const struct stm32_dfsdm_str2field stm32_dfsdm_chan_src[] = { > + /* External SPI clock (CLKIN x) */ > + { "CLKIN", DFSDM_CHANNEL_SPI_CLOCK_EXTERNAL }, > + /* Internal SPI clock (CLKOUT) */ > + { "CLKOUT", DFSDM_CHANNEL_SPI_CLOCK_INTERNAL }, > + /* Internal SPI clock divided by 2 (falling edge) */ > + { "CLKOUT_F", DFSDM_CHANNEL_SPI_CLOCK_INTERNAL_DIV2_FALLING }, > + /* Internal SPI clock divided by 2 (falling edge) */ > + { "CLKOUT_R", DFSDM_CHANNEL_SPI_CLOCK_INTERNAL_DIV2_RISING }, > + {}, > +}; > + > +static int stm32_dfsdm_str2val(const char *str, > + const struct stm32_dfsdm_str2field *list) > +{ > + const struct stm32_dfsdm_str2field *p = list; > + > + for (p = list; p && p->name; p++) > + if (!strcmp(p->name, str)) > + return p->val; > + > + return -EINVAL; > +} > + > +static int stm32_dfsdm_set_osrs(struct stm32_dfsdm_filter *fl, > + unsigned int fast, unsigned int oversamp) > +{ > + unsigned int i, d, fosr, iosr; > + u64 res; > + s64 delta; > + unsigned int m = 1; /* multiplication factor */ > + unsigned int p = fl->ford; /* filter order (ford) */ > + > + pr_debug("%s: Requested oversampling: %d\n", __func__, oversamp); > + /* > + * This function tries to compute filter oversampling and integrator > + * oversampling, base on oversampling ratio requested by user. > + * > + * Decimation d depends on the filter order and the oversampling ratios. > + * ford: filter order > + * fosr: filter over sampling ratio > + * iosr: integrator over sampling ratio > + */ > + if (fl->ford == DFSDM_FASTSINC_ORDER) { > + m = 2; > + p = 2; > + } > + > + /* > + * Look for filter and integrator oversampling ratios which allows > + * to reach 24 bits data output resolution. > + * Leave as soon as if exact resolution if reached. > + * Otherwise the higher resolution below 32 bits is kept. > + */ > + for (fosr = 1; fosr <= DFSDM_MAX_FL_OVERSAMPLING; fosr++) { > + for (iosr = 1; iosr <= DFSDM_MAX_INT_OVERSAMPLING; iosr++) { > + if (fast) > + d = fosr * iosr; > + else if (fl->ford == DFSDM_FASTSINC_ORDER) > + d = fosr * (iosr + 3) + 2; > + else > + d = fosr * (iosr - 1 + p) + p; > + > + if (d > oversamp) > + break; > + else if (d != oversamp) > + continue; > + /* > + * Check resolution (limited to signed 32 bits) > + * res <= 2^31 > + * Sincx filters: > + * res = m * fosr^p x iosr (with m=1, p=ford) > + * FastSinc filter > + * res = m * fosr^p x iosr (with m=2, p=2) > + */ > + res = fosr; > + for (i = p - 1; i > 0; i--) { > + res = res * (u64)fosr; > + if (res > DFSDM_MAX_RES) > + break; > + } > + if (res > DFSDM_MAX_RES) > + continue; > + res = res * (u64)m * (u64)iosr; > + if (res > DFSDM_MAX_RES) > + continue; > + > + delta = res - DFSDM_DATA_RES; > + > + if (res >= fl->res) { > + fl->res = res; > + fl->fosr = fosr; > + fl->iosr = iosr; > + fl->fast = fast; > + pr_debug("%s: fosr = %d, iosr = %d\n", > + __func__, fl->fosr, fl->iosr); > + } > + > + if (!delta) > + return 0; > + } > + } > + > + if (!fl->fosr) > + return -EINVAL; > + > + return 0; > +} > + > +static int stm32_dfsdm_start_channel(struct stm32_dfsdm *dfsdm, > + unsigned int ch_id) > +{ > + return regmap_update_bits(dfsdm->regmap, DFSDM_CHCFGR1(ch_id), > + DFSDM_CHCFGR1_CHEN_MASK, > + DFSDM_CHCFGR1_CHEN(1)); > +} > + > +static void stm32_dfsdm_stop_channel(struct stm32_dfsdm *dfsdm, > + unsigned int ch_id) > +{ > + regmap_update_bits(dfsdm->regmap, DFSDM_CHCFGR1(ch_id), > + DFSDM_CHCFGR1_CHEN_MASK, DFSDM_CHCFGR1_CHEN(0)); > +} > + > +static int stm32_dfsdm_chan_configure(struct stm32_dfsdm *dfsdm, > + struct stm32_dfsdm_channel *ch) > +{ > + unsigned int id = ch->id; > + struct regmap *regmap = dfsdm->regmap; > + int ret; > + > + ret = regmap_update_bits(regmap, DFSDM_CHCFGR1(id), > + DFSDM_CHCFGR1_SITP_MASK, > + DFSDM_CHCFGR1_SITP(ch->type)); > + if (ret < 0) > + return ret; > + ret = regmap_update_bits(regmap, DFSDM_CHCFGR1(id), > + DFSDM_CHCFGR1_SPICKSEL_MASK, > + DFSDM_CHCFGR1_SPICKSEL(ch->src)); > + if (ret < 0) > + return ret; > + return regmap_update_bits(regmap, DFSDM_CHCFGR1(id), > + DFSDM_CHCFGR1_CHINSEL_MASK, > + DFSDM_CHCFGR1_CHINSEL(ch->alt_si)); > +} > + > +static int stm32_dfsdm_start_filter(struct stm32_dfsdm *dfsdm, > + unsigned int fl_id) > +{ > + int ret; > + > + /* Enable filter */ > + ret = regmap_update_bits(dfsdm->regmap, DFSDM_CR1(fl_id), > + DFSDM_CR1_DFEN_MASK, DFSDM_CR1_DFEN(1)); > + if (ret < 0) > + return ret; > + > + /* Start conversion */ > + return regmap_update_bits(dfsdm->regmap, DFSDM_CR1(fl_id), > + DFSDM_CR1_RSWSTART_MASK, > + DFSDM_CR1_RSWSTART(1)); > +} > + > +void stm32_dfsdm_stop_filter(struct stm32_dfsdm *dfsdm, unsigned int fl_id) > +{ > + /* Disable conversion */ > + regmap_update_bits(dfsdm->regmap, DFSDM_CR1(fl_id), > + DFSDM_CR1_DFEN_MASK, DFSDM_CR1_DFEN(0)); > +} > + > +static int stm32_dfsdm_filter_configure(struct stm32_dfsdm *dfsdm, > + unsigned int fl_id, unsigned int ch_id) > +{ > + struct regmap *regmap = dfsdm->regmap; > + struct stm32_dfsdm_filter *fl = &dfsdm->fl_list[fl_id]; > + int ret; > + > + /* Average integrator oversampling */ > + ret = regmap_update_bits(regmap, DFSDM_FCR(fl_id), DFSDM_FCR_IOSR_MASK, > + DFSDM_FCR_IOSR(fl->iosr - 1)); > + if (ret) > + return ret; > + > + /* Filter order and Oversampling */ > + ret = regmap_update_bits(regmap, DFSDM_FCR(fl_id), DFSDM_FCR_FOSR_MASK, > + DFSDM_FCR_FOSR(fl->fosr - 1)); > + if (ret) > + return ret; > + > + ret = regmap_update_bits(regmap, DFSDM_FCR(fl_id), DFSDM_FCR_FORD_MASK, > + DFSDM_FCR_FORD(fl->ford)); > + if (ret) > + return ret; > + > + /* No scan mode supported for the moment */ > + ret = regmap_update_bits(regmap, DFSDM_CR1(fl_id), DFSDM_CR1_RCH_MASK, > + DFSDM_CR1_RCH(ch_id)); > + if (ret) > + return ret; > + > + return regmap_update_bits(regmap, DFSDM_CR1(fl_id), > + DFSDM_CR1_RSYNC_MASK, > + DFSDM_CR1_RSYNC(fl->sync_mode)); > +} > + > +int stm32_dfsdm_channel_parse_of(struct stm32_dfsdm *dfsdm, > + struct iio_dev *indio_dev, > + struct iio_chan_spec *ch) > +{ > + struct stm32_dfsdm_channel *df_ch; > + const char *of_str; > + int chan_idx = ch->scan_index; > + int ret, val; > + > + ret = of_property_read_u32_index(indio_dev->dev.of_node, > + "st,adc-channels", chan_idx, > + &ch->channel); > + if (ret < 0) { > + dev_err(&indio_dev->dev, > + " Error parsing 'st,adc-channels' for idx %d\n", > + chan_idx); > + return ret; > + } > + if (ch->channel >= dfsdm->num_chs) { > + dev_err(&indio_dev->dev, > + " Error bad channel number %d (max = %d)\n", > + ch->channel, dfsdm->num_chs); > + return -EINVAL; > + } > + > + ret = of_property_read_string_index(indio_dev->dev.of_node, > + "st,adc-channel-names", chan_idx, > + &ch->datasheet_name); > + if (ret < 0) { > + dev_err(&indio_dev->dev, > + " Error parsing 'st,adc-channel-names' for idx %d\n", > + chan_idx); > + return ret; > + } > + > + df_ch = &dfsdm->ch_list[ch->channel]; > + df_ch->id = ch->channel; > + > + ret = of_property_read_string_index(indio_dev->dev.of_node, > + "st,adc-channel-types", chan_idx, > + &of_str); > + if (!ret) { > + val = stm32_dfsdm_str2val(of_str, stm32_dfsdm_chan_type); > + if (val < 0) > + return val; > + } else { > + val = 0; > + } > + df_ch->type = val; > + > + ret = of_property_read_string_index(indio_dev->dev.of_node, > + "st,adc-channel-clk-src", chan_idx, > + &of_str); > + if (!ret) { > + val = stm32_dfsdm_str2val(of_str, stm32_dfsdm_chan_src); > + if (val < 0) > + return val; > + } else { > + val = 0; > + } > + df_ch->src = val; > + > + ret = of_property_read_u32_index(indio_dev->dev.of_node, > + "st,adc-alt-channel", chan_idx, > + &df_ch->alt_si); > + if (ret < 0) > + df_ch->alt_si = 0; > + > + return 0; > +} > + > +static int stm32_dfsdm_start_conv(struct stm32_dfsdm_adc *adc, bool dma) > +{ > + struct regmap *regmap = adc->dfsdm->regmap; > + int ret; > + > + ret = stm32_dfsdm_start_channel(adc->dfsdm, adc->ch_id); > + if (ret < 0) > + return ret; > + > + ret = stm32_dfsdm_filter_configure(adc->dfsdm, adc->fl_id, > + adc->ch_id); > + if (ret < 0) > + goto stop_channels; > + > + ret = stm32_dfsdm_start_filter(adc->dfsdm, adc->fl_id); > + if (ret < 0) > + goto stop_channels; > + > + return 0; > + > +stop_channels: > + regmap_update_bits(regmap, DFSDM_CR1(adc->fl_id), > + DFSDM_CR1_RDMAEN_MASK, 0); > + > + regmap_update_bits(regmap, DFSDM_CR1(adc->fl_id), > + DFSDM_CR1_RCONT_MASK, 0); > + stm32_dfsdm_stop_channel(adc->dfsdm, adc->fl_id); > + > + return ret; > +} > + > +static void stm32_dfsdm_stop_conv(struct stm32_dfsdm_adc *adc) > +{ > + struct regmap *regmap = adc->dfsdm->regmap; > + > + stm32_dfsdm_stop_filter(adc->dfsdm, adc->fl_id); > + > + /* Clean conversion options */ > + regmap_update_bits(regmap, DFSDM_CR1(adc->fl_id), > + DFSDM_CR1_RDMAEN_MASK, 0); > + > + regmap_update_bits(regmap, DFSDM_CR1(adc->fl_id), > + DFSDM_CR1_RCONT_MASK, 0); > + > + stm32_dfsdm_stop_channel(adc->dfsdm, adc->ch_id); > +} > + > +static int stm32_dfsdm_single_conv(struct iio_dev *indio_dev, > + const struct iio_chan_spec *chan, int *res) > +{ > + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); > + long timeout; > + int ret; > + > + reinit_completion(&adc->completion); > + > + adc->buffer = res; > + > + ret = stm32_dfsdm_start_dfsdm(adc->dfsdm); > + if (ret < 0) > + return ret; > + > + ret = regmap_update_bits(adc->dfsdm->regmap, DFSDM_CR2(adc->fl_id), > + DFSDM_CR2_REOCIE_MASK, DFSDM_CR2_REOCIE(1)); > + if (ret < 0) > + goto stop_dfsdm; > + > + ret = stm32_dfsdm_start_conv(adc, false); > + if (ret < 0) { > + regmap_update_bits(adc->dfsdm->regmap, DFSDM_CR2(adc->fl_id), > + DFSDM_CR2_REOCIE_MASK, DFSDM_CR2_REOCIE(0)); > + goto stop_dfsdm; > + } > + > + timeout = wait_for_completion_interruptible_timeout(&adc->completion, > + DFSDM_TIMEOUT); > + > + /* Mask IRQ for regular conversion achievement*/ > + regmap_update_bits(adc->dfsdm->regmap, DFSDM_CR2(adc->fl_id), > + DFSDM_CR2_REOCIE_MASK, DFSDM_CR2_REOCIE(0)); > + > + if (timeout == 0) > + ret = -ETIMEDOUT; > + else if (timeout < 0) > + ret = timeout; > + else > + ret = IIO_VAL_INT; > + > + stm32_dfsdm_stop_conv(adc); > + > +stop_dfsdm: > + stm32_dfsdm_stop_dfsdm(adc->dfsdm); > + > + return ret; > +} > + > +static int stm32_dfsdm_write_raw(struct iio_dev *indio_dev, > + struct iio_chan_spec const *chan, > + int val, int val2, long mask) > +{ > + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); > + struct stm32_dfsdm_filter *fl = &adc->dfsdm->fl_list[adc->fl_id]; > + int ret = -EINVAL; > + > + if (mask == IIO_CHAN_INFO_OVERSAMPLING_RATIO) { > + ret = stm32_dfsdm_set_osrs(fl, 0, val); > + if (!ret) > + adc->oversamp = val; > + } > + > + return ret; > +} > + > +static int stm32_dfsdm_read_raw(struct iio_dev *indio_dev, > + struct iio_chan_spec const *chan, int *val, > + int *val2, long mask) > +{ > + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); > + int ret; > + > + switch (mask) { > + case IIO_CHAN_INFO_RAW: > + ret = iio_hw_consumer_enable(adc->hwc); > + if (ret < 0) { > + dev_err(&indio_dev->dev, > + "%s: IIO enable failed (channel %d)\n", > + __func__, chan->channel); > + return ret; > + } > + ret = stm32_dfsdm_single_conv(indio_dev, chan, val); > + iio_hw_consumer_disable(adc->hwc); > + if (ret < 0) { > + dev_err(&indio_dev->dev, > + "%s: Conversion failed (channel %d)\n", > + __func__, chan->channel); > + return ret; > + } > + return IIO_VAL_INT; > + > + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: > + *val = adc->oversamp; > + > + return IIO_VAL_INT; > + } > + > + return -EINVAL; > +} > + > +static const struct iio_info stm32_dfsdm_info_adc = { > + .read_raw = stm32_dfsdm_read_raw, > + .write_raw = stm32_dfsdm_write_raw, > +}; > + > +static irqreturn_t stm32_dfsdm_irq(int irq, void *arg) > +{ > + struct stm32_dfsdm_adc *adc = arg; > + struct iio_dev *indio_dev = iio_priv_to_dev(adc); > + struct regmap *regmap = adc->dfsdm->regmap; > + unsigned int status, int_en; > + > + regmap_read(regmap, DFSDM_ISR(adc->fl_id), &status); > + regmap_read(regmap, DFSDM_CR2(adc->fl_id), &int_en); > + > + if (status & DFSDM_ISR_REOCF_MASK) { > + /* Read the data register clean the IRQ status */ > + regmap_read(regmap, DFSDM_RDATAR(adc->fl_id), adc->buffer); > + complete(&adc->completion); > + } > + > + if (status & DFSDM_ISR_ROVRF_MASK) { > + if (int_en & DFSDM_CR2_ROVRIE_MASK) > + dev_warn(&indio_dev->dev, "Overrun detected\n"); > + regmap_update_bits(regmap, DFSDM_ICR(adc->fl_id), > + DFSDM_ICR_CLRROVRF_MASK, > + DFSDM_ICR_CLRROVRF_MASK); > + } > + > + return IRQ_HANDLED; > +} > + > +static int stm32_dfsdm_adc_chan_init_one(struct iio_dev *indio_dev, > + struct iio_chan_spec *ch) > +{ > + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); > + int ret; > + > + ret = stm32_dfsdm_channel_parse_of(adc->dfsdm, indio_dev, ch); > + if (ret < 0) > + return ret; > + > + ch->type = IIO_VOLTAGE; > + ch->indexed = 1; > + > + /* > + * IIO_CHAN_INFO_RAW: used to compute regular conversion > + * IIO_CHAN_INFO_OVERSAMPLING_RATIO: used to set oversampling > + */ > + ch->info_mask_separate = BIT(IIO_CHAN_INFO_RAW); > + ch->info_mask_shared_by_all = BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO); > + > + ch->scan_type.sign = 'u'; > + ch->scan_type.realbits = 24; > + ch->scan_type.storagebits = 32; > + adc->ch_id = ch->channel; > + > + return stm32_dfsdm_chan_configure(adc->dfsdm, > + &adc->dfsdm->ch_list[ch->channel]); > +} > + > +static int stm32_dfsdm_adc_init(struct iio_dev *indio_dev) > +{ > + struct iio_chan_spec *ch; > + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); > + int num_ch; > + int ret, chan_idx; > + > + adc->oversamp = DFSDM_DEFAULT_OVERSAMPLING; > + ret = stm32_dfsdm_set_osrs(&adc->dfsdm->fl_list[adc->fl_id], 0, > + adc->oversamp); > + if (ret < 0) > + return ret; > + > + num_ch = of_property_count_u32_elems(indio_dev->dev.of_node, > + "st,adc-channels"); > + if (num_ch < 0 || num_ch > adc->dfsdm->num_chs) { > + dev_err(&indio_dev->dev, "Bad st,adc-channels\n"); > + return num_ch < 0 ? num_ch : -EINVAL; > + } > + > + /* Bind to SD modulator IIO device */ > + adc->hwc = devm_iio_hw_consumer_alloc(&indio_dev->dev); > + if (IS_ERR(adc->hwc)) > + return -EPROBE_DEFER; > + > + ch = devm_kcalloc(&indio_dev->dev, num_ch, sizeof(*ch), > + GFP_KERNEL); > + if (!ch) > + return -ENOMEM; > + > + for (chan_idx = 0; chan_idx < num_ch; chan_idx++) { > + ch->scan_index = chan_idx; > + ret = stm32_dfsdm_adc_chan_init_one(indio_dev, ch); > + if (ret < 0) { > + dev_err(&indio_dev->dev, "Channels init failed\n"); > + return ret; > + } > + } > + > + indio_dev->num_channels = num_ch; > + indio_dev->channels = ch; > + > + init_completion(&adc->completion); > + > + return 0; > +} > + > +static const struct stm32_dfsdm_dev_data stm32h7_dfsdm_adc_data = { > + .type = DFSDM_IIO, > + .init = stm32_dfsdm_adc_init, > +}; > + > +static const struct of_device_id stm32_dfsdm_adc_match[] = { > + { .compatible = "st,stm32-dfsdm-adc", Slightly odd alignment, I'd drop .compatible onto a new line as well.. > + .data = &stm32h7_dfsdm_adc_data, > + }, > + {} > +}; > + > +static int stm32_dfsdm_adc_probe(struct platform_device *pdev) > +{ > + struct device *dev = &pdev->dev; > + struct stm32_dfsdm_adc *adc; > + struct device_node *np = dev->of_node; > + const struct stm32_dfsdm_dev_data *dev_data; > + struct iio_dev *iio; > + const struct of_device_id *of_id; > + char *name; > + int ret, irq, val; > + > + of_id = of_match_node(stm32_dfsdm_adc_match, np); > + if (!of_id->data) { > + dev_err(&pdev->dev, "Data associated to device is missing\n"); > + return -EINVAL; > + } > + > + dev_data = (const struct stm32_dfsdm_dev_data *)of_id->data; > + > + iio = devm_iio_device_alloc(dev, sizeof(*adc)); > + if (IS_ERR(iio)) { > + dev_err(dev, "%s: Failed to allocate IIO\n", __func__); > + return PTR_ERR(iio); > + } > + > + adc = iio_priv(iio); > + if (IS_ERR(adc)) { > + dev_err(dev, "%s: Failed to allocate ADC\n", __func__); > + return PTR_ERR(adc); > + } > + adc->dfsdm = dev_get_drvdata(dev->parent); > + > + iio->dev.parent = dev; > + iio->dev.of_node = np; > + iio->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_SOFTWARE; > + > + platform_set_drvdata(pdev, adc); > + > + ret = of_property_read_u32(dev->of_node, "reg", &adc->fl_id); > + if (ret != 0) { > + dev_err(dev, "Missing reg property\n"); > + return -EINVAL; > + } > + > + name = devm_kzalloc(dev, sizeof("dfsdm-adc0"), GFP_KERNEL); > + if (!name) > + return -ENOMEM; > + iio->info = &stm32_dfsdm_info_adc; > + snprintf(name, sizeof("dfsdm-adc0"), "dfsdm-adc%d", adc->fl_id); > + iio->name = name; > + > + /* > + * In a first step IRQs generated for channels are not treated. > + * So IRQ associated to filter instance 0 is dedicated to the Filter 0. > + */ > + irq = platform_get_irq(pdev, 0); > + ret = devm_request_irq(dev, irq, stm32_dfsdm_irq, > + 0, pdev->name, adc); > + if (ret < 0) { > + dev_err(dev, "Failed to request IRQ\n"); > + return ret; > + } > + > + ret = of_property_read_u32(dev->of_node, "st,filter-order", &val); > + if (ret < 0) { > + dev_err(dev, "Failed to set filter order\n"); > + return ret; > + } > + > + adc->dfsdm->fl_list[adc->fl_id].ford = val; > + > + ret = of_property_read_u32(dev->of_node, "st,filter0-sync", &val); > + if (!ret) > + adc->dfsdm->fl_list[adc->fl_id].sync_mode = val; > + > + adc->dev_data = dev_data; > + ret = dev_data->init(iio); > + if (ret < 0) > + return ret; > + > + return iio_device_register(iio); > +} > + > +static int stm32_dfsdm_adc_remove(struct platform_device *pdev) > +{ > + struct stm32_dfsdm_adc *adc = platform_get_drvdata(pdev); > + struct iio_dev *indio_dev = iio_priv_to_dev(adc); > + > + iio_device_unregister(indio_dev); > + > + return 0; > +} > + > +static struct platform_driver stm32_dfsdm_adc_driver = { > + .driver = { > + .name = "stm32-dfsdm-adc", > + .of_match_table = stm32_dfsdm_adc_match, > + }, > + .probe = stm32_dfsdm_adc_probe, > + .remove = stm32_dfsdm_adc_remove, > + Real nitpick but no blank line here! > +}; > +module_platform_driver(stm32_dfsdm_adc_driver); > + > +MODULE_DESCRIPTION("STM32 sigma delta ADC"); > +MODULE_AUTHOR("Arnaud Pouliquen <arnaud.pouliquen@st.com>"); > +MODULE_LICENSE("GPL v2"); -- To unsubscribe from this list: send the line "unsubscribe linux-iio" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index b729ae0..98ca30b 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -677,6 +677,19 @@ config STM32_DFSDM_CORE This driver can also be built as a module. If so, the module will be called stm32-dfsdm-core. +config STM32_DFSDM_ADC + tristate "STMicroelectronics STM32 dfsdm adc" + depends on (ARCH_STM32 && OF) || COMPILE_TEST + select STM32_DFSDM_CORE + select REGMAP_MMIO + select IIO_BUFFER_HW_CONSUMER + help + Select this option to support ADCSigma delta modulator for + STMicroelectronics STM32 digital filter for sigma delta converter. + + This driver can also be built as a module. If so, the module + will be called stm32-dfsdm-adc. + config STX104 tristate "Apex Embedded Systems STX104 driver" depends on PC104 && X86 && ISA_BUS_API diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index b52d0a0..c4f5d15 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -64,6 +64,7 @@ obj-$(CONFIG_SUN4I_GPADC) += sun4i-gpadc-iio.o obj-$(CONFIG_STM32_ADC_CORE) += stm32-adc-core.o obj-$(CONFIG_STM32_ADC) += stm32-adc.o obj-$(CONFIG_STM32_DFSDM_CORE) += stm32-dfsdm-core.o +obj-$(CONFIG_STM32_DFSDM_ADC) += stm32-dfsdm-adc.o obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o obj-$(CONFIG_TI_ADC0832) += ti-adc0832.o obj-$(CONFIG_TI_ADC084S021) += ti-adc084s021.o diff --git a/drivers/iio/adc/stm32-dfsdm-adc.c b/drivers/iio/adc/stm32-dfsdm-adc.c new file mode 100644 index 0000000..f9419ab --- /dev/null +++ b/drivers/iio/adc/stm32-dfsdm-adc.c @@ -0,0 +1,741 @@ +/* + * This file is the ADC part of the STM32 DFSDM driver + * + * Copyright (C) 2017, STMicroelectronics - All Rights Reserved + * Author: Arnaud Pouliquen <arnaud.pouliquen@st.com>. + * + * License type: GPL V2.0. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/interrupt.h> +#include <linux/iio/buffer.h> +#include <linux/iio/hw-consumer.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/slab.h> + +#include "stm32-dfsdm.h" + +/* Conversion timeout */ +#define DFSDM_TIMEOUT_US 100000 +#define DFSDM_TIMEOUT (msecs_to_jiffies(DFSDM_TIMEOUT_US / 1000)) + +/* Oversampling attribute default */ +#define DFSDM_DEFAULT_OVERSAMPLING 100 + +/* Oversampling max values */ +#define DFSDM_MAX_INT_OVERSAMPLING 256 +#define DFSDM_MAX_FL_OVERSAMPLING 1024 + +/* Max sample resolutions */ +#define DFSDM_MAX_RES BIT(31) +#define DFSDM_DATA_RES BIT(23) + +enum sd_converter_type { + DFSDM_AUDIO, + DFSDM_IIO, +}; + +struct stm32_dfsdm_dev_data { + int type; + int (*init)(struct iio_dev *indio_dev); + unsigned int num_channels; + const struct regmap_config *regmap_cfg; +}; + +struct stm32_dfsdm_adc { + struct stm32_dfsdm *dfsdm; + const struct stm32_dfsdm_dev_data *dev_data; + unsigned int fl_id; + unsigned int ch_id; + + /* ADC specific */ + unsigned int oversamp; + struct iio_hw_consumer *hwc; + struct completion completion; + u32 *buffer; + +}; + +struct stm32_dfsdm_str2field { + const char *name; + unsigned int val; +}; + +/* DFSDM channel serial interface type */ +static const struct stm32_dfsdm_str2field stm32_dfsdm_chan_type[] = { + { "SPI_R", 0 }, /* SPI with data on rising edge */ + { "SPI_F", 1 }, /* SPI with data on falling edge */ + { "MANCH_R", 2 }, /* Manchester codec, rising edge = logic 0 */ + { "MANCH_F", 3 }, /* Manchester codec, falling edge = logic 1 */ + {}, +}; + +/* DFSDM channel clock source */ +static const struct stm32_dfsdm_str2field stm32_dfsdm_chan_src[] = { + /* External SPI clock (CLKIN x) */ + { "CLKIN", DFSDM_CHANNEL_SPI_CLOCK_EXTERNAL }, + /* Internal SPI clock (CLKOUT) */ + { "CLKOUT", DFSDM_CHANNEL_SPI_CLOCK_INTERNAL }, + /* Internal SPI clock divided by 2 (falling edge) */ + { "CLKOUT_F", DFSDM_CHANNEL_SPI_CLOCK_INTERNAL_DIV2_FALLING }, + /* Internal SPI clock divided by 2 (falling edge) */ + { "CLKOUT_R", DFSDM_CHANNEL_SPI_CLOCK_INTERNAL_DIV2_RISING }, + {}, +}; + +static int stm32_dfsdm_str2val(const char *str, + const struct stm32_dfsdm_str2field *list) +{ + const struct stm32_dfsdm_str2field *p = list; + + for (p = list; p && p->name; p++) + if (!strcmp(p->name, str)) + return p->val; + + return -EINVAL; +} + +static int stm32_dfsdm_set_osrs(struct stm32_dfsdm_filter *fl, + unsigned int fast, unsigned int oversamp) +{ + unsigned int i, d, fosr, iosr; + u64 res; + s64 delta; + unsigned int m = 1; /* multiplication factor */ + unsigned int p = fl->ford; /* filter order (ford) */ + + pr_debug("%s: Requested oversampling: %d\n", __func__, oversamp); + /* + * This function tries to compute filter oversampling and integrator + * oversampling, base on oversampling ratio requested by user. + * + * Decimation d depends on the filter order and the oversampling ratios. + * ford: filter order + * fosr: filter over sampling ratio + * iosr: integrator over sampling ratio + */ + if (fl->ford == DFSDM_FASTSINC_ORDER) { + m = 2; + p = 2; + } + + /* + * Look for filter and integrator oversampling ratios which allows + * to reach 24 bits data output resolution. + * Leave as soon as if exact resolution if reached. + * Otherwise the higher resolution below 32 bits is kept. + */ + for (fosr = 1; fosr <= DFSDM_MAX_FL_OVERSAMPLING; fosr++) { + for (iosr = 1; iosr <= DFSDM_MAX_INT_OVERSAMPLING; iosr++) { + if (fast) + d = fosr * iosr; + else if (fl->ford == DFSDM_FASTSINC_ORDER) + d = fosr * (iosr + 3) + 2; + else + d = fosr * (iosr - 1 + p) + p; + + if (d > oversamp) + break; + else if (d != oversamp) + continue; + /* + * Check resolution (limited to signed 32 bits) + * res <= 2^31 + * Sincx filters: + * res = m * fosr^p x iosr (with m=1, p=ford) + * FastSinc filter + * res = m * fosr^p x iosr (with m=2, p=2) + */ + res = fosr; + for (i = p - 1; i > 0; i--) { + res = res * (u64)fosr; + if (res > DFSDM_MAX_RES) + break; + } + if (res > DFSDM_MAX_RES) + continue; + res = res * (u64)m * (u64)iosr; + if (res > DFSDM_MAX_RES) + continue; + + delta = res - DFSDM_DATA_RES; + + if (res >= fl->res) { + fl->res = res; + fl->fosr = fosr; + fl->iosr = iosr; + fl->fast = fast; + pr_debug("%s: fosr = %d, iosr = %d\n", + __func__, fl->fosr, fl->iosr); + } + + if (!delta) + return 0; + } + } + + if (!fl->fosr) + return -EINVAL; + + return 0; +} + +static int stm32_dfsdm_start_channel(struct stm32_dfsdm *dfsdm, + unsigned int ch_id) +{ + return regmap_update_bits(dfsdm->regmap, DFSDM_CHCFGR1(ch_id), + DFSDM_CHCFGR1_CHEN_MASK, + DFSDM_CHCFGR1_CHEN(1)); +} + +static void stm32_dfsdm_stop_channel(struct stm32_dfsdm *dfsdm, + unsigned int ch_id) +{ + regmap_update_bits(dfsdm->regmap, DFSDM_CHCFGR1(ch_id), + DFSDM_CHCFGR1_CHEN_MASK, DFSDM_CHCFGR1_CHEN(0)); +} + +static int stm32_dfsdm_chan_configure(struct stm32_dfsdm *dfsdm, + struct stm32_dfsdm_channel *ch) +{ + unsigned int id = ch->id; + struct regmap *regmap = dfsdm->regmap; + int ret; + + ret = regmap_update_bits(regmap, DFSDM_CHCFGR1(id), + DFSDM_CHCFGR1_SITP_MASK, + DFSDM_CHCFGR1_SITP(ch->type)); + if (ret < 0) + return ret; + ret = regmap_update_bits(regmap, DFSDM_CHCFGR1(id), + DFSDM_CHCFGR1_SPICKSEL_MASK, + DFSDM_CHCFGR1_SPICKSEL(ch->src)); + if (ret < 0) + return ret; + return regmap_update_bits(regmap, DFSDM_CHCFGR1(id), + DFSDM_CHCFGR1_CHINSEL_MASK, + DFSDM_CHCFGR1_CHINSEL(ch->alt_si)); +} + +static int stm32_dfsdm_start_filter(struct stm32_dfsdm *dfsdm, + unsigned int fl_id) +{ + int ret; + + /* Enable filter */ + ret = regmap_update_bits(dfsdm->regmap, DFSDM_CR1(fl_id), + DFSDM_CR1_DFEN_MASK, DFSDM_CR1_DFEN(1)); + if (ret < 0) + return ret; + + /* Start conversion */ + return regmap_update_bits(dfsdm->regmap, DFSDM_CR1(fl_id), + DFSDM_CR1_RSWSTART_MASK, + DFSDM_CR1_RSWSTART(1)); +} + +void stm32_dfsdm_stop_filter(struct stm32_dfsdm *dfsdm, unsigned int fl_id) +{ + /* Disable conversion */ + regmap_update_bits(dfsdm->regmap, DFSDM_CR1(fl_id), + DFSDM_CR1_DFEN_MASK, DFSDM_CR1_DFEN(0)); +} + +static int stm32_dfsdm_filter_configure(struct stm32_dfsdm *dfsdm, + unsigned int fl_id, unsigned int ch_id) +{ + struct regmap *regmap = dfsdm->regmap; + struct stm32_dfsdm_filter *fl = &dfsdm->fl_list[fl_id]; + int ret; + + /* Average integrator oversampling */ + ret = regmap_update_bits(regmap, DFSDM_FCR(fl_id), DFSDM_FCR_IOSR_MASK, + DFSDM_FCR_IOSR(fl->iosr - 1)); + if (ret) + return ret; + + /* Filter order and Oversampling */ + ret = regmap_update_bits(regmap, DFSDM_FCR(fl_id), DFSDM_FCR_FOSR_MASK, + DFSDM_FCR_FOSR(fl->fosr - 1)); + if (ret) + return ret; + + ret = regmap_update_bits(regmap, DFSDM_FCR(fl_id), DFSDM_FCR_FORD_MASK, + DFSDM_FCR_FORD(fl->ford)); + if (ret) + return ret; + + /* No scan mode supported for the moment */ + ret = regmap_update_bits(regmap, DFSDM_CR1(fl_id), DFSDM_CR1_RCH_MASK, + DFSDM_CR1_RCH(ch_id)); + if (ret) + return ret; + + return regmap_update_bits(regmap, DFSDM_CR1(fl_id), + DFSDM_CR1_RSYNC_MASK, + DFSDM_CR1_RSYNC(fl->sync_mode)); +} + +int stm32_dfsdm_channel_parse_of(struct stm32_dfsdm *dfsdm, + struct iio_dev *indio_dev, + struct iio_chan_spec *ch) +{ + struct stm32_dfsdm_channel *df_ch; + const char *of_str; + int chan_idx = ch->scan_index; + int ret, val; + + ret = of_property_read_u32_index(indio_dev->dev.of_node, + "st,adc-channels", chan_idx, + &ch->channel); + if (ret < 0) { + dev_err(&indio_dev->dev, + " Error parsing 'st,adc-channels' for idx %d\n", + chan_idx); + return ret; + } + if (ch->channel >= dfsdm->num_chs) { + dev_err(&indio_dev->dev, + " Error bad channel number %d (max = %d)\n", + ch->channel, dfsdm->num_chs); + return -EINVAL; + } + + ret = of_property_read_string_index(indio_dev->dev.of_node, + "st,adc-channel-names", chan_idx, + &ch->datasheet_name); + if (ret < 0) { + dev_err(&indio_dev->dev, + " Error parsing 'st,adc-channel-names' for idx %d\n", + chan_idx); + return ret; + } + + df_ch = &dfsdm->ch_list[ch->channel]; + df_ch->id = ch->channel; + + ret = of_property_read_string_index(indio_dev->dev.of_node, + "st,adc-channel-types", chan_idx, + &of_str); + if (!ret) { + val = stm32_dfsdm_str2val(of_str, stm32_dfsdm_chan_type); + if (val < 0) + return val; + } else { + val = 0; + } + df_ch->type = val; + + ret = of_property_read_string_index(indio_dev->dev.of_node, + "st,adc-channel-clk-src", chan_idx, + &of_str); + if (!ret) { + val = stm32_dfsdm_str2val(of_str, stm32_dfsdm_chan_src); + if (val < 0) + return val; + } else { + val = 0; + } + df_ch->src = val; + + ret = of_property_read_u32_index(indio_dev->dev.of_node, + "st,adc-alt-channel", chan_idx, + &df_ch->alt_si); + if (ret < 0) + df_ch->alt_si = 0; + + return 0; +} + +static int stm32_dfsdm_start_conv(struct stm32_dfsdm_adc *adc, bool dma) +{ + struct regmap *regmap = adc->dfsdm->regmap; + int ret; + + ret = stm32_dfsdm_start_channel(adc->dfsdm, adc->ch_id); + if (ret < 0) + return ret; + + ret = stm32_dfsdm_filter_configure(adc->dfsdm, adc->fl_id, + adc->ch_id); + if (ret < 0) + goto stop_channels; + + ret = stm32_dfsdm_start_filter(adc->dfsdm, adc->fl_id); + if (ret < 0) + goto stop_channels; + + return 0; + +stop_channels: + regmap_update_bits(regmap, DFSDM_CR1(adc->fl_id), + DFSDM_CR1_RDMAEN_MASK, 0); + + regmap_update_bits(regmap, DFSDM_CR1(adc->fl_id), + DFSDM_CR1_RCONT_MASK, 0); + stm32_dfsdm_stop_channel(adc->dfsdm, adc->fl_id); + + return ret; +} + +static void stm32_dfsdm_stop_conv(struct stm32_dfsdm_adc *adc) +{ + struct regmap *regmap = adc->dfsdm->regmap; + + stm32_dfsdm_stop_filter(adc->dfsdm, adc->fl_id); + + /* Clean conversion options */ + regmap_update_bits(regmap, DFSDM_CR1(adc->fl_id), + DFSDM_CR1_RDMAEN_MASK, 0); + + regmap_update_bits(regmap, DFSDM_CR1(adc->fl_id), + DFSDM_CR1_RCONT_MASK, 0); + + stm32_dfsdm_stop_channel(adc->dfsdm, adc->ch_id); +} + +static int stm32_dfsdm_single_conv(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int *res) +{ + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + long timeout; + int ret; + + reinit_completion(&adc->completion); + + adc->buffer = res; + + ret = stm32_dfsdm_start_dfsdm(adc->dfsdm); + if (ret < 0) + return ret; + + ret = regmap_update_bits(adc->dfsdm->regmap, DFSDM_CR2(adc->fl_id), + DFSDM_CR2_REOCIE_MASK, DFSDM_CR2_REOCIE(1)); + if (ret < 0) + goto stop_dfsdm; + + ret = stm32_dfsdm_start_conv(adc, false); + if (ret < 0) { + regmap_update_bits(adc->dfsdm->regmap, DFSDM_CR2(adc->fl_id), + DFSDM_CR2_REOCIE_MASK, DFSDM_CR2_REOCIE(0)); + goto stop_dfsdm; + } + + timeout = wait_for_completion_interruptible_timeout(&adc->completion, + DFSDM_TIMEOUT); + + /* Mask IRQ for regular conversion achievement*/ + regmap_update_bits(adc->dfsdm->regmap, DFSDM_CR2(adc->fl_id), + DFSDM_CR2_REOCIE_MASK, DFSDM_CR2_REOCIE(0)); + + if (timeout == 0) + ret = -ETIMEDOUT; + else if (timeout < 0) + ret = timeout; + else + ret = IIO_VAL_INT; + + stm32_dfsdm_stop_conv(adc); + +stop_dfsdm: + stm32_dfsdm_stop_dfsdm(adc->dfsdm); + + return ret; +} + +static int stm32_dfsdm_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + struct stm32_dfsdm_filter *fl = &adc->dfsdm->fl_list[adc->fl_id]; + int ret = -EINVAL; + + if (mask == IIO_CHAN_INFO_OVERSAMPLING_RATIO) { + ret = stm32_dfsdm_set_osrs(fl, 0, val); + if (!ret) + adc->oversamp = val; + } + + return ret; +} + +static int stm32_dfsdm_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = iio_hw_consumer_enable(adc->hwc); + if (ret < 0) { + dev_err(&indio_dev->dev, + "%s: IIO enable failed (channel %d)\n", + __func__, chan->channel); + return ret; + } + ret = stm32_dfsdm_single_conv(indio_dev, chan, val); + iio_hw_consumer_disable(adc->hwc); + if (ret < 0) { + dev_err(&indio_dev->dev, + "%s: Conversion failed (channel %d)\n", + __func__, chan->channel); + return ret; + } + return IIO_VAL_INT; + + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *val = adc->oversamp; + + return IIO_VAL_INT; + } + + return -EINVAL; +} + +static const struct iio_info stm32_dfsdm_info_adc = { + .read_raw = stm32_dfsdm_read_raw, + .write_raw = stm32_dfsdm_write_raw, +}; + +static irqreturn_t stm32_dfsdm_irq(int irq, void *arg) +{ + struct stm32_dfsdm_adc *adc = arg; + struct iio_dev *indio_dev = iio_priv_to_dev(adc); + struct regmap *regmap = adc->dfsdm->regmap; + unsigned int status, int_en; + + regmap_read(regmap, DFSDM_ISR(adc->fl_id), &status); + regmap_read(regmap, DFSDM_CR2(adc->fl_id), &int_en); + + if (status & DFSDM_ISR_REOCF_MASK) { + /* Read the data register clean the IRQ status */ + regmap_read(regmap, DFSDM_RDATAR(adc->fl_id), adc->buffer); + complete(&adc->completion); + } + + if (status & DFSDM_ISR_ROVRF_MASK) { + if (int_en & DFSDM_CR2_ROVRIE_MASK) + dev_warn(&indio_dev->dev, "Overrun detected\n"); + regmap_update_bits(regmap, DFSDM_ICR(adc->fl_id), + DFSDM_ICR_CLRROVRF_MASK, + DFSDM_ICR_CLRROVRF_MASK); + } + + return IRQ_HANDLED; +} + +static int stm32_dfsdm_adc_chan_init_one(struct iio_dev *indio_dev, + struct iio_chan_spec *ch) +{ + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + int ret; + + ret = stm32_dfsdm_channel_parse_of(adc->dfsdm, indio_dev, ch); + if (ret < 0) + return ret; + + ch->type = IIO_VOLTAGE; + ch->indexed = 1; + + /* + * IIO_CHAN_INFO_RAW: used to compute regular conversion + * IIO_CHAN_INFO_OVERSAMPLING_RATIO: used to set oversampling + */ + ch->info_mask_separate = BIT(IIO_CHAN_INFO_RAW); + ch->info_mask_shared_by_all = BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO); + + ch->scan_type.sign = 'u'; + ch->scan_type.realbits = 24; + ch->scan_type.storagebits = 32; + adc->ch_id = ch->channel; + + return stm32_dfsdm_chan_configure(adc->dfsdm, + &adc->dfsdm->ch_list[ch->channel]); +} + +static int stm32_dfsdm_adc_init(struct iio_dev *indio_dev) +{ + struct iio_chan_spec *ch; + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + int num_ch; + int ret, chan_idx; + + adc->oversamp = DFSDM_DEFAULT_OVERSAMPLING; + ret = stm32_dfsdm_set_osrs(&adc->dfsdm->fl_list[adc->fl_id], 0, + adc->oversamp); + if (ret < 0) + return ret; + + num_ch = of_property_count_u32_elems(indio_dev->dev.of_node, + "st,adc-channels"); + if (num_ch < 0 || num_ch > adc->dfsdm->num_chs) { + dev_err(&indio_dev->dev, "Bad st,adc-channels\n"); + return num_ch < 0 ? num_ch : -EINVAL; + } + + /* Bind to SD modulator IIO device */ + adc->hwc = devm_iio_hw_consumer_alloc(&indio_dev->dev); + if (IS_ERR(adc->hwc)) + return -EPROBE_DEFER; + + ch = devm_kcalloc(&indio_dev->dev, num_ch, sizeof(*ch), + GFP_KERNEL); + if (!ch) + return -ENOMEM; + + for (chan_idx = 0; chan_idx < num_ch; chan_idx++) { + ch->scan_index = chan_idx; + ret = stm32_dfsdm_adc_chan_init_one(indio_dev, ch); + if (ret < 0) { + dev_err(&indio_dev->dev, "Channels init failed\n"); + return ret; + } + } + + indio_dev->num_channels = num_ch; + indio_dev->channels = ch; + + init_completion(&adc->completion); + + return 0; +} + +static const struct stm32_dfsdm_dev_data stm32h7_dfsdm_adc_data = { + .type = DFSDM_IIO, + .init = stm32_dfsdm_adc_init, +}; + +static const struct of_device_id stm32_dfsdm_adc_match[] = { + { .compatible = "st,stm32-dfsdm-adc", + .data = &stm32h7_dfsdm_adc_data, + }, + {} +}; + +static int stm32_dfsdm_adc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct stm32_dfsdm_adc *adc; + struct device_node *np = dev->of_node; + const struct stm32_dfsdm_dev_data *dev_data; + struct iio_dev *iio; + const struct of_device_id *of_id; + char *name; + int ret, irq, val; + + of_id = of_match_node(stm32_dfsdm_adc_match, np); + if (!of_id->data) { + dev_err(&pdev->dev, "Data associated to device is missing\n"); + return -EINVAL; + } + + dev_data = (const struct stm32_dfsdm_dev_data *)of_id->data; + + iio = devm_iio_device_alloc(dev, sizeof(*adc)); + if (IS_ERR(iio)) { + dev_err(dev, "%s: Failed to allocate IIO\n", __func__); + return PTR_ERR(iio); + } + + adc = iio_priv(iio); + if (IS_ERR(adc)) { + dev_err(dev, "%s: Failed to allocate ADC\n", __func__); + return PTR_ERR(adc); + } + adc->dfsdm = dev_get_drvdata(dev->parent); + + iio->dev.parent = dev; + iio->dev.of_node = np; + iio->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_SOFTWARE; + + platform_set_drvdata(pdev, adc); + + ret = of_property_read_u32(dev->of_node, "reg", &adc->fl_id); + if (ret != 0) { + dev_err(dev, "Missing reg property\n"); + return -EINVAL; + } + + name = devm_kzalloc(dev, sizeof("dfsdm-adc0"), GFP_KERNEL); + if (!name) + return -ENOMEM; + iio->info = &stm32_dfsdm_info_adc; + snprintf(name, sizeof("dfsdm-adc0"), "dfsdm-adc%d", adc->fl_id); + iio->name = name; + + /* + * In a first step IRQs generated for channels are not treated. + * So IRQ associated to filter instance 0 is dedicated to the Filter 0. + */ + irq = platform_get_irq(pdev, 0); + ret = devm_request_irq(dev, irq, stm32_dfsdm_irq, + 0, pdev->name, adc); + if (ret < 0) { + dev_err(dev, "Failed to request IRQ\n"); + return ret; + } + + ret = of_property_read_u32(dev->of_node, "st,filter-order", &val); + if (ret < 0) { + dev_err(dev, "Failed to set filter order\n"); + return ret; + } + + adc->dfsdm->fl_list[adc->fl_id].ford = val; + + ret = of_property_read_u32(dev->of_node, "st,filter0-sync", &val); + if (!ret) + adc->dfsdm->fl_list[adc->fl_id].sync_mode = val; + + adc->dev_data = dev_data; + ret = dev_data->init(iio); + if (ret < 0) + return ret; + + return iio_device_register(iio); +} + +static int stm32_dfsdm_adc_remove(struct platform_device *pdev) +{ + struct stm32_dfsdm_adc *adc = platform_get_drvdata(pdev); + struct iio_dev *indio_dev = iio_priv_to_dev(adc); + + iio_device_unregister(indio_dev); + + return 0; +} + +static struct platform_driver stm32_dfsdm_adc_driver = { + .driver = { + .name = "stm32-dfsdm-adc", + .of_match_table = stm32_dfsdm_adc_match, + }, + .probe = stm32_dfsdm_adc_probe, + .remove = stm32_dfsdm_adc_remove, + +}; +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");
Add DFSDM driver to handle sigma delta ADC. Signed-off-by: Arnaud Pouliquen <arnaud.pouliquen@st.com> --- drivers/iio/adc/Kconfig | 13 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/stm32-dfsdm-adc.c | 741 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 755 insertions(+) create mode 100644 drivers/iio/adc/stm32-dfsdm-adc.c