Linux驱动学习day18(I2C设备ap3216c驱动编写)

一、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得到数据 如此循环。