I2C協議標準文檔
THE I2C-BUS SPECIFICATION VERSION 2.1 JANUARY 2000: https://www.csd.uoc.gr/~hy428/reading/i2c_spec.pdf
I2C全稱Inter-IC,又寫作IIC,有些又歸類爲TWI(Two-Wire Interface).
電路原理
IIC僅由SDA數據線、SCL時鐘線構成。並且兩根線都需要接上拉電阻,原因是採用了OD門。
OD門(Open Drain漏極開路)的作用:
適用於 輸出\輸入
單獨使用時,可獨立輸出或輸入低電平和高阻態(可理解爲開路)
- 我們只需要給一個上拉電阻產生高電平,在高阻態/開路的作用下,線路電平就會等同於上拉電阻的高電平;
- 在低電平的作用下,線路電平仍然是低電平;
所以接上拉電阻,可以讓OD門有 輸入\輸出 高低電平 的功能,可以使用半雙工,這就是通信的物理電路基礎。
高電平一般有1.8V 3.3V 5V 三種。
上拉電阻阻值(3.3K~10K)與速度和容性負載相關,可以決定穩定性。
而且標準文檔還提供了不同電平之間的電路兼容方案
連接方式
所有從機SDA都並聯到主機上,所有從機SCK線都並聯到主機SCL上。
時序圖
關鍵點:
空閒狀態:SCL/SDA都是高電平。
工作狀態:SCL脈衝在高電平狀態下指示SDA有效。
經典的開始和結束信號
SDA下降沿表示開始,上升沿表示結束
多機衝突與通訊
有低電平的話,同一線上的都會被拉低。
接到同一SCL線的都會被同步
對端SDA的可判斷電平不一致而打斷操作。
如果需要FPGA實現,還需要留意標準文檔裏的Table5和Fig.31的時間間隔要求。
主要傳輸形式
速度: 0100KHz400KHz~3.4MHz
不同速度模式的設備混合接入總線系統,速度如表:
速度是需要“協商”的,如啓用 High Speed Mode
保留地址作爲管理碼(master code),詳見標準手冊 10.1 Definition of bits in the first byte
開始條件S 8位管理碼(00001xxx) 一位NACK
普通速率下傳輸數據(7地址模式)
其數據可抽象簡化爲
DEV_ADDR
BYTE_DATA
[..BYTE_DATA]
發送設備地址(7位+R/W標識位) 另外也有10位地址的,就是兩個地址字而已,用得少可以看標準文檔。
發送數據(8位一次)
可選繼續發送數據(每次也都是8位)
每8位都需要從機回覆一個A/A應答位。
也就說,它是基於字節傳輸的超短距簡單低速協議。
一般來說,I2C最適合用於讀寫寄存器,其數據形態與可以現有計算
機/微機體系匹配,符合8/16/32/64位等以字節整倍數的寄存器。
一個寫入寄存器的例子(16位寄存器地址,32位寄存器數據): DEV_ADDR
REG_ADDR0
REG_ADDR1
REG_DATA0
REG_DATA1
REG_DATA2
REG_DATA3
Tips: 基於字節傳輸的協議都可以替代串口協議的部分應用場景。
測試工具
Linux上一般使用i2c-tools這個包的工具
i2cdetect 用於掃描總線上掛接的從機設備地址。注意:非標設備不回覆,則掃描不到。i2cdetect -y -r 0
代表確認並以Read掃描一次IIC總線0下的所有非保留地址。把-r換成-q則表示以QUICK寫模式掃描。
i2cdump 用於讀取某個i2c從機設備的所有寄存器數據。
小技巧:當i2cdetect掃描不到的時候,可以用循環i2cdump抓出所有的從機設備數據,只要有數據的,說明該地址就有設備。
關於i2cdetect不回覆的問題排查,可以查看i2ctools源碼: https://github.com/mozilla-b2g/i2c-tools/blob/master/tools/i2cdetect.c#L50
其實就是從機要支持主機發送的 SMBUG_READ 指令
有個案例:https://bbs.aw-ol.com/topic/2304/分析筆記-linux-i2c-tools-使用踩坑筆記/2
變種
I2C的變種有 SMBUS MDIO I3C MIPI系等等協議。
本質上都是1時鐘線+1數據線,OD門實現雙向高低電平。可並聯。
Linux內核I2C&SMBUS子系統 API文檔
see: https://www.kernel.org/doc/html/v5.14/driver-api/i2c.html
文檔路徑 » The Linux driver implementer’s API guide » I2C and SMBus Subsystem
SMBUS是I2C的兄弟協議,大部分SMBUS也是I2C,並且電氣規定上比I2C更嚴格。
(SoC)I2C控制器收發的內核API
// 普通版本
int i2c_master_send(const struct i2c_client *client, const char *buf, int count)
int i2c_master_recv(const struct i2c_client *client, char *buf, int count)
// 帶DMA控制器的版本
int i2c_master_send_dmasafe(const struct i2c_client *client, const char *buf, int count)
int i2c_master_recv_dmasafe(const struct i2c_client *client, char *buf, int count)
一些重要的結構體
架構分層
貼張經典老圖,出處找不到了,侵權請聯繫
APP
/dev/i2c-x (device文件節點)
設備驅動driver
i2c-Core I2C_Adapter (控制器)
控制器一般是這樣的:一般由I2C控制器的IP設計服務商給到SoC廠商,SoC廠商在處理器微電路設計適配後,僅放出寄存器給用戶。所以我們用戶只需要根據SoC數據手冊對SoC寄存器操作即可。SoC會根據寄存器內容對控制器進行微電路操作的。
SOC廠家的對接代碼
硬件上對應:
n個SOC片上控制器 - Adapter
n個從機硬件設備 - 一個Driver+多個同系列型號Device
Adapter結構體裏還包含了傳輸算法algo、獨佔操作lock_ops、適配器對應的device及其name、下掛的userspace_clients,總線恢復操作bus_rec、特性quirks、中斷irq_domain等等。
傳輸算法algo裏面其實就是i2c和smbus的傳輸函數指針而已。
//設備識別
struct i2c_device_identity {
u16 manufacturer_id; //0 - 4095, database maintained by NXP
#define I2C_DEVICE_ID_NXP_SEMICONDUCTORS 0;
//...
#define I2C_DEVICE_ID_ATMEL 13;
#define I2C_DEVICE_ID_NONE 0xffff;
u16 part_id;
u8 die_revision;
};
//板載設備信息模板 template for device creation
struct i2c_board_info {
char type[I2C_NAME_SIZE];
unsigned short flags;
unsigned short addr;
const char *dev_name;
void *platform_data;
struct device_node *of_node;
struct fwnode_handle *fwnode;
const struct software_node *swnode;
const struct resource *resources;
unsigned int num_resources;
int irq;
};
//時序控制
struct i2c_timings {
u32 bus_freq_hz;
u32 scl_rise_ns;
u32 scl_fall_ns;
u32 scl_int_delay_ns;
u32 sda_fall_ns;
u32 sda_hold_ns;
u32 digital_filter_width_ns;
u32 analog_filter_cutoff_freq_hz;
};
總線API
//鎖,實現獨佔操作
void i2c_lock_bus(struct i2c_adapter *adapter, unsigned int flags)
int i2c_trylock_bus(struct i2c_adapter *adapter, unsigned int flags)
void i2c_unlock_bus(struct i2c_adapter *adapter, unsigned int flags)
電源操作
void i2c_mark_adapter_suspended(struct i2c_adapter *adap)
void i2c_mark_adapter_resumed(struct i2c_adapter *adap)
特性檢測
bool i2c_check_quirks(struct i2c_adapter *adap, u64 quirks)
初始化/卸載操作
int i2c_register_board_info(int busnum, struct i2c_board_info const *info, unsigned len)
struct i2c_client * i2c_verify_client(struct device *dev)
struct i2c_client * i2c_new_client_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
void i2c_unregister_device(struct i2c_client *client)
//dummy是虛擬設備
struct i2c_client * i2c_new_dummy_device(struct i2c_adapter *adapter, u16 address)
struct i2c_client * devm_i2c_new_dummy_device(struct device *dev, struct i2c_adapter *adapter, u16 address)
struct i2c_client * i2c_new_ancillary_device(struct i2c_client *client, const char *name, u16 default_addr)
struct i2c_adapter * i2c_verify_adapter(struct device *dev)
int i2c_handle_smbus_host_notify(struct i2c_adapter *adap, unsigned short addr)
int i2c_add_adapter(struct i2c_adapter *adapter)
void i2c_del_adapter(struct i2c_adapter *adap)
int i2c_add_numbered_adapter(struct i2c_adapter *adap)
int devm_i2c_add_adapter(struct device *dev, struct i2c_adapter *adapter)
void i2c_parse_fw_timings(struct device *dev, struct i2c_timings *t, bool use_defaults)
void i2c_del_driver(struct i2c_driver *driver)
向從機讀寫數據
int __i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
int i2c_transfer_buffer_flags(const struct i2c_client *client, char *buf, int count, u16 flags)
int i2c_get_device_id(const struct i2c_client *client, struct i2c_device_identity *id)
u8* i2c_get_dma_safe_msg_buf(struct i2c_msg *msg, unsigned int threshold)
void i2c_put_dma_safe_msg_buf(u8 *buf, struct i2c_msg *msg, bool xferred)
u8 i2c_smbus_pec(u8 crc, u8 *p, size_t count)
s32 i2c_smbus_read_byte(const struct i2c_client *client)
s32 i2c_smbus_write_byte(const struct i2c_client *client, u8 value)
s32 i2c_smbus_read_byte_data(const struct i2c_client *client, u8 command)
s32 i2c_smbus_write_byte_data(const struct i2c_client *client, u8 command, u8 value)
s32 i2c_smbus_read_word_data(const struct i2c_client *client, u8 command)
s32 i2c_smbus_write_word_data(const struct i2c_client *client, u8 command, u16 value)
s32 i2c_smbus_read_block_data(const struct i2c_client *client, u8 command, u8 *values)
s32 i2c_smbus_write_block_data(const struct i2c_client *client, u8 command, u8 length, const u8 *values)
s32 i2c_smbus_xfer(struct i2c_adapter *adapter, u16 addr, unsigned short flags, char read_write, u8 command, int protocol, union i2c_smbus_data *data)
s32 i2c_smbus_read_i2c_block_data_or_emulated(const struct i2c_client *client, u8 command, u8 length, u8 *values)
struct i2c_client * i2c_new_smbus_alert_device(struct i2c_adapter *adapter, struct i2c_smbus_alert_setup *setup)
Linux內核I2C&SMBUS子系統架構
從機設備驅動部分
設備驅動編寫:https://docs.kernel.org/i2c/writing-clients.html (較簡單)
I2C SysFs的:https://docs.kernel.org/i2c/i2c-sysfs.html
用法:https://docs.kernel.org/i2c/instantiating-devices.html
有以下幾種爲Linux內核創建I2C設備的方法(任選其一即可):
-
結構信息
- 設備樹
- ACPI(可以理解爲X86的設備樹)
-
代碼調用
- 填充 struct i2c_board_info 結構體並使用i2c_new_client_device()創建
- 調用i2c_new_scanned_device() 等API函數
- 填充 struct i2c_board_info 結構體並使用i2c_new_client_device()創建
-
驅動裏寫
- probe(比較常見) 見:lm90_driver and lm90_detect() in drivers/hwmon/lm90.c
- probe(比較常見) 見:lm90_driver and lm90_detect() in drivers/hwmon/lm90.c
-
用戶空間使用sysfs接口
- 例子
echo eeprom 0x50 > /sys/bus/i2c/devices/i2c-3/new_device
或者delete_device
- 例子
核心 I2C-Core
總線部分 BUS主控/片上外設驅動 Adapter
這部分一般是原廠在弄
//i2c.h裏的適配器結構體
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 */
const struct i2c_lock_operations *lock_ops;
struct rt_mutex bus_lock;
struct rt_mutex mux_lock;
int timeout; /* in jiffies */
int retries;
struct device dev; /* the adapter device */
unsigned long locked_flags; /* owned by the I2C core */
#define I2C_ALF_IS_SUSPENDED 0
#define I2C_ALF_SUSPEND_REPORTED 1
int nr;
char name[48];
struct completion dev_released;
struct mutex userspace_clients_lock;
struct list_head userspace_clients;
struct i2c_bus_recovery_info *bus_recovery_info;
const struct i2c_adapter_quirks *quirks;
struct irq_domain *host_notify_domain;
struct regulator *bus_regulator;
struct dentry *debugfs;
};
//設備器成員函數-收發接口
struct i2c_algorithm {
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
int (*master_xfer_atomic)(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
int (*smbus_xfer)(struct i2c_adapter *adap, u16 addr,unsigned short flags, char read_write, u8 command, int size, union i2c_smbus_data *data);
int (*smbus_xfer_atomic)(struct i2c_adapter *adap, u16 addr,unsigned short flags, char read_write, u8 command, int size, union i2c_smbus_data *data);
u32 (*functionality)(struct i2c_adapter *adap);
#if IS_ENABLED(CONFIG_I2C_SLAVE);
int (*reg_slave)(struct i2c_client *client);
int (*unreg_slave)(struct i2c_client *client);
#endif;
};
//適配器成員函數-鎖定解鎖操作
struct i2c_lock_operations {
void (*lock_bus)(struct i2c_adapter *adapter, unsigned int flags);
int (*trylock_bus)(struct i2c_adapter *adapter, unsigned int flags);
void (*unlock_bus)(struct i2c_adapter *adapter, unsigned int flags);
};
//適配器成員-特性結構體
struct i2c_adapter_quirks {
u64 flags;
int max_num_msgs;
u16 max_write_len;
u16 max_read_len;
u16 max_comb_1st_msg_len;
u16 max_comb_2nd_msg_len;
};
//適配器成員函數-操作兩根線
struct i2c_bus_recovery_info {
int (*recover_bus)(struct i2c_adapter *adap);
int (*get_scl)(struct i2c_adapter *adap);
void (*set_scl)(struct i2c_adapter *adap, int val);
int (*get_sda)(struct i2c_adapter *adap);
void (*set_sda)(struct i2c_adapter *adap, int val);
int (*get_bus_free)(struct i2c_adapter *adap);
void (*prepare_recovery)(struct i2c_adapter *adap);
void (*unprepare_recovery)(struct i2c_adapter *adap);
struct gpio_desc *scl_gpiod;
struct gpio_desc *sda_gpiod;
struct pinctrl *pinctrl;
struct pinctrl_state *pins_default;
struct pinctrl_state *pins_gpio;
};
從機設備節點
//從機設備結構體
struct i2c_client {
unsigned short flags;
#define I2C_CLIENT_PEC 0x04 ;
#define I2C_CLIENT_TEN 0x10 ;
#define I2C_CLIENT_SLAVE 0x20 ;
#define I2C_CLIENT_HOST_NOTIFY 0x40 ;
#define I2C_CLIENT_WAKE 0x80 ;
#define I2C_CLIENT_SCCB 0x9000 ;
unsigned short addr;
char name[I2C_NAME_SIZE];
struct i2c_adapter *adapter;
struct device dev;
int init_irq;
int irq;
struct list_head detected;
#if IS_ENABLED(CONFIG_I2C_SLAVE);
i2c_slave_cb_t slave_cb;
#endif;
void *devres_group_id;
};
從機設備驅動/板上外設驅動
拿到新的IIC從機設備,首先就是根據其數據手冊適配該外設的驅動
//從機設備驅動接口
struct i2c_driver {
unsigned int class;
int (*probe)(struct i2c_client *client, const struct i2c_device_id *id);
int (*remove)(struct i2c_client *client);
int (*probe_new)(struct i2c_client *client);
void (*shutdown)(struct i2c_client *client);
void (*alert)(struct i2c_client *client, enum i2c_alert_protocol protocol, 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;
int (*detect)(struct i2c_client *client, struct i2c_board_info *info);
const unsigned short *address_list;
struct list_head clients;
};
我們要實現i2c_algorithm的transfer接口,才能完成對I2C主控/Soc的驅動適配。
drivers/i2c
除了一些 include/linux/i2c.h之類相關的頭文件,剩下的實現都在 drivers/i2c
i2c-core.h
頭文件主要是一個i2c_devinfo
結構體和一些static inline靜態內聯函數定義
struct i2c_devinfo {
struct list_head list;
int busnum;
struct i2c_board_info board_info;
};
i2c-core-base.c
core層的實現,支撐了i2c-core-xxx.c的功能。
i2c-core-of.c
是設備樹的實現,主要是match、設備樹sysfs導出、設備樹屬性reg
host-notify
wakeup-source
等支持,註冊/反註冊設備。
i2c-core-acpi.c
是X86特有的ACPI表(ACPI比ARM設備樹更高級強大)
i2c-core-slave.c
是從機設備的核心支持,主要是從機模式選擇、事件、註冊/反註冊的支持。
i2c-core-smbus.c
是SMBus支持。其讀寫最終會調用__i2c_smbus_xfer
到adapter->algo->smbus_xfer()
xxx有acpi表、base、of設備樹、從機slave、SMBUS等類型的註冊/反註冊。
i2c-atr.c
是I2C Address Translator地址翻譯器的縮寫,
i2c-boardinfo.c
用於靜態聲明板載從機I2C設備。這個源碼很少,僅有一個函數(加鎖,然後把填充i2c_devinfo
並加到i2c_board鏈表)。
i2c-dev.c
主要是SoC片上i2c總線控制器的驅動實現,即I2C主機設備字符驅動,代表了一個 i2c_adapter
struct i2c_dev {
struct list_head list;
struct i2c_adapter *adap;
struct device dev;
struct cdev cdev;
};
i2c-mux.c
是I2C多路總線驅動(Multiplexed I2C bus driver),用於支持控制器的多路通道設計。
i2c-smbus.c
是其SMBus驅動實現,幾乎所有的I2C主控都是同時支持SMBus的。
i2c-stub.c
是I2C/SMBus芯片模擬器
剩下的兩個i2c-slave-eeprom.c
和i2c-slave-testuint.c
都是I2C從機模擬器
目錄 drivers/i2c/algos
下面是一些通用控制器的algo實現,比較古老了一般用不到,現在都是高度集成到SoC裏了。目前實現了移位寄存器類型/PCF8584/PCA9564這幾類拓展的適配器。放張PCF8584的框圖欣賞一下,FPGA要實現也可以參考這種。
目錄drivers/i2c/busses
則較爲龐大,是各廠家提供的總線驅動,遵守內核的i2c_adapter模型。SoC廠家可能會使用不同的IP定製,使得自家SoC能採用不同的I2C控制器,但是他們一般對片上外設的控制器是一致的,這個叫做總線控制。例如博通的[drivers/i2c/busses/i2c-bcm2835.c](struct bcm2835_i2c_dev {)
和賽靈思的 drivers/i2c/busses/i2c-xiic.c ,雖然看起來複雜,但裏面也基本都是ARM核通過寄存器配置片上外設(I2C控制器),比單片機裸機寄存器編程難,但也不會太難。
這部分只有需要定製自己的SoC時才需要,一般能做SoC的都是原廠。
目錄drivers/i2c/muxes
是一些板上外設的分線器/拓展器驅動,例如你SoC線不夠用了,可以通過在板子上加個外設如PCA9544
來拓展I2C的,讓你1路變4路,多爽啊。
總結
- i2c-core是內核對I2c的核心支持,最主要的就是提供了適配器模型,
- 廠家提供的總線驅動
drivers/i2c/busses
都需要遵循這個適配器模型,而裏面的操作基本都是寫處理器的寄存器,由處理器最後去操作對應的控制器IP - 板載外設IIC設備的驅動編寫則使用內核提供的Client API,見
https://docs.kernel.org/i2c/writing-clients.html
- 板載外設IIC拓展器使用的是
drivers/i2c/muxes
- SMBUS有自己的讀寫概念,所以額外實現。