一、ap3216c驱动程序

下面这段读函数的代码主要是发送读信号给client并从client中读到信号。由于是char kernelbuf,所以这边读到16位的字节用两个buf来存,kernel_buf[0] = val & 0xff; 从读到的数据取出低8位字节放到这个位置。kernel_buf[1] = (val >> 8) & 0xff;从读到的数据取出高8位字节放到这个位置。
cpp
static ssize_t ap3216c_read (struct file *file, char __user *buf, size_t size, loff_t *off)
{
printk(KERN_INFO "[ap3126c_drv] %s %s:%d\n" , __FILE__ , __FUNCTION__ , __LINE__);
char kernel_buf[6];
int val;
if(size != 6){
return -EINVAL;
}
val = i2c_smbus_read_word_data(ap3216c_client, 0xA);
kernel_buf[0] = val & 0xff;
kernel_buf[1] = (val >> 8) & 0xff;
val = i2c_smbus_read_word_data(ap3216c_client, 0xC);
kernel_buf[2] = val & 0xff;
kernel_buf[3] = (val >> 8) & 0xff;
val = i2c_smbus_read_word_data(ap3216c_client, 0xE);
kernel_buf[4] = val & 0xff;
kernel_buf[5] = (val >> 8) & 0xff;
if(copy_to_user(buf, kernel_buf, size) != 0)
{
return -1;
}
return size;
}
cpp
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/mutex.h>
#include <linux/mod_devicetable.h>
#include <linux/log2.h>
#include <linux/fs.h>
#include <linux/bitops.h>
#include <linux/jiffies.h>
#include <linux/property.h>
#include <linux/acpi.h>
#include <linux/i2c.h>
#include <linux/nvmem-provider.h>
#include <linux/regmap.h>
#include <linux/platform_data/at24.h>
#include <linux/pm_runtime.h>
#include <linux/gpio/consumer.h>
static int major;
static struct class *ap3216c_class;
static struct i2c_client *ap3216c_client;
static const struct of_device_id ap3216c_of_match[] = {
{ .compatible = "com_name,chip_name", .data = NULL },
{ /* END OF LIST */ },
};
static const struct i2c_device_id ap3216c_ids[] = {
{ "chip_name", (kernel_ulong_t)NULL },
{ /* END OF LIST */ }
};
static int ap3216c_open (struct inode *inode, struct file *file)
{
/* reset */
i2c_smbus_write_byte_data(ap3216c_client, 0, 0x4);
mdelay(20);
/* enable */
i2c_smbus_write_byte_data(ap3216c_client, 0, 0x3);
return 0;
}
static ssize_t ap3216c_read (struct file *file, char __user *buf, size_t size, loff_t *off)
{
printk(KERN_INFO "[ap3126c_drv] %s %s:%d\n" , __FILE__ , __FUNCTION__ , __LINE__);
char kernel_buf[6];
int val;
if(size != 6){
return -EINVAL;
}
val = i2c_smbus_read_word_data(ap3216c_client, 0xA);
kernel_buf[0] = val & 0xff;
kernel_buf[1] = (val >> 8) & 0xff;
val = i2c_smbus_read_word_data(ap3216c_client, 0xC);
kernel_buf[2] = val & 0xff;
kernel_buf[3] = (val >> 8) & 0xff;
val = i2c_smbus_read_word_data(ap3216c_client, 0xE);
kernel_buf[4] = val & 0xff;
kernel_buf[5] = (val >> 8) & 0xff;
if(copy_to_user(buf, kernel_buf, size) != 0)
{
return -1;
}
return size;
}
static struct file_operations i2c_example_ops = {
.owner = THIS_MODULE,
.open = ap3216c_open,
.read = ap3216c_read,
}
static int ap3216c_probe(struct i2c_client *client)
{
printk(KERN_INFO "[ap3126c_drv] %s %s:%d\n" , __FILE__ , __FUNCTION__ , __LINE__);
ap3216c_client = client;
/* register_chrdev */
major = register_chrdev(0 , "ap3216c_drv" , &i2c_example_ops);
ap3216c_class = class_create(THIS_MODULE, "ap3216c_class");
device_create(ap3216c_class, NULL, MKDEV(major, 0), NULL, "ap3216c");
return 0;
}
static int ap3216c_remove(struct i2c_client *client)
{
printk(KERN_INFO "[ap3126c_drv] %s %s:%d\n" , __FILE__ , __FUNCTION__ , __LINE__);
device_destroy(ap3216c_class, MKDEV(major, 0));
class_destroy(ap3216c_class);
unregister_chrdev(major, "ap3216c_drv");
return 0;
}
static struct i2c_driver ap3216c_driver = {
.driver = {
.name = "ap3216c",
.of_match_table = ap3216c_of_match,
},
.probe_new = ap3216c_probe,
.remove = ap3216c_remove,
.id_table = ap3216c_ids,
};
static int __init ap3216c_init(void)
{
printk(KERN_INFO "[ap3126c_drv] %s %s:%d\n" , __FILE__ , __FUNCTION__ , __LINE__);
return i2c_add_driver(&ap3216c_driver);
}
module_init(ap3216c_init);
static void __exit ap3216c_exit(void)
{
printk(KERN_INFO "[ap3126c_drv] %s %s:%d\n" , __FILE__ , __FUNCTION__ , __LINE__);
i2c_del_driver(&ap3216c_driver);
}
module_exit(ap3216c_exit);
MODULE_LICENSE("GPL");
二、I2C_Adapter驱动框架
2.1、核心结构体
在Linux中,总是使用一个结构体来描述一个对象,在I2C中也是如此,使用i2c_adapter结构体描述一个i2c总线(i2c适配器、i2c控制器)。
2.1.1 i2c_adapter结构体

2.1.2 i2c_algorithm结构体

master_xfer:这是最重要的函数,实现了一般的I2C传输,用来传输一个或者多个i2c_msg。
2.2 驱动框架
分配、设置、注册 i2c_adapter 结构体。一切都和字符设备驱动框架一样,只不过完善的是不同的结构体,有了设备树之后方便许多了。
2.2.1 设备树中添加一个i2c结点
2.2.2 写驱动程序注册platform_driver,并且在probe中分配、设置、注册 i2c_adapter 结构体
cpp
/* 不确认adapter中的nr成员使用,nr = -1系统会自动分配 */
i2c_add_adapter
/* 确定使用的数字 */
i2c_add_numbered_adapter
2.2.3 完善i2c_adapter结构体中的重要成员
cpp
static int i2c_bus_virtual_probe(struct platform_device *pdev)
{
printk("%s %s %d\n" , __FILE__ , __FUNCTION__ , __LINE__);
/* register set i2c_adapter */
g_adap = kzalloc(sizeof(*g_adap), GFP_KERNEL);
g_adap->owner = THIS_MODULE;
g_adap->class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
g_adap->nr = -1;
snprintf(g_adap->name, sizeof(g_adap->name), "i2c_virtual");
g_adap->algo = &i2c_bus_virtual_algorithm;
i2c_add_adapter(g_adap);
return 0;
}
2.2.4 i2c_algorithm结构体中实现master_xfer函数
这里主要有三种情况,下面列出各个情况使用i2ctool的命令:
1、写错地址:
cpp
i2cset -y -f 7 0x55 1 0x56
由于这个驱动程序中只有一个0x50的虚拟设备,所以地址不是0x50会直接返回错误号。
2、写数据到virtual_eeporm中
cpp
i2cset -y -f 7 0x50 1 0x56
该命令的意思是,使用i2c 7 控制器,往0x50设备中的1地址写入0x56。
3、从virtual_eeporm中读取数据
cpp
i2cget -y -f 7 0x50 1
读数据比较复杂,模拟eeporm来读写数据,当收到这个指令时,其实会收到两条msg。第一条msg是写信号,写入要读取的地址。第二条信号是读信号,读之前写的地址中的数据。
cpp
msgs[0].addr = 0x50;
msgs[0].flags = 0; // 写
msgs[0].len = 1;
msgs[0].buf = [ 0x03 ]; // 要读取的寄存器地址
msgs[1].addr = 0x50;
msgs[1].flags = I2C_M_RD; // 读
msgs[1].len = 1;
msgs[1].buf = [ ] // 将读取到的数据存入这里
cpp
static int eeporm_cur_addr = 0;
static char eeporm_buffer[512];
static eeporm_emulate_xfer(struct i2c_adapter *adap , struct i2c_msg *msgs)
{
int i;
if(msgs->flags & I2C_M_RD)
{
msgs->buf[i] = eeporm_buffer[eeporm_cur_addr++];
if(eeporm_cur_addr == 512)
eeporm_cur_addr = 0;
}else{
eeporm_cur_addr = msgs->buf[0];
for(i = 1 ; i < msgs->len ; i++){
eeporm_buffer[eeporm_cur_addr++] = msgs->buf[i];
if(eeporm_cur_addr == 512)
eeporm_cur_addr = 0;
}
}
return 0;
}
static int i2c_bus_virtual_master_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
{
int i;
int j;
while (i < num)
{
if(msgs.addr != 0x50){
i = -EIO;
return i;
}
/* 两条指令 i2cget -y -f 7 0x50 1*/
if( (i + 1 < num) && !(msgs[i].flags == I2C_M_RD)
&& (msgs[i + 1].flags == I2C_M_RD))
{
eeporm_cur_addr = msgs[i][0];
for(j = 0 ; j < msgs[i + 1]->len ; i++)
{
msgs[i+1].buf[j] = eeporm_buffer[eeporm_cur_addr++];
if(eeporm_cur_addr == 512)
eeporm_cur_addr = 0;
}
i += 2;
}
else{
/* 只有一条指令的时候 */
eeporm_emulate_xfer(adap , &msg[i]);
i++;
}
}
return 0;
}
cpp
static int i2c_bus_virtual_master_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
{
int i;
for(i = 0 ; i < num ; i++)
{
if (msgs[i].addr == 0x50)
{
eeporm_emulate_xfer(adap , &msgs[i]);
}
else
{
i = -EIO;
break;
}
}
return i;
}
2.2.5 完整代码
cpp
#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/i2c-algo-bit.h>
#include <linux/platform_data/i2c-gpio.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/gpio/consumer.h>
#include <linux/of.h>
static struct i2c_adapter *g_adap;
static char eeporm_buffer[512];
static int eeporm_cur_addr = 0;
static const struct of_device_id i2c_bus_virtual_dt_ids[] = {
{ .compatible = "xupt,i2c_virtual", },
{ /* sentinel */ }
};
static int eeporm_emulate_xfer(struct i2c_adapter *adap , struct i2c_msg *msgs)
{
int i;
if(msgs->flags & I2C_M_RD)
{
for(i = 0 ; i < msgs->len ; i++)
{
msgs->buf[i] = eeporm_buffer[eeporm_cur_addr++];
if(eeporm_cur_addr == 512)
eeporm_cur_addr = 0;
}
}
else
{
eeporm_cur_addr = msgs->buf[0];
for(i = 1 ; i < msgs->len ; i++)
{
eeporm_buffer[eeporm_cur_addr++] = msgs->buf[i];
if(eeporm_cur_addr == 512)
eeporm_cur_addr = 0;
}
}
return 0;
}
// static int i2c_bus_virtual_master_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
// {
// int i;
// for(i = 0 ; i < num ; i++)
// {
// if (msgs[i].addr == 0x50)
// {
// eeporm_emulate_xfer(adap , &msgs[i]);
// }
// else
// {
// i = -EIO;
// break;
// }
// }
// return i;
// }
static int i2c_bus_virtual_master_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
{
int i = 0;
int j;
printk(KERN_INFO"num of msgs = %d" , num);
while (i < num) {
if (msgs[i].addr != 0x50)
return -EIO;
if ((i + 1 < num) &&
!(msgs[i].flags & I2C_M_RD) &&
(msgs[i+1].flags & I2C_M_RD)) {
eeporm_cur_addr = msgs[i].buf[0];
for (j = 0; j < msgs[i+1].len; j++) {
msgs[i+1].buf[j] = eeporm_buffer[eeporm_cur_addr++];
if (eeporm_cur_addr == 512)
eeporm_cur_addr = 0;
}
i += 2;
}
else {
eeporm_emulate_xfer(adap, &msgs[i]);
i++;
}
}
return num;
}
static u32 i2c_bus_virtual_functionality(struct i2c_adapter *adap)
{
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL |
I2C_FUNC_PROTOCOL_MANGLING | I2C_FUNC_NOSTART;
}
static const struct i2c_algorithm i2c_bus_virtual_algorithm = {
.master_xfer = i2c_bus_virtual_master_xfer,
.functionality = i2c_bus_virtual_functionality,
};
static int i2c_bus_virtual_probe(struct platform_device *pdev)
{
printk("%s %s %d\n" , __FILE__ , __FUNCTION__ , __LINE__);
/* register set i2c_adapter */
g_adap = kzalloc(sizeof(*g_adap), GFP_KERNEL);
g_adap->owner = THIS_MODULE;
g_adap->class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
g_adap->nr = -1;
snprintf(g_adap->name, sizeof(g_adap->name), "i2c_virtual");
g_adap->algo = &i2c_bus_virtual_algorithm;
i2c_add_adapter(g_adap);
return 0;
}
static int i2c_bus_virtual_remove(struct platform_device *pdev)
{
printk("%s %s %d\n" , __FILE__ , __FUNCTION__ , __LINE__);
i2c_del_adapter(g_adap);
kfree(g_adap);
return 0;
}
static struct platform_driver i2c_bus_virtual_driver = {
.driver = {
.name = "xupt,i2c_virtual",
.of_match_table = of_match_ptr(i2c_bus_virtual_dt_ids),
},
.probe = i2c_bus_virtual_probe,
.remove = i2c_bus_virtual_remove,
};
static int __init i2c_bus_virtual_init(void)
{
int ret;
printk("%s %s %d\n" , __FILE__ , __FUNCTION__ , __LINE__);
ret = platform_driver_register(&i2c_bus_virtual_driver);
if (ret)
printk(KERN_ERR "i2c_bus_virtual_driver: probe failed: %d\n", ret);
return ret;
}
module_init(i2c_bus_virtual_init);
static void __exit i2c_bus_virtual_exit(void)
{
printk("%s %s %d\n" , __FILE__ , __FUNCTION__ , __LINE__);
platform_driver_unregister(&i2c_bus_virtual_driver);
}
module_exit(i2c_bus_virtual_exit);
MODULE_LICENSE("GPL");
三、使用GPIO模拟I2C控制器

reg本意是用来表示寄存器register的地址信息:<起始地址><大小>后来也可以用来分辨设备,
比如对于12C设备,可以用reg来表示它的设备地址。

3.1 设置引脚为GPIO功能

3.2 GPIO设为输出、开极或者开漏
如果没有在设备树中指定,系统的i2c-gpio.c驱动代码中会帮忙设置其flags。
3.3 要有上拉电阻
3.4 设备树
cpp
i2c_gpio{
compatible = "i2c-gpio";
gpios = <&gpio4 20 0
&gpio4 21 0>;
i2c-gpio,delay-us = <5>; /* ~100KHz */
#address-cells = <1>;
#size-cells = <0>;
}
四、I2C控制器内部结构抽象
GPIO 模拟 I2C 在数据多、中断多或需要高性能时非常不适合,因此大多数芯片都内置 I2C 控制器用于高效通信。
像之前的使用GPIO来模拟I2C,在传输过程中不允许有中断产生,整个过程如果有很多数据就会导致占用系统资源过多,系统变得缓慢。所以一般的芯片中都会有I2C控制器,发送数据的时候,会直接发送到发送寄存器,然后再发送到移位寄存器上给SDA发送,这时候发送数据函数就可以休眠,系统就会去做其他的事情,发送数据结束之后会产生中断。

4.1 I2C控制器操作方法
4.1.1 使能时钟、设置时钟
4.1.2 发送数据
把数据写入tx_register,等待中断发生。中断发生后,判断状态:是否发生错误、是否得到回应信号(ACK)。把下一个数据写入txregister,等待中断:如此循环。
4.1.3 接收数据
设置controller_register,进入接收模式,启动接收,等待中断发生。中断发生后,判断状态,读取rxregister得到数据 如此循环。