BeagleBoard OMAP3530 Linux SDIO Expansion Libertas Trace

這兩個月的工作就是在讓BeagleBoard上面的Expansion Slot可以跑MMC的功能,然後讓SDIO WiFi可以Work。弄了很久,結果發現是硬體的功能好像有問題Orz。

Linux Kernel MMC/SD Source Trace

首先至少會有三個部分,Linux MMC Core (drivers/mmc/core)、MMC Controller 的Driver (drivers/mmc/host/omap_hsmmc.c)、SDIO Card的Driver (drivers/net/wireless/libertas)。

Core 中負責的是 MMC 的Protocol的部分,例如要送甚麼Command,怎麼Parse 卡片回來的Response之類的。這裡也是Bus Driver的所在地,但我對Bus Driver沒有看很懂~

Hsmmc (high speed mmc)則是負責要怎麼送出資料(設定DMA之類的),所以會跟硬體有關。

Libertas則是負責跟SDIO無線網卡的溝通。

drivers/mmc/core/core.c 中的mmc_rescan會去掃描目前的卡片類型,下面是我查到的卡片類型會送出的命令序列,這裡有一份很好的參考文件,可以去找SDIO Simplified Specification Version 2.00.PDF。原本SDIO SPEC是要簽NDA的,但後來釋出簡化版的讓Linux可以將SDIO包到Kernel Source裡面,但有一些比較屬於硬體的Timing過程就沒有公布了~

MMC/SD/SDIO Command 詢問過程

SDIO: 0 8 5(SDIO_SEND_OP_COND) 5 3 7 52 52 52

SD: 0 8 5 55 41 0 8 55 41(SD_APP) 2(Send All CID)

上面都失敗之後就換MMC

MMC: 1(MMC_SEND_OP_COND)

上面可以看到SDIO會回第一個5的CMD,而SD不會回應,所以就改為送出55,MMC則是都不回應,最後會回應CMD 1。

如果問到5有回應之後,就會交給 drivers/mmc/core/sdio.c的mmc_attach_sdio、drivers/mmc/core/sd.c的mmc_attach_sd、drivers/mmc/core/mmc.c的mmc_attach_mmc

SDIO定義一張卡最多可以有七個function (沒有用到0),每一個function可以決定支援哪一種interface (Ex: Camera or WLAN)。mmc_card->sdio_func,而每一個sdio_func下面都會有一個device,看起來Kernel是把每一個function都定義為一個device。一張卡片可以是多個device。

CMD5的Response包含 ocr (Operation Conditions Register) 會定義可以操作的電壓,最左邊的3個bit會告訴host有幾個funcs,然後呼叫kernel的 device_add(&func->dev),我們用的libertas只有一個function (WiFi)

*********************************************************************

這裡講解一下IRQ,以前學校一直再說IRQ,但根本不知道怎麼實現的XD

在Linux Kernel裡面,註冊ISR很簡單,只要給一個IRQ Number,然後傳進一個function pointer就可以了,在嵌入式環境IRQ Number都已經定義好了,而且這部分是負責Porting Kernel的人做的,他必須把版子上的硬體相關資訊都在kernel一啟動的時候加進kernel裡面。下面就解釋一下MMC IRQ的建立過程:

BeagleBoard的MMC IRQ 建立過程:

MMC Controller在Kernel裡面算是一個 platform_device,然後會有一個成員是 resource (可能是 memory mapped 位址、IRQ……之類的),透過platform_get_resource取得

arch/asm/mach-omap2/board-omap3beagle.c 裡面的 beagle_twl_gpio_setup 會呼叫arch/asm/mach-omap2/mmc-twl4030.c 裡面的 hsmmc_init 接著再呼叫

arch/asm/mach-omap2/devices.c 裡面的 omap2_init_mmc這裡會把 SPEC上面寫的 IRQ 分別對應到 controller N,分別是 83、86、94 (定義在 arch/arm/plat-omap/include/mach/irq.h),這是硬體就layout好的,此函式會加入 kernel的platform_device,最後呼叫 arch/arm/plat-omap/devices.c 的 omap_mmc_add 這裡的 Device Name 會被指定為 mmci-omap,然後把 irq 放到 platform_device下面的 resource裡面。

至於為什麼以前常聽說PC的IRQ會衝?因為PC上面有BIOS,Embedded 沒有BIOS,較單純,在BIOS那裏會對PCI做IRQ分配然後通知OS。現在ARM也有支援PCI了,好像使用 bios32.c 這個檔案來模擬,詳情我不懂~

下面是Bus Driver沒有看很懂~

在 drivers/mmc/core/bus.c 的 mmc_bus_probe 會把傳進來的device轉為 mmc_card 和 mmc_driver 傳給 hsmmc的omap_mmc_probe。

然後在 drivers/mmc/host/omap_hsmmc.c 裡面的omap_mmc_probe,kernel會把 platform_device傳進來,必須使用platform_get_irq(把之前定義好的 irq Number取出,然後使用 request_irq 去把ISR串起來。

*********************************************************************

arch/arm/mach-omap2/devices.c裡面會指派 MMC1 Controller 的Base Address 給 device,0x4809c000 定義在 arch/arm/plat-omap/include/mach/mmc.h裡面

下面是SD卡的Trace

drivers/mmc/card/block.c 會先註冊 /dev/mmcblk 的 device

drivers/mmc/host/omap_hsmmc.c 中的 omap_mmc_probe 此函式會先把傳進的 platform_device 轉為 mmc_omap_host,這裡會註冊request_irq(host->irq, mmc_omap_irq 和card_detection_IRQ,接下來執行mmc_add_host

drivers/mmc/core/host.c 中的 mmc_alloc_host

再呼叫 drivers/mmc/core/core.c 中的mmc_rescan 掃描 SDIO, SD, MMC,如果其中一個有回應就跳出,因為一個插槽只給一種卡使用

下面去看SD的SPEC比較快,他會跟你說要送哪些CMD,把握一個原則,送哪些Command是Core裡面決定的,至於怎麼送,則是Controller Driver的工作,所以如果要看怎麼設定DMA之類的,就去看hsmmc.c。

偵測卡片的函式: drivers/mmc/core/sd_ops.c 中的 mmc_send_app_op_cond->mmc_wait_for_app_cmd->mmc_app_cmd->

再呼叫 drivers/mmc/core/core.c 中的 mmc_wait_for_cmd -> mmc_wait_for_req->mmc_start_request 這裡會呼叫 函式指標呼叫 drivers/mmc/host/omap_hsmmc.c 中的 mmc_host_ops mmc_omap_ops = { .request = omap_mmc_request} -> mmc_omap_start_command -> 接下來就是呼叫底層的 IO R/W 的函式了(),後面再Callback回來

*********************************************************************

如果正常偵測到卡片回覆命令是正常的話,就會掛載 SD Card

再呼叫 drivers/mmc/card/sd.c 中的 mmc_attach_sd

再呼叫 drivers/mmc/core/bus.c

mmc_alloc_card

mmc_add_card

mmc_bus_uevent

mmc_bus_probe

再呼叫 drivers/mmc/card/block.c

mmc_blk_probe

*********************************************************************

讓BeagleBoard的Expansion可以啟用。

先說明,這裡一般人是不能測試的,因為expansion吐出來的電壓是1.8 V,而SD的SPEC至少要2.x 忘了以上才能動。所以還要去訂購一個叫做 MMC Transceiver (型號是 SN74AVCA406LZXY)的東西,如果沒有門路的話,一次就要定1000個XD,定來之後還是不能用,因為他是一個BGA的元件,上面有20根pin,整個IC的大小只有0.2公分,根本不能自己焊,所以又花了五萬塊請人家做個PCB版來接線XD。

但你如果要啟用expansion slot的gpio或是SPI功能的話就可以類似的改法就可以用了

要多加一組MMC2的話,

先做最簡單的事,就是跟kernel說我們有兩個Controller的Device,下面就是宣告這個~

arch/arm/mach-omap2/board-omap3beagle.c 中的 beagle_twl_gpio_setup

會呼叫 arch/arm/mach-omap2/mmc-twl4030.c 中的 hsmmc_init 這裡我多加了一組,wp是write protection的意思,MMC2是沒有此根線的,那個29代表的是gpio 29的線MMC Slot透過機械裝置來觸發這根pin,然後讓軟體控制是否是唯讀~

static struct twl4030_hsmmc_info mmc[] = {

{

.mmc = 1,

.wires = 8,

.gpio_wp = 29,

},

{

.mmc = 2,

.wires = 8,

.gpio_wp = 29,

},

{} /* Terminator */

};

ARM的CPU可以把一根pin做為多用途,所以我們必須先設定那根腳位要做甚麼用的,設的方式就是參考OMAP 353x的SPEC。

接下來就是要決定某根pin的功能是甚麼了。在OMAP35x Application Processor SPEC裡面有一個大表格,根據前人的教學,必須修改三個地方。

arch/arm/mach-omap2/mux.c 加上

MUX_CFG_34XX("AE2_34XX_MMC2_CLK", 0x158,

OMAP34XX_MUX_MODE0 | OMAP34XX_PIN_OUTPUT)

MUX_CFG_34XX("AG5_34XX_MMC2_CMD", 0x15a,

OMAP34XX_MUX_MODE0 | OMAP34XX_PIN_INPUT_PULLDOWN)

MUX_CFG_34XX("AH5_34XX_MMC2_DAT0", 0x15c,

OMAP34XX_MUX_MODE0 | OMAP34XX_PIN_INPUT_PULLDOWN)

MUX_CFG_34XX("AH4_34XX_MMC2_DAT1", 0x15e,

OMAP34XX_MUX_MODE0 | OMAP34XX_PIN_INPUT_PULLDOWN)

MUX_CFG_34XX("AG4_34XX_MMC2_DAT2", 0x160,

OMAP34XX_MUX_MODE0 | OMAP34XX_PIN_INPUT_PULLDOWN)

MUX_CFG_34XX("AF4_34XX_MMC2_DAT3", 0x162,

OMAP34XX_MUX_MODE0 | OMAP34XX_PIN_INPUT_PULLDOWN)

MUX_CFG_34XX("AE4_34XX_MMC2_DIR_DAT0", 0x164,

OMAP34XX_MUX_MODE1 | OMAP34XX_PIN_OUTPUT)

MUX_CFG_34XX("AH3_34XX_MMC2_DIR_DAT1", 0x166,

OMAP34XX_MUX_MODE1 | OMAP34XX_PIN_OUTPUT)

MUX_CFG_34XX("AF3_34XX_MMC2_DIR_CMD", 0x168,

OMAP34XX_MUX_MODE1 | OMAP34XX_PIN_OUTPUT)

MUX_CFG_34XX("AE3_34XX_MMC2_CLKIN", 0x16a,

OMAP34XX_MUX_MODE1 | OMAP34XX_PIN_INPUT_PULLUP)

arch/arm/plat-omap/include/mach/mux.h

AE2_34XX_MMC2_CLK,

AG5_34XX_MMC2_CMD,

AH5_34XX_MMC2_DAT0,

AH4_34XX_MMC2_DAT1,

AG4_34XX_MMC2_DAT2,

AF4_34XX_MMC2_DAT3,

AE4_34XX_MMC2_DIR_DAT0,

AH3_34XX_MMC2_DIR_DAT1,

AF3_34XX_MMC2_DIR_CMD,

AE3_34XX_MMC2_CLKIN,

vim arch/arm/mach-omap2/board-omap3beagle.c

omap_cfg_reg(AE2_34XX_MMC2_CLK);

omap_cfg_reg(AG5_34XX_MMC2_CMD);

omap_cfg_reg(AH5_34XX_MMC2_DAT0);

omap_cfg_reg(AH4_34XX_MMC2_DAT1);

omap_cfg_reg(AG4_34XX_MMC2_DAT2);

omap_cfg_reg(AF4_34XX_MMC2_DAT3);

omap_cfg_reg(AE4_34XX_MMC2_DIR_DAT0);

omap_cfg_reg(AH3_34XX_MMC2_DIR_DAT1);

omap_cfg_reg(AF3_34XX_MMC2_DIR_CMD);

omap_cfg_reg(AE3_34XX_MMC2_CLKIN);

Menuconfig 要改的地方:

Enable loadable module support->Forced module loading

System Type->OMAP multiplexing support

最後,如果要驗證到底有沒有修改成功,可以修改arch/arm/mach-omap2/mux.c裡面的omap2_cfg_debug(const struct pin_config *cfg, u16 reg) 裡面加上 debug =1; 這樣在開機之後就可以使用dmesg 來檢查 Kernel 有沒有調整pin了~

你可以在hsmmc裡面的Start_Command裡面使用printk,就會看到看機的時候會送出命令了

Reference:

Include/linux/mmc.h 裡面會定義 mmc CMD

SDIO + Libertas

這個部分還是建議先看一下SDIO的SDIO Simplified Specification Version 2.00.PDF,看完之後再看Code會比較快。

我的問題是 Linux Kernel預設沒有啟用SDIO IRQ,所以我要把它啟用起來。可能有人會有疑問,IRQ不是之前就已經啟用了嗎?其實這裡有兩個IRQ,第一個IRQ指的是MMC Controller通知CPU說他做完了。第二個IRQ是卡片通知Controller的IRQ。據我所知,USB Device是沒有IRQ的,所以Controller就必須一直去問各個Device的狀態,所以他的速度可以那麼快,我還覺得滿神奇的~

至於卡片怎麼通知Controller,SPEC有提到,是使用DAT[1]/INT 這根pin,可以看到有兩個功能共用一個pin,所以解決辦法就是使用timing。這個Timing在SPEC裡面就沒有提到了,可能是做硬體的人才需要知道這個timing吧~

SDIO有規定,卡片上面必須有兩個暫存器,一個是IENx(register = 0x04 SDIO SPEC定義) 另一個是SDIO_CCCR_INTX (register = 0x05 SDIO SPEC定義),第一個是定意卡片上如果有中斷的時候要不要通知Controller,第二個則是一個Status Register,上面記錄了哪一個func有中斷。如果Controller沒有使用SDIO IRQ的話,那麼就是一直去看 INTX,來判斷說是不是有資料了。

甚麼時候會設定這兩個暫存器,當上層的 libertas 呼叫的時候。

Libertas IRQ的設定:在Probe函式裡面會使用 sdio_claim_irq 把 callback函式到 drivers/mmc/core/sdio_irq.c 裡的sdio_claim_irq,會把callback存在 irq_handler,給 sdio_irq裡面的 process_sdio_pending_irqs 呼叫用,

在sdio_claim_irq 一開始會先做一件事情,就是把卡片的某一個function的Interrupt啟用起來,使用IENx(register = 0x04 SDIO SPEC定義) set對應的bit。

process_sdio_pending_irqs 會使用SDIO_CCCR_INTX (register = 0x05 SDIO SPEC定義)來檢查是否有資料要進來,如果有就callback。這裡很奇怪的是driver這裡要怎麼清除Card上面的 INTX,看SDIO SPEC這個register是 Readonly的,但是在libertas的if_sdio_interrupt裡面則是直接把INTX 的直接讀出來,然後做NOT寫回去。何時該清除會定義一個Interrupt Clear Timing,不然就會重複觸發ISR

the interrupt line shall be held active (low) until it is either recognized and acted upon by the host or de-asserted due to the end of the Interrupt Period (see 8.1.2). an interrupt shall only be sent by the card and recognized by the host during a specific time. The time that a low on Pin 8 shall be recognized as an interrupt is defined as the Interrupt Period.

An SDIO host shall only sample the level on Pin 8 (DAT[1]/IRQ) into the interrupt detector during the Interrupt Period.

所以一旦有Libertas的Driver掛上去的時候,卡片的IRQ就被啟用了。

但卡片上的IRQ要能正確傳到hsmmc的ISR來還有一到順序,就是啟用Controller的SDIO Interrupt Driven。這裡參考SPEC為 ISE和IE兩個Register。我們必須在hsmmc.c裡面實作 enable_sdio_irq這個function,就是設定ISE 跟 IE

正確的流程,libertas呼叫kernel提供的claim_irq,此時kernel會送出SDIO 標準的指令跟卡片說要啟用Interrupt模式(寫入卡片的reg),Core是從SDIO定義的Register ( Int Enable0x04、Int Pending0x05)來決定是否有資料要接收的。但如果host本身沒有啟用interrupt的功能,就變成要Polling去檢查 Int Pending這個暫存器是否某個func有資料。

而在sdio_irq_thread裡面會判斷 SDIO 的屬性 MMC_CAP_SDIO_IRQ來決定是否是要用 polling的方式,還是呼叫 enable_sdio_irq。

drivers/mmc/core/sdio_irq.c 裡的sdio_claim_irq在儲存完callback之後會呼叫sdio_card_irq_get 啟用 kthread sdio_irq_thread。

drivers/mmc/core/sdio_irq.c的sdio_irq_thread 這個函式主要是決定要採用 polling 還是 interrupt driven的地方,程式裡面有提到為了讓不支援SDIO的host也可以支援SDIO卡,所以要用polling的方式,他無法使用非同步呼叫。此函式是一個無窮迴圈,如果是polling的話,會等待10 ms 以內(時間決定有一點演算法,如果這次有資料就時間減半,否則就+1 ms)就問一下看有沒有中斷 ( 透過 CMD 52 IO_RW_DIRECT arg=CCCR_INTx )。如果是interrupt的話,就會呼叫schedule(),將CPU釋放出去,等到有卡片的IRQ來的時候在醒過來去檢查卡片上的INTX 決定是哪一個function所產生的中斷。

ISR被觸發之後,檢查host的STAT的CIRQ bit,如果是的話,先把host的interrupt關掉,再去讀取卡片的INTx reg看是哪一個func要求的中斷,這個func就會呼叫一開始libertas所傳進來的if_sdio_interrupt了。而在libertas裡面會再把卡片上面的中斷清除掉。

懷疑是硬體的問題,

一旦kernel決定要使用SDIO IRQ的方式的話,會設定ISE 跟 IE,但現在問題是一旦設了ISE 跟 IE之後,我的ISR就會一直被觸發,可是讀卡片上面的INTx又會讀不到東西。收到的STAT為 CIRQ[8] & CC[0] bit,CC (Command Complete) 是因為收到 CIRQ,所以送出 CMD 52去讀取CCCR_INTx,就會導致收到CC的STAT。但不知道為什麼會一直收到CIRQ。使用示波器去量 MMC DAT[1] pin並沒有看到中斷發生的訊號,Level sensitive = low

Libertas的流程

If_sdio.c: 註冊 driver函式: 填好sdio_driver,裡面會有id_table (VenderId, ProductId), Probe (if_sdio_probe), Remove(if_sdio_remove)

if_sdio_probe裡面把會把卡片的model傳進來,接著比對內部宣告的model,8686為0x0B,比好之後會建立一個新的if_sdio_card的物件,然後把全域變數的firmware檔名填進去。接下來從scratch檢查是否已經成功載入firmware,再來就是呼叫 main.c 裡面的lbs_add_card(配置一些系統資源,ex: 指定傳送的函式是 lbs_hard_start_xmit),再來就是lbs_start_card,裡面呼叫了lbs_setup_firmware更新了 firmware的資訊到private,順便印出firmware version(使用 CMD_GET_HW_SPEC)和啟用了802.11d跟update_channel。

送出封包的時候系統會呼叫 tx.c 裡面的 lbs_hard_start_xmit,裡面會把上面傳下來的 skb 填到 priv->tx_pending_buf (size 只有 2312 ),然後設定 priv->tx_pending_len。這樣 main thread 就可以根據 priv->tx_pending_len來判斷是否有封包要送。

Main thread 裡面準備要送封包的時候,會把 tx_pending_buf存到 priv->card->packet 如果已經存在就存到priv->card->packet->next 去,在 host_to_card_worker的時候會把整個 queue 都丟出去。

卡住的地方是main thread 會檢查 priv->dnld_sent,如果還沒送的話就會執行 schedule(); 把CPU交出去,這時就會耗 8 msec左右,這個變數會在 host_to_card_done 被修改。但問題是如果不呼叫 schedule(); ,那麼 host_to_card_done 好像就不會被執行,導致 dnld_sent永遠都是傳送中。

main.c: lbs_add_card會配置一個kthread,跑lbs_thread,此函式會處理從firmware從過來的RX,與kernel丟下來的TX。

處理順序:

1. CMD_RESPONSE

2. 檢查之前的CMD有沒有TIMEOUT或是重傳的

3. hardware events (card removed, link lost)

4. execute next command

5. transmit data

TX的時候使用DAT的方式送出

int ret = priv->hw_host_to_card(priv, MVMS_DAT, priv->tx_pending_buf, priv->tx_pending_len);

hw_host_to_card會根據不同的interface指到不同的函式, ex: if_cf_host_to_card, if_sdio_host_to_card

送CMD,則是在lbs_thread裡呼叫 lbs_execute_next_cmd->lbs_submit_cmd->hw_host_to_card (指到 if_sdio_host_to_card) ->會使用schedule_work(&card->packet_worker); 交給if_sdio_host_to_card_worker

在if_sdio.c裡面會使用 sdio_claim_irq 傳進一個 if_sdio_interrupt的函式指標,當底層的controller發現有資料要讀取的時候,會callback此函式,裡面就是先讀一個中斷的狀態,然後看是 Download還是Upload,分別呼叫 lbs_host_to_card_done和if_sdio_card_to_host。

底層MMC Controller不一定是使用 IRQ的方式,在host不支援 SDIO時候,使用Polling 可以讓SDIO卡片運作,而BeagleBoard就是使用Polling的方式。

Priv->dnld_sent 會在 lbs_ost_to_card_done被設為0,在main thread裡面會檢查這個flag是否已經改為0,否則就會進行 schedule(); 拖慢速度。

printk 大概要多花 0.01 sec

This entry was posted in 工作. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s