版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_16777851/article/details/89036654
第一点,配置内核支持i2c
Device Drivers --->
I2C support --->
<*> I2C device interface
[*] Autoselect pertinent helper modules
I2C Hardware Bus support --->
<*> S3C2410 I2C Driver
[*] I2C Core debugging messages
[*] I2C Algorithm debugging messages
[*] I2C Bus debugging messages
I2C device interface 为使用i2cdev这个字符接口。
首先我们在设备树文件中定义这个设备,
&i2c0 {
status = "okay";
audio-codec@1b {
compatible = "wlf,wm8580";
reg = <0x1b>;
};
eeprom@50 {
compatible = "atmel,24c08";
reg = <0x50>;
};
};
可以看到在i2c总线下面会有这两个设备。
接下来我们在设备树文件的i2c总线下面去掉这两个设备。
&i2c0 {
status = "okay";
};
第一节我们使用比较简单的方式在内核下创建一个i2c设备。
扫描二维码关注公众号,回复:
6079666 查看本文章
此时我们需要写一个i2c的驱动程序。
这里我们就给at24c02写一个简单的驱动程序。
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/capability.h>
#include <linux/jiffies.h>
#include <linux/i2c.h>
#include <linux/mutex.h>
#include <linux/uaccess.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#define DEVICE_NAME "at24cxx"
static struct i2c_client * at24cxx_client;
static struct class *drv_class;
static struct cdev *chr_dev;
static struct device *device ;
static dev_t devnum;
/*
* open函数,没什么时可做,暂时就绑定i2c设备的客户端到这个设备文件
*/
static int at24cxx_open(struct inode *inode, struct file *file)
{
file->private_data = at24cxx_client;
return 0;
}
static ssize_t at24cxx_read(struct file *file, char __user *buf, size_t count,
loff_t *offset)
{
unsigned char data[50];
unsigned char addr;
int ret = 0;
/*
* 读数据包前要发送读的起始寄存器地址,之后才开始读操作
*/
struct i2c_msg msg[] = {
[0] = {
.addr = at24cxx_client->addr,
.flags = 0,
.len = 1,
.buf = &addr,
},
[1] = {
.addr = at24cxx_client->addr,
.flags = I2C_M_RD,
.len = count,
.buf = data,
}
};
/*
* 获取设备地址
*/
if(copy_from_user(&addr, buf, 1)) {
return -EFAULT;
}
if (i2c_transfer(at24cxx_client->adapter, msg, 2) != 2) {
printk(KERN_ERR"i2c_transfer fail\n");
ret = -EIO;
}
if (copy_to_user(buf ,data, count) )
return -EFAULT;
return count;
}
}
static ssize_t at24cxx_write(struct file *file, const char __user *buf,
size_t count, loff_t *off)
{
unsigned char data[50];
/*
* 写是数据包
*/
struct i2c_msg msg = {
.addr = at24cxx_client->addr,
.flags = 0,
.len = count,
.buf = data,
};
/*
* 要写的数据拷贝到data,第一个字节是寄存器地址
*/
if(copy_from_user(data, buf, count )) {
return -EFAULT;
}
if (i2c_transfer(at24cxx_client->adapter,& msg, 1) != 1) {
printk(KERN_ERR"i2c_transfer fail\n");
return -EIO;
}
return count;
}
static struct file_operations at24cxx_fops = {
.owner = THIS_MODULE,
.open = at24cxx_open,
.read = at24cxx_read,
.write = at24cxx_write,
};
static int at24cxx_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
int ret;
printk(KERN_INFO"at24cxx_probe \n");
at24cxx_client = client;
/*
* 获取一个主设备号
*/
ret = alloc_chrdev_region(&devnum, 0 , 1, "xxx" );
if(ret < 0) {
printk(KERN_ERR"alloc_chrdev_region fail\n");
goto err_alloc_chrdev_region;
}
printk(KERN_INFO"major = %d\b",MAJOR(devnum) );
/*
* 获取一个struct cdev结构体,这个结构体是自动释放的
*/
chr_dev = cdev_alloc();
if(!chr_dev ) {
goto err_cdev_alloc;
}
/*
* 初始化cdev
*/
cdev_init(chr_dev, &at24cxx_fops);
chr_dev->owner = THIS_MODULE;
/*
* 把这个cdev加入字符设备表
*/
ret = cdev_add(chr_dev, devnum, 1);
if(ret) {
printk(KERN_ERR"cdev_add fail");
goto err_cdev_add;
}
/*
* 创建一个类
*/
drv_class = class_create(THIS_MODULE, "xxxx");
if(!drv_class) {
printk(KERN_ERR"class_create fail\n");
goto err_class_create;
}
/*
* 创建一个设备
*/
device = device_create(drv_class,NULL, devnum, NULL, DEVICE_NAME);
if( !device) {
printk(KERN_ERR"device_create fail \n");
goto err_device_create;
}
return 0;
err_device_create:
class_destroy(drv_class);
err_class_create:
err_cdev_add:
cdev_del(chr_dev);
err_cdev_alloc:
unregister_chrdev_region(devnum, 1);
err_alloc_chrdev_region:
return ret;
}
static int at24cxx_remove(struct i2c_client *client)
{
device_destroy(drv_class, devnum);
class_destroy(drv_class);
cdev_del(chr_dev);
unregister_chrdev_region(devnum, 1);
return 0;
}
/*
* i2c驱动是根据下面这个i2c_device_id表中的名字来匹配,client的名字的
*/
static const struct i2c_device_id at24cxx_id_table[] = {
{ "at24cxx", 0 },
{}
};
/* Addresses to scan */
static const unsigned short normal_i2c[] = { 0x50, 0x51, 0x52, 0x53, 0x54, I2C_CLIENT_END };
static struct i2c_driver at24cxx_driver = {
.driver = {
.name = "at24cxx", /* 这个随便起名字 */
},
.probe = at24cxx_probe,
.remove = at24cxx_remove,
.id_table = at24cxx_id_table,
.address_list = normal_i2c,
};
module_i2c_driver(at24cxx_driver);
MODULE_LICENSE("GPL");
测试程序,应用程序
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
void usge_print(char *file)
{
/*
* 使用说明
*/
printf("%s </dev/at24cxx> <r> <addr> <num>\n", file);
printf("%s </dev/at24cxx> <w> <addr> <str>\n", file);
}
int main(int argc,char *argv[])
{
int fd;
unsigned char buf[50], addr;
int len, i;
if(argc != 5) {
usge_print(argv[0]);
exit(1);
}
fd = open(argv[1], O_RDWR);
if(fd < 0) {
perror("open fail\n");
exit(1);
}
memset(buf, 0, 50);
if(strcmp(argv[2], "r") == 0) {
/*
* 提取要写的寄存器地址和要写的数据长度
*/
addr = strtoul(argv[3], NULL, 0);
len = strtoul(argv[4], NULL, 0);
printf("read addr = %x , len = %d\n", addr, len);
buf[0] = addr;
/*
* 读数据
*/
if( read(fd,buf, len ) < len) {
perror("read fail\n");
exit(1);
}
for(i = 0; i < len; i ++)
printf("read data = %03d, %02x, %c\n", buf[i], buf[i],buf[i]);
} else if(strcmp(argv[2], "w") == 0) {
/*
* 提取要写的寄存器地址和要写的数据长度
*/
addr = strtoul(argv[3], NULL, 0);
buf[0] = addr;
len = strlen(argv[4]);
/*
* 第一个地址是要写的寄存器地址,所有数据要向后延长一个字节
*/
for(i = 0; i < len; i++) {
buf[i + 1] = argv[4][i];
}
if(write(fd, buf, len + 1) < (len + 1)) {
perror("write fail\n");
exit(1);
}
}
else {
usge_print(argv[0]);
exit(1);
}
return 0;
}
测试前的基础知识
我们用的这块at24c02,有32个页,每页有8个字节。
对于读操作
手册中说的很明确,在确定读的地址之后,每次读取地址会增加1,超出地址范围后,会重新返回,传过来第一个读的地址位置开始,继续顺序读取。
对于写,则不一样。
每次写到本页的最后一个字节,如果继续写,则会返回到本页的第一个字节重新开始写。这个也是为了保证,写出现问题后,不会破坏过多的数据。
我们的at24c02,每页有8个字节,所以如果从每个页的起始位置开始,最多写8个字节。
如果要继续写,就要重新开始一次传输,从下一页的首地址开始。
我们的测试程序和驱动都写的比较,没有考虑写的时候的页翻转问题,正式的代码必须考虑着这种情况,并作出处理。
测试结果
用到的知识:
static int i2c_register_adapter(struct i2c_adapter *adap)
{
......
dev_set_name(&adap->dev, "i2c-%d", adap->nr);
adap->dev.bus = &i2c_bus_type;
adap->dev.type = &i2c_adapter_type; //type,也就是sys文件系统中的属性文件
res = device_register(&adap->dev);
if (res) {
pr_err("adapter '%s': can't register device (%d)\n", adap->name, res);
goto out_list;
}
.....
}
static struct attribute *i2c_adapter_attrs[] = {
&dev_attr_name.attr, /* 查看adaptor名字 */
&dev_attr_new_device.attr, /* 创建一个client */
&dev_attr_delete_device.attr, /* 删除一个client */
NULL
};
ATTRIBUTE_GROUPS(i2c_adapter);
/*
* 属性的集合
*/
struct device_type i2c_adapter_type = {
.groups = i2c_adapter_groups,
.release = i2c_adapter_dev_release,
};
这里我们主要看创建和删除
/*
* Let users instantiate I2C devices through sysfs. This can be used when
* platform initialization code doesn't contain the proper data for
* whatever reason. Also useful for drivers that do device detection and
* detection fails, either because the device uses an unexpected address,
* or this is a compatible device with different ID register values.
*
* Parameter checking may look overzealous, but we really don't want
* the user to provide incorrect parameters.
*/
static ssize_t
i2c_sysfs_new_device(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct i2c_adapter *adap = to_i2c_adapter(dev);
struct i2c_board_info info;
struct i2c_client *client;
char *blank, end;
int res;
memset(&info, 0, sizeof(struct i2c_board_info));
/*
* 提取完空格信息
*/
blank = strchr(buf, ' ');
if (!blank) {
dev_err(dev, "%s: Missing parameters\n", "new_device");
return -EINVAL;
}
if (blank - buf > I2C_NAME_SIZE - 1) {
dev_err(dev, "%s: Invalid device name\n", "new_device");
return -EINVAL;
}
memcpy(info.type, buf, blank - buf); /* 设置设的名字 */
/* Parse remaining parameters, reject extra parameters */
res = sscanf(++blank, "%hi%c", &info.addr, &end); /* 提取设备的地址 */
if (res < 1) {
dev_err(dev, "%s: Can't parse I2C address\n", "new_device");
return -EINVAL;
}
if (res > 1 && end != '\n') {
dev_err(dev, "%s: Extra parameters\n", "new_device");
return -EINVAL;
}
/*
* 根据设备地址,增加一个属性给这个设备
*/
if ((info.addr & I2C_ADDR_OFFSET_TEN_BIT) == I2C_ADDR_OFFSET_TEN_BIT) {
info.addr &= ~I2C_ADDR_OFFSET_TEN_BIT;
info.flags |= I2C_CLIENT_TEN;
}
if (info.addr & I2C_ADDR_OFFSET_SLAVE) {
info.addr &= ~I2C_ADDR_OFFSET_SLAVE;
info.flags |= I2C_CLIENT_SLAVE;
}
/*
* 创建一个client,设备会在这个里面device_register中和驱动进行一次匹配
*/
client = i2c_new_device(adap, &info);
if (!client)
return -EINVAL;
/* Keep track of the added device */
mutex_lock(&adap->userspace_clients_lock);
/*
* 因为这个client是从用户空间创建的,所以加入用户空间client链表
* 保证从用户空间删除时,不是删掉内核空间创建的client
*/
list_add_tail(&client->detected, &adap->userspace_clients);
mutex_unlock(&adap->userspace_clients_lock);
dev_info(dev, "%s: Instantiated device %s at 0x%02hx\n", "new_device",
info.type, info.addr);
return count;
}
static DEVICE_ATTR(new_device, S_IWUSR, NULL, i2c_sysfs_new_device);
/*
* And of course let the users delete the devices they instantiated, if
* they got it wrong. This interface can only be used to delete devices
* instantiated by i2c_sysfs_new_device above. This guarantees that we
* don't delete devices to which some kernel code still has references.
*
* Parameter checking may look overzealous, but we really don't want
* the user to delete the wrong device.
*/
static ssize_t
i2c_sysfs_delete_device(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct i2c_adapter *adap = to_i2c_adapter(dev);
struct i2c_client *client, *next;
unsigned short addr;
char end;
int res;
/* Parse parameters, reject extra parameters */
/* 提取设备地址 */
res = sscanf(buf, "%hi%c", &addr, &end);
if (res < 1) {
dev_err(dev, "%s: Can't parse I2C address\n", "delete_device");
return -EINVAL;
}
if (res > 1 && end != '\n') {
dev_err(dev, "%s: Extra parameters\n", "delete_device");
return -EINVAL;
}
/* Make sure the device was added through sysfs */
res = -ENOENT;
mutex_lock_nested(&adap->userspace_clients_lock,
i2c_adapter_depth(adap));
/* 根据设备地址,查看用户空间链表是不是有这个地址,有的话则从这个链表删除,且注销这个设备 */
list_for_each_entry_safe(client, next, &adap->userspace_clients,
detected) {
if (i2c_encode_flags_to_addr(client) == addr) {
dev_info(dev, "%s: Deleting device %s at 0x%02hx\n",
"delete_device", client->name, client->addr);
list_del(&client->detected);
i2c_unregister_device(client);
res = count;
break;
}
}
mutex_unlock(&adap->userspace_clients_lock);
if (res < 0)
dev_err(dev, "%s: Can't find device in list\n",
"delete_device");
return res;
}
设和驱动的匹配规则match
struct bus_type i2c_bus_type = {
.name = "i2c",
.match = i2c_device_match,
.probe = i2c_device_probe,
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
};
static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
struct i2c_client *client = i2c_verify_client(dev);
struct i2c_driver *driver;
/* Attempt an OF style match,设备树中的优先级是最高的 */
if (i2c_of_match_device(drv->of_match_table, client))
return 1;
/* Then ACPI style match 高级配置电源管理的也比较高 */
if (acpi_driver_match_device(dev, drv))
return 1;
driver = to_i2c_driver(drv);
/* Finally an I2C match 普通情况下的匹配,使用的是id_table */
if (i2c_match_id(driver->id_table, client))
return 1;
return 0;
}
/*
* 注意这里使用的是id_table中的名字和设备名字匹配,而不是driver中的名字
*/
const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id,
const struct i2c_client *client)
{
if (!(id && client))
return NULL;
while (id->name[0]) {
if (strcmp(client->name, id->name) == 0)
return id;
id++;
}
return NULL;
}
本文所有源码都已经编译测试通过,代码仓库位置如下
https://github.com/To-run-away/linux-i2c-driver/tree/master/i2c_user_space_creare_client