現(xiàn)在ARM下對SoC開發(fā)板的硬件描述都是采用devicetree文件,使用linux自帶的dtc程序?qū)ts編譯成dtb之后,由u-boot將dtb導(dǎo)入給linux內(nèi)核,linux內(nèi)核讀取dtb,然后注冊設(shè)備的resource,linux內(nèi)核使用of_系列函數(shù)API讀取硬件資源。具體的說明可以看下: .dts文件根據(jù)具體的硬件配置好后,編譯生成.dtb文件。
然后需要在menuconfig內(nèi)核配置中為硬件選擇驅(qū)動程序,只有硬件驅(qū)動程序和dts中的硬件名字匹配時,才能觸發(fā)驅(qū)動的probe函數(shù)
rtc-8564和pcf8563的驅(qū)動是兼容的,均為pcf8563驅(qū)動。
注:以下的分析基于3.12.0linux內(nèi)核。個人分析難免存在紕漏,懇請大家指正。
一、I2C的linux主要涉及4個結(jié)構(gòu)體:i2c_adapter,i2c_algorithm,i2c_client,i2c_driver
struct i2c_adapter {
struct module *owner;
unsigned int class; /* classes to allow probing for */
const struct i2c_algorithm *algo; /* the algorithm to access the bus */
void *algo_data;
/* data fields that are valid for all devices */
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 mutex userspace_clients_lock;
struct list_head userspace_clients;
struct i2c_bus_recovery_info *bus_recovery_info;
};
i2c總線控制器數(shù)據(jù)依附于algo_data,比如xi2cps,s3c24xx_i2c。
struct device dev;成員表明i2c_adapter是一個硬件,對應(yīng)SoC上的I2C控制器。而i2c_algorithm則是這個I2C控制器的底層驅(qū)動程序。
同理:
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;
};
struct i2c_client代表一個掛載到i2c總線上的i2c從設(shè)備,該設(shè)備所需要的數(shù)據(jù)結(jié)構(gòu),其中包括
該i2c從設(shè)備所依附的i2c主設(shè)備 struct i2c_adapter *adapter
該i2c從設(shè)備的驅(qū)動程序struct i2c_driver *driver
作為i2c從設(shè)備所通用的成員變量,比如addr, name等
該i2c從設(shè)備驅(qū)動所特有的數(shù)據(jù),依附于dev->driver_data下,在i2c_driver中的probe函數(shù)中設(shè)置這個結(jié)構(gòu)體成員。比如eeprom的eeprom_data。
所有i2c從設(shè)備組成的雙向鏈表:detected
struct device dev表明struct i2c_client代表的是一個硬件,比如eeprom芯片,或則rtc芯片,通過i2c總線連接到i2c_adapter硬件上。
而i2c_driver則是這個i2c_client芯片硬件的驅(qū)動程序。
我們一般會對每個I2C字符設(shè)備定義一個私有信息結(jié)構(gòu)體,而i2c_client一般被包含在這個私有信息結(jié)構(gòu)體中。看過LDR3源代碼的hacker應(yīng)該比較清楚。
i2c_client依附于i2c_adapter,也就是I2C設(shè)備和I2C總線控制器的對應(yīng)關(guān)系,一個i2c_adapter可以掛接多個i2c_client,i2c_adapter的struct list_head userspace_clients;結(jié)構(gòu)成員就是所有client的鏈表。
linux的最新版本基本上支持目前所有的I2C適配器硬件和I2C從設(shè)備,但是對于工程師來說,可能要面臨各種情況:為i2c_adapter和i2c_client編寫驅(qū)動程序。
二、I2C核心
I2C核心是源碼位于drivers/i2c/i2c-core.c,它并不依賴于硬件平臺的接口函數(shù),是I2C總線驅(qū)動和設(shè)備驅(qū)動的紐帶。
增加/刪除i2c_adapter
int i2c_add_adapter(struct i2c_adapter *adapter)
//調(diào)用i2c_register_adapter()
int i2c_del_adapter(struct i2c_adapter *adapter)
增加/刪除i2c_driver
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
int i2c_add_driver(struct i2c_driver *driver)
//調(diào)用i2c_register_driver
void i2c_del_driver(struct i2c_driver *driver)
增加/刪除i2c_client
struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
void i2c_unregister_device(struct i2c_client *client)
注:在2.6.30版本之前使用的是i2c_attach_client()和i2c_detach_client()函數(shù)。之后attach被merge到了i2c_new_device中,而detach直接被unregister取代。實際上這兩個函數(shù)內(nèi)部都是調(diào)用了device_register()和device_unregister()
I2C傳輸、發(fā)送接收
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
int i2c_master_send(struct i2c_client *client,const char *buf ,int count)
int i2c_master_recv(struct i2c_client *client, char *buf ,int count)
i2c_transfer()函數(shù)用于進行I2C 適配器和I2C 設(shè)備之間的一組消息交互,i2c_master_send()函數(shù)和i2c_master_recv()函數(shù)內(nèi)部會調(diào)用i2c_transfer()函數(shù)分別完成一條寫消息和一條讀消息。
i2c_transfer()本身不能和硬件完成消息交互,它尋找i2c_adapter對應(yīng)的i2c_algorithm,要實現(xiàn)數(shù)據(jù)傳送就要實現(xiàn)i2c_algorithm的master_xfer(),這個函數(shù)與具體的硬件有關(guān),大部分時間由廠商完成。
i2c_transfer()通過調(diào)用__i2c_transfer()完成I2C通訊:
int __i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
unsigned long orig_jiffies;
int ret, try;
/* Retry automatically on arbitration loss */
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;
}
return ret;
}
可見retries為重傳嘗試次數(shù),timeout為超時時間。
三、Linux I2C總線驅(qū)動
1、I2C適配器的加載和卸除
加載:申請硬件資源,比如IO地址,中斷號,調(diào)用i2c_add_adapter加載適配器
i2c_add_adapter中會調(diào)用i2c_register_adapter函數(shù)
static int i2c_register_adapter(struct i2c_adapter *adap)
{
... ...
device_register(&adap->dev);
//完成I2C主設(shè)備adapter的注冊,即注冊object和發(fā)送uevent等
i2c_scan_static_board_info(adap);
//注冊i2c_client
... ...
}
static void i2c_scan_static_board_info(struct i2c_adapter *adapter)
{
struct i2c_devinfo *devinfo;
down_read(&__i2c_board_lock);
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_new_device調(diào)用device_register注冊i2c從設(shè)備。
那么,這個I2C從設(shè)備組成的雙向循環(huán)鏈表,是什么時候通過什么方式建立起來的呢?
以 /arch/arm/mach-pxa/saar.c 為例
static void __init saar_init(void)
{
... ...
saar_init_i2c();
........
}
static void __init saar_init_i2c(void)
{
pxa_set_i2c_info(NULL);
i2c_register_board_info(0, ARRAY_AND_SIZE(saar_i2c_info));
}
static struct i2c_board_info saar_i2c_info[] = {
[0] = {
.type = "da9034",
.addr = 0x34,
.platform_data = &saar_da9034_info,
.irq = PXA_GPIO_TO_IRQ(mfp_to_gpio(MFP_PIN_GPIO83)),
},
};
/* drivers/i2c/i2c-boardinfo.c */
int __init i2c_register_board_info(int busnum, structi2c_board_info const *info, unsigned len)
{
... ...
struct i2c_devinfo *devinfo;
devinfo->board_info = *info;
list_add_tail(&devinfo->list, &__i2c_board_list); //將I2C從設(shè)備加入該鏈表中
... ...
}
所以,在系統(tǒng)初始化的過程中,我們可以通過 i2c_register_board_info,將所需要的I2C從設(shè)備加入一個名為__i2c_board_list雙向循環(huán)鏈表,系統(tǒng)在成功加載I2C主設(shè)備adapt后,就會對這張鏈表里所有I2C從設(shè)備逐一地完成 i2c_client的注冊。
也就是說,i2c_client和i2c_adapter都是由i2c_core來維護的。
在xilinx-linux中,i2c從設(shè)備是通過dts文件傳遞給內(nèi)核的,內(nèi)核通過zynq_init_machine函數(shù)注冊所有的i2c從設(shè)備,i2c_client.
在linux的設(shè)備和驅(qū)動管理體系中,所有的非熱插拔設(shè)備默認(rèn)是在 init_machine函數(shù)成員中加入相應(yīng)維護設(shè)備的雙向鏈表中,包括platform_device和其他的設(shè)備。當(dāng)一個特定的設(shè)備驅(qū)動通過driver_register加入對應(yīng)的總線下時,回去遍歷對應(yīng)總線下的設(shè)備雙向鏈表,當(dāng)驅(qū)動和設(shè)備匹配時,會觸發(fā)驅(qū)動的probe函數(shù)。
DT_MACHINE_START(XILINX_EP107, "Xilinx Zynq Platform")
.smp = smp_ops(zynq_smp_ops),
.map_io = zynq_map_io,
.init_irq = zynq_irq_init,
.init_machine = zynq_init_machine,
.init_late = zynq_init_late,
.init_time = zynq_timer_init,
.dt_compat = zynq_dt_match,
.reserve = zynq_memory_init,
.restart = zynq_system_reset,
MACHINE_END
可以參考mach-zynq的電路板初始化代碼
卸除:釋放硬件資源,調(diào)用i2c_del_adapter卸載i2c適配器
void i2c_del_adapter(struct i2c_adapter *adap)
{
..........
list_for_each_entry_safe(client, next, &adap->userspace_clients,
detected) {
dev_dbg(&adap->dev, "Removing %s at 0x%x\n", client->name,
client->addr);
list_del(&client->detected);
i2c_unregister_device(client);
}
卸載所有的從i2c設(shè)備
..............
device_unregister(&adap->dev);
卸載i2c適配器
..............
}
2、編寫I2C的總線通訊方法algorithm
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
int num);
/* To determine what the adapter supports */
u32 (*functionality) (struct i2c_adapter *);
主要實現(xiàn)上面的兩個函數(shù)。
大部分時間,我們需要定義一個XXX_i2c結(jié)構(gòu)體,比如drivers/i2c/busses/i2c-s3c2410.c中的struct s3c24xx_i2c。
XXX_i2c結(jié)構(gòu)體中包含struct i2c_msg *msg;struct i2c_adapter adap;void __iomem *regs;等
struct i2c_msg *msg接收用戶層的數(shù)據(jù)發(fā)到i2c總線,或從i2c總線讀取數(shù)據(jù)到用戶層。
在適配器的probe函數(shù)中:
struct xi2cps *id;
platform_set_drvdata(pdev, id);
id->adap.dev.of_node = pdev->dev.of_node;
id->adap.algo = (struct i2c_algorithm *) &xi2cps_algo;
id->adap.timeout = 0x1F;
/* Default timeout value */
id->adap.retries = 3;
/* Default retry value. */
id->adap.algo_data = id;
id->adap.dev.parent = &pdev->dev;
四、linux i2c從設(shè)備驅(qū)動
硬件方面,I2C主設(shè)備已經(jīng)集成在主芯片內(nèi),軟件方面,linux也為我們提供了相應(yīng)的驅(qū)動程序,位于drivers/i2c/bus下。那么接下來I2C從設(shè)備驅(qū)動就變得容易得多。既然系統(tǒng)加載I2C主設(shè)備驅(qū)動時已經(jīng)注冊了i2c_adapter和i2c_client,那么I2C從設(shè)備主要完成三大任務(wù):
系統(tǒng)初始化時添加以i2c_board_info為結(jié)構(gòu)的I2C從設(shè)備的信息(針對不使用dts描述硬件信息的電路板)
在I2C從設(shè)備驅(qū)動程序里使用i2c_adapter里所提供的算法,即實現(xiàn)I2C通信。
將I2C從設(shè)備的特有數(shù)據(jù)結(jié)構(gòu)掛在到i2c_client.dev->driver_data下。
以/driver/misc/eeprom/eeprom.c為例:
static struct i2c_driver eeprom_driver = {
.driver = {
.name = "eeprom",
},
.probe = eeprom_probe,
.remove = eeprom_remove,
.id_table = eeprom_id,
.class = I2C_CLASS_DDC | I2C_CLASS_SPD,
.detect = eeprom_detect,
.address_list = normal_i2c,
};
static struct i2c_driver eeprom_driver = {
.driver = {
.name = "eeprom",
},
.probe = eeprom_probe,
.remove = eeprom_remove,
.id_table = eeprom_id,
.class = I2C_CLASS_DDC | I2C_CLASS_SPD,
.detect = eeprom_detect,
.address_list = normal_i2c,
};
i2c_driver 中的driver.name 不一定要和i2c_client一致,因為這只是他們配備的依據(jù)之一。id_table 是i2c_device_id結(jié)構(gòu)體的一個對象,里面定義了i2c驅(qū)動對應(yīng)設(shè)備的i2c地址。struct i2c_device_id里面的字符串與 I2C_BOARD_INFO里面的匹配后,xxx_led_probe也會調(diào)用,這是設(shè)備和驅(qū)動匹配的依據(jù)之二。
使用Device Tree后,驅(qū)動需要與.dts中描述的設(shè)備結(jié)點進行匹配,從而引發(fā)驅(qū)動的probe()函數(shù)執(zhí)行。對于platform_driver而言,需要添加一個OF匹配表
static const struct i2c_device_id pcf8563_id[] = {
{ "pcf8563", 0 },
{ "rtc8564", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, pcf8563_id);
#ifdef CONFIG_OF
static const struct of_device_id pcf8563_of_match[] = {
{ .compatible = "nxp,pcf8563" },
{}
};
MODULE_DEVICE_TABLE(of, pcf8563_of_match);
#endif
static struct i2c_driver pcf8563_driver = {
.driver = {
.name = "rtc-pcf8563",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(pcf8563_of_match),
},
.probe = pcf8563_probe,
.id_table = pcf8563_id,
};
/* Each client has this additional data */
不過這邊有一點需要提醒的是,I2C和SPI外設(shè)驅(qū)動和Device Tree中設(shè)備結(jié)點的compatible 屬性還有一種弱式匹配方法,就是別名匹配。compatible 屬性的組織形式為,,別名其實就是去掉compatible 屬性中逗號前的manufacturer前綴。關(guān)于這一點,可查看drivers/spi/spi.c的源代碼,函數(shù)spi_match_device()暴露了更多的細節(jié),如果別名出現(xiàn)在設(shè)備spi_driver的id_table里面,或者別名與spi_driver的name字段相同,SPI設(shè)備和驅(qū)動都可以匹配上:
struct eeprom_data {
struct mutex update_lock;
u8 valid; /* bitfield, bit!=0 if slice is valid */
unsigned long last_updated[8]; /* In jiffies, 8 slices */
u8 data[EEPROM_SIZE]; /* Register values */
enum eeprom_nature nature;
};
static int eeprom_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
... ...
struct eeprom_data *data;
......
i2c_set_clientdata(client, data);//將設(shè)備的數(shù)據(jù)結(jié)構(gòu)掛到i2c_client.dev->p->driver_data下
.............
name[0] = i2c_smbus_read_byte_data(client, 0x80);//i2c-core提供的接口,利用i2c_adapter的算法實現(xiàn)I2C通信
}
這部分內(nèi)容建議參考
以及
也就是說,i2c_adapter是一個i2c總線控制器,i2c_add_driver會把i2c_driver掛到i2c總線上,并搜索總線上所有和它匹配的i2c_client,成功的話i2c_driver的probe函數(shù)就會被調(diào)用,搜索到的i2c_client會作為參數(shù)傳遞給probe函數(shù)。因為一個i2c_driver可能被多個i2c_client使用,因此就了解i2c_set_clientdata(client, data);調(diào)用的必要性了。就是說多個clients可以用一個driver,但是各自有自己的私有數(shù)據(jù)。
注:module_i2c_driver是一個針對i2c的宏定義,
定義位于/include/linux/i2c.h/
/**
* module_i2c_driver() - Helper macro for registering a I2C driver
* @__i2c_driver: i2c_driver struct
*
* Helper macro for I2C drivers which do not do anything special in module
* init/exit. This eliminates a lot of boilerplate. Each module may only
* use this macro once, and calling it replaces module_init() and module_exit()
*/
#define module_i2c_driver(__i2c_driver) \
module_driver(__i2c_driver, i2c_add_driver, \
i2c_del_driver)
使用module_i2c_driver(xxx_i2c_driver)
可以取代
-static int __init xxx_i2c_init(void)
-{
-
return i2c_add_driver(&xxx_i2c_driver);
-}
-
-static void __exit xxx_i2c_exit(void)
-{
-
i2c_del_driver(&xxx_i2c_driver);
-}
-
-
-module_init(xxx_i2c_init);
-module_exit(xxx_i2c_exit);
評論
查看更多