mmc: sdhci-msm: Add SD card's err_stat sysfs entry

Add sysfs entry to query SD card errors statistics.
This feature is useful for debug and testing.

Change-Id: I15f2599683cbb811ad16796dc43fb34de019506c
Signed-off-by: Bao D. Nguyen <nguyenb@codeaurora.org>
This commit is contained in:
Bao D. Nguyen 2020-08-31 03:29:33 -07:00
commit 459180cc35
5 changed files with 174 additions and 9 deletions

View file

@ -3032,6 +3032,9 @@ void mmc_rescan(struct work_struct *work)
if (freqs[i] <= host->f_min)
break;
}
#if defined(CONFIG_SDC_QTI)
host->err_stats[MMC_ERR_CMD_TIMEOUT] = 0;
#endif
mmc_release_host(host);
out:

View file

@ -672,11 +672,19 @@ int mmc_send_tuning(struct mmc_host *host, u32 opcode, int *cmd_error)
if (cmd.error) {
err = cmd.error;
#if defined(CONFIG_SDC_QTI)
/* Ignore crc errors occurred during tuning */
if (host->err_stats[MMC_ERR_CMD_CRC])
host->err_stats[MMC_ERR_CMD_CRC]--;
#endif
goto out;
}
if (data.error) {
err = data.error;
#if defined(CONFIG_SDC_QTI)
host->err_stats[MMC_ERR_DAT_CRC]--;
#endif
goto out;
}

View file

@ -3586,6 +3586,106 @@ static int sdhci_msm_setup_ice_clk(struct sdhci_msm_host *msm_host,
return ret;
}
#if defined(CONFIG_SDC_QTI)
static ssize_t err_state_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct sdhci_host *host = dev_get_drvdata(dev);
if (!host || !host->mmc)
return -EINVAL;
return scnprintf(buf, PAGE_SIZE, "%d\n", !!host->mmc->err_occurred);
}
static ssize_t err_state_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct sdhci_host *host = dev_get_drvdata(dev);
unsigned int val;
if (kstrtouint(buf, 0, &val))
return -EINVAL;
if (!host || !host->mmc)
return -EINVAL;
host->mmc->err_occurred = !!val;
if (!val)
memset(host->mmc->err_stats, 0, sizeof(host->mmc->err_stats));
return count;
}
static DEVICE_ATTR_RW(err_state);
static ssize_t err_stats_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct sdhci_host *host = dev_get_drvdata(dev);
char tmp[100];
if (!host || !host->mmc)
return -EINVAL;
scnprintf(tmp, sizeof(tmp), "# Command Timeout Error: %d\n",
host->mmc->err_stats[MMC_ERR_CMD_TIMEOUT]);
strlcpy(buf, tmp, PAGE_SIZE);
scnprintf(tmp, sizeof(tmp), "# Command CRC Error: %d\n",
host->mmc->err_stats[MMC_ERR_CMD_CRC]);
strlcat(buf, tmp, PAGE_SIZE);
scnprintf(tmp, sizeof(tmp), "# Data Timeout Error: %d\n",
host->mmc->err_stats[MMC_ERR_DAT_TIMEOUT]);
strlcat(buf, tmp, PAGE_SIZE);
scnprintf(tmp, sizeof(tmp), "# Data CRC Error: %d\n",
host->mmc->err_stats[MMC_ERR_DAT_CRC]);
strlcat(buf, tmp, PAGE_SIZE);
scnprintf(tmp, sizeof(tmp), "# Auto-Cmd Error: %d\n",
host->mmc->err_stats[MMC_ERR_ADMA]);
strlcat(buf, tmp, PAGE_SIZE);
scnprintf(tmp, sizeof(tmp), "# ADMA Error: %d\n",
host->mmc->err_stats[MMC_ERR_ADMA]);
strlcat(buf, tmp, PAGE_SIZE);
scnprintf(tmp, sizeof(tmp), "# Tuning Error: %d\n",
host->mmc->err_stats[MMC_ERR_TUNING]);
strlcat(buf, tmp, PAGE_SIZE);
scnprintf(tmp, sizeof(tmp), "# Request Timedout Error: %d\n",
host->mmc->err_stats[MMC_ERR_REQ_TIMEOUT]);
strlcat(buf, tmp, PAGE_SIZE);
return strlen(buf);
}
static DEVICE_ATTR_RO(err_stats);
static struct attribute *sdhci_msm_sysfs_attrs[] = {
&dev_attr_err_state.attr,
&dev_attr_err_stats.attr,
NULL
};
static const struct attribute_group sdhci_msm_sysfs_group = {
.name = "qcom",
.attrs = sdhci_msm_sysfs_attrs,
};
static int sdhci_msm_init_sysfs(struct platform_device *pdev)
{
int ret;
ret = sysfs_create_group(&pdev->dev.kobj, &sdhci_msm_sysfs_group);
if (ret)
dev_err(&pdev->dev, "%s: Failed sdhci_msm sysfs group err=%d\n",
__func__, ret);
return ret;
}
#endif
static int sdhci_msm_probe(struct platform_device *pdev)
{
struct sdhci_host *host;
@ -3860,7 +3960,9 @@ static int sdhci_msm_probe(struct platform_device *pdev)
* value won't be over-written.
*/
host->mmc->max_busy_timeout = 0;
#if defined(CONFIG_SDC_QTI)
sdhci_msm_init_sysfs(pdev);
#endif
/*
* Set platfm_init_done only after sdhci_add_host().
* So that we don't turn off vqmmc while we reset sdhc as

View file

@ -108,7 +108,9 @@ void sdhci_dumpregs(struct sdhci_host *host)
sdhci_readl(host, SDHCI_ADMA_ADDRESS));
}
}
#if defined(CONFIG_SDC_QTI)
host->mmc->err_occurred = true;
#endif
SDHCI_DUMP("============================================\n");
}
EXPORT_SYMBOL_GPL(sdhci_dumpregs);
@ -2880,6 +2882,9 @@ static void sdhci_timeout_timer(struct timer_list *t)
spin_lock_irqsave(&host->lock, flags);
if (host->cmd && !sdhci_data_line_cmd(host->cmd)) {
#if defined(CONFIG_SDC_QTI)
host->mmc->err_stats[MMC_ERR_REQ_TIMEOUT]++;
#endif
pr_err("%s: Timeout waiting for hardware cmd interrupt.\n",
mmc_hostname(host->mmc));
sdhci_dumpregs(host);
@ -2902,6 +2907,9 @@ static void sdhci_timeout_data_timer(struct timer_list *t)
if (host->data || host->data_cmd ||
(host->cmd && sdhci_data_line_cmd(host->cmd))) {
#if defined(CONFIG_SDC_QTI)
host->mmc->err_stats[MMC_ERR_REQ_TIMEOUT]++;
#endif
pr_err("%s: Timeout waiting for hardware interrupt.\n",
mmc_hostname(host->mmc));
sdhci_dumpregs(host);
@ -2961,11 +2969,17 @@ static void sdhci_cmd_irq(struct sdhci_host *host, u32 intmask, u32 *intmask_p)
if (intmask & (SDHCI_INT_TIMEOUT | SDHCI_INT_CRC |
SDHCI_INT_END_BIT | SDHCI_INT_INDEX)) {
if (intmask & SDHCI_INT_TIMEOUT)
if (intmask & SDHCI_INT_TIMEOUT) {
host->cmd->error = -ETIMEDOUT;
else
#if defined(CONFIG_SDC_QTI)
host->mmc->err_stats[MMC_ERR_CMD_TIMEOUT]++;
#endif
} else {
host->cmd->error = -EILSEQ;
#if defined(CONFIG_SDC_QTI)
host->mmc->err_stats[MMC_ERR_CMD_CRC]++;
#endif
}
/* Treat data command CRC error the same as data CRC error */
if (host->cmd->data &&
(intmask & (SDHCI_INT_CRC | SDHCI_INT_TIMEOUT)) ==
@ -2986,7 +3000,9 @@ static void sdhci_cmd_irq(struct sdhci_host *host, u32 intmask, u32 *intmask_p)
int err = (auto_cmd_status & SDHCI_AUTO_CMD_TIMEOUT) ?
-ETIMEDOUT :
-EILSEQ;
#if defined(CONFIG_SDC_QTI)
host->mmc->err_stats[MMC_ERR_AUTO_CMD]++;
#endif
if (mrq->sbc && (host->flags & SDHCI_AUTO_CMD23)) {
mrq->sbc->error = err;
__sdhci_finish_mrq(host, mrq);
@ -3057,6 +3073,9 @@ static void sdhci_data_irq(struct sdhci_host *host, u32 intmask)
if (intmask & SDHCI_INT_DATA_TIMEOUT) {
host->data_cmd = NULL;
data_cmd->error = -ETIMEDOUT;
#if defined(CONFIG_SDC_QTI)
host->mmc->err_stats[MMC_ERR_CMD_TIMEOUT]++;
#endif
__sdhci_finish_mrq(host, data_cmd->mrq);
return;
}
@ -3090,18 +3109,29 @@ static void sdhci_data_irq(struct sdhci_host *host, u32 intmask)
return;
}
if (intmask & SDHCI_INT_DATA_TIMEOUT)
if (intmask & SDHCI_INT_DATA_TIMEOUT) {
host->data->error = -ETIMEDOUT;
#if defined(CONFIG_SDC_QTI)
host->mmc->err_stats[MMC_ERR_DAT_TIMEOUT]++;
#endif
}
else if (intmask & SDHCI_INT_DATA_END_BIT)
host->data->error = -EILSEQ;
else if ((intmask & SDHCI_INT_DATA_CRC) &&
SDHCI_GET_CMD(sdhci_readw(host, SDHCI_COMMAND))
!= MMC_BUS_TEST_R)
!= MMC_BUS_TEST_R) {
host->data->error = -EILSEQ;
#if defined(CONFIG_SDC_QTI)
host->mmc->err_stats[MMC_ERR_DAT_CRC]++;
#endif
}
else if (intmask & SDHCI_INT_ADMA_ERROR) {
pr_err("%s: ADMA error: 0x%08x\n", mmc_hostname(host->mmc),
intmask);
sdhci_adma_show_error(host);
#if defined(CONFIG_SDC_QTI)
host->mmc->err_stats[MMC_ERR_ADMA]++;
#endif
host->data->error = -EIO;
if (host->ops->adma_workaround)
host->ops->adma_workaround(host, intmask);

View file

@ -82,6 +82,25 @@ struct mmc_ios {
struct mmc_host;
#if defined(CONFIG_SDC_QTI)
enum {
MMC_ERR_CMD_TIMEOUT,
MMC_ERR_CMD_CRC,
MMC_ERR_DAT_TIMEOUT,
MMC_ERR_DAT_CRC,
MMC_ERR_AUTO_CMD,
MMC_ERR_ADMA,
MMC_ERR_TUNING,
MMC_ERR_CMDQ_RED,
MMC_ERR_CMDQ_GCE,
MMC_ERR_CMDQ_ICCE,
MMC_ERR_REQ_TIMEOUT,
MMC_ERR_CMDQ_REQ_TIMEOUT,
MMC_ERR_ICE_CFG,
MMC_ERR_MAX,
};
#endif
struct mmc_host_ops {
/*
* It is optional for the host to implement pre_req and post_req in
@ -518,7 +537,10 @@ struct mmc_host {
struct mmc_supply supply;
struct dentry *debugfs_root;
#if defined(CONFIG_SDC_QTI)
bool err_occurred;
u32 err_stats[MMC_ERR_MAX];
#endif
/* Ongoing data transfer that allows commands during transfer */
struct mmc_request *ongoing_mrq;