sdhci_set_transfer_mode函数解析

前言

最近逻辑和测试对软件传输数据的流程有很多问号???软件是否会设置控制器相关寄存器,让控制器自行发 stop transfer的命令,即 Auto CMD23或 Auto CMD12。本着对版本负责,作为寄存器配置工程师,即使是内核原生流程,我也会将相关代码解析告诉他们。

在具体分析代码前,我们大概了解下 CMD12和 CMD23。CMD12,STOP_TRANSMISSION,停止多块传输操作。启动多块读 CMD18或者多块写 CMD25后,可以通过 CMD12停止传输。CMD23,SET_BLOCK_COUNT,定义读/写的块数量。启动多块读 CMD18或者多块写 CMD25前,如果通过 CMD23定义了传输的块数量,则完成指定数量的传输后,将停止传输。当然 CMD12和 CMD23还能影响 CMD30、CMD27这些命令。

开始

基于 linux 4.14.212版本进行分析,linux 4.19.163差异也不大,代码如下。

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
static void sdhci_set_transfer_mode(struct sdhci_host *host,
struct mmc_command *cmd)
{
u16 mode = 0;
struct mmc_data *data = cmd->data;

if (data == NULL) {
if (host->quirks2 &
SDHCI_QUIRK2_CLEAR_TRANSFERMODE_REG_BEFORE_CMD) {
sdhci_writew(host, 0x0, SDHCI_TRANSFER_MODE);
} else {
/* clear Auto CMD settings for no data CMDs */
mode = sdhci_readw(host, SDHCI_TRANSFER_MODE);
sdhci_writew(host, mode & ~(SDHCI_TRNS_AUTO_CMD12 |
SDHCI_TRNS_AUTO_CMD23), SDHCI_TRANSFER_MODE);
}
return;
}

WARN_ON(!host->data);

if (!(host->quirks2 & SDHCI_QUIRK2_SUPPORT_SINGLE))
mode = SDHCI_TRNS_BLK_CNT_EN;

if (mmc_op_multi(cmd->opcode) || data->blocks > 1) {
mode = SDHCI_TRNS_BLK_CNT_EN | SDHCI_TRNS_MULTI;
/*
* If we are sending CMD23, CMD12 never gets sent
* on successful completion (so no Auto-CMD12).
*/
if (sdhci_auto_cmd12(host, cmd->mrq) &&
(cmd->opcode != SD_IO_RW_EXTENDED))
mode |= SDHCI_TRNS_AUTO_CMD12;
else if (cmd->mrq->sbc && (host->flags & SDHCI_AUTO_CMD23)) {
mode |= SDHCI_TRNS_AUTO_CMD23;
sdhci_writel(host, cmd->mrq->sbc->arg, SDHCI_ARGUMENT2);
}
}

if (data->flags & MMC_DATA_READ)
mode |= SDHCI_TRNS_READ;
if (host->flags & SDHCI_REQ_USE_DMA)
mode |= SDHCI_TRNS_DMA;

sdhci_writew(host, mode, SDHCI_TRANSFER_MODE);
}

sdhci_set_transfer_mode()主要由两部分组成,即本次传输是否需要传输数据。

Non-DAT transfer

无数据传输分支,会先对控制器的特性进行判断,确定是否需要在发送命令前清零传输模式寄存器(0x0c) (SDHCI_QUIRK2_CLEAR_TRANSFERMODE_REG_BEFORE_CMD)。不需要则把 Auto CMD关掉,因为没有数据传输,就不需要发 CMD12或者 CMD23了,至此就设置好无数据传输的传输模式寄存器。

1
2
3
4
5
6
7
8
9
10
11
12
if (data == NULL) {
if (host->quirks2 &
SDHCI_QUIRK2_CLEAR_TRANSFERMODE_REG_BEFORE_CMD) {
sdhci_writew(host, 0x0, SDHCI_TRANSFER_MODE);
} else {
/* clear Auto CMD settings for no data CMDs */
mode = sdhci_readw(host, SDHCI_TRANSFER_MODE);
sdhci_writew(host, mode & ~(SDHCI_TRNS_AUTO_CMD12 |
SDHCI_TRNS_AUTO_CMD23), SDHCI_TRANSFER_MODE);
}
return;
}

Data transfer

数据传输分支,先确认软件是否配置了控制器支持单块传输计数,没有则使能控制器块计数(block count enable)。

1
2
if (!(host->quirks2 & SDHCI_QUIRK2_SUPPORT_SINGLE))
mode = SDHCI_TRNS_BLK_CNT_EN;

然后判断这次传输的命令是否是多块读或是多块写 (mmc_op_multi(cmd->opcode)),或者是传输的块数量大于 1 (data->blocks > 1),是则会使能控制器块计数和多块传输。

接着判断控制器如果不支持 Auto CMD12 (sdhci_auto_cmd12() 返回 false,具体解析见下一段),同时命令不是 CMD53(SDIO多块读写命令),则使能控制器 Auto CMD23能力。如果不是上述条件则继续判断,struct mmc_request的变量 sbc(SET_BLOCK_COUNT for multiblock)是否已经初始化,同时软件初始化控制器是否设置了 Auto CMD23标志 (SDHCI_AUTO_CMD23),是,则使能控制器 Auto CMD23能力和将配置好的 mrq->sbc->arg(blocks / ../core/mmc_test.c +210)写入控制器的 SDHCI_ARGUMENT2中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if (mmc_op_multi(cmd->opcode) || data->blocks > 1) {
mode = SDHCI_TRNS_BLK_CNT_EN | SDHCI_TRNS_MULTI;
/*
* If we are sending CMD23, CMD12 never gets sent
* on successful completion (so no Auto-CMD12).
*/
if (sdhci_auto_cmd12(host, cmd->mrq) &&
(cmd->opcode != SD_IO_RW_EXTENDED))
mode |= SDHCI_TRNS_AUTO_CMD12;
else if (cmd->mrq->sbc && (host->flags & SDHCI_AUTO_CMD23)) {
mode |= SDHCI_TRNS_AUTO_CMD23;
sdhci_writel(host, cmd->mrq->sbc->arg, SDHCI_ARGUMENT2);
}
}

sdhci_auto_cmd12,返回 true则控制器当前配置使能了 Auto CMD12,false反之。分析下 return的这三个条件,1、struct mmc_request的变量 sbc(SET_BLOCK_COUNT for multiblock)没有初始化;

2、软件初始化控制器时,设置了 Auto CMD12标志 (SDHCI_AUTO_CMD12);

3、struct mmc_request的变量 cap_cmd_during_tfr(数据传输或者忙等待中,允许发送命令)是 false(不允许)。

同时满足上述三个条件,则逻辑会自行发 CMD12。

1
2
3
4
5
6
static inline bool sdhci_auto_cmd12(struct sdhci_host *host,
struct mmc_request *mrq)
{
return !mrq->sbc && (host->flags & SDHCI_AUTO_CMD12) &&
!mrq->cap_cmd_during_tfr;
}

后续再判断是否是读传输和是否使用 DMA。

1
2
3
4
if (data->flags & MMC_DATA_READ)
mode |= SDHCI_TRNS_READ;
if (host->flags & SDHCI_REQ_USE_DMA)
mode |= SDHCI_TRNS_DMA;

最后将上述配置写入传输模式寄存器 SDHCI_TRANSFER_MODE。

1
sdhci_writew(host, mode, SDHCI_TRANSFER_MODE);

结束

2021的第一篇博客/文档,真是写了一年的,希望 2021升职加薪,love&peace。


sdhci_set_transfer_mode函数解析
http://xxxdk.xyz/xxx/2021/01/sdhci-set-transfer-mode函数解析/
作者
sni
发布于
2021年1月1日
许可协议