Linux內核之I2C協議

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)與速度和容性負載相關,可以決定穩定性。
image

image

而且標準文檔還提供了不同電平之間的電路兼容方案
image

連接方式

所有從機SDA都並聯到主機上,所有從機SCK線都並聯到主機SCL上。

時序圖

關鍵點:
空閒狀態:SCL/SDA都是高電平。
工作狀態:SCL脈衝在高電平狀態下指示SDA有效。

經典的開始和結束信號

SDA下降沿表示開始,上升沿表示結束

image

多機衝突與通訊
有低電平的話,同一線上的都會被拉低。
接到同一SCL線的都會被同步
對端SDA的可判斷電平不一致而打斷操作。
image
image

如果需要FPGA實現,還需要留意標準文檔裏的Table5和Fig.31的時間間隔要求。

主要傳輸形式

速度: 0100KHz400KHz~3.4MHz
不同速度模式的設備混合接入總線系統,速度如表:
image
速度是需要“協商”的,如啓用 High Speed Mode
image

保留地址作爲管理碼(master code),詳見標準手冊 10.1 Definition of bits in the first byte
image

開始條件S 8位管理碼(00001xxx) 一位NACK
image

普通速率下傳輸數據(7地址模式)

其數據可抽象簡化爲
DEV_ADDR BYTE_DATA [..BYTE_DATA]

發送設備地址(7位+R/W標識位) 另外也有10位地址的,就是兩個地址字而已,用得少可以看標準文檔。
發送數據(8位一次)
可選繼續發送數據(每次也都是8位)

每8位都需要從機回覆一個A/A應答位。
也就說,它是基於字節傳輸的超短距簡單低速協議。
I2C文檔-時序

I2C時序
一般來說,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 指令
image

有個案例: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)

一些重要的結構體

架構分層

貼張經典老圖,出處找不到了,侵權請聯繫

image

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()創建
      image
    • 調用i2c_new_scanned_device() 等API函數
      image
  • 驅動裏寫

  • 用戶空間使用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
src-file-tree

i2c-core.h頭文件主要是一個i2c_devinfo結構體和一些static inline靜態內聯函數定義

struct i2c_devinfo {
	struct list_head	list;
	int			busnum;
	struct i2c_board_info	board_info;
};

image

i2c-core-base.ccore層的實現,支撐了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_xferadapter->algo->smbus_xfer()
image

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
image

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的。
image
i2c-stub.c是I2C/SMBus芯片模擬器
image
剩下的兩個i2c-slave-eeprom.ci2c-slave-testuint.c都是I2C從機模擬器

目錄 drivers/i2c/algos下面是一些通用控制器的algo實現,比較古老了一般用不到,現在都是高度集成到SoC裏了。目前實現了移位寄存器類型/PCF8584/PCA9564這幾類拓展的適配器。放張PCF8584的框圖欣賞一下,FPGA要實現也可以參考這種。
image

目錄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路,多爽啊。
image

總結

  • i2c-core是內核對I2c的核心支持,最主要的就是提供了適配器模型,
  • 廠家提供的總線驅動drivers/i2c/busses都需要遵循這個適配器模型,而裏面的操作基本都是寫處理器的寄存器,由處理器最後去操作對應的控制器IP
  • 板載外設IIC設備的驅動編寫則使用內核提供的Client API,見 https://docs.kernel.org/i2c/writing-clients.html
  • 板載外設IIC拓展器使用的是 drivers/i2c/muxes
  • SMBUS有自己的讀寫概念,所以額外實現。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章