/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../../input/touchscreen/stm/fts_ts.h" #include "tz_cdev.h" #include "tztui.h" MODULE_AUTHOR("Egor Uleyskiy "); 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);