| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * 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>. |
| */ |
| |
| #include <linux/dmaengine.h> |
| #include <linux/dma-mapping.h> |
| #include <linux/iio/adc/stm32-dfsdm-adc.h> |
| #include <linux/iio/buffer.h> |
| #include <linux/iio/hw-consumer.h> |
| #include <linux/iio/sysfs.h> |
| #include <linux/iio/timer/stm32-lptim-trigger.h> |
| #include <linux/iio/timer/stm32-timer-trigger.h> |
| #include <linux/iio/trigger.h> |
| #include <linux/iio/trigger_consumer.h> |
| #include <linux/iio/triggered_buffer.h> |
| #include <linux/interrupt.h> |
| #include <linux/module.h> |
| #include <linux/of.h> |
| #include <linux/of_platform.h> |
| #include <linux/platform_device.h> |
| #include <linux/regmap.h> |
| #include <linux/slab.h> |
| |
| #include "stm32-dfsdm.h" |
| |
| #define DFSDM_DMA_BUFFER_SIZE (4 * PAGE_SIZE) |
| |
| /* 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 |
| |
| /* Limit filter output resolution to 31 bits. (i.e. sample range is +/-2^30) */ |
| #define DFSDM_DATA_MAX BIT(30) |
| /* |
| * Data are output as two's complement data in a 24 bit field. |
| * Data from filters are in the range +/-2^(n-1) |
| * 2^(n-1) maximum positive value cannot be coded in 2's complement n bits |
| * An extra bit is required to avoid wrap-around of the binary code for 2^(n-1) |
| * So, the resolution of samples from filter is actually limited to 23 bits |
| */ |
| #define DFSDM_DATA_RES 24 |
| |
| /* Filter configuration */ |
| #define DFSDM_CR1_CFG_MASK (DFSDM_CR1_RCH_MASK | DFSDM_CR1_RCONT_MASK | \ |
| DFSDM_CR1_RSYNC_MASK | DFSDM_CR1_JSYNC_MASK | \ |
| DFSDM_CR1_JSCAN_MASK) |
| |
| enum sd_converter_type { |
| DFSDM_AUDIO, |
| DFSDM_IIO, |
| }; |
| |
| struct stm32_dfsdm_dev_data { |
| int type; |
| int (*init)(struct device *dev, 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 nconv; |
| unsigned long smask; |
| |
| /* ADC specific */ |
| unsigned int oversamp; |
| struct iio_hw_consumer *hwc; |
| struct completion completion; |
| u32 *buffer; |
| |
| /* Audio specific */ |
| unsigned int spi_freq; /* SPI bus clock frequency */ |
| unsigned int sample_freq; /* Sample frequency after filter decimation */ |
| int (*cb)(const void *data, size_t size, void *cb_priv); |
| void *cb_priv; |
| |
| /* DMA */ |
| u8 *rx_buf; |
| unsigned int bufi; /* Buffer current position */ |
| unsigned int buf_sz; /* Buffer size */ |
| struct dma_chan *dma_chan; |
| dma_addr_t dma_buf; |
| }; |
| |
| 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; |
| } |
| |
| /** |
| * struct stm32_dfsdm_trig_info - DFSDM trigger info |
| * @name: name of the trigger, corresponding to its source |
| * @jextsel: trigger signal selection |
| */ |
| struct stm32_dfsdm_trig_info { |
| const char *name; |
| unsigned int jextsel; |
| }; |
| |
| /* hardware injected trigger enable, edge selection */ |
| enum stm32_dfsdm_jexten { |
| STM32_DFSDM_JEXTEN_DISABLED, |
| STM32_DFSDM_JEXTEN_RISING_EDGE, |
| STM32_DFSDM_JEXTEN_FALLING_EDGE, |
| STM32_DFSDM_EXTEN_BOTH_EDGES, |
| }; |
| |
| static const struct stm32_dfsdm_trig_info stm32_dfsdm_trigs[] = { |
| { TIM1_TRGO, 0 }, |
| { TIM1_TRGO2, 1 }, |
| { TIM8_TRGO, 2 }, |
| { TIM8_TRGO2, 3 }, |
| { TIM3_TRGO, 4 }, |
| { TIM4_TRGO, 5 }, |
| { TIM16_OC1, 6 }, |
| { TIM6_TRGO, 7 }, |
| { TIM7_TRGO, 8 }, |
| { LPTIM1_OUT, 26 }, |
| { LPTIM2_OUT, 27 }, |
| { LPTIM3_OUT, 28 }, |
| {}, |
| }; |
| |
| static int stm32_dfsdm_get_jextsel(struct iio_dev *indio_dev, |
| struct iio_trigger *trig) |
| { |
| int i; |
| |
| /* lookup triggers registered by stm32 timer trigger driver */ |
| for (i = 0; stm32_dfsdm_trigs[i].name; i++) { |
| /** |
| * Checking both stm32 timer trigger type and trig name |
| * should be safe against arbitrary trigger names. |
| */ |
| if ((is_stm32_timer_trigger(trig) || |
| is_stm32_lptim_trigger(trig)) && |
| !strcmp(stm32_dfsdm_trigs[i].name, trig->name)) { |
| return stm32_dfsdm_trigs[i].jextsel; |
| } |
| } |
| |
| return -EINVAL; |
| } |
| |
| static int stm32_dfsdm_compute_osrs(struct stm32_dfsdm_filter *fl, |
| unsigned int fast, unsigned int oversamp) |
| { |
| unsigned int i, d, fosr, iosr; |
| u64 res, max; |
| int bits, shift; |
| unsigned int m = 1; /* multiplication factor */ |
| unsigned int p = fl->ford; /* filter order (ford) */ |
| struct stm32_dfsdm_filter_osr *flo = &fl->flo[fast]; |
| |
| pr_debug("Requested oversampling: %d\n", 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 maximize data output resolution. |
| */ |
| 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_DATA_MAX) |
| break; |
| } |
| if (res > DFSDM_DATA_MAX) |
| continue; |
| |
| res = res * (u64)m * (u64)iosr; |
| if (res > DFSDM_DATA_MAX) |
| continue; |
| |
| if (res >= flo->res) { |
| flo->res = res; |
| flo->fosr = fosr; |
| flo->iosr = iosr; |
| |
| bits = fls(flo->res); |
| /* 8 LBSs in data register contain chan info */ |
| max = flo->res << 8; |
| |
| /* if resolution is not a power of two */ |
| if (flo->res > BIT(bits - 1)) |
| bits++; |
| else |
| max--; |
| |
| shift = DFSDM_DATA_RES - bits; |
| /* |
| * Compute right/left shift |
| * Right shift is performed by hardware |
| * when transferring samples to data register. |
| * Left shift is done by software on buffer |
| */ |
| if (shift > 0) { |
| /* Resolution is lower than 24 bits */ |
| flo->rshift = 0; |
| flo->lshift = shift; |
| } else { |
| /* |
| * If resolution is 24 bits or more, |
| * max positive value may be ambiguous |
| * (equal to max negative value as sign |
| * bit is dropped). |
| * Reduce resolution to 23 bits (rshift) |
| * to keep the sign on bit 23 and treat |
| * saturation before rescaling on 24 |
| * bits (lshift). |
| */ |
| flo->rshift = 1 - shift; |
| flo->lshift = 1; |
| max >>= flo->rshift; |
| } |
| flo->max = (s32)max; |
| flo->bits = bits; |
| |
| pr_debug("fast %d, fosr %d, iosr %d, res 0x%llx/%d bits, rshift %d, lshift %d\n", |
| fast, flo->fosr, flo->iosr, |
| flo->res, bits, flo->rshift, |
| flo->lshift); |
| } |
| } |
| } |
| |
| if (!flo->res) |
| return -EINVAL; |
| |
| return 0; |
| } |
| |
| static int stm32_dfsdm_compute_all_osrs(struct iio_dev *indio_dev, |
| unsigned int oversamp) |
| { |
| struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); |
| struct stm32_dfsdm_filter *fl = &adc->dfsdm->fl_list[adc->fl_id]; |
| int ret0, ret1; |
| |
| memset(&fl->flo[0], 0, sizeof(fl->flo[0])); |
| memset(&fl->flo[1], 0, sizeof(fl->flo[1])); |
| |
| ret0 = stm32_dfsdm_compute_osrs(fl, 0, oversamp); |
| ret1 = stm32_dfsdm_compute_osrs(fl, 1, oversamp); |
| if (ret0 < 0 && ret1 < 0) { |
| dev_err(&indio_dev->dev, |
| "Filter parameters not found: errors %d/%d\n", |
| ret0, ret1); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static int stm32_dfsdm_start_channel(struct iio_dev *indio_dev) |
| { |
| struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); |
| struct regmap *regmap = adc->dfsdm->regmap; |
| const struct iio_chan_spec *chan; |
| unsigned int bit; |
| int ret; |
| |
| for_each_set_bit(bit, &adc->smask, sizeof(adc->smask) * BITS_PER_BYTE) { |
| chan = indio_dev->channels + bit; |
| ret = regmap_update_bits(regmap, DFSDM_CHCFGR1(chan->channel), |
| DFSDM_CHCFGR1_CHEN_MASK, |
| DFSDM_CHCFGR1_CHEN(1)); |
| if (ret < 0) |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static void stm32_dfsdm_stop_channel(struct iio_dev *indio_dev) |
| { |
| struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); |
| struct regmap *regmap = adc->dfsdm->regmap; |
| const struct iio_chan_spec *chan; |
| unsigned int bit; |
| |
| for_each_set_bit(bit, &adc->smask, sizeof(adc->smask) * BITS_PER_BYTE) { |
| chan = indio_dev->channels + bit; |
| regmap_update_bits(regmap, DFSDM_CHCFGR1(chan->channel), |
| 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_adc *adc, |
| unsigned int fl_id, |
| struct iio_trigger *trig) |
| { |
| struct stm32_dfsdm *dfsdm = adc->dfsdm; |
| 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; |
| |
| /* Nothing more to do for injected (scan mode/triggered) conversions */ |
| if (adc->nconv > 1 || trig) |
| return 0; |
| |
| /* Software start (single or continuous) regular conversion */ |
| return regmap_update_bits(dfsdm->regmap, DFSDM_CR1(fl_id), |
| DFSDM_CR1_RSWSTART_MASK, |
| DFSDM_CR1_RSWSTART(1)); |
| } |
| |
| static 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_set_trig(struct iio_dev *indio_dev, |
| unsigned int fl_id, |
| struct iio_trigger *trig) |
| { |
| struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); |
| struct regmap *regmap = adc->dfsdm->regmap; |
| u32 jextsel = 0, jexten = STM32_DFSDM_JEXTEN_DISABLED; |
| int ret; |
| |
| if (trig) { |
| ret = stm32_dfsdm_get_jextsel(indio_dev, trig); |
| if (ret < 0) |
| return ret; |
| |
| /* set trigger source and polarity (default to rising edge) */ |
| jextsel = ret; |
| jexten = STM32_DFSDM_JEXTEN_RISING_EDGE; |
| } |
| |
| ret = regmap_update_bits(regmap, DFSDM_CR1(fl_id), |
| DFSDM_CR1_JEXTSEL_MASK | DFSDM_CR1_JEXTEN_MASK, |
| DFSDM_CR1_JEXTSEL(jextsel) | |
| DFSDM_CR1_JEXTEN(jexten)); |
| if (ret < 0) |
| return ret; |
| |
| return 0; |
| } |
| |
| static int stm32_dfsdm_channels_configure(struct iio_dev *indio_dev, |
| unsigned int fl_id, |
| struct iio_trigger *trig) |
| { |
| struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); |
| struct regmap *regmap = adc->dfsdm->regmap; |
| struct stm32_dfsdm_filter *fl = &adc->dfsdm->fl_list[fl_id]; |
| struct stm32_dfsdm_filter_osr *flo = &fl->flo[0]; |
| const struct iio_chan_spec *chan; |
| unsigned int bit; |
| int ret; |
| |
| fl->fast = 0; |
| |
| /* |
| * In continuous mode, use fast mode configuration, |
| * if it provides a better resolution. |
| */ |
| if (adc->nconv == 1 && !trig && iio_buffer_enabled(indio_dev)) { |
| if (fl->flo[1].res >= fl->flo[0].res) { |
| fl->fast = 1; |
| flo = &fl->flo[1]; |
| } |
| } |
| |
| if (!flo->res) |
| return -EINVAL; |
| |
| dev_dbg(&indio_dev->dev, "Samples actual resolution: %d bits", |
| min(flo->bits, (u32)DFSDM_DATA_RES - 1)); |
| |
| for_each_set_bit(bit, &adc->smask, |
| sizeof(adc->smask) * BITS_PER_BYTE) { |
| chan = indio_dev->channels + bit; |
| |
| ret = regmap_update_bits(regmap, |
| DFSDM_CHCFGR2(chan->channel), |
| DFSDM_CHCFGR2_DTRBS_MASK, |
| DFSDM_CHCFGR2_DTRBS(flo->rshift)); |
| if (ret) |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int stm32_dfsdm_filter_configure(struct iio_dev *indio_dev, |
| unsigned int fl_id, |
| struct iio_trigger *trig) |
| { |
| struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); |
| struct regmap *regmap = adc->dfsdm->regmap; |
| struct stm32_dfsdm_filter *fl = &adc->dfsdm->fl_list[fl_id]; |
| struct stm32_dfsdm_filter_osr *flo = &fl->flo[fl->fast]; |
| u32 cr1; |
| const struct iio_chan_spec *chan; |
| unsigned int bit, jchg = 0; |
| int ret; |
| |
| /* Average integrator oversampling */ |
| ret = regmap_update_bits(regmap, DFSDM_FCR(fl_id), DFSDM_FCR_IOSR_MASK, |
| DFSDM_FCR_IOSR(flo->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(flo->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; |
| |
| ret = stm32_dfsdm_filter_set_trig(indio_dev, fl_id, trig); |
| if (ret) |
| return ret; |
| |
| ret = regmap_update_bits(regmap, DFSDM_CR1(fl_id), |
| DFSDM_CR1_FAST_MASK, |
| DFSDM_CR1_FAST(fl->fast)); |
| if (ret) |
| return ret; |
| |
| /* |
| * DFSDM modes configuration W.R.T audio/iio type modes |
| * ---------------------------------------------------------------- |
| * Modes | regular | regular | injected | injected | |
| * | | continuous | | + scan | |
| * --------------|---------|--------------|----------|------------| |
| * single conv | x | | | | |
| * (1 chan) | | | | | |
| * --------------|---------|--------------|----------|------------| |
| * 1 Audio chan | | sample freq | | | |
| * | | or sync_mode | | | |
| * --------------|---------|--------------|----------|------------| |
| * 1 IIO chan | | sample freq | trigger | | |
| * | | or sync_mode | | | |
| * --------------|---------|--------------|----------|------------| |
| * 2+ IIO chans | | | | trigger or | |
| * | | | | sync_mode | |
| * ---------------------------------------------------------------- |
| */ |
| if (adc->nconv == 1 && !trig) { |
| bit = __ffs(adc->smask); |
| chan = indio_dev->channels + bit; |
| |
| /* Use regular conversion for single channel without trigger */ |
| cr1 = DFSDM_CR1_RCH(chan->channel); |
| |
| /* Continuous conversions triggered by SPI clk in buffer mode */ |
| if (iio_buffer_enabled(indio_dev)) |
| cr1 |= DFSDM_CR1_RCONT(1); |
| |
| cr1 |= DFSDM_CR1_RSYNC(fl->sync_mode); |
| } else { |
| /* Use injected conversion for multiple channels */ |
| for_each_set_bit(bit, &adc->smask, |
| sizeof(adc->smask) * BITS_PER_BYTE) { |
| chan = indio_dev->channels + bit; |
| jchg |= BIT(chan->channel); |
| } |
| ret = regmap_write(regmap, DFSDM_JCHGR(fl_id), jchg); |
| if (ret < 0) |
| return ret; |
| |
| /* Use scan mode for multiple channels */ |
| cr1 = DFSDM_CR1_JSCAN((adc->nconv > 1) ? 1 : 0); |
| |
| /* |
| * Continuous conversions not supported in injected mode, |
| * either use: |
| * - conversions in sync with filter 0 |
| * - triggered conversions |
| */ |
| if (!fl->sync_mode && !trig) |
| return -EINVAL; |
| cr1 |= DFSDM_CR1_JSYNC(fl->sync_mode); |
| } |
| |
| return regmap_update_bits(regmap, DFSDM_CR1(fl_id), DFSDM_CR1_CFG_MASK, |
| cr1); |
| } |
| |
| static 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 ssize_t dfsdm_adc_audio_get_spiclk(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->spi_freq); |
| } |
| |
| static int dfsdm_adc_set_samp_freq(struct iio_dev *indio_dev, |
| unsigned int sample_freq, |
| unsigned int spi_freq) |
| { |
| struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); |
| unsigned int oversamp; |
| int ret; |
| |
| oversamp = DIV_ROUND_CLOSEST(spi_freq, sample_freq); |
| if (spi_freq % sample_freq) |
| dev_dbg(&indio_dev->dev, |
| "Rate not accurate. requested (%u), actual (%u)\n", |
| sample_freq, spi_freq / oversamp); |
| |
| ret = stm32_dfsdm_compute_all_osrs(indio_dev, oversamp); |
| if (ret < 0) |
| return ret; |
| |
| adc->sample_freq = spi_freq / oversamp; |
| adc->oversamp = oversamp; |
| |
| return 0; |
| } |
| |
| static ssize_t dfsdm_adc_audio_set_spiclk(struct iio_dev *indio_dev, |
| uintptr_t priv, |
| const struct iio_chan_spec *chan, |
| const char *buf, size_t len) |
| { |
| struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); |
| struct stm32_dfsdm_channel *ch = &adc->dfsdm->ch_list[chan->channel]; |
| unsigned int sample_freq = adc->sample_freq; |
| unsigned int spi_freq; |
| int ret; |
| |
| dev_err(&indio_dev->dev, "enter %s\n", __func__); |
| /* If DFSDM is master on SPI, SPI freq can not be updated */ |
| if (ch->src != DFSDM_CHANNEL_SPI_CLOCK_EXTERNAL) |
| return -EPERM; |
| |
| ret = kstrtoint(buf, 0, &spi_freq); |
| if (ret) |
| return ret; |
| |
| if (!spi_freq) |
| return -EINVAL; |
| |
| if (sample_freq) { |
| ret = dfsdm_adc_set_samp_freq(indio_dev, sample_freq, spi_freq); |
| if (ret < 0) |
| return ret; |
| } |
| adc->spi_freq = spi_freq; |
| |
| return len; |
| } |
| |
| static int stm32_dfsdm_start_conv(struct iio_dev *indio_dev, |
| struct iio_trigger *trig) |
| { |
| struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); |
| struct regmap *regmap = adc->dfsdm->regmap; |
| int ret; |
| |
| ret = stm32_dfsdm_channels_configure(indio_dev, adc->fl_id, trig); |
| if (ret < 0) |
| return ret; |
| |
| ret = stm32_dfsdm_start_channel(indio_dev); |
| if (ret < 0) |
| return ret; |
| |
| ret = stm32_dfsdm_filter_configure(indio_dev, adc->fl_id, trig); |
| if (ret < 0) |
| goto stop_channels; |
| |
| ret = stm32_dfsdm_start_filter(adc, adc->fl_id, trig); |
| if (ret < 0) |
| goto filter_unconfigure; |
| |
| return 0; |
| |
| filter_unconfigure: |
| regmap_update_bits(regmap, DFSDM_CR1(adc->fl_id), |
| DFSDM_CR1_CFG_MASK, 0); |
| stop_channels: |
| stm32_dfsdm_stop_channel(indio_dev); |
| |
| return ret; |
| } |
| |
| static void stm32_dfsdm_stop_conv(struct iio_dev *indio_dev) |
| { |
| struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); |
| struct regmap *regmap = adc->dfsdm->regmap; |
| |
| stm32_dfsdm_stop_filter(adc->dfsdm, adc->fl_id); |
| |
| regmap_update_bits(regmap, DFSDM_CR1(adc->fl_id), |
| DFSDM_CR1_CFG_MASK, 0); |
| |
| stm32_dfsdm_stop_channel(indio_dev); |
| } |
| |
| static int stm32_dfsdm_set_watermark(struct iio_dev *indio_dev, |
| unsigned int val) |
| { |
| struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); |
| unsigned int watermark = DFSDM_DMA_BUFFER_SIZE / 2; |
| unsigned int rx_buf_sz = DFSDM_DMA_BUFFER_SIZE; |
| |
| /* |
| * DMA cyclic transfers are used, buffer is split into two periods. |
| * There should be : |
| * - always one buffer (period) DMA is working on |
| * - one buffer (period) driver pushed to ASoC side. |
| */ |
| watermark = min(watermark, val * (unsigned int)(sizeof(u32))); |
| adc->buf_sz = min(rx_buf_sz, watermark * 2 * adc->nconv); |
| |
| return 0; |
| } |
| |
| static unsigned int stm32_dfsdm_adc_dma_residue(struct stm32_dfsdm_adc *adc) |
| { |
| struct dma_tx_state state; |
| enum dma_status status; |
| |
| status = dmaengine_tx_status(adc->dma_chan, |
| adc->dma_chan->cookie, |
| &state); |
| if (status == DMA_IN_PROGRESS) { |
| /* Residue is size in bytes from end of buffer */ |
| unsigned int i = adc->buf_sz - state.residue; |
| unsigned int size; |
| |
| /* Return available bytes */ |
| if (i >= adc->bufi) |
| size = i - adc->bufi; |
| else |
| size = adc->buf_sz + i - adc->bufi; |
| |
| return size; |
| } |
| |
| return 0; |
| } |
| |
| static inline void stm32_dfsdm_process_data(struct stm32_dfsdm_adc *adc, |
| s32 *buffer) |
| { |
| struct stm32_dfsdm_filter *fl = &adc->dfsdm->fl_list[adc->fl_id]; |
| struct stm32_dfsdm_filter_osr *flo = &fl->flo[fl->fast]; |
| unsigned int i = adc->nconv; |
| s32 *ptr = buffer; |
| |
| while (i--) { |
| /* Mask 8 LSB that contains the channel ID */ |
| *ptr &= 0xFFFFFF00; |
| /* Convert 2^(n-1) sample to 2^(n-1)-1 to avoid wrap-around */ |
| if (*ptr > flo->max) |
| *ptr -= 1; |
| /* |
| * Samples from filter are retrieved with 23 bits resolution |
| * or less. Shift left to align MSB on 24 bits. |
| */ |
| *ptr <<= flo->lshift; |
| |
| ptr++; |
| } |
| } |
| |
| static void stm32_dfsdm_dma_buffer_done(void *data) |
| { |
| struct iio_dev *indio_dev = data; |
| struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); |
| int available = stm32_dfsdm_adc_dma_residue(adc); |
| size_t old_pos; |
| |
| /* |
| * FIXME: In Kernel interface does not support cyclic DMA buffer,and |
| * offers only an interface to push data samples per samples. |
| * For this reason IIO buffer interface is not used and interface is |
| * bypassed using a private callback registered by ASoC. |
| * This should be a temporary solution waiting a cyclic DMA engine |
| * support in IIO. |
| */ |
| |
| dev_dbg(&indio_dev->dev, "pos = %d, available = %d\n", |
| adc->bufi, available); |
| old_pos = adc->bufi; |
| |
| while (available >= indio_dev->scan_bytes) { |
| s32 *buffer = (s32 *)&adc->rx_buf[adc->bufi]; |
| |
| stm32_dfsdm_process_data(adc, buffer); |
| |
| available -= indio_dev->scan_bytes; |
| adc->bufi += indio_dev->scan_bytes; |
| if (adc->bufi >= adc->buf_sz) { |
| if (adc->cb) |
| adc->cb(&adc->rx_buf[old_pos], |
| adc->buf_sz - old_pos, adc->cb_priv); |
| adc->bufi = 0; |
| old_pos = 0; |
| } |
| /* |
| * In DMA mode the trigger services of IIO are not used |
| * (e.g. no call to iio_trigger_poll). |
| * Calling irq handler associated to the hardware trigger is not |
| * relevant as the conversions have already been done. Data |
| * transfers are performed directly in DMA callback instead. |
| * This implementation avoids to call trigger irq handler that |
| * may sleep, in an atomic context (DMA irq handler context). |
| */ |
| if (adc->dev_data->type == DFSDM_IIO) |
| iio_push_to_buffers(indio_dev, buffer); |
| } |
| if (adc->cb) |
| adc->cb(&adc->rx_buf[old_pos], adc->bufi - old_pos, |
| adc->cb_priv); |
| } |
| |
| static int stm32_dfsdm_adc_dma_start(struct iio_dev *indio_dev) |
| { |
| struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); |
| /* |
| * The DFSDM supports half-word transfers. However, for 16 bits record, |
| * 4 bytes buswidth is kept, to avoid losing samples LSBs when left |
| * shift is required. |
| */ |
| struct dma_slave_config config = { |
| .src_addr = (dma_addr_t)adc->dfsdm->phys_base, |
| .src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES, |
| }; |
| struct dma_async_tx_descriptor *desc; |
| dma_cookie_t cookie; |
| int ret; |
| |
| if (!adc->dma_chan) |
| return -EINVAL; |
| |
| dev_dbg(&indio_dev->dev, "size=%d watermark=%d\n", |
| adc->buf_sz, adc->buf_sz / 2); |
| |
| if (adc->nconv == 1 && !indio_dev->trig) |
| config.src_addr += DFSDM_RDATAR(adc->fl_id); |
| else |
| config.src_addr += DFSDM_JDATAR(adc->fl_id); |
| ret = dmaengine_slave_config(adc->dma_chan, &config); |
| if (ret) |
| return ret; |
| |
| /* Prepare a DMA cyclic transaction */ |
| desc = dmaengine_prep_dma_cyclic(adc->dma_chan, |
| adc->dma_buf, |
| adc->buf_sz, adc->buf_sz / 2, |
| DMA_DEV_TO_MEM, |
| DMA_PREP_INTERRUPT); |
| if (!desc) |
| return -EBUSY; |
| |
| desc->callback = stm32_dfsdm_dma_buffer_done; |
| desc->callback_param = indio_dev; |
| |
| cookie = dmaengine_submit(desc); |
| ret = dma_submit_error(cookie); |
| if (ret) |
| goto err_stop_dma; |
| |
| /* Issue pending DMA requests */ |
| dma_async_issue_pending(adc->dma_chan); |
| |
| if (adc->nconv == 1 && !indio_dev->trig) { |
| /* Enable regular DMA transfer*/ |
| ret = regmap_update_bits(adc->dfsdm->regmap, |
| DFSDM_CR1(adc->fl_id), |
| DFSDM_CR1_RDMAEN_MASK, |
| DFSDM_CR1_RDMAEN_MASK); |
| } else { |
| /* Enable injected DMA transfer*/ |
| ret = regmap_update_bits(adc->dfsdm->regmap, |
| DFSDM_CR1(adc->fl_id), |
| DFSDM_CR1_JDMAEN_MASK, |
| DFSDM_CR1_JDMAEN_MASK); |
| } |
| |
| if (ret < 0) |
| goto err_stop_dma; |
| |
| return 0; |
| |
| err_stop_dma: |
| dmaengine_terminate_all(adc->dma_chan); |
| |
| return ret; |
| } |
| |
| static void stm32_dfsdm_adc_dma_stop(struct iio_dev *indio_dev) |
| { |
| struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); |
| |
| if (!adc->dma_chan) |
| return; |
| |
| regmap_update_bits(adc->dfsdm->regmap, DFSDM_CR1(adc->fl_id), |
| DFSDM_CR1_RDMAEN_MASK | DFSDM_CR1_JDMAEN_MASK, 0); |
| dmaengine_terminate_all(adc->dma_chan); |
| } |
| |
| static int stm32_dfsdm_update_scan_mode(struct iio_dev *indio_dev, |
| const unsigned long *scan_mask) |
| { |
| struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); |
| |
| adc->nconv = bitmap_weight(scan_mask, indio_dev->masklength); |
| adc->smask = *scan_mask; |
| |
| dev_dbg(&indio_dev->dev, "nconv=%d mask=%lx\n", adc->nconv, *scan_mask); |
| |
| return 0; |
| } |
| |
| static int stm32_dfsdm_postenable(struct iio_dev *indio_dev) |
| { |
| struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); |
| int ret; |
| |
| /* Reset adc buffer index */ |
| adc->bufi = 0; |
| |
| if (adc->hwc) { |
| ret = iio_hw_consumer_enable(adc->hwc); |
| if (ret < 0) |
| return ret; |
| } |
| |
| ret = stm32_dfsdm_start_dfsdm(adc->dfsdm); |
| if (ret < 0) |
| goto err_stop_hwc; |
| |
| ret = stm32_dfsdm_adc_dma_start(indio_dev); |
| if (ret) { |
| dev_err(&indio_dev->dev, "Can't start DMA\n"); |
| goto stop_dfsdm; |
| } |
| |
| ret = stm32_dfsdm_start_conv(indio_dev, indio_dev->trig); |
| if (ret) { |
| dev_err(&indio_dev->dev, "Can't start conversion\n"); |
| goto err_stop_dma; |
| } |
| |
| return 0; |
| |
| err_stop_dma: |
| stm32_dfsdm_adc_dma_stop(indio_dev); |
| stop_dfsdm: |
| stm32_dfsdm_stop_dfsdm(adc->dfsdm); |
| err_stop_hwc: |
| if (adc->hwc) |
| iio_hw_consumer_disable(adc->hwc); |
| |
| return ret; |
| } |
| |
| static int stm32_dfsdm_predisable(struct iio_dev *indio_dev) |
| { |
| struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); |
| |
| stm32_dfsdm_stop_conv(indio_dev); |
| |
| stm32_dfsdm_adc_dma_stop(indio_dev); |
| |
| stm32_dfsdm_stop_dfsdm(adc->dfsdm); |
| |
| if (adc->hwc) |
| iio_hw_consumer_disable(adc->hwc); |
| |
| return 0; |
| } |
| |
| static const struct iio_buffer_setup_ops stm32_dfsdm_buffer_setup_ops = { |
| .postenable = &stm32_dfsdm_postenable, |
| .predisable = &stm32_dfsdm_predisable, |
| }; |
| |
| /** |
| * stm32_dfsdm_get_buff_cb() - register a callback that will be called when |
| * DMA transfer period is achieved. |
| * |
| * @iio_dev: Handle to IIO device. |
| * @cb: Pointer to callback function: |
| * - data: pointer to data buffer |
| * - size: size in byte of the data buffer |
| * - private: pointer to consumer private structure. |
| * @private: Pointer to consumer private structure. |
| */ |
| int stm32_dfsdm_get_buff_cb(struct iio_dev *iio_dev, |
| int (*cb)(const void *data, size_t size, |
| void *private), |
| void *private) |
| { |
| struct stm32_dfsdm_adc *adc; |
| |
| if (!iio_dev) |
| return -EINVAL; |
| adc = iio_priv(iio_dev); |
| |
| adc->cb = cb; |
| adc->cb_priv = private; |
| |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(stm32_dfsdm_get_buff_cb); |
| |
| /** |
| * stm32_dfsdm_release_buff_cb - unregister buffer callback |
| * |
| * @iio_dev: Handle to IIO device. |
| */ |
| int stm32_dfsdm_release_buff_cb(struct iio_dev *iio_dev) |
| { |
| struct stm32_dfsdm_adc *adc; |
| |
| if (!iio_dev) |
| return -EINVAL; |
| adc = iio_priv(iio_dev); |
| |
| adc->cb = NULL; |
| adc->cb_priv = NULL; |
| |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(stm32_dfsdm_release_buff_cb); |
| |
| 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; |
| |
| adc->nconv = 1; |
| adc->smask = BIT(chan->scan_index); |
| ret = stm32_dfsdm_start_conv(indio_dev, NULL); |
| 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(indio_dev); |
| |
| stm32_dfsdm_process_data(adc, res); |
| |
| 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_channel *ch = &adc->dfsdm->ch_list[chan->channel]; |
| unsigned int spi_freq; |
| int ret = -EINVAL; |
| |
| switch (ch->src) { |
| case DFSDM_CHANNEL_SPI_CLOCK_INTERNAL: |
| spi_freq = adc->dfsdm->spi_master_freq; |
| break; |
| case DFSDM_CHANNEL_SPI_CLOCK_INTERNAL_DIV2_FALLING: |
| case DFSDM_CHANNEL_SPI_CLOCK_INTERNAL_DIV2_RISING: |
| spi_freq = adc->dfsdm->spi_master_freq / 2; |
| break; |
| default: |
| spi_freq = adc->spi_freq; |
| } |
| |
| switch (mask) { |
| case IIO_CHAN_INFO_OVERSAMPLING_RATIO: |
| ret = iio_device_claim_direct_mode(indio_dev); |
| if (ret) |
| return ret; |
| |
| ret = stm32_dfsdm_compute_all_osrs(indio_dev, val); |
| if (!ret) { |
| dev_dbg(&indio_dev->dev, |
| "Sampling rate changed from (%u) to (%u)\n", |
| adc->sample_freq, spi_freq / val); |
| adc->oversamp = val; |
| adc->sample_freq = spi_freq / val; |
| } |
| iio_device_release_direct_mode(indio_dev); |
| return ret; |
| |
| case IIO_CHAN_INFO_SAMP_FREQ: |
| if (!val) |
| return -EINVAL; |
| |
| ret = iio_device_claim_direct_mode(indio_dev); |
| if (ret) |
| return ret; |
| |
| ret = dfsdm_adc_set_samp_freq(indio_dev, val, spi_freq); |
| iio_device_release_direct_mode(indio_dev); |
| return ret; |
| } |
| |
| return -EINVAL; |
| } |
| |
| 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_device_claim_direct_mode(indio_dev); |
| if (ret) |
| return ret; |
| 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); |
| iio_device_release_direct_mode(indio_dev); |
| 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); |
| iio_device_release_direct_mode(indio_dev); |
| return ret; |
| } |
| iio_device_release_direct_mode(indio_dev); |
| return IIO_VAL_INT; |
| |
| case IIO_CHAN_INFO_OVERSAMPLING_RATIO: |
| *val = adc->oversamp; |
| |
| return IIO_VAL_INT; |
| |
| case IIO_CHAN_INFO_SAMP_FREQ: |
| *val = adc->sample_freq; |
| |
| return IIO_VAL_INT; |
| } |
| |
| return -EINVAL; |
| } |
| |
| static int stm32_dfsdm_validate_trigger(struct iio_dev *indio_dev, |
| struct iio_trigger *trig) |
| { |
| return stm32_dfsdm_get_jextsel(indio_dev, trig) < 0 ? -EINVAL : 0; |
| } |
| |
| static const struct iio_info stm32_dfsdm_info_audio = { |
| .hwfifo_set_watermark = stm32_dfsdm_set_watermark, |
| .read_raw = stm32_dfsdm_read_raw, |
| .write_raw = stm32_dfsdm_write_raw, |
| .update_scan_mode = stm32_dfsdm_update_scan_mode, |
| }; |
| |
| static const struct iio_info stm32_dfsdm_info_adc = { |
| .hwfifo_set_watermark = stm32_dfsdm_set_watermark, |
| .read_raw = stm32_dfsdm_read_raw, |
| .write_raw = stm32_dfsdm_write_raw, |
| .update_scan_mode = stm32_dfsdm_update_scan_mode, |
| .validate_trigger = stm32_dfsdm_validate_trigger, |
| }; |
| |
| static irqreturn_t stm32_dfsdm_irq(int irq, void *arg) |
| { |
| struct iio_dev *indio_dev = arg; |
| struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); |
| 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; |
| } |
| |
| /* |
| * Define external info for SPI Frequency and audio sampling rate that can be |
| * configured by ASoC driver through consumer.h API |
| */ |
| static const struct iio_chan_spec_ext_info dfsdm_adc_audio_ext_info[] = { |
| /* spi_clk_freq : clock freq on SPI/manchester bus used by channel */ |
| { |
| .name = "spi_clk_freq", |
| .shared = IIO_SHARED_BY_TYPE, |
| .read = dfsdm_adc_audio_get_spiclk, |
| .write = dfsdm_adc_audio_set_spiclk, |
| }, |
| {}, |
| }; |
| |
| static void stm32_dfsdm_dma_release(struct iio_dev *indio_dev) |
| { |
| struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); |
| |
| if (adc->dma_chan) { |
| dma_free_coherent(adc->dma_chan->device->dev, |
| DFSDM_DMA_BUFFER_SIZE, |
| adc->rx_buf, adc->dma_buf); |
| dma_release_channel(adc->dma_chan); |
| } |
| } |
| |
| static int stm32_dfsdm_dma_request(struct device *dev, |
| struct iio_dev *indio_dev) |
| { |
| struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); |
| |
| adc->dma_chan = dma_request_chan(dev, "rx"); |
| if (IS_ERR(adc->dma_chan)) { |
| int ret = PTR_ERR(adc->dma_chan); |
| |
| adc->dma_chan = NULL; |
| return ret; |
| } |
| |
| adc->rx_buf = dma_alloc_coherent(adc->dma_chan->device->dev, |
| DFSDM_DMA_BUFFER_SIZE, |
| &adc->dma_buf, GFP_KERNEL); |
| if (!adc->rx_buf) { |
| dma_release_channel(adc->dma_chan); |
| return -ENOMEM; |
| } |
| |
| indio_dev->modes |= INDIO_BUFFER_SOFTWARE; |
| indio_dev->setup_ops = &stm32_dfsdm_buffer_setup_ops; |
| |
| return 0; |
| } |
| |
| 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) | |
| BIT(IIO_CHAN_INFO_SAMP_FREQ); |
| |
| if (adc->dev_data->type == DFSDM_AUDIO) { |
| ch->ext_info = dfsdm_adc_audio_ext_info; |
| } else { |
| ch->scan_type.shift = 8; |
| } |
| ch->scan_type.sign = 's'; |
| ch->scan_type.realbits = 24; |
| ch->scan_type.storagebits = 32; |
| |
| return stm32_dfsdm_chan_configure(adc->dfsdm, |
| &adc->dfsdm->ch_list[ch->channel]); |
| } |
| |
| static int stm32_dfsdm_audio_init(struct device *dev, struct iio_dev *indio_dev) |
| { |
| struct iio_chan_spec *ch; |
| struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); |
| struct stm32_dfsdm_channel *d_ch; |
| int ret; |
| |
| ch = devm_kzalloc(&indio_dev->dev, sizeof(*ch), GFP_KERNEL); |
| if (!ch) |
| return -ENOMEM; |
| |
| ch->scan_index = 0; |
| |
| ret = stm32_dfsdm_adc_chan_init_one(indio_dev, ch); |
| if (ret < 0) { |
| dev_err(&indio_dev->dev, "Channels init failed\n"); |
| return ret; |
| } |
| ch->info_mask_separate = BIT(IIO_CHAN_INFO_SAMP_FREQ); |
| |
| d_ch = &adc->dfsdm->ch_list[ch->channel]; |
| if (d_ch->src != DFSDM_CHANNEL_SPI_CLOCK_EXTERNAL) |
| adc->spi_freq = adc->dfsdm->spi_master_freq; |
| |
| indio_dev->num_channels = 1; |
| indio_dev->channels = ch; |
| |
| return stm32_dfsdm_dma_request(dev, indio_dev); |
| } |
| |
| static int stm32_dfsdm_adc_init(struct device *dev, 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_compute_all_osrs(indio_dev, 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[chan_idx].scan_index = chan_idx; |
| ret = stm32_dfsdm_adc_chan_init_one(indio_dev, &ch[chan_idx]); |
| 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); |
| |
| /* Optionally request DMA */ |
| ret = stm32_dfsdm_dma_request(dev, indio_dev); |
| if (ret) { |
| if (ret != -ENODEV) |
| return dev_err_probe(dev, ret, |
| "DMA channel request failed with\n"); |
| |
| dev_dbg(dev, "No DMA support\n"); |
| return 0; |
| } |
| |
| ret = iio_triggered_buffer_setup(indio_dev, |
| &iio_pollfunc_store_time, NULL, |
| &stm32_dfsdm_buffer_setup_ops); |
| if (ret) { |
| stm32_dfsdm_dma_release(indio_dev); |
| dev_err(&indio_dev->dev, "buffer setup failed\n"); |
| return ret; |
| } |
| |
| /* lptimer/timer hardware triggers */ |
| indio_dev->modes |= INDIO_HARDWARE_TRIGGERED; |
| |
| return 0; |
| } |
| |
| static const struct stm32_dfsdm_dev_data stm32h7_dfsdm_adc_data = { |
| .type = DFSDM_IIO, |
| .init = stm32_dfsdm_adc_init, |
| }; |
| |
| static const struct stm32_dfsdm_dev_data stm32h7_dfsdm_audio_data = { |
| .type = DFSDM_AUDIO, |
| .init = stm32_dfsdm_audio_init, |
| }; |
| |
| static const struct of_device_id stm32_dfsdm_adc_match[] = { |
| { |
| .compatible = "st,stm32-dfsdm-adc", |
| .data = &stm32h7_dfsdm_adc_data, |
| }, |
| { |
| .compatible = "st,stm32-dfsdm-dmic", |
| .data = &stm32h7_dfsdm_audio_data, |
| }, |
| {} |
| }; |
| MODULE_DEVICE_TABLE(of, stm32_dfsdm_adc_match); |
| |
| 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; |
| char *name; |
| int ret, irq, val; |
| |
| dev_data = of_device_get_match_data(dev); |
| iio = devm_iio_device_alloc(dev, sizeof(*adc)); |
| if (!iio) { |
| dev_err(dev, "%s: Failed to allocate IIO\n", __func__); |
| return -ENOMEM; |
| } |
| |
| adc = iio_priv(iio); |
| adc->dfsdm = dev_get_drvdata(dev->parent); |
| |
| iio->dev.of_node = np; |
| iio->modes = INDIO_DIRECT_MODE; |
| |
| platform_set_drvdata(pdev, iio); |
| |
| ret = of_property_read_u32(dev->of_node, "reg", &adc->fl_id); |
| if (ret != 0 || adc->fl_id >= adc->dfsdm->num_fls) { |
| dev_err(dev, "Missing or bad reg property\n"); |
| return -EINVAL; |
| } |
| |
| name = devm_kzalloc(dev, sizeof("dfsdm-adc0"), GFP_KERNEL); |
| if (!name) |
| return -ENOMEM; |
| if (dev_data->type == DFSDM_AUDIO) { |
| iio->info = &stm32_dfsdm_info_audio; |
| snprintf(name, sizeof("dfsdm-pdm0"), "dfsdm-pdm%d", adc->fl_id); |
| } else { |
| 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); |
| if (irq < 0) |
| return irq; |
| |
| ret = devm_request_irq(dev, irq, stm32_dfsdm_irq, |
| 0, pdev->name, iio); |
| 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(dev, iio); |
| if (ret < 0) |
| return ret; |
| |
| ret = iio_device_register(iio); |
| if (ret < 0) |
| goto err_cleanup; |
| |
| if (dev_data->type == DFSDM_AUDIO) { |
| ret = of_platform_populate(np, NULL, NULL, dev); |
| if (ret < 0) { |
| dev_err(dev, "Failed to find an audio DAI\n"); |
| goto err_unregister; |
| } |
| } |
| |
| return 0; |
| |
| err_unregister: |
| iio_device_unregister(iio); |
| err_cleanup: |
| stm32_dfsdm_dma_release(iio); |
| |
| return ret; |
| } |
| |
| static void stm32_dfsdm_adc_remove(struct platform_device *pdev) |
| { |
| struct iio_dev *indio_dev = platform_get_drvdata(pdev); |
| struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); |
| |
| if (adc->dev_data->type == DFSDM_AUDIO) |
| of_platform_depopulate(&pdev->dev); |
| iio_device_unregister(indio_dev); |
| stm32_dfsdm_dma_release(indio_dev); |
| } |
| |
| static int stm32_dfsdm_adc_suspend(struct device *dev) |
| { |
| struct iio_dev *indio_dev = dev_get_drvdata(dev); |
| |
| if (iio_buffer_enabled(indio_dev)) |
| stm32_dfsdm_predisable(indio_dev); |
| |
| return 0; |
| } |
| |
| static int stm32_dfsdm_adc_resume(struct device *dev) |
| { |
| struct iio_dev *indio_dev = dev_get_drvdata(dev); |
| struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); |
| const struct iio_chan_spec *chan; |
| struct stm32_dfsdm_channel *ch; |
| int i, ret; |
| |
| /* restore channels configuration */ |
| for (i = 0; i < indio_dev->num_channels; i++) { |
| chan = indio_dev->channels + i; |
| ch = &adc->dfsdm->ch_list[chan->channel]; |
| ret = stm32_dfsdm_chan_configure(adc->dfsdm, ch); |
| if (ret) |
| return ret; |
| } |
| |
| if (iio_buffer_enabled(indio_dev)) |
| stm32_dfsdm_postenable(indio_dev); |
| |
| return 0; |
| } |
| |
| static DEFINE_SIMPLE_DEV_PM_OPS(stm32_dfsdm_adc_pm_ops, |
| stm32_dfsdm_adc_suspend, |
| stm32_dfsdm_adc_resume); |
| |
| static struct platform_driver stm32_dfsdm_adc_driver = { |
| .driver = { |
| .name = "stm32-dfsdm-adc", |
| .of_match_table = stm32_dfsdm_adc_match, |
| .pm = pm_sleep_ptr(&stm32_dfsdm_adc_pm_ops), |
| }, |
| .probe = stm32_dfsdm_adc_probe, |
| .remove_new = 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"); |