2538 lines
64 KiB
C
Executable file
2538 lines
64 KiB
C
Executable file
/*
|
|
* Driver for the SX938x
|
|
* Copyright (c) 2011 Semtech Corp
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/input.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/pm_wakeup.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/of_gpio.h>
|
|
|
|
#include <linux/regulator/consumer.h>
|
|
#include <linux/power_supply.h>
|
|
#include "sx938x.h"
|
|
|
|
#if IS_ENABLED(CONFIG_SENSORS_CORE_AP)
|
|
#include <linux/sensor/sensors_core.h>
|
|
#endif
|
|
|
|
#if IS_ENABLED(CONFIG_PDIC_NOTIFIER)
|
|
#include <linux/usb/typec/common/pdic_notifier.h>
|
|
#endif
|
|
#if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER)
|
|
#include <linux/usb/typec/manager/usb_typec_manager_notifier.h>
|
|
#endif
|
|
|
|
#if IS_ENABLED(CONFIG_HALL_NOTIFIER)
|
|
#include <linux/hall/hall_ic_notifier.h>
|
|
#define HALL_NAME "hall"
|
|
#define HALL_CERT_NAME "certify_hall"
|
|
#define HALL_FLIP_NAME "flip"
|
|
#define HALL_ATTACH 1
|
|
#define HALL_DETACH 0
|
|
#endif
|
|
|
|
|
|
|
|
#define I2C_M_WR 0 /* for i2c Write */
|
|
//#define I2C_M_RD 1 /* for i2c Read */
|
|
|
|
#define HAS_ERROR -1
|
|
#define IDLE 0
|
|
#define ACTIVE 1
|
|
|
|
#define DIFF_READ_NUM 10
|
|
#define GRIP_LOG_TIME 15 /* 30 sec */
|
|
|
|
#define REF_OFF_MAIN_ON 0x02
|
|
#define REF_OFF_MAIN_OFF 0x00
|
|
|
|
#define IRQ_PROCESS_CONDITION (SX938x_IRQSTAT_TOUCH_FLAG \
|
|
| SX938x_IRQSTAT_RELEASE_FLAG \
|
|
| SX938x_IRQSTAT_COMPDONE_FLAG)
|
|
|
|
#define SX938x_MODE_SLEEP 0
|
|
#define SX938x_MODE_NORMAL 1
|
|
|
|
/* Failure Index */
|
|
#define SX938x_ID_ERROR (-1)
|
|
#define SX938x_NIRQ_ERROR (-2)
|
|
#define SX938x_CONN_ERROR (-3)
|
|
#define SX938x_I2C_ERROR (-4)
|
|
#define SX938x_REG_ERROR (-5)
|
|
|
|
#define GRIP_HAS_ERR -1
|
|
#define GRIP_WORKING 1
|
|
#define GRIP_RELEASE 2
|
|
|
|
#define INTERRUPT_HIGH 1
|
|
#define INTERRUPT_LOW 0
|
|
|
|
#define TYPE_USB 1
|
|
#define TYPE_HALL 2
|
|
#define TYPE_BOOT 3
|
|
#define TYPE_FORCE 4
|
|
|
|
#define UNKNOWN_ON 1
|
|
#define UNKNOWN_OFF 2
|
|
|
|
#define MAX_I2C_FAIL_COUNT 3
|
|
|
|
enum grip_error_state {
|
|
FAIL_UPDATE_PREV_STATE = 1,
|
|
FAIL_SETUP_REGISTER,
|
|
FAIL_I2C_ENABLE,
|
|
FAIL_I2C_READ_3_TIMES,
|
|
FAIL_DATA_STUCK,
|
|
FAIL_RESET,
|
|
FAIL_MCC_RESET,
|
|
FAIL_IRQ_MISS_MATCH
|
|
};
|
|
|
|
struct sx938x_p {
|
|
struct i2c_client *client;
|
|
struct input_dev *input;
|
|
struct input_dev *noti_input_dev;
|
|
struct device *factory_device;
|
|
struct delayed_work init_work;
|
|
struct delayed_work irq_work;
|
|
struct delayed_work debug_work;
|
|
struct wakeup_source *grip_ws;
|
|
struct mutex mode_mutex;
|
|
struct mutex read_mutex;
|
|
#if IS_ENABLED(CONFIG_PDIC_NOTIFIER)
|
|
struct notifier_block pdic_nb;
|
|
#endif
|
|
#if IS_ENABLED(CONFIG_HALL_NOTIFIER)
|
|
struct notifier_block hall_nb;
|
|
#endif
|
|
struct regulator *dvdd_vreg; /* regulator */
|
|
const char *dvdd_vreg_name; /* regulator name */
|
|
|
|
#if IS_ENABLED(CONFIG_SENSORS_COMMON_VDD_SUB)
|
|
int gpio_nirq_sub;
|
|
#endif
|
|
int pre_attach;
|
|
int debug_count;
|
|
int debug_zero_count;
|
|
int fail_status_code;
|
|
int is_unknown_mode;
|
|
int motion;
|
|
int irq_count;
|
|
int abnormal_mode;
|
|
int ldo_en;
|
|
int irq;
|
|
int gpio_nirq;
|
|
int init_done;
|
|
int noti_enable;
|
|
int again_m;
|
|
int dgain_m;
|
|
int diff_cnt;
|
|
|
|
atomic_t enable;
|
|
|
|
u32 unknown_sel;
|
|
u32 err_state;
|
|
|
|
s32 useful_avg;
|
|
s32 capMain;
|
|
s32 useful;
|
|
|
|
u16 detect_threshold;
|
|
u16 offset;
|
|
|
|
s16 avg;
|
|
s16 diff;
|
|
s16 diff_avg;
|
|
s16 max_diff;
|
|
s16 max_normal_diff;
|
|
|
|
u8 ic_num;
|
|
u8 i2c_fail_count;
|
|
u8 state_miss_matching_count;
|
|
s8 state;
|
|
s8 prev_state;
|
|
|
|
bool is_irq_active;
|
|
bool first_working;
|
|
bool skip_data;
|
|
bool check_abnormal_working;
|
|
};
|
|
|
|
static int sx938x_get_nirq_state(struct sx938x_p *data)
|
|
{
|
|
return gpio_get_value_cansleep(data->gpio_nirq);
|
|
}
|
|
|
|
static void enter_unknown_mode(struct sx938x_p *data, int type);
|
|
|
|
static void enter_error_mode(struct sx938x_p *data, enum grip_error_state err_state)
|
|
{
|
|
|
|
if (data->is_irq_active) {
|
|
// disable_irq(data->irq);
|
|
// disable_irq_wake(data->irq);
|
|
data->is_irq_active = false;
|
|
}
|
|
|
|
data->check_abnormal_working = true;
|
|
data->err_state |= 0x1 << err_state;
|
|
enter_unknown_mode(data, TYPE_FORCE);
|
|
#if IS_ENABLED(CONFIG_SENSORS_GRIP_FAILURE_DEBUG)
|
|
update_grip_error(data->ic_num, data->err_state);
|
|
#endif
|
|
GRIP_ERR("%d\n", data->err_state);
|
|
}
|
|
|
|
static void check_irq_error(struct sx938x_p *data, u8 irq_state, bool is_irq_func, bool is_enable_func)
|
|
{
|
|
if (data->is_irq_active && data->check_abnormal_working == false) {
|
|
GRIP_INFO("prev %x state %x\n", data->prev_state, irq_state);
|
|
|
|
if (is_irq_func) {
|
|
data->state_miss_matching_count = 0;
|
|
data->prev_state = irq_state;
|
|
} else if (is_enable_func) {
|
|
data->prev_state = irq_state;
|
|
} else if (data->prev_state != irq_state) {
|
|
data->state_miss_matching_count++;
|
|
data->prev_state = irq_state;
|
|
}
|
|
|
|
if (data->state_miss_matching_count >= 3) {
|
|
GRIP_INFO("enter_error_mode with IRQ\n");
|
|
enter_error_mode(data, FAIL_IRQ_MISS_MATCH);
|
|
} else
|
|
GRIP_INFO("%d\n", data->state_miss_matching_count);
|
|
}
|
|
}
|
|
|
|
static int sx938x_i2c_write(struct sx938x_p *data, u8 reg_addr, u8 buf)
|
|
{
|
|
int ret = -1;
|
|
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;
|
|
|
|
if (data->i2c_fail_count < MAX_I2C_FAIL_COUNT)
|
|
ret = i2c_transfer(data->client->adapter, &msg, 1);
|
|
|
|
if (ret < 0) {
|
|
if (data->i2c_fail_count < MAX_I2C_FAIL_COUNT)
|
|
data->i2c_fail_count++;
|
|
if (data->i2c_fail_count >= MAX_I2C_FAIL_COUNT)
|
|
enter_error_mode(data, FAIL_I2C_READ_3_TIMES);
|
|
|
|
GRIP_ERR("err %d, %u", ret, data->i2c_fail_count);
|
|
} else {
|
|
data->i2c_fail_count = 0;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int sx938x_i2c_read(struct sx938x_p *data, u8 reg_addr, u8 *buf)
|
|
{
|
|
int ret = -1;
|
|
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 = 1;
|
|
msg[1].buf = buf;
|
|
|
|
if (data->i2c_fail_count < MAX_I2C_FAIL_COUNT)
|
|
ret = i2c_transfer(data->client->adapter, msg, 2);
|
|
|
|
if (ret < 0) {
|
|
if (data->i2c_fail_count < MAX_I2C_FAIL_COUNT)
|
|
data->i2c_fail_count++;
|
|
if (data->i2c_fail_count >= MAX_I2C_FAIL_COUNT)
|
|
enter_error_mode(data, FAIL_I2C_READ_3_TIMES);
|
|
|
|
GRIP_ERR("err %d, %u", ret, data->i2c_fail_count);
|
|
} else {
|
|
data->i2c_fail_count = 0;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int sx938x_read_reg_stat(struct sx938x_p *data)
|
|
{
|
|
if (data) {
|
|
u8 value = 0;
|
|
if (sx938x_i2c_read(data, SX938x_IRQSTAT_REG, &value) >= 0)
|
|
return (value & 0x00FF);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void sx938x_initialize_register(struct sx938x_p *data)
|
|
{
|
|
u8 val = 0;
|
|
int idx, ret, retry;
|
|
|
|
data->init_done = OFF;
|
|
|
|
for (idx = 0; idx < (int)(ARRAY_SIZE(setup_reg[data->ic_num])); idx++) {
|
|
retry = MAX_I2C_FAIL_COUNT;
|
|
|
|
while (retry--) {
|
|
ret = sx938x_i2c_write(data, setup_reg[data->ic_num][idx].reg, setup_reg[data->ic_num][idx].val);
|
|
|
|
if (ret >= 0) {
|
|
GRIP_INFO("Write Reg 0x%x Val 0x%x\n",
|
|
setup_reg[data->ic_num][idx].reg,
|
|
setup_reg[data->ic_num][idx].val);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ret < 0 && retry == 0) {
|
|
GRIP_ERR("setup reg fail\n");
|
|
enter_error_mode(data, FAIL_SETUP_REGISTER);
|
|
goto exit_initialize;
|
|
}
|
|
|
|
ret = sx938x_i2c_read(data, setup_reg[data->ic_num][idx].reg, &val);
|
|
if (ret < 0)
|
|
GRIP_INFO("Read Reg fail\n");
|
|
else
|
|
GRIP_INFO("Read Reg 0x%x Val 0x%x\n",
|
|
setup_reg[data->ic_num][idx].reg, val);
|
|
|
|
if (data->check_abnormal_working)
|
|
goto exit_initialize;
|
|
}
|
|
|
|
ret = sx938x_i2c_read(data, SX938x_PROXCTRL5_REG, &val);
|
|
if (ret < 0) {
|
|
data->detect_threshold = 0;
|
|
GRIP_ERR("detect threshold update1 fail\n");
|
|
} else {
|
|
data->detect_threshold = (u16)val * (u16)val / 2;
|
|
val = 0;
|
|
ret = sx938x_i2c_read(data, SX938x_PROXCTRL4_REG, &val);
|
|
if (ret < 0) {
|
|
GRIP_ERR("detect threshold update2 fail\n");
|
|
} else {
|
|
val = (val & 0x30) >> 4;
|
|
if (val)
|
|
data->detect_threshold += data->detect_threshold >> (5 - val);
|
|
}
|
|
}
|
|
|
|
GRIP_INFO("detect threshold %u\n", data->detect_threshold);
|
|
|
|
data->init_done = ON;
|
|
exit_initialize:
|
|
return;
|
|
}
|
|
|
|
static int sx938x_hardware_check(struct sx938x_p *data)
|
|
{
|
|
int ret;
|
|
u8 whoami = 0;
|
|
u8 loop = 0;
|
|
|
|
//Check th IRQ Status
|
|
GRIP_INFO("irq state %d", sx938x_get_nirq_state(data));
|
|
|
|
while (sx938x_get_nirq_state(data) == INTERRUPT_LOW) {
|
|
sx938x_read_reg_stat(data);
|
|
GRIP_INFO("irq state %d", sx938x_get_nirq_state(data));
|
|
if (++loop > 10) {
|
|
data->fail_status_code = SX938x_NIRQ_ERROR;
|
|
return data->fail_status_code;
|
|
}
|
|
usleep_range(10000, 11000);
|
|
}
|
|
ret = sx938x_i2c_read(data, SX938x_WHOAMI_REG, &whoami);
|
|
if (ret < 0) {
|
|
data->fail_status_code = SX938x_I2C_ERROR;
|
|
return data->fail_status_code;
|
|
}
|
|
|
|
GRIP_INFO("whoami 0x%x\n", whoami);
|
|
|
|
if (whoami != SX938x_WHOAMI_VALUE) {
|
|
data->fail_status_code = SX938x_ID_ERROR;
|
|
return data->fail_status_code;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int sx938x_manual_offset_calibration(struct sx938x_p *data)
|
|
{
|
|
s32 ret = 0;
|
|
|
|
GRIP_INFO("\n");
|
|
if (data->check_abnormal_working)
|
|
return -1;
|
|
ret = sx938x_i2c_write(data, SX938x_STAT_REG, SX938x_STAT_COMPSTAT_ALL_FLAG);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static void sx938x_send_event(struct sx938x_p *data, u8 state)
|
|
{
|
|
if (data->skip_data == true) {
|
|
GRIP_INFO("skip grip event\n");
|
|
return;
|
|
}
|
|
|
|
if (state == ACTIVE) {
|
|
data->state = ACTIVE;
|
|
GRIP_INFO("touched\n");
|
|
} else if (state == IDLE) {
|
|
data->state = IDLE;
|
|
GRIP_INFO("released\n");
|
|
} else {
|
|
data->state = HAS_ERROR;
|
|
GRIP_INFO("released\n");
|
|
}
|
|
|
|
if (state == ACTIVE)
|
|
input_report_rel(data->input, REL_MISC, GRIP_WORKING);
|
|
else if (state == IDLE)
|
|
input_report_rel(data->input, REL_MISC, GRIP_RELEASE);
|
|
else
|
|
input_report_rel(data->input, REL_MISC, GRIP_HAS_ERR);
|
|
|
|
if (data->unknown_sel)
|
|
input_report_rel(data->input, REL_X, data->is_unknown_mode);
|
|
input_sync(data->input);
|
|
}
|
|
|
|
static void sx938x_get_gain(struct sx938x_p *data)
|
|
{
|
|
u8 msByte = 0;
|
|
static const int again_phm[] = {7500, 22500, 37500, 52500, 60000, 75000, 90000, 105000};
|
|
|
|
if (data->check_abnormal_working)
|
|
return;
|
|
|
|
sx938x_i2c_read(data, SX938x_AFE_PARAM1_PHM_REG, &msByte);
|
|
msByte = (msByte >> 4) & 0x07;
|
|
data->again_m = again_phm[msByte];
|
|
|
|
msByte = 0;
|
|
sx938x_i2c_read(data, SX938x_PROXCTRL0_PHM_REG, &msByte);
|
|
msByte = (msByte >> 3) & 0x07;
|
|
if (msByte)
|
|
data->dgain_m = 1 << (msByte - 1);
|
|
else
|
|
data->dgain_m = 1;
|
|
}
|
|
|
|
static int sx938x_get_data(struct sx938x_p *data)
|
|
{
|
|
int ret = -1;
|
|
if (data) {
|
|
u8 msb = 0, lsb = 0;
|
|
u8 convstat;
|
|
s16 useful_ref, retry = 0;
|
|
u16 offset_ref = 0;
|
|
int diff = 0;
|
|
|
|
mutex_lock(&data->read_mutex);
|
|
|
|
/* Semtech reference code receives the DIFF data, when 'CONVERSION' is completed (IRQ falling),
|
|
but Samsung receive diff data by POLLING. So we have to check the completion of 'CONVERSION'.*/
|
|
while (1) {
|
|
if (data->check_abnormal_working)
|
|
break;
|
|
|
|
convstat = 0xff;
|
|
sx938x_i2c_read(data, SX938x_STAT_REG, &convstat);
|
|
convstat &= 0x01;
|
|
if (++retry > 5 || convstat == 0)
|
|
break;
|
|
|
|
msleep(20);
|
|
}
|
|
|
|
GRIP_INFO("retry %d, CONVSTAT %u\n", retry, convstat);
|
|
if (data->check_abnormal_working) {
|
|
mutex_unlock(&data->read_mutex);
|
|
goto exit_get_data;
|
|
}
|
|
|
|
//READ REF Channel data
|
|
sx938x_i2c_read(data, SX938x_USEMSB_PHR, &msb);
|
|
sx938x_i2c_read(data, SX938x_USELSB_PHR, &lsb);
|
|
useful_ref = (s16)((msb << 8) | lsb);
|
|
|
|
msb = lsb = 0;
|
|
sx938x_i2c_read(data, SX938x_OFFSETMSB_PHR, &msb);
|
|
sx938x_i2c_read(data, SX938x_OFFSETLSB_PHR, &lsb);
|
|
offset_ref = (u16)((msb << 8) | lsb);
|
|
if (data->check_abnormal_working) {
|
|
mutex_unlock(&data->read_mutex);
|
|
goto exit_get_data;
|
|
}
|
|
|
|
//READ Main channel data
|
|
msb = lsb = 0;
|
|
sx938x_i2c_read(data, SX938x_USEMSB_PHM, &msb);
|
|
sx938x_i2c_read(data, SX938x_USELSB_PHM, &lsb);
|
|
data->useful = (s16)((msb << 8) | lsb);
|
|
|
|
msb = lsb = 0;
|
|
sx938x_i2c_read(data, SX938x_AVGMSB_PHM, &msb);
|
|
sx938x_i2c_read(data, SX938x_AVGLSB_PHM, &lsb);
|
|
data->avg = (s16)((msb << 8) | lsb);
|
|
if (data->check_abnormal_working) {
|
|
mutex_unlock(&data->read_mutex);
|
|
goto exit_get_data;
|
|
}
|
|
#if 0
|
|
sx938x_i2c_read(data, SX938x_DIFFMSB_PHM, &msb);
|
|
sx938x_i2c_read(data, SX938x_DIFFLSB_PHM, &lsb);
|
|
data->diff = (s32)((msb << 8) | lsb);
|
|
if (data->diff > 32767)
|
|
data->diff -= 65536;
|
|
#endif
|
|
|
|
diff = data->useful - data->avg;
|
|
if (diff > 32767)
|
|
data->diff = 32767;
|
|
else if (diff < -32768)
|
|
data->diff = -32768;
|
|
else
|
|
data->diff = diff;
|
|
|
|
msb = lsb = 0;
|
|
sx938x_i2c_read(data, SX938x_OFFSETMSB_PHM, &msb);
|
|
sx938x_i2c_read(data, SX938x_OFFSETLSB_PHM, &lsb);
|
|
if (!data->check_abnormal_working) {
|
|
data->capMain = (int)(msb >> 7) * 2369640 + (int)(msb & 0x7F) * 30380 + (int)(lsb * 270) +
|
|
(int)(((int)data->useful * data->again_m) / (data->dgain_m * 32768));
|
|
data->offset = (u16)((msb << 8) | lsb);
|
|
ret = 0;
|
|
}
|
|
|
|
mutex_unlock(&data->read_mutex);
|
|
|
|
GRIP_INFO("[MAIN] capMain %d Useful %d Average %d DIFF %d Offset %d [REF] Useful %d Offset %d\n",
|
|
data->capMain, data->useful, data->avg, data->diff,
|
|
data->offset, useful_ref, offset_ref);
|
|
}
|
|
|
|
exit_get_data:
|
|
return ret;
|
|
}
|
|
|
|
static int sx938x_set_mode(struct sx938x_p *data, unsigned char mode)
|
|
{
|
|
int ret = -EINVAL;
|
|
u8 val = 0;
|
|
|
|
if (data->check_abnormal_working) {
|
|
GRIP_INFO("abnormal working\n");
|
|
return -1;
|
|
}
|
|
GRIP_INFO("%u\n", mode);
|
|
|
|
mutex_lock(&data->mode_mutex);
|
|
if (mode == SX938x_MODE_SLEEP) {
|
|
ret = sx938x_i2c_write(data, SX938x_GNRL_CTRL0_REG, REF_OFF_MAIN_OFF);
|
|
} else if (mode == SX938x_MODE_NORMAL) {
|
|
val = setup_reg[data->ic_num][SX938x_GNRL_CTRL0_REG_IDX].val;
|
|
ret = sx938x_i2c_write(data, SX938x_GNRL_CTRL0_REG, val);
|
|
if (ret >= 0) {
|
|
msleep(20);
|
|
ret = sx938x_manual_offset_calibration(data);
|
|
if (ret >= 0)
|
|
msleep(450);
|
|
}
|
|
}
|
|
mutex_unlock(&data->mode_mutex);
|
|
|
|
GRIP_INFO("changed %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
static void sx938x_check_status(struct sx938x_p *data)
|
|
{
|
|
int ret;
|
|
u8 status = 0;
|
|
|
|
if (data->skip_data == true) {
|
|
input_report_rel(data->input, REL_MISC, GRIP_RELEASE);
|
|
if (data->unknown_sel)
|
|
input_report_rel(data->input, REL_X, UNKNOWN_OFF);
|
|
input_sync(data->input);
|
|
return;
|
|
}
|
|
|
|
if (data->check_abnormal_working)
|
|
return;
|
|
|
|
ret = sx938x_i2c_read(data, SX938x_STAT_REG, &status);
|
|
GRIP_INFO("ret %d 0x%x\n", ret, status);
|
|
|
|
if ((ret < 0) || (data->detect_threshold == 0)
|
|
|| (data->check_abnormal_working == true))
|
|
sx938x_send_event(data, HAS_ERROR);
|
|
else if ((status & SX938x_PROXSTAT_FLAG) && (data->diff > data->detect_threshold))
|
|
sx938x_send_event(data, ACTIVE);
|
|
else
|
|
sx938x_send_event(data, IDLE);
|
|
}
|
|
|
|
static void sx938x_set_enable(struct sx938x_p *data, int enable)
|
|
{
|
|
int pre_enable = atomic_read(&data->enable);
|
|
|
|
GRIP_INFO("%d\n", enable);
|
|
if (data->check_abnormal_working == true) {
|
|
if (enable)
|
|
atomic_set(&data->enable, ON);
|
|
else
|
|
atomic_set(&data->enable, OFF);
|
|
|
|
if (enable) {
|
|
GRIP_INFO("abnormal working\n");
|
|
enter_error_mode(data, FAIL_UPDATE_PREV_STATE);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
if (enable) {
|
|
if (pre_enable == OFF) {
|
|
int ret;
|
|
u8 status = 0;
|
|
|
|
data->diff_avg = 0;
|
|
data->diff_cnt = 0;
|
|
data->useful_avg = 0;
|
|
sx938x_get_data(data);
|
|
sx938x_check_status(data);
|
|
|
|
msleep(20);
|
|
sx938x_read_reg_stat(data);
|
|
|
|
/* enable interrupt */
|
|
ret = sx938x_i2c_write(data, SX938x_IRQ_ENABLE_REG, 0x0E);
|
|
if (ret < 0)
|
|
GRIP_INFO("set enable irq reg fail\n");
|
|
|
|
if (data->is_irq_active == false) {
|
|
enable_irq(data->irq);
|
|
enable_irq_wake(data->irq);
|
|
data->is_irq_active = true;
|
|
}
|
|
|
|
if (data->state == HAS_ERROR)
|
|
status = IDLE;
|
|
else
|
|
status = data->state;
|
|
check_irq_error(data, status, false, true);
|
|
atomic_set(&data->enable, ON);
|
|
}
|
|
} else {
|
|
if (pre_enable == ON) {
|
|
int ret;
|
|
/* disable interrupt */
|
|
ret = sx938x_i2c_write(data, SX938x_IRQ_ENABLE_REG, 0x00);
|
|
if (ret < 0)
|
|
GRIP_INFO("set disable irq reg fail\n");
|
|
|
|
if (data->is_irq_active == true) {
|
|
disable_irq(data->irq);
|
|
disable_irq_wake(data->irq);
|
|
data->is_irq_active = false;
|
|
}
|
|
atomic_set(&data->enable, OFF);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void sx938x_set_debug_work(struct sx938x_p *data, u8 enable,
|
|
unsigned int time_ms)
|
|
{
|
|
if (enable == ON && !data->check_abnormal_working) {
|
|
data->debug_count = 0;
|
|
data->debug_zero_count = 0;
|
|
schedule_delayed_work(&data->debug_work,
|
|
msecs_to_jiffies(time_ms));
|
|
} else {
|
|
cancel_delayed_work_sync(&data->debug_work);
|
|
}
|
|
}
|
|
|
|
static void enter_unknown_mode(struct sx938x_p *data, int type)
|
|
{
|
|
if (data->noti_enable && !data->skip_data && data->unknown_sel) {
|
|
data->motion = 0;
|
|
data->first_working = false;
|
|
if (data->is_unknown_mode == UNKNOWN_OFF) {
|
|
data->is_unknown_mode = UNKNOWN_ON;
|
|
if (atomic_read(&data->enable) == ON) {
|
|
input_report_rel(data->input, REL_X, UNKNOWN_ON);
|
|
input_sync(data->input);
|
|
}
|
|
GRIP_INFO("UNKNOWN Re-enter\n");
|
|
} else {
|
|
GRIP_INFO("already UNKNOWN\n");
|
|
}
|
|
input_report_rel(data->noti_input_dev, REL_X, type);
|
|
input_sync(data->noti_input_dev);
|
|
}
|
|
}
|
|
|
|
static ssize_t sx938x_get_offset_calibration_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
u8 reg_value = 0;
|
|
struct sx938x_p *data = dev_get_drvdata(dev);
|
|
|
|
sx938x_i2c_read(data, SX938x_IRQSTAT_REG, ®_value);
|
|
|
|
return sprintf(buf, "%d\n", reg_value);
|
|
}
|
|
|
|
static ssize_t sx938x_set_offset_calibration_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct sx938x_p *data = dev_get_drvdata(dev);
|
|
unsigned long val;
|
|
|
|
if (kstrtoul(buf, 0, &val))
|
|
return -EINVAL;
|
|
if (val)
|
|
sx938x_manual_offset_calibration(data);
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t sx938x_sw_reset_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct sx938x_p *data = dev_get_drvdata(dev);
|
|
u8 compstat = 0xFF;
|
|
int retry = 0;
|
|
|
|
GRIP_INFO("\n");
|
|
sx938x_manual_offset_calibration(data);
|
|
msleep(450);
|
|
sx938x_get_data(data);
|
|
|
|
while (retry++ <= 3) {
|
|
sx938x_i2c_read(data, SX938x_STAT_REG, &compstat);
|
|
GRIP_INFO("compstat 0x%x\n", compstat);
|
|
compstat &= 0x04;
|
|
|
|
if (compstat == 0)
|
|
break;
|
|
|
|
msleep(50);
|
|
}
|
|
|
|
if (compstat == 0)
|
|
return snprintf(buf, PAGE_SIZE, "%d\n", 0);
|
|
return snprintf(buf, PAGE_SIZE, "%d\n", -1);
|
|
}
|
|
|
|
static ssize_t sx938x_vendor_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
return snprintf(buf, PAGE_SIZE, "%s\n", VENDOR_NAME);
|
|
}
|
|
|
|
static ssize_t sx938x_name_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct sx938x_p *data = dev_get_drvdata(dev);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%s\n", device_name[data->ic_num]);
|
|
}
|
|
|
|
static ssize_t sx938x_touch_mode_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
return snprintf(buf, PAGE_SIZE, "1\n");
|
|
}
|
|
|
|
static ssize_t sx938x_raw_data_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
static s32 sum_diff, sum_useful;
|
|
struct sx938x_p *data = dev_get_drvdata(dev);
|
|
|
|
sx938x_get_data(data);
|
|
|
|
if (data->diff_cnt == 0) {
|
|
sum_diff = (s32)data->diff;
|
|
sum_useful = data->useful;
|
|
} else {
|
|
sum_diff += (s32)data->diff;
|
|
sum_useful += data->useful;
|
|
}
|
|
|
|
if (++data->diff_cnt >= DIFF_READ_NUM) {
|
|
data->diff_avg = (s16)(sum_diff / DIFF_READ_NUM);
|
|
data->useful_avg = sum_useful / DIFF_READ_NUM;
|
|
data->diff_cnt = 0;
|
|
}
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%ld,%ld,%u,%d,%d\n", (long int)data->capMain,
|
|
(long int)data->useful, data->offset, data->diff, data->avg);
|
|
}
|
|
|
|
static ssize_t sx938x_diff_avg_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct sx938x_p *data = dev_get_drvdata(dev);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d\n", data->diff_avg);
|
|
}
|
|
|
|
static ssize_t sx938x_useful_avg_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct sx938x_p *data = dev_get_drvdata(dev);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%ld\n", (long int)data->useful_avg);
|
|
}
|
|
|
|
static ssize_t sx938x_avgnegfilt_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct sx938x_p *data = dev_get_drvdata(dev);
|
|
u8 avgnegfilt = 0;
|
|
|
|
sx938x_i2c_read(data, SX938x_PROXCTRL3_REG, &avgnegfilt);
|
|
|
|
avgnegfilt = (avgnegfilt & 0x38) >> 3;
|
|
|
|
if (avgnegfilt == 7)
|
|
return snprintf(buf, PAGE_SIZE, "1\n");
|
|
else if (avgnegfilt > 0 && avgnegfilt < 7)
|
|
return snprintf(buf, PAGE_SIZE, "1-1/%d\n", 1 << avgnegfilt);
|
|
else if (avgnegfilt == 0)
|
|
return snprintf(buf, PAGE_SIZE, "0\n");
|
|
|
|
return snprintf(buf, PAGE_SIZE, "not set\n");
|
|
}
|
|
|
|
static ssize_t sx938x_avgposfilt_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct sx938x_p *data = dev_get_drvdata(dev);
|
|
u8 avgposfilt = 0;
|
|
|
|
sx938x_i2c_read(data, SX938x_PROXCTRL3_REG, &avgposfilt);
|
|
avgposfilt = avgposfilt & 0x07;
|
|
|
|
if (avgposfilt == 7)
|
|
return snprintf(buf, PAGE_SIZE, "1\n");
|
|
else if (avgposfilt > 1 && avgposfilt < 7)
|
|
return snprintf(buf, PAGE_SIZE, "1-1/%d\n", 16 << avgposfilt);
|
|
else if (avgposfilt == 1)
|
|
return snprintf(buf, PAGE_SIZE, "1-1/16\n");
|
|
else
|
|
return snprintf(buf, PAGE_SIZE, "0\n");
|
|
}
|
|
|
|
static ssize_t sx938x_gain_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct sx938x_p *data = dev_get_drvdata(dev);
|
|
u8 gain = 0;
|
|
|
|
sx938x_i2c_read(data, SX938x_PROXCTRL0_PHM_REG, &gain);
|
|
gain = (gain & 0x38) >> 3;
|
|
|
|
if (gain > 0 && gain < 5)
|
|
return snprintf(buf, PAGE_SIZE, "x%u\n", 1 << (gain - 1));
|
|
|
|
return snprintf(buf, PAGE_SIZE, "Reserved\n");
|
|
}
|
|
|
|
static ssize_t sx938x_avgthresh_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct sx938x_p *data = dev_get_drvdata(dev);
|
|
u8 avgthresh = 0;
|
|
|
|
sx938x_i2c_read(data, SX938x_PROXCTRL1_REG, &avgthresh);
|
|
avgthresh = avgthresh & 0x3F;
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%ld\n", 512 * (long int)avgthresh);
|
|
}
|
|
|
|
static ssize_t sx938x_rawfilt_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct sx938x_p *data = dev_get_drvdata(dev);
|
|
u8 rawfilt = 0;
|
|
|
|
sx938x_i2c_read(data, SX938x_PROXCTRL0_PHM_REG, &rawfilt);
|
|
rawfilt = rawfilt & 0x07;
|
|
|
|
if (rawfilt > 0 && rawfilt < 8)
|
|
return snprintf(buf, PAGE_SIZE, "1-1/%d\n", 1 << rawfilt);
|
|
else
|
|
return snprintf(buf, PAGE_SIZE, "0\n");
|
|
}
|
|
|
|
static ssize_t sx938x_sampling_freq_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct sx938x_p *data = dev_get_drvdata(dev);
|
|
u8 sampling_freq = 0;
|
|
const char *table[16] = {
|
|
"250", "200", "166.67", "142.86", "125", "100", "83.33", "71.43",
|
|
"62.50", "50", "41.67", "35.71", "27.78", "20.83", "15.62", "7.81"
|
|
};
|
|
|
|
sx938x_i2c_read(data, SX938x_AFE_PARAM1_PHM_REG, &sampling_freq);
|
|
sampling_freq = sampling_freq & 0x0F;
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%skHz\n", table[sampling_freq]);
|
|
}
|
|
|
|
static ssize_t sx938x_scan_period_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct sx938x_p *data = dev_get_drvdata(dev);
|
|
u8 scan_period = 0;
|
|
|
|
sx938x_i2c_read(data, SX938x_GNRL_CTRL2_REG, &scan_period);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%ld\n",
|
|
(long int)(((long int)scan_period << 11) / 1000));
|
|
}
|
|
|
|
static ssize_t sx938x_again_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct sx938x_p *data = dev_get_drvdata(dev);
|
|
const char *table[8] = {
|
|
"+/-0.75", "+/-2.25", "+/-3.75", "+/-5.25",
|
|
"+/-6", "+/-7.5", "+/-9", "+/-10.5"
|
|
};
|
|
u8 again = 0;
|
|
|
|
sx938x_i2c_read(data, SX938x_AFE_PARAM1_PHM_REG, &again);
|
|
again = (again & 0x70) >> 4;
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%spF\n", table[again]);
|
|
}
|
|
|
|
static ssize_t sx938x_phase_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
return snprintf(buf, PAGE_SIZE, "1\n");
|
|
}
|
|
|
|
static ssize_t sx938x_hysteresis_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct sx938x_p *data = dev_get_drvdata(dev);
|
|
const char *table[4] = {"None", "+/-6%", "+/-12%", "+/-25%"};
|
|
u8 hyst = 0;
|
|
|
|
sx938x_i2c_read(data, SX938x_PROXCTRL4_REG, &hyst);
|
|
hyst = (hyst & 0x30) >> 4;
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%s\n", table[hyst]);
|
|
}
|
|
|
|
static ssize_t sx938x_resolution_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct sx938x_p *data = dev_get_drvdata(dev);
|
|
u8 resolution = 0;
|
|
|
|
sx938x_i2c_read(data, SX938x_AFE_PARAM0_PHM_REG, &resolution);
|
|
resolution = resolution & 0x7;
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%u\n", 1 << (resolution + 3));
|
|
}
|
|
|
|
static ssize_t sx938x_useful_filt_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct sx938x_p *data = dev_get_drvdata(dev);
|
|
u8 useful_filt = 0;
|
|
|
|
sx938x_i2c_read(data, SX938x_USEFILTER4_REG, &useful_filt);
|
|
useful_filt = useful_filt & 0x01;
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%s\n", useful_filt ? "on" : "off");
|
|
}
|
|
|
|
static ssize_t sx938x_resistor_filter_input_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct sx938x_p *data = dev_get_drvdata(dev);
|
|
u8 resistor_filter = 0;
|
|
|
|
sx938x_i2c_read(data, SX938x_AFE_CTRL1_REG, &resistor_filter);
|
|
resistor_filter = resistor_filter & 0x0F;
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d kohm\n", resistor_filter * 2);
|
|
}
|
|
|
|
static ssize_t sx938x_closedeb_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct sx938x_p *data = dev_get_drvdata(dev);
|
|
u8 closedeb = 0;
|
|
|
|
sx938x_i2c_read(data, SX938x_PROXCTRL4_REG, &closedeb);
|
|
closedeb = closedeb & 0x0C;
|
|
closedeb = closedeb >> 2;
|
|
|
|
if (closedeb == 0)
|
|
return snprintf(buf, PAGE_SIZE, "off");
|
|
return snprintf(buf, PAGE_SIZE, "%d\n", 1 << closedeb);
|
|
}
|
|
|
|
static ssize_t sx938x_fardeb_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct sx938x_p *data = dev_get_drvdata(dev);
|
|
u8 fardeb = 0;
|
|
|
|
sx938x_i2c_read(data, SX938x_PROXCTRL4_REG, &fardeb);
|
|
fardeb = fardeb & 0x03;
|
|
|
|
if (fardeb == 0)
|
|
return snprintf(buf, PAGE_SIZE, "off");
|
|
return snprintf(buf, PAGE_SIZE, "%d\n", 1 << fardeb);
|
|
}
|
|
|
|
|
|
static ssize_t sx938x_irq_count_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct sx938x_p *data = dev_get_drvdata(dev);
|
|
|
|
int ret = 0;
|
|
s16 max_diff_val = 0;
|
|
|
|
if (data->irq_count) {
|
|
ret = -1;
|
|
max_diff_val = data->max_diff;
|
|
} else {
|
|
max_diff_val = data->max_normal_diff;
|
|
}
|
|
|
|
GRIP_INFO("called\n");
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d,%d,%d\n",
|
|
ret, data->irq_count, max_diff_val);
|
|
}
|
|
|
|
static ssize_t sx938x_irq_count_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct sx938x_p *data = dev_get_drvdata(dev);
|
|
|
|
u8 onoff;
|
|
int ret;
|
|
|
|
ret = kstrtou8(buf, 10, &onoff);
|
|
if (ret < 0) {
|
|
GRIP_ERR("kstrtou8 fail %d\n", ret);
|
|
return count;
|
|
}
|
|
|
|
mutex_lock(&data->read_mutex);
|
|
|
|
if (onoff == 0) {
|
|
data->abnormal_mode = OFF;
|
|
} else if (onoff == 1) {
|
|
data->abnormal_mode = ON;
|
|
data->irq_count = 0;
|
|
data->max_diff = 0;
|
|
data->max_normal_diff = 0;
|
|
} else {
|
|
GRIP_ERR("unknown val %d\n", onoff);
|
|
}
|
|
|
|
mutex_unlock(&data->read_mutex);
|
|
|
|
GRIP_INFO("%d\n", onoff);
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t sx938x_normal_threshold_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct sx938x_p *data = dev_get_drvdata(dev);
|
|
u8 th_buf = 0, hyst = 0;
|
|
u32 threshold = 0;
|
|
|
|
sx938x_i2c_read(data, SX938x_PROXCTRL5_REG, &th_buf);
|
|
threshold = (u32)th_buf * (u32)th_buf / 2;
|
|
|
|
sx938x_i2c_read(data, SX938x_PROXCTRL4_REG, &hyst);
|
|
hyst = (hyst & 0x30) >> 4;
|
|
|
|
switch (hyst) {
|
|
case 0x01: /* 6% */
|
|
hyst = threshold >> 4;
|
|
break;
|
|
case 0x02: /* 12% */
|
|
hyst = threshold >> 3;
|
|
break;
|
|
case 0x03: /* 25% */
|
|
hyst = threshold >> 2;
|
|
break;
|
|
default:
|
|
/* None */
|
|
break;
|
|
}
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%lu,%lu\n",
|
|
(u32)threshold + (u32)hyst, (u32)threshold - (u32)hyst);
|
|
}
|
|
|
|
static ssize_t sx938x_onoff_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct sx938x_p *data = dev_get_drvdata(dev);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%u\n", !data->skip_data);
|
|
}
|
|
|
|
static ssize_t sx938x_onoff_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct sx938x_p *data = dev_get_drvdata(dev);
|
|
int ret;
|
|
u8 val;
|
|
|
|
ret = kstrtou8(buf, 2, &val);
|
|
if (ret) {
|
|
GRIP_ERR("kstrtoint fail %d, %s\n", ret, buf);
|
|
return ret;
|
|
}
|
|
|
|
if (val == 0) {
|
|
data->skip_data = true;
|
|
if (atomic_read(&data->enable) == ON) {
|
|
data->state = IDLE;
|
|
input_report_rel(data->input, REL_MISC, GRIP_RELEASE);
|
|
if (data->unknown_sel)
|
|
input_report_rel(data->input, REL_X, UNKNOWN_OFF);
|
|
input_sync(data->input);
|
|
}
|
|
data->motion = 1;
|
|
data->is_unknown_mode = UNKNOWN_OFF;
|
|
data->first_working = false;
|
|
} else {
|
|
data->skip_data = false;
|
|
}
|
|
|
|
GRIP_INFO("%u\n", val);
|
|
return count;
|
|
}
|
|
|
|
static ssize_t sx938x_register_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
int idx;
|
|
int i = 0;
|
|
u8 val;
|
|
char *p = buf;
|
|
struct sx938x_p *data = dev_get_drvdata(dev);
|
|
|
|
for (idx = 0; idx < (int)(ARRAY_SIZE(setup_reg[data->ic_num])); idx++) {
|
|
sx938x_i2c_read(data, setup_reg[data->ic_num][idx].reg, &val);
|
|
i += snprintf(p + i, PAGE_SIZE - i, "(0x%02x)=0x%02x\n", setup_reg[data->ic_num][idx].reg, val);
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
|
|
static ssize_t sx938x_register_write_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
int reg_address = 0, val = 0;
|
|
struct sx938x_p *data = dev_get_drvdata(dev);
|
|
|
|
if (sscanf(buf, "%x,%x", ®_address, &val) != 2) {
|
|
GRIP_ERR("sscanf err\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
sx938x_i2c_write(data, (unsigned char)reg_address, (unsigned char)val);
|
|
GRIP_INFO("Regi 0x%x Val 0x%x\n", reg_address, val);
|
|
|
|
sx938x_get_gain(data);
|
|
|
|
return count;
|
|
}
|
|
|
|
static unsigned int register_read_store_reg;
|
|
static unsigned int register_read_store_val;
|
|
static ssize_t sx938x_register_read_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
return sprintf(buf, "0x%02x \n", register_read_store_val);
|
|
}
|
|
|
|
static ssize_t sx938x_register_read_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
u8 val = 0;
|
|
int regist = 0;
|
|
struct sx938x_p *data = dev_get_drvdata(dev);
|
|
|
|
GRIP_INFO("\n");
|
|
|
|
if (sscanf(buf, "%x", ®ist) != 1) {
|
|
GRIP_ERR("sscanf err\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
sx938x_i2c_read(data, regist, &val);
|
|
GRIP_INFO("Regi 0x%2x Val 0x%2x\n", regist, val);
|
|
register_read_store_reg = (unsigned int)regist;
|
|
register_read_store_val = (unsigned int)val;
|
|
return count;
|
|
}
|
|
|
|
/* show far near status reg data */
|
|
static ssize_t sx938x_status_regdata_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
u8 val0;
|
|
struct sx938x_p *data = dev_get_drvdata(dev);
|
|
|
|
sx938x_i2c_read(data, SX938x_STAT_REG, &val0);
|
|
GRIP_INFO("Status 0x%2x \n", val0);
|
|
|
|
return sprintf(buf, "%d \n", val0);
|
|
}
|
|
|
|
static ssize_t sx938x_status_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
u8 val = 0;
|
|
int status = 0;
|
|
struct sx938x_p *data = dev_get_drvdata(dev);
|
|
|
|
sx938x_i2c_read(data, SX938x_STAT_REG, &val);
|
|
|
|
if ((val & 0x08) == 0)//bit3 for proxstat
|
|
status = 0;
|
|
else
|
|
status = 1;
|
|
|
|
return sprintf(buf, "%d\n", status);
|
|
}
|
|
|
|
/* check if manual calibrate success or not */
|
|
static ssize_t sx938x_cal_state_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
u8 val = 0;
|
|
int status = 0;
|
|
struct sx938x_p *data = dev_get_drvdata(dev);
|
|
|
|
sx938x_i2c_read(data, SX938x_STAT_REG, &val);
|
|
GRIP_INFO("Regi 0x01 Val 0x%2x\n", val);
|
|
if ((val & 0x06) == 0)
|
|
status = 1;
|
|
else
|
|
status = 0;
|
|
|
|
return sprintf(buf, "%d\n", status);
|
|
}
|
|
|
|
static ssize_t sx938x_motion_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct sx938x_p *data = dev_get_drvdata(dev);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%s\n",
|
|
data->motion == 1 ? "motion_detect" : "motion_non_detect");
|
|
}
|
|
|
|
static ssize_t sx938x_motion_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
u8 val;
|
|
int ret;
|
|
struct sx938x_p *data = dev_get_drvdata(dev);
|
|
|
|
ret = kstrtou8(buf, 2, &val);
|
|
if (ret) {
|
|
GRIP_ERR("kstrtou8 fail %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
data->motion = val;
|
|
|
|
GRIP_INFO("%u\n", val);
|
|
return count;
|
|
}
|
|
|
|
static ssize_t sx938x_unknown_state_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct sx938x_p *data = dev_get_drvdata(dev);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%s\n",
|
|
(data->is_unknown_mode == UNKNOWN_ON) ? "UNKNOWN" : "NORMAL");
|
|
}
|
|
|
|
static ssize_t sx938x_unknown_state_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
u8 val;
|
|
int ret;
|
|
struct sx938x_p *data = dev_get_drvdata(dev);
|
|
|
|
ret = kstrtou8(buf, 2, &val);
|
|
if (ret) {
|
|
GRIP_ERR("kstrtou8 fail %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
if (val == 1)
|
|
enter_unknown_mode(data, TYPE_FORCE);
|
|
else if (val == 0)
|
|
data->is_unknown_mode = UNKNOWN_OFF;
|
|
else
|
|
GRIP_INFO("Invalid Val %u\n", val);
|
|
|
|
GRIP_INFO("%u\n", val);
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t sx938x_noti_enable_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t size)
|
|
{
|
|
int ret;
|
|
u8 enable;
|
|
struct sx938x_p *data = dev_get_drvdata(dev);
|
|
|
|
ret = kstrtou8(buf, 2, &enable);
|
|
if (ret) {
|
|
GRIP_ERR("kstrtou8 fail %d\n", ret);
|
|
return size;
|
|
}
|
|
|
|
GRIP_INFO("new val %d\n", (int)enable);
|
|
|
|
data->noti_enable = enable;
|
|
|
|
if (data->noti_enable)
|
|
enter_unknown_mode(data, TYPE_BOOT);
|
|
else {
|
|
data->motion = 1;
|
|
data->first_working = false;
|
|
data->is_unknown_mode = UNKNOWN_OFF;
|
|
}
|
|
|
|
return size;
|
|
}
|
|
|
|
static ssize_t sx938x_noti_enable_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct sx938x_p *data = dev_get_drvdata(dev);
|
|
|
|
return sprintf(buf, "%d\n", data->noti_enable);
|
|
}
|
|
|
|
static DEVICE_ATTR(regproxdata, 0444, sx938x_status_regdata_show, NULL);
|
|
static DEVICE_ATTR(proxstatus, 0444, sx938x_status_show, NULL);
|
|
static DEVICE_ATTR(cal_state, 0444, sx938x_cal_state_show, NULL);
|
|
|
|
static DEVICE_ATTR(menual_calibrate, S_IRUGO | S_IWUSR | S_IWGRP,
|
|
sx938x_get_offset_calibration_show,
|
|
sx938x_set_offset_calibration_store);
|
|
static DEVICE_ATTR(register_write, S_IWUSR | S_IWGRP,
|
|
NULL, sx938x_register_write_store);
|
|
static DEVICE_ATTR(register_read_all, S_IRUGO, sx938x_register_show, NULL);
|
|
static DEVICE_ATTR(register_read, S_IRUGO | S_IWUSR | S_IWGRP,
|
|
sx938x_register_read_show, sx938x_register_read_store);
|
|
static DEVICE_ATTR(reset, S_IRUGO, sx938x_sw_reset_show, NULL);
|
|
|
|
static DEVICE_ATTR(name, S_IRUGO, sx938x_name_show, NULL);
|
|
static DEVICE_ATTR(vendor, S_IRUGO, sx938x_vendor_show, NULL);
|
|
static DEVICE_ATTR(mode, S_IRUGO, sx938x_touch_mode_show, NULL);
|
|
static DEVICE_ATTR(raw_data, S_IRUGO, sx938x_raw_data_show, NULL);
|
|
static DEVICE_ATTR(diff_avg, S_IRUGO, sx938x_diff_avg_show, NULL);
|
|
static DEVICE_ATTR(useful_avg, S_IRUGO, sx938x_useful_avg_show, NULL);
|
|
static DEVICE_ATTR(onoff, S_IRUGO | S_IWUSR | S_IWGRP,
|
|
sx938x_onoff_show, sx938x_onoff_store);
|
|
static DEVICE_ATTR(normal_threshold, S_IRUGO,
|
|
sx938x_normal_threshold_show, NULL);
|
|
|
|
static DEVICE_ATTR(avg_negfilt, S_IRUGO, sx938x_avgnegfilt_show, NULL);
|
|
static DEVICE_ATTR(avg_posfilt, S_IRUGO, sx938x_avgposfilt_show, NULL);
|
|
static DEVICE_ATTR(avg_thresh, S_IRUGO, sx938x_avgthresh_show, NULL);
|
|
static DEVICE_ATTR(rawfilt, S_IRUGO, sx938x_rawfilt_show, NULL);
|
|
static DEVICE_ATTR(sampling_freq, S_IRUGO, sx938x_sampling_freq_show, NULL);
|
|
static DEVICE_ATTR(scan_period, S_IRUGO, sx938x_scan_period_show, NULL);
|
|
static DEVICE_ATTR(gain, S_IRUGO, sx938x_gain_show, NULL);
|
|
static DEVICE_ATTR(analog_gain, S_IRUGO, sx938x_again_show, NULL);
|
|
static DEVICE_ATTR(phase, S_IRUGO, sx938x_phase_show, NULL);
|
|
static DEVICE_ATTR(hysteresis, S_IRUGO, sx938x_hysteresis_show, NULL);
|
|
static DEVICE_ATTR(irq_count, S_IRUGO | S_IWUSR | S_IWGRP,
|
|
sx938x_irq_count_show, sx938x_irq_count_store);
|
|
static DEVICE_ATTR(resolution, S_IRUGO, sx938x_resolution_show, NULL);
|
|
static DEVICE_ATTR(useful_filt, S_IRUGO, sx938x_useful_filt_show, NULL);
|
|
static DEVICE_ATTR(resistor_filter_input, S_IRUGO, sx938x_resistor_filter_input_show, NULL);
|
|
static DEVICE_ATTR(closedeb, S_IRUGO, sx938x_closedeb_show, NULL);
|
|
static DEVICE_ATTR(fardeb, S_IRUGO, sx938x_fardeb_show, NULL);
|
|
static DEVICE_ATTR(motion, S_IRUGO | S_IWUSR | S_IWGRP,
|
|
sx938x_motion_show, sx938x_motion_store);
|
|
static DEVICE_ATTR(unknown_state, S_IRUGO | S_IWUSR | S_IWGRP,
|
|
sx938x_unknown_state_show, sx938x_unknown_state_store);
|
|
static DEVICE_ATTR(noti_enable, 0664, sx938x_noti_enable_show, sx938x_noti_enable_store);
|
|
|
|
static struct device_attribute *sensor_attrs[] = {
|
|
&dev_attr_regproxdata,
|
|
&dev_attr_proxstatus,
|
|
&dev_attr_cal_state,
|
|
&dev_attr_menual_calibrate,
|
|
&dev_attr_register_write,
|
|
&dev_attr_register_read,
|
|
&dev_attr_register_read_all,
|
|
&dev_attr_reset,
|
|
&dev_attr_name,
|
|
&dev_attr_vendor,
|
|
&dev_attr_mode,
|
|
&dev_attr_raw_data,
|
|
&dev_attr_diff_avg,
|
|
&dev_attr_useful_avg,
|
|
&dev_attr_onoff,
|
|
&dev_attr_normal_threshold,
|
|
&dev_attr_avg_negfilt,
|
|
&dev_attr_avg_posfilt,
|
|
&dev_attr_avg_thresh,
|
|
&dev_attr_rawfilt,
|
|
&dev_attr_sampling_freq,
|
|
&dev_attr_scan_period,
|
|
&dev_attr_gain,
|
|
&dev_attr_analog_gain,
|
|
&dev_attr_phase,
|
|
&dev_attr_hysteresis,
|
|
&dev_attr_irq_count,
|
|
&dev_attr_resolution,
|
|
&dev_attr_useful_filt,
|
|
&dev_attr_resistor_filter_input,
|
|
&dev_attr_closedeb,
|
|
&dev_attr_fardeb,
|
|
&dev_attr_motion,
|
|
&dev_attr_unknown_state,
|
|
&dev_attr_noti_enable,
|
|
NULL,
|
|
};
|
|
|
|
static ssize_t sx938x_enable_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct sx938x_p *data = dev_get_drvdata(dev);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d\n", atomic_read(&data->enable));
|
|
}
|
|
|
|
static ssize_t sx938x_enable_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t size)
|
|
{
|
|
u8 enable;
|
|
int ret;
|
|
struct sx938x_p *data = dev_get_drvdata(dev);
|
|
|
|
ret = kstrtou8(buf, 2, &enable);
|
|
if (ret) {
|
|
GRIP_ERR("kstrtou8 fail %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
GRIP_INFO("new val %u\n", enable);
|
|
if ((enable == 0) || (enable == 1))
|
|
sx938x_set_enable(data, (int)enable);
|
|
return size;
|
|
}
|
|
|
|
static DEVICE_ATTR(enable, S_IRUGO | S_IWUSR | S_IWGRP,
|
|
sx938x_enable_show, sx938x_enable_store);
|
|
|
|
static struct attribute *sx938x_attributes[] = {
|
|
&dev_attr_enable.attr,
|
|
NULL
|
|
};
|
|
|
|
static struct attribute_group sx938x_attribute_group = {
|
|
.attrs = sx938x_attributes
|
|
};
|
|
|
|
static void sx938x_touch_process(struct sx938x_p *data)
|
|
{
|
|
u8 status = 0;
|
|
|
|
sx938x_i2c_read(data, SX938x_STAT_REG, &status);
|
|
GRIP_INFO("0x%x\n", status);
|
|
|
|
sx938x_get_data(data);
|
|
|
|
if (data->abnormal_mode) {
|
|
if (status & SX938x_PROXSTAT_FLAG) {
|
|
if (data->max_diff < data->diff)
|
|
data->max_diff = data->diff;
|
|
data->irq_count++;
|
|
}
|
|
}
|
|
|
|
if (data->state == IDLE) {
|
|
if (status & SX938x_PROXSTAT_FLAG) {
|
|
if (data->is_unknown_mode == UNKNOWN_ON && data->motion)
|
|
data->first_working = true;
|
|
sx938x_send_event(data, ACTIVE);
|
|
} else {
|
|
GRIP_INFO("0x%x already released.\n", status);
|
|
}
|
|
} else { /* User released button */
|
|
if (!(status & SX938x_PROXSTAT_FLAG)) {
|
|
if (data->is_unknown_mode == UNKNOWN_ON && data->motion) {
|
|
GRIP_INFO("unknown mode off\n");
|
|
data->is_unknown_mode = UNKNOWN_OFF;
|
|
}
|
|
sx938x_send_event(data, IDLE);
|
|
} else {
|
|
GRIP_INFO("0x%x still touched\n", status);
|
|
}
|
|
}
|
|
|
|
/* Test Code */
|
|
if (data->state == HAS_ERROR)
|
|
status = IDLE;
|
|
else
|
|
status = data->state;
|
|
check_irq_error(data, status, true, false);
|
|
}
|
|
|
|
static void sx938x_process_interrupt(struct sx938x_p *data)
|
|
{
|
|
u8 status = 0;
|
|
|
|
/* since we are not in an interrupt don't need to disable irq. */
|
|
status = sx938x_read_reg_stat(data);
|
|
|
|
GRIP_INFO("status %d\n", status);
|
|
|
|
if (status & IRQ_PROCESS_CONDITION)
|
|
sx938x_touch_process(data);
|
|
}
|
|
|
|
static void sx938x_init_work_func(struct work_struct *work)
|
|
{
|
|
struct sx938x_p *data = container_of((struct delayed_work *)work,
|
|
struct sx938x_p, init_work);
|
|
|
|
if (data && !data->check_abnormal_working) {
|
|
GRIP_INFO("initialize\n");
|
|
|
|
sx938x_initialize_register(data);
|
|
|
|
if (!data->check_abnormal_working) {
|
|
msleep(100);
|
|
sx938x_manual_offset_calibration(data);
|
|
}
|
|
|
|
sx938x_set_mode(data, SX938x_MODE_NORMAL);
|
|
|
|
sx938x_read_reg_stat(data);
|
|
sx938x_get_gain(data);
|
|
}
|
|
return;
|
|
}
|
|
|
|
static void sx938x_irq_work_func(struct work_struct *work)
|
|
{
|
|
struct sx938x_p *data = container_of((struct delayed_work *)work,
|
|
struct sx938x_p, irq_work);
|
|
|
|
if (sx938x_get_nirq_state(data) == INTERRUPT_LOW)
|
|
sx938x_process_interrupt(data);
|
|
else
|
|
GRIP_ERR("nirq read high %d\n", sx938x_get_nirq_state(data));
|
|
}
|
|
|
|
static void sx938x_check_first_working(struct sx938x_p *data)
|
|
{
|
|
if (data->noti_enable && data->motion) {
|
|
if (data->detect_threshold < data->diff) {
|
|
data->first_working = true;
|
|
GRIP_INFO("first working detected %d\n", data->diff);
|
|
} else {
|
|
if (data->first_working) {
|
|
data->is_unknown_mode = UNKNOWN_OFF;
|
|
GRIP_INFO("Release detected %d, unknown mode off\n", data->diff);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void sx938x_debug_work_func(struct work_struct *work)
|
|
{
|
|
struct sx938x_p *data = container_of((struct delayed_work *)work,
|
|
struct sx938x_p, debug_work);
|
|
int ret = 0;
|
|
|
|
if ((atomic_read(&data->enable) == ON)
|
|
&& (data->abnormal_mode)){
|
|
ret = sx938x_get_data(data);
|
|
if (data->max_normal_diff < data->diff && !ret)
|
|
data->max_normal_diff = data->diff;
|
|
} else if (data->debug_count >= GRIP_LOG_TIME) {
|
|
ret = sx938x_get_data(data);
|
|
if (data->fail_status_code != 0 && !ret)
|
|
GRIP_ERR("ret %d err %d", ret, data->fail_status_code);
|
|
if (data->is_unknown_mode == UNKNOWN_ON && data->motion)
|
|
sx938x_check_first_working(data);
|
|
data->debug_count = 0;
|
|
} else {
|
|
data->debug_count++;
|
|
}
|
|
|
|
GRIP_ERR("ret %d en %d", !ret?1:0, (atomic_read(&data->enable) == ON)?1:0);
|
|
|
|
if ((atomic_read(&data->enable) == ON) && !ret) {
|
|
u8 status = 0;
|
|
ret = sx938x_i2c_read(data, SX938x_STAT_REG, &status);
|
|
GRIP_ERR("ret %d status %u", ret, status & SX938x_PROXSTAT_FLAG);
|
|
if (ret >= 0) {
|
|
status = status & SX938x_PROXSTAT_FLAG;
|
|
if (status)
|
|
status = ACTIVE;
|
|
else
|
|
status = IDLE;
|
|
check_irq_error(data, status, false, false);
|
|
}
|
|
}
|
|
|
|
schedule_delayed_work(&data->debug_work, msecs_to_jiffies(2000));
|
|
}
|
|
|
|
static irqreturn_t sx938x_interrupt_thread(int irq, void *pdata)
|
|
{
|
|
struct sx938x_p *data = pdata;
|
|
|
|
__pm_wakeup_event(data->grip_ws, jiffies_to_msecs(3 * HZ));
|
|
schedule_delayed_work(&data->irq_work, msecs_to_jiffies(100));
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int sx938x_input_init(struct sx938x_p *data)
|
|
{
|
|
int ret = 0;
|
|
struct input_dev *dev = NULL;
|
|
|
|
/* Create the input device */
|
|
dev = input_allocate_device();
|
|
if (!dev)
|
|
return -ENOMEM;
|
|
|
|
dev->name = module_name[data->ic_num];
|
|
dev->id.bustype = BUS_I2C;
|
|
|
|
input_set_capability(dev, EV_REL, REL_MISC);
|
|
input_set_capability(dev, EV_REL, REL_X);
|
|
input_set_drvdata(dev, data);
|
|
|
|
ret = input_register_device(dev);
|
|
if (ret < 0) {
|
|
input_free_device(dev);
|
|
return ret;
|
|
}
|
|
|
|
#if IS_ENABLED(CONFIG_SENSORS_CORE_AP)
|
|
ret = sensors_create_symlink(&dev->dev.kobj, dev->name);
|
|
if (ret < 0) {
|
|
GRIP_ERR("fail to create symlink %d\n", ret);
|
|
input_unregister_device(dev);
|
|
return ret;
|
|
}
|
|
|
|
ret = sysfs_create_group(&dev->dev.kobj, &sx938x_attribute_group);
|
|
if (ret < 0) {
|
|
GRIP_ERR("fail to create sysfs group %d\n", ret);
|
|
sensors_remove_symlink(&data->input->dev.kobj, data->input->name);
|
|
input_unregister_device(dev);
|
|
return ret;
|
|
}
|
|
#else
|
|
ret = sensors_create_symlink(dev);
|
|
if (ret < 0) {
|
|
GRIP_ERR("fail to create symlink %d\n", ret);
|
|
input_unregister_device(dev);
|
|
return ret;
|
|
}
|
|
|
|
ret = sysfs_create_group(&dev->dev.kobj, &sx938x_attribute_group);
|
|
if (ret < 0) {
|
|
sensors_remove_symlink(dev);
|
|
input_unregister_device(dev);
|
|
return ret;
|
|
}
|
|
#endif
|
|
/* save the input pointer and finish initialization */
|
|
data->input = dev;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sx938x_noti_input_init(struct sx938x_p *data)
|
|
{
|
|
int ret = 0;
|
|
struct input_dev *noti_input_dev = NULL;
|
|
|
|
if (data->unknown_sel) {
|
|
/* Create the input device */
|
|
noti_input_dev = input_allocate_device();
|
|
if (!noti_input_dev) {
|
|
GRIP_ERR("input_allocate_device fail\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
noti_input_dev->name = NOTI_MODULE_NAME;
|
|
noti_input_dev->id.bustype = BUS_I2C;
|
|
|
|
input_set_capability(noti_input_dev, EV_REL, REL_X);
|
|
input_set_drvdata(noti_input_dev, data);
|
|
|
|
ret = input_register_device(noti_input_dev);
|
|
if (ret < 0) {
|
|
GRIP_ERR("fail to regi input dev for noti %d\n", ret);
|
|
input_free_device(noti_input_dev);
|
|
return ret;
|
|
}
|
|
|
|
data->noti_input_dev = noti_input_dev;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sx938x_setup_pin(struct sx938x_p *data)
|
|
{
|
|
int ret;
|
|
|
|
ret = gpio_request(data->gpio_nirq, "SX938X_nIRQ");
|
|
if (ret < 0) {
|
|
GRIP_ERR("gpio %d req fail %d\n", data->gpio_nirq, ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = gpio_direction_input(data->gpio_nirq);
|
|
if (ret < 0) {
|
|
GRIP_ERR("fail to set gpio %d as input %d\n", data->gpio_nirq, ret);
|
|
gpio_free(data->gpio_nirq);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
/* There is an issue that a grip address depends on the sequence of power and IRQ. So, Add power control function */
|
|
static int sx938x_power_onoff(struct sx938x_p *data, bool on)
|
|
{
|
|
int ret = 0;
|
|
int voltage = 0;
|
|
int reg_enabled = 0;
|
|
|
|
if (data->ldo_en) {
|
|
ret = gpio_request(data->ldo_en, "sx938x_ldo_en");
|
|
if (ret < 0) {
|
|
GRIP_ERR("gpio %d req fail %d\n", data->ldo_en, ret);
|
|
return ret;
|
|
}
|
|
gpio_set_value(data->ldo_en, on);
|
|
GRIP_INFO("ldo_en power %d\n", on);
|
|
gpio_free(data->ldo_en);
|
|
}
|
|
|
|
if (data->dvdd_vreg_name) {
|
|
if (data->dvdd_vreg == NULL) {
|
|
data->dvdd_vreg = regulator_get(NULL, data->dvdd_vreg_name);
|
|
if (IS_ERR(data->dvdd_vreg)) {
|
|
data->dvdd_vreg = NULL;
|
|
GRIP_ERR("fail to get dvdd_vreg %s\n", data->dvdd_vreg_name);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (data->dvdd_vreg) {
|
|
voltage = regulator_get_voltage(data->dvdd_vreg);
|
|
reg_enabled = regulator_is_enabled(data->dvdd_vreg);
|
|
GRIP_INFO("dvdd_vreg reg_enabled=%d voltage=%d\n", reg_enabled, voltage);
|
|
}
|
|
|
|
if (on) {
|
|
if (data->dvdd_vreg) {
|
|
if (reg_enabled == 0) {
|
|
ret = regulator_enable(data->dvdd_vreg);
|
|
if (ret) {
|
|
GRIP_ERR("dvdd reg enable fail\n");
|
|
return ret;
|
|
}
|
|
GRIP_INFO("dvdd_vreg turn on\n");
|
|
}
|
|
}
|
|
} else {
|
|
if (data->dvdd_vreg) {
|
|
if (reg_enabled == 1) {
|
|
ret = regulator_disable(data->dvdd_vreg);
|
|
if (ret) {
|
|
GRIP_ERR("dvdd reg disable fail\n");
|
|
return ret;
|
|
}
|
|
GRIP_INFO("dvdd_vreg turn off\n");
|
|
}
|
|
}
|
|
}
|
|
GRIP_INFO("%s\n", on ? "on" : "off");
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static void sx938x_initialize_variable(struct sx938x_p *data)
|
|
{
|
|
data->init_done = OFF;
|
|
data->skip_data = false;
|
|
data->state = IDLE;
|
|
data->fail_status_code = 0;
|
|
data->pre_attach = -1;
|
|
|
|
data->is_unknown_mode = UNKNOWN_OFF;
|
|
data->motion = 1;
|
|
data->first_working = false;
|
|
|
|
atomic_set(&data->enable, OFF);
|
|
}
|
|
|
|
|
|
static int sx938x_read_setupreg(struct sx938x_p *data, struct device_node *dnode, const char *str, int idx)
|
|
{
|
|
u32 temp_val;
|
|
int ret;
|
|
|
|
ret = of_property_read_u32(dnode, str, &temp_val);
|
|
if (!ret) {
|
|
setup_reg[data->ic_num][idx].val = (u8)temp_val;
|
|
GRIP_INFO("reg debug str %s add 0x%2x val 0x%2x",
|
|
str, setup_reg[data->ic_num][idx].reg,
|
|
setup_reg[data->ic_num][idx].val);
|
|
} else if (ret == -22)
|
|
GRIP_ERR("%s default 0x%2x %d\n", str, temp_val, ret);
|
|
else {
|
|
GRIP_ERR("%s property read err 0x%2x %d\n", str, temp_val, ret);
|
|
data->fail_status_code = SX938x_REG_ERROR;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int sx938x_check_dependency(struct device *dev, int ic_num)
|
|
{
|
|
struct device_node *dNode = dev->of_node;
|
|
struct regulator *dvdd_vreg = NULL; /* regulator */
|
|
char *dvdd_vreg_name = NULL; /* regulator name */
|
|
|
|
if (ic_num == MAIN_GRIP) {
|
|
if (of_property_read_string_index(dNode, "sx938x,dvdd_vreg_name", 0,
|
|
(const char **)&dvdd_vreg_name)) {
|
|
dvdd_vreg_name = NULL;
|
|
}
|
|
}
|
|
#if IS_ENABLED(CONFIG_SENSORS_SX9380_SUB)
|
|
if (ic_num == SUB_GRIP) {
|
|
if (of_property_read_string_index(dNode, "sx938x_sub,dvdd_vreg_name", 0,
|
|
(const char **)&dvdd_vreg_name)) {
|
|
dvdd_vreg_name = NULL;
|
|
}
|
|
}
|
|
#endif
|
|
#if IS_ENABLED(CONFIG_SENSORS_SX9380_SUB2)
|
|
if (ic_num == SUB2_GRIP) {
|
|
if (of_property_read_string_index(dNode, "sx938x_sub2,dvdd_vreg_name", 0,
|
|
(const char **)&dvdd_vreg_name)) {
|
|
dvdd_vreg_name = NULL;
|
|
}
|
|
}
|
|
#endif
|
|
#if IS_ENABLED(CONFIG_SENSORS_SX9380_WIFI)
|
|
if (ic_num == WIFI_GRIP) {
|
|
if (of_property_read_string_index(dNode, "sx938x_wifi,dvdd_vreg_name", 0,
|
|
(const char **)&dvdd_vreg_name)) {
|
|
dvdd_vreg_name = NULL;
|
|
}
|
|
}
|
|
#endif
|
|
pr_info("[GRIP_%d] dvdd_vreg_name %s\n", ic_num, dvdd_vreg_name);
|
|
|
|
if (dvdd_vreg_name) {
|
|
if (dvdd_vreg == NULL) {
|
|
dvdd_vreg = regulator_get(NULL, dvdd_vreg_name);
|
|
if (IS_ERR(dvdd_vreg)) {
|
|
pr_info("[GRIP_%d] %s\n", ic_num, __func__);
|
|
return -EPROBE_DEFER;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sx938x_parse_dt(struct sx938x_p *data, struct device *dev)
|
|
{
|
|
struct device_node *dNode = dev->of_node;
|
|
enum of_gpio_flags flags;
|
|
int reg_size = 0, ret = 0;
|
|
int i;
|
|
|
|
if (dNode == NULL)
|
|
return -ENODEV;
|
|
|
|
if (data->ic_num == MAIN_GRIP) {
|
|
#if IS_ENABLED(CONFIG_SENSORS_COMMON_VDD_SUB)
|
|
data->gpio_nirq_sub = of_get_named_gpio_flags(dNode,
|
|
"sx938x,nirq_gpio_sub", 0, &flags);
|
|
if (data->gpio_nirq_sub < 0)
|
|
GRIP_ERR("nirq_gpio_sub is null\n");
|
|
else
|
|
GRIP_INFO("get nirq_gpio_sub %d\n", data->gpio_nirq_sub);
|
|
#endif
|
|
data->gpio_nirq = of_get_named_gpio_flags(dNode,
|
|
"sx938x,nirq-gpio", 0, &flags);
|
|
ret = of_property_read_u32(dNode, "sx938x,unknown_sel", &data->unknown_sel);
|
|
#if IS_ENABLED(CONFIG_SENSORS_SX9380_SUB)
|
|
} else if (data->ic_num == SUB_GRIP) {
|
|
data->gpio_nirq = of_get_named_gpio_flags(dNode, "sx938x_sub,nirq-gpio", 0, &flags);
|
|
ret = of_property_read_u32(dNode, "sx938x_sub,unknown_sel", &data->unknown_sel);
|
|
#endif
|
|
#if IS_ENABLED(CONFIG_SENSORS_SX9380_SUB2)
|
|
} else if (data->ic_num == SUB2_GRIP) {
|
|
data->gpio_nirq = of_get_named_gpio_flags(dNode, "sx938x_sub2,nirq-gpio", 0, &flags);
|
|
ret = of_property_read_u32(dNode, "sx938x_sub2,unknown_sel", &data->unknown_sel);
|
|
#endif
|
|
#if IS_ENABLED(CONFIG_SENSORS_SX9380_WIFI)
|
|
} else if (data->ic_num == WIFI_GRIP) {
|
|
data->gpio_nirq = of_get_named_gpio_flags(dNode, "sx938x_wifi,nirq-gpio", 0, &flags);
|
|
ret = of_property_read_u32(dNode, "sx938x_wifi,unknown_sel", &data->unknown_sel);
|
|
#endif
|
|
}
|
|
|
|
if (ret < 0) {
|
|
GRIP_ERR("unknown_sel read fail %d\n", ret);
|
|
data->unknown_sel = 1;
|
|
ret = 0;
|
|
}
|
|
|
|
GRIP_INFO("unknown_sel %d\n", data->unknown_sel);
|
|
|
|
if (data->gpio_nirq < 0) {
|
|
GRIP_ERR("get gpio_nirq err\n");
|
|
return -ENODEV;
|
|
} else {
|
|
GRIP_INFO("get gpio_nirq %d\n", data->gpio_nirq);
|
|
}
|
|
|
|
if (data->ic_num == MAIN_GRIP)
|
|
data->ldo_en = of_get_named_gpio_flags(dNode, "sx938x,ldo_en", 0, &flags);
|
|
#if IS_ENABLED(CONFIG_SENSORS_SX9380_SUB)
|
|
if (data->ic_num == SUB_GRIP)
|
|
data->ldo_en = of_get_named_gpio_flags(dNode, "sx938x_sub,ldo_en", 0, &flags);
|
|
#endif
|
|
#if IS_ENABLED(CONFIG_SENSORS_SX9380_SUB2)
|
|
if (data->ic_num == SUB2_GRIP)
|
|
data->ldo_en = of_get_named_gpio_flags(dNode, "sx938x_sub2,ldo_en", 0, &flags);
|
|
#endif
|
|
#if IS_ENABLED(CONFIG_SENSORS_SX9380_WIFI)
|
|
if (data->ic_num == WIFI_GRIP)
|
|
data->ldo_en = of_get_named_gpio_flags(dNode, "sx938x_wifi,ldo_en", 0, &flags);
|
|
#endif
|
|
|
|
if (data->ldo_en < 0) {
|
|
GRIP_ERR("skip ldo_en\n");
|
|
data->ldo_en = 0;
|
|
} else {
|
|
if (data->ic_num == MAIN_GRIP)
|
|
ret = gpio_request(data->ldo_en, "sx938x_ldo_en");
|
|
#if IS_ENABLED(CONFIG_SENSORS_SX9380_SUB)
|
|
if (data->ic_num == SUB_GRIP)
|
|
ret = gpio_request(data->ldo_en, "sx938x_sub_ldo_en");
|
|
#endif
|
|
#if IS_ENABLED(CONFIG_SENSORS_SX9380_SUB2)
|
|
if (data->ic_num == SUB2_GRIP)
|
|
ret = gpio_request(data->ldo_en, "sx938x_sub2_ldo_en");
|
|
#endif
|
|
#if IS_ENABLED(CONFIG_SENSORS_SX9380_WIFI)
|
|
if (data->ic_num == WIFI_GRIP)
|
|
ret = gpio_request(data->ldo_en, "sx938x_wifi_ldo_en");
|
|
#endif
|
|
if (ret < 0) {
|
|
GRIP_ERR("gpio %d req fail %d\n", data->ldo_en, ret);
|
|
return ret;
|
|
}
|
|
gpio_direction_output(data->ldo_en, 0);
|
|
gpio_free(data->ldo_en);
|
|
}
|
|
|
|
if (data->ic_num == MAIN_GRIP) {
|
|
if (of_property_read_string_index(dNode, "sx938x,dvdd_vreg_name", 0,
|
|
(const char **)&data->dvdd_vreg_name)) {
|
|
data->dvdd_vreg_name = NULL;
|
|
}
|
|
}
|
|
#if IS_ENABLED(CONFIG_SENSORS_SX9380_SUB)
|
|
if (data->ic_num == SUB_GRIP) {
|
|
if (of_property_read_string_index(dNode, "sx938x_sub,dvdd_vreg_name", 0,
|
|
(const char **)&data->dvdd_vreg_name)) {
|
|
data->dvdd_vreg_name = NULL;
|
|
}
|
|
}
|
|
#endif
|
|
#if IS_ENABLED(CONFIG_SENSORS_SX9380_SUB2)
|
|
if (data->ic_num == SUB2_GRIP) {
|
|
if (of_property_read_string_index(dNode, "sx938x_sub2,dvdd_vreg_name", 0,
|
|
(const char **)&data->dvdd_vreg_name)) {
|
|
data->dvdd_vreg_name = NULL;
|
|
}
|
|
}
|
|
#endif
|
|
#if IS_ENABLED(CONFIG_SENSORS_SX9380_WIFI)
|
|
if (data->ic_num == WIFI_GRIP) {
|
|
if (of_property_read_string_index(dNode, "sx938x_wifi,dvdd_vreg_name", 0,
|
|
(const char **)&data->dvdd_vreg_name)) {
|
|
data->dvdd_vreg_name = NULL;
|
|
}
|
|
}
|
|
#endif
|
|
GRIP_INFO("dvdd_vreg_name %s\n", data->dvdd_vreg_name);
|
|
|
|
if (data->ic_num == MAIN_GRIP)
|
|
reg_size = sizeof(sx938x_parse_reg) / sizeof(char *);
|
|
#if IS_ENABLED(CONFIG_SENSORS_SX9380_SUB)
|
|
else if (data->ic_num == SUB_GRIP)
|
|
reg_size = sizeof(sx938x_sub_parse_reg) / sizeof(char *);
|
|
#endif
|
|
#if IS_ENABLED(CONFIG_SENSORS_SX9380_SUB2)
|
|
else if (data->ic_num == SUB2_GRIP)
|
|
reg_size = sizeof(sx938x_sub2_parse_reg) / sizeof(char *);
|
|
#endif
|
|
#if IS_ENABLED(CONFIG_SENSORS_SX9380_WIFI)
|
|
else if (data->ic_num == WIFI_GRIP)
|
|
reg_size = sizeof(sx938x_wifi_parse_reg) / sizeof(char *);
|
|
#endif
|
|
for (i = 0; i < reg_size; i++) {
|
|
if (data->ic_num == MAIN_GRIP)
|
|
sx938x_read_setupreg(data, dNode, sx938x_parse_reg[i], i);
|
|
#if IS_ENABLED(CONFIG_SENSORS_SX9380_SUB)
|
|
else if (data->ic_num == SUB_GRIP)
|
|
sx938x_read_setupreg(data, dNode, sx938x_sub_parse_reg[i], i);
|
|
#endif
|
|
#if IS_ENABLED(CONFIG_SENSORS_SX9380_SUB2)
|
|
else if (data->ic_num == SUB2_GRIP)
|
|
sx938x_read_setupreg(data, dNode, sx938x_sub2_parse_reg[i], i);
|
|
#endif
|
|
#if IS_ENABLED(CONFIG_SENSORS_SX9380_WIFI)
|
|
else if (data->ic_num == WIFI_GRIP)
|
|
sx938x_read_setupreg(data, dNode, sx938x_wifi_parse_reg[i], i);
|
|
#endif
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) && IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER)
|
|
static int sx938x_pdic_handle_notification(struct notifier_block *nb,
|
|
unsigned long action, void *pdic_data)
|
|
{
|
|
PD_NOTI_ATTACH_TYPEDEF usb_typec_info = *(PD_NOTI_ATTACH_TYPEDEF *)pdic_data;
|
|
struct sx938x_p *data = container_of(nb, struct sx938x_p, pdic_nb);
|
|
|
|
if (usb_typec_info.id != PDIC_NOTIFY_ID_ATTACH)
|
|
return 0;
|
|
|
|
if (data->pre_attach == usb_typec_info.attach)
|
|
return 0;
|
|
|
|
GRIP_INFO("src %d id %d attach %d rprd %d\n",
|
|
usb_typec_info.src, usb_typec_info.id, usb_typec_info.attach, usb_typec_info.rprd);
|
|
|
|
if (data->init_done == ON) {
|
|
enter_unknown_mode(data, TYPE_USB);
|
|
sx938x_manual_offset_calibration(data);
|
|
}
|
|
|
|
data->pre_attach = usb_typec_info.attach;
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#if IS_ENABLED(CONFIG_HALL_NOTIFIER)
|
|
static int sx938x_hall_notifier(struct notifier_block *nb,
|
|
unsigned long action, void *hall_data)
|
|
{
|
|
struct hall_notifier_context *hall_notifier;
|
|
struct sx938x_p *data =
|
|
container_of(nb, struct sx938x_p, hall_nb);
|
|
hall_notifier = hall_data;
|
|
|
|
if (action == HALL_ATTACH) {
|
|
GRIP_INFO("%s attach\n", hall_notifier->name);
|
|
enter_unknown_mode(data, TYPE_HALL);
|
|
sx938x_manual_offset_calibration(data);
|
|
} else {
|
|
enter_unknown_mode(data, TYPE_HALL);
|
|
return 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static int sx938x_probe(struct i2c_client *client, const struct i2c_device_id *id)
|
|
{
|
|
int ret = -ENODEV;
|
|
struct sx938x_p *data = NULL;
|
|
int ic_num = 0;
|
|
|
|
if (strcmp(client->name, "sx938x") == 0)
|
|
ic_num = MAIN_GRIP;
|
|
#if IS_ENABLED(CONFIG_SENSORS_SX9380_SUB)
|
|
else if (strcmp(client->name, "sx938x_sub") == 0)
|
|
ic_num = SUB_GRIP;
|
|
#endif
|
|
#if IS_ENABLED(CONFIG_SENSORS_SX9380_SUB2)
|
|
else if (strcmp(client->name, "sx938x_sub2") == 0)
|
|
ic_num = SUB2_GRIP;
|
|
#endif
|
|
#if IS_ENABLED(CONFIG_SENSORS_SX9380_WIFI)
|
|
else if (strcmp(client->name, "sx938x_wifi") == 0)
|
|
ic_num = WIFI_GRIP;
|
|
#endif
|
|
else {
|
|
pr_err("[GRIP] name %s can't find grip ic num", client->name);
|
|
return -1;
|
|
}
|
|
pr_info("[GRIP_%s] %s start 0x%x\n", grip_name[ic_num], __func__, client->addr);
|
|
|
|
ret = sx938x_check_dependency(&client->dev, ic_num);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
|
|
pr_info("[GRIP_%s] i2c func err\n", grip_name[ic_num]);
|
|
goto exit;
|
|
}
|
|
|
|
data = kzalloc(sizeof(struct sx938x_p), GFP_KERNEL);
|
|
if (data == NULL) {
|
|
pr_info("[GRIP_%s] Fail to mem alloc\n", grip_name[ic_num]);
|
|
ret = -ENOMEM;
|
|
goto exit_kzalloc;
|
|
}
|
|
|
|
data->ic_num = ic_num;
|
|
i2c_set_clientdata(client, data);
|
|
data->client = client;
|
|
data->factory_device = &client->dev;
|
|
|
|
ret = sx938x_input_init(data);
|
|
if (ret < 0)
|
|
goto exit_input_init;
|
|
|
|
data->grip_ws = wakeup_source_register(&client->dev, "grip_wake_lock");
|
|
mutex_init(&data->mode_mutex);
|
|
mutex_init(&data->read_mutex);
|
|
|
|
ret = sx938x_parse_dt(data, &client->dev);
|
|
if (ret < 0) {
|
|
GRIP_ERR("of_node err\n");
|
|
ret = -ENODEV;
|
|
goto exit_of_node;
|
|
}
|
|
ret = sx938x_noti_input_init(data);
|
|
if (ret < 0)
|
|
goto exit_noti_input_init;
|
|
|
|
//If Host NIRQ pin has a protection diode and PULL UP (state HIGH), addr can be 0x29 or 0x2A
|
|
//However, if the state of the pin is LOW, 0x28 is guaranteed.
|
|
#if IS_ENABLED(CONFIG_SENSORS_COMMON_VDD_SUB)
|
|
if (ic_num == SUB_GRIP) {
|
|
GRIP_INFO("skip irq outmode\n");
|
|
} else {
|
|
ret = gpio_direction_output(data->gpio_nirq, 0);
|
|
if (ret < 0)
|
|
GRIP_ERR("could not setup outmode\n");
|
|
if (ic_num == MAIN_GRIP) {
|
|
ret = gpio_direction_output(data->gpio_nirq_sub, 0);
|
|
if (ret < 0)
|
|
GRIP_ERR("could not setup outmode(sub)\n");
|
|
}
|
|
usleep_range(1000, 1100);
|
|
sx938x_power_onoff(data, 1);
|
|
usleep_range(6000, 6100); // Tpor > 5ms
|
|
}
|
|
#else
|
|
ret = gpio_direction_output(data->gpio_nirq, 0);
|
|
if (ret < 0)
|
|
GRIP_ERR("could not setup outmode\n");
|
|
usleep_range(1000, 1100);
|
|
|
|
sx938x_power_onoff(data, 1);
|
|
usleep_range(6000, 6100); // Tpor > 5ms
|
|
#endif
|
|
|
|
ret = sx938x_setup_pin(data);
|
|
if (ret) {
|
|
GRIP_ERR("could not setup pin\n");
|
|
goto exit_setup_pin;
|
|
}
|
|
|
|
/* read chip id */
|
|
ret = sx938x_hardware_check(data);
|
|
if (ret < 0) {
|
|
GRIP_ERR("chip id check fail %d\n", ret);
|
|
goto exit_chip_reset;
|
|
}
|
|
|
|
sx938x_initialize_variable(data);
|
|
INIT_DELAYED_WORK(&data->init_work, sx938x_init_work_func);
|
|
INIT_DELAYED_WORK(&data->irq_work, sx938x_irq_work_func);
|
|
INIT_DELAYED_WORK(&data->debug_work, sx938x_debug_work_func);
|
|
|
|
data->irq = gpio_to_irq(data->gpio_nirq);
|
|
/* initailize interrupt reporting */
|
|
ret = request_threaded_irq(data->irq, NULL, sx938x_interrupt_thread,
|
|
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
|
|
device_name[data->ic_num], data);
|
|
if (ret < 0) {
|
|
GRIP_ERR("fail to req thread irq %d ret %d\n", data->irq, ret);
|
|
goto exit_request_threaded_irq;
|
|
}
|
|
disable_irq(data->irq);
|
|
data->is_irq_active = false;
|
|
|
|
ret = sensors_register(&data->factory_device, data, sensor_attrs, (char *)module_name[data->ic_num]);
|
|
if (ret) {
|
|
GRIP_ERR("could not regi sensor %d\n", ret);
|
|
goto exit_register_failed;
|
|
}
|
|
|
|
schedule_delayed_work(&data->init_work, msecs_to_jiffies(300));
|
|
sx938x_set_debug_work(data, ON, 20000);
|
|
|
|
#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) && IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER)
|
|
GRIP_INFO("regi pdic notifier\n");
|
|
manager_notifier_register(&data->pdic_nb,
|
|
sx938x_pdic_handle_notification,
|
|
MANAGER_NOTIFY_PDIC_SENSORHUB);
|
|
#endif
|
|
#if IS_ENABLED(CONFIG_HALL_NOTIFIER)
|
|
GRIP_INFO("regi hall notifier\n");
|
|
data->hall_nb.priority = 1;
|
|
data->hall_nb.notifier_call = sx938x_hall_notifier;
|
|
hall_notifier_register(&data->hall_nb);
|
|
#endif
|
|
|
|
GRIP_INFO("done!\n");
|
|
|
|
return 0;
|
|
exit_register_failed:
|
|
sensors_unregister(data->factory_device, sensor_attrs);
|
|
free_irq(data->irq, data);
|
|
exit_request_threaded_irq:
|
|
exit_chip_reset:
|
|
gpio_free(data->gpio_nirq);
|
|
exit_setup_pin:
|
|
if (data->unknown_sel)
|
|
input_unregister_device(data->noti_input_dev);
|
|
exit_noti_input_init:
|
|
exit_of_node:
|
|
mutex_destroy(&data->mode_mutex);
|
|
mutex_destroy(&data->read_mutex);
|
|
wakeup_source_unregister(data->grip_ws);
|
|
sysfs_remove_group(&data->input->dev.kobj, &sx938x_attribute_group);
|
|
#if IS_ENABLED(CONFIG_SENSORS_CORE_AP)
|
|
sensors_remove_symlink(&data->input->dev.kobj, data->input->name);
|
|
#else
|
|
sensors_remove_symlink(data->input);
|
|
#endif
|
|
input_unregister_device(data->input);
|
|
exit_input_init:
|
|
kfree(data);
|
|
exit_kzalloc:
|
|
exit:
|
|
pr_err("[GRIP_%s] Probe fail!\n", grip_name[ic_num]);
|
|
return ret;
|
|
|
|
}
|
|
|
|
static int sx938x_remove(struct i2c_client *client)
|
|
{
|
|
struct sx938x_p *data = (struct sx938x_p *)i2c_get_clientdata(client);
|
|
|
|
if (atomic_read(&data->enable) == ON)
|
|
sx938x_set_enable(data, OFF);
|
|
|
|
sx938x_set_mode(data, SX938x_MODE_SLEEP);
|
|
|
|
cancel_delayed_work_sync(&data->init_work);
|
|
cancel_delayed_work_sync(&data->irq_work);
|
|
cancel_delayed_work_sync(&data->debug_work);
|
|
free_irq(data->irq, data);
|
|
gpio_free(data->gpio_nirq);
|
|
|
|
wakeup_source_unregister(data->grip_ws);
|
|
sensors_unregister(data->factory_device, sensor_attrs);
|
|
#if IS_ENABLED(CONFIG_SENSORS_CORE_AP)
|
|
sensors_remove_symlink(&data->input->dev.kobj, data->input->name);
|
|
#else
|
|
sensors_remove_symlink(data->input);
|
|
#endif
|
|
sysfs_remove_group(&data->input->dev.kobj, &sx938x_attribute_group);
|
|
input_unregister_device(data->input);
|
|
input_unregister_device(data->noti_input_dev);
|
|
mutex_destroy(&data->mode_mutex);
|
|
mutex_destroy(&data->read_mutex);
|
|
|
|
kfree(data);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sx938x_suspend(struct device *dev)
|
|
{
|
|
struct sx938x_p *data = dev_get_drvdata(dev);
|
|
int cnt = 0;
|
|
|
|
GRIP_INFO("\n");
|
|
/* before go to sleep, make the interrupt pin as high*/
|
|
while ((sx938x_get_nirq_state(data) == INTERRUPT_LOW) && (cnt++ < 3)) {
|
|
sx938x_read_reg_stat(data);
|
|
msleep(20);
|
|
}
|
|
if (cnt >= 3)
|
|
GRIP_ERR("s/w reset fail(%d)\n", cnt);
|
|
|
|
sx938x_set_debug_work(data, OFF, 1000);
|
|
|
|
return 0;
|
|
}
|
|
static int sx938x_resume(struct device *dev)
|
|
{
|
|
struct sx938x_p *data = dev_get_drvdata(dev);
|
|
|
|
GRIP_INFO("\n");
|
|
sx938x_set_debug_work(data, ON, 1000);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void sx938x_shutdown(struct i2c_client *client)
|
|
{
|
|
struct sx938x_p *data = i2c_get_clientdata(client);
|
|
|
|
GRIP_INFO("\n");
|
|
sx938x_set_debug_work(data, OFF, 1000);
|
|
if (atomic_read(&data->enable) == ON)
|
|
sx938x_set_enable(data, OFF);
|
|
|
|
sx938x_set_mode(data, SX938x_MODE_SLEEP);
|
|
}
|
|
|
|
|
|
static const struct of_device_id sx938x_match_table[] = {
|
|
{ .compatible = "sx938x",},
|
|
{},
|
|
};
|
|
|
|
static const struct i2c_device_id sx938x_id[] = {
|
|
{ "SX938X", 0 },
|
|
{ }
|
|
};
|
|
|
|
static const struct dev_pm_ops sx938x_pm_ops = {
|
|
.suspend = sx938x_suspend,
|
|
.resume = sx938x_resume,
|
|
};
|
|
|
|
static struct i2c_driver sx938x_driver = {
|
|
.driver = {
|
|
.name = "SX9380",
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = sx938x_match_table,
|
|
.pm = &sx938x_pm_ops
|
|
},
|
|
.probe = sx938x_probe,
|
|
.remove = sx938x_remove,
|
|
.shutdown = sx938x_shutdown,
|
|
.id_table = sx938x_id,
|
|
};
|
|
|
|
#if IS_ENABLED(CONFIG_SENSORS_SX9380_SUB)
|
|
static const struct of_device_id sx938x_sub_match_table[] = {
|
|
{ .compatible = "sx938x_sub",},
|
|
{},
|
|
};
|
|
|
|
static const struct i2c_device_id sx938x_sub_id[] = {
|
|
{ "SX938X_SUB", 0 },
|
|
{ }
|
|
};
|
|
|
|
static struct i2c_driver sx938x_sub_driver = {
|
|
.driver = {
|
|
.name = "SX9380_SUB",
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = sx938x_sub_match_table,
|
|
.pm = &sx938x_pm_ops
|
|
},
|
|
.probe = sx938x_probe,
|
|
.remove = sx938x_remove,
|
|
.shutdown = sx938x_shutdown,
|
|
.id_table = sx938x_sub_id,
|
|
};
|
|
#endif
|
|
|
|
#if IS_ENABLED(CONFIG_SENSORS_SX9380_SUB2)
|
|
static const struct of_device_id sx938x_sub2_match_table[] = {
|
|
{ .compatible = "sx938x_sub2",},
|
|
{},
|
|
};
|
|
|
|
static const struct i2c_device_id sx938x_sub2_id[] = {
|
|
{ "SX938X_SUB2", 0 },
|
|
{ }
|
|
};
|
|
|
|
static struct i2c_driver sx938x_sub2_driver = {
|
|
.driver = {
|
|
.name = "SX9380_SUB2",
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = sx938x_sub2_match_table,
|
|
.pm = &sx938x_pm_ops
|
|
},
|
|
.probe = sx938x_probe,
|
|
.remove = sx938x_remove,
|
|
.shutdown = sx938x_shutdown,
|
|
.id_table = sx938x_sub2_id,
|
|
};
|
|
#endif
|
|
|
|
#if IS_ENABLED(CONFIG_SENSORS_SX9380_WIFI)
|
|
static const struct of_device_id sx938x_wifi_match_table[] = {
|
|
{ .compatible = "sx938x_wifi",},
|
|
{},
|
|
};
|
|
|
|
static const struct i2c_device_id sx938x_wifi_id[] = {
|
|
{ "SX938X_WIFI", 0 },
|
|
{ }
|
|
};
|
|
|
|
static struct i2c_driver sx938x_wifi_driver = {
|
|
.driver = {
|
|
.name = "SX9380_WIFI",
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = sx938x_wifi_match_table,
|
|
.pm = &sx938x_pm_ops
|
|
},
|
|
.probe = sx938x_probe,
|
|
.remove = sx938x_remove,
|
|
.shutdown = sx938x_shutdown,
|
|
.id_table = sx938x_wifi_id,
|
|
};
|
|
#endif
|
|
|
|
static int __init sx938x_init(void)
|
|
{
|
|
int ret = 0;
|
|
|
|
ret = i2c_add_driver(&sx938x_driver);
|
|
if (ret != 0)
|
|
pr_err("[GRIP] sx938x_driver probe fail\n");
|
|
#if IS_ENABLED(CONFIG_SENSORS_SX9380_SUB)
|
|
ret = i2c_add_driver(&sx938x_sub_driver);
|
|
if (ret != 0)
|
|
pr_err("[GRIP_SUB] sx938x_sub_driver probe fail\n");
|
|
#endif
|
|
#if IS_ENABLED(CONFIG_SENSORS_SX9380_SUB2)
|
|
ret = i2c_add_driver(&sx938x_sub2_driver);
|
|
if (ret != 0)
|
|
pr_err("[GRIP_SUB] sx938x_sub2_driver probe fail\n");
|
|
#endif
|
|
#if IS_ENABLED(CONFIG_SENSORS_SX9380_WIFI)
|
|
ret = i2c_add_driver(&sx938x_wifi_driver);
|
|
if (ret != 0)
|
|
pr_err("[GRIP_WIFI] sx938x_wifi_driver probe fail\n");
|
|
#endif
|
|
return ret;
|
|
|
|
}
|
|
|
|
static void __exit sx938x_exit(void)
|
|
{
|
|
i2c_del_driver(&sx938x_driver);
|
|
#if IS_ENABLED(CONFIG_SENSORS_SX9380_SUB)
|
|
i2c_del_driver(&sx938x_sub_driver);
|
|
#endif
|
|
#if IS_ENABLED(CONFIG_SENSORS_SX9380_SUB2)
|
|
i2c_del_driver(&sx938x_sub2_driver);
|
|
#endif
|
|
#if IS_ENABLED(CONFIG_SENSORS_SX9380_WIFI)
|
|
i2c_del_driver(&sx938x_wifi_driver);
|
|
#endif
|
|
|
|
}
|
|
|
|
module_init(sx938x_init);
|
|
module_exit(sx938x_exit);
|
|
|
|
|
|
MODULE_DESCRIPTION("Semtech Corp. SX9380 Capacitive Touch Controller Driver");
|
|
MODULE_AUTHOR("Samsung Electronics");
|
|
MODULE_LICENSE("GPL");
|
|
|
|
|