一.前言
SD卡的大名是耳熟能详,但是SDIO总线确是不为人解,不过说起他的近亲SPI就知道了。我们这里主要是理解SDIO总线,并不去理解SPI总线。也许大家会畏惧其庞大的代码,其实我们并不需要详细理解其具体的实现,我们需要理解其架构。
二.主机(host)
在linux2.6.28中,在sdhci-s3c.c实现对主机的sdhci的驱动,而其设备的添加则在smdk6410_devices中。
在sdhci-s3c.c的sdhci_s3c_probe实现对主机端的驱,对于里面具体的一些实现我们就不看了,主要是看其中通用的code。
static int __devinit sdhci_s3c_probe(struct platform_device *pdev)
{
struct sdhci_host *host;
struct sdhci_s3c *sc;
host = sdhci_alloc_host(dev, sizeof(struct sdhci_s3c));
sc = sdhci_priv(host);
sdhci_add_host(host);
}
对于上面的函数,主要就是关心上面的几段代码。首先,来看sdhci_host这个结构体,这个是个很重要的东西,这个将会贯穿整个驱动。
struct sdhci_host {
/* Data set by hardware interface driver */
const char *hw_name; /* Hardware bus name */
unsigned int quirks; /* Deviations from spec. */
int irq; /* Device IRQ */
void __iomem * ioaddr; /* Mapped address */
const struct sdhci_ops *ops; /* Low level hw interface */
struct mmc_host *mmc; /* MMC structure */
u64 dma_mask; /* custom DMA mask */
spinlock_t lock; /* Mutex */
int flags; /* Host attributes */
unsigned int version; /* SDHCI spec. version */
unsigned int max_clk; /* Max possible freq (MHz) */
unsigned int timeout_clk; /* Timeout freq (KHz) */
unsigned int clock; /* Current clock (MHz) */
unsigned short power; /* Current voltage */
struct mmc_request *mrq; /* Current request */
struct mmc_command *cmd; /* Current command */
struct mmc_data *data; /* Current data request */
unsigned int data_early:1; /* Data finished before cmd */
struct sg_mapping_iter sg_miter; /* SG state for PIO */
unsigned int blocks; /* remaining PIO blocks */
int sg_count; /* Mapped sg entries */
u8 *adma_desc; /* ADMA descriptor table */
u8 *align_buffer; /* Bounce buffer */
dma_addr_t adma_addr; /* Mapped ADMA descr. table */
dma_addr_t align_addr; /* Mapped bounce buffer */
struct tasklet_struct card_tasklet; /* Tasklet structures */
struct tasklet_struct finish_tasklet;
struct timer_list timer; /* Timer for timeouts */
unsigned long private[0] ____cacheline_aligned;
};
上面的结构体主要需要关注mmc_host和两个tasklet_struct。
struct mmc_host {
struct device *parent;
struct device class_dev;
int index;
const struct mmc_host_ops *ops;
unsigned int f_min;
unsigned int f_max;
u32 ocr_avail;
unsigned long caps; /* Host capabilities */
/* host specific block data */
unsigned int max_seg_size; /* see blk_queue_max_segment_size */
unsigned short max_hw_segs; /* see blk_queue_max_hw_segments */
unsigned short max_phys_segs; /* see blk_queue_max_phys_segments */
unsigned short unused;
unsigned int max_req_size; /* maximum number of bytes in one req */
unsigned int max_blk_size; /* maximum size of one mmc block */
unsigned int max_blk_count; /* maximum number of blocks in one req */
/* private data */
spinlock_t lock; /* lock for claim and bus ops */
struct mmc_ios ios; /* current io bus settings */
u32 ocr; /* the current OCR setting */
/* group bitfields together to minimize padding */
unsigned int use_spi_crc:1;
unsigned int claimed:1; /* host exclusively claimed */
unsigned int bus_dead:1; /* bus has been released */
struct mmc_card *card; /* device attached to this host */
wait_queue_head_t wq;
struct delayed_work detect;
const struct mmc_bus_ops *bus_ops; /* current bus driver */
unsigned int bus_refs; /* reference counter */
unsigned int sdio_irqs;
struct task_struct *sdio_irq_thread;
atomic_t sdio_irq_thread_abort;
struct dentry *debugfs_root;
unsigned long private[0] ____cacheline_aligned;
};
Mmc_host可以认为是总线设备的一种封装。
继续上面的源码在sdhci_alloc_host->mmc_alloc_host中,会发现我们的申请的mmc_host设备指向一个特殊的类mmc_host_class,在该类中并没有什么特殊的function只有一个mmc_host_classdev_release,从下面的code可知主要是释放在mmc_alloc_host中开辟的空间。为什么需要这样来设计就不知道???
static void mmc_host_classdev_release(struct device *dev)
{
struct mmc_host *host = cls_dev_to_mmc_host(dev);
kfree(host);
}
下面来看sdhci_add_host这个函数。
int sdhci_add_host(struct sdhci_host *host)
{
struct mmc_host *mmc;
mmc = host->mmc;
tasklet_init(&host->card_tasklet,sdhci_tasklet_card, (unsigned long)host);
tasklet_init(&host->finish_tasklet,sdhci_tasklet_finish, (unsigned long)host);
setup_timer(&host->timer, sdhci_timeout_timer, (unsigned long)host);
request_irq(host->irq, sdhci_irq, IRQF_SHARED,mmc_hostname(mmc), host);
mmc_add_host(mmc);
return 0;
}
也许大家很困惑,为什么只关注这几行code,在该函数中主要是集中在mmc_host成员的初始化赋值。由于中断中需要处理的内容较多故采用底半部的办法来处理部分内容。
card_tasklet用于处理MMC插槽上的状态变化。finish_tasklet用于命令传输完成后的处理。Timer用于等待硬件中断。Irq则是SDHCI的中断处理。
将host添加入mmc管理,其本质即是添加host的class_dev
int mmc_add_host(struct mmc_host *host)
{
device_add(&host->class_dev);
mmc_start_host(host);
return 0;
}
void mmc_start_host(struct mmc_host *host) –启动host
{
mmc_power_off(host); --关闭MMC
if (host->caps & MMC_CAP_BOOT_ONTHEFLY)
mmc_rescan(&host->detect.work); --重新扫描
else
mmc_detect_change(host, 0); --处理状态改变
}
void mmc_detect_change(struct mmc_host *host, unsigned long delay)
{
mmc_schedule_delayed_work(&host->detect, delay);
}
在mmc_alloc_host时,存在下面一行code
INIT_DELAYED_WORK(&host->detect, mmc_rescan);
到此就会发现mmc_start_host一定会执行mmc_rescan只不过一个是延时后执行,一个是没有延时执行。
void mmc_rescan(struct work_struct *work)
{
struct mmc_host *host =container_of(work, struct mmc_host, detect.work);
u32 ocr;
int err;
mmc_bus_get(host);
if (host->bus_ops == NULL) { --如果为第一次扫描总线上设备。
/*
* Only we can add a new handler, so it's safe torelease the lock here.
*/
mmc_bus_put(host);
if (host->ops->get_cd && host->ops->get_cd(host) == 0)
goto out;
mmc_claim_host(host); --申请一个host
mmc_power_up(host); --开启host电源
mmc_go_idle(host); --设置host为idle模式
mmc_send_if_cond(host, host->ocr_avail); --是用于验证SD卡接口操作状态的有效性命令(CMD8)。如果 SD_SEND_IF_COND指示为符合SD2.0标准的卡,则设置操作状态寄存器ocrbit30指示能 够处理块地址SDHC卡
/*
* First we search for SDIO...
*/
err = mmc_send_io_op_cond(host, 0, &ocr);
if (!err) {
if (mmc_attach_sdio(host, ocr))
mmc_power_off(host);
goto out;
}
/*
* ...then normal SD...
*/
err = mmc_send_app_op_cond(host, 0, &ocr);
if (!err) {
if (mmc_attach_sd(host, ocr))
mmc_power_off(host);
goto out;
}
/*
* ...and finally MMC.
*/
err = mmc_send_op_cond(host, 0, &ocr);
if (!err) {
if (mmc_attach_mmc(host, ocr))
mmc_power_off(host);
goto out;
}
mmc_release_host(host);
mmc_power_off(host);
} else {
if (host->bus_ops->detect && !host->bus_dead)
host->bus_ops->detect(host);
mmc_bus_put(host);
}
out:
if (host->caps & MMC_CAP_NEEDS_POLL)
mmc_schedule_delayed_work(&host->detect, HZ);
}
那先来看mmc_send_if_cond实现,从字面意思上看该函数就是发送一个SD标准命令,亦如usb的标准命令一样。
在这里不得不先说明一点就是在SD子系统中,所有的数据传输均是由host发起。Host发送完一命令则会等待中断的产生,在这里采用完成量来实现进程的阻塞。
void mmc_wait_for_req(struct mmc_host *host, struct mmc_request *mrq)
{
DECLARE_COMPLETION_ONSTACK(complete);
mrq->done_data = &complete;
mrq->done = mmc_wait_done;
mmc_start_request(host, mrq); --开始request
wait_for_completion(&complete);
}
mmc_start_request->host->ops->request(即sdhci_request)
static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq)
{
struct sdhci_host *host;
unsigned long flags;
host = mmc_priv(mmc);
spin_lock_irqsave(&host->lock, flags);
host->mrq = mrq;
if ((mmc->caps & MMC_CAP_ON_BOARD) || (host->flags & SDHCI_DEVICE_ALIVE))
sdhci_send_command(host, mrq->cmd);
else {
if (!(readl(host->ioaddr + SDHCI_PRESENT_STATE) & SDHCI_CARD_PRESENT)
|| (host->flags & SDHCI_DEVICE_DEAD)) {
host->mrq->cmd->error = -ENOMEDIUM;
tasklet_schedule(&host->finish_tasklet);
} else
sdhci_send_command(host, mrq->cmd);
}
mmiowb();
spin_unlock_irqrestore(&host->lock, flags);
}
sdhci_request-> sdhci_send_command发送命令。
当命令传输完成系统调用中断处理函数sdhci_irq。在其中进行中断完成后的处理。