I2C驱动要完成哪些事?
想要编写i2c驱动,就必须先弄清楚i2c驱动是干啥的。i2c驱动对上需要提供给用户操作对象,用户才能通过这个对象最终访问到i2c设备;对下需要能正确操作i2c设备,也就是实现i2c设备的读写操作;对中间能对接i2c核心提供的接口,实现驱动与设备关联,也方便系统统一管理。
综上,i2c驱动需要干以下三件事:
- 使用i2c核心提供的接口将驱动注册到系统,并实现驱动与设备匹配成功所需要执行的probe函数,以及设备或驱动卸载时执行的remove函数。
- 注册字符驱动,创建设备节点,让用户空间有访问i2c设备的对象。
- 实现i2c设备的操作方法集(file_operatin),按i2c设备手册要求,完成读写操作。
i2c设备代码(简要)
……
static struct i2c_board_info my_tmp75_info = {
I2C_BOARD_INFO("my_tmp75", 0x48),
}; //设备信息描述结构体,用于填充 i2c_client结构体
static struct i2c_client *my_tmp75_client; //tmp75 i2c设备描述结构体
static int my_tmp75_init(void)
{
struct i2c_adapter *i2c_adapt;
i2c_adapt = i2c_get_adapter(6); //获取总线6(i2c控制器6)描述结构体,获取哪个总线取决i2c设备挂在哪个i2c控制器上
if (i2c_adapt == NULL)
{
printk("get adapter fail!\n");
goto get_adapter_fail;
}
my_tmp75_client = i2c_new_device(i2c_adapt, &my_tmp75_info); //分配并注册i2c设备
if (my_tmp75_client == NULL)
{
printk("i2c new fail!\n");
goto i2c_new_fail;
}
i2c_put_adapter(i2c_adapt);
return 0;
i2c_new_fail:
i2c_put_adapter(i2c_adapt);
get_adapter_fail:
return -1;
}
……
i2c驱动代码
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/i2c.h>
#include <linux/uaccess.h>
#include <linux/cdev.h>
#define MY_I2C_MAGIC_NUMBER 'k'
#define GET_TEMP _IO(MY_I2C_MAGIC_NUMBER, 0)
#define GET_CONFIG _IO(MY_I2C_MAGIC_NUMBER, 1)
#define GET_TLOW_REG _IO(MY_I2C_MAGIC_NUMBER, 2)
#define GET_THIGH_REG _IO(MY_I2C_MAGIC_NUMBER, 3)
#define SET_CONFIG _IO(MY_I2C_MAGIC_NUMBER, 4)
#define SET_TLOW_REG _IO(MY_I2C_MAGIC_NUMBER, 5)
#define SET_THIGH_REG _IO(MY_I2C_MAGIC_NUMBER, 6)
#define TMP_REG_ADDR 0x00
#define TMP_CFIG_ADDR 0x01
#define TMP_TLOW_ADDR 0x02
#define TMP_THIGH_ADDR 0x03
#define DEV_NAME "my_i2c"
#define NAME_SIZE 32
#define NINOR_MAX_NUM 4095
#define DEV_ADD_NUM 1
static int my_debug = 1;
module_param(my_debug, int, 0644);
MODULE_PARM_DESC(my_debug, "enable debugging informationg");
#define debug_printk(ftm, args...) if(my_debug){printk(ftm, ##args);}
unsigned char TMP_ADDR[] = {TMP_REG_ADDR, TMP_CFIG_ADDR, \
TMP_TLOW_ADDR, TMP_THIGH_ADDR, \
TMP_CFIG_ADDR, TMP_TLOW_ADDR, TMP_THIGH_ADDR};
struct my_tmp75_device
{
struct i2c_client *client;
struct cdev tmp75_cdev;
struct mutex my_mutex;
char name[NAME_SIZE];
};
struct tmp75_i2c_drv {
dev_t dev;
struct class *my_i2c_class;
unsigned int dev_count;
unsigned int minor_count;
};
static struct tmp75_i2c_drv my_i2c_drv = {
.dev = 0,
.my_i2c_class = NULL,
.dev_count = 0,
.minor_count = 0,
};
static int my_tmp75_open(struct inode *inode, struct file *filp)
{
struct my_tmp75_device *tmp75_device;
tmp75_device = container_of(inode->i_cdev, struct my_tmp75_device, tmp75_cdev);
filp->private_data = tmp75_device;
return 0;
}
static long tmp_read_data(struct i2c_client *client, unsigned char reg_addr)
{
long ret = 0;
unsigned char buf[2];
struct i2c_msg msg[2];
//判断通信方法是否存在,存在则调用
if (client->adapter->algo->master_xfer != NULL)
{
msg[0].addr = client->addr;
msg[0].buf = ®_addr;
msg[0].len = sizeof(reg_addr);
msg[0].flags = 0; //写标志
msg[1].addr = client->addr;
msg[1].buf = buf;
msg[1].len = sizeof(buf);
msg[1].flags = I2C_M_RD; //读标志
ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
if (ARRAY_SIZE(msg) == ret)
{
ret = buf[1];
ret = (ret << 8) | buf[0];
}
}
else if (client->adapter->algo->smbus_xfer != NULL)
{
ret = i2c_smbus_read_word_data(client, reg_addr);
}
else
{
ret = -EOPNOTSUPP;
}
return ret;
}
static long tmp_write_data(struct i2c_client *client, unsigned char reg_addr, unsigned long data)
{
long ret = 0;
struct i2c_msg msg;
char msg_count = 1;
unsigned char buf[3];
//判断通信方法是否存在,存在则调用
if (client->adapter->algo->master_xfer != NULL)
{
buf[0] = reg_addr;
buf[1] = data & 0xff;
buf[2] = (data >> 8) & 0xff;
debug_printk("[0]=%x [1]=%x [2]=%c \n", buf[0], buf[1], buf[2]);
msg.addr = client->addr;
msg.buf = buf;
msg.len = sizeof(buf);
msg.flags = 0; //写标志
ret = i2c_transfer(client->adapter, &msg, msg_count);
if (msg_count ==ret)
{
ret = 0;
}
}
else if (client->adapter->algo->smbus_xfer != NULL)
{
ret = i2c_smbus_write_word_data(client, reg_addr, data);
}
else
{
ret = -EOPNOTSUPP;
}
return ret;
}
static long my_tmp75_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
long ret = 0;
unsigned long data;
struct my_tmp75_device *tmp75_device;
tmp75_device = filp->private_data;
if ((_IOC_TYPE(cmd) != MY_I2C_MAGIC_NUMBER))
{
printk("%s:pid = %d, command type [%c] error!\n", __func__, current->pid, _IOC_TYPE(cmd));
return -EINVAL;
}
//加锁,防止多线程操作
mutex_lock(&tmp75_device->my_mutex);
switch (_IOC_NR(cmd))
{
case _IOC_NR(GET_TEMP):
case _IOC_NR(GET_CONFIG):
case _IOC_NR(GET_TLOW_REG):
case _IOC_NR(GET_THIGH_REG):
{
ret = tmp_read_data(tmp75_device->client, TMP_ADDR[_IOC_NR(cmd)]);
if (ret > 0)
{
data = (unsigned long)ret;
ret = copy_to_user((void *)arg, &data, sizeof(data));
}
break;
}
case _IOC_NR(SET_CONFIG):
case _IOC_NR(SET_TLOW_REG):
case _IOC_NR(SET_THIGH_REG):
{
ret = copy_from_user(&data, (void *)arg, sizeof(data));
if (0 == ret)
{
ret = tmp_write_data(tmp75_device->client, TMP_ADDR[_IOC_NR(cmd)], data);
}
break;
}
default:
{
printk("%s:pid = %d, command error!\n", __func__, current->pid);
ret = -EINVAL;
break;
}
}
mutex_unlock(&tmp75_device->my_mutex);
if (ret < 0)
{
printk("read/write failed!\n");
}
return ret;
}
//i2c设备的操作方法集
static const struct file_operations my_tmp75_fops = {
.owner = THIS_MODULE,
.open = my_tmp75_open,
.unlocked_ioctl = my_tmp75_ioctl,
};
//驱动与设备的匹配列表
static const struct i2c_device_id my_tmp75_ids[] = {
{.name = "my_tmp75", 0},
{.name = "my_tmp175", 1},
{ },
};
MODULE_DEVICE_TABLE(i2c, my_tmp75_ids);
static int my_tmp75_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
struct my_tmp75_device *tmp75_dev;
struct device *my_i2c_class_dev;
dev_t tmep_dev;
int ret = 0;
//devm_kzalloc分配的托管内存,当设备或驱动卸载时自动释放
tmp75_dev = devm_kzalloc(&client->dev, sizeof(struct my_tmp75_device), GFP_KERNEL);
if (NULL == tmp75_dev)
{
printk("devm_kzalloc fail!\n");
return -ENOMEM;
}
tmp75_dev->client = client;
mutex_init(&(tmp75_dev->my_mutex));
//设置设备节点名称,根据设备名称、总线号、设备地址来设置,不会相同
sprintf(tmp75_dev->name, "%s-%d-0x%x", client->name, client->adapter->nr, client->addr);
i2c_set_clientdata(client, tmp75_dev);//设置tmp75_dev为client中dev的私有数据
//如果是第一次注册字符驱动,则自动分配设备号,否则在原来主设备号基础上增加新设备
if (my_i2c_drv.dev)//如果设备号为空,表示第一次注册
{
tmep_dev = MKDEV(MAJOR(my_i2c_drv.dev), MINOR(my_i2c_drv.dev) + my_i2c_drv.minor_count);
ret = register_chrdev_region(tmep_dev, DEV_ADD_NUM, tmp75_dev->name);
}
else
{
ret = alloc_chrdev_region(&my_i2c_drv.dev, 0, DEV_ADD_NUM, tmp75_dev->name);
tmep_dev = my_i2c_drv.dev;
}
if (ret)//字符驱动注册失败
{
printk("failed to char device region,minor_count = %d\n", my_i2c_drv.minor_count);
goto char_region_fail;
}
cdev_init(&tmp75_dev->tmp75_cdev, &my_tmp75_fops);
ret = cdev_add(&tmp75_dev->tmp75_cdev, tmep_dev, DEV_ADD_NUM);
if (ret)
{
printk("cdev_add failed\n");
goto cdev_add_fail;
}
if (NULL == my_i2c_drv.my_i2c_class)//如果设备类指针为空,表示第一次注册
{
my_i2c_drv.my_i2c_class = class_create(THIS_MODULE, DEV_NAME);
if (IS_ERR(my_i2c_drv.my_i2c_class))
{
ret = PTR_ERR(my_i2c_drv.my_i2c_class);
printk("class_create failed\n");
goto class_create_fail;
}
}
//每一次设备与驱动匹配成功,都创建设备节点
my_i2c_class_dev = device_create(my_i2c_drv.my_i2c_class, \
NULL, tmep_dev, NULL, tmp75_dev->name);
if (IS_ERR(my_i2c_class_dev))
{
ret = PTR_ERR(my_i2c_class_dev);
printk("device_create failed\n");
goto device_create_fail;
}
my_i2c_drv.dev_count++;//记录当前存活设备
//记录次设备号,用于下一次注册设备,最多不超过4095个次设备
//my_i2c_drv.minor_count = (++my_i2c_drv.minor_count) & NINOR_MAX_NUM;
my_i2c_drv.minor_count = (my_i2c_drv.minor_count + 1) & NINOR_MAX_NUM;
debug_printk("register %s successed! dev_count = %d,minor_count = %d\n", \
tmp75_dev->name, my_i2c_drv.dev_count, my_i2c_drv.minor_count);
return 0;
device_create_fail:
if (!my_i2c_drv.dev_count)
{
class_destroy(my_i2c_drv.my_i2c_class);
}
my_i2c_drv.my_i2c_class = NULL;
class_create_fail:
cdev_del(&tmp75_dev->tmp75_cdev);
cdev_add_fail:
unregister_chrdev_region(tmep_dev, DEV_ADD_NUM);
char_region_fail:
devm_kfree(&client->dev, tmp75_dev);
tmp75_dev = NULL;
return ret;
}
static int my_tmp75_remove(struct i2c_client *client)
{
struct my_tmp75_device *tmp75_dev;
tmp75_dev = i2c_get_clientdata(client);
my_i2c_drv.dev_count--;//每卸载一个设备,则设备存活计数减1
device_destroy(my_i2c_drv.my_i2c_class, tmp75_dev->tmp75_cdev.dev);
if (!my_i2c_drv.dev_count)
{
class_destroy(my_i2c_drv.my_i2c_class);
my_i2c_drv.my_i2c_class = NULL;
}
cdev_del(&tmp75_dev->tmp75_cdev);
unregister_chrdev_region(tmp75_dev->tmp75_cdev.dev, DEV_ADD_NUM);
debug_printk("%sx is removed!\n", tmp75_dev->name);
return 0;
}
struct i2c_driver my_tmp75_driver = {
.driver = {
.name = "my_i2c",
.owner = THIS_MODULE,
},
.probe = my_tmp75_probe,
.remove = my_tmp75_remove,
.id_table = my_tmp75_ids,
};
static int __init my_tmp75_driver_init(void)
{
return i2c_add_driver(&my_tmp75_driver);
}
static void __exit my_tmp75_driver_exit(void)
{
i2c_del_driver(&my_tmp75_driver);
}
module_init(my_tmp75_driver_init);
module_exit(my_tmp75_driver_exit);
//module_i2c_driver(my_tmp75_driver);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("caodongwang");
MODULE_DESCRIPTION("This i2c driver for my tmp75!");
好了,i2c相关讲解告一段落咯~~~