【Linux学习笔记】IIC驱动

系列文章目录

【Linux学习笔记】开发板挂载根文件系统

【Linux学习笔记】设备树学习

【Linux学习笔记】pinctrl和gpio子系统

【Linux学习笔记】Linux 并发与竞争

【Linux学习笔记】Linux内核定时器

【Linux学习笔记】Linux中断

【Linux学习笔记】阻塞和非阻塞IO

【Linux学习笔记】异步通知

【Linux学习笔记】platform 设备

【Linux学习笔记】MISC

【Linux学习笔记】INPUT 子系统


本系列使用的开发板为正点原子阿尔法IMX6ULL开发板,及根据正点原子所的提供教程学习同系列笔记已放置链接在上面。



步骤:

  1. 修改设备树
  2. 定义i2c_driver结构体,然后在of_device_id设置compatible值匹配设备树
  3. 注册i2c_add_driver(放在入口函数)
  4. 编写probe函数函数,设备匹配成功会自动运行
  5. 初始化I2C器件(放在open函数)
  6. 编写对应器件的读/写

以ap3216c为例:

一、修改设备树

1. 添加I2C引脚

打开 imx6ull-alientek-emmc.dts,在pinctrl_i2c:i2clgrp中已经定义好了两个引脚,这是NXP写的,直接用就可以了

2. 在i2c节点追加子节点

找到&i2c1这个节点,内容如下:

&i2c1 {
	clock-frequency = <100000>;//i2c的频率为100KHz
	pinctrl-names = "default";//
	pinctrl-0 = <&pinctrl_i2c1>;//指定i2c所使用的IO
	status = "okay";//
};

追加内容后为:

&i2c1 {
	clock-frequency = <100000>;//i2c的频率为100KHz
	pinctrl-names = "default";//
	pinctrl-0 = <&pinctrl_i2c1>;//指定i2c所使用的IO
	status = "okay";//

	ap3216c@1e {//1e为器件地址
		compatible = "alientek,ap3216c";//匹配值
		reg = <0x1e>;//器件地址
	};
};

追加成功后会在/sys/bus/i2c/devices目录下看到x-001e的子目录。

二、I2C设备驱动

关于I2C总线(控制器或适配器)驱动一般是由半导体厂商完成,所以不多关注。
需要重点关注的两个数据结构i2c_client 和 i2c_driver

2.1 i2c_client结构体

i2c_client 结构体定义在 include/linux/i2c.h 文件中,内容如下:

struct i2c_client {
	unsigned short flags; /* 标志 */
	unsigned short addr; /* 芯片地址,7 位,存在低 7 位*/
	......
	char name[I2C_NAME_SIZE]; /* 名字 */
	struct i2c_adapter *adapter; /* 对应的 I2C 适配器 */
	struct device dev; /* 设备结构体 */
	int irq; /* 中断 */
	struct list_head detected;
	......
};

一个设备对应一个i2c_client,每检测到一个I2C设备就会给这个I2C设备分配一个i2c_client。

2.2 i2c_driver 结构体

类似 platform_driver,是需要重点处理的内容,其定义在 include/linux/i2c.h 文件中,包含了驱动与设备的匹配和兼容属性(compatible),匹配过程由I2C核心来完成。

i2c_driver 结构体构建完成以后需要使用 int i2c_register_driver向Linux内核注册这个i2c_driver, int i2c_register_driver函数原型如下:

int i2c_register_driver(struct module *owner, 
 						struct i2c_driver *driver)
owner:一般为 THIS_MODULE。
driver:要注册的 i2c_driver。
返回值:0,成功;负值,失败。

也可以使用:

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

注销需要使用函数i2c_del_driver 函数,此函数原型如下:

void i2c_del_driver(struct i2c_driver *driver)
driver:要注销的 i2c_driver。
返回值:无。

2.3 i2c_driver注册示例

/* i2c 驱动的 probe 函数 */
static int xxx_probe(struct i2c_client *client,
						const struct i2c_device_id *id)
{
	/* 函数具体程序 */
	eturn 0;
}

/* i2c 驱动的 remove 函数 */
static int xxx_remove(struct i2c_client *client)
{
	/* 函数具体程序 */
	return 0;
}

/* 传统匹配方式 ID 列表 */
static const struct i2c_device_id xxx_id[] = {
	{"xxx", 0}, 
	{}
};

/* 设备树匹配列表 */
static const struct of_device_id xxx_of_match[] = {
	{ .compatible = "xxx" },
	{ /* Sentinel */ }
};

/* i2c 驱动结构体 */
static struct i2c_driver xxx_driver = {
	.probe = xxx_probe,
	.remove = xxx_remove,
	.driver = {
		.owner = THIS_MODULE,
		.name = "xxx",
		.of_match_table = xxx_of_match,
	},
	.id_table = xxx_id,
};

 /* 驱动入口函数 */
static int __init xxx_init(void)
{
	int ret = 0;
	
	ret = i2c_add_driver(&xxx_driver);
	return ret;
}

/* 驱动出口函数 */
static void __exit xxx_exit(void)
{
	i2c_del_driver(&xxx_driver);
}

module_init(xxx_init);
module_exit(xxx_exit);

三、数据的收发

对I2C设备寄存器进行读写操作,要用到i2c_transfer 函数了。i2c_transfer 函数最终会调用 I2C 适配器中 i2c_algorithm 里面的 master_xfer 函数,对于 I.MX6U 而言就是i2c_imx_xfer 这个函数。i2c_transfer 函数原型如下:

int i2c_transfer(struct i2c_adapter *adap,
					struct i2c_msg *msgs,
					int num)
adap:所使用的 I2C 适配器,i2c_client 会保存其对应的 i2c_adapter。,就是i2c_client里面的struct i2c_adapter *adapter
msgs:I2C 要发送的一个或多个消息。
num:消息数量,也就是 msgs 的数量。
返回值:负值,失败,其他非负值,发送的 msgs 数量。

需要重点关注的是msgs参数,这是一个i2c_msg 类型的指针参数,是需要传递的消息,其定义在include/uapi/linux/i2c.h 文件中,结构体内容如下:

struct i2c_msg {
	__u16 addr; /* 从机地址 */
	__u16 flags; /* 标志 */
	#define I2C_M_TEN 0x0010
	#define I2C_M_RD 0x0001
	#define I2C_M_STOP 0x8000
	#define I2C_M_NOSTART 0x4000
	#define I2C_M_REV_DIR_ADDR 0x2000 
	#define I2C_M_IGNORE_NAK 0x1000 
	#define I2C_M_NO_RD_ACK 0x0800
	#define I2C_M_RECV_LEN 0x0400
	__u16 len; /* 消息(本 msg)长度 */
	__u8 *buf; /* 消息数据 */
};

使用 i2c_transfer 函数发送数据之前要先构建好 i2c_msg,使用 i2c_transfer 进行 I2C 数据收发的示例代码如下:

/* 设备结构体 */
struct xxx_dev {
	......
	void *private_data; /* 私有数据,一般会设置为 i2c_client */
};

/*
* @description : 读取 I2C 设备多个寄存器数据
* @param – dev : I2C 设备
* @param – reg : 要读取的寄存器首地址
* @param – val : 读取到的数据
* @param – len : 要读取的数据长度
* @return : 操作结果
*/
static int xxx_read_regs(struct xxx_dev *dev, u8 reg, void *val,
						int len)
{
	int ret;
	struct i2c_msg msg[2];
	struct i2c_client *client = (struct i2c_client *)
	dev->private_data;
	
	 /* msg[0],第一条写消息,发送要读取的寄存器首地址 */
	msg[0].addr = client->addr; /* I2C 器件地址 */
	msg[0].flags = 0; /* 标记为发送数据 */
	msg[0].buf = &reg; /* 读取的首地址 */
	msg[0].len = 1; /* reg 长度 */
	
	/* msg[1],第二条读消息,读取寄存器数据 */
	msg[1].addr = client->addr; /* I2C 器件地址 */
	msg[1].flags = I2C_M_RD; /* 标记为读取数据 */
	msg[1].buf = val; /* 读取数据缓冲区 */
	msg[1].len = len; /* 要读取的数据长度 */
	ret = i2c_transfer(client->adapter, msg, 2);
	if(ret == 2) {
		ret = 0;
	} else {
		ret = -EREMOTEIO;
	}
	return ret;
}

/*
* @description : 向 I2C 设备多个寄存器写入数据
* @param – dev : 要写入的设备结构体
* @param – reg : 要写入的寄存器首地址
* @param – buf : 要写入的数据缓冲区
* @param – len : 要写入的数据长度
* @return : 操作结果
*/
static s32 xxx_write_regs(struct xxx_dev *dev, u8 reg, u8 *buf,
							u8 len)
{
	u8 b[256];
	struct i2c_msg msg;
	struct i2c_client *client = (struct i2c_client *)
	dev->private_data;
	
	b[0] = reg; /* 寄存器首地址 */
	memcpy(&b[1],buf,len); /* 将要发送的数据拷贝到数组 b 里面 */
	
	msg.addr = client->addr; /* I2C 器件地址 */
	msg.flags = 0; /* 标记为写数据 */
	
	msg.buf = b; /* 要发送的数据缓冲区 */
	msg.len = len + 1; /* 要发送的数据长度 */
	
	return i2c_transfer(client->adapter, &msg, 1);
}

另外还有两个API函数分别用于I2C数据的收发操作,这两个函数最终都会调用i2c_transfer。

  1. 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)类型的数据。
返回值:负值,失败,其他非负值,发送的字节数。
  1. 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)类型的数据。
返回值:负值,失败,其他非负值,发送的字节数。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值