在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和方法一相同