Skip to content

iio: dac: add ad5706r#3096

Open
actorreno wants to merge 4 commits intomainfrom
dev_ad5706r
Open

iio: dac: add ad5706r#3096
actorreno wants to merge 4 commits intomainfrom
dev_ad5706r

Conversation

@actorreno
Copy link
Contributor

PR Description

  • Adding driver for AD5706R, a quad 16-bit current output digital-to-analog converter with integrated precision reference.
  • Datasheet of AD5706R.
  • Features Implemented:
    • 4 independent current output channels (16-bit resolution)
    • Programmable output ranges: 50mA, 150mA, 200mA, 300mA
    • Hardware and software LDAC triggering via PWM
    • Toggle mode for dynamic current switching
    • Dither mode for improved linearity
    • Channel monitoring (voltage, current, temperature, PVDD)
    • Configurable output states (normal, shutdown, tristate)
    • Dynamic SPI interface up to 100MHz. Separate read and write speed.
    • Debugfs streaming for register write of 32 bytes

PR Type

  • Bug fix (a change that fixes an issue)
  • New feature (a change that adds new functionality)
  • Breaking change (a change that affects other repos or cause CIs to fail)

PR Checklist

  • I have conducted a self-review of my own code changes
  • I have compiled my changes, including the documentation
  • I have tested the changes on the relevant hardware
  • I have updated the documentation outside this repo accordingly
  • I have provided links for the relevant upstream lore

Add device tree bindings for the Analog Devices AD5706R 16-bit
4-channel current output DAC.

The AD5706R features:
- 4 independent current output channels
- 16-bit resolution
- SPI interface (up to 50MHz write, 25MHz read)
- Multiple LDAC trigger modes (hardware/software)
- Toggle and dither function generators
- Programmable output ranges (50mA to 300mA)

Signed-off-by: Alexis Czezar Torreno <alexisczezar.torreno@analog.com>
@actorreno actorreno force-pushed the dev_ad5706r branch 3 times, most recently from d8113bb to 620846b Compare January 29, 2026 03:31
@actorreno actorreno marked this pull request as ready for review January 29, 2026 07:15
Copy link
Collaborator

@stefpopa stefpopa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few small issues and some things that need to be clarified. Overall looks good!

ret = clk_set_rate(st->reference_clk, rate * 2);

/* Re-enable the clock after setting the rate */
clk_prepare_enable(st->reference_clk);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we check the return value of the clk_prepare_enable()? Otherwise, in case it fails, the clock remains disabled, but we move forward.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes i've checked a few other codes, will need to edit this to have error check.

break;
case RANGE_SEL_300:
default:
*val = 300 * MEGA / AD5706R_DAC_MAX_CODE;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the other ranges, HZ_PER_MHZ is used, but for RANGE_SEL_300, MEGA is used, should we use the same constant everywhere? Otherwise, add a comment here explaining why a different constant is used.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will make this consistent (I'm also wondering why I did this)

mutex_init(&st->lock);
st->spi = spi;

_ad5706r_setup(st);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we acquire a lock during the ad5706r_setup? It seems that inside the function we are accessing the HW.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will add a lock in the function

break;
case RANGE_SEL_300:
default:
*val = 300 * MEGA / AD5706R_DAC_MAX_CODE;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we using a different range for RANGE_SEL_300? Either use MAGA everywhere or please add a comment here explaining why a different constant is needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

already acknowledged similar comment above, will change for consistency

ret = clk_set_rate(st->reference_clk, rate * 2);

/* Re-enable the clock after setting the rate */
clk_prepare_enable(st->reference_clk);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we check the return value of the clk_prepare_enable()? Otherwise, if enabling the clock fails, we continue while the clock remains disabled.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto, similar comment above

if (val < 1)
val = 1;
if (val > 10000000)
val = 10000000;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could define the minimum and maximum frequencies as constants at the top of the file with other defines and use clamp() to enforce the valid range for the PWM generator.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will figure out how to use this but yes noted on this.

ldacb_pwm_state.enabled = true;
ret = pwm_apply_might_sleep(st->ldacb_pwm, &ldacb_pwm_state);
if (ret)
return ret;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we use dev_err_probe() here? Check everywhere in _ad5706r_setup(). Sometimes ret is returned directly, sometimes dev_err_probe() is returned. For consistency sake, we can return dev_err_probe() everywhere.

Copy link
Contributor Author

@actorreno actorreno Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

am not sure if its a style but

when the function is something like xxxx = devm_yyyy_get, this is where dev_err_probe is used
others just return ret

or is it any function in the probe must receieve the dev_err treatment

edit: i added the dev_err to everything inside the function for consistency

st->debug_spi_speed_hz_read = current_rate;

/* Wait for clock to stabilize */
usleep_range(3000, 3100);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is concerning to me. It adds a 3ms delay with every SPI transaction. Is this a real HW requirement? Isn't it enough to add a short delay after initial setup? I assume 100-200us should be plenty.
Or is it necessary to have a _ad5706r_set_clk_rate() call with each read/write?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is related to the not so common feature the requestor wants.

They want to have runtime configurable spi_speeds, and spi_read and spi_write should be separate speeds. The use case something like spi_write at 50MHz and read is 25Mhz.

After testing on higher frequencies, the data fetch fails if spi read/write are fast, not the same speed, and alternating. when read/write alternates the spi fails on high frequency setting.

This 3ms was manually tested as the minimum to stabilize the clock when alternating read/write.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If reads are infrequent, the 3ms delay on speed changes is acceptable, but if we have to perform frequent alternating read/writes, it's a heavy penalty.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Were you able to identify the root cause of the clock stabilization? Is it something related to the SPI Engine?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anyway, I guess it's not so bad, if we do multiple consecutive writes and an occasional read.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my best guess here is it's caused by changing the actual clock rate of the spi_engine

i mean normally the way it goes is like
clk_gen -> spi_engine (regmap -> interconnect -> execution module) -> spi transaction

changing spi_speeds can be done in the execution module but this is only an integer divider (a 50Mhz input makes 25MHz, 16.66, 12.5, 10, etc)

requestor wanted more fine changes so I controlled the speed on the clk_gen itself

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aha! So, it must be related to the PLL lock time. Do you know what specific frequencies they actually need?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To quote the requestor's message to us:

"Reason is different customers have different clock requirements. We have seen numbers like 48MHz, 16.384MHz, 33MHz in optical modules where this part goes."

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, then the delay makes sense, as dynamic clock reconfiguration is necessary for these requirements. Sorry for asking so many questions, but I am a bit worried about making this acceptable upstream. In the best case scenario, we will need to provide an explanation.

@actorreno actorreno force-pushed the dev_ad5706r branch 2 times, most recently from 98610ab to 6704915 Compare February 12, 2026 03:34
@actorreno
Copy link
Contributor Author

v2:

  • Error check with clk_prepare_enable
  • Consistency to use HZ_PER_MHZ
  • Added lock on setup function
  • Kept %lx as there was compiler warning using %x. Apparently GENMASK returns unsigned long
  • Added define for min/max of sampling freq, added clamp() in code to simplify if/else
  • Used dev_err in all error check inside the setup function
  • minor style issue fixed

Copy link
Collaborator

@stefpopa stefpopa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR looks good overall, there is only one outstanding topic regarding the 3ms delay.
It seems that this delay is required for PLL reconfiguration which is needed for special speeds such as 48/16/33MHz.
However, PLL reconfiguration is not required for standard speeds such as 50/25MHz. Therefore, customers using these speeds should not experience any performance penalty.
One suggestion is to use a hybrid approach by trying to achieve a target frequency via the SPI Engine integer division (inside the _ad5706r_set_clk_rate() function). This eliminates the 3ms delay.
If this is not possible, then we must go on the "slow path" which needs precise frequency not achievable via integer division. This is the current implementation with the 3ms delay.
We also need to document this properly.
Additional suggestion is to add a device tree property to let customers choose the path. It can be something like this:

properties:
  adi,spi-speed-mode:
    description: |
      SPI clock speed configuration mode.
      
      "auto" (default): Automatically use integer division for standard 
      frequencies (faster), fall back to PLL reconfiguration for exact
      frequencies not achievable via division.
      
      "precise": Always use PLL reconfiguration for exact requested 
      frequency (adds 3ms delay on speed changes).
      
      "fast-only": Restrict to frequencies achievable via integer division
      from 100MHz base clock. Reject attempts to set other frequencies.
    enum: [auto, precise, fast-only]
    default: auto

@actorreno actorreno force-pushed the dev_ad5706r branch 2 times, most recently from 398a3a6 to 36982ac Compare February 18, 2026 07:08
@actorreno
Copy link
Contributor Author

v3:

  • added automated clock mode switching
  • each time the user changes an SPI speed it goes to a clock mode of UNKNOWN
  • the driver will look at the desired write/read speeds. if in relation to the devicetree max-spi-frequency, the write and read values can be produced from the SPI engine clock divider, it goes to that mode. essentially simplifying frequency differences between read and write (sample: max_freq = 100MHz, write 50Mhz, read 25Mhz)
  • if read and write values are not simple, fall back and use the CLKGEN for spi clock changes, this incurs a manually tested 3ms delay each time the clock changes speed to accommodate clock stabilization especially in higher frequencies.

The SPI Engine mode was added to avoid the 3ms penalty of purely using the clkgen for SPI speed changes.

Regarding the Additional suggestion to add a device tree property, I currently opted not and did the automatic version.
aside from a more complex code, it feels more cumbersome to shift modes from/to 'fast-only' to/from 'precise' and both have their own cons. The automatic mode is still the best of both.

@actorreno actorreno requested a review from stefpopa February 18, 2026 07:49
Copy link
Collaborator

@stefpopa stefpopa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One small refactoring to improve readability, rest looks good.

Add driver for the Analog Devices AD5706R 16-bit 4-channel
current output DAC with SPI interface.

The device features:
- 4 independent current output channels (OUT0-OUT3)
- 16-bit resolution
- Programmable output ranges: 50mA, 150mA, 200mA, 300mA
- Hardware and software LDAC trigger modes
- Toggle and dither function generators
- Output shutdown modes (tri-state or ground)
- Asynchronous/synchronous LDAC operation
- Dynamic SPI clock frequency adjustment

The driver provides IIO standard interfaces for:
- DAC output control via raw values
- Output range selection (scale)
- LDAC trigger configuration
- Output state control (normal/shutdown)
- Function generator modes (toggle/dither)

Signed-off-by: Alexis Czezar Torreno <alexisczezar.torreno@analog.com>
Adding maintainer entry for AD5706R.

Signed-off-by: Alexis Czezar Torreno <alexisczezar.torreno@analog.com>
Editing kconfig.adi to imply AD5706R

Signed-off-by: Alexis Czezar Torreno <alexisczezar.torreno@analog.com>
Copy link
Collaborator

@stefpopa stefpopa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Good job!

@actorreno
Copy link
Contributor Author

Will Proceed to upstream, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants