mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Andy Shevchenko <andriy.shevchenko@intel.com>
To: radu.sabau@analog.com
Cc: Lars-Peter Clausen <lars@metafoo.de>,
	Michael Hennerich <Michael.Hennerich@analog.com>,
	Nuno Sa <nuno.sa@analog.com>, Jonathan Cameron <jic23@kernel.org>,
	David Lechner <dlechner@baylibre.com>,
	Andy Shevchenko <andy@kernel.org>,
	Alexandru Ardelean <alexandru.ardelean@analog.com>,
	Rob Herring <robh@kernel.org>,
	Krzysztof Kozlowski <krzk+dt@kernel.org>,
	Conor Dooley <conor+dt@kernel.org>,
	linux-iio@vger.kernel.org, linux-kernel@vger.kernel.org,
	devicetree@vger.kernel.org, linux@analog.com
Subject: Re: [PATCH v4 5/8] iio: imu: Add driver for the ADI ADIS16607
Date: Fri, 23 Oct 2026 13:26:36 +0300	[thread overview]
Message-ID: <ats2XBBnfGmusCI2@ashevche-desk.local> (raw)
In-Reply-To: <20261023-adis16607-v4-5-a4d541ebfd13@analog.com>

On Fri, Oct 23, 2026 at 12:30:22PM +0300, Radu Sabau via B4 Relay wrote:

> The ADIS16607 is a Precision MEMS IMU from Analog Devices with
> triaxial gyroscope and accelerometer, delta angle/velocity, and
> temperature channels, communicating over a custom SPI Half-Duplex
> protocol. ADIS16607-2 has a ±450°/s gyroscope range, ADIS16607-3
> ±2000°/s; both share a ±40g accelerometer range and an 8 kHz internal
> clock.
> 
> This is the base driver: probe, raw channel reads, sampling-frequency
> control, startup/self-test via adislib, and debugfs identity entries.
> No buffered capture, external clock sync, or calibration bias yet -
> those follow in later patches.
> 
> Self-test leaves the device's SELF_TEST register cleared on every exit
> path, not just on success, so a read failure or a failed delta check
> doesn't leave the sensor stuck in its self-test/force state until the
> next reset.

...

> +++ b/drivers/iio/imu/Kconfig

> +	tristate "Analog Devices ADIS16607 and similar IMU driver"

(see below)


...

> +struct adis16607_chip_info {
> +	const struct iio_chan_spec *channels;
> +	const char *name;
> +	u32 num_channels;
> +	u32 gyro_max_val;
> +	u32 gyro_max_scale;
> +	u32 accel_max_val;
> +	u32 accel_max_scale;
> +	u32 temp_scale;
> +	u32 deltang_max_val;
> +	u32 deltvel_max_val;
> +	u32 int_clk_hz;

_Hz

> +	u16 max_dec;
> +};

...

> +struct adis16607 {
> +	struct adis adis;
> +	const struct adis16607_chip_info *info;
> +	unsigned long clk_freq_mhz;

_MHz or _mHz? (I believe it's the first one.)
This is why case-sensitive units (as per physics) are important.

> +	u64 sample_period_ns;
> +};

...

> +		/* Combine: 24-bit value with MSW in upper 16 bits, LSW in lower 8 bits */

Make it multi-line and to fix 80 characters.

> +		*val = ((val_msw << 8) & 0xFFFF00) | val_lsw;

Why not GENMASK()? Can val_msw be u16 or other 16-bit type?

...

> +static int adis16607_sensor_self_test(struct adis16607 *st)
> +{
> +	struct adis *adis = &st->adis;
> +	u16 st1_val[6], st2_val[6];
> +	s16 delta_max[6] = {
> +		ADIS16607_ACCEL_XY_DELTA_MAX,
> +		ADIS16607_ACCEL_XY_DELTA_MAX,
> +		ADIS16607_ACCEL_Z_DELTA_MAX,
> +		ADIS16607_GYRO_DELTA_MAX,
> +		ADIS16607_GYRO_DELTA_MAX,
> +		ADIS16607_GYRO_DELTA_MAX,
> +	};
> +	int ret, i;

Why is 'i' signed?

> +	/* Enable sensor self-test */
> +	ret = __adis_write_reg_16(adis, ADIS16607_REG_SELF_TEST,
> +				  ADIS16607_SNSR_SELF_TEST_MASK);
> +	if (ret)
> +		return ret;
> +
> +	/* Wait for self-test to settle before reading results */
> +	msleep(adis->data->timeouts->self_test_ms);
> +
> +	/* Read first set of self-test data */
> +	for (i = 0; i < ARRAY_SIZE(st1_val); i++) {
> +		ret = __adis_read_reg_16(adis, ADIS16607_REG_SELF_TEST_DATA(i),
> +					 &st1_val[i]);
> +		if (ret)
> +			goto clear_self_test;
> +	}
> +
> +	/* Force self-test */
> +	ret = __adis_write_reg_16(adis, ADIS16607_REG_SELF_TEST,
> +				  ADIS16607_SNSR_SELF_TEST_MASK | ADIS16607_ST_FORCE_MASK);
> +	if (ret)
> +		goto clear_self_test;
> +
> +	/* Wait for forced self-test to settle before reading results */
> +	msleep(adis->data->timeouts->self_test_ms);
> +
> +	/* Read second set of self-test data */
> +	for (i = 0; i < ARRAY_SIZE(st2_val); i++) {

In both for-loops iterator is local, so

	for (unsigned int i = 0; i < ARRAY_SIZE(st2_val); i++) {

> +		ret = __adis_read_reg_16(adis, ADIS16607_REG_SELF_TEST_DATA(i),
> +					 &st2_val[i]);
> +		if (ret)
> +			goto clear_self_test;
> +
> +		if (!adis16607_delta_check((s16)st1_val[i], (s16)st2_val[i],
> +					   delta_max[i])) {
> +			dev_err(&adis->spi->dev,
> +				"Self-test failed for axis %d: delta out of range\n", i);
> +			ret = -EINVAL;
> +			goto clear_self_test;
> +		}
> +	}
> +
> +	/* Clear self-test register */
> +	return __adis_write_reg_16(adis, ADIS16607_REG_SELF_TEST, 0);
> +
> +clear_self_test:
> +	/*
> +	 * Best-effort cleanup after a failure above - leave the device out
> +	 * of its self-test/force state rather than stuck in it until the
> +	 * next reset. Warn rather than returning this write's own result,
> +	 * so a cleanup failure doesn't mask the real error that got us here.
> +	 */
> +	if (__adis_write_reg_16(adis, ADIS16607_REG_SELF_TEST, 0))
> +		dev_warn(&adis->spi->dev, "failed to clear self-test register\n");
> +
> +	return ret;
> +}

...

> +static int adis16607_self_test(struct adis *adis)
> +{
> +	struct adis16607 *st = container_of(adis, struct adis16607, adis);
> +	struct device *dev = &adis->spi->dev;
> +	u16 diag_stat;
> +	int ret;
> +
> +	/* Inertial Settling Time (Table 1), needed before self-test reads sensor data */

Make it fit 80.

> +	msleep(40);
> +
> +	ret = adis16607_sensor_self_test(st);
> +	if (ret)
> +		return ret;
> +
> +	/* Read diag_stat again to clear any faults latched during self-test */
> +	ret = __adis_read_reg_16(adis, ADIS16607_REG_DIAG_STAT, &diag_stat);
> +	if (ret)
> +		return ret;
> +
> +	ret = __adis_check_status(adis);
> +	if (ret)
> +		dev_err(dev, "status check after self-test failed: %d\n", ret);
> +
> +	return ret;
> +}

...

> +static int adis16607_firmware_revision_show(struct seq_file *s, void *arg)
> +{
> +	struct adis16607 *st = s->private;
> +	u16 rev;

Can this be

	struct revision {
		u8 minor;
		u8 major;
	} rev;

?

> +	int ret;
> +
> +	ret = adis_read_reg_16(&st->adis, ADIS16607_REG_REV_ID, &rev);
> +	if (ret)
> +		return ret;
> +
> +	seq_printf(s, "%u.%u\n", (rev >> 8) & 0xff, rev & 0xff);
> +	return 0;
> +}

...

> +static int adis16607_show_serial_number(void *arg, u64 *val)
> +{
> +	struct adis16607 *st = arg;
> +	u64 serial;
> +	u16 tmp;
> +	int ret;
> +
> +	ret = adis_read_reg_16(&st->adis, ADIS16607_REG_SERIAL_NUM0, &tmp);
> +	if (ret)
> +		return ret;
> +
> +	serial = (u64)tmp;

How is explicit casting helpful here?

> +	ret = adis_read_reg_16(&st->adis, ADIS16607_REG_SERIAL_NUM1, &tmp);
> +	if (ret)
> +		return ret;
> +
> +	serial |= ((u64)tmp << 16);
> +
> +	ret = adis_read_reg_16(&st->adis, ADIS16607_REG_SERIAL_NUM2, &tmp);
> +	if (ret)
> +		return ret;
> +
> +	serial |= ((u64)tmp << 32);

What about reversing the order and shift serial instead?

	u64 serial = 0;

	ret = adis_read_reg_16(&st->adis, ADIS16607_REG_SERIAL_NUM2, &tmp);
	if (ret)
		return ret;

	serial |= tmp;

	ret = adis_read_reg_16(&st->adis, ADIS16607_REG_SERIAL_NUM1, &tmp);
	if (ret)
		return ret;

	serial <<= 16;
	serial |= tmp;

	ret = adis_read_reg_16(&st->adis, ADIS16607_REG_SERIAL_NUM0, &tmp);
	if (ret)
		return ret;

	serial <<= 16;
	serial |= tmp;

> +	*val = serial;
> +	return 0;
> +}

...

> +static int adis16607_set_freq(struct adis16607 *st, u32 freq)
> +{
> +	u32 dec;
> +	int ret;
> +
> +	if (!freq)
> +		return -EINVAL;
> +
> +	adis_dev_auto_lock(&st->adis);
> +
> +	dec = DIV_ROUND_CLOSEST(st->clk_freq_mhz, freq);
> +	if (dec)
> +		dec--;
> +
> +	dec = min_t(u32, dec, st->info->max_dec);

No min_t(). Make the types to be compatible for min().

> +	ret = __adis_write_reg_16(&st->adis, ADIS16607_REG_DEC_RATE, dec);
> +	if (ret)
> +		return ret;
> +
> +	st->sample_period_ns = DIV_ROUND_CLOSEST_ULL((u64)NSEC_PER_SEC * 1000ULL * (dec + 1),
> +						     st->clk_freq_mhz);

Make it fit. Using temporary variable usually helps. Also you don't need (u64)
casting or ULL suffix. On top of that, what is another 1000 multiplier for?

I would expect a comment on top with explaining magic(s).

> +	return 0;
> +}

...

> +	case IIO_CHAN_INFO_SAMP_FREQ:
> +		ret = adis16607_get_freq(st, &tmp);
> +		if (ret)
> +			return ret;
> +		*val = tmp / 1000;
> +		*val2 = (tmp % 1000) * 1000;

What is 1000? HZ_PER_KHZ? KHZ_PER_MHZ?

> +		return IIO_VAL_INT_PLUS_MICRO;

...

> +static int adis16607_write_raw(struct iio_dev *indio_dev,
> +			       const struct iio_chan_spec *chan,
> +			       int val, int val2, long info)
> +{
> +	struct adis16607 *st = iio_priv(indio_dev);
> +	u32 tmp;
> +
> +	switch (info) {
> +	case IIO_CHAN_INFO_SAMP_FREQ:
> +		tmp = val * 1000 + val2 / 1000;
> +		return adis16607_set_freq(st, tmp);

Ditto.

> +	default:
> +		return -EINVAL;
> +	}
> +}

...

> +#define ADIS16607_MOD_CHAN(_type, _mod, _address, _si) { \
> +	.type = (_type), \
> +	.modified = 1, \
> +	.channel2 = (_mod), \
> +	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
> +	.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
> +	.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \
> +	.address = (_address), \
> +	.scan_index = (_si), \
> +	.scan_type = { \
> +		.sign = 's', \
> +		.realbits = 24, \
> +		.storagebits = 32, \
> +		.endianness = IIO_BE, \
> +	}, \
> +}

This is not a function, I would rather shift it right by a tab with a split
(also indent the backslashes, it makes it easier to read).

#define ADIS16607_MOD_CHAN(_type, _mod, _address, _si)		\
	{							\
		.type = (_type),				\
		...
	}

...

> +#define ADIS16607_MOD_CHAN_DELTA(_type, _mod, _address, _si) { \
> +	.type = (_type), \
> +	.modified = 1, \
> +	.channel2 = (_mod), \
> +	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
> +	.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
> +	.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \
> +	.address = (_address), \
> +	.scan_index = (_si), \
> +	.scan_type = { \
> +		.sign = 's', \
> +		.realbits = 24, \
> +		.storagebits = 32, \
> +		.endianness = IIO_BE, \
> +	}, \
> +}

Ditto.

...

> +/*
> + * No external clock sync support yet: always run on the internal clock.
> + * Default dec=0 after reset, so period = 1 / clk_freq_mhz.
> + */
> +static int adis16607_config_clk(struct adis16607 *st)
> +{
> +	st->clk_freq_mhz = st->info->int_clk_hz * MILLI;

So, it's milli-hertz then? We have MILLIHZ_PER_HZ.

> +	st->sample_period_ns =
> +		DIV_ROUND_CLOSEST_ULL((u64)NSEC_PER_SEC * MILLI, st->clk_freq_mhz);
> +	return 0;
> +}

Yeah, this magic 1000 should be explained everywhere.

...

> +MODULE_DESCRIPTION("Analog Devices ADIS16607 IMU driver");

Make the text the same as per Kconfig. Whatever wording you choose.

-- 
With Best Regards,
Andy Shevchenko



  reply	other threads:[~2026-10-23 10:26 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-23  9:30 [PATCH v4 0/8] iio: imu: Add support " Radu Sabau via B4 Relay
2026-10-23  9:30 ` [PATCH v4 1/8] iio: imu: adis: Add optional self_test callback Radu Sabau via B4 Relay
2026-10-23  9:30 ` [PATCH v4 2/8] iio: imu: adis: Fix custom reset dispatch in adislib Radu Sabau via B4 Relay
2026-10-23  9:30 ` [PATCH v4 3/8] iio: imu: adis: Add optional post_reset_setup callback Radu Sabau via B4 Relay
2026-10-23  9:30 ` [PATCH v4 4/8] dt-bindings: iio: imu: Add bindings for ADI ADIS16607 Radu Sabau via B4 Relay
2026-10-23  9:30 ` [PATCH v4 5/8] iio: imu: Add driver for the " Radu Sabau via B4 Relay
2026-10-23 10:26   ` Andy Shevchenko [this message]
2026-10-23  9:30 ` [PATCH v4 6/8] iio: imu: adis16607: Add FIFO-based buffered/triggered capture Radu Sabau via B4 Relay
2026-10-23  9:30 ` [PATCH v4 7/8] iio: imu: adis16607: Add external clock sync support Radu Sabau via B4 Relay
2026-10-23  9:30 ` [PATCH v4 8/8] iio: imu: adis16607: Add calibration bias support for gyro/accel Radu Sabau via B4 Relay

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=ats2XBBnfGmusCI2@ashevche-desk.local \
    --to=andriy.shevchenko@intel.com \
    --cc=Michael.Hennerich@analog.com \
    --cc=alexandru.ardelean@analog.com \
    --cc=andy@kernel.org \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=dlechner@baylibre.com \
    --cc=jic23@kernel.org \
    --cc=krzk+dt@kernel.org \
    --cc=lars@metafoo.de \
    --cc=linux-iio@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux@analog.com \
    --cc=nuno.sa@analog.com \
    --cc=radu.sabau@analog.com \
    --cc=robh@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®