diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/i2c/i2c-dev.c |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'drivers/i2c/i2c-dev.c')
-rw-r--r-- | drivers/i2c/i2c-dev.c | 552 |
1 files changed, 552 insertions, 0 deletions
diff --git a/drivers/i2c/i2c-dev.c b/drivers/i2c/i2c-dev.c new file mode 100644 index 00000000000..86c4d0149e8 --- /dev/null +++ b/drivers/i2c/i2c-dev.c @@ -0,0 +1,552 @@ +/* + 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> */ + +#include <linux/config.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 DEFINE_SPINLOCK(i2c_dev_array_lock); + +static 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; +} + +static struct i2c_dev *i2c_dev_get_by_adapter(struct i2c_adapter *adap) +{ + struct i2c_dev *i2c_dev = NULL; + + spin_lock(&i2c_dev_array_lock); + if ((i2c_dev_array[adap->nr]) && + (i2c_dev_array[adap->nr]->adap == adap)) + i2c_dev = i2c_dev_array[adap->nr]; + spin_unlock(&i2c_dev_array_lock); + return i2c_dev; +} + +static struct i2c_dev *get_free_i2c_dev(struct i2c_adapter *adap) +{ + struct i2c_dev *i2c_dev; + + 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); + if (i2c_dev_array[adap->nr]) { + spin_unlock(&i2c_dev_array_lock); + dev_err(&adap->dev, "i2c-dev already has a device assigned to this adapter\n"); + goto error; + } + i2c_dev->minor = adap->nr; + i2c_dev_array[adap->nr] = i2c_dev; + spin_unlock(&i2c_dev_array_lock); + return i2c_dev; +error: + 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_adapter_name(struct class_device *class_dev, char *buf) +{ + struct i2c_dev *i2c_dev = to_i2c_dev(class_dev); + return sprintf(buf, "%s\n", i2c_dev->adap->name); +} +static CLASS_DEVICE_ATTR(name, S_IRUGO, show_adapter_name, 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: i2c-%d reading %zd 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: i2c-%d writing %zd bytes.\n", + iminor(file->f_dentry->d_inode), count); + + ret = i2c_master_send(client,tmp,count); + kfree(tmp); + return ret; +} + +static 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 __user **data_ptrs; + int i,datasize,res; + unsigned long funcs; + + dev_dbg(&client->adapter->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 > I2C_RDRW_IOCTL_MAX_MSGS) + 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 = kmalloc(rdwr_arg.nmsgs * sizeof(u8 __user *), 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] = (u8 __user *)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->adapter->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->adapter->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->adapter->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(adap); + 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 == &platform_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; + i2c_dev->class_dev.devt = MKDEV(I2C_MAJOR, i2c_dev->minor); + snprintf(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_name); + 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", + .addr = -1, + .driver = &i2cdev_driver, +}; + +static int __init i2c_dev_init(void) +{ + int res; + + printk(KERN_INFO "i2c /dev entries driver\n"); + + res = register_chrdev(I2C_MAJOR, "i2c", &i2cdev_fops); + if (res) + goto out; + + res = class_register(&i2c_dev_class); + if (res) + goto out_unreg_chrdev; + + res = i2c_add_driver(&i2cdev_driver); + if (res) + goto out_unreg_class; + + devfs_mk_dir("i2c"); + + return 0; + +out_unreg_class: + class_unregister(&i2c_dev_class); +out_unreg_chrdev: + unregister_chrdev(I2C_MAJOR, "i2c"); +out: + printk(KERN_ERR "%s: Driver Initialisation failed\n", __FILE__); + return res; +} + +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); |