lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
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

Powered by Openwall GNU/*/Linux Powered by OpenVZ