嵌入式Linux知识:S3C2440上 MMC/SD卡驱动实例开发讲解(2)

五、实例分析MMC/SD卡设备驱动程序
  1. Mini2440开发板的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    /* VDD voltage 1.65 - 1.95 */
    # define  MMC_VDD_20_21   0x00000100    /* VDD voltage 2.0 ~ 2.1 */
    # define  MMC_VDD_21_22   0x00000200    /* VDD voltage 2.1 ~ 2.2 */
    # define  MMC_VDD_22_23   0x00000400    /* VDD voltage 2.2 ~ 2.3 */
    # define  MMC_VDD_23_24   0x00000800    /* VDD voltage 2.3 ~ 2.4 */
    # define  MMC_VDD_24_25   0x00001000    /* VDD voltage 2.4 ~ 2.5 */
    # define  MMC_VDD_25_26   0x00002000    /* VDD voltage 2.5 ~ 2.6 */
    # define  MMC_VDD_26_27   0x00004000    /* VDD voltage 2.6 ~ 2.7 */
    # define  MMC_VDD_27_28   0x00008000    /* VDD voltage 2.7 ~ 2.8 */
    # define  MMC_VDD_28_29   0x00010000    /* VDD voltage 2.8 ~ 2.9 */
    # define  MMC_VDD_29_30   0x00020000    /* VDD voltage 2.9 ~ 3.0 */
    # define  MMC_VDD_30_31   0x00040000    /* VDD voltage 3.0 ~ 3.1 */
    # define  MMC_VDD_31_32   0x00080000    /* VDD voltage 3.1 ~ 3.2 */
    # define  MMC_VDD_32_33   0x00100000    /* VDD voltage 3.2 ~ 3.3 */
    # define  MMC_VDD_33_34   0x00200000    /* VDD voltage 3.3 ~ 3.4 */
    # define  MMC_VDD_34_35   0x00400000    /* VDD voltage 3.4 ~ 3.5 */
    # define  MMC_VDD_35_36   0x00800000    /* VDD voltage 3.5 ~ 3.6 */

        unsigned  long        caps;          /* Host capabilities */

    # define  MMC_CAP_4_BIT_DATA    (< <  0) /* Can the host do 4 bit transfers */
    # define  MMC_CAP_MMC_HIGHSPEED (< <  1) /* Can do MMC high-speed timing */
    # define  MMC_CAP_SD_HIGHSPEED  (< <  2) /* Can do SD high-speed timing */
    # define  MMC_CAP_SDIO_IRQ      (< <  3) /* Can signal pending SDIO IRQs */
    # define  MMC_CAP_SPI           (< <  4) /* Talks only SPI protocols */
    # define  MMC_CAP_NEEDS_POLL    (< <  5) /* Needs polling for card-detection */
    # define  MMC_CAP_8_BIT_DATA    (< <  6) /* Can the host do 8 bit transfers */

        /* host specific block data */
        unsigned  int     max_seg_size;    /* see blk_queue_max_segment_size */
        unsigned  short   max_hw_segs;     /* see blk_queue_max_hw_segments */
        unsigned  short   max_phys_segs;   /* see blk_queue_max_phys_segments */
        unsigned  short   unused;
        unsigned  int     max_req_size;    /* maximum number of bytes in one req */
        unsigned  int     max_blk_size;    /* maximum size of one mmc block */
        unsigned  int     max_blk_count;   /* maximum number of blocks in one req */

        /* private data */
        spinlock_t      lock;   /* lock for claim and bus ops */

        struct  mmc_ios  ios ;    /* current io bus settings */
        u32             ocr;    /* the current OCR setting */

        /* group bitfields together to minimize padding */
        unsigned  int         use_spi_crc: 1;
        unsigned  int         claimed: 1;     /* host exclusively claimed */
        unsigned  int         bus_dead: 1;    /* bus has been released */
    # ifdef  CONFIG_MMC_DEBUG
        unsigned  int         removed: 1;     /* host is being removed */
    # endif

        struct  mmc_card     * card;         /* device attached to this host */

        wait_queue_head_t   wq;

        struct  delayed_work    detect;

        const  struct  mmc_bus_ops * bus_ops;   /* current bus driver */
        unsigned  int         bus_refs;        /* reference counter */

        unsigned  int         sdio_irqs;
        struct  task_struct  * sdio_irq_thread;
        atomic_t            sdio_irq_thread_abort;

    # ifdef  CONFIG_LEDS_TRIGGERS
        struct  led_trigger  * led;         /* activity 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( ) ;
    }



      • 所 以,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;
    }


      • 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;
      }


      • 从 探测函数中可以看到,我们接下来要实现的功能就很清晰了。他们分别是:
        a. s3cmci_ops SDI主机控制器操作接口函数功能;
        b. s3cmci_irq_cd SDI的卡检测中断服务功能;
        c. s3cmci_irq SDI的中断服务功能;

    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)
    {
        . . . . . .
        /* Check if read-only switch is active.*/
        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) ;
    }

    http://www.linuxidc.com/Linux/2011-02/32646p6.htm

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值