diff options
| author | Evan Wilson <evan@oliodevices.com> | 2015-06-30 10:23:50 -0700 |
|---|---|---|
| committer | Evan Wilson <evan@oliodevices.com> | 2015-06-30 10:23:50 -0700 |
| commit | 688863c9ef555051ce7cfbc3170e7ab65b832f03 (patch) | |
| tree | f877ba825c58bf0c5b434760f69da8f55d0956a6 | |
| parent | 5629b2f2364f4d376f903d1f144b6f0ca9f0238b (diff) | |
| download | olio-linux-3.10-688863c9ef555051ce7cfbc3170e7ab65b832f03.tar.xz olio-linux-3.10-688863c9ef555051ce7cfbc3170e7ab65b832f03.zip | |
Initial ST patch
Change-Id: I66a587f5fcdf026ed472cb867fe9903051c2f78b
| -rw-r--r-- | Documentation/devicetree/bindings/iio/st_lsm6ds3.txt | 19 | ||||
| -rw-r--r-- | drivers/iio/imu/Kconfig | 1 | ||||
| -rw-r--r-- | drivers/iio/imu/Makefile | 1 | ||||
| -rw-r--r-- | drivers/iio/imu/st_lsm6ds3/Kconfig | 75 | ||||
| -rw-r--r-- | drivers/iio/imu/st_lsm6ds3/Makefile | 11 | ||||
| -rw-r--r-- | drivers/iio/imu/st_lsm6ds3/st_lsm6ds3.h | 280 | ||||
| -rw-r--r-- | drivers/iio/imu/st_lsm6ds3/st_lsm6ds3_buffer.c | 448 | ||||
| -rw-r--r-- | drivers/iio/imu/st_lsm6ds3/st_lsm6ds3_core.c | 2137 | ||||
| -rw-r--r-- | drivers/iio/imu/st_lsm6ds3/st_lsm6ds3_i2c.c | 159 | ||||
| -rw-r--r-- | drivers/iio/imu/st_lsm6ds3/st_lsm6ds3_i2c_master.c | 1387 | ||||
| -rw-r--r-- | drivers/iio/imu/st_lsm6ds3/st_lsm6ds3_spi.c | 180 | ||||
| -rw-r--r-- | drivers/iio/imu/st_lsm6ds3/st_lsm6ds3_trigger.c | 175 |
12 files changed, 4873 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/iio/st_lsm6ds3.txt b/Documentation/devicetree/bindings/iio/st_lsm6ds3.txt new file mode 100644 index 00000000000..bef52f2e290 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/st_lsm6ds3.txt @@ -0,0 +1,19 @@ +/* STMicroelectronics lsm6ds3 sensor */ + +Required properties: + - compatible : should be "st,lsm6ds3" + - reg : the I2C address of the sensor + +Optional properties: + - interrupt-parent : should be the phandle for the interrupt controller + - interrupts : interrupt mapping for GPIO IRQ, it should by configured with + flags IRQ_TYPE_EDGE_RISING + +Example: + +lsm6ds3@6b { + compatible = "st,lsm6ds3"; + reg = <0x6b>; + interrupt-parent = <&gpio>; + interrupts = <1 IRQ_TYPE_EDGE_RISING>; +};
\ No newline at end of file diff --git a/drivers/iio/imu/Kconfig b/drivers/iio/imu/Kconfig index 4f40a10cb74..124aa491101 100644 --- a/drivers/iio/imu/Kconfig +++ b/drivers/iio/imu/Kconfig @@ -38,3 +38,4 @@ config IIO_ADIS_LIB_BUFFER family. source "drivers/iio/imu/inv_mpu6050/Kconfig" +source "drivers/iio/imu/st_lsm6ds3/Kconfig" diff --git a/drivers/iio/imu/Makefile b/drivers/iio/imu/Makefile index f2f56ceaed2..ebbe6001a32 100644 --- a/drivers/iio/imu/Makefile +++ b/drivers/iio/imu/Makefile @@ -13,3 +13,4 @@ adis_lib-$(CONFIG_IIO_ADIS_LIB_BUFFER) += adis_buffer.o obj-$(CONFIG_IIO_ADIS_LIB) += adis_lib.o obj-y += inv_mpu6050/ +obj-y += st_lsm6ds3/ diff --git a/drivers/iio/imu/st_lsm6ds3/Kconfig b/drivers/iio/imu/st_lsm6ds3/Kconfig new file mode 100644 index 00000000000..cb350b25d12 --- /dev/null +++ b/drivers/iio/imu/st_lsm6ds3/Kconfig @@ -0,0 +1,75 @@ +# +# st-lsm6ds3 drivers for STMicroelectronics combo sensor +# + +config ST_LSM6DS3_IIO + tristate "STMicroelectronics LSM6DS3 sensor" + depends on (I2C || SPI) && SYSFS + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + This driver supports the STMicroelectronics LSM6DS3 sensor. + It is a gyroscope/accelerometer combo device. + This driver can be built as a module. The module will be called + st-lsm6ds3. + +config ST_LSM6DS3_IIO_LIMIT_FIFO + int "Limit fifo read lenght (#n byte)" + depends on ST_LSM6DS3_IIO + range 0 8192 + default 0 + help + Limit atomic fifo read to #n byte. In some platform i2c/spi read + can be limited by software or hardware. + + Set 0 to disable the limit. + +config ST_LSM6DS3_IIO_SENSORS_WAKEUP + bool "All sensors can wake-up system during suspend" + depends on ST_LSM6DS3_IIO + default n + help + If disabled only tilt and significant motion can wake-up system + during suspend. + + If enabled all sensors can wake-up system during suspend. + +menuconfig ST_LSM6DS3_IIO_MASTER_SUPPORT + bool "I2C master controller" + depends on I2C && ST_LSM6DS3_IIO + default n + help + Added support for I2C master controller. Supported sensors up + to 4. + +if ST_LSM6DS3_IIO_MASTER_SUPPORT + +config ST_LSM6DS3_ENABLE_INTERNAL_PULLUP + bool "Enabled internals pull-up resistors" + default y + +choice + prompt "External sensor 0" + default ST_LSM6DS3_IIO_EXT0_LIS3MDL + help + Choose the external sensor 0 connected to LSM6DS3. + +config ST_LSM6DS3_IIO_EXT0_LIS3MDL + bool "LIS3MDL" +config ST_LSM6DS3_IIO_EXT0_AKM09912 + bool "AKM09912" +endchoice + +choice + prompt "External sensor 1" + default ST_LSM6DS3_IIO_EXT1_DISABLED + help + Choose the external sensor 1 connected to LSM6DS3. + +config ST_LSM6DS3_IIO_EXT1_DISABLED + bool "Disabled" +config ST_LSM6DS3_IIO_EXT1_LPS22HB + bool "LPS22HB" +endchoice + +endif diff --git a/drivers/iio/imu/st_lsm6ds3/Makefile b/drivers/iio/imu/st_lsm6ds3/Makefile new file mode 100644 index 00000000000..36348a34e89 --- /dev/null +++ b/drivers/iio/imu/st_lsm6ds3/Makefile @@ -0,0 +1,11 @@ +# +# Makefile for STMicroelectronics LSM6DS3 sensor. +# + +obj-$(CONFIG_ST_LSM6DS3_IIO) += st_lsm6ds3.o +st_lsm6ds3-objs := st_lsm6ds3_core.o + +obj-$(CONFIG_ST_LSM6DS3_IIO) += st_lsm6ds3_spi.o st_lsm6ds3_i2c.o +st_lsm6ds3-$(CONFIG_IIO_BUFFER) += st_lsm6ds3_buffer.o +st_lsm6ds3-$(CONFIG_IIO_TRIGGER) += st_lsm6ds3_trigger.o +st_lsm6ds3-$(CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT) += st_lsm6ds3_i2c_master.o diff --git a/drivers/iio/imu/st_lsm6ds3/st_lsm6ds3.h b/drivers/iio/imu/st_lsm6ds3/st_lsm6ds3.h new file mode 100644 index 00000000000..78fbf76f4c2 --- /dev/null +++ b/drivers/iio/imu/st_lsm6ds3/st_lsm6ds3.h @@ -0,0 +1,280 @@ +/* + * STMicroelectronics lsm6ds3 driver + * + * Copyright 2014 STMicroelectronics Inc. + * + * Denis Ciocca <denis.ciocca@st.com> + * v. 2.1.2 + * Licensed under the GPL-2. + */ + +#ifndef ST_LSM6DS3_H +#define ST_LSM6DS3_H + +#include <linux/types.h> +#include <linux/iio/trigger.h> + +#ifdef CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT +#include <linux/i2c.h> +#endif /* CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT */ + +#define LSM6DS3_DEV_NAME "lsm6ds3" + +#define ST_INDIO_DEV_ACCEL 0 +#define ST_INDIO_DEV_GYRO 1 +#define ST_INDIO_DEV_SIGN_MOTION 2 +#define ST_INDIO_DEV_STEP_COUNTER 3 +#define ST_INDIO_DEV_STEP_DETECTOR 4 +#define ST_INDIO_DEV_TILT 5 +#define ST_INDIO_DEV_NUM 6 + +#define ST_INDIO_DEV_EXT0 ST_INDIO_DEV_NUM +#define ST_INDIO_DEV_EXT1 (ST_INDIO_DEV_NUM + 1) + +#define ST_LSM6DS3_ACCEL_DEPENDENCY ((1 << ST_INDIO_DEV_ACCEL) | \ + (1 << ST_INDIO_DEV_STEP_COUNTER) | \ + (1 << ST_INDIO_DEV_TILT) | \ + (1 << ST_INDIO_DEV_SIGN_MOTION) | \ + (1 << ST_INDIO_DEV_STEP_DETECTOR) | \ + (1 << ST_INDIO_DEV_EXT0) | \ + (1 << ST_INDIO_DEV_EXT1)) + +#define ST_LSM6DS3_PEDOMETER_DEPENDENCY ((1 << ST_INDIO_DEV_STEP_COUNTER) | \ + (1 << ST_INDIO_DEV_STEP_DETECTOR) | \ + (1 << ST_INDIO_DEV_SIGN_MOTION)) + +#define ST_LSM6DS3_EXTRA_DEPENDENCY ((1 << ST_INDIO_DEV_STEP_COUNTER) | \ + (1 << ST_INDIO_DEV_TILT) | \ + (1 << ST_INDIO_DEV_SIGN_MOTION) | \ + (1 << ST_INDIO_DEV_STEP_DETECTOR) | \ + (1 << ST_INDIO_DEV_EXT0) | \ + (1 << ST_INDIO_DEV_EXT1)) + +#define ST_LSM6DS3_USE_BUFFER ((1 << ST_INDIO_DEV_ACCEL) | \ + (1 << ST_INDIO_DEV_GYRO) | \ + (1 << ST_INDIO_DEV_STEP_COUNTER)) + +#define ST_LSM6DS3_EXT_SENSORS ((1 << ST_INDIO_DEV_EXT0) | \ + (1 << ST_INDIO_DEV_EXT1)) + +#ifdef CONFIG_ST_LSM6DS3_IIO_SENSORS_WAKEUP +#define ST_LSM6DS3_WAKE_UP_SENSORS ((1 << ST_INDIO_DEV_SIGN_MOTION) | \ + (1 << ST_INDIO_DEV_TILT)) +#else /* CONFIG_ST_LSM6DS3_IIO_SENSORS_WAKEUP */ +#define ST_LSM6DS3_WAKE_UP_SENSORS ((1 << ST_INDIO_DEV_SIGN_MOTION) | \ + (1 << ST_INDIO_DEV_TILT) | \ + (1 << ST_INDIO_DEV_ACCEL) | \ + (1 << ST_INDIO_DEV_GYRO) | \ + (1 << ST_INDIO_DEV_STEP_COUNTER) | \ + (1 << ST_INDIO_DEV_STEP_DETECTOR) | \ + (1 << ST_INDIO_DEV_EXT0) | \ + (1 << ST_INDIO_DEV_EXT1)) +#endif /* CONFIG_ST_LSM6DS3_IIO_SENSORS_WAKEUP */ + +#define ST_LSM6DS3_TX_MAX_LENGTH 12 +#define ST_LSM6DS3_RX_MAX_LENGTH 8193 + +#define ST_LSM6DS3_BYTE_FOR_CHANNEL 2 +#define ST_LSM6DS3_FIFO_ELEMENT_LEN_BYTE 6 + +#ifdef CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT +#define ST_LSM6DS3_NUM_CLIENTS 2 +#else /* CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT */ +#define ST_LSM6DS3_NUM_CLIENTS 0 +#endif /* CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT */ + +#define ST_LSM6DS3_LSM_CHANNELS(device_type, modif, index, mod, \ + endian, sbits, rbits, addr, s) \ +{ \ + .type = device_type, \ + .modified = modif, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .scan_index = index, \ + .channel2 = mod, \ + .address = addr, \ + .scan_type = { \ + .sign = s, \ + .realbits = rbits, \ + .shift = sbits - rbits, \ + .storagebits = sbits, \ + .endianness = endian, \ + }, \ +} + +#define ST_LSM6DS3_FIFO_LENGHT() \ + IIO_DEVICE_ATTR(hw_fifo_lenght, S_IRUGO, \ + st_lsm6ds3_sysfs_get_hw_fifo_lenght, NULL, 0); + +#define ST_LSM6DS3_FIFO_FLUSH() \ + IIO_DEVICE_ATTR(flush, S_IWUSR, NULL, st_lsm6ds3_sysfs_flush_fifo, 0); + +enum fifo_mode { + BYPASS = 0, + CONTINUOS, +}; + +struct st_lsm6ds3_transfer_buffer { + struct mutex buf_lock; + u8 rx_buf[ST_LSM6DS3_RX_MAX_LENGTH]; + u8 tx_buf[ST_LSM6DS3_TX_MAX_LENGTH] ____cacheline_aligned; +}; + +struct lsm6ds3_data { + const char *name; + + bool reset_steps; + bool sign_motion_event_ready; + + u8 *fifo_data; + u8 sensors_enabled; + u8 gyro_selftest_status; + u8 accel_selftest_status; + u8 accel_samples_in_pattern; + u8 gyro_samples_in_pattern; +#ifdef CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT + u8 ext0_samples_in_pattern; + u8 ext1_samples_in_pattern; +#endif /* CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT */ + u8 accel_samples_to_discard; + u8 gyro_samples_to_discard; +#ifdef CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT + u8 ext_samples_to_discard[ST_LSM6DS3_NUM_CLIENTS]; +#endif /* CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT */ + + u16 fifo_threshold; + + int irq; + + s64 timestamp; + int64_t accel_deltatime; + int64_t accel_timestamp; + int64_t gyro_deltatime; + int64_t gyro_timestamp; +#ifdef CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT + int64_t ext0_deltatime; + int64_t ext0_timestamp; + int64_t ext1_deltatime; + int64_t ext1_timestamp; +#endif /* CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT */ + + struct work_struct data_work; + + struct device *dev; + struct iio_dev *indio_dev[ST_INDIO_DEV_NUM + ST_LSM6DS3_NUM_CLIENTS]; + struct iio_trigger *trig[ST_INDIO_DEV_NUM + ST_LSM6DS3_NUM_CLIENTS]; + struct mutex bank_registers_lock; + struct mutex fifo_lock; + +#ifdef CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT + struct i2c_client *master_client[ST_LSM6DS3_NUM_CLIENTS]; + struct mutex passthrough_lock; + bool ext0_available; + bool ext1_available; +#endif /* CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT */ + + const struct st_lsm6ds3_transfer_function *tf; + struct st_lsm6ds3_transfer_buffer tb; +}; + +struct st_lsm6ds3_transfer_function { + int (*write) (struct lsm6ds3_data *cdata, + u8 reg_addr, int len, u8 *data, bool b_lock); + int (*read) (struct lsm6ds3_data *cdata, + u8 reg_addr, int len, u8 *data, bool b_lock); +}; + +struct lsm6ds3_sensor_data { + struct lsm6ds3_data *cdata; + + unsigned int c_odr; + unsigned int c_gain[3]; + + u8 num_data_channels; + u8 sindex; + u8 *buffer_data; +}; + +int st_lsm6ds3_write_data_with_mask(struct lsm6ds3_data *cdata, + u8 reg_addr, u8 mask, u8 data, bool b_lock); + +int st_lsm6ds3_common_probe(struct lsm6ds3_data *cdata, int irq); +void st_lsm6ds3_common_remove(struct lsm6ds3_data *cdata, int irq); + +int st_lsm6ds3_set_enable(struct lsm6ds3_sensor_data *sdata, bool enable); +int st_lsm6ds3_set_axis_enable(struct lsm6ds3_sensor_data *sdata, u8 value); +int st_lsm6ds3_set_drdy_irq(struct lsm6ds3_sensor_data *sdata, bool state); +int st_lsm6ds3_set_fifo_mode(struct lsm6ds3_data *cdata, enum fifo_mode fm); +int st_lsm6ds3_reconfigure_fifo(struct lsm6ds3_data *cdata, + bool disable_irq_and_flush); + +ssize_t st_lsm6ds3_sysfs_get_hw_fifo_lenght(struct device *dev, + struct device_attribute *attr, char *buf); +ssize_t st_lsm6ds3_sysfs_flush_fifo(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size); + +#ifdef CONFIG_IIO_BUFFER +int st_lsm6ds3_allocate_rings(struct lsm6ds3_data *cdata); +void st_lsm6ds3_deallocate_rings(struct lsm6ds3_data *cdata); +int st_lsm6ds3_trig_set_state(struct iio_trigger *trig, bool state); +void st_lsm6ds3_read_fifo(struct lsm6ds3_data *cdata, bool check_fifo_len); +int st_lsm6ds3_set_fifo_decimators_and_threshold(struct lsm6ds3_data *cdata); +#define ST_LSM6DS3_TRIGGER_SET_STATE (&st_lsm6ds3_trig_set_state) +#else /* CONFIG_IIO_BUFFER */ +static inline int st_lsm6ds3_allocate_rings(struct lsm6ds3_data *cdata) +{ + return 0; +} +static inline void st_lsm6ds3_deallocate_rings(struct lsm6ds3_data *cdata) +{ +} +#define ST_LSM6DS3_TRIGGER_SET_STATE NULL +#endif /* CONFIG_IIO_BUFFER */ + +#ifdef CONFIG_IIO_TRIGGER +void st_lsm6ds3_flush_works(void); +int st_lsm6ds3_allocate_triggers(struct lsm6ds3_data *cdata, + const struct iio_trigger_ops *trigger_ops); + +void st_lsm6ds3_deallocate_triggers(struct lsm6ds3_data *cdata); + +#else /* CONFIG_IIO_TRIGGER */ +static inline int st_lsm6ds3_allocate_triggers(struct lsm6ds3_data *cdata, + const struct iio_trigger_ops *trigger_ops, int irq) +{ + return 0; +} +static inline void st_lsm6ds3_deallocate_triggers(struct lsm6ds3_data *cdata, + int irq) +{ + return; +} +static inline void st_lsm6ds3_flush_works() +{ + return; +} +#endif /* CONFIG_IIO_TRIGGER */ + +#ifdef CONFIG_PM +int st_lsm6ds3_common_suspend(struct lsm6ds3_data *cdata); +int st_lsm6ds3_common_resume(struct lsm6ds3_data *cdata); +#endif /* CONFIG_PM */ + +#ifdef CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT +int st_lsm6ds3_i2c_master_probe(struct lsm6ds3_data *cdata); +int st_lsm6ds3_i2c_master_exit(struct lsm6ds3_data *cdata); +int st_lsm6ds3_enable_passthrough(struct lsm6ds3_data *cdata, bool enable); +int st_lsm6ds3_enable_accel_dependency(struct lsm6ds3_sensor_data *sdata, + bool enable); +#else /* CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT */ +static inline int st_lsm6ds3_i2c_master_probe(struct lsm6ds3_data *cdata) +{ + return 0; +} +static inline int st_lsm6ds3_i2c_master_exit(struct lsm6ds3_data *cdata) +{ + return 0; +} +#endif /* CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT */ + +#endif /* ST_LSM6DS3_H */ diff --git a/drivers/iio/imu/st_lsm6ds3/st_lsm6ds3_buffer.c b/drivers/iio/imu/st_lsm6ds3/st_lsm6ds3_buffer.c new file mode 100644 index 00000000000..8a7cc5176c2 --- /dev/null +++ b/drivers/iio/imu/st_lsm6ds3/st_lsm6ds3_buffer.c @@ -0,0 +1,448 @@ +/* + * STMicroelectronics lsm6ds3 buffer driver + * + * Copyright 2014 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 "st_lsm6ds3.h" + +#define ST_LSM6DS3_ENABLE_AXIS 0x07 +#define ST_LSM6DS3_FIFO_DIFF_L 0x3a +#define ST_LSM6DS3_FIFO_DIFF_MASK 0x0fff +#define ST_LSM6DS3_FIFO_DATA_OUT_L 0x3e +#define ST_LSM6DS3_FIFO_DATA_OVR_2REGS 0x4000 + +static void st_lsm6ds3_push_data_with_timestamp(struct lsm6ds3_data *cdata, + u8 index, u8 *data, int64_t timestamp) +{ + int i, n = 0; + struct iio_chan_spec const *chs = cdata->indio_dev[index]->channels; + uint16_t bfch, bfchs_out = 0, bfchs_in = 0; + struct lsm6ds3_sensor_data *sdata = iio_priv(cdata->indio_dev[index]); + + for (i = 0; i < sdata->num_data_channels; i++) { + bfch = chs[i].scan_type.storagebits >> 3; + + if (test_bit(i, cdata->indio_dev[index]->active_scan_mask)) { + memcpy(&sdata->buffer_data[bfchs_out], + &data[bfchs_in], bfch); + n++; + bfchs_out += bfch; + } + + bfchs_in += bfch; + } + + if (cdata->indio_dev[index]->scan_timestamp) + *(s64 *)((u8 *)sdata->buffer_data + + ALIGN(bfchs_out, sizeof(s64))) = timestamp; + + iio_push_to_buffers(cdata->indio_dev[index], sdata->buffer_data); +} + +static void st_lsm6ds3_parse_fifo_data(struct lsm6ds3_data *cdata, u16 read_len) +{ + u16 fifo_offset = 0; + u8 gyro_sip, accel_sip; +#ifdef CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT + u8 ext0_sip, ext1_sip; +#endif /* CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT */ + + while (fifo_offset < read_len) { + gyro_sip = cdata->gyro_samples_in_pattern; + accel_sip = cdata->accel_samples_in_pattern; +#ifdef CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT + ext0_sip = cdata->ext0_samples_in_pattern; + ext1_sip = cdata->ext1_samples_in_pattern; +#endif /* CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT */ + + do { + if (gyro_sip > 0) { + if (cdata->gyro_samples_to_discard > 0) + cdata->gyro_samples_to_discard--; + else { + st_lsm6ds3_push_data_with_timestamp( + cdata, ST_INDIO_DEV_GYRO, + &cdata->fifo_data[fifo_offset], + cdata->gyro_timestamp); + } + + cdata->gyro_timestamp += cdata->gyro_deltatime; + fifo_offset += ST_LSM6DS3_FIFO_ELEMENT_LEN_BYTE; + gyro_sip--; + } + + if (accel_sip > 0) { + if (cdata->accel_samples_to_discard > 0) + cdata->accel_samples_to_discard--; + else { + st_lsm6ds3_push_data_with_timestamp( + cdata, ST_INDIO_DEV_ACCEL, + &cdata->fifo_data[fifo_offset], + cdata->accel_timestamp); + } + + cdata->accel_timestamp += + cdata->accel_deltatime; + fifo_offset += ST_LSM6DS3_FIFO_ELEMENT_LEN_BYTE; + accel_sip--; + } + +#ifdef CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT + if (ext0_sip > 0) { + if (cdata->ext_samples_to_discard[0] > 0) + cdata->ext_samples_to_discard[0]--; + else { + st_lsm6ds3_push_data_with_timestamp( + cdata, ST_INDIO_DEV_EXT0, + &cdata->fifo_data[fifo_offset], + cdata->ext0_timestamp); + } + + cdata->ext0_timestamp += cdata->ext0_deltatime; + fifo_offset += ST_LSM6DS3_FIFO_ELEMENT_LEN_BYTE; + ext0_sip--; + } + + if (ext1_sip > 0) { + if (cdata->ext_samples_to_discard[1] > 0) + cdata->ext_samples_to_discard[1]--; + else { + st_lsm6ds3_push_data_with_timestamp( + cdata, ST_INDIO_DEV_EXT1, + &cdata->fifo_data[fifo_offset], + cdata->ext1_timestamp); + } + + cdata->ext1_timestamp += cdata->ext1_deltatime; + fifo_offset += ST_LSM6DS3_FIFO_ELEMENT_LEN_BYTE; + ext1_sip--; + } + + } while ((accel_sip > 0) || (gyro_sip > 0) || + (ext0_sip > 0) || (ext1_sip > 0)); +#else /* CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT */ + } while ((accel_sip > 0) || (gyro_sip > 0)); +#endif /* CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT */ + } + + return; +} + +void st_lsm6ds3_read_fifo(struct lsm6ds3_data *cdata, bool check_fifo_len) +{ + int err; +#if (CONFIG_ST_LSM6DS3_IIO_LIMIT_FIFO > 0) + u16 data_remaining, data_to_read; +#endif /* CONFIG_ST_LSM6DS3_IIO_LIMIT_FIFO */ + u16 read_len = cdata->fifo_threshold, byte_in_pattern; + + if (!cdata->fifo_data) + return; + + if (check_fifo_len) { + err = cdata->tf->read(cdata, ST_LSM6DS3_FIFO_DIFF_L, + 2, (u8 *)&read_len, true); + if (err < 0) + return; + + if (read_len & ST_LSM6DS3_FIFO_DATA_OVR_2REGS) { + dev_err(cdata->dev, + "data fifo overrun, failed to read it.\n"); + return; + } + + read_len &= ST_LSM6DS3_FIFO_DIFF_MASK; + read_len *= ST_LSM6DS3_BYTE_FOR_CHANNEL; + +#ifdef CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT + byte_in_pattern = (cdata->accel_samples_in_pattern + + cdata->gyro_samples_in_pattern + + cdata->ext0_samples_in_pattern + + cdata->ext1_samples_in_pattern) * + ST_LSM6DS3_FIFO_ELEMENT_LEN_BYTE; +#else /* CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT */ + byte_in_pattern = (cdata->accel_samples_in_pattern + + cdata->gyro_samples_in_pattern) * + ST_LSM6DS3_FIFO_ELEMENT_LEN_BYTE; +#endif /* CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT */ + + read_len = (read_len / byte_in_pattern) * byte_in_pattern; + + if (read_len > cdata->fifo_threshold) + read_len = cdata->fifo_threshold; + } + + if (read_len == 0) + return; + +#if (CONFIG_ST_LSM6DS3_IIO_LIMIT_FIFO == 0) + err = cdata->tf->read(cdata, ST_LSM6DS3_FIFO_DATA_OUT_L, + read_len, cdata->fifo_data, true); + if (err < 0) + return; +#else /* CONFIG_ST_LSM6DS3_IIO_LIMIT_FIFO */ + data_remaining = read_len; + + do { + if (data_remaining > CONFIG_ST_LSM6DS3_IIO_LIMIT_FIFO) + data_to_read = CONFIG_ST_LSM6DS3_IIO_LIMIT_FIFO; + else + data_to_read = data_remaining; + + err = cdata->tf->read(cdata, ST_LSM6DS3_FIFO_DATA_OUT_L, + data_to_read, &cdata->fifo_data[read_len - data_remaining], true); + if (err < 0) + return; + + data_remaining -= data_to_read; + } while (data_remaining > 0); +#endif /* CONFIG_ST_LSM6DS3_IIO_LIMIT_FIFO */ + + st_lsm6ds3_parse_fifo_data(cdata, read_len); +} + +static irqreturn_t st_lsm6ds3_step_counter_trigger_handler(int irq, void *p) +{ + int err; + struct timespec ts; + int64_t timestamp = 0; + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct lsm6ds3_sensor_data *sdata = iio_priv(indio_dev); + + if (!sdata->cdata->reset_steps) { + err = sdata->cdata->tf->read(sdata->cdata, + (u8)indio_dev->channels[0].address, + ST_LSM6DS3_BYTE_FOR_CHANNEL, + sdata->buffer_data, true); + if (err < 0) + goto st_lsm6ds3_step_counter_done; + + timestamp = sdata->cdata->timestamp; + } else { + memset(sdata->buffer_data, 0, ST_LSM6DS3_BYTE_FOR_CHANNEL); + get_monotonic_boottime(&ts); + timestamp = timespec_to_ns(&ts); + sdata->cdata->reset_steps = false; + } + + if (indio_dev->scan_timestamp) + *(s64 *)((u8 *)sdata->buffer_data + + ALIGN(ST_LSM6DS3_BYTE_FOR_CHANNEL, + sizeof(s64))) = timestamp; + + iio_push_to_buffers(indio_dev, sdata->buffer_data); + +st_lsm6ds3_step_counter_done: + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; +} + +static inline irqreturn_t st_lsm6ds3_handler_empty(int irq, void *p) +{ + return IRQ_HANDLED; +} + +int st_lsm6ds3_trig_set_state(struct iio_trigger *trig, bool state) +{ + int err; + struct lsm6ds3_sensor_data *sdata; + + sdata = iio_priv(iio_trigger_get_drvdata(trig)); + + err = st_lsm6ds3_set_drdy_irq(sdata, state); + + return err < 0 ? err : 0; +} + +static int st_lsm6ds3_buffer_preenable(struct iio_dev *indio_dev) +{ + int err; + struct lsm6ds3_sensor_data *sdata = iio_priv(indio_dev); + + err = st_lsm6ds3_set_enable(sdata, true); + if (err < 0) + return err; + + err = st_lsm6ds3_reconfigure_fifo(sdata->cdata, true); + if (err < 0) + return err; + + return iio_sw_buffer_preenable(indio_dev); +} + +static int st_lsm6ds3_buffer_postenable(struct iio_dev *indio_dev) +{ + int err; + struct lsm6ds3_sensor_data *sdata = iio_priv(indio_dev); + + if ((1 << sdata->sindex) & ST_LSM6DS3_USE_BUFFER) { + sdata->buffer_data = kmalloc(indio_dev->scan_bytes, GFP_KERNEL); + if (sdata->buffer_data == NULL) + return -ENOMEM; + } + + if ((sdata->sindex == ST_INDIO_DEV_ACCEL) || + (sdata->sindex == ST_INDIO_DEV_GYRO)) { + err = st_lsm6ds3_set_axis_enable(sdata, + (u8)indio_dev->active_scan_mask[0]); + if (err < 0) + goto free_buffer_data; + } + + err = iio_triggered_buffer_postenable(indio_dev); + if (err < 0) + goto free_buffer_data; + + if (sdata->sindex == ST_INDIO_DEV_STEP_COUNTER) { + iio_trigger_poll_chained( + sdata->cdata->trig[ST_INDIO_DEV_STEP_COUNTER], 0); + } + + if (sdata->sindex == ST_INDIO_DEV_SIGN_MOTION) + sdata->cdata->sign_motion_event_ready = true; + + return 0; + +free_buffer_data: + if ((1 << sdata->sindex) & ST_LSM6DS3_USE_BUFFER) + kfree(sdata->buffer_data); + + return err; +} + +static int st_lsm6ds3_buffer_predisable(struct iio_dev *indio_dev) +{ + int err; + struct lsm6ds3_sensor_data *sdata = iio_priv(indio_dev); + + err = iio_triggered_buffer_predisable(indio_dev); + if (err < 0) + return err; + + if ((sdata->sindex == ST_INDIO_DEV_ACCEL) || + (sdata->sindex == ST_INDIO_DEV_GYRO)) { + err = st_lsm6ds3_set_axis_enable(sdata, ST_LSM6DS3_ENABLE_AXIS); + if (err < 0) + return err; + } + + if (sdata->sindex == ST_INDIO_DEV_SIGN_MOTION) + sdata->cdata->sign_motion_event_ready = false; + + err = st_lsm6ds3_set_enable(sdata, false); + if (err < 0) + return err; + + err = st_lsm6ds3_reconfigure_fifo(sdata->cdata, true); + if (err < 0) + return err; + + if ((1 << sdata->sindex) & ST_LSM6DS3_USE_BUFFER) + kfree(sdata->buffer_data); + + return 0; +} + +static const struct iio_buffer_setup_ops st_lsm6ds3_buffer_setup_ops = { + .preenable = &st_lsm6ds3_buffer_preenable, + .postenable = &st_lsm6ds3_buffer_postenable, + .predisable = &st_lsm6ds3_buffer_predisable, +}; + +int st_lsm6ds3_allocate_rings(struct lsm6ds3_data *cdata) +{ + int err; + + err = iio_triggered_buffer_setup(cdata->indio_dev[ST_INDIO_DEV_ACCEL], + &st_lsm6ds3_handler_empty, NULL, + &st_lsm6ds3_buffer_setup_ops); + if (err < 0) + return err; + + err = iio_triggered_buffer_setup(cdata->indio_dev[ST_INDIO_DEV_GYRO], + &st_lsm6ds3_handler_empty, NULL, + &st_lsm6ds3_buffer_setup_ops); + if (err < 0) + goto buffer_cleanup_accel; + + err = iio_triggered_buffer_setup( + cdata->indio_dev[ST_INDIO_DEV_SIGN_MOTION], + &st_lsm6ds3_handler_empty, NULL, + &st_lsm6ds3_buffer_setup_ops); + if (err < 0) + goto buffer_cleanup_gyro; + + err = iio_triggered_buffer_setup( + cdata->indio_dev[ST_INDIO_DEV_STEP_COUNTER], + NULL, + &st_lsm6ds3_step_counter_trigger_handler, + &st_lsm6ds3_buffer_setup_ops); + if (err < 0) + goto buffer_cleanup_sign_motion; + + err = iio_triggered_buffer_setup( + cdata->indio_dev[ST_INDIO_DEV_STEP_DETECTOR], + &st_lsm6ds3_handler_empty, NULL, + &st_lsm6ds3_buffer_setup_ops); + if (err < 0) + goto buffer_cleanup_step_counter; + + err = iio_triggered_buffer_setup( + cdata->indio_dev[ST_INDIO_DEV_TILT], + &st_lsm6ds3_handler_empty, NULL, + &st_lsm6ds3_buffer_setup_ops); + if (err < 0) + goto buffer_cleanup_step_detector; + + return 0; + +buffer_cleanup_step_detector: + iio_triggered_buffer_cleanup( + cdata->indio_dev[ST_INDIO_DEV_STEP_DETECTOR]); +buffer_cleanup_step_counter: + iio_triggered_buffer_cleanup( + cdata->indio_dev[ST_INDIO_DEV_STEP_COUNTER]); +buffer_cleanup_sign_motion: + iio_triggered_buffer_cleanup( + cdata->indio_dev[ST_INDIO_DEV_SIGN_MOTION]); +buffer_cleanup_gyro: + iio_triggered_buffer_cleanup(cdata->indio_dev[ST_INDIO_DEV_GYRO]); +buffer_cleanup_accel: + iio_triggered_buffer_cleanup(cdata->indio_dev[ST_INDIO_DEV_ACCEL]); + return err; +} + +void st_lsm6ds3_deallocate_rings(struct lsm6ds3_data *cdata) +{ + iio_triggered_buffer_cleanup(cdata->indio_dev[ST_INDIO_DEV_TILT]); + iio_triggered_buffer_cleanup( + cdata->indio_dev[ST_INDIO_DEV_STEP_DETECTOR]); + iio_triggered_buffer_cleanup( + cdata->indio_dev[ST_INDIO_DEV_STEP_COUNTER]); + iio_triggered_buffer_cleanup( + cdata->indio_dev[ST_INDIO_DEV_SIGN_MOTION]); + iio_triggered_buffer_cleanup(cdata->indio_dev[ST_INDIO_DEV_ACCEL]); + iio_triggered_buffer_cleanup(cdata->indio_dev[ST_INDIO_DEV_GYRO]); +} + +MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics lsm6ds3 buffer driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/imu/st_lsm6ds3/st_lsm6ds3_core.c b/drivers/iio/imu/st_lsm6ds3/st_lsm6ds3_core.c new file mode 100644 index 00000000000..72881512c1b --- /dev/null +++ b/drivers/iio/imu/st_lsm6ds3/st_lsm6ds3_core.c @@ -0,0 +1,2137 @@ +/* + * STMicroelectronics lsm6ds3 core driver + * + * Copyright 2014 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/gpio.h> +#include <linux/delay.h> +#include <linux/of.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/events.h> +#include <asm/unaligned.h> + +#include <linux/iio/common/st_sensors.h> +#include "st_lsm6ds3.h" + +#define MS_TO_NS(msec) ((msec) * 1000 * 1000) + +#ifndef MAX +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#endif + +#define MIN_BNZ(a, b) (((a) < (b)) ? ((a == 0) ? \ + (b) : (a)) : ((b == 0) ? \ + (a) : (b))) + +/* COMMON VALUES FOR ACCEL-GYRO SENSORS */ +#define ST_LSM6DS3_WAI_ADDRESS 0x0f +#define ST_LSM6DS3_WAI_EXP 0x69 +#define ST_LSM6DS3_AXIS_EN_MASK 0x38 +#define ST_LSM6DS3_INT1_ADDR 0x0d +#define ST_LSM6DS3_INT2_ADDR 0x0e +#define ST_LSM6DS3_MD1_ADDR 0x5e +#define ST_LSM6DS3_ODR_LIST_NUM 5 +#define ST_LSM6DS3_ODR_POWER_OFF_VAL 0x00 +#define ST_LSM6DS3_ODR_26HZ_VAL 0x02 +#define ST_LSM6DS3_ODR_52HZ_VAL 0x03 +#define ST_LSM6DS3_ODR_104HZ_VAL 0x04 +#define ST_LSM6DS3_ODR_208HZ_VAL 0x05 +#define ST_LSM6DS3_ODR_416HZ_VAL 0x06 +#define ST_LSM6DS3_FS_LIST_NUM 4 +#define ST_LSM6DS3_BDU_ADDR 0x12 +#define ST_LSM6DS3_BDU_MASK 0x40 +#define ST_LSM6DS3_EN_BIT 0x01 +#define ST_LSM6DS3_DIS_BIT 0x00 +#define ST_LSM6DS3_FUNC_EN_ADDR 0x19 +#define ST_LSM6DS3_FUNC_EN_MASK 0x04 +#define ST_LSM6DS3_FUNC_CFG_ACCESS_ADDR 0x01 +#define ST_LSM6DS3_FUNC_CFG_ACCESS_MASK 0x01 +#define ST_LSM6DS3_FUNC_CFG_ACCESS_MASK2 0x04 +#define ST_LSM6DS3_FUNC_CFG_REG2_MASK 0x80 +#define ST_LSM6DS3_FUNC_CFG_START1_ADDR 0x62 +#define ST_LSM6DS3_FUNC_CFG_START2_ADDR 0x63 +#define ST_LSM6DS3_PASS_THROUGH_MODE_ADDR 0x1a +#define ST_LSM6DS3_PASS_THROUGH_MODE_MASK 0x04 +#define ST_LSM6DS3_INTER_PULLUP_ADDR 0x1a +#define ST_LSM6DS3_INTER_PULLUP_MASK 0x08 +#define ST_LSM6DS3_SENSORHUB_ADDR 0x1a +#define ST_LSM6DS3_SENSORHUB_MASK 0x01 +#define ST_LSM6DS3_STARTCONFIG_ADDR 0x1a +#define ST_LSM6DS3_STARTCONFIG_MASK 0x10 +#define ST_LSM6DS3_SELFTEST_ADDR 0x14 +#define ST_LSM6DS3_SELFTEST_ACCEL_MASK 0x03 +#define ST_LSM6DS3_SELFTEST_GYRO_MASK 0x0c +#define ST_LSM6DS3_SELF_TEST_DISABLED_VAL 0x00 +#define ST_LSM6DS3_SELF_TEST_POS_SIGN_VAL 0x01 +#define ST_LSM6DS3_SELF_TEST_NEG_ACCEL_SIGN_VAL 0x02 +#define ST_LSM6DS3_SELF_TEST_NEG_GYRO_SIGN_VAL 0x03 +#define ST_LSM6DS3_LIR_ADDR 0x58 +#define ST_LSM6DS3_LIR_MASK 0x01 +#define ST_LSM6DS3_TIMER_EN_ADDR 0x58 +#define ST_LSM6DS3_TIMER_EN_MASK 0x80 +#define ST_LSM6DS3_PEDOMETER_EN_ADDR 0x58 +#define ST_LSM6DS3_PEDOMETER_EN_MASK 0x40 +#define ST_LSM6DS3_INT2_ON_INT1_ADDR 0x13 +#define ST_LSM6DS3_INT2_ON_INT1_MASK 0x20 +#define ST_LSM6DS3_MIN_DURATION_MS 1638 +#define ST_LSM6DS3_ROUNDING_ADDR 0x16 +#define ST_LSM6DS3_ROUNDING_MASK 0x04 +#define ST_LSM6DS3_FIFO_MODE_ADDR 0x0a +#define ST_LSM6DS3_FIFO_MODE_MASK 0x07 +#define ST_LSM6DS3_FIFO_MODE_BYPASS 0x00 +#define ST_LSM6DS3_FIFO_MODE_CONTINUOS 0x06 +#define ST_LSM6DS3_FIFO_THRESHOLD_IRQ_MASK 0x08 +#define ST_LSM6DS3_FIFO_ODR_ADDR 0x0a +#define ST_LSM6DS3_FIFO_ODR_MASK 0x78 +#define ST_LSM6DS3_FIFO_ODR_MAX 0x08 +#define ST_LSM6DS3_FIFO_ODR_OFF 0x00 +#define ST_LSM6DS3_FIFO_DECIMATOR_ADDR 0x08 +#define ST_LSM6DS3_FIFO_ACCEL_DECIMATOR_MASK 0x07 +#define ST_LSM6DS3_FIFO_GYRO_DECIMATOR_MASK 0x38 +#define ST_LSM6DS3_FIFO_DECIMATOR2_ADDR 0x09 +#define ST_LSM6DS3_FIFO_DS3_DECIMATOR_MASK 0x07 +#define ST_LSM6DS3_FIFO_DS4_DECIMATOR_MASK 0x38 +#define ST_LSM6DS3_FIFO_THR_L_ADDR 0x06 +#define ST_LSM6DS3_FIFO_THR_H_ADDR 0x07 +#define ST_LSM6DS3_FIFO_THR_H_MASK 0x0f +#define ST_LSM6DS3_FIFO_THR_IRQ_MASK 0x08 +#define ST_LSM6DS3_RESET_ADDR 0x12 +#define ST_LSM6DS3_RESET_MASK 0x01 +#define ST_LSM6DS3_MAX_FIFO_SIZE 8192 +#define ST_LSM6DS3_MAX_FIFO_LENGHT (ST_LSM6DS3_MAX_FIFO_SIZE / \ + ST_LSM6DS3_FIFO_ELEMENT_LEN_BYTE) + +/* CUSTOM VALUES FOR ACCEL SENSOR */ +#define ST_LSM6DS3_ACCEL_ODR_ADDR 0x10 +#define ST_LSM6DS3_ACCEL_ODR_MASK 0xf0 +#define ST_LSM6DS3_ACCEL_FS_ADDR 0x10 +#define ST_LSM6DS3_ACCEL_FS_MASK 0x0c +#define ST_LSM6DS3_ACCEL_FS_2G_VAL 0x00 +#define ST_LSM6DS3_ACCEL_FS_4G_VAL 0x02 +#define ST_LSM6DS3_ACCEL_FS_8G_VAL 0x03 +#define ST_LSM6DS3_ACCEL_FS_16G_VAL 0x01 +#define ST_LSM6DS3_ACCEL_FS_2G_GAIN IIO_G_TO_M_S_2(61) +#define ST_LSM6DS3_ACCEL_FS_4G_GAIN IIO_G_TO_M_S_2(122) +#define ST_LSM6DS3_ACCEL_FS_8G_GAIN IIO_G_TO_M_S_2(244) +#define ST_LSM6DS3_ACCEL_FS_16G_GAIN IIO_G_TO_M_S_2(488) +#define ST_LSM6DS3_ACCEL_OUT_X_L_ADDR 0x28 +#define ST_LSM6DS3_ACCEL_OUT_Y_L_ADDR 0x2a +#define ST_LSM6DS3_ACCEL_OUT_Z_L_ADDR 0x2c +#define ST_LSM6DS3_ACCEL_AXIS_EN_ADDR 0x18 +#define ST_LSM6DS3_ACCEL_STD 3 + +/* CUSTOM VALUES FOR GYRO SENSOR */ +#define ST_LSM6DS3_GYRO_ODR_ADDR 0x11 +#define ST_LSM6DS3_GYRO_ODR_MASK 0xf0 +#define ST_LSM6DS3_GYRO_FS_ADDR 0x11 +#define ST_LSM6DS3_GYRO_FS_MASK 0x0c +#define ST_LSM6DS3_GYRO_FS_245_VAL 0x00 +#define ST_LSM6DS3_GYRO_FS_500_VAL 0x01 +#define ST_LSM6DS3_GYRO_FS_1000_VAL 0x02 +#define ST_LSM6DS3_GYRO_FS_2000_VAL 0x03 +#define ST_LSM6DS3_GYRO_FS_245_GAIN IIO_DEGREE_TO_RAD(4375) +#define ST_LSM6DS3_GYRO_FS_500_GAIN IIO_DEGREE_TO_RAD(8750) +#define ST_LSM6DS3_GYRO_FS_1000_GAIN IIO_DEGREE_TO_RAD(17500) +#define ST_LSM6DS3_GYRO_FS_2000_GAIN IIO_DEGREE_TO_RAD(70000) +#define ST_LSM6DS3_GYRO_OUT_X_L_ADDR 0x22 +#define ST_LSM6DS3_GYRO_OUT_Y_L_ADDR 0x24 +#define ST_LSM6DS3_GYRO_OUT_Z_L_ADDR 0x26 +#define ST_LSM6DS3_GYRO_AXIS_EN_ADDR 0x19 +#define ST_LSM6DS3_GYRO_STD 6 + +/* CUSTOM VALUES FOR SIGNIFICANT MOTION SENSOR */ +#define ST_LSM6DS3_SIGN_MOTION_EN_ADDR 0x19 +#define ST_LSM6DS3_SIGN_MOTION_EN_MASK 0x01 + +/* CUSTOM VALUES FOR STEP DETECTOR SENSOR */ +#define ST_LSM6DS3_STEP_DETECTOR_DRDY_IRQ_MASK 0x80 + +/* CUSTOM VALUES FOR STEP COUNTER SENSOR */ +#define ST_LSM6DS3_STEP_COUNTER_DRDY_IRQ_MASK 0x80 +#define ST_LSM6DS3_STEP_COUNTER_OUT_L_ADDR 0x4b +#define ST_LSM6DS3_STEP_COUNTER_RES_ADDR 0x19 +#define ST_LSM6DS3_STEP_COUNTER_RES_MASK 0x06 +#define ST_LSM6DS3_STEP_COUNTER_RES_ALL_EN 0x03 +#define ST_LSM6DS3_STEP_COUNTER_RES_FUNC_EN 0x02 +#define ST_LSM6DS3_STEP_COUNTER_DURATION_ADDR 0x15 + +/* CUSTOM VALUES FOR TILT SENSOR */ +#define ST_LSM6DS3_TILT_EN_ADDR 0x58 +#define ST_LSM6DS3_TILT_EN_MASK 0x20 +#define ST_LSM6DS3_TILT_DRDY_IRQ_MASK 0x02 + +#define ST_LSM6DS3_ACCEL_SUFFIX_NAME "accel" +#define ST_LSM6DS3_GYRO_SUFFIX_NAME "gyro" +#define ST_LSM6DS3_STEP_COUNTER_SUFFIX_NAME "step_c" +#define ST_LSM6DS3_STEP_DETECTOR_SUFFIX_NAME "step_d" +#define ST_LSM6DS3_SIGN_MOTION_SUFFIX_NAME "sign_motion" +#define ST_LSM6DS3_TILT_SUFFIX_NAME "tilt" + +#define ST_LSM6DS3_DEV_ATTR_SAMP_FREQ() \ + IIO_DEV_ATTR_SAMP_FREQ(S_IWUSR | S_IRUGO, \ + st_lsm6ds3_sysfs_get_sampling_frequency, \ + st_lsm6ds3_sysfs_set_sampling_frequency) + +#define ST_LSM6DS3_DEV_ATTR_SAMP_FREQ_AVAIL() \ + IIO_DEV_ATTR_SAMP_FREQ_AVAIL( \ + st_lsm6ds3_sysfs_sampling_frequency_avail) + +#define ST_LSM6DS3_DEV_ATTR_SCALE_AVAIL(name) \ + IIO_DEVICE_ATTR(name, S_IRUGO, \ + st_lsm6ds3_sysfs_scale_avail, NULL , 0); + +static struct st_lsm6ds3_selftest_table { + char *string_mode; + u8 accel_value; + u8 gyro_value; + u8 accel_mask; + u8 gyro_mask; +} st_lsm6ds3_selftest_table[] = { + [0] = { + .string_mode = "disabled", + .accel_value = ST_LSM6DS3_SELF_TEST_DISABLED_VAL, + .gyro_value = ST_LSM6DS3_SELF_TEST_DISABLED_VAL, + }, + [1] = { + .string_mode = "positive-sign", + .accel_value = ST_LSM6DS3_SELF_TEST_POS_SIGN_VAL, + .gyro_value = ST_LSM6DS3_SELF_TEST_POS_SIGN_VAL + }, + [2] = { + .string_mode = "negative-sign", + .accel_value = ST_LSM6DS3_SELF_TEST_NEG_ACCEL_SIGN_VAL, + .gyro_value = ST_LSM6DS3_SELF_TEST_NEG_GYRO_SIGN_VAL + }, +}; + +struct st_lsm6ds3_odr_reg { + unsigned int hz; + u8 value; +}; + +static struct st_lsm6ds3_odr_table { + u8 addr[2]; + u8 mask[2]; + struct st_lsm6ds3_odr_reg odr_avl[ST_LSM6DS3_ODR_LIST_NUM]; +} st_lsm6ds3_odr_table = { + .addr[ST_INDIO_DEV_ACCEL] = ST_LSM6DS3_ACCEL_ODR_ADDR, + .mask[ST_INDIO_DEV_ACCEL] = ST_LSM6DS3_ACCEL_ODR_MASK, + .addr[ST_INDIO_DEV_GYRO] = ST_LSM6DS3_GYRO_ODR_ADDR, + .mask[ST_INDIO_DEV_GYRO] = ST_LSM6DS3_GYRO_ODR_MASK, + .odr_avl[0] = { .hz = 26, .value = ST_LSM6DS3_ODR_26HZ_VAL }, + .odr_avl[1] = { .hz = 52, .value = ST_LSM6DS3_ODR_52HZ_VAL }, + .odr_avl[2] = { .hz = 104, .value = ST_LSM6DS3_ODR_104HZ_VAL }, + .odr_avl[3] = { .hz = 208, .value = ST_LSM6DS3_ODR_208HZ_VAL }, + .odr_avl[4] = { .hz = 416, .value = ST_LSM6DS3_ODR_416HZ_VAL }, +}; + +struct st_lsm6ds3_fs_reg { + unsigned int gain; + u8 value; +}; + +static struct st_lsm6ds3_fs_table { + u8 addr; + u8 mask; + struct st_lsm6ds3_fs_reg fs_avl[ST_LSM6DS3_FS_LIST_NUM]; +} st_lsm6ds3_fs_table[ST_INDIO_DEV_NUM] = { + [ST_INDIO_DEV_ACCEL] = { + .addr = ST_LSM6DS3_ACCEL_FS_ADDR, + .mask = ST_LSM6DS3_ACCEL_FS_MASK, + .fs_avl[0] = { .gain = ST_LSM6DS3_ACCEL_FS_2G_GAIN, + .value = ST_LSM6DS3_ACCEL_FS_2G_VAL }, + .fs_avl[1] = { .gain = ST_LSM6DS3_ACCEL_FS_4G_GAIN, + .value = ST_LSM6DS3_ACCEL_FS_4G_VAL }, + .fs_avl[2] = { .gain = ST_LSM6DS3_ACCEL_FS_8G_GAIN, + .value = ST_LSM6DS3_ACCEL_FS_8G_VAL }, + .fs_avl[3] = { .gain = ST_LSM6DS3_ACCEL_FS_16G_GAIN, + .value = ST_LSM6DS3_ACCEL_FS_16G_VAL }, + }, + [ST_INDIO_DEV_GYRO] = { + .addr = ST_LSM6DS3_GYRO_FS_ADDR, + .mask = ST_LSM6DS3_GYRO_FS_MASK, + .fs_avl[0] = { .gain = ST_LSM6DS3_GYRO_FS_245_GAIN, + .value = ST_LSM6DS3_GYRO_FS_245_VAL }, + .fs_avl[1] = { .gain = ST_LSM6DS3_GYRO_FS_500_GAIN, + .value = ST_LSM6DS3_GYRO_FS_500_VAL }, + .fs_avl[2] = { .gain = ST_LSM6DS3_GYRO_FS_1000_GAIN, + .value = ST_LSM6DS3_GYRO_FS_1000_VAL }, + .fs_avl[3] = { .gain = ST_LSM6DS3_GYRO_FS_2000_GAIN, + .value = ST_LSM6DS3_GYRO_FS_2000_VAL }, + } +}; + +static const struct iio_chan_spec st_lsm6ds3_accel_ch[] = { + ST_LSM6DS3_LSM_CHANNELS(IIO_ACCEL, 1, 0, IIO_MOD_X, IIO_LE, + 16, 16, ST_LSM6DS3_ACCEL_OUT_X_L_ADDR, 's'), + ST_LSM6DS3_LSM_CHANNELS(IIO_ACCEL, 1, 1, IIO_MOD_Y, IIO_LE, + 16, 16, ST_LSM6DS3_ACCEL_OUT_Y_L_ADDR, 's'), + ST_LSM6DS3_LSM_CHANNELS(IIO_ACCEL, 1, 2, IIO_MOD_Z, IIO_LE, + 16, 16, ST_LSM6DS3_ACCEL_OUT_Z_L_ADDR, 's'), + IIO_CHAN_SOFT_TIMESTAMP(3) +}; + +static const struct iio_chan_spec st_lsm6ds3_gyro_ch[] = { + ST_LSM6DS3_LSM_CHANNELS(IIO_ANGL_VEL, 1, 0, IIO_MOD_X, IIO_LE, + 16, 16, ST_LSM6DS3_GYRO_OUT_X_L_ADDR, 's'), + ST_LSM6DS3_LSM_CHANNELS(IIO_ANGL_VEL, 1, 1, IIO_MOD_Y, IIO_LE, + 16, 16, ST_LSM6DS3_GYRO_OUT_Y_L_ADDR, 's'), + ST_LSM6DS3_LSM_CHANNELS(IIO_ANGL_VEL, 1, 2, IIO_MOD_Z, IIO_LE, + 16, 16, ST_LSM6DS3_GYRO_OUT_Z_L_ADDR, 's'), + IIO_CHAN_SOFT_TIMESTAMP(3) +}; + +static const struct iio_chan_spec st_lsm6ds3_sign_motion_ch[] = { + { + .type = IIO_SIGN_MOTION, + .channel = 0, + .modified = 0, + .event_mask = IIO_EV_BIT(IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING), + }, + IIO_CHAN_SOFT_TIMESTAMP(1) +}; + +static const struct iio_chan_spec st_lsm6ds3_step_c_ch[] = { + { + .type = IIO_STEP_COUNTER, + .modified = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .address = ST_LSM6DS3_STEP_COUNTER_OUT_L_ADDR, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(1) +}; + +static const struct iio_chan_spec st_lsm6ds3_step_d_ch[] = { + { + .type = IIO_STEP_DETECTOR, + .channel = 0, + .modified = 0, + .event_mask = IIO_EV_BIT(IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING), + }, + IIO_CHAN_SOFT_TIMESTAMP(1) +}; + +static const struct iio_chan_spec st_lsm6ds3_tilt_ch[] = { + { + .type = IIO_TILT, + .channel = 0, + .modified = 0, + .event_mask = IIO_EV_BIT(IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING), + }, + IIO_CHAN_SOFT_TIMESTAMP(1) +}; + +int st_lsm6ds3_write_data_with_mask(struct lsm6ds3_data *cdata, + u8 reg_addr, u8 mask, u8 data, bool b_lock) +{ + int err; + u8 new_data = 0x00, old_data = 0x00; + + err = cdata->tf->read(cdata, reg_addr, 1, &old_data, b_lock); + if (err < 0) + return err; + + new_data = ((old_data & (~mask)) | ((data << __ffs(mask)) & mask)); + + if (new_data == old_data) + return 1; + + return cdata->tf->write(cdata, reg_addr, 1, &new_data, b_lock); +} +EXPORT_SYMBOL(st_lsm6ds3_write_data_with_mask); + +#ifdef CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT +static int st_lsm6ds3_enable_sensor_hub(struct lsm6ds3_data *cdata, bool enable) +{ + int err; + + if (enable) { + if (cdata->sensors_enabled & ST_LSM6DS3_EXT_SENSORS) { + err = st_lsm6ds3_write_data_with_mask(cdata, + ST_LSM6DS3_STARTCONFIG_ADDR, + ST_LSM6DS3_STARTCONFIG_MASK, + ST_LSM6DS3_DIS_BIT, true); + if (err < 0) + return err; + } + + if (cdata->sensors_enabled & ST_LSM6DS3_EXTRA_DEPENDENCY) { + err = st_lsm6ds3_write_data_with_mask(cdata, + ST_LSM6DS3_FUNC_EN_ADDR, + ST_LSM6DS3_FUNC_EN_MASK, + ST_LSM6DS3_EN_BIT, true); + if (err < 0) + return err; + } + + if (cdata->sensors_enabled & ST_LSM6DS3_EXT_SENSORS) { + err = st_lsm6ds3_write_data_with_mask(cdata, + ST_LSM6DS3_SENSORHUB_ADDR, + ST_LSM6DS3_SENSORHUB_MASK, + ST_LSM6DS3_EN_BIT, true); + if (err < 0) + return err; + } + } else { + err = st_lsm6ds3_write_data_with_mask(cdata, + ST_LSM6DS3_STARTCONFIG_ADDR, + ST_LSM6DS3_STARTCONFIG_MASK, + ST_LSM6DS3_EN_BIT, true); + if (err < 0) + return err; + + usleep_range(1500, 4000); + + err = st_lsm6ds3_write_data_with_mask(cdata, + ST_LSM6DS3_SENSORHUB_ADDR, + ST_LSM6DS3_SENSORHUB_MASK, + ST_LSM6DS3_DIS_BIT, true); + if (err < 0) + return err; + + err = st_lsm6ds3_write_data_with_mask(cdata, + ST_LSM6DS3_FUNC_EN_ADDR, + ST_LSM6DS3_FUNC_EN_MASK, + ST_LSM6DS3_DIS_BIT, true); + if (err < 0) + return err; + } + + return 0; +} + +int st_lsm6ds3_enable_passthrough(struct lsm6ds3_data *cdata, bool enable) +{ + int err; + u8 reg_value; + + if (enable) + reg_value = ST_LSM6DS3_EN_BIT; + else + reg_value = ST_LSM6DS3_DIS_BIT; + + if (enable) { + err = st_lsm6ds3_enable_sensor_hub(cdata, false); + if (err < 0) + return err; + +#ifdef CONFIG_ST_LSM6DS3_ENABLE_INTERNAL_PULLUP + err = st_lsm6ds3_write_data_with_mask(cdata, + ST_LSM6DS3_INTER_PULLUP_ADDR, + ST_LSM6DS3_INTER_PULLUP_MASK, + ST_LSM6DS3_DIS_BIT, true); + if (err < 0) + return err; +#endif /* CONFIG_ST_LSM6DS3_ENABLE_INTERNAL_PULLUP */ + } + + err = st_lsm6ds3_write_data_with_mask(cdata, + ST_LSM6DS3_PASS_THROUGH_MODE_ADDR, + ST_LSM6DS3_PASS_THROUGH_MODE_MASK, + reg_value, enable); + if (err < 0) + return err; + + if (!enable) { +#ifdef CONFIG_ST_LSM6DS3_ENABLE_INTERNAL_PULLUP + err = st_lsm6ds3_write_data_with_mask(cdata, + ST_LSM6DS3_INTER_PULLUP_ADDR, + ST_LSM6DS3_INTER_PULLUP_MASK, + ST_LSM6DS3_EN_BIT, true); + if (err < 0) + return err; +#endif /* CONFIG_ST_LSM6DS3_ENABLE_INTERNAL_PULLUP */ + + err = st_lsm6ds3_enable_sensor_hub(cdata, true); + if (err < 0) + return err; + } + + return 0; +} +EXPORT_SYMBOL(st_lsm6ds3_enable_passthrough); +#endif /* CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT */ + +static int st_lsm6ds3_set_fifo_enable(struct lsm6ds3_data *cdata, bool status) +{ + int err; + u8 reg_value; + struct timespec ts; + + if (status) + reg_value = ST_LSM6DS3_FIFO_ODR_MAX; + else + reg_value = ST_LSM6DS3_FIFO_ODR_OFF; + + err = st_lsm6ds3_write_data_with_mask(cdata, + ST_LSM6DS3_FIFO_ODR_ADDR, + ST_LSM6DS3_FIFO_ODR_MASK, + reg_value, true); + if (err < 0) + return err; + + get_monotonic_boottime(&ts); + cdata->gyro_timestamp = timespec_to_ns(&ts); + cdata->accel_timestamp = cdata->gyro_timestamp; +#ifdef CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT + cdata->ext0_timestamp = cdata->gyro_timestamp; + cdata->ext1_timestamp = cdata->gyro_timestamp; +#endif /* CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT */ + + return 0; +} + +int st_lsm6ds3_set_fifo_mode(struct lsm6ds3_data *cdata, enum fifo_mode fm) +{ + int err; + u8 reg_value; + bool enable_fifo; + + switch (fm) { + case BYPASS: + reg_value = ST_LSM6DS3_FIFO_MODE_BYPASS; + enable_fifo = false; + break; + case CONTINUOS: + reg_value = ST_LSM6DS3_FIFO_MODE_CONTINUOS; + enable_fifo = true; + break; + default: + return -EINVAL; + } + + err = st_lsm6ds3_set_fifo_enable(cdata, enable_fifo); + if (err < 0) + return err; + + return st_lsm6ds3_write_data_with_mask(cdata, ST_LSM6DS3_FIFO_MODE_ADDR, + ST_LSM6DS3_FIFO_MODE_MASK, reg_value, true); +} +EXPORT_SYMBOL(st_lsm6ds3_set_fifo_mode); + +#ifdef CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT +static int st_lsm6ds3_force_accel_odr(struct lsm6ds3_sensor_data *sdata, + unsigned int odr) +{ + int i; + + for (i = 0; i < ST_LSM6DS3_ODR_LIST_NUM; i++) { + if (st_lsm6ds3_odr_table.odr_avl[i].hz == odr) + break; + } + if (i == ST_LSM6DS3_ODR_LIST_NUM) + return -EINVAL; + + return st_lsm6ds3_write_data_with_mask(sdata->cdata, + st_lsm6ds3_odr_table.addr[sdata->sindex], + st_lsm6ds3_odr_table.mask[sdata->sindex], + st_lsm6ds3_odr_table.odr_avl[i].value, true); +} +#endif /* CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT */ + +int st_lsm6ds3_set_fifo_decimators_and_threshold(struct lsm6ds3_data *cdata) +{ + int err; + struct iio_dev *indio_dev; + u16 min_num_pattern, max_num_pattern; + unsigned int min_odr = 416, max_odr = 0; + u8 accel_decimator = 0, gyro_decimator = 0; + u16 num_pattern_accel = 0, num_pattern_gyro = 0; + struct lsm6ds3_sensor_data *sdata_accel, *sdata_gyro; + u16 fifo_len, fifo_threshold, fifo_len_accel = 0, fifo_len_gyro = 0; +#ifdef CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT + int i; + bool force_accel_odr = false; + u8 ext0_decimator = 0, ext1_decimator = 0; + u16 num_pattern_ext1 = 0, fifo_len_ext1 = 0; + u16 num_pattern_ext0 = 0, fifo_len_ext0 = 0; + struct lsm6ds3_sensor_data *sdata_ext0 = NULL, *sdata_ext1 = NULL; +#endif /* CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT */ + + indio_dev = cdata->indio_dev[ST_INDIO_DEV_ACCEL]; + sdata_accel = iio_priv(indio_dev); + if ((1 << sdata_accel->sindex) & cdata->sensors_enabled) { + if (min_odr > sdata_accel->c_odr) + min_odr = sdata_accel->c_odr; + + if (max_odr < sdata_accel->c_odr) + max_odr = sdata_accel->c_odr; + + fifo_len_accel = (indio_dev->buffer->length / 2); + } + + indio_dev = cdata->indio_dev[ST_INDIO_DEV_GYRO]; + sdata_gyro = iio_priv(indio_dev); + if ((1 << sdata_gyro->sindex) & cdata->sensors_enabled) { + if (min_odr > sdata_gyro->c_odr) + min_odr = sdata_gyro->c_odr; + + if (max_odr < sdata_gyro->c_odr) + max_odr = sdata_gyro->c_odr; + + fifo_len_gyro = (indio_dev->buffer->length / 2); + } + +#ifdef CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT + if (cdata->ext0_available) { + indio_dev = cdata->indio_dev[ST_INDIO_DEV_EXT0]; + sdata_ext0 = iio_priv(indio_dev); + if ((1 << sdata_ext0->sindex) & cdata->sensors_enabled) { + if (min_odr > sdata_ext0->c_odr) + min_odr = sdata_ext0->c_odr; + + if (max_odr < sdata_ext0->c_odr) { + force_accel_odr = true; + max_odr = sdata_ext0->c_odr; + } + + fifo_len_ext0 = (indio_dev->buffer->length / 2); + } + } + + if (cdata->ext1_available) { + indio_dev = cdata->indio_dev[ST_INDIO_DEV_EXT1]; + sdata_ext1 = iio_priv(indio_dev); + if ((1 << sdata_ext1->sindex) & cdata->sensors_enabled) { + if (min_odr > sdata_ext1->c_odr) + min_odr = sdata_ext1->c_odr; + + if (max_odr < sdata_ext1->c_odr) { + force_accel_odr = true; + max_odr = sdata_ext1->c_odr; + } + + fifo_len_ext1 = (indio_dev->buffer->length / 2); + } + } + + if (force_accel_odr) { + err = st_lsm6ds3_force_accel_odr(sdata_accel, max_odr); + if (err < 0) + return err; + } else { + for (i = 0; i < ST_LSM6DS3_ODR_LIST_NUM; i++) { + if (st_lsm6ds3_odr_table.odr_avl[i].hz == + sdata_accel->c_odr) + break; + } + if (i == ST_LSM6DS3_ODR_LIST_NUM) + return -EINVAL; + + if (cdata->sensors_enabled & (1 << sdata_accel->sindex)) { + cdata->accel_samples_to_discard = ST_LSM6DS3_ACCEL_STD; + + err = st_lsm6ds3_write_data_with_mask(cdata, + st_lsm6ds3_odr_table.addr[sdata_accel->sindex], + st_lsm6ds3_odr_table.mask[sdata_accel->sindex], + st_lsm6ds3_odr_table.odr_avl[i].value, true); + if (err < 0) + return err; + } + } +#endif /* CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT */ + + if ((1 << sdata_accel->sindex) & cdata->sensors_enabled) { + cdata->accel_samples_in_pattern = + (sdata_accel->c_odr / min_odr); + num_pattern_accel = MAX(fifo_len_accel / + cdata->accel_samples_in_pattern, 1); + cdata->accel_deltatime = (1000000000ULL / sdata_accel->c_odr); + accel_decimator = max_odr / sdata_accel->c_odr; + } else + cdata->accel_samples_in_pattern = 0; + + err = st_lsm6ds3_write_data_with_mask(cdata, + ST_LSM6DS3_FIFO_DECIMATOR_ADDR, + ST_LSM6DS3_FIFO_ACCEL_DECIMATOR_MASK, + accel_decimator, true); + if (err < 0) + return err; + + if ((1 << sdata_gyro->sindex) & cdata->sensors_enabled) { + cdata->gyro_samples_in_pattern = (sdata_gyro->c_odr / min_odr); + num_pattern_gyro = MAX(fifo_len_gyro / + cdata->gyro_samples_in_pattern, 1); + cdata->gyro_deltatime = (1000000000ULL / sdata_gyro->c_odr); + gyro_decimator = max_odr / sdata_gyro->c_odr; + } else + cdata->gyro_samples_in_pattern = 0; + + err = st_lsm6ds3_write_data_with_mask(cdata, + ST_LSM6DS3_FIFO_DECIMATOR_ADDR, + ST_LSM6DS3_FIFO_GYRO_DECIMATOR_MASK, + gyro_decimator, true); + if (err < 0) + return err; + +#ifdef CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT + if (cdata->ext0_available) { + if ((1 << sdata_ext0->sindex) & cdata->sensors_enabled) { + cdata->ext0_samples_in_pattern = + (sdata_ext0->c_odr / min_odr); + num_pattern_ext0 = MAX(fifo_len_ext0 / + cdata->ext0_samples_in_pattern, 1); + cdata->ext0_deltatime = + (1000000000ULL / sdata_ext0->c_odr); + ext0_decimator = max_odr / sdata_ext0->c_odr; + } else + cdata->ext0_samples_in_pattern = 0; + + err = st_lsm6ds3_write_data_with_mask(cdata, + ST_LSM6DS3_FIFO_DECIMATOR2_ADDR, + ST_LSM6DS3_FIFO_DS3_DECIMATOR_MASK, + ext0_decimator, true); + if (err < 0) + return err; + } + + if (cdata->ext1_available) { + if ((1 << sdata_ext1->sindex) & cdata->sensors_enabled) { + cdata->ext1_samples_in_pattern = + (sdata_ext1->c_odr / min_odr); + num_pattern_ext1 = MAX(fifo_len_ext1 / + cdata->ext1_samples_in_pattern, 1); + cdata->ext1_deltatime = + (1000000000ULL / sdata_ext1->c_odr); + ext1_decimator = max_odr / sdata_ext1->c_odr; + } else + cdata->ext1_samples_in_pattern = 0; + + err = st_lsm6ds3_write_data_with_mask(cdata, + ST_LSM6DS3_FIFO_DECIMATOR2_ADDR, + ST_LSM6DS3_FIFO_DS4_DECIMATOR_MASK, + ext1_decimator, true); + if (err < 0) + return err; + } +#endif /* CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT */ + +#ifdef CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT + min_num_pattern = MIN_BNZ(MIN_BNZ(MIN_BNZ(num_pattern_gyro, + num_pattern_accel), num_pattern_ext0), num_pattern_ext1); +#else /* CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT */ + min_num_pattern = MIN_BNZ(num_pattern_gyro, num_pattern_accel); +#endif /* CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT */ + +#ifdef CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT + if ((cdata->accel_samples_in_pattern + + cdata->gyro_samples_in_pattern + + cdata->ext0_samples_in_pattern + + cdata->ext1_samples_in_pattern) > 0) { + max_num_pattern = ST_LSM6DS3_MAX_FIFO_SIZE / + ((cdata->accel_samples_in_pattern + + cdata->gyro_samples_in_pattern + + cdata->ext0_samples_in_pattern + + cdata->ext1_samples_in_pattern) * + ST_LSM6DS3_FIFO_ELEMENT_LEN_BYTE); + + if (min_num_pattern > max_num_pattern) + min_num_pattern = max_num_pattern; + } + + fifo_len = (cdata->accel_samples_in_pattern + + cdata->gyro_samples_in_pattern + + cdata->ext0_samples_in_pattern + + cdata->ext1_samples_in_pattern) * + min_num_pattern * ST_LSM6DS3_FIFO_ELEMENT_LEN_BYTE; +#else /* CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT */ + if ((cdata->accel_samples_in_pattern + + cdata->gyro_samples_in_pattern) > 0) { + max_num_pattern = ST_LSM6DS3_MAX_FIFO_SIZE / + ((cdata->accel_samples_in_pattern + + cdata->gyro_samples_in_pattern) * + ST_LSM6DS3_FIFO_ELEMENT_LEN_BYTE); + + if (min_num_pattern > max_num_pattern) + min_num_pattern = max_num_pattern; + } + + fifo_len = (cdata->accel_samples_in_pattern + + cdata->gyro_samples_in_pattern) * + min_num_pattern * ST_LSM6DS3_FIFO_ELEMENT_LEN_BYTE; +#endif /* CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT */ + + if (fifo_len > 0) { + fifo_threshold = fifo_len / 2; + + err = cdata->tf->write(cdata, ST_LSM6DS3_FIFO_THR_L_ADDR, + 1, (u8 *)&fifo_threshold, true); + if (err < 0) + return err; + + err = st_lsm6ds3_write_data_with_mask(cdata, + ST_LSM6DS3_FIFO_THR_H_ADDR, + ST_LSM6DS3_FIFO_THR_H_MASK, + *(((u8 *)&fifo_threshold) + 1), true); + if (err < 0) + return err; + + cdata->fifo_threshold = fifo_len; + } + kfree(cdata->fifo_data); + cdata->fifo_data = 0; + + if (fifo_len > 0) { + cdata->fifo_data = kmalloc(cdata->fifo_threshold, GFP_KERNEL); + if (!cdata->fifo_data) + return -ENOMEM; + } + + return fifo_len; +} +EXPORT_SYMBOL(st_lsm6ds3_set_fifo_decimators_and_threshold); + +int st_lsm6ds3_reconfigure_fifo(struct lsm6ds3_data *cdata, + bool disable_irq_and_flush) +{ + int err, fifo_len; + + if (disable_irq_and_flush) { + disable_irq(cdata->irq); + st_lsm6ds3_flush_works(); + } + + mutex_lock(&cdata->fifo_lock); + + st_lsm6ds3_read_fifo(cdata, true); + + err = st_lsm6ds3_set_fifo_mode(cdata, BYPASS); + if (err < 0) + goto reconfigure_fifo_irq_restore; + + fifo_len = st_lsm6ds3_set_fifo_decimators_and_threshold(cdata); + if (fifo_len < 0) { + err = fifo_len; + goto reconfigure_fifo_irq_restore; + } + + if (fifo_len > 0) { + err = st_lsm6ds3_set_fifo_mode(cdata, CONTINUOS); + if (err < 0) + goto reconfigure_fifo_irq_restore; + } + +reconfigure_fifo_irq_restore: + mutex_unlock(&cdata->fifo_lock); + + if (disable_irq_and_flush) + enable_irq(cdata->irq); + + return err; +} +EXPORT_SYMBOL(st_lsm6ds3_reconfigure_fifo); + +int st_lsm6ds3_set_drdy_irq(struct lsm6ds3_sensor_data *sdata, bool state) +{ + u8 reg_addr, mask, value; + + if (state) + value = ST_LSM6DS3_EN_BIT; + else + value = ST_LSM6DS3_DIS_BIT; + + switch (sdata->sindex) { + case ST_INDIO_DEV_ACCEL: +#ifdef CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT + if ((sdata->cdata->sensors_enabled & + (1 << ST_INDIO_DEV_GYRO)) || + (sdata->cdata->sensors_enabled & + ST_LSM6DS3_EXT_SENSORS)) + return 0; +#else /* CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT */ + if (sdata->cdata->sensors_enabled & (1 << ST_INDIO_DEV_GYRO)) + return 0; +#endif /* CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT */ + + reg_addr = ST_LSM6DS3_INT1_ADDR; + mask = ST_LSM6DS3_FIFO_THR_IRQ_MASK; + break; + case ST_INDIO_DEV_GYRO: +#ifdef CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT + if ((sdata->cdata->sensors_enabled & + (1 << ST_INDIO_DEV_ACCEL)) || + (sdata->cdata->sensors_enabled & + ST_LSM6DS3_EXT_SENSORS)) + return 0; +#else /* CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT */ + if (sdata->cdata->sensors_enabled & (1 << ST_INDIO_DEV_ACCEL)) + return 0; +#endif /* CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT */ + + reg_addr = ST_LSM6DS3_INT1_ADDR; + mask = ST_LSM6DS3_FIFO_THR_IRQ_MASK; + break; + case ST_INDIO_DEV_SIGN_MOTION: + if (sdata->cdata->sensors_enabled & + (1 << ST_INDIO_DEV_STEP_DETECTOR)) + return 0; + + reg_addr = ST_LSM6DS3_INT1_ADDR; + mask = ST_LSM6DS3_STEP_DETECTOR_DRDY_IRQ_MASK; + break; + case ST_INDIO_DEV_STEP_COUNTER: + reg_addr = ST_LSM6DS3_INT2_ADDR; + mask = ST_LSM6DS3_STEP_COUNTER_DRDY_IRQ_MASK; + break; + case ST_INDIO_DEV_STEP_DETECTOR: + if (sdata->cdata->sensors_enabled & + (1 << ST_INDIO_DEV_SIGN_MOTION)) + return 0; + + reg_addr = ST_LSM6DS3_INT1_ADDR; + mask = ST_LSM6DS3_STEP_DETECTOR_DRDY_IRQ_MASK; + break; + case ST_INDIO_DEV_TILT: + reg_addr = ST_LSM6DS3_MD1_ADDR; + mask = ST_LSM6DS3_TILT_DRDY_IRQ_MASK; + break; +#ifdef CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT + case ST_INDIO_DEV_EXT0: + if ((sdata->cdata->sensors_enabled & + (1 << ST_INDIO_DEV_ACCEL)) || + (sdata->cdata->sensors_enabled & + (1 << ST_INDIO_DEV_GYRO)) || + (sdata->cdata->sensors_enabled & + (1 << ST_INDIO_DEV_EXT1))) + return 0; + + reg_addr = ST_LSM6DS3_INT1_ADDR; + mask = ST_LSM6DS3_FIFO_THR_IRQ_MASK; + break; + case ST_INDIO_DEV_EXT1: + if ((sdata->cdata->sensors_enabled & + (1 << ST_INDIO_DEV_ACCEL)) || + (sdata->cdata->sensors_enabled & + (1 << ST_INDIO_DEV_GYRO)) || + (sdata->cdata->sensors_enabled & + (1 << ST_INDIO_DEV_EXT0))) + return 0; + + reg_addr = ST_LSM6DS3_INT1_ADDR; + mask = ST_LSM6DS3_FIFO_THR_IRQ_MASK; + break; +#endif /* CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT */ + default: + return -EINVAL; + } + + return st_lsm6ds3_write_data_with_mask(sdata->cdata, + reg_addr, mask, value, true); +} +EXPORT_SYMBOL(st_lsm6ds3_set_drdy_irq); + +int st_lsm6ds3_set_axis_enable(struct lsm6ds3_sensor_data *sdata, u8 value) +{ + u8 reg_addr; + + switch (sdata->sindex) { + case ST_INDIO_DEV_ACCEL: + reg_addr = ST_LSM6DS3_ACCEL_AXIS_EN_ADDR; + break; + case ST_INDIO_DEV_GYRO: + reg_addr = ST_LSM6DS3_GYRO_AXIS_EN_ADDR; + break; + default: + return 0; + } + + return st_lsm6ds3_write_data_with_mask(sdata->cdata, + reg_addr, ST_LSM6DS3_AXIS_EN_MASK, value, true); +} +EXPORT_SYMBOL(st_lsm6ds3_set_axis_enable); + +int st_lsm6ds3_enable_accel_dependency(struct lsm6ds3_sensor_data *sdata, + bool enable) +{ + int err; + + if (!((sdata->cdata->sensors_enabled & + ST_LSM6DS3_ACCEL_DEPENDENCY) & ~(1 << sdata->sindex))) { + if (enable) { + err = st_lsm6ds3_write_data_with_mask(sdata->cdata, + st_lsm6ds3_odr_table.addr[ST_INDIO_DEV_ACCEL], + st_lsm6ds3_odr_table.mask[ST_INDIO_DEV_ACCEL], + st_lsm6ds3_odr_table.odr_avl[0].value, true); + if (err < 0) + return err; + } else { + err = st_lsm6ds3_write_data_with_mask(sdata->cdata, + st_lsm6ds3_odr_table.addr[ST_INDIO_DEV_ACCEL], + st_lsm6ds3_odr_table.mask[ST_INDIO_DEV_ACCEL], + ST_LSM6DS3_ODR_POWER_OFF_VAL, true); + if (err < 0) + return err; + } + } + + return 0; +} +EXPORT_SYMBOL(st_lsm6ds3_enable_accel_dependency); + +static int st_lsm6ds3_set_extra_dependency(struct lsm6ds3_sensor_data *sdata, + bool enable) +{ + int err; + + if (!((sdata->cdata->sensors_enabled & + ST_LSM6DS3_EXTRA_DEPENDENCY) & ~(1 << sdata->sindex))) { + if (enable) { + err = st_lsm6ds3_write_data_with_mask(sdata->cdata, + ST_LSM6DS3_FUNC_EN_ADDR, + ST_LSM6DS3_FUNC_EN_MASK, + ST_LSM6DS3_EN_BIT, true); + if (err < 0) + return err; + } else { + err = st_lsm6ds3_write_data_with_mask(sdata->cdata, + ST_LSM6DS3_FUNC_EN_ADDR, + ST_LSM6DS3_FUNC_EN_MASK, + ST_LSM6DS3_DIS_BIT, true); + if (err < 0) + return err; + } + } + + return st_lsm6ds3_enable_accel_dependency(sdata, enable); +} + +static int st_lsm6ds3_enable_pedometer(struct lsm6ds3_sensor_data *sdata, + bool enable) +{ + u8 value = ST_LSM6DS3_DIS_BIT; + + if ((sdata->cdata->sensors_enabled & ~(1 << sdata->sindex)) & + ST_LSM6DS3_PEDOMETER_DEPENDENCY) + return 0; + + if (enable) + value = ST_LSM6DS3_EN_BIT; + + return st_lsm6ds3_write_data_with_mask(sdata->cdata, + ST_LSM6DS3_PEDOMETER_EN_ADDR, + ST_LSM6DS3_PEDOMETER_EN_MASK, + value, true); + +} + +static int st_lsm6ds3_enable_sensors(struct lsm6ds3_sensor_data *sdata) +{ + int err, i; + + switch (sdata->sindex) { + case ST_INDIO_DEV_ACCEL: + case ST_INDIO_DEV_GYRO: + for (i = 0; i < ST_LSM6DS3_ODR_LIST_NUM; i++) { + if (st_lsm6ds3_odr_table.odr_avl[i].hz == sdata->c_odr) + break; + } + if (i == ST_LSM6DS3_ODR_LIST_NUM) + return -EINVAL; + + if (sdata->sindex == ST_INDIO_DEV_ACCEL) { + sdata->cdata->accel_samples_to_discard = + ST_LSM6DS3_ACCEL_STD; + } + + sdata->cdata->gyro_samples_to_discard = ST_LSM6DS3_GYRO_STD; + + err = st_lsm6ds3_write_data_with_mask(sdata->cdata, + st_lsm6ds3_odr_table.addr[sdata->sindex], + st_lsm6ds3_odr_table.mask[sdata->sindex], + st_lsm6ds3_odr_table.odr_avl[i].value, true); + if (err < 0) + return err; + + sdata->c_odr = st_lsm6ds3_odr_table.odr_avl[i].hz; + + break; + case ST_INDIO_DEV_SIGN_MOTION: + err = st_lsm6ds3_write_data_with_mask(sdata->cdata, + ST_LSM6DS3_SIGN_MOTION_EN_ADDR, + ST_LSM6DS3_SIGN_MOTION_EN_MASK, + ST_LSM6DS3_EN_BIT, true); + if (err < 0) + return err; + + if ((sdata->cdata->sensors_enabled & ~(1 << sdata->sindex)) & + ST_LSM6DS3_PEDOMETER_DEPENDENCY) { + err = st_lsm6ds3_write_data_with_mask(sdata->cdata, + ST_LSM6DS3_PEDOMETER_EN_ADDR, + ST_LSM6DS3_PEDOMETER_EN_MASK, + ST_LSM6DS3_DIS_BIT, true); + if (err < 0) + return err; + + err = st_lsm6ds3_write_data_with_mask(sdata->cdata, + ST_LSM6DS3_PEDOMETER_EN_ADDR, + ST_LSM6DS3_PEDOMETER_EN_MASK, + ST_LSM6DS3_EN_BIT, true); + if (err < 0) + return err; + } else { + err = st_lsm6ds3_enable_pedometer(sdata, true); + if (err < 0) + return err; + } + + break; + case ST_INDIO_DEV_STEP_COUNTER: + err = st_lsm6ds3_write_data_with_mask(sdata->cdata, + ST_LSM6DS3_TIMER_EN_ADDR, + ST_LSM6DS3_TIMER_EN_MASK, + ST_LSM6DS3_EN_BIT, true); + if (err < 0) + return err; + + case ST_INDIO_DEV_STEP_DETECTOR: + err = st_lsm6ds3_enable_pedometer(sdata, true); + if (err < 0) + return err; + + break; + case ST_INDIO_DEV_TILT: + err = st_lsm6ds3_write_data_with_mask(sdata->cdata, + ST_LSM6DS3_TILT_EN_ADDR, + ST_LSM6DS3_TILT_EN_MASK, + ST_LSM6DS3_EN_BIT, true); + if (err < 0) + return err; + + break; + default: + return -EINVAL; + } + + err = st_lsm6ds3_set_extra_dependency(sdata, true); + if (err < 0) + return err; + + sdata->cdata->sensors_enabled |= (1 << sdata->sindex); + + return 0; +} + +static int st_lsm6ds3_disable_sensors(struct lsm6ds3_sensor_data *sdata) +{ + int err; + + switch (sdata->sindex) { + case ST_INDIO_DEV_ACCEL: + if (sdata->cdata->sensors_enabled & + ST_LSM6DS3_EXTRA_DEPENDENCY) { + err = st_lsm6ds3_write_data_with_mask(sdata->cdata, + st_lsm6ds3_odr_table.addr[sdata->sindex], + st_lsm6ds3_odr_table.mask[sdata->sindex], + st_lsm6ds3_odr_table.odr_avl[0].value, true); + } else { + err = st_lsm6ds3_write_data_with_mask(sdata->cdata, + st_lsm6ds3_odr_table.addr[sdata->sindex], + st_lsm6ds3_odr_table.mask[sdata->sindex], + ST_LSM6DS3_ODR_POWER_OFF_VAL, true); + } + if (err < 0) + return err; + + break; + case ST_INDIO_DEV_GYRO: + err = st_lsm6ds3_write_data_with_mask(sdata->cdata, + st_lsm6ds3_odr_table.addr[sdata->sindex], + st_lsm6ds3_odr_table.mask[sdata->sindex], + ST_LSM6DS3_ODR_POWER_OFF_VAL, true); + if (err < 0) + return err; + + break; + case ST_INDIO_DEV_SIGN_MOTION: + err = st_lsm6ds3_write_data_with_mask(sdata->cdata, + ST_LSM6DS3_SIGN_MOTION_EN_ADDR, + ST_LSM6DS3_SIGN_MOTION_EN_MASK, + ST_LSM6DS3_DIS_BIT, true); + if (err < 0) + return err; + + err = st_lsm6ds3_enable_pedometer(sdata, false); + if (err < 0) + return err; + + break; + case ST_INDIO_DEV_STEP_COUNTER: + err = st_lsm6ds3_write_data_with_mask(sdata->cdata, + ST_LSM6DS3_TIMER_EN_ADDR, + ST_LSM6DS3_TIMER_EN_MASK, + ST_LSM6DS3_DIS_BIT, true); + if (err < 0) + return err; + + case ST_INDIO_DEV_STEP_DETECTOR: + err = st_lsm6ds3_enable_pedometer(sdata, false); + if (err < 0) + return err; + + break; + case ST_INDIO_DEV_TILT: + err = st_lsm6ds3_write_data_with_mask(sdata->cdata, + ST_LSM6DS3_TILT_EN_ADDR, + ST_LSM6DS3_TILT_EN_MASK, + ST_LSM6DS3_DIS_BIT, true); + if (err < 0) + return err; + + break; + default: + return -EINVAL; + } + + err = st_lsm6ds3_set_extra_dependency(sdata, false); + if (err < 0) + return err; + + sdata->cdata->sensors_enabled &= ~(1 << sdata->sindex); + + return 0; +} + +int st_lsm6ds3_set_enable(struct lsm6ds3_sensor_data *sdata, bool enable) +{ + if (enable) + return st_lsm6ds3_enable_sensors(sdata); + else + return st_lsm6ds3_disable_sensors(sdata); +} +EXPORT_SYMBOL(st_lsm6ds3_set_enable); + +static int st_lsm6ds3_set_odr(struct lsm6ds3_sensor_data *sdata, + unsigned int odr) +{ + int err, i; + + for (i = 0; i < ST_LSM6DS3_ODR_LIST_NUM; i++) { + if (st_lsm6ds3_odr_table.odr_avl[i].hz == odr) + break; + } + if (i == ST_LSM6DS3_ODR_LIST_NUM) + return -EINVAL; + + if (sdata->cdata->sensors_enabled & (1 << sdata->sindex)) { + disable_irq(sdata->cdata->irq); + st_lsm6ds3_flush_works(); + + if (sdata->sindex == ST_INDIO_DEV_ACCEL) + sdata->cdata->accel_samples_to_discard = + ST_LSM6DS3_ACCEL_STD; + + sdata->cdata->gyro_samples_to_discard = ST_LSM6DS3_GYRO_STD; + + err = st_lsm6ds3_write_data_with_mask(sdata->cdata, + st_lsm6ds3_odr_table.addr[sdata->sindex], + st_lsm6ds3_odr_table.mask[sdata->sindex], + st_lsm6ds3_odr_table.odr_avl[i].value, true); + if (err < 0) { + enable_irq(sdata->cdata->irq); + return err; + } + + sdata->c_odr = st_lsm6ds3_odr_table.odr_avl[i].hz; + + st_lsm6ds3_reconfigure_fifo(sdata->cdata, false); + enable_irq(sdata->cdata->irq); + } else + sdata->c_odr = st_lsm6ds3_odr_table.odr_avl[i].hz; + + return 0; +} + +static int st_lsm6ds3_set_fs(struct lsm6ds3_sensor_data *sdata, + unsigned int gain) +{ + int err, i; + + for (i = 0; i < ST_LSM6DS3_FS_LIST_NUM; i++) { + if (st_lsm6ds3_fs_table[sdata->sindex].fs_avl[i].gain == gain) + break; + } + if (i == ST_LSM6DS3_FS_LIST_NUM) + return -EINVAL; + + err = st_lsm6ds3_write_data_with_mask(sdata->cdata, + st_lsm6ds3_fs_table[sdata->sindex].addr, + st_lsm6ds3_fs_table[sdata->sindex].mask, + st_lsm6ds3_fs_table[sdata->sindex].fs_avl[i].value, true); + if (err < 0) + return err; + + sdata->c_gain[0] = gain; + + return 0; +} + +static int st_lsm6ds3_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *ch, int *val, + int *val2, long mask) +{ + int err; + u8 outdata[ST_LSM6DS3_BYTE_FOR_CHANNEL]; + struct lsm6ds3_sensor_data *sdata = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&indio_dev->mlock); + + if (indio_dev->currentmode == INDIO_BUFFER_TRIGGERED) { + mutex_unlock(&indio_dev->mlock); + return -EBUSY; + } + + err = st_lsm6ds3_set_enable(sdata, true); + if (err < 0) { + mutex_unlock(&indio_dev->mlock); + return -EBUSY; + } + + if (sdata->sindex == ST_INDIO_DEV_ACCEL) + msleep(40); + + if (sdata->sindex == ST_INDIO_DEV_GYRO) + msleep(120); + + err = sdata->cdata->tf->read(sdata->cdata, ch->address, + ST_LSM6DS3_BYTE_FOR_CHANNEL, outdata, true); + if (err < 0) { + mutex_unlock(&indio_dev->mlock); + return err; + } + + *val = (s16)get_unaligned_le16(outdata); + *val = *val >> ch->scan_type.shift; + + err = st_lsm6ds3_set_enable(sdata, false); + + mutex_unlock(&indio_dev->mlock); + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = sdata->c_gain[0]; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + + return 0; +} + +static int st_lsm6ds3_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, long mask) +{ + int err; + struct lsm6ds3_sensor_data *sdata = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + mutex_lock(&indio_dev->mlock); + + if (indio_dev->currentmode == INDIO_BUFFER_TRIGGERED) { + mutex_unlock(&indio_dev->mlock); + return -EBUSY; + } + + err = st_lsm6ds3_set_fs(sdata, val2); + mutex_unlock(&indio_dev->mlock); + break; + default: + return -EINVAL; + } + + return err; +} + +static int st_lsm6ds3_reset_steps(struct lsm6ds3_data *cdata) +{ + int err; + u8 reg_value = 0x00; + + err = cdata->tf->read(cdata, + ST_LSM6DS3_STEP_COUNTER_RES_ADDR, 1, ®_value, true); + if (err < 0) + return err; + + if (reg_value & ST_LSM6DS3_FUNC_EN_MASK) + reg_value = ST_LSM6DS3_STEP_COUNTER_RES_FUNC_EN; + else + reg_value = ST_LSM6DS3_DIS_BIT; + + err = st_lsm6ds3_write_data_with_mask(cdata, + ST_LSM6DS3_STEP_COUNTER_RES_ADDR, + ST_LSM6DS3_STEP_COUNTER_RES_MASK, + ST_LSM6DS3_STEP_COUNTER_RES_ALL_EN, true); + if (err < 0) + return err; + + err = st_lsm6ds3_write_data_with_mask(cdata, + ST_LSM6DS3_STEP_COUNTER_RES_ADDR, + ST_LSM6DS3_STEP_COUNTER_RES_MASK, + reg_value, true); + if (err < 0) + return err; + + cdata->reset_steps = true; + + return 0; +} + +static int st_lsm6ds3_init_sensor(struct lsm6ds3_data *cdata) +{ + int err, i; + u8 default_reg_value = 0; + struct lsm6ds3_sensor_data *sdata; + + mutex_init(&cdata->tb.buf_lock); + + cdata->sensors_enabled = 0; + cdata->reset_steps = false; + cdata->sign_motion_event_ready = false; + + err = st_lsm6ds3_write_data_with_mask(cdata, ST_LSM6DS3_RESET_ADDR, + ST_LSM6DS3_RESET_MASK, ST_LSM6DS3_EN_BIT, true); + if (err < 0) + return err; + + for (i = 0; i < ST_INDIO_DEV_NUM; i++) { + sdata = iio_priv(cdata->indio_dev[i]); + + err = st_lsm6ds3_set_enable(sdata, false); + if (err < 0) + return err; + + err = st_lsm6ds3_set_drdy_irq(sdata, false); + if (err < 0) + return err; + + switch (sdata->sindex) { + case ST_INDIO_DEV_ACCEL: + case ST_INDIO_DEV_GYRO: + sdata->num_data_channels = + ARRAY_SIZE(st_lsm6ds3_accel_ch) - 1; + + err = st_lsm6ds3_set_fs(sdata, sdata->c_gain[0]); + if (err < 0) + return err; + + break; + case ST_INDIO_DEV_STEP_COUNTER: + sdata->num_data_channels = + ARRAY_SIZE(st_lsm6ds3_step_c_ch) - 1; + break; + default: + break; + } + } + + cdata->gyro_selftest_status = 0; + cdata->accel_selftest_status = 0; + + err = st_lsm6ds3_write_data_with_mask(cdata, ST_LSM6DS3_LIR_ADDR, + ST_LSM6DS3_LIR_MASK, ST_LSM6DS3_EN_BIT, true); + if (err < 0) + return err; + + err = st_lsm6ds3_write_data_with_mask(cdata, ST_LSM6DS3_BDU_ADDR, + ST_LSM6DS3_BDU_MASK, ST_LSM6DS3_EN_BIT, true); + if (err < 0) + return err; + + err = st_lsm6ds3_set_fifo_enable(sdata->cdata, false); + if (err < 0) + return err; + + err = st_lsm6ds3_write_data_with_mask(cdata, + ST_LSM6DS3_ROUNDING_ADDR, + ST_LSM6DS3_ROUNDING_MASK, + ST_LSM6DS3_EN_BIT, true); + if (err < 0) + return err; + + err = st_lsm6ds3_write_data_with_mask(cdata, + ST_LSM6DS3_INT2_ON_INT1_ADDR, + ST_LSM6DS3_INT2_ON_INT1_MASK, + ST_LSM6DS3_EN_BIT, true); + if (err < 0) + return err; + + err = st_lsm6ds3_reset_steps(sdata->cdata); + if (err < 0) + return err; + + mutex_lock(&cdata->bank_registers_lock); + + err = st_lsm6ds3_write_data_with_mask(sdata->cdata, + ST_LSM6DS3_FUNC_CFG_ACCESS_ADDR, + ST_LSM6DS3_FUNC_CFG_REG2_MASK, + ST_LSM6DS3_EN_BIT, false); + if (err < 0) + goto st_lsm6ds3_init_sensor_mutex_unlock; + + err = sdata->cdata->tf->write(sdata->cdata, + ST_LSM6DS3_STEP_COUNTER_DURATION_ADDR, + 1, &default_reg_value, false); + if (err < 0) + goto st_lsm6ds3_init_sensor_mutex_unlock; + + err = st_lsm6ds3_write_data_with_mask(sdata->cdata, + ST_LSM6DS3_FUNC_CFG_ACCESS_ADDR, + ST_LSM6DS3_FUNC_CFG_REG2_MASK, + ST_LSM6DS3_DIS_BIT, false); + if (err < 0) + goto st_lsm6ds3_init_sensor_mutex_unlock; + + mutex_unlock(&cdata->bank_registers_lock); + + sdata->c_odr = 0; + + return 0; + +st_lsm6ds3_init_sensor_mutex_unlock: + mutex_unlock(&cdata->bank_registers_lock); + return err; +} + +static int st_lsm6ds3_set_selftest(struct lsm6ds3_sensor_data *sdata, int index) +{ + int err; + u8 mode, mask; + + switch (sdata->sindex) { + case ST_INDIO_DEV_ACCEL: + mask = ST_LSM6DS3_SELFTEST_ACCEL_MASK; + mode = st_lsm6ds3_selftest_table[index].accel_value; + break; + case ST_INDIO_DEV_GYRO: + mask = ST_LSM6DS3_SELFTEST_GYRO_MASK; + mode = st_lsm6ds3_selftest_table[index].gyro_value; + break; + default: + return -EINVAL; + } + + err = st_lsm6ds3_write_data_with_mask(sdata->cdata, + ST_LSM6DS3_SELFTEST_ADDR, mask, mode, true); + if (err < 0) + return err; + + return 0; +} + +static ssize_t st_lsm6ds3_sysfs_set_max_delivery_rate(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + u8 duration; + int err, err2; + unsigned int max_delivery_rate; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct lsm6ds3_sensor_data *sdata = iio_priv(indio_dev); + + err = kstrtouint(buf, 10, &max_delivery_rate); + if (err < 0) + return -EINVAL; + + if (max_delivery_rate == sdata->c_odr) + return size; + + duration = max_delivery_rate / ST_LSM6DS3_MIN_DURATION_MS; + + mutex_lock(&sdata->cdata->bank_registers_lock); + + err = st_lsm6ds3_write_data_with_mask(sdata->cdata, + ST_LSM6DS3_FUNC_CFG_ACCESS_ADDR, + ST_LSM6DS3_FUNC_CFG_REG2_MASK, + ST_LSM6DS3_EN_BIT, false); + if (err < 0) + goto st_lsm6ds3_sysfs_set_max_delivery_rate_mutex_unlock; + + err = sdata->cdata->tf->write(sdata->cdata, + ST_LSM6DS3_STEP_COUNTER_DURATION_ADDR, + 1, &duration, false); + if (err < 0) + goto st_lsm6ds3_sysfs_set_max_delivery_rate_restore_bank; + + err = st_lsm6ds3_write_data_with_mask(sdata->cdata, + ST_LSM6DS3_FUNC_CFG_ACCESS_ADDR, + ST_LSM6DS3_FUNC_CFG_REG2_MASK, + ST_LSM6DS3_DIS_BIT, false); + if (err < 0) + goto st_lsm6ds3_sysfs_set_max_delivery_rate_restore_bank; + + mutex_unlock(&sdata->cdata->bank_registers_lock); + + sdata->c_odr = max_delivery_rate; + + return size; + +st_lsm6ds3_sysfs_set_max_delivery_rate_restore_bank: + do { + err2 = st_lsm6ds3_write_data_with_mask(sdata->cdata, + ST_LSM6DS3_FUNC_CFG_ACCESS_ADDR, + ST_LSM6DS3_FUNC_CFG_REG2_MASK, + ST_LSM6DS3_DIS_BIT, false); + + msleep(500); + } while (err2 < 0); + +st_lsm6ds3_sysfs_set_max_delivery_rate_mutex_unlock: + mutex_unlock(&sdata->cdata->bank_registers_lock); + return err; +} + +static ssize_t st_lsm6ds3_sysfs_get_max_delivery_rate(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct lsm6ds3_sensor_data *sdata = iio_priv(dev_get_drvdata(dev)); + + return sprintf(buf, "%d\n", sdata->c_odr); +} + +static ssize_t st_lsm6ds3_sysfs_reset_counter(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + int err; + struct lsm6ds3_sensor_data *sdata = iio_priv(dev_get_drvdata(dev)); + + err = st_lsm6ds3_reset_steps(sdata->cdata); + if (err < 0) + return err; + + return size; +} + +static ssize_t st_lsm6ds3_sysfs_get_sampling_frequency(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct lsm6ds3_sensor_data *sdata = iio_priv(dev_get_drvdata(dev)); + + return sprintf(buf, "%d\n", sdata->c_odr); +} + +static ssize_t st_lsm6ds3_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); + struct lsm6ds3_sensor_data *sdata = iio_priv(indio_dev); + + err = kstrtoint(buf, 10, &odr); + if (err < 0) + return err; + + mutex_lock(&indio_dev->mlock); + + if (sdata->c_odr != odr) + err = st_lsm6ds3_set_odr(sdata, odr); + + mutex_unlock(&indio_dev->mlock); + + return err < 0 ? err : size; +} + +static ssize_t st_lsm6ds3_sysfs_sampling_frequency_avail(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i, len = 0; + + for (i = 0; i < ST_LSM6DS3_ODR_LIST_NUM; i++) { + len += scnprintf(buf + len, PAGE_SIZE - len, "%d ", + st_lsm6ds3_odr_table.odr_avl[i].hz); + } + buf[len - 1] = '\n'; + + return len; +} + +static ssize_t st_lsm6ds3_sysfs_scale_avail(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i, len = 0; + struct lsm6ds3_sensor_data *sdata = iio_priv(dev_get_drvdata(dev)); + + for (i = 0; i < ST_LSM6DS3_FS_LIST_NUM; i++) { + len += scnprintf(buf + len, PAGE_SIZE - len, "0.%06u ", + st_lsm6ds3_fs_table[sdata->sindex].fs_avl[i].gain); + } + buf[len - 1] = '\n'; + + return len; +} + +static ssize_t st_lsm6ds3_sysfs_get_selftest_available(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%s, %s, %s\n", + st_lsm6ds3_selftest_table[0].string_mode, + st_lsm6ds3_selftest_table[1].string_mode, + st_lsm6ds3_selftest_table[2].string_mode); +} + +static ssize_t st_lsm6ds3_sysfs_get_selftest_status(struct device *dev, + struct device_attribute *attr, char *buf) +{ + u8 index; + struct lsm6ds3_sensor_data *sdata = iio_priv(dev_get_drvdata(dev)); + + switch (sdata->sindex) { + case ST_INDIO_DEV_ACCEL: + index = sdata->cdata->accel_selftest_status; + break; + case ST_INDIO_DEV_GYRO: + index = sdata->cdata->gyro_selftest_status; + break; + default: + return -EINVAL; + } + + return sprintf(buf, "%s\n", + st_lsm6ds3_selftest_table[index].string_mode); +} + +static ssize_t st_lsm6ds3_sysfs_set_selftest_status(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + int err, i; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct lsm6ds3_sensor_data *sdata = iio_priv(indio_dev); + + for (i = 0; i < ARRAY_SIZE(st_lsm6ds3_selftest_table); i++) { + if (strncmp(buf, st_lsm6ds3_selftest_table[i].string_mode, + size - 2) == 0) + break; + } + if (i == ARRAY_SIZE(st_lsm6ds3_selftest_table)) + return -EINVAL; + + err = st_lsm6ds3_set_selftest(sdata, i); + if (err < 0) + return err; + + switch (sdata->sindex) { + case ST_INDIO_DEV_ACCEL: + sdata->cdata->accel_selftest_status = i; + break; + case ST_INDIO_DEV_GYRO: + sdata->cdata->gyro_selftest_status = i; + break; + default: + return -EINVAL; + } + + return size; +} + +ssize_t st_lsm6ds3_sysfs_get_hw_fifo_lenght(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", ST_LSM6DS3_MAX_FIFO_LENGHT); +} + +ssize_t st_lsm6ds3_sysfs_flush_fifo(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct lsm6ds3_sensor_data *sdata = iio_priv(indio_dev); + + disable_irq(sdata->cdata->irq); + st_lsm6ds3_flush_works(); + + mutex_lock(&sdata->cdata->fifo_lock); + + st_lsm6ds3_read_fifo(sdata->cdata, true); + + mutex_unlock(&sdata->cdata->fifo_lock); + + enable_irq(sdata->cdata->irq); + + return size; +} + +static ST_LSM6DS3_DEV_ATTR_SAMP_FREQ(); +static ST_LSM6DS3_DEV_ATTR_SAMP_FREQ_AVAIL(); +static ST_LSM6DS3_DEV_ATTR_SCALE_AVAIL(in_accel_scale_available); +static ST_LSM6DS3_DEV_ATTR_SCALE_AVAIL(in_anglvel_scale_available); +static ST_LSM6DS3_FIFO_LENGHT(); +static ST_LSM6DS3_FIFO_FLUSH(); + +static IIO_DEVICE_ATTR(reset_counter, S_IWUSR, + NULL, st_lsm6ds3_sysfs_reset_counter, 0); + +static IIO_DEVICE_ATTR(max_delivery_rate, S_IWUSR | S_IRUGO, + st_lsm6ds3_sysfs_get_max_delivery_rate, + st_lsm6ds3_sysfs_set_max_delivery_rate, 0); + +static IIO_DEVICE_ATTR(self_test_available, S_IRUGO, + st_lsm6ds3_sysfs_get_selftest_available, + NULL, 0); + +static IIO_DEVICE_ATTR(self_test, S_IWUSR | S_IRUGO, + st_lsm6ds3_sysfs_get_selftest_status, + st_lsm6ds3_sysfs_set_selftest_status, 0); + +static struct attribute *st_lsm6ds3_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, + &iio_dev_attr_self_test_available.dev_attr.attr, + &iio_dev_attr_self_test.dev_attr.attr, + &iio_dev_attr_hw_fifo_lenght.dev_attr.attr, + &iio_dev_attr_flush.dev_attr.attr, + NULL, +}; + +static const struct attribute_group st_lsm6ds3_accel_attribute_group = { + .attrs = st_lsm6ds3_accel_attributes, +}; + +static const struct iio_info st_lsm6ds3_accel_info = { + .driver_module = THIS_MODULE, + .attrs = &st_lsm6ds3_accel_attribute_group, + .read_raw = &st_lsm6ds3_read_raw, + .write_raw = &st_lsm6ds3_write_raw, +}; + +static struct attribute *st_lsm6ds3_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, + &iio_dev_attr_self_test_available.dev_attr.attr, + &iio_dev_attr_self_test.dev_attr.attr, + &iio_dev_attr_hw_fifo_lenght.dev_attr.attr, + &iio_dev_attr_flush.dev_attr.attr, + NULL, +}; + +static const struct attribute_group st_lsm6ds3_gyro_attribute_group = { + .attrs = st_lsm6ds3_gyro_attributes, +}; + +static const struct iio_info st_lsm6ds3_gyro_info = { + .driver_module = THIS_MODULE, + .attrs = &st_lsm6ds3_gyro_attribute_group, + .read_raw = &st_lsm6ds3_read_raw, + .write_raw = &st_lsm6ds3_write_raw, +}; + +static struct attribute *st_lsm6ds3_sign_motion_attributes[] = { + NULL, +}; + +static const struct attribute_group st_lsm6ds3_sign_motion_attribute_group = { + .attrs = st_lsm6ds3_sign_motion_attributes, +}; + +static const struct iio_info st_lsm6ds3_sign_motion_info = { + .driver_module = THIS_MODULE, + .attrs = &st_lsm6ds3_sign_motion_attribute_group, +}; + +static struct attribute *st_lsm6ds3_step_c_attributes[] = { + &iio_dev_attr_reset_counter.dev_attr.attr, + &iio_dev_attr_max_delivery_rate.dev_attr.attr, + NULL, +}; + +static const struct attribute_group st_lsm6ds3_step_c_attribute_group = { + .attrs = st_lsm6ds3_step_c_attributes, +}; + +static const struct iio_info st_lsm6ds3_step_c_info = { + .driver_module = THIS_MODULE, + .attrs = &st_lsm6ds3_step_c_attribute_group, + .read_raw = &st_lsm6ds3_read_raw, +}; + +static struct attribute *st_lsm6ds3_step_d_attributes[] = { + NULL, +}; + +static const struct attribute_group st_lsm6ds3_step_d_attribute_group = { + .attrs = st_lsm6ds3_step_d_attributes, +}; + +static const struct iio_info st_lsm6ds3_step_d_info = { + .driver_module = THIS_MODULE, + .attrs = &st_lsm6ds3_step_d_attribute_group, +}; + +static struct attribute *st_lsm6ds3_tilt_attributes[] = { + NULL, +}; + +static const struct attribute_group st_lsm6ds3_tilt_attribute_group = { + .attrs = st_lsm6ds3_tilt_attributes, +}; + +static const struct iio_info st_lsm6ds3_tilt_info = { + .driver_module = THIS_MODULE, + .attrs = &st_lsm6ds3_tilt_attribute_group, +}; + +#ifdef CONFIG_IIO_TRIGGER +static const struct iio_trigger_ops st_lsm6ds3_trigger_ops = { + .owner = THIS_MODULE, + .set_trigger_state = ST_LSM6DS3_TRIGGER_SET_STATE, +}; +#define ST_LSM6DS3_TRIGGER_OPS (&st_lsm6ds3_trigger_ops) +#else +#define ST_LSM6DS3_TRIGGER_OPS NULL +#endif + +int st_lsm6ds3_common_probe(struct lsm6ds3_data *cdata, int irq) +{ + u8 wai = 0x00; + int i, n, err; + struct lsm6ds3_sensor_data *sdata; + + mutex_init(&cdata->bank_registers_lock); + mutex_init(&cdata->fifo_lock); + +#ifdef CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT + mutex_init(&cdata->passthrough_lock); +#endif /* CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT */ + + cdata->fifo_data = 0; + + err = cdata->tf->read(cdata, ST_LSM6DS3_WAI_ADDRESS, 1, &wai, true); + if (err < 0) { + dev_err(cdata->dev, "failed to read Who-Am-I register.\n"); + return err; + } + if (wai != ST_LSM6DS3_WAI_EXP) { + dev_err(cdata->dev, "Who-Am-I value not valid.\n"); + return -ENODEV; + } + + for (i = 0; i < ST_INDIO_DEV_NUM; i++) { + cdata->indio_dev[i] = iio_device_alloc(sizeof(*sdata)); + if (cdata->indio_dev[i] == NULL) { + err = -ENOMEM; + goto iio_device_free; + } + sdata = iio_priv(cdata->indio_dev[i]); + sdata->cdata = cdata; + sdata->sindex = i; + + if ((i == ST_INDIO_DEV_ACCEL) || (i == ST_INDIO_DEV_GYRO)) { + sdata->c_odr = st_lsm6ds3_odr_table.odr_avl[0].hz; + sdata->c_gain[0] = + st_lsm6ds3_fs_table[i].fs_avl[0].gain; + } + cdata->indio_dev[i]->modes = INDIO_DIRECT_MODE; + } + + if (irq > 0) { + cdata->irq = irq; + dev_info(cdata->dev, "driver use DRDY int pin 1\n"); + } + + cdata->indio_dev[ST_INDIO_DEV_ACCEL]->name = + kasprintf(GFP_KERNEL, "%s_%s", cdata->name, + ST_LSM6DS3_ACCEL_SUFFIX_NAME); + cdata->indio_dev[ST_INDIO_DEV_ACCEL]->info = &st_lsm6ds3_accel_info; + cdata->indio_dev[ST_INDIO_DEV_ACCEL]->channels = st_lsm6ds3_accel_ch; + cdata->indio_dev[ST_INDIO_DEV_ACCEL]->num_channels = + ARRAY_SIZE(st_lsm6ds3_accel_ch); + + cdata->indio_dev[ST_INDIO_DEV_GYRO]->name = + kasprintf(GFP_KERNEL, "%s_%s", cdata->name, + ST_LSM6DS3_GYRO_SUFFIX_NAME); + cdata->indio_dev[ST_INDIO_DEV_GYRO]->info = &st_lsm6ds3_gyro_info; + cdata->indio_dev[ST_INDIO_DEV_GYRO]->channels = st_lsm6ds3_gyro_ch; + cdata->indio_dev[ST_INDIO_DEV_GYRO]->num_channels = + ARRAY_SIZE(st_lsm6ds3_gyro_ch); + + cdata->indio_dev[ST_INDIO_DEV_SIGN_MOTION]->name = + kasprintf(GFP_KERNEL, "%s_%s", cdata->name, + ST_LSM6DS3_SIGN_MOTION_SUFFIX_NAME); + cdata->indio_dev[ST_INDIO_DEV_SIGN_MOTION]->info = + &st_lsm6ds3_sign_motion_info; + cdata->indio_dev[ST_INDIO_DEV_SIGN_MOTION]->channels = + st_lsm6ds3_sign_motion_ch; + cdata->indio_dev[ST_INDIO_DEV_SIGN_MOTION]->num_channels = + ARRAY_SIZE(st_lsm6ds3_sign_motion_ch); + + cdata->indio_dev[ST_INDIO_DEV_STEP_COUNTER]->name = + kasprintf(GFP_KERNEL, "%s_%s", cdata->name, + ST_LSM6DS3_STEP_COUNTER_SUFFIX_NAME); + cdata->indio_dev[ST_INDIO_DEV_STEP_COUNTER]->info = + &st_lsm6ds3_step_c_info; + cdata->indio_dev[ST_INDIO_DEV_STEP_COUNTER]->channels = + st_lsm6ds3_step_c_ch; + cdata->indio_dev[ST_INDIO_DEV_STEP_COUNTER]->num_channels = + ARRAY_SIZE(st_lsm6ds3_step_c_ch); + + cdata->indio_dev[ST_INDIO_DEV_STEP_DETECTOR]->name = + kasprintf(GFP_KERNEL, "%s_%s", cdata->name, + ST_LSM6DS3_STEP_DETECTOR_SUFFIX_NAME); + cdata->indio_dev[ST_INDIO_DEV_STEP_DETECTOR]->info = + &st_lsm6ds3_step_d_info; + cdata->indio_dev[ST_INDIO_DEV_STEP_DETECTOR]->channels = + st_lsm6ds3_step_d_ch; + cdata->indio_dev[ST_INDIO_DEV_STEP_DETECTOR]->num_channels = + ARRAY_SIZE(st_lsm6ds3_step_d_ch); + + cdata->indio_dev[ST_INDIO_DEV_TILT]->name = + kasprintf(GFP_KERNEL, "%s_%s", cdata->name, + ST_LSM6DS3_TILT_SUFFIX_NAME); + cdata->indio_dev[ST_INDIO_DEV_TILT]->info = &st_lsm6ds3_tilt_info; + cdata->indio_dev[ST_INDIO_DEV_TILT]->channels = st_lsm6ds3_tilt_ch; + cdata->indio_dev[ST_INDIO_DEV_TILT]->num_channels = + ARRAY_SIZE(st_lsm6ds3_tilt_ch); + + err = st_lsm6ds3_init_sensor(cdata); + if (err < 0) + goto iio_device_free; + + err = st_lsm6ds3_allocate_rings(cdata); + if (err < 0) + goto iio_device_free; + + if (irq > 0) { + err = st_lsm6ds3_allocate_triggers(cdata, + ST_LSM6DS3_TRIGGER_OPS); + if (err < 0) + goto deallocate_ring; + } + + for (n = 0; n < ST_INDIO_DEV_NUM; n++) { + err = iio_device_register(cdata->indio_dev[n]); + if (err) + goto iio_device_unregister_and_trigger_deallocate; + } + + err = st_lsm6ds3_i2c_master_probe(cdata); + if (err < 0) + goto iio_device_unregister_and_trigger_deallocate; + + device_init_wakeup(cdata->dev, true); + + return 0; + +iio_device_unregister_and_trigger_deallocate: + for (n--; n >= 0; n--) + iio_device_unregister(cdata->indio_dev[n]); + + if (irq > 0) + st_lsm6ds3_deallocate_triggers(cdata); +deallocate_ring: + st_lsm6ds3_deallocate_rings(cdata); +iio_device_free: + for (i--; i >= 0; i--) + iio_device_free(cdata->indio_dev[i]); + + return err; +} +EXPORT_SYMBOL(st_lsm6ds3_common_probe); + +void st_lsm6ds3_common_remove(struct lsm6ds3_data *cdata, int irq) +{ + int i; + + for (i = 0; i < ST_INDIO_DEV_NUM; i++) + iio_device_unregister(cdata->indio_dev[i]); + + if (irq > 0) + st_lsm6ds3_deallocate_triggers(cdata); + + st_lsm6ds3_deallocate_rings(cdata); + + for (i = 0; i < ST_INDIO_DEV_NUM; i++) + iio_device_free(cdata->indio_dev[i]); + + st_lsm6ds3_i2c_master_exit(cdata); +} +EXPORT_SYMBOL(st_lsm6ds3_common_remove); + +#ifdef CONFIG_PM +int st_lsm6ds3_common_suspend(struct lsm6ds3_data *cdata) +{ +#ifndef CONFIG_ST_LSM6DS3_IIO_SENSORS_WAKEUP + int err, i; + u8 tmp_sensors_enabled; + struct lsm6ds3_sensor_data *sdata; + + tmp_sensors_enabled = cdata->sensors_enabled; + + for (i = 0; i < ST_INDIO_DEV_NUM; i++) { + if ((i == ST_INDIO_DEV_SIGN_MOTION) || (i == ST_INDIO_DEV_TILT)) + continue; + + sdata = iio_priv(cdata->indio_dev[i]); + + err = st_lsm6ds3_set_enable(sdata, false); + if (err < 0) + return err; + } + cdata->sensors_enabled = tmp_sensors_enabled; +#endif /* CONFIG_ST_LSM6DS3_IIO_SENSORS_WAKEUP */ + + if (cdata->sensors_enabled & ST_LSM6DS3_WAKE_UP_SENSORS) { + if (device_may_wakeup(cdata->dev)) + enable_irq_wake(cdata->irq); + } + + return 0; +} +EXPORT_SYMBOL(st_lsm6ds3_common_suspend); + +int st_lsm6ds3_common_resume(struct lsm6ds3_data *cdata) +{ +#ifndef CONFIG_ST_LSM6DS3_IIO_SENSORS_WAKEUP + int err, i; + struct lsm6ds3_sensor_data *sdata; + + for (i = 0; i < ST_INDIO_DEV_NUM; i++) { + if ((i == ST_INDIO_DEV_SIGN_MOTION) || (i == ST_INDIO_DEV_TILT)) + continue; + + sdata = iio_priv(cdata->indio_dev[i]); + + if ((1 << sdata->sindex) & cdata->sensors_enabled) { + err = st_lsm6ds3_set_enable(sdata, true); + if (err < 0) + return err; + } + } +#endif /* CONFIG_ST_LSM6DS3_IIO_SENSORS_WAKEUP */ + + if (cdata->sensors_enabled & ST_LSM6DS3_WAKE_UP_SENSORS) { + if (device_may_wakeup(cdata->dev)) + disable_irq_wake(cdata->irq); + } + + return 0; +} +EXPORT_SYMBOL(st_lsm6ds3_common_resume); +#endif /* CONFIG_PM */ + +MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics lsm6ds3 core driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/imu/st_lsm6ds3/st_lsm6ds3_i2c.c b/drivers/iio/imu/st_lsm6ds3/st_lsm6ds3_i2c.c new file mode 100644 index 00000000000..436f09e0060 --- /dev/null +++ b/drivers/iio/imu/st_lsm6ds3/st_lsm6ds3_i2c.c @@ -0,0 +1,159 @@ +/* + * STMicroelectronics lsm6ds3 i2c driver + * + * Copyright 2014 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 "st_lsm6ds3.h" + +static int st_lsm6ds3_i2c_read(struct lsm6ds3_data *cdata, + u8 reg_addr, int len, u8 *data, bool b_lock) +{ + int err = 0; + struct i2c_msg msg[2]; + struct i2c_client *client = to_i2c_client(cdata->dev); + + msg[0].addr = client->addr; + msg[0].flags = client->flags; + msg[0].len = 1; + msg[0].buf = ®_addr; + + msg[1].addr = client->addr; + msg[1].flags = client->flags | I2C_M_RD; + msg[1].len = len; + msg[1].buf = data; + + if (b_lock) { + mutex_lock(&cdata->bank_registers_lock); + err = i2c_transfer(client->adapter, msg, 2); + mutex_unlock(&cdata->bank_registers_lock); + } else + err = i2c_transfer(client->adapter, msg, 2); + + return err; +} + +static int st_lsm6ds3_i2c_write(struct lsm6ds3_data *cdata, + u8 reg_addr, int len, u8 *data, bool b_lock) +{ + int err = 0; + u8 send[len + 1]; + struct i2c_msg msg; + struct i2c_client *client = to_i2c_client(cdata->dev); + + send[0] = reg_addr; + memcpy(&send[1], data, len * sizeof(u8)); + len++; + + msg.addr = client->addr; + msg.flags = client->flags; + msg.len = len; + msg.buf = send; + + if (b_lock) { + mutex_lock(&cdata->bank_registers_lock); + err = i2c_transfer(client->adapter, &msg, 1); + mutex_unlock(&cdata->bank_registers_lock); + } else + err = i2c_transfer(client->adapter, &msg, 1); + + return err; +} + +static const struct st_lsm6ds3_transfer_function st_lsm6ds3_tf_i2c = { + .write = st_lsm6ds3_i2c_write, + .read = st_lsm6ds3_i2c_read, +}; + +static int st_lsm6ds3_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int err; + struct lsm6ds3_data *cdata; + + cdata = kmalloc(sizeof(*cdata), GFP_KERNEL); + if (!cdata) + return -ENOMEM; + + cdata->dev = &client->dev; + cdata->name = client->name; + i2c_set_clientdata(client, cdata); + + cdata->tf = &st_lsm6ds3_tf_i2c; + + err = st_lsm6ds3_common_probe(cdata, client->irq); + if (err < 0) + goto free_data; + + return 0; + +free_data: + kfree(cdata); + return err; +} + +static int st_lsm6ds3_i2c_remove(struct i2c_client *client) +{ + struct lsm6ds3_data *cdata = i2c_get_clientdata(client); + + st_lsm6ds3_common_remove(cdata, client->irq); + kfree(cdata); + + return 0; +} + +#ifdef CONFIG_PM +static int st_lsm6ds3_suspend(struct device *dev) +{ + struct lsm6ds3_data *cdata = i2c_get_clientdata(to_i2c_client(dev)); + + return st_lsm6ds3_common_suspend(cdata); +} + +static int st_lsm6ds3_resume(struct device *dev) +{ + struct lsm6ds3_data *cdata = i2c_get_clientdata(to_i2c_client(dev)); + + return st_lsm6ds3_common_resume(cdata); +} + +static const struct dev_pm_ops st_lsm6ds3_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(st_lsm6ds3_suspend, st_lsm6ds3_resume) +}; + +#define ST_LSM6DS3_PM_OPS (&st_lsm6ds3_pm_ops) +#else /* CONFIG_PM */ +#define ST_LSM6DS3_PM_OPS NULL +#endif /* CONFIG_PM */ + +static const struct i2c_device_id st_lsm6ds3_id_table[] = { + { LSM6DS3_DEV_NAME }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, st_lsm6ds3_id_table); + +static struct i2c_driver st_lsm6ds3_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "st-lsm6ds3-i2c", + .pm = ST_LSM6DS3_PM_OPS, + }, + .probe = st_lsm6ds3_i2c_probe, + .remove = st_lsm6ds3_i2c_remove, + .id_table = st_lsm6ds3_id_table, +}; +module_i2c_driver(st_lsm6ds3_driver); + +MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics lsm6ds3 i2c driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/imu/st_lsm6ds3/st_lsm6ds3_i2c_master.c b/drivers/iio/imu/st_lsm6ds3/st_lsm6ds3_i2c_master.c new file mode 100644 index 00000000000..49e0a08c8b5 --- /dev/null +++ b/drivers/iio/imu/st_lsm6ds3/st_lsm6ds3_i2c_master.c @@ -0,0 +1,1387 @@ +/* + * STMicroelectronics lsm6ds3 i2c master driver + * + * Copyright 2014 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/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/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> +#include <asm/unaligned.h> + +#include "st_lsm6ds3.h" + +#define ST_LSM6DS3_EXT0_INDEX 0 +#define ST_LSM6DS3_EXT1_INDEX 1 + +#define ST_LSM6DS3_I2C_MASTER_ODR_LIST_NUM 4 +#define ST_LSM6DS3_EN_BIT 0x01 +#define ST_LSM6DS3_DIS_BIT 0x00 +#define ST_LSM6DS3_HUB_REG1_ADDR 0x2e +#define ST_LSM6DS3_FUNC_CFG_ACCESS_ADDR 0x01 +#define ST_LSM6DS3_FUNC_CFG_REG2_MASK 0x80 +#define ST_LSM6DS3_SLV0_ADDR_ADDR 0x02 +#define ST_LSM6DS3_SLV0_SUBADDR_ADDR 0x03 +#define ST_LSM6DS3_SLV0_CONFIG_ADDR 0x04 +#define ST_LSM6DS3_SLV0_CONFIG_MASK 0x07 +#define ST_LSM6DS3_SLV1_ADDR_ADDR 0x05 +#define ST_LSM6DS3_SLV1_SUBADDR_ADDR 0x06 +#define ST_LSM6DS3_SLV1_CONFIG_ADDR 0x07 +#define ST_LSM6DS3_SLV1_CONFIG_MASK 0x07 +#define ST_LSM6DS3_SLV2_ADDR_ADDR 0x08 +#define ST_LSM6DS3_SLV2_SUBADDR_ADDR 0x09 +#define ST_LSM6DS3_SLV2_CONFIG_ADDR 0x0a +#define ST_LSM6DS3_SLV2_CONFIG_MASK 0x07 +#define ST_LSM6DS3_SLV_AUX_ADDR 0x04 +#define ST_LSM6DS3_SLV_AUX_MASK 0x30 +#define ST_LSM6DS3_SLV_AUX_1 0x00 +#define ST_LSM6DS3_SLV_AUX_2 0x01 +#define ST_LSM6DS3_SLV_AUX_3 0x02 + +/* External sensors configuration */ +#ifdef CONFIG_ST_LSM6DS3_IIO_EXT0_LIS3MDL +static int lis3mdl_initialization(struct lsm6ds3_sensor_data *sdata, + int ext_num); + +#define ST_LSM6DS3_EXT0_ADDR 0x1e +#define ST_LSM6DS3_EXT0_WAI_ADDR 0x0f +#define ST_LSM6DS3_EXT0_WAI_VALUE 0x3d +#define ST_LSM6DS3_EXT0_RESET_ADDR 0x21 +#define ST_LSM6DS3_EXT0_RESET_MASK 0x04 +#define ST_LSM6DS3_EXT0_FULLSCALE_ADDR 0x21 +#define ST_LSM6DS3_EXT0_FULLSCALE_MASK 0x60 +#define ST_LSM6DS3_EXT0_FULLSCALE_VALUE 0x02 +#define ST_LSM6DS3_EXT0_ODR_ADDR 0x20 +#define ST_LSM6DS3_EXT0_ODR_MASK 0x1c +#define ST_LSM6DS3_EXT0_ODR0_HZ 10 +#define ST_LSM6DS3_EXT0_ODR0_VALUE 0x04 +#define ST_LSM6DS3_EXT0_ODR1_HZ 20 +#define ST_LSM6DS3_EXT0_ODR1_VALUE 0x05 +#define ST_LSM6DS3_EXT0_ODR2_HZ 40 +#define ST_LSM6DS3_EXT0_ODR2_VALUE 0x06 +#define ST_LSM6DS3_EXT0_ODR3_HZ 80 +#define ST_LSM6DS3_EXT0_ODR3_VALUE 0x07 +#define ST_LSM6DS3_EXT0_PW_ADDR 0x22 +#define ST_LSM6DS3_EXT0_PW_MASK 0x03 +#define ST_LSM6DS3_EXT0_PW_OFF 0x02 +#define ST_LSM6DS3_EXT0_PW_ON 0x00 +#define ST_LSM6DS3_EXT0_GAIN_VALUE 438 +#define ST_LSM6DS3_EXT0_OUT_X_L_ADDR 0x28 +#define ST_LSM6DS3_EXT0_OUT_Y_L_ADDR 0x2a +#define ST_LSM6DS3_EXT0_OUT_Z_L_ADDR 0x2c +#define ST_LSM6DS3_EXT0_READ_DATA_LEN 6 +#define ST_LSM6DS3_EXT0_BDU_ADDR 0x24 +#define ST_LSM6DS3_EXT0_BDU_MASK 0x40 +#define ST_LSM6DS3_EXT0_STD 3 +#define ST_LSM6DS3_EXT0_BOOT_FUNCTION (&lis3mdl_initialization) +#endif /* CONFIG_ST_LSM6DS3_IIO_EXT0_LIS3MDL */ + +#ifdef CONFIG_ST_LSM6DS3_IIO_EXT0_AKM09912 +static int akm09912_initialization(struct lsm6ds3_sensor_data *sdata, + int ext_num); + +#define ST_LSM6DS3_EXT0_ADDR 0x0c +#define ST_LSM6DS3_EXT0_WAI_ADDR 0x01 +#define ST_LSM6DS3_EXT0_WAI_VALUE 0x04 +#define ST_LSM6DS3_EXT0_RESET_ADDR 0x32 +#define ST_LSM6DS3_EXT0_RESET_MASK 0x01 +#define ST_LSM6DS3_EXT0_FULLSCALE_ADDR 0x00 +#define ST_LSM6DS3_EXT0_FULLSCALE_MASK 0x00 +#define ST_LSM6DS3_EXT0_FULLSCALE_VALUE 0x00 +#define ST_LSM6DS3_EXT0_ODR_ADDR 0x31 +#define ST_LSM6DS3_EXT0_ODR_MASK 0x1f +#define ST_LSM6DS3_EXT0_ODR0_HZ 10 +#define ST_LSM6DS3_EXT0_ODR0_VALUE 0x02 +#define ST_LSM6DS3_EXT0_ODR1_HZ 20 +#define ST_LSM6DS3_EXT0_ODR1_VALUE 0x04 +#define ST_LSM6DS3_EXT0_ODR2_HZ 50 +#define ST_LSM6DS3_EXT0_ODR2_VALUE 0x06 +#define ST_LSM6DS3_EXT0_ODR3_HZ 100 +#define ST_LSM6DS3_EXT0_ODR3_VALUE 0x08 +#define ST_LSM6DS3_EXT0_PW_ADDR ST_LSM6DS3_EXT0_ODR_ADDR +#define ST_LSM6DS3_EXT0_PW_MASK ST_LSM6DS3_EXT0_ODR_MASK +#define ST_LSM6DS3_EXT0_PW_OFF 0x00 +#define ST_LSM6DS3_EXT0_PW_ON ST_LSM6DS3_EXT0_ODR0_VALUE +#define ST_LSM6DS3_EXT0_GAIN_VALUE 1500 +#define ST_LSM6DS3_EXT0_OUT_X_L_ADDR 0x11 +#define ST_LSM6DS3_EXT0_OUT_Y_L_ADDR 0x13 +#define ST_LSM6DS3_EXT0_OUT_Z_L_ADDR 0x15 +#define ST_LSM6DS3_EXT0_READ_DATA_LEN 6 +#define ST_LSM6DS3_EXT0_SENSITIVITY_ADDR 0x60 +#define ST_LSM6DS3_EXT0_SENSITIVITY_LEN 3 +#define ST_LSM6DS3_EXT0_STD 3 +#define ST_LSM6DS3_EXT0_BOOT_FUNCTION (&akm09912_initialization) +#define ST_LSM6DS3_EXT0_DATA_STATUS 0x18 +#endif /* CONFIG_ST_LSM6DS3_IIO_EXT0_AKM09912 */ + +#ifdef CONFIG_ST_LSM6DS3_IIO_EXT1_LPS22HB +static int lps22hb_initialization(struct lsm6ds3_sensor_data *sdata, + int ext_num); + +#define ST_LSM6DS3_EXT1_ADDR 0x5d +#define ST_LSM6DS3_EXT1_WAI_ADDR 0x0f +#define ST_LSM6DS3_EXT1_WAI_VALUE 0xb1 +#define ST_LSM6DS3_EXT1_RESET_ADDR 0x11 +#define ST_LSM6DS3_EXT1_RESET_MASK 0x80 +#define ST_LSM6DS3_EXT1_FULLSCALE_ADDR 0x00 +#define ST_LSM6DS3_EXT1_FULLSCALE_MASK 0x00 +#define ST_LSM6DS3_EXT1_FULLSCALE_VALUE 0x00 +#define ST_LSM6DS3_EXT1_ODR_ADDR 0x10 +#define ST_LSM6DS3_EXT1_ODR_MASK 0x70 +#define ST_LSM6DS3_EXT1_ODR0_HZ 1 +#define ST_LSM6DS3_EXT1_ODR0_VALUE 0x01 +#define ST_LSM6DS3_EXT1_ODR1_HZ 10 +#define ST_LSM6DS3_EXT1_ODR1_VALUE 0x02 +#define ST_LSM6DS3_EXT1_ODR2_HZ 25 +#define ST_LSM6DS3_EXT1_ODR2_VALUE 0x03 +#define ST_LSM6DS3_EXT1_ODR3_HZ 50 +#define ST_LSM6DS3_EXT1_ODR3_VALUE 0x04 +#define ST_LSM6DS3_EXT1_PW_ADDR ST_LSM6DS3_EXT1_ODR_ADDR +#define ST_LSM6DS3_EXT1_PW_MASK ST_LSM6DS3_EXT1_ODR_MASK +#define ST_LSM6DS3_EXT1_PW_OFF 0x00 +#define ST_LSM6DS3_EXT1_PW_ON ST_LSM6DS3_EXT1_ODR0_VALUE +#define ST_LSM6DS3_EXT1_GAIN_VALUE 244 +#define ST_LSM6DS3_EXT1_OUT_P_L_ADDR 0x28 +#define ST_LSM6DS3_EXT1_OUT_T_L_ADDR 0x2b +#define ST_LSM6DS3_EXT1_READ_DATA_LEN 5 +#define ST_LSM6DS3_EXT1_BDU_ADDR 0x10 +#define ST_LSM6DS3_EXT1_BDU_MASK 0x02 +#define ST_LSM6DS3_EXT1_STD 1 +#define ST_LSM6DS3_EXT1_BOOT_FUNCTION (&lps22hb_initialization) +#endif /* CONFIG_ST_LSM6DS3_IIO_EXT1_LPS22HB */ + +/* SENSORS SUFFIX NAMES */ +#define ST_LSM6DS3_EXT0_SUFFIX_NAME "magn" +#define ST_LSM6DS3_EXT1_SUFFIX_NAME "press" + +struct st_lsm6ds3_i2c_master_odr_reg { + unsigned int hz; + u8 value; +}; + +struct st_lsm6ds3_i2c_master_odr_table { + u8 addr; + u8 mask; + struct st_lsm6ds3_i2c_master_odr_reg odr_avl[ST_LSM6DS3_I2C_MASTER_ODR_LIST_NUM]; +}; + +static int st_lsm6ds3_i2c_master_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *ch, int *val, int *val2, long mask); + +static const struct iio_chan_spec st_lsm6ds3_ext0_ch[] = { + ST_LSM6DS3_LSM_CHANNELS(IIO_MAGN, 1, 0, IIO_MOD_X, IIO_LE, + 16, 16, ST_LSM6DS3_EXT0_OUT_X_L_ADDR, 's'), + ST_LSM6DS3_LSM_CHANNELS(IIO_MAGN, 1, 1, IIO_MOD_Y, IIO_LE, + 16, 16, ST_LSM6DS3_EXT0_OUT_Y_L_ADDR, 's'), + ST_LSM6DS3_LSM_CHANNELS(IIO_MAGN, 1, 2, IIO_MOD_Z, IIO_LE, + 16, 16, ST_LSM6DS3_EXT0_OUT_Z_L_ADDR, 's'), + IIO_CHAN_SOFT_TIMESTAMP(3) +}; + +#ifndef CONFIG_ST_LSM6DS3_IIO_EXT1_DISABLED +static const struct iio_chan_spec st_lsm6ds3_ext1_ch[] = { + ST_LSM6DS3_LSM_CHANNELS(IIO_PRESSURE, 0, 0, IIO_NO_MOD, IIO_LE, + 24, 24, ST_LSM6DS3_EXT1_OUT_P_L_ADDR, 'u'), + ST_LSM6DS3_LSM_CHANNELS(IIO_TEMP, 0, 1, IIO_NO_MOD, IIO_LE, + 16, 16, ST_LSM6DS3_EXT1_OUT_T_L_ADDR, 's'), + IIO_CHAN_SOFT_TIMESTAMP(2) +}; +#endif /* CONFIG_ST_LSM6DS3_IIO_EXT1_DISABLED */ + +static int st_lsm6ds3_i2c_master_set_odr(struct lsm6ds3_sensor_data *sdata, + unsigned int odr); + +static ssize_t st_lsm6ds3_i2c_master_sysfs_sampling_frequency_avail( + struct device *dev, struct device_attribute *attr, char *buf) +{ + return scnprintf(buf, PAGE_SIZE, + "%d %d %d %d %d\n", 26, 52, 104, 208, 416); +} + +static ssize_t st_lsm6ds3_i2c_master_sysfs_get_sampling_frequency( + struct device *dev, struct device_attribute *attr, char *buf) +{ + struct lsm6ds3_sensor_data *sdata = iio_priv(dev_get_drvdata(dev)); + + return sprintf(buf, "%d\n", sdata->c_odr); +} + +static ssize_t st_lsm6ds3_i2c_master_sysfs_set_sampling_frequency( + struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + int err, err2 = -EINVAL; + unsigned int odr; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct lsm6ds3_sensor_data *sdata = iio_priv(indio_dev); + + err = kstrtoint(buf, 10, &odr); + if (err < 0) + return err; + + mutex_lock(&indio_dev->mlock); + + switch (odr) { + case 26: + case 52: + case 104: + case 208: + case 416: + if (sdata->c_odr != odr) { + + mutex_lock(&sdata->cdata->passthrough_lock); + + disable_irq(sdata->cdata->irq); + st_lsm6ds3_flush_works(); + + err = st_lsm6ds3_enable_passthrough(sdata->cdata, true); + if (err < 0) { + enable_irq(sdata->cdata->irq); + mutex_unlock(&sdata->cdata->passthrough_lock); + mutex_unlock(&indio_dev->mlock); + return err; + } + + err2 = st_lsm6ds3_i2c_master_set_odr(sdata, odr); + + err = st_lsm6ds3_enable_passthrough(sdata->cdata, + false); + if (err < 0) { + enable_irq(sdata->cdata->irq); + mutex_unlock(&sdata->cdata->passthrough_lock); + mutex_unlock(&indio_dev->mlock); + return err; + } + + enable_irq(sdata->cdata->irq); + mutex_unlock(&sdata->cdata->passthrough_lock); + } + break; + default: + err2 = -EINVAL; + break; + } + + mutex_unlock(&indio_dev->mlock); + + return err2 < 0 ? err2 : size; +} + +static IIO_DEV_ATTR_SAMP_FREQ(S_IWUSR | S_IRUGO, + st_lsm6ds3_i2c_master_sysfs_get_sampling_frequency, + st_lsm6ds3_i2c_master_sysfs_set_sampling_frequency); + +static IIO_DEV_ATTR_SAMP_FREQ_AVAIL( + st_lsm6ds3_i2c_master_sysfs_sampling_frequency_avail); +static ST_LSM6DS3_FIFO_LENGHT(); +static ST_LSM6DS3_FIFO_FLUSH(); + +static struct attribute *st_lsm6ds3_ext0_attributes[] = { + &iio_dev_attr_sampling_frequency_available.dev_attr.attr, + &iio_dev_attr_sampling_frequency.dev_attr.attr, + &iio_dev_attr_hw_fifo_lenght.dev_attr.attr, + &iio_dev_attr_flush.dev_attr.attr, + NULL, +}; + +static const struct attribute_group st_lsm6ds3_ext0_attribute_group = { + .attrs = st_lsm6ds3_ext0_attributes, +}; + +static const struct iio_info st_lsm6ds3_ext0_info = { + .driver_module = THIS_MODULE, + .attrs = &st_lsm6ds3_ext0_attribute_group, + .read_raw = &st_lsm6ds3_i2c_master_read_raw, +}; + +static struct attribute *st_lsm6ds3_ext1_attributes[] = { + &iio_dev_attr_sampling_frequency_available.dev_attr.attr, + &iio_dev_attr_sampling_frequency.dev_attr.attr, + &iio_dev_attr_hw_fifo_lenght.dev_attr.attr, + &iio_dev_attr_flush.dev_attr.attr, + NULL, +}; + +static const struct attribute_group st_lsm6ds3_ext1_attribute_group = { + .attrs = st_lsm6ds3_ext1_attributes, +}; + +static const struct iio_info st_lsm6ds3_ext1_info = { + .driver_module = THIS_MODULE, + .attrs = &st_lsm6ds3_ext1_attribute_group, + .read_raw = &st_lsm6ds3_i2c_master_read_raw, +}; + +struct st_lsm6ds3_iio_info_data { + char suffix_name[20]; + struct iio_info *info; + struct iio_chan_spec *channels; + int num_channels; +}; + +struct st_lsm6ds3_reg { + u8 addr; + u8 mask; + u8 def_value; +}; + +struct st_lsm6ds3_power_reg { + u8 addr; + u8 mask; + u8 off_value; + u8 on_value; + bool isodr; +}; + +struct st_lsm6ds3_custom_function { + int (*boot_initialization) (struct lsm6ds3_sensor_data *sdata, + int ext_num); +}; + +static struct st_lsm6ds3_exs_list { + struct st_lsm6ds3_reg wai; + struct st_lsm6ds3_reg reset; + struct st_lsm6ds3_reg fullscale; + struct st_lsm6ds3_i2c_master_odr_table odr; + struct st_lsm6ds3_power_reg power; + u8 fullscale_value; + u8 samples_to_discard; + u8 read_data_len; + u8 num_data_channels; + bool available; + unsigned int gain; + struct i2c_board_info board_info; + struct st_lsm6ds3_iio_info_data data; + struct st_lsm6ds3_custom_function cf; +} st_lsm6ds3_exs_list[] = { + { + .wai = { + .addr = ST_LSM6DS3_EXT0_WAI_ADDR, + .def_value = ST_LSM6DS3_EXT0_WAI_VALUE, + }, + .reset = { + .addr = ST_LSM6DS3_EXT0_RESET_ADDR, + .mask = ST_LSM6DS3_EXT0_RESET_MASK, + }, + .fullscale = { + .addr = ST_LSM6DS3_EXT0_FULLSCALE_ADDR, + .mask = ST_LSM6DS3_EXT0_FULLSCALE_MASK, + .def_value = ST_LSM6DS3_EXT0_FULLSCALE_VALUE, + }, + .odr = { + .addr = ST_LSM6DS3_EXT0_ODR_ADDR, + .mask = ST_LSM6DS3_EXT0_ODR_MASK, + .odr_avl = { + { + .hz = ST_LSM6DS3_EXT0_ODR0_HZ, + .value = ST_LSM6DS3_EXT0_ODR0_VALUE, + }, + { + .hz = ST_LSM6DS3_EXT0_ODR1_HZ, + .value = ST_LSM6DS3_EXT0_ODR1_VALUE, + }, + { + .hz = ST_LSM6DS3_EXT0_ODR2_HZ, + .value = ST_LSM6DS3_EXT0_ODR2_VALUE, + }, + { + .hz = ST_LSM6DS3_EXT0_ODR3_HZ, + .value = ST_LSM6DS3_EXT0_ODR3_VALUE, + }, + }, + }, + .power = { + .addr = ST_LSM6DS3_EXT0_PW_ADDR, + .mask = ST_LSM6DS3_EXT0_PW_MASK, + .off_value = ST_LSM6DS3_EXT0_PW_OFF, + .on_value = ST_LSM6DS3_EXT0_PW_ON, + }, + .samples_to_discard = ST_LSM6DS3_EXT0_STD, + .read_data_len = ST_LSM6DS3_EXT0_READ_DATA_LEN, + .num_data_channels = ARRAY_SIZE(st_lsm6ds3_ext0_ch) - 1, + .available = false, + .gain = ST_LSM6DS3_EXT0_GAIN_VALUE, + .board_info = { .addr = ST_LSM6DS3_EXT0_ADDR, }, + .data = { + .suffix_name = ST_LSM6DS3_EXT0_SUFFIX_NAME, + .info = (struct iio_info *)&st_lsm6ds3_ext0_info, + .channels = (struct iio_chan_spec *)&st_lsm6ds3_ext0_ch, + .num_channels = ARRAY_SIZE(st_lsm6ds3_ext0_ch), + }, + .cf.boot_initialization = ST_LSM6DS3_EXT0_BOOT_FUNCTION, + }, +#ifndef CONFIG_ST_LSM6DS3_IIO_EXT1_DISABLED + { + .wai = { + .addr = ST_LSM6DS3_EXT1_WAI_ADDR, + .def_value = ST_LSM6DS3_EXT1_WAI_VALUE, + }, + .reset = { + .addr = ST_LSM6DS3_EXT1_RESET_ADDR, + .mask = ST_LSM6DS3_EXT1_RESET_MASK, + }, + .fullscale = { + .addr = ST_LSM6DS3_EXT1_FULLSCALE_ADDR, + .mask = ST_LSM6DS3_EXT1_FULLSCALE_MASK, + .def_value = ST_LSM6DS3_EXT1_FULLSCALE_VALUE, + }, + .odr = { + .addr = ST_LSM6DS3_EXT1_ODR_ADDR, + .mask = ST_LSM6DS3_EXT1_ODR_MASK, + .odr_avl = { + { + .hz = ST_LSM6DS3_EXT1_ODR0_HZ, + .value = ST_LSM6DS3_EXT1_ODR0_VALUE, + }, + { + .hz = ST_LSM6DS3_EXT1_ODR1_HZ, + .value = ST_LSM6DS3_EXT1_ODR1_VALUE, + }, + { + .hz = ST_LSM6DS3_EXT1_ODR2_HZ, + .value = ST_LSM6DS3_EXT1_ODR2_VALUE, + }, + { + .hz = ST_LSM6DS3_EXT1_ODR3_HZ, + .value = ST_LSM6DS3_EXT1_ODR3_VALUE, + }, + }, + }, + .power = { + .addr = ST_LSM6DS3_EXT1_PW_ADDR, + .mask = ST_LSM6DS3_EXT1_PW_MASK, + .off_value = ST_LSM6DS3_EXT1_PW_OFF, + .on_value = ST_LSM6DS3_EXT1_PW_ON, + }, + .samples_to_discard = ST_LSM6DS3_EXT1_STD, + .read_data_len = ST_LSM6DS3_EXT1_READ_DATA_LEN, + .num_data_channels = ARRAY_SIZE(st_lsm6ds3_ext1_ch) - 1, + .available = false, + .gain = ST_LSM6DS3_EXT1_GAIN_VALUE, + .board_info = { .addr = ST_LSM6DS3_EXT1_ADDR, }, + .data = { + .suffix_name = ST_LSM6DS3_EXT1_SUFFIX_NAME, + .info = (struct iio_info *)&st_lsm6ds3_ext1_info, + .channels = (struct iio_chan_spec *)&st_lsm6ds3_ext1_ch, + .num_channels = ARRAY_SIZE(st_lsm6ds3_ext1_ch), + }, + .cf.boot_initialization = ST_LSM6DS3_EXT1_BOOT_FUNCTION, + }, +#else /* CONFIG_ST_LSM6DS3_IIO_EXT1_DISABLED */ + { + }, +#endif /* CONFIG_ST_LSM6DS3_IIO_EXT1_DISABLED */ +}; + +static int st_lsm6ds3_i2c_master_read(struct i2c_client *client, + u8 reg_addr, int len, u8 *data) +{ + struct i2c_msg msg[2]; + + msg[0].addr = client->addr; + msg[0].flags = client->flags; + msg[0].len = 1; + msg[0].buf = ®_addr; + + msg[1].addr = client->addr; + msg[1].flags = client->flags | I2C_M_RD; + msg[1].len = len; + msg[1].buf = data; + + return i2c_transfer(client->adapter, msg, 2); +} + +static int st_lsm6ds3_i2c_master_write(struct i2c_client *client, + u8 reg_addr, int len, u8 *data) +{ + u8 send[len + 1]; + struct i2c_msg msg; + + send[0] = reg_addr; + memcpy(&send[1], data, len * sizeof(u8)); + len++; + + msg.addr = client->addr; + msg.flags = client->flags; + msg.len = len; + msg.buf = send; + + return i2c_transfer(client->adapter, &msg, 1); +} + +static int st_lsm6ds3_i2c_master_write_data_with_mask(struct i2c_client *client, + u8 reg_addr, u8 mask, u8 data) +{ + int err; + u8 new_data = 0x00, old_data = 0x00; + + err = st_lsm6ds3_i2c_master_read(client, reg_addr, 1, &old_data); + if (err < 0) + return err; + + new_data = ((old_data & (~mask)) | ((data << __ffs(mask)) & mask)); + + if (new_data == old_data) + return 1; + + return st_lsm6ds3_i2c_master_write(client, reg_addr, 1, &new_data); +} + +#ifdef CONFIG_ST_LSM6DS3_IIO_EXT0_LIS3MDL +static int lis3mdl_initialization(struct lsm6ds3_sensor_data *sdata, + int ext_num) +{ + + return st_lsm6ds3_i2c_master_write_data_with_mask( + sdata->cdata->master_client[ext_num], + ST_LSM6DS3_EXT0_BDU_ADDR, + ST_LSM6DS3_EXT0_BDU_MASK, ST_LSM6DS3_EN_BIT); +} +#endif /* CONFIG_ST_LSM6DS3_IIO_EXT0_LIS3MDL */ + +#ifdef CONFIG_ST_LSM6DS3_IIO_EXT0_AKM09912 +static int akm09912_initialization(struct lsm6ds3_sensor_data *sdata, + int ext_num) +{ + int err; + u8 data[ST_LSM6DS3_EXT0_SENSITIVITY_LEN]; + + err = st_lsm6ds3_i2c_master_read(sdata->cdata->master_client[ext_num], + ST_LSM6DS3_EXT0_SENSITIVITY_ADDR, + ST_LSM6DS3_EXT0_SENSITIVITY_LEN, + data); + if (err < 0) + return err; + + sdata->c_gain[0] *= ((((data[0] - 128) * 1000) >> 8) + 1000); + sdata->c_gain[1] *= ((((data[1] - 128) * 1000) >> 8) + 1000); + sdata->c_gain[2] *= ((((data[2] - 128) * 1000) >> 8) + 1000); + + return 0; +} +#endif /* CONFIG_ST_LSM6DS3_IIO_EXT0_AKM09912 */ + +#ifdef CONFIG_ST_LSM6DS3_IIO_EXT1_LPS22HB +static int lps22hb_initialization(struct lsm6ds3_sensor_data *sdata, + int ext_num) +{ + + return st_lsm6ds3_i2c_master_write_data_with_mask( + sdata->cdata->master_client[ext_num], + ST_LSM6DS3_EXT1_BDU_ADDR, + ST_LSM6DS3_EXT1_BDU_MASK, ST_LSM6DS3_EN_BIT); +} +#endif /* CONFIG_ST_LSM6DS3_IIO_EXT1_LPS22HB */ + +static int st_lsm6ds3_i2c_master_set_odr(struct lsm6ds3_sensor_data *sdata, + unsigned int odr) +{ + int err, i, ext_num = sdata->sindex - ST_INDIO_DEV_EXT0; + + for (i = 0; i < ST_LSM6DS3_I2C_MASTER_ODR_LIST_NUM; i++) { + if (st_lsm6ds3_exs_list[ext_num].odr.odr_avl[i].hz >= odr) + break; + } + if (i == ST_LSM6DS3_I2C_MASTER_ODR_LIST_NUM) + i--; + + if (sdata->cdata->sensors_enabled & (1 << sdata->sindex)) { + err = st_lsm6ds3_i2c_master_write_data_with_mask( + sdata->cdata->master_client[ext_num], + st_lsm6ds3_exs_list[ext_num].odr.addr, + st_lsm6ds3_exs_list[ext_num].odr.mask, + st_lsm6ds3_exs_list[ext_num].odr.odr_avl[i].value); + if (err < 0) + return err; + + sdata->cdata->ext_samples_to_discard[ext_num] = + st_lsm6ds3_exs_list[ext_num].samples_to_discard; + + sdata->c_odr = odr; + + if (st_lsm6ds3_exs_list[ext_num].power.isodr) + st_lsm6ds3_exs_list[ext_num].power.on_value = + st_lsm6ds3_exs_list[ext_num].odr.odr_avl[i].value; + + st_lsm6ds3_reconfigure_fifo(sdata->cdata, false); + } else { + sdata->c_odr = odr; + + if (st_lsm6ds3_exs_list[ext_num].power.isodr) + st_lsm6ds3_exs_list[ext_num].power.on_value = + st_lsm6ds3_exs_list[ext_num].odr.odr_avl[i].value; + } + + return 0; +} + +static int st_lsm6ds3_i2c_master_set_enable(struct lsm6ds3_sensor_data *sdata, + bool enable) +{ + u8 reg_value; + int err, ext_num = sdata->sindex - ST_INDIO_DEV_EXT0; + + if (enable) + reg_value = st_lsm6ds3_exs_list[ext_num].power.on_value; + else + reg_value = st_lsm6ds3_exs_list[ext_num].power.off_value; + + err = st_lsm6ds3_i2c_master_write_data_with_mask( + sdata->cdata->master_client[ext_num], + st_lsm6ds3_exs_list[ext_num].power.addr, + st_lsm6ds3_exs_list[ext_num].power.mask, + reg_value); + if (err < 0) + return err; + + sdata->cdata->ext_samples_to_discard[ext_num] = + st_lsm6ds3_exs_list[ext_num].samples_to_discard; + + if (enable) + sdata->cdata->sensors_enabled |= (1 << sdata->sindex); + else + sdata->cdata->sensors_enabled &= ~(1 << sdata->sindex); + + return 0; +} + +static int st_lsm6ds3_i2c_master_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *ch, int *val, int *val2, long mask) +{ + int err; + u8 outdata[(ch->scan_type.storagebits >> 3) + 1]; + struct lsm6ds3_sensor_data *sdata = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&indio_dev->mlock); + + if (indio_dev->currentmode == INDIO_BUFFER_TRIGGERED) { + mutex_unlock(&indio_dev->mlock); + return -EBUSY; + } + + mutex_lock(&sdata->cdata->passthrough_lock); + + err = st_lsm6ds3_enable_accel_dependency(sdata, true); + if (err < 0) + return err; + + disable_irq(sdata->cdata->irq); + st_lsm6ds3_flush_works(); + + err = st_lsm6ds3_enable_passthrough(sdata->cdata, true); + if (err < 0) + goto read_raw_reset_passthrough; + + err = st_lsm6ds3_i2c_master_set_enable(sdata, true); + if (err < 0) + goto read_raw_reset_passthrough; + + memset(outdata, 0, (ch->scan_type.storagebits >> 3) + 1); + + msleep(200); + + err = st_lsm6ds3_i2c_master_read( + sdata->cdata->master_client[sdata->sindex - + ST_INDIO_DEV_EXT0], ch->address, + ch->scan_type.storagebits >> 3, outdata); + if (err < 0) + goto read_raw_reset_passthrough; + + err = st_lsm6ds3_i2c_master_set_enable(sdata, false); + if (err < 0) + goto read_raw_reset_passthrough; + + err = st_lsm6ds3_enable_passthrough(sdata->cdata, false); + if (err < 0) + goto read_raw_reset_passthrough; + + enable_irq(sdata->cdata->irq); + mutex_unlock(&sdata->cdata->passthrough_lock); + + err = st_lsm6ds3_enable_accel_dependency(sdata, false); + if (err < 0) + return err; + + if ((ch->scan_type.storagebits >> 3) > 2) + *val = (s32)get_unaligned_le32(outdata); + else + *val = (s16)get_unaligned_le16(outdata); + + *val = *val >> ch->scan_type.shift; + + mutex_unlock(&indio_dev->mlock); + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = sdata->c_gain[ch->scan_index]; + + if (ch->type == IIO_TEMP) { + *val = 1; + *val2 = 0; + return IIO_VAL_INT; + } + +#ifdef CONFIG_ST_LSM6DS3_IIO_EXT0_AKM09912 + if (sdata->sindex == ST_INDIO_DEV_EXT0) + return IIO_VAL_INT_PLUS_NANO; +#endif /* CONFIG_ST_LSM6DS3_IIO_EXT0_AKM09912 */ + + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + + return 0; + +read_raw_reset_passthrough: + st_lsm6ds3_enable_passthrough(sdata->cdata, false); + enable_irq(sdata->cdata->irq); + mutex_unlock(&sdata->cdata->passthrough_lock); + st_lsm6ds3_enable_accel_dependency(sdata, false); + return err; +} + +static int st_lsm6ds3_i2c_master_buffer_preenable(struct iio_dev *indio_dev) +{ + int err; + struct lsm6ds3_sensor_data *sdata = iio_priv(indio_dev); + + mutex_lock(&sdata->cdata->passthrough_lock); + + err = st_lsm6ds3_enable_accel_dependency(sdata, true); + if (err < 0) + return err; + + disable_irq(sdata->cdata->irq); + st_lsm6ds3_flush_works(); + + err = st_lsm6ds3_enable_passthrough(sdata->cdata, true); + if (err < 0) + goto preenable_reset_passthrough; + + err = st_lsm6ds3_i2c_master_set_enable(sdata, true); + if (err < 0) + goto preenable_reset_passthrough; + + err = st_lsm6ds3_enable_passthrough(sdata->cdata, false); + if (err < 0) + goto preenable_reset_passthrough; + + enable_irq(sdata->cdata->irq); + mutex_unlock(&sdata->cdata->passthrough_lock); + + err = st_lsm6ds3_reconfigure_fifo(sdata->cdata, true); + if (err < 0) + return err; + + return iio_sw_buffer_preenable(indio_dev); + +preenable_reset_passthrough: + st_lsm6ds3_enable_passthrough(sdata->cdata, false); + enable_irq(sdata->cdata->irq); + mutex_unlock(&sdata->cdata->passthrough_lock); + + return err; +} + +static int st_lsm6ds3_i2c_master_buffer_postenable(struct iio_dev *indio_dev) +{ + int err; + struct lsm6ds3_sensor_data *sdata = iio_priv(indio_dev); + + sdata->buffer_data = kmalloc(indio_dev->scan_bytes, GFP_KERNEL); + if (sdata->buffer_data == NULL) + return -ENOMEM; + + err = iio_triggered_buffer_postenable(indio_dev); + if (err < 0) + goto free_buffer_data; + + return 0; + +free_buffer_data: + kfree(sdata->buffer_data); + + return err; +} + +static int st_lsm6ds3_i2c_master_buffer_predisable(struct iio_dev *indio_dev) +{ + int err; + struct lsm6ds3_sensor_data *sdata = iio_priv(indio_dev); + + err = iio_triggered_buffer_predisable(indio_dev); + if (err < 0) + return err; + + mutex_lock(&sdata->cdata->passthrough_lock); + + disable_irq(sdata->cdata->irq); + st_lsm6ds3_flush_works(); + + err = st_lsm6ds3_enable_passthrough(sdata->cdata, true); + if (err < 0) + goto predisable_reset_passthrough; + + err = st_lsm6ds3_i2c_master_set_enable(sdata, false); + if (err < 0) + goto predisable_reset_passthrough; + + err = st_lsm6ds3_enable_passthrough(sdata->cdata, false); + if (err < 0) + goto predisable_reset_passthrough; + + enable_irq(sdata->cdata->irq); + mutex_unlock(&sdata->cdata->passthrough_lock); + + err = st_lsm6ds3_enable_accel_dependency(sdata, false); + if (err < 0) + return err; + + err = st_lsm6ds3_reconfigure_fifo(sdata->cdata, true); + if (err < 0) + return err; + + kfree(sdata->buffer_data); + + return 0; + +predisable_reset_passthrough: + st_lsm6ds3_enable_passthrough(sdata->cdata, false); + enable_irq(sdata->cdata->irq); + mutex_unlock(&sdata->cdata->passthrough_lock); + + return err; +} + +static const struct iio_trigger_ops st_lsm6ds3_i2c_master_trigger_ops = { + .owner = THIS_MODULE, + .set_trigger_state = &st_lsm6ds3_trig_set_state, +}; + +int st_lsm6ds3_i2c_master_allocate_trigger(struct lsm6ds3_data *cdata, + int dev_index) +{ + int err; + + cdata->trig[dev_index] = iio_trigger_alloc("%s-trigger", + cdata->indio_dev[dev_index]->name); + if (!cdata->trig[dev_index]) { + dev_err(cdata->dev, "failed to allocate iio trigger.\n"); + return -ENOMEM; + } + + iio_trigger_set_drvdata(cdata->trig[dev_index], + cdata->indio_dev[dev_index]); + cdata->trig[dev_index]->ops = &st_lsm6ds3_i2c_master_trigger_ops; + cdata->trig[dev_index]->dev.parent = cdata->dev; + + err = iio_trigger_register(cdata->trig[dev_index]); + if (err < 0) { + dev_err(cdata->dev, "failed to register iio trigger.\n"); + goto deallocate_trigger; + } + + cdata->indio_dev[dev_index]->trig = cdata->trig[dev_index]; + + return 0; + +deallocate_trigger: + iio_trigger_free(cdata->trig[dev_index]); + return err; +} + +static void st_lsm6ds3_i2c_master_deallocate_trigger(struct lsm6ds3_data *cdata, + int dev_index) +{ + iio_trigger_unregister(cdata->trig[dev_index]); +} + +static const struct iio_buffer_setup_ops st_lsm6ds3_i2c_master_buffer_setup_ops = { + .preenable = &st_lsm6ds3_i2c_master_buffer_preenable, + .postenable = &st_lsm6ds3_i2c_master_buffer_postenable, + .predisable = &st_lsm6ds3_i2c_master_buffer_predisable, +}; + +static inline irqreturn_t st_lsm6ds3_i2c_master_handler_empty(int irq, void *p) +{ + return IRQ_HANDLED; +} + +static int st_lsm6ds3_i2c_master_allocate_buffer(struct lsm6ds3_data *cdata, + int dev_index) +{ + return iio_triggered_buffer_setup(cdata->indio_dev[dev_index], + &st_lsm6ds3_i2c_master_handler_empty, NULL, + &st_lsm6ds3_i2c_master_buffer_setup_ops); +} + +static void st_lsm6ds3_i2c_master_deallocate_buffer(struct lsm6ds3_data *cdata, + int dev_index) +{ + iio_triggered_buffer_cleanup(cdata->indio_dev[dev_index]); +} + +static int st_lsm6ds3_i2c_master_send_sensor_hub_parameters( + struct lsm6ds3_sensor_data *sdata, int ext_num) +{ + int err; + u8 i2c_address_reg, data_start_address_reg, slave_num; + u8 i2c_address, data_start_address, config_reg_addr, config_reg_mask; +#ifdef CONFIG_ST_LSM6DS3_IIO_EXT0_AKM09912 + u8 i2c_address_reg_akm, data_start_address_reg_akm, temp_reg; + u8 config_reg_addr_akm, config_reg_mask_akm; +#endif /* CONFIG_ST_LSM6DS3_IIO_EXT0_AKM09912 */ + + switch (ext_num) { + case ST_LSM6DS3_EXT0_INDEX: + i2c_address_reg = ST_LSM6DS3_SLV0_ADDR_ADDR; + data_start_address_reg = ST_LSM6DS3_SLV0_SUBADDR_ADDR; + config_reg_addr = ST_LSM6DS3_SLV0_CONFIG_ADDR; + config_reg_mask = ST_LSM6DS3_SLV0_CONFIG_MASK; + break; + case ST_LSM6DS3_EXT1_INDEX: + i2c_address_reg = ST_LSM6DS3_SLV1_ADDR_ADDR; + data_start_address_reg = ST_LSM6DS3_SLV1_SUBADDR_ADDR; + config_reg_addr = ST_LSM6DS3_SLV1_CONFIG_ADDR; + config_reg_mask = ST_LSM6DS3_SLV1_CONFIG_MASK; + break; + default: + return -EINVAL; + } + + i2c_address = (st_lsm6ds3_exs_list[ext_num].board_info.addr << 1) | + ST_LSM6DS3_EN_BIT; + + data_start_address = + st_lsm6ds3_exs_list[ext_num].data.channels[0].address; + + mutex_lock(&sdata->cdata->bank_registers_lock); + + err = st_lsm6ds3_write_data_with_mask(sdata->cdata, + ST_LSM6DS3_FUNC_CFG_ACCESS_ADDR, + ST_LSM6DS3_FUNC_CFG_REG2_MASK, + ST_LSM6DS3_EN_BIT, false); + if (err < 0) + goto st_lsm6ds3_init_sensor_mutex_unlock; + + err = sdata->cdata->tf->write(sdata->cdata, i2c_address_reg, + 1, &i2c_address, false); + if (err < 0) + goto st_lsm6ds3_init_sensor_mutex_unlock; + + err = sdata->cdata->tf->write(sdata->cdata, + data_start_address_reg, + 1, &data_start_address, false); + if (err < 0) + goto st_lsm6ds3_init_sensor_mutex_unlock; + + err = st_lsm6ds3_write_data_with_mask(sdata->cdata, + config_reg_addr, config_reg_mask, + st_lsm6ds3_exs_list[ext_num].read_data_len, + false); + if (err < 0) + goto st_lsm6ds3_init_sensor_mutex_unlock; + + if (ext_num == ST_LSM6DS3_EXT0_INDEX) { + if (sdata->cdata->ext1_available) { +#ifdef CONFIG_ST_LSM6DS3_IIO_EXT0_AKM09912 + slave_num = ST_LSM6DS3_SLV_AUX_3; + i2c_address_reg_akm = ST_LSM6DS3_SLV2_ADDR_ADDR; + data_start_address_reg_akm = ST_LSM6DS3_SLV2_SUBADDR_ADDR; + config_reg_addr_akm = ST_LSM6DS3_SLV2_CONFIG_ADDR; + config_reg_mask_akm = ST_LSM6DS3_SLV2_CONFIG_MASK; +#else /* CONFIG_ST_LSM6DS3_IIO_EXT0_AKM09912 */ + slave_num = ST_LSM6DS3_SLV_AUX_2; +#endif /* CONFIG_ST_LSM6DS3_IIO_EXT0_AKM09912 */ + } else { +#ifdef CONFIG_ST_LSM6DS3_IIO_EXT0_AKM09912 + slave_num = ST_LSM6DS3_SLV_AUX_2; + i2c_address_reg_akm = ST_LSM6DS3_SLV1_ADDR_ADDR; + data_start_address_reg_akm = ST_LSM6DS3_SLV1_SUBADDR_ADDR; + config_reg_addr_akm = ST_LSM6DS3_SLV1_CONFIG_ADDR; + config_reg_mask_akm = ST_LSM6DS3_SLV1_CONFIG_MASK; +#else /* CONFIG_ST_LSM6DS3_IIO_EXT0_AKM09912 */ + slave_num = ST_LSM6DS3_SLV_AUX_1; +#endif /* CONFIG_ST_LSM6DS3_IIO_EXT0_AKM09912 */ + } + + err = st_lsm6ds3_write_data_with_mask(sdata->cdata, + ST_LSM6DS3_SLV_AUX_ADDR, + ST_LSM6DS3_SLV_AUX_MASK, + slave_num, false); + if (err < 0) + goto st_lsm6ds3_init_sensor_mutex_unlock; + +#ifdef CONFIG_ST_LSM6DS3_IIO_EXT0_AKM09912 + temp_reg = (ST_LSM6DS3_EXT0_ADDR << 1) | ST_LSM6DS3_EN_BIT; + + err = sdata->cdata->tf->write(sdata->cdata, i2c_address_reg_akm, + 1, &temp_reg, false); + if (err < 0) + goto st_lsm6ds3_init_sensor_mutex_unlock; + + temp_reg = ST_LSM6DS3_EXT0_DATA_STATUS; + + err = sdata->cdata->tf->write(sdata->cdata, + data_start_address_reg_akm, + 1, &temp_reg, false); + if (err < 0) + goto st_lsm6ds3_init_sensor_mutex_unlock; + + err = st_lsm6ds3_write_data_with_mask(sdata->cdata, + config_reg_addr_akm, config_reg_mask_akm, + 1, false); + if (err < 0) + goto st_lsm6ds3_init_sensor_mutex_unlock; +#endif /* CONFIG_ST_LSM6DS3_IIO_EXT0_AKM09912 */ + } + + err = st_lsm6ds3_write_data_with_mask(sdata->cdata, + ST_LSM6DS3_FUNC_CFG_ACCESS_ADDR, + ST_LSM6DS3_FUNC_CFG_REG2_MASK, + ST_LSM6DS3_DIS_BIT, false); + if (err < 0) + goto st_lsm6ds3_init_sensor_mutex_unlock; + + mutex_unlock(&sdata->cdata->bank_registers_lock); + + return 0; + +st_lsm6ds3_init_sensor_mutex_unlock: + mutex_unlock(&sdata->cdata->bank_registers_lock); + return err; +} + +static int st_lsm6ds3_i2c_master_init_sensor(struct lsm6ds3_sensor_data *sdata, + int ext_num, int dev_index) +{ + int err; + + mutex_lock(&sdata->cdata->passthrough_lock); + + err = st_lsm6ds3_enable_passthrough(sdata->cdata, true); + if (err < 0) + goto unlock_passthrough; + + sdata->c_gain[0] = st_lsm6ds3_exs_list[ext_num].gain; + sdata->c_gain[1] = st_lsm6ds3_exs_list[ext_num].gain; + sdata->c_gain[2] = st_lsm6ds3_exs_list[ext_num].gain; + + if ((st_lsm6ds3_exs_list[ext_num].power.addr == + st_lsm6ds3_exs_list[ext_num].odr.addr) && + (st_lsm6ds3_exs_list[ext_num].power.mask == + st_lsm6ds3_exs_list[ext_num].odr.mask)) + st_lsm6ds3_exs_list[ext_num].power.isodr = true; + else + st_lsm6ds3_exs_list[ext_num].power.isodr = false; + + err = st_lsm6ds3_i2c_master_write_data_with_mask( + sdata->cdata->master_client[ext_num], + st_lsm6ds3_exs_list[ext_num].reset.addr, + st_lsm6ds3_exs_list[ext_num].reset.mask, + ST_LSM6DS3_EN_BIT); + if (err < 0) + goto unlock_passthrough; + + usleep_range(3000, 8000); + + if (st_lsm6ds3_exs_list[ext_num].fullscale.addr > 0) { + err = st_lsm6ds3_i2c_master_write_data_with_mask( + sdata->cdata->master_client[ext_num], + st_lsm6ds3_exs_list[ext_num].fullscale.addr, + st_lsm6ds3_exs_list[ext_num].fullscale.mask, + st_lsm6ds3_exs_list[ext_num].fullscale.def_value); + if (err < 0) + goto unlock_passthrough; + } + + if (st_lsm6ds3_exs_list[ext_num].cf.boot_initialization != NULL) { + err = st_lsm6ds3_exs_list[ext_num].cf.boot_initialization(sdata, ext_num); + if (err < 0) + goto unlock_passthrough; + } + + err = st_lsm6ds3_i2c_master_set_enable(sdata, false); + if (err < 0) + goto unlock_passthrough; + + err = st_lsm6ds3_i2c_master_set_odr(sdata, 26); + if (err < 0) + goto unlock_passthrough; + + err = st_lsm6ds3_i2c_master_send_sensor_hub_parameters(sdata, ext_num); + if (err < 0) + goto unlock_passthrough; + + err = st_lsm6ds3_enable_passthrough(sdata->cdata, false); + if (err < 0) + goto unlock_passthrough; + + mutex_unlock(&sdata->cdata->passthrough_lock); + + return 0; + +unlock_passthrough: + mutex_unlock(&sdata->cdata->passthrough_lock); + return err; +} + +static int st_lsm6ds3_i2c_master_allocate_device(struct lsm6ds3_data *cdata, + int ext_num) +{ + int err, dev_index; + struct lsm6ds3_sensor_data *sdata_ext; + + switch (ext_num) { + case ST_LSM6DS3_EXT0_INDEX: + dev_index = ST_INDIO_DEV_EXT0; + break; + case ST_LSM6DS3_EXT1_INDEX: + dev_index = ST_INDIO_DEV_EXT1; + break; + default: + return -EINVAL; + } + + cdata->indio_dev[dev_index] = iio_device_alloc(sizeof(*sdata_ext)); + if (!cdata->indio_dev[dev_index]) + return -ENOMEM; + + sdata_ext = iio_priv(cdata->indio_dev[dev_index]); + sdata_ext->cdata = cdata; + sdata_ext->sindex = dev_index; + + sdata_ext->num_data_channels = + st_lsm6ds3_exs_list[ext_num].num_data_channels; + + cdata->indio_dev[dev_index]->name = kasprintf(GFP_KERNEL, + "%s_%s", cdata->name, + st_lsm6ds3_exs_list[ext_num].data.suffix_name); + + cdata->indio_dev[dev_index]->info = + st_lsm6ds3_exs_list[ext_num].data.info; + cdata->indio_dev[dev_index]->channels = + st_lsm6ds3_exs_list[ext_num].data.channels; + cdata->indio_dev[dev_index]->num_channels = + st_lsm6ds3_exs_list[ext_num].data.num_channels; + + cdata->indio_dev[dev_index]->modes = INDIO_DIRECT_MODE; + + err = st_lsm6ds3_i2c_master_init_sensor(sdata_ext, ext_num, dev_index); + if (err < 0) + goto iio_device_free; + + err = st_lsm6ds3_i2c_master_allocate_buffer(cdata, dev_index); + if (err < 0) + goto iio_device_free; + + err = st_lsm6ds3_i2c_master_allocate_trigger(cdata, dev_index); + if (err < 0) + goto iio_deallocate_buffer; + + err = iio_device_register(cdata->indio_dev[dev_index]); + if (err < 0) + goto iio_deallocate_trigger; + + return 0; + +iio_deallocate_trigger: + st_lsm6ds3_i2c_master_deallocate_trigger(cdata, dev_index); +iio_deallocate_buffer: + st_lsm6ds3_i2c_master_deallocate_buffer(cdata, dev_index); +iio_device_free: + iio_device_free(cdata->indio_dev[dev_index]); + + return err; +} + +static void st_lsm6ds3_i2c_master_deallocate_device(struct lsm6ds3_data *cdata, + int ext_num) +{ + int dev_index; + + switch (ext_num) { + case ST_LSM6DS3_EXT0_INDEX: + dev_index = ST_INDIO_DEV_EXT0; + break; + case ST_LSM6DS3_EXT1_INDEX: + dev_index = ST_INDIO_DEV_EXT1; + break; + default: + return; + } + + iio_device_unregister(cdata->indio_dev[dev_index]); + st_lsm6ds3_i2c_master_deallocate_trigger(cdata, dev_index); + st_lsm6ds3_i2c_master_deallocate_buffer(cdata, dev_index); + iio_device_free(cdata->indio_dev[dev_index]); +} + +int st_lsm6ds3_i2c_master_probe(struct lsm6ds3_data *cdata) +{ + u8 wai; + int err; + struct i2c_client *client = to_i2c_client(cdata->dev); + + cdata->ext0_available = false; + cdata->ext1_available = false; + + cdata->ext0_samples_in_pattern = 0; + cdata->ext1_samples_in_pattern = 0; + + sprintf(st_lsm6ds3_exs_list[ST_LSM6DS3_EXT0_INDEX].board_info.type, + "%s_ext%d", client->name, ST_LSM6DS3_EXT0_INDEX); + + cdata->master_client[ST_LSM6DS3_EXT0_INDEX] = + i2c_new_device(client->adapter, + &st_lsm6ds3_exs_list[ST_LSM6DS3_EXT0_INDEX].board_info); + if (!cdata->master_client[ST_LSM6DS3_EXT0_INDEX]) + return -ENOMEM; + +#ifndef CONFIG_ST_LSM6DS3_IIO_EXT1_DISABLED + sprintf(st_lsm6ds3_exs_list[ST_LSM6DS3_EXT1_INDEX].board_info.type, + "%s_ext%d", client->name, ST_LSM6DS3_EXT1_INDEX); + + cdata->master_client[ST_LSM6DS3_EXT1_INDEX] = + i2c_new_device(client->adapter, + &st_lsm6ds3_exs_list[ST_LSM6DS3_EXT1_INDEX].board_info); + if (!cdata->master_client[ST_LSM6DS3_EXT1_INDEX]) { + err = -ENOMEM; + goto unregister_ext0_i2c_client; + } +#endif /* CONFIG_ST_LSM6DS3_IIO_EXT1_DISABLED */ + + mutex_lock(&cdata->passthrough_lock); + + err = st_lsm6ds3_enable_passthrough(cdata, true); + if (err < 0) + goto master_probe_passthrough_lock; + + err = st_lsm6ds3_i2c_master_read( + cdata->master_client[ST_LSM6DS3_EXT0_INDEX], + st_lsm6ds3_exs_list[ST_LSM6DS3_EXT0_INDEX].wai.addr, 1, &wai); + if (err < 0) { + dev_err(cdata->dev, "external sensor 0 not available\n"); + i2c_unregister_device(cdata->master_client[ST_LSM6DS3_EXT0_INDEX]); + } else { + if (wai != st_lsm6ds3_exs_list[ST_LSM6DS3_EXT0_INDEX].wai.def_value) { + dev_err(cdata->dev, "wai value of external sensor 0 mismatch\n"); + i2c_unregister_device(cdata->master_client[ST_LSM6DS3_EXT0_INDEX]); + } else + cdata->ext0_available = true; + } + +#ifndef CONFIG_ST_LSM6DS3_IIO_EXT1_DISABLED + err = st_lsm6ds3_i2c_master_read( + cdata->master_client[ST_LSM6DS3_EXT1_INDEX], + st_lsm6ds3_exs_list[ST_LSM6DS3_EXT1_INDEX].wai.addr, 1, &wai); + if (err < 0) { + dev_err(cdata->dev, "external sensor 1 not available\n"); + i2c_unregister_device(cdata->master_client[ST_LSM6DS3_EXT1_INDEX]); + } else { + if (wai != st_lsm6ds3_exs_list[ST_LSM6DS3_EXT1_INDEX].wai.def_value) { + dev_err(cdata->dev, "wai value of external sensor 1 mismatch\n"); + i2c_unregister_device(cdata->master_client[ST_LSM6DS3_EXT1_INDEX]); + } else + cdata->ext1_available = true; + } +#endif /* CONFIG_ST_LSM6DS3_IIO_EXT1_DISABLED */ + + err = st_lsm6ds3_enable_passthrough(cdata, false); + if (err < 0) { + if (cdata->ext0_available) + i2c_unregister_device(cdata->master_client[ST_LSM6DS3_EXT0_INDEX]); + +#ifndef CONFIG_ST_LSM6DS3_IIO_EXT1_DISABLED + if (cdata->ext1_available) + i2c_unregister_device(cdata->master_client[ST_LSM6DS3_EXT1_INDEX]); +#endif /* CONFIG_ST_LSM6DS3_IIO_EXT1_DISABLED */ + + mutex_unlock(&cdata->passthrough_lock); + + return err; + } + + mutex_unlock(&cdata->passthrough_lock); + + if (cdata->ext0_available) { + err = st_lsm6ds3_i2c_master_allocate_device(cdata, ST_LSM6DS3_EXT0_INDEX); + if (err < 0) + goto unregister_with_check_i2c_clients; + + } + +#ifndef CONFIG_ST_LSM6DS3_IIO_EXT1_DISABLED + if (cdata->ext1_available) { + err = st_lsm6ds3_i2c_master_allocate_device(cdata, ST_LSM6DS3_EXT1_INDEX); + if (err < 0) + goto deallocate_ext0_device; + + } +#endif /* CONFIG_ST_LSM6DS3_IIO_EXT1_DISABLED */ + + return 0; + +#ifndef CONFIG_ST_LSM6DS3_IIO_EXT1_DISABLED +deallocate_ext0_device: +#endif /* CONFIG_ST_LSM6DS3_IIO_EXT1_DISABLED */ + st_lsm6ds3_i2c_master_deallocate_device(cdata, ST_LSM6DS3_EXT0_INDEX); +unregister_with_check_i2c_clients: +#ifndef CONFIG_ST_LSM6DS3_IIO_EXT1_DISABLED + if (cdata->ext1_available) + i2c_unregister_device(cdata->master_client[ST_LSM6DS3_EXT1_INDEX]); +#endif /* CONFIG_ST_LSM6DS3_IIO_EXT1_DISABLED */ + if (cdata->ext0_available) + i2c_unregister_device(cdata->master_client[ST_LSM6DS3_EXT0_INDEX]); + + return err; + +master_probe_passthrough_lock: + mutex_unlock(&cdata->passthrough_lock); +#ifndef CONFIG_ST_LSM6DS3_IIO_EXT1_DISABLED + i2c_unregister_device(cdata->master_client[ST_LSM6DS3_EXT1_INDEX]); +unregister_ext0_i2c_client: +#endif /* CONFIG_ST_LSM6DS3_IIO_EXT1_DISABLED */ + i2c_unregister_device(cdata->master_client[ST_LSM6DS3_EXT0_INDEX]); + + return err; +} +EXPORT_SYMBOL(st_lsm6ds3_i2c_master_probe); + +int st_lsm6ds3_i2c_master_exit(struct lsm6ds3_data *cdata) +{ + if (cdata->ext0_available) { + st_lsm6ds3_i2c_master_deallocate_device(cdata, ST_LSM6DS3_EXT0_INDEX); + i2c_unregister_device(cdata->master_client[ST_LSM6DS3_EXT0_INDEX]); + } +#ifndef CONFIG_ST_LSM6DS3_IIO_EXT1_DISABLED + if (cdata->ext1_available) { + st_lsm6ds3_i2c_master_deallocate_device(cdata, ST_LSM6DS3_EXT1_INDEX); + i2c_unregister_device(cdata->master_client[ST_LSM6DS3_EXT1_INDEX]); + } +#endif /* CONFIG_ST_LSM6DS3_IIO_EXT1_DISABLED */ + + return 0; +} +EXPORT_SYMBOL(st_lsm6ds3_i2c_master_exit); + +MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics lsm6ds3 i2c master driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/imu/st_lsm6ds3/st_lsm6ds3_spi.c b/drivers/iio/imu/st_lsm6ds3/st_lsm6ds3_spi.c new file mode 100644 index 00000000000..7212ae225f8 --- /dev/null +++ b/drivers/iio/imu/st_lsm6ds3/st_lsm6ds3_spi.c @@ -0,0 +1,180 @@ +/* + * STMicroelectronics lsm6ds3 spi driver + * + * Copyright 2014 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 "st_lsm6ds3.h" + +#define ST_SENSORS_SPI_READ 0x80 + +static int st_lsm6ds3_spi_read(struct lsm6ds3_data *cdata, + u8 reg_addr, int len, u8 *data, bool b_lock) +{ + int err; + + struct spi_transfer xfers[] = { + { + .tx_buf = cdata->tb.tx_buf, + .bits_per_word = 8, + .len = 1, + }, + { + .rx_buf = cdata->tb.rx_buf, + .bits_per_word = 8, + .len = len, + } + }; + + if (b_lock) + mutex_lock(&cdata->bank_registers_lock); + + mutex_lock(&cdata->tb.buf_lock); + cdata->tb.tx_buf[0] = reg_addr | ST_SENSORS_SPI_READ; + + err = spi_sync_transfer(to_spi_device(cdata->dev), + xfers, ARRAY_SIZE(xfers)); + if (err) + goto acc_spi_read_error; + + memcpy(data, cdata->tb.rx_buf, len*sizeof(u8)); + mutex_unlock(&cdata->tb.buf_lock); + if (b_lock) + mutex_unlock(&cdata->bank_registers_lock); + + return len; + +acc_spi_read_error: + mutex_unlock(&cdata->tb.buf_lock); + if (b_lock) + mutex_unlock(&cdata->bank_registers_lock); + + return err; +} + +static int st_lsm6ds3_spi_write(struct lsm6ds3_data *cdata, + u8 reg_addr, int len, u8 *data, bool b_lock) +{ + int err; + + struct spi_transfer xfers = { + .tx_buf = cdata->tb.tx_buf, + .bits_per_word = 8, + .len = len + 1, + }; + + if (len >= ST_LSM6DS3_RX_MAX_LENGTH) + return -ENOMEM; + + if (b_lock) + mutex_lock(&cdata->bank_registers_lock); + + mutex_lock(&cdata->tb.buf_lock); + cdata->tb.tx_buf[0] = reg_addr; + + memcpy(&cdata->tb.tx_buf[1], data, len); + + err = spi_sync_transfer(to_spi_device(cdata->dev), &xfers, 1); + mutex_unlock(&cdata->tb.buf_lock); + if (b_lock) + mutex_unlock(&cdata->bank_registers_lock); + + return err; +} + +static const struct st_lsm6ds3_transfer_function st_lsm6ds3_tf_spi = { + .write = st_lsm6ds3_spi_write, + .read = st_lsm6ds3_spi_read, +}; + +static int st_lsm6ds3_spi_probe(struct spi_device *spi) +{ + int err; + struct lsm6ds3_data *cdata; + + cdata = kmalloc(sizeof(*cdata), GFP_KERNEL); + if (!cdata) + return -ENOMEM; + + cdata->dev = &spi->dev; + cdata->name = spi->modalias; + spi_set_drvdata(spi, cdata); + + cdata->tf = &st_lsm6ds3_tf_spi; + + err = st_lsm6ds3_common_probe(cdata, spi->irq); + if (err < 0) + goto free_data; + + return 0; + +free_data: + kfree(cdata); + return err; +} + +static int st_lsm6ds3_spi_remove(struct spi_device *spi) +{ + struct lsm6ds3_data *cdata = spi_get_drvdata(spi); + + st_lsm6ds3_common_remove(cdata, spi->irq); + kfree(cdata); + + return 0; +} + +#ifdef CONFIG_PM +static int st_lsm6ds3_suspend(struct device *dev) +{ + struct lsm6ds3_data *cdata = spi_get_drvdata(to_spi_device(dev)); + + return st_lsm6ds3_common_suspend(cdata); +} + +static int st_lsm6ds3_resume(struct device *dev) +{ + struct lsm6ds3_data *cdata = spi_get_drvdata(to_spi_device(dev)); + + return st_lsm6ds3_common_resume(cdata); +} + +static const struct dev_pm_ops st_lsm6ds3_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(st_lsm6ds3_suspend, st_lsm6ds3_resume) +}; + +#define ST_LSM6DS3_PM_OPS (&st_lsm6ds3_pm_ops) +#else /* CONFIG_PM */ +#define ST_LSM6DS3_PM_OPS NULL +#endif /* CONFIG_PM */ + +static const struct spi_device_id st_lsm6ds3_id_table[] = { + { LSM6DS3_DEV_NAME }, + { }, +}; +MODULE_DEVICE_TABLE(spi, st_lsm6ds3_id_table); + +static struct spi_driver st_lsm6ds3_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "st-lsm6ds3-spi", + .pm = ST_LSM6DS3_PM_OPS, + }, + .probe = st_lsm6ds3_spi_probe, + .remove = st_lsm6ds3_spi_remove, + .id_table = st_lsm6ds3_id_table, +}; +module_spi_driver(st_lsm6ds3_driver); + +MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics lsm6ds3 spi driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/imu/st_lsm6ds3/st_lsm6ds3_trigger.c b/drivers/iio/imu/st_lsm6ds3/st_lsm6ds3_trigger.c new file mode 100644 index 00000000000..fb739c883d4 --- /dev/null +++ b/drivers/iio/imu/st_lsm6ds3/st_lsm6ds3_trigger.c @@ -0,0 +1,175 @@ +/* + * STMicroelectronics lsm6ds3 trigger driver + * + * Copyright 2014 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/events.h> + +#include "st_lsm6ds3.h" + +#define ST_LSM6DS3_SRC_FUNC_ADDR 0x53 +#define ST_LSM6DS3_FIFO_DATA_AVL_ADDR 0x3b + +#define ST_LSM6DS3_SRC_STEP_DETECTOR_DATA_AVL 0x10 +#define ST_LSM6DS3_SRC_TILT_DATA_AVL 0x20 +#define ST_LSM6DS3_SRC_STEP_COUNTER_DATA_AVL 0x80 +#define ST_LSM6DS3_FIFO_DATA_AVL 0x80 +#define ST_LSM6DS3_FIFO_DATA_OVR 0x40 + +static struct workqueue_struct *st_lsm6ds3_wq; + +void st_lsm6ds3_flush_works() +{ + flush_workqueue(st_lsm6ds3_wq); +} + +irqreturn_t st_lsm6ds3_save_timestamp(int irq, void *private) +{ + struct timespec ts; + struct lsm6ds3_data *cdata = private; + + get_monotonic_boottime(&ts); + cdata->timestamp = timespec_to_ns(&ts); + queue_work(st_lsm6ds3_wq, &cdata->data_work); + + disable_irq_nosync(irq); + + return IRQ_HANDLED; +} + +static void st_lsm6ds3_irq_management(struct work_struct *data_work) +{ + struct lsm6ds3_data *cdata; + u8 src_value = 0x00, src_fifo = 0x00; + + cdata = container_of((struct work_struct *)data_work, + struct lsm6ds3_data, data_work); + + cdata->tf->read(cdata, ST_LSM6DS3_SRC_FUNC_ADDR, 1, &src_value, true); + cdata->tf->read(cdata, ST_LSM6DS3_FIFO_DATA_AVL_ADDR, 1, + &src_fifo, true); + + if (src_fifo & ST_LSM6DS3_FIFO_DATA_AVL) { + if (src_fifo & ST_LSM6DS3_FIFO_DATA_OVR) { + st_lsm6ds3_set_fifo_mode(cdata, BYPASS); + st_lsm6ds3_set_fifo_mode(cdata, CONTINUOS); + dev_err(cdata->dev, + "data fifo overrun, reduce fifo size.\n"); + } else + st_lsm6ds3_read_fifo(cdata, false); + } + + if (src_value & ST_LSM6DS3_SRC_STEP_DETECTOR_DATA_AVL) { + iio_push_event(cdata->indio_dev[ST_INDIO_DEV_STEP_DETECTOR], + IIO_UNMOD_EVENT_CODE(IIO_STEP_DETECTOR, + 0, IIO_EV_TYPE_THRESH, IIO_EV_DIR_EITHER), + cdata->timestamp); + + if (cdata->sign_motion_event_ready) { + iio_push_event(cdata->indio_dev[ + ST_INDIO_DEV_SIGN_MOTION], + IIO_UNMOD_EVENT_CODE(IIO_SIGN_MOTION, + 0, IIO_EV_TYPE_THRESH, IIO_EV_DIR_EITHER), + cdata->timestamp); + + cdata->sign_motion_event_ready = false; + } + } + + if (src_value & ST_LSM6DS3_SRC_STEP_COUNTER_DATA_AVL) { + iio_trigger_poll_chained( + cdata->trig[ST_INDIO_DEV_STEP_COUNTER], 0); + } + + if (src_value & ST_LSM6DS3_SRC_TILT_DATA_AVL) { + iio_push_event(cdata->indio_dev[ST_INDIO_DEV_TILT], + IIO_UNMOD_EVENT_CODE(IIO_TILT, + 0, IIO_EV_TYPE_THRESH, IIO_EV_DIR_EITHER), + cdata->timestamp); + } + + enable_irq(cdata->irq); + return; +} + +int st_lsm6ds3_allocate_triggers(struct lsm6ds3_data *cdata, + const struct iio_trigger_ops *trigger_ops) +{ + int err, i, n; + + if (!st_lsm6ds3_wq) + st_lsm6ds3_wq = create_workqueue(cdata->name); + + if (!st_lsm6ds3_wq) + return -EINVAL; + + INIT_WORK(&cdata->data_work, st_lsm6ds3_irq_management); + + for (i = 0; i < ST_INDIO_DEV_NUM; i++) { + cdata->trig[i] = iio_trigger_alloc("%s-trigger", + cdata->indio_dev[i]->name); + if (!cdata->trig[i]) { + dev_err(cdata->dev, + "failed to allocate iio trigger.\n"); + err = -ENOMEM; + goto deallocate_trigger; + } + iio_trigger_set_drvdata(cdata->trig[i], cdata->indio_dev[i]); + cdata->trig[i]->ops = trigger_ops; + cdata->trig[i]->dev.parent = cdata->dev; + } + + err = request_threaded_irq(cdata->irq, st_lsm6ds3_save_timestamp, NULL, + IRQF_TRIGGER_HIGH, cdata->name, cdata); + if (err) + goto deallocate_trigger; + + for (n = 0; n < ST_INDIO_DEV_NUM; n++) { + err = iio_trigger_register(cdata->trig[n]); + if (err < 0) { + dev_err(cdata->dev, + "failed to register iio trigger.\n"); + goto free_irq; + } + cdata->indio_dev[n]->trig = cdata->trig[n]; + } + + return 0; + +free_irq: + free_irq(cdata->irq, cdata); + for (n--; n >= 0; n--) + iio_trigger_unregister(cdata->trig[n]); +deallocate_trigger: + for (i--; i >= 0; i--) + iio_trigger_free(cdata->trig[i]); + + return err; +} +EXPORT_SYMBOL(st_lsm6ds3_allocate_triggers); + +void st_lsm6ds3_deallocate_triggers(struct lsm6ds3_data *cdata) +{ + int i; + + free_irq(cdata->irq, cdata); + + for (i = 0; i < ST_INDIO_DEV_NUM; i++) + iio_trigger_unregister(cdata->trig[i]); +} +EXPORT_SYMBOL(st_lsm6ds3_deallocate_triggers); + +MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics lsm6ds3 trigger driver"); +MODULE_LICENSE("GPL v2"); |