194 lines
4.9 KiB
C
194 lines
4.9 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (C) 2017 MediaTek Inc.
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/init.h>
|
|
#include <linux/module.h>
|
|
#include <linux/cdev.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/device.h>
|
|
#include <linux/kobject.h>
|
|
#include <linux/sysfs.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/poll.h>
|
|
#include <linux/slab.h>
|
|
#include <asm/ioctl.h>
|
|
#include <linux/compiler.h>
|
|
#if IS_ENABLED(CONFIG_COMPAT)
|
|
#include <linux/compat.h>
|
|
#endif
|
|
|
|
#include "ccci_util_log.h"
|
|
#include "ccci_util_lib_main.h"
|
|
|
|
/* PIN broadcast event defination */
|
|
struct pin_status_event {
|
|
int pin_value;
|
|
char pin_name[32];
|
|
};
|
|
|
|
struct pin_event_user_ctrl {
|
|
struct pin_status_event pin_event;
|
|
int pin_update;
|
|
struct list_head entry;
|
|
};
|
|
|
|
static dev_t s_pin_status_dev;
|
|
static struct cdev s_pin_char_dev;
|
|
static struct class *s_ccci_pin_class;
|
|
static struct pin_status_event *pin_event;
|
|
static wait_queue_head_t pin_event_wait;
|
|
static spinlock_t pin_event_update_lock;
|
|
static struct list_head user_list;
|
|
|
|
void inject_pin_status_event(int pin_value, const char pin_name[])
|
|
{
|
|
struct pin_event_user_ctrl *user_ctrl;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&pin_event_update_lock, flags);
|
|
if (pin_name != NULL)
|
|
scnprintf(pin_event->pin_name, 32, "%s", pin_name);
|
|
else
|
|
scnprintf(pin_event->pin_name, 32, "%s", "----");
|
|
pin_event->pin_value = pin_value;
|
|
list_for_each_entry(user_ctrl, &user_list, entry)
|
|
user_ctrl->pin_update = 1;
|
|
wake_up_interruptible(&pin_event_wait);
|
|
spin_unlock_irqrestore(&pin_event_update_lock, flags);
|
|
}
|
|
EXPORT_SYMBOL(inject_pin_status_event);
|
|
|
|
static int ccci_util_pin_bc_open(struct inode *inode, struct file *filp)
|
|
{
|
|
struct pin_event_user_ctrl *user_ctrl;
|
|
unsigned long flags;
|
|
|
|
user_ctrl = kzalloc(sizeof(struct pin_event_user_ctrl), GFP_KERNEL);
|
|
if (user_ctrl == NULL)
|
|
return -ENOMEM;
|
|
|
|
INIT_LIST_HEAD(&user_ctrl->entry);
|
|
filp->private_data = user_ctrl;
|
|
spin_lock_irqsave(&pin_event_update_lock, flags);
|
|
list_add_tail(&user_ctrl->entry, &user_list);
|
|
spin_unlock_irqrestore(&pin_event_update_lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ccci_util_pin_bc_release(struct inode *inode, struct file *filp)
|
|
{
|
|
struct pin_event_user_ctrl *user_ctrl;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&pin_event_update_lock, flags);
|
|
user_ctrl = filp->private_data;
|
|
user_ctrl->pin_update = 0;
|
|
|
|
list_del(&user_ctrl->entry);
|
|
spin_unlock_irqrestore(&pin_event_update_lock, flags);
|
|
kfree(user_ctrl);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t ccci_util_pin_bc_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
|
|
{
|
|
struct pin_event_user_ctrl *user_ctrl;
|
|
int ret;
|
|
unsigned long flags;
|
|
|
|
user_ctrl = filp->private_data;
|
|
if (filp->f_flags & O_NONBLOCK) {
|
|
spin_lock_irqsave(&pin_event_update_lock, flags);
|
|
if (user_ctrl->pin_update == 0) {
|
|
spin_unlock_irqrestore(&pin_event_update_lock, flags);
|
|
return 0;
|
|
}
|
|
memcpy(&user_ctrl->pin_event, pin_event, sizeof(struct pin_status_event));
|
|
spin_unlock_irqrestore(&pin_event_update_lock, flags);
|
|
if (copy_to_user(buf, &user_ctrl->pin_event, sizeof(struct pin_status_event)))
|
|
return -EFAULT;
|
|
} else {
|
|
ret = wait_event_interruptible(pin_event_wait, user_ctrl->pin_update == 1);
|
|
if (ret < 0)
|
|
return -EINTR;
|
|
spin_lock_irqsave(&pin_event_update_lock, flags);
|
|
user_ctrl->pin_update = 0;
|
|
memcpy(&user_ctrl->pin_event, pin_event, sizeof(struct pin_status_event));
|
|
spin_unlock_irqrestore(&pin_event_update_lock, flags);
|
|
if (copy_to_user(buf, &user_ctrl->pin_event, sizeof(struct pin_status_event)))
|
|
return -EFAULT;
|
|
}
|
|
|
|
return sizeof(struct pin_status_event);
|
|
}
|
|
|
|
static unsigned int ccci_util_pin_bc_poll(struct file *filp, struct poll_table_struct *wait)
|
|
{
|
|
struct pin_event_user_ctrl *user_ctrl;
|
|
unsigned int mask = 0;
|
|
|
|
user_ctrl = filp->private_data;
|
|
poll_wait(filp, &pin_event_wait, wait);
|
|
if (user_ctrl->pin_update == 1)
|
|
mask |= POLLIN|POLLRDNORM;
|
|
|
|
return mask;
|
|
}
|
|
|
|
static const struct file_operations pin_sta_bc_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = ccci_util_pin_bc_open,
|
|
.read = ccci_util_pin_bc_read,
|
|
.poll = ccci_util_pin_bc_poll,
|
|
.release = ccci_util_pin_bc_release,
|
|
};
|
|
|
|
int ccci_util_pin_broadcast_init(void)
|
|
{
|
|
int ret;
|
|
|
|
pin_event = kzalloc(sizeof(struct pin_status_event), GFP_KERNEL);
|
|
if (pin_event == NULL)
|
|
goto _exit;
|
|
|
|
spin_lock_init(&pin_event_update_lock);
|
|
init_waitqueue_head(&pin_event_wait);
|
|
INIT_LIST_HEAD(&user_list);
|
|
|
|
ret = alloc_chrdev_region(&s_pin_status_dev, 0, 1, "ccci_pin_sta");
|
|
if (ret != 0) {
|
|
CCCI_UTIL_ERR_MSG("alloc chrdev fail for ccci_pin_sta(%d)\n", ret);
|
|
goto _exit_1;
|
|
}
|
|
cdev_init(&s_pin_char_dev, &pin_sta_bc_fops);
|
|
s_pin_char_dev.owner = THIS_MODULE;
|
|
ret = cdev_add(&s_pin_char_dev, s_pin_status_dev, 1);
|
|
if (ret) {
|
|
CCCI_UTIL_ERR_MSG("cdev_add failed\n");
|
|
goto _exit_2;
|
|
}
|
|
|
|
s_ccci_pin_class = class_create(THIS_MODULE, "ccci_pin_sta");
|
|
device_create(s_ccci_pin_class, NULL, s_pin_status_dev, NULL, "ccci_pin_sta");
|
|
|
|
return 0;
|
|
|
|
_exit_2:
|
|
cdev_del(&s_pin_char_dev);
|
|
|
|
_exit_1:
|
|
unregister_chrdev_region(s_pin_status_dev, 1);
|
|
|
|
_exit:
|
|
kfree(pin_event);
|
|
|
|
return -1;
|
|
}
|
|
|