一、I2C体系结构
Linux的I2C体系结构分为3个组成部分:I2C核心、I2C总线驱动、I2C设备,如下图所示。I2C核心提供总线驱动和设备驱动的注册、注销方法。它以通用的,与平台无关的接口实现了I2C中设备与适配器的沟通。I2C总线驱动对硬件体系结构中适配器的实现,主要包括适配器i2c_adapter、适配器通信算法i2c_algorithm,如果CPU集成了I2C控制器并且linux内核支持这个CPU,那么总线驱动就不用管,比如S3C2440就属于这类情况,在后文中我们将分析它的总线驱动;I2C设备驱动是具体的一个设备(如AT24C02),挂接在CPU控制的I2C适配器的设备驱动,有了这部分驱动才能使用控制器操作该设备,设备驱动主要包括i2c_driver 和i2c_client数据结构。填充i2c_driver 结构体并实现其本身对应设备类型的驱动。
二、分析I2C核心
linux-2.6.32.2/drivers/i2c/i2c-core.c //i2c子系统的公用代码,驱动开发者只需要用而不需要修改
1. 初始化i2c子系统
static int __init i2c_init(void)//在系统启动模块加载阶段中调用来初始化i2c子系统
1.1 bus_register(&i2c_bus_type);//注册一条IIC总线,注册适配器、IIC设备、IIC设备驱动都会连接到这条总线上
1.2 class_compat_register("i2c-adapter");//注册适配器类,用于实现文件系统的部分功能 (驱动人员不用关心)
1.3 retval = i2c_add_driver(&dummy_driver);//将一个空驱动注册到总线上 (驱动人员不用关心)
struct bus_type i2c_bus_type = {
.name = "i2c",
.match = i2c_device_match,
.probe = i2c_device_probe,
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
.suspend = i2c_device_suspend,
.resume = i2c_device_resume,
};
2. 卸载i2c子系统
static void __exit i2c_exit(void)
i2c_del_driver(&dummy_driver);//注销空驱动
bus_unregister(&i2c_bus_type);//注销一条IIC总线
3.函数接口
3.1适配器i2c_adapter的添加、删除
int i2c_add_adapter(struct i2c_adapter *adapter)
int i2c_add_numbered_adapter(struct i2c_adapter *adap)//两个接口都是向IIC子系统添加适配器结构体,该结构体在前面要进行分配和初始化
使用:在总线驱动probe()被调用。如:s3c24xx_i2c_probe,先设置adap.name、adap.owner、adap.algo、adap.retries adap.class、adap.algo_data、adap.dev.parent、adap.nr等成员
int i2c_del_adapter(struct i2c_adapter *adap)//删除上面两个接口添加的适配器结构体
3.2增加/删除i2c_driver
int i2c_register_driver(struct module *owner, struct i2c_driver *driver);
int i2c_del_driver(struct i2c_driver *driver);
inline int i2c_add_driver(struct i2c_driver *driver);
使用:在设备驱动i2c_dev_init被调用。如i2c-dev.c中的i2c_dev_init()函数。首先要定义一个i2c_driver结构体
static struct i2c_driver i2cdev_driver = { .driver = { .name = "dev_driver", }, .attach_adapter = i2cdev_attach_adapter, .detach_adapter = i2cdev_detach_adapter,};
在i2c_register_driver设置driver->driver.owner、driver->driver.bus
3.3 i2c_client依附/脱离
int i2c_attach_client(struct i2c_client *client);
int i2c_detach_client(struct i2c_client *client);
//当一个具体的client被侦测到并被关联的时候,设备和sysfs文件将被注册。相反地,在client被取消关联的时候,sysfs文件和设备也被注销。
3.4i2c传输、发送和接收
int i2c_transfer(struct i2c_adapter * adap, struct i2c_msg *msgs, int num);
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);
i2c_transfer()函数用于进行I2C适配器和I2C设备之间的一组消息交互,i2c_master_send()函数和i2c_master_recv()函数内部会调用i2c_transfer()函数分别完成一条写消息和一条读消息。 i2c_transfer()函数本身不具备驱动适配器物理硬件完成消息交互的能力,它只是寻找到i2c_adapter 对应的i2c_algorithm,并使用i2c_algorithm的master_xfer()函数真正驱动硬件流程。(比如:s3c24xx_i2c_xfer)
2.2 IDR机制/include/linux/idr.h
二、分析重要的结构体
linux-2.6.32.2/include/linux/i2c.h
1.struct i2c_msg; 消息结构体,是适配器到IIC设备传输数据的基本单位
struct i2c_msg {
__u16 addr; /* slave address IIC设备地址 */
__u16 flags; /*消息类型标志*/
__u16 len; /* msg length 消息字节长度 */
__u8 *buf; /* pointer to msg data 指向消息数据的缓冲区*/
#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 */ //第一次接收的字节长度
};
//==============Start IIC总线层=========================================
2.struct i2c_algorithm; 描述了适配器与设备之间的通信方法
struct i2c_algorithm {
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,int num);
//指向实现IIC总线通信协议的函数
int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,unsigned short flags, char read_write,
u8 command, int size, union i2c_smbus_data *data);
//指向实现SMBus总线通信协议的函数,SMBus总线通信协议是基于IIC协议的原理(也是2条总线,时钟和数据)
u32 (*functionality) (struct i2c_adapter *);//确定适配器支持哪些传输类型
};
例子:
static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
.master_xfer = s3c24xx_i2c_xfer,
.functionality = s3c24xx_i2c_func,
};
3.struct i2c_adapter;
IIC总线适配器,即是IIC总线控制器。主要功能是完成IIC总线控制器相关的数据通信。为描述了各种IIC适配器提供了通用的"模版",定义指向具体IIC适配器的总线通信方法i2c_algorithm的指针algo、实现IIC总线操作原子性操作的lock信号量。特定的适配器可在此基础上进行扩充
struct i2c_adapter {
struct module *owner;
unsigned int id;
unsigned int class; /* classes to allow probing for 允许探测的驱动类型 */
const struct i2c_algorithm *algo; /* the algorithm to access the bus 总线通信方法指针*/
void *algo_data; /* algorithm数据*/
u8 level; /* nesting level for lockdep */
struct mutex bus_lock;
int timeout; /* in jiffies */
int retries;
struct device dev; /* the adapter device */
int nr; //通过nr整型数从红黑树得到与之对应的i2c_adapter适配器结构
char name[48]; /*适配器名称*/
struct completion dev_released; /*用于同步*/
};
#define to_i2c_adapter(d) container_of(d, struct i2c_adapter, dev)
//==================End IIC总线==================================
//==================Start IIC设备层================================
4.struct i2c_client; //IIC设备,一个结构体描述一个真实的物理IIC设备
struct i2c_client {
unsigned short flags; /* div., see below */
unsigned short addr; /* chip address - NOTE: 7bit 芯片低7位地址*/
char name[I2C_NAME_SIZE]; /*设备名字*/
struct i2c_adapter *adapter; /* the adapter we sit on 依附i2c_adapter*/
struct i2c_driver *driver; /* and our access routines 依附i2c_driver */
struct device dev; /* the device structure 设备结构体 */
int irq; /* irq issued by device */
struct list_head detected; /*链表头 */
};
#define to_i2c_client(d) container_of(d, struct i2c_client, dev)
5.struct i2c_driver; //IIC设备驱动
每个IIC设备对应一个驱动,即是每个i2c_client对应一个i2c_driver结构,通过包含指针来连接
struct i2c_driver {
unsigned int class; /*驱动类型*/
/*************************************传统函数**********************************/
int (*attach_adapter)(struct i2c_adapter *); /*依附i2c_adapter函数指针*/
int (*detach_adapter)(struct i2c_adapter *); /*脱离i2c_adapter函数指针*/
/**********************************************************************************/
//新旧两种驱动程序函数,只能选择其中一种。新的支持IIC设备的动态插入和拔出,旧的不支持!!!!!!!
/************************************新型函数**************************************/
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 *); /* probe,remove,suspend,resume驱动方法重要成员函数 */
/***********************************************************************************/
int (*command)(struct i2c_client *client, unsigned int cmd, void *arg); /*类似ioctl*/
struct device_driver driver; /*指向设备驱动的结构体*/
const struct i2c_device_id *id_table; /* 驱动支持多个设备,这里面就要包含这些设备的ID */
const struct i2c_client_address_data *address_data; / *设备映射到内存的地址范围*/
struct list_head clients; /*将该驱动支持的所有IIC设备连成链表*/
int (*detect)(struct i2c_client *, int kind, struct i2c_board_info *); /*i2c client脱离函数指针*/
};
#define to_i2c_driver(d) container_of(d, struct i2c_driver, driver)
//=================End IIC设备层==================================
四个重要结构体的关系:
(1)总线层: i2c_adapter 与i2c_algorithm
i2c_adapter 与i2c_algorithm,i2c_adapter 对应于物理上的一个适配器,而i2c_algorithm对应一套通信方法。一个I2C适配器需要i2c_algorithm中提供的通信函数来控制适配器上产生特定的访问周期。缺少i2c_algorithm 的i2c_adapter 什么也做不了,因此i2c_adapter 中包含其使用的i2c_algorithm的指针。i2c_algorithm 中的关键函数master_xfer()用于产生I2C 访问周期需要的信号,以i2c_msg(即I2C消息)为单位。
(2)设备层:i2c_driver 与i2c_client
i2c_driver 与i2c_client,i2c_driver 对应一套驱动方法,是纯粹的用于辅助作用的数据结构,它不对应于任何的物理实体,主要的成员函数有probe() remove() suspend() resume()等。更外id_table成员是该驱动所支持的IIC设备的ID表。i2c_client对应于真实的物理设备,每个I2C设备都需要一个i2c_client来描述。i2c_client一般被包含在I2C字符设备的私有信息结构体中。可以知道i2c_driver 与i2c_client是一对多的关系,即是一个i2c_driver上可以支持多个同类型的 i2c_client。
i2c_driver 与i2c_client发生关联的时刻在i2c_driver的attach_adapter()函数被运行时。attach_adapter()会探测物理设备,当确定一个client存在时,把该client使用的i2c_client数据结构的adapter指针指向对应的i2c_adapter, driver指针指向该i2c_driver,并会调用i2c_adapter的client_register()函数。相反的过程发生在 i2c_driver 的detach_client()函数被调用的时候。
(3)i2c_adpater 与i2c_client
i2c_adpater 与i2c_client 的关系与I2C 硬件体系中适配器和设备的关系一致,即i2c_client 依附i2c_adpater。
//==================End IIC设备层================================
6.union i2c_smbus_data;
#define I2C_SMBUS_BLOCK_MAX 32 /* As specified in SMBus standard */
union i2c_smbus_data {
__u8 byte;
__u16 word;
__u8 block[I2C_SMBUS_BLOCK_MAX + 2]; /* block[0] is used for length */
/* and one more for user-space compatibility */
};
7.struct i2c_board_info; //linux-2.6.32.2/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;
int irq;
};
#define I2C_BOARD_INFO(dev_type, dev_addr) \
.type = dev_type, .addr = (dev_addr)
8.struct i2c_devinfo; //i2c-core.h
struct i2c_devinfo { struct list_head list; int busnum; struct i2c_board_info board_info;};
LIST_HEAD(__i2c_board_list);// 定义并初始化 在/linux-2.6.32.2/drivers/i2c/i2c-boardinfo.cEXPORT_SYMBOL_GPL(__i2c_board_list);
链表__i2c_board_list保存着
一些平台信息,和I2C设备地址
9.struct i2c_client_address_data;//linux-2.6.32.2/include/linux/i2c.h
struct i2c_client_address_data { 不懂
const unsigned short *normal_i2c;
//该数组指定对每个适配器上的指定地址都进行探测
const unsigned short *probe;
//该数组是匹对出现的只对指定适配器的指定地址进行探测,前一个
数是适配
器后面是指该适配器的上的一个地址
const unsigned short *ignore;
//在进行normal_i2c探测是看看此中是否忽略,若忽略则放弃探测,
其也是成对
出现的,前者指适配器后者指地址
const unsigned short * const *forces;
//强制使用的地址,确定某个地址代
表 的设备已经连接在总线上了。这个指针指向一个指针数组,每一个成员所
指的内容也是每个适配器号后紧跟一个地址
};
四、实例(IIC总线驱动层)
linux-2.6.32.2/drivers/i2c/busses/i2c-s3c2410.c
1、具体的适配器s3c24xx_i2c
struct s3c24xx_i2c {
spinlock_t lock;
wait_queue_head_t wait;
unsigned int suspended:1; //表示设备是否挂起
struct i2c_msg *msg; //从适配器到设备一次传输的单位,用结构体将数据封装起来便于操作
unsigned int msg_num; //消息个数
unsigned int msg_idx; //表示第几个消息,完成后一个消息后自加1
unsigned int msg_ptr; //指向当前要处理的下一个字节,在i2c_msg.buf中的偏移位置
unsigned int tx_setup; //写一个寄存器的延时时间
unsigned int irq;
enum s3c24xx_i2c_state state; //IIC目前的状态
unsigned long clkrate; //时钟速率
void __iomem *regs; //寄存器地址,映射
struct clk *clk; //对应的时钟
struct device *dev; //适配器对应的设备结构体
struct resource *ioarea; //适配器的资源
struct i2c_adapter adap; //适配器主体 模版!!!!
#ifdef CONFIG_CPU_FREQ
struct notifier_block freq_transition;
#endif
};
2、IIC总线通信方法
static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
.master_xfer = s3c24xx_i2c_xfer,
.functionality = s3c24xx_i2c_func,
};
static u32 s3c24xx_i2c_func(struct i2c_adapter *adap)//返回总线支持的协议
static int s3c24xx_i2c_xfer(struct i2c_adapter *adap,struct i2c_msg *msgs, int num)
static int s3c24xx_i2c_doxfer(struct s3c24xx_i2c *i2c,struct i2c_msg *msgs, int num)
//实现IIC通信协议,将i2c_msg消息传给IIC设备
3、寄存器操作
static int s3c24xx_i2c_set_master(struct s3c24xx_i2c *i2c)//判断总线忙闲状态
static void s3c24xx_i2c_message_start(struct s3c24xx_i2c *i2c,struct i2c_msg *msg)//启动适配器消息传输函数
static inline void s3c24xx_i2c_stop(struct s3c24xx_i2c *i2c, int ret)//适配器传输停止函数
static inline void s3c24xx_i2c_master_complete(struct s3c24xx_i2c *i2c, int ret)//传输完成函数
static inline void s3c24xx_i2c_disable_ack(struct s3c24xx_i2c *i2c)
static inline void s3c24xx_i2c_enable_ack(struct s3c24xx_i2c *i2c)//禁止/使能应答
static irqreturn_t s3c24xx_i2c_irq(int irqno, void *dev_id)//中断处理函数
static inline void s3c24xx_i2c_enable_irq(struct s3c24xx_i2c *i2c)
static inline void s3c24xx_i2c_disable_irq(struct s3c24xx_i2c *i2c)//使能/禁止中断
static int i2s_s3c_irq_nextbyte(struct s3c24xx_i2c *i2c, unsigned long iicstat)//传输下一个字节
static inline int is_lastmsg(struct s3c24xx_i2c *i2c)//判断当前处理的消息是否为最后一个消息
static inline int is_msglast(struct s3c24xx_i2c *i2c)
static inline int is_msgend(struct s3c24xx_i2c *i2c)//判断当前消息是否已经传输完所有字节
static int s3c24xx_i2c_calcdivisor(unsigned long clkin, unsigned int wanted,unsigned int *div1, unsigned int *divs)
static int s3c24xx_i2c_clockrate(struct s3c24xx_i2c *i2c, unsigned int *got)
//计算传输分频系数、设置控制器的数据发送频率 在s3c24xx_i2c_init被调用
static int s3c24xx_i2c_init(struct s3c24xx_i2c *i2c)//IIC控制器的初始化,在s3c24xx_i2c_probe调用
4、IIC设备层驱动程序
4.1 实现函数操作
static int s3c24xx_i2c_probe(struct platform_device *pdev)//平台设备注册函数platform_driver_register()会调用探测函数
申请一个适配器结构i2c,并赋初值
获得i2c时钟资源
将适配器的寄存器资源映射到虚拟内存中去
申请中断处理函数
初始化IIC控制器
添加适配器i2c到内核中
static int s3c24xx_i2c_remove(struct platform_device *pdev)//与s3c24xx_i2c_probe相反
4.2 填充平台设备结构体
static struct platform_device_id s3c24xx_driver_ids[] = {
{
.name = "s3c2410-i2c",
.driver_data = TYPE_S3C2410,
}, {
.name = "s3c2440-i2c",
.driver_data = TYPE_S3C2440,
}, { },
};
MODULE_DEVICE_TABLE(platform, s3c24xx_driver_ids);
static struct platform_driver s3c24xx_i2c_driver = {
.probe = s3c24xx_i2c_probe,
.remove = s3c24xx_i2c_remove,
.id_table = s3c24xx_driver_ids,
.driver = {
.owner = THIS_MODULE,
.name = "s3c-i2c",
.pm = S3C24XX_DEV_PM_OPS,
},
};
4.2注册、注销平台驱动
static int __init i2c_adap_s3c_init(void)
{
return platform_driver_register(&s3c24xx_i2c_driver);
}
static void __exit i2c_adap_s3c_exit(void)
{
platform_driver_unregister(&s3c24xx_i2c_driver);
}
五、IIC设备驱动
Linux i2c-dev.c文件分析
i2c-dev.c文件完全可以被看作一个I2C设备驱动,其结构与上述的描述是基本一致的,不过,它实现的一个i2c_client是虚拟的、临时的,随着设备文件的打开而产生,并随设备文件的关闭而撤销,并没有被添加到i2c_adapter的clients链表中。i2c-dev.c针对每个I2C适配器生成一个主设备为 89的设备文件,实现了i2c_driver的成员函数以及文件操作接口,所以i2c-dev.c的主体是“i2c_driver成员函数 + 字符设备驱动”。 i2c-dev.c中提供i2cdev_read()、i2cdev_write()函数来对应用户空间要使用的read()和 write()文件操作接口,这两个函数分别调用I2C核心的i2c_master_recv()和i2c_master_send()函数来构造1条 I2C消息并引发适配器algorithm通信函数的调用,完成消息的传输。但是,很遗憾,
大多数稍微复杂一点I2C设备的读写流程并不对应于1条消息,往往需要2条甚至更多的消息来进行一次读写周期,这种情况下,在应用层仍然调用read()、write()文件API来读写I2C设备,将不能正确地读写。
鉴于上述原因,i2c-dev.c中i2cdev_read()和i2cdev_write()函数不具备太强的通用性,没有太大的实用价值,只能适用于非 RepStart模式的情况。对于2条以上消息组成的读写,在用户空间需要组织i2c_msg消息数组并调用I2C_RDWR IOCTL命令。
系统中i2c-dev.c文件定义的主设备号为89的设备可以方便地给应用程序提供读写I2C设备的寄存器的能力,使得工程师大多时候不需要为具体的I2C设备定义文件接口函数。
六、i2c
设备的4种构建方法
6.1在板文件(如:mach-mini2440.c)定义一个i2c_board_info, 里面有:名字, 设备地址 然后i2c_register_board_info(busnum, ...) (把它们放入__i2c_board_list链表) list_add_tail(&devinfo->list, &__i2c_board_list);
链表何时使用: i2c_register_adapter > i2c_scan_static_board_info > i2c_new_device
使用限制:必须在 i2c_register_adapter 之前 i2c_register_board_info 所以:不适合我们动态加载insmod
例如:
需要通过i2c_register_board_info()函数注册i2c_board_info,向内核提供i2c设备的相关信息。
在arch/arm/mach-s3c2440/mach-mini2440.c
static struct i2c_board_info i2c_devices[] __initdata = { { I2C_BOARD_INFO("at24cxx", 0x50), },
{ I2C_BOARD_INFO("at24c02", 0x52), },
{ I2C_BOARD_INFO("at24c08", 0x58), },
};
static void __init mini2440_machine_init(void)
{
i2c_register_board_info(0,i2c_devices,ARRAY_SIZE(i2c_devices));
........
}
这样启动内核后在
sys/bus/i2c/devices/目录下就有
0-0050、
0-0052、
0-0058,其目录下的name就保存着要匹配的名字。如:
cat sys/bus/i2c/devices/0-0052/name 可以看到
at24c02,这个名字要与设备驱动中
i2c_device_id
结构体填充name要一样。若匹配成功就会调用设备驱动中
i2c_driver结构体里的proble()函数。
例子:
下面7.2 newstyle方式
6.2 直接i2c_new_device, i2c_new_probed_device两种方法。注意区别
i2c_new_device :
认为设备肯定存在
i2c_new_probed_device :
对于"已经识别出来的设备"(probed_device),才会创建("new")
会根据传递进来的地址列表参数addr_list进行判断
有没有地址列表中的设备,如果有并且设备可用就调用
i2c_new_device来创建设备i2c_client。
i2c_new_probed_device(i2c_adap,&at24cxx_info,addr_list);
i2c_check_functionality(adap, I2C_FUNC_SMBUS_READ_BYTE)//判断总线
i2c_check_addr(adap, addr_list[i])//检查地址是否可用
i2c_smbus_xfer(adap, addr_list[i], 0,I2C_SMBUS_READ, 0,I2C_SMBUS_BYTE, &data)
i2c_smbus_xfer(adap, addr_list[i], 0,I2C_SMBUS_WRITE, 0,I2C_SMBUS_QUICK, NULL)
//判断是否有相应从而得知该地址的设备是否存在
info->addr = addr_list[i];//如果有响应则设置地址
i2c_new_device(adap, info);//创建设备结构i2c_client
例子代码:
/*注册一个在总线上IIC设备
*采用i2c_new_probe_device
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
static struct i2c_client *at24cxx_client;
static unsigned short const addr_list[]={0x60,0x50,I2C_CLIENT_END};
//地址列表,开发板的地址是0x50。如果列表没有设备将会不能注册进内核
//mini2440开发板0x50,0x51,0x52,0x53
static int __init 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, "at24cxx_dev_drv", I2C_NAME_SIZE);//匹配名字
i2c_adap = i2c_get_adapter(0);//获得第几个适配器,对于mini2440只有一个就是0
at24cxx_client = i2c_new_probed_device(i2c_adap,&at24cxx_info,addr_list);
i2c_put_adapter(i2c_adap);
if(at24cxx_client)
return 0;
else{
printk("creat at24cxx_client failed\n");
return -ENODEV;
}
}
static void __exit at24cxx_dev_exit(void)
{
i2c_unregister_device(at24cxx_client);
}
module_init(at24cxx_dev_init);
module_exit(at24cxx_dev_exit);
MODULE_DESCRIPTION("Driver for most I2C EEPROMs");
MODULE_AUTHOR("lys");
MODULE_LICENSE("GPL");
/*采用i2c_new_device*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
static struct i2c_client *at24cxx_client;
static struct i2c_board_info at24cxx_info[] = {
{I2C_BOARD_INFO("at24cxx_dev_drv", 0x50),},
};
static int __init at24cxx_dev_init(void)
{
struct i2c_adapter *i2c_adap;
i2c_adap = i2c_get_adapter(0);//获得第几个适配器,对于mini2440只有一个就是0
if(i2c_adap == NULL)
{
printk("get i2c_adap failed\n");
return -ENODEV;
}
at24cxx_client = i2c_new_device(i2c_adap,&at24cxx_info);
i2c_put_adapter(i2c_adap);
if(at24cxx_client)
return 0;
else{
printk("creat at24cxx_client failed\n");
return -ENODEV;
}
}
static void __exit at24cxx_dev_exit(void)
{
i2c_unregister_device(at24cxx_client);
}
module_init(at24cxx_dev_init);
module_exit(at24cxx_dev_exit);
MODULE_DESCRIPTION("Driver for most I2C EEPROMs");
MODULE_AUTHOR("lys");
MODULE_LICENSE("GPL");
测试条件:insmod i2c-s3c2410.ko
加载自己写驱动
6.3从用户空间创建设备
创建:
执行后就导致i2c_new_device被调用
echo
at24cxx_dev_drv(设备名称,与驱动文件中要匹配)
0x50 > /sys/class/i2c-adapter/
adapter_devi2c-0(适配器设备名)/new_device
echo
at24cxx_dev_drv
0x50 > /sys/devices/platform/s3c2440-i2c/adapter_devi2c-0/new_device
删除:
导致i2c_unregister_device
echo
0x50 (设备地址)> /sys/class/i2c-adapter/adapter_devi2c-0/delete_device
echo
0x50
> /sys/devices/platform/s3c2440-i2c/adapter_devi2c-0/delete_device
创建后在sys/bus/i2c/devices/目录下就会有该设备,表示IIC总线下支持设备地址为
0x50的设备
分析:
static DEVICE_ATTR(new_device, S_IWUSR, NULL, i2c_sysfs_new_device);
static DEVICE_ATTR(delete_device, S_IWUSR, NULL, i2c_sysfs_delete_device);
当在应用层操作new_device这个文件时候就会调用i2c_sysfs_new_device()函数
测试条件:insmod i2c-s3c2410.ko
加载自己写驱动
6.4前面的3种方法都要事先确定适配器(I2C总线,I2C控制器)
如果我事先并不知道这个I2C设备在哪个适配器上,怎么办?去class表示的所有的适配器上查找有上一些I2C设备的地址是一样,怎么继续分配它是哪一款?用detect函数
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, /* 这些设备的地址 */
去"class表示的这一类"I2C适配器,用"detect函数"来确定能否找到"address_list里的设备",如果能找到就调用i2c_new_device来注册i2c_client, 这会和i2c_driver的id_table比较,如果匹配,调用probe
i2c_add_driver
i2c_register_driver
1.driver->driver.bus = &i2c_bus_type; //at24cxx_driver放入i2c_bus_type的drv链表
driver_register(&driver->driver);//并且从dev链表里取出能匹配的i2c_client并调用probe
2.
对于每一个适配器,
调用__attach_adapter()函数,确定
address_list里的设备是否存在。
如果存在,再调用detect进一步确定、设置,然后i2c_new_device
bus_for_each_dev(&i2c_bus_type, NULL, driver, __attach_adapter);
__attach_adapter()
adapter = to_i2c_adapter(dev);
i2c_detect(adapter, driver);
i2c_detect_address(temp_client, kind, driver);
driver->detect(temp_client, kind, &info);//回调驱动层的detect()函数
七、如何下手写驱动
一方面,适配器驱动可能是
Linux
内核本身还不包含的。另一方面,挂接在适配器上的具体设备驱动可能也是
Linux
不存在的。即便上述设备驱动都存在于
Linux
内核中,其基于的平台也可能与我们的电路板不一样。因此,工程师要实现的主要工作将包括:
1
、提供
I2C
适配器的硬件驱动,探测、初始化
I2C
适配器(如申请
I2C
的
I/O
地址和中断号)、驱动
CPU
控制的
I2C
适配器从硬件上产生各种信号以及处理
I2C
中断等。
2
、提供
I2C
适配器的
algorithm
,用具体适配器的
xxx_xfer()
函数填充
i2c_algorithm
的
master_xfer
指针,并把
i2c_algorithm
指针赋值给
i2c_adapter
的
algo
指针
3
、实现
I2C
设备驱动与
i2c_driver
接口,用具体设备
yyy
的
yyy_attach_adapter()
函数指针、
yyy_detach_client()
函数指针和
yyy_command()
函数指针的赋值给
i2c_driver
的
attach_adapter
、
detach_adapter
和
detach_client
指针。
4
、实现
I2C
设备驱动的文件操作接口,即实现具体设备
yyy
的
yyy_read()
、
yyy_write()
和
yyy_ioctl()
函数等。
上述工作中
1
、
2
属于
I2C
总线驱动,
3
、
4
属于
I2C
设备驱动,做完这些工作,系统会增加两个内核模块。
7.1I2C总线驱动(一般不需要我们自己写)
(1)主要是完成
i2c_adapter
适配器结构的注册
i2c_add_numbered_adapter()
i2c_add_adapter()
i2c_register_adapter()
/*1、设置适配器结构的成员,所属总线类型,然后注册*/
dev_set_name(&adap->dev, "i2c-%d", adap->nr);
adap->dev.bus = &i2c_bus_type;
adap->dev.type = &i2c_adapter_type;
res = device_register(&adap->dev);
/*2、搜索与平台相关的地址信息*/
i2c_scan_static_board_info(adap)
/*3、*/
bus_for_each_drv(&i2c_bus_type, NULL, adap,i2c_do_add_adapter);
i2c_do_add_adapter()
i2c_detect(adap, driver);
i2c_detect_address()
i2c_new_device(adapter, &info)
device_register(&client->dev)
(2)
I2C总线驱动写法
1、定义一个i2c_adapter和算法结构体
static const struct i2c_algorithm i2c_bus_s3c2440_algo = {
.master_xfer = i2c_bus_s3c2440_xfer,
.functionality = i2c_bus_s3c2440_func,
};
struct i2c_adapter i2c_bus_s3c2440_adapter{
.owner =THIS_MODULE,
.name = "i2c_s3c2440_adap",
.algo =i2c_bus_s3c2440_algo,
};
2、两个算法函数的实现
static int i2c_bus_s3c2440_xfer(struct i2c_adapter *adap,struct i2c_msg *msgs, int num)
//硬件方面的操作
static u32 i2c_bus_s3c2440_func(struct i2c_adapter *adap)
3、模块加载卸载
static int __init i2c_bus_s3c2440(void)
{
/*硬件初始化、寄存器虚拟地址映射*/
s3c2440_i2c_regsp = ioremap(0x54000000,sizeof(struct s3c2440_i2c_regs));
/*2注册一个i2c_adapter*/
i2c_add_adapter(&i2c_bus_s3c2440_adapter);
return 0;
}
static void __exit i2c_bus_s3c2440(void)
{
i2c_del_adapter(&i2c_bus_s3c2440_adapter);
iounmap(s3c2440_i2c_regsp);
}
7.2 I2C设备驱动(i2cdev_driver
)
(1)i2c设备驱动注册
补充分析(参考i2c_dev.c,提供了以i2c为通信协议的设备通用接口,注册了主设备号89的字符驱动)
i2c_dev_init(void)
register_chrdev(I2C_MAJOR, "i2c", &i2cdev_fops);
//字符驱动
class_create(THIS_MODULE, "i2c-dev");
//创建类exit in /sys/class/
i2c_add_driver(&i2cdev_driver) //
注册i2c设备驱动, 实现在linux-2.6.32.2/include/linux/i2c.h
i2c_register_driver(THIS_MODULE, driver);//实现在linux-2.6.32.2/drivers/i2c/i2c-core.c
driver->driver.bus = &i2c_bus_type;//设置i2c_driver (即为i2cdev_driver)的总线类型,即属于哪条总线
driver_register(&driver->driver); //注册驱动,
调用结束后就会调用proble()函数
bus_for_each_dev(&i2c_bus_type, NULL, driver,
__attach_adapter);
//调用__attach_adapter在总线上查找合适driver的适配器
driver_register(struct device_driver *drv)
struct device_driver *other = driver_find(drv->name, drv->bus); //判断i2c_driver 是否被注册金内核
bus_add_driver(drv);//将i2c_driver 挂接到i2c总线上
driver_attach(drv);
bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
//对i2c总线上的每一个i2c设备i2c_client都会调用
__driver_attach,这里的dev即i2c_client,drv即i2c_driver
driver_bind()
driver_match_device(struct device_driver *drv, struct device *dev)
return drv->bus->match ? drv->bus->match(dev, drv) : 1;
实际上就是调用总线i2c_bus_type结构中match函数,即是:
i2c_device_match(struct device *dev, struct device_driver *drv)
i2c_match_id(driver->id_table, client) //若i2c_client的名字和i2c_device_id的中名字相
同,则匹配成功,
才会调用后面的probe()
driver_probe_device(struct device_driver *drv, struct device *dev)
really_probe(struct device *dev, struct device_driver *drv)
dev->bus->probe(dev);//调用
总线i2c_bus_type结构中proble()函数
实际上就是 i2c_device_probe()
driver->
probe(client, i2c_match_id(driver->id_table, client));
//调用到i2c_driver的probe()函数
(即是自己写的设备驱动proble()函数) __attach_adapter(struct device *dev, void *data)
i2c_detect(adapter, driver);
//探测适配器支持的设备的地址
i2c_detect_address(struct i2c_client *temp_client, int kind, struct i2c_driver *driver)
i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
device_register(&client->dev); //注册一个真是IIC设备i2c_client
driver->attach_adapter(adapter);//如果Legacy 型驱动,则调用i2c_driver结构中attach_adapter函数为适配器创建设备节点
(2)i2c设备驱动的写法
(参考
i2c-dev.c,
自定义文件操作函数接口,没有借用内核
i2c-dev.c字符驱动文件操作接口
)
newstyle方式 :(
i2c_driver成员函数 + 字符设备驱动
)
1.
注册
i2c
设备相关信息(目的是让总线支持该设备,方法有四种。具体见上面:
六、i2c设备的4种构建方法
)
需要通过i2c_register_board_info()函数注册i2c
_board_info,
向内核提供
i2c
设备的相关信息。
在arch/arm/mach-s3c2440/mach-mini2440.c
static struct i2c_board_info i2c_devices[] __initdata = {
{ I2C_BOARD_INFO("
at24cxx", 0x50), },
{ I2C_BOARD_INFO("
at24c02", 0x52), },
{ I2C_BOARD_INFO("
at24c08", 0x58), },
};
static void __init mini2440_machine_init(void)
{
i2c_register_board_info(0,i2c_devices,ARRAY_SIZE(i2c_devices));
........
}
这样启动内核后在
sys/bus/i2c/devices/目录下就有
0-0050、
0-0052、
0-0058,其目录下的name就保存着要匹配的名字。如:
cat sys/bus/i2c/devices/0-0052/name 可以看到
at24c02,这个名字要与设备驱动中
i2c_device_id
结构体填充name要一样。若匹配成功就会调用设备驱动中
i2c_driver结构体里的proble()函数。
2.定义填充
i2c_driver结构体
static const struct i2c_device_id at24cxx_id[] = {
{ "
at24cxx", 0 }, //匹配时,I2C_BOARD_INFO
{ }
};
MODULE_DEVICE_TABLE(i2c, at24cxx_id);
static struct i2c_driver at24cxx_driver = {
.driver = {
.name = "at24c08b",
.owner = THIS_MODULE,
},
.probe = at24cxx_probe,
.remove = __devexit_p(at24cxx_remove),
.id_table = at24cxx_id,
};
3.
实现
i2c_driver结构体中函数
static int at24cxx_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
//(1)分配一个i2c_client 结构体at24cxx_client(全局结构)
//(2)设置i2c_client
//(3)注册一个IIC字符设备
//(4)创建设备类和设备节点,sys/class下产生类,同时在/dev下自动创建设备节点(应用层open要用到)
}
在加载该模块时i2c_add_driver(&at24cxx_driver)会将驱动注册到IIC总线上,并且进行设备的探测。探测主要是调用i2c_match_id()函数比较i2c_client结构和i2c_device_id结构中的name是否相同,相同时匹配就成功。说明总线上有支持该模块驱动的设备,这时就会
调用at24cxx_probe()函数注册一个字符驱动。(
与下面对应i2c_core.c中函数)
static const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id,const struct i2c_client *client)
{
while (id->name[0]) {
if (strcmp(client->name, id->name) == 0)
return id;
id++;
}
return NULL;
}
static int i2c_device_probe(struct device *dev)
{
struct i2c_client *client = i2c_verify_client(dev);
struct i2c_driver *driver;
int status;
if (!client)
return 0;
driver = to_i2c_driver(dev->driver);
if (!driver->probe || !driver->id_table)
return -ENODEV;
client->driver = driver;
if (!device_can_wakeup(&client->dev))
device_init_wakeup(&client->dev,
client->flags & I2C_CLIENT_WAKE);
dev_dbg(dev, "probe\n");
status =
driver->probe(client, i2c_match_id(driver->id_table, client));
if (status)
client->driver = NULL;
return status;
}
4.
I2C设备驱动模块加载与卸载
static int __init at24cxx_init(void)
{
return i2c_add_driver(&at24cxx_driver);
}
static void __exit at24cxx_exit(void)
{
i2c_del_driver(&at24cxx_driver);
}
5.填充字符驱动函数操作结构体file_operations
static struct file_operations at24cxx_fops ={
.owner = THIS_MODULE,
.read = at24cxx_read,
.write = at24cxx_write,
.open = at24cxx_open,
.release = at24cxx_release,
};
6.
实现字符驱动的操作函数
例子:下面是驱动程序和测试程序
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define DEVICE_NAME "at24c_name" //cat /proc/devices
static int at24cxx_major = 110;
struct at24cxx_dev{
struct i2c_client *client;
};
struct at24cxx_dev *at24cxx_dev_p;
static struct i2c_driver at24cxx_driver;
static struct class *at24cxx_class;
static struct device *at24cxx_class_devs;
static int at24cxx_open(struct inode *inode, struct file *file)
{
file->private_data = at24cxx_dev_p;
return 0;
}
static int at24cxx_release(struct inode *inode, struct file *file)
{
file->private_data = NULL;
return 0;
}
static ssize_t at24cxx_read(struct file *file, char __user *buf, size_t size, loff_t * offset)
{
unsigned char addr,data;
copy_from_user(&addr,buf,1);
data = i2c_smbus_read_byte_data(at24cxx_dev_p->client,addr);
copy_to_user(buf,&data,1);
return 1;
}
static ssize_t at24cxx_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
unsigned char ker_buf[2];
unsigned char addr,data;
copy_from_user(ker_buf,buf,2);
addr = ker_buf[0];
data = ker_buf[1];
if(!i2c_smbus_write_byte_data(at24cxx_dev_p->client,addr,data))
return 2;
else
return -EIO;
}
static struct file_operations at24cxx_fops ={
.owner = THIS_MODULE,
.read = at24cxx_read,
.write = at24cxx_write,
.open = at24cxx_open,
.release = at24cxx_release,
};
static int at24cxx_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
int ret;
at24cxx_dev_p = kzalloc(sizeof(struct at24cxx_dev), GFP_KERNEL);
if(!at24cxx_dev_p){
ret = -ENOMEM;
return ret;
}
memset(at24cxx_dev_p, 0, sizeof(struct at24cxx_dev));
at24cxx_dev_p->client = client;
ret = register_chrdev(at24cxx_major,DEVICE_NAME,&at24cxx_fops);//
if(ret)
goto out;
at24cxx_class = class_create(THIS_MODULE,"at24cxx_class");//sys/class //设备类
at24cxx_class_devs = device_create(at24cxx_class,NULL,MKDEV(at24cxx_major,0),NULL,"at24cxx%d",0);// dev/设备节点
if (IS_ERR(at24cxx_class)) {
ret = PTR_ERR(at24cxx_class);
goto out_unreg_chrdev;
}
printk(DEVICE_NAME"\tinitialized\n");
return 0;
out_unreg_chrdev:
unregister_chrdev(at24cxx_major, DEVICE_NAME);
out:
printk(KERN_ERR "%s: Driver Initialisation failed\n", __FILE__);
return ret;
}
static int __devexit at24cxx_remove(struct i2c_client *client)
{
device_destroy(at24cxx_class,MKDEV(at24cxx_major,0));
class_destroy(at24cxx_class);
kfree(at24cxx_dev_p);
unregister_chrdev(at24cxx_major, DEVICE_NAME);
return 0;
}
static const struct i2c_device_id at24cxx_ids[] = {
{ "at24cxx", 0 },//匹配
{ }
};
MODULE_DEVICE_TABLE(i2c, at24cxx_ids);
static struct i2c_driver at24cxx_driver = {
.driver = {
.name = "at24cxx_driver",
.owner = THIS_MODULE,
},
.probe = at24cxx_probe,
.remove = __devexit_p(at24cxx_remove),
.id_table = at24cxx_ids,
};
static int __init at24cxx_init(void)
{
return i2c_add_driver(&at24cxx_driver);
}
static void __exit at24cxx_exit(void)
{
i2c_del_driver(&at24cxx_driver);
}
module_init(at24cxx_init);
module_exit(at24cxx_exit);
MODULE_DESCRIPTION("Driver for most I2C EEPROMs");
MODULE_AUTHOR("lys");
MODULE_LICENSE("GPL");
#include
#include
#include
#include
#include
#include
/* i2c_addr_test r addr
* i2c_addr_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/at24cxx0", O_RDWR);
if (fd < 0)
{
printf("can't open /dev/at24cxx0\n");
return -1;
}
if (strcmp(argv[1], "r") == 0)
{
buf[0] = strtoul(argv[2], NULL, 0);
if(read(fd, buf, 1) == 1)
{
printf("read data: %d, 0x%2x\n", buf[0], buf[0]);
}
}
else if ((strcmp(argv[1], "w") == 0) && (argc == 4))
{
buf[0] = strtoul(argv[2], NULL, 0);//addr
buf[1] = strtoul(argv[3], NULL, 0);//data
if(write(fd, buf, 2) != 2)
{
printf("write err\n");
}
}
else
{
print_usage(argv[0]);
return -1;
}
return 0;
}
测试条件:1、insmod i2c-s3c2410.ko
2、为内核添加IIC设备(方法见上面分析的四种)
八、用户应用程序直接访问IIC设备
用户可以不用为IIC设备写驱动,而是在应用程序直接访问IIC设备。这种方法其实是借用内核
i2c-dev.c字符驱动文件操作接口,应用程序需要一个头文件i2c-dev.h 。可以百度下载i2c-tools-3.1.1.tar.bz2开源工具包。
i2c_smbus_read_word_data和i2c_smbus_write_byte_data函数来读写数据。参考程序如下:
#include
#include
#include
#include
#include
#include
#include "i2c-dev.h"
/* 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 addr,data;
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;
}
addr = strtoul(argv[2],NULL,0);
if(ioctl(fd,I2C_SLAVE,addr) < 0)
{
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("read data: %d, 0x%2x\n", data, data);
}
else if ((strcmp(argv[3], "w") == 0) && (argc == 6))
{
addr = strtoul(argv[4], NULL, 0);//addr
data = strtoul(argv[5], NULL, 0);//data
i2c_smbus_write_byte_data(fd,addr,data);
}
else
{
print_usage(argv[0]);
return -1;
}
return 0;
}
测试条件:1、insmod i2c-s3c2410.ko
2、insmod i2c-dev.ko
以上是我学IIC驱动做的一些笔记总结,仅供大家学习参考,如果有错误的恳请指正。
参考博客:
Linux I2C
核心、总线与设备驱动
http://tanatseng.blog.163.com/blog/static/1749916292011440122182/
i2c
驱动之设备模型建立
http://chxxxyg.blog.163.com/blog/static/1502811932010635818167/
i2c
驱动之难点释疑
http://chxxxyg.blog.163.com/blog/static/1502811932010636351825/
写一个
IIC
适配器驱动需要做些什么
http://chxxxyg.blog.163.com/blog/static/150281193201063103618798/
I2c-s3c2440.c 分析
http://blog.chinaunix.net/uid-25120309-id-3357609.html
http://www.doc88.com/p-910624500869.html