diff options
Diffstat (limited to 'drivers/iio')
72 files changed, 8580 insertions, 279 deletions
diff --git a/drivers/iio/accel/Kconfig b/drivers/iio/accel/Kconfig index 05e996f..bb59496 100644 --- a/drivers/iio/accel/Kconfig +++ b/drivers/iio/accel/Kconfig @@ -14,4 +14,42 @@ config HID_SENSOR_ACCEL_3D Say yes here to build support for the HID SENSOR accelerometers 3D. +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 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) + select IIO_ST_ACCEL_BUFFER if (IIO_TRIGGERED_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, will be created + these modules: + - 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 + endmenu diff --git a/drivers/iio/accel/Makefile b/drivers/iio/accel/Makefile index 5bc6855..87d8fa2 100644 --- a/drivers/iio/accel/Makefile +++ b/drivers/iio/accel/Makefile @@ -3,3 +3,12 @@ # obj-$(CONFIG_HID_SENSOR_ACCEL_3D) += hid-sensor-accel-3d.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 + +obj-$(CONFIG_KXSD9) += kxsd9.o diff --git a/drivers/iio/accel/hid-sensor-accel-3d.c b/drivers/iio/accel/hid-sensor-accel-3d.c index 0b0c3c6..dd8ea42 100644 --- a/drivers/iio/accel/hid-sensor-accel-3d.c +++ b/drivers/iio/accel/hid-sensor-accel-3d.c @@ -28,7 +28,6 @@ #include <linux/iio/buffer.h> #include <linux/iio/trigger_consumer.h> #include <linux/iio/triggered_buffer.h> -#include "../common/hid-sensors/hid-sensor-attributes.h" #include "../common/hid-sensors/hid-sensor-trigger.h" /*Format: HID-SENSOR-usage_id_in_hex*/ @@ -44,7 +43,7 @@ enum accel_3d_channel { struct accel_3d_state { struct hid_sensor_hub_callbacks callbacks; - struct hid_sensor_iio_common common_attributes; + struct hid_sensor_common common_attributes; struct hid_sensor_hub_attribute_info accel[ACCEL_3D_CHANNEL_MAX]; u32 accel_val[ACCEL_3D_CHANNEL_MAX]; }; diff --git a/drivers/iio/accel/kxsd9.c b/drivers/iio/accel/kxsd9.c new file mode 100644 index 0000000..c2229a5 --- /dev/null +++ b/drivers/iio/accel/kxsd9.c @@ -0,0 +1,287 @@ +/* + * 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) + return ret; + return (((u16)(st->rx[0])) << 8) | (st->rx[1] & 0xF0); +} + +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 = IIO_CHAN_INFO_RAW_SEPARATE_BIT | \ + IIO_CHAN_INFO_SCALE_SHARED_BIT, \ + .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 = IIO_CHAN_INFO_RAW_SEPARATE_BIT, + .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; + int ret; + + indio_dev = iio_device_alloc(sizeof(*st)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + 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); + + ret = iio_device_register(indio_dev); + if (ret) + goto error_free_dev; + + return 0; + +error_free_dev: + iio_device_free(indio_dev); +error_ret: + return ret; +} + +static int kxsd9_remove(struct spi_device *spi) +{ + iio_device_unregister(spi_get_drvdata(spi)); + iio_device_free(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/st_accel.h b/drivers/iio/accel/st_accel.h new file mode 100644 index 0000000..37949b9 --- /dev/null +++ b/drivers/iio/accel/st_accel.h @@ -0,0 +1,47 @@ +/* + * 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" + +int st_accel_common_probe(struct iio_dev *indio_dev); +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 0000000..6bd82c7 --- /dev/null +++ b/drivers/iio/accel/st_accel_buffer.c @@ -0,0 +1,114 @@ +/* + * 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 = trig->private_data; + + return st_sensors_set_dataready_irq(indio_dev, state); +} + +static int st_accel_buffer_preenable(struct iio_dev *indio_dev) +{ + int err; + + err = st_sensors_set_enable(indio_dev, true); + if (err < 0) + goto st_accel_set_enable_error; + + err = iio_sw_buffer_preenable(indio_dev); + +st_accel_set_enable_error: + return err; +} + +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 0000000..e0f5a3c --- /dev/null +++ b/drivers/iio/accel/st_accel_core.c @@ -0,0 +1,500 @@ +/* + * 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" + +/* 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_MASK 0x10 +#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_MASK 0x02 +#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_MASK 0x80 +#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, ST_SENSORS_SCAN_X, IIO_MOD_X, IIO_LE, + ST_SENSORS_DEFAULT_12_REALBITS, ST_ACCEL_DEFAULT_OUT_X_L_ADDR), + ST_SENSORS_LSM_CHANNELS(IIO_ACCEL, ST_SENSORS_SCAN_Y, IIO_MOD_Y, IIO_LE, + ST_SENSORS_DEFAULT_12_REALBITS, ST_ACCEL_DEFAULT_OUT_Y_L_ADDR), + ST_SENSORS_LSM_CHANNELS(IIO_ACCEL, ST_SENSORS_SCAN_Z, IIO_MOD_Z, IIO_LE, + ST_SENSORS_DEFAULT_12_REALBITS, 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, ST_SENSORS_SCAN_X, IIO_MOD_X, IIO_LE, + ST_SENSORS_DEFAULT_16_REALBITS, ST_ACCEL_DEFAULT_OUT_X_L_ADDR), + ST_SENSORS_LSM_CHANNELS(IIO_ACCEL, ST_SENSORS_SCAN_Y, IIO_MOD_Y, IIO_LE, + ST_SENSORS_DEFAULT_16_REALBITS, ST_ACCEL_DEFAULT_OUT_Y_L_ADDR), + ST_SENSORS_LSM_CHANNELS(IIO_ACCEL, ST_SENSORS_SCAN_Z, IIO_MOD_Z, IIO_LE, + ST_SENSORS_DEFAULT_16_REALBITS, 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 = ST_ACCEL_1_DRDY_IRQ_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 = ST_ACCEL_2_DRDY_IRQ_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 = ST_ACCEL_3_DRDY_IRQ_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) +{ + int err; + struct st_sensor_data *adata = iio_priv(indio_dev); + + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &accel_info; + + err = st_sensors_check_device_support(indio_dev, + ARRAY_SIZE(st_accel_sensors), st_accel_sensors); + if (err < 0) + goto st_accel_common_probe_error; + + 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; + + err = st_sensors_init_sensor(indio_dev); + if (err < 0) + goto st_accel_common_probe_error; + + if (adata->get_irq_data_ready(indio_dev) > 0) { + err = st_accel_allocate_ring(indio_dev); + if (err < 0) + goto st_accel_common_probe_error; + + 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; + + return err; + +st_accel_device_register_error: + if (adata->get_irq_data_ready(indio_dev) > 0) + st_sensors_deallocate_trigger(indio_dev); +st_accel_probe_trigger_error: + if (adata->get_irq_data_ready(indio_dev) > 0) + st_accel_deallocate_ring(indio_dev); +st_accel_common_probe_error: + 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); + + 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); + } + iio_device_free(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 0000000..ffc9d09 --- /dev/null +++ b/drivers/iio/accel/st_accel_i2c.c @@ -0,0 +1,86 @@ +/* + * 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 = iio_device_alloc(sizeof(*adata)); + if (indio_dev == NULL) { + err = -ENOMEM; + goto iio_device_alloc_error; + } + + adata = iio_priv(indio_dev); + adata->dev = &client->dev; + + st_sensors_i2c_configure(indio_dev, client, adata); + + err = st_accel_common_probe(indio_dev); + if (err < 0) + goto st_accel_common_probe_error; + + return 0; + +st_accel_common_probe_error: + iio_device_free(indio_dev); +iio_device_alloc_error: + return err; +} + +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 0000000..22b35bf --- /dev/null +++ b/drivers/iio/accel/st_accel_spi.c @@ -0,0 +1,85 @@ +/* + * 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 = iio_device_alloc(sizeof(*adata)); + if (indio_dev == NULL) { + err = -ENOMEM; + goto iio_device_alloc_error; + } + + adata = iio_priv(indio_dev); + adata->dev = &spi->dev; + + st_sensors_spi_configure(indio_dev, spi, adata); + + err = st_accel_common_probe(indio_dev); + if (err < 0) + goto st_accel_common_probe_error; + + return 0; + +st_accel_common_probe_error: + iio_device_free(indio_dev); +iio_device_alloc_error: + return err; +} + +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"); diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index fe822a1..e372257 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -100,10 +100,8 @@ config LP8788_ADC config MAX1363 tristate "Maxim max1363 ADC driver" depends on I2C - select IIO_TRIGGER - select MAX1363_RING_BUFFER select IIO_BUFFER - select IIO_KFIFO_BUF + select IIO_TRIGGERED_BUFFER help Say yes here to build support for many Maxim i2c analog to digital converters (ADC). (max1361, max1362, max1363, max1364, max1036, diff --git a/drivers/iio/adc/lp8788_adc.c b/drivers/iio/adc/lp8788_adc.c index 72955e4..763f575 100644 --- a/drivers/iio/adc/lp8788_adc.c +++ b/drivers/iio/adc/lp8788_adc.c @@ -179,7 +179,7 @@ static int lp8788_iio_map_register(struct iio_dev *indio_dev, ret = iio_map_array_register(indio_dev, map); if (ret) { - dev_err(adc->lp->dev, "iio map err: %d\n", ret); + dev_err(&indio_dev->dev, "iio map err: %d\n", ret); return ret; } @@ -187,12 +187,6 @@ static int lp8788_iio_map_register(struct iio_dev *indio_dev, return 0; } -static inline void lp8788_iio_map_unregister(struct iio_dev *indio_dev, - struct lp8788_adc *adc) -{ - iio_map_array_unregister(indio_dev, adc->map); -} - static int lp8788_adc_probe(struct platform_device *pdev) { struct lp8788 *lp = dev_get_drvdata(pdev->dev.parent); @@ -208,13 +202,14 @@ static int lp8788_adc_probe(struct platform_device *pdev) adc->lp = lp; platform_set_drvdata(pdev, indio_dev); + indio_dev->dev.of_node = pdev->dev.of_node; ret = lp8788_iio_map_register(indio_dev, lp->pdata, adc); if (ret) goto err_iio_map; mutex_init(&adc->lock); - indio_dev->dev.parent = lp->dev; + indio_dev->dev.parent = &pdev->dev; indio_dev->name = pdev->name; indio_dev->modes = INDIO_DIRECT_MODE; indio_dev->info = &lp8788_adc_info; @@ -223,14 +218,14 @@ static int lp8788_adc_probe(struct platform_device *pdev) ret = iio_device_register(indio_dev); if (ret) { - dev_err(lp->dev, "iio dev register err: %d\n", ret); + dev_err(&pdev->dev, "iio dev register err: %d\n", ret); goto err_iio_device; } return 0; err_iio_device: - lp8788_iio_map_unregister(indio_dev, adc); + iio_map_array_unregister(indio_dev); err_iio_map: iio_device_free(indio_dev); return ret; @@ -239,10 +234,9 @@ err_iio_map: static int lp8788_adc_remove(struct platform_device *pdev) { struct iio_dev *indio_dev = platform_get_drvdata(pdev); - struct lp8788_adc *adc = iio_priv(indio_dev); iio_device_unregister(indio_dev); - lp8788_iio_map_unregister(indio_dev, adc); + iio_map_array_unregister(indio_dev); iio_device_free(indio_dev); return 0; diff --git a/drivers/iio/adc/max1363.c b/drivers/iio/adc/max1363.c index 03b25b3..6c1cfb7 100644 --- a/drivers/iio/adc/max1363.c +++ b/drivers/iio/adc/max1363.c @@ -39,6 +39,7 @@ #include <linux/iio/driver.h> #include <linux/iio/kfifo_buf.h> #include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> #define MAX1363_SETUP_BYTE(a) ((a) | 0x80) @@ -55,7 +56,7 @@ #define MAX1363_SETUP_POWER_UP_INT_REF 0x10 #define MAX1363_SETUP_POWER_DOWN_INT_REF 0x00 -/* think about includeing max11600 etc - more settings */ +/* think about including max11600 etc - more settings */ #define MAX1363_SETUP_EXT_CLOCK 0x08 #define MAX1363_SETUP_INT_CLOCK 0x00 #define MAX1363_SETUP_UNIPOLAR 0x00 @@ -86,7 +87,7 @@ /* max123{6-9} only */ #define MAX1236_SCAN_MID_TO_CHANNEL 0x40 -/* max1363 only - merely part of channel selects or don't care for others*/ +/* max1363 only - merely part of channel selects or don't care for others */ #define MAX1363_CONFIG_EN_MON_MODE_READ 0x18 #define MAX1363_CHANNEL_SEL(a) ((a) << 1) @@ -133,7 +134,7 @@ enum max1363_modes { * @mode_list: array of available scan modes * @default_mode: the scan mode in which the chip starts up * @int_vref_mv: the internal reference voltage - * @num_channels: number of channels + * @num_modes: number of modes * @bits: accuracy of the adc in bits */ struct max1363_chip_info { @@ -152,7 +153,7 @@ struct max1363_chip_info { * @client: i2c_client * @setupbyte: cache of current device setup byte * @configbyte: cache of current device config byte - * @chip_info: chip model specific constants, available modes etc + * @chip_info: chip model specific constants, available modes, etc. * @current_mode: the scan mode of this chip * @requestedmask: a valid requested set of channels * @reg: supply regulator @@ -162,6 +163,8 @@ struct max1363_chip_info { * @mask_low: bitmask for enabled low thresholds * @thresh_high: high threshold values * @thresh_low: low threshold values + * @vref: Reference voltage regulator + * @vref_uv: Actual (external or internal) reference voltage */ struct max1363_state { struct i2c_client *client; @@ -181,6 +184,8 @@ struct max1363_state { /* 4x unipolar first then the fours bipolar ones */ s16 thresh_high[8]; s16 thresh_low[8]; + struct regulator *vref; + u32 vref_uv; }; #define MAX1363_MODE_SINGLE(_num, _mask) { \ @@ -293,7 +298,7 @@ static const struct max1363_mode max1363_mode_table[] = { static const struct max1363_mode *max1363_match_mode(const unsigned long *mask, -const struct max1363_chip_info *ci) + const struct max1363_chip_info *ci) { int i; if (mask) @@ -334,7 +339,7 @@ static int max1363_read_single_chan(struct iio_dev *indio_dev, { int ret = 0; s32 data; - char rxbuf[2]; + u8 rxbuf[2]; struct max1363_state *st = iio_priv(indio_dev); struct i2c_client *client = st->client; @@ -366,7 +371,8 @@ static int max1363_read_single_chan(struct iio_dev *indio_dev, ret = data; goto error_ret; } - data = (s32)(rxbuf[1]) | ((s32)(rxbuf[0] & 0x0F)) << 8; + data = (rxbuf[1] | rxbuf[0] << 8) & + ((1 << st->chip_info->bits) - 1); } else { /* Get reading */ data = i2c_master_recv(client, rxbuf, 1); @@ -391,6 +397,8 @@ static int max1363_read_raw(struct iio_dev *indio_dev, { struct max1363_state *st = iio_priv(indio_dev); int ret; + unsigned long scale_uv; + switch (m) { case IIO_CHAN_INFO_RAW: ret = max1363_read_single_chan(indio_dev, chan, val, m); @@ -398,16 +406,10 @@ static int max1363_read_raw(struct iio_dev *indio_dev, return ret; return IIO_VAL_INT; case IIO_CHAN_INFO_SCALE: - if ((1 << (st->chip_info->bits + 1)) > - st->chip_info->int_vref_mv) { - *val = 0; - *val2 = 500000; - return IIO_VAL_INT_PLUS_MICRO; - } else { - *val = (st->chip_info->int_vref_mv) - >> st->chip_info->bits; - return IIO_VAL_INT; - } + scale_uv = st->vref_uv >> st->chip_info->bits; + *val = scale_uv / 1000; + *val2 = (scale_uv % 1000) * 1000; + return IIO_VAL_INT_PLUS_MICRO; default: return -EINVAL; } @@ -1388,13 +1390,17 @@ static const struct max1363_chip_info max1363_chip_info_tbl[] = { static int max1363_initial_setup(struct max1363_state *st) { - st->setupbyte = MAX1363_SETUP_AIN3_IS_AIN3_REF_IS_VDD - | MAX1363_SETUP_POWER_UP_INT_REF - | MAX1363_SETUP_INT_CLOCK + st->setupbyte = MAX1363_SETUP_INT_CLOCK | MAX1363_SETUP_UNIPOLAR | MAX1363_SETUP_NORESET; - /* Set scan mode writes the config anyway so wait until then*/ + if (st->vref) + st->setupbyte |= MAX1363_SETUP_AIN3_IS_REF_EXT_TO_REF; + else + st->setupbyte |= MAX1363_SETUP_POWER_UP_INT_REF + | MAX1363_SETUP_AIN3_IS_AIN3_REF_IS_INT; + + /* Set scan mode writes the config anyway so wait until then */ st->setupbyte = MAX1363_SETUP_BYTE(st->setupbyte); st->current_mode = &max1363_mode_table[st->chip_info->default_mode]; st->configbyte = MAX1363_CONFIG_BYTE(st->configbyte); @@ -1408,8 +1414,9 @@ static int max1363_alloc_scan_masks(struct iio_dev *indio_dev) unsigned long *masks; int i; - masks = kzalloc(BITS_TO_LONGS(MAX1363_MAX_CHANNELS)*sizeof(long)* - (st->chip_info->num_modes + 1), GFP_KERNEL); + masks = devm_kzalloc(&indio_dev->dev, + BITS_TO_LONGS(MAX1363_MAX_CHANNELS) * sizeof(long) * + (st->chip_info->num_modes + 1), GFP_KERNEL); if (!masks) return -ENOMEM; @@ -1423,7 +1430,6 @@ static int max1363_alloc_scan_masks(struct iio_dev *indio_dev) return 0; } - static irqreturn_t max1363_trigger_handler(int irq, void *p) { struct iio_poll_func *pf = p; @@ -1483,54 +1489,13 @@ static const struct iio_buffer_setup_ops max1363_buffered_setup_ops = { .predisable = &iio_triggered_buffer_predisable, }; -static int max1363_register_buffered_funcs_and_init(struct iio_dev *indio_dev) -{ - struct max1363_state *st = iio_priv(indio_dev); - int ret = 0; - - indio_dev->buffer = iio_kfifo_allocate(indio_dev); - if (!indio_dev->buffer) { - ret = -ENOMEM; - goto error_ret; - } - indio_dev->pollfunc = iio_alloc_pollfunc(NULL, - &max1363_trigger_handler, - IRQF_ONESHOT, - indio_dev, - "%s_consumer%d", - st->client->name, - indio_dev->id); - if (indio_dev->pollfunc == NULL) { - ret = -ENOMEM; - goto error_deallocate_sw_rb; - } - /* Buffer functions - here trigger setup related */ - indio_dev->setup_ops = &max1363_buffered_setup_ops; - - /* Flag that polled buffering is possible */ - indio_dev->modes |= INDIO_BUFFER_TRIGGERED; - - return 0; - -error_deallocate_sw_rb: - iio_kfifo_free(indio_dev->buffer); -error_ret: - return ret; -} - -static void max1363_buffer_cleanup(struct iio_dev *indio_dev) -{ - /* ensure that the trigger has been detached */ - iio_dealloc_pollfunc(indio_dev->pollfunc); - iio_kfifo_free(indio_dev->buffer); -} - static int max1363_probe(struct i2c_client *client, const struct i2c_device_id *id) { int ret; struct max1363_state *st; struct iio_dev *indio_dev; + struct regulator *vref; indio_dev = iio_device_alloc(sizeof(struct max1363_state)); if (indio_dev == NULL) { @@ -1538,13 +1503,14 @@ static int max1363_probe(struct i2c_client *client, goto error_out; } + indio_dev->dev.of_node = client->dev.of_node; ret = iio_map_array_register(indio_dev, client->dev.platform_data); if (ret < 0) goto error_free_device; st = iio_priv(indio_dev); - st->reg = regulator_get(&client->dev, "vcc"); + st->reg = devm_regulator_get(&client->dev, "vcc"); if (IS_ERR(st->reg)) { ret = PTR_ERR(st->reg); goto error_unregister_map; @@ -1552,7 +1518,7 @@ static int max1363_probe(struct i2c_client *client, ret = regulator_enable(st->reg); if (ret) - goto error_put_reg; + goto error_unregister_map; /* this is only used for device removal purposes */ i2c_set_clientdata(client, indio_dev); @@ -1560,35 +1526,45 @@ static int max1363_probe(struct i2c_client *client, st->chip_info = &max1363_chip_info_tbl[id->driver_data]; st->client = client; + st->vref_uv = st->chip_info->int_vref_mv * 1000; + vref = devm_regulator_get(&client->dev, "vref"); + if (!IS_ERR(vref)) { + int vref_uv; + + ret = regulator_enable(vref); + if (ret) + goto error_disable_reg; + st->vref = vref; + vref_uv = regulator_get_voltage(vref); + if (vref_uv <= 0) { + ret = -EINVAL; + goto error_disable_reg; + } + st->vref_uv = vref_uv; + } + ret = max1363_alloc_scan_masks(indio_dev); if (ret) goto error_disable_reg; - /* Estabilish that the iio_dev is a child of the i2c device */ + /* Establish that the iio_dev is a child of the i2c device */ indio_dev->dev.parent = &client->dev; indio_dev->name = id->name; indio_dev->channels = st->chip_info->channels; indio_dev->num_channels = st->chip_info->num_channels; indio_dev->info = st->chip_info->info; indio_dev->modes = INDIO_DIRECT_MODE; - indio_dev->channels = st->chip_info->channels; - indio_dev->num_channels = st->chip_info->num_channels; ret = max1363_initial_setup(st); if (ret < 0) - goto error_free_available_scan_masks; - - ret = max1363_register_buffered_funcs_and_init(indio_dev); - if (ret) - goto error_free_available_scan_masks; + goto error_disable_reg; - ret = iio_buffer_register(indio_dev, - st->chip_info->channels, - st->chip_info->num_channels); + ret = iio_triggered_buffer_setup(indio_dev, NULL, + &max1363_trigger_handler, &max1363_buffered_setup_ops); if (ret) - goto error_cleanup_buffer; + goto error_disable_reg; if (client->irq) { - ret = request_threaded_irq(st->client->irq, + ret = devm_request_threaded_irq(&client->dev, st->client->irq, NULL, &max1363_event_handler, IRQF_TRIGGER_RISING | IRQF_ONESHOT, @@ -1601,24 +1577,18 @@ static int max1363_probe(struct i2c_client *client, ret = iio_device_register(indio_dev); if (ret < 0) - goto error_free_irq; + goto error_uninit_buffer; return 0; -error_free_irq: - if (client->irq) - free_irq(st->client->irq, indio_dev); + error_uninit_buffer: - iio_buffer_unregister(indio_dev); -error_cleanup_buffer: - max1363_buffer_cleanup(indio_dev); -error_free_available_scan_masks: - kfree(indio_dev->available_scan_masks); + iio_triggered_buffer_cleanup(indio_dev); error_disable_reg: + if (st->vref) + regulator_disable(st->vref); regulator_disable(st->reg); -error_put_reg: - regulator_put(st->reg); error_unregister_map: - iio_map_array_unregister(indio_dev, client->dev.platform_data); + iio_map_array_unregister(indio_dev); error_free_device: iio_device_free(indio_dev); error_out: @@ -1631,14 +1601,11 @@ static int max1363_remove(struct i2c_client *client) struct max1363_state *st = iio_priv(indio_dev); iio_device_unregister(indio_dev); - if (client->irq) - free_irq(st->client->irq, indio_dev); - iio_buffer_unregister(indio_dev); - max1363_buffer_cleanup(indio_dev); - kfree(indio_dev->available_scan_masks); + iio_triggered_buffer_cleanup(indio_dev); + if (st->vref) + regulator_disable(st->vref); regulator_disable(st->reg); - regulator_put(st->reg); - iio_map_array_unregister(indio_dev, client->dev.platform_data); + iio_map_array_unregister(indio_dev); iio_device_free(indio_dev); return 0; diff --git a/drivers/iio/buffer_cb.c b/drivers/iio/buffer_cb.c index 4d40e24..9201022 100644 --- a/drivers/iio/buffer_cb.c +++ b/drivers/iio/buffer_cb.c @@ -25,7 +25,7 @@ static struct iio_buffer_access_funcs iio_cb_access = { .store_to = &iio_buffer_cb_store_to, }; -struct iio_cb_buffer *iio_channel_get_all_cb(const char *name, +struct iio_cb_buffer *iio_channel_get_all_cb(struct device *dev, int (*cb)(u8 *data, void *private), void *private) @@ -46,7 +46,7 @@ struct iio_cb_buffer *iio_channel_get_all_cb(const char *name, cb_buff->buffer.access = &iio_cb_access; INIT_LIST_HEAD(&cb_buff->buffer.demux_list); - cb_buff->channels = iio_channel_get_all(name); + cb_buff->channels = iio_channel_get_all(dev); if (IS_ERR(cb_buff->channels)) { ret = PTR_ERR(cb_buff->channels); goto error_free_cb_buff; diff --git a/drivers/iio/common/Kconfig b/drivers/iio/common/Kconfig index ed45ee5..0b6e97d 100644 --- a/drivers/iio/common/Kconfig +++ b/drivers/iio/common/Kconfig @@ -3,3 +3,4 @@ # source "drivers/iio/common/hid-sensors/Kconfig" +source "drivers/iio/common/st_sensors/Kconfig" diff --git a/drivers/iio/common/Makefile b/drivers/iio/common/Makefile index 8158400..c2352be 100644 --- a/drivers/iio/common/Makefile +++ b/drivers/iio/common/Makefile @@ -7,3 +7,4 @@ # obj-y += hid-sensors/ +obj-y += st_sensors/ diff --git a/drivers/iio/common/hid-sensors/hid-sensor-attributes.c b/drivers/iio/common/hid-sensors/hid-sensor-attributes.c index 7537495..75b5473 100644 --- a/drivers/iio/common/hid-sensors/hid-sensor-attributes.c +++ b/drivers/iio/common/hid-sensors/hid-sensor-attributes.c @@ -25,7 +25,6 @@ #include <linux/hid-sensor-hub.h> #include <linux/iio/iio.h> #include <linux/iio/sysfs.h> -#include "hid-sensor-attributes.h" static int pow_10(unsigned power) { @@ -114,7 +113,7 @@ static u32 convert_to_vtf_format(int size, int exp, int val1, int val2) return value; } -int hid_sensor_read_samp_freq_value(struct hid_sensor_iio_common *st, +int hid_sensor_read_samp_freq_value(struct hid_sensor_common *st, int *val1, int *val2) { s32 value; @@ -141,7 +140,7 @@ int hid_sensor_read_samp_freq_value(struct hid_sensor_iio_common *st, } EXPORT_SYMBOL(hid_sensor_read_samp_freq_value); -int hid_sensor_write_samp_freq_value(struct hid_sensor_iio_common *st, +int hid_sensor_write_samp_freq_value(struct hid_sensor_common *st, int val1, int val2) { s32 value; @@ -169,7 +168,7 @@ int hid_sensor_write_samp_freq_value(struct hid_sensor_iio_common *st, } EXPORT_SYMBOL(hid_sensor_write_samp_freq_value); -int hid_sensor_read_raw_hyst_value(struct hid_sensor_iio_common *st, +int hid_sensor_read_raw_hyst_value(struct hid_sensor_common *st, int *val1, int *val2) { s32 value; @@ -191,7 +190,7 @@ int hid_sensor_read_raw_hyst_value(struct hid_sensor_iio_common *st, } EXPORT_SYMBOL(hid_sensor_read_raw_hyst_value); -int hid_sensor_write_raw_hyst_value(struct hid_sensor_iio_common *st, +int hid_sensor_write_raw_hyst_value(struct hid_sensor_common *st, int val1, int val2) { s32 value; @@ -212,7 +211,7 @@ EXPORT_SYMBOL(hid_sensor_write_raw_hyst_value); int hid_sensor_parse_common_attributes(struct hid_sensor_hub_device *hsdev, u32 usage_id, - struct hid_sensor_iio_common *st) + struct hid_sensor_common *st) { sensor_hub_input_get_attribute_info(hsdev, diff --git a/drivers/iio/common/hid-sensors/hid-sensor-attributes.h b/drivers/iio/common/hid-sensors/hid-sensor-attributes.h deleted file mode 100644 index a4676a0..0000000 --- a/drivers/iio/common/hid-sensors/hid-sensor-attributes.h +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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. - * - */ -#ifndef _HID_SENSORS_ATTRIBUTES_H -#define _HID_SENSORS_ATTRIBUTES_H - -/* Common hid sensor iio structure */ -struct hid_sensor_iio_common { - struct hid_sensor_hub_device *hsdev; - struct platform_device *pdev; - unsigned usage_id; - bool data_ready; - struct hid_sensor_hub_attribute_info poll; - struct hid_sensor_hub_attribute_info report_state; - struct hid_sensor_hub_attribute_info power_state; - struct hid_sensor_hub_attribute_info sensitivity; -}; - -/*Convert from hid unit expo to regular exponent*/ -static inline int hid_sensor_convert_exponent(int unit_expo) -{ - if (unit_expo < 0x08) - return unit_expo; - else if (unit_expo <= 0x0f) - return -(0x0f-unit_expo+1); - else - return 0; -} - -int hid_sensor_parse_common_attributes(struct hid_sensor_hub_device *hsdev, - u32 usage_id, - struct hid_sensor_iio_common *st); -int hid_sensor_write_raw_hyst_value(struct hid_sensor_iio_common *st, - int val1, int val2); -int hid_sensor_read_raw_hyst_value(struct hid_sensor_iio_common *st, - int *val1, int *val2); -int hid_sensor_write_samp_freq_value(struct hid_sensor_iio_common *st, - int val1, int val2); -int hid_sensor_read_samp_freq_value(struct hid_sensor_iio_common *st, - int *val1, int *val2); - -#endif diff --git a/drivers/iio/common/hid-sensors/hid-sensor-trigger.c b/drivers/iio/common/hid-sensors/hid-sensor-trigger.c index d60198a..7a525a9 100644 --- a/drivers/iio/common/hid-sensors/hid-sensor-trigger.c +++ b/drivers/iio/common/hid-sensors/hid-sensor-trigger.c @@ -26,13 +26,12 @@ #include <linux/iio/iio.h> #include <linux/iio/trigger.h> #include <linux/iio/sysfs.h> -#include "hid-sensor-attributes.h" #include "hid-sensor-trigger.h" static int hid_sensor_data_rdy_trigger_set_state(struct iio_trigger *trig, bool state) { - struct hid_sensor_iio_common *st = trig->private_data; + struct hid_sensor_common *st = trig->private_data; int state_val; state_val = state ? 1 : 0; @@ -64,7 +63,7 @@ static const struct iio_trigger_ops hid_sensor_trigger_ops = { }; int hid_sensor_setup_trigger(struct iio_dev *indio_dev, const char *name, - struct hid_sensor_iio_common *attrb) + struct hid_sensor_common *attrb) { int ret; struct iio_trigger *trig; diff --git a/drivers/iio/common/hid-sensors/hid-sensor-trigger.h b/drivers/iio/common/hid-sensors/hid-sensor-trigger.h index fd98297..9a87314 100644 --- a/drivers/iio/common/hid-sensors/hid-sensor-trigger.h +++ b/drivers/iio/common/hid-sensors/hid-sensor-trigger.h @@ -20,7 +20,7 @@ #define _HID_SENSOR_TRIGGER_H int hid_sensor_setup_trigger(struct iio_dev *indio_dev, const char *name, - struct hid_sensor_iio_common *attrb); + struct hid_sensor_common *attrb); void hid_sensor_remove_trigger(struct iio_dev *indio_dev); #endif diff --git a/drivers/iio/common/st_sensors/Kconfig b/drivers/iio/common/st_sensors/Kconfig new file mode 100644 index 0000000..865f1ca --- /dev/null +++ b/drivers/iio/common/st_sensors/Kconfig @@ -0,0 +1,14 @@ +# +# STMicroelectronics sensors common library +# + +config IIO_ST_SENSORS_I2C + tristate + +config IIO_ST_SENSORS_SPI + tristate + +config IIO_ST_SENSORS_CORE + tristate + select IIO_ST_SENSORS_I2C if I2C + select IIO_ST_SENSORS_SPI if SPI_MASTER diff --git a/drivers/iio/common/st_sensors/Makefile b/drivers/iio/common/st_sensors/Makefile new file mode 100644 index 0000000..9f3e24f --- /dev/null +++ b/drivers/iio/common/st_sensors/Makefile @@ -0,0 +1,10 @@ +# +# Makefile for the STMicroelectronics sensor common modules. +# + +obj-$(CONFIG_IIO_ST_SENSORS_I2C) += st_sensors_i2c.o +obj-$(CONFIG_IIO_ST_SENSORS_SPI) += st_sensors_spi.o +obj-$(CONFIG_IIO_ST_SENSORS_CORE) += st_sensors.o +st_sensors-y := st_sensors_core.o +st_sensors-$(CONFIG_IIO_BUFFER) += st_sensors_buffer.o +st_sensors-$(CONFIG_IIO_TRIGGER) += st_sensors_trigger.o diff --git a/drivers/iio/common/st_sensors/st_sensors_buffer.c b/drivers/iio/common/st_sensors/st_sensors_buffer.c new file mode 100644 index 0000000..09b236d --- /dev/null +++ b/drivers/iio/common/st_sensors/st_sensors_buffer.c @@ -0,0 +1,116 @@ +/* + * STMicroelectronics sensors buffer library 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/iio/iio.h> +#include <linux/iio/trigger.h> +#include <linux/interrupt.h> +#include <linux/iio/buffer.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> +#include <linux/irqreturn.h> + +#include <linux/iio/common/st_sensors.h> + + +int st_sensors_get_buffer_element(struct iio_dev *indio_dev, u8 *buf) +{ + int i, n = 0, len; + u8 addr[ST_SENSORS_NUMBER_DATA_CHANNELS]; + struct st_sensor_data *sdata = iio_priv(indio_dev); + + for (i = 0; i < ST_SENSORS_NUMBER_DATA_CHANNELS; i++) { + if (test_bit(i, indio_dev->active_scan_mask)) { + addr[n] = indio_dev->channels[i].address; + n++; + } + } + switch (n) { + case 1: + len = sdata->tf->read_multiple_byte(&sdata->tb, sdata->dev, + addr[0], ST_SENSORS_BYTE_FOR_CHANNEL, buf, + sdata->multiread_bit); + break; + case 2: + if ((addr[1] - addr[0]) == ST_SENSORS_BYTE_FOR_CHANNEL) { + len = sdata->tf->read_multiple_byte(&sdata->tb, + sdata->dev, addr[0], + ST_SENSORS_BYTE_FOR_CHANNEL*n, + buf, sdata->multiread_bit); + } else { + u8 rx_array[ST_SENSORS_BYTE_FOR_CHANNEL* + ST_SENSORS_NUMBER_DATA_CHANNELS]; + len = sdata->tf->read_multiple_byte(&sdata->tb, + sdata->dev, addr[0], + ST_SENSORS_BYTE_FOR_CHANNEL* + ST_SENSORS_NUMBER_DATA_CHANNELS, + rx_array, sdata->multiread_bit); + if (len < 0) + goto read_data_channels_error; + + for (i = 0; i < n * ST_SENSORS_NUMBER_DATA_CHANNELS; + i++) { + if (i < n) + buf[i] = rx_array[i]; + else + buf[i] = rx_array[n + i]; + } + len = ST_SENSORS_BYTE_FOR_CHANNEL*n; + } + break; + case 3: + len = sdata->tf->read_multiple_byte(&sdata->tb, sdata->dev, + addr[0], ST_SENSORS_BYTE_FOR_CHANNEL* + ST_SENSORS_NUMBER_DATA_CHANNELS, + buf, sdata->multiread_bit); + break; + default: + len = -EINVAL; + goto read_data_channels_error; + } + if (len != ST_SENSORS_BYTE_FOR_CHANNEL*n) { + len = -EIO; + goto read_data_channels_error; + } + +read_data_channels_error: + return len; +} +EXPORT_SYMBOL(st_sensors_get_buffer_element); + +irqreturn_t st_sensors_trigger_handler(int irq, void *p) +{ + int len; + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct st_sensor_data *sdata = iio_priv(indio_dev); + + len = st_sensors_get_buffer_element(indio_dev, sdata->buffer_data); + if (len < 0) + goto st_sensors_get_buffer_element_error; + + if (indio_dev->scan_timestamp) + *(s64 *)((u8 *)sdata->buffer_data + + ALIGN(len, sizeof(s64))) = pf->timestamp; + + iio_push_to_buffers(indio_dev, sdata->buffer_data); + +st_sensors_get_buffer_element_error: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} +EXPORT_SYMBOL(st_sensors_trigger_handler); + +MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics ST-sensors buffer"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/common/st_sensors/st_sensors_core.c b/drivers/iio/common/st_sensors/st_sensors_core.c new file mode 100644 index 0000000..0198324 --- /dev/null +++ b/drivers/iio/common/st_sensors/st_sensors_core.c @@ -0,0 +1,446 @@ +/* + * STMicroelectronics sensors core library 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/delay.h> +#include <linux/iio/iio.h> +#include <asm/unaligned.h> + +#include <linux/iio/common/st_sensors.h> + + +#define ST_SENSORS_WAI_ADDRESS 0x0f + +static int st_sensors_write_data_with_mask(struct iio_dev *indio_dev, + u8 reg_addr, u8 mask, u8 data) +{ + int err; + u8 new_data; + struct st_sensor_data *sdata = iio_priv(indio_dev); + + err = sdata->tf->read_byte(&sdata->tb, sdata->dev, reg_addr, &new_data); + if (err < 0) + goto st_sensors_write_data_with_mask_error; + + new_data = ((new_data & (~mask)) | ((data << __ffs(mask)) & mask)); + err = sdata->tf->write_byte(&sdata->tb, sdata->dev, reg_addr, new_data); + +st_sensors_write_data_with_mask_error: + return err; +} + +static int st_sensors_match_odr(struct st_sensors *sensor, + unsigned int odr, struct st_sensor_odr_avl *odr_out) +{ + int i, ret = -EINVAL; + + for (i = 0; i < ST_SENSORS_ODR_LIST_MAX; i++) { + if (sensor->odr.odr_avl[i].hz == 0) + goto st_sensors_match_odr_error; + + if (sensor->odr.odr_avl[i].hz == odr) { + odr_out->hz = sensor->odr.odr_avl[i].hz; + odr_out->value = sensor->odr.odr_avl[i].value; + ret = 0; + break; + } + } + +st_sensors_match_odr_error: + return ret; +} + +int st_sensors_set_odr(struct iio_dev *indio_dev, unsigned int odr) +{ + int err; + struct st_sensor_odr_avl odr_out; + struct st_sensor_data *sdata = iio_priv(indio_dev); + + err = st_sensors_match_odr(sdata->sensor, odr, &odr_out); + if (err < 0) + goto st_sensors_match_odr_error; + + if ((sdata->sensor->odr.addr == sdata->sensor->pw.addr) && + (sdata->sensor->odr.mask == sdata->sensor->pw.mask)) { + if (sdata->enabled == true) { + err = st_sensors_write_data_with_mask(indio_dev, + sdata->sensor->odr.addr, + sdata->sensor->odr.mask, + odr_out.value); + } else { + err = 0; + } + } else { + err = st_sensors_write_data_with_mask(indio_dev, + sdata->sensor->odr.addr, sdata->sensor->odr.mask, + odr_out.value); + } + if (err >= 0) + sdata->odr = odr_out.hz; + +st_sensors_match_odr_error: + return err; +} +EXPORT_SYMBOL(st_sensors_set_odr); + +static int st_sensors_match_fs(struct st_sensors *sensor, + unsigned int fs, int *index_fs_avl) +{ + int i, ret = -EINVAL; + + for (i = 0; i < ST_SENSORS_FULLSCALE_AVL_MAX; i++) { + if (sensor->fs.fs_avl[i].num == 0) + goto st_sensors_match_odr_error; + + if (sensor->fs.fs_avl[i].num == fs) { + *index_fs_avl = i; + ret = 0; + break; + } + } + +st_sensors_match_odr_error: + return ret; +} + +static int st_sensors_set_fullscale(struct iio_dev *indio_dev, unsigned int fs) +{ + int err, i; + struct st_sensor_data *sdata = iio_priv(indio_dev); + + err = st_sensors_match_fs(sdata->sensor, fs, &i); + if (err < 0) + goto st_accel_set_fullscale_error; + + err = st_sensors_write_data_with_mask(indio_dev, + sdata->sensor->fs.addr, + sdata->sensor->fs.mask, + sdata->sensor->fs.fs_avl[i].value); + if (err < 0) + goto st_accel_set_fullscale_error; + + sdata->current_fullscale = (struct st_sensor_fullscale_avl *) + &sdata->sensor->fs.fs_avl[i]; + return err; + +st_accel_set_fullscale_error: + dev_err(&indio_dev->dev, "failed to set new fullscale.\n"); + return err; +} + +int st_sensors_set_enable(struct iio_dev *indio_dev, bool enable) +{ + bool found; + u8 tmp_value; + int err = -EINVAL; + struct st_sensor_odr_avl odr_out; + struct st_sensor_data *sdata = iio_priv(indio_dev); + + if (enable) { + found = false; + tmp_value = sdata->sensor->pw.value_on; + if ((sdata->sensor->odr.addr == sdata->sensor->pw.addr) && + (sdata->sensor->odr.mask == sdata->sensor->pw.mask)) { + err = st_sensors_match_odr(sdata->sensor, + sdata->odr, &odr_out); + if (err < 0) + goto set_enable_error; + tmp_value = odr_out.value; + found = true; + } + err = st_sensors_write_data_with_mask(indio_dev, + sdata->sensor->pw.addr, + sdata->sensor->pw.mask, tmp_value); + if (err < 0) + goto set_enable_error; + + sdata->enabled = true; + + if (found) + sdata->odr = odr_out.hz; + } else { + err = st_sensors_write_data_with_mask(indio_dev, + sdata->sensor->pw.addr, + sdata->sensor->pw.mask, + sdata->sensor->pw.value_off); + if (err < 0) + goto set_enable_error; + + sdata->enabled = false; + } + +set_enable_error: + return err; +} +EXPORT_SYMBOL(st_sensors_set_enable); + +int st_sensors_set_axis_enable(struct iio_dev *indio_dev, u8 axis_enable) +{ + struct st_sensor_data *sdata = iio_priv(indio_dev); + + return st_sensors_write_data_with_mask(indio_dev, + sdata->sensor->enable_axis.addr, + sdata->sensor->enable_axis.mask, axis_enable); +} +EXPORT_SYMBOL(st_sensors_set_axis_enable); + +int st_sensors_init_sensor(struct iio_dev *indio_dev) +{ + int err; + struct st_sensor_data *sdata = iio_priv(indio_dev); + + mutex_init(&sdata->tb.buf_lock); + + err = st_sensors_set_enable(indio_dev, false); + if (err < 0) + goto init_error; + + err = st_sensors_set_fullscale(indio_dev, + sdata->current_fullscale->num); + if (err < 0) + goto init_error; + + err = st_sensors_set_odr(indio_dev, sdata->odr); + if (err < 0) + goto init_error; + + /* set BDU */ + err = st_sensors_write_data_with_mask(indio_dev, + sdata->sensor->bdu.addr, sdata->sensor->bdu.mask, true); + if (err < 0) + goto init_error; + + err = st_sensors_set_axis_enable(indio_dev, ST_SENSORS_ENABLE_ALL_AXIS); + +init_error: + return err; +} +EXPORT_SYMBOL(st_sensors_init_sensor); + +int st_sensors_set_dataready_irq(struct iio_dev *indio_dev, bool enable) +{ + int err; + struct st_sensor_data *sdata = iio_priv(indio_dev); + + /* Enable/Disable the interrupt generator 1. */ + if (sdata->sensor->drdy_irq.ig1.en_addr > 0) { + err = st_sensors_write_data_with_mask(indio_dev, + sdata->sensor->drdy_irq.ig1.en_addr, + sdata->sensor->drdy_irq.ig1.en_mask, (int)enable); + if (err < 0) + goto st_accel_set_dataready_irq_error; + } + + /* Enable/Disable the interrupt generator for data ready. */ + err = st_sensors_write_data_with_mask(indio_dev, + sdata->sensor->drdy_irq.addr, + sdata->sensor->drdy_irq.mask, (int)enable); + +st_accel_set_dataready_irq_error: + return err; +} +EXPORT_SYMBOL(st_sensors_set_dataready_irq); + +int st_sensors_set_fullscale_by_gain(struct iio_dev *indio_dev, int scale) +{ + int err = -EINVAL, i; + struct st_sensor_data *sdata = iio_priv(indio_dev); + + for (i = 0; i < ST_SENSORS_FULLSCALE_AVL_MAX; i++) { + if ((sdata->sensor->fs.fs_avl[i].gain == scale) && + (sdata->sensor->fs.fs_avl[i].gain != 0)) { + err = 0; + break; + } + } + if (err < 0) + goto st_sensors_match_scale_error; + + err = st_sensors_set_fullscale(indio_dev, + sdata->sensor->fs.fs_avl[i].num); + +st_sensors_match_scale_error: + return err; +} +EXPORT_SYMBOL(st_sensors_set_fullscale_by_gain); + +static int st_sensors_read_axis_data(struct iio_dev *indio_dev, + u8 ch_addr, int *data) +{ + int err; + u8 outdata[ST_SENSORS_BYTE_FOR_CHANNEL]; + struct st_sensor_data *sdata = iio_priv(indio_dev); + + err = sdata->tf->read_multiple_byte(&sdata->tb, sdata->dev, + ch_addr, ST_SENSORS_BYTE_FOR_CHANNEL, + outdata, sdata->multiread_bit); + if (err < 0) + goto read_error; + + *data = (s16)get_unaligned_le16(outdata); + +read_error: + return err; +} + +int st_sensors_read_info_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *ch, int *val) +{ + int err; + struct st_sensor_data *sdata = iio_priv(indio_dev); + + mutex_lock(&indio_dev->mlock); + if (indio_dev->currentmode == INDIO_BUFFER_TRIGGERED) { + err = -EBUSY; + goto read_error; + } else { + err = st_sensors_set_enable(indio_dev, true); + if (err < 0) + goto read_error; + + msleep((sdata->sensor->bootime * 1000) / sdata->odr); + err = st_sensors_read_axis_data(indio_dev, ch->address, val); + if (err < 0) + goto read_error; + + *val = *val >> ch->scan_type.shift; + } + mutex_unlock(&indio_dev->mlock); + + return err; + +read_error: + mutex_unlock(&indio_dev->mlock); + return err; +} +EXPORT_SYMBOL(st_sensors_read_info_raw); + +int st_sensors_check_device_support(struct iio_dev *indio_dev, + int num_sensors_list, const struct st_sensors *sensors) +{ + u8 wai; + int i, n, err; + struct st_sensor_data *sdata = iio_priv(indio_dev); + + err = sdata->tf->read_byte(&sdata->tb, sdata->dev, + ST_SENSORS_DEFAULT_WAI_ADDRESS, &wai); + if (err < 0) { + dev_err(&indio_dev->dev, "failed to read Who-Am-I register.\n"); + goto read_wai_error; + } + + for (i = 0; i < num_sensors_list; i++) { + if (sensors[i].wai == wai) + break; + } + if (i == num_sensors_list) + goto device_not_supported; + + for (n = 0; n < ARRAY_SIZE(sensors[i].sensors_supported); n++) { + if (strcmp(indio_dev->name, + &sensors[i].sensors_supported[n][0]) == 0) + break; + } + if (n == ARRAY_SIZE(sensors[i].sensors_supported)) { + dev_err(&indio_dev->dev, "device name and WhoAmI mismatch.\n"); + goto sensor_name_mismatch; + } + + sdata->sensor = (struct st_sensors *)&sensors[i]; + + return i; + +device_not_supported: + dev_err(&indio_dev->dev, "device not supported: WhoAmI (0x%x).\n", wai); +sensor_name_mismatch: + err = -ENODEV; +read_wai_error: + return err; +} +EXPORT_SYMBOL(st_sensors_check_device_support); + +ssize_t st_sensors_sysfs_get_sampling_frequency(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct st_sensor_data *adata = iio_priv(dev_get_drvdata(dev)); + + return sprintf(buf, "%d\n", adata->odr); +} +EXPORT_SYMBOL(st_sensors_sysfs_get_sampling_frequency); + +ssize_t st_sensors_sysfs_set_sampling_frequency(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + int err; + unsigned int odr; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + + err = kstrtoint(buf, 10, &odr); + if (err < 0) + goto conversion_error; + + mutex_lock(&indio_dev->mlock); + err = st_sensors_set_odr(indio_dev, odr); + mutex_unlock(&indio_dev->mlock); + +conversion_error: + return err < 0 ? err : size; +} +EXPORT_SYMBOL(st_sensors_sysfs_set_sampling_frequency); + +ssize_t st_sensors_sysfs_sampling_frequency_avail(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i, len = 0; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct st_sensor_data *sdata = iio_priv(indio_dev); + + mutex_lock(&indio_dev->mlock); + for (i = 0; i < ST_SENSORS_ODR_LIST_MAX; i++) { + if (sdata->sensor->odr.odr_avl[i].hz == 0) + break; + + len += scnprintf(buf + len, PAGE_SIZE - len, "%d ", + sdata->sensor->odr.odr_avl[i].hz); + } + mutex_unlock(&indio_dev->mlock); + buf[len - 1] = '\n'; + + return len; +} +EXPORT_SYMBOL(st_sensors_sysfs_sampling_frequency_avail); + +ssize_t st_sensors_sysfs_scale_avail(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i, len = 0; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct st_sensor_data *sdata = iio_priv(indio_dev); + + mutex_lock(&indio_dev->mlock); + for (i = 0; i < ST_SENSORS_FULLSCALE_AVL_MAX; i++) { + if (sdata->sensor->fs.fs_avl[i].num == 0) + break; + + len += scnprintf(buf + len, PAGE_SIZE - len, "0.%06u ", + sdata->sensor->fs.fs_avl[i].gain); + } + mutex_unlock(&indio_dev->mlock); + buf[len - 1] = '\n'; + + return len; +} +EXPORT_SYMBOL(st_sensors_sysfs_scale_avail); + +MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics ST-sensors core"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/common/st_sensors/st_sensors_i2c.c b/drivers/iio/common/st_sensors/st_sensors_i2c.c new file mode 100644 index 0000000..38af944 --- /dev/null +++ b/drivers/iio/common/st_sensors/st_sensors_i2c.c @@ -0,0 +1,81 @@ +/* + * STMicroelectronics sensors i2c library 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/iio/iio.h> + +#include <linux/iio/common/st_sensors_i2c.h> + + +#define ST_SENSORS_I2C_MULTIREAD 0x80 + +static unsigned int st_sensors_i2c_get_irq(struct iio_dev *indio_dev) +{ + struct st_sensor_data *sdata = iio_priv(indio_dev); + + return to_i2c_client(sdata->dev)->irq; +} + +static int st_sensors_i2c_read_byte(struct st_sensor_transfer_buffer *tb, + struct device *dev, u8 reg_addr, u8 *res_byte) +{ + int err; + + err = i2c_smbus_read_byte_data(to_i2c_client(dev), reg_addr); + if (err < 0) + goto st_accel_i2c_read_byte_error; + + *res_byte = err & 0xff; + +st_accel_i2c_read_byte_error: + return err < 0 ? err : 0; +} + +static int st_sensors_i2c_read_multiple_byte( + struct st_sensor_transfer_buffer *tb, struct device *dev, + u8 reg_addr, int len, u8 *data, bool multiread_bit) +{ + if (multiread_bit) + reg_addr |= ST_SENSORS_I2C_MULTIREAD; + + return i2c_smbus_read_i2c_block_data(to_i2c_client(dev), + reg_addr, len, data); +} + +static int st_sensors_i2c_write_byte(struct st_sensor_transfer_buffer *tb, + struct device *dev, u8 reg_addr, u8 data) +{ + return i2c_smbus_write_byte_data(to_i2c_client(dev), reg_addr, data); +} + +static const struct st_sensor_transfer_function st_sensors_tf_i2c = { + .read_byte = st_sensors_i2c_read_byte, + .write_byte = st_sensors_i2c_write_byte, + .read_multiple_byte = st_sensors_i2c_read_multiple_byte, +}; + +void st_sensors_i2c_configure(struct iio_dev *indio_dev, + struct i2c_client *client, struct st_sensor_data *sdata) +{ + i2c_set_clientdata(client, indio_dev); + + indio_dev->dev.parent = &client->dev; + indio_dev->name = client->name; + + sdata->tf = &st_sensors_tf_i2c; + sdata->get_irq_data_ready = st_sensors_i2c_get_irq; +} +EXPORT_SYMBOL(st_sensors_i2c_configure); + +MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics ST-sensors i2c driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/common/st_sensors/st_sensors_spi.c b/drivers/iio/common/st_sensors/st_sensors_spi.c new file mode 100644 index 0000000..f0aa2f1 --- /dev/null +++ b/drivers/iio/common/st_sensors/st_sensors_spi.c @@ -0,0 +1,128 @@ +/* + * STMicroelectronics sensors spi library 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/iio/iio.h> + +#include <linux/iio/common/st_sensors_spi.h> + + +#define ST_SENSORS_SPI_MULTIREAD 0xc0 +#define ST_SENSORS_SPI_READ 0x80 + +static unsigned int st_sensors_spi_get_irq(struct iio_dev *indio_dev) +{ + struct st_sensor_data *sdata = iio_priv(indio_dev); + + return to_spi_device(sdata->dev)->irq; +} + +static int st_sensors_spi_read(struct st_sensor_transfer_buffer *tb, + struct device *dev, u8 reg_addr, int len, u8 *data, bool multiread_bit) +{ + struct spi_message msg; + int err; + + struct spi_transfer xfers[] = { + { + .tx_buf = tb->tx_buf, + .bits_per_word = 8, + .len = 1, + }, + { + .rx_buf = tb->rx_buf, + .bits_per_word = 8, + .len = len, + } + }; + + mutex_lock(&tb->buf_lock); + if ((multiread_bit) && (len > 1)) + tb->tx_buf[0] = reg_addr | ST_SENSORS_SPI_MULTIREAD; + else + tb->tx_buf[0] = reg_addr | ST_SENSORS_SPI_READ; + + spi_message_init(&msg); + spi_message_add_tail(&xfers[0], &msg); + spi_message_add_tail(&xfers[1], &msg); + err = spi_sync(to_spi_device(dev), &msg); + if (err) + goto acc_spi_read_error; + + memcpy(data, tb->rx_buf, len*sizeof(u8)); + mutex_unlock(&tb->buf_lock); + return len; + +acc_spi_read_error: + mutex_unlock(&tb->buf_lock); + return err; +} + +static int st_sensors_spi_read_byte(struct st_sensor_transfer_buffer *tb, + struct device *dev, u8 reg_addr, u8 *res_byte) +{ + return st_sensors_spi_read(tb, dev, reg_addr, 1, res_byte, false); +} + +static int st_sensors_spi_read_multiple_byte( + struct st_sensor_transfer_buffer *tb, struct device *dev, + u8 reg_addr, int len, u8 *data, bool multiread_bit) +{ + return st_sensors_spi_read(tb, dev, reg_addr, len, data, multiread_bit); +} + +static int st_sensors_spi_write_byte(struct st_sensor_transfer_buffer *tb, + struct device *dev, u8 reg_addr, u8 data) +{ + struct spi_message msg; + int err; + + struct spi_transfer xfers = { + .tx_buf = tb->tx_buf, + .bits_per_word = 8, + .len = 2, + }; + + mutex_lock(&tb->buf_lock); + tb->tx_buf[0] = reg_addr; + tb->tx_buf[1] = data; + + spi_message_init(&msg); + spi_message_add_tail(&xfers, &msg); + err = spi_sync(to_spi_device(dev), &msg); + mutex_unlock(&tb->buf_lock); + + return err; +} + +static const struct st_sensor_transfer_function st_sensors_tf_spi = { + .read_byte = st_sensors_spi_read_byte, + .write_byte = st_sensors_spi_write_byte, + .read_multiple_byte = st_sensors_spi_read_multiple_byte, +}; + +void st_sensors_spi_configure(struct iio_dev *indio_dev, + struct spi_device *spi, struct st_sensor_data *sdata) +{ + spi_set_drvdata(spi, indio_dev); + + indio_dev->dev.parent = &spi->dev; + indio_dev->name = spi->modalias; + + sdata->tf = &st_sensors_tf_spi; + sdata->get_irq_data_ready = st_sensors_spi_get_irq; +} +EXPORT_SYMBOL(st_sensors_spi_configure); + +MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics ST-sensors spi driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/common/st_sensors/st_sensors_trigger.c b/drivers/iio/common/st_sensors/st_sensors_trigger.c new file mode 100644 index 0000000..139ed03 --- /dev/null +++ b/drivers/iio/common/st_sensors/st_sensors_trigger.c @@ -0,0 +1,77 @@ +/* + * STMicroelectronics sensors trigger library 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/iio/iio.h> +#include <linux/iio/trigger.h> +#include <linux/interrupt.h> + +#include <linux/iio/common/st_sensors.h> + + +int st_sensors_allocate_trigger(struct iio_dev *indio_dev, + const struct iio_trigger_ops *trigger_ops) +{ + int err; + struct st_sensor_data *sdata = iio_priv(indio_dev); + + sdata->trig = iio_trigger_alloc("%s-trigger", indio_dev->name); + if (sdata->trig == NULL) { + err = -ENOMEM; + dev_err(&indio_dev->dev, "failed to allocate iio trigger.\n"); + goto iio_trigger_alloc_error; + } + + err = request_threaded_irq(sdata->get_irq_data_ready(indio_dev), + iio_trigger_generic_data_rdy_poll, + NULL, + IRQF_TRIGGER_RISING, + sdata->trig->name, + sdata->trig); + if (err) + goto request_irq_error; + + sdata->trig->private_data = indio_dev; + sdata->trig->ops = trigger_ops; + sdata->trig->dev.parent = sdata->dev; + + err = iio_trigger_register(sdata->trig); + if (err < 0) { + dev_err(&indio_dev->dev, "failed to register iio trigger.\n"); + goto iio_trigger_register_error; + } + indio_dev->trig = sdata->trig; + + return 0; + +iio_trigger_register_error: + free_irq(sdata->get_irq_data_ready(indio_dev), sdata->trig); +request_irq_error: + iio_trigger_free(sdata->trig); +iio_trigger_alloc_error: + return err; +} +EXPORT_SYMBOL(st_sensors_allocate_trigger); + +void st_sensors_deallocate_trigger(struct iio_dev *indio_dev) +{ + struct st_sensor_data *sdata = iio_priv(indio_dev); + + iio_trigger_unregister(sdata->trig); + free_irq(sdata->get_irq_data_ready(indio_dev), sdata->trig); + iio_trigger_free(sdata->trig); +} +EXPORT_SYMBOL(st_sensors_deallocate_trigger); + +MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics ST-sensors trigger"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dac/ad5360.c b/drivers/iio/dac/ad5360.c index 54b46fd..9277121 100644 --- a/drivers/iio/dac/ad5360.c +++ b/drivers/iio/dac/ad5360.c @@ -213,7 +213,6 @@ static int ad5360_read(struct iio_dev *indio_dev, unsigned int type, unsigned int addr) { struct ad5360_state *st = iio_priv(indio_dev); - struct spi_message m; int ret; struct spi_transfer t[] = { { @@ -226,10 +225,6 @@ static int ad5360_read(struct iio_dev *indio_dev, unsigned int type, }, }; - spi_message_init(&m); - spi_message_add_tail(&t[0], &m); - spi_message_add_tail(&t[1], &m); - mutex_lock(&indio_dev->mlock); st->data[0].d32 = cpu_to_be32(AD5360_CMD(AD5360_CMD_SPECIAL_FUNCTION) | @@ -237,7 +232,7 @@ static int ad5360_read(struct iio_dev *indio_dev, unsigned int type, AD5360_READBACK_TYPE(type) | AD5360_READBACK_ADDR(addr)); - ret = spi_sync(st->spi, &m); + ret = spi_sync_transfer(st->spi, t, ARRAY_SIZE(t)); if (ret >= 0) ret = be32_to_cpu(st->data[1].d32) & 0xffff; diff --git a/drivers/iio/dac/ad5421.c b/drivers/iio/dac/ad5421.c index 43be948..6b86a63 100644 --- a/drivers/iio/dac/ad5421.c +++ b/drivers/iio/dac/ad5421.c @@ -127,7 +127,6 @@ static int ad5421_write(struct iio_dev *indio_dev, unsigned int reg, static int ad5421_read(struct iio_dev *indio_dev, unsigned int reg) { struct ad5421_state *st = iio_priv(indio_dev); - struct spi_message m; int ret; struct spi_transfer t[] = { { @@ -140,15 +139,11 @@ static int ad5421_read(struct iio_dev *indio_dev, unsigned int reg) }, }; - spi_message_init(&m); - spi_message_add_tail(&t[0], &m); - spi_message_add_tail(&t[1], &m); - mutex_lock(&indio_dev->mlock); st->data[0].d32 = cpu_to_be32((1 << 23) | (reg << 16)); - ret = spi_sync(st->spi, &m); + ret = spi_sync_transfer(st->spi, t, ARRAY_SIZE(t)); if (ret >= 0) ret = be32_to_cpu(st->data[1].d32) & 0xffff; diff --git a/drivers/iio/dac/ad5504.c b/drivers/iio/dac/ad5504.c index 0661829..e5e5974 100644 --- a/drivers/iio/dac/ad5504.c +++ b/drivers/iio/dac/ad5504.c @@ -85,11 +85,7 @@ static int ad5504_spi_read(struct spi_device *spi, u8 addr) .rx_buf = &val, .len = 2, }; - struct spi_message m; - - spi_message_init(&m); - spi_message_add_tail(&t, &m); - ret = spi_sync(spi, &m); + ret = spi_sync_transfer(spi, &t, 1); if (ret < 0) return ret; diff --git a/drivers/iio/dac/ad5686.c b/drivers/iio/dac/ad5686.c index ca9609d..5e554af 100644 --- a/drivers/iio/dac/ad5686.c +++ b/drivers/iio/dac/ad5686.c @@ -117,18 +117,13 @@ static int ad5686_spi_read(struct ad5686_state *st, u8 addr) .len = 3, }, }; - struct spi_message m; int ret; - spi_message_init(&m); - spi_message_add_tail(&t[0], &m); - spi_message_add_tail(&t[1], &m); - st->data[0].d32 = cpu_to_be32(AD5686_CMD(AD5686_CMD_READBACK_ENABLE) | AD5686_ADDR(addr)); st->data[1].d32 = cpu_to_be32(AD5686_CMD(AD5686_CMD_NOOP)); - ret = spi_sync(st->spi, &m); + ret = spi_sync_transfer(st->spi, t, ARRAY_SIZE(t)); if (ret < 0) return ret; diff --git a/drivers/iio/dac/ad5755.c b/drivers/iio/dac/ad5755.c index 0869bbd..71faabc 100644 --- a/drivers/iio/dac/ad5755.c +++ b/drivers/iio/dac/ad5755.c @@ -153,7 +153,6 @@ static int ad5755_write_ctrl(struct iio_dev *indio_dev, unsigned int channel, static int ad5755_read(struct iio_dev *indio_dev, unsigned int addr) { struct ad5755_state *st = iio_priv(indio_dev); - struct spi_message m; int ret; struct spi_transfer t[] = { { @@ -167,16 +166,12 @@ static int ad5755_read(struct iio_dev *indio_dev, unsigned int addr) }, }; - spi_message_init(&m); - spi_message_add_tail(&t[0], &m); - spi_message_add_tail(&t[1], &m); - mutex_lock(&indio_dev->mlock); st->data[0].d32 = cpu_to_be32(AD5755_READ_FLAG | (addr << 16)); st->data[1].d32 = cpu_to_be32(AD5755_NOOP); - ret = spi_sync(st->spi, &m); + ret = spi_sync_transfer(st->spi, t, ARRAY_SIZE(t)); if (ret >= 0) ret = be32_to_cpu(st->data[1].d32) & 0xffff; diff --git a/drivers/iio/dac/ad5764.c b/drivers/iio/dac/ad5764.c index 7f9045e..5b7acd3 100644 --- a/drivers/iio/dac/ad5764.c +++ b/drivers/iio/dac/ad5764.c @@ -135,7 +135,6 @@ static int ad5764_read(struct iio_dev *indio_dev, unsigned int reg, unsigned int *val) { struct ad5764_state *st = iio_priv(indio_dev); - struct spi_message m; int ret; struct spi_transfer t[] = { { @@ -148,15 +147,11 @@ static int ad5764_read(struct iio_dev *indio_dev, unsigned int reg, }, }; - spi_message_init(&m); - spi_message_add_tail(&t[0], &m); - spi_message_add_tail(&t[1], &m); - mutex_lock(&indio_dev->mlock); st->data[0].d32 = cpu_to_be32((1 << 23) | (reg << 16)); - ret = spi_sync(st->spi, &m); + ret = spi_sync_transfer(st->spi, t, ARRAY_SIZE(t)); if (ret >= 0) *val = be32_to_cpu(st->data[1].d32) & 0xffff; diff --git a/drivers/iio/dac/ad5791.c b/drivers/iio/dac/ad5791.c index 6407b54..8dfd3da 100644 --- a/drivers/iio/dac/ad5791.c +++ b/drivers/iio/dac/ad5791.c @@ -125,7 +125,6 @@ static int ad5791_spi_read(struct spi_device *spi, u8 addr, u32 *val) u8 d8[4]; } data[3]; int ret; - struct spi_message msg; struct spi_transfer xfers[] = { { .tx_buf = &data[0].d8[1], @@ -144,10 +143,7 @@ static int ad5791_spi_read(struct spi_device *spi, u8 addr, u32 *val) AD5791_ADDR(addr)); data[1].d32 = cpu_to_be32(AD5791_ADDR(AD5791_ADDR_NOOP)); - spi_message_init(&msg); - spi_message_add_tail(&xfers[0], &msg); - spi_message_add_tail(&xfers[1], &msg); - ret = spi_sync(spi, &msg); + ret = spi_sync_transfer(spi, xfers, ARRAY_SIZE(xfers)); *val = be32_to_cpu(data[2].d32); diff --git a/drivers/iio/frequency/ad9523.c b/drivers/iio/frequency/ad9523.c index 8030747..1ea132e 100644 --- a/drivers/iio/frequency/ad9523.c +++ b/drivers/iio/frequency/ad9523.c @@ -287,7 +287,6 @@ struct ad9523_state { static int ad9523_read(struct iio_dev *indio_dev, unsigned addr) { struct ad9523_state *st = iio_priv(indio_dev); - struct spi_message m; int ret; /* We encode the register size 1..3 bytes into the register address. @@ -305,15 +304,11 @@ static int ad9523_read(struct iio_dev *indio_dev, unsigned addr) }, }; - spi_message_init(&m); - spi_message_add_tail(&t[0], &m); - spi_message_add_tail(&t[1], &m); - st->data[0].d32 = cpu_to_be32(AD9523_READ | AD9523_CNT(AD9523_TRANSF_LEN(addr)) | AD9523_ADDR(addr)); - ret = spi_sync(st->spi, &m); + ret = spi_sync_transfer(st->spi, t, ARRAY_SIZE(t)); if (ret < 0) dev_err(&indio_dev->dev, "read failed (%d)", ret); else @@ -326,7 +321,6 @@ static int ad9523_read(struct iio_dev *indio_dev, unsigned addr) static int ad9523_write(struct iio_dev *indio_dev, unsigned addr, unsigned val) { struct ad9523_state *st = iio_priv(indio_dev); - struct spi_message m; int ret; struct spi_transfer t[] = { { @@ -338,16 +332,12 @@ static int ad9523_write(struct iio_dev *indio_dev, unsigned addr, unsigned val) }, }; - spi_message_init(&m); - spi_message_add_tail(&t[0], &m); - spi_message_add_tail(&t[1], &m); - st->data[0].d32 = cpu_to_be32(AD9523_WRITE | AD9523_CNT(AD9523_TRANSF_LEN(addr)) | AD9523_ADDR(addr)); st->data[1].d32 = cpu_to_be32(val); - ret = spi_sync(st->spi, &m); + ret = spi_sync_transfer(st->spi, t, ARRAY_SIZE(t)); if (ret < 0) dev_err(&indio_dev->dev, "write failed (%d)", ret); diff --git a/drivers/iio/gyro/Kconfig b/drivers/iio/gyro/Kconfig index 96b68f6..6be4628 100644 --- a/drivers/iio/gyro/Kconfig +++ b/drivers/iio/gyro/Kconfig @@ -3,6 +3,13 @@ # menu "Digital gyroscope sensors" +config ADIS16080 + tristate "Analog Devices ADIS16080/100 Yaw Rate Gyroscope with SPI driver" + depends on SPI + help + Say yes here to build support for Analog Devices ADIS16080, ADIS16100 Yaw + Rate Gyroscope with SPI. + config ADIS16136 tristate "Analog devices ADIS16136 and similar gyroscopes driver" depends on SPI_MASTER @@ -12,6 +19,16 @@ config ADIS16136 Say yes here to build support for the Analog Devices ADIS16133, ADIS16135, ADIS16136 gyroscope devices. +config ADXRS450 + tristate "Analog Devices ADXRS450/3 Digital Output Gyroscope SPI driver" + depends on SPI + help + Say yes here to build support for Analog Devices ADXRS450 and ADXRS453 + programmable digital output gyroscope. + + This driver can also be built as a module. If so, the module + will be called adxrs450. + config HID_SENSOR_GYRO_3D depends on HID_SENSOR_HUB select IIO_BUFFER @@ -23,4 +40,42 @@ config HID_SENSOR_GYRO_3D Say yes here to build support for the HID SENSOR Gyroscope 3D. +config IIO_ST_GYRO_3AXIS + tristate "STMicroelectronics gyroscopes 3-Axis Driver" + depends on (I2C || SPI_MASTER) && SYSFS + select IIO_ST_SENSORS_CORE + select IIO_ST_GYRO_I2C_3AXIS if (I2C) + select IIO_ST_GYRO_SPI_3AXIS if (SPI_MASTER) + select IIO_TRIGGERED_BUFFER if (IIO_BUFFER) + select IIO_ST_GYRO_BUFFER if (IIO_TRIGGERED_BUFFER) + help + Say yes here to build support for STMicroelectronics gyroscopes: + L3G4200D, LSM330DL, L3GD20, L3GD20H, LSM330DLC, L3G4IS, LSM330. + + This driver can also be built as a module. If so, will be created + these modules: + - st_gyro (core functions for the driver [it is mandatory]); + - st_gyro_i2c (necessary for the I2C devices [optional*]); + - st_gyro_spi (necessary for the SPI devices [optional*]); + + (*) one of these is necessary to do something. + +config IIO_ST_GYRO_I2C_3AXIS + tristate + depends on IIO_ST_GYRO_3AXIS + depends on IIO_ST_SENSORS_I2C + +config IIO_ST_GYRO_SPI_3AXIS + tristate + depends on IIO_ST_GYRO_3AXIS + depends on IIO_ST_SENSORS_SPI + +config ITG3200 + tristate "InvenSense ITG3200 Digital 3-Axis Gyroscope I2C driver" + depends on I2C + select IIO_TRIGGERED_BUFFER if IIO_BUFFER + help + Say yes here to add support for the InvenSense ITG3200 digital + 3-axis gyroscope sensor. + endmenu diff --git a/drivers/iio/gyro/Makefile b/drivers/iio/gyro/Makefile index 702a058..225d289 100644 --- a/drivers/iio/gyro/Makefile +++ b/drivers/iio/gyro/Makefile @@ -2,5 +2,19 @@ # Makefile for industrial I/O gyroscope sensor drivers # +obj-$(CONFIG_ADIS16080) += adis16080.o obj-$(CONFIG_ADIS16136) += adis16136.o +obj-$(CONFIG_ADXRS450) += adxrs450.o + obj-$(CONFIG_HID_SENSOR_GYRO_3D) += hid-sensor-gyro-3d.o + +itg3200-y := itg3200_core.o +itg3200-$(CONFIG_IIO_BUFFER) += itg3200_buffer.o +obj-$(CONFIG_ITG3200) += itg3200.o + +obj-$(CONFIG_IIO_ST_GYRO_3AXIS) += st_gyro.o +st_gyro-y := st_gyro_core.o +st_gyro-$(CONFIG_IIO_BUFFER) += st_gyro_buffer.o + +obj-$(CONFIG_IIO_ST_GYRO_I2C_3AXIS) += st_gyro_i2c.o +obj-$(CONFIG_IIO_ST_GYRO_SPI_3AXIS) += st_gyro_spi.o diff --git a/drivers/iio/gyro/adis16080.c b/drivers/iio/gyro/adis16080.c new file mode 100644 index 0000000..1861287 --- /dev/null +++ b/drivers/iio/gyro/adis16080.c @@ -0,0 +1,259 @@ +/* + * ADIS16080/100 Yaw Rate Gyroscope with SPI driver + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ +#include <linux/delay.h> +#include <linux/mutex.h> +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/spi/spi.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include <linux/module.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#define ADIS16080_DIN_GYRO (0 << 10) /* Gyroscope output */ +#define ADIS16080_DIN_TEMP (1 << 10) /* Temperature output */ +#define ADIS16080_DIN_AIN1 (2 << 10) +#define ADIS16080_DIN_AIN2 (3 << 10) + +/* + * 1: Write contents on DIN to control register. + * 0: No changes to control register. + */ + +#define ADIS16080_DIN_WRITE (1 << 15) + +struct adis16080_chip_info { + int scale_val; + int scale_val2; +}; + +/** + * struct adis16080_state - device instance specific data + * @us: actual spi_device to write data + * @info: chip specific parameters + * @buf: transmit or receive buffer + **/ +struct adis16080_state { + struct spi_device *us; + const struct adis16080_chip_info *info; + + __be16 buf ____cacheline_aligned; +}; + +static int adis16080_read_sample(struct iio_dev *indio_dev, + u16 addr, int *val) +{ + struct adis16080_state *st = iio_priv(indio_dev); + struct spi_message m; + int ret; + struct spi_transfer t[] = { + { + .tx_buf = &st->buf, + .len = 2, + .cs_change = 1, + }, { + .rx_buf = &st->buf, + .len = 2, + }, + }; + + st->buf = cpu_to_be16(addr | ADIS16080_DIN_WRITE); + + spi_message_init(&m); + spi_message_add_tail(&t[0], &m); + spi_message_add_tail(&t[1], &m); + + ret = spi_sync(st->us, &m); + if (ret == 0) + *val = sign_extend32(be16_to_cpu(st->buf), 11); + + return ret; +} + +static int adis16080_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + struct adis16080_state *st = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&indio_dev->mlock); + ret = adis16080_read_sample(indio_dev, chan->address, val); + mutex_unlock(&indio_dev->mlock); + return ret ? ret : IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ANGL_VEL: + *val = st->info->scale_val; + *val2 = st->info->scale_val2; + return IIO_VAL_FRACTIONAL; + case IIO_VOLTAGE: + /* VREF = 5V, 12 bits */ + *val = 5000; + *val2 = 12; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_TEMP: + /* 85 C = 585, 25 C = 0 */ + *val = 85000 - 25000; + *val2 = 585; + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + switch (chan->type) { + case IIO_VOLTAGE: + /* 2.5 V = 0 */ + *val = 2048; + return IIO_VAL_INT; + case IIO_TEMP: + /* 85 C = 585, 25 C = 0 */ + *val = DIV_ROUND_CLOSEST(25 * 585, 85 - 25); + return IIO_VAL_INT; + default: + return -EINVAL; + } + default: + break; + } + + return -EINVAL; +} + +static const struct iio_chan_spec adis16080_channels[] = { + { + .type = IIO_ANGL_VEL, + .modified = 1, + .channel2 = IIO_MOD_Z, + .info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SEPARATE_BIT, + .address = ADIS16080_DIN_GYRO, + }, { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 0, + .info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SEPARATE_BIT | + IIO_CHAN_INFO_OFFSET_SEPARATE_BIT, + .address = ADIS16080_DIN_AIN1, + }, { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 1, + .info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SEPARATE_BIT | + IIO_CHAN_INFO_OFFSET_SEPARATE_BIT, + .address = ADIS16080_DIN_AIN2, + }, { + .type = IIO_TEMP, + .indexed = 1, + .channel = 0, + .info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SEPARATE_BIT | + IIO_CHAN_INFO_OFFSET_SEPARATE_BIT, + .address = ADIS16080_DIN_TEMP, + } +}; + +static const struct iio_info adis16080_info = { + .read_raw = &adis16080_read_raw, + .driver_module = THIS_MODULE, +}; + +enum { + ID_ADIS16080, + ID_ADIS16100, +}; + +static const struct adis16080_chip_info adis16080_chip_info[] = { + [ID_ADIS16080] = { + /* 80 degree = 819, 819 rad = 46925 degree */ + .scale_val = 80, + .scale_val2 = 46925, + }, + [ID_ADIS16100] = { + /* 300 degree = 1230, 1230 rad = 70474 degree */ + .scale_val = 300, + .scale_val2 = 70474, + }, +}; + +static int adis16080_probe(struct spi_device *spi) +{ + const struct spi_device_id *id = spi_get_device_id(spi); + int ret; + struct adis16080_state *st; + struct iio_dev *indio_dev; + + /* setup the industrialio driver allocated elements */ + indio_dev = iio_device_alloc(sizeof(*st)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + st = iio_priv(indio_dev); + /* this is only used for removal purposes */ + spi_set_drvdata(spi, indio_dev); + + /* Allocate the comms buffers */ + st->us = spi; + st->info = &adis16080_chip_info[id->driver_data]; + + indio_dev->name = spi->dev.driver->name; + indio_dev->channels = adis16080_channels; + indio_dev->num_channels = ARRAY_SIZE(adis16080_channels); + indio_dev->dev.parent = &spi->dev; + indio_dev->info = &adis16080_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_free_dev; + return 0; + +error_free_dev: + iio_device_free(indio_dev); +error_ret: + return ret; +} + +static int adis16080_remove(struct spi_device *spi) +{ + iio_device_unregister(spi_get_drvdata(spi)); + iio_device_free(spi_get_drvdata(spi)); + + return 0; +} + +static const struct spi_device_id adis16080_ids[] = { + { "adis16080", ID_ADIS16080 }, + { "adis16100", ID_ADIS16100 }, + {}, +}; +MODULE_DEVICE_TABLE(spi, adis16080_ids); + +static struct spi_driver adis16080_driver = { + .driver = { + .name = "adis16080", + .owner = THIS_MODULE, + }, + .probe = adis16080_probe, + .remove = adis16080_remove, + .id_table = adis16080_ids, +}; +module_spi_driver(adis16080_driver); + +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_DESCRIPTION("Analog Devices ADIS16080/100 Yaw Rate Gyroscope Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/gyro/adxrs450.c b/drivers/iio/gyro/adxrs450.c new file mode 100644 index 0000000..5b79953 --- /dev/null +++ b/drivers/iio/gyro/adxrs450.c @@ -0,0 +1,494 @@ +/* + * ADXRS450/ADXRS453 Digital Output Gyroscope Driver + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/delay.h> +#include <linux/mutex.h> +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/spi/spi.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include <linux/list.h> +#include <linux/module.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#define ADXRS450_STARTUP_DELAY 50 /* ms */ + +/* The MSB for the spi commands */ +#define ADXRS450_SENSOR_DATA (0x20 << 24) +#define ADXRS450_WRITE_DATA (0x40 << 24) +#define ADXRS450_READ_DATA (0x80 << 24) + +#define ADXRS450_RATE1 0x00 /* Rate Registers */ +#define ADXRS450_TEMP1 0x02 /* Temperature Registers */ +#define ADXRS450_LOCST1 0x04 /* Low CST Memory Registers */ +#define ADXRS450_HICST1 0x06 /* High CST Memory Registers */ +#define ADXRS450_QUAD1 0x08 /* Quad Memory Registers */ +#define ADXRS450_FAULT1 0x0A /* Fault Registers */ +#define ADXRS450_PID1 0x0C /* Part ID Register 1 */ +#define ADXRS450_SNH 0x0E /* Serial Number Registers, 4 bytes */ +#define ADXRS450_SNL 0x10 +#define ADXRS450_DNC1 0x12 /* Dynamic Null Correction Registers */ +/* Check bits */ +#define ADXRS450_P 0x01 +#define ADXRS450_CHK 0x02 +#define ADXRS450_CST 0x04 +#define ADXRS450_PWR 0x08 +#define ADXRS450_POR 0x10 +#define ADXRS450_NVM 0x20 +#define ADXRS450_Q 0x40 +#define ADXRS450_PLL 0x80 +#define ADXRS450_UV 0x100 +#define ADXRS450_OV 0x200 +#define ADXRS450_AMP 0x400 +#define ADXRS450_FAIL 0x800 + +#define ADXRS450_WRERR_MASK (0x7 << 29) + +#define ADXRS450_MAX_RX 4 +#define ADXRS450_MAX_TX 4 + +#define ADXRS450_GET_ST(a) ((a >> 26) & 0x3) + +enum { + ID_ADXRS450, + ID_ADXRS453, +}; + +/** + * struct adxrs450_state - device instance specific data + * @us: actual spi_device + * @buf_lock: mutex to protect tx and rx + * @tx: transmit buffer + * @rx: receive buffer + **/ +struct adxrs450_state { + struct spi_device *us; + struct mutex buf_lock; + __be32 tx ____cacheline_aligned; + __be32 rx; + +}; + +/** + * adxrs450_spi_read_reg_16() - read 2 bytes from a register pair + * @indio_dev: device associated with child of actual iio_dev + * @reg_address: the address of the lower of the two registers, which should be + * an even address, the second register's address is reg_address + 1. + * @val: somewhere to pass back the value read + **/ +static int adxrs450_spi_read_reg_16(struct iio_dev *indio_dev, + u8 reg_address, + u16 *val) +{ + struct spi_message msg; + struct adxrs450_state *st = iio_priv(indio_dev); + u32 tx; + int ret; + struct spi_transfer xfers[] = { + { + .tx_buf = &st->tx, + .bits_per_word = 8, + .len = sizeof(st->tx), + .cs_change = 1, + }, { + .rx_buf = &st->rx, + .bits_per_word = 8, + .len = sizeof(st->rx), + }, + }; + + mutex_lock(&st->buf_lock); + tx = ADXRS450_READ_DATA | (reg_address << 17); + + if (!(hweight32(tx) & 1)) + tx |= ADXRS450_P; + + st->tx = cpu_to_be32(tx); + spi_message_init(&msg); + spi_message_add_tail(&xfers[0], &msg); + spi_message_add_tail(&xfers[1], &msg); + ret = spi_sync(st->us, &msg); + if (ret) { + dev_err(&st->us->dev, "problem while reading 16 bit register 0x%02x\n", + reg_address); + goto error_ret; + } + + *val = (be32_to_cpu(st->rx) >> 5) & 0xFFFF; + +error_ret: + mutex_unlock(&st->buf_lock); + return ret; +} + +/** + * adxrs450_spi_write_reg_16() - write 2 bytes data to a register pair + * @indio_dev: device associated with child of actual actual iio_dev + * @reg_address: the address of the lower of the two registers,which should be + * an even address, the second register's address is reg_address + 1. + * @val: value to be written. + **/ +static int adxrs450_spi_write_reg_16(struct iio_dev *indio_dev, + u8 reg_address, + u16 val) +{ + struct adxrs450_state *st = iio_priv(indio_dev); + u32 tx; + int ret; + + mutex_lock(&st->buf_lock); + tx = ADXRS450_WRITE_DATA | (reg_address << 17) | (val << 1); + + if (!(hweight32(tx) & 1)) + tx |= ADXRS450_P; + + st->tx = cpu_to_be32(tx); + ret = spi_write(st->us, &st->tx, sizeof(st->tx)); + if (ret) + dev_err(&st->us->dev, "problem while writing 16 bit register 0x%02x\n", + reg_address); + usleep_range(100, 1000); /* enforce sequential transfer delay 0.1ms */ + mutex_unlock(&st->buf_lock); + return ret; +} + +/** + * adxrs450_spi_sensor_data() - read 2 bytes sensor data + * @indio_dev: device associated with child of actual iio_dev + * @val: somewhere to pass back the value read + **/ +static int adxrs450_spi_sensor_data(struct iio_dev *indio_dev, s16 *val) +{ + struct spi_message msg; + struct adxrs450_state *st = iio_priv(indio_dev); + int ret; + struct spi_transfer xfers[] = { + { + .tx_buf = &st->tx, + .bits_per_word = 8, + .len = sizeof(st->tx), + .cs_change = 1, + }, { + .rx_buf = &st->rx, + .bits_per_word = 8, + .len = sizeof(st->rx), + }, + }; + + mutex_lock(&st->buf_lock); + st->tx = cpu_to_be32(ADXRS450_SENSOR_DATA); + + spi_message_init(&msg); + spi_message_add_tail(&xfers[0], &msg); + spi_message_add_tail(&xfers[1], &msg); + ret = spi_sync(st->us, &msg); + if (ret) { + dev_err(&st->us->dev, "Problem while reading sensor data\n"); + goto error_ret; + } + + *val = (be32_to_cpu(st->rx) >> 10) & 0xFFFF; + +error_ret: + mutex_unlock(&st->buf_lock); + return ret; +} + +/** + * adxrs450_spi_initial() - use for initializing procedure. + * @st: device instance specific data + * @val: somewhere to pass back the value read + * @chk: Whether to perform fault check + **/ +static int adxrs450_spi_initial(struct adxrs450_state *st, + u32 *val, char chk) +{ + int ret; + u32 tx; + struct spi_transfer xfers = { + .tx_buf = &st->tx, + .rx_buf = &st->rx, + .bits_per_word = 8, + .len = sizeof(st->tx), + }; + + mutex_lock(&st->buf_lock); + tx = ADXRS450_SENSOR_DATA; + if (chk) + tx |= (ADXRS450_CHK | ADXRS450_P); + st->tx = cpu_to_be32(tx); + ret = spi_sync_transfer(st->us, &xfers, 1); + if (ret) { + dev_err(&st->us->dev, "Problem while reading initializing data\n"); + goto error_ret; + } + + *val = be32_to_cpu(st->rx); + +error_ret: + mutex_unlock(&st->buf_lock); + return ret; +} + +/* Recommended Startup Sequence by spec */ +static int adxrs450_initial_setup(struct iio_dev *indio_dev) +{ + u32 t; + u16 data; + int ret; + struct adxrs450_state *st = iio_priv(indio_dev); + + msleep(ADXRS450_STARTUP_DELAY*2); + ret = adxrs450_spi_initial(st, &t, 1); + if (ret) + return ret; + if (t != 0x01) + dev_warn(&st->us->dev, "The initial power on response is not correct! Restart without reset?\n"); + + msleep(ADXRS450_STARTUP_DELAY); + ret = adxrs450_spi_initial(st, &t, 0); + if (ret) + return ret; + + msleep(ADXRS450_STARTUP_DELAY); + ret = adxrs450_spi_initial(st, &t, 0); + if (ret) + return ret; + if (((t & 0xff) | 0x01) != 0xff || ADXRS450_GET_ST(t) != 2) { + dev_err(&st->us->dev, "The second response is not correct!\n"); + return -EIO; + + } + ret = adxrs450_spi_initial(st, &t, 0); + if (ret) + return ret; + if (((t & 0xff) | 0x01) != 0xff || ADXRS450_GET_ST(t) != 2) { + dev_err(&st->us->dev, "The third response is not correct!\n"); + return -EIO; + + } + ret = adxrs450_spi_read_reg_16(indio_dev, ADXRS450_FAULT1, &data); + if (ret) + return ret; + if (data & 0x0fff) { + dev_err(&st->us->dev, "The device is not in normal status!\n"); + return -EINVAL; + } + + return 0; +} + +static int adxrs450_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + int ret; + switch (mask) { + case IIO_CHAN_INFO_CALIBBIAS: + if (val < -0x400 || val >= 0x400) + return -EINVAL; + ret = adxrs450_spi_write_reg_16(indio_dev, + ADXRS450_DNC1, val); + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static int adxrs450_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + int ret; + s16 t; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_ANGL_VEL: + ret = adxrs450_spi_sensor_data(indio_dev, &t); + if (ret) + break; + *val = t; + ret = IIO_VAL_INT; + break; + case IIO_TEMP: + ret = adxrs450_spi_read_reg_16(indio_dev, + ADXRS450_TEMP1, &t); + if (ret) + break; + *val = (t >> 6) + 225; + ret = IIO_VAL_INT; + break; + default: + ret = -EINVAL; + break; + } + break; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ANGL_VEL: + *val = 0; + *val2 = 218166; + return IIO_VAL_INT_PLUS_NANO; + case IIO_TEMP: + *val = 200; + *val2 = 0; + return IIO_VAL_INT; + default: + return -EINVAL; + } + break; + case IIO_CHAN_INFO_QUADRATURE_CORRECTION_RAW: + ret = adxrs450_spi_read_reg_16(indio_dev, ADXRS450_QUAD1, &t); + if (ret) + break; + *val = t; + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_CALIBBIAS: + ret = adxrs450_spi_read_reg_16(indio_dev, ADXRS450_DNC1, &t); + if (ret) + break; + *val = sign_extend32(t, 9); + ret = IIO_VAL_INT; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static const struct iio_chan_spec adxrs450_channels[2][2] = { + [ID_ADXRS450] = { + { + .type = IIO_ANGL_VEL, + .modified = 1, + .channel2 = IIO_MOD_Z, + .info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT | + IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | + IIO_CHAN_INFO_QUADRATURE_CORRECTION_RAW_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SEPARATE_BIT, + }, { + .type = IIO_TEMP, + .indexed = 1, + .channel = 0, + .info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SEPARATE_BIT, + } + }, + [ID_ADXRS453] = { + { + .type = IIO_ANGL_VEL, + .modified = 1, + .channel2 = IIO_MOD_Z, + .info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SEPARATE_BIT | + IIO_CHAN_INFO_QUADRATURE_CORRECTION_RAW_SEPARATE_BIT, + }, { + .type = IIO_TEMP, + .indexed = 1, + .channel = 0, + .info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SEPARATE_BIT, + } + }, +}; + +static const struct iio_info adxrs450_info = { + .driver_module = THIS_MODULE, + .read_raw = &adxrs450_read_raw, + .write_raw = &adxrs450_write_raw, +}; + +static int adxrs450_probe(struct spi_device *spi) +{ + int ret; + struct adxrs450_state *st; + struct iio_dev *indio_dev; + + /* setup the industrialio driver allocated elements */ + indio_dev = iio_device_alloc(sizeof(*st)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + st = iio_priv(indio_dev); + st->us = spi; + mutex_init(&st->buf_lock); + /* This is only used for removal purposes */ + spi_set_drvdata(spi, indio_dev); + + indio_dev->dev.parent = &spi->dev; + indio_dev->info = &adxrs450_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = + adxrs450_channels[spi_get_device_id(spi)->driver_data]; + indio_dev->num_channels = ARRAY_SIZE(adxrs450_channels); + indio_dev->name = spi->dev.driver->name; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_free_dev; + + /* Get the device into a sane initial state */ + ret = adxrs450_initial_setup(indio_dev); + if (ret) + goto error_initial; + return 0; +error_initial: + iio_device_unregister(indio_dev); +error_free_dev: + iio_device_free(indio_dev); + +error_ret: + return ret; +} + +static int adxrs450_remove(struct spi_device *spi) +{ + iio_device_unregister(spi_get_drvdata(spi)); + iio_device_free(spi_get_drvdata(spi)); + + return 0; +} + +static const struct spi_device_id adxrs450_id[] = { + {"adxrs450", ID_ADXRS450}, + {"adxrs453", ID_ADXRS453}, + {} +}; +MODULE_DEVICE_TABLE(spi, adxrs450_id); + +static struct spi_driver adxrs450_driver = { + .driver = { + .name = "adxrs450", + .owner = THIS_MODULE, + }, + .probe = adxrs450_probe, + .remove = adxrs450_remove, + .id_table = adxrs450_id, +}; +module_spi_driver(adxrs450_driver); + +MODULE_AUTHOR("Cliff Cai <cliff.cai@xxxxxxxxxx>"); +MODULE_DESCRIPTION("Analog Devices ADXRS450/ADXRS453 Gyroscope SPI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/gyro/hid-sensor-gyro-3d.c b/drivers/iio/gyro/hid-sensor-gyro-3d.c index 06e7cc3..fcfc83a 100644 --- a/drivers/iio/gyro/hid-sensor-gyro-3d.c +++ b/drivers/iio/gyro/hid-sensor-gyro-3d.c @@ -28,7 +28,6 @@ #include <linux/iio/buffer.h> #include <linux/iio/trigger_consumer.h> #include <linux/iio/triggered_buffer.h> -#include "../common/hid-sensors/hid-sensor-attributes.h" #include "../common/hid-sensors/hid-sensor-trigger.h" /*Format: HID-SENSOR-usage_id_in_hex*/ @@ -44,7 +43,7 @@ enum gyro_3d_channel { struct gyro_3d_state { struct hid_sensor_hub_callbacks callbacks; - struct hid_sensor_iio_common common_attributes; + struct hid_sensor_common common_attributes; struct hid_sensor_hub_attribute_info gyro[GYRO_3D_CHANNEL_MAX]; u32 gyro_val[GYRO_3D_CHANNEL_MAX]; }; diff --git a/drivers/iio/gyro/itg3200_buffer.c b/drivers/iio/gyro/itg3200_buffer.c new file mode 100644 index 0000000..f667d2c --- /dev/null +++ b/drivers/iio/gyro/itg3200_buffer.c @@ -0,0 +1,156 @@ +/* + * itg3200_buffer.c -- support InvenSense ITG3200 + * Digital 3-Axis Gyroscope driver + * + * Copyright (c) 2011 Christian Strobel <christian.strobel@iis.fraunhofer.de> + * Copyright (c) 2011 Manuel Stahl <manuel.stahl@iis.fraunhofer.de> + * Copyright (c) 2012 Thorsten Nowak <thorsten.nowak@iis.fraunhofer.de> + * + * 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/slab.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> + +#include <linux/iio/iio.h> +#include <linux/iio/buffer.h> +#include <linux/iio/trigger.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> +#include <linux/iio/gyro/itg3200.h> + + +static int itg3200_read_all_channels(struct i2c_client *i2c, __be16 *buf) +{ + u8 tx = 0x80 | ITG3200_REG_TEMP_OUT_H; + struct i2c_msg msg[2] = { + { + .addr = i2c->addr, + .flags = i2c->flags, + .len = 1, + .buf = &tx, + }, + { + .addr = i2c->addr, + .flags = i2c->flags | I2C_M_RD, + .len = ITG3200_SCAN_ELEMENTS * sizeof(s16), + .buf = (char *)&buf, + }, + }; + + return i2c_transfer(i2c->adapter, msg, 2); +} + +static irqreturn_t itg3200_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct itg3200 *st = iio_priv(indio_dev); + __be16 buf[ITG3200_SCAN_ELEMENTS + sizeof(s64)/sizeof(u16)]; + + int ret = itg3200_read_all_channels(st->i2c, buf); + if (ret < 0) + goto error_ret; + + if (indio_dev->scan_timestamp) + memcpy(buf + indio_dev->scan_bytes - sizeof(s64), + &pf->timestamp, sizeof(pf->timestamp)); + + iio_push_to_buffers(indio_dev, (u8 *)buf); + iio_trigger_notify_done(indio_dev->trig); + +error_ret: + return IRQ_HANDLED; +} + +int itg3200_buffer_configure(struct iio_dev *indio_dev) +{ + return iio_triggered_buffer_setup(indio_dev, &iio_pollfunc_store_time, + itg3200_trigger_handler, NULL); +} + +void itg3200_buffer_unconfigure(struct iio_dev *indio_dev) +{ + iio_triggered_buffer_cleanup(indio_dev); +} + + +static int itg3200_data_rdy_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + struct iio_dev *indio_dev = trig->private_data; + int ret; + u8 msc; + + ret = itg3200_read_reg_8(indio_dev, ITG3200_REG_IRQ_CONFIG, &msc); + if (ret) + goto error_ret; + + if (state) + msc |= ITG3200_IRQ_DATA_RDY_ENABLE; + else + msc &= ~ITG3200_IRQ_DATA_RDY_ENABLE; + + ret = itg3200_write_reg_8(indio_dev, ITG3200_REG_IRQ_CONFIG, msc); + if (ret) + goto error_ret; + +error_ret: + return ret; + +} + +static const struct iio_trigger_ops itg3200_trigger_ops = { + .owner = THIS_MODULE, + .set_trigger_state = &itg3200_data_rdy_trigger_set_state, +}; + +int itg3200_probe_trigger(struct iio_dev *indio_dev) +{ + int ret; + struct itg3200 *st = iio_priv(indio_dev); + + st->trig = iio_trigger_alloc("%s-dev%d", indio_dev->name, + indio_dev->id); + if (!st->trig) + return -ENOMEM; + + ret = request_irq(st->i2c->irq, + &iio_trigger_generic_data_rdy_poll, + IRQF_TRIGGER_RISING, + "itg3200_data_rdy", + st->trig); + if (ret) + goto error_free_trig; + + + st->trig->dev.parent = &st->i2c->dev; + st->trig->ops = &itg3200_trigger_ops; + st->trig->private_data = indio_dev; + ret = iio_trigger_register(st->trig); + if (ret) + goto error_free_irq; + + /* select default trigger */ + indio_dev->trig = st->trig; + + return 0; + +error_free_irq: + free_irq(st->i2c->irq, st->trig); +error_free_trig: + iio_trigger_free(st->trig); + return ret; +} + +void itg3200_remove_trigger(struct iio_dev *indio_dev) +{ + struct itg3200 *st = iio_priv(indio_dev); + + iio_trigger_unregister(st->trig); + free_irq(st->i2c->irq, st->trig); + iio_trigger_free(st->trig); +} diff --git a/drivers/iio/gyro/itg3200_core.c b/drivers/iio/gyro/itg3200_core.c new file mode 100644 index 0000000..df2e6aa --- /dev/null +++ b/drivers/iio/gyro/itg3200_core.c @@ -0,0 +1,401 @@ +/* + * itg3200_core.c -- support InvenSense ITG3200 + * Digital 3-Axis Gyroscope driver + * + * Copyright (c) 2011 Christian Strobel <christian.strobel@iis.fraunhofer.de> + * Copyright (c) 2011 Manuel Stahl <manuel.stahl@iis.fraunhofer.de> + * Copyright (c) 2012 Thorsten Nowak <thorsten.nowak@iis.fraunhofer.de> + * + * 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. + * + * TODO: + * - Support digital low pass filter + * - Support power management + */ + +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/i2c.h> +#include <linux/gpio.h> +#include <linux/slab.h> +#include <linux/stat.h> +#include <linux/module.h> +#include <linux/delay.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/events.h> +#include <linux/iio/buffer.h> + +#include <linux/iio/gyro/itg3200.h> + + +int itg3200_write_reg_8(struct iio_dev *indio_dev, + u8 reg_address, u8 val) +{ + struct itg3200 *st = iio_priv(indio_dev); + + return i2c_smbus_write_byte_data(st->i2c, 0x80 | reg_address, val); +} + +int itg3200_read_reg_8(struct iio_dev *indio_dev, + u8 reg_address, u8 *val) +{ + struct itg3200 *st = iio_priv(indio_dev); + int ret; + + ret = i2c_smbus_read_byte_data(st->i2c, reg_address); + if (ret < 0) + return ret; + *val = ret; + return 0; +} + +static int itg3200_read_reg_s16(struct iio_dev *indio_dev, u8 lower_reg_address, + int *val) +{ + struct itg3200 *st = iio_priv(indio_dev); + struct i2c_client *client = st->i2c; + int ret; + s16 out; + + struct i2c_msg msg[2] = { + { + .addr = client->addr, + .flags = client->flags, + .len = 1, + .buf = (char *)&lower_reg_address, + }, + { + .addr = client->addr, + .flags = client->flags | I2C_M_RD, + .len = 2, + .buf = (char *)&out, + }, + }; + + lower_reg_address |= 0x80; + ret = i2c_transfer(client->adapter, msg, 2); + be16_to_cpus(&out); + *val = out; + + return (ret == 2) ? 0 : ret; +} + +static int itg3200_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long info) +{ + int ret = 0; + u8 reg; + + switch (info) { + case IIO_CHAN_INFO_RAW: + reg = (u8)chan->address; + ret = itg3200_read_reg_s16(indio_dev, reg, val); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + if (chan->type == IIO_TEMP) + *val2 = 1000000000/280; + else + *val2 = 1214142; /* (1 / 14,375) * (PI / 180) */ + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_OFFSET: + /* Only the temperature channel has an offset */ + *val = 23000; + return IIO_VAL_INT; + default: + return -EINVAL; + } + + return ret; +} + +static ssize_t itg3200_read_frequency(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + int ret, sps; + u8 val; + + ret = itg3200_read_reg_8(indio_dev, ITG3200_REG_DLPF, &val); + if (ret) + return ret; + + sps = (val & ITG3200_DLPF_CFG_MASK) ? 1000 : 8000; + + ret = itg3200_read_reg_8(indio_dev, ITG3200_REG_SAMPLE_RATE_DIV, &val); + if (ret) + return ret; + + sps /= val + 1; + + return sprintf(buf, "%d\n", sps); +} + +static ssize_t itg3200_write_frequency(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + unsigned val; + int ret; + u8 t; + + ret = kstrtouint(buf, 10, &val); + if (ret) + return ret; + + mutex_lock(&indio_dev->mlock); + + ret = itg3200_read_reg_8(indio_dev, ITG3200_REG_DLPF, &t); + if (ret) + goto err_ret; + + if (val == 0) { + ret = -EINVAL; + goto err_ret; + } + t = ((t & ITG3200_DLPF_CFG_MASK) ? 1000u : 8000u) / val - 1; + + ret = itg3200_write_reg_8(indio_dev, ITG3200_REG_SAMPLE_RATE_DIV, t); + +err_ret: + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : len; +} + +/* + * Reset device and internal registers to the power-up-default settings + * Use the gyro clock as reference, as suggested by the datasheet + */ +static int itg3200_reset(struct iio_dev *indio_dev) +{ + struct itg3200 *st = iio_priv(indio_dev); + int ret; + + dev_dbg(&st->i2c->dev, "reset device"); + + ret = itg3200_write_reg_8(indio_dev, + ITG3200_REG_POWER_MANAGEMENT, + ITG3200_RESET); + if (ret) { + dev_err(&st->i2c->dev, "error resetting device"); + goto error_ret; + } + + /* Wait for PLL (1ms according to datasheet) */ + udelay(1500); + + ret = itg3200_write_reg_8(indio_dev, + ITG3200_REG_IRQ_CONFIG, + ITG3200_IRQ_ACTIVE_HIGH | + ITG3200_IRQ_PUSH_PULL | + ITG3200_IRQ_LATCH_50US_PULSE | + ITG3200_IRQ_LATCH_CLEAR_ANY); + + if (ret) + dev_err(&st->i2c->dev, "error init device"); + +error_ret: + return ret; +} + +/* itg3200_enable_full_scale() - Disables the digital low pass filter */ +static int itg3200_enable_full_scale(struct iio_dev *indio_dev) +{ + u8 val; + int ret; + + ret = itg3200_read_reg_8(indio_dev, ITG3200_REG_DLPF, &val); + if (ret) + goto err_ret; + + val |= ITG3200_DLPF_FS_SEL_2000; + return itg3200_write_reg_8(indio_dev, ITG3200_REG_DLPF, val); + +err_ret: + return ret; +} + +static int itg3200_initial_setup(struct iio_dev *indio_dev) +{ + struct itg3200 *st = iio_priv(indio_dev); + int ret; + u8 val; + + ret = itg3200_read_reg_8(indio_dev, ITG3200_REG_ADDRESS, &val); + if (ret) + goto err_ret; + + if (((val >> 1) & 0x3f) != 0x34) { + dev_err(&st->i2c->dev, "invalid reg value 0x%02x", val); + ret = -ENXIO; + goto err_ret; + } + + ret = itg3200_reset(indio_dev); + if (ret) + goto err_ret; + + ret = itg3200_enable_full_scale(indio_dev); +err_ret: + return ret; +} + +#define ITG3200_TEMP_INFO_MASK (IIO_CHAN_INFO_OFFSET_SHARED_BIT | \ + IIO_CHAN_INFO_SCALE_SHARED_BIT | \ + IIO_CHAN_INFO_RAW_SEPARATE_BIT) +#define ITG3200_GYRO_INFO_MASK (IIO_CHAN_INFO_SCALE_SHARED_BIT | \ + IIO_CHAN_INFO_RAW_SEPARATE_BIT) + +#define ITG3200_ST \ + { .sign = 's', .realbits = 16, .storagebits = 16, .endianness = IIO_BE } + +#define ITG3200_GYRO_CHAN(_mod) { \ + .type = IIO_ANGL_VEL, \ + .modified = 1, \ + .channel2 = IIO_MOD_ ## _mod, \ + .info_mask = ITG3200_GYRO_INFO_MASK, \ + .address = ITG3200_REG_GYRO_ ## _mod ## OUT_H, \ + .scan_index = ITG3200_SCAN_GYRO_ ## _mod, \ + .scan_type = ITG3200_ST, \ +} + +static const struct iio_chan_spec itg3200_channels[] = { + { + .type = IIO_TEMP, + .channel2 = IIO_NO_MOD, + .info_mask = ITG3200_TEMP_INFO_MASK, + .address = ITG3200_REG_TEMP_OUT_H, + .scan_index = ITG3200_SCAN_TEMP, + .scan_type = ITG3200_ST, + }, + ITG3200_GYRO_CHAN(X), + ITG3200_GYRO_CHAN(Y), + ITG3200_GYRO_CHAN(Z), + IIO_CHAN_SOFT_TIMESTAMP(ITG3200_SCAN_ELEMENTS), +}; + +/* IIO device attributes */ +static IIO_DEV_ATTR_SAMP_FREQ(S_IWUSR | S_IRUGO, itg3200_read_frequency, + itg3200_write_frequency); + +static struct attribute *itg3200_attributes[] = { + &iio_dev_attr_sampling_frequency.dev_attr.attr, + NULL +}; + +static const struct attribute_group itg3200_attribute_group = { + .attrs = itg3200_attributes, +}; + +static const struct iio_info itg3200_info = { + .attrs = &itg3200_attribute_group, + .read_raw = &itg3200_read_raw, + .driver_module = THIS_MODULE, +}; + +static const unsigned long itg3200_available_scan_masks[] = { 0xffffffff, 0x0 }; + +static int itg3200_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret; + struct itg3200 *st; + struct iio_dev *indio_dev; + + dev_dbg(&client->dev, "probe I2C dev with IRQ %i", client->irq); + + indio_dev = iio_device_alloc(sizeof(*st)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + + st = iio_priv(indio_dev); + + i2c_set_clientdata(client, indio_dev); + st->i2c = client; + + indio_dev->dev.parent = &client->dev; + indio_dev->name = client->dev.driver->name; + indio_dev->channels = itg3200_channels; + indio_dev->num_channels = ARRAY_SIZE(itg3200_channels); + indio_dev->available_scan_masks = itg3200_available_scan_masks; + indio_dev->info = &itg3200_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = itg3200_buffer_configure(indio_dev); + if (ret) + goto error_free_dev; + + if (client->irq) { + ret = itg3200_probe_trigger(indio_dev); + if (ret) + goto error_unconfigure_buffer; + } + + ret = itg3200_initial_setup(indio_dev); + if (ret) + goto error_remove_trigger; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_remove_trigger; + + return 0; + +error_remove_trigger: + if (client->irq) + itg3200_remove_trigger(indio_dev); +error_unconfigure_buffer: + itg3200_buffer_unconfigure(indio_dev); +error_free_dev: + iio_device_free(indio_dev); +error_ret: + return ret; +} + +static int itg3200_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + + iio_device_unregister(indio_dev); + + if (client->irq) + itg3200_remove_trigger(indio_dev); + + itg3200_buffer_unconfigure(indio_dev); + + iio_device_free(indio_dev); + + return 0; +} + +static const struct i2c_device_id itg3200_id[] = { + { "itg3200", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, itg3200_id); + +static struct i2c_driver itg3200_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "itg3200", + }, + .id_table = itg3200_id, + .probe = itg3200_probe, + .remove = itg3200_remove, +}; + +module_i2c_driver(itg3200_driver); + +MODULE_AUTHOR("Christian Strobel <christian.strobel@iis.fraunhofer.de>"); +MODULE_DESCRIPTION("ITG3200 Gyroscope I2C driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/gyro/st_gyro.h b/drivers/iio/gyro/st_gyro.h new file mode 100644 index 0000000..3ad9907 --- /dev/null +++ b/drivers/iio/gyro/st_gyro.h @@ -0,0 +1,45 @@ +/* + * STMicroelectronics gyroscopes driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca <denis.ciocca@st.com> + * v. 1.0.0 + * Licensed under the GPL-2. + */ + +#ifndef ST_GYRO_H +#define ST_GYRO_H + +#include <linux/types.h> +#include <linux/iio/common/st_sensors.h> + +#define L3G4200D_GYRO_DEV_NAME "l3g4200d" +#define LSM330D_GYRO_DEV_NAME "lsm330d_gyro" +#define LSM330DL_GYRO_DEV_NAME "lsm330dl_gyro" +#define LSM330DLC_GYRO_DEV_NAME "lsm330dlc_gyro" +#define L3GD20_GYRO_DEV_NAME "l3gd20" +#define L3GD20H_GYRO_DEV_NAME "l3gd20h" +#define L3G4IS_GYRO_DEV_NAME "l3g4is_ui" +#define LSM330_GYRO_DEV_NAME "lsm330_gyro" + +int st_gyro_common_probe(struct iio_dev *indio_dev); +void st_gyro_common_remove(struct iio_dev *indio_dev); + +#ifdef CONFIG_IIO_BUFFER +int st_gyro_allocate_ring(struct iio_dev *indio_dev); +void st_gyro_deallocate_ring(struct iio_dev *indio_dev); +int st_gyro_trig_set_state(struct iio_trigger *trig, bool state); +#define ST_GYRO_TRIGGER_SET_STATE (&st_gyro_trig_set_state) +#else /* CONFIG_IIO_BUFFER */ +static inline int st_gyro_allocate_ring(struct iio_dev *indio_dev) +{ + return 0; +} +static inline void st_gyro_deallocate_ring(struct iio_dev *indio_dev) +{ +} +#define ST_GYRO_TRIGGER_SET_STATE NULL +#endif /* CONFIG_IIO_BUFFER */ + +#endif /* ST_GYRO_H */ diff --git a/drivers/iio/gyro/st_gyro_buffer.c b/drivers/iio/gyro/st_gyro_buffer.c new file mode 100644 index 0000000..da4d122 --- /dev/null +++ b/drivers/iio/gyro/st_gyro_buffer.c @@ -0,0 +1,114 @@ +/* + * STMicroelectronics gyroscopes 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_gyro.h" + +int st_gyro_trig_set_state(struct iio_trigger *trig, bool state) +{ + struct iio_dev *indio_dev = trig->private_data; + + return st_sensors_set_dataready_irq(indio_dev, state); +} + +static int st_gyro_buffer_preenable(struct iio_dev *indio_dev) +{ + int err; + + err = st_sensors_set_enable(indio_dev, true); + if (err < 0) + goto st_gyro_set_enable_error; + + err = iio_sw_buffer_preenable(indio_dev); + +st_gyro_set_enable_error: + return err; +} + +static int st_gyro_buffer_postenable(struct iio_dev *indio_dev) +{ + int err; + struct st_sensor_data *gdata = iio_priv(indio_dev); + + gdata->buffer_data = kmalloc(indio_dev->scan_bytes, GFP_KERNEL); + if (gdata->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_gyro_buffer_postenable_error; + + err = iio_triggered_buffer_postenable(indio_dev); + if (err < 0) + goto st_gyro_buffer_postenable_error; + + return err; + +st_gyro_buffer_postenable_error: + kfree(gdata->buffer_data); +allocate_memory_error: + return err; +} + +static int st_gyro_buffer_predisable(struct iio_dev *indio_dev) +{ + int err; + struct st_sensor_data *gdata = iio_priv(indio_dev); + + err = iio_triggered_buffer_predisable(indio_dev); + if (err < 0) + goto st_gyro_buffer_predisable_error; + + err = st_sensors_set_axis_enable(indio_dev, ST_SENSORS_ENABLE_ALL_AXIS); + if (err < 0) + goto st_gyro_buffer_predisable_error; + + err = st_sensors_set_enable(indio_dev, false); + +st_gyro_buffer_predisable_error: + kfree(gdata->buffer_data); + return err; +} + +static const struct iio_buffer_setup_ops st_gyro_buffer_setup_ops = { + .preenable = &st_gyro_buffer_preenable, + .postenable = &st_gyro_buffer_postenable, + .predisable = &st_gyro_buffer_predisable, +}; + +int st_gyro_allocate_ring(struct iio_dev *indio_dev) +{ + return iio_triggered_buffer_setup(indio_dev, &iio_pollfunc_store_time, + &st_sensors_trigger_handler, &st_gyro_buffer_setup_ops); +} + +void st_gyro_deallocate_ring(struct iio_dev *indio_dev) +{ + iio_triggered_buffer_cleanup(indio_dev); +} + +MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics gyroscopes buffer"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/gyro/st_gyro_core.c b/drivers/iio/gyro/st_gyro_core.c new file mode 100644 index 0000000..fa9b242 --- /dev/null +++ b/drivers/iio/gyro/st_gyro_core.c @@ -0,0 +1,368 @@ +/* + * STMicroelectronics gyroscopes 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/delay.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_gyro.h" + +/* DEFAULT VALUE FOR SENSORS */ +#define ST_GYRO_DEFAULT_OUT_X_L_ADDR 0x28 +#define ST_GYRO_DEFAULT_OUT_Y_L_ADDR 0x2a +#define ST_GYRO_DEFAULT_OUT_Z_L_ADDR 0x2c + +/* FULLSCALE */ +#define ST_GYRO_FS_AVL_250DPS 250 +#define ST_GYRO_FS_AVL_500DPS 500 +#define ST_GYRO_FS_AVL_2000DPS 2000 + +/* CUSTOM VALUES FOR SENSOR 1 */ +#define ST_GYRO_1_WAI_EXP 0xd3 +#define ST_GYRO_1_ODR_ADDR 0x20 +#define ST_GYRO_1_ODR_MASK 0xc0 +#define ST_GYRO_1_ODR_AVL_100HZ_VAL 0x00 +#define ST_GYRO_1_ODR_AVL_200HZ_VAL 0x01 +#define ST_GYRO_1_ODR_AVL_400HZ_VAL 0x02 +#define ST_GYRO_1_ODR_AVL_800HZ_VAL 0x03 +#define ST_GYRO_1_PW_ADDR 0x20 +#define ST_GYRO_1_PW_MASK 0x08 +#define ST_GYRO_1_FS_ADDR 0x23 +#define ST_GYRO_1_FS_MASK 0x30 +#define ST_GYRO_1_FS_AVL_250_VAL 0x00 +#define ST_GYRO_1_FS_AVL_500_VAL 0x01 +#define ST_GYRO_1_FS_AVL_2000_VAL 0x02 +#define ST_GYRO_1_FS_AVL_250_GAIN IIO_DEGREE_TO_RAD(8750) +#define ST_GYRO_1_FS_AVL_500_GAIN IIO_DEGREE_TO_RAD(17500) +#define ST_GYRO_1_FS_AVL_2000_GAIN IIO_DEGREE_TO_RAD(70000) +#define ST_GYRO_1_BDU_ADDR 0x23 +#define ST_GYRO_1_BDU_MASK 0x80 +#define ST_GYRO_1_DRDY_IRQ_ADDR 0x22 +#define ST_GYRO_1_DRDY_IRQ_MASK 0x08 +#define ST_GYRO_1_MULTIREAD_BIT true + +/* CUSTOM VALUES FOR SENSOR 2 */ +#define ST_GYRO_2_WAI_EXP 0xd4 +#define ST_GYRO_2_ODR_ADDR 0x20 +#define ST_GYRO_2_ODR_MASK 0xc0 +#define ST_GYRO_2_ODR_AVL_95HZ_VAL 0x00 +#define ST_GYRO_2_ODR_AVL_190HZ_VAL 0x01 +#define ST_GYRO_2_ODR_AVL_380HZ_VAL 0x02 +#define ST_GYRO_2_ODR_AVL_760HZ_VAL 0x03 +#define ST_GYRO_2_PW_ADDR 0x20 +#define ST_GYRO_2_PW_MASK 0x08 +#define ST_GYRO_2_FS_ADDR 0x23 +#define ST_GYRO_2_FS_MASK 0x30 +#define ST_GYRO_2_FS_AVL_250_VAL 0x00 +#define ST_GYRO_2_FS_AVL_500_VAL 0x01 +#define ST_GYRO_2_FS_AVL_2000_VAL 0x02 +#define ST_GYRO_2_FS_AVL_250_GAIN IIO_DEGREE_TO_RAD(8750) +#define ST_GYRO_2_FS_AVL_500_GAIN IIO_DEGREE_TO_RAD(17500) +#define ST_GYRO_2_FS_AVL_2000_GAIN IIO_DEGREE_TO_RAD(70000) +#define ST_GYRO_2_BDU_ADDR 0x23 +#define ST_GYRO_2_BDU_MASK 0x80 +#define ST_GYRO_2_DRDY_IRQ_ADDR 0x22 +#define ST_GYRO_2_DRDY_IRQ_MASK 0x08 +#define ST_GYRO_2_MULTIREAD_BIT true + +static const struct iio_chan_spec st_gyro_16bit_channels[] = { + ST_SENSORS_LSM_CHANNELS(IIO_ANGL_VEL, ST_SENSORS_SCAN_X, + IIO_MOD_X, IIO_LE, ST_SENSORS_DEFAULT_16_REALBITS, + ST_GYRO_DEFAULT_OUT_X_L_ADDR), + ST_SENSORS_LSM_CHANNELS(IIO_ANGL_VEL, ST_SENSORS_SCAN_Y, + IIO_MOD_Y, IIO_LE, ST_SENSORS_DEFAULT_16_REALBITS, + ST_GYRO_DEFAULT_OUT_Y_L_ADDR), + ST_SENSORS_LSM_CHANNELS(IIO_ANGL_VEL, ST_SENSORS_SCAN_Z, + IIO_MOD_Z, IIO_LE, ST_SENSORS_DEFAULT_16_REALBITS, + ST_GYRO_DEFAULT_OUT_Z_L_ADDR), + IIO_CHAN_SOFT_TIMESTAMP(3) +}; + +static const struct st_sensors st_gyro_sensors[] = { + { + .wai = ST_GYRO_1_WAI_EXP, + .sensors_supported = { + [0] = L3G4200D_GYRO_DEV_NAME, + [1] = LSM330DL_GYRO_DEV_NAME, + }, + .ch = (struct iio_chan_spec *)st_gyro_16bit_channels, + .odr = { + .addr = ST_GYRO_1_ODR_ADDR, + .mask = ST_GYRO_1_ODR_MASK, + .odr_avl = { + { 100, ST_GYRO_1_ODR_AVL_100HZ_VAL, }, + { 200, ST_GYRO_1_ODR_AVL_200HZ_VAL, }, + { 400, ST_GYRO_1_ODR_AVL_400HZ_VAL, }, + { 800, ST_GYRO_1_ODR_AVL_800HZ_VAL, }, + }, + }, + .pw = { + .addr = ST_GYRO_1_PW_ADDR, + .mask = ST_GYRO_1_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_GYRO_1_FS_ADDR, + .mask = ST_GYRO_1_FS_MASK, + .fs_avl = { + [0] = { + .num = ST_GYRO_FS_AVL_250DPS, + .value = ST_GYRO_1_FS_AVL_250_VAL, + .gain = ST_GYRO_1_FS_AVL_250_GAIN, + }, + [1] = { + .num = ST_GYRO_FS_AVL_500DPS, + .value = ST_GYRO_1_FS_AVL_500_VAL, + .gain = ST_GYRO_1_FS_AVL_500_GAIN, + }, + [2] = { + .num = ST_GYRO_FS_AVL_2000DPS, + .value = ST_GYRO_1_FS_AVL_2000_VAL, + .gain = ST_GYRO_1_FS_AVL_2000_GAIN, + }, + }, + }, + .bdu = { + .addr = ST_GYRO_1_BDU_ADDR, + .mask = ST_GYRO_1_BDU_MASK, + }, + .drdy_irq = { + .addr = ST_GYRO_1_DRDY_IRQ_ADDR, + .mask = ST_GYRO_1_DRDY_IRQ_MASK, + }, + .multi_read_bit = ST_GYRO_1_MULTIREAD_BIT, + .bootime = 2, + }, + { + .wai = ST_GYRO_2_WAI_EXP, + .sensors_supported = { + [0] = L3GD20_GYRO_DEV_NAME, + [1] = L3GD20H_GYRO_DEV_NAME, + [2] = LSM330D_GYRO_DEV_NAME, + [3] = LSM330DLC_GYRO_DEV_NAME, + [4] = L3G4IS_GYRO_DEV_NAME, + [5] = LSM330_GYRO_DEV_NAME, + }, + .ch = (struct iio_chan_spec *)st_gyro_16bit_channels, + .odr = { + .addr = ST_GYRO_2_ODR_ADDR, + .mask = ST_GYRO_2_ODR_MASK, + .odr_avl = { + { 95, ST_GYRO_2_ODR_AVL_95HZ_VAL, }, + { 190, ST_GYRO_2_ODR_AVL_190HZ_VAL, }, + { 380, ST_GYRO_2_ODR_AVL_380HZ_VAL, }, + { 760, ST_GYRO_2_ODR_AVL_760HZ_VAL, }, + }, + }, + .pw = { + .addr = ST_GYRO_2_PW_ADDR, + .mask = ST_GYRO_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_GYRO_2_FS_ADDR, + .mask = ST_GYRO_2_FS_MASK, + .fs_avl = { + [0] = { + .num = ST_GYRO_FS_AVL_250DPS, + .value = ST_GYRO_2_FS_AVL_250_VAL, + .gain = ST_GYRO_2_FS_AVL_250_GAIN, + }, + [1] = { + .num = ST_GYRO_FS_AVL_500DPS, + .value = ST_GYRO_2_FS_AVL_500_VAL, + .gain = ST_GYRO_2_FS_AVL_500_GAIN, + }, + [2] = { + .num = ST_GYRO_FS_AVL_2000DPS, + .value = ST_GYRO_2_FS_AVL_2000_VAL, + .gain = ST_GYRO_2_FS_AVL_2000_GAIN, + }, + }, + }, + .bdu = { + .addr = ST_GYRO_2_BDU_ADDR, + .mask = ST_GYRO_2_BDU_MASK, + }, + .drdy_irq = { + .addr = ST_GYRO_2_DRDY_IRQ_ADDR, + .mask = ST_GYRO_2_DRDY_IRQ_MASK, + }, + .multi_read_bit = ST_GYRO_2_MULTIREAD_BIT, + .bootime = 2, + }, +}; + +static int st_gyro_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 *gdata = 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 = gdata->current_fullscale->gain; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + +read_error: + return err; +} + +static int st_gyro_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: + err = -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_anglvel_scale_available); + +static struct attribute *st_gyro_attributes[] = { + &iio_dev_attr_sampling_frequency_available.dev_attr.attr, + &iio_dev_attr_in_anglvel_scale_available.dev_attr.attr, + &iio_dev_attr_sampling_frequency.dev_attr.attr, + NULL, +}; + +static const struct attribute_group st_gyro_attribute_group = { + .attrs = st_gyro_attributes, +}; + +static const struct iio_info gyro_info = { + .driver_module = THIS_MODULE, + .attrs = &st_gyro_attribute_group, + .read_raw = &st_gyro_read_raw, + .write_raw = &st_gyro_write_raw, +}; + +#ifdef CONFIG_IIO_TRIGGER +static const struct iio_trigger_ops st_gyro_trigger_ops = { + .owner = THIS_MODULE, + .set_trigger_state = ST_GYRO_TRIGGER_SET_STATE, +}; +#define ST_GYRO_TRIGGER_OPS (&st_gyro_trigger_ops) +#else +#define ST_GYRO_TRIGGER_OPS NULL +#endif + +int st_gyro_common_probe(struct iio_dev *indio_dev) +{ + int err; + struct st_sensor_data *gdata = iio_priv(indio_dev); + + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &gyro_info; + + err = st_sensors_check_device_support(indio_dev, + ARRAY_SIZE(st_gyro_sensors), st_gyro_sensors); + if (err < 0) + goto st_gyro_common_probe_error; + + gdata->multiread_bit = gdata->sensor->multi_read_bit; + indio_dev->channels = gdata->sensor->ch; + indio_dev->num_channels = ST_SENSORS_NUMBER_ALL_CHANNELS; + + gdata->current_fullscale = (struct st_sensor_fullscale_avl *) + &gdata->sensor->fs.fs_avl[0]; + gdata->odr = gdata->sensor->odr.odr_avl[0].hz; + + err = st_sensors_init_sensor(indio_dev); + if (err < 0) + goto st_gyro_common_probe_error; + + if (gdata->get_irq_data_ready(indio_dev) > 0) { + err = st_gyro_allocate_ring(indio_dev); + if (err < 0) + goto st_gyro_common_probe_error; + + err = st_sensors_allocate_trigger(indio_dev, + ST_GYRO_TRIGGER_OPS); + if (err < 0) + goto st_gyro_probe_trigger_error; + } + + err = iio_device_register(indio_dev); + if (err) + goto st_gyro_device_register_error; + + return err; + +st_gyro_device_register_error: + if (gdata->get_irq_data_ready(indio_dev) > 0) + st_sensors_deallocate_trigger(indio_dev); +st_gyro_probe_trigger_error: + if (gdata->get_irq_data_ready(indio_dev) > 0) + st_gyro_deallocate_ring(indio_dev); +st_gyro_common_probe_error: + return err; +} +EXPORT_SYMBOL(st_gyro_common_probe); + +void st_gyro_common_remove(struct iio_dev *indio_dev) +{ + struct st_sensor_data *gdata = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + if (gdata->get_irq_data_ready(indio_dev) > 0) { + st_sensors_deallocate_trigger(indio_dev); + st_gyro_deallocate_ring(indio_dev); + } + iio_device_free(indio_dev); +} +EXPORT_SYMBOL(st_gyro_common_remove); + +MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics gyroscopes driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/gyro/st_gyro_i2c.c b/drivers/iio/gyro/st_gyro_i2c.c new file mode 100644 index 0000000..8a31050 --- /dev/null +++ b/drivers/iio/gyro/st_gyro_i2c.c @@ -0,0 +1,84 @@ +/* + * STMicroelectronics gyroscopes 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_gyro.h" + +static int st_gyro_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct iio_dev *indio_dev; + struct st_sensor_data *gdata; + int err; + + indio_dev = iio_device_alloc(sizeof(*gdata)); + if (indio_dev == NULL) { + err = -ENOMEM; + goto iio_device_alloc_error; + } + + gdata = iio_priv(indio_dev); + gdata->dev = &client->dev; + + st_sensors_i2c_configure(indio_dev, client, gdata); + + err = st_gyro_common_probe(indio_dev); + if (err < 0) + goto st_gyro_common_probe_error; + + return 0; + +st_gyro_common_probe_error: + iio_device_free(indio_dev); +iio_device_alloc_error: + return err; +} + +static int st_gyro_i2c_remove(struct i2c_client *client) +{ + st_gyro_common_remove(i2c_get_clientdata(client)); + + return 0; +} + +static const struct i2c_device_id st_gyro_id_table[] = { + { L3G4200D_GYRO_DEV_NAME }, + { LSM330D_GYRO_DEV_NAME }, + { LSM330DL_GYRO_DEV_NAME }, + { LSM330DLC_GYRO_DEV_NAME }, + { L3GD20_GYRO_DEV_NAME }, + { L3GD20H_GYRO_DEV_NAME }, + { L3G4IS_GYRO_DEV_NAME }, + { LSM330_GYRO_DEV_NAME }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, st_gyro_id_table); + +static struct i2c_driver st_gyro_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "st-gyro-i2c", + }, + .probe = st_gyro_i2c_probe, + .remove = st_gyro_i2c_remove, + .id_table = st_gyro_id_table, +}; +module_i2c_driver(st_gyro_driver); + +MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics gyroscopes i2c driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/gyro/st_gyro_spi.c b/drivers/iio/gyro/st_gyro_spi.c new file mode 100644 index 0000000..f354039 --- /dev/null +++ b/drivers/iio/gyro/st_gyro_spi.c @@ -0,0 +1,83 @@ +/* + * STMicroelectronics gyroscopes 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_gyro.h" + +static int st_gyro_spi_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct st_sensor_data *gdata; + int err; + + indio_dev = iio_device_alloc(sizeof(*gdata)); + if (indio_dev == NULL) { + err = -ENOMEM; + goto iio_device_alloc_error; + } + + gdata = iio_priv(indio_dev); + gdata->dev = &spi->dev; + + st_sensors_spi_configure(indio_dev, spi, gdata); + + err = st_gyro_common_probe(indio_dev); + if (err < 0) + goto st_gyro_common_probe_error; + + return 0; + +st_gyro_common_probe_error: + iio_device_free(indio_dev); +iio_device_alloc_error: + return err; +} + +static int st_gyro_spi_remove(struct spi_device *spi) +{ + st_gyro_common_remove(spi_get_drvdata(spi)); + + return 0; +} + +static const struct spi_device_id st_gyro_id_table[] = { + { L3G4200D_GYRO_DEV_NAME }, + { LSM330D_GYRO_DEV_NAME }, + { LSM330DL_GYRO_DEV_NAME }, + { LSM330DLC_GYRO_DEV_NAME }, + { L3GD20_GYRO_DEV_NAME }, + { L3GD20H_GYRO_DEV_NAME }, + { L3G4IS_GYRO_DEV_NAME }, + { LSM330_GYRO_DEV_NAME }, + {}, +}; +MODULE_DEVICE_TABLE(spi, st_gyro_id_table); + +static struct spi_driver st_gyro_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "st-gyro-spi", + }, + .probe = st_gyro_spi_probe, + .remove = st_gyro_spi_remove, + .id_table = st_gyro_id_table, +}; +module_spi_driver(st_gyro_driver); + +MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics gyroscopes spi driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/imu/Kconfig b/drivers/iio/imu/Kconfig index 3d79a40..4f40a10 100644 --- a/drivers/iio/imu/Kconfig +++ b/drivers/iio/imu/Kconfig @@ -3,6 +3,17 @@ # menu "Inertial measurement units" +config ADIS16400 + tristate "Analog Devices ADIS16400 and similar IMU SPI driver" + depends on SPI + select IIO_ADIS_LIB + select IIO_ADIS_LIB_BUFFER if IIO_BUFFER + help + Say yes here to build support for Analog Devices adis16300, adis16344, + adis16350, adis16354, adis16355, adis16360, adis16362, adis16364, + adis16365, adis16400 and adis16405 triaxial inertial sensors + (adis16400 series also have magnetometers). + config ADIS16480 tristate "Analog Devices ADIS16480 and similar IMU driver" depends on SPI @@ -25,3 +36,5 @@ config IIO_ADIS_LIB_BUFFER help A set of buffer helper functions for the Analog Devices ADIS* device family. + +source "drivers/iio/imu/inv_mpu6050/Kconfig" diff --git a/drivers/iio/imu/Makefile b/drivers/iio/imu/Makefile index cfe5763..f2f56ce 100644 --- a/drivers/iio/imu/Makefile +++ b/drivers/iio/imu/Makefile @@ -2,9 +2,14 @@ # Makefile for Inertial Measurement Units # +adis16400-y := adis16400_core.o +adis16400-$(CONFIG_IIO_BUFFER) += adis16400_buffer.o +obj-$(CONFIG_ADIS16400) += adis16400.o obj-$(CONFIG_ADIS16480) += adis16480.o adis_lib-y += adis.o adis_lib-$(CONFIG_IIO_ADIS_LIB_BUFFER) += adis_trigger.o adis_lib-$(CONFIG_IIO_ADIS_LIB_BUFFER) += adis_buffer.o obj-$(CONFIG_IIO_ADIS_LIB) += adis_lib.o + +obj-y += inv_mpu6050/ diff --git a/drivers/iio/imu/adis16400.h b/drivers/iio/imu/adis16400.h new file mode 100644 index 0000000..2f8f9d6 --- /dev/null +++ b/drivers/iio/imu/adis16400.h @@ -0,0 +1,212 @@ +/* + * adis16400.h support Analog Devices ADIS16400 + * 3d 18g accelerometers, + * 3d gyroscopes, + * 3d 2.5gauss magnetometers via SPI + * + * Copyright (c) 2009 Manuel Stahl <manuel.stahl@iis.fraunhofer.de> + * Copyright (c) 2007 Jonathan Cameron <jic23@kernel.org> + * + * Loosely based upon lis3l02dq.h + * + * 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_ADIS16400_H_ +#define SPI_ADIS16400_H_ + +#include <linux/iio/imu/adis.h> + +#define ADIS16400_STARTUP_DELAY 290 /* ms */ +#define ADIS16400_MTEST_DELAY 90 /* ms */ + +#define ADIS16400_FLASH_CNT 0x00 /* Flash memory write count */ +#define ADIS16400_SUPPLY_OUT 0x02 /* Power supply measurement */ +#define ADIS16400_XGYRO_OUT 0x04 /* X-axis gyroscope output */ +#define ADIS16400_YGYRO_OUT 0x06 /* Y-axis gyroscope output */ +#define ADIS16400_ZGYRO_OUT 0x08 /* Z-axis gyroscope output */ +#define ADIS16400_XACCL_OUT 0x0A /* X-axis accelerometer output */ +#define ADIS16400_YACCL_OUT 0x0C /* Y-axis accelerometer output */ +#define ADIS16400_ZACCL_OUT 0x0E /* Z-axis accelerometer output */ +#define ADIS16400_XMAGN_OUT 0x10 /* X-axis magnetometer measurement */ +#define ADIS16400_YMAGN_OUT 0x12 /* Y-axis magnetometer measurement */ +#define ADIS16400_ZMAGN_OUT 0x14 /* Z-axis magnetometer measurement */ +#define ADIS16400_TEMP_OUT 0x16 /* Temperature output */ +#define ADIS16400_AUX_ADC 0x18 /* Auxiliary ADC measurement */ + +#define ADIS16350_XTEMP_OUT 0x10 /* X-axis gyroscope temperature measurement */ +#define ADIS16350_YTEMP_OUT 0x12 /* Y-axis gyroscope temperature measurement */ +#define ADIS16350_ZTEMP_OUT 0x14 /* Z-axis gyroscope temperature measurement */ + +#define ADIS16300_PITCH_OUT 0x12 /* X axis inclinometer output measurement */ +#define ADIS16300_ROLL_OUT 0x14 /* Y axis inclinometer output measurement */ +#define ADIS16300_AUX_ADC 0x16 /* Auxiliary ADC measurement */ + +#define ADIS16448_BARO_OUT 0x16 /* Barometric pressure output */ +#define ADIS16448_TEMP_OUT 0x18 /* Temperature output */ + +/* Calibration parameters */ +#define ADIS16400_XGYRO_OFF 0x1A /* X-axis gyroscope bias offset factor */ +#define ADIS16400_YGYRO_OFF 0x1C /* Y-axis gyroscope bias offset factor */ +#define ADIS16400_ZGYRO_OFF 0x1E /* Z-axis gyroscope bias offset factor */ +#define ADIS16400_XACCL_OFF 0x20 /* X-axis acceleration bias offset factor */ +#define ADIS16400_YACCL_OFF 0x22 /* Y-axis acceleration bias offset factor */ +#define ADIS16400_ZACCL_OFF 0x24 /* Z-axis acceleration bias offset factor */ +#define ADIS16400_XMAGN_HIF 0x26 /* X-axis magnetometer, hard-iron factor */ +#define ADIS16400_YMAGN_HIF 0x28 /* Y-axis magnetometer, hard-iron factor */ +#define ADIS16400_ZMAGN_HIF 0x2A /* Z-axis magnetometer, hard-iron factor */ +#define ADIS16400_XMAGN_SIF 0x2C /* X-axis magnetometer, soft-iron factor */ +#define ADIS16400_YMAGN_SIF 0x2E /* Y-axis magnetometer, soft-iron factor */ +#define ADIS16400_ZMAGN_SIF 0x30 /* Z-axis magnetometer, soft-iron factor */ + +#define ADIS16400_GPIO_CTRL 0x32 /* Auxiliary digital input/output control */ +#define ADIS16400_MSC_CTRL 0x34 /* Miscellaneous control */ +#define ADIS16400_SMPL_PRD 0x36 /* Internal sample period (rate) control */ +#define ADIS16400_SENS_AVG 0x38 /* Dynamic range and digital filter control */ +#define ADIS16400_SLP_CNT 0x3A /* Sleep mode control */ +#define ADIS16400_DIAG_STAT 0x3C /* System status */ + +/* Alarm functions */ +#define ADIS16400_GLOB_CMD 0x3E /* System command */ +#define ADIS16400_ALM_MAG1 0x40 /* Alarm 1 amplitude threshold */ +#define ADIS16400_ALM_MAG2 0x42 /* Alarm 2 amplitude threshold */ +#define ADIS16400_ALM_SMPL1 0x44 /* Alarm 1 sample size */ +#define ADIS16400_ALM_SMPL2 0x46 /* Alarm 2 sample size */ +#define ADIS16400_ALM_CTRL 0x48 /* Alarm control */ +#define ADIS16400_AUX_DAC 0x4A /* Auxiliary DAC data */ + +#define ADIS16334_LOT_ID1 0x52 /* Lot identification code 1 */ +#define ADIS16334_LOT_ID2 0x54 /* Lot identification code 2 */ +#define ADIS16400_PRODUCT_ID 0x56 /* Product identifier */ +#define ADIS16334_SERIAL_NUMBER 0x58 /* Serial number, lot specific */ + +#define ADIS16400_ERROR_ACTIVE (1<<14) +#define ADIS16400_NEW_DATA (1<<14) + +/* MSC_CTRL */ +#define ADIS16400_MSC_CTRL_MEM_TEST (1<<11) +#define ADIS16400_MSC_CTRL_INT_SELF_TEST (1<<10) +#define ADIS16400_MSC_CTRL_NEG_SELF_TEST (1<<9) +#define ADIS16400_MSC_CTRL_POS_SELF_TEST (1<<8) +#define ADIS16400_MSC_CTRL_GYRO_BIAS (1<<7) +#define ADIS16400_MSC_CTRL_ACCL_ALIGN (1<<6) +#define ADIS16400_MSC_CTRL_DATA_RDY_EN (1<<2) +#define ADIS16400_MSC_CTRL_DATA_RDY_POL_HIGH (1<<1) +#define ADIS16400_MSC_CTRL_DATA_RDY_DIO2 (1<<0) + +/* SMPL_PRD */ +#define ADIS16400_SMPL_PRD_TIME_BASE (1<<7) +#define ADIS16400_SMPL_PRD_DIV_MASK 0x7F + +/* DIAG_STAT */ +#define ADIS16400_DIAG_STAT_ZACCL_FAIL 15 +#define ADIS16400_DIAG_STAT_YACCL_FAIL 14 +#define ADIS16400_DIAG_STAT_XACCL_FAIL 13 +#define ADIS16400_DIAG_STAT_XGYRO_FAIL 12 +#define ADIS16400_DIAG_STAT_YGYRO_FAIL 11 +#define ADIS16400_DIAG_STAT_ZGYRO_FAIL 10 +#define ADIS16400_DIAG_STAT_ALARM2 9 +#define ADIS16400_DIAG_STAT_ALARM1 8 +#define ADIS16400_DIAG_STAT_FLASH_CHK 6 +#define ADIS16400_DIAG_STAT_SELF_TEST 5 +#define ADIS16400_DIAG_STAT_OVERFLOW 4 +#define ADIS16400_DIAG_STAT_SPI_FAIL 3 +#define ADIS16400_DIAG_STAT_FLASH_UPT 2 +#define ADIS16400_DIAG_STAT_POWER_HIGH 1 +#define ADIS16400_DIAG_STAT_POWER_LOW 0 + +/* GLOB_CMD */ +#define ADIS16400_GLOB_CMD_SW_RESET (1<<7) +#define ADIS16400_GLOB_CMD_P_AUTO_NULL (1<<4) +#define ADIS16400_GLOB_CMD_FLASH_UPD (1<<3) +#define ADIS16400_GLOB_CMD_DAC_LATCH (1<<2) +#define ADIS16400_GLOB_CMD_FAC_CALIB (1<<1) +#define ADIS16400_GLOB_CMD_AUTO_NULL (1<<0) + +/* SLP_CNT */ +#define ADIS16400_SLP_CNT_POWER_OFF (1<<8) + +#define ADIS16334_RATE_DIV_SHIFT 8 +#define ADIS16334_RATE_INT_CLK BIT(0) + +#define ADIS16400_SPI_SLOW (u32)(300 * 1000) +#define ADIS16400_SPI_BURST (u32)(1000 * 1000) +#define ADIS16400_SPI_FAST (u32)(2000 * 1000) + +#define ADIS16400_HAS_PROD_ID BIT(0) +#define ADIS16400_NO_BURST BIT(1) +#define ADIS16400_HAS_SLOW_MODE BIT(2) +#define ADIS16400_HAS_SERIAL_NUMBER BIT(3) + +struct adis16400_state; + +struct adis16400_chip_info { + const struct iio_chan_spec *channels; + const int num_channels; + const long flags; + unsigned int gyro_scale_micro; + unsigned int accel_scale_micro; + int temp_scale_nano; + int temp_offset; + int (*set_freq)(struct adis16400_state *st, unsigned int freq); + int (*get_freq)(struct adis16400_state *st); +}; + +/** + * struct adis16400_state - device instance specific data + * @variant: chip variant info + * @filt_int: integer part of requested filter frequency + * @adis: adis device + **/ +struct adis16400_state { + struct adis16400_chip_info *variant; + int filt_int; + + struct adis adis; +}; + +/* At the moment triggers are only used for ring buffer + * filling. This may change! + */ + +enum { + ADIS16400_SCAN_SUPPLY, + ADIS16400_SCAN_GYRO_X, + ADIS16400_SCAN_GYRO_Y, + ADIS16400_SCAN_GYRO_Z, + ADIS16400_SCAN_ACC_X, + ADIS16400_SCAN_ACC_Y, + ADIS16400_SCAN_ACC_Z, + ADIS16400_SCAN_MAGN_X, + ADIS16400_SCAN_MAGN_Y, + ADIS16400_SCAN_MAGN_Z, + ADIS16400_SCAN_BARO, + ADIS16350_SCAN_TEMP_X, + ADIS16350_SCAN_TEMP_Y, + ADIS16350_SCAN_TEMP_Z, + ADIS16300_SCAN_INCLI_X, + ADIS16300_SCAN_INCLI_Y, + ADIS16400_SCAN_ADC, +}; + +#ifdef CONFIG_IIO_BUFFER + +ssize_t adis16400_read_data_from_ring(struct device *dev, + struct device_attribute *attr, + char *buf); + + +int adis16400_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask); +irqreturn_t adis16400_trigger_handler(int irq, void *p); + +#else /* CONFIG_IIO_BUFFER */ + +#define adis16400_update_scan_mode NULL +#define adis16400_trigger_handler NULL + +#endif /* CONFIG_IIO_BUFFER */ + +#endif /* SPI_ADIS16400_H_ */ diff --git a/drivers/iio/imu/adis16400_buffer.c b/drivers/iio/imu/adis16400_buffer.c new file mode 100644 index 0000000..054c01d --- /dev/null +++ b/drivers/iio/imu/adis16400_buffer.c @@ -0,0 +1,96 @@ +#include <linux/interrupt.h> +#include <linux/mutex.h> +#include <linux/kernel.h> +#include <linux/spi/spi.h> +#include <linux/slab.h> +#include <linux/bitops.h> +#include <linux/export.h> + +#include <linux/iio/iio.h> +#include <linux/iio/buffer.h> +#include <linux/iio/triggered_buffer.h> +#include <linux/iio/trigger_consumer.h> + +#include "adis16400.h" + +int adis16400_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct adis16400_state *st = iio_priv(indio_dev); + struct adis *adis = &st->adis; + uint16_t *tx, *rx; + + if (st->variant->flags & ADIS16400_NO_BURST) + return adis_update_scan_mode(indio_dev, scan_mask); + + kfree(adis->xfer); + kfree(adis->buffer); + + adis->xfer = kcalloc(2, sizeof(*adis->xfer), GFP_KERNEL); + if (!adis->xfer) + return -ENOMEM; + + adis->buffer = kzalloc(indio_dev->scan_bytes + sizeof(u16), + GFP_KERNEL); + if (!adis->buffer) + return -ENOMEM; + + rx = adis->buffer; + tx = adis->buffer + indio_dev->scan_bytes; + + tx[0] = ADIS_READ_REG(ADIS16400_GLOB_CMD); + tx[1] = 0; + + adis->xfer[0].tx_buf = tx; + adis->xfer[0].bits_per_word = 8; + adis->xfer[0].len = 2; + adis->xfer[1].tx_buf = tx; + adis->xfer[1].bits_per_word = 8; + adis->xfer[1].len = indio_dev->scan_bytes; + + spi_message_init(&adis->msg); + spi_message_add_tail(&adis->xfer[0], &adis->msg); + spi_message_add_tail(&adis->xfer[1], &adis->msg); + + return 0; +} + +irqreturn_t adis16400_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct adis16400_state *st = iio_priv(indio_dev); + struct adis *adis = &st->adis; + u32 old_speed_hz = st->adis.spi->max_speed_hz; + int ret; + + if (!adis->buffer) + return -ENOMEM; + + if (!(st->variant->flags & ADIS16400_NO_BURST) && + st->adis.spi->max_speed_hz > ADIS16400_SPI_BURST) { + st->adis.spi->max_speed_hz = ADIS16400_SPI_BURST; + spi_setup(st->adis.spi); + } + + ret = spi_sync(adis->spi, &adis->msg); + if (ret) + dev_err(&adis->spi->dev, "Failed to read data: %d\n", ret); + + if (!(st->variant->flags & ADIS16400_NO_BURST)) { + st->adis.spi->max_speed_hz = old_speed_hz; + spi_setup(st->adis.spi); + } + + /* Guaranteed to be aligned with 8 byte boundary */ + if (indio_dev->scan_timestamp) { + void *b = adis->buffer + indio_dev->scan_bytes - sizeof(s64); + *(s64 *)b = pf->timestamp; + } + + iio_push_to_buffers(indio_dev, adis->buffer); + + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} diff --git a/drivers/iio/imu/adis16400_core.c b/drivers/iio/imu/adis16400_core.c new file mode 100644 index 0000000..b7f215e --- /dev/null +++ b/drivers/iio/imu/adis16400_core.c @@ -0,0 +1,965 @@ +/* + * adis16400.c support Analog Devices ADIS16400/5 + * 3d 2g Linear Accelerometers, + * 3d Gyroscopes, + * 3d Magnetometers via SPI + * + * Copyright (c) 2009 Manuel Stahl <manuel.stahl@iis.fraunhofer.de> + * Copyright (c) 2007 Jonathan Cameron <jic23@kernel.org> + * Copyright (c) 2011 Analog Devices Inc. + * + * 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/interrupt.h> +#include <linux/irq.h> +#include <linux/delay.h> +#include <linux/mutex.h> +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/spi/spi.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/debugfs.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/buffer.h> + +#include "adis16400.h" + +#ifdef CONFIG_DEBUG_FS + +static ssize_t adis16400_show_serial_number(struct file *file, + char __user *userbuf, size_t count, loff_t *ppos) +{ + struct adis16400_state *st = file->private_data; + u16 lot1, lot2, serial_number; + char buf[16]; + size_t len; + int ret; + + ret = adis_read_reg_16(&st->adis, ADIS16334_LOT_ID1, &lot1); + if (ret < 0) + return ret; + + ret = adis_read_reg_16(&st->adis, ADIS16334_LOT_ID2, &lot2); + if (ret < 0) + return ret; + + ret = adis_read_reg_16(&st->adis, ADIS16334_SERIAL_NUMBER, + &serial_number); + if (ret < 0) + return ret; + + len = snprintf(buf, sizeof(buf), "%.4x-%.4x-%.4x\n", lot1, lot2, + serial_number); + + return simple_read_from_buffer(userbuf, count, ppos, buf, len); +} + +static const struct file_operations adis16400_serial_number_fops = { + .open = simple_open, + .read = adis16400_show_serial_number, + .llseek = default_llseek, + .owner = THIS_MODULE, +}; + +static int adis16400_show_product_id(void *arg, u64 *val) +{ + struct adis16400_state *st = arg; + uint16_t prod_id; + int ret; + + ret = adis_read_reg_16(&st->adis, ADIS16400_PRODUCT_ID, &prod_id); + if (ret < 0) + return ret; + + *val = prod_id; + + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(adis16400_product_id_fops, + adis16400_show_product_id, NULL, "%lld\n"); + +static int adis16400_show_flash_count(void *arg, u64 *val) +{ + struct adis16400_state *st = arg; + uint16_t flash_count; + int ret; + + ret = adis_read_reg_16(&st->adis, ADIS16400_FLASH_CNT, &flash_count); + if (ret < 0) + return ret; + + *val = flash_count; + + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(adis16400_flash_count_fops, + adis16400_show_flash_count, NULL, "%lld\n"); + +static int adis16400_debugfs_init(struct iio_dev *indio_dev) +{ + struct adis16400_state *st = iio_priv(indio_dev); + + if (st->variant->flags & ADIS16400_HAS_SERIAL_NUMBER) + debugfs_create_file("serial_number", 0400, + indio_dev->debugfs_dentry, st, + &adis16400_serial_number_fops); + if (st->variant->flags & ADIS16400_HAS_PROD_ID) + debugfs_create_file("product_id", 0400, + indio_dev->debugfs_dentry, st, + &adis16400_product_id_fops); + debugfs_create_file("flash_count", 0400, indio_dev->debugfs_dentry, + st, &adis16400_flash_count_fops); + + return 0; +} + +#else + +static int adis16400_debugfs_init(struct iio_dev *indio_dev) +{ + return 0; +} + +#endif + +enum adis16400_chip_variant { + ADIS16300, + ADIS16334, + ADIS16350, + ADIS16360, + ADIS16362, + ADIS16364, + ADIS16400, + ADIS16448, +}; + +static int adis16334_get_freq(struct adis16400_state *st) +{ + int ret; + uint16_t t; + + ret = adis_read_reg_16(&st->adis, ADIS16400_SMPL_PRD, &t); + if (ret < 0) + return ret; + + t >>= ADIS16334_RATE_DIV_SHIFT; + + return 819200 >> t; +} + +static int adis16334_set_freq(struct adis16400_state *st, unsigned int freq) +{ + unsigned int t; + + if (freq < 819200) + t = ilog2(819200 / freq); + else + t = 0; + + if (t > 0x31) + t = 0x31; + + t <<= ADIS16334_RATE_DIV_SHIFT; + t |= ADIS16334_RATE_INT_CLK; + + return adis_write_reg_16(&st->adis, ADIS16400_SMPL_PRD, t); +} + +static int adis16400_get_freq(struct adis16400_state *st) +{ + int sps, ret; + uint16_t t; + + ret = adis_read_reg_16(&st->adis, ADIS16400_SMPL_PRD, &t); + if (ret < 0) + return ret; + + sps = (t & ADIS16400_SMPL_PRD_TIME_BASE) ? 52851 : 1638404; + sps /= (t & ADIS16400_SMPL_PRD_DIV_MASK) + 1; + + return sps; +} + +static int adis16400_set_freq(struct adis16400_state *st, unsigned int freq) +{ + unsigned int t; + uint8_t val = 0; + + t = 1638404 / freq; + if (t >= 128) { + val |= ADIS16400_SMPL_PRD_TIME_BASE; + t = 52851 / freq; + if (t >= 128) + t = 127; + } else if (t != 0) { + t--; + } + + val |= t; + + if (t >= 0x0A || (val & ADIS16400_SMPL_PRD_TIME_BASE)) + st->adis.spi->max_speed_hz = ADIS16400_SPI_SLOW; + else + st->adis.spi->max_speed_hz = ADIS16400_SPI_FAST; + + return adis_write_reg_8(&st->adis, ADIS16400_SMPL_PRD, val); +} + +static ssize_t adis16400_read_frequency(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct adis16400_state *st = iio_priv(indio_dev); + int ret; + + ret = st->variant->get_freq(st); + if (ret < 0) + return ret; + + return sprintf(buf, "%d.%.3d\n", ret / 1000, ret % 1000); +} + +static const unsigned adis16400_3db_divisors[] = { + [0] = 2, /* Special case */ + [1] = 6, + [2] = 12, + [3] = 25, + [4] = 50, + [5] = 100, + [6] = 200, + [7] = 200, /* Not a valid setting */ +}; + +static int adis16400_set_filter(struct iio_dev *indio_dev, int sps, int val) +{ + struct adis16400_state *st = iio_priv(indio_dev); + uint16_t val16; + int i, ret; + + for (i = ARRAY_SIZE(adis16400_3db_divisors) - 1; i >= 1; i--) { + if (sps / adis16400_3db_divisors[i] >= val) + break; + } + + ret = adis_read_reg_16(&st->adis, ADIS16400_SENS_AVG, &val16); + if (ret < 0) + return ret; + + ret = adis_write_reg_16(&st->adis, ADIS16400_SENS_AVG, + (val16 & ~0x07) | i); + return ret; +} + +static ssize_t adis16400_write_frequency(struct device *dev, + struct device_attribute *attr, const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct adis16400_state *st = iio_priv(indio_dev); + int i, f, val; + int ret; + + ret = iio_str_to_fixpoint(buf, 100, &i, &f); + if (ret) + return ret; + + val = i * 1000 + f; + + if (val <= 0) + return -EINVAL; + + mutex_lock(&indio_dev->mlock); + st->variant->set_freq(st, val); + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : len; +} + +/* Power down the device */ +static int adis16400_stop_device(struct iio_dev *indio_dev) +{ + struct adis16400_state *st = iio_priv(indio_dev); + int ret; + + ret = adis_write_reg_16(&st->adis, ADIS16400_SLP_CNT, + ADIS16400_SLP_CNT_POWER_OFF); + if (ret) + dev_err(&indio_dev->dev, + "problem with turning device off: SLP_CNT"); + + return ret; +} + +static int adis16400_initial_setup(struct iio_dev *indio_dev) +{ + struct adis16400_state *st = iio_priv(indio_dev); + uint16_t prod_id, smp_prd; + unsigned int device_id; + int ret; + + /* use low spi speed for init if the device has a slow mode */ + if (st->variant->flags & ADIS16400_HAS_SLOW_MODE) + st->adis.spi->max_speed_hz = ADIS16400_SPI_SLOW; + else + st->adis.spi->max_speed_hz = ADIS16400_SPI_FAST; + st->adis.spi->mode = SPI_MODE_3; + spi_setup(st->adis.spi); + + ret = adis_initial_startup(&st->adis); + if (ret) + return ret; + + if (st->variant->flags & ADIS16400_HAS_PROD_ID) { + ret = adis_read_reg_16(&st->adis, + ADIS16400_PRODUCT_ID, &prod_id); + if (ret) + goto err_ret; + + sscanf(indio_dev->name, "adis%u\n", &device_id); + + if (prod_id != device_id) + dev_warn(&indio_dev->dev, "Device ID(%u) and product ID(%u) do not match.", + device_id, prod_id); + + dev_info(&indio_dev->dev, "%s: prod_id 0x%04x at CS%d (irq %d)\n", + indio_dev->name, prod_id, + st->adis.spi->chip_select, st->adis.spi->irq); + } + /* use high spi speed if possible */ + if (st->variant->flags & ADIS16400_HAS_SLOW_MODE) { + ret = adis_read_reg_16(&st->adis, ADIS16400_SMPL_PRD, &smp_prd); + if (ret) + goto err_ret; + + if ((smp_prd & ADIS16400_SMPL_PRD_DIV_MASK) < 0x0A) { + st->adis.spi->max_speed_hz = ADIS16400_SPI_FAST; + spi_setup(st->adis.spi); + } + } + +err_ret: + return ret; +} + +static IIO_DEV_ATTR_SAMP_FREQ(S_IWUSR | S_IRUGO, + adis16400_read_frequency, + adis16400_write_frequency); + +static const uint8_t adis16400_addresses[] = { + [ADIS16400_SCAN_GYRO_X] = ADIS16400_XGYRO_OFF, + [ADIS16400_SCAN_GYRO_Y] = ADIS16400_YGYRO_OFF, + [ADIS16400_SCAN_GYRO_Z] = ADIS16400_ZGYRO_OFF, + [ADIS16400_SCAN_ACC_X] = ADIS16400_XACCL_OFF, + [ADIS16400_SCAN_ACC_Y] = ADIS16400_YACCL_OFF, + [ADIS16400_SCAN_ACC_Z] = ADIS16400_ZACCL_OFF, +}; + +static int adis16400_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, long info) +{ + struct adis16400_state *st = iio_priv(indio_dev); + int ret, sps; + + switch (info) { + case IIO_CHAN_INFO_CALIBBIAS: + mutex_lock(&indio_dev->mlock); + ret = adis_write_reg_16(&st->adis, + adis16400_addresses[chan->scan_index], val); + mutex_unlock(&indio_dev->mlock); + return ret; + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + /* + * Need to cache values so we can update if the frequency + * changes. + */ + mutex_lock(&indio_dev->mlock); + st->filt_int = val; + /* Work out update to current value */ + sps = st->variant->get_freq(st); + if (sps < 0) { + mutex_unlock(&indio_dev->mlock); + return sps; + } + + ret = adis16400_set_filter(indio_dev, sps, + val * 1000 + val2 / 1000); + mutex_unlock(&indio_dev->mlock); + return ret; + default: + return -EINVAL; + } +} + +static int adis16400_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, long info) +{ + struct adis16400_state *st = iio_priv(indio_dev); + int16_t val16; + int ret; + + switch (info) { + case IIO_CHAN_INFO_RAW: + return adis_single_conversion(indio_dev, chan, 0, val); + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ANGL_VEL: + *val = 0; + *val2 = st->variant->gyro_scale_micro; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_VOLTAGE: + *val = 0; + if (chan->channel == 0) { + *val = 2; + *val2 = 418000; /* 2.418 mV */ + } else { + *val = 0; + *val2 = 805800; /* 805.8 uV */ + } + return IIO_VAL_INT_PLUS_MICRO; + case IIO_ACCEL: + *val = 0; + *val2 = st->variant->accel_scale_micro; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_MAGN: + *val = 0; + *val2 = 500; /* 0.5 mgauss */ + return IIO_VAL_INT_PLUS_MICRO; + case IIO_TEMP: + *val = st->variant->temp_scale_nano / 1000000; + *val2 = (st->variant->temp_scale_nano % 1000000); + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_CALIBBIAS: + mutex_lock(&indio_dev->mlock); + ret = adis_read_reg_16(&st->adis, + adis16400_addresses[chan->scan_index], &val16); + mutex_unlock(&indio_dev->mlock); + if (ret) + return ret; + val16 = ((val16 & 0xFFF) << 4) >> 4; + *val = val16; + return IIO_VAL_INT; + case IIO_CHAN_INFO_OFFSET: + /* currently only temperature */ + *val = st->variant->temp_offset; + return IIO_VAL_INT; + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + mutex_lock(&indio_dev->mlock); + /* Need both the number of taps and the sampling frequency */ + ret = adis_read_reg_16(&st->adis, + ADIS16400_SENS_AVG, + &val16); + if (ret < 0) { + mutex_unlock(&indio_dev->mlock); + return ret; + } + ret = st->variant->get_freq(st); + if (ret >= 0) { + ret /= adis16400_3db_divisors[val16 & 0x07]; + *val = ret / 1000; + *val2 = (ret % 1000) * 1000; + } + mutex_unlock(&indio_dev->mlock); + if (ret < 0) + return ret; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +#define ADIS16400_VOLTAGE_CHAN(addr, bits, name, si) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = 0, \ + .extend_name = name, \ + .info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT | \ + IIO_CHAN_INFO_SCALE_SEPARATE_BIT, \ + .address = (addr), \ + .scan_index = (si), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = (bits), \ + .storagebits = 16, \ + .shift = 0, \ + .endianness = IIO_BE, \ + }, \ +} + +#define ADIS16400_SUPPLY_CHAN(addr, bits) \ + ADIS16400_VOLTAGE_CHAN(addr, bits, "supply", ADIS16400_SCAN_SUPPLY) + +#define ADIS16400_AUX_ADC_CHAN(addr, bits) \ + ADIS16400_VOLTAGE_CHAN(addr, bits, NULL, ADIS16400_SCAN_ADC) + +#define ADIS16400_GYRO_CHAN(mod, addr, bits) { \ + .type = IIO_ANGL_VEL, \ + .modified = 1, \ + .channel2 = IIO_MOD_ ## mod, \ + .info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT | \ + IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | \ + IIO_CHAN_INFO_SCALE_SHARED_BIT | \ + IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY_SHARED_BIT, \ + .address = addr, \ + .scan_index = ADIS16400_SCAN_GYRO_ ## mod, \ + .scan_type = { \ + .sign = 's', \ + .realbits = (bits), \ + .storagebits = 16, \ + .shift = 0, \ + .endianness = IIO_BE, \ + }, \ +} + +#define ADIS16400_ACCEL_CHAN(mod, addr, bits) { \ + .type = IIO_ACCEL, \ + .modified = 1, \ + .channel2 = IIO_MOD_ ## mod, \ + .info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT | \ + IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | \ + IIO_CHAN_INFO_SCALE_SHARED_BIT | \ + IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY_SHARED_BIT, \ + .address = (addr), \ + .scan_index = ADIS16400_SCAN_ACC_ ## mod, \ + .scan_type = { \ + .sign = 's', \ + .realbits = (bits), \ + .storagebits = 16, \ + .shift = 0, \ + .endianness = IIO_BE, \ + }, \ +} + +#define ADIS16400_MAGN_CHAN(mod, addr, bits) { \ + .type = IIO_MAGN, \ + .modified = 1, \ + .channel2 = IIO_MOD_ ## mod, \ + .info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT | \ + IIO_CHAN_INFO_SCALE_SHARED_BIT | \ + IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY_SHARED_BIT, \ + .address = (addr), \ + .scan_index = ADIS16400_SCAN_MAGN_ ## mod, \ + .scan_type = { \ + .sign = 's', \ + .realbits = (bits), \ + .storagebits = 16, \ + .shift = 0, \ + .endianness = IIO_BE, \ + }, \ +} + +#define ADIS16400_MOD_TEMP_NAME_X "x" +#define ADIS16400_MOD_TEMP_NAME_Y "y" +#define ADIS16400_MOD_TEMP_NAME_Z "z" + +#define ADIS16400_MOD_TEMP_CHAN(mod, addr, bits) { \ + .type = IIO_TEMP, \ + .indexed = 1, \ + .channel = 0, \ + .extend_name = ADIS16400_MOD_TEMP_NAME_ ## mod, \ + .info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT | \ + IIO_CHAN_INFO_OFFSET_SEPARATE_BIT | \ + IIO_CHAN_INFO_SCALE_SEPARATE_BIT | \ + IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY_SHARED_BIT, \ + .address = (addr), \ + .scan_index = ADIS16350_SCAN_TEMP_ ## mod, \ + .scan_type = { \ + .sign = 's', \ + .realbits = (bits), \ + .storagebits = 16, \ + .shift = 0, \ + .endianness = IIO_BE, \ + }, \ +} + +#define ADIS16400_TEMP_CHAN(addr, bits) { \ + .type = IIO_TEMP, \ + .indexed = 1, \ + .channel = 0, \ + .info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT | \ + IIO_CHAN_INFO_OFFSET_SEPARATE_BIT | \ + IIO_CHAN_INFO_SCALE_SEPARATE_BIT, \ + .address = (addr), \ + .scan_index = ADIS16350_SCAN_TEMP_X, \ + .scan_type = { \ + .sign = 's', \ + .realbits = (bits), \ + .storagebits = 16, \ + .shift = 0, \ + .endianness = IIO_BE, \ + }, \ +} + +#define ADIS16400_INCLI_CHAN(mod, addr, bits) { \ + .type = IIO_INCLI, \ + .modified = 1, \ + .channel2 = IIO_MOD_ ## mod, \ + .info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT | \ + IIO_CHAN_INFO_SCALE_SHARED_BIT, \ + .address = (addr), \ + .scan_index = ADIS16300_SCAN_INCLI_ ## mod, \ + .scan_type = { \ + .sign = 's', \ + .realbits = (bits), \ + .storagebits = 16, \ + .shift = 0, \ + .endianness = IIO_BE, \ + }, \ +} + +static const struct iio_chan_spec adis16400_channels[] = { + ADIS16400_SUPPLY_CHAN(ADIS16400_SUPPLY_OUT, 14), + ADIS16400_GYRO_CHAN(X, ADIS16400_XGYRO_OUT, 14), + ADIS16400_GYRO_CHAN(Y, ADIS16400_YGYRO_OUT, 14), + ADIS16400_GYRO_CHAN(Z, ADIS16400_ZGYRO_OUT, 14), + ADIS16400_ACCEL_CHAN(X, ADIS16400_XACCL_OUT, 14), + ADIS16400_ACCEL_CHAN(Y, ADIS16400_YACCL_OUT, 14), + ADIS16400_ACCEL_CHAN(Z, ADIS16400_ZACCL_OUT, 14), + ADIS16400_MAGN_CHAN(X, ADIS16400_XMAGN_OUT, 14), + ADIS16400_MAGN_CHAN(Y, ADIS16400_YMAGN_OUT, 14), + ADIS16400_MAGN_CHAN(Z, ADIS16400_ZMAGN_OUT, 14), + ADIS16400_TEMP_CHAN(ADIS16400_TEMP_OUT, 12), + ADIS16400_AUX_ADC_CHAN(ADIS16400_AUX_ADC, 12), + IIO_CHAN_SOFT_TIMESTAMP(12) +}; + +static const struct iio_chan_spec adis16448_channels[] = { + ADIS16400_GYRO_CHAN(X, ADIS16400_XGYRO_OUT, 16), + ADIS16400_GYRO_CHAN(Y, ADIS16400_YGYRO_OUT, 16), + ADIS16400_GYRO_CHAN(Z, ADIS16400_ZGYRO_OUT, 16), + ADIS16400_ACCEL_CHAN(X, ADIS16400_XACCL_OUT, 16), + ADIS16400_ACCEL_CHAN(Y, ADIS16400_YACCL_OUT, 16), + ADIS16400_ACCEL_CHAN(Z, ADIS16400_ZACCL_OUT, 16), + ADIS16400_MAGN_CHAN(X, ADIS16400_XMAGN_OUT, 16), + ADIS16400_MAGN_CHAN(Y, ADIS16400_YMAGN_OUT, 16), + ADIS16400_MAGN_CHAN(Z, ADIS16400_ZMAGN_OUT, 16), + { + .type = IIO_PRESSURE, + .info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SHARED_BIT, + .address = ADIS16448_BARO_OUT, + .scan_index = ADIS16400_SCAN_BARO, + .scan_type = IIO_ST('s', 16, 16, 0), + }, + ADIS16400_TEMP_CHAN(ADIS16448_TEMP_OUT, 12), + IIO_CHAN_SOFT_TIMESTAMP(11) +}; + +static const struct iio_chan_spec adis16350_channels[] = { + ADIS16400_SUPPLY_CHAN(ADIS16400_SUPPLY_OUT, 12), + ADIS16400_GYRO_CHAN(X, ADIS16400_XGYRO_OUT, 14), + ADIS16400_GYRO_CHAN(Y, ADIS16400_YGYRO_OUT, 14), + ADIS16400_GYRO_CHAN(Z, ADIS16400_ZGYRO_OUT, 14), + ADIS16400_ACCEL_CHAN(X, ADIS16400_XACCL_OUT, 14), + ADIS16400_ACCEL_CHAN(Y, ADIS16400_YACCL_OUT, 14), + ADIS16400_ACCEL_CHAN(Z, ADIS16400_ZACCL_OUT, 14), + ADIS16400_MAGN_CHAN(X, ADIS16400_XMAGN_OUT, 14), + ADIS16400_MAGN_CHAN(Y, ADIS16400_YMAGN_OUT, 14), + ADIS16400_MAGN_CHAN(Z, ADIS16400_ZMAGN_OUT, 14), + ADIS16400_AUX_ADC_CHAN(ADIS16300_AUX_ADC, 12), + ADIS16400_MOD_TEMP_CHAN(X, ADIS16350_XTEMP_OUT, 12), + ADIS16400_MOD_TEMP_CHAN(Y, ADIS16350_YTEMP_OUT, 12), + ADIS16400_MOD_TEMP_CHAN(Z, ADIS16350_ZTEMP_OUT, 12), + IIO_CHAN_SOFT_TIMESTAMP(11) +}; + +static const struct iio_chan_spec adis16300_channels[] = { + ADIS16400_SUPPLY_CHAN(ADIS16400_SUPPLY_OUT, 12), + ADIS16400_GYRO_CHAN(X, ADIS16400_XGYRO_OUT, 14), + ADIS16400_ACCEL_CHAN(X, ADIS16400_XACCL_OUT, 14), + ADIS16400_ACCEL_CHAN(Y, ADIS16400_YACCL_OUT, 14), + ADIS16400_ACCEL_CHAN(Z, ADIS16400_ZACCL_OUT, 14), + ADIS16400_TEMP_CHAN(ADIS16350_XTEMP_OUT, 12), + ADIS16400_AUX_ADC_CHAN(ADIS16300_AUX_ADC, 12), + ADIS16400_INCLI_CHAN(X, ADIS16300_PITCH_OUT, 13), + ADIS16400_INCLI_CHAN(Y, ADIS16300_ROLL_OUT, 13), + IIO_CHAN_SOFT_TIMESTAMP(14) +}; + +static const struct iio_chan_spec adis16334_channels[] = { + ADIS16400_GYRO_CHAN(X, ADIS16400_XGYRO_OUT, 14), + ADIS16400_GYRO_CHAN(Y, ADIS16400_YGYRO_OUT, 14), + ADIS16400_GYRO_CHAN(Z, ADIS16400_ZGYRO_OUT, 14), + ADIS16400_ACCEL_CHAN(X, ADIS16400_XACCL_OUT, 14), + ADIS16400_ACCEL_CHAN(Y, ADIS16400_YACCL_OUT, 14), + ADIS16400_ACCEL_CHAN(Z, ADIS16400_ZACCL_OUT, 14), + ADIS16400_TEMP_CHAN(ADIS16350_XTEMP_OUT, 12), + IIO_CHAN_SOFT_TIMESTAMP(8) +}; + +static struct attribute *adis16400_attributes[] = { + &iio_dev_attr_sampling_frequency.dev_attr.attr, + NULL +}; + +static const struct attribute_group adis16400_attribute_group = { + .attrs = adis16400_attributes, +}; + +static struct adis16400_chip_info adis16400_chips[] = { + [ADIS16300] = { + .channels = adis16300_channels, + .num_channels = ARRAY_SIZE(adis16300_channels), + .flags = ADIS16400_HAS_SLOW_MODE, + .gyro_scale_micro = IIO_DEGREE_TO_RAD(50000), /* 0.05 deg/s */ + .accel_scale_micro = 5884, + .temp_scale_nano = 140000000, /* 0.14 C */ + .temp_offset = 25000000 / 140000, /* 25 C = 0x00 */ + .set_freq = adis16400_set_freq, + .get_freq = adis16400_get_freq, + }, + [ADIS16334] = { + .channels = adis16334_channels, + .num_channels = ARRAY_SIZE(adis16334_channels), + .flags = ADIS16400_HAS_PROD_ID | ADIS16400_NO_BURST | + ADIS16400_HAS_SERIAL_NUMBER, + .gyro_scale_micro = IIO_DEGREE_TO_RAD(50000), /* 0.05 deg/s */ + .accel_scale_micro = IIO_G_TO_M_S_2(1000), /* 1 mg */ + .temp_scale_nano = 67850000, /* 0.06785 C */ + .temp_offset = 25000000 / 67850, /* 25 C = 0x00 */ + .set_freq = adis16334_set_freq, + .get_freq = adis16334_get_freq, + }, + [ADIS16350] = { + .channels = adis16350_channels, + .num_channels = ARRAY_SIZE(adis16350_channels), + .gyro_scale_micro = IIO_DEGREE_TO_RAD(73260), /* 0.07326 deg/s */ + .accel_scale_micro = IIO_G_TO_M_S_2(2522), /* 0.002522 g */ + .temp_scale_nano = 145300000, /* 0.1453 C */ + .temp_offset = 25000000 / 145300, /* 25 C = 0x00 */ + .flags = ADIS16400_NO_BURST | ADIS16400_HAS_SLOW_MODE, + .set_freq = adis16400_set_freq, + .get_freq = adis16400_get_freq, + }, + [ADIS16360] = { + .channels = adis16350_channels, + .num_channels = ARRAY_SIZE(adis16350_channels), + .flags = ADIS16400_HAS_PROD_ID | ADIS16400_HAS_SLOW_MODE | + ADIS16400_HAS_SERIAL_NUMBER, + .gyro_scale_micro = IIO_DEGREE_TO_RAD(50000), /* 0.05 deg/s */ + .accel_scale_micro = IIO_G_TO_M_S_2(3333), /* 3.333 mg */ + .temp_scale_nano = 136000000, /* 0.136 C */ + .temp_offset = 25000000 / 136000, /* 25 C = 0x00 */ + .set_freq = adis16400_set_freq, + .get_freq = adis16400_get_freq, + }, + [ADIS16362] = { + .channels = adis16350_channels, + .num_channels = ARRAY_SIZE(adis16350_channels), + .flags = ADIS16400_HAS_PROD_ID | ADIS16400_HAS_SLOW_MODE | + ADIS16400_HAS_SERIAL_NUMBER, + .gyro_scale_micro = IIO_DEGREE_TO_RAD(50000), /* 0.05 deg/s */ + .accel_scale_micro = IIO_G_TO_M_S_2(333), /* 0.333 mg */ + .temp_scale_nano = 136000000, /* 0.136 C */ + .temp_offset = 25000000 / 136000, /* 25 C = 0x00 */ + .set_freq = adis16400_set_freq, + .get_freq = adis16400_get_freq, + }, + [ADIS16364] = { + .channels = adis16350_channels, + .num_channels = ARRAY_SIZE(adis16350_channels), + .flags = ADIS16400_HAS_PROD_ID | ADIS16400_HAS_SLOW_MODE | + ADIS16400_HAS_SERIAL_NUMBER, + .gyro_scale_micro = IIO_DEGREE_TO_RAD(50000), /* 0.05 deg/s */ + .accel_scale_micro = IIO_G_TO_M_S_2(1000), /* 1 mg */ + .temp_scale_nano = 136000000, /* 0.136 C */ + .temp_offset = 25000000 / 136000, /* 25 C = 0x00 */ + .set_freq = adis16400_set_freq, + .get_freq = adis16400_get_freq, + }, + [ADIS16400] = { + .channels = adis16400_channels, + .num_channels = ARRAY_SIZE(adis16400_channels), + .flags = ADIS16400_HAS_PROD_ID | ADIS16400_HAS_SLOW_MODE, + .gyro_scale_micro = IIO_DEGREE_TO_RAD(50000), /* 0.05 deg/s */ + .accel_scale_micro = IIO_G_TO_M_S_2(3333), /* 3.333 mg */ + .temp_scale_nano = 140000000, /* 0.14 C */ + .temp_offset = 25000000 / 140000, /* 25 C = 0x00 */ + .set_freq = adis16400_set_freq, + .get_freq = adis16400_get_freq, + }, + [ADIS16448] = { + .channels = adis16448_channels, + .num_channels = ARRAY_SIZE(adis16448_channels), + .flags = ADIS16400_HAS_PROD_ID | + ADIS16400_HAS_SERIAL_NUMBER, + .gyro_scale_micro = IIO_DEGREE_TO_RAD(10000), /* 0.01 deg/s */ + .accel_scale_micro = IIO_G_TO_M_S_2(833), /* 1/1200 g */ + .temp_scale_nano = 73860000, /* 0.07386 C */ + .temp_offset = 31000000 / 73860, /* 31 C = 0x00 */ + .set_freq = adis16334_set_freq, + .get_freq = adis16334_get_freq, + } +}; + +static const struct iio_info adis16400_info = { + .driver_module = THIS_MODULE, + .read_raw = &adis16400_read_raw, + .write_raw = &adis16400_write_raw, + .attrs = &adis16400_attribute_group, + .update_scan_mode = adis16400_update_scan_mode, + .debugfs_reg_access = adis_debugfs_reg_access, +}; + +static const unsigned long adis16400_burst_scan_mask[] = { + ~0UL, + 0, +}; + +static const char * const adis16400_status_error_msgs[] = { + [ADIS16400_DIAG_STAT_ZACCL_FAIL] = "Z-axis accelerometer self-test failure", + [ADIS16400_DIAG_STAT_YACCL_FAIL] = "Y-axis accelerometer self-test failure", + [ADIS16400_DIAG_STAT_XACCL_FAIL] = "X-axis accelerometer self-test failure", + [ADIS16400_DIAG_STAT_XGYRO_FAIL] = "X-axis gyroscope self-test failure", + [ADIS16400_DIAG_STAT_YGYRO_FAIL] = "Y-axis gyroscope self-test failure", + [ADIS16400_DIAG_STAT_ZGYRO_FAIL] = "Z-axis gyroscope self-test failure", + [ADIS16400_DIAG_STAT_ALARM2] = "Alarm 2 active", + [ADIS16400_DIAG_STAT_ALARM1] = "Alarm 1 active", + [ADIS16400_DIAG_STAT_FLASH_CHK] = "Flash checksum error", + [ADIS16400_DIAG_STAT_SELF_TEST] = "Self test error", + [ADIS16400_DIAG_STAT_OVERFLOW] = "Sensor overrange", + [ADIS16400_DIAG_STAT_SPI_FAIL] = "SPI failure", + [ADIS16400_DIAG_STAT_FLASH_UPT] = "Flash update failed", + [ADIS16400_DIAG_STAT_POWER_HIGH] = "Power supply above 5.25V", + [ADIS16400_DIAG_STAT_POWER_LOW] = "Power supply below 4.75V", +}; + +static const struct adis_data adis16400_data = { + .msc_ctrl_reg = ADIS16400_MSC_CTRL, + .glob_cmd_reg = ADIS16400_GLOB_CMD, + .diag_stat_reg = ADIS16400_DIAG_STAT, + + .read_delay = 50, + .write_delay = 50, + + .self_test_mask = ADIS16400_MSC_CTRL_MEM_TEST, + .startup_delay = ADIS16400_STARTUP_DELAY, + + .status_error_msgs = adis16400_status_error_msgs, + .status_error_mask = BIT(ADIS16400_DIAG_STAT_ZACCL_FAIL) | + BIT(ADIS16400_DIAG_STAT_YACCL_FAIL) | + BIT(ADIS16400_DIAG_STAT_XACCL_FAIL) | + BIT(ADIS16400_DIAG_STAT_XGYRO_FAIL) | + BIT(ADIS16400_DIAG_STAT_YGYRO_FAIL) | + BIT(ADIS16400_DIAG_STAT_ZGYRO_FAIL) | + BIT(ADIS16400_DIAG_STAT_ALARM2) | + BIT(ADIS16400_DIAG_STAT_ALARM1) | + BIT(ADIS16400_DIAG_STAT_FLASH_CHK) | + BIT(ADIS16400_DIAG_STAT_SELF_TEST) | + BIT(ADIS16400_DIAG_STAT_OVERFLOW) | + BIT(ADIS16400_DIAG_STAT_SPI_FAIL) | + BIT(ADIS16400_DIAG_STAT_FLASH_UPT) | + BIT(ADIS16400_DIAG_STAT_POWER_HIGH) | + BIT(ADIS16400_DIAG_STAT_POWER_LOW), +}; + +static int adis16400_probe(struct spi_device *spi) +{ + struct adis16400_state *st; + struct iio_dev *indio_dev; + int ret; + + indio_dev = iio_device_alloc(sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + /* this is only used for removal purposes */ + spi_set_drvdata(spi, indio_dev); + + /* setup the industrialio driver allocated elements */ + st->variant = &adis16400_chips[spi_get_device_id(spi)->driver_data]; + indio_dev->dev.parent = &spi->dev; + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->channels = st->variant->channels; + indio_dev->num_channels = st->variant->num_channels; + indio_dev->info = &adis16400_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + if (!(st->variant->flags & ADIS16400_NO_BURST)) + indio_dev->available_scan_masks = adis16400_burst_scan_mask; + + ret = adis_init(&st->adis, indio_dev, spi, &adis16400_data); + if (ret) + goto error_free_dev; + + ret = adis_setup_buffer_and_trigger(&st->adis, indio_dev, + adis16400_trigger_handler); + if (ret) + goto error_free_dev; + + /* Get the device into a sane initial state */ + ret = adis16400_initial_setup(indio_dev); + if (ret) + goto error_cleanup_buffer; + ret = iio_device_register(indio_dev); + if (ret) + goto error_cleanup_buffer; + + adis16400_debugfs_init(indio_dev); + return 0; + +error_cleanup_buffer: + adis_cleanup_buffer_and_trigger(&st->adis, indio_dev); +error_free_dev: + iio_device_free(indio_dev); + return ret; +} + +static int adis16400_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct adis16400_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + adis16400_stop_device(indio_dev); + + adis_cleanup_buffer_and_trigger(&st->adis, indio_dev); + + iio_device_free(indio_dev); + + return 0; +} + +static const struct spi_device_id adis16400_id[] = { + {"adis16300", ADIS16300}, + {"adis16334", ADIS16334}, + {"adis16350", ADIS16350}, + {"adis16354", ADIS16350}, + {"adis16355", ADIS16350}, + {"adis16360", ADIS16360}, + {"adis16362", ADIS16362}, + {"adis16364", ADIS16364}, + {"adis16365", ADIS16360}, + {"adis16400", ADIS16400}, + {"adis16405", ADIS16400}, + {"adis16448", ADIS16448}, + {} +}; +MODULE_DEVICE_TABLE(spi, adis16400_id); + +static struct spi_driver adis16400_driver = { + .driver = { + .name = "adis16400", + .owner = THIS_MODULE, + }, + .id_table = adis16400_id, + .probe = adis16400_probe, + .remove = adis16400_remove, +}; +module_spi_driver(adis16400_driver); + +MODULE_AUTHOR("Manuel Stahl <manuel.stahl@iis.fraunhofer.de>"); +MODULE_DESCRIPTION("Analog Devices ADIS16400/5 IMU SPI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/imu/inv_mpu6050/Kconfig b/drivers/iio/imu/inv_mpu6050/Kconfig new file mode 100644 index 0000000..b5cfa3a --- /dev/null +++ b/drivers/iio/imu/inv_mpu6050/Kconfig @@ -0,0 +1,13 @@ +# +# inv-mpu6050 drivers for Invensense MPU devices and combos +# + +config INV_MPU6050_IIO + tristate "Invensense MPU6050 devices" + depends on I2C && SYSFS + select IIO_TRIGGERED_BUFFER + help + This driver supports the Invensense MPU6050 devices. + It is a gyroscope/accelerometer combo device. + This driver can be built as a module. The module will be called + inv-mpu6050. diff --git a/drivers/iio/imu/inv_mpu6050/Makefile b/drivers/iio/imu/inv_mpu6050/Makefile new file mode 100644 index 0000000..3a677c7 --- /dev/null +++ b/drivers/iio/imu/inv_mpu6050/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for Invensense MPU6050 device. +# + +obj-$(CONFIG_INV_MPU6050_IIO) += inv-mpu6050.o +inv-mpu6050-objs := inv_mpu_core.o inv_mpu_ring.o inv_mpu_trigger.o diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c new file mode 100644 index 0000000..37ca05b --- /dev/null +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c @@ -0,0 +1,795 @@ +/* +* Copyright (C) 2012 Invensense, Inc. +* +* This software is licensed under the terms of the GNU General Public +* License version 2, as published by the Free Software Foundation, and +* may be copied, distributed, and modified under those terms. +* +* This program is distributed in the hope that 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. +*/ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/sysfs.h> +#include <linux/jiffies.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/kfifo.h> +#include <linux/spinlock.h> +#include "inv_mpu_iio.h" + +/* + * this is the gyro scale translated from dynamic range plus/minus + * {250, 500, 1000, 2000} to rad/s + */ +static const int gyro_scale_6050[] = {133090, 266181, 532362, 1064724}; + +/* + * this is the accel scale translated from dynamic range plus/minus + * {2, 4, 8, 16} to m/s^2 + */ +static const int accel_scale[] = {598, 1196, 2392, 4785}; + +static const struct inv_mpu6050_reg_map reg_set_6050 = { + .sample_rate_div = INV_MPU6050_REG_SAMPLE_RATE_DIV, + .lpf = INV_MPU6050_REG_CONFIG, + .user_ctrl = INV_MPU6050_REG_USER_CTRL, + .fifo_en = INV_MPU6050_REG_FIFO_EN, + .gyro_config = INV_MPU6050_REG_GYRO_CONFIG, + .accl_config = INV_MPU6050_REG_ACCEL_CONFIG, + .fifo_count_h = INV_MPU6050_REG_FIFO_COUNT_H, + .fifo_r_w = INV_MPU6050_REG_FIFO_R_W, + .raw_gyro = INV_MPU6050_REG_RAW_GYRO, + .raw_accl = INV_MPU6050_REG_RAW_ACCEL, + .temperature = INV_MPU6050_REG_TEMPERATURE, + .int_enable = INV_MPU6050_REG_INT_ENABLE, + .pwr_mgmt_1 = INV_MPU6050_REG_PWR_MGMT_1, + .pwr_mgmt_2 = INV_MPU6050_REG_PWR_MGMT_2, +}; + +static const struct inv_mpu6050_chip_config chip_config_6050 = { + .fsr = INV_MPU6050_FSR_2000DPS, + .lpf = INV_MPU6050_FILTER_20HZ, + .fifo_rate = INV_MPU6050_INIT_FIFO_RATE, + .gyro_fifo_enable = false, + .accl_fifo_enable = false, + .accl_fs = INV_MPU6050_FS_02G, +}; + +static const struct inv_mpu6050_hw hw_info[INV_NUM_PARTS] = { + { + .num_reg = 117, + .name = "MPU6050", + .reg = ®_set_6050, + .config = &chip_config_6050, + }, +}; + +int inv_mpu6050_write_reg(struct inv_mpu6050_state *st, int reg, u8 d) +{ + return i2c_smbus_write_i2c_block_data(st->client, reg, 1, &d); +} + +int inv_mpu6050_switch_engine(struct inv_mpu6050_state *st, bool en, u32 mask) +{ + u8 d, mgmt_1; + int result; + + /* switch clock needs to be careful. Only when gyro is on, can + clock source be switched to gyro. Otherwise, it must be set to + internal clock */ + if (INV_MPU6050_BIT_PWR_GYRO_STBY == mask) { + result = i2c_smbus_read_i2c_block_data(st->client, + st->reg->pwr_mgmt_1, 1, &mgmt_1); + if (result != 1) + return result; + + mgmt_1 &= ~INV_MPU6050_BIT_CLK_MASK; + } + + if ((INV_MPU6050_BIT_PWR_GYRO_STBY == mask) && (!en)) { + /* turning off gyro requires switch to internal clock first. + Then turn off gyro engine */ + mgmt_1 |= INV_CLK_INTERNAL; + result = inv_mpu6050_write_reg(st, st->reg->pwr_mgmt_1, mgmt_1); + if (result) + return result; + } + + result = i2c_smbus_read_i2c_block_data(st->client, + st->reg->pwr_mgmt_2, 1, &d); + if (result != 1) + return result; + if (en) + d &= ~mask; + else + d |= mask; + result = inv_mpu6050_write_reg(st, st->reg->pwr_mgmt_2, d); + if (result) + return result; + + if (en) { + /* Wait for output stablize */ + msleep(INV_MPU6050_TEMP_UP_TIME); + if (INV_MPU6050_BIT_PWR_GYRO_STBY == mask) { + /* switch internal clock to PLL */ + mgmt_1 |= INV_CLK_PLL; + result = inv_mpu6050_write_reg(st, + st->reg->pwr_mgmt_1, mgmt_1); + if (result) + return result; + } + } + + return 0; +} + +int inv_mpu6050_set_power_itg(struct inv_mpu6050_state *st, bool power_on) +{ + int result; + + if (power_on) + result = inv_mpu6050_write_reg(st, st->reg->pwr_mgmt_1, 0); + else + result = inv_mpu6050_write_reg(st, st->reg->pwr_mgmt_1, + INV_MPU6050_BIT_SLEEP); + if (result) + return result; + + if (power_on) + msleep(INV_MPU6050_REG_UP_TIME); + + return 0; +} + +/** + * inv_mpu6050_init_config() - Initialize hardware, disable FIFO. + * + * Initial configuration: + * FSR: ± 2000DPS + * DLPF: 20Hz + * FIFO rate: 50Hz + * Clock source: Gyro PLL + */ +static int inv_mpu6050_init_config(struct iio_dev *indio_dev) +{ + int result; + u8 d; + struct inv_mpu6050_state *st = iio_priv(indio_dev); + + result = inv_mpu6050_set_power_itg(st, true); + if (result) + return result; + d = (INV_MPU6050_FSR_2000DPS << INV_MPU6050_GYRO_CONFIG_FSR_SHIFT); + result = inv_mpu6050_write_reg(st, st->reg->gyro_config, d); + if (result) + return result; + + d = INV_MPU6050_FILTER_20HZ; + result = inv_mpu6050_write_reg(st, st->reg->lpf, d); + if (result) + return result; + + d = INV_MPU6050_ONE_K_HZ / INV_MPU6050_INIT_FIFO_RATE - 1; + result = inv_mpu6050_write_reg(st, st->reg->sample_rate_div, d); + if (result) + return result; + + d = (INV_MPU6050_FS_02G << INV_MPU6050_ACCL_CONFIG_FSR_SHIFT); + result = inv_mpu6050_write_reg(st, st->reg->accl_config, d); + if (result) + return result; + + memcpy(&st->chip_config, hw_info[st->chip_type].config, + sizeof(struct inv_mpu6050_chip_config)); + result = inv_mpu6050_set_power_itg(st, false); + + return result; +} + +static int inv_mpu6050_sensor_show(struct inv_mpu6050_state *st, int reg, + int axis, int *val) +{ + int ind, result; + __be16 d; + + ind = (axis - IIO_MOD_X) * 2; + result = i2c_smbus_read_i2c_block_data(st->client, reg + ind, 2, + (u8 *)&d); + if (result != 2) + return -EINVAL; + *val = (short)be16_to_cpup(&d); + + return IIO_VAL_INT; +} + +static int inv_mpu6050_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) { + struct inv_mpu6050_state *st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + { + int ret, result; + + ret = IIO_VAL_INT; + result = 0; + mutex_lock(&indio_dev->mlock); + if (!st->chip_config.enable) { + result = inv_mpu6050_set_power_itg(st, true); + if (result) + goto error_read_raw; + } + /* when enable is on, power is already on */ + switch (chan->type) { + case IIO_ANGL_VEL: + if (!st->chip_config.gyro_fifo_enable || + !st->chip_config.enable) { + result = inv_mpu6050_switch_engine(st, true, + INV_MPU6050_BIT_PWR_GYRO_STBY); + if (result) + goto error_read_raw; + } + ret = inv_mpu6050_sensor_show(st, st->reg->raw_gyro, + chan->channel2, val); + if (!st->chip_config.gyro_fifo_enable || + !st->chip_config.enable) { + result = inv_mpu6050_switch_engine(st, false, + INV_MPU6050_BIT_PWR_GYRO_STBY); + if (result) + goto error_read_raw; + } + break; + case IIO_ACCEL: + if (!st->chip_config.accl_fifo_enable || + !st->chip_config.enable) { + result = inv_mpu6050_switch_engine(st, true, + INV_MPU6050_BIT_PWR_ACCL_STBY); + if (result) + goto error_read_raw; + } + ret = inv_mpu6050_sensor_show(st, st->reg->raw_accl, + chan->channel2, val); + if (!st->chip_config.accl_fifo_enable || + !st->chip_config.enable) { + result = inv_mpu6050_switch_engine(st, false, + INV_MPU6050_BIT_PWR_ACCL_STBY); + if (result) + goto error_read_raw; + } + break; + case IIO_TEMP: + /* wait for stablization */ + msleep(INV_MPU6050_SENSOR_UP_TIME); + inv_mpu6050_sensor_show(st, st->reg->temperature, + IIO_MOD_X, val); + break; + default: + ret = -EINVAL; + break; + } +error_read_raw: + if (!st->chip_config.enable) + result |= inv_mpu6050_set_power_itg(st, false); + mutex_unlock(&indio_dev->mlock); + if (result) + return result; + + return ret; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ANGL_VEL: + *val = 0; + *val2 = gyro_scale_6050[st->chip_config.fsr]; + + return IIO_VAL_INT_PLUS_NANO; + case IIO_ACCEL: + *val = 0; + *val2 = accel_scale[st->chip_config.accl_fs]; + + return IIO_VAL_INT_PLUS_MICRO; + case IIO_TEMP: + *val = 0; + *val2 = INV_MPU6050_TEMP_SCALE; + + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + switch (chan->type) { + case IIO_TEMP: + *val = INV_MPU6050_TEMP_OFFSET; + + return IIO_VAL_INT; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int inv_mpu6050_write_fsr(struct inv_mpu6050_state *st, int fsr) +{ + int result; + u8 d; + + if (fsr < 0 || fsr > INV_MPU6050_MAX_GYRO_FS_PARAM) + return -EINVAL; + if (fsr == st->chip_config.fsr) + return 0; + + d = (fsr << INV_MPU6050_GYRO_CONFIG_FSR_SHIFT); + result = inv_mpu6050_write_reg(st, st->reg->gyro_config, d); + if (result) + return result; + st->chip_config.fsr = fsr; + + return 0; +} + +static int inv_mpu6050_write_accel_fs(struct inv_mpu6050_state *st, int fs) +{ + int result; + u8 d; + + if (fs < 0 || fs > INV_MPU6050_MAX_ACCL_FS_PARAM) + return -EINVAL; + if (fs == st->chip_config.accl_fs) + return 0; + + d = (fs << INV_MPU6050_ACCL_CONFIG_FSR_SHIFT); + result = inv_mpu6050_write_reg(st, st->reg->accl_config, d); + if (result) + return result; + st->chip_config.accl_fs = fs; + + return 0; +} + +static int inv_mpu6050_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) { + struct inv_mpu6050_state *st = iio_priv(indio_dev); + int result; + + mutex_lock(&indio_dev->mlock); + /* we should only update scale when the chip is disabled, i.e., + not running */ + if (st->chip_config.enable) { + result = -EBUSY; + goto error_write_raw; + } + result = inv_mpu6050_set_power_itg(st, true); + if (result) + goto error_write_raw; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ANGL_VEL: + result = inv_mpu6050_write_fsr(st, val); + break; + case IIO_ACCEL: + result = inv_mpu6050_write_accel_fs(st, val); + break; + default: + result = -EINVAL; + break; + } + break; + default: + result = -EINVAL; + break; + } + +error_write_raw: + result |= inv_mpu6050_set_power_itg(st, false); + mutex_unlock(&indio_dev->mlock); + + return result; +} + +/** + * inv_mpu6050_set_lpf() - set low pass filer based on fifo rate. + * + * Based on the Nyquist principle, the sampling rate must + * exceed twice of the bandwidth of the signal, or there + * would be alising. This function basically search for the + * correct low pass parameters based on the fifo rate, e.g, + * sampling frequency. + */ +static int inv_mpu6050_set_lpf(struct inv_mpu6050_state *st, int rate) +{ + const int hz[] = {188, 98, 42, 20, 10, 5}; + const int d[] = {INV_MPU6050_FILTER_188HZ, INV_MPU6050_FILTER_98HZ, + INV_MPU6050_FILTER_42HZ, INV_MPU6050_FILTER_20HZ, + INV_MPU6050_FILTER_10HZ, INV_MPU6050_FILTER_5HZ}; + int i, h, result; + u8 data; + + h = (rate >> 1); + i = 0; + while ((h < hz[i]) && (i < ARRAY_SIZE(d) - 1)) + i++; + data = d[i]; + result = inv_mpu6050_write_reg(st, st->reg->lpf, data); + if (result) + return result; + st->chip_config.lpf = data; + + return 0; +} + +/** + * inv_mpu6050_fifo_rate_store() - Set fifo rate. + */ +static ssize_t inv_mpu6050_fifo_rate_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + s32 fifo_rate; + u8 d; + int result; + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct inv_mpu6050_state *st = iio_priv(indio_dev); + + if (kstrtoint(buf, 10, &fifo_rate)) + return -EINVAL; + if (fifo_rate < INV_MPU6050_MIN_FIFO_RATE || + fifo_rate > INV_MPU6050_MAX_FIFO_RATE) + return -EINVAL; + if (fifo_rate == st->chip_config.fifo_rate) + return count; + + mutex_lock(&indio_dev->mlock); + if (st->chip_config.enable) { + result = -EBUSY; + goto fifo_rate_fail; + } + result = inv_mpu6050_set_power_itg(st, true); + if (result) + goto fifo_rate_fail; + + d = INV_MPU6050_ONE_K_HZ / fifo_rate - 1; + result = inv_mpu6050_write_reg(st, st->reg->sample_rate_div, d); + if (result) + goto fifo_rate_fail; + st->chip_config.fifo_rate = fifo_rate; + + result = inv_mpu6050_set_lpf(st, fifo_rate); + if (result) + goto fifo_rate_fail; + +fifo_rate_fail: + result |= inv_mpu6050_set_power_itg(st, false); + mutex_unlock(&indio_dev->mlock); + if (result) + return result; + + return count; +} + +/** + * inv_fifo_rate_show() - Get the current sampling rate. + */ +static ssize_t inv_fifo_rate_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct inv_mpu6050_state *st = iio_priv(dev_to_iio_dev(dev)); + + return sprintf(buf, "%d\n", st->chip_config.fifo_rate); +} + +/** + * inv_attr_show() - calling this function will show current + * parameters. + */ +static ssize_t inv_attr_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct inv_mpu6050_state *st = iio_priv(dev_to_iio_dev(dev)); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + s8 *m; + + switch (this_attr->address) { + /* In MPU6050, the two matrix are the same because gyro and accel + are integrated in one chip */ + case ATTR_GYRO_MATRIX: + case ATTR_ACCL_MATRIX: + m = st->plat_data.orientation; + + return sprintf(buf, "%d, %d, %d; %d, %d, %d; %d, %d, %d\n", + m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8]); + default: + return -EINVAL; + } +} + +/** + * inv_mpu6050_validate_trigger() - validate_trigger callback for invensense + * MPU6050 device. + * @indio_dev: The IIO device + * @trig: The new trigger + * + * Returns: 0 if the 'trig' matches the trigger registered by the MPU6050 + * device, -EINVAL otherwise. + */ +static int inv_mpu6050_validate_trigger(struct iio_dev *indio_dev, + struct iio_trigger *trig) +{ + struct inv_mpu6050_state *st = iio_priv(indio_dev); + + if (st->trig != trig) + return -EINVAL; + + return 0; +} + +#define INV_MPU6050_CHAN(_type, _channel2, _index) \ + { \ + .type = _type, \ + .modified = 1, \ + .channel2 = _channel2, \ + .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT \ + | IIO_CHAN_INFO_RAW_SEPARATE_BIT, \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .shift = 0 , \ + .endianness = IIO_BE, \ + }, \ + } + +static const struct iio_chan_spec inv_mpu_channels[] = { + IIO_CHAN_SOFT_TIMESTAMP(INV_MPU6050_SCAN_TIMESTAMP), + /* + * Note that temperature should only be via polled reading only, + * not the final scan elements output. + */ + { + .type = IIO_TEMP, + .info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT + | IIO_CHAN_INFO_OFFSET_SEPARATE_BIT + | IIO_CHAN_INFO_SCALE_SEPARATE_BIT, + .scan_index = -1, + }, + INV_MPU6050_CHAN(IIO_ANGL_VEL, IIO_MOD_X, INV_MPU6050_SCAN_GYRO_X), + INV_MPU6050_CHAN(IIO_ANGL_VEL, IIO_MOD_Y, INV_MPU6050_SCAN_GYRO_Y), + INV_MPU6050_CHAN(IIO_ANGL_VEL, IIO_MOD_Z, INV_MPU6050_SCAN_GYRO_Z), + + INV_MPU6050_CHAN(IIO_ACCEL, IIO_MOD_X, INV_MPU6050_SCAN_ACCL_X), + INV_MPU6050_CHAN(IIO_ACCEL, IIO_MOD_Y, INV_MPU6050_SCAN_ACCL_Y), + INV_MPU6050_CHAN(IIO_ACCEL, IIO_MOD_Z, INV_MPU6050_SCAN_ACCL_Z), +}; + +/* constant IIO attribute */ +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("10 20 50 100 200 500"); +static IIO_DEV_ATTR_SAMP_FREQ(S_IRUGO | S_IWUSR, inv_fifo_rate_show, + inv_mpu6050_fifo_rate_store); +static IIO_DEVICE_ATTR(in_gyro_matrix, S_IRUGO, inv_attr_show, NULL, + ATTR_GYRO_MATRIX); +static IIO_DEVICE_ATTR(in_accel_matrix, S_IRUGO, inv_attr_show, NULL, + ATTR_ACCL_MATRIX); + +static struct attribute *inv_attributes[] = { + &iio_dev_attr_in_gyro_matrix.dev_attr.attr, + &iio_dev_attr_in_accel_matrix.dev_attr.attr, + &iio_dev_attr_sampling_frequency.dev_attr.attr, + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group inv_attribute_group = { + .attrs = inv_attributes +}; + +static const struct iio_info mpu_info = { + .driver_module = THIS_MODULE, + .read_raw = &inv_mpu6050_read_raw, + .write_raw = &inv_mpu6050_write_raw, + .attrs = &inv_attribute_group, + .validate_trigger = inv_mpu6050_validate_trigger, +}; + +/** + * inv_check_and_setup_chip() - check and setup chip. + */ +static int inv_check_and_setup_chip(struct inv_mpu6050_state *st, + const struct i2c_device_id *id) +{ + int result; + + st->chip_type = INV_MPU6050; + st->hw = &hw_info[st->chip_type]; + st->reg = hw_info[st->chip_type].reg; + + /* reset to make sure previous state are not there */ + result = inv_mpu6050_write_reg(st, st->reg->pwr_mgmt_1, + INV_MPU6050_BIT_H_RESET); + if (result) + return result; + msleep(INV_MPU6050_POWER_UP_TIME); + /* toggle power state. After reset, the sleep bit could be on + or off depending on the OTP settings. Toggling power would + make it in a definite state as well as making the hardware + state align with the software state */ + result = inv_mpu6050_set_power_itg(st, false); + if (result) + return result; + result = inv_mpu6050_set_power_itg(st, true); + if (result) + return result; + + result = inv_mpu6050_switch_engine(st, false, + INV_MPU6050_BIT_PWR_ACCL_STBY); + if (result) + return result; + result = inv_mpu6050_switch_engine(st, false, + INV_MPU6050_BIT_PWR_GYRO_STBY); + if (result) + return result; + + return 0; +} + +/** + * inv_mpu_probe() - probe function. + * @client: i2c client. + * @id: i2c device id. + * + * Returns 0 on success, a negative error code otherwise. + */ +static int inv_mpu_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct inv_mpu6050_state *st; + struct iio_dev *indio_dev; + int result; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_I2C_BLOCK | + I2C_FUNC_SMBUS_WRITE_I2C_BLOCK)) { + result = -ENOSYS; + goto out_no_free; + } + indio_dev = iio_device_alloc(sizeof(*st)); + if (indio_dev == NULL) { + result = -ENOMEM; + goto out_no_free; + } + st = iio_priv(indio_dev); + st->client = client; + st->plat_data = *(struct inv_mpu6050_platform_data + *)dev_get_platdata(&client->dev); + /* power is turned on inside check chip type*/ + result = inv_check_and_setup_chip(st, id); + if (result) + goto out_free; + + result = inv_mpu6050_init_config(indio_dev); + if (result) { + dev_err(&client->dev, + "Could not initialize device.\n"); + goto out_free; + } + + i2c_set_clientdata(client, indio_dev); + indio_dev->dev.parent = &client->dev; + indio_dev->name = id->name; + indio_dev->channels = inv_mpu_channels; + indio_dev->num_channels = ARRAY_SIZE(inv_mpu_channels); + + indio_dev->info = &mpu_info; + indio_dev->modes = INDIO_BUFFER_TRIGGERED; + + result = iio_triggered_buffer_setup(indio_dev, + inv_mpu6050_irq_handler, + inv_mpu6050_read_fifo, + NULL); + if (result) { + dev_err(&st->client->dev, "configure buffer fail %d\n", + result); + goto out_free; + } + result = inv_mpu6050_probe_trigger(indio_dev); + if (result) { + dev_err(&st->client->dev, "trigger probe fail %d\n", result); + goto out_unreg_ring; + } + + INIT_KFIFO(st->timestamps); + spin_lock_init(&st->time_stamp_lock); + result = iio_device_register(indio_dev); + if (result) { + dev_err(&st->client->dev, "IIO register fail %d\n", result); + goto out_remove_trigger; + } + + return 0; + +out_remove_trigger: + inv_mpu6050_remove_trigger(st); +out_unreg_ring: + iio_triggered_buffer_cleanup(indio_dev); +out_free: + iio_device_free(indio_dev); +out_no_free: + + return result; +} + +static int inv_mpu_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct inv_mpu6050_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + inv_mpu6050_remove_trigger(st); + iio_triggered_buffer_cleanup(indio_dev); + iio_device_free(indio_dev); + + return 0; +} +#ifdef CONFIG_PM_SLEEP + +static int inv_mpu_resume(struct device *dev) +{ + return inv_mpu6050_set_power_itg( + iio_priv(i2c_get_clientdata(to_i2c_client(dev))), true); +} + +static int inv_mpu_suspend(struct device *dev) +{ + return inv_mpu6050_set_power_itg( + iio_priv(i2c_get_clientdata(to_i2c_client(dev))), false); +} +static SIMPLE_DEV_PM_OPS(inv_mpu_pmops, inv_mpu_suspend, inv_mpu_resume); + +#define INV_MPU6050_PMOPS (&inv_mpu_pmops) +#else +#define INV_MPU6050_PMOPS NULL +#endif /* CONFIG_PM_SLEEP */ + +/* + * device id table is used to identify what device can be + * supported by this driver + */ +static const struct i2c_device_id inv_mpu_id[] = { + {"mpu6050", INV_MPU6050}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, inv_mpu_id); + +static struct i2c_driver inv_mpu_driver = { + .probe = inv_mpu_probe, + .remove = inv_mpu_remove, + .id_table = inv_mpu_id, + .driver = { + .owner = THIS_MODULE, + .name = "inv-mpu6050", + .pm = INV_MPU6050_PMOPS, + }, +}; + +module_i2c_driver(inv_mpu_driver); + +MODULE_AUTHOR("Invensense Corporation"); +MODULE_DESCRIPTION("Invensense device MPU6050 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h b/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h new file mode 100644 index 0000000..f383955 --- /dev/null +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h @@ -0,0 +1,246 @@ +/* +* Copyright (C) 2012 Invensense, Inc. +* +* This software is licensed under the terms of the GNU General Public +* License version 2, as published by the Free Software Foundation, and +* may be copied, distributed, and modified under those terms. +* +* This program is distributed in the hope that 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. +*/ +#include <linux/i2c.h> +#include <linux/kfifo.h> +#include <linux/spinlock.h> +#include <linux/iio/iio.h> +#include <linux/iio/buffer.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/kfifo_buf.h> +#include <linux/iio/trigger.h> +#include <linux/iio/triggered_buffer.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/platform_data/invensense_mpu6050.h> + +/** + * struct inv_mpu6050_reg_map - Notable registers. + * @sample_rate_div: Divider applied to gyro output rate. + * @lpf: Configures internal low pass filter. + * @user_ctrl: Enables/resets the FIFO. + * @fifo_en: Determines which data will appear in FIFO. + * @gyro_config: gyro config register. + * @accl_config: accel config register + * @fifo_count_h: Upper byte of FIFO count. + * @fifo_r_w: FIFO register. + * @raw_gyro: Address of first gyro register. + * @raw_accl: Address of first accel register. + * @temperature: temperature register + * @int_enable: Interrupt enable register. + * @pwr_mgmt_1: Controls chip's power state and clock source. + * @pwr_mgmt_2: Controls power state of individual sensors. + */ +struct inv_mpu6050_reg_map { + u8 sample_rate_div; + u8 lpf; + u8 user_ctrl; + u8 fifo_en; + u8 gyro_config; + u8 accl_config; + u8 fifo_count_h; + u8 fifo_r_w; + u8 raw_gyro; + u8 raw_accl; + u8 temperature; + u8 int_enable; + u8 pwr_mgmt_1; + u8 pwr_mgmt_2; +}; + +/*device enum */ +enum inv_devices { + INV_MPU6050, + INV_NUM_PARTS +}; + +/** + * struct inv_mpu6050_chip_config - Cached chip configuration data. + * @fsr: Full scale range. + * @lpf: Digital low pass filter frequency. + * @accl_fs: accel full scale range. + * @enable: master enable state. + * @accl_fifo_enable: enable accel data output + * @gyro_fifo_enable: enable gyro data output + * @fifo_rate: FIFO update rate. + */ +struct inv_mpu6050_chip_config { + unsigned int fsr:2; + unsigned int lpf:3; + unsigned int accl_fs:2; + unsigned int enable:1; + unsigned int accl_fifo_enable:1; + unsigned int gyro_fifo_enable:1; + u16 fifo_rate; +}; + +/** + * struct inv_mpu6050_hw - Other important hardware information. + * @num_reg: Number of registers on device. + * @name: name of the chip. + * @reg: register map of the chip. + * @config: configuration of the chip. + */ +struct inv_mpu6050_hw { + u8 num_reg; + u8 *name; + const struct inv_mpu6050_reg_map *reg; + const struct inv_mpu6050_chip_config *config; +}; + +/* + * struct inv_mpu6050_state - Driver state variables. + * @TIMESTAMP_FIFO_SIZE: fifo size for timestamp. + * @trig: IIO trigger. + * @chip_config: Cached attribute information. + * @reg: Map of important registers. + * @hw: Other hardware-specific information. + * @chip_type: chip type. + * @time_stamp_lock: spin lock to time stamp. + * @client: i2c client handle. + * @plat_data: platform data. + * @timestamps: kfifo queue to store time stamp. + */ +struct inv_mpu6050_state { +#define TIMESTAMP_FIFO_SIZE 16 + struct iio_trigger *trig; + struct inv_mpu6050_chip_config chip_config; + const struct inv_mpu6050_reg_map *reg; + const struct inv_mpu6050_hw *hw; + enum inv_devices chip_type; + spinlock_t time_stamp_lock; + struct i2c_client *client; + struct inv_mpu6050_platform_data plat_data; + DECLARE_KFIFO(timestamps, long long, TIMESTAMP_FIFO_SIZE); +}; + +/*register and associated bit definition*/ +#define INV_MPU6050_REG_SAMPLE_RATE_DIV 0x19 +#define INV_MPU6050_REG_CONFIG 0x1A +#define INV_MPU6050_REG_GYRO_CONFIG 0x1B +#define INV_MPU6050_REG_ACCEL_CONFIG 0x1C + +#define INV_MPU6050_REG_FIFO_EN 0x23 +#define INV_MPU6050_BIT_ACCEL_OUT 0x08 +#define INV_MPU6050_BITS_GYRO_OUT 0x70 + +#define INV_MPU6050_REG_INT_ENABLE 0x38 +#define INV_MPU6050_BIT_DATA_RDY_EN 0x01 +#define INV_MPU6050_BIT_DMP_INT_EN 0x02 + +#define INV_MPU6050_REG_RAW_ACCEL 0x3B +#define INV_MPU6050_REG_TEMPERATURE 0x41 +#define INV_MPU6050_REG_RAW_GYRO 0x43 + +#define INV_MPU6050_REG_USER_CTRL 0x6A +#define INV_MPU6050_BIT_FIFO_RST 0x04 +#define INV_MPU6050_BIT_DMP_RST 0x08 +#define INV_MPU6050_BIT_I2C_MST_EN 0x20 +#define INV_MPU6050_BIT_FIFO_EN 0x40 +#define INV_MPU6050_BIT_DMP_EN 0x80 + +#define INV_MPU6050_REG_PWR_MGMT_1 0x6B +#define INV_MPU6050_BIT_H_RESET 0x80 +#define INV_MPU6050_BIT_SLEEP 0x40 +#define INV_MPU6050_BIT_CLK_MASK 0x7 + +#define INV_MPU6050_REG_PWR_MGMT_2 0x6C +#define INV_MPU6050_BIT_PWR_ACCL_STBY 0x38 +#define INV_MPU6050_BIT_PWR_GYRO_STBY 0x07 + +#define INV_MPU6050_REG_FIFO_COUNT_H 0x72 +#define INV_MPU6050_REG_FIFO_R_W 0x74 + +#define INV_MPU6050_BYTES_PER_3AXIS_SENSOR 6 +#define INV_MPU6050_FIFO_COUNT_BYTE 2 +#define INV_MPU6050_FIFO_THRESHOLD 500 +#define INV_MPU6050_POWER_UP_TIME 100 +#define INV_MPU6050_TEMP_UP_TIME 100 +#define INV_MPU6050_SENSOR_UP_TIME 30 +#define INV_MPU6050_REG_UP_TIME 5 + +#define INV_MPU6050_TEMP_OFFSET 12421 +#define INV_MPU6050_TEMP_SCALE 2941 +#define INV_MPU6050_MAX_GYRO_FS_PARAM 3 +#define INV_MPU6050_MAX_ACCL_FS_PARAM 3 +#define INV_MPU6050_THREE_AXIS 3 +#define INV_MPU6050_GYRO_CONFIG_FSR_SHIFT 3 +#define INV_MPU6050_ACCL_CONFIG_FSR_SHIFT 3 + +/* 6 + 6 round up and plus 8 */ +#define INV_MPU6050_OUTPUT_DATA_SIZE 24 + +/* init parameters */ +#define INV_MPU6050_INIT_FIFO_RATE 50 +#define INV_MPU6050_TIME_STAMP_TOR 5 +#define INV_MPU6050_MAX_FIFO_RATE 1000 +#define INV_MPU6050_MIN_FIFO_RATE 4 +#define INV_MPU6050_ONE_K_HZ 1000 + +/* scan element definition */ +enum inv_mpu6050_scan { + INV_MPU6050_SCAN_ACCL_X, + INV_MPU6050_SCAN_ACCL_Y, + INV_MPU6050_SCAN_ACCL_Z, + INV_MPU6050_SCAN_GYRO_X, + INV_MPU6050_SCAN_GYRO_Y, + INV_MPU6050_SCAN_GYRO_Z, + INV_MPU6050_SCAN_TIMESTAMP, +}; + +enum inv_mpu6050_filter_e { + INV_MPU6050_FILTER_256HZ_NOLPF2 = 0, + INV_MPU6050_FILTER_188HZ, + INV_MPU6050_FILTER_98HZ, + INV_MPU6050_FILTER_42HZ, + INV_MPU6050_FILTER_20HZ, + INV_MPU6050_FILTER_10HZ, + INV_MPU6050_FILTER_5HZ, + INV_MPU6050_FILTER_2100HZ_NOLPF, + NUM_MPU6050_FILTER +}; + +/* IIO attribute address */ +enum INV_MPU6050_IIO_ATTR_ADDR { + ATTR_GYRO_MATRIX, + ATTR_ACCL_MATRIX, +}; + +enum inv_mpu6050_accl_fs_e { + INV_MPU6050_FS_02G = 0, + INV_MPU6050_FS_04G, + INV_MPU6050_FS_08G, + INV_MPU6050_FS_16G, + NUM_ACCL_FSR +}; + +enum inv_mpu6050_fsr_e { + INV_MPU6050_FSR_250DPS = 0, + INV_MPU6050_FSR_500DPS, + INV_MPU6050_FSR_1000DPS, + INV_MPU6050_FSR_2000DPS, + NUM_MPU6050_FSR +}; + +enum inv_mpu6050_clock_sel_e { + INV_CLK_INTERNAL = 0, + INV_CLK_PLL, + NUM_CLK +}; + +irqreturn_t inv_mpu6050_irq_handler(int irq, void *p); +irqreturn_t inv_mpu6050_read_fifo(int irq, void *p); +int inv_mpu6050_probe_trigger(struct iio_dev *indio_dev); +void inv_mpu6050_remove_trigger(struct inv_mpu6050_state *st); +int inv_reset_fifo(struct iio_dev *indio_dev); +int inv_mpu6050_switch_engine(struct inv_mpu6050_state *st, bool en, u32 mask); +int inv_mpu6050_write_reg(struct inv_mpu6050_state *st, int reg, u8 val); +int inv_mpu6050_set_power_itg(struct inv_mpu6050_state *st, bool power_on); diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c new file mode 100644 index 0000000..331781f --- /dev/null +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c @@ -0,0 +1,196 @@ +/* +* Copyright (C) 2012 Invensense, Inc. +* +* This software is licensed under the terms of the GNU General Public +* License version 2, as published by the Free Software Foundation, and +* may be copied, distributed, and modified under those terms. +* +* This program is distributed in the hope that 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. +*/ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/sysfs.h> +#include <linux/jiffies.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/kfifo.h> +#include <linux/poll.h> +#include "inv_mpu_iio.h" + +int inv_reset_fifo(struct iio_dev *indio_dev) +{ + int result; + u8 d; + struct inv_mpu6050_state *st = iio_priv(indio_dev); + + /* disable interrupt */ + result = inv_mpu6050_write_reg(st, st->reg->int_enable, 0); + if (result) { + dev_err(&st->client->dev, "int_enable failed %d\n", result); + return result; + } + /* disable the sensor output to FIFO */ + result = inv_mpu6050_write_reg(st, st->reg->fifo_en, 0); + if (result) + goto reset_fifo_fail; + /* disable fifo reading */ + result = inv_mpu6050_write_reg(st, st->reg->user_ctrl, 0); + if (result) + goto reset_fifo_fail; + + /* reset FIFO*/ + result = inv_mpu6050_write_reg(st, st->reg->user_ctrl, + INV_MPU6050_BIT_FIFO_RST); + if (result) + goto reset_fifo_fail; + /* enable interrupt */ + if (st->chip_config.accl_fifo_enable || + st->chip_config.gyro_fifo_enable) { + result = inv_mpu6050_write_reg(st, st->reg->int_enable, + INV_MPU6050_BIT_DATA_RDY_EN); + if (result) + return result; + } + /* enable FIFO reading and I2C master interface*/ + result = inv_mpu6050_write_reg(st, st->reg->user_ctrl, + INV_MPU6050_BIT_FIFO_EN); + if (result) + goto reset_fifo_fail; + /* enable sensor output to FIFO */ + d = 0; + if (st->chip_config.gyro_fifo_enable) + d |= INV_MPU6050_BITS_GYRO_OUT; + if (st->chip_config.accl_fifo_enable) + d |= INV_MPU6050_BIT_ACCEL_OUT; + result = inv_mpu6050_write_reg(st, st->reg->fifo_en, d); + if (result) + goto reset_fifo_fail; + + return 0; + +reset_fifo_fail: + dev_err(&st->client->dev, "reset fifo failed %d\n", result); + result = inv_mpu6050_write_reg(st, st->reg->int_enable, + INV_MPU6050_BIT_DATA_RDY_EN); + + return result; +} + +static void inv_clear_kfifo(struct inv_mpu6050_state *st) +{ + unsigned long flags; + + /* take the spin lock sem to avoid interrupt kick in */ + spin_lock_irqsave(&st->time_stamp_lock, flags); + kfifo_reset(&st->timestamps); + spin_unlock_irqrestore(&st->time_stamp_lock, flags); +} + +/** + * inv_mpu6050_irq_handler() - Cache a timestamp at each data ready interrupt. + */ +irqreturn_t inv_mpu6050_irq_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct inv_mpu6050_state *st = iio_priv(indio_dev); + s64 timestamp; + + timestamp = iio_get_time_ns(); + spin_lock(&st->time_stamp_lock); + kfifo_in(&st->timestamps, ×tamp, 1); + spin_unlock(&st->time_stamp_lock); + + return IRQ_WAKE_THREAD; +} + +/** + * inv_mpu6050_read_fifo() - Transfer data from hardware FIFO to KFIFO. + */ +irqreturn_t inv_mpu6050_read_fifo(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct inv_mpu6050_state *st = iio_priv(indio_dev); + size_t bytes_per_datum; + int result; + u8 data[INV_MPU6050_OUTPUT_DATA_SIZE]; + u16 fifo_count; + s64 timestamp; + u64 *tmp; + + mutex_lock(&indio_dev->mlock); + if (!(st->chip_config.accl_fifo_enable | + st->chip_config.gyro_fifo_enable)) + goto end_session; + bytes_per_datum = 0; + if (st->chip_config.accl_fifo_enable) + bytes_per_datum += INV_MPU6050_BYTES_PER_3AXIS_SENSOR; + + if (st->chip_config.gyro_fifo_enable) + bytes_per_datum += INV_MPU6050_BYTES_PER_3AXIS_SENSOR; + + /* + * read fifo_count register to know how many bytes inside FIFO + * right now + */ + result = i2c_smbus_read_i2c_block_data(st->client, + st->reg->fifo_count_h, + INV_MPU6050_FIFO_COUNT_BYTE, data); + if (result != INV_MPU6050_FIFO_COUNT_BYTE) + goto end_session; + fifo_count = be16_to_cpup((__be16 *)(&data[0])); + if (fifo_count < bytes_per_datum) + goto end_session; + /* fifo count can't be odd number, if it is odd, reset fifo*/ + if (fifo_count & 1) + goto flush_fifo; + if (fifo_count > INV_MPU6050_FIFO_THRESHOLD) + goto flush_fifo; + /* Timestamp mismatch. */ + if (kfifo_len(&st->timestamps) > + fifo_count / bytes_per_datum + INV_MPU6050_TIME_STAMP_TOR) + goto flush_fifo; + while (fifo_count >= bytes_per_datum) { + result = i2c_smbus_read_i2c_block_data(st->client, + st->reg->fifo_r_w, + bytes_per_datum, data); + if (result != bytes_per_datum) + goto flush_fifo; + + result = kfifo_out(&st->timestamps, ×tamp, 1); + /* when there is no timestamp, put timestamp as 0 */ + if (0 == result) + timestamp = 0; + + tmp = (u64 *)data; + tmp[DIV_ROUND_UP(bytes_per_datum, 8)] = timestamp; + result = iio_push_to_buffers(indio_dev, data); + if (result) + goto flush_fifo; + fifo_count -= bytes_per_datum; + } + +end_session: + mutex_unlock(&indio_dev->mlock); + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; + +flush_fifo: + /* Flush HW and SW FIFOs. */ + inv_reset_fifo(indio_dev); + inv_clear_kfifo(st); + mutex_unlock(&indio_dev->mlock); + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c new file mode 100644 index 0000000..e1d0869 --- /dev/null +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c @@ -0,0 +1,155 @@ +/* +* Copyright (C) 2012 Invensense, Inc. +* +* This software is licensed under the terms of the GNU General Public +* License version 2, as published by the Free Software Foundation, and +* may be copied, distributed, and modified under those terms. +* +* This program is distributed in the hope that 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. +*/ + +#include "inv_mpu_iio.h" + +static void inv_scan_query(struct iio_dev *indio_dev) +{ + struct inv_mpu6050_state *st = iio_priv(indio_dev); + + st->chip_config.gyro_fifo_enable = + test_bit(INV_MPU6050_SCAN_GYRO_X, + indio_dev->active_scan_mask) || + test_bit(INV_MPU6050_SCAN_GYRO_Y, + indio_dev->active_scan_mask) || + test_bit(INV_MPU6050_SCAN_GYRO_Z, + indio_dev->active_scan_mask); + + st->chip_config.accl_fifo_enable = + test_bit(INV_MPU6050_SCAN_ACCL_X, + indio_dev->active_scan_mask) || + test_bit(INV_MPU6050_SCAN_ACCL_Y, + indio_dev->active_scan_mask) || + test_bit(INV_MPU6050_SCAN_ACCL_Z, + indio_dev->active_scan_mask); +} + +/** + * inv_mpu6050_set_enable() - enable chip functions. + * @indio_dev: Device driver instance. + * @enable: enable/disable + */ +static int inv_mpu6050_set_enable(struct iio_dev *indio_dev, bool enable) +{ + struct inv_mpu6050_state *st = iio_priv(indio_dev); + int result; + + if (enable) { + result = inv_mpu6050_set_power_itg(st, true); + if (result) + return result; + inv_scan_query(indio_dev); + if (st->chip_config.gyro_fifo_enable) { + result = inv_mpu6050_switch_engine(st, true, + INV_MPU6050_BIT_PWR_GYRO_STBY); + if (result) + return result; + } + if (st->chip_config.accl_fifo_enable) { + result = inv_mpu6050_switch_engine(st, true, + INV_MPU6050_BIT_PWR_ACCL_STBY); + if (result) + return result; + } + result = inv_reset_fifo(indio_dev); + if (result) + return result; + } else { + result = inv_mpu6050_write_reg(st, st->reg->fifo_en, 0); + if (result) + return result; + + result = inv_mpu6050_write_reg(st, st->reg->int_enable, 0); + if (result) + return result; + + result = inv_mpu6050_write_reg(st, st->reg->user_ctrl, 0); + if (result) + return result; + + result = inv_mpu6050_switch_engine(st, false, + INV_MPU6050_BIT_PWR_GYRO_STBY); + if (result) + return result; + + result = inv_mpu6050_switch_engine(st, false, + INV_MPU6050_BIT_PWR_ACCL_STBY); + if (result) + return result; + result = inv_mpu6050_set_power_itg(st, false); + if (result) + return result; + } + st->chip_config.enable = enable; + + return 0; +} + +/** + * inv_mpu_data_rdy_trigger_set_state() - set data ready interrupt state + * @trig: Trigger instance + * @state: Desired trigger state + */ +static int inv_mpu_data_rdy_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + return inv_mpu6050_set_enable(trig->private_data, state); +} + +static const struct iio_trigger_ops inv_mpu_trigger_ops = { + .owner = THIS_MODULE, + .set_trigger_state = &inv_mpu_data_rdy_trigger_set_state, +}; + +int inv_mpu6050_probe_trigger(struct iio_dev *indio_dev) +{ + int ret; + struct inv_mpu6050_state *st = iio_priv(indio_dev); + + st->trig = iio_trigger_alloc("%s-dev%d", + indio_dev->name, + indio_dev->id); + if (st->trig == NULL) { + ret = -ENOMEM; + goto error_ret; + } + ret = request_irq(st->client->irq, &iio_trigger_generic_data_rdy_poll, + IRQF_TRIGGER_RISING, + "inv_mpu", + st->trig); + if (ret) + goto error_free_trig; + st->trig->dev.parent = &st->client->dev; + st->trig->private_data = indio_dev; + st->trig->ops = &inv_mpu_trigger_ops; + ret = iio_trigger_register(st->trig); + if (ret) + goto error_free_irq; + indio_dev->trig = st->trig; + + return 0; + +error_free_irq: + free_irq(st->client->irq, st->trig); +error_free_trig: + iio_trigger_free(st->trig); +error_ret: + return ret; +} + +void inv_mpu6050_remove_trigger(struct inv_mpu6050_state *st) +{ + iio_trigger_unregister(st->trig); + free_irq(st->client->irq, st->trig); + iio_trigger_free(st->trig); +} diff --git a/drivers/iio/industrialio-trigger.c b/drivers/iio/industrialio-trigger.c index 4fe0ead..4d6c7d8 100644 --- a/drivers/iio/industrialio-trigger.c +++ b/drivers/iio/industrialio-trigger.c @@ -160,7 +160,7 @@ void iio_trigger_notify_done(struct iio_trigger *trig) trig->use_count--; if (trig->use_count == 0 && trig->ops && trig->ops->try_reenable) if (trig->ops->try_reenable(trig)) - /* Missed and interrupt so launch new poll now */ + /* Missed an interrupt so launch new poll now */ iio_trigger_poll(trig, 0); } EXPORT_SYMBOL(iio_trigger_notify_done); @@ -193,7 +193,7 @@ static void iio_trigger_put_irq(struct iio_trigger *trig, int irq) * This is not currently handled. Alternative of not enabling trigger unless * the relevant function is in there may be the best option. */ -/* Worth protecting against double additions?*/ +/* Worth protecting against double additions? */ static int iio_trigger_attach_poll_func(struct iio_trigger *trig, struct iio_poll_func *pf) { @@ -201,7 +201,7 @@ static int iio_trigger_attach_poll_func(struct iio_trigger *trig, bool notinuse = bitmap_empty(trig->pool, CONFIG_IIO_CONSUMERS_PER_TRIGGER); - /* Prevent the module being removed whilst attached to a trigger */ + /* Prevent the module from being removed whilst attached to a trigger */ __module_get(pf->indio_dev->info->driver_module); pf->irq = iio_trigger_get_irq(trig); ret = request_threaded_irq(pf->irq, pf->h, pf->thread, @@ -288,7 +288,7 @@ void iio_dealloc_pollfunc(struct iio_poll_func *pf) EXPORT_SYMBOL_GPL(iio_dealloc_pollfunc); /** - * iio_trigger_read_current() - trigger consumer sysfs query which trigger + * iio_trigger_read_current() - trigger consumer sysfs query current trigger * * For trigger consumers the current_trigger interface allows the trigger * used by the device to be queried. @@ -305,7 +305,7 @@ static ssize_t iio_trigger_read_current(struct device *dev, } /** - * iio_trigger_write_current() trigger consumer sysfs set current trigger + * iio_trigger_write_current() - trigger consumer sysfs set current trigger * * For trigger consumers the current_trigger interface allows the trigger * used for this device to be specified at run time based on the triggers @@ -476,7 +476,7 @@ void iio_device_register_trigger_consumer(struct iio_dev *indio_dev) void iio_device_unregister_trigger_consumer(struct iio_dev *indio_dev) { - /* Clean up and associated but not attached triggers references */ + /* Clean up an associated but not attached trigger reference */ if (indio_dev->trig) iio_trigger_put(indio_dev->trig); } diff --git a/drivers/iio/inkern.c b/drivers/iio/inkern.c index d55e98f..b289915b 100644 --- a/drivers/iio/inkern.c +++ b/drivers/iio/inkern.c @@ -54,39 +54,25 @@ error_ret: EXPORT_SYMBOL_GPL(iio_map_array_register); -/* Assumes the exact same array (e.g. memory locations) - * used at unregistration as used at registration rather than - * more complex checking of contents. +/* + * Remove all map entries associated with the given iio device */ -int iio_map_array_unregister(struct iio_dev *indio_dev, - struct iio_map *maps) +int iio_map_array_unregister(struct iio_dev *indio_dev) { - int i = 0, ret = 0; - bool found_it; + int ret = -ENODEV; struct iio_map_internal *mapi; - - if (maps == NULL) - return 0; + struct list_head *pos, *tmp; mutex_lock(&iio_map_list_lock); - while (maps[i].consumer_dev_name != NULL) { - found_it = false; - list_for_each_entry(mapi, &iio_map_list, l) - if (&maps[i] == mapi->map) { - list_del(&mapi->l); - kfree(mapi); - found_it = true; - break; - } - if (!found_it) { - ret = -ENODEV; - goto error_ret; + list_for_each_safe(pos, tmp, &iio_map_list) { + mapi = list_entry(pos, struct iio_map_internal, l); + if (indio_dev == mapi->indio_dev) { + list_del(&mapi->l); + kfree(mapi); + ret = 0; } - i++; } -error_ret: mutex_unlock(&iio_map_list_lock); - return ret; } EXPORT_SYMBOL_GPL(iio_map_array_unregister); @@ -107,7 +93,8 @@ static const struct iio_chan_spec } -struct iio_channel *iio_channel_get(const char *name, const char *channel_name) +static struct iio_channel *iio_channel_get_sys(const char *name, + const char *channel_name) { struct iio_map_internal *c_i = NULL, *c = NULL; struct iio_channel *channel; @@ -158,6 +145,14 @@ error_no_mem: iio_device_put(c->indio_dev); return ERR_PTR(err); } + +struct iio_channel *iio_channel_get(struct device *dev, + const char *channel_name) +{ + const char *name = dev ? dev_name(dev) : NULL; + + return iio_channel_get_sys(name, channel_name); +} EXPORT_SYMBOL_GPL(iio_channel_get); void iio_channel_release(struct iio_channel *channel) @@ -167,16 +162,18 @@ void iio_channel_release(struct iio_channel *channel) } EXPORT_SYMBOL_GPL(iio_channel_release); -struct iio_channel *iio_channel_get_all(const char *name) +struct iio_channel *iio_channel_get_all(struct device *dev) { + const char *name; struct iio_channel *chans; struct iio_map_internal *c = NULL; int nummaps = 0; int mapind = 0; int i, ret; - if (name == NULL) + if (dev == NULL) return ERR_PTR(-EINVAL); + name = dev_name(dev); mutex_lock(&iio_map_list_lock); /* first count the matching maps */ diff --git a/drivers/iio/kfifo_buf.c b/drivers/iio/kfifo_buf.c index 5bc5c86..a923c78 100644 --- a/drivers/iio/kfifo_buf.c +++ b/drivers/iio/kfifo_buf.c @@ -22,7 +22,6 @@ static inline int __iio_allocate_kfifo(struct iio_kfifo *buf, if ((length == 0) || (bytes_per_datum == 0)) return -EINVAL; - __iio_update_buffer(&buf->buffer, bytes_per_datum, length); return __kfifo_alloc((struct __kfifo *)&buf->kf, length, bytes_per_datum, GFP_KERNEL); } diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig index dbf80ab..5ef1a39 100644 --- a/drivers/iio/light/Kconfig +++ b/drivers/iio/light/Kconfig @@ -32,6 +32,16 @@ config SENSORS_LM3533 changes. The ALS-control output values can be set per zone for the three current output channels. +config SENSORS_TSL2563 + tristate "TAOS TSL2560, TSL2561, TSL2562 and TSL2563 ambient light sensors" + depends on I2C + help + If you say yes here you get support for the Taos TSL2560, + TSL2561, TSL2562 and TSL2563 ambient light sensors. + + This driver can also be built as a module. If so, the module + will be called tsl2563. + config VCNL4000 tristate "VCNL4000 combined ALS and proximity sensor" depends on I2C diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile index 21a8f0d..040d9c7 100644 --- a/drivers/iio/light/Makefile +++ b/drivers/iio/light/Makefile @@ -4,5 +4,6 @@ obj-$(CONFIG_ADJD_S311) += adjd_s311.o obj-$(CONFIG_SENSORS_LM3533) += lm3533-als.o +obj-$(CONFIG_SENSORS_TSL2563) += tsl2563.o obj-$(CONFIG_VCNL4000) += vcnl4000.o obj-$(CONFIG_HID_SENSOR_ALS) += hid-sensor-als.o diff --git a/drivers/iio/light/hid-sensor-als.c b/drivers/iio/light/hid-sensor-als.c index e2d042f..3d7e8c9 100644 --- a/drivers/iio/light/hid-sensor-als.c +++ b/drivers/iio/light/hid-sensor-als.c @@ -28,7 +28,6 @@ #include <linux/iio/buffer.h> #include <linux/iio/trigger_consumer.h> #include <linux/iio/triggered_buffer.h> -#include "../common/hid-sensors/hid-sensor-attributes.h" #include "../common/hid-sensors/hid-sensor-trigger.h" /*Format: HID-SENSOR-usage_id_in_hex*/ @@ -39,7 +38,7 @@ struct als_state { struct hid_sensor_hub_callbacks callbacks; - struct hid_sensor_iio_common common_attributes; + struct hid_sensor_common common_attributes; struct hid_sensor_hub_attribute_info als_illum; u32 illum; }; diff --git a/drivers/iio/light/tsl2563.c b/drivers/iio/light/tsl2563.c new file mode 100644 index 0000000..fd8be69 --- /dev/null +++ b/drivers/iio/light/tsl2563.c @@ -0,0 +1,887 @@ +/* + * drivers/iio/light/tsl2563.c + * + * Copyright (C) 2008 Nokia Corporation + * + * Written by Timo O. Karjalainen <timo.o.karjalainen@nokia.com> + * Contact: Amit Kucheria <amit.kucheria@verdurent.com> + * + * Converted to IIO driver + * Amit Kucheria <amit.kucheria@verdurent.com> + * + * 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. + * + * This program is distributed in the hope that 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/module.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/sched.h> +#include <linux/mutex.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/err.h> +#include <linux/slab.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/events.h> +#include <linux/platform_data/tsl2563.h> + +/* Use this many bits for fraction part. */ +#define ADC_FRAC_BITS 14 + +/* Given number of 1/10000's in ADC_FRAC_BITS precision. */ +#define FRAC10K(f) (((f) * (1L << (ADC_FRAC_BITS))) / (10000)) + +/* Bits used for fraction in calibration coefficients.*/ +#define CALIB_FRAC_BITS 10 +/* 0.5 in CALIB_FRAC_BITS precision */ +#define CALIB_FRAC_HALF (1 << (CALIB_FRAC_BITS - 1)) +/* Make a fraction from a number n that was multiplied with b. */ +#define CALIB_FRAC(n, b) (((n) << CALIB_FRAC_BITS) / (b)) +/* Decimal 10^(digits in sysfs presentation) */ +#define CALIB_BASE_SYSFS 1000 + +#define TSL2563_CMD 0x80 +#define TSL2563_CLEARINT 0x40 + +#define TSL2563_REG_CTRL 0x00 +#define TSL2563_REG_TIMING 0x01 +#define TSL2563_REG_LOWLOW 0x02 /* data0 low threshold, 2 bytes */ +#define TSL2563_REG_LOWHIGH 0x03 +#define TSL2563_REG_HIGHLOW 0x04 /* data0 high threshold, 2 bytes */ +#define TSL2563_REG_HIGHHIGH 0x05 +#define TSL2563_REG_INT 0x06 +#define TSL2563_REG_ID 0x0a +#define TSL2563_REG_DATA0LOW 0x0c /* broadband sensor value, 2 bytes */ +#define TSL2563_REG_DATA0HIGH 0x0d +#define TSL2563_REG_DATA1LOW 0x0e /* infrared sensor value, 2 bytes */ +#define TSL2563_REG_DATA1HIGH 0x0f + +#define TSL2563_CMD_POWER_ON 0x03 +#define TSL2563_CMD_POWER_OFF 0x00 +#define TSL2563_CTRL_POWER_MASK 0x03 + +#define TSL2563_TIMING_13MS 0x00 +#define TSL2563_TIMING_100MS 0x01 +#define TSL2563_TIMING_400MS 0x02 +#define TSL2563_TIMING_MASK 0x03 +#define TSL2563_TIMING_GAIN16 0x10 +#define TSL2563_TIMING_GAIN1 0x00 + +#define TSL2563_INT_DISBLED 0x00 +#define TSL2563_INT_LEVEL 0x10 +#define TSL2563_INT_PERSIST(n) ((n) & 0x0F) + +struct tsl2563_gainlevel_coeff { + u8 gaintime; + u16 min; + u16 max; +}; + +static const struct tsl2563_gainlevel_coeff tsl2563_gainlevel_table[] = { + { + .gaintime = TSL2563_TIMING_400MS | TSL2563_TIMING_GAIN16, + .min = 0, + .max = 65534, + }, { + .gaintime = TSL2563_TIMING_400MS | TSL2563_TIMING_GAIN1, + .min = 2048, + .max = 65534, + }, { + .gaintime = TSL2563_TIMING_100MS | TSL2563_TIMING_GAIN1, + .min = 4095, + .max = 37177, + }, { + .gaintime = TSL2563_TIMING_13MS | TSL2563_TIMING_GAIN1, + .min = 3000, + .max = 65535, + }, +}; + +struct tsl2563_chip { + struct mutex lock; + struct i2c_client *client; + struct delayed_work poweroff_work; + + /* Remember state for suspend and resume functions */ + bool suspended; + + struct tsl2563_gainlevel_coeff const *gainlevel; + + u16 low_thres; + u16 high_thres; + u8 intr; + bool int_enabled; + + /* Calibration coefficients */ + u32 calib0; + u32 calib1; + int cover_comp_gain; + + /* Cache current values, to be returned while suspended */ + u32 data0; + u32 data1; +}; + +static int tsl2563_set_power(struct tsl2563_chip *chip, int on) +{ + struct i2c_client *client = chip->client; + u8 cmd; + + cmd = on ? TSL2563_CMD_POWER_ON : TSL2563_CMD_POWER_OFF; + return i2c_smbus_write_byte_data(client, + TSL2563_CMD | TSL2563_REG_CTRL, cmd); +} + +/* + * Return value is 0 for off, 1 for on, or a negative error + * code if reading failed. + */ +static int tsl2563_get_power(struct tsl2563_chip *chip) +{ + struct i2c_client *client = chip->client; + int ret; + + ret = i2c_smbus_read_byte_data(client, TSL2563_CMD | TSL2563_REG_CTRL); + if (ret < 0) + return ret; + + return (ret & TSL2563_CTRL_POWER_MASK) == TSL2563_CMD_POWER_ON; +} + +static int tsl2563_configure(struct tsl2563_chip *chip) +{ + int ret; + + ret = i2c_smbus_write_byte_data(chip->client, + TSL2563_CMD | TSL2563_REG_TIMING, + chip->gainlevel->gaintime); + if (ret) + goto error_ret; + ret = i2c_smbus_write_byte_data(chip->client, + TSL2563_CMD | TSL2563_REG_HIGHLOW, + chip->high_thres & 0xFF); + if (ret) + goto error_ret; + ret = i2c_smbus_write_byte_data(chip->client, + TSL2563_CMD | TSL2563_REG_HIGHHIGH, + (chip->high_thres >> 8) & 0xFF); + if (ret) + goto error_ret; + ret = i2c_smbus_write_byte_data(chip->client, + TSL2563_CMD | TSL2563_REG_LOWLOW, + chip->low_thres & 0xFF); + if (ret) + goto error_ret; + ret = i2c_smbus_write_byte_data(chip->client, + TSL2563_CMD | TSL2563_REG_LOWHIGH, + (chip->low_thres >> 8) & 0xFF); +/* + * Interrupt register is automatically written anyway if it is relevant + * so is not here. + */ +error_ret: + return ret; +} + +static void tsl2563_poweroff_work(struct work_struct *work) +{ + struct tsl2563_chip *chip = + container_of(work, struct tsl2563_chip, poweroff_work.work); + tsl2563_set_power(chip, 0); +} + +static int tsl2563_detect(struct tsl2563_chip *chip) +{ + int ret; + + ret = tsl2563_set_power(chip, 1); + if (ret) + return ret; + + ret = tsl2563_get_power(chip); + if (ret < 0) + return ret; + + return ret ? 0 : -ENODEV; +} + +static int tsl2563_read_id(struct tsl2563_chip *chip, u8 *id) +{ + struct i2c_client *client = chip->client; + int ret; + + ret = i2c_smbus_read_byte_data(client, TSL2563_CMD | TSL2563_REG_ID); + if (ret < 0) + return ret; + + *id = ret; + + return 0; +} + +/* + * "Normalized" ADC value is one obtained with 400ms of integration time and + * 16x gain. This function returns the number of bits of shift needed to + * convert between normalized values and HW values obtained using given + * timing and gain settings. + */ +static int adc_shiftbits(u8 timing) +{ + int shift = 0; + + switch (timing & TSL2563_TIMING_MASK) { + case TSL2563_TIMING_13MS: + shift += 5; + break; + case TSL2563_TIMING_100MS: + shift += 2; + break; + case TSL2563_TIMING_400MS: + /* no-op */ + break; + } + + if (!(timing & TSL2563_TIMING_GAIN16)) + shift += 4; + + return shift; +} + +/* Convert a HW ADC value to normalized scale. */ +static u32 normalize_adc(u16 adc, u8 timing) +{ + return adc << adc_shiftbits(timing); +} + +static void tsl2563_wait_adc(struct tsl2563_chip *chip) +{ + unsigned int delay; + + switch (chip->gainlevel->gaintime & TSL2563_TIMING_MASK) { + case TSL2563_TIMING_13MS: + delay = 14; + break; + case TSL2563_TIMING_100MS: + delay = 101; + break; + default: + delay = 402; + } + /* + * TODO: Make sure that we wait at least required delay but why we + * have to extend it one tick more? + */ + schedule_timeout_interruptible(msecs_to_jiffies(delay) + 2); +} + +static int tsl2563_adjust_gainlevel(struct tsl2563_chip *chip, u16 adc) +{ + struct i2c_client *client = chip->client; + + if (adc > chip->gainlevel->max || adc < chip->gainlevel->min) { + + (adc > chip->gainlevel->max) ? + chip->gainlevel++ : chip->gainlevel--; + + i2c_smbus_write_byte_data(client, + TSL2563_CMD | TSL2563_REG_TIMING, + chip->gainlevel->gaintime); + + tsl2563_wait_adc(chip); + tsl2563_wait_adc(chip); + + return 1; + } else + return 0; +} + +static int tsl2563_get_adc(struct tsl2563_chip *chip) +{ + struct i2c_client *client = chip->client; + u16 adc0, adc1; + int retry = 1; + int ret = 0; + + if (chip->suspended) + goto out; + + if (!chip->int_enabled) { + cancel_delayed_work(&chip->poweroff_work); + + if (!tsl2563_get_power(chip)) { + ret = tsl2563_set_power(chip, 1); + if (ret) + goto out; + ret = tsl2563_configure(chip); + if (ret) + goto out; + tsl2563_wait_adc(chip); + } + } + + while (retry) { + ret = i2c_smbus_read_word_data(client, + TSL2563_CMD | TSL2563_REG_DATA0LOW); + if (ret < 0) + goto out; + adc0 = ret; + + ret = i2c_smbus_read_word_data(client, + TSL2563_CMD | TSL2563_REG_DATA1LOW); + if (ret < 0) + goto out; + adc1 = ret; + + retry = tsl2563_adjust_gainlevel(chip, adc0); + } + + chip->data0 = normalize_adc(adc0, chip->gainlevel->gaintime); + chip->data1 = normalize_adc(adc1, chip->gainlevel->gaintime); + + if (!chip->int_enabled) + schedule_delayed_work(&chip->poweroff_work, 5 * HZ); + + ret = 0; +out: + return ret; +} + +static inline int calib_to_sysfs(u32 calib) +{ + return (int) (((calib * CALIB_BASE_SYSFS) + + CALIB_FRAC_HALF) >> CALIB_FRAC_BITS); +} + +static inline u32 calib_from_sysfs(int value) +{ + return (((u32) value) << CALIB_FRAC_BITS) / CALIB_BASE_SYSFS; +} + +/* + * Conversions between lux and ADC values. + * + * The basic formula is lux = c0 * adc0 - c1 * adc1, where c0 and c1 are + * appropriate constants. Different constants are needed for different + * kinds of light, determined by the ratio adc1/adc0 (basically the ratio + * of the intensities in infrared and visible wavelengths). lux_table below + * lists the upper threshold of the adc1/adc0 ratio and the corresponding + * constants. + */ + +struct tsl2563_lux_coeff { + unsigned long ch_ratio; + unsigned long ch0_coeff; + unsigned long ch1_coeff; +}; + +static const struct tsl2563_lux_coeff lux_table[] = { + { + .ch_ratio = FRAC10K(1300), + .ch0_coeff = FRAC10K(315), + .ch1_coeff = FRAC10K(262), + }, { + .ch_ratio = FRAC10K(2600), + .ch0_coeff = FRAC10K(337), + .ch1_coeff = FRAC10K(430), + }, { + .ch_ratio = FRAC10K(3900), + .ch0_coeff = FRAC10K(363), + .ch1_coeff = FRAC10K(529), + }, { + .ch_ratio = FRAC10K(5200), + .ch0_coeff = FRAC10K(392), + .ch1_coeff = FRAC10K(605), + }, { + .ch_ratio = FRAC10K(6500), + .ch0_coeff = FRAC10K(229), + .ch1_coeff = FRAC10K(291), + }, { + .ch_ratio = FRAC10K(8000), + .ch0_coeff = FRAC10K(157), + .ch1_coeff = FRAC10K(180), + }, { + .ch_ratio = FRAC10K(13000), + .ch0_coeff = FRAC10K(34), + .ch1_coeff = FRAC10K(26), + }, { + .ch_ratio = ULONG_MAX, + .ch0_coeff = 0, + .ch1_coeff = 0, + }, +}; + +/* Convert normalized, scaled ADC values to lux. */ +static unsigned int adc_to_lux(u32 adc0, u32 adc1) +{ + const struct tsl2563_lux_coeff *lp = lux_table; + unsigned long ratio, lux, ch0 = adc0, ch1 = adc1; + + ratio = ch0 ? ((ch1 << ADC_FRAC_BITS) / ch0) : ULONG_MAX; + + while (lp->ch_ratio < ratio) + lp++; + + lux = ch0 * lp->ch0_coeff - ch1 * lp->ch1_coeff; + + return (unsigned int) (lux >> ADC_FRAC_BITS); +} + +/* Apply calibration coefficient to ADC count. */ +static u32 calib_adc(u32 adc, u32 calib) +{ + unsigned long scaled = adc; + + scaled *= calib; + scaled >>= CALIB_FRAC_BITS; + + return (u32) scaled; +} + +static int tsl2563_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct tsl2563_chip *chip = iio_priv(indio_dev); + + if (chan->channel == IIO_MOD_LIGHT_BOTH) + chip->calib0 = calib_from_sysfs(val); + else + chip->calib1 = calib_from_sysfs(val); + + return 0; +} + +static int tsl2563_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + int ret = -EINVAL; + u32 calib0, calib1; + struct tsl2563_chip *chip = iio_priv(indio_dev); + + mutex_lock(&chip->lock); + switch (m) { + case IIO_CHAN_INFO_RAW: + case IIO_CHAN_INFO_PROCESSED: + switch (chan->type) { + case IIO_LIGHT: + ret = tsl2563_get_adc(chip); + if (ret) + goto error_ret; + calib0 = calib_adc(chip->data0, chip->calib0) * + chip->cover_comp_gain; + calib1 = calib_adc(chip->data1, chip->calib1) * + chip->cover_comp_gain; + *val = adc_to_lux(calib0, calib1); + ret = IIO_VAL_INT; + break; + case IIO_INTENSITY: + ret = tsl2563_get_adc(chip); + if (ret) + goto error_ret; + if (chan->channel == 0) + *val = chip->data0; + else + *val = chip->data1; + ret = IIO_VAL_INT; + break; + default: + break; + } + break; + + case IIO_CHAN_INFO_CALIBSCALE: + if (chan->channel == 0) + *val = calib_to_sysfs(chip->calib0); + else + *val = calib_to_sysfs(chip->calib1); + ret = IIO_VAL_INT; + break; + default: + ret = -EINVAL; + goto error_ret; + } + +error_ret: + mutex_unlock(&chip->lock); + return ret; +} + +static const struct iio_chan_spec tsl2563_channels[] = { + { + .type = IIO_LIGHT, + .indexed = 1, + .info_mask = IIO_CHAN_INFO_PROCESSED_SEPARATE_BIT, + .channel = 0, + }, { + .type = IIO_INTENSITY, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_BOTH, + .info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT | + IIO_CHAN_INFO_CALIBSCALE_SEPARATE_BIT, + .event_mask = (IIO_EV_BIT(IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING) | + IIO_EV_BIT(IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING)), + }, { + .type = IIO_INTENSITY, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_IR, + .info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT | + IIO_CHAN_INFO_CALIBSCALE_SEPARATE_BIT, + } +}; + +static int tsl2563_read_thresh(struct iio_dev *indio_dev, + u64 event_code, + int *val) +{ + struct tsl2563_chip *chip = iio_priv(indio_dev); + + switch (IIO_EVENT_CODE_EXTRACT_DIR(event_code)) { + case IIO_EV_DIR_RISING: + *val = chip->high_thres; + break; + case IIO_EV_DIR_FALLING: + *val = chip->low_thres; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int tsl2563_write_thresh(struct iio_dev *indio_dev, + u64 event_code, + int val) +{ + struct tsl2563_chip *chip = iio_priv(indio_dev); + int ret; + u8 address; + + if (IIO_EVENT_CODE_EXTRACT_DIR(event_code) == IIO_EV_DIR_RISING) + address = TSL2563_REG_HIGHLOW; + else + address = TSL2563_REG_LOWLOW; + mutex_lock(&chip->lock); + ret = i2c_smbus_write_byte_data(chip->client, TSL2563_CMD | address, + val & 0xFF); + if (ret) + goto error_ret; + ret = i2c_smbus_write_byte_data(chip->client, + TSL2563_CMD | (address + 1), + (val >> 8) & 0xFF); + if (IIO_EVENT_CODE_EXTRACT_DIR(event_code) == IIO_EV_DIR_RISING) + chip->high_thres = val; + else + chip->low_thres = val; + +error_ret: + mutex_unlock(&chip->lock); + + return ret; +} + +static irqreturn_t tsl2563_event_handler(int irq, void *private) +{ + struct iio_dev *dev_info = private; + struct tsl2563_chip *chip = iio_priv(dev_info); + + iio_push_event(dev_info, + IIO_UNMOD_EVENT_CODE(IIO_LIGHT, + 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + iio_get_time_ns()); + + /* clear the interrupt and push the event */ + i2c_smbus_write_byte(chip->client, TSL2563_CMD | TSL2563_CLEARINT); + return IRQ_HANDLED; +} + +static int tsl2563_write_interrupt_config(struct iio_dev *indio_dev, + u64 event_code, + int state) +{ + struct tsl2563_chip *chip = iio_priv(indio_dev); + int ret = 0; + + mutex_lock(&chip->lock); + if (state && !(chip->intr & 0x30)) { + chip->intr &= ~0x30; + chip->intr |= 0x10; + /* ensure the chip is actually on */ + cancel_delayed_work(&chip->poweroff_work); + if (!tsl2563_get_power(chip)) { + ret = tsl2563_set_power(chip, 1); + if (ret) + goto out; + ret = tsl2563_configure(chip); + if (ret) + goto out; + } + ret = i2c_smbus_write_byte_data(chip->client, + TSL2563_CMD | TSL2563_REG_INT, + chip->intr); + chip->int_enabled = true; + } + + if (!state && (chip->intr & 0x30)) { + chip->intr &= ~0x30; + ret = i2c_smbus_write_byte_data(chip->client, + TSL2563_CMD | TSL2563_REG_INT, + chip->intr); + chip->int_enabled = false; + /* now the interrupt is not enabled, we can go to sleep */ + schedule_delayed_work(&chip->poweroff_work, 5 * HZ); + } +out: + mutex_unlock(&chip->lock); + + return ret; +} + +static int tsl2563_read_interrupt_config(struct iio_dev *indio_dev, + u64 event_code) +{ + struct tsl2563_chip *chip = iio_priv(indio_dev); + int ret; + + mutex_lock(&chip->lock); + ret = i2c_smbus_read_byte_data(chip->client, + TSL2563_CMD | TSL2563_REG_INT); + mutex_unlock(&chip->lock); + if (ret < 0) + return ret; + + return !!(ret & 0x30); +} + +static const struct iio_info tsl2563_info_no_irq = { + .driver_module = THIS_MODULE, + .read_raw = &tsl2563_read_raw, + .write_raw = &tsl2563_write_raw, +}; + +static const struct iio_info tsl2563_info = { + .driver_module = THIS_MODULE, + .read_raw = &tsl2563_read_raw, + .write_raw = &tsl2563_write_raw, + .read_event_value = &tsl2563_read_thresh, + .write_event_value = &tsl2563_write_thresh, + .read_event_config = &tsl2563_read_interrupt_config, + .write_event_config = &tsl2563_write_interrupt_config, +}; + +static int tsl2563_probe(struct i2c_client *client, + const struct i2c_device_id *device_id) +{ + struct iio_dev *indio_dev; + struct tsl2563_chip *chip; + struct tsl2563_platform_data *pdata = client->dev.platform_data; + int err = 0; + u8 id = 0; + + indio_dev = iio_device_alloc(sizeof(*chip)); + if (!indio_dev) + return -ENOMEM; + + chip = iio_priv(indio_dev); + + i2c_set_clientdata(client, chip); + chip->client = client; + + err = tsl2563_detect(chip); + if (err) { + dev_err(&client->dev, "detect error %d\n", -err); + goto fail1; + } + + err = tsl2563_read_id(chip, &id); + if (err) { + dev_err(&client->dev, "read id error %d\n", -err); + goto fail1; + } + + mutex_init(&chip->lock); + + /* Default values used until userspace says otherwise */ + chip->low_thres = 0x0; + chip->high_thres = 0xffff; + chip->gainlevel = tsl2563_gainlevel_table; + chip->intr = TSL2563_INT_PERSIST(4); + chip->calib0 = calib_from_sysfs(CALIB_BASE_SYSFS); + chip->calib1 = calib_from_sysfs(CALIB_BASE_SYSFS); + + if (pdata) + chip->cover_comp_gain = pdata->cover_comp_gain; + else + chip->cover_comp_gain = 1; + + dev_info(&client->dev, "model %d, rev. %d\n", id >> 4, id & 0x0f); + indio_dev->name = client->name; + indio_dev->channels = tsl2563_channels; + indio_dev->num_channels = ARRAY_SIZE(tsl2563_channels); + indio_dev->dev.parent = &client->dev; + indio_dev->modes = INDIO_DIRECT_MODE; + + if (client->irq) + indio_dev->info = &tsl2563_info; + else + indio_dev->info = &tsl2563_info_no_irq; + + if (client->irq) { + err = request_threaded_irq(client->irq, + NULL, + &tsl2563_event_handler, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + "tsl2563_event", + indio_dev); + if (err) { + dev_err(&client->dev, "irq request error %d\n", -err); + goto fail1; + } + } + + err = tsl2563_configure(chip); + if (err) { + dev_err(&client->dev, "configure error %d\n", -err); + goto fail2; + } + + INIT_DELAYED_WORK(&chip->poweroff_work, tsl2563_poweroff_work); + + /* The interrupt cannot yet be enabled so this is fine without lock */ + schedule_delayed_work(&chip->poweroff_work, 5 * HZ); + + err = iio_device_register(indio_dev); + if (err) { + dev_err(&client->dev, "iio registration error %d\n", -err); + goto fail3; + } + + return 0; + +fail3: + cancel_delayed_work(&chip->poweroff_work); + flush_scheduled_work(); +fail2: + if (client->irq) + free_irq(client->irq, indio_dev); +fail1: + iio_device_free(indio_dev); + return err; +} + +static int tsl2563_remove(struct i2c_client *client) +{ + struct tsl2563_chip *chip = i2c_get_clientdata(client); + struct iio_dev *indio_dev = iio_priv_to_dev(chip); + + iio_device_unregister(indio_dev); + if (!chip->int_enabled) + cancel_delayed_work(&chip->poweroff_work); + /* Ensure that interrupts are disabled - then flush any bottom halves */ + chip->intr &= ~0x30; + i2c_smbus_write_byte_data(chip->client, TSL2563_CMD | TSL2563_REG_INT, + chip->intr); + flush_scheduled_work(); + tsl2563_set_power(chip, 0); + if (client->irq) + free_irq(client->irq, indio_dev); + + iio_device_free(indio_dev); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int tsl2563_suspend(struct device *dev) +{ + struct tsl2563_chip *chip = i2c_get_clientdata(to_i2c_client(dev)); + int ret; + + mutex_lock(&chip->lock); + + ret = tsl2563_set_power(chip, 0); + if (ret) + goto out; + + chip->suspended = true; + +out: + mutex_unlock(&chip->lock); + return ret; +} + +static int tsl2563_resume(struct device *dev) +{ + struct tsl2563_chip *chip = i2c_get_clientdata(to_i2c_client(dev)); + int ret; + + mutex_lock(&chip->lock); + + ret = tsl2563_set_power(chip, 1); + if (ret) + goto out; + + ret = tsl2563_configure(chip); + if (ret) + goto out; + + chip->suspended = false; + +out: + mutex_unlock(&chip->lock); + return ret; +} + +static SIMPLE_DEV_PM_OPS(tsl2563_pm_ops, tsl2563_suspend, tsl2563_resume); +#define TSL2563_PM_OPS (&tsl2563_pm_ops) +#else +#define TSL2563_PM_OPS NULL +#endif + +static const struct i2c_device_id tsl2563_id[] = { + { "tsl2560", 0 }, + { "tsl2561", 1 }, + { "tsl2562", 2 }, + { "tsl2563", 3 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, tsl2563_id); + +static struct i2c_driver tsl2563_i2c_driver = { + .driver = { + .name = "tsl2563", + .pm = TSL2563_PM_OPS, + }, + .probe = tsl2563_probe, + .remove = tsl2563_remove, + .id_table = tsl2563_id, +}; +module_i2c_driver(tsl2563_i2c_driver); + +MODULE_AUTHOR("Nokia Corporation"); +MODULE_DESCRIPTION("tsl2563 light sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/magnetometer/Kconfig b/drivers/iio/magnetometer/Kconfig index ff11d68..cd29be5 100644 --- a/drivers/iio/magnetometer/Kconfig +++ b/drivers/iio/magnetometer/Kconfig @@ -14,4 +14,34 @@ config HID_SENSOR_MAGNETOMETER_3D Say yes here to build support for the HID SENSOR Magnetometer 3D. +config IIO_ST_MAGN_3AXIS + tristate "STMicroelectronics magnetometers 3-Axis Driver" + depends on (I2C || SPI_MASTER) && SYSFS + select IIO_ST_SENSORS_CORE + select IIO_ST_MAGN_I2C_3AXIS if (I2C) + select IIO_ST_MAGN_SPI_3AXIS if (SPI_MASTER) + select IIO_TRIGGERED_BUFFER if (IIO_BUFFER) + select IIO_ST_MAGN_BUFFER if (IIO_TRIGGERED_BUFFER) + help + Say yes here to build support for STMicroelectronics magnetometers: + LSM303DLHC, LSM303DLM, LIS3MDL. + + This driver can also be built as a module. If so, will be created + these modules: + - st_magn (core functions for the driver [it is mandatory]); + - st_magn_i2c (necessary for the I2C devices [optional*]); + - st_magn_spi (necessary for the SPI devices [optional*]); + + (*) one of these is necessary to do something. + +config IIO_ST_MAGN_I2C_3AXIS + tristate + depends on IIO_ST_MAGN_3AXIS + depends on IIO_ST_SENSORS_I2C + +config IIO_ST_MAGN_SPI_3AXIS + tristate + depends on IIO_ST_MAGN_3AXIS + depends on IIO_ST_SENSORS_SPI + endmenu diff --git a/drivers/iio/magnetometer/Makefile b/drivers/iio/magnetometer/Makefile index 60dc4f2..e786728 100644 --- a/drivers/iio/magnetometer/Makefile +++ b/drivers/iio/magnetometer/Makefile @@ -3,3 +3,10 @@ # obj-$(CONFIG_HID_SENSOR_MAGNETOMETER_3D) += hid-sensor-magn-3d.o + +obj-$(CONFIG_IIO_ST_MAGN_3AXIS) += st_magn.o +st_magn-y := st_magn_core.o +st_magn-$(CONFIG_IIO_BUFFER) += st_magn_buffer.o + +obj-$(CONFIG_IIO_ST_MAGN_I2C_3AXIS) += st_magn_i2c.o +obj-$(CONFIG_IIO_ST_MAGN_SPI_3AXIS) += st_magn_spi.o diff --git a/drivers/iio/magnetometer/hid-sensor-magn-3d.c b/drivers/iio/magnetometer/hid-sensor-magn-3d.c index 7ac2c74..d8d0126 100644 --- a/drivers/iio/magnetometer/hid-sensor-magn-3d.c +++ b/drivers/iio/magnetometer/hid-sensor-magn-3d.c @@ -28,7 +28,6 @@ #include <linux/iio/buffer.h> #include <linux/iio/trigger_consumer.h> #include <linux/iio/triggered_buffer.h> -#include "../common/hid-sensors/hid-sensor-attributes.h" #include "../common/hid-sensors/hid-sensor-trigger.h" /*Format: HID-SENSOR-usage_id_in_hex*/ @@ -44,7 +43,7 @@ enum magn_3d_channel { struct magn_3d_state { struct hid_sensor_hub_callbacks callbacks; - struct hid_sensor_iio_common common_attributes; + struct hid_sensor_common common_attributes; struct hid_sensor_hub_attribute_info magn[MAGN_3D_CHANNEL_MAX]; u32 magn_val[MAGN_3D_CHANNEL_MAX]; }; diff --git a/drivers/iio/magnetometer/st_magn.h b/drivers/iio/magnetometer/st_magn.h new file mode 100644 index 0000000..7e81d00 --- /dev/null +++ b/drivers/iio/magnetometer/st_magn.h @@ -0,0 +1,45 @@ +/* + * STMicroelectronics magnetometers driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca <denis.ciocca@st.com> + * v. 1.0.0 + * Licensed under the GPL-2. + */ + +#ifndef ST_MAGN_H +#define ST_MAGN_H + +#include <linux/types.h> +#include <linux/iio/common/st_sensors.h> + +#define LSM303DLHC_MAGN_DEV_NAME "lsm303dlhc_magn" +#define LSM303DLM_MAGN_DEV_NAME "lsm303dlm_magn" +#define LIS3MDL_MAGN_DEV_NAME "lis3mdl" + +int st_magn_common_probe(struct iio_dev *indio_dev); +void st_magn_common_remove(struct iio_dev *indio_dev); + +#ifdef CONFIG_IIO_BUFFER +int st_magn_allocate_ring(struct iio_dev *indio_dev); +void st_magn_deallocate_ring(struct iio_dev *indio_dev); +#else /* CONFIG_IIO_BUFFER */ +static inline int st_magn_probe_trigger(struct iio_dev *indio_dev, int irq) +{ + return 0; +} +static inline void st_magn_remove_trigger(struct iio_dev *indio_dev, int irq) +{ + return; +} +static inline int st_magn_allocate_ring(struct iio_dev *indio_dev) +{ + return 0; +} +static inline void st_magn_deallocate_ring(struct iio_dev *indio_dev) +{ +} +#endif /* CONFIG_IIO_BUFFER */ + +#endif /* ST_MAGN_H */ diff --git a/drivers/iio/magnetometer/st_magn_buffer.c b/drivers/iio/magnetometer/st_magn_buffer.c new file mode 100644 index 0000000..708857b --- /dev/null +++ b/drivers/iio/magnetometer/st_magn_buffer.c @@ -0,0 +1,98 @@ +/* + * STMicroelectronics magnetometers 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_magn.h" + +static int st_magn_buffer_preenable(struct iio_dev *indio_dev) +{ + int err; + + err = st_sensors_set_enable(indio_dev, true); + if (err < 0) + goto st_magn_set_enable_error; + + err = iio_sw_buffer_preenable(indio_dev); + +st_magn_set_enable_error: + return err; +} + +static int st_magn_buffer_postenable(struct iio_dev *indio_dev) +{ + int err; + struct st_sensor_data *mdata = iio_priv(indio_dev); + + mdata->buffer_data = kmalloc(indio_dev->scan_bytes, GFP_KERNEL); + if (mdata->buffer_data == NULL) { + err = -ENOMEM; + goto allocate_memory_error; + } + + err = iio_triggered_buffer_postenable(indio_dev); + if (err < 0) + goto st_magn_buffer_postenable_error; + + return err; + +st_magn_buffer_postenable_error: + kfree(mdata->buffer_data); +allocate_memory_error: + return err; +} + +static int st_magn_buffer_predisable(struct iio_dev *indio_dev) +{ + int err; + struct st_sensor_data *mdata = iio_priv(indio_dev); + + err = iio_triggered_buffer_predisable(indio_dev); + if (err < 0) + goto st_magn_buffer_predisable_error; + + err = st_sensors_set_enable(indio_dev, false); + +st_magn_buffer_predisable_error: + kfree(mdata->buffer_data); + return err; +} + +static const struct iio_buffer_setup_ops st_magn_buffer_setup_ops = { + .preenable = &st_magn_buffer_preenable, + .postenable = &st_magn_buffer_postenable, + .predisable = &st_magn_buffer_predisable, +}; + +int st_magn_allocate_ring(struct iio_dev *indio_dev) +{ + return iio_triggered_buffer_setup(indio_dev, &iio_pollfunc_store_time, + &st_sensors_trigger_handler, &st_magn_buffer_setup_ops); +} + +void st_magn_deallocate_ring(struct iio_dev *indio_dev) +{ + iio_triggered_buffer_cleanup(indio_dev); +} + +MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics magnetometers buffer"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/magnetometer/st_magn_core.c b/drivers/iio/magnetometer/st_magn_core.c new file mode 100644 index 0000000..16f0d6d --- /dev/null +++ b/drivers/iio/magnetometer/st_magn_core.c @@ -0,0 +1,400 @@ +/* + * STMicroelectronics magnetometers 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/delay.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/buffer.h> + +#include <linux/iio/common/st_sensors.h> +#include "st_magn.h" + +/* DEFAULT VALUE FOR SENSORS */ +#define ST_MAGN_DEFAULT_OUT_X_L_ADDR 0X04 +#define ST_MAGN_DEFAULT_OUT_Y_L_ADDR 0X08 +#define ST_MAGN_DEFAULT_OUT_Z_L_ADDR 0X06 + +/* FULLSCALE */ +#define ST_MAGN_FS_AVL_1300MG 1300 +#define ST_MAGN_FS_AVL_1900MG 1900 +#define ST_MAGN_FS_AVL_2500MG 2500 +#define ST_MAGN_FS_AVL_4000MG 4000 +#define ST_MAGN_FS_AVL_4700MG 4700 +#define ST_MAGN_FS_AVL_5600MG 5600 +#define ST_MAGN_FS_AVL_8000MG 8000 +#define ST_MAGN_FS_AVL_8100MG 8100 +#define ST_MAGN_FS_AVL_10000MG 10000 + +/* CUSTOM VALUES FOR SENSOR 1 */ +#define ST_MAGN_1_WAI_EXP 0x3c +#define ST_MAGN_1_ODR_ADDR 0x00 +#define ST_MAGN_1_ODR_MASK 0x1c +#define ST_MAGN_1_ODR_AVL_1HZ_VAL 0x00 +#define ST_MAGN_1_ODR_AVL_2HZ_VAL 0x01 +#define ST_MAGN_1_ODR_AVL_3HZ_VAL 0x02 +#define ST_MAGN_1_ODR_AVL_8HZ_VAL 0x03 +#define ST_MAGN_1_ODR_AVL_15HZ_VAL 0x04 +#define ST_MAGN_1_ODR_AVL_30HZ_VAL 0x05 +#define ST_MAGN_1_ODR_AVL_75HZ_VAL 0x06 +#define ST_MAGN_1_ODR_AVL_220HZ_VAL 0x07 +#define ST_MAGN_1_PW_ADDR 0x02 +#define ST_MAGN_1_PW_MASK 0x03 +#define ST_MAGN_1_PW_ON 0x00 +#define ST_MAGN_1_PW_OFF 0x03 +#define ST_MAGN_1_FS_ADDR 0x01 +#define ST_MAGN_1_FS_MASK 0xe0 +#define ST_MAGN_1_FS_AVL_1300_VAL 0x01 +#define ST_MAGN_1_FS_AVL_1900_VAL 0x02 +#define ST_MAGN_1_FS_AVL_2500_VAL 0x03 +#define ST_MAGN_1_FS_AVL_4000_VAL 0x04 +#define ST_MAGN_1_FS_AVL_4700_VAL 0x05 +#define ST_MAGN_1_FS_AVL_5600_VAL 0x06 +#define ST_MAGN_1_FS_AVL_8100_VAL 0x07 +#define ST_MAGN_1_FS_AVL_1300_GAIN_XY 1100 +#define ST_MAGN_1_FS_AVL_1900_GAIN_XY 855 +#define ST_MAGN_1_FS_AVL_2500_GAIN_XY 670 +#define ST_MAGN_1_FS_AVL_4000_GAIN_XY 450 +#define ST_MAGN_1_FS_AVL_4700_GAIN_XY 400 +#define ST_MAGN_1_FS_AVL_5600_GAIN_XY 330 +#define ST_MAGN_1_FS_AVL_8100_GAIN_XY 230 +#define ST_MAGN_1_FS_AVL_1300_GAIN_Z 980 +#define ST_MAGN_1_FS_AVL_1900_GAIN_Z 760 +#define ST_MAGN_1_FS_AVL_2500_GAIN_Z 600 +#define ST_MAGN_1_FS_AVL_4000_GAIN_Z 400 +#define ST_MAGN_1_FS_AVL_4700_GAIN_Z 355 +#define ST_MAGN_1_FS_AVL_5600_GAIN_Z 295 +#define ST_MAGN_1_FS_AVL_8100_GAIN_Z 205 +#define ST_MAGN_1_MULTIREAD_BIT false + +/* CUSTOM VALUES FOR SENSOR 2 */ +#define ST_MAGN_2_WAI_EXP 0x3d +#define ST_MAGN_2_ODR_ADDR 0x20 +#define ST_MAGN_2_ODR_MASK 0x1c +#define ST_MAGN_2_ODR_AVL_1HZ_VAL 0x00 +#define ST_MAGN_2_ODR_AVL_2HZ_VAL 0x01 +#define ST_MAGN_2_ODR_AVL_3HZ_VAL 0x02 +#define ST_MAGN_2_ODR_AVL_5HZ_VAL 0x03 +#define ST_MAGN_2_ODR_AVL_10HZ_VAL 0x04 +#define ST_MAGN_2_ODR_AVL_20HZ_VAL 0x05 +#define ST_MAGN_2_ODR_AVL_40HZ_VAL 0x06 +#define ST_MAGN_2_ODR_AVL_80HZ_VAL 0x07 +#define ST_MAGN_2_PW_ADDR 0x22 +#define ST_MAGN_2_PW_MASK 0x03 +#define ST_MAGN_2_PW_ON 0x00 +#define ST_MAGN_2_PW_OFF 0x03 +#define ST_MAGN_2_FS_ADDR 0x21 +#define ST_MAGN_2_FS_MASK 0x60 +#define ST_MAGN_2_FS_AVL_4000_VAL 0x00 +#define ST_MAGN_2_FS_AVL_8000_VAL 0x01 +#define ST_MAGN_2_FS_AVL_10000_VAL 0x02 +#define ST_MAGN_2_FS_AVL_4000_GAIN 430 +#define ST_MAGN_2_FS_AVL_8000_GAIN 230 +#define ST_MAGN_2_FS_AVL_10000_GAIN 230 +#define ST_MAGN_2_MULTIREAD_BIT false +#define ST_MAGN_2_OUT_X_L_ADDR 0x28 +#define ST_MAGN_2_OUT_Y_L_ADDR 0x2a +#define ST_MAGN_2_OUT_Z_L_ADDR 0x2c + +static const struct iio_chan_spec st_magn_16bit_channels[] = { + ST_SENSORS_LSM_CHANNELS(IIO_MAGN, ST_SENSORS_SCAN_X, IIO_MOD_X, IIO_LE, + ST_SENSORS_DEFAULT_16_REALBITS, ST_MAGN_DEFAULT_OUT_X_L_ADDR), + ST_SENSORS_LSM_CHANNELS(IIO_MAGN, ST_SENSORS_SCAN_Y, IIO_MOD_Y, IIO_LE, + ST_SENSORS_DEFAULT_16_REALBITS, ST_MAGN_DEFAULT_OUT_Y_L_ADDR), + ST_SENSORS_LSM_CHANNELS(IIO_MAGN, ST_SENSORS_SCAN_Z, IIO_MOD_Z, IIO_LE, + ST_SENSORS_DEFAULT_16_REALBITS, ST_MAGN_DEFAULT_OUT_Z_L_ADDR), + IIO_CHAN_SOFT_TIMESTAMP(3) +}; + +static const struct iio_chan_spec st_magn_2_16bit_channels[] = { + ST_SENSORS_LSM_CHANNELS(IIO_MAGN, ST_SENSORS_SCAN_X, IIO_MOD_X, IIO_LE, + ST_SENSORS_DEFAULT_16_REALBITS, ST_MAGN_2_OUT_X_L_ADDR), + ST_SENSORS_LSM_CHANNELS(IIO_MAGN, ST_SENSORS_SCAN_Y, IIO_MOD_Y, IIO_LE, + ST_SENSORS_DEFAULT_16_REALBITS, ST_MAGN_2_OUT_Y_L_ADDR), + ST_SENSORS_LSM_CHANNELS(IIO_MAGN, ST_SENSORS_SCAN_Z, IIO_MOD_Z, IIO_LE, + ST_SENSORS_DEFAULT_16_REALBITS, ST_MAGN_2_OUT_Z_L_ADDR), + IIO_CHAN_SOFT_TIMESTAMP(3) +}; + +static const struct st_sensors st_magn_sensors[] = { + { + .wai = ST_MAGN_1_WAI_EXP, + .sensors_supported = { + [0] = LSM303DLHC_MAGN_DEV_NAME, + [1] = LSM303DLM_MAGN_DEV_NAME, + }, + .ch = (struct iio_chan_spec *)st_magn_16bit_channels, + .odr = { + .addr = ST_MAGN_1_ODR_ADDR, + .mask = ST_MAGN_1_ODR_MASK, + .odr_avl = { + { 1, ST_MAGN_1_ODR_AVL_1HZ_VAL, }, + { 2, ST_MAGN_1_ODR_AVL_2HZ_VAL, }, + { 3, ST_MAGN_1_ODR_AVL_3HZ_VAL, }, + { 8, ST_MAGN_1_ODR_AVL_8HZ_VAL, }, + { 15, ST_MAGN_1_ODR_AVL_15HZ_VAL, }, + { 30, ST_MAGN_1_ODR_AVL_30HZ_VAL, }, + { 75, ST_MAGN_1_ODR_AVL_75HZ_VAL, }, + { 220, ST_MAGN_1_ODR_AVL_220HZ_VAL, }, + }, + }, + .pw = { + .addr = ST_MAGN_1_PW_ADDR, + .mask = ST_MAGN_1_PW_MASK, + .value_on = ST_MAGN_1_PW_ON, + .value_off = ST_MAGN_1_PW_OFF, + }, + .fs = { + .addr = ST_MAGN_1_FS_ADDR, + .mask = ST_MAGN_1_FS_MASK, + .fs_avl = { + [0] = { + .num = ST_MAGN_FS_AVL_1300MG, + .value = ST_MAGN_1_FS_AVL_1300_VAL, + .gain = ST_MAGN_1_FS_AVL_1300_GAIN_XY, + .gain2 = ST_MAGN_1_FS_AVL_1300_GAIN_Z, + }, + [1] = { + .num = ST_MAGN_FS_AVL_1900MG, + .value = ST_MAGN_1_FS_AVL_1900_VAL, + .gain = ST_MAGN_1_FS_AVL_1900_GAIN_XY, + .gain2 = ST_MAGN_1_FS_AVL_1900_GAIN_Z, + }, + [2] = { + .num = ST_MAGN_FS_AVL_2500MG, + .value = ST_MAGN_1_FS_AVL_2500_VAL, + .gain = ST_MAGN_1_FS_AVL_2500_GAIN_XY, + .gain2 = ST_MAGN_1_FS_AVL_2500_GAIN_Z, + }, + [3] = { + .num = ST_MAGN_FS_AVL_4000MG, + .value = ST_MAGN_1_FS_AVL_4000_VAL, + .gain = ST_MAGN_1_FS_AVL_4000_GAIN_XY, + .gain2 = ST_MAGN_1_FS_AVL_4000_GAIN_Z, + }, + [4] = { + .num = ST_MAGN_FS_AVL_4700MG, + .value = ST_MAGN_1_FS_AVL_4700_VAL, + .gain = ST_MAGN_1_FS_AVL_4700_GAIN_XY, + .gain2 = ST_MAGN_1_FS_AVL_4700_GAIN_Z, + }, + [5] = { + .num = ST_MAGN_FS_AVL_5600MG, + .value = ST_MAGN_1_FS_AVL_5600_VAL, + .gain = ST_MAGN_1_FS_AVL_5600_GAIN_XY, + .gain2 = ST_MAGN_1_FS_AVL_5600_GAIN_Z, + }, + [6] = { + .num = ST_MAGN_FS_AVL_8100MG, + .value = ST_MAGN_1_FS_AVL_8100_VAL, + .gain = ST_MAGN_1_FS_AVL_8100_GAIN_XY, + .gain2 = ST_MAGN_1_FS_AVL_8100_GAIN_Z, + }, + }, + }, + .multi_read_bit = ST_MAGN_1_MULTIREAD_BIT, + .bootime = 2, + }, + { + .wai = ST_MAGN_2_WAI_EXP, + .sensors_supported = { + [0] = LIS3MDL_MAGN_DEV_NAME, + }, + .ch = (struct iio_chan_spec *)st_magn_2_16bit_channels, + .odr = { + .addr = ST_MAGN_2_ODR_ADDR, + .mask = ST_MAGN_2_ODR_MASK, + .odr_avl = { + { 1, ST_MAGN_2_ODR_AVL_1HZ_VAL, }, + { 2, ST_MAGN_2_ODR_AVL_2HZ_VAL, }, + { 3, ST_MAGN_2_ODR_AVL_3HZ_VAL, }, + { 5, ST_MAGN_2_ODR_AVL_5HZ_VAL, }, + { 10, ST_MAGN_2_ODR_AVL_10HZ_VAL, }, + { 20, ST_MAGN_2_ODR_AVL_20HZ_VAL, }, + { 40, ST_MAGN_2_ODR_AVL_40HZ_VAL, }, + { 80, ST_MAGN_2_ODR_AVL_80HZ_VAL, }, + }, + }, + .pw = { + .addr = ST_MAGN_2_PW_ADDR, + .mask = ST_MAGN_2_PW_MASK, + .value_on = ST_MAGN_2_PW_ON, + .value_off = ST_MAGN_2_PW_OFF, + }, + .fs = { + .addr = ST_MAGN_2_FS_ADDR, + .mask = ST_MAGN_2_FS_MASK, + .fs_avl = { + [0] = { + .num = ST_MAGN_FS_AVL_4000MG, + .value = ST_MAGN_2_FS_AVL_4000_VAL, + .gain = ST_MAGN_2_FS_AVL_4000_GAIN, + }, + [1] = { + .num = ST_MAGN_FS_AVL_8000MG, + .value = ST_MAGN_2_FS_AVL_8000_VAL, + .gain = ST_MAGN_2_FS_AVL_8000_GAIN, + }, + [2] = { + .num = ST_MAGN_FS_AVL_10000MG, + .value = ST_MAGN_2_FS_AVL_10000_VAL, + .gain = ST_MAGN_2_FS_AVL_10000_GAIN, + }, + }, + }, + .multi_read_bit = ST_MAGN_2_MULTIREAD_BIT, + .bootime = 2, + }, +}; + +static int st_magn_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 *mdata = 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; + if ((ch->scan_index == ST_SENSORS_SCAN_Z) && + (mdata->current_fullscale->gain2 != 0)) + *val2 = mdata->current_fullscale->gain2; + else + *val2 = mdata->current_fullscale->gain; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + +read_error: + return err; +} + +static int st_magn_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: + err = -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_magn_scale_available); + +static struct attribute *st_magn_attributes[] = { + &iio_dev_attr_sampling_frequency_available.dev_attr.attr, + &iio_dev_attr_in_magn_scale_available.dev_attr.attr, + &iio_dev_attr_sampling_frequency.dev_attr.attr, + NULL, +}; + +static const struct attribute_group st_magn_attribute_group = { + .attrs = st_magn_attributes, +}; + +static const struct iio_info magn_info = { + .driver_module = THIS_MODULE, + .attrs = &st_magn_attribute_group, + .read_raw = &st_magn_read_raw, + .write_raw = &st_magn_write_raw, +}; + +int st_magn_common_probe(struct iio_dev *indio_dev) +{ + int err; + struct st_sensor_data *mdata = iio_priv(indio_dev); + + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &magn_info; + + err = st_sensors_check_device_support(indio_dev, + ARRAY_SIZE(st_magn_sensors), st_magn_sensors); + if (err < 0) + goto st_magn_common_probe_error; + + mdata->multiread_bit = mdata->sensor->multi_read_bit; + indio_dev->channels = mdata->sensor->ch; + indio_dev->num_channels = ST_SENSORS_NUMBER_ALL_CHANNELS; + + mdata->current_fullscale = (struct st_sensor_fullscale_avl *) + &mdata->sensor->fs.fs_avl[0]; + mdata->odr = mdata->sensor->odr.odr_avl[0].hz; + + err = st_sensors_init_sensor(indio_dev); + if (err < 0) + goto st_magn_common_probe_error; + + if (mdata->get_irq_data_ready(indio_dev) > 0) { + err = st_magn_allocate_ring(indio_dev); + if (err < 0) + goto st_magn_common_probe_error; + err = st_sensors_allocate_trigger(indio_dev, NULL); + if (err < 0) + goto st_magn_probe_trigger_error; + } + + err = iio_device_register(indio_dev); + if (err) + goto st_magn_device_register_error; + + return err; + +st_magn_device_register_error: + if (mdata->get_irq_data_ready(indio_dev) > 0) + st_sensors_deallocate_trigger(indio_dev); +st_magn_probe_trigger_error: + if (mdata->get_irq_data_ready(indio_dev) > 0) + st_magn_deallocate_ring(indio_dev); +st_magn_common_probe_error: + return err; +} +EXPORT_SYMBOL(st_magn_common_probe); + +void st_magn_common_remove(struct iio_dev *indio_dev) +{ + struct st_sensor_data *mdata = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + if (mdata->get_irq_data_ready(indio_dev) > 0) { + st_sensors_deallocate_trigger(indio_dev); + st_magn_deallocate_ring(indio_dev); + } + iio_device_free(indio_dev); +} +EXPORT_SYMBOL(st_magn_common_remove); + +MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics magnetometers driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/magnetometer/st_magn_i2c.c b/drivers/iio/magnetometer/st_magn_i2c.c new file mode 100644 index 0000000..e6adc4a --- /dev/null +++ b/drivers/iio/magnetometer/st_magn_i2c.c @@ -0,0 +1,80 @@ +/* + * STMicroelectronics magnetometers 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_magn.h" + +static int st_magn_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct iio_dev *indio_dev; + struct st_sensor_data *mdata; + int err; + + indio_dev = iio_device_alloc(sizeof(*mdata)); + if (indio_dev == NULL) { + err = -ENOMEM; + goto iio_device_alloc_error; + } + + mdata = iio_priv(indio_dev); + mdata->dev = &client->dev; + + st_sensors_i2c_configure(indio_dev, client, mdata); + + err = st_magn_common_probe(indio_dev); + if (err < 0) + goto st_magn_common_probe_error; + + return 0; + +st_magn_common_probe_error: + iio_device_free(indio_dev); +iio_device_alloc_error: + return err; +} + +static int st_magn_i2c_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + st_magn_common_remove(indio_dev); + + return 0; +} + +static const struct i2c_device_id st_magn_id_table[] = { + { LSM303DLHC_MAGN_DEV_NAME }, + { LSM303DLM_MAGN_DEV_NAME }, + { LIS3MDL_MAGN_DEV_NAME }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, st_magn_id_table); + +static struct i2c_driver st_magn_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "st-magn-i2c", + }, + .probe = st_magn_i2c_probe, + .remove = st_magn_i2c_remove, + .id_table = st_magn_id_table, +}; +module_i2c_driver(st_magn_driver); + +MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics magnetometers i2c driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/magnetometer/st_magn_spi.c b/drivers/iio/magnetometer/st_magn_spi.c new file mode 100644 index 0000000..51adb79 --- /dev/null +++ b/drivers/iio/magnetometer/st_magn_spi.c @@ -0,0 +1,79 @@ +/* + * STMicroelectronics magnetometers 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_magn.h" + +static int st_magn_spi_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct st_sensor_data *mdata; + int err; + + indio_dev = iio_device_alloc(sizeof(*mdata)); + if (indio_dev == NULL) { + err = -ENOMEM; + goto iio_device_alloc_error; + } + + mdata = iio_priv(indio_dev); + mdata->dev = &spi->dev; + + st_sensors_spi_configure(indio_dev, spi, mdata); + + err = st_magn_common_probe(indio_dev); + if (err < 0) + goto st_magn_common_probe_error; + + return 0; + +st_magn_common_probe_error: + iio_device_free(indio_dev); +iio_device_alloc_error: + return err; +} + +static int st_magn_spi_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + st_magn_common_remove(indio_dev); + + return 0; +} + +static const struct spi_device_id st_magn_id_table[] = { + { LSM303DLHC_MAGN_DEV_NAME }, + { LSM303DLM_MAGN_DEV_NAME }, + { LIS3MDL_MAGN_DEV_NAME }, + {}, +}; +MODULE_DEVICE_TABLE(spi, st_magn_id_table); + +static struct spi_driver st_magn_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "st-magn-spi", + }, + .probe = st_magn_spi_probe, + .remove = st_magn_spi_remove, + .id_table = st_magn_id_table, +}; +module_spi_driver(st_magn_driver); + +MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics magnetometers spi driver"); +MODULE_LICENSE("GPL v2"); |