SDIO MSHC 采样相位与驱动相位

采样相位和驱动相位

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

下图为sdhc控制器的不同时钟和延迟:

image-sdio-clocks-delay

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流程描述:

image-sdio-clocks-delay

那么drv phase clk是用来做什么?
我理解tuning是为了解决接收端发送数据的时延的,发送端的clk和data也会有时延吗?通过drv_ph去消除时延?

答:tuning过程中,也可能会用到drv phase,如果卡侧没有delay line,host这边需要把发送的数据和时钟相位进行调整,这也是一种tuning。

另外,在设计的指示中,SDHC控制器内部还有可以调节相位寄存器:

image-sdio-clocks-delay

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

image-sdio-clocks-delay

据设计说,该寄存器可以做精度更细的调谐。目前我们的软件中只修改了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;
/* fallthrough */
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);

/* We should delay 1ms wait for timing setting finished. */
usleep_range(1000, 2000);
}

hs_timing_cfg是海思静态定义的一个timing table,可能存储的是一些经验值,如果tuning流程对bitmap上每一位都置位了,这意味着每一个相位都通过了eMMc的检验,就会使用timing table里的值

随后它还配置了**mci_writel(host, GPIO, 0x0);**,该寄存器确实为dw ip里的,bit field如下:

image-sdio-gpio-reg
这个寄存器,手册里的信息实在太少了。海思在会首先将该寄存器清零,在配置UHS_REG_EXT寄存器后还会配置这个寄存器,不清楚是何目的。

总之,我的猜想并不对,没有找到先clkgen调谐再UHS_REG_EXT调谐的例子。


后来设计查询了他们的代码,我们clkgen中对sample phase的调节就是在控制sdhc的clk_sample_phase_ctrl信号。
rk的代码使用的是clkgen,海思使用的是UHS_REG_EXT,可能各家的实现不一样,我们的驱动代码沿用原来那一套即可。