| /* |
| * driver/ccic/ccic_misc.c - S2MM005 CCIC MISC driver |
| * |
| * Copyright (C) 2017 Samsung Electronics |
| * Author: Wookwang Lee <wookwang.lee@samsung.com> |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * 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. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; If not, see <http://www.gnu.org/licenses/>. |
| * |
| */ |
| //serial_acm.c |
| #include <linux/miscdevice.h> |
| #include <linux/fs.h> |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/slab.h> |
| #include <linux/spinlock.h> |
| #include <linux/device.h> |
| #include <linux/poll.h> |
| #include <linux/ccic/ccic_misc.h> |
| |
| static struct ccic_misc_dev *c_dev; |
| |
| #define MAX_BUF 255 |
| #define DEXDOCK_PRODUCT_ID 0xA020 |
| #define NODE_OF_MISC "ccic_misc" |
| #define CCIC_IOCTL_UVDM _IOWR('C', 0, struct uvdm_data) |
| #ifdef CONFIG_COMPAT |
| #define CCIC_IOCTL_UVDM_32 _IOWR('C', 0, struct uvdm_data_32) |
| #endif |
| |
| static inline int _lock(atomic_t *excl) |
| { |
| if (atomic_inc_return(excl) == 1) { |
| return 0; |
| } else { |
| atomic_dec(excl); |
| return -1; |
| } |
| } |
| |
| static inline void _unlock(atomic_t *excl) |
| { |
| atomic_dec(excl); |
| } |
| |
| static int ccic_misc_open(struct inode *inode, struct file *file) |
| { |
| int ret = 0; |
| |
| pr_info("%s + open success\n", __func__); |
| if (!c_dev) { |
| pr_err("%s - error : c_dev is NULL\n", __func__); |
| ret = -ENODEV; |
| goto err; |
| } |
| |
| if (_lock(&c_dev->open_excl)) { |
| pr_err("%s - error : device busy\n", __func__); |
| ret = -EBUSY; |
| goto err1; |
| } |
| |
| if (!samsung_uvdm_ready()) { |
| // check if there is some connection |
| _unlock(&c_dev->open_excl); |
| pr_err("%s - error : uvdm is not ready\n", __func__); |
| ret = -EBUSY; |
| goto err1; |
| } |
| |
| pr_info("%s - open success\n", __func__); |
| |
| return 0; |
| err1: |
| err: |
| return ret; |
| } |
| |
| static int ccic_misc_close(struct inode *inode, struct file *file) |
| { |
| if (c_dev) |
| _unlock(&c_dev->open_excl); |
| samsung_uvdm_close(); |
| pr_info("%s - close success\n", __func__); |
| return 0; |
| } |
| |
| static int send_uvdm_message(void *data, int size) |
| { |
| int ret; |
| |
| pr_info("%s - size : %d\n", __func__, size); |
| ret = samsung_uvdm_out_request_message(data, size); |
| return ret; |
| } |
| |
| static int receive_uvdm_message(void *data, int size) |
| { |
| int ret; |
| |
| pr_info("%s - size : %d\n", __func__, size); |
| ret = samsung_uvdm_in_request_message(data); |
| return ret; |
| } |
| |
| static long |
| ccic_misc_ioctl(struct file *file, unsigned int cmd, unsigned long arg) |
| { |
| int ret = 0; |
| void *buf = NULL; |
| |
| if (_lock(&c_dev->ioctl_excl)) { |
| pr_err("%s - error : ioctl busy - cmd : %d\n", __func__, cmd); |
| ret = -EBUSY; |
| goto err2; |
| } |
| |
| switch (cmd) { |
| case CCIC_IOCTL_UVDM: |
| pr_info("%s - CCIC_IOCTL_UVDM cmd\n", __func__); |
| if (copy_from_user(&c_dev->u_data, (void __user *) arg, |
| sizeof(struct uvdm_data))) { |
| ret = -EIO; |
| pr_err("%s - copy_from_user error\n", __func__); |
| goto err1; |
| } |
| |
| buf = kzalloc(MAX_BUF, GFP_KERNEL); |
| if (!buf) { |
| ret = -EINVAL; |
| pr_err("%s - kzalloc error\n", __func__); |
| goto err1; |
| } |
| |
| if (c_dev->u_data.size > MAX_BUF) { |
| ret = -ENOMEM; |
| pr_err("%s - user data size is %d error\n", __func__, c_dev->u_data.size); |
| goto err; |
| } |
| |
| if (c_dev->u_data.dir == DIR_OUT) { |
| if (copy_from_user(buf, c_dev->u_data.pData,\ |
| c_dev->u_data.size)) { |
| ret = -EIO; |
| pr_err("%s - copy_from_user error\n", __func__); |
| goto err; |
| } |
| ret = send_uvdm_message(buf, c_dev->u_data.size); |
| if (ret <= 0) { |
| pr_err("%s - send_uvdm_message error\n", __func__); |
| goto err; |
| } |
| } else { |
| ret = receive_uvdm_message(buf, c_dev->u_data.size); |
| if (ret <= 0) { |
| pr_err("%s - receive_uvdm_message error\n", __func__); |
| goto err; |
| } |
| if (copy_to_user((void __user *)c_dev->u_data.pData, |
| buf, ret)) { |
| ret = -EIO; |
| pr_err("%s - copy_to_user error\n", __func__); |
| goto err; |
| } |
| } |
| break; |
| #ifdef CONFIG_COMPAT |
| case CCIC_IOCTL_UVDM_32: |
| pr_info("%s - CCIC_IOCTL_UVDM_32 cmd\n", __func__); |
| if (copy_from_user(&c_dev->u_data_32, compat_ptr(arg), |
| sizeof(struct uvdm_data_32))) { |
| ret = -EIO; |
| pr_err("%s - copy_from_user error\n", __func__); |
| goto err1; |
| } |
| |
| buf = kzalloc(MAX_BUF, GFP_KERNEL); |
| if (!buf) { |
| ret = -EINVAL; |
| pr_err("%s - kzalloc error\n", __func__); |
| goto err1; |
| } |
| |
| if (c_dev->u_data_32.size > MAX_BUF) { |
| ret = -ENOMEM; |
| pr_err("%s - user data size is %d error\n", __func__, c_dev->u_data_32.size); |
| goto err; |
| } |
| |
| if (c_dev->u_data_32.dir == DIR_OUT) { |
| if (copy_from_user(buf, compat_ptr(c_dev->u_data_32.pData),\ |
| c_dev->u_data_32.size)) { |
| ret = -EIO; |
| pr_err("%s - copy_from_user error\n", __func__); |
| goto err; |
| } |
| ret = send_uvdm_message(buf, c_dev->u_data_32.size); |
| if (ret < 0) { |
| pr_err("%s - send_uvdm_message error\n", __func__); |
| ret = -EINVAL; |
| goto err; |
| } |
| } else { |
| ret = receive_uvdm_message(buf, c_dev->u_data_32.size); |
| if (ret < 0) { |
| pr_err("%s - receive_uvdm_message error\n", __func__); |
| ret = -EINVAL; |
| goto err; |
| } |
| if (copy_to_user(compat_ptr(c_dev->u_data_32.pData), |
| buf, ret)) { |
| ret = -EIO; |
| pr_err("%s - copy_to_user error\n", __func__); |
| goto err; |
| } |
| } |
| break; |
| #endif |
| default: |
| pr_err("%s - unknown ioctl cmd : %d\n", __func__, cmd); |
| ret = -ENOIOCTLCMD; |
| goto err; |
| } |
| err: |
| kfree(buf); |
| err1: |
| _unlock(&c_dev->ioctl_excl); |
| err2: |
| return ret; |
| } |
| |
| #ifdef CONFIG_COMPAT |
| static long |
| ccic_misc_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg) |
| { |
| int ret = 0; |
| pr_info("%s - cmd : %d\n", __func__, cmd); |
| ret = ccic_misc_ioctl(file, cmd, (unsigned long)compat_ptr(arg)); |
| return ret; |
| } |
| #endif |
| |
| static const struct file_operations ccic_misc_fops = { |
| .owner = THIS_MODULE, |
| .open = ccic_misc_open, |
| .release = ccic_misc_close, |
| .llseek = no_llseek, |
| .unlocked_ioctl = ccic_misc_ioctl, |
| #ifdef CONFIG_COMPAT |
| .compat_ioctl = ccic_misc_compat_ioctl, |
| #endif |
| }; |
| |
| static struct miscdevice ccic_misc_device = { |
| .minor = MISC_DYNAMIC_MINOR, |
| .name = NODE_OF_MISC, |
| .fops = &ccic_misc_fops, |
| }; |
| |
| int ccic_misc_init(void) |
| { |
| int ret = 0; |
| |
| ret = misc_register(&ccic_misc_device); |
| if (ret) { |
| pr_err("%s - return error : %d\n", __func__, ret); |
| goto err; |
| } |
| |
| c_dev = kzalloc(sizeof(struct ccic_misc_dev), GFP_KERNEL); |
| if (!c_dev) { |
| ret = -ENOMEM; |
| pr_err("%s - kzalloc failed : %d\n", __func__, ret); |
| goto err1; |
| } |
| atomic_set(&c_dev->open_excl, 0); |
| atomic_set(&c_dev->ioctl_excl, 0); |
| |
| pr_info("%s - register success\n", __func__); |
| return 0; |
| err1: |
| misc_deregister(&ccic_misc_device); |
| err: |
| return ret; |
| } |
| EXPORT_SYMBOL(ccic_misc_init); |
| |
| void ccic_misc_exit(void) |
| { |
| pr_info("%s() called\n", __func__); |
| if (!c_dev) |
| return; |
| kfree(c_dev); |
| misc_deregister(&ccic_misc_device); |
| } |
| EXPORT_SYMBOL(ccic_misc_exit); |