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

S3C2410原理图导读 电脑里有主板,主板上有CPU、内存、网卡、串口等 开发板就是一台小电脑 在SOC如2440当中,有CPU和串口模块、I2C模块等等, 还有内存、网卡等,将这些加在一起做成一个开发板 点灯 我们可以看到2410主芯片这里有两个灯 如何点亮这两个灯? 只需要将GPB5或GPB6配置成输出引脚并且配置成低电平即可。 CS8900网卡 网卡属于类似内存的接口这一类 那么怎么访问这张网卡芯片呢? 看片选信号 打开2410手册中的内存映射图 只要地址再这个范围内,就可以访问这个网卡 这边通过与非门来选择IO读 阅读详情
五、实例分析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    ( 1 < <  0) /* Can the host do 4 bit transfers */
    # define  MMC_CAP_MMC_HIGHSPEED ( 1 < <  1) /* Can do MMC high-speed timing */
    # define  MMC_CAP_SD_HIGHSPEED  ( 1 < <  2) /* Can do SD high-speed timing */
    # define  MMC_CAP_SDIO_IRQ      ( 1 < <  3) /* Can signal pending SDIO IRQs */
    # define  MMC_CAP_SPI           ( 1 < <  4) /* Talks only SPI protocols */
    # define  MMC_CAP_NEEDS_POLL    ( 1 < <  5) /* Needs polling for card-detection */
    # define  MMC_CAP_8_BIT_DATA    ( 1 < <  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

S3C2440平台SD卡驱动开发实战详解 SD卡驱动是嵌入式系统中实现主机处理器与SD存储卡通信的关键软件模块,运行于操作系统内核层,负责抽象底层硬件差异并提供统一访问接口。其主要职责包括:上电初始化、卡类型识别(MMC/SD/SDHC)、命令发送与响应解析、块数据读写控制以及错误处理与恢复机制。驱动通过标准总线协议(如MMC/SD规范)与SD控制器交互,控制CLK、CMD和DAT线上的时序与电平变化,完成OCR协商、CID/CSD寄存器读取、RCA分配等关键流程,并支持PIO与DMA两种数据传输模式,确保数据完整性与系统效率。 阅读详情

相关推荐

MINI2440的UCOS+SD卡驱动,基于Eclipse环境

工程由友善之臂提供的原始UCOS和裸机代码修改完善而来,环境为Eclipse前端+yagarto gcc,可以参照OPEN JTAG的环境搭建教程。工程中提供了基于OPEN JTAG和JLINK GDB sever的外部SRAM初始化脚本,可根据实际情况选择。

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

一、开发环境   主  机:VMWare--Fedora 9 开发板:Mini2440--64MB Nand, Kernel:2.6.30.4 编译器:arm-linux-gcc-4.3.2 二、MMC/SD介绍及SDI主机控制器   首先我们来理清几个概念: MMC:(Multi Media Card)由西门子公司和首推CF的SanDisk于1997年

sunshine 2126

亲身实践s3c2440 gcc版本的USB HID和U盘裸奔代码

完全抛弃IDE,基于arm的gcc版本设计的USB设备。实现了HID鼠标和U盘的功能。 有问题联系我:woodsboy.cn@163.com

linux下查看设备的接口,linux 查看sdio接口有哪些设备?

static struct pxamci_platform_data luther_mci_platform_data = {.detect_delay = 20,//检测到sd设备插入之后,延时detect_delay个tick之后,执行函数.ocr_mask = MMC_VDD_32_33|MMC_VDD_33_34,.init = luther_mci_init,.setpower = lu...

weixin_33250328的博客 801

S3C2440开发板底板原理图和pcb

开发板底板的原理图和pcb板,已经覆铜,希望对开始学arm的人来说有所帮助!

ARM S3C2440 开发板原理图

ARM9 S3C2440 开发板原理图,最小系统原理图,扩展板原理图,另外还有扩展板的PCB可以直接做板

S3C2440上 MMC/SD卡驱动实例开发讲解

首先我们来理清几个概念: 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标 准上定义了一种外设接

menuconfig的专栏 2350

STM32多模式智能小车状态机设计与中断优先级实践

在嵌入式实时系统中,状态机是实现多任务协同与功能切换的核心范式。其本质是通过有限状态集合、明确转移条件和确定性动作,构建可预测、易维护的控制逻辑。基于STM32等资源受限MCU时,需兼顾状态调度效率、中断并发安全与硬件资源约束。关键技术价值体现在:提升系统模块解耦度、支持物理按键与蓝牙双源触发、保障实时响应不丢帧。典型应用场景包括智能小车多模运行(循迹、避障、遥控、定距跟随等)、工业设备工作模式切换及IoT终端低功耗状态管理。本文以STM32F103C8T6平台为例,深入解析全局模式变量设计、双通道状态切换

weixin_42509888的博客 93

嵌入式Linux之我行——S3C2440上MMC/SD卡驱动实例开发讲解(一)

http://blog.ednchina.com/mcuandme/1787530/Message.aspx   http://blog.chinaunix.net/u3/101649/showart_2232724.html 嵌入式Linux之我行,主要讲述和总结了本人在学习嵌入式linux中的每个步骤。一为总结经验,二希望能给想入门嵌入式Linux的朋友提供方便。如有错误之处,谢请指正。

keep moving 3184

禅道18.3版本二开避坑指南:手把手教你扩展自定义报表功能

本文详细介绍了禅道18.3版本的二次开发实践,重点讲解如何扩展自定义报表功能。通过zentaoPHP框架的深度定制,从开发环境搭建到核心数据模型扩展,再到可视化配置和性能优化,手把手教你构建多维报表分析系统,满足企业级项目管理需求。

day7的博客 370

TSMaster二次开发实战:如何用Python搞定CAN/CANFD事件回调(附完整代码)

本文详细介绍了如何利用Python进行TSMaster二次开发,实现CAN/CANFD事件回调功能。通过实战代码演示,讲解了预发送与后处理回调的核心区别、注册流程、资源管理及性能优化技巧,帮助工程师高效构建自动化测试与数据监控应用。

star的博客 668

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

二、MMC/SD介绍及SDI主机控制器 首先我们来理清几个概念: 1. MMC:(Multi Media Card)由西门子公司和首推CF的SanDisk于1997年推出的多媒体记忆卡标准。 2. SD:(Secure Digital Memory Card)由日本松下、东芝及美国SanDisk公司于1999年8月共同开发研制的新一代记忆卡标准,已完全兼容MMC标准。

menuconfig的专栏 1481

用宝塔面板+CentOS 7.6,30分钟搞定DNF手游私服搭建(附全套资源下载)

本文详细介绍了如何使用宝塔面板和CentOS 7.6在30分钟内快速搭建DNF手游私服。通过图形化界面操作,无需复杂命令,即可完成服务端部署、MySQL数据库配置及客户端适配,适合Linux新手快速上手。

weixin_33737774的博客 599

MMC/SD卡驱动实例开发讲解(一)

transition平台工作linuxioc 嵌入式Linux之我行,主要讲述和总结了本人在学习嵌入式linux中的每个步骤。一为总结经验,二希望能给想入门嵌入式Linux的朋友提供方便。如有错误之处,谢请指正。 共享资源,欢迎转载:http://hbhuanggang.cublog.cn 一、开发环境 主  机:VMWare--Fedora 9 开发板:Mi

wuyuwei45的专栏 1637

虚幻商城 蓝图汇总

虚幻引擎是一个非常强大和灵活的游戏开发工具,拥有众多的蓝图可以帮助我们更高效地工作。

一碗清深的博客 1507

使用Simulink进行四足机器人的非线性动力学建模与仿真

四足机器人的运动包含多个自由度(DOF),包括腿部的摆动和支撑阶段的动力学特性。由于存在接触力、摩擦以及不同地形的影响,四足机器人的动力学行为呈现出高度的非线性特征。因此,构建准确的非线性动力学模型对于研究机器人的运动学和动力学特性至关重要。定义代表机器人状态的变量,如位置、速度、角度和角速度。matlab深色版本通过本指南,我们介绍了如何基于Simulink搭建一个四足机器人的非线性动力学模型,并进行了详细分析。

xiaoheshang_123的博客 311

S3C2440+Linux2.6mmc/sd驱动程序

S3C2440+Linux2.6mmc/sd驱动程序  这几天要做SD卡方面的事情了,看了看相关资料,从网上得到了 s3c2440 在2.6下的驱动,经过修改,目前可以工作了,现拿出与大家分享.希望共同学习讨论.     1 /*      2  *  linux/drivers/mmc/s3c2410mci.h - Samsung S3C2410 SDI Interface driver

阶廷成长日记 2660

QGIS004:【05矢量属性表】-保留字段、删除字段、添加唯一值索引字段、添加字段到属性表、添加自增量字段、添加XY字段到图层、文本型转浮点型、重构字段、重命名字段

摘要:QGIS矢量属性表工具箱包括保留字段、删除字段、添加唯一值索引字段、添加字段到属性表、添加自增量字段、添加XY字段到图层、文本型转浮点型、重构字段、重命名字段等选项,本文介绍各选项的基本操作。

qq_31762031的博客 2312

s3c2440上MMC/SD卡驱动的分析(一)

一、开发环境 主  机:VMWare--Fedora 9 开发板:Mini2440--64MB Nand, Kernel:2.6.30.4 编译器:arm-linux-gcc-4.3.2 二、MMC/SD介绍及SDI主机控制器   首先我们来理清几个概念: MMC:(Multi Media Card)由西门子公司和首推CF的SanDisk于1997年推出的多媒体

阿戟哥哥的专栏 1666

Unity零基础到入门☀️| 万字教程 讲解Unity中的数据存储 PlayerPrefs、XML、JSON【建议收藏】

本文章是Unity零基础到入门专栏的系列文章。 适合对Unity3D引擎感兴趣的小伙伴学习,偏向于新手学习! 本篇文章就来介绍一下Unity中三种基本的数据存储方式:PlayerPrefs、XML、JSON

努力前行,总会成为自己心中的那道光 8万+

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

SD卡驱动程序解析

一、开发环境 主 机:VMWare--Fedora 9 开发板:Mini2440--64MB Nand, Kernel:2.6.30.4 编译器:arm-linux-gcc-4.3.2 二、MMC/SD介绍及SDI主机控制器 首先我们来理清几个概念: MMC:(Multi Media Card)由西门子公司和首推CF的SanDisk于1997年推出的多媒体记忆卡标准。 SD:(S...

u010783226的专栏 3462

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

嵌入式linux sd卡读写,嵌入式Linux之我行——S3C2440上MMC/SD卡驱动实例开发讲解(二)...

嵌入式Linux之我行,主要讲述和总结了本人在学习嵌入式linux中的每个步骤。一为总结经验,二希望能给想入门嵌入式Linux的朋友提供方便。如有错误之处,谢请指正。一、开发环境主 机:VMWare--Fedora 9开发板:Mini2440--64MB Nand, Kernel:2.6.30.4编译器:arm-linux-gcc-4.3.26. s3cmci_ops SDI主机控制器操作接口函...

weixin_27224119的博客 739

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

块设备驱动2--sd卡驱动1(linux-3.2.36的sdchi.h分析)

我的经历: 看了某些大神的说明,我胆怯地进入了drivers/mmc/host/,看过我之前的文章知道我用的是s3c2440。 看驱动时,我首先会找s3c字样,于是我注意到sdhci-s3c.c和s3cmci.c。 看了一下Kconfig config MMC_S3C     tristate "Samsung S3C SD/MMC Card Interface support"

xxxxxlllllxl的专栏 5325
上一篇: 嵌入式Linux知识:S3C2440上 MMC/SD卡驱动实例开发讲解(1)
下一篇: SD/MMC SPI模式下命令集
menuconfig
博客等级 码龄19年 713粉丝 6原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值