On Mon, 2024-10-21 at 14:40 +0200, Angelo Dureghello wrote: > From: Angelo Dureghello <adureghello@xxxxxxxxxxxx> > > Add High Speed ad3552r platform driver. > > The ad3552r DAC is controlled by a custom (fpga-based) DAC IP > through the current AXI backend, or similar alternative IIO backend. > > Compared to the existing driver (ad3552r.c), that is a simple SPI > driver, this driver is coupled with a DAC IIO backend that finally > controls the ad3552r by a fpga-based "QSPI+DDR" interface, to reach > maximum transfer rate of 33MUPS using dma stream capabilities. > > All commands involving QSPI bus read/write are delegated to the backend > through the provided APIs for bus read/write. > > Signed-off-by: Angelo Dureghello <adureghello@xxxxxxxxxxxx> > --- > drivers/iio/dac/Kconfig | 14 ++ > drivers/iio/dac/Makefile | 1 + > drivers/iio/dac/ad3552r-hs.c | 547 > +++++++++++++++++++++++++++++++++++++++++++ > drivers/iio/dac/ad3552r-hs.h | 18 ++ > drivers/iio/dac/ad3552r.h | 4 + > 5 files changed, 584 insertions(+) > > diff --git a/drivers/iio/dac/Kconfig b/drivers/iio/dac/Kconfig > index fa091995d002..fc11698e88f2 100644 > --- a/drivers/iio/dac/Kconfig > +++ b/drivers/iio/dac/Kconfig > @@ -6,6 +6,20 @@ > > menu "Digital to analog converters" > > +config AD3552R_HS > + tristate "Analog Devices AD3552R DAC High Speed driver" > + select ADI_AXI_DAC > + help > + Say yes here to build support for Analog Devices AD3552R > + Digital to Analog Converter High Speed driver. > + > + The driver requires the assistance of an IP core to operate, > + since data is streamed into target device via DMA, sent over a > + QSPI + DDR (Double Data Rate) bus. > + > + To compile this driver as a module, choose M here: the > + module will be called ad3552r-hs. > + > config AD3552R > tristate "Analog Devices AD3552R DAC driver" > depends on SPI_MASTER > diff --git a/drivers/iio/dac/Makefile b/drivers/iio/dac/Makefile > index c92de0366238..d92e08ca93ca 100644 > --- a/drivers/iio/dac/Makefile > +++ b/drivers/iio/dac/Makefile > @@ -4,6 +4,7 @@ > # > > # When adding new entries keep the list in alphabetical order > +obj-$(CONFIG_AD3552R_HS) += ad3552r-hs.o ad3552r-common.o > obj-$(CONFIG_AD3552R) += ad3552r.o ad3552r-common.o > obj-$(CONFIG_AD5360) += ad5360.o > obj-$(CONFIG_AD5380) += ad5380.o > diff --git a/drivers/iio/dac/ad3552r-hs.c b/drivers/iio/dac/ad3552r-hs.c > new file mode 100644 > index 000000000000..27bdc35fdc29 > --- /dev/null > +++ b/drivers/iio/dac/ad3552r-hs.c > @@ -0,0 +1,547 @@ > +// SPDX-License-Identifier: GPL-2.0-only > +/* > + * Analog Devices AD3552R > + * Digital to Analog converter driver, High Speed version > + * > + * Copyright 2024 Analog Devices Inc. > + */ > + > +#include <linux/bitfield.h> > +#include <linux/delay.h> > +#include <linux/gpio/consumer.h> > +#include <linux/iio/backend.h> > +#include <linux/iio/buffer.h> > +#include <linux/mod_devicetable.h> > +#include <linux/platform_device.h> > +#include <linux/property.h> > +#include <linux/units.h> > + > +#include "ad3552r.h" > +#include "ad3552r-hs.h" > + > +struct ad3552r_hs_state { > + const struct ad3552r_model_data *model_data; > + struct gpio_desc *reset_gpio; > + struct device *dev; > + struct iio_backend *back; > + bool single_channel; > + struct ad3552r_ch_data ch_data[AD3552R_MAX_CH]; > + struct ad3552r_hs_platform_data *data; > +}; > + > +static int ad3552r_qspi_update_reg_bits(struct ad3552r_hs_state *st, > + u32 reg, u32 mask, u32 val, > + size_t xfer_size) > +{ > + u32 rval; > + int ret; > + > + ret = st->data->bus_reg_read(st->back, reg, &rval, xfer_size); > + if (ret) > + return ret; > + > + rval = (rval & ~mask) | val; > + > + return st->data->bus_reg_write(st->back, reg, rval, xfer_size); > +} > + > +static int ad3552r_hs_read_raw(struct iio_dev *indio_dev, > + struct iio_chan_spec const *chan, > + int *val, int *val2, long mask) > +{ > + struct ad3552r_hs_state *st = iio_priv(indio_dev); > + int ret; > + int ch = chan->channel; > + > + switch (mask) { > + case IIO_CHAN_INFO_SAMP_FREQ: { > + int sclk; > + > + ret = iio_backend_read_raw(st->back, chan, &sclk, 0, > + IIO_CHAN_INFO_FREQUENCY); > + if (ret != IIO_VAL_INT) > + return -EINVAL; > + I just saw you had some questions on v6 that everyone failed to see. See my reply to David here: https://lore.kernel.org/linux-iio/61cf3072af74a8b2951c948ddc2383ba1e55954d.camel@xxxxxxxxx/ It should be easy and it's something that makes sense (at least to me :)) > + /* > + * Using 4 lanes (QSPI), then using 2 as DDR mode is > + * considered always on (considering buffering mode always). > + */ > + *val = DIV_ROUND_CLOSEST(sclk * 4 * 2, > + chan->scan_type.realbits); > + > + return IIO_VAL_INT; > + } > + case IIO_CHAN_INFO_RAW: > + ret = st->data->bus_reg_read(st->back, > + AD3552R_REG_ADDR_CH_DAC_16B(chan->channel), > + val, 2); > + if (ret) > + return ret; > + > + return IIO_VAL_INT; > + case IIO_CHAN_INFO_SCALE: > + *val = st->ch_data[ch].scale_int; > + *val2 = st->ch_data[ch].scale_dec; > + return IIO_VAL_INT_PLUS_MICRO; > + case IIO_CHAN_INFO_OFFSET: > + *val = st->ch_data[ch].offset_int; > + *val2 = st->ch_data[ch].offset_dec; > + return IIO_VAL_INT_PLUS_MICRO; > + default: > + return -EINVAL; > + } > +} > + > +static int ad3552r_hs_write_raw(struct iio_dev *indio_dev, > + struct iio_chan_spec const *chan, > + int val, int val2, long mask) > +{ > + struct ad3552r_hs_state *st = iio_priv(indio_dev); > + > + switch (mask) { > + case IIO_CHAN_INFO_RAW: > + iio_device_claim_direct_scoped(return -EBUSY, indio_dev) { > + return st->data->bus_reg_write(st->back, > + AD3552R_REG_ADDR_CH_DAC_16B(chan- > >channel), > + val, 2); > + } Maybe we'll get the new stuff in time for this :) ... > + > +static int ad3552r_hs_reset(struct ad3552r_hs_state *st) > +{ > + int ret; > + > + /* > + * Using inverted "active-high" logic here, since ad3552r classic-spi > + * fdt node (and driver) is using the same logic. > + */ > + I don't understand this. This is a new device with a different compatible. Why keeping the wrong logic? AFAICT, there's nothing in the bindings about the pin polarity. > + st->reset_gpio = devm_gpiod_get_optional(st->dev, > + "reset", GPIOD_OUT_LOW); > + if (IS_ERR(st->reset_gpio)) > + return PTR_ERR(st->reset_gpio); > + > + if (st->reset_gpio) { > + fsleep(10); > + gpiod_set_value_cansleep(st->reset_gpio, 1); > + } else { > + ret = ad3552r_qspi_update_reg_bits(st, > + AD3552R_REG_ADDR_INTERFACE_CONFIG_A, > + AD3552R_MASK_SOFTWARE_RESET, > + AD3552R_MASK_SOFTWARE_RESET, 1); > + if (ret) > + return ret; > + } > + msleep(100); > + > + return 0; > +} > + > +static int ad3552r_hs_scratch_pad_test(struct ad3552r_hs_state *st) > +{ > + int ret, val; > + > + ret = st->data->bus_reg_write(st->back, AD3552R_REG_ADDR_SCRATCH_PAD, > + AD3552R_SCRATCH_PAD_TEST_VAL1, 1); > + if (ret) > + return ret; > + > + ret = st->data->bus_reg_read(st->back, AD3552R_REG_ADDR_SCRATCH_PAD, > + &val, 1); > + if (ret) > + return ret; > + > + if (val != AD3552R_SCRATCH_PAD_TEST_VAL1) > + return dev_err_probe(st->dev, -EIO, > + "SCRATCH_PAD_TEST mismatch. Expected 0x%x, Read > 0x%x\n", > + AD3552R_SCRATCH_PAD_TEST_VAL1, val); > + > + ret = st->data->bus_reg_write(st->back, AD3552R_REG_ADDR_SCRATCH_PAD, > + AD3552R_SCRATCH_PAD_TEST_VAL2, 1); > + if (ret) > + return ret; > + > + ret = st->data->bus_reg_read(st->back, AD3552R_REG_ADDR_SCRATCH_PAD, > + &val, 1); > + if (ret) > + return ret; > + > + if (val != AD3552R_SCRATCH_PAD_TEST_VAL2) > + return dev_err_probe(st->dev, -EIO, > + "SCRATCH_PAD_TEST mismatch. Expected 0x%x, Read > 0x%x\n", > + AD3552R_SCRATCH_PAD_TEST_VAL2, val); > + > + return 0; > +} > + > +static int ad3552r_hs_setup_custom_gain(struct ad3552r_hs_state *st, > + int ch, u16 gain, u16 offset) > +{ > + int ret; > + > + ret = st->data->bus_reg_write(st->back, > AD3552R_REG_ADDR_CH_OFFSET(ch), > + offset, 1); > + if (ret) > + return dev_err_probe(st->dev, ret, "Error writing > register\n"); > + > + ret = st->data->bus_reg_write(st->back, AD3552R_REG_ADDR_CH_GAIN(ch), > + gain, 1); > + if (ret) > + return dev_err_probe(st->dev, ret, "Error writing > register\n"); > + > + return 0; nit: Not a big fan of these logs on read/write registers functions... Also seems that you're not being consistent (either you have them or not). FWIW, I would simplify and drop them. That would allow to do return st->data->bus_reg_write(st->back, AD3552R_REG_ADDR_CH_GAIN(ch), gain, 1); > +} > + > +static int ad3552r_hs_setup(struct ad3552r_hs_state *st) > +{ > + s16 goffs; > + u16 id; > + u16 gain = 0, offset = 0; > + u32 ch, val, range; > + int ret; > + > + ret = ad3552r_hs_reset(st); > + if (ret) > + return ret; > + > + ret = iio_backend_ddr_disable(st->back); > + if (ret) > + return ret; > + > + ret = ad3552r_hs_scratch_pad_test(st); > + if (ret) > + return ret; > + > + ret = st->data->bus_reg_read(st->back, AD3552R_REG_ADDR_PRODUCT_ID_L, > + &val, 1); > + if (ret) > + return ret; > + > + id = val; > + > + ret = st->data->bus_reg_read(st->back, AD3552R_REG_ADDR_PRODUCT_ID_H, > + &val, 1); > + if (ret) > + return ret; > + > + id |= val << 8; > + if (id != st->model_data->chip_id) > + dev_info(st->dev, "Chip ID error. Expected 0x%x, Read > 0x%x\n", > + AD3552R_ID, id); > + > + ret = st->data->bus_reg_write(st->back, > + AD3552R_REG_ADDR_SH_REFERENCE_CONFIG, > + 0, 1); > + if (ret) > + return ret; > + > + ret = st->data->bus_reg_write(st->back, > + AD3552R_REG_ADDR_TRANSFER_REGISTER, > + FIELD_PREP(AD3552R_MASK_MULTI_IO_MODE, > + AD3552R_QUAD_SPI) | > + AD3552R_MASK_STREAM_LENGTH_KEEP_VALUE, 1); > + if (ret) > + return ret; > + > + ret = iio_backend_data_source_set(st->back, 0, IIO_BACKEND_EXTERNAL); > + if (ret) > + return ret; > + > + ret = iio_backend_data_source_set(st->back, 1, IIO_BACKEND_EXTERNAL); > + if (ret) > + return ret; > + > + ret = ad3552r_get_ref_voltage(st->dev, &val); > + if (ret < 0) > + return ret; > + > + val = ret; > + > + ret = ad3552r_qspi_update_reg_bits(st, > + AD3552R_REG_ADDR_SH_REFERENCE_CONFIG, > + AD3552R_MASK_REFERENCE_VOLTAGE_SEL, > + val, 1); > + if (ret) > + return ret; > + > + ret = ad3552r_get_drive_strength(st->dev, &val); > + if (!ret) { > + ret = ad3552r_qspi_update_reg_bits(st, > + AD3552R_REG_ADDR_INTERFACE_CONFIG_D, > + AD3552R_MASK_SDO_DRIVE_STRENGTH, > + val, 1); > + if (ret) > + return ret; > + } > + > + device_for_each_child_node_scoped(st->dev, child) { > + ret = fwnode_property_read_u32(child, "reg", &ch); > + if (ret) > + return dev_err_probe(st->dev, ret, > + "reg property missing\n"); > + > + ret = ad3552r_get_output_range(st->dev, st->model_data, > child, > + &range); > + if (!ret) { > + st->ch_data[ch].range = range; > + > + ret = ad3552r_hs_set_output_range(st, ch, range); > + if (ret) > + return ret; > + > + } else if (ret == -ENOENT) { > + ret = ad3552r_get_custom_gain(st->dev, child, > + &st->ch_data[ch].p, > + &st->ch_data[ch].n, > + &st->ch_data[ch].rfb, > + &st- > >ch_data[ch].gain_offset); > + if (ret) > + return ret; > + > + gain = ad3552r_calc_custom_gain(st->ch_data[ch].p, > + st->ch_data[ch].n, > + st->ch_data[ch].gain_offset); > + offset = abs(goffs); > + > + st->ch_data[ch].range_override = 1; > + > + ret = ad3552r_hs_setup_custom_gain(st, ch, gain, > + offset); > + if (ret) > + return ret; > + } else { > + return ret; > + } Just personal preference... I think this would be neater: if (ret && ret != ENOENT) return ret; if (ret == -ENOENT) { ... } else { ... } Advantage is that it also handles errors first (which is the typical pattern) > + > + ad3552r_calc_gain_and_offset(&st->ch_data[ch], st- > >model_data); > + } > + > + return 0; > +} > + > +static const struct iio_buffer_setup_ops ad3552r_hs_buffer_setup_ops = { > + .postenable = ad3552r_hs_buffer_postenable, > + .predisable = ad3552r_hs_buffer_predisable, > +}; > + > +#define AD3552R_CHANNEL(ch) { \ > + .type = IIO_VOLTAGE, \ > + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ > + BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ > + BIT(IIO_CHAN_INFO_SCALE) | \ > + BIT(IIO_CHAN_INFO_OFFSET), \ > + .output = 1, \ > + .indexed = 1, \ > + .channel = (ch), \ > + .scan_index = (ch), \ > + .scan_type = { \ > + .sign = 'u', \ > + .realbits = 16, \ > + .storagebits = 16, \ > + .endianness = IIO_BE, \ > + } \ > +} > + > +static const struct iio_chan_spec ad3552r_hs_channels[] = { > + AD3552R_CHANNEL(0), > + AD3552R_CHANNEL(1), > +}; > + > +static const struct iio_info ad3552r_hs_info = { > + .read_raw = &ad3552r_hs_read_raw, > + .write_raw = &ad3552r_hs_write_raw, > +}; > + > +static int ad3552r_hs_probe(struct platform_device *pdev) > +{ > + struct ad3552r_hs_state *st; > + struct iio_dev *indio_dev; > + int ret; > + > + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*st)); > + if (!indio_dev) > + return -ENOMEM; > + > + st = iio_priv(indio_dev); > + st->dev = &pdev->dev; > + > + st->data = pdev->dev.platform_data; dev_get_platdata() - Nuno Sá