summaryrefslogtreecommitdiff
path: root/drivers/sensor/k2hh.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/sensor/k2hh.c')
-rw-r--r--drivers/sensor/k2hh.c1523
1 files changed, 1523 insertions, 0 deletions
diff --git a/drivers/sensor/k2hh.c b/drivers/sensor/k2hh.c
new file mode 100644
index 00000000..aa0831cc
--- /dev/null
+++ b/drivers/sensor/k2hh.c
@@ -0,0 +1,1523 @@
+/*
+ * Copyright (C) 2013 Samsung Electronics. All rights reserved.
+ *
+ * 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.
+ *
+ * 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/fs.h>
+#include <linux/uaccess.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/errno.h>
+#include <linux/input.h>
+#include <linux/workqueue.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/of_gpio.h>
+#include <linux/wakelock.h>
+#include <linux/regulator/consumer.h>
+#include <linux/irq.h>
+#include <linux/gpio.h>
+#include <linux/interrupt.h>
+#include <linux/sensors_core.h>
+
+
+#define I2C_M_WR 0 /* for i2c Write */
+#define I2c_M_RD 1 /* for i2c Read */
+#define READ_DATA_LENTH 6
+
+#define VENDOR_NAME "STM"
+#define MODEL_NAME "K2HH"
+#define MODULE_NAME "accelerometer_sensor"
+
+#define CALIBRATION_FILE_PATH "/efs/accel_calibration_data"
+#define CALIBRATION_DATA_AMOUNT 20
+#define MAX_ACCEL_1G 16384
+
+#define K2HH_DEFAULT_DELAY 200000000LL
+
+#define CHIP_ID_RETRIES 3
+#define ACCEL_LOG_TIME 15 /* 15 sec */
+
+#define K2HH_MODE_SUSPEND 0
+#define K2HH_MODE_NORMAL 1
+
+#define SENSITIVITY_2G 61
+#define SENSITIVITY_4G 122
+#define SENSITIVITY_8G 244
+
+#define K2HH_RANGE_2G 0
+#define K2HH_RANGE_4G 1
+#define K2HH_RANGE_8G 2
+
+#define WHOAMI_REG 0x0F
+#define AXISDATA_REG 0x28
+
+#define CTRL1_REG 0x20
+#define CTRL2_REG 0x21
+#define CTRL3_REG 0x22
+#define CTRL4_REG 0x23
+#define CTRL5_REG 0x24
+#define CTRL6_REG 0x25
+#define CTRL7_REG 0x26
+#define STATUS_REG 0x27
+
+/* CTRL1 */
+#define CTRL1_HR_DISABLE 0x00
+#define CTRL1_HR_ENABLE 0x80
+#define CTRL1_HR_MASK 0x80
+#define CTRL1_BDU_ENABLE 0x08
+#define CTRL1_BDU_MASK 0x08
+
+/* CTRL2 */
+#define CTRL2_IG1_INT1 0x08
+
+/* CTRL3 */
+#define CTRL3_IG1_INT1 0x08
+
+/* CTRL7 */
+#define CTRL7_LIR2 0x08
+#define CTRL7_LIR1 0x04
+
+#define ACC_PM_OFF 0x00
+#define ACC_ENABLE_ALL_AXES 0x07
+
+#define INT_CFG1_REG 0x30
+#define INT_SRC1_REG 0x31
+#define K2HH_CHIP_ID 0x41
+
+#define K2HH_ACC_FS_MASK 0x30
+#define K2HH_ACC_ODR_MASK 0x70
+#define K2HH_ACC_AXES_MASK 0x07
+
+#define SELF_TEST_2G_MAX_LSB 24576
+#define SELF_TEST_2G_MIN_LSB 1146
+
+#define K2HH_ACC_FS_2G 0x00
+#define K2HH_ACC_FS_4G 0x20
+#define K2HH_ACC_FS_8G 0x30
+
+#define INT_THSX1_REG 0x32
+#define INT_THSY1_REG 0x33
+#define INT_THSZ1_REG 0x34
+
+#define DYNAMIC_THRESHOLD 5000
+
+enum {
+ OFF = 0,
+ ON = 1
+};
+
+struct k2hh_v {
+ union {
+ s16 v[3];
+ struct {
+ s16 x;
+ s16 y;
+ s16 z;
+ };
+ };
+};
+
+struct k2hh_p {
+ struct wake_lock reactive_wake_lock;
+ struct i2c_client *client;
+ struct input_dev *input;
+ struct delayed_work irq_work;
+ struct device *factory_device;
+ struct k2hh_v accdata;
+ struct k2hh_v caldata;
+ struct mutex mode_mutex;
+ struct hrtimer accel_timer;
+ struct workqueue_struct *accel_wq;
+ struct work_struct work;
+ struct regulator *vdd;
+ //struct regulator *vio;
+ ktime_t poll_delay;
+ atomic_t enable;
+
+ int recog_flag;
+ int irq1;
+ int irq_state;
+ int acc_int1;
+ int sda_gpio;
+ int scl_gpio;
+ int time_count;
+
+ u8 axis_map_x;
+ u8 axis_map_y;
+ u8 axis_map_z;
+
+ u8 negate_x;
+ u8 negate_y;
+ u8 negate_z;
+};
+
+#define ACC_ODR10 0x10 /* 10Hz output data rate */
+#define ACC_ODR50 0x20 /* 50Hz output data rate */
+#define ACC_ODR100 0x30 /* 100Hz output data rate */
+#define ACC_ODR200 0x40 /* 200Hz output data rate */
+#define ACC_ODR400 0x50 /* 400Hz output data rate */
+#define ACC_ODR800 0x60 /* 800Hz output data rate */
+#define ACC_ODR_MASK 0X70
+
+struct k2hh_acc_odr {
+ unsigned int cutoff_ms;
+ unsigned int mask;
+};
+
+#define OUTPUT_ALWAYS_ANTI_ALIASED
+
+const struct k2hh_acc_odr k2hh_acc_odr_table[] = {
+ { 2, ACC_ODR800},
+ { 3, ACC_ODR400},
+ { 5, ACC_ODR200},
+ { 10, ACC_ODR100},
+#ifndef OUTPUT_ALWAYS_ANTI_ALIASED
+ { 20, ACC_ODR50},
+ {100, ACC_ODR10},
+#endif
+};
+
+static int k2hh_open_calibration(struct k2hh_p *);
+static int k2hh_regulator_onoff(struct k2hh_p *data, bool onoff);
+#if 0
+static int k2hh_i2c_recovery(struct k2hh_p *data)
+{
+ int ret = 0;
+ struct gpiomux_setting old_config[2];
+ struct gpiomux_setting recovery_config = {
+ .func = GPIOMUX_FUNC_3,
+ .drv = GPIOMUX_DRV_2MA,
+ .pull = GPIOMUX_PULL_NONE,
+ .dir = GPIOMUX_IN,
+ };
+
+ if (data->sda_gpio < 0 || data->scl_gpio < 0) {
+ pr_err("[SENSOR]: %s - no sda, scl gpio\n", __func__);
+ return -EINVAL;
+ }
+
+ pr_err("[SENSOR] ################# %s #################\n", __func__);
+
+ /*If LDO's are disabled then enable, Fallback LDO off scenario*/
+ do {
+ if( 0 == regulator_is_enabled(data->vdd) ) {
+ ret = regulator_enable(data->vdd);
+ if (ret){
+ pr_err(KERN_ERR "%s: vdd enable failed (%d)\n",__func__, ret);
+ break;
+ }
+ ret++;
+ }
+ if( 0 == regulator_is_enabled(data->vio) ) {
+ ret = regulator_enable(data->vio);
+ if (ret){
+ pr_err(KERN_ERR "%s: vio enable failed (%d)\n",__func__, ret);
+ break;
+ }
+ ret++;
+ }
+ if(ret)
+ msleep(30);
+ }while(0);
+
+ if(ret){
+ pr_err("RECOVERY called as one of regulator was off:****\n Regulator \
+ on/recovery complete****\n ");
+ }
+
+
+ ret = msm_gpiomux_write(data->sda_gpio, GPIOMUX_ACTIVE,
+ &recovery_config, &old_config[0]);
+ if (ret < 0) {
+ pr_err("[SENSOR]: %s sda_gpio have no active setting %d\n",
+ __func__, ret);
+ goto exit;
+ }
+
+ ret = msm_gpiomux_write(data->scl_gpio, GPIOMUX_ACTIVE,
+ &recovery_config, &old_config[1]);
+ if (ret < 0) {
+ pr_err("[SENSOR]: %s scl_gpio have no active setting %d\n",
+ __func__, ret);
+ goto exit;
+ }
+
+ ret = gpio_request(data->sda_gpio, "SENSOR_SDA");
+ if (ret < 0) {
+ pr_err("[SENSOR]: %s - gpio %d request failed (%d)\n",
+ __func__, data->sda_gpio, ret);
+ goto exit;
+ }
+
+ ret = gpio_request(data->scl_gpio, "SENSOR_SCL");
+ if (ret < 0) {
+ pr_err("[SENSOR]: %s - gpio %d request failed (%d)\n",
+ __func__, data->scl_gpio, ret);
+ gpio_free(data->scl_gpio);
+ goto exit;
+ }
+
+ ret = gpio_direction_input(data->sda_gpio);
+ if (ret < 0) {
+ pr_err("[SENSOR]: %s - failed to set gpio %d as output (%d)\n",
+ __func__, data->sda_gpio, ret);
+ goto exit_to_free;
+ }
+
+ ret = gpio_direction_input(data->scl_gpio);
+ if (ret < 0) {
+ pr_err("[SENSOR]: %s - failed to set gpio %d as output (%d)\n",
+ __func__, data->scl_gpio, ret);
+ goto exit_to_free;
+ }
+
+ /*Setting initial value after recovery*/
+ udelay(100);
+ gpio_set_value_cansleep(data->sda_gpio, 0);
+ gpio_set_value_cansleep(data->scl_gpio, 0);
+ udelay(100);
+ gpio_set_value_cansleep(data->sda_gpio, 1);
+ gpio_set_value_cansleep(data->scl_gpio, 1);
+
+
+exit_to_free:
+ gpio_free(data->sda_gpio);
+ gpio_free(data->scl_gpio);
+exit:
+ msm_gpiomux_write(data->sda_gpio, GPIOMUX_ACTIVE, &old_config[0], NULL);
+ msm_gpiomux_write(data->scl_gpio, GPIOMUX_ACTIVE, &old_config[1], NULL);
+
+ return ret;
+}
+#endif
+
+static int k2hh_i2c_read(struct k2hh_p *data,
+ unsigned char reg_addr, unsigned char *buf, unsigned int len)
+{
+ int ret, retries = 0;
+ struct i2c_msg msg[2];
+
+ msg[0].addr = data->client->addr;
+ msg[0].flags = I2C_M_WR;
+ msg[0].len = 1;
+ msg[0].buf = &reg_addr;
+
+ msg[1].addr = data->client->addr;
+ msg[1].flags = I2C_M_RD;
+ msg[1].len = len;
+ msg[1].buf = buf;
+
+ do {
+ ret = i2c_transfer(data->client->adapter, msg, 2);
+ if (ret >= 0)
+ break;
+ //else
+ // k2hh_i2c_recovery(data);
+ } while (retries++ < 2);
+
+ if (ret < 0) {
+ pr_err("[SENSOR]: %s - i2c read error %d\n", __func__, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int k2hh_i2c_write(struct k2hh_p *data,
+ unsigned char reg_addr, unsigned char buf)
+{
+ int ret, retries = 0;
+ struct i2c_msg msg;
+ unsigned char w_buf[2];
+
+ w_buf[0] = reg_addr;
+ w_buf[1] = buf;
+
+ msg.addr = data->client->addr;
+ msg.flags = I2C_M_WR;
+ msg.len = 2;
+ msg.buf = (char *)w_buf;
+
+ do {
+ ret = i2c_transfer(data->client->adapter, &msg, 1);
+ if (ret >= 0)
+ break;
+ //else
+ // k2hh_i2c_recovery(data);
+ } while (retries++ < 2);
+
+ if (ret < 0) {
+ pr_err("[SENSOR]: %s - i2c write error %d\n", __func__, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int k2hh_set_range(struct k2hh_p *data, unsigned char range)
+{
+ int ret = 0;
+ unsigned char temp, new_range, buf, mask;
+
+ switch (range) {
+ case K2HH_RANGE_2G:
+ new_range = K2HH_ACC_FS_2G;
+ break;
+ case K2HH_RANGE_4G:
+ new_range = K2HH_ACC_FS_4G;
+ break;
+ case K2HH_RANGE_8G:
+ new_range = K2HH_ACC_FS_8G;
+ break;
+ default:
+ new_range = K2HH_ACC_FS_2G;
+ break;
+ }
+
+ mask = K2HH_ACC_FS_MASK;
+ ret = k2hh_i2c_read(data, CTRL4_REG, &temp, 1);
+#ifndef OUTPUT_ALWAYS_ANTI_ALIASED
+ buf = (mask & new_range) | ((~mask) & temp);
+#else
+ buf = 0xCC;
+#endif
+ ret += k2hh_i2c_write(data, CTRL4_REG, buf);
+
+ return ret;
+}
+
+static int k2hh_set_odr(struct k2hh_p *data)
+{
+ int ret = 0, i;
+ unsigned char buf, new_odr, mask, temp;
+
+ /* 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(k2hh_acc_odr_table) - 1; i >= 0; i--) {
+ if ((k2hh_acc_odr_table[i].cutoff_ms <= \
+ ktime_to_ms(data->poll_delay)) || (i == 0))
+ break;
+ }
+
+ if (data->recog_flag == ON)
+ i = ARRAY_SIZE(k2hh_acc_odr_table) - 1;
+
+ new_odr = k2hh_acc_odr_table[i].mask;
+
+ mask = K2HH_ACC_ODR_MASK;
+ ret = k2hh_i2c_read(data, CTRL1_REG, &temp, 1);
+ buf = ((mask & new_odr) | ((~mask) & temp));
+ ret += k2hh_i2c_write(data, CTRL1_REG, buf);
+
+ pr_info("[SENSOR]: %s - change odr %d\n", __func__, i);
+ return ret;
+}
+
+static int k2hh_set_mode(struct k2hh_p *data, unsigned char mode)
+{
+ int ret = 0;
+ unsigned char buf, mask, temp;
+
+ mutex_lock(&data->mode_mutex);
+
+ switch (mode) {
+ case K2HH_MODE_NORMAL:
+ mask = K2HH_ACC_AXES_MASK;
+ ret = k2hh_i2c_read(data, CTRL1_REG, &temp, 1);
+ buf = ((mask & ACC_ENABLE_ALL_AXES) | ((~mask) & temp));
+ ret += k2hh_i2c_write(data, CTRL1_REG, buf);
+ k2hh_set_odr(data);
+ pr_info("[SENSOR]: %s - normal mode\n", __func__);
+ break;
+
+ case K2HH_MODE_SUSPEND:
+ if (data->recog_flag == ON)
+ break;
+
+ mask = K2HH_ACC_AXES_MASK | K2HH_ACC_ODR_MASK;
+ ret = k2hh_i2c_read(data, CTRL1_REG, &temp, 1);
+ buf = ((mask & ACC_PM_OFF) | ((~mask) & temp));
+ ret += k2hh_i2c_write(data, CTRL1_REG, buf);
+ pr_info("[SENSOR]: %s - suspend mode\n", __func__);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ mutex_unlock(&data->mode_mutex);
+ return ret;
+}
+
+static int k2hh_read_accel_xyz(struct k2hh_p *data, struct k2hh_v *acc)
+{
+ int ret = 0;
+ struct k2hh_v rawdata;
+ unsigned char buf[READ_DATA_LENTH];
+
+ ret += k2hh_i2c_read(data, AXISDATA_REG, buf, READ_DATA_LENTH);
+
+ if (ret < 0)
+ goto exit;
+
+ rawdata.v[0] = ((s16) ((buf[1] << 8) | buf[0]));
+ rawdata.v[1] = ((s16) ((buf[3] << 8) | buf[2]));
+ rawdata.v[2] = ((s16) ((buf[5] << 8) | buf[4]));
+
+ acc->v[0] = ((data->negate_x) ? (-rawdata.v[data->axis_map_x])
+ : (rawdata.v[data->axis_map_x]));
+ acc->v[1] = ((data->negate_y) ? (-rawdata.v[data->axis_map_y])
+ : (rawdata.v[data->axis_map_y]));
+ acc->v[2] = ((data->negate_z) ? (-rawdata.v[data->axis_map_z])
+ : (rawdata.v[data->axis_map_z]));
+
+exit:
+ return ret;
+}
+
+static enum hrtimer_restart k2hh_timer_func(struct hrtimer *timer)
+{
+ struct k2hh_p *data = container_of(timer,
+ struct k2hh_p, accel_timer);
+
+ if (!work_pending(&data->work))
+ queue_work(data->accel_wq, &data->work);
+
+ hrtimer_forward_now(&data->accel_timer, data->poll_delay);
+
+ return HRTIMER_RESTART;
+}
+
+static void k2hh_work_func(struct work_struct *work)
+{
+ int ret;
+ struct k2hh_v acc;
+ struct k2hh_p *data = container_of(work, struct k2hh_p, work);
+
+ ret = k2hh_read_accel_xyz(data, &acc);
+ if (ret < 0)
+ goto exit;
+
+ data->accdata.x = acc.x - data->caldata.x;
+ data->accdata.y = acc.y - data->caldata.y;
+ data->accdata.z = acc.z - data->caldata.z;
+
+ input_report_rel(data->input, REL_X, data->accdata.x);
+ input_report_rel(data->input, REL_Y, data->accdata.y);
+ input_report_rel(data->input, REL_Z, data->accdata.z);
+ input_sync(data->input);
+
+exit:
+ if ((ktime_to_ns(data->poll_delay) * (int64_t)data->time_count)
+ >= ((int64_t)ACCEL_LOG_TIME * NSEC_PER_SEC)) {
+ pr_info("[SENSOR]: %s - x = %d, y = %d, z = %d (ra:%d)\n",
+ __func__, data->accdata.x, data->accdata.y,
+ data->accdata.z, data->recog_flag);
+ data->time_count = 0;
+ } else
+ data->time_count++;
+}
+
+static void k2hh_set_enable(struct k2hh_p *data, int enable)
+{
+ if (enable == ON) {
+ hrtimer_start(&data->accel_timer, data->poll_delay,
+ HRTIMER_MODE_REL);
+ } else {
+ hrtimer_cancel(&data->accel_timer);
+ cancel_work_sync(&data->work);
+ }
+}
+
+static ssize_t k2hh_enable_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct k2hh_p *data = dev_get_drvdata(dev);
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", atomic_read(&data->enable));
+}
+
+static ssize_t k2hh_enable_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ u8 enable;
+ int ret, pre_enable;
+ struct k2hh_p *data = dev_get_drvdata(dev);
+
+ ret = kstrtou8(buf, 2, &enable);
+ if (ret) {
+ pr_err("[SENSOR]: %s - Invalid Argument\n", __func__);
+ return ret;
+ }
+
+ pr_info("[SENSOR]: %s - new_value = %u\n", __func__, enable);
+ pre_enable = atomic_read(&data->enable);
+
+ if (enable) {
+ if (pre_enable == OFF) {
+ k2hh_regulator_onoff(data, true);
+ k2hh_open_calibration(data);
+ k2hh_set_range(data, K2HH_RANGE_2G);
+
+ k2hh_set_mode(data, K2HH_MODE_NORMAL);
+ atomic_set(&data->enable, ON);
+ k2hh_set_enable(data, ON);
+ }
+ } else {
+ if (pre_enable == ON) {
+ atomic_set(&data->enable, OFF);
+ k2hh_set_mode(data, K2HH_MODE_SUSPEND);
+ k2hh_set_enable(data, OFF);
+ k2hh_regulator_onoff(data, false);
+ }
+ }
+
+ return size;
+}
+
+static ssize_t k2hh_delay_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct k2hh_p *data = dev_get_drvdata(dev);
+
+ return snprintf(buf, PAGE_SIZE, "%lld\n",
+ ktime_to_ns(data->poll_delay));
+}
+
+static ssize_t k2hh_delay_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ int ret;
+ int64_t delay;
+ struct k2hh_p *data = dev_get_drvdata(dev);
+
+ ret = kstrtoll(buf, 10, &delay);
+ if (ret) {
+ pr_err("[SENSOR]: %s - Invalid Argument\n", __func__);
+ return ret;
+ }
+
+ data->poll_delay = ns_to_ktime(delay);
+ k2hh_set_odr(data);
+
+ if (atomic_read(&data->enable) == ON) {
+ k2hh_set_mode(data, K2HH_MODE_SUSPEND);
+ k2hh_set_mode(data, K2HH_MODE_NORMAL);
+ }
+
+ pr_info("[SENSOR]: %s - poll_delay = %lld\n", __func__, delay);
+ return size;
+}
+
+static DEVICE_ATTR(poll_delay, S_IRUGO | S_IWUSR | S_IWGRP,
+ k2hh_delay_show, k2hh_delay_store);
+static DEVICE_ATTR(enable, S_IRUGO | S_IWUSR | S_IWGRP,
+ k2hh_enable_show, k2hh_enable_store);
+
+static struct attribute *k2hh_attributes[] = {
+ &dev_attr_poll_delay.attr,
+ &dev_attr_enable.attr,
+ NULL
+};
+
+static struct attribute_group k2hh_attribute_group = {
+ .attrs = k2hh_attributes
+};
+
+
+static ssize_t k2hh_vendor_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "%s\n", VENDOR_NAME);
+}
+
+static ssize_t k2hh_name_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "%s\n", MODEL_NAME);
+}
+
+static int k2hh_open_calibration(struct k2hh_p *data)
+{
+ int ret = 0;
+ mm_segment_t old_fs;
+ struct file *cal_filp = NULL;
+
+ old_fs = get_fs();
+ set_fs(KERNEL_DS);
+
+ cal_filp = filp_open(CALIBRATION_FILE_PATH, O_RDONLY, 0666);
+ if (IS_ERR(cal_filp)) {
+ set_fs(old_fs);
+ ret = PTR_ERR(cal_filp);
+
+ data->caldata.x = 0;
+ data->caldata.y = 0;
+ data->caldata.z = 0;
+
+ pr_info("[SENSOR]: %s - No Calibration\n", __func__);
+
+ return ret;
+ }
+
+ ret = cal_filp->f_op->read(cal_filp, (char *)&data->caldata.v,
+ 3 * sizeof(s16), &cal_filp->f_pos);
+ if (ret != 3 * sizeof(s16)) {
+ pr_err("[SENSOR] %s: - Can't read the cal data\n", __func__);
+ ret = -EIO;
+ }
+
+ filp_close(cal_filp, current->files);
+ set_fs(old_fs);
+
+ pr_info("[SENSOR]: open accel calibration %d, %d, %d\n",
+ data->caldata.x, data->caldata.y, data->caldata.z);
+
+ if ((data->caldata.x == 0) && (data->caldata.y == 0)
+ && (data->caldata.z == 0))
+ return -EIO;
+
+ return ret;
+}
+
+static int k2hh_do_calibrate(struct k2hh_p *data, int enable)
+{
+ int sum[3] = { 0, };
+ int ret = 0, cnt;
+ struct file *cal_filp = NULL;
+ struct k2hh_v acc;
+ mm_segment_t old_fs;
+
+ if (enable) {
+ data->caldata.x = 0;
+ data->caldata.y = 0;
+ data->caldata.z = 0;
+
+ if (atomic_read(&data->enable) == ON)
+ k2hh_set_enable(data, OFF);
+ else
+ k2hh_set_mode(data, K2HH_MODE_NORMAL);
+
+ msleep(300);
+
+ for (cnt = 0; cnt < CALIBRATION_DATA_AMOUNT; cnt++) {
+ k2hh_read_accel_xyz(data, &acc);
+ sum[0] += acc.x;
+ sum[1] += acc.y;
+ sum[2] += acc.z;
+ mdelay(10);
+ }
+
+ if (atomic_read(&data->enable) == ON)
+ k2hh_set_enable(data, ON);
+ else
+ k2hh_set_mode(data, K2HH_MODE_SUSPEND);
+
+ data->caldata.x = (sum[0] / CALIBRATION_DATA_AMOUNT);
+ data->caldata.y = (sum[1] / CALIBRATION_DATA_AMOUNT);
+ data->caldata.z = (sum[2] / CALIBRATION_DATA_AMOUNT);
+
+ if (data->caldata.z > 0)
+ data->caldata.z -= MAX_ACCEL_1G;
+ else if (data->caldata.z < 0)
+ data->caldata.z += MAX_ACCEL_1G;
+ } else {
+ data->caldata.x = 0;
+ data->caldata.y = 0;
+ data->caldata.z = 0;
+ }
+
+ pr_info("[SENSOR]: %s - do accel calibrate %d, %d, %d\n", __func__,
+ data->caldata.x, data->caldata.y, data->caldata.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("[SENSOR]: %s - Can't open calibration file\n",
+ __func__);
+ set_fs(old_fs);
+ ret = PTR_ERR(cal_filp);
+ return ret;
+ }
+
+ ret = cal_filp->f_op->write(cal_filp, (char *)&data->caldata.v,
+ 3 * sizeof(s16), &cal_filp->f_pos);
+ if (ret != 3 * sizeof(s16)) {
+ pr_err("[SENSOR]: %s - Can't write the caldata to file\n",
+ __func__);
+ ret = -EIO;
+ }
+
+ filp_close(cal_filp, current->files);
+ set_fs(old_fs);
+
+ return ret;
+}
+
+static ssize_t k2hh_calibration_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int ret;
+ struct k2hh_p *data = dev_get_drvdata(dev);
+
+ ret = k2hh_open_calibration(data);
+ if (ret < 0)
+ pr_err("[SENSOR]: %s - calibration open failed(%d)\n",
+ __func__, ret);
+
+ pr_info("[SENSOR]: %s - cal data %d %d %d - ret : %d\n", __func__,
+ data->caldata.x, data->caldata.y, data->caldata.z, ret);
+
+ return snprintf(buf, PAGE_SIZE, "%d %d %d %d\n", ret, data->caldata.x,
+ data->caldata.y, data->caldata.z);
+}
+
+static ssize_t k2hh_calibration_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ int ret;
+ int64_t dEnable;
+ struct k2hh_p *data = dev_get_drvdata(dev);
+
+ ret = kstrtoll(buf, 10, &dEnable);
+ if (ret < 0)
+ return ret;
+
+ ret = k2hh_do_calibrate(data, (int)dEnable);
+ if (ret < 0)
+ pr_err("[SENSOR]: %s - accel calibrate failed\n", __func__);
+
+ return size;
+}
+
+static ssize_t k2hh_raw_data_read(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct k2hh_v acc;
+ struct k2hh_p *data = dev_get_drvdata(dev);
+
+ if (atomic_read(&data->enable) == OFF) {
+ k2hh_set_mode(data, K2HH_MODE_NORMAL);
+ msleep(20);
+ k2hh_read_accel_xyz(data, &acc);
+ k2hh_set_mode(data, K2HH_MODE_SUSPEND);
+
+ acc.x = acc.x - data->caldata.x;
+ acc.y = acc.y - data->caldata.y;
+ acc.z = acc.z - data->caldata.z;
+ } else {
+ acc = data->accdata;
+ }
+
+ return snprintf(buf, PAGE_SIZE, "%d,%d,%d\n",
+ acc.x, acc.y, acc.z);
+}
+
+static ssize_t k2hh_reactive_alert_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ unsigned char threshx, threshy, threshz;
+ int enable = OFF, factory_mode = OFF;
+ struct k2hh_v acc;
+ struct k2hh_p *data = dev_get_drvdata(dev);
+
+ if (sysfs_streq(buf, "0")) {
+ enable = OFF;
+ factory_mode = OFF;
+ pr_info("[SENSOR]: %s - disable\n", __func__);
+ } else if (sysfs_streq(buf, "1")) {
+ enable = ON;
+ factory_mode = OFF;
+ pr_info("[SENSOR]: %s - enable\n", __func__);
+ } else if (sysfs_streq(buf, "2")) {
+ enable = ON;
+ factory_mode = ON;
+ pr_info("[SENSOR]: %s - factory mode\n", __func__);
+ } else {
+ pr_err("[SENSOR]: %s - invalid value %d\n", __func__, *buf);
+ return -EINVAL;
+ }
+
+ if ((enable == ON) && (data->recog_flag == OFF)) {
+ data->irq_state = 0;
+ data->recog_flag = ON;
+
+ if (factory_mode == ON) {
+ k2hh_i2c_write(data, INT_THSX1_REG, 0x00);
+ k2hh_i2c_write(data, INT_THSY1_REG, 0x00);
+ k2hh_i2c_write(data, INT_THSZ1_REG, 0x00);
+ k2hh_i2c_write(data, INT_CFG1_REG, 0x3f);
+ } else {
+ k2hh_set_odr(data);
+ if (atomic_read(&data->enable) == OFF) {
+ k2hh_set_mode(data, K2HH_MODE_NORMAL);
+ msleep(20);
+ k2hh_read_accel_xyz(data, &acc);
+ k2hh_set_mode(data, K2HH_MODE_SUSPEND);
+ } else {
+ acc.x = data->accdata.x;
+ acc.y = data->accdata.y;
+ acc.z = data->accdata.z;
+ }
+
+ threshx = (abs(acc.v[data->axis_map_x]) \
+ + DYNAMIC_THRESHOLD) >> 8;
+ threshy = (abs(acc.v[data->axis_map_y]) \
+ + DYNAMIC_THRESHOLD) >> 8;
+ threshz = (abs(acc.v[data->axis_map_z]) \
+ + DYNAMIC_THRESHOLD) >> 8;
+
+ k2hh_i2c_write(data, INT_THSX1_REG, threshx);
+ k2hh_i2c_write(data, INT_THSY1_REG, threshy);
+ k2hh_i2c_write(data, INT_THSZ1_REG, threshz);
+ k2hh_i2c_write(data, INT_CFG1_REG, 0x0a);
+ }
+
+ k2hh_i2c_write(data, CTRL7_REG, CTRL7_LIR1);
+ k2hh_i2c_write(data, CTRL3_REG, CTRL3_IG1_INT1);
+
+ enable_irq(data->irq1);
+ enable_irq_wake(data->irq1);
+
+ pr_info("[SENSOR]: %s - reactive alert is on!\n", __func__);
+ } else if ((enable == OFF) && (data->recog_flag == ON)) {
+ k2hh_i2c_write(data, CTRL3_REG, 0x00);
+
+ disable_irq_wake(data->irq1);
+ disable_irq_nosync(data->irq1);
+ data->recog_flag = OFF;
+ pr_info("[SENSOR]: %s - reactive alert is off! irq = %d\n",
+ __func__, data->irq_state);
+ }
+
+ return size;
+}
+
+static ssize_t k2hh_reactive_alert_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct k2hh_p *data = dev_get_drvdata(dev);
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", data->irq_state);
+}
+
+static ssize_t k2hh_selftest_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct k2hh_p *data = dev_get_drvdata(dev);
+ struct k2hh_v acc;
+ unsigned char temp;
+ int result = 1, i;
+ ssize_t ret;
+ s32 NO_ST[3] = {0, 0, 0};
+ s32 ST[3] = {0, 0, 0};
+
+ if (atomic_read(&data->enable) == OFF)
+ k2hh_set_mode(data, K2HH_MODE_NORMAL);
+ else
+ k2hh_set_enable(data, OFF);
+
+ k2hh_i2c_write(data, CTRL1_REG, 0x3f);
+ k2hh_i2c_write(data, CTRL4_REG, 0x04);
+ k2hh_i2c_write(data, CTRL5_REG, 0x00);
+ k2hh_i2c_write(data, CTRL6_REG, 0x00);
+
+ mdelay(80);
+
+ k2hh_read_accel_xyz(data, &acc);
+
+ for (i = 0; i < 5; i++) {
+ while (1) {
+ if (k2hh_i2c_read(data, STATUS_REG, &temp, 1) < 0) {
+ pr_err("[SENSOR] %s: i2c error", __func__);
+ goto exit_status_err;
+ }
+
+ if (temp & 0x08)
+ break;
+ }
+
+ k2hh_read_accel_xyz(data, &acc);
+ NO_ST[0] += acc.x;
+ NO_ST[1] += acc.y;
+ NO_ST[2] += acc.z;
+ }
+ NO_ST[0] /= 5;
+ NO_ST[1] /= 5;
+ NO_ST[2] /= 5;
+
+ k2hh_i2c_write(data, CTRL5_REG, 0x04);
+
+ mdelay(80);
+
+ k2hh_read_accel_xyz(data, &acc);
+
+ for (i = 0; i < 5; i++) {
+ while (1) {
+ if (k2hh_i2c_read(data, STATUS_REG, &temp, 1) < 0) {
+ pr_err("[SENSOR] %s: i2c error", __func__);
+ goto exit_status_err;
+ }
+
+ if (temp & 0x08)
+ break;
+ }
+
+ k2hh_read_accel_xyz(data, &acc);
+ ST[0] += acc.x;
+ ST[1] += acc.y;
+ ST[2] += acc.z;
+ }
+
+ ST[0] /= 5;
+ ST[1] /= 5;
+ ST[2] /= 5;
+
+ for (i = 0; i < 3; i++) {
+ ST[i] -= NO_ST[i];
+ ST[i] = abs(ST[i]);
+
+ if ((SELF_TEST_2G_MIN_LSB > ST[i]) \
+ || (ST[i] > SELF_TEST_2G_MAX_LSB)) {
+ pr_info("[SENSOR] %s: %d Out of range!! (%d)\n",
+ __func__, i, ST[i]);
+ result = 0;
+ }
+ }
+
+ if (result)
+ ret = sprintf(buf, "1,%d,%d,%d\n", ST[0], ST[1], ST[2]);
+ else
+ ret = sprintf(buf, "0,%d,%d,%d\n", ST[0], ST[1], ST[2]);
+
+ goto exit;
+
+exit_status_err:
+ ret = sprintf(buf, "-1,0,0,0\n");
+exit:
+ k2hh_i2c_write(data, CTRL1_REG, 0x00);
+ k2hh_i2c_write(data, CTRL5_REG, 0x00);
+
+ if (atomic_read(&data->enable) == OFF) {
+ k2hh_set_mode(data, K2HH_MODE_SUSPEND);
+ } else {
+ k2hh_set_mode(data, K2HH_MODE_NORMAL);
+ k2hh_set_enable(data, ON);
+ }
+
+ return ret;
+}
+
+static DEVICE_ATTR(selftest, S_IRUGO, k2hh_selftest_show, NULL);
+static DEVICE_ATTR(name, S_IRUGO, k2hh_name_show, NULL);
+static DEVICE_ATTR(vendor, S_IRUGO, k2hh_vendor_show, NULL);
+static DEVICE_ATTR(calibration, S_IRUGO | S_IWUSR | S_IWGRP,
+ k2hh_calibration_show, k2hh_calibration_store);
+static DEVICE_ATTR(raw_data, S_IRUGO, k2hh_raw_data_read, NULL);
+static DEVICE_ATTR(reactive_alert, S_IRUGO | S_IWUSR | S_IWGRP,
+ k2hh_reactive_alert_show, k2hh_reactive_alert_store);
+
+static struct device_attribute *sensor_attrs[] = {
+ &dev_attr_name,
+ &dev_attr_vendor,
+ &dev_attr_calibration,
+ &dev_attr_raw_data,
+ &dev_attr_reactive_alert,
+ &dev_attr_selftest,
+ NULL,
+};
+
+static void k2hh_irq_work_func(struct work_struct *work)
+{
+ struct k2hh_p *data = container_of((struct delayed_work *)work,
+ struct k2hh_p, irq_work);
+ unsigned char buf;
+
+ k2hh_i2c_write(data, INT_CFG1_REG, 0x00);
+ k2hh_i2c_read(data, INT_SRC1_REG, &buf, 1);
+}
+
+static irqreturn_t k2hh_irq_thread(int irq, void *k2hh_data_p)
+{
+ struct k2hh_p *data = k2hh_data_p;
+
+ data->irq_state = 1;
+ wake_lock_timeout(&data->reactive_wake_lock,
+ msecs_to_jiffies(2000));
+ schedule_delayed_work(&data->irq_work, msecs_to_jiffies(100));
+ pr_info("###### [SENSOR]: %s reactive irq ######\n", __func__);
+
+ return IRQ_HANDLED;
+}
+
+static int k2hh_setup_pin(struct k2hh_p *data)
+{
+ int ret;
+
+ ret = gpio_request(data->acc_int1, "ACC_INT1");
+ if (ret < 0) {
+ pr_err("[SENSOR] %s - gpio %d request failed (%d)\n",
+ __func__, data->acc_int1, ret);
+ goto exit;
+ }
+
+ ret = gpio_direction_input(data->acc_int1);
+ if (ret < 0) {
+ pr_err("[SENSOR]: %s - failed to set gpio %d as input (%d)\n",
+ __func__, data->acc_int1, ret);
+ goto exit_acc_int1;
+ }
+
+ wake_lock_init(&data->reactive_wake_lock, WAKE_LOCK_SUSPEND,
+ "reactive_wake_lock");
+
+ data->irq1 = gpio_to_irq(data->acc_int1);
+ ret = request_threaded_irq(data->irq1, NULL, k2hh_irq_thread,
+ IRQF_TRIGGER_RISING | IRQF_ONESHOT, "k2hh_accel", data);
+ if (ret < 0) {
+ pr_err("[SENSOR]: %s - can't allocate irq.\n", __func__);
+ goto exit_reactive_irq;
+ }
+
+ disable_irq(data->irq1);
+ goto exit;
+
+exit_reactive_irq:
+ wake_lock_destroy(&data->reactive_wake_lock);
+exit_acc_int1:
+ gpio_free(data->acc_int1);
+exit:
+ return ret;
+}
+
+static int k2hh_input_init(struct k2hh_p *data)
+{
+ int ret = 0;
+ struct input_dev *dev;
+
+ dev = input_allocate_device();
+ if (!dev)
+ return -ENOMEM;
+
+ dev->name = MODULE_NAME;
+ dev->id.bustype = BUS_I2C;
+
+ input_set_capability(dev, EV_REL, REL_X);
+ input_set_capability(dev, EV_REL, REL_Y);
+ input_set_capability(dev, EV_REL, REL_Z);
+ input_set_drvdata(dev, data);
+
+ ret = input_register_device(dev);
+ if (ret < 0) {
+ input_free_device(dev);
+ return ret;
+ }
+ /* sysfs node creation */
+ ret = sysfs_create_group(&dev->dev.kobj, &k2hh_attribute_group);
+ if (ret < 0) {
+ input_unregister_device(dev);
+ return ret;
+ }
+
+ data->input = dev;
+ return 0;
+}
+
+static int k2hh_parse_dt(struct k2hh_p *data, struct device *dev)
+{
+ struct device_node *dNode = dev->of_node;
+ enum of_gpio_flags flags;
+ int ret;
+ u32 temp;
+
+ if (dNode == NULL)
+ return -ENODEV;
+
+ data->acc_int1 = of_get_named_gpio_flags(dNode,
+ "stm,irq_gpio", 0, &flags);
+ if (data->acc_int1 < 0) {
+ pr_err("[SENSOR]: %s - get acc_int1 error\n", __func__);
+ return -ENODEV;
+ }
+
+ data->sda_gpio = of_get_named_gpio_flags(dNode,
+ "stm,sda", 0, &flags);
+ if (data->sda_gpio < 0)
+ pr_info("[SENSOR]: %s - no sda_gpio\n", __func__);
+
+ data->scl_gpio = of_get_named_gpio_flags(dNode,
+ "stm,scl", 0, &flags);
+ if (data->scl_gpio < 0)
+ pr_info("[SENSOR]: %s - no scl_gpio\n", __func__);
+
+ ret = of_property_read_u32(dNode,"stm,axis_map_x", &temp);
+ if ((data->axis_map_x > 2) || (ret < 0)) {
+ pr_err("%s: invalid x axis_map value %u\n",
+ __func__, data->axis_map_x);
+ data->axis_map_x = 0;
+ } else {
+ data->axis_map_x= (u8)temp;
+ }
+
+ ret = of_property_read_u32(dNode,"stm,axis_map_y", &temp);
+ if ((data->axis_map_y > 2) || (ret < 0)) {
+ pr_err("%s: invalid y axis_map value %u\n",
+ __func__, data->axis_map_y);
+ data->axis_map_y = 1;
+ } else {
+ data->axis_map_y= (u8)temp;
+ }
+
+ ret = of_property_read_u32(dNode,"stm,axis_map_z", &temp);
+ if ((data->axis_map_z > 2) || (ret < 0)) {
+ pr_err("%s: invalid z axis_map value %u\n",
+ __func__, data->axis_map_z);
+ data->axis_map_z = 2;
+ } else {
+ data->axis_map_z= (u8)temp;
+ }
+
+ ret = of_property_read_u32(dNode,"stm,negate_x", &temp);
+ if ((data->negate_x > 1) || (ret < 0)) {
+ pr_err("%s: invalid x axis_map value %u\n",
+ __func__, data->negate_x);
+ data->negate_x = 0;
+ } else {
+ data->negate_x= (u8)temp;
+ }
+
+ ret = of_property_read_u32(dNode,"stm,negate_y", &temp);
+ if ((data->negate_y > 1) || (ret < 0)) {
+ pr_err("%s: invalid y axis_map value %u\n",
+ __func__, data->negate_y);
+ data->negate_y = 0;
+ } else {
+ data->negate_y= (u8)temp;
+ }
+
+ ret = of_property_read_u32(dNode,"stm,negate_z", &temp);
+ if ((data->negate_z > 1) || (ret < 0)) {
+ pr_err("%s: invalid z axis_map value %u\n",
+ __func__, data->negate_z);
+ data->negate_z = 0;
+ } else {
+ data->negate_z= (u8)temp;
+ }
+
+ return 0;
+}
+
+static int k2hh_regulator_onoff(struct k2hh_p *data, bool onoff)
+{
+#if 0
+ int ret = -1;
+
+ if (!data->vdd) {
+ data->vdd = regulator_get(&data->client->dev, "stm,reg_vdd");
+ if (!data->vdd) {
+ pr_err("%s: regulator pointer null vdd, rc=%d\n",
+ __func__, ret);
+ return ret;
+ }
+ ret = regulator_set_voltage(data->vdd, 3000000, 3000000);
+ if (ret) {
+ pr_err("%s: set voltage failed on vdd, rc=%d\n",
+ __func__, ret);
+ return ret;
+ }
+ }
+ /*if (!data->vio) {
+ data->vio = regulator_get(&data->client->dev, "stm,reg_vio");
+ if(!data->vio){
+ pr_err("%s: regulator_get for vio failed\n",
+ __func__);
+ return 0;
+ }
+ }*/
+ if(onoff){
+ ret = regulator_enable(data->vdd);
+ if (ret) {
+ pr_err("%s: Failed to enable regulator vdd.\n",
+ __func__);
+ return ret;
+ }
+ /*ret = regulator_enable(data->vio);
+ if (ret) {
+ pr_err("%s: Failed to enable regulator vio.\n",
+ __func__);
+ return ret;
+ }
+ msleep(30);*/
+ }
+ else {
+ ret = regulator_disable(data->vdd);
+ if (ret) {
+ pr_err("%s: Failed to disable regulator vdd.\n",
+ __func__);
+ return ret;
+ }
+ /*ret = regulator_disable(data->vio);
+ if (ret) {
+ pr_err("%s: Failed to disable regulator vio.\n",
+ __func__);
+ return ret;
+ }*/
+ }
+#endif
+ return 0;
+}
+
+
+static int k2hh_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ u8 temp;
+ int ret = -ENODEV, i;
+ struct k2hh_p *data = NULL;
+
+ pr_err("[SENSOR]: %s - Probe Start!\n", __func__);
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ pr_err("[SENSOR]: %s - i2c_check_functionality error\n",
+ __func__);
+ goto exit;
+ }
+
+ data = kzalloc(sizeof(struct k2hh_p), GFP_KERNEL);
+ if (data == NULL) {
+ pr_err("[SENSOR]: %s - kzalloc error\n", __func__);
+ ret = -ENOMEM;
+ goto exit_kzalloc;
+ }
+
+ i2c_set_clientdata(client, data);
+ data->client = client;
+
+ ret = k2hh_parse_dt(data, &client->dev);
+ if (ret < 0) {
+ pr_err("[SENSOR]: %s - of_node error\n", __func__);
+ ret = -ENODEV;
+ goto exit_of_node;
+ }
+
+ ret = k2hh_regulator_onoff(data, true);
+ if (ret < 0)
+ pr_err("[SENSOR]: %s - No regulator\n", __func__);
+
+ ret = k2hh_setup_pin(data);
+ if (ret < 0) {
+ pr_err("[SENSOR]: %s - could not setup pin\n", __func__);
+ goto exit_setup_pin;
+ }
+
+ mutex_init(&data->mode_mutex);
+
+ /* read chip id */
+ k2hh_set_mode(data, K2HH_MODE_NORMAL);
+ for (i = 0; i < CHIP_ID_RETRIES; i++) {
+ ret = k2hh_i2c_read(data, WHOAMI_REG, &temp, 1);
+ if (temp != K2HH_CHIP_ID) {
+ pr_err("[SENSOR]: %s - chip id failed 0x%x : %d\n",
+ __func__, temp, ret);
+ } else {
+ pr_info("[SENSOR]: %s - chip id success 0x%x\n",
+ __func__, temp);
+ break;
+ }
+ msleep(20);
+ }
+
+ if (i >= CHIP_ID_RETRIES) {
+ ret = -ENODEV;
+ goto exit_read_chipid;
+ }
+
+ /* input device init */
+ ret = k2hh_input_init(data);
+ if (ret < 0)
+ goto exit_input_init;
+
+ sensors_register(data->factory_device, data, sensor_attrs, MODULE_NAME);
+
+ /* accel_timer settings. we poll for light values using a timer. */
+ hrtimer_init(&data->accel_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ data->poll_delay = ns_to_ktime(K2HH_DEFAULT_DELAY);
+ data->accel_timer.function = k2hh_timer_func;
+
+ /* the timer just fires off a work queue request. we need a thread
+ to read the i2c (can be slow and blocking). */
+ data->accel_wq = create_singlethread_workqueue("accel_wq");
+ if (!data->accel_wq) {
+ ret = -ENOMEM;
+ pr_err("[SENSOR]: %s - could not create workqueue\n", __func__);
+ goto exit_create_workqueue;
+ }
+
+ /* this is the thread function we run on the work queue */
+ INIT_WORK(&data->work, k2hh_work_func);
+ INIT_DELAYED_WORK(&data->irq_work, k2hh_irq_work_func);
+
+ atomic_set(&data->enable, OFF);
+ data->time_count = 0;
+ data->irq_state = 0;
+ data->recog_flag = OFF;
+
+ k2hh_set_range(data, K2HH_RANGE_2G);
+ k2hh_set_mode(data, K2HH_MODE_SUSPEND);
+
+ /*power off the regulators, for next enable power will be on*/
+ ret = k2hh_regulator_onoff(data, false);
+
+ pr_err("[SENSOR]: %s - Probe done!\n", __func__);
+
+ return 0;
+
+exit_create_workqueue:
+ sensors_unregister(data->factory_device, sensor_attrs);
+ sysfs_remove_group(&data->input->dev.kobj, &k2hh_attribute_group);
+ input_unregister_device(data->input);
+exit_input_init:
+exit_read_chipid:
+ mutex_destroy(&data->mode_mutex);
+ free_irq(data->irq1, data);
+ wake_lock_destroy(&data->reactive_wake_lock);
+ gpio_free(data->acc_int1);
+exit_setup_pin:
+exit_of_node:
+ kfree(data);
+exit_kzalloc:
+exit:
+ pr_err("[SENSOR]: %s - Probe fail!\n", __func__);
+ return ret;
+}
+
+static void k2hh_shutdown(struct i2c_client *client)
+{
+ struct k2hh_p *data = (struct k2hh_p *)i2c_get_clientdata(client);
+
+ pr_info("[SENSOR]: %s\n", __func__);
+
+ if (atomic_read(&data->enable) == ON)
+ k2hh_set_enable(data, OFF);
+
+ atomic_set(&data->enable, OFF);
+ k2hh_set_mode(data, K2HH_MODE_SUSPEND);
+}
+
+/*
+static int k2hh_remove(struct i2c_client *client)
+{
+ struct k2hh_p *data = (struct k2hh_p *)i2c_get_clientdata(client);
+
+ if (atomic_read(&data->enable) == ON)
+ k2hh_set_enable(data, OFF);
+
+ atomic_set(&data->enable, OFF);
+ cancel_delayed_work_sync(&data->irq_work);
+
+ k2hh_set_mode(data, K2HH_MODE_SUSPEND);
+ sensors_unregister(data->factory_device, sensor_attrs);
+
+ sysfs_remove_group(&data->input->dev.kobj, &k2hh_attribute_group);
+ input_unregister_device(data->input);
+
+ free_irq(data->irq1, data);
+ wake_lock_destroy(&data->reactive_wake_lock);
+ mutex_destroy(&data->mode_mutex);
+ gpio_free(data->acc_int1);
+ kfree(data);
+
+ return 0;
+}
+*/
+
+static int k2hh_suspend(struct device *dev)
+{
+ struct k2hh_p *data = dev_get_drvdata(dev);
+
+ pr_info("[SENSOR]: %s\n", __func__);
+
+ if (atomic_read(&data->enable) == ON) {
+ k2hh_set_mode(data, K2HH_MODE_SUSPEND);
+ k2hh_set_enable(data, OFF);
+ }
+
+ return 0;
+}
+
+static int k2hh_resume(struct device *dev)
+{
+ struct k2hh_p *data = dev_get_drvdata(dev);
+
+ pr_info("[SENSOR]: %s\n", __func__);
+
+ if (atomic_read(&data->enable) == ON) {
+ k2hh_set_mode(data, K2HH_MODE_NORMAL);
+ k2hh_set_enable(data, ON);
+ }
+
+ return 0;
+}
+
+static struct of_device_id k2hh_match_table[] = {
+ { .compatible = "stm,k2hh",},
+ {},
+};
+
+static const struct i2c_device_id k2hh_id[] = {
+ { "k2hh_match_table", 0 },
+ { }
+};
+
+static const struct dev_pm_ops k2hh_pm_ops = {
+ .suspend = k2hh_suspend,
+ .resume = k2hh_resume
+};
+
+static struct i2c_driver k2hh_driver = {
+ .driver = {
+ .name = MODEL_NAME,
+ .owner = THIS_MODULE,
+ .of_match_table = k2hh_match_table,
+ .pm = &k2hh_pm_ops
+ },
+ .probe = k2hh_probe,
+ .shutdown = k2hh_shutdown,
+/* .remove = k2hh_remove, */
+ .id_table = k2hh_id,
+};
+
+static int __init k2hh_init(void)
+{
+ return i2c_add_driver(&k2hh_driver);
+}
+
+static void __exit k2hh_exit(void)
+{
+ i2c_del_driver(&k2hh_driver);
+}
+
+module_init(k2hh_init);
+module_exit(k2hh_exit);
+
+MODULE_DESCRIPTION("k2hh accelerometer sensor driver");
+MODULE_AUTHOR("Samsung Electronics");
+MODULE_LICENSE("GPL");
+