IIC设备驱动分析

IIC设备驱动分析

一.前言

之前调试 3520D 的时候,我们使用的 IT6801 驱动是直接在我们的应用程序中进行驱动。这样做虽然简单,但是可移植性移植不是很好。

后来,我们在制作 3521D 的板子的时候,我们找了一个海思的方案商,他可以帮我们一起调试驱动。于是我们就把 IT6801 的驱动程序给了他,然后他根据我们提供的应用驱动写了一个 kernel 的驱动程序。

之前一直想要学习一下 kernel 的驱动方法,但是,由于我们的项目中一直没有需要我们自己写 kernel 驱动的,因此,一直没有机会学习。这次刚好有机会可以学习一下。

二.驱动框架

驱动框架是驱动程序所具备的最基本的特征,驱动程序具备几个不同于我们应用程序的函数特点。

这里先将一个驱动程序最基本的特征列出来:

module_init(it6801_init);
module_exit(it6801_exit);

MODULE_LICENSE("GPL");

上面的三个部分是每个驱动程序必备的三个部分。

  • module_init(it6801_init);

    这个我们的驱动的入口,也就是我们在 insmod 加载驱动的时候我们的 kernel 是由这个函数入口进去的。

  • module_exit(it6801_exit);

    这个函数是我们的驱动出口,也就是当我们使用 remod 命令移除驱动的时候,我们的内核会调用这个函数去销毁我们的驱动配置。

  • MODULE_LICENSE(“GPL”);

    这个宏是我们用来告诉我们的内核我们的驱动模块带有一个 GPL 的许可证,这样内核在加载的时候就不会认为其实非法的了。

三.驱动初始化分析

static int __init it6801_init(void) 
{
   
	unsigned char ret;
	ret = i2c_client_init(); //初始化IIC从设备
	if(ret != 0)
	{
   
		printk("i2c_client_init error\n");
		return -1;
	}
	if(IT6802_fsm_init() != 0) //
	{
   
		printk("IT6802_fsm_init failed\n");
		return -1;
	}
    it6802PortSelect(F_PORT_SEL_0);
    it6802HPDCtrl(1, 1);
	//xxxxx

    if (misc_register(&it6801_dev)) //注册一个 miscdevice 对象
    {
   
        printk("ERROR: could not register it6801 devices\n");
		return -1;
    }

	mutex_init(&gs_mutex_lock); //初始化全局互斥锁
	gs_irq_wq = create_singlethread_workqueue("it6801_0"); //创建内核线程
	
	init_timer(&gs_timer); //初始化定时器参数
	gs_timer.function = (void *)tick_query_std; //设置定时器超时函数
	gs_timer.expires = HZ / 3 + jiffies; //设置定时时间
	add_timer(&gs_timer); //添加定时器,定时器开始生效

    return 0;
}

这个就是我们上面的 module_init 函数调用的函数。这个函数就是我们的所有的操作入口,下面分析一下这个函数。

  • static int __init it6801_init(void)

    可以看到,这个函数的函数名使用的是static int类型,而且还带了一个*__init*的标记,这里的类型定义不是强制的,但是,我们习惯上还是使用这样的命名方式。因为这样我们可以很方便的知道这个是我们驱动的入口。

  • i2c_client_init();

    这个是我们的i2c设备的初始化的操作,这里先省略,我们后面分析。

  • if(IT6802_fsm_init() != 0)

    这里其实是我们的具体业务逻辑,这里不对其进行分析,它里面的所有操作都是调用我的i2c接口函数i2c_write_byte i2c_read_byte进行的具体操作。

  • it6802PortSelect(F_PORT_SEL_0);

  • **it6802HPDCtrl(1, 1) ; **

    这两个也都是我们的具体业务操作,这里不对它们进行分析。

  • **if (misc_register(&it6801_dev)) **

    这里我们注册里一个miscdevice类型的函数,也就是说我们把我们的it6801当作了miscdevice设备进行住注册了。这里我们之所以把IIC设备使用定义为miscdevice设备,主要是我们的内核把驱动设备分了以下几类:

    • 字符设备
    • 块设备
    • 网络设备
    • 杂项设备

    而我们的miscdevice设备就是我们的杂项设备,因为IIC不在这个这些设备范围内,因此,这里我们将其归类为其他设备的范围内。

  • mutex_init(&gs_mutex_lock);

    这个是初始化了一个互斥锁,主要是防止我们的多个驱动同时调用我们的IIC设备。

  • **gs_irq_wq = create_singlethread_workqueue(“it6801_0”); **

    创建一个内核线程,并给其命名为it6801_0,这里返回了内核给这个线程分配的内存地址空间。

  • **init_timer(&gs_timer); **

    初始化timer_list的结构成员。

  • *gs_timer.function = (void )tick_query_std;

    设置定时超时函数。

  • gs_timer.expires = HZ / 3 + jiffies;

    设置定时超时时间。

  • add_timer(&gs_timer);

    timer加入内核timer列表中,等待处理。

四.驱动具体操作分析

1.IIC设备初始化

int i2c_client_init(void)
int i2c_client_init(void) 
{
   
	struct i2c_adapter* i2c_adap;
	i2c_adap = i2c_get_adapter(0); //得到IIC设备0的适配器(IIC模块)
	//强制创建一个新的设备对象 it6801
    it6801_client = i2c_new_device(i2c_adap, &hi_info); 
	i2c_put_adapter(i2c_adap); //释放IIC的适配器
	if(it6801_client == NULL)
		return -1;
	return 0;
}
struct i2c_adapter* i2c_adap;

定义一个 i2c 适配器指针,这里是我们 kernel 进行管理的模块,这里我们定义一个新的模块,以方便后续的操作。

这里把这个结构体的成员列出来:

struct i2c_adapter {
   
    struct module *owner;             // 所有者
    unsigned int id;
    unsigned int class;               // 该适配器支持的从设备的类型
    const struct i2c_algorithm *algo; // 该适配器与从设备的通信算法
    void *algo_data;

    /* data fields that are valid for all devices    */
    struct rt_mutex bus_lock;

    int timeout;              // 超时时间
    int retries;
    struct device dev;        // 该适配器设备对应的device

    int nr;                   // 适配器的编号
    char name[48];            // 适配器的名字
    struct completion dev_released;

    struct list_head userspace_clients;  // 用来挂接与适配器匹配成功的从设备i2c_client的一个链表头
};

什么是adapter:

这里我们需要知道什么是 adapter,其实 adapter 是我们的芯片内部自带的一个硬件设备,而这类设备由于在我们的芯片内部,因此,我们的 CPU 可以直接通过总线的方式对其进行操作。因此,我们的内核也就可以直接对其进行管理。这一部分是在我们的内核代码里面的,我们通过内核编译选项的方式可以加载这个设备。

这里我们使用的是 i2c 的 adapter,因为我们要使用这个设备来实现我们对外部芯片的操作,因此,这里我们就需要先定义一个这个设备。

  • i2c_adap = i2c_get_adapter(0);

    获得一个 i2c适配器,并将其关联给我们创建的适配器的指针。

    这里的参数 0 是我们的 /dev 下面的 i2c 的设备号,这里我们 /dev 下只有一个 i2c-0 设备,因此,这里的参数 0 即是获取到的这个设备。

  • it6801_client = i2c_new_device(i2c_adap, &hi_info);

    i2c_new_device函数原型:

    ​ **struct i2c_client * i2c_new_device(struct i2c_adapter adap, struct i2c_board_info const info);

    上面可以看到,我们这个函数的原型中有两个参数:

    • *struct i2c_adapter adap;

      这个参数我们上面已经知道了,这里不再解释。

    • *struct i2c_board_info const info:

      先看这个结构体的原型:

      //内核include/linux/i2c.h中
      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 acpi_dev_node acpi_node;
              int             irq;
      };
      

      这里我们使用的参数:

      //我们的驱动代码
      static struct i2c_board_info hi_info = {
             
          I2C_BOARD_INFO("it6801", 0x90),
      };
      //内核include/linux/i2c.h中
      #define I2C_BOARD_INFO(dev_type, dev_addr) \
              .type = dev_type, .addr = (dev_addr)
      

      由我们定义的结构体可以看出,我们的这个参数其实就是定义了我的 i2c 设备的设备地址和设备名。然后将这个设备的设备信息给我们的 i2c_adapter

      我们通过 i2c_new_device 这个函数,将给我们自己的创建的 i2c_adapter 和我们的要操作的对象i2c_client关联了起来。然后返回 i2c_client 的指针。

    • it6801_client:

      这里的返回值是 struct i2c_client 类型的结构体指针,这里我们看一下这个结构体的内容:

      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;
      };
      

      可以看到这个结构体中有 i2c_adapter 这个成员,其实我们的 IT6801 最终就是要实例化为一个 i2c_client 的对象。这个对象包含了它的名字,地址,以及它所使用的 i2c_adapter 等信息,这样我们就可以直接通过这个对象来获取或操作我们想要的信息。

  • i2c_put_adapter(i2c_adap);

    释放 i2c 的适配器,我们将获得的设备属性给赋给了i2c_client了,因此,这里我们的 i2c_get_adapter 函数的功能完成了,这里我们需要给它释放掉。

    adapter和client:

    这里有我们有两个概念,一个是 adapter 一个是 client。这两个其实分别对应了我们驱动的两个层次,一个是我们的片内驱动,一个是片外驱动。

    adapter 属于片内驱动,它和我们的 CPU 是通过总线的方式通信的,而它对于我们的 CPU 而言也是一个设备,不过这个设备是集成在我们的 kernel 内部的,我们可以通过配置 kernel 的方式来启用它。

    client 属于片外驱动,它相当于我们的芯片外部的设备,它是通过 IIC 总线的方式和我们的芯片相连接的。我们使用这个设备其实本质是我们的 CPU 通过控制 adapter 进而间接的控制它。

2.client设备初始化和控制:

if(IT6802_fsm_init() != 0)
{
   
    printk("IT6802_fsm_init failed\n");
    return -1;
}
it6802PortSelect(F_PORT_SEL_0);
it6802HPDCtrl(1, 1) ;  

这一部分是我们对我们的 IT6801 进行的初始化和操作。之前的初始化只是创建了一个 client 这个设备对象,这里的初始化才开始真正的对这个设备进行初始化操作。

  • if(IT6802_fsm_init() != 0)

    int IT6802_fsm_init(void)
    {
         
    	struct it6802_dev_data *it6802data = get_it6802_dev_data();
    
    	IT680X_DEBUG_PRINTF(("IT6802_fsm_init( ) \n"));
    
    	//FIX_ID_002 xxxxx 	Check IT6802 chip version Identify for TogglePolarity and Port 1 Deskew
    	if(IT6802_Identify_Chip() == 0)
        {
         
            return -1;
        } 
    
        IT6802_Rst(it6802data);
        .........
        .........
        //FIX_ID_041 xxxxx Add EDID reset
    	// fo IT6803 EDID fail issue
    	hdmirxset(REG_RX_0C0, 0x20, 0x20);	//xxxxx 2014-0731 [5] 1 for  reset edid
    	delay1ms(1);
    	hdmirxset(REG_RX_0C0, 0x20, 0x00);
        ........
        ........
    }
    

    这里的初始化代码比较长,我们不对功能进行分析。这里主要分析我们如何通过这个函数来操作 i2c 总线的。

    这里我们通过对代码进行追踪,最后发现所有的操作其实都是调用了下面的函数:

    BOOL i2c_write_byte( BYTE address,BYTE offset,BYTE byteno,BYTE *p_data,BYTE device )
    {
         
    	int ret;
    	int idx = 0; 
        for (idx = 0; idx < byteno; idx++)
        {
         
    		ret = __I2CWriteByte8(address, offset + idx, &p_data[idx]);
            if (ret <= 0)
            {
         
                return 0;
            }
        }
        return 1;
    }
    
    BOOL i2c_read_byte( BYTE address,BYTE offset,BYTE byteno,BYTE *p_data,BYTE device )
    {
         
    	int ret;
    	int idx = 0; 
        for (idx = 0; idx < byteno; idx++)
        {
         
    		ret = __I2CReadByte8(address, offset + idx, &p_data[idx]);
            if (ret <= 0)
            {
         
    			printk("ret: %d\n", ret);
                return 0;
            }
        }    
        return 1;
    }
    

    通过上面两个函数可以看到,这两个函数主要的实现方式是使用了以下两个函数:

    int __I2CWriteByte8(unsigned char chip_addr, unsigned char reg_addr, unsigned char *pdata)
    {
         
        int ret;
        unsigned char buf[2];
        struct i2c_client* client = it6801_client;
        
        it6801_client->addr = chip_addr;
    
        buf[0] = reg_addr;
        buf[1] = *pdata;
    
        ret = i2c_master_send(client, buf, 2);
        udelay(300);
        return ret;
    }
    
    int __I2CReadByte8(unsigned char chip_addr, unsigned char reg_addr, unsigned char *pdata)
    {
         
        int ret;
        struct i2c_client* client = it6801_client;
        unsigned char buf[2];
    
        it6801_client->addr = chip_addr;
    
        buf[0] = reg_addr;
        ret = i2c_master_recv(client, buf, 1);
        if (ret >= 0)
        {
         
            *pdata = buf
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值