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:

  1. add IIO_CHAN_INFO_SCALE and IIO_CHAN_INFO_OFFSET to the channel definitions,
  2. alter IIO_CHAN_INFO_SCALE to properly return 9 instead of 10 in differential channels,
  3. implement IIO_CHAN_INFO_OFFSET, and
  4. 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).