diff options
Diffstat (limited to 'drivers/iio/accel')
| -rw-r--r-- | drivers/iio/accel/Kconfig | 80 | ||||
| -rw-r--r-- | drivers/iio/accel/Makefile | 16 | ||||
| -rw-r--r-- | drivers/iio/accel/bma180.c | 680 | ||||
| -rw-r--r-- | drivers/iio/accel/hid-sensor-accel-3d.c | 431 | ||||
| -rw-r--r-- | drivers/iio/accel/kxsd9.c | 276 | ||||
| -rw-r--r-- | drivers/iio/accel/mma8452.c | 445 | ||||
| -rw-r--r-- | drivers/iio/accel/st_accel.h | 56 | ||||
| -rw-r--r-- | drivers/iio/accel/st_accel_buffer.c | 105 | ||||
| -rw-r--r-- | drivers/iio/accel/st_accel_core.c | 532 | ||||
| -rw-r--r-- | drivers/iio/accel/st_accel_i2c.c | 79 | ||||
| -rw-r--r-- | drivers/iio/accel/st_accel_spi.c | 78 | 
11 files changed, 2778 insertions, 0 deletions
diff --git a/drivers/iio/accel/Kconfig b/drivers/iio/accel/Kconfig new file mode 100644 index 00000000000..1e120fa1e15 --- /dev/null +++ b/drivers/iio/accel/Kconfig @@ -0,0 +1,80 @@ +# +# Accelerometer drivers +# +# When adding new entries keep the list in alphabetical order + +menu "Accelerometers" + +config BMA180 +	tristate "Bosch BMA180 3-Axis Accelerometer Driver" +	depends on I2C +	select IIO_BUFFER +	select IIO_TRIGGERED_BUFFER +	help +	  Say Y here if you want to build a driver for the Bosch BMA180 +	  triaxial acceleration sensor. + +	  To compile this driver as a module, choose M here: the +	  module will be called bma180. + +config HID_SENSOR_ACCEL_3D +	depends on HID_SENSOR_HUB +	select IIO_BUFFER +	select IIO_TRIGGERED_BUFFER +	select HID_SENSOR_IIO_COMMON +	select HID_SENSOR_IIO_TRIGGER +	tristate "HID Accelerometers 3D" +	help +	  Say yes here to build support for the HID SENSOR +	  accelerometers 3D. + +config IIO_ST_ACCEL_3AXIS +	tristate "STMicroelectronics accelerometers 3-Axis Driver" +	depends on (I2C || SPI_MASTER) && SYSFS +	select IIO_ST_SENSORS_CORE +	select IIO_ST_ACCEL_I2C_3AXIS if (I2C) +	select IIO_ST_ACCEL_SPI_3AXIS if (SPI_MASTER) +	select IIO_TRIGGERED_BUFFER if (IIO_BUFFER) +	help +	  Say yes here to build support for STMicroelectronics accelerometers: +	  LSM303DLH, LSM303DLHC, LIS3DH, LSM330D, LSM330DL, LSM330DLC, +	  LIS331DLH, LSM303DL, LSM303DLM, LSM330. + +	  This driver can also be built as a module. If so, these modules +	  will be created: +	  - st_accel (core functions for the driver [it is mandatory]); +	  - st_accel_i2c (necessary for the I2C devices [optional*]); +	  - st_accel_spi (necessary for the SPI devices [optional*]); + +	  (*) one of these is necessary to do something. + +config IIO_ST_ACCEL_I2C_3AXIS +	tristate +	depends on IIO_ST_ACCEL_3AXIS +	depends on IIO_ST_SENSORS_I2C + +config IIO_ST_ACCEL_SPI_3AXIS +	tristate +	depends on IIO_ST_ACCEL_3AXIS +	depends on IIO_ST_SENSORS_SPI + +config KXSD9 +	tristate "Kionix KXSD9 Accelerometer Driver" +	depends on SPI +	help +	  Say yes here to build support for the Kionix KXSD9 accelerometer. +	  Currently this only supports the device via an SPI interface. + +config MMA8452 +	tristate "Freescale MMA8452Q Accelerometer Driver" +	depends on I2C +	select IIO_BUFFER +	select IIO_TRIGGERED_BUFFER +	help +	  Say yes here to build support for the Freescale MMA8452Q 3-axis +	  accelerometer. + +	  To compile this driver as a module, choose M here: the module +	  will be called mma8452. + +endmenu diff --git a/drivers/iio/accel/Makefile b/drivers/iio/accel/Makefile new file mode 100644 index 00000000000..dc0e379c259 --- /dev/null +++ b/drivers/iio/accel/Makefile @@ -0,0 +1,16 @@ +# +# Makefile for industrial I/O accelerometer drivers +# + +# When adding new entries keep the list in alphabetical order +obj-$(CONFIG_BMA180) += bma180.o +obj-$(CONFIG_HID_SENSOR_ACCEL_3D) += hid-sensor-accel-3d.o +obj-$(CONFIG_KXSD9)	+= kxsd9.o +obj-$(CONFIG_MMA8452)	+= mma8452.o + +obj-$(CONFIG_IIO_ST_ACCEL_3AXIS) += st_accel.o +st_accel-y := st_accel_core.o +st_accel-$(CONFIG_IIO_BUFFER) += st_accel_buffer.o + +obj-$(CONFIG_IIO_ST_ACCEL_I2C_3AXIS) += st_accel_i2c.o +obj-$(CONFIG_IIO_ST_ACCEL_SPI_3AXIS) += st_accel_spi.o diff --git a/drivers/iio/accel/bma180.c b/drivers/iio/accel/bma180.c new file mode 100644 index 00000000000..a077cc86421 --- /dev/null +++ b/drivers/iio/accel/bma180.c @@ -0,0 +1,680 @@ +/* + * bma180.c - IIO driver for Bosch BMA180 triaxial acceleration sensor + * + * Copyright 2013 Oleksandr Kravchenko <x0199363@ti.com> + * + * This file is subject to the terms and conditions of version 2 of + * the GNU General Public License.  See the file COPYING in the main + * directory of this archive for more details. + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/of.h> +#include <linux/bitops.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/buffer.h> +#include <linux/iio/trigger.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> + +#define BMA180_DRV_NAME "bma180" +#define BMA180_IRQ_NAME "bma180_event" + +/* Register set */ +#define BMA180_CHIP_ID		0x00 /* Need to distinguish BMA180 from other */ +#define BMA180_ACC_X_LSB	0x02 /* First of 6 registers of accel data */ +#define BMA180_CTRL_REG0	0x0d +#define BMA180_RESET		0x10 +#define BMA180_BW_TCS		0x20 +#define BMA180_CTRL_REG3	0x21 +#define BMA180_TCO_Z		0x30 +#define BMA180_OFFSET_LSB1	0x35 + +/* BMA180_CTRL_REG0 bits */ +#define BMA180_DIS_WAKE_UP	BIT(0) /* Disable wake up mode */ +#define BMA180_SLEEP		BIT(1) /* 1 - chip will sleep */ +#define BMA180_EE_W		BIT(4) /* Unlock writing to addr from 0x20 */ +#define BMA180_RESET_INT	BIT(6) /* Reset pending interrupts */ + +/* BMA180_CTRL_REG3 bits */ +#define BMA180_NEW_DATA_INT	BIT(1) /* Intr every new accel data is ready */ + +/* BMA180_OFFSET_LSB1 skipping mode bit */ +#define BMA180_SMP_SKIP		BIT(0) + +/* Bit masks for registers bit fields */ +#define BMA180_RANGE		0x0e /* Range of measured accel values*/ +#define BMA180_BW		0xf0 /* Accel bandwidth */ +#define BMA180_MODE_CONFIG	0x03 /* Config operation modes */ + +/* We have to write this value in reset register to do soft reset */ +#define BMA180_RESET_VAL	0xb6 + +#define BMA_180_ID_REG_VAL	0x03 + +/* Chip power modes */ +#define BMA180_LOW_NOISE	0x00 +#define BMA180_LOW_POWER	0x03 + +#define BMA180_LOW_NOISE_STR	"low_noise" +#define BMA180_LOW_POWER_STR	"low_power" + +/* Defaults values */ +#define BMA180_DEF_PMODE	0 +#define BMA180_DEF_BW		20 +#define BMA180_DEF_SCALE	2452 + +/* Available values for sysfs */ +#define BMA180_FLP_FREQ_AVAILABLE \ +	"10 20 40 75 150 300" +#define BMA180_SCALE_AVAILABLE \ +	"0.001275 0.001863 0.002452 0.003727 0.004903 0.009709 0.019417" + +struct bma180_data { +	struct i2c_client *client; +	struct iio_trigger *trig; +	struct mutex mutex; +	int sleep_state; +	int scale; +	int bw; +	int pmode; +	char *buff; +}; + +enum bma180_axis { +	AXIS_X, +	AXIS_Y, +	AXIS_Z, +}; + +static int bw_table[] = { 10, 20, 40, 75, 150, 300 }; /* Hz */ +static int scale_table[] = { 1275, 1863, 2452, 3727, 4903, 9709, 19417 }; + +static int bma180_get_acc_reg(struct bma180_data *data, enum bma180_axis axis) +{ +	u8 reg = BMA180_ACC_X_LSB + axis * 2; +	int ret; + +	if (data->sleep_state) +		return -EBUSY; + +	ret = i2c_smbus_read_word_data(data->client, reg); +	if (ret < 0) +		dev_err(&data->client->dev, +			"failed to read accel_%c registers\n", 'x' + axis); + +	return ret; +} + +static int bma180_set_bits(struct bma180_data *data, u8 reg, u8 mask, u8 val) +{ +	int ret = i2c_smbus_read_byte_data(data->client, reg); +	u8 reg_val = (ret & ~mask) | (val << (ffs(mask) - 1)); + +	if (ret < 0) +		return ret; + +	return i2c_smbus_write_byte_data(data->client, reg, reg_val); +} + +static int bma180_reset_intr(struct bma180_data *data) +{ +	int ret = bma180_set_bits(data, BMA180_CTRL_REG0, BMA180_RESET_INT, 1); + +	if (ret) +		dev_err(&data->client->dev, "failed to reset interrupt\n"); + +	return ret; +} + +static int bma180_set_new_data_intr_state(struct bma180_data *data, int state) +{ +	u8 reg_val = state ? BMA180_NEW_DATA_INT : 0x00; +	int ret = i2c_smbus_write_byte_data(data->client, BMA180_CTRL_REG3, +			reg_val); + +	if (ret) +		goto err; +	ret = bma180_reset_intr(data); +	if (ret) +		goto err; + +	return 0; + +err: +	dev_err(&data->client->dev, +		"failed to set new data interrupt state %d\n", state); +	return ret; +} + +static int bma180_set_sleep_state(struct bma180_data *data, int state) +{ +	int ret = bma180_set_bits(data, BMA180_CTRL_REG0, BMA180_SLEEP, state); + +	if (ret) { +		dev_err(&data->client->dev, +			"failed to set sleep state %d\n", state); +		return ret; +	} +	data->sleep_state = state; + +	return 0; +} + +static int bma180_set_ee_writing_state(struct bma180_data *data, int state) +{ +	int ret = bma180_set_bits(data, BMA180_CTRL_REG0, BMA180_EE_W, state); + +	if (ret) +		dev_err(&data->client->dev, +			"failed to set ee writing state %d\n", state); + +	return ret; +} + +static int bma180_set_bw(struct bma180_data *data, int val) +{ +	int ret, i; + +	if (data->sleep_state) +		return -EBUSY; + +	for (i = 0; i < ARRAY_SIZE(bw_table); ++i) { +		if (bw_table[i] == val) { +			ret = bma180_set_bits(data, +					BMA180_BW_TCS, BMA180_BW, i); +			if (ret) { +				dev_err(&data->client->dev, +					"failed to set bandwidth\n"); +				return ret; +			} +			data->bw = val; +			return 0; +		} +	} + +	return -EINVAL; +} + +static int bma180_set_scale(struct bma180_data *data, int val) +{ +	int ret, i; + +	if (data->sleep_state) +		return -EBUSY; + +	for (i = 0; i < ARRAY_SIZE(scale_table); ++i) +		if (scale_table[i] == val) { +			ret = bma180_set_bits(data, +					BMA180_OFFSET_LSB1, BMA180_RANGE, i); +			if (ret) { +				dev_err(&data->client->dev, +					"failed to set scale\n"); +				return ret; +			} +			data->scale = val; +			return 0; +		} + +	return -EINVAL; +} + +static int bma180_set_pmode(struct bma180_data *data, int mode) +{ +	u8 reg_val = mode ? BMA180_LOW_POWER : BMA180_LOW_NOISE; +	int ret = bma180_set_bits(data, BMA180_TCO_Z, BMA180_MODE_CONFIG, +			reg_val); + +	if (ret) { +		dev_err(&data->client->dev, "failed to set power mode\n"); +		return ret; +	} +	data->pmode = mode; + +	return 0; +} + +static int bma180_soft_reset(struct bma180_data *data) +{ +	int ret = i2c_smbus_write_byte_data(data->client, +			BMA180_RESET, BMA180_RESET_VAL); + +	if (ret) +		dev_err(&data->client->dev, "failed to reset the chip\n"); + +	return ret; +} + +static int bma180_chip_init(struct bma180_data *data) +{ +	/* Try to read chip_id register. It must return 0x03. */ +	int ret = i2c_smbus_read_byte_data(data->client, BMA180_CHIP_ID); + +	if (ret < 0) +		goto err; +	if (ret != BMA_180_ID_REG_VAL) { +		ret = -ENODEV; +		goto err; +	} + +	ret = bma180_soft_reset(data); +	if (ret) +		goto err; +	/* +	 * No serial transaction should occur within minimum 10 us +	 * after soft_reset command +	 */ +	msleep(20); + +	ret = bma180_set_bits(data, BMA180_CTRL_REG0, BMA180_DIS_WAKE_UP, 1); +	if (ret) +		goto err; +	ret = bma180_set_ee_writing_state(data, 1); +	if (ret) +		goto err; +	ret = bma180_set_new_data_intr_state(data, 0); +	if (ret) +		goto err; +	ret = bma180_set_bits(data, BMA180_OFFSET_LSB1, BMA180_SMP_SKIP, 1); +	if (ret) +		goto err; +	ret = bma180_set_pmode(data, BMA180_DEF_PMODE); +	if (ret) +		goto err; +	ret = bma180_set_bw(data, BMA180_DEF_BW); +	if (ret) +		goto err; +	ret = bma180_set_scale(data, BMA180_DEF_SCALE); +	if (ret) +		goto err; + +	return 0; + +err: +	dev_err(&data->client->dev, "failed to init the chip\n"); +	return ret; +} + +static void bma180_chip_disable(struct bma180_data *data) +{ +	if (bma180_set_new_data_intr_state(data, 0)) +		goto err; +	if (bma180_set_ee_writing_state(data, 0)) +		goto err; +	if (bma180_set_sleep_state(data, 1)) +		goto err; + +	return; + +err: +	dev_err(&data->client->dev, "failed to disable the chip\n"); +} + +static IIO_CONST_ATTR(in_accel_filter_low_pass_3db_frequency_available, +		BMA180_FLP_FREQ_AVAILABLE); +static IIO_CONST_ATTR(in_accel_scale_available, BMA180_SCALE_AVAILABLE); + +static struct attribute *bma180_attributes[] = { +	&iio_const_attr_in_accel_filter_low_pass_3db_frequency_available.dev_attr.attr, +	&iio_const_attr_in_accel_scale_available.dev_attr.attr, +	NULL, +}; + +static const struct attribute_group bma180_attrs_group = { +	.attrs = bma180_attributes, +}; + +static int bma180_read_raw(struct iio_dev *indio_dev, +		struct iio_chan_spec const *chan, int *val, int *val2, +		long mask) +{ +	struct bma180_data *data = iio_priv(indio_dev); +	int ret; + +	switch (mask) { +	case IIO_CHAN_INFO_RAW: +		mutex_lock(&data->mutex); +		if (iio_buffer_enabled(indio_dev)) +			ret = -EBUSY; +		else +			ret = bma180_get_acc_reg(data, chan->scan_index); +		mutex_unlock(&data->mutex); +		if (ret < 0) +			return ret; +		*val = (s16)ret >> chan->scan_type.shift; +		return IIO_VAL_INT; +	case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: +		*val = data->bw; +		return IIO_VAL_INT; +	case IIO_CHAN_INFO_SCALE: +		*val = 0; +		*val2 = data->scale; +		return IIO_VAL_INT_PLUS_MICRO; +	default: +		return -EINVAL; +	} +} + +static int bma180_write_raw(struct iio_dev *indio_dev, +		struct iio_chan_spec const *chan, int val, int val2, long mask) +{ +	struct bma180_data *data = iio_priv(indio_dev); +	int ret; + +	switch (mask) { +	case IIO_CHAN_INFO_SCALE: +		if (val) +			return -EINVAL; +		mutex_lock(&data->mutex); +		ret = bma180_set_scale(data, val2); +		mutex_unlock(&data->mutex); +		return ret; +	case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: +		if (val2) +			return -EINVAL; +		mutex_lock(&data->mutex); +		ret = bma180_set_bw(data, val); +		mutex_unlock(&data->mutex); +		return ret; +	default: +		return -EINVAL; +	} +} + +static int bma180_update_scan_mode(struct iio_dev *indio_dev, +		const unsigned long *scan_mask) +{ +	struct bma180_data *data = iio_priv(indio_dev); + +	if (data->buff) +		devm_kfree(&indio_dev->dev, data->buff); +	data->buff = devm_kzalloc(&indio_dev->dev, +			indio_dev->scan_bytes, GFP_KERNEL); +	if (!data->buff) +		return -ENOMEM; + +	return 0; +} + +static const struct iio_info bma180_info = { +	.attrs			= &bma180_attrs_group, +	.read_raw		= bma180_read_raw, +	.write_raw		= bma180_write_raw, +	.update_scan_mode	= bma180_update_scan_mode, +	.driver_module		= THIS_MODULE, +}; + +static const char * const bma180_power_modes[] = { +	BMA180_LOW_NOISE_STR, +	BMA180_LOW_POWER_STR, +}; + +static int bma180_get_power_mode(struct iio_dev *indio_dev, +		const struct iio_chan_spec *chan) +{ +	struct bma180_data *data = iio_priv(indio_dev); + +	return data->pmode; +} + +static int bma180_set_power_mode(struct iio_dev *indio_dev, +		const struct iio_chan_spec *chan, unsigned int mode) +{ +	struct bma180_data *data = iio_priv(indio_dev); +	int ret; + +	mutex_lock(&data->mutex); +	ret = bma180_set_pmode(data, mode); +	mutex_unlock(&data->mutex); + +	return ret; +} + +static const struct iio_enum bma180_power_mode_enum = { +	.items = bma180_power_modes, +	.num_items = ARRAY_SIZE(bma180_power_modes), +	.get = bma180_get_power_mode, +	.set = bma180_set_power_mode, +}; + +static const struct iio_chan_spec_ext_info bma180_ext_info[] = { +	IIO_ENUM("power_mode", true, &bma180_power_mode_enum), +	IIO_ENUM_AVAILABLE("power_mode", &bma180_power_mode_enum), +	{ }, +}; + +#define BMA180_CHANNEL(_axis) {					\ +	.type = IIO_ACCEL,						\ +	.modified = 1,							\ +	.channel2 = IIO_MOD_##_axis,					\ +	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),			\ +	.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) |		\ +		BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY),	\ +	.scan_index = AXIS_##_axis,					\ +	.scan_type = {							\ +		.sign = 's',						\ +		.realbits = 14,						\ +		.storagebits = 16,					\ +		.shift = 2,						\ +	},								\ +	.ext_info = bma180_ext_info,					\ +} + +static const struct iio_chan_spec bma180_channels[] = { +	BMA180_CHANNEL(X), +	BMA180_CHANNEL(Y), +	BMA180_CHANNEL(Z), +	IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static irqreturn_t bma180_trigger_handler(int irq, void *p) +{ +	struct iio_poll_func *pf = p; +	struct iio_dev *indio_dev = pf->indio_dev; +	struct bma180_data *data = iio_priv(indio_dev); +	int64_t time_ns = iio_get_time_ns(); +	int bit, ret, i = 0; + +	mutex_lock(&data->mutex); + +	for_each_set_bit(bit, indio_dev->buffer->scan_mask, +			 indio_dev->masklength) { +		ret = bma180_get_acc_reg(data, bit); +		if (ret < 0) { +			mutex_unlock(&data->mutex); +			goto err; +		} +		((s16 *)data->buff)[i++] = ret; +	} +	mutex_unlock(&data->mutex); + +	iio_push_to_buffers_with_timestamp(indio_dev, data->buff, time_ns); +err: +	iio_trigger_notify_done(indio_dev->trig); + +	return IRQ_HANDLED; +} + +static int bma180_data_rdy_trigger_set_state(struct iio_trigger *trig, +		bool state) +{ +	struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); +	struct bma180_data *data = iio_priv(indio_dev); + +	return bma180_set_new_data_intr_state(data, state); +} + +static int bma180_trig_try_reen(struct iio_trigger *trig) +{ +	struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); +	struct bma180_data *data = iio_priv(indio_dev); + +	return bma180_reset_intr(data); +} + +static const struct iio_trigger_ops bma180_trigger_ops = { +	.set_trigger_state = bma180_data_rdy_trigger_set_state, +	.try_reenable = bma180_trig_try_reen, +	.owner = THIS_MODULE, +}; + +static int bma180_probe(struct i2c_client *client, +		const struct i2c_device_id *id) +{ +	struct bma180_data *data; +	struct iio_dev *indio_dev; +	struct iio_trigger *trig; +	int ret; + +	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); +	if (!indio_dev) +		return -ENOMEM; + +	data = iio_priv(indio_dev); +	i2c_set_clientdata(client, indio_dev); +	data->client = client; + +	ret = bma180_chip_init(data); +	if (ret < 0) +		goto err_chip_disable; + +	mutex_init(&data->mutex); + +	indio_dev->dev.parent = &client->dev; +	indio_dev->channels = bma180_channels; +	indio_dev->num_channels = ARRAY_SIZE(bma180_channels); +	indio_dev->name = BMA180_DRV_NAME; +	indio_dev->modes = INDIO_DIRECT_MODE; +	indio_dev->info = &bma180_info; + +	trig = iio_trigger_alloc("%s-dev%d", indio_dev->name, indio_dev->id); +	if (!trig) { +		ret = -ENOMEM; +		goto err_chip_disable; +	} + +	ret = devm_request_irq(&client->dev, client->irq, +			iio_trigger_generic_data_rdy_poll, +			IRQF_TRIGGER_RISING, BMA180_IRQ_NAME, trig); +	if (ret) { +		dev_err(&client->dev, "unable to request IRQ\n"); +		goto err_trigger_free; +	} + +	trig->dev.parent = &client->dev; +	trig->ops = &bma180_trigger_ops; +	iio_trigger_set_drvdata(trig, indio_dev); +	data->trig = trig; +	indio_dev->trig = trig; + +	ret = iio_trigger_register(trig); +	if (ret) +		goto err_trigger_free; + +	ret = iio_triggered_buffer_setup(indio_dev, NULL, +			bma180_trigger_handler, NULL); +	if (ret < 0) { +		dev_err(&client->dev, "unable to setup iio triggered buffer\n"); +		goto err_trigger_unregister; +	} + +	ret = iio_device_register(indio_dev); +	if (ret < 0) { +		dev_err(&client->dev, "unable to register iio device\n"); +		goto err_buffer_cleanup; +	} + +	return 0; + +err_buffer_cleanup: +	iio_triggered_buffer_cleanup(indio_dev); +err_trigger_unregister: +	iio_trigger_unregister(trig); +err_trigger_free: +	iio_trigger_free(trig); +err_chip_disable: +	bma180_chip_disable(data); + +	return ret; +} + +static int bma180_remove(struct i2c_client *client) +{ +	struct iio_dev *indio_dev = i2c_get_clientdata(client); +	struct bma180_data *data = iio_priv(indio_dev); + +	iio_device_unregister(indio_dev); +	iio_triggered_buffer_cleanup(indio_dev); +	iio_trigger_unregister(data->trig); +	iio_trigger_free(data->trig); + +	mutex_lock(&data->mutex); +	bma180_chip_disable(data); +	mutex_unlock(&data->mutex); + +	return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int bma180_suspend(struct device *dev) +{ +	struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); +	struct bma180_data *data = iio_priv(indio_dev); +	int ret; + +	mutex_lock(&data->mutex); +	ret = bma180_set_sleep_state(data, 1); +	mutex_unlock(&data->mutex); + +	return ret; +} + +static int bma180_resume(struct device *dev) +{ +	struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); +	struct bma180_data *data = iio_priv(indio_dev); +	int ret; + +	mutex_lock(&data->mutex); +	ret = bma180_set_sleep_state(data, 0); +	mutex_unlock(&data->mutex); + +	return ret; +} + +static SIMPLE_DEV_PM_OPS(bma180_pm_ops, bma180_suspend, bma180_resume); +#define BMA180_PM_OPS (&bma180_pm_ops) +#else +#define BMA180_PM_OPS NULL +#endif + +static struct i2c_device_id bma180_id[] = { +	{ BMA180_DRV_NAME, 0 }, +	{ } +}; + +MODULE_DEVICE_TABLE(i2c, bma180_id); + +static struct i2c_driver bma180_driver = { +	.driver = { +		.name	= BMA180_DRV_NAME, +		.owner	= THIS_MODULE, +		.pm	= BMA180_PM_OPS, +	}, +	.probe		= bma180_probe, +	.remove		= bma180_remove, +	.id_table	= bma180_id, +}; + +module_i2c_driver(bma180_driver); + +MODULE_AUTHOR("Kravchenko Oleksandr <x0199363@ti.com>"); +MODULE_AUTHOR("Texas Instruments, Inc."); +MODULE_DESCRIPTION("Bosch BMA180 triaxial acceleration sensor"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/accel/hid-sensor-accel-3d.c b/drivers/iio/accel/hid-sensor-accel-3d.c new file mode 100644 index 00000000000..54e464e4bb7 --- /dev/null +++ b/drivers/iio/accel/hid-sensor-accel-3d.c @@ -0,0 +1,431 @@ +/* + * HID Sensors Driver + * Copyright (c) 2012, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + */ +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/hid-sensor-hub.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/buffer.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> +#include "../common/hid-sensors/hid-sensor-trigger.h" + +enum accel_3d_channel { +	CHANNEL_SCAN_INDEX_X, +	CHANNEL_SCAN_INDEX_Y, +	CHANNEL_SCAN_INDEX_Z, +	ACCEL_3D_CHANNEL_MAX, +}; + +struct accel_3d_state { +	struct hid_sensor_hub_callbacks callbacks; +	struct hid_sensor_common common_attributes; +	struct hid_sensor_hub_attribute_info accel[ACCEL_3D_CHANNEL_MAX]; +	u32 accel_val[ACCEL_3D_CHANNEL_MAX]; +	int scale_pre_decml; +	int scale_post_decml; +	int scale_precision; +	int value_offset; +}; + +static const u32 accel_3d_addresses[ACCEL_3D_CHANNEL_MAX] = { +	HID_USAGE_SENSOR_ACCEL_X_AXIS, +	HID_USAGE_SENSOR_ACCEL_Y_AXIS, +	HID_USAGE_SENSOR_ACCEL_Z_AXIS +}; + +/* Channel definitions */ +static const struct iio_chan_spec accel_3d_channels[] = { +	{ +		.type = IIO_ACCEL, +		.modified = 1, +		.channel2 = IIO_MOD_X, +		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), +		.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | +		BIT(IIO_CHAN_INFO_SCALE) | +		BIT(IIO_CHAN_INFO_SAMP_FREQ) | +		BIT(IIO_CHAN_INFO_HYSTERESIS), +		.scan_index = CHANNEL_SCAN_INDEX_X, +	}, { +		.type = IIO_ACCEL, +		.modified = 1, +		.channel2 = IIO_MOD_Y, +		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), +		.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | +		BIT(IIO_CHAN_INFO_SCALE) | +		BIT(IIO_CHAN_INFO_SAMP_FREQ) | +		BIT(IIO_CHAN_INFO_HYSTERESIS), +		.scan_index = CHANNEL_SCAN_INDEX_Y, +	}, { +		.type = IIO_ACCEL, +		.modified = 1, +		.channel2 = IIO_MOD_Z, +		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), +		.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | +		BIT(IIO_CHAN_INFO_SCALE) | +		BIT(IIO_CHAN_INFO_SAMP_FREQ) | +		BIT(IIO_CHAN_INFO_HYSTERESIS), +		.scan_index = CHANNEL_SCAN_INDEX_Z, +	} +}; + +/* Adjust channel real bits based on report descriptor */ +static void accel_3d_adjust_channel_bit_mask(struct iio_chan_spec *channels, +						int channel, int size) +{ +	channels[channel].scan_type.sign = 's'; +	/* Real storage bits will change based on the report desc. */ +	channels[channel].scan_type.realbits = size * 8; +	/* Maximum size of a sample to capture is u32 */ +	channels[channel].scan_type.storagebits = sizeof(u32) * 8; +} + +/* Channel read_raw handler */ +static int accel_3d_read_raw(struct iio_dev *indio_dev, +			      struct iio_chan_spec const *chan, +			      int *val, int *val2, +			      long mask) +{ +	struct accel_3d_state *accel_state = iio_priv(indio_dev); +	int report_id = -1; +	u32 address; +	int ret_type; +	s32 poll_value; + +	*val = 0; +	*val2 = 0; +	switch (mask) { +	case 0: +		poll_value = hid_sensor_read_poll_value( +					&accel_state->common_attributes); +		if (poll_value < 0) +			return -EINVAL; + +		hid_sensor_power_state(&accel_state->common_attributes, true); +		msleep_interruptible(poll_value * 2); +		report_id = accel_state->accel[chan->scan_index].report_id; +		address = accel_3d_addresses[chan->scan_index]; +		if (report_id >= 0) +			*val = sensor_hub_input_attr_get_raw_value( +					accel_state->common_attributes.hsdev, +					HID_USAGE_SENSOR_ACCEL_3D, address, +					report_id); +		else { +			*val = 0; +			hid_sensor_power_state(&accel_state->common_attributes, +						 false); +			return -EINVAL; +		} +		hid_sensor_power_state(&accel_state->common_attributes, false); +		ret_type = IIO_VAL_INT; +		break; +	case IIO_CHAN_INFO_SCALE: +		*val = accel_state->scale_pre_decml; +		*val2 = accel_state->scale_post_decml; +		ret_type = accel_state->scale_precision; +		break; +	case IIO_CHAN_INFO_OFFSET: +		*val = accel_state->value_offset; +		ret_type = IIO_VAL_INT; +		break; +	case IIO_CHAN_INFO_SAMP_FREQ: +		ret_type = hid_sensor_read_samp_freq_value( +			&accel_state->common_attributes, val, val2); +		break; +	case IIO_CHAN_INFO_HYSTERESIS: +		ret_type = hid_sensor_read_raw_hyst_value( +			&accel_state->common_attributes, val, val2); +		break; +	default: +		ret_type = -EINVAL; +		break; +	} + +	return ret_type; +} + +/* Channel write_raw handler */ +static int accel_3d_write_raw(struct iio_dev *indio_dev, +			       struct iio_chan_spec const *chan, +			       int val, +			       int val2, +			       long mask) +{ +	struct accel_3d_state *accel_state = iio_priv(indio_dev); +	int ret = 0; + +	switch (mask) { +	case IIO_CHAN_INFO_SAMP_FREQ: +		ret = hid_sensor_write_samp_freq_value( +				&accel_state->common_attributes, val, val2); +		break; +	case IIO_CHAN_INFO_HYSTERESIS: +		ret = hid_sensor_write_raw_hyst_value( +				&accel_state->common_attributes, val, val2); +		break; +	default: +		ret = -EINVAL; +	} + +	return ret; +} + +static const struct iio_info accel_3d_info = { +	.driver_module = THIS_MODULE, +	.read_raw = &accel_3d_read_raw, +	.write_raw = &accel_3d_write_raw, +}; + +/* Function to push data to buffer */ +static void hid_sensor_push_data(struct iio_dev *indio_dev, const void *data, +	int len) +{ +	dev_dbg(&indio_dev->dev, "hid_sensor_push_data\n"); +	iio_push_to_buffers(indio_dev, data); +} + +/* Callback handler to send event after all samples are received and captured */ +static int accel_3d_proc_event(struct hid_sensor_hub_device *hsdev, +				unsigned usage_id, +				void *priv) +{ +	struct iio_dev *indio_dev = platform_get_drvdata(priv); +	struct accel_3d_state *accel_state = iio_priv(indio_dev); + +	dev_dbg(&indio_dev->dev, "accel_3d_proc_event\n"); +	if (atomic_read(&accel_state->common_attributes.data_ready)) +		hid_sensor_push_data(indio_dev, +				accel_state->accel_val, +				sizeof(accel_state->accel_val)); + +	return 0; +} + +/* Capture samples in local storage */ +static int accel_3d_capture_sample(struct hid_sensor_hub_device *hsdev, +				unsigned usage_id, +				size_t raw_len, char *raw_data, +				void *priv) +{ +	struct iio_dev *indio_dev = platform_get_drvdata(priv); +	struct accel_3d_state *accel_state = iio_priv(indio_dev); +	int offset; +	int ret = -EINVAL; + +	switch (usage_id) { +	case HID_USAGE_SENSOR_ACCEL_X_AXIS: +	case HID_USAGE_SENSOR_ACCEL_Y_AXIS: +	case HID_USAGE_SENSOR_ACCEL_Z_AXIS: +		offset = usage_id - HID_USAGE_SENSOR_ACCEL_X_AXIS; +		accel_state->accel_val[CHANNEL_SCAN_INDEX_X + offset] = +						*(u32 *)raw_data; +		ret = 0; +	break; +	default: +		break; +	} + +	return ret; +} + +/* Parse report which is specific to an usage id*/ +static int accel_3d_parse_report(struct platform_device *pdev, +				struct hid_sensor_hub_device *hsdev, +				struct iio_chan_spec *channels, +				unsigned usage_id, +				struct accel_3d_state *st) +{ +	int ret; +	int i; + +	for (i = 0; i <= CHANNEL_SCAN_INDEX_Z; ++i) { +		ret = sensor_hub_input_get_attribute_info(hsdev, +				HID_INPUT_REPORT, +				usage_id, +				HID_USAGE_SENSOR_ACCEL_X_AXIS + i, +				&st->accel[CHANNEL_SCAN_INDEX_X + i]); +		if (ret < 0) +			break; +		accel_3d_adjust_channel_bit_mask(channels, +				CHANNEL_SCAN_INDEX_X + i, +				st->accel[CHANNEL_SCAN_INDEX_X + i].size); +	} +	dev_dbg(&pdev->dev, "accel_3d %x:%x, %x:%x, %x:%x\n", +			st->accel[0].index, +			st->accel[0].report_id, +			st->accel[1].index, st->accel[1].report_id, +			st->accel[2].index, st->accel[2].report_id); + +	st->scale_precision = hid_sensor_format_scale( +				HID_USAGE_SENSOR_ACCEL_3D, +				&st->accel[CHANNEL_SCAN_INDEX_X], +				&st->scale_pre_decml, &st->scale_post_decml); + +	/* Set Sensitivity field ids, when there is no individual modifier */ +	if (st->common_attributes.sensitivity.index < 0) { +		sensor_hub_input_get_attribute_info(hsdev, +			HID_FEATURE_REPORT, usage_id, +			HID_USAGE_SENSOR_DATA_MOD_CHANGE_SENSITIVITY_ABS | +			HID_USAGE_SENSOR_DATA_ACCELERATION, +			&st->common_attributes.sensitivity); +		dev_dbg(&pdev->dev, "Sensitivity index:report %d:%d\n", +			st->common_attributes.sensitivity.index, +			st->common_attributes.sensitivity.report_id); +	} + +	return ret; +} + +/* Function to initialize the processing for usage id */ +static int hid_accel_3d_probe(struct platform_device *pdev) +{ +	int ret = 0; +	static const char *name = "accel_3d"; +	struct iio_dev *indio_dev; +	struct accel_3d_state *accel_state; +	struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data; +	struct iio_chan_spec *channels; + +	indio_dev = devm_iio_device_alloc(&pdev->dev, +					  sizeof(struct accel_3d_state)); +	if (indio_dev == NULL) +		return -ENOMEM; + +	platform_set_drvdata(pdev, indio_dev); + +	accel_state = iio_priv(indio_dev); +	accel_state->common_attributes.hsdev = hsdev; +	accel_state->common_attributes.pdev = pdev; + +	ret = hid_sensor_parse_common_attributes(hsdev, +					HID_USAGE_SENSOR_ACCEL_3D, +					&accel_state->common_attributes); +	if (ret) { +		dev_err(&pdev->dev, "failed to setup common attributes\n"); +		return ret; +	} + +	channels = kmemdup(accel_3d_channels, sizeof(accel_3d_channels), +			   GFP_KERNEL); +	if (!channels) { +		dev_err(&pdev->dev, "failed to duplicate channels\n"); +		return -ENOMEM; +	} + +	ret = accel_3d_parse_report(pdev, hsdev, channels, +					HID_USAGE_SENSOR_ACCEL_3D, accel_state); +	if (ret) { +		dev_err(&pdev->dev, "failed to setup attributes\n"); +		goto error_free_dev_mem; +	} + +	indio_dev->channels = channels; +	indio_dev->num_channels = ARRAY_SIZE(accel_3d_channels); +	indio_dev->dev.parent = &pdev->dev; +	indio_dev->info = &accel_3d_info; +	indio_dev->name = name; +	indio_dev->modes = INDIO_DIRECT_MODE; + +	ret = iio_triggered_buffer_setup(indio_dev, &iio_pollfunc_store_time, +		NULL, NULL); +	if (ret) { +		dev_err(&pdev->dev, "failed to initialize trigger buffer\n"); +		goto error_free_dev_mem; +	} +	atomic_set(&accel_state->common_attributes.data_ready, 0); +	ret = hid_sensor_setup_trigger(indio_dev, name, +					&accel_state->common_attributes); +	if (ret < 0) { +		dev_err(&pdev->dev, "trigger setup failed\n"); +		goto error_unreg_buffer_funcs; +	} + +	ret = iio_device_register(indio_dev); +	if (ret) { +		dev_err(&pdev->dev, "device register failed\n"); +		goto error_remove_trigger; +	} + +	accel_state->callbacks.send_event = accel_3d_proc_event; +	accel_state->callbacks.capture_sample = accel_3d_capture_sample; +	accel_state->callbacks.pdev = pdev; +	ret = sensor_hub_register_callback(hsdev, HID_USAGE_SENSOR_ACCEL_3D, +					&accel_state->callbacks); +	if (ret < 0) { +		dev_err(&pdev->dev, "callback reg failed\n"); +		goto error_iio_unreg; +	} + +	return ret; + +error_iio_unreg: +	iio_device_unregister(indio_dev); +error_remove_trigger: +	hid_sensor_remove_trigger(&accel_state->common_attributes); +error_unreg_buffer_funcs: +	iio_triggered_buffer_cleanup(indio_dev); +error_free_dev_mem: +	kfree(indio_dev->channels); +	return ret; +} + +/* Function to deinitialize the processing for usage id */ +static int hid_accel_3d_remove(struct platform_device *pdev) +{ +	struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data; +	struct iio_dev *indio_dev = platform_get_drvdata(pdev); +	struct accel_3d_state *accel_state = iio_priv(indio_dev); + +	sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_ACCEL_3D); +	iio_device_unregister(indio_dev); +	hid_sensor_remove_trigger(&accel_state->common_attributes); +	iio_triggered_buffer_cleanup(indio_dev); +	kfree(indio_dev->channels); + +	return 0; +} + +static struct platform_device_id hid_accel_3d_ids[] = { +	{ +		/* Format: HID-SENSOR-usage_id_in_hex_lowercase */ +		.name = "HID-SENSOR-200073", +	}, +	{ /* sentinel */ } +}; +MODULE_DEVICE_TABLE(platform, hid_accel_3d_ids); + +static struct platform_driver hid_accel_3d_platform_driver = { +	.id_table = hid_accel_3d_ids, +	.driver = { +		.name	= KBUILD_MODNAME, +		.owner	= THIS_MODULE, +	}, +	.probe		= hid_accel_3d_probe, +	.remove		= hid_accel_3d_remove, +}; +module_platform_driver(hid_accel_3d_platform_driver); + +MODULE_DESCRIPTION("HID Sensor Accel 3D"); +MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@intel.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/accel/kxsd9.c b/drivers/iio/accel/kxsd9.c new file mode 100644 index 00000000000..98ba761cbb9 --- /dev/null +++ b/drivers/iio/accel/kxsd9.c @@ -0,0 +1,276 @@ +/* + * kxsd9.c	simple support for the Kionix KXSD9 3D + *		accelerometer. + * + * Copyright (c) 2008-2009 Jonathan Cameron <jic23@kernel.org> + * + * 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. + * + * The i2c interface is very similar, so shouldn't be a problem once + * I have a suitable wire made up. + * + * TODO:	Support the motion detector + *		Uses register address incrementing so could have a + *		heavily optimized ring buffer access function. + */ + +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/spi/spi.h> +#include <linux/sysfs.h> +#include <linux/slab.h> +#include <linux/module.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#define KXSD9_REG_X		0x00 +#define KXSD9_REG_Y		0x02 +#define KXSD9_REG_Z		0x04 +#define KXSD9_REG_AUX		0x06 +#define KXSD9_REG_RESET		0x0a +#define KXSD9_REG_CTRL_C	0x0c + +#define KXSD9_FS_MASK		0x03 + +#define KXSD9_REG_CTRL_B	0x0d +#define KXSD9_REG_CTRL_A	0x0e + +#define KXSD9_READ(a) (0x80 | (a)) +#define KXSD9_WRITE(a) (a) + +#define KXSD9_STATE_RX_SIZE 2 +#define KXSD9_STATE_TX_SIZE 2 +/** + * struct kxsd9_state - device related storage + * @buf_lock:	protect the rx and tx buffers. + * @us:		spi device + * @rx:		single rx buffer storage + * @tx:		single tx buffer storage + **/ +struct kxsd9_state { +	struct mutex buf_lock; +	struct spi_device *us; +	u8 rx[KXSD9_STATE_RX_SIZE] ____cacheline_aligned; +	u8 tx[KXSD9_STATE_TX_SIZE]; +}; + +#define KXSD9_SCALE_2G "0.011978" +#define KXSD9_SCALE_4G "0.023927" +#define KXSD9_SCALE_6G "0.035934" +#define KXSD9_SCALE_8G "0.047853" + +/* reverse order */ +static const int kxsd9_micro_scales[4] = { 47853, 35934, 23927, 11978 }; + +static int kxsd9_write_scale(struct iio_dev *indio_dev, int micro) +{ +	int ret, i; +	struct kxsd9_state *st = iio_priv(indio_dev); +	bool foundit = false; + +	for (i = 0; i < 4; i++) +		if (micro == kxsd9_micro_scales[i]) { +			foundit = true; +			break; +		} +	if (!foundit) +		return -EINVAL; + +	mutex_lock(&st->buf_lock); +	ret = spi_w8r8(st->us, KXSD9_READ(KXSD9_REG_CTRL_C)); +	if (ret) +		goto error_ret; +	st->tx[0] = KXSD9_WRITE(KXSD9_REG_CTRL_C); +	st->tx[1] = (ret & ~KXSD9_FS_MASK) | i; + +	ret = spi_write(st->us, st->tx, 2); +error_ret: +	mutex_unlock(&st->buf_lock); +	return ret; +} + +static int kxsd9_read(struct iio_dev *indio_dev, u8 address) +{ +	int ret; +	struct kxsd9_state *st = iio_priv(indio_dev); +	struct spi_transfer xfers[] = { +		{ +			.bits_per_word = 8, +			.len = 1, +			.delay_usecs = 200, +			.tx_buf = st->tx, +		}, { +			.bits_per_word = 8, +			.len = 2, +			.rx_buf = st->rx, +		}, +	}; + +	mutex_lock(&st->buf_lock); +	st->tx[0] = KXSD9_READ(address); +	ret = spi_sync_transfer(st->us, xfers, ARRAY_SIZE(xfers)); +	if (!ret) +		ret = (((u16)(st->rx[0])) << 8) | (st->rx[1] & 0xF0); +	mutex_unlock(&st->buf_lock); +	return ret; +} + +static IIO_CONST_ATTR(accel_scale_available, +		KXSD9_SCALE_2G " " +		KXSD9_SCALE_4G " " +		KXSD9_SCALE_6G " " +		KXSD9_SCALE_8G); + +static struct attribute *kxsd9_attributes[] = { +	&iio_const_attr_accel_scale_available.dev_attr.attr, +	NULL, +}; + +static int kxsd9_write_raw(struct iio_dev *indio_dev, +			   struct iio_chan_spec const *chan, +			   int val, +			   int val2, +			   long mask) +{ +	int ret = -EINVAL; + +	if (mask == IIO_CHAN_INFO_SCALE) { +		/* Check no integer component */ +		if (val) +			return -EINVAL; +		ret = kxsd9_write_scale(indio_dev, val2); +	} + +	return ret; +} + +static int kxsd9_read_raw(struct iio_dev *indio_dev, +			  struct iio_chan_spec const *chan, +			  int *val, int *val2, long mask) +{ +	int ret = -EINVAL; +	struct kxsd9_state *st = iio_priv(indio_dev); + +	switch (mask) { +	case IIO_CHAN_INFO_RAW: +		ret = kxsd9_read(indio_dev, chan->address); +		if (ret < 0) +			goto error_ret; +		*val = ret; +		break; +	case IIO_CHAN_INFO_SCALE: +		ret = spi_w8r8(st->us, KXSD9_READ(KXSD9_REG_CTRL_C)); +		if (ret) +			goto error_ret; +		*val2 = kxsd9_micro_scales[ret & KXSD9_FS_MASK]; +		ret = IIO_VAL_INT_PLUS_MICRO; +		break; +	} + +error_ret: +	return ret; +}; +#define KXSD9_ACCEL_CHAN(axis)						\ +	{								\ +		.type = IIO_ACCEL,					\ +		.modified = 1,						\ +		.channel2 = IIO_MOD_##axis,				\ +		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),		\ +		.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),	\ +		.address = KXSD9_REG_##axis,				\ +	} + +static const struct iio_chan_spec kxsd9_channels[] = { +	KXSD9_ACCEL_CHAN(X), KXSD9_ACCEL_CHAN(Y), KXSD9_ACCEL_CHAN(Z), +	{ +		.type = IIO_VOLTAGE, +		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), +		.indexed = 1, +		.address = KXSD9_REG_AUX, +	} +}; + +static const struct attribute_group kxsd9_attribute_group = { +	.attrs = kxsd9_attributes, +}; + +static int kxsd9_power_up(struct kxsd9_state *st) +{ +	int ret; + +	st->tx[0] = 0x0d; +	st->tx[1] = 0x40; +	ret = spi_write(st->us, st->tx, 2); +	if (ret) +		return ret; + +	st->tx[0] = 0x0c; +	st->tx[1] = 0x9b; +	return spi_write(st->us, st->tx, 2); +}; + +static const struct iio_info kxsd9_info = { +	.read_raw = &kxsd9_read_raw, +	.write_raw = &kxsd9_write_raw, +	.attrs = &kxsd9_attribute_group, +	.driver_module = THIS_MODULE, +}; + +static int kxsd9_probe(struct spi_device *spi) +{ +	struct iio_dev *indio_dev; +	struct kxsd9_state *st; + +	indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); +	if (!indio_dev) +		return -ENOMEM; + +	st = iio_priv(indio_dev); +	spi_set_drvdata(spi, indio_dev); + +	st->us = spi; +	mutex_init(&st->buf_lock); +	indio_dev->channels = kxsd9_channels; +	indio_dev->num_channels = ARRAY_SIZE(kxsd9_channels); +	indio_dev->name = spi_get_device_id(spi)->name; +	indio_dev->dev.parent = &spi->dev; +	indio_dev->info = &kxsd9_info; +	indio_dev->modes = INDIO_DIRECT_MODE; + +	spi->mode = SPI_MODE_0; +	spi_setup(spi); +	kxsd9_power_up(st); + +	return iio_device_register(indio_dev); +} + +static int kxsd9_remove(struct spi_device *spi) +{ +	iio_device_unregister(spi_get_drvdata(spi)); + +	return 0; +} + +static const struct spi_device_id kxsd9_id[] = { +	{"kxsd9", 0}, +	{ }, +}; +MODULE_DEVICE_TABLE(spi, kxsd9_id); + +static struct spi_driver kxsd9_driver = { +	.driver = { +		.name = "kxsd9", +		.owner = THIS_MODULE, +	}, +	.probe = kxsd9_probe, +	.remove = kxsd9_remove, +	.id_table = kxsd9_id, +}; +module_spi_driver(kxsd9_driver); + +MODULE_AUTHOR("Jonathan Cameron <jic23@kernel.org>"); +MODULE_DESCRIPTION("Kionix KXSD9 SPI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/accel/mma8452.c b/drivers/iio/accel/mma8452.c new file mode 100644 index 00000000000..2a5fa9a436e --- /dev/null +++ b/drivers/iio/accel/mma8452.c @@ -0,0 +1,445 @@ +/* + * mma8452.c - Support for Freescale MMA8452Q 3-axis 12-bit accelerometer + * + * Copyright 2014 Peter Meerwald <pmeerw@pmeerw.net> + * + * This file is subject to the terms and conditions of version 2 of + * the GNU General Public License.  See the file COPYING in the main + * directory of this archive for more details. + * + * 7-bit I2C slave address 0x1c/0x1d (pin selectable) + * + * TODO: interrupt, thresholding, orientation / freefall events, autosleep + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/buffer.h> +#include <linux/iio/triggered_buffer.h> +#include <linux/delay.h> + +#define MMA8452_STATUS 0x00 +#define MMA8452_OUT_X 0x01 /* MSB first, 12-bit  */ +#define MMA8452_OUT_Y 0x03 +#define MMA8452_OUT_Z 0x05 +#define MMA8452_WHO_AM_I 0x0d +#define MMA8452_DATA_CFG 0x0e +#define MMA8452_OFF_X 0x2f +#define MMA8452_OFF_Y 0x30 +#define MMA8452_OFF_Z 0x31 +#define MMA8452_CTRL_REG1 0x2a +#define MMA8452_CTRL_REG2 0x2b + +#define MMA8452_STATUS_DRDY (BIT(2) | BIT(1) | BIT(0)) + +#define MMA8452_CTRL_DR_MASK (BIT(5) | BIT(4) | BIT(3)) +#define MMA8452_CTRL_DR_SHIFT 3 +#define MMA8452_CTRL_DR_DEFAULT 0x4 /* 50 Hz sample frequency */ +#define MMA8452_CTRL_ACTIVE BIT(0) + +#define MMA8452_DATA_CFG_FS_MASK (BIT(1) | BIT(0)) +#define MMA8452_DATA_CFG_FS_2G 0 +#define MMA8452_DATA_CFG_FS_4G 1 +#define MMA8452_DATA_CFG_FS_8G 2 + +#define MMA8452_DEVICE_ID 0x2a + +struct mma8452_data { +	struct i2c_client *client; +	struct mutex lock; +	u8 ctrl_reg1; +	u8 data_cfg; +}; + +static int mma8452_drdy(struct mma8452_data *data) +{ +	int tries = 150; + +	while (tries-- > 0) { +		int ret = i2c_smbus_read_byte_data(data->client, +			MMA8452_STATUS); +		if (ret < 0) +			return ret; +		if ((ret & MMA8452_STATUS_DRDY) == MMA8452_STATUS_DRDY) +			return 0; +		msleep(20); +	} + +	dev_err(&data->client->dev, "data not ready\n"); +	return -EIO; +} + +static int mma8452_read(struct mma8452_data *data, __be16 buf[3]) +{ +	int ret = mma8452_drdy(data); +	if (ret < 0) +		return ret; +	return i2c_smbus_read_i2c_block_data(data->client, +		MMA8452_OUT_X, 3 * sizeof(__be16), (u8 *) buf); +} + +static ssize_t mma8452_show_int_plus_micros(char *buf, +	const int (*vals)[2], int n) +{ +	size_t len = 0; + +	while (n-- > 0) +		len += scnprintf(buf + len, PAGE_SIZE - len, +			"%d.%06d ", vals[n][0], vals[n][1]); + +	/* replace trailing space by newline */ +	buf[len - 1] = '\n'; + +	return len; +} + +static int mma8452_get_int_plus_micros_index(const int (*vals)[2], int n, +					int val, int val2) +{ +	while (n-- > 0) +		if (val == vals[n][0] && val2 == vals[n][1]) +			return n; + +	return -EINVAL; +} + +static const int mma8452_samp_freq[8][2] = { +	{800, 0}, {400, 0}, {200, 0}, {100, 0}, {50, 0}, {12, 500000}, +	{6, 250000}, {1, 560000} +}; + +/*  + * Hardware has fullscale of -2G, -4G, -8G corresponding to raw value -2048 + * The userspace interface uses m/s^2 and we declare micro units + * So scale factor is given by: + * 	g * N * 1000000 / 2048 for N = 2, 4, 8 and g=9.80665 + */ +static const int mma8452_scales[3][2] = { +	{0, 9577}, {0, 19154}, {0, 38307} +}; + +static ssize_t mma8452_show_samp_freq_avail(struct device *dev, +				struct device_attribute *attr, char *buf) +{ +	return mma8452_show_int_plus_micros(buf, mma8452_samp_freq, +		ARRAY_SIZE(mma8452_samp_freq)); +} + +static ssize_t mma8452_show_scale_avail(struct device *dev, +				struct device_attribute *attr, char *buf) +{ +	return mma8452_show_int_plus_micros(buf, mma8452_scales, +		ARRAY_SIZE(mma8452_scales)); +} + +static IIO_DEV_ATTR_SAMP_FREQ_AVAIL(mma8452_show_samp_freq_avail); +static IIO_DEVICE_ATTR(in_accel_scale_available, S_IRUGO, +	mma8452_show_scale_avail, NULL, 0); + +static int mma8452_get_samp_freq_index(struct mma8452_data *data, +	int val, int val2) +{ +	return mma8452_get_int_plus_micros_index(mma8452_samp_freq, +		ARRAY_SIZE(mma8452_samp_freq), val, val2); +} + +static int mma8452_get_scale_index(struct mma8452_data *data, +	int val, int val2) +{ +	return mma8452_get_int_plus_micros_index(mma8452_scales, +		ARRAY_SIZE(mma8452_scales), val, val2); +} + +static int mma8452_read_raw(struct iio_dev *indio_dev, +			    struct iio_chan_spec const *chan, +			    int *val, int *val2, long mask) +{ +	struct mma8452_data *data = iio_priv(indio_dev); +	__be16 buffer[3]; +	int i, ret; + +	switch (mask) { +	case IIO_CHAN_INFO_RAW: +		if (iio_buffer_enabled(indio_dev)) +			return -EBUSY; + +		mutex_lock(&data->lock); +		ret = mma8452_read(data, buffer); +		mutex_unlock(&data->lock); +		if (ret < 0) +			return ret; +		*val = sign_extend32( +			be16_to_cpu(buffer[chan->scan_index]) >> 4, 11); +		return IIO_VAL_INT; +	case IIO_CHAN_INFO_SCALE: +		i = data->data_cfg & MMA8452_DATA_CFG_FS_MASK; +		*val = mma8452_scales[i][0]; +		*val2 = mma8452_scales[i][1]; +		return IIO_VAL_INT_PLUS_MICRO; +	case IIO_CHAN_INFO_SAMP_FREQ: +		i = (data->ctrl_reg1 & MMA8452_CTRL_DR_MASK) >> +			MMA8452_CTRL_DR_SHIFT; +		*val = mma8452_samp_freq[i][0]; +		*val2 = mma8452_samp_freq[i][1]; +		return IIO_VAL_INT_PLUS_MICRO; +	case IIO_CHAN_INFO_CALIBBIAS: +		ret = i2c_smbus_read_byte_data(data->client, MMA8452_OFF_X + +			chan->scan_index); +		if (ret < 0) +			return ret; +		*val = sign_extend32(ret, 7); +		return IIO_VAL_INT; +	} +	return -EINVAL; +} + +static int mma8452_standby(struct mma8452_data *data) +{ +	return i2c_smbus_write_byte_data(data->client, MMA8452_CTRL_REG1, +		data->ctrl_reg1 & ~MMA8452_CTRL_ACTIVE); +} + +static int mma8452_active(struct mma8452_data *data) +{ +	return i2c_smbus_write_byte_data(data->client, MMA8452_CTRL_REG1, +		data->ctrl_reg1); +} + +static int mma8452_change_config(struct mma8452_data *data, u8 reg, u8 val) +{ +	int ret; + +	mutex_lock(&data->lock); + +	/* config can only be changed when in standby */ +	ret = mma8452_standby(data); +	if (ret < 0) +		goto fail; + +	ret = i2c_smbus_write_byte_data(data->client, reg, val); +	if (ret < 0) +		goto fail; + +	ret = mma8452_active(data); +	if (ret < 0) +		goto fail; + +	ret = 0; +fail: +	mutex_unlock(&data->lock); +	return ret; +} + +static int mma8452_write_raw(struct iio_dev *indio_dev, +			     struct iio_chan_spec const *chan, +			     int val, int val2, long mask) +{ +	struct mma8452_data *data = iio_priv(indio_dev); +	int i; + +	if (iio_buffer_enabled(indio_dev)) +		return -EBUSY; + +	switch (mask) { +	case IIO_CHAN_INFO_SAMP_FREQ: +		i = mma8452_get_samp_freq_index(data, val, val2); +		if (i < 0) +			return -EINVAL; + +		data->ctrl_reg1 &= ~MMA8452_CTRL_DR_MASK; +		data->ctrl_reg1 |= i << MMA8452_CTRL_DR_SHIFT; +		return mma8452_change_config(data, MMA8452_CTRL_REG1, +			data->ctrl_reg1); +	case IIO_CHAN_INFO_SCALE: +		i = mma8452_get_scale_index(data, val, val2); +		if (i < 0) +			return -EINVAL; +		data->data_cfg &= ~MMA8452_DATA_CFG_FS_MASK; +		data->data_cfg |= i; +		return mma8452_change_config(data, MMA8452_DATA_CFG, +			data->data_cfg); +	case IIO_CHAN_INFO_CALIBBIAS: +		if (val < -128 || val > 127) +			return -EINVAL; +		return mma8452_change_config(data, MMA8452_OFF_X + +			chan->scan_index, val); +	default: +		return -EINVAL; +	} +} + +static irqreturn_t mma8452_trigger_handler(int irq, void *p) +{ +	struct iio_poll_func *pf = p; +	struct iio_dev *indio_dev = pf->indio_dev; +	struct mma8452_data *data = iio_priv(indio_dev); +	u8 buffer[16]; /* 3 16-bit channels + padding + ts */ +	int ret; + +	ret = mma8452_read(data, (__be16 *) buffer); +	if (ret < 0) +		goto done; + +	iio_push_to_buffers_with_timestamp(indio_dev, buffer, +		iio_get_time_ns()); + +done: +	iio_trigger_notify_done(indio_dev->trig); +	return IRQ_HANDLED; +} + +#define MMA8452_CHANNEL(axis, idx) { \ +	.type = IIO_ACCEL, \ +	.modified = 1, \ +	.channel2 = IIO_MOD_##axis, \ +	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ +		BIT(IIO_CHAN_INFO_CALIBBIAS), \ +	.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ +		BIT(IIO_CHAN_INFO_SCALE), \ +	.scan_index = idx, \ +	.scan_type = { \ +		.sign = 's', \ +		.realbits = 12, \ +		.storagebits = 16, \ +		.shift = 4, \ +		.endianness = IIO_BE, \ +	}, \ +} + +static const struct iio_chan_spec mma8452_channels[] = { +	MMA8452_CHANNEL(X, 0), +	MMA8452_CHANNEL(Y, 1), +	MMA8452_CHANNEL(Z, 2), +	IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static struct attribute *mma8452_attributes[] = { +	&iio_dev_attr_sampling_frequency_available.dev_attr.attr, +	&iio_dev_attr_in_accel_scale_available.dev_attr.attr, +	NULL +}; + +static const struct attribute_group mma8452_group = { +	.attrs = mma8452_attributes, +}; + +static const struct iio_info mma8452_info = { +	.attrs = &mma8452_group, +	.read_raw = &mma8452_read_raw, +	.write_raw = &mma8452_write_raw, +	.driver_module = THIS_MODULE, +}; + +static const unsigned long mma8452_scan_masks[] = {0x7, 0}; + +static int mma8452_probe(struct i2c_client *client, +			 const struct i2c_device_id *id) +{ +	struct mma8452_data *data; +	struct iio_dev *indio_dev; +	int ret; + +	ret = i2c_smbus_read_byte_data(client, MMA8452_WHO_AM_I); +	if (ret < 0) +		return ret; +	if (ret != MMA8452_DEVICE_ID) +		return -ENODEV; + +	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); +	if (!indio_dev) +		return -ENOMEM; + +	data = iio_priv(indio_dev); +	data->client = client; +	mutex_init(&data->lock); + +	i2c_set_clientdata(client, indio_dev); +	indio_dev->info = &mma8452_info; +	indio_dev->name = id->name; +	indio_dev->dev.parent = &client->dev; +	indio_dev->modes = INDIO_DIRECT_MODE; +	indio_dev->channels = mma8452_channels; +	indio_dev->num_channels = ARRAY_SIZE(mma8452_channels); +	indio_dev->available_scan_masks = mma8452_scan_masks; + +	data->ctrl_reg1 = MMA8452_CTRL_ACTIVE | +		(MMA8452_CTRL_DR_DEFAULT << MMA8452_CTRL_DR_SHIFT); +	ret = i2c_smbus_write_byte_data(client, MMA8452_CTRL_REG1, +		data->ctrl_reg1); +	if (ret < 0) +		return ret; + +	data->data_cfg = MMA8452_DATA_CFG_FS_2G; +	ret = i2c_smbus_write_byte_data(client, MMA8452_DATA_CFG, +		data->data_cfg); +	if (ret < 0) +		return ret; + +	ret = iio_triggered_buffer_setup(indio_dev, NULL, +		mma8452_trigger_handler, NULL); +	if (ret < 0) +		return ret; + +	ret = iio_device_register(indio_dev); +	if (ret < 0) +		goto buffer_cleanup; +	return 0; + +buffer_cleanup: +	iio_triggered_buffer_cleanup(indio_dev); +	return ret; +} + +static int mma8452_remove(struct i2c_client *client) +{ +	struct iio_dev *indio_dev = i2c_get_clientdata(client); + +	iio_device_unregister(indio_dev); +	iio_triggered_buffer_cleanup(indio_dev); +	mma8452_standby(iio_priv(indio_dev)); + +	return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int mma8452_suspend(struct device *dev) +{ +	return mma8452_standby(iio_priv(i2c_get_clientdata( +		to_i2c_client(dev)))); +} + +static int mma8452_resume(struct device *dev) +{ +	return mma8452_active(iio_priv(i2c_get_clientdata( +		to_i2c_client(dev)))); +} + +static SIMPLE_DEV_PM_OPS(mma8452_pm_ops, mma8452_suspend, mma8452_resume); +#define MMA8452_PM_OPS (&mma8452_pm_ops) +#else +#define MMA8452_PM_OPS NULL +#endif + +static const struct i2c_device_id mma8452_id[] = { +	{ "mma8452", 0 }, +	{ } +}; +MODULE_DEVICE_TABLE(i2c, mma8452_id); + +static struct i2c_driver mma8452_driver = { +	.driver = { +		.name	= "mma8452", +		.pm	= MMA8452_PM_OPS, +	}, +	.probe = mma8452_probe, +	.remove = mma8452_remove, +	.id_table = mma8452_id, +}; +module_i2c_driver(mma8452_driver); + +MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>"); +MODULE_DESCRIPTION("Freescale MMA8452 accelerometer driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/accel/st_accel.h b/drivers/iio/accel/st_accel.h new file mode 100644 index 00000000000..c3877630b2e --- /dev/null +++ b/drivers/iio/accel/st_accel.h @@ -0,0 +1,56 @@ +/* + * STMicroelectronics accelerometers driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca <denis.ciocca@st.com> + * v. 1.0.0 + * Licensed under the GPL-2. + */ + +#ifndef ST_ACCEL_H +#define ST_ACCEL_H + +#include <linux/types.h> +#include <linux/iio/common/st_sensors.h> + +#define LSM303DLHC_ACCEL_DEV_NAME	"lsm303dlhc_accel" +#define LIS3DH_ACCEL_DEV_NAME		"lis3dh" +#define LSM330D_ACCEL_DEV_NAME		"lsm330d_accel" +#define LSM330DL_ACCEL_DEV_NAME		"lsm330dl_accel" +#define LSM330DLC_ACCEL_DEV_NAME	"lsm330dlc_accel" +#define LIS331DLH_ACCEL_DEV_NAME	"lis331dlh" +#define LSM303DL_ACCEL_DEV_NAME		"lsm303dl_accel" +#define LSM303DLH_ACCEL_DEV_NAME	"lsm303dlh_accel" +#define LSM303DLM_ACCEL_DEV_NAME	"lsm303dlm_accel" +#define LSM330_ACCEL_DEV_NAME		"lsm330_accel" + +/** +* struct st_sensors_platform_data - default accel platform data +* @drdy_int_pin: default accel DRDY is available on INT1 pin. +*/ +static const struct st_sensors_platform_data default_accel_pdata = { +	.drdy_int_pin = 1, +}; + +int st_accel_common_probe(struct iio_dev *indio_dev, +					struct st_sensors_platform_data *pdata); +void st_accel_common_remove(struct iio_dev *indio_dev); + +#ifdef CONFIG_IIO_BUFFER +int st_accel_allocate_ring(struct iio_dev *indio_dev); +void st_accel_deallocate_ring(struct iio_dev *indio_dev); +int st_accel_trig_set_state(struct iio_trigger *trig, bool state); +#define ST_ACCEL_TRIGGER_SET_STATE (&st_accel_trig_set_state) +#else /* CONFIG_IIO_BUFFER */ +static inline int st_accel_allocate_ring(struct iio_dev *indio_dev) +{ +	return 0; +} +static inline void st_accel_deallocate_ring(struct iio_dev *indio_dev) +{ +} +#define ST_ACCEL_TRIGGER_SET_STATE NULL +#endif /* CONFIG_IIO_BUFFER */ + +#endif /* ST_ACCEL_H */ diff --git a/drivers/iio/accel/st_accel_buffer.c b/drivers/iio/accel/st_accel_buffer.c new file mode 100644 index 00000000000..a1e642ee13d --- /dev/null +++ b/drivers/iio/accel/st_accel_buffer.c @@ -0,0 +1,105 @@ +/* + * STMicroelectronics accelerometers driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca <denis.ciocca@st.com> + * + * Licensed under the GPL-2. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/stat.h> +#include <linux/interrupt.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/iio/iio.h> +#include <linux/iio/buffer.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> + +#include <linux/iio/common/st_sensors.h> +#include "st_accel.h" + +int st_accel_trig_set_state(struct iio_trigger *trig, bool state) +{ +	struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + +	return st_sensors_set_dataready_irq(indio_dev, state); +} + +static int st_accel_buffer_preenable(struct iio_dev *indio_dev) +{ +	return st_sensors_set_enable(indio_dev, true); +} + +static int st_accel_buffer_postenable(struct iio_dev *indio_dev) +{ +	int err; +	struct st_sensor_data *adata = iio_priv(indio_dev); + +	adata->buffer_data = kmalloc(indio_dev->scan_bytes, GFP_KERNEL); +	if (adata->buffer_data == NULL) { +		err = -ENOMEM; +		goto allocate_memory_error; +	} + +	err = st_sensors_set_axis_enable(indio_dev, +					(u8)indio_dev->active_scan_mask[0]); +	if (err < 0) +		goto st_accel_buffer_postenable_error; + +	err = iio_triggered_buffer_postenable(indio_dev); +	if (err < 0) +		goto st_accel_buffer_postenable_error; + +	return err; + +st_accel_buffer_postenable_error: +	kfree(adata->buffer_data); +allocate_memory_error: +	return err; +} + +static int st_accel_buffer_predisable(struct iio_dev *indio_dev) +{ +	int err; +	struct st_sensor_data *adata = iio_priv(indio_dev); + +	err = iio_triggered_buffer_predisable(indio_dev); +	if (err < 0) +		goto st_accel_buffer_predisable_error; + +	err = st_sensors_set_axis_enable(indio_dev, ST_SENSORS_ENABLE_ALL_AXIS); +	if (err < 0) +		goto st_accel_buffer_predisable_error; + +	err = st_sensors_set_enable(indio_dev, false); + +st_accel_buffer_predisable_error: +	kfree(adata->buffer_data); +	return err; +} + +static const struct iio_buffer_setup_ops st_accel_buffer_setup_ops = { +	.preenable = &st_accel_buffer_preenable, +	.postenable = &st_accel_buffer_postenable, +	.predisable = &st_accel_buffer_predisable, +}; + +int st_accel_allocate_ring(struct iio_dev *indio_dev) +{ +	return iio_triggered_buffer_setup(indio_dev, &iio_pollfunc_store_time, +		&st_sensors_trigger_handler, &st_accel_buffer_setup_ops); +} + +void st_accel_deallocate_ring(struct iio_dev *indio_dev) +{ +	iio_triggered_buffer_cleanup(indio_dev); +} + +MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics accelerometers buffer"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/accel/st_accel_core.c b/drivers/iio/accel/st_accel_core.c new file mode 100644 index 00000000000..a2abf7c2ce3 --- /dev/null +++ b/drivers/iio/accel/st_accel_core.c @@ -0,0 +1,532 @@ +/* + * STMicroelectronics accelerometers driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca <denis.ciocca@st.com> + * + * Licensed under the GPL-2. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/types.h> +#include <linux/mutex.h> +#include <linux/interrupt.h> +#include <linux/i2c.h> +#include <linux/gpio.h> +#include <linux/irq.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/trigger.h> +#include <linux/iio/buffer.h> + +#include <linux/iio/common/st_sensors.h> +#include "st_accel.h" + +#define ST_ACCEL_NUMBER_DATA_CHANNELS		3 + +/* DEFAULT VALUE FOR SENSORS */ +#define ST_ACCEL_DEFAULT_OUT_X_L_ADDR		0x28 +#define ST_ACCEL_DEFAULT_OUT_Y_L_ADDR		0x2a +#define ST_ACCEL_DEFAULT_OUT_Z_L_ADDR		0x2c + +/* FULLSCALE */ +#define ST_ACCEL_FS_AVL_2G			2 +#define ST_ACCEL_FS_AVL_4G			4 +#define ST_ACCEL_FS_AVL_6G			6 +#define ST_ACCEL_FS_AVL_8G			8 +#define ST_ACCEL_FS_AVL_16G			16 + +/* CUSTOM VALUES FOR SENSOR 1 */ +#define ST_ACCEL_1_WAI_EXP			0x33 +#define ST_ACCEL_1_ODR_ADDR			0x20 +#define ST_ACCEL_1_ODR_MASK			0xf0 +#define ST_ACCEL_1_ODR_AVL_1HZ_VAL		0x01 +#define ST_ACCEL_1_ODR_AVL_10HZ_VAL		0x02 +#define ST_ACCEL_1_ODR_AVL_25HZ_VAL		0x03 +#define ST_ACCEL_1_ODR_AVL_50HZ_VAL		0x04 +#define ST_ACCEL_1_ODR_AVL_100HZ_VAL		0x05 +#define ST_ACCEL_1_ODR_AVL_200HZ_VAL		0x06 +#define ST_ACCEL_1_ODR_AVL_400HZ_VAL		0x07 +#define ST_ACCEL_1_ODR_AVL_1600HZ_VAL		0x08 +#define ST_ACCEL_1_FS_ADDR			0x23 +#define ST_ACCEL_1_FS_MASK			0x30 +#define ST_ACCEL_1_FS_AVL_2_VAL			0x00 +#define ST_ACCEL_1_FS_AVL_4_VAL			0x01 +#define ST_ACCEL_1_FS_AVL_8_VAL			0x02 +#define ST_ACCEL_1_FS_AVL_16_VAL		0x03 +#define ST_ACCEL_1_FS_AVL_2_GAIN		IIO_G_TO_M_S_2(1000) +#define ST_ACCEL_1_FS_AVL_4_GAIN		IIO_G_TO_M_S_2(2000) +#define ST_ACCEL_1_FS_AVL_8_GAIN		IIO_G_TO_M_S_2(4000) +#define ST_ACCEL_1_FS_AVL_16_GAIN		IIO_G_TO_M_S_2(12000) +#define ST_ACCEL_1_BDU_ADDR			0x23 +#define ST_ACCEL_1_BDU_MASK			0x80 +#define ST_ACCEL_1_DRDY_IRQ_ADDR		0x22 +#define ST_ACCEL_1_DRDY_IRQ_INT1_MASK		0x10 +#define ST_ACCEL_1_DRDY_IRQ_INT2_MASK		0x08 +#define ST_ACCEL_1_MULTIREAD_BIT		true + +/* CUSTOM VALUES FOR SENSOR 2 */ +#define ST_ACCEL_2_WAI_EXP			0x32 +#define ST_ACCEL_2_ODR_ADDR			0x20 +#define ST_ACCEL_2_ODR_MASK			0x18 +#define ST_ACCEL_2_ODR_AVL_50HZ_VAL		0x00 +#define ST_ACCEL_2_ODR_AVL_100HZ_VAL		0x01 +#define ST_ACCEL_2_ODR_AVL_400HZ_VAL		0x02 +#define ST_ACCEL_2_ODR_AVL_1000HZ_VAL		0x03 +#define ST_ACCEL_2_PW_ADDR			0x20 +#define ST_ACCEL_2_PW_MASK			0xe0 +#define ST_ACCEL_2_FS_ADDR			0x23 +#define ST_ACCEL_2_FS_MASK			0x30 +#define ST_ACCEL_2_FS_AVL_2_VAL			0X00 +#define ST_ACCEL_2_FS_AVL_4_VAL			0X01 +#define ST_ACCEL_2_FS_AVL_8_VAL			0x03 +#define ST_ACCEL_2_FS_AVL_2_GAIN		IIO_G_TO_M_S_2(1000) +#define ST_ACCEL_2_FS_AVL_4_GAIN		IIO_G_TO_M_S_2(2000) +#define ST_ACCEL_2_FS_AVL_8_GAIN		IIO_G_TO_M_S_2(3900) +#define ST_ACCEL_2_BDU_ADDR			0x23 +#define ST_ACCEL_2_BDU_MASK			0x80 +#define ST_ACCEL_2_DRDY_IRQ_ADDR		0x22 +#define ST_ACCEL_2_DRDY_IRQ_INT1_MASK		0x02 +#define ST_ACCEL_2_DRDY_IRQ_INT2_MASK		0x10 +#define ST_ACCEL_2_MULTIREAD_BIT		true + +/* CUSTOM VALUES FOR SENSOR 3 */ +#define ST_ACCEL_3_WAI_EXP			0x40 +#define ST_ACCEL_3_ODR_ADDR			0x20 +#define ST_ACCEL_3_ODR_MASK			0xf0 +#define ST_ACCEL_3_ODR_AVL_3HZ_VAL		0x01 +#define ST_ACCEL_3_ODR_AVL_6HZ_VAL		0x02 +#define ST_ACCEL_3_ODR_AVL_12HZ_VAL		0x03 +#define ST_ACCEL_3_ODR_AVL_25HZ_VAL		0x04 +#define ST_ACCEL_3_ODR_AVL_50HZ_VAL		0x05 +#define ST_ACCEL_3_ODR_AVL_100HZ_VAL		0x06 +#define ST_ACCEL_3_ODR_AVL_200HZ_VAL		0x07 +#define ST_ACCEL_3_ODR_AVL_400HZ_VAL		0x08 +#define ST_ACCEL_3_ODR_AVL_800HZ_VAL		0x09 +#define ST_ACCEL_3_ODR_AVL_1600HZ_VAL		0x0a +#define ST_ACCEL_3_FS_ADDR			0x24 +#define ST_ACCEL_3_FS_MASK			0x38 +#define ST_ACCEL_3_FS_AVL_2_VAL			0X00 +#define ST_ACCEL_3_FS_AVL_4_VAL			0X01 +#define ST_ACCEL_3_FS_AVL_6_VAL			0x02 +#define ST_ACCEL_3_FS_AVL_8_VAL			0x03 +#define ST_ACCEL_3_FS_AVL_16_VAL		0x04 +#define ST_ACCEL_3_FS_AVL_2_GAIN		IIO_G_TO_M_S_2(61) +#define ST_ACCEL_3_FS_AVL_4_GAIN		IIO_G_TO_M_S_2(122) +#define ST_ACCEL_3_FS_AVL_6_GAIN		IIO_G_TO_M_S_2(183) +#define ST_ACCEL_3_FS_AVL_8_GAIN		IIO_G_TO_M_S_2(244) +#define ST_ACCEL_3_FS_AVL_16_GAIN		IIO_G_TO_M_S_2(732) +#define ST_ACCEL_3_BDU_ADDR			0x20 +#define ST_ACCEL_3_BDU_MASK			0x08 +#define ST_ACCEL_3_DRDY_IRQ_ADDR		0x23 +#define ST_ACCEL_3_DRDY_IRQ_INT1_MASK		0x80 +#define ST_ACCEL_3_DRDY_IRQ_INT2_MASK		0x00 +#define ST_ACCEL_3_IG1_EN_ADDR			0x23 +#define ST_ACCEL_3_IG1_EN_MASK			0x08 +#define ST_ACCEL_3_MULTIREAD_BIT		false + +static const struct iio_chan_spec st_accel_12bit_channels[] = { +	ST_SENSORS_LSM_CHANNELS(IIO_ACCEL, +			BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), +			ST_SENSORS_SCAN_X, 1, IIO_MOD_X, 's', IIO_LE, 12, 16, +			ST_ACCEL_DEFAULT_OUT_X_L_ADDR), +	ST_SENSORS_LSM_CHANNELS(IIO_ACCEL, +			BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), +			ST_SENSORS_SCAN_Y, 1, IIO_MOD_Y, 's', IIO_LE, 12, 16, +			ST_ACCEL_DEFAULT_OUT_Y_L_ADDR), +	ST_SENSORS_LSM_CHANNELS(IIO_ACCEL, +			BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), +			ST_SENSORS_SCAN_Z, 1, IIO_MOD_Z, 's', IIO_LE, 12, 16, +			ST_ACCEL_DEFAULT_OUT_Z_L_ADDR), +	IIO_CHAN_SOFT_TIMESTAMP(3) +}; + +static const struct iio_chan_spec st_accel_16bit_channels[] = { +	ST_SENSORS_LSM_CHANNELS(IIO_ACCEL, +			BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), +			ST_SENSORS_SCAN_X, 1, IIO_MOD_X, 's', IIO_LE, 16, 16, +			ST_ACCEL_DEFAULT_OUT_X_L_ADDR), +	ST_SENSORS_LSM_CHANNELS(IIO_ACCEL, +			BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), +			ST_SENSORS_SCAN_Y, 1, IIO_MOD_Y, 's', IIO_LE, 16, 16, +			ST_ACCEL_DEFAULT_OUT_Y_L_ADDR), +	ST_SENSORS_LSM_CHANNELS(IIO_ACCEL, +			BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), +			ST_SENSORS_SCAN_Z, 1, IIO_MOD_Z, 's', IIO_LE, 16, 16, +			ST_ACCEL_DEFAULT_OUT_Z_L_ADDR), +	IIO_CHAN_SOFT_TIMESTAMP(3) +}; + +static const struct st_sensors st_accel_sensors[] = { +	{ +		.wai = ST_ACCEL_1_WAI_EXP, +		.sensors_supported = { +			[0] = LIS3DH_ACCEL_DEV_NAME, +			[1] = LSM303DLHC_ACCEL_DEV_NAME, +			[2] = LSM330D_ACCEL_DEV_NAME, +			[3] = LSM330DL_ACCEL_DEV_NAME, +			[4] = LSM330DLC_ACCEL_DEV_NAME, +		}, +		.ch = (struct iio_chan_spec *)st_accel_12bit_channels, +		.odr = { +			.addr = ST_ACCEL_1_ODR_ADDR, +			.mask = ST_ACCEL_1_ODR_MASK, +			.odr_avl = { +				{ 1, ST_ACCEL_1_ODR_AVL_1HZ_VAL, }, +				{ 10, ST_ACCEL_1_ODR_AVL_10HZ_VAL, }, +				{ 25, ST_ACCEL_1_ODR_AVL_25HZ_VAL, }, +				{ 50, ST_ACCEL_1_ODR_AVL_50HZ_VAL, }, +				{ 100, ST_ACCEL_1_ODR_AVL_100HZ_VAL, }, +				{ 200, ST_ACCEL_1_ODR_AVL_200HZ_VAL, }, +				{ 400, ST_ACCEL_1_ODR_AVL_400HZ_VAL, }, +				{ 1600, ST_ACCEL_1_ODR_AVL_1600HZ_VAL, }, +			}, +		}, +		.pw = { +			.addr = ST_ACCEL_1_ODR_ADDR, +			.mask = ST_ACCEL_1_ODR_MASK, +			.value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE, +		}, +		.enable_axis = { +			.addr = ST_SENSORS_DEFAULT_AXIS_ADDR, +			.mask = ST_SENSORS_DEFAULT_AXIS_MASK, +		}, +		.fs = { +			.addr = ST_ACCEL_1_FS_ADDR, +			.mask = ST_ACCEL_1_FS_MASK, +			.fs_avl = { +				[0] = { +					.num = ST_ACCEL_FS_AVL_2G, +					.value = ST_ACCEL_1_FS_AVL_2_VAL, +					.gain = ST_ACCEL_1_FS_AVL_2_GAIN, +				}, +				[1] = { +					.num = ST_ACCEL_FS_AVL_4G, +					.value = ST_ACCEL_1_FS_AVL_4_VAL, +					.gain = ST_ACCEL_1_FS_AVL_4_GAIN, +				}, +				[2] = { +					.num = ST_ACCEL_FS_AVL_8G, +					.value = ST_ACCEL_1_FS_AVL_8_VAL, +					.gain = ST_ACCEL_1_FS_AVL_8_GAIN, +				}, +				[3] = { +					.num = ST_ACCEL_FS_AVL_16G, +					.value = ST_ACCEL_1_FS_AVL_16_VAL, +					.gain = ST_ACCEL_1_FS_AVL_16_GAIN, +				}, +			}, +		}, +		.bdu = { +			.addr = ST_ACCEL_1_BDU_ADDR, +			.mask = ST_ACCEL_1_BDU_MASK, +		}, +		.drdy_irq = { +			.addr = ST_ACCEL_1_DRDY_IRQ_ADDR, +			.mask_int1 = ST_ACCEL_1_DRDY_IRQ_INT1_MASK, +			.mask_int2 = ST_ACCEL_1_DRDY_IRQ_INT2_MASK, +		}, +		.multi_read_bit = ST_ACCEL_1_MULTIREAD_BIT, +		.bootime = 2, +	}, +	{ +		.wai = ST_ACCEL_2_WAI_EXP, +		.sensors_supported = { +			[0] = LIS331DLH_ACCEL_DEV_NAME, +			[1] = LSM303DL_ACCEL_DEV_NAME, +			[2] = LSM303DLH_ACCEL_DEV_NAME, +			[3] = LSM303DLM_ACCEL_DEV_NAME, +		}, +		.ch = (struct iio_chan_spec *)st_accel_12bit_channels, +		.odr = { +			.addr = ST_ACCEL_2_ODR_ADDR, +			.mask = ST_ACCEL_2_ODR_MASK, +			.odr_avl = { +				{ 50, ST_ACCEL_2_ODR_AVL_50HZ_VAL, }, +				{ 100, ST_ACCEL_2_ODR_AVL_100HZ_VAL, }, +				{ 400, ST_ACCEL_2_ODR_AVL_400HZ_VAL, }, +				{ 1000, ST_ACCEL_2_ODR_AVL_1000HZ_VAL, }, +			}, +		}, +		.pw = { +			.addr = ST_ACCEL_2_PW_ADDR, +			.mask = ST_ACCEL_2_PW_MASK, +			.value_on = ST_SENSORS_DEFAULT_POWER_ON_VALUE, +			.value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE, +		}, +		.enable_axis = { +			.addr = ST_SENSORS_DEFAULT_AXIS_ADDR, +			.mask = ST_SENSORS_DEFAULT_AXIS_MASK, +		}, +		.fs = { +			.addr = ST_ACCEL_2_FS_ADDR, +			.mask = ST_ACCEL_2_FS_MASK, +			.fs_avl = { +				[0] = { +					.num = ST_ACCEL_FS_AVL_2G, +					.value = ST_ACCEL_2_FS_AVL_2_VAL, +					.gain = ST_ACCEL_2_FS_AVL_2_GAIN, +				}, +				[1] = { +					.num = ST_ACCEL_FS_AVL_4G, +					.value = ST_ACCEL_2_FS_AVL_4_VAL, +					.gain = ST_ACCEL_2_FS_AVL_4_GAIN, +				}, +				[2] = { +					.num = ST_ACCEL_FS_AVL_8G, +					.value = ST_ACCEL_2_FS_AVL_8_VAL, +					.gain = ST_ACCEL_2_FS_AVL_8_GAIN, +				}, +			}, +		}, +		.bdu = { +			.addr = ST_ACCEL_2_BDU_ADDR, +			.mask = ST_ACCEL_2_BDU_MASK, +		}, +		.drdy_irq = { +			.addr = ST_ACCEL_2_DRDY_IRQ_ADDR, +			.mask_int1 = ST_ACCEL_2_DRDY_IRQ_INT1_MASK, +			.mask_int2 = ST_ACCEL_2_DRDY_IRQ_INT2_MASK, +		}, +		.multi_read_bit = ST_ACCEL_2_MULTIREAD_BIT, +		.bootime = 2, +	}, +	{ +		.wai = ST_ACCEL_3_WAI_EXP, +		.sensors_supported = { +			[0] = LSM330_ACCEL_DEV_NAME, +		}, +		.ch = (struct iio_chan_spec *)st_accel_16bit_channels, +		.odr = { +			.addr = ST_ACCEL_3_ODR_ADDR, +			.mask = ST_ACCEL_3_ODR_MASK, +			.odr_avl = { +				{ 3, ST_ACCEL_3_ODR_AVL_3HZ_VAL }, +				{ 6, ST_ACCEL_3_ODR_AVL_6HZ_VAL, }, +				{ 12, ST_ACCEL_3_ODR_AVL_12HZ_VAL, }, +				{ 25, ST_ACCEL_3_ODR_AVL_25HZ_VAL, }, +				{ 50, ST_ACCEL_3_ODR_AVL_50HZ_VAL, }, +				{ 100, ST_ACCEL_3_ODR_AVL_100HZ_VAL, }, +				{ 200, ST_ACCEL_3_ODR_AVL_200HZ_VAL, }, +				{ 400, ST_ACCEL_3_ODR_AVL_400HZ_VAL, }, +				{ 800, ST_ACCEL_3_ODR_AVL_800HZ_VAL, }, +				{ 1600, ST_ACCEL_3_ODR_AVL_1600HZ_VAL, }, +			}, +		}, +		.pw = { +			.addr = ST_ACCEL_3_ODR_ADDR, +			.mask = ST_ACCEL_3_ODR_MASK, +			.value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE, +		}, +		.enable_axis = { +			.addr = ST_SENSORS_DEFAULT_AXIS_ADDR, +			.mask = ST_SENSORS_DEFAULT_AXIS_MASK, +		}, +		.fs = { +			.addr = ST_ACCEL_3_FS_ADDR, +			.mask = ST_ACCEL_3_FS_MASK, +			.fs_avl = { +				[0] = { +					.num = ST_ACCEL_FS_AVL_2G, +					.value = ST_ACCEL_3_FS_AVL_2_VAL, +					.gain = ST_ACCEL_3_FS_AVL_2_GAIN, +				}, +				[1] = { +					.num = ST_ACCEL_FS_AVL_4G, +					.value = ST_ACCEL_3_FS_AVL_4_VAL, +					.gain = ST_ACCEL_3_FS_AVL_4_GAIN, +				}, +				[2] = { +					.num = ST_ACCEL_FS_AVL_6G, +					.value = ST_ACCEL_3_FS_AVL_6_VAL, +					.gain = ST_ACCEL_3_FS_AVL_6_GAIN, +				}, +				[3] = { +					.num = ST_ACCEL_FS_AVL_8G, +					.value = ST_ACCEL_3_FS_AVL_8_VAL, +					.gain = ST_ACCEL_3_FS_AVL_8_GAIN, +				}, +				[4] = { +					.num = ST_ACCEL_FS_AVL_16G, +					.value = ST_ACCEL_3_FS_AVL_16_VAL, +					.gain = ST_ACCEL_3_FS_AVL_16_GAIN, +				}, +			}, +		}, +		.bdu = { +			.addr = ST_ACCEL_3_BDU_ADDR, +			.mask = ST_ACCEL_3_BDU_MASK, +		}, +		.drdy_irq = { +			.addr = ST_ACCEL_3_DRDY_IRQ_ADDR, +			.mask_int1 = ST_ACCEL_3_DRDY_IRQ_INT1_MASK, +			.mask_int2 = ST_ACCEL_3_DRDY_IRQ_INT2_MASK, +			.ig1 = { +				.en_addr = ST_ACCEL_3_IG1_EN_ADDR, +				.en_mask = ST_ACCEL_3_IG1_EN_MASK, +			}, +		}, +		.multi_read_bit = ST_ACCEL_3_MULTIREAD_BIT, +		.bootime = 2, +	}, +}; + +static int st_accel_read_raw(struct iio_dev *indio_dev, +			struct iio_chan_spec const *ch, int *val, +							int *val2, long mask) +{ +	int err; +	struct st_sensor_data *adata = iio_priv(indio_dev); + +	switch (mask) { +	case IIO_CHAN_INFO_RAW: +		err = st_sensors_read_info_raw(indio_dev, ch, val); +		if (err < 0) +			goto read_error; + +		return IIO_VAL_INT; +	case IIO_CHAN_INFO_SCALE: +		*val = 0; +		*val2 = adata->current_fullscale->gain; +		return IIO_VAL_INT_PLUS_MICRO; +	default: +		return -EINVAL; +	} + +read_error: +	return err; +} + +static int st_accel_write_raw(struct iio_dev *indio_dev, +		struct iio_chan_spec const *chan, int val, int val2, long mask) +{ +	int err; + +	switch (mask) { +	case IIO_CHAN_INFO_SCALE: +		err = st_sensors_set_fullscale_by_gain(indio_dev, val2); +		break; +	default: +		return -EINVAL; +	} + +	return err; +} + +static ST_SENSOR_DEV_ATTR_SAMP_FREQ(); +static ST_SENSORS_DEV_ATTR_SAMP_FREQ_AVAIL(); +static ST_SENSORS_DEV_ATTR_SCALE_AVAIL(in_accel_scale_available); + +static struct attribute *st_accel_attributes[] = { +	&iio_dev_attr_sampling_frequency_available.dev_attr.attr, +	&iio_dev_attr_in_accel_scale_available.dev_attr.attr, +	&iio_dev_attr_sampling_frequency.dev_attr.attr, +	NULL, +}; + +static const struct attribute_group st_accel_attribute_group = { +	.attrs = st_accel_attributes, +}; + +static const struct iio_info accel_info = { +	.driver_module = THIS_MODULE, +	.attrs = &st_accel_attribute_group, +	.read_raw = &st_accel_read_raw, +	.write_raw = &st_accel_write_raw, +}; + +#ifdef CONFIG_IIO_TRIGGER +static const struct iio_trigger_ops st_accel_trigger_ops = { +	.owner = THIS_MODULE, +	.set_trigger_state = ST_ACCEL_TRIGGER_SET_STATE, +}; +#define ST_ACCEL_TRIGGER_OPS (&st_accel_trigger_ops) +#else +#define ST_ACCEL_TRIGGER_OPS NULL +#endif + +int st_accel_common_probe(struct iio_dev *indio_dev, +				struct st_sensors_platform_data *plat_data) +{ +	struct st_sensor_data *adata = iio_priv(indio_dev); +	int irq = adata->get_irq_data_ready(indio_dev); +	int err; + +	indio_dev->modes = INDIO_DIRECT_MODE; +	indio_dev->info = &accel_info; + +	st_sensors_power_enable(indio_dev); + +	err = st_sensors_check_device_support(indio_dev, +				ARRAY_SIZE(st_accel_sensors), st_accel_sensors); +	if (err < 0) +		return err; + +	adata->num_data_channels = ST_ACCEL_NUMBER_DATA_CHANNELS; +	adata->multiread_bit = adata->sensor->multi_read_bit; +	indio_dev->channels = adata->sensor->ch; +	indio_dev->num_channels = ST_SENSORS_NUMBER_ALL_CHANNELS; + +	adata->current_fullscale = (struct st_sensor_fullscale_avl *) +						&adata->sensor->fs.fs_avl[0]; +	adata->odr = adata->sensor->odr.odr_avl[0].hz; + +	if (!plat_data) +		plat_data = +			(struct st_sensors_platform_data *)&default_accel_pdata; + +	err = st_sensors_init_sensor(indio_dev, plat_data); +	if (err < 0) +		return err; + +	err = st_accel_allocate_ring(indio_dev); +	if (err < 0) +		return err; + +	if (irq > 0) { +		err = st_sensors_allocate_trigger(indio_dev, +						 ST_ACCEL_TRIGGER_OPS); +		if (err < 0) +			goto st_accel_probe_trigger_error; +	} + +	err = iio_device_register(indio_dev); +	if (err) +		goto st_accel_device_register_error; + +	dev_info(&indio_dev->dev, "registered accelerometer %s\n", +		 indio_dev->name); + +	return 0; + +st_accel_device_register_error: +	if (irq > 0) +		st_sensors_deallocate_trigger(indio_dev); +st_accel_probe_trigger_error: +	st_accel_deallocate_ring(indio_dev); + +	return err; +} +EXPORT_SYMBOL(st_accel_common_probe); + +void st_accel_common_remove(struct iio_dev *indio_dev) +{ +	struct st_sensor_data *adata = iio_priv(indio_dev); + +	st_sensors_power_disable(indio_dev); + +	iio_device_unregister(indio_dev); +	if (adata->get_irq_data_ready(indio_dev) > 0) +		st_sensors_deallocate_trigger(indio_dev); + +	st_accel_deallocate_ring(indio_dev); +} +EXPORT_SYMBOL(st_accel_common_remove); + +MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics accelerometers driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/accel/st_accel_i2c.c b/drivers/iio/accel/st_accel_i2c.c new file mode 100644 index 00000000000..d7bedbdfc81 --- /dev/null +++ b/drivers/iio/accel/st_accel_i2c.c @@ -0,0 +1,79 @@ +/* + * STMicroelectronics accelerometers driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca <denis.ciocca@st.com> + * + * Licensed under the GPL-2. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/iio/iio.h> + +#include <linux/iio/common/st_sensors.h> +#include <linux/iio/common/st_sensors_i2c.h> +#include "st_accel.h" + +static int st_accel_i2c_probe(struct i2c_client *client, +						const struct i2c_device_id *id) +{ +	struct iio_dev *indio_dev; +	struct st_sensor_data *adata; +	int err; + +	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*adata)); +	if (!indio_dev) +		return -ENOMEM; + +	adata = iio_priv(indio_dev); +	adata->dev = &client->dev; + +	st_sensors_i2c_configure(indio_dev, client, adata); + +	err = st_accel_common_probe(indio_dev, client->dev.platform_data); +	if (err < 0) +		return err; + +	return 0; +} + +static int st_accel_i2c_remove(struct i2c_client *client) +{ +	st_accel_common_remove(i2c_get_clientdata(client)); + +	return 0; +} + +static const struct i2c_device_id st_accel_id_table[] = { +	{ LSM303DLH_ACCEL_DEV_NAME }, +	{ LSM303DLHC_ACCEL_DEV_NAME }, +	{ LIS3DH_ACCEL_DEV_NAME }, +	{ LSM330D_ACCEL_DEV_NAME }, +	{ LSM330DL_ACCEL_DEV_NAME }, +	{ LSM330DLC_ACCEL_DEV_NAME }, +	{ LIS331DLH_ACCEL_DEV_NAME }, +	{ LSM303DL_ACCEL_DEV_NAME }, +	{ LSM303DLM_ACCEL_DEV_NAME }, +	{ LSM330_ACCEL_DEV_NAME }, +	{}, +}; +MODULE_DEVICE_TABLE(i2c, st_accel_id_table); + +static struct i2c_driver st_accel_driver = { +	.driver = { +		.owner = THIS_MODULE, +		.name = "st-accel-i2c", +	}, +	.probe = st_accel_i2c_probe, +	.remove = st_accel_i2c_remove, +	.id_table = st_accel_id_table, +}; +module_i2c_driver(st_accel_driver); + +MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics accelerometers i2c driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/accel/st_accel_spi.c b/drivers/iio/accel/st_accel_spi.c new file mode 100644 index 00000000000..195639646e3 --- /dev/null +++ b/drivers/iio/accel/st_accel_spi.c @@ -0,0 +1,78 @@ +/* + * STMicroelectronics accelerometers driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca <denis.ciocca@st.com> + * + * Licensed under the GPL-2. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/spi/spi.h> +#include <linux/iio/iio.h> + +#include <linux/iio/common/st_sensors.h> +#include <linux/iio/common/st_sensors_spi.h> +#include "st_accel.h" + +static int st_accel_spi_probe(struct spi_device *spi) +{ +	struct iio_dev *indio_dev; +	struct st_sensor_data *adata; +	int err; + +	indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*adata)); +	if (!indio_dev) +		return -ENOMEM; + +	adata = iio_priv(indio_dev); +	adata->dev = &spi->dev; + +	st_sensors_spi_configure(indio_dev, spi, adata); + +	err = st_accel_common_probe(indio_dev, spi->dev.platform_data); +	if (err < 0) +		return err; + +	return 0; +} + +static int st_accel_spi_remove(struct spi_device *spi) +{ +	st_accel_common_remove(spi_get_drvdata(spi)); + +	return 0; +} + +static const struct spi_device_id st_accel_id_table[] = { +	{ LSM303DLH_ACCEL_DEV_NAME }, +	{ LSM303DLHC_ACCEL_DEV_NAME }, +	{ LIS3DH_ACCEL_DEV_NAME }, +	{ LSM330D_ACCEL_DEV_NAME }, +	{ LSM330DL_ACCEL_DEV_NAME }, +	{ LSM330DLC_ACCEL_DEV_NAME }, +	{ LIS331DLH_ACCEL_DEV_NAME }, +	{ LSM303DL_ACCEL_DEV_NAME }, +	{ LSM303DLM_ACCEL_DEV_NAME }, +	{ LSM330_ACCEL_DEV_NAME }, +	{}, +}; +MODULE_DEVICE_TABLE(spi, st_accel_id_table); + +static struct spi_driver st_accel_driver = { +	.driver = { +		.owner = THIS_MODULE, +		.name = "st-accel-spi", +	}, +	.probe = st_accel_spi_probe, +	.remove = st_accel_spi_remove, +	.id_table = st_accel_id_table, +}; +module_spi_driver(st_accel_driver); + +MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics accelerometers spi driver"); +MODULE_LICENSE("GPL v2");  | 
