Linux I2C 驱动 24C256 E2PROM_i2c_client结构体-程序员宅基地

技术标签: Linux  linux  i.MX6  i2c  eeprom  24c256  

I2C 总线驱动

之前类似于input输入设备都是挂载在platform总线上,platform 是虚拟出来的一条总线,目的是为了实现总线、设备、驱动框架。

对于I2C 而言,不需要虚拟出一条总线,直接使用I2C总线即可。

I2C 总线驱动重点是I2C 适配器(也就是SOC 的I2C 接口控制器)驱动,这里要用到两个重要的数据结构:i2c_adapteri2c_algorithm,Linux 内核将SOC 的I2C 适配器(控制器)抽象成i2c_adapteri2c_adapter 结构体定义在include/linux/i2c.h 文件中,结构体内容如下:
在这里插入图片描述
i2c_algorithm 类型的指针变量algo,对于一个I2C 适配器,肯定要对外提供读写API 函数,设备驱动程序可以使用这些API 函数来完成读写操作。i2c_algorithm 就是I2C 适配器与IIC 设备进行通信的方法。

i2c_algorithm 结构体定义在include/linux/i2c.h 文件中,内容如下:
在这里插入图片描述
master_xfer 就是I2C 适配器的传输函数,可以通过此函数来完成与IIC 设备之间的通信。

smbus_xfer 就是SMBUS 总线的传输函数

综上所述,I2C 总线驱动,或者说I2C 适配器驱动的主要工作就是初始化i2c_adapter 结构体变量,然后设置i2c_algorithm 中的master_xfer 函数。完成以后通过i2c_add_numbered_adapter或i2c_add_adapter 这两个函数向系统注册设置好的i2c_adapter,这两个函数的原型如下:

int i2c_add_adapter(struct i2c_adapter *adapter)
int i2c_add_numbered_adapter(struct i2c_adapter *adap)

这两个函数的区别在于i2c_add_adapter 使用动态的总线号,而i2c_add_numbered_adapter使用静态总线号。函数参数和返回值含义如下:

adapteradap:要添加到Linux 内核中的i2c_adapter,也就是I2C 适配器。

返回值:0,成功;负值,失败。

如果要删除I2C 适配器的话使用i2c_del_adapter 函数即可,函数原型如下:

void i2c_del_adapter(struct i2c_adapter * adap)

函数参数和返回值含义如下:

adap:要删除的I2C 适配器。

返回值:无。

关于I2C 的总线(控制器或适配器)驱动就讲解到这里,一般SOC 的I2C 总线驱动都是由半导体厂商编写的,比如I.MX6U 的I2C 适配器驱动NXP 已经编写好了,这个不需要用户去编写。因此I2C 总线驱动对我们这些SOC 使用者来说是被屏蔽掉的,我们只要专注于I2C 设备驱动即可.

I2C 设备驱动

I2C 设备驱动重点关注两个数据结构:i2c_clienti2c_driver,根据总线、设备和驱动模型,I2C 总线上一小节已经讲了。还剩下设备和驱动,i2c_client 就是描述设备信息的,i2c_driver 描述驱动内容,类似于platform_driver

i2c_client 结构体

i2c_client 结构体定义在include/linux/i2c.h 文件中,内容如下:
在这里插入图片描述
一个设备对应一个i2c_client,每检测到一个I2C 设备就会给这个I2C 设备分配一个i2c_client

i2c_driver 结构体

i2c_driver 类似platform_driver,是我们编写I2C 设备驱动重点要处理的内容,i2c_driver 结构体定义在include/linux/i2c.h 文件中,内容如下:
在这里插入图片描述
当I2C 设备和驱动匹配成功以后probe 函数就会执行,和platform 驱动一样。

device_driver 驱动结构体,如果使用设备树的话,需要设置device_driverof_match_table 成员变量,也就是驱动的兼容(compatible)属性。

id_table 是传统的、未使用设备树的设备匹配ID 表。

对于我们I2C 设备驱动编写人来说,重点工作就是构建i2c_driver,构建完成以后需要向Linux 内核注册这个i2c_driver。i2c_driver 注册函数为int i2c_register_driver,此函数原型如下:

int i2c_register_driver(struct module *owner, struct i2c_driver *driver)

函数参数和返回值含义如下:

owner:一般为THIS_MODULE。
driver:要注册的i2c_driver。
返回值:0,成功;负值,失败。

另外i2c_add_driver 也常常用于注册i2c_driver,i2c_add_driver 是一个宏,定义如下:

#define i2c_add_driver(driver) \
	i2c_register_driver(THIS_MODULE, driver)

i2c_add_driver 就是对i2c_register_driver 做了一个简单的封装,只有一个参数,就是要注册的i2c_driver。

注销I2C 设备驱动的时候需要将前面注册的i2c_driver 从Linux 内核中注销掉,需要用到i2c_del_driver 函数,此函数原型如下:

void i2c_del_driver(struct i2c_driver *driver)

函数参数和返回值含义如下:

driver:要注销的i2c_driver。
返回值:无。

i2c_driver 的注册示例代码如下:

1 /* i2c 驱动的probe 函数 */
2 static int xxx_probe(struct i2c_client *client, const struct i2c_device_id *id)
3 {
    
4 /* 函数具体程序 */
5 return 0;
6 }
7
8 /* i2c 驱动的remove 函数 */
9 static int ap3216c_remove(struct i2c_client *client)
10 {
    
11 /* 函数具体程序 */
12 return 0;
13 }
14
15 /* 传统匹配方式ID 列表 */
16 static const struct i2c_device_id xxx_id[] = {
    
17 {
    "xxx", 0},
18 {
    }
19 };
20
21 /* 设备树匹配列表 */
22 static const struct of_device_id xxx_of_match[] = {
    
23 {
     .compatible = "xxx" },
24 {
     /* Sentinel */ }
25 };
26
27 /* i2c 驱动结构体 */
28 static struct i2c_driver xxx_driver = {
    
29 .probe = xxx_probe,
30 .remove = xxx_remove,
31 .driver = {
    
32 .owner = THIS_MODULE,
33 .name = "xxx",
34 .of_match_table = xxx_of_match,
35 },
36 .id_table = xxx_id,
37 };
38
39 /* 驱动入口函数 */
40 static int __init xxx_init(void)
41 {
    
42 int ret = 0;
43
44 ret = i2c_add_driver(&xxx_driver);
45 return ret;
46 }
47
48 /* 驱动出口函数 */
49 static void __exit xxx_exit(void)
50 {
    
51 i2c_del_driver(&xxx_driver);
52 }
53
54 module_init(xxx_init);
55 module_exit(xxx_exit);

第16~19 行,i2c_device_id,无设备树的时候匹配ID 表。
第22~25 行,of_device_id,设备树所使用的匹配表。
第28~37 行,i2c_driver,当I2C 设备和I2C 驱动匹配成功以后probe 函数就会执行,这些和platform 驱动一样,probe 函数里面基本就是标准的字符设备驱动那一套了。

I2C 设备和驱动匹配过程

I2C 设备和驱动的匹配过程是由I2C 核心来完成的,drivers/i2c/i2c-core.c 就是I2C 的核心部分,I2C 核心提供了一些与具体硬件无关的API 函数,比如前面讲过的:

1、i2c_adapter 注册/注销函数

int i2c_add_adapter(struct i2c_adapter *adapter)
int i2c_add_numbered_adapter(struct i2c_adapter *adap)
void i2c_del_adapter(struct i2c_adapter * adap)

2、i2c_driver 注册/注销函数

int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
int i2c_add_driver (struct i2c_driver *driver)
void i2c_del_driver(struct i2c_driver *driver)

设备和驱动的匹配过程也是由I2C 总线完成的,I2C 总线的数据结构为i2c_bus_type,定义在drivers/i2c/i2c-core.c 文件,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,
};

.match 就是I2C 总线的设备和驱动匹配函数,在这里就是i2c_device_match 这个函数,此函数内容如下:

static int i2c_device_match(struct device *dev, struct device_driver *drv)
 {
    
 struct i2c_client *client = i2c_verify_client(dev);
 struct i2c_driver *driver;

 if (!client)
 return 0;

 /* Attempt an OF style match */
 if (of_driver_match_device(dev, drv))
 return 1;

 /* Then ACPI style match */
 if (acpi_driver_match_device(dev, drv))
 return 1;

 driver = to_i2c_driver(drv);
 /* match on an id table if there is one */
 if (driver->id_table)
 return i2c_match_id(driver->id_table, client) != NULL;

 return 0;
 }

of_driver_match_device 函数用于完成设备树设备和驱动匹配。比较I2C 设备节点的compatible 属性和of_device_id 中的compatible 属性是否相等,如果相当的话就表示I2C设备和驱动匹配。

acpi_driver_match_device 函数用于ACPI 形式的匹配。

i2c_match_id 函数用于传统的、无设备树的I2C 设备和驱动匹配过程。比较I2C设备名字和i2c_device_idname 字段是否相等,相等的话就说明I2C 设备和驱动匹配。

I.MX6q 的I2C 适配器驱动分析

I2C 设备驱动是需要用户根据不同的I2C 设备去编写,而I2C 适配器驱动一般都是SOC 厂商去编写的,比如NXP 就编写好了I.MX6q 的I2C 适配器驱动。在imx6qdl.dtsi 文件中找到I.MX6q 的I2C1 控制器节点,节点内容如下所示:

i2c1: i2c@021a0000 {
    
		#address-cells = <1>;
		#size-cells = <0>;
		compatible = "fsl,imx6q-i2c", "fsl,imx21-i2c";
		reg = <0x021a0000 0x4000>;
		interrupts = <0 36 IRQ_TYPE_LEVEL_HIGH>;
		clocks = <&clks IMX6QDL_CLK_I2C1>;
		status = "disabled";
	};

重点关注i2c1 节点的compatible 属性值,因为通过compatible 属性值可以在Linux 源码里面找到对应的驱动文件。

这里i2c1 节点的compatible 属性值有两个:“fsl,imx6q-i2c”, “fsl,imx21-i2c”,在Linux 源码中搜索这两个字符串即可找到对应的驱动文件。

grep -rn "fsl,imx6q-i2c" *

grep -rn "fsl,imx21-i2c" *

或者

grep -rn "fsl,imx21-i2c"&"fsl,imx6q-i2c" *
 
* : 表示当前目录所有文件,也可以是某个文件名

-r 是递归查找

-n 是显示行号

-R 查找所有文件包含子目录

-i 忽略大小写

在这里插入图片描述
在这里插入图片描述

从搜索结果中可以看出

I.MX6q 的I2C 适配器驱动驱动文件为drivers/i2c/busses/i2c-imx.c,在此文件中有如下内容:
在这里插入图片描述
在这里插入图片描述

可以看出,I.MX6的I2C 适配器驱动是个标准的platform 驱动,由此可以看出,虽然I2C 总线为别的设备提供了一种总线驱动框架,但是I2C 适配器却是platform驱动

“fsl,imx21-i2c”属性值,设备树中 i2c1 节点的compatible 属性值就是与此匹配上的。因此i2c-imx.c 文件就是I.MX6 的I2C 适配器驱动文件。

当设备和驱动匹配成功以后i2c_imx_probe 函数就会执行,i2c_imx_probe 函数就会完成I2C 适配器初始化工作。

i2c_imx_probe 函数内容如下所示(有省略):

示例代码61.2.3 i2c_imx_probe 函数代码段
971 static int i2c_imx_probe(struct platform_device *pdev)
972 {
    
973 const struct of_device_id *of_id =
974 of_match_device(i2c_imx_dt_ids, &pdev->dev);
975 struct imx_i2c_struct *i2c_imx;
976 struct resource *res;
977 struct imxi2c_platform_data *pdata =
dev_get_platdata(&pdev->dev);
978 void __iomem *base;
979 int irq, ret;
980 dma_addr_t phy_addr;
981
982 dev_dbg(&pdev->dev, "<%s>\n", __func__);
983
984 irq = platform_get_irq(pdev, 0);
......
990 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
991 base = devm_ioremap_resource(&pdev->dev, res);
992 if (IS_ERR(base))
993 return PTR_ERR(base);
994
995 phy_addr = (dma_addr_t)res->start;
996 i2c_imx = devm_kzalloc(&pdev->dev, sizeof(*i2c_imx),
GFP_KERNEL);
997 if (!i2c_imx)
998 return -ENOMEM;
999
1000 if (of_id)
1001 i2c_imx->hwdata = of_id->data;
1002 else
1003 i2c_imx->hwdata = (struct imx_i2c_hwdata *)
1004 platform_get_device_id(pdev)->driver_data;
1005
1006 /* Setup i2c_imx driver structure */
1007 strlcpy(i2c_imx->adapter.name, pdev->name,
sizeof(i2c_imx->adapter.name));
1008 i2c_imx->adapter.owner = THIS_MODULE;
1009 i2c_imx->adapter.algo = &i2c_imx_algo;
1010 i2c_imx->adapter.dev.parent = &pdev->dev;
1011 i2c_imx->adapter.nr = pdev->id;
1012 i2c_imx->adapter.dev.of_node = pdev->dev.of_node;
1013 i2c_imx->base = base;
1014
1015 /* Get I2C clock */
1016 i2c_imx->clk = devm_clk_get(&pdev->dev, NULL);
......
1022 ret = clk_prepare_enable(i2c_imx->clk);
......
1027 /* Request IRQ */
1028 ret = devm_request_irq(&pdev->dev, irq, i2c_imx_isr,
1029 IRQF_NO_SUSPEND, pdev->name, i2c_imx);
......
1035 /* Init queue */
1036 init_waitqueue_head(&i2c_imx->queue);
1037
1038 /* Set up adapter data */
1039 i2c_set_adapdata(&i2c_imx->adapter, i2c_imx);
1040
1041 /* Set up clock divider */
1042 i2c_imx->bitrate = IMX_I2C_BIT_RATE;
1043 ret = of_property_read_u32(pdev->dev.of_node,
1044 "clock-frequency", &i2c_imx->bitrate);
1045 if (ret < 0 && pdata && pdata->bitrate)
1046 i2c_imx->bitrate = pdata->bitrate;
1047
1048 /* Set up chip registers to defaults */
1049 imx_i2c_write_reg(i2c_imx->hwdata->i2cr_ien_opcode ^ I2CR_IEN,
1050 i2c_imx, IMX_I2C_I2CR);
1051 imx_i2c_write_reg(i2c_imx->hwdata->i2sr_clr_opcode, i2c_imx,
IMX_I2C_I2SR);
1052
1053 /* Add I2C adapter */
1054 ret = i2c_add_numbered_adapter(&i2c_imx->adapter);
1055 if (ret < 0) {
    
1056 dev_err(&pdev->dev, "registration failed\n");
1057 goto clk_disable;
1058 }
1059
1060 /* Set up platform driver data */
1061 platform_set_drvdata(pdev, i2c_imx);
1062 clk_disable_unprepare(i2c_imx->clk);
......
1070 /* Init DMA config if supported */
1071 i2c_imx_dma_request(i2c_imx, phy_addr);
1072
1073 return 0; /* Return OK */
1074
1075 clk_disable:
1076 clk_disable_unprepare(i2c_imx->clk);
1077 return ret;
1078 }

第984 行,调用platform_get_irq 函数获取中断号

第990~991 行,调用platform_get_resource 函数从设备树中获取I2C1 控制器寄存器物理基地址,也就是0X021A0000。获取到寄存器基地址以后使用devm_ioremap_resource 函数对其进行内存映射,得到可以在Linux 内核中使用的虚拟地址。

第996 行,NXP 使用imx_i2c_struct 结构体来表示I.MX 系列SOC 的I2C 控制器,这里使用devm_kzalloc 函数来申请内存。

第1008~1013 行,imx_i2c_struct 结构体要有个叫做adapter 的成员变量,adapter 就是i2c_adapter,这里初始化i2c_adapter。第1009 行设置i2c_adapter 的algo 成员变量为i2c_imx_algo,也就是设置i2c_algorithm。

第1028~1029 行,注册I2C 控制器中断,中断服务函数为i2c_imx_isr

第1042~1044 行,设置I2C 频率默认为IMX_I2C_BIT_RATE=100KHz,如果设备树节点设置了“clock-frequency”属性的话I2C 频率就使用clock-frequency 属性值。

第1049~1051 行,设置I2C1 控制的I2CRI2SR 寄存器。

第1054 行,调用i2c_add_numbered_adapter 函数向Linux 内核注册i2c_adapter。

第1071 行,申请DMA,看来I.MX 的I2C 适配器驱动采用了DMA 方式。
i2c_imx_probe 函数主要的工作就是一下两点:
①、初始化i2c_adapter,设置i2c_algorithmi2c_imx_algo,最后向Linux 内核注册i2c_adapter。
②、初始化I2C1 控制器的相关寄存器。i2c_imx_algo 包含I2C1 适配器与I2C 设备的通信函数master_xfer,i2c_imx_algo 结构体定义如下:

static struct i2c_algorithm i2c_imx_algo = {
    
.master_xfer = i2c_imx_xfer,
.functionality = i2c_imx_func,
};

我们先来看一下. functionality,functionality用于返回此I2C适配器支持什么样的通信协议,在这里functionality 就是i2c_imx_func 函数,i2c_imx_func 函数内容如下:

static u32 i2c_imx_func(struct i2c_adapter *adapter)
{
    
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL
| I2C_FUNC_SMBUS_READ_BLOCK_DATA;
}

重点来看一下i2c_imx_xfer 函数,因为最终就是通过此函数来完成与I2C 设备通信的,此
函数内容如下(有省略):

888 static int i2c_imx_xfer(struct i2c_adapter *adapter,
889 struct i2c_msg *msgs, int num)
890 {
    
891 unsigned int i, temp;
892 int result;
893 bool is_lastmsg = false;
894 struct imx_i2c_struct *i2c_imx = i2c_get_adapdata(adapter);
895
896 dev_dbg(&i2c_imx->adapter.dev, "<%s>\n", __func__);
897
898 /* Start I2C transfer */
899 result = i2c_imx_start(i2c_imx);
900 if (result)
901 goto fail0;
902
903 /* read/write data */
904 for (i = 0; i < num; i++) {
    
905 if (i == num - 1)
906 is_lastmsg = true;
907
908 if (i) {
    
909 dev_dbg(&i2c_imx->adapter.dev,
910 "<%s> repeated start\n", __func__);
911 temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR);
912 temp |= I2CR_RSTA;
913 imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR);
914 result = i2c_imx_bus_busy(i2c_imx, 1);
915 if (result)
916 goto fail0;
917 }
918 dev_dbg(&i2c_imx->adapter.dev,
919 "<%s> transfer message: %d\n", __func__, i);
920 /* write/read data */
......
938 if (msgs[i].flags & I2C_M_RD)
939 result = i2c_imx_read(i2c_imx, &msgs[i], is_lastmsg);
940 else {
    
941 if (i2c_imx->dma && msgs[i].len >= DMA_THRESHOLD)
942 result = i2c_imx_dma_write(i2c_imx, &msgs[i]);
943 else
944 result = i2c_imx_write(i2c_imx, &msgs[i]);
945 }
946 if (result)
947 goto fail0;
948 }
949
950 fail0:
951 /* Stop I2C transfer */
952 i2c_imx_stop(i2c_imx);
953
954 dev_dbg(&i2c_imx->adapter.dev, "<%s> exit with: %s: %d\n",
__func__,
955 (result < 0) ? "error" : "success msg",
956 (result < 0) ? result : num);
957 return (result < 0) ? result : num;
958 }

第899 行,调用i2c_imx_start 函数开启I2C 通信。

第939 行,如果是从I2C 设备读数据的话就调用i2c_imx_read 函数。

第941~945 行,向I2C 设备写数据,如果要用DMA 的话就使用i2c_imx_dma_write 函数来完成写数据。如果不使用DMA 的话就使用i2c_imx_write 函数完成写数据。

第952 行,I2C 通信完成以后调用i2c_imx_stop 函数停止I2C 通信。

i2c_imx_start、i2c_imx_read、i2c_imx_write 和i2c_imx_stop 这些函数就是I2C 寄存器的具
体操作函数

I2C 设备驱动编写流程

I2C 适配器驱动SOC 厂商已经替我们编写好了,我们需要做的就是编写具体的设备驱动

I2C 设备信息描述(使用设备树)

使用设备树的时候I2C 设备信息通过创建相应的节点就行了

这里我们添加的是AT24C256C型号的EEPROM器件

设备驱动内核源码中是存在的,我们通过搜索找到了驱动在源码中的位置以及设备树说明文档
在这里插入图片描述
在这里插入图片描述

说明文档位置

Documentation/devicetree/bindings/eeprom.txt

文档内容

EEPROMs (I2C)

Required properties:

  - compatible : should be "<manufacturer>,<type>"
		 If there is no specific driver for <manufacturer>, a generic
		 driver based on <type> is selected. Possible types are:
		 24c00, 24c01, 24c02, 24c04, 24c08, 24c16, 24c32, 24c64,
		 24c128, 24c256, 24c512, 24c1024, spd

  - reg : the I2C address of the EEPROM

Optional properties:

  - pagesize : the length of the pagesize for writing. Please consult the
               manual of your device, that value varies a lot. A wrong value
	       may result in data loss! If not specified, a safety value of
	       '1' is used which will be very slow.

  - read-only: this parameterless property disables writes to the eeprom

Example:

eeprom@52 {
    
	compatible = "atmel,24c32";
	reg = <0x52>;
	pagesize = <32>;
};

  • compatible : should be “,”
    If there is no specific driver for , a generic
    driver based on is selected. Possible types are:
    24c00, 24c01, 24c02, 24c04, 24c08, 24c16, 24c32, 24c64,
    24c128, 24c256, 24c512, 24c1024, spd
    对于本设计为 24c256
    在这里插入图片描述

  • reg : the I2C address of the EEPROM

对于设计中at24c256,前四位(1010)是固定的,A2, A1, A0 与硬件连接有关系,外部输入电压为高时是1,外部电压输入为低是0。

本设计中A2, A1, A0都是接地,所以我的设备地址是1010000X,也就是10100000 或10100001。

最后一位R/W是读写位,读操作时为0,写操作时为1。

对于linux IIC子系统,读写位驱动会自动添加,实际设备地址位高七位 1010000B 也就是0x50

在这里插入图片描述

  • pagesize : 用于写入的 pagesize 的长度。 请咨询您的设备手册,该值变化很大。 错误的值可能会导致数据丢失! 如果未指定,则安全值为使用“1”会很慢。
    查阅数据手册,256k的大小分为了512页,每页64-bytes
    在这里插入图片描述

I2C 设备数据收发处理流程

I2C 设备驱动首先要做的就是初始化i2c_driver 并向Linux 内核注册。当设备和驱动匹配以后i2c_driver 里面的probe 函数就会执行,probe 函数里面所做的就是字符设备驱动那一套了。

一般需要在probe 函数里面初始化I2C 设备,要初始化I2C 设备就必须能够对I2C 设备寄存器进行读写操作,这里就要用到i2c_transfer 函数了。

i2c_transfer 函数最终会调用I2C 适配器中i2c_algorithm 里面的master_xfer 函数,对于I.MX6而言就是i2c_imx_xfer 这个函数。i2c_transfer 函数原型如下:

int i2c_transfer(struct i2c_adapter *adap,
struct i2c_msg *msgs,
int num)

函数参数和返回值含义如下:
adap:所使用的I2C 适配器,i2c_client 会保存其对应的i2c_adapter。
msgs:I2C 要发送的一个或多个消息。
num:消息数量,也就是msgs 的数量。
返回值:负值,失败,其他非负值,发送的msgs 数量。

重点来看一下msgs 这个参数,这是一个i2c_msg 类型的指针参数,I2C 进行数据收发说白了就是消息的传递,Linux 内核使用i2c_msg 结构体来描述一个消息。i2c_msg 结构体定义在include/uapi/linux/i2c.h 文件中,结构体内容如下:

68 struct i2c_msg {
    
69 __u16 addr; /* 从机地址 */
70 __u16 flags; /* 标志 */
71 #define I2C_M_TEN 0x0010
72 #define I2C_M_RD 0x0001
73 #define I2C_M_STOP 0x8000
74 #define I2C_M_NOSTART 0x4000
75 #define I2C_M_REV_DIR_ADDR 0x2000
76 #define I2C_M_IGNORE_NAK 0x1000
77 #define I2C_M_NO_RD_ACK 0x0800
78 #define I2C_M_RECV_LEN 0x0400
79 __u16 len; /* 消息(本msg)长度 */
80 __u8 *buf; /* 消息数据 */
81 };

另外还有两个API函数分别用于I2C 数据的收发操作,这两个函数最终都会调用i2c_transfer。首先来看一下I2C 数据发送函数i2c_master_send,函数原型如下:

int i2c_master_send(const struct i2c_client *client,
const char *buf,
int count)

函数参数和返回值含义如下:
client:I2C 设备对应的i2c_client。
buf:要发送的数据。
count:要发送的数据字节数,要小于64KB,以为i2c_msg 的len 成员变量是一个u16(无符号16 位)类型的数据。
返回值:负值,失败,其他非负值,发送的字节数。

I2C 数据接收函数为i2c_master_recv,函数原型如下:

int i2c_master_recv(const struct i2c_client *client,
char *buf,
int count)

函数参数和返回值含义如下:
client:I2C 设备对应的i2c_client。
buf:要接收的数据。
count:要接收的数据字节数,要小于64KB,以为i2c_msg 的len 成员变量是一个u16(无符号16 位)类型的数据。
返回值:负值,失败,其他非负值,发送的字节数。

关于Linux 下I2C 设备驱动的编写流程就讲解到这里,重点就是i2c_msg 的构建和i2c_transfer 函数的调用

修改设备树

在i2c1中添加设备节点

	E2PROM:AT24C256C@50 {
    
		compatible = "atmel,24c256";		// 匹配驱动
		reg = <0x50>;						// 设置器件地址
		pagesize = <64>;
	};

pinctrl子系统一般不需要修改

		pinctrl_i2c1: i2c1grp {
    
			fsl,pins = <
				MX6QDL_PAD_CSI0_DAT8__I2C1_SDA		0x4001b8b1
				MX6QDL_PAD_CSI0_DAT9__I2C1_SCL		0x4001b8b1
			>;
		};

编译设备树,在烧录之前,查看板子的/sys/bus/i2c/devices/信息
在这里插入图片描述
烧录之后,可以看到设备树添加 成功,i2c1对应的是i2c-0,0-0050也就是我们添加的e2prom
在这里插入图片描述
进入0-0050
在这里插入图片描述

测试程序编写

通过sysfs文件系统访问I2C设备

eeprom的设备驱动在/sys/bus/i2c/devices/2-0050/目录下把eeprom设备映射为一个二进制节点,文件名为eeprom。对这个eeprom文件的读写就是对eeprom进行读写。

我们可以先用 cat 命令来看下 eeprom 的内容。

[root@FORLINX210]# cat eeprom
�����������X�����������������������������������������������

发现里面都是乱码,然后用 echo 命令把字符串“test”输入给 eeprom 文件,然后再 cat 出来。

就会发现字符串 test 已经存在 eeprom 里面了,我们知道 sysfs 文件系统断电后就没了,也无法对数据进行保存,为了验证确实把“test”字符串存储在了 eeprom,可以把系统断电重启,然后 cat eeprom,会发现 test 还是存在的,证明确实对 eeprom 进行了写入操作。

当然,因为 eeprom 已经映射为一个文件了,我们还可以通过文件 I/O 写应用程序对其进行简单的访问测试。比如以下程序对特定地址写入特定数据,然后再把写入的数据在此地址上读出来。

#include <stdio.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/types.h>
#include <unistd.h>
#include <time.h>
#include <string.h>

int main()
{
    
    int fd;
    int i, write_data_size = 0;
    // char write_data[8] = {'A',0x3A,0x3B,0x3C,0x3D,0x3E,0x3F,0x40};
    // char write_data[] = {1,2,3,4,5,6,7,8,9,0};
    char write_data[] = {
    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
                         0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
                         0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
                         0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
                        };
    char write_data1[] = {
    '1','2','3','4','5','6','7','8','9','0',
                          '2','2','3','4','5','6','7','8','9','0',
                          '3','2','3','4','5','6','7','8','9','0',
                          '4','2','3','4','5','6','7','8','9','0',
                          '5','2','3','4','5','6','7','8','9','0',
                          '6','2','3','4','5','6','7','8','9','0',
                          '7','2','3','4'
                          };

    char read_data[256] = {
    0};

    write_data_size = sizeof(write_data);

    fd= open("/sys/bus/i2c/devices/2-0050/eeprom",O_RDWR);//这里需要改成你平台的路径
    //写 0 位置
    lseek(fd, 0, SEEK_SET);
    write(fd, write_data, write_data_size);
    usleep(20000);
    // 读 0 位置
    lseek(fd, 0, SEEK_SET);
    read(fd, read_data, write_data_size);
    usleep(20000);
    for(i=0; i<write_data_size; i++){
    
        if(i%10 == 0)
            printf("\n");
        printf("%02x ", read_data[i]);
        // printf("%c\n", read_data[i]);
    }

    // printf("%s\n", &read_data);
    printf("\n");

    // 写 128 位置
    write_data_size = sizeof(write_data1);
    lseek(fd, 128, SEEK_SET);
    write(fd, write_data1, write_data_size);
    usleep(20000);

    // 读 128 位置
    lseek(fd, 128, SEEK_SET);
    read(fd, read_data, write_data_size);
    usleep(20000);
    for(i=0; i<write_data_size; i++){
    
        if(i%10 == 0)
            printf("\n");
        printf("%02x  ", read_data[i]);
        // printf("%c\n", read_data[i]);
    }
    // printf("%s\n", &read_data);
    printf("\n");

    // 读 0 位置
    memset(read_data,0,256);
    lseek(fd, 0, SEEK_SET);
    read(fd, read_data, 255);
    usleep(20000);
    for(i=0; i<64; i++){
    
        if(i%10 == 0)
            printf("\n");
        printf("%02x ", read_data[i]);
        // printf("%c\n", read_data[i]);
    }

    printf("\n");

}

在这里插入图片描述

通过devfs访问I2C设备

linux的i2c驱动会针对每个i2c适配器在/dev/目录下生成一个主设备号为89的设备文件,简单的来说,对于本例的eeprom驱动,/dev/i2c/0就是它的设备文件,因此接下来的eeprom的访问就变为了对此设备文件的访问。

我们需要用到两个结构体 i2c_msg 和 i2c_rdwr_ioctl_data。

struct i2c_msg {
     //i2c消息结构体,每个i2c消息对应一个结构体
 __u16 addr; /* 从设备地址,此处就是eeprom地址,即0x50 */
 __u16 flags;    /* 一些标志,比如i2c读等*/
 __u16 len;      /* i2c消息的长度 */
 __u8 *buf;      /* 指向i2c消息中的数据 */
 };

struct i2c_rdwr_ioctl_data {
    
 struct i2c_msg __user *msgs;    /* 指向一个i2c消息 */
 __u32 nmsgs;            /* i2c消息的数量 */
};

对一个 eeprom 上的特定地址(0x10)写入特定数据(0x58)并在从此地址读出写入数据的示例程序如下所示。

#include <stdio.h>
#include <linux/types.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>

int main()
{
    
    int fd,ret;
    struct i2c_rdwr_ioctl_data e2prom_data;

    fd=open("/dev/i2c-2", O_RDWR);                                                              //打开eeprom设备文件结点

    if(fd<0)
    {
    
        perror("open error");
    }

    e2prom_data.nmsgs = 2;
    e2prom_data.msgs  = (struct i2c_msg*)malloc(e2prom_data.nmsgs*sizeof(struct i2c_msg));      //分配空间
    
    if(!e2prom_data.msgs){
    
        perror("malloc error");
        exit(1);
    }

    ioctl(fd, I2C_TIMEOUT, 1);                                                                  /*超时时间*/
    ioctl(fd, I2C_RETRIES, 2);                                                                  /*重复次数*/

    /*写eeprom*/
    e2prom_data.nmsgs            = 1;                                                           // 由前面eeprom读写分析可知,写eeprom需要一条消息
    (e2prom_data.msgs[0]).len    = 3;                                                           // 此消息的长度为3个字节,前两个字节是要写入数据的地址,第三个字节是要写入的数据
    (e2prom_data.msgs[0]).addr   = 0x50;                                                        // e2prom 设备地址
    (e2prom_data.msgs[0]).flags  = 0;                                                           // 写
    (e2prom_data.msgs[0]).buf    = (unsigned char*)malloc(3);
    (e2prom_data.msgs[0]).buf[0] = 0x10;                                                        // e2prom 写入目标的地址
    (e2prom_data.msgs[0]).buf[2] = 0x58;                                                        // 写入的数据
    
    ret = ioctl(fd,I2C_RDWR,(unsigned long)&e2prom_data);                                       // 通过ioctl进行实际写入操作,后面会详细分析
    
    if(ret<0){
    
        
        perror("ioctl error1");
    }
    
    sleep(1);

    /*读eeprom*/
    e2prom_data.nmsgs            = 2;                                                           // 读eeprom需要两条消息
    (e2prom_data.msgs[0]).len    = 2;                                                           // 第一条消息实际是写eeprom,需要告诉eeprom需要读数据的地址,因此长度为2个字节
    (e2prom_data.msgs[0]).addr   = 0x50;                                                        // e2prom 设备地址
    (e2prom_data.msgs[0]).flags  = 0;                                                           // 先是写
    (e2prom_data.msgs[0]).buf[0] = 0x10;                                                        // e2prom上需要读的数据的地址

    (e2prom_data.msgs[1]).len    = 3;                                                           // 第二条消息才是读eeprom,
    (e2prom_data.msgs[1]).addr   = 0x50;                                                        // e2prom 设备地址
    (e2prom_data.msgs[1]).flags  = I2C_M_RD;                                                    // 然后是读
    (e2prom_data.msgs[1]).buf    = (unsigned char*)malloc(1);                                   // 存放返回值的地址。
    (e2prom_data.msgs[1]).buf[0] = 0;                                                           // 初始化读缓冲,读到的数据放到此缓冲区

    ret = ioctl(fd,I2C_RDWR,(unsigned long)&e2prom_data);                                       // 通过ioctl进行实际的读操作
    
    if(ret<0){
    
        
        perror("ioctl error2");
    }

    printf("buff[0]=0x%02x\n",(e2prom_data.msgs[1]).buf[0]);
    /***打印读出的值,没错的话,就应该是前面写的0x58了***/

    close(fd);

    return 0;
}

注意

eeprom写保护问题:
当你发现能够从eeprom中读出数据,但是无法往eeprom中写数据时,请检查eeprom芯片的wp(write protect)引脚是否被上拉了。

参考

https://hello2mao.github.io/2015/12/02/Linux_I2C_driver/#3

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/szm1234/article/details/119139357

智能推荐

PTA 7-5 成绩排序_把成绩单按分数从高到低的顺序进行排序并输出,成绩之间有一个空格,最后的成绩后没-程序员宅基地

该文章是关于对班里学生某门课程成绩进行排序的问题。要求对班里的学生按照成绩从高到低排序输出。输入包括学生数目和每个学生的成绩,输出为按成绩从高到低的排序结果。

Elasticsearch 解决集群 Yellow 与 Red 的问题_es yellow删除索引-程序员宅基地

文章浏览阅读963次。集群健康度分片健康红:至少有一个主分片没有分配黄:至少有一个副本没有分配绿:主副本分片全部正常分配索引健康:最差的分片的状态集群健康:最差的索引的状态Health 相关的 APIGET _cluster/health集群的状态(检查 节点数量)GET _cluster/health?level=indices所有索引的健康状态 (查看有问题的索引GET _cluster/health/my_index单个索引的健康状态(查看具体的索引)GET _cl_es yellow删除索引

关于图片大小的理解_图片数据像素点占比多少-程序员宅基地

文章浏览阅读559次。A:透明度R:红色G:绿B:蓝Bitmap.Config ARGB_4444:每个像素占四位,即A=4,R=4,G=4,B=4,那么一个像素点占4+4+4+4=16位 Bitmap.Config ARGB_8888:每个像素占四位,即A=8,R=8,G=8,B=8,那么一个像素点占8+8+8+8=32位Bitmap.Config RGB_565:每个像素占四位,即R=5,G_图片数据像素点占比多少

Ueditor自定义上传文件通过SpringMVC上传图片到FTP_ueditor 上传图片 spring-程序员宅基地

文章浏览阅读512次。一、修改Ueditor的config.json的文档图片访问路径前缀改成FTP对外暴露的访问地址"imageUrlPrefix": "http://192.168.85.98:8280/up/"二、初始化Ueditor时绑定自定义文件上传方法<!-- 富文本编辑器 --><div id="content"> <script id="edit..._ueditor 上传图片 spring

STM32芯片的DFU编程及相关话题_syscfg_memoryremapconfig( syscfg_memoryremap_sram -程序员宅基地

文章浏览阅读2.2k次。相当部分的 STM32芯片都带USB模块,有时我们会考虑利用STM32芯片的USB模块进行程序代码的下载或升级。USB协议中有专门针对设备固件升级的类协议,即可以通过DFU类协议进行产品固件的加载或更新。 关于STM32产品的DFU程序下载和升级,ST官方有相关的资料文档。可以去www.stmcu.com.cn 或者去www.st.com 搜索DFUse下载相关资料。_syscfg_memoryremapconfig( syscfg_memoryremap_sram )作用是什么;

关于利用shell实现ftp_shell标本实现ftp-程序员宅基地

文章浏览阅读904次。过去,我是在写expect脚本来实现自动登陆并上传下载文件。不过略感不顺。参考文档:http://blog.chinaunix.net/uid-20526681-id-3549245.html现在有一个好的方法cd 到本地你要上传或下载的目录中ftp -niv &lt;&lt; EOFopen ip_addressuser username passwordasciiput filen..._shell标本实现ftp

随便推点

BERT跨模态之后:占领了视觉常识推理任务榜单TOP 2!-程序员宅基地

文章浏览阅读388次。星标/置顶小屋,带你解锁最萌最前沿的NLP、搜索与推荐技术文 | 小鹿鹿lulu编 | YY前言由于 BERT-like 模型在 NLP 领域上的成功,研究者们开始尝试将其应用到更为复杂..._bert进行知识推理

Java微笔记(7)-程序员宅基地

文章浏览阅读45次。String 类常用方法注意点:字符串 str 中字符的索引从0开始,范围为 0 到 str.length()-1使用 indexOf 进行字符或字符串查找时,如果匹配返回位置索引;如果没有匹配结果,返回 -1使用 substring(beginIndex , endIndex) 进行字符串截取时,包括 beginIndex 位置的字符,不包括 endIndex 位置的字符“==” ...

AMA回顾|ENVELOP 将在本周首次部署,十月将有大动作_envelop上了几个平台-程序员宅基地

文章浏览阅读334次。9月29日,ENVELOP项目在Crypto Horses社区举办了AMA活动,与5万多位社群成员分享了项目进展。项目CEOAlex Shedogubov与大家积极互动交流,一起探讨项目发展与治理。嘉宾及项目介绍Hi there! I am Alex Shedogubov and I am a CEO in ENVELOP. I manage the project and the product development. I have more than 8 years of mana..._envelop上了几个平台

Python中的f字符串的用法_python f-程序员宅基地

文章浏览阅读3.4w次,点赞26次,收藏131次。Python中的f字符串的用法:要在字符串中插入变量的值,可在前引号前加上字母f,再将要插入的变量放在花括号内。举例子如下:first_name="ada"last_name="lovelace"full_name=f"{first_name}{last_name}"print(f"Hello,{full_name.title()}!")打印结果为:Hello,Ada Lovelace!还可以使用f字符串来创建消息,再把整条消息赋给变量:举例子:first_name=_python f

如何在react-native实现自定义的垂直方向跑马灯_react-native-anchor-carousel 竖向-程序员宅基地

文章浏览阅读2.9k次。项目需求是需要实现一个垂直方向的跑马灯轮播,早期采用react-native-swiper解决方案,此方案在ios端正常使用,在android端不能使用,所有果断放弃。第二方案打算使用ant-mobile的Carousel组件,import { Carousel, WingBlank } from 'antd-mobile';import { Text } from 'react-nat..._react-native-anchor-carousel 竖向

无线电A类考试试题_a类无线电考试卷-程序员宅基地

文章浏览阅读2k次,点赞2次,收藏2次。[I]LK0001[Q]我国现行法律体系中专门针对无线电管理的最高法律文件及其立法机关是:[A]中华人民共和国无线电管理条例,国务院和中央军委[B]中华人民共和国无线电管理办法,工业和信息化部[C]中华人民共和国电信条例,国务院[D]中华人民共和国业余无线电台管理办法,工业和信息化部[P][I]LK0002[Q]我国现行法律体系中专门针对业余无线电台管理的最高法律文件及其立法机关是:[A]业余无线电台管理办法,工业和信息化部[B]个人业余无线电台管理暂行办法,国家体委和国家无委[C]业_a类无线电考试卷

推荐文章

热门文章

相关标签