Message ID | e340f48324b0ea3afb1c715cb2fba184c27112a1.1717539384.git.marcelo.schmitt@analog.com (mailing list archive) |
---|---|
State | Superseded |
Headers | show |
Series | Add support for AD4000 series of ADCs | expand |
On Wed, 2024-06-05 at 08:14 -0300, Marcelo Schmitt wrote: > Add support for AD4000 series of low noise, low power, high speed, > successive aproximation register (SAR) ADCs. > > Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com> > --- ... > + > +static const struct ad4000_chip_info ad4000_chips[] = { > + [ID_AD4000] = { > + .dev_name = "ad4000", > + .chan_spec = AD4000_PSEUDO_DIFF_CHANNEL('u', 16), > + }, > + [ID_AD4001] = { > + .dev_name = "ad4001", > + .chan_spec = AD4000_DIFF_CHANNEL('s', 16), > + }, > + [ID_AD4002] = { > + .dev_name = "ad4002", > + .chan_spec = AD4000_PSEUDO_DIFF_CHANNEL('u', 18), > + }, > + [ID_AD4003] = { > + .dev_name = "ad4003", > + .chan_spec = AD4000_DIFF_CHANNEL('s', 18), > + }, > + [ID_AD4004] = { > + .dev_name = "ad4004", > + .chan_spec = AD4000_PSEUDO_DIFF_CHANNEL('u', 16), > + }, > + [ID_AD4005] = { > + .dev_name = "ad4005", > + .chan_spec = AD4000_DIFF_CHANNEL('s', 16), > + }, > + [ID_AD4006] = { > + .dev_name = "ad4006", > + .chan_spec = AD4000_PSEUDO_DIFF_CHANNEL('u', 18), > + }, > + [ID_AD4007] = { > + .dev_name = "ad4007", > + .chan_spec = AD4000_DIFF_CHANNEL('s', 18), > + }, > + [ID_AD4008] = { > + .dev_name = "ad4008", > + .chan_spec = AD4000_PSEUDO_DIFF_CHANNEL('u', 16), > + }, > + [ID_AD4010] = { > + .dev_name = "ad4010", > + .chan_spec = AD4000_PSEUDO_DIFF_CHANNEL('u', 18), > + }, > + [ID_AD4011] = { > + .dev_name = "ad4011", > + .chan_spec = AD4000_DIFF_CHANNEL('s', 18), > + }, > + [ID_AD4020] = { > + .dev_name = "ad4020", > + .chan_spec = AD4000_DIFF_CHANNEL('s', 20), > + }, > + [ID_AD4021] = { > + .dev_name = "ad4021", > + .chan_spec = AD4000_DIFF_CHANNEL('s', 20), > + }, > + [ID_AD4022] = { > + .dev_name = "ad4022", > + .chan_spec = AD4000_DIFF_CHANNEL('s', 20), > + }, > + [ID_ADAQ4001] = { > + .dev_name = "adaq4001", > + .chan_spec = AD4000_DIFF_CHANNEL('s', 16), > + }, > + [ID_ADAQ4003] = { > + .dev_name = "adaq4003", > + .chan_spec = AD4000_DIFF_CHANNEL('s', 18), > + }, > +}; > + Please have the above as a different variable per device rather than the array. Likely no need for the enum then... > +struct ad4000_state { > + struct spi_device *spi; > + struct gpio_desc *cnv_gpio; > + struct spi_transfer xfers[2]; > + struct spi_message msg; > + int vref; > + enum ad4000_spi_mode spi_mode; > + bool span_comp; > + bool turbo_mode; > + int gain_milli; > + int scale_tbl[2][2]; > + > + /* > + * DMA (thus cache coherency maintenance) requires the > + * transfer buffers to live in their own cache lines. > + */ > + struct { > + union { > + __be16 sample_buf16; > + __be32 sample_buf32; > + } data; > + s64 timestamp __aligned(8); > + } scan __aligned(IIO_DMA_MINALIGN); > + __be16 tx_buf; > + __be16 rx_buf; > +}; > + > +static void ad4000_fill_scale_tbl(struct ad4000_state *st, int scale_bits, > + const struct ad4000_chip_info *chip) > +{ > + int diff = chip->chan_spec.differential; > + int val, val2, tmp0, tmp1; > + u64 tmp2; > + > + val2 = scale_bits; > + val = st->vref / 1000; > + /* > + * The gain is stored as a fraction of 1000 and, as we need to > + * divide vref by gain, we invert the gain/1000 fraction. > + * Also multiply by an extra MILLI to avoid losing precision. > + */ > + val = mult_frac(val, MILLI * MILLI, st->gain_milli); Why not MICRO :)? > + /* Would multiply by NANO here but we multiplied by extra MILLI */ > + tmp2 = shift_right((u64)val * MICRO, val2); > + tmp0 = (int)div_s64_rem(tmp2, NANO, &tmp1); no cast needed... > + /* Store scale for when span compression is disabled */ > + st->scale_tbl[0][0] = tmp0; /* Integer part */ > + st->scale_tbl[0][1] = abs(tmp1); /* Fractional part */ > + /* Store scale for when span compression is enabled */ > + st->scale_tbl[1][0] = tmp0; > + /* The integer part is always zero so don't bother to divide it. */ > + if (diff) > + st->scale_tbl[1][1] = DIV_ROUND_CLOSEST(abs(tmp1) * 4, 5); > + else > + st->scale_tbl[1][1] = DIV_ROUND_CLOSEST(abs(tmp1) * 9, 10); > +} > + > +static int ad4000_write_reg(struct ad4000_state *st, uint8_t val) > +{ > + st->tx_buf = cpu_to_be16(AD4000_WRITE_COMMAND << BITS_PER_BYTE | > val); > + return spi_write(st->spi, &st->tx_buf, 2); sizeof(tx_buf)? > +} > + > +static int ad4000_read_reg(struct ad4000_state *st, unsigned int *val) > +{ > + struct spi_transfer t[] = { > + { > + .tx_buf = &st->tx_buf, > + .rx_buf = &st->rx_buf, > + .len = 2, > + }, > + }; > + int ret; > + > + st->tx_buf = cpu_to_be16(AD4000_READ_COMMAND << BITS_PER_BYTE); > + ret = spi_sync_transfer(st->spi, t, ARRAY_SIZE(t)); > + if (ret < 0) > + return ret; > + > + *val = be16_to_cpu(st->rx_buf); > + > + return ret; > +} > + > +static void ad4000_unoptimize_msg(void *msg) > +{ > + spi_unoptimize_message(msg); > +} > + > +/* > + * This executes a data sample transfer for when the device connections are > + * in "3-wire" mode, selected by setting the adi,spi-mode device tree > property > + * to "single". In this connection mode, the ADC SDI pin is connected to MOSI > or > + * to VIO and ADC CNV pin is connected either to a SPI controller CS or to a > GPIO. > + * AD4000 series of devices initiate conversions on the rising edge of CNV > pin. > + * > + * If the CNV pin is connected to an SPI controller CS line (which is by > default > + * active low), the ADC readings would have a latency (delay) of one read. > + * Moreover, since we also do ADC sampling for filling the buffer on > triggered > + * buffer mode, the timestamps of buffer readings would be disarranged. > + * To prevent the read latency and reduce the time discrepancy between the > + * sample read request and the time of actual sampling by the ADC, do a > + * preparatory transfer to pulse the CS/CNV line. > + */ > +static int ad4000_prepare_3wire_mode_message(struct ad4000_state *st, > + const struct iio_chan_spec > *chan) > +{ > + unsigned int cnv_pulse_time = st->turbo_mode ? AD4000_TQUIET1_NS > + : AD4000_TCONV_NS; > + struct spi_transfer *xfers = st->xfers; > + int ret; > + > + xfers[0].cs_change = 1; > + xfers[0].cs_change_delay.value = cnv_pulse_time; > + xfers[0].cs_change_delay.unit = SPI_DELAY_UNIT_NSECS; > + > + xfers[1].rx_buf = &st->scan.data; > + xfers[1].len = BITS_TO_BYTES(chan->scan_type.storagebits); > + xfers[1].delay.value = AD4000_TQUIET2_NS; > + xfers[1].delay.unit = SPI_DELAY_UNIT_NSECS; > + > + spi_message_init_with_transfers(&st->msg, st->xfers, 2); > + > + ret = spi_optimize_message(st->spi, &st->msg); > + if (ret) > + return ret; > + > + return devm_add_action_or_reset(&st->spi->dev, ad4000_unoptimize_msg, > + &st->msg); > +} > + > +/* > + * This executes a data sample transfer for when the device connections are > + * in "4-wire" mode, selected when the adi,spi-mode device tree > + * property is absent or empty. In this connection mode, the controller CS > pin > + * is connected to ADC SDI pin and a GPIO is connected to ADC CNV pin. > + * The GPIO connected to ADC CNV pin is set outside of the SPI transfer. > + */ > +static int ad4000_prepare_4wire_mode_message(struct ad4000_state *st, > + const struct iio_chan_spec > *chan) > +{ > + unsigned int cnv_to_sdi_time = st->turbo_mode ? AD4000_TQUIET1_NS > + : AD4000_TCONV_NS; > + struct spi_transfer *xfers = st->xfers; > + int ret; > + > + /* > + * Dummy transfer to cause enough delay between CNV going high and > SDI > + * going low. > + */ > + xfers[0].cs_off = 1; > + xfers[0].delay.value = cnv_to_sdi_time; > + xfers[0].delay.unit = SPI_DELAY_UNIT_NSECS; > + > + xfers[1].rx_buf = &st->scan.data; > + xfers[1].len = BITS_TO_BYTES(chan->scan_type.storagebits); > + > + spi_message_init_with_transfers(&st->msg, st->xfers, 2); > + > + ret = spi_optimize_message(st->spi, &st->msg); > + if (ret) > + return ret; > + > + return devm_add_action_or_reset(&st->spi->dev, ad4000_unoptimize_msg, > + &st->msg); > +} > + > +static int ad4000_convert_and_acquire(struct ad4000_state *st) > +{ > + int ret; > + > + /* > + * In 4-wire mode, the CNV line is held high for the entire > conversion > + * and acquisition process. In other modes st->cnv_gpio is NULL and > is > + * ignored (CS is wired to CNV in those cases). > + */ > + gpiod_set_value_cansleep(st->cnv_gpio, 1); Not sure it's a good practise to assume internal details as you're going for GPIO. I would prefer to have an explicit check for st->cnv_gpio being NULL or not. > + ret = spi_sync(st->spi, &st->msg); > + gpiod_set_value_cansleep(st->cnv_gpio, 0); > + > + return ret; > +} > + > +static int ad4000_single_conversion(struct iio_dev *indio_dev, > + const struct iio_chan_spec *chan, int > *val) > +{ > + struct ad4000_state *st = iio_priv(indio_dev); > + u32 sample; > + int ret; > + > + ret = ad4000_convert_and_acquire(st); > no error check > + if (chan->scan_type.storagebits > 16) > + sample = be32_to_cpu(st->scan.data.sample_buf32); > + else > + sample = be16_to_cpu(st->scan.data.sample_buf16); > + > + switch (chan->scan_type.realbits) { > + case 16: > + break; > + case 18: > + sample = FIELD_GET(AD4000_18BIT_MSK, sample); > + break; > + case 20: > + sample = FIELD_GET(AD4000_20BIT_MSK, sample); > + break; > + default: > + return -EINVAL; > + } > + > + if (chan->scan_type.sign == 's') > + *val = sign_extend32(sample, chan->scan_type.realbits - 1); > + > + return IIO_VAL_INT; > +} > + > +static int ad4000_read_raw(struct iio_dev *indio_dev, > + struct iio_chan_spec const *chan, int *val, > + int *val2, long info) > +{ > + struct ad4000_state *st = iio_priv(indio_dev); > + > + switch (info) { > + case IIO_CHAN_INFO_RAW: > + iio_device_claim_direct_scoped(return -EBUSY, indio_dev) > + return ad4000_single_conversion(indio_dev, chan, > val); > + unreachable(); > + case IIO_CHAN_INFO_SCALE: > + *val = st->scale_tbl[st->span_comp][0]; > + *val2 = st->scale_tbl[st->span_comp][1]; > + return IIO_VAL_INT_PLUS_NANO; > + case IIO_CHAN_INFO_OFFSET: > + *val = 0; > + if (st->span_comp) > + *val = mult_frac(st->vref / 1000, 1, 10); > + > + return IIO_VAL_INT; > + default: > + return -EINVAL; > + } > +} > + > +static int ad4000_read_avail(struct iio_dev *indio_dev, > + struct iio_chan_spec const *chan, > + const int **vals, int *type, int *length, > + long info) > +{ > + struct ad4000_state *st = iio_priv(indio_dev); > + > + switch (info) { > + case IIO_CHAN_INFO_SCALE: > + *vals = (int *)st->scale_tbl; > + *length = 2 * 2; > + *type = IIO_VAL_INT_PLUS_NANO; > + return IIO_AVAIL_LIST; > + default: > + return -EINVAL; > + } > +} > + > +static int ad4000_write_raw_get_fmt(struct iio_dev *indio_dev, > + struct iio_chan_spec const *chan, long > mask) > +{ > + switch (mask) { > + case IIO_CHAN_INFO_SCALE: > + return IIO_VAL_INT_PLUS_NANO; > + default: > + return IIO_VAL_INT_PLUS_MICRO; > + } > +} > + > +static int ad4000_write_raw(struct iio_dev *indio_dev, > + struct iio_chan_spec const *chan, int val, int > val2, > + long mask) > +{ > + struct ad4000_state *st = iio_priv(indio_dev); > + unsigned int reg_val; > + bool span_comp_en; > + int ret; > + > + switch (mask) { > + case IIO_CHAN_INFO_SCALE: > + iio_device_claim_direct_scoped(return -EBUSY, indio_dev) { > + ret = ad4000_read_reg(st, ®_val); > + if (ret < 0) > + return ret; > + > + span_comp_en = (val2 == st->scale_tbl[1][1]); no () needed > + reg_val &= ~AD4000_CFG_SPAN_COMP; > + reg_val |= FIELD_PREP(AD4000_CFG_SPAN_COMP, > span_comp_en); > + > + ret = ad4000_write_reg(st, reg_val); > + if (ret < 0) > + return ret; > + > + st->span_comp = span_comp_en; > + return 0; > + } > + unreachable(); > + default: > + return -EINVAL; > + } > +} > + > +static irqreturn_t ad4000_trigger_handler(int irq, void *p) > +{ > + struct iio_poll_func *pf = p; > + struct iio_dev *indio_dev = pf->indio_dev; > + struct ad4000_state *st = iio_priv(indio_dev); > + int ret; > + > + ret = ad4000_convert_and_acquire(st); > + if (ret < 0) > + goto err_out; > + > + iio_push_to_buffers_with_timestamp(indio_dev, &st->scan, > + iio_get_time_ns(indio_dev)); > + > +err_out: > + iio_trigger_notify_done(indio_dev->trig); > + return IRQ_HANDLED; > +} > + > +static int ad4000_reg_access(struct iio_dev *indio_dev, unsigned int reg, > + unsigned int writeval, unsigned int *readval) > +{ > + struct ad4000_state *st = iio_priv(indio_dev); > + int ret; > + > + if (readval) > + ret = ad4000_read_reg(st, readval); > + else > + ret = ad4000_write_reg(st, writeval); if (readval) return ad4000_read_reg(); return ad4000_write_reg(); > + > + return ret; > +} > + > +static const struct iio_info ad4000_info = { > + .read_raw = &ad4000_read_raw, > + .read_avail = &ad4000_read_avail, > + .write_raw = &ad4000_write_raw, > + .write_raw_get_fmt = &ad4000_write_raw_get_fmt, > + .debugfs_reg_access = &ad4000_reg_access, > + > +}; > + > +static int ad4000_config(struct ad4000_state *st) > +{ > + unsigned int reg_val; > + > + if (device_property_present(&st->spi->dev, "adi,high-z-input")) > + reg_val |= FIELD_PREP(AD4000_CFG_HIGHZ, 1); > + > + /* > + * The ADC SDI pin might be connected to controller CS line in which > + * case the write might fail. This, however, does not prevent the > device > + * from functioning even though in a configuration other than the > + * requested one. > + */ This raises the question if there's any way to describe that through DT (if not doing it already)? So that, if SDI is connected to CS we don't even call this? Other question that comes to mind is that in case SDI is connected to CS, will all writes fail? Because if that's the case we other writes (like scale) that won't work and we should take care of that... > + return ad4000_write_reg(st, reg_val); > +} > + > +static void ad4000_regulator_disable(void *reg) > +{ > + regulator_disable(reg); > +} > + > +static int ad4000_probe(struct spi_device *spi) > +{ > + const struct ad4000_chip_info *chip; > + struct regulator *vref_reg; > + struct iio_dev *indio_dev; > + struct ad4000_state *st; > + int ret; > + > + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); > + if (!indio_dev) > + return -ENOMEM; > + > + chip = spi_get_device_match_data(spi); > + if (!chip) > + return -EINVAL; > + > + st = iio_priv(indio_dev); > + st->spi = spi; > + > + ret = devm_regulator_get_enable(&spi->dev, "vdd"); > + if (ret) > + return dev_err_probe(&spi->dev, ret, "Failed to enable VDD > supply\n"); > + > + ret = devm_regulator_get_enable(&spi->dev, "vio"); > + if (ret) > + return dev_err_probe(&spi->dev, ret, "Failed to enable VIO > supply\n"); > + > + vref_reg = devm_regulator_get(&spi->dev, "ref"); > + if (IS_ERR(vref_reg)) > + return dev_err_probe(&spi->dev, PTR_ERR(vref_reg), > + "Failed to get vref regulator\n"); Should this not be an optional one? If not, why not devm_regulator_get_enable()? Also consider the new devm_regulator_get_enable_read_voltage() - you need to handle -ENODEV in case this is optional. > + > + ret = regulator_enable(vref_reg); > + if (ret < 0) > + return dev_err_probe(&spi->dev, ret, > + "Failed to enable voltage regulator\n"); > + > + ret = devm_add_action_or_reset(&spi->dev, ad4000_regulator_disable, > vref_reg); > + if (ret) > + return dev_err_probe(&spi->dev, ret, > + "Failed to add regulator disable > action\n"); > + > + st->vref = regulator_get_voltage(vref_reg); > + if (st->vref < 0) > + return dev_err_probe(&spi->dev, st->vref, "Failed to get > vref\n"); > + I think in all places you're using this you st->vref / 1000, right? Do it here just once... > + st->cnv_gpio = devm_gpiod_get_optional(&spi->dev, "cnv", > GPIOD_OUT_HIGH); > + if (IS_ERR(st->cnv_gpio)) > + return dev_err_probe(&spi->dev, PTR_ERR(st->cnv_gpio), > + "Failed to get CNV GPIO"); > + > + ret = device_property_match_property_string(&spi->dev, "adi,spi- > mode", > + ad4000_spi_modes, > + > ARRAY_SIZE(ad4000_spi_modes)); > + /* Default to 4-wire mode if adi,spi-mode property is not present */ > + if (ret == -EINVAL) > + st->spi_mode = AD4000_SPI_MODE_DEFAULT; > + else if (ret < 0) > + return dev_err_probe(&spi->dev, ret, > + "getting adi,spi-mode property > failed\n"); > + else > + st->spi_mode = ret; > + > + switch (st->spi_mode) { > + case AD4000_SPI_MODE_DEFAULT: > + ret = ad4000_prepare_4wire_mode_message(st, &chip- > >chan_spec); > + if (ret) > + return ret; > + > + break; > + case AD4000_SPI_MODE_SINGLE: > + ret = ad4000_prepare_3wire_mode_message(st, &chip- > >chan_spec); > + if (ret) > + return ret; > + > + /* > + * In "3-wire mode", the ADC SDI line must be kept high when > + * data is not being clocked out of the controller. > + * Request the SPI controller to make MOSI idle high. > + */ > + spi->mode = SPI_MODE_0 | SPI_MOSI_IDLE_HIGH; > + if (spi_setup(spi)) > + dev_warn(&st->spi->dev, "SPI controller setup > failed\n"); Does not look like a warn to me... Also, spi_setup() should already print an error message so this will duplicate that. > + > + break; > + } > + > + ret = ad4000_config(st); > + if (ret < 0) > + dev_dbg(&st->spi->dev, "Failed to config device\n"); Also questionable but see the my comment on ad4000_config(). In any case this should be visible to the user so I would at least use dev_warn(). > + > + indio_dev->name = chip->dev_name; > + indio_dev->info = &ad4000_info; > + indio_dev->channels = &chip->chan_spec; > + indio_dev->num_channels = 1; > + > + /* Hardware gain only applies to ADAQ devices */ > + st->gain_milli = 1000; > + if (device_property_present(&spi->dev, "adi,gain-milli")) { > + > + ret = device_property_read_u32(&spi->dev, "adi,gain-milli", > + &st->gain_milli); Can we have full unsigned int range for the gain? I guess not :) - Nuno Sá
Hi Marcelo, kernel test robot noticed the following build warnings: [auto build test WARNING on broonie-spi/for-next] [also build test WARNING on jic23-iio/togreg linus/master v6.10-rc2 next-20240605] [If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in https://git-scm.com/docs/git-format-patch#_base_tree_information] url: https://github.com/intel-lab-lkp/linux/commits/Marcelo-Schmitt/spi-Add-SPI-mode-bit-for-MOSI-idle-state-configuration/20240605-231912 base: https://git.kernel.org/pub/scm/linux/kernel/git/broonie/spi.git for-next patch link: https://lore.kernel.org/r/e340f48324b0ea3afb1c715cb2fba184c27112a1.1717539384.git.marcelo.schmitt%40analog.com patch subject: [PATCH v3 6/6] iio: adc: Add support for AD4000 config: alpha-allyesconfig (https://download.01.org/0day-ci/archive/20240606/202406060440.I43MwC4B-lkp@intel.com/config) compiler: alpha-linux-gcc (GCC) 13.2.0 reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20240606/202406060440.I43MwC4B-lkp@intel.com/reproduce) If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags | Reported-by: kernel test robot <lkp@intel.com> | Closes: https://lore.kernel.org/oe-kbuild-all/202406060440.I43MwC4B-lkp@intel.com/ All warnings (new ones prefixed by >>): drivers/iio/adc/ad4000.c: In function 'ad4000_single_conversion': >> drivers/iio/adc/ad4000.c:375:13: warning: variable 'ret' set but not used [-Wunused-but-set-variable] 375 | int ret; | ^~~ vim +/ret +375 drivers/iio/adc/ad4000.c 369 370 static int ad4000_single_conversion(struct iio_dev *indio_dev, 371 const struct iio_chan_spec *chan, int *val) 372 { 373 struct ad4000_state *st = iio_priv(indio_dev); 374 u32 sample; > 375 int ret; 376 377 ret = ad4000_convert_and_acquire(st); 378 379 if (chan->scan_type.storagebits > 16) 380 sample = be32_to_cpu(st->scan.data.sample_buf32); 381 else 382 sample = be16_to_cpu(st->scan.data.sample_buf16); 383 384 switch (chan->scan_type.realbits) { 385 case 16: 386 break; 387 case 18: 388 sample = FIELD_GET(AD4000_18BIT_MSK, sample); 389 break; 390 case 20: 391 sample = FIELD_GET(AD4000_20BIT_MSK, sample); 392 break; 393 default: 394 return -EINVAL; 395 } 396 397 if (chan->scan_type.sign == 's') 398 *val = sign_extend32(sample, chan->scan_type.realbits - 1); 399 400 return IIO_VAL_INT; 401 } 402
Hi Marcelo, kernel test robot noticed the following build warnings: [auto build test WARNING on broonie-spi/for-next] [also build test WARNING on jic23-iio/togreg linus/master v6.10-rc2 next-20240605] [If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in https://git-scm.com/docs/git-format-patch#_base_tree_information] url: https://github.com/intel-lab-lkp/linux/commits/Marcelo-Schmitt/spi-Add-SPI-mode-bit-for-MOSI-idle-state-configuration/20240605-231912 base: https://git.kernel.org/pub/scm/linux/kernel/git/broonie/spi.git for-next patch link: https://lore.kernel.org/r/e340f48324b0ea3afb1c715cb2fba184c27112a1.1717539384.git.marcelo.schmitt%40analog.com patch subject: [PATCH v3 6/6] iio: adc: Add support for AD4000 config: hexagon-allmodconfig (https://download.01.org/0day-ci/archive/20240606/202406060558.kJtbRid3-lkp@intel.com/config) compiler: clang version 19.0.0git (https://github.com/llvm/llvm-project d7d2d4f53fc79b4b58e8d8d08151b577c3699d4a) reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20240606/202406060558.kJtbRid3-lkp@intel.com/reproduce) If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags | Reported-by: kernel test robot <lkp@intel.com> | Closes: https://lore.kernel.org/oe-kbuild-all/202406060558.kJtbRid3-lkp@intel.com/ All warnings (new ones prefixed by >>): In file included from drivers/iio/adc/ad4000.c:17: In file included from include/linux/regulator/consumer.h:35: In file included from include/linux/suspend.h:5: In file included from include/linux/swap.h:9: In file included from include/linux/memcontrol.h:13: In file included from include/linux/cgroup.h:26: In file included from include/linux/kernel_stat.h:9: In file included from include/linux/interrupt.h:11: In file included from include/linux/hardirq.h:11: In file included from ./arch/hexagon/include/generated/asm/hardirq.h:1: In file included from include/asm-generic/hardirq.h:17: In file included from include/linux/irq.h:20: In file included from include/linux/io.h:14: In file included from arch/hexagon/include/asm/io.h:328: include/asm-generic/io.h:548:31: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic] 548 | val = __raw_readb(PCI_IOBASE + addr); | ~~~~~~~~~~ ^ include/asm-generic/io.h:561:61: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic] 561 | val = __le16_to_cpu((__le16 __force)__raw_readw(PCI_IOBASE + addr)); | ~~~~~~~~~~ ^ include/uapi/linux/byteorder/little_endian.h:37:51: note: expanded from macro '__le16_to_cpu' 37 | #define __le16_to_cpu(x) ((__force __u16)(__le16)(x)) | ^ In file included from drivers/iio/adc/ad4000.c:17: In file included from include/linux/regulator/consumer.h:35: In file included from include/linux/suspend.h:5: In file included from include/linux/swap.h:9: In file included from include/linux/memcontrol.h:13: In file included from include/linux/cgroup.h:26: In file included from include/linux/kernel_stat.h:9: In file included from include/linux/interrupt.h:11: In file included from include/linux/hardirq.h:11: In file included from ./arch/hexagon/include/generated/asm/hardirq.h:1: In file included from include/asm-generic/hardirq.h:17: In file included from include/linux/irq.h:20: In file included from include/linux/io.h:14: In file included from arch/hexagon/include/asm/io.h:328: include/asm-generic/io.h:574:61: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic] 574 | val = __le32_to_cpu((__le32 __force)__raw_readl(PCI_IOBASE + addr)); | ~~~~~~~~~~ ^ include/uapi/linux/byteorder/little_endian.h:35:51: note: expanded from macro '__le32_to_cpu' 35 | #define __le32_to_cpu(x) ((__force __u32)(__le32)(x)) | ^ In file included from drivers/iio/adc/ad4000.c:17: In file included from include/linux/regulator/consumer.h:35: In file included from include/linux/suspend.h:5: In file included from include/linux/swap.h:9: In file included from include/linux/memcontrol.h:13: In file included from include/linux/cgroup.h:26: In file included from include/linux/kernel_stat.h:9: In file included from include/linux/interrupt.h:11: In file included from include/linux/hardirq.h:11: In file included from ./arch/hexagon/include/generated/asm/hardirq.h:1: In file included from include/asm-generic/hardirq.h:17: In file included from include/linux/irq.h:20: In file included from include/linux/io.h:14: In file included from arch/hexagon/include/asm/io.h:328: include/asm-generic/io.h:585:33: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic] 585 | __raw_writeb(value, PCI_IOBASE + addr); | ~~~~~~~~~~ ^ include/asm-generic/io.h:595:59: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic] 595 | __raw_writew((u16 __force)cpu_to_le16(value), PCI_IOBASE + addr); | ~~~~~~~~~~ ^ include/asm-generic/io.h:605:59: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic] 605 | __raw_writel((u32 __force)cpu_to_le32(value), PCI_IOBASE + addr); | ~~~~~~~~~~ ^ In file included from drivers/iio/adc/ad4000.c:17: In file included from include/linux/regulator/consumer.h:35: In file included from include/linux/suspend.h:5: In file included from include/linux/swap.h:9: In file included from include/linux/memcontrol.h:21: In file included from include/linux/mm.h:2253: include/linux/vmstat.h:514:36: warning: arithmetic between different enumeration types ('enum node_stat_item' and 'enum lru_list') [-Wenum-enum-conversion] 514 | return node_stat_name(NR_LRU_BASE + lru) + 3; // skip "nr_" | ~~~~~~~~~~~ ^ ~~~ drivers/iio/adc/ad4000.c:375:6: warning: variable 'ret' set but not used [-Wunused-but-set-variable] 375 | int ret; | ^ >> drivers/iio/adc/ad4000.c:538:3: warning: variable 'reg_val' is uninitialized when used here [-Wuninitialized] 538 | reg_val |= FIELD_PREP(AD4000_CFG_HIGHZ, 1); | ^~~~~~~ drivers/iio/adc/ad4000.c:535:22: note: initialize the variable 'reg_val' to silence this warning 535 | unsigned int reg_val; | ^ | = 0 9 warnings generated. vim +/reg_val +538 drivers/iio/adc/ad4000.c 369 370 static int ad4000_single_conversion(struct iio_dev *indio_dev, 371 const struct iio_chan_spec *chan, int *val) 372 { 373 struct ad4000_state *st = iio_priv(indio_dev); 374 u32 sample; > 375 int ret; 376 377 ret = ad4000_convert_and_acquire(st); 378 379 if (chan->scan_type.storagebits > 16) 380 sample = be32_to_cpu(st->scan.data.sample_buf32); 381 else 382 sample = be16_to_cpu(st->scan.data.sample_buf16); 383 384 switch (chan->scan_type.realbits) { 385 case 16: 386 break; 387 case 18: 388 sample = FIELD_GET(AD4000_18BIT_MSK, sample); 389 break; 390 case 20: 391 sample = FIELD_GET(AD4000_20BIT_MSK, sample); 392 break; 393 default: 394 return -EINVAL; 395 } 396 397 if (chan->scan_type.sign == 's') 398 *val = sign_extend32(sample, chan->scan_type.realbits - 1); 399 400 return IIO_VAL_INT; 401 } 402 403 static int ad4000_read_raw(struct iio_dev *indio_dev, 404 struct iio_chan_spec const *chan, int *val, 405 int *val2, long info) 406 { 407 struct ad4000_state *st = iio_priv(indio_dev); 408 409 switch (info) { 410 case IIO_CHAN_INFO_RAW: 411 iio_device_claim_direct_scoped(return -EBUSY, indio_dev) 412 return ad4000_single_conversion(indio_dev, chan, val); 413 unreachable(); 414 case IIO_CHAN_INFO_SCALE: 415 *val = st->scale_tbl[st->span_comp][0]; 416 *val2 = st->scale_tbl[st->span_comp][1]; 417 return IIO_VAL_INT_PLUS_NANO; 418 case IIO_CHAN_INFO_OFFSET: 419 *val = 0; 420 if (st->span_comp) 421 *val = mult_frac(st->vref / 1000, 1, 10); 422 423 return IIO_VAL_INT; 424 default: 425 return -EINVAL; 426 } 427 } 428 429 static int ad4000_read_avail(struct iio_dev *indio_dev, 430 struct iio_chan_spec const *chan, 431 const int **vals, int *type, int *length, 432 long info) 433 { 434 struct ad4000_state *st = iio_priv(indio_dev); 435 436 switch (info) { 437 case IIO_CHAN_INFO_SCALE: 438 *vals = (int *)st->scale_tbl; 439 *length = 2 * 2; 440 *type = IIO_VAL_INT_PLUS_NANO; 441 return IIO_AVAIL_LIST; 442 default: 443 return -EINVAL; 444 } 445 } 446 447 static int ad4000_write_raw_get_fmt(struct iio_dev *indio_dev, 448 struct iio_chan_spec const *chan, long mask) 449 { 450 switch (mask) { 451 case IIO_CHAN_INFO_SCALE: 452 return IIO_VAL_INT_PLUS_NANO; 453 default: 454 return IIO_VAL_INT_PLUS_MICRO; 455 } 456 } 457 458 static int ad4000_write_raw(struct iio_dev *indio_dev, 459 struct iio_chan_spec const *chan, int val, int val2, 460 long mask) 461 { 462 struct ad4000_state *st = iio_priv(indio_dev); 463 unsigned int reg_val; 464 bool span_comp_en; 465 int ret; 466 467 switch (mask) { 468 case IIO_CHAN_INFO_SCALE: 469 iio_device_claim_direct_scoped(return -EBUSY, indio_dev) { 470 ret = ad4000_read_reg(st, ®_val); 471 if (ret < 0) 472 return ret; 473 474 span_comp_en = (val2 == st->scale_tbl[1][1]); 475 reg_val &= ~AD4000_CFG_SPAN_COMP; 476 reg_val |= FIELD_PREP(AD4000_CFG_SPAN_COMP, span_comp_en); 477 478 ret = ad4000_write_reg(st, reg_val); 479 if (ret < 0) 480 return ret; 481 482 st->span_comp = span_comp_en; 483 return 0; 484 } 485 unreachable(); 486 default: 487 return -EINVAL; 488 } 489 } 490 491 static irqreturn_t ad4000_trigger_handler(int irq, void *p) 492 { 493 struct iio_poll_func *pf = p; 494 struct iio_dev *indio_dev = pf->indio_dev; 495 struct ad4000_state *st = iio_priv(indio_dev); 496 int ret; 497 498 ret = ad4000_convert_and_acquire(st); 499 if (ret < 0) 500 goto err_out; 501 502 iio_push_to_buffers_with_timestamp(indio_dev, &st->scan, 503 iio_get_time_ns(indio_dev)); 504 505 err_out: 506 iio_trigger_notify_done(indio_dev->trig); 507 return IRQ_HANDLED; 508 } 509 510 static int ad4000_reg_access(struct iio_dev *indio_dev, unsigned int reg, 511 unsigned int writeval, unsigned int *readval) 512 { 513 struct ad4000_state *st = iio_priv(indio_dev); 514 int ret; 515 516 if (readval) 517 ret = ad4000_read_reg(st, readval); 518 else 519 ret = ad4000_write_reg(st, writeval); 520 521 return ret; 522 } 523 524 static const struct iio_info ad4000_info = { 525 .read_raw = &ad4000_read_raw, 526 .read_avail = &ad4000_read_avail, 527 .write_raw = &ad4000_write_raw, 528 .write_raw_get_fmt = &ad4000_write_raw_get_fmt, 529 .debugfs_reg_access = &ad4000_reg_access, 530 531 }; 532 533 static int ad4000_config(struct ad4000_state *st) 534 { 535 unsigned int reg_val; 536 537 if (device_property_present(&st->spi->dev, "adi,high-z-input")) > 538 reg_val |= FIELD_PREP(AD4000_CFG_HIGHZ, 1); 539 540 /* 541 * The ADC SDI pin might be connected to controller CS line in which 542 * case the write might fail. This, however, does not prevent the device 543 * from functioning even though in a configuration other than the 544 * requested one. 545 */ 546 return ad4000_write_reg(st, reg_val); 547 } 548
> > + > > +static int ad4000_convert_and_acquire(struct ad4000_state *st) > > +{ > > + int ret; > > + > > + /* > > + * In 4-wire mode, the CNV line is held high for the entire > > conversion > > + * and acquisition process. In other modes st->cnv_gpio is NULL and > > is > > + * ignored (CS is wired to CNV in those cases). > > + */ > > + gpiod_set_value_cansleep(st->cnv_gpio, 1); > > Not sure it's a good practise to assume internal details as you're going for > GPIO. I would prefer to have an explicit check for st->cnv_gpio being NULL or > not. Hmm. I had it in my head that this was documented behaviour, but I can't find such in the docs, so agreed checking it makes sense. I would be very surprised if this ever changed as it's one of the things that makes optional gpios easy to work with but who knows! +CC Linus and Bartosz for feedback on this one. > > > + ret = spi_sync(st->spi, &st->msg); > > + gpiod_set_value_cansleep(st->cnv_gpio, 0); > > + > > + return ret; > > +} > > + > > +static int ad4000_config(struct ad4000_state *st) > > +{ > > + unsigned int reg_val; > > + > > + if (device_property_present(&st->spi->dev, "adi,high-z-input")) > > + reg_val |= FIELD_PREP(AD4000_CFG_HIGHZ, 1); > > + > > + /* > > + * The ADC SDI pin might be connected to controller CS line in which > > + * case the write might fail. This, however, does not prevent the > > device > > + * from functioning even though in a configuration other than the > > + * requested one. > > + */ > > This raises the question if there's any way to describe that through DT (if not > doing it already)? So that, if SDI is connected to CS we don't even call this? > Other question that comes to mind is that in case SDI is connected to CS, will > all writes fail? Because if that's the case we other writes (like scale) that > won't work and we should take care of that... Definitely needs describing and all configuration sysfs etc needs to be read only if we can't control it. > > > + return ad4000_write_reg(st, reg_val); > > +} > > + Jonathan
Sun, Jun 09, 2024 at 10:23:54AM +0100, Jonathan Cameron kirjoitti: ... > > > + /* > > > + * In 4-wire mode, the CNV line is held high for the entire > > > conversion > > > + * and acquisition process. In other modes st->cnv_gpio is NULL and > > > is > > > + * ignored (CS is wired to CNV in those cases). > > > + */ > > > + gpiod_set_value_cansleep(st->cnv_gpio, 1); > > > > Not sure it's a good practise to assume internal details as you're going for > > GPIO. I would prefer to have an explicit check for st->cnv_gpio being NULL or > > not. > > Hmm. I had it in my head that this was documented behaviour, but > I can't find such in the docs, so agreed checking it makes sense. > > I would be very surprised if this ever changed as it's one of the > things that makes optional gpios easy to work with but who knows! Not Linus and not Bart, but we have tons of drivers that call GPIO APIs unconditionally as long as they want optional GPIO. What I see here is the comment that should be rewritten to say something like "if GPIO is defined blablabla, otherwise blablabla." I.o.w. do not mention that implementation detail (being NULL, i.e. optional).
On Tue, 11 Jun 2024 13:34:29 +0300 Andy Shevchenko <andy.shevchenko@gmail.com> wrote: > Sun, Jun 09, 2024 at 10:23:54AM +0100, Jonathan Cameron kirjoitti: > > ... > > > > > + /* > > > > + * In 4-wire mode, the CNV line is held high for the entire > > > > conversion > > > > + * and acquisition process. In other modes st->cnv_gpio is NULL and > > > > is > > > > + * ignored (CS is wired to CNV in those cases). > > > > + */ > > > > + gpiod_set_value_cansleep(st->cnv_gpio, 1); > > > > > > Not sure it's a good practise to assume internal details as you're going for > > > GPIO. I would prefer to have an explicit check for st->cnv_gpio being NULL or > > > not. > > > > Hmm. I had it in my head that this was documented behaviour, but > > I can't find such in the docs, so agreed checking it makes sense. > > > > I would be very surprised if this ever changed as it's one of the > > things that makes optional gpios easy to work with but who knows! > > Not Linus and not Bart, but we have tons of drivers that call GPIO APIs > unconditionally as long as they want optional GPIO. > > What I see here is the comment that should be rewritten to say something like > > "if GPIO is defined blablabla, otherwise blablabla." > > I.o.w. do not mention that implementation detail (being NULL, i.e. optional). > Good point - handy comment there already and this minor tweak will make it clear. Thanks Andy! Jonathan
diff --git a/MAINTAINERS b/MAINTAINERS index 1f052b9cd912..c732cf13f511 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1206,6 +1206,7 @@ L: linux-iio@vger.kernel.org S: Supported W: https://ez.analog.com/linux-software-drivers F: Documentation/devicetree/bindings/iio/adc/adi,ad4000.yaml +F: drivers/iio/adc/ad4000.c ANALOG DEVICES INC AD4130 DRIVER M: Cosmin Tanislav <cosmin.tanislav@analog.com> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 5030319249c5..dcc49d9711a4 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -21,6 +21,18 @@ config AD_SIGMA_DELTA select IIO_BUFFER select IIO_TRIGGERED_BUFFER +config AD4000 + tristate "Analog Devices AD4000 ADC Driver" + depends on SPI + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for Analog Devices AD4000 high speed + SPI analog to digital converters (ADC). + + To compile this driver as a module, choose M here: the module will be + called ad4000. + config AD4130 tristate "Analog Device AD4130 ADC Driver" depends on SPI diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index 37ac689a0209..c32bd0ef6128 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -6,6 +6,7 @@ # When adding new entries keep the list in alphabetical order obj-$(CONFIG_AB8500_GPADC) += ab8500-gpadc.o obj-$(CONFIG_AD_SIGMA_DELTA) += ad_sigma_delta.o +obj-$(CONFIG_AD4000) += ad4000.o obj-$(CONFIG_AD4130) += ad4130.o obj-$(CONFIG_AD7091R) += ad7091r-base.o obj-$(CONFIG_AD7091R5) += ad7091r5.o diff --git a/drivers/iio/adc/ad4000.c b/drivers/iio/adc/ad4000.c new file mode 100644 index 000000000000..55dc36fbd549 --- /dev/null +++ b/drivers/iio/adc/ad4000.c @@ -0,0 +1,735 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * AD4000 SPI ADC driver + * + * Copyright 2024 Analog Devices Inc. + */ +#include <asm/unaligned.h> +#include <linux/bits.h> +#include <linux/bitfield.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/kernel.h> +#include <linux/math.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/gpio/consumer.h> +#include <linux/regulator/consumer.h> +#include <linux/spi/spi.h> +#include <linux/sysfs.h> +#include <linux/units.h> +#include <linux/util_macros.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/buffer.h> +#include <linux/iio/triggered_buffer.h> +#include <linux/iio/trigger_consumer.h> + +#define AD4000_READ_COMMAND 0x54 +#define AD4000_WRITE_COMMAND 0x14 + +#define AD4000_CONFIG_REG_MSK 0xFF + +/* AD4000 Configuration Register programmable bits */ +#define AD4000_CFG_STATUS BIT(4) /* Status bits output */ +#define AD4000_CFG_SPAN_COMP BIT(3) /* Input span compression */ +#define AD4000_CFG_HIGHZ BIT(2) /* High impedance mode */ +#define AD4000_CFG_TURBO BIT(1) /* Turbo mode */ + +#define AD4000_TQUIET1_NS 190 +#define AD4000_TQUIET2_NS 60 +#define AD4000_TCONV_NS 320 + +#define AD4000_18BIT_MSK GENMASK(31, 14) +#define AD4000_20BIT_MSK GENMASK(31, 12) + +#define AD4000_DIFF_CHANNEL(_sign, _real_bits) \ + { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .differential = 1, \ + .channel = 0, \ + .channel2 = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE),\ + .scan_type = { \ + .sign = _sign, \ + .realbits = _real_bits, \ + .storagebits = _real_bits > 16 ? 32 : 16, \ + .shift = _real_bits > 16 ? 32 - _real_bits : 0, \ + .endianness = IIO_BE, \ + }, \ + } \ + +#define AD4000_PSEUDO_DIFF_CHANNEL(_sign, _real_bits) \ + { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = 0, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ + .info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE),\ + .scan_type = { \ + .sign = _sign, \ + .realbits = _real_bits, \ + .storagebits = _real_bits > 16 ? 32 : 16, \ + .shift = _real_bits > 16 ? 32 - _real_bits : 0, \ + .endianness = IIO_BE, \ + }, \ + } \ + +enum ad4000_ids { + ID_AD4000, + ID_AD4001, + ID_AD4002, + ID_AD4003, + ID_AD4004, + ID_AD4005, + ID_AD4006, + ID_AD4007, + ID_AD4008, + ID_AD4010, + ID_AD4011, + ID_AD4020, + ID_AD4021, + ID_AD4022, + ID_ADAQ4001, + ID_ADAQ4003, +}; + +enum ad4000_spi_mode { + /* datasheet calls this "4-wire mode" (controller CS goes to ADC SDI!) */ + AD4000_SPI_MODE_DEFAULT, + /* datasheet calls this "3-wire mode" (not related to SPI_3WIRE!) */ + AD4000_SPI_MODE_SINGLE, +}; + +/* maps adi,spi-mode property value to enum */ +static const char * const ad4000_spi_modes[] = { + [AD4000_SPI_MODE_DEFAULT] = "", + [AD4000_SPI_MODE_SINGLE] = "single", +}; + +struct ad4000_chip_info { + const char *dev_name; + struct iio_chan_spec chan_spec; +}; + +static const struct ad4000_chip_info ad4000_chips[] = { + [ID_AD4000] = { + .dev_name = "ad4000", + .chan_spec = AD4000_PSEUDO_DIFF_CHANNEL('u', 16), + }, + [ID_AD4001] = { + .dev_name = "ad4001", + .chan_spec = AD4000_DIFF_CHANNEL('s', 16), + }, + [ID_AD4002] = { + .dev_name = "ad4002", + .chan_spec = AD4000_PSEUDO_DIFF_CHANNEL('u', 18), + }, + [ID_AD4003] = { + .dev_name = "ad4003", + .chan_spec = AD4000_DIFF_CHANNEL('s', 18), + }, + [ID_AD4004] = { + .dev_name = "ad4004", + .chan_spec = AD4000_PSEUDO_DIFF_CHANNEL('u', 16), + }, + [ID_AD4005] = { + .dev_name = "ad4005", + .chan_spec = AD4000_DIFF_CHANNEL('s', 16), + }, + [ID_AD4006] = { + .dev_name = "ad4006", + .chan_spec = AD4000_PSEUDO_DIFF_CHANNEL('u', 18), + }, + [ID_AD4007] = { + .dev_name = "ad4007", + .chan_spec = AD4000_DIFF_CHANNEL('s', 18), + }, + [ID_AD4008] = { + .dev_name = "ad4008", + .chan_spec = AD4000_PSEUDO_DIFF_CHANNEL('u', 16), + }, + [ID_AD4010] = { + .dev_name = "ad4010", + .chan_spec = AD4000_PSEUDO_DIFF_CHANNEL('u', 18), + }, + [ID_AD4011] = { + .dev_name = "ad4011", + .chan_spec = AD4000_DIFF_CHANNEL('s', 18), + }, + [ID_AD4020] = { + .dev_name = "ad4020", + .chan_spec = AD4000_DIFF_CHANNEL('s', 20), + }, + [ID_AD4021] = { + .dev_name = "ad4021", + .chan_spec = AD4000_DIFF_CHANNEL('s', 20), + }, + [ID_AD4022] = { + .dev_name = "ad4022", + .chan_spec = AD4000_DIFF_CHANNEL('s', 20), + }, + [ID_ADAQ4001] = { + .dev_name = "adaq4001", + .chan_spec = AD4000_DIFF_CHANNEL('s', 16), + }, + [ID_ADAQ4003] = { + .dev_name = "adaq4003", + .chan_spec = AD4000_DIFF_CHANNEL('s', 18), + }, +}; + +struct ad4000_state { + struct spi_device *spi; + struct gpio_desc *cnv_gpio; + struct spi_transfer xfers[2]; + struct spi_message msg; + int vref; + enum ad4000_spi_mode spi_mode; + bool span_comp; + bool turbo_mode; + int gain_milli; + int scale_tbl[2][2]; + + /* + * DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + */ + struct { + union { + __be16 sample_buf16; + __be32 sample_buf32; + } data; + s64 timestamp __aligned(8); + } scan __aligned(IIO_DMA_MINALIGN); + __be16 tx_buf; + __be16 rx_buf; +}; + +static void ad4000_fill_scale_tbl(struct ad4000_state *st, int scale_bits, + const struct ad4000_chip_info *chip) +{ + int diff = chip->chan_spec.differential; + int val, val2, tmp0, tmp1; + u64 tmp2; + + val2 = scale_bits; + val = st->vref / 1000; + /* + * The gain is stored as a fraction of 1000 and, as we need to + * divide vref by gain, we invert the gain/1000 fraction. + * Also multiply by an extra MILLI to avoid losing precision. + */ + val = mult_frac(val, MILLI * MILLI, st->gain_milli); + /* Would multiply by NANO here but we multiplied by extra MILLI */ + tmp2 = shift_right((u64)val * MICRO, val2); + tmp0 = (int)div_s64_rem(tmp2, NANO, &tmp1); + /* Store scale for when span compression is disabled */ + st->scale_tbl[0][0] = tmp0; /* Integer part */ + st->scale_tbl[0][1] = abs(tmp1); /* Fractional part */ + /* Store scale for when span compression is enabled */ + st->scale_tbl[1][0] = tmp0; + /* The integer part is always zero so don't bother to divide it. */ + if (diff) + st->scale_tbl[1][1] = DIV_ROUND_CLOSEST(abs(tmp1) * 4, 5); + else + st->scale_tbl[1][1] = DIV_ROUND_CLOSEST(abs(tmp1) * 9, 10); +} + +static int ad4000_write_reg(struct ad4000_state *st, uint8_t val) +{ + st->tx_buf = cpu_to_be16(AD4000_WRITE_COMMAND << BITS_PER_BYTE | val); + return spi_write(st->spi, &st->tx_buf, 2); +} + +static int ad4000_read_reg(struct ad4000_state *st, unsigned int *val) +{ + struct spi_transfer t[] = { + { + .tx_buf = &st->tx_buf, + .rx_buf = &st->rx_buf, + .len = 2, + }, + }; + int ret; + + st->tx_buf = cpu_to_be16(AD4000_READ_COMMAND << BITS_PER_BYTE); + ret = spi_sync_transfer(st->spi, t, ARRAY_SIZE(t)); + if (ret < 0) + return ret; + + *val = be16_to_cpu(st->rx_buf); + + return ret; +} + +static void ad4000_unoptimize_msg(void *msg) +{ + spi_unoptimize_message(msg); +} + +/* + * This executes a data sample transfer for when the device connections are + * in "3-wire" mode, selected by setting the adi,spi-mode device tree property + * to "single". In this connection mode, the ADC SDI pin is connected to MOSI or + * to VIO and ADC CNV pin is connected either to a SPI controller CS or to a GPIO. + * AD4000 series of devices initiate conversions on the rising edge of CNV pin. + * + * If the CNV pin is connected to an SPI controller CS line (which is by default + * active low), the ADC readings would have a latency (delay) of one read. + * Moreover, since we also do ADC sampling for filling the buffer on triggered + * buffer mode, the timestamps of buffer readings would be disarranged. + * To prevent the read latency and reduce the time discrepancy between the + * sample read request and the time of actual sampling by the ADC, do a + * preparatory transfer to pulse the CS/CNV line. + */ +static int ad4000_prepare_3wire_mode_message(struct ad4000_state *st, + const struct iio_chan_spec *chan) +{ + unsigned int cnv_pulse_time = st->turbo_mode ? AD4000_TQUIET1_NS + : AD4000_TCONV_NS; + struct spi_transfer *xfers = st->xfers; + int ret; + + xfers[0].cs_change = 1; + xfers[0].cs_change_delay.value = cnv_pulse_time; + xfers[0].cs_change_delay.unit = SPI_DELAY_UNIT_NSECS; + + xfers[1].rx_buf = &st->scan.data; + xfers[1].len = BITS_TO_BYTES(chan->scan_type.storagebits); + xfers[1].delay.value = AD4000_TQUIET2_NS; + xfers[1].delay.unit = SPI_DELAY_UNIT_NSECS; + + spi_message_init_with_transfers(&st->msg, st->xfers, 2); + + ret = spi_optimize_message(st->spi, &st->msg); + if (ret) + return ret; + + return devm_add_action_or_reset(&st->spi->dev, ad4000_unoptimize_msg, + &st->msg); +} + +/* + * This executes a data sample transfer for when the device connections are + * in "4-wire" mode, selected when the adi,spi-mode device tree + * property is absent or empty. In this connection mode, the controller CS pin + * is connected to ADC SDI pin and a GPIO is connected to ADC CNV pin. + * The GPIO connected to ADC CNV pin is set outside of the SPI transfer. + */ +static int ad4000_prepare_4wire_mode_message(struct ad4000_state *st, + const struct iio_chan_spec *chan) +{ + unsigned int cnv_to_sdi_time = st->turbo_mode ? AD4000_TQUIET1_NS + : AD4000_TCONV_NS; + struct spi_transfer *xfers = st->xfers; + int ret; + + /* + * Dummy transfer to cause enough delay between CNV going high and SDI + * going low. + */ + xfers[0].cs_off = 1; + xfers[0].delay.value = cnv_to_sdi_time; + xfers[0].delay.unit = SPI_DELAY_UNIT_NSECS; + + xfers[1].rx_buf = &st->scan.data; + xfers[1].len = BITS_TO_BYTES(chan->scan_type.storagebits); + + spi_message_init_with_transfers(&st->msg, st->xfers, 2); + + ret = spi_optimize_message(st->spi, &st->msg); + if (ret) + return ret; + + return devm_add_action_or_reset(&st->spi->dev, ad4000_unoptimize_msg, + &st->msg); +} + +static int ad4000_convert_and_acquire(struct ad4000_state *st) +{ + int ret; + + /* + * In 4-wire mode, the CNV line is held high for the entire conversion + * and acquisition process. In other modes st->cnv_gpio is NULL and is + * ignored (CS is wired to CNV in those cases). + */ + gpiod_set_value_cansleep(st->cnv_gpio, 1); + ret = spi_sync(st->spi, &st->msg); + gpiod_set_value_cansleep(st->cnv_gpio, 0); + + return ret; +} + +static int ad4000_single_conversion(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int *val) +{ + struct ad4000_state *st = iio_priv(indio_dev); + u32 sample; + int ret; + + ret = ad4000_convert_and_acquire(st); + + if (chan->scan_type.storagebits > 16) + sample = be32_to_cpu(st->scan.data.sample_buf32); + else + sample = be16_to_cpu(st->scan.data.sample_buf16); + + switch (chan->scan_type.realbits) { + case 16: + break; + case 18: + sample = FIELD_GET(AD4000_18BIT_MSK, sample); + break; + case 20: + sample = FIELD_GET(AD4000_20BIT_MSK, sample); + break; + default: + return -EINVAL; + } + + if (chan->scan_type.sign == 's') + *val = sign_extend32(sample, chan->scan_type.realbits - 1); + + return IIO_VAL_INT; +} + +static int ad4000_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long info) +{ + struct ad4000_state *st = iio_priv(indio_dev); + + switch (info) { + case IIO_CHAN_INFO_RAW: + iio_device_claim_direct_scoped(return -EBUSY, indio_dev) + return ad4000_single_conversion(indio_dev, chan, val); + unreachable(); + case IIO_CHAN_INFO_SCALE: + *val = st->scale_tbl[st->span_comp][0]; + *val2 = st->scale_tbl[st->span_comp][1]; + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_OFFSET: + *val = 0; + if (st->span_comp) + *val = mult_frac(st->vref / 1000, 1, 10); + + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int ad4000_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long info) +{ + struct ad4000_state *st = iio_priv(indio_dev); + + switch (info) { + case IIO_CHAN_INFO_SCALE: + *vals = (int *)st->scale_tbl; + *length = 2 * 2; + *type = IIO_VAL_INT_PLUS_NANO; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int ad4000_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + default: + return IIO_VAL_INT_PLUS_MICRO; + } +} + +static int ad4000_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, + long mask) +{ + struct ad4000_state *st = iio_priv(indio_dev); + unsigned int reg_val; + bool span_comp_en; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + iio_device_claim_direct_scoped(return -EBUSY, indio_dev) { + ret = ad4000_read_reg(st, ®_val); + if (ret < 0) + return ret; + + span_comp_en = (val2 == st->scale_tbl[1][1]); + reg_val &= ~AD4000_CFG_SPAN_COMP; + reg_val |= FIELD_PREP(AD4000_CFG_SPAN_COMP, span_comp_en); + + ret = ad4000_write_reg(st, reg_val); + if (ret < 0) + return ret; + + st->span_comp = span_comp_en; + return 0; + } + unreachable(); + default: + return -EINVAL; + } +} + +static irqreturn_t ad4000_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ad4000_state *st = iio_priv(indio_dev); + int ret; + + ret = ad4000_convert_and_acquire(st); + if (ret < 0) + goto err_out; + + iio_push_to_buffers_with_timestamp(indio_dev, &st->scan, + iio_get_time_ns(indio_dev)); + +err_out: + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; +} + +static int ad4000_reg_access(struct iio_dev *indio_dev, unsigned int reg, + unsigned int writeval, unsigned int *readval) +{ + struct ad4000_state *st = iio_priv(indio_dev); + int ret; + + if (readval) + ret = ad4000_read_reg(st, readval); + else + ret = ad4000_write_reg(st, writeval); + + return ret; +} + +static const struct iio_info ad4000_info = { + .read_raw = &ad4000_read_raw, + .read_avail = &ad4000_read_avail, + .write_raw = &ad4000_write_raw, + .write_raw_get_fmt = &ad4000_write_raw_get_fmt, + .debugfs_reg_access = &ad4000_reg_access, + +}; + +static int ad4000_config(struct ad4000_state *st) +{ + unsigned int reg_val; + + if (device_property_present(&st->spi->dev, "adi,high-z-input")) + reg_val |= FIELD_PREP(AD4000_CFG_HIGHZ, 1); + + /* + * The ADC SDI pin might be connected to controller CS line in which + * case the write might fail. This, however, does not prevent the device + * from functioning even though in a configuration other than the + * requested one. + */ + return ad4000_write_reg(st, reg_val); +} + +static void ad4000_regulator_disable(void *reg) +{ + regulator_disable(reg); +} + +static int ad4000_probe(struct spi_device *spi) +{ + const struct ad4000_chip_info *chip; + struct regulator *vref_reg; + struct iio_dev *indio_dev; + struct ad4000_state *st; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + chip = spi_get_device_match_data(spi); + if (!chip) + return -EINVAL; + + st = iio_priv(indio_dev); + st->spi = spi; + + ret = devm_regulator_get_enable(&spi->dev, "vdd"); + if (ret) + return dev_err_probe(&spi->dev, ret, "Failed to enable VDD supply\n"); + + ret = devm_regulator_get_enable(&spi->dev, "vio"); + if (ret) + return dev_err_probe(&spi->dev, ret, "Failed to enable VIO supply\n"); + + vref_reg = devm_regulator_get(&spi->dev, "ref"); + if (IS_ERR(vref_reg)) + return dev_err_probe(&spi->dev, PTR_ERR(vref_reg), + "Failed to get vref regulator\n"); + + ret = regulator_enable(vref_reg); + if (ret < 0) + return dev_err_probe(&spi->dev, ret, + "Failed to enable voltage regulator\n"); + + ret = devm_add_action_or_reset(&spi->dev, ad4000_regulator_disable, vref_reg); + if (ret) + return dev_err_probe(&spi->dev, ret, + "Failed to add regulator disable action\n"); + + st->vref = regulator_get_voltage(vref_reg); + if (st->vref < 0) + return dev_err_probe(&spi->dev, st->vref, "Failed to get vref\n"); + + st->cnv_gpio = devm_gpiod_get_optional(&spi->dev, "cnv", GPIOD_OUT_HIGH); + if (IS_ERR(st->cnv_gpio)) + return dev_err_probe(&spi->dev, PTR_ERR(st->cnv_gpio), + "Failed to get CNV GPIO"); + + ret = device_property_match_property_string(&spi->dev, "adi,spi-mode", + ad4000_spi_modes, + ARRAY_SIZE(ad4000_spi_modes)); + /* Default to 4-wire mode if adi,spi-mode property is not present */ + if (ret == -EINVAL) + st->spi_mode = AD4000_SPI_MODE_DEFAULT; + else if (ret < 0) + return dev_err_probe(&spi->dev, ret, + "getting adi,spi-mode property failed\n"); + else + st->spi_mode = ret; + + switch (st->spi_mode) { + case AD4000_SPI_MODE_DEFAULT: + ret = ad4000_prepare_4wire_mode_message(st, &chip->chan_spec); + if (ret) + return ret; + + break; + case AD4000_SPI_MODE_SINGLE: + ret = ad4000_prepare_3wire_mode_message(st, &chip->chan_spec); + if (ret) + return ret; + + /* + * In "3-wire mode", the ADC SDI line must be kept high when + * data is not being clocked out of the controller. + * Request the SPI controller to make MOSI idle high. + */ + spi->mode = SPI_MODE_0 | SPI_MOSI_IDLE_HIGH; + if (spi_setup(spi)) + dev_warn(&st->spi->dev, "SPI controller setup failed\n"); + + break; + } + + ret = ad4000_config(st); + if (ret < 0) + dev_dbg(&st->spi->dev, "Failed to config device\n"); + + indio_dev->name = chip->dev_name; + indio_dev->info = &ad4000_info; + indio_dev->channels = &chip->chan_spec; + indio_dev->num_channels = 1; + + /* Hardware gain only applies to ADAQ devices */ + st->gain_milli = 1000; + if (device_property_present(&spi->dev, "adi,gain-milli")) { + + ret = device_property_read_u32(&spi->dev, "adi,gain-milli", + &st->gain_milli); + if (ret) + return dev_err_probe(&spi->dev, ret, + "Failed to read gain property\n"); + } + + /* + * ADCs that output two's complement code have one less bit to express + * voltage magnitude. + */ + if (chip->chan_spec.scan_type.sign == 's') + ad4000_fill_scale_tbl(st, chip->chan_spec.scan_type.realbits - 1, + chip); + else + ad4000_fill_scale_tbl(st, chip->chan_spec.scan_type.realbits, + chip); + + ret = devm_iio_triggered_buffer_setup(&spi->dev, indio_dev, + &iio_pollfunc_store_time, + &ad4000_trigger_handler, NULL); + if (ret) + return ret; + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const struct spi_device_id ad4000_id[] = { + { "ad4000", (kernel_ulong_t)&ad4000_chips[ID_AD4000] }, + { "ad4001", (kernel_ulong_t)&ad4000_chips[ID_AD4001] }, + { "ad4002", (kernel_ulong_t)&ad4000_chips[ID_AD4002] }, + { "ad4003", (kernel_ulong_t)&ad4000_chips[ID_AD4003] }, + { "ad4004", (kernel_ulong_t)&ad4000_chips[ID_AD4004] }, + { "ad4005", (kernel_ulong_t)&ad4000_chips[ID_AD4005] }, + { "ad4006", (kernel_ulong_t)&ad4000_chips[ID_AD4006] }, + { "ad4007", (kernel_ulong_t)&ad4000_chips[ID_AD4007] }, + { "ad4008", (kernel_ulong_t)&ad4000_chips[ID_AD4008] }, + { "ad4010", (kernel_ulong_t)&ad4000_chips[ID_AD4010] }, + { "ad4011", (kernel_ulong_t)&ad4000_chips[ID_AD4011] }, + { "ad4020", (kernel_ulong_t)&ad4000_chips[ID_AD4020] }, + { "ad4021", (kernel_ulong_t)&ad4000_chips[ID_AD4021] }, + { "ad4022", (kernel_ulong_t)&ad4000_chips[ID_AD4022] }, + { "adaq4001", (kernel_ulong_t)&ad4000_chips[ID_ADAQ4001] }, + { "adaq4003", (kernel_ulong_t)&ad4000_chips[ID_ADAQ4003] }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad4000_id); + +static const struct of_device_id ad4000_of_match[] = { + { .compatible = "adi,ad4000", .data = &ad4000_chips[ID_AD4000] }, + { .compatible = "adi,ad4001", .data = &ad4000_chips[ID_AD4001] }, + { .compatible = "adi,ad4002", .data = &ad4000_chips[ID_AD4002] }, + { .compatible = "adi,ad4003", .data = &ad4000_chips[ID_AD4003] }, + { .compatible = "adi,ad4004", .data = &ad4000_chips[ID_AD4004] }, + { .compatible = "adi,ad4005", .data = &ad4000_chips[ID_AD4005] }, + { .compatible = "adi,ad4006", .data = &ad4000_chips[ID_AD4006] }, + { .compatible = "adi,ad4007", .data = &ad4000_chips[ID_AD4007] }, + { .compatible = "adi,ad4008", .data = &ad4000_chips[ID_AD4008] }, + { .compatible = "adi,ad4010", .data = &ad4000_chips[ID_AD4010] }, + { .compatible = "adi,ad4011", .data = &ad4000_chips[ID_AD4011] }, + { .compatible = "adi,ad4020", .data = &ad4000_chips[ID_AD4020] }, + { .compatible = "adi,ad4021", .data = &ad4000_chips[ID_AD4021] }, + { .compatible = "adi,ad4022", .data = &ad4000_chips[ID_AD4022] }, + { .compatible = "adi,adaq4001", .data = &ad4000_chips[ID_ADAQ4001] }, + { .compatible = "adi,adaq4003", .data = &ad4000_chips[ID_ADAQ4003] }, + { } +}; +MODULE_DEVICE_TABLE(of, ad4000_of_match); + +static struct spi_driver ad4000_driver = { + .driver = { + .name = "ad4000", + .of_match_table = ad4000_of_match, + }, + .probe = ad4000_probe, + .id_table = ad4000_id, +}; +module_spi_driver(ad4000_driver); + +MODULE_AUTHOR("Marcelo Schmitt <marcelo.schmitt@analog.com>"); +MODULE_DESCRIPTION("Analog Devices AD4000 ADC driver"); +MODULE_LICENSE("GPL");
Add support for AD4000 series of low noise, low power, high speed, successive aproximation register (SAR) ADCs. Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com> --- MAINTAINERS | 1 + drivers/iio/adc/Kconfig | 12 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/ad4000.c | 735 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 749 insertions(+) create mode 100644 drivers/iio/adc/ad4000.c