Linux3.5下I2C设备驱动程序

知识背景:

1. I2C协议

2. 4412处理器I2C接口说明

3. bus-dev-drv模型(详见文章-Linux下驱动:分层、分离机制学习笔记)

4. linux内核下驱动设计基本知识


一、驱动框架

      以4412+linux3.5平台为例,说明Linux下I2C设备驱动程序。

      如果一条I2C总线上只连接一个I2C设备,那么只需要以字符型设备驱动框架来写驱动即可(填充file_opreoration结构体中的各个成员),那是典型的字符型设备驱动程序。

      实际上,一款处理器可能有多条I2C总线,多个I2C控制器,每条总线上可能挂接多个设备,按照典型的字符型驱动框架写驱动就要为每个这样的设备写一个驱动程序,而且要做很多重复性的工作。

      所以,有很多东西可以抽象的就抽象出来,实现一种框架,这种框架模型还是bus-dev-drv模型。4412处理器有8条可用I2C总线,每条总线上挂接的每个I2C设备都明白它们跟4412通信数据的含义。4412访问I2C设备时,都需要发出Start信号,都需要发出设备地址等等,这些共性的东西就可以抽象出来用一些函数实现,这些函数就是在另外一层即核心层驱动程序。

      核心层驱动程序提供统一的I2C设备操作函数,比如读写I2C设备的操作函数;还有设备驱动层(file_opreoration结构体相关);另外一层就是适配层,这一层是处理器的每个I2C适配器(也叫控制器)的驱动,适配层驱动程序提供统一的处理器I2C接口硬件操作函数。

       应用程序需要操作I2C设备时,根据设备节点调用读写函数(系统调用),然后就会执行设备驱动层的相应读写函数(它们是file_opreoration结构体的成员),这些读写函数里会调用核心层提供的统一的I2C设备读写操作函数,这些函数最终会通过适配器(I2C控制器)给连接在I2C总线上的I2C设备发送相应读写命令。适配器那一层驱动程序就是些I2C控制器的硬件操作,它是根据I2C协议来的(这里根据的I2C协议往往是硬件已经做好了的,软件只需要配置CPU的I2C控制器的某个寄存器即可实现I2C协议),是最底层,当I2C控制器的某个寄存器被控制后,I2C控制器就会自动地往I2C线上根据I2C协议发出相应信号与2IC设备通信。

       由此可见,写设备驱动程序即实现设备驱动层时,只要弄清楚bus-dev-drv驱动模型,(调用了probe函数后)剩下的就是典型的字符型设备驱动那一套了(构建file_opreoration结构体、分配主次设备号、创建设备节点),file_opreoration的读写函数调用的方法也由核心层提供。图一。

Linux3.5下I2C设备驱动程序_第1张图片

      上述三层是通过总线-设备-驱动模型融合到一起的。Linux内核中构建了许多总线,但并不是都是真实的,比如平台总线就是虚拟的,平台总线中也有设备链表,驱动链表。

      针对I2C总线,也有一个I2C总线的结构即i2c_bus_type结构,此结构里面也有设备链表和也有驱动链表。设备链表里存放i2c_client的结构体,这些结构体是注册i2c_client时加入的,不但要加入这些结构体,还会在总线的驱动链表中一个一个地比较drv即i2c_driver来判断是否有匹配的,如果有将调用drv里面的probe函数,匹配函数由总线提供;驱动链表里存放i2c_driver结构体,这些结构体是注册i2c_driver时加入的,不但要加入这些结构体,还会在总线的设备链表中一个一个地比较dev即比较i2c_client来判断是否有匹配的,如果匹配将调用drv里面的probe函数。

     上述的匹配函数就是i2c_bus_type结构里面的i2c_device_match函数。i2c_device_match函数通过id_table(i2c_driver结构的成员,它表示能支持哪些设备)来比较dev与drv是否匹配,具体方法是用id_table的name去比较,name分别是i2c_client结构和i2c_driver结构的成员。如果名字相同,就表示此驱动i2c_driver能支持这个设备i2c_client。

      总的说来,I2C基于bus-dev-drv模型的构建过程如下:

(1)左边注册一个设备,i2c_client

(2)右边注册一个驱动,i2c_driver

(3)比较它们的名字,如果相同,则调用driver的probe函数。

(4)probe函数里面做的事情由用户决定(比如注册、构建设备节点等,这些属于设备驱动)。


二、设置和注册i2c_client结构体(EEPROM为例)

      在Linux内核文档(/Documentation/i2c/instantiating-devices)中,介绍了注册即构造一个i2c_client的4种方法,并且建议使用如下前三种方法,后一种较复杂,万不得已才用。

1.  通过总线号声明一个I2C设备

      通过总线号声明一个I2C设备即构造一个i2c_board_info结构体,它里面有名字和地址,名字可以用来找到drv结构,地址可以用来访问哪个I2C设备,还可以放其他信息,这些信息会被将来的probe函数用到,也就是说在probe函数中要使用到哪些信息,这里面就可以增加哪些信息。然后使用i2c_register_board_info函数注册这个结构体,此函数的第一个参数即总线号,表示此设备属于哪条总线(这就是标题通过总线号声明一个i2c设备的含义),在此函数中会把此结构体放入链表__i2c_board_list

      然后在i2c_scan_static_board_info函数中使用到__i2c_board_list链表,即调用i2c_new_device函数把链表中的每个成员构造成一个i2c_client,并放入bus-dev-drv模型中总线中设备链表中去。在i2c_new_device函数中,分配一个i2c_client结构体后,设置它,并调用device_register函数注册,此函数最终会调用前面文章中提到device_add函数。i2c_scan_static_board_info函数是被i2c_register_adapter函数调用的,所以这里总的过程为i2c_register_adapter > i2c_scan_static_board_info > i2c_new_device。

     ft5x06_ts类型的I2C触摸屏驱动中就是使用的这种方法。在内核文件arch/arm/mach-exynos/mach-tiny4412.c中,对应触摸屏的i2c_register_board_info函数调用过程如下:

MACHINE_START(TINY4412, "TINY4412")
    .xxx
    .init_machine    = smdk4x12_machine_init,
    .xxx
MACHINE_END
                                 ↓

         调用smdk4x12_machine_init函数

                                 ↓

调用i2c_register_board_info(1, smdk4x12_i2c_devs1,ARRAY_SIZE(smdk4x12_i2c_devs1));

       可见,上述i2c_register_board_info函数的第一个参数为1,表示ft5x06_ts类型的I2C触摸屏挂接在处理器的I2C1那条总线上。


这种方法使用限制:在注册I2C适配器之前注册i2c_board_info结构体,即必须在 i2c_register_adapter 之前 i2c_register_board_info,所以不适合动态加载insmod。


2.  直接创建设备(直接调用i2c_new_device、i2c_new_probed_device)

(1)i2c_new_device方法

      第一种方法显得有些麻烦,这里就直接调用 i2c_new_device或i2c_new_probed_device函数实现。

      i2c_new_device函数总共有两个参数,第一个为要指定的适配器i2c_adapter(一个用来标识物理I2C总线结构,即用哪个I2C控制器发出I2C信号,某些CPU有多个I2C适配器),即要把i2c设备跟哪个适配器相连,这样以后在访问I2C设备时,就知道使用哪个适配器的操作函数了。第二个参数是用来描述I2C设备的相关信息的,即i2c_board_info结构体。所以这种方法需要在dev程序中定义i2c_adapter和i2c_board_info结构,i2c_board_info结构可以直接在dev程序中定义,i2c_adapter创建的代码如下:

    struct   i2c_adapter *i2c_adap;
    i2c_adap = i2c_get_adapter(0);
    at24cxx_client = i2c_new_device(i2c_adap, &at24cxx_info);
    i2c_put_adapter(i2c_adap);

     先定义一个i2c_adapter结构指针存放i2c_get_adapter(0)的返回值,此函数的参数为0,表示第0条总线,i2c_new_device调用完后要调用  i2c_put_adapter函数。通过这样的方式,在第0条总线下创建了一个新设备,以后就可以使用i2c_adap这个i2c_adapter的操作函数发出I2C的信号了,比如起始信号、停止信号等。实验代码如下:

at24cxx_dev.c :

#include 
#include 
#include 
#include 
#include 
#include 
#include 

//0x50表示I2C设备的地址,一般在	I2C设备芯片手册可以查到
static struct i2c_board_info at24cxx_info = {	
	I2C_BOARD_INFO("at24c08", 0x50),//这个名字要和drv程序中的id_table中名字要一样
};

static struct i2c_client *at24cxx_client;

static int at24cxx_dev_init(void)
{
	struct i2c_adapter *i2c_adap;

	i2c_adap = i2c_get_adapter(0);//这里要实验的EEPROM是挂接在第0条I2C总线上的,所以这里的参数是0
	at24cxx_client = i2c_new_device(i2c_adap, &at24cxx_info);
	i2c_put_adapter(i2c_adap);
	
	return 0;
}

static void at24cxx_dev_exit(void)
{
	i2c_unregister_device(at24cxx_client);
}

module_init(at24cxx_dev_init);
module_exit(at24cxx_dev_exit);
MODULE_LICENSE("GPL");
at24cxx_drv.c :
#include 
#include 
#include 
#include 
#include 
#include 
#include 

static int __devinit at24cxx_probe(struct i2c_client *client,
				  const struct i2c_device_id *id)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}

static int __devexit at24cxx_remove(struct i2c_client *client)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}

static const struct i2c_device_id at24cxx_id_table[] = {
	{ "at24c08", 0 },//用到哪些就声明哪些内容,比如driver_data用不到,所以这里就写0
	{}
};

/* 1. 分配/设置i2c_driver */
static struct i2c_driver at24cxx_driver = {
	.driver	= {
		.name	= "100ask",//在这里,这个名字并不重要,重要的是id_table里面的名字,所以这里可以随便起
		.owner	= THIS_MODULE,
	},
	.probe		= at24cxx_probe,
	.remove		= __devexit_p(at24cxx_remove),
	.id_table	= at24cxx_id_table,
};

static int at24cxx_drv_init(void)
{
	/* 2. 注册i2c_driver */
	i2c_add_driver(&at24cxx_driver);//实际使用时一定要判断返回值
	
	return 0;
}

static void at24cxx_drv_exit(void)
{
	i2c_del_driver(&at24cxx_driver);
}

module_init(at24cxx_drv_init);
module_exit(at24cxx_drv_exit);
MODULE_LICENSE("GPL");

Makefile :

KERN_DIR = /home/samba/linuxKernel_ext4Fs_src/linux-3.5-2015-8

all:
	make -C $(KERN_DIR) M=`pwd` modules 

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order

obj-m	+= at24cxx_dev.o
obj-m	+= at24cxx_drv.o

      分别加载at24cxx_dev.ko和at24cxx_drv.ko,at24cxx_probe函数会被调用,随意修改i2c_board_info结构里面的地址,at24cxx_probe函数照样被调用。所以这种方法不会去真正地验证连接的I2C设备的地址是否为i2c_board_info结构里面的地址。

(2) i2c_new_probed_device方法

     i2c_new_device : 认为设备肯定存在,实验时可以用这种方法改掉I2C设备的地址,改成任意值都是可以的。     i2c_new_probed_device :对于"已经识别出来的设备"(probed_device),才会创建("new")     i2c_new_probed_device函数主要做如下三件事:               probe(adap, addr_list[i])   /* 如果i2c_new_probed_device最后个参数中没有指定probe函数,将使用默认probe函数。不管哪个,它都要确定设备是否真实存在 */               info->addr = addr_list[i];   /* 如果在addr_list中找到了一个地址和现实中连接在I2C总线上的设备匹配,将这个地址放入i2c_board_info结构,并传给i2c_new_device */               i2c_new_device(adap, info);     为了实验,分别编译如下代码,然后分别加载at24cxx_dev.ko和at24cxx_drv.ko,如果在addr_list数组中有一个地址是I2C总线上设备的地址,那么在加载at24cxx_dev.ko驱动模块时,能加载成功,并且加载at24cxx_drv.ko模块后,将调用drv的probe函数。如果没有那个地址,那么在加载at24cxx_dev.ko驱动模块时会失败,提示如下信息:

insmod: can't insert 'at24cxx_dev.ko': No such device

     这就是直接使用i2c_new_device和使用i2c_new_probed_device创建i2c_client的区别。

at24cxx_dev.c :

#include 
#include 
#include 
#include 
#include 
#include 
#include 

static struct i2c_client *at24cxx_client;

//如果挂接在I2C总线上的i2c设备的地址在此数组里面都找不到,将不能加载此驱动模块
static const unsigned short addr_list[] = { 0x60,0x50,I2C_CLIENT_END };

static int at24cxx_dev_init(void)
{
	struct i2c_adapter *i2c_adap;
	struct i2c_board_info at24cxx_info;

	memset(&at24cxx_info, 0, sizeof(struct i2c_board_info));	
	strlcpy(at24cxx_info.type, "at24c08", I2C_NAME_SIZE);

	i2c_adap = i2c_get_adapter(0);
	at24cxx_client = i2c_new_probed_device(i2c_adap, &at24cxx_info, addr_list, NULL);
	i2c_put_adapter(i2c_adap);

	if (at24cxx_client)
		return 0;
	else
		return -ENODEV;
}

static void at24cxx_dev_exit(void)
{
	i2c_unregister_device(at24cxx_client);
}

module_init(at24cxx_dev_init);
module_exit(at24cxx_dev_exit);
MODULE_LICENSE("GPL");

at24cxx_drv.c :

#include 
#include 
#include 
#include 
#include 
#include 
#include 

static int __devinit at24cxx_probe(struct i2c_client *client,
				                   const struct i2c_device_id *id)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}

static int __devexit at24cxx_remove(struct i2c_client *client)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}

static const struct i2c_device_id at24cxx_id_table[] = {
	{ "at24c08", 0 },//用到哪些就声明哪些内容,比如driver_data用不到,所以这里就写0
	{}
};

/* 1. 分配/设置i2c_driver */
static struct i2c_driver at24cxx_driver = {
	.driver	= {
		.name	= "100ask",//在这里,这个名字并不重要,重要的是id_table里面的名字,所以这里可以随便起
		.owner	= THIS_MODULE,
	},
	.probe		= at24cxx_probe,
	.remove		= __devexit_p(at24cxx_remove),
	.id_table	= at24cxx_id_table,
};

static int at24cxx_drv_init(void)
{
	/* 2. 注册i2c_driver */
	i2c_add_driver(&at24cxx_driver);//一定要判断返回值
	
	return 0;
}

static void at24cxx_drv_exit(void)
{
	i2c_del_driver(&at24cxx_driver);
}

module_init(at24cxx_drv_init);
module_exit(at24cxx_drv_exit);
MODULE_LICENSE("GPL");

3.  从用户空间创建设备(详细阅读/Documentation/i2c/instantiating-devices文档)

      执行命令cd /sys/class/i2c-adapter/,可以看到内容i2c-0  i2c-1  i2c-2  i2c-3  i2c-7  i2c-8,说明有多款适配器,即多个I2C控制器,即多条I2C总线。其中EEPROM是挂接在I2C-0下面的(看板子原理图)。
     < 做下面实验需要把内核中静态编译进的drv驱动给去掉,然后加载自己的drv驱动>

创建设备
       echo at24c08 0x50 > /sys/class/i2c-adapter/i2c-0/new_device,导致i2c_new_device被调用,最后drv里的probe函数就不会被调用。如果把地址改为0x51,那么也会在bus的                                                                                                            dev链表中增加一个dev结构,所以这种方法也是不会判断地址是否正确。

删除设备
       echo 0x50 > /sys/class/i2c-adapter/i2c-0/delete_device,导致i2c_unregister_device。       

4.  注册设置i2c_client的第四种方法(此方法交复杂,前三种都不行时才用)

     上述三种方法都是知道I2C设备属于哪个适配器,即知道连在了哪条I2C总线上,如果不知道属于哪个适配器的情况下(4412有多个I2C适配器)就需要用本方法,本方法可以参考例子/drivers/hwmon/lm90.c。

     如果事先并不知道这个I2C设备在哪个适配器上,怎么办?去class表示的所有的适配器上查找。
     有些I2C设备的地址是一样,怎么继续区分它是哪一款?用detect函数。

     此方法过程:   
     由于事先并不知道I2C设备在哪个适配器上,所以去"class表示的那一类"I2C适配器上找,用"detect函数"来确定能否找到"address_list里的设备",如果能找到就调用i2c_new_device来注册i2c_client, 这会和i2c_driver的id_table比较,如果匹配,调用probe。      

     详细代码调用过程:
i2c_add_driver
    i2c_register_driver
        a. at24cxx_driver放入i2c_bus_type的drv链表
           并且从dev链表里取出能匹配的i2c_client并调用probe
           driver_register
           
        b. 对于每一个适配器,调用__process_new_driver(在i2c_bus_type的dev链表中不但要挂i2c_client外,还会挂i2c_adpter。当drv和dev链表比较的时候,drv不会跟i2c_adpter比较,只会跟i2c_client比较,因为i2c_adpter的.type成员可以用来分辨是i2c_adpter还是i2c_client)。
           
           对于每一个适配器,调用它的函数确定address_list里的设备是否存在(确定的方法是给I2C设备发一个地址,看它是否回应ACK,即SDA是否被拉低),即是否支持这个设备。如果存在,再调用detect进一步确定、设置以确定是哪类设备,因为有些设备地址一样,单从地址是没办法分辨是哪类设备的(详细可以阅读内核文档
           /Documentation/i2c/instantiating-devices)。然后i2c_new_device
        /* Walk the adapters that are already present */
        i2c_for_each_dev(driver, __process_new_driver);
            __process_new_driver
                i2c_do_add_adapter
                    /* Detect supported devices on that bus, and instantiate them */
                    i2c_detect(adap, driver);
                        for (i = 0; address_list[i] != I2C_CLIENT_END; i += 1) {
                            err = i2c_detect_address(temp_client, driver);
                                        /* 判断这个设备是否存在:简单的发出S信号确定有ACK */
                                        if (!i2c_default_probe(adapter, addr))
                                            return 0;
                                        
                                        memset(&info, 0, sizeof(struct i2c_board_info));
                                        info.addr = addr;    
                                        
                                        // 设置info.type,调用strlcpy函数拷贝
                                        err = driver->detect(temp_client, &info);
                    
                                        i2c_new_device(最终注册设置i2c_client)   


at24cxx_drv.c :

#include 
#include 
#include 
#include 
#include 
#include 
#include 

static int __devinit at24cxx_probe(struct i2c_client *client,
				  const struct i2c_device_id *id)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}

static int __devexit at24cxx_remove(struct i2c_client *client)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}

static const struct i2c_device_id at24cxx_id_table[] = {
	{ "at24c08", 0 },
	{}
};

static int at24cxx_detect(struct i2c_client *client,
		       struct i2c_board_info *info)
{
	/* 能运行到这里, 表示该addr的设备是存在的,即dev链表中是有这个设备的
	 * 但是有些设备单凭地址无法分辨(A芯片的地址是0x50, B芯片的地址也是0x50,当发0x50后它们都会回应,这个时候还是不能区分到底是A还是B,A和B是不可能同时挂在一条总线上的)
	 * 还需要进一步读写I2C设备来分辨是哪款芯片,比如读A芯片可能有一些值,读B芯片就会有另外一些值
	 * detect就是用来进一步分辨这个芯片是哪一款,并且设置info->type
	 */
	
	printk("at24cxx_detect : addr = 0x%x\n", client->addr);

	/* 这里应该进一步判断是哪一款,这里不用判断 */
	
	strlcpy(info->type, "at24c08", I2C_NAME_SIZE);
	return 0;
}

//0x60、0x50表示I2C设备的地址
static const unsigned short addr_list[] = { 0x60, 0x50, I2C_CLIENT_END };

/* 1. 分配/设置i2c_driver */
static struct i2c_driver at24cxx_driver = {
	.class  = I2C_CLASS_HWMON, /* 表示去哪些适配器上找设备 */
	.driver	= {
		.name	= "100ask",
		.owner	= THIS_MODULE,
	},
	.probe		= at24cxx_probe,
	.remove		= __devexit_p(at24cxx_remove),
	.id_table	= at24cxx_id_table,
	.detect     = at24cxx_detect,  /* 用这个函数来检测设备确实存在 */
	.address_list	= addr_list,   /* 这些设备的地址 */
};

static int at24cxx_drv_init(void)
{
	/* 2. 注册i2c_driver */
	i2c_add_driver(&at24cxx_driver);
	
	return 0;
}

static void at24cxx_drv_exit(void)
{
	i2c_del_driver(&at24cxx_driver);
}

module_init(at24cxx_drv_init);
module_exit(at24cxx_drv_exit);
MODULE_LICENSE("GPL");

三、I2C设备驱动程序的编写

      上面主要介绍了注册i2c_client结构的四种方法,并伴随测试程序at24cxx_dev.c和at24cxx_drv.c。

      然而,我们的目的是应用程序能通过系统调用读写EEPROM存储器,这就需要实现图一中的设备驱动程序。实现的地方就是在probe函数中,当i2c_client结构和i2c_driver结构都注册后,在i2c_bus_type结构的i2c_client链表中就会有dev,i2c_driver链表中有drv,bus的i2c_device_match函数中匹配dev和drv,成功将调用probe函数。在上面测试过程中probe函数基本上什么也没做,原因在于为了测试i2c_client结构的注册,这里的目的是实现设备驱动层,所以会在里面实现注册设备驱动、创建设备节点等操作。

      注意,针对i2c_driver结构的probe成员,也就是上面说的probe函数的参数也是非常有用的。当probe函数成功调用后,它的第一个参数就记录了对应的I2C设备,也就是i2c_client结构体,第二个参数记录对应I2C设备的i2c_device_id。在后面设备驱动读写函数中将调用核心层的读写函数,这些函数的第一个参数就是要知道是哪个I2C设备,即要传入i2c_client结构体。所以,在probe函数中,可以定义结构体指针指向probe函数参数,通过这样的方式记录保存了i2c_client和i2c_device_id。

     这样,图一中的设备驱动层就实现了,而核心层和适配器层都是内核自带的,主要是提供接口供设备驱动使用。下面是应用层操作EEPROM将要用到的所有完整代码:

at24cxx_dev.c :

#include 
#include 
#include 
#include 
#include 
#include 
#include 

//0x50表示I2C设备的地址,一般在	I2C设备芯片手册可以查到
static struct i2c_board_info at24cxx_info = {	
	I2C_BOARD_INFO("at24c08", 0x50),//这个名字要和drv程序中的id_table中名字要一样
};

static struct i2c_client *at24cxx_client;

static int at24cxx_dev_init(void)
{
	struct i2c_adapter *i2c_adap;
	int busNum = 0 ;//把这个总线号改为1,也能成功加载此驱动,原因在于i2c_new_device而不是i2c_new_probed_device方法
	
	printk("at24cxx dev of bus-dev-drv module_init!\n");
	
	i2c_adap = i2c_get_adapter(busNum);//这里要实验的EEPROM是挂接在第0条I2C总线上的,所以这里的参数是0
	if (!i2c_adap) {
		pr_err("failed to get adapter i2c%d\n", busNum);
		return -ENODEV;
	}
	
	at24cxx_client = i2c_new_device(i2c_adap, &at24cxx_info);//设置和注册i2c_client结构体
	if (!at24cxx_client){
		//pr_err("failed to register %s to i2c%d\n",at24cxx_info.type, busNum);
		pr_err("failed to register at24c08 to i2c%d\n",busNum);
		return -ENODEV;
	}	
	
	i2c_put_adapter(i2c_adap);
	
	return 0;
}

static void at24cxx_dev_exit(void)
{
	printk("at24cxx dev of bus-dev-drv module_exit!\n");
	i2c_unregister_device(at24cxx_client);
}


module_init(at24cxx_dev_init);
module_exit(at24cxx_dev_exit);
MODULE_LICENSE("GPL");

at24cxx_drv.c :

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

static int major;
static struct class *class;
static struct i2c_client *at24cxx_client;

/* 传入: buf[0] : addr, 即将访问I2C设备的地址
 * 输出: buf[0] : data
 */
static ssize_t at24cxx_read(struct file * file, char __user *buf, size_t count, loff_t *off)
{
	unsigned char addr, data;
	
	if (copy_from_user(&addr, buf, 1)){
		printk("at24cxx_read: copy_from_user error\n");
		return -EFAULT;
	}
/*
      根据EEPROM读时序,在smbus-protocol文档中找到i2c_smbus_read_byte_data
   函数。
*/	
	data = i2c_smbus_read_byte_data(at24cxx_client, addr);
	if (data < 0) {
		printk("at24cxx_read: i2c_smbus_read_byte_data error\n");
		return data;
	}
	
	if (copy_to_user(buf, &data, 1)){
		printk("at24cxx_read: copy_to_user error\n");
		return -EFAULT;
	}
	
	return 1;
}

/* buf[0] : addr, 即将访问I2C设备的地址
 * buf[1] : data
 */
static ssize_t at24cxx_write(struct file *file, const char __user *buf, size_t count, loff_t *off)
{
	unsigned char ker_buf[2];
	unsigned char addr, data;

	if (copy_from_user(ker_buf, buf, 2)){
		printk("at24cxx_write: copy_from_user error\n");
		return -EFAULT;
	}
	
	addr = ker_buf[0];
	data = ker_buf[1];

	printk("addr = 0x%02x, data = 0x%02x\n", addr, data);

	/*
		   读写操作函数由核心层提供,有两种方式:
		1. SMBUS协议,系统管理总线,内核文档建议用个方式,原因详见文档
		2. I2C_transfer
		   根据smbus-protocol文档,i2c_smbus_write_byte_data函数的时序和
		EEPROM的写操作时序完全一样,所以用它。第一个参数at24cxx_client在
		probe函数中已经做了记录。
	*/
	if (!i2c_smbus_write_byte_data(at24cxx_client, addr, data)){
		return 2;//如果写成功,返回写成功字节
	}
	else
	{
		printk("at24cxx_write: i2c_smbus_write_byte_data error\n");
		return -EIO;	
    }
}

static struct file_operations at24cxx_fops = {
	.owner = THIS_MODULE,
	.read  = at24cxx_read,
	.write = at24cxx_write,
};

static int __devinit at24cxx_probe(struct i2c_client *client,
				  const struct i2c_device_id *id)
{
	struct device *class_dev = NULL;
	
	at24cxx_client = client;//记录i2c_client结构,调用SMBUS方法的时候方便使用
		
	//printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	major = register_chrdev(0, "at24cxx", &at24cxx_fops);
	if (major < 0) { 
		printk("at24cxx_probe: can't register at24cxx char device\n");
		return major;
	}
	
	class = class_create(THIS_MODULE, "at24cxx");//创建类
	if (IS_ERR(class)) {
		printk("at24cxx_probe: class create failed\n");
		unregister_chrdev(major, "at24cxx");
		return PTR_ERR(class);
	}
	
	/* 在类下面创建设备,自动创建设备节点。device_create会调用那个mdev,
	   mdev就会根据环境变量创建设备节点*/
	/* /dev/at24cxx */
	class_dev = device_create(class, NULL,MKDEV(major, 0),NULL,"at24cxx");
	if (IS_ERR(class_dev)) {
		printk("at24cxx_probe: class device create failed\n");
		unregister_chrdev(major, "at24cxx");
		return PTR_ERR(class_dev);
	}
	
	return 0;
}

static int __devexit at24cxx_remove(struct i2c_client *client)
{
	printk("at24cxx drv of bus-dev-drv at24cxx_remove!\n");
	device_destroy(class, MKDEV(major, 0));//删除设备节点
	class_destroy(class);//摧毁类
	unregister_chrdev(major, "at24cxx");
		
	return 0;
}

static const struct i2c_device_id at24cxx_id_table[] = {
	{ "at24c08", 0 },//用到哪些就声明哪些内容,比如driver_data用不到,所以这里就写0
	{}
};

/* 1. 分配/设置i2c_driver */
static struct i2c_driver at24cxx_driver = {
	.driver	= {
		.name	= "100ask",//在这里,这个名字并不重要,重要的是id_table里面的名字,所以这里可以随便起
		.owner	= THIS_MODULE,
	},
	.probe		= at24cxx_probe,
	.remove		= __devexit_p(at24cxx_remove),
	.id_table	= at24cxx_id_table,
};

static int at24cxx_drv_init(void)
{
	int ret;
	
	printk("at24cxx drv of bus-dev-drv module_init!\n");
	
	/* 2. 注册i2c_driver */
	ret = i2c_add_driver(&at24cxx_driver);
	if (ret != 0){
		pr_err("Failed to register at24cxx I2C driver: %d\n", ret);
	}
		
	return 0;
}

static void at24cxx_drv_exit(void)
{
	printk("at24cxx drv of bus-dev-drv module_exit!\n");
	i2c_del_driver(&at24cxx_driver);
}

module_init(at24cxx_drv_init);
module_exit(at24cxx_drv_exit);
MODULE_LICENSE("GPL");

4th_device_driver_i2c_test.c:

#include 
#include 
#include 
#include 
#include 
#include 

/* i2c_test r addr
 * i2c_test w addr val
 */

void print_usage(char *file)
{
	printf("%s r addr\n", file);
	printf("%s w addr val\n", file);
}

int main(int argc, char **argv)
{
	int fd;
	unsigned char buf[2];
	
	if ((argc != 3) && (argc != 4))
	{
		print_usage(argv[0]);
		return -1;
	}

	fd = open("/dev/at24cxx", O_RDWR);
	if (fd < 0)
	{
		printf("can't open /dev/at24cxx\n");
		return -1;
	}

	if (strcmp(argv[1], "r") == 0)
	{
		buf[0] = strtoul(argv[2], NULL, 0);
		read(fd, buf, 1);
		printf("data: %c, %d, 0x%2x\n", buf[0], buf[0], buf[0]);
	}
	else if ((strcmp(argv[1], "w") == 0) && (argc == 4))
	{
		buf[0] = strtoul(argv[2], NULL, 0);
		buf[1] = strtoul(argv[3], NULL, 0);
		if (write(fd, buf, 2) != 2)
			printf("write err, addr = 0x%02x, data = 0x%02x\n", buf[0], buf[1]);
	}
	else
	{
		print_usage(argv[0]);
		return -1;
	}
	
	return 0;
}

四、不自己写驱动直接访问

      在图一中,核心层已经提供了统一的I2C设备操作函数:
(1)SMBUS协议方式(SMBUS协议,是I2C协议的子集,某些设备只支持此协议,所以内核文档smbus-protocol建议优先使用此方法)
(2)i2c_transfer方式(I2C协议)
      应用程序可以直接通过上述两种方式访问I2C设备,因为内核已经封装了一套驱动程序,应用程序只需要使用那套驱动就可以访问I2C设备了。应用程序设计方法可以参考文档dev-interface,这里给出测试代码。

Device Drivers
     I2C support
        <*>   I2C device interface

i2c_usr_test.c :

#include 
#include 
#include 
#include 
#include 
#include 
#include "i2c-dev.h"

/* i2c_usr_test   r addr
 * i2c_usr_test   w addr val
 */

void print_usage(char *file)
{
	printf("%s   r addr\n", file);
	printf("%s   w addr val\n", file);
}

int main(int argc, char **argv)
{
	int fd;
	unsigned char addr, data;
	int dev_addr;
	
	if ((argc != 5) && (argc != 6))
	{
		print_usage(argv[0]);
		return -1;
	}

	fd = open(argv[1], O_RDWR);
	if (fd < 0)
	{
		printf("can't open %s\n", argv[1]);
		return -1;
	}

	dev_addr = strtoul(argv[2], NULL, 0);
	if (ioctl(fd, I2C_SLAVE, dev_addr) < 0)
	{    
		/* ERROR HANDLING; you can check errno to see what went wrong */    
		printf("set addr error!\n");
		return -1;
	}

	if (strcmp(argv[3], "r") == 0)
	{
		addr = strtoul(argv[4], NULL, 0);
		
		data = i2c_smbus_read_word_data(fd, addr);
			
		printf("data: %c, %d, 0x%2x\n", data, data, data);
	}
	else if ((strcmp(argv[3], "w") == 0) && (argc == 6))
	{
		addr = strtoul(argv[4], NULL, 0);
		data = strtoul(argv[5], NULL, 0);
		i2c_smbus_write_byte_data(fd, addr, data);		
	}
	else
	{
		print_usage(argv[0]);
		return -1;
	}
	
	return 0;
}

五、编写"总线(适配器adapter)"驱动

       应用程序调用设备驱动程序,设备驱动程序会调用核心层提供的某些函数(如SMBUS相关函数),核心层的这些函数最终会调用到适配器层的相关函数,这些函数是处理器I2C接口的相关硬件操作,它们会根据I2C协议向I2C设备发出相应信号达到控制I2C设备的目的。
      这里设计的驱动即设计适配器驱动程序,先找到Linux3.5中内核自带的适配器驱动。
在make menuconfig后,找到如下选项:
Device Drivers
     I2C support
         I2C Hardware Bus support
             < > S3C2410 I2C Driver
       选中S3C2410 I2C Driver,按下键盘上的h键,找到第一行出现的那个宏 CONFIG_I2C_S3C2410 , 然后在内核源码目录下执行 grep "CONFIG_I2C_S3C2410" -R *后,找到如下信息:
drivers/i2c/busses/Makefile:obj-$(CONFIG_I2C_S3C2410)   += i2c-s3c2410.o
      从上面信息中就可以知道I2C总线适配器对应的驱动程序文件为i2c-s3c2410.c文件。然后分析此文件,分析类似驱动程序都是先从入口函数看。下面给出测试代码(基于2440):

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

//#define PRINTK printk
#define PRINTK(...) 

enum s3c24xx_i2c_state {
	STATE_IDLE,
	STATE_START,
	STATE_READ,
	STATE_WRITE,
	STATE_STOP
};

struct s3c2440_i2c_regs {
	unsigned int iiccon;
	unsigned int iicstat;
	unsigned int iicadd;
	unsigned int iicds;
	unsigned int iiclc;
};

struct s3c2440_i2c_xfer_data {
	struct i2c_msg *msgs;
	int msn_num;
	int cur_msg;
	int cur_ptr;
	int state;
	int err;
	wait_queue_head_t wait;
};

static struct s3c2440_i2c_xfer_data s3c2440_i2c_xfer_data;

static struct s3c2440_i2c_regs *s3c2440_i2c_regs;

static void s3c2440_i2c_start(void)
{
	s3c2440_i2c_xfer_data.state = STATE_START;
	
	if (s3c2440_i2c_xfer_data.msgs->flags & I2C_M_RD) /* 读 */
	{
		s3c2440_i2c_regs->iicds		 = s3c2440_i2c_xfer_data.msgs->addr << 1;
		s3c2440_i2c_regs->iicstat 	 = 0xb0;	// 主机接收,启动
	}
	else /* 写 */
	{
		s3c2440_i2c_regs->iicds		 = s3c2440_i2c_xfer_data.msgs->addr << 1;
		s3c2440_i2c_regs->iicstat    = 0xf0; 		// 主机发送,启动
	}
}

static void s3c2440_i2c_stop(int err)
{
	s3c2440_i2c_xfer_data.state = STATE_STOP;
	s3c2440_i2c_xfer_data.err   = err;

	PRINTK("STATE_STOP, err = %d\n", err);


	if (s3c2440_i2c_xfer_data.msgs->flags & I2C_M_RD) /* 读 */
	{
		// 下面两行恢复I2C操作,发出P信号
		s3c2440_i2c_regs->iicstat = 0x90;
		s3c2440_i2c_regs->iiccon  = 0xaf;
		ndelay(50);  // 等待一段时间以便P信号已经发出
	}
	else /* 写 */
	{
		// 下面两行用来恢复I2C操作,发出P信号
		s3c2440_i2c_regs->iicstat = 0xd0;
		s3c2440_i2c_regs->iiccon  = 0xaf;
		ndelay(50);  // 等待一段时间以便P信号已经发出
	}

	/* 唤醒 */
	wake_up(&s3c2440_i2c_xfer_data.wait);
	
}

static int s3c2440_i2c_xfer(struct i2c_adapter *adap,
			struct i2c_msg *msgs, int num)
{
	unsigned long timeout;
	
	/* 把num个msg的I2C数据发送出去/读进来 */
	s3c2440_i2c_xfer_data.msgs    = msgs;
	s3c2440_i2c_xfer_data.msn_num = num;
	s3c2440_i2c_xfer_data.cur_msg = 0;
	s3c2440_i2c_xfer_data.cur_ptr = 0;
	s3c2440_i2c_xfer_data.err     = -ENODEV;

	s3c2440_i2c_start();

	/* 休眠 */
	timeout = wait_event_timeout(s3c2440_i2c_xfer_data.wait, (s3c2440_i2c_xfer_data.state == STATE_STOP), HZ * 5);
	if (0 == timeout)
	{
		printk("s3c2440_i2c_xfer time out\n");
		return -ETIMEDOUT;
	}
	else
	{
		return s3c2440_i2c_xfer_data.err;
	}
}

static u32 s3c2440_i2c_func(struct i2c_adapter *adap)
{
	return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_PROTOCOL_MANGLING;
}

static const struct i2c_algorithm s3c2440_i2c_algo = {
//	.smbus_xfer     = ,
	.master_xfer	= s3c2440_i2c_xfer,
	.functionality	= s3c2440_i2c_func,
};

/* 1. 分配/设置i2c_adapter
 */
static struct i2c_adapter s3c2440_i2c_adapter = {
 .name			 = "s3c2440_100ask",
 .algo			 = &s3c2440_i2c_algo,
 .owner 		 = THIS_MODULE,
};

static int isLastMsg(void)
{
	return (s3c2440_i2c_xfer_data.cur_msg == s3c2440_i2c_xfer_data.msn_num - 1);
}

static int isEndData(void)
{
	return (s3c2440_i2c_xfer_data.cur_ptr >= s3c2440_i2c_xfer_data.msgs->len);
}

static int isLastData(void)
{
	return (s3c2440_i2c_xfer_data.cur_ptr == s3c2440_i2c_xfer_data.msgs->len - 1);
}

static irqreturn_t s3c2440_i2c_xfer_irq(int irq, void *dev_id)
{
	unsigned int iicSt;
    iicSt  = s3c2440_i2c_regs->iicstat; 

	if(iicSt & 0x8){ printk("Bus arbitration failed\n\r"); }

	switch (s3c2440_i2c_xfer_data.state)
	{
		case STATE_START : /* 发出S和设备地址后,产生中断 */
		{
			PRINTK("Start\n");
			/* 如果没有ACK, 返回错误 */
			if (iicSt & S3C2410_IICSTAT_LASTBIT)
			{
				s3c2440_i2c_stop(-ENODEV);
				break;
			}

			if (isLastMsg() && isEndData())
			{
				s3c2440_i2c_stop(0);
				break;
			}

			/* 进入下一个状态 */
			if (s3c2440_i2c_xfer_data.msgs->flags & I2C_M_RD) /* 读 */
			{
				s3c2440_i2c_xfer_data.state = STATE_READ;
				goto next_read;
			}
			else
			{
				s3c2440_i2c_xfer_data.state = STATE_WRITE;
			}	
		}

		case STATE_WRITE:
		{
			PRINTK("STATE_WRITE\n");
			/* 如果没有ACK, 返回错误 */
			if (iicSt & S3C2410_IICSTAT_LASTBIT)
			{
				s3c2440_i2c_stop(-ENODEV);
				break;
			}

			if (!isEndData())  /* 如果当前msg还有数据要发送 */
			{
				s3c2440_i2c_regs->iicds = s3c2440_i2c_xfer_data.msgs->buf[s3c2440_i2c_xfer_data.cur_ptr];
				s3c2440_i2c_xfer_data.cur_ptr++;
				
				// 将数据写入IICDS后,需要一段时间才能出现在SDA线上
				ndelay(50);	
				
				s3c2440_i2c_regs->iiccon = 0xaf;		// 恢复I2C传输
				break;				
			}
			else if (!isLastMsg())
			{
				/* 开始处理下一个消息 */
				s3c2440_i2c_xfer_data.msgs++;
				s3c2440_i2c_xfer_data.cur_msg++;
				s3c2440_i2c_xfer_data.cur_ptr = 0;
				s3c2440_i2c_xfer_data.state = STATE_START;
				/* 发出START信号和发出设备地址 */
				s3c2440_i2c_start();
				break;
			}
			else
			{
				/* 是最后一个消息的最后一个数据 */
				s3c2440_i2c_stop(0);
				break;				
			}

			break;
		}

		case STATE_READ:
		{
			PRINTK("STATE_READ\n");
			/* 读出数据 */
			s3c2440_i2c_xfer_data.msgs->buf[s3c2440_i2c_xfer_data.cur_ptr] = s3c2440_i2c_regs->iicds;			
			s3c2440_i2c_xfer_data.cur_ptr++;
next_read:
			if (!isEndData()) /* 如果数据没读写, 继续发起读操作 */
			{
				if (isLastData())  /* 如果即将读的数据是最后一个, 不发ack */
				{
					s3c2440_i2c_regs->iiccon = 0x2f;   // 恢复I2C传输,接收到下一数据时无ACK
				}
				else
				{
					s3c2440_i2c_regs->iiccon = 0xaf;   // 恢复I2C传输,接收到下一数据时发出ACK
				}				
				break;
			}
			else if (!isLastMsg())
			{
				/* 开始处理下一个消息 */
				s3c2440_i2c_xfer_data.msgs++;
				s3c2440_i2c_xfer_data.cur_msg++;
				s3c2440_i2c_xfer_data.cur_ptr = 0;
				s3c2440_i2c_xfer_data.state = STATE_START;
				/* 发出START信号和发出设备地址 */
				s3c2440_i2c_start();
				break;
			}
			else
			{
				/* 是最后一个消息的最后一个数据 */
				s3c2440_i2c_stop(0);
				break;								
			}
			break;
		}

		default: break;
	}

	/* 清中断 */
	s3c2440_i2c_regs->iiccon &= ~(S3C2410_IICCON_IRQPEND);

	return IRQ_HANDLED;	
}

/*
 * I2C初始化
 */
static void s3c2440_i2c_init(void)
{
	struct clk *clk;

	clk = clk_get(NULL, "i2c");
	clk_enable(clk);
	
    // 选择引脚功能:GPE15:IICSDA, GPE14:IICSCL
    s3c_gpio_cfgpin(S3C2410_GPE(14), S3C2410_GPE14_IICSCL);
	s3c_gpio_cfgpin(S3C2410_GPE(15), S3C2410_GPE15_IICSDA);

    /* bit[7] = 1, 使能ACK
     * bit[6] = 0, IICCLK = PCLK/16
     * bit[5] = 1, 使能中断
     * bit[3:0] = 0xf, Tx clock = IICCLK/16
     * PCLK = 50MHz, IICCLK = 3.125MHz, Tx Clock = 0.195MHz
     */
    s3c2440_i2c_regs->iiccon = (1<<7) | (0<<6) | (1<<5) | (0xf);  // 0xaf

    s3c2440_i2c_regs->iicadd  = 0x10;     // S3C24xx slave address = [7:1]
    s3c2440_i2c_regs->iicstat = 0x10;     // I2C串行输出使能(Rx/Tx)
}

static int i2c_bus_s3c2440_init(void)
{
	/* 2. 硬件相关的设置 */
	s3c2440_i2c_regs = ioremap(0x54000000, sizeof(struct s3c2440_i2c_regs));
	
	s3c2440_i2c_init();

	request_irq(IRQ_IIC, s3c2440_i2c_xfer_irq, 0, "s3c2440-i2c", NULL);

	init_waitqueue_head(&s3c2440_i2c_xfer_data.wait);
	
	/* 3. 注册i2c_adapter */
	i2c_add_adapter(&s3c2440_i2c_adapter);
	
	return 0;
}

static void i2c_bus_s3c2440_exit(void)
{
	i2c_del_adapter(&s3c2440_i2c_adapter);	
	free_irq(IRQ_IIC, NULL);
	iounmap(s3c2440_i2c_regs);
}

module_init(i2c_bus_s3c2440_init);
module_exit(i2c_bus_s3c2440_exit);
MODULE_LICENSE("GPL");

六、总结

      通过上述分析,可以总结出一个设计驱动程序的方法,这个方法适用于Linux内核的各个版本:

1.  如果要设计I2C设备驱动,看 Documentation/i2c目录下的相关内核文档;I2C协议;处理器的I2C接口;掌握bus-dev-drv模型;掌握驱动程序设计相关知识。

2.  实现bus-dev-drv模型驱动。比如要先注册i2c_client,而内核文档中instantiating-devices(构造设备)文件就说明了方法。然后根据方法,搜索内核中对应的例子,模仿就可以        设计出需要的驱动;然后要注册i2c_driver,也是模仿其他如何设计即可。总之,Linux是非常庞大的系统,里面有很多例子可以参考的。

3. 理清思路,搞清楚框架中哪些工作是需要做的,哪些是不需要做的。


    《smbus-protocol》中介绍了核心层各种读写接口,这些接口被设备驱动的读写接口调用。根据此文档,下面举例说明这些接口的应用:


SMBus Read Byte:  i2c_smbus_read_byte_data()
============================================

This reads a single byte from a device, from a designated register.
The register is specified through the Comm byte.

S Addr Wr [A] Comm [A] S Addr Rd [A] [Data] NA P

   

      i2c_smbus_read_byte_data函数在Linux内核中原型如下:

s32 i2c_smbus_read_byte_data(const struct i2c_client *client, u8 command)
{
    union i2c_smbus_data data;
    int status;

    status = i2c_smbus_xfer(client->adapter, client->addr, client->flags,
                I2C_SMBUS_READ, command,
                I2C_SMBUS_BYTE_DATA, &data);
    return (status < 0) ? status : data.byte;
}

      从字面意思上就可以猜测i2c_smbus_read_byte_data函数是从I2C的子集smbus上读取一个字节的数据,它的第一个参数为i2c_client结构体,这个结构在probe函数被调用后就被记录了(记录方法就是用一个i2c_client结构体指针指向probe函数的参数),第二个参数的含义是明确读取寄存器的地址,比如用户要读取EEPROM地址为1地方的数据,那么这里就把1传给内核空间并传给此函数。i2c_smbus_read_byte_data函数的返回值即从它的第二个参数Comm为地址的地方读出的数据。

     然而,对于4412上的I2C总线0上接的EEPROM设备来说,为什么使用这个函数呢?原因在于这个函数的时序和EEPROM芯片手册中读时序类似,下图是EEPROM读时序:

Linux3.5下I2C设备驱动程序_第2张图片

       根据上图,对照smbus-protocol文档中对i2c_smbus_read_byte_data()函数的介绍,即S Addr Wr [A] Comm [A] S Addr Rd [A] [Data] NA P,下面是详细说明:

       第一个为S表示起始信号;

       第二个为Addr表示I2C设备的地址,这里就是EEPOM的地址0x50;

       第三个为Wr表示操作方向为写;

       第四个为[A]表示EERPOM给处理器I2C控制器的回应,实际上就是把SDA拉低而已;

       第五个为Comm,写地址。表示要读取EEPROM中哪个地方的数据,是个地址,也是i2c_smbus_read_byte_data函数的第二个参数,

                     一般由用户空间提供;

       第六个为[A]表示EERPOM给处理器I2C控制器的回应,实际上就是把SDA拉低而已;

       第七个为S表示重新发送起始信号;

       第八个为Addr表示I2C设备的地址,这里就是EEPOM的地址0x50;

       第九个为Rd表示操作方向为读;

       第十个为[A]表示EERPOM给处理器I2C控制器的回应,实际上就是把SDA拉低而已;

       第十一个为[Data],表示从EEPROM中Comm地址处读取的数据;

       第十二个为NA,表示上述读取的数据为最后一个数据,这个时候处理器不用给eeprom回应,即NO ACK;    

       第十三个为P,表示由主机给出的停止信号,结束对EEPROM的操作。


















你可能感兴趣的:(Linux系统)