1375 lines
33 KiB
C
1375 lines
33 KiB
C
/* Copyright (c) 2016-2019, The Linux Foundation. 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 and
|
|
* only 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.
|
|
*/
|
|
#define DEBUG
|
|
#define pr_fmt(fmt) "qbt1000:%s: " fmt, __func__
|
|
|
|
#include <linux/delay.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/types.h>
|
|
#include <linux/cdev.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/workqueue.h>
|
|
#include <linux/pm.h>
|
|
#include <linux/of.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/atomic.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_gpio.h>
|
|
#include <linux/input.h>
|
|
#include <linux/kfifo.h>
|
|
#include <linux/poll.h>
|
|
#include <uapi/linux/qbt1000.h>
|
|
#include <soc/qcom/scm.h>
|
|
#include "../../misc/qseecom_kernel.h"
|
|
|
|
#define QBT1000_DEV "qbt1000"
|
|
#define QBT1000_IN_DEV_NAME "qbt1000_key_input"
|
|
#define QBT1000_IN_DEV_VERSION 0x0100
|
|
#define MAX_FW_EVENTS 128
|
|
#define FW_MAX_IPC_MSG_DATA_SIZE 0x500
|
|
#define SEND_TZ_CMD_RETRY_CNT 10
|
|
#define SEND_TZ_CMD_DELAY 50
|
|
|
|
/*
|
|
* shared buffer size - init with max value,
|
|
* user space will provide new value upon tz app load
|
|
*/
|
|
static uint32_t g_app_buf_size = SZ_256K;
|
|
static char const *const FP_APP_NAME = "fingerpr";
|
|
|
|
enum fp_app_cmd {
|
|
FP_APP_CMD_RX_IPC = 132,
|
|
|
|
};
|
|
|
|
enum ipc_msg_id {
|
|
IPC_MSG_ID_CBGE_REQUIRED = 29,
|
|
IPC_MSG_ID_GESTURE_SWIPE_DOWN = 50,
|
|
IPC_MSG_ID_GESTURE_SWIPE_UP = 51,
|
|
IPC_MSG_ID_GESTURE_SWIPE_LEFT = 52,
|
|
IPC_MSG_ID_GESTURE_SWIPE_RIGHT = 53,
|
|
IPC_MSG_ID_GESTURE_LONG_PRESS = 54,
|
|
IPC_MSG_ID_FINGER_ON_SENSOR = 55,
|
|
IPC_MSG_ID_FINGER_OFF_SENSOR = 56,
|
|
};
|
|
|
|
enum fd_indication_mode {
|
|
FD_IND_MODE_BIOMETRIC = 0,
|
|
FD_IND_MODE_GESTURES,
|
|
};
|
|
|
|
struct finger_detect_gpio {
|
|
int gpio;
|
|
int active_low;
|
|
int irq;
|
|
struct work_struct work;
|
|
unsigned int key_code;
|
|
int power_key_enabled;
|
|
int last_gpio_state;
|
|
int event_reported;
|
|
};
|
|
|
|
struct fw_event_desc {
|
|
enum qbt1000_fw_event ev;
|
|
};
|
|
|
|
struct fw_ipc_info {
|
|
int gpio;
|
|
int irq;
|
|
};
|
|
|
|
struct qbt1000_drvdata {
|
|
struct class *qbt1000_class;
|
|
struct cdev qbt1000_cdev;
|
|
struct device *dev;
|
|
char *qbt1000_node;
|
|
struct clk **clocks;
|
|
unsigned int clock_count;
|
|
uint8_t clock_state;
|
|
unsigned int root_clk_idx;
|
|
unsigned int frequency;
|
|
atomic_t available;
|
|
struct mutex mutex;
|
|
struct mutex fw_events_mutex;
|
|
struct input_dev *in_dev;
|
|
struct fw_ipc_info fw_ipc;
|
|
struct finger_detect_gpio fd_gpio;
|
|
DECLARE_KFIFO(fw_events, struct fw_event_desc, MAX_FW_EVENTS);
|
|
wait_queue_head_t read_wait_queue;
|
|
struct qseecom_handle *app_handle;
|
|
struct qseecom_handle *fp_app_handle;
|
|
enum fd_indication_mode fd_ind_mode;
|
|
bool ipc_is_stale;
|
|
bool gestures_enabled;
|
|
atomic_t wakelock_acquired;
|
|
};
|
|
|
|
/*
|
|
* struct fw_ipc_cmd -
|
|
* used to store IPC commands to/from firmware
|
|
* @status - indicates whether sending/getting the IPC message was successful
|
|
* @msg_type - the type of IPC message
|
|
* @msg_len - the length of the message data
|
|
* @resp_needed - whether a response is needed for this message
|
|
* @msg_data - any extra data associated with the message
|
|
*/
|
|
struct fw_ipc_cmd {
|
|
uint32_t status;
|
|
uint32_t numMsgs;
|
|
uint8_t msg_data[FW_MAX_IPC_MSG_DATA_SIZE];
|
|
};
|
|
|
|
struct fw_ipc_header {
|
|
uint32_t msg_type;
|
|
uint32_t msg_len;
|
|
uint32_t resp_needed;
|
|
};
|
|
|
|
/*
|
|
* 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 qbt1000_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},
|
|
};
|
|
|
|
/**
|
|
* get_cmd_rsp_buffers() - Function sets cmd & rsp buffer pointers and
|
|
* aligns buffer lengths
|
|
* @hdl: index of qseecom_handle
|
|
* @cmd: req buffer - set to qseecom_handle.sbuf
|
|
* @cmd_len: ptr to req buffer len
|
|
* @rsp: rsp buffer - set to qseecom_handle.sbuf + offset
|
|
* @rsp_len: ptr to rsp buffer len
|
|
*
|
|
* Return: 0 on success. Error code on failure.
|
|
*/
|
|
static int get_cmd_rsp_buffers(struct qseecom_handle *hdl,
|
|
void **cmd,
|
|
uint32_t *cmd_len,
|
|
void **rsp,
|
|
uint32_t *rsp_len)
|
|
{
|
|
/* 64 bytes alignment for QSEECOM */
|
|
uint64_t aligned_cmd_len = ALIGN((uint64_t)*cmd_len, 64);
|
|
uint64_t aligned_rsp_len = ALIGN((uint64_t)*rsp_len, 64);
|
|
|
|
if ((aligned_rsp_len + aligned_cmd_len) > (uint64_t)g_app_buf_size)
|
|
return -ENOMEM;
|
|
|
|
*cmd = hdl->sbuf;
|
|
*cmd_len = aligned_cmd_len;
|
|
*rsp = hdl->sbuf + *cmd_len;
|
|
*rsp_len = aligned_rsp_len;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* send_tz_cmd() - Function sends a command to TZ
|
|
*
|
|
* @drvdata: pointer to driver data
|
|
* @app_handle: handle to tz app
|
|
* @is_user_space: 1 if the cmd buffer is in user space, 0
|
|
* otherwise
|
|
* @cmd: command buffer to send
|
|
* @cmd_len: length of the command buffer
|
|
* @rsp: output, will be set to location of response buffer
|
|
* @rsp_len: max size of response
|
|
*
|
|
* Return: 0 on success.
|
|
*/
|
|
static int send_tz_cmd(struct qbt1000_drvdata *drvdata,
|
|
struct qseecom_handle *app_handle,
|
|
int is_user_space,
|
|
void *cmd, uint32_t cmd_len,
|
|
void **rsp, uint32_t rsp_len)
|
|
{
|
|
int rc = 0;
|
|
void *aligned_cmd;
|
|
void *aligned_rsp;
|
|
uint32_t aligned_cmd_len;
|
|
uint32_t aligned_rsp_len;
|
|
|
|
/* init command and response buffers and align lengths */
|
|
aligned_cmd_len = cmd_len;
|
|
aligned_rsp_len = rsp_len;
|
|
|
|
rc = get_cmd_rsp_buffers(app_handle,
|
|
(void **)&aligned_cmd,
|
|
&aligned_cmd_len,
|
|
(void **)&aligned_rsp,
|
|
&aligned_rsp_len);
|
|
|
|
if (rc != 0)
|
|
goto end;
|
|
|
|
if (!aligned_cmd) {
|
|
dev_err(drvdata->dev, "%s: Null command buffer\n",
|
|
__func__);
|
|
rc = -EINVAL;
|
|
goto end;
|
|
}
|
|
|
|
if (is_user_space) {
|
|
rc = copy_from_user(aligned_cmd, (void __user *)cmd,
|
|
cmd_len);
|
|
if (rc != 0) {
|
|
pr_err("failure to copy user space buf %d\n", rc);
|
|
rc = -EFAULT;
|
|
goto end;
|
|
}
|
|
} else
|
|
memcpy(aligned_cmd, cmd, cmd_len);
|
|
|
|
/* send cmd to TZ */
|
|
rc = qseecom_send_command(app_handle,
|
|
aligned_cmd,
|
|
aligned_cmd_len,
|
|
aligned_rsp,
|
|
aligned_rsp_len);
|
|
|
|
if (rc != 0) {
|
|
pr_err("failure to send tz cmd %d\n", rc);
|
|
goto end;
|
|
}
|
|
|
|
*rsp = aligned_rsp;
|
|
|
|
end:
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* qbt1000_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 qbt1000_open(struct inode *inode, struct file *file)
|
|
{
|
|
int rc = 0;
|
|
|
|
struct qbt1000_drvdata *drvdata = container_of(inode->i_cdev,
|
|
struct qbt1000_drvdata,
|
|
qbt1000_cdev);
|
|
file->private_data = drvdata;
|
|
|
|
pr_debug("%s begin\n", __func__);
|
|
/* disallowing concurrent opens */
|
|
if (!atomic_dec_and_test(&drvdata->available)) {
|
|
atomic_inc(&drvdata->available);
|
|
rc = -EBUSY;
|
|
}
|
|
|
|
pr_debug("%s end : %d\n", __func__, rc);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* qbt1000_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 qbt1000_release(struct inode *inode, struct file *file)
|
|
{
|
|
struct qbt1000_drvdata *drvdata;
|
|
|
|
if (!file || !file->private_data) {
|
|
pr_err("%s: NULL pointer passed", __func__);
|
|
return -EINVAL;
|
|
}
|
|
drvdata = file->private_data;
|
|
atomic_inc(&drvdata->available);
|
|
if (atomic_read(&drvdata->wakelock_acquired) != 0) {
|
|
pr_debug("Releasing wakelock\n");
|
|
pm_relax(drvdata->dev);
|
|
atomic_set(&drvdata->wakelock_acquired, 0);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* qbt1000_ioctl() - Function called when user space calls ioctl.
|
|
* @file: struct file - not used
|
|
* @cmd: cmd identifier:QBT1000_LOAD_APP,QBT1000_UNLOAD_APP,
|
|
* QBT1000_SEND_TZCMD
|
|
* @arg: ptr to relevant structe: either qbt1000_app or
|
|
* qbt1000_send_tz_cmd depending on which cmd is passed
|
|
*
|
|
* Return: 0 on success. Error code on failure.
|
|
*/
|
|
static long qbt1000_ioctl(
|
|
struct file *file, unsigned int cmd, unsigned long arg)
|
|
{
|
|
int rc = 0;
|
|
void __user *priv_arg = (void __user *)arg;
|
|
struct qbt1000_drvdata *drvdata;
|
|
|
|
if (!file || !file->private_data) {
|
|
pr_err("%s: NULL pointer passed", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
drvdata = file->private_data;
|
|
|
|
if (IS_ERR(priv_arg)) {
|
|
dev_err(drvdata->dev, "%s: invalid user space pointer %lu\n",
|
|
__func__, arg);
|
|
return -EINVAL;
|
|
}
|
|
|
|
mutex_lock(&drvdata->mutex);
|
|
|
|
pr_debug("%s %d\n", __func__, cmd);
|
|
|
|
switch (cmd) {
|
|
case QBT1000_LOAD_APP:
|
|
{
|
|
struct qbt1000_app app;
|
|
struct qseecom_handle *app_handle;
|
|
|
|
if (copy_from_user(&app, priv_arg,
|
|
sizeof(app)) != 0) {
|
|
rc = -EFAULT;
|
|
pr_err("failed copy from user space-LOAD\n");
|
|
goto end;
|
|
}
|
|
|
|
if (!app.app_handle) {
|
|
dev_err(drvdata->dev, "%s: LOAD app_handle is null\n",
|
|
__func__);
|
|
rc = -EINVAL;
|
|
goto end;
|
|
}
|
|
|
|
if (strcmp(app.name, FP_APP_NAME)) {
|
|
dev_err(drvdata->dev, "%s: Invalid app name\n",
|
|
__func__);
|
|
rc = -EINVAL;
|
|
goto end;
|
|
}
|
|
|
|
if (drvdata->app_handle) {
|
|
dev_err(drvdata->dev, "%s: LOAD app already loaded, unloading first\n",
|
|
__func__);
|
|
drvdata->fp_app_handle = 0;
|
|
rc = qseecom_shutdown_app(&drvdata->app_handle);
|
|
if (rc != 0) {
|
|
dev_err(drvdata->dev, "%s: LOAD current app failed to shutdown\n",
|
|
__func__);
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
pr_debug("app %s load before\n", app.name);
|
|
app.name[MAX_NAME_SIZE - 1] = '\0';
|
|
|
|
/* start the TZ app */
|
|
rc = qseecom_start_app(
|
|
&drvdata->app_handle, app.name, app.size);
|
|
if (rc == 0) {
|
|
g_app_buf_size = app.size;
|
|
rc = qseecom_set_bandwidth(drvdata->app_handle,
|
|
app.high_band_width == 1 ? true : false);
|
|
if (rc != 0) {
|
|
/* log error, allow to continue */
|
|
pr_err("App %s failed to set bw\n", app.name);
|
|
}
|
|
} else {
|
|
dev_err(drvdata->dev, "%s: Fingerprint Trusted App failed to load\n",
|
|
__func__);
|
|
goto end;
|
|
}
|
|
|
|
/* copy a fake app handle to user */
|
|
app_handle = drvdata->app_handle ?
|
|
(struct qseecom_handle *)123456 : 0;
|
|
rc = copy_to_user((void __user *)app.app_handle, &app_handle,
|
|
sizeof(*app.app_handle));
|
|
|
|
if (rc != 0) {
|
|
dev_err(drvdata->dev,
|
|
"%s: Failed copy 2us LOAD rc:%d\n",
|
|
__func__, rc);
|
|
rc = -ENOMEM;
|
|
goto end;
|
|
}
|
|
|
|
pr_debug("app %s load after\n", app.name);
|
|
|
|
drvdata->fp_app_handle = drvdata->app_handle;
|
|
break;
|
|
}
|
|
case QBT1000_UNLOAD_APP:
|
|
{
|
|
struct qbt1000_app app;
|
|
struct qseecom_handle *app_handle = 0;
|
|
|
|
if (copy_from_user(&app, priv_arg,
|
|
sizeof(app)) != 0) {
|
|
rc = -ENOMEM;
|
|
pr_err("failed copy from user space-UNLOAD\n");
|
|
goto end;
|
|
}
|
|
|
|
if (!app.app_handle) {
|
|
dev_err(drvdata->dev, "%s: UNLOAD app_handle is null\n",
|
|
__func__);
|
|
rc = -EINVAL;
|
|
goto end;
|
|
}
|
|
|
|
rc = copy_from_user(&app_handle, app.app_handle,
|
|
sizeof(app_handle));
|
|
|
|
if (rc != 0) {
|
|
dev_err(drvdata->dev,
|
|
"%s: Failed copy from user space-UNLOAD handle rc:%d\n",
|
|
__func__, rc);
|
|
rc = -ENOMEM;
|
|
goto end;
|
|
}
|
|
|
|
/* if the app hasn't been loaded already, return err */
|
|
if (!drvdata->app_handle) {
|
|
pr_err("app not loaded\n");
|
|
rc = -EINVAL;
|
|
goto end;
|
|
}
|
|
|
|
if (drvdata->fp_app_handle == drvdata->app_handle)
|
|
drvdata->fp_app_handle = 0;
|
|
|
|
/* set bw & shutdown the TZ app */
|
|
qseecom_set_bandwidth(drvdata->app_handle,
|
|
app.high_band_width == 1 ? true : false);
|
|
rc = qseecom_shutdown_app(&drvdata->app_handle);
|
|
if (rc != 0) {
|
|
pr_err("app failed to shutdown\n");
|
|
goto end;
|
|
}
|
|
|
|
/* copy the app handle (should be null) to user */
|
|
rc = copy_to_user((void __user *)app.app_handle, &app_handle,
|
|
sizeof(*app.app_handle));
|
|
|
|
if (rc != 0) {
|
|
dev_err(drvdata->dev,
|
|
"%s: Failed copy 2us UNLOAD rc:%d\n",
|
|
__func__, rc);
|
|
rc = -ENOMEM;
|
|
goto end;
|
|
}
|
|
|
|
break;
|
|
}
|
|
case QBT1000_SEND_TZCMD:
|
|
{
|
|
struct qbt1000_send_tz_cmd tzcmd;
|
|
void *rsp_buf;
|
|
|
|
if (copy_from_user(&tzcmd, priv_arg,
|
|
sizeof(tzcmd))
|
|
!= 0) {
|
|
rc = -EFAULT;
|
|
pr_err("failed copy from user space %d\n", rc);
|
|
goto end;
|
|
}
|
|
|
|
if (tzcmd.req_buf_len > g_app_buf_size ||
|
|
tzcmd.rsp_buf_len > g_app_buf_size) {
|
|
rc = -ENOMEM;
|
|
pr_err("invalid cmd buf len, req=%d, rsp=%d\n",
|
|
tzcmd.req_buf_len, tzcmd.rsp_buf_len);
|
|
goto end;
|
|
}
|
|
|
|
/* if the app hasn't been loaded already, return err */
|
|
if (!drvdata->app_handle) {
|
|
pr_err("app not loaded\n");
|
|
rc = -EINVAL;
|
|
goto end;
|
|
}
|
|
|
|
rc = send_tz_cmd(drvdata,
|
|
drvdata->app_handle, 1,
|
|
tzcmd.req_buf, tzcmd.req_buf_len,
|
|
&rsp_buf, tzcmd.rsp_buf_len);
|
|
|
|
if (rc < 0) {
|
|
pr_err("failure sending command to tz\n");
|
|
goto end;
|
|
}
|
|
|
|
/* copy rsp buf back to user space buffer */
|
|
rc = copy_to_user((void __user *)tzcmd.rsp_buf,
|
|
rsp_buf, tzcmd.rsp_buf_len);
|
|
if (rc != 0) {
|
|
pr_err("failed copy 2us rc:%d bytes %d:\n",
|
|
rc, tzcmd.rsp_buf_len);
|
|
rc = -EFAULT;
|
|
goto end;
|
|
}
|
|
|
|
break;
|
|
}
|
|
case QBT1000_SET_FINGER_DETECT_KEY:
|
|
{
|
|
struct qbt1000_set_finger_detect_key set_fd_key;
|
|
|
|
if (copy_from_user(&set_fd_key, priv_arg,
|
|
sizeof(set_fd_key))
|
|
!= 0) {
|
|
rc = -EFAULT;
|
|
pr_err("failed copy from user space %d\n", rc);
|
|
goto end;
|
|
}
|
|
drvdata->fd_gpio.key_code = set_fd_key.key_code;
|
|
break;
|
|
}
|
|
case QBT1000_CONFIGURE_POWER_KEY:
|
|
{
|
|
struct qbt1000_configure_power_key power_key;
|
|
|
|
if (copy_from_user(&power_key, priv_arg,
|
|
sizeof(power_key))
|
|
!= 0) {
|
|
rc = -EFAULT;
|
|
pr_err("failed copy from user space %d\n", rc);
|
|
goto end;
|
|
}
|
|
|
|
drvdata->fd_gpio.power_key_enabled = power_key.enable;
|
|
break;
|
|
}
|
|
case QBT1000_ENABLE_GESTURES:
|
|
{
|
|
struct qbt1000_enable_gestures enable_gestures;
|
|
|
|
if (copy_from_user(&enable_gestures, priv_arg,
|
|
sizeof(enable_gestures))
|
|
!= 0) {
|
|
rc = -EFAULT;
|
|
pr_err("failed copy from user space %d\n", rc);
|
|
goto end;
|
|
}
|
|
|
|
if (enable_gestures.enable) {
|
|
drvdata->fd_ind_mode = FD_IND_MODE_GESTURES;
|
|
drvdata->gestures_enabled = true;
|
|
} else {
|
|
drvdata->fd_ind_mode = FD_IND_MODE_BIOMETRIC;
|
|
drvdata->gestures_enabled = false;
|
|
}
|
|
break;
|
|
}
|
|
case QBT1000_ACQUIRE_WAKELOCK:
|
|
{
|
|
if (atomic_read(&drvdata->wakelock_acquired) == 0) {
|
|
pr_debug("Acquiring wakelock\n");
|
|
pm_stay_awake(drvdata->dev);
|
|
}
|
|
atomic_inc(&drvdata->wakelock_acquired);
|
|
break;
|
|
}
|
|
case QBT1000_RELEASE_WAKELOCK:
|
|
{
|
|
if (atomic_read(&drvdata->wakelock_acquired) == 0)
|
|
break;
|
|
if (atomic_dec_and_test(&drvdata->wakelock_acquired)) {
|
|
pr_debug("Releasing wakelock\n");
|
|
pm_relax(drvdata->dev);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
pr_err("invalid cmd %d\n", cmd);
|
|
rc = -ENOIOCTLCMD;
|
|
goto end;
|
|
}
|
|
|
|
end:
|
|
mutex_unlock(&drvdata->mutex);
|
|
return rc;
|
|
}
|
|
|
|
static int get_events_fifo_len_locked(struct qbt1000_drvdata *drvdata)
|
|
{
|
|
int len;
|
|
|
|
mutex_lock(&drvdata->fw_events_mutex);
|
|
len = kfifo_len(&drvdata->fw_events);
|
|
mutex_unlock(&drvdata->fw_events_mutex);
|
|
|
|
return len;
|
|
}
|
|
|
|
static ssize_t qbt1000_read(struct file *filp, char __user *ubuf,
|
|
size_t cnt, loff_t *ppos)
|
|
{
|
|
struct fw_event_desc fw_event;
|
|
struct qbt1000_drvdata *drvdata = filp->private_data;
|
|
|
|
if (cnt < sizeof(fw_event.ev))
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&drvdata->fw_events_mutex);
|
|
|
|
while (kfifo_len(&drvdata->fw_events) == 0) {
|
|
mutex_unlock(&drvdata->fw_events_mutex);
|
|
|
|
if (filp->f_flags & O_NONBLOCK)
|
|
return -EAGAIN;
|
|
|
|
pr_debug("fw_events fifo: empty, waiting\n");
|
|
|
|
if (wait_event_interruptible(drvdata->read_wait_queue,
|
|
(get_events_fifo_len_locked(drvdata) > 0)))
|
|
return -ERESTARTSYS;
|
|
|
|
mutex_lock(&drvdata->fw_events_mutex);
|
|
}
|
|
|
|
if (!kfifo_get(&drvdata->fw_events, &fw_event)) {
|
|
pr_debug("fw_events fifo: unexpectedly empty\n");
|
|
|
|
mutex_unlock(&drvdata->fw_events_mutex);
|
|
return -EINVAL;
|
|
}
|
|
|
|
mutex_unlock(&drvdata->fw_events_mutex);
|
|
|
|
pr_debug("fw_event: %d\n", (int)fw_event.ev);
|
|
return copy_to_user(ubuf, &fw_event.ev, sizeof(fw_event.ev));
|
|
}
|
|
|
|
static unsigned int qbt1000_poll(struct file *filp,
|
|
struct poll_table_struct *wait)
|
|
{
|
|
struct qbt1000_drvdata *drvdata = filp->private_data;
|
|
unsigned int mask = 0;
|
|
|
|
poll_wait(filp, &drvdata->read_wait_queue, wait);
|
|
|
|
if (kfifo_len(&drvdata->fw_events) > 0)
|
|
mask |= (POLLIN | POLLRDNORM);
|
|
|
|
return mask;
|
|
}
|
|
|
|
static const struct file_operations qbt1000_fops = {
|
|
.owner = THIS_MODULE,
|
|
.unlocked_ioctl = qbt1000_ioctl,
|
|
.open = qbt1000_open,
|
|
.release = qbt1000_release,
|
|
.read = qbt1000_read,
|
|
.poll = qbt1000_poll
|
|
};
|
|
|
|
static int qbt1000_dev_register(struct qbt1000_drvdata *drvdata)
|
|
{
|
|
dev_t dev_no;
|
|
int ret = 0;
|
|
size_t node_size;
|
|
char *node_name = QBT1000_DEV;
|
|
struct device *dev = drvdata->dev;
|
|
struct device *device;
|
|
|
|
node_size = strlen(node_name) + 1;
|
|
|
|
drvdata->qbt1000_node = devm_kzalloc(dev, node_size, GFP_KERNEL);
|
|
if (!drvdata->qbt1000_node) {
|
|
ret = -ENOMEM;
|
|
goto err_alloc;
|
|
}
|
|
|
|
strlcpy(drvdata->qbt1000_node, node_name, node_size);
|
|
|
|
ret = alloc_chrdev_region(&dev_no, 0, 1, drvdata->qbt1000_node);
|
|
if (ret) {
|
|
pr_err("alloc_chrdev_region failed %d\n", ret);
|
|
goto err_alloc;
|
|
}
|
|
|
|
cdev_init(&drvdata->qbt1000_cdev, &qbt1000_fops);
|
|
|
|
drvdata->qbt1000_cdev.owner = THIS_MODULE;
|
|
ret = cdev_add(&drvdata->qbt1000_cdev, dev_no, 1);
|
|
if (ret) {
|
|
pr_err("cdev_add failed %d\n", ret);
|
|
goto err_cdev_add;
|
|
}
|
|
|
|
drvdata->qbt1000_class = class_create(THIS_MODULE,
|
|
drvdata->qbt1000_node);
|
|
if (IS_ERR(drvdata->qbt1000_class)) {
|
|
ret = PTR_ERR(drvdata->qbt1000_class);
|
|
pr_err("class_create failed %d\n", ret);
|
|
goto err_class_create;
|
|
}
|
|
|
|
device = device_create(drvdata->qbt1000_class, NULL,
|
|
drvdata->qbt1000_cdev.dev, drvdata,
|
|
drvdata->qbt1000_node);
|
|
if (IS_ERR(device)) {
|
|
ret = PTR_ERR(device);
|
|
pr_err("device_create failed %d\n", ret);
|
|
goto err_dev_create;
|
|
}
|
|
|
|
return 0;
|
|
err_dev_create:
|
|
class_destroy(drvdata->qbt1000_class);
|
|
err_class_create:
|
|
cdev_del(&drvdata->qbt1000_cdev);
|
|
err_cdev_add:
|
|
unregister_chrdev_region(drvdata->qbt1000_cdev.dev, 1);
|
|
err_alloc:
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* qbt1000_create_input_device() - Function allocates an input
|
|
* device, configures it for key events and registers it
|
|
*
|
|
* @drvdata: ptr to driver data
|
|
*
|
|
* Return: 0 on success. Error code on failure.
|
|
*/
|
|
static int qbt1000_create_input_device(struct qbt1000_drvdata *drvdata)
|
|
{
|
|
int rc = 0;
|
|
|
|
drvdata->in_dev = input_allocate_device();
|
|
if (drvdata->in_dev == NULL) {
|
|
dev_err(drvdata->dev, "%s: input_allocate_device() failed\n",
|
|
__func__);
|
|
rc = -ENOMEM;
|
|
goto end;
|
|
}
|
|
|
|
drvdata->in_dev->name = QBT1000_IN_DEV_NAME;
|
|
drvdata->in_dev->phys = NULL;
|
|
drvdata->in_dev->id.bustype = BUS_HOST;
|
|
drvdata->in_dev->id.vendor = 0x0001;
|
|
drvdata->in_dev->id.product = 0x0001;
|
|
drvdata->in_dev->id.version = QBT1000_IN_DEV_VERSION;
|
|
|
|
drvdata->in_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
|
|
drvdata->in_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
|
|
|
|
drvdata->in_dev->keybit[BIT_WORD(KEY_HOMEPAGE)] |=
|
|
BIT_MASK(KEY_HOMEPAGE);
|
|
drvdata->in_dev->keybit[BIT_WORD(KEY_CAMERA)] |=
|
|
BIT_MASK(KEY_CAMERA);
|
|
drvdata->in_dev->keybit[BIT_WORD(KEY_VOLUMEDOWN)] |=
|
|
BIT_MASK(KEY_VOLUMEDOWN);
|
|
drvdata->in_dev->keybit[BIT_WORD(KEY_POWER)] |=
|
|
BIT_MASK(KEY_POWER);
|
|
drvdata->in_dev->keybit[BIT_WORD(KEY_FP_GESTURE_UP)] |=
|
|
BIT_MASK(KEY_FP_GESTURE_UP);
|
|
drvdata->in_dev->keybit[BIT_WORD(KEY_FP_GESTURE_DOWN)] |=
|
|
BIT_MASK(KEY_FP_GESTURE_DOWN);
|
|
drvdata->in_dev->keybit[BIT_WORD(KEY_FP_GESTURE_LEFT)] |=
|
|
BIT_MASK(KEY_FP_GESTURE_LEFT);
|
|
drvdata->in_dev->keybit[BIT_WORD(KEY_FP_GESTURE_RIGHT)] |=
|
|
BIT_MASK(KEY_FP_GESTURE_RIGHT);
|
|
drvdata->in_dev->keybit[BIT_WORD(KEY_FP_GESTURE_LONG_PRESS)] |=
|
|
BIT_MASK(KEY_FP_GESTURE_LONG_PRESS);
|
|
drvdata->in_dev->keybit[BIT_WORD(KEY_FP_GESTURE_TAP)] |=
|
|
BIT_MASK(KEY_FP_GESTURE_TAP);
|
|
|
|
input_set_abs_params(drvdata->in_dev, ABS_X,
|
|
0,
|
|
1000,
|
|
0, 0);
|
|
input_set_abs_params(drvdata->in_dev, ABS_Y,
|
|
0,
|
|
1000,
|
|
0, 0);
|
|
|
|
rc = input_register_device(drvdata->in_dev);
|
|
if (rc) {
|
|
dev_err(drvdata->dev, "%s: input_reg_dev() failed %d\n",
|
|
__func__, rc);
|
|
goto end;
|
|
}
|
|
|
|
end:
|
|
if (rc)
|
|
input_free_device(drvdata->in_dev);
|
|
return rc;
|
|
}
|
|
|
|
static void purge_finger_events(struct qbt1000_drvdata *drvdata)
|
|
{
|
|
int i, fifo_len;
|
|
struct fw_event_desc fw_event;
|
|
|
|
fifo_len = kfifo_len(&drvdata->fw_events);
|
|
|
|
for (i = 0; i < fifo_len; i++) {
|
|
if (!kfifo_get(&drvdata->fw_events, &fw_event))
|
|
pr_err("fw events fifo: could not remove oldest item\n");
|
|
else if (fw_event.ev != FW_EVENT_FINGER_DOWN
|
|
&& fw_event.ev != FW_EVENT_FINGER_UP)
|
|
kfifo_put(&drvdata->fw_events, fw_event);
|
|
}
|
|
}
|
|
|
|
static void gpio_report_event(struct qbt1000_drvdata *drvdata, int state)
|
|
{
|
|
struct fw_event_desc fw_event;
|
|
|
|
if (drvdata->fd_gpio.event_reported
|
|
&& state == drvdata->fd_gpio.last_gpio_state)
|
|
return;
|
|
|
|
pr_debug("gpio %d: report state %d\n", drvdata->fd_gpio.gpio, state);
|
|
|
|
drvdata->fd_gpio.event_reported = 1;
|
|
drvdata->fd_gpio.last_gpio_state = state;
|
|
|
|
if (drvdata->fd_ind_mode == FD_IND_MODE_GESTURES) {
|
|
pr_debug("tap detected\n");
|
|
/*
|
|
* If a gesture IPC was sent but not yet decrypted and
|
|
* dealt with mark it as stale and don't report it
|
|
*/
|
|
drvdata->ipc_is_stale = true;
|
|
input_event(drvdata->in_dev,
|
|
EV_KEY, KEY_FP_GESTURE_TAP, 1);
|
|
input_sync(drvdata->in_dev);
|
|
input_event(drvdata->in_dev,
|
|
EV_KEY, KEY_FP_GESTURE_TAP, 0);
|
|
input_sync(drvdata->in_dev);
|
|
} else if (drvdata->fd_ind_mode == FD_IND_MODE_BIOMETRIC) {
|
|
if (state && drvdata->fd_gpio.power_key_enabled) {
|
|
input_event(drvdata->in_dev, EV_KEY, KEY_POWER, 1);
|
|
input_sync(drvdata->in_dev);
|
|
input_event(drvdata->in_dev, EV_KEY, KEY_POWER, 0);
|
|
input_sync(drvdata->in_dev);
|
|
} else if (!drvdata->gestures_enabled) {
|
|
input_event(drvdata->in_dev, EV_KEY,
|
|
drvdata->fd_gpio.key_code, !!state);
|
|
input_sync(drvdata->in_dev);
|
|
}
|
|
fw_event.ev = state ? FW_EVENT_FINGER_DOWN : FW_EVENT_FINGER_UP;
|
|
|
|
mutex_lock(&drvdata->fw_events_mutex);
|
|
|
|
if (kfifo_is_full(&drvdata->fw_events)) {
|
|
struct fw_event_desc dummy_fw_event;
|
|
|
|
pr_warn("fw events fifo: full, dropping oldest item\n");
|
|
if (!kfifo_get(&drvdata->fw_events, &dummy_fw_event))
|
|
pr_err("fw events fifo: could not remove oldest item\n");
|
|
}
|
|
|
|
purge_finger_events(drvdata);
|
|
|
|
if (!kfifo_put(&drvdata->fw_events, fw_event))
|
|
pr_err("fw events fifo: error adding item\n");
|
|
|
|
mutex_unlock(&drvdata->fw_events_mutex);
|
|
wake_up_interruptible(&drvdata->read_wait_queue);
|
|
} else {
|
|
pr_err("invalid mode %d\n", drvdata->fd_ind_mode);
|
|
}
|
|
}
|
|
|
|
static void qbt1000_gpio_report_event(struct qbt1000_drvdata *drvdata)
|
|
{
|
|
int state;
|
|
|
|
state = (__gpio_get_value(drvdata->fd_gpio.gpio) ? 1 : 0)
|
|
^ drvdata->fd_gpio.active_low;
|
|
gpio_report_event(drvdata, state);
|
|
}
|
|
static void qbt1000_gpio_work_func(struct work_struct *work)
|
|
{
|
|
struct qbt1000_drvdata *drvdata =
|
|
container_of(work, struct qbt1000_drvdata, fd_gpio.work);
|
|
|
|
qbt1000_gpio_report_event(drvdata);
|
|
|
|
pm_relax(drvdata->dev);
|
|
}
|
|
|
|
static irqreturn_t qbt1000_gpio_isr(int irq, void *dev_id)
|
|
{
|
|
struct qbt1000_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;
|
|
}
|
|
|
|
pm_stay_awake(drvdata->dev);
|
|
schedule_work(&drvdata->fd_gpio.work);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
/**
|
|
* qbt1000_ipc_irq_handler() - function processes IPC
|
|
* interrupts on its own thread
|
|
* @irq: the interrupt that occurred
|
|
* @dev_id: pointer to the qbt1000_drvdata
|
|
*
|
|
* Return: IRQ_HANDLED when complete
|
|
*/
|
|
static irqreturn_t qbt1000_ipc_irq_handler(int irq, void *dev_id)
|
|
{
|
|
uint8_t *msg_buffer;
|
|
struct fw_ipc_cmd *rx_cmd;
|
|
struct fw_ipc_header *header;
|
|
int i, j;
|
|
uint32_t rxipc = FP_APP_CMD_RX_IPC;
|
|
struct qbt1000_drvdata *drvdata = (struct qbt1000_drvdata *)dev_id;
|
|
int rc = 0;
|
|
uint32_t retry_count = SEND_TZ_CMD_RETRY_CNT;
|
|
|
|
pm_stay_awake(drvdata->dev);
|
|
|
|
mutex_lock(&drvdata->mutex);
|
|
|
|
if (irq != drvdata->fw_ipc.irq) {
|
|
pr_warn("invalid irq %d (expected %d)\n",
|
|
irq, drvdata->fw_ipc.irq);
|
|
goto end;
|
|
}
|
|
|
|
pr_debug("firmware interrupt received (irq %d)\n", irq);
|
|
|
|
if (!drvdata->fp_app_handle)
|
|
goto end;
|
|
|
|
/*
|
|
* Indicate that IPC is valid (not stale)
|
|
* This is relevant only for gestures IPCs
|
|
*/
|
|
drvdata->ipc_is_stale = false;
|
|
|
|
while (retry_count > 0) {
|
|
/*
|
|
* send the TZ command to fetch the message from firmware
|
|
* TZ will process the message if it can
|
|
*/
|
|
rc = send_tz_cmd(drvdata, drvdata->fp_app_handle, 0,
|
|
&rxipc, sizeof(rxipc),
|
|
(void *)&rx_cmd, sizeof(*rx_cmd));
|
|
if (rc < 0) {
|
|
/* sleep before retry */
|
|
msleep(SEND_TZ_CMD_DELAY);
|
|
retry_count -= 1;
|
|
continue;
|
|
} else {
|
|
pr_err("retry_count %d\n", retry_count);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (rc < 0) {
|
|
pr_err("failure sending tz cmd %d\n", rxipc);
|
|
goto end;
|
|
}
|
|
|
|
if (rx_cmd->status != 0) {
|
|
pr_err("tz command failed to complete\n");
|
|
goto end;
|
|
}
|
|
|
|
msg_buffer = rx_cmd->msg_data;
|
|
|
|
for (j = 0; j < rx_cmd->numMsgs; j++) {
|
|
unsigned int key_event_code = 0;
|
|
|
|
header = (struct fw_ipc_header *) msg_buffer;
|
|
|
|
switch (header->msg_type) {
|
|
case IPC_MSG_ID_GESTURE_SWIPE_UP:
|
|
key_event_code = KEY_FP_GESTURE_UP;
|
|
break;
|
|
case IPC_MSG_ID_GESTURE_SWIPE_DOWN:
|
|
key_event_code = KEY_FP_GESTURE_DOWN;
|
|
break;
|
|
case IPC_MSG_ID_GESTURE_SWIPE_LEFT:
|
|
key_event_code = KEY_FP_GESTURE_LEFT;
|
|
break;
|
|
case IPC_MSG_ID_GESTURE_SWIPE_RIGHT:
|
|
key_event_code = KEY_FP_GESTURE_RIGHT;
|
|
break;
|
|
case IPC_MSG_ID_GESTURE_LONG_PRESS:
|
|
key_event_code = KEY_FP_GESTURE_LONG_PRESS;
|
|
break;
|
|
default:
|
|
key_event_code = 0;
|
|
break;
|
|
}
|
|
|
|
if (key_event_code != 0) {
|
|
pr_debug("geseture detected %d\n", key_event_code);
|
|
/*
|
|
* Send gesture event if no tap arrived
|
|
* since the IPC was sent
|
|
*/
|
|
if (drvdata->ipc_is_stale == false) {
|
|
input_event(drvdata->in_dev, EV_KEY,
|
|
key_event_code, 1);
|
|
input_sync(drvdata->in_dev);
|
|
input_event(drvdata->in_dev, EV_KEY,
|
|
key_event_code, 0);
|
|
input_sync(drvdata->in_dev);
|
|
}
|
|
} else {
|
|
|
|
/*
|
|
* given the IPC message type, search for a
|
|
* corresponding event for the driver client.
|
|
* If found, add to the events FIFO
|
|
*/
|
|
for (i = 0; i < ARRAY_SIZE(g_msg_to_event); i++) {
|
|
uint32_t msg_type = g_msg_to_event[i].msg_type;
|
|
|
|
if (msg_type == header->msg_type) {
|
|
enum qbt1000_fw_event ev =
|
|
g_msg_to_event[i].fw_event;
|
|
struct fw_event_desc fw_ev_desc;
|
|
|
|
mutex_lock(&drvdata->fw_events_mutex);
|
|
pr_debug("fw events: add %d\n",
|
|
(int) ev);
|
|
fw_ev_desc.ev = ev;
|
|
|
|
purge_finger_events(drvdata);
|
|
|
|
if (!kfifo_put(&drvdata->fw_events,
|
|
fw_ev_desc))
|
|
pr_err("fw events: fifo full");
|
|
pr_err(", drop event %d\n",
|
|
(int) ev);
|
|
|
|
mutex_unlock(&drvdata->fw_events_mutex);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
msg_buffer += sizeof(*header) + header->msg_len;
|
|
}
|
|
wake_up_interruptible(&drvdata->read_wait_queue);
|
|
end:
|
|
mutex_unlock(&drvdata->mutex);
|
|
pm_relax(drvdata->dev);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int setup_fd_gpio_irq(struct platform_device *pdev,
|
|
struct qbt1000_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 end;
|
|
}
|
|
|
|
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 end;
|
|
}
|
|
|
|
drvdata->fd_gpio.irq = irq;
|
|
INIT_WORK(&drvdata->fd_gpio.work, qbt1000_gpio_work_func);
|
|
|
|
rc = devm_request_any_context_irq(&pdev->dev, drvdata->fd_gpio.irq,
|
|
qbt1000_gpio_isr, 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 end;
|
|
}
|
|
|
|
end:
|
|
return rc;
|
|
}
|
|
|
|
static int setup_ipc_irq(struct platform_device *pdev,
|
|
struct qbt1000_drvdata *drvdata)
|
|
{
|
|
int rc = 0;
|
|
const char *desc = "qbt_ipc";
|
|
|
|
drvdata->fw_ipc.irq = gpio_to_irq(drvdata->fw_ipc.gpio);
|
|
pr_debug("\nirq %d gpio %d\n",
|
|
drvdata->fw_ipc.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 end;
|
|
}
|
|
|
|
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 end;
|
|
}
|
|
|
|
rc = devm_request_threaded_irq(&pdev->dev,
|
|
drvdata->fw_ipc.irq,
|
|
NULL,
|
|
qbt1000_ipc_irq_handler,
|
|
IRQF_ONESHOT | IRQF_TRIGGER_RISING,
|
|
desc,
|
|
drvdata);
|
|
|
|
if (rc < 0) {
|
|
pr_err("failed to register for ipc irq %d, rc = %d\n",
|
|
drvdata->fw_ipc.irq, rc);
|
|
goto end;
|
|
}
|
|
|
|
end:
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* qbt1000_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 qbt1000_read_device_tree(struct platform_device *pdev,
|
|
struct qbt1000_drvdata *drvdata)
|
|
{
|
|
int rc = 0;
|
|
uint32_t rate;
|
|
int gpio;
|
|
enum of_gpio_flags flags;
|
|
|
|
/* read clock frequency */
|
|
if (of_property_read_u32(pdev->dev.of_node,
|
|
"clock-frequency", &rate) == 0) {
|
|
pr_debug("clk frequency %d\n", rate);
|
|
drvdata->frequency = rate;
|
|
}
|
|
|
|
/* 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 end;
|
|
}
|
|
|
|
/**
|
|
* TODO: Need to revisit after adding GPIO in DTSI- read
|
|
* finger detect GPIO configuration
|
|
*/
|
|
|
|
gpio = of_get_named_gpio_flags(pdev->dev.of_node,
|
|
"qcom,finger-detect-gpio", 0, &flags);
|
|
if (gpio < 0) {
|
|
pr_err("failed to get gpio flags\n");
|
|
rc = gpio;
|
|
goto end;
|
|
}
|
|
|
|
drvdata->fd_gpio.gpio = gpio;
|
|
drvdata->fd_gpio.active_low = flags & OF_GPIO_ACTIVE_LOW;
|
|
|
|
end:
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* qbt1000_probe() - Function loads hardware config from device tree
|
|
* @pdev: ptr to platform device object
|
|
*
|
|
* Return: 0 on success. Error code on failure.
|
|
*/
|
|
static int qbt1000_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct qbt1000_drvdata *drvdata;
|
|
int rc = 0;
|
|
|
|
pr_debug("%s begin\n", __func__);
|
|
drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL);
|
|
if (!drvdata)
|
|
return -ENOMEM;
|
|
|
|
drvdata->dev = &pdev->dev;
|
|
platform_set_drvdata(pdev, drvdata);
|
|
|
|
rc = qbt1000_read_device_tree(pdev, drvdata);
|
|
if (rc < 0)
|
|
goto end;
|
|
|
|
atomic_set(&drvdata->available, 1);
|
|
atomic_set(&drvdata->wakelock_acquired, 0);
|
|
|
|
mutex_init(&drvdata->mutex);
|
|
mutex_init(&drvdata->fw_events_mutex);
|
|
|
|
rc = qbt1000_dev_register(drvdata);
|
|
if (rc < 0)
|
|
goto end;
|
|
|
|
INIT_KFIFO(drvdata->fw_events);
|
|
init_waitqueue_head(&drvdata->read_wait_queue);
|
|
|
|
rc = qbt1000_create_input_device(drvdata);
|
|
if (rc < 0)
|
|
goto end;
|
|
|
|
rc = setup_fd_gpio_irq(pdev, drvdata);
|
|
if (rc < 0)
|
|
goto end;
|
|
|
|
rc = setup_ipc_irq(pdev, drvdata);
|
|
if (rc < 0)
|
|
goto end;
|
|
|
|
rc = device_init_wakeup(&pdev->dev, 1);
|
|
if (rc < 0)
|
|
goto end;
|
|
|
|
drvdata->fd_ind_mode = FD_IND_MODE_BIOMETRIC;
|
|
drvdata->ipc_is_stale = false;
|
|
drvdata->gestures_enabled = false;
|
|
|
|
end:
|
|
pr_debug("%s : %d\n", __func__, rc);
|
|
return rc;
|
|
}
|
|
|
|
static int qbt1000_remove(struct platform_device *pdev)
|
|
{
|
|
struct qbt1000_drvdata *drvdata = platform_get_drvdata(pdev);
|
|
|
|
input_unregister_device(drvdata->in_dev);
|
|
|
|
mutex_destroy(&drvdata->mutex);
|
|
mutex_destroy(&drvdata->fw_events_mutex);
|
|
|
|
device_destroy(drvdata->qbt1000_class, drvdata->qbt1000_cdev.dev);
|
|
class_destroy(drvdata->qbt1000_class);
|
|
cdev_del(&drvdata->qbt1000_cdev);
|
|
unregister_chrdev_region(drvdata->qbt1000_cdev.dev, 1);
|
|
|
|
device_init_wakeup(&pdev->dev, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int qbt1000_suspend(struct platform_device *pdev, pm_message_t state)
|
|
{
|
|
int rc = 0;
|
|
struct qbt1000_drvdata *drvdata = platform_get_drvdata(pdev);
|
|
|
|
/*
|
|
* 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;
|
|
|
|
if (drvdata->clock_state)
|
|
rc = -EBUSY;
|
|
else {
|
|
enable_irq_wake(drvdata->fd_gpio.irq);
|
|
enable_irq_wake(drvdata->fw_ipc.irq);
|
|
}
|
|
|
|
mutex_unlock(&drvdata->mutex);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int qbt1000_resume(struct platform_device *pdev)
|
|
{
|
|
struct qbt1000_drvdata *drvdata = platform_get_drvdata(pdev);
|
|
|
|
disable_irq_wake(drvdata->fd_gpio.irq);
|
|
disable_irq_wake(drvdata->fw_ipc.irq);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id qbt1000_match[] = {
|
|
{ .compatible = "qcom,qbt1000" },
|
|
{}
|
|
};
|
|
|
|
static struct platform_driver qbt1000_plat_driver = {
|
|
.probe = qbt1000_probe,
|
|
.remove = qbt1000_remove,
|
|
.suspend = qbt1000_suspend,
|
|
.resume = qbt1000_resume,
|
|
.driver = {
|
|
.name = "qbt1000",
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = qbt1000_match,
|
|
},
|
|
};
|
|
|
|
module_platform_driver(qbt1000_plat_driver);
|
|
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_DESCRIPTION("Qualcomm Technologies, Inc. QBT1000 driver");
|