默认情况下,I2C以从模式工作。接口在生成起始位后,会自动由从模式切换到主模式,并在仲裁丢失或生成停止位时从主模式切换为主模式。
在主模式下,I2C接口会启动数据传输并生成时钟信号。
任何能够进行发送和接收的设备都可以成为主设备。一个主控制器能控制信号的传输和时钟频率,当然在任何时间点上只能有一个主控制器。
空闲状态:I2C总线的SDA和SCL两条信号线同时处于高电平。
进行数据传输时,在SCL呈现高电平期间,SDA上电平必须保持稳定,低电平为数据0,高电平为数据1.
I2C总线上所有数据都是以8字节传送的,发送器每发送一个字节,就在时钟脉冲期间释放数据线,由接收器反馈一个应答信号。
I2C核心:I2C提供了I2C总线驱动和设备驱动的注册和注销方法,I2C通信方法(algorithm) 上层的,与具体适配器无关的代码以及探测设备,检测设备地址的上层代码
I2C总线:I2C总线驱动是对I2C硬件体系结构中适配器端的实现,适配器可由CPU控制,其可直接集成在CPU内部。
一个I2C适配器需要i2c_algorithm中提供的通信函数来控制适配器上产生的特定的访问周期。
master_xfer();用于产生i2c访问周期需要的start,stop,ack信号.
i2c_scan_static_borad_info()为整个I2C子系统的核心,会去遍历一个由I2C设备组成的双向循环链表,并完成所有I2C从设备的i2c_client的注册。
i2c从设备主要完成三大任务
系统初始化时添加i2c_board_info为结构的i2c从设备的信息
在i2c从设备驱动里使用i2c_adapter提供的算法实现i2c通信
将i2c从设备特有数据结构挂载到i2c_client->driver_data
i2c_adapter对应i2c总线控制器
/*
* i2c_adapter is the structure used to identify a physical i2c bus along
* with the access algorithms necessary to access it.
*/
struct i2c_adapter {
struct module *owner;
unsigned int class; /* classes to allow probing for */
const struct i2c_algorithm *algo; /* the algorithm to access the bus */
void *algo_data;
/* data fields that are valid for all devices */
struct rt_mutex bus_lock;
int timeout; /* in jiffies */
int retries;
struct device dev; /* the adapter device */
int nr;
char name[48];
struct completion dev_released;
struct mutex userspace_clients_lock;
struct list_head userspace_clients;
};
i2c_client对应真实的i2c物理设备
/**
* struct i2c_client - represent an I2C slave device
* @flags: I2C_CLIENT_TEN indicates the device uses a ten bit chip address;
* I2C_CLIENT_PEC indicates it uses SMBus Packet Error Checking
* @addr: Address used on the I2C bus connected to the parent adapter.
* @name: Indicates the type of the device, usually a chip name that's
* generic enough to hide second-sourcing and compatible revisions.
* @adapter: manages the bus segment hosting this I2C device
* @driver: device's driver, hence pointer to access routines
* @dev: Driver model device node for the slave.
* @irq: indicates the IRQ generated by this device (if any)
* @detected: member of an i2c_driver.clients list or i2c-core's
* userspace_devices list
*
* An i2c_client identifies a single device (i.e. chip) connected to an
* i2c bus. The behaviour exposed to Linux is defined by the driver
* managing the device.
*/
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 i2c_driver *driver; /* and our access routines */
struct device dev; /* the device structure */
int irq; /* irq issued by device */
struct list_head detected;
};
i2c_driver和i2c_client发生关联的时刻在i2c_driver的i2c_attach_adapter()函数运行时。
当一个具体的client被侦测到并被关联的时候,设备和sysfs文件将被注册。
说起Linux下的子系统,I2C子系统已经算是比较简单的框架了。
首先相关代码在driver/i2c下
.built-in.o.cmd .i2c-dev.o.cmd algos/ i2c-boardinfo.c i2c-core.h i2c-dev.o muxes/.i2c-boardinfo.o.cmd Kconfig built-in.o i2c-boardinfo.o i2c-core.o i2c-mux.c .i2c-core.o.cmd Makefile busses/ i2c-core.c i2c-dev.c i2c-smbus.c
i2c-dev是用户态直接相关联的,里面有相关文件(linux中设备即文件)操作的函数,最后依然会调用到i2c-core(是硬件层与系统调用层的中间缓冲层)的函数(i2c_master_send,i2c_master_recv最终还是调用i2c_transfer),i2c默认的主设备号为89.
static int __init i2c_dev_init(void)
{
int res;
printk(KERN_INFO "i2c /dev entries driver\n");
res = register_chrdev(I2C_MAJOR, "i2c", &i2cdev_fops);
if (res)
goto out;
i2c_dev_class = class_create(THIS_MODULE, "i2c-dev");
if (IS_ERR(i2c_dev_class)) {
res = PTR_ERR(i2c_dev_class);
goto out_unreg_chrdev;
}
/* Keep track of adapters which will be added or removed later */
res = bus_register_notifier(&i2c_bus_type, &i2cdev_notifier);
if (res)
goto out_unreg_class;
/* Bind to already existing adapters right away */
i2c_for_each_dev(NULL, i2cdev_attach_adapter);
return 0;
out_unreg_class:
class_destroy(i2c_dev_class);
out_unreg_chrdev:
unregister_chrdev(I2C_MAJOR, "i2c");
out:
printk(KERN_ERR "%s: Driver Initialisation failed\n", __FILE__);
return res;
}
在i2c-core中最后还是调用我们在busses/下写的i2c控制器模块中的传输函数(回调) ,在xxx_i2c_probe中,初始化一些硬件环境(中断,内存空间,时钟),i2c控制器抽象成i2c_adapter,最后调用i2c_add_numbered_adapter().注册到i2c子系统中。在该函数中调用i2c_register_adapter(),最终还是调用device_register().创建设备节点。其中之前一直很让我困惑的是节点设备的名字,原来在子系统中已经将设备节点的名字封装好,不需要我们在去命名,注意这里的节点设备名字和platform_device的名字是可以不相同的。
如果register成功就调用另一个核心的函数:
/* create pre-declared device nodes */
if (adap->nr < __i2c_first_dynamic_bus_num)
i2c_scan_static_board_info(adap);
扫描之前静态注册的I2C控制器设备,代码在(arch/xxx_cpu/xxx_sys/i2c_board_info.c)中:
关键函数在:
static int __init xxx_i2c_board_init(void)
{
i2c_register_board_info(0,xxx_i2c_1_board_info,ARRAY_SIZE(xxx_i2c_1_board_info));
i2c_register_board_info(1,xxx_i2c_2_board_info,ARRAY_SIZE(xxx_i2c_2_board_info));
i2c_register_board_info(2,xxx_i2c_3_board_info,ARRAY_SIZE(xxx_i2c_3_board_info));
i2c_register_board_info(3,xxx_i2c_4_board_info,ARRAY_SIZE(xxx_i2c_4_board_info));
return 0;
}
如果注册总线号和i2c控制器的number相同则又要创建一个注册新的i2c设备i2c_new_device()//??也不知道为什么
最关键的是是i2c通信过程。主要在xxx_i2c_probe()中dev->adap->algo = &i2c_xxx_algo;
static struct i2c_algorithm i2c_xxx_algo = {
.master_xfer = i2c_xxx_xfer,
.functionality = i2c_xxx_func,
};
最终i2c-core中的i2c_transfer()会调用i2c_xxx_xfer();里面主要对I2C控制器芯片的寄存器操作。
信息的内容被封装成i2c_message;
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测试程序的编写了,我们使用的是eeprom,因为是支持i2c接口,所以向i2c从设备地址写入若干数值,再从该地址读取数值,如果写入和读出是相同的,那么就代表i2c驱动是对的。
编写具体的i2c驱动
提供i2c适配器的硬件驱动,探测,初始化i2c适配器(申请i2c,I/O地址和中断号),驱动cpu控制的i2c适配器从硬件上产生。
提供i2c控制的algorithm(xxx_tranfer)
实现i2c设备驱动中的i2c_driver接口,只是实现设备与总线的连接
实现i2c设备对应类型的具体驱动