[PATCH v3 1/2] mmc: cqhci: Add support for setting the Send Status Command Idle Timer

From: Changhuang Liang

Date: Mon Nov 09 2026 - 03:23:04 EST


Add cit_ns and cit members to struct cqhci_host so that host drivers can
set the Send Status Command Idle Timer (CIT) in CQHCI_SSC1, either in
nanoseconds or directly in internal timer clock cycles.

The CIT defines how long the bus has to be idle, in internal timer clock
cycles, before the CQE issues CMD13 to poll the Queue Status Register.
Its reset value is controller specific and can be far from optimal, so
host drivers may need to tune it for their platform.

If cit_ns is non-zero, cqhci_init() converts it to cycles of the internal
timer clock frequency (ITCF) derived from the ITCFMUL and ITCFVAL fields
of the CQHCI capabilities register. The result is rounded up so that the
programmed idle time is never shorter than requested. Expressing the
time in nanoseconds lets platforms with different ITCF share the same
setting. If the ITCF is not reported or the result does not fit the
16-bit CIT field, a warning is printed and cit is left unchanged. If
both are set, cit_ns takes precedence over cit.

If cit is non-zero, program it into CQHCI_SSC1 every time the CQE is
enabled in __cqhci_enable().

Signed-off-by: Changhuang Liang <changhuang.liang@xxxxxxxxxxxxxxxx>
---
drivers/mmc/host/cqhci-core.c | 45 +++++++++++++++++++++++++++++++++++
drivers/mmc/host/cqhci.h | 13 ++++++++++
2 files changed, 58 insertions(+)

diff --git a/drivers/mmc/host/cqhci-core.c b/drivers/mmc/host/cqhci-core.c
index a466cd88b043..58182561e14e 100644
--- a/drivers/mmc/host/cqhci-core.c
+++ b/drivers/mmc/host/cqhci-core.c
@@ -279,6 +279,14 @@ static void __cqhci_enable(struct cqhci_host *cq_host)
cqhci_writel(cq_host, upper_32_bits(cq_host->desc_dma_base),
CQHCI_TDLBAU);

+ if (cq_host->cit) {
+ u32 ssc1 = cqhci_readl(cq_host, CQHCI_SSC1);
+
+ ssc1 &= ~CQHCI_SSC1_CIT_MASK;
+ ssc1 |= FIELD_PREP(CQHCI_SSC1_CIT_MASK, cq_host->cit);
+ cqhci_writel(cq_host, ssc1, CQHCI_SSC1);
+ }
+
cqhci_writel(cq_host, cq_host->rca, CQHCI_SSC2);

cqhci_set_irqs(cq_host, 0);
@@ -1163,6 +1171,41 @@ struct cqhci_host *cqhci_pltfm_init(struct platform_device *pdev)
}
EXPORT_SYMBOL(cqhci_pltfm_init);

+static void cqhci_init_cit(struct cqhci_host *cq_host)
+{
+ u32 cap, itcfmul, itcfval;
+ u64 cit;
+
+ if (!cq_host->cit_ns)
+ return;
+
+ cap = cqhci_readl(cq_host, CQHCI_CAP);
+ itcfmul = CQHCI_ITCFMUL(cap);
+ itcfval = CQHCI_ITCFVAL(cap);
+
+ /* ITCFMUL: 0 = 1 kHz, 1 = 10 kHz, ..., 4 = 10 MHz, others reserved */
+ if (itcfmul > 4 || !itcfval) {
+ pr_warn("%s: cqhci: internal timer clock frequency not available (CAP 0x%08x), CIT not set\n",
+ mmc_hostname(cq_host->mmc), cap);
+ return;
+ }
+
+ /*
+ * ITCF in kHz is int_pow(10, itcfmul) * itcfval, so kHz * ns / 10^6
+ * gives the number of cycles. Round up so that the idle time is never
+ * shorter than requested.
+ */
+ cit = DIV_ROUND_UP_ULL(int_pow(10, itcfmul) * itcfval * cq_host->cit_ns,
+ USEC_PER_SEC);
+ if (cit > FIELD_MAX(CQHCI_SSC1_CIT_MASK)) {
+ pr_warn("%s: cqhci: CIT %u ns out of range, CIT not set\n",
+ mmc_hostname(cq_host->mmc), cq_host->cit_ns);
+ return;
+ }
+
+ cq_host->cit = cit;
+}
+
static unsigned int cqhci_ver_major(struct cqhci_host *cq_host)
{
return CQHCI_VER_MAJOR(cqhci_readl(cq_host, CQHCI_VER));
@@ -1207,6 +1250,8 @@ int cqhci_init(struct cqhci_host *cq_host, struct mmc_host *mmc,
goto out_err;
}

+ cqhci_init_cit(cq_host);
+
spin_lock_init(&cq_host->lock);

init_completion(&cq_host->halt_comp);
diff --git a/drivers/mmc/host/cqhci.h b/drivers/mmc/host/cqhci.h
index 8fbbc48c3f85..7381a2104449 100644
--- a/drivers/mmc/host/cqhci.h
+++ b/drivers/mmc/host/cqhci.h
@@ -26,6 +26,8 @@
#define CQHCI_CAP_CS 0x10000000 /* Crypto Support */
#define CQHCI_CAP_ITCFMUL GENMASK(15, 12)
#define CQHCI_ITCFMUL(x) FIELD_GET(CQHCI_CAP_ITCFMUL, (x))
+#define CQHCI_CAP_ITCFVAL GENMASK(9, 0)
+#define CQHCI_ITCFVAL(x) FIELD_GET(CQHCI_CAP_ITCFVAL, (x))

/* configuration */
#define CQHCI_CFG 0x08
@@ -234,6 +236,17 @@ struct cqhci_host {
int num_slots;
int qcnt;

+ /*
+ * Send Status Command Idle Timer (CIT), set by host drivers before
+ * calling cqhci_init(), either in nanoseconds via @cit_ns or directly
+ * in internal timer clock cycles via @cit. A non-zero @cit_ns takes
+ * precedence and is converted to @cit by cqhci_init(). If the
+ * conversion fails, @cit is left unchanged. A zero @cit keeps the
+ * reset default.
+ */
+ u32 cit_ns;
+ u16 cit;
+
u32 dcmd_slot;
u32 caps;
#define CQHCI_TASK_DESC_SZ_128 0x1
--
2.25.1