Skip to content

Commit

Permalink
iio: adc: ad7944: add spi offload support
Browse files Browse the repository at this point in the history
This adds support for SPI Engine offload to the ad7944-ex driver. This
allows reaching the max sample rate of 2/2.5 MSPS when the chip is wired
in 3-wire mode.

Signed-off-by: David Lechner <[email protected]>
  • Loading branch information
dlech committed Mar 12, 2024
1 parent b40cec2 commit 3be6b87
Showing 1 changed file with 228 additions and 5 deletions.
233 changes: 228 additions & 5 deletions drivers/iio/adc/ad7944.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,36 @@
#include <linux/gpio/consumer.h>
#include <linux/module.h>
#include <linux/property.h>
#include <linux/pwm.h>
#include <linux/regulator/consumer.h>
#include <linux/spi/spi-engine-ex.h>
#include <linux/spi/spi.h>
#include <linux/string_helpers.h>

#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
#include <linux/iio/buffer-dmaengine.h>
#include <linux/iio/trigger_consumer.h>
#include <linux/iio/triggered_buffer.h>

/*
* Older SPI Engine HDL versions have a bug where the offload is level triggered
* instead of edge triggered, so we use a small duty cycle to allow sampling
* at lower rates without spurious triggers.
*/
#define AD7944_PWM_TRIGGER_DUTY_CYCLE_NS 20
#define AD7944_DEFAULT_SAMPLE_FREQ_HZ 10000 /* arbitrary */
#define AD7944_INTERNAL_REF_MV 4096

struct ad7944_timing_spec {
/* Normal mode max conversion time (t_{CONV}). */
unsigned int conv_ns;
/* TURBO mode max conversion time (t_{CONV}). */
unsigned int turbo_conv_ns;
/* Normal mode data read during conversion time (t_{DATA}). */
unsigned int data_ns;
/* TURBO mode data read during conversion time (t_{DATA}). */
unsigned int turbo_data_ns;
};

enum ad7944_spi_mode {
Expand All @@ -53,6 +67,8 @@ struct ad7944_adc {
enum ad7944_spi_mode spi_mode;
struct spi_transfer xfers[3];
struct spi_message msg;
struct spi_transfer turbo_xfers[3];
struct spi_message turbo_msg;
/* Chip-specific timing specifications. */
const struct ad7944_timing_spec *timing_spec;
/* GPIO connected to CNV pin. */
Expand All @@ -63,6 +79,8 @@ struct ad7944_adc {
bool always_turbo;
/* Reference voltage (millivolts). */
unsigned int ref_mv;
/* SPI offload trigger. */
struct pwm_device *pwm;

/*
* DMA (thus cache coherency maintenance) requires the
Expand All @@ -77,17 +95,25 @@ struct ad7944_adc {
} sample __aligned(IIO_DMA_MINALIGN);
};

/* minimum CNV high time */
#define T_CNVH_NS 10
/* CS low to data valid time */
#define T_EN_NS 5
/* quite time before CNV rising edge */
#define T_QUIET_NS 20

static const struct ad7944_timing_spec ad7944_timing_spec = {
.conv_ns = 420,
.turbo_conv_ns = 320,
.data_ns = 290,
.turbo_data_ns = 190,
};

static const struct ad7944_timing_spec ad7986_timing_spec = {
.conv_ns = 500,
.turbo_conv_ns = 400,
.data_ns = 300,
.turbo_data_ns = 200,
};

struct ad7944_chip_info {
Expand Down Expand Up @@ -180,6 +206,57 @@ static int ad7944_3wire_cs_mode_init_msg(struct device *dev, struct ad7944_adc *
return devm_add_action_or_reset(dev, ad7944_unoptimize_msg, &adc->msg);
}

static int ad7944_3wire_cs_mode_init_turbo_msg(struct device *dev,
struct ad7944_adc *adc,
const struct iio_chan_spec *chan)
{
struct spi_transfer *xfers = adc->turbo_xfers;
int ret;

/*
* This sequence of xfers performs a read during conversion in 3-wire CS
* mode with timings for when TURBO mode is enabled. Using this msg will
* only work with SPI offloads since the timing needs to be precise to
* actually be able to read during the conversion time.
*/

/* change CNV to high to trigger conversion */
xfers[0].cs_off = 1;
xfers[0].bits_per_word = chan->scan_type.realbits;
xfers[0].delay.value = T_CNVH_NS;
xfers[0].delay.unit = SPI_DELAY_UNIT_NSECS;

/* read sample data from previous conversion */
xfers[1].rx_buf = &adc->sample.raw;
xfers[1].len = BITS_TO_BYTES(chan->scan_type.storagebits);
xfers[1].bits_per_word = chan->scan_type.realbits;
/*
* CNV has to be high at end of conversion to avoid triggering the busy
* signal on the SDO line.
*/
xfers[1].cs_change = 1;
/* t[CONV] - t[CNVH] - t[EN] - t[DATA] */
xfers[1].cs_change_delay.value = adc->timing_spec->turbo_conv_ns -
T_CNVH_NS - T_EN_NS -
adc->timing_spec->turbo_data_ns;
xfers[1].cs_change_delay.unit = SPI_DELAY_UNIT_NSECS;

/*
* Since this is the last xfer, this changes CNV to low and keeps it
* there until the next xfer.
*/
xfers[2].cs_change = 1;
xfers[2].bits_per_word = chan->scan_type.realbits;

spi_message_init_with_transfers(&adc->turbo_msg, xfers, 3);

ret = spi_optimize_message(adc->spi, &adc->turbo_msg);
if (ret)
return ret;

return devm_add_action_or_reset(dev, ad7944_unoptimize_msg, &adc->turbo_msg);
}

static int ad7944_4wire_mode_init_msg(struct device *dev, struct ad7944_adc *adc,
const struct iio_chan_spec *chan)
{
Expand Down Expand Up @@ -327,8 +404,120 @@ static int ad7944_read_raw(struct iio_dev *indio_dev,
}
}

static ssize_t ad7944_sampling_frequency_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
struct ad7944_adc *adc = iio_priv(indio_dev);
u64 period_ns;

if (!adc->pwm)
return -ENODEV;

period_ns = pwm_get_period(adc->pwm);

return sysfs_emit(buf, "%llu\n", div64_u64(NSEC_PER_SEC, period_ns));
}

static ssize_t ad7944_sampling_frequency_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len)
{
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
struct ad7944_adc *adc = iio_priv(indio_dev);
u64 period_ns;
u32 val;
int ret;

if (!adc->pwm)
return -ENODEV;

ret = kstrtouint(buf, 0, &val);
if (ret)
return ret;

if (val == 0)
return -EINVAL;

period_ns = div_u64(NSEC_PER_SEC, val);

ret = pwm_config(adc->pwm, AD7944_PWM_TRIGGER_DUTY_CYCLE_NS, period_ns);
if (ret)
return ret;

return len;
}

static IIO_DEV_ATTR_SAMP_FREQ(0644, ad7944_sampling_frequency_show,
ad7944_sampling_frequency_store);

static struct attribute *ad7944_attrs[] = {
&iio_dev_attr_sampling_frequency.dev_attr.attr,
NULL
};

static const struct attribute_group ad7944_group = {
.attrs = ad7944_attrs,
/*
* REVISIT: could make use of is_visible to conditionally hide
* sampling_frequency attr when PWM trigger is not present.
*/
};

static const struct iio_info ad7944_iio_info = {
.read_raw = &ad7944_read_raw,
.attrs = &ad7944_group,
};

static int ad7944_offload_ex_buffer_preenable(struct iio_dev *indio_dev)
{
struct ad7944_adc *adc = iio_priv(indio_dev);

switch (adc->spi_mode) {
case AD7944_SPI_MODE_DEFAULT:
return spi_engine_ex_offload_load_msg(adc->spi, &adc->msg);
case AD7944_SPI_MODE_SINGLE:
/* REVISIT: could do non-turbo for lower sample rates */
gpiod_set_value_cansleep(adc->turbo, 1);
return spi_engine_ex_offload_load_msg(adc->spi, &adc->turbo_msg);
default:
return -EOPNOTSUPP;
}
}

static int ad7944_offload_ex_buffer_postenable(struct iio_dev *indio_dev)
{
struct ad7944_adc *adc = iio_priv(indio_dev);

spi_engine_ex_offload_enable(adc->spi, true);

return 0;
}

static int ad7944_offload_ex_buffer_predisable(struct iio_dev *indio_dev)
{
struct ad7944_adc *adc = iio_priv(indio_dev);

spi_engine_ex_offload_enable(adc->spi, false);

return 0;
}

static int ad7944_offload_ex_buffer_postdisable(struct iio_dev *indio_dev)
{
struct ad7944_adc *adc = iio_priv(indio_dev);

gpiod_set_value_cansleep(adc->turbo, 0);

return 0;
}

static const struct iio_buffer_setup_ops ad7944_offload_ex_buffer_setup_ops = {
.preenable = &ad7944_offload_ex_buffer_preenable,
.postenable = &ad7944_offload_ex_buffer_postenable,
.predisable = &ad7944_offload_ex_buffer_predisable,
.postdisable = &ad7944_offload_ex_buffer_postdisable,
};

static irqreturn_t ad7944_trigger_handler(int irq, void *p)
Expand Down Expand Up @@ -526,6 +715,10 @@ static int ad7944_probe(struct spi_device *spi)
if (ret)
return ret;

ret = ad7944_3wire_cs_mode_init_turbo_msg(dev, adc, &chip_info->channels[0]);
if (ret)
return ret;

break;
case AD7944_SPI_MODE_CHAIN:
return dev_err_probe(dev, -EINVAL, "chain mode is not implemented\n");
Expand All @@ -537,11 +730,41 @@ static int ad7944_probe(struct spi_device *spi)
indio_dev->channels = chip_info->channels;
indio_dev->num_channels = ARRAY_SIZE(chip_info->channels);

ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
iio_pollfunc_store_time,
ad7944_trigger_handler, NULL);
if (ret)
return ret;
if (spi_engine_ex_offload_supported(spi)) {
struct pwm_state state = {
.period = NSEC_PER_SEC / AD7944_DEFAULT_SAMPLE_FREQ_HZ,
.duty_cycle = AD7944_PWM_TRIGGER_DUTY_CYCLE_NS,
.enabled = true,
.time_unit = PWM_UNIT_NSEC,
};

adc->pwm = devm_pwm_get(dev, NULL);
if (IS_ERR(adc->pwm))
return dev_err_probe(dev, PTR_ERR(adc->pwm),
"failed to get PWM\n");

ret = pwm_apply_state(adc->pwm, &state);
if (ret)
return dev_err_probe(dev, ret,
"failed to apply PWM state\n");

ret = devm_iio_dmaengine_buffer_setup(dev, indio_dev, "rx",
IIO_BUFFER_DIRECTION_IN);
if (ret)
return ret;

indio_dev->setup_ops = &ad7944_offload_ex_buffer_setup_ops;

/* can't have soft timestamp with SPI offload */
indio_dev->num_channels--;
} else {
ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
iio_pollfunc_store_time,
ad7944_trigger_handler,
NULL);
if (ret)
return ret;
}

return devm_iio_device_register(dev, indio_dev);
}
Expand Down

0 comments on commit 3be6b87

Please sign in to comment.