天天看點

Linux SDIO總線驅動SDIO卡

SDIO卡

       SDIO卡是在SD記憶體卡接口的基礎上發展起來的接口,SDIO接口相容以前的SD記憶體卡,并且可以連接配接SDIO接口的裝置,目前根據SDIO協定的SPEC,SDIO接口支援的裝置總類有藍牙,網卡,電視卡等。

       SDIO協定是由SD卡的協定演化更新而來的,很多地方保留了SD卡的讀寫協定,同時SDIO協定又在SD卡協定之上添加了CMD52和CMD53指令。由于這個,SDIO和SD卡規範間的一個重要差別是增加了低速标準,低速卡的目标應用是以最小的硬體開始來支援低速I/O能力。低速卡支援類似數據機,條形碼掃描器和GPS接收器等應用。高速卡支援網卡,電視卡還有“組合”卡等,組合卡指的是存儲器+SDIO。

       SDIO和SD卡的SPEC間的又一個重要差別是增加了低速标準。SDIO卡隻需要SPI和1位SD傳輸模式。低速卡的目标應用是以最小的硬體開支來支援低速I/O能力,低速卡支援類似MODEM,條形掃描器和GPS接收器等應用。對組合卡來說,全速和4BIT操作對卡記憶體儲器和SDIO部分都是強制要求的。

       在非組合卡的SDIO裝置裡,其最高速度要隻有達到25M,而組合卡的最高速度同SD卡的最高速度一樣,要高于25M。

SDIO總線

       SDIO總線和USB總線類似,SDIO總線也有兩端,其中一端是主機(HOST)端,另一端是裝置端(DEVICE),采用HOST- DEVICE這樣的設計是為了簡化DEVICE的設計,所有的通信都是由HOST端發出指令開始的。在DEVICE端隻要能解溪HOST的指令,就可以同HOST進行通信了。

       SDIO的HOST可以連接配接多個DEVICE,如下圖所示:

       這個是同SD的總線一樣的,其中有如下的幾種信号

1.       CLK信号:HOST給DEVICE的時鐘信号.

2.       CMD信号:雙向的信号,用于傳送指令和反應。

3.       DAT0-DAT3 信号:四條用于傳送的資料線。

4.       VDD信号:電源信号。

5.       VSS1,VSS2:電源地信号。

在SDIO總線定義中,DAT1信号線複用為中斷線。在SDIO的1BIT模式下DAT0用來傳輸資料,DAT1用作中斷線。在SDIO的4BIT模式下DAT0-DAT3用來傳輸資料,其中DAT1複用作中斷線。

SDIO指令:

       SDIO總線上都是HOST端發起請求,然後DEVICE端回應請求。其中請求和回應中會資料資訊。

1.       Command:用于開始傳輸的指令,是由HOST端發往DEVICE端的。其中指令是通過CMD信号線傳送的。

2.       Response:回應是DEVICE傳回的HOST的指令,作為Command的回應。也是通過

CMD線傳送的。

3.       Data:資料是雙向的傳送的。可以設定為1線模式,也可以設定為4線模式。資料是通過DAT0-DAT3信号線傳輸的。

SDIO的每次操作都是由HOST在CMD線上發起一個CMD,對于有的CMD,DEVICE需要傳回Response,有的則不需要。

       對于讀指令,首先HOST會向DEVICE發送指令,緊接着DEVICE會傳回一個握手信号,此時,當HOST收到回應的握手信号後,會将資料放在4位的資料線上,在傳送資料的同時會跟随着CRC校驗碼。當整個讀傳送完畢後,HOST會再次發送一個指令,通知DEVICE操作完畢,DEVICE同時會傳回一個響應。

       對于寫指令,首先HOST會向DEVICE發送指令,緊接着DEVICE會傳回一個握手信号,此時,當HOST收到回應的握手信号後,會将資料放在4位的資料線上,在傳送資料的同時會跟随着CRC校驗碼。當整個寫傳送完畢後,HOST會再次發送一個指令,通知DEVICE操作完畢,DEVICE同時會傳回一個響應。

SDIO的寄存器:

      SDIO卡的裝置驅動80%的任務就是操作SDIO卡上的有關寄存器。SDIO卡最多允許有7個功能(function),這個同其功能号是對應的(0~7),每個功能都對應一個128K位元組大小的寄存器,這個見下面的圖。功能号之是以取值範圍是1~7,而沒有包含0,是因為功能0并不代表真正的功能,而代表CIA寄存器,即Common I/O Area,這個紀錄着SDIO卡的一些基本資訊和特性,并且可以改寫這些寄存器。其中位址0x1000~0x17fff是SDIO卡的CIS區域,就是基本資訊區域,Common Information Structure。初始化的時候讀取并配對SDIO裝置。

       這些寄存器的詳細分區已經其對應的功能,在開發過程中都是需要仔細研讀的,這些都在協定的SPEC中都有詳細說明,這裡就不在羅索了。 

CMD52指令:

SDIO裝置為了和SD記憶體卡相容,SD卡所有Command和Response完全相容,同時加入了一些新的Command和Response。例如,初始化SD記憶體卡使用ACMD41,而SDIO卡裝置則用CMD5通知DEVICE進行初始化。

但二者最重要的差別是,SDIO卡比SD記憶體卡多了CMD52和CMD53指令,這兩個指令可以友善的通路某個功能的某個位址寄存器。

CMD52指令是IO_RW_DIRECT指令的簡稱,其指令格式如下

首先第一位為0,表明是起始位,第二位為傳輸方向,這裡為1,代表方向為HOST向DEVICE裝置傳送,其後6位為指令号,這裡是110100b,用十進制表示為52,CMD52的名字也由此而來。緊接着是讀寫标志位。

      然後是操作的功能号。也就是functionnumber。如果為0則訓示為CCCR寄存器組。

       緊接着是寄存器位址,用17訓示,由于功能寄存器有128K位址,17位正好能尋址。

       再下來8位Write data or Staff Bits的意思是說,如果目前為寫操作,則為資料,否則8位為填充位。無意義。

       最後7位為CRC校驗碼。最後一位為結束位0。

       對于CMD52的Response是48位,指令格式如下:

       總結下,CMD52是由HOST發往DEVICE的,它必須有DEVICE傳回來的Response。 CMD52不需要占用DAT線,讀寫的資料是通過CMD52或者Response來傳送。每次CMD52隻能讀或者寫一個byte.

CMD53指令:

CMD52每次隻能讀寫一個位元組,因為有了CMD53對讀寫進行了擴充,CMD53允許每次讀寫多個位元組或者多個塊(BLOCK)。CMD53的指令格式如下:

       第一位是1,為開始位,然後是一位方向位,總是1,代表方向為HOST向DEVICE裝置傳送,其後6位為指令号,這裡是110101b,用十進制表示為53,CMD53的名字也由此而來。

       然後是1位的讀寫标志。接着是3位功能号,這個同CMD52都是相同的。BlockMode如果1代表是塊傳輸模式,否則為位元組傳輸模式。

       OP Code為操作位,如果是0,代表資料往固定的位置讀寫,如果1代表是地質增量讀寫。例如,對位址0固定讀寫16個位元組,相當于16次讀寫的位址0,而對位址0增量讀寫16個位元組,相當于讀寫0~15位址的資料。

       然後是17位的位址寄存器,可以尋址到128K位元組的位址,然後是9位的讀寫的計數,對于位元組讀取,讀寫大小就是這個計數,而對于塊讀寫,讀寫的大小是計數乘以塊的大小。

       随後的7位為CRC校驗碼。最後一位為1。

       當讀寫操作是塊操作的時候,塊的大小是可以通過設定FBR中的相關寄存器來設定。

       同CMD52指令不同的是,CMD53沒有傳回的指令的,這裡判斷是否DEVICE裝置讀寫完畢是需要驅動裡面自己判斷的,一般有2個方法,1.設定相應的讀寫完畢中斷。如果DEVICE裝置讀寫完畢,則對HOST裝置發送中斷。2.HOST裝置主動查詢DEVICE裝置是否讀寫完畢,可以通過CMD指令是否有傳回來判斷是否DEVICE是否讀寫完畢。

驅動:

以SDIO為例其會采用mmc_attach_sdio來實作驅動和裝置的比對,其本質還是根據sdio_bus的比對規則來實作比對。在mmc_attach_sdio中首先是mmc比對一個bus,即采用何種bus來進行mmc bus來處理host。在這裡需要了解一點就是在SDIO中,對于SD卡存儲器mmc為實體裝置,而對于非SD卡存儲器,如SDIO接口的裝置,則mmc則表征為bus,這個比較重要。除了mmc bus外還存在SDIO_BUS。

int mmc_attach_sdio(struct mmc_host *host)

{

int err, i, funcs;

u32 ocr;

struct mmc_card *card;

BUG_ON(!host);

WARN_ON(!host->claimed);

err = mmc_send_io_op_cond(host, 0, &ocr);

if (err)

return err;

mmc_attach_bus(host, &mmc_sdio_ops);

if (host->ocr_avail_sdio)

host->ocr_avail = host->ocr_avail_sdio;

if (ocr & 0x7F) {

pr_warning("%s: card claims to support voltages "

      "below the defined range. These will be ignored.\n",

      mmc_hostname(host));

ocr &= ~0x7F;

}

host->ocr = mmc_select_voltage(host, ocr);

if (!host->ocr) {

err = -EINVAL;

goto err;

}

err = mmc_sdio_init_card(host, host->ocr, NULL, 0);

if (err) {

if (err == -EAGAIN) {

host->ocr &= ~R4_18V_PRESENT;

err = mmc_sdio_init_card(host, host->ocr, NULL, 0);

}

if (err)

goto err;

}

card = host->card;

if (host->caps & MMC_CAP_POWER_OFF_CARD) {

err = pm_runtime_set_active(&card->dev);

if (err)

goto remove;

pm_runtime_enable(&card->dev);

}

funcs = (ocr & 0x70000000) >> 28;

card->sdio_funcs = 0;

#ifdef CONFIG_MMC_EMBEDDED_SDIO

if (host->embedded_sdio_data.funcs)

card->sdio_funcs = funcs = host->embedded_sdio_data.num_funcs;

#endif

for (i = 0; i < funcs; i++, card->sdio_funcs++) {

#ifdef CONFIG_MMC_EMBEDDED_SDIO

if (host->embedded_sdio_data.funcs) {

struct sdio_func *tmp;

tmp = sdio_alloc_func(host->card);

if (IS_ERR(tmp))

goto remove;

tmp->num = (i + 1);

card->sdio_func[i] = tmp;

tmp->class = host->embedded_sdio_data.funcs[i].f_class;

tmp->max_blksize = host->embedded_sdio_data.funcs[i].f_maxblksize;

tmp->vendor = card->cis.vendor;

tmp->device = card->cis.device;

} else {

#endif

err = sdio_init_func(host->card, i + 1);

if (err)

goto remove;

#ifdef CONFIG_MMC_EMBEDDED_SDIO

}

#endif

if (host->caps & MMC_CAP_POWER_OFF_CARD)

pm_runtime_enable(&card->sdio_func[i]->dev);

}

mmc_release_host(host);

err = mmc_add_card(host->card);

if (err)

goto remove_added;

for (i = 0;i < funcs;i++) {

err = sdio_add_func(host->card->sdio_func[i]);

if (err)

goto remove_added;

}

mmc_claim_host(host);

return 0;

remove_added:

mmc_sdio_remove(host);

mmc_claim_host(host);

remove:

mmc_release_host(host);

if (host->card)

mmc_sdio_remove(host);

mmc_claim_host(host);

err:

mmc_detach_bus(host);

pr_err("%s: error %d whilst initialising SDIO card\n",

mmc_hostname(host), err);

return err;

}

比較難以了解的是func,這個東東其實是一個實體裝置的封裝,可以認為其是一個裝置。

struct sdio_func *sdio_alloc_func(struct mmc_card *card)

{

struct sdio_func *func;

func = kzalloc(sizeof(struct sdio_func), GFP_KERNEL);

if (!func)

return ERR_PTR(-ENOMEM);

func->card = card;

device_initialize(&func->dev);

func->dev.parent = &card->dev;//card裝置為sdio裝置的父裝置。

func->dev.bus = &sdio_bus_type;

func->dev.release = sdio_release_func;

return func;

}

上面的code一目了然,其就是具體裝置實體的封裝,其bus類型為sdio_bus. sdio_init_func僅僅是初始化一個裝置,而并沒有register。在sdio_add_func實作裝置的register,同理就是card實體,在mmc_add_card之前并沒有注冊,在mmc_add_card函數中才實作裝置的注冊。

到此裝置注冊也就完成了,其實sdio總線在形式上類似于usb bus,為什麼呢?編寫過usb驅動的童鞋們應該知道,編寫usb驅動僅僅是編寫驅動的加載,并沒有具體加載裝置實體,導緻很多童鞋的困惑,為什麼沒有裝置的加載,其實在usb裝置插入時,會動态的建立一個usb裝置實體,在usb裝置實體建立完成後,根據不同裝置id調用相比對的驅動。而SDIO裝置裝置也是一樣的。上面的code比較混亂,總是讓人看不出具體的裝置的加載。其實在上面的code中,其中包括了mmc host的驅動。