/*
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
);