drivers:iio:stm:imu:st_asm330lhhx: Starting support asm330lhhx STMEMS IMU

Renamed st_asm330lhh driver to st_asm330lhhx, in order to include all
asm330lhh IMU device sensor family.

Signed-off-by: Mario Tesi <mario.tesi@st.com>
Change-Id: I98355a2050269eac4576d3e0a2d6243284c37387
Reviewed-on: https://gerrit.st.com/c/linuxandroidopen/stm-ldd-iio/+/260376
Tested-by: CITOOLS <MDG-smet-aci-reviews@list.st.com>
Reviewed-by: Denis CIOCCA <denis.ciocca@st.com>
This commit is contained in:
Mario Tesi 2022-07-14 16:28:16 +02:00 committed by Denis CIOCCA
parent 3f13ee31e8
commit c8055e7d00
20 changed files with 4043 additions and 4005 deletions

View File

@ -1,8 +1,9 @@
* st_asm330lhh driver for imu MEMS sensors
* st_asm330lhhx driver for imu MEMS sensors
Required properties for all bus drivers:
- compatible: must be one of:
"st,asm330lhh"
"st,asm330lhhx"
Required properties for the i2c bindings:
- reg: i2c slave address
@ -32,7 +33,7 @@ Optional properties for all bus drivers:
Refer to interrupt-controller/interrupts.txt for generic interrupt
client node bindings.
Example for an spi device node:
Example for an spi asm330lhh device node:
asm330lhh-imu@0 {
compatible = "st,asm330lhh";
@ -47,3 +48,18 @@ asm330lhh-imu@0 {
"0", "1", "0",
"0", "0", "1";
};
Example for an i2c asm330lhhx device node (SA0 pulled down):
asm330lhhx-imu@0x6a {
compatible = "st,asm330lhhx";
reg = <0x6a>;
interrupt-parent = <&gpio0>;
interrupts = <0 IRQ_TYPE_LEVEL_HIGH>;
vddio-supply = <&sensors_vddio>;
vdd-supply = <&sensors_vdd>;
st,int-pin = <1>;
mount-matrix = "1", "0", "0",
"0", "1", "0",
"0", "0", "1";
};

View File

@ -9,7 +9,7 @@ menu "Inertial measurement units"
source "drivers/iio/stm/imu/st_lsm6dsox/Kconfig"
source "drivers/iio/stm/imu/st_lsm6dsrx/Kconfig"
source "drivers/iio/stm/imu/st_ism330dhcx/Kconfig"
source "drivers/iio/stm/imu/st_asm330lhh/Kconfig"
source "drivers/iio/stm/imu/st_asm330lhhx/Kconfig"
source "drivers/iio/stm/imu/st_imu68/Kconfig"
source "drivers/iio/stm/imu/st_ism330dlc/Kconfig"
source "drivers/iio/stm/imu/st_lsm6ds3/Kconfig"

View File

@ -7,7 +7,7 @@
obj-y += st_lsm6dsox/
obj-y += st_lsm6dsrx/
obj-y += st_ism330dhcx/
obj-y += st_asm330lhh/
obj-y += st_asm330lhhx/
obj-y += st_imu68/
obj-y += st_ism330dlc/
obj-y += st_lsm6ds3/

View File

@ -1,8 +0,0 @@
st_asm330lhh-y := st_asm330lhh_core.o st_asm330lhh_buffer.o \
st_asm330lhh_events.o
st_asm330lhh-$(CONFIG_IIO_ST_ASM330LHH_ASYNC_HW_TIMESTAMP) += st_asm330lhh_hwtimestamp.o
obj-$(CONFIG_IIO_ST_ASM330LHH) += st_asm330lhh.o
obj-$(CONFIG_IIO_ST_ASM330LHH_I2C) += st_asm330lhh_i2c.o
obj-$(CONFIG_IIO_ST_ASM330LHH_SPI) += st_asm330lhh_spi.o
obj-$(CONFIG_IIO_ST_ASM330LHH_ASYNC_HW_TIMESTAMP) += st_asm330lhh_hwtimestamp.o

View File

@ -1,568 +0,0 @@
/*
* STMicroelectronics st_asm330lhh sensor driver
*
* Copyright 2019 STMicroelectronics Inc.
*
* Lorenzo Bianconi <lorenzo.bianconi@st.com>
*
* Licensed under the GPL-2.
*/
#ifndef ST_ASM330LHH_H
#define ST_ASM330LHH_H
#include <linux/device.h>
#include <linux/iio/iio.h>
#include <linux/delay.h>
#include <linux/workqueue.h>
#include <linux/hrtimer.h>
#include <linux/spinlock.h>
#define ST_ASM330LHH_DRV_VERSION "1.1"
#define ST_ASM330LHH_DEBUG_DISCHARGE
#define ST_ASM330LHH_MAX_ODR 833
#define ST_ASM330LHH_ODR_LIST_SIZE 8
#define ST_ASM330LHH_ODR_EXPAND(odr, uodr) ((odr * 1000000) + uodr)
#define ST_ASM330LHH_DEV_NAME "asm330lhh"
#define ST_ASM330LHH_DEFAULT_XL_FS_INDEX 2
#define ST_ASM330LHH_DEFAULT_XL_ODR_INDEX 1
#define ST_ASM330LHH_DEFAULT_G_FS_INDEX 2
#define ST_ASM330LHH_DEFAULT_G_ODR_INDEX 1
#define ST_ASM330LHH_DEFAULT_T_FS_INDEX 0
#define ST_ASM330LHH_DEFAULT_T_ODR_INDEX 1
#define ST_ASM330LHH_REG_FIFO_CTRL1_ADDR 0x07
#define ST_ASM330LHH_REG_FIFO_CTRL2_ADDR 0x08
#define ST_ASM330LHH_REG_FIFO_WTM_MASK GENMASK(8, 0)
#define ST_ASM330LHH_REG_FIFO_WTM8_MASK BIT(0)
#define ST_ASM330LHH_REG_FIFO_STATUS_DIFF GENMASK(9, 0)
#define ST_ASM330LHH_REG_FIFO_CTRL3_ADDR 0x09
#define ST_ASM330LHH_REG_BDR_XL_MASK GENMASK(3, 0)
#define ST_ASM330LHH_REG_BDR_GY_MASK GENMASK(7, 4)
#define ST_ASM330LHH_REG_FIFO_CTRL4_ADDR 0x0a
#define ST_ASM330LHH_REG_FIFO_MODE_MASK GENMASK(2, 0)
#define ST_ASM330LHH_REG_DEC_TS_MASK GENMASK(7, 6)
#define ST_ASM330LHH_REG_ODR_T_BATCH_MASK GENMASK(5, 4)
#define ST_ASM330LHH_REG_INT1_CTRL_ADDR 0x0d
#define ST_ASM330LHH_REG_INT2_CTRL_ADDR 0x0e
#define ST_ASM330LHH_REG_INT_FIFO_TH_MASK BIT(3)
#define ST_ASM330LHH_REG_WHOAMI_ADDR 0x0f
#define ST_ASM330LHH_WHOAMI_VAL 0x6b
#define ST_ASM330LHH_CTRL1_XL_ADDR 0x10
#define ST_ASM330LHH_CTRL2_G_ADDR 0x11
#define ST_ASM330LHH_REG_CTRL3_C_ADDR 0x12
#define ST_ASM330LHH_REG_SW_RESET_MASK BIT(0)
#define ST_ASM330LHH_REG_PP_OD_MASK BIT(4)
#define ST_ASM330LHH_REG_H_LACTIVE_MASK BIT(5)
#define ST_ASM330LHH_REG_BDU_MASK BIT(6)
#define ST_ASM330LHH_REG_BOOT_MASK BIT(7)
#define ST_ASM330LHH_REG_CTRL4_C_ADDR 0x13
#define ST_ASM330LHH_REG_DRDY_MASK BIT(3)
#define ST_ASM330LHH_REG_CTRL5_C_ADDR 0x14
#define ST_ASM330LHH_REG_ROUNDING_MASK GENMASK(6, 5)
#define ST_ASM330LHH_REG_ST_G_MASK GENMASK(3, 2)
#define ST_ASM330LHH_REG_ST_XL_MASK GENMASK(1, 0)
#define ST_ASM330LHH_SELFTEST_ACCEL_MIN 737
#define ST_ASM330LHH_SELFTEST_ACCEL_MAX 13934
#define ST_ASM330LHH_SELFTEST_GYRO_MIN 2142
#define ST_ASM330LHH_SELFTEST_GYRO_MAX 10000
#define ST_ASM330LHH_SELF_TEST_DISABLED_VAL 0
#define ST_ASM330LHH_SELF_TEST_POS_SIGN_VAL 1
#define ST_ASM330LHH_SELF_TEST_NEG_ACCEL_SIGN_VAL 2
#define ST_ASM330LHH_SELF_TEST_NEG_GYRO_SIGN_VAL 3
#define ST_ASM330LHH_REG_CTRL9_XL_ADDR 0x18
#define ST_ASM330LHH_REG_DEVICE_CONF_MASK BIT(1)
#define ST_ASM330LHH_REG_CTRL10_C_ADDR 0x19
#define ST_ASM330LHH_REG_TIMESTAMP_EN_MASK BIT(5)
#define ST_ASM330LHH_REG_STATUS_ADDR 0x1e
#define ST_ASM330LHH_REG_STATUS_XLDA BIT(0)
#define ST_ASM330LHH_REG_STATUS_GDA BIT(1)
#define ST_ASM330LHH_REG_STATUS_TDA BIT(2)
#define ST_ASM330LHH_REG_OUT_TEMP_L_ADDR 0x20
#define ST_ASM330LHH_REG_OUTX_L_A_ADDR 0x28
#define ST_ASM330LHH_REG_OUTY_L_A_ADDR 0x2a
#define ST_ASM330LHH_REG_OUTZ_L_A_ADDR 0x2c
#define ST_ASM330LHH_REG_OUTX_L_G_ADDR 0x22
#define ST_ASM330LHH_REG_OUTY_L_G_ADDR 0x24
#define ST_ASM330LHH_REG_OUTZ_L_G_ADDR 0x26
#define ST_ASM330LHH_REG_TIMESTAMP0_ADDR 0x40
#define ST_ASM330LHH_REG_TAP_CFG0_ADDR 0x56
#define ST_ASM330LHH_REG_LIR_MASK BIT(0)
#define ST_ASM330LHH_REG_THS_6D_ADDR 0x59
#define ST_ASM330LHH_SIXD_THS_MASK GENMASK(6, 5)
#define ST_ASM330LHH_REG_WAKE_UP_THS_ADDR 0x5b
#define ST_ASM330LHH_WAKE_UP_THS_MASK GENMASK(5, 0)
#define ST_ASM330LHH_REG_WAKE_UP_DUR_ADDR 0x5c
#define ST_ASM330LHH_WAKE_UP_DUR_MASK GENMASK(6, 5)
#define ST_ASM330LHH_REG_FREE_FALL_ADDR 0x5d
#define ST_ASM330LHH_FF_THS_MASK GENMASK(2, 0)
#define ST_ASM330LHH_INTERNAL_FREQ_FINE 0x63
/* Timestamp Tick 25us/LSB */
#define ST_ASM330LHH_TS_DELTA_NS 25000ULL
/* Temperature in uC */
#define ST_ASM330LHH_TEMP_GAIN 256
#define ST_ASM330LHH_TEMP_FS_GAIN 1000000 / ST_ASM330LHH_TEMP_GAIN
#define ST_ASM330LHH_TEMP_OFFSET 6400
/* FIFO simple size and depth */
#define ST_ASM330LHH_SAMPLE_SIZE 6
#define ST_ASM330LHH_TS_SAMPLE_SIZE 4
#define ST_ASM330LHH_TAG_SIZE 1
#define ST_ASM330LHH_FIFO_SAMPLE_SIZE (ST_ASM330LHH_SAMPLE_SIZE + \
ST_ASM330LHH_TAG_SIZE)
#define ST_ASM330LHH_MAX_FIFO_DEPTH 416
#define ST_ASM330LHH_DEFAULT_KTIME (200000000)
#define ST_ASM330LHH_FAST_KTIME (5000000)
#define ST_ASM330LHH_DATA_CHANNEL(chan_type, addr, mod, ch2, scan_idx, \
rb, sb, sg) \
{ \
.type = chan_type, \
.address = addr, \
.modified = mod, \
.channel2 = ch2, \
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
BIT(IIO_CHAN_INFO_SCALE), \
.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \
.scan_index = scan_idx, \
.scan_type = { \
.sign = sg, \
.realbits = rb, \
.storagebits = sb, \
.endianness = IIO_LE, \
}, \
.ext_info = st_asm330lhh_ext_info, \
}
static const struct iio_event_spec st_asm330lhh_flush_event = {
.type = IIO_EV_TYPE_FIFO_FLUSH,
.dir = IIO_EV_DIR_EITHER,
};
static const struct iio_event_spec st_asm330lhh_thr_event = {
.type = IIO_EV_TYPE_THRESH,
.dir = IIO_EV_DIR_RISING,
.mask_separate = BIT(IIO_EV_INFO_ENABLE),
};
#define ST_ASM330LHH_EVENT_CHANNEL(ctype, etype) \
{ \
.type = ctype, \
.modified = 0, \
.scan_index = -1, \
.indexed = -1, \
.event_spec = &st_asm330lhh_##etype##_event, \
.num_event_specs = 1, \
}
#define ST_ASM330LHH_RX_MAX_LENGTH 64
#define ST_ASM330LHH_TX_MAX_LENGTH 16
struct st_asm330lhh_transfer_buffer {
u8 rx_buf[ST_ASM330LHH_RX_MAX_LENGTH];
u8 tx_buf[ST_ASM330LHH_TX_MAX_LENGTH] ____cacheline_aligned;
};
struct st_asm330lhh_transfer_function {
int (*read)(struct device *dev, u8 addr, int len, u8 *data);
int (*write)(struct device *dev, u8 addr, int len, const u8 *data);
};
/**
* struct st_asm330lhh_reg - Generic sensor register description (addr + mask)
* @addr: Address of register.
* @mask: Bitmask register for proper usage.
*/
struct st_asm330lhh_reg {
u8 addr;
u8 mask;
};
enum st_asm330lhh_suspend_resume_register {
ST_ASM330LHH_CTRL1_XL_REG = 0,
ST_ASM330LHH_CTRL2_G_REG,
ST_ASM330LHH_REG_CTRL3_C_REG,
ST_ASM330LHH_REG_CTRL4_C_REG,
ST_ASM330LHH_REG_CTRL5_C_REG,
ST_ASM330LHH_REG_CTRL10_C_REG,
ST_ASM330LHH_REG_TAP_CFG0_REG,
ST_ASM330LHH_REG_INT1_CTRL_REG,
ST_ASM330LHH_REG_INT2_CTRL_REG,
ST_ASM330LHH_REG_FIFO_CTRL1_REG,
ST_ASM330LHH_REG_FIFO_CTRL2_REG,
ST_ASM330LHH_REG_FIFO_CTRL3_REG,
ST_ASM330LHH_REG_FIFO_CTRL4_REG,
ST_ASM330LHH_SUSPEND_RESUME_REGS,
};
struct st_asm330lhh_suspend_resume_entry {
u8 addr;
u8 val;
u8 mask;
};
/**
* struct st_asm330lhh_odr - Single ODR entry
* @hz: Most significant part of the sensor ODR (Hz).
* @uhz: Less significant part of the sensor ODR (micro Hz).
* @val: ODR register value.
* @batch_val: Batching ODR register value.
*/
struct st_asm330lhh_odr {
u16 hz;
u32 uhz;
u8 val;
u8 batch_val;
};
/**
* struct st_asm330lhh_odr_table_entry - Sensor ODR table
* @size: Size of ODR table.
* @reg: ODR register.
* @batching_reg: ODR register for batching on fifo.
* @odr_avl: Array of supported ODR value.
*/
struct st_asm330lhh_odr_table_entry {
u8 size;
struct st_asm330lhh_reg reg;
struct st_asm330lhh_reg batching_reg;
struct st_asm330lhh_odr odr_avl[ST_ASM330LHH_ODR_LIST_SIZE];
};
/**
* struct st_asm330lhh_fs - Full Scale sensor table entry
* @reg: Register description for FS settings.
* @gain: Sensor sensitivity (mdps/LSB, mg/LSB and uC/LSB).
* @val: FS register value.
*/
struct st_asm330lhh_fs {
struct st_asm330lhh_reg reg;
u32 gain;
u8 val;
};
#define ST_ASM330LHH_FS_LIST_SIZE 5
#define ST_ASM330LHH_FS_ACC_LIST_SIZE 4
#define ST_ASM330LHH_FS_GYRO_LIST_SIZE 5
#ifdef CONFIG_IIO_ST_ASM330LHH_EN_TEMPERATURE
#define ST_ASM330LHH_FS_TEMP_LIST_SIZE 1
#endif /* CONFIG_IIO_ST_ASM330LHH_EN_TEMPERATURE */
/**
* struct st_asm330lhh_fs_table_entry - Full Scale sensor table
* @size: Full Scale sensor table size.
* @fs_avl: Full Scale list entries.
*/
struct st_asm330lhh_fs_table_entry {
u8 size;
struct st_asm330lhh_fs fs_avl[ST_ASM330LHH_FS_LIST_SIZE];
};
#define ST_ASM330LHH_ACC_FS_2G_GAIN IIO_G_TO_M_S_2(61000)
#define ST_ASM330LHH_ACC_FS_4G_GAIN IIO_G_TO_M_S_2(122000)
#define ST_ASM330LHH_ACC_FS_8G_GAIN IIO_G_TO_M_S_2(244000)
#define ST_ASM330LHH_ACC_FS_16G_GAIN IIO_G_TO_M_S_2(488000)
#define ST_ASM330LHH_GYRO_FS_250_GAIN IIO_DEGREE_TO_RAD(8750000)
#define ST_ASM330LHH_GYRO_FS_500_GAIN IIO_DEGREE_TO_RAD(17500000)
#define ST_ASM330LHH_GYRO_FS_1000_GAIN IIO_DEGREE_TO_RAD(35000000)
#define ST_ASM330LHH_GYRO_FS_2000_GAIN IIO_DEGREE_TO_RAD(70000000)
#define ST_ASM330LHH_GYRO_FS_4000_GAIN IIO_DEGREE_TO_RAD(140000000)
enum st_asm330lhh_sensor_id {
ST_ASM330LHH_ID_GYRO = 0,
ST_ASM330LHH_ID_ACC,
#ifdef CONFIG_IIO_ST_ASM330LHH_EN_TEMPERATURE
ST_ASM330LHH_ID_TEMP,
#endif /* CONFIG_IIO_ST_ASM330LHH_EN_TEMPERATURE */
ST_ASM330LHH_ID_EVENT,
ST_ASM330LHH_ID_FF = ST_ASM330LHH_ID_EVENT,
ST_ASM330LHH_ID_SC,
ST_ASM330LHH_ID_TRIGGER,
ST_ASM330LHH_ID_WK = ST_ASM330LHH_ID_TRIGGER,
ST_ASM330LHH_ID_6D,
ST_ASM330LHH_ID_MAX,
};
enum st_asm330lhh_fifo_mode {
ST_ASM330LHH_FIFO_BYPASS = 0x0,
ST_ASM330LHH_FIFO_CONT = 0x6,
};
enum {
ST_ASM330LHH_HW_FLUSH,
ST_ASM330LHH_HW_OPERATIONAL,
};
/**
* struct st_asm330lhh_sensor - ST IMU sensor instance
* @id: Sensor identifier.
* @hw: Pointer to instance of struct st_asm330lhh_hw.
* @gain: Configured sensor sensitivity.
* @offset: Sensor data offset.
* @conf: Used in case of sensor event to manage configuration.
* @odr: Output data rate of the sensor [Hz].
* @uodr: Output data rate of the sensor [uHz].
* @max_watermark: Max supported watermark level.
* @watermark: Sensor watermark level.
* @last_fifo_timestamp: Store last sample timestamp in FIFO, used by flush
* @selftest_status: Last status of self test output
* @min_st, @max_st: Min/Max acc/gyro data values during self test procedure
*/
struct st_asm330lhh_sensor {
enum st_asm330lhh_sensor_id id;
struct st_asm330lhh_hw *hw;
struct iio_trigger *trig;
union {
/* sensor with odrs, gain and offset */
struct {
u32 gain;
u32 offset;
int odr;
int uodr;
#ifdef ST_ASM330LHH_DEBUG_DISCHARGE
u32 discharged_samples;
#endif /* ST_ASM330LHH_DEBUG_DISCHARGE */
u16 max_watermark;
u16 watermark;
s64 last_fifo_timestamp;
/* self test */
int8_t selftest_status;
int min_st;
int max_st;
};
/* sensor specific data configuration */
struct {
u32 conf[6];
};
};
};
/**
* struct st_asm330lhh_hw - ST IMU MEMS hw instance
* @dev: Pointer to instance of struct device (I2C or SPI).
* @irq: Device interrupt line (I2C or SPI).
* @int_pin: Save interrupt pin used by sensor.
* @lock: Mutex to protect read and write operations.
* @fifo_lock: Mutex to prevent concurrent access to the hw FIFO.
* @page_lock: Mutex to prevent concurrent memory page configuration.
* @fifo_mode: FIFO operating mode supported by the device.
* @state: hw operational state.
* @enable_mask: Enabled sensor bitmask.
* @hw_timestamp_global: hw timestamp value always monotonic where the most
* significant 8byte are incremented at every disable/enable.
* @timesync_workqueue: runs the async task in private workqueue.
* @timesync_work: actual work to be done in the async task workqueue.
* @timesync_timer: hrtimer used to schedule period read for the async task.
* @hwtimestamp_lock: spinlock for the 64bit timestamp value.
* @timesync_ktime: interval value used by the hrtimer.
* @timestamp_c: counter used for counting number of timesync updates.
* @ts_offset: Hw timestamp offset.
* @ts_delta_ns: Calibrate delta time tick.
* @hw_ts: Latest hw timestamp from the sensor.
* @val_ts_old: Hold hw timestamp for timer rollover.
* @hw_ts_high: Save MSB hw timestamp.
* @tsample: Timestamp for each sensor sample.
* @delta_ts: Delta time between two consecutive interrupts.
* @ts: Latest timestamp from irq handler.
* @odr_table_entry: Sensors ODR table.
* @iio_devs: Pointers to acc/gyro iio_dev instances.
* @tf: Transfer function structure used by I/O operations.
* @tb: Transfer buffers used by SPI I/O operations.
* @orientation: sensor chip orientation relative to main hardware.
*/
struct st_asm330lhh_hw {
struct device *dev;
int irq;
int int_pin;
struct mutex lock;
struct mutex fifo_lock;
struct mutex page_lock;
enum st_asm330lhh_fifo_mode fifo_mode;
unsigned long state;
u32 enable_mask;
s64 hw_timestamp_global;
#if defined (CONFIG_IIO_ST_ASM330LHH_ASYNC_HW_TIMESTAMP)
struct workqueue_struct *timesync_workqueue;
struct work_struct timesync_work;
struct hrtimer timesync_timer;
spinlock_t hwtimestamp_lock;
ktime_t timesync_ktime;
int timesync_c;
#endif /* CONFIG_IIO_ST_ASM330LHH_ASYNC_HW_TIMESTAMP */
s64 ts_offset;
u64 ts_delta_ns;
s64 hw_ts;
u32 val_ts_old;
u32 hw_ts_high;
s64 tsample;
s64 delta_ts;
s64 ts;
const struct st_asm330lhh_odr_table_entry *odr_table_entry;
struct iio_dev *iio_devs[ST_ASM330LHH_ID_MAX];
struct regulator *vdd_supply;
struct regulator *vddio_supply;
const struct st_asm330lhh_transfer_function *tf;
struct st_asm330lhh_transfer_buffer tb;
struct iio_mount_matrix orientation;
};
/**
* struct st_asm330lhh_ff_th - Free Fall threshold table
* @mg: Threshold in mg.
* @val: Register value.
*/
struct st_asm330lhh_ff_th {
u32 mg;
u8 val;
};
/**
* struct st_asm330lhh_6D_th - 6D threshold table
* @deg: Threshold in degrees.
* @val: Register value.
*/
struct st_asm330lhh_6D_th {
u8 deg;
u8 val;
};
extern const struct dev_pm_ops st_asm330lhh_pm_ops;
static inline int st_asm330lhh_read_atomic(struct st_asm330lhh_hw *hw, u8 addr,
int len, u8 *data)
{
int err;
mutex_lock(&hw->page_lock);
err = hw->tf->read(hw->dev, addr, len, data);
mutex_unlock(&hw->page_lock);
return err;
}
static inline int st_asm330lhh_write_atomic(struct st_asm330lhh_hw *hw, u8 addr,
int len, u8 *data)
{
int err;
mutex_lock(&hw->page_lock);
err = hw->tf->write(hw->dev, addr, len, data);
mutex_unlock(&hw->page_lock);
return err;
}
int __st_asm330lhh_write_with_mask(struct st_asm330lhh_hw *hw, u8 addr, u8 mask,
u8 val);
static inline int st_asm330lhh_write_with_mask(struct st_asm330lhh_hw *hw, u8 addr,
u8 mask, u8 val)
{
int err;
mutex_lock(&hw->page_lock);
err = __st_asm330lhh_write_with_mask(hw, addr, mask, val);
mutex_unlock(&hw->page_lock);
return err;
}
static inline bool st_asm330lhh_is_fifo_enabled(struct st_asm330lhh_hw *hw)
{
return hw->enable_mask & (BIT(ST_ASM330LHH_ID_GYRO) |
BIT(ST_ASM330LHH_ID_ACC));
}
static inline s64 st_asm330lhh_get_time_ns(struct iio_dev *iio_dev)
{
return iio_get_time_ns(iio_dev);
}
int st_asm330lhh_probe(struct device *dev, int irq,
const struct st_asm330lhh_transfer_function *tf_ops);
int st_asm330lhh_sensor_set_enable(struct st_asm330lhh_sensor *sensor,
bool enable);
int st_asm330lhh_buffers_setup(struct st_asm330lhh_hw *hw);
int st_asm330lhh_get_batch_val(struct st_asm330lhh_sensor *sensor, int odr,
int uodr, u8 *val);
int st_asm330lhh_update_watermark(struct st_asm330lhh_sensor *sensor,
u16 watermark);
ssize_t st_asm330lhh_flush_fifo(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size);
ssize_t st_asm330lhh_get_max_watermark(struct device *dev,
struct device_attribute *attr, char *buf);
ssize_t st_asm330lhh_get_watermark(struct device *dev,
struct device_attribute *attr, char *buf);
ssize_t st_asm330lhh_set_watermark(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size);
int st_asm330lhh_suspend_fifo(struct st_asm330lhh_hw *hw);
int st_asm330lhh_set_fifo_mode(struct st_asm330lhh_hw *hw,
enum st_asm330lhh_fifo_mode fifo_mode);
int __st_asm330lhh_set_sensor_batching_odr(struct st_asm330lhh_sensor *sensor,
bool enable);
int st_asm330lhh_update_batching(struct iio_dev *iio_dev, bool enable);
int st_asm330lhh_reset_hwts(struct st_asm330lhh_hw *hw);
#ifdef CONFIG_IIO_ST_ASM330LHH_EN_BASIC_FEATURES
int st_asm330lhh_event_handler(struct st_asm330lhh_hw *hw);
int st_asm330lhh_probe_event(struct st_asm330lhh_hw *hw);
int st_asm330lhh_set_wake_up_thershold(struct st_asm330lhh_hw *hw, int th_ug);
int st_asm330lhh_set_wake_up_duration(struct st_asm330lhh_hw *hw, int dur_ms);
int st_asm330lhh_set_freefall_threshold(struct st_asm330lhh_hw *hw, int th_mg);
int st_asm330lhh_set_6D_threshold(struct st_asm330lhh_hw *hw, int deg);
int st_asm330lhh_read_with_mask(struct st_asm330lhh_hw *hw, u8 addr, u8 mask,
u8 *val);
#endif /* CONFIG_IIO_ST_ASM330LHH_EN_BASIC_FEATURES */
#if defined (CONFIG_IIO_ST_ASM330LHH_ASYNC_HW_TIMESTAMP)
int st_asm330lhh_hwtimesync_init(struct st_asm330lhh_hw *hw);
#else /* CONFIG_IIO_ST_ASM330LHH_ASYNC_HW_TIMESTAMP */
static inline int st_asm330lhh_hwtimesync_init(struct st_asm330lhh_hw *hw)
{
return 0;
}
#endif /* CONFIG_IIO_ST_ASM330LHH_ASYNC_HW_TIMESTAMP */
#endif /* ST_ASM330LHH_H */

View File

@ -1,673 +0,0 @@
/*
* STMicroelectronics st_asm330lhh FIFO buffer library driver
*
* Copyright 2019 STMicroelectronics Inc.
*
* Lorenzo Bianconi <lorenzo.bianconi@st.com>
* Mario Tesi <mario.tesi@st.com>
*
* Licensed under the GPL-2.
*/
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/iio/iio.h>
#include <linux/iio/kfifo_buf.h>
#include <linux/iio/events.h>
#include <linux/iio/buffer.h>
#include <asm/unaligned.h>
#include <linux/iio/buffer.h>
#include "st_asm330lhh.h"
#define ST_ASM330LHH_REG_FIFO_STATUS1_ADDR 0x3a
#define ST_ASM330LHH_REG_TIMESTAMP2_ADDR 0x42
#define ST_ASM330LHH_REG_FIFO_DATA_OUT_TAG_ADDR 0x78
#define ST_ASM330LHH_SAMPLE_DISCHARD 0x7ffd
/* Timestamp convergence filter parameter */
#define ST_ASM330LHH_EWMA_LEVEL 120
#define ST_ASM330LHH_EWMA_DIV 128
#define ST_ASM330LHH_TIMESTAMP_RESET_VALUE 0xaa
enum {
ST_ASM330LHH_GYRO_TAG = 0x01,
ST_ASM330LHH_ACC_TAG = 0x02,
#ifdef CONFIG_IIO_ST_ASM330LHH_EN_TEMPERATURE
ST_ASM330LHH_TEMP_TAG = 0x03,
#endif /* CONFIG_IIO_ST_ASM330LHH_EN_TEMPERATURE */
ST_ASM330LHH_TS_TAG = 0x04,
};
/* Default timeout before to re-enable gyro */
int delay_gyro = 10;
module_param(delay_gyro, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
MODULE_PARM_DESC(delay_gyro, "Delay for Gyro arming");
static bool delayed_enable_gyro;
static inline s64 st_asm330lhh_ewma(s64 old, s64 new, int weight)
{
s64 diff, incr;
diff = new - old;
incr = div_s64((ST_ASM330LHH_EWMA_DIV - weight) * diff,
ST_ASM330LHH_EWMA_DIV);
return old + incr;
}
inline int st_asm330lhh_reset_hwts(struct st_asm330lhh_hw *hw)
{
#if defined(CONFIG_IIO_ST_ASM330LHH_ASYNC_HW_TIMESTAMP)
struct st_asm330lhh_sensor *sensor;
int i;
#endif /* CONFIG_IIO_ST_ASM330LHH_ASYNC_HW_TIMESTAMP */
u8 data = ST_ASM330LHH_TIMESTAMP_RESET_VALUE;
int ret;
ret = st_asm330lhh_write_atomic(hw, ST_ASM330LHH_REG_TIMESTAMP2_ADDR,
sizeof(data), &data);
if (ret < 0)
return ret;
#if defined(CONFIG_IIO_ST_ASM330LHH_ASYNC_HW_TIMESTAMP)
spin_lock_irq(&hw->hwtimestamp_lock);
hw->hw_timestamp_global = (hw->hw_timestamp_global + (1LL << 32)) &
GENMASK_ULL(63, 32);
spin_unlock_irq(&hw->hwtimestamp_lock);
for (i = 0; i < ST_ASM330LHH_ID_MAX; ++i) {
if (!hw->iio_devs[i])
continue;
sensor = iio_priv(hw->iio_devs[i]);
}
hw->timesync_c = 0;
hw->timesync_ktime = ktime_set(0, ST_ASM330LHH_FAST_KTIME);
#else /* CONFIG_IIO_ST_ASM330LHH_ASYNC_HW_TIMESTAMP */
hw->hw_timestamp_global = (hw->hw_timestamp_global + (1LL << 32)) &
GENMASK_ULL(63, 32);
#endif /* CONFIG_IIO_ST_ASM330LHH_ASYNC_HW_TIMESTAMP */
hw->ts = st_asm330lhh_get_time_ns(hw->iio_devs[0]);
hw->ts_offset = hw->ts;
hw->val_ts_old = 0;
hw->hw_ts_high = 0;
hw->tsample = 0ull;
return 0;
}
int st_asm330lhh_set_fifo_mode(struct st_asm330lhh_hw *hw,
enum st_asm330lhh_fifo_mode fifo_mode)
{
int err;
err = st_asm330lhh_write_with_mask(hw, ST_ASM330LHH_REG_FIFO_CTRL4_ADDR,
ST_ASM330LHH_REG_FIFO_MODE_MASK,
fifo_mode);
if (err < 0)
return err;
hw->fifo_mode = fifo_mode;
if (fifo_mode == ST_ASM330LHH_FIFO_BYPASS)
clear_bit(ST_ASM330LHH_HW_OPERATIONAL, &hw->state);
else
set_bit(ST_ASM330LHH_HW_OPERATIONAL, &hw->state);
return 0;
}
int __st_asm330lhh_set_sensor_batching_odr(struct st_asm330lhh_sensor *s,
bool enable)
{
enum st_asm330lhh_sensor_id id = s->id;
struct st_asm330lhh_hw *hw = s->hw;
u8 data = 0;
int err;
if (enable) {
err = st_asm330lhh_get_batch_val(s, s->odr, s->uodr, &data);
if (err < 0)
return err;
}
return __st_asm330lhh_write_with_mask(hw,
hw->odr_table_entry[id].batching_reg.addr,
hw->odr_table_entry[id].batching_reg.mask,
data);
}
static inline int
st_asm330lhh_set_sensor_batching_odr(struct st_asm330lhh_sensor *sensor,
bool enable)
{
struct st_asm330lhh_hw *hw = sensor->hw;
int err;
mutex_lock(&hw->page_lock);
err = __st_asm330lhh_set_sensor_batching_odr(sensor, enable);
mutex_unlock(&hw->page_lock);
return err;
}
int st_asm330lhh_update_watermark(struct st_asm330lhh_sensor *sensor,
u16 watermark)
{
u16 fifo_watermark = ST_ASM330LHH_MAX_FIFO_DEPTH, cur_watermark = 0;
struct st_asm330lhh_hw *hw = sensor->hw;
struct st_asm330lhh_sensor *cur_sensor;
__le16 wdata;
int i, err;
u8 data;
for (i = ST_ASM330LHH_ID_GYRO; i < ST_ASM330LHH_ID_MAX; i++) {
if (!hw->iio_devs[i])
continue;
cur_sensor = iio_priv(hw->iio_devs[i]);
if (!(hw->enable_mask & BIT(cur_sensor->id)))
continue;
cur_watermark = (cur_sensor == sensor) ? watermark
: cur_sensor->watermark;
fifo_watermark = min_t(u16, fifo_watermark, cur_watermark);
}
fifo_watermark = max_t(u16, fifo_watermark, 2);
mutex_lock(&hw->lock);
err = st_asm330lhh_read_atomic(hw, ST_ASM330LHH_REG_FIFO_CTRL1_ADDR + 1,
sizeof(data), &data);
if (err < 0)
goto out;
fifo_watermark = ((data << 8) & ~ST_ASM330LHH_REG_FIFO_WTM_MASK) |
(fifo_watermark & ST_ASM330LHH_REG_FIFO_WTM_MASK);
wdata = cpu_to_le16(fifo_watermark);
err = st_asm330lhh_write_atomic(hw, ST_ASM330LHH_REG_FIFO_CTRL1_ADDR,
sizeof(wdata), (u8 *)&wdata);
out:
mutex_unlock(&hw->lock);
return err < 0 ? err : 0;
}
static struct iio_dev *st_asm330lhh_get_iiodev_from_tag(struct st_asm330lhh_hw *hw,
u8 tag)
{
struct iio_dev *iio_dev;
switch (tag) {
case ST_ASM330LHH_GYRO_TAG:
iio_dev = hw->iio_devs[ST_ASM330LHH_ID_GYRO];
break;
case ST_ASM330LHH_ACC_TAG:
iio_dev = hw->iio_devs[ST_ASM330LHH_ID_ACC];
break;
#ifdef CONFIG_IIO_ST_ASM330LHH_EN_TEMPERATURE
case ST_ASM330LHH_TEMP_TAG:
iio_dev = hw->iio_devs[ST_ASM330LHH_ID_TEMP];
break;
#endif /* CONFIG_IIO_ST_ASM330LHH_EN_TEMPERATURE */
default:
iio_dev = NULL;
break;
}
return iio_dev;
}
static inline void st_asm330lhh_sync_hw_ts(struct st_asm330lhh_hw *hw, s64 ts)
{
s64 delta = ts - hw->hw_ts;
hw->ts_offset = st_asm330lhh_ewma(hw->ts_offset, delta,
ST_ASM330LHH_EWMA_LEVEL);
}
static int st_asm330lhh_read_fifo(struct st_asm330lhh_hw *hw)
{
u8 iio_buf[ALIGN(ST_ASM330LHH_SAMPLE_SIZE, sizeof(s64)) +
sizeof(s64) + sizeof(s64)];
u8 buf[6 * ST_ASM330LHH_FIFO_SAMPLE_SIZE], tag, *ptr;
int i, err, word_len, fifo_len, read_len;
__le64 hw_timestamp_push;
struct iio_dev *iio_dev;
s64 ts_irq, hw_ts_old;
__le16 fifo_status;
u16 fifo_depth;
s16 drdymask;
u32 val;
/* return if FIFO is already disabled */
if (!test_bit(ST_ASM330LHH_HW_OPERATIONAL, &hw->state)) {
dev_warn(hw->dev, "%s: FIFO in bypass mode\n", __func__);
return 0;
}
ts_irq = hw->ts - hw->delta_ts;
err = st_asm330lhh_read_atomic(hw, ST_ASM330LHH_REG_FIFO_STATUS1_ADDR,
sizeof(fifo_status), (u8 *)&fifo_status);
if (err < 0)
return err;
fifo_depth = le16_to_cpu(fifo_status) & ST_ASM330LHH_REG_FIFO_STATUS_DIFF;
if (!fifo_depth)
return 0;
fifo_len = fifo_depth * ST_ASM330LHH_FIFO_SAMPLE_SIZE;
read_len = 0;
while (read_len < fifo_len) {
word_len = min_t(int, fifo_len - read_len, sizeof(buf));
err = st_asm330lhh_read_atomic(hw,
ST_ASM330LHH_REG_FIFO_DATA_OUT_TAG_ADDR,
word_len, buf);
if (err < 0)
return err;
for (i = 0; i < word_len; i += ST_ASM330LHH_FIFO_SAMPLE_SIZE) {
ptr = &buf[i + ST_ASM330LHH_TAG_SIZE];
tag = buf[i] >> 3;
if (tag == ST_ASM330LHH_TS_TAG) {
val = get_unaligned_le32(ptr);
#if defined(CONFIG_IIO_ST_ASM330LHH_ASYNC_HW_TIMESTAMP)
spin_lock_irq(&hw->hwtimestamp_lock);
#endif /* CONFIG_IIO_ST_ASM330LHH_ASYNC_HW_TIMESTAMP */
hw->hw_timestamp_global =
(hw->hw_timestamp_global &
GENMASK_ULL(63, 32)) |
(u32)le32_to_cpu(get_unaligned_le32(ptr));
#if defined(CONFIG_IIO_ST_ASM330LHH_ASYNC_HW_TIMESTAMP)
spin_unlock_irq(&hw->hwtimestamp_lock);
#endif /* CONFIG_IIO_ST_ASM330LHH_ASYNC_HW_TIMESTAMP */
if (hw->val_ts_old > val)
hw->hw_ts_high++;
hw_ts_old = hw->hw_ts;
/* check hw rollover */
hw->val_ts_old = val;
hw->hw_ts = (val + ((s64)hw->hw_ts_high << 32)) *
hw->ts_delta_ns;
hw->ts_offset = st_asm330lhh_ewma(hw->ts_offset,
ts_irq - hw->hw_ts,
ST_ASM330LHH_EWMA_LEVEL);
if (!test_bit(ST_ASM330LHH_HW_FLUSH, &hw->state))
/* sync ap timestamp and sensor one */
st_asm330lhh_sync_hw_ts(hw, ts_irq);
ts_irq += hw->hw_ts;
if (!hw->tsample)
hw->tsample = hw->ts_offset + hw->hw_ts;
else
hw->tsample = hw->tsample + hw->hw_ts - hw_ts_old;
} else {
struct st_asm330lhh_sensor *sensor;
iio_dev = st_asm330lhh_get_iiodev_from_tag(hw, tag);
if (!iio_dev)
continue;
sensor = iio_priv(iio_dev);
/* skip samples if not ready */
drdymask = (s16)le16_to_cpu(get_unaligned_le16(ptr));
if (unlikely(drdymask >= ST_ASM330LHH_SAMPLE_DISCHARD)) {
#ifdef ST_ASM330LHH_DEBUG_DISCHARGE
sensor->discharged_samples++;
#endif /* ST_ASM330LHH_DEBUG_DISCHARGE */
continue;
}
memcpy(iio_buf, ptr, ST_ASM330LHH_SAMPLE_SIZE);
#if defined(CONFIG_IIO_ST_ASM330LHH_ASYNC_HW_TIMESTAMP)
spin_lock_irq(&hw->hwtimestamp_lock);
#endif /* CONFIG_IIO_ST_ASM330LHH_ASYNC_HW_TIMESTAMP */
hw_timestamp_push = cpu_to_le64(hw->hw_timestamp_global);
#if defined(CONFIG_IIO_ST_ASM330LHH_ASYNC_HW_TIMESTAMP)
spin_unlock_irq(&hw->hwtimestamp_lock);
#endif /* CONFIG_IIO_ST_ASM330LHH_ASYNC_HW_TIMESTAMP */
memcpy(&iio_buf[ALIGN(ST_ASM330LHH_SAMPLE_SIZE, sizeof(s64))],
&hw_timestamp_push, sizeof(hw_timestamp_push));
hw->tsample = min_t(s64,
st_asm330lhh_get_time_ns(hw->iio_devs[0]),
hw->tsample);
iio_push_to_buffers_with_timestamp(iio_dev,
iio_buf,
hw->tsample);
sensor->last_fifo_timestamp = hw_timestamp_push;
}
}
read_len += word_len;
}
return read_len;
}
ssize_t st_asm330lhh_get_max_watermark(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct st_asm330lhh_sensor *sensor = iio_priv(dev_get_drvdata(dev));
return sprintf(buf, "%d\n", sensor->max_watermark);
}
ssize_t st_asm330lhh_get_watermark(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct st_asm330lhh_sensor *sensor = iio_priv(dev_get_drvdata(dev));
return sprintf(buf, "%d\n", sensor->watermark);
}
ssize_t st_asm330lhh_set_watermark(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
struct iio_dev *iio_dev = dev_get_drvdata(dev);
struct st_asm330lhh_sensor *sensor = iio_priv(iio_dev);
int err, val;
err = iio_device_claim_direct_mode(iio_dev);
if (err)
return err;
err = kstrtoint(buf, 10, &val);
if (err < 0)
goto out;
err = st_asm330lhh_update_watermark(sensor, val);
if (err < 0)
goto out;
sensor->watermark = val;
out:
iio_device_release_direct_mode(iio_dev);
return err < 0 ? err : size;
}
ssize_t st_asm330lhh_flush_fifo(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
struct iio_dev *iio_dev = dev_get_drvdata(dev);
struct st_asm330lhh_sensor *sensor = iio_priv(iio_dev);
struct st_asm330lhh_hw *hw = sensor->hw;
s64 event;
int count;
s64 type;
s64 fts;
s64 ts;
mutex_lock(&hw->fifo_lock);
ts = st_asm330lhh_get_time_ns(iio_dev);
hw->delta_ts = ts - hw->ts;
hw->ts = ts;
set_bit(ST_ASM330LHH_HW_FLUSH, &hw->state);
count = st_asm330lhh_read_fifo(hw);
fts = sensor->last_fifo_timestamp;
mutex_unlock(&hw->fifo_lock);
type = count > 0 ? IIO_EV_DIR_FIFO_DATA : IIO_EV_DIR_FIFO_EMPTY;
event = IIO_UNMOD_EVENT_CODE(iio_dev->channels[0].type, -1,
IIO_EV_TYPE_FIFO_FLUSH, type);
iio_push_event(iio_dev, event, fts);
return size;
}
int st_asm330lhh_suspend_fifo(struct st_asm330lhh_hw *hw)
{
int err;
mutex_lock(&hw->fifo_lock);
st_asm330lhh_read_fifo(hw);
err = st_asm330lhh_set_fifo_mode(hw, ST_ASM330LHH_FIFO_BYPASS);
mutex_unlock(&hw->fifo_lock);
return err;
}
int st_asm330lhh_update_batching(struct iio_dev *iio_dev, bool enable)
{
struct st_asm330lhh_sensor *sensor = iio_priv(iio_dev);
struct st_asm330lhh_hw *hw = sensor->hw;
int err;
disable_irq(hw->irq);
err = st_asm330lhh_set_sensor_batching_odr(sensor, enable);
enable_irq(hw->irq);
return err;
}
static int st_asm330lhh_update_fifo(struct iio_dev *iio_dev, bool enable)
{
struct st_asm330lhh_sensor *sensor = iio_priv(iio_dev);
struct st_asm330lhh_hw *hw = sensor->hw;
int err;
if (sensor->id == ST_ASM330LHH_ID_GYRO && !enable)
delayed_enable_gyro = true;
if (sensor->id == ST_ASM330LHH_ID_GYRO &&
enable && delayed_enable_gyro) {
delayed_enable_gyro = false;
msleep(delay_gyro);
}
disable_irq(hw->irq);
#if defined(CONFIG_IIO_ST_ASM330LHH_ASYNC_HW_TIMESTAMP)
hrtimer_cancel(&hw->timesync_timer);
cancel_work_sync(&hw->timesync_work);
#endif /* CONFIG_IIO_ST_ASM330LHH_ASYNC_HW_TIMESTAMP */
err = st_asm330lhh_sensor_set_enable(sensor, enable);
if (err < 0)
goto out;
err = st_asm330lhh_set_sensor_batching_odr(sensor, enable);
if (err < 0)
goto out;
#ifdef CONFIG_IIO_ST_ASM330LHH_EN_TEMPERATURE
/*
* This is an auxiliary sensor, it need to get batched
* toghether at least with a primary sensor (Acc/Gyro).
*/
if (sensor->id == ST_ASM330LHH_ID_TEMP) {
if (!(hw->enable_mask & (BIT(ST_ASM330LHH_ID_ACC) |
BIT(ST_ASM330LHH_ID_GYRO)))) {
struct st_asm330lhh_sensor *acc_sensor;
u8 data = 0;
acc_sensor = iio_priv(hw->iio_devs[ST_ASM330LHH_ID_ACC]);
if (enable) {
err = st_asm330lhh_get_batch_val(acc_sensor,
sensor->odr, sensor->uodr,
&data);
if (err < 0)
goto out;
}
err = st_asm330lhh_write_with_mask(hw,
hw->odr_table_entry[ST_ASM330LHH_ID_ACC].batching_reg.addr,
hw->odr_table_entry[ST_ASM330LHH_ID_ACC].batching_reg.mask,
data);
if (err < 0)
goto out;
}
}
#endif /* CONFIG_IIO_ST_ASM330LHH_EN_TEMPERATURE */
err = st_asm330lhh_update_watermark(sensor, sensor->watermark);
if (err < 0)
goto out;
if (enable && hw->fifo_mode == ST_ASM330LHH_FIFO_BYPASS) {
st_asm330lhh_reset_hwts(hw);
err = st_asm330lhh_set_fifo_mode(hw, ST_ASM330LHH_FIFO_CONT);
} else if (!hw->enable_mask) {
err = st_asm330lhh_set_fifo_mode(hw, ST_ASM330LHH_FIFO_BYPASS);
}
#if defined(CONFIG_IIO_ST_ASM330LHH_ASYNC_HW_TIMESTAMP)
if (hw->fifo_mode != ST_ASM330LHH_FIFO_BYPASS) {
hrtimer_start(&hw->timesync_timer,
ktime_set(0, 0),
HRTIMER_MODE_REL);
}
#endif /* CONFIG_IIO_ST_ASM330LHH_ASYNC_HW_TIMESTAMP */
out:
enable_irq(hw->irq);
return err;
}
static irqreturn_t st_asm330lhh_handler_irq(int irq, void *private)
{
struct st_asm330lhh_hw *hw = (struct st_asm330lhh_hw *)private;
s64 ts = st_asm330lhh_get_time_ns(hw->iio_devs[0]);
hw->delta_ts = ts - hw->ts;
hw->ts = ts;
return IRQ_WAKE_THREAD;
}
static irqreturn_t st_asm330lhh_handler_thread(int irq, void *private)
{
struct st_asm330lhh_hw *hw = (struct st_asm330lhh_hw *)private;
mutex_lock(&hw->fifo_lock);
st_asm330lhh_read_fifo(hw);
clear_bit(ST_ASM330LHH_HW_FLUSH, &hw->state);
mutex_unlock(&hw->fifo_lock);
#ifdef CONFIG_IIO_ST_ASM330LHH_EN_BASIC_FEATURES
return st_asm330lhh_event_handler(hw);
#else /* CONFIG_IIO_ST_ASM330LHH_EN_BASIC_FEATURES */
return IRQ_HANDLED;
#endif /* CONFIG_IIO_ST_ASM330LHH_EN_BASIC_FEATURES */
}
static int st_asm330lhh_fifo_preenable(struct iio_dev *iio_dev)
{
return st_asm330lhh_update_fifo(iio_dev, true);
}
static int st_asm330lhh_fifo_postdisable(struct iio_dev *iio_dev)
{
return st_asm330lhh_update_fifo(iio_dev, false);
}
static const struct iio_buffer_setup_ops st_asm330lhh_fifo_ops = {
.preenable = st_asm330lhh_fifo_preenable,
.postdisable = st_asm330lhh_fifo_postdisable,
};
static int st_asm330lhh_enable_timestamp(struct st_asm330lhh_hw *hw,
bool enable)
{
return st_asm330lhh_write_with_mask(hw,
ST_ASM330LHH_REG_FIFO_CTRL4_ADDR,
ST_ASM330LHH_REG_DEC_TS_MASK,
enable == true ? 1 : 0);
}
static int st_asm330lhh_fifo_init(struct st_asm330lhh_hw *hw)
{
return st_asm330lhh_enable_timestamp(hw, true);
}
int st_asm330lhh_buffers_setup(struct st_asm330lhh_hw *hw)
{
struct iio_buffer *buffer;
unsigned long irq_type;
bool irq_active_low;
int i, err;
irq_type = irqd_get_trigger_type(irq_get_irq_data(hw->irq));
if (irq_type == IRQF_TRIGGER_NONE)
irq_type = IRQF_TRIGGER_HIGH;
switch (irq_type) {
case IRQF_TRIGGER_HIGH:
irq_active_low = false;
break;
case IRQF_TRIGGER_LOW:
irq_active_low = true;
break;
default:
dev_info(hw->dev, "mode %lx unsupported\n", irq_type);
return -EINVAL;
}
err = st_asm330lhh_write_with_mask(hw, ST_ASM330LHH_REG_CTRL3_C_ADDR,
ST_ASM330LHH_REG_H_LACTIVE_MASK,
irq_active_low);
if (err < 0)
return err;
if (device_property_read_bool(hw->dev, "drive-open-drain")) {
err = st_asm330lhh_write_with_mask(hw,
ST_ASM330LHH_REG_CTRL3_C_ADDR,
ST_ASM330LHH_REG_PP_OD_MASK,
1);
if (err < 0)
return err;
irq_type |= IRQF_SHARED;
}
err = devm_request_threaded_irq(hw->dev, hw->irq,
st_asm330lhh_handler_irq,
st_asm330lhh_handler_thread,
irq_type | IRQF_ONESHOT,
"asm330lhh", hw);
if (err) {
dev_err(hw->dev, "failed to request trigger irq %d\n",
hw->irq);
return err;
}
for (i = 0; i < ST_ASM330LHH_ID_EVENT; i++) {
if (!hw->iio_devs[i])
continue;
buffer = devm_iio_kfifo_allocate(hw->dev);
if (!buffer)
return -ENOMEM;
iio_device_attach_buffer(hw->iio_devs[i], buffer);
hw->iio_devs[i]->modes |= INDIO_BUFFER_SOFTWARE;
hw->iio_devs[i]->setup_ops = &st_asm330lhh_fifo_ops;
}
err = st_asm330lhh_hwtimesync_init(hw);
if (err)
return err;
return st_asm330lhh_fifo_init(hw);
}

File diff suppressed because it is too large Load Diff

View File

@ -1,616 +0,0 @@
/**
* STMicroelectronics st_asm330lhh events function sensor driver
*
* Copyright 2020 STMicroelectronics Inc.
*
* Mario Tesi <mario.tesi@st.com>
*
* Licensed under the GPL-2.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/iio/events.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
#include <linux/iio/trigger_consumer.h>
#include <linux/iio/triggered_buffer.h>
#include <linux/iio/trigger.h>
#include <linux/iio/buffer.h>
#include "st_asm330lhh.h"
#ifdef CONFIG_IIO_ST_ASM330LHH_EN_BASIC_FEATURES
#define ST_ASM330LHH_REG_ALL_INT_SRC_ADDR 0x1a
#define ST_ASM330LHH_FF_IA_MASK BIT(0)
#define ST_ASM330LHH_WU_IA_MASK BIT(1)
#define ST_ASM330LHH_D6D_IA_MASK BIT(4)
#define ST_ASM330LHH_SLEEP_CHANGE_MASK BIT(5)
#define ST_ASM330LHH_REG_WAKE_UP_SRC_ADDR 0x1b
#define ST_ASM330LHH_WAKE_UP_EVENT_MASK GENMASK(3, 0)
#define ST_ASM330LHH_REG_D6D_SRC_ADDR 0x1d
#define ST_ASM330LHH_D6D_EVENT_MASK GENMASK(5, 0)
#define ST_ASM330LHH_REG_INT_CFG1_ADDR 0x58
#define ST_ASM330LHH_INTERRUPTS_ENABLE_MASK BIT(7)
#define ST_ASM330LHH_REG_MD1_CFG_ADDR 0x5e
#define ST_ASM330LHH_REG_MD2_CFG_ADDR 0x5f
#define ST_ASM330LHH_INT_6D_MASK BIT(2)
#define ST_ASM330LHH_INT_FF_MASK BIT(4)
#define ST_ASM330LHH_INT_WU_MASK BIT(5)
#define ST_ASM330LHH_INT_SLEEP_CHANGE_MASK BIT(7)
static const unsigned long st_asm330lhh_event_available_scan_masks[] = {
0x1, 0x0
};
static const struct iio_chan_spec st_asm330lhh_wk_channels[] = {
{
.type = IIO_GESTURE,
.scan_index = 0,
.scan_type = {
.sign = 'u',
.realbits = 8,
.storagebits = 8,
},
},
IIO_CHAN_SOFT_TIMESTAMP(1),
};
static const struct iio_chan_spec st_asm330lhh_ff_channels[] = {
ST_ASM330LHH_EVENT_CHANNEL(IIO_GESTURE, thr),
};
static const struct iio_chan_spec st_asm330lhh_sc_channels[] = {
ST_ASM330LHH_EVENT_CHANNEL(IIO_GESTURE, thr),
};
static const struct iio_chan_spec st_asm330lhh_6D_channels[] = {
{
.type = IIO_GESTURE,
.scan_index = 0,
.scan_type = {
.sign = 'u',
.realbits = 8,
.storagebits = 8,
},
},
IIO_CHAN_SOFT_TIMESTAMP(1),
};
static
int st_asm330lhh_event_sensor_set_enable(struct st_asm330lhh_sensor *sensor,
bool enable)
{
int err, eint = !!enable;
struct st_asm330lhh_hw *hw = sensor->hw;
u8 int_reg = hw->int_pin == 1 ? ST_ASM330LHH_REG_MD1_CFG_ADDR :
ST_ASM330LHH_REG_MD2_CFG_ADDR;
switch (sensor->id) {
case ST_ASM330LHH_ID_WK:
err = st_asm330lhh_write_with_mask(hw,
int_reg,
ST_ASM330LHH_INT_WU_MASK,
eint);
if (err < 0)
return err;
break;
case ST_ASM330LHH_ID_FF:
err = st_asm330lhh_write_with_mask(hw,
int_reg,
ST_ASM330LHH_INT_FF_MASK,
eint);
if (err < 0)
return err;
break;
case ST_ASM330LHH_ID_SC:
err = st_asm330lhh_write_with_mask(hw,
int_reg,
ST_ASM330LHH_INT_SLEEP_CHANGE_MASK,
eint);
if (err < 0)
return err;
break;
case ST_ASM330LHH_ID_6D:
err = st_asm330lhh_write_with_mask(hw,
int_reg,
ST_ASM330LHH_INT_6D_MASK,
eint);
if (err < 0)
return err;
break;
default:
err = -EINVAL;
break;
}
if (err >= 0) {
err = st_asm330lhh_write_with_mask(hw,
ST_ASM330LHH_REG_INT_CFG1_ADDR,
ST_ASM330LHH_INTERRUPTS_ENABLE_MASK,
eint);
if (eint == 0)
hw->enable_mask &= ~BIT(sensor->id);
else
hw->enable_mask |= BIT(sensor->id);
}
return err;
}
static int st_asm330lhh_read_event_config(struct iio_dev *iio_dev,
const struct iio_chan_spec *chan,
enum iio_event_type type,
enum iio_event_direction dir)
{
struct st_asm330lhh_sensor *sensor = iio_priv(iio_dev);
struct st_asm330lhh_hw *hw = sensor->hw;
return !!(hw->enable_mask & BIT(sensor->id));
}
static int st_asm330lhh_write_event_config(struct iio_dev *iio_dev,
const struct iio_chan_spec *chan,
enum iio_event_type type,
enum iio_event_direction dir,
int state)
{
struct st_asm330lhh_sensor *sensor = iio_priv(iio_dev);
int err;
mutex_lock(&iio_dev->mlock);
err = st_asm330lhh_event_sensor_set_enable(sensor, state);
mutex_unlock(&iio_dev->mlock);
return err;
}
ssize_t st_asm330lhh_wakeup_threshold_get(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct st_asm330lhh_sensor *sensor = iio_priv(dev_get_drvdata(dev));
return sprintf(buf, "%d\n", sensor->conf[0]);
}
ssize_t st_asm330lhh_wakeup_threshold_set(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
struct iio_dev *iio_dev = dev_get_drvdata(dev);
struct st_asm330lhh_sensor *sensor = iio_priv(iio_dev);
int err, val;
err = iio_device_claim_direct_mode(iio_dev);
if (err)
return err;
err = kstrtoint(buf, 10, &val);
if (err < 0)
goto out;
err = st_asm330lhh_set_wake_up_thershold(sensor->hw, val);
if (err < 0)
goto out;
sensor->conf[0] = val;
out:
iio_device_release_direct_mode(iio_dev);
return err < 0 ? err : size;
}
ssize_t st_asm330lhh_wakeup_duration_get(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct st_asm330lhh_sensor *sensor = iio_priv(dev_get_drvdata(dev));
return sprintf(buf, "%d\n", sensor->conf[1]);
}
ssize_t st_asm330lhh_wakeup_duration_set(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
struct iio_dev *iio_dev = dev_get_drvdata(dev);
struct st_asm330lhh_sensor *sensor = iio_priv(iio_dev);
int err, val;
err = iio_device_claim_direct_mode(iio_dev);
if (err)
return err;
err = kstrtoint(buf, 10, &val);
if (err < 0)
goto out;
err = st_asm330lhh_set_wake_up_duration(sensor->hw, val);
if (err < 0)
goto out;
sensor->conf[1] = val;
out:
iio_device_release_direct_mode(iio_dev);
return err < 0 ? err : size;
}
ssize_t st_asm330lhh_freefall_threshold_get(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct st_asm330lhh_sensor *sensor = iio_priv(dev_get_drvdata(dev));
return sprintf(buf, "%d\n", sensor->conf[2]);
}
ssize_t st_asm330lhh_freefall_threshold_set(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
struct iio_dev *iio_dev = dev_get_drvdata(dev);
struct st_asm330lhh_sensor *sensor = iio_priv(iio_dev);
int err, val;
err = iio_device_claim_direct_mode(iio_dev);
if (err)
return err;
err = kstrtoint(buf, 10, &val);
if (err < 0)
goto out;
err = st_asm330lhh_set_freefall_threshold(sensor->hw, val);
if (err < 0)
goto out;
sensor->conf[2] = val;
out:
iio_device_release_direct_mode(iio_dev);
return err < 0 ? err : size;
}
ssize_t st_asm330lhh_6D_threshold_get(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct st_asm330lhh_sensor *sensor = iio_priv(dev_get_drvdata(dev));
return sprintf(buf, "%d\n", sensor->conf[3]);
}
ssize_t st_asm330lhh_6D_threshold_set(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
struct iio_dev *iio_dev = dev_get_drvdata(dev);
struct st_asm330lhh_sensor *sensor = iio_priv(iio_dev);
int err, val;
err = iio_device_claim_direct_mode(iio_dev);
if (err)
return err;
err = kstrtoint(buf, 10, &val);
if (err < 0)
goto out;
err = st_asm330lhh_set_6D_threshold(sensor->hw, val);
if (err < 0)
goto out;
sensor->conf[3] = val;
out:
iio_device_release_direct_mode(iio_dev);
return err < 0 ? err : size;
}
static IIO_DEVICE_ATTR(wakeup_threshold, 0644,
st_asm330lhh_wakeup_threshold_get,
st_asm330lhh_wakeup_threshold_set, 0);
static IIO_DEVICE_ATTR(wakeup_duration, 0644,
st_asm330lhh_wakeup_duration_get,
st_asm330lhh_wakeup_duration_set, 0);
static IIO_DEVICE_ATTR(freefall_threshold, 0644,
st_asm330lhh_freefall_threshold_get,
st_asm330lhh_freefall_threshold_set, 0);
static IIO_DEVICE_ATTR(sixd_threshold, 0644,
st_asm330lhh_6D_threshold_get,
st_asm330lhh_6D_threshold_set, 0);
static struct attribute *st_asm330lhh_wk_attributes[] = {
&iio_dev_attr_wakeup_threshold.dev_attr.attr,
&iio_dev_attr_wakeup_duration.dev_attr.attr,
NULL,
};
static const struct attribute_group st_asm330lhh_wk_attribute_group = {
.attrs = st_asm330lhh_wk_attributes,
};
static const struct iio_info st_asm330lhh_wk_info = {
.attrs = &st_asm330lhh_wk_attribute_group,
};
static struct attribute *st_asm330lhh_ff_attributes[] = {
&iio_dev_attr_freefall_threshold.dev_attr.attr,
NULL,
};
static const struct attribute_group st_asm330lhh_ff_attribute_group = {
.attrs = st_asm330lhh_ff_attributes,
};
static const struct iio_info st_asm330lhh_ff_info = {
.attrs = &st_asm330lhh_ff_attribute_group,
.read_event_config = st_asm330lhh_read_event_config,
.write_event_config = st_asm330lhh_write_event_config,
};
static struct attribute *st_asm330lhh_sc_attributes[] = {
NULL,
};
static const struct attribute_group st_asm330lhh_sc_attribute_group = {
.attrs = st_asm330lhh_sc_attributes,
};
static const struct iio_info st_asm330lhh_sc_info = {
.attrs = &st_asm330lhh_sc_attribute_group,
.read_event_config = st_asm330lhh_read_event_config,
.write_event_config = st_asm330lhh_write_event_config,
};
static struct attribute *st_asm330lhh_6D_attributes[] = {
&iio_dev_attr_sixd_threshold.dev_attr.attr,
NULL,
};
static const struct attribute_group st_asm330lhh_6D_attribute_group = {
.attrs = st_asm330lhh_6D_attributes,
};
static const struct iio_info st_asm330lhh_6D_info = {
.attrs = &st_asm330lhh_6D_attribute_group,
};
static
struct iio_dev *st_asm330lhh_alloc_event_iiodev(struct st_asm330lhh_hw *hw,
enum st_asm330lhh_sensor_id id)
{
struct st_asm330lhh_sensor *sensor;
struct iio_dev *iio_dev;
iio_dev = devm_iio_device_alloc(hw->dev, sizeof(*sensor));
if (!iio_dev)
return NULL;
iio_dev->modes = INDIO_DIRECT_MODE;
iio_dev->dev.parent = hw->dev;
sensor = iio_priv(iio_dev);
sensor->id = id;
sensor->hw = hw;
sensor->watermark = 1;
iio_dev->available_scan_masks = st_asm330lhh_event_available_scan_masks;
switch (id) {
case ST_ASM330LHH_ID_WK:
iio_dev->channels = st_asm330lhh_wk_channels;
iio_dev->num_channels = ARRAY_SIZE(st_asm330lhh_wk_channels);
iio_dev->name = "asm330lhh_wk";
iio_dev->info = &st_asm330lhh_wk_info;
break;
case ST_ASM330LHH_ID_FF:
iio_dev->channels = st_asm330lhh_ff_channels;
iio_dev->num_channels = ARRAY_SIZE(st_asm330lhh_ff_channels);
iio_dev->name = "asm330lhh_ff";
iio_dev->info = &st_asm330lhh_ff_info;
break;
case ST_ASM330LHH_ID_SC:
iio_dev->channels = st_asm330lhh_sc_channels;
iio_dev->num_channels = ARRAY_SIZE(st_asm330lhh_sc_channels);
iio_dev->name = "asm330lhh_sc";
iio_dev->info = &st_asm330lhh_sc_info;
break;
case ST_ASM330LHH_ID_6D:
iio_dev->channels = st_asm330lhh_6D_channels;
iio_dev->num_channels = ARRAY_SIZE(st_asm330lhh_6D_channels);
iio_dev->name = "asm330lhh_6d";
iio_dev->info = &st_asm330lhh_6D_info;
break;
default:
iio_device_free(iio_dev);
return NULL;
}
return iio_dev;
}
int st_asm330lhh_event_handler(struct st_asm330lhh_hw *hw)
{
struct iio_dev *iio_dev;
u8 status;
s64 event;
int err;
if (hw->enable_mask &
(BIT(ST_ASM330LHH_ID_WK) | BIT(ST_ASM330LHH_ID_FF) |
BIT(ST_ASM330LHH_ID_SC) | BIT(ST_ASM330LHH_ID_6D))) {
err = hw->tf->read(hw->dev,
ST_ASM330LHH_REG_ALL_INT_SRC_ADDR,
sizeof(status), &status);
if (err < 0)
return IRQ_HANDLED;
/* base function sensors */
if (status & ST_ASM330LHH_FF_IA_MASK) {
iio_dev = hw->iio_devs[ST_ASM330LHH_ID_FF];
event = IIO_UNMOD_EVENT_CODE(IIO_GESTURE, -1,
IIO_EV_TYPE_THRESH,
IIO_EV_DIR_RISING);
iio_push_event(iio_dev, event,
st_asm330lhh_get_time_ns(iio_dev));
}
if (status & ST_ASM330LHH_WU_IA_MASK) {
struct st_asm330lhh_sensor *sensor;
iio_dev = hw->iio_devs[ST_ASM330LHH_ID_WK];
sensor = iio_priv(iio_dev);
iio_trigger_poll_chained(sensor->trig);
}
if (status & ST_ASM330LHH_SLEEP_CHANGE_MASK) {
iio_dev = hw->iio_devs[ST_ASM330LHH_ID_SC];
event = IIO_UNMOD_EVENT_CODE(IIO_GESTURE, -1,
IIO_EV_TYPE_THRESH,
IIO_EV_DIR_RISING);
iio_push_event(iio_dev, event,
st_asm330lhh_get_time_ns(iio_dev));
}
if (status & ST_ASM330LHH_D6D_IA_MASK) {
struct st_asm330lhh_sensor *sensor;
iio_dev = hw->iio_devs[ST_ASM330LHH_ID_6D];
sensor = iio_priv(iio_dev);
iio_trigger_poll_chained(sensor->trig);
}
}
return IRQ_HANDLED;
}
static inline int st_asm330lhh_get_6D(struct st_asm330lhh_hw *hw, u8 *out)
{
return st_asm330lhh_read_with_mask(hw, ST_ASM330LHH_REG_D6D_SRC_ADDR,
ST_ASM330LHH_D6D_EVENT_MASK, out);
}
static inline int st_asm330lhh_get_wk(struct st_asm330lhh_hw *hw, u8 *out)
{
return st_asm330lhh_read_with_mask(hw,
ST_ASM330LHH_REG_WAKE_UP_SRC_ADDR,
ST_ASM330LHH_WAKE_UP_EVENT_MASK, out);
}
static irqreturn_t st_asm330lhh_6D_handler_thread(int irq, void *p)
{
struct iio_poll_func *pf = p;
struct iio_dev *iio_dev = pf->indio_dev;
struct st_asm330lhh_sensor *sensor = iio_priv(iio_dev);
u8 buffer[sizeof(u8) + sizeof(s64)];
st_asm330lhh_get_6D(sensor->hw, buffer);
iio_push_to_buffers_with_timestamp(iio_dev, buffer,
st_asm330lhh_get_time_ns(iio_dev));
iio_trigger_notify_done(sensor->trig);
return IRQ_HANDLED;
}
static irqreturn_t st_asm330lhh_wk_handler_thread(int irq, void *p)
{
struct iio_poll_func *pf = p;
struct iio_dev *iio_dev = pf->indio_dev;
struct st_asm330lhh_sensor *sensor = iio_priv(iio_dev);
u8 buffer[sizeof(u8) + sizeof(s64)];
st_asm330lhh_get_wk(sensor->hw, buffer);
iio_push_to_buffers_with_timestamp(iio_dev, buffer,
st_asm330lhh_get_time_ns(iio_dev));
iio_trigger_notify_done(sensor->trig);
return IRQ_HANDLED;
}
static const struct iio_trigger_ops st_asm330lhh_trigger_ops = {
NULL,
};
static int st_asm330lhh_buffer_preenable(struct iio_dev *iio_dev)
{
return st_asm330lhh_event_sensor_set_enable(iio_priv(iio_dev), true);
}
static int st_asm330lhh_buffer_postdisable(struct iio_dev *iio_dev)
{
return st_asm330lhh_event_sensor_set_enable(iio_priv(iio_dev), false);
}
static const struct iio_buffer_setup_ops st_asm330lhh_buffer_ops = {
.preenable = st_asm330lhh_buffer_preenable,
.postdisable = st_asm330lhh_buffer_postdisable,
};
int st_asm330lhh_probe_event(struct st_asm330lhh_hw *hw)
{
struct st_asm330lhh_sensor *sensor;
struct iio_dev *iio_dev;
irqreturn_t (*pthread[ST_ASM330LHH_ID_MAX - ST_ASM330LHH_ID_TRIGGER])(int irq, void *p) = {
[0] = st_asm330lhh_wk_handler_thread,
[1] = st_asm330lhh_6D_handler_thread,
/* add here all other trigger handler funcions */
};
int i, err;
for (i = ST_ASM330LHH_ID_EVENT; i < ST_ASM330LHH_ID_MAX; i++) {
hw->iio_devs[i] = st_asm330lhh_alloc_event_iiodev(hw, i);
if (!hw->iio_devs[i])
return -ENOMEM;
}
/* configure trigger sensors */
for (i = ST_ASM330LHH_ID_TRIGGER; i < ST_ASM330LHH_ID_MAX; i++) {
iio_dev = hw->iio_devs[i];
sensor = iio_priv(iio_dev);
err = devm_iio_triggered_buffer_setup(hw->dev, iio_dev,
NULL, pthread[i - ST_ASM330LHH_ID_TRIGGER],
&st_asm330lhh_buffer_ops);
if (err < 0)
return err;
sensor->trig = devm_iio_trigger_alloc(hw->dev, "%s-trigger",
iio_dev->name);
if (!sensor->trig)
return -ENOMEM;
iio_trigger_set_drvdata(sensor->trig, iio_dev);
sensor->trig->ops = &st_asm330lhh_trigger_ops;
sensor->trig->dev.parent = hw->dev;
iio_dev->trig = iio_trigger_get(sensor->trig);
err = devm_iio_trigger_register(hw->dev, sensor->trig);
if (err)
return err;
}
for (i = ST_ASM330LHH_ID_EVENT; i < ST_ASM330LHH_ID_MAX; i++) {
if (!hw->iio_devs[i])
continue;
err = devm_iio_device_register(hw->dev, hw->iio_devs[i]);
if (err)
return err;
}
return 0;
}
#endif /* CONFIG_IIO_ST_ASM330LHH_EN_BASIC_FEATURES */

View File

@ -1,111 +0,0 @@
/*
* STMicroelectronics st_asm330lhh spi driver
*
* Copyright 2019 STMicroelectronics Inc.
*
* Lorenzo Bianconi <lorenzo.bianconi@st.com>
* Mario Tesi <mario.tesi@st.com>
*
* Licensed under the GPL-2.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/spi/spi.h>
#include <linux/slab.h>
#include <linux/of.h>
#include "st_asm330lhh.h"
#define SENSORS_SPI_READ BIT(7)
static int st_asm330lhh_spi_read(struct device *dev, u8 addr, int len,
u8 *data)
{
struct spi_device *spi = to_spi_device(dev);
struct st_asm330lhh_hw *hw = spi_get_drvdata(spi);
int err;
struct spi_transfer xfers[] = {
{
.tx_buf = hw->tb.tx_buf,
.bits_per_word = 8,
.len = 1,
},
{
.rx_buf = hw->tb.rx_buf,
.bits_per_word = 8,
.len = len,
}
};
hw->tb.tx_buf[0] = addr | SENSORS_SPI_READ;
err = spi_sync_transfer(spi, xfers, ARRAY_SIZE(xfers));
if (err < 0)
return err;
memcpy(data, hw->tb.rx_buf, len * sizeof(u8));
return len;
}
static int st_asm330lhh_spi_write(struct device *dev, u8 addr, int len,
const u8 *data)
{
struct st_asm330lhh_hw *hw;
struct spi_device *spi;
if (len >= ST_ASM330LHH_TX_MAX_LENGTH)
return -ENOMEM;
spi = to_spi_device(dev);
hw = spi_get_drvdata(spi);
hw->tb.tx_buf[0] = addr;
memcpy(&hw->tb.tx_buf[1], data, len);
return spi_write(spi, hw->tb.tx_buf, len + 1);
}
static const struct st_asm330lhh_transfer_function st_asm330lhh_transfer_fn = {
.read = st_asm330lhh_spi_read,
.write = st_asm330lhh_spi_write,
};
static int st_asm330lhh_spi_probe(struct spi_device *spi)
{
return st_asm330lhh_probe(&spi->dev, spi->irq,
&st_asm330lhh_transfer_fn);
}
static const struct of_device_id st_asm330lhh_spi_of_match[] = {
{
.compatible = "st,asm330lhh",
},
{},
};
MODULE_DEVICE_TABLE(of, st_asm330lhh_spi_of_match);
static const struct spi_device_id st_asm330lhh_spi_id_table[] = {
{ ST_ASM330LHH_DEV_NAME },
{},
};
MODULE_DEVICE_TABLE(spi, st_asm330lhh_spi_id_table);
static struct spi_driver st_asm330lhh_driver = {
.driver = {
.name = "st_asm330lhh_spi",
.pm = &st_asm330lhh_pm_ops,
.of_match_table = st_asm330lhh_spi_of_match,
},
.probe = st_asm330lhh_spi_probe,
.id_table = st_asm330lhh_spi_id_table,
};
module_spi_driver(st_asm330lhh_driver);
MODULE_AUTHOR("Lorenzo Bianconi <lorenzo.bianconi@st.com>");
MODULE_AUTHOR("Mario Tesi <mario.tesi@st.com>");
MODULE_DESCRIPTION("STMicroelectronics st_asm330lhh spi driver");
MODULE_LICENSE("GPL v2");
MODULE_VERSION(ST_ASM330LHH_DRV_VERSION);

View File

@ -1,47 +1,47 @@
config IIO_ST_ASM330LHH
tristate "STMicroelectronics ASM330LHH sensor"
config IIO_ST_ASM330LHHX
tristate "STMicroelectronics ASM330LHHX sensor"
depends on (I2C || SPI)
select IIO_BUFFER
select IIO_KFIFO_BUF
select IIO_TRIGGERED_BUFFER
select IIO_ST_ASM330LHH_I2C if (I2C)
select IIO_ST_ASM330LHH_SPI if (SPI_MASTER)
select IIO_ST_ASM330LHHX_I2C if (I2C)
select IIO_ST_ASM330LHHX_SPI if (SPI_MASTER)
help
Say yes here to build support for STMicroelectronics ASM330LHH imu
sensor.
Say yes here to build support for STMicroelectronics ASM330LHH/ASM330LHHX imu
sensors.
To compile this driver as a module, choose M here: the module
will be called st_asm330lhh.
will be called st_asm330lhhx.
config IIO_ST_ASM330LHH_I2C
config IIO_ST_ASM330LHHX_I2C
tristate
depends on IIO_ST_ASM330LHH
depends on IIO_ST_ASM330LHHX
config IIO_ST_ASM330LHH_SPI
config IIO_ST_ASM330LHHX_SPI
tristate
depends on IIO_ST_ASM330LHH
depends on IIO_ST_ASM330LHHX
config IIO_ST_ASM330LHH_MAY_WAKEUP
config IIO_ST_ASM330LHHX_MAY_WAKEUP
bool "Enable wake-up irq"
depends on IIO_ST_ASM330LHH
depends on IIO_ST_ASM330LHHX
help
Enable wake-up irq on interrupt line
config IIO_ST_ASM330LHH_EN_TEMPERATURE
config IIO_ST_ASM330LHHX_EN_TEMPERATURE
bool "Enable internal temperature sensor"
depends on IIO_ST_ASM330LHH
depends on IIO_ST_ASM330LHHX
help
Enable internal temperature sensor channel
config IIO_ST_ASM330LHH_EN_BASIC_FEATURES
config IIO_ST_ASM330LHHX_EN_BASIC_FEATURES
bool "Enable internal basic features event detection"
depends on IIO_ST_ASM330LHH
depends on IIO_ST_ASM330LHHX
help
Enable internal basic features event detection sensor
config IIO_ST_ASM330LHH_ASYNC_HW_TIMESTAMP
config IIO_ST_ASM330LHHX_ASYNC_HW_TIMESTAMP
bool "Enable async hw timestamp read"
depends on IIO_ST_ASM330LHH
depends on IIO_ST_ASM330LHHX
help
Enable async task that sends over hw timestamp events.

View File

@ -0,0 +1,8 @@
st_asm330lhhx-y := st_asm330lhhx_core.o st_asm330lhhx_buffer.o \
st_asm330lhhx_events.o
st_asm330lhhx-$(CONFIG_IIO_ST_ASM330LHHX_ASYNC_HW_TIMESTAMP) += st_asm330lhhx_hwtimestamp.o
obj-$(CONFIG_IIO_ST_ASM330LHHX) += st_asm330lhhx.o
obj-$(CONFIG_IIO_ST_ASM330LHHX_I2C) += st_asm330lhhx_i2c.o
obj-$(CONFIG_IIO_ST_ASM330LHHX_SPI) += st_asm330lhhx_spi.o
obj-$(CONFIG_IIO_ST_ASM330LHHX_ASYNC_HW_TIMESTAMP) += st_asm330lhhx_hwtimestamp.o

View File

@ -0,0 +1,579 @@
/*
* STMicroelectronics st_asm330lhhx sensor driver
*
* Copyright 2019 STMicroelectronics Inc.
*
* Lorenzo Bianconi <lorenzo.bianconi@st.com>
*
* Licensed under the GPL-2.
*/
#ifndef ST_ASM330LHHX_H
#define ST_ASM330LHHX_H
#include <linux/device.h>
#include <linux/iio/iio.h>
#include <linux/delay.h>
#include <linux/workqueue.h>
#include <linux/hrtimer.h>
#include <linux/spinlock.h>
#define ST_ASM330LHHX_DRV_VERSION "1.1"
#define ST_ASM330LHHX_DEBUG_DISCHARGE
#define ST_ASM330LHHX_MAX_ODR 833
#define ST_ASM330LHHX_ODR_LIST_SIZE 8
#define ST_ASM330LHHX_ODR_EXPAND(odr, uodr) ((odr * 1000000) + uodr)
#define ST_ASM330LHH_DEV_NAME "asm330lhh"
#define ST_ASM330LHHX_DEV_NAME "asm330lhhx"
#define ST_ASM330LHHX_DEFAULT_XL_FS_INDEX 2
#define ST_ASM330LHHX_DEFAULT_XL_ODR_INDEX 1
#define ST_ASM330LHHX_DEFAULT_G_FS_INDEX 2
#define ST_ASM330LHHX_DEFAULT_G_ODR_INDEX 1
#define ST_ASM330LHHX_DEFAULT_T_FS_INDEX 0
#define ST_ASM330LHHX_DEFAULT_T_ODR_INDEX 1
#define ST_ASM330LHHX_REG_FIFO_CTRL1_ADDR 0x07
#define ST_ASM330LHHX_REG_FIFO_CTRL2_ADDR 0x08
#define ST_ASM330LHHX_REG_FIFO_WTM_MASK GENMASK(8, 0)
#define ST_ASM330LHHX_REG_FIFO_WTM8_MASK BIT(0)
#define ST_ASM330LHHX_REG_FIFO_STATUS_DIFF GENMASK(9, 0)
#define ST_ASM330LHHX_REG_FIFO_CTRL3_ADDR 0x09
#define ST_ASM330LHHX_REG_BDR_XL_MASK GENMASK(3, 0)
#define ST_ASM330LHHX_REG_BDR_GY_MASK GENMASK(7, 4)
#define ST_ASM330LHHX_REG_FIFO_CTRL4_ADDR 0x0a
#define ST_ASM330LHHX_REG_FIFO_MODE_MASK GENMASK(2, 0)
#define ST_ASM330LHHX_REG_DEC_TS_MASK GENMASK(7, 6)
#define ST_ASM330LHHX_REG_ODR_T_BATCH_MASK GENMASK(5, 4)
#define ST_ASM330LHHX_REG_INT1_CTRL_ADDR 0x0d
#define ST_ASM330LHHX_REG_INT2_CTRL_ADDR 0x0e
#define ST_ASM330LHHX_REG_INT_FIFO_TH_MASK BIT(3)
#define ST_ASM330LHHX_REG_WHOAMI_ADDR 0x0f
#define ST_ASM330LHHX_WHOAMI_VAL 0x6b
#define ST_ASM330LHHX_CTRL1_XL_ADDR 0x10
#define ST_ASM330LHHX_CTRL2_G_ADDR 0x11
#define ST_ASM330LHHX_REG_CTRL3_C_ADDR 0x12
#define ST_ASM330LHHX_REG_SW_RESET_MASK BIT(0)
#define ST_ASM330LHHX_REG_PP_OD_MASK BIT(4)
#define ST_ASM330LHHX_REG_H_LACTIVE_MASK BIT(5)
#define ST_ASM330LHHX_REG_BDU_MASK BIT(6)
#define ST_ASM330LHHX_REG_BOOT_MASK BIT(7)
#define ST_ASM330LHHX_REG_CTRL4_C_ADDR 0x13
#define ST_ASM330LHHX_REG_DRDY_MASK BIT(3)
#define ST_ASM330LHHX_REG_CTRL5_C_ADDR 0x14
#define ST_ASM330LHHX_REG_ROUNDING_MASK GENMASK(6, 5)
#define ST_ASM330LHHX_REG_ST_G_MASK GENMASK(3, 2)
#define ST_ASM330LHHX_REG_ST_XL_MASK GENMASK(1, 0)
#define ST_ASM330LHHX_SELFTEST_ACCEL_MIN 737
#define ST_ASM330LHHX_SELFTEST_ACCEL_MAX 13934
#define ST_ASM330LHHX_SELFTEST_GYRO_MIN 2142
#define ST_ASM330LHHX_SELFTEST_GYRO_MAX 10000
#define ST_ASM330LHHX_SELF_TEST_DISABLED_VAL 0
#define ST_ASM330LHHX_SELF_TEST_POS_SIGN_VAL 1
#define ST_ASM330LHHX_SELF_TEST_NEG_ACCEL_SIGN_VAL 2
#define ST_ASM330LHHX_SELF_TEST_NEG_GYRO_SIGN_VAL 3
#define ST_ASM330LHHX_REG_CTRL9_XL_ADDR 0x18
#define ST_ASM330LHHX_REG_DEVICE_CONF_MASK BIT(1)
#define ST_ASM330LHHX_REG_CTRL10_C_ADDR 0x19
#define ST_ASM330LHHX_REG_TIMESTAMP_EN_MASK BIT(5)
#define ST_ASM330LHHX_REG_STATUS_ADDR 0x1e
#define ST_ASM330LHHX_REG_STATUS_XLDA BIT(0)
#define ST_ASM330LHHX_REG_STATUS_GDA BIT(1)
#define ST_ASM330LHHX_REG_STATUS_TDA BIT(2)
#define ST_ASM330LHHX_REG_OUT_TEMP_L_ADDR 0x20
#define ST_ASM330LHHX_REG_OUTX_L_A_ADDR 0x28
#define ST_ASM330LHHX_REG_OUTY_L_A_ADDR 0x2a
#define ST_ASM330LHHX_REG_OUTZ_L_A_ADDR 0x2c
#define ST_ASM330LHHX_REG_OUTX_L_G_ADDR 0x22
#define ST_ASM330LHHX_REG_OUTY_L_G_ADDR 0x24
#define ST_ASM330LHHX_REG_OUTZ_L_G_ADDR 0x26
#define ST_ASM330LHHX_REG_TIMESTAMP0_ADDR 0x40
#define ST_ASM330LHHX_REG_TAP_CFG0_ADDR 0x56
#define ST_ASM330LHHX_REG_LIR_MASK BIT(0)
#define ST_ASM330LHHX_REG_THS_6D_ADDR 0x59
#define ST_ASM330LHHX_SIXD_THS_MASK GENMASK(6, 5)
#define ST_ASM330LHHX_REG_WAKE_UP_THS_ADDR 0x5b
#define ST_ASM330LHHX_WAKE_UP_THS_MASK GENMASK(5, 0)
#define ST_ASM330LHHX_REG_WAKE_UP_DUR_ADDR 0x5c
#define ST_ASM330LHHX_WAKE_UP_DUR_MASK GENMASK(6, 5)
#define ST_ASM330LHHX_REG_FREE_FALL_ADDR 0x5d
#define ST_ASM330LHHX_FF_THS_MASK GENMASK(2, 0)
#define ST_ASM330LHHX_INTERNAL_FREQ_FINE 0x63
/* Timestamp Tick 25us/LSB */
#define ST_ASM330LHHX_TS_DELTA_NS 25000ULL
/* Temperature in uC */
#define ST_ASM330LHHX_TEMP_GAIN 256
#define ST_ASM330LHHX_TEMP_FS_GAIN 1000000 / ST_ASM330LHHX_TEMP_GAIN
#define ST_ASM330LHHX_TEMP_OFFSET 6400
/* FIFO simple size and depth */
#define ST_ASM330LHHX_SAMPLE_SIZE 6
#define ST_ASM330LHHX_TS_SAMPLE_SIZE 4
#define ST_ASM330LHHX_TAG_SIZE 1
#define ST_ASM330LHHX_FIFO_SAMPLE_SIZE (ST_ASM330LHHX_SAMPLE_SIZE + \
ST_ASM330LHHX_TAG_SIZE)
#define ST_ASM330LHHX_MAX_FIFO_DEPTH 416
#define ST_ASM330LHHX_DEFAULT_KTIME (200000000)
#define ST_ASM330LHHX_FAST_KTIME (5000000)
#define ST_ASM330LHHX_DATA_CHANNEL(chan_type, addr, mod, ch2, scan_idx, \
rb, sb, sg) \
{ \
.type = chan_type, \
.address = addr, \
.modified = mod, \
.channel2 = ch2, \
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
BIT(IIO_CHAN_INFO_SCALE), \
.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \
.scan_index = scan_idx, \
.scan_type = { \
.sign = sg, \
.realbits = rb, \
.storagebits = sb, \
.endianness = IIO_LE, \
}, \
.ext_info = st_asm330lhhx_ext_info, \
}
static const struct iio_event_spec st_asm330lhhx_flush_event = {
.type = IIO_EV_TYPE_FIFO_FLUSH,
.dir = IIO_EV_DIR_EITHER,
};
static const struct iio_event_spec st_asm330lhhx_thr_event = {
.type = IIO_EV_TYPE_THRESH,
.dir = IIO_EV_DIR_RISING,
.mask_separate = BIT(IIO_EV_INFO_ENABLE),
};
#define ST_ASM330LHHX_EVENT_CHANNEL(ctype, etype) \
{ \
.type = ctype, \
.modified = 0, \
.scan_index = -1, \
.indexed = -1, \
.event_spec = &st_asm330lhhx_##etype##_event, \
.num_event_specs = 1, \
}
#define ST_ASM330LHHX_RX_MAX_LENGTH 64
#define ST_ASM330LHHX_TX_MAX_LENGTH 16
struct st_asm330lhhx_transfer_buffer {
u8 rx_buf[ST_ASM330LHHX_RX_MAX_LENGTH];
u8 tx_buf[ST_ASM330LHHX_TX_MAX_LENGTH] ____cacheline_aligned;
};
struct st_asm330lhhx_transfer_function {
int (*read)(struct device *dev, u8 addr, int len, u8 *data);
int (*write)(struct device *dev, u8 addr, int len, const u8 *data);
};
/**
* struct st_asm330lhhx_reg - Generic sensor register description (addr + mask)
* @addr: Address of register.
* @mask: Bitmask register for proper usage.
*/
struct st_asm330lhhx_reg {
u8 addr;
u8 mask;
};
enum st_asm330lhhx_suspend_resume_register {
ST_ASM330LHHX_CTRL1_XL_REG = 0,
ST_ASM330LHHX_CTRL2_G_REG,
ST_ASM330LHHX_REG_CTRL3_C_REG,
ST_ASM330LHHX_REG_CTRL4_C_REG,
ST_ASM330LHHX_REG_CTRL5_C_REG,
ST_ASM330LHHX_REG_CTRL10_C_REG,
ST_ASM330LHHX_REG_TAP_CFG0_REG,
ST_ASM330LHHX_REG_INT1_CTRL_REG,
ST_ASM330LHHX_REG_INT2_CTRL_REG,
ST_ASM330LHHX_REG_FIFO_CTRL1_REG,
ST_ASM330LHHX_REG_FIFO_CTRL2_REG,
ST_ASM330LHHX_REG_FIFO_CTRL3_REG,
ST_ASM330LHHX_REG_FIFO_CTRL4_REG,
ST_ASM330LHHX_SUSPEND_RESUME_REGS,
};
struct st_asm330lhhx_suspend_resume_entry {
u8 addr;
u8 val;
u8 mask;
};
/**
* struct st_asm330lhhx_odr - Single ODR entry
* @hz: Most significant part of the sensor ODR (Hz).
* @uhz: Less significant part of the sensor ODR (micro Hz).
* @val: ODR register value.
* @batch_val: Batching ODR register value.
*/
struct st_asm330lhhx_odr {
u16 hz;
u32 uhz;
u8 val;
u8 batch_val;
};
/**
* struct st_asm330lhhx_odr_table_entry - Sensor ODR table
* @size: Size of ODR table.
* @reg: ODR register.
* @batching_reg: ODR register for batching on fifo.
* @odr_avl: Array of supported ODR value.
*/
struct st_asm330lhhx_odr_table_entry {
u8 size;
struct st_asm330lhhx_reg reg;
struct st_asm330lhhx_reg batching_reg;
struct st_asm330lhhx_odr odr_avl[ST_ASM330LHHX_ODR_LIST_SIZE];
};
/**
* struct st_asm330lhhx_fs - Full Scale sensor table entry
* @reg: Register description for FS settings.
* @gain: Sensor sensitivity (mdps/LSB, mg/LSB and uC/LSB).
* @val: FS register value.
*/
struct st_asm330lhhx_fs {
struct st_asm330lhhx_reg reg;
u32 gain;
u8 val;
};
#define ST_ASM330LHHX_FS_LIST_SIZE 5
#define ST_ASM330LHHX_FS_ACC_LIST_SIZE 4
#define ST_ASM330LHHX_FS_GYRO_LIST_SIZE 5
#ifdef CONFIG_IIO_ST_ASM330LHHX_EN_TEMPERATURE
#define ST_ASM330LHHX_FS_TEMP_LIST_SIZE 1
#endif /* CONFIG_IIO_ST_ASM330LHHX_EN_TEMPERATURE */
/**
* struct st_asm330lhhx_fs_table_entry - Full Scale sensor table
* @size: Full Scale sensor table size.
* @fs_avl: Full Scale list entries.
*/
struct st_asm330lhhx_fs_table_entry {
u8 size;
struct st_asm330lhhx_fs fs_avl[ST_ASM330LHHX_FS_LIST_SIZE];
};
#define ST_ASM330LHHX_ACC_FS_2G_GAIN IIO_G_TO_M_S_2(61000)
#define ST_ASM330LHHX_ACC_FS_4G_GAIN IIO_G_TO_M_S_2(122000)
#define ST_ASM330LHHX_ACC_FS_8G_GAIN IIO_G_TO_M_S_2(244000)
#define ST_ASM330LHHX_ACC_FS_16G_GAIN IIO_G_TO_M_S_2(488000)
#define ST_ASM330LHHX_GYRO_FS_250_GAIN IIO_DEGREE_TO_RAD(8750000)
#define ST_ASM330LHHX_GYRO_FS_500_GAIN IIO_DEGREE_TO_RAD(17500000)
#define ST_ASM330LHHX_GYRO_FS_1000_GAIN IIO_DEGREE_TO_RAD(35000000)
#define ST_ASM330LHHX_GYRO_FS_2000_GAIN IIO_DEGREE_TO_RAD(70000000)
#define ST_ASM330LHHX_GYRO_FS_4000_GAIN IIO_DEGREE_TO_RAD(140000000)
enum st_asm330lhhx_sensor_id {
ST_ASM330LHHX_ID_GYRO = 0,
ST_ASM330LHHX_ID_ACC,
#ifdef CONFIG_IIO_ST_ASM330LHHX_EN_TEMPERATURE
ST_ASM330LHHX_ID_TEMP,
#endif /* CONFIG_IIO_ST_ASM330LHHX_EN_TEMPERATURE */
ST_ASM330LHHX_ID_EVENT,
ST_ASM330LHHX_ID_FF = ST_ASM330LHHX_ID_EVENT,
ST_ASM330LHHX_ID_SC,
ST_ASM330LHHX_ID_TRIGGER,
ST_ASM330LHHX_ID_WK = ST_ASM330LHHX_ID_TRIGGER,
ST_ASM330LHHX_ID_6D,
ST_ASM330LHHX_ID_MAX,
};
enum st_asm330lhhx_fifo_mode {
ST_ASM330LHHX_FIFO_BYPASS = 0x0,
ST_ASM330LHHX_FIFO_CONT = 0x6,
};
enum {
ST_ASM330LHHX_HW_FLUSH,
ST_ASM330LHHX_HW_OPERATIONAL,
};
/**
* struct st_asm330lhhx_sensor - ST IMU sensor instance
* @id: Sensor identifier.
* @hw: Pointer to instance of struct st_asm330lhhx_hw.
* @gain: Configured sensor sensitivity.
* @offset: Sensor data offset.
* @conf: Used in case of sensor event to manage configuration.
* @odr: Output data rate of the sensor [Hz].
* @uodr: Output data rate of the sensor [uHz].
* @max_watermark: Max supported watermark level.
* @watermark: Sensor watermark level.
* @last_fifo_timestamp: Store last sample timestamp in FIFO, used by flush
* @selftest_status: Last status of self test output
* @min_st, @max_st: Min/Max acc/gyro data values during self test procedure
*/
struct st_asm330lhhx_sensor {
enum st_asm330lhhx_sensor_id id;
struct st_asm330lhhx_hw *hw;
struct iio_trigger *trig;
union {
/* sensor with odrs, gain and offset */
struct {
u32 gain;
u32 offset;
int odr;
int uodr;
#ifdef ST_ASM330LHHX_DEBUG_DISCHARGE
u32 discharged_samples;
#endif /* ST_ASM330LHHX_DEBUG_DISCHARGE */
u16 max_watermark;
u16 watermark;
s64 last_fifo_timestamp;
/* self test */
int8_t selftest_status;
int min_st;
int max_st;
};
/* sensor specific data configuration */
struct {
u32 conf[6];
};
};
};
/**
* struct st_asm330lhhx_hw - ST IMU MEMS hw instance
* @dev: Pointer to instance of struct device (I2C or SPI).
* @irq: Device interrupt line (I2C or SPI).
* @int_pin: Save interrupt pin used by sensor.
* @lock: Mutex to protect read and write operations.
* @fifo_lock: Mutex to prevent concurrent access to the hw FIFO.
* @page_lock: Mutex to prevent concurrent memory page configuration.
* @fifo_mode: FIFO operating mode supported by the device.
* @state: hw operational state.
* @enable_mask: Enabled sensor bitmask.
* @hw_timestamp_global: hw timestamp value always monotonic where the most
* significant 8byte are incremented at every disable/enable.
* @timesync_workqueue: runs the async task in private workqueue.
* @timesync_work: actual work to be done in the async task workqueue.
* @timesync_timer: hrtimer used to schedule period read for the async task.
* @hwtimestamp_lock: spinlock for the 64bit timestamp value.
* @timesync_ktime: interval value used by the hrtimer.
* @timestamp_c: counter used for counting number of timesync updates.
* @ts_offset: Hw timestamp offset.
* @ts_delta_ns: Calibrate delta time tick.
* @hw_ts: Latest hw timestamp from the sensor.
* @val_ts_old: Hold hw timestamp for timer rollover.
* @hw_ts_high: Save MSB hw timestamp.
* @tsample: Timestamp for each sensor sample.
* @delta_ts: Delta time between two consecutive interrupts.
* @ts: Latest timestamp from irq handler.
* @odr_table_entry: Sensors ODR table.
* @iio_devs: Pointers to acc/gyro iio_dev instances.
* @tf: Transfer function structure used by I/O operations.
* @tb: Transfer buffers used by SPI I/O operations.
* @orientation: sensor chip orientation relative to main hardware.
*/
struct st_asm330lhhx_hw {
struct device *dev;
int irq;
int int_pin;
struct mutex lock;
struct mutex fifo_lock;
struct mutex page_lock;
enum st_asm330lhhx_fifo_mode fifo_mode;
unsigned long state;
u32 enable_mask;
s64 hw_timestamp_global;
#if defined (CONFIG_IIO_ST_ASM330LHHX_ASYNC_HW_TIMESTAMP)
struct workqueue_struct *timesync_workqueue;
struct work_struct timesync_work;
struct hrtimer timesync_timer;
spinlock_t hwtimestamp_lock;
ktime_t timesync_ktime;
int timesync_c;
#endif /* CONFIG_IIO_ST_ASM330LHHX_ASYNC_HW_TIMESTAMP */
s64 ts_offset;
u64 ts_delta_ns;
s64 hw_ts;
u32 val_ts_old;
u32 hw_ts_high;
s64 tsample;
s64 delta_ts;
s64 ts;
const struct st_asm330lhhx_odr_table_entry *odr_table_entry;
struct iio_dev *iio_devs[ST_ASM330LHHX_ID_MAX];
struct regulator *vdd_supply;
struct regulator *vddio_supply;
const struct st_asm330lhhx_transfer_function *tf;
struct st_asm330lhhx_transfer_buffer tb;
struct iio_mount_matrix orientation;
};
/**
* struct st_asm330lhhx_ff_th - Free Fall threshold table
* @mg: Threshold in mg.
* @val: Register value.
*/
struct st_asm330lhhx_ff_th {
u32 mg;
u8 val;
};
/**
* struct st_asm330lhhx_6D_th - 6D threshold table
* @deg: Threshold in degrees.
* @val: Register value.
*/
struct st_asm330lhhx_6D_th {
u8 deg;
u8 val;
};
extern const struct dev_pm_ops st_asm330lhhx_pm_ops;
static inline int st_asm330lhhx_read_atomic(struct st_asm330lhhx_hw *hw,
u8 addr, int len, u8 *data)
{
int err;
mutex_lock(&hw->page_lock);
err = hw->tf->read(hw->dev, addr, len, data);
mutex_unlock(&hw->page_lock);
return err;
}
static inline int
st_asm330lhhx_write_atomic(struct st_asm330lhhx_hw *hw,
u8 addr, int len, u8 *data)
{
int err;
mutex_lock(&hw->page_lock);
err = hw->tf->write(hw->dev, addr, len, data);
mutex_unlock(&hw->page_lock);
return err;
}
int __st_asm330lhhx_write_with_mask(struct st_asm330lhhx_hw *hw, u8 addr,
u8 mask, u8 val);
static inline int
st_asm330lhhx_write_with_mask(struct st_asm330lhhx_hw *hw, u8 addr,
u8 mask, u8 val)
{
int err;
mutex_lock(&hw->page_lock);
err = __st_asm330lhhx_write_with_mask(hw, addr, mask, val);
mutex_unlock(&hw->page_lock);
return err;
}
static inline bool
st_asm330lhhx_is_fifo_enabled(struct st_asm330lhhx_hw *hw)
{
return hw->enable_mask & (BIT(ST_ASM330LHHX_ID_GYRO) |
BIT(ST_ASM330LHHX_ID_ACC));
}
static inline s64 st_asm330lhhx_get_time_ns(struct iio_dev *iio_dev)
{
return iio_get_time_ns(iio_dev);
}
int st_asm330lhhx_probe(struct device *dev, int irq,
const struct st_asm330lhhx_transfer_function *tf_ops);
int st_asm330lhhx_sensor_set_enable(struct st_asm330lhhx_sensor *sensor,
bool enable);
int st_asm330lhhx_buffers_setup(struct st_asm330lhhx_hw *hw);
int st_asm330lhhx_get_batch_val(struct st_asm330lhhx_sensor *sensor,
int odr, int uodr, u8 *val);
int st_asm330lhhx_update_watermark(struct st_asm330lhhx_sensor *sensor,
u16 watermark);
ssize_t st_asm330lhhx_flush_fifo(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size);
ssize_t st_asm330lhhx_get_max_watermark(struct device *dev,
struct device_attribute *attr,
char *buf);
ssize_t st_asm330lhhx_get_watermark(struct device *dev,
struct device_attribute *attr,
char *buf);
ssize_t st_asm330lhhx_set_watermark(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size);
int st_asm330lhhx_suspend_fifo(struct st_asm330lhhx_hw *hw);
int st_asm330lhhx_set_fifo_mode(struct st_asm330lhhx_hw *hw,
enum st_asm330lhhx_fifo_mode fifo_mode);
int __st_asm330lhhx_set_sensor_batching_odr(struct st_asm330lhhx_sensor *sensor,
bool enable);
int st_asm330lhhx_update_batching(struct iio_dev *iio_dev, bool enable);
int st_asm330lhhx_reset_hwts(struct st_asm330lhhx_hw *hw);
#ifdef CONFIG_IIO_ST_ASM330LHHX_EN_BASIC_FEATURES
int st_asm330lhhx_event_handler(struct st_asm330lhhx_hw *hw);
int st_asm330lhhx_probe_event(struct st_asm330lhhx_hw *hw);
int st_asm330lhhx_set_wake_up_thershold(struct st_asm330lhhx_hw *hw,
int th_ug);
int st_asm330lhhx_set_wake_up_duration(struct st_asm330lhhx_hw *hw,
int dur_ms);
int st_asm330lhhx_set_freefall_threshold(struct st_asm330lhhx_hw *hw,
int th_mg);
int st_asm330lhhx_set_6D_threshold(struct st_asm330lhhx_hw *hw,
int deg);
int st_asm330lhhx_read_with_mask(struct st_asm330lhhx_hw *hw, u8 addr,
u8 mask, u8 *val);
#endif /* CONFIG_IIO_ST_ASM330LHHX_EN_BASIC_FEATURES */
#if defined (CONFIG_IIO_ST_ASM330LHHX_ASYNC_HW_TIMESTAMP)
int st_asm330lhhx_hwtimesync_init(struct st_asm330lhhx_hw *hw);
#else /* CONFIG_IIO_ST_ASM330LHHX_ASYNC_HW_TIMESTAMP */
static inline int
st_asm330lhhx_hwtimesync_init(struct st_asm330lhhx_hw *hw)
{
return 0;
}
#endif /* CONFIG_IIO_ST_ASM330LHHX_ASYNC_HW_TIMESTAMP */
#endif /* ST_ASM330LHHX_H */

View File

@ -0,0 +1,676 @@
/*
* STMicroelectronics st_asm330lhhx FIFO buffer library driver
*
* Copyright 2019 STMicroelectronics Inc.
*
* Lorenzo Bianconi <lorenzo.bianconi@st.com>
* Mario Tesi <mario.tesi@st.com>
*
* Licensed under the GPL-2.
*/
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/iio/iio.h>
#include <linux/iio/kfifo_buf.h>
#include <linux/iio/events.h>
#include <linux/iio/buffer.h>
#include <asm/unaligned.h>
#include <linux/iio/buffer.h>
#include "st_asm330lhhx.h"
#define ST_ASM330LHHX_REG_FIFO_STATUS1_ADDR 0x3a
#define ST_ASM330LHHX_REG_TIMESTAMP2_ADDR 0x42
#define ST_ASM330LHHX_REG_FIFO_DATA_OUT_TAG_ADDR 0x78
#define ST_ASM330LHHX_SAMPLE_DISCHARD 0x7ffd
/* Timestamp convergence filter parameter */
#define ST_ASM330LHHX_EWMA_LEVEL 120
#define ST_ASM330LHHX_EWMA_DIV 128
#define ST_ASM330LHHX_TIMESTAMP_RESET_VALUE 0xaa
enum {
ST_ASM330LHHX_GYRO_TAG = 0x01,
ST_ASM330LHHX_ACC_TAG = 0x02,
#ifdef CONFIG_IIO_ST_ASM330LHHX_EN_TEMPERATURE
ST_ASM330LHHX_TEMP_TAG = 0x03,
#endif /* CONFIG_IIO_ST_ASM330LHHX_EN_TEMPERATURE */
ST_ASM330LHHX_TS_TAG = 0x04,
};
/* Default timeout before to re-enable gyro */
int delay_gyro = 10;
module_param(delay_gyro, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
MODULE_PARM_DESC(delay_gyro, "Delay for Gyro arming");
static bool delayed_enable_gyro;
static inline s64 st_asm330lhhx_ewma(s64 old, s64 new, int weight)
{
s64 diff, incr;
diff = new - old;
incr = div_s64((ST_ASM330LHHX_EWMA_DIV - weight) * diff,
ST_ASM330LHHX_EWMA_DIV);
return old + incr;
}
inline int st_asm330lhhx_reset_hwts(struct st_asm330lhhx_hw *hw)
{
#if defined(CONFIG_IIO_ST_ASM330LHHX_ASYNC_HW_TIMESTAMP)
struct st_asm330lhhx_sensor *sensor;
int i;
#endif /* CONFIG_IIO_ST_ASM330LHHX_ASYNC_HW_TIMESTAMP */
u8 data = ST_ASM330LHHX_TIMESTAMP_RESET_VALUE;
int ret;
ret = st_asm330lhhx_write_atomic(hw, ST_ASM330LHHX_REG_TIMESTAMP2_ADDR,
sizeof(data), &data);
if (ret < 0)
return ret;
#if defined(CONFIG_IIO_ST_ASM330LHHX_ASYNC_HW_TIMESTAMP)
spin_lock_irq(&hw->hwtimestamp_lock);
hw->hw_timestamp_global = (hw->hw_timestamp_global + (1LL << 32)) &
GENMASK_ULL(63, 32);
spin_unlock_irq(&hw->hwtimestamp_lock);
for (i = 0; i < ST_ASM330LHHX_ID_MAX; ++i) {
if (!hw->iio_devs[i])
continue;
sensor = iio_priv(hw->iio_devs[i]);
}
hw->timesync_c = 0;
hw->timesync_ktime = ktime_set(0, ST_ASM330LHHX_FAST_KTIME);
#else /* CONFIG_IIO_ST_ASM330LHHX_ASYNC_HW_TIMESTAMP */
hw->hw_timestamp_global = (hw->hw_timestamp_global + (1LL << 32)) &
GENMASK_ULL(63, 32);
#endif /* CONFIG_IIO_ST_ASM330LHHX_ASYNC_HW_TIMESTAMP */
hw->ts = st_asm330lhhx_get_time_ns(hw->iio_devs[0]);
hw->ts_offset = hw->ts;
hw->val_ts_old = 0;
hw->hw_ts_high = 0;
hw->tsample = 0ull;
return 0;
}
int st_asm330lhhx_set_fifo_mode(struct st_asm330lhhx_hw *hw,
enum st_asm330lhhx_fifo_mode fifo_mode)
{
int err;
err = st_asm330lhhx_write_with_mask(hw, ST_ASM330LHHX_REG_FIFO_CTRL4_ADDR,
ST_ASM330LHHX_REG_FIFO_MODE_MASK,
fifo_mode);
if (err < 0)
return err;
hw->fifo_mode = fifo_mode;
if (fifo_mode == ST_ASM330LHHX_FIFO_BYPASS)
clear_bit(ST_ASM330LHHX_HW_OPERATIONAL, &hw->state);
else
set_bit(ST_ASM330LHHX_HW_OPERATIONAL, &hw->state);
return 0;
}
int __st_asm330lhhx_set_sensor_batching_odr(struct st_asm330lhhx_sensor *s,
bool enable)
{
enum st_asm330lhhx_sensor_id id = s->id;
struct st_asm330lhhx_hw *hw = s->hw;
u8 data = 0;
int err;
if (enable) {
err = st_asm330lhhx_get_batch_val(s, s->odr, s->uodr, &data);
if (err < 0)
return err;
}
return __st_asm330lhhx_write_with_mask(hw,
hw->odr_table_entry[id].batching_reg.addr,
hw->odr_table_entry[id].batching_reg.mask,
data);
}
static inline int
st_asm330lhhx_set_sensor_batching_odr(struct st_asm330lhhx_sensor *sensor,
bool enable)
{
struct st_asm330lhhx_hw *hw = sensor->hw;
int err;
mutex_lock(&hw->page_lock);
err = __st_asm330lhhx_set_sensor_batching_odr(sensor, enable);
mutex_unlock(&hw->page_lock);
return err;
}
int st_asm330lhhx_update_watermark(struct st_asm330lhhx_sensor *sensor,
u16 watermark)
{
u16 fifo_watermark = ST_ASM330LHHX_MAX_FIFO_DEPTH, cur_watermark = 0;
struct st_asm330lhhx_hw *hw = sensor->hw;
struct st_asm330lhhx_sensor *cur_sensor;
__le16 wdata;
int i, err;
u8 data;
for (i = ST_ASM330LHHX_ID_GYRO; i < ST_ASM330LHHX_ID_MAX; i++) {
if (!hw->iio_devs[i])
continue;
cur_sensor = iio_priv(hw->iio_devs[i]);
if (!(hw->enable_mask & BIT(cur_sensor->id)))
continue;
cur_watermark = (cur_sensor == sensor) ? watermark
: cur_sensor->watermark;
fifo_watermark = min_t(u16, fifo_watermark, cur_watermark);
}
fifo_watermark = max_t(u16, fifo_watermark, 2);
mutex_lock(&hw->lock);
err = st_asm330lhhx_read_atomic(hw, ST_ASM330LHHX_REG_FIFO_CTRL1_ADDR + 1,
sizeof(data), &data);
if (err < 0)
goto out;
fifo_watermark = ((data << 8) & ~ST_ASM330LHHX_REG_FIFO_WTM_MASK) |
(fifo_watermark & ST_ASM330LHHX_REG_FIFO_WTM_MASK);
wdata = cpu_to_le16(fifo_watermark);
err = st_asm330lhhx_write_atomic(hw, ST_ASM330LHHX_REG_FIFO_CTRL1_ADDR,
sizeof(wdata), (u8 *)&wdata);
out:
mutex_unlock(&hw->lock);
return err < 0 ? err : 0;
}
static struct iio_dev *st_asm330lhhx_get_iiodev_from_tag(struct st_asm330lhhx_hw *hw,
u8 tag)
{
struct iio_dev *iio_dev;
switch (tag) {
case ST_ASM330LHHX_GYRO_TAG:
iio_dev = hw->iio_devs[ST_ASM330LHHX_ID_GYRO];
break;
case ST_ASM330LHHX_ACC_TAG:
iio_dev = hw->iio_devs[ST_ASM330LHHX_ID_ACC];
break;
#ifdef CONFIG_IIO_ST_ASM330LHHX_EN_TEMPERATURE
case ST_ASM330LHHX_TEMP_TAG:
iio_dev = hw->iio_devs[ST_ASM330LHHX_ID_TEMP];
break;
#endif /* CONFIG_IIO_ST_ASM330LHHX_EN_TEMPERATURE */
default:
iio_dev = NULL;
break;
}
return iio_dev;
}
static inline void st_asm330lhhx_sync_hw_ts(struct st_asm330lhhx_hw *hw, s64 ts)
{
s64 delta = ts - hw->hw_ts;
hw->ts_offset = st_asm330lhhx_ewma(hw->ts_offset, delta,
ST_ASM330LHHX_EWMA_LEVEL);
}
static int st_asm330lhhx_read_fifo(struct st_asm330lhhx_hw *hw)
{
u8 iio_buf[ALIGN(ST_ASM330LHHX_SAMPLE_SIZE, sizeof(s64)) +
sizeof(s64) + sizeof(s64)];
u8 buf[6 * ST_ASM330LHHX_FIFO_SAMPLE_SIZE], tag, *ptr;
int i, err, word_len, fifo_len, read_len;
__le64 hw_timestamp_push;
struct iio_dev *iio_dev;
s64 ts_irq, hw_ts_old;
__le16 fifo_status;
u16 fifo_depth;
s16 drdymask;
u32 val;
/* return if FIFO is already disabled */
if (!test_bit(ST_ASM330LHHX_HW_OPERATIONAL, &hw->state)) {
dev_warn(hw->dev, "%s: FIFO in bypass mode\n", __func__);
return 0;
}
ts_irq = hw->ts - hw->delta_ts;
err = st_asm330lhhx_read_atomic(hw, ST_ASM330LHHX_REG_FIFO_STATUS1_ADDR,
sizeof(fifo_status), (u8 *)&fifo_status);
if (err < 0)
return err;
fifo_depth = le16_to_cpu(fifo_status) & ST_ASM330LHHX_REG_FIFO_STATUS_DIFF;
if (!fifo_depth)
return 0;
fifo_len = fifo_depth * ST_ASM330LHHX_FIFO_SAMPLE_SIZE;
read_len = 0;
while (read_len < fifo_len) {
word_len = min_t(int, fifo_len - read_len, sizeof(buf));
err = st_asm330lhhx_read_atomic(hw,
ST_ASM330LHHX_REG_FIFO_DATA_OUT_TAG_ADDR,
word_len, buf);
if (err < 0)
return err;
for (i = 0; i < word_len; i += ST_ASM330LHHX_FIFO_SAMPLE_SIZE) {
ptr = &buf[i + ST_ASM330LHHX_TAG_SIZE];
tag = buf[i] >> 3;
if (tag == ST_ASM330LHHX_TS_TAG) {
val = get_unaligned_le32(ptr);
#if defined(CONFIG_IIO_ST_ASM330LHHX_ASYNC_HW_TIMESTAMP)
spin_lock_irq(&hw->hwtimestamp_lock);
#endif /* CONFIG_IIO_ST_ASM330LHHX_ASYNC_HW_TIMESTAMP */
hw->hw_timestamp_global =
(hw->hw_timestamp_global &
GENMASK_ULL(63, 32)) |
(u32)le32_to_cpu(get_unaligned_le32(ptr));
#if defined(CONFIG_IIO_ST_ASM330LHHX_ASYNC_HW_TIMESTAMP)
spin_unlock_irq(&hw->hwtimestamp_lock);
#endif /* CONFIG_IIO_ST_ASM330LHHX_ASYNC_HW_TIMESTAMP */
if (hw->val_ts_old > val)
hw->hw_ts_high++;
hw_ts_old = hw->hw_ts;
/* check hw rollover */
hw->val_ts_old = val;
hw->hw_ts = (val + ((s64)hw->hw_ts_high << 32)) *
hw->ts_delta_ns;
hw->ts_offset = st_asm330lhhx_ewma(hw->ts_offset,
ts_irq - hw->hw_ts,
ST_ASM330LHHX_EWMA_LEVEL);
if (!test_bit(ST_ASM330LHHX_HW_FLUSH, &hw->state))
/* sync ap timestamp and sensor one */
st_asm330lhhx_sync_hw_ts(hw, ts_irq);
ts_irq += hw->hw_ts;
if (!hw->tsample)
hw->tsample = hw->ts_offset + hw->hw_ts;
else
hw->tsample = hw->tsample + hw->hw_ts - hw_ts_old;
} else {
struct st_asm330lhhx_sensor *sensor;
iio_dev = st_asm330lhhx_get_iiodev_from_tag(hw, tag);
if (!iio_dev)
continue;
sensor = iio_priv(iio_dev);
/* skip samples if not ready */
drdymask = (s16)le16_to_cpu(get_unaligned_le16(ptr));
if (unlikely(drdymask >= ST_ASM330LHHX_SAMPLE_DISCHARD)) {
#ifdef ST_ASM330LHHX_DEBUG_DISCHARGE
sensor->discharged_samples++;
#endif /* ST_ASM330LHHX_DEBUG_DISCHARGE */
continue;
}
memcpy(iio_buf, ptr, ST_ASM330LHHX_SAMPLE_SIZE);
#if defined(CONFIG_IIO_ST_ASM330LHHX_ASYNC_HW_TIMESTAMP)
spin_lock_irq(&hw->hwtimestamp_lock);
#endif /* CONFIG_IIO_ST_ASM330LHHX_ASYNC_HW_TIMESTAMP */
hw_timestamp_push = cpu_to_le64(hw->hw_timestamp_global);
#if defined(CONFIG_IIO_ST_ASM330LHHX_ASYNC_HW_TIMESTAMP)
spin_unlock_irq(&hw->hwtimestamp_lock);
#endif /* CONFIG_IIO_ST_ASM330LHHX_ASYNC_HW_TIMESTAMP */
memcpy(&iio_buf[ALIGN(ST_ASM330LHHX_SAMPLE_SIZE, sizeof(s64))],
&hw_timestamp_push, sizeof(hw_timestamp_push));
hw->tsample = min_t(s64,
st_asm330lhhx_get_time_ns(hw->iio_devs[0]),
hw->tsample);
iio_push_to_buffers_with_timestamp(iio_dev,
iio_buf,
hw->tsample);
sensor->last_fifo_timestamp = hw_timestamp_push;
}
}
read_len += word_len;
}
return read_len;
}
ssize_t st_asm330lhhx_get_max_watermark(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct st_asm330lhhx_sensor *sensor = iio_priv(dev_get_drvdata(dev));
return sprintf(buf, "%d\n", sensor->max_watermark);
}
ssize_t st_asm330lhhx_get_watermark(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct st_asm330lhhx_sensor *sensor = iio_priv(dev_get_drvdata(dev));
return sprintf(buf, "%d\n", sensor->watermark);
}
ssize_t st_asm330lhhx_set_watermark(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
struct iio_dev *iio_dev = dev_get_drvdata(dev);
struct st_asm330lhhx_sensor *sensor = iio_priv(iio_dev);
int err, val;
err = iio_device_claim_direct_mode(iio_dev);
if (err)
return err;
err = kstrtoint(buf, 10, &val);
if (err < 0)
goto out;
err = st_asm330lhhx_update_watermark(sensor, val);
if (err < 0)
goto out;
sensor->watermark = val;
out:
iio_device_release_direct_mode(iio_dev);
return err < 0 ? err : size;
}
ssize_t st_asm330lhhx_flush_fifo(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
struct iio_dev *iio_dev = dev_get_drvdata(dev);
struct st_asm330lhhx_sensor *sensor = iio_priv(iio_dev);
struct st_asm330lhhx_hw *hw = sensor->hw;
s64 event;
int count;
s64 type;
s64 fts;
s64 ts;
mutex_lock(&hw->fifo_lock);
ts = st_asm330lhhx_get_time_ns(iio_dev);
hw->delta_ts = ts - hw->ts;
hw->ts = ts;
set_bit(ST_ASM330LHHX_HW_FLUSH, &hw->state);
count = st_asm330lhhx_read_fifo(hw);
fts = sensor->last_fifo_timestamp;
mutex_unlock(&hw->fifo_lock);
type = count > 0 ? IIO_EV_DIR_FIFO_DATA : IIO_EV_DIR_FIFO_EMPTY;
event = IIO_UNMOD_EVENT_CODE(iio_dev->channels[0].type, -1,
IIO_EV_TYPE_FIFO_FLUSH, type);
iio_push_event(iio_dev, event, fts);
return size;
}
int st_asm330lhhx_suspend_fifo(struct st_asm330lhhx_hw *hw)
{
int err;
mutex_lock(&hw->fifo_lock);
st_asm330lhhx_read_fifo(hw);
err = st_asm330lhhx_set_fifo_mode(hw, ST_ASM330LHHX_FIFO_BYPASS);
mutex_unlock(&hw->fifo_lock);
return err;
}
int st_asm330lhhx_update_batching(struct iio_dev *iio_dev, bool enable)
{
struct st_asm330lhhx_sensor *sensor = iio_priv(iio_dev);
struct st_asm330lhhx_hw *hw = sensor->hw;
int err;
disable_irq(hw->irq);
err = st_asm330lhhx_set_sensor_batching_odr(sensor, enable);
enable_irq(hw->irq);
return err;
}
static int st_asm330lhhx_update_fifo(struct iio_dev *iio_dev,
bool enable)
{
struct st_asm330lhhx_sensor *sensor = iio_priv(iio_dev);
struct st_asm330lhhx_hw *hw = sensor->hw;
int err;
if (sensor->id == ST_ASM330LHHX_ID_GYRO && !enable)
delayed_enable_gyro = true;
if (sensor->id == ST_ASM330LHHX_ID_GYRO &&
enable && delayed_enable_gyro) {
delayed_enable_gyro = false;
msleep(delay_gyro);
}
disable_irq(hw->irq);
#if defined(CONFIG_IIO_ST_ASM330LHHX_ASYNC_HW_TIMESTAMP)
hrtimer_cancel(&hw->timesync_timer);
cancel_work_sync(&hw->timesync_work);
#endif /* CONFIG_IIO_ST_ASM330LHHX_ASYNC_HW_TIMESTAMP */
err = st_asm330lhhx_sensor_set_enable(sensor, enable);
if (err < 0)
goto out;
err = st_asm330lhhx_set_sensor_batching_odr(sensor, enable);
if (err < 0)
goto out;
#ifdef CONFIG_IIO_ST_ASM330LHHX_EN_TEMPERATURE
/*
* This is an auxiliary sensor, it need to get batched
* toghether at least with a primary sensor (Acc/Gyro).
*/
if (sensor->id == ST_ASM330LHHX_ID_TEMP) {
if (!(hw->enable_mask & (BIT(ST_ASM330LHHX_ID_ACC) |
BIT(ST_ASM330LHHX_ID_GYRO)))) {
struct st_asm330lhhx_sensor *acc_sensor;
u8 data = 0;
acc_sensor = iio_priv(hw->iio_devs[ST_ASM330LHHX_ID_ACC]);
if (enable) {
err = st_asm330lhhx_get_batch_val(acc_sensor,
sensor->odr, sensor->uodr,
&data);
if (err < 0)
goto out;
}
err = st_asm330lhhx_write_with_mask(hw,
hw->odr_table_entry[ST_ASM330LHHX_ID_ACC].batching_reg.addr,
hw->odr_table_entry[ST_ASM330LHHX_ID_ACC].batching_reg.mask,
data);
if (err < 0)
goto out;
}
}
#endif /* CONFIG_IIO_ST_ASM330LHHX_EN_TEMPERATURE */
err = st_asm330lhhx_update_watermark(sensor, sensor->watermark);
if (err < 0)
goto out;
if (enable && hw->fifo_mode == ST_ASM330LHHX_FIFO_BYPASS) {
st_asm330lhhx_reset_hwts(hw);
err = st_asm330lhhx_set_fifo_mode(hw, ST_ASM330LHHX_FIFO_CONT);
} else if (!hw->enable_mask) {
err = st_asm330lhhx_set_fifo_mode(hw, ST_ASM330LHHX_FIFO_BYPASS);
}
#if defined(CONFIG_IIO_ST_ASM330LHHX_ASYNC_HW_TIMESTAMP)
if (hw->fifo_mode != ST_ASM330LHHX_FIFO_BYPASS) {
hrtimer_start(&hw->timesync_timer,
ktime_set(0, 0),
HRTIMER_MODE_REL);
}
#endif /* CONFIG_IIO_ST_ASM330LHHX_ASYNC_HW_TIMESTAMP */
out:
enable_irq(hw->irq);
return err;
}
static irqreturn_t st_asm330lhhx_handler_irq(int irq, void *private)
{
struct st_asm330lhhx_hw *hw = (struct st_asm330lhhx_hw *)private;
s64 ts = st_asm330lhhx_get_time_ns(hw->iio_devs[0]);
hw->delta_ts = ts - hw->ts;
hw->ts = ts;
return IRQ_WAKE_THREAD;
}
static irqreturn_t st_asm330lhhx_handler_thread(int irq, void *private)
{
struct st_asm330lhhx_hw *hw = (struct st_asm330lhhx_hw *)private;
mutex_lock(&hw->fifo_lock);
st_asm330lhhx_read_fifo(hw);
clear_bit(ST_ASM330LHHX_HW_FLUSH, &hw->state);
mutex_unlock(&hw->fifo_lock);
#ifdef CONFIG_IIO_ST_ASM330LHHX_EN_BASIC_FEATURES
return st_asm330lhhx_event_handler(hw);
#else /* CONFIG_IIO_ST_ASM330LHHX_EN_BASIC_FEATURES */
return IRQ_HANDLED;
#endif /* CONFIG_IIO_ST_ASM330LHHX_EN_BASIC_FEATURES */
}
static int st_asm330lhhx_fifo_preenable(struct iio_dev *iio_dev)
{
return st_asm330lhhx_update_fifo(iio_dev, true);
}
static int st_asm330lhhx_fifo_postdisable(struct iio_dev *iio_dev)
{
return st_asm330lhhx_update_fifo(iio_dev, false);
}
static const struct iio_buffer_setup_ops st_asm330lhhx_fifo_ops = {
.preenable = st_asm330lhhx_fifo_preenable,
.postdisable = st_asm330lhhx_fifo_postdisable,
};
static int st_asm330lhhx_enable_timestamp(struct st_asm330lhhx_hw *hw,
bool enable)
{
return st_asm330lhhx_write_with_mask(hw,
ST_ASM330LHHX_REG_FIFO_CTRL4_ADDR,
ST_ASM330LHHX_REG_DEC_TS_MASK,
enable == true ? 1 : 0);
}
static int st_asm330lhhx_fifo_init(struct st_asm330lhhx_hw *hw)
{
return st_asm330lhhx_enable_timestamp(hw, true);
}
int st_asm330lhhx_buffers_setup(struct st_asm330lhhx_hw *hw)
{
struct iio_buffer *buffer;
unsigned long irq_type;
bool irq_active_low;
int i, err;
irq_type = irqd_get_trigger_type(irq_get_irq_data(hw->irq));
if (irq_type == IRQF_TRIGGER_NONE)
irq_type = IRQF_TRIGGER_HIGH;
switch (irq_type) {
case IRQF_TRIGGER_HIGH:
irq_active_low = false;
break;
case IRQF_TRIGGER_LOW:
irq_active_low = true;
break;
default:
dev_info(hw->dev, "mode %lx unsupported\n", irq_type);
return -EINVAL;
}
err = st_asm330lhhx_write_with_mask(hw, ST_ASM330LHHX_REG_CTRL3_C_ADDR,
ST_ASM330LHHX_REG_H_LACTIVE_MASK,
irq_active_low);
if (err < 0)
return err;
if (device_property_read_bool(hw->dev, "drive-open-drain")) {
err = st_asm330lhhx_write_with_mask(hw,
ST_ASM330LHHX_REG_CTRL3_C_ADDR,
ST_ASM330LHHX_REG_PP_OD_MASK,
1);
if (err < 0)
return err;
irq_type |= IRQF_SHARED;
}
err = devm_request_threaded_irq(hw->dev, hw->irq,
st_asm330lhhx_handler_irq,
st_asm330lhhx_handler_thread,
irq_type | IRQF_ONESHOT,
"asm330lhhx", hw);
if (err) {
dev_err(hw->dev, "failed to request trigger irq %d\n",
hw->irq);
return err;
}
for (i = 0; i < ST_ASM330LHHX_ID_EVENT; i++) {
if (!hw->iio_devs[i])
continue;
buffer = devm_iio_kfifo_allocate(hw->dev);
if (!buffer)
return -ENOMEM;
iio_device_attach_buffer(hw->iio_devs[i], buffer);
hw->iio_devs[i]->modes |= INDIO_BUFFER_SOFTWARE;
hw->iio_devs[i]->setup_ops = &st_asm330lhhx_fifo_ops;
}
err = st_asm330lhhx_hwtimesync_init(hw);
if (err)
return err;
return st_asm330lhhx_fifo_init(hw);
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,616 @@
/**
* STMicroelectronics st_asm330lhhx events function sensor driver
*
* Copyright 2020 STMicroelectronics Inc.
*
* Mario Tesi <mario.tesi@st.com>
*
* Licensed under the GPL-2.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/iio/events.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
#include <linux/iio/trigger_consumer.h>
#include <linux/iio/triggered_buffer.h>
#include <linux/iio/trigger.h>
#include <linux/iio/buffer.h>
#include "st_asm330lhhx.h"
#ifdef CONFIG_IIO_ST_ASM330LHHX_EN_BASIC_FEATURES
#define ST_ASM330LHHX_REG_ALL_INT_SRC_ADDR 0x1a
#define ST_ASM330LHHX_FF_IA_MASK BIT(0)
#define ST_ASM330LHHX_WU_IA_MASK BIT(1)
#define ST_ASM330LHHX_D6D_IA_MASK BIT(4)
#define ST_ASM330LHHX_SLEEP_CHANGE_MASK BIT(5)
#define ST_ASM330LHHX_REG_WAKE_UP_SRC_ADDR 0x1b
#define ST_ASM330LHHX_WAKE_UP_EVENT_MASK GENMASK(3, 0)
#define ST_ASM330LHHX_REG_D6D_SRC_ADDR 0x1d
#define ST_ASM330LHHX_D6D_EVENT_MASK GENMASK(5, 0)
#define ST_ASM330LHHX_REG_INT_CFG1_ADDR 0x58
#define ST_ASM330LHHX_INTERRUPTS_ENABLE_MASK BIT(7)
#define ST_ASM330LHHX_REG_MD1_CFG_ADDR 0x5e
#define ST_ASM330LHHX_REG_MD2_CFG_ADDR 0x5f
#define ST_ASM330LHHX_INT_6D_MASK BIT(2)
#define ST_ASM330LHHX_INT_FF_MASK BIT(4)
#define ST_ASM330LHHX_INT_WU_MASK BIT(5)
#define ST_ASM330LHHX_INT_SLEEP_CHANGE_MASK BIT(7)
static const unsigned long st_asm330lhhx_event_available_scan_masks[] = {
0x1, 0x0
};
static const struct iio_chan_spec st_asm330lhhx_wk_channels[] = {
{
.type = IIO_GESTURE,
.scan_index = 0,
.scan_type = {
.sign = 'u',
.realbits = 8,
.storagebits = 8,
},
},
IIO_CHAN_SOFT_TIMESTAMP(1),
};
static const struct iio_chan_spec st_asm330lhhx_ff_channels[] = {
ST_ASM330LHHX_EVENT_CHANNEL(IIO_GESTURE, thr),
};
static const struct iio_chan_spec st_asm330lhhx_sc_channels[] = {
ST_ASM330LHHX_EVENT_CHANNEL(IIO_GESTURE, thr),
};
static const struct iio_chan_spec st_asm330lhhx_6D_channels[] = {
{
.type = IIO_GESTURE,
.scan_index = 0,
.scan_type = {
.sign = 'u',
.realbits = 8,
.storagebits = 8,
},
},
IIO_CHAN_SOFT_TIMESTAMP(1),
};
static
int st_asm330lhhx_event_sensor_set_enable(struct st_asm330lhhx_sensor *sensor,
bool enable)
{
int err, eint = !!enable;
struct st_asm330lhhx_hw *hw = sensor->hw;
u8 int_reg = hw->int_pin == 1 ? ST_ASM330LHHX_REG_MD1_CFG_ADDR :
ST_ASM330LHHX_REG_MD2_CFG_ADDR;
switch (sensor->id) {
case ST_ASM330LHHX_ID_WK:
err = st_asm330lhhx_write_with_mask(hw,
int_reg,
ST_ASM330LHHX_INT_WU_MASK,
eint);
if (err < 0)
return err;
break;
case ST_ASM330LHHX_ID_FF:
err = st_asm330lhhx_write_with_mask(hw,
int_reg,
ST_ASM330LHHX_INT_FF_MASK,
eint);
if (err < 0)
return err;
break;
case ST_ASM330LHHX_ID_SC:
err = st_asm330lhhx_write_with_mask(hw,
int_reg,
ST_ASM330LHHX_INT_SLEEP_CHANGE_MASK,
eint);
if (err < 0)
return err;
break;
case ST_ASM330LHHX_ID_6D:
err = st_asm330lhhx_write_with_mask(hw,
int_reg,
ST_ASM330LHHX_INT_6D_MASK,
eint);
if (err < 0)
return err;
break;
default:
err = -EINVAL;
break;
}
if (err >= 0) {
err = st_asm330lhhx_write_with_mask(hw,
ST_ASM330LHHX_REG_INT_CFG1_ADDR,
ST_ASM330LHHX_INTERRUPTS_ENABLE_MASK,
eint);
if (eint == 0)
hw->enable_mask &= ~BIT(sensor->id);
else
hw->enable_mask |= BIT(sensor->id);
}
return err;
}
static int st_asm330lhhx_read_event_config(struct iio_dev *iio_dev,
const struct iio_chan_spec *chan,
enum iio_event_type type,
enum iio_event_direction dir)
{
struct st_asm330lhhx_sensor *sensor = iio_priv(iio_dev);
struct st_asm330lhhx_hw *hw = sensor->hw;
return !!(hw->enable_mask & BIT(sensor->id));
}
static int st_asm330lhhx_write_event_config(struct iio_dev *iio_dev,
const struct iio_chan_spec *chan,
enum iio_event_type type,
enum iio_event_direction dir,
int state)
{
struct st_asm330lhhx_sensor *sensor = iio_priv(iio_dev);
int err;
mutex_lock(&iio_dev->mlock);
err = st_asm330lhhx_event_sensor_set_enable(sensor, state);
mutex_unlock(&iio_dev->mlock);
return err;
}
ssize_t st_asm330lhhx_wakeup_threshold_get(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct st_asm330lhhx_sensor *sensor = iio_priv(dev_get_drvdata(dev));
return sprintf(buf, "%d\n", sensor->conf[0]);
}
ssize_t st_asm330lhhx_wakeup_threshold_set(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
struct iio_dev *iio_dev = dev_get_drvdata(dev);
struct st_asm330lhhx_sensor *sensor = iio_priv(iio_dev);
int err, val;
err = iio_device_claim_direct_mode(iio_dev);
if (err)
return err;
err = kstrtoint(buf, 10, &val);
if (err < 0)
goto out;
err = st_asm330lhhx_set_wake_up_thershold(sensor->hw, val);
if (err < 0)
goto out;
sensor->conf[0] = val;
out:
iio_device_release_direct_mode(iio_dev);
return err < 0 ? err : size;
}
ssize_t st_asm330lhhx_wakeup_duration_get(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct st_asm330lhhx_sensor *sensor = iio_priv(dev_get_drvdata(dev));
return sprintf(buf, "%d\n", sensor->conf[1]);
}
ssize_t st_asm330lhhx_wakeup_duration_set(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
struct iio_dev *iio_dev = dev_get_drvdata(dev);
struct st_asm330lhhx_sensor *sensor = iio_priv(iio_dev);
int err, val;
err = iio_device_claim_direct_mode(iio_dev);
if (err)
return err;
err = kstrtoint(buf, 10, &val);
if (err < 0)
goto out;
err = st_asm330lhhx_set_wake_up_duration(sensor->hw, val);
if (err < 0)
goto out;
sensor->conf[1] = val;
out:
iio_device_release_direct_mode(iio_dev);
return err < 0 ? err : size;
}
ssize_t st_asm330lhhx_freefall_threshold_get(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct st_asm330lhhx_sensor *sensor = iio_priv(dev_get_drvdata(dev));
return sprintf(buf, "%d\n", sensor->conf[2]);
}
ssize_t st_asm330lhhx_freefall_threshold_set(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
struct iio_dev *iio_dev = dev_get_drvdata(dev);
struct st_asm330lhhx_sensor *sensor = iio_priv(iio_dev);
int err, val;
err = iio_device_claim_direct_mode(iio_dev);
if (err)
return err;
err = kstrtoint(buf, 10, &val);
if (err < 0)
goto out;
err = st_asm330lhhx_set_freefall_threshold(sensor->hw, val);
if (err < 0)
goto out;
sensor->conf[2] = val;
out:
iio_device_release_direct_mode(iio_dev);
return err < 0 ? err : size;
}
ssize_t st_asm330lhhx_6D_threshold_get(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct st_asm330lhhx_sensor *sensor = iio_priv(dev_get_drvdata(dev));
return sprintf(buf, "%d\n", sensor->conf[3]);
}
ssize_t st_asm330lhhx_6D_threshold_set(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
struct iio_dev *iio_dev = dev_get_drvdata(dev);
struct st_asm330lhhx_sensor *sensor = iio_priv(iio_dev);
int err, val;
err = iio_device_claim_direct_mode(iio_dev);
if (err)
return err;
err = kstrtoint(buf, 10, &val);
if (err < 0)
goto out;
err = st_asm330lhhx_set_6D_threshold(sensor->hw, val);
if (err < 0)
goto out;
sensor->conf[3] = val;
out:
iio_device_release_direct_mode(iio_dev);
return err < 0 ? err : size;
}
static IIO_DEVICE_ATTR(wakeup_threshold, 0644,
st_asm330lhhx_wakeup_threshold_get,
st_asm330lhhx_wakeup_threshold_set, 0);
static IIO_DEVICE_ATTR(wakeup_duration, 0644,
st_asm330lhhx_wakeup_duration_get,
st_asm330lhhx_wakeup_duration_set, 0);
static IIO_DEVICE_ATTR(freefall_threshold, 0644,
st_asm330lhhx_freefall_threshold_get,
st_asm330lhhx_freefall_threshold_set, 0);
static IIO_DEVICE_ATTR(sixd_threshold, 0644,
st_asm330lhhx_6D_threshold_get,
st_asm330lhhx_6D_threshold_set, 0);
static struct attribute *st_asm330lhhx_wk_attributes[] = {
&iio_dev_attr_wakeup_threshold.dev_attr.attr,
&iio_dev_attr_wakeup_duration.dev_attr.attr,
NULL,
};
static const struct attribute_group st_asm330lhhx_wk_attribute_group = {
.attrs = st_asm330lhhx_wk_attributes,
};
static const struct iio_info st_asm330lhhx_wk_info = {
.attrs = &st_asm330lhhx_wk_attribute_group,
};
static struct attribute *st_asm330lhhx_ff_attributes[] = {
&iio_dev_attr_freefall_threshold.dev_attr.attr,
NULL,
};
static const struct attribute_group st_asm330lhhx_ff_attribute_group = {
.attrs = st_asm330lhhx_ff_attributes,
};
static const struct iio_info st_asm330lhhx_ff_info = {
.attrs = &st_asm330lhhx_ff_attribute_group,
.read_event_config = st_asm330lhhx_read_event_config,
.write_event_config = st_asm330lhhx_write_event_config,
};
static struct attribute *st_asm330lhhx_sc_attributes[] = {
NULL,
};
static const struct attribute_group st_asm330lhhx_sc_attribute_group = {
.attrs = st_asm330lhhx_sc_attributes,
};
static const struct iio_info st_asm330lhhx_sc_info = {
.attrs = &st_asm330lhhx_sc_attribute_group,
.read_event_config = st_asm330lhhx_read_event_config,
.write_event_config = st_asm330lhhx_write_event_config,
};
static struct attribute *st_asm330lhhx_6D_attributes[] = {
&iio_dev_attr_sixd_threshold.dev_attr.attr,
NULL,
};
static const struct attribute_group st_asm330lhhx_6D_attribute_group = {
.attrs = st_asm330lhhx_6D_attributes,
};
static const struct iio_info st_asm330lhhx_6D_info = {
.attrs = &st_asm330lhhx_6D_attribute_group,
};
static
struct iio_dev *st_asm330lhhx_alloc_event_iiodev(struct st_asm330lhhx_hw *hw,
enum st_asm330lhhx_sensor_id id)
{
struct st_asm330lhhx_sensor *sensor;
struct iio_dev *iio_dev;
iio_dev = devm_iio_device_alloc(hw->dev, sizeof(*sensor));
if (!iio_dev)
return NULL;
iio_dev->modes = INDIO_DIRECT_MODE;
iio_dev->dev.parent = hw->dev;
sensor = iio_priv(iio_dev);
sensor->id = id;
sensor->hw = hw;
sensor->watermark = 1;
iio_dev->available_scan_masks = st_asm330lhhx_event_available_scan_masks;
switch (id) {
case ST_ASM330LHHX_ID_WK:
iio_dev->channels = st_asm330lhhx_wk_channels;
iio_dev->num_channels = ARRAY_SIZE(st_asm330lhhx_wk_channels);
iio_dev->name = "asm330lhhx_wk";
iio_dev->info = &st_asm330lhhx_wk_info;
break;
case ST_ASM330LHHX_ID_FF:
iio_dev->channels = st_asm330lhhx_ff_channels;
iio_dev->num_channels = ARRAY_SIZE(st_asm330lhhx_ff_channels);
iio_dev->name = "asm330lhhx_ff";
iio_dev->info = &st_asm330lhhx_ff_info;
break;
case ST_ASM330LHHX_ID_SC:
iio_dev->channels = st_asm330lhhx_sc_channels;
iio_dev->num_channels = ARRAY_SIZE(st_asm330lhhx_sc_channels);
iio_dev->name = "asm330lhhx_sc";
iio_dev->info = &st_asm330lhhx_sc_info;
break;
case ST_ASM330LHHX_ID_6D:
iio_dev->channels = st_asm330lhhx_6D_channels;
iio_dev->num_channels = ARRAY_SIZE(st_asm330lhhx_6D_channels);
iio_dev->name = "asm330lhhx_6d";
iio_dev->info = &st_asm330lhhx_6D_info;
break;
default:
iio_device_free(iio_dev);
return NULL;
}
return iio_dev;
}
int st_asm330lhhx_event_handler(struct st_asm330lhhx_hw *hw)
{
struct iio_dev *iio_dev;
u8 status;
s64 event;
int err;
if (hw->enable_mask &
(BIT(ST_ASM330LHHX_ID_WK) | BIT(ST_ASM330LHHX_ID_FF) |
BIT(ST_ASM330LHHX_ID_SC) | BIT(ST_ASM330LHHX_ID_6D))) {
err = hw->tf->read(hw->dev,
ST_ASM330LHHX_REG_ALL_INT_SRC_ADDR,
sizeof(status), &status);
if (err < 0)
return IRQ_HANDLED;
/* base function sensors */
if (status & ST_ASM330LHHX_FF_IA_MASK) {
iio_dev = hw->iio_devs[ST_ASM330LHHX_ID_FF];
event = IIO_UNMOD_EVENT_CODE(IIO_GESTURE, -1,
IIO_EV_TYPE_THRESH,
IIO_EV_DIR_RISING);
iio_push_event(iio_dev, event,
st_asm330lhhx_get_time_ns(iio_dev));
}
if (status & ST_ASM330LHHX_WU_IA_MASK) {
struct st_asm330lhhx_sensor *sensor;
iio_dev = hw->iio_devs[ST_ASM330LHHX_ID_WK];
sensor = iio_priv(iio_dev);
iio_trigger_poll_chained(sensor->trig);
}
if (status & ST_ASM330LHHX_SLEEP_CHANGE_MASK) {
iio_dev = hw->iio_devs[ST_ASM330LHHX_ID_SC];
event = IIO_UNMOD_EVENT_CODE(IIO_GESTURE, -1,
IIO_EV_TYPE_THRESH,
IIO_EV_DIR_RISING);
iio_push_event(iio_dev, event,
st_asm330lhhx_get_time_ns(iio_dev));
}
if (status & ST_ASM330LHHX_D6D_IA_MASK) {
struct st_asm330lhhx_sensor *sensor;
iio_dev = hw->iio_devs[ST_ASM330LHHX_ID_6D];
sensor = iio_priv(iio_dev);
iio_trigger_poll_chained(sensor->trig);
}
}
return IRQ_HANDLED;
}
static inline int st_asm330lhhx_get_6D(struct st_asm330lhhx_hw *hw, u8 *out)
{
return st_asm330lhhx_read_with_mask(hw, ST_ASM330LHHX_REG_D6D_SRC_ADDR,
ST_ASM330LHHX_D6D_EVENT_MASK, out);
}
static inline int st_asm330lhhx_get_wk(struct st_asm330lhhx_hw *hw, u8 *out)
{
return st_asm330lhhx_read_with_mask(hw,
ST_ASM330LHHX_REG_WAKE_UP_SRC_ADDR,
ST_ASM330LHHX_WAKE_UP_EVENT_MASK, out);
}
static irqreturn_t st_asm330lhhx_6D_handler_thread(int irq, void *p)
{
struct iio_poll_func *pf = p;
struct iio_dev *iio_dev = pf->indio_dev;
struct st_asm330lhhx_sensor *sensor = iio_priv(iio_dev);
u8 buffer[sizeof(u8) + sizeof(s64)];
st_asm330lhhx_get_6D(sensor->hw, buffer);
iio_push_to_buffers_with_timestamp(iio_dev, buffer,
st_asm330lhhx_get_time_ns(iio_dev));
iio_trigger_notify_done(sensor->trig);
return IRQ_HANDLED;
}
static irqreturn_t st_asm330lhhx_wk_handler_thread(int irq, void *p)
{
struct iio_poll_func *pf = p;
struct iio_dev *iio_dev = pf->indio_dev;
struct st_asm330lhhx_sensor *sensor = iio_priv(iio_dev);
u8 buffer[sizeof(u8) + sizeof(s64)];
st_asm330lhhx_get_wk(sensor->hw, buffer);
iio_push_to_buffers_with_timestamp(iio_dev, buffer,
st_asm330lhhx_get_time_ns(iio_dev));
iio_trigger_notify_done(sensor->trig);
return IRQ_HANDLED;
}
static const struct iio_trigger_ops st_asm330lhhx_trigger_ops = {
NULL,
};
static int st_asm330lhhx_buffer_preenable(struct iio_dev *iio_dev)
{
return st_asm330lhhx_event_sensor_set_enable(iio_priv(iio_dev), true);
}
static int st_asm330lhhx_buffer_postdisable(struct iio_dev *iio_dev)
{
return st_asm330lhhx_event_sensor_set_enable(iio_priv(iio_dev), false);
}
static const struct iio_buffer_setup_ops st_asm330lhhx_buffer_ops = {
.preenable = st_asm330lhhx_buffer_preenable,
.postdisable = st_asm330lhhx_buffer_postdisable,
};
int st_asm330lhhx_probe_event(struct st_asm330lhhx_hw *hw)
{
struct st_asm330lhhx_sensor *sensor;
struct iio_dev *iio_dev;
irqreturn_t (*pthread[ST_ASM330LHHX_ID_MAX - ST_ASM330LHHX_ID_TRIGGER])(int irq, void *p) = {
[0] = st_asm330lhhx_wk_handler_thread,
[1] = st_asm330lhhx_6D_handler_thread,
/* add here all other trigger handler funcions */
};
int i, err;
for (i = ST_ASM330LHHX_ID_EVENT; i < ST_ASM330LHHX_ID_MAX; i++) {
hw->iio_devs[i] = st_asm330lhhx_alloc_event_iiodev(hw, i);
if (!hw->iio_devs[i])
return -ENOMEM;
}
/* configure trigger sensors */
for (i = ST_ASM330LHHX_ID_TRIGGER; i < ST_ASM330LHHX_ID_MAX; i++) {
iio_dev = hw->iio_devs[i];
sensor = iio_priv(iio_dev);
err = devm_iio_triggered_buffer_setup(hw->dev, iio_dev,
NULL, pthread[i - ST_ASM330LHHX_ID_TRIGGER],
&st_asm330lhhx_buffer_ops);
if (err < 0)
return err;
sensor->trig = devm_iio_trigger_alloc(hw->dev, "%s-trigger",
iio_dev->name);
if (!sensor->trig)
return -ENOMEM;
iio_trigger_set_drvdata(sensor->trig, iio_dev);
sensor->trig->ops = &st_asm330lhhx_trigger_ops;
sensor->trig->dev.parent = hw->dev;
iio_dev->trig = iio_trigger_get(sensor->trig);
err = devm_iio_trigger_register(hw->dev, sensor->trig);
if (err)
return err;
}
for (i = ST_ASM330LHHX_ID_EVENT; i < ST_ASM330LHHX_ID_MAX; i++) {
if (!hw->iio_devs[i])
continue;
err = devm_iio_device_register(hw->dev, hw->iio_devs[i]);
if (err)
return err;
}
return 0;
}
#endif /* CONFIG_IIO_ST_ASM330LHHX_EN_BASIC_FEATURES */

View File

@ -1,5 +1,5 @@
/*
* STMicroelectronics st_asm330lhh hwtimestamp library driver
* STMicroelectronics st_asm330lhhx hwtimestamp library driver
*
* Copyright 2021 STMicroelectronics Inc.
*
@ -14,11 +14,11 @@
#include <linux/iio/buffer.h>
#include <asm/unaligned.h>
#include <linux/iio/buffer.h>
#include "st_asm330lhh.h"
#include "st_asm330lhhx.h"
#define ST_ASM330LHH_TSYNC_OFFSET_NS (300 * 1000LL)
#define ST_ASM330LHHX_TSYNC_OFFSET_NS (300 * 1000LL)
static void st_asm330lhh_read_hw_timestamp(struct st_asm330lhh_hw *hw)
static void st_asm330lhhx_read_hw_timestamp(struct st_asm330lhhx_hw *hw)
{
s64 timestamp_hw_global;
s64 eventLSB, eventMSB;
@ -27,13 +27,13 @@ static void st_asm330lhh_read_hw_timestamp(struct st_asm330lhh_hw *hw)
__le32 tmp;
int err;
err = st_asm330lhh_read_atomic(hw, ST_ASM330LHH_REG_TIMESTAMP0_ADDR,
sizeof(timestamp_hw), (u8 *)&timestamp_hw);
err = st_asm330lhhx_read_atomic(hw, ST_ASM330LHHX_REG_TIMESTAMP0_ADDR,
sizeof(timestamp_hw), (u8 *)&timestamp_hw);
if (err < 0)
return;
timestamp_cpu = st_asm330lhh_get_time_ns(hw->iio_devs[0]) -
ST_ASM330LHH_TSYNC_OFFSET_NS;
timestamp_cpu = st_asm330lhhx_get_time_ns(hw->iio_devs[0]) -
ST_ASM330LHHX_TSYNC_OFFSET_NS;
eventLSB = IIO_EVENT_CODE(IIO_COUNT, 0, 0, 0,
IIO_EV_TYPE_TIME_SYNC, 0, 0, 0);
@ -51,58 +51,58 @@ static void st_asm330lhh_read_hw_timestamp(struct st_asm330lhh_hw *hw)
tmp = cpu_to_le32((u32)(timestamp_hw_global >> 32));
memcpy(&((int8_t *)&eventMSB)[0], &tmp, sizeof(tmp));
if (hw->enable_mask & BIT(ST_ASM330LHH_ID_GYRO)) {
iio_push_event(hw->iio_devs[ST_ASM330LHH_ID_GYRO], eventLSB,
if (hw->enable_mask & BIT(ST_ASM330LHHX_ID_GYRO)) {
iio_push_event(hw->iio_devs[ST_ASM330LHHX_ID_GYRO], eventLSB,
timestamp_cpu);
iio_push_event(hw->iio_devs[ST_ASM330LHH_ID_GYRO], eventMSB,
iio_push_event(hw->iio_devs[ST_ASM330LHHX_ID_GYRO], eventMSB,
timestamp_cpu);
}
if (hw->enable_mask & BIT(ST_ASM330LHH_ID_ACC)) {
iio_push_event(hw->iio_devs[ST_ASM330LHH_ID_ACC], eventLSB,
if (hw->enable_mask & BIT(ST_ASM330LHHX_ID_ACC)) {
iio_push_event(hw->iio_devs[ST_ASM330LHHX_ID_ACC], eventLSB,
timestamp_cpu);
iio_push_event(hw->iio_devs[ST_ASM330LHH_ID_ACC], eventMSB,
iio_push_event(hw->iio_devs[ST_ASM330LHHX_ID_ACC], eventMSB,
timestamp_cpu);
}
#ifdef CONFIG_IIO_ST_ASM330LHH_EN_TEMPERATURE
if (hw->enable_mask & BIT(ST_ASM330LHH_ID_TEMP)) {
iio_push_event(hw->iio_devs[ST_ASM330LHH_ID_TEMP], eventLSB,
#ifdef CONFIG_IIO_ST_ASM330LHHX_EN_TEMPERATURE
if (hw->enable_mask & BIT(ST_ASM330LHHX_ID_TEMP)) {
iio_push_event(hw->iio_devs[ST_ASM330LHHX_ID_TEMP], eventLSB,
timestamp_cpu);
iio_push_event(hw->iio_devs[ST_ASM330LHH_ID_TEMP], eventMSB,
iio_push_event(hw->iio_devs[ST_ASM330LHHX_ID_TEMP], eventMSB,
timestamp_cpu);
}
#endif /* CONFIG_IIO_ST_ASM330LHH_EN_TEMPERATURE */
#endif /* CONFIG_IIO_ST_ASM330LHHX_EN_TEMPERATURE */
if (hw->timesync_c < 6)
hw->timesync_c++;
else
hw->timesync_ktime = ktime_set(0, ST_ASM330LHH_DEFAULT_KTIME);
hw->timesync_ktime = ktime_set(0, ST_ASM330LHHX_DEFAULT_KTIME);
}
static void st_asm330lhh_timesync_fn(struct work_struct *work)
static void st_asm330lhhx_timesync_fn(struct work_struct *work)
{
struct st_asm330lhh_hw *hw = container_of(work, struct st_asm330lhh_hw,
struct st_asm330lhhx_hw *hw = container_of(work, struct st_asm330lhhx_hw,
timesync_work);
st_asm330lhh_read_hw_timestamp(hw);
st_asm330lhhx_read_hw_timestamp(hw);
}
static enum hrtimer_restart st_asm330lhh_timer_fn(struct hrtimer *timer)
static enum hrtimer_restart st_asm330lhhx_timer_fn(struct hrtimer *timer)
{
struct st_asm330lhh_hw *hw;
struct st_asm330lhhx_hw *hw;
hw = container_of(timer, struct st_asm330lhh_hw, timesync_timer);
hw = container_of(timer, struct st_asm330lhhx_hw, timesync_timer);
hrtimer_forward(timer, hrtimer_cb_get_time(timer), hw->timesync_ktime);
queue_work(hw->timesync_workqueue, &hw->timesync_work);
return HRTIMER_RESTART;
}
int st_asm330lhh_hwtimesync_init(struct st_asm330lhh_hw *hw)
int st_asm330lhhx_hwtimesync_init(struct st_asm330lhhx_hw *hw)
{
hw->timesync_c = 0;
hw->timesync_ktime = ktime_set(0, ST_ASM330LHH_DEFAULT_KTIME);
hw->timesync_ktime = ktime_set(0, ST_ASM330LHHX_DEFAULT_KTIME);
hrtimer_init(&hw->timesync_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
hw->timesync_timer.function = st_asm330lhh_timer_fn;
hw->timesync_timer.function = st_asm330lhhx_timer_fn;
spin_lock_init(&hw->hwtimestamp_lock);
hw->hw_timestamp_global = 0;
@ -112,8 +112,8 @@ int st_asm330lhh_hwtimesync_init(struct st_asm330lhh_hw *hw)
return -ENOMEM;
}
INIT_WORK(&hw->timesync_work, st_asm330lhh_timesync_fn);
INIT_WORK(&hw->timesync_work, st_asm330lhhx_timesync_fn);
return 0;
}
EXPORT_SYMBOL(st_asm330lhh_hwtimesync_init);
EXPORT_SYMBOL(st_asm330lhhx_hwtimesync_init);

View File

@ -1,5 +1,5 @@
/*
* STMicroelectronics st_asm330lhh i2c driver
* STMicroelectronics st_asm330lhhx i2c driver
*
* Copyright 2019 STMicroelectronics Inc.
*
@ -15,9 +15,9 @@
#include <linux/slab.h>
#include <linux/of.h>
#include "st_asm330lhh.h"
#include "st_asm330lhhx.h"
static int st_asm330lhh_i2c_read(struct device *dev, u8 addr, int len, u8 *data)
static int st_asm330lhhx_i2c_read(struct device *dev, u8 addr, int len, u8 *data)
{
struct i2c_client *client = to_i2c_client(dev);
struct i2c_msg msg[2];
@ -35,7 +35,7 @@ static int st_asm330lhh_i2c_read(struct device *dev, u8 addr, int len, u8 *data)
return i2c_transfer(client->adapter, msg, 2);
}
static int st_asm330lhh_i2c_write(struct device *dev, u8 addr, int len,
static int st_asm330lhhx_i2c_write(struct device *dev, u8 addr, int len,
const u8 *data)
{
struct i2c_client *client = to_i2c_client(dev);
@ -53,45 +53,49 @@ static int st_asm330lhh_i2c_write(struct device *dev, u8 addr, int len,
return i2c_transfer(client->adapter, &msg, 1);
}
static const struct st_asm330lhh_transfer_function st_asm330lhh_transfer_fn = {
.read = st_asm330lhh_i2c_read,
.write = st_asm330lhh_i2c_write,
static const struct st_asm330lhhx_transfer_function st_asm330lhhx_transfer_fn = {
.read = st_asm330lhhx_i2c_read,
.write = st_asm330lhhx_i2c_write,
};
static int st_asm330lhh_i2c_probe(struct i2c_client *client,
static int st_asm330lhhx_i2c_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
return st_asm330lhh_probe(&client->dev, client->irq,
&st_asm330lhh_transfer_fn);
return st_asm330lhhx_probe(&client->dev, client->irq,
&st_asm330lhhx_transfer_fn);
}
static const struct of_device_id st_asm330lhh_i2c_of_match[] = {
static const struct of_device_id st_asm330lhhx_i2c_of_match[] = {
{
.compatible = "st,asm330lhhx",
},
{
.compatible = "st,asm330lhh",
},
{},
};
MODULE_DEVICE_TABLE(of, st_asm330lhh_i2c_of_match);
MODULE_DEVICE_TABLE(of, st_asm330lhhx_i2c_of_match);
static const struct i2c_device_id st_asm330lhh_i2c_id_table[] = {
static const struct i2c_device_id st_asm330lhhx_i2c_id_table[] = {
{ ST_ASM330LHH_DEV_NAME },
{ ST_ASM330LHHX_DEV_NAME },
{},
};
MODULE_DEVICE_TABLE(i2c, st_asm330lhh_i2c_id_table);
MODULE_DEVICE_TABLE(i2c, st_asm330lhhx_i2c_id_table);
static struct i2c_driver st_asm330lhh_driver = {
static struct i2c_driver st_asm330lhhx_driver = {
.driver = {
.name = "st_asm330lhh_i2c",
.pm = &st_asm330lhh_pm_ops,
.of_match_table = st_asm330lhh_i2c_of_match,
.name = "st_asm330lhhx_i2c",
.pm = &st_asm330lhhx_pm_ops,
.of_match_table = st_asm330lhhx_i2c_of_match,
},
.probe = st_asm330lhh_i2c_probe,
.id_table = st_asm330lhh_i2c_id_table,
.probe = st_asm330lhhx_i2c_probe,
.id_table = st_asm330lhhx_i2c_id_table,
};
module_i2c_driver(st_asm330lhh_driver);
module_i2c_driver(st_asm330lhhx_driver);
MODULE_AUTHOR("Lorenzo Bianconi <lorenzo.bianconi@st.com>");
MODULE_AUTHOR("Mario Tesi <mario.tesi@st.com>");
MODULE_DESCRIPTION("STMicroelectronics st_asm330lhh i2c driver");
MODULE_DESCRIPTION("STMicroelectronics st_asm330lhhx i2c driver");
MODULE_LICENSE("GPL v2");
MODULE_VERSION(ST_ASM330LHH_DRV_VERSION);
MODULE_VERSION(ST_ASM330LHHX_DRV_VERSION);

View File

@ -0,0 +1,115 @@
/*
* STMicroelectronics st_asm330lhhx spi driver
*
* Copyright 2019 STMicroelectronics Inc.
*
* Lorenzo Bianconi <lorenzo.bianconi@st.com>
* Mario Tesi <mario.tesi@st.com>
*
* Licensed under the GPL-2.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/spi/spi.h>
#include <linux/slab.h>
#include <linux/of.h>
#include "st_asm330lhhx.h"
#define SENSORS_SPI_READ BIT(7)
static int st_asm330lhhx_spi_read(struct device *dev, u8 addr, int len,
u8 *data)
{
struct spi_device *spi = to_spi_device(dev);
struct st_asm330lhhx_hw *hw = spi_get_drvdata(spi);
int err;
struct spi_transfer xfers[] = {
{
.tx_buf = hw->tb.tx_buf,
.bits_per_word = 8,
.len = 1,
},
{
.rx_buf = hw->tb.rx_buf,
.bits_per_word = 8,
.len = len,
}
};
hw->tb.tx_buf[0] = addr | SENSORS_SPI_READ;
err = spi_sync_transfer(spi, xfers, ARRAY_SIZE(xfers));
if (err < 0)
return err;
memcpy(data, hw->tb.rx_buf, len * sizeof(u8));
return len;
}
static int st_asm330lhhx_spi_write(struct device *dev, u8 addr, int len,
const u8 *data)
{
struct st_asm330lhhx_hw *hw;
struct spi_device *spi;
if (len >= ST_ASM330LHHX_TX_MAX_LENGTH)
return -ENOMEM;
spi = to_spi_device(dev);
hw = spi_get_drvdata(spi);
hw->tb.tx_buf[0] = addr;
memcpy(&hw->tb.tx_buf[1], data, len);
return spi_write(spi, hw->tb.tx_buf, len + 1);
}
static const struct st_asm330lhhx_transfer_function st_asm330lhhx_transfer_fn = {
.read = st_asm330lhhx_spi_read,
.write = st_asm330lhhx_spi_write,
};
static int st_asm330lhhx_spi_probe(struct spi_device *spi)
{
return st_asm330lhhx_probe(&spi->dev, spi->irq,
&st_asm330lhhx_transfer_fn);
}
static const struct of_device_id st_asm330lhhx_spi_of_match[] = {
{
.compatible = "st,asm330lhhx",
},
{
.compatible = "st,asm330lhh",
},
{},
};
MODULE_DEVICE_TABLE(of, st_asm330lhhx_spi_of_match);
static const struct spi_device_id st_asm330lhhx_spi_id_table[] = {
{ ST_ASM330LHH_DEV_NAME },
{ ST_ASM330LHHX_DEV_NAME },
{},
};
MODULE_DEVICE_TABLE(spi, st_asm330lhhx_spi_id_table);
static struct spi_driver st_asm330lhhx_driver = {
.driver = {
.name = "st_asm330lhhx_spi",
.pm = &st_asm330lhhx_pm_ops,
.of_match_table = st_asm330lhhx_spi_of_match,
},
.probe = st_asm330lhhx_spi_probe,
.id_table = st_asm330lhhx_spi_id_table,
};
module_spi_driver(st_asm330lhhx_driver);
MODULE_AUTHOR("Lorenzo Bianconi <lorenzo.bianconi@st.com>");
MODULE_AUTHOR("Mario Tesi <mario.tesi@st.com>");
MODULE_DESCRIPTION("STMicroelectronics st_asm330lhhx spi driver");
MODULE_LICENSE("GPL v2");
MODULE_VERSION(ST_ASM330LHHX_DRV_VERSION);

View File

@ -1,6 +0,0 @@
CONFIG_IIO_ST_ASM330LHH=m
CONFIG_IIO_ST_ASM330LHH_I2C=m
CONFIG_IIO_ST_ASM330LHH_SPI=m
CONFIG_IIO_ST_ASM330LHH_EN_TEMPERATURE=y
CONFIG_IIO_ST_ASM330LHH_EN_BASIC_FEATURES=y
CONFIG_IIO_ST_ASM330LHH_MAY_WAKEUP=y

View File

@ -0,0 +1,6 @@
CONFIG_IIO_ST_ASM330LHHX=m
CONFIG_IIO_ST_ASM330LHHX_I2C=m
CONFIG_IIO_ST_ASM330LHHX_SPI=m
CONFIG_IIO_ST_ASM330LHHX_EN_TEMPERATURE=y
CONFIG_IIO_ST_ASM330LHHX_EN_BASIC_FEATURES=y
CONFIG_IIO_ST_ASM330LHHX_MAY_WAKEUP=y