分类: 嵌入式
2014-05-03 20:39:43
原文地址:S3C2440上MMC/SD卡驱动实例开发讲解(二) 作者:apple_guet
一、开发环境
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端口来判断卡是否存在
};
void (*request)(struct mmc_host *host, struct mmc_request *req); |
static void mmc_start_request(struct mmc_host *host, struct mmc_request *mrq) |
好了,我们开始分析每个接口函数的具体实现吧,从简单的开始吧。 判断卡是否存在,如下代码:
static int s3cmci_card_present(struct mmc_host *mmc) |
获取卡是否写有保护,其实实现跟卡检查类似,代码如下:
static int s3cmci_get_ro(struct mmc_host *mmc) |
static void s3cmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) |
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);//如果卡还存在则发出请求
}
}
//发送请求 |
//数据请求处理设置,主要是数据控制寄存器的配置 //复位整个MMC/SD时钟 con |= S3C2440_SDICON_SDRESET; |
//使用DMA传输数据方式,注意:这里就不讲如何使用DMA的具体细节了,以后再讲。 //返回值是传送的DMA缓冲区数,可能会小于sg_len,也就是说sg_len与dma_len可能是不同。 //sg_dma_address返回的是总线(DMA)的地址,sg_dma_len返回的是缓存区的长度
//DMA回调函数, 当一段数据传输完后该函数被调用 |
//使用FIFO传输数据方式。具体操作就是调用do_pio_write往FIFO中填充数据,当64字节的FIFO少于33字节时就会产生中断; |
//以上三段代码是对发送数据请求处理的,下面是发送命令请求
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);
}