本文默认读者掌握裸机下的I2C操作,该部分只做简单介绍, 主要内容是对linux-4.12系统下I2C驱动的分析。(上一篇二十一、Linux驱动之IIC驱动(基于linux2.6.22.6内核)对linux-2.6.22.6内核的I2C进行了分析,新内核的I2C有了很大的变化,但是也有部分类似,为了保证完整性,我会全部从头分析。linux-4.12的移植和对应之前驱动的移植以后会进行更新)
I2C是由数据线SDA和时钟SCL构成的串行总线,可发送和接收数据,是一个多主机的半双工通信方式,每个挂接在总线上的器件都有个唯一的地址。位速在标准模式下可达100kbit/s,在快速模式下可达400kbit/s,在高速模式下可待3.4Mbit/s。
I2C总线仅仅使用SCL、 SDA这两根信号线就实现了设备之间的数据交互, 极大地简化了对硬件资源和PCB板布线空间的占用。 因此, I2C总线非常广泛地应用在EEPROM、 实时钟、 小型LCD等设备与CPU的接口中。
在linux-4.12内核的driver/i2c目录下内有如下文件:
里面保存I2C的通信方面的算法(algorithms)。
里面保存I2C总线驱动相关的文件,比如i2c-omap.c、 i2c-versatile.c、 i2c-s3c2410.c等。
里面保存I2C切换芯片相关驱动,本节不使用。
完成I2C总线、设备、驱动模型,对用户提供sys文件系统访问支持;为I2C内部adpter等提供注册接口。
实现了I2C适配器设备文件的功能,每一个I2C适配器都被分配为一个设备。通过适配器访问设备是的主设备号都为89,次设备号为0~255。应用程序通过“i2c-%d”(i2c-0、i2c-1、i2c-2...)文件名并使用文件操作结构open()、write()、read()、ioctl()和close()等来访问这个设备。
i2c-dev.c并没有针对特定的设备而设计,只是提供了通用read()等接口,应用程序可以借用这些接口访问适配器上的I2C设别的存储空间或寄存器,并控制I2C设别的工作方式。
I2C体系架构图如下:
主要分为3个部分,I2C核心、I2C总线驱动、I2C设备驱动。
是Linux内核用来维护和管理的I2C的核心部分,其中维护了两个静态的List,分别记录系统中的I2C driver结构和I2C adapter结构。I2C core提供接口函数,允许一个I2C adatper、I2C driver和I2C client初始化时在I2C core中进行注册,以及退出时进行注销等操作函数。同时还提供了I2C总线读写访问的一般接口。
I2C总线驱动是对I2C硬件体系结构中适配器端的实现,适配器可由CPU控制,甚至可以直接集成在CPU内部。I2C总线驱动主要包含了I2C适配器数据结构i2c_adapter、I2C适配器的算法结构i2c_algorithm和控制I2C适配器产生通信信号的函数。经由I2C总线驱动的代码,我们可以控制I2C适配器以主控方式产生开始位、停止位、读写周期,以及以从设备方式被读写、产生ACK等。
I2C设备驱动是对I2C硬件体系结构中设备端的实现,设备一般挂接在受CPU控制的I2C适配器上,通过I2C适配器与CPU交换数据。I2C设备驱动主要包含了数据结构i2c_driver和i2c_client,我们需要根据具体设备实现其中的成员函数,可以为一个具体的I2C设备开发特定的I2C设备驱动程序,在驱动中完成对特定的数据格式的解释以及实现一些专用的功能。
1. i2c_adapter与i2c_algorithm
i2c_adapter对应与物理上的一个适配器,而i2c_algorithm对应一套通信方法,一个i2c适配器需要i2c_algorithm中提供的(i2c_algorithm中的又是更下层与硬件相关的代码提供)通信函数来控制适配器上产生特定的访问周期。缺少i2c_algorithm的i2c_adapter什么也做不了,因此i2c_adapter中包含其使用i2c_algorithm的指针。
2. i2c_driver和i2c_client
i2c_driver对应一套驱动方法,i2c_client对应真实的i2c物理设备device,每个i2c设备都需要一个i2c_client来描述i2c_driver与i2c_client的关系是一对多。一个i2c_driver上可以支持多个同等类型的i2c_client。
3. i2c_adapter和i2c_client
i2c_adapter和i2c_client的关系与i2c硬件体系中适配器和设备的关系一致,即i2c_client依附于i2c_adapter,由于一个适配器上可以连接多个i2c设备,所以i2c_adapter中包含依附于它的i2c_client的链表。
在单片机中使用I2C只需要简单地配置几个I2C寄存器,构建包含开始位、停止位、读写周期、ACK等简单概念函数即可,为什么在Linux上需要构建这么复杂的I2C体系架构呢?为了通用性及为了符合Linux内核驱动模式。
对于linux系统来说,支持各式各样的CPU类型,并且还想要满足各种I2C芯片硬件特性,就必须将一些稳定性好的部分抽离出来,这样就抽象出了i2c_client(描述具体的I2C设备)、i2c_driver(描述操作I2C设备方法的接口)、i2c_adpter(描述CPU如何控制I2C总线)、i2c_algorithm(I2C通信算法)等。前两个就是抽象出的I2C设备驱动,后两个为I2C总线驱动。
在一个SoC上可能有多条I2C总线,一条总线对应一个I2C总线驱动(I2C适配器),每一条总线上又可以接多个I2C设备。假如一个SoC上有3个I2C适配器,外接3个I2C设备,想要3个适配器分别都能驱动3个I2C设备,我们只需写3个适配器代码,3个I2C设备代码即可。
下面开始深入内核分析I2C驱动。
内核的I2C驱动围绕着以下几个数据结构进行盘根交错的展开。
i2c_adapter是用来描述一个I2C适配器,在SoC中的指的就是内部外设I2C控制器,当向I2C核心层注册一个I2C适配器时就需要提供这样的一个结构体变量。
struct i2c_adapter {
struct module *owner; //所有者
/*
class表示该I2C bus支持哪些类型的I2C从设备,只有匹配的I2C从设备才能和适配器绑定。
具体的类型包括:
1. I2C_CLASS_HWMON,硬件监控类,如lm_sensors等;
2. I2C_CLASS_DDC,DDC是数字显示通道(Digital Display Channel)的意思, 通常用于显示设备信息的获取;
3. I2C_CLASS_SPD,存储类的模组;
4. I2C_CLASS_DEPRECATED,不支持自动探测。
*/
unsigned int class;
const struct i2c_algorithm *algo; //该适配器与从设备的通信算法
void *algo_data;
/* data fields that are valid for all devices */
const struct i2c_lock_operations *lock_ops;
struct rt_mutex bus_lock;
struct rt_mutex mux_lock;
int timeout; //超时时间
int retries; //重试次数
struct device dev; //该适配器设备对应的device
int nr; //该适配器的ID,会体现在sysfs中(/sys/bus/i2c/devices/i2c-n中的‘n’)
char name[48]; //适配器的名字
struct completion dev_released; //用来挂接与适配器匹配成功的从设备i2c_client的一个链表头
struct mutex userspace_clients_lock;
struct list_head userspace_clients; //clients链表
struct i2c_bus_recovery_info *bus_recovery_info;
const struct i2c_adapter_quirks *quirks;
struct irq_domain *host_notify_domain;
};
i2c_algorithm结构体代表的是适配器的通信算法,在构建i2c_adapter结构体变量的时候会去填充这个元素。
struct i2c_algorithm {
/*
I2C协议有关的数据传输接口,输入参数是struct i2c_msg类型(可参考2.3小节的介绍)
的数组(大小由num指定)。返回值是成功传输的msg的个数,如有错误返回负值。
*/
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
int num);
/*SMBUS有关的数据传输接口,如果为NULL,I2C core会尝试使用master_xfer模拟。*/
int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
unsigned short flags, char read_write,
u8 command, int size, union i2c_smbus_data *data);
/*
该I2C adapter支持的功能:
1. I2C_FUNC_I2C,支持传统的I2C功能;
2. I2C_FUNC_10BIT_ADDR,支持10bit地址;
3. I2C_FUNC_PROTOCOL_MANGLING,支持非标准的协议行为(具体请参考2.3小节的介绍);
4. I2C_FUNC_NOSTART,支持不需要发送START信号的I2C传输(具体请参考2.3小节的介绍);
5. I2C_FUNC_SMBUS_xxx,SMBUS相关的功能,不再详细介绍。
*/
u32 (*functionality) (struct i2c_adapter *);
#if IS_ENABLED(CONFIG_I2C_SLAVE)
int (*reg_slave)(struct i2c_client *client);
int (*unreg_slave)(struct i2c_client *client);
#endif
};
i2c_client描述一个I2C次设备。
struct i2c_client {
unsigned short flags; // 标志
unsigned short addr; // 该I2C设备的7位设备地址
char name[I2C_NAME_SIZE]; // 设备名字
struct i2c_adapter *adapter; // 该I2C设备支持哪个适配器
struct device dev; // 设备结构体
int irq; //设备所使用的结构体
struct list_head detected; //链表头
#if IS_ENABLED(CONFIG_I2C_SLAVE)
i2c_slave_cb_t slave_cb; /* callback for slave mode */
#endif
};
i2c_driver描述一个I2C设备驱动。
struct i2c_driver {
unsigned int class; //所属类
/* Notifies the driver that a new bus has appeared. You should avoid
* using this, it will be removed in a near future.
*/
int (*attach_adapter)(struct i2c_adapter *) __deprecated; //匹配适配器函数指针,不使用
/* 标准驱动模型接口 */
int (*probe)(struct i2c_client *, const struct i2c_device_id *);
int (*remove)(struct i2c_client *);
/* 新驱动模型接口 */
int (*probe_new)(struct i2c_client *);
/* driver model interfaces that don't relate to enumeration */
void (*shutdown)(struct i2c_client *);
/* 闹钟回调函数 */
void (*alert)(struct i2c_client *, enum i2c_alert_protocol protocol,
unsigned int data);
/* 类似于ioctl的命令接口函数 */
int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
struct device_driver driver; //该i2c设备驱动所对应的device_driver
const struct i2c_device_id *id_table; //存放该i2c设备驱动id名,用于与i2c_client->name比较匹配
/* 检测回调函数 */
int (*detect)(struct i2c_client *, struct i2c_board_info *);
const unsigned short *address_list;
struct list_head clients;
bool disable_i2c_core_irq_mapping;
};
i2c_msg描述一个I2C数据包。
struct i2c_msg {
__u16 addr; //I2C从机的设备地址
__u16 flags; //当flags=0表示写, flags= I2C_M_RD表示读
#define I2C_M_RD 0x0001 /* read data, from slave to master */
/* I2C_M_RD is guaranteed to be 0x0001! */
#define I2C_M_TEN 0x0010 /* this is a ten bit chip address */
#define I2C_M_RECV_LEN 0x0400 /* length will be first received byte */
#define I2C_M_NO_RD_ACK 0x0800 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK 0x1000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_REV_DIR_ADDR 0x2000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NOSTART 0x4000 /* if I2C_FUNC_NOSTART */
#define I2C_M_STOP 0x8000 /* if I2C_FUNC_PROTOCOL_MANGLING */
__u16 len; //传输的数据长度,等于buf数组里的字节数
__u8 *buf; //存放数据的数组
};
内核是最好的学习工具。想要写一个linux驱动,最好的方法就是分析内核自带的驱动程序。对于I2C总线驱动我们可以分析内核源码drivers/i2c/busses/i2c-s3c2410.c文件。
static const struct platform_device_id s3c24xx_driver_ids[] = {
{
.name = "s3c2410-i2c",
.driver_data = 0,
}, {
.name = "s3c2440-i2c",
.driver_data = QUIRK_S3C2440,
}, {
.name = "s3c2440-hdmiphy-i2c",
.driver_data = QUIRK_S3C2440 | QUIRK_HDMIPHY | QUIRK_NO_GPIO,
}, { },
};
... ...
static struct platform_driver s3c24xx_i2c_driver = {
.probe = s3c24xx_i2c_probe,
.remove = s3c24xx_i2c_remove,
.id_table = s3c24xx_driver_ids,
.driver = {
.name = "s3c-i2c",
.pm = S3C24XX_DEV_PM_OPS,
.of_match_table = of_match_ptr(s3c24xx_i2c_match),
},
};
static int __init i2c_adap_s3c_init(void)
{
return platform_driver_register(&s3c24xx_i2c_driver); //以platform模型注册platform_driver
}
首先在platform总线上注册一个platform_driver结构,当platform总线上有与id_table->name相同(“s3c2440-i2c”)的platform_device被注册或已经注册时调用s3c2440_i2c_driver->probe函数。也就是s3c24xx_i2c_probe()函数。
static int s3c24xx_i2c_probe(struct platform_device *pdev)
{
struct s3c24xx_i2c *i2c;
struct s3c2410_platform_i2c *pdata = NULL;
... ...
pdata = dev_get_platdata(&pdev->dev); //得到资源
... ...
/* 分配相关结构体内存 */
i2c = devm_kzalloc(&pdev->dev, sizeof(struct s3c24xx_i2c), GFP_KERNEL);
if (!i2c)
return -ENOMEM;
i2c->pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
if (!i2c->pdata)
return -ENOMEM;
... ...
/* 填充适配器i2c_adapter结构体 */
strlcpy(i2c->adap.name, "s3c2410-i2c", sizeof(i2c->adap.name)); //填充适配器名字
i2c->adap.owner = THIS_MODULE; //所有者
i2c->adap.algo = &s3c24xx_i2c_algorithm; //填充算法
i2c->adap.retries = 2; //重试次数
i2c->adap.class = I2C_CLASS_DEPRECATED; //支持的设备类型,I2C_CLASS_DEPRECATED表示不支持自动检测
i2c->tx_setup = 50;
init_waitqueue_head(&i2c->wait); //初始化等待队列头
...
i2c->clk = devm_clk_get(&pdev->dev, "i2c"); //获得时钟
/* 获得资源并映射 */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
i2c->regs = devm_ioremap_resource(&pdev->dev, res);
/*设置i2c_adapter适配器结构体, 将i2c结构体设为adap的私有数据成员*/
i2c->adap.algo_data = i2c;
i2c->adap.dev.parent = &pdev->dev;
/*初始化I2C控制器*/
ret = clk_prepare_enable(i2c->clk); //使能时钟
if (ret) {
dev_err(&pdev->dev, "I2C clock enable failed\n");
return ret;
}
ret = s3c24xx_i2c_init(i2c); //初始化2440相关寄存器
clk_disable(i2c->clk); //使能时钟
/*申请中断*/
ret = devm_request_irq(&pdev->dev, i2c->irq, s3c24xx_i2c_irq,0, dev_name(&pdev->dev), i2c);
... ...
/*设置i2c_adapter适配器*/
i2c->adap.nr = i2c->pdata->bus_num; //设置适配器ID,如果私有数据没有设置默认为0
i2c->adap.dev.of_node = pdev->dev.of_node;
... ...
/*注册i2c_adapter适配器*/
ret = i2c_add_numbered_adapter(&i2c->adap);
... ...
}
该函数主要作用如下:
1. 分配设置i2c_adapter适配器相关结构
2. 硬件相关设置
3. 设置等待队列
4. 申请中断
5. 注册i2c_adapter适配器
最后调用i2c_add_numbered_adapter注册i2c_adapter适配器。
看看该函数是如何实现的:
int i2c_add_numbered_adapter(struct i2c_adapter *adap)
{
if (adap->nr == -1) /* adap->nr=0,不满足条件 */
return i2c_add_adapter(adap);
return __i2c_add_numbered_adapter(adap);
}
注册i2c_adapter适配器有两个函数,i2c_add_adapter会自动分配adapter ID,i2c_add_numbered_adapter则可以指定ID。
在内核启动时调用(mach-smdk2440.c里)smdk2440_machine_init()函数,里面又调用s3c_i2c0_set_platdata()函数。函数代码如下:
void __init s3c_i2c0_set_platdata(struct s3c2410_platform_i2c *pd)
{
struct s3c2410_platform_i2c *npd;
if (!pd) {
pd = &default_i2c_data;
pd->bus_num = 0;
}
npd = s3c_set_platdata(pd, sizeof(struct s3c2410_platform_i2c),
&s3c_device_i2c0);
if (!npd->cfg_gpio)
npd->cfg_gpio = s3c_i2c0_cfg_gpio;
}
在s3c_i2c0_set_platdata中设置pd->bus_num = 0,而在s3c24xx_i2c_probe()函数中有如下代码:
可得adap->nr=0,所以i2c_add_numbered_adapter()函数实际调用的是__i2c_add_numbered_adapter函数(i2c_add_adapter自动分配ID后也是调用这个函数)。__i2c_add_numbered_adapter函数又调用了i2c_register_adapter()函数,函数如下:
static int i2c_register_adapter(struct i2c_adapter *adap)
{
... ...
dev_set_name(&adap->dev, "i2c-%d", adap->nr); //设置适配器名字
adap->dev.bus = &i2c_bus_type; //设置适配器成员dev的总线为i2c_bus_type
adap->dev.type = &i2c_adapter_type; //设置适配器成员dev的类型为适配器类型
res = device_register(&adap->dev); //注册适配器成员dev设备
... ...
/* 对dts上所描述的i2c_client设备进行实例化,并创建相应的sys文件:sys/bus/i2c/devices/xxx */
of_i2c_register_devices(adap);
i2c_acpi_register_devices(adap);
i2c_acpi_install_space_handler(adap);
/* 内核启动时(注册适配器之前),如果调用i2c_register_board_info()函数注册
过i2c_board_info结构(描述i2c_client的结构),就调用i2c_scan_static_board_info
对这些i2c_board_info结构里的i2c_client进行实例化 */
if (adap->nr < __i2c_first_dynamic_bus_num)
i2c_scan_static_board_info(adap);
... ...
/* 遍历i2c_bus_type匹配i2c_driver与i2c_adapter */
bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter);
... ...
}
该函数主要作用如下3大点:
1. 设置适配器名字
2. 设置适配器成员dev的总线为i2c_bus_type
3. 设置适配器成员dev的类型为适配器类型
4. 注册设备(适配器成员dev,以后可以通过这个dev找到该适配器)
1. 如果使用了设备树,对dts上所描述的i2c_client设备进行实例化,并创建相应的sys文件:sys/bus/i2c/devices/xxx。
2. 内核启动时(注册适配器之前),如果调用i2c_register_board_info()函数注册过i2c_board_info结构(描述i2c_client的结构),就调用i2c_scan_static_board_info()函数对这些i2c_board_info结构里的i2c_client进行实例化。
匹配过程实际是使用__process_new_adapter()函数来匹配i2c_bus_type上的每一个i2c_drivers,该函数最终又是调用i2c_detect()函数:
static int __process_new_adapter(struct device_driver *d, void *data)
{
return i2c_do_add_adapter(to_i2c_driver(d), data);
}
... ...
static int i2c_do_add_adapter(struct i2c_driver *driver, struct i2c_adapter *adap)
{
i2c_detect(adap, driver);
... ...
}
i2c_detect()函数部分代码如下:
static int i2c_detect(struct i2c_adapter *adapter, struct i2c_driver *driver)
{
const unsigned short *address_list;
struct i2c_client *temp_client;
int i, err = 0;
int adap_id = i2c_adapter_id(adapter); //得到适配器ID = adap->nr
address_list = driver->address_list; //从i2c_driver得到I2C设备地址列表
... ...
/* 如果适配器dapter->class定义为不自动检测类型,函数返回 */
if (adapter->class == I2C_CLASS_DEPRECATED) {
... ...
return 0;
}
/* 如果i2c_driver支持的设备类型与适配器支持的类型不一样,函数返回 */
if (!(adapter->class & driver->class))
return 0;
/* 分配设置i2c_client */
temp_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);
if (!temp_client)
return -ENOMEM;
temp_client->adapter = adapter;
/* 取出每一个address_list里的设备地址进行检测 */
for (i = 0; address_list[i] != I2C_CLIENT_END; i += 1) {
dev_dbg(&adapter->dev,
"found normal entry for adapter %d, addr 0x%02x\n",
adap_id, address_list[i]);
temp_client->addr = address_list[i];
err = i2c_detect_address(temp_client, driver); //检测该设备地址
if (unlikely(err))
break;
}
kfree(temp_client); //释放i2c_client
return err;
}
该函数主要作用如下:
1. 从i2c_driver得到I2C设备地址列表address_list
2. 判断适配器i2c_adapter、i2c_driver支持的设备类型class
3. 取出每一个address_list里的设备地址进行检测(调用i2c_detect_address()函数)
分析作用2:在前面s3c24xx_i2c_probe()函数中定义了适配器i2c_adapter为I2C_CLASS_DEPRECATED不自动检测类型,如下图:
所以对于i2c-s3c2410.c这个内核(这里只针对linux-4.12,比如linux-3.4.2 这里的class设置的是I2C_CLASS_HWMON | I2C_CLASS_SPD)提供的总线驱动来说,在 i2c_detect()函数中什么也没做直接返回了。但是对于driver/i2c/busses内核文件内的其他总线驱动,i2c_adapter适配器的class成员设置的是支持自动检测类型(如设为I2C_CLASS_HWMON),如果同时满足adapter->class与driver->class类型相同,就可以继续往下执行检测设备地址。
分析作用3:循环取出i2c_driver->address_list里存放的设备地址进行检测,即调用i2c_detect_address()函数:
static int i2c_detect_address(struct i2c_client *temp_client, struct i2c_driver *driver)
{
struct i2c_board_info info;
struct i2c_adapter *adapter = temp_client->adapter;
int addr = temp_client->addr;
int err;
/* 确保7位I2C设备地址在0x08~0x77之间 */
err = i2c_check_7bit_addr_validity_strict(addr);
if (err) {
dev_warn(&adapter->dev, "Invalid probe address 0x%02x\n",
addr);
return err;
}
... ...
/* 确保总线上真实挂接有该设备地址的I2C芯片,通过i2c_smbus_xfer发送设备地址,
如果有ACK返回,表示确实有真实的I2C芯片接在该总线上,如果没有ACK,函数返回 */
if (!i2c_default_probe(adapter, addr))
return 0;
/* 调用i2c_driver->detect检测函数再次检测芯片具体是什么芯片,如通过读写芯片特定的寄存器 */
memset(&info, 0, sizeof(struct i2c_board_info));
info.addr = addr;
err = driver->detect(temp_client, &info);
... ...
/*创建设备*/
client = i2c_new_device(adapter, &info);
... ...
return 0;
}
该函数主要作用如下:
1. 确保传入的7位I2C设备地址在0x08~0x77之间(I2C芯片通常是7位设备地址,范围在0x08~0x77之间)
2. 确保总线上真实挂接有该设备地址的I2C芯片(在i2c_default_probe()函数中通过i2c_smbus_xfer发送设备地址,如果有ACK返回,表示确实有真实的I2C芯片接在该总线上,如果没有ACK,函数返回)
3. 调用i2c_driver->detect检测函数再次检测芯片具体是什么芯片(如通过读写I2C芯片特定的寄存器)
4. 调用i2c_new_device创建设备(创建I2C设备驱动的i2c_client)
下面先来看看i2c_smbus_xfer()函数是如何检测设备地址是否真实存在的。
函数部分代码如下:
s32 i2c_smbus_xfer(struct i2c_adapter *adapter, u16 addr, unsigned short flags,
char read_write, u8 command, int protocol,
union i2c_smbus_data *data)
{
... ...
flags &= I2C_M_TEN | I2C_CLIENT_PEC | I2C_CLIENT_SCCB;
if (adapter->algo->smbus_xfer) { //如果适配器指定了algo->smbus_xfer成员
... ...
}
res = i2c_smbus_xfer_emulated(adapter, addr, flags, read_write,
command, protocol, data); //使用smbus协议发送I2C消息
... ...
}
该函数首先判断i2c_adapter适配器成员algo->smbus_xfer函数,可以回顾一下i2c-s3c2410.c中的i2c_adapter适配器是如何定义的,s3c24xx_i2c_probe()函数中有如下代码:
s3c24xx_i2c_algorithm定义如下:
可见适配器并没有algo->smbus_xfer函数,所以i2c_smbus_xfer()函数实际是使用i2c_smbus_xfer_emulated()函数进行发送数据的,i2c_smbus_xfer_emulated()函数部分代码如下:
static s32 i2c_smbus_xfer_emulated(struct i2c_adapter *adapter, u16 addr,
unsigned short flags,
char read_write, u8 command, int size,
union i2c_smbus_data *data)
{
unsigned char msgbuf0[I2C_SMBUS_BLOCK_MAX+3]; //属于 msg[0]的buf成员
unsigned char msgbuf1[I2C_SMBUS_BLOCK_MAX+2]; //属于 msg[1]的buf成员
int num = read_write == I2C_SMBUS_READ ? 2 : 1; //如果为读命令,就等于2,表示要执行两次数据传输
int i;
u8 partial_pec = 0;
int status;
struct i2c_msg msg[2] = {
{
.addr = addr,
.flags = flags,
.len = 1,
.buf = msgbuf0,
}, {
.addr = addr,
.flags = flags | I2C_M_RD,
.len = 0,
.buf = msgbuf1,
},
};
msgbuf0[0] = command; //IIC设备地址最低位为读写命令
switch (size) {
... ...
/*读操作需要两个i2c_msg,写操作需要一个i2c_msg*/
case I2C_SMBUS_BYTE_DATA:
if (read_write == I2C_SMBUS_READ) //如果是读字节
msg[1].len = 1;
else {
msg[0].len = 2;
msgbuf0[1] = data->byte;
}
break;
... ...
status = i2c_transfer(adapter, msg, num); //将i2c_msg结构体的内容发送给I2C设备
if (status < 0) //返回值不等于0表示没有收到ACK
return status;
}
可见该函数是通过构造i2c_msg结构体通过i2c_transfer()函数发送给I2C设备,如果I2C设备返回ACK表示设备存在。i2c_transfer()函数实际又调用了__i2c_transfer()函数,部分代码如下:
int __i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
... ...
orig_jiffies = jiffies;
for (ret = 0, try = 0; try <= adap->retries; try++) {
ret = adap->algo->master_xfer(adap, msgs, num);
if (ret != -EAGAIN)
break;
if (time_after(jiffies, orig_jiffies + adap->timeout))
break;
}
... ...
}
最终是调用到了I2C总线驱动里注册的adapter适配器的算法函数master_xfer发送i2c_msg。master_xfer()函数调用过程:
1. 传输数据时,调用s3c24xx_i2c_algorithm结构体中的数据传输函数s3c24xx_i2c_xfer()。
2. s3c24xx_i2c_xfer()中会调用s3c24xx_i2c_doxfer()进行数据的传输。
3. s3c24xx_i2c_doxfer()中向总线发送IIC设备地址和开始信号S后,便会调用wati_event_timeout()函数进入等待状态。
4. 将数据准备好发送时,将产生中断,并调用实现注册的中断处理函数s3c24xx_i2c_irq()。
5. s3c24xx_i2c_irq()调用下一个字节传输函数i2c_s3c_irq_nextbyte()来传输数据。
6. 当数据传输完成后,会调用 s3c24xx_i2c_stop()。
7. 最后调用wake_up()唤醒等待队列,完成数据的传输过程。
其中第5点调用i2s_s3c_irq_nextbyte()函数中有如下代码:
case STATE_START:
if (iicstat & S3C2410_IICSTAT_LASTBIT &&
!(i2c->msg->flags & I2C_M_IGNORE_NAK)) {
/* 如果没有收到ACK */
dev_dbg(i2c->dev, "ack was not received\n");
s3c24xx_i2c_stop(i2c, -ENXIO);
goto out_ack;
}
当使用适配器发送数据没有收到ACK会有如下调用:
s3c24xx_i2c_stop(i2c, -ENXIO);
s3c24xx_i2c_master_complete(i2c, ret); // ret=-ENXIO
if (ret)
i2c->msg_idx = ret; // i2c->msg_idx=-ENXIO
i2c_smbus_xfer()又通过以下调用得到返回值-ENXIO:
i2c_smbus_xfer()
i2c_transfer()
s3c24xx_i2c_xfer()
s3c24xx_i2c_doxfer
ret = i2c->msg_idx; // ret=-ENXIO
return ret;
回顾3.2.3.3中讲的i2c_detect_address()函数内容:
1. 确保传入的7位I2C设备地址在0x08~0x77之间(I2C芯片通常是7位设备地址,范围在0x08~0x77之间)
2. 确保总线上真实挂接有该设备地址的I2C芯片(在i2c_default_probe()函数中通过i2c_smbus_xfer发送设备地址,如果有ACK返回,表示确实有真实的I2C芯片接在该总线上,如果没有ACK,函数返回)
3. 调用i2c_driver->detect检测函数再次检测芯片具体是什么芯片(如通过读写I2C芯片特定的寄存器)
4. 调用i2c_new_device创建设备(创建I2C设备驱动的i2c_client)
i2c_detect_address()函数中检验设备地址i2c_default_probe()函数使用如下:
static int i2c_default_probe(struct i2c_adapter *adap, unsigned short addr)
{
int err;
... ...
err = i2c_smbus_xfer(... ...);
... ...
return err >= 0;
}
... ...
static int i2c_detect_address(struct i2c_client *temp_client,
struct i2c_driver *driver)
{
... ...
if (!i2c_default_probe(adapter, addr))
return 0;
/* 调用i2c_driver->detect检测函数再次检测芯片具体是什么芯片,如通过读写芯片特定的寄存器 */
memset(&info, 0, sizeof(struct i2c_board_info));
info.addr = addr;
err = driver->detect(temp_client, &info);
... ...
/*创建设备*/
client = i2c_new_device(adapter, &info);
... ...
return 0;
}
可见,当使用i2c_smbus_xfer()函数(最终是调用到了I2C总线驱动里注册的adapter适配器的算法函数master_xfer)发送数据(设备地址)时:
1. 如果没有收到I2C设备的ACK, i2c_smbus_xfer()返回-ENXIO,所以i2c_default_probe()函数返回0,i2c_detect_address()函数也就会返回0。
2. 如果收到ACK表示I2C总线上确实有该设备地址的芯片存在,就会调用i2c_driver->detect检测函数再次检测该设备地址的芯片具体是什么芯片(如通过读写I2C芯片特定的寄存器)。接着便调用i2c_new_device()创建设备(i2c_client),该函数是I2C设备驱动中很重要的组成部分,放到后面在仔细分析。
到目前为止,i2c-s3c2410.c文件为我们实现了对2440上I2C控制器的设置、I2C通信协议相关代码等,组成了一个适配器i2c_adapter(I2C总线驱动),使用这个适配器内核就知道如何与挂在I2C总线上的I2C设备通信了。下面讲解I2C设备驱动部分。
其实在以上讲解分析I2C总线驱动部分已经涉及到I2C设备驱动的使用了,实例化创建i2c_client、使用i2c_client、i2c_driver等。I2C总线驱动与I2C设备驱动相互盘根交错,并没有明显的界线。如果说I2C总线驱动的核心是向内核注册适配器(i2c_adapter),那么I2C设备驱动的核心就是实例化I2C设备(i2c_client)与注册设备驱动(i2c_driver)。
在Linux内核文档(/Documentation/i2c/instantiating-devices)中,介绍了实例化I2C设备(注册i2c_client)的4种方法,实际上这4种方法最终都是通过调用i2c_new_device()函数来实例化I2C设备的,所以先来仔细分析一下i2c_new_device()函数。
实例化I2C设备核心就是调用i2c_new_device()函数,该函数部分代码如下:
struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
struct i2c_client *client;
int status;
client = kzalloc(sizeof *client, GFP_KERNEL); //分配i2c_client结构体内存
... ...
/*设置i2c_client结构体*/
client->adapter = adap; //设置该从设备的适配器
... ...
client->flags = info->flags; //设置该从设备的标志
client->addr = info->addr; //设置该从设备的设备地址
client->irq = info->irq; //中断号
... ...
strlcpy(client->name, info->type, sizeof(client->name)); //设置该从设备的名字
... ...
client->dev.parent = &client->adapter->dev; //设置设备父类为适配器的dev
client->dev.bus = &i2c_bus_type; //设置设备总线类型
client->dev.type = &i2c_client_type; //设置设备类型
client->dev.of_node = info->of_node; //设备节点
client->dev.fwnode = info->fwnode;
i2c_dev_set_name(adap, client);
/* 向设备添加属性 */
if (info->properties) {
status = device_add_properties(&client->dev, info->properties);
if (status) {
dev_err(&adap->dev,
"Failed to add properties to client %s: %d\n",
client->name, status);
goto out_err;
}
}
/* 注册设备 */
status = device_register(&client->dev);
... ...
return client;
... ...
}
该函数传入两个参数,struct i2c_adapter结构和struct i2c_board_info结构,前者即是I2C总线驱动中的适配器,后者就是一个I2C设备信息表,结构原型如下(include/linux/i2c.h):
struct i2c_board_info {
char type[I2C_NAME_SIZE]; //描述I2C设备类型名
unsigned short flags;
unsigned short addr; //描述I2C设备地址
void *platform_data; //平台数据
struct dev_archdata *archdata;
struct device_node *of_node; //设备节点
struct fwnode_handle *fwnode;
const struct property_entry *properties;
const struct resource *resources;
unsigned int num_resources;
int irq;
};
/*设置i2c_board_info结构体type与addr成员的宏 */
#define I2C_BOARD_INFO(dev_type, dev_addr) \
.type = dev_type, .addr = (dev_addr)
我们重点关心type与addr成员,type描述一个I2C设备类型名,addr描述I2C设备地址。在i2c_new_device()函数中会将传入的i2c_board_info结构体成员拷贝给i2c_client结构,现在再来总结一下i2c_new_device()函数内容:
1. 分配i2c_client结构体
2. 设置i2c_client结构体
2.1 设置适配器
2.2 设置flag
2.3 设置设备地址(i2c_board_info->addr)
2.4 设置irq
2.4 设置设备名字(i2c_board_info->type)
3. 设置i2c_client描述的device设备(通过这个device可以找到i2c_client)
4. 使用device_register()函数注册device设备
最后是调用device_register注册这个device结构,由总线设备驱动模型可以猜到,必定有地方调用driver_register注册一个device_driver结构。内核通过调用i2c_add_driver()函数注册设备驱动(i2c_driver)里,就会注册与i2c_driver相关联的device_driver结构。
对应的释放设备函数原型为:
void i2c_unregister_device(struct i2c_client *client);
在include/linux/i2c.h中有如下宏定义:
//include/linux/i2c.h
#define i2c_add_driver(driver) \
i2c_register_driver(THIS_MODULE, driver)
所以i2c_add_driver()函数实际调用了i2c_register_driver()函数,该函数代码如下:
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{
int res;
... ...
/* 设置i2c_driver的device_driver成员 */
driver->driver.owner = owner; //设置device_driver成员的所有者
driver->driver.bus = &i2c_bus_type; //设置device_driver成员的总线类型
INIT_LIST_HEAD(&driver->clients);
/* 注册device_driver结构体 */
res = driver_register(&driver->driver);
if (res)
return res;
/* 遍历i2c_bus_type匹配i2c_driver与i2c_adapter */
i2c_for_each_dev(driver, __process_new_driver);
return 0;
}
该函数作用如下:
1. 设置i2c_driver的device_driver成员
2. 使用driver_register()函数注册device_driver
3. 遍历i2c_bus_type匹配i2c_driver与i2c_adapter
由总线设备驱动模型可以知道,当向内核注册device_driver驱动或者注册device设备,都会调用到device_driver或device的总线结构体里的.match匹配函数匹配设备链表与驱动链表,由于他们的总线类型都设为i2c_bus_type,所以使用i2c_new_device()函数实例化I2C设备(i2c_client)或者使用i2c_add_driver()函数注册设备驱动(i2c_driver)时,都会调用到i2c_bus_type结构的.match匹配函数,i2c_bus_type结构定义如下:
struct bus_type i2c_bus_type = {
.name = "i2c",
.match = i2c_device_match,
.probe = i2c_device_probe,
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
};
其中i2c_device_match()函数代码如下:
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) //通过比较.name成员进行匹配
return id;
id++;
}
return NULL;
}
... ...
static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
struct i2c_client *client = i2c_verify_client(dev); //得到i2c_client结构体
struct i2c_driver *driver;
/* 使用设备树匹配i2c_driver->of_match_table与i2c_client */
if (i2c_of_match_device(drv->of_match_table, client))
return 1;
/* ACPI类型匹配 */
if (acpi_driver_match_device(dev, drv))
return 1;
driver = to_i2c_driver(drv); //得到i2c_driver结构体
/* 匹配i2c_driver->id_table与i2c_client */
if (i2c_match_id(driver->id_table, client))
return 1;
return 0;
}
可见i2c_bus_type->match匹配函数实际是通过判断i2c_driver->id_table->name与i2c_client->name是否相同,(这里思考一个问题,在调用i2c_new_device()函数注册i2c_client时,是通过传入的i2c_board_info->type成员赋值给i2c_client->name的,如果i2c_board_info不是由我们构建传入的,调用i2c_new_device()函数之前要在哪里指定这个i2c_board_info->type呢?)。相同则匹配成功,调用i2c_bus_type->probe函数,该函数部分代码如下:
static int i2c_device_probe(struct device *dev)
{
struct i2c_client *client = i2c_verify_client(dev); //得到i2c_client结构体
struct i2c_driver *driver;
int status;
... ...
driver = to_i2c_driver(dev->driver); //得到i2c_driver结构体
... ...
/* i2c_driver没有id_table成员且设备树匹配失败则函数返回 */
if (!driver->id_table &&
!i2c_of_match_device(dev->driver->of_match_table, client))
return -ENODEV;
... ...
if (driver->probe_new) //如果i2c_driver结构体有probe_new成员则调用
status = driver->probe_new(client);
else if (driver->probe) //如果i2c_driver结构体有probe成员则调用
status = driver->probe(client,
i2c_match_id(driver->id_table, client));
else
status = -EINVAL;
... ...
return 0;
... ...
}
i2c_bus_type->probe函数最终调用的是i2c_driver结构体的probe_new成员或者probe成员。probe函数里面做的事情由我们自己决定比如注册、构建设备节点等。
i2c_register_driver()函数的作用3遍历i2c_bus_type匹配i2c_driver与i2c_adapter就是调用下面代码:
__process_new_driver()函数内容如下:
static int __process_new_driver(struct device *dev, void *data)
{
if (dev->type != &i2c_adapter_type)
return 0;
return i2c_do_add_adapter(data, to_i2c_adapter(dev));
}
只调用了i2c_do_add_adapter()函数,是不是很熟悉呢?没错,在3.2.3中注册i2c_adapter适配器时最终也调用了该函数,
作用就是遍历i2c_bus_type匹配i2c_driver与i2c_adapter的,所以调用i2c_add_driver()函数会做与3.2.3.3节完全一样的工作,这里就不再重复了。
Linux内核中构建了许多总线,但并不都是真实的,比如platform平台总线就是虚拟的,平台总线中也有设备链表,驱动链表。针对I2C总线,也有一个I2C总线的结构即i2c_bus_type结构,此结构里面也有设备链表和也有驱动链表。
设备链表里存放i2c_client的结构体,这些结构体是调用i2c_new_device()函数时加入的,不但要加入这些结构体,还会在i2c_bus_type结构的驱动链表中一个一个地比较drv里的i2c_driver来判断是否有匹配的,如果有将调用i2c_driver里面的probe函数,匹配函数由总线提供。
驱动链表里存放i2c_driver结构体,这些结构体是调用i2c_add_driver()时加入的,不但要加入这些结构体,还会在i2c_bus_type结构的设备链表中一个一个地比较dev里的i2c_client来判断是否有匹配的,如果匹配将调用i2c_driver里面的probe函数。
上述的匹配函数就是i2c_bus_type结构里面的i2c_device_match函数。i2c_device_match函数通过i2c_driver->id_table->name与i2c_client->name比较(使用设备树是比较compatible属性),如果相同,就表示此驱动drv里存放的i2c_driver能支持这个设备dev里存放的i2c_client。
总的说来,I2C设备驱动基于bus-dev-drv模型的构建过程如下:
1. 左边注册一个设备,设备里存有i2c_client(调用i2c_new_device()函数)
2. 右边注册一个驱动,驱动里存有i2c_driver(调用i2c_add_driver()函数)
3. 比较i2c_driver->id_table->name与i2c_client->name(使用设备树是比较compatible属性),如果相同,则调用i2c_driver的probe函数。
4. probe函数里面做的事情由用户决定(比如注册、构建设备节点等)。
上面介绍了实例化I2C设备的底层概念,接下来分析对于编写驱动我们应该做什么。总共有四种方法(如果算上设备树总共有5种方法,这里就不介绍设备树方式了),这4种方法最终都是通过调用i2c_new_device()函数来实例化I2C设备的。
该方式核心是向内核静态注册i2c_board_info结构。
回顾3.2.3 注册i2c_adapter适配器中,内核启动后会装载i2c-s3c2410.c(默认编译进内核)该驱动,也就是会调用到i2c_add_numbered_adapter()函数然后调用i2c_register_adapter()函数,该函数部分代码如下:
static int i2c_register_adapter(struct i2c_adapter *adap)
{
... ...
dev_set_name(&adap->dev, "i2c-%d", adap->nr); //设置适配器名字
adap->dev.bus = &i2c_bus_type; //设置适配器成员dev的总线为i2c_bus_type
adap->dev.type = &i2c_adapter_type; //设置适配器成员dev的类型为适配器类型
res = device_register(&adap->dev); //注册适配器成员dev设备
... ...
/* 对dts上所描述的i2c_client设备进行实例化,并创建相应的sys文件:sys/bus/i2c/devices/xxx */
of_i2c_register_devices(adap);
i2c_acpi_register_devices(adap);
i2c_acpi_install_space_handler(adap);
/* 内核启动时(注册适配器之前),如果调用i2c_register_board_info()函数注册
过i2c_board_info结构(描述i2c_client的结构),就调用i2c_scan_static_board_info
对这些i2c_board_info结构里的i2c_client进行实例化 */
if (adap->nr < __i2c_first_dynamic_bus_num)
i2c_scan_static_board_info(adap);
... ...
/* 遍历i2c_bus_type匹配i2c_driver与i2c_adapter */
bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter);
... ...
}
通过判断比较adap->nr与 __i2c_first_dynamic_bus_num决定是否执行i2c_scan_static_board_info()函数。假设执行了该函数,看看该函数做了什么工作:
static void i2c_scan_static_board_info(struct i2c_adapter *adapter)
{
struct i2c_devinfo *devinfo;
down_read(&__i2c_board_lock);
/* 循环取出__i2c_board_list链表里的devinfo->board_info传入i2c_new_device()函数 */
list_for_each_entry(devinfo, &__i2c_board_list, list) {
if (devinfo->busnum == adapter->nr
&& !i2c_new_device(adapter,
&devinfo->board_info))
dev_err(&adapter->dev,
"Can't create device at 0x%02x\n",
devinfo->board_info.addr);
}
up_read(&__i2c_board_lock);
}
只做了一件事:循环取出__i2c_board_list链表里的devinfo->i2c_board_info传入i2c_new_device()函数。也就是将__i2c_board_list链表里保存的I2C设备信息实例化了。
下面分析看看什么情况下会执行i2c_scan_static_board_info()函数。从3.2.3 注册i2c_adapter适配器中我们得到adap->nr=0,那么__i2c_first_dynamic_bus_num为多少呢?搜索内核发现,在driver/i2c/i2c_boardinfo.c文件中有如下内容:
int __i2c_first_dynamic_bus_num; //定义__i2c_first_dynamic_bus_num,默认初始化为0
EXPORT_SYMBOL_GPL(__i2c_first_dynamic_bus_num);
... ...
int i2c_register_board_info(int busnum, struct i2c_board_info const *info, unsigned len)
{
int status;
... ...
if (busnum >= __i2c_first_dynamic_bus_num)
__i2c_first_dynamic_bus_num = busnum + 1;
/* 为每一个I2C设备信息分配内存,添加到__i2c_board_list链表里 */
for (status = 0; len; len--, info++) {
struct i2c_devinfo *devinfo;
devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL);
if (!devinfo) {
pr_debug("i2c-core: can't register boardinfo!\n");
status = -ENOMEM;
break;
}
devinfo->busnum = busnum;
devinfo->board_info = *info;
... ...
list_add_tail(&devinfo->list, &__i2c_board_list); //添加到__i2c_board_list链表
}
... ...
return status;
}
在driver/i2c/i2c_boardinfo.c文件中定义__i2c_first_dynamic_bus_num未初始化则默认为0,该变量只在i2c_register_board_info()注册单板I2C信息函数里执行修改操作(还在i2c_init()函数里修改该变量,但是没有调用该函数)。
对于2440的单板文件mach-smdk2440.c里并没有使用i2c_register_board_info()注册单板I2C信息函数,所以__i2c_first_dynamic_bus_num一直为0,也就是说在i2c_register_adapter()函数里不会执行i2c_scan_static_board_info()扫描单板I2C信息函数。我们参考mach-mini2440.c单板文件如何使用i2c_register_board_info()函数的:
static struct i2c_board_info mini2440_i2c_devs[] __initdata = {
{
I2C_BOARD_INFO("24c08", 0x50), //"24c08"表示设备名,0x50表示设备地址
.platform_data = &at24c08,
},
};
... ...
static void __init mini2440_init(void)
{
... ...
/* 设置platform_device I2C相关信息 */
s3c_i2c0_set_platdata(NULL);
/* 注册单板I2C相关信息
参数1:I2C总线号,0
参数2:i2c_board_info结构体数组指针
参数3:i2c_board_info结构体数组的成员个数
*/
i2c_register_board_info(0, mini2440_i2c_devs,
ARRAY_SIZE(mini2440_i2c_devs));
... ...
}
MACHINE_START(MINI2440, "MINI2440")
/* Maintainer: Michel Pollet */
.atag_offset = 0x100,
.map_io = mini2440_map_io,
.init_machine = mini2440_init,
.init_irq = s3c2440_init_irq,
.init_time = mini2440_init_time,
MACHINE_END
当内核启动时就会调用mini2440_init()函数,里面调用了i2c_register_board_info()注册单板I2C信息函数(注意:该函数要在注册适配器之前调用),参数1为I2C总线号0(适配器0),参数2为定义的i2c_board_info结构体数组指针,参数3为i2c_board_info结构体数组成员个数,i2c_board_info结构体在3.3.1 i2c_new_device()函数的讲解中介绍了,这里不再重复。
这样调用i2c_register_board_info()函数如下图,导致__i2c_first_dynamic_bus_num=1。
所以在调用i2c_register_adapter()函数时会调用到i2c_scan_static_board_info()函数。针对JZ2440开发板(单板相关文件对应mach-smdk2440.c)仿照mach-mini2440.c修改内核源码即可实现实例化I2C设备。(具体实现放在后面的5.1 方法一)
该方式核心是向内核动态注册i2c_board_info结构。
与实例化I2C设备方法一类似,该方法也是通过构建i2c_board_info结构体,然后直接调用i2c_new_device()函数,对于该函数第一个参数需要的i2c_adapter结构体,可以通过i2c_get_adapter()函数(传入参数0表示想要获得适配器0)得到。与方法一的区别在于可以方便insmod,不需要修改源码,也就是可以在注册适配器之后再实例化I2C设备。
还可以通过调用i2c_new_probed_device()函数,该函数部分代码如下:
struct i2c_client *i2c_new_probed_device(struct i2c_adapter *adap,
struct i2c_board_info *info,
unsigned short const *addr_list,
int (*probe)(struct i2c_adapter *, unsigned short addr))
{
int i;
if (!probe)
probe = i2c_default_probe; //如果参数4没有指定检测函数,使用默认检测函数检测设备地址是否真实存在
/* 循环判断addr_list上的设备地址是否真实存在,存在则打破循环 */
for (i = 0; addr_list[i] != I2C_CLIENT_END; i++) {
/* Check address validity */
if (i2c_check_7bit_addr_validity_strict(addr_list[i]) < 0) {
dev_warn(&adap->dev, "Invalid 7-bit address 0x%02x\n",
addr_list[i]);
continue;
}
/* Check address availability (7 bit, no need to encode flags) */
if (i2c_check_addr_busy(adap, addr_list[i])) {
dev_dbg(&adap->dev,
"Address 0x%02x already in use, not probing\n",
addr_list[i]);
continue;
}
/* Test address responsiveness */
if (probe(adap, addr_list[i]))
break;
}
if (addr_list[i] == I2C_CLIENT_END) {
dev_dbg(&adap->dev, "Probing failed, no device found\n");
return NULL;
}
info->addr = addr_list[i]; //将该设备地址存入i2c_board_info
return i2c_new_device(adap, info); //创建设备
}
该函数主要内容如下:
1. 判断参数4是否指定检测函数,没有则使用默认检测函数检测设备地址是否真实存在
2. 循环判断addr_list上的设备地址是否真实存在,存在则打破循环
3. 调用i2c_new_device创建设备
最终还是调用了i2c_new_device()函数,只不过先检测了设备地址是否真实存在(调用i2c_default_probe()函数检验设备地址是否有ACK)。(具体实现放在后面的5.2 方法二)
该方式核心是使用i2c_add_driver()函数注册设备驱动时会自动匹配适配器,自动实例化I2C设备。
回顾3.3.2 i2c_add_driver()函数讲的,当调用i2c_add_driver()函数最终会调用到i2c_detect()函数,函数部分代码如下:
static int i2c_detect(struct i2c_adapter *adapter, struct i2c_driver *driver)
{
const unsigned short *address_list;
struct i2c_client *temp_client;
int i, err = 0;
int adap_id = i2c_adapter_id(adapter); //得到适配器ID = adap->nr
address_list = driver->address_list; //从i2c_driver得到I2C设备地址列表
... ...
/* 如果适配器dapter->class定义为不自动检测类型,函数返回 */
if (adapter->class == I2C_CLASS_DEPRECATED) {
... ...
return 0;
}
/* 如果i2c_driver支持的设备类型与适配器支持的类型不一样,函数返回 */
if (!(adapter->class & driver->class))
return 0;
/* 分配设置i2c_client */
temp_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);
if (!temp_client)
return -ENOMEM;
temp_client->adapter = adapter;
/* 取出每一个address_list里的设备地址进行检测 */
for (i = 0; address_list[i] != I2C_CLIENT_END; i += 1) {
dev_dbg(&adapter->dev,
"found normal entry for adapter %d, addr 0x%02x\n",
adap_id, address_list[i]);
temp_client->addr = address_list[i];
err = i2c_detect_address(temp_client, driver); //检测该设备地址
if (unlikely(err))
break;
}
kfree(temp_client); //释放i2c_client
return err;
}
首先对于mach-smdk2440.c这个适配器来说,之前代码分析了注册该适配器时会指定adapter->class = I2C_CLASS_DEPRECATED,我们可以通过修改该内容,让这个适配器支持自动检测。然后当调用i2c_add_driver()函数传入的i2c_driver结构体定义了class成员(驱动支持的类型),并且与某个(2440只有一个)适配器的class(适配器支持的类型)匹配,表示使用自动检测模式注册设备驱动,会继续调用i2c_detect_address()函数,该函数部分代码如下:
static int i2c_detect_address(struct i2c_client *temp_client, struct i2c_driver *driver)
{
struct i2c_board_info info;
struct i2c_adapter *adapter = temp_client->adapter;
int addr = temp_client->addr;
int err;
/* 确保7位I2C设备地址在0x08~0x77之间 */
err = i2c_check_7bit_addr_validity_strict(addr);
if (err) {
dev_warn(&adapter->dev, "Invalid probe address 0x%02x\n",
addr);
return err;
}
... ...
/* 确保总线上真实挂接有该设备地址的I2C芯片,通过i2c_smbus_xfer发送设备地址,
如果有ACK返回,表示确实有真实的I2C芯片接在该总线上,如果没有ACK,函数返回 */
if (!i2c_default_probe(adapter, addr))
return 0;
/* 调用i2c_driver->detect检测函数再次检测芯片具体是什么芯片,如通过读写芯片特定的寄存器 */
memset(&info, 0, sizeof(struct i2c_board_info));
info.addr = addr;
err = driver->detect(temp_client, &info);
... ...
/*创建设备*/
client = i2c_new_device(adapter, &info);
... ...
return 0;
}
最终也会调用i2c_new_device()函数注册设备(详细分析内容看3.2.3.3)。这里回答3.3.2.1 里提出的思考问题:调用i2c_new_device()函数注册i2c_client时,是通过传入的i2c_board_info->type成员赋值给i2c_client->name的,如果i2c_board_info不是由我们构建传入的,调用i2c_new_device()函数之前要在哪里指定这个i2c_board_info->type呢?可以看到调用i2c_new_device()函数之前会调用i2c_driver->detect检测函数,我们可以在该函数里赋值i2c_board_info->type成员。
方法二调用i2c_add_driver()函数注册设备驱动时不指定i2c_driver结构体的class成员,所以这里直接返回,需要我们自己调用i2c_new_device()函数。
(具体实现放在后面的5.3 方法三)
该方式核心是从用户空间直接实例化I2C设备。
在用户空间执行例如“echo at24c08 0x50 > /sys/class/i2c-adapter/i2c-0/new_device”:
内核会自动创建一个使用适配器0的设备名为“at24c08”,设备地址为0x50的I2C设备,相当于方法二中构建i2c_board_info结构体,然后直接调用i2c_new_device()函数。
在用户空间执行例如“echo 0x50 > /sys/class/i2c-adapter/i2c-0/delete_device”:
内核会调用i2c_unregister_device()函数删除这个设备地址为0x50的I2C设备。
(具体实现放在后面的5.4 方法四)
讲解具体实现I2C设备驱动之前先讲解一下硬件信息。由于使用的JZ2440开发板上没有接I2C设备,所以通过杜邦线外接一个DS3231时钟芯片。
DS3231是低成本、高精度I2C实时时钟RTC,RTC保存秒、分、时、星期、日期、月和年信息。少于31天的月份,将自动调整月末的日期,包括闰年的修正。时钟的工作格式可以是24小时或带/AM/PM指示的12小时格式。提供两个可设置的日历闹钟和一个可设置的方波输出。地址与数据通过I2C双向总线串行传输。详细芯片资料参考:https://html.alldatasheet.com/html-pdf/112132/DALLAS/DS3231/2425/11/DS3231.html
通过芯片手册可得以下信息:
1. 通过读写00h~06h这7个寄存器地址就能读取与设置时钟和日历了。
2. 从设备地址为7位ds3231地址1101000。
内核:linux-4.12
编译器:arm-linux-gcc-4.4.3
环境:ubuntu9.10
使用该方法编写I2C设备驱动特点:
1. 需要修改内核源码
2. 需要指定使用哪个适配器(S3C2440只有一个)
3. 手动构建i2c_board_info(指定设备名字,指定设备地址)
4. 内核帮我们调用i2c_new_device()函数
5. 不检测设备地址是否真实存在
下面开始修改内核源码并测试。
在mach-smdk2440.c中添加构建单板I2C设备信息数组,并在smdk2440_machine_init()函数中注册该单板I2C设备信息,完整修改代码如下:
/*构建单板I2C设备信息*/
static struct i2c_board_info smdk2440_i2c_devs[] __initdata = {
{
I2C_BOARD_INFO("ds3231", 0x68),
},
};
static void __init smdk2440_machine_init(void)
{
s3c24xx_fb_set_platdata(&smdk2440_fb_info);
s3c_i2c0_set_platdata(NULL);
/*
注册单板I2C设备信息
参数1:使用适配器0
参数2:i2c_board_info结构体数组
参数3:i2c_board_info结构体数组成员个数
*/
i2c_register_board_info(0, smdk2440_i2c_devs, ARRAY_SIZE(smdk2440_i2c_devs));
platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));
smdk_machine_init();
}
重新编译内核。下载启动。
通过上面的修改,按照3.3.4.1 实例化I2C设备方法一的分析,当内核启动后将会调用到i2c_new_device()函数注册i2c_board_info结构体(注册了i2c_client),所以接下来我们只需编写注册i2c_driver部分即可,完整代码ds3231.c如下:
#include
#include
#include
#include
#include
#include
#include
#include
#define BCD2BIN(val) (((val) & 0x0f) + ((val)>>4)*10)
#define BIN2BCD(val) ((((val)/10)<<4) + (val)%10)
#define DS3231_REG_SEC 0x00 //秒
#define DS3231_REG_MIN 0x01 //分
#define DS3231_REG_HOURS 0x02 //时
#define DS3231_REG_DAY 0x03 //星期
#define DS3231_REG_DATE 0x04 //日
#define DS3231_REG_MOUNTH 0x05 //月
#define DS3231_REG_YEAR 0x06 //年
static struct i2c_driver ds3231_driver;
static struct i2c_client *ds3231_client;
int major; //主设备号
static struct cdev ds3231_cdev;
static struct class *ds3231_class;
static ssize_t ds3231_read(struct file *file, char __user *buf, size_t size, loff_t * offset)
{
unsigned char time[7];
int reg = DS3231_REG_YEAR;
int i=sizeof(time);
if (size != sizeof(time))
return -EINVAL;
/* 实际调用了i2c_smbus_xfer函数发送数据
* 循环读取时钟与日历寄存器数据,读出来的数据是BCD码,
* 先转换成16进制并拷贝给应用程序
*/
for (reg = DS3231_REG_YEAR; reg >= DS3231_REG_SEC; reg--){
int tmp;
if ((tmp = i2c_smbus_read_byte_data(ds3231_client, reg)) < 0) {
dev_warn(&ds3231_client->dev,
"can't read from ds3231 chip\n");
return -EIO;
}
time[--i] = BCD2BIN(tmp) & 0xff;
}
return copy_to_user(buf, &time, sizeof(time));
}
static ssize_t ds3231_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
unsigned char time[7];
int reg;
int i=0;
if (size != sizeof(time))
return -EINVAL;
/* 拷贝要写入的7个16进制时钟与日历值 */
if(copy_from_user(time, buf, sizeof(time)))
return -EINVAL;
/* 实际调用了i2c_smbus_xfer函数发送数据
* 循环写入转换成BCD码的时钟与日历值
*/
for (reg = DS3231_REG_SEC; reg <= DS3231_REG_YEAR; reg++) {
if (i2c_smbus_write_byte_data(ds3231_client, reg, BIN2BCD(time[i]))< 0) {
dev_warn(&ds3231_client->dev,
"can't write to ds3231 chip\n");
return -EINVAL;
}
i++;
}
return sizeof(time);
}
static struct file_operations ds3231_fops = {
.owner = THIS_MODULE,
.write = ds3231_write,
.read = ds3231_read,
};
static int ds3231_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
int err;
dev_t devid;
ds3231_client = client;
printk("ds3231 probe !\n");
/* 创建字符设备 */
devid = MKDEV(major, 0); //从主设备号major,次设备号0得到dev_t类型
if (major)
{
err=register_chrdev_region(devid, 1, "ds3231"); //注册字符设备
}
else
{
err=alloc_chrdev_region(&devid, 0, 1, "ds3231"); //注册字符设备
major = MAJOR(devid); //从dev_t类型得到主设备
}
if(err < 0)
return err;
cdev_init(&ds3231_cdev, &ds3231_fops);
cdev_add(&ds3231_cdev, devid, 1);
ds3231_class = class_create(THIS_MODULE, "ds3231");
device_create(ds3231_class, NULL, MKDEV(major, 0), NULL, "ds3231"); /* /dev/ds3231 */
return err;
}
static int ds3231_remove(struct i2c_client *client)
{
printk("ds3231_remove !\n");
device_destroy(ds3231_class,MKDEV(major, 0));
class_destroy(ds3231_class);
cdev_del(&ds3231_cdev);
unregister_chrdev_region(MKDEV(major, 0), 1);
return 0;
}
static const struct i2c_device_id ds3231_id_table[] = {
{ "ds3231", 0 }, //名字“ds3231”与i2c_board_info的.type一样
{}
};
static struct i2c_driver ds3231_driver = {
.driver = {
.name = "ds3231_driver",
.owner = THIS_MODULE,
},
.probe = ds3231_probe,
.remove = ds3231_remove,
.id_table = ds3231_id_table,
};
static int ds3231_init(void)
{
return i2c_add_driver(&ds3231_driver);
}
static void ds3231_exit(void)
{
i2c_del_driver(&ds3231_driver);
}
module_init(ds3231_init);
module_exit(ds3231_exit);
MODULE_AUTHOR("LVZHENHAI");
MODULE_LICENSE("GPL");
当装载该驱动时,由总线设备驱动模型会调用到该驱动的probe函数,我们在该函数里面创建字符设备,再编写一个测试程序ds3231_test.c,完整代码如下:
#include
#include
#include
#include
#include
#include
/* ./ds3231_test r //读时钟与日历格式
* ./ds3231_test w Year Mounth Date Day Hour Min Sec //设置时钟与日历格式
*/
void print_usage(char *file)
{
printf("%s r\n", file);
printf("%s w Year Mounth Date Day Hour Min Sec\n", file);
}
int main(int argc, char **argv)
{
int fd;
unsigned char buf[7];
int i;
if ((argc != 2) && (argc != 9))
{
print_usage(argv[0]);
return -1;
}
fd = open("/dev/ds3231", O_RDWR);
if (fd < 0)
{
printf("can't open /dev/ds3231\n");
return -1;
}
if (strcmp(argv[1], "r") == 0) //读取时钟与日历
{
read(fd, buf, sizeof(buf));
printf("Now Time= Year:%02d, Mounth:%02d, Date:%02d, Day:%d, Hour:%02d, Min:%02d, Sec:%02d\n", \
buf[6], buf[5],buf[4], buf[3], buf[2],buf[1],buf[0]);
}
else if (strcmp(argv[1], "w") == 0) //写时钟与日历
{
for(i=0;i<7;i++)
{
buf[6-i] = strtoul(argv[i+2], NULL, 0); //将字符转换成数值
}
printf("Set Time Year:%02d, Mounth:%02d, Date:%02d, Day:%d, Hour:%02d, Min:%02d, Sec:%02d\n", \
buf[6], buf[5],buf[4], buf[3], buf[2],buf[1],buf[0]);
write(fd, buf, sizeof(buf));
}
else
{
print_usage(argv[0]);
return -1;
}
return 0;
}
Makefile代码如下:
KERN_DIR = /work/tools/linux-4.12
all:
make -C $(KERN_DIR) M=`pwd` modules
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
obj-m += ds3231.o
1. 启动修改好的内核。
2. 拷贝以上3个文件到网络文件系统编译并安装驱动,进入文件目录,执行如下命令:
make
arm-linux-gcc -o ds3231_test ds3231_test.c
ls得到如下:
安装驱动如下:
可以看到调用了ds3231.c驱动的probe函数,使用应用程序测试,执行如下命令:
执行如下命令:
./ds3231_test w 18 12 31 1 23 59 55 (设置当前时间为18年12月31日星期1 23点59分55秒)
./ds3231_test r (读取当前时间)
使用该方法编写I2C设备驱动特点:
1. 需要指定使用哪个适配器(S3C2440只有一个)
2. 手动构建i2c_board_info(指定设备名字,指定设备地址)
3. 编写驱动调用i2c_new_device()函数(不检测设备地址是否真实存在)或者调用i2c_new_probed_device()函数(检测设备地址是否真实存在)
按照3.3.4.2 实例化I2C设备方法二的分析编写I2C设备驱动,在ds3231_dev.c里注册i2c_client,在ds3231_drv.c(与方法一的ds3231.c相同,不贴代码了)里注册i2c_driver,ds3231_dev.c的编写有两种,主要不同在使用i2c_new_device()函数(不检测设备地址是否真实存在)或者调用i2c_new_probed_device()函数。
使用i2c_new_device()函数的ds3231_dev.c代码如下:
#include
#include
#include
#include
#include
#include
#include
static struct i2c_board_info ds3231_info = {
I2C_BOARD_INFO("ds3231", 0x68),
};
static struct i2c_client *ds3231_client;
static int ds3231_dev_init(void)
{
struct i2c_adapter *i2c_adap;
i2c_adap = i2c_get_adapter(0); /*因为硬件挂接在I2C总线0上,所以参数是0*/
/*在该适配器下创建了一个新设备,设备信息在i2c_board_info结构体中,
以后就可以使用这个i2c_adapter 的操作函数对该设备发出I2C的信号了*/
ds3231_client = i2c_new_device(i2c_adap, &ds3231_info);
i2c_put_adapter(i2c_adap); /*使用完要释放*/
return 0;
}
static void ds3231_dev_exit(void)
{
i2c_unregister_device(ds3231_client); /*卸载注册的设备*/
}
module_init(ds3231_dev_init);
module_exit(ds3231_dev_exit);
MODULE_AUTHOR("LVZHENHAI");
MODULE_LICENSE("GPL");
使用i2c_new_probed_device()函数的ds3231_dev.c代码如下:
#include
#include
#include
#include
#include
#include
#include
static struct i2c_client *ds3231_client;
/*short addr_list[]中可以存放多个设备地址,
在 i2c_new_probed_device函数中依次进行匹配,
I2C_CLIENT_END 表示后面没有参数了*/
static const unsigned short addr_list[] = { 0x60, 0x68, I2C_CLIENT_END };
static int ds3231_dev_init(void)
{
struct i2c_adapter *i2c_adap;
struct i2c_board_info ds3231_info;
memset(&ds3231_info, 0, sizeof(struct i2c_board_info));
strlcpy(ds3231_info.type, "ds3231", I2C_NAME_SIZE);
i2c_adap = i2c_get_adapter(0);
ds3231_client = i2c_new_probed_device(i2c_adap, &ds3231_info, addr_list, NULL);
i2c_put_adapter(i2c_adap);
if (ds3231_client)
return 0;
else
return -ENODEV;
}
static void ds3231_dev_exit(void)
{
i2c_unregister_device(ds3231_client);
}
module_init(ds3231_dev_init);
module_exit(ds3231_dev_exit);
MODULE_AUTHOR("LVZHENHAI");
MODULE_LICENSE("GPL");
测试程序ds3231_test.c与方法一的一样,ds3231_drv.c与方法一的ds3231.c相同。Makefile代码如下:
KERN_DIR = /work/tools/linux-4.12
all:
make -C $(KERN_DIR) M=`pwd` modules
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
obj-m += ds3231_dev.o
obj-m += ds3231_drv.o
注意如果使用过方法一修改了mach-smdk2440.c文件要修改回去,否则装载ds3231_dev.c时提示不能重复装载相同设备地址的驱动。
拷贝以上4个文件到网络文件系统编译并安装驱动,进入文件目录,执行如下命令:
make
arm-linux-gcc -o ds3231_test ds3231_test.c
ls得到如下:
安装驱动如下:
可以看到调用了ds3231_drv.c驱动的probe函数,后面的使用应用程序测试与方法一一样。
使用该方法编写I2C设备驱动特点:
1. 修改源码(由于该版本内核不支持i2c-s3c2410.c这个适配器自动检测,修改让其支持)
2. 不需要指定使用哪个适配器
3. 不需要构建i2c_board_info,但通过赋值给i2c_board_info->type成员传递设备名字,构建一个address_list传递地址
4. 内核帮我们调用i2c_new_device()函数
5. 检测设备地址是否真实存在
按照3.3.4.2 实例化I2C设备方法二的分析,修改适配器文件driver/i2c/busses/i2c-s3c2410.c文件,由于该版本内核不支持这个适配器自动检测I2C_CLASS_DEPRECATED,修改让其支持改为I2C_CLASS_HWMON,如下:
然后重新编译内核。
按照3.3.4.2 实例化I2C设备方法二的分析编写I2C设备驱动,我们只需注册i2c_driver,完整代码ds3231.c如下:
#include
#include
#include
#include
#include
#include
#include
#include
#define BCD2BIN(val) (((val) & 0x0f) + ((val)>>4)*10)
#define BIN2BCD(val) ((((val)/10)<<4) + (val)%10)
#define DS3231_REG_SEC 0x00 //秒
#define DS3231_REG_MIN 0x01 //分
#define DS3231_REG_HOURS 0x02 //时
#define DS3231_REG_DAY 0x03 //星期
#define DS3231_REG_DATE 0x04 //日
#define DS3231_REG_MOUNTH 0x05 //月
#define DS3231_REG_YEAR 0x06 //年
static struct i2c_driver ds3231_driver;
static struct i2c_client *ds3231_client;
int major; //主设备号
static struct cdev ds3231_cdev;
static struct class *ds3231_class;
static ssize_t ds3231_read(struct file *file, char __user *buf, size_t size, loff_t * offset)
{
unsigned char time[7];
int reg = DS3231_REG_YEAR;
int i=sizeof(time);
if (size != sizeof(time))
return -EINVAL;
/* 实际调用了i2c_smbus_xfer函数发送数据
* 循环读取时钟与日历寄存器数据,读出来的数据是BCD码,
* 先转换成16进制并拷贝给应用程序
*/
for (reg = DS3231_REG_YEAR; reg >= DS3231_REG_SEC; reg--){
int tmp;
if ((tmp = i2c_smbus_read_byte_data(ds3231_client, reg)) < 0) {
dev_warn(&ds3231_client->dev,
"can't read from ds3231 chip\n");
return -EIO;
}
time[--i] = BCD2BIN(tmp) & 0xff;
}
return copy_to_user(buf, &time, sizeof(time));
}
static ssize_t ds3231_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
unsigned char time[7];
int reg;
int i=0;
if (size != sizeof(time))
return -EINVAL;
/* 拷贝要写入的7个16进制时钟与日历值 */
if(copy_from_user(time, buf, sizeof(time)))
return -EINVAL;
/* 实际调用了i2c_smbus_xfer函数发送数据
* 循环写入转换成BCD码的时钟与日历值
*/
for (reg = DS3231_REG_SEC; reg <= DS3231_REG_YEAR; reg++) {
if (i2c_smbus_write_byte_data(ds3231_client, reg, BIN2BCD(time[i]))< 0) {
dev_warn(&ds3231_client->dev,
"can't write to ds3231 chip\n");
return -EINVAL;
}
i++;
}
return sizeof(time);
}
static struct file_operations ds3231_fops = {
.owner = THIS_MODULE,
.write = ds3231_write,
.read = ds3231_read,
};
static int ds3231_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
int err;
dev_t devid;
ds3231_client = client;
printk("ds3231 probe !\n");
/* 创建字符设备 */
devid = MKDEV(major, 0); //从主设备号major,次设备号0得到dev_t类型
if (major)
{
err=register_chrdev_region(devid, 1, "ds3231"); //注册字符设备
}
else
{
err=alloc_chrdev_region(&devid, 0, 1, "ds3231"); //注册字符设备
major = MAJOR(devid); //从dev_t类型得到主设备
}
if(err < 0)
return err;
cdev_init(&ds3231_cdev, &ds3231_fops);
cdev_add(&ds3231_cdev, devid, 1);
ds3231_class = class_create(THIS_MODULE, "ds3231");
device_create(ds3231_class, NULL, MKDEV(major, 0), NULL, "ds3231"); /* /dev/ds3231 */
return err;
}
static int ds3231_remove(struct i2c_client *client)
{
printk("ds3231_remove !\n");
device_destroy(ds3231_class,MKDEV(major, 0));
class_destroy(ds3231_class);
cdev_del(&ds3231_cdev);
unregister_chrdev_region(MKDEV(major, 0), 1);
return 0;
}
static const struct i2c_device_id ds3231_id_table[] = {
{ "ds3231", 0 }, //设备名字
{}
};
static int ds3231_detect(struct i2c_client *client,
struct i2c_board_info *info)
{
/* 能运行到这里, 表示该addr的设备是存在的
* 但是有些设备单凭地址无法分辨(A芯片的地址是0x68, B芯片的地址也是0x68)
* 还需要进一步读写I2C设备来分辨是哪款芯片
* detect就是用来进一步分辨这个芯片是哪一款,并且设置info->type
*/
printk("ds3231_detect : addr = 0x%x\n", client->addr);
/* 在这里可以进一步判断是哪一款芯片 */
strlcpy(info->type, "ds3231", I2C_NAME_SIZE); //与i2c_device_id里的设备名字一样
return 0;
}
static const unsigned short addr_list[] = { 0x60, 0x68, I2C_CLIENT_END };
static struct i2c_driver ds3231_driver = {
.class = I2C_CLASS_HWMON, /* 表示该驱动是什么类型,匹配支持相同的类型适配器 */
.driver = {
.name = "ds3231_driver",
.owner = THIS_MODULE,
},
.probe = ds3231_probe,
.remove = ds3231_remove,
.id_table = ds3231_id_table,
.detect = ds3231_detect, /* 用这个函数来检测设备确实存在 */
.address_list = addr_list, /* 这些设备的地址 */
};
static int ds3231_init(void)
{
return i2c_add_driver(&ds3231_driver);
}
static void ds3231_exit(void)
{
i2c_del_driver(&ds3231_driver);
}
module_init(ds3231_init);
module_exit(ds3231_exit);
MODULE_AUTHOR("LVZHENHAI");
MODULE_LICENSE("GPL");
测试程序ds3231_test.c与方法一一样,Makefile也一样。
1. 启动修改好的内核。
2. 拷贝以上3个文件到网络文件系统编译并安装驱动,进入文件目录,执行如下命令:
make
arm-linux-gcc -o ds3231_test ds3231_test.c
ls得到如下:
安装驱动如下:
可以看到先调用了ds3231_drv.c驱动的detect函数,然后执行probe函数,后面的使用应用程序测试与方法一一样。
使用该方法编写I2C设备驱动特点:
1. 直接在用户空间创建或删除I2C设备(通过命令指定设备名字,指定设备地址)
2. 不检测设备地址是否真实存在(注册i2c_driver时去匹配上面的设备名字,匹配成功就会执行驱动的probe函数)
启动内核在开发板上执行如下命令:
ls /sys/class/i2c-adapter/
可以看到只有一个i2c-0,这就是2440唯一一个适配器。下面直接通过命令创建设备或删除设备。
执行如下命令:
echo ds3231 0x68 > /sys/class/i2c-adapter/i2c-0/new_device
内核会自动创建一个使用适配器0的设备名为“ds3231”,设备地址为0x68的I2C设备,相当于方法二中构建i2c_board_info结构体,然后内核帮我们调用i2c_new_device()函数。
执行如下命令:
echo 0x68 > /sys/class/i2c-adapter/i2c-0/delete_device
内核会调用i2c_unregister_device()函数删除这个设备地址为0x68的I2C设备。
使用5.4.1 创建设备后,再使用方法一的设备驱动程序ds3231.c、测试程序ds3231_test.c与Makefile。测试方法与效果也一样。
还有一种方法是使用设备树方式,这里就先不介绍了。