[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <c724a5a972742a411b18364860fddd816f7927fd.1371418586.git.andi@etezian.org>
Date: Sun, 16 Jun 2013 23:40:27 +0200
From: Andi Shyti <andi@...zian.org>
To: arnd@...db.de, gregkh@...uxfoundation.org, rob@...dley.net
Cc: oatilla@...il.com, szsolt@...il.com, linux-kernel@...r.kernel.org,
andi@...zian.org, linux-doc@...r.kernel.org
Subject: [PATCH 1/2] drivers/misc: etzkx driver for kxcnl/kxtnk accelerometer
The ETZKX state machine interrupt driven accelerometer is a
support for the Kionix kxtnk and kxcnl 3d accelerometer.
The driver generates three interfaces to the userspace:
- sysfs: the interfaces allow the user to enable/disable the
streaming of the x, y, z values, set output data rate and g
range sensitivity, enable/disable self_test
- /dev/input/eventX: is possible to read the x, y, z coordinates
- /dev/etzkx_stm: the user can trigger the state machine gesture
recognition
ETZKX_ACCEL is the flag which enables the accelerometer.
Signed-off-by: Andi Shyti <andi@...zian.org>
Tested-by: Onur Atilla <oatilla@...il.com>
Tested-by: Simon Zsolt <szsolt@...il.com>
---
drivers/misc/Kconfig | 12 +
drivers/misc/Makefile | 1 +
drivers/misc/etzkx.c | 2313 +++++++++++++++++++++++++++++++++++++++++++++
include/linux/i2c/etzkx.h | 157 +++
4 files changed, 2483 insertions(+)
create mode 100644 drivers/misc/etzkx.c
create mode 100644 include/linux/i2c/etzkx.h
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index c002d86..0d6baf6 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -381,6 +381,18 @@ config HMC6352
This driver provides support for the Honeywell HMC6352 compass,
providing configuration and heading data via sysfs.
+config ETZKX_ACCEL
+ tristate "ETZKX kxcnl/kxtnk 3d digital accelerometer"
+ depends on I2C && INPUT
+ default n
+ help
+ This driver provides support for the Kionix kxtnk/kxcnl three-axis
+ accelerometer, which supports programmable state machines for
+ gesture recognition
+
+ To compile this driver as a module, say M here: the module will be
+ called etzkx.ko. If unsure, say N here
+
config EP93XX_PWM
tristate "EP93xx PWM support"
depends on ARCH_EP93XX
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index c235d5b..72271eb 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -33,6 +33,7 @@ obj-$(CONFIG_APDS9802ALS) += apds9802als.o
obj-$(CONFIG_ISL29003) += isl29003.o
obj-$(CONFIG_ISL29020) += isl29020.o
obj-$(CONFIG_SENSORS_TSL2550) += tsl2550.o
+obj-$(CONFIG_ETZKX_ACCEL) += etzkx.o
obj-$(CONFIG_EP93XX_PWM) += ep93xx_pwm.o
obj-$(CONFIG_DS1682) += ds1682.o
obj-$(CONFIG_TI_DAC7512) += ti_dac7512.o
diff --git a/drivers/misc/etzkx.c b/drivers/misc/etzkx.c
new file mode 100644
index 0000000..27930b1
--- /dev/null
+++ b/drivers/misc/etzkx.c
@@ -0,0 +1,2313 @@
+/*
+ * etzkx: lisn3dsh/kxtnk 3d accelerometer driver
+ *
+ * Copyright(C) 2013 Andi Shyti <andi@...zian.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/workqueue.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/input.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/cdev.h>
+#include <linux/poll.h>
+#include <linux/i2c/etzkx.h>
+
+/* register map */
+#define ETZKX_REG_INFO1 0x0D
+#define ETZKX_REG_INFO2 0x0E
+#define ETZKX_REG_WIA 0x0F
+#define ETZKX_REG_OUTX_L 0x10
+#define ETZKX_REG_OUTX_H 0x11
+#define ETZKX_REG_OUTY_L 0x12
+#define ETZKX_REG_OUTY_H 0x13
+#define ETZKX_REG_OUTZ_L 0x14
+#define ETZKX_REG_OUTZ_H 0x15
+#define ETZKX_REG_LC_L 0x16
+#define ETZKX_REG_LC_H 0x17
+#define ETZKX_REG_STAT 0x18
+#define ETZKX_REG_PEAK1 0x19
+#define ETZKX_REG_PEAK2 0x1A
+#define ETZKX_REG_CNTL1 0x1B
+#define ETZKX_REG_CNTL2 0x1C
+#define ETZKX_REG_CNTL3 0x1D
+#define ETZKX_REG_CNTL4 0x1E
+#define ETZKX_REG_THRS3 0x1F
+#define ETZKX_REG_OFF_X 0x20
+#define ETZKX_REG_OFF_Y 0x21
+#define ETZKX_REG_OFF_Z 0x22
+#define ETZKX_REG_CS_X 0x24
+#define ETZKX_REG_CS_Y 0x25
+#define ETZKX_REG_CS_Z 0x26
+#define ETZKX_REG_X_DEBUG 0x28
+#define ETZKX_REG_Y_DEBUG 0x29
+#define ETZKX_REG_Z_DEBUG 0x2A
+#define ETZKX_REG_VFC_1 0x2C
+#define ETZKX_REG_VFC_2 0x2D
+#define ETZKX_REG_VFC_3 0x2E
+#define ETZKX_REG_VFC_4 0x2F
+
+#define ETZKX_REG_ST1_1 0x40
+#define ETZKX_REG_ST2_1 0x41
+#define ETZKX_REG_ST3_1 0x42
+#define ETZKX_REG_ST4_1 0x43
+#define ETZKX_REG_ST5_1 0x44
+#define ETZKX_REG_ST6_1 0x45
+#define ETZKX_REG_ST7_1 0x46
+#define ETZKX_REG_ST8_1 0x47
+#define ETZKX_REG_ST9_1 0x48
+#define ETZKX_REG_ST10_1 0x49
+#define ETZKX_REG_ST11_1 0x4A
+#define ETZKX_REG_ST12_1 0x4B
+#define ETZKX_REG_ST13_1 0x4C
+#define ETZKX_REG_ST14_1 0x4D
+#define ETZKX_REG_ST15_1 0x4E
+#define ETZKX_REG_ST16_1 0x4F
+#define ETZKX_REG_TIM4_1 0x50
+#define ETZKX_REG_TIM3_1 0x51
+#define ETZKX_REG_TIM2_L_1 0x52
+#define ETZKX_REG_TIM2_H_1 0x53
+#define ETZKX_REG_TIM1_L_1 0x54
+#define ETZKX_REG_TIM1_H_1 0x55
+#define ETZKX_REG_THRS2_1 0x56
+#define ETZKX_REG_THRS1_1 0x57
+#define ETZKX_REG_SA_1 0x59
+#define ETZKX_REG_MA_1 0x5A
+#define ETZKX_REG_SETT_1 0x5B
+#define ETZKX_REG_PR_1 0x5C
+#define ETZKX_REG_TC_L_1 0x5D
+#define ETZKX_REG_TC_H_1 0x5E
+#define ETZKX_REG_OUTS_1 0x5F
+
+#define ETZKX_REG_ST1_2 0x60
+#define ETZKX_REG_ST2_2 0x61
+#define ETZKX_REG_ST3_2 0x62
+#define ETZKX_REG_ST4_2 0x63
+#define ETZKX_REG_ST5_2 0x64
+#define ETZKX_REG_ST6_2 0x65
+#define ETZKX_REG_ST7_2 0x66
+#define ETZKX_REG_ST8_2 0x67
+#define ETZKX_REG_ST9_2 0x68
+#define ETZKX_REG_ST10_2 0x69
+#define ETZKX_REG_ST11_2 0x6A
+#define ETZKX_REG_ST12_2 0x6B
+#define ETZKX_REG_ST13_2 0x6C
+#define ETZKX_REG_ST14_2 0x6D
+#define ETZKX_REG_ST15_2 0x6E
+#define ETZKX_REG_ST16_2 0x6F
+#define ETZKX_REG_TIM4_2 0x70
+#define ETZKX_REG_TIM3_2 0x71
+#define ETZKX_REG_TIM2_L_2 0x72
+#define ETZKX_REG_TIM2_H_2 0x73
+#define ETZKX_REG_TIM1_L_2 0x74
+#define ETZKX_REG_TIM1_H_2 0x75
+#define ETZKX_REG_THRS2_2 0x76
+#define ETZKX_REG_THRS1_2 0x77
+#define ETZKX_REG_DES_2 0x78
+#define ETZKX_REG_SA_2 0x79
+#define ETZKX_REG_MA_2 0x7A
+#define ETZKX_REG_SETT_2 0x7B
+#define ETZKX_REG_PR_2 0x7C
+#define ETZKX_REG_TC_L_2 0x7D
+#define ETZKX_REG_TC_H_2 0x7E
+#define ETZKX_REG_OUTS_2 0x7F
+
+/* registers mask */
+/* CNTL1 masks */
+#define ETZKX_CNTL1_IEN_MASK 1
+#define ETZKX_CNTL1_ODR_MASK (7 << 2)
+#define ETZKX_CNTL1_G_MASK (3 << 5)
+#define ETZKX_CNTL1_PC_MASK (1 << 7)
+
+/* CNTL2/CNTL3 masks */
+#define ETZKX_CNTLX_SMX_EN_MASK 1
+#define ETZKX_CNTLX_SMX_PIN_MASK (1 << 3)
+
+/* CNTL4 masks */
+#define ETZKX_CNTL4_STRT_MASK 1
+#define ETZKX_CNTL4_STP_MASK (1 << 1)
+#define ETZKX_CNTL4_VFILT_MASK (1 << 2)
+#define ETZKX_CNTL4_INT1_EN_MASK (1 << 3)
+#define ETZKX_CNTL4_INT2_EN_MASK (1 << 4)
+#define ETZKX_CNTL4_IEL_MASK (1 << 5)
+#define ETZKX_CNTL4_IEA_MASK (1 << 6)
+#define ETZKX_CNTL4_DR_EN_MASK (1 << 7)
+
+/* SETT_X masks */
+#define ETZKX_SETT_D_CS_MASK (1 << 3)
+#define ETZKX_SETT_RADI_MASK (1 << 4)
+
+/* ODR masks */
+#define ETZKX_ODR_3_125_MASK (ETZKX_ODR_3_125 << 2)
+#define ETZKX_ODR_6_25_MASK (ETZKX_ODR_6_25 << 2)
+#define ETZKX_ODR_12_5_MASK (ETZKX_ODR_12_5 << 2)
+#define ETZKX_ODR_25_MASK (ETZKX_ODR_25 << 2)
+#define ETZKX_ODR_50_MASK (ETZKX_ODR_50 << 2)
+#define ETZKX_ODR_100_MASK (ETZKX_ODR_100 << 2)
+#define ETZKX_ODR_400_MASK (ETZKX_ODR_400 << 2)
+#define ETZKX_ODR_1600_MASK (ETZKX_ODR_1600 << 2)
+
+/* etzkx driver states */
+#define ETZKX_STATE_POWER_OFF 0
+#define ETZKX_STATE_STDBY 1
+#define ETZKX_STATE_ACTIVE (1 << 1)
+#define ETZKX_STATE_STRM (1 << 2)
+#define ETZKX_STATE_STM_1 (1 << 3)
+#define ETZKX_STATE_STM_2 (1 << 4)
+#define ETZKX_STATE_DRDY (1 << 5)
+#define ETZKX_STATE_SELF_TEST (1 << 6)
+#define ETZKX_STATE_STRM_STM1 (ETZKX_STATE_STRM | ETZKX_STATE_STM_1)
+#define ETZKX_STATE_STRM_STM2 (ETZKX_STATE_STRM | ETZKX_STATE_STM_2)
+#define ETZKX_STATE_STM1_STM2 (ETZKX_STATE_STM_1 | ETZKX_STATE_STM_2)
+#define ETZKX_STATE_STRM_STM1_STM2 (ETZKX_STATE_STRM | \
+ ETZKX_STATE_STM_1 | \
+ ETZKX_STATE_STM_2)
+#define ETZKX_STATE_DRDY_STM2 (ETZKX_STATE_DRDY | ETZKX_STATE_STM_2)
+
+#define ETZKX_MAX_POLL_RATE 65535
+
+/* State machine definitions */
+#define ETZKX_STM_MAX_STEP 16
+#define ETZKX_STM_LEN 28
+#define ETZKX_NO_STM_RUNNING 0xFF
+#define ETZKX_STM_REG_GAP (ETZKX_REG_ST1_2 - ETZKX_REG_ST1_1)
+
+/* algo selection algorithm */
+#define ETZKX_STM_MATCH_ID 1
+#define ETZKX_STM_MATCH_ODR 2
+#define ETZKX_STM_MATCH_RANGE 4
+#define ETZKX_STM_MATCH_OK (ETZKX_STM_MATCH_ID | \
+ ETZKX_STM_MATCH_ODR | \
+ ETZKX_STM_MATCH_RANGE)
+
+/* Instructions set: Next/Reset conditions */
+#define ETZKX_NOP 0x0
+#define ETZKX_TI1 0x1
+#define ETZKX_TI2 0x2
+#define ETZKX_TI3 0x3
+#define ETZKX_TI4 0x4
+#define ETZKX_GNTH1 0x5
+#define ETZKX_GNTH2 0x6
+#define ETZKX_LNTH1 0x7
+#define ETZKX_LNTH2 0x8
+#define ETZKX_GTTH1 0x9
+#define ETZKX_LLTH2 0xA
+#define ETZKX_GRTH1 0xB
+#define ETZKX_LRTH1 0xC
+#define ETZKX_GRTH2 0xD
+#define ETZKX_LRTH2 0xE
+#define ETZKX_NZERO 0xF
+/* Instruction set: Commands */
+#define ETZKX_STOP 0x00
+#define ETZKX_CONT 0x11
+#define ETZKX_JMP 0x22
+#define ETZKX_SRP 0x33
+#define ETZKX_CRP 0x44
+#define ETZKX_SETP 0x55
+#define ETZKX_SETS1 0x66
+#define ETZKX_STHR1 0x77
+#define ETZKX_OUTC 0x88
+#define ETZKX_OUTW 0x99
+#define ETZKX_STHR2 0xAA
+#define ETZKX_DEC 0xBB
+#define ETZKX_SISW 0xCC
+#define ETZKX_REL 0xDD
+#define ETZKX_STHR3 0xEE
+#define ETZKX_SSYNC 0xFF
+
+/* specific state machine definitions */
+/* orientation algorithm */
+#define ETZKX_ORIENTATION_PORTRAIT 0x20
+#define ETZKX_ORIENTATION_LANDSCAPE 0x80
+
+/* double tap algorithm */
+#define ETZKX_DOUBLE_TAP_PLUS_X 0x40
+#define ETZKX_DOUBLE_TAP_MINUS_X 0x80
+#define ETZKX_DOUBLE_TAP_PLUS_Y 0x10
+#define ETZKX_DOUBLE_TAP_MINUS_Y 0x20
+#define ETZKX_DOUBLE_TAP_PLUS_Z 0x04
+#define ETZKX_DOUBLE_TAP_MINUS_Z 0x08
+
+/* accelerometer thresholds for landscape detection */
+#define ETZKX_ORIENTATION_LIMIT 150
+#define ETZKX_MAX_RANGE 8
+
+/* accelerometer dimension x, y, z, v */
+#define ETZKX_DIMENSION 8
+
+#define ETZKX_ALGO_DES_IDX 24
+#define ETZKX_ALGO_MASK_IDX 25
+#define ETZKX_ALGO_SETT_IDX 27
+
+#define etzkx_load_stm1(s, i) __etzkx_load_stm(s, i, 0)
+#define etzkx_load_stm2(s, i) __etzkx_load_stm(s, i, \
+ ETZKX_STM_REG_GAP)
+#define etzkx_enable_stm1(s) __etzkx_enable_stm(s, ETZKX_REG_CNTL2)
+#define etzkx_enable_stm2(s) __etzkx_enable_stm(s, ETZKX_REG_CNTL3)
+#define etzkx_disable_stm1(s) __etzkx_disable_stm(s, ETZKX_REG_CNTL2)
+#define etzkx_disable_stm2(s) __etzkx_disable_stm(s, ETZKX_REG_CNTL3)
+#define etzkx_switch_on_vfilter(s, i) __etzkx_switch_vfilter(s, i, 1)
+#define etzkx_switch_off_vfilter(s, i) __etzkx_switch_vfilter(s, i, 0)
+
+/* conditions for an algorithm to be loaded in state machine 2 */
+#define ETZKX_ALGO_STM2(s, o) ((s[ETZKX_ALGO_SETT_IDX] & \
+ ETZKX_SETT_RADI_MASK) || \
+ ((o == 1600) && (s[ETZKX_ALGO_DES_IDX])))
+
+/* WAI register values */
+#define ETZKX_WIA_LISN3DSH 0x3B
+#define ETZKX_WIA_KXTNK 0x6E
+#define ETZKX_WIA_KXCNL 0x0F
+
+/* ETZKX accelerometer type names */
+#define ETZKX_LISN3DSH_NAME "lisn3dsh"
+#define ETZKX_KXTNK_NAME "kxtnk-1000"
+
+#define ETZKX_CHARDEV_NAME "etzkx_stm"
+
+struct etzkx_data {
+ struct i2c_client *client;
+ struct etzkx_platform_data *pdata;
+ struct mutex mutex;
+
+ struct input_dev *input_dev;
+ struct delayed_work poll_read_work;
+
+ u8 wai;
+ u8 hw_version;
+ u8 drv_state;
+
+ u16 poll_rate;
+ u16 odr;
+ u8 range;
+
+ u8 stm1;
+ u8 stm2;
+ u8 range_back;
+ u8 mask_matrix[ETZKX_DIMENSION];
+
+ struct cdev cdev;
+ struct etzkx_stm_data running_stm;
+ wait_queue_head_t wq;
+};
+
+struct etzkx_algo_data {
+ u8 stm_id;
+ u8 stm[ETZKX_STM_LEN];
+ u8 thrs3;
+ u8 v[4];
+ u8 odr;
+ u8 range;
+};
+
+static const struct etzkx_algo_data etzkx_algos[] = {
+ { ETZKX_STM_ID_TIMING,
+ { ETZKX_NOP << 4 | ETZKX_TI3, /* 0 */
+ ETZKX_NOP << 4 | ETZKX_TI3, /* 1 */
+ ETZKX_NOP << 4 | ETZKX_TI3, /* 2 */
+ ETZKX_NOP << 4 | ETZKX_TI3, /* 3 */
+ ETZKX_NOP << 4 | ETZKX_TI3, /* 4 */
+ ETZKX_NOP << 4 | ETZKX_TI3, /* 5 */
+ ETZKX_NOP << 4 | ETZKX_TI3, /* 6 */
+ ETZKX_NOP << 4 | ETZKX_TI3, /* 7 */
+ ETZKX_NOP << 4 | ETZKX_TI3, /* 8 */
+ ETZKX_NOP << 4 | ETZKX_TI3, /* 9 */
+ ETZKX_NOP << 4 | ETZKX_TI3, /* 10 */
+ ETZKX_NOP << 4 | ETZKX_TI3, /* 11 */
+ ETZKX_NOP << 4 | ETZKX_TI3, /* 12 */
+ ETZKX_NOP << 4 | ETZKX_TI3, /* 13 */
+ ETZKX_NOP << 4 | ETZKX_TI3, /* 14 */
+ ETZKX_CONT, /* 15 */
+ 0x00, /* TIM4 */
+ 0x02, /* TIM3 */
+ 0x00, /* TIM2 high */
+ 0x00, /* TIM2 low */
+ 0x00, /* TIM1 high */
+ 0x00, /* TIM1 low */
+ 0x00, /* THRS2 */
+ 0x00, /* THRS1 */
+ 0x00, /* DES */
+ 0x00, /* mask 2 */
+ 0x00, /* mask 1 */
+ 0x01, /* settings */
+ },
+ 0x00, /* THRS3 */
+ {0x00, 0x00, 0x00, 0x00}, /* V filter */
+ ETZKX_ODR_DONT_CARE,
+ ETZKX_G_RANGE_DONT_CARE,
+ },
+ { /* orientation, 1600Hz, 4g */
+ ETZKX_STM_ID_ORIENTATION,
+ { ETZKX_NOP << 4 | ETZKX_GNTH1, /* 0 */
+ ETZKX_TI2 << 4 | ETZKX_TI4, /* 1 */
+ ETZKX_TI1 << 4 | ETZKX_LNTH1, /* 2 */
+ ETZKX_GNTH1 << 4 | ETZKX_TI2, /* 3 */
+ ETZKX_TI1 << 4 | ETZKX_TI4, /* 4 */
+ ETZKX_LNTH2 << 4 | ETZKX_TI3, /* 5 */
+ ETZKX_LNTH2 << 4 | ETZKX_LNTH2, /* 6 */
+ ETZKX_TI3 << 4 | ETZKX_TI3, /* 7 */
+ ETZKX_TI2 << 4 | ETZKX_TI4, /* 8 */
+ ETZKX_NOP << 4 | ETZKX_GNTH1, /* 9 */
+ ETZKX_TI1 << 4 | ETZKX_TI4, /* 10 */
+ ETZKX_TI1 << 4 | ETZKX_LNTH1, /* 11 */
+ ETZKX_GNTH1 << 4 | ETZKX_TI2, /* 12 */
+ ETZKX_TI2 << 4 | ETZKX_TI4, /* 13 */
+ ETZKX_LNTH2 << 4 | ETZKX_TI3, /* 14 */
+ ETZKX_TI1 << 4 | ETZKX_TI1, /* 15 */
+ 0x00, /* TIM4 */
+ 0xa0, /* TIM3 */
+ 0x78, /* TIM2 high */
+ 0x01, /* TIM2 low */
+ 0xa0, /* TIM1 high */
+ 0x00, /* TIM1 low */
+ 0x10, /* THRS2 */
+ 0x0b, /* THRS1 */
+ 0x00, /* DES */
+ 0x20, /* mask 2 */
+ 0x80, /* mask 1 */
+ 0x21, /* settings */
+ },
+ 0x00, /* THRS3 */
+ {0x00, 0x00, 0x00, 0x00}, /* V filter */
+ ETZKX_ODR_1600,
+ ETZKX_G_RANGE_4G,
+ },
+ { /* double tap, 1600Hz, 4g */
+ ETZKX_STM_ID_DOUBLE_TAP,
+ { ETZKX_GNTH1 << 4 | ETZKX_TI1, /* 0 */
+ ETZKX_GNTH1 << 4 | ETZKX_TI1, /* 1 */
+ ETZKX_NOP << 4 | ETZKX_GNTH2, /* 2 */
+ ETZKX_TI3 << 4 | ETZKX_LNTH2, /* 3 */
+ ETZKX_NOP << 4 | ETZKX_TI4, /* 4 */
+ ETZKX_GTTH1 << 4 | ETZKX_TI1, /* 5 */
+ ETZKX_TI2 << 4 | ETZKX_GNTH2, /* 6 */
+ ETZKX_TI3 << 4 | ETZKX_LNTH2, /* 7 */
+ ETZKX_NOP << 4 | ETZKX_TI4, /* 8 */
+ ETZKX_GTTH1 << 4 | ETZKX_TI1, /* 9 */
+ ETZKX_TI1 << 4 | ETZKX_TI1, /* 10 */
+ ETZKX_NOP << 4 | ETZKX_NOP, /* 11 */
+ ETZKX_NOP << 4 | ETZKX_NOP, /* 12 */
+ ETZKX_NOP << 4 | ETZKX_NOP, /* 13 */
+ ETZKX_NOP << 4 | ETZKX_NOP, /* 14 */
+ ETZKX_NOP << 4 | ETZKX_NOP, /* 15 */
+ 0x20, /* TIM4 */
+ 0x0d, /* TIM3 */
+ 0x20, /* TIM2 high */
+ 0x03, /* TIM2 low */
+ 0x70, /* TIM1 high */
+ 0x00, /* TIM1 low */
+ 0x44, /* THRS2 */
+ 0x3a, /* THRS1 */
+ 0x00, /* DES */
+ 0x00, /* mask 2 */
+ 0xfc, /* mask 1 */
+ 0xa1, /* settings */
+ },
+ 0x00, /* THRS3 */
+ {0x00, 0x00, 0x00, 0x00}, /* V filter */
+ ETZKX_ODR_1600,
+ ETZKX_G_RANGE_4G,
+ },
+ { /* double tap, 1600Hz, 4g */
+ ETZKX_STM_ID_WAKEUP,
+ {
+ ETZKX_NOP << 4 | ETZKX_TI3, /* 0 */
+ ETZKX_GNTH1 << 4 | ETZKX_TI1, /* 1 */
+ ETZKX_NOP << 4 | ETZKX_OUTC, /* 2 */
+ ETZKX_NOP << 4 | ETZKX_GNTH1, /* 3 */
+ ETZKX_CONT, /* 4 */
+ ETZKX_NOP, /* 5 */
+ ETZKX_NOP, /* 6 */
+ ETZKX_NOP, /* 7 */
+ ETZKX_NOP, /* 8 */
+ ETZKX_NOP, /* 9 */
+ ETZKX_NOP, /* 10 */
+ ETZKX_NOP, /* 11 */
+ ETZKX_NOP, /* 12 */
+ ETZKX_NOP, /* 13 */
+ ETZKX_NOP, /* 14 */
+ ETZKX_NOP, /* 15 */
+ 0x00, /* TIM4 */
+ 0x00, /* TIM3 */
+ 0x00, /* TIM2 high */
+ 0x00, /* TIM2 low */
+ 0x00, /* TIM1 high */
+ 0x10, /* TIM1 low */
+ 0x00, /* THRS2 */
+ 0x03, /* THRS1 */
+ 0x00, /* DES */
+ 0x00, /* mask 2 */
+ 0xFC, /* mask 1 */
+ 0x31, /* settings */
+ },
+ 0x00, /* THRS3 */
+ {0x00, 0x00, 0x00, 0x00}, /* V filter */
+ ETZKX_ODR_1600,
+ ETZKX_G_RANGE_4G,
+ },
+ { /* double tap based on V filter */
+ ETZKX_STM_ID_V_DOUBLE_TAP,
+ {
+ ETZKX_GNTH1 << 4 | ETZKX_TI1, /* 0 */
+ ETZKX_GNTH1 << 4 | ETZKX_TI1, /* 1 */
+ ETZKX_NOP << 4 | ETZKX_GNTH2, /* 2 */
+ ETZKX_TI3 << 4 | ETZKX_LNTH2, /* 3 */
+ ETZKX_NOP << 4 | ETZKX_TI4, /* 4 */
+ ETZKX_GTTH1 << 4 | ETZKX_TI1, /* 5 */
+ ETZKX_TI2 << 4 | ETZKX_GNTH2, /* 6 */
+ ETZKX_TI3 << 4 | ETZKX_LNTH2, /* 7 */
+ ETZKX_NOP << 4 | ETZKX_TI4, /* 8 */
+ ETZKX_GTTH1 << 4 | ETZKX_TI1, /* 9 */
+ ETZKX_TI1 << 4 | ETZKX_TI1, /* 10 */
+ ETZKX_NOP, /* 11 */
+ ETZKX_NOP, /* 12 */
+ ETZKX_NOP, /* 13 */
+ ETZKX_NOP, /* 14 */
+ ETZKX_NOP, /* 15 */
+ 0x98, /* TIM4 */
+ 0x0d, /* TIM3 */
+ 0x20, /* TIM2 high */
+ 0x03, /* TIM2 low */
+ 0x70, /* TIM1 high */
+ 0x00, /* TIM1 low */
+ 0x05, /* THRS2 */
+ 0x03, /* THRS1 */
+ 0x00, /* DES */
+ 0xf0, /* mask 2 */
+ 0x03, /* mask 1 */
+ 0xe1, /* settings */
+ },
+ 0x46, /* THRS3 */
+ {0x09, 0x27, 0x57, 0x7f}, /* V filter */
+ ETZKX_ODR_1600,
+ ETZKX_G_RANGE_4G,
+ },
+};
+
+/* driver state prototypes */
+static int etzkx_state_go_stdby(struct etzkx_data *);
+static int etzkx_state_go_active(struct etzkx_data *);
+static int etzkx_state_enable_streaming(struct etzkx_data *);
+static int etzkx_state_disable_streaming(struct etzkx_data *);
+static int etzkx_state_enable_st(struct etzkx_data *);
+static int etzkx_state_disable_st(struct etzkx_data *);
+static int etzkx_state_enable_stm1(struct etzkx_data *, u8, u8);
+static int etzkx_state_enable_stm2(struct etzkx_data *, u8, u8);
+static int etzkx_state_disable_stm1(struct etzkx_data *, u8);
+static int etzkx_state_disable_stm2(struct etzkx_data *, u8);
+
+static const u16 etzkx_rate_ms[] = {320, 160, 80, 40, 20, 10, 2, 0};
+static const u16 etzkx_rate_hz[] = {3, 6, 12, 25, 50, 100, 400, 1600};
+static const char * const etzkx_rate_str[] = {"3.125", "6.25", "12.5", "25",
+ "50", "100", "400", "1600"};
+static dev_t etzkx_dev_number;
+static struct class *etzkx_class;
+
+/* get mutex lock before calling set_odr and set_range */
+static int etzkx_set_odr(struct etzkx_data *sdata, u16 new_odr)
+{
+ u8 odr_mask;
+ s32 cntl1_reg;
+
+ if (sdata->odr == new_odr)
+ return 0;
+
+ if ((new_odr < ETZKX_ODR_3_125) || (new_odr > ETZKX_ODR_1600))
+ return -EINVAL;
+
+ odr_mask = new_odr << 2;
+
+ cntl1_reg = i2c_smbus_read_byte_data(sdata->client, ETZKX_REG_CNTL1);
+ if (cntl1_reg < 0)
+ return -EIO;
+ cntl1_reg = (cntl1_reg & ~ETZKX_CNTL1_ODR_MASK) | odr_mask;
+ cntl1_reg = i2c_smbus_write_byte_data(sdata->client,
+ ETZKX_REG_CNTL1, cntl1_reg);
+ if (cntl1_reg < 0)
+ return -EIO;
+
+ sdata->odr = new_odr;
+
+ return 0;
+}
+
+static int etzkx_set_range(struct etzkx_data *sdata, u8 new_range)
+{
+ u8 new_mask_range;
+ s32 cntl1_val;
+
+ if ((new_range != ETZKX_G_RANGE_2G) &&
+ (new_range != ETZKX_G_RANGE_4G) &&
+ (new_range != ETZKX_G_RANGE_6G) &&
+ (new_range != ETZKX_G_RANGE_8G))
+ return -EINVAL;
+
+ cntl1_val = i2c_smbus_read_byte_data(sdata->client, ETZKX_REG_CNTL1);
+ if (cntl1_val < 0)
+ return cntl1_val;
+ cntl1_val &= ~ETZKX_CNTL1_G_MASK;
+ new_mask_range = (new_range / 2 - 1) << 5;
+
+ cntl1_val = i2c_smbus_write_byte_data(sdata->client, ETZKX_REG_CNTL1,
+ new_mask_range | cntl1_val);
+ if (cntl1_val < 0)
+ return -EIO;
+
+ sdata->range = new_range;
+
+ return 0;
+}
+
+/* get mutex lock before calling state functions */
+static int etzkx_state_go_stdby(struct etzkx_data *sdata)
+{
+ s32 cntl1_reg;
+
+ cntl1_reg = i2c_smbus_read_byte_data(sdata->client, ETZKX_REG_CNTL1);
+ if (cntl1_reg < 0)
+ return cntl1_reg;
+ cntl1_reg &= ~ETZKX_CNTL1_PC_MASK;
+ cntl1_reg = i2c_smbus_write_byte_data(sdata->client, ETZKX_REG_CNTL1,
+ cntl1_reg);
+ if (cntl1_reg < 0)
+ return cntl1_reg;
+
+ usleep_range(500, 700);
+ sdata->drv_state = ETZKX_STATE_STDBY;
+ sdata->stm1 = ETZKX_NO_STM_RUNNING;
+ sdata->stm2 = ETZKX_NO_STM_RUNNING;
+
+ return 0;
+}
+
+static int etzkx_state_go_active(struct etzkx_data *sdata)
+{
+ s32 reg;
+
+ switch (sdata->drv_state) {
+ case ETZKX_STATE_STDBY:
+ reg = i2c_smbus_read_byte_data(sdata->client, ETZKX_REG_CNTL1);
+ reg &= ETZKX_CNTL1_G_MASK | ETZKX_CNTL1_ODR_MASK;
+ reg |= ETZKX_CNTL1_PC_MASK | sdata->odr << 2 |
+ (sdata->range / 2 - 1) << 5;
+ reg = i2c_smbus_write_byte_data(sdata->client,
+ ETZKX_REG_CNTL1, reg);
+ if (reg)
+ return reg;
+
+ usleep_range(500, 600);
+ sdata->drv_state = ETZKX_STATE_ACTIVE;
+
+ case ETZKX_STATE_SELF_TEST:
+ etzkx_state_disable_st(sdata);
+ case ETZKX_STATE_STRM:
+ etzkx_state_disable_streaming(sdata);
+ sdata->drv_state = ETZKX_STATE_ACTIVE;
+ return 0;
+ case ETZKX_STATE_STM_1:
+ return etzkx_state_disable_stm1(sdata,
+ etzkx_algos[sdata->stm1].stm_id);
+ case ETZKX_STATE_STM_2:
+ return etzkx_state_disable_stm2(sdata,
+ etzkx_algos[sdata->stm2].stm_id);
+ }
+ return -EPERM;
+}
+
+static int etzkx_state_enable_streaming(struct etzkx_data *sdata)
+{
+ int err;
+
+ if (sdata->drv_state & ETZKX_STATE_STRM)
+ return 0;
+
+ switch (sdata->drv_state) {
+ case ETZKX_STATE_STDBY:
+ err = etzkx_state_go_active(sdata);
+ if (err)
+ return err;
+ case ETZKX_STATE_ACTIVE:
+ if (sdata->odr > ETZKX_ODR_100) {
+ err = etzkx_set_odr(sdata, ETZKX_ODR_100);
+ sdata->poll_rate = etzkx_rate_ms[ETZKX_ODR_100];
+ }
+ schedule_delayed_work(&sdata->poll_read_work,
+ msecs_to_jiffies(sdata->poll_rate));
+ sdata->drv_state = ETZKX_STATE_STRM;
+ return 0;
+ case ETZKX_STATE_SELF_TEST:
+ err = etzkx_state_disable_st(sdata);
+ return err ? err : 0;
+ case ETZKX_STATE_STM_1:
+ case ETZKX_STATE_STM_2:
+ case ETZKX_STATE_STM1_STM2:
+ schedule_delayed_work(&sdata->poll_read_work,
+ msecs_to_jiffies(sdata->poll_rate));
+ sdata->drv_state |= ETZKX_STATE_STRM;
+ return 0;
+ }
+ return -EPERM;
+}
+
+static int etzkx_state_disable_streaming(struct etzkx_data *sdata)
+{
+ int err;
+
+ switch (sdata->drv_state) {
+ case ETZKX_STATE_POWER_OFF:
+ case ETZKX_STATE_STDBY:
+ case ETZKX_STATE_ACTIVE:
+ case ETZKX_STATE_STM_1:
+ case ETZKX_STATE_STM_2:
+ case ETZKX_STATE_STM1_STM2:
+ return 0;
+ case ETZKX_STATE_SELF_TEST:
+ err = etzkx_state_disable_st(sdata);
+ if (err)
+ return err;
+ case ETZKX_STATE_STRM:
+ case ETZKX_STATE_STRM_STM1:
+ case ETZKX_STATE_STRM_STM2:
+ case ETZKX_STATE_STRM_STM1_STM2:
+ cancel_delayed_work_sync(&sdata->poll_read_work);
+ sdata->drv_state = (sdata->drv_state == ETZKX_STATE_STRM) ?
+ ETZKX_STATE_ACTIVE :
+ (sdata->drv_state & ~ETZKX_STATE_STRM);
+ return 0;
+ }
+
+ return -EPERM;
+}
+
+static int etzkx_state_enable_st(struct etzkx_data *sdata)
+{
+ int reg;
+
+ switch (sdata->drv_state) {
+ case ETZKX_STATE_STRM:
+ case ETZKX_STATE_POWER_OFF:
+ case ETZKX_STATE_STDBY:
+ reg = etzkx_state_go_active(sdata);
+ if (reg)
+ return reg;
+ case ETZKX_STATE_ACTIVE:
+ if (sdata->drv_state == ETZKX_STATE_STRM)
+ cancel_delayed_work_sync(&sdata->poll_read_work);
+
+ if (sdata->odr > ETZKX_ODR_100)
+ return -EPERM;
+
+ reg = i2c_smbus_read_byte_data(sdata->client, ETZKX_REG_CNTL4);
+ if (reg < 0)
+ return reg;
+
+ reg |= ETZKX_CNTL4_STP_MASK;
+ reg = i2c_smbus_write_byte_data(sdata->client,
+ ETZKX_REG_CNTL4, reg);
+ if (reg < 0)
+ return reg;
+ /*
+ * schedule the polling 4 odr later, due to some
+ * hardware limitations
+ */
+ schedule_delayed_work(&sdata->poll_read_work,
+ 4*msecs_to_jiffies(etzkx_rate_ms[sdata->odr]));
+ sdata->drv_state = ETZKX_STATE_SELF_TEST;
+
+ return 0;
+ }
+ return -EPERM;
+}
+
+static int etzkx_state_disable_st(struct etzkx_data *sdata)
+{
+ int reg;
+
+ if (sdata->drv_state != ETZKX_STATE_SELF_TEST)
+ return 0;
+
+ reg = i2c_smbus_read_byte_data(sdata->client, ETZKX_REG_CNTL4);
+ if (reg < 0)
+ return reg;
+
+ reg &= ~ETZKX_CNTL4_STP_MASK;
+ reg = i2c_smbus_write_byte_data(sdata->client, ETZKX_REG_CNTL4, reg);
+ if (reg < 0)
+ return 0;
+
+ sdata->drv_state = ETZKX_STATE_STRM;
+
+ return 0;
+}
+
+/*
+ * etzkx_select_stm - selects the desired state machine
+ * @sdata: running odr and g range value
+ * @stm_id: desired state machine
+ * @match_bitops: pointer to the bit operations that has the following meaning:
+ *
+ * 000: no stm found
+ * 001: g range and odr must be changed
+ * 011: g range must be changed
+ * 101: odr must be changed
+ * 111: the state machine can be applied without any change
+ *
+ * The return value is the index of the algo from the global algorithm vector.
+ * Lower than 0 if an error occured.
+ *
+ * The matchings have a different relevance that are weighed:
+ * 3: algo id (redundant)
+ * 2: odr
+ * 1: range
+ */
+static u8 etzkx_select_stm(struct etzkx_data *sdata,
+ int stm_id, u8 *match_bitops)
+{
+ u8 i, algo_idx;
+ u8 match, final_match;
+
+ for (i = 0, match = 0, *match_bitops = 0, algo_idx = 0, final_match = 0;
+ (i < ARRAY_SIZE(etzkx_algos)) &&
+ (*match_bitops != ETZKX_STM_MATCH_OK);
+ i++, match = 0) {
+
+ if (etzkx_algos[i].stm_id != stm_id)
+ continue;
+
+ match += 3;
+ if (match > final_match) {
+ *match_bitops |= ETZKX_STM_MATCH_ID;
+ algo_idx = i;
+ final_match = match;
+ }
+
+ if ((etzkx_algos[i].odr == sdata->odr) ||
+ (etzkx_algos[i].odr == ETZKX_ODR_DONT_CARE) ||
+ ((sdata->odr == etzkx_rate_hz[ETZKX_ODR_1600]) &&
+ (etzkx_algos[i].stm[ETZKX_ALGO_DES_IDX]))) {
+
+ match += 2;
+ if (match > final_match) {
+ final_match = match;
+ algo_idx = i;
+ *match_bitops |= ETZKX_STM_MATCH_ODR;
+ }
+ }
+
+ if ((etzkx_algos[i].range == sdata->range) ||
+ (etzkx_algos[i].range ==
+ ETZKX_G_RANGE_DONT_CARE)) {
+ match++;
+ if (match > final_match) {
+ final_match = match;
+ algo_idx = i;
+ *match_bitops |= ETZKX_STM_MATCH_RANGE;
+ }
+ }
+ }
+
+ return algo_idx;
+}
+
+static u8 etzkx_mask_orientation(struct etzkx_data *sdata, u8 val)
+{
+ int i;
+ u8 new_val = 0;
+
+ if (!val)
+ return 0;
+
+ for (i = 0; i < ETZKX_DIMENSION; i++)
+ if (sdata->mask_matrix[i] & val)
+ new_val |= (1 << (ETZKX_DIMENSION - 1 - i));
+
+ return new_val;
+}
+
+static int __etzkx_switch_vfilter(struct etzkx_data *sdata, u8 stm_id, u8 onoff)
+{
+ s32 cntl4, err;
+
+ cntl4 = i2c_smbus_read_byte_data(sdata->client, ETZKX_REG_CNTL4);
+
+ if (onoff) {
+ err = i2c_smbus_write_i2c_block_data(sdata->client,
+ ETZKX_REG_VFC_1, 4,
+ etzkx_algos[stm_id].v);
+ if (err)
+ return err;
+
+ cntl4 |= ETZKX_CNTL4_VFILT_MASK;
+ } else {
+ cntl4 &= ~ETZKX_CNTL4_VFILT_MASK;
+ }
+
+ cntl4 = i2c_smbus_write_byte_data(sdata->client,
+ ETZKX_REG_CNTL4, cntl4);
+
+ return (cntl4 < 0) ? cntl4 : 0;
+}
+
+static int __etzkx_load_stm(struct etzkx_data *sdata, u8 stm_id, u8 offset)
+{
+ s32 err;
+ u8 mask[2];
+ u8 idx;
+
+ idx = !offset ? sdata->stm1 : sdata->stm2;
+
+ if (etzkx_algos[stm_id].thrs3 &&
+ (idx == ETZKX_NO_STM_RUNNING || !etzkx_algos[idx].thrs3)) {
+
+ err = i2c_smbus_write_byte_data(sdata->client, ETZKX_REG_THRS3,
+ etzkx_algos[stm_id].thrs3);
+ if (err)
+ return err;
+ }
+
+ if (*((u32 *) etzkx_algos[stm_id].v) &&
+ (idx == ETZKX_NO_STM_RUNNING ||
+ (*((u32 *) etzkx_algos[idx].v))))
+
+ etzkx_switch_on_vfilter(sdata, stm_id);
+
+ err = i2c_smbus_write_i2c_block_data(sdata->client,
+ ETZKX_REG_ST1_1 + offset,
+ ETZKX_STM_LEN,
+ etzkx_algos[stm_id].stm);
+ if (err)
+ return err;
+
+
+ mask[0] = etzkx_mask_orientation(sdata,
+ etzkx_algos[stm_id].stm[ETZKX_ALGO_MASK_IDX]);
+ mask[1] = etzkx_mask_orientation(sdata,
+ etzkx_algos[stm_id].stm[ETZKX_ALGO_MASK_IDX+1]);
+
+ err = i2c_smbus_write_i2c_block_data(sdata->client,
+ ETZKX_REG_SA_1 + offset, 2, mask);
+
+ return (err < 0) ? err : 0;
+}
+
+static int __etzkx_enable_stm(struct etzkx_data *sdata, u8 reg)
+{
+ int cntl1, cntl4, cntlx;
+
+ cntl1 = i2c_smbus_read_byte_data(sdata->client, ETZKX_REG_CNTL1);
+ if (cntl1 < 0)
+ return cntl1;
+ cntlx = i2c_smbus_read_byte_data(sdata->client, reg);
+ if (cntlx < 0)
+ return cntlx;
+ cntl4 = i2c_smbus_read_byte_data(sdata->client, ETZKX_REG_CNTL4);
+ if (cntl4 < 0)
+ return cntl4;
+
+ cntl1 |= ETZKX_CNTL1_IEN_MASK;
+ cntlx |= ETZKX_CNTLX_SMX_EN_MASK;
+ cntl4 |= ETZKX_CNTL4_IEA_MASK | ETZKX_CNTL4_IEL_MASK; /* pulsed irq */
+
+ if (reg == ETZKX_REG_CNTL2)
+ cntl4 |= ETZKX_CNTL4_INT1_EN_MASK;
+ else if (reg == ETZKX_REG_CNTL3) {
+ cntl4 |= ETZKX_CNTL4_INT2_EN_MASK;
+ cntlx |= ETZKX_CNTLX_SMX_PIN_MASK;
+ } else
+ return -EINVAL;
+
+ cntl1 = i2c_smbus_write_byte_data(sdata->client,
+ ETZKX_REG_CNTL1, cntl1);
+ if (cntl1 < 0)
+ return cntl1;
+ cntl4 = i2c_smbus_write_byte_data(sdata->client,
+ ETZKX_REG_CNTL4, cntl4);
+ if (cntl4 < 0)
+ return cntl4;
+ cntlx = i2c_smbus_write_byte_data(sdata->client, reg, cntlx);
+ if (cntlx < 0)
+ return cntlx;
+
+ return 0;
+}
+
+static int __etzkx_disable_stm(struct etzkx_data *sdata, u8 reg)
+{
+ s32 cntlx;
+
+ cntlx = i2c_smbus_read_byte_data(sdata->client, reg);
+ if (cntlx < 0)
+ return cntlx;
+
+ cntlx &= ~ETZKX_CNTLX_SMX_EN_MASK;
+ cntlx = i2c_smbus_write_byte_data(sdata->client, reg, cntlx);
+
+ return (cntlx) ? cntlx : 0;
+}
+
+static int etzkx_set_stm_params(struct etzkx_data *sdata,
+ u8 match_bitops, u8 new_odr, u8 new_range)
+{
+ int ret;
+ u8 odr_back = sdata->odr;
+ sdata->range_back = sdata->range;
+
+ /*
+ * '0' means that odr/range doesn't match with the running
+ * config, therefore, needs to be changed
+ */
+ if (!(match_bitops & ETZKX_STM_MATCH_ODR)) {
+ ret = etzkx_set_odr(sdata, new_odr);
+ if (ret)
+ return ret;
+ }
+
+ if (!(match_bitops & ETZKX_STM_MATCH_RANGE)) {
+ ret = etzkx_set_range(sdata, new_range);
+ if (ret)
+ goto restore_odr;
+ }
+
+ return 0;
+
+restore_odr:
+ etzkx_set_odr(sdata, odr_back);
+
+ return ret;
+}
+
+static int __etzkx_state_enable_stm(struct etzkx_data *sdata, u8 i, u8 state)
+{
+ int ret;
+
+ switch (state) {
+ case ETZKX_STATE_STM_1:
+ ret = etzkx_load_stm1(sdata, i);
+ return ret ? ret : etzkx_enable_stm1(sdata);
+
+ case ETZKX_STATE_STM_2:
+ ret = etzkx_load_stm2(sdata, i);
+ return ret ? ret : etzkx_enable_stm2(sdata);
+ }
+ return -EINVAL;
+}
+
+static int etzkx_move_stm_2_to_1(struct etzkx_data *sdata)
+{
+ int err;
+
+ err = etzkx_disable_stm2(sdata);
+ if (err)
+ return err;
+
+ err = etzkx_load_stm1(sdata, sdata->stm2);
+ if (err)
+ return err;
+
+ err = etzkx_enable_stm1(sdata);
+ if (err)
+ return err;
+
+ sdata->drv_state &= ~ETZKX_STATE_STM_2;
+ sdata->drv_state |= ETZKX_STATE_STM_1;
+ sdata->stm1 = sdata->stm2;
+ sdata->stm2 = ETZKX_NO_STM_RUNNING;
+
+ return 0;
+}
+
+static int etzkx_move_stm_1_to_2(struct etzkx_data *sdata)
+{
+ int err;
+
+ err = etzkx_disable_stm1(sdata);
+ if (err)
+ return err;
+
+ err = etzkx_load_stm2(sdata, sdata->stm1);
+ if (err)
+ return err;
+
+ err = etzkx_enable_stm2(sdata);
+ if (err)
+ return err;
+
+ sdata->drv_state &= ~ETZKX_STATE_STM_1;
+ sdata->drv_state |= ETZKX_STATE_STM_2;
+ sdata->stm2 = sdata->stm1;
+ sdata->stm1 = ETZKX_NO_STM_RUNNING;
+
+ return 0;
+}
+
+static int etzkx_state_enable_stm1(struct etzkx_data *sdata, u8 stm_id, u8 i)
+{
+ int ret;
+ u8 match_bitops;
+
+ if (!i) {
+ i = etzkx_select_stm(sdata, stm_id, &match_bitops);
+ if (!match_bitops)
+ return 0;
+ }
+
+ switch (sdata->drv_state) {
+ case ETZKX_STATE_STDBY:
+ ret = etzkx_state_go_active(sdata);
+ if (ret)
+ return ret;
+
+ case ETZKX_STATE_ACTIVE:
+ case ETZKX_STATE_STRM:
+ if (match_bitops) {
+ ret = etzkx_set_stm_params(sdata, match_bitops,
+ etzkx_algos[i].odr,
+ etzkx_algos[i].range);
+ if (ret)
+ return ret;
+ } else if ((etzkx_algos[i].odr != sdata->odr) ||
+ (etzkx_algos[i].range != sdata->range))
+ return -EPERM;
+
+ if ((etzkx_algos[i].stm[ETZKX_ALGO_DES_IDX]) ||
+ (etzkx_algos[i].stm[ETZKX_ALGO_SETT_IDX] &
+ ETZKX_SETT_RADI_MASK))
+ return etzkx_state_enable_stm2(sdata, stm_id, i);
+
+ ret = __etzkx_state_enable_stm(sdata, i, ETZKX_STATE_STM_1);
+ if (ret)
+ return ret;
+
+ sdata->drv_state = (sdata->drv_state == ETZKX_STATE_ACTIVE) ?
+ ETZKX_STATE_STM_1 :
+ ETZKX_STATE_STRM_STM1;
+ sdata->stm1 = i;
+ return 0;
+
+ case ETZKX_STATE_STM_2:
+ case ETZKX_STATE_STRM_STM2:
+ if (etzkx_algos[i].range != sdata->range)
+ return -EPERM;
+
+ if (ETZKX_ALGO_STM2(etzkx_algos[i].stm, sdata->odr) &&
+ !ETZKX_ALGO_STM2(etzkx_algos[sdata->stm2].stm,
+ sdata->odr)) {
+ ret = etzkx_move_stm_2_to_1(sdata);
+ return ret ? ret :
+ etzkx_state_enable_stm2(sdata, stm_id, i);
+ } else if (sdata->odr != etzkx_algos[i].odr) {
+ return -EPERM;
+ }
+
+ ret = __etzkx_state_enable_stm(sdata, i, ETZKX_STATE_STM_1);
+ if (ret)
+ return ret;
+
+ sdata->drv_state = (sdata->drv_state == ETZKX_STATE_STM_2) ?
+ ETZKX_STATE_STM1_STM2 :
+ ETZKX_STATE_STRM_STM1_STM2;
+ sdata->stm1 = i;
+ return 0;
+ }
+
+ return -EPERM;
+}
+
+static int etzkx_state_enable_stm2(struct etzkx_data *sdata, u8 stm_id, u8 i)
+{
+ int ret;
+ u8 match_bitops;
+
+ if (!i) {
+ i = etzkx_select_stm(sdata, stm_id, &match_bitops);
+ if (!match_bitops)
+ return 0;
+ }
+
+ switch (sdata->drv_state) {
+ case ETZKX_STATE_STDBY:
+ ret = etzkx_state_go_active(sdata);
+ if (ret)
+ return ret;
+
+ case ETZKX_STATE_ACTIVE:
+ case ETZKX_STATE_STRM:
+ if (match_bitops) {
+ ret = etzkx_set_stm_params(sdata, match_bitops,
+ etzkx_algos[i].odr,
+ etzkx_algos[i].range);
+ if (ret)
+ return ret;
+ } else if ((etzkx_algos[i].odr != sdata->odr) ||
+ (etzkx_algos[i].range != sdata->range)) {
+ return -EPERM;
+ }
+
+ ret = __etzkx_state_enable_stm(sdata, i, ETZKX_STATE_STM_2);
+ if (ret)
+ return ret;
+
+ sdata->drv_state = (sdata->drv_state == ETZKX_STATE_ACTIVE) ?
+ ETZKX_STATE_STM_2 :
+ ETZKX_STATE_STRM_STM2;
+ sdata->stm2 = i;
+ return 0;
+
+ case ETZKX_STATE_STM_1:
+ case ETZKX_STATE_STRM_STM1:
+ /* check if the selected stm is compatible with stm2 */
+ if ((etzkx_algos[i].range != sdata->range) &&
+ (etzkx_algos[i].range != ETZKX_G_RANGE_DONT_CARE))
+ return -EPERM;
+
+ if (!ETZKX_ALGO_STM2(etzkx_algos[i].stm, etzkx_algos[i].odr) &&
+ ETZKX_ALGO_STM2(etzkx_algos[sdata->stm1].stm,
+ etzkx_rate_hz[ETZKX_ODR_1600])) {
+ if ((etzkx_algos[i].odr ==
+ etzkx_rate_hz[ETZKX_ODR_1600]) &&
+ (sdata->odr !=
+ etzkx_rate_hz[ETZKX_ODR_1600])) {
+
+ ret = etzkx_set_odr(sdata,
+ etzkx_rate_hz[ETZKX_ODR_1600]);
+ if (ret)
+ return ret;
+ }
+ ret = etzkx_move_stm_1_to_2(sdata);
+ return ret ? ret :
+ etzkx_state_enable_stm1(sdata, stm_id, i);
+ } else if ((etzkx_algos[i].odr != sdata->odr) &&
+ (etzkx_algos[i].odr != ETZKX_ODR_DONT_CARE)) {
+ return -EPERM;
+ }
+
+ ret = __etzkx_state_enable_stm(sdata, i, ETZKX_STATE_STM_2);
+ if (ret)
+ return ret;
+
+ sdata->drv_state = (sdata->drv_state == ETZKX_STATE_STM_1) ?
+ ETZKX_STATE_STM1_STM2 :
+ ETZKX_STATE_STRM_STM1_STM2;
+ sdata->stm2 = i;
+ return 0;
+ }
+
+ return -EPERM;
+}
+
+static int etzkx_restore_after_stm(struct etzkx_data *sdata)
+{
+ int i, err;
+
+ for (i = 0;
+ i < (ARRAY_SIZE(etzkx_rate_ms) - 2) &&
+ (sdata->poll_rate < etzkx_rate_ms[i]);
+ i++)
+ ;
+
+ err = etzkx_set_odr(sdata, i);
+ if (sdata->range != sdata->range_back)
+ err |= etzkx_set_range(sdata, sdata->range_back);
+
+ return (err) ? err : 0;
+}
+static int etzkx_state_disable_stm1(struct etzkx_data *sdata, u8 stm_id)
+{
+ int err;
+
+ if (sdata->stm1 == ETZKX_NO_STM_RUNNING)
+ return 0;
+ if (stm_id != etzkx_algos[sdata->stm1].stm_id)
+ return 0;
+
+ switch (sdata->drv_state) {
+ case ETZKX_STATE_STDBY:
+ case ETZKX_STATE_ACTIVE:
+ return 0;
+ case ETZKX_STATE_STM_1:
+ case ETZKX_STATE_STRM_STM1:
+ err = etzkx_disable_stm1(sdata);
+ if (err)
+ return err;
+
+ sdata->drv_state = (sdata->drv_state == ETZKX_STATE_STM_1) ?
+ ETZKX_STATE_ACTIVE :
+ ETZKX_STATE_STRM;
+ sdata->stm1 = ETZKX_NO_STM_RUNNING;
+
+ return etzkx_restore_after_stm(sdata);
+
+ case ETZKX_STATE_STM1_STM2:
+ case ETZKX_STATE_STRM_STM1_STM2:
+ err = etzkx_disable_stm1(sdata);
+ if (err)
+ return err;
+
+ if (*((u32 *) etzkx_algos[sdata->stm1].v)) {
+ err = etzkx_switch_off_vfilter(sdata, sdata->stm2);
+ if (err)
+ return err;
+ }
+
+ if ((etzkx_algos[sdata->stm1].odr ==
+ etzkx_rate_hz[ETZKX_ODR_1600]) &&
+ etzkx_algos[sdata->stm2].stm[ETZKX_ALGO_DES_IDX])
+
+ err = etzkx_set_odr(sdata,
+ etzkx_algos[sdata->stm2].odr);
+
+ else if (etzkx_algos[sdata->stm2].odr == ETZKX_ODR_DONT_CARE)
+ err = etzkx_restore_after_stm(sdata);
+
+ sdata->drv_state = (sdata->drv_state == ETZKX_STATE_STM1_STM2) ?
+ ETZKX_STATE_STM_2 :
+ ETZKX_STATE_STRM_STM2;
+ sdata->stm1 = ETZKX_NO_STM_RUNNING;
+
+ return err;
+ }
+
+ return -EPERM;
+}
+
+static int etzkx_state_disable_stm2(struct etzkx_data *sdata, u8 stm_id)
+{
+ int err;
+
+ if (sdata->stm2 == ETZKX_NO_STM_RUNNING)
+ return 0;
+ if (stm_id != etzkx_algos[sdata->stm2].stm_id)
+ return 0;
+
+ switch (sdata->drv_state) {
+ case ETZKX_STATE_STDBY:
+ case ETZKX_STATE_ACTIVE:
+ return 0;
+ case ETZKX_STATE_STM_2:
+ case ETZKX_STATE_STRM_STM2:
+ err = etzkx_disable_stm2(sdata);
+ if (err < 0)
+ return err;
+
+ sdata->drv_state = (sdata->drv_state == ETZKX_STATE_STM_2) ?
+ ETZKX_STATE_ACTIVE :
+ ETZKX_STATE_STRM;
+ sdata->stm2 = ETZKX_NO_STM_RUNNING;
+
+ return etzkx_restore_after_stm(sdata);
+
+ case ETZKX_STATE_STM1_STM2:
+ case ETZKX_STATE_STRM_STM1_STM2:
+ err = etzkx_disable_stm2(sdata);
+ if (err)
+ return err;
+
+ if (etzkx_algos[sdata->stm1].odr == ETZKX_ODR_DONT_CARE) {
+ err = etzkx_restore_after_stm(sdata);
+ if (err)
+ return err;
+ }
+
+ if (*((u32 *) etzkx_algos[sdata->stm2].v))
+ err = etzkx_switch_off_vfilter(sdata, sdata->stm2);
+
+ sdata->drv_state = (sdata->drv_state == ETZKX_STATE_STM1_STM2) ?
+ ETZKX_STATE_STM_1 :
+ ETZKX_STATE_STRM_STM1;
+ sdata->stm2 = ETZKX_NO_STM_RUNNING;
+
+ return err;
+ }
+
+ return -EPERM;
+}
+
+static void etzkx_report_xyz(struct input_dev *dev, int *xyz)
+{
+ input_report_abs(dev, ABS_X, xyz[0]);
+ input_report_abs(dev, ABS_Y, xyz[1]);
+ input_report_abs(dev, ABS_Z, xyz[2]);
+ input_sync(dev);
+}
+
+static int etzkx_read_xyz(struct etzkx_data *sdata, int *xyz)
+{
+
+ int err;
+ u8 reg_xyz[6];
+ s16 raw_xyz[3] = { 0 };
+
+ err = i2c_smbus_read_i2c_block_data(sdata->client, ETZKX_REG_OUTX_L,
+ 6, reg_xyz);
+ if (err != 6)
+ return -EIO;
+
+ raw_xyz[0] = ((s16) ((reg_xyz[1] << 8) | reg_xyz[0]));
+ raw_xyz[1] = ((s16) ((reg_xyz[3] << 8) | reg_xyz[2]));
+ raw_xyz[2] = ((s16) ((reg_xyz[5] << 8) | reg_xyz[4]));
+
+ xyz[0] = ((sdata->pdata->x_negate) ? (-raw_xyz[sdata->pdata->x_map])
+ : (raw_xyz[sdata->pdata->x_map]));
+ xyz[1] = ((sdata->pdata->y_negate) ? (-raw_xyz[sdata->pdata->y_map])
+ : (raw_xyz[sdata->pdata->y_map]));
+ xyz[2] = ((sdata->pdata->z_negate) ? (-raw_xyz[sdata->pdata->z_map])
+ : (raw_xyz[sdata->pdata->z_map]));
+
+ return 0;
+}
+
+static void etzkx_poll_read_work(struct work_struct *work)
+{
+ int err;
+ int xyz[3];
+ struct etzkx_data *etzkx = container_of((struct delayed_work *) work,
+ struct etzkx_data, poll_read_work);
+
+ err = etzkx_read_xyz(etzkx, xyz);
+ if (err) {
+ dev_err(&etzkx->client->dev, "i2c read/write error\n");
+ mutex_lock(&etzkx->mutex);
+ etzkx_state_disable_streaming(etzkx);
+ mutex_unlock(&etzkx->mutex);
+ } else {
+ etzkx_report_xyz(etzkx->input_dev, xyz);
+ }
+
+ if (etzkx->drv_state & (ETZKX_STATE_STRM | ETZKX_STATE_SELF_TEST)) {
+ schedule_delayed_work(&etzkx->poll_read_work,
+ msecs_to_jiffies(etzkx->poll_rate));
+ }
+}
+
+static int etzkx_stm_handle(struct etzkx_data *sdata, u8 i, u8 outs)
+{
+ switch (etzkx_algos[i].stm_id) {
+ case ETZKX_STM_ID_TIMING:
+ sdata->running_stm.id = ETZKX_STM_ID_TIMING;
+ break;
+ case ETZKX_STM_ID_ORIENTATION:
+ sdata->running_stm.id = ETZKX_STM_ID_ORIENTATION;
+ switch (outs) {
+ case ETZKX_ORIENTATION_PORTRAIT:
+ sdata->running_stm.algo.portrait = 1;
+ sdata->running_stm.algo.landscape = 0;
+ break;
+ case ETZKX_ORIENTATION_LANDSCAPE:
+ sdata->running_stm.algo.portrait = 0;
+ sdata->running_stm.algo.landscape = 1;
+ break;
+ }
+ break;
+ case ETZKX_STM_ID_DOUBLE_TAP:
+ sdata->running_stm.id = ETZKX_STM_ID_DOUBLE_TAP;
+ switch (outs) {
+ case ETZKX_DOUBLE_TAP_PLUS_X:
+ sdata->running_stm.algo.x = 1;
+ sdata->running_stm.algo.y = 0;
+ sdata->running_stm.algo.z = 0;
+ sdata->running_stm.algo.peak = 0;
+ break;
+ case ETZKX_DOUBLE_TAP_MINUS_X:
+ sdata->running_stm.algo.x = 2;
+ sdata->running_stm.algo.y = 0;
+ sdata->running_stm.algo.z = 0;
+ sdata->running_stm.algo.peak = 0;
+ break;
+ case ETZKX_DOUBLE_TAP_PLUS_Y:
+ sdata->running_stm.algo.x = 0;
+ sdata->running_stm.algo.y = 1;
+ sdata->running_stm.algo.z = 0;
+ sdata->running_stm.algo.peak = 0;
+ break;
+ case ETZKX_DOUBLE_TAP_MINUS_Y:
+ sdata->running_stm.algo.x = 0;
+ sdata->running_stm.algo.y = 2;
+ sdata->running_stm.algo.z = 0;
+ sdata->running_stm.algo.peak = 0;
+ break;
+ case ETZKX_DOUBLE_TAP_PLUS_Z:
+ sdata->running_stm.algo.x = 0;
+ sdata->running_stm.algo.y = 0;
+ sdata->running_stm.algo.z = 1;
+ sdata->running_stm.algo.peak = 0;
+ break;
+ case ETZKX_DOUBLE_TAP_MINUS_Z:
+ sdata->running_stm.algo.x = 0;
+ sdata->running_stm.algo.y = 0;
+ sdata->running_stm.algo.z = 2;
+ sdata->running_stm.algo.peak = 0;
+ break;
+ }
+ break;
+ case ETZKX_STM_ID_WAKEUP:
+ sdata->running_stm.id = ETZKX_STM_ID_WAKEUP;
+ sdata->running_stm.algo.sleep = !outs;
+ sdata->running_stm.algo.wakeup =
+ !sdata->running_stm.algo.sleep;
+ break;
+ case ETZKX_STM_ID_V_DOUBLE_TAP:
+ sdata->running_stm.id = ETZKX_STM_ID_V_DOUBLE_TAP;
+ sdata->running_stm.algo.vtap = 1;
+ break;
+ default:
+ return 0;
+ }
+
+ wake_up_interruptible(&sdata->wq);
+ return 0;
+}
+
+static irqreturn_t etzkx_irq_handler(int irq, void *dev)
+{
+ struct etzkx_data *sdata = dev;
+ s32 outs;
+ u8 masked_outs;
+
+ if (irq == sdata->pdata->irq1) {
+ outs = i2c_smbus_read_byte_data(sdata->client,
+ ETZKX_REG_OUTS_1);
+ masked_outs = etzkx_mask_orientation(sdata, outs);
+ etzkx_stm_handle(dev, sdata->stm1, masked_outs);
+ } else if (irq == sdata->pdata->irq2) {
+ outs = i2c_smbus_read_byte_data(sdata->client,
+ ETZKX_REG_OUTS_2);
+ masked_outs = etzkx_mask_orientation(sdata, outs);
+ etzkx_stm_handle(dev, sdata->stm2, masked_outs);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int etzkx_hw_detect(struct etzkx_data *sdata)
+{
+ int err;
+ u8 wai_reg[3];
+
+ err = i2c_smbus_read_i2c_block_data(sdata->client, ETZKX_REG_INFO1,
+ 3, wai_reg);
+ if (err < 0)
+ return err;
+ if (err != 3)
+ return -EIO;
+
+ switch (wai_reg[2]) {
+ case ETZKX_WIA_LISN3DSH:
+ dev_info(&sdata->client->dev,
+ "ST lisn3dsh vers. %u accelerometer detected\n",
+ wai_reg[0]);
+ break;
+
+ case ETZKX_WIA_KXTNK:
+ dev_info(&sdata->client->dev,
+ "Kionix kxtnk-1000 vers %u accelerometer detected\n",
+ wai_reg[0]);
+ break;
+
+ case ETZKX_WIA_KXCNL:
+ dev_info(&sdata->client->dev,
+ "Kionix kxcnl-1010 vers %u accelerometer detected\n",
+ wai_reg[0]);
+ break;
+
+ default:
+ return -ENODEV;
+ }
+
+ sdata->wai = wai_reg[2];
+ sdata->hw_version = wai_reg[0];
+
+ return 0;
+}
+
+static ssize_t etzkx_sysfs_read_hwid(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct etzkx_data *sdata = dev_get_drvdata(dev);
+
+ switch (sdata->wai) {
+ case ETZKX_WIA_LISN3DSH:
+ return sprintf(buf, "lisn3dsh (%u)\n", sdata->hw_version);
+ case ETZKX_WIA_KXTNK:
+ return sprintf(buf, "kxtnk-1000 (%u)\n", sdata->hw_version);
+ case ETZKX_WIA_KXCNL:
+ return sprintf(buf, "kxcnl-1010 (%u)\n", sdata->hw_version);
+ }
+
+ return -ENODEV;
+}
+
+static ssize_t etzkx_sysfs_get_strm(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct etzkx_data *sdata = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%u\n", !!(sdata->drv_state & ETZKX_STATE_STRM));
+}
+
+static ssize_t etzkx_sysfs_set_strm(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ int err;
+ unsigned long value;
+ struct etzkx_data *sdata = dev_get_drvdata(dev);
+
+ if (kstrtoul(buf, 0, &value))
+ return -EINVAL;
+
+ mutex_lock(&sdata->mutex);
+ if (value)
+ err = etzkx_state_enable_streaming(sdata);
+ else
+ err = etzkx_state_disable_streaming(sdata);
+ mutex_unlock(&sdata->mutex);
+
+ return (err < 0) ? err : len;
+}
+
+static ssize_t etzkx_sysfs_get_odr(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct etzkx_data *sdata = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%s\n", etzkx_rate_str[sdata->odr]);
+}
+
+static ssize_t etzkx_sysfs_set_odr(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ unsigned long value;
+ int ret, i;
+ struct etzkx_data *sdata = dev_get_drvdata(dev);
+
+ if (kstrtoul(buf, 0, &value))
+ return -EINVAL;
+ /*
+ * odr = 1600 Hz is not allowed for streaming
+ * 1600Hz should be set only for state machine purpose
+ */
+
+ if ((sdata->stm1 != ETZKX_NO_STM_RUNNING) ||
+ (sdata->stm2 != ETZKX_NO_STM_RUNNING))
+ return -EPERM;
+
+ for (i = 0;
+ (i < ARRAY_SIZE(etzkx_rate_hz)) && (value >= etzkx_rate_hz[i]);
+ i++)
+ ;
+
+ mutex_lock(&sdata->mutex);
+ if (i == 0)
+ ret = etzkx_set_odr(sdata, i);
+ else
+ ret = etzkx_set_odr(sdata, i-1);
+ if (etzkx_rate_ms[sdata->odr] < etzkx_rate_ms[ETZKX_ODR_100])
+ sdata->poll_rate = etzkx_rate_ms[ETZKX_ODR_100];
+ else
+ sdata->poll_rate = etzkx_rate_ms[sdata->odr];
+ mutex_unlock(&sdata->mutex);
+
+ return (ret < 0) ? ret : len;
+}
+
+static ssize_t etzkx_sysfs_set_poll_rate(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ int i, err = 0;
+ unsigned long value;
+ struct etzkx_data *sdata = dev_get_drvdata(dev);
+
+ if (kstrtoul(buf, 0, &value))
+ return -EINVAL;
+
+ /* doesn't allow polling rates faster than 10 ms */
+ if (value < etzkx_rate_ms[ETZKX_ODR_100])
+ value = etzkx_rate_ms[ETZKX_ODR_100];
+ /* doesn't allow polling rates slower than 65535 ms */
+ if (value > ETZKX_MAX_POLL_RATE)
+ value = ETZKX_MAX_POLL_RATE;
+
+ for (i = 0;
+ i < ARRAY_SIZE(etzkx_rate_ms) && (value < etzkx_rate_ms[i]);
+ i++)
+ ;
+
+ /*
+ * set the device frequency one step lower
+ * that the polling rate frequency
+ */
+ mutex_lock(&sdata->mutex);
+ if ((sdata->stm1 == ETZKX_NO_STM_RUNNING) &&
+ sdata->stm2 == ETZKX_NO_STM_RUNNING) {
+ if (i > ETZKX_ODR_100)
+ err = etzkx_set_odr(sdata, ETZKX_ODR_100);
+ else
+ err = etzkx_set_odr(sdata, i);
+ }
+ if (!err) {
+ cancel_delayed_work_sync(&sdata->poll_read_work);
+ sdata->poll_rate = value;
+ if (sdata->drv_state & ETZKX_STATE_STRM)
+ schedule_delayed_work(&sdata->poll_read_work,
+ msecs_to_jiffies(sdata->poll_rate));
+ }
+ mutex_unlock(&sdata->mutex);
+
+ return (err < 0) ? err : len;
+}
+
+static ssize_t etzkx_sysfs_get_poll_rate(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct etzkx_data *sdata = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%u\n", sdata->poll_rate);
+}
+
+static ssize_t etzkx_sysfs_get_range(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct etzkx_data *sdata = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%u\n", sdata->range);
+}
+
+static ssize_t etzkx_sysfs_set_range(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ ssize_t ret;
+ unsigned long value;
+ struct etzkx_data *sdata = dev_get_drvdata(dev);
+
+ if (kstrtoul(buf, 0, &value))
+ return -EINVAL;
+
+ if ((sdata->stm1 != ETZKX_NO_STM_RUNNING) ||
+ (sdata->stm2 != ETZKX_NO_STM_RUNNING))
+ return -EPERM;
+
+ mutex_lock(&sdata->mutex);
+ ret = etzkx_set_range(sdata, value);
+ mutex_unlock(&sdata->mutex);
+
+ return (ret < 0) ? ret : len;
+}
+
+static ssize_t etzkx_sysfs_get_st(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct etzkx_data *sdata = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%u\n",
+ !!(sdata->drv_state & ETZKX_STATE_SELF_TEST));
+}
+
+static ssize_t etzkx_sysfs_set_st(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ int ret;
+ unsigned long value;
+ struct etzkx_data *sdata = dev_get_drvdata(dev);
+
+ if (kstrtoul(buf, 0, &value))
+ return -EINVAL;
+
+ mutex_lock(&sdata->mutex);
+ if (value)
+ ret = etzkx_state_enable_st(sdata);
+ else
+ ret = etzkx_state_disable_st(sdata);
+
+ mutex_unlock(&sdata->mutex);
+
+ return (ret < 0) ? ret : len;
+}
+
+static ssize_t etzkx_sysfs_read_version(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%u\n", ETZKX_DRV_VERSION);
+}
+
+static DEVICE_ATTR(hwid, S_IRUGO, etzkx_sysfs_read_hwid, NULL);
+static DEVICE_ATTR(enable, S_IRUGO | S_IWUSR,
+ etzkx_sysfs_get_strm, etzkx_sysfs_set_strm);
+static DEVICE_ATTR(odr, S_IRUGO | S_IWUSR,
+ etzkx_sysfs_get_odr, etzkx_sysfs_set_odr);
+static DEVICE_ATTR(delay, S_IRUGO | S_IWUSR,
+ etzkx_sysfs_get_poll_rate, etzkx_sysfs_set_poll_rate);
+static DEVICE_ATTR(range, S_IRUGO | S_IWUSR,
+ etzkx_sysfs_get_range, etzkx_sysfs_set_range);
+static DEVICE_ATTR(self_test, S_IRUGO | S_IWUSR,
+ etzkx_sysfs_get_st, etzkx_sysfs_set_st);
+static DEVICE_ATTR(drv_version, S_IRUGO, etzkx_sysfs_read_version, NULL);
+
+static struct attribute *sysfs_attrs[] = {
+ &dev_attr_hwid.attr,
+ &dev_attr_enable.attr,
+ &dev_attr_odr.attr,
+ &dev_attr_delay.attr,
+ &dev_attr_range.attr,
+ &dev_attr_self_test.attr,
+ &dev_attr_drv_version.attr,
+ NULL
+};
+
+static struct attribute_group etzkx_attribute_group = {
+ .attrs = sysfs_attrs
+};
+
+static int etzkx_chardev_open(struct inode *inode, struct file *file)
+{
+ struct etzkx_data *sdata =
+ container_of(inode->i_cdev, struct etzkx_data, cdev);
+
+ file->private_data = sdata;
+
+ return 0;
+}
+
+static int etzkx_chardev_release(struct inode *inode, struct file *file)
+{
+ return 0;
+}
+
+static ssize_t etzkx_chardev_read(struct file *file, char __user *buffer,
+ size_t length, loff_t *offset)
+{
+ int ret;
+ struct etzkx_data *sdata = file->private_data;
+
+ ret = wait_event_interruptible(sdata->wq, sdata->running_stm.id);
+ if (ret != 0)
+ return ret;
+
+ ret = copy_to_user(buffer, &sdata->running_stm,
+ sizeof(sdata->running_stm));
+ if (ret != 0)
+ ret = -EFAULT;
+
+ mutex_lock(&sdata->mutex);
+ sdata->running_stm.id = ETZKX_STM_ID_NO_STM;
+ mutex_unlock(&sdata->mutex);
+
+ return ret;
+}
+
+static ssize_t etzkx_chardev_write(struct file *file,
+ const char __user *buffer, size_t length, loff_t *offset)
+{
+ return 0;
+}
+
+static unsigned int etzkx_chardev_poll(struct file *file,
+ struct poll_table_struct *wait)
+{
+ struct etzkx_data *sdata = file->private_data;
+ poll_wait(file, &sdata->wq, wait);
+
+ return (sdata->running_stm.id) ? (POLLIN | POLLRDNORM) : 0;
+}
+
+static long etzkx_chardev_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ s32 pc;
+ int err = 0;
+ int reg_xyz[3];
+ s16 range_mult;
+ struct etzkx_stm_data info;
+ int (*etzkx_state_enable_stm)(struct etzkx_data*, u8, u8);
+ struct etzkx_data *sdata = file->private_data;
+
+ /* switch on the latest two bits of cmd */
+ switch ((cmd >> _IOC_NRSHIFT) & 0x03) {
+ case ETZKXIO_ENABLE:
+ /*
+ * check if there are free slots and if the
+ * requested state machine is already running
+ */
+
+ if (sdata->stm1 == ETZKX_NO_STM_RUNNING) {
+ if (sdata->stm2 != ETZKX_NO_STM_RUNNING &&
+ etzkx_algos[sdata->stm2].stm_id ==
+ (((cmd & 0xFF) >> _IOC_NRSHIFT) >> 2))
+ return -EPERM;
+
+ etzkx_state_enable_stm = etzkx_state_enable_stm1;
+
+ } else if (sdata->stm2 == ETZKX_NO_STM_RUNNING) {
+ if (sdata->stm1 != ETZKX_NO_STM_RUNNING &&
+ etzkx_algos[sdata->stm1].stm_id ==
+ (((cmd & 0xFF) >> _IOC_NRSHIFT) >> 2))
+ return -EPERM;
+
+ etzkx_state_enable_stm = etzkx_state_enable_stm2;
+
+ } else {
+ return -EPERM;
+ }
+
+ mutex_lock(&sdata->mutex);
+ switch (cmd) {
+ case ETZKXIO_ENABLE_TIMING:
+ err = etzkx_state_enable_stm(sdata,
+ ETZKX_STM_ID_TIMING, 0);
+ break;
+ case ETZKXIO_ENABLE_ORIENTATION:
+ err = etzkx_state_enable_stm(sdata,
+ ETZKX_STM_ID_ORIENTATION, 0);
+ break;
+ case ETZKXIO_ENABLE_DOUBLE_TAP:
+ err = etzkx_state_enable_stm(sdata,
+ ETZKX_STM_ID_DOUBLE_TAP, 0);
+ break;
+ case ETZKXIO_ENABLE_WAKEUP:
+ err = etzkx_state_enable_stm(sdata,
+ ETZKX_STM_ID_WAKEUP, 0);
+ break;
+ case ETZKXIO_ENABLE_V_DOUBLE_TAP:
+ err = etzkx_state_enable_stm(sdata,
+ ETZKX_STM_ID_V_DOUBLE_TAP, 0);
+ break;
+ default:
+ err = -EINVAL;
+ }
+ break;
+
+ case ETZKXIO_DISABLE:
+ /* get the algo id from the ioctl command */
+ cmd = (cmd & 0xFF) >> 2;
+ mutex_lock(&sdata->mutex);
+ if ((sdata->stm1 != ETZKX_NO_STM_RUNNING) &&
+ (etzkx_algos[sdata->stm1].stm_id == cmd))
+ err = etzkx_state_disable_stm1(sdata,
+ etzkx_algos[sdata->stm1].stm_id);
+
+ else if ((sdata->stm2 != ETZKX_NO_STM_RUNNING) &&
+ (etzkx_algos[sdata->stm2].stm_id == cmd))
+ err = etzkx_state_disable_stm2(sdata,
+ etzkx_algos[sdata->stm2].stm_id);
+ else
+ err = -EINVAL;
+ break;
+
+ case ETZKXIO_STATE:
+ mutex_lock(&sdata->mutex);
+ switch (cmd) {
+ case ETZKXIO_WHICH_ORIENTATION:
+ if ((sdata->stm1 != ETZKX_NO_STM_RUNNING) &&
+ (etzkx_algos[sdata->stm1].stm_id ==
+ ETZKX_STM_ID_ORIENTATION)) {
+
+ pc = i2c_smbus_read_byte_data(sdata->client,
+ ETZKX_REG_PR_1);
+ if (pc < 0) {
+ err = pc;
+ goto ioctl_err;
+ }
+ } else if ((sdata->stm2 != ETZKX_NO_STM_RUNNING) &&
+ (etzkx_algos[sdata->stm2].stm_id ==
+ ETZKX_STM_ID_ORIENTATION)) {
+ pc = i2c_smbus_read_byte_data(sdata->client,
+ ETZKX_REG_PR_2);
+ if (pc < 0) {
+ err = pc;
+ goto ioctl_err;
+ }
+ /*
+ * address register gap between
+ * addresses for stm1 and stm2
+ */
+ pc -= ETZKX_STM_REG_GAP;
+ } else {
+ err = -EINVAL;
+ goto ioctl_err;
+ }
+
+ info.id = ETZKX_STM_ID_ORIENTATION;
+ info.algo.portrait = (pc <= ETZKX_REG_ST7_1);
+ info.algo.landscape = !info.algo.portrait;
+
+ if (copy_to_user((void __user *) arg,
+ &info, sizeof(info)))
+ err = -EFAULT;
+ break;
+
+ case ETZKXIO_INSTANT_ORIENTATION:
+ if (sdata->drv_state == ETZKX_STATE_STDBY) {
+ err = -EFAULT;
+ goto ioctl_err;
+ }
+
+ if (sdata->range == ETZKX_G_RANGE_DONT_CARE) {
+ err = -EINVAL;
+ goto ioctl_err;
+ }
+
+ err = etzkx_read_xyz(sdata, reg_xyz);
+ if (err)
+ goto ioctl_err;
+
+ range_mult = ETZKX_MAX_RANGE / sdata->range;
+ info.id = ETZKX_STM_ID_ORIENTATION;
+ info.algo.landscape = ((reg_xyz[1] <
+ ETZKX_ORIENTATION_LIMIT * range_mult) &&
+ (abs(reg_xyz[0]) >
+ ETZKX_ORIENTATION_LIMIT * range_mult));
+ info.algo.portrait = !info.algo.landscape;
+
+ if (copy_to_user((void __user *) arg,
+ &info, sizeof(info)))
+ err = -EFAULT;
+
+ break;
+
+ case ETZKXIO_RUNNING_ALGO:
+ info.id = ETZKX_STM_ID_NO_STM;
+ info.algo.stm1 =
+ (sdata->stm1 == ETZKX_NO_STM_RUNNING) ?
+ ETZKX_STM_ID_NO_STM :
+ etzkx_algos[sdata->stm1].stm_id;
+ info.algo.stm2 =
+ (sdata->stm2 == ETZKX_NO_STM_RUNNING) ?
+ ETZKX_STM_ID_NO_STM :
+ etzkx_algos[sdata->stm2].stm_id;
+
+ if (copy_to_user((void __user *) arg,
+ &info, sizeof(info)))
+ err = -EFAULT;
+ break;
+
+ default:
+ err = -EINVAL;
+ }
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ioctl_err:
+ mutex_unlock(&sdata->mutex);
+ return err;
+}
+
+static const struct file_operations etzkx_chardev_fops = {
+ .owner = THIS_MODULE,
+ .open = etzkx_chardev_open,
+ .release = etzkx_chardev_release,
+ .read = etzkx_chardev_read,
+ .write = etzkx_chardev_write,
+ .poll = etzkx_chardev_poll,
+ .unlocked_ioctl = etzkx_chardev_ioctl,
+};
+
+static int etzkx_input_register_device(struct etzkx_data *sdata)
+{
+ int err;
+
+ sdata->input_dev = input_allocate_device();
+ if (!sdata->input_dev)
+ return -ENOMEM;
+
+ sdata->input_dev->name = ETZKX_DEV_NAME;
+ sdata->input_dev->id.bustype = BUS_I2C;
+ sdata->input_dev->id.vendor = sdata->wai;
+ sdata->input_dev->id.version = sdata->hw_version;
+ sdata->input_dev->dev.parent = &sdata->client->dev;
+
+ set_bit(EV_ABS, sdata->input_dev->evbit);
+ input_set_abs_params(sdata->input_dev, ABS_X, INT_MIN, INT_MAX, 0, 0);
+ input_set_abs_params(sdata->input_dev, ABS_Y, INT_MIN, INT_MAX, 0, 0);
+ input_set_abs_params(sdata->input_dev, ABS_Z, INT_MIN, INT_MAX, 0, 0);
+
+ err = input_register_device(sdata->input_dev);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+static void etzkx_input_cleanup(struct etzkx_data *sdata)
+{
+ input_unregister_device(sdata->input_dev);
+}
+
+static int etzkx_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ int err;
+ struct etzkx_data *sdata;
+
+ if (client->dev.platform_data == NULL) {
+ dev_err(&client->dev, "no platform data declared\n");
+ return -ENODEV;
+ }
+
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_I2C_BLOCK)) {
+ dev_err(&client->dev,
+ "no algorithm associated to the i2c bus\n");
+ return -ENODEV;
+ }
+
+ sdata = devm_kzalloc(&client->dev,
+ sizeof(struct etzkx_data), GFP_KERNEL);
+ if (!sdata) {
+ dev_err(&client->dev, "no memory available\n");
+ return -ENOMEM;
+ }
+
+ mutex_init(&sdata->mutex);
+ mutex_lock(&sdata->mutex);
+
+ sdata->client = client;
+ i2c_set_clientdata(client, sdata);
+
+ sdata->pdata = client->dev.platform_data;
+
+ msleep(50);
+
+ err = etzkx_hw_detect(sdata);
+ if (err) {
+ dev_err(&client->dev, "device not recognized\n");
+ goto free_stmt;
+ }
+
+ if (sdata->pdata->init) {
+ err = sdata->pdata->init();
+ if (err) {
+ dev_err(&client->dev,
+ "impossible to initialize the device\n");
+ goto free_stmt;
+ }
+ }
+
+ err = etzkx_input_register_device(sdata);
+ if (err < 0) {
+ dev_err(&client->dev,
+ "impossible to associate the device to an event\n");
+ goto free_src;
+ }
+
+ err = sysfs_create_group(&sdata->client->dev.kobj,
+ &etzkx_attribute_group);
+ if (err) {
+ dev_err(&client->dev,
+ "impossible to allocate sysfs resources\n");
+ goto free_input;
+ }
+
+ err = alloc_chrdev_region(&etzkx_dev_number, 0, 1, ETZKX_CHARDEV_NAME);
+ if (err)
+ dev_err(&client->dev, "cannot register device\n");
+
+ etzkx_class = class_create(THIS_MODULE, ETZKX_CHARDEV_NAME);
+ cdev_init(&sdata->cdev, &etzkx_chardev_fops);
+ sdata->cdev.owner = THIS_MODULE;
+
+ err = cdev_add(&sdata->cdev, etzkx_dev_number, 1);
+ if (err)
+ dev_err(&client->dev, "cannot register device\n");
+
+ device_create(etzkx_class, NULL, MKDEV(MAJOR(etzkx_dev_number), 0),
+ NULL, ETZKX_CHARDEV_NAME);
+
+ init_waitqueue_head(&sdata->wq);
+
+ if (sdata->pdata->irq1) {
+ err = request_threaded_irq(sdata->pdata->irq1, NULL,
+ etzkx_irq_handler, IRQF_TRIGGER_RISING,
+ "etzkx_irq1", sdata);
+ if (err) {
+ dev_err(&client->dev, "unable to request irq1\n");
+ goto free_sysfs;
+ }
+ }
+
+ if (sdata->pdata->irq2) {
+ err = request_threaded_irq(sdata->pdata->irq2, NULL,
+ etzkx_irq_handler, IRQF_TRIGGER_RISING,
+ "etzkx_irq2", sdata);
+ if (err) {
+ dev_err(&client->dev, "unable to request irq2\n");
+ goto free_irq1;
+ }
+ }
+
+ if ((sdata->pdata->odr >= ETZKX_ODR_3_125) &&
+ (sdata->pdata->odr <= ETZKX_ODR_1600))
+ sdata->odr = sdata->pdata->odr;
+ else
+ sdata->odr = ETZKX_DEFAULT_ODR;
+ if (sdata->pdata->range != ETZKX_G_RANGE_2G &&
+ sdata->pdata->range != ETZKX_G_RANGE_4G &&
+ sdata->pdata->range != ETZKX_G_RANGE_6G &&
+ sdata->pdata->range != ETZKX_G_RANGE_8G)
+ sdata->range = ETZKX_DEFAULT_G_RANGE;
+ else
+ sdata->range = sdata->pdata->range;
+ sdata->poll_rate = etzkx_rate_ms[sdata->odr];
+ err = etzkx_state_go_stdby(sdata);
+ if (err) {
+ dev_err(&client->dev, "unable to switch on the device\n");
+ goto free_irq2;
+ }
+
+ /* build mask matrix */
+ sdata->mask_matrix[0] = 1 <<
+ (ETZKX_DIMENSION - (sdata->pdata->x_map + 1) * 2 +
+ !sdata->pdata->x_negate);
+ sdata->mask_matrix[1] = 1 <<
+ (ETZKX_DIMENSION - (sdata->pdata->x_map + 1) * 2 +
+ sdata->pdata->x_negate);
+ sdata->mask_matrix[2] = 1 <<
+ (ETZKX_DIMENSION - (sdata->pdata->y_map + 1) * 2 +
+ !sdata->pdata->y_negate);
+ sdata->mask_matrix[3] = 1 <<
+ (ETZKX_DIMENSION - (sdata->pdata->y_map + 1) * 2 +
+ sdata->pdata->y_negate);
+ sdata->mask_matrix[4] = 1 <<
+ (ETZKX_DIMENSION - (sdata->pdata->z_map + 1) * 2 +
+ !sdata->pdata->z_negate);
+ sdata->mask_matrix[5] = 1 <<
+ (ETZKX_DIMENSION - (sdata->pdata->z_map + 1) * 2 +
+ sdata->pdata->z_negate);
+ sdata->mask_matrix[6] = 2;
+ sdata->mask_matrix[7] = 1;
+
+ INIT_DELAYED_WORK(&sdata->poll_read_work, etzkx_poll_read_work);
+
+ mutex_unlock(&sdata->mutex);
+
+ return 0;
+
+free_irq2:
+ if (sdata->pdata->irq2)
+ free_irq(sdata->pdata->irq2, sdata);
+free_irq1:
+ if (sdata->pdata->irq1)
+ free_irq(sdata->pdata->irq1, sdata);
+free_sysfs:
+ sysfs_remove_group(&sdata->client->dev.kobj, &etzkx_attribute_group);
+free_input:
+ etzkx_input_cleanup(sdata);
+free_src:
+ if (sdata->pdata->release)
+ sdata->pdata->release();
+free_stmt:
+ mutex_unlock(&sdata->mutex);
+
+ return err;
+}
+
+static int etzkx_remove(struct i2c_client *client)
+{
+ struct etzkx_data *sdata = i2c_get_clientdata(client);
+
+ sysfs_remove_group(&sdata->client->dev.kobj, &etzkx_attribute_group);
+
+ unregister_chrdev_region(etzkx_dev_number, 1);
+ device_destroy(etzkx_class, MKDEV(MAJOR(etzkx_dev_number), 0));
+ cdev_del(&sdata->cdev);
+ class_destroy(etzkx_class);
+
+ if (sdata->pdata->irq2)
+ free_irq(sdata->pdata->irq2, sdata);
+
+ if (sdata->pdata->irq1)
+ free_irq(sdata->pdata->irq1, sdata);
+
+ if (sdata->drv_state & ETZKX_STATE_STRM)
+ cancel_delayed_work_sync(&sdata->poll_read_work);
+
+ etzkx_input_cleanup(sdata);
+
+ if (sdata->pdata->release)
+ sdata->pdata->release();
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int etzkx_suspend(struct device *dev)
+{
+ return 0;
+}
+
+static int etzkx_resume(struct device *dev)
+{
+ return 0;
+}
+#else
+#define etzkx_suspend NULL
+#define etzkx_resume NULL
+#endif
+
+#ifdef CONFIG_PM_RUNTIME
+static int etzkx_runtime_suspend(struct device *dev)
+{
+ return 0;
+}
+
+static int etzkx_runtime_resume(struct device *dev)
+{
+ return 0;
+}
+#else
+#define etzkx_runtime_suspend NULL
+#define etzkx_runtime_resume NULL
+#endif
+
+static const struct i2c_device_id etzkx_id[] = {
+ { ETZKX_DEV_NAME, 0 },
+ { ETZKX_LISN3DSH_NAME, 0 },
+ { ETZKX_KXTNK_NAME, 0 },
+ { },
+ };
+
+MODULE_DEVICE_TABLE(i2c, etzkx_id);
+
+static const struct dev_pm_ops etzkx_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(etzkx_suspend, etzkx_resume)
+ SET_RUNTIME_PM_OPS(etzkx_runtime_suspend, etzkx_runtime_resume, NULL)
+};
+
+static struct i2c_driver etzkx_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = ETZKX_DEV_NAME,
+ .pm = &etzkx_pm_ops,
+ },
+ .probe = etzkx_probe,
+ .remove = etzkx_remove,
+ .id_table = etzkx_id,
+};
+
+module_i2c_driver(etzkx_driver);
+
+MODULE_DESCRIPTION("State Machine Interrupt Driven Accelerometer");
+MODULE_AUTHOR("Andi Shyti");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/i2c/etzkx.h b/include/linux/i2c/etzkx.h
new file mode 100644
index 0000000..23bab6d
--- /dev/null
+++ b/include/linux/i2c/etzkx.h
@@ -0,0 +1,157 @@
+/*
+ * etzkx: lisn3dsh/kxtnk 3d accelerometer driver
+ *
+ * Copyright (C) 2013 Andi Shyti <andi@...zian.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __ETZKX_H__
+#define __ETZKX_H__
+
+#define ETZKX_G_RANGE_DONT_CARE 0
+#define ETZKX_G_RANGE_2G 2
+#define ETZKX_G_RANGE_4G 4
+#define ETZKX_G_RANGE_6G 6
+#define ETZKX_G_RANGE_8G 8
+
+#define ETZKX_ODR_3_125 0x00
+#define ETZKX_ODR_6_25 0x01
+#define ETZKX_ODR_12_5 0x02
+#define ETZKX_ODR_25 0x03
+#define ETZKX_ODR_50 0x04
+#define ETZKX_ODR_100 0x05
+#define ETZKX_ODR_400 0x06
+#define ETZKX_ODR_1600 0x07
+#define ETZKX_ODR_DONT_CARE 0xFF
+
+#define ETZKX_STM_ID_NO_STM 0
+#define ETZKX_STM_ID_TIMING 1
+#define ETZKX_STM_ID_ORIENTATION 2
+#define ETZKX_STM_ID_DOUBLE_TAP 3
+#define ETZKX_STM_ID_WAKEUP 4
+#define ETZKX_STM_ID_V_DOUBLE_TAP 5
+
+#define ETZKX_DEFAULT_G_RANGE ETZKX_G_RANGE_4G
+#define ETZKX_DEFAULT_ODR ETZKX_ODR_50
+
+/* ioctl-number: 'x' 00-2F (32 commands) */
+#define ETZKX_IOCTL_NUM ('x')
+
+struct etzkx_stm_data {
+ s32 id;
+ union {
+ /* double tap */
+ struct {
+ u8 x;
+ u8 y;
+ u8 z;
+ u8 peak;
+ };
+ /* orientation */
+ struct {
+ u16 portrait;
+ u16 landscape;
+ };
+ /* wake up */
+ struct {
+ u16 wakeup;
+ u16 sleep;
+ };
+ /* v double tap */
+ struct {
+ u32 vtap;
+ };
+ /* running algos */
+ struct {
+ u16 stm1;
+ u16 stm2;
+ };
+ } algo;
+};
+
+/* etzkx ioctl command types */
+#define ETZKXIO_ENABLE 0x01
+#define ETZKXIO_DISABLE 0x02
+#define ETZKXIO_STATE 0x03
+/*
+ * etzkx ioctl commands
+ * - first 6 bits identify the algorithm id
+ * - last 2 bits identify the operation type on the algorithm
+ */
+#define ETZKXIO_ENABLE_TIMING _IO(ETZKX_IOCTL_NUM, \
+ (ETZKX_STM_ID_TIMING << 2) | \
+ ETZKXIO_ENABLE)
+#define ETZKXIO_DISABLE_TIMING _IO(ETZKX_IOCTL_NUM, \
+ (ETZKX_STM_ID_TIMING << 2) | \
+ ETZKXIO_DISABLE)
+#define ETZKXIO_ENABLE_ORIENTATION _IO(ETZKX_IOCTL_NUM, \
+ (ETZKX_STM_ID_ORIENTATION << 2) | \
+ ETZKXIO_ENABLE)
+#define ETZKXIO_DISABLE_ORIENTATION _IO(ETZKX_IOCTL_NUM, \
+ (ETZKX_STM_ID_ORIENTATION << 2) | \
+ ETZKXIO_DISABLE)
+#define ETZKXIO_ENABLE_WAKEUP _IO(ETZKX_IOCTL_NUM, \
+ (ETZKX_STM_ID_WAKEUP << 2) | \
+ ETZKXIO_ENABLE)
+#define ETZKXIO_DISABLE_WAKEUP _IO(ETZKX_IOCTL_NUM, \
+ (ETZKX_STM_ID_WAKEUP << 2) | \
+ ETZKXIO_DISABLE)
+#define ETZKXIO_ENABLE_V_DOUBLE_TAP _IO(ETZKX_IOCTL_NUM, \
+ (ETZKX_STM_ID_V_DOUBLE_TAP << 2) | \
+ ETZKXIO_ENABLE)
+#define ETZKXIO_DISABLE_V_DOUBLE_TAP _IO(ETZKX_IOCTL_NUM, \
+ (ETZKX_STM_ID_V_DOUBLE_TAP << 2) | \
+ ETZKXIO_DISABLE)
+#define ETZKXIO_WHICH_ORIENTATION _IOR(ETZKX_IOCTL_NUM, \
+ (ETZKX_STM_ID_ORIENTATION << 2) | \
+ ETZKXIO_STATE, \
+ struct etzkx_stm_data *)
+#define ETZKXIO_ENABLE_DOUBLE_TAP _IO(ETZKX_IOCTL_NUM, \
+ (ETZKX_STM_ID_DOUBLE_TAP << 2) | \
+ ETZKXIO_ENABLE)
+#define ETZKXIO_DISABLE_DOUBLE_TAP _IO(ETZKX_IOCTL_NUM, \
+ (ETZKX_STM_ID_DOUBLE_TAP << 2) | \
+ ETZKXIO_DISABLE)
+#define ETZKXIO_RUNNING_ALGO _IOR(ETZKX_IOCTL_NUM, \
+ (0x0A << 2) | ETZKXIO_STATE, \
+ struct etzkx_stm_data *)
+#define ETZKXIO_INSTANT_ORIENTATION _IOR(ETZKX_IOCTL_NUM, \
+ (0x0B << 2) | ETZKXIO_STATE, \
+ struct etzkx_stm_data *)
+
+#define ETZKX_DEV_NAME "etzkx"
+#define ETZKX_DRV_VERSION 5
+
+struct etzkx_platform_data {
+ int (*init)(void);
+ void (*release)(void);
+
+ u8 x_map;
+ u8 y_map;
+ u8 z_map;
+
+ u8 x_negate;
+ u8 y_negate;
+ u8 z_negate;
+
+ u8 odr;
+ u8 range;
+
+ u16 irq1;
+ u16 irq2;
+};
+
+#endif
--
1.7.10.4
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
Powered by blists - more mailing lists