linux驱动系列学习之i2c子系统(四)

一、i2c子系统简介

1. i2c总线

        i2c总线因为只用SCL、SDA两根线就实现了设备之间的数据互传,极大的简化PCB布线,因此,2c总线在EEPROM、小型LCD等设备中应用极光。i2c的相关时序操作等介绍,请参考其他的博主博文,本文主要将的是Linux系统下的i2c子系统。

2. i2c子系统

        Linux系统定义了i2c驱动体系结构,在Linux系统中,i2c驱动由三部分组成,分别是i2c核心、i2c总线、i2c设备驱动。这三部分互相配合协作,形成了非常通用、可适应强的i2c框架。

        1)i2c核心:主要是i2c总线驱动和设备驱动的注册、注销方法,i2c通信方法,上层的与具体设备其无关的代码以及探测设备、检测设备地址的上层代码。i2c框架中已经做好,做设备驱动开发,一般不需要动。

        2)i2c总线:是i2c硬件提携结构中适配器端的实现,适配器可由CPU控制,一般Soc芯片里面都集成了i2c适配器。在这部分里面,控制i2c适配器产生信号,控制读写周期、产生ACK等等。该部分的代码一般由Soc厂家进行编写。

        3)i2c设备驱动:是对i2c体系的设备端的实现,i2c接口的设备一般挂载到CPU控制的i2c适配器上。主要的数据结构为i2c_driver、i2c_client,这部分主要由设备驱动开发人员完成。

二、代码实现

这部分主要以i2c接口的0.96寸oled代码为例展示使用方式,其对应的i2c地址为0x3c。

1. 入口、退出函数

一个驱动的最开始和结束,由入口函数和退出函数决定。如下:

入口函数

static int __init oled_init(void)
{
	int ret = 0;
	ret = i2c_add_driver(&oled_driver);  
	printk("i2c ret: %d\n",ret);
	return ret;
}
module_init(oled_init);

退出函数

static void __exit oled_exit(void)
{
	i2c_del_driver(&oled_driver);	
}
module_exit(oled_exit);

其中,oled_driver是struct i2c_driver类型的结构体,全部在下面。

也可以使用更简洁的方式

module_i2c_driver(oled_driver);

2.  相关数据结构

        i2c设备端代码主要是i2c_driver结构体的填写。如下

static  struct i2c_driver oled_driver = {
	.probe = oled_probe,
	.remove = oled_remove,
	.driver = {
			.owner = THIS_MODULE,
			.name = "oled",
			.of_match_table = oled_of_match_table, 
		   },
	.id_table = oled_id_table,
};

在oled_drive结构体里面可以看到oled_probe、oled_remove,分别对应i2c驱动子系统匹配drive和device匹配到和卸载掉的情况。oled_of_match_table和oled_id_table则是对spi驱动子系统匹配需要的必要信息,只要在表里面的即可匹配。

oled_of_match_table和oled_id_table如下:
 

static const struct i2c_device_id oled_id_table[] = {
    {"htq,oled", 0},  
};
static const struct of_device_id oled_of_match_table[] = {
    { .compatible = "htq,oled", },
};

compatible是设备树中对应的名字描述。oled屏幕挂载到第0个i2c适配器上,地址是0x3c,在开发板中可以看到

 3. 相关数据结构的填写

        在i2c_driver结构体中,由probe、remove成员变量。probe是设备匹配到驱动之后调用的,其将两者(driver和device)在软总线上进行比较,若一致,则两者匹配且调用probe函数。而remove则是卸载之后调用的函数,在里面会释放掉申请的各种资源。i2c子系统是注册到platform总线上面的,具体的内容以后谈到platform时再谈。

        设备匹配到之后,调用probe这个成员变量对应的函数,具体的则是oled_probe函数。

static int oled_probe(struct i2c_client *client, const struct i2c_device_id *id);

在这个函数里面,需要做:

    1. 构建设备号
        if(oled_device.major){
            oled_device.dev_id = MKDEV(oled_device.major, 0);
            register_chrdev_region(oled_device.dev_id, 1, OLED_NAME);
        } else {
            alloc_chrdev_region(&oled_device.dev_id, 0,1, OLED_NAME);
            oled_device.major = MAJOR(oled_device.dev_id);
        }
    2. 添加字符设备到内核
        cdev_init(&oled_device.cdev, &oled_fops);
        cdev_add(&oled_device.cdev, oled_device.dev_id, 1);
    3. 创建类
        oled_device.class = class_create(THIS_MODULE, OLED_NAME);
    4. 创建设备
        oled_device.device = device_create(oled_device.class,NULL, oled_device.dev_id, NULL, OLED_NAME);
    以上四个基本通用,下面的就是根据具体硬件做配置
    5. 初始化i2c设备
    等等

在第2步中,有一个oled_fops,

static const struct file_operations oled_fops = {
    .open = oled_open,
    .read =oled_read,
    .write = oled_write,
    .unlocked_ioctl = oled_unlocked_ioctl,
    .release = oled_release,
};


这个oled_fops跟之前入门led驱动led_fops类似,给驱动添加open、release、write、read、ioctl等接口。

oled_device是struct oled_device类型的自定义结构体,如下
 

struct oled_device
{
    dev_t                  dev_id;         //设备id   
    struct cdev            cdev;           //字符设备  
    struct class           *class;         //类
    struct device          *device;        //设备 
    struct device_node     *device_node;   //设备节点
    void                   *privare_data;  //私有数据
    int                    major;  
};

4. 发送、接受数据

        1) 发送数据:主要使用

struct i2c_msg m;
struct i2c_client *client;
m.addr   = client->addr;               //i2c设备从机地址,0x3c
m.buf    = buffer;                     //发送缓冲区
m.flags  = 0;
m.len    = size;                       //发送字节数
ret = i2c_transfer(client->adapter, &m, 1);

这两个结构体,在m中配置好addr、buf、flags、len即可。addr就是设备的i2c地址,这里是0x3c。

buf是发送的缓冲区,len是发送的字节数,flags为0即可。配置好之后,调用i2c_transfer(client->adapter, &m, 1),就能发送数据,这里1表示发送一个i2c_msg消息,并不是发送1Byte。

        2)接受数据:与发送数据类似,只是flags需要对其进行标记为I2C_M_RD。

m.flags = I2C_M_RD;

三、总结

总结:i2c子系统整体比较复杂,由三部分组成,i2c核心、i2c总线驱动、i2c设备驱动。i2c核心是i2c总线驱动、i2c设备驱动中间枢纽,以通用的、与平台无关的接口实现了i2c中设备与适配器的沟通。i2c总线驱动填充i2c_adapter和i2c_algorithm结构体。i2c设备驱动填充i2c_driver结构体并实现其本身对应设备类型的驱动。使用i2c子系统,从设备驱动开发看,主要做的是i2c设备驱动,调用相关API,使用比较简单,并不需要考虑其他。

环境:服务器ubuntu16,正点原子imx6ull开发板emmc版本。

参考书:Linux设备驱动开发详解(基于最新的Linux4.0内核) 宋宝华著

              Linux设备驱动程序   J & G著  

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Linux I2C设备驱动Linux内核中的一个子系统,用于处理I2C总线上的设备驱动程序。I2C是一种串行通信协议,通常用于连接各种外设,例如传感器、LCD屏幕、EEPROM、温度传感器等。 Linux I2C设备驱动程序通常包括以下几个部分: 1. i2c_driver结构体:定义I2C设备驱动的属性和操作函数。这个结构体包含了设备的名称、ID等信息,以及设备的初始化函数、读写函数等。通过注册这个结构体,将I2C设备驱动程序和I2C总线绑定在一起。 2. i2c_client结构体:定义I2C设备的属性和操作函数。这个结构体包含了设备的地址、名称等信息,以及设备的读写函数等。通过这个结构体,可以访问I2C设备,读写寄存器等。 3. probe函数:用于初始化I2C设备。当I2C总线扫描到一个新的设备时,会调用该函数,完成设备的初始化工作。 4. remove函数:用于卸载I2C设备。当I2C总线上的设备被移除时,会调用该函数,完成设备的清理工作。 5. ioctl函数:用于实现设备的特殊操作。例如,设置I2C设备的工作模式、读取设备的状态等。 通过实现这些函数,可以编写一个完整的Linux I2C设备驱动程序。在驱动程序中,可以使用Linux内核提供的函数,例如i2c_transfer函数、i2c_smbus_read_byte函数等,来实现I2C设备的读写操作。同时,也可以使用Linux的调试工具来调试驱动程序,例如dmesg命令、insmod命令等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

紫川宁520

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值