XuSenfeng

个人站

复读了,更新随缘,有的文件不全或者图片缺失具体看我的笔记库(https://github.com/XuSenfeng/note)


I2C

目录

I2C

分为I2C控制器驱动, 由原厂进行编写, 还有具体的设备驱动, 由我们自己编写

之前使用的platform是虚拟出来的总线, I2C可以自己作为一条总线, 重点就是I2C是适配器

总线驱动

重要的数据结构:i2c_adapter和i2c_algorithm,定义在include/linux/i2c.h文件中

驱动是i2c-imx.c文件, 使用platform从设备树获取设备

定义一个物理的控制器

或者说I2C适配器驱动的主要工作就是初始化i2c_adapter结构体变量,然后设置i2c_algorithm中的master_xfer函数, 这个函数用来进行I2C读写操作, 还实现了一个functionality函数, 用于返回可以提供的功能

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

注册结构体, 这个结构体中的i2c_algorithm包含了IIC控制器访问的接口函数, 需要实现

void i2c_del_adapter(struct i2c_adapter * adap)

删除

设备驱动

  • i2c_client结构体

定义在include/linux/i2c.h文件中

struct i2c_client {
	unsigned short flags;		/* div., see below		*/
	unsigned short addr; /* chip address - NOTE: 7bit	*/
					/* addresses are stored in the	*/
					/* _LOWER_ 7 bits		*/
	char name[I2C_NAME_SIZE];
	struct i2c_adapter *adapter;	/* the adapter we sit on	*/
	struct device dev;		/* the device structure		*/
	int irq;			/* irq issued by device		*/
	struct list_head detected;
#if IS_ENABLED(CONFIG_I2C_SLAVE)
	i2c_slave_cb_t slave_cb;	/* callback for slave mode	*/
#endif
};

描述具体的设备, flgs: 标志, addr芯片的地址, 7位放在低位, adapter: 使用的控制器

不需要自己创建i2c_client, 通过设备树来告诉

&i2c1 {
	clock-frequency = <100000>;
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_i2c1>;
	status = "okay";

	mag3110@0e {
		compatible = "fsl,mag3110";
		reg = <0x0e>;
		position = <2>;
	};

	fxls8471@1e {
		compatible = "fsl,fxls8471";
		reg = <0x1e>;
		position = <0>;
		interrupt-parent = <&gpio5>;
		interrupts = <0 8>;
	};
};

一般在这里添加具体的芯片, 系统在解析的时候就会知道然后创建对应的i2c_client

  • i2c_driver结构体

需要驱动编写人员进行编写,

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;

	/* Standard driver model interfaces */
	int (*probe)(struct i2c_client *, const struct i2c_device_id *);
	int (*remove)(struct i2c_client *);

	/* driver model interfaces that don't relate to enumeration  */
	void (*shutdown)(struct i2c_client *);

	/* Alert callback, for example for the SMBus alert protocol.
	 * The format and meaning of the data value depends on the protocol.
	 * For the SMBus alert protocol, there is a single bit of data passed
	 * as the alert response's low bit ("event flag").
	 */
	void (*alert)(struct i2c_client *, unsigned int data);

	/* a ioctl like command that can be used to perform specific functions
	 * with the device.
	 */
	int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);

	struct device_driver driver;
	const struct i2c_device_id *id_table;

	/* Device detection callback for automatic device creation */
	int (*detect)(struct i2c_client *, struct i2c_board_info *);
	const unsigned short *address_list;
	struct list_head clients;
};

有参数probe和remove函数, 具体就是初始化这个然后注册

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

注册函数

另外i2c_add_driver也常常用于注册i2c_driver,i2c_add_driver是一个宏

#define i2c_add_driver(driver) i2c_register_driver(THIS_MODULE,driver)
void i2c_del_driver(struct i2c_driver *driver)

删除

没有使用设备树

需要在BSP里面使用i2c_board_info结构体来描述一个具体的I2C设备

struct i2c_board_info {
	char		type[I2C_NAME_SIZE];
	unsigned short	flags;
	unsigned short	addr;
	void		*platform_data;
	struct dev_archdata	*archdata;
	struct device_node *of_node;
	struct fwnode_handle *fwnode;
	int		irq;
};

使用设备树

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

&i2c1 {
	clock-frequency = <100000>;	//总线时钟频率
	pinctrl-names = "default";	//两个引脚
	pinctrl-0 = <&pinctrl_i2c1>;
	status = "okay";		//打开控制器

	mag3110@0e {
		compatible = "fsl,mag3110";	//名字, 自己起
		reg = <0x0e>;	//具体的地址
		position = <2>;	//方向,可选
	};

	fxls8471@1e {
		compatible = "fsl,fxls8471";
		reg = <0x1e>;
		position = <0>;
		interrupt-parent = <&gpio5>;
		interrupts = <0 8>;
	};
};

添加设备信息, 挂在到哪一个设备树下面就使用哪一个, 创建重点是compatible属性和reg属性的设置

实际编写

  1. 修改设备树, 添加相关的信息, 添加的地址属性是7位的地址
  2. 挂载i2c_driver结构体i2c_add_driver, i2c_del_driver
  3. 初始化这个结构体
static int ap3216c_probe(struct i2c_client *client, const 									struct i2c_device_id *id);

static int ap3216c_remove(struct i2c_client *client);

//设备树匹配表
static struct of_device_id ap3216c_of_match[] = {
    {.compatible  = "jiao,ap3216c"},
    {/* sentinel */},
};


//传统匹配表
static struct i2c_device_id ap3216c_id[] = {
    {"jiao,ap3216c123",0},
    {}
};


static struct i2c_driver ap3216c_driver = {
    .probe = ap3216c_probe,
    .remove = ap3216c_remove,
    .driver = {
        .name = "ap3216c",
        .owner = THIS_MODULE,
        .of_match_table = of_match_ptr(), //设备树匹配方式
    },
    .id_table = ap3216c_id, //老版本的匹配方式
};

两种方法一同使用的时候才可以正常使用, id_table必须存在, 但可以不匹配, 原因是probe的判断条件是只有两个都存在的时候才会进行

  1. 使用字符设备
  2. 通过IIC控制器中的函数进行发送接收数据
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);

第一个参数, adap就是设备对应的适配器, 就是传入probe函数的第一个结构体的参数之一

  struct i2c_msg {
  	__u16 addr;	/* 从机地址*/
  	__u16 flags;	//标志, 就是下面的, 0表示发送
  #define I2C_M_TEN		0x0010	/* this is a ten bit chip address */
  #define I2C_M_RD		0x0001	/* read data, from slave to master */
  #define I2C_M_STOP		0x8000	/* if I2C_FUNC_PROTOCOL_MANGLING */
  #define I2C_M_NOSTART		0x4000	/* if I2C_FUNC_NOSTART */
  #define I2C_M_REV_DIR_ADDR	0x2000	/* if I2C_FUNC_PROTOCOL_MANGLING */
  #define I2C_M_IGNORE_NAK	0x1000	/* if I2C_FUNC_PROTOCOL_MANGLING */
  #define I2C_M_NO_RD_ACK		0x0800	/* if I2C_FUNC_PROTOCOL_MANGLING */
  #define I2C_M_RECV_LEN		0x0400	/* length will be first received byte */
  	__u16 len;		/* 消息的长度	*/
  	__u8 *buf;		/* 消息			*/
  };

不同的传输的数据

  struct i2c_client *client = *dev->private_data;
  struct i2c_msg msg[2];
  //发送要读取的寄存器
  msg[0].addr = client->addr;
  msg[0].flags = 0;
  msg[0].buf = &reg; //发送的数据,就是寄存器首地址
  msg[0].len = 1;
  //接受地址
  msg[1].addr = client->addr;
  msg[1].flags = I2C_M_RD;
  msg[1].buf = val; //接收
  msg[1].len = len;
  i2c_transfer(client->adapter, msg, 2);

根据协议, 首先发送寄存器首地址, 之后进行读取数据

  struct i2c_client *client = dev->private_data;
  u8 b[256];
  b[0] = reg;	//根据协议构建发送的数据
  memcpy(&b[1], buf, len);
  struct i2c_msg msg;
  msg.addr = client->addr;
  msg.flags = 0;
  msg.buf = b; //发送的数据
  msg.len = 1+len;
  return return i2c_transfer(client->adapter, msg, 1);

也可以使用函数i2c_smbus_read_byte_data读取一个