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

上接: 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)
{
    . . . . . .
    /* 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) ;
}

//数据请求处理设置,主要是数据控制寄存器的配置
static int s3cmci_setup_data( struct s3cmci_host * host, struct mmc_data * data)
{
    u32 dcon, imsk, stoptries = 3;

    /*如果不是 数据处理请求则清零SDI数据控制寄存器*/
    if ( ! data)
    {
        writel( 0, host- > base + S3C2410_SDIDCON) ;
        return 0;
    }

    //根据SDI模块大小寄存器描述,如果在多模块下BlkSize必须分配字大小 即:BlkSize[1:0]=00
    // 所以这里与上3(即:二进制的11)来判断的是单模块
    if ( ( data- > blksz & 3) ! = 0)
    {
        //如果在单模块处理的情况下,模块数大于1了,就出现异常
        if ( data- > blocks > 1)
        {
            pr_warning( "%s: can't do non-word sized block transfers (blksz %d)/n" , __func__ , data- > blksz) ;
            return - EINVAL;
        }
    }

    //循环判断数据是否正在传输中(发送或者接收)
    while ( readl( host- > base + S3C2410_SDIDSTA) & ( S3C2410_SDIDSTA_TXDATAON | S3C2410_SDIDSTA_RXDATAON) )
    {
        dbg( host, dbg_err, "mci_setup_data() transfer stillin progress./n" ) ;

        //如果正在传输中则立刻停止传输
        writel( S3C2410_SDIDCON_STOP, host- > base + S3C2410_SDIDCON) ;
        //接着立刻 复位整个MMC/SD时钟
        s3cmci_reset( host) ;

        //这里应该是起到一个延迟的效果。因为硬件停止传输到复位MMC/SD需要一点时 间,而循环判断非常快。
        //如 果在这个时间内硬件还处在数据传输中而没有复位好,则异常
        if ( ( stoptries- - ) = = 0)
        {
            return - EINVAL;
        }
    }

    dcon = data- > blocks & S3C2410_SDIDCON_BLKNUM_MASK;

    //如果使用DMA传输,则使能SDI数据控制寄存器的DMA
    if ( host- > dodma)
        dcon | = S3C2410_SDIDCON_DMAEN;

    //如果设置总线宽度为4线,则使能SDI数据控制寄存器的总线宽度模式为宽总线 模式(即:4线模式)
    if ( host- > bus_width = = MMC_BUS_WIDTH_4)
        dcon | = S3C2410_SDIDCON_WIDEBUS;

    //配置 SDI数据控制寄存器的数据传输模式为模块数据传输
    if ( ! ( data- > flags & MMC_DATA_STREAM) )
        dcon | = S3C2410_SDIDCON_BLOCKMODE;

    if ( data- > flags & MMC_DATA_WRITE)
    {
        //数据发送命令响应收到后开始数据传输
        dcon | = S3C2410_SDIDCON_TXAFTERRESP;
        //数据发送模式
        dcon | = S3C2410_SDIDCON_XFER_TXSTART;
    }

    if ( data- > flags & MMC_DATA_READ)
    {
        //数据发送命令响应收到后开始数据接收
        dcon | = S3C2410_SDIDCON_RXAFTERCMD;
        // 数据接收模式
        dcon | = S3C2410_SDIDCON_XFER_RXSTART;
    }

    //FIFO传输的大小使用字传输类型
    dcon | = S3C2440_SDIDCON_DS_WORD;
    
    //数据传输开始
    dcon | = S3C2440_SDIDCON_DATSTART;

    //将以上配 置的值写入SDI数据控制寄存器生效
    writel( dcon, host- > base + S3C2410_SDIDCON) ;

    //配置模块大小寄存器的块大小值
    writel( data- > blksz, host- > base + S3C2410_SDIBSIZE) ;

    //出现 FIFO失败SDI中断使能;数据接收CRC错误SDI中断使能;数据接收超时SDI中断使能;数据计时器为0SDI中断使能
    imsk = S3C2410_SDIIMSK_FIFOFAIL | S3C2410_SDIIMSK_DATACRC | S3C2410_SDIIMSK_DATATIMEOUT | S3C2410_SDIIMSK_DATAFINISH;
    enable_imask( host, imsk) ;
//使能中断


    //将配置的值写入SDI中断屏蔽寄存器,使之生效
    writel( 0x007FFFFF, host- > base + S3C2410_SDITIMER) ;

    return 0;
}

//复位整个MMC/SD时钟
static void s3cmci_reset(struct s3cmci_host * host)
{
    u32 con = readl( host-> base + S3C2410_SDICON
);

    con |= S3C2440_SDICON_SDRESET;
    writel( con, host-> base + S3C2410_SDICON);
}


//使能中断
static inline u32 enable_imask( struct s3cmci_host * host, u32 imask)
{
    u32 newmask;

    newmask = readl( host- > base + host- > sdiimsk) ;
    newmask | = imask;

    writel( newmask, host- > base + host- > sdiimsk) ;

    return newmask;
}

//屏蔽中断
static inline u32 disable_imask( struct s3cmci_host * host, u32 imask)
{
    u32 newmask;

    newmask = readl( host- > base + host- > sdiimsk) ;
    newmask & = ~ imask;

    writel( newmask, host- > base + host- > sdiimsk) ;

    return newmask;
}

//清空中断屏蔽寄存器
static inline void clear_imask( struct s3cmci_host * host)
{
    writel( 0, host- > base + host- > sdiimsk) ;
}

//使用DMA传输数据方式,注意:这里就不讲如何使用DMA的具体细节了,以后再讲。
//对于驱动中相关DMA操作的方法都在plat-s3c24xx/dma.c中定 义了。
static int s3cmci_prepare_dma( struct s3cmci_host * host, struct mmc_data * data)
{
    int dma_len, i;
    
    //判断DMA传输的方向是读还是写
    int rw = ( data- > flags & MMC_DATA_WRITE) ? 1 : 0;

    //根据传输的方向来配置DMA相关寄存器
    s3cmci_dma_setup( host, rw ? S3C2410_DMASRC_MEM : S3C2410_DMASRC_HW) ;
    //s3c2410_dma_ctrl函数将根据标志flag来控制DMA传输的开始、停止等操作
    s3c2410_dma_ctrl( host- > dma, S3C2410_DMAOP_FLUSH) ;

    //合并 data->sg上相邻的段,映射一个发散/汇聚DMA操作

    //返回值是传送的DMA缓冲区数,可能会小于sg_len,也就是说sg_len与dma_len可能是不同。
    dma_len = dma_map_sg( mmc_dev( host- > mmc) , data- > sg, data- > sg_len,
             ( rw) ? DMA_TO_DEVICE : DMA_FROM_DEVICE) ;

    if ( dma_len = = 0)
        return - ENOMEM;

    host- > dma_complete = 0; //初始DMA操作的状态
    host- > dmatogo = dma_len; //保存合并后的段数

    for ( i = 0; i < dma_len; i+ + )
    {
        int res;

        //分配一个数据段管理结构体,并将各数据段穿成单向链表,以及加载一个数据段到 DMA通道

        //sg_dma_address返回的是总线(DMA)的地址,sg_dma_len返回的是缓存区的长度
        res = s3c2410_dma_enqueue( host- > dma, ( void * ) host, sg_dma_address( & data- > sg[ i] ) , sg_dma_len( & data- > sg[ i] ) ) ;

        if ( res)
        {
            s3c2410_dma_ctrl( host- > dma, S3C2410_DMAOP_FLUSH) ;
            return - EBUSY;
        }
    }

    //开始DMA数据传输,数据传输会在接收到请求后真正开始
    s3c2410_dma_ctrl( host- > dma, S3C2410_DMAOP_START) ;

    return 0;
}

// 根据传输的方向来配置DMA相关寄存器,详细描述请查看数据手册DMA章节
static void s3cmci_dma_setup( struct s3cmci_host * host, enum s3c2410_dmasrc source)
{
    static enum s3c2410_dmasrc last_source = - 1;
    static int setup_ok;

    if ( last_source = = source)
        return ;

    last_source = source;

    //配置DMA源或者目标硬件类型和地址,这里DMA使用的是物理地址,不是虚拟 地址。
    s3c2410_dma_devconfig( host- > dma, source, 3, host- > mem- > start + host- > sdidata) ;

    //这个判断的作用是让下面的代码只执行一次,以后不在被执行
    if ( ! setup_ok)
    {
        //配置DMA控制寄存器中的传输数据大小单位
        s3c2410_dma_config( host- > dma, 4, 0) ;
        //设置DMA回调函数为s3cmci_dma_done_callback,当一段数据传输完后该函数被调用
        s3c2410_dma_set_buffdone_fn( host- > dma, s3cmci_dma_done_callback) ;
        s3c2410_dma_setflags( host- > dma, S3C2410_DMAF_AUTOSTART) ;
        setup_ok = 1;
    }
}

 

//DMA回调函数, 当一段数据传输完后该函数被调用
static void s3cmci_dma_done_callback( struct s3c2410_dma_chan * dma_ch, void * buf_id, int size,
                 enum s3c2410_dma_buffresult result)
{
    struct s3cmci_host * host = buf_id;
//这个s3cmci_host类型的参数是在s3c2410_dma_enqueue的时候传递进来的

    unsigned long iflags;
    u32 mci_csta, mci_dsta, mci_fsta, mci_dcnt;

    mci_csta = readl( host- > base + S3C2410_SDICMDSTAT) ; //命令状态寄存器的值
    mci_dsta = readl( host- > base + S3C2410_SDIDSTA) ; //数据状态寄存器的值
    mci_fsta = readl( host- > base + S3C2410_SDIFSTA) ; //FIFO状态寄存器的值
    mci_dcnt = readl( host- > base + S3C2410_SDIDCNT) ; //数据保留计数器寄存器的值

    spin_lock_irqsave( & host- > complete_lock, iflags) ;

    //如果DMA返回错误,则调到错误处理处进行错误处理
    if ( result ! = S3C2410_RES_OK)
    {
        goto fail_request;
    }

    host- > dmatogo- - ;
//合并data->sg上相邻后的段数递减

    
    //如果合并的段数不为0,即所有的段还没有处理完
    if ( host- > dmatogo)
    {
        goto out;
    }

    //否则,标识这次DMA操作真正完成了
    host- > complete_what = COMPLETION_FINALIZE;

out:
    //切换到中断底半部执行
    tasklet_schedule( & host- > pio_tasklet) ;
    spin_unlock_irqrestore( & host- > complete_lock, iflags) ;
    return ;

fail_request:
    host- > mrq- > data- > error = - EINVAL;
    host- > complete_what = COMPLETION_FINALIZE;
    //如果DMA请求失败,则屏蔽SDI中断
    writel( 0, host- > base + host- > sdiimsk) ;
    goto out;
}

//使用FIFO传输数据方式。具体操作就是调用do_pio_write往FIFO中填充数据,当64字节的FIFO少于33字节时就会产生 中断;
//或者是从SD读数据,则先使能中断, 当FIFO多于31字节时时,则会调用中断服务程序,中断服务程序中将会调用do_pio_read读出FIFO的数据。
static int s3cmci_prepare_pio( struct s3cmci_host * host, struct mmc_data * data)
{
    //跟DMA类 似,这里同样要判断FIFO传输的方向是读还是写
    int rw = ( data- > flags & MMC_DATA_WRITE) ? 1 : 0;

    host- > pio_sgptr = 0;
    host- > pio_bytes = 0;
    host- > pio_count = 0;
    host- > pio_active = rw ? XFER_WRITE : XFER_READ;
//记录FIFO操作状态共三种:读、写和无操作,定义在驱动头文件中

    if ( rw) //写
    {
        //FIFO写操作
        do_pio_write( host) ;
        //使能中断。根据数据手册SDI中断屏蔽寄存器的描述,当发送FIFO半填满就产生SDI中断
        enable_imask( host, S3C2410_SDIIMSK_TXFIFOHALF) ;
    }
    else //读
    {
        //使能中断。根据数据手册SDI中断屏蔽寄存器的描述,当接收FIFO半填满或者接收FIFO有最后数据就产生 SDI中断
        enable_imask( host, S3C2410_SDIIMSK_RXFIFOHALF | S3C2410_SDIIMSK_RXFIFOLAST) ;
    }

    return 0;
}

//FIFO写操作(即填充FIFO)
static void do_pio_write( struct s3cmci_host * host)
{
    void __iomem * to_ptr;
    int res;
    u32 fifo;
    u32 * ptr;

    //SDI数据寄存器的虚拟地址
    to_ptr = host- > base + host- > sdidata;

    //检查FIFO中当前的剩余空间
    while ( ( fifo = fifo_free( host) ) > 3)
    {
        if ( ! host- > pio_bytes)
        {
            //从分散聚集列表中获取要写的数据缓存,这里主要是获取缓存的长度和开始地址
            res = get_data_buffer( host, & host- > pio_bytes, & host- > pio_ptr) ;
            if ( res)
            {
                host- > pio_active = XFER_NONE;
                return ;
            }
        }

        //如果FIFO剩余空间比这一次要写入的数据段长度要大
        if ( fifo > = host- > pio_bytes)
            fifo = host- > pio_bytes;
        else
            fifo - = fifo & 3;

        host- > pio_bytes - = fifo; //更新还剩下没写完的缓存长度
        host- > pio_count + = fifo;
        fifo = ( fifo + 3) > > 2; //将字节数转化为字数
        ptr = host- > pio_ptr;
        
        while ( fifo- - ) //写入FIFO
            writel( * ptr+ + , to_ptr) ;
            
        host- > pio_ptr = ptr;
//更新当前地址指针的位置

    }

    //FIFO半填满时发生MMC/SD中断
    enable_imask( host, S3C2410_SDIIMSK_TXFIFOHALF) ;
}

//FIFO读操作
static void do_pio_read( struct s3cmci_host * host)
{
    int res;
    u32 fifo;
    u32 * ptr;
    u32 fifo_words;
    void __iomem * from_ptr;

    //设置SDI波特率预定标器寄存器的值
    writel( host- > prescaler, host- > base + S3C2410_SDIPRE) ;

    //SDI数 据寄存器的虚拟地址
    from_ptr = host- > base + host- > sdidata;

    //检测FIFO中当前的数据个数
    while ( ( fifo = fifo_count( host) ) )
    {
        if ( ! host- > pio_bytes)
        {
            //从分散聚集列表中获取要读数据缓存,这里主要是获取缓存的长度和开始地址的指针 位置
            res = get_data_buffer( host, & host- > pio_bytes, & host- > pio_ptr) ;
            if ( res)
            {
                host- > pio_active = XFER_NONE;
                host- > complete_what = COMPLETION_FINALIZE;
                return ;
            }
        }

        //如果FIFO中当前的数据个数比这一次要读出的数据段长度要大
        if ( fifo > = host- > pio_bytes)
            fifo = host- > pio_bytes;
        else
            fifo - = fifo & 3;

        host- > pio_bytes - = fifo; //更新还剩下没读完的缓存长度
        host- > pio_count + = fifo;
        fifo_words = fifo > > 2; //将字节数转化为字数
        ptr = host- > pio_ptr;
        
        while ( fifo_words- - ) //从FIFO中读出数据
            * ptr+ + = readl( from_ptr) ;
            
        host- > pio_ptr = ptr;
//更新当前地址指针的位置


        //如果fifo中的数据非字对齐则读取非对齐部分
        if ( fifo & 3)
        {
            u32 n = fifo & 3;
            u32 data = readl( from_ptr) ;
            u8 * p = ( u8 * ) host- > pio_ptr;

            while ( n- - )
            {
                * p+ + = data;
                data > > = 8;
            }
        }
    }

    //请求的数据已读完
    if ( ! host- > pio_bytes)
    {
        res = get_data_buffer( host, & host- > pio_bytes, & host- > pio_ptr) ;
        if ( res)
        {
            host- > pio_active = XFER_NONE;
            host- > complete_what = COMPLETION_FINALIZE;
            return ;
        }
    }

    //接收FIFO半满或者接收FIFO有最后数据时发生MMC/SD中断
    enable_imask( host, S3C2410_SDIIMSK_RXFIFOHALF | S3C2410_SDIIMSK_RXFIFOLAST) ;
}

//检测FIFO中当前的数据个数
static inline u32 fifo_count( struct s3cmci_host * host)
{
    //读取SDI FIFO状态寄存器
    u32 fifostat = readl( host- > base + S3C2410_SDIFSTA) ;

    //FIFO 中的数据个数是保存在寄存器的0-6位,所以与上S3C2410_SDIFSTA_COUNTMASK得出数据个数值
    //S3C2410_SDIFSTA_COUNTMASK定义在regs- sdi.h中为:0x7f,即:1111111
    fifostat & = S3C2410_SDIFSTA_COUNTMASK;
    return fifostat;
}

//检查FIFO中 当前的剩余空间
static inline u32 fifo_free( struct s3cmci_host * host)
{
    //这里跟检测 FIFO中当前的数据个数是一样的
    u32 fifostat = readl( host- > base + S3C2410_SDIFSTA) ;

    fifostat & = S3C2410_SDIFSTA_COUNTMASK;
    return 63 - fifostat; //用FIFO的总容量-FIFO中当前的数据个数=剩余空间
}

//MMC /SD核心为mrq->data成员分配了一个struct scatterlist的表,用来支持分散聚集,
//使用这种方法,使物理上不一致的内存页,被组装成一个连续的数组,避免了分配大 的缓冲区的问题
static inline int get_data_buffer( struct s3cmci_host * host, u32 * bytes, u32 * * pointer)
{
    struct scatterlist * sg;

    //FIFO当前的操作状态验证
    if ( host- > pio_active = = XFER_NONE)
        return - EINVAL;

    //MMC/SD请求及数据有效性验证
    if ( ( ! host- > mrq) | | ( ! host- > mrq- > data) )
        return - EINVAL;

    //数据缓存的入口有没有超过分散列表的范围
    if ( host- > pio_sgptr > = host- > mrq- > data- > sg_len)
        return - EBUSY;

    //从分散聚集列表中获取一段数据缓存
    sg = & host- > mrq- > data- > sg[ host- > pio_sgptr] ;

    * bytes = sg- > length; //该段数据缓存的长度
    * pointer = sg_virt( sg) ;
//该段数据缓存的入口地址(为虚拟地址),相当于一个游标的意思

    host- > pio_sgptr+ + ;
//准备下一段数据缓存的入口

    return 0;
}

//以上三段代码是对发送数据请求处理的,下面是发送命令请求
static void s3cmci_send_command( struct s3cmci_host * host, struct mmc_command * cmd)
{
    u32 ccon, imsk;

    //出现CRC状态错误|命令响应超时|接收命令响应|命令发出|响应CRC校验失 败时,将产生SDI中断
    imsk = S3C2410_SDIIMSK_CRCSTATUS | S3C2410_SDIIMSK_CMDTIMEOUT |
        S3C2410_SDIIMSK_RESPONSEND | S3C2410_SDIIMSK_CMDSENT |
        S3C2410_SDIIMSK_RESPONSECRC;

    //将值写入SDI中断屏蔽寄存器中
    enable_imask( host, imsk) ;

    //判断请求所处在何种状态
    if ( cmd- > data)
        //如果有数据传输,则设当前任务为完成数据传输且接收命令响应状态
        host- > complete_what = COMPLETION_XFERFINISH_RSPFIN;
    else if ( cmd- > flags & MMC_RSP_PRESENT)
        host- > complete_what = COMPLETION_RSPFIN;
    else
        //命令发送状态
        host- > complete_what = COMPLETION_CMDSENT;

    //设置命令参数寄存器
    writel( cmd- > arg , host- > base + S3C2410_SDICMDARG) ;

    ccon = cmd- > opcode & S3C2410_SDICMDCON_INDEX;
    ccon | = S3C2410_SDICMDCON_SENDERHOST | S3C2410_SDICMDCON_CMDSTART; //命令操作开始

    if ( cmd- > flags & MMC_RSP_PRESENT)
        ccon | = S3C2410_SDICMDCON_WAITRSP; //主设备等待响应

    if ( cmd- > flags & MMC_RSP_136)
        ccon | = S3C2410_SDICMDCON_LONGRSP;
//主设备接收一个136位长的响应


    //设置命令控制寄存器,开始命令的传输
    writel( ccon, host- > base + S3C2410_SDICMDCON) ;
}

7. s3cmci_irq_cd SDI的卡检测中断服务功能

//当MMC/SD卡插入卡槽时引发的中断
static irqreturn_t s3cmci_irq_cd( int irq, void * dev_id)
{
    //这个dev_id参数是申请中断时传递过来的
    struct s3cmci_host * host = ( struct s3cmci_host * ) dev_id;

    //调用核心层中的方法将将struct delayed_work detect加入共享工作队列,
    //其 处理函数为核心层中的mmc_rescan方法,用于卡的识别并初始化。
    mmc_detect_change( host- > mmc, msecs_to_jiffies( 500) ) ;

    return IRQ_HANDLED;
}

8. s3cmci_irq SDI的中断服务功能。我们从第6小节中对MMC/SD各种请求处理的代码中和(一)中“命令、数据发送流程图”中可以看出,在这个中断服务中将要处理很 多请求相关的事情。但对于中断服务来说,这样会严重影响系统的性能,所以这正是为什么要在驱动中实现中断的底半部机制。下面看代码进行分析。

//MMC/SD卡中断服务程序
static irqreturn_t s3cmci_irq( int irq, void * dev_id)
{
    //dev_id参数是申请中断的时候传递过来的s3cmci_host结构 体,void类型的指针可以存放任何的数据类型
    struct s3cmci_host * host = dev_id;
    struct mmc_command * cmd;
    u32 mci_csta, mci_dsta, mci_fsta, mci_dcnt, mci_imsk;
    u32 mci_cclear, mci_dclear;
    unsigned long iflags;

    //关中断并保持状态字
    spin_lock_irqsave( & host- > complete_lock, iflags) ;

    //分别读命令状态、数据状态、数据保留计数器、FIFO状态、中断屏蔽寄存器的 值
    mci_csta = readl( host- > base + S3C2410_SDICMDSTAT) ;
    mci_dsta = readl( host- > base + S3C2410_SDIDSTA) ;
    mci_dcnt = readl( host- > base + S3C2410_SDIDCNT) ;
    mci_fsta = readl( host- > base + S3C2410_SDIFSTA) ;
    mci_imsk = readl( host- > base + host- > sdiimsk) ;
    mci_cclear = 0;
    mci_dclear = 0;

    //如果当前没有请求状态或者请求已经完成了,则恢复中断什么都不做
    if ( ( host- > complete_what = = COMPLETION_NONE) | | ( host- > complete_what = = COMPLETION_FINALIZE) )
    {
        host- > status = "nothing to complete" ;
        clear_imask( host) ;
        goto irq_out;
    }

    //如果核心层无MMC/SD请求,则恢复中断什么都不做
    if ( ! host- > mrq)
    {
        host- > status = "no active mrq" ;
        clear_imask( host) ;
        goto irq_out;
    }

    //获取当前发送命令有无完成
    cmd = host- > cmd_is_stop ? host- > mrq- > stop : host- > mrq- > cmd;

    // 如果发送命令完成了,则恢复中断什么都不做
    if ( ! cmd)
    {
        host- > status = "no active cmd" ;
        clear_imask( host) ;
        goto irq_out;
    }

    //判断在数据传输状态时使用的传输方式
    if ( ! host- > dodma)
    {
        //不是DMA传输。如果是FIFO写,则切换到底半部去进行FIFO的写操作
        if ( ( host- > pio_active = = XFER_WRITE) & & ( mci_fsta & S3C2410_SDIFSTA_TFDET) )
        {
            disable_imask( host, S3C2410_SDIIMSK_TXFIFOHALF) ;
            tasklet_schedule( & host- > pio_tasklet) ;
            host- > status = "pio tx" ;
        }

        //如果是FIFO读,则切换到底半部去进行FIFO的读操作
        if ( ( host- > pio_active = = XFER_READ) & & ( mci_fsta & S3C2410_SDIFSTA_RFDET) )
        {
            disable_imask( host, S3C2410_SDIIMSK_RXFIFOHALF | S3C2410_SDIIMSK_RXFIFOLAST) ;
            tasklet_schedule( & host- > pio_tasklet) ;
            host- > status = "pio rx" ;
        }
    }

    //命令响应超时
    if ( mci_csta & S3C2410_SDICMDSTAT_CMDTIMEOUT)
    {
        dbg( host, dbg_err, "CMDSTAT: error CMDTIMEOUT/n" ) ;
        cmd- > error = - ETIMEDOUT;
        host- > status = "error: command timeout" ;
        goto fail_transfer;
    }

    //命令发送结束
    if ( mci_csta & S3C2410_SDICMDSTAT_CMDSENT)
    {
        if ( host- > complete_what = = COMPLETION_CMDSENT)
        {
            host- > status = "ok: command sent" ;
            goto close_transfer;
        }

        mci_cclear | = S3C2410_SDICMDSTAT_CMDSENT;
    }

    //收到命令响应,CRC校验失败
    if ( mci_csta & S3C2410_SDICMDSTAT_CRCFAIL)
    {
        if ( cmd- > flags & MMC_RSP_CRC)
        {
            if ( host- > mrq- > cmd- > flags & MMC_RSP_136)
            {
                dbg( host, dbg_irq, "fixup: ignore CRC fail with long rsp/n" ) ;
            } else {
                
/* note, we used to fail the transfer
                 * here, but it seems that this is just
                 * the hardware getting it wrong.
                 *
                 * cmd->error = -EILSEQ;
                 * host->status = "error: bad command crc";
                 * goto fail_transfer;
                */

            }
        }

        mci_cclear | = S3C2410_SDICMDSTAT_CRCFAIL;
    }

    //收到命令响应,响应结束
    if ( mci_csta & S3C2410_SDICMDSTAT_RSPFIN)
    {
        //如果当前任务是完成,接收命令响应
        if ( host- > complete_what = = COMPLETION_RSPFIN)
        {
            host- > status = "ok: command response received" ;
            goto close_transfer;
//停止传输

        }
        
        //当前任务是完成数据传输和接收命令响应
        if ( host- > complete_what = = COMPLETION_XFERFINISH_RSPFIN)
            // 标记当前任务为完成数据传输
            host- > complete_what = COMPLETION_XFERFINISH;

        //清除收到命令响应标志
        mci_cclear | = S3C2410_SDICMDSTAT_RSPFIN;
    }

    if ( ! cmd- > data)
        goto clear_status_bits;

    //FIFO失败
    if ( mci_fsta & S3C2440_SDIFSTA_FIFOFAIL)
    {
        dbg( host, dbg_err, "FIFO failure/n" ) ;
        host- > mrq- > data- > error = - EILSEQ;
        host- > status = "error: 2440 fifo failure" ;
        goto fail_transfer;
    }

    //接收CRC错误
    if ( mci_dsta & S3C2410_SDIDSTA_RXCRCFAIL)
    {
        dbg( host, dbg_err, "bad data crc (outgoing)/n" ) ;
        cmd- > data- > error = - EILSEQ;
        host- > status = "error: bad data crc (outgoing)" ;
        goto fail_transfer;
    }

    //发送数据后,CRC状态错误
    if ( mci_dsta & S3C2410_SDIDSTA_CRCFAIL)
    {
        dbg( host, dbg_err, "bad data crc (incoming)/n" ) ;
        cmd- > data- > error = - EILSEQ;
        host- > status = "error: bad data crc (incoming)" ;
        goto fail_transfer;
    }

    //数据/忙接收超时
    if ( mci_dsta & S3C2410_SDIDSTA_DATATIMEOUT)
    {
        dbg( host, dbg_err, "data timeout/n" ) ;
        cmd- > data- > error = - ETIMEDOUT;
        host- > status = "error: data timeout" ;
        goto fail_transfer;
    }

    //数据计数器为0,和本次请求的全部数据传输结束
    if ( mci_dsta & S3C2410_SDIDSTA_XFERFINISH)
    {
        //如果当前任务是完成数据传输则结束数据传输
        if ( host- > complete_what = = COMPLETION_XFERFINISH)
        {
            host- > status = "ok: data transfer completed" ;
            goto close_transfer;
        }

        //如果当前任务是完成数据传输和接收命令响应
        if ( host- > complete_what = = COMPLETION_XFERFINISH_RSPFIN)
            //标记当前任务为完成 接收命令响应
            host- > complete_what = COMPLETION_RSPFIN;

        //清除数据传输完标志
        mci_dclear | = S3C2410_SDIDSTA_XFERFINISH;
    }

 //清除状态字
clear_status_bits:
    writel( mci_cclear, host- > base + S3C2410_SDICMDSTAT) ;
    writel( mci_dclear, host- > base + S3C2410_SDIDSTA) ;

    goto irq_out;

// 传输失败
fail_transfer:
    host- > pio_active = XFER_NONE;

//传输结束
close_transfer:
    host- > complete_what = COMPLETION_FINALIZE;

    clear_imask( host) ;
    tasklet_schedule( & host- > pio_tasklet) ;

    goto irq_out;

irq_out:
    dbg( host, dbg_irq, "csta:0x%08x dsta:0x%08x fsta:0x%08x dcnt:0x%08x status:%s./n" ,
     mci_csta, mci_dsta, mci_fsta, mci_dcnt, host- > status) ;

    //开中断并恢复状态字
    spin_unlock_irqrestore( & host- > complete_lock, iflags) ;
    return IRQ_HANDLED;
}

//MMC/SD卡 中断底半部程序
static void pio_tasklet( unsigned long data)
{
    //data 参数是在s3cmci_probe中的tasklet_init的时候传递过来的
    struct s3cmci_host * host = ( struct s3cmci_host * ) data;

    //在 执行底半部程序的时候屏蔽中断
    disable_irq( host- > irq) ;

    //判断如果当前存在FIFO的写状态,则进行FIFO的写操作
    if ( host- > pio_active = = XFER_WRITE)
        do_pio_write( host) ;

    //判断如果当前存在FIFO的读状态,则进行FIFO的读操作
    if ( host- > pio_active = = XFER_READ)
        do_pio_read( host) ;

    //判断如果当前的请求状态为完成状态,则准备进行完成请求处理
    if ( host- > complete_what = = COMPLETION_FINALIZE)
    {
        //清空中断屏蔽寄存器
        clear_imask( host) ;
        
        //FIFO状态验证
        if ( host- > pio_active ! = XFER_NONE)
        {
            if ( host- > mrq- > data)
                host- > mrq- > data- > error = - EINVAL;
        }

        //完成请求处理
        finalize_request( host) ;
    }
    else
        //当前请求状态为其他,则使能中断继续请求处理
        enable_irq( host- > irq) ;
}

//完成请求处理
static void finalize_request( struct s3cmci_host * host)
{
    struct mmc_request * mrq = host- > mrq;
    struct mmc_command * cmd = host- > cmd_is_stop ? mrq- > stop : mrq- > cmd;
    int debug_as_failure = 0;

    //如果当前请求状态不为完成状态,则为错误
    if ( host- > complete_what ! = COMPLETION_FINALIZE)
        return ;

    if ( ! mrq)
        return ;

    if ( cmd- > data & & ( cmd- > error = = 0) & & ( cmd- > data- > error = = 0) )
    {
        if ( host- > dodma & & ( ! host- > dma_complete) )
        {
            dbg( host, dbg_dma, "DMA Missing!/n" ) ;
            return ;
        }
    }

    //读响应寄存器
    cmd- > resp[ 0] = readl( host- > base + S3C2410_SDIRSP0) ;
    cmd- > resp[ 1] = readl( host- > base + S3C2410_SDIRSP1) ;
    cmd- > resp[ 2] = readl( host- > base + S3C2410_SDIRSP2) ;
    cmd- > resp[ 3] = readl( host- > base + S3C2410_SDIRSP3) ;

    writel( host- > prescaler, host- > base + S3C2410_SDIPRE) ;

    if ( cmd- > error )
        debug_as_failure = 1;

    if ( cmd- > data & & cmd- > data- > error )
        debug_as_failure = 1;

    dbg_dumpcmd( host, cmd, debug_as_failure) ;

    //清空命令参数、数据配置、命令配置、中断屏蔽寄存器
    writel( 0, host- > base + S3C2410_SDICMDARG) ;
    writel( S3C2410_SDIDCON_STOP, host- > base + S3C2410_SDIDCON) ;
    writel( 0, host- > base + S3C2410_SDICMDCON) ;
    writel( 0, host- > base + host- > sdiimsk) ;

    if ( cmd- > data & & cmd- > error )
        cmd- > data- > error = cmd- > error ;

    //有数据请求,有传输停止命令,数据传输命令已发送
    if ( cmd- > data & & cmd- > data- > stop & & ( ! host- > cmd_is_stop) )
    {
        host- > cmd_is_stop = 1;
        s3cmci_send_request( host- > mmc) ;
//传输停止命令

        return ;
    }

    if ( ! mrq- > data)
        goto request_done;

    //计算已传 输的数据量
    if ( mrq- > data- > error = = 0)
    {
        mrq- > data- > bytes_xfered = ( mrq- > data- > blocks * mrq- > data- > blksz) ;
    }
    else
    {
        mrq- > data- > bytes_xfered = 0;
    }

    if ( mrq- > data- > error ! = 0)
    {
        if ( host- > dodma)
            s3c2410_dma_ctrl( host- > dma, S3C2410_DMAOP_FLUSH) ;

        // 清除和复位FIFO状态寄存器
        writel( S3C2440_SDIFSTA_FIFORESET | S3C2440_SDIFSTA_FIFOFAIL, host- > base + S3C2410_SDIFSTA) ;
    }

//完成请求
request_done:
    host- > complete_what = COMPLETION_NONE;
    host- > mrq = NULL ;
    mmc_request_done( host- > mmc, mrq) ;
}

 

 

转自:http://blog.chinaunix.net/u3/101649/showart_2246773.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值