天天看點

S3C2440上 MMC/SD卡驅動執行個體開發講解 .

嵌入式Linux之我行,主要講述和總結了本人在學習嵌入式linux中的每個步驟。一為總結經驗,二希望能 給想入門嵌入式Linux的朋友提供友善。如有錯誤之處,謝請指正。

一、開發環境

  • 主  機:VMWare--Fedora 9
  • 開發闆:Mini2440--64MB Nand, Kernel:2.6.30.4
  • 編譯器:arm-linux-gcc-4.3.2

二、MMC/SD介紹及SDI主機控制器

  首先我們來理清幾個概念:

  1. MMC:(Multi Media Card)由西門子公司和首推CF的SanDisk于1997年推出的多媒體記憶卡标準。
  2. SD:(Secure Digital Memory Card)由日本松下、東芝及美國SanDisk公司于1999年8月共同開發研制的新一代記憶卡标準,已完全相容MMC标準。
  3. SDIO:(Secure Digital Input and Output Card)安全數字輸入輸出卡。SDIO是在SD标 準上定義了一種外設接口,通過SD的I/O接腳來連接配接外圍裝置,并且通過SD上的 I/O資料接位與這些外圍裝置進行資料傳輸。是目前較熱門的技術,如下圖中的一些裝置:GPS、相機、Wi-Fi、調頻廣播、條形碼讀卡器、藍牙等。
    S3C2440上 MMC/SD卡驅動執行個體開發講解 .
  4. 工作模式:工作模式是針對主機控制器來說的。也就是說,S3C2440中的SDI 控制器可以在符合MMC的标準下工作,或者可以在符合SD的标準下工作,或者可以在符合SDIO的标準下工作。故就分别簡稱為:MMC模式、SD模式和 SDIO模式。
  5. 傳輸模式:傳輸模式也是針對主機控制器來說的,指控制器與卡之間資料的傳輸模式, 或者說是總線類型。S3C2440中的SDI控制器可支援SPI、1位和4位的三種傳輸模式(總線類型)。那麼什麼又是SPI呢?請參考這裡:SPI協定簡介;至于1位和4位又是什麼意思呢?他們是指傳輸資料總線的線寬,具體參考資料手冊。

  下面使用表格列出了MMC、SD、SDIO的電氣特性及性能和不同工作模式下支援的傳輸模式情況:

S3C2440上 MMC/SD卡驅動執行個體開發講解 .

  那麼,我們現在怎樣讓主機控制器在我們所要求的工作模式和傳輸模式上工作呢?很簡單,就是對主機控制器的各個寄存器進行相應的配置 即可。下面來簡單介紹一下SDI主機控制器的結構和各寄存器的用途。

S3C2440内的SDI主機控制器結構圖如下:

S3C2440上 MMC/SD卡驅動執行個體開發講解 .

  如上圖所示,SDI主機控制器是使用1個串行時鐘線與5條資料線同步進行資訊移位和采樣。傳輸頻率通過設定SDIPRE寄存器的相應位的設定來 控制,可以修改頻率來調節波特率資料寄存器的值。

各主要寄存器介紹,對于具體的寄存器位的設定就參考資料手冊:

  1. SDICON:控制寄存器,完成SD卡基礎配置,包括大小端,中斷允許,模式選擇,時鐘使能 等。
  2. SDIPRE:波特率預定标器寄存器,對SDCLK的配置。
  3. SDICmdArg:指令參數寄存器,指令的參數存放在這裡。
  4. SDICCON:控制指令形式的寄存器,配置SPI還是SDI指令,指令的回報長 度,是否等待回報,是否運作指令,指令的索引等。
  5. SDICmdSta:指令狀态寄存器,指令是否逾時,傳送,結束,CRC是否正确 等。
  6. SDIRSP0-3:反映SD的狀态。
  7. SDIDTimer:設定逾時時間。
  8. SDIBSize:子產品大小寄存器。
  9. SDIDatCon:資料控制寄存器,配置是幾線傳輸,資料發送方向,資料傳送方 式等。
  10. SDIDatSta:資料狀态寄存器,資料是否發送完,CRC效驗,逾時等。
  11. SDIFSTA:FIFO狀态寄存器,DMA傳輸是否判斷FIFO。
  12. SDIIntMsk:中斷屏蔽寄存器。
  13. SDIDAT:SDI資料寄存器。

SDI主機控制器在SD/MMC工作模式下的設定步驟:(注意:因為SD模式相容MMC模式,是以我們隻需了解SD模式的即可,而SDIO的工作模 式則是針對SDIO裝置的,是以這裡就不讨論了)

  1. 設定SDICON寄存器來配置适當的時鐘及中斷使能;
  2. 設定SDIPRE寄存器适當的值;
  3. 等待74個SDCLK時鐘以初始化卡;
  4. 指令操作步驟:

    a. 寫指令參數32位到SDICmdArg寄存器;

    b. 設定指令類型并通過設定SDICCON寄存器開始指令傳輸;

    c. 當SDICSTA寄存器的特殊标志被置位,确認指令操作完成;

    d. 如果指令類型相應,标志是RspFin,否則标志是CmdSend;

    e. 通過對相應位寫1,清除SDICmdSta的标志。

  5. 資料操作步驟:

    a. 寫資料逾時時間到SDIDTimer寄存器;

    b. 寫子產品大小到SDIBSize寄存器(通常是0x80位元組);

    c. 确定子產品模式、總線線寬、DMA等且通過設定SDIDatCon寄存器開始資料傳輸;

    d. 發送資料->寫資料到SDIDAT寄存器,當發送FIFO有效(TFDET置位),或一半(TFHalf置位),或空(TFEmpty置位);

    e. 接收資料->從資料寄存器SDIDAT讀資料,當接收FIFO有效(RFDET置位),或滿(RFFull置位),或一半(RFHalf置位),或 準備最後資料(RFLast置位);

    f. 當SDIDatSta寄存器的DatFin标志置位,确認資料操作完成;

    g. 通過對相應位寫1,清除SDIDatSta的标志。

三、MMC/SD協定

  這裡我并不是要讨論MMC/SD的整個通信協定(詳細的協定請看MMC/SD規範),而是遵循MMC/SD協定了解一下MMC/SD在被驅動的 過程中卡所處的各種階段和狀态。根據協定,MMC/SD卡的驅動被分為:卡識别階段和資料傳輸階段。在卡識别階段通過指令使MMC/SD處于:空閑 (idle)、準備(ready)、識别(ident)、等待(stby)、不活動(ina)幾種不同的狀态;而在資料傳輸階段通過指令使MMC/SD處 于:發送(data)、傳輸(tran)、接收(rcv)、程式(prg)、斷開連接配接(dis)幾種不同的狀态。是以可以總結MMC/SD在工作的整個過 程中分為兩個階段和十種狀态。下面使用圖形來描述一下在兩個階段中這十種狀态之間的轉換關系。

卡識别階段,如下圖:

S3C2440上 MMC/SD卡驅動執行個體開發講解 .

資料傳輸階段,如下圖:

S3C2440上 MMC/SD卡驅動執行個體開發講解 .

四、MMC/SD裝置驅動在Linux中的結構層次

    我們翻開MMC/SD裝置驅動代碼在Linux源碼中的位置 /linux-2.6.30.4/drivers/mmc/,乍一看,分别有card、core和host三個檔案夾,那哪一個檔案才是我們要找的驅動代 碼檔案啊?答案是他們都是。不是吧,聽起來有些可怕,三個檔案夾下有多少代碼啊。呵呵,還是先放下對龐大而又神秘代碼的恐懼感吧,因為在實際驅動開發中, 其實隻需要在host檔案夾下實作你具體的MMC/SD裝置驅動部分代碼,現在你的心情是不是要好點了。具體的MMC/SD裝置是什麼意思呢?他包括 RAM晶片中的SDI控制器(支援對MMC/SD卡的控制,俗稱MMC/SD主機控制器)和SDI控制器與MMC/SD卡的硬體接口電路。

    那為什麼剛才又說card、core和host都是MMC/SD裝置的驅動呢?這就好比我們建房子,建房子首先要的是什麼,地皮對吧, 有了地皮然後要到政府部門備案,備案後才能開始建,是這樣的吧。在Linux中MMC/SD卡的記憶體都當作塊裝置。那麼,我們這裡的card層就是要把 操作的資料以塊裝置的處理方式寫到記憶體上或從記憶體上讀取,就好比是在地皮上填沙石、挖地基等;core層則是将資料以何種格式,何種方式在 MMC/SD主機控制器與MMC/SD卡的記憶體(即塊裝置)之間進行傳遞,這種格式、方式被稱之為規範或協定,就好比到政府部門備案,備案就會要求你的 房子應該按照怎樣的行業标準進行建造;最後隻剩下host層了,上面也講到了,host層下的代碼就是你要動手實作的具體MMC/SD裝置驅動了,就好比 現在地皮買好挖好了,建房的标準也定好了,剩下的就需要人開始動工了。       那麼, card、core和host這三層的關系,我們用一幅圖來進行描述,圖如下:

S3C2440上 MMC/SD卡驅動執行個體開發講解 .

    從這幅圖中的關系可以看出,整個MMC/SD子產品中最重要的部分是Core核心層,他提供了一系列的接口函數,對上提供了将主機驅動注冊到系統,給應用程 序提供裝置通路接口,對下提供了對主機控制器控制的方法及塊裝置請求的支援。對于主機控制器的操作就是對相關寄存器進行讀寫,而對于MMC/SD裝置的請 求處理則比較複雜。那麼在主機驅動層中的一個請求處理是怎麼通過核心層送出到塊裝置請求層的呢?   在網上找到一副圖來說明他們之間的關聯和處理流程,如下圖:

S3C2440上 MMC/SD卡驅動執行個體開發講解 .

  指令、資料發送流程如下圖:  

S3C2440上 MMC/SD卡驅動執行個體開發講解 .

  其中,黑色粗線部分為指令發送或者資料發送都要經過的流程,橙色方框部分判斷所有類型的請 求是否完成。   下面我們就來具體執行個體分析一個MMC/SD卡裝置驅動程式。 五、執行個體分析MMC/SD卡裝置驅動程式

  1. Mini2440開發闆的MMC/SD硬體接口電路原路圖如下:
    S3C2440上 MMC/SD卡驅動執行個體開發講解 .
    從電路原理圖上可以看出,SD分别使用S3C2440的複用IO端口GPE7-10作為4根資料信号線、使用 GPE6作指令信号線、使用GPE5作時鐘信号線,使用複用端口GPG8的外部中斷功能來作SD卡的插拔檢測,使用GPH8端口來判斷SD卡是否寫有保 護。
  2. MMC/SD卡驅動程式的重要資料結構,該結果位于Core核心層,主要用于核心 層與主機驅動層的資料交換處理。定義在/include/linux/mmc/host.h中:

    struct mmc_host  {     struct device *parent;     struct device class_dev;     int           index;     const struct  mmc_host_ops *ops;     unsigned int  f_min;     unsigned int  f_max;     u32           ocr_avail; #define MMC_VDD_165_195 0x00000080     #define MMC_VDD_20_21   0x00000100     #define MMC_VDD_21_22   0x00000200     #define MMC_VDD_22_23   0x00000400     #define MMC_VDD_23_24   0x00000800     #define MMC_VDD_24_25   0x00001000     #define MMC_VDD_25_26   0x00002000     #define MMC_VDD_26_27   0x00004000     #define MMC_VDD_27_28   0x00008000     #define MMC_VDD_28_29   0x00010000     #define MMC_VDD_29_30   0x00020000     #define MMC_VDD_30_31   0x00040000     #define MMC_VDD_31_32   0x00080000     #define MMC_VDD_32_33   0x00100000     #define MMC_VDD_33_34   0x00200000     #define MMC_VDD_34_35   0x00400000     #define MMC_VDD_35_36   0x00800000         unsigned long       caps;          #define MMC_CAP_4_BIT_DATA    (1 << 0) #define MMC_CAP_MMC_HIGHSPEED (1 << 1) #define MMC_CAP_SD_HIGHSPEED  (1 << 2) #define MMC_CAP_SDIO_IRQ      (1 << 3) #define MMC_CAP_SPI           (1 << 4) #define MMC_CAP_NEEDS_POLL    (1 << 5) #define MMC_CAP_8_BIT_DATA    (1 << 6)          unsigned int    max_seg_size;        unsigned short  max_hw_segs;         unsigned short  max_phys_segs;       unsigned short  unused;     unsigned int    max_req_size;        unsigned int    max_blk_size;        unsigned int    max_blk_count;            spinlock_t      lock;       struct mmc_ios  ios;        u32             ocr;             unsigned int        use_spi_crc:1;     unsigned int        claimed:1;         unsigned int        bus_dead:1;    #ifdef CONFIG_MMC_DEBUG     unsigned int        removed:1;     #endif     struct mmc_card     *card;             wait_queue_head_t   wq;     struct delayed_work    detect;     const struct mmc_bus_ops *bus_ops;       unsigned int        bus_refs;            unsigned int        sdio_irqs;     struct task_struct  *sdio_irq_thread;     atomic_t            sdio_irq_thread_abort; #ifdef CONFIG_LEDS_TRIGGERS     struct led_trigger  *led;         #endif     struct dentry       *debugfs_root;     unsigned long       private[0] ____cacheline_aligned; };

  3. MMC/SD卡驅動程式的頭檔案中一些變量的定 義,這些變量在驅動中都會用到。先不用看這些變量将用做什麼,等驅動中用到時自然就明白了。代碼如下:

    #define S3CMCI_DMA 0 enum s3cmci_waitfor  {     COMPLETION_NONE,     COMPLETION_FINALIZE,     COMPLETION_CMDSENT,     COMPLETION_RSPFIN,     COMPLETION_XFERFINISH,     COMPLETION_XFERFINISH_RSPFIN, }; struct s3cmci_host  {     struct platform_device    *pdev;

        struct s3c24xx_mci_pdata  *pdata;     struct mmc_host           *mmc;     struct resource           *mem;     struct clk                *clk;     void __iomem              *base;     int                       irq;     int                       irq_cd;     int                       dma;     unsigned long             clk_rate;     unsigned long             clk_div;     unsigned long             real_rate;     u8                        prescaler;     unsigned                  sdiimsk;     unsigned                  sdidata;     int                       dodma;     int                       dmatogo;     struct mmc_request        *mrq;     int                       cmd_is_stop;     spinlock_t                complete_lock;     enum s3cmci_waitfor       complete_what;     int                       dma_complete;     u32                       pio_sgptr;     u32                       pio_bytes;     u32                       pio_count;     u32                       *pio_ptr; #define XFER_NONE             0 #define XFER_READ             1 #define XFER_WRITE            2     u32                       pio_active;     int                       bus_width;     char                      dbgmsg_cmd[301];     char                      dbgmsg_dat[301];     char                      *status;     unsigned int              ccnt, dcnt;     struct tasklet_struct     pio_tasklet; #ifdef CONFIG_CPU_FREQ     struct notifier_block     freq_transition; #endif };

    1. MMC/SD卡驅動程式的加載與解除安裝部分:

      在 Linux中,MMC/SD裝置是被作為平台裝置添加到系統的。可以檢視核心代碼:/arch/arm/plat-s3c24xx/devs.c中為 MMC/SD主機控制器SDI定義了平台裝置和平台裝置資源,然後在/arch/arm/mach-s3c2440/mach-smdk2440.c中的 系統初始化的時候添加到系統中。如下:

    1. // 平台裝置資源 static struct resource s3c_sdi_resource[] = {     [0] = {         .start = S3C24XX_PA_SDI,         .end = S3C24XX_PA_SDI + S3C24XX_SZ_SDI - 1,         .flags = IORESOURCE_MEM,     },     [1] = {         .start = IRQ_SDI,         .end = IRQ_SDI,         .flags = IORESOURCE_IRQ,     } };

    //定義 SDI平台裝置

    struct platform_device s3c_device_sdi = {

        .name         = "s3c2410-sdi",

        .id         = -1,

        .num_resources     = ARRAY_SIZE(s3c_sdi_resource),

        .resource     = s3c_sdi_resource,

    };

    EXPORT_SYMBOL(s3c_device_sdi);

    //添加 SDI平台裝置到平台裝置清單

    static struct platform_device *smdk2440_devices[] __initdata = {

        &s3c_device_usb,

        &s3c_device_sdi,

        &s3c_device_lcd,

        &s3c_device_wdt,

        &s3c_device_rtc,

        &s3c_device_dm9000,

        .

        .

        .

    };

    //平台 裝置添加到系統

    static void __init smdk2440_machine_init(void)

    {

        .

        .

        .

        platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));

        smdk_machine_init();

    }

    1. 所 以,MMC/SD裝置驅動程式的加載和解除安裝部分很簡單,就是注冊和登出平台裝置,代碼如下:
    1. //加載 static int __init s3cmci_init(void) {     platform_driver_register(&s3cmci_driver);     return 0; } //解除安裝 static void __exit s3cmci_exit(void) {     platform_driver_unregister(&s3cmci_driver); } //平台裝置操作結構體 static struct platform_driver s3cmci_driver = {     .driver.name    = "s3c2410-sdi",// 名稱和平台裝置定義中的對應     .driver.owner   = THIS_MODULE,     .probe          = s3cmci_probe,// 平台裝置探測接口函數     .remove         = __devexit_p(s3cmci_remove),//__devexit_p的作用以前将過     .shutdown       = s3cmci_shutdown,     .suspend        = s3cmci_suspend,     .resume         = s3cmci_resume, };

    1. 平台探測函數s3cmci_probe的講解:
    1. static int __devinit s3cmci_probe(struct platform_device *pdev) {     //該結構體定義在頭檔案中,現在執行個體 一個名為host的結構體指針為結構體中的成員指派做準備     struct s3cmci_host *host;     //執行個體一個名為mmc的結構體指針, 用于與Core核心層中的mmc_host結構體指針相關聯     struct mmc_host    *mmc;     int ret;          //初始化一個名為 complete_lock的自旋鎖以備後用,該自旋鎖的定義在s3cmci_host結構體中     spin_lock_init(&host->complete_lock);          //初始化一個名為pio_tasklet的tasklet,用于實作中斷的底半部 機制,底半部服務函數為pio_tasklet,     //将host結構體變量作為服務函數的參數。注意:這裡tasklet的變量名與 服務函數名稱同名了(這是可以的)。     tasklet_init(&host->pio_tasklet, pio_tasklet, (unsigned long) host);     //配置設定 mmc_host結構體指針的記憶體空間大小,該函數在host.c中實作,這裡要注意一點,為什麼參數     //是s3cmci_host結構體 的大小,到host.c中看,實際這裡配置設定的是mmc_host加s3cmci_host的大小。     mmc = mmc_alloc_host(sizeof(struct s3cmci_host), &pdev->dev);     if (!mmc)      {         ret = -ENOMEM;         goto probe_out;     }     //調用mmc_priv函數将 mmc_host和s3cmci_host結構體的對象關聯起來,mmc_priv定義在host.h中     host = mmc_priv(mmc);          //下面就開始初始化 s3cmci_host結構體的各成員     host->mmc     = mmc;     host->pdev    = pdev;

        host->pdata   = pdev->dev.platform_data;          //SDI主機控制器的中斷屏蔽寄存 器和資料寄存器,他們定義在mach-s3c2410/include/mach/regs-sdi.h中     host->sdiimsk    = S3C2440_SDIIMSK;     host->sdidata    = S3C2440_SDIDATA;          //complete_what定義 在s3cmci_host結構體中,用來記錄請求處理所處的目前狀态,這裡初始化為     //COMPLETION_NONE即 無狀态,定義在頭檔案的s3cmci_waitfor中,裡面枚舉了6種狀态。     host->complete_what = COMPLETION_NONE;          //pio_active定義在 s3cmci_host結構體中,用來标記請求處理資料在FIFO方式下的資料方向是讀還是寫     host->pio_active     = XFER_NONE;          //dodma和dma友善用于标記 是否要使用DMA資料傳輸方式和DMA通道資源,0表示不使用DMA功能     host->dodma        = 0;     host->dma    = S3CMCI_DMA;          //從SDI平台裝置資源中擷取 SDI的IO端口資源,該資源在plat-s3c24xx/devs.c的s3c_sdi_resource中指定的     host->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);     if (!host->mem)     {         dev_err(&pdev->dev, "failed to get io memory region resouce.\n");         ret = -ENOENT;         goto probe_free_host;     }     //申請SDI的IO端口資源所占用的IO空間(要注意了解IO空間和記憶體空間的區 别)     host->mem = request_mem_region(host->mem->start, RESSIZE(host->mem), pdev->name);     if (!host->mem)      {         dev_err(&pdev->dev, "failed to request io memory region.\n");         ret = -ENOENT;         goto probe_free_host;     }     //将SDI的IO端口占用的這段IO空間映射到記憶體的虛拟位址,ioremap定 義在io.h中。   //注意:IO空間要映射後才能使用,以後對虛拟位址的操作就是對IO空間的操作。     host->base = ioremap(host->mem->start, RESSIZE(host->mem));     if (!host->base) {         dev_err(&pdev->dev, "failed to ioremap() io memory region.\n");         ret = -EINVAL;         goto probe_free_mem_region;     }     //同樣從SDI平台裝置資源中擷取SDI的中斷号     host->irq = platform_get_irq(pdev, 0);     if (host->irq == 0)      {         dev_err(&pdev->dev, "failed to get interrupt resouce.\n");         ret = -EINVAL;         goto probe_iounmap;     }     //申請SDI的中斷服務,服務函數為 s3cmci_irq,主要參數為host     if (request_irq(host->irq, s3cmci_irq, 0, DRIVER_NAME, host))      {         dev_err(&pdev->dev, "failed to request mci interrupt.\n");         ret = -ENOENT;         goto probe_iounmap;     }     //在SDI未準備好之前先屏蔽SDI的中斷功能     disable_irq(host->irq);     //根據開發闆原理圖分别設定GPG8、GPH8端口為SD卡插入拔出的檢測和有無 寫保護的檢查,

        //注意:其實有沒有寫保護就是檢查SD卡側面有個移動按鈕的開關,MMC卡無此功能

        host->pdata->gpio_detect = S3C2410_GPG8;

        host->pdata->gpio_wprotect = S3C2410_GPH8;

        // 擷取GPG8複用端口中斷功能的中斷号     host->irq_cd = s3c2410_gpio_getirq(host->pdata->gpio_detect);     //GPG8是複用端口,要使用中斷功 能則要配置成中斷功能,GPG8對應的中斷功能是外部中斷EINT16,這個資料手冊上有講到     s3c2410_gpio_cfgpin(S3C2410_GPG8, S3C2410_GPG8_EINT16);     //申請SDI的卡檢測中斷服務,服 務函數為s3cmci_irq_cd,主要參數也為host     if (request_irq(host->irq_cd, s3cmci_irq_cd, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, DRIVER_NAME, host))      {         dev_err(&pdev->dev, "can't get card detect irq.\n");         ret = -ENOENT;         goto probe_free_irq;     }     //擷取DMA通道并申請DMA中 斷,S3C2440中DMA的使用在後續的文章中再了解     if (s3c2410_dma_request(S3CMCI_DMA, &s3cmci_dma_client, NULL) < 0)      {         dev_err(&pdev->dev, "unable to get DMA channel.\n");         ret = -EBUSY;         goto probe_free_irq_cd;     }     //從平台時鐘隊列中擷取SDI的時鐘源,在arch/arm/plat- s3c24xx/s3c2410-clock.c中有定義     host->clk = clk_get(&pdev->dev, "sdi");     if (IS_ERR(host->clk))      {         dev_err(&pdev->dev, "failed to find clock source.\n");         ret = PTR_ERR(host->clk);         host->clk = NULL;         goto probe_free_host;     }     //啟動擷取的時鐘源     ret = clk_enable(host->clk);     if (ret)      {         dev_err(&pdev->dev, "failed to enable clock source.\n");         goto clk_free;     }     //通過SDI的時鐘源擷取CPU的 PCLK頻率,這裡為什麼要獲得CPU的PCLK頻率呢,     //通過資料手冊SDI控制器的方框圖得知,SDI的時鐘頻率 (SDCLK)=PCLK/(Prescaler+1)     host->clk_rate = clk_get_rate(host->clk);     host->clk_div    = 1;//設定預分頻值,即:Prescaler的值          //下面對mmc_host進行初始化     mmc->ops       = &s3cmci_ops;    //SDI主機控制器操作結構體     mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;   //設定工作電壓範圍     mmc->caps      = MMC_CAP_4_BIT_DATA;              //設定總線寬度為4位     mmc->f_min     = host->clk_rate / (host->clk_div * 256); //設定最小工作頻率     mmc->f_max     = host->clk_rate / host->clk_div;  //設定最大工作頻率     mmc->max_blk_count  = 4095;     mmc->max_blk_size   = 4095;     mmc->max_req_size   = 4095 * 512;     mmc->max_seg_size   = mmc->max_req_size;     mmc->max_phys_segs  = 128;     mmc->max_hw_segs    = 128;     //Linux的通知鍊機制,實作到後面再講     ret = s3cmci_cpufreq_register(host);     if (ret)      {         dev_err(&pdev->dev, "failed to register cpufreq\n");         goto free_dmabuf;     }     //将SDI host裝置注冊到系統中     ret = mmc_add_host(mmc);     if (ret)      {         dev_err(&pdev->dev, "failed to add mmc host.\n");         goto free_cpufreq;     }     //将SDI host裝置的資料指派給系統平台裝置     platform_set_drvdata(pdev, mmc);     return 0; //以下是錯誤處理  free_cpufreq:     s3cmci_cpufreq_deregister(host);  free_dmabuf:     clk_disable(host->clk);  clk_free:     clk_put(host->clk);  probe_free_irq_cd:     if (host->irq_cd >= 0)         free_irq(host->irq_cd, host);  probe_free_irq:     free_irq(host->irq, host);  probe_iounmap:     iounmap(host->base);  probe_free_mem_region:     release_mem_region(host->mem->start, RESSIZE(host->mem));  probe_free_host:     mmc_free_host(mmc);  probe_out:     return ret; }

    1. Linux 的通知鍊機制
    1. //Linux的通知鍊機制,需要核心配置時的支援。 //這個通知鍊機制在MMC/SD卡驅動中應用的目的是,當CPU頻率發生改變時,MMC/SD時鐘頻率也要改變。 //具體通知鍊機制的原理請看下一篇轉載的文章。 #ifdef CONFIG_CPU_FREQ static int s3cmci_cpufreq_transition(struct notifier_block *nb, unsigned long val, void *data) {     struct s3cmci_host *host;     struct mmc_host *mmc;     unsigned long newclk;     unsigned long flags;     host = container_of(nb, struct s3cmci_host, freq_transition);     newclk = clk_get_rate(host->clk);     mmc = host->mmc;     if ((val == CPUFREQ_PRECHANGE && newclk > host->clk_rate) ||      (val == CPUFREQ_POSTCHANGE && newclk < host->clk_rate)) {         spin_lock_irqsave(&mmc->lock, flags);         host->clk_rate = newclk;         if (mmc->ios.power_mode != MMC_POWER_OFF &&          mmc->ios.clock != 0)             s3cmci_set_clk(host, &mmc->ios);         spin_unlock_irqrestore(&mmc->lock, flags);     }     return 0; } static inline int s3cmci_cpufreq_register(struct s3cmci_host *host) {     host->freq_transition.notifier_call = s3cmci_cpufreq_transition;     return cpufreq_register_notifier(&host->freq_transition, CPUFREQ_TRANSITION_NOTIFIER); } static inline void s3cmci_cpufreq_deregister(struct s3cmci_host *host) {     cpufreq_unregister_notifier(&host->freq_transition, CPUFREQ_TRANSITION_NOTIFIER); } #else//如果核心配置時沒有選擇此功能支援,則其實作為空即可 static inline int s3cmci_cpufreq_register(struct s3cmci_host *host) {     return 0; }

    1. 從 探測函數中可以看到,我們接下來要實作的功能就很清晰了。他們分别是:

      a. s3cmci_ops SDI主機控制器操作接口函數功能;

      b. s3cmci_irq_cd SDI的卡檢測中斷服務功能;

      c. s3cmci_irq SDI的中斷服務功能;

嵌入式Linux之我行,主要講述和總結了本人在學習嵌入式linux中的每個步驟。一為總結經驗,二希望能給想入門嵌入式Linux的朋友提供友善。如有錯誤之處,謝請指正。

一、開發環境

  • 主  機:VMWare--Fedora 9
  • 開發闆:Mini2440--64MB Nand, Kernel:2.6.30.4
  • 編譯器:arm-linux-gcc-4.3.2

上接:S3C2440上MMC/SD卡驅動執行個體開發講解(一)

6. s3cmci_ops SDI主機控制器操作接口函數功能分析:

static struct mmc_host_ops s3cmci_ops =  {     .request = s3cmci_request,//實作host的請求處理(即:指令和資料的發送和接收)     .set_ios = s3cmci_set_ios,//通過核心層傳遞過來的ios,配置host寄存器(使能時鐘、總線帶寬等)     .get_ro  = s3cmci_get_ro,//通過讀取GPIO端口來判斷卡是否寫有保護     .get_cd  = s3cmci_card_present,//通過讀取GPIO端口來判斷卡是否存在 };

mmc_host_ops結構體定義了對host主機進行操作的各種方法,其定義在Core核心層的host.h中,也就是Core核心層對Host主機層提供的接口函數。這裡各種方法的函數原型如下:

void  (*request)(struct mmc_host *host, struct mmc_request *req); void  (*set_ios)(struct mmc_host *host, struct mmc_ios *ios); int   (*get_ro)(struct mmc_host *host); int   (*get_cd)(struct mmc_host *host);

從各函數原型上看,他們都将mmc_host結構體作為參數,是以我在剛開始的時候就說過mmc_host結構體是MMC/SD卡驅動中比較重要的資料結構。 可以這樣說,他是Core層與Host層進行資料交換的載體。那麼,這些接口函數何時會被調用呢?答案可以在Core層的core.c和sd.c中找到,我們可以看到如下部分代碼:

static void mmc_start_request(struct mmc_host *host, struct mmc_request *mrq) {     ......     host->ops->request(host, mrq);//導緻s3cmci_request被調用 } static inline void mmc_set_ios(struct mmc_host *host) {     ......     host->ops->set_ios(host, ios);//導緻s3cmci_set_ios被調用 } void mmc_rescan(struct work_struct *work) {     ......//導緻s3cmci_card_present被調用     if (host->ops->get_cd && host->ops->get_cd(host) == 0)             goto out;     ...... } static int mmc_sd_init_card(struct mmc_host *host, u32 ocr,     struct mmc_card *oldcard) {     ......          if (!oldcard)      {   //導緻s3cmci_get_ro被調用         if (!host->ops->get_ro || host->ops->get_ro(host) < 0)          {             printk(KERN_WARNING "%s: host does not "                 "support reading read-only "                 "switch. assuming write-enable.\n",                 mmc_hostname(host));         }          else          {             if (host->ops->get_ro(host) > 0)                 mmc_card_set_readonly(card);         }     }     ...... }

好了,我們開始分析每個接口函數的具體實作吧,從簡單的開始吧。 判斷卡是否存在,如下代碼:

static int s3cmci_card_present(struct mmc_host *mmc) {     //從mmc_host的對象中擷取出s3cmci_host結構體的資料,在s3cmci_probe函數中進行關聯的     struct s3cmci_host *host = mmc_priv(mmc);     struct s3c24xx_mci_pdata *pdata = host->pdata;     int ret;     //判斷有無設定卡檢測引腳端口,引腳在s3cmci_probe函數中已設定     if (pdata->gpio_detect == 0)         return -ENOSYS;     //從設定的卡檢測引腳中讀出目前的電平值,來判斷卡是插入存在的還是被拔出不存在的     ret = s3c2410_gpio_getpin(pdata->gpio_detect) ? 0 : 1;     return ret ^ pdata->detect_invert; }

擷取卡是否寫有保護,其實實作跟卡檢查類似,代碼如下:

static int s3cmci_get_ro(struct mmc_host *mmc) {     //從mmc_host的對象中擷取出s3cmci_host結構體的資料,在s3cmci_probe函數中進行關聯的     struct s3cmci_host *host = mmc_priv(mmc);     struct s3c24xx_mci_pdata *pdata = host->pdata;     int ret;     //判斷有無設定卡寫保護引腳端口,引腳在s3cmci_probe函數中已設定     if (pdata->gpio_wprotect == 0)         return 0;     //從設定的卡寫保護引腳中讀出目前的電平值,來判斷卡是否寫有保護     ret = s3c2410_gpio_getpin(pdata->gpio_wprotect);     if (pdata->wprotect_invert)         ret = !ret;     return ret; }

配置host寄存器的時鐘和總線寬度,代碼如下:

static void s3cmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) {     //從mmc_host的對象中擷取出s3cmci_host結構體的資料,在s3cmci_probe函數中進行關聯的     struct s3cmci_host *host = mmc_priv(mmc);     u32 mci_con;     //讀取SDI控制寄存器的值     mci_con = readl(host->base + S3C2410_SDICON);     //ios結構體參數從Core層傳遞過來,根據不同的電源狀态來配置SDI各寄存器     switch (ios->power_mode)      {         case MMC_POWER_ON:         case MMC_POWER_UP:             //根據開發闆引腳連接配接情況配置SDI控制器的各信号線,包括:時鐘線、指令線和四條資料線             s3c2410_gpio_cfgpin(S3C2410_GPE5, S3C2410_GPE5_SDCLK);             s3c2410_gpio_cfgpin(S3C2410_GPE6, S3C2410_GPE6_SDCMD);             s3c2410_gpio_cfgpin(S3C2410_GPE7, S3C2410_GPE7_SDDAT0);             s3c2410_gpio_cfgpin(S3C2410_GPE8, S3C2410_GPE8_SDDAT1);             s3c2410_gpio_cfgpin(S3C2410_GPE9, S3C2410_GPE9_SDDAT2);             s3c2410_gpio_cfgpin(S3C2410_GPE10, S3C2410_GPE10_SDDAT3);                  if (host->pdata->set_power)                 host->pdata->set_power(ios->power_mode, ios->vdd);                  break;              case MMC_POWER_OFF:         default:             //如果電源狀态為關閉或者預設情況下,關閉SDI的時鐘信号             s3c2410_gpio_setpin(S3C2410_GPE5, 0);             s3c2410_gpio_cfgpin(S3C2410_GPE5, S3C2410_GPE5_OUTP);                  //根據資料手冊的SDICON寄存器位的介紹,此處是将整個sdmmc時鐘複位             mci_con |= S3C2440_SDICON_SDRESET;                  if (host->pdata->set_power)                 host->pdata->set_power(ios->power_mode, ios->vdd);                  break;     }     //設定SDI波特率預定标器寄存器以确定時鐘,看其定義部分     s3cmci_set_clk(host, ios);     //根據SDI目前的時鐘頻率來設定寄存器的使能時鐘位     if (ios->clock)         mci_con |= S3C2410_SDICON_CLOCKTYPE;     else         mci_con &= ~S3C2410_SDICON_CLOCKTYPE;     //将計算好的值寫回SDI控制寄存器     writel(mci_con, host->base + S3C2410_SDICON);     //下面隻是一些調試資訊,可以不要     if ((ios->power_mode == MMC_POWER_ON) || (ios->power_mode == MMC_POWER_UP))      {         dbg(host, dbg_conf, "running at %lukHz (requested: %ukHz).\n",             host->real_rate/1000, ios->clock/1000);     }      else      {         dbg(host, dbg_conf, "powered down.\n");     }     //設定總線寬度     host->bus_width = ios->bus_width; } //設定SDI波特率預定标器寄存器以确定時鐘 static void s3cmci_set_clk(struct s3cmci_host *host, struct mmc_ios *ios) {     u32 mci_psc;     //根據SDI工作時鐘頻率範圍來确定時鐘預分頻器值     for (mci_psc = 0; mci_psc < 255; mci_psc++)      {         host->real_rate = host->clk_rate / (host->clk_div*(mci_psc+1));         if (host->real_rate <= ios->clock)             break;     }     //根據資料手冊描述,SDI波特率預定标器寄存器隻有8個位,是以最大值為255     if (mci_psc > 255)         mci_psc = 255;     host->prescaler = mci_psc;//确定的預分頻器值          //将預分頻器值寫于SDI波特率預定标器寄存器中     writel(host->prescaler, host->base + S3C2410_SDIPRE);     if (ios->clock == 0)         host->real_rate = 0; }

MMC/SD請求處理,這是Host驅動中比較重要的一部分。請求處理的整個流程請參考(一)中的流程圖,他很好的描述了一個請求是怎樣從Host層發出,通過Core層送出到Card層被塊裝置處理的。下面看代碼:

static void s3cmci_request(struct mmc_host *mmc, struct mmc_request *mrq) {     //從mmc_host的對象中擷取出s3cmci_host結構體的資料,在s3cmci_probe函數中進行關聯的     struct s3cmci_host *host = mmc_priv(mmc);     //s3cmci_host結構體定義的status主要是記錄請求過程所處的階段及狀态,友善調試時使用     host->status = "mmc request";     //請求處理主要包括MMC/SD指令和資料處理,是以定義cmd_is_stop來區分是哪種請求     host->cmd_is_stop = 0;     //将Core層的mmc_request對象儲存到Host層中以備使用     host->mrq = mrq;     //在開始發出一個請求前先要檢測一下卡是否還存在,否則送出到了塊裝置層而沒有請求處理的對象發生錯誤     if (s3cmci_card_present(mmc) == 0)      {         dbg(host, dbg_err, "%s: no medium present\n", __func__);         host->mrq->cmd->error = -ENOMEDIUM;         mmc_request_done(mmc, mrq);//如果卡不存在則馬上結束這次請求     }      else     {         s3cmci_send_request(mmc);//如果卡還存在則送出請求     } }

//發送請求 static void s3cmci_send_request(struct mmc_host *mmc) {     //從mmc_host的對象中擷取出s3cmci_host結構體的資料,在s3cmci_probe函數中進行關聯的     struct s3cmci_host *host = mmc_priv(mmc);     //取出在s3cmci_request函數中儲存的mmc_request對象以使用     struct mmc_request *mrq = host->mrq;     //在s3cmci_request函數中設定的cmd_is_stop初始值為0,表示目前是指令請求     struct mmc_command *cmd = host->cmd_is_stop ? mrq->stop : mrq->cmd;     //清空SDI指令狀态寄存器、資料狀态寄存器和FIFO狀态寄存器     writel(0xFFFFFFFF, host->base + S3C2410_SDICMDSTAT);     writel(0xFFFFFFFF, host->base + S3C2410_SDIDSTA);     writel(0xFFFFFFFF, host->base + S3C2410_SDIFSTA);     //如果目前這次的請求是資料請求     if (cmd->data)      {         //進入資料請求處理設定,主要是資料控制寄存器的配置         int res = s3cmci_setup_data(host, cmd->data);         if (res)          {             //如果在資料請求設定中出現異常,則馬上結束這次請求             dbg(host, dbg_err, "setup data error %d\n", res);             cmd->error = res;             cmd->data->error = res;             mmc_request_done(mmc, mrq);             return;         }         //判斷資料處理的方式是DAM還是FIFO,在s3cmci_probe函數中初始的是0,是以沒有使用DMA的方式         if (host->dodma)             res = s3cmci_prepare_dma(host, cmd->data);         else             res = s3cmci_prepare_pio(host, cmd->data);         if (res)          {             //如果請求處理資料失敗則也要馬上結束這次請求             dbg(host, dbg_err, "data prepare error %d\n", res);             cmd->error = res;             cmd->data->error = res;             mmc_request_done(mmc, mrq);             return;         }     }     //否則這次請求是指令請求     s3cmci_send_command(host, cmd);     //還記得在s3cmci_probe中SDI未準備好是屏蔽了SD中斷,是以這裡就使能中斷     enable_irq(host->irq); }

繼續閱讀