Skip to content

Commit

Permalink
iio: adc: ad4630: update to use pwm waveforms API
Browse files Browse the repository at this point in the history
Reserve setting of PWMs for ad4630_buffer_preenable(), and
ad4630_update_sample_fetch_trigger(). Disable them with pwm_disable() in
two places: ad4630_pwm_get() (so that the PWM outputs aren't on after
initialization) and ad4630_buffer_postdisable(). Some instances of the
driver state had the const qualifiers removed since they now actually
need to be modified.

Since the PWM rounding API outputs may vary depending on the reference
clock used, add a loop to continually try rounding the conversion
waveform until an appropriate duty_length_ns value is obtained. Change
AD4630_TQUIET_CNV_DELAY_PS to AD4630_TQUIET_CNV_DELAY_NS and the nearest
appropriate value, so that we don't have to round when calculating PWM
offsets now. Do the same rounding process for the fetch waveform's
duty_offset_ns as for the conv waveform's duty_length_ns.

Also add a bit more logic to buffer_preenable() to solve a bug where we
weren't re-entering config mode on error if the write for exiting the
mode failed.

Signed-off-by: Trevor Gamblin <[email protected]>
  • Loading branch information
threexc committed Dec 17, 2024
1 parent 6ae3643 commit 10ec3a6
Showing 1 changed file with 99 additions and 53 deletions.
152 changes: 99 additions & 53 deletions drivers/iio/adc/ad4630.c
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,10 @@
/* sequence starting with "1 0 1" to enable reg access */
#define AD4630_REG_ACCESS 0x2000
/* Sampling timing */
#define AD4630_TQUIET_CNV_DELAY_PS 9800
#define AD4630_MAX_RATE_1_LANE 1750000
#define AD4630_MAX_RATE 2000000
/* Datasheet says 9.8ns, so use the closest integer value */
#define AD4630_TQUIET_CNV_DELAY_NS 10

#define AD4630_MAX_CHANNEL_NR 3
#define AD4630_VREF_MIN (4096 * MILLI)
Expand Down Expand Up @@ -192,6 +193,8 @@ struct ad4630_state {
struct regulator_bulk_data regulators[3];
struct pwm_device *conv_trigger;
struct pwm_device *fetch_trigger;
struct pwm_waveform conv_wf;
struct pwm_waveform fetch_wf;
struct gpio_descs *pga_gpios;
struct spi_device *spi;
struct regmap *regmap;
Expand Down Expand Up @@ -258,10 +261,7 @@ static int ad4630_reg_access(struct iio_dev *indio_dev, unsigned int reg,

static void ad4630_get_sampling_freq(const struct ad4630_state *st, int *freq)
{
struct pwm_state conversion_state;

pwm_get_state(st->conv_trigger, &conversion_state);
*freq = DIV_ROUND_CLOSEST_ULL(NANO, conversion_state.period);
*freq = DIV_ROUND_CLOSEST_ULL(NANO, st->conv_wf.period_length_ns);
}

static int ad4630_get_chan_gain(struct iio_dev *indio_dev, int ch, int *val)
Expand Down Expand Up @@ -370,26 +370,41 @@ static int ad4630_read_avail(struct iio_dev *indio_dev,
}
}

static int __ad4630_set_sampling_freq(const struct ad4630_state *st, unsigned int freq)
static int __ad4630_set_sampling_freq(struct ad4630_state *st, unsigned int freq)
{
struct pwm_state conv_state = {
.duty_cycle = 10,
.enabled = true,
}, fetch_state = {
.duty_cycle = 10,
.enabled = true,
struct pwm_waveform conv_wf = {
.duty_length_ns = 10,
}, fetch_wf = {
.duty_length_ns = 10,
};
int ret;
u64 target = conv_wf.duty_length_ns;

conv_state.period = DIV_ROUND_CLOSEST(NSEC_PER_SEC, freq);
ret = pwm_apply_state(st->conv_trigger, &conv_state);
conv_wf.period_length_ns = DIV_ROUND_CLOSEST(NSEC_PER_SEC, freq);
/*
* The datasheet lists a minimum time of 9.8 ns, but no maximum. If the
* rounded PWM's value is less than 10, increase the target value by 10
* and attempt to round the waveform again, until the value is at least
* 10 ns. Use a separate variable to represent the target in case the
* rounding is severe enough to keep putting the first few results under
* the minimum 10ns condition checked by the while loop.
*/
ret = pwm_round_waveform_might_sleep(st->conv_trigger, &conv_wf);
if (ret)
return ret;

do {
conv_wf.duty_length_ns = target;
ret = pwm_round_waveform_might_sleep(st->conv_trigger, &conv_wf);
if (ret)
return ret;
target += 10;
} while (conv_wf.duty_length_ns < 10);

if (!st->fetch_trigger)
return 0;

fetch_state.period = conv_state.period;
fetch_wf.period_length_ns = conv_wf.period_length_ns;

if (st->out_data == AD4630_30_AVERAGED_DIFF) {
u32 avg;
Expand All @@ -398,23 +413,42 @@ static int __ad4630_set_sampling_freq(const struct ad4630_state *st, unsigned in
if (ret)
return ret;

fetch_state.period <<= FIELD_GET(AD4630_AVG_AVG_VAL, avg);
fetch_wf.period_length_ns <<= FIELD_GET(AD4630_AVG_AVG_VAL, avg);
}

/*
* The hardware does the capture on zone 2 (when spi trigger PWM
* is used). This means that the spi trigger signal should happen at
* tsync + tquiet_con_delay being tsync the conversion signal period
* and tquiet_con_delay 9.8ns. Hence set the PWM phase accordingly.
*
* The PWM waveform API only supports nanosecond resolution right now,
* so round this setting to the closest available value.
*/
fetch_state.phase = AD4630_TQUIET_CNV_DELAY_PS;
fetch_wf.duty_offset_ns = AD4630_TQUIET_CNV_DELAY_NS;
target = fetch_wf.duty_offset_ns;

return pwm_apply_state(st->fetch_trigger, &fetch_state);
ret = pwm_round_waveform_might_sleep(st->fetch_trigger, &fetch_wf);
if (ret)
return ret;

do {
fetch_wf.duty_offset_ns = target;
ret = pwm_round_waveform_might_sleep(st->fetch_trigger, &fetch_wf);
if (ret)
return ret;
target += 10;
} while (fetch_wf.duty_offset_ns < AD4630_TQUIET_CNV_DELAY_NS);

st->conv_wf = conv_wf;
st->fetch_wf = fetch_wf;

return 0;
}

static int ad4630_set_sampling_freq(struct iio_dev *indio_dev, unsigned int freq)
{
const struct ad4630_state *st = iio_priv(indio_dev);
struct ad4630_state *st = iio_priv(indio_dev);
int ret;

if (!freq || freq > st->max_rate)
Expand Down Expand Up @@ -590,26 +624,31 @@ static int ad4630_write_raw(struct iio_dev *indio_dev,
}
}

static int ad4630_update_sample_fetch_trigger(const struct ad4630_state *st, u32 avg)
static int ad4630_update_sample_fetch_trigger(struct ad4630_state *st, u32 avg)
{
struct pwm_state fetch_state, conv_state;
struct pwm_waveform fetch_wf = st->fetch_wf;
int ret;

if (!st->fetch_trigger)
return 0;

pwm_get_state(st->conv_trigger, &conv_state);
pwm_get_state(st->fetch_trigger, &fetch_state);
fetch_state.period = conv_state.period * 1 << avg;
fetch_state.phase = AD4630_TQUIET_CNV_DELAY_PS;
fetch_wf.period_length_ns = st->conv_wf.period_length_ns << avg;
fetch_wf.duty_offset_ns = AD4630_TQUIET_CNV_DELAY_NS;

return pwm_apply_state(st->fetch_trigger, &fetch_state);
ret = pwm_round_waveform_might_sleep(st->fetch_trigger, &fetch_wf);
if (ret)
return ret;

st->fetch_wf = fetch_wf;

return 0;
}

static int ad4630_set_avg_frame_len(struct iio_dev *dev,
const struct iio_chan_spec *chan,
unsigned int avg_len)
{
const struct ad4630_state *st = iio_priv(dev);
struct ad4630_state *st = iio_priv(dev);
int ret;

ret = iio_device_claim_direct_mode(dev);
Expand All @@ -630,7 +669,7 @@ static int ad4630_set_avg_frame_len(struct iio_dev *dev,
static int ad4630_get_avg_frame_len(struct iio_dev *dev,
const struct iio_chan_spec *chan)
{
const struct ad4630_state *st = iio_priv(dev);
struct ad4630_state *st = iio_priv(dev);
unsigned int avg_len;
int ret;

Expand All @@ -646,25 +685,6 @@ static int ad4630_get_avg_frame_len(struct iio_dev *dev,
return avg_len - 1;
}

static int ad4630_sampling_enable(const struct ad4630_state *st, bool enable)
{
struct pwm_state conv_state, fetch_state;
int ret;

pwm_get_state(st->conv_trigger, &conv_state);
conv_state.enabled = enable;

ret = pwm_apply_state(st->conv_trigger, &conv_state);
if (ret)
return ret;
if (!st->fetch_trigger)
return 0;

pwm_get_state(st->fetch_trigger, &fetch_state);
fetch_state.enabled = enable;
return pwm_apply_state(st->fetch_trigger, &fetch_state);
}

static int ad4630_spi_transfer_update(struct ad4630_state *st)
{
u8 bits_per_w;
Expand Down Expand Up @@ -708,6 +728,7 @@ static int ad4630_buffer_preenable(struct iio_dev *indio_dev)
{
struct ad4630_state *st = iio_priv(indio_dev);
int ret;
u32 dummy;

ret = pm_runtime_resume_and_get(&st->spi->dev);
if (ret < 0)
Expand All @@ -719,8 +740,10 @@ static int ad4630_buffer_preenable(struct iio_dev *indio_dev)
goto out_error;

ret = regmap_write(st->regmap, AD4630_REG_EXIT_CFG_MODE, BIT(0));
if (ret)
if (ret) {
ret = regmap_read(st->regmap, AD4630_REG_ACCESS, &dummy);
goto out_error;
}

ret = spi_optimize_message(st->spi, &st->offload_msg);
if (ret < 0)
Expand All @@ -729,12 +752,25 @@ static int ad4630_buffer_preenable(struct iio_dev *indio_dev)
spi_bus_lock(st->spi->master);
spi_engine_ex_offload_load_msg(st->spi, &st->offload_msg);
spi_engine_ex_offload_enable(st->spi, true);
ad4630_sampling_enable(st, true);

ret = pwm_set_waveform_might_sleep(st->conv_trigger, &st->conv_wf, false);
if (ret)
goto out_error;

if (!st->fetch_trigger)
return 0;

ret = pwm_set_waveform_might_sleep(st->fetch_trigger, &st->fetch_wf, false);
if (ret) {
pwm_disable(st->conv_trigger);
goto out_error;
}

return 0;
out_error:
pm_runtime_mark_last_busy(&st->spi->dev);
pm_runtime_put_autosuspend(&st->spi->dev);
spi_unoptimize_message(&st->offload_msg);
return ret;
}

Expand All @@ -744,16 +780,15 @@ static int ad4630_buffer_postdisable(struct iio_dev *indio_dev)
u32 dummy;
int ret;

ret = ad4630_sampling_enable(st, false);
if (ret)
goto out_error;
pwm_disable(st->conv_trigger);
pwm_disable(st->fetch_trigger);

spi_engine_ex_offload_enable(st->spi, false);
spi_bus_unlock(st->spi->master);

spi_unoptimize_message(&st->offload_msg);
ret = regmap_read(st->regmap, AD4630_REG_ACCESS, &dummy);
out_error:

pm_runtime_mark_last_busy(&st->spi->dev);
pm_runtime_put_autosuspend(&st->spi->dev);
return ret;
Expand Down Expand Up @@ -1197,6 +1232,12 @@ static int ad4630_pwm_get(struct ad4630_state *st)
return dev_err_probe(dev, PTR_ERR(st->conv_trigger),
"Failed to get cnv pwm\n");

/*
* Preemptively disable the PWM, since we only want to enable it with
* the buffer
*/
pwm_disable(st->conv_trigger);

/*
* the trigger is optional... We can have the device BUSY pin
* acting as trigger.
Expand All @@ -1208,6 +1249,11 @@ static int ad4630_pwm_get(struct ad4630_state *st)
if (IS_ERR(st->fetch_trigger))
return dev_err_probe(dev, PTR_ERR(st->fetch_trigger),
"Failed to get spi engine pwm\n");
/*
* Preemptively disable the PWM, since we only want to enable it with
* the buffer
*/
pwm_disable(st->fetch_trigger);

out_sampling_freq:
return __ad4630_set_sampling_freq(st, st->max_rate);
Expand Down

0 comments on commit 10ec3a6

Please sign in to comment.