Linux 2.6.38
S3C2440
通过“SDIO驱动(10)Host的operations实现”的s3cmci_send_command函数知道了命令的发送方式,接下来分析数据的发送实现。本来吧,无论命令还是数据,它们本质上都是数据,之所以分开说,是因为硬件实现上它们就是独立的,命令和数据分别有自己的寄存器管理、控制。
接着s3cmci_send_request函数分析,18行s3cmci_setup_data:
static int s3cmci_setup_data(struct s3cmci_host *host, struct mmc_data *data)
{
u32 dcon, imsk, stoptries = 3;
/* write DCON register */
if (!data) {
writel(0, host->base + S3C2410_SDIDCON);
return 0;
}
if ((data->blksz & 3) != 0) {
/* We cannot deal with unaligned blocks with more than
* one block being transfered. */
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);
s3cmci_reset(host);
if ((stoptries--) == 0) {
dbg_dumpregs(host, "DRF");
return -EINVAL;
}
}
dcon = data->blocks & S3C2410_SDIDCON_BLKNUM_MASK;
if (s3cmci_host_usedma(host))
dcon |= S3C2410_SDIDCON_DMAEN;
if (host->bus_width == MMC_BUS_WIDTH_4)
dcon |= S3C2410_SDIDCON_WIDEBUS;
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;
}
if (host->is2440) {
dcon |= S3C2440_SDIDCON_DS_WORD;
dcon |= S3C2440_SDIDCON_DATSTART;
}
writel(dcon, host->base + S3C2410_SDIDCON);
/* write BSIZE register */
writel(data->blksz, host->base + S3C2410_SDIBSIZE);
/* add to IMASK register */
imsk = S3C2410_SDIIMSK_FIFOFAIL | S3C2410_SDIIMSK_DATACRC |
S3C2410_SDIIMSK_DATATIMEOUT | S3C2410_SDIIMSK_DATAFINISH;
enable_imask(host, imsk);
/* write TIMER register */
if (host->is2440) {
writel(0x007FFFFF, host->base + S3C2410_SDITIMER);
} else {
writel(0x0000FFFF, host->base + S3C2410_SDITIMER);
/* FIX: set slow clock to prevent timeouts on read */
if (data->flags & MMC_DATA_READ)
writel(0xFF, host->base + S3C2410_SDIPRE);
}
return 0;
}
s3cmci_setup_data函数用来为数据发送准备好所需环境。
7~10行,写代码的时候,函数开头往往需要大段大段的参数检查代码,这样使函数显得冗长而又失去了简洁性;但是,参数检测又是不得不做的工作,尤其对于涉及指针、
内存的参数,忘记检查就意味着系统崩溃。有的情况下缺憾无处不在啊,所以说:美中不足今方信,纵然是齐眉举案,到底意难平!这里如果是空指针说明没有数据需要发送,直接撤了。还记得,在调用s3cmci_setup_data之前有对传入的参数进行过检查;这里依旧检查一遍,用来预防其他没有提前检测参数的调用可能产生的问题。
12~20行,如果数据的blocks是大于1的,那么传输的数据每一个block的block size必须满足4字节对齐。
22~35行,S3C2410_SDIDSTA_TXDATAON正在处理数据发送;S3C2410_SDIDSTA_RXDATAON正在处理数据接收。while循环:如果有数据正待处理,则启动处理流程(28行),然后复位整个sd/mmc模块。
37行,数据控制寄存器(SDIDCON)的数据块数占12位,所以这里必须限制在0xFFF以内。
39~63行,配置SDIDCON寄存器。
67行,配置块大小寄存器(SDIBSIZE),之前说过块大小必须4字节对齐。
69~73行,配置我们关心的中断事件:FIFO异常中断(S3C2410_SDIIMSK_FIFOFAIL)、数据CRC校验异常中断(S3C2410_SDIIMSK_DATACRC)、数据接收超时中断(S3C2410_SDIIMSK_DATATIMEOUT)、数据计数器位0中断(数据发送/接收完成S3C2410_SDIIMSK_DATAFINISH)。
77~85行,数据处理、busy状态的定时器,即数据必须在定时器设置的时间内完成处理、如果host处于busy状态则停留时间不能超过设置时间。
s3cmci_send_request函数22~29行,环境准备异常直接返回错误信息。
31~34行,启动数据的发送。在计算机的世界中,数据传输相对于CPU频率是很慢的,如果由CPU负责数据传输其效率肯定大打折扣;基于这种显示,一种专门用于数据传输的硬件模块产生了,即DMA(Direct Memory Access)。这里就是询问host,是打算用DMA方式就执行s3cmci_prepare_dma;否则s3cmci_prepare_pio。DMA另外再说,我们看s3cmci_prepare_pio:
static int s3cmci_prepare_pio(struct s3cmci_host *host, struct mmc_data *data)
{
int rw = (data->flags & MMC_DATA_WRITE) ? 1 : 0;
BUG_ON((data->flags & BOTH_DIR) == BOTH_DIR);
host->pio_sgptr = 0;
host->pio_bytes = 0;
host->pio_count = 0;
host->pio_active = rw ? XFER_WRITE : XFER_READ;
if (rw) {
do_pio_write(host);
enable_imask(host, S3C2410_SDIIMSK_TXFIFOHALF);
} else {
enable_imask(host, S3C2410_SDIIMSK_RXFIFOHALF
| S3C2410_SDIIMSK_RXFIFOLAST);
}
return 0;
}
又是不干事的家伙。这里主要回想下data->flags的MMC_DATA_WRITE/MMC_DATA_READ由谁在什么时候设置的?在构建命令的时候设置读写标志,还记得命令的组成?其中有1个bit用来标识这条命令是读还是写:
14行,如果发送数据的FIFO满了一半,产生一个中断。现在看来实际发送数据的就是do_pio_write(host)了:
static void do_pio_write(struct s3cmci_host *host)8行to_ptr为数据寄存器(SDIDAT)的地址,当然这是经过映射之后的地址。
{
void __iomem *to_ptr;
int res;
u32 fifo;
u32 *ptr;
to_ptr = host->base + host->sdidata;
while ((fifo = fifo_free(host)) > 3) {
if (!host->pio_bytes) {
res = get_data_buffer(host, &host->pio_bytes,
&host->pio_ptr);
if (res) {
dbg(host, dbg_pio,
"pio_write(): complete (no more data).\n");
host->pio_active = XFER_NONE;
return;
}
dbg(host, dbg_pio,
"pio_write(): new source: [%i]@[%p]\n",
host->pio_bytes, host->pio_ptr);
}
/* If we have reached the end of the block, we have to
* write exactly the remaining number of bytes. If we
* in the middle of the block, we have to write full
* words, so round down to an even multiple of 4. */
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--)
writel(*ptr++, to_ptr);
host->pio_ptr = ptr;
}
enable_imask(host, S3C2410_SDIIMSK_TXFIFOHALF);
}
11~26行的if分支用来把数据放到host->pio_ptr指向的内存区域,32~40行获取需要写到FIFO的数据个数。由2440的datasheet我们知道,数据的传递方向是:写数据到发送寄存器->FIFO->移位寄存器,移位寄存器最终把数据移到数据线上,在时钟信号有效时,数据发送出去。
这里需要清楚的是:fifo、host->pio_bytes变量的单位是byte,寄存器位宽SDIDAT都是32位即4bytes,所以40行最终计算时需要右移2位。
42、43行,写数据到SDIDAT寄存器,一次4个字节。44行更新当前数据指针,指向下一个需要发送的字节。循环以上步骤,直到数据发送完成:“pio_write(): complete (no more data)”。