blob: ed3d84990d2788efd9a2e02fbc80fcfe7ce4bb1f [file] [log] [blame]
/*
* Copyright (C) 2018 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.
*/
#include "fingerprint.h"
#include "qbt2000_common.h"
static struct qbt2000_drvdata *g_data = NULL;
/*
* struct ipc_msg_type_to_fw_event -
* entry in mapping between an IPC message type to a firmware event
* @msg_type - IPC message type, as reported by firmware
* @fw_event - corresponding firmware event code to report to driver client
*/
struct ipc_msg_type_to_fw_event {
uint32_t msg_type;
enum qbt2000_fw_event fw_event;
};
/* mapping between firmware IPC message types to HLOS firmware events */
struct ipc_msg_type_to_fw_event g_msg_to_event[] = {
{IPC_MSG_ID_CBGE_REQUIRED, FW_EVENT_CBGE_REQUIRED},
{IPC_MSG_ID_FINGER_ON_SENSOR, FW_EVENT_FINGER_DOWN},
{IPC_MSG_ID_FINGER_OFF_SENSOR, FW_EVENT_FINGER_UP},
};
static ssize_t qbt2000_vendor_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%s\n", VENDOR);
}
static ssize_t qbt2000_name_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%s\n", CHIP_ID);
}
static ssize_t qbt2000_adm_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%d\n", DETECT_ADM);
}
static ssize_t qbt2000_bfs_values_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct qbt2000_drvdata *drvdata = dev_get_drvdata(dev);
return snprintf(buf, PAGE_SIZE, "\"FP_SPICLK\":\"%d\"\n",
drvdata->spi_speed);
}
static ssize_t qbt2000_type_check_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct qbt2000_drvdata *drvdata = dev_get_drvdata(dev);
pr_info("%s\n", sensor_status[drvdata->sensortype + 2]);
return snprintf(buf, PAGE_SIZE, "%d\n", drvdata->sensortype);
}
static ssize_t qbt2000_position_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct qbt2000_drvdata *drvdata = dev_get_drvdata(dev);
return snprintf(buf, PAGE_SIZE, "%s\n", drvdata->sensor_position);
}
static ssize_t qbt2000_cbgecnt_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct qbt2000_drvdata *drvdata = dev_get_drvdata(dev);
return snprintf(buf, PAGE_SIZE, "%d\n", drvdata->cbge_count);
}
static ssize_t qbt2000_cbgecnt_store(struct device *dev,
struct device_attribute *attr, const char *buf,
size_t size)
{
struct qbt2000_drvdata *drvdata = dev_get_drvdata(dev);
if (sysfs_streq(buf, "c")) {
drvdata->cbge_count = 0;
#ifdef QBT2000_AVOID_NOISE
drvdata->ignored_cbge_count = 0;
#endif
pr_info("initialization is done\n");
} else if (sysfs_streq(buf, "wuhb")) {
/* For User HwModuleTest IntTest */
drvdata->wuhb_test_flag = 1;
drvdata->wuhb_test_result = 0;
if (drvdata->fd_gpio.gpio) {
if (drvdata->enabled_wuhb) {
drvdata->wuhb_test_flag = 0;
drvdata->wuhb_test_result = -1;
pr_err("wuhb test procedure can not perform.\n");
} else {
enable_irq(drvdata->fd_gpio.irq);
drvdata->enabled_wuhb = true;
pr_info("wuhb test start.flag:%d,result:%d,en:%d\n",
drvdata->wuhb_test_flag,
drvdata->wuhb_test_result,
drvdata->enabled_wuhb);
}
} else {
drvdata->wuhb_test_flag = 0;
drvdata->wuhb_test_result = -1;
pr_err("fd_gpio does not supports this hw rev.\n");
}
}
return size;
}
static ssize_t qbt2000_intcnt_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct qbt2000_drvdata *drvdata = dev_get_drvdata(dev);
return snprintf(buf, PAGE_SIZE, "%d\n", drvdata->wuhb_count);
}
static ssize_t qbt2000_intcnt_store(struct device *dev,
struct device_attribute *attr, const char *buf,
size_t size)
{
struct qbt2000_drvdata *drvdata = dev_get_drvdata(dev);
if (sysfs_streq(buf, "c")) {
drvdata->wuhb_count = 0;
pr_info("initialization is done\n");
}
return size;
}
static ssize_t qbt2000_resetcnt_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct qbt2000_drvdata *drvdata = dev_get_drvdata(dev);
return snprintf(buf, PAGE_SIZE, "%d\n", drvdata->reset_count);
}
static ssize_t qbt2000_resetcnt_store(struct device *dev,
struct device_attribute *attr, const char *buf,
size_t size)
{
struct qbt2000_drvdata *drvdata = dev_get_drvdata(dev);
if (sysfs_streq(buf, "c")) {
drvdata->reset_count = 0;
pr_info("initialization is done\n");
}
return size;
}
static ssize_t qbt2000_wuhbtest_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct qbt2000_drvdata *drvdata = dev_get_drvdata(dev);
int rc = drvdata->wuhb_test_result;
if (drvdata->wuhb_test_flag == 1) {
if (drvdata->enabled_wuhb) {
disable_irq(drvdata->fd_gpio.irq);
drvdata->enabled_wuhb = false;
}
}
drvdata->wuhb_test_result = 0;
drvdata->wuhb_test_flag = 0;
pr_info("wuhb test complete.rc=%d,wuhb_en:%d\n", rc, drvdata->enabled_wuhb);
return snprintf(buf, PAGE_SIZE, "%d\n", rc);
}
static DEVICE_ATTR(bfs_values, 0444, qbt2000_bfs_values_show, NULL);
static DEVICE_ATTR(type_check, 0444, qbt2000_type_check_show, NULL);
static DEVICE_ATTR(vendor, 0444, qbt2000_vendor_show, NULL);
static DEVICE_ATTR(name, 0444, qbt2000_name_show, NULL);
static DEVICE_ATTR(adm, 0444, qbt2000_adm_show, NULL);
static DEVICE_ATTR(position, 0444, qbt2000_position_show, NULL);
static DEVICE_ATTR(cbgecnt, 0664, qbt2000_cbgecnt_show, qbt2000_cbgecnt_store);
static DEVICE_ATTR(intcnt, 0664, qbt2000_intcnt_show, qbt2000_intcnt_store);
static DEVICE_ATTR(resetcnt, 0664, qbt2000_resetcnt_show, qbt2000_resetcnt_store);
static DEVICE_ATTR(wuhbtest, 0444, qbt2000_wuhbtest_show, NULL);
static struct device_attribute *fp_attrs[] = {
&dev_attr_bfs_values,
&dev_attr_type_check,
&dev_attr_vendor,
&dev_attr_name,
&dev_attr_adm,
&dev_attr_position,
&dev_attr_cbgecnt,
&dev_attr_intcnt,
&dev_attr_resetcnt,
&dev_attr_wuhbtest,
NULL,
};
#if defined(ENABLE_SENSORS_FPRINT_SECURE)
int fpsensor_goto_suspend = 0;
int fps_resume_set(void) {
int rc = 0;
if (fpsensor_goto_suspend)
fpsensor_goto_suspend = 0;
return rc;
}
#endif
#ifdef QBT2000_AVOID_NOISE
static int qbt2000_noise_control(struct qbt2000_drvdata *drvdata, int control)
{
int retry = 3;
int rc = 0;
if (control == QBT2000_NOISE_UNBLOCK) {
rc = set_wacom_ble_charge_mode(true); /* 0:pass, etc:fail */
if (rc == 0)
drvdata->noise_onoff_flag = QBT2000_NOISE_UNBLOCK;
pr_info("%d, rc:%d, flag:%d\n", control, rc, drvdata->noise_onoff_flag);
} else if ((control == QBT2000_NOISE_BLOCK) && (drvdata->noise_onoff_flag == QBT2000_NOISE_UNBLOCK)) {
drvdata->noise_onoff_flag = QBT2000_NOISE_BLOCK;
while (retry--) {
rc = set_wacom_ble_charge_mode(false);
pr_info("%d, retry:%d, rc:%d\n", control, retry, rc);
if (rc == 0)
break;
usleep_range(4950, 5000);
}
} else {
pr_err("invalid value %d,%d\n", control, drvdata->noise_onoff_flag);
}
if (rc < 0) {
drvdata->noise_onoff_flag = control ? QBT2000_NOISE_BLOCK : QBT2000_NOISE_UNBLOCK;
drvdata->i2c_error_set++;
}
return rc;
}
#endif
static int qbt2000_power_control(struct qbt2000_drvdata *drvdata, int onoff)
{
int rc = 0;
if (drvdata->ldogpio >= 0) {
gpio_set_value(drvdata->ldogpio, onoff);
drvdata->enabled_ldo = onoff;
#if !defined(ENABLE_SENSORS_FPRINT_SECURE) || defined(DISABLED_GPIO_PROTECTION)
if (onoff) {
if (drvdata->pins_poweron) {
rc = pinctrl_select_state(drvdata->p, drvdata->pins_poweron);
pr_debug("pinctrl for poweron\n");
}
} else {
if (drvdata->pins_poweroff) {
rc = pinctrl_select_state(drvdata->p, drvdata->pins_poweroff);
pr_debug("pinctrl for poweroff\n");
}
}
#endif
pr_info("%s\n", onoff ? "ON" : "OFF");
} else if (drvdata->regulator_1p8 != NULL) {
if (onoff) {
rc = regulator_enable(drvdata->regulator_1p8);
if (rc)
pr_err("regulator enable failed, rc=%d\n", rc);
} else {
rc = regulator_disable(drvdata->regulator_1p8);
if (rc)
pr_err("regulator disable failed, rc=%d\n", rc);
}
#if !defined(ENABLE_SENSORS_FPRINT_SECURE) || defined(DISABLED_GPIO_PROTECTION)
if (onoff) {
if (drvdata->pins_poweron) {
rc = pinctrl_select_state(drvdata->p, drvdata->pins_poweron);
pr_debug("pinctrl for poweron\n");
}
} else {
if (drvdata->pins_poweroff) {
rc = pinctrl_select_state(drvdata->p, drvdata->pins_poweroff);
pr_debug("pinctrl for poweroff\n");
}
}
#endif
drvdata->enabled_ldo = onoff;
pr_info("%s\n", onoff ? "ON" : "OFF");
} else {
pr_info("This hw revision does not support power control\n");
}
return rc;
}
static int qbt2000_enable_spi_clock(struct qbt2000_drvdata *drvdata)
{
int rc = 0;
rc = qbt2000_set_clk(drvdata, 1);
return rc;
}
static int qbt2000_disable_spi_clock(struct qbt2000_drvdata *drvdata)
{
int rc = 0;
rc = qbt2000_set_clk(drvdata, 0);
return rc;
}
static int qbt2000_enable_ipc(struct qbt2000_drvdata *drvdata)
{
int rc = 0;
if (drvdata->fw_ipc.gpio) {
if (drvdata->enabled_ipc) {
rc = -EINVAL;
pr_err("already enabled ipc\n");
} else {
enable_irq(drvdata->fw_ipc.irq);
#ifdef ENABLE_SENSORS_FPRINT_SECURE
enable_irq_wake(drvdata->fw_ipc.irq);
#endif
drvdata->enabled_ipc = true;
}
}
return rc;
}
static int qbt2000_disable_ipc(struct qbt2000_drvdata *drvdata)
{
int rc = 0;
if (drvdata->fw_ipc.gpio) {
if (drvdata->enabled_ipc) {
#ifdef ENABLE_SENSORS_FPRINT_SECURE
disable_irq_wake(drvdata->fw_ipc.irq);
#endif
disable_irq(drvdata->fw_ipc.irq);
drvdata->enabled_ipc = false;
} else {
rc = -EINVAL;
pr_err("already disabled ipc\n");
}
}
return rc;
}
static int qbt2000_enable_wuhb(struct qbt2000_drvdata *drvdata)
{
int rc = 0;
int gpio = 0;
if (drvdata->fd_gpio.gpio) {
if (drvdata->enabled_wuhb) {
rc = -EINVAL;
pr_err("already enabled wuhb\n");
} else {
enable_irq(drvdata->fd_gpio.irq);
enable_irq_wake(drvdata->fd_gpio.irq);
drvdata->enabled_wuhb = true;
/* To prevent FingerUp Missing issue. */
gpio = gpio_get_value(drvdata->fd_gpio.gpio);
if (drvdata->fd_gpio.last_gpio_state == FINGER_DOWN_GPIO_STATE &&
gpio == FINGER_LEAVE_GPIO_STATE) {
pr_info("Finger leave event already occured. %d, %d\n",
drvdata->fd_gpio.last_gpio_state, gpio);
wake_lock_timeout(&drvdata->fp_signal_lock,
msecs_to_jiffies(QBT2000_WAKELOCK_HOLD_TIME));
schedule_work(&drvdata->fd_gpio.work);
}
}
}
return rc;
}
static int qbt2000_disable_wuhb(struct qbt2000_drvdata *drvdata)
{
int rc = 0;
if (drvdata->fd_gpio.gpio) {
if (drvdata->enabled_wuhb) {
disable_irq(drvdata->fd_gpio.irq);
disable_irq_wake(drvdata->fd_gpio.irq);
drvdata->enabled_wuhb = false;
} else {
rc = -EINVAL;
pr_err("already disabled wuhb\n");
}
}
return rc;
}
/**
* qbt2000_open() - Function called when user space opens device.
* Successful if driver not currently open.
* @inode: ptr to inode object
* @file: ptr to file object
*
* Return: 0 on success. Error code on failure.
*/
static int qbt2000_open(struct inode *inode, struct file *file)
{
struct qbt2000_drvdata *drvdata = NULL;
int rc = 0;
int minor_no = iminor(inode);
if (minor_no == MINOR_NUM_FD) {
drvdata = container_of(inode->i_cdev, struct qbt2000_drvdata, qbt2000_fd_cdev);
} else if (minor_no == MINOR_NUM_IPC) {
drvdata = container_of(inode->i_cdev, struct qbt2000_drvdata, qbt2000_ipc_cdev);
} else {
pr_err("Invalid minor number\n");
return -EINVAL;
}
file->private_data = drvdata;
/* disallowing concurrent opens */
if (minor_no == MINOR_NUM_FD && !atomic_dec_and_test(&drvdata->fd_available)) {
atomic_inc(&drvdata->fd_available);
pr_err("fd_unavailable\n");
rc = -EBUSY;
} else if (minor_no == MINOR_NUM_IPC && !atomic_dec_and_test(&drvdata->ipc_available)) {
atomic_inc(&drvdata->ipc_available);
pr_err("ipc_unavailable\n");
rc = -EBUSY;
}
pr_debug("minor_no=%d, rc=%d,%d,%d\n", minor_no, rc,
atomic_read(&drvdata->fd_available),
atomic_read(&drvdata->ipc_available));
return rc;
}
/**
* qbt2000_release() - Function called when user space closes device.
* @inode: ptr to inode object
* @file: ptr to file object
*
* Return: 0 on success. Error code on failure.
*/
static int qbt2000_release(struct inode *inode, struct file *file)
{
struct qbt2000_drvdata *drvdata;
int minor_no;
int rc = 0;
if (!file || !file->private_data) {
pr_err("qbt2000_release: NULL pointer passed\n");
return -EINVAL;
}
drvdata = file->private_data;
minor_no = iminor(inode);
if (minor_no == MINOR_NUM_FD) {
atomic_inc(&drvdata->fd_available);
} else if (minor_no == MINOR_NUM_IPC) {
atomic_inc(&drvdata->ipc_available);
} else {
pr_err("Invalid minor number\n");
rc = -EINVAL;
}
pr_debug("minor_no=%d, rc=%d,%d,%d\n", minor_no, rc,
atomic_read(&drvdata->fd_available),
atomic_read(&drvdata->ipc_available));
return rc;
}
/**
* qbt2000_ioctl() - Function called when user space calls ioctl.
* @file: struct file - not used
* @cmd: cmd identifier:QBT2000_LOAD_APP,QBT2000_UNLOAD_APP,
* QBT2000_SEND_TZCMD
* @arg: ptr to relevant structe: either qbt2000_app or
* qbt2000_send_tz_cmd depending on which cmd is passed
*
* Return: 0 on success. Error code on failure.
*/
static long qbt2000_ioctl(
struct file *file, unsigned int cmd, unsigned long arg)
{
int rc = 0;
int data = 0;
void __user *priv_arg = (void __user *)arg;
struct qbt2000_drvdata *drvdata;
if (!file || !file->private_data) {
pr_err("qbt2000_ioctl: NULL pointer passed\n");
return -EINVAL;
}
drvdata = file->private_data;
if (IS_ERR(priv_arg)) {
pr_err("invalid user space pointer %lu\n", arg);
return -EINVAL;
}
mutex_lock(&drvdata->ioctl_mutex);
switch (cmd) {
case QBT2000_IS_WHUB_CONNECTED:
break;
case QBT2000_POWER_CONTROL:
if (copy_from_user(&data, (void *)arg, sizeof(int)) != 0) {
pr_err("Failed copy from user.(POWER_CONTROL)\n");
rc = -EFAULT;
goto ioctl_failed;
}
if (drvdata->enabled_ldo != data) {
pr_debug("POWER_CONTROL\n");
qbt2000_power_control(drvdata, data);
}
break;
case QBT2000_ENABLE_SPI_CLOCK:
pr_info("ENABLE_SPI_CLOCK\n");
rc = qbt2000_enable_spi_clock(drvdata);
break;
case QBT2000_DISABLE_SPI_CLOCK:
pr_info("DISABLE_SPI_CLOCK\n");
rc = qbt2000_disable_spi_clock(drvdata);
break;
case QBT2000_ENABLE_IPC:
pr_info("ENABLE_IPC\n");
rc = qbt2000_enable_ipc(drvdata);
break;
case QBT2000_DISABLE_IPC:
pr_info("DISABLE_IPC\n");
rc = qbt2000_disable_ipc(drvdata);
break;
case QBT2000_ENABLE_WUHB:
pr_info("ENABLE_WUHB\n");
drvdata->wuhb_test_flag = 0;
rc = qbt2000_enable_wuhb(drvdata);
break;
case QBT2000_DISABLE_WUHB:
pr_info("DISABLE_WUHB\n");
/* initialize IntTest flag */
drvdata->wuhb_test_flag = 0;
rc = qbt2000_disable_wuhb(drvdata);
break;
case QBT2000_CPU_SPEEDUP:
if (copy_from_user(&data, (void *)arg, sizeof(int)) != 0) {
pr_err("Failed copy from user.(SPEEDUP)\n");
rc = -EFAULT;
goto ioctl_failed;
}
rc = qbt2000_set_cpu_speedup(drvdata, data);
break;
case QBT2000_SET_SENSOR_TYPE:
if (copy_from_user(&data, (void *)arg, sizeof(int)) != 0) {
pr_err("Failed to copy sensor type from user to kernel\n");
rc = -EFAULT;
goto ioctl_failed;
}
if (data >= SENSOR_OOO && data < SENSOR_MAXIMUM) {
if (data == SENSOR_OOO && drvdata->sensortype == SENSOR_FAILED) {
pr_err("Maintain type check from out of oder :%s\n",
sensor_status[drvdata->sensortype + 2]);
} else {
drvdata->sensortype = data;
pr_info("SET_SENSOR_TYPE :%s\n",
sensor_status[drvdata->sensortype + 2]);
}
} else {
pr_err("SET_SENSOR_TYPE : invalid value %d\n", data);
drvdata->sensortype = SENSOR_UNKNOWN;
}
break;
case QBT2000_SET_LOCKSCREEN:
break;
case QBT2000_SENSOR_RESET:
drvdata->reset_count++;
pr_err("SENSOR_RESET\n");
break;
case QBT2000_SENSOR_TEST:
if (copy_from_user(&data, (void *)arg, sizeof(int)) != 0) {
pr_err("Failed to copy BGECAL from user to kernel\n");
rc = -EFAULT;
goto ioctl_failed;
}
#ifndef ENABLE_SENSORS_FPRINT_SECURE //only for factory
#ifdef QBT2000_AVOID_NOISE
if (data == QBT2000_SENSORTEST_DONE) {
pr_info("SENSORTEST Finished\n");
qbt2000_noise_control(drvdata, QBT2000_NOISE_BLOCK);
} else {
pr_info("SENSORTEST Start : 0x%x\n", data);
qbt2000_noise_control(drvdata, QBT2000_NOISE_UNBLOCK);
}
#endif
#endif
break;
case QBT2000_NOISE_REQUEST_STOP:
#ifdef QBT2000_AVOID_NOISE
pr_info("QBT2000_NOISE_REQUEST_STOP. entry\n");
drvdata->noise_i2c_result = 1;
schedule_work(&drvdata->work_noise_control);
msleep(50);
#endif
break;
case QBT2000_NOISE_I2C_RESULT_GET:
#ifdef QBT2000_AVOID_NOISE
pr_info("QBT2000_NOISE_I2C_RESULT_GET : %d\n", drvdata->noise_i2c_result);
if (copy_to_user((void __user *)priv_arg, &drvdata->noise_i2c_result, sizeof(drvdata->noise_status)) != 0) {
pr_err("Failed to copy I2C_RESULT to user\n");
rc = -EFAULT;
}
#endif
break;
case QBT2000_NOISE_STATUS_GET:
#ifdef QBT2000_AVOID_NOISE
drvdata->noise_status = get_wacom_scan_info(false);
if (drvdata->noise_status == QBT2000_NOISE_MODE_CHANGED) {
drvdata->ignored_cbge_count++;
} else if (drvdata->noise_status == QBT2000_NOISE_CHARGING) {
drvdata->i2c_charging++;
} else if (drvdata->noise_status < 0) {
drvdata->i2c_error_get++;
}
pr_info("QBT2000_NOISE_STATUS_GET : %d\n", drvdata->noise_status);
if (copy_to_user((void __user *)priv_arg, &drvdata->noise_status, sizeof(drvdata->noise_status)) != 0) {
pr_err("Failed to copy NOISE_STATUS to user\n");
rc = -EFAULT;
}
#endif
break;
case QBT2000_NOISE_REQUEST_START:
#ifdef QBT2000_AVOID_NOISE
pr_info("QBT2000_NOISE_REQUEST_START\n");
qbt2000_noise_control(drvdata, QBT2000_NOISE_UNBLOCK);
#endif
break;
case QBT2000_NOISE_REQUEST_STATUS:
#ifdef QBT2000_AVOID_NOISE
pr_info("QBT2000_NOISE_REQUEST_STATUS : %d\n", drvdata->noise_onoff_flag);
if (copy_to_user((void __user *)priv_arg, &drvdata->noise_onoff_flag, sizeof(drvdata->noise_onoff_flag)) != 0) {
pr_err("Failed to copy REQUEST_STATUS to user\n");
rc = -EFAULT;
}
#endif
break;
case QBT2000_GET_MODELINFO:
pr_info("QBT2000_GET_MODELINFO : %s\n", drvdata->model_info);
if (copy_to_user((void __user *)priv_arg, drvdata->model_info, 10)) {
pr_err("Failed to copy GET_MODELINFO to user\n");
rc = -EFAULT;
}
break;
default:
pr_err("invalid cmd %d\n", cmd);
rc = -ENOIOCTLCMD;
}
ioctl_failed:
mutex_unlock(&drvdata->ioctl_mutex);
return rc;
}
static int get_events_fifo_len_locked(struct qbt2000_drvdata *drvdata, int minor_no)
{
int len = 0;
if (minor_no == MINOR_NUM_FD) {
mutex_lock(&drvdata->fd_events_mutex);
len = kfifo_len(&drvdata->fd_events);
mutex_unlock(&drvdata->fd_events_mutex);
} else if (minor_no == MINOR_NUM_IPC) {
mutex_lock(&drvdata->ipc_events_mutex);
len = kfifo_len(&drvdata->ipc_events);
mutex_unlock(&drvdata->ipc_events_mutex);
}
return len;
}
static ssize_t qbt2000_read(struct file *filp, char __user *ubuf,
size_t cnt, loff_t *ppos)
{
struct fw_event_desc fw_event;
struct qbt2000_drvdata *drvdata = filp->private_data;
wait_queue_head_t *read_wait_queue = NULL;
int rc = 0;
int minor_no = iminor(filp->f_path.dentry->d_inode);
int fifo_len = get_events_fifo_len_locked(drvdata, minor_no);
if (cnt < sizeof(fw_event.ev)) {
pr_err("Num bytes to read is too small, numBytes=%zd\n", cnt);
return -EINVAL;
}
if (minor_no == MINOR_NUM_FD) {
read_wait_queue = &drvdata->read_wait_queue_fd;
} else if (minor_no == MINOR_NUM_IPC) {
read_wait_queue = &drvdata->read_wait_queue_ipc;
} else {
pr_err("Invalid minor number\n");
return -EINVAL;
}
while (fifo_len == 0) {
if (filp->f_flags & O_NONBLOCK) {
pr_debug("fw_events fifo: empty, returning\n");
return -EAGAIN;
}
pr_debug("fw_events fifo: empty, waiting\n");
if (wait_event_interruptible(*read_wait_queue,
(get_events_fifo_len_locked(drvdata, minor_no) > 0)))
return -ERESTARTSYS;
fifo_len = get_events_fifo_len_locked(drvdata, minor_no);
}
if (minor_no == MINOR_NUM_FD) {
mutex_lock(&drvdata->fd_events_mutex);
rc = kfifo_get(&drvdata->fd_events, &fw_event);
mutex_unlock(&drvdata->fd_events_mutex);
} else if (minor_no == MINOR_NUM_IPC) {
mutex_lock(&drvdata->ipc_events_mutex);
rc = kfifo_get(&drvdata->ipc_events, &fw_event);
mutex_unlock(&drvdata->ipc_events_mutex);
} else {
pr_err("Invalid minor number\n");
}
if (!rc) {
pr_err("fw_events fifo: unexpectedly empty\n");
return -EINVAL;
}
rc = copy_to_user(ubuf, &fw_event.ev, sizeof(fw_event.ev));
if (rc != 0) {
pr_err("Failed to copy_to_user:%d - event:%d, minor:%d\n",
rc, (int)fw_event.ev, minor_no);
} else {
if (minor_no == MINOR_NUM_FD) {
#ifdef QBT2000_AVOID_NOISE
#ifdef ENABLE_SENSORS_FPRINT_SECURE
mutex_unlock(&drvdata->fod_event_mutex);
#endif
#endif
pr_info("Firmware event %d at minor no %d read at time %lu uS, mutex_unlock\n",
(int)fw_event.ev, minor_no,
(unsigned long)ktime_to_us(ktime_get()));
} else {
pr_info("Firmware event %d at minor no %d read at time %lu uS\n",
(int)fw_event.ev, minor_no,
(unsigned long)ktime_to_us(ktime_get()));
}
}
return rc;
}
static unsigned int qbt2000_poll(struct file *filp,
struct poll_table_struct *wait)
{
struct qbt2000_drvdata *drvdata = filp->private_data;
unsigned int mask = 0;
int minor_no = iminor(filp->f_path.dentry->d_inode);
if (minor_no == MINOR_NUM_FD) {
poll_wait(filp, &drvdata->read_wait_queue_fd, wait);
if (kfifo_len(&drvdata->fd_events) > 0) {
mask |= (POLLIN | POLLRDNORM);
}
} else if (minor_no == MINOR_NUM_IPC) {
poll_wait(filp, &drvdata->read_wait_queue_ipc, wait);
if (kfifo_len(&drvdata->ipc_events) > 0) {
mask |= (POLLIN | POLLRDNORM);
}
} else {
pr_err("Invalid minor number\n");
return -EINVAL;
}
return mask;
}
static const struct file_operations qbt2000_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = qbt2000_ioctl,
.open = qbt2000_open,
.release = qbt2000_release,
.read = qbt2000_read,
.poll = qbt2000_poll
};
static int qbt2000_dev_register(struct qbt2000_drvdata *drvdata)
{
dev_t dev_no, major_no;
int rc = 0;
struct device *dev = drvdata->dev;
rc = alloc_chrdev_region(&dev_no, 0, 2, QBT2000_DEV);
if (rc) {
pr_err("alloc_chrdev_region failed %d\n", rc);
goto err_alloc;
}
major_no = MAJOR(dev_no);
cdev_init(&drvdata->qbt2000_fd_cdev, &qbt2000_fops);
drvdata->qbt2000_fd_cdev.owner = THIS_MODULE;
rc = cdev_add(&drvdata->qbt2000_fd_cdev, MKDEV(major_no, MINOR_NUM_FD), 1);
if (rc) {
pr_err("cdev_add failed for fd %d\n", rc);
goto err_cdev_add;
}
cdev_init(&drvdata->qbt2000_ipc_cdev, &qbt2000_fops);
drvdata->qbt2000_ipc_cdev.owner = THIS_MODULE;
rc = cdev_add(&drvdata->qbt2000_ipc_cdev, MKDEV(major_no, MINOR_NUM_IPC), 1);
if (rc) {
pr_err("cdev_add failed for ipc %d\n", rc);
goto err_cdev_add;
}
drvdata->qbt2000_class = class_create(THIS_MODULE, QBT2000_DEV);
if (IS_ERR(drvdata->qbt2000_class)) {
rc = PTR_ERR(drvdata->qbt2000_class);
pr_err("class_create failed %d\n", rc);
goto err_class_create;
}
dev = device_create(drvdata->qbt2000_class, NULL, drvdata->qbt2000_fd_cdev.dev,
drvdata, "%s_fd", QBT2000_DEV);
if (IS_ERR(dev)) {
rc = PTR_ERR(dev);
pr_err("fd device_create failed %d\n", rc);
goto err_dev_create_fd;
}
dev = device_create(drvdata->qbt2000_class, NULL, drvdata->qbt2000_ipc_cdev.dev,
drvdata, "%s_ipc", QBT2000_DEV);
if (IS_ERR(dev)) {
rc = PTR_ERR(dev);
pr_err("ipc device_create failed %d\n", rc);
goto err_dev_create_ipc;
}
pr_info("finished\n");
return 0;
err_dev_create_ipc:
device_destroy(drvdata->qbt2000_class, drvdata->qbt2000_fd_cdev.dev);
err_dev_create_fd:
class_destroy(drvdata->qbt2000_class);
err_class_create:
cdev_del(&drvdata->qbt2000_fd_cdev);
cdev_del(&drvdata->qbt2000_ipc_cdev);
err_cdev_add:
unregister_chrdev_region(drvdata->qbt2000_fd_cdev.dev, 1);
unregister_chrdev_region(drvdata->qbt2000_ipc_cdev.dev, 1);
err_alloc:
return rc;
}
int qbt2000_pinctrl_register(struct qbt2000_drvdata *drvdata)
{
int rc = 0;
drvdata->p = pinctrl_get_select_default(drvdata->dev);
if (IS_ERR(drvdata->p)) {
rc = -EINVAL;
pr_err("failed pinctrl_get\n");
goto pinctrl_register_default_exit;
}
#if !defined(ENABLE_SENSORS_FPRINT_SECURE) || defined(DISABLED_GPIO_PROTECTION)
drvdata->pins_poweroff = pinctrl_lookup_state(drvdata->p, "pins_poweroff");
if (IS_ERR(drvdata->pins_poweroff)) {
pr_err("could not get pins sleep_state (%li)\n",
PTR_ERR(drvdata->pins_poweroff));
drvdata->pins_poweroff = NULL;
drvdata->pins_poweron = NULL;
goto pinctrl_register_exit;
}
drvdata->pins_poweron = pinctrl_lookup_state(drvdata->p, "pins_poweron");
if (IS_ERR(drvdata->pins_poweron)) {
pr_err("could not get pins idle_state (%li)\n",
PTR_ERR(drvdata->pins_poweron));
drvdata->pins_poweron = NULL;
goto pinctrl_register_exit;
}
#endif
pr_info("finished\n");
return rc;
#if !defined(ENABLE_SENSORS_FPRINT_SECURE) || defined(DISABLED_GPIO_PROTECTION)
pinctrl_register_exit:
pinctrl_put(drvdata->p);
#endif
pinctrl_register_default_exit:
pr_err("failed %d\n", rc);
return rc;
}
static void qbt2000_gpio_report_event(struct qbt2000_drvdata *drvdata)
{
int state;
struct fw_event_desc fw_event;
state = (__gpio_get_value(drvdata->fd_gpio.gpio) ? FINGER_DOWN_GPIO_STATE : FINGER_LEAVE_GPIO_STATE)
^ drvdata->fd_gpio.active_low;
if (state == drvdata->fd_gpio.last_gpio_state) {
#ifdef QBT2000_AVOID_NOISE
#ifdef ENABLE_SENSORS_FPRINT_SECURE
pr_debug("skip the report_event. this event already reported, last_gpio:%d\n", state);
mutex_unlock(&drvdata->fod_event_mutex);
#endif
#endif
return;
}
drvdata->fd_gpio.last_gpio_state = state;
fw_event.ev = (state ? FW_EVENT_FINGER_DOWN : FW_EVENT_FINGER_UP);
mutex_lock(&drvdata->fd_events_mutex);
kfifo_reset(&drvdata->fd_events);
if (!kfifo_put(&drvdata->fd_events, fw_event))
pr_err("fw events fifo: error adding item\n");
#ifdef QBT2000_AVOID_NOISE
if (!state)
qbt2000_noise_control(drvdata, QBT2000_NOISE_UNBLOCK);
#endif
mutex_unlock(&drvdata->fd_events_mutex);
wake_up_interruptible(&drvdata->read_wait_queue_fd);
pr_info("state: %s\n", state ? "Finger Down" : "Finger Leave");
}
static void qbt2000_wuhb_work_func(struct work_struct *work)
{
struct qbt2000_drvdata *drvdata =
container_of(work, struct qbt2000_drvdata, fd_gpio.work);
#ifdef QBT2000_AVOID_NOISE
#ifdef ENABLE_SENSORS_FPRINT_SECURE
mutex_lock(&drvdata->fod_event_mutex);
pr_debug("mutex_lock\n");
#endif
#endif
qbt2000_gpio_report_event(drvdata);
}
#ifdef QBT2000_AVOID_NOISE
static void qbt2000_wuhb_work_noise_down_func(struct work_struct *work)
{
struct qbt2000_drvdata *drvdata =
container_of(work, struct qbt2000_drvdata, fd_gpio.work_noise_down);
int delay_time = QBT2000_NOISE_BLOCK_DELAY;
#ifdef ENABLE_SENSORS_FPRINT_SECURE
mutex_lock(&drvdata->fod_event_mutex);
pr_debug("mutex_lock\n");
#endif
schedule_delayed_work(&drvdata->fd_gpio.delayed_noise_down_work, msecs_to_jiffies(delay_time));
qbt2000_noise_control(drvdata, QBT2000_NOISE_BLOCK);
}
static void qbt2000_ipc_handler_noise_status(struct work_struct *work)
{
struct qbt2000_drvdata *drvdata =
container_of(work, struct qbt2000_drvdata, work_ipc_noise_status);
drvdata->noise_status = get_wacom_scan_info(true);
pr_info("entry : %d\n", drvdata->noise_status);
if (drvdata->noise_status < 0)
drvdata->i2c_error_get++;
else if (drvdata->noise_status == QBT2000_NOISE_CHARGING)
drvdata->i2c_charging++;
}
static void qbt2000_work_noise_control(struct work_struct *work)
{
struct qbt2000_drvdata *drvdata =
container_of(work, struct qbt2000_drvdata, work_noise_control);
int rc = 0;
pr_info("entry\n");
rc = qbt2000_noise_control(drvdata, QBT2000_NOISE_BLOCK);
drvdata->noise_i2c_result = rc;
pr_info("done : %d\n", drvdata->noise_i2c_result);
}
static void qbt2000_gpio_report_event_delayed(struct qbt2000_drvdata *drvdata, int state)
{
struct fw_event_desc fw_event;
int rc = 0;
if (state == drvdata->fd_gpio.last_gpio_state) {
pr_info("already done %d %d, rc = %d\n", state, drvdata->fd_gpio.last_gpio_state, rc);
#ifdef ENABLE_SENSORS_FPRINT_SECURE
mutex_unlock(&drvdata->fod_event_mutex);
#endif
return;
}
drvdata->fd_gpio.last_gpio_state = state;
fw_event.ev = (state ? FW_EVENT_FINGER_DOWN : FW_EVENT_FINGER_UP);
mutex_lock(&drvdata->fd_events_mutex);
kfifo_reset(&drvdata->fd_events);
if (!kfifo_put(&drvdata->fd_events, fw_event))
pr_err("fw events fifo: error adding item\n");
mutex_unlock(&drvdata->fd_events_mutex);
wake_up_interruptible(&drvdata->read_wait_queue_fd);
pr_info("state: %s\n", state ? "Finger Down" : "Finger Leave");
}
static void qbt2000_wuhb_delayed_work_func(struct work_struct *work)
{
struct qbt2000_drvdata *drvdata =
container_of(work, struct qbt2000_drvdata, fd_gpio.delayed_noise_down_work.work);
qbt2000_gpio_report_event_delayed(drvdata, drvdata->now_state);
}
#endif
static irqreturn_t qbt2000_wuhb_irq_handler(int irq, void *dev_id)
{
struct qbt2000_drvdata *drvdata = dev_id;
if (irq != drvdata->fd_gpio.irq) {
pr_warn("invalid irq %d (expected %d)\n", irq, drvdata->fd_gpio.irq);
return IRQ_HANDLED;
}
if (drvdata->wuhb_test_flag == 1) {
pr_info("IntTest. interrupt occured.flag:%d,result:%d,en:%d\n",
drvdata->wuhb_test_flag,
drvdata->wuhb_test_result,
drvdata->enabled_wuhb);
drvdata->wuhb_test_result = 1;
return IRQ_HANDLED;
}
drvdata->wuhb_count++;
wake_lock_timeout(&drvdata->fp_signal_lock,
msecs_to_jiffies(QBT2000_WAKELOCK_HOLD_TIME));
#ifndef QBT2000_AVOID_NOISE // not use digitizer
schedule_work(&drvdata->fd_gpio.work);
#else
#ifndef ENABLE_SENSORS_FPRINT_SECURE // use digitizer but nontz
schedule_work(&drvdata->fd_gpio.work);
#else // use digitizer and tz
drvdata->now_state = (__gpio_get_value(drvdata->fd_gpio.gpio) ? FINGER_DOWN_GPIO_STATE : FINGER_LEAVE_GPIO_STATE) ^ drvdata->fd_gpio.active_low;
pr_info("gpio state : %d\n", drvdata->now_state);
if (drvdata->now_state == FINGER_DOWN_GPIO_STATE) { // in case of finger down
schedule_work(&drvdata->fd_gpio.work_noise_down);
} else {
schedule_work(&drvdata->fd_gpio.work);
}
#endif //ENABLE_SENSORS_FPRINT_SECURE
#endif //QBT2000_AVOID_NOISE
return IRQ_HANDLED;
}
/*
* qbt2000_ipc_irq_handler() - function processes IPC
* interrupts on its own thread
* @irq: the interrupt that occurred
* @dev_id: pointer to the qbt2000_drvdata
*
* Return: IRQ_HANDLED when complete
*/
static irqreturn_t qbt2000_ipc_irq_handler(int irq, void *dev_id)
{
struct qbt2000_drvdata *drvdata = (struct qbt2000_drvdata *)dev_id;
enum qbt2000_fw_event ev = FW_EVENT_CBGE_REQUIRED;
struct fw_event_desc fw_ev_des;
wake_lock_timeout(&drvdata->fp_signal_lock,
msecs_to_jiffies(QBT2000_WAKELOCK_HOLD_TIME));
mutex_lock(&drvdata->mutex);
if (irq != drvdata->fw_ipc.irq) {
pr_warn("invalid irq %d (expected %d)\n", irq, drvdata->fw_ipc.irq);
goto ipc_irq_failed;
}
#ifdef ENABLE_SENSORS_FPRINT_SECURE
#ifdef QBT2000_AVOID_NOISE
schedule_work(&drvdata->work_ipc_noise_status);
#endif
#endif
mutex_lock(&drvdata->ipc_events_mutex);
fw_ev_des.ev = ev;
if (!kfifo_put(&drvdata->ipc_events, fw_ev_des))
pr_err("fw events: fifo full, drop event %d\n", (int) ev);
drvdata->cbge_count++;
mutex_unlock(&drvdata->ipc_events_mutex);
wake_up_interruptible(&drvdata->read_wait_queue_ipc);
pr_debug("ipc interrupt received, irq=%d, event=%d\n", irq, (int)ev);
ipc_irq_failed:
mutex_unlock(&drvdata->mutex);
return IRQ_HANDLED;
}
static int qbt2000_setup_fd_gpio_irq(struct platform_device *pdev,
struct qbt2000_drvdata *drvdata)
{
int rc = 0;
int irq;
const char *desc = "qbt_finger_detect";
rc = devm_gpio_request_one(&pdev->dev, drvdata->fd_gpio.gpio,
GPIOF_IN, desc);
if (rc < 0) {
pr_err("failed to request gpio %d, error %d\n",
drvdata->fd_gpio.gpio, rc);
goto fd_gpio_failed;
}
irq = gpio_to_irq(drvdata->fd_gpio.gpio);
if (irq < 0) {
rc = irq;
pr_err("unable to get irq number for gpio %d, error %d\n",
drvdata->fd_gpio.gpio, rc);
goto fd_gpio_failed_request;
}
drvdata->fd_gpio.irq = irq;
INIT_WORK(&drvdata->fd_gpio.work, qbt2000_wuhb_work_func);
#ifdef QBT2000_AVOID_NOISE
INIT_WORK(&drvdata->fd_gpio.work_noise_down, qbt2000_wuhb_work_noise_down_func);
INIT_DELAYED_WORK(&drvdata->fd_gpio.delayed_noise_down_work, qbt2000_wuhb_delayed_work_func);
INIT_WORK(&drvdata->work_ipc_noise_status, qbt2000_ipc_handler_noise_status);
INIT_WORK(&drvdata->work_noise_control, qbt2000_work_noise_control);
#endif
rc = devm_request_any_context_irq(&pdev->dev, drvdata->fd_gpio.irq,
qbt2000_wuhb_irq_handler, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
desc, drvdata);
if (rc < 0) {
pr_err("unable to claim irq %d; error %d\n",
drvdata->fd_gpio.irq, rc);
goto fd_gpio_failed_request;
}
enable_irq_wake(drvdata->fd_gpio.irq);
drvdata->enabled_wuhb = true;
qbt2000_disable_wuhb(drvdata);
pr_debug("irq=%d,gpio=%d,rc=%d\n", drvdata->fd_gpio.irq, drvdata->fd_gpio.gpio, rc);
fd_gpio_failed_request:
gpio_free(drvdata->fd_gpio.gpio);
fd_gpio_failed:
return rc;
}
static int qbt2000_setup_ipc_irq(struct platform_device *pdev,
struct qbt2000_drvdata *drvdata)
{
int rc = 0;
const char *desc = "qbt_ipc";
drvdata->fw_ipc.irq = gpio_to_irq(drvdata->fw_ipc.gpio);
if (drvdata->fw_ipc.irq < 0) {
rc = drvdata->fw_ipc.irq;
pr_err("no irq for gpio %d, error=%d\n",
drvdata->fw_ipc.gpio, rc);
goto ipc_gpio_failed;
}
rc = devm_gpio_request_one(&pdev->dev, drvdata->fw_ipc.gpio,
GPIOF_IN, desc);
if (rc < 0) {
pr_err("failed to request gpio %d, error %d\n",
drvdata->fw_ipc.gpio, rc);
goto ipc_gpio_failed;
}
rc = devm_request_threaded_irq(&pdev->dev,
drvdata->fw_ipc.irq,
NULL,
qbt2000_ipc_irq_handler,
IRQF_ONESHOT | IRQF_TRIGGER_FALLING,
desc,
drvdata);
if (rc < 0) {
pr_err("failed to register for ipc irq %d, rc = %d\n",
drvdata->fw_ipc.irq, rc);
goto ipc_gpio_failed_request;
}
#ifdef ENABLE_SENSORS_FPRINT_SECURE
enable_irq_wake(drvdata->fw_ipc.irq);
#endif
drvdata->enabled_ipc = true;
qbt2000_disable_ipc(drvdata);
pr_debug("irq=%d,gpio=%d,rc=%d\n", drvdata->fw_ipc.irq, drvdata->fw_ipc.gpio, rc);
ipc_gpio_failed_request:
gpio_free(drvdata->fw_ipc.gpio);
ipc_gpio_failed:
return rc;
}
/**
* qbt2000_read_device_tree() - Function reads device tree
* properties into driver data
* @pdev: ptr to platform device object
* @drvdata: ptr to driver data
*
* Return: 0 on success. Error code on failure.
*/
static int qbt2000_read_device_tree(struct platform_device *pdev,
struct qbt2000_drvdata *drvdata)
{
int rc = 0;
enum of_gpio_flags flags;
/* read IPC gpio */
drvdata->fw_ipc.gpio = of_get_named_gpio(pdev->dev.of_node,
"qcom,ipc-gpio", 0);
if (drvdata->fw_ipc.gpio < 0) {
rc = drvdata->fw_ipc.gpio;
pr_err("ipc gpio not found, error=%d\n", rc);
goto dt_failed;
}
/* read WUHB gpio */
drvdata->fd_gpio.gpio = of_get_named_gpio_flags(pdev->dev.of_node,
"qcom,wuhb-gpio", 0, &flags);
if (drvdata->fd_gpio.gpio < 0) {
rc = drvdata->fd_gpio.gpio;
pr_err("wuhb gpio not found, error=%d\n", rc);
goto dt_failed;
} else {
drvdata->fd_gpio.active_low = flags & OF_GPIO_ACTIVE_LOW;
}
drvdata->ldogpio = of_get_named_gpio(pdev->dev.of_node, "qcom,ldo-gpio", 0);
if (drvdata->ldogpio < 0) {
pr_info("ldo gpio not found. %d\n", drvdata->ldogpio);
} else {
rc = gpio_request(drvdata->ldogpio, "qbt_ldo_en");
gpio_direction_output(drvdata->ldogpio, 0);
drvdata->enabled_ldo = 0;
}
if (of_property_read_string(pdev->dev.of_node, "qcom,btp-regulator", &drvdata->btp_vdd) < 0) {
pr_info("not use btp_regulator\n");
drvdata->btp_vdd = NULL;
} else {
drvdata->regulator_1p8 = regulator_get(NULL, drvdata->btp_vdd);
if (IS_ERR(drvdata->regulator_1p8) ||
(drvdata->regulator_1p8) == NULL) {
pr_info("not use regulator_1p8\n");
drvdata->regulator_1p8 = NULL;
} else {
pr_info("btp_regulator ok\n");
}
}
if (of_property_read_u32(pdev->dev.of_node, "qcom,min_cpufreq_limit",
&drvdata->min_cpufreq_limit))
drvdata->min_cpufreq_limit = 0;
if (of_property_read_string_index(pdev->dev.of_node, "qcom,position", 0,
(const char **)&drvdata->sensor_position))
drvdata->sensor_position = "13.77,0.00,9.00,4.00,14.80,14.80,11.00,11.00,5.00";
pr_info("Sensor Position: %s\n", drvdata->sensor_position);
if (of_property_read_string_index(pdev->dev.of_node, "qcom,modelinfo", 0,
(const char **)&drvdata->model_info)) {
drvdata->model_info = "NONE";
}
pr_info("modelinfo: %s\n", drvdata->model_info);
pr_info("finished\n");
return rc;
dt_failed:
pr_err("failed:%d\n", rc);
return rc;
}
static void qbt2000_work_func_debug(struct work_struct *work)
{
#ifndef QBT2000_AVOID_NOISE
pr_info("ldo:%d,ipc:%d,wuhb:%d,tz:%d,type:%s,int:%d,%d\n",
g_data->enabled_ldo, g_data->enabled_ipc,
g_data->enabled_wuhb, g_data->tz_mode,
sensor_status[g_data->sensortype + 2],
g_data->cbge_count, g_data->wuhb_count);
#else
pr_info("ldo:%d,ipc:%d,wuhb:%d,tz:%d,type:%s,int:%d,%d,%d,%d,%d,%d,%d\n",
g_data->enabled_ldo, g_data->enabled_ipc,
g_data->enabled_wuhb, g_data->tz_mode,
sensor_status[g_data->sensortype + 2],
g_data->cbge_count, g_data->ignored_cbge_count,
g_data->wuhb_count, g_data->i2c_error_set,
g_data->i2c_error_get, g_data->i2c_charging,
g_data->noise_onoff_flag);
#endif
}
static void qbt2000_enable_debug_timer(void)
{
mod_timer(&g_data->dbg_timer,
round_jiffies_up(jiffies + FPSENSOR_DEBUG_TIMER_SEC));
}
static void qbt2000_disable_debug_timer(void)
{
del_timer_sync(&g_data->dbg_timer);
cancel_work_sync(&g_data->work_debug);
}
static void qbt2000_timer_func(unsigned long ptr)
{
queue_work(g_data->wq_dbg, &g_data->work_debug);
mod_timer(&g_data->dbg_timer,
round_jiffies_up(jiffies + FPSENSOR_DEBUG_TIMER_SEC));
}
static int qbt2000_set_timer(struct qbt2000_drvdata *drvdata)
{
int rc = 0;
setup_timer(&drvdata->dbg_timer, qbt2000_timer_func,
(unsigned long)drvdata);
drvdata->wq_dbg = create_singlethread_workqueue("qbt2000_debug_wq");
if (!drvdata->wq_dbg) {
rc = -ENOMEM;
pr_err("could not create workqueue\n");
return rc;
}
INIT_WORK(&drvdata->work_debug, qbt2000_work_func_debug);
return rc;
}
/**
* qbt2000_probe() - Function loads hardware config from device tree
* @pdev: ptr to platform device object
*
* Return: 0 on success. Error code on failure.
*/
static int qbt2000_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct qbt2000_drvdata *drvdata;
int rc = 0;
pr_info("Start\n");
#ifdef CONFIG_BATTERY_SAMSUNG
if (lpcharge) {
pr_info("Do not load driver due to : lpm %d\n", lpcharge);
return rc;
}
#endif
drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL);
if (!drvdata)
return -ENOMEM;
drvdata->dev = &pdev->dev;
platform_set_drvdata(pdev, drvdata);
rc = qbt2000_read_device_tree(pdev, drvdata);
if (rc < 0)
goto probe_failed_dt;
atomic_set(&drvdata->fd_available, 1);
atomic_set(&drvdata->ipc_available, 1);
mutex_init(&drvdata->mutex);
mutex_init(&drvdata->ioctl_mutex);
mutex_init(&drvdata->fd_events_mutex);
mutex_init(&drvdata->ipc_events_mutex);
#ifdef QBT2000_AVOID_NOISE
mutex_init(&drvdata->fod_event_mutex);
#endif
rc = qbt2000_dev_register(drvdata);
if (rc < 0)
goto probe_failed_dev_register;
INIT_KFIFO(drvdata->fd_events);
INIT_KFIFO(drvdata->ipc_events);
init_waitqueue_head(&drvdata->read_wait_queue_fd);
init_waitqueue_head(&drvdata->read_wait_queue_ipc);
wake_lock_init(&drvdata->fp_spi_lock,
WAKE_LOCK_SUSPEND, "qbt2000_spi_lock");
wake_lock_init(&drvdata->fp_signal_lock,
WAKE_LOCK_SUSPEND, "qbt2000_signal_lock");
rc = qbt2000_pinctrl_register(drvdata);
if (rc < 0)
goto probe_failed_pinctrl;
rc = qbt2000_setup_fd_gpio_irq(pdev, drvdata);
if (rc < 0)
goto probe_failed_fd_gpio;
rc = qbt2000_setup_ipc_irq(pdev, drvdata);
if (rc < 0)
goto probe_failed_ipc_gpio;
rc = device_init_wakeup(&pdev->dev, 1);
if (rc < 0)
goto probe_failed_device_init_wakeup;
rc = qbt2000_register_platform_variable(drvdata);
if (rc < 0)
goto probe_failed_platform_variable;
rc = fingerprint_register(drvdata->fp_device,
drvdata, fp_attrs, "fingerprint");
if (rc ) {
pr_err("sysfs register failed\n");
goto probe_failed_sysfs_register;
}
#ifndef ENABLE_SENSORS_FPRINT_SECURE
qbt2000_power_control(drvdata, 1);
#endif
g_data = drvdata;
drvdata->spi_speed = SPI_CLOCK_MAX;
#ifdef ENABLE_SENSORS_FPRINT_SECURE
drvdata->enabled_clk = false;
drvdata->tz_mode = true;
#else
drvdata->enabled_clk = true;
drvdata->tz_mode = false;
#endif
drvdata->sensortype = SENSOR_QBT2000;
drvdata->cbge_count = 0;
#ifdef QBT2000_AVOID_NOISE
drvdata->ignored_cbge_count = 0;
drvdata->i2c_error_set = 0;
drvdata->i2c_error_get = 0;
drvdata->i2c_charging = 0;
drvdata->noise_status = QBT2000_NOISE_NO_CHARGING;
drvdata->noise_onoff_flag = QBT2000_NOISE_UNBLOCK;
#endif
drvdata->wuhb_count = 0;
drvdata->reset_count = 0;
drvdata->wuhb_test_flag = 0;
drvdata->wuhb_test_result = 0;
qbt2000_set_timer(drvdata);
qbt2000_enable_debug_timer();
pr_info("finished\n");
return 0;
probe_failed_sysfs_register:
probe_failed_platform_variable:
probe_failed_device_init_wakeup:
gpio_free(drvdata->fw_ipc.gpio);
probe_failed_ipc_gpio:
gpio_free(drvdata->fd_gpio.gpio);
probe_failed_fd_gpio:
pinctrl_put(drvdata->p);
probe_failed_pinctrl:
wake_lock_destroy(&drvdata->fp_spi_lock);
wake_lock_destroy(&drvdata->fp_signal_lock);
device_destroy(drvdata->qbt2000_class, drvdata->qbt2000_ipc_cdev.dev);
device_destroy(drvdata->qbt2000_class, drvdata->qbt2000_fd_cdev.dev);
class_destroy(drvdata->qbt2000_class);
cdev_del(&drvdata->qbt2000_fd_cdev);
cdev_del(&drvdata->qbt2000_ipc_cdev);
unregister_chrdev_region(drvdata->qbt2000_fd_cdev.dev, 1);
unregister_chrdev_region(drvdata->qbt2000_ipc_cdev.dev, 1);
probe_failed_dev_register:
if (drvdata->regulator_1p8)
regulator_put(drvdata->regulator_1p8);
probe_failed_dt:
kfree(drvdata);
pr_err("failed: %d\n", rc);
return rc;
}
static int qbt2000_remove(struct platform_device *pdev)
{
struct qbt2000_drvdata *drvdata = platform_get_drvdata(pdev);
pr_info("called\n");
mutex_destroy(&drvdata->mutex);
mutex_destroy(&drvdata->ioctl_mutex);
mutex_destroy(&drvdata->fd_events_mutex);
mutex_destroy(&drvdata->ipc_events_mutex);
#ifdef QBT2000_AVOID_NOISE
mutex_destroy(&drvdata->fod_event_mutex);
#endif
device_destroy(drvdata->qbt2000_class, drvdata->qbt2000_fd_cdev.dev);
device_destroy(drvdata->qbt2000_class, drvdata->qbt2000_ipc_cdev.dev);
qbt2000_disable_debug_timer();
if (drvdata->regulator_1p8)
regulator_put(drvdata->regulator_1p8);
wake_lock_destroy(&drvdata->fp_spi_lock);
wake_lock_destroy(&drvdata->fp_signal_lock);
class_destroy(drvdata->qbt2000_class);
cdev_del(&drvdata->qbt2000_fd_cdev);
cdev_del(&drvdata->qbt2000_ipc_cdev);
unregister_chrdev_region(drvdata->qbt2000_fd_cdev.dev, 1);
unregister_chrdev_region(drvdata->qbt2000_ipc_cdev.dev, 1);
fingerprint_unregister(drvdata->fp_device, fp_attrs);
device_init_wakeup(&pdev->dev, 0);
qbt2000_unregister_platform_variable(drvdata);
pinctrl_put(drvdata->p);
kfree(drvdata);
return 0;
}
static int qbt2000_suspend(struct platform_device *pdev, pm_message_t state)
{
int rc = 0;
struct qbt2000_drvdata *drvdata = platform_get_drvdata(pdev);
#ifdef CONFIG_BATTERY_SAMSUNG
if (lpcharge)
return rc;
#endif
#if defined(ENABLE_SENSORS_FPRINT_SECURE)
fpsensor_goto_suspend = 1;
#endif
/*
* Returning an error code if driver currently making a TZ call.
* Note: The purpose of this driver is to ensure that the clocks are on
* while making a TZ call. Hence the clock check to determine if the
* driver will allow suspend to occur.
*/
if (!mutex_trylock(&drvdata->mutex))
return -EBUSY;
else {
#ifndef ENABLE_SENSORS_FPRINT_SECURE
qbt2000_power_control(drvdata, 0);
#endif
qbt2000_disable_debug_timer();
pr_info("ret = %d\n", rc);
}
mutex_unlock(&drvdata->mutex);
return 0;
}
static int qbt2000_resume(struct platform_device *pdev)
{
int rc = 0;
#ifdef CONFIG_BATTERY_SAMSUNG
if (lpcharge)
return rc;
#endif
#if defined(ENABLE_SENSORS_FPRINT_SECURE)
if (fpsensor_goto_suspend) {
fps_resume_set();
}
#else
qbt2000_power_control(g_data, 1);
#endif
qbt2000_enable_debug_timer();
pr_info("ret = %d\n", rc);
return 0;
}
static const struct of_device_id qbt2000_match[] = {
{ .compatible = "qcom,qbt2000" },
{}
};
static struct platform_driver qbt2000_plat_driver = {
.probe = qbt2000_probe,
.remove = qbt2000_remove,
.suspend = qbt2000_suspend,
.resume = qbt2000_resume,
.driver = {
.name = QBT2000_DEV,
.owner = THIS_MODULE,
.of_match_table = qbt2000_match,
},
};
module_platform_driver(qbt2000_plat_driver);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Kangwook.Her");
MODULE_DESCRIPTION("Samsung Electronics Inc. QBT2000 driver");