Skip to content

Commit

Permalink
iio: dac: ad5791: Add offload support
Browse files Browse the repository at this point in the history
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 <ahaslam@baylibre.com>
  • Loading branch information
ahaslam2 committed Oct 15, 2024
1 parent 3f39ac3 commit 3976d47
Showing 1 changed file with 209 additions and 13 deletions.
222 changes: 209 additions & 13 deletions drivers/iio/dac/ad5791.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@
#include <linux/regulator/consumer.h>
#include <linux/module.h>
#include <linux/bitops.h>
#include <linux/spi/spi.h>
#include <linux/spi/spi-engine-ex.h>
#include <linux/pwm.h>
#include <linux/clk.h>
#include <linux/iio/buffer_impl.h>
#include <linux/iio/buffer.h>
#include <linux/iio/buffer-dma.h>
#include <linux/iio/buffer-dmaengine.h>

#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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)
{
Expand All @@ -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;
}
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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), \
Expand All @@ -310,13 +381,39 @@ 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),
[ID_AD5781] = AD5791_CHAN(18, 2),
[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,
Expand All @@ -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,
Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit 3976d47

Please sign in to comment.