從零開始之驅動發開、linux驅動(四十七、linux下的IIC框架【1】)

I2C總線僅僅使用SCL、 SDA這兩根信號線就實現了設備之間的數據交互, 極大地簡化了對硬件資源和PCB板佈線空間的佔用。 因此, I2C總線非常廣泛地應用在EEPROM、 實時鐘、 小型LCD等設備與CPU的接口中。
 

如果在裸機情況下直接操作I2C,其實很簡單,只要正確把握IIC的操作時序就可以了。

https://blog.csdn.net/qq_16777851/article/details/81024031

但是在linux系統中,I2C子系統結構是比較複雜的,因爲它涉及到很多linux內核相關的知識,理解起來十分費勁。

 在Linux系統中, I2C驅動由3部分組成, 即I2C核心、 I2C總線驅動和I2C設備驅動。 這3部分相互協作, 形成了非常通用、 可適應性很強的I2C框架。
 

(一)I2C架構概述

我們都知道,I2C協議是主從式的,包括master(主設備)和slave(從設備),而Linux kernel的I2C framework只抽象了I2C master有關的功能,

Linux 的I2C體系架構分爲3個組成部分:

(1)I2C核心:I2C核心提供了I2C總線驅動和總線設備註冊,註銷,通信方法。還提供了與適配器無關的代碼以及探測代碼等。

(2)I2C總線驅動:I2C總線驅動是對I2C硬件體系結構中適配器驅動的實現。I2C總線驅動主要包含I2C適配器數據結構i2c_adapter、 I2C適配器的Algorithm數據結構i2c_algorithm和控制I2C適配器產生通信信號的函數。經由I2C總線驅動的代碼, 我們可以控制I2C適配器以主控方式產生開始位、 停止位、 讀寫週期, 以及以從設備方式被讀寫、 產生ACK等。(IIC使用的是平臺設備驅動模型)

(3)I2C設備驅動:I2C設備驅動(也稱爲客戶驅動) 是對I2C硬件體系結構中設備端的實現, 設備一般掛接在受CPU控制的I2C適配器上, 通過I2C適配器與CPU交換數據。I2C設備驅動主要包含數據結構i2c_driver和i2c_client, 我們需要根據具體設備實現其中的成員函數。
 

 

 

 

注:這裏先按照2.6.35.7的內核分析,這裏分析完了會按照最新的4.19版本的內核再分析的。

在Linux 2.6內核中, 所有的I2C設備都在sysfs文件系統中顯示, 存於/sys/bus/i2c/目錄下, 以適配器地
址和芯片地址的形式列出, 例如:
 

$ tree /sys/bus/i2c/
/sys/bus/i2c/
|-- devices
| |-- i2c0 -> ../../../devices/platform/versatile-i2c.0/i2c-0
| '-- i2c1 -> ../../../devices/platform/versatile-i2c.0/i2c-1
'-- drivers
'-- dummy

 

在Linux內核源代碼中的drivers目錄下有一個i2c目錄, 而在i2c目錄下又包含如下文件和文件夾。
(1) i2c-core.c
這個文件實現了I2C核心的功能以及/proc/bus/i2c*接口。
(2) i2c-dev.c
實現了I2C適配器設備文件的功能, 每一個I2C適配器都被分配一個設備。 通過適配器訪問設備時的主設備號都爲89, 次設備號爲0~255。 應用程序通過“i2c-%d”(i2c-0, i2c-1, …, i2c-10, …) 文件名並使用文件操作接口open() 、 write() 、 read() 、 ioctl() 和close() 等來訪問這個設備。i2c-dev.c並不是針對特定的設備而設計的, 只是提供了通用的read() 、 write() 和ioctl() 等接口,應用層可以借用這些接口訪問掛接在適配器上的I2C設備的存儲空間或寄存器, 並控制I2C設備的工作方式。
(3) busses文件夾
這個文件包含了一些I2C主機控制器的驅動, 如i2c-tegra.c、 i2c-omap.c、 i2c-versatile.c、 i2c-s3c2410.c等。
(4) algos文件夾
實現了一些I2C總線適配器的通信方法。此外, 內核中的i2c.h頭文件對i2c_adapter、 i2c_algorithm、 i2c_driver和i2c_client這4個數據結構進行了定義。 理解這4個結構體的作用十分重要, 它們的定義位於include/linux/i2c.h文件中
 


/*
 * 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 id;
	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 list_head userspace_clients;
};

 

 

/*
 * The following structs are for those who like to implement new bus drivers:
 * i2c_algorithm is the interface to a class of hardware solutions which can
 * be addressed using the same bus algorithms - i.e. bit-banging or the PCF8584
 * to name two of the most common.
 */
struct i2c_algorithm {
	/* If an adapter algorithm can't do I2C-level access, set master_xfer
	   to NULL. If an adapter algorithm can do SMBus access, set
	   smbus_xfer. If set to NULL, the SMBus protocol is simulated
	   using common I2C messages */
	/* master_xfer should return the number of messages successfully
	   processed, or a negative value on error */
	int (*master_xfer)(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);

	/* To determine what the adapter supports */
	u32 (*functionality) (struct i2c_adapter *);
};

 


/**
 * struct i2c_driver - represent an I2C device driver
 * @class: What kind of i2c device we instantiate (for detect)
 * @attach_adapter: Callback for bus addition (for legacy drivers)
 * @detach_adapter: Callback for bus removal (for legacy drivers)
 * @probe: Callback for device binding
 * @remove: Callback for device unbinding
 * @shutdown: Callback for device shutdown
 * @suspend: Callback for device suspend
 * @resume: Callback for device resume
 * @command: Callback for bus-wide signaling (optional)
 * @driver: Device driver model driver
 * @id_table: List of I2C devices supported by this driver
 * @detect: Callback for device detection
 * @address_list: The I2C addresses to probe (for detect)
 * @clients: List of detected clients we created (for i2c-core use only)
 *
 * The driver.owner field should be set to the module owner of this driver.
 * The driver.name field should be set to the name of this driver.
 *
 * For automatic device detection, both @detect and @address_data must
 * be defined. @class should also be set, otherwise only devices forced
 * with module parameters will be created. The detect function must
 * fill at least the name field of the i2c_board_info structure it is
 * handed upon successful detection, and possibly also the flags field.
 *
 * If @detect is missing, the driver will still work fine for enumerated
 * devices. Detected devices simply won't be supported. This is expected
 * for the many I2C/SMBus devices which can't be detected reliably, and
 * the ones which can always be enumerated in practice.
 *
 * The i2c_client structure which is handed to the @detect callback is
 * not a real i2c_client. It is initialized just enough so that you can
 * call i2c_smbus_read_byte_data and friends on it. Don't do anything
 * else with it. In particular, calling dev_dbg and friends on it is
 * not allowed.
 */
struct i2c_driver {
	unsigned int class;

	/* Notifies the driver that a new bus has appeared or is about to be
	 * removed. You should avoid using this if you can, it will probably
	 * be removed in a near future.
	 */
	int (*attach_adapter)(struct i2c_adapter *);
	int (*detach_adapter)(struct i2c_adapter *);

	/* Standard driver model interfaces */
	int (*probe)(struct i2c_client *, const struct i2c_device_id *);
	int (*remove)(struct i2c_client *);

	/* driver model interfaces that don't relate to enumeration  */
	void (*shutdown)(struct i2c_client *);
	int (*suspend)(struct i2c_client *, pm_message_t mesg);
	int (*resume)(struct i2c_client *);

	/* Alert callback, for example for the SMBus alert protocol.
	 * The format and meaning of the data value depends on the protocol.
	 * For the SMBus alert protocol, there is a single bit of data passed
	 * as the alert response's low bit ("event flag").
	 */
	void (*alert)(struct i2c_client *, unsigned int data);

	/* a ioctl like command that can be used to perform specific functions
	 * with the device.
	 */
	int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);

	struct device_driver driver;
	const struct i2c_device_id *id_table;

	/* Device detection callback for automatic device creation */
	int (*detect)(struct i2c_client *, struct i2c_board_info *);
	const unsigned short *address_list;
	struct list_head clients;
};

 


/**
 * 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_adapter、 i2c_algorithm、 i2c_driver和i2c_client這4個數據結構的作用及其關係。
 

(1) i2c_adapter與i2c_algorithm
i2c_adapter對應於物理上的一個適配器, 而i2c_algorithm對應一套通信方法。 一個I2C適配器需要i2c_algorithm提供的通信函數來控制適配器產生特定的訪問週期。 缺少i2c_algorithm的i2c_adapter什麼也做不了, 因此i2c_adapter中包含所使用的i2c_algorithm的指針。
i2c_algorithm中的關鍵函數master_xfer() 用於產生I2C訪問週期需要的信號, 以i2c_msg(即I2C消息) 爲單位。 i2c_msg結構體也是非常重要的, 它定義於include/uapi/linux/i2c.h(在uapi目錄下, 證明用戶空間的應用也可能使用這個結構體) 中, 下面給出了它的定義, 其中的成員表明了I2C的傳輸地址、 方向、 緩衝區、 緩衝區長度等信息。
 

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			*/
};

 

 

(2) i2c_driver與i2c_client
i2c_driver對應於一套驅動方法, 其主要成員函數是probe() 、 remove() 、 suspend() 、resume() 等, 另外, struct i2c_device_id形式的id_table是該驅動所支持的I2C設備的ID表。 i2c_client對應於真實的物理設備, 每個I2C設備都需要一個i2c_client來描述。 i2c_driver與i2c_client的關係是一對多, 一個i2c_driver可以支持多個同類型的i2c_client。
 

i2c_client的信息通常在BSP的板文件中通過i2c_board_info填充, 如下面的代碼就定義了一個I2C設備的ID爲“wm8580”、 地址爲0x1b、 的i2c_client:

static struct i2c_board_info i2c_devs0[] __initdata = {
	{
		I2C_BOARD_INFO("wm8580", 0x1b),
	},
};

 


/**
 * struct i2c_board_info - template for device creation
 * @type: chip type, to initialize i2c_client.name
 * @flags: to initialize i2c_client.flags
 * @addr: stored in i2c_client.addr
 * @platform_data: stored in i2c_client.dev.platform_data
 * @archdata: copied into i2c_client.dev.archdata
 * @irq: stored in i2c_client.irq
 *
 * I2C doesn't actually support hardware probing, although controllers and
 * devices may be able to use I2C_SMBUS_QUICK to tell whether or not there's
 * a device at a given address.  Drivers commonly need more information than
 * that, such as chip type, configuration, associated IRQ, and so on.
 *
 * i2c_board_info is used to build tables of information listing I2C devices
 * that are present.  This information is used to grow the driver model tree.
 * For mainboards this is done statically using i2c_register_board_info();
 * bus numbers identify adapters that aren't yet available.  For add-on boards,
 * i2c_new_device() does this dynamically with the adapter already known.
 */
struct i2c_board_info {
	char		type[I2C_NAME_SIZE];
	unsigned short	flags;
	unsigned short	addr;
	void		*platform_data;
	struct dev_archdata	*archdata;
#ifdef CONFIG_OF
	struct device_node *of_node;
#endif
	int		irq;
};

在I2C總線驅動i2c_bus_type的match() 函數i2c_device_match() 中, 會調用i2c_match_id() 函數匹配在板文件中定義的ID和i2c_driver所支持的ID表。
 

 

當然這種匹配還是以名字方式匹配的

static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
	struct i2c_client	*client = i2c_verify_client(dev);
	struct i2c_driver	*driver;

	if (!client)
		return 0;

	driver = to_i2c_driver(drv);
	/* match on an id table if there is one */
	if (driver->id_table)
		return i2c_match_id(driver->id_table, client) != NULL;

	return 0;
}

static const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id,
						const struct i2c_client *client)
{
	while (id->name[0]) {
		if (strcmp(client->name, id->name) == 0)
			return id;
		id++;
	}
	return NULL;
}

 

(3) i2c_adpater與i2c_client
i2c_adpater與i2c_client的關係與I2C硬件體系中適配器和設備的關係一致, 即i2c_client依附於i2c_adpater。 由於一個適配器可以連接多個I2C設備, 所以一個i2c_adpater也可以被多個i2c_client依附,i2c_adpater中包括依附於它的i2c_client的鏈表。

假設I2C總線適配器xxx上有兩個使用相同驅動程序的yyy I2C設備, 在打開該I2C總線的設備節點後,相關數據結構之間的邏輯組織關係將如圖。
 

 

從上面的分析可知, 雖然I2C硬件體系結構比較簡單, 但是I2C體系結構在Linux中的實現卻相當複雜。 當工程師拿到實際的電路板時, 面對複雜的Linux I2C子系統, 應該如何下手寫驅動呢? 究竟有哪些是需要親自做的, 哪些是內核已經提供的呢? 理清這個問題非常有意義, 可以使我們在面對具體問題時迅速抓住重點。
 

一方面, 適配器驅動可能是Linux內核本身還不包含的; 另一方面, 掛接在適配器上的具體設備驅動可能也是Linux內核還不包含的。 因此, 工程師要實現的主要工作如下。

  • 提供I2C適配器的硬件驅動, 探測、 初始化I2C適配器(如申請I2C的I/O地址和中斷號) 、 驅動CPU控制的I2C適配器從硬件上產生各種信號以及處理I2C中斷等。
  • 提供I2C適配器的Algorithm, 用具體適配器的xxx_xfer() 函數填充i2c_algorithm的master_xfer指針,並把i2c_algorithm指針賦值給i2c_adapter的algo指針。
  • 實現I2C設備驅動中的i2c_driver接口, 用具體設備yyy的yyy_probe() 、 yyy_remove() 、yyy_suspend() 、 yyy_resume() 函數指針和i2c_device_id設備ID表賦值給i2c_driver的probe、 remove、suspend、 resume和id_table指針。
  • 實現I2C設備所對應類型的具體驅動, i2c_driver只是實現設備與總線的掛接, 而掛接在總線上的設備則千差萬別。 例如, 如果是字符設備, 就實現文件操作接口, 即實現具體設備yyy的yyy_read() 、yyy_write() 和yyy_ioctl() 函數等; 如果是聲卡, 就實現ALSA驅動。
     

上述工作中前兩個屬於I2C總線驅動, 後兩個屬於I2C設備驅動。
 

 

這裏借用一位網友總結來說明一下IIC在linux中的框架(圖片原文鏈接會在後面附上)

這裏我們從下向上看

1.i2c Device都是掛接在一個i2c總線上的,這部分通常屬於硬件工程師來實現的。

2.i2c adaptor,就是我們通常所說的i2c總線控制器了,這裏也叫適配器。通常一個soc會有不止一個i2c適配器,當然這個也是硬件相關的,屬於芯片廠商的做的部分。

3.i2c adaptor的驅動程序,這部分通常要包括一些寄存器的實現,底層發生接收函數的接口,這個通常是芯片廠商的工程師來實現。

4.再向上,就屬於純軟件的內容了。i2c核心層,這部分內容通常是由寫內核驅動框架的大牛們完成,這部分主要是起到一個承上啓下的作用。

5.再向上,設備驅動層,這些驅動主要就是space1中所指的這些設備執行所需要的讀寫驅動程序,這寫設備驅動通常也叫Client。

6.再網上就是用戶程序了,用戶程序是直接使用具體設備的,並不會關心底層總線之類的實現細節。

 

這裏我們回到核心層,再看一下。

一個iic設備,是不會關心,自己掛接在那個iic總線控制器上的。它這個設備(Client)驅動程序更關心的是這個設備怎麼用。

比如對一個RTC設備,時間怎麼讀,具體的寄存器地址在是多少,時間如何修改等。或者比如一個觸摸屏驅動程序,關注的是觸摸屏按下位置的座標,是不是多點觸摸的等等。

 

 

 

圖片鏈接

https://blog.csdn.net/li_wen01/article/details/51657040

還參考了《linux設備驅動開發詳解》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章