Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2974773
  • 博文数量: 401
  • 博客积分: 12926
  • 博客等级: 上将
  • 技术积分: 4588
  • 用 户 组: 普通用户
  • 注册时间: 2009-02-22 14:51
文章分类

全部博文(401)

文章存档

2015年(16)

2014年(4)

2013年(12)

2012年(82)

2011年(98)

2010年(112)

2009年(77)

分类: LINUX

2010-11-15 10:15:12

//数据请求处理设置,主要是数据控制寄存器的配置
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 (= 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 *= (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;
}



阅读(1531) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~