blob: d192dec6e9e890b98dcebf2f4df1bfed4c3d9d23 [file] [log] [blame]
/******************** (C) COPYRIGHT 2012 STMicroelectronics ********************
*
* File Name : lis3dsh_acc.c
* Authors : MSH - Motion Mems BU - Application Team
* : Matteo Dameno (matteo.dameno@st.com)
* : Denis Ciocca (denis.ciocca@st.com)
* : Author is willing to be considered the contact
* : and update point for the driver.
* Version : V.1.0.0
* Date : 2012/08/01
* Description : LIS3DSH accelerometer driver
*
*******************************************************************************
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* THE PRESENT SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES
* OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, FOR THE SOLE
* PURPOSE TO SUPPORT YOUR APPLICATION DEVELOPMENT.
* AS A RESULT, STMICROELECTRONICS SHALL NOT BE HELD LIABLE FOR ANY DIRECT,
* INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING FROM THE
* CONTENT OF SUCH SOFTWARE AND/OR THE USE MADE BY CUSTOMERS OF THE CODING
* INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS.
*
* THIS SOFTWARE IS SPECIFICALLY DESIGNED FOR EXCLUSIVE USE WITH ST PARTS.
*
******************************************************************************
Version History.
V 1.0.0 First Release
******************************************************************************/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/delay.h>
#include <linux/fs.h>
#include <linux/i2c.h>
#include <linux/input.h>
#include <linux/uaccess.h>
#include <linux/workqueue.h>
#include <linux/irq.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/wakelock.h>
#include <linux/input/lis3dsh.h>
#include <asm/intel_scu_flis.h>
/* TILT_WAKELOCK_HOLD_MS defines time to hold wakelock to allow receiver of
* tilt event to grab their own wakelock
*/
#define TILT_WAKELOCK_HOLD_MS (HZ / 2)
#define TILT_LOCK_NAME "tilt_detect"
#define DEBUG
#define LOAD_STATE_PROGRAM1
#define LOAD_SP1_PARAMETERS
#if 0 /* Define to enable SM2 */
#define LOAD_STATE_PROGRAM2
#define LOAD_SP2_PARAMETERS
#endif
#define I2C_RETRY_DELAY 5 /* Waiting for signals [ms] */
#define I2C_RETRIES 5 /* Number of retries */
#define I2C_AUTO_INCREMENT 0x80 /* Autoincrement i2c address */
#define SENSITIVITY_2G 60 /* ug/LSB */
#define SENSITIVITY_4G 120 /* ug/LSB */
#define SENSITIVITY_6G 180 /* ug/LSB */
#define SENSITIVITY_8G 240 /* ug/LSB */
#define SENSITIVITY_16G 730 /* ug/LSB */
#define LIS3DSH_ACC_FS_MASK (0x38)
/* Output Data Rates ODR */
#define LIS3DSH_ODR_MASK 0XF0
#define LIS3DSH_PM_OFF 0x00 /* OFF */
#define LIS3DSH_ODR3_125 0x10 /* 3.125 Hz */
#define LIS3DSH_ODR6_25 0x20 /* 6.25 Hz */
#define LIS3DSH_ODR12_5 0x30 /* 12.5 Hz */
#define LIS3DSH_ODR25 0x40 /* 25 Hz */
#define LIS3DSH_ODR50 0x50 /* 50 Hz */
#define LIS3DSH_ODR100 0x60 /* 100 Hz */
#define LIS3DSH_ODR400 0x70 /* 400 Hz */
#define LIS3DSH_ODR800 0x80 /* 800 Hz */
#define LIS3DSH_ODR1600 0x90 /* 1600 Hz */
/* Registers configuration Mask and settings */
/* CTRLREG1 */
#define LIS3DSH_INTEN_MASK 0x01
#define LIS3DSH_INTEN_OFF 0x00
#define LIS3DSH_INTEN_ON 0x01
/* CTRLREG2 */
#define LIS3DSH_HIST1_MASK 0xE0
#define LIS3DSH_SM1INT_PIN_MASK 0x08
#define LIS3DSH_SM1INT_PINB 0x08
#define LIS3DSH_SM1INT_PINA 0x00
#define LIS3DSH_SM1_EN_MASK 0x01
#define LIS3DSH_SM1_EN_ON 0x01
#define LIS3DSH_SM1INT_PINA 0x00
#define LIS3DSH_SM1_EN_MASK 0x01
#define LIS3DSH_SM1_EN_ON 0x01
#define LIS3DSH_SM1_EN_OFF 0x00
/* */
/* CTRLREG3 */
#define LIS3DSH_HIST2_MASK 0xE0
#define LIS3DSH_SM2INT_PIN_MASK 0x08
#define LIS3DSH_SM2INT_PINB 0x08
#define LIS3DSH_SM2INT_PINA 0x00
#define LIS3DSH_SM2_EN_MASK 0x01
#define LIS3DSH_SM2_EN_ON 0x01
#define LIS3DSH_SM2_EN_OFF 0x00
/* */
/* CTRLREG4 */
#define LIS3DSH_INT_ACT_MASK (0x01 << 6)
#define LIS3DSH_INT_ACT_H (0x01 << 6)
#define LIS3DSH_INT_ACT_L 0x00
#define LIS3DSH_INT2_EN_MASK (0x01 << 4)
#define LIS3DSH_INT2_EN_ON (0x01 << 4)
#define LIS3DSH_INT2_EN_OFF 0x00
#define LIS3DSH_INT1_EN_MASK (0x01 << 3)
#define LIS3DSH_INT1_EN_ON (0x01 << 3)
#define LIS3DSH_INT1_EN_OFF 0x00
#ifdef CONFIG_ST_LIS3DSH_SELFTEST
/* Macro for manufacturing test */
#define LIS3DSH_ST_MASK 0x06
#define LIS3DSH_ST_PS (0x01 << 1)
#define LIS3DSH_ST_NS (0x01 << 2)
#define LIS3DSH_ST_OFF 0x00
#define LIS3DSH_DIFF_MIN 70000 /* ug */
#define LIS3DSH_DIFF_MAX 1400000 /* ug */
#define ABS(x) (((x) >= 0) ? (x) : (0 - (x)))
#define MAX_READ_COUNTER 50
#define LIS3DSH_SAMPLE_NUM 5
#define DELAY_DOR_SAMPLE 200
#endif
#define OUT_AXISDATA_REG LIS3DSH_OUTX_L
#define WHOAMI_LIS3DSH_ACC 0x3F /* Expected content for WAI */
/* CONTROL REGISTERS */
#define LIS3DSH_WHO_AM_I 0x0F /* WhoAmI register Address */
#define LIS3DSH_OUTX_L 0x28 /* Output X LSByte */
#define LIS3DSH_OUTX_H 0x29 /* Output X MSByte */
#define LIS3DSH_OUTY_L 0x2A /* Output Y LSByte */
#define LIS3DSH_OUTY_H 0x2B /* Output Y MSByte */
#define LIS3DSH_OUTZ_L 0x2C /* Output Z LSByte */
#define LIS3DSH_OUTZ_H 0x2D /* Output Z MSByte */
#define LIS3DSH_LC_L 0x16 /* LSByte Long Counter Status */
#define LIS3DSH_LC_H 0x17 /* MSByte Long Counter Status */
#define LIS3DSH_STATUS_REG 0x27 /* Status */
#define ZYXOR_MASK (1 << 7) /* X, Y, and Z axis data overrun. */
#define ZOR_MASK (1 << 6) /* Z axis data overrun. */
#define YOR_MASK (1 << 5) /* Y axis data overrun. */
#define XOR_MASK (1 << 4) /* X axis data overrun. */
#define ZYXDA_RDY_MASK (1 << 3) /* X, Y, and Z axis new data available. */
#define ZDA_RDY_MASK (1 << 2) /* Z axis new data available. */
#define YDA_RDY_MASK (1 << 1) /* Y axis new data available. */
#define XDA_RDY_MASK (1 << 0) /* X axis new data available. */
#define LIS3DSH_CTRL_REG1 0x20 /* control reg 1 */
#define LIS3DSH_CTRL_REG2 0x21 /* control reg 2 */
#define LIS3DSH_CTRL_REG3 0x22 /* control reg 3 */
#define LIS3DSH_CTRL_REG4 0x23 /* control reg 4 */
#define LIS3DSH_CTRL_REG5 0x24 /* control reg 3 */
#define LIS3DSH_CTRL_REG6 0x25 /* control reg 4 */
#define LIS3DSH_OFF_X 0x10 /* Offset X Corr */
#define LIS3DSH_OFF_Y 0x11 /* Offset Y Corr */
#define LIS3DSH_OFF_Z 0x12 /* Offset Z Corr */
#define LIS3DSH_CS_X 0x13 /* Const Shift X */
#define LIS3DSH_CS_Y 0x14 /* Const Shift Y */
#define LIS3DSH_CS_Z 0x15 /* Const Shift Z */
#define LIS3DSH_VFC_1 0x1B /* Vect Filter Coeff 1 */
#define LIS3DSH_VFC_2 0x1C /* Vect Filter Coeff 2 */
#define LIS3DSH_VFC_3 0x1D /* Vect Filter Coeff 3 */
#define LIS3DSH_VFC_4 0x1E /* Vect Filter Coeff 4 */
/* state program 1 */
#define LIS3DSH_STATEPR1 0X40 /* State Program 1 16 bytes */
#define LIS3DSH_TIM4_1 0X50 /* SPr1 Timer4 */
#define LIS3DSH_TIM3_1 0X51 /* SPr1 Timer3 */
#define LIS3DSH_TIM2_1 0X52 /* SPr1 Timer2 2bytes */
#define LIS3DSH_TIM1_1 0X54 /* SPr1 Timer1 2bytes */
#define LIS3DSH_THRS2_1 0X56 /* SPr1 Threshold1 */
#define LIS3DSH_THRS1_1 0X57 /* SPr1 Threshold2 */
#define LIS3DSH_SA_1 0X59 /* SPr1 Swap Axis Sign Msk */
#define LIS3DSH_MA_1 0X5A /* SPr1 Axis Sign Msk */
#define LIS3DSH_SETT_1 0X5B /* SPr1 */
#define LIS3DSH_PPRP_1 0X5C /* SPr1 ProgPointer ResetPointer */
#define LIS3DSH_TC_1 0X5D /* SPr1 2bytes */
#define LIS3DSH_OUTS_1 0X5F /* SPr1 */
/* state program 2 */
#define LIS3DSH_STATEPR2 0X60 /* State Program 2 16 bytes */
#define LIS3DSH_TIM4_2 0X70 /* SPr2 Timer4 */
#define LIS3DSH_TIM3_2 0X71 /* SPr2 Timer3 */
#define LIS3DSH_TIM2_2 0X72 /* SPr2 Timer2 2bytes */
#define LIS3DSH_TIM1_2 0X74 /* SPr2 Timer1 2bytes */
#define LIS3DSH_THRS2_2 0X76 /* SPr2 Threshold1 */
#define LIS3DSH_THRS1_2 0X77 /* SPr2 Threshold2 */
#define LIS3DSH_DES_2 0X78 /* SPr2 Decimation */
#define LIS3DSH_SA_2 0X79 /* SPr2 Swap Axis Sign Msk */
#define LIS3DSH_MA_2 0X7A /* SPr2 Axis Sign Msk */
#define LIS3DSH_SETT_2 0X7B /* SPr2 */
#define LIS3DSH_PPRP_2 0X7C /* SPr2 ProgPointer ResetPointer */
#define LIS3DSH_TC_2 0X7D /* SPr2 2bytes */
#define LIS3DSH_OUTS_2 0X7F /* SPr2 */
/* end CONTROL REGISTRES */
/* LIS3DSH State Machine Opcodes */
#define NOP 0x00 /* No operation */
#define TI1_OPCODE 0x01 /* Timer 1 (8-bit value) valid */
#define TI2_OPCODE 0x02 /* Timer 2 (8-bit value) valid */
#define TI3_OPCODE 0x03 /* Timer 3 (8-bit value) valid */
#define TI4_OPCODE 0x04 /* Timer 4 (8-bit value) valid */
#define GNTH1_OPCODE 0x05 /* Any/triggered axis > THRS1 */
#define GNTH2_OPCODE 0x06 /* Any/triggered axis > THRS2 */
#define LNTH1_OPCODE 0x07 /* Any/triggered axis <= THRS1 */
#define LNTH2_OPCODE 0x08 /* Any/triggered axis <= THRS2 */
#define GTTH1_OPCODE 0x09 /* Any/triggered axis > THRS1 but using MASK1 */
#define LLTH2_OPCODE 0x0A /* All axis <= to THRS2 */
#define GRTH1_OPCODE 0x0B /* Any/triggered axis > reversed THRS1 */
#define LRTH1_OPCODE 0x0C /* Any/triggered axis <= reversed THRS1 */
#define GRTH2_OPCODE 0x0D /* Any/triggered axis > reversed THRS2 */
#define LRTH2_OPCODE 0x0E /* Any/triggered axis <= reversed THRS2 */
#define NZERO_OPCODE 0x0F /* Any axis zero crossed */
/* LIS3DSH State Machine Commands */
#define STOP_CMD 0x00 /* Stops execution, and resets to start */
#define CONT_CMD 0x11 /* Continues execution from RESET- POINT */
#define JMP_CMD 0x22
#define SRP_CMD 0x33
#define CRP_CMD 0x44
#define SETP_CMD 0x55
#define SETS1_CMD 0x66
#define STHR1_CMD 0x77
#define OUTC_CMD 0x88
#define OUTW_CMD 0x99
#define STHR2_CMD 0xAA
#define DEC_CMD 0xBB
#define SISW_CMD 0xCC
#define REL_CMD 0xDD
#define STHR3_CMD 0xEE
#define SSYNC_CMD 0xFF
#define SABS0_CMD 0x12 /* Set SETTy(ABS=0) (unsigned threshold) */
#define SABS1_CMD 0x13 /* Set SETTy(ABS=1) (signed threshold) */
#define SELMA_CMD 0x14 /* Set mask to MASK_A */
#define SELSA_CMD 0x24 /* Set mask to MASK_B */
/* RESUME STATE INDICES */
#define LIS3DSH_RES_LC_L 0
#define LIS3DSH_RES_LC_H 1
#define LIS3DSH_RES_CTRL_REG1 2
#define LIS3DSH_RES_CTRL_REG2 3
#define LIS3DSH_RES_CTRL_REG3 4
#define LIS3DSH_RES_CTRL_REG4 5
#define LIS3DSH_RES_CTRL_REG5 6
#define LIS3DSH_RES_TIM4_1 20
#define LIS3DSH_RES_TIM3_1 21
#define LIS3DSH_RES_TIM2_1_L 22
#define LIS3DSH_RES_TIM2_1_H 23
#define LIS3DSH_RES_TIM1_1_L 24
#define LIS3DSH_RES_TIM1_1_H 25
#define LIS3DSH_RES_THRS2_1 26
#define LIS3DSH_RES_THRS1_1 27
#define LIS3DSH_RES_SA_1 28
#define LIS3DSH_RES_MA_1 29
#define LIS3DSH_RES_SETT_1 30
#define LIS3DSH_RES_TIM4_2 31
#define LIS3DSH_RES_TIM3_2 32
#define LIS3DSH_RES_TIM2_2_L 33
#define LIS3DSH_RES_TIM2_2_H 34
#define LIS3DSH_RES_TIM1_2_L 35
#define LIS3DSH_RES_TIM1_2_H 36
#define LIS3DSH_RES_THRS2_2 37
#define LIS3DSH_RES_THRS1_2 38
#define LIS3DSH_RES_DES_2 39
#define LIS3DSH_RES_SA_2 40
#define LIS3DSH_RES_MA_2 41
#define LIS3DSH_RES_SETT_2 42
#define LIS3DSH_RESUME_ENTRIES 43
#define LIS3DSH_STATE_PR_SIZE 16
/* end RESUME STATE INDICES */
/* STATE PROGRAMS ENABLE CONTROLS */
#define LIS3DSH_SM1_DIS_SM2_DIS 0x00
#define LIS3DSH_SM1_DIS_SM2_EN 0x01
#define LIS3DSH_SM1_EN_SM2_DIS 0x02
#define LIS3DSH_SM1_EN_SM2_EN 0x03
/* INTERRUPTS ENABLE CONTROLS */
#define LIS3DSH_INT1_DIS_INT2_DIS 0x00
#define LIS3DSH_INT1_DIS_INT2_EN 0x01
#define LIS3DSH_INT1_EN_INT2_DIS 0x02
#define LIS3DSH_INT1_EN_INT2_EN 0x03
struct {
unsigned int cutoff_ms;
unsigned int mask;
} lis3dsh_acc_odr_table[] = {
{ 1, LIS3DSH_ODR1600 },
{ 3, LIS3DSH_ODR400 },
{ 10, LIS3DSH_ODR100 },
{ 20, LIS3DSH_ODR50 },
{ 40, LIS3DSH_ODR25 },
{ 80, LIS3DSH_ODR12_5 },
{ 160, LIS3DSH_ODR6_25 },
{ 320, LIS3DSH_ODR3_125},
};
static const struct lis3dsh_acc_platform_data default_lis3dsh_acc_pdata = {
.fs_range = LIS3DSH_ACC_G_2G,
.axis_map_x = 0,
.axis_map_y = 1,
.axis_map_z = 2,
.negate_x = 0,
.negate_y = 0,
.negate_z = 0,
.poll_interval = 100,
.min_interval = LIS3DSH_ACC_MIN_POLL_PERIOD_MS,
};
struct lis3dsh_acc_data {
struct i2c_client *client;
struct lis3dsh_acc_platform_data *pdata;
struct mutex lock;
struct input_dev *input_dev;
int hw_initialized;
/* hw_working=-1 means not tested yet */
int hw_working;
atomic_t enabled;
int on_before_suspend;
u16 sensitivity;
u8 stateprogs_enable_setting;
u8 interrupts_enable_setting;
u8 resume_state[LIS3DSH_RESUME_ENTRIES];
u8 resume_stmach_program1[LIS3DSH_STATE_PR_SIZE];
u8 resume_stmach_program2[LIS3DSH_STATE_PR_SIZE];
int irq1;
int irq2;
struct wake_lock tilt_wakelock;
#ifdef DEBUG
u8 reg_addr;
#endif
};
static int lis3dsh_acc_disable(struct lis3dsh_acc_data *acc);
static void report_motion(struct lis3dsh_acc_data *acc);
/* sets default init values to be written in registers at probe stage */
static void lis3dsh_acc_set_init_register_values(struct lis3dsh_acc_data *acc)
{
acc->resume_state[LIS3DSH_RES_LC_L] = 0x01;
acc->resume_state[LIS3DSH_RES_LC_H] = 0x00;
acc->resume_state[LIS3DSH_RES_CTRL_REG2] = 0x01;
acc->resume_state[LIS3DSH_RES_CTRL_REG1] = 0x57;
acc->resume_state[LIS3DSH_RES_CTRL_REG4] = 0x58;
acc->resume_state[LIS3DSH_RES_CTRL_REG3] = 0x00;
acc->resume_state[LIS3DSH_RES_CTRL_REG5] = 0x00;
}
static void lis3dsh_acc_set_init_statepr1_inst(struct lis3dsh_acc_data *acc)
{
#ifdef LOAD_STATE_PROGRAM1
acc->resume_stmach_program1[0] = LNTH1_OPCODE;
acc->resume_stmach_program1[1] = (GNTH1_OPCODE << 4) | TI1_OPCODE;
acc->resume_stmach_program1[2] = SETP_CMD;
acc->resume_stmach_program1[3] = 0x50;
acc->resume_stmach_program1[4] = 0x01;
acc->resume_stmach_program1[5] = OUTC_CMD;
acc->resume_stmach_program1[6] = SRP_CMD;
acc->resume_stmach_program1[7] = GNTH1_OPCODE;
acc->resume_stmach_program1[8] = (LNTH1_OPCODE << 4) | TI1_OPCODE;
acc->resume_stmach_program1[9] = SETP_CMD;
acc->resume_stmach_program1[10] = 0x50;
acc->resume_stmach_program1[11] = 0x02;
acc->resume_stmach_program1[12] = OUTC_CMD;
acc->resume_stmach_program1[13] = CRP_CMD;
acc->resume_stmach_program1[14] = CONT_CMD;
acc->resume_stmach_program1[15] = 0x00;
#endif
}
static void lis3dsh_acc_set_init_statepr2_inst(struct lis3dsh_acc_data *acc)
{
}
static void lis3dsh_acc_set_init_statepr1_param(struct lis3dsh_acc_data *acc)
{
#ifdef LOAD_SP1_PARAMETERS
acc->resume_state[LIS3DSH_RES_TIM4_1] = 0x00;
acc->resume_state[LIS3DSH_RES_TIM3_1] = 0x00;
acc->resume_state[LIS3DSH_RES_TIM2_1_L] = 0x00;
acc->resume_state[LIS3DSH_RES_TIM2_1_H] = 0x00;
acc->resume_state[LIS3DSH_RES_TIM1_1_L] = 0x0F;
acc->resume_state[LIS3DSH_RES_TIM1_1_H] = 0x00;
acc->resume_state[LIS3DSH_RES_THRS2_1] = 0x0;
acc->resume_state[LIS3DSH_RES_THRS1_1] = 0x1B;
/* DES1 not available*/
acc->resume_state[LIS3DSH_RES_SA_1] = 0x00;
acc->resume_state[LIS3DSH_RES_MA_1] = 0x20;
acc->resume_state[LIS3DSH_RES_SETT_1] = 0x20;
#endif
}
static void lis3dsh_acc_set_init_statepr2_param(struct lis3dsh_acc_data *acc)
{
}
static int lis3dsh_acc_i2c_read(struct lis3dsh_acc_data *acc,
u8 *buf, int len)
{
int err;
int tries = 0;
struct i2c_msg msgs[] = {
{
.addr = acc->client->addr,
.flags = acc->client->flags & I2C_M_TEN,
.len = 1,
.buf = buf,
},
{
.addr = acc->client->addr,
.flags = (acc->client->flags & I2C_M_TEN) | I2C_M_RD,
.len = len,
.buf = buf,
},
};
do {
err = i2c_transfer(acc->client->adapter, msgs, 2);
if (err != 2)
msleep_interruptible(I2C_RETRY_DELAY);
} while ((err != 2) && (++tries < I2C_RETRIES));
if (err != 2) {
dev_err(&acc->client->dev, "read transfer error\n");
err = -EIO;
} else {
err = 0;
}
return err;
}
static int lis3dsh_acc_i2c_write(struct lis3dsh_acc_data *acc, u8 *buf,
int len)
{
int err;
int tries = 0;
struct i2c_msg msgs[] = {
{
.addr = acc->client->addr,
.flags = acc->client->flags & I2C_M_TEN,
.len = len + 1,
.buf = buf,
},
};
do {
err = i2c_transfer(acc->client->adapter, msgs, 1);
if (err != 1)
msleep_interruptible(I2C_RETRY_DELAY);
} while ((err != 1) && (++tries < I2C_RETRIES));
if (err != 1) {
dev_err(&acc->client->dev, "write transfer error\n");
err = -EIO;
} else {
err = 0;
}
return err;
}
static int lis3dsh_acc_i2c_update(struct lis3dsh_acc_data *acc,
u8 reg_address, u8 mask, u8 new_bit_values)
{
int err = -1;
u8 rdbuf[1] = { reg_address };
u8 wrbuf[2] = { reg_address , 0x00 };
u8 init_val;
u8 updated_val;
err = lis3dsh_acc_i2c_read(acc, rdbuf, 1);
if (err < 0)
return err;
init_val = rdbuf[0];
updated_val = ((mask & new_bit_values) | ((~mask) & init_val));
wrbuf[1] = updated_val;
return lis3dsh_acc_i2c_write(acc, wrbuf, 1);
}
static int lis3dsh_acc_hw_init(struct lis3dsh_acc_data *acc)
{
int i;
int err = -1;
u8 buf[17];
pr_info("%s: hw init start\n", LIS3DSH_ACC_DEV_NAME);
buf[0] = LIS3DSH_WHO_AM_I;
err = lis3dsh_acc_i2c_read(acc, buf, 1);
if (err < 0) {
dev_warn(&acc->client->dev, "Error reading WHO_AM_I: is device available/working?\n");
goto err_firstread;
} else
acc->hw_working = 1;
if (buf[0] != WHOAMI_LIS3DSH_ACC) {
dev_err(&acc->client->dev, "device unknown. Expected: 0x%x, Replies: 0x%x\n",
WHOAMI_LIS3DSH_ACC, buf[0]);
err = -ENODEV; /* choose the right coded error */
goto err_unknown_device;
}
/**
* LC (16h - 17h)
* 16-bit long-counter register for interrupt state machine programs timing.
* 01h=counting stopped, 00h=counter full:interrupt available and counter is set to default.
* Values higher than 00h:counting
*/
buf[0] = (I2C_AUTO_INCREMENT | LIS3DSH_LC_L);
buf[1] = acc->resume_state[LIS3DSH_RES_LC_L];
buf[2] = acc->resume_state[LIS3DSH_RES_LC_H];
err = lis3dsh_acc_i2c_write(acc, buf, 2);
if (err < 0)
goto err_resume_state;
/**
* TIM4_1 (50h)
* 8-bit general timer (unsigned value) for SM1 operation timing.
* TIM3_1 (51h)
* 8-bit general timer (unsigned value) for SM1 operation timing.
* TIM2_1 (52h - 53h)
* 16-bit general timer (unsigned value) for SM1 operation timing.
* TIM1_1 (54h - 55h)
* 16-bit general timer (unsigned value) for SM1 operation timing.
* THRS2_1 (56h)
* Threshold value for SM1 operation.
* THRS1_1 (57h)
* Threshold value for SM1 operation.
*/
buf[0] = (I2C_AUTO_INCREMENT | LIS3DSH_TIM4_1);
buf[1] = acc->resume_state[LIS3DSH_RES_TIM4_1];
buf[2] = acc->resume_state[LIS3DSH_RES_TIM3_1];
buf[3] = acc->resume_state[LIS3DSH_RES_TIM2_1_L];
buf[4] = acc->resume_state[LIS3DSH_RES_TIM2_1_H];
buf[5] = acc->resume_state[LIS3DSH_RES_TIM1_1_L];
buf[6] = acc->resume_state[LIS3DSH_RES_TIM1_1_H];
buf[7] = acc->resume_state[LIS3DSH_RES_THRS2_1];
buf[8] = acc->resume_state[LIS3DSH_RES_THRS1_1];
err = lis3dsh_acc_i2c_write(acc, buf, 8);
if (err < 0)
goto err_resume_state;
/**
* MASK1_B (59h)
* Axis and sign mask (swap) for SM1 motion detection operation.
* MASK1_A (5Ah)
* Axis and sign mask (default) for SM1 motion detection operation.
* SETT1 (5Bh)
* Setting of threshold, peak detection and flags for SM1 motion detection operation.
*/
buf[0] = (I2C_AUTO_INCREMENT | LIS3DSH_SA_1);
buf[1] = acc->resume_state[LIS3DSH_RES_SA_1];
buf[2] = acc->resume_state[LIS3DSH_RES_MA_1];
buf[3] = acc->resume_state[LIS3DSH_RES_SETT_1];
err = lis3dsh_acc_i2c_write(acc, buf, 3);
if (err < 0)
goto err_resume_state;
/**
* TIM4_2 (70h)
* 8-bit general timer (unsigned value) for SM2 operation timing.
* TIM3_2 (71h)
* 8-bit general timer (unsigned value) for SM2 operation timing.
* TIM2_2 (72h - 73h)
* 16-bit general timer (unsigned value) for SM2 operation timing
* TIM1_2 (74h - 75h)
* 16-bit general timer (unsigned value) for SM2 operation timing.
* THRS2_2 (76h) Threshold signed value for SM2 operation.
* THRS1_2 (77h) Threshold signed value for SM2 operation.
* MASK2_B (79h) Axis and sign mask (swap) for SM2 motion detection operation.
* MASK2_A (7Ah) Axis and sign mask (default) for SM2 motion detection operation.
* SETT2 (7Bh) Setting of threshold, peak detection and flags for SM2 motion detection operation.
*/
buf[0] = (I2C_AUTO_INCREMENT | LIS3DSH_TIM4_2);
buf[1] = acc->resume_state[LIS3DSH_RES_TIM4_2];
buf[2] = acc->resume_state[LIS3DSH_RES_TIM3_2];
buf[3] = acc->resume_state[LIS3DSH_RES_TIM2_2_L];
buf[4] = acc->resume_state[LIS3DSH_RES_TIM2_2_H];
buf[5] = acc->resume_state[LIS3DSH_RES_TIM1_2_L];
buf[6] = acc->resume_state[LIS3DSH_RES_TIM1_2_H];
buf[7] = acc->resume_state[LIS3DSH_RES_THRS2_2];
buf[8] = acc->resume_state[LIS3DSH_RES_THRS1_2];
buf[9] = acc->resume_state[LIS3DSH_RES_DES_2];
buf[10] = acc->resume_state[LIS3DSH_RES_SA_2];
buf[11] = acc->resume_state[LIS3DSH_RES_MA_2];
buf[12] = acc->resume_state[LIS3DSH_RES_SETT_2];
err = lis3dsh_acc_i2c_write(acc, buf, 12);
if (err < 0)
goto err_resume_state;
/* state program 1 */
/**
* STx_1 (40h-4Fh)
* State machine 1 code register STx_1 (x = 1-16).
* State machine 1 system register is made up of 16, 8- bit registers
* to implement 16-step opcode.
*/
buf[0] = (I2C_AUTO_INCREMENT | LIS3DSH_STATEPR1);
for (i = 1; i <= LIS3DSH_STATE_PR_SIZE; i++) {
buf[i] = acc->resume_stmach_program1[i-1];
#ifdef DEBUG
pr_info("i=%d, st pr1 buf[i]=0x%x\n", i, buf[i]);
#endif
};
err = lis3dsh_acc_i2c_write(acc, buf, LIS3DSH_STATE_PR_SIZE);
if (err < 0)
goto err_resume_state;
/* state program 2 */
/**
* STx_1 (60h-6Fh)
* State Machine 2 code register STx_1 (x = 1-16).
* State machine 2 system register is made up of 16 8-bit registers,
* to implement 16-step opcode.
*/
buf[0] = (I2C_AUTO_INCREMENT | LIS3DSH_STATEPR2);
for (i = 1; i <= LIS3DSH_STATE_PR_SIZE; i++) {
buf[i] = acc->resume_stmach_program2[i-1];
#ifdef DEBUG
pr_info("i=%d, st pr2 buf[i]=0x%x\n", i, buf[i]);
#endif
};
err = lis3dsh_acc_i2c_write(acc, buf, LIS3DSH_STATE_PR_SIZE);
if (err < 0)
goto err_resume_state;
/**
* CTRL_REG2 (22h) State program 2 interrupt MNG - SM2 control register.
* CTRL_REG3 (23h) Control register 3.
*/
buf[0] = (I2C_AUTO_INCREMENT | LIS3DSH_CTRL_REG3);
buf[1] = acc->resume_state[LIS3DSH_RES_CTRL_REG3];
buf[2] = acc->resume_state[LIS3DSH_RES_CTRL_REG4];
err = lis3dsh_acc_i2c_write(acc, buf, 2);
if (err < 0)
goto err_resume_state;
/**
* CTRL_REG4 (20h) ODR3 ODR2 ODR1 ODR0 BDU ZEN YEN XEN
* CTRL_REG1 (21h) HYST2_1 HYST1_1 HYST0_1 - SM1_PIN - - SM1_EN
*/
buf[0] = (I2C_AUTO_INCREMENT | LIS3DSH_CTRL_REG1);
buf[1] = acc->resume_state[LIS3DSH_RES_CTRL_REG1];
buf[2] = acc->resume_state[LIS3DSH_RES_CTRL_REG2];
err = lis3dsh_acc_i2c_write(acc, buf, 2);
if (err < 0)
goto err_resume_state;
acc->hw_initialized = 1;
pr_info("%s: hw init done\n", LIS3DSH_ACC_DEV_NAME);
return 0;
err_firstread:
acc->hw_working = 0;
err_unknown_device:
err_resume_state:
acc->hw_initialized = 0;
dev_err(&acc->client->dev, "hw init error 0x%x,0x%x: %d\n",
buf[0], buf[1], err);
return err;
}
static void lis3dsh_acc_device_power_off(struct lis3dsh_acc_data *acc)
{
int err;
err = lis3dsh_acc_i2c_update(acc, LIS3DSH_CTRL_REG1,
LIS3DSH_ODR_MASK, LIS3DSH_PM_OFF);
if (err < 0)
dev_err(&acc->client->dev, "soft power off failed: %d\n", err);
if (acc->pdata->power_off) {
if (acc->pdata->gpio_int1 > 0)
disable_irq_nosync(acc->irq1);
if (acc->pdata->gpio_int2 > 0)
disable_irq_nosync(acc->irq2);
acc->pdata->power_off();
acc->hw_initialized = 0;
}
if (acc->hw_initialized) {
if (acc->pdata->gpio_int1 > 0)
disable_irq_nosync(acc->irq1);
if (acc->pdata->gpio_int2 > 0)
disable_irq_nosync(acc->irq2);
acc->hw_initialized = 0;
}
}
static int lis3dsh_acc_device_power_on(struct lis3dsh_acc_data *acc)
{
int err = -1;
if (acc->pdata->power_on) {
err = acc->pdata->power_on();
if (err < 0) {
dev_err(&acc->client->dev,
"power_on failed: %d\n", err);
return err;
}
if (acc->pdata->gpio_int1 > 0)
enable_irq(acc->irq1);
if (acc->pdata->gpio_int2 > 0)
enable_irq(acc->irq2);
}
if (!acc->hw_initialized) {
err = lis3dsh_acc_hw_init(acc);
if (acc->hw_working == 1 && err < 0) {
lis3dsh_acc_device_power_off(acc);
return err;
}
}
if (acc->hw_initialized) {
if (acc->pdata->gpio_int1 > 0)
enable_irq(acc->irq1);
if (acc->pdata->gpio_int2 > 0)
enable_irq(acc->irq2);
}
return 0;
}
static irqreturn_t lis3dsh_acc_isr_threaded(int irq, void *dev)
{
struct i2c_client *client = container_of(dev, struct i2c_client, dev);
struct lis3dsh_acc_data *acc = i2c_get_clientdata(client);
int err = 0;
u8 val = 0;
mutex_lock(&acc->lock);
val = 0x50;
err = lis3dsh_acc_i2c_read(acc, &val, 1);
if (err < 0) {
dev_err((struct device *)dev, "lis3dsh_acc_i2c_read failed\n");
goto exit;
}
dev_err((struct device *)dev, "lis3dsh_acc_isr_threaded: val=%d\n", val);
if (val & 0x02) { /* watch is pointing eyes? */
report_motion(acc);
}
/* reading OUTSx register resets SMx interrupt (IRQx) */
if (irq == acc->irq1)
val = LIS3DSH_OUTS_1;
else
val = LIS3DSH_OUTS_2;
err = lis3dsh_acc_i2c_read(acc, &val, 1);
if (err < 0) {
dev_err((struct device *)dev,
"unrecoverable error, shutting down lis3dsh driver.\n");
lis3dsh_acc_disable(acc);
}
exit:
mutex_unlock(&acc->lock);
return IRQ_HANDLED;
}
static int lis3dsh_acc_register_masked_update(struct lis3dsh_acc_data *acc,
u8 reg_address, u8 mask, u8 new_bit_values, int resume_index)
{
u8 config[2] = {0};
u8 init_val, updated_val;
int err;
int step = 0;
config[0] = reg_address;
err = lis3dsh_acc_i2c_read(acc, config, 1);
if (err < 0)
goto error;
init_val = config[0];
init_val = acc->resume_state[resume_index];
step = 1;
updated_val = ((mask & new_bit_values) | ((~mask) & init_val));
config[0] = reg_address;
config[1] = updated_val;
err = lis3dsh_acc_i2c_write(acc, config, 1);
if (err < 0)
goto error;
acc->resume_state[resume_index] = updated_val;
return err;
error:
dev_err(&acc->client->dev,
"register 0x%x update failed at step %d, error: %d\n",
config[0], step, err);
return err;
}
static int lis3dsh_acc_update_fs_range(struct lis3dsh_acc_data *acc,
u8 new_fs_range)
{
int err = -1;
u16 sensitivity;
switch (new_fs_range) {
case LIS3DSH_ACC_G_2G:
sensitivity = SENSITIVITY_2G;
break;
case LIS3DSH_ACC_G_4G:
sensitivity = SENSITIVITY_4G;
break;
case LIS3DSH_ACC_G_6G:
sensitivity = SENSITIVITY_6G;
break;
case LIS3DSH_ACC_G_8G:
sensitivity = SENSITIVITY_8G;
break;
case LIS3DSH_ACC_G_16G:
sensitivity = SENSITIVITY_16G;
break;
default:
dev_err(&acc->client->dev, "invalid g range requested: %u\n",
new_fs_range);
return -EINVAL;
}
if (atomic_read(&acc->enabled)) {
/* Updates configuration register 1,
* which contains g range setting */
err = lis3dsh_acc_register_masked_update(acc, LIS3DSH_CTRL_REG5,
LIS3DSH_ACC_FS_MASK, new_fs_range, LIS3DSH_RES_CTRL_REG5);
if (err < 0) {
dev_err(&acc->client->dev, "update g range failed\n");
return err;
} else
acc->sensitivity = sensitivity;
}
if (err < 0)
dev_err(&acc->client->dev, "update g range not executed because the device is off\n");
return err;
}
static int lis3dsh_acc_update_odr(struct lis3dsh_acc_data *acc,
int poll_interval_ms)
{
int err = -1;
int i;
u8 new_odr;
/* Following, looks for the longest possible odr interval scrolling the
* odr_table vector from the end (shortest interval) backward (longest
* interval), to support the poll_interval requested by the system.
* It must be the longest interval lower then the poll interval.*/
for (i = ARRAY_SIZE(lis3dsh_acc_odr_table) - 1; i >= 0; i--) {
if (lis3dsh_acc_odr_table[i].cutoff_ms <= poll_interval_ms)
break;
}
new_odr = lis3dsh_acc_odr_table[i].mask;
/* If device is currently enabled, we need to write new
* configuration out to it */
if (atomic_read(&acc->enabled)) {
err = lis3dsh_acc_register_masked_update(acc,
LIS3DSH_CTRL_REG1, LIS3DSH_ODR_MASK, new_odr,
LIS3DSH_RES_CTRL_REG1);
}
if (err < 0)
dev_err(&acc->client->dev, "update odr failed\n");
return err;
}
#ifdef DEBUG
static int lis3dsh_acc_register_write(struct lis3dsh_acc_data *acc, u8 *buf,
u8 reg_address, u8 new_value)
{
int err = -1;
/* Sets configuration register at reg_address
* NOTE: this is a straight overwrite */
buf[0] = reg_address;
buf[1] = new_value;
err = lis3dsh_acc_i2c_write(acc, buf, 1);
if (err < 0)
return err;
return err;
}
static int lis3dsh_acc_register_read(struct lis3dsh_acc_data *acc, u8 *buf,
u8 reg_address)
{
int err = -1;
buf[0] = (reg_address);
err = lis3dsh_acc_i2c_read(acc, buf, 1);
return err;
}
static int lis3dsh_acc_register_update(struct lis3dsh_acc_data *acc, u8 *buf,
u8 reg_address, u8 mask, u8 new_bit_values)
{
int err = -1;
u8 init_val;
u8 updated_val;
err = lis3dsh_acc_register_read(acc, buf, reg_address);
if (!(err < 0)) {
init_val = buf[0];
updated_val = ((mask & new_bit_values) | ((~mask) & init_val));
err = lis3dsh_acc_register_write(acc, buf, reg_address,
updated_val);
}
return err;
}
#endif
static int lis3dsh_acc_get_acceleration_data(struct lis3dsh_acc_data *acc,
int *xyz)
{
int err = -1;
/* Data bytes from hardware xL, xH, yL, yH, zL, zH */
u8 acc_data[6];
/* x,y,z hardware data */
s32 hw_d[3] = { 0 };
acc_data[0] = (I2C_AUTO_INCREMENT | OUT_AXISDATA_REG);
err = lis3dsh_acc_i2c_read(acc, acc_data, 6);
if (err < 0)
return err;
hw_d[0] = ((s16) ((acc_data[1] << 8) | acc_data[0]));
hw_d[1] = ((s16) ((acc_data[3] << 8) | acc_data[2]));
hw_d[2] = ((s16) ((acc_data[5] << 8) | acc_data[4]));
hw_d[0] = hw_d[0] * acc->sensitivity;
hw_d[1] = hw_d[1] * acc->sensitivity;
hw_d[2] = hw_d[2] * acc->sensitivity;
xyz[0] = ((acc->pdata->negate_x) ? (-hw_d[acc->pdata->axis_map_x])
: (hw_d[acc->pdata->axis_map_x]));
xyz[1] = ((acc->pdata->negate_y) ? (-hw_d[acc->pdata->axis_map_y])
: (hw_d[acc->pdata->axis_map_y]));
xyz[2] = ((acc->pdata->negate_z) ? (-hw_d[acc->pdata->axis_map_z])
: (hw_d[acc->pdata->axis_map_z]));
#ifdef DEBUG
pr_info("%s read x=%d, y=%d, z=%d\n",
LIS3DSH_ACC_DEV_NAME, xyz[0], xyz[1], xyz[2]);
#endif
return err;
}
static void report_motion(struct lis3dsh_acc_data *acc)
{
/**
* Grab or reprime a temporary wakelock to keep system awake
* long enough for receiver of tilt event to grab it's own
* wakelock
*/
wake_lock_timeout(&acc->tilt_wakelock, TILT_WAKELOCK_HOLD_MS);
/* Wrist tilt detected, send tilt event up to the HAL */
input_event(acc->input_dev, EV_MSC, MSC_GESTURE, 1);
input_sync(acc->input_dev);
}
static int lis3dsh_acc_enable(struct lis3dsh_acc_data *acc)
{
int err;
if (!atomic_cmpxchg(&acc->enabled, 0, 1)) {
err = lis3dsh_acc_device_power_on(acc);
if (err < 0) {
atomic_set(&acc->enabled, 0);
return err;
}
}
return 0;
}
static int lis3dsh_acc_disable(struct lis3dsh_acc_data *acc)
{
if (atomic_cmpxchg(&acc->enabled, 1, 0))
lis3dsh_acc_device_power_off(acc);
return 0;
}
static ssize_t attr_get_polling_rate(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int val;
struct lis3dsh_acc_data *acc = dev_get_drvdata(dev);
mutex_lock(&acc->lock);
val = acc->pdata->poll_interval;
mutex_unlock(&acc->lock);
return sprintf(buf, "%d\n", val);
}
static ssize_t attr_set_polling_rate(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
int err;
struct lis3dsh_acc_data *acc = dev_get_drvdata(dev);
unsigned long interval_ms;
if (kstrtoul(buf, 10, &interval_ms))
return -EINVAL;
if (!interval_ms)
return -EINVAL;
mutex_lock(&acc->lock);
err = lis3dsh_acc_update_odr(acc, interval_ms);
if (err >= 0)
acc->pdata->poll_interval = interval_ms;
mutex_unlock(&acc->lock);
return size;
}
static ssize_t attr_get_range(struct device *dev,
struct device_attribute *attr, char *buf)
{
u8 val;
struct lis3dsh_acc_data *acc = dev_get_drvdata(dev);
int range = 2;
mutex_lock(&acc->lock);
val = acc->pdata->fs_range;
switch (val) {
case LIS3DSH_ACC_G_2G:
range = 2;
break;
case LIS3DSH_ACC_G_4G:
range = 4;
break;
case LIS3DSH_ACC_G_6G:
range = 6;
break;
case LIS3DSH_ACC_G_8G:
range = 8;
break;
case LIS3DSH_ACC_G_16G:
range = 16;
break;
}
mutex_unlock(&acc->lock);
return sprintf(buf, "%d\n", range);
}
static ssize_t attr_set_range(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
int err;
struct lis3dsh_acc_data *acc = dev_get_drvdata(dev);
unsigned long val;
u8 range;
if (kstrtoul(buf, 10, &val))
return -EINVAL;
switch (val) {
case 2:
range = LIS3DSH_ACC_G_2G;
break;
case 4:
range = LIS3DSH_ACC_G_4G;
break;
case 6:
range = LIS3DSH_ACC_G_6G;
break;
case 8:
range = LIS3DSH_ACC_G_8G;
break;
case 16:
range = LIS3DSH_ACC_G_16G;
break;
default:
return -1;
}
mutex_lock(&acc->lock);
err = lis3dsh_acc_update_fs_range(acc, range);
if (err >= 0)
acc->pdata->fs_range = range;
mutex_unlock(&acc->lock);
return size;
}
static ssize_t attr_get_enable(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct lis3dsh_acc_data *acc = dev_get_drvdata(dev);
int val = atomic_read(&acc->enabled);
return sprintf(buf, "%d\n", val);
}
static ssize_t attr_set_enable(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
struct lis3dsh_acc_data *acc = dev_get_drvdata(dev);
unsigned long val;
if (kstrtoul(buf, 10, &val))
return -EINVAL;
if (val)
lis3dsh_acc_enable(acc);
else
lis3dsh_acc_disable(acc);
return size;
}
/**
* CTRL_REG3 (23h) DR_EN IEA IEL INT2_EN INT1_EN VFILT - STRT
* CTRL_REG1 (21h) HYST2_1 HYST1_1 HYST0_1 - SM1_PIN - - SM1_EN
*/
static int lis3dsh_acc_interrupt_enable_control(struct lis3dsh_acc_data *acc,
u8 settings)
{
u8 val1;
u8 val2 = LIS3DSH_INTEN_ON;
u8 mask1 = (LIS3DSH_INT1_EN_MASK | LIS3DSH_INT2_EN_MASK);
int err = -1;
settings = settings & 0x03;
switch (settings) {
case LIS3DSH_INT1_DIS_INT2_DIS:
val1 = (LIS3DSH_INT1_EN_OFF | LIS3DSH_INT2_EN_OFF);
val2 = LIS3DSH_INTEN_OFF;
break;
case LIS3DSH_INT1_DIS_INT2_EN:
val1 = (LIS3DSH_INT1_EN_OFF | LIS3DSH_INT2_EN_ON);
break;
case LIS3DSH_INT1_EN_INT2_DIS:
val1 = (LIS3DSH_INT1_EN_ON | LIS3DSH_INT2_EN_OFF);
break;
case LIS3DSH_INT1_EN_INT2_EN:
val1 = (LIS3DSH_INT1_EN_ON | LIS3DSH_INT2_EN_ON);
break;
default:
pr_err("invalid interrupt setting : 0x%02x\n", settings);
return err;
}
err = lis3dsh_acc_register_masked_update(acc,
LIS3DSH_CTRL_REG4, mask1, val1, LIS3DSH_RES_CTRL_REG4);
if (err < 0)
return err;
err = lis3dsh_acc_register_masked_update(acc,
LIS3DSH_CTRL_REG2, LIS3DSH_INTEN_MASK, val2,
LIS3DSH_RES_CTRL_REG2);
if (err < 0)
return err;
acc->interrupts_enable_setting = settings;
#ifdef DEBUG
pr_err("interrupt setting : 0x%02x\n", acc->interrupts_enable_setting);
#endif
return err;
}
static ssize_t attr_get_interr_enable(struct device *dev,
struct device_attribute *attr, char *buf)
{
u8 val;
struct lis3dsh_acc_data *acc = dev_get_drvdata(dev);
mutex_lock(&acc->lock);
val = acc->interrupts_enable_setting;
mutex_unlock(&acc->lock);
return sprintf(buf, "0x%02x\n", val);
}
static ssize_t attr_set_interr_enable(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
int err = -1;
struct lis3dsh_acc_data *acc = dev_get_drvdata(dev);
long val = 0;
if (kstrtoul(buf, 16, &val))
return -EINVAL;
if (val < 0x00 || val > LIS3DSH_INT1_EN_INT2_EN) {
#ifdef DEBUG
pr_err("invalid interrupt setting, val: %ld\n", val);
#endif
return -EINVAL;
}
mutex_lock(&acc->lock);
err = lis3dsh_acc_interrupt_enable_control(acc, val);
mutex_unlock(&acc->lock);
if (err < 0)
return err;
return size;
}
static int lis3dsh_acc_state_progrs_enable_control(
struct lis3dsh_acc_data *acc, u8 settings)
{
u8 val1, val2;
int err = -1;
/* settings = settings & 0x03; */
switch (settings) {
case LIS3DSH_SM1_DIS_SM2_DIS:
val1 = LIS3DSH_SM1_EN_OFF;
val2 = LIS3DSH_SM2_EN_OFF;
break;
case LIS3DSH_SM1_DIS_SM2_EN:
val1 = LIS3DSH_SM1_EN_OFF;
val2 = LIS3DSH_SM2_EN_ON;
break;
case LIS3DSH_SM1_EN_SM2_DIS:
val1 = LIS3DSH_SM1_EN_ON;
val2 = LIS3DSH_SM2_EN_OFF;
break;
case LIS3DSH_SM1_EN_SM2_EN:
val1 = LIS3DSH_SM1_EN_ON;
val2 = LIS3DSH_SM2_EN_ON;
break;
default:
pr_err("invalid state program setting : 0x%02x\n", settings);
return err;
}
err = lis3dsh_acc_register_masked_update(acc,
LIS3DSH_CTRL_REG2, LIS3DSH_SM1_EN_MASK, val1,
LIS3DSH_RES_CTRL_REG2);
if (err < 0)
return err;
err = lis3dsh_acc_register_masked_update(acc,
LIS3DSH_CTRL_REG3, LIS3DSH_SM2_EN_MASK, val2,
LIS3DSH_RES_CTRL_REG3);
if (err < 0)
return err;
acc->stateprogs_enable_setting = settings;
#ifdef DEBUG
pr_err("state program setting : 0x%02x\n", acc->stateprogs_enable_setting);
#endif
return err;
}
static ssize_t attr_set_enable_state_prog(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
int err = -1;
struct lis3dsh_acc_data *acc = dev_get_drvdata(dev);
long val = 0;
if (kstrtoul(buf, 16, &val))
return -EINVAL;
if (val < 0x00 || val > LIS3DSH_SM1_EN_SM2_EN) {
#ifdef DEBUG
pr_err("invalid state program setting, val: %ld\n", val);
#endif
return -EINVAL;
}
mutex_lock(&acc->lock);
err = lis3dsh_acc_state_progrs_enable_control(acc, val);
mutex_unlock(&acc->lock);
if (err < 0)
return err;
return size;
}
static ssize_t attr_get_enable_state_prog(struct device *dev,
struct device_attribute *attr, char *buf)
{
u8 val;
struct lis3dsh_acc_data *acc = dev_get_drvdata(dev);
mutex_lock(&acc->lock);
val = acc->stateprogs_enable_setting;
mutex_unlock(&acc->lock);
return sprintf(buf, "0x%02x\n", val);
}
#ifdef CONFIG_ST_LIS3DSH_SELFTEST
int lis3dsh_average_sample(struct lis3dsh_acc_data *acc, int *out_data)
{
int i, err, counter = 0;
int hw_data[3] = {0, 0, 0};
u8 stat_reg;
bool data_rdy = false;
msleep(DELAY_DOR_SAMPLE);
for (i = 0; i < LIS3DSH_SAMPLE_NUM + 1; i++) {
while (!data_rdy) {
dev_dbg(&acc->client->dev, "counter=%d\n", counter);
if (++counter > MAX_READ_COUNTER) {
dev_err(&acc->client->dev, "Exceeding the max read counter\n");
return -EAGAIN;
}
msleep(acc->pdata->poll_interval);
stat_reg = LIS3DSH_STATUS_REG;
err = lis3dsh_acc_i2c_read(acc, &stat_reg, 1);
if (err < 0) {
dev_err(&acc->client->dev, "failed to read Status register.\n");
return err;
}
if (stat_reg & ZYXDA_RDY_MASK)
data_rdy = true;
}
err = lis3dsh_acc_get_acceleration_data(acc, hw_data);
if (err < 0) {
dev_err(&acc->client->dev, "failed to get accel data.\n");
return err;
}
/* Skip the first sample data */
if (i != 0) {
out_data[0] += hw_data[0];
out_data[1] += hw_data[1];
out_data[2] += hw_data[2];
}
data_rdy = false;
}
out_data[0] /= LIS3DSH_SAMPLE_NUM;
out_data[1] /= LIS3DSH_SAMPLE_NUM;
out_data[2] /= LIS3DSH_SAMPLE_NUM;
dev_dbg(&acc->client->dev, "x=%d, y=%d, z=%d\n",
out_data[0], out_data[1], out_data[2]);
return 0;
}
static ssize_t attr_selftest(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct lis3dsh_acc_data *acc = dev_get_drvdata(dev);
int ret;
int nost[3] = {0, 0, 0};
int st[3] = {0, 0, 0};
int out_diff[3] = {0, 0, 0};
ret = lis3dsh_average_sample(acc, nost);
if (ret < 0)
return ret;
/* Positive sign self-test */
ret = lis3dsh_acc_register_masked_update(acc, LIS3DSH_CTRL_REG5,
LIS3DSH_ST_MASK, LIS3DSH_ST_PS, LIS3DSH_RES_CTRL_REG5);
if (ret < 0) {
dev_err(dev, "Positive sign self-test failed.\n");
return ret;
}
ret = lis3dsh_average_sample(acc, st);
if (ret < 0)
return ret;
/* Disable self-test function */
ret = lis3dsh_acc_register_masked_update(acc, LIS3DSH_CTRL_REG5,
LIS3DSH_ST_MASK, LIS3DSH_ST_OFF, LIS3DSH_RES_CTRL_REG5);
if (ret < 0) {
dev_err(dev, "Disable self-test failed.\n");
return ret;
}
out_diff[0] = ABS((nost[0] - st[0]));
out_diff[1] = ABS((nost[1] - st[1]));
out_diff[2] = ABS((nost[2] - st[2]));
dev_dbg(dev, "diff x=%d, y=%d, z=%d\n", out_diff[0], out_diff[1], out_diff[2]);
if (out_diff[0] > LIS3DSH_DIFF_MIN &&
out_diff[0] < LIS3DSH_DIFF_MAX &&
out_diff[1] > LIS3DSH_DIFF_MIN &&
out_diff[1] < LIS3DSH_DIFF_MAX &&
out_diff[2] > LIS3DSH_DIFF_MIN &&
out_diff[2] < LIS3DSH_DIFF_MAX)
return sprintf(buf, "%s\n", "PASSED");
else
return sprintf(buf, "%s\n", "FAILED");
}
#endif
#ifdef DEBUG
/* PAY ATTENTION: These DEBUG funtions don't manage resume_state */
static ssize_t attr_reg_set(struct device *dev, struct device_attribute *attr,
const char *buf, size_t size)
{
int rc;
struct lis3dsh_acc_data *acc = dev_get_drvdata(dev);
u8 x[2];
unsigned long val;
if (kstrtoul(buf, 16, &val))
return -EINVAL;
mutex_lock(&acc->lock);
x[0] = acc->reg_addr;
mutex_unlock(&acc->lock);
x[1] = val;
rc = lis3dsh_acc_i2c_write(acc, x, 1);
/*TODO: error need to be managed */
return size;
}
static ssize_t attr_reg_get(struct device *dev, struct device_attribute *attr,
char *buf)
{
ssize_t ret;
struct lis3dsh_acc_data *acc = dev_get_drvdata(dev);
int rc;
u8 data;
mutex_lock(&acc->lock);
data = acc->reg_addr;
mutex_unlock(&acc->lock);
rc = lis3dsh_acc_i2c_read(acc, &data, 1);
/*TODO: error need to be managed */
ret = sprintf(buf, "0x%02x\n", data);
return ret;
}
static ssize_t attr_addr_set(struct device *dev, struct device_attribute *attr,
const char *buf, size_t size)
{
struct lis3dsh_acc_data *acc = dev_get_drvdata(dev);
unsigned long val;
if (kstrtoul(buf, 16, &val))
return -EINVAL;
mutex_lock(&acc->lock);
acc->reg_addr = val;
mutex_unlock(&acc->lock);
return size;
}
#endif
static struct device_attribute attributes[] = {
__ATTR(poll_period_ms, 0664, attr_get_polling_rate,
attr_set_polling_rate),
__ATTR(range, 0664, attr_get_range, attr_set_range),
__ATTR(enable_device, 0664, attr_get_enable, attr_set_enable),
__ATTR(enable_interrupt_output, 0664, attr_get_interr_enable,
attr_set_interr_enable),
__ATTR(enable_state_prog, 0664, attr_get_enable_state_prog,
attr_set_enable_state_prog),
#ifdef CONFIG_ST_LIS3DSH_SELFTEST
__ATTR(selftest, 0444, attr_selftest, NULL),
#endif
#ifdef DEBUG
__ATTR(reg_value, 0600, attr_reg_get, attr_reg_set),
__ATTR(reg_addr, 0200, NULL, attr_addr_set),
#endif
};
static int create_sysfs_interfaces(struct device *dev)
{
int i;
for (i = 0; i < ARRAY_SIZE(attributes); i++)
if (device_create_file(dev, attributes + i))
goto error;
return 0;
error:
for (; i >= 0; i--)
device_remove_file(dev, attributes + i);
dev_err(dev, "%s:Unable to create interface\n", __func__);
return -1;
}
static int remove_sysfs_interfaces(struct device *dev)
{
int i;
for (i = 0; i < ARRAY_SIZE(attributes); i++)
device_remove_file(dev, attributes + i);
return 0;
}
int lis3dsh_acc_input_open(struct input_dev *input)
{
return 0;
}
void lis3dsh_acc_input_close(struct input_dev *dev)
{
return;
}
static int lis3dsh_acc_validate_pdata(struct lis3dsh_acc_data *acc)
{
acc->pdata->poll_interval = max(acc->pdata->poll_interval,
acc->pdata->min_interval);
if (acc->pdata->axis_map_x > 2 ||
acc->pdata->axis_map_y > 2 ||
acc->pdata->axis_map_z > 2) {
dev_err(&acc->client->dev, "invalid axis_map value x:%u y:%u z%u\n",
acc->pdata->axis_map_x,
acc->pdata->axis_map_y,
acc->pdata->axis_map_z);
return -EINVAL;
}
/* Only allow 0 and 1 for negation boolean flag */
if (acc->pdata->negate_x > 1 || acc->pdata->negate_y > 1
|| acc->pdata->negate_z > 1) {
dev_err(&acc->client->dev, "invalid negate value x:%u y:%u z:%u\n",
acc->pdata->negate_x,
acc->pdata->negate_y,
acc->pdata->negate_z);
return -EINVAL;
}
/* Enforce minimum polling interval */
if (acc->pdata->poll_interval < acc->pdata->min_interval) {
dev_err(&acc->client->dev, "minimum poll interval violated\n");
return -EINVAL;
}
return 0;
}
static int lis3dsh_acc_input_init(struct lis3dsh_acc_data *acc)
{
int err;
acc->input_dev = input_allocate_device();
if (!acc->input_dev) {
err = -ENOMEM;
dev_err(&acc->client->dev, "input device allocation failed\n");
goto err0;
}
acc->input_dev->open = lis3dsh_acc_input_open;
acc->input_dev->close = lis3dsh_acc_input_close;
acc->input_dev->name = LIS3DSH_ACC_DEV_NAME;
acc->input_dev->id.bustype = BUS_I2C;
acc->input_dev->dev.parent = &acc->client->dev;
input_set_drvdata(acc->input_dev, acc);
input_set_capability(acc->input_dev, EV_MSC, MSC_GESTURE);
__set_bit(EV_SYN, acc->input_dev->evbit);
err = input_register_device(acc->input_dev);
if (err) {
dev_err(&acc->client->dev,
"unable to register input device %s\n",
acc->input_dev->name);
goto err1;
}
return 0;
err1:
input_free_device(acc->input_dev);
err0:
return err;
}
static void lis3dsh_acc_input_cleanup(struct lis3dsh_acc_data *acc)
{
input_unregister_device(acc->input_dev);
input_free_device(acc->input_dev);
}
static int lis3dsh_acc_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct lis3dsh_acc_data *acc;
int err = -1;
pr_info("%s: probe start.\n", LIS3DSH_ACC_DEV_NAME);
if (client->dev.platform_data == NULL) {
dev_err(&client->dev, "platform data is NULL. exiting.\n");
err = -ENODEV;
goto exit_check_functionality_failed;
}
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
dev_err(&client->dev, "client not i2c capable\n");
err = -ENODEV;
goto exit_check_functionality_failed;
}
acc = kzalloc(sizeof(struct lis3dsh_acc_data), GFP_KERNEL);
if (acc == NULL) {
err = -ENOMEM;
dev_err(&client->dev,
"failed to allocate memory for module data: %d\n",
err);
goto exit_check_functionality_failed;
}
mutex_init(&acc->lock);
mutex_lock(&acc->lock);
acc->client = client;
i2c_set_clientdata(client, acc);
wake_lock_init(&acc->tilt_wakelock, WAKE_LOCK_SUSPEND, TILT_LOCK_NAME);
acc->pdata = kmalloc(sizeof(*acc->pdata), GFP_KERNEL);
if (acc->pdata == NULL) {
err = -ENOMEM;
dev_err(&client->dev,
"failed to allocate memory for pdata: %d\n",
err);
goto err_mutexunlock;
}
memcpy(acc->pdata, client->dev.platform_data,
sizeof(*acc->pdata));
err = lis3dsh_acc_validate_pdata(acc);
if (err < 0) {
dev_err(&client->dev, "failed to validate platform data\n");
goto exit_kfree_pdata;
}
if (acc->pdata->init) {
err = acc->pdata->init();
if (err < 0) {
dev_err(&client->dev, "init failed: %d\n", err);
goto err_pdata_init;
}
}
if (acc->pdata->gpio_int1 > 0) {
err = gpio_request(acc->pdata->gpio_int1, "tilt_irq1");
if (err < 0) {
pr_err("Failed to config GPIO %d\n", acc->pdata->gpio_int1);
gpio_free(acc->pdata->gpio_int1);
} else {
gpio_direction_input(acc->pdata->gpio_int1);
acc->irq1 = gpio_to_irq(acc->pdata->gpio_int1);
pr_info("%s: %s has set irq1 to irq: %d mapped on gpio:%d\n",
LIS3DSH_ACC_DEV_NAME, __func__, acc->irq1,
acc->pdata->gpio_int1);
}
}
if (acc->pdata->gpio_int2 > 0) {
err = gpio_request(acc->pdata->gpio_int2, "tilt_irq2");
if (err < 0) {
pr_err("Failed to config GPIO %d\n", acc->pdata->gpio_int2);
gpio_free(acc->pdata->gpio_int2);
} else {
acc->irq2 = gpio_to_irq(acc->pdata->gpio_int2);
pr_info("%s: %s has set irq2 to irq: %d mapped on gpio:%d\n",
LIS3DSH_ACC_DEV_NAME, __func__, acc->irq2,
acc->pdata->gpio_int2);
}
}
/* resume state init config */
lis3dsh_acc_set_init_register_values(acc);
/* init state program1 and params */
lis3dsh_acc_set_init_statepr1_param(acc);
lis3dsh_acc_set_init_statepr1_inst(acc);
/* init state program2 and params */
lis3dsh_acc_set_init_statepr2_param(acc);
lis3dsh_acc_set_init_statepr2_inst(acc);
err = lis3dsh_acc_device_power_on(acc);
if (err < 0) {
dev_err(&client->dev, "power on failed: %d\n", err);
goto err_pdata_init;
}
atomic_set(&acc->enabled, 1);
err = lis3dsh_acc_interrupt_enable_control(acc,
acc->interrupts_enable_setting);
if (err < 0) {
dev_err(&client->dev, "interrupt settings failed\n");
goto err_power_off;
}
err = lis3dsh_acc_update_fs_range(acc, acc->pdata->fs_range);
if (err < 0) {
dev_err(&client->dev, "update_fs_range failed\n");
goto err_power_off;
}
err = lis3dsh_acc_update_odr(acc, acc->pdata->poll_interval);
if (err < 0) {
dev_err(&client->dev, "update_odr failed\n");
goto err_power_off;
}
err = lis3dsh_acc_input_init(acc);
if (err < 0) {
dev_err(&client->dev, "input init failed\n");
goto err_power_off;
}
err = create_sysfs_interfaces(&client->dev);
if (err < 0) {
dev_err(&client->dev,
"device LIS3DSH_ACC_DEV_NAME sysfs register failed\n");
goto err_input_cleanup;
}
lis3dsh_acc_device_power_off(acc);
/* As default, do not report information */
atomic_set(&acc->enabled, 0);
if (acc->pdata->gpio_int1 > 0) {
err = request_threaded_irq(acc->irq1, NULL,
lis3dsh_acc_isr_threaded,
IRQF_ONESHOT | IRQF_TRIGGER_RISING,
LIS3DSH_ACC_DEV_NAME, &client->dev);
if (err < 0) {
dev_err(&client->dev, "request irq1 failed: %d\n", err);
goto err_remove_sysfs_int;
}
disable_irq_nosync(acc->irq1);
}
if (acc->pdata->gpio_int2 > 0) {
err = request_threaded_irq(acc->irq2, NULL,
lis3dsh_acc_isr_threaded,
IRQF_ONESHOT | IRQF_TRIGGER_RISING,
LIS3DSH_ACC_DEV_NAME, &client->dev);
if (err < 0) {
dev_err(&client->dev, "request irq2 failed: %d\n", err);
goto err_free_irq1;
}
disable_irq_nosync(acc->irq2);
}
mutex_unlock(&acc->lock);
dev_info(&client->dev, "%s: probed\n", LIS3DSH_ACC_DEV_NAME);
return 0;
err_free_irq1:
free_irq(acc->irq1, acc);
err_remove_sysfs_int:
remove_sysfs_interfaces(&client->dev);
err_input_cleanup:
lis3dsh_acc_input_cleanup(acc);
err_power_off:
lis3dsh_acc_device_power_off(acc);
err_pdata_init:
if (acc->pdata->exit)
acc->pdata->exit();
exit_kfree_pdata:
kfree(acc->pdata);
err_mutexunlock:
wake_lock_destroy(&acc->tilt_wakelock);
mutex_unlock(&acc->lock);
kfree(acc);
exit_check_functionality_failed:
pr_err("%s: Driver Init failed\n", LIS3DSH_ACC_DEV_NAME);
return err;
}
static int lis3dsh_acc_remove(struct i2c_client *client)
{
struct lis3dsh_acc_data *acc = i2c_get_clientdata(client);
if (acc->pdata->gpio_int1 > 0) {
free_irq(acc->irq1, acc);
gpio_free(acc->pdata->gpio_int1);
}
if (acc->pdata->gpio_int2 > 0) {
free_irq(acc->irq2, acc);
gpio_free(acc->pdata->gpio_int2);
}
lis3dsh_acc_device_power_off(acc);
lis3dsh_acc_input_cleanup(acc);
remove_sysfs_interfaces(&client->dev);
if (acc->pdata->exit)
acc->pdata->exit();
kfree(acc->pdata);
wake_lock_destroy(&acc->tilt_wakelock);
kfree(acc);
return 0;
}
#ifdef CONFIG_PM
static int lis3dsh_acc_resume(struct i2c_client *client)
{
struct lis3dsh_acc_data *acc = i2c_get_clientdata(client);
/* lis3dsh should not be disabled in sleep */
if (acc->pdata->gpio_int1 > 0)
disable_irq_wake(acc->pdata->gpio_int1);
return 0;
}
static int lis3dsh_acc_suspend(struct i2c_client *client, pm_message_t mesg)
{
struct lis3dsh_acc_data *acc = i2c_get_clientdata(client);
/* lis3dsh should not be disabled in sleep */
if (acc->pdata->gpio_int1 > 0)
enable_irq_wake(acc->pdata->gpio_int1);
return 0;
}
#else
#define lis3dsh_acc_suspend NULL
#define lis3dsh_acc_resume NULL
#endif /* CONFIG_PM */
static const struct i2c_device_id lis3dsh_acc_id[]
= { { LIS3DSH_ACC_DEV_NAME, 0 }, { }, };
MODULE_DEVICE_TABLE(i2c, lis3dsh_acc_id);
static struct i2c_driver lis3dsh_acc_driver = {
.driver = {
.owner = THIS_MODULE,
.name = LIS3DSH_ACC_DEV_NAME,
},
.probe = lis3dsh_acc_probe,
.remove = lis3dsh_acc_remove,
.suspend = lis3dsh_acc_suspend,
.resume = lis3dsh_acc_resume,
.id_table = lis3dsh_acc_id,
};
static int __init lis3dsh_acc_init(void)
{
pr_info("%s accelerometer driver: init\n", LIS3DSH_ACC_DEV_NAME);
return i2c_add_driver(&lis3dsh_acc_driver);
}
static void __exit lis3dsh_acc_exit(void)
{
#ifdef DEBUG
pr_info("%s accelerometer driver exit\n", LIS3DSH_ACC_DEV_NAME);
#endif /* DEBUG */
i2c_del_driver(&lis3dsh_acc_driver);
return;
}
module_init(lis3dsh_acc_init);
module_exit(lis3dsh_acc_exit);
MODULE_DESCRIPTION("lis3dsh accelerometer driver");
MODULE_AUTHOR("Matteo Dameno, Denis Ciocca, STMicroelectronics");
MODULE_LICENSE("GPL");