SDIO驱动(11)Host是如何把数据发出去的

时间:2022-06-10 10:03:07
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->flagsMMC_DATA_WRITE/MMC_DATA_READ由谁在什么时候设置的?在构建命令的时候设置读写标志,还记得命令的组成?其中有1个bit用来标识这条命令是读还是写:

SDIO驱动(11)Host是如何把数据发出去的

14行,如果发送数据的FIFO满了一半,产生一个中断。现在看来实际发送数据的就是do_pio_write(host)了:

static void do_pio_write(struct s3cmci_host *host)
{
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);
}
8行to_ptr为数据寄存器(SDIDAT)的地址,当然这是经过映射之后的地址。

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)”。