| /* |
| * 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, see <http://www.gnu.org/licenses/>. |
| */ |
| #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/sensor/sensors_core.h> |
| |
| #define I2C_M_WR 0 /* for i2c Write */ |
| #define I2c_M_RD 1 /* for i2c Read */ |
| #define READ_DATA_LENGTH 6 |
| |
| #define VENDOR_NAME "STM" |
| #define MODEL_NAME "K2HH" |
| #define MODULE_NAME "accelerometer_sensor" |
| |
| #define CALIBRATION_FILE_PATH "/efs/FactoryApp/accel_calibration_data" |
| #define CALIBRATION_DATA_AMOUNT 20 |
| #define MAX_ACCEL_1G 8192 |
| |
| #define K2HH_DEFAULT_DELAY 200000000LL |
| #define K2HH_MIN_DELAY 5000000LL |
| |
| #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_DFC_MASK 0x60 |
| #define CTRL2_DFC_50 0x00 |
| #define CTRL2_DFC_100 0x20 |
| #define CTRL2_DFC_9 0x40 |
| #define CTRL2_DFC_400 0x60 |
| |
| /* 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_BW_MASK 0xC0 |
| #define K2HH_ACC_AXES_MASK 0x07 |
| #define K2HH_ACC_BW_SCALE_ODR_MASK 0x08 |
| |
| #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 K2HH_ACC_BW_50 0xC0 |
| #define K2HH_ACC_BW_100 0x80 |
| #define K2HH_ACC_BW_200 0x40 |
| #define K2HH_ACC_BW_400 0x00 |
| |
| #define INT_THSX1_REG 0x32 |
| #define INT_THSY1_REG 0x33 |
| #define INT_THSZ1_REG 0x34 |
| |
| #define K2HH_ACC_BW_SCALE_ODR_ENABLE 0x08 |
| #define K2HH_ACC_BW_SCALE_ODR_DISABLE 0x00 |
| |
| #define DYNAMIC_THRESHOLD 5000 |
| |
| #define ENABLE_LPF_CUT_OFF_FREQ 1 |
| #define ENABLE_LOG_ACCEL_MAX_OUT 1 |
| #if defined(ENABLE_LOG_ACCEL_MAX_OUT) |
| #define ACCEL_MAX_OUTPUT 32760 |
| #endif |
| |
| #if defined(CONFIG_SAMSUNG_LPM_MODE) |
| extern int poweroff_charging; |
| #endif |
| |
| 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_accel; |
| struct regulator *reg_vio; |
| #ifdef CONFIG_SENSORS_K2HH_VDD |
| struct regulator *reg_vdd; |
| #endif |
| ktime_t poll_delay; |
| atomic_t enable; |
| |
| int recog_flag; |
| int irq1; |
| int irq_state; |
| int acc_int1; |
| int time_count; |
| |
| u8 odr; |
| u8 hr; |
| |
| u8 axis_map_x; |
| u8 axis_map_y; |
| u8 axis_map_z; |
| |
| u8 negate_x; |
| u8 negate_y; |
| u8 negate_z; |
| |
| u64 old_timestamp; |
| }; |
| |
| #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 /* Anti aliasing filter */ |
| |
| 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_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 = ®_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; |
| } while (retries++ < 2); |
| |
| if (ret < 0) { |
| SENSOR_ERR("i2c read error %d\n", 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; |
| } while (retries++ < 2); |
| |
| if (ret < 0) { |
| SENSOR_ERR("i2c write error %d\n", ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| 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_LENGTH]; |
| |
| ret = k2hh_i2c_read(data, AXISDATA_REG, buf, READ_DATA_LENGTH); |
| 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 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); |
| |
| buf = (mask & new_range) | ((~mask) & temp); |
| ret += k2hh_i2c_write(data, CTRL4_REG, buf); |
| SENSOR_INFO("0x%x\n", new_range); |
| |
| return ret; |
| } |
| |
| static int k2hh_set_odr(struct k2hh_p *data) |
| { |
| int ret = 0, i; |
| unsigned char buf, new_odr, mask, temp; |
| |
| 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); |
| |
| data->odr = new_odr; |
| |
| SENSOR_INFO("change odr %d\n", i); |
| |
| #if defined(ENABLE_LPF_CUT_OFF_FREQ) |
| /* To increase LPF cut-off frequency, ODR/DFC */ |
| k2hh_i2c_read(data, CTRL2_REG, &buf, 1); |
| |
| buf = (CTRL2_DFC_MASK & CTRL2_DFC_9) | ((~CTRL2_DFC_MASK) & buf); |
| k2hh_i2c_write(data, CTRL2_REG, buf); |
| SENSOR_INFO("ctrl2:%x\n", buf); |
| #endif |
| |
| return ret; |
| } |
| |
| static int k2hh_set_bw(struct k2hh_p *data) |
| { |
| int ret = 0; |
| unsigned char temp, buf, mask, new_range; |
| |
| #if defined(OUTPUT_ALWAYS_ANTI_ALIASED) |
| new_range = K2HH_ACC_BW_SCALE_ODR_ENABLE; |
| mask = K2HH_ACC_BW_SCALE_ODR_MASK; |
| ret = k2hh_i2c_read(data, CTRL4_REG, &temp, 1); |
| |
| buf = (mask & new_range) | ((~mask) & temp); |
| ret += k2hh_i2c_write(data, CTRL4_REG, buf); |
| new_range = K2HH_ACC_BW_50; |
| #else |
| new_range = K2HH_ACC_BW_400; |
| #endif |
| mask = K2HH_ACC_BW_MASK; |
| ret = k2hh_i2c_read(data, CTRL4_REG, &temp, 1); |
| |
| buf = (mask & new_range) | ((~mask) & temp); |
| ret += k2hh_i2c_write(data, CTRL4_REG, buf); |
| |
| return ret; |
| } |
| |
| static int k2hh_set_hr(struct k2hh_p *data, int set) |
| { |
| int ret; |
| u8 buf, bw, odr; |
| |
| SENSOR_INFO("%d\n", set); |
| |
| if (set) { |
| data->hr = CTRL1_HR_ENABLE; |
| odr = data->odr; |
| k2hh_set_bw(data); |
| } else { |
| data->hr = CTRL1_HR_DISABLE; |
| odr = ACC_ODR800; |
| bw = K2HH_ACC_BW_400; |
| k2hh_i2c_read(data, CTRL4_REG, &buf, 1); |
| buf = (K2HH_ACC_BW_MASK & bw) | ((~K2HH_ACC_BW_MASK) & buf); |
| k2hh_i2c_write(data, CTRL4_REG, buf); |
| |
| #if defined(OUTPUT_ALWAYS_ANTI_ALIASED) |
| bw = K2HH_ACC_BW_SCALE_ODR_DISABLE; |
| k2hh_i2c_read(data, CTRL4_REG, &buf, 1); |
| buf = (K2HH_ACC_BW_SCALE_ODR_MASK & bw) | |
| ((~K2HH_ACC_BW_SCALE_ODR_MASK) & buf); |
| k2hh_i2c_write(data, CTRL4_REG, buf); |
| #endif |
| } |
| |
| ret = k2hh_i2c_read(data, CTRL1_REG, &buf, 1); |
| buf = data->hr | ((~CTRL1_HR_MASK) & buf); |
| ret += k2hh_i2c_write(data, CTRL1_REG, buf); |
| ret += k2hh_i2c_read(data, CTRL1_REG, &buf, 1); |
| buf = ((K2HH_ACC_ODR_MASK & odr) | ((~K2HH_ACC_ODR_MASK) & buf)); |
| ret += k2hh_i2c_write(data, CTRL1_REG, buf); |
| |
| return ret; |
| } |
| |
| 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_accel); |
| } |
| } |
| |
| 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_ODR_MASK; |
| ret = k2hh_i2c_read(data, CTRL1_REG, &temp, 1); |
| buf = ((mask & data->odr) | ((~mask) & temp)); |
| buf = data->hr | ((~CTRL1_HR_MASK) & buf); |
| buf = CTRL1_BDU_ENABLE | ((~CTRL1_BDU_MASK) & buf); |
| ret += k2hh_i2c_write(data, CTRL1_REG, buf); |
| break; |
| case K2HH_MODE_SUSPEND: |
| if (data->recog_flag == ON) |
| break; |
| |
| 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); |
| break; |
| default: |
| ret = -EINVAL; |
| break; |
| } |
| mutex_unlock(&data->mode_mutex); |
| SENSOR_INFO("change mode %u\n", mode); |
| |
| return ret; |
| } |
| |
| 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, 0); |
| 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; |
| |
| SENSOR_INFO("No Calibration\n"); |
| |
| 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)) { |
| SENSOR_ERR("can't read the cal data\n"); |
| ret = -EIO; |
| } |
| |
| filp_close(cal_filp, current->files); |
| set_fs(old_fs); |
| |
| SENSOR_INFO("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; |
| |
| data->caldata.x = 0; |
| data->caldata.y = 0; |
| data->caldata.z = 0; |
| |
| if (enable) { |
| 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; |
| msleep(20); |
| } |
| |
| 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; |
| } |
| |
| SENSOR_INFO("do accel calibrate %d, %d, %d\n", |
| 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, 0660); |
| if (IS_ERR(cal_filp)) { |
| SENSOR_ERR("can't open calibration file\n"); |
| 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)) { |
| SENSOR_ERR("can't write the caldata to file\n"); |
| ret = -EIO; |
| } |
| |
| filp_close(cal_filp, current->files); |
| set_fs(old_fs); |
| |
| 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_accel)) |
| queue_work(data->accel_wq, &data->work_accel); |
| |
| hrtimer_forward_now(&data->accel_timer, data->poll_delay); |
| |
| return HRTIMER_RESTART; |
| } |
| |
| static void k2hh_work_func(struct work_struct *work) |
| { |
| struct k2hh_v acc; |
| struct k2hh_p *data = container_of(work, struct k2hh_p, work_accel); |
| struct timespec ts; |
| u64 timestamp_new; |
| u64 delay = ktime_to_ns(data->poll_delay); |
| int time_hi, time_lo; |
| int ret; |
| |
| ret = k2hh_read_accel_xyz(data, &acc); |
| if (ret < 0) |
| goto exit; |
| |
| #if defined(ENABLE_LOG_ACCEL_MAX_OUT) |
| /* For debugging if happened exceptional situation */ |
| if (acc.x > ACCEL_MAX_OUTPUT || |
| acc.y > ACCEL_MAX_OUTPUT || |
| acc.z > ACCEL_MAX_OUTPUT) { |
| unsigned char buf[4], status; |
| |
| k2hh_i2c_read(data, CTRL1_REG, buf, 4); |
| k2hh_i2c_read(data, STATUS_REG, &status, 1); |
| |
| SENSOR_INFO("MAX_OUTPUT x = %d, y = %d, z = %d\n", |
| acc.x, acc.y, acc.z); |
| SENSOR_INFO("CTRL(20h~23h) : %X, %X, %X, %X - STATUS(27h) : %X\n", |
| buf[0], buf[1], buf[2], buf[3], status); |
| } |
| #endif |
| |
| ts = ktime_to_timespec(ktime_get_boottime()); |
| timestamp_new = ts.tv_sec * 1000000000ULL + ts.tv_nsec; |
| |
| data->accdata.x = acc.x - data->caldata.x; |
| data->accdata.y = acc.y - data->caldata.y; |
| data->accdata.z = acc.z - data->caldata.z; |
| |
| if (((timestamp_new - data->old_timestamp) * 10 > delay * 18) |
| && (data->old_timestamp != 0)) { |
| u64 shift_timestamp = delay >> 1; |
| u64 timestamp = 0ULL; |
| |
| for (timestamp = data->old_timestamp + delay; |
| timestamp < timestamp_new - shift_timestamp; timestamp += delay) { |
| time_hi = (int)((timestamp & TIME_HI_MASK) >> TIME_HI_SHIFT); |
| time_lo = (int)(timestamp & TIME_LO_MASK); |
| 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_report_rel(data->input, REL_DIAL, time_hi); |
| input_report_rel(data->input, REL_MISC, time_lo); |
| input_sync(data->input); |
| } |
| } |
| time_hi = (int)((timestamp_new & TIME_HI_MASK) >> TIME_HI_SHIFT); |
| time_lo = (int)(timestamp_new & TIME_LO_MASK); |
| |
| 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_report_rel(data->input, REL_DIAL, time_hi); |
| input_report_rel(data->input, REL_MISC, time_lo); |
| input_sync(data->input); |
| |
| data->old_timestamp = timestamp_new; |
| |
| exit: |
| if ((ktime_to_ns(data->poll_delay) * (int64_t)data->time_count) |
| >= ((int64_t)ACCEL_LOG_TIME * NSEC_PER_SEC)) { |
| SENSOR_INFO("x = %d, y = %d, z = %d (ra:%d)\n", |
| data->accdata.x, data->accdata.y, |
| data->accdata.z, data->recog_flag); |
| data->time_count = 0; |
| } else |
| data->time_count++; |
| } |
| |
| 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) { |
| SENSOR_ERR("Invalid Argument\n"); |
| return ret; |
| } |
| |
| pre_enable = atomic_read(&data->enable); |
| SENSOR_INFO("pre_enable = %d, enable = %d\n", pre_enable, enable); |
| |
| if (enable) { |
| if (pre_enable == OFF) { |
| data->old_timestamp = 0LL; |
| if (data->caldata.x == 0 && data->caldata.y == 0 |
| && data->caldata.z == 0) |
| k2hh_open_calibration(data); |
| k2hh_set_range(data, K2HH_RANGE_4G); |
| k2hh_set_bw(data); |
| 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); |
| } |
| } |
| |
| 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) { |
| SENSOR_ERR("Invalid Argument\n"); |
| return ret; |
| } |
| |
| if (delay > K2HH_DEFAULT_DELAY) |
| delay = K2HH_DEFAULT_DELAY; |
| else if (delay < K2HH_MIN_DELAY) |
| delay = K2HH_MIN_DELAY; |
| |
| 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); |
| } |
| |
| SENSOR_INFO("poll_delay = %lld\n", delay); |
| return size; |
| } |
| |
| static DEVICE_ATTR(poll_delay, 0664, |
| k2hh_delay_show, k2hh_delay_store); |
| static DEVICE_ATTR(enable, 0664, |
| 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 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) |
| SENSOR_ERR("calibration open failed(%d)\n", ret); |
| |
| SENSOR_INFO("cal data %d %d %d - ret : %d\n", |
| 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 d_enable; |
| struct k2hh_p *data = dev_get_drvdata(dev); |
| |
| ret = kstrtoll(buf, 10, &d_enable); |
| if (ret < 0) |
| return ret; |
| |
| ret = k2hh_do_calibrate(data, (int)d_enable); |
| if (ret < 0) |
| SENSOR_ERR("accel calibrate failed\n"); |
| |
| return size; |
| } |
| |
| static ssize_t k2hh_lowpassfilter_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| int ret; |
| struct k2hh_p *data = dev_get_drvdata(dev); |
| |
| if (data->hr == CTRL1_HR_ENABLE) |
| ret = 1; |
| else |
| ret = 0; |
| |
| return snprintf(buf, PAGE_SIZE, "%d\n", ret); |
| } |
| |
| static ssize_t k2hh_lowpassfilter_store(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t size) |
| { |
| int ret; |
| int64_t d_enable; |
| struct k2hh_p *data = dev_get_drvdata(dev); |
| |
| SENSOR_INFO("\n"); |
| |
| ret = kstrtoll(buf, 10, &d_enable); |
| if (ret < 0) |
| SENSOR_ERR("kstrtoll failed\n"); |
| |
| ret = k2hh_set_hr(data, d_enable); |
| if (ret < 0) |
| SENSOR_ERR("set_hr failed\n"); |
| |
| 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_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_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; |
| SENSOR_INFO("disable\n"); |
| } else if (sysfs_streq(buf, "1")) { |
| enable = ON; |
| factory_mode = OFF; |
| SENSOR_INFO("enable\n"); |
| } else if (sysfs_streq(buf, "2")) { |
| enable = ON; |
| factory_mode = ON; |
| SENSOR_INFO("factory mode\n"); |
| } else { |
| SENSOR_ERR("invalid value %d\n", *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); |
| } |
| |
| enable_irq(data->irq1); |
| enable_irq_wake(data->irq1); |
| |
| k2hh_i2c_write(data, CTRL7_REG, CTRL7_LIR1); |
| k2hh_i2c_write(data, CTRL3_REG, CTRL3_IG1_INT1); |
| |
| SENSOR_INFO("reactive alert is on!\n"); |
| } 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; |
| SENSOR_INFO("reactive alert is off! irq = %d\n", |
| data->irq_state); |
| } |
| |
| return size; |
| } |
| |
| 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, backup[4]; |
| int result = 1, i, retry; |
| ssize_t ret; |
| s32 NO_ST[3] = {0, 0, 0}; |
| s32 ST[3] = {0, 0, 0}; |
| |
| k2hh_i2c_read(data, CTRL1_REG, &backup[0], 1); |
| k2hh_i2c_read(data, CTRL4_REG, &backup[1], 1); |
| k2hh_i2c_read(data, CTRL5_REG, &backup[2], 1); |
| k2hh_i2c_read(data, CTRL6_REG, &backup[3], 1); |
| |
| 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); |
| |
| msleep(80); |
| |
| k2hh_read_accel_xyz(data, &acc); |
| |
| for (i = 0; i < 5; i++) { |
| retry = 5; |
| do { |
| ret = k2hh_i2c_read(data, STATUS_REG, &temp, 1); |
| if (ret < 0) { |
| SENSOR_ERR("i2c error"); |
| goto exit_status_err; |
| } |
| |
| if (temp & 0x08) |
| break; |
| |
| msleep(20); |
| } while (retry-- >= 0); |
| |
| if (retry < 0) { |
| SENSOR_ERR("failed to update data\n"); |
| goto exit_status_err; |
| } |
| |
| 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); |
| |
| msleep(80); |
| |
| k2hh_read_accel_xyz(data, &acc); |
| |
| for (i = 0; i < 5; i++) { |
| retry = 5; |
| do { |
| ret = k2hh_i2c_read(data, STATUS_REG, &temp, 1); |
| if (ret < 0) { |
| SENSOR_ERR("i2c error"); |
| goto exit_status_err; |
| } |
| |
| if (temp & 0x08) |
| break; |
| |
| msleep(20); |
| } while (retry-- >= 0); |
| |
| if (retry < 0) { |
| SENSOR_ERR("failed to update data\n"); |
| goto exit_status_err; |
| } |
| |
| 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 ((ST[i] < SELF_TEST_2G_MIN_LSB) |
| || (ST[i] > SELF_TEST_2G_MAX_LSB)) { |
| SENSOR_ERR("%d Out of range!! (%d)\n", i, ST[i]); |
| result = 0; |
| } |
| } |
| |
| if (result) |
| ret = snprintf(buf, 0xff, "1,%d,%d,%d\n", ST[0], ST[1], ST[2]); |
| else |
| ret = snprintf(buf, 0xff, "0,%d,%d,%d\n", ST[0], ST[1], ST[2]); |
| |
| goto exit; |
| |
| exit_status_err: |
| ret = snprintf(buf, 0xff, "-1,0,0,0\n"); |
| exit: |
| k2hh_i2c_write(data, CTRL1_REG, 0x00); |
| k2hh_i2c_write(data, CTRL5_REG, 0x00); |
| |
| k2hh_i2c_write(data, CTRL1_REG, backup[0]); |
| k2hh_i2c_write(data, CTRL4_REG, backup[1]); |
| k2hh_i2c_write(data, CTRL5_REG, backup[2]); |
| k2hh_i2c_write(data, CTRL6_REG, backup[3]); |
| |
| 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, 0444, k2hh_selftest_show, NULL); |
| static DEVICE_ATTR(name, 0444, k2hh_name_show, NULL); |
| static DEVICE_ATTR(vendor, 0444, k2hh_vendor_show, NULL); |
| static DEVICE_ATTR(calibration, 0664, |
| k2hh_calibration_show, k2hh_calibration_store); |
| static DEVICE_ATTR(lowpassfilter, 0664, |
| k2hh_lowpassfilter_show, k2hh_lowpassfilter_store); |
| static DEVICE_ATTR(raw_data, 0444, k2hh_raw_data_read, NULL); |
| static DEVICE_ATTR(reactive_alert, 0664, |
| 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_lowpassfilter, |
| &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)); |
| SENSOR_INFO("###### reactive irq ######\n"); |
| |
| 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) { |
| SENSOR_ERR("gpio %d request failed (%d)\n", |
| data->acc_int1, ret); |
| goto exit; |
| } |
| |
| ret = gpio_direction_input(data->acc_int1); |
| if (ret < 0) { |
| SENSOR_ERR("failed to set gpio %d as input (%d)\n", |
| 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); |
| /* add IRQF_NO_SUSPEND option in case of Spreadtrum AP */ |
| ret = request_threaded_irq(data->irq1, NULL, k2hh_irq_thread, |
| IRQF_TRIGGER_RISING | IRQF_ONESHOT, "k2hh_accel", data); |
| if (ret < 0) { |
| SENSOR_ERR("can't allocate irq.\n"); |
| 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_capability(dev, EV_REL, REL_DIAL); |
| input_set_capability(dev, EV_REL, REL_MISC); |
| input_set_drvdata(dev, data); |
| |
| ret = input_register_device(dev); |
| if (ret < 0) |
| goto err_register_input_dev; |
| |
| ret = sensors_create_symlink(&dev->dev.kobj, dev->name); |
| if (ret < 0) |
| goto err_create_sensor_symlink; |
| |
| /* sysfs node creation */ |
| ret = sysfs_create_group(&dev->dev.kobj, &k2hh_attribute_group); |
| if (ret < 0) |
| goto err_create_sysfs_group; |
| |
| data->input = dev; |
| |
| return 0; |
| |
| err_create_sysfs_group: |
| sensors_remove_symlink(&dev->dev.kobj, dev->name); |
| err_create_sensor_symlink: |
| input_unregister_device(dev); |
| err_register_input_dev: |
| input_free_device(dev); |
| return ret; |
| } |
| |
| static int k2hh_parse_dt(struct k2hh_p *data, struct device *dev) |
| { |
| struct device_node *d_node = dev->of_node; |
| enum of_gpio_flags flags; |
| int ret; |
| u32 temp; |
| |
| if (d_node == NULL) |
| return -ENODEV; |
| |
| data->acc_int1 = of_get_named_gpio_flags(d_node, "k2hh,irq_gpio", 0, &flags); |
| if (data->acc_int1 < 0) { |
| SENSOR_ERR("get acc_int1 error\n"); |
| return -ENODEV; |
| } |
| |
| ret = of_property_read_u32(d_node, "k2hh,axis_map_x", &temp); |
| if ((data->axis_map_x > 2) || (ret < 0)) { |
| SENSOR_ERR("invalid x axis_map value %u\n", |
| data->axis_map_x); |
| data->axis_map_x = 0; |
| } else |
| data->axis_map_x = (u8)temp; |
| |
| ret = of_property_read_u32(d_node, "k2hh,axis_map_y", &temp); |
| if ((data->axis_map_y > 2) || (ret < 0)) { |
| SENSOR_ERR("invalid y axis_map value %u\n", |
| data->axis_map_y); |
| data->axis_map_y = 1; |
| } else |
| data->axis_map_y = (u8)temp; |
| |
| ret = of_property_read_u32(d_node, "k2hh,axis_map_z", &temp); |
| if ((data->axis_map_z > 2) || (ret < 0)) { |
| SENSOR_ERR("invalid z axis_map value %u\n", |
| data->axis_map_z); |
| data->axis_map_z = 2; |
| } else |
| data->axis_map_z = (u8)temp; |
| |
| ret = of_property_read_u32(d_node, "k2hh,negate_x", &temp); |
| if ((data->negate_x > 1) || (ret < 0)) { |
| SENSOR_ERR("invalid x axis_map value %u\n", |
| data->negate_x); |
| data->negate_x = 0; |
| } else |
| data->negate_x = (u8)temp; |
| |
| ret = of_property_read_u32(d_node, "k2hh,negate_y", &temp); |
| if ((data->negate_y > 1) || (ret < 0)) { |
| SENSOR_ERR("invalid y axis_map value %u\n", |
| data->negate_y); |
| data->negate_y = 0; |
| } else |
| data->negate_y = (u8)temp; |
| |
| ret = of_property_read_u32(d_node, "k2hh,negate_z", &temp); |
| if ((data->negate_z > 1) || (ret < 0)) { |
| SENSOR_ERR("invalid z axis_map value %u\n", |
| 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) |
| { |
| int ret = 0; |
| |
| SENSOR_INFO("%s\n", (onoff) ? "on" : "off"); |
| |
| #ifdef CONFIG_SENSORS_K2HH_VDD |
| if (!data->reg_vdd) { |
| SENSOR_INFO("VDD get regulator\n"); |
| data->reg_vdd = devm_regulator_get(&data->client->dev, |
| "k2hh,vdd"); |
| if (IS_ERR(data->reg_vdd)) { |
| SENSOR_ERR("could not get vdd, %ld\n", |
| PTR_ERR(data->reg_vdd)); |
| ret = -ENODEV; |
| goto err_vdd; |
| } |
| regulator_set_voltage(data->reg_vdd, 2850000, 2850000); |
| } |
| #endif |
| if (!data->reg_vio) { |
| SENSOR_INFO("VIO get regulator\n"); |
| data->reg_vio = devm_regulator_get(&data->client->dev, |
| "k2hh,vio"); |
| if (IS_ERR(data->reg_vio)) { |
| SENSOR_ERR("could not get vio, %ld\n", |
| PTR_ERR(data->reg_vio)); |
| ret = -ENODEV; |
| goto err_vio; |
| } |
| regulator_set_voltage(data->reg_vio, 1800000, 1800000); |
| } |
| |
| if (onoff) { |
| #ifdef CONFIG_SENSORS_K2HH_VDD |
| ret = regulator_enable(data->reg_vdd); |
| if (ret) |
| SENSOR_ERR("failed to enable vdd.\n"); |
| #endif |
| ret = regulator_enable(data->reg_vio); |
| if (ret) |
| SENSOR_ERR("failed to enable vio.\n"); |
| msleep(30); |
| } else { |
| #ifdef CONFIG_SENSORS_K2HH_VDD |
| ret = regulator_disable(data->reg_vdd); |
| if (ret) |
| SENSOR_ERR("failed to disable vdd.\n"); |
| #endif |
| ret = regulator_disable(data->reg_vio); |
| if (ret) |
| SENSOR_ERR("failed to disable vio.\n"); |
| msleep(30); |
| } |
| |
| return 0; |
| |
| err_vio: |
| #ifdef CONFIG_SENSORS_K2HH_VDD |
| SENSOR_INFO("VDD put\n"); |
| devm_regulator_put(data->reg_vdd); |
| err_vdd: |
| #endif |
| |
| return ret; |
| } |
| |
| static int k2hh_probe(struct i2c_client *client, |
| const struct i2c_device_id *id) |
| { |
| u8 temp; |
| int ret = -ENODEV, i; |
| struct k2hh_p *data; |
| |
| SENSOR_INFO("start!\n"); |
| |
| if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { |
| SENSOR_ERR("i2c_check_functionality error\n"); |
| goto exit; |
| } |
| |
| data = kzalloc(sizeof(struct k2hh_p), GFP_KERNEL); |
| if (data == NULL) { |
| SENSOR_ERR("kzalloc error\n"); |
| ret = -ENOMEM; |
| goto exit; |
| } |
| |
| i2c_set_clientdata(client, data); |
| data->client = client; |
| |
| ret = k2hh_parse_dt(data, &client->dev); |
| if (ret < 0) { |
| SENSOR_ERR("of_node error\n"); |
| ret = -ENODEV; |
| goto exit_of_node; |
| } |
| |
| ret = k2hh_regulator_onoff(data, true); |
| if (ret < 0) { |
| SENSOR_ERR("No regulator\n"); |
| goto exit_no_regulator; |
| } |
| |
| 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) { |
| SENSOR_INFO("chip id failed 0x%x : %d\n", |
| temp, ret); |
| } else { |
| SENSOR_INFO("chip id success 0x%x\n", |
| 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; |
| |
| ret = sensors_register(&data->factory_device, data, sensor_attrs, |
| MODULE_NAME); |
| if (ret) { |
| SENSOR_ERR("failed to sensors_register (%d)\n", ret); |
| goto exit_sensor_register_failed; |
| } |
| |
| /* 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; |
| |
| data->accel_wq = create_singlethread_workqueue("accel_wq"); |
| if (!data->accel_wq) { |
| ret = -ENOMEM; |
| SENSOR_ERR("could not create workqueue\n"); |
| goto exit_create_workqueue; |
| } |
| |
| /* this is the thread function we run on the work queue */ |
| INIT_WORK(&data->work_accel, k2hh_work_func); |
| INIT_DELAYED_WORK(&data->irq_work, k2hh_irq_work_func); |
| |
| ret = k2hh_setup_pin(data); |
| if (ret < 0) { |
| SENSOR_ERR("could not setup pin\n"); |
| goto exit_setup_pin; |
| } |
| |
| atomic_set(&data->enable, OFF); |
| data->time_count = 0; |
| data->irq_state = 0; |
| data->recog_flag = OFF; |
| data->hr = CTRL1_HR_ENABLE; |
| |
| k2hh_set_range(data, K2HH_RANGE_4G); |
| k2hh_set_mode(data, K2HH_MODE_SUSPEND); |
| |
| SENSOR_INFO("done!\n"); |
| |
| return 0; |
| |
| exit_setup_pin: |
| cancel_delayed_work_sync(&data->irq_work); |
| destroy_workqueue(data->accel_wq); |
| exit_create_workqueue: |
| sensors_unregister(data->factory_device, sensor_attrs); |
| exit_sensor_register_failed: |
| sensors_remove_symlink(&data->input->dev.kobj, data->input->name); |
| 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); |
| k2hh_regulator_onoff(data, false); |
| exit_no_regulator: |
| exit_of_node: |
| kfree(data); |
| exit: |
| SENSOR_ERR("fail!\n"); |
| return ret; |
| } |
| |
| static int k2hh_remove(struct i2c_client *client) |
| { |
| SENSOR_INFO("\n"); |
| return 0; |
| } |
| |
| static int k2hh_suspend(struct device *dev) |
| { |
| struct k2hh_p *data = dev_get_drvdata(dev); |
| |
| SENSOR_INFO("\n"); |
| |
| 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); |
| |
| SENSOR_INFO("\n"); |
| |
| if (atomic_read(&data->enable) == ON) { |
| k2hh_set_mode(data, K2HH_MODE_NORMAL); |
| k2hh_set_enable(data, ON); |
| } |
| |
| return 0; |
| } |
| |
| static void k2hh_shutdown(struct i2c_client *client) |
| { |
| struct k2hh_p *data = i2c_get_clientdata(client); |
| |
| if (atomic_read(&data->enable) == ON) |
| k2hh_set_enable(data, OFF); |
| |
| atomic_set(&data->enable, OFF); |
| k2hh_set_mode(data, K2HH_MODE_SUSPEND); |
| } |
| |
| static const struct of_device_id k2hh_match_table[] = { |
| { .compatible = "k2hh-i2c",}, |
| {}, |
| }; |
| |
| 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 = "k2hh-i2c", |
| .owner = THIS_MODULE, |
| .of_match_table = k2hh_match_table, |
| .pm = &k2hh_pm_ops |
| }, |
| .probe = k2hh_probe, |
| .remove = k2hh_remove, |
| .shutdown = k2hh_shutdown, |
| .id_table = k2hh_id, |
| }; |
| |
| static int __init k2hh_init(void) |
| { |
| #if defined(CONFIG_SAMSUNG_LPM_MODE) |
| if (poweroff_charging) { |
| SENSOR_INFO("Skip init due to low power charging mode %d\n", poweroff_charging); |
| return 0; |
| } |
| #endif |
| |
| 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"); |
| |