采样相位和驱动相位
在SDIO控制器的时钟体系中,一般有两个和tuning相关的时钟:smpl phase clk和drv phase clk,分别是采样相位和驱动相位。之前一直搞不懂这两个采样相位的关系,今天和我们的芯片设计聊了一下,这里做一个总结:
下图为sdhc控制器的不同时钟和延迟:

smpl phase和drv phase 分别用于配置控制红框里的两个clock delay,其中cclk_in_sample将会影响ccmd_in和cdata_in两个时钟,这是输入方向的时钟。cclk_in_drv将会影响ccmd_out,cdata_out,这是输出时钟的延迟。cclk_out是输出给卡的时钟。输出给卡的数据就是drv_clk驱动的。
一般来说,tuning流程中的第一步中提到的cclk_in_sample即为smpl phase:
下图为tuning流程描述:

那么drv phase clk是用来做什么?
我理解tuning是为了解决接收端发送数据的时延的,发送端的clk和data也会有时延吗?通过drv_ph去消除时延?
答:tuning过程中,也可能会用到drv phase,如果卡侧没有delay line,host这边需要把发送的数据和时钟相位进行调整,这也是一种tuning。
另外,在设计的指示中,SDHC控制器内部还有可以调节相位寄存器:

该寄存器名为UHS_REG_EXT,bit field描述如下:

据设计说,该寄存器可以做精度更细的调谐。目前我们的软件中只修改了clkgen模块中提供的smpl clk,也许加入对UHS_REG_EXT的配置后,速率提升会更加明显(clkgen调谐后实际速率只有40M)。
按这个道理,如果要加入对UHS_REG_EXT寄存器中的smpl域控制的调谐,我的想法是,在对clkgen模块中的smpl clk寄存器做调谐之后,找到一个比较准确的采样点,然后再执行对UHS_REG_EXT的调谐,相当于游标卡尺的主尺和游标
为了验证我的猜想,我去内核代码里粗略找了一下,对UHS_REG_EXT配置的代码只有hi3660在做:
1 2 3 4 5 6 7 8 9
| luyuan@qudong-MS-7B97:~/workspace/sdk_57B_new/kernel/drivers/mmc$ grep "UHS_REG_EXT" -nr ./ ./host/dw_mmc-k3.c:45:#define UHS_REG_EXT_SAMPLE_PHASE_MASK GENMASK(20, 16) ./host/dw_mmc-k3.c:46:#define UHS_REG_EXT_SAMPLE_DRVPHASE_MASK GENMASK(25, 21) ./host/dw_mmc-k3.c:47:#define UHS_REG_EXT_SAMPLE_DLY_MASK GENMASK(30, 26) ./host/dw_mmc-k3.c:256: reg_value = FIELD_PREP(UHS_REG_EXT_SAMPLE_PHASE_MASK, smpl_phase) | ./host/dw_mmc-k3.c:257: FIELD_PREP(UHS_REG_EXT_SAMPLE_DLY_MASK, smpl_dly) | ./host/dw_mmc-k3.c:258: FIELD_PREP(UHS_REG_EXT_SAMPLE_DRVPHASE_MASK, drv_phase); ./host/dw_mmc-k3.c:259: mci_writel(host, UHS_REG_EXT, reg_value); ./host/dw_mmc.h:321:#define SDMMC_UHS_REG_EXT 0x108
|
海思也使用的DW的IP,观察GENMASK那几行和寄存器偏移那一行,bit field和偏移也都一致。
FIELD_PREP这个宏用于将smpl_phase和smpl_dly配置到它们的bit field中生成reg配置值。
再找到海思的tuning接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| static int dw_mci_hi3660_execute_tuning(struct dw_mci_slot *slot, u32 opcode) { int i = 0; struct dw_mci *host = slot->host; struct mmc_host *mmc = slot->mmc; int smpl_phase = 0; u32 tuning_sample_flag = 0; int best_clksmpl = 0;
for (i = 0; i < NUM_PHASES; ++i, ++smpl_phase) { smpl_phase %= 32;
mci_writel(host, TMOUT, ~0); dw_mci_hs_set_timing(host, mmc->ios.timing, smpl_phase);
if (!mmc_send_tuning(mmc, opcode, NULL)) tuning_sample_flag |= (1 << smpl_phase); else tuning_sample_flag &= ~(1 << smpl_phase); }
best_clksmpl = dw_mci_get_best_clksmpl(tuning_sample_flag); if (best_clksmpl < 0) { dev_err(host->dev, "All phases bad!\n"); return -EIO; }
dw_mci_hs_set_timing(host, mmc->ios.timing, best_clksmpl);
dev_info(host->dev, "tuning ok best_clksmpl %u tuning_sample_flag %x\n", best_clksmpl, tuning_sample_flag); return 0; }
|
海思的tuning接口写的比较简单,用一个u32长度的bitmap – tuning_sample_flag变量存储所有通过的采样点,再通过dw_mci_get_best_clksmpl函数获取最优采样点。
但最后它还调用了dw_mci_hs_set_timing函数来设置一些timing参数,这里我不是很理解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| static void dw_mci_hs_set_timing(struct dw_mci *host, int timing, int smpl_phase) { u32 drv_phase; u32 smpl_dly; u32 use_smpl_dly = 0; u32 enable_shift = 0; u32 reg_value; int ctrl_id; struct k3_priv *priv;
priv = host->priv; ctrl_id = priv->ctrl_id;
drv_phase = hs_timing_cfg[ctrl_id][timing].drv_phase; smpl_dly = hs_timing_cfg[ctrl_id][timing].smpl_dly; if (smpl_phase == -1) smpl_phase = (hs_timing_cfg[ctrl_id][timing].smpl_phase_max + hs_timing_cfg[ctrl_id][timing].smpl_phase_min) / 2;
switch (timing) { case MMC_TIMING_UHS_SDR104: if (smpl_phase >= USE_DLY_MIN_SMPL && smpl_phase <= USE_DLY_MAX_SMPL) use_smpl_dly = 1; case MMC_TIMING_UHS_SDR50: if (smpl_phase >= ENABLE_SHIFT_MIN_SMPL && smpl_phase <= ENABLE_SHIFT_MAX_SMPL) enable_shift = 1; break; }
mci_writel(host, GPIO, 0x0); usleep_range(5, 10);
reg_value = FIELD_PREP(UHS_REG_EXT_SAMPLE_PHASE_MASK, smpl_phase) | FIELD_PREP(UHS_REG_EXT_SAMPLE_DLY_MASK, smpl_dly) | FIELD_PREP(UHS_REG_EXT_SAMPLE_DRVPHASE_MASK, drv_phase); mci_writel(host, UHS_REG_EXT, reg_value);
mci_writel(host, ENABLE_SHIFT, enable_shift);
reg_value = FIELD_PREP(GPIO_CLK_DIV_MASK, GENCLK_DIV) | FIELD_PREP(GPIO_USE_SAMPLE_DLY_MASK, use_smpl_dly); mci_writel(host, GPIO, (unsigned int)reg_value | GPIO_CLK_ENABLE);
usleep_range(1000, 2000); }
|
hs_timing_cfg是海思静态定义的一个timing table,可能存储的是一些经验值,如果tuning流程对bitmap上每一位都置位了,这意味着每一个相位都通过了eMMc的检验,就会使用timing table里的值
随后它还配置了**mci_writel(host, GPIO, 0x0);**,该寄存器确实为dw ip里的,bit field如下:

这个寄存器,手册里的信息实在太少了。海思在会首先将该寄存器清零,在配置UHS_REG_EXT寄存器后还会配置这个寄存器,不清楚是何目的。
总之,我的猜想并不对,没有找到先clkgen调谐再UHS_REG_EXT调谐的例子。
后来设计查询了他们的代码,我们clkgen中对sample phase的调节就是在控制sdhc的clk_sample_phase_ctrl信号。
rk的代码使用的是clkgen,海思使用的是UHS_REG_EXT,可能各家的实现不一样,我们的驱动代码沿用原来那一套即可。