Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
$id: http://devicetree.org/schemas/iio/adc/lltc,ltc2497.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#

title: Linear Technology / Analog Devices LTC2497 and LTC2309 ADC
title: Linear Technology / Analog Devices LTC2497, LTC2499 and LTC2309 ADC

maintainers:
- Michael Hennerich <michael.hennerich@analog.com>
Expand All @@ -22,6 +22,10 @@ description: |
16bit ADC supporting up to 16 single ended or 8 differential inputs.
I2C interface.

The LTC2499 additionally provides an internal temperature sensor. Its
scale and offset are derived from the reference voltage, so an accurate
vref-supply is required for the temperature channel to report correctly.

https://www.analog.com/media/en/technical-documentation/data-sheets/2497fb.pdf
https://www.analog.com/media/en/technical-documentation/data-sheets/2499fe.pdf

Expand Down
1 change: 1 addition & 0 deletions arch/arm/boot/dts/overlays/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,7 @@ dtbo-$(CONFIG_ARCH_BCM2835) += \
rpi-hmc425a.dtbo \
rpi-lm75.dtbo \
rpi-ltc2497.dtbo \
rpi-ltc2499.dtbo \
rpi-ltc2664.dtbo \
rpi-ltc2672.dtbo \
rpi-ltc2688.dtbo \
Expand Down
46 changes: 46 additions & 0 deletions arch/arm/boot/dts/overlays/rpi-ltc2499-overlay.dts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// SPDX-License-Identifier: GPL-2.0
/*
* rpi-ltc2499-overlay.dts - Raspberry Pi overlay for the LTC2499 ADC
*
* Instantiates an LTC2499 on the Raspberry Pi I2C1 bus (40-pin header) and
* wires its ADC reference to the board's existing 3.3 V fixed regulator via
*
* vref-supply = <&vdd_3v3_reg>;
*
* so that regulator_get_voltage() returns 3300000 and the LTC2499 temperature
* channel (in_temp_scale / in_temp_offset) reports correctly without relying on
* any driver-side fallback.
*
* Note: this overlay targets a Raspberry Pi wired with a 3.3 V VREF. Other
* boards must supply their own Device Tree node describing the actual VREF
* regulator (2.5 V, 3.3 V, 4.096 V, etc.); the 3.3 V value here is not a
* universal hardware assumption.
*/
/dts-v1/;
/plugin/;

/ {
compatible = "brcm,bcm2835";

fragment@0 {
target = <&i2c1>;
__overlay__ {
#address-cells = <1>;
#size-cells = <0>;
status = "okay";

ltc2499: ltc2499@76 {
compatible = "lltc,ltc2499";
reg = <0x76>;
/* Board's own 3.3 V rail - see arch DT __symbols__ */
vref-supply = <&vdd_3v3_reg>;
#io-channel-cells = <1>;
status = "okay";
};
};
};

__overrides__ {
addr = <&ltc2499>,"reg:0";
};
};
212 changes: 205 additions & 7 deletions drivers/iio/adc/ltc2497-core.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,36 +7,72 @@
*/

#include <linux/delay.h>
#include <linux/device.h>
#include <linux/iio/iio.h>
#include <linux/iio/driver.h>
#include <linux/math64.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/regulator/consumer.h>
#include <linux/slab.h>

#include "ltc2497.h"

#define LTC2497_SGL BIT(4)
#define LTC2497_DIFF 0
#define LTC2497_SIGN BIT(3)

static int ltc2497core_wait_conv(struct ltc2497core_driverdata *ddata)
/*
* Output-rate modes, indexed by ltc2497core_driverdata.sped_2x
* (0 = 1x, the power-on default; 1 = 2x, LTC2499 only). The advertised
* sampling_frequency and the conversion-time budget are two views of the same
* mode, so they are kept in lock-step here and can never drift apart. Only the
* two simultaneous 50/60Hz rejection rates are reachable today; adding FA/FB
* rejection selection later turns this into a [rejection][speed] lookup without
* changing any caller.
*/
static const int ltc2497core_samp_freq_avail[] = {
6, 800000, /* 1x: ~6.8 Hz (1 / t_CONV_1 typ 146.9ms) */
13, 600000, /* 2x: ~13.6 Hz (1 / t_CONV_2 typ 73.6ms) */
};

static const unsigned int ltc2497core_conv_time_ms_tbl[] = {
LTC2497_CONV_TIME_1X_MS, /* 1x */
LTC2499_CONV_TIME_2X_MS, /* 2x */
};

static unsigned int ltc2497core_conv_time_ms(struct ltc2497core_driverdata *ddata,
u8 address)
{
/*
* SPD is ignored by the part during a temperature measurement: it
* always converts at 1x, so budget the 1x time regardless of the
* selected voltage-channel mode.
*/
if (address == LTC2497_TEMP_ADDR)
return ltc2497core_conv_time_ms_tbl[0];

return ltc2497core_conv_time_ms_tbl[ddata->sped_2x];
}

static int ltc2497core_wait_conv(struct ltc2497core_driverdata *ddata,
unsigned int conv_time_ms)
{
s64 time_elapsed;

time_elapsed = ktime_ms_delta(ktime_get(), ddata->time_prev);

if (time_elapsed < LTC2497_CONVERSION_TIME_MS) {
if (time_elapsed < conv_time_ms) {
/* delay if conversion time not passed
* since last read or write
*/
if (msleep_interruptible(
LTC2497_CONVERSION_TIME_MS - time_elapsed))
if (msleep_interruptible(conv_time_ms - time_elapsed))
return -ERESTARTSYS;

return 0;
}

if (time_elapsed - LTC2497_CONVERSION_TIME_MS <= 0) {
if (time_elapsed - conv_time_ms <= 0) {
/* We're in automatic mode -
* so the last reading is still not outdated
*/
Expand All @@ -48,9 +84,18 @@ static int ltc2497core_wait_conv(struct ltc2497core_driverdata *ddata)

static int ltc2497core_read(struct ltc2497core_driverdata *ddata, u8 address, int *val)
{
unsigned int conv_time_ms = ltc2497core_conv_time_ms(ddata, address);
int ret;

ret = ltc2497core_wait_conv(ddata);
/*
* Wait for the conversion currently in flight, whose duration was fixed
* by the mode active when it was started (ddata->conv_time_prev). This
* can be longer than the freshly selected mode's time - e.g. a 1x
* conversion is still running when the first 2x read arrives after a
* sampling_frequency change - and reprogramming the device before it
* finishes would be NACKed (-EIO).
*/
ret = ltc2497core_wait_conv(ddata, ddata->conv_time_prev);
if (ret < 0)
return ret;

Expand All @@ -60,7 +105,9 @@ static int ltc2497core_read(struct ltc2497core_driverdata *ddata, u8 address, in
return ret;
ddata->addr_prev = address;

if (msleep_interruptible(LTC2497_CONVERSION_TIME_MS))
/* The reprogram above starts a conversion in the new mode. */
ddata->conv_time_prev = conv_time_ms;
if (msleep_interruptible(conv_time_ms))
return -ERESTARTSYS;
}

Expand All @@ -69,6 +116,8 @@ static int ltc2497core_read(struct ltc2497core_driverdata *ddata, u8 address, in
return ret;

ddata->time_prev = ktime_get();
/* The read above auto-starts the next conversion in the current mode. */
ddata->conv_time_prev = conv_time_ms;

return ret;
}
Expand All @@ -95,11 +144,115 @@ static int ltc2497core_read_raw(struct iio_dev *indio_dev,
if (ret < 0)
return ret;

if (chan->type == IIO_TEMP) {
/*
* raw is normalised to 2^(resolution + 1), i.e.
* raw = 2 * DATAOUT24, so the PTAT scale (datasheet
* Vref / 1570 per kelvin) doubles its denominator and,
* in m°C, becomes Vref_uV / 3140000.
*/
*val = ret;
*val2 = 3140000;
return IIO_VAL_FRACTIONAL;
}

*val = ret / 1000;
*val2 = ddata->chip_info->resolution + 1;

return IIO_VAL_FRACTIONAL_LOG2;

case IIO_CHAN_INFO_OFFSET:
if (chan->type == IIO_TEMP) {
ret = regulator_get_voltage(ddata->ref);
if (ret < 0)
return ret;
/*
* 0 °C == 273.15 K must map to raw + offset such that
* (raw + offset) * scale == 0 m°C, i.e.
* offset = -273150 / scale
* = -273150 * 3140000 / Vref_uV
* Computed in 64-bit to avoid overflow.
*/
*val = (int)div_s64(-273150LL * 3140000LL, ret);
return IIO_VAL_INT;
}
return -EINVAL;

case IIO_CHAN_INFO_SAMP_FREQ:
/*
* Only advertised on the voltage channels of parts with a speed
* mode; the sampling frequency is a property of the selected 1x/2x
* mode, not of an individual conversion.
*/
mutex_lock(&ddata->lock);
*val = ltc2497core_samp_freq_avail[ddata->sped_2x * 2];
*val2 = ltc2497core_samp_freq_avail[ddata->sped_2x * 2 + 1];
mutex_unlock(&ddata->lock);

return IIO_VAL_INT_PLUS_MICRO;

default:
return -EINVAL;
}
}

static int ltc2497core_read_avail(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
const int **vals, int *type, int *length,
long mask)
{
switch (mask) {
case IIO_CHAN_INFO_SAMP_FREQ:
*vals = ltc2497core_samp_freq_avail;
*type = IIO_VAL_INT_PLUS_MICRO;
*length = ARRAY_SIZE(ltc2497core_samp_freq_avail);
return IIO_AVAIL_LIST;

default:
return -EINVAL;
}
}

static int ltc2497core_write_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int val, int val2, long mask)
{
struct ltc2497core_driverdata *ddata = iio_priv(indio_dev);
bool sped_2x;
int i;

switch (mask) {
case IIO_CHAN_INFO_SAMP_FREQ:
/* Match the (val, val2) pair against the advertised rates. */
for (i = 0; i < ARRAY_SIZE(ltc2497core_samp_freq_avail); i += 2) {
if (val == ltc2497core_samp_freq_avail[i] &&
val2 == ltc2497core_samp_freq_avail[i + 1])
break;
}
if (i >= ARRAY_SIZE(ltc2497core_samp_freq_avail))
return -EINVAL;

sped_2x = i / 2;

mutex_lock(&ddata->lock);
ddata->sped_2x = sped_2x;
/*
* The new speed only takes effect once the second command byte
* is reprogrammed, so force the next read to reprogram rather
* than reuse the value already latched for this address.
* LTC2497_CONFIG_DEFAULT is not a valid channel/temperature
* address, so it is a safe re-arm sentinel (as used at probe).
*
* A conversion started under the old speed may still be in
* flight; its own duration (conv_time_prev), not the new mode's,
* still gates the next reprogram, so the timing state is left
* untouched here.
*/
ddata->addr_prev = LTC2497_CONFIG_DEFAULT;
mutex_unlock(&ddata->lock);

return 0;

default:
return -EINVAL;
}
Expand All @@ -126,6 +279,15 @@ static int ltc2497core_read_raw(struct iio_dev *indio_dev,
.differential = 1, \
}

#define LTC2497_TEMP_CHANNEL { \
.type = IIO_TEMP, \
.address = LTC2497_TEMP_ADDR, \
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
BIT(IIO_CHAN_INFO_SCALE) | \
BIT(IIO_CHAN_INFO_OFFSET), \
.datasheet_name = "TEMP", \
}

static const struct iio_chan_spec ltc2497core_channel[] = {
LTC2497_CHAN(0, LTC2497_SGL, "CH0"),
LTC2497_CHAN(1, LTC2497_SGL, "CH1"),
Expand Down Expand Up @@ -159,10 +321,13 @@ static const struct iio_chan_spec ltc2497core_channel[] = {
LTC2497_CHAN_DIFF(5, LTC2497_DIFF | LTC2497_SIGN),
LTC2497_CHAN_DIFF(6, LTC2497_DIFF | LTC2497_SIGN),
LTC2497_CHAN_DIFF(7, LTC2497_DIFF | LTC2497_SIGN),
LTC2497_TEMP_CHANNEL,
};

static const struct iio_info ltc2497core_info = {
.read_raw = ltc2497core_read_raw,
.read_avail = ltc2497core_read_avail,
.write_raw = ltc2497core_write_raw,
};

int ltc2497core_probe(struct device *dev, struct iio_dev *indio_dev)
Expand All @@ -183,6 +348,37 @@ int ltc2497core_probe(struct device *dev, struct iio_dev *indio_dev)
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->channels = ltc2497core_channel;
indio_dev->num_channels = ARRAY_SIZE(ltc2497core_channel);
/* Only the ltc2499 has a temperature channel; it is the last entry. */
if (!ddata->chip_info->has_temp)
indio_dev->num_channels--;

/*
* Parts with a speed mode expose in_voltage_sampling_frequency /
* _available on the voltage channels only. SPD is ignored during a
* temperature measurement, so the temperature channel deliberately
* carries no SAMP_FREQ attribute. Patch a private copy of the shared
* channel array so parts without a speed mode stay untouched.
*/
Comment on lines +355 to +361

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I don't see how this is the best approach.
why can't the temp channel be correctly const instead of patching it on probe?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

The reason I kept the runtime-patched copy is extensibility: the channel metadata is expected to grow with additional optional features on the LTC2499 family, and keeping a single base channel description avoids maintaining multiple near-duplicate channel tables.
I'm happy to switch to a static const table if you feel that is the preferred direction for this series.

if (ddata->chip_info->has_speed_mode) {
struct iio_chan_spec *channels;
unsigned int i;

channels = devm_kmemdup(dev, ltc2497core_channel,
sizeof(ltc2497core_channel), GFP_KERNEL);
if (!channels)
return -ENOMEM;

for (i = 0; i < indio_dev->num_channels; i++) {
if (channels[i].type != IIO_VOLTAGE)
continue;
channels[i].info_mask_shared_by_type |=
BIT(IIO_CHAN_INFO_SAMP_FREQ);
channels[i].info_mask_shared_by_type_available |=
BIT(IIO_CHAN_INFO_SAMP_FREQ);
}

indio_dev->channels = channels;
}

ret = ddata->result_and_measure(ddata, LTC2497_CONFIG_DEFAULT, NULL);
if (ret < 0)
Expand Down Expand Up @@ -214,6 +410,8 @@ int ltc2497core_probe(struct device *dev, struct iio_dev *indio_dev)

ddata->addr_prev = LTC2497_CONFIG_DEFAULT;
ddata->time_prev = ktime_get();
/* Power-on default mode is 1x; a conversion is already in flight. */
ddata->conv_time_prev = LTC2497_CONV_TIME_1X_MS;

mutex_init(&ddata->lock);

Expand Down
Loading