------ 總線上先添加好所有具體驅動,i2c.c遍歷i2c_boardinfo鏈表,依次建立i2c_client, 並對每一個i2c_client與所有這個線上的驅動匹配,匹配上,就調用這個驅動的i2c_xxx_probe ------
所有設備驅動在init函數里,一般只做注冊平台驅動的動作,注意不是平台設備.以i2c.c為例,這個驅動的平台probe函數里做的事情比較多.因為i2c_boardinfo早已在具體驅動注冊到鏈表,i2c.c的平台驅動就是要把每一個i2c_boardinfo,實例化一個設備出來,並把這個設備添加到總線上來.好了, i2c總線上有了一個設備,然后總線針對這個設備,會遍歷總線上的所有驅動,這些驅動也是在具體驅動的平台probe里調用i2c_add_driver添加到總線上來的.所以在設備添加到總線上之前,i2c總線上的所有驅動都已經添加好了.注意這里所說的設備是i2c設備,即由i2c_boardinfo實例出一個i2c設備.添加設備,完整說法應是在總線上添加設備,一個實體只有依附於總線,被總線所管理,才能算是一個設備.所以一般根據設備所在的總線來命名設備,如i2c設備.在代碼里由i2c_client結構描述.好了,簡單小總結一下.
按發生時間順序說:
第1步: 每個設備的驅動在init時, 先要本設備的i2c信息, 包括地址等, 添加到i2c全局鏈表中. 使用i2c總線通訊的每個設備的驅動, 都要在模塊初始化的一開始, 就要做這件事. 所有設備的驅動初始化都走完之后, 再走i2c總線設備的初始化。所有驅動的init函數里把自己的i2c_boardinfo添加到i2c全局鏈表里,這個鏈表會在第4步,i2c.c的probe添加i2c設備時用到.以光感供應商光寶驅動代碼為例,在6795平台,路徑kernel-3.0/drivers/misc/mediatec/alsps/ltr559/ltr559.c,
static int __init ltr559_init(void) { struct alsps_hw *hw = ltr559_get_cust_alsps_hw(); i2c_register_board_info(hw->i2c_num, &i2c_ltr559, 1); if(platform_driver_register(<r559_alsps_driver)) { APS_ERR("failed to register driver"); return -ENODEV; } return 0; }
其他加速度,地磁,陀螺儀init都清一色和上面一樣的調用. 在自己去寫這個init函數或看到init函數時,要想到mt_i2c_probe用這個東西做的事情,添加設備啦,匹配驅動啦.
第2步: 所有具體驅動在i2c總線上都添加好,這個時間發生在各具體驅動的平台probe里調用i2c_add_driver. 還以上面為例:
static struct platform_driver ltr559_alsps_driver = { .probe = ltr559_probe, .remove = ltr559_remove, .driver = { .name = "als_ps", .owner = THIS_MODULE, .of_match_table = alsps_of_match, } }; static int ltr559_probe(struct platform_device *pdev) { struct alsps_hw *hw = ltr559_get_cust_alsps_hw(); ltr559_power(hw, 1); if(i2c_add_driver(<r559_i2c_driver)) { APS_ERR("add driver error\n"); return -1; } return 0; }
寫這個函數時,要知道這是在mt_i2c_probe之前,只有這樣才能被mt_i2c_probe添加設備時遍歷到.寫一處代碼,能聯想到其關聯地方.
第3步: i2c.c的平台probe調用.雖然同是module_init, 但makefile可以按排調用順序.i2c.c的probe一定是在第一步中所有具體驅動都添加完才調用.以確保i2c.c的probe能夠把所有具體驅動都能遍歷到.在注冊adapter之前,i2c總線也算一個設備, 所以有設備的驅動模塊, 以mt6795為例,kernel-3.0/drivers/misc/mediatek/i2c/i2c.c,平台probe函數為:
static struct platform_driver mt_i2c_driver = { .probe = mt_i2c_probe, }; static S32 mt_i2c_probe(struct platform_device *pdev) { .......... 其關鍵一句為如下 ret = i2c_add_numbered_adapter(&i2c->adap); //這就走進了標准i2c-core.c ........... }
第4步: i2c.c的probe調用i2c-core.c標准核心的i2c_adapter_register,一條i2c總線只有一個適配器.這個probe運行一次,就初始化一條i2c總線.目前liux 都采用i2c_add_numbered_addapter方式,即靜態注冊方式,即把adapter注冊到指定的i2c總線上。
i2c_adapter_register建立這條i2c總線的sysfs設備模型,包括的屬性文件有:
name, new_device, delete_device. 還有一個包括這條i2c總線上的所有掛載設備的集合。
如8850項目的所有sensor都掛到第3路i2c總線上,即i2c-2. 在/sys/devices/platform/mt-i2c.2/i2c-2下就有以各i2c設備地址命名的目錄名, 如bma056 這個設備的i2c模型在/sys/devices/platform/mt-i2c.2/i2c-2/2-0018. 2-0018, 就是這個目錄, 2表示所在的i2c總線的id, 0018表示這個設備的地址。 看一條總線上所有設備的地址, 就看這里就行。 代碼在:kernel-3.0/drivers/i2c/i2c-core.c
static int __i2c_add_numbered_adapter(struct i2c_adapter *adap) { int id; mutex_lock(&core_lock); id = idr_alloc(&i2c_adapter_idr, adap, adap->nr, adap->nr + 1, GFP_KERNEL); mutex_unlock(&core_lock); if (id < 0) return id == -ENOSPC ? -EBUSY : id; return i2c_register_adapter(adap); } static int i2c_register_adapter(struct i2c_adapter *adap) {
dev_set_name(&adap->dev, "i2c-%d", adap->nr);
adap->dev.bus = &i2c_bus_type;
adap->dev.type = &i2c_adapter_type;
res = device_register(&adap->dev); // adapter也作為一個設備注冊,這里得到的sys路徑為:
// /sys/devices/platform/mt-i2c.2/i2c-2
if (adap->nr < __i2c_first_dynamic_bus_num){ printk("lct1: %s call i2c_scan_staic_board_info for i2c new device. \n", __func__); i2c_scan_static_board_info(adap); } } 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, // 將i2c_boardinfo傳入,構造一個i2c_client, 將此i2c設備與所有此i2c線上的驅動匹配 &devinfo->board_info)) dev_err(&adapter->dev, "Can't create device at 0x%02x\n", devinfo->board_info.addr); } up_read(&__i2c_board_lock); } struct i2c_client * i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info) {
device_register(); // 這個注冊函數調用的,正面不好追碩,看下函數調用棧
}
[ 4.082399].(5)[1:swapper/0][<ffffffc00057e068>] ltr559_i2c_probe+0x410/0x8ac
[ 4.083295].(5)[1:swapper/0][<ffffffc00074e8f0>] i2c_device_probe+0xac/0x108
[ 4.084201].(5)[1:swapper/0][<ffffffc000386538>] really_probe+0x80/0x2e8
[ 4.085040].(5)[1:swapper/0][<ffffffc00038682c>] __device_attach+0x5c/0x6c
[ 4.085900].(5)[1:swapper/0][<ffffffc000384f14>] bus_for_each_drv+0x50/0x94
[ 4.086770].(5)[1:swapper/0][<ffffffc000386414>] device_attach+0xa0/0xc4
[ 4.087607].(5)[1:swapper/0][<ffffffc000385190>] bus_probe_device+0x90/0xb8
[ 4.088476].(5)[1:swapper/0][<ffffffc00038318c>] device_add+0x4b0/0x564
[ 4.089301].(5)[1:swapper/0][<ffffffc000383258>] device_register+0x18/0x28
[ 4.090158].(5)[1:swapper/0][<ffffffc00074ebb8>] i2c_new_device+0x154/0x184
以上從device_register到i2c_device_probe經歷的調用,是一個標准過程,中間必有bus_for_each_drv, 可見不管什么設備注冊,要要在此設備注冊時,遍歷該總線上的所有驅動,那些mipi總線,spi總線,uart總線,無一例外.
static int i2c_device_probe(struct device *dev)
{
........
status = driver->probe(client, i2c_match_id(driver->id_table, client)); // 調到具體驅動的i2c_xxx_probe
......
}
由此可見,i2c.c的probe最終要把i2c全局鏈表里的i2c_boardinfo都建立一個i2c設備,即i2c_client,並且要很隆重的對每個設備,和在這個線上的所有具體驅動,一一匹配,只有一個才能匹配上,匹配上了,就調用這個具體驅動的probe對此設備做初始化了.
這個就是具體驅動大名鼎鼎的i2c_xxx_probe的,是干活最多的.上電,對reset腳高低高置位,讀芯片id,id讀對了,就說明i2c通了,這是具體芯片驅動調試的第一步.做芯片驅動兼容的,也在這一步,這一步不對,就不用繼續下面的了,這些下面的做有建立sys設備模型,初始化work,timer等,注冊中斷.設置misc的文件函數集,注冊misc設備(這樣可文件讀寫,ioctl就可以用了),再有就是注冊輸入子系統.yamaha的芯片自檢節點就在輸入子系統的class下. 好像就這些吧.寫這個函數時,要意識到i2c.c在添加設備,給這設備做驅動匹配時,全靠設備的名字與驅動的名字是否相同來匹配.
----------------------------------- 接口函數中的 i2c 地址不同 -----------------------------------
一般i2c通訊的接口函數, 參數依次為, 從設備地址, 寄存器地址, 數據指針, 長度。 在feature phone上, 一次i2c通訊為8位數據, 開始部分的地址占8位,其中7位是地址, 最后一位是讀/寫標志位。 mtk feature phone的i2采用軟件模擬方式, 按着原理寫通訊函數, 先要把數據線電位拉低. 在智能機上, 采用linux內核, i2c的地址不包括讀寫標志位。 每個設備驅動都有自己實現的i2c通訊函數。mtk公開的i2c讀寫接口為:
在kernel-3.0/drivers/misc/mediatek/i2c/i2c.c中定義:
int mtk_i2c_master_recv(const struct i2c_client *client, char *buf, int count, u32 ext_flag,u32 timing) { struct i2c_adapter *adap = client->adapter; struct mt_i2c_msg msg; int ret; msg.addr = client->addr; msg.flags = client->flags & I2C_M_TEN; msg.timing = timing; msg.flags |= I2C_M_RD; msg.len = count; msg.buf = buf; msg.ext_flag = ext_flag; ret = mtk_i2c_transfer(adap, &msg, 1); // transfer函數由mtk平台驅動工程師要實現這個函數 /* * If everything went ok (i.e. 1 msg received), return #bytes received, * else error code. */ return (ret == 1) ? count : ret; } int mtk_i2c_master_send(const struct i2c_client *client, const char *buf, int count, u32 ext_flag,u32 timing) { int ret; struct i2c_adapter *adap = client->adapter; struct mt_i2c_msg msg; msg.addr = client->addr; msg.flags = client->flags & I2C_M_TEN; msg.len = count; msg.timing = timing; msg.buf = (char *)buf; msg.ext_flag = ext_flag; ret = mtk_i2c_transfer(adap, &msg, 1); /* * If everything went ok (i.e. 1 msg transmitted), return #bytes * transmitted, else error code. */ return (ret == 1) ? count : ret; }
光感ltr559.c對此調用為:
static int ltr559_i2c_read_reg(u8 regnum) { u8 buffer[1],reg_value[1]; int res = 0; mutex_lock(&read_lock); buffer[0]= regnum; res = i2c_master_send(ltr559_obj->client, buffer, 0x1);//傳入i2c_client, 發送的數據,發送的字節個數 if(res <= 0) { mutex_unlock(&read_lock); APS_ERR("read reg send res = %d\n",res); return res; } res = i2c_master_recv(ltr559_obj->client, reg_value, 0x1); if(res <= 0) { mutex_unlock(&read_lock); APS_ERR("read reg recv res = %d\n",res); return res; } mutex_unlock(&read_lock); return reg_value[0]; }
--------------------------- i2c 基本原理 及 i2c gpio 配置 -------------------------------------
i2c 是多主機方式, 同一條總線上,可以有多個主線,硬件上, 每一條i2c線,有一個i2c適配器。 6592基帶主芯片都有i2c的實現,如果需要使用,可以把相應的gpio口配置成i2c模式, 這樣就是硬件i2c方式。 能接到i2c通訊的器件, 也都有i2c接口, 這在芯片的規格書上都有說明的,都會有支持i2c串行接口的說明,i2c地址也是固定的。 在規格書明確寫出的。 有些有可選地址, 某個pin角被拉高, 是一個地址, 被拉低是另一個地址。有i2c接口的器件內部都有一個三極管開關電路, pin腳接三極管的集電極, 當基極通電流時, 開關被打開, 外接引腳被拉低。
總線上的各種狀態:
總線空閑狀態: 數據線和時鍾線均拉高,
當數據線有下降沿時, 即是i2c通訊的啟動信號
當數據線有上升沿時, 即是i2c通訊的結束信號
誰發起i2c通訊,就由誰結束i2c通訊。 所以i2c的啟動信號與下降信號都由主機產生。
所有時鍾線控制整個i2c總線的狀態, 時鍾線如果為高電平, 表示i2c可以是啟動或結束, 如果為低電平, 表示不能啟動或結束, 只能是傳輸數據狀態。
i2c 先配好i2c的數據線與時鍾線. mtk的feature phone和智能機, 都用dws來配制gpio, i2c可用gpio來模擬,
dws對i2c gpio 的配置, 數據線和時鍾線都要配置成內部上拉, 方向配置成既可輸入,又可輸出。 還需要配名字, 驅動代碼中有表示i2c pin角的名字變量,這樣dws生成代碼這個變量就會被賦值成這個pin角。 如果沒配, 這個變量就取默認值0xff. 如果沒配制 i2c gpio, 那么數據就會讀異常,有時也能讀出來,但異常, 如一個字節要用兩個字節的數組存放。
i2c要在dws配置gpio口, 如果gpio配制成i2c模式,就是硬件i2c, 如果沒有配置成i2c模式,就是軟件i2c, 即軟件模擬i2c,就在用代碼寫i2c的時鍾,而硬件i2c的時鍾是由晶振的倍頻產生的,硬件i2c比軟件模擬i2c更穩定。
判斷i2c是硬件方式, 還是軟件模擬方式, 就是看dws配置表上, gpio的模式。 無論是硬件還是軟件, 都需要配置gpio配置表, 這樣代表i2c的pin腳變量會得到賦值。 這個變量在i2c通訊函數會被用到。 mt6260 feature phone平台, 用的是軟件模擬i2c. 供應商驅動有自己的i2c通訊函數的實現, 一般都能找到硬件i2c通訊的實現,和軟件i2c通訊的實現。 有軟件去設置時鍾線,由於目前的mtk智能機平台, 都用硬件i2c方式, 所以簡單看下mt6592 平台上的硬件i2c方式的實現:
調試初期要做的是, 看i2c通沒通, 一般先讀一下寄存器的id, i2c讀函數返回小於0, 表示i2c通訊有錯。
---------------- i2c總線作為一種平台設備被添加到平台總線 ---------------------
主芯片內部有i2c的實現, 相應的gpio可以配置成i2c硬件方式, 這個適配器也在主芯片內部. i2c總線作為一種平台設備,在板級初始化時,被添加到平台總線上.
mt6592有三條i2c總線(mt6795有四條),i2c總線是作為一種平台設備被注冊的。 在板級初始化時,注冊了三條i2c總線設備。在/sys/devices/platform/ 會有三個i2c目錄,平台設備的類型為platform_device,有兩個重要的成員,name, id. 如果平台設備名相同,可以用id區分,如三條i2c總線作為平台設備,名字相同,id不同。
現在已經注冊了三個i2c總線平台設備。有三個name相同, id不同的平台設備。那么在注冊平台驅動時,相應的probe,就會被調用三次。 即mt6592平台的對i2c總線的驅動模塊i2c.c 的平台驅動的probe就被調用三次。
代碼位置:kernel-3.0/drivers/misc/mach/mt6795/mt_devs.c
static struct platform_device mt_device_i2c[] = { { .name = "mt-i2c", .id = 0, .num_resources = ARRAY_SIZE(mt_resource_i2c0), .resource = mt_resource_i2c0, }, { .name = "mt-i2c", .id = 1, .num_resources = ARRAY_SIZE(mt_resource_i2c1), .resource = mt_resource_i2c1, }, { .name = "mt-i2c", .id = 2, .num_resources = ARRAY_SIZE(mt_resource_i2c2), .resource = mt_resource_i2c2, }, { .name = "mt-i2c", .id = 3, .num_resources = ARRAY_SIZE(mt_resource_i2c3), .resource = mt_resource_i2c3, }, }; __init int mt_board_init(void) { .......... // 平台設備很多,都在這里注冊. retval = platform_device_register(&gpio_dev); for (i = 0; i < ARRAY_SIZE(mt_device_i2c); i++){ retval = platform_device_register(&mt_device_i2c[i]); printk("[%s]: mt_device_i2c[%d] finished probe, retval=%d\n", __func__, i, retval); if (retval != 0){ return retval; } } ............. } late_initcall(mt_board_init);
platform_device_register 會調用device_add,這樣就會走一個流程,即總線上新加一個設備,總線就會把這個總線上的所有驅動與這個設備匹配,作為i2c總線這種平台設備,與其匹配的是i2c.c中的驅動相匹配,最終調用mt_i2c_probe這個平台函數.這個就是i2c總線的一部分,做出把所有設備添加,並把所有驅動與每個設備匹配,這種驚天動地的事,都是這個函數做出來的.在做這些事之前,mt_i2c_probe做如下事情:
1. 申請內存資源, 中斷資源, i2c總線作為一個設備, 是應該有對應的地址, 所以分配點內存資源給它.
2. i2c中斷響應函數做的事: 清除中斷掩碼, 保存中斷狀態, 清除中斷狀態.
3. i2c有很多寄存器, 如控制寄存器, 從地址寄存器, 往這些寄存器寫數據, 以完成相應的功能, 都需要i2c設備驅動來完成. i2c硬件初始化就要對軟件復位寄存器寫1.
4. 設備i2c適配器的數據來源, 適配器是一個設備, 該設備的數據來源就是i2c總線.
5. 添加i2c適配器, 這一步有兩個事情, 一個是把adapter添加到基樹中, 一個注冊i2c適配器. 注冊i2c適配器. 這個注冊函數會把所有設備驅動初始化時所添加到i2c設備信息鏈表里的所有節點都遍歷一遍, 對每個節點, 都調用i2c_new_device, 建立一個i2c設備, 隨之建立一個i2c設備模型。每個掛到i2c總線上的設備都有自己的i2c設備模型。 這是注冊i2c adapter非常重要的一步, 經過這一步, 掛在i2c總線上的設備才算是i2c設備, 即可以進行i2c通訊的設備。
static S32 mt_i2c_probe(struct platform_device *pdev) { res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!request_mem_region(res->start, resource_size(res), pdev->name)) { return -ENOMEM; } if (NULL == (i2c = kzalloc(sizeof(mt_i2c), GFP_KERNEL))) return -ENOMEM; #ifdef CONFIG_OF i2c->adap.dev.of_node = pdev->dev.of_node; #endif i2c->adap.nr = i2c->id; i2c->adap.owner = THIS_MODULE; i2c->adap.algo = &mt_i2c_algorithm; i2c->adap.algo_data = NULL; i2c->adap.timeout = 2 * HZ; /*2s*/ i2c->adap.retries = 1; /*DO NOT TRY*/ /*need GFP_DMA32 flag to confirm DMA alloc PA is 32bit range*/ i2c->dma_buf.vaddr = dma_alloc_coherent(&pdev->dev, MAX_DMA_TRANS_NUM, &i2c->dma_buf.paddr, GFP_KERNEL|GFP_DMA32); memset(i2c->dma_buf.vaddr, 0, MAX_DMA_TRANS_NUM); snprintf(i2c->adap.name, sizeof(i2c->adap.name), I2C_DRV_NAME); init_waitqueue_head(&i2c->wait); ret = request_irq(irq, mt_i2c_irq, IRQF_TRIGGER_LOW, I2C_DRV_NAME, i2c); mt_i2c_init_hw(i2c); i2c_set_adapdata(&i2c->adap, i2c); printk("lct1: %s, register adapter. \n", __func__); ret = i2c_add_numbered_adapter(&i2c->adap); }
------- 先把設備添加好,再添加驅動的例子 ------
i2c總線這種都是在把所有驅動加載好,再添加設備的.但也有出現反過來的.比如下面的例子,就是把設備添加好,然后再添加驅動.如果一個設備不需要掛在實際真實的總線上,就要把它掛載到平台總線,平台總線沒有實際的總線.
像紅外控制,就是gpio口,發送編碼后的pwm波.所以就直接掛到平台總線上.代碼如下:
代碼位置: kernel-3.0/drivers/misc/mediatek/consumerir/consumerir.c
static struct platform_driver mt_consumerir_driver = { .driver = { .name = "consumerir", }, .probe = consumerir_probe, }; static struct platform_device mt_consumerir_dev = { .name = "consumerir", .id = -1, }; static int __init consumerir_init(void) { int ret = 0; if(platform_device_register(&mt_consumerir_dev) != 0) { return -ENODEV; } ret = platform_driver_register(&mt_consumerir_driver); if (ret) { return -ENODEV; } return 0; }
由此明顯看出,設備中的name與驅動中name必須一致.這樣總線在為這個設備遍歷各個驅動時,才能和這個驅動匹配上.如兩個玉佩,到時見面,就互相認識了.之前在i2c總線,驅動,設備時說過,設備在所有驅動添加完再添加.device_regisgter會調用bus_for_each_drv來遍歷所有驅動.但在driver_register也會調用bus_for_each_div遍歷所有設備. 所以遍歷是雙向的.在這個例子,用了第二種.
--------------------------------- 先在總線上添加設備,之后在總線上注冊驅動 -------------------------
先說平台設備的添加,與i2c設備的添加。平台設備是平台總線設備的簡稱,i2c設備是i2c總線設備的簡稱。這里的設備以其所在的總線命名。平台設備類型為platform_device, i2c設備類型為i2c_client, 兩個類型都有device這個成員。
這里所說的設備並不實際意義的設備,只是站在某一個角度對設備的描述。
其實對實際的設備說,就是給它下寄存器,它就能工作了,放在總線上,就是為了利用總線,設備向總芯片發命令,設備輸出數據傳給主芯片,所以調試初期,先看總線通沒通,一般是i2c總線。實際的設備也是掛在總線上,從代碼上,要有一種統一的管理,這種管理架構就要與實際盡量吻合,所以軟件架構中出現的平台設備,i2c設備都是以總線的角度對設備的描述。
所謂描述就是建立一個模型吧,通過對這個模型的操作來達到對實際設備的操作, 如解決一個物理問題時,要有各種變量,模型反映出各變量之間的聯系,利用模型中的信息求出未知變量。
在驅動注冊到總線之前,必須保證總線上的所有設備都添加完。 所以在總線上添加設備的動作都是在驅動注冊之前完成。
在板級初始化時,在平台總線上注冊平台設備,即在平台總線上添加設備。 這個階段會把所有設備都添加到平台總線上。 這個階段完成后,在/sys/devices/platform/ 就會很多目錄,每個目錄描述一個平台設備。
在具體模塊驅動init時,先在i2c總線上添加一個設備,這個設備就是i2c設備,實現是通過一個函數向i2c信息鏈表中添加一個節點, 在稍后的i2c總線驅動模塊的平台驅動的probe, 會調用i2c_register_adapter, 把i2c信息鏈表中的每個節點都建立對應的i2c設備,即i2c_client對象。
---------------------------- 在總線上注冊驅動時,遍歷總線上的所有設備 ------------------------
注冊驅動的機制是,這個驅動會遍歷平台總線上的所有設備,有些書本會寫, 探測總線上的所有設備,給人感覺好像硬件探測一樣,其實這里完全是軟件上的一種遍歷,就是驅動的名字如果與設備的名字如果相同,那么驅動就和該設備匹配上。 這時具體驅動模塊的平台驅動probe被調用。
這個平台驅動的probe一般只會在i2c總線上注冊一個驅動(如果是camera,應該是在mipi總線上注冊一個驅動),並沒有做其他具體的事情。 具體事情由i2c驅動的probe完成,包括建立一個driver的設備模型, 對設備下寄存器,注冊中斷,注冊輸入子系統,打開sensor供電。
舉個例子,gsensor, 板級初始化時, 注冊平台設備, 代碼:
static struct platform_device sensor_gsensor = {
.name = "gsensor",
.id = -1,
};
注冊平台設備
platform_device_register(&sensor_gsensor);
bosch的加速度驅動文件bma056.c中,init函數中, 在平台總線上注冊一個平台驅動,
static struct platform_driver gsensor_platform_driver = {
.probe = platfrom_probe,
};
platform_driver_register(&gsensor_platform_driver);
平台驅動的probe會在i2c總線上注冊一個i2c驅動, 即
i2c_add_driver(&i2c_driver);
既然在平台總線上注冊驅動之前,平台總線上就已經添加好了設備。 那么在i2c總線上注冊驅動之前,i2c總線上是否也已經添加好的設備。
這個問題的答案在, 在添加i2c_board_info時, 設備的名字, 與設備的地址傳給i2c的一個鏈表,i2c初始化時,對鏈表中的每一個建立一個i2c設備。 這個設備會傳給i2c驅動的probe。
看用了哪個驅動,可以到設備模型/sys/bus/i2c/drivers下有沒有這個驅動的名字。
如果平台設備的名字與平台驅動的名字不一樣,就驅動就不會probe. 之前在做bosch與st兼容時,就遇到這個問題。
------------------------------ i2c 設備參數 -----------------------
------------------
設備的參數數據, 以加速度驅動為例.
通過i2c總線從設備讀過來的數據都是原始數據, 這些數據在驅動要做一些轉換,轉換的參數如靈敏度,坐標系映射, 校准參數。 這些參數對這個設備是必須的,這些設備參數就要跟設備相關聯,即拿到了設備,這個設備就帶着這個設備的參數。有專門的函數設置設備參數,即dev_set_drvdata(). 對於i2c總線上的設備, 即注冊到i2c總線上,這個設備就是一個i2c設備,即i2c_client. 有專門的函數設置設備的參數數據, 即i2c_set_clientdata(client, data). 這個函數封裝了dev_set_drvdata().
每個sensor驅動都有設備參數結構體類型,因為是i2c設備,所以類型的名字都是...i2c_data; 在從i2c總線讀取sensor數據時,一般都要i2c_get_clientdata(client)先獲取設備參數。
在哪創建設備節點,要看傳給創建設備節點的kobject是誰,可以是driver, 也可以是device.
6752的sensor驅動換了傳入參數,而我還在原來的地方找設備節點,就當然找不到了。
-------------------------- i2c_probe 函數棧 --------------------
驅動中有空指針錯誤, 串口log就有oops。 如果在i2c_probe有空指針, 就有如下函數棧:
[ 4.082399].(5)[1:swapper/0][<ffffffc00057e068>] ltr559_i2c_probe+0x410/0x8ac
[ 4.083295].(5)[1:swapper/0][<ffffffc00074e8f0>] i2c_device_probe+0xac/0x108
[ 4.084201].(5)[1:swapper/0][<ffffffc000386538>] really_probe+0x80/0x2e8
[ 4.085040].(5)[1:swapper/0][<ffffffc00038682c>] __device_attach+0x5c/0x6c
[ 4.085900].(5)[1:swapper/0][<ffffffc000384f14>] bus_for_each_drv+0x50/0x94
[ 4.086770].(5)[1:swapper/0][<ffffffc000386414>] device_attach+0xa0/0xc4
[ 4.087607].(5)[1:swapper/0][<ffffffc000385190>] bus_probe_device+0x90/0xb8
[ 4.088476].(5)[1:swapper/0][<ffffffc00038318c>] device_add+0x4b0/0x564
[ 4.089301].(5)[1:swapper/0][<ffffffc000383258>] device_register+0x18/0x28
[ 4.090158].(5)[1:swapper/0][<ffffffc00074ebb8>] i2c_new_device+0x154/0x184
[ 4.091027].(5)[1:swapper/0][<ffffffc00074ef78>] i2c_register_adapter+0x1fc/0x30c
[ 4.091961].(5)[1:swapper/0][<ffffffc00074f0e0>] __i2c_add_numbered_adapter+0x58/0x88
[ 4.092939].(5)[1:swapper/0][<ffffffc00074f264>] i2c_add_numbered_adapter+0x14/0x2c
[ 4.093920].(5)[1:swapper/0][<ffffffc000647304>] mt_i2c_probe+0x348/0x45c
[ 4.094771].(5)[1:swapper/0][<ffffffc000387b94>] platform_drv_probe+0x18/0x24
[ 4.095663].(5)[1:swapper/0][<ffffffc000386624>] really_probe+0x16c/0x2e8
[ 4.096512].(5)[1:swapper/0][<ffffffc0003868e4>] __driver_attach+0xa8/0xb0
[ 4.097370].(5)[1:swapper/0][<ffffffc000384a30>] bus_for_each_dev+0x54/0x98
[ 4.098240].(5)[1:swapper/0][<ffffffc000386454>] driver_attach+0x1c/0x28
[ 4.099078].(5)[1:swapper/0][<ffffffc00038547c>] bus_add_driver+0x1bc/0x258
[ 4.099948].(5)[1:swapper/0][<ffffffc0003870d0>] driver_register+0x68/0x15c
[ 4.100821].(5)[1:swapper/0][<ffffffc0003887b8>] platform_driver_register+0x58/0x64
[ 4.101782].(5)[1:swapper/0][<ffffffc000e11c84>] mt_i2c_init+0x6c/0x78
[ 4.102601].(5)[1:swapper/0][<ffffffc000de583c>] do_one_initcall+0xa0/0x1b4
[ 4.103473].(5)[1:swapper/0][<ffffffc000de5a8c>] kernel_init_freeable+0x13c/0x1e0
[ 4.104433].(5)[1:swapper/0][<ffffffc0009c19c4>] kernel_init+0x14/0x14c
[ 4.105257]-(5)[1:swapper/0]Internal error: Oops: 96000045 [#1] PREEMPT SMP
------------------ i2c_xxx_probe在i2c總線init調用的平台probe里被遍歷 ------------------
mt的i2c總線驅動也做為一種普通的驅動, 放在/kernel-3.0/dirver/misc/medietek/i2c/mt6795下, 文件名為i2c.c
這個驅動的模塊初始化也一般具體驅動的初始化, 都是調用平台注冊函數, platform_register. 不同是, 一般具體驅動平台probe函數里, 只調用i2c_add_driver. 即設置好本驅
動的i2c的回調函數, 而i2c.c里的平台probe則會完成此i2c總線上各個驅動的i2c_xxX_probe的調用, 如光感的ltr559_i2c_probe, 地磁的yas_i2c_probe, 加速度的
bmi_acc_i2c_probe都會在第2條i2c總線的平台probe調用. 其調用棧為:
[ 4.082399].(5)[1:swapper/0][<ffffffc00057e068>] ltr559_i2c_probe+0x410/0x8ac
[ 4.083295].(5)[1:swapper/0][<ffffffc00074e8f0>] i2c_device_probe+0xac/0x108
[ 4.084201].(5)[1:swapper/0][<ffffffc000386538>] really_probe+0x80/0x2e8
[ 4.085040].(5)[1:swapper/0][<ffffffc00038682c>] __device_attach+0x5c/0x6c
[ 4.085900].(5)[1:swapper/0][<ffffffc000384f14>] bus_for_each_drv+0x50/0x94
[ 4.086770].(5)[1:swapper/0][<ffffffc000386414>] device_attach+0xa0/0xc4
[ 4.087607].(5)[1:swapper/0][<ffffffc000385190>] bus_probe_device+0x90/0xb8
[ 4.088476].(5)[1:swapper/0][<ffffffc00038318c>] device_add+0x4b0/0x564
[ 4.089301].(5)[1:swapper/0][<ffffffc000383258>] device_register+0x18/0x28
[ 4.090158].(5)[1:swapper/0][<ffffffc00074ebb8>] i2c_new_device+0x154/0x184
[ 4.091027].(5)[1:swapper/0][<ffffffc00074ef78>] i2c_register_adapter+0x1fc/0x30c
[ 4.091961].(5)[1:swapper/0][<ffffffc00074f0e0>] __i2c_add_numbered_adapter+0x58/0x88
[ 4.092939].(5)[1:swapper/0][<ffffffc00074f264>] i2c_add_numbered_adapter+0x14/0x2c
[ 4.093920].(5)[1:swapper/0][<ffffffc000647304>] mt_i2c_probe+0x348/0x45c
這個在ltr559_i2c_probe埋個空指針錯誤, 開機kernel_log就會有這個調用棧.
各驅動的probe調用順序, 應該是同時的, 因為只有具體驅動的probe在調i2c_add_driver把本驅動的i2c_probe等回調設置好后, i2c.c的平台probe才能把他遍歷到.
從上面的函數棧來看, i2c注冊適配器時, 會把掛在這條i2c總線上的每個設備都建立一個i2c設備對象, 即i2c_new_device, 就是把這個設備變成一個i2c設備. 具體驅動的init時,
的i2c_register_board_info向i2c鏈表里添加的這個設備的i2c信息(包括i2c第幾條線, i2c地址)就會傳給這個i2c_new_device函數. 這個變化需要做兩件事, 一是要准備一個i2c
client. 一個是要調用到這個設備的i2c_xxx_probe函數, 並且把准備好的i2c client傳給這個函數. 從軟件的角度, 完成這些才算是把這個設備納入到這個總線的管理下. 所以
軟件的初始化, 就好像建立一個模型, 里面有各種變量的初值, 以及回調函數的有效設置.
mtklod內含mobile log, modem log. mobilelog又包括kernel log, main log, 這些都有開關設置的, 有一個開關控制開機時自啟的log, 這個log在分析重啟時是必須的.