blob: bc7acf0c5ab36f483eb5f1b9418a76802fe8fc1e [file] [log] [blame]
/* tui/main.c
*
* Samsung TUI HW Handler driver.
*
* Copyright (c) 2015 Samsung Electronics
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/cdev.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/ioctl.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/workqueue.h>
#include "stui_core.h"
#include "stui_hal.h"
#include "stui_inf.h"
#include "stui_ioctl.h"
static struct device *stui_device;
static struct cdev stui_cdev;
static struct class *tui_class;
static DEFINE_MUTEX(stui_mode_mutex);
struct device *get_stui_device(void)
{
return stui_device;
}
static void stui_wq_func(struct work_struct *param)
{
struct delayed_work *wq = container_of(param, struct delayed_work, work);
long ret;
mutex_lock(&stui_mode_mutex);
ret = stui_process_cmd(NULL, STUI_HW_IOCTL_FINISH_TUI, 0);
if (ret != STUI_RET_OK)
pr_err("[STUI] STUI_HW_IOCTL_FINISH_TUI in wq fail: %ld\n", ret);
kfree(wq);
mutex_unlock(&stui_mode_mutex);
}
static int stui_open(struct inode *inode, struct file *filp)
{
int ret = 0;
mutex_lock(&stui_mode_mutex);
filp->private_data = NULL;
if (stui_get_mode() & STUI_MODE_ALL) {
ret = -EBUSY;
pr_err("[STUI] Device is busy\n");
}
mutex_unlock(&stui_mode_mutex);
return ret;
}
static int stui_release(struct inode *inode, struct file *filp)
{
struct delayed_work *work;
mutex_lock(&stui_mode_mutex);
if ((stui_get_mode() & STUI_MODE_ALL) && filp->private_data) {
pr_err("[STUI] Device close while TUI session is active\n");
work = kmalloc(sizeof(struct delayed_work), GFP_KERNEL);
if (!work) {
mutex_unlock(&stui_mode_mutex);
return -ENOMEM;
}
INIT_DELAYED_WORK(work, stui_wq_func);
schedule_delayed_work(work, msecs_to_jiffies(4000));
}
mutex_unlock(&stui_mode_mutex);
return 0;
}
static long stui_handler_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
{
long ret;
mutex_lock(&stui_mode_mutex);
ret = stui_process_cmd(f, cmd, arg);
if (stui_get_mode() & STUI_MODE_ALL) {
f->private_data = (void *)1UL;
} else {
f->private_data = (void *)0UL;
}
mutex_unlock(&stui_mode_mutex);
return ret;
}
static const struct file_operations tui_fops = {
.owner = THIS_MODULE,
.open = stui_open,
.release = stui_release,
.unlocked_ioctl = stui_handler_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = stui_handler_ioctl,
#endif /* CONFIG_COMPAT */
};
static int stui_handler_init(void)
{
int err = 0;
dev_t devno;
pr_debug("[STUI] stui_handler_init\n");
err = alloc_chrdev_region(&devno, 0, 1, STUI_DEV_NAME);
if (err) {
pr_err("[STUI] Unable to allocate TUI device number(%d)\n", err);
return err;
}
tui_class = class_create(THIS_MODULE, STUI_DEV_NAME);
if (IS_ERR(tui_class)) {
pr_err("[STUI] Failed to create TUI class\n");
err = PTR_ERR(tui_class);
goto err_class_create;
}
cdev_init(&stui_cdev, &tui_fops);
err = cdev_add(&stui_cdev, devno, 1);
if (err) {
pr_err("[STUI] Unable to add TUI char device(%d)\n", err);
goto err_cdev_add;
}
wake_lock_init(&tui_wakelock, WAKE_LOCK_SUSPEND, "TUI_WAKELOCK");
stui_device = device_create(tui_class, NULL, devno, NULL, STUI_DEV_NAME);
if (!IS_ERR(stui_device))
return 0;
err = PTR_ERR(stui_device);
wake_lock_destroy(&tui_wakelock);
err_cdev_add:
class_destroy(tui_class);
err_class_create:
unregister_chrdev_region(devno, 1);
return err;
}
static void stui_handler_exit(void)
{
pr_debug("[STUI] stui_handler_exit\n");
wake_lock_destroy(&tui_wakelock);
unregister_chrdev_region(stui_cdev.dev, 1);
cdev_del(&stui_cdev);
device_destroy(tui_class, stui_cdev.dev);
class_destroy(tui_class);
}
module_init(stui_handler_init);
module_exit(stui_handler_exit);