diff --git a/Documentation/devicetree/bindings/iio/stm/st_lis2hh12.txt b/Documentation/devicetree/bindings/iio/stm/st_lis2hh12.txt new file mode 100644 index 000000000000..5f4bbb3948f5 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/stm/st_lis2hh12.txt @@ -0,0 +1,34 @@ +* lis2hh12 driver for accel MEMS sensors + +Required properties for all bus drivers: +- compatible: must be one of: + "st,lis2hh12" + +Required properties for the i2c bindings: +- reg: i2c slave address + +Required properties for the spi bindings: +- reg: the chipselect index +- spi-max-frequency: maximal bus speed, should be set to 1000000 unless + constrained by external circuitry + +Optional properties for all bus drivers: +- st,drdy-int-pin: the pin on the package that will be used to signal + "data ready" (valid values: 1 or 2, default: 1). + +- interrupts: interrupt mapping for IRQ. It should be configured with + flags IRQ_TYPE_LEVEL_HIGH. + + Refer to interrupt-controller/interrupts.txt for generic + interrupt client node bindings. + +Example for an spi device node: + +lis2hh12-accel@0 { + compatible = "st,lis2hh12"; + reg = <0x0>; + spi-max-frequency = <1000000>; + interrupt-parent = <&gpio0>; + interrupts = <0 IRQ_TYPE_LEVEL_HIGH>; + st,drdy-int-pin = <1>; +}; diff --git a/drivers/iio/stm/accel/Kconfig b/drivers/iio/stm/accel/Kconfig index 4df0152f91dc..5dd85965ae50 100644 --- a/drivers/iio/stm/accel/Kconfig +++ b/drivers/iio/stm/accel/Kconfig @@ -140,4 +140,23 @@ config IIO_ST_LIS3DHH This driver can also be built as a module. If so, will be named st_lis3dhh +config IIO_ST_LIS2HH12 + tristate "STMicroelectronics LIS2HH12 Accelerometer driver" + depends on (I2C || SPI_MASTER) && SYSFS + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select IIO_ST_LIS2HH12_I2C if (I2C) + select IIO_ST_LIS2HH12_SPI if (SPI) + help + Say yes here to build support for the LIS2HH12 accelerometer. + +config IIO_ST_LIS2HH12_I2C + tristate + depends on IIO_ST_LIS2HH12 + depends on I2C + +config IIO_ST_LIS2HH12_SPI + tristate + depends on IIO_ST_LIS2HH12 + endmenu diff --git a/drivers/iio/stm/accel/Makefile b/drivers/iio/stm/accel/Makefile index f0ac2ecc0652..7eb80e69184a 100644 --- a/drivers/iio/stm/accel/Makefile +++ b/drivers/iio/stm/accel/Makefile @@ -28,3 +28,9 @@ obj-$(CONFIG_IIO_ST_ISM303DAC_ACCEL_SPI) += st_ism303dac_accel_spi.o st-lis3dhh-y := st_lis3dhh.o st_lis3dhh_buffer.o obj-$(CONFIG_IIO_ST_LIS3DHH) += st-lis3dhh.o + +lis2hh12-y += st_lis2hh12_core.o st_lis2hh12_buffer.o st_lis2hh12_trigger.o +obj-$(CONFIG_IIO_ST_LIS2HH12) += lis2hh12.o +obj-$(CONFIG_IIO_ST_LIS2HH12_I2C) += st_lis2hh12_i2c.o +obj-$(CONFIG_IIO_ST_LIS2HH12_SPI) += st_lis2hh12_spi.o + diff --git a/drivers/iio/stm/accel/st_lis2hh12.h b/drivers/iio/stm/accel/st_lis2hh12.h new file mode 100644 index 000000000000..ea80e91c49bc --- /dev/null +++ b/drivers/iio/stm/accel/st_lis2hh12.h @@ -0,0 +1,215 @@ +/* + * STMicroelectronics lis2hh12 driver + * + * Copyright 2016 STMicroelectronics Inc. + * + * Armando Visconti + * + * Licensed under the GPL-2. + */ + +#ifndef __LIS2HH12_H +#define __LIS2HH12_H + +#include +#include +#include + +#define LIS2HH12_WHO_AM_I_ADDR 0x0f +#define LIS2HH12_WHO_AM_I_DEF 0x41 + +#define LIS2HH12_CTRL1_ADDR 0x20 +#define LIS2HH12_CTRL2_ADDR 0x21 +#define LIS2HH12_CTRL3_ADDR 0x22 +#define LIS2HH12_CTRL4_ADDR 0x23 +#define LIS2HH12_CTRL5_ADDR 0x24 +#define LIS2HH12_CTRL6_ADDR 0x25 +#define LIS2HH12_CTRL7_ADDR 0x26 + +#define LIS2HH12_STATUS_ADDR 0x27 +#define LIS2HH12_DATA_XYZ_RDY 0x08 + +#define LIS2HH12_FIFO_CTRL_ADDR 0x2E + +#define LIS2HH12_OUTX_L_ADDR 0x28 +#define LIS2HH12_OUTY_L_ADDR 0x2A +#define LIS2HH12_OUTZ_L_ADDR 0x2C + +#define LIS2HH12_FIFO_THS_ADDR LIS2HH12_FIFO_CTRL_ADDR +#define LIS2HH12_FIFO_THS_MASK 0x1f + +#define LIS2HH12_FIFO_MODE_ADDR LIS2HH12_FIFO_CTRL_ADDR +#define LIS2HH12_FIFO_MODE_MASK 0xe0 +#define LIS2HH12_FIFO_MODE_BYPASS 0x00 +#define LIS2HH12_FIFO_MODE_STREAM 0x02 + +#define LIS2HH12_FIFO_SRC_ADDR 0x2F +#define LIS2HH12_FIFO_STATUS_ADDR LIS2HH12_FIFO_SRC_ADDR +#define LIS2HH12_FIFO_FSS_MASK 0x1F +#define LIS2HH12_FIFO_SRC_FTH_MASK 0x80 + +#define LIS2HH12_ODR_ADDR LIS2HH12_CTRL1_ADDR +#define LIS2HH12_ODR_MASK 0x70 +#define LIS2HH12_ODR_POWER_DOWN_VAL 0x00 +#define LIS2HH12_ODR_10HZ_VAL 0x01 +#define LIS2HH12_ODR_50HZ_VAL 0x02 +#define LIS2HH12_ODR_100HZ_VAL 0x03 +#define LIS2HH12_ODR_200HZ_VAL 0x04 +#define LIS2HH12_ODR_400HZ_VAL 0x05 +#define LIS2HH12_ODR_800HZ_VAL 0x06 +#define LIS2HH12_ODR_LIST_NUM 7 + +#define LIS2HH12_FS_ADDR LIS2HH12_CTRL4_ADDR +#define LIS2HH12_FS_MASK 0x30 +#define LIS2HH12_FS_2G_VAL 0x00 +#define LIS2HH12_FS_4G_VAL 0x02 +#define LIS2HH12_FS_8G_VAL 0x03 +#define LIS2HH12_FS_LIST_NUM 3 + +#define LIS2HH12_FS_2G_GAIN IIO_G_TO_M_S_2(61) +#define LIS2HH12_FS_4G_GAIN IIO_G_TO_M_S_2(122) +#define LIS2HH12_FS_8G_GAIN IIO_G_TO_M_S_2(244) + +#define LIS2HH12_INT_CFG_ADDR LIS2HH12_CTRL3_ADDR +#define LIS2HH12_INT_DRDY_MASK 0x01 +#define LIS2HH12_INT_FTH_MASK 0x02 +#define LIS2HH12_INT_FOVR_MASK 0x04 + +#define LIS2HH12_FIFO_EN_ADDR LIS2HH12_CTRL3_ADDR +#define LIS2HH12_FIFO_EN_MASK 0x80 + +#define LIS2HH12_BDU_ADDR LIS2HH12_CTRL1_ADDR +#define LIS2HH12_BDU_MASK 0x08 +#define LIS2HH12_SOFT_RESET_ADDR LIS2HH12_CTRL5_ADDR +#define LIS2HH12_SOFT_RESET_MASK 0x40 +#define LIS2HH12_LIR_ADDR LIS2HH12_CTRL7_ADDR +#define LIS2HH12_LIR1_MASK 0x04 +#define LIS2HH12_LIR2_MASK 0x08 + +#define LIS2HH12_MAX_FIFO_LENGHT 32 +#define LIS2HH12_MAX_FIFO_THS (LIS2HH12_MAX_FIFO_LENGHT - 1) +#define LIS2HH12_FIFO_NUM_AXIS 3 +#define LIS2HH12_FIFO_BYTE_X_AXIS 2 +#define LIS2HH12_FIFO_BYTE_FOR_SAMPLE (LIS2HH12_FIFO_NUM_AXIS * \ + LIS2HH12_FIFO_BYTE_X_AXIS) +#define LIS2HH12_TIMESTAMP_SIZE 8 + +#define LIS2HH12_EN_BIT 0x01 +#define LIS2HH12_DIS_BIT 0x00 + +#define LIS2HH12_MAX_CHANNEL_SPEC 5 + +#define LIS2HH12_ACCEL 0 +#define LIS2HH12_SENSORS_NUMB 1 + +#define LIS2HH12_DEV_NAME "lis2hh12" +#define SET_BIT(a, b) {a |= (1 << b);} +#define RESET_BIT(a, b) {a &= ~(1 << b);} +#define CHECK_BIT(a, b) (a & (1 << b)) + +#define ST_LIS2HH12_FLUSH_CHANNEL(device_type) \ +{ \ + .type = device_type, \ + .modified = 0, \ + .scan_index = -1, \ + .indexed = -1, \ + .event_spec = &lis2hh12_fifo_flush_event,\ + .num_event_specs = 1, \ +} + +#define ST_LIS2HH12_HWFIFO_ENABLED() \ + IIO_DEVICE_ATTR(hwfifo_enabled, S_IWUSR | S_IRUGO, \ + lis2hh12_sysfs_get_hwfifo_enabled,\ + lis2hh12_sysfs_set_hwfifo_enabled, 0); + +#define ST_LIS2HH12_HWFIFO_WATERMARK() \ + IIO_DEVICE_ATTR(hwfifo_watermark, S_IWUSR | S_IRUGO, \ + lis2hh12_sysfs_get_hwfifo_watermark,\ + lis2hh12_sysfs_set_hwfifo_watermark, 0); + +#define ST_LIS2HH12_HWFIFO_WATERMARK_MIN() \ + IIO_DEVICE_ATTR(hwfifo_watermark_min, S_IRUGO, \ + lis2hh12_sysfs_get_hwfifo_watermark_min, NULL, 0); + +#define ST_LIS2HH12_HWFIFO_WATERMARK_MAX() \ + IIO_DEVICE_ATTR(hwfifo_watermark_max, S_IRUGO, \ + lis2hh12_sysfs_get_hwfifo_watermark_max, NULL, 0); + +#define ST_LIS2HH12_HWFIFO_FLUSH() \ + IIO_DEVICE_ATTR(hwfifo_flush, S_IWUSR, NULL, \ + lis2hh12_sysfs_flush_fifo, 0); + +enum fifo_mode { + BYPASS = 0, + STREAM, +}; + +#define LIS2HH12_TX_MAX_LENGTH 12 +#define LIS2HH12_RX_MAX_LENGTH 8193 + +struct lis2hh12_transfer_buffer { + struct mutex buf_lock; + u8 rx_buf[LIS2HH12_RX_MAX_LENGTH]; + u8 tx_buf[LIS2HH12_TX_MAX_LENGTH] ____cacheline_aligned; +}; + +struct lis2hh12_data; + +struct lis2hh12_transfer_function { + int (*write)(struct lis2hh12_data *cdata, u8 reg_addr, int len, u8 *data); + int (*read)(struct lis2hh12_data *cdata, u8 reg_addr, int len, u8 *data); +}; + +struct lis2hh12_sensor_data { + struct lis2hh12_data *cdata; + const char *name; + s64 timestamp; + u8 enabled; + u32 odr; + u32 gain; + u8 sindex; + u8 sample_to_discard; +}; + +struct lis2hh12_data { + const char *name; + u8 drdy_int_pin; + u8 enabled_sensor; + u8 hwfifo_enabled; + u8 hwfifo_watermark; + u32 common_odr; + int irq; + s64 timestamp; + s64 sensor_deltatime; + s64 sensor_timestamp; + u8 *fifo_data; + u16 fifo_size; + struct device *dev; + struct iio_dev *iio_sensors_dev[LIS2HH12_SENSORS_NUMB]; + struct iio_trigger *iio_trig[LIS2HH12_SENSORS_NUMB]; + const struct lis2hh12_transfer_function *tf; + struct lis2hh12_transfer_buffer tb; +}; + +int lis2hh12_common_probe(struct lis2hh12_data *cdata, int irq); +#ifdef CONFIG_PM +int lis2hh12_common_suspend(struct lis2hh12_data *cdata); +int lis2hh12_common_resume(struct lis2hh12_data *cdata); +#endif +int lis2hh12_allocate_rings(struct lis2hh12_data *cdata); +int lis2hh12_allocate_triggers(struct lis2hh12_data *cdata, + const struct iio_trigger_ops *trigger_ops); +int lis2hh12_trig_set_state(struct iio_trigger *trig, bool state); +int lis2hh12_read_register(struct lis2hh12_data *cdata, u8 reg_addr, int data_len, + u8 *data); +int lis2hh12_update_drdy_irq(struct lis2hh12_sensor_data *sdata, bool state); +int lis2hh12_set_enable(struct lis2hh12_sensor_data *sdata, bool enable); +int lis2hh12_update_fifo_ths(struct lis2hh12_data *cdata, u8 fifo_len); +void lis2hh12_common_remove(struct lis2hh12_data *cdata, int irq); +void lis2hh12_read_fifo(struct lis2hh12_data *cdata, bool check_fifo_len); +void lis2hh12_deallocate_rings(struct lis2hh12_data *cdata); +void lis2hh12_deallocate_triggers(struct lis2hh12_data *cdata); +int lis2hh12_set_fifo_mode(struct lis2hh12_data *cdata, enum fifo_mode fm); +void lis2hh12_read_xyz(struct lis2hh12_data *cdata); + +#endif /* __LIS2HH12_H */ diff --git a/drivers/iio/stm/accel/st_lis2hh12_buffer.c b/drivers/iio/stm/accel/st_lis2hh12_buffer.c new file mode 100644 index 000000000000..788ee05d8c83 --- /dev/null +++ b/drivers/iio/stm/accel/st_lis2hh12_buffer.c @@ -0,0 +1,174 @@ +/* + * STMicroelectronics lis2hh12 driver + * + * Copyright 2016 STMicroelectronics Inc. + * + * Armando Visconti + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "st_lis2hh12.h" + +#define LIS2HH12_ACCEL_BUFFER_SIZE \ + ALIGN(LIS2HH12_FIFO_BYTE_FOR_SAMPLE + LIS2HH12_TIMESTAMP_SIZE, \ + LIS2HH12_TIMESTAMP_SIZE) + +static void lis2hh12_push_fifo_data(struct lis2hh12_data *cdata, u16 fifo_ptr) +{ + size_t offset; + u8 buffer[LIS2HH12_ACCEL_BUFFER_SIZE], out_buf_index; + struct iio_dev *indio_dev = cdata->iio_sensors_dev[LIS2HH12_ACCEL]; + struct lis2hh12_sensor_data *sdata; + + out_buf_index = 0; + + /* Accelerometer data */ + sdata = iio_priv(indio_dev); + if (sdata->enabled) { + if (indio_dev->active_scan_mask && + test_bit(0, indio_dev->active_scan_mask)) { + memcpy(&buffer[out_buf_index], &cdata->fifo_data[fifo_ptr], + LIS2HH12_FIFO_BYTE_FOR_SAMPLE); + out_buf_index += LIS2HH12_FIFO_BYTE_FOR_SAMPLE; + } + + if (indio_dev->scan_timestamp) { + offset = indio_dev->scan_bytes / sizeof(s64) - 1; + ((s64 *)buffer)[offset] = cdata->sensor_timestamp; + } + + iio_push_to_buffers(indio_dev, buffer); + } +} + +void lis2hh12_read_xyz(struct lis2hh12_data *cdata) +{ + int err; + + err = lis2hh12_read_register(cdata, LIS2HH12_OUTX_L_ADDR, + LIS2HH12_FIFO_BYTE_FOR_SAMPLE, cdata->fifo_data); + if (err < 0) + return; + + cdata->sensor_timestamp = cdata->timestamp; + lis2hh12_push_fifo_data(cdata, 0); +} + +void lis2hh12_read_fifo(struct lis2hh12_data *cdata, bool check_fifo_len) +{ + int err; + u8 fifo_src; + u16 read_len = cdata->fifo_size; + uint16_t i; + + if (!cdata->fifo_data) + return; + + if (check_fifo_len) { + err = lis2hh12_read_register(cdata, LIS2HH12_FIFO_STATUS_ADDR, 1, &fifo_src); + if (err < 0) + return; + + read_len = (fifo_src & LIS2HH12_FIFO_FSS_MASK); + read_len *= LIS2HH12_FIFO_BYTE_FOR_SAMPLE; + + if (read_len > cdata->fifo_size) + read_len = cdata->fifo_size; + } + + err = lis2hh12_read_register(cdata, LIS2HH12_OUTX_L_ADDR, read_len, + cdata->fifo_data); + if (err < 0) + return; + + for (i = 0; i < read_len; i += LIS2HH12_FIFO_BYTE_FOR_SAMPLE) { + lis2hh12_push_fifo_data(cdata, i); + cdata->sensor_timestamp += cdata->sensor_deltatime; + } +} + +static inline irqreturn_t lis2hh12_handler_empty(int irq, void *p) +{ + return IRQ_HANDLED; +} + +int lis2hh12_trig_set_state(struct iio_trigger *trig, bool state) +{ + return 0; +} + +static int lis2hh12_buffer_preenable(struct iio_dev *indio_dev) +{ + int err; + struct lis2hh12_sensor_data *sdata = iio_priv(indio_dev); + + err = lis2hh12_set_enable(sdata, true); + if (err < 0) + return err; + + return 0; +} + +static int lis2hh12_buffer_postdisable(struct iio_dev *indio_dev) +{ + int err; + struct lis2hh12_sensor_data *sdata = iio_priv(indio_dev); + + err = lis2hh12_set_enable(sdata, false); + if (err < 0) + return err; + + return 0; +} + +static const struct iio_buffer_setup_ops lis2hh12_buffer_setup_ops = { + .preenable = &lis2hh12_buffer_preenable, + .postdisable = &lis2hh12_buffer_postdisable, +}; + +int lis2hh12_allocate_rings(struct lis2hh12_data *cdata) +{ + int err, i; + + for (i = 0; i < LIS2HH12_SENSORS_NUMB; i++) { + err = iio_triggered_buffer_setup( + cdata->iio_sensors_dev[i], + &lis2hh12_handler_empty, + NULL, + &lis2hh12_buffer_setup_ops); + if (err < 0) + goto buffer_cleanup; + } + + return 0; + +buffer_cleanup: + for (i--; i >= 0; i--) + iio_triggered_buffer_cleanup(cdata->iio_sensors_dev[i]); + + return err; +} + +void lis2hh12_deallocate_rings(struct lis2hh12_data *cdata) +{ + int i; + + for (i = 0; i < LIS2HH12_SENSORS_NUMB; i++) + iio_triggered_buffer_cleanup(cdata->iio_sensors_dev[i]); +} + +MODULE_DESCRIPTION("STMicroelectronics lis2hh12 driver"); +MODULE_AUTHOR("Armando Visconti "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/stm/accel/st_lis2hh12_core.c b/drivers/iio/stm/accel/st_lis2hh12_core.c new file mode 100644 index 000000000000..23b8d3d8e501 --- /dev/null +++ b/drivers/iio/stm/accel/st_lis2hh12_core.c @@ -0,0 +1,911 @@ +/* + * STMicroelectronics lis2hh12 driver + * + * Copyright 2016 STMicroelectronics Inc. + * + * Armando Visconti + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "st_lis2hh12.h" +#ifndef CONFIG_OF +#include +#endif + +#define ST_LIS2HH12_DEV_ATTR_SAMP_FREQ() \ + IIO_DEV_ATTR_SAMP_FREQ(S_IWUSR | S_IRUGO, \ + lis2hh12_sysfs_get_sampling_frequency, \ + lis2hh12_sysfs_set_sampling_frequency) + +#define ST_LIS2HH12_DEV_ATTR_SAMP_FREQ_AVAIL() \ + IIO_DEV_ATTR_SAMP_FREQ_AVAIL( \ + lis2hh12_sysfs_sampling_frequency_avail) + +#define ST_LIS2HH12_DEV_ATTR_SCALE_AVAIL(name) \ + IIO_DEVICE_ATTR(name, S_IRUGO, \ + lis2hh12_sysfs_scale_avail, NULL , 0); + +#define LIS2HH12_ADD_CHANNEL(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, \ + }, \ +} + +struct lis2hh12_odr_reg { + u32 hz; + u8 value; +}; + +static const struct lis2hh12_odr_table_t { + u8 addr; + u8 mask; + struct lis2hh12_odr_reg odr_avl[LIS2HH12_ODR_LIST_NUM]; +} lis2hh12_odr_table = { + .addr = LIS2HH12_ODR_ADDR, + .mask = LIS2HH12_ODR_MASK, + + .odr_avl[0] = {.hz = 0,.value = LIS2HH12_ODR_POWER_DOWN_VAL,}, + .odr_avl[1] = {.hz = 10,.value = LIS2HH12_ODR_10HZ_VAL,}, + .odr_avl[2] = {.hz = 50,.value = LIS2HH12_ODR_50HZ_VAL,}, + .odr_avl[3] = {.hz = 100,.value = LIS2HH12_ODR_100HZ_VAL,}, + .odr_avl[4] = {.hz = 200,.value = LIS2HH12_ODR_200HZ_VAL,}, + .odr_avl[5] = {.hz = 400,.value = LIS2HH12_ODR_400HZ_VAL,}, + .odr_avl[6] = {.hz = 800,.value = LIS2HH12_ODR_800HZ_VAL,}, +}; + +struct lis2hh12_fs_reg { + unsigned int gain; + u8 value; +}; + +static struct lis2hh12_fs_table { + u8 addr; + u8 mask; + struct lis2hh12_fs_reg fs_avl[LIS2HH12_FS_LIST_NUM]; +} lis2hh12_fs_table = { + .addr = LIS2HH12_FS_ADDR, + .mask = LIS2HH12_FS_MASK, + .fs_avl[0] = { + .gain = LIS2HH12_FS_2G_GAIN, + .value = LIS2HH12_FS_2G_VAL, + }, + .fs_avl[1] = { + .gain = LIS2HH12_FS_4G_GAIN, + .value = LIS2HH12_FS_4G_VAL, + }, + .fs_avl[2] = { + .gain = LIS2HH12_FS_8G_GAIN, + .value = LIS2HH12_FS_8G_VAL, + }, +}; + +const struct iio_event_spec lis2hh12_fifo_flush_event = { + .type = IIO_EV_TYPE_FIFO_FLUSH, + .dir = IIO_EV_DIR_EITHER, +}; + +static const struct lis2hh12_sensors_table { + const char *name; + const char *description; + const u32 min_odr_hz; + const u8 iio_channel_size; + const struct iio_chan_spec iio_channel[LIS2HH12_MAX_CHANNEL_SPEC]; +} lis2hh12_sensors_table[LIS2HH12_SENSORS_NUMB] = { + [LIS2HH12_ACCEL] = { + .name = "accel", + .description = "ST LIS2HH12 Accelerometer Sensor", + .min_odr_hz = 10, + .iio_channel = { + LIS2HH12_ADD_CHANNEL(IIO_ACCEL, 1, 0, IIO_MOD_X, IIO_LE, + 16, 16, LIS2HH12_OUTX_L_ADDR, 's'), + LIS2HH12_ADD_CHANNEL(IIO_ACCEL, 1, 1, IIO_MOD_Y, IIO_LE, + 16, 16, LIS2HH12_OUTY_L_ADDR, 's'), + LIS2HH12_ADD_CHANNEL(IIO_ACCEL, 1, 2, IIO_MOD_Z, IIO_LE, + 16, 16, LIS2HH12_OUTZ_L_ADDR, 's'), + ST_LIS2HH12_FLUSH_CHANNEL(IIO_ACCEL), + IIO_CHAN_SOFT_TIMESTAMP(3) + }, + .iio_channel_size = LIS2HH12_MAX_CHANNEL_SPEC, + }, +}; + +inline int lis2hh12_read_register(struct lis2hh12_data *cdata, u8 reg_addr, int data_len, + u8 *data) +{ + return cdata->tf->read(cdata, reg_addr, data_len, data); +} + +static int lis2hh12_write_register(struct lis2hh12_data *cdata, u8 reg_addr, + u8 mask, u8 data) +{ + int err; + u8 new_data = 0x00, old_data = 0x00; + + err = lis2hh12_read_register(cdata, 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 cdata->tf->write(cdata, reg_addr, 1, &new_data); +} + +int lis2hh12_set_fifo_mode(struct lis2hh12_data *cdata, enum fifo_mode fm) +{ + int err; + u8 reg_value; + u8 set_bit = LIS2HH12_DIS_BIT; + + switch (fm) { + case BYPASS: + reg_value = LIS2HH12_FIFO_MODE_BYPASS; + set_bit = LIS2HH12_DIS_BIT; + break; + case STREAM: + reg_value = LIS2HH12_FIFO_MODE_STREAM; + set_bit = LIS2HH12_EN_BIT; + break; + default: + return -EINVAL; + } + + err = lis2hh12_write_register(cdata, LIS2HH12_FIFO_MODE_ADDR, + LIS2HH12_FIFO_MODE_MASK, reg_value); + + if (err < 0) + return err; + + cdata->sensor_timestamp = + iio_get_time_ns(cdata->iio_sensors_dev[LIS2HH12_ACCEL]); + + err = lis2hh12_write_register(cdata, LIS2HH12_FIFO_EN_ADDR, + LIS2HH12_FIFO_EN_MASK, set_bit); + if (err < 0) + return err; + + return 0; +} +EXPORT_SYMBOL(lis2hh12_set_fifo_mode); + +int lis2hh12_write_max_odr(struct lis2hh12_sensor_data *sdata) +{ + int err, i; + u32 max_odr = 0; + struct lis2hh12_sensor_data *t_sdata; + + for (i = 0; i < LIS2HH12_SENSORS_NUMB; i++) + if (CHECK_BIT(sdata->cdata->enabled_sensor, i)) { + t_sdata = iio_priv(sdata->cdata->iio_sensors_dev[i]); + if (t_sdata->odr > max_odr) + max_odr = t_sdata->odr; + } + + if (max_odr != sdata->cdata->common_odr) { + for (i = 0; i < LIS2HH12_ODR_LIST_NUM; i++) { + if (lis2hh12_odr_table.odr_avl[i].hz >= max_odr) + break; + } + if (i == LIS2HH12_ODR_LIST_NUM) + return -EINVAL; + + err = lis2hh12_write_register(sdata->cdata, + lis2hh12_odr_table.addr, + lis2hh12_odr_table.mask, + lis2hh12_odr_table.odr_avl[i].value); + if (err < 0) + return err; + + sdata->cdata->common_odr = max_odr; + sdata->cdata->sensor_deltatime = (max_odr) ? 1000000000L / max_odr : 0; + } + + return 0; +} + +int lis2hh12_set_fs(struct lis2hh12_sensor_data *sdata, unsigned int gain) +{ + int err, i; + + for (i = 0; i < LIS2HH12_FS_LIST_NUM; i++) { + if (lis2hh12_fs_table.fs_avl[i].gain == gain) + break; + } + + if (i == LIS2HH12_FS_LIST_NUM) + return -EINVAL; + + err = lis2hh12_write_register(sdata->cdata, + lis2hh12_fs_table.addr, lis2hh12_fs_table.mask, + lis2hh12_fs_table.fs_avl[i].value); + if (err < 0) + return err; + + sdata->gain = lis2hh12_fs_table.fs_avl[i].gain; + + return 0; +} + +int lis2hh12_update_drdy_irq(struct lis2hh12_sensor_data *sdata, bool state) +{ + u8 reg_addr, reg_val, reg_mask; + + switch (sdata->sindex) { + case LIS2HH12_ACCEL: + reg_addr = LIS2HH12_INT_CFG_ADDR; + if (sdata->cdata->hwfifo_enabled) + reg_mask = (LIS2HH12_INT_FTH_MASK); + else + reg_mask = (LIS2HH12_INT_DRDY_MASK); + + if (state) + reg_val = LIS2HH12_EN_BIT; + else + reg_val = LIS2HH12_DIS_BIT; + + break; + + default: + return -EINVAL; + } + + return lis2hh12_write_register(sdata->cdata, reg_addr, reg_mask, reg_val); +} +EXPORT_SYMBOL(lis2hh12_update_drdy_irq); + +static int lis2hh12_alloc_fifo(struct lis2hh12_data *cdata) +{ + int fifo_size; + + fifo_size = LIS2HH12_MAX_FIFO_LENGHT * LIS2HH12_FIFO_BYTE_FOR_SAMPLE; + + cdata->fifo_data = kmalloc(fifo_size, GFP_KERNEL); + if (!cdata->fifo_data) + return -ENOMEM; + + cdata->fifo_size = fifo_size; + + return 0; +} + +int lis2hh12_update_fifo_ths(struct lis2hh12_data *cdata, u8 fifo_len) +{ + int err; + struct iio_dev *indio_dev; + + indio_dev = cdata->iio_sensors_dev[LIS2HH12_ACCEL]; + + err = lis2hh12_write_register(cdata, LIS2HH12_FIFO_THS_ADDR, + LIS2HH12_FIFO_THS_MASK, + fifo_len); + if (err < 0) + return err; + + return 0; +} +EXPORT_SYMBOL(lis2hh12_update_fifo_ths); + +int lis2hh12_set_enable(struct lis2hh12_sensor_data *sdata, bool state) +{ + int err = 0; + u8 mode; + + if (sdata->enabled == state) + return 0; + + /* + * Start assuming the sensor enabled if state == true. + * It will be restored if an error occur. + */ + if (state) { + SET_BIT(sdata->cdata->enabled_sensor, sdata->sindex); + mode = STREAM; + } else { + RESET_BIT(sdata->cdata->enabled_sensor, sdata->sindex); + mode = BYPASS; + } + + /* Program the device */ + err = lis2hh12_update_drdy_irq(sdata, state); + if (err < 0) + goto enable_sensor_error; + + err = lis2hh12_set_fifo_mode(sdata->cdata, mode); + if (err < 0) + goto enable_sensor_error; + + err = lis2hh12_write_max_odr(sdata); + if (err < 0) + goto enable_sensor_error; + + sdata->enabled = state; + + return 0; + +enable_sensor_error: + if (state) { + RESET_BIT(sdata->cdata->enabled_sensor, sdata->sindex); + } else + SET_BIT(sdata->cdata->enabled_sensor, sdata->sindex); + + return err; +} +EXPORT_SYMBOL(lis2hh12_set_enable); + +int lis2hh12_init_sensors(struct lis2hh12_data *cdata) +{ + int err; + + /* + * Soft reset the device on power on. + */ + err = lis2hh12_write_register(cdata, LIS2HH12_SOFT_RESET_ADDR, + LIS2HH12_SOFT_RESET_MASK, + LIS2HH12_EN_BIT); + if (err < 0) + return err; + + mdelay(40); + + /* + * Enable latched interrupt mode on INT1. + */ + err = lis2hh12_write_register(cdata, LIS2HH12_LIR_ADDR, + LIS2HH12_LIR1_MASK, + LIS2HH12_EN_BIT); + if (err < 0) + return err; + + /* + * Enable block data update feature. + */ + err = lis2hh12_write_register(cdata, LIS2HH12_BDU_ADDR, + LIS2HH12_BDU_MASK, + LIS2HH12_EN_BIT); + if (err < 0) + return err; + + return 0; +} + +static ssize_t lis2hh12_sysfs_get_sampling_frequency(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct lis2hh12_sensor_data *sdata = iio_priv(dev_get_drvdata(dev)); + + return sprintf(buf, "%d\n", sdata->odr); +} + +ssize_t lis2hh12_sysfs_set_sampling_frequency(struct device * dev, + struct device_attribute * attr, const char *buf, size_t count) +{ + int err; + u8 mode_count; + unsigned int odr, i; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct lis2hh12_sensor_data *sdata = iio_priv(indio_dev); + + err = kstrtoint(buf, 10, &odr); + if (err < 0) + return err; + + if (sdata->odr == odr) + return count; + + mode_count = LIS2HH12_ODR_LIST_NUM; + + for (i = 0; i < mode_count; i++) { + if (lis2hh12_odr_table.odr_avl[i].hz >= odr) + break; + } + if (i == LIS2HH12_ODR_LIST_NUM) + return -EINVAL; + + mutex_lock(&indio_dev->mlock); + sdata->odr = lis2hh12_odr_table.odr_avl[i].hz; + mutex_unlock(&indio_dev->mlock); + + err = lis2hh12_write_max_odr(sdata); + if (err < 0) + return err; + + return (err < 0) ? err : count; +} + +static ssize_t lis2hh12_sysfs_sampling_frequency_avail(struct device *dev, + struct device_attribute + *attr, char *buf) +{ + int i, len = 0; + + for (i = 1; i < LIS2HH12_ODR_LIST_NUM; i++) { + len += scnprintf(buf + len, PAGE_SIZE - len, "%d ", + lis2hh12_odr_table.odr_avl[i].hz); + } + buf[len - 1] = '\n'; + + return len; +} + +static ssize_t lis2hh12_sysfs_scale_avail(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i, len = 0; + + for (i = 0; i < LIS2HH12_FS_LIST_NUM; i++) { + len += scnprintf(buf + len, PAGE_SIZE - len, "0.%06u ", + lis2hh12_fs_table.fs_avl[i].gain); + } + buf[len - 1] = '\n'; + + return len; +} + +ssize_t lis2hh12_sysfs_flush_fifo(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + u64 event_type; + int64_t sensor_last_timestamp; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct lis2hh12_sensor_data *sdata = iio_priv(indio_dev); + + mutex_lock(&indio_dev->mlock); + + if (indio_dev->currentmode == INDIO_BUFFER_TRIGGERED) { + disable_irq(sdata->cdata->irq); + } else { + mutex_unlock(&indio_dev->mlock); + return -EINVAL; + } + + sensor_last_timestamp = sdata->cdata->sensor_timestamp; + + lis2hh12_read_fifo(sdata->cdata, true); + + if (sensor_last_timestamp == sdata->cdata->sensor_timestamp) + event_type = IIO_EV_DIR_FIFO_EMPTY; + else + event_type = IIO_EV_DIR_FIFO_DATA; + + iio_push_event(indio_dev, IIO_UNMOD_EVENT_CODE(IIO_ACCEL, + -1, IIO_EV_TYPE_FIFO_FLUSH, event_type), + sdata->cdata->sensor_timestamp); + + enable_irq(sdata->cdata->irq); + mutex_unlock(&indio_dev->mlock); + + return size; +} + +static ssize_t lis2hh12_sysfs_get_hwfifo_enabled(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct lis2hh12_sensor_data *sdata = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", sdata->cdata->hwfifo_enabled); +} + +ssize_t lis2hh12_sysfs_set_hwfifo_enabled(struct device * dev, + struct device_attribute * attr, const char *buf, size_t count) +{ + int err = 0, enable = 0; + u8 mode = BYPASS; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct lis2hh12_sensor_data *sdata = iio_priv(indio_dev); + + err = kstrtoint(buf, 10, &enable); + if (err < 0) + return err; + + if (enable != 0x0 && enable != 0x1) + return -EINVAL; + + mode = (enable == 0x0) ? BYPASS : STREAM; + + err = lis2hh12_set_fifo_mode(sdata->cdata, mode); + if (err < 0) + return err; + + sdata->cdata->hwfifo_enabled = enable; + + return count; +} + +static ssize_t lis2hh12_sysfs_get_hwfifo_watermark(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct lis2hh12_sensor_data *sdata = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", sdata->cdata->hwfifo_watermark); +} + +ssize_t lis2hh12_sysfs_set_hwfifo_watermark(struct device * dev, + struct device_attribute * attr, const char *buf, size_t count) +{ + int err = 0, watermark = 0; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct lis2hh12_sensor_data *sdata = iio_priv(indio_dev); + + err = kstrtoint(buf, 10, &watermark); + if (err < 0) + return err; + + if ((watermark < 1) || (watermark > LIS2HH12_MAX_FIFO_THS)) + return -EINVAL; + + err = lis2hh12_update_fifo_ths(sdata->cdata, watermark); + if (err < 0) + return err; + + sdata->cdata->hwfifo_watermark = watermark; + + return count; +} + +static ssize_t lis2hh12_sysfs_get_hwfifo_watermark_min(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", 1); +} + +static ssize_t lis2hh12_sysfs_get_hwfifo_watermark_max(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", LIS2HH12_MAX_FIFO_THS); +} + +static int lis2hh12_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *ch, int *val, + int *val2, long mask) +{ + int err; + u8 outdata[2], nbytes; + struct lis2hh12_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 = lis2hh12_set_enable(sdata, true); + if (err < 0) { + mutex_unlock(&indio_dev->mlock); + return -EBUSY; + } + + msleep(40); + + nbytes = ch->scan_type.realbits / 8; + + err = lis2hh12_read_register(sdata->cdata, ch->address, nbytes, outdata); + if (err < 0) { + mutex_unlock(&indio_dev->mlock); + return err; + } + + *val = (s16)get_unaligned_le16(outdata); + *val = *val >> ch->scan_type.shift; + + err = lis2hh12_set_enable(sdata, false); + mutex_unlock(&indio_dev->mlock); + + if (err < 0) + return err; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = sdata->gain; + + return IIO_VAL_INT_PLUS_MICRO; + + default: + return -EINVAL; + } + + return 0; +} + +static int lis2hh12_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, long mask) +{ + int err, i; + struct lis2hh12_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; + } + + for (i = 0; i < LIS2HH12_FS_LIST_NUM; i++) { + if (lis2hh12_fs_table.fs_avl[i].gain == val2) + break; + } + + err = lis2hh12_set_fs(sdata, lis2hh12_fs_table.fs_avl[i].gain); + mutex_unlock(&indio_dev->mlock); + + break; + + default: + return -EINVAL; + } + + return err; +} + +static ST_LIS2HH12_DEV_ATTR_SAMP_FREQ(); +static ST_LIS2HH12_DEV_ATTR_SAMP_FREQ_AVAIL(); +static ST_LIS2HH12_DEV_ATTR_SCALE_AVAIL(in_accel_scale_available); + +static ST_LIS2HH12_HWFIFO_ENABLED(); +static ST_LIS2HH12_HWFIFO_WATERMARK(); +static ST_LIS2HH12_HWFIFO_WATERMARK_MIN(); +static ST_LIS2HH12_HWFIFO_WATERMARK_MAX(); +static ST_LIS2HH12_HWFIFO_FLUSH(); + +static struct attribute *lis2hh12_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_hwfifo_enabled.dev_attr.attr, + &iio_dev_attr_hwfifo_watermark.dev_attr.attr, + &iio_dev_attr_hwfifo_watermark_min.dev_attr.attr, + &iio_dev_attr_hwfifo_watermark_max.dev_attr.attr, + &iio_dev_attr_hwfifo_flush.dev_attr.attr, + NULL, +}; + +static const struct attribute_group lis2hh12_accel_attribute_group = { + .attrs = lis2hh12_accel_attributes, +}; + +static const struct iio_info lis2hh12_info[LIS2HH12_SENSORS_NUMB] = { + [LIS2HH12_ACCEL] = { + .attrs = &lis2hh12_accel_attribute_group, + .read_raw = &lis2hh12_read_raw, + .write_raw = &lis2hh12_write_raw, + }, +}; + +#ifdef CONFIG_IIO_TRIGGER +static const struct iio_trigger_ops lis2hh12_trigger_ops = { + .set_trigger_state = (&lis2hh12_trig_set_state), +}; +#define LIS2HH12_TRIGGER_OPS (&lis2hh12_trigger_ops) +#else /*CONFIG_IIO_TRIGGER */ +#define LIS2HH12_TRIGGER_OPS NULL +#endif /*CONFIG_IIO_TRIGGER */ + +#ifdef CONFIG_OF +static const struct of_device_id lis2hh12_dt_id[] = { + {.compatible = "st,lis2hh12",}, + {}, +}; + +MODULE_DEVICE_TABLE(of, lis2hh12_dt_id); + +static u32 lis2hh12_parse_dt(struct lis2hh12_data *cdata) +{ + u32 val; + struct device_node *np; + + np = cdata->dev->of_node; + if (!np) + return -EINVAL; + /*TODO for this device interrupt pin is only one!!*/ + if (!of_property_read_u32(np, "st,drdy-int-pin", &val) && + (val <= 1) && (val > 0)) + cdata->drdy_int_pin = (u8) val; + else + cdata->drdy_int_pin = 1; + + return 0; +} +#endif /*CONFIG_OF */ + +int lis2hh12_common_probe(struct lis2hh12_data *cdata, int irq) +{ + u8 wai = 0; + int32_t err, i, n; + struct iio_dev *piio_dev; + struct lis2hh12_sensor_data *sdata; + + mutex_init(&cdata->tb.buf_lock); + + cdata->fifo_data = 0; + cdata->hwfifo_enabled = 0; + cdata->hwfifo_watermark = 0; + + err = lis2hh12_read_register(cdata, LIS2HH12_WHO_AM_I_ADDR, 1, &wai); + if (err < 0) { + dev_err(cdata->dev, "failed to read Who-Am-I register.\n"); + + return err; + } + if (wai != LIS2HH12_WHO_AM_I_DEF) { + dev_err(cdata->dev, "Who-Am-I value not valid.\n"); + + return -ENODEV; + } + + if (irq > 0) { + cdata->irq = irq; +#ifdef CONFIG_OF + err = lis2hh12_parse_dt(cdata); + if (err < 0) + return err; +#else /* CONFIG_OF */ + if (cdata->dev->platform_data) { + cdata->drdy_int_pin = ((struct lis2hh12_platform_data *) + cdata->dev->platform_data)->drdy_int_pin; + + if ((cdata->drdy_int_pin > 1) || (cdata->drdy_int_pin < 1)) + cdata->drdy_int_pin = 1; + } else + cdata->drdy_int_pin = 1; +#endif /* CONFIG_OF */ + + dev_info(cdata->dev, "driver use DRDY int pin %d\n", + cdata->drdy_int_pin); + } + + cdata->common_odr = 0; + cdata->enabled_sensor = 0; + + err = lis2hh12_alloc_fifo(cdata); + if (err) + return err; + + for (i = 0; i < LIS2HH12_SENSORS_NUMB; i++) { + piio_dev = devm_iio_device_alloc(cdata->dev, + sizeof(struct lis2hh12_sensor_data *)); + if (piio_dev == NULL) { + err = -ENOMEM; + + goto iio_device_free; + } + + cdata->iio_sensors_dev[i] = piio_dev; + sdata = iio_priv(piio_dev); + sdata->enabled = false; + sdata->cdata = cdata; + sdata->sindex = i; + sdata->name = lis2hh12_sensors_table[i].name; + sdata->odr = lis2hh12_sensors_table[i].min_odr_hz; + sdata->gain = lis2hh12_fs_table.fs_avl[0].gain; + + piio_dev->channels = lis2hh12_sensors_table[i].iio_channel; + piio_dev->num_channels = lis2hh12_sensors_table[i].iio_channel_size; + piio_dev->info = &lis2hh12_info[i]; + piio_dev->modes = INDIO_DIRECT_MODE; + piio_dev->name = kasprintf(GFP_KERNEL, "%s_%s", cdata->name, + sdata->name); + } + + err = lis2hh12_set_fifo_mode(sdata->cdata, BYPASS); + if (err < 0) + goto iio_device_free; + + err = lis2hh12_init_sensors(cdata); + if (err < 0) + goto iio_device_free; + + err = lis2hh12_allocate_rings(cdata); + if (err < 0) + goto iio_device_free; + + if (irq > 0) { + err = lis2hh12_allocate_triggers(cdata, LIS2HH12_TRIGGER_OPS); + if (err < 0) + goto deallocate_ring; + } + + for (n = 0; n < LIS2HH12_SENSORS_NUMB; n++) { + err = iio_device_register(cdata->iio_sensors_dev[n]); + if (err) + goto iio_device_unregister_and_trigger_deallocate; + } + + dev_info(cdata->dev, "%s: probed\n", LIS2HH12_DEV_NAME); + return 0; + +iio_device_unregister_and_trigger_deallocate: + for (n--; n >= 0; n--) + iio_device_unregister(cdata->iio_sensors_dev[n]); + +deallocate_ring: + lis2hh12_deallocate_rings(cdata); + +iio_device_free: + for (i--; i >= 0; i--) + iio_device_free(cdata->iio_sensors_dev[i]); + + return err; +} +EXPORT_SYMBOL(lis2hh12_common_probe); + +void lis2hh12_common_remove(struct lis2hh12_data *cdata, int irq) +{ + int i; + + if (cdata->fifo_data) { + kfree(cdata->fifo_data); + cdata->fifo_size = 0; + } + + for (i = 0; i < LIS2HH12_SENSORS_NUMB; i++) + iio_device_unregister(cdata->iio_sensors_dev[i]); + + if (irq > 0) + lis2hh12_deallocate_triggers(cdata); + + lis2hh12_deallocate_rings(cdata); + + for (i = 0; i < LIS2HH12_SENSORS_NUMB; i++) + iio_device_free(cdata->iio_sensors_dev[i]); +} + +EXPORT_SYMBOL(lis2hh12_common_remove); + +#ifdef CONFIG_PM +int lis2hh12_common_suspend(struct lis2hh12_data *cdata) +{ + return 0; +} + +EXPORT_SYMBOL(lis2hh12_common_suspend); + +int lis2hh12_common_resume(struct lis2hh12_data *cdata) +{ + return 0; +} + +EXPORT_SYMBOL(lis2hh12_common_resume); +#endif /* CONFIG_PM */ + +MODULE_DESCRIPTION("STMicroelectronics lis2hh12 driver"); +MODULE_AUTHOR("Armando Visconti "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/stm/accel/st_lis2hh12_i2c.c b/drivers/iio/stm/accel/st_lis2hh12_i2c.c new file mode 100644 index 000000000000..ffeeaae01578 --- /dev/null +++ b/drivers/iio/stm/accel/st_lis2hh12_i2c.c @@ -0,0 +1,155 @@ +/* + * STMicroelectronics lis2hh12 driver + * + * Copyright 2016 STMicroelectronics Inc. + * + * Armando Visconti + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include + +#include "st_lis2hh12.h" + +static int lis2hh12_i2c_read(struct lis2hh12_data *cdata, u8 reg_addr, int len, + u8 * data) +{ + 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; + + return(i2c_transfer(client->adapter, msg, 2)); +} + +static int lis2hh12_i2c_write(struct lis2hh12_data *cdata, u8 reg_addr, int len, + u8 * data) +{ + 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; + + return(i2c_transfer(client->adapter, &msg, 1)); +} + +static const struct lis2hh12_transfer_function lis2hh12_tf_i2c = { + .write = lis2hh12_i2c_write, + .read = lis2hh12_i2c_read, +}; + +static int lis2hh12_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int err; + struct lis2hh12_data *cdata; + + cdata = kmalloc(sizeof(*cdata), GFP_KERNEL); + if (!cdata) + return -ENOMEM; + + cdata->dev = &client->dev; + cdata->name = client->name; + cdata->tf = &lis2hh12_tf_i2c; + i2c_set_clientdata(client, cdata); + + err = lis2hh12_common_probe(cdata, client->irq); + if (err < 0) + goto free_data; + + return 0; + +free_data: + kfree(cdata); + return err; +} + +static int lis2hh12_i2c_remove(struct i2c_client *client) +{ + struct lis2hh12_data *cdata = i2c_get_clientdata(client); + + lis2hh12_common_remove(cdata, client->irq); + dev_info(cdata->dev, "%s: removed\n", LIS2HH12_DEV_NAME); + kfree(cdata); + return 0; +} + +#ifdef CONFIG_PM +static int lis2hh12_suspend(struct device *dev) +{ + struct lis2hh12_data *cdata = i2c_get_clientdata(to_i2c_client(dev)); + + return lis2hh12_common_suspend(cdata); +} + +static int lis2hh12_resume(struct device *dev) +{ + struct lis2hh12_data *cdata = i2c_get_clientdata(to_i2c_client(dev)); + + return lis2hh12_common_resume(cdata); +} + +static const struct dev_pm_ops lis2hh12_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(lis2hh12_suspend, lis2hh12_resume) +}; + +#define LIS2HH12_PM_OPS (&lis2hh12_pm_ops) +#else /* CONFIG_PM */ +#define LIS2HH12_PM_OPS NULL +#endif /* CONFIG_PM */ + +static const struct i2c_device_id lis2hh12_ids[] = { + {"lis2hh12", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, lis2hh12_ids); + +#ifdef CONFIG_OF +static const struct of_device_id lis2hh12_id_table[] = { + {.compatible = "st,lis2hh12",}, + {}, +}; + +MODULE_DEVICE_TABLE(of, lis2hh12_id_table); +#endif /* CONFIG_OF */ + +static struct i2c_driver lis2hh12_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = LIS2HH12_DEV_NAME, + .pm = LIS2HH12_PM_OPS, +#ifdef CONFIG_OF + .of_match_table = lis2hh12_id_table, +#endif /* CONFIG_OF */ + }, + .probe = lis2hh12_i2c_probe, + .remove = lis2hh12_i2c_remove, + .id_table = lis2hh12_ids, +}; + +module_i2c_driver(lis2hh12_i2c_driver); + +MODULE_DESCRIPTION("STMicroelectronics lis2hh12 i2c driver"); +MODULE_AUTHOR("Armando Visconti "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/stm/accel/st_lis2hh12_spi.c b/drivers/iio/stm/accel/st_lis2hh12_spi.c new file mode 100644 index 000000000000..9b9a42c926b0 --- /dev/null +++ b/drivers/iio/stm/accel/st_lis2hh12_spi.c @@ -0,0 +1,185 @@ +/* + * STMicroelectronics lis2hh12 driver + * + * Copyright 2016 STMicroelectronics Inc. + * + * Armando Visconti + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include + +#include "st_lis2hh12.h" + +#define ST_SENSORS_SPI_READ 0x80 + +static int lis2hh12_spi_read(struct lis2hh12_data *cdata, + u8 reg_addr, int len, u8 *data) +{ + 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, + } + }; + + 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); + + return len; + +acc_spi_read_error: + mutex_unlock(&cdata->tb.buf_lock); + + return err; +} + +static int lis2hh12_spi_write(struct lis2hh12_data *cdata, + u8 reg_addr, int len, u8 *data) +{ + int err; + + struct spi_transfer xfers = { + .tx_buf = cdata->tb.tx_buf, + .bits_per_word = 8, + .len = len + 1, + }; + + if (len >= LIS2HH12_RX_MAX_LENGTH) + return -ENOMEM; + + 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); + + return err; +} + +static const struct lis2hh12_transfer_function lis2hh12_tf_spi = { + .write = lis2hh12_spi_write, + .read = lis2hh12_spi_read, +}; + +static int lis2hh12_spi_probe(struct spi_device *spi) +{ + int err; + struct lis2hh12_data *cdata; + + cdata = kmalloc(sizeof(*cdata), GFP_KERNEL); + if (!cdata) + return -ENOMEM; + + cdata->dev = &spi->dev; + cdata->name = spi->modalias; + cdata->tf = &lis2hh12_tf_spi; + spi_set_drvdata(spi, cdata); + + err = lis2hh12_common_probe(cdata, spi->irq); + if (err < 0) + goto free_data; + + return 0; + +free_data: + kfree(cdata); + return err; +} + +static int lis2hh12_spi_remove(struct spi_device *spi) +{ + struct lis2hh12_data *cdata = spi_get_drvdata(spi); + + lis2hh12_common_remove(cdata, spi->irq); + dev_info(cdata->dev, "%s: removed\n", LIS2HH12_DEV_NAME); + kfree(cdata); + + return 0; +} + +#ifdef CONFIG_PM +static int lis2hh12_suspend(struct device *dev) +{ + struct lis2hh12_data *cdata = spi_get_drvdata(to_spi_device(dev)); + + return lis2hh12_common_suspend(cdata); +} + +static int lis2hh12_resume(struct device *dev) +{ + struct lis2hh12_data *cdata = spi_get_drvdata(to_spi_device(dev)); + + return lis2hh12_common_resume(cdata); +} + +static const struct dev_pm_ops lis2hh12_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(lis2hh12_suspend, lis2hh12_resume) +}; + +#define LIS2HH12_PM_OPS (&lis2hh12_pm_ops) +#else /* CONFIG_PM */ +#define LIS2HH12_PM_OPS NULL +#endif /* CONFIG_PM */ + +static const struct spi_device_id lis2hh12_ids[] = { + {"lis2hh12", 0}, + {} +}; + +MODULE_DEVICE_TABLE(spi, lis2hh12_ids); + +#ifdef CONFIG_OF +static const struct of_device_id lis2hh12_id_table[] = { + { .compatible = "st,lis2hh12"}, + {}, +}; + +MODULE_DEVICE_TABLE(of, lis2hh12_id_table); +#endif /* CONFIG_OF */ + +static struct spi_driver lis2hh12_spi_driver = { + .driver = { + .owner = THIS_MODULE, + .name = LIS2HH12_DEV_NAME, + .pm = LIS2HH12_PM_OPS, +#ifdef CONFIG_OF + .of_match_table = lis2hh12_id_table, +#endif /* CONFIG_OF */ + }, + .probe = lis2hh12_spi_probe, + .remove = lis2hh12_spi_remove, + .id_table = lis2hh12_ids, +}; + +module_spi_driver(lis2hh12_spi_driver); + +MODULE_DESCRIPTION("STMicroelectronics lis2hh12 spi driver"); +MODULE_AUTHOR("Armando Visconti "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/stm/accel/st_lis2hh12_trigger.c b/drivers/iio/stm/accel/st_lis2hh12_trigger.c new file mode 100644 index 000000000000..817d6cf882bd --- /dev/null +++ b/drivers/iio/stm/accel/st_lis2hh12_trigger.c @@ -0,0 +1,106 @@ +/* + * STMicroelectronics lis2hh12 driver + * + * Copyright 2016 STMicroelectronics Inc. + * + * Armando Visconti + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "st_lis2hh12.h" + +static irqreturn_t lis2hh12_irq_management(int irq, void *private) +{ + struct lis2hh12_data *cdata = private; + u8 status; + + cdata->timestamp = + iio_get_time_ns(cdata->iio_sensors_dev[LIS2HH12_ACCEL]); + + if (cdata->hwfifo_enabled) { + cdata->tf->read(cdata, LIS2HH12_FIFO_STATUS_ADDR, 1, &status); + + if (status & LIS2HH12_FIFO_SRC_FTH_MASK) + lis2hh12_read_fifo(cdata, true); + } else { + cdata->tf->read(cdata, LIS2HH12_STATUS_ADDR, 1, &status); + + if (status & LIS2HH12_DATA_XYZ_RDY) + lis2hh12_read_xyz(cdata); + } + + return IRQ_HANDLED; +} + +int lis2hh12_allocate_triggers(struct lis2hh12_data *cdata, + const struct iio_trigger_ops *trigger_ops) +{ + int err, i, n; + + for (i = 0; i < LIS2HH12_SENSORS_NUMB; i++) { + cdata->iio_trig[i] = iio_trigger_alloc("%s-trigger", + cdata->iio_sensors_dev[i]->name); + if (!cdata->iio_trig[i]) { + dev_err(cdata->dev, "failed to allocate iio trigger.\n"); + err = -ENOMEM; + + goto deallocate_trigger; + } + iio_trigger_set_drvdata(cdata->iio_trig[i], + cdata->iio_sensors_dev[i]); + cdata->iio_trig[i]->ops = trigger_ops; + cdata->iio_trig[i]->dev.parent = cdata->dev; + } + + err = request_threaded_irq(cdata->irq, NULL, lis2hh12_irq_management, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, cdata->name, cdata); + if (err) + goto deallocate_trigger; + + for (n = 0; n < LIS2HH12_SENSORS_NUMB; n++) { + err = iio_trigger_register(cdata->iio_trig[n]); + if (err < 0) { + dev_err(cdata->dev, "failed to register iio trigger.\n"); + + goto free_irq; + } + cdata->iio_sensors_dev[n]->trig = cdata->iio_trig[n]; + } + + return 0; + +free_irq: + free_irq(cdata->irq, cdata); + for (n--; n >= 0; n--) + iio_trigger_unregister(cdata->iio_trig[n]); +deallocate_trigger: + for (i--; i >= 0; i--) + iio_trigger_free(cdata->iio_trig[i]); + + return err; +} +EXPORT_SYMBOL(lis2hh12_allocate_triggers); + +void lis2hh12_deallocate_triggers(struct lis2hh12_data *cdata) +{ + int i; + + free_irq(cdata->irq, cdata); + + for (i = 0; i < LIS2HH12_SENSORS_NUMB; i++) + iio_trigger_unregister(cdata->iio_trig[i]); +} +EXPORT_SYMBOL(lis2hh12_deallocate_triggers); + +MODULE_DESCRIPTION("STMicroelectronics lis2hh12 driver"); +MODULE_AUTHOR("Armando Visconti "); +MODULE_LICENSE("GPL v2"); diff --git a/stm_iio_configs/lis2hh12_defconfig b/stm_iio_configs/lis2hh12_defconfig new file mode 100644 index 000000000000..23a471d5cfc6 --- /dev/null +++ b/stm_iio_configs/lis2hh12_defconfig @@ -0,0 +1,3 @@ +CONFIG_IIO_ST_LIS2HH12=m +CONFIG_IIO_ST_LIS2HH12_I2C=m +CONFIG_IIO_ST_LIS2HH12_SPI=m