blob: 45d3dd449dbc7ee4c40653eb5db886cf6d59dd84 [file] [log] [blame]
/*
* Copyright (C) 2015 Samsung Electronics, Inc.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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 <linux/kernel.h>
#include <linux/spinlock.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/ioctl.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/version.h>
#include <linux/types.h>
#include <linux/clk.h>
#include <asm/io.h>
#include <linux/i2c.h>
#include <linux/pm.h>
#include <linux/pm_runtime.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/input.h>
#include <linux/kthread.h>
#include <linux/completion.h>
#include <linux/atomic.h>
#include <linux/kallsyms.h>
#include <linux/irq.h>
#include <linux/irqdesc.h>
#include <linux/suspend.h>
#include "../../input/touchscreen/stm/fts_ts.h"
#include "tz_cdev.h"
#include "tztui.h"
MODULE_AUTHOR("Egor Uleyskiy <e.uleyskiy@samsung.com>");
MODULE_DESCRIPTION("TZTUI driver"); /* TrustZone Trusted User Interface */
MODULE_LICENSE("GPL");
int tztui_verbosity = 2;
module_param(tztui_verbosity, int, 0644);
MODULE_PARM_DESC(tztui_verbosity, "0: normal, 1: verbose, 2: debug");
#define TUI_OFFLINE 0x0
#define TUI_ONLINE 0x1
#define TZTUI_IOCTL_OPEN_SESSION 0x10
#define TZTUI_IOCTL_CLOSE_SESSION 0x11
#define TZTUI_IOCTL_CANCEL 0x12
#define TZTUI_IOCTL_BLANK_FB 0x20
#define TZTUI_IOCTL_UNBLANK_FB 0x21
#define TZTUI_IOCTL_GETINFO 0x30
struct dummy_qup_i2c_dev {
struct device *dev;
};
static DEFINE_SPINLOCK(tui_event_lock);
static DEFINE_MUTEX(tui_mode_mutex);
static DECLARE_WAIT_QUEUE_HEAD(irq_wait_queue);
static bool tui_status;
static int tui_mode = TUI_OFFLINE;
static unsigned int tui_event_flag;
static volatile int thread_handler_stop;
static struct device *local_dev, *local_i2c_dev;
static struct fts_ts_info *local_fts_info;
static irq_handler_t local_irq_handler;
static struct task_struct *ts;
static const char i2c_dev_name[] = "1-0049";
static atomic_t touch_requested;
static struct completion irq_completion;
static struct wake_lock wakelock;
static int request_touch(void);
static int release_touch(void);
static int fts_pm_notify(struct notifier_block *b, unsigned long ev, void *p)
{
(void)b;
(void)p;
DBG("%s: ev=%lu", __func__, ev);
if (ev == PM_SUSPEND_PREPARE ||
ev == PM_HIBERNATION_PREPARE)
release_touch();
return NOTIFY_DONE;
}
static struct notifier_block fts_pm_notifier;
int tui_get_current(void)
{
int mode;
mutex_lock(&tui_mode_mutex);
mode = tui_mode;
mutex_unlock(&tui_mode_mutex);
return mode;
}
int tui_set_mode(int mode)
{
int ret_val = (-EINVAL);
DBG("%s(%d) : Enter", __func__, mode);
mutex_lock(&tui_mode_mutex);
if (mode == tui_mode)
ret_val = (-EALREADY);
else if (mode == TUI_ONLINE) {
ret_val = request_touch();
if (!ret_val) {
tui_mode = mode;
tui_event_flag = 0;
thread_handler_stop = 0;
}
} else if (mode == TUI_OFFLINE) {
ret_val = release_touch();
if (!ret_val)
tui_mode = mode;
}
mutex_unlock(&tui_mode_mutex);
return ret_val;
}
void tui_set_event(unsigned int event)
{
unsigned long flags;
spin_lock_irqsave(&tui_event_lock, flags);
tui_event_flag |= event;
spin_unlock_irqrestore(&tui_event_lock, flags);
wake_up(&irq_wait_queue);
}
/* Get pointer to synaptic driver on i2c bus */
struct device *get_fts_device(const char *name)
{
struct device *dev = NULL;
DBG("%s() : Enter", __func__);
dev = bus_find_device_by_name(&i2c_bus_type, NULL, name);
return dev;
}
int thread_handler(void *data)
{
unsigned long flags, event;
(void)data;
do {
if (wait_event_interruptible(irq_wait_queue, (tui_event_flag || thread_handler_stop)) == ERESTARTSYS)
break;
spin_lock_irqsave(&tui_event_lock, flags);
event = tui_event_flag;
tui_event_flag = 0;
spin_unlock_irqrestore(&tui_event_lock, flags);
if (thread_handler_stop || kthread_should_stop())
break;
if (event) {
tztui_smc_tui_event(event);
complete(&irq_completion);
}
} while (1);
return 0;
}
/* GPIO irq handler */
static irqreturn_t tui_fts_irq(int irq, void *data)
{
(void)irq;
(void)data;
tui_set_event(TUI_EVENT_TOUCH_IRQ);
wait_for_completion_interruptible_timeout(&irq_completion, msecs_to_jiffies(10 * MSEC_PER_SEC));
return IRQ_HANDLED;
}
static irq_handler_t find_irqhandler(int irq, void *dev_id)
{
typedef struct irq_desc* (*irq_to_desc_func_t) (unsigned int irq);
irq_to_desc_func_t irq_to_desc = NULL;
struct irq_desc *desc;
struct irqaction *action = NULL;
struct irqaction *action_ptr = NULL;
unsigned long flags;
irq_to_desc = (irq_to_desc_func_t)((void **)kallsyms_lookup_name("irq_to_desc"));
if (!irq_to_desc)
return NULL;
desc = irq_to_desc(irq);
if (!desc)
return NULL;
raw_spin_lock_irqsave(&desc->lock, flags);
action_ptr = desc->action;
while (1) {
action = action_ptr;
if (!action) {
raw_spin_unlock_irqrestore(&desc->lock, flags);
return NULL;
}
if (action->dev_id == dev_id) {
DBG("FOUND thread_fn=%p", action->thread_fn);
DBG("FOUND handler=%p", action->handler);
break;
}
action_ptr = action->next;
}
if (!action->handler) {
raw_spin_unlock_irqrestore(&desc->lock, flags);
return NULL;
}
raw_spin_unlock_irqrestore(&desc->lock, flags);
return action->thread_fn;
}
/* Get pointer to qup-i2c device connected to touch controller */
struct device *get_i2c_device(struct fts_ts_info *fts_info)
{
struct device *dev = NULL;
struct i2c_adapter *i2c_adapt;
struct dummy_qup_i2c_dev *qup_dev;
DBG("%s() : Enter", __func__);
if (fts_info && fts_info->client) {
i2c_adapt = fts_info->client->adapter;
if (i2c_adapt) {
qup_dev = i2c_get_adapdata(i2c_adapt);
if (qup_dev)
dev = qup_dev->dev;
}
}
return dev;
}
/* Find synaptic touch driver by name and try to disallow its working */
static int request_touch(void)
{
int retval = -EFAULT;
struct device *dev = NULL, *i2c_dev = NULL;
struct fts_ts_info *fts_info;
irq_handler_t irqhandler = NULL;
(void)tui_fts_irq;
DBG("%s() : Enter", __func__);
if (atomic_read(&touch_requested) == 1)
return -EALREADY;
dev = get_fts_device(i2c_dev_name);
if (!dev) {
ERR("FTS Device %s not found.", i2c_dev_name);
retval = -ENXIO;
goto exit_err;
}
fts_info = dev_get_drvdata(dev);
if (!fts_info) {
ERR("Touchscreen driver internal data is empty.");
retval = -EFAULT;
goto exit_err;
}
wake_lock(&wakelock);
wake_lock(&fts_info->wakelock);
/*mutex_lock(&fts_info->device_mutex);*/
if (fts_info->touch_stopped) {
DBG("Sensor stopped.");
retval = -EBUSY;
goto exit_err;
}
if (pm_runtime_get(fts_info->client->adapter->dev.parent) < 0)
ERR("pm_runtime_get error");
INIT_COMPLETION(fts_info->st_powerdown);
/*INIT_COMPLETION(info->st_interrupt);*/
i2c_dev = get_i2c_device(fts_info);
if (!i2c_dev) {
ERR("QUP-I2C Device not found.");
retval = -ENXIO;
goto exit_err2;
}
DBG("start");
irqhandler = find_irqhandler(fts_info->irq, fts_info);
if (!irqhandler)
ERR("irqhandler not found");
synchronize_irq(fts_info->client->irq);
local_irq_handler = irqhandler;
fts_info->touch_stopped = true;
/* disable irq */
if (fts_info->irq_enabled) {
disable_irq(fts_info->irq);
free_irq(fts_info->irq, fts_info);
DBG("Disable IRQ of fts_touch");
fts_info->irq_enabled = false;
}
local_dev = dev;
local_i2c_dev = i2c_dev;
local_fts_info = fts_info;
INIT_COMPLETION(irq_completion);
retval = request_threaded_irq(fts_info->irq, NULL, tui_fts_irq, IRQF_TRIGGER_LOW | IRQF_ONESHOT, "tztui", tui_fts_irq);
/*retval = request_irq(fts_info->irq, tui_fts_irq, IRQF_TRIGGER_LOW | IRQF_ONESHOT, "tztui", tui_fts_irq);*/
if (retval < 0) {
ERR("Request IRQ failed:%d", retval);
goto exit_err3;
}
atomic_set(&touch_requested, 1);
fts_pm_notifier.notifier_call = fts_pm_notify;
fts_pm_notifier.priority = 0;
retval = register_pm_notifier(&fts_pm_notifier);
if (retval < 0) {
DBG("register_pm_notifier: retval=%d", retval);
goto exit_err4;
}
DBG("Touch requested");
return 0;
exit_err4:
retval = unregister_pm_notifier(&fts_pm_notifier);
if (retval < 0)
DBG("unregister_pm_notifier: retval=%d", retval);
exit_err3:
disable_irq(fts_info->irq);
retval = request_threaded_irq(fts_info->irq, NULL, irqhandler, IRQF_TRIGGER_LOW | IRQF_ONESHOT, FTS_TS_DRV_NAME, fts_info);
if (retval < 0)
ERR("Request IRQ FATAL:%d", retval);
fts_info->irq_enabled = true;
wake_unlock(&fts_info->wakelock);
wake_unlock(&wakelock);
complete(&fts_info->st_powerdown);
exit_err2:
pm_runtime_put(fts_info->client->adapter->dev.parent);
exit_err:
fts_info->touch_stopped = false;
local_dev = NULL;
local_fts_info = NULL;
local_i2c_dev = NULL;
local_irq_handler = NULL;
return retval;
}
/* Release synaptic touch driver */
static int release_touch(void)
{
int retval = -EFAULT;
struct fts_ts_info *fts_info = local_fts_info;
struct device *i2c_dev = local_i2c_dev, *dev = local_dev;
irq_handler_t irqhandler = local_irq_handler;
(void) i2c_dev;
(void) dev;
DBG("%s() : Enter", __func__);
if (atomic_read(&touch_requested) != 1)
return -EALREADY;
synchronize_irq(fts_info->client->irq);
fts_info->touch_stopped = false;
if (!fts_info->irq_enabled) {
disable_irq(fts_info->irq);
free_irq(fts_info->irq, tui_fts_irq);
retval = request_threaded_irq(fts_info->irq, NULL, irqhandler, IRQF_TRIGGER_LOW | IRQF_ONESHOT, FTS_TS_DRV_NAME, fts_info);
if (retval < 0) {
ERR("Request IRQ failed:%d", retval);
goto exit_err;
}
fts_info->irq_enabled = true;
DBG("fts_touch irqhandler restored");
}
retval = pm_runtime_put(fts_info->client->adapter->dev.parent);
/*mutex_unlock(&fts_info->device_mutex);*/
if (retval < 0)
ERR("pm_runtime_put fauled:%d", retval);
retval = unregister_pm_notifier(&fts_pm_notifier);
DBG("%s: retval=%d", __func__, retval);
wake_unlock(&fts_info->wakelock);
wake_unlock(&wakelock);
complete(&fts_info->st_powerdown);
exit_err:
atomic_set(&touch_requested, 0);
return retval;
}
static long tztui_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
int rc = -ENOTTY;
(void)arg;
(void)file;
switch (cmd) {
case TZTUI_IOCTL_OPEN_SESSION:
rc = tui_set_mode(TUI_ONLINE);
break;
case TZTUI_IOCTL_CLOSE_SESSION:
rc = tui_set_mode(TUI_OFFLINE);
break;
case TZTUI_IOCTL_CANCEL:
if (tui_get_current() == TUI_ONLINE) {
tui_set_event(TUI_EVENT_CANCEL);
rc = 0;
} else
rc = -EPERM;
break;
default:
return -ENOTTY; /* Wrong or not-implemented function */
}
return rc;
}
static int tztui_open(struct inode *inode, struct file *filp)
{
mutex_lock(&tui_mode_mutex);
if (!tui_status) {
tui_status = true;
mutex_unlock(&tui_mode_mutex);
return 0;
} else {
mutex_unlock(&tui_mode_mutex);
ERR("TUI Device already used");
return -EBUSY;
}
}
static int tztui_release(struct inode *inode, struct file *filp)
{
mutex_lock(&tui_mode_mutex);
tui_status = false;
mutex_unlock(&tui_mode_mutex);
return 0;
}
static const struct file_operations tztui_fops = {
.owner = THIS_MODULE,
.open = tztui_open,
.release = tztui_release,
.unlocked_ioctl = tztui_ioctl,
};
static struct tz_cdev tztui_cdev = {
.name = "tztui",
.fops = &tztui_fops,
.owner = THIS_MODULE,
};
static int __init tztui_init(void)
{
int rc;
int cpu_num = 0;
(void)cpu_num;
(void)ts;
(void)thread_handler_stop;
(void)irq_wait_queue;
wake_lock_init(&wakelock, WAKE_LOCK_SUSPEND, "tztui_wakelock");
ts = kthread_create(thread_handler, NULL, "per_cpu_thread");
kthread_bind(ts, cpu_num);
if (!IS_ERR(ts))
wake_up_process(ts);
else {
ERR("Failed to bind thread to CPU %d", cpu_num);
return -1;
}
init_completion(&irq_completion);
rc = tz_cdev_register(&tztui_cdev);
if (rc) {
thread_handler_stop = 1;
wake_up(&irq_wait_queue);
kthread_stop(ts);
ts = NULL;
}
return rc;
}
static void __exit tztui_exit(void)
{
tui_set_mode(TUI_OFFLINE);
wake_lock_destroy(&wakelock);
if (ts) {
thread_handler_stop = 1;
wake_up(&irq_wait_queue);
kthread_stop(ts);
ts = NULL;
}
tz_cdev_unregister(&tztui_cdev);
}
module_init(tztui_init);
module_exit(tztui_exit);