天天看点

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

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用来标识这条命令是读还是写:

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

继续阅读