From 3976d47e4f5f3596400267f7c71f2211d7deadfa Mon Sep 17 00:00:00 2001 From: Axel Haslam Date: Tue, 15 Oct 2024 15:10:11 +0200 Subject: [PATCH] iio: dac: ad5791: Add offload support Add spi offload support to stream tx buffers using dma. This allows loading samples to the dac with a rate of 1 MSPS. Signed-off-by: Axel Haslam --- drivers/iio/dac/ad5791.c | 222 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 209 insertions(+), 13 deletions(-) diff --git a/drivers/iio/dac/ad5791.c b/drivers/iio/dac/ad5791.c index 0e56e2f06cff9b..abb76e0aa8acad 100644 --- a/drivers/iio/dac/ad5791.c +++ b/drivers/iio/dac/ad5791.c @@ -16,6 +16,14 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include #include #include @@ -59,6 +67,11 @@ #define AD5791_DAC_PWRDN_6K 0 #define AD5791_DAC_PWRDN_3STATE 1 +/* Arbitrary sane max sampling rate. datasheet only mentions + * max SPI bauderate of 35Mbps (x 24 bits) + */ +#define AD5791_MAX_SAMPLING_RATE 1000000 + /** * struct ad5791_chip_info - chip specific information * @get_lin_comp: function pointer to the device specific function @@ -89,6 +102,10 @@ struct ad5791_state { struct gpio_desc *gpio_clear; struct gpio_desc *gpio_ldac; const struct ad5791_chip_info *chip_info; + struct spi_message spi_msg; + struct spi_transfer spi_transfer; + struct pwm_device *cnv_trigger; + unsigned long ref_clk_rate; unsigned short vref_mv; unsigned int vref_neg_mv; unsigned ctrl; @@ -175,6 +192,24 @@ static const struct iio_enum ad5791_powerdown_mode_enum = { .set = ad5791_set_powerdown_mode, }; +static int __ad5791_dac_powerdown(struct iio_dev *indio_dev, + bool pwr_down) +{ + struct ad5791_state *st = iio_priv(indio_dev); + + if (!pwr_down) { + st->ctrl &= ~(AD5791_CTRL_OPGND | AD5791_CTRL_DACTRI); + } else { + if (st->pwr_down_mode == AD5791_DAC_PWRDN_6K) + st->ctrl |= AD5791_CTRL_OPGND; + else if (st->pwr_down_mode == AD5791_DAC_PWRDN_3STATE) + st->ctrl |= AD5791_CTRL_DACTRI; + } + st->pwr_down = pwr_down; + + return ad5791_spi_write(st, AD5791_ADDR_CTRL, st->ctrl); +} + static ssize_t ad5791_read_dac_powerdown(struct iio_dev *indio_dev, uintptr_t private, const struct iio_chan_spec *chan, char *buf) { @@ -189,23 +224,12 @@ static ssize_t ad5791_write_dac_powerdown(struct iio_dev *indio_dev, { bool pwr_down; int ret; - struct ad5791_state *st = iio_priv(indio_dev); ret = kstrtobool(buf, &pwr_down); if (ret) return ret; - if (!pwr_down) { - st->ctrl &= ~(AD5791_CTRL_OPGND | AD5791_CTRL_DACTRI); - } else { - if (st->pwr_down_mode == AD5791_DAC_PWRDN_6K) - st->ctrl |= AD5791_CTRL_OPGND; - else if (st->pwr_down_mode == AD5791_DAC_PWRDN_3STATE) - st->ctrl |= AD5791_CTRL_DACTRI; - } - st->pwr_down = pwr_down; - - ret = ad5791_spi_write(st, AD5791_ADDR_CTRL, st->ctrl); + ret = __ad5791_dac_powerdown(indio_dev, pwr_down); return ret ? ret : len; } @@ -246,6 +270,49 @@ static const struct ad5791_chip_info ad5791_chip_info_tbl[] = { }, }; +static int ad5791_get_sampling_freq(struct ad5791_state *st) +{ + return DIV_ROUND_CLOSEST_ULL(1000000000000, + pwm_get_period(st->cnv_trigger)); +} + +static int __ad5791_set_sampling_freq(struct ad5791_state *st, int freq) +{ + unsigned long long ref_clk_period_ps; + struct pwm_state cnv_state; + + /* Sync up PWM state and prepare for pwm_apply_state(). */ + pwm_init_state(st->cnv_trigger, &cnv_state); + + ref_clk_period_ps = DIV_ROUND_CLOSEST_ULL(1000000000000, + st->ref_clk_rate); + cnv_state.period = DIV_ROUND_CLOSEST_ULL(1000000000000, freq); + cnv_state.duty_cycle = ref_clk_period_ps; + cnv_state.time_unit = PWM_UNIT_PSEC; + return pwm_apply_state(st->cnv_trigger, &cnv_state); +} + +static int ad5791_set_sampling_freq(struct iio_dev *indio_dev, unsigned int freq) +{ + struct ad5791_state *st = iio_priv(indio_dev); + int ret; + + if (!st->cnv_trigger) + return -ENODEV; + + if (!freq || freq > AD5791_MAX_SAMPLING_RATE) + return -EINVAL; + + ret = iio_device_claim_direct_mode(indio_dev); + if (ret) + return ret; + + ret = __ad5791_set_sampling_freq(st, freq); + iio_device_release_direct_mode(indio_dev); + + return ret; +} + static int ad5791_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int *val, @@ -273,6 +340,9 @@ static int ad5791_read_raw(struct iio_dev *indio_dev, do_div(val64, st->vref_mv); *val = -val64; return IIO_VAL_INT; + case IIO_CHAN_INFO_SAMP_FREQ: + *val = ad5791_get_sampling_freq(st); + return IIO_VAL_INT; default: return -EINVAL; } @@ -301,6 +371,7 @@ static const struct iio_chan_spec_ext_info ad5791_ext_info[] = { .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ BIT(IIO_CHAN_INFO_OFFSET), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),\ .scan_type = { \ .sign = 'u', \ .realbits = (bits), \ @@ -310,6 +381,25 @@ static const struct iio_chan_spec_ext_info ad5791_ext_info[] = { .ext_info = ad5791_ext_info, \ } +#define AD5791_OFFLOAD_CHAN(bits, _shift) { \ + .type = IIO_VOLTAGE, \ + .output = 1, \ + .indexed = 1, \ + .address = AD5791_ADDR_DAC0, \ + .channel = 0, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),\ + .scan_type = { \ + .sign = 'u', \ + .realbits = (bits), \ + .storagebits = 32, \ + .shift = (_shift), \ + }, \ + .ext_info = ad5791_ext_info, \ +} + static const struct iio_chan_spec ad5791_channels[] = { [ID_AD5760] = AD5791_CHAN(16, 4), [ID_AD5780] = AD5791_CHAN(18, 2), @@ -317,6 +407,13 @@ static const struct iio_chan_spec ad5791_channels[] = { [ID_AD5791] = AD5791_CHAN(20, 0) }; +static const struct iio_chan_spec ad5791_offload_channels[] = { + [ID_AD5760] = AD5791_OFFLOAD_CHAN(16, 4), + [ID_AD5780] = AD5791_OFFLOAD_CHAN(18, 2), + [ID_AD5781] = AD5791_OFFLOAD_CHAN(18, 2), + [ID_AD5791] = AD5791_OFFLOAD_CHAN(20, 0) +}; + static int ad5791_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int val, @@ -331,12 +428,79 @@ static int ad5791_write_raw(struct iio_dev *indio_dev, val <<= chan->scan_type.shift; return ad5791_spi_write(st, chan->address, val); - + case IIO_CHAN_INFO_SAMP_FREQ: + return ad5791_set_sampling_freq(indio_dev, val); default: return -EINVAL; } } +static int ad5791_buffer_preenable(struct iio_dev *indio_dev) +{ + struct ad5791_state *st = iio_priv(indio_dev); + int ret; + + ret = __ad5791_dac_powerdown(indio_dev, false); + if (ret) + return ret; + + ret = pwm_enable(st->cnv_trigger); + if (ret) { + __ad5791_dac_powerdown(indio_dev, true); + return ret; + } + + spi_bus_lock(st->spi->master); + spi_engine_ex_offload_enable(st->spi, true); + + return 0; +} + +static int ad5791_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct ad5791_state *st = iio_priv(indio_dev); + + spi_engine_ex_offload_enable(st->spi, false); + pwm_disable(st->cnv_trigger); + spi_bus_unlock(st->spi->master); + + return __ad5791_dac_powerdown(indio_dev, true); +} + +static const struct iio_buffer_setup_ops ad5791_buffer_setup_ops = { + .preenable = &ad5791_buffer_preenable, + .postdisable = &ad5791_buffer_postdisable, +}; + +static void ad5791_pwm_diasble(void *data) +{ + pwm_disable(data); +} + +static int ad5791_pwm_setup(struct spi_device *spi, struct ad5791_state *st) +{ + struct clk *ref_clk; + int ret; + + ref_clk = devm_clk_get_enabled(&spi->dev, "ref_clk"); + + if (IS_ERR(ref_clk)) + return PTR_ERR(ref_clk); + + st->ref_clk_rate = clk_get_rate(ref_clk); + + st->cnv_trigger = devm_pwm_get(&spi->dev, "cnv"); + if (IS_ERR(st->cnv_trigger)) + return PTR_ERR(st->cnv_trigger); + + ret = devm_add_action_or_reset(&spi->dev, ad5791_pwm_diasble, + st->cnv_trigger); + if (ret) + return ret; + + return __ad5791_set_sampling_freq(st, AD5791_MAX_SAMPLING_RATE); +} + static const struct iio_info ad5791_info = { .read_raw = &ad5791_read_raw, .write_raw = &ad5791_write_raw, @@ -438,6 +602,38 @@ static int ad5791_probe(struct spi_device *spi) = &ad5791_channels[spi_get_device_id(spi)->driver_data]; indio_dev->num_channels = 1; indio_dev->name = spi_get_device_id(st->spi)->name; + + if (spi_engine_ex_offload_supported(spi)) { + indio_dev->channels = + &ad5791_channels[spi_get_device_id(spi)->driver_data]; + + st->spi_transfer.len = 4; + st->spi_transfer.bits_per_word = 24; + st->spi_transfer.tx_buf = (void *)-1; /* steaming tx */ + + spi_message_init_with_transfers(&st->spi_msg, &st->spi_transfer, 1); + + ret = spi_optimize_message(st->spi, &st->spi_msg); + if (ret) + goto error_disable_reg_neg; + + ret = spi_engine_ex_offload_load_msg(st->spi, &st->spi_msg); + if (ret < 0) + goto error_disable_reg_neg; + + ret = ad5791_pwm_setup(spi, st); + if (ret) + goto error_disable_reg_neg; + + ret = devm_iio_dmaengine_buffer_setup(indio_dev->dev.parent, + indio_dev, "tx", + IIO_BUFFER_DIRECTION_OUT); + if (ret) + goto error_disable_reg_neg; + + indio_dev->setup_ops = &ad5791_buffer_setup_ops; + } + ret = iio_device_register(indio_dev); if (ret) goto error_disable_reg_neg;