二十三、Linux驅動之IIC驅動(基於linux4.12內核)

1. 基本概念

本文默認讀者掌握裸機下的I2C操作,該部分只做簡單介紹, 主要內容是對linux-4.12系統下I2C驅動的分析。(上一篇二十一、Linux驅動之IIC驅動(基於linux2.6.22.6內核)linux-2.6.22.6內核的I2C進行了分析,新內核的I2C有了很大的變化,但是也有部分類似,爲了保證完整性,我會全部從頭分析。linux-4.12的移植和對應之前驅動的移植以後會進行更新)

    I2C是由數據線SDA和時鐘SCL構成的串行總線,可發送和接收數據,是一個多主機的半雙工通信方式,每個掛接在總線上的器件都有個唯一的地址。位速在標準模式下可達100kbit/s,在快速模式下可達400kbit/s,在高速模式下可待3.4Mbit/s
    I2C總線僅僅使用SCL、 SDA這兩根信號線就實現了設備之間的數據交互, 極大地簡化了對硬件資源和PCB板佈線空間的佔用。 因此, I2C總線非常廣泛地應用在EEPROM、 實時鐘、 小型LCD等設備與CPU的接口中。

2. I2C結構

2.1 文件結構

    在linux-4.12內核的driver/i2c目錄下內有如下文件:
 

2.1.1 algos文件夾

    裏面保存I2C的通信方面的算法(algorithms)

2.1.2 busses文件夾

    裏面保存I2C總線驅動相關的文件,比如i2c-omap.c、 i2c-versatile.c、 i2c-s3c2410.c等。

2.1.3 muxes文件夾

    裏面保存I2C切換芯片相關驅動,本節不使用。

2.1.4 i2c-core.c文件

    完成I2C總線、設備、驅動模型,對用戶提供sys文件系統訪問支持;爲I2C內部adpter等提供註冊接口。

2.1.5 i2c-dev.c文件

    實現了I2C適配器設備文件的功能,每一個I2C適配器都被分配爲一個設備。通過適配器訪問設備是的主設備號都爲89,次設備號爲0~255。應用程序通過“i2c-%d”(i2c-0、i2c-1、i2c-2...)文件名並使用文件操作結構open()、write()、read()、ioctl()close()等來訪問這個設備。
    i2c-dev.c並沒有針對特定的設備而設計,只是提供了通用read()等接口,應用程序可以借用這些接口訪問適配器上的I2C設別的存儲空間或寄存器,並控制I2C設別的工作方式。

2.2 I2C體系架構

    I2C體系架構圖如下:
   
    主要分爲3個部分,I2C核心、I2C總線驅動、I2C設備驅動

2.2.1 I2C核心

    是Linux內核用來維護和管理的I2C的核心部分,其中維護了兩個靜態的List,分別記錄系統中的I2C driver結構和I2C adapter結構。I2C core提供接口函數,允許一個I2C adatper、I2C driverI2C client初始化時在I2C core中進行註冊,以及退出時進行註銷等操作函數。同時還提供了I2C總線讀寫訪問的一般接口。

2.2.2 I2C總線驅動

    I2C總線驅動是對I2C硬件體系結構中適配器端的實現,適配器可由CPU控制,甚至可以直接集成在CPU內部。I2C總線驅動主要包含了I2C適配器數據結構i2c_adapterI2C適配器的算法結構i2c_algorithm和控制I2C適配器產生通信信號的函數。經由I2C總線驅動的代碼,我們可以控制I2C適配器以主控方式產生開始位、停止位、讀寫週期,以及以從設備方式被讀寫、產生ACK等。

2.2.3 I2C設備驅動

    I2C設備驅動是對I2C硬件體系結構中設備端的實現,設備一般掛接在受CPU控制的I2C適配器上,通過I2C適配器與CPU交換數據。I2C設備驅動主要包含了數據結構i2c_driveri2c_client,我們需要根據具體設備實現其中的成員函數,可以爲一個具體的I2C設備開發特定的I2C設備驅動程序,在驅動中完成對特定的數據格式的解釋以及實現一些專用的功能。

2.2.4 各結構之間的關係

1. i2c_adapter與i2c_algorithm

    i2c_adapter對應與物理上的一個適配器,而i2c_algorithm對應一套通信方法,一個i2c適配器需要i2c_algorithm中提供的(i2c_algorithm中的又是更下層與硬件相關的代碼提供)通信函數來控制適配器上產生特定的訪問週期。缺少i2c_algorithmi2c_adapter什麼也做不了,因此i2c_adapter中包含其使用i2c_algorithm的指針。

2. i2c_driver和i2c_client

    i2c_driver對應一套驅動方法,i2c_client對應真實的i2c物理設備device,每個i2c設備都需要一個i2c_client來描述i2c_driveri2c_client的關係是一對多。一個i2c_driver上可以支持多個同等類型的i2c_client

3. i2c_adapter和i2c_client

    i2c_adapteri2c_client的關係與i2c硬件體系中適配器和設備的關係一致,即i2c_client依附於i2c_adapter,由於一個適配器上可以連接多個i2c設備,所以i2c_adapter中包含依附於它的i2c_client的鏈表。

2.2.5 爲什麼構建這麼複雜的I2C體系架構?

    在單片機中使用I2C只需要簡單地配置幾個I2C寄存器,構建包含開始位、停止位、讀寫週期、ACK等簡單概念函數即可,爲什麼在Linux上需要構建這麼複雜的I2C體系架構呢?爲了通用性及爲了符合Linux內核驅動模式。
    對於linux系統來說,支持各式各樣的CPU類型,並且還想要滿足各種I2C芯片硬件特性,就必須將一些穩定性好的部分抽離出來,這樣就抽象出了i2c_client(描述具體的I2C設備)i2c_driver(描述操作I2C設備方法的接口)i2c_adpter(描述CPU如何控制I2C總線)、i2c_algorithm(I2C通信算法)等。前兩個就是抽象出的I2C設備驅動,後兩個爲I2C總線驅動。
    在一個SoC上可能有多條I2C總線,一條總線對應一個I2C總線驅動(I2C適配器),每一條總線上又可以接多個I2C設備。假如一個SoC上有3個I2C適配器,外接3個I2C設備,想要3個適配器分別都能驅動3個I2C設備,我們只需寫3個適配器代碼,3個I2C設備代碼即可。

3. 分析內核(linux-4.12)

    下面開始深入內核分析I2C驅動。

3.1 重要數據結構

    內核的I2C驅動圍繞着以下幾個數據結構進行盤根交錯的展開。

3.1.1 i2c_adapter(I2C適配器結構)

    i2c_adapter是用來描述一個I2C適配器,在SoC中的指的就是內部外設I2C控制器,當向I2C核心層註冊一個I2C適配器時就需要提供這樣的一個結構體變量。

struct i2c_adapter {
	struct module *owner;    //所有者

        /*
        class表示該I2C bus支持哪些類型的I2C從設備,只有匹配的I2C從設備才能和適配器綁定。
        具體的類型包括:
        1. I2C_CLASS_HWMON,硬件監控類,如lm_sensors等;
        2. I2C_CLASS_DDC,DDC是數字顯示通道(Digital Display Channel)的意思, 通常用於顯示設備信息的獲取;
        3. I2C_CLASS_SPD,存儲類的模組;
        4. I2C_CLASS_DEPRECATED,不支持自動探測。
        */
	unsigned int class; 
	const struct i2c_algorithm *algo;    //該適配器與從設備的通信算法
	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;    //超時時間
	int retries;    //重試次數
	struct device dev;    //該適配器設備對應的device

	int nr;    //該適配器的ID,會體現在sysfs中(/sys/bus/i2c/devices/i2c-n中的‘n’)
	char name[48];    //適配器的名字
	struct completion dev_released;    //用來掛接與適配器匹配成功的從設備i2c_client的一個鏈表頭

	struct mutex userspace_clients_lock;    
	struct list_head userspace_clients;    //clients鏈表

	struct i2c_bus_recovery_info *bus_recovery_info;
	const struct i2c_adapter_quirks *quirks;

	struct irq_domain *host_notify_domain;
};

3.1.2 i2c_algorithm(I2C算法結構)

    i2c_algorithm結構體代表的是適配器的通信算法,在構建i2c_adapter結構體變量的時候會去填充這個元素。

struct i2c_algorithm {
        /*
        I2C協議有關的數據傳輸接口,輸入參數是struct i2c_msg類型(可參考2.3小節的介紹)
        的數組(大小由num指定)。返回值是成功傳輸的msg的個數,如有錯誤返回負值。
        */
	int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
			   int num);

        /*SMBUS有關的數據傳輸接口,如果爲NULL,I2C core會嘗試使用master_xfer模擬。*/
	int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
			   unsigned short flags, char read_write,
			   u8 command, int size, union i2c_smbus_data *data);

	/* 
        該I2C adapter支持的功能:
        1. I2C_FUNC_I2C,支持傳統的I2C功能;
        2. I2C_FUNC_10BIT_ADDR,支持10bit地址;
        3. I2C_FUNC_PROTOCOL_MANGLING,支持非標準的協議行爲(具體請參考2.3小節的介紹);
        4. I2C_FUNC_NOSTART,支持不需要發送START信號的I2C傳輸(具體請參考2.3小節的介紹);
        5. I2C_FUNC_SMBUS_xxx,SMBUS相關的功能,不再詳細介紹。
        */
	u32 (*functionality) (struct i2c_adapter *);

#if IS_ENABLED(CONFIG_I2C_SLAVE)
	int (*reg_slave)(struct i2c_client *client);
	int (*unreg_slave)(struct i2c_client *client);
#endif
};

3.1.3 i2c_client(I2C次設備結構)

    i2c_client描述一個I2C次設備。

struct i2c_client {
	unsigned short flags;		// 標誌
	unsigned short addr;		// 該I2C設備的7位設備地址

	char name[I2C_NAME_SIZE];       // 設備名字
	struct i2c_adapter *adapter;	// 該I2C設備支持哪個適配器
	struct device dev;		// 設備結構體
	int irq;			//設備所使用的結構體
	struct list_head detected;      //鏈表頭
#if IS_ENABLED(CONFIG_I2C_SLAVE)
	i2c_slave_cb_t slave_cb;	/* callback for slave mode	*/
#endif
};

3.1.4 i2c_driver(I2C設備驅動結構)

    i2c_driver描述一個I2C設備驅動。

struct i2c_driver {
	unsigned int class;    //所屬類

	/* Notifies the driver that a new bus has appeared. You should avoid
	 * using this, it will be removed in a near future.
	 */
	int (*attach_adapter)(struct i2c_adapter *) __deprecated;    //匹配適配器函數指針,不使用

	/* 標準驅動模型接口 */
	int (*probe)(struct i2c_client *, const struct i2c_device_id *);
	int (*remove)(struct i2c_client *);

	/* 新驅動模型接口 */
	int (*probe_new)(struct i2c_client *);

	/* driver model interfaces that don't relate to enumeration  */
	void (*shutdown)(struct i2c_client *);

        /* 鬧鐘回調函數 */
	void (*alert)(struct i2c_client *, enum i2c_alert_protocol protocol,
		      unsigned int data);

        /* 類似於ioctl的命令接口函數 */
	int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);

	struct device_driver driver;    //該i2c設備驅動所對應的device_driver
	const struct i2c_device_id *id_table;    //存放該i2c設備驅動id名,用於與i2c_client->name比較匹配

	/* 檢測回調函數 */
	int (*detect)(struct i2c_client *, struct i2c_board_info *);
	const unsigned short *address_list;
	struct list_head clients;

	bool disable_i2c_core_irq_mapping;
};

3.1.5 i2c_msg(I2C數據包)

    i2c_msg描述一個I2C數據包。

struct i2c_msg {
	__u16 addr;	//I2C從機的設備地址
	__u16 flags;    //當flags=0表示寫, flags= I2C_M_RD表示讀
#define I2C_M_RD		0x0001	/* read data, from slave to master */
					/* I2C_M_RD is guaranteed to be 0x0001! */
#define I2C_M_TEN		0x0010	/* this is a ten bit chip address */
#define I2C_M_RECV_LEN		0x0400	/* length will be first received byte */
#define I2C_M_NO_RD_ACK		0x0800	/* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK	0x1000	/* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_REV_DIR_ADDR	0x2000	/* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NOSTART		0x4000	/* if I2C_FUNC_NOSTART */
#define I2C_M_STOP		0x8000	/* if I2C_FUNC_PROTOCOL_MANGLING */
	__u16 len;	//傳輸的數據長度,等於buf數組裏的字節數
	__u8 *buf;	//存放數據的數組
};

3.2 分析I2C總線驅動

    內核是最好的學習工具。想要寫一個linux驅動,最好的方法就是分析內核自帶的驅動程序。對於I2C總線驅動我們可以分析內核源碼drivers/i2c/busses/i2c-s3c2410.c文件。

3.2.1 入口函數

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,
	}, { },
};

... ...

static struct platform_driver s3c24xx_i2c_driver = {
	.probe		= s3c24xx_i2c_probe,
	.remove		= s3c24xx_i2c_remove,
	.id_table	= s3c24xx_driver_ids,
	.driver		= {
		.name	= "s3c-i2c",
		.pm	= S3C24XX_DEV_PM_OPS,
		.of_match_table = of_match_ptr(s3c24xx_i2c_match),
	},
};

static int __init i2c_adap_s3c_init(void)
{
	return platform_driver_register(&s3c24xx_i2c_driver);    //以platform模型註冊platform_driver
}

    首先在platform總線上註冊一個platform_driver結構platform總線上有與id_table->name相同(“s3c2440-i2c”)platform_device被註冊或已經註冊時調用s3c2440_i2c_driver->probe函數。也就是s3c24xx_i2c_probe()函數。

3.2.2 s3c24xx_i2c_probe()函數

static int s3c24xx_i2c_probe(struct platform_device *pdev)
{
        struct s3c24xx_i2c *i2c;
	struct s3c2410_platform_i2c *pdata = NULL;   
        ... ...

        pdata = dev_get_platdata(&pdev->dev);    //得到資源
        ... ...
        
        /* 分配相關結構體內存 */
        i2c = devm_kzalloc(&pdev->dev, sizeof(struct s3c24xx_i2c), GFP_KERNEL);
	if (!i2c)
		return -ENOMEM;

	i2c->pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
	if (!i2c->pdata)
		return -ENOMEM;
        ... ...
        
        /* 填充適配器i2c_adapter結構體 */
        strlcpy(i2c->adap.name, "s3c2410-i2c", sizeof(i2c->adap.name));    //填充適配器名字
	i2c->adap.owner = THIS_MODULE;    //所有者    
	i2c->adap.algo = &s3c24xx_i2c_algorithm;    //填充算法
	i2c->adap.retries = 2;    //重試次數
	i2c->adap.class = I2C_CLASS_DEPRECATED;    //支持的設備類型,I2C_CLASS_DEPRECATED表示不支持自動檢測
	i2c->tx_setup = 50;

	init_waitqueue_head(&i2c->wait);        //初始化等待隊列頭

        ...
        i2c->clk = devm_clk_get(&pdev->dev, "i2c");    //獲得時鐘

        /* 獲得資源並映射 */
        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);    
        i2c->regs = devm_ioremap_resource(&pdev->dev, res);

        /*設置i2c_adapter適配器結構體, 將i2c結構體設爲adap的私有數據成員*/
        i2c->adap.algo_data = i2c;
	i2c->adap.dev.parent = &pdev->dev;

        /*初始化I2C控制器*/
        ret = clk_prepare_enable(i2c->clk);    //使能時鐘
	if (ret) {
		dev_err(&pdev->dev, "I2C clock enable failed\n");
		return ret;
	}

	ret = s3c24xx_i2c_init(i2c);    //初始化2440相關寄存器
	clk_disable(i2c->clk);    //使能時鐘

        /*申請中斷*/
        ret = devm_request_irq(&pdev->dev, i2c->irq, s3c24xx_i2c_irq,0, dev_name(&pdev->dev), i2c);

        ... ...
        /*設置i2c_adapter適配器*/
        i2c->adap.nr = i2c->pdata->bus_num;    //設置適配器ID,如果私有數據沒有設置默認爲0
	i2c->adap.dev.of_node = pdev->dev.of_node;

        ... ...
        /*註冊i2c_adapter適配器*/
        ret = i2c_add_numbered_adapter(&i2c->adap);
        ... ...
}

    該函數主要作用如下:
      1. 分配設置i2c_adapter適配器相關結構
      2. 硬件相關設置
      3. 設置等待隊列
      4. 申請中斷
      5. 註冊i2c_adapter適配器

    最後調用i2c_add_numbered_adapter註冊i2c_adapter適配器。

3.2.3 註冊i2c_adapter適配器

    看看該函數是如何實現的:

int i2c_add_numbered_adapter(struct i2c_adapter *adap)
{
	if (adap->nr == -1) /* adap->nr=0,不滿足條件 */
		return i2c_add_adapter(adap);

	return __i2c_add_numbered_adapter(adap);
}

    註冊i2c_adapter適配器有兩個函數,i2c_add_adapter會自動分配adapter IDi2c_add_numbered_adapter則可以指定ID。
   
在內核啓動時調用(mach-smdk2440.c裏)smdk2440_machine_init()函數,裏面又調用s3c_i2c0_set_platdata()函數。函數代碼如下:

void __init s3c_i2c0_set_platdata(struct s3c2410_platform_i2c *pd)
{
	struct s3c2410_platform_i2c *npd;

	if (!pd) {
		pd = &default_i2c_data;
		pd->bus_num = 0;
	}

	npd = s3c_set_platdata(pd, sizeof(struct s3c2410_platform_i2c),
			       &s3c_device_i2c0);

	if (!npd->cfg_gpio)
		npd->cfg_gpio = s3c_i2c0_cfg_gpio;
}

    在s3c_i2c0_set_platdata中設置pd->bus_num = 0而在s3c24xx_i2c_probe()函數中有如下代碼:
   
    可得adap->nr=0,所以i2c_add_numbered_adapter()函數實際調用的是__i2c_add_numbered_adapter函數(i2c_add_adapter自動分配ID後也是調用這個函數)__i2c_add_numbered_adapter函數又調用了i2c_register_adapter()函數,函數如下:

static int i2c_register_adapter(struct i2c_adapter *adap)
{
        ... ...
        dev_set_name(&adap->dev, "i2c-%d", adap->nr);    //設置適配器名字
	adap->dev.bus = &i2c_bus_type;    //設置適配器成員dev的總線爲i2c_bus_type
	adap->dev.type = &i2c_adapter_type;    //設置適配器成員dev的類型爲適配器類型
	res = device_register(&adap->dev);    //註冊適配器成員dev設備

        ... ...
        
        /* 對dts上所描述的i2c_client設備進行實例化,並創建相應的sys文件:sys/bus/i2c/devices/xxx */
        of_i2c_register_devices(adap);
	i2c_acpi_register_devices(adap);
	i2c_acpi_install_space_handler(adap);

        /* 內核啓動時(註冊適配器之前),如果調用i2c_register_board_info()函數註冊
        過i2c_board_info結構(描述i2c_client的結構),就調用i2c_scan_static_board_info
        對這些i2c_board_info結構裏的i2c_client進行實例化 */
        if (adap->nr < __i2c_first_dynamic_bus_num)
	i2c_scan_static_board_info(adap);
        
        ... ...
        /* 遍歷i2c_bus_type匹配i2c_driver與i2c_adapter */
        bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter);
        ... ...
}

    該函數主要作用如下3大點:

3.2.3.1 設置註冊適配器i2c_adapter

    1. 設置適配器名字
    2. 設置適配器成員dev的總線爲i2c_bus_type
    3. 設置適配器成員dev的類型爲適配器類型
    4. 註冊設備(適配器成員dev,以後可以通過這個dev找到該適配器)

3.2.3.2 實例化i2c_client設備(該部分在I2C設備驅動部分詳細講解)

    1. 如果使用了設備樹,對dts上所描述的i2c_client設備進行實例化,並創建相應的sys文件:sys/bus/i2c/devices/xxx。
    2.
內核啓動時(註冊適配器之前),如果調用i2c_register_board_info()函數註冊過i2c_board_info結構(描述i2c_client的結構),就調用i2c_scan_static_board_info()函數對這些i2c_board_info結構裏的i2c_client進行實例化。

3.2.3.3 遍歷i2c_bus_type匹配i2c_driver與i2c_adapter

    匹配過程實際是使用__process_new_adapter()函數來匹配i2c_bus_type上的每一個i2c_drivers,該函數最終又是調用i2c_detect()函數:

static int __process_new_adapter(struct device_driver *d, void *data)
{
	return i2c_do_add_adapter(to_i2c_driver(d), data);
}
... ...
static int i2c_do_add_adapter(struct i2c_driver *driver, struct i2c_adapter *adap)
{
	i2c_detect(adap, driver);
        ... ...
}

    i2c_detect()函數部分代碼如下:

static int i2c_detect(struct i2c_adapter *adapter, struct i2c_driver *driver)
{
	const unsigned short *address_list;
	struct i2c_client *temp_client;
	int i, err = 0;
	int adap_id = i2c_adapter_id(adapter);    //得到適配器ID = adap->nr

	address_list = driver->address_list;    //從i2c_driver得到I2C設備地址列表
        ... ...

	/* 如果適配器dapter->class定義爲不自動檢測類型,函數返回 */
	if (adapter->class == I2C_CLASS_DEPRECATED) {
                ... ...
		return 0;
	}

	/* 如果i2c_driver支持的設備類型與適配器支持的類型不一樣,函數返回 */
	if (!(adapter->class & driver->class))
		return 0;

	/* 分配設置i2c_client */
	temp_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);
	if (!temp_client)
		return -ENOMEM;
	temp_client->adapter = adapter;

        /* 取出每一個address_list裏的設備地址進行檢測 */
	for (i = 0; address_list[i] != I2C_CLIENT_END; i += 1) {
		dev_dbg(&adapter->dev,
			"found normal entry for adapter %d, addr 0x%02x\n",
			adap_id, address_list[i]);
		temp_client->addr = address_list[i];
		err = i2c_detect_address(temp_client, driver);    //檢測該設備地址
		if (unlikely(err))
			break;
	}

	kfree(temp_client);    //釋放i2c_client 
	return err;
}

    該函數主要作用如下:
      1. i2c_driver得到I2C設備地址列表address_list
      2. 判斷適配器i2c_adapteri2c_driver支持的設備類型class
      3. 取出每一個address_list裏的設備地址進行檢測
(調用i2c_detect_address()函數)

    分析作用2:在前面s3c24xx_i2c_probe()函數中定義了適配器i2c_adapterI2C_CLASS_DEPRECATED不自動檢測類型,如下圖:
   

    所以對於i2c-s3c2410.c這個內核(這裏只針對linux-4.12,比如linux-3.4.2 這裏的class設置的是I2C_CLASS_HWMON | I2C_CLASS_SPD)提供的總線驅動來說,在 i2c_detect()函數中什麼也沒做直接返回了。但是對於driver/i2c/busses內核文件內的其他總線驅動,i2c_adapter適配器的class成員設置的是支持自動檢測類型(如設爲I2C_CLASS_HWMON),如果同時滿足adapter->classdriver->class類型相同,就可以繼續往下執行檢測設備地址。
    分析作用3:循環取出i2c_driver->address_list裏存放的設備地址進行檢測,即調用i2c_detect_address()函數:

static int i2c_detect_address(struct i2c_client *temp_client, struct i2c_driver *driver)
{
	struct i2c_board_info info;
	struct i2c_adapter *adapter = temp_client->adapter;
	int addr = temp_client->addr;
	int err;

	/* 確保7位I2C設備地址在0x08~0x77之間 */
	err = i2c_check_7bit_addr_validity_strict(addr);
	if (err) {
		dev_warn(&adapter->dev, "Invalid probe address 0x%02x\n",
			 addr);
		return err;
	}
        ... ...

	/* 確保總線上真實掛接有該設備地址的I2C芯片,通過i2c_smbus_xfer發送設備地址,
        如果有ACK返回,表示確實有真實的I2C芯片接在該總線上,如果沒有ACK,函數返回 */
	if (!i2c_default_probe(adapter, addr))
		return 0;

	/* 調用i2c_driver->detect檢測函數再次檢測芯片具體是什麼芯片,如通過讀寫芯片特定的寄存器 */
	memset(&info, 0, sizeof(struct i2c_board_info));
	info.addr = addr;
	err = driver->detect(temp_client, &info);

        ... ...

        /*創建設備*/
	client = i2c_new_device(adapter, &info);
        ... ...
	return 0;
}

    該函數主要作用如下:
      1. 確保傳入的7位I2C設備地址在0x08~0x77之間(I2C芯片通常是7位設備地址,範圍在0x08~0x77之間)
      2. 確保總線上真實掛接有該設備地址的I2C芯片(在i2c_default_probe()函數中通過i2c_smbus_xfer發送設備地址,如果有ACK返回,表示確實有真實的I2C芯片接在該總線上,如果沒有ACK,函數返回)
      3. 調用i2c_driver->detect檢測函數再次檢測芯片具體是什麼芯片(如通過讀寫I2C芯片特定的寄存器)
      4. 調用i2c_new_device創建設備(創建I2C設備驅動的i2c_client)
    下面先來看看i2c_smbus_xfer()函數是如何檢測設備地址是否真實存在的。

3.2.4 i2c_smbus_xfer()函數

    函數部分代碼如下:

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)
{
        ... ...  

	flags &= I2C_M_TEN | I2C_CLIENT_PEC | I2C_CLIENT_SCCB;

	if (adapter->algo->smbus_xfer) {    //如果適配器指定了algo->smbus_xfer成員
        ... ...
	}

	res = i2c_smbus_xfer_emulated(adapter, addr, flags, read_write,
				      command, protocol, data);    //使用smbus協議發送I2C消息
        ... ...
}

    該函數首先判斷i2c_adapter適配器成員algo->smbus_xfer函數,可以回顧一下i2c-s3c2410.c中的i2c_adapter適配器是如何定義的,s3c24xx_i2c_probe()函數中有如下代碼:
   

    s3c24xx_i2c_algorithm定義如下:

   

    可見適配器並沒有algo->smbus_xfer函數,所以i2c_smbus_xfer()函數實際是使用i2c_smbus_xfer_emulated()函數進行發送數據的,i2c_smbus_xfer_emulated()函數部分代碼如下:

static s32 i2c_smbus_xfer_emulated(struct i2c_adapter *adapter, u16 addr,
				   unsigned short flags,
				   char read_write, u8 command, int size,
				   union i2c_smbus_data *data)
{
	unsigned char msgbuf0[I2C_SMBUS_BLOCK_MAX+3];    //屬於 msg[0]的buf成員
	unsigned char msgbuf1[I2C_SMBUS_BLOCK_MAX+2];    //屬於 msg[1]的buf成員
	int num = read_write == I2C_SMBUS_READ ? 2 : 1;    //如果爲讀命令,就等於2,表示要執行兩次數據傳輸
	int i;
	u8 partial_pec = 0;
	int status;
	struct i2c_msg msg[2] = {
		{
			.addr = addr,
			.flags = flags,
			.len = 1,
			.buf = msgbuf0,
		}, {
			.addr = addr,
			.flags = flags | I2C_M_RD,
			.len = 0,
			.buf = msgbuf1,
		},
	};

	msgbuf0[0] = command;    //IIC設備地址最低位爲讀寫命令

        switch (size) {
        ... ...

        /*讀操作需要兩個i2c_msg,寫操作需要一個i2c_msg*/
	case I2C_SMBUS_BYTE_DATA:
		if (read_write == I2C_SMBUS_READ)    //如果是讀字節
			msg[1].len = 1;
		else {
			msg[0].len = 2;
			msgbuf0[1] = data->byte;
		}
		break;
        ... ...

        status = i2c_transfer(adapter, msg, num);    //將i2c_msg結構體的內容發送給I2C設備
    	if (status < 0)    //返回值不等於0表示沒有收到ACK
		return status;
	}

    可見該函數是通過構造i2c_msg結構體通過i2c_transfer()函數發送給I2C設備,如果I2C設備返回ACK表示設備存在。i2c_transfer()函數實際又調用了__i2c_transfer()函數,部分代碼如下:

int __i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
        ... ...

        orig_jiffies = jiffies;
	for (ret = 0, try = 0; try <= adap->retries; try++) {
		ret = adap->algo->master_xfer(adap, msgs, num);
		if (ret != -EAGAIN)
			break;
		if (time_after(jiffies, orig_jiffies + adap->timeout))
			break;
	}
        ... ...
}

    最終是調用到了I2C總線驅動裏註冊的adapter適配器的算法函數master_xfer發送i2c_msg。master_xfer()函數調用過程:
      1. 傳輸數據時,調用s3c24xx_i2c_algorithm結構體中的數據傳輸函數s3c24xx_i2c_xfer()。
     
2. s3c24xx_i2c_xfer()中會調用s3c24xx_i2c_doxfer()進行數據的傳輸。
     
3. s3c24xx_i2c_doxfer()中向總線發送IIC設備地址和開始信號S後,便會調用wati_event_timeout()函數進入等待狀態。
     
4. 將數據準備好發送時,將產生中斷,並調用實現註冊的中斷處理函數s3c24xx_i2c_irq()。
     
5. s3c24xx_i2c_irq()調用下一個字節傳輸函數i2c_s3c_irq_nextbyte()來傳輸數據。
     
6. 當數據傳輸完成後,會調用 s3c24xx_i2c_stop()。
     
7. 最後調用wake_up()喚醒等待隊列,完成數據的傳輸過程。
    其中第5點調用i2s_s3c_irq_nextbyte()函數中有如下代碼:

case STATE_START:
	if (iicstat & S3C2410_IICSTAT_LASTBIT &&
	    !(i2c->msg->flags & I2C_M_IGNORE_NAK)) {
		
                /* 如果沒有收到ACK */
		dev_dbg(i2c->dev, "ack was not received\n");
		s3c24xx_i2c_stop(i2c, -ENXIO);
		goto out_ack;
	}

    當使用適配器發送數據沒有收到ACK會有如下調用:
        s3c24xx_i2c_stop(i2c, -ENXIO);
            s3c24xx_i2c_master_complete(i2c, ret);   
// ret=-ENXIO
                    if (ret)
                        i2c->msg_idx = ret;   
// i2c->msg_idx=-ENXIO
    i2c_smbus_xfer()又通過以下調用得到返回值-ENXIO
              i2c_smbus_xfer()
                    i2c_transfer()
                          s3c24xx_i2c_xfer()
                              s3c24xx_i2c_doxfer
                                  ret = i2c->msg_idx;   
// ret=-ENXIO
                                      return ret;
    回顧3.2.3.3中講的i2c_detect_address()函數內容:
      1. 確保傳入的7位I2C設備地址在0x08~0x77之間(I2C芯片通常是7位設備地址,範圍在0x08~0x77之間)
      2. 確保總線上真實掛接有該設備地址的I2C芯片(在i2c_default_probe()函數中通過i2c_smbus_xfer發送設備地址,如果有ACK返回,表示確實有真實的I2C芯片接在該總線上,如果沒有ACK,函數返回)
      3. 調用i2c_driver->detect檢測函數再次檢測芯片具體是什麼芯片(如通過讀寫I2C芯片特定的寄存器)
      4. 調用i2c_new_device創建設備(創建I2C設備驅動的i2c_client)
   
i2c_detect_address()函數中檢驗設備地址i2c_default_probe()函數使用如下:

static int i2c_default_probe(struct i2c_adapter *adap, unsigned short addr)
{
        int err;
        ... ...
        err = i2c_smbus_xfer(... ...);
        ... ...
        return err >= 0;
}
... ...
static int i2c_detect_address(struct i2c_client *temp_client,
			      struct i2c_driver *driver)
{
        ... ...
        if (!i2c_default_probe(adapter, addr))
                return 0;
        
	/* 調用i2c_driver->detect檢測函數再次檢測芯片具體是什麼芯片,如通過讀寫芯片特定的寄存器 */
	memset(&info, 0, sizeof(struct i2c_board_info));
	info.addr = addr;
	err = driver->detect(temp_client, &info);

        ... ...

        /*創建設備*/
	client = i2c_new_device(adapter, &info);
        ... ...
	return 0;
}

    可見,當使用i2c_smbus_xfer()函數(最終是調用到了I2C總線驅動裏註冊的adapter適配器的算法函數master_xfer)發送數據(設備地址)時:
    1. 如果沒有收到I2C設備的ACK, i2c_smbus_xfer()返回-ENXIO,所以i2c_default_probe()函數返回0,i2c_detect_address()函數也就會返回0。
   
2. 如果收到ACK表示I2C總線上確實有該設備地址的芯片存在,就會調用i2c_driver->detect檢測函數再次檢測該設備地址的芯片具體是什麼芯片(如通過讀寫I2C芯片特定的寄存器)。接着便調用i2c_new_device()創建設備(i2c_client),該函數是I2C設備驅動中很重要的組成部分,放到後面在仔細分析。

3.2.5 總結

    到目前爲止,i2c-s3c2410.c文件爲我們實現了對2440I2C控制器的設置、I2C通信協議相關代碼等,組成了一個適配器i2c_adapter(I2C總線驅動),使用這個適配器內核就知道如何與掛在I2C總線上的I2C設備通信了下面講解I2C設備驅動部分。

3.3 分析I2C設備驅動

    其實在以上講解分析I2C總線驅動部分已經涉及到I2C設備驅動的使用了,實例化創建i2c_client、使用i2c_client、i2c_driver等。I2C總線驅動與I2C設備驅動相互盤根交錯,並沒有明顯的界線。如果說I2C總線驅動的核心是向內核註冊適配器(i2c_adapter),那麼I2C設備驅動的核心就是實例化I2C設備(i2c_client)與註冊設備驅動(i2c_driver)
   
Linux內核文檔(/Documentation/i2c/instantiating-devices)中,介紹了實例化I2C設備(註冊i2c_client)的4種方法,實際上這4種方法最終都是通過調用i2c_new_device()函數來實例化I2C設備的,所以先來仔細分析一下i2c_new_device()函數。

3.3.1 i2c_new_device()函數

    實例化I2C設備核心就是調用i2c_new_device()函數,該函數部分代碼如下:

struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
	struct i2c_client	*client;
	int			status;

	client = kzalloc(sizeof *client, GFP_KERNEL);    //分配i2c_client結構體內存
        ... ...

        /*設置i2c_client結構體*/
	client->adapter = adap;    //設置該從設備的適配器
        ... ...

	client->flags = info->flags;    //設置該從設備的標誌
	client->addr = info->addr;    //設置該從設備的設備地址

	client->irq = info->irq;    //中斷號
        ... ...

	strlcpy(client->name, info->type, sizeof(client->name));    //設置該從設備的名字
        ... ...

	client->dev.parent = &client->adapter->dev;    //設置設備父類爲適配器的dev
	client->dev.bus = &i2c_bus_type;    //設置設備總線類型
	client->dev.type = &i2c_client_type;    //設置設備類型
	client->dev.of_node = info->of_node;    //設備節點
	client->dev.fwnode = info->fwnode;

	i2c_dev_set_name(adap, client);

        /* 向設備添加屬性 */
	if (info->properties) {
		status = device_add_properties(&client->dev, info->properties);
		if (status) {
			dev_err(&adap->dev,
				"Failed to add properties to client %s: %d\n",
				client->name, status);
			goto out_err;
		}
	}

        /* 註冊設備 */
	status = device_register(&client->dev);
        ... ...

	return client;
        ... ...
}

    該函數傳入兩個參數,struct i2c_adapter結構和struct i2c_board_info結構,前者即是I2C總線驅動中的適配器,後者就是一個I2C設備信息表,結構原型如下(include/linux/i2c.h)

struct i2c_board_info {
	char		type[I2C_NAME_SIZE];    //描述I2C設備類型名
	unsigned short	flags;    
	unsigned short	addr;    //描述I2C設備地址
	void		*platform_data;    //平臺數據
	struct dev_archdata	*archdata;
	struct device_node *of_node;    //設備節點
	struct fwnode_handle *fwnode;
	const struct property_entry *properties;
	const struct resource *resources;
	unsigned int	num_resources;
	int		irq;
};

/*設置i2c_board_info結構體type與addr成員的宏 */
#define I2C_BOARD_INFO(dev_type, dev_addr) \
	.type = dev_type, .addr = (dev_addr)

    我們重點關心typeaddr成員,type描述一個I2C設備類型名,addr描述I2C設備地址。在i2c_new_device()函數中會將傳入的i2c_board_info結構體成員拷貝給i2c_client結構,現在再來總結一下i2c_new_device()函數內容:
      1. 分配i2c_client結構體
      2. 設置i2c_client結構體
        2.1 設置適配器
        2.2 設置flag
        2.3 設置設備地址
(i2c_board_info->addr)
        2.4 設置irq
        2.4 設置設備名字
(i2c_board_info->type)
      3. 設置i2c_client描述的device設備(通過這個device可以找到i2c_client)

      4. 使用device_register()函數註冊device設備
    最後是調用device_register註冊這個device結構,由總線設備驅動模型可以猜到,必定有地方調用driver_register註冊一個device_driver結構。內核通過調用i2c_add_driver()函數註冊設備驅動(i2c_driver)裏,就會註冊與i2c_driver相關聯的device_driver結構。
    對應的釋放設備函數原型爲:

void i2c_unregister_device(struct i2c_client *client);

3.3.2 i2c_add_driver()函數

    在include/linux/i2c.h中有如下宏定義:

//include/linux/i2c.h
#define i2c_add_driver(driver) \
	i2c_register_driver(THIS_MODULE, driver)

    所以i2c_add_driver()函數實際調用了i2c_register_driver()函數,該函數代碼如下:

int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{
	int res;
        ... ...

	/* 設置i2c_driver的device_driver成員 */
	driver->driver.owner = owner;    //設置device_driver成員的所有者
	driver->driver.bus = &i2c_bus_type;    //設置device_driver成員的總線類型
	INIT_LIST_HEAD(&driver->clients);
    
        /* 註冊device_driver結構體 */
	res = driver_register(&driver->driver);    
	if (res)
		return res;

	/* 遍歷i2c_bus_type匹配i2c_driver與i2c_adapter */
	i2c_for_each_dev(driver, __process_new_driver);

	return 0;
}

    該函數作用如下:
      1. 設置i2c_driver的device_driver成員
      2. 使用driver_register()函數註冊device_driver
      3. 遍歷i2c_bus_type匹配i2c_driver與i2c_adapter

3.3.2.1 分析作用2

    由總線設備驅動模型可以知道,當向內核註冊device_driver驅動或者註冊device設備,都會調用到device_driverdevice的總線結構體裏的.match匹配函數匹配設備鏈表與驅動鏈表,由於他們的總線類型都設爲i2c_bus_type,所以使用i2c_new_device()函數實例化I2C設備(i2c_client)或者使用i2c_add_driver()函數註冊設備驅動(i2c_driver)時,都會調用到i2c_bus_type結構的.match匹配函數,i2c_bus_type結構定義如下:

struct bus_type i2c_bus_type = {
	.name		= "i2c",
	.match		= i2c_device_match,
	.probe		= i2c_device_probe,
	.remove		= i2c_device_remove,
	.shutdown	= i2c_device_shutdown,
};

    其中i2c_device_match()函數代碼如下:


const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id,
						const struct i2c_client *client)
{
	if (!(id && client))
		return NULL;

	while (id->name[0]) {
		if (strcmp(client->name, id->name) == 0)    //通過比較.name成員進行匹配
			return id;
		id++;
	}
	return NULL;
}
... ...
static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
	struct i2c_client	*client = i2c_verify_client(dev);    //得到i2c_client結構體
	struct i2c_driver	*driver;


	/* 使用設備樹匹配i2c_driver->of_match_table與i2c_client */
	if (i2c_of_match_device(drv->of_match_table, client))
		return 1;

	/* ACPI類型匹配 */
	if (acpi_driver_match_device(dev, drv))
		return 1;

	driver = to_i2c_driver(drv);    //得到i2c_driver結構體

	/* 匹配i2c_driver->id_table與i2c_client */
	if (i2c_match_id(driver->id_table, client))
		return 1;

	return 0;
}

    可見i2c_bus_type->match匹配函數實際是通過判斷i2c_driver->id_table->namei2c_client->name是否相同,(這裏思考一個問題,在調用i2c_new_device()函數註冊i2c_client時,是通過傳入的i2c_board_info->type成員賦值給i2c_client->name的,如果i2c_board_info不是由我們構建傳入的,調用i2c_new_device()函數之前要在哪裏指定這個i2c_board_info->type呢?)相同則匹配成功,調用i2c_bus_type->probe函數,該函數部分代碼如下:

static int i2c_device_probe(struct device *dev)
{
	struct i2c_client	*client = i2c_verify_client(dev);    //得到i2c_client結構體
	struct i2c_driver	*driver;
	int status;
        
        ... ...
        driver = to_i2c_driver(dev->driver);    //得到i2c_driver結構體
        ... ...

        /* i2c_driver沒有id_table成員且設備樹匹配失敗則函數返回 */
	if (!driver->id_table &&
	    !i2c_of_match_device(dev->driver->of_match_table, client))
		return -ENODEV;

        ... ...

        if (driver->probe_new)    //如果i2c_driver結構體有probe_new成員則調用
		status = driver->probe_new(client);
	else if (driver->probe)    //如果i2c_driver結構體有probe成員則調用
		status = driver->probe(client,
				       i2c_match_id(driver->id_table, client));
	else
		status = -EINVAL;
        ... ...
        return 0;
        ... ...
}

    i2c_bus_type->probe函數最終調用的是i2c_driver結構體的probe_new成員或者probe成員。probe函數裏面做的事情由我們自己決定比如註冊、構建設備節點等。

3.3.2.2 分析作用3

    i2c_register_driver()函數的作用3遍歷i2c_bus_type匹配i2c_driver與i2c_adapter就是調用下面代碼:
  

    __process_new_driver()函數內容如下:

static int __process_new_driver(struct device *dev, void *data)
{
	if (dev->type != &i2c_adapter_type)
		return 0;
	return i2c_do_add_adapter(data, to_i2c_adapter(dev));
}

    只調用了i2c_do_add_adapter()函數,是不是很熟悉呢?沒錯,在3.2.3中註冊i2c_adapter適配器時最終也調用了該函數,
作用就是遍歷i2c_bus_type匹配i2c_driveri2c_adapter的,所以調用i2c_add_driver()函數會做與3.2.3.3節完全一樣的工作,這裏就不再重複了。

3.3.3 總結I2C設備驅動

    Linux內核中構建了許多總線,但並不都是真實的,比如platform平臺總線就是虛擬的,平臺總線中也有設備鏈表,驅動鏈表。針對I2C總線,也有一個I2C總線的結構即i2c_bus_type結構,此結構裏面也有設備鏈表和也有驅動鏈表。
    設備鏈表裏存放i2c_client的結構體,這些結構體是調用i2c_new_device()函數時加入的,不但要加入這些結構體,還會在i2c_bus_type結構的驅動鏈表中一個一個地比較drv裏的i2c_driver來判斷是否有匹配的,如果有將調用i2c_driver裏面的probe函數,匹配函數由總線提供。
    驅動鏈表裏存放i2c_driver結構體,這些結構體是調用i2c_add_driver()時加入的,不但要加入這些結構體,還會在i2c_bus_type結構的設備鏈表中一個一個地比較dev裏的i2c_client來判斷是否有匹配的,如果匹配將調用i2c_driver裏面的probe函數。
    上述的匹配函數就是i2c_bus_type結構裏面的i2c_device_match函數。i2c_device_match函數通過i2c_driver->id_table->namei2c_client->name比較(使用設備樹是比較compatible屬性),如果相同,就表示此驅動drv裏存放的i2c_driver能支持這個設備dev裏存放的i2c_client
    總的說來,I2C設備驅動基於bus-dev-drv模型的構建過程如下:
      1. 左邊註冊一個設備,設備裏存有i2c_client(調用i2c_new_device()函數)
      2. 右邊註冊一個驅動,驅動裏存有i2c_driver(調用i2c_add_driver()函數)
      3. 比較i2c_driver->id_table->namei2c_client->name(使用設備樹是比較compatible屬性),如果相同,則調用i2c_driver的probe函數。
      4. probe函數裏面做的事情由用戶決定
(比如註冊、構建設備節點等)

3.3.4 實例化I2C設備

    上面介紹了實例化I2C設備的底層概念,接下來分析對於編寫驅動我們應該做什麼。總共有四種方法(如果算上設備樹總共有5種方法,這裏就不介紹設備樹方式了),這4種方法最終都是通過調用i2c_new_device()函數來實例化I2C設備的。

3.3.4.1 實例化I2C設備方法一(需指定適配器,指定設備地址不檢測是否存在)

    該方式核心是向內核靜態註冊i2c_board_info結構。
   回顧3.2.3 註冊i2c_adapter適配器中,內核啓動後會裝載i2c-s3c2410.c(默認編譯進內核)該驅動,也就是會調用到i2c_add_numbered_adapter()函數然後調用i2c_register_adapter()函數,該函數部分代碼如下:

static int i2c_register_adapter(struct i2c_adapter *adap)
{
        ... ...
        dev_set_name(&adap->dev, "i2c-%d", adap->nr);    //設置適配器名字
	adap->dev.bus = &i2c_bus_type;    //設置適配器成員dev的總線爲i2c_bus_type
	adap->dev.type = &i2c_adapter_type;    //設置適配器成員dev的類型爲適配器類型
	res = device_register(&adap->dev);    //註冊適配器成員dev設備

        ... ...
        
        /* 對dts上所描述的i2c_client設備進行實例化,並創建相應的sys文件:sys/bus/i2c/devices/xxx */
        of_i2c_register_devices(adap);
	i2c_acpi_register_devices(adap);
	i2c_acpi_install_space_handler(adap);

        /* 內核啓動時(註冊適配器之前),如果調用i2c_register_board_info()函數註冊
        過i2c_board_info結構(描述i2c_client的結構),就調用i2c_scan_static_board_info
        對這些i2c_board_info結構裏的i2c_client進行實例化 */
        if (adap->nr < __i2c_first_dynamic_bus_num)
	i2c_scan_static_board_info(adap);
        
        ... ...
        /* 遍歷i2c_bus_type匹配i2c_driver與i2c_adapter */
        bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter);
        ... ...
}

    通過判斷比較adap->nr __i2c_first_dynamic_bus_num決定是否執行i2c_scan_static_board_info()函數。假設執行了該函數,看看該函數做了什麼工作:

static void i2c_scan_static_board_info(struct i2c_adapter *adapter)
{
	struct i2c_devinfo	*devinfo;

	down_read(&__i2c_board_lock);

        /* 循環取出__i2c_board_list鏈表裏的devinfo->board_info傳入i2c_new_device()函數 */
	list_for_each_entry(devinfo, &__i2c_board_list, list) {
		if (devinfo->busnum == adapter->nr
				&& !i2c_new_device(adapter,
						&devinfo->board_info))
			dev_err(&adapter->dev,
				"Can't create device at 0x%02x\n",
				devinfo->board_info.addr);
	}
	up_read(&__i2c_board_lock);
}

    只做了一件事:循環取出__i2c_board_list鏈表裏的devinfo->i2c_board_info傳入i2c_new_device()函數。也就是將__i2c_board_list鏈表裏保存的I2C設備信息實例化了。
    下面分析看看什麼情況下會執行i2c_scan_static_board_info()函數。從3.2.3 註冊i2c_adapter適配器中我們得到adap->nr=0,那麼__i2c_first_dynamic_bus_num爲多少呢?搜索內核發現,在driver/i2c/i2c_boardinfo.c文件中有如下內容:

int __i2c_first_dynamic_bus_num;    //定義__i2c_first_dynamic_bus_num,默認初始化爲0
EXPORT_SYMBOL_GPL(__i2c_first_dynamic_bus_num);

... ...
int i2c_register_board_info(int busnum, struct i2c_board_info const *info, unsigned len)
{
	int status;
        ... ...
	if (busnum >= __i2c_first_dynamic_bus_num)
		__i2c_first_dynamic_bus_num = busnum + 1;

        /* 爲每一個I2C設備信息分配內存,添加到__i2c_board_list鏈表裏 */
	for (status = 0; len; len--, info++) {
		struct i2c_devinfo	*devinfo;

		devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL);
		if (!devinfo) {
			pr_debug("i2c-core: can't register boardinfo!\n");
			status = -ENOMEM;
			break;
		}

		devinfo->busnum = busnum;
		devinfo->board_info = *info;

                ... ...

		list_add_tail(&devinfo->list, &__i2c_board_list);    //添加到__i2c_board_list鏈表
	}
        ... ...

	return status;
}

    在driver/i2c/i2c_boardinfo.c文件中定義__i2c_first_dynamic_bus_num未初始化則默認爲0,該變量只在i2c_register_board_info()註冊單板I2C信息函數裏執行修改操作(還在i2c_init()函數裏修改該變量,但是沒有調用該函數)。
    對於2440的單板文件mach-smdk2440.c裏並沒有使用i2c_register_board_info()註冊單板I2C信息函數,所以__i2c_first_dynamic_bus_num一直爲0,也就是說在i2c_register_adapter()函數裏不會執行i2c_scan_static_board_info()掃描單板I2C信息函數。我們參考mach-mini2440.c單板文件如何使用i2c_register_board_info()函數的:

static struct i2c_board_info mini2440_i2c_devs[] __initdata = {
	{
		I2C_BOARD_INFO("24c08", 0x50),    //"24c08"表示設備名,0x50表示設備地址
		.platform_data = &at24c08,
	},
};
... ...
static void __init mini2440_init(void)
{

        ... ...
        /* 設置platform_device I2C相關信息 */
	s3c_i2c0_set_platdata(NULL);

        /* 註冊單板I2C相關信息
           參數1:I2C總線號,0
           參數2:i2c_board_info結構體數組指針
           參數3:i2c_board_info結構體數組的成員個數
        */
	i2c_register_board_info(0, mini2440_i2c_devs,
				ARRAY_SIZE(mini2440_i2c_devs));
        ... ...

}
MACHINE_START(MINI2440, "MINI2440")
	/* Maintainer: Michel Pollet <[email protected]> */
	.atag_offset	= 0x100,
	.map_io		= mini2440_map_io,
	.init_machine	= mini2440_init,
	.init_irq	= s3c2440_init_irq,
	.init_time	= mini2440_init_time,
MACHINE_END

    當內核啓動時就會調用mini2440_init()函數,裏面調用了i2c_register_board_info()註冊單板I2C信息函數(注意:該函數要在註冊適配器之前調用),參數1爲I2C總線號0(適配器0),參數2爲定義的i2c_board_info結構體數組指針,參數3爲i2c_board_info結構體數組成員個數,i2c_board_info結構體在3.3.1 i2c_new_device()函數的講解中介紹了,這裏不再重複。
    這樣調用i2c_register_board_info()函數如下圖,導致__i2c_first_dynamic_bus_num=1。

    所以在調用i2c_register_adapter()函數時會調用到i2c_scan_static_board_info()函數。針對JZ2440開發板(單板相關文件對應mach-smdk2440.c)仿照mach-mini2440.c修改內核源碼即可實現實例化I2C設備。(具體實現放在後面的5.1 方法一

3.3.4.2 實例化I2C設備方法二(需指定適配器,指定設備地址(檢測與不檢測兩種函數))

    該方式核心是向內核動態註冊i2c_board_info結構。
   
與實例化I2C設備方法一類似,該方法也是通過構建i2c_board_info結構體,然後直接調用i2c_new_device()函數,對於該函數第一個參數需要的i2c_adapter結構體,可以通過i2c_get_adapter()函數(傳入參數0表示想要獲得適配器0)得到。與方法一的區別在於可以方便insmod,不需要修改源碼,也就是可以在註冊適配器之後再實例化I2C設備。
   還可以通過調用i2c_new_probed_device()函數,該函數部分代碼如下:

struct i2c_client *i2c_new_probed_device(struct i2c_adapter *adap,
		      struct i2c_board_info *info,
		      unsigned short const *addr_list,
		      int (*probe)(struct i2c_adapter *, unsigned short addr))
{
	int i;

	if (!probe)
		probe = i2c_default_probe;    //如果參數4沒有指定檢測函數,使用默認檢測函數檢測設備地址是否真實存在

        /* 循環判斷addr_list上的設備地址是否真實存在,存在則打破循環 */
	for (i = 0; addr_list[i] != I2C_CLIENT_END; i++) {
		/* Check address validity */
		if (i2c_check_7bit_addr_validity_strict(addr_list[i]) < 0) {
			dev_warn(&adap->dev, "Invalid 7-bit address 0x%02x\n",
				 addr_list[i]);
			continue;
		}

		/* Check address availability (7 bit, no need to encode flags) */
		if (i2c_check_addr_busy(adap, addr_list[i])) {
			dev_dbg(&adap->dev,
				"Address 0x%02x already in use, not probing\n",
				addr_list[i]);
			continue;
		}

		/* Test address responsiveness */
		if (probe(adap, addr_list[i]))
			break;
	}

	if (addr_list[i] == I2C_CLIENT_END) {
		dev_dbg(&adap->dev, "Probing failed, no device found\n");
		return NULL;
	}

	info->addr = addr_list[i];    //將該設備地址存入i2c_board_info
	return i2c_new_device(adap, info);    //創建設備
}

    該函數主要內容如下:
      1. 判斷參數4是否指定檢測函數,沒有則使用默認檢測函數檢測設備地址是否真實存在
      2. 循環判斷addr_list上的設備地址是否真實存在,存在則打破循環
      3. 調用i2c_new_device創建設備

    最終還是調用了i2c_new_device()函數,只不過先檢測了設備地址是否真實存在(調用i2c_default_probe()函數檢驗設備地址是否有ACK(具體實現放在後面的5.2 方法二

3.3.4.3 實例化I2C設備方法三(不需要指定適配器,需要指定設備地址並檢測是否存在)

    該方式核心是使用i2c_add_driver()函數註冊設備驅動時會自動匹配適配器,自動實例化I2C設備。
    回顧3.3.2 i2c_add_driver()函數講的,當調用i2c_add_driver()函數最終會調用到i2c_detect()函數,函數部分代碼如下:

static int i2c_detect(struct i2c_adapter *adapter, struct i2c_driver *driver)
{
	const unsigned short *address_list;
	struct i2c_client *temp_client;
	int i, err = 0;
	int adap_id = i2c_adapter_id(adapter);    //得到適配器ID = adap->nr

	address_list = driver->address_list;    //從i2c_driver得到I2C設備地址列表
        ... ...

	/* 如果適配器dapter->class定義爲不自動檢測類型,函數返回 */
	if (adapter->class == I2C_CLASS_DEPRECATED) {
                ... ...
		return 0;
	}

	/* 如果i2c_driver支持的設備類型與適配器支持的類型不一樣,函數返回 */
	if (!(adapter->class & driver->class))
		return 0;

	/* 分配設置i2c_client */
	temp_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);
	if (!temp_client)
		return -ENOMEM;
	temp_client->adapter = adapter;

        /* 取出每一個address_list裏的設備地址進行檢測 */
	for (i = 0; address_list[i] != I2C_CLIENT_END; i += 1) {
		dev_dbg(&adapter->dev,
			"found normal entry for adapter %d, addr 0x%02x\n",
			adap_id, address_list[i]);
		temp_client->addr = address_list[i];
		err = i2c_detect_address(temp_client, driver);    //檢測該設備地址
		if (unlikely(err))
			break;
	}

	kfree(temp_client);    //釋放i2c_client 
	return err;
}

    首先對於mach-smdk2440.c這個適配器來說,之前代碼分析了註冊該適配器時會指定adapter->class = I2C_CLASS_DEPRECATED,我們可以通過修改該內容,讓這個適配器支持自動檢測。然後當調用i2c_add_driver()函數傳入的i2c_driver結構體定義了class成員(驅動支持的類型),並且與某個(2440只有一個)適配器的class(適配器支持的類型)匹配,表示使用自動檢測模式註冊設備驅動,會繼續調用i2c_detect_address()函數,該函數部分代碼如下:

static int i2c_detect_address(struct i2c_client *temp_client, struct i2c_driver *driver)
{
	struct i2c_board_info info;
	struct i2c_adapter *adapter = temp_client->adapter;
	int addr = temp_client->addr;
	int err;

	/* 確保7位I2C設備地址在0x08~0x77之間 */
	err = i2c_check_7bit_addr_validity_strict(addr);
	if (err) {
		dev_warn(&adapter->dev, "Invalid probe address 0x%02x\n",
			 addr);
		return err;
	}
        ... ...

	/* 確保總線上真實掛接有該設備地址的I2C芯片,通過i2c_smbus_xfer發送設備地址,
        如果有ACK返回,表示確實有真實的I2C芯片接在該總線上,如果沒有ACK,函數返回 */
	if (!i2c_default_probe(adapter, addr))
		return 0;

	/* 調用i2c_driver->detect檢測函數再次檢測芯片具體是什麼芯片,如通過讀寫芯片特定的寄存器 */
	memset(&info, 0, sizeof(struct i2c_board_info));
	info.addr = addr;
	err = driver->detect(temp_client, &info);

        ... ...

        /*創建設備*/
	client = i2c_new_device(adapter, &info);
        ... ...
	return 0;
}

    最終也會調用i2c_new_device()函數註冊設備(詳細分析內容看3.2.3.3)。這裏回答3.3.2.1 裏提出的思考問題:調用i2c_new_device()函數註冊i2c_client時,是通過傳入的i2c_board_info->type成員賦值給i2c_client->name的,如果i2c_board_info不是由我們構建傳入的,調用i2c_new_device()函數之前要在哪裏指定這個i2c_board_info->type呢?可以看到調用i2c_new_device()函數之前會調用i2c_driver->detect檢測函數,我們可以在該函數裏賦值i2c_board_info->type成員。
    方法二調用i2c_add_driver()函數註冊設備驅動時不指定i2c_driver結構體的class成員,所以這裏直接返回,需要我們自己調用i2c_new_device()函數。
(具體實現放在後面的5.3 方法三

3.3.4.4 實例化I2C設備方法四(需要指定適配器,需要指定設備地址不檢測是否存在)

    該方式核心是從用戶空間直接實例化I2C設備。
    在用戶空間執行例如
echo at24c08 0x50 > /sys/class/i2c-adapter/i2c-0/new_device”:
   
內核會自動創建一個使用適配器0的設備名爲“at24c08”,設備地址爲0x50I2C設備,相當於方法二中構建i2c_board_info結構體,然後直接調用i2c_new_device()函數。
    在用戶空間執行例如echo 0x50 > /sys/class/i2c-adapter/i2c-0/delete_device”:
    內核會調用i2c_unregister_device()函數刪除這個設備地址爲0x50I2C設備。
(具體實現放在後面的5.4 方法四

4 分析硬件

    講解具體實現I2C設備驅動之前先講解一下硬件信息。由於使用的JZ2440開發板上沒有接I2C設備,所以通過杜邦線外接一個DS3231時鐘芯片。

4.1 DS3231介紹

    DS3231是低成本、高精度I2C實時時鐘RTC,RTC保存秒、分、時、星期、日期、月和年信息。少於31天的月份,將自動調整月末的日期,包括閏年的修正。時鐘的工作格式可以是24小時或帶/AM/PM指示的12小時格式。提供兩個可設置的日曆鬧鐘和一個可設置的方波輸出。地址與數據通過I2C雙向總線串行傳輸。詳細芯片資料參考:https://html.alldatasheet.com/html-pdf/112132/DALLAS/DS3231/2425/11/DS3231.html

4.2 原理圖

    原理圖如下(DS3231時鐘芯片通過杜邦線連接到開發板):

4.3 相關寄存器

    ds3231其中的一組時鐘和日曆相關寄存器如下圖所示:

通過芯片手冊可得以下信息:  
    1. 通過讀寫00h~06h這7個寄存器地址就能讀取與設置時鐘和日曆了。
    2. 從設備地址爲7位ds3231地址1101000。

5. 編寫I2C設備驅動與測試

內核:linux-4.12
編譯器:arm-linux-gcc-4.4.3
環境:ubuntu9.10

5.1 方法一

    使用該方法編寫I2C設備驅動特點:
      1. 需要修改內核源碼
      2. 需要指定使用哪個適配器(S3C2440只有一個)
      3. 手動構建i2c_board_info(指定設備名字,指定設備地址)
      4. 內核幫我們調用i2c_new_device()函數
      5. 不檢測設備地址是否真實存在
   
下面開始修改內核源碼並測試。

5.1.1 修改arch/arm/mach-s3c24xx/mach-smdk2440.c

    在mach-smdk2440.c中添加構建單板I2C設備信息數組,並在smdk2440_machine_init()函數中註冊該單板I2C設備信息,完整修改代碼如下:

/*構建單板I2C設備信息*/
static struct i2c_board_info smdk2440_i2c_devs[] __initdata = {
	{
		I2C_BOARD_INFO("ds3231", 0x68),
	},
};

static void __init smdk2440_machine_init(void)
{
	s3c24xx_fb_set_platdata(&smdk2440_fb_info);
	s3c_i2c0_set_platdata(NULL);

        /*
        註冊單板I2C設備信息
         參數1:使用適配器0
         參數2:i2c_board_info結構體數組
         參數3:i2c_board_info結構體數組成員個數
        */
	i2c_register_board_info(0, smdk2440_i2c_devs, ARRAY_SIZE(smdk2440_i2c_devs));

	platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));
	smdk_machine_init();
}

    重新編譯內核。下載啓動。

5.1.2 編寫I2C設備驅動

    通過上面的修改,按照3.3.4.1 實例化I2C設備方法一的分析,當內核啓動後將會調用到i2c_new_device()函數註冊i2c_board_info結構體(註冊了i2c_client),所以接下來我們只需編寫註冊i2c_driver部分即可,完整代碼ds3231.c如下:

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/i2c.h>
#include <linux/rtc.h>
#include <linux/mutex.h>
#include <linux/workqueue.h>
#include <linux/device.h>

#define BCD2BIN(val)	(((val) & 0x0f) + ((val)>>4)*10)
#define BIN2BCD(val)	((((val)/10)<<4) + (val)%10)

#define DS3231_REG_SEC		0x00	//秒
#define DS3231_REG_MIN		0x01	//分
#define DS3231_REG_HOURS	0x02	//時
#define DS3231_REG_DAY		0x03	//星期
#define DS3231_REG_DATE		0x04	//日
#define DS3231_REG_MOUNTH	0x05	//月
#define DS3231_REG_YEAR		0x06	//年
 
static struct i2c_driver ds3231_driver;
static struct i2c_client *ds3231_client;
 
int major;	//主設備號
static struct cdev ds3231_cdev;
static struct class *ds3231_class;
 
static ssize_t ds3231_read(struct file *file, char __user *buf, size_t size, loff_t * offset)
{
	unsigned char time[7];
	int reg = DS3231_REG_YEAR;
	int i=sizeof(time);
	
	if (size != sizeof(time))
		return -EINVAL;
 
	/* 實際調用了i2c_smbus_xfer函數發送數據
	 * 循環讀取時鐘與日曆寄存器數據,讀出來的數據是BCD碼,
	 * 先轉換成16進制並拷貝給應用程序
	 */
	for (reg = DS3231_REG_YEAR; reg >= DS3231_REG_SEC; reg--){
		int tmp;
		if ((tmp = i2c_smbus_read_byte_data(ds3231_client, reg)) < 0) {
			dev_warn(&ds3231_client->dev,
				 "can't read from ds3231 chip\n");
			return -EIO;
		}
		time[--i] = BCD2BIN(tmp) & 0xff;
	}
	
	return copy_to_user(buf, &time, sizeof(time));
}
 
static ssize_t ds3231_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
	unsigned char time[7];
	int reg;
	int i=0;
	
	if (size != sizeof(time))
		return -EINVAL;
 
	/* 拷貝要寫入的7個16進制時鐘與日曆值 */
	if(copy_from_user(time, buf, sizeof(time)))
		return -EINVAL;
 
	/* 實際調用了i2c_smbus_xfer函數發送數據
	 * 循環寫入轉換成BCD碼的時鐘與日曆值
	 */
	for (reg = DS3231_REG_SEC; reg <= DS3231_REG_YEAR; reg++) {
		if (i2c_smbus_write_byte_data(ds3231_client, reg, BIN2BCD(time[i]))< 0) {
			dev_warn(&ds3231_client->dev,
				 "can't write to ds3231 chip\n");
			return -EINVAL;
		}
		i++;
	}
	return sizeof(time);
}
 
static struct file_operations ds3231_fops = {
    .owner    =   THIS_MODULE,
    .write    =   ds3231_write,     
    .read     =	  ds3231_read,	   
};
 
static int ds3231_probe(struct i2c_client *client,
				  const struct i2c_device_id *id)
{
	int err;
	dev_t devid;
	ds3231_client = client;	

 	printk("ds3231 probe !\n");
	/* 創建字符設備 */	
	devid = MKDEV(major, 0);	//從主設備號major,次設備號0得到dev_t類型
	if (major) 
	{
		err=register_chrdev_region(devid, 1, "ds3231");	//註冊字符設備
	} 
	else 
	{
		err=alloc_chrdev_region(&devid, 0, 1, "ds3231");	//註冊字符設備
		major = MAJOR(devid);	//從dev_t類型得到主設備
	}
	if(err < 0)
		return err;
	
	cdev_init(&ds3231_cdev, &ds3231_fops);
	cdev_add(&ds3231_cdev, devid, 1);
	
	ds3231_class = class_create(THIS_MODULE, "ds3231");
	device_create(ds3231_class, NULL, MKDEV(major, 0), NULL, "ds3231"); /* /dev/ds3231 */
 
	return err;
}
 
static int ds3231_remove(struct i2c_client *client)
{
	printk("ds3231_remove !\n");
	device_destroy(ds3231_class,MKDEV(major, 0));
	class_destroy(ds3231_class);
	cdev_del(&ds3231_cdev);
	unregister_chrdev_region(MKDEV(major, 0), 1);
		
	return 0;
}

static const struct i2c_device_id ds3231_id_table[] = {
	{ "ds3231", 0 },    //名字“ds3231”與i2c_board_info的.type一樣
	{}
};
 
static struct i2c_driver ds3231_driver = {
	.driver	= {
		.name	= "ds3231_driver",
		.owner	= THIS_MODULE,
	},
	.probe 		= ds3231_probe,
	.remove		= ds3231_remove,
	.id_table	= ds3231_id_table,
};
 
static int ds3231_init(void)
{
	return i2c_add_driver(&ds3231_driver);
}
 
static void ds3231_exit(void)
{
	i2c_del_driver(&ds3231_driver);
}
 
module_init(ds3231_init);
module_exit(ds3231_exit);
 
MODULE_AUTHOR("LVZHENHAI");
MODULE_LICENSE("GPL");

    當裝載該驅動時,由總線設備驅動模型會調用到該驅動的probe函數,我們在該函數裏面創建字符設備,再編寫一個測試程序ds3231_test.c,完整代碼如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
 
/* ./ds3231_test r    //讀時鐘與日曆格式
 * ./ds3231_test w Year Mounth Date Day Hour Min Sec    //設置時鐘與日曆格式
 */
 
void print_usage(char *file)
{
	printf("%s r\n", file);
	printf("%s w Year Mounth Date Day Hour Min Sec\n", file);
}
 
int main(int argc, char **argv)
{
	int fd;
	unsigned char buf[7];
	int i;
	
	if ((argc != 2) && (argc != 9))
	{
		print_usage(argv[0]);
		return -1;
	}
 
	fd = open("/dev/ds3231", O_RDWR);
	if (fd < 0)
	{
		printf("can't open /dev/ds3231\n");
		return -1;
	}
 
	if (strcmp(argv[1], "r") == 0)	//讀取時鐘與日曆
	{
		read(fd, buf, sizeof(buf));
		printf("Now Time= Year:%02d, Mounth:%02d, Date:%02d, Day:%d, Hour:%02d, Min:%02d, Sec:%02d\n", \
			 buf[6], buf[5],buf[4], buf[3], buf[2],buf[1],buf[0]);
	}
	else if (strcmp(argv[1], "w") == 0)	//寫時鐘與日曆
	{
		for(i=0;i<7;i++)
		{
			buf[6-i] = strtoul(argv[i+2], NULL, 0);	//將字符轉換成數值
		}
		printf("Set Time Year:%02d, Mounth:%02d, Date:%02d, Day:%d, Hour:%02d, Min:%02d, Sec:%02d\n", \
		buf[6], buf[5],buf[4], buf[3], buf[2],buf[1],buf[0]);
		write(fd, buf, sizeof(buf));
	}
	else
	{
		print_usage(argv[0]);
		return -1;
	}
	
	return 0;
}

    Makefile代碼如下:

KERN_DIR = /work/tools/linux-4.12
 
all:
	make -C $(KERN_DIR) M=`pwd` modules 
 
clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order
 
obj-m	+= ds3231.o

5.1.3 測試

    1. 啓動修改好的內核。
    2. 拷貝以上3個文件到網絡文件系統編譯並安裝驅動,進入文件目錄,執行如下命令:
      make
      arm-linux-gcc -o ds3231_test ds3231_test.c
   
ls得到如下:
   
    安裝驅動如下:
   
    可以看到調用了ds3231.c驅動的probe函數,使用應用程序測試,執行如下命令:
   
    執行如下命令:
      ./ds3231_test w 18 12 31 1 23 59 55 (設置當前時間爲18年12月31日星期1 23點59分55秒)
      ./ds3231_test r (讀取當前時間)
   

5.2 方法二

    使用該方法編寫I2C設備驅動特點:
      1. 需要指定使用哪個適配器(S3C2440只有一個)
      2. 手動構建i2c_board_info(指定設備名字,指定設備地址)

      3. 編寫驅動調用i2c_new_device()函數(不檢測設備地址是否真實存在)或者調用i2c_new_probed_device()函數(檢測設備地址是否真實存在)
   
按照3.3.4.2 實例化I2C設備方法二的分析編寫I2C設備驅動,在ds3231_dev.c裏註冊i2c_client,在ds3231_drv.c(與方法一的ds3231.c相同,不貼代碼了)裏註冊i2c_driverds3231_dev.c的編寫有兩種,主要不同在使用i2c_new_device()函數(不檢測設備地址是否真實存在)或者調用i2c_new_probed_device()函數。

5.2.1 編寫I2C設備驅動(使用i2c_new_device

    使用i2c_new_device()函數的ds3231_dev.c代碼如下:

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>
#include <linux/err.h>
#include <linux/regmap.h>
#include <linux/slab.h>

static struct i2c_board_info ds3231_info = {	
	I2C_BOARD_INFO("ds3231", 0x68),
};
static struct i2c_client *ds3231_client;

static int ds3231_dev_init(void)
{
	struct i2c_adapter *i2c_adap;

	i2c_adap = i2c_get_adapter(0);	/*因爲硬件掛接在I2C總線0上,所以參數是0*/

/*在該適配器下創建了一個新設備,設備信息在i2c_board_info結構體中,
以後就可以使用這個i2c_adapter	的操作函數對該設備發出I2C的信號了*/
	ds3231_client = i2c_new_device(i2c_adap, &ds3231_info);	

	i2c_put_adapter(i2c_adap);	/*使用完要釋放*/
	return 0;
}
static void ds3231_dev_exit(void)
{
	i2c_unregister_device(ds3231_client);	/*卸載註冊的設備*/
}
module_init(ds3231_dev_init);
module_exit(ds3231_dev_exit);

MODULE_AUTHOR("LVZHENHAI");
MODULE_LICENSE("GPL");

5.2.2 編寫I2C設備驅動(使用i2c_new_probed_device

    使用i2c_new_probed_device()函數的ds3231_dev.c代碼如下:

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>
#include <linux/err.h>
#include <linux/regmap.h>
#include <linux/slab.h>

static struct i2c_client *ds3231_client;

/*short addr_list[]中可以存放多個設備地址,
在 i2c_new_probed_device函數中依次進行匹配,
I2C_CLIENT_END	表示後面沒有參數了*/
static const unsigned short addr_list[] = { 0x60, 0x68, I2C_CLIENT_END };

static int ds3231_dev_init(void)
{
	struct i2c_adapter *i2c_adap;
	struct i2c_board_info ds3231_info;

	memset(&ds3231_info, 0, sizeof(struct i2c_board_info));	
	strlcpy(ds3231_info.type, "ds3231", I2C_NAME_SIZE);

	i2c_adap = i2c_get_adapter(0);
	ds3231_client = i2c_new_probed_device(i2c_adap, &ds3231_info, addr_list, NULL);
	i2c_put_adapter(i2c_adap);

	if (ds3231_client)
		return 0;
	else
		return -ENODEV;
}
static void ds3231_dev_exit(void)
{
	i2c_unregister_device(ds3231_client);
}

module_init(ds3231_dev_init);
module_exit(ds3231_dev_exit);

MODULE_AUTHOR("LVZHENHAI");
MODULE_LICENSE("GPL");

    測試程序ds3231_test.c與方法一的一樣,ds3231_drv.c與方法一的ds3231.c相同。Makefile代碼如下:

KERN_DIR = /work/tools/linux-4.12
 
all:
	make -C $(KERN_DIR) M=`pwd` modules 
 
clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order
 
obj-m	+= ds3231_dev.o
obj-m	+= ds3231_drv.o

5.2.3 測試

    注意如果使用過方法一修改了mach-smdk2440.c文件要修改回去,否則裝載ds3231_dev.c時提示不能重複裝載相同設備地址的驅動。
    拷貝以上4個文件到網絡文件系統編譯並安裝驅動,進入文件目錄,執行如下命令:
      make
      arm-linux-gcc -o ds3231_test ds3231_test.c
   
ls得到如下:
   
    安裝驅動如下:
   
    可以看到調用了ds3231_drv.c驅動的probe函數,後面的使用應用程序測試與方法一一樣。

5.3 方法三

    使用該方法編寫I2C設備驅動特點:
      1. 修改源碼(由於該版本內核不支持i2c-s3c2410.c這個適配器自動檢測,修改讓其支持)
      2. 不需要指定使用哪個適配器
      3. 不需要構建i2c_board_info,但通過賦值給i2c_board_info->type成員傳遞設備名字,構建一個address_list傳遞地址
      4. 內核幫我們調用i2c_new_device()函數
      5. 檢測設備地址是否真實存在

5.3.1 修改driver/i2c/busses/i2c-s3c2410.c

    按照3.3.4.2 實例化I2C設備方法二的分析,修改適配器文件driver/i2c/busses/i2c-s3c2410.c文件,由於該版本內核不支持這個適配器自動檢測I2C_CLASS_DEPRECATED,修改讓其支持改爲I2C_CLASS_HWMON,如下:
   
    然後重新編譯內核。

5.3.2 編寫I2C設備驅動

    按照3.3.4.2 實例化I2C設備方法二的分析編寫I2C設備驅動,我們只需註冊i2c_driver,完整代碼ds3231.c如下:

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/i2c.h>
#include <linux/rtc.h>
#include <linux/mutex.h>
#include <linux/workqueue.h>
#include <linux/device.h>

#define BCD2BIN(val)	(((val) & 0x0f) + ((val)>>4)*10)
#define BIN2BCD(val)	((((val)/10)<<4) + (val)%10)

#define DS3231_REG_SEC		0x00	//秒
#define DS3231_REG_MIN		0x01	//分
#define DS3231_REG_HOURS	0x02	//時
#define DS3231_REG_DAY		0x03	//星期
#define DS3231_REG_DATE		0x04	//日
#define DS3231_REG_MOUNTH	0x05	//月
#define DS3231_REG_YEAR		0x06	//年
 
static struct i2c_driver ds3231_driver;
static struct i2c_client *ds3231_client;
 
int major;	//主設備號
static struct cdev ds3231_cdev;
static struct class *ds3231_class;
 
static ssize_t ds3231_read(struct file *file, char __user *buf, size_t size, loff_t * offset)
{
	unsigned char time[7];
	int reg = DS3231_REG_YEAR;
	int i=sizeof(time);
	
	if (size != sizeof(time))
		return -EINVAL;
 
	/* 實際調用了i2c_smbus_xfer函數發送數據
	 * 循環讀取時鐘與日曆寄存器數據,讀出來的數據是BCD碼,
	 * 先轉換成16進制並拷貝給應用程序
	 */
	for (reg = DS3231_REG_YEAR; reg >= DS3231_REG_SEC; reg--){
		int tmp;
		if ((tmp = i2c_smbus_read_byte_data(ds3231_client, reg)) < 0) {
			dev_warn(&ds3231_client->dev,
				 "can't read from ds3231 chip\n");
			return -EIO;
		}
		time[--i] = BCD2BIN(tmp) & 0xff;
	}
	
	return copy_to_user(buf, &time, sizeof(time));
}
 
static ssize_t ds3231_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
	unsigned char time[7];
	int reg;
	int i=0;
	
	if (size != sizeof(time))
		return -EINVAL;
 
	/* 拷貝要寫入的7個16進制時鐘與日曆值 */
	if(copy_from_user(time, buf, sizeof(time)))
		return -EINVAL;
 
	/* 實際調用了i2c_smbus_xfer函數發送數據
	 * 循環寫入轉換成BCD碼的時鐘與日曆值
	 */
	for (reg = DS3231_REG_SEC; reg <= DS3231_REG_YEAR; reg++) {
		if (i2c_smbus_write_byte_data(ds3231_client, reg, BIN2BCD(time[i]))< 0) {
			dev_warn(&ds3231_client->dev,
				 "can't write to ds3231 chip\n");
			return -EINVAL;
		}
		i++;
	}
	return sizeof(time);
}
 
static struct file_operations ds3231_fops = {
    .owner  =   THIS_MODULE,
    .write  =   ds3231_write,     
    .read   =	ds3231_read,	   
};
 
static int ds3231_probe(struct i2c_client *client,
				  const struct i2c_device_id *id)
{
	int err;
	dev_t devid;
	ds3231_client = client;	

 	printk("ds3231 probe !\n");
	/* 創建字符設備 */	
	devid = MKDEV(major, 0);	//從主設備號major,次設備號0得到dev_t類型
	if (major) 
	{
		err=register_chrdev_region(devid, 1, "ds3231");	//註冊字符設備
	} 
	else 
	{
		err=alloc_chrdev_region(&devid, 0, 1, "ds3231");	//註冊字符設備
		major = MAJOR(devid);	//從dev_t類型得到主設備
	}
	if(err < 0)
		return err;
	
	cdev_init(&ds3231_cdev, &ds3231_fops);
	cdev_add(&ds3231_cdev, devid, 1);
	
	ds3231_class = class_create(THIS_MODULE, "ds3231");
	device_create(ds3231_class, NULL, MKDEV(major, 0), NULL, "ds3231"); /* /dev/ds3231 */
 
	return err;
}
 
static int ds3231_remove(struct i2c_client *client)
{
	printk("ds3231_remove !\n");
	device_destroy(ds3231_class,MKDEV(major, 0));
	class_destroy(ds3231_class);
	cdev_del(&ds3231_cdev);
	unregister_chrdev_region(MKDEV(major, 0), 1);
		
	return 0;
}

static const struct i2c_device_id ds3231_id_table[] = {
	{ "ds3231", 0 },	//設備名字
	{}
};

static int ds3231_detect(struct i2c_client *client,
		       struct i2c_board_info *info)
{
	/* 能運行到這裏, 表示該addr的設備是存在的
	 * 但是有些設備單憑地址無法分辨(A芯片的地址是0x68, B芯片的地址也是0x68)
	 * 還需要進一步讀寫I2C設備來分辨是哪款芯片
	 * detect就是用來進一步分辨這個芯片是哪一款,並且設置info->type
	 */
	
	printk("ds3231_detect : addr = 0x%x\n", client->addr);
	/* 在這裏可以進一步判斷是哪一款芯片 */
	
	strlcpy(info->type, "ds3231", I2C_NAME_SIZE);    //與i2c_device_id裏的設備名字一樣
	return 0;
}

static const unsigned short addr_list[] = { 0x60, 0x68, I2C_CLIENT_END };

 static struct i2c_driver ds3231_driver = {
	.class  = I2C_CLASS_HWMON, /* 表示該驅動是什麼類型,匹配支持相同的類型適配器 */
	.driver	= {
		.name	= "ds3231_driver",
		.owner	= THIS_MODULE,
	},
	.probe		= ds3231_probe,
	.remove		= ds3231_remove,
	.id_table	= ds3231_id_table,
	.detect     = ds3231_detect,  /* 用這個函數來檢測設備確實存在 */
	.address_list	= addr_list,   /* 這些設備的地址 */
};
 
static int ds3231_init(void)
{
	return i2c_add_driver(&ds3231_driver);
}
 
static void ds3231_exit(void)
{
	i2c_del_driver(&ds3231_driver);
}
 
module_init(ds3231_init);
module_exit(ds3231_exit);
 
MODULE_AUTHOR("LVZHENHAI");
MODULE_LICENSE("GPL");

    測試程序ds3231_test.c與方法一一樣,Makefile也一樣。

5.3.3 測試

    1. 啓動修改好的內核。
    2. 拷貝以上3個文件到網絡文件系統編譯並安裝驅動,進入文件目錄,執行如下命令:
      make
      arm-linux-gcc -o ds3231_test ds3231_test.c
   
ls得到如下:
   
    安裝驅動如下:
   
    可以看到先調用了ds3231_drv.c驅動的detect函數,然後執行probe函數,後面的使用應用程序測試與方法一一樣。

5.4 方法四

    使用該方法編寫I2C設備驅動特點:
      1. 直接在用戶空間創建或刪除I2C設備(通過命令指定設備名字,指定設備地址
     
2. 不檢測設備地址是否真實存在(註冊i2c_driver時去匹配上面的設備名字,匹配成功就會執行驅動的probe函數)
    啓動內核在開發板上執行如下命令:
      ls /sys/class/i2c-adapter/
   

    可以看到只有一個i2c-0,這就是2440唯一一個適配器。下面直接通過命令創建設備或刪除設備。

5.4.1 創建設備

    執行如下命令:
      echo ds3231 0x68 > /sys/class/i2c-adapter/i2c-0/new_device
   

    內核會自動創建一個使用適配器0的設備名爲“ds3231”,設備地址爲0x68I2C設備,相當於方法二中構建i2c_board_info結構體,然後內核幫我們調用i2c_new_device()函數。

5.4.2 刪除設備

    執行如下命令:
      echo 0x68 > /sys/class/i2c-adapter/i2c-0/delete_device
   
    內核會調用i2c_unregister_device()函數刪除這個設備地址爲0x68I2C設備。

5.4.3 測試

    使用5.4.1 創建設備後,再使用方法一的設備驅動程序ds3231.c、測試程序ds3231_test.cMakefile。測試方法與效果也一樣。

還有一種方法是使用設備樹方式,這裏就先不介紹了。

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