---
tags: Linux Kernel Module, System Software, 作業系統
---
# 今晚我想來點不一樣的 Driver: I2C Device
## Quick introduction of I2C
### Overview
[I2C](https://en.wikipedia.org/wiki/I%C2%B2C)(讀做 $I^2C$ I-squared-C) 是用於讓 CPU 和周邊進行資料傳輸的傳輸的介面。
:::info
討論 I2C 時經常也會提及 [SMbus](https://en.wikipedia.org/wiki/System_Management_Bus),例如 [Implementing I2C device drivers](https://www.kernel.org/doc/html/latest/i2c/writing-clients.html)。粗略來說,SMBus 是從 I2C 簡化而來的子集合。詳細可參閱 [I2C 和 SMBus 有甚麼不同?](https://eeepage.info/i2c-bus-smbus/)
:::
在 I2C 的 usage model 中分別有兩個重要的角色:
* Master: 負責發出 command,提供 clock signal 以及開始和結束 bus transfer,在 bus 上可以存在不限數量的多個 masters(但特定時間段內只會有一個 master 工作)
* Slave: master 透過 address 指定要 bus transfer 的對象,因為 address 長度的限制(一般而言是 7 bits),在 I2C 中的數量是有限制的
在 I2C 中一般只透過兩條訊號進行傳輸,分別用來傳輸時脈(以下稱為 CLK) 和傳輸資料(以下稱為 DAT),兩者都是 wired-and signal。簡單來說,就是電路的特性和 and 邏輯的操作一樣:
* 當所有的裝置都輸出 high 時,bus 上的狀態才會是 high
* 只要有任何一個裝置輸出 low,bus 上的狀態就會是 low
透過這個特性,當裝置 A 在輸出 high 時,藉由檢查 bus 的狀態是不是反映出 high,就可以得知是不是有其它的裝置同時也在輸出 low 訊號,這個特性讓 I2C 可以不必透過額外的 signal 進行**仲裁(arbitration)**(我們會在後續介紹這個專有名詞)。
> 更多 wired-and 的介紹: [I2C 界面解密 — PART 1](https://makerpro.cc/2020/05/i2c-interface-part1/)
### Data transfer
:::warning
這裡我借用 [SMBus spec](http://smbus.org/specs/) 中的圖進行解說,其觀念與 I2C 是共用的
:::
根據協議,idle 狀態下,CLK 和 DAT 皆為 high,而資料的傳輸只有在 CLK 為 high 時可以進行,並且只能在 CLK 拉為 low 後才可以變更要傳輸的資料。如果在 CLK 為 high 時將 DAT signal 從 high 變成 low,則意味著傳遞出 START message,表示某個 master 裝置想佔據 bus 進行傳輸。如果在 CLK 為 high 時將 DAT signal 從 low 變成 high,則意味著傳遞出 STOP message,表示某個 master 結束對 bus 的占用。


如下圖展示了一個基本的資料傳輸:
1. 開始時 master 先發出 START
2. 接著,每次的傳輸由 1 bytes(8 bits)組成,每個 bytes 的傳輸是從最高有效位(most significant bit, MSB)開始傳輸
3. 每次傳送 1 bytes 後,slave 都必須回應一個 ack 或 nack
4. 在 slave 回應後,CLK 和 DAT 被拉 low,讓 slave 有時間對收到的資料進行對應的處理
5. 直到 CLK 被拉起 master 才可以進行下一次的傳輸(回到 step 2 循環 )
6. 最後,master 需要發出 STOP 結束傳輸。

### Arbitration
如果同一時間有兩個 device 想要控制 bus,則需要透過仲裁(arbitration)機制來決定誰可以贏得控制權。Arbitration 會在每次 CLK 為 high 時進行,CLK 為 high 時,每個嘗試控制 bus 的裝置檢查 DATA 的結果是否與自己的 output,若是,則可以繼續傳輸,否則表示輸掉 bus 的控制權,停止繼續傳輸。
如下圖,device 1 和 device 2 在前 4 次的 arbitration 中傳輸的 data 皆相同,因此兩者都嘗試繼續工作,直到第 5 次的 arbitration 時,想嘗試發出 high 的 device 1 因為 wired-and 的 bus 特性而無法在 DAT 上得到預期的結果,則讓出 bus 的控制。

## I2C subsystem in Linux
I2C 雖然原理並不複雜,但在 Linux subsystem 中卻被抽象成許多不同的部件,如下圖所展示:

> [i2c wiki - Driver Architecture](https://i2c.wiki.kernel.org/index.php/Driver_Architecture)

> [Linux驱动子系统之I2C(一)](https://www.cnblogs.com/cslunatic/archive/2013/06/13/3134607.html)
以下我們將簡要介紹 I2C subsystem 中存在的主要組成部件,以及它們在系統中所扮演的角色。
### Platform device / Platform driver
在 Linux 的 device model 中,裝置與作業系統的關係透過 bus, device, driver 連繫在一起。bus 是連接 device 和 driver 的橋樑,每個連接到 bus 上的 device 可以告訴作業系統自己的類型以及對應的硬體資源(bus enumeration),這讓 kernel 可以辨認 bus 上的 device 並尋找匹配的 drvier。
然而,有些裝置是 CPU 無法透過硬體機制找尋到的。這些 device 通常是我們預先知道其存在,再由軟體層設定進行連接。它們通常可以透過 CPU bus 直接被存取。為了達到介面的一致性,Linux 系統中對這些 device 抽象出了 paltform 的概念。這些 device 被稱為 platform device,後者掛在虛擬的 "platform bus" 之上, platform driver 可以藉由 platform bus 找到對應的 device。本文中所會討論到的 I2C controller driver(注意不等於 I2C driver) 即從屬於 platform bus。
### I2C Adapter / I2C algorithm
I2C adpater 抽象出 I2C controller 的操作介面,其中包含的關鍵結構 I2C algorithm 定義 I2C 的通信方法,並提供與 device 之間交換訊息的協議。
### I2C Client / I2C drvier
I2C client 代表一個掛接在 I2C bus 上的 I2C device 實例,而 I2C driver 則是該 client 的 device driver。
### I2C-core
I2C-core 讓 I2C device 分出 adapter 和 client 兩個獨立的部份,向上 client 可以提供 I2C driver 對 device 操作的一致介面。向下 adapter 則可以定義與 device hardware 的傳輸協議。
### I2C-dev
I2C-dev 並不是整個 I2C subsystem 中必要的部件,它是 Linux 中對 I2C 操作的 char device 封裝,提供了 user space 對 I2C device 操作的介面。
## Virtual I2C
只從文字敘述理解 I2C subsystem,可能會對整個框架有些模糊。為此,我撰寫了 [vi2c-diva](https://github.com/RinHizakura/vi2c-diva) 這個簡單的範例進行解說。

先回顧一下上面的架構,接著讓我們來看看 vi2c-diva 專案中包含三個的 module: 分別位於 pdev, adap, 和 diva-dev 中。概要每個 module 在這個架構中之角色的話:
* pdev: 對應圖中 platform device 部份
* adap: 對應圖中的 platform driver 部分,並且會建立虛擬裝置的 adapter/algorithm 以及相對應的 client,
* 向下需要使用 `platform_driver_register` 把 platform driver 和對應的 platform device match 在一起
* 向上需要透過 `i2c_add_adapter` 對 i2c-core 註冊,把 platform device 和 adapter 也聯繫在一起
* 最後透過 `i2c_new_client_device` (圖中 `i2c_new_device` 的新 API)為使用這個 adapter 的 client 註冊到 kernel 中
* diva-dev: 對應的圖中的 i2c 設備驅動(device driver)部份
* 向下需要透過 `i2c_add_driver` 對 i2c-core 註冊,把 device driver 和 client match 在一起
* 向上則可以提供 user space 操作的介面
對 driver 有一些概念的人都應該知道,probe 並不是在 module 被掛載後就會直接被執行的,只有 driver 在 kernel 中找到對應的 device("match")時才會被呼叫。而 adap 和 diva-dev module 中並沒有 `module_init` function,意味在 module 被掛載時沒有主動運行的程式,整個註冊的流程是被動的透過 probe 進行的,那麼 probe 具體來說是在 kernel 程式碼的哪一部份呢? kernel 又是透過什麼方去確認 device 和 driver 的對應關係,以確保 probe 是正確的被呼叫呢?
下面我們將嘗試探討這些問題的解答!
### pdev
就如我們之前提到的,對於那些不處在任何實體 bus 上,不能被硬體方式探測的裝置,它們都被歸類為 platform device。後者需被掛載在 Linux 中虛擬的 platform bus,以允許 platform device 對應的 driver 在被掛載時可以成功 match 之。
在 pdev 中,module 在掛載並執行 [`platform_device_register`](https://elixir.bootlin.com/linux/v5.16-rc7/source/drivers/base/platform.c#L766) 之後就立即返回了。
#### `platform_device_register`
```cpp
int platform_device_register(struct platform_device *pdev)
{
device_initialize(&pdev->dev);
setup_pdev_dma_masks(pdev);
return platform_device_add(pdev);
}
EXPORT_SYMBOL_GPL(platform_device_register);
```
而 `platform_device_register` 的關鍵在於呼叫的 [`platform_device_add`](https://elixir.bootlin.com/linux/v5.16-rc7/source/drivers/base/platform.c#L651)。
#### `platform_device_add`
```cpp
int platform_device_add(struct platform_device *pdev)
{
u32 i;
int ret;
if (!pdev)
return -EINVAL;
if (!pdev->dev.parent)
pdev->dev.parent = &platform_bus;
pdev->dev.bus = &platform_bus_type;
switch (pdev->id) {
default:
dev_set_name(&pdev->dev, "%s.%d", pdev->name, pdev->id);
break;
case PLATFORM_DEVID_NONE:
dev_set_name(&pdev->dev, "%s", pdev->name);
break;
case PLATFORM_DEVID_AUTO:
/*
* Automatically allocated device ID. We mark it as such so
* that we remember it must be freed, and we append a suffix
* to avoid namespace collision with explicit IDs.
*/
ret = ida_alloc(&platform_devid_ida, GFP_KERNEL);
if (ret < 0)
goto err_out;
pdev->id = ret;
pdev->id_auto = true;
dev_set_name(&pdev->dev, "%s.%d.auto", pdev->name, pdev->id);
break;
}
...
ret = device_add(&pdev->dev);
if (ret == 0)
return ret;
failed:
...
}
EXPORT_SYMBOL_GPL(platform_device_add);
```
* `pdev->dev.bus` 被設定為 [`platform_bus`](https://elixir.bootlin.com/linux/v5.16-rc7/source/drivers/base/platform.c#L40),後者是會作為所有 platform device 之頂端的全域 `struct device`
* 可以看到 pdev->id 的設定會影響到 `pdev->dev` 最終被註冊在 kernel 中的名稱
* platform device 被掛在 platform bus 的關鍵是 [`device_add`](https://elixir.bootlin.com/linux/v5.16-rc7/source/drivers/base/core.c#L3275)
#### `device_add`
```cpp
int device_add(struct device *dev)
{
...
parent = get_device(dev->parent);
kobj = get_device_parent(dev, parent);
if (IS_ERR(kobj)) {
error = PTR_ERR(kobj);
goto parent_error;
}
if (kobj)
dev->kobj.parent = kobj;
/* use parent numa_node */
if (parent && (dev_to_node(dev) == NUMA_NO_NODE))
set_dev_node(dev, dev_to_node(parent));
/* first, register with generic layer. */
/* we require the name to be set before, and pass NULL */
error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);
if (error) {
glue_dir = get_glue_dir(dev);
goto Error;
}
...
}
```
這裡會透過 [`kobject_add`](https://elixir.bootlin.com/linux/v5.16-rc7/source/lib/kobject.c#L426) 把新增的 deivce 之 kobject 加入到 Linux 的架構之中。更精準的說,是將 kobject 初始化並插入到某個 kset 的 linked list 中,並註冊到 sysfs 上。其關鍵是 [`kobject_add_internal`](https://elixir.bootlin.com/linux/v5.16-rc7/source/lib/kobject.c#L225)。透過這個步驟,我們應該可以在 `/sys/bus/platform/devices/` 路徑下找到 i2c-diva 這個裝置。
> kobject 的意義可能需要在另外的文章中討論,目前可以先參考
> * [Linux设备驱动模型系列 - 2 kobject篇](https://reurl.cc/dXrA7y)
> * [Linux設備模型(2)_Kobject](https://www.cntofu.com/book/46/linux_driver/linuxshe_bei_mo578b28_2__kobject.md)
實際上,platform device 在 kernel 中的註冊存在兩個方法。除了我們在範例中使用的 `platform_device_register`,更普遍的作法是透過 [device tree](https://www.kernel.org/doc/html/latest/devicetree/usage-model.html) 註冊。在 Linux 中,我們可以透過 device tree source 這種格式的文件描述硬體的資料結構,讓 kernel 在 [`of_platform_populate`](https://elixir.bootlin.com/linux/v5.16-rc7/source/drivers/of/platform.c#L469) 中把 device 的 kobject 加入到核心中(循著 [`of_platform_bus_create`](https://elixir.bootlin.com/linux/v5.16-rc7/source/drivers/of/platform.c#L347) ->[`of_platform_device_create_pdata`](https://elixir.bootlin.com/linux/v5.16-rc7/source/drivers/of/platform.c#L171) -> [`of_device_add`](https://elixir.bootlin.com/linux/v5.16-rc7/source/drivers/of/device.c#L37) 可以找到 `device_add`)。
### adap
建立了虛擬的 platform device 後,接下來我們要註冊其對應的 driver。[`module_platform_driver`](https://elixir.bootlin.com/linux/v5.16-rc7/source/include/linux/platform_device.h#L251) 內部會使用 [`platform_driver_register`](https://elixir.bootlin.com/linux/v5.16-rc7/source/drivers/base/platform.c#L852) 嘗試把我們定義的 platform driver structure 註冊到核心中,其中的第一關鍵是 [`driver_register`](https://elixir.bootlin.com/linux/v5.16-rc7/source/drivers/base/driver.c#L147)
#### `driver_register`
```cpp
int driver_register(struct device_driver *drv)
{
...
other = driver_find(drv->name, drv->bus);
if (other) {
pr_err("Error: Driver '%s' is already registered, "
"aborting...\n", drv->name);
return -EBUSY;
}
ret = bus_add_driver(drv);
if (ret)
return ret;
ret = driver_add_groups(drv, drv->groups);
if (ret) {
bus_remove_driver(drv);
return ret;
}
kobject_uevent(&drv->p->kobj, KOBJ_ADD);
return ret;
}
```
* driver_find 透過 driver 的名稱和屬於的 bus 類型(在 `platform_driver_register` 時已經被預先設為 [`platform_bus_type`](https://elixir.bootlin.com/linux/v5.16-rc7/source/drivers/base/platform.c#L1473)) 檢查 driver 是否已經被註冊
* 下一個關鍵是 [`bus_add_driver`](https://elixir.bootlin.com/linux/v5.16-rc7/source/drivers/base/bus.c#L590)
#### `bus_add_driver`
```cpp
int bus_add_driver(struct device_driver *drv)
{
struct bus_type *bus;
struct driver_private *priv;
int error = 0;
bus = bus_get(drv->bus);
if (!bus)
return -EINVAL;
pr_debug("bus: '%s': add driver %s\n", bus->name, drv->name);
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
if (!priv) {
error = -ENOMEM;
goto out_put_bus;
}
klist_init(&priv->klist_devices, NULL, NULL);
priv->driver = drv;
drv->p = priv;
priv->kobj.kset = bus->p->drivers_kset;
error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,
"%s", drv->name);
if (error)
goto out_unregister;
klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);
if (drv->bus->p->drivers_autoprobe) {
error = driver_attach(drv);
if (error)
goto out_unregister;
}
...
}
```
* [`bus_get`](https://elixir.bootlin.com/linux/v5.16-rc7/source/drivers/base/bus.c#L42) 取得 driver 屬於的 bus 類型
* `klist_init`: 初始化 `klist_devices` 這個結構,則之後屬於這個 platform device driver 的 device 節點之後可以加入進來
* `kobject_init_and_add`: 透過這個步驟,類似於 `kobject_add` 的相同流程,加入 driver 對應的 kobject 到核心中,我們應該可以在 `/sys/bus/platform/drivers/` 找到所註冊的 driver
* `klist_add_tail`: 加入 driver 到所屬 bus 的 klist 結構中
* 如果 `drv->bus->p->drivers_autoprobe` 被設為 true,表示該 bus 定義有 `probe`,需呼叫 [`driver_attach`](https://elixir.bootlin.com/linux/v5.16-rc7/source/drivers/base/dd.c#L1155) 去處理
> * [linux驱动篇之 driver_register 过程分析(二)bus_add_driver](https://blog.csdn.net/Richard_LiuJH/article/details/48245715)
> * [Linux 驱动开发 | 驱动世界里的宏伟建筑](https://www.modb.pro/db/87753)
#### `driver_attach`
```cpp
int driver_attach(struct device_driver *drv)
{
return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}
```
[`driver_attach`](https://elixir.bootlin.com/linux/v5.16-rc7/source/drivers/base/dd.c#L1155) 會對 driver 隸屬的 bus 上所有的 device 進行 [`__driver_attach`](https://elixir.bootlin.com/linux/v5.16-rc7/source/drivers/base/dd.c#L1092)。
```cpp
static int __driver_attach(struct device *dev, void *data)
{
...
ret = driver_match_device(drv, dev);
...
__device_driver_lock(dev, dev->parent);
driver_probe_device(drv, dev);
__device_driver_unlock(dev, dev->parent);
return 0;
}
```
[`driver_match_device`](https://elixir.bootlin.com/linux/v5.16-rc7/source/drivers/base/base.h#L144) 時,platform driver 的 bus 被預先設為 [`platform_bus_type`](https://elixir.bootlin.com/linux/v5.16-rc7/source/drivers/base/platform.c#L1473),因此 `dev->bus->match` 為 true,會呼叫 [`platform_match`](https://elixir.bootlin.com/linux/v5.16-rc7/source/drivers/base/platform.c#L1342)。
```cpp
static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv);
/* When driver_override is set, only bind to the matching driver */
if (pdev->driver_override)
return !strcmp(pdev->driver_override, drv->name);
/* Attempt an OF style match first */
if (of_driver_match_device(dev, drv))
return 1;
/* Then try ACPI style match */
if (acpi_driver_match_device(dev, drv))
return 1;
/* Then try to match against the id table */
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;
/* fall-back to driver name match */
return (strcmp(pdev->name, drv->name) == 0);
}
```
這裡我們可以看到比較的過程分成不同方式的 match。以我們的 driver 而言,我們沒有提供過多的資訊,因此顯然它是透過最後的 `strcmp`,以 `i2c-diva` 這個名字和 platform device 對上的。
回到 `__driver_attach`,如果成功對上,則會前進到最後的 [`driver_probe_device`](https://elixir.bootlin.com/linux/v5.16-rc7/source/drivers/base/dd.c#L775)。其呼叫的主流程是 [`__driver_probe_device`](https://elixir.bootlin.com/linux/v5.16-rc7/source/drivers/base/dd.c#L730)->[`really_probe`](https://elixir.bootlin.com/linux/v5.16-rc7/source/drivers/base/dd.c#L541)->[`call_driver_probe`](https://elixir.bootlin.com/linux/v5.16-rc7/source/drivers/base/dd.c#L510)(中間有許多細節的程式碼請容我先忽略qq)
```cpp
static int call_driver_probe(struct device *dev, struct device_driver *drv)
{
int ret = 0;
if (dev->bus->probe)
ret = dev->bus->probe(dev);
else if (drv->probe)
ret = drv->probe(dev);
...
}
```
platform driver 的 bus 被預先設為 [`platform_bus_type`](https://elixir.bootlin.com/linux/v5.16-rc7/source/drivers/base/platform.c#L1473),因此 `dev->bus->probe` 為 true,會呼叫 [`platform_probe`](https://elixir.bootlin.com/linux/v5.16-rc7/source/drivers/base/platform.c#L1386),最後後者呼叫 `drv->probe(dev)` 呼叫到我們所定義的 `diva_i2c_probe`。若匹配成功,`nvme_driver.probe` 就會被執行
#### `diva_i2c_probe`
穿過複雜的核心程式碼,我們終於回到 driver 的部分。
```cpp
static int diva_i2c_probe(struct platform_device *pdev)
{
int ret;
struct diva_i2c_dev *i2c_dev;
struct i2c_adapter *adap;
i2c_dev = devm_kzalloc(&pdev->dev, sizeof(struct diva_i2c_dev), GFP_KERNEL);
if (!i2c_dev)
return -ENOMEM;
platform_set_drvdata(pdev, i2c_dev);
i2c_dev->dev = &pdev->dev;
```
* 我們分配出一個 `struct diva_i2c_dev`,這個容器中會存放 driver 所需要的結構
* `platform_set_drvdata` 將 `i2c_dev` 的位址設置到 `struct platform_device` 底下的 `struct device` 中的指標中
* `i2c_dev->dev = &pdev->dev`: `i2c_dev` 也把 `struct device` 儲存在自己結e構之下,總結來說,兩個結構彼此可以互相連結
* 只是方便從某一結構找到另一個,視情形不一定要這樣設計
```cpp
adap = &i2c_dev->adapter;
i2c_set_adapdata(adap, i2c_dev);
adap->owner = THIS_MODULE;
adap->class = I2C_CLASS_DEPRECATED;
snprintf(adap->name, sizeof(adap->name), "%s", pdev->name);
adap->algo = &diva_i2c_algo;
adap->dev.parent = &pdev->dev;
```
這一段將 adapter 重要的 member 設定好。
* 前兩行的目的與前面類似,`i2c_set_adapdata` 允許之後可以使用 `i2c_get_adapdata` 從 `adapter` 取得 `i2c_dev` 結構,這對於使用 `diva_i2c_algo` 介面時有益
* `diva_i2c_algo` 是關鍵,需要提供 `master_xfer` 和 `functionality` 註冊到 i2c-core,後者才可以向上對 i2c-client 提供 I2C 的傳輸方法
```cpp
ret = i2c_add_adapter(adap);
if (ret < 0)
goto fail_ret;
ret = diva_i2c_add_client(i2c_dev);
if (ret < 0)
goto del_adap;
```
* `i2c_add_adapter` 將 adapter 註冊到 kernel 中
* 最後,`diva_i2c_add_client` 內部呼叫到 [`i2c_new_client_device`](https://elixir.bootlin.com/linux/v5.16-rc7/source/drivers/i2c/i2c-core-base.c#L1011),後者把 adapter 和一個 `struct i2c_board_info` 連繫在一起,我們可以看到 client 的名稱會通過 [`i2c_board_info`](https://elixir.bootlin.com/linux/v5.16-rc7/source/include/linux/i2c.h#L430) 的 `type` 命名。最後,產生一個 `struct i2c_client` 的 instace。
### diva-dev
完成 platform device 和 driver 的註冊,並建立我們虛擬的 I2C driver 需要的 adapter 和 client 之後,剩下的就是註冊一個 i2c device driver,以使用 client 並透過其介面來對底層的 I2C 進行控制了。
[`module_i2c_driver`](https://elixir.bootlin.com/linux/v5.16-rc7/source/include/linux/i2c.h#L953) 是註冊的起點,其呼叫 [`i2c_register_driver`](https://elixir.bootlin.com/linux/v5.16-rc7/source/drivers/i2c/i2c-core-base.c#L1944) 註冊 driver。前面的步驟與 [`platform_driver_register`](https://elixir.bootlin.com/linux/v5.16-rc7/source/drivers/base/platform.c#L852) 的流程相似,[`i2c_bus_type`](https://elixir.bootlin.com/linux/v5.16-rc7/source/drivers/i2c/i2c-core-base.c#L1944) 只是對應的 bus type 是 [`i2c_bus_type`](https://elixir.bootlin.com/linux/v5.16-rc7/source/drivers/i2c/i2c-core-base.c#L770)。
```cpp
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{
int res;
/* Can't register until after driver model init */
if (WARN_ON(!is_registered))
return -EAGAIN;
/* add the driver to the list of i2c drivers in the driver core */
driver->driver.owner = owner;
driver->driver.bus = &i2c_bus_type;
INIT_LIST_HEAD(&driver->clients);
/* When registration returns, the driver core
* will have called probe() for all matching-but-unbound devices.
*/
res = driver_register(&driver->driver);
if (res)
return res;
pr_debug("driver [%s] registered\n", driver->driver.name);
/* Walk the adapters that are already present */
i2c_for_each_dev(driver, __process_new_driver);
return 0;
}
```
因此,match 的流程是執行 [`i2c_device_match`](https://elixir.bootlin.com/linux/v5.16-rc7/source/drivers/i2c/i2c-core-base.c#L116),在我們的案例中 match 的關鍵是 [`i2c_match_id`](https://elixir.bootlin.com/linux/v5.16-rc7/source/drivers/i2c/i2c-core-base.c#L101),後者會將 `id_table` 所有 entry 的 `name` 欄位和 `client->name` 做比較,成功 match 者才可以進行 probe。而 probe 的過程會從 [`i2c_device_probe`](https://elixir.bootlin.com/linux/v5.16-rc7/source/drivers/i2c/i2c-core-base.c#L466) 開始。
```cpp
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)
return id;
id++;
}
return NULL;
}
```
除此之外,I2C 裝置也提供動態註冊的方式,疊代已知的每個 adapter。並透過 [`__process_new_driver`](https://elixir.bootlin.com/linux/v5.16-rc7/source/drivers/i2c/i2c-core-base.c#L1932) 將所有掛在 `i2c_bus_type` 底下的裝置和 driver 嘗試進行 detect。主要流程是 [`i2c_do_add_adapter`](https://elixir.bootlin.com/linux/v5.16-rc7/source/drivers/i2c/i2c-core-base.c#L1434) -> [`i2c_detect`](https://elixir.bootlin.com/linux/v5.16-rc7/source/drivers/i2c/i2c-core-base.c#L2446),這裡暫不深入討論。
> See: [How to instantiate I2C devices - Method 3: Probe an I2C bus for certain devices](https://www.kernel.org/doc/html/latest/i2c/instantiating-devices.html#method-3-probe-an-i2c-bus-for-certain-devices)
自此,我們展示了如何使用 kernel API 註冊特定的 I2C controller driver,並添加相對應的 I2C device driver。不過,client 端該如何應用 I2C core 介面以使用 adapter 對裝置進行資料的傳輸呢? 這是我們下一個需要探討的問題。
### `i2c_smbus_write_byte`
- [ ] TODO
:::warning
- [ ] TODO: 透過 ftrace 驗證追蹤流程的正確性
:::
## Reference
> * [成大資工 Wik - I2C: Inter-Integrated Circuit](http://wiki.csie.ncku.edu.tw/embedded/I2C)
> * [SMBus spec](http://smbus.org/specs/)
> * [Day 6:I2C (Part 1) - 簡介與環境設置](https://ithelp.ithome.com.tw/articles/10241633)
> * [Day 10:I2C Driver (Part 1) - 使用 Device Tree 來找 Driver](https://ithelp.ithome.com.tw/articles/10244211)
> * [Day 12:I2C Driver (Part 2) - 細節、追蹤與驗證](https://ithelp.ithome.com.tw/articles/10245130?sc=rss.iron)
> * [Implementing I2C device drivers](https://www.kernel.org/doc/html/latest/i2c/writing-clients.html)
> * [设备驱动中的i2c(kernel-4.7)](https://blog.csdn.net/viewsky11/article/details/53647203)
> * [i2c-bcm2835.c](https://github.com/torvalds/linux/blob/5bfc75d92efd494db37f5c4c173d3639d4772966/drivers/i2c/busses/i2c-bcm2835.c)
> * [Linux驱动子系统之I2C(一)](https://www.cnblogs.com/cslunatic/archive/2013/06/13/3134607.html)
> * [Linux I2C framework(1)](http://www.wowotech.net/comm/i2c_overview.html)
> * [Platform Devices and Drivers](https://www.kernel.org/doc/Documentation/driver-model/platform.txt)
> * [The platform device API](https://lwn.net/Articles/448499/)
> * [What is the difference between a Linux platform driver and normal device driver?](https://stackoverflow.com/questions/15610570/what-is-the-difference-between-a-linux-platform-driver-and-normal-device-driver)
> * [i2c-kempld.c](https://elixir.bootlin.com/linux/v5.16-rc7/source/drivers/i2c/busses/i2c-kempld.c)
> * [How to instantiate I2C devices](https://www.kernel.org/doc/html/latest/i2c/instantiating-devices.html)