默認情況下,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設備對應類型的具體驅動