2013-06-16 21:39:13

by Andi Shyti

[permalink] [raw]
Subject: [PATCH 0/2] etzkx accelerometer support

Hi,

these two patches provide the etzkx device driver which supports
the following accelerometers:

- Kionix kxcnl
- Kionix kxtnk
- ST lisn3dsh

Support for ST lis3dsh accelerometer is still in progress.

The etzkx accelerometers are a kind of devices that are able to
do gesture recognition directly on the device. It uses two slots
of 16 steps (bytes) each for loading algorithms which perform
gesture recognition.

The second patch contains the documentation of the device driver.

On http://www.etezian.org/etzkx/etzkx/ is possible to find more
informations about the accelerometer.

Thanks,
Andi

Andi Shyti (2):
drivers/misc: etzkx driver for kxcnl/kxtnk accelerometer
Documentation: added etzkx driver documentation

Documentation/misc-devices/etzkx.txt | 325 +++++
drivers/misc/Kconfig | 12 +
drivers/misc/Makefile | 1 +
drivers/misc/etzkx.c | 2313 ++++++++++++++++++++++++++++++++++
include/linux/i2c/etzkx.h | 157 +++
5 files changed, 2808 insertions(+)
create mode 100644 Documentation/misc-devices/etzkx.txt
create mode 100644 drivers/misc/etzkx.c
create mode 100644 include/linux/i2c/etzkx.h

--
1.7.10.4


2013-06-16 21:39:18

by Andi Shyti

[permalink] [raw]
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 <[email protected]>
Tested-by: Onur Atilla <[email protected]>
Tested-by: Simon Zsolt <[email protected]>
---
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 <[email protected]>
+ *
+ * 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 <[email protected]>
+ *
+ * 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

2013-06-16 21:39:26

by Andi Shyti

[permalink] [raw]
Subject: [PATCH 2/2] Documentation: added etzkx driver documentation

The etzkx driver's documentation contains some basic information about

- accelerometer chip overview
- driver interfaces
- driver usage

Signed-off-by: Andi Shyti <[email protected]>
Reviewed-by: Onur Atilla <[email protected]>
---
Documentation/misc-devices/etzkx.txt | 325 ++++++++++++++++++++++++++++++++++
1 file changed, 325 insertions(+)
create mode 100644 Documentation/misc-devices/etzkx.txt

diff --git a/Documentation/misc-devices/etzkx.txt b/Documentation/misc-devices/etzkx.txt
new file mode 100644
index 0000000..5d2017c
--- /dev/null
+++ b/Documentation/misc-devices/etzkx.txt
@@ -0,0 +1,325 @@
+ ETZKX - State Machine Interrupt Driven Accelerometer
+ ====================================================
+
+Author: Andi Shyti <[email protected]>
+
+The etzkx driver provides support for the programmable state machine interrupt
+driven 3-axis accelerometer.
+The chip is manifactured by:
+
+ ROHM Semiconductor: kxcnl-1010
+
+Device description
+------------------
+The kxcnl-1010 accelerometer is an ultra low-power device based on a
+differential capacitance arising from acceleration-induced motion of the sensor
+element. The device has a dynamically user selectable sensitivity scale of
++-2/+-4/+-6/+-8g. Moreover the user has the possibility to select the sampling
+frequency (odr) from 3.125Hz to 1.6kHz.
+
+The X, Y, Z coordinates are accessible by polling the device. Interrupt
+generation on data ready is also possible if preferred.
+
+The device is capable of running two state machines for gesture recognition
+such as orientation change, shake, tap, etc. Two sets of registers are used to
+load the algorithms for the state machine.
+
+The communication with the CPU is done via I2C bus. Two interrupt lines allow
+signalling the state machines behavior and possibly the presence of new
+data.
+
+The device implements a hardware selftest functionality which allows testing of
+the reliability of the device.
+
+The driver
+----------
+The driver is located under
+
+ driver/misc/etzkx.c
+ include/linux/i2c/etzkx.h
+
+and is placed in between the i2c driver and the userspace interfaces
+
+ __________________________
+ | | | |
+ | input | sysfs | char |
+ | device | | device |
+ |________|________|________|
+ | |
+ | etzkx |
+ |__________________________|
+ | |
+ | i2c smbus |
+ |__________________________|
+ | |
+ | i2c xfer |
+ | |
+ +==========================+
+ | |
+ | kxcnl-1010 |
+ |__________________________|
+
+
+In the menuconfig the driver is reachable
+
+ Device Drivers --->
+ [*] Misc devices --->
+ <*> ETZKX kxcnl 3d digital accelerometer
+
+The interfaces
+--------------
+The driver generates three types of interfaces:
+
+ * sysfs: provides information related to the device and sets some basic
+ parameters like output data rate (odr) and range.
+
+ * input event: provides the X, Y, Z coordinates.
+
+ * character device: provides state machine related information.
+
+Sysfs interfaces
+----------------
+Here is a list of all the sysfs interfaces generated by the driver
+
+hwid
+ RO - shows the chip installed on the board
+
+enable
+ RW - enables/disables the streaming of X, Y, Z coordinates which are
+ readable from the input event file
+ 1 enables the the streaming
+ 0 disables the streaming
+
+odr
+ RW - sets the output data rate of the chip, i.e. sets the frequency of
+ working of the chip. The available data rates are (in Hz) 3.125,
+ 6.25, 12.5, 25, 50, 100, 400, 1600. It is possible to write a
+ generic frequency and the driver will normalize it to the closest
+ available. When a state machine is running the user is prevented
+ form changing the odr.
+
+delay
+ RW - sets the driver polling rate in ms. Usually is
+
+ 1
+ delay = -----
+ odr
+
+ but depending of some state machine conditions, odr and delay
+ might not be synchronized.
+
+range
+ RW - sets the sensitivity measured in g. The available g range are
+ +-2g, +-4g, +-6g, +-8g. When a state machine is running, the user
+ is prevented from changing the range.
+
+self_test
+ RW - enables/disables the self-test functionality. When the self_test is
+ enabled the X, Y, Z axes are affected by an offset, that offset
+ allows to evaluate the liability of the device (refer to the
+ datasheets for the self test patterns)
+ 1 enables self test
+ 0 disables self test
+
+drv_version
+ RO - shows the driver version
+
+Input event interface
+---------------------
+The input event is generated under /dev/input/eventX, where X is an incremental
+number chosen by the input driver framework.
+
+To eventX file is associated the 'etzkx' name which is discoverable using an
+ioctl function with EVIOCGNAME flag.
+
+ DIR *dir;
+ struct dirent *de;
+ char *fname;
+ [...]
+
+ dir = opendir("/dev/input");
+ while ( (de = readdir(dir)) )
+ {
+ fd = open(de->d_name, O_RDONLY))
+ ioctl(fd, EVIOCGNAME(sizeof(fname) - 1), &fname)
+
+ if (!strcmp(fname, "etzkx"))
+ /*** found it! ***/
+
+ close (fd);
+ }
+
+The driver streams the coordinates every "delay" ms (or "odr" Hz) through this
+interface. The input driver sends a struct input_event to the interface buffer,
+which has the following structure:
+
+ #include <linux/input.h>
+
+ struct input_event {
+ struct timeval time;
+ __u16 type;
+ __u16 code;
+ __s32 value;
+ };
+
+where the type event is EV_ABS, the code of the event is ABS_X, ABS_Y, ABS_Z
+corresponding to the X, Y, Z coordiantes and in the end the value field is the
+specific coordinate value. The driver sends three different events for each
+coordinate followed by a synchronization event which has type=0, code=0 and
+value=0.
+
+A possible reading algorithm can be
+
+ struct input_event ev;
+
+ while (1)
+ {
+ do
+ {
+ read(fd, &ev, sizeof(struct input_event));
+
+ if (ev.type == EV_ABS)
+ switch (ev.code)
+ {
+ case ABS_X:
+ X = ev.value;
+ break;
+ case ABS_Y:
+ Y = ev.value;
+ break;
+ case ABS_Z:
+ Z = ev.value;
+ break;
+ }
+ } while (ev.type != EV_SYN);
+ }
+
+Character device
+----------------
+The character device is used to report to the userspace the running state
+machine's outcome. The interface is capable of polling so that it is possible to
+use select() or poll() on it. The character interface is generated as
+
+ /dev/etzkx_stm
+
+The driver communicates with userspace by writing 64 bit structure on the
+interface
+
+ +-----+--------------------------+
+ | 32 | algorithm id |
+ +-----+--------------------------+
+ | 32 | specific algorithm data |
+ +-----+--------------------------+
+
+Currently, three algorithms are implemented:
+
+ - algorithm id
+ ETZKX_STM_ID_TIMING: timing algorithm used for testing. It sends an interrupt
+ every 16 odr.
+
+ ETZKX_STM_ID_ORIENTATION: sends an interrupt every orientation change
+ (portrait or landscape)
+
+ ETZKX_STM_ID_DOUBLE_TAP: sends an interrupt every time that a double tap
+ is detected
+
+ - specific algorithm data
+ timing (bit order)
+ 32 bit: not relevant, uninitialized
+
+ orientation (bit order)
+ 16 bit: 1 if portrait, 0 otherwise
+ 16 bit: 1 if landscape, 0 otherwise
+
+ double tap (bit order)
+ 8 bit: 1 if +x, 2 if -x, 0 otherwise
+ 8 bit: 1 if +y, 2 if -y, 0 otherwise
+ 8 bit: 1 if +z, 2 if -z, 0 otherwise
+ 8 bit: double tap peak
+
+The /dev/etzkx_stm file is capable of ioctl with the following flags
+
+ ETZKXIO_ENABLE_TIMING: enables timing algorithm
+ ETZKXIO_DISABLE_TIMING: disable timing algorithm
+ ETZKXIO_ENABLE_ORIENTATION: enables orientation algorithm
+ ETZKXIO_DISABLE_ORIENTATION: disables orientation algorithm
+ ETZKXIO_ENABLE_DOUBLE_TAP: enables double tap algorithm
+ ETZKXIO_DISABLE_DOUBLE_TAP: disables double tap algorithm
+
+ ETZKXIO_WHICH_ORIENTATION: asks to the driver the actual orientation
+ status. The driver provides via ioctl an algorithm structure like
+ this:
+
+ 32 bit: ETZKX_STM_ID_ORIENTATION
+ 16 bit: 1 if portrait, 0 otherwise
+ 16 bit: 1 if landscape, 0 otherwise
+
+ ETZKXIO_INSTANT_ORIENTATION: like ETZKXIO_WHICH_ORIENTATION, with the
+ difference that the calculation of the orientation is calculated by
+ comparing the X and Y coordinates.
+
+ ETZKXIO_RUNNING_ALGO: reports to the userspace the two running
+ algorithms for each state machine slot:
+
+ 32 bit: running algorithm id on slot 1
+ 32 bit: running algorithm id on slot 2
+
+ if no state machine is loaded, the id is equal to ETZKX_STM_ID_NO_STM
+
+Driver usage
+------------
+The driver follows a state machine way of working and at any time it tracks the
+state which the driver is in.
+
+ __STRM --- STRM + STM1
+ / \/ \
+ / /\ \
+ STBY --- ACTIVE --- STM1 STRM + STM2 --- STRM + STM1 + STM2
+ \ \/ /
+ \__ /\ /
+ STM2 --- STM1 + STM2
+
+To reach each driver state, userspace applications have to enable/disable the
+available features using the related interfaces.
+
+Enable/disable streaming (STRM state):
+ echo 1 > /sys/.../enable
+ echo 0 > /sys/.../enable
+
+Enable/disable state machine (STM1/STM2 state):
+ ioctl (fd, ETZKXIO_ENABLE_ORIENTATION, 0);
+ ioctl (fd, ETZKXIO_ENABLE_DOUBLE_TAP, 0);
+ ioctl (fd, ETZKXIO_DISABLE_ORIENTATION, 0);
+ ioctl (fd, ETZKXIO_DISABLE_DOUBLE_TAP, 0);
+
+Self test
+---------
+The self test checks the electromechanical functionality of the sensor. The
+driver sets the self test state as a specific branch of the driver state
+machine:
+
+ _
+ / ST
+ / ^
+ STDBY <---> ACTIVE |
+ \ |
+ \_ v
+ STRM
+
+To enable/disable the self test
+
+ echo 1 > /sys/.../self_test
+ echo 0 > /sys/.../self_test
+
+This applies an extra capacitance to every axes which adds an offset to the X,
+Y, Z output, it can be different for each coordinate.
+
+The suppliers provides the minimum Dx, Dy, Dz offset in order to consider the
+device reliable. The evaluation should be done like follows:
+
+ |Xst - X| > Dx
+ |Yst - Y| > Dy
+ |Zst - Z| > Dz
+
+where Xst, Yst and Zst are the coordinates measured after applying the self test
+on the accelerometer.
--
1.7.10.4

2013-06-16 21:50:35

by Greg Kroah-Hartman

[permalink] [raw]
Subject: Re: [PATCH 0/2] etzkx accelerometer support

On Sun, Jun 16, 2013 at 11:40:26PM +0200, Andi Shyti wrote:
> Hi,
>
> these two patches provide the etzkx device driver which supports
> the following accelerometers:
>
> - Kionix kxcnl
> - Kionix kxtnk
> - ST lisn3dsh
>
> Support for ST lis3dsh accelerometer is still in progress.
>
> The etzkx accelerometers are a kind of devices that are able to
> do gesture recognition directly on the device. It uses two slots
> of 16 steps (bytes) each for loading algorithms which perform
> gesture recognition.
>
> The second patch contains the documentation of the device driver.
>
> On http://www.etezian.org/etzkx/etzkx/ is possible to find more
> informations about the accelerometer.

Why isn't this a drivers/iio/accel/ driver instead of a misc driver?
Please make this an iio driver, then you will be using the correct
user/kernel api to talk to your device.

thanks,

greg k-h

2013-06-16 22:25:27

by Andi Shyti

[permalink] [raw]
Subject: Re: [PATCH 0/2] etzkx accelerometer support

> > these two patches provide the etzkx device driver which supports
> > the following accelerometers:
> >
> > - Kionix kxcnl
> > - Kionix kxtnk
> > - ST lisn3dsh
> >
> > Support for ST lis3dsh accelerometer is still in progress.
> >
> > The etzkx accelerometers are a kind of devices that are able to
> > do gesture recognition directly on the device. It uses two slots
> > of 16 steps (bytes) each for loading algorithms which perform
> > gesture recognition.
> >
> > The second patch contains the documentation of the device driver.
> >
> > On http://www.etezian.org/etzkx/etzkx/ is possible to find more
> > informations about the accelerometer.
>
> Why isn't this a drivers/iio/accel/ driver instead of a misc driver?
> Please make this an iio driver, then you will be using the correct
> user/kernel api to talk to your device.

I was thinking it may be an issue, but it's not iio because the
time I developed the driver, iio was in the first staging period
and mainly because the on the board I'm using I cannot update the
Kernel to use the latest iio.

I guess you don't like the promise "I can do it later" once the
supplier sends me a development board (which is planned) :)

Andi

2013-06-17 02:58:18

by Greg Kroah-Hartman

[permalink] [raw]
Subject: Re: [PATCH 0/2] etzkx accelerometer support

On Mon, Jun 17, 2013 at 12:26:48AM +0200, Andi Shyti wrote:
> > > these two patches provide the etzkx device driver which supports
> > > the following accelerometers:
> > >
> > > - Kionix kxcnl
> > > - Kionix kxtnk
> > > - ST lisn3dsh
> > >
> > > Support for ST lis3dsh accelerometer is still in progress.
> > >
> > > The etzkx accelerometers are a kind of devices that are able to
> > > do gesture recognition directly on the device. It uses two slots
> > > of 16 steps (bytes) each for loading algorithms which perform
> > > gesture recognition.
> > >
> > > The second patch contains the documentation of the device driver.
> > >
> > > On http://www.etezian.org/etzkx/etzkx/ is possible to find more
> > > informations about the accelerometer.
> >
> > Why isn't this a drivers/iio/accel/ driver instead of a misc driver?
> > Please make this an iio driver, then you will be using the correct
> > user/kernel api to talk to your device.
>
> I was thinking it may be an issue, but it's not iio because the
> time I developed the driver, iio was in the first staging period
> and mainly because the on the board I'm using I cannot update the
> Kernel to use the latest iio.
>
> I guess you don't like the promise "I can do it later" once the
> supplier sends me a development board (which is planned) :)

Sure, I'll gladly accept "I can do it later" from anyone, as long as you
don't mind my, "I will merge it later" as well :)

Sorry, I can't accept a driver now, that is known to going to be
changing the kernel/user api in the future. So please, rewrite it to
use IIO, that's what that interface / subsystem is there for.

thanks,

greg k-h

2013-06-17 08:26:27

by Andi Shyti

[permalink] [raw]
Subject: Re: [PATCH 0/2] etzkx accelerometer support

> > > > these two patches provide the etzkx device driver which supports
> > > > the following accelerometers:
> > > >
> > > > - Kionix kxcnl
> > > > - Kionix kxtnk
> > > > - ST lisn3dsh
> > > >
> > > > Support for ST lis3dsh accelerometer is still in progress.
> > > >
> > > > The etzkx accelerometers are a kind of devices that are able to
> > > > do gesture recognition directly on the device. It uses two slots
> > > > of 16 steps (bytes) each for loading algorithms which perform
> > > > gesture recognition.
> > > >
> > > > The second patch contains the documentation of the device driver.
> > > >
> > > > On http://www.etezian.org/etzkx/etzkx/ is possible to find more
> > > > informations about the accelerometer.
> > >
> > > Why isn't this a drivers/iio/accel/ driver instead of a misc driver?
> > > Please make this an iio driver, then you will be using the correct
> > > user/kernel api to talk to your device.
> >
> > I was thinking it may be an issue, but it's not iio because the
> > time I developed the driver, iio was in the first staging period
> > and mainly because the on the board I'm using I cannot update the
> > Kernel to use the latest iio.
> >
> > I guess you don't like the promise "I can do it later" once the
> > supplier sends me a development board (which is planned) :)
>
> Sure, I'll gladly accept "I can do it later" from anyone, as long as you
> don't mind my, "I will merge it later" as well :)
>
> Sorry, I can't accept a driver now, that is known to going to be
> changing the kernel/user api in the future. So please, rewrite it to
> use IIO, that's what that interface / subsystem is there for.

Fair enough! Let's accept the "I'll do it later" from each other :)

Then... see you later and thanks,
Andi

2013-08-08 22:21:27

by Andi Shyti

[permalink] [raw]
Subject: Re: [PATCH 0/2] etzkx accelerometer support

Hi Greg,

> > > > these two patches provide the etzkx device driver which supports
> > > > the following accelerometers:
> > >
> > > Why isn't this a drivers/iio/accel/ driver instead of a misc driver?
> > > Please make this an iio driver, then you will be using the correct
> > > user/kernel api to talk to your device.
> >
> > I was thinking it may be an issue, but it's not iio because the
> > time I developed the driver, iio was in the first staging period
> > and mainly because the on the board I'm using I cannot update the
> > Kernel to use the latest iio.
> >
> > I guess you don't like the promise "I can do it later" once the
> > supplier sends me a development board (which is planned) :)
>
> Sure, I'll gladly accept "I can do it later" from anyone, as long as you
> don't mind my, "I will merge it later" as well :)
>
> Sorry, I can't accept a driver now, that is known to going to be
> changing the kernel/user api in the future. So please, rewrite it to
> use IIO, that's what that interface / subsystem is there for.

What about submitting this accelerometer driver under staging
even if it's still not iio and slowly put it in shape?

Andi

2013-08-08 22:25:26

by Greg Kroah-Hartman

[permalink] [raw]
Subject: Re: [PATCH 0/2] etzkx accelerometer support

On Fri, Aug 09, 2013 at 12:21:08AM +0200, Andi Shyti wrote:
> Hi Greg,
>
> > > > > these two patches provide the etzkx device driver which supports
> > > > > the following accelerometers:
> > > >
> > > > Why isn't this a drivers/iio/accel/ driver instead of a misc driver?
> > > > Please make this an iio driver, then you will be using the correct
> > > > user/kernel api to talk to your device.
> > >
> > > I was thinking it may be an issue, but it's not iio because the
> > > time I developed the driver, iio was in the first staging period
> > > and mainly because the on the board I'm using I cannot update the
> > > Kernel to use the latest iio.
> > >
> > > I guess you don't like the promise "I can do it later" once the
> > > supplier sends me a development board (which is planned) :)
> >
> > Sure, I'll gladly accept "I can do it later" from anyone, as long as you
> > don't mind my, "I will merge it later" as well :)
> >
> > Sorry, I can't accept a driver now, that is known to going to be
> > changing the kernel/user api in the future. So please, rewrite it to
> > use IIO, that's what that interface / subsystem is there for.
>
> What about submitting this accelerometer driver under staging
> even if it's still not iio and slowly put it in shape?

There are a number of staging iio drivers already, why not put it there?

I don't want to accept a driver with a user/kernel interface that we
know is wrong, into staging. What is wrong with taking a few days to
fix this up now? If you aren't willing to do it now, why would I
believe that you will be willing to do it in the future once it is in
staging? :)

thanks,

greg k-h