插曲:
因为使用的平台是telechips的tcc803x,其芯片用户手册描述寄存器都是四字节寻址的,但是在代码驱动中用的很可能是单字节寻址,咋一看,有可能有的地址在芯片手册上没有或者感觉写错了,其实不是,这个需要注意一下。
简单流程:
mmc host主控器注册完成之后,会分别生成一个底层硬件相关的主控制设备(struct)sdhci_host和通用抽象的主控制器设备(struct)mmc_host,当检测到有sd卡等mmc从设备插入时,mmc host主控制器会向sd卡等mmc从设备发起会话,sd卡等从设备作出相应的应答。mmc host主控制建立会话的机制是先通过通用抽象的(struct)mmc_host主设备的通用操作集(struct)mmc_host_ops,再由通用操作集(struct)mmc_host_ops进一步调用底层硬件相关的操作集(struct)sdhci_ops实现主从设备的通信的。
转载:linux MMC framework(2) - sdhci host driver_Hacker_Albert的博客-优快云博客
注册mmc主控制器:
static int sdhci_tcc_probe(struct platform_device *pdev)
{
const struct of_device_id *match;
const struct sdhci_tcc_soc_data *soc_data;
struct sdhci_host *host;
struct sdhci_pltfm_host *pltfm_host;
struct sdhci_tcc *tcc = NULL;
match = of_match_device(sdhci_tcc_of_match_table, &pdev->dev);
soc_data = match->data;
host = sdhci_pltfm_init(pdev, soc_data->pdata, sizeof(*tcc));
pltfm_host = sdhci_priv(host);
tcc = sdhci_pltfm_priv(pltfm_host);
sdhci_get_of_property(pdev);
mmc_of_parse(host->mmc);
sdhci_tcc_parse_configs(pdev, host);
//设置SDMMC1的基础时钟,即PCLKCTL_IO2,输出到外设SDMMC1的时钟
tcc->soc_data->set_core_clock(host);
sdhci_add_host(host);
}
sdhci_tcc_of_match_table表:
static const struct of_device_id sdhci_tcc_of_match_table[] = {
{ .compatible = "telechips,tcc803x-sdhci,module-only", .data = &soc_data_tcc803x},
{}
};
表里的soc_data_tcc803x结构:
static const struct sdhci_tcc_soc_data soc_data_tcc803x = {
.pdata = &sdhci_tcc803x_pdata,
.parse_channel_configs = sdhci_tcc803x_parse_channel_configs,
.set_channel_configs = sdhci_tcc803x_set_channel_configs,
.set_core_clock = sdhci_tcc803x_set_core_clock,
.sdhci_tcc_quirks = 0,
};
sdhci_tcc803x_pdata平台数据结构:
static const struct sdhci_pltfm_data sdhci_tcc803x_pdata = {
.ops = &sdhci_tcc803x_ops,
//重点关注quirks,表示异于通常特性
.quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN,
.quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN |
SDHCI_QUIRK2_STOP_WITH_TC,
};
sdhci_tcc803x_ops操作集:
static const struct sdhci_ops sdhci_tcc803x_ops = {
.get_max_clock = sdhci_tcc803x_clk_get_max_clock,
.set_clock = sdhci_tcc_set_clock,
.set_bus_width = sdhci_set_bus_width,
.reset = sdhci_tcc_reset,
.hw_reset = sdhci_tcc_hw_reset,
.set_uhs_signaling = sdhci_set_uhs_signaling,
.get_ro = sdhci_tcc_get_ro,
};
sdhci_ops与主控制硬件打交道,所以是再host主控制器驱动中实现的。
重新回到probe函数,看sdhci_pltfm_init函数:
struct sdhci_host *sdhci_pltfm_init(struct platform_device *pdev,
const struct sdhci_pltfm_data *pdata,
size_t priv_size)
{
struct sdhci_host *host;
//分配一个struct sdhci_host结构空间,附带分配一个struct sdhci_pltfm_host结构空间
//和一个priv_size大小的空间
host = sdhci_alloc_host(&pdev->dev,sizeof(struct sdhci_pltfm_host) + priv_size);
if (pdata && pdata->ops)
host->ops = pdata->ops; //对应上面定义的struct sdhci_ops sdhci_tcc803x_ops变量
return host;
}
sdhci_alloc_host函数:
struct sdhci_host *sdhci_alloc_host(struct device *dev,
size_t priv_size)
{
struct mmc_host *mmc;
struct sdhci_host *host;
//在分配一个mmc_host内存空间的同时也分配了一个sdhci_host内存空间以及sdhci_host附属空间
mmc = mmc_alloc_host(sizeof(struct sdhci_host) + priv_size, dev);
host = mmc_priv(mmc);
host->mmc = mmc;
host->mmc_host_ops = sdhci_ops;
mmc->ops = &host->mmc_host_ops;
return host;
}
mmc_alloc_host函数:分配一个mmc_host结构体内存空间,也分配了其它以外的内存空间。
struct mmc_host *mmc_alloc_host(int extra, struct device *dev)
{
int err;
struct mmc_host *host;
//除了分配mmc_host内存空间,也分配了其它以外的内存空间
host = kzalloc(sizeof(struct mmc_host) + extra, GFP_KERNEL);
/* scanning will be enabled when we're ready */
host->rescan_disable = 1;
dev_set_name(&host->class_dev, "mmc%d", host->index);
//mmc_host的父设备是probe函数层层传下来的pdev,就是sdhc主控制器设备,即对应sdhc设备节点
host->parent = dev;
host->class_dev.parent = dev;
host->class_dev.class = &mmc_host_class;
device_initialize(&host->class_dev);
init_waitqueue_head(&host->wq);
INIT_DELAYED_WORK(&host->detect, mmc_rescan);
INIT_DELAYED_WORK(&host->sdio_irq_work, sdio_irq_work);
setup_timer(&host->retune_timer, mmc_retune_timer, (unsigned long)host);
return host;
}
mmc_priv函数:
static inline void *mmc_priv(struct mmc_host *host)
{
return (void *)host->private;
}
mmc_host结构体:
struct mmc_host {
struct device *parent;
struct device class_dev;
const struct mmc_host_ops *ops;
unsigned int f_min;
unsigned int f_max;
unsigned int f_init;
......
unsigned long private[0] ____cacheline_aligned;
}
回到probe函数,看sdhci_priv函数:
static inline void *sdhci_priv(struct sdhci_host *host)
{
return host->private;
}
sdhci_host结构:
struct sdhci_host {
/* Data set by hardware interface driver */
const char *hw_name; /* Hardware bus name */
const struct sdhci_ops *ops; /* Low level hw interface */
/* Internal data */
struct mmc_host *mmc; /* MMC structure */
struct mmc_host_ops mmc_host_ops; /* MMC host ops */
......
unsigned long private[0] ____cacheline_aligned;
}
回到probe函数,继续往下看:
sdhci_get_of_property函数:
void sdhci_get_of_property(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct sdhci_host *host = platform_get_drvdata(pdev);
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
u32 bus_width;
if (of_get_property(np, "sdhci,auto-cmd12", NULL))
host->quirks |= SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
if (of_get_property(np, "sdhci,1-bit-only", NULL) ||
(of_property_read_u32(np, "bus-width", &bus_width) == 0 &&
bus_width == 1))
host->quirks |= SDHCI_QUIRK_FORCE_1_BIT_DATA;
if (sdhci_of_wp_inverted(np))
host->quirks |= SDHCI_QUIRK_INVERTED_WRITE_PROTECT;
if (of_get_property(np, "broken-cd", NULL))
host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION;
if (of_get_property(np, "no-1-8-v", NULL))
host->quirks2 |= SDHCI_QUIRK2_NO_1_8_V;
......
}
mmc_of_parse函数:
mmc_of_parse
device_property_read_u32(dev, "bus-width", &bus_width);
......
device_property_read_u32(dev, "max-frequency", &host->f_max);
if (device_property_read_bool(dev, "cap-sd-highspeed"))
host->caps |= MMC_CAP_SD_HIGHSPEED;
......
sdhci_tcc803x_set_core_clock函数:
sdhci_tcc803x_set_core_clock
//成员变量mmc的数据类型是struct mmc_host*
clk_set_rate(pltfm_host->clk, host->mmc->f_max);
clk_core_set_rate_nolock
clk_change_rate
core->ops->set_rate(core->hw, core->new_rate, best_parent_rate);
static struct clk_ops tcc_peri_ops = {
.set_rate = tcc_peri_set_rate,
......
};
tcc_peri_set_rate
//rate传到安全核设置
arm_smccc_smc(SIP_CLK_SET_PCLKCTRL, tcc->id, 1, rate, vflags, 0, 0, 0, &res);
sdhci_add_host函数:
int sdhci_add_host(struct sdhci_host *host)
{
sdhci_setup_host(host);
__sdhci_add_host(host);
return 0;
}
sdhci_setup_host函数:
sdhci_setup_host
sdhci_read_caps(host);
......
if (host->caps & SDHCI_CAN_DO_HISPD)
mmc->caps |= MMC_CAP_SD_HIGHSPEED | MMC_CAP_MMC_HIGHSPEED;
......
//host->quirks2见上面pdata有定义
if (host->quirks2 & SDHCI_QUIRK2_NO_1_8_V) {
host->caps1 &= ~(SDHCI_SUPPORT_SDR104 | SDHCI_SUPPORT_SDR50 | SDHCI_SUPPORT_DDR50);
}
if (host->caps1 & (SDHCI_SUPPORT_SDR104 | SDHCI_SUPPORT_SDR50 | SDHCI_SUPPORT_DDR50))
mmc->caps |= MMC_CAP_UHS_SDR12 | MMC_CAP_UHS_SDR25;
if (host->caps1 & SDHCI_SUPPORT_SDR104) {
mmc->caps |= MMC_CAP_UHS_SDR104 | MMC_CAP_UHS_SDR50;
} else if (host->caps1 & SDHCI_SUPPORT_SDR50) {
mmc->caps |= MMC_CAP_UHS_SDR50;
}
if ((host->caps1 & SDHCI_SUPPORT_DDR50) &&!(host->quirks2 &
SDHCI_QUIRK2_BROKEN_DDR50))
mmc->caps |= MMC_CAP_UHS_DDR50;
sdhci_read_caps函数:
sdhci_read_caps
__sdhci_read_caps(host, NULL, NULL, NULL);
of_property_read_u64(mmc_dev(host->mmc)->of_node,"sdhci-caps-mask",
&dt_caps_mask);
of_property_read_u64(mmc_dev(host->mmc)->of_node,"sdhci-caps", &dt_caps);
......
host->caps = sdhci_readl(host, SDHCI_CAPABILITIES);
host->caps &= ~lower_32_bits(dt_caps_mask);
host->caps |= lower_32_bits(dt_caps);
host->caps1 = sdhci_readl(host, SDHCI_CAPABILITIES_1);
host->caps1 &= ~upper_32_bits(dt_caps_mask);
host->caps1 |= upper_32_bits(dt_caps);
__sdhci_add_host函数:
int __sdhci_add_host(struct sdhci_host *host)
{
struct mmc_host *mmc = host->mmc;
.....
sdhci_init(host, 0);
request_threaded_irq(host->irq, sdhci_irq, sdhci_thread_irq,
IRQF_SHARED, mmc_hostname(mmc), host);
.....
mmc_add_host(mmc);
......
return 0;
}
sdhc主控制器的中断处理函数分为上半部分sdhci_irq和下半部分sdhci_thread_irq。
sdhci_irq:
static irqreturn_t sdhci_irq(int irq, void *dev_id)
{
irqreturn_t result = IRQ_NONE;
struct sdhci_host *host = dev_id;
u32 intmask, mask, unexpected = 0;
int max_loops = 16;
intmask = sdhci_readl(host, SDHCI_INT_STATUS);
if (!intmask || intmask == 0xffffffff) {
result = IRQ_NONE;
goto out;
}
do {
if (intmask & (SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE)) {
u32 present = sdhci_readl(host, SDHCI_PRESENT_STATE) & SDHCI_CARD_PRESENT;
......
result = IRQ_WAKE_THREAD;
}
if (intmask & SDHCI_INT_CMD_MASK){
sdhci_cmd_irq(host, intmask & SDHCI_INT_CMD_MASK, &intmask);
}
......
cont:
if (result == IRQ_NONE)
result = IRQ_HANDLED;
intmask = sdhci_readl(host, SDHCI_INT_STATUS);
} while (intmask && --max_loops);
out:
return result;
}
sdhci_cmd_irq函数:
static void sdhci_cmd_irq(struct sdhci_host *host, u32 intmask, u32 *intmask_p)
{
......
//#define SDHCI_INT_RESPONSE 0x00000001
if (intmask & SDHCI_INT_RESPONSE){
sdhci_finish_command(host);
}
}
中断状态寄存器:
auto-cmd12、auto-cmd23补充:
软件设置控制器相关寄存器,让控制器自行发 stop transfer的命令,即 Auto CMD23或 Auto CMD12。
sdhci_set_transfer_mode函数解析 - xxxdk's blog
struct mmc_request的变量 sbc,即SET_BLOCK_COUNT。
mmc_add_host函数:
mmc_add_host
mmc_start_host
mmc_gpiod_request_cd_irq
_mmc_detect_change
//对应上面的INIT_DELAYED_WORK(&host->detect, mmc_rescan);
mmc_schedule_delayed_work(&host->detect, delay);
mmc_rescan函数:该函数是detect工作队列的处理函数,该工作队列只在两个地方被调用,一个是在函数_mmc_detect_change里被调用(该函数虽然可能会被多个地方调用,但是正常时候只在初始化的时候被mmc_sart_host函数调用一次),一个是在函数mmc_rescan里递归调用。
void mmc_rescan(struct work_struct *work)
{
struct mmc_host *host = container_of(work, struct mmc_host, detect.work);
int i;
if (host->rescan_disable)
return;
/* If there is a non-removable card registered, only scan once */
if (!mmc_card_is_removable(host) && host->rescan_entered)
return;
host->rescan_entered = 1;
.....
/* if there is a _removable_ card registered, check whether it is still present*/
if (host->bus_ops && !host->bus_dead && mmc_card_is_removable(host))
host->bus_ops->detect(host);
......
for (i = 0; i < ARRAY_SIZE(freqs); i++) {
if (!mmc_rescan_try_freq(host, max(freqs[i], host->f_min)))
break;
if (freqs[i] <= host->f_min)
break;
}
.....
out:
if (host->caps & MMC_CAP_NEEDS_POLL) //只有SD卡主控制器会递归调用轮询扫描
mmc_schedule_delayed_work(&host->detect, HZ);
}
mmc_rescan_try_freq函数:
mmc_rescan_try_freq
mmc_attach_sdio
mmc_sdio_init_card
mmc_set_clock(host, mmc_sdio_get_max_clock(card));
mmc_set_clock函数:设置实际频率,除了sdio设备,emmc也是调用该函数设置时钟频率。
void mmc_set_clock(struct mmc_host *host, unsigned int hz)
{
//hz:最高频率,host->f_max:实际设置的频率,在设备树中定义,
//对应max-frequency属性,上面有对该属性值获取
if (hz > host->f_max)
hz = host->f_max;
host->ios.clock = hz; //注意:是ios.clock
mmc_set_ios(host);
}
mmc_set_ios函数:该函数除了被mmc_set_clock函数调用,还在mmc_rescan扫描设备的时候和更前面mmc_start_host->mmc_power_up的时候被调用设置时钟,这时的频率:host->f_init = freq;
mmc_set_ios
host->ops->set_ios(host, ios);
static const struct mmc_host_ops sdhci_ops = {
.set_ios = sdhci_set_ios,
......
};
sdhci_set_ios
//注意:第一次设置的是ios->clock,第二次设置的是host->clock
// host->version == SDHCI_SPEC_300
// host->preset_enabled == 0
if (!ios->clock || ios->clock != host->clock) {
host->ops->set_clock(host, ios->clock);
host->clock = ios->clock;
}
host->ops->set_uhs_signaling(host, ios->timing);
host->timing = ios->timing;
if (host->version >= SDHCI_SPEC_300) {
//set_clock对应sdhci_tcc_set_clock函数
host->ops->set_clock(host, host->clock);
}
sdhci_tcc_set_clock
sdhci_set_clock(host, clock);
host->mmc->actual_clock = 0;
sdhci_writew(host, 0, SDHCI_CLOCK_CONTROL);
//如果传入的clock等于0,返回
if (clock == 0){
return;
}
clk = sdhci_calc_clk(host, clock, &host->mmc->actual_clock);
sdhci_enable_clk(host, clk);
sdhci_calc_clk
int div = 0;
int real_div = div, clk_mul = 1; //注意:clk_mul和host->clk_mul不一样
u16 clk = 0;
bool switch_base_clk = false;
// host->version == SDHCI_SPEC_300
//host->preset_enabled == 0
//host->clk_mul == 0
if (host->version >= SDHCI_SPEC_300) {
if (!host->clk_mul || switch_base_clk) {
//1、如果max_clk小于等于clock,设置div等参数,使能该频率;
//2、否则对max_clk分频,分频后的频率小于clock,设置div等参数,使能该频率;
if (host->max_clk <= clock){
div = 1;
}
else {
for (div = 2; div < SDHCI_MAX_DIV_SPEC_300;div += 2) {
if ((host->max_clk / div) <= clock)
break;
}
}
real_div = div;
div >>= 1;
}
if (real_div){
*actual_clock = (host->max_clk * clk_mul) / real_div;
}
clk |= (div & SDHCI_DIV_MASK) << SDHCI_DIVIDER_SHIFT;
clk |= ((div & SDHCI_DIV_HI_MASK) >> SDHCI_DIV_MASK_LEN)
<< SDHCI_DIVIDER_HI_SHIFT;
注:
quirks: 怪癖的意思,也就是说它某种特性与通常的某种设备不相同。
如果设置3.3V、高速,只要在sdhc主控制器节点上添加“no-1-8-v;”和“cap-sd-highspeed;”
SDR模式下:一个时钟采样一次,一次4bit,即0.5Byte,50MHZ,传输速度25MB/s。
卡容量:
1) 标准容量卡(SDSC):不超于2GB
2) 高容量卡 (SDHC):大于2GB又不超过32GB
3) 扩展容量卡(SDXC):大于32GB又不超过2TB的卡
工作电压范围:2.7V~3.6V
总线速率:(SDR-single Data Rate) DDR(Double Data Rate)
1) 默认速率模式:3.3V 信号,高达 25MHz,数据速率 12.5MB/S
2) 高速率模式:3.3V 信号,高达 50MHz,数据速率 25MB/S
3) SDR12:1.8V 信号,高达 25MHz,数据速率 12.5MB/S
4) SDR25:1.8V 信号,高达 50MHz,数据速率 25MB/S
5) SDR50:1.8V 信号,高达 100MHz,数据速率 50MB/S
6) SDR104:1.8V 信号,高达 208MHz,数据速率 104MB/S
7) DDR50:1.8V 信号,高达 50MHz,双时钟沿采样数据,数据速率 50MB/S
ocr:
u32 mmc_select_voltage(struct mmc_host *host, u32 ocr)
{
int bit;
//低7位是reserve位,清零
if (ocr & 0x7F) {
dev_warn(mmc_dev(host),
"card claims to support voltages below defined range\n");
ocr &= ~0x7F;
}
......
}
参考:
第12章 SD卡和SDIO接口(一)
[mmc]Linux下MMC/SD/SDIO的识别与操作_anxuan3201的博客-优快云博客
UHS:Ultra High-Speed
超高速和超高清接口是下一代实现 SDHC 和 SDXC 卡高速数据传输的的总线接口。
UHS 总线接口现在共有三个版本:UHS-I 、UHS-II、UHS-III。
CCCR全称是Card Common Control Registers。
CIS:card infomation structure,在CCCR寄存器中。
参考:二,sdio总线简介之Commond_sdio cmd_初雨细无声的博客-优快云博客
其它: