diff options
Diffstat (limited to 'drivers/sensor/lsm330dlc_accel.c')
-rw-r--r-- | drivers/sensor/lsm330dlc_accel.c | 1402 |
1 files changed, 1402 insertions, 0 deletions
diff --git a/drivers/sensor/lsm330dlc_accel.c b/drivers/sensor/lsm330dlc_accel.c new file mode 100644 index 0000000..a564d52 --- /dev/null +++ b/drivers/sensor/lsm330dlc_accel.c @@ -0,0 +1,1402 @@ +/* + * STMicroelectronics lsm330dlc_accel acceleration sensor driver + * + * Copyright (C) 2011 Samsung Electronics Co.Ltd + * + * 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. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/fs.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/miscdevice.h> +#include <linux/uaccess.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/input.h> +#include <linux/sensor/lsm330dlc_accel.h> +#include <linux/sensor/sensors_core.h> +#include <linux/printk.h> +#include <linux/wakelock.h> + +/* For debugging */ +#if 1 +#define accel_dbgmsg(str, args...)\ + pr_info("%s: " str, __func__, ##args) +#else +#define accel_dbgmsg(str, args...)\ + pr_debug("%s: " str, __func__, ##args) +#endif + +#undef LSM330DLC_ACCEL_LOGGING +#undef DEBUG_ODR +#undef DEBUG_REACTIVE_ALERT +/* It will be used, when google fusion is enabled. */ +#undef USES_INPUT_DEV + +#define VENDOR "STM" +#define CHIP_ID "LSM330" + +/* The default settings when sensor is on is for all 3 axis to be enabled + * and output data rate set to 400Hz. Output is via a ioctl read call. + */ +#define DEFAULT_POWER_ON_SETTING (ODR400 | ENABLE_ALL_AXES) + +#ifdef USES_MOVEMENT_RECOGNITION +#define DEFAULT_CTRL3_SETTING 0x60 /* INT1_A enable */ +#define DEFAULT_CTRL6_SETTING 0x20 /* INT2_A enable */ +#define DEFAULT_INTERRUPT_SETTING 0x0A /* INT1_A XH,YH : enable */ +#define DEFAULT_INTERRUPT2_SETTING 0x20 /* INT2_A ZH enable */ +#define DEFAULT_THRESHOLD 0x7F /* 2032mg (16*0x7F) */ +#define DYNAMIC_THRESHOLD 300 /* mg */ +#define DYNAMIC_THRESHOLD2 700 /* mg */ +#define MOVEMENT_DURATION 0x00 /*INT1_A (DURATION/odr)ms*/ +enum { + OFF = 0, + ON = 1 +}; +#define ABS(a) (((a) < 0) ? -(a) : (a)) +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#endif + +#define ACC_DEV_NAME "accelerometer" + +#define CALIBRATION_FILE_PATH "/efs/calibration_data" +#define CAL_DATA_AMOUNT 20 + +static const struct odr_delay { + u8 odr; /* odr reg setting */ + u64 delay_ns; /* odr in ns */ +} odr_delay_table[] = { + { ODR1344, 744047LL }, /* 1344Hz */ + { ODR400, 2500000LL }, /* 400Hz */ + { ODR200, 5000000LL }, /* 200Hz */ + { ODR100, 10000000LL }, /* 100Hz */ + { ODR50, 20000000LL }, /* 50Hz */ + { ODR25, 40000000LL }, /* 25Hz */ + { ODR10, 100000000LL }, /* 10Hz */ + { ODR1, 1000000000LL }, /* 1Hz */ +}; + +/* It will be used, when google fusion is enabled. */ +static const int position_map[][3][3] = { + {{-1, 0, 0}, { 0, -1, 0}, { 0, 0, 1} }, /* 0 top/upper-left */ + {{ 0, -1, 0}, { 1, 0, 0}, { 0, 0, 1} }, /* 1 top/upper-right */ + {{ 1, 0, 0}, { 0, 1, 0}, { 0, 0, 1} }, /* 2 top/lower-right */ + {{ 0, 1, 0}, {-1, 0, 0}, { 0, 0, 1} }, /* 3 top/lower-left */ + {{ 1, 0, 0}, { 0, -1, 0}, { 0, 0, -1} }, /* 4 bottom/upper-left */ + {{ 0, 1, 0}, { 1, 0, 0}, { 0, 0, -1} }, /* 5 bottom/upper-right */ + {{-1, 0, 0}, { 0, 1, 0}, { 0, 0, -1} }, /* 6 bottom/lower-right */ + {{ 0, -1, 0}, {-1, 0, 0}, { 0, 0, -1} }, /* 7 bottom/lower-left*/ +}; + +struct lsm330dlc_accel_data { + struct i2c_client *client; + struct miscdevice lsm330dlc_accel_device; + struct mutex read_lock; + struct mutex write_lock; + struct lsm330dlc_acc cal_data; + struct device *dev; + u8 ctrl_reg1_shadow; + atomic_t opened; /* opened implies enabled */ +#ifdef USES_MOVEMENT_RECOGNITION + int movement_recog_flag; + unsigned char interrupt_state; + struct wake_lock reactive_wake_lock; +#endif + ktime_t poll_delay; +#ifdef USES_INPUT_DEV + struct input_dev *input_dev; + struct work_struct work; + struct workqueue_struct *work_queue; + struct hrtimer timer; +#endif + int position; + struct lsm330dlc_acc acc_xyz; + bool axis_adjust; +}; + + /* Read X,Y and Z-axis acceleration raw data */ +static int lsm330dlc_accel_read_raw_xyz(struct lsm330dlc_accel_data *data, + struct lsm330dlc_acc *acc) +{ + int err; + s8 reg = OUT_X_L | AC; /* read from OUT_X_L to OUT_Z_H by auto-inc */ + u8 acc_data[6]; + + err = i2c_smbus_read_i2c_block_data(data->client, reg, + sizeof(acc_data), acc_data); + if (err != sizeof(acc_data)) { + pr_err("%s : failed to read 6 bytes for getting x/y/z\n", + __func__); + return -EIO; + } + + acc->x = (acc_data[1] << 8) | acc_data[0]; + acc->y = (acc_data[3] << 8) | acc_data[2]; + acc->z = (acc_data[5] << 8) | acc_data[4]; + + acc->x = acc->x >> 4; + acc->y = acc->y >> 4; + acc->z = acc->z >> 4; + + return 0; +} + +static int lsm330dlc_accel_read_xyz(struct lsm330dlc_accel_data *data, + struct lsm330dlc_acc *acc) +{ + int err = 0; + + mutex_lock(&data->read_lock); + err = lsm330dlc_accel_read_raw_xyz(data, acc); + mutex_unlock(&data->read_lock); + if (err < 0) { + pr_err("%s: lsm330dlc_accel_read_xyz() failed\n", __func__); + return err; + } + + acc->x -= data->cal_data.x; + acc->y -= data->cal_data.y; + acc->z -= data->cal_data.z; + + return err; +} + +static int lsm330dlc_accel_open_calibration(struct lsm330dlc_accel_data *data) +{ + struct file *cal_filp = NULL; + int err = 0; + mm_segment_t old_fs; + + old_fs = get_fs(); + set_fs(KERNEL_DS); + + cal_filp = filp_open(CALIBRATION_FILE_PATH, O_RDONLY, 0666); + if (IS_ERR(cal_filp)) { + err = PTR_ERR(cal_filp); + if (err != -ENOENT) + pr_err("%s: Can't open calibration file\n", __func__); + set_fs(old_fs); + return err; + } + + err = cal_filp->f_op->read(cal_filp, + (char *)&data->cal_data, 3 * sizeof(s16), &cal_filp->f_pos); + if (err != 3 * sizeof(s16)) { + pr_err("%s: Can't read the cal data from file\n", __func__); + err = -EIO; + } + + accel_dbgmsg("(%d,%d,%d)\n", + data->cal_data.x, data->cal_data.y, data->cal_data.z); + + filp_close(cal_filp, current->files); + set_fs(old_fs); + + return err; +} + +static int lsm330dlc_accel_do_calibrate(struct device *dev, bool do_calib) +{ + struct lsm330dlc_accel_data *acc_data = dev_get_drvdata(dev); + struct lsm330dlc_acc data = { 0, }; + struct file *cal_filp = NULL; + int sum[3] = { 0, }; + int err = 0; + int i; + mm_segment_t old_fs; + + if (do_calib) { + for (i = 0; i < CAL_DATA_AMOUNT; i++) { + mutex_lock(&acc_data->read_lock); + err = lsm330dlc_accel_read_raw_xyz(acc_data, &data); + mutex_unlock(&acc_data->read_lock); + if (err < 0) { + pr_err("%s: lsm330dlc_accel_read_raw_xyz() " + "failed in the %dth loop\n", + __func__, i); + return err; + } + + sum[0] += data.x; + sum[1] += data.y; + sum[2] += data.z; + } + + acc_data->cal_data.x = sum[0] / CAL_DATA_AMOUNT; + acc_data->cal_data.y = sum[1] / CAL_DATA_AMOUNT; + acc_data->cal_data.z = (sum[2] / CAL_DATA_AMOUNT); + if (acc_data->cal_data.z > 0) { + acc_data->cal_data.z -= 1024; + pr_info("acc_data->cal_data.z= %d\n", + acc_data->cal_data.z); + } else if (acc_data->cal_data.z < 0) { + acc_data->cal_data.z += 1024; + pr_info("acc_data->cal_data.z= %d\n", + acc_data->cal_data.z); + } + } else { + acc_data->cal_data.x = 0; + acc_data->cal_data.y = 0; + acc_data->cal_data.z = 0; + } + + printk(KERN_INFO "%s: cal data (%d,%d,%d)\n", __func__, + acc_data->cal_data.x, acc_data->cal_data.y + , acc_data->cal_data.z); + + old_fs = get_fs(); + set_fs(KERNEL_DS); + + cal_filp = filp_open(CALIBRATION_FILE_PATH, + O_CREAT | O_TRUNC | O_WRONLY, 0666); + if (IS_ERR(cal_filp)) { + pr_err("%s: Can't open calibration file\n", __func__); + set_fs(old_fs); + err = PTR_ERR(cal_filp); + return err; + } + + err = cal_filp->f_op->write(cal_filp, + (char *)&acc_data->cal_data, 3 * sizeof(s16), &cal_filp->f_pos); + if (err != 3 * sizeof(s16)) { + pr_err("%s: Can't write the cal data to file\n", __func__); + err = -EIO; + } + + filp_close(cal_filp, current->files); + set_fs(old_fs); + + return err; +} + +static int lsm330dlc_accel_enable(struct lsm330dlc_accel_data *data) +{ + int err = 0; + + mutex_lock(&data->write_lock); + if (atomic_read(&data->opened) == 0) { + err = lsm330dlc_accel_open_calibration(data); + if (err < 0 && err != -ENOENT) + pr_err("%s: lsm330dlc_accel_open_calibration() failed\n", + __func__); + data->ctrl_reg1_shadow = DEFAULT_POWER_ON_SETTING; + err = i2c_smbus_write_byte_data(data->client, CTRL_REG1, + DEFAULT_POWER_ON_SETTING); + if (err) + pr_err("%s: i2c write ctrl_reg1 failed\n", __func__); + + err = i2c_smbus_write_byte_data(data->client, CTRL_REG4, + CTRL_REG4_HR); + if (err) + pr_err("%s: i2c write ctrl_reg4 failed\n", __func__); +#ifdef USES_INPUT_DEV + hrtimer_start(&data->timer, data->poll_delay, HRTIMER_MODE_REL); +#endif + } + + atomic_set(&data->opened, 1); + mutex_unlock(&data->write_lock); + + return err; +} + +static int lsm330dlc_accel_disable(struct lsm330dlc_accel_data *data) +{ + int err = 0; + + mutex_lock(&data->write_lock); +#ifdef USES_MOVEMENT_RECOGNITION + if (data->movement_recog_flag == ON) { + accel_dbgmsg("LOW_PWR_MODE.\n"); + err = i2c_smbus_write_byte_data(data->client, + CTRL_REG1, LOW_PWR_MODE); + if (atomic_read(&data->opened) == 1) + data->ctrl_reg1_shadow = PM_OFF; + } else if (atomic_read(&data->opened) == 1) { +#else + if (atomic_read(&data->opened) == 1) { +#endif +#ifdef USES_INPUT_DEV + hrtimer_cancel(&data->timer); + cancel_work_sync(&data->work); +#endif + err = i2c_smbus_write_byte_data(data->client, CTRL_REG1, + PM_OFF); + data->ctrl_reg1_shadow = PM_OFF; + } + atomic_set(&data->opened, 0); + mutex_unlock(&data->write_lock); + + return err; +} + +/* open command for lsm330dlc_accel device file */ +static int lsm330dlc_accel_open(struct inode *inode, struct file *file) +{ + accel_dbgmsg("is called.\n"); + return 0; +} + +/* release command for lsm330dlc_accel device file */ +static int lsm330dlc_accel_close(struct inode *inode, struct file *file) +{ + accel_dbgmsg("is called.\n"); + return 0; +} + +static int lsm330dlc_accel_set_delay(struct lsm330dlc_accel_data *data + , u64 delay_ns) +{ + int odr_value = ODR1; + int err = 0, i; + + /* round to the nearest delay that is less than + * the requested value (next highest freq) + */ + mutex_lock(&data->write_lock); +#ifdef USES_INPUT_DEV + if (atomic_read(&data->opened)) + hrtimer_cancel(&data->timer); +#endif + + for (i = 0; i < ARRAY_SIZE(odr_delay_table); i++) { + if (delay_ns < odr_delay_table[i].delay_ns) + break; + } + if (i > 0) + i--; + odr_value = odr_delay_table[i].odr; + + accel_dbgmsg("old=%lldns,new=%lldns,odr=0x%x,opened=%d\n", + ktime_to_ns(data->poll_delay), delay_ns, odr_value, + atomic_read(&data->opened)); + data->poll_delay = ns_to_ktime(delay_ns); + if (odr_value != (data->ctrl_reg1_shadow & ODR_MASK)) { + u8 ctrl = (data->ctrl_reg1_shadow & ~ODR_MASK); + ctrl |= odr_value; + data->ctrl_reg1_shadow = ctrl; + err = i2c_smbus_write_byte_data(data->client, CTRL_REG1, ctrl); + if (err < 0) + pr_err("%s: i2c write ctrl_reg1 failed(err=%d)\n", + __func__, err); + } + +#ifdef USES_INPUT_DEV + if (atomic_read(&data->opened)) + hrtimer_start(&data->timer, data->poll_delay, + HRTIMER_MODE_REL); +#endif + mutex_unlock(&data->write_lock); + return err; +} + +/* ioctl command for lsm330dlc_accel device file */ +static long lsm330dlc_accel_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + int err = 0; + struct lsm330dlc_accel_data *data = + container_of(file->private_data, struct lsm330dlc_accel_data, + lsm330dlc_accel_device); + u64 delay_ns; + int enable = 0, j; + s16 raw[3] = {0,}; + struct lsm330dlc_acc xyz_adjusted = {0,}; + + /* cmd mapping */ + switch (cmd) { + case LSM330DLC_ACCEL_IOCTL_SET_ENABLE: + if (copy_from_user(&enable, (void __user *)arg, + sizeof(enable))) + return -EFAULT; + + accel_dbgmsg("opened = %d, enable = %d\n", + atomic_read(&data->opened), enable); + if (enable) + err = lsm330dlc_accel_enable(data); + else + err = lsm330dlc_accel_disable(data); + break; + case LSM330DLC_ACCEL_IOCTL_SET_DELAY: + if (copy_from_user(&delay_ns, (void __user *)arg, + sizeof(delay_ns))) + return -EFAULT; + err = lsm330dlc_accel_set_delay(data, delay_ns); + + break; + case LSM330DLC_ACCEL_IOCTL_GET_DELAY: + if (put_user(ktime_to_ns(data->poll_delay), (u64 __user *)arg)) + return -EFAULT; + break; + case LSM330DLC_ACCEL_IOCTL_READ_XYZ: + err = lsm330dlc_accel_read_xyz(data, &data->acc_xyz); + #ifdef LSM330DLC_ACCEL_LOGGING + accel_dbgmsg("raw x = %d, y = %d, z = %d\n", + data->acc_xyz.x, data->acc_xyz.y, + data->acc_xyz.z); + #endif + if (err) + break; + if (data->axis_adjust) { + raw[0] = data->acc_xyz.x; + raw[1] = data->acc_xyz.y; + raw[2] = data->acc_xyz.z; + for (j = 0; j < 3; j++) { + xyz_adjusted.x += + (position_map[data->position][0][j] * raw[j]); + xyz_adjusted.y += + (position_map[data->position][1][j] * raw[j]); + xyz_adjusted.z += + (position_map[data->position][2][j] * raw[j]); + } +#ifdef LSM330DLC_ACCEL_LOGGING + accel_dbgmsg("adjusted x = %d, y = %d, z = %d\n", + xyz_adjusted.x, xyz_adjusted.y, xyz_adjusted.z); +#endif + if (copy_to_user((void __user *)arg, + &xyz_adjusted, sizeof(xyz_adjusted))) + return -EFAULT; + } else + if (copy_to_user((void __user *)arg, + &data->acc_xyz, sizeof(data->acc_xyz))) + return -EFAULT; + break; + default: + err = -EINVAL; + break; + } + + return err; +} + +static int lsm330dlc_accel_suspend(struct device *dev) +{ + int res = 0; + struct lsm330dlc_accel_data *data = dev_get_drvdata(dev); + +#ifdef USES_INPUT_DEV + if (atomic_read(&data->opened) > 0) { + hrtimer_cancel(&data->timer); + cancel_work_sync(&data->work); + } +#endif + +#ifdef USES_MOVEMENT_RECOGNITION + if (data->movement_recog_flag == ON) { + accel_dbgmsg("LOW_PWR_MODE.\n"); + res = i2c_smbus_write_byte_data(data->client, + CTRL_REG1, LOW_PWR_MODE); + } else if (atomic_read(&data->opened) > 0) { +#else + if (atomic_read(&data->opened) > 0) { +#endif + accel_dbgmsg("PM_OFF.\n"); + res = i2c_smbus_write_byte_data(data->client, + CTRL_REG1, PM_OFF); + } + + return res; +} + +static int lsm330dlc_accel_resume(struct device *dev) +{ + int res = 0; + struct lsm330dlc_accel_data *data = dev_get_drvdata(dev); + + if (atomic_read(&data->opened) > 0) { + accel_dbgmsg("ctrl_reg1_shadow = 0x%x\n" + , data->ctrl_reg1_shadow); + res = i2c_smbus_write_byte_data(data->client, CTRL_REG1, + data->ctrl_reg1_shadow); +#ifdef USES_INPUT_DEV + hrtimer_start(&data->timer, + data->poll_delay, HRTIMER_MODE_REL); +#endif + } + + return res; +} + +static const struct dev_pm_ops lsm330dlc_accel_pm_ops = { + .suspend = lsm330dlc_accel_suspend, + .resume = lsm330dlc_accel_resume, +}; + +static const struct file_operations lsm330dlc_accel_fops = { + .owner = THIS_MODULE, + .open = lsm330dlc_accel_open, + .release = lsm330dlc_accel_close, + .unlocked_ioctl = lsm330dlc_accel_ioctl, +}; + +static ssize_t lsm330dlc_accel_fs_read(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct lsm330dlc_accel_data *data = dev_get_drvdata(dev); + + if (data->axis_adjust) { + int i, j; + s16 raw[3] = {0,}, accel_adjusted[3] = {0,}; + + raw[0] = data->acc_xyz.x; + raw[1] = data->acc_xyz.y; + raw[2] = data->acc_xyz.z; + for (i = 0; i < 3; i++) { + for (j = 0; j < 3; j++) + accel_adjusted[i] + += position_map[data->position][i][j] * raw[j]; + } +#ifdef CONFIG_SLP + return sprintf(buf, "raw:%d,%d,%d adjust:%d,%d,%d\n", + raw[0], raw[1], raw[2], accel_adjusted[0], + accel_adjusted[1], accel_adjusted[2]); +#else + return sprintf(buf, "%d,%d,%d\n", + accel_adjusted[0], accel_adjusted[1], + accel_adjusted[2]); +#endif + } else + return sprintf(buf, "%d,%d,%d\n", + data->acc_xyz.x, data->acc_xyz.y, data->acc_xyz.z); +} + +static ssize_t lsm330dlc_accel_calibration_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int err; + struct lsm330dlc_accel_data *data = dev_get_drvdata(dev); + + err = lsm330dlc_accel_open_calibration(data); + if (err < 0) + pr_err("%s: lsm330dlc_accel_open_calibration() failed\n"\ + , __func__); + + if (!data->cal_data.x && !data->cal_data.y && !data->cal_data.z) + err = -1; + + return sprintf(buf, "%d %d %d %d\n", + err, data->cal_data.x, data->cal_data.y, data->cal_data.z); +} + +static ssize_t lsm330dlc_accel_calibration_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct lsm330dlc_accel_data *data = dev_get_drvdata(dev); + bool do_calib; + int err; + + if (sysfs_streq(buf, "1")) + do_calib = true; + else if (sysfs_streq(buf, "0")) + do_calib = false; + else { + pr_debug("%s: invalid value %d\n", __func__, *buf); + return -EINVAL; + } + + if (atomic_read(&data->opened) == 0) { + /* if off, turn on the device.*/ + err = i2c_smbus_write_byte_data(data->client, CTRL_REG1, + DEFAULT_POWER_ON_SETTING); + if (err) { + pr_err("%s: i2c write ctrl_reg1 failed(err=%d)\n", + __func__, err); + } + } + + err = lsm330dlc_accel_do_calibrate(dev, do_calib); + if (err < 0) { + pr_err("%s: lsm330dlc_accel_do_calibrate() failed\n", __func__); + return err; + } + + if (atomic_read(&data->opened) == 0) { + /* if off, turn on the device.*/ + err = i2c_smbus_write_byte_data(data->client, CTRL_REG1, + PM_OFF); + if (err) { + pr_err("%s: i2c write ctrl_reg1 failed(err=%d)\n", + __func__, err); + } + } + + return count; +} + +#ifdef USES_MOVEMENT_RECOGNITION +static ssize_t lsm330dlc_accel_reactive_alert_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int onoff = OFF, err = 0, ctrl_reg = 0; + struct lsm330dlc_accel_data *data = dev_get_drvdata(dev); + bool factory_test = false; + struct lsm330dlc_acc raw_data; + u8 thresh1 = 0, thresh2 = 0; + + if (sysfs_streq(buf, "1")) + onoff = ON; + else if (sysfs_streq(buf, "0")) + onoff = OFF; + else if (sysfs_streq(buf, "2")) { + onoff = ON; + factory_test = true; + accel_dbgmsg("factory_test = %d\n", factory_test); + } else { + pr_err("%s: invalid value %d\n", __func__, *buf); + return -EINVAL; + } + + if (onoff == ON && data->movement_recog_flag == OFF) { + accel_dbgmsg("reactive alert is on.\n"); + data->interrupt_state = 0; /* Init interrupt state.*/ + + if (atomic_read(&data->opened) == 0) { + err = i2c_smbus_write_byte_data(data->client, CTRL_REG1, + FASTEST_MODE); + if (err) { + ctrl_reg = CTRL_REG1; + goto err_i2c_write; + } + /* trun on time, T = 7/odr ms */ + usleep_range(10000, 10000); + } + enable_irq(data->client->irq); + if (device_may_wakeup(&data->client->dev)) + enable_irq_wake(data->client->irq); + /* Get x, y, z data to set threshold1, threshold2. */ + err = lsm330dlc_accel_read_xyz(data, &raw_data); + accel_dbgmsg("raw x = %d, y = %d, z = %d\n", + raw_data.x, raw_data.y, raw_data.z); + if (err < 0) { + pr_err("%s: lsm330dlc_accel_read_xyz failed\n", + __func__); + goto exit; + } + if (atomic_read(&data->opened) == 0) { + err = i2c_smbus_write_byte_data(data->client, CTRL_REG1, + LOW_PWR_MODE); /* Change to 50Hz*/ + if (err) { + ctrl_reg = CTRL_REG1; + goto err_i2c_write; + } + } + /* Change raw data to threshold value & settng threshold */ + thresh1 = (MAX(ABS(raw_data.x), ABS(raw_data.y)) + + DYNAMIC_THRESHOLD)/16; + if (factory_test == true) + thresh2 = 0; /* for z axis */ + else + thresh2 = (ABS(raw_data.z) + DYNAMIC_THRESHOLD2)/16; + accel_dbgmsg("threshold1 = 0x%x, threshold2 = 0x%x\n", + thresh1, thresh2); + err = i2c_smbus_write_byte_data(data->client, INT1_THS + , thresh1); + if (err) { + ctrl_reg = INT1_THS; + goto err_i2c_write; + } + ctrl_reg = INT2_THS; + err = i2c_smbus_write_byte_data(data->client, INT2_THS, + thresh2); + if (err) { + ctrl_reg = INT2_THS; + goto err_i2c_write; + } + /* INT_A enable */ + err = i2c_smbus_write_byte_data(data->client, CTRL_REG3, + DEFAULT_CTRL3_SETTING); + if (err) { + ctrl_reg = CTRL_REG3; + goto err_i2c_write; + } + err = i2c_smbus_write_byte_data(data->client, CTRL_REG6, + DEFAULT_CTRL6_SETTING); + if (err) { + ctrl_reg = CTRL_REG6; + goto err_i2c_write; + } + + data->movement_recog_flag = ON; + } else if (onoff == OFF && data->movement_recog_flag == ON) { + accel_dbgmsg("reactive alert is off.\n"); + /* INT_A disable */ + err = i2c_smbus_write_byte_data(data->client, CTRL_REG3, + PM_OFF); + if (err) { + ctrl_reg = CTRL_REG3; + goto err_i2c_write; + } + err = i2c_smbus_write_byte_data(data->client, CTRL_REG6, + PM_OFF); + if (err) { + ctrl_reg = CTRL_REG6; + goto err_i2c_write; + } + if (device_may_wakeup(&data->client->dev)) + disable_irq_wake(data->client->irq); + disable_irq_nosync(data->client->irq); + /* return the power state */ + err = i2c_smbus_write_byte_data(data->client, CTRL_REG1, + data->ctrl_reg1_shadow); + if (err) { + ctrl_reg = CTRL_REG1; + goto err_i2c_write; + } + data->movement_recog_flag = OFF; + data->interrupt_state = 0; /* Init interrupt state.*/ + } + return count; +err_i2c_write: + pr_err("%s: i2c write ctrl_reg = 0x%d failed(err=%d)\n", + __func__, ctrl_reg, err); +exit: + return ((err < 0) ? err : -err); +} + +static ssize_t lsm330dlc_accel_reactive_alert_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct lsm330dlc_accel_data *data = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", data->interrupt_state); +} +#endif + +#ifdef DEBUG_ODR +static ssize_t lsm330dlc_accel_odr_read(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct lsm330dlc_accel_data *data = dev_get_drvdata(dev); + u8 odr_data = 0, fs_data = 0; + + /* read output data rate & operation mode */ + odr_data = (u8)i2c_smbus_read_byte_data(data->client, CTRL_REG1); + if (odr_data < 0) + pr_err("%s, read CTRL_REG1 failed\n", __func__); + + /* read full scale setting */ + fs_data = (u8)i2c_smbus_read_byte_data(data->client, CTRL_REG4); + if (fs_data < 0) + pr_err("%s, read CTRL_REG4 failed\n", __func__); + + return sprintf(buf, "odr:0x%x,fs:0x%x\n", + odr_data, fs_data); +} +#endif + +#ifdef USES_INPUT_DEV +static ssize_t lsm330dlc_accel_delay_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct lsm330dlc_accel_data *data = dev_get_drvdata(dev); + + return sprintf(buf, "%lld\n", ktime_to_ns(data->poll_delay)); +} + +static ssize_t lsm330dlc_accel_delay_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int err = 0; + u64 delay_ns = 0; + struct lsm330dlc_accel_data *data = dev_get_drvdata(dev); + + err = strict_strtoll(buf, 10, &delay_ns); + + if (err < 0) + return err; + + lsm330dlc_accel_set_delay(data, delay_ns); + + return count; +} + +static ssize_t lsm330dlc_enable_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct lsm330dlc_accel_data *data = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", atomic_read(&data->opened)); +} + +static ssize_t lsm330dlc_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct lsm330dlc_accel_data *data = dev_get_drvdata(dev); + int enable = -1, err = 0; + + err = kstrtoint(buf, 10, &enable); + if (err < 0) + pr_err("%s, kstrtoint failed.", __func__); + + accel_dbgmsg("opened = %d, enable = %d\n", + atomic_read(&data->opened), enable); + + if (enable) + lsm330dlc_accel_enable(data); + else + lsm330dlc_accel_disable(data); + + return count; +} + +static DEVICE_ATTR(poll_delay, 0644, + lsm330dlc_accel_delay_show, lsm330dlc_accel_delay_store); +static DEVICE_ATTR(enable, 0644, + lsm330dlc_enable_show, lsm330dlc_enable_store); + +static struct attribute *lsm330dlc_sysfs_attrs[] = { + &dev_attr_enable.attr, + &dev_attr_poll_delay.attr, + NULL +}; + +static struct attribute_group lsm330dlc_attribute_group = { + .attrs = lsm330dlc_sysfs_attrs, +}; +#endif + +static ssize_t +lsm330dlc_accel_position_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct lsm330dlc_accel_data *data = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", data->position); +} + +static ssize_t +lsm330dlc_accel_position_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct lsm330dlc_accel_data *data = dev_get_drvdata(dev); + int err = 0; + + err = kstrtoint(buf, 10, &data->position); + if (err < 0) + pr_err("%s, kstrtoint failed.", __func__); + + return count; +} + +static ssize_t lsm330dlc_accel_vendor_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%s\n", VENDOR); +} + +static ssize_t lsm330dlc_accel_name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%s\n", CHIP_ID); +} + +static DEVICE_ATTR(name, 0664, + lsm330dlc_accel_name_show, NULL); + +static DEVICE_ATTR(vendor, 0664, + lsm330dlc_accel_vendor_show, NULL); + +static DEVICE_ATTR(position, 0664, + lsm330dlc_accel_position_show, lsm330dlc_accel_position_store); + +static DEVICE_ATTR(calibration, 0664, + lsm330dlc_accel_calibration_show + , lsm330dlc_accel_calibration_store); +static DEVICE_ATTR(raw_data, 0664, lsm330dlc_accel_fs_read, NULL); +#ifdef USES_MOVEMENT_RECOGNITION +static DEVICE_ATTR(reactive_alert, 0664, + lsm330dlc_accel_reactive_alert_show, + lsm330dlc_accel_reactive_alert_store); +#endif + +#ifdef DEBUG_ODR +static DEVICE_ATTR(odr, 0664, lsm330dlc_accel_odr_read, NULL); +#endif + +void lsm330dlc_accel_shutdown(struct i2c_client *client) +{ + int res = 0; + struct lsm330dlc_accel_data *data = i2c_get_clientdata(client); + + accel_dbgmsg("is called.\n"); +#ifdef USES_INPUT_DEV + if (atomic_read(&data->opened) > 0) { + hrtimer_cancel(&data->timer); + cancel_work_sync(&data->work); + } +#endif + res = i2c_smbus_write_byte_data(data->client, + CTRL_REG1, PM_OFF); + if (res < 0) + pr_err("%s: pm_off failed %d\n", __func__, res); +} + +#ifdef USES_MOVEMENT_RECOGNITION +static irqreturn_t lsm330dlc_accel_interrupt_thread(int irq\ + , void *lsm330dlc_accel_data_p) +{ + struct lsm330dlc_accel_data *data = lsm330dlc_accel_data_p; +#ifdef DEBUG_REACTIVE_ALERT + u8 int1_src_reg = 0, int2_src_reg = 0; +#else + int err = 0; +#endif + +#ifndef DEBUG_REACTIVE_ALERT + /* INT1_A disable */ + err = i2c_smbus_write_byte_data(data->client, CTRL_REG3, + PM_OFF); + if (err) + pr_err("%s: i2c write ctrl_reg3 failed\n", __func__); + + /* INT2_A disable */ + err = i2c_smbus_write_byte_data(data->client, CTRL_REG6, + PM_OFF); + if (err) + pr_err("%s: i2c write ctrl_reg6 failed\n", __func__); +#else + int1_src_reg = (u8)i2c_smbus_read_byte_data(data->client, INT1_SRC); + if (int1_src_reg < 0) + pr_err("%s, read int1_src failed\n", __func__); + accel_dbgmsg("interrupt source reg1 = 0x%x\n", int1_src_reg); + + int2_src_reg = (u8)i2c_smbus_read_byte_data(data->client, INT2_SRC); + if (int2_src_reg < 0) + pr_err("%s, read int2_src failed\n", __func__); + accel_dbgmsg("interrupt source reg2 = 0x%x\n", int2_src_reg); +#endif + + data->interrupt_state = 1; + wake_lock_timeout(&data->reactive_wake_lock, msecs_to_jiffies(2000)); + accel_dbgmsg("irq is handled\n"); + + return IRQ_HANDLED; +} +#endif + +#ifdef USES_INPUT_DEV +static enum hrtimer_restart lsm330dlc_timer_func(struct hrtimer *timer) +{ + struct lsm330dlc_accel_data *data + = container_of(timer, struct lsm330dlc_accel_data, timer); + queue_work(data->work_queue, &data->work); + hrtimer_forward_now(&data->timer, data->poll_delay); + return HRTIMER_RESTART; +} + +static void lsm330dlc_work_func(struct work_struct *work) +{ + struct lsm330dlc_accel_data *data + = container_of(work, struct lsm330dlc_accel_data, work); + s16 raw[3] = {0,}, accel_adjusted[3] = {0,}; + int i, j; + + lsm330dlc_accel_read_xyz(data, &data->acc_xyz); + + if (data->axis_adjust) { + raw[0] = data->acc_xyz.x; + raw[1] = data->acc_xyz.y; + raw[2] = data->acc_xyz.z; + for (i = 0; i < 3; i++) { + for (j = 0; j < 3; j++) + accel_adjusted[i] + += position_map[data->position][i][j] * raw[j]; + } + } else { + accel_adjusted[0] = data->acc_xyz.x; + accel_adjusted[1] = data->acc_xyz.y; + accel_adjusted[2] = data->acc_xyz.z; + } + input_report_rel(data->input_dev, REL_X, accel_adjusted[0]); + input_report_rel(data->input_dev, REL_Y, accel_adjusted[1]); + input_report_rel(data->input_dev, REL_Z, accel_adjusted[2]); + input_sync(data->input_dev); +#ifdef LSM330DLC_ACCEL_LOGGING + accel_dbgmsg("raw x = %d, y = %d, z = %d\n", + accel_adjusted[0], accel_adjusted[1], accel_adjusted[2]); +#endif +} +#endif + +static int lsm330dlc_accel_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct lsm330dlc_accel_data *data; + struct accel_platform_data *pdata; + int err = 0; + + accel_dbgmsg("is started\n"); + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_WRITE_BYTE_DATA | + I2C_FUNC_SMBUS_READ_I2C_BLOCK)) { + pr_err("%s: i2c functionality check failed!\n", __func__); + err = -ENODEV; + goto exit; + } + + data = kzalloc(sizeof(struct lsm330dlc_accel_data), GFP_KERNEL); + if (data == NULL) { + dev_err(&client->dev, + "failed to allocate memory for module data\n"); + err = -ENOMEM; + goto exit; + } + + /* Checking device */ + err = i2c_smbus_write_byte_data(client, CTRL_REG1, + PM_OFF); + if (err) { + pr_err("%s: there is no such device, err = %d\n", + __func__, err); + goto err_read_reg; + } + + data->client = client; + i2c_set_clientdata(client, data); + + mutex_init(&data->read_lock); + mutex_init(&data->write_lock); + atomic_set(&data->opened, 0); +#ifdef USES_INPUT_DEV + hrtimer_init(&data->timer, + CLOCK_MONOTONIC, HRTIMER_MODE_REL); + data->poll_delay = ns_to_ktime(200 * NSEC_PER_MSEC); + data->timer.function = lsm330dlc_timer_func; + data->work_queue = + create_singlethread_workqueue("lsm330dlc_workqueue"); + if (!data->work_queue) { + err = -ENOMEM; + pr_err("%s: count not create workqueue\n", __func__); + goto err_create_workqueue; + } + + INIT_WORK(&data->work, lsm330dlc_work_func); + + data->input_dev = input_allocate_device(); + if (!data->input_dev) { + pr_err("%s: count not allocate input device\n", __func__); + err = -ENOMEM; + goto err_input_allocate; + } + + input_set_drvdata(data->input_dev, data); + data->input_dev->name = "accelerometer_sensor"; + input_set_capability(data->input_dev, EV_REL, REL_X); + input_set_capability(data->input_dev, EV_REL, REL_Y); + input_set_capability(data->input_dev, EV_REL, REL_Z); + + err = input_register_device(data->input_dev); + if (err < 0) { + input_free_device(data->input_dev); + pr_err("%s: could not register input device\n", __func__); + goto err_input_allocate; + } + + err = sysfs_create_group(&data->input_dev->dev.kobj, + &lsm330dlc_attribute_group); + if (err) { + pr_err("%s: could not create sysfs group\n", __func__); + goto err_create_sysfs; + } +#else + /* sensor HAL expects to find /dev/accelerometer */ + data->lsm330dlc_accel_device.minor = MISC_DYNAMIC_MINOR; + data->lsm330dlc_accel_device.name = ACC_DEV_NAME; + data->lsm330dlc_accel_device.fops = &lsm330dlc_accel_fops; + + err = misc_register(&data->lsm330dlc_accel_device); + if (err) { + pr_err("%s: misc_register failed\n", __FILE__); + goto err_misc_register; + } +#endif + +#ifdef USES_MOVEMENT_RECOGNITION + data->movement_recog_flag = OFF; + /* wake lock init for accelerometer sensor */ + wake_lock_init(&data->reactive_wake_lock, WAKE_LOCK_SUSPEND, + "reactive_wake_lock"); + err = i2c_smbus_write_byte_data(data->client, INT1_THS + , DEFAULT_THRESHOLD); + if (err) { + pr_err("%s: i2c write int1_ths failed\n", __func__); + goto err_request_irq; + } + + err = i2c_smbus_write_byte_data(data->client, INT1_DURATION + , MOVEMENT_DURATION); + if (err) { + pr_err("%s: i2c write int1_duration failed\n" + , __func__); + goto err_request_irq; + } + + err = i2c_smbus_write_byte_data(data->client, INT1_CFG, + DEFAULT_INTERRUPT_SETTING); + if (err) { + pr_err("%s: i2c write int1_cfg failed\n", __func__); + goto err_request_irq; + } + + err = i2c_smbus_write_byte_data(data->client, INT2_THS + , DEFAULT_THRESHOLD); + if (err) { + pr_err("%s: i2c write int2_ths failed\n", __func__); + goto err_request_irq; + } + + err = i2c_smbus_write_byte_data(data->client, INT2_DURATION + , MOVEMENT_DURATION); + if (err) { + pr_err("%s: i2c write int1_duration failed\n" + , __func__); + goto err_request_irq; + } + err = i2c_smbus_write_byte_data(data->client, INT2_CFG, + DEFAULT_INTERRUPT2_SETTING); + if (err) { + pr_err("%s: i2c write ctrl_reg3 failed\n", __func__); + goto err_request_irq; + } + + err = request_threaded_irq(data->client->irq, NULL, + lsm330dlc_accel_interrupt_thread\ + , IRQF_TRIGGER_RISING | IRQF_ONESHOT,\ + "lsm330dlc_accel", data); + if (err < 0) { + pr_err("%s: can't allocate irq.\n", __func__); + goto err_request_irq; + } + + disable_irq(data->client->irq); + device_init_wakeup(&data->client->dev, 1); +#endif + + /* creating device for test & calibration */ + data->dev = sensors_classdev_register("accelerometer_sensor"); + if (IS_ERR(data->dev)) { + pr_err("%s: class create failed(accelerometer_sensor)\n", + __func__); + err = PTR_ERR(data->dev); + goto err_acc_device_create; + } + + err = device_create_file(data->dev, &dev_attr_raw_data); + if (err < 0) { + pr_err("%s: Failed to create device file(%s)\n", + __func__, dev_attr_raw_data.attr.name); + goto err_acc_device_create_file; + } + + err = device_create_file(data->dev, &dev_attr_calibration); + if (err < 0) { + pr_err("%s: Failed to create device file(%s)\n", + __func__, dev_attr_calibration.attr.name); + goto err_cal_device_create_file; + } + +#ifdef USES_MOVEMENT_RECOGNITION + err = device_create_file(data->dev, &dev_attr_reactive_alert); + if (err < 0) { + pr_err("%s: Failed to create device file(%s)\n", + __func__, dev_attr_reactive_alert.attr.name); + goto err_reactive_device_create_file; + } +#endif + +#ifdef DEBUG_ODR + err = device_create_file(data->dev, &dev_attr_odr); + if (err < 0) { + pr_err("%s: Failed to create device file(%s)\n", + __func__, dev_attr_odr.attr.name); + goto err_odr_device_create_file; + } +#endif + + /* set mounting position of the sensor */ + pdata = client->dev.platform_data; + if (!pdata) { + /*Set by default position 2, it doesn't adjust raw value*/ + data->position = 2; + data->axis_adjust = false; + accel_dbgmsg("using defualt position = %d\n", data->position); + } else { + data->position = pdata->accel_get_position(); + data->axis_adjust = pdata->axis_adjust; + accel_dbgmsg("successful, position = %d\n", data->position); + } + err = device_create_file(data->dev, &dev_attr_position); + if (err < 0) { + pr_err("%s: Failed to create device file(%s)\n", + __func__, dev_attr_position.attr.name); + goto err_position_device_create_file; + } + err = device_create_file(data->dev, &dev_attr_vendor); + if (err < 0) { + pr_err("%s: Failed to create device file(%s)\n", + __func__, dev_attr_vendor.attr.name); + goto err_vendor_device_create_file; + } + err = device_create_file(data->dev, &dev_attr_name); + if (err < 0) { + pr_err("%s: Failed to create device file(%s)\n", + __func__, dev_attr_name.attr.name); + goto err_name_device_create_file; + } + + dev_set_drvdata(data->dev, data); + + return 0; + +err_name_device_create_file: + device_remove_file(data->dev, &dev_attr_vendor); +err_vendor_device_create_file: + device_remove_file(data->dev, &dev_attr_position); +err_position_device_create_file: +#ifdef DEBUG_ODR + device_remove_file(data->dev, &dev_attr_odr); +err_odr_device_create_file: +#ifdef USES_MOVEMENT_RECOGNITION + device_remove_file(data->dev, &dev_attr_reactive_alert); +#else + device_remove_file(data->dev, &dev_attr_calibration); +#endif +#endif +#ifdef USES_MOVEMENT_RECOGNITION +err_reactive_device_create_file: + device_remove_file(data->dev, &dev_attr_calibration); +#endif +err_cal_device_create_file: + device_remove_file(data->dev, &dev_attr_raw_data); +err_acc_device_create_file: + sensors_classdev_unregister(data->dev); +err_acc_device_create: +#ifdef USES_MOVEMENT_RECOGNITION + free_irq(data->client->irq, data); +err_request_irq: + wake_lock_destroy(&data->reactive_wake_lock); +#endif +#ifdef USES_INPUT_DEV + sysfs_remove_group(&data->input_dev->dev.kobj, + &lsm330dlc_attribute_group); +err_create_sysfs: + input_unregister_device(data->input_dev); +err_input_allocate: + destroy_workqueue(data->work_queue); +err_create_workqueue: +#else + misc_deregister(&data->lsm330dlc_accel_device); +err_misc_register: +#endif + mutex_destroy(&data->read_lock); + mutex_destroy(&data->write_lock); +err_read_reg: + kfree(data); +exit: + return err; +} + +static int lsm330dlc_accel_remove(struct i2c_client *client) +{ + struct lsm330dlc_accel_data *data = i2c_get_clientdata(client); + int err = 0; + + if (atomic_read(&data->opened) > 0) { +#ifdef USES_INPUT_DEV + hrtimer_cancel(&data->timer); + cancel_work_sync(&data->work); +#endif + err = i2c_smbus_write_byte_data(data->client, + CTRL_REG1, PM_OFF); + if (err < 0) + pr_err("%s: pm_off failed %d\n", __func__, err); + } + + device_remove_file(data->dev, &dev_attr_name); + device_remove_file(data->dev, &dev_attr_vendor); + device_remove_file(data->dev, &dev_attr_position); +#ifdef DEBUG_ODR + device_remove_file(data->dev, &dev_attr_odr); +#endif +#ifdef USES_MOVEMENT_RECOGNITION + wake_lock_destroy(&data->reactive_wake_lock); + device_remove_file(data->dev, &dev_attr_reactive_alert); +#endif + device_remove_file(data->dev, &dev_attr_calibration); + device_remove_file(data->dev, &dev_attr_raw_data); + sensors_classdev_unregister(data->dev); + +#ifdef USES_MOVEMENT_RECOGNITION + device_init_wakeup(&data->client->dev, 0); + free_irq(data->client->irq, data); +#endif +#ifdef USES_INPUT_DEV + destroy_workqueue(data->work_queue); + sysfs_remove_group(&data->input_dev->dev.kobj, + &lsm330dlc_attribute_group); + input_unregister_device(data->input_dev); +#else + misc_deregister(&data->lsm330dlc_accel_device); +#endif + mutex_destroy(&data->read_lock); + mutex_destroy(&data->write_lock); + kfree(data); + + return 0; +} + +static const struct i2c_device_id lsm330dlc_accel_id[] = { + { "lsm330dlc_accel", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, lsm330dlc_accel_id); + +static struct i2c_driver lsm330dlc_accel_driver = { + .probe = lsm330dlc_accel_probe, + .shutdown = lsm330dlc_accel_shutdown, + .remove = __devexit_p(lsm330dlc_accel_remove), + .id_table = lsm330dlc_accel_id, + .driver = { + .pm = &lsm330dlc_accel_pm_ops, + .owner = THIS_MODULE, + .name = "lsm330dlc_accel", + }, +}; + +static int __init lsm330dlc_accel_init(void) +{ + return i2c_add_driver(&lsm330dlc_accel_driver); +} + +static void __exit lsm330dlc_accel_exit(void) +{ + i2c_del_driver(&lsm330dlc_accel_driver); +} + +module_init(lsm330dlc_accel_init); +module_exit(lsm330dlc_accel_exit); + +MODULE_DESCRIPTION("LSM330DLC accelerometer driver"); +MODULE_AUTHOR("Samsung Electronics"); +MODULE_LICENSE("GPL"); |