linux i2c总线分析使用设备树

linux i2c总线分析使用设备树


i2c总线简要说明

I2C总线是由Philips公司开发的一种简单、双向二线制同步串行总线。它只需要两根线即可在连接于总线上的器件之间传送信息。分别为时钟线SCL和数据线SDA,这里不重点分析i2c的物理特性,我们主要是分析linux下的i2c的软件框架,在MCU用过i2c的读者应该知道,i2c在MCU里面属于一个外设控制器,用户设置好时钟,从机地址,和数据搬运到读写寄存器外设控制器就可以把数据在i2c总线上发送出去。

linux下i2c的总线模型

linux是一个支持多平台的操作系统,可以支持很多的芯片NXP,ST,Rockchip等,每个厂商使用的i2c外设控制器实现的方法都不一样,配置的寄存器都不一样,为了让使用者无需关心各个平台的i2c控制器的实现方法,就是不必深入了解如何配置寄存器,linux系统引入了i2c总线的软件框架,框架下面分为控制器驱动和设备驱动(比如触摸屏设备),下面我们主要解释一下重要的一些概念。

  • i2c控制器,抽象为struct i2c_adapter,提供i2c控制器收发数据等。
  • 用来描述从机i2c地址、工作的速率,抽象为struct i2c_client
  • 设备驱动部分,TP(触摸屏),陀螺仪sensor,光感sensor如何使用i2c进行收发数据, 抽象为struct i2c_driver
  • 每个struct i2c_client都有一个struct i2c_driver进行匹配上,然后驱动才能工作

我们知道在主控SOC芯片这边有i2c控制器所以linux系统把这部分抽象为struct i2c_adapter。在i2c控制器上可以挂载多个从机设备可能有TP(触摸屏),陀螺仪sensor,光感sensor等,每个从机设备都有一个从机地址和别人是不同的,工作的速率可能也是不同的这些属性就由struct i2c_client来描述。TP(触摸屏),陀螺仪sensor,每个产品的寄存器代表的意思都不一样,需要实现不同的驱动,所以linx i2c软件框架会对这种不同的东西单独抽象出来一个结构体叫struct i2c_driver。

查看Rockchip芯片平台的i2c adapter控制器驱动 dts定义

i2c0: i2c@ff180000 {
		compatible = "rockchip,rk3399-i2c";  //每个厂商都会实现 adapter 部分的代码,搜索这个字符串可以搜索到源代码的位置
		reg = <0x0 0xff180000 0x0 0x1000>;   //i2c adapter 寄存器的地址范围
		clocks =  <&cru SCLK_I2C0>, <&cru PCLK_I2C0>; //i2c adapter 的时钟来源
		clock-names = "i2c", "pclk";
		interrupts = <GIC_SPI 7 IRQ_TYPE_LEVEL_HIGH>;
		pinctrl-names = "default";
		pinctrl-0 = <&i2c0_xfer>; //GPIO设置为i2c模式
		#address-cells = <1>;
		#size-cells = <0>;
		status = "disabled"; //默认关闭,使用的时候再打开
	};
	

在linux i2c软件架构上,芯片厂商要完成i2c adapter部分的代码,然后用户就可以很简单的使用芯片平台的i2c外设而不必关心怎么配置芯片厂商的寄存器和时钟等,我们可以通过ompatible = “rockchip,rk3399-i2c”;这个字符串找到代码所以的.c文件下面我们逐步分析Rockchip是如何将i2c如何添加到i2c软件框架中的。

Soc芯片控制器驱动部分

\kernel\drivers\i2c\busses\i2c-rk3x.c //通过命令 grep "rockchip,rk3399-i2c" -nR 搜索到源码的位置

static struct platform_driver rk3x_i2c_driver = {
	.probe   = rk3x_i2c_probe,  //step 1 probe函数会被调用
	.remove  = rk3x_i2c_remove,
	.driver  = {
		.name  = "rk3x-i2c",
		.of_match_table = rk3x_i2c_match,
		.pm = &rk3x_i2c_pm_ops,
	},
};

static int rk3x_i2c_probe(struct platform_device *pdev)
{
	i2c = devm_kzalloc(&pdev->dev, sizeof(struct rk3x_i2c), GFP_KERNEL);
	if (!i2c)
		return -ENOMEM;
		
	i2c->adap.algo = &rk3x_i2c_algorithm; //控制器端提供i2c读写函数,和硬件平台相关,不重点分析	
		
	//原始的代码非常多有设置时钟,有芯片平台独有的设置,有分配中断等,这些都是和Rockchip芯片平台相关,为了不迷路我都删除了,因为这些都不是我们需要关心的平台做好了我们拿来使用就好了。	
	clk_rate = clk_get_rate(i2c->clk);  
	rk3x_i2c_adapt_div(i2c, clk_rate);
	
	//下面这个函数是最重要的,上面已经分配了struct i2c_adapter adap结构体,然后注册到系统中,下面我们就跟踪后面做了什么
	ret = i2c_add_adapter(&i2c->adap);  //step 2
	if (ret < 0) {
		dev_err(&pdev->dev, "Could not register adapter\n");
		goto err_clk_notifier;
	}
	
	return 0;
}

//芯片平台添加adapter和client代码到linux i2c总线框架跟踪
static int rk3x_i2c_probe(struct platform_device *pdev)
        i2c_add_adapter(&i2c->adap);
            __i2c_add_numbered_adapter(adapter);
               i2c_register_adapter(adap);
                   res = device_register(&adap->dev);//i2c总线底层使用的也是linux下的bus总线
                   of_i2c_register_devices(adap);//通过设备树的方式添加devices节点,这里的devices会转化为我们上面说的struct i2c_client这个概念,大家也可以理解为devices就是i2c_client
                       for_each_available_child_of_node(adap->dev.of_node, node) {
		                    of_i2c_register_device(adap, node); //添加每个(devices)i2c_client,和i2c adapter关联,也可以理解为添加到adapter上,每个devices代表下个挂有多少个i2c从机设备。
		                        i2c_new_device(adap, &info);//添加每个i2c_client 到bus总线
		                        { 
                                  struct i2c_client	*	client = kzalloc(sizeof *client, GFP_KERNEL);
                                   status = device_register(&client->dev);
		                        }
	                   }

上面分析的是芯片控制器端提供的驱动部分i2c_adapter和i2c_client是如何注册到软件架构中的,for_each_available_child_of_node函数就是根据下面的设备树节点把两个i2c_client添加到链表中的,下面是分析i2c_client的节点在dts中写法。

&i2c0 { //引用外设驱动dts节点
	status = "okay";  //上面默认关闭,现在使用的打开
	clock-frequency = <400000>;
	i2c-scl-rising-time-ns = <280>;
	i2c-scl-falling-time-ns = <16>;
	
	//从下面两个子节点可以看到这个i2c上挂载有两个从机设备,分别为rk809,和触摸屏gt9xx。
	rk809: pmic@20 { 
		compatible = "rockchip,rk809";
		reg = <0x20>; //从机地址
    }
    
    gt9xx:gt9xx@5d  {
		compatible = "goodix,gt9xx";
	    reg = <0x5d>; //从机地址
    }

上面有可i2c_client就应该有i2c_driver,因为只有i2c_client和i2c_driver成对存在驱动才能正常工作,i2c_driver在那里定义呢?i2c_driver一般定义在具体的设备驱动中,比如触摸屏的驱动,会定义一个struct i2c_driver结构体,然后注册到i2c_add_driver,下面是设备驱动的代码的分析。

static struct of_device_id goodix_ts_dt_ids[] = {   //step 1
    { .compatible = "goodix,gt9xx" },
};

static struct i2c_driver goodix_ts_driver = {  //step 2
    .probe      = goodix_ts_probe,
    .remove     = goodix_ts_remove,
    .id_table   = goodix_ts_id,
    .driver = {
        .name     = GTP_I2C_NAME,
	 .of_match_table = of_match_ptr(goodix_ts_dt_ids),
    },
};

i2c_add_driver(&goodix_ts_driver); //step3

//step 4
static int goodix_ts_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    //代码已经精简,只挑重点
    ts->client = client;  //保存struct i2c_client *client结构体,用于数据收发。
    
     ret = gtp_request_input_dev(client, ts);//注册一个input设备,这里我们不关心
    return ret;
}

//step 5
s32 gtp_i2c_write(struct i2c_client *client,u8 *buf,s32 len)
{
    //设备驱动使用标准i2c发送函数进行数据发送
    struct i2c_msg msg; //定义一个msg
    msg.flags = (!I2C_M_RD);  //设置为写
    msg.len   = len; 
    msg.buf   = buf;
    ret = i2c_transfer(client->adapter, &msg, 1); //调用这个标准的函数将数据发送出去,需要注意的是这里要传入goodix_ts_probe保存的struct i2c_client指针。
    return ret;
}
//step 6
s32 gtp_i2c_read(struct i2c_client *client, u8 *buf, s32 len)
{
    struct i2c_msg msgs[2];
   
    msgs[0].flags = !I2C_M_RD;  //设置为写,先写触摸屏的寄存器地址
    msgs[0].addr  = client->addr;
    msgs[0].len   = GTP_ADDR_LENGTH;
    msgs[0].buf   = &buf[0];

    msgs[1].flags = I2C_M_RD; //设置为读,将触摸屏寄存器读回来
    msgs[1].addr  = client->addr;
    msgs[1].len   = len - GTP_ADDR_LENGTH;
    msgs[1].buf   = &buf[GTP_ADDR_LENGTH];
    
    ret = i2c_transfer(client->adapter, msgs, 2);  //数据传输
    return ret;
}

上面是精简版触摸屏设备驱动程序,我们为了说明linux下i2c的驱动框架,我们删除了很多和触摸屏相关的代码,step1 step2 step3是为了在系统初始化的时候把goodix_ts_driver注册到系统中,这个i2c_driver和上面控制器端的i2c_client匹配会跑到step4 goodix_ts_probe这个函数被调用,重点是保存struct i2c_client *client这结构体指针,然后通过这个结构体指针gtp_i2c_write和gtp_i2c_read就可以调用系统提供的i2c_transfer函数进行数据收发。

Guess you like

Origin blog.csdn.net/qq_27809619/article/details/110133416