[TODO] Potential Staging Clean-up: accel:adis16201.

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi All

I was looking over what we have left in staging for any low hanging fruit
and came across the adis16201 driver.

I was wondering if anyone would like to clean it up?

This would make a good opportuntity for someone who is fairly new to
kernel development to make a useful contribution.

To that end I've done a review as if it had been submitted as a new
driver.

If you want to take this on, please reply to say so in order to avoid
several people looking at this one driver.  Obviously if things change
and you then can't get to it reasonably quickly then 'hand it back'
on the mailing list.

I'm happy to do this for a few more drivers if there are enough
interested people so do ping me if someone has claimed this one and you
would like a similar starting point.

Ideally we would find someone who actually has hardware, but for the simple
stuff in this one I think we are unlikely to break anything that can't be
picked up in review. Hence I'm not totally bothered about it if no one
comes forward!

Alison, Daniel. Feel free to add this to the outreachy task list if no
one else grabs it before the process starts again (just let us know if you are
doing that).

Thanks,

Jonathan

> /*
>  * ADIS16201 Dual-Axis Digital Inclinometer and Accelerometer
>  *
>  * Copyright 2010 Analog Devices Inc.
>  *
>  * Licensed under the GPL-2 or later.

Might as well convert to SPDX format for the license if we
are working on the driver anyway.

>  */
> 
> #include <linux/delay.h>
> #include <linux/mutex.h>
> #include <linux/device.h>
> #include <linux/kernel.h>
> #include <linux/spi/spi.h>
> #include <linux/slab.h>
> #include <linux/sysfs.h>
> #include <linux/module.h>

Slight preference for alphabetical order in headers.

However, it is useful to keep the iio headers separate
as they are here.

> 
> #include <linux/iio/iio.h>
> #include <linux/iio/sysfs.h>
> #include <linux/iio/buffer.h>
> #include <linux/iio/imu/adis.h>
> 
> #define ADIS16201_STARTUP_DELAY	220 /* ms */

Why not just make this ADIS16201_STARTUP_DELAY_MS as then it will
be obvious what the units are throughout the driver and remove
the need for the comment.

> 
> /* Flash memory write count */
> #define ADIS16201_FLASH_CNT      0x00

To my mind there is far more commenting going on here than is actually
useful.  If the name of the register doesn't make it reasonably clear
what it is used for then it is not a good choice of name.

It is made more complex by the fact these are the datasheet names
so we need to keep 'close' to those names for ease of comparisom.

So unless something odd is going on I would not expect to see any
comments in here at all.

Formatting wise, should be using tabs rather than spaces before the
numbers.

Also, we have a mixture of defines for register addresses and
fields with in registers.  That is fine, but I would prefer
the nice clear _REG postfix on the registers defines to make
it more obvious which is which.

e.g. What I'd expect here is something like:

#define ADIS16201_FLASH_CNT_REG			0x00
#define ADIS16201_SUPPLY_OUT_REG		0x02
#define ADIS16201_XACCL_OUT_REG			0x04

etc.

> 
> /* Output, power supply */
> #define ADIS16201_SUPPLY_OUT     0x02

The out naming is awkward given it doesn't mean the device is supplying the
power, but rather that this is the reading the device is outputting.  Still
can't do that much about this without breaking away from datasheet naming.

> 
> /* Output, x-axis accelerometer */
> #define ADIS16201_XACCL_OUT      0x04
> 
> /* Output, y-axis accelerometer */
> #define ADIS16201_YACCL_OUT      0x06
> 
> /* Output, auxiliary ADC input */
> #define ADIS16201_AUX_ADC        0x08
> 
> /* Output, temperature */
> #define ADIS16201_TEMP_OUT       0x0A
> 
> /* Output, x-axis inclination */
> #define ADIS16201_XINCL_OUT      0x0C
> 
> /* Output, y-axis inclination */
> #define ADIS16201_YINCL_OUT      0x0E
> 
> /* Calibration, x-axis acceleration offset */
> #define ADIS16201_XACCL_OFFS     0x10
> 
> /* Calibration, y-axis acceleration offset */
> #define ADIS16201_YACCL_OFFS     0x12
> 
> /* x-axis acceleration scale factor */
> #define ADIS16201_XACCL_SCALE    0x14
> 
> /* y-axis acceleration scale factor */
> #define ADIS16201_YACCL_SCALE    0x16
> 
> /* Calibration, x-axis inclination offset */
> #define ADIS16201_XINCL_OFFS     0x18
> 
> /* Calibration, y-axis inclination offset */
> #define ADIS16201_YINCL_OFFS     0x1A
> 
> /* x-axis inclination scale factor */
> #define ADIS16201_XINCL_SCALE    0x1C
> 
> /* y-axis inclination scale factor */
> #define ADIS16201_YINCL_SCALE    0x1E
> 
> /* Alarm 1 amplitude threshold */
> #define ADIS16201_ALM_MAG1       0x20
> 
> /* Alarm 2 amplitude threshold */
> #define ADIS16201_ALM_MAG2       0x22
> 
> /* Alarm 1, sample period */
> #define ADIS16201_ALM_SMPL1      0x24
> 
> /* Alarm 2, sample period */
> #define ADIS16201_ALM_SMPL2      0x26
> 
> /* Alarm control */
> #define ADIS16201_ALM_CTRL       0x28
> 
> /* Auxiliary DAC data */
> #define ADIS16201_AUX_DAC        0x30
> 
> /* General-purpose digital input/output control */
> #define ADIS16201_GPIO_CTRL      0x32
> 
> /* Miscellaneous control */
> #define ADIS16201_MSC_CTRL       0x34
> 
> /* Internal sample period (rate) control */
> #define ADIS16201_SMPL_PRD       0x36
> 
> /* Operation, filter configuration */
> #define ADIS16201_AVG_CNT        0x38
> 
> /* Operation, sleep mode control */
> #define ADIS16201_SLP_CNT        0x3A
> 
> /* Diagnostics, system status register */
> #define ADIS16201_DIAG_STAT      0x3C
> 
> /* Operation, system command register */
> #define ADIS16201_GLOB_CMD       0x3E
> 
> /* MSC_CTRL */
> 
A really nice convention that has come up in recent drivers and makes
all these register / register field relationships obvious is to
use some subtle indenting of the fields and put them next to
the register address.  An example is:

#define ADIS16201_MSC_CTRL_REG			0x34
#define   ADIS16201_MSC_CTRL_SELF_TEST_EN	BIT(8)
#define   ADIS16201_MSC_CTRL_DATA_RDY_EN	BIT(2)
#define   ADIS16201_MSC_CTRL_ACTIVE_HIGH	BIT(1)
#define   ADIS16201_MSC_CTRL_DATA_RDY_DIO1	BIT(0)

Again the naming of the fields is sufficient and the documentation
doesn't add anything so I would drop it.

> /* Self-test enable */
> #define ADIS16201_MSC_CTRL_SELF_TEST_EN	        BIT(8)
> 
> /* Data-ready enable: 1 = enabled, 0 = disabled */
> #define ADIS16201_MSC_CTRL_DATA_RDY_EN	        BIT(2)
> 
> /* Data-ready polarity: 1 = active high, 0 = active low */
> #define ADIS16201_MSC_CTRL_ACTIVE_HIGH	        BIT(1)
> 
> /* Data-ready line selection: 1 = DIO1, 0 = DIO0 */
> #define ADIS16201_MSC_CTRL_DATA_RDY_DIO1	BIT(0)
> 
> /* DIAG_STAT */
> 
> /* Alarm 2 status: 1 = alarm active, 0 = alarm inactive */
> #define ADIS16201_DIAG_STAT_ALARM2        BIT(9)
> 
> /* Alarm 1 status: 1 = alarm active, 0 = alarm inactive */
> #define ADIS16201_DIAG_STAT_ALARM1        BIT(8)
> 
> /* SPI communications failure */
> #define ADIS16201_DIAG_STAT_SPI_FAIL_BIT   3
> 
> /* Flash update failure */
> #define ADIS16201_DIAG_STAT_FLASH_UPT_BIT  2
> 
> /* Power supply above 3.625 V */
> #define ADIS16201_DIAG_STAT_POWER_HIGH_BIT 1
> 
> /* Power supply below 3.15 V */
> #define ADIS16201_DIAG_STAT_POWER_LOW_BIT  0
> 
> /* GLOB_CMD */
> 
> #define ADIS16201_GLOB_CMD_SW_RESET	BIT(7)
> #define ADIS16201_GLOB_CMD_FACTORY_CAL	BIT(1)
> 
> #define ADIS16201_ERROR_ACTIVE          BIT(14)
> 
> enum adis16201_scan {
> 	ADIS16201_SCAN_ACC_X,
> 	ADIS16201_SCAN_ACC_Y,
> 	ADIS16201_SCAN_INCLI_X,
> 	ADIS16201_SCAN_INCLI_Y,
> 	ADIS16201_SCAN_SUPPLY,
> 	ADIS16201_SCAN_AUX_ADC,
> 	ADIS16201_SCAN_TEMP,
> };
> 
> static const u8 adis16201_addresses[] = {

It is rather odd that this is only used for the offsets, not
any of the other channel specific registers (scales, or the reading itself).

> 	[ADIS16201_SCAN_ACC_X] = ADIS16201_XACCL_OFFS,
> 	[ADIS16201_SCAN_ACC_Y] = ADIS16201_YACCL_OFFS,
> 	[ADIS16201_SCAN_INCLI_X] = ADIS16201_XINCL_OFFS,
> 	[ADIS16201_SCAN_INCLI_Y] = ADIS16201_YINCL_OFFS,
> };
> 
> static int adis16201_read_raw(struct iio_dev *indio_dev,
> 			      struct iio_chan_spec const *chan,
> 			      int *val, int *val2,
> 			      long mask)
> {
> 	struct adis *st = iio_priv(indio_dev);
> 	int ret;
> 	int bits;
> 	u8 addr;
> 	s16 val16;
> 
> 	switch (mask) {
> 	case IIO_CHAN_INFO_RAW:
> 		return adis_single_conversion(indio_dev, chan,
> 				ADIS16201_ERROR_ACTIVE, val);

Indenting could I think make the parameters align with the opening bracket.

> 	case IIO_CHAN_INFO_SCALE:
> 		switch (chan->type) {
> 		case IIO_VOLTAGE:
> 			if (chan->channel == 0) {
> 				*val = 1;
> 				*val2 = 220000; /* 1.22 mV */

Comments don't add a huge amount given they kind of just
state the value before them.

> 			} else {

Given there are only two relevant channels I would like to see
them move explicitly matched using a switch statement.

The default case would be an error return -EINVAL;

> 				*val = 0;
> 				*val2 = 610000; /* 0.610 mV */
> 			}
> 			return IIO_VAL_INT_PLUS_MICRO;
> 		case IIO_TEMP:
> 			*val = -470; /* 0.47 C */
> 			*val2 = 0;
> 			return IIO_VAL_INT_PLUS_MICRO;
> 		case IIO_ACCEL:
> 			*val = 0;
> 			*val2 = IIO_G_TO_M_S_2(462400); /* 0.4624 mg */

Not sure that comment adds anything..

> 			return IIO_VAL_INT_PLUS_NANO;
> 		case IIO_INCLI:
> 			*val = 0;
> 			*val2 = 100000; /* 0.1 degree */

Or this one.

> 			return IIO_VAL_INT_PLUS_MICRO;
> 		default:
> 			return -EINVAL;
> 		}
> 		break;
> 	case IIO_CHAN_INFO_OFFSET:
> 		*val = 25000 / -470 - 1278; /* 25 C = 1278 */
> 		return IIO_VAL_INT;
> 	case IIO_CHAN_INFO_CALIBBIAS:
> 		switch (chan->type) {
> 		case IIO_ACCEL:
> 			bits = 12;
> 			break;
> 		case IIO_INCLI:
> 			bits = 9;
> 			break;
> 		default:
> 			return -EINVAL;
> 		}
> 		addr = adis16201_addresses[chan->scan_index];
> 		ret = adis_read_reg_16(st, addr, &val16);
> 		if (ret)
> 			return ret;
> 		val16 &= (1 << bits) - 1;

Given the following sign extend, I don't think this does anything useful.

> 		val16 = (s16)(val16 << (16 - bits)) >> (16 - bits);

This looks like a 16bit sign extend to me.  We have functions to
handle this nicely. See how it is done in other driverss

> 		*val = val16;

blank line before return always makes them more obvious and hence the
code slightly more readable.  Do this throughout (including 2 lines
below this)

> 		return IIO_VAL_INT;
> 	}
> 	return -EINVAL;
> }
> 
> static int adis16201_write_raw(struct iio_dev *indio_dev,
> 			       struct iio_chan_spec const *chan,
> 			       int val,
> 			       int val2,
> 			       long mask)
> {
> 	struct adis *st = iio_priv(indio_dev);
> 	int bits;
> 	s16 val16;
> 	u8 addr;
> 
> 	switch (mask) {
> 	case IIO_CHAN_INFO_CALIBBIAS:
> 		switch (chan->type) {
> 		case IIO_ACCEL:
> 			bits = 12;
> 			break;
> 		case IIO_INCLI:
> 			bits = 9;
> 			break;
> 		default:
> 			return -EINVAL;
> 		}
> 		val16 = val & ((1 << bits) - 1);

Given the above bits value is used only for this, why not create
a mask in the switch statement instead of carrying the number of
bits around.

Then you can use GENMASK to make it nice and obvious what
is going on.

> 		addr = adis16201_addresses[chan->scan_index];
> 		return adis_write_reg_16(st, addr, val16);
> 	}
> 	return -EINVAL;
> }
> 
> static const struct iio_chan_spec adis16201_channels[] = {
> 	ADIS_SUPPLY_CHAN(ADIS16201_SUPPLY_OUT, ADIS16201_SCAN_SUPPLY, 0, 12),
> 	ADIS_TEMP_CHAN(ADIS16201_TEMP_OUT, ADIS16201_SCAN_TEMP, 0, 12),
> 	ADIS_ACCEL_CHAN(X, ADIS16201_XACCL_OUT, ADIS16201_SCAN_ACC_X,
> 			BIT(IIO_CHAN_INFO_CALIBBIAS), 0, 14),
> 	ADIS_ACCEL_CHAN(Y, ADIS16201_YACCL_OUT, ADIS16201_SCAN_ACC_Y,
> 			BIT(IIO_CHAN_INFO_CALIBBIAS), 0, 14),
> 	ADIS_AUX_ADC_CHAN(ADIS16201_AUX_ADC, ADIS16201_SCAN_AUX_ADC, 0, 12),
> 	ADIS_INCLI_CHAN(X, ADIS16201_XINCL_OUT, ADIS16201_SCAN_INCLI_X,
> 			BIT(IIO_CHAN_INFO_CALIBBIAS), 0, 14),
> 	ADIS_INCLI_CHAN(X, ADIS16201_YINCL_OUT, ADIS16201_SCAN_INCLI_Y,
> 			BIT(IIO_CHAN_INFO_CALIBBIAS), 0, 14),
> 	IIO_CHAN_SOFT_TIMESTAMP(7)
> };
> 
> static const struct iio_info adis16201_info = {
> 	.read_raw = adis16201_read_raw,
> 	.write_raw = adis16201_write_raw,
> 	.update_scan_mode = adis_update_scan_mode,
> };
> 
> static const char * const adis16201_status_error_msgs[] = {
> 	[ADIS16201_DIAG_STAT_SPI_FAIL_BIT] = "SPI failure",
> 	[ADIS16201_DIAG_STAT_FLASH_UPT_BIT] = "Flash update failed",
> 	[ADIS16201_DIAG_STAT_POWER_HIGH_BIT] = "Power supply above 3.625V",
> 	[ADIS16201_DIAG_STAT_POWER_LOW_BIT] = "Power supply below 3.15V",
> };
> 
> static const struct adis_data adis16201_data = {
> 	.read_delay = 20,
> 	.msc_ctrl_reg = ADIS16201_MSC_CTRL,
> 	.glob_cmd_reg = ADIS16201_GLOB_CMD,
> 	.diag_stat_reg = ADIS16201_DIAG_STAT,
> 
> 	.self_test_mask = ADIS16201_MSC_CTRL_SELF_TEST_EN,
> 	.self_test_no_autoclear = true,
> 	.startup_delay = ADIS16201_STARTUP_DELAY,
> 
> 	.status_error_msgs = adis16201_status_error_msgs,
> 	.status_error_mask = BIT(ADIS16201_DIAG_STAT_SPI_FAIL_BIT) |
> 		BIT(ADIS16201_DIAG_STAT_FLASH_UPT_BIT) |
> 		BIT(ADIS16201_DIAG_STAT_POWER_HIGH_BIT) |
> 		BIT(ADIS16201_DIAG_STAT_POWER_LOW_BIT),
> };
> 
> static int adis16201_probe(struct spi_device *spi)
> {
> 	int ret;
> 	struct adis *st;
> 	struct iio_dev *indio_dev;
There is a convention (when nothing else is guiding ordering) of doing
things in reverse Christmas tree.

That means longest line first and shortest last.  Very minor point though.

> 
> 	/* setup the industrialio driver allocated elements */

Comment doesn't really add anything so drop it.

> 	indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
> 	if (!indio_dev)
> 		return -ENOMEM;
> 
> 	st = iio_priv(indio_dev);
> 	/* this is only used for removal purposes */

Again, comment isn't really that useful...

> 	spi_set_drvdata(spi, indio_dev);
> 
> 	indio_dev->name = spi->dev.driver->name;
> 	indio_dev->dev.parent = &spi->dev;
> 	indio_dev->info = &adis16201_info;
> 
> 	indio_dev->channels = adis16201_channels;
> 	indio_dev->num_channels = ARRAY_SIZE(adis16201_channels);
> 	indio_dev->modes = INDIO_DIRECT_MODE;
> 
> 	ret = adis_init(st, indio_dev, spi, &adis16201_data);
> 	if (ret)
> 		return ret;

A blank line here would help readability by separating these two
unconnected operations.

> 	ret = adis_setup_buffer_and_trigger(st, indio_dev, NULL);
> 	if (ret)
> 		return ret;
> 
> 	/* Get the device into a sane initial state */
> 	ret = adis_initial_startup(st);
> 	if (ret)
> 		goto error_cleanup_buffer_trigger;
> 
> 	ret = iio_device_register(indio_dev);
> 	if (ret < 0)
> 		goto error_cleanup_buffer_trigger;

Blank line here.

> 	return 0;
> 
> error_cleanup_buffer_trigger:
> 	adis_cleanup_buffer_and_trigger(st, indio_dev);

blank line here.

> 	return ret;
> }
> 
> static int adis16201_remove(struct spi_device *spi)
> {
> 	struct iio_dev *indio_dev = spi_get_drvdata(spi);
> 	struct adis *st = iio_priv(indio_dev);
> 
> 	iio_device_unregister(indio_dev);
> 	adis_cleanup_buffer_and_trigger(st, indio_dev);
> 
> 	return 0;
> }
> 
> static struct spi_driver adis16201_driver = {
> 	.driver = {
> 		.name = "adis16201",
> 	},
> 	.probe = adis16201_probe,
> 	.remove = adis16201_remove,
> };
> module_spi_driver(adis16201_driver);

Probably want to add devicetree bindings but not strictly necessary to
move out of staging...

> 
> MODULE_AUTHOR("Barry Song <21cnbao@xxxxxxxxx>");
> MODULE_DESCRIPTION("Analog Devices ADIS16201 Dual-Axis Digital Inclinometer and Accelerometer");
> MODULE_LICENSE("GPL v2");
> MODULE_ALIAS("spi:adis16201");
--
To unsubscribe from this list: send the line "unsubscribe linux-iio" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Input]     [Linux Kernel]     [Linux SCSI]     [X.org]

  Powered by Linux