Re: [PATCH] staging:iio:accel: Add driver for LIS331DLH

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

 



Hi Manuel,

How similar is this to those parts supported by the lis3 driver
in misc?  I have no particular problem with supporting this in staging
but we are probably going to have more issues making sure nothing is lost
if we attempt to merge it with that driver when outside staging.

There are also a number of previous drivers out there for this part
http://www.gossamer-threads.com/lists/linux/kernel/1300192

I have no idea if the lis3lv02d driver support this part currently or
not (which was the issue I raised with that previous driver).

Anyhow, we are already carrying the lis3l02dq driver mainly because I have
one on a board and it is handy for testing various things, so what's one
more.  Having said that we probably want to document this in Kconfig to
make it clear why this is there.

For that matter, I've not really looked at this with a view to merging the
support with that driver either!

Review, ignoring all those possible issues follows.

On 11/02/11 10:09, Manuel Stahl wrote:
> Signed-off-by: Manuel Stahl <manuel.stahl@xxxxxxxxxxxxxxxxx>
> ---
>  drivers/staging/iio/accel/Kconfig          |   24 ++
>  drivers/staging/iio/accel/Makefile         |    4 +
>  drivers/staging/iio/accel/lis331dlh.h      |  224 +++++++++++++++
>  drivers/staging/iio/accel/lis331dlh_core.c |  406 ++++++++++++++++++++++++++++
>  drivers/staging/iio/accel/lis331dlh_i2c.c  |  176 ++++++++++++
>  drivers/staging/iio/accel/lis331dlh_spi.c  |  231 ++++++++++++++++
>  6 files changed, 1065 insertions(+), 0 deletions(-)
>  create mode 100644 drivers/staging/iio/accel/lis331dlh.h
>  create mode 100644 drivers/staging/iio/accel/lis331dlh_core.c
>  create mode 100644 drivers/staging/iio/accel/lis331dlh_i2c.c
>  create mode 100644 drivers/staging/iio/accel/lis331dlh_spi.c
> 
> diff --git a/drivers/staging/iio/accel/Kconfig b/drivers/staging/iio/accel/Kconfig
> index 5ab7167..7473393 100644
> --- a/drivers/staging/iio/accel/Kconfig
> +++ b/drivers/staging/iio/accel/Kconfig
> @@ -94,6 +94,30 @@ config LIS3L02DQ_BUF_RING_SW
>  
>  endchoice
>  
> +config LIS331DLH
> +	bool "ST Microelectronics LIS331DLH Accelerometer Driver"
> +	help
> +	  Say yes here to build support for the ST microelectronics LIS331DLH
> +	  accelerometer. The driver supplies direct access via sysfs files.
> +
> +if LIS331DLH
> +
> +config LIS331DLH_SPI
> +	tristate "ST Microelectronics LIS331DLH Accelerometer Driver (SPI)"
> +	depends on SPI
> +	help
> +	  Say yes here to build SPI interface support for the
> +	  ST microelectronics LIS331DLH accelerometer.
> +
> +config LIS331DLH_I2C
> +	tristate "ST Microelectronics LIS331DLH Accelerometer Driver (I2C)"
> +	depends on I2C
> +	help
> +	  Say yes here to build I2C interface support for the
> +	  ST microelectronics LIS331DLH accelerometer.
> +
> +endif # LIS331DLH
> +
>  config SCA3000
>  	depends on IIO_BUFFER
>  	depends on SPI
> diff --git a/drivers/staging/iio/accel/Makefile b/drivers/staging/iio/accel/Makefile
> index 95c6666..22986b7 100644
> --- a/drivers/staging/iio/accel/Makefile
> +++ b/drivers/staging/iio/accel/Makefile
> @@ -27,6 +27,10 @@ obj-$(CONFIG_ADIS16240) += adis16240.o
>  
>  obj-$(CONFIG_KXSD9)	+= kxsd9.o
>  
> +lis331dlh-y		:= lis331dlh_core.o
> +obj-$(CONFIG_LIS331DLH_SPI)	+= lis331dlh.o lis331dlh_spi.o
> +obj-$(CONFIG_LIS331DLH_I2C)	+= lis331dlh.o lis331dlh_i2c.o
> +
>  lis3l02dq-y		:= lis3l02dq_core.o
>  lis3l02dq-$(CONFIG_IIO_BUFFER) += lis3l02dq_ring.o
>  obj-$(CONFIG_LIS3L02DQ)	+= lis3l02dq.o
> diff --git a/drivers/staging/iio/accel/lis331dlh.h b/drivers/staging/iio/accel/lis331dlh.h
> new file mode 100644
> index 0000000..1c5a39d
> --- /dev/null
> +++ b/drivers/staging/iio/accel/lis331dlh.h
> @@ -0,0 +1,224 @@
> +/*
> + * lis331dlh.h -- support STMicroelectronics LIS331DLH
> + *                3d 2g/4g/8g Linear Accelerometers
> + *
> + * Copyright (c) 2007 Jonathan Cameron <jic23@xxxxxxxxx>
> + * Copyright (c) 2011 Manuel Stahl <manuel.stahl@xxxxxxxxxxxxxxxxx>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +
> +#ifndef SPI_LIS331DLH_H_
> +#define SPI_LIS331DLH_H_
> +
> +#include <asm/byteorder.h>
This is unusual to see in here, so perhaps a comment saying why you
are messing with endianness?
> +
> +#define LIS331DLH "lis331dlh"
> +
> +#define LIS331DLH_READ_REG(a) ((a) | 0x80)
> +#define LIS331DLH_WRITE_REG(a) a
> +#define LIS331DLH_I2C_AUTO_INC(a) ((a) | 0x80)
> +
> +#define LIS331DLH_REG_WHO_AM_I_ADDR			0x0F
> +#define LIS331DLH_REG_WHO_AM_I_DEFAULT			0x32
> +
> +/* Control Register (1 of 5) */
> +#define LIS331DLH_REG_CTRL_1_ADDR			0x20
> +/* Power ctrl - either bit set corresponds to on */
> +#define LIS331DLH_REG_CTRL_1_POWER_OFF			0x00
> +#define LIS331DLH_REG_CTRL_1_POWER_ON			0x20
> +#define LIS331DLH_REG_CTRL_1_LOW_POWER_05		0x40
> +
> +/* Data Rate */
> +#define LIS331DLH_REG_CTRL_1_DR_50			0x00
> +#define LIS331DLH_REG_CTRL_1_DR_100			0x08
> +#define LIS331DLH_REG_CTRL_1_DR_400			0x10
> +#define LIS331DLH_REG_CTRL_1_DR_1000			0x18
> +#define LIS331DLH_REG_CTRL_1_DR_MASK			0x18
> +
> +/* Axes enable ctrls */
> +#define LIS331DLH_REG_CTRL_1_AXES_Z_ENABLE		0x04
> +#define LIS331DLH_REG_CTRL_1_AXES_Y_ENABLE		0x02
> +#define LIS331DLH_REG_CTRL_1_AXES_X_ENABLE		0x01
> +
> +/* Control Register (2 of 5) */
> +#define LIS331DLH_REG_CTRL_2_ADDR			0x21
> +
> +#define LIS331DLH_REG_CTRL_2_BOOT			0x80
> +
> +#define LIS331DLH_REG_CTRL_2_HP_NORM			0x00
> +#define LIS331DLH_REG_CTRL_2_HP_REF			0x40
> +
> +#define LIS331DLH_REG_CTRL_2_FDS_INT			0x10
> +#define LIS331DLH_REG_CTRL_2_HPEN2			0x08
> +#define LIS331DLH_REG_CTRL_2_HPEN1			0x04
> +#define LIS331DLH_REG_CTRL_2_HPCF1			0x02
> +#define LIS331DLH_REG_CTRL_2_HPCF0			0x01
> +
> +/* Control Register (3 of 5) */
> +#define LIS331DLH_REG_CTRL_3_ADDR			0x22
> +
> +#define LIS331DLH_REG_CTRL_3_IHL			0x80
> +#define LIS331DLH_REG_CTRL_3_PP_OD			0x40
> +#define LIS331DLH_REG_CTRL_3_LIR2			0x20
> +#define LIS331DLH_REG_CTRL_3_I2_I			0x00
> +#define LIS331DLH_REG_CTRL_3_I2_1OR2			0x08
> +#define LIS331DLH_REG_CTRL_3_I2_DRDY			0x10
> +#define LIS331DLH_REG_CTRL_3_I2_RUN			0x18
> +#define LIS331DLH_REG_CTRL_3_I2_MASK			0x18
> +#define LIS331DLH_REG_CTRL_3_LIR1			0x04
> +#define LIS331DLH_REG_CTRL_3_I1_I			0x00
> +#define LIS331DLH_REG_CTRL_3_I1_1OR2			0x01
> +#define LIS331DLH_REG_CTRL_3_I1_DRDY			0x02
> +#define LIS331DLH_REG_CTRL_3_I1_RUN			0x03
> +#define LIS331DLH_REG_CTRL_3_I1_MASK			0x03
> +
> +/* Control Register (4 of 5) */
> +#define LIS331DLH_REG_CTRL_4_ADDR			0x23
> +
> +/* Block Data Update only after MSB and LSB read */
> +#define LIS331DLH_REG_CTRL_4_BLOCK_UPDATE		0x80
> +
> +/* Set to big endian output */
> +#define LIS331DLH_REG_CTRL_4_BIG_ENDIAN			0x40
> +
> +/* Full scale selection */
> +#define LIS331DLH_REG_CTRL_4_FS_2G			0x00
> +#define LIS331DLH_REG_CTRL_4_FS_4G			0x10
> +#define LIS331DLH_REG_CTRL_4_FS_8G			0x30
> +#define LIS331DLH_REG_CTRL_4_FS_MASK			0x30
> +
> +/* Self Test Sign */
> +#define LIS331DLH_REG_CTRL_4_ST_SIGN			0x08
> +
> +/* Self Test Enable */
> +#define LIS331DLH_REG_CTRL_4_ST_ON			0x02
> +
> +/* SPI 3 wire mode */
> +#define LIS331DLH_REG_CTRL_4_THREE_WIRE_SPI_MODE	0x01
> +
> +/* Control Register (5 of 5) */
> +#define LIS331DLH_REG_CTRL_5_ADDR			0x24
> +
> +#define LIS331DLH_REG_CTRL_5_SLEEP_WAKE_OFF		0x00
> +#define LIS331DLH_REG_CTRL_5_LOW_POWER			0x03
> +
> +/* Dummy register */
> +#define LIS331DLH_REG_HP_FILTER_RESET			0x25
> +
> +/* Reference register */
> +#define LIS331DLH_REG_REFERENCE				0x26
> +
> +/* Status register */
> +#define LIS331DLH_REG_STATUS_ADDR			0x27
> +/* XYZ axis data overrun - first is all overrun? */
> +#define LIS331DLH_REG_STATUS_XYZ_OVERRUN		0x80
> +#define LIS331DLH_REG_STATUS_Z_OVERRUN			0x40
> +#define LIS331DLH_REG_STATUS_Y_OVERRUN			0x20
> +#define LIS331DLH_REG_STATUS_X_OVERRUN			0x10
> +/* XYZ new data available - first is all 3 available? */
> +#define LIS331DLH_REG_STATUS_XYZ_NEW_DATA		0x08
> +#define LIS331DLH_REG_STATUS_Z_NEW_DATA			0x04
> +#define LIS331DLH_REG_STATUS_Y_NEW_DATA			0x02
> +#define LIS331DLH_REG_STATUS_X_NEW_DATA			0x01
> +
> +/* The accelerometer readings - low and high bytes.
> +Form of high byte dependant on justification set in ctrl reg */
> +#define LIS331DLH_REG_OUT_X_L_ADDR			0x28
> +#define LIS331DLH_REG_OUT_X_H_ADDR			0x29
> +#define LIS331DLH_REG_OUT_Y_L_ADDR			0x2A
> +#define LIS331DLH_REG_OUT_Y_H_ADDR			0x2B
> +#define LIS331DLH_REG_OUT_Z_L_ADDR			0x2C
> +#define LIS331DLH_REG_OUT_Z_H_ADDR			0x2D
> +
> +/* Interrupt 1 config register */
> +#define LIS331DLH_REG_INT1_CFG_ADDR			0x30
> +#define LIS331DLH_REG_INT1_SRC_ADDR			0x31
> +#define LIS331DLH_REG_INT1_THS_ADDR			0x32
> +#define LIS331DLH_REG_INT1_DURATION_ADDR		0x33
> +
> +/* Interrupt 2 config register */
> +#define LIS331DLH_REG_INT2_CFG_ADDR			0x34
> +#define LIS331DLH_REG_INT2_SRC_ADDR			0x35
> +#define LIS331DLH_REG_INT2_THS_ADDR			0x36
> +#define LIS331DLH_REG_INT2_DURATION_ADDR		0x37
> +
> +#define LIS331DLH_REG_INT_AOI				0x80
> +#define LIS331DLH_REG_INT_6D				0x40
> +#define LIS331DLH_REG_INT_Z_HIGH			0x20
> +#define LIS331DLH_REG_INT_Z_LOW				0x10
> +#define LIS331DLH_REG_INT_Y_HIGH			0x08
> +#define LIS331DLH_REG_INT_Y_LOW				0x04
> +#define LIS331DLH_REG_INT_X_HIGH			0x02
> +#define LIS331DLH_REG_INT_X_LOW				0x01
> +
> +
> +/* Default control settings */
> +#define LIS331DLH_DEFAULT_CTRL1  (LIS331DLH_REG_CTRL_1_POWER_ON	     \
> +				| LIS331DLH_REG_CTRL_1_DR_50         \
> +				| LIS331DLH_REG_CTRL_1_AXES_Z_ENABLE \
> +				| LIS331DLH_REG_CTRL_1_AXES_Y_ENABLE \
> +				| LIS331DLH_REG_CTRL_1_AXES_X_ENABLE)
> +
> +#define LIS331DLH_DEFAULT_CTRL2	 LIS331DLH_REG_CTRL_2_HP_NORM
> +
> +#define LIS331DLH_DEFAULT_CTRL3  LIS331DLH_REG_CTRL_3_I1_DRDY
> +
> +#ifdef __BIG_ENDIAN
> +#define LIS331DLH_REG_CTRL_4_CPU_ENDIAN LIS331DLH_REG_CTRL_4_BIG_ENDIAN
> +#else
> +#define LIS331DLH_REG_CTRL_4_CPU_ENDIAN 0
> +#endif
> +
> +#define LIS331DLH_DEFAULT_CTRL4  (LIS331DLH_REG_CTRL_4_BLOCK_UPDATE \
> +				| LIS331DLH_REG_CTRL_4_CPU_ENDIAN \
> +				| LIS331DLH_REG_CTRL_4_FS_2G)
> +
> +#define LIS331DLH_MAX_TX 12
> +#define LIS331DLH_MAX_RX 12
> +
> +static const u8 read_all_tx_array[] = {
> +	LIS331DLH_READ_REG(LIS331DLH_REG_OUT_X_L_ADDR), 0,
> +	LIS331DLH_READ_REG(LIS331DLH_REG_OUT_X_H_ADDR), 0,
> +	LIS331DLH_READ_REG(LIS331DLH_REG_OUT_Y_L_ADDR), 0,
> +	LIS331DLH_READ_REG(LIS331DLH_REG_OUT_Y_H_ADDR), 0,
> +	LIS331DLH_READ_REG(LIS331DLH_REG_OUT_Z_L_ADDR), 0,
> +	LIS331DLH_READ_REG(LIS331DLH_REG_OUT_Z_H_ADDR), 0,
> +};
> +
> +/**
> + * struct lis331dlh_state - device instance specific data
> + * @us:			pointer to actual bus (spi/i2c)
> + * @buf_lock:		mutex to protect tx and rx
> + * @tx:			transmit buffer
> + * @rx:			receive buffer (16bit aligned)
> + * @read/write		bus specific functions to access registers
> + **/
> +struct lis331dlh_state {
> +	void			*us;
> +	struct mutex		buf_lock;
> +
> +	u8	tx[LIS331DLH_MAX_TX] ____cacheline_aligned;
> +	u16	rx[LIS331DLH_MAX_RX/2] ____cacheline_aligned;
> +
Break this out into a const ops structure so that the two implementations
can assign it as one pointer.
> +	int (*read_reg_8)  (struct lis331dlh_state *st, u8 addr, u8 *val);
> +	int (*write_reg_8) (struct lis331dlh_state *st, u8 addr, u8 val);
> +	int (*read_reg_16) (struct lis331dlh_state *st, u8 low_addr, u16 *val);
> +	int (*read_all) (struct lis331dlh_state *st, u8 *rx_array);
> +};
> +
> +int lis331dlh_probe(struct iio_dev *indio_dev, struct device *dev);
> +int lis331dlh_remove(struct iio_dev *indio_dev);
> +
> +int lis331dlh_stop_device(struct iio_dev *indio_dev);
> +
> +enum LIS331DLH_CHANNELS {
> +	LIS331DLH_ACCEL_X,
> +	LIS331DLH_ACCEL_Y,
> +	LIS331DLH_ACCEL_Z,
> +	LIS331DLH_TIMESTAMP,
> +};
> +
> +#endif /* SPI_LIS331DLH_H_ */
> diff --git a/drivers/staging/iio/accel/lis331dlh_core.c b/drivers/staging/iio/accel/lis331dlh_core.c
> new file mode 100644
> index 0000000..0d06f20
> --- /dev/null
> +++ b/drivers/staging/iio/accel/lis331dlh_core.c
> @@ -0,0 +1,406 @@
> +/*
> + * lis331dlh_core.c	support STMicroelectronics LIS331DLH
> + *			3d 2g/4g/8g Linear Accelerometers
> + *
> + * Copyright (c) 2007 Jonathan Cameron <jic23@xxxxxxxxx>
> + * Copyright (c) 2011 Manuel Stahl <manuel.stahl@xxxxxxxxxxxxxxxxx>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +
> +#include <linux/mutex.h>
> +#include <linux/device.h>
> +#include <linux/kernel.h>
> +#include <linux/sysfs.h>
> +#include <linux/module.h>
> +
> +#include "../iio.h"
> +#include "../sysfs.h"
> +#include "../events.h"
> +
> +#include "lis331dlh.h"
> +
> +static int lis331dlh_read_raw(struct iio_dev *indio_dev,
> +			      struct iio_chan_spec const *chan,
> +			      int *val,
> +			      int *val2,
> +			      long mask)
> +{
> +	struct lis331dlh_state *st = iio_priv(indio_dev);
> +	s16 value;
> +	u8 utemp;
> +	ssize_t ret = 0;
> +	u8 reg;
> +
> +	switch (mask) {
> +	case 0:
> +		/* Take the iio_dev status lock */
> +		mutex_lock(&indio_dev->mlock);
> +		ret = st->read_reg_16(st, chan->address, (u16 *)&value);
> +		*val = value;
> +		mutex_unlock(&indio_dev->mlock);
> +		return ret ? ret : IIO_VAL_INT;
> +	case IIO_CHAN_INFO_SCALE:
> +		ret = st->read_reg_8(st, LIS331DLH_REG_CTRL_4_ADDR, &utemp);
> +		if (ret)
> +			return ret;
> +
> +		utemp &= LIS331DLH_REG_CTRL_4_FS_MASK;
> +		switch (utemp) {
> +		case LIS331DLH_REG_CTRL_4_FS_2G:
> +			*val2 = 569;
> +			break;
> +		case LIS331DLH_REG_CTRL_4_FS_4G:
> +			*val2 = 1135;
> +			break;
> +		case LIS331DLH_REG_CTRL_4_FS_8G:
> +			*val2 = 2270;
> +			break;
> +		default:
> +			*val2 = 0;
> +			break;
> +		}
> +		*val = 0;
> +		return IIO_VAL_INT_PLUS_MICRO;
> +	}
> +	return -1;
> +}
> +
> +static ssize_t lis331dlh_read_frequency(struct device *dev,
> +					struct device_attribute *attr,
> +					char *buf)
> +{
> +	struct iio_dev *indio_dev = dev_get_drvdata(dev);
> +	struct lis331dlh_state *st = iio_priv(indio_dev);
> +	int ret, len = 0;
> +	u8 val;
> +
> +	ret = st->read_reg_8(st, LIS331DLH_REG_CTRL_1_ADDR, &val);
> +	if (ret)
> +		return ret;
> +
> +	val &= LIS331DLH_REG_CTRL_1_DR_MASK;
> +	switch (val) {
> +	case LIS331DLH_REG_CTRL_1_DR_50:
> +		len = sprintf(buf, "50\n");
> +		break;
> +	case LIS331DLH_REG_CTRL_1_DR_100:
> +		len = sprintf(buf, "100\n");
> +		break;
> +	case LIS331DLH_REG_CTRL_1_DR_400:
> +		len = sprintf(buf, "400\n");
> +		break;
> +	case LIS331DLH_REG_CTRL_1_DR_1000:
> +		len = sprintf(buf, "1000\n");
> +		break;
> +	}
> +	return len;
> +}
> +
> +static ssize_t lis331dlh_write_frequency(struct device *dev,
> +					 struct device_attribute *attr,
> +					 const char *buf,
> +					 size_t len)
> +{
> +	struct iio_dev *indio_dev = dev_get_drvdata(dev);
> +	struct lis331dlh_state *st = iio_priv(indio_dev);
> +	unsigned long freq;
> +	int ret;
> +	u8 val;
> +
> +	ret = strict_strtoul(buf, 10, &freq);
> +	if (ret)
> +		return ret;
> +
> +	mutex_lock(&indio_dev->mlock);
> +	ret = st->read_reg_8(st, LIS331DLH_REG_CTRL_1_ADDR, &val);
> +	if (ret)
> +		goto error_ret_mutex;
> +
> +	val &= ~LIS331DLH_REG_CTRL_1_DR_MASK;
> +	switch (freq) {
> +	case 50:
> +		val |= LIS331DLH_REG_CTRL_1_DR_50;
> +		break;
> +	case 100:
> +		val |= LIS331DLH_REG_CTRL_1_DR_100;
> +		break;
> +	case 400:
> +		val |= LIS331DLH_REG_CTRL_1_DR_400;
> +		break;
> +	case 1000:
> +		val |= LIS331DLH_REG_CTRL_1_DR_1000;
> +		break;
> +	default:
> +		ret = -EINVAL;
> +		goto error_ret_mutex;
> +	};
> +
> +	ret = st->write_reg_8(st, LIS331DLH_REG_CTRL_1_ADDR, val);
> +
> +error_ret_mutex:
> +	mutex_unlock(&indio_dev->mlock);
> +
> +	return ret ? ret : len;
> +}
> +
> +static ssize_t lis331dlh_read_range(struct device *dev,
> +				    struct device_attribute *attr,
> +				    char *buf)
> +{
> +	struct iio_dev *indio_dev = dev_get_drvdata(dev);
> +	struct lis331dlh_state *st = iio_priv(indio_dev);
> +	int ret, len = 0;
> +	u8 val;
> +
> +	ret = st->read_reg_8(st, LIS331DLH_REG_CTRL_4_ADDR, &val);
> +	if (ret)
> +		return ret;
> +
> +	val &= LIS331DLH_REG_CTRL_4_FS_MASK;
> +	switch (val) {
> +	case LIS331DLH_REG_CTRL_4_FS_2G:
> +		len = sprintf(buf, "2G\n");
We have accelerometers units as m/s^2 not G, so please adjust these
to match and verify the rest are correct as well.
> +		break;
> +	case LIS331DLH_REG_CTRL_4_FS_4G:
> +		len = sprintf(buf, "4G\n");
> +		break;
> +	case LIS331DLH_REG_CTRL_4_FS_8G:
> +		len = sprintf(buf, "8G\n");
> +		break;
> +	}
> +	return len;
> +}
> +
> +static ssize_t lis331dlh_write_range(struct device *dev,
> +				     struct device_attribute *attr,
> +				     const char *buf,
> +				     size_t len)
> +{
> +	struct iio_dev *indio_dev = dev_get_drvdata(dev);
> +	struct lis331dlh_state *st = iio_priv(indio_dev);
> +	unsigned long freq;
> +	int ret;
> +	u8 val;
> +
> +	ret = strict_strtoul(buf, 10, &freq);
> +	if (ret)
> +		return ret;
> +
> +	mutex_lock(&indio_dev->mlock);
> +	ret = st->read_reg_8(st, LIS331DLH_REG_CTRL_4_ADDR, &val);
> +	if (ret)
> +		goto error_ret_mutex;
> +
> +	val &= ~LIS331DLH_REG_CTRL_4_FS_MASK;
> +	switch (freq) {
> +	case 2:
> +		val |= LIS331DLH_REG_CTRL_4_FS_2G;
> +		break;
> +	case 4:
> +		val |= LIS331DLH_REG_CTRL_4_FS_4G;
> +		break;
> +	case 8:
> +		val |= LIS331DLH_REG_CTRL_4_FS_8G;
> +		break;
> +	default:
> +		ret = -EINVAL;
> +		goto error_ret_mutex;
> +	};
> +
> +	ret = st->write_reg_8(st, LIS331DLH_REG_CTRL_4_ADDR, val);
> +
> +error_ret_mutex:
> +	mutex_unlock(&indio_dev->mlock);
> +
> +	return ret ? ret : len;
> +}
> +
> +static int lis331dlh_init(struct iio_dev *indio_dev)
> +{
> +	struct lis331dlh_state *st = iio_priv(indio_dev);
> +	int ret;
> +	u8 val;
> +
> +	ret = st->read_reg_8(st, LIS331DLH_REG_WHO_AM_I_ADDR, &val);
> +	if (ret || (val != LIS331DLH_REG_WHO_AM_I_DEFAULT)) {
> +		dev_err(indio_dev->dev.parent,
> +			"reading WHO_AM_I register failed: 0x%02X", val);
> +		goto err_ret;
> +	}
> +
> +	/* Write suitable defaults to ctrl1 */
> +	ret = st->write_reg_8(st, LIS331DLH_REG_CTRL_1_ADDR,
> +				  LIS331DLH_DEFAULT_CTRL1);
> +	if (ret) {
> +		dev_err(indio_dev->dev.parent,
> +				"problem with setup control register 1");
> +		goto err_ret;
> +	}
> +
> +	/* Write suitable defaults to ctrl2 */
> +	ret = st->write_reg_8(st, LIS331DLH_REG_CTRL_2_ADDR,
> +				  LIS331DLH_DEFAULT_CTRL2);
> +	if (ret) {
> +		dev_err(indio_dev->dev.parent,
> +				"problem with setup control register 2");
> +		goto err_ret;
> +	}
> +
> +	/* Write suitable defaults to ctrl3 */
> +	ret = st->write_reg_8(st, LIS331DLH_REG_CTRL_3_ADDR,
> +				  LIS331DLH_DEFAULT_CTRL3);
> +	if (ret) {
> +		dev_err(indio_dev->dev.parent,
> +				"problem with setup control register 3");
> +		goto err_ret;
> +	}
> +
> +	/* Write suitable defaults to ctrl4 */
> +	ret = st->write_reg_8(st, LIS331DLH_REG_CTRL_4_ADDR,
> +				  LIS331DLH_DEFAULT_CTRL4);
> +	if (ret) {
> +		dev_err(indio_dev->dev.parent,
> +				"problem with setup control register 4");
> +		goto err_ret;
> +	}
> +
> +	/* Read int1 src reg to clear pending irqs */
> +	ret = st->read_reg_8(st, LIS331DLH_REG_INT1_SRC_ADDR, &val);
> +	if (ret) {
> +		dev_err(indio_dev->dev.parent,
> +				"problem with int1 src register");
> +		goto err_ret;
> +	}
> +
> +	/* Read int2 src reg to clear pending irqs */
> +	ret = st->read_reg_8(st, LIS331DLH_REG_INT2_SRC_ADDR, &val);
> +	if (ret) {
> +		dev_err(indio_dev->dev.parent,
> +				"problem with int2 src register");
> +		goto err_ret;
> +	}
> +
> +err_ret:
> +	return ret;
> +}
> +
> +static IIO_DEV_ATTR_SAMP_FREQ(S_IWUSR | S_IRUGO,
> +			      lis331dlh_read_frequency,
> +			      lis331dlh_write_frequency);
> +
> +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("50 100 400 1000");
> +
Not abi compliant. Should be in_accel_range and for that matter
we should add range to the iio_chan_info_enum as it seems to be
way more popular than I'd like... 

> +static IIO_DEVICE_ATTR(range, S_IWUSR | S_IRUGO,
> +		       lis331dlh_read_range,
> +		       lis331dlh_write_range, 0);
> +
> +#define LIS331DLH_EVENT_MASK					\
> +	(IIO_EV_BIT(IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING) |	\
> +	 IIO_EV_BIT(IIO_EV_TYPE_THRESH, IIO_EV_DIR_FALLING))
> +
> +#define LIS331DLH_ACCEL_CHAN_SPEC(_mod, _si, _addr)		\
> +	{ .type = IIO_ACCEL,					\
> +	  .modified = 1,					\
> +	  .channel2 = _mod,					\
> +	  .address = _addr,					\
> +	  .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT,		\
> +	  .scan_index = _si,					\
> +	  .scan_type = IIO_ST('s', 16, 16, 0),			\
> +	  .event_mask = LIS331DLH_EVENT_MASK,			\
> +	}
> +
> +static struct iio_chan_spec lis331dlh_channels[] = {
> +	LIS331DLH_ACCEL_CHAN_SPEC(IIO_MOD_X, LIS331DLH_ACCEL_X,
> +				  LIS331DLH_REG_OUT_X_L_ADDR),
> +	LIS331DLH_ACCEL_CHAN_SPEC(IIO_MOD_Y, LIS331DLH_ACCEL_Y,
> +				  LIS331DLH_REG_OUT_Y_L_ADDR),
> +	LIS331DLH_ACCEL_CHAN_SPEC(IIO_MOD_Z, LIS331DLH_ACCEL_Z,
> +				  LIS331DLH_REG_OUT_Z_L_ADDR),
> +	IIO_CHAN_SOFT_TIMESTAMP(LIS331DLH_TIMESTAMP)
> +};
> +
> +static struct attribute *lis331dlh_attributes[] = {
> +	&iio_dev_attr_sampling_frequency.dev_attr.attr,
> +	&iio_const_attr_sampling_frequency_available.dev_attr.attr,
> +	&iio_dev_attr_range.dev_attr.attr,
> +	NULL
> +};
> +
> +static const struct attribute_group lis331dlh_attribute_group = {
> +	.attrs = lis331dlh_attributes,
> +};
> +
> +static const struct iio_info lis331dlh_info = {
> +	.read_raw = &lis331dlh_read_raw,
> +	.driver_module = THIS_MODULE,
> +	.attrs = &lis331dlh_attribute_group,
> +};
> +
> +int lis331dlh_probe(struct iio_dev *indio_dev, struct device *dev)
> +{
> +	struct lis331dlh_state *st = iio_priv(indio_dev);
> +	int ret;
> +
> +	mutex_init(&st->buf_lock);
> +
> +	indio_dev->name = LIS331DLH;
> +	indio_dev->dev.parent = dev;
> +	indio_dev->info = &lis331dlh_info;
> +	indio_dev->channels = lis331dlh_channels;
> +	indio_dev->num_channels = ARRAY_SIZE(lis331dlh_channels);
> +	indio_dev->modes = INDIO_DIRECT_MODE;
> +
> +	/* Get the device into a sane initial state */
> +	ret = lis331dlh_init(indio_dev);
> +	if (ret)
> +		return ret;
> +
> +	ret = iio_device_register(indio_dev);
> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +EXPORT_SYMBOL_GPL(lis331dlh_probe);
> +
> +/* Power down the device */
> +int lis331dlh_power_down(struct iio_dev *indio_dev)
> +{
> +	struct lis331dlh_state *st = iio_priv(indio_dev);
> +	int ret;
> +
> +	mutex_lock(&indio_dev->mlock);
> +	ret = st->write_reg_8(st, LIS331DLH_REG_CTRL_1_ADDR,
> +				  LIS331DLH_REG_CTRL_1_POWER_OFF);
> +	if (ret) {
> +		dev_err(&indio_dev->dev, "problem with turning device off");
> +		goto err_ret;
> +	}
> +
> +err_ret:
> +	mutex_unlock(&indio_dev->mlock);
> +	return ret;
> +}
> +EXPORT_SYMBOL_GPL(lis331dlh_power_down);
> +
> +int lis331dlh_remove(struct iio_dev *indio_dev)
> +{
> +	int ret;
> +
> +	ret = lis331dlh_power_down(indio_dev);
> +	if (ret)
> +		return ret;
> +
> +	iio_device_unregister(indio_dev);
> +	iio_free_device(indio_dev);
> +
> +	return 0;
> +}
> +EXPORT_SYMBOL_GPL(lis331dlh_remove);
> +
> +MODULE_AUTHOR("Manuel Stahl <manuel.stahl@xxxxxxxxxxxxxxxxx>");
> +MODULE_DESCRIPTION("ST LIS331DLH Accelerometer driver");
> +MODULE_LICENSE("GPL v2");
> diff --git a/drivers/staging/iio/accel/lis331dlh_i2c.c b/drivers/staging/iio/accel/lis331dlh_i2c.c
> new file mode 100644
> index 0000000..35c9c98
> --- /dev/null
> +++ b/drivers/staging/iio/accel/lis331dlh_i2c.c
> @@ -0,0 +1,176 @@
> +/*
> + * lis331dlh_i2c.c	support STMicroelectronics LISD02DQ
> + *			3d 2g/4g/8g Linear Accelerometers
> + *
> + * Copyright (c) 2007 Jonathan Cameron <jic23@xxxxxxxxx>
> + * Copyright (c) 2011 Manuel Stahl <manuel.stahl@xxxxxxxxxxxxxxxxx>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +
> +#include <linux/i2c.h>
> +
> +#include "../iio.h"
> +
> +#include "lis331dlh.h"
> +
> +static int lis331dlh_i2c_read_reg_8(struct lis331dlh_state *st,
> +				    u8 reg_address, u8 *val)
> +{
> +	struct i2c_client *client = st->us;
> +	int ret;
> +
> +	struct i2c_msg msg[] = { {
> +		.addr = client->addr,
> +		.flags = client->flags & I2C_M_TEN,
> +		.len = 1,
> +		.buf = &reg_address,
> +	}, {
> +		.addr = client->addr,
> +		.flags = (client->flags & I2C_M_TEN) | I2C_M_RD,
> +		.len = 1,
> +		.buf = val,
> +	} };
> +
> +	reg_address = LIS331DLH_WRITE_REG(reg_address);
> +	ret = i2c_transfer(client->adapter, msg, 2);
> +	return (ret == 2) ? 0 : ret;
> +}
> +
> +static int lis331dlh_i2c_write_reg_8(struct lis331dlh_state *st,
> +				     u8 reg_address, u8 val)
> +{
> +	struct i2c_client *client = st->us;
> +	u8 buf[2];
> +	int ret;
> +
> +	buf[0] = reg_address;
> +	buf[1] = val;
use u8 buf[2] = { reg_address, val };
to keep this compact. Also, I'd make only negative values errors then
you can avoid the messing around with the return value and shorten
this somewhat.  Also, thinking about it, this looks like an smbus
command, so even better to use the relevant one of those if possible?
(I guess they might not play with the 10 bit address). Is this really
a device that uses that?
> +
> +	ret = i2c_master_send(client, buf, sizeof(buf));
> +	return (ret == sizeof(buf)) ? 0 : ret;
> +}
> +
> +static int lis331dlh_i2c_read_reg_16(struct lis331dlh_state *st,
> +				     u8 lower_reg_address, u16 *val)
> +{
> +	struct i2c_client *client = st->us;
> +	int ret;
> +
> +	struct i2c_msg msg[] = { {
> +		.addr = client->addr,
> +		.flags = client->flags & I2C_M_TEN,
> +		.len = 1,
> +		.buf = &lower_reg_address,
> +	}, {
> +		.addr = client->addr,
> +		.flags = (client->flags & I2C_M_TEN) | I2C_M_RD,
> +		.len = 2,
> +		.buf = (u8 *)val,
> +	} };
> +
> +	lower_reg_address = LIS331DLH_I2C_AUTO_INC(lower_reg_address);
> +	ret = i2c_transfer(client->adapter, msg, 2);
> +	le16_to_cpus(val);
> +	return (ret == 2) ? 0 : ret;
> +}
> +
> +static int lis331dlh_i2c_read_all(struct lis331dlh_state *st, u8 *rx_array)
> +{
> +	struct i2c_client *client = st->us;
> +	int ret;
> +	u8 reg = LIS331DLH_I2C_AUTO_INC(LIS331DLH_REG_OUT_X_L_ADDR);
> +
> +	struct i2c_msg msg[] = { {
> +		.addr = client->addr,
> +		.flags = client->flags & I2C_M_TEN,
> +		.len = 1,
> +		.buf = &reg,
> +	}, {
> +		.addr = client->addr,
> +		.flags = (client->flags & I2C_M_TEN) | I2C_M_RD,
> +		.len = 6,
> +		.buf = rx_array,
> +	} };
> +
> +	ret = i2c_transfer(client->adapter, msg, 2);
> +	if (ret != 2)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int lis331dlh_i2c_probe(struct i2c_client *i2c,
> +			       const struct i2c_device_id *id)
> +{
> +	int ret;
> +	struct lis331dlh_state *st;
> +	struct iio_dev *indio_dev;
> +
> +	indio_dev = iio_allocate_device(sizeof *st);
> +	if (!indio_dev)
> +		return -ENOMEM;
> +
> +	st = iio_priv(indio_dev);
> +	st->read_reg_8  = lis331dlh_i2c_read_reg_8;
> +	st->write_reg_8 = lis331dlh_i2c_write_reg_8;
> +	st->read_reg_16 = lis331dlh_i2c_read_reg_16;
> +	st->read_all	= lis331dlh_i2c_read_all;
> +
> +	st->us = i2c;
> +
> +	/* this is only used for removal purposes */
> +	i2c_set_clientdata(i2c, indio_dev);
> +
> +	ret = lis331dlh_probe(indio_dev, &i2c->dev);
> +	if (ret)
> +		goto error_free_dev;
> +
> +	return 0;
> +
> +error_free_dev:
> +	iio_free_device(indio_dev);
> +	return ret;
> +}
> +
> +static int lis331dlh_i2c_remove(struct i2c_client *i2c)
> +{
> +	struct iio_dev *indio_dev = i2c_get_clientdata(i2c);
> +	return lis331dlh_remove(indio_dev);
> +}
> +
> +static const struct i2c_device_id lis331dlh_id[] = {
> +	{"lis331dlh", 0x19 },
> +	{}
> +};
> +
> +MODULE_DEVICE_TABLE(i2c, lis331dlh_id);
> +
> +static struct i2c_driver lis331dlh_i2c_driver = {
> +	.driver	 = {
> +		.name   = "lis331dlh_i2c",
> +		.owner  = THIS_MODULE,
> +	},
> +	.probe	= lis331dlh_i2c_probe,
> +	.remove	= __devexit_p(lis331dlh_i2c_remove),
> +	.id_table = lis331dlh_id,
> +};
> +
> +static int __init lis331dlh_i2c_module_init(void)
> +{
> +	return i2c_add_driver(&lis331dlh_i2c_driver);
> +}
> +
> +static void __exit lis331dlh_i2c_module_exit(void)
> +{
> +	i2c_del_driver(&lis331dlh_i2c_driver);
> +}
> +
> +MODULE_AUTHOR("Manuel Stahl <manuel.stahl@xxxxxxxxxxxxxxxxx>");
> +MODULE_DESCRIPTION("ST LIS331DLH Accelerometer I2C driver");
> +MODULE_LICENSE("GPL v2");
> +
> +module_init(lis331dlh_i2c_module_init);
> +module_exit(lis331dlh_i2c_module_exit);
> diff --git a/drivers/staging/iio/accel/lis331dlh_spi.c b/drivers/staging/iio/accel/lis331dlh_spi.c
> new file mode 100644
> index 0000000..0aee730
> --- /dev/null
> +++ b/drivers/staging/iio/accel/lis331dlh_spi.c
> @@ -0,0 +1,231 @@
> +/*
> + * lis331dlh_spi.c	support STMicroelectronics LISD02DQ
> + *			3d 2g/4g/8g Linear Accelerometers via SPI
> + *
> + * Copyright (c) 2007 Jonathan Cameron <jic23@xxxxxxxxx>
> + * Copyright (c) 2011 Manuel Stahl <manuel.stahl@xxxxxxxxxxxxxxxxx>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +
> +#include <linux/spi/spi.h>
> +
> +#include "../iio.h"
> +
> +#include "lis331dlh.h"
> +
> +/* At the moment the spi framework doesn't allow global setting of cs_change.
> + * It's in the likely to be added comment at the top of spi.h.
> + * This means that use cannot be made of spi_write etc.
> + */
> +
> +/**
> + * lis331dlh_spi_read_reg_8() - read single byte from a single register
> + * @dev: device asosciated with child of actual device (iio_dev or iio_trig)
> + * @reg_address: the address of the register to be read
> + * @val: pass back the resulting value
> + **/
> +static int lis331dlh_spi_read_reg_8(struct lis331dlh_state *st,
> +				   u8 reg_address, u8 *val)
> +{
> +	struct spi_device *spi = st->us;
> +	struct spi_message msg;
> +	int ret;
> +
> +	struct spi_transfer xfer = {
> +		.tx_buf = st->tx,
> +		.rx_buf = st->rx,
> +		.bits_per_word = 8,
> +		.len = 2,
> +		.cs_change = 1,
Really, hold cs high between reads?  Surely doesn't do any
harm to not do so?
> +	};
> +
> +	mutex_lock(&st->buf_lock);
> +	st->tx[0] = LIS331DLH_READ_REG(reg_address);
> +	st->tx[1] = 0;
> +
> +	spi_message_init(&msg);
> +	spi_message_add_tail(&xfer, &msg);
> +	ret = spi_sync(spi, &msg);
> +	*val = st->rx[1];
> +	mutex_unlock(&st->buf_lock);
> +
> +	return ret;
> +}
> +
> +/**
> + * lis331dlh_spi_write_reg_8() - write single byte to a register
> + * @dev: device associated with child of actual device (iio_dev or iio_trig)
> + * @reg_address: the address of the register to be writen
> + * @val: the value to write
> + **/
> +static int lis331dlh_spi_write_reg_8(struct lis331dlh_state *st,
> +				     u8 reg_address, u8 val)
> +{
> +	int ret;
> +	struct spi_message msg;
> +	struct spi_device *spi = st->us;
> +	struct spi_transfer xfer = {
> +		.tx_buf = st->tx,
> +		.bits_per_word = 8,
> +		.len = 2,
> +		.cs_change = 1,
> +	};
> +
> +	mutex_lock(&st->buf_lock);
> +	st->tx[0] = LIS331DLH_WRITE_REG(reg_address);
> +	st->tx[1] = val;
> +
> +	spi_message_init(&msg);
> +	spi_message_add_tail(&xfer, &msg);
> +	ret = spi_sync(spi, &msg);
> +	mutex_unlock(&st->buf_lock);
> +
> +	return ret;
> +}
> +
> +/**
> + * lisl302dq_spi_read_reg_16() - read 2 bytes from a pair of registers
> + * @dev: device associated with child of actual device (iio_dev or iio_trig)
> + * @reg_address: the address of the lower of the two registers. Second register
> + *               is assumed to have address one greater.
> + * @val: somewhere to pass back the value read
> + **/
> +static int lis331dlh_spi_read_reg_16(struct lis331dlh_state *st,
> +				     u8 lower_reg_address,
> +				     u16 *val)
> +{
> +	struct spi_message msg;
> +	struct spi_device *spi = st->us;
> +	int ret;
> +	struct spi_transfer xfers[] = { {
> +			.tx_buf = st->tx,
> +			.rx_buf = st->rx,
> +			.bits_per_word = 8,
> +			.len = 2,
> +			.cs_change = 1,
> +		}, {
> +			.tx_buf = st->tx + 2,
> +			.rx_buf = st->rx + 2,
> +			.bits_per_word = 8,
> +			.len = 2,
> +			.cs_change = 1,
> +
weird white space.
> +		},
> +	};
> +
> +	mutex_lock(&st->buf_lock);
> +	st->tx[0] = LIS331DLH_READ_REG(lower_reg_address);
> +	st->tx[1] = 0;
> +	st->tx[2] = LIS331DLH_READ_REG(lower_reg_address+1);
> +	st->tx[3] = 0;
> +
> +	spi_message_init(&msg);
> +	spi_message_add_tail(&xfers[0], &msg);
> +	spi_message_add_tail(&xfers[1], &msg);
> +	ret = spi_sync(spi, &msg);
> +	if (ret) {
> +		dev_err(&spi->dev, "problem when reading 16 bit register");
> +		goto error_ret;
> +	}
> +	*val = (u16)(st->rx[1]) | ((u16)(st->rx[3]) << 8);
> +
> +error_ret:
> +	mutex_unlock(&st->buf_lock);
> +	return ret;
> +}
> +
> +static int lis331dlh_spi_read_all(struct lis331dlh_state *st, u8 *rx_array)
> +{
> +	struct spi_transfer xfers[2];
> +	struct spi_message msg;
> +	struct spi_device *spi = st->us;
> +
> +	st->tx[0] = read_all_tx_array[0];
> +	xfers[0].tx_buf = st->tx;
> +	xfers[0].rx_buf = NULL;
> +	xfers[0].bits_per_word = 8;
> +	xfers[0].len = 1;
> +	xfers[0].cs_change = 1;
> +
> +	xfers[1].tx_buf = NULL;
> +	xfers[1].rx_buf = rx_array;
> +	xfers[1].bits_per_word = 8;
> +	xfers[1].len = LIS331DLH_SCAN_ELEMENTS * 2;
> +	xfers[0].cs_change = 1;
> +
> +	spi_message_init(&msg);
> +	spi_message_add_tail(&xfers[0], &msg);
> +	spi_message_add_tail(&xfers[1], &msg);
> +
> +	return spi_sync(spi, &msg);
> +}
> +
> +static int __devinit lis331dlh_spi_probe(struct spi_device *spi)
> +{
> +	int ret;
> +	struct lis331dlh_state *st;
> +	struct iio_dev *indio_dev;
> +
> +	indio_dev = iio_allocate_device(sizeof *st);
> +	if (!indio_dev)
> +		return -ENOMEM;
> +
> +	st = iio_priv(indio_dev);
> +	st->read_reg_8  = lis331dlh_spi_read_reg_8;
> +	st->write_reg_8 = lis331dlh_spi_write_reg_8;
> +	st->read_reg_16 = lis331dlh_spi_read_reg_16;
> +	st->read_all	= lis331dlh_spi_read_all;
> +
> +	st->us = spi;
> +
> +	spi->mode = SPI_MODE_3;
> +	spi_setup(spi);
> +
> +	/* this is only used tor removal purposes */
> +	spi_set_drvdata(spi, indio_dev);
> +
> +	ret = lis331dlh_probe(indio_dev, &spi->dev);
> +	if (ret)
> +		goto error_free_st;
> +
> +	return 0;
> +
> +error_free_st:
> +	kfree(st);
> +	return ret;
> +}
> +
> +static int lis331dlh_spi_remove(struct spi_device *spi)
> +{
> +	struct iio_dev *indio_dev = spi_get_drvdata(spi);
> +	return lis331dlh_remove(indio_dev);
> +}
> +
> +static struct spi_driver lis331dlh_spi_driver = {
> +	.driver = {
> +		.name = "lis331dlh_spi",
> +		.owner = THIS_MODULE,
> +	},
> +	.probe = lis331dlh_spi_probe,
> +	.remove = __devexit_p(lis331dlh_spi_remove),
> +};
> +
> +static __init int lis331dlh_spi_module_init(void)
> +{
> +	return spi_register_driver(&lis331dlh_spi_driver);
> +}
> +
> +static __exit void lis331dlh_spi_module_exit(void)
> +{
> +	spi_unregister_driver(&lis331dlh_spi_driver);
> +}
> +
> +MODULE_AUTHOR("Manuel Stahl <manuel.stahl@xxxxxxxxxxxxxxxxx>");
> +MODULE_DESCRIPTION("ST LIS331DLH Accelerometer SPI driver");
> +MODULE_LICENSE("GPL v2");
> +
> +module_init(lis331dlh_spi_module_init);
> +module_exit(lis331dlh_spi_module_exit);

--
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