转自:http://blog.csdn.net/armeasy/article/details/6035341
飞思卡尔开发板留有可插拨的SD卡卡槽,BSP包中提供了热插拨的检测机制。在sdhci_probe_slot函数中,gpio_sdhc_active函数初始化SD卡相关的GPIO口,包括SD卡检测脚的初始化。下面的程序实现SD卡检测中断号的申请,以及中断的触发方式:
host->detect_irq = platform_get_irq(pdev, 1);//申请卡检测中断号
if (!host->detect_irq)
{
host->flags &= ~SDHCI_CD_PRESENT;
if ((pdev->id >= 0) && (pdev->id < MXC_SDHCI_NUM))
mxc_fix_chips[pdev->id] = chip;
goto no_detect_irq;
}
do
{
ret = host->plat_data->status(host->mmc->parent);//获取SD卡的存在状态
if (ret)//根据当前SD卡存在状态设置卡检测中断触发类型
set_irq_type(host->detect_irq, IRQF_TRIGGER_FALLING);
else
set_irq_type(host->detect_irq, IRQF_TRIGGER_RISING);
} while (ret != host->plat_data->status(host->mmc->parent));
ret = host->plat_data->status(host->mmc->parent);
程序首先使用platform_get_irq函数申请一个中断号,然后读取SD卡的存在状态,如果SD卡不在卡槽内,则设置为下降沿触发,反之则设置为上升沿触发。这一点需要与硬件接口对应起来,开发板的SD卡硬件电路如下图所示:
SD1_DET脚即为SD卡检测脚,在没有卡存在的情况下,该脚被硬件上拉为高电平,插入卡后,该脚被卡槽直连到地。因此,在没有卡存在时,需要设置为下降沿触发,反之设置为上升沿触发。其实如果CPU支持双边沿触发,可以设置为双边沿触发,然后在中断处理函数中通过检测脚的电平判断是插入还是拨出。
紧接着程序通过下面的函数初始化自旋锁:
spin_lock_init(&host->lock);
使用INIT_WORK函数初始化卡检测函数的工作队列:
INIT_WORK(&host->cd_wq, esdhc_cd_callback);
使用request_irq函数申请卡检测中断:
if (host->detect_irq)//lqm changed
{
ret = request_irq(host->detect_irq, sdhci_cd_irq, 0,pdev->name, host);//响应检测SD卡中断
if (ret)//失败
goto out4;
}
sdhci_cd_irq函数为中断顶半部:
static irqreturn_t sdhci_cd_irq(int irq, void *dev_id)
{
struct sdhci_host *host = dev_id;
schedule_work(&host->cd_wq);
return IRQ_HANDLED;
}
当卡检测中断产生后,中断顶半部调用schedule_work函数调度工作队列执行。这里的传入参数host->cd_wq和前面工作队列初始化的第一个传入参数必须一致。工作队列初始化的第二个参数esdhc_cd_callback为卡检测中断的底半部,产生中断后需要做的工作全在这个函数里面。schedule_work执行后该函数马上会执行:
static void esdhc_cd_callback(struct work_struct *work)
{
unsigned long flags;
unsigned int cd_status = 0;
struct sdhci_host *host = container_of(work, struct sdhci_host, cd_wq);
do
{
if (host->detect_irq == 0)
break;
cd_status = host->plat_data->status(host->mmc->parent);//读取SD卡状态
if (cd_status)//如果卡槽内没有卡,则设置为下降沿触发,否则为上升沿触发
set_irq_type(host->detect_irq, IRQF_TRIGGER_FALLING);
else
set_irq_type(host->detect_irq, IRQF_TRIGGER_RISING);
} while (cd_status != host->plat_data->status(host->mmc->parent));
cd_status = host->plat_data->status(host->mmc->parent);//获得卡状态
printk(KERN_INFO"cd_status=%d %s/n",cd_status, cd_status ? "removed" : "inserted");//lqm added.
/* If there is no card, call the card detection func immediately. */
if (!cd_status) // 当没卡时插入卡,开启定时器
{
if (host->flags & SDHCI_CD_TIMEOUT)
host->flags &= ~SDHCI_CD_TIMEOUT;
else
{
mod_timer(&host->cd_timer, jiffies + HZ / 4);
return;
}
}
cd_status = host->plat_data->status(host->mmc->parent);
if (cd_status)//cd_status=1表示没有卡
host->flags &= ~SDHCI_CD_PRESENT;
else
host->flags |= SDHCI_CD_PRESENT;
/* Detect there is a card in slot or not */
spin_lock_irqsave(&host->lock, flags);//自旋锁第三步:获得自旋锁,保护临界区
if (!(host->flags & SDHCI_CD_PRESENT)) //当卡拨出时执行下面程序
{
printk(KERN_INFO"%s: Card removed and resetting controller./n",mmc_hostname(host->mmc));
if (host->mrq)
{
struct mmc_data *data;
data = host->data;
host->data = NULL;
printk(KERN_ERR"%s: Card removed during transfer!/n",mmc_hostname(host->mmc));
printk(KERN_ERR"%s: Resetting controller./n",mmc_hostname(host->mmc));
if ((host->flags & SDHCI_USE_EXTERNAL_DMA) && (data != NULL))
{
dma_unmap_sg(mmc_dev(host->mmc), data->sg,
host->dma_len, host->dma_dir);
host->dma_size = 0;
}
sdhci_reset(host, SDHCI_RESET_CMD);
sdhci_reset(host, SDHCI_RESET_DATA);
host->mrq->cmd->error = -ENOMEDIUM;
tasklet_schedule(&host->finish_tasklet);
}
if (host->init_flag > 0)
host->init_flag--;
else
sdhci_init(host);
}
spin_unlock_irqrestore(&host->lock, flags);//自旋锁第四步:解锁
if (host->flags & SDHCI_CD_PRESENT)
{
printk(KERN_ERR"SDHCI_CD_PRESENT.../n");// lqm added.
del_timer(&host->cd_timer);
mmc_detect_change(host->mmc, msecs_to_jiffies(100));// lqm masked for test.
}
else
{
printk(KERN_ERR"no SDHCI_CD_PRESENT.../n");
mmc_detect_change(host->mmc, 0);
}
}
由于卡状态发生了改变,因此函数的第一步即先读取卡状态,根据不同的状态设置对应的卡中断方式。如果是卡插入造成的中断,则打开定时器,进而执行卡挂载等操作,如果是卡拨出造成的中断,则复位SD卡模块。注意在卡拨出执行的代码段添加了自旋锁,正因为它,前面才使用了spin_lock_init函数初始化自旋锁。