Linux中I2C子系统框架

在linux中的i2c分层结构,也是代码的分层结构。



这张图片是从程序调用角度的i2c结构图,其中左侧的/sys和/dev也就是用户访问设备的方式。



代码调用层次图 

  有时候代码比任何文字描述都来得直接,但是过多的代码展示反而让人觉得枯燥。这个时候,需要一幅图来梳理一下上面的内容


上面这些代码的展示是告诉我们:linux内核和芯片提供商为我们的的驱动程序提供了 i2c驱动的框架,以及框架底层与硬件相关的代码的实现。
  剩下的就是针对挂载在i2c两线上的i2c设备了device,而编写的即具体设备驱动了,这里的设备就是硬件接口外挂载的设备,而非硬件接口本身(soc硬件接口本身的驱动可以理解为总线驱动)


架构层次分类

  第一层:提供i2c adapter的硬件驱动,探测、初始化i2c adapter(如申请i2c的io地址和中断号),驱动soc控制的i2c adapter在硬件上产生信号(start、stop、ack)以及处理i2c中断。覆盖图中的硬件实现层

  第二层:提供i2c adapter的algorithm,用具体适配器的xxx_xferf()函数来填充i2c_algorithm的master_xfer函数指针,并把赋值后的i2c_algorithm再赋值给i2c_adapter的algo指针。覆盖图中的访问抽象层、i2c核心层

  第三层:实现i2c设备驱动中的i2c_driver接口,用具体的i2c device设备的attach_adapter()、detach_adapter()方法赋值给i2c_driver的成员函数指针。实现设备device与总线(或者叫adapter)的挂接。覆盖图中的driver驱动层

  第四层:实现i2c设备所对应的具体device的驱动,i2c_driver只是实现设备与总线的挂接,而挂接在总线上的设备则是千差万别的,所以要实现具体设备device的write()、read()、ioctl()等方法,赋值给file_operations,然后注册字符设备(多数是字符设备)。覆盖图中的driver驱动层

  第一层和第二层又叫i2c总线驱动(bus),第三第四属于i2c设备驱动(device driver)。

  在linux驱动架构中,几乎不需要驱动开发人员再添加bus,因为linux内核几乎集成所有总线bus,如usb、pci、i2c等等。并且总线bus中的(与特定硬件相关的代码)已由芯片提供商编写完成,例如三星的s3c-2440平台i2c总线bus为/drivers/i2c/buses/i2c-s3c2410.c

        第三第四层与特定device相干的就需要驱动工程师来实现了。

 

设备驱动

设备驱动就是特定的器件的设备驱动,其中有两个主要的结构体

i2c_driver(i2c设备驱动结构体)

struct i2c_driver {  
	unsigned int class;  
	int (*attach_adapter)(struct i2c_adapter *);//依附i2c_adapter函数指针  
	int (*detach_adapter)(struct i2c_adapter *);//脱离i2c_adapter函数指针  
	int (*probe)(struct i2c_client *, const struct i2c_device_id *);  
	int (*remove)(struct i2c_client *);  
	void (*shutdown)(struct i2c_client *);  
	int (*suspend)(struct i2c_client *, pm_message_t mesg);  
	int (*resume)(struct i2c_client *);  
	void (*alert)(struct i2c_client *, unsigned int data);  
	int (*command)(struct i2c_client *client, unsigned int cmd, void*arg);//命令列表  
	struct device_driver driver;  
	const struct i2c_device_id *id_table;//该驱动所支持的设备ID表  
	int (*detect)(struct i2c_client *, struct i2c_board_info *);  
	const unsigned short *address_list;  
	struct list_head clients;  
}; 

i2c_client(i2c设备描述结构体)

struct i2c_client {  
	unsigned short flags;//标志    
	unsigned short addr; //低7位为芯片地址    
	char name[I2C_NAME_SIZE];//设备名称  
	struct i2c_adapter *adapter;//依附的i2c_adapter  
	struct i2c_driver *driver;//依附的i2c_driver   
	struct device dev;//设备结构体    
	int irq;//设备所使用的结构体    
	struct list_head detected;//链表头  
};  

添加设备驱动函数

static inline int i2c_add_driver(struct i2c_driver *driver)  
{  
    return i2c_register_driver(THIS_MODULE, driver);  
}  

删除设备驱动函数

void i2c_del_driver (struct i2c_driver * driver);

i2c-core核心层

i2c总线的初始化、注册和适配器的添加和注销

适配器驱动

I2c控制器的驱动实现


I2c消息结构体

struct i2c_msg {  
	__u16 addr;     /* slave address            */  
	__u16 flags;  
	#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_NOSTART       0x4000   /* if I2C_FUNC_PROTOCOL_MANGLING */  
	#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;      /* msg length               */  
	__u8 *buf;      /* pointer to msg data           */  
};  

i2c核心提供api函数

i2c传输、发送和接收

int i2c_master_send(struct i2c_client *client,const char *buf ,int count);
int i2c_master_recv(struct i2c_client *client, char *buf ,int count);
int i2c_transfer(struct i2c_adapter * adap, struct i2c_msg *msgs, int num);

i2c_transfer ()函数用于进行I2C适配器和I2C设备之间的一组消息交互,i2c_master_send()函数和i2c_master_recv()函数内部会调用i2c_transfer()函数分别完成一条写消息和一条读消息。

int i2c_transfer(struct i2c_adapter * adap, struct i2c_msg *msgs, int num)  
{  
    int ret;  
    if (adap->algo->master_xfer) {//如果master_xfer函数存在,则调用,否则返回错误  
        ret = adap->algo->master_xfer(adap,msgs,num);//这个函数在硬件相关的代码中给algorithm赋值  
        return ret;  
    } else {  
        return -ENOSYS;  
    }  
}  
int i2c_master_send(struct i2c_client *client,const char *buf ,int count) 
{ 
    int ret; 
    struct i2c_adapter *adap=client->adapter;  // 获取adapter信息 
    struct i2c_msg msg;                        // 定义一个临时的数据包 
 
    msg.addr = client->addr;                   // 将从机地址写入数据包 
    msg.flags = client->flags & I2C_M_TEN;     // 将从机标志并入数据包 
    msg.len = count;                           // 将此次发送的数据字节数写入数据包 
    msg.buf = (char *)buf;                     // 将发送数据指针写入数据包 
 
    ret = i2c_transfer(adap, &msg, 1);         // 调用平台接口发送数据 
 
    /* If everything went ok (i.e. 1 msg transmitted), return #bytes
       transmitted, else error code. */ 
    return (ret == 1) ? count : ret;           // 如果发送成功就返回字节数 
} 
EXPORT_SYMBOL(i2c_master_send);
int i2c_master_recv(struct i2c_client *client, char *buf ,int count) 
{ 
    struct i2c_adapter *adap=client->adapter;  // 获取adapter信息 
    struct i2c_msg msg;                        // 定义一个临时的数据包 
    int ret; 
 
    msg.addr = client->addr;                   // 将从机地址写入数据包 
    msg.flags = client->flags & I2C_M_TEN;     // 将从机标志并入数据包 
    msg.flags |= I2C_M_RD;                     // 将此次通信的标志并入数据包 
    msg.len = count;                           // 将此次接收的数据字节数写入数据包 
    msg.buf = buf; 
 
    ret = i2c_transfer(adap, &msg, 1);         // 调用平台接口接收数据 
 
    /* If everything went ok (i.e. 1 msg transmitted), return #bytes
       transmitted, else error code. */ 
    return (ret == 1) ? count : ret;           // 如果接收成功就返回字节数 
} 
EXPORT_SYMBOL(i2c_master_recv);

I2c适配器结构体i2c_adapter

struct i2c_adapter {  
	struct module *owner;//所属模块  
	unsigned int id;//algorithm的类型,定义于i2c-id.h,  
	unsigned int class;      
	const struct i2c_algorithm *algo; //总线通信方法结构体指针  
	void *algo_data;//algorithm数据  
	/* --- administration stuff. */  
	int (*client_register)(struct i2c_client *);  
	int (*client_unregister)(struct i2c_client *);  
	
	/* data fields that are valid for all devices   */  
	struct rt_mutex bus_lock;//控制并发访问的自旋锁  
	struct mutex clist_lock;  
	int timeout;     
	int retries;//重试次数  
	struct device dev; //适配器设备   
	struct class_device class_dev;  /* the class device */  
	int nr;  
	struct list_head clients;  
	struct list_head list;  
	char name[I2C_NAME_SIZE];//适配器名称  
	struct completion dev_released;//用于同步  
	struct list_head userspace_clients;//client链表头  
};  

编写驱动需要完成的工作

  编写具体的I2C驱动时,工程师需要处理的主要工作如下:

  1).提供I2C适配器的硬件驱动,探测,初始化I2C适配器(如申请I2C的I/O地址和中断号),驱动CPU控制的I2C适配器从硬件上产生。

  2).提供I2C控制的algorithm, 用具体适配器的xxx_xfer()函数填充i2c_algorithm的master_xfer指针,并把i2c_algorithm指针赋给i2c_adapter的algo指针。

  3).实现I2C设备驱动中的i2c_driver接口,用具体yyy的yyy_probe(),yyy_remove(),yyy_suspend(),yyy_resume()函数指针和i2c_device_id设备ID表赋给i2c_driver的probe,remove,suspend,resume和id_table指针。

  4).实现I2C设备所对应类型的具体驱动,i2c_driver只是实现设备与总线的挂接。

上面的工作中前两个属于I2C总线驱动,后面两个属于I2C设备驱动。

AT24C02驱动示例

客户驱动程序开发的一般步骤

(1)注册板载i2c设备信息

(2)定义i2c驱动设备id

(3)定义i2c_driver结构并完成其相应函数

(4)模块初始化时添加/撤销时删除i2c_driver

(5)/dev entry 访问方法 /sysfs访问方法

其中1,2为device,3,4,5为driver

(1)注册板载信息

在mach-smdk2440.c文件中静态声明一个I2C设备

static struct i2c_board_info i2c_devices[] __initdata = {  
    {I2C_BOARD_INFO("24c02", 0x50), },  
     {}  
}; 

在smdk2440_machine_init()函数中,向总线注册I2C设备信息:

i2c_register_board_info(0,i2c_devices,ARRAY_SIZE(i2c_devices)); 

(2)定义i2c驱动设备id:

static struct i2c_device_id foo_idtable[] = {  
    { “24c01", 0x51 },  
    {“20402”,0x50 },  
    {}  
};  

(3)定义i2c_driver结构并完成其相应函数:

static struct i2c_driver my_i2c_driver = {  
    .driver = {  
		.name = "i2c_demo", 
		.owner = THIS_MODULE,  
    },  
    .probe = my_i2c_probe,  
    .remove = my_i2c_remove,  
    .id_table = my_ids,  
};  

(4)模块初始化时添加/撤销时删除i2c_driver

static int __init  my_i2c_client(void)  
{  
   return i2c_add_driver(&my_i2c_driver);  
}  
static void __exit my_i2c_exit(void)  
{  
   i2c_del_driver(&my_i2c_driver);  
}  

(5)使用/dev entry 访问方法

注册字符设备

register_chrdev(I2C_MAJOR,DEVICE_NAME,&i2c_fops); 

创建类

class_create(THIS_MODULE, DEVICE_NAME); 

在/dev下创建设备节点 

device_create(my_dev_class,&client->dev,MKDEV(I2C_MAJOR, 0), NULL, DEVICE_NAME);  

在/doc/Documentation/i2c/instantiating-devices文档中提供了4中方法实例化一个iic设备

上面提到的是一个,下面介绍另外三个


第二种方法

(1)      定义i2c设备信息,i2c_board_info结构体

(2)      在device文件中init函数中使用i2c_new_device或者i2c_new_probed_device()

i2c_new_device        : 认为设备肯定存在

i2c_new_probed_device :对于"已经识别出来的设备"(probed_device),才会创建("new")

int at24cxx_dev_init(void)
{
	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_put_adapter(i2c_adap);
	return 0;
}
void at24cxx_dev_exit(void)
{
	i2c_unregister_device(at24cxx_client);
}

3,4,5和方法一一样


第三种方法

从用户空间创建设备

创建设备

echo name addr >/sys/bus/i2c/devices/i2c-3/new_device

如echo eeprom 0x50 > /sys/class/i2c-adapter/i2c-0/new_device

删除设备

如echo 0x50 > /sys/class/i2c-adapter/i2c-0/delete_device


第四种方法

Device用1,2完成

(3)      在driver中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,   /* 这些设备的地址 */
};

4,5和方法一相同


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值