最近在做一個基於全志A33芯片的android移植時發現嵌入式設備很多都用到了I2C總線通信,比如說攝像頭,G-sensor,觸摸屏等,為此我覺得很好的理解I2C設備驅動在今后的嵌入式開發中是非常有好處的,而目前我也是處於學習階段,便將這些學習的過程給記錄下來,如果有存在的問題,還希望不吝指正。
我曾經用51單片機的IO口模擬I2C總線寫過驅動,實現24C02存取數據還是非常簡單的,100多行代碼就能解決,但是Linux中的I2C框架卻極為復雜,主要是在Linux下設備驅動都采用了分層的思想,在設備模型的框架下,實現一個驅動,對總線,驅動,設備的描述都將增大整個驅動的代碼,然而這樣的好處就是很方便的進行移植,當我們想要實現某一個硬件設備的驅動時,只需要做好最底層函數的實現就可以了,下面我以MMA7660(Freescale G-sensor)的驅動為例,分析一下I2C的驅動的掛載和綁定。
首先是
module_init(mma7660_init); //模塊入口凡是接觸過驅動的人都知道,當我們insmod一個驅動ko文件時,驅動的加載從上面第一個宏開始,那么分析驅動也從這里開始,由這個宏可以知道驅動入口是mma7660_init
module_exit(mma7660_exit); //模塊出口
static int __init mma7660_init(void) //為了簡潔已經去掉所有debug信息其實上述函數和驅動框架相關的就一句話ret = i2c_add_driver(&mma7660_driver),其余部分和全志的參數獲取方式有關,而這一句話表示向I2C總線注冊一個驅動,根據宏
{
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函數
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;}很明顯向總線注冊一個驅動最終調用了driver_register注冊,如果注冊成功則res = 0 之后變判斷suspend以及resume的存在性,最后調用i2c_for_each_dev函數,其實這個函數的目的就是遍歷設備鏈表,我們可以用source insight向下檢索可以知道最終調用了bus_for_each_dev函數而這個函數有如下內容,具體函數就不貼出來了。
while ((dev = next_device(&i)) && !error)可以知道遍歷鏈表后獲得dev對每個dev都執行一次fn函數(fn函數就是傳入的__process_new_driver函數),檢索__process_new_driver函數
error = fn(dev, data);
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_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); 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即將登場
本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。