Linux驅動之I2C總線設備以及驅動

[ 導讀] 本文通過閱讀內核代碼,來梳理一下I2C子系統的整體視圖。在開發I2C設備驅動程序時,往往缺乏對於系統整體的認識,導致沒有一個清晰的思路。所以從高層級來分析一下I2C系統的設計思路,將有助於設計調試具體的驅動程序。

I2C/SMBUS基礎

I2C是一種芯片間通訊總線技術,最早由Philips設計制定。下面內容參考I2C 2.1 規格書

  • 半雙工通信方式,通信採用主/從結構

  • 支持多主模式,下圖來源於I2C 2.0規格書

  • 其內部電氣實現採用集電極開路(Open-collector)/漏極開路(open-drain)結構以實現線與功能,這是總線的實現基礎,多芯片通過查詢總線狀態實現介質仲裁以實現總線控制。

  • 總線信號由兩線實現,串行時鐘線SCL(Serial Clock Line)/串行數據線SDA(serial Data Line)。
  • 具有三種通訊速率模式:
    • standard mode:0-100 kbps (bps: bit/s)
    • Fast mode:0-400 kbps
    • High-speed mode : 0-3.4Mbps
  • 可支持混速模式

  • 不同的速率在硬件設計時需要注意信號的完整性,I2C總線等效電容cx
  • 支持7bit/10bit 兩種芯片地址模式
  • I2C總線電氣特性,這個非常重要,須嚴格遵守標準的電氣特性

  • SMBUS(system management bus) 。 大多數SMBus系統也符合I2C,電氣約束對於SMBus更為嚴格,並且它標準化了特定的協議消息和習慣用語。 支持I2C的控制器也可以支持大多數SMBus操作,但是SMBus控制器並不支持I2C控制器將支持的所有協議選項。 通過使用I2C原語或通過向不支持這些I2C操作的i2c_adapter設備發出SMBus命令,可以執行各種SMBus協議操作。

    //smbus.org/

  • I2C bus(Inter-Integrated Circuit bus) //www.i2c-bus.org/

I2C 在Linux設備中的拓撲結構

  • 在PC體系中,大體如下拓撲:

PC體系中通過橋接芯片,擴展出PCI,在由PCI擴展出I2C適配器,進而得到I2C總線,或者橋接芯片直接擴展出SMBUS/I2C總線。

  • 在嵌入式應用中,則可能為:

嵌入式應用中,則可能更多的情況是處理器內置了I2C/SMBUS總線控制器,直接可得到I2C/SMBUS總線。嵌入式系統中常常會設計很多傳感器掛載在I2C總線上,比如溫度檢測,壓力檢測等等,又或者諸如電容觸摸屏、電源管理IC等等。

代碼實現

  • I2C 的core實現位於./drivers/i2c/下,實現了I2C總線設備以及驅動(適配器)和設備驅動的註冊、註銷方法,I2C通信方法algorithm抽象,以及與具體硬件無關的代碼
  • I2C主控制器驅動位於 ./drivers/i2c/busses/,這裡主要實現總線控制器,具體體現為i2c_adapter的實現。負責I2C適配器與從設備通信。I2C總線驅動由i2c_adapter和i2c_algorithm來抽象描述。
  • 設備驅動則分散在./driver/下,這取決於具體的實現,種類繁多。
    • i2c-dev,大多位於drivers/i2c/i2c-dev.c,這種方法只是封裝了主機(I2Cmaster,一般是SoC中內置的I2C控制器)的I2C基本操作,並且嚮應用層提供相應的操作接口,應用層代碼需要自己去實現對slave的控制和操作,所以這種I2C驅動相當於提供給應用層可以訪問slave硬件設備的接口,本身並未對硬件做任何操作,應用需要實現對硬件的操作。這種模式也稱為應用驅動程序。
    • 另一種I2C驅動是將所有的代碼都放在驅動層實現,直接嚮應用層提供最終結果。應用層甚至不需要知道這裏面有I2C存在,譬如電容式觸摸屏驅動,直接嚮應用層提供/dev/input/event1的操作接口,應用層編程的人根本不知道event1中涉及到了I2C。

I2C子系統的主要目的是,對I2C總線以及設備利用面向對象編程思想實現統一建模,以高內聚-低耦合的軟件工程思想,實現一個分層體系結構,以便於內核統一管理I2C設備,從而可以更容易的在linux下實現I2C設備以及高可移植。

主要數據結構

其內部有幾個關鍵數據結構,來梳理一下:

  • i2c_client, 用於抽象掛載在I2C總線上的從設備
  • i2c_driver,用於驅動掛載在I2C總線生的從設備,也即從設備的設備驅動程序
  • i2c_adapter,用於抽象I2C的主設備
  • i2c_algorithm,抽象I2C總線操作接口

i2c_devinfo

該結構體主要用於板級I2C信息管理

i2c_msg

該結構體主要用於抽象I2C報文,其內容如下:

i2c_timings

主要用於抽象I2C電氣特性,對於支持設備樹的系統構建而言,主要通過以下內核接口函數,從設備樹解析電氣特性參數。

void i2c_parse_fw_timings(struct device *dev, struct i2c_timings *t, bool use_defaults)
{
	int ret;

	memset(t, 0, sizeof(*t));

	ret = device_property_read_u32(dev, "clock-frequency", &t->bus_freq_hz);
	if (ret && use_defaults)
		t->bus_freq_hz = 100000;

	ret = device_property_read_u32(dev, "i2c-scl-rising-time-ns", &t->scl_rise_ns);
	if (ret && use_defaults) {
		if (t->bus_freq_hz <= 100000)
			t->scl_rise_ns = 1000;
		else if (t->bus_freq_hz <= 400000)
			t->scl_rise_ns = 300;
		else
			t->scl_rise_ns = 120;
	}

	ret = device_property_read_u32(dev, "i2c-scl-falling-time-ns", &t->scl_fall_ns);
	if (ret && use_defaults) {
		if (t->bus_freq_hz <= 400000)
			t->scl_fall_ns = 300;
		else
			t->scl_fall_ns = 120;
	}

	device_property_read_u32(dev, "i2c-scl-internal-delay-ns", &t->scl_int_delay_ns);

	ret = device_property_read_u32(dev, "i2c-sda-falling-time-ns", &t->sda_fall_ns);
	if (ret && use_defaults)
		t->sda_fall_ns = t->scl_fall_ns;

	device_property_read_u32(dev, "i2c-sda-hold-time-ns", &t->sda_hold_ns);
}
EXPORT_SYMBOL_GPL(i2c_parse_fw_timings);

i2c_device_identity

該結構體主要用於抽象I2C 設備的ID屬性,通過內核接口函數i2c_get_device_id以獲取設備ID屬性。

總體框架

概述

Linux I2C編程接口支持總線交互的主端和從端。從高層級看由兩種驅動程序和兩種設備構成:

  • 適配器設備與適配器設備驅動對:I2C 適配器驅動程序用於抽象控制器硬件;它綁定到一個物理設備(可能是一個PCI設備(PC體系多一些)或platform_device(嵌入式應用居多)),並構建i2c_adapter實體以呈現所管理的1個I2C總線段。

    • platform_device。 比如:i2c-s3c2410,如下:
static const struct platform_device_id s3c24xx_driver_ids[] = {
	{
		.name		= "s3c2410-i2c",
		.driver_data	= 0,
	}, {
		.name		= "s3c2440-i2c",
		.driver_data	= QUIRK_S3C2440,
	}, {
		.name		= "s3c2440-hdmiphy-i2c",
		.driver_data	= QUIRK_S3C2440 | QUIRK_HDMIPHY | QUIRK_NO_GPIO,
	}, { },
};
MODULE_DEVICE_TABLE(platform, s3c24xx_driver_ids);
  • pci-I2C 適配器設備。如在i2c-ali1535.c中:
/* ALI1535 device address register bits */
#define	ALI1535_RD_ADDR		0x01	/* Read/Write Bit in Device	*/
					/*  Address field		*/
					/*  -> Write = 0		*/
					/*  -> Read  = 1		*/
#define	ALI1535_SMBIO_EN	0x04	/* SMB I/O Space enable		*/
/*PCI 設備驅動*/
static struct pci_driver ali1535_driver;
static unsigned long ali1535_smba;
static unsigned short ali1535_offset;
  • I2C從設備及設備驅動對:每個I2C總線段上將有一個由結構i2c_client表示的I2C設備。這些設備將被綁定到一個struct i2c_driver,遵循標準的Linux驅動程序模型。

架構

圖片來源://www.kernel.org/doc/html/latest/i2c/slave-interface.html

主端總線驅動職責:

  • 適配器和算法驅動程序,見下面i2c_algorithm結構體詳細描述
  • 管理I2C總線交互

從端設備驅動職責:

  • i2c_client結構體具有設備的I2C總線地址以及適配器的驅動程序指針

當用戶程序發出文件操作申請I2C事務時

  1. i2C_transfer (i2C-core.c) 調用 adap_algo_master_xfer,數據或消息以i2c_msg結構體傳入。
  2. 適配器對硬件I / O地址進行讀/寫操作,實現底層的I2C讀寫設備操作。

從應用程序直到底層的大致交互流程如下:

總結一下

I2C總線子系統在Linux內核中I2C總線模型分為主/從兩端,主端主要有適配器以及適配器驅動負責管理總線,從端主要有從設備抽象以及設備驅動實現具體的從設備應用。主端適配器以兩種形式存在於內核代碼PCI橋接適配器或者platform_device形式。從總體理解I2C子系統的驅動模型,以及相應主要數據結構之間的關係,將有助於開發調試驅動程序,快速定位問題。
文章出自微信公眾號:嵌入式客棧,由於時間關係,博客可能無法及時更新,最新內容,請關注本人公眾號,嚴禁商業使用,違法必究