ad7292: Round Two
With all other projects completed, it was now time to go back to ad7292 differential mode implementation. I spoke with the original developer of the driver, a former student at my university, and he helped me plan out my steps. I’d have to:
- add
IIO_CHAN_INFO_SCALE
andIIO_CHAN_INFO_OFFSET
to the channel definitions, - alter
IIO_CHAN_INFO_SCALE
to properly return 9 instead of 10 in differential channels, - implement IIO_CHAN_INFO_OFFSET, and
- configure the device to operate in differential mode, which requires me to write a bit into a specific subregister, so I’d have to implement a ad7292_spi_subreg_write() function too.
Binary time
I spoke at length about binary encoding in the previous kernel post, so I won’t be going into that this time. The encoding used is 10-bit signed offset binary, which means to convert the 10-bit code I receive to a regular number, I have to subtract 0x200
(the midpoint offset) and signal to the IIO enviroment that the scale is relative to 2^9
possible values, not 2^10
like in the straight binary case (as one of our bits is used for the sign). The offset is done in the aptly named IIO_CHAN_INFO_OFFSET
case of read_raw
and the scale adjustment is just an if
in the IIO_CHAN_INFO_SCALE
case. That dealt with steps 1, 2 and 3.
static int ad7292_read_raw(struct iio_dev *indio_dev,
const struct iio_chan_spec *chan,
int *val, int *val2, long info)
{
struct ad7292_state *st = iio_priv(indio_dev);
unsigned int ch_addr;
int ret;
switch (info) {
case IIO_CHAN_INFO_RAW:
ch_addr = AD7292_REG_ADC_CH(chan->channel);
ret = ad7292_single_conversion(st, ch_addr);
if (ret < 0)
return ret;
*val = AD7292_ADC_DATA(ret);
return IIO_VAL_INT;
case IIO_CHAN_INFO_SCALE:
ret = ad7292_vin_range_multiplier(st, chan->channel);
if (ret < 0)
return ret;
*val = st->vref_mv * ret;
if (chan->differential)
*val2 = 9;
else
*val2 = 10;
return IIO_VAL_FRACTIONAL_LOG2;
case IIO_CHAN_INFO_OFFSET:
if (chan->differential)
*val = -0x200;
else
*val = 0;
return IIO_VAL_INT;
default:
break;
}
return -EINVAL;
}
Step 4
I hadn’t expected to need to add new functionality to the driver when I first took on this project in the beginning of the semester, but it was easier than expected. I had to read a bunch of documentation to make sure what I was doing was correct, but it was a simple function to implement and it should work as expected. According to the device’s datasheet, I’d need to send a 8 bit signal composed of a write bit and the address of a register, followed by another 8 bit signal composed of the address of the subregister and finally the 16 bit code that I want to write to that subregister. Since I only want to change a specific bit, I’ll read the subregister values, replace the bit I want to change and write the changed version back into the device.
#define AD7292_WR_FLAG_MSK(x) (BIT(6) | ((x) & 0x3F))
static int ad7292_spi_subreg_write(struct ad7292_state *st, unsigned int addr,
unsigned int sub_addr, unsigned int val, unsigned int len)
{
int ret;
unsigned int data = cpu_to_be16(val);
st->d8[0] = AD7292_WR_FLAG_MSK(addr);
st->d8[1] = sub_addr;
st->d8[2] = data & 0x00FF;
st->d8[3] = (data >> 8) & 0x00FF;
ret = spi_write(st->spi, st->d8, 2 + len);
if (ret < 0)
return ret;
return 0;
}
e
#define AD7292_DIFF_FLAG_MSK BIT(0)
...
if (diff_channels) {
ret = ad7292_spi_subreg_read(st, AD7292_REG_CONF_BANK,
AD7292_BANK_REG_SAMP_MODE, 2);
if (ret < 0) {
dev_err(&spi->dev,
"Failed to read ADC differential mode subregister\n");
return ret;
}
ret = ad7292_spi_subreg_write(st, AD7292_REG_CONF_BANK,
AD7292_BANK_REG_SAMP_MODE,
ret | AD7292_DIFF_FLAG_MSK, 2);
if (ret < 0) {
dev_err(&spi->dev,
"Failed to enable ADC differential mode\n");
return ret;
}
...
I still haven’t tested the code, but hopefully it’ll work (it does compile, at least).