blob: fa21ef9b20fad1e9dcfab5568adb8b58f13728bb [file] [log] [blame]
/*
* 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 <linux/fs.h>
#include <linux/delay.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/syscalls.h>
#include <linux/rmi.h>
#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");