sdhc框架概述

Secure Digital(SD) Host Controller一般挂载sd和sdio设备,隶属于mmc子系统,其核心其实跟mmc子系统没什么差异;从我们先前的经验看,总线总是作为数据流通的的载体,那么这个载体我们总是分三段来看:设备--核心--主机;也可以简化成一句话:"设备在收发数据的时候,会调用核心提供的接口,这个接口会去匹配到设备挂载的具体控制器的具体收发的接口实现"。

那我们还是按照先前这个并不成熟的总结依然往下看

shdci主机驱动

这是一个简化的主机驱动的流程,下面我们会按这个依次往下看

asr_sdhci_probe
	sdhci_pltfm_init
		irq = platform_get_irq(pdev, 0);
		sdhci_alloc_host
			mmc_alloc_host
				INIT_DELAYED_WORK(&host->detect, mmc_rescan);
			mmc->ops = sdhci_ops
				.request = sdhci_request
	sdhci_add_host
		__sdhci_add_host
			request_threaded_irq(host->irq, sdhci_irq, sdhci_thread_irq,IRQF_SHARED,mmc_hostname(mmc), host);
				sdhci_thread_irq
					_mmc_detect_change
			mmc_add_host
				mmc_start_host
					mmc_gpiod_request_cd_irq(host);
					_mmc_detect_change(host, 0, false);
						mmc_schedule_delayed_work(&host->detect, delay);

 初始化主机

首先对平台数据初始化,比如拿中断,申请一个主机,将操作函数放进这个主机

struct sdhci_host *sdhci_alloc_host(struct device *dev,
        size_t priv_size)
{
        struct mmc_host *mmc;
        struct sdhci_host *host;

        WARN_ON(dev == NULL);

        mmc = mmc_alloc_host(sizeof(struct sdhci_host) + priv_size, dev);
        if (!mmc)
                return ERR_PTR(-ENOMEM);

        host = mmc_priv(mmc);
        host->mmc = mmc;
        host->mmc_host_ops = sdhci_ops;
        mmc->ops = &host->mmc_host_ops;

        host->flags = SDHCI_SIGNALING_330;

        host->cqe_ier     = SDHCI_CQE_INT_MASK;
        host->cqe_err_ier = SDHCI_CQE_INT_ERR_MASK;

        host->tuning_delay = -1;
        host->tuning_loop_count = MAX_TUNING_LOOP;

        host->sdma_boundary = SDHCI_DEFAULT_BOUNDARY_ARG;

        /*
         * The DMA table descriptor count is calculated as the maximum
         * number of segments times 2, to allow for an alignment
         * descriptor for each segment, plus 1 for a nop end descriptor.
         */
        host->adma_table_cnt = SDHCI_MAX_SEGS * 2 + 1;
        host->max_adma = 65536;

        return host;
}

 添加主机

将初始主机拿到的中断号,来注册中断:1.以处理各中断事件(sdhci_irq);2。处理request的完成情况和插拔设备后的探测操作(sdhci_thread_irq);最后就是添加主机,和使能卡探测

int __sdhci_add_host(struct sdhci_host *host)
{
	unsigned int flags = WQ_UNBOUND | WQ_MEM_RECLAIM | WQ_HIGHPRI;
	struct mmc_host *mmc = host->mmc;
	int ret;

	if ((mmc->caps2 & MMC_CAP2_CQE) &&
	    (host->quirks & SDHCI_QUIRK_BROKEN_CQE)) {
		mmc->caps2 &= ~MMC_CAP2_CQE;
		mmc->cqe_ops = NULL;
	}

	host->complete_wq = alloc_workqueue("sdhci", flags, 0);
	if (!host->complete_wq)
		return -ENOMEM;

	INIT_WORK(&host->complete_work, sdhci_complete_work);

	timer_setup(&host->timer, sdhci_timeout_timer, 0);
	timer_setup(&host->data_timer, sdhci_timeout_data_timer, 0);

	init_waitqueue_head(&host->buf_ready_int);

	sdhci_init(host, 0);

	ret = request_threaded_irq(host->irq, sdhci_irq, sdhci_thread_irq,
				   IRQF_SHARED,	mmc_hostname(mmc), host);
	if (ret) {
		pr_err("%s: Failed to request IRQ %d: %d\n",
		       mmc_hostname(mmc), host->irq, ret);
		goto unwq;
	}

	ret = sdhci_led_register(host);
	if (ret) {
		pr_err("%s: Failed to register LED device: %d\n",
		       mmc_hostname(mmc), ret);
		goto unirq;
	}

	ret = mmc_add_host(mmc);
	if (ret)
		goto unled;

	pr_info("%s: SDHCI controller on %s [%s] using %s\n",
		mmc_hostname(mmc), host->hw_name, dev_name(mmc_dev(mmc)),
		host->use_external_dma ? "External DMA" :
		(host->flags & SDHCI_USE_ADMA) ?
		(host->flags & SDHCI_USE_64_BIT_DMA) ? "ADMA 64-bit" : "ADMA" :
		(host->flags & SDHCI_USE_SDMA) ? "DMA" : "PIO");

	sdhci_enable_card_detection(host);

	return 0;

unled:
	sdhci_led_unregister(host);
unirq:
	sdhci_reset_for_all(host);
	sdhci_writel(host, 0, SDHCI_INT_ENABLE);
	sdhci_writel(host, 0, SDHCI_SIGNAL_ENABLE);
	free_irq(host->irq, host);
unwq:
	destroy_workqueue(host->complete_wq);

	return ret;
}

 创建设备

 然后将设备探测函数,作为一个工作,待适合的时机(中断或者poll)的来调度一次,以生成设备;可以看到如果探测到sdio设备,就会调用sdio_init_func读取设备的vendor id,然后添加这个card设备

mmc_rescan
	mmc_rescan_try_freq
		mmc_attach_sdio
			sdio_init_func
				mmc_add_card
					device_add

static int sdio_init_func(struct mmc_card *card, unsigned int fn)
{
	int ret;
	struct sdio_func *func;

	if (WARN_ON(fn > SDIO_MAX_FUNCS))
		return -EINVAL;

	func = sdio_alloc_func(card);
	if (IS_ERR(func))
		return PTR_ERR(func);

	func->num = fn;

	if (!(card->quirks & MMC_QUIRK_NONSTD_SDIO)) {
		ret = sdio_read_fbr(func);
		if (ret)
			goto fail;

		ret = sdio_read_func_cis(func);
		if (ret)
			goto fail;
	} else {
		func->vendor = func->card->cis.vendor;
		func->device = func->card->cis.device;
		func->max_blksize = func->card->cis.blksize;
	}

	card->sdio_func[fn - 1] = func;

	return 0;

fail:
	/*
	 * It is okay to remove the function here even though we hold
	 * the host lock as we haven't registered the device yet.
	 */
	sdio_remove_func(func);
	return ret;
}

 数据收发的具体实现

sdhci_request就是mmc->ops里request的具体实现,它包含了普通通道和dma通道的两种具体实现

sdhci_request
	sdhci_data_irq
		sdhci_transfer_pio //普通通道
			sdhci_write_block_pio/sdhci_read_block_pio
	sdhci_request_done //使能dma就会在这等待完成传输

普通写接口,就是通过写寄存器的方式,将主机的sg聚合的数据按固定字节大小,依次发送

static void sdhci_write_block_pio(struct sdhci_host *host)
{
	unsigned long flags;
	size_t blksize, len, chunk;
	u32 scratch;
	u8 *buf;

	DBG("PIO writing\n");

	blksize = host->data->blksz;
	chunk = 0;
	scratch = 0;

	local_irq_save(flags);

	while (blksize) {
		BUG_ON(!sg_miter_next(&host->sg_miter));

		len = min(host->sg_miter.length, blksize);

		blksize -= len;
		host->sg_miter.consumed = len;

		buf = host->sg_miter.addr;

		while (len) {
			scratch |= (u32)*buf << (chunk * 8);

			buf++;
			chunk++;
			len--;

			if ((chunk == 4) || ((len == 0) && (blksize == 0))) {
				sdhci_writel(host, scratch, SDHCI_BUFFER);
				chunk = 0;
				scratch = 0;
			}
		}
	}

	sg_miter_stop(&host->sg_miter);

	local_irq_restore(flags);
}

使用dma,就使用sdhci_request_done来确保sg聚合的buffer以通过dma传输完成

static bool sdhci_request_done(struct sdhci_host *host)
{
	unsigned long flags;
	struct mmc_request *mrq;
	int i;

	spin_lock_irqsave(&host->lock, flags);

	for (i = 0; i < SDHCI_MAX_MRQS; i++) {
		mrq = host->mrqs_done[i];
		if (mrq)
			break;
	}

	if (!mrq) {
		spin_unlock_irqrestore(&host->lock, flags);
		return true;
	}

	/*
	 * The controller needs a reset of internal state machines
	 * upon error conditions.
	 */
	if (sdhci_needs_reset(host, mrq)) {
		/*
		 * Do not finish until command and data lines are available for
		 * reset. Note there can only be one other mrq, so it cannot
		 * also be in mrqs_done, otherwise host->cmd and host->data_cmd
		 * would both be null.
		 */
		if (host->cmd || host->data_cmd) {
			spin_unlock_irqrestore(&host->lock, flags);
			return true;
		}

		/* Some controllers need this kick or reset won't work here */
		if (host->quirks & SDHCI_QUIRK_CLOCK_BEFORE_RESET)
			/* This is to force an update */
			host->ops->set_clock(host, host->clock);

		/*
		 * Spec says we should do both at the same time, but Ricoh
		 * controllers do not like that.
		 */
		sdhci_do_reset(host, SDHCI_RESET_CMD);
		sdhci_do_reset(host, SDHCI_RESET_DATA);

		host->pending_reset = false;
	}

	/*
	 * Always unmap the data buffers if they were mapped by
	 * sdhci_prepare_data() whenever we finish with a request.
	 * This avoids leaking DMA mappings on error.
	 */
	if (host->flags & SDHCI_REQ_USE_DMA) {
		struct mmc_data *data = mrq->data;

		if (data && data->host_cookie == COOKIE_MAPPED) {
			if (host->bounce_buffer) {
				/*
				 * On reads, copy the bounced data into the
				 * sglist
				 */
				if (mmc_get_dma_dir(data) == DMA_FROM_DEVICE) {
					unsigned int length = data->bytes_xfered;

					if (length > host->bounce_buffer_size) {
						pr_err("%s: bounce buffer is %u bytes but DMA claims to have transferred %u bytes\n",
						       mmc_hostname(host->mmc),
						       host->bounce_buffer_size,
						       data->bytes_xfered);
						/* Cap it down and continue */
						length = host->bounce_buffer_size;
					}
					dma_sync_single_for_cpu(
						host->mmc->parent,
						host->bounce_addr,
						host->bounce_buffer_size,
						DMA_FROM_DEVICE);
					sg_copy_from_buffer(data->sg,
						data->sg_len,
						host->bounce_buffer,
						length);
				} else {
					/* No copying, just switch ownership */
					dma_sync_single_for_cpu(
						host->mmc->parent,
						host->bounce_addr,
						host->bounce_buffer_size,
						mmc_get_dma_dir(data));
				}
			} else {
				/* Unmap the raw data */
				dma_unmap_sg(mmc_dev(host->mmc), data->sg,
					     data->sg_len,
					     mmc_get_dma_dir(data));
			}
			data->host_cookie = COOKIE_UNMAPPED;
		}
	}

	host->mrqs_done[i] = NULL;

	spin_unlock_irqrestore(&host->lock, flags);

	mmc_request_done(host->mmc, mrq);

	return false;
}

设备驱动

其实说的很复杂,设备驱动调用的这一个核心接口就可以看出,最上面的那句总结

sdio_memcpy_toio
	sdio_io_rw_ext_helper   
		mmc_io_rw_extended//这里填充sg
			mmc_wait_for_req
				mmc_start_request
					host->ops->request(host, mrq);

sdio_write_sg_extended//在这之前填充sg,并作为这个函数的参数,不调用sdio_memcpy_toio,将少一次拷贝,减少cpu利用率
	mmc_wait_for_req
		mmc_start_request
			host->ops->request(host, mrq);

 /drivers/mmc/core/sdio_io.c里通用的核心接口sdio_memcpy_toio,是在这个接口里面去填充sg,并最终调用控制器的ops的request来完成收发数据

nt mmc_io_rw_extended(struct mmc_card *card, int write, unsigned fn,
	unsigned addr, int incr_addr, u8 *buf, unsigned blocks, unsigned blksz)
{
	struct mmc_request mrq = {NULL};
	struct mmc_command cmd = {0};
	struct mmc_data data = {0};
	struct scatterlist sg, *sg_ptr;
	struct sg_table sgtable;
	unsigned int nents, left_size, i;
	unsigned int seg_size = card->host->max_seg_size;

	BUG_ON(!card);
	BUG_ON(fn > 7);
	WARN_ON(blksz == 0);

	/* sanity check */
	if (addr & ~0x1FFFF)
		return -EINVAL;

	mrq.cmd = &cmd;
	mrq.data = &data;

	cmd.opcode = SD_IO_RW_EXTENDED;
	cmd.arg = write ? 0x80000000 : 0x00000000;
	cmd.arg |= fn << 28;
	cmd.arg |= incr_addr ? 0x04000000 : 0x00000000;
	cmd.arg |= addr << 9;
	if (blocks == 0)
		cmd.arg |= (blksz == 512) ? 0 : blksz;	/* byte mode */
	else
		cmd.arg |= 0x08000000 | blocks;		/* block mode */
	cmd.flags = MMC_RSP_SPI_R5 | MMC_RSP_R5 | MMC_CMD_ADTC;
	cmd.retries = MMC_CMD_RETRIES;

	data.blksz = blksz;
	/* Code in host drivers/fwk assumes that "blocks" always is >=1 */
	data.blocks = blocks ? blocks : 1;
	data.flags = write ? MMC_DATA_WRITE : MMC_DATA_READ;

	left_size = data.blksz * data.blocks;
	nents = (left_size - 1) / seg_size + 1;
	if (nents > 1) {
		if (sg_alloc_table(&sgtable, nents, GFP_KERNEL))
			return -ENOMEM;

		data.sg = sgtable.sgl;
		data.sg_len = nents;

		for_each_sg(data.sg, sg_ptr, data.sg_len, i) {
			sg_set_page(sg_ptr, virt_to_page(buf + (i * seg_size)),
					min(seg_size, left_size),
					offset_in_page(buf + (i * seg_size)));
			left_size = left_size - seg_size;
		}
	} else {
		data.sg = &sg;
		data.sg_len = 1;

		sg_init_one(&sg, buf, left_size);
	}

	mmc_set_data_timeout(&data, card);

	mmc_wait_for_req(card->host, &mrq);

	if (nents > 1)
		sg_free_table(&sgtable);

	if (cmd.error)
		return cmd.error;
	if (data.error)
		return data.error;

	if (mmc_host_is_spi(card->host)) {
		/* host driver already reported errors */
	} else {
		if (cmd.resp[0] & R5_ERROR)
			return -EIO;
		if (cmd.resp[0] & R5_FUNCTION_NUMBER)
			return -EINVAL;
		if (cmd.resp[0] & R5_OUT_OF_RANGE)
			return -ERANGE;
	}

	return 0;
}

当然还有一种优化方式,设备驱动不调用sdio_memcpy_toio,而是先在设备驱动里去填充sg;这样将少了sdio_memcpy_toio的一次拷贝,以减少cpu利用率

int sdio_write_sg_extended(struct mmc_card *card, unsigned fn, unsigned int addr,
	struct tx_scatterlist *sg_list, int nents, int count, unsigned blksz)
{
	struct mmc_request mrq = {NULL};
	struct mmc_command cmd = {0};
	struct mmc_data data = {0};
	struct scatterlist *sg_ptr;
	struct sg_table sgtable;
	int i;
	int blkcnt;
	int size;
	int err;
	BUG_ON(!card);
	BUG_ON(fn > 7);
	if (sg_alloc_table(&sgtable, nents, GFP_KERNEL))
		return -ENOMEM;
	data.sg = sgtable.sgl;
	data.sg_len = nents;
	if (count < blksz) {
		size = 0;
		for_each_sg(data.sg, sg_ptr, data.sg_len, i) {
			sg_set_buf(sg_ptr, sg_list[i].buf, sg_list[i].len);
			size += sg_list[i].len;
			/* add for log */
			//printk(KERN_ERR "count=%d,blksz=%d size=%d : sg_list[%d].buf=%p,sg_list[%d].len=%d.\n",count,blksz,size,i,sg_list[i].buf,i,sg_list[i].len);
			/* add end */
		}
		BUG_ON(size != count);
		mrq.cmd = &cmd;
		mrq.data = &data;
		cmd.opcode = SD_IO_RW_EXTENDED;
		cmd.arg = 1 << 31;
		cmd.arg |= fn << 28;
		cmd.arg |= 0x04000000;	/* incr_addr */
		cmd.arg |= addr << 9;
		cmd.arg |= size; /* byte mode */
		cmd.flags = MMC_RSP_SPI_R5 | MMC_RSP_R5 | MMC_CMD_ADTC;
		data.blksz = size;
		data.blocks = 1;
		data.flags = MMC_DATA_WRITE;
		mmc_set_data_timeout(&data, card);
		mmc_wait_for_req(card->host, &mrq);
		if (cmd.error)
			err = cmd.error;
		else if (data.error)
			err = data.error;
		else if (cmd.resp[0] & R5_ERROR)
			err = -EIO;
		else if (cmd.resp[0] & R5_FUNCTION_NUMBER)
			err = -EINVAL;
		else if (cmd.resp[0] & R5_OUT_OF_RANGE)
			err = -ERANGE;
		else
			err = 0;
		sg_free_table(&sgtable);
		if (err) {
			printk(KERN_ERR "CMD53 write cmd_error = %d data_error=%d\n",
				cmd.error, data.error);
			return -1;
		}
		return 0;
	}

	size = 0;
	blkcnt = count / blksz;
	if (blkcnt) {
		for_each_sg(data.sg, sg_ptr, data.sg_len, i) {
			sg_set_buf(sg_ptr, sg_list[i].buf, sg_list[i].len);
			size += sg_list[i].len;
			/* add for log */
			//printk(KERN_ERR "count=%d,blksz=%d size=%d : sg_list[%d].buf=%p,sg_list[%d].len=%d.\n",count,blksz,size,i,sg_list[i].buf,i,sg_list[i].len);	
                        /* add end */
		}
		mrq.cmd = &cmd;
		mrq.data = &data;
		cmd.opcode = SD_IO_RW_EXTENDED;
		cmd.arg = 1 << 31;
		cmd.arg |= fn << 28;
		cmd.arg |= 0x04000000;	/* incr_addr */
		cmd.arg |= addr << 9;
		cmd.arg |= 0x08000000 | blkcnt; /* block mode */
		cmd.flags = MMC_RSP_SPI_R5 | MMC_RSP_R5 | MMC_CMD_ADTC;
		data.blksz = blksz;
		data.blocks = blkcnt;
		data.flags = MMC_DATA_WRITE;
		mmc_set_data_timeout(&data, card);
		mmc_wait_for_req(card->host, &mrq);
		if (cmd.error)
			err = cmd.error;
		else if (data.error)
			err = data.error;
		else if (cmd.resp[0] & R5_ERROR)
			err = -EIO;
		else if (cmd.resp[0] & R5_FUNCTION_NUMBER)
			err = -EINVAL;
		else if (cmd.resp[0] & R5_OUT_OF_RANGE)
			err = -ERANGE;
		else
			err = 0;
		sg_free_table(&sgtable);
		if (err) {
			printk(KERN_ERR "CMD53 write cmd_error = %d data_error=%d\n",
				cmd.error, data.error);
			return -1;
		}
		size -= blkcnt * blksz;
	}
	BUG_ON(size > 0);
	return 0;
}

  • 10
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值