Linux下I2C驅動分析(一)
阿新 • • 發佈:2019-01-23
最近在做一個基於全志A33晶片的android移植時發現嵌入式裝置很多都用到了I2C匯流排通訊,比如說攝像頭,G-sensor,觸控式螢幕等,為此我覺得很好的理解I2C裝置驅動在今後的嵌入式開發中是非常有好處的,而目前我也是處於學習階段,便將這些學習的過程給記錄下來,如果有存在的問題,還希望不吝指正。
我曾經用51微控制器的IO口模擬I2C匯流排寫過驅動,實現24C02存取資料還是非常簡單的,100多行程式碼就能解決,但是Linux中的I2C框架卻極為複雜,主要是在Linux下裝置驅動都採用了分層的思想,在裝置模型的框架下,實現一個驅動,對匯流排,驅動,裝置的描述都將增大整個驅動的程式碼,然而這樣的好處就是很方便的進行移植,當我們想要實現某一個硬體裝置的驅動時,只需要做好最底層函式的實現就可以了,下面我以MMA7660(Freescale G-sensor)的驅動為例,分析一下I2C的驅動的掛載和繫結。
首先是
module_init(mma7660_init); //模組入口
module_exit(mma7660_exit); //模組出口
凡是接觸過驅動的人都知道,當我們insmod一個驅動ko檔案時,驅動的載入從上面第一個巨集開始,那麼分析驅動也從這裡開始,由這個巨集可以知道驅動入口是mma7660_init
其實上述函式和驅動框架相關的就一句話ret = i2c_add_driver(&mma7660_driver),其餘部分和全志的引數獲取方式有關,而這一句話表示向I2C匯流排註冊一個驅動,根據巨集static int __init mma7660_init(void) //為了簡潔已經去掉所有debug資訊 { int ret = -1; if (input_fetch_sysconfig_para(&(gsensor_info.input_type))) { //從配置檔案中獲取是否存在G-sensor裝置引數 return -1; } else twi_id = gsensor_info.twi_id; ret = i2c_add_driver(&mma7660_driver); //向I2C匯流排註冊一個MMA7660的驅動(此處的I2C匯流排指的是Linux驅動中裝置模型的匯流排,這是一個虛擬的平臺匯流排,而不是實際的I2C匯流排) if (ret < 0) { return -ENODEV; } return ret; }
#define i2c_add_driver(driver) \
i2c_register_driver(THIS_MODULE, driver)
檢索到i2c_register_driver函式很明顯向匯流排註冊一個驅動最終呼叫了driver_register註冊,如果註冊成功則res = 0 之後變判斷suspend以及resume的存在性,最後呼叫i2c_for_each_dev函式,其實這個函式的目的就是遍歷裝置連結串列,我們可以用source insight向下檢索可以知道最終呼叫了bus_for_each_dev函式而這個函式有如下內容,具體函式就不貼出來了。int i2c_register_driver(struct module *owner, struct i2c_driver *driver) { int res; if (unlikely(WARN_ON(!i2c_bus_type.p))) return -EAGAIN; driver->driver.owner = owner; driver->driver.bus = &i2c_bus_type; res = driver_register(&driver->driver); if (res) return res; if (driver->suspend) pr_warn("i2c-core: driver [%s] using legacy suspend method\n", driver->driver.name); if (driver->resume) pr_warn("i2c-core: driver [%s] using legacy resume method\n", driver->driver.name); INIT_LIST_HEAD(&driver->clients); i2c_for_each_dev(driver, __process_new_driver); return 0; }
while ((dev = next_device(&i)) && !error)
error = fn(dev, data);
可以知道遍歷連結串列後獲得dev對每個dev都執行一次fn函式(fn函式就是傳入的__process_new_driver函式),檢索__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,檢索這個函式可以得到其中有一個i2c_detect函式,一直向下檢索最終可以得到i2c_detect_address函式,根據i2c_detectstatic 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);
address_list = driver->address_list;
if (!driver->detect || !address_list)
return 0;
if (!(adapter->class & driver->class))
return 0;
temp_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);
if (!temp_client)
return -ENOMEM;
temp_client->adapter = adapter;
for (i = 0; address_list[i] != I2C_CLIENT_END; i += 1) {
temp_client->addr = address_list[i];
err = i2c_detect_address(temp_client, driver);
if (unlikely(err))
break;
}
kfree(temp_client);
return err;
}
可以知道該函式的作用就是對每一個地址連結串列(該地址連結串列就是最開始註冊驅動時提供的驅動結構體裡面的地址連結串列)執行i2c_detect_address再檢索這個函式能看到其中有 memset(&info, 0, sizeof(struct i2c_board_info));
info.addr = addr;
err = driver->detect(temp_client, &info);
if (err) {
return err == -ENODEV ? 0 : err;
}
if (info.type[0] == '\0') {
dev_err(&adapter->dev, "%s detection function provided "
"no name for 0x%x\n", driver->driver.name,
addr);
} else {
struct i2c_client *client;
client = i2c_new_device(adapter, &info);
if (client)
list_add_tail(&client->detected, &driver->clients);
else
dev_err(&adapter->dev, "Failed creating %s at 0x%02x\n",
info.type, info.addr);
}
該函式呼叫了driver->detect這就是我們寫的驅動裡面的detect函式,繞了一個大圈子,終於又回到了我們自己寫的驅動static int gsensor_detect(struct i2c_client *client, struct i2c_board_info *info)
{
struct i2c_adapter *adapter = client->adapter;
int ret;
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
return -ENODEV;
if(twi_id == adapter->nr){
pr_info("%s: addr= %x\n",__func__,client->addr);
ret = gsensor_i2c_test(client);
if(!ret){
pr_info("%s:I2C connection might be something wrong or maybe the other gsensor equipment! \n",__func__);
return -ENODEV;
}else{
pr_info("I2C connection sucess!\n");
strlcpy(info->type, SENSOR_NAME, I2C_NAME_SIZE);
return 0;
}
}else{
return -ENODEV;
}
}
根據對應client傳送一個測試資料如果沒有問題則證明這個client是這個驅動所需要的裝置,最後將裝置新增到連結串列,可以檢索i2c_new_device得到bus_probe_device函式開始繫結裝置,至此裝置模型的三個要素:匯流排,驅動,裝置都已經存在,probe即將登場