Subversion Repositories shark

Rev

Rev 420 | Rev 462 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
    i2c-dev.c - i2c-bus driver, char device interface  

    Copyright (C) 1995-97 Simon G. Vogl
    Copyright (C) 1998-99 Frodo Looijaard <frodol@dds.nl>
    Copyright (C) 2003 Greg Kroah-Hartman <greg@kroah.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, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/


/* Note that this is a complete rewrite of Simon Vogl's i2c-dev module.
   But I have used so much of his original code and ideas that it seems
   only fair to recognize him as co-author -- Frodo */


/* The I2C_RDWR ioctl code is written by Kolja Waschk <waschk@telos.de> */

/* The devfs code is contributed by Philipp Matthias Hahn
   <pmhahn@titan.lahn.de> */


/* If you want debugging uncomment: */
/* #define DEBUG 1 */

#include <linuxcomp.h>

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/smp_lock.h>
#include <linux/devfs_fs_kernel.h>
#include <linux/init.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <asm/uaccess.h>

static struct i2c_client i2cdev_client_template;

struct i2c_dev {
        int minor;
        struct i2c_adapter *adap;
        struct class_device class_dev;
        struct completion released;     /* FIXME, we need a class_device_unregister() */
};
#define to_i2c_dev(d) container_of(d, struct i2c_dev, class_dev)

#define I2C_MINORS      256
static struct i2c_dev *i2c_dev_array[I2C_MINORS];
static spinlock_t i2c_dev_array_lock = SPIN_LOCK_UNLOCKED;

struct i2c_dev *i2c_dev_get_by_minor(unsigned index)
{
        struct i2c_dev *i2c_dev;

        spin_lock(&i2c_dev_array_lock);
        i2c_dev = i2c_dev_array[index];
        spin_unlock(&i2c_dev_array_lock);
        return i2c_dev;
}

struct i2c_dev *i2c_dev_get_by_adapter(struct i2c_adapter *adap)
{
        struct i2c_dev *i2c_dev = NULL;
        int i;

        spin_lock(&i2c_dev_array_lock);
        for (i = 0; i < I2C_MINORS; ++i) {
                if ((i2c_dev_array[i]) &&
                    (i2c_dev_array[i]->adap == adap)) {
                        i2c_dev = i2c_dev_array[i];
                        break;
                }
        }
        spin_unlock(&i2c_dev_array_lock);
        return i2c_dev;
}

static struct i2c_dev *get_free_i2c_dev(void)
{
        struct i2c_dev *i2c_dev;
        unsigned int i;

        i2c_dev = kmalloc(sizeof(*i2c_dev), GFP_KERNEL);
        if (!i2c_dev)
                return ERR_PTR(-ENOMEM);
        memset(i2c_dev, 0x00, sizeof(*i2c_dev));

        spin_lock(&i2c_dev_array_lock);
        for (i = 0; i < I2C_MINORS; ++i) {
                if (i2c_dev_array[i])
                        continue;
                i2c_dev->minor = i;
                i2c_dev_array[i] = i2c_dev;
                spin_unlock(&i2c_dev_array_lock);
                return i2c_dev;
        }
        spin_unlock(&i2c_dev_array_lock);
        kfree(i2c_dev);
        return ERR_PTR(-ENODEV);
}

static void return_i2c_dev(struct i2c_dev *i2c_dev)
{
        spin_lock(&i2c_dev_array_lock);
        i2c_dev_array[i2c_dev->minor] = NULL;
        spin_unlock(&i2c_dev_array_lock);
}

static ssize_t show_dev(struct class_device *class_dev, char *buf)
{
        struct i2c_dev *i2c_dev = to_i2c_dev(class_dev);
        return print_dev_t(buf, MKDEV(I2C_MAJOR, i2c_dev->minor));
}
static CLASS_DEVICE_ATTR(dev, S_IRUGO, show_dev, NULL);

static ssize_t i2cdev_read (struct file *file, char __user *buf, size_t count,
                            loff_t *offset)
{
        char *tmp;
        int ret;

        struct i2c_client *client = (struct i2c_client *)file->private_data;

        if (count > 8192)
                count = 8192;

        tmp = kmalloc(count,GFP_KERNEL);
        if (tmp==NULL)
                return -ENOMEM;

        pr_debug("i2c-dev.o: i2c-%d reading %d bytes.\n",
                iminor(file->f_dentry->d_inode), count);

        ret = i2c_master_recv(client,tmp,count);
        if (ret >= 0)
                ret = copy_to_user(buf,tmp,count)?-EFAULT:ret;
        kfree(tmp);
        return ret;
}

static ssize_t i2cdev_write (struct file *file, const char __user *buf, size_t count,
                             loff_t *offset)
{
        int ret;
        char *tmp;
        struct i2c_client *client = (struct i2c_client *)file->private_data;

        if (count > 8192)
                count = 8192;

        tmp = kmalloc(count,GFP_KERNEL);
        if (tmp==NULL)
                return -ENOMEM;
        if (copy_from_user(tmp,buf,count)) {
                kfree(tmp);
                return -EFAULT;
        }

        pr_debug("i2c-dev.o: i2c-%d writing %d bytes.\n",
                iminor(file->f_dentry->d_inode), count);

        ret = i2c_master_send(client,tmp,count);
        kfree(tmp);
        return ret;
}

int i2cdev_ioctl (struct inode *inode, struct file *file, unsigned int cmd,
                  unsigned long arg)
{
        struct i2c_client *client = (struct i2c_client *)file->private_data;
        struct i2c_rdwr_ioctl_data rdwr_arg;
        struct i2c_smbus_ioctl_data data_arg;
        union i2c_smbus_data temp;
        struct i2c_msg *rdwr_pa;
        u8 **data_ptrs;
        int i,datasize,res;
        unsigned long funcs;

        dev_dbg(&client->dev, "i2c-%d ioctl, cmd: 0x%x, arg: %lx.\n",
                iminor(inode),cmd, arg);

        switch ( cmd ) {
        case I2C_SLAVE:
        case I2C_SLAVE_FORCE:
                if ((arg > 0x3ff) ||
                    (((client->flags & I2C_M_TEN) == 0) && arg > 0x7f))
                        return -EINVAL;
                if ((cmd == I2C_SLAVE) && i2c_check_addr(client->adapter,arg))
                        return -EBUSY;
                client->addr = arg;
                return 0;
        case I2C_TENBIT:
                if (arg)
                        client->flags |= I2C_M_TEN;
                else
                        client->flags &= ~I2C_M_TEN;
                return 0;
        case I2C_PEC:
                if (arg)
                        client->flags |= I2C_CLIENT_PEC;
                else
                        client->flags &= ~I2C_CLIENT_PEC;
                return 0;
        case I2C_FUNCS:
                funcs = i2c_get_functionality(client->adapter);
                return (copy_to_user((unsigned long __user *)arg, &funcs,
                                     sizeof(unsigned long)))?-EFAULT:0;

        case I2C_RDWR:
                if (copy_from_user(&rdwr_arg,
                                   (struct i2c_rdwr_ioctl_data __user *)arg,
                                   sizeof(rdwr_arg)))
                        return -EFAULT;

                /* Put an arbritrary limit on the number of messages that can
                 * be sent at once */

                if (rdwr_arg.nmsgs > 42)
                        return -EINVAL;
               
                rdwr_pa = (struct i2c_msg *)
                        kmalloc(rdwr_arg.nmsgs * sizeof(struct i2c_msg),
                        GFP_KERNEL);

                if (rdwr_pa == NULL) return -ENOMEM;

                if (copy_from_user(rdwr_pa, rdwr_arg.msgs,
                                   rdwr_arg.nmsgs * sizeof(struct i2c_msg))) {
                        kfree(rdwr_pa);
                        return -EFAULT;
                }

                data_ptrs = (u8 **) kmalloc(rdwr_arg.nmsgs * sizeof(u8 *),
                                            GFP_KERNEL);
                if (data_ptrs == NULL) {
                        kfree(rdwr_pa);
                        return -ENOMEM;
                }

                res = 0;
                for( i=0; i<rdwr_arg.nmsgs; i++ ) {
                        /* Limit the size of the message to a sane amount */
                        if (rdwr_pa[i].len > 8192) {
                                res = -EINVAL;
                                break;
                        }
                        data_ptrs[i] = rdwr_pa[i].buf;
                        rdwr_pa[i].buf = kmalloc(rdwr_pa[i].len, GFP_KERNEL);
                        if(rdwr_pa[i].buf == NULL) {
                                res = -ENOMEM;
                                break;
                        }
                        if(copy_from_user(rdwr_pa[i].buf,
                                data_ptrs[i],
                                rdwr_pa[i].len)) {
                                        ++i; /* Needs to be kfreed too */
                                        res = -EFAULT;
                                break;
                        }
                }
                if (res < 0) {
                        int j;
                        for (j = 0; j < i; ++j)
                                kfree(rdwr_pa[j].buf);
                        kfree(data_ptrs);
                        kfree(rdwr_pa);
                        return res;
                }

                res = i2c_transfer(client->adapter,
                        rdwr_pa,
                        rdwr_arg.nmsgs);
                while(i-- > 0) {
                        if( res>=0 && (rdwr_pa[i].flags & I2C_M_RD)) {
                                if(copy_to_user(
                                        data_ptrs[i],
                                        rdwr_pa[i].buf,
                                        rdwr_pa[i].len)) {
                                        res = -EFAULT;
                                }
                        }
                        kfree(rdwr_pa[i].buf);
                }
                kfree(data_ptrs);
                kfree(rdwr_pa);
                return res;

        case I2C_SMBUS:
                if (copy_from_user(&data_arg,
                                   (struct i2c_smbus_ioctl_data __user *) arg,
                                   sizeof(struct i2c_smbus_ioctl_data)))
                        return -EFAULT;
                if ((data_arg.size != I2C_SMBUS_BYTE) &&
                    (data_arg.size != I2C_SMBUS_QUICK) &&
                    (data_arg.size != I2C_SMBUS_BYTE_DATA) &&
                    (data_arg.size != I2C_SMBUS_WORD_DATA) &&
                    (data_arg.size != I2C_SMBUS_PROC_CALL) &&
                    (data_arg.size != I2C_SMBUS_BLOCK_DATA) &&
                    (data_arg.size != I2C_SMBUS_I2C_BLOCK_DATA) &&
                    (data_arg.size != I2C_SMBUS_BLOCK_PROC_CALL)) {
                        dev_dbg(&client->dev,
                                "size out of range (%x) in ioctl I2C_SMBUS.\n",
                                data_arg.size);
                        return -EINVAL;
                }
                /* Note that I2C_SMBUS_READ and I2C_SMBUS_WRITE are 0 and 1,
                   so the check is valid if size==I2C_SMBUS_QUICK too. */

                if ((data_arg.read_write != I2C_SMBUS_READ) &&
                    (data_arg.read_write != I2C_SMBUS_WRITE)) {
                        dev_dbg(&client->dev,
                                "read_write out of range (%x) in ioctl I2C_SMBUS.\n",
                                data_arg.read_write);
                        return -EINVAL;
                }

                /* Note that command values are always valid! */

                if ((data_arg.size == I2C_SMBUS_QUICK) ||
                    ((data_arg.size == I2C_SMBUS_BYTE) &&
                    (data_arg.read_write == I2C_SMBUS_WRITE)))
                        /* These are special: we do not use data */
                        return i2c_smbus_xfer(client->adapter, client->addr,
                                              client->flags,
                                              data_arg.read_write,
                                              data_arg.command,
                                              data_arg.size, NULL);

                if (data_arg.data == NULL) {
                        dev_dbg(&client->dev,
                                "data is NULL pointer in ioctl I2C_SMBUS.\n");
                        return -EINVAL;
                }

                if ((data_arg.size == I2C_SMBUS_BYTE_DATA) ||
                    (data_arg.size == I2C_SMBUS_BYTE))
                        datasize = sizeof(data_arg.data->byte);
                else if ((data_arg.size == I2C_SMBUS_WORD_DATA) ||
                         (data_arg.size == I2C_SMBUS_PROC_CALL))
                        datasize = sizeof(data_arg.data->word);
                else /* size == smbus block, i2c block, or block proc. call */
                        datasize = sizeof(data_arg.data->block);

                if ((data_arg.size == I2C_SMBUS_PROC_CALL) ||
                    (data_arg.size == I2C_SMBUS_BLOCK_PROC_CALL) ||
                    (data_arg.read_write == I2C_SMBUS_WRITE)) {
                        if (copy_from_user(&temp, data_arg.data, datasize))
                                return -EFAULT;
                }
                res = i2c_smbus_xfer(client->adapter,client->addr,client->flags,
                      data_arg.read_write,
                      data_arg.command,data_arg.size,&temp);
                if (! res && ((data_arg.size == I2C_SMBUS_PROC_CALL) ||
                              (data_arg.size == I2C_SMBUS_BLOCK_PROC_CALL) ||
                              (data_arg.read_write == I2C_SMBUS_READ))) {
                        if (copy_to_user(data_arg.data, &temp, datasize))
                                return -EFAULT;
                }
                return res;

        default:
                return i2c_control(client,cmd,arg);
        }
        return 0;
}

static int i2cdev_open(struct inode *inode, struct file *file)
{
        unsigned int minor = iminor(inode);
        struct i2c_client *client;
        struct i2c_adapter *adap;
        struct i2c_dev *i2c_dev;

        i2c_dev = i2c_dev_get_by_minor(minor);
        if (!i2c_dev)
                return -ENODEV;

        adap = i2c_get_adapter(i2c_dev->adap->nr);
        if (!adap)
                return -ENODEV;

        client = kmalloc(sizeof(*client), GFP_KERNEL);
        if (!client) {
                i2c_put_adapter(adap);
                return -ENOMEM;
        }
        memcpy(client, &i2cdev_client_template, sizeof(*client));

        /* registered with adapter, passed as client to user */
        client->adapter = adap;
        file->private_data = client;

        return 0;
}

static int i2cdev_release(struct inode *inode, struct file *file)
{
        struct i2c_client *client = file->private_data;

        i2c_put_adapter(client->adapter);
        kfree(client);
        file->private_data = NULL;

        return 0;
}

static struct file_operations i2cdev_fops = {
        .owner          = THIS_MODULE,
        .llseek         = no_llseek,
        .read           = i2cdev_read,
        .write          = i2cdev_write,
        .ioctl          = i2cdev_ioctl,
        .open           = i2cdev_open,
        .release        = i2cdev_release,
};

static void release_i2c_dev(struct class_device *dev)
{
        struct i2c_dev *i2c_dev = to_i2c_dev(dev);
        complete(&i2c_dev->released);
}

static struct class i2c_dev_class = {
        .name           = "i2c-dev",
        .release        = &release_i2c_dev,
};

static int i2cdev_attach_adapter(struct i2c_adapter *adap)
{
        struct i2c_dev *i2c_dev;
        int retval;

        i2c_dev = get_free_i2c_dev();
        if (IS_ERR(i2c_dev))
                return PTR_ERR(i2c_dev);

        devfs_mk_cdev(MKDEV(I2C_MAJOR, i2c_dev->minor),
                        S_IFCHR|S_IRUSR|S_IWUSR, "i2c/%d", i2c_dev->minor);
        dev_dbg(&adap->dev, "Registered as minor %d\n", i2c_dev->minor);

        /* register this i2c device with the driver core */
        i2c_dev->adap = adap;
        if (adap->dev.parent == &legacy_bus)
                i2c_dev->class_dev.dev = &adap->dev;
        else
                i2c_dev->class_dev.dev = adap->dev.parent;
        i2c_dev->class_dev.class = &i2c_dev_class;
        snprintf26(i2c_dev->class_dev.class_id, BUS_ID_SIZE, "i2c-%d", i2c_dev->minor);
        retval = class_device_register(&i2c_dev->class_dev);
        if (retval)
                goto error;
        class_device_create_file(&i2c_dev->class_dev, &class_device_attr_dev);
        return 0;
error:
        return_i2c_dev(i2c_dev);
        kfree(i2c_dev);
        return retval;
}

static int i2cdev_detach_adapter(struct i2c_adapter *adap)
{
        struct i2c_dev *i2c_dev;

        i2c_dev = i2c_dev_get_by_adapter(adap);
        if (!i2c_dev)
                return -ENODEV;

        init_completion(&i2c_dev->released);
        devfs_remove("i2c/%d", i2c_dev->minor);
        return_i2c_dev(i2c_dev);
        class_device_unregister(&i2c_dev->class_dev);
        wait_for_completion(&i2c_dev->released);
        kfree(i2c_dev);

        dev_dbg(&adap->dev, "Adapter unregistered\n");
        return 0;
}

static int i2cdev_detach_client(struct i2c_client *client)
{
        return 0;
}

static int i2cdev_command(struct i2c_client *client, unsigned int cmd,
                           void *arg)
{
        return -1;
}

static struct i2c_driver i2cdev_driver = {
        .owner          = THIS_MODULE,
        .name           = "dev_driver",
        .id             = I2C_DRIVERID_I2CDEV,
        .flags          = I2C_DF_NOTIFY,
        .attach_adapter = i2cdev_attach_adapter,
        .detach_adapter = i2cdev_detach_adapter,
        .detach_client  = i2cdev_detach_client,
        .command        = i2cdev_command,
};

static struct i2c_client i2cdev_client_template = {
        .name           = "I2C /dev entry",
        .id             = 1,
        .addr           = -1,
        .driver         = &i2cdev_driver,
};

int __init i2c_dev_init(void)
{
        int res;

        printk(KERN_INFO "i2c /dev entries driver\n");

        if (register_chrdev(I2C_MAJOR,"i2c",&i2cdev_fops)) {
                printk(KERN_ERR "i2c-dev.o: unable to get major %d for i2c bus\n",
                       I2C_MAJOR);
                return -EIO;
        }
        devfs_mk_dir("i2c");
        class_register(&i2c_dev_class);
        if ((res = i2c_add_driver(&i2cdev_driver))) {
                printk(KERN_ERR "i2c-dev.o: Driver registration failed, module not inserted.\n");
                devfs_remove("i2c");
                unregister_chrdev(I2C_MAJOR,"i2c");
                return res;
        }
        return 0;
}

static void __exit i2c_dev_exit(void)
{
        i2c_del_driver(&i2cdev_driver);
        class_unregister(&i2c_dev_class);
        devfs_remove("i2c");
        unregister_chrdev(I2C_MAJOR,"i2c");
}

MODULE_AUTHOR("Frodo Looijaard <frodol@dds.nl> and "
                "Simon G. Vogl <simon@tk.uni-linz.ac.at>");
MODULE_DESCRIPTION("I2C /dev entries driver");
MODULE_LICENSE("GPL");

module_init(i2c_dev_init);
module_exit(i2c_dev_exit);