/* * Copyright (c) 2011 Synaptics Incorporated * * 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, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include #include #include #include #include #include "rmi_driver.h" #define CHAR_DEVICE_NAME "rmi" #define REG_ADDR_LIMIT 0xFFFF /*store dynamically allocated major number of char device*/ static int rmi_char_dev_major_num; /* file operations for RMI char device */ /* * rmi_char_dev_llseek: - use to setup register address * * @filp: file structure for seek * @off: offset * if whence == SEEK_SET, * high 16 bits: page address * low 16 bits: register address * * if whence == SEEK_CUR, * offset from current position * * if whence == SEEK_END, * offset from END(0xFFFF) * * @whence: SEEK_SET , SEEK_CUR or SEEK_END */ static loff_t rmi_char_dev_llseek(struct file *filp, loff_t off, int whence) { loff_t newpos; struct rmi_char_dev *my_char_dev = filp->private_data; if (IS_ERR(my_char_dev)) { pr_err("%s: pointer of char device is invalid", __func__); return -EBADF; } mutex_lock(&(my_char_dev->mutex_file_op)); switch (whence) { case SEEK_SET: newpos = off; break; case SEEK_CUR: newpos = filp->f_pos + off; break; case SEEK_END: newpos = REG_ADDR_LIMIT + off; break; default: /* can't happen */ newpos = -EINVAL; goto clean_up; } if (newpos < 0 || newpos > REG_ADDR_LIMIT) { dev_err(my_char_dev->phys->dev, "newpos 0x%04x is invalid.\n", (unsigned int)newpos); newpos = -EINVAL; goto clean_up; } filp->f_pos = newpos; clean_up: mutex_unlock(&(my_char_dev->mutex_file_op)); return newpos; } /* * rmi_char_dev_read: - use to read data from RMI stream * * @filp: file structure for read * @buf: user-level buffer pointer * * @count: number of byte read * @f_pos: offset (starting register address) * * @return number of bytes read into user buffer (buf) if succeeds * negative number if error occurs. */ static ssize_t rmi_char_dev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) { struct rmi_char_dev *my_char_dev = filp->private_data; ssize_t ret_value = 0; unsigned char tmpbuf[count+1]; struct rmi_phys_device *phys; /* limit offset to REG_ADDR_LIMIT-1 */ if (count > (REG_ADDR_LIMIT - *f_pos)) count = REG_ADDR_LIMIT - *f_pos; if (count == 0) return 0; if (IS_ERR(my_char_dev)) { pr_err("%s: pointer of char device is invalid", __func__); ret_value = -EBADF; return ret_value; } mutex_lock(&(my_char_dev->mutex_file_op)); phys = my_char_dev->phys; /* * just let it go through , because we do not know the register is FIFO * register or not */ ret_value = phys->read_block(phys, *f_pos, tmpbuf, count); if (ret_value < 0) goto clean_up; else *f_pos += ret_value; if (copy_to_user(buf, tmpbuf, count)) ret_value = -EFAULT; clean_up: mutex_unlock(&(my_char_dev->mutex_file_op)); return ret_value; } /* * rmi_char_dev_write: - use to write data into RMI stream * * @filep : file structure for write * @buf: user-level buffer pointer contains data to be written * @count: number of byte be be written * @f_pos: offset (starting register address) * * @return number of bytes written from user buffer (buf) if succeeds * negative number if error occurs. */ static ssize_t rmi_char_dev_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) { struct rmi_char_dev *my_char_dev = filp->private_data; ssize_t ret_value = 0; unsigned char tmpbuf[count+1]; struct rmi_phys_device *phys; /* limit offset to REG_ADDR_LIMIT-1 */ if (count > (REG_ADDR_LIMIT - *f_pos)) count = REG_ADDR_LIMIT - *f_pos; if (count == 0) return 0; if (IS_ERR(my_char_dev)) { pr_err("%s: pointer of char device is invalid", __func__); ret_value = -EBADF; return ret_value; } if (copy_from_user(tmpbuf, buf, count)) { ret_value = -EFAULT; return ret_value; } mutex_lock(&(my_char_dev->mutex_file_op)); phys = my_char_dev->phys; /* * just let it go through , because we do not know the register is FIFO * register or not */ ret_value = phys->write_block(phys, *f_pos, tmpbuf, count); if (ret_value >= 0) *f_pos += count; mutex_unlock(&(my_char_dev->mutex_file_op)); return ret_value; } /* * rmi_char_dev_open: - get a new handle for from RMI stream * @inp : inode struture * @filp: file structure for read/write * * @return 0 if succeeds */ static int rmi_char_dev_open(struct inode *inp, struct file *filp) { /* store the device pointer to file structure */ struct rmi_char_dev *my_dev = container_of(inp->i_cdev, struct rmi_char_dev, main_dev); struct rmi_phys_device *phys = my_dev->phys; int ret_value = 0; filp->private_data = my_dev; if (!phys) return -EACCES; mutex_lock(&(my_dev->mutex_file_op)); if (my_dev->ref_count < 1) my_dev->ref_count++; else ret_value = -EACCES; mutex_unlock(&(my_dev->mutex_file_op)); return ret_value; } /* * rmi_char_dev_release: - release an existing handle * @inp: inode structure * @filp: file structure for read/write * * @return 0 if succeeds */ static int rmi_char_dev_release(struct inode *inp, struct file *filp) { struct rmi_char_dev *my_dev = container_of(inp->i_cdev, struct rmi_char_dev, main_dev); struct rmi_phys_device *phys = my_dev->phys; if (!phys) return -EACCES; mutex_lock(&(my_dev->mutex_file_op)); my_dev->ref_count--; if (my_dev->ref_count < 0) my_dev->ref_count = 0; mutex_unlock(&(my_dev->mutex_file_op)); return 0; } static const struct file_operations rmi_char_dev_fops = { .owner = THIS_MODULE, .llseek = rmi_char_dev_llseek, .read = rmi_char_dev_read, .write = rmi_char_dev_write, .open = rmi_char_dev_open, .release = rmi_char_dev_release, }; /* * rmi_char_dev_clean_up - release memory or unregister driver * @rmi_char_dev: rmi_char_dev structure * */ static void rmi_char_dev_clean_up(struct rmi_char_dev *char_dev, struct class *char_device_class) { dev_t devno; /* Get rid of our char dev entries */ if (char_dev) { devno = char_dev->main_dev.dev; cdev_del(&char_dev->main_dev); kfree(char_dev); if (char_device_class) { device_destroy(char_device_class, devno); class_unregister(char_device_class); class_destroy(char_device_class); } /* cleanup_module is never called if registering failed */ unregister_chrdev_region(devno, 1); pr_debug("%s: rmi_char_dev is removed\n", __func__); } } /* * rmi_char_devnode - return device permission * * @dev: char device structure * @mode: file permission * */ static char *rmi_char_devnode(struct device *dev, mode_t *mode) { if (!mode) return NULL; /* rmi** */ /**mode = 0666*/ *mode = (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH); return kasprintf(GFP_KERNEL, "rmi/%s", dev_name(dev)); } /* * rmi_char_dev_register - register char device (called from up-level) * * @phy: a pointer to an rmi_phys_devices structure * * @return: zero if suceeds */ int rmi_char_dev_register(struct rmi_phys_device *phys) { struct rmi_char_dev *char_dev; dev_t dev_no; int err; int result; struct device *device_ptr; if (rmi_char_dev_major_num) { dev_no = MKDEV(rmi_char_dev_major_num, 0); result = register_chrdev_region(dev_no, 1, CHAR_DEVICE_NAME); } else { result = alloc_chrdev_region(&dev_no, 0, 1, CHAR_DEVICE_NAME); /* let kernel allocate a major for us */ rmi_char_dev_major_num = MAJOR(dev_no); dev_info(phys->dev, "Major number of rmi_char_dev: %d\n", rmi_char_dev_major_num); } if (result < 0) return result; char_dev = kzalloc(sizeof(struct rmi_char_dev), GFP_KERNEL); if (!char_dev) { dev_err(phys->dev, "Failed to allocate rmi_char_dev.\n"); /* unregister the char device region */ __unregister_chrdev(rmi_char_dev_major_num, MINOR(dev_no), 1, CHAR_DEVICE_NAME); return -ENOMEM; } mutex_init(&char_dev->mutex_file_op); phys->char_dev = char_dev; char_dev->phys = phys; cdev_init(&char_dev->main_dev, &rmi_char_dev_fops); err = cdev_add(&char_dev->main_dev, dev_no, 1); if (err) { dev_err(phys->dev, "Error %d adding rmi_char_dev.\n", err); rmi_char_dev_clean_up(phys->char_dev, phys->rmi_char_device_class); return err; } /* create device node */ phys->rmi_char_device_class = class_create(THIS_MODULE, CHAR_DEVICE_NAME); if (IS_ERR(phys->rmi_char_device_class)) { dev_err(phys->dev, "Failed to create /dev/%s.\n", CHAR_DEVICE_NAME); rmi_char_dev_clean_up(phys->char_dev, phys->rmi_char_device_class); return -ENODEV; } /* setup permission */ phys->rmi_char_device_class->devnode = rmi_char_devnode; /* class creation */ device_ptr = device_create( phys->rmi_char_device_class, NULL, dev_no, NULL, CHAR_DEVICE_NAME"%d", MINOR(dev_no)); if (IS_ERR(device_ptr)) { dev_err(phys->dev, "Failed to create rmi device.\n"); rmi_char_dev_clean_up(phys->char_dev, phys->rmi_char_device_class); return -ENODEV; } return 0; } EXPORT_SYMBOL(rmi_char_dev_register); /* rmi_char_dev_unregister - unregister char device (called from up-level) * * @phys: pointer to an rmi_phys_device structure */ void rmi_char_dev_unregister(struct rmi_phys_device *phys) { /* clean up */ if (phys) rmi_char_dev_clean_up(phys->char_dev, phys->rmi_char_device_class); } EXPORT_SYMBOL(rmi_char_dev_unregister); MODULE_AUTHOR("Synaptics, Inc."); MODULE_DESCRIPTION("RMI4 Char Device"); MODULE_LICENSE("GPL");