drivers/mmc/host/rtsx_usb_sdmmc.c | 156 ++++++++++++++++++++++++++++-- 1 file changed, 148 insertions(+), 8 deletions(-)
Some Realtek USB SD readers with a tray can assert SD_CD even when no
card is present. This can make the MMC core believe a card exists, and
may trigger unnecessary initialization and suspend failures.
Debounce the CD signal and validate a newly detected card by probing for
a response (CMD0 + CMD8/CMD55/CMD1) before reporting it present. Also
treat SD_INT as a removal indication even if SD_CD stays asserted.
Tested on a Realtek RTS5129 USB reader (0bda:0129) with tray inserted
and no card. No mmc device was created, and suspend pm_test=devices
passed.
Tested-by: Sean Rhodes <sean@starlabs.systems>
Signed-off-by: Sean Rhodes <sean@starlabs.systems>
---
drivers/mmc/host/rtsx_usb_sdmmc.c | 156 ++++++++++++++++++++++++++++--
1 file changed, 148 insertions(+), 8 deletions(-)
diff --git a/drivers/mmc/host/rtsx_usb_sdmmc.c b/drivers/mmc/host/rtsx_usb_sdmmc.c
index 84674659a84d..ec3eeea78e95 100644
--- a/drivers/mmc/host/rtsx_usb_sdmmc.c
+++ b/drivers/mmc/host/rtsx_usb_sdmmc.c
@@ -19,6 +19,7 @@
#include <linux/scatterlist.h>
#include <linux/pm.h>
#include <linux/pm_runtime.h>
+#include <linux/jiffies.h>
#include <linux/rtsx_usb.h>
#include <linux/unaligned.h>
@@ -30,6 +31,9 @@
#define RTSX_USB_USE_LEDS_CLASS
#endif
+#define RTSX_USB_SD_CD_DEBOUNCE_CNT 2
+#define RTSX_USB_SD_INSERT_RETRY_MS 1000
+
struct rtsx_usb_sdmmc {
struct platform_device *pdev;
struct rtsx_ucr *ucr;
@@ -46,6 +50,8 @@ struct rtsx_usb_sdmmc {
bool card_exist;
bool initial_mode;
bool ddr_mode;
+ u8 cd_debounce;
+ unsigned long next_insert_check;
unsigned char power_mode;
u16 ocp_stat;
@@ -72,6 +78,13 @@ static inline void sd_clear_error(struct rtsx_usb_sdmmc *host)
rtsx_usb_clear_fsm_err(ucr);
}
+static int sd_set_bus_width(struct rtsx_usb_sdmmc *host,
+ unsigned char bus_width);
+static int sd_set_timing(struct rtsx_usb_sdmmc *host,
+ unsigned char timing, bool *ddr_mode);
+static int sd_power_on(struct rtsx_usb_sdmmc *host);
+static int sd_power_off(struct rtsx_usb_sdmmc *host);
+
#ifdef DEBUG
static void sd_print_debug_regs(struct rtsx_usb_sdmmc *host)
{
@@ -768,12 +781,94 @@ static int sdmmc_get_ro(struct mmc_host *mmc)
return 0;
}
+static bool sdmmc_validate_insert_locked(struct rtsx_usb_sdmmc *host)
+{
+ struct rtsx_ucr *ucr = host->ucr;
+ struct mmc_command cmd = { };
+ int err = 0;
+ bool probe_powered = false;
+ bool ddr_mode = false;
+
+ /*
+ * Some readers with a tray assert the mechanical SD_CD pin even when no
+ * card is present. Only report a card present when it responds to a
+ * minimal reset/probe sequence, similar to the old rts5139 behavior.
+ *
+ * Must be called with ucr->dev_mutex held.
+ */
+ if (host->power_mode == MMC_POWER_OFF) {
+ err = sd_power_on(host);
+ if (err)
+ return false;
+ probe_powered = true;
+
+ /* Issue clock signals to card for at least 74 clocks. */
+ rtsx_usb_write_register(ucr, SD_BUS_STAT,
+ SD_CLK_TOGGLE_EN, SD_CLK_TOGGLE_EN);
+ usleep_range(200, 400);
+ rtsx_usb_write_register(ucr, SD_BUS_STAT, SD_CLK_TOGGLE_EN, 0);
+ }
+
+ /*
+ * Ensure the interface is in a safe, legacy / initial-clock mode before
+ * probing for a response. The MMC core may not have configured ios yet.
+ */
+ err = sd_set_bus_width(host, MMC_BUS_WIDTH_1);
+ if (err)
+ goto out;
+
+ err = sd_set_timing(host, MMC_TIMING_LEGACY, &ddr_mode);
+ if (err)
+ goto out;
+
+ ucr->cur_clk = 0;
+ err = rtsx_usb_switch_clock(ucr, 400000, SSC_DEPTH_512K,
+ true, true, false);
+ if (err)
+ goto out;
+
+ cmd.opcode = MMC_GO_IDLE_STATE;
+ cmd.arg = 0;
+ cmd.flags = MMC_RSP_NONE | MMC_CMD_BC;
+ sd_send_cmd_get_rsp(host, &cmd);
+
+ /* SD v2.0+: CMD8 */
+ cmd.opcode = SD_SEND_IF_COND;
+ cmd.arg = 0x1aa;
+ cmd.flags = MMC_RSP_R7 | MMC_CMD_BCR;
+ sd_send_cmd_get_rsp(host, &cmd);
+ if (!cmd.error)
+ goto out;
+
+ /* SD v1.x: CMD55 */
+ cmd.opcode = MMC_APP_CMD;
+ cmd.arg = 0;
+ cmd.flags = MMC_RSP_R1 | MMC_CMD_AC;
+ sd_send_cmd_get_rsp(host, &cmd);
+ if (!cmd.error)
+ goto out;
+
+ /* MMC: CMD1 */
+ cmd.opcode = MMC_SEND_OP_COND;
+ cmd.arg = 0;
+ cmd.flags = MMC_RSP_R3 | MMC_CMD_BCR;
+ sd_send_cmd_get_rsp(host, &cmd);
+
+out:
+ if (probe_powered)
+ sd_power_off(host);
+ return !err && !cmd.error;
+}
+
static int sdmmc_get_cd(struct mmc_host *mmc)
{
struct rtsx_usb_sdmmc *host = mmc_priv(mmc);
struct rtsx_ucr *ucr = host->ucr;
int err;
u16 val;
+ u8 pend;
+ bool sd_int = false;
+ bool cd_raw = false;
if (host->host_removal)
return -ENOMEDIUM;
@@ -782,28 +877,71 @@ static int sdmmc_get_cd(struct mmc_host *mmc)
/* Check SD card detect */
err = rtsx_usb_get_card_status(ucr, &val);
-
- mutex_unlock(&ucr->dev_mutex);
-
- /* Treat failed detection as non-exist */
if (err)
- goto no_card;
+ goto no_card_unlock;
/* get OCP status */
host->ocp_stat = (val >> 4) & 0x03;
- if (val & SD_CD) {
- host->card_exist = true;
+ cd_raw = !!(val & SD_CD);
+
+ /* Use SD_INT as a reliable removal indication on some tray readers. */
+ err = rtsx_usb_read_register(ucr, CARD_INT_PEND, &pend);
+ if (!err) {
+ sd_int = !!(pend & SD_INT);
+ if (sd_int)
+ rtsx_usb_write_register(ucr, CARD_INT_PEND,
+ SD_INT, SD_INT);
+ }
+
+ if (!cd_raw) {
+ host->cd_debounce = 0;
+ host->next_insert_check = 0;
+ goto no_card_unlock;
+ }
+
+ /*
+ * rts5139-style: when a card is already known present, treat SD_INT as
+ * a removal event even if SD_CD stays high (e.g. tray-based readers).
+ */
+ if (host->card_exist) {
+ if (sd_int) {
+ host->cd_debounce = 0;
+ host->next_insert_check = 0;
+ goto no_card_unlock;
+ }
+ mutex_unlock(&ucr->dev_mutex);
return 1;
}
-no_card:
+ /* Debounce mechanical CD before probing for a response. */
+ if (host->cd_debounce < RTSX_USB_SD_CD_DEBOUNCE_CNT) {
+ host->cd_debounce++;
+ goto no_card_unlock;
+ }
+
+ /* Avoid pounding the bus with probes if CD is stuck asserted. */
+ if (time_before(jiffies, host->next_insert_check))
+ goto no_card_unlock;
+
+ if (!sdmmc_validate_insert_locked(host)) {
+ host->next_insert_check = jiffies +
+ msecs_to_jiffies(RTSX_USB_SD_INSERT_RETRY_MS);
+ goto no_card_unlock;
+ }
+
+ host->card_exist = true;
+ mutex_unlock(&ucr->dev_mutex);
+ return 1;
+
+no_card_unlock:
/* clear OCP status */
if (host->ocp_stat & (MS_OCP_NOW | MS_OCP_EVER)) {
rtsx_usb_write_register(ucr, OCPCTL, MS_OCP_CLEAR, MS_OCP_CLEAR);
host->ocp_stat = 0;
}
host->card_exist = false;
+ mutex_unlock(&ucr->dev_mutex);
return 0;
}
@@ -1359,6 +1497,8 @@ static void rtsx_usb_init_host(struct rtsx_usb_sdmmc *host)
host->power_mode = MMC_POWER_OFF;
host->ocp_stat = 0;
+ host->cd_debounce = 0;
+ host->next_insert_check = 0;
}
static int rtsx_usb_sdmmc_drv_probe(struct platform_device *pdev)
Hi,
Some Realtek USB SD readers with a tray can assert SD_CD when only the
tray is inserted. This can lead to false card-detect, unnecessary card
initialization, and in the worst case suspend/shutdown failures when the
MMC core waits for a non-existent card.
This series hardens card detection by debouncing SD_CD and validating a
new insertion by probing for card responses before reporting it present.
It then brings rtsx_usb_sdmmc closer to the previously shipped Realtek
behaviour (excluding xD/CPRM) for power-up and UHS signalling, and fixes
runtime PM corner cases around USB transfers.
The insertion validation approach is intentionally conservative and is
aligned with the older Realtek rts5139 staging driver (drivers/staging/
rts5139), which was removed in:
00d8521dcd236d1b8f664f54a0309e96bfdcb4f9
("staging: remove rts5139 driver code", 2014-05-23)
This is a resend as v2; the original standalone submission of patch 1
was sent on 2026-02-19 with Message-ID:
<1ca7b488a11e03b3f107f1829a40cf2c92c7d5fd.1771533586.git.sean@starlabs.systems>
Testing (v6.18-rc4 based):
- Readers: Realtek RTS5129, Realtek RTS5170, Realtek
RTS51379
- Tests (all readers): cold-boot detection (card present at power-on),
hotplug insert/remove, and suspend pm_test=devices
- Tray behaviour (tray readers): tray inserted/no card does not create a
phantom mmc device; insert/remove transitions are reported correctly
- Card modes/speeds: UHS-I SDXC cards negotiate UHS SDR104 (1.8V,
208MHz) where supported by the reader/card; non-UHS cards fall back to
SD High Speed (50MHz)
Thanks,
Sean Rhodes
Sean Rhodes (6):
mmc: rtsx_usb_sdmmc: avoid false card-detect on tray readers
mmc: rtsx_usb_sdmmc: start card power-up at 3.3V
mmc: rtsx_usb_sdmmc: advertise UHS SDR104 and DDR50
mmc: rtsx_usb_sdmmc: program SD30 mode for UHS SDR12/SDR25
rtsx_usb: hold runtime PM during transfers
rtsx_usb: avoid USB I/O in runtime autosuspend
drivers/misc/cardreader/rtsx_usb.c | 49 +++++++--
drivers/mmc/host/rtsx_usb_sdmmc.c | 167 +++++++++++++++++++++++++++--
include/linux/rtsx_usb.h | 3 +
3 files changed, 199 insertions(+), 20 deletions(-)
--
2.51.0
Hi,
Some Realtek USB SD readers with a tray can assert SD_CD when only the
tray is inserted. This can lead to false card-detect, unnecessary card
initialization, and in the worst case suspend/shutdown failures when the
MMC core waits for a non-existent card.
This series hardens card detection by debouncing SD_CD and validating a
new insertion by probing for card responses before reporting it present.
It then brings rtsx_usb_sdmmc closer to the previously shipped Realtek
behaviour (excluding xD/CPRM) for power-up and UHS signalling, and fixes
runtime PM corner cases around USB transfers.
The insertion validation approach is intentionally conservative and is
aligned with the older Realtek rts5139 staging driver (drivers/staging/
rts5139), which was removed in:
00d8521dcd236d1b8f664f54a0309e96bfdcb4f9
("staging: remove rts5139 driver code", 2014-05-23)
This is a resend as v2; the original standalone submission of patch 1
was sent on 2026-02-19 with Message-ID:
<1ca7b488a11e03b3f107f1829a40cf2c92c7d5fd.1771533586.git.sean@starlabs.systems>
No changes since v2. Resending for visibility.
Testing (v6.18-rc4 based):
- Readers: Realtek RTS5129, Realtek RTS5170, Realtek
RTS51379
- Tests (all readers): cold-boot detection (card present at power-on),
hotplug insert/remove, and suspend pm_test=devices
- Tray behaviour (tray readers): tray inserted/no card does not create a
phantom mmc device; insert/remove transitions are reported correctly
- Card modes/speeds: UHS-I SDXC cards negotiate UHS SDR104 (1.8V,
208MHz) where supported by the reader/card; non-UHS cards fall back to
SD High Speed (50MHz)
Thanks,
Sean Rhodes
Sean Rhodes (6):
mmc: rtsx_usb_sdmmc: avoid false card-detect on tray readers
mmc: rtsx_usb_sdmmc: start card power-up at 3.3V
mmc: rtsx_usb_sdmmc: advertise UHS SDR104 and DDR50
mmc: rtsx_usb_sdmmc: program SD30 mode for UHS SDR12/SDR25
rtsx_usb: hold runtime PM during transfers
rtsx_usb: avoid USB I/O in runtime autosuspend
drivers/misc/cardreader/rtsx_usb.c | 49 +++++++--
drivers/mmc/host/rtsx_usb_sdmmc.c | 167 +++++++++++++++++++++++++++--
include/linux/rtsx_usb.h | 3 +
3 files changed, 199 insertions(+), 20 deletions(-)
--
2.51.0
Some Realtek USB SD readers with a tray can assert SD_CD even when no
card is present. This can make the MMC core believe a card exists and
trigger unnecessary initialization and suspend/shutdown failures.
Debounce the CD signal and validate a newly detected card by probing for
a response (CMD0 + CMD8/CMD55/CMD1) before reporting it present. Also
treat SD_INT as a removal indication even if SD_CD stays asserted.
Tested: Realtek RTS5129 (0bda:0129), tray inserted, no card (2026-02-24)
Tested: Realtek RTS5129 (0bda:0129), tray + SDXC card, mmcblk0 (2026-02-24)
Signed-off-by: Sean Rhodes <sean@starlabs.systems>
---
drivers/mmc/host/rtsx_usb_sdmmc.c | 156 ++++++++++++++++++++++++++++--
1 file changed, 148 insertions(+), 8 deletions(-)
diff --git a/drivers/mmc/host/rtsx_usb_sdmmc.c b/drivers/mmc/host/rtsx_usb_sdmmc.c
index 84674659a84d..ec3eeea78e95 100644
--- a/drivers/mmc/host/rtsx_usb_sdmmc.c
+++ b/drivers/mmc/host/rtsx_usb_sdmmc.c
@@ -19,6 +19,7 @@
#include <linux/scatterlist.h>
#include <linux/pm.h>
#include <linux/pm_runtime.h>
+#include <linux/jiffies.h>
#include <linux/rtsx_usb.h>
#include <linux/unaligned.h>
@@ -30,6 +31,9 @@
#define RTSX_USB_USE_LEDS_CLASS
#endif
+#define RTSX_USB_SD_CD_DEBOUNCE_CNT 2
+#define RTSX_USB_SD_INSERT_RETRY_MS 1000
+
struct rtsx_usb_sdmmc {
struct platform_device *pdev;
struct rtsx_ucr *ucr;
@@ -46,6 +50,8 @@ struct rtsx_usb_sdmmc {
bool card_exist;
bool initial_mode;
bool ddr_mode;
+ u8 cd_debounce;
+ unsigned long next_insert_check;
unsigned char power_mode;
u16 ocp_stat;
@@ -72,6 +78,13 @@ static inline void sd_clear_error(struct rtsx_usb_sdmmc *host)
rtsx_usb_clear_fsm_err(ucr);
}
+static int sd_set_bus_width(struct rtsx_usb_sdmmc *host,
+ unsigned char bus_width);
+static int sd_set_timing(struct rtsx_usb_sdmmc *host,
+ unsigned char timing, bool *ddr_mode);
+static int sd_power_on(struct rtsx_usb_sdmmc *host);
+static int sd_power_off(struct rtsx_usb_sdmmc *host);
+
#ifdef DEBUG
static void sd_print_debug_regs(struct rtsx_usb_sdmmc *host)
{
@@ -768,12 +781,94 @@ static int sdmmc_get_ro(struct mmc_host *mmc)
return 0;
}
+static bool sdmmc_validate_insert_locked(struct rtsx_usb_sdmmc *host)
+{
+ struct rtsx_ucr *ucr = host->ucr;
+ struct mmc_command cmd = { };
+ int err = 0;
+ bool probe_powered = false;
+ bool ddr_mode = false;
+
+ /*
+ * Some readers with a tray assert the mechanical SD_CD pin even when no
+ * card is present. Only report a card present when it responds to a
+ * minimal reset/probe sequence, similar to the old rts5139 behavior.
+ *
+ * Must be called with ucr->dev_mutex held.
+ */
+ if (host->power_mode == MMC_POWER_OFF) {
+ err = sd_power_on(host);
+ if (err)
+ return false;
+ probe_powered = true;
+
+ /* Issue clock signals to card for at least 74 clocks. */
+ rtsx_usb_write_register(ucr, SD_BUS_STAT,
+ SD_CLK_TOGGLE_EN, SD_CLK_TOGGLE_EN);
+ usleep_range(200, 400);
+ rtsx_usb_write_register(ucr, SD_BUS_STAT, SD_CLK_TOGGLE_EN, 0);
+ }
+
+ /*
+ * Ensure the interface is in a safe, legacy / initial-clock mode before
+ * probing for a response. The MMC core may not have configured ios yet.
+ */
+ err = sd_set_bus_width(host, MMC_BUS_WIDTH_1);
+ if (err)
+ goto out;
+
+ err = sd_set_timing(host, MMC_TIMING_LEGACY, &ddr_mode);
+ if (err)
+ goto out;
+
+ ucr->cur_clk = 0;
+ err = rtsx_usb_switch_clock(ucr, 400000, SSC_DEPTH_512K,
+ true, true, false);
+ if (err)
+ goto out;
+
+ cmd.opcode = MMC_GO_IDLE_STATE;
+ cmd.arg = 0;
+ cmd.flags = MMC_RSP_NONE | MMC_CMD_BC;
+ sd_send_cmd_get_rsp(host, &cmd);
+
+ /* SD v2.0+: CMD8 */
+ cmd.opcode = SD_SEND_IF_COND;
+ cmd.arg = 0x1aa;
+ cmd.flags = MMC_RSP_R7 | MMC_CMD_BCR;
+ sd_send_cmd_get_rsp(host, &cmd);
+ if (!cmd.error)
+ goto out;
+
+ /* SD v1.x: CMD55 */
+ cmd.opcode = MMC_APP_CMD;
+ cmd.arg = 0;
+ cmd.flags = MMC_RSP_R1 | MMC_CMD_AC;
+ sd_send_cmd_get_rsp(host, &cmd);
+ if (!cmd.error)
+ goto out;
+
+ /* MMC: CMD1 */
+ cmd.opcode = MMC_SEND_OP_COND;
+ cmd.arg = 0;
+ cmd.flags = MMC_RSP_R3 | MMC_CMD_BCR;
+ sd_send_cmd_get_rsp(host, &cmd);
+
+out:
+ if (probe_powered)
+ sd_power_off(host);
+ return !err && !cmd.error;
+}
+
static int sdmmc_get_cd(struct mmc_host *mmc)
{
struct rtsx_usb_sdmmc *host = mmc_priv(mmc);
struct rtsx_ucr *ucr = host->ucr;
int err;
u16 val;
+ u8 pend;
+ bool sd_int = false;
+ bool cd_raw = false;
if (host->host_removal)
return -ENOMEDIUM;
@@ -782,28 +877,71 @@ static int sdmmc_get_cd(struct mmc_host *mmc)
/* Check SD card detect */
err = rtsx_usb_get_card_status(ucr, &val);
-
- mutex_unlock(&ucr->dev_mutex);
-
- /* Treat failed detection as non-exist */
if (err)
- goto no_card;
+ goto no_card_unlock;
/* get OCP status */
host->ocp_stat = (val >> 4) & 0x03;
- if (val & SD_CD) {
- host->card_exist = true;
+ cd_raw = !!(val & SD_CD);
+
+ /* Use SD_INT as a reliable removal indication on some tray readers. */
+ err = rtsx_usb_read_register(ucr, CARD_INT_PEND, &pend);
+ if (!err) {
+ sd_int = !!(pend & SD_INT);
+ if (sd_int)
+ rtsx_usb_write_register(ucr, CARD_INT_PEND,
+ SD_INT, SD_INT);
+ }
+
+ if (!cd_raw) {
+ host->cd_debounce = 0;
+ host->next_insert_check = 0;
+ goto no_card_unlock;
+ }
+
+ /*
+ * rts5139-style: when a card is already known present, treat SD_INT as
+ * a removal event even if SD_CD stays high (e.g. tray-based readers).
+ */
+ if (host->card_exist) {
+ if (sd_int) {
+ host->cd_debounce = 0;
+ host->next_insert_check = 0;
+ goto no_card_unlock;
+ }
+ mutex_unlock(&ucr->dev_mutex);
return 1;
}
-no_card:
+ /* Debounce mechanical CD before probing for a response. */
+ if (host->cd_debounce < RTSX_USB_SD_CD_DEBOUNCE_CNT) {
+ host->cd_debounce++;
+ goto no_card_unlock;
+ }
+
+ /* Avoid pounding the bus with probes if CD is stuck asserted. */
+ if (time_before(jiffies, host->next_insert_check))
+ goto no_card_unlock;
+
+ if (!sdmmc_validate_insert_locked(host)) {
+ host->next_insert_check = jiffies +
+ msecs_to_jiffies(RTSX_USB_SD_INSERT_RETRY_MS);
+ goto no_card_unlock;
+ }
+
+ host->card_exist = true;
+ mutex_unlock(&ucr->dev_mutex);
+ return 1;
+
+no_card_unlock:
/* clear OCP status */
if (host->ocp_stat & (MS_OCP_NOW | MS_OCP_EVER)) {
rtsx_usb_write_register(ucr, OCPCTL, MS_OCP_CLEAR, MS_OCP_CLEAR);
host->ocp_stat = 0;
}
host->card_exist = false;
+ mutex_unlock(&ucr->dev_mutex);
return 0;
}
@@ -1359,6 +1497,8 @@ static void rtsx_usb_init_host(struct rtsx_usb_sdmmc *host)
host->power_mode = MMC_POWER_OFF;
host->ocp_stat = 0;
+ host->cd_debounce = 0;
+ host->next_insert_check = 0;
}
static int rtsx_usb_sdmmc_drv_probe(struct platform_device *pdev)
--
2.51.0
+ Matthew Schwartz
On Thu, 12 Mar 2026 at 13:16, Sean Rhodes <sean@starlabs.systems> wrote:
>
> Some Realtek USB SD readers with a tray can assert SD_CD even when no
> card is present. This can make the MMC core believe a card exists and
> trigger unnecessary initialization and suspend/shutdown failures.
Would you mind elaborating on this so I can better understand the problem?
>
> Debounce the CD signal and validate a newly detected card by probing for
> a response (CMD0 + CMD8/CMD55/CMD1) before reporting it present. Also
> treat SD_INT as a removal indication even if SD_CD stays asserted.
What prevents the MMC core from handling this correctly?
Don't some of the commands time out, eventually causing mmc_rescan()
to bail out?
>
> Tested: Realtek RTS5129 (0bda:0129), tray inserted, no card (2026-02-24)
> Tested: Realtek RTS5129 (0bda:0129), tray + SDXC card, mmcblk0 (2026-02-24)
>
> Signed-off-by: Sean Rhodes <sean@starlabs.systems>
I have looped in Matthew who recently worked on some similar problems
for the PCI variant [1] of the driver. Perhaps he and Ricky have some
input on this series too.
[1]
https://lore.kernel.org/all/20260105060236.400366-1-matthew.schwartz@linux.dev/
Please see a few more comments below.
> ---
> drivers/mmc/host/rtsx_usb_sdmmc.c | 156 ++++++++++++++++++++++++++++--
> 1 file changed, 148 insertions(+), 8 deletions(-)
>
> diff --git a/drivers/mmc/host/rtsx_usb_sdmmc.c b/drivers/mmc/host/rtsx_usb_sdmmc.c
> index 84674659a84d..ec3eeea78e95 100644
> --- a/drivers/mmc/host/rtsx_usb_sdmmc.c
> +++ b/drivers/mmc/host/rtsx_usb_sdmmc.c
> @@ -19,6 +19,7 @@
> #include <linux/scatterlist.h>
> #include <linux/pm.h>
> #include <linux/pm_runtime.h>
> +#include <linux/jiffies.h>
>
> #include <linux/rtsx_usb.h>
> #include <linux/unaligned.h>
> @@ -30,6 +31,9 @@
> #define RTSX_USB_USE_LEDS_CLASS
> #endif
>
> +#define RTSX_USB_SD_CD_DEBOUNCE_CNT 2
> +#define RTSX_USB_SD_INSERT_RETRY_MS 1000
> +
> struct rtsx_usb_sdmmc {
> struct platform_device *pdev;
> struct rtsx_ucr *ucr;
> @@ -46,6 +50,8 @@ struct rtsx_usb_sdmmc {
> bool card_exist;
> bool initial_mode;
> bool ddr_mode;
> + u8 cd_debounce;
> + unsigned long next_insert_check;
>
> unsigned char power_mode;
> u16 ocp_stat;
> @@ -72,6 +78,13 @@ static inline void sd_clear_error(struct rtsx_usb_sdmmc *host)
> rtsx_usb_clear_fsm_err(ucr);
> }
>
> +static int sd_set_bus_width(struct rtsx_usb_sdmmc *host,
> + unsigned char bus_width);
> +static int sd_set_timing(struct rtsx_usb_sdmmc *host,
> + unsigned char timing, bool *ddr_mode);
> +static int sd_power_on(struct rtsx_usb_sdmmc *host);
> +static int sd_power_off(struct rtsx_usb_sdmmc *host);
> +
> #ifdef DEBUG
> static void sd_print_debug_regs(struct rtsx_usb_sdmmc *host)
> {
> @@ -768,12 +781,94 @@ static int sdmmc_get_ro(struct mmc_host *mmc)
> return 0;
> }
>
> +static bool sdmmc_validate_insert_locked(struct rtsx_usb_sdmmc *host)
> +{
> + struct rtsx_ucr *ucr = host->ucr;
> + struct mmc_command cmd = { };
> + int err = 0;
> + bool probe_powered = false;
> + bool ddr_mode = false;
> +
> + /*
> + * Some readers with a tray assert the mechanical SD_CD pin even when no
> + * card is present. Only report a card present when it responds to a
> + * minimal reset/probe sequence, similar to the old rts5139 behavior.
> + *
> + * Must be called with ucr->dev_mutex held.
> + */
> + if (host->power_mode == MMC_POWER_OFF) {
> + err = sd_power_on(host);
> + if (err)
> + return false;
> + probe_powered = true;
> +
> + /* Issue clock signals to card for at least 74 clocks. */
> + rtsx_usb_write_register(ucr, SD_BUS_STAT,
> + SD_CLK_TOGGLE_EN, SD_CLK_TOGGLE_EN);
> + usleep_range(200, 400);
> + rtsx_usb_write_register(ucr, SD_BUS_STAT, SD_CLK_TOGGLE_EN, 0);
> + }
> +
> + /*
> + * Ensure the interface is in a safe, legacy / initial-clock mode before
> + * probing for a response. The MMC core may not have configured ios yet.
> + */
> + err = sd_set_bus_width(host, MMC_BUS_WIDTH_1);
> + if (err)
> + goto out;
> +
> + err = sd_set_timing(host, MMC_TIMING_LEGACY, &ddr_mode);
> + if (err)
> + goto out;
> +
> + ucr->cur_clk = 0;
> + err = rtsx_usb_switch_clock(ucr, 400000, SSC_DEPTH_512K,
> + true, true, false);
> + if (err)
> + goto out;
> +
> + cmd.opcode = MMC_GO_IDLE_STATE;
> + cmd.arg = 0;
> + cmd.flags = MMC_RSP_NONE | MMC_CMD_BC;
> + sd_send_cmd_get_rsp(host, &cmd);
> +
> + /* SD v2.0+: CMD8 */
> + cmd.opcode = SD_SEND_IF_COND;
> + cmd.arg = 0x1aa;
> + cmd.flags = MMC_RSP_R7 | MMC_CMD_BCR;
> + sd_send_cmd_get_rsp(host, &cmd);
> + if (!cmd.error)
> + goto out;
> +
> + /* SD v1.x: CMD55 */
> + cmd.opcode = MMC_APP_CMD;
> + cmd.arg = 0;
> + cmd.flags = MMC_RSP_R1 | MMC_CMD_AC;
> + sd_send_cmd_get_rsp(host, &cmd);
> + if (!cmd.error)
> + goto out;
> +
> + /* MMC: CMD1 */
> + cmd.opcode = MMC_SEND_OP_COND;
> + cmd.arg = 0;
> + cmd.flags = MMC_RSP_R3 | MMC_CMD_BCR;
> + sd_send_cmd_get_rsp(host, &cmd);
> +
> +out:
> + if (probe_powered)
> + sd_power_off(host);
> + return !err && !cmd.error;
> +}
No, the above code doesn't belong in an MMC host driver.
I will try to guide you toward a better solution, but first I need to
understand the problem better.
I guess one key piece of information I'm missing is what happens when
the mmc_recsan() work tries to send commands to initialize a card,
when there is no card inserted?
> +
> static int sdmmc_get_cd(struct mmc_host *mmc)
> {
> struct rtsx_usb_sdmmc *host = mmc_priv(mmc);
> struct rtsx_ucr *ucr = host->ucr;
> int err;
> u16 val;
> + u8 pend;
> + bool sd_int = false;
> + bool cd_raw = false;
>
> if (host->host_removal)
> return -ENOMEDIUM;
> @@ -782,28 +877,71 @@ static int sdmmc_get_cd(struct mmc_host *mmc)
>
> /* Check SD card detect */
> err = rtsx_usb_get_card_status(ucr, &val);
> -
> - mutex_unlock(&ucr->dev_mutex);
> -
> - /* Treat failed detection as non-exist */
> if (err)
> - goto no_card;
> + goto no_card_unlock;
>
> /* get OCP status */
> host->ocp_stat = (val >> 4) & 0x03;
>
> - if (val & SD_CD) {
> - host->card_exist = true;
> + cd_raw = !!(val & SD_CD);
> +
> + /* Use SD_INT as a reliable removal indication on some tray readers. */
> + err = rtsx_usb_read_register(ucr, CARD_INT_PEND, &pend);
> + if (!err) {
> + sd_int = !!(pend & SD_INT);
> + if (sd_int)
> + rtsx_usb_write_register(ucr, CARD_INT_PEND,
> + SD_INT, SD_INT);
> + }
> +
> + if (!cd_raw) {
> + host->cd_debounce = 0;
> + host->next_insert_check = 0;
> + goto no_card_unlock;
> + }
> +
> + /*
> + * rts5139-style: when a card is already known present, treat SD_INT as
> + * a removal event even if SD_CD stays high (e.g. tray-based readers).
> + */
> + if (host->card_exist) {
> + if (sd_int) {
> + host->cd_debounce = 0;
> + host->next_insert_check = 0;
> + goto no_card_unlock;
> + }
> + mutex_unlock(&ucr->dev_mutex);
> return 1;
> }
>
> -no_card:
> + /* Debounce mechanical CD before probing for a response. */
> + if (host->cd_debounce < RTSX_USB_SD_CD_DEBOUNCE_CNT) {
> + host->cd_debounce++;
> + goto no_card_unlock;
> + }
> +
> + /* Avoid pounding the bus with probes if CD is stuck asserted. */
> + if (time_before(jiffies, host->next_insert_check))
> + goto no_card_unlock;
> +
> + if (!sdmmc_validate_insert_locked(host)) {
> + host->next_insert_check = jiffies +
> + msecs_to_jiffies(RTSX_USB_SD_INSERT_RETRY_MS);
> + goto no_card_unlock;
> + }
> +
> + host->card_exist = true;
> + mutex_unlock(&ucr->dev_mutex);
> + return 1;
> +
> +no_card_unlock:
> /* clear OCP status */
> if (host->ocp_stat & (MS_OCP_NOW | MS_OCP_EVER)) {
> rtsx_usb_write_register(ucr, OCPCTL, MS_OCP_CLEAR, MS_OCP_CLEAR);
> host->ocp_stat = 0;
> }
> host->card_exist = false;
> + mutex_unlock(&ucr->dev_mutex);
> return 0;
> }
>
> @@ -1359,6 +1497,8 @@ static void rtsx_usb_init_host(struct rtsx_usb_sdmmc *host)
>
> host->power_mode = MMC_POWER_OFF;
> host->ocp_stat = 0;
> + host->cd_debounce = 0;
> + host->next_insert_check = 0;
> }
>
> static int rtsx_usb_sdmmc_drv_probe(struct platform_device *pdev)
> --
> 2.51.0
Kind regards
Uffe
Hi Uffe
Thanks for the feedback - to answer your questions:
> Would you mind elaborating on this so I can better understand the problem?
SD_CD is used for presence, but that's asserted by trays or blanking cards.
> What prevents the MMC core from handling this correctly?
>
> Don't some of the commands time out, eventually causing mmc_rescan() to bail out?
It does bail out, but SD_CD is still asserted, so it tries again. The
main thing this breaks is S3 entry - it's either slow (like 100
seconds), or never gets there.
> I guess one key piece of information I'm missing is what happens when the mmc_recsan() work tries to send commands to initialize a card, when there is no card inserted?
They fail, and we use that result.
On Tue, 24 Mar 2026 at 11:26, Ulf Hansson <ulf.hansson@linaro.org> wrote:
>
> + Matthew Schwartz
>
> On Thu, 12 Mar 2026 at 13:16, Sean Rhodes <sean@starlabs.systems> wrote:
> >
> > Some Realtek USB SD readers with a tray can assert SD_CD even when no
> > card is present. This can make the MMC core believe a card exists and
> > trigger unnecessary initialization and suspend/shutdown failures.
>
> Would you mind elaborating on this so I can better understand the problem?
>
> >
> > Debounce the CD signal and validate a newly detected card by probing for
> > a response (CMD0 + CMD8/CMD55/CMD1) before reporting it present. Also
> > treat SD_INT as a removal indication even if SD_CD stays asserted.
>
> What prevents the MMC core from handling this correctly?
>
> Don't some of the commands time out, eventually causing mmc_rescan()
> to bail out?
>
> >
> > Tested: Realtek RTS5129 (0bda:0129), tray inserted, no card (2026-02-24)
> > Tested: Realtek RTS5129 (0bda:0129), tray + SDXC card, mmcblk0 (2026-02-24)
> >
> > Signed-off-by: Sean Rhodes <sean@starlabs.systems>
>
> I have looped in Matthew who recently worked on some similar problems
> for the PCI variant [1] of the driver. Perhaps he and Ricky have some
> input on this series too.
>
> [1]
> https://lore.kernel.org/all/20260105060236.400366-1-matthew.schwartz@linux.dev/
>
> Please see a few more comments below.
>
> > ---
> > drivers/mmc/host/rtsx_usb_sdmmc.c | 156 ++++++++++++++++++++++++++++--
> > 1 file changed, 148 insertions(+), 8 deletions(-)
> >
> > diff --git a/drivers/mmc/host/rtsx_usb_sdmmc.c b/drivers/mmc/host/rtsx_usb_sdmmc.c
> > index 84674659a84d..ec3eeea78e95 100644
> > --- a/drivers/mmc/host/rtsx_usb_sdmmc.c
> > +++ b/drivers/mmc/host/rtsx_usb_sdmmc.c
> > @@ -19,6 +19,7 @@
> > #include <linux/scatterlist.h>
> > #include <linux/pm.h>
> > #include <linux/pm_runtime.h>
> > +#include <linux/jiffies.h>
> >
> > #include <linux/rtsx_usb.h>
> > #include <linux/unaligned.h>
> > @@ -30,6 +31,9 @@
> > #define RTSX_USB_USE_LEDS_CLASS
> > #endif
> >
> > +#define RTSX_USB_SD_CD_DEBOUNCE_CNT 2
> > +#define RTSX_USB_SD_INSERT_RETRY_MS 1000
> > +
> > struct rtsx_usb_sdmmc {
> > struct platform_device *pdev;
> > struct rtsx_ucr *ucr;
> > @@ -46,6 +50,8 @@ struct rtsx_usb_sdmmc {
> > bool card_exist;
> > bool initial_mode;
> > bool ddr_mode;
> > + u8 cd_debounce;
> > + unsigned long next_insert_check;
> >
> > unsigned char power_mode;
> > u16 ocp_stat;
> > @@ -72,6 +78,13 @@ static inline void sd_clear_error(struct rtsx_usb_sdmmc *host)
> > rtsx_usb_clear_fsm_err(ucr);
> > }
> >
> > +static int sd_set_bus_width(struct rtsx_usb_sdmmc *host,
> > + unsigned char bus_width);
> > +static int sd_set_timing(struct rtsx_usb_sdmmc *host,
> > + unsigned char timing, bool *ddr_mode);
> > +static int sd_power_on(struct rtsx_usb_sdmmc *host);
> > +static int sd_power_off(struct rtsx_usb_sdmmc *host);
> > +
> > #ifdef DEBUG
> > static void sd_print_debug_regs(struct rtsx_usb_sdmmc *host)
> > {
> > @@ -768,12 +781,94 @@ static int sdmmc_get_ro(struct mmc_host *mmc)
> > return 0;
> > }
> >
> > +static bool sdmmc_validate_insert_locked(struct rtsx_usb_sdmmc *host)
> > +{
> > + struct rtsx_ucr *ucr = host->ucr;
> > + struct mmc_command cmd = { };
> > + int err = 0;
> > + bool probe_powered = false;
> > + bool ddr_mode = false;
> > +
> > + /*
> > + * Some readers with a tray assert the mechanical SD_CD pin even when no
> > + * card is present. Only report a card present when it responds to a
> > + * minimal reset/probe sequence, similar to the old rts5139 behavior.
> > + *
> > + * Must be called with ucr->dev_mutex held.
> > + */
> > + if (host->power_mode == MMC_POWER_OFF) {
> > + err = sd_power_on(host);
> > + if (err)
> > + return false;
> > + probe_powered = true;
> > +
> > + /* Issue clock signals to card for at least 74 clocks. */
> > + rtsx_usb_write_register(ucr, SD_BUS_STAT,
> > + SD_CLK_TOGGLE_EN, SD_CLK_TOGGLE_EN);
> > + usleep_range(200, 400);
> > + rtsx_usb_write_register(ucr, SD_BUS_STAT, SD_CLK_TOGGLE_EN, 0);
> > + }
> > +
> > + /*
> > + * Ensure the interface is in a safe, legacy / initial-clock mode before
> > + * probing for a response. The MMC core may not have configured ios yet.
> > + */
> > + err = sd_set_bus_width(host, MMC_BUS_WIDTH_1);
> > + if (err)
> > + goto out;
> > +
> > + err = sd_set_timing(host, MMC_TIMING_LEGACY, &ddr_mode);
> > + if (err)
> > + goto out;
> > +
> > + ucr->cur_clk = 0;
> > + err = rtsx_usb_switch_clock(ucr, 400000, SSC_DEPTH_512K,
> > + true, true, false);
> > + if (err)
> > + goto out;
> > +
> > + cmd.opcode = MMC_GO_IDLE_STATE;
> > + cmd.arg = 0;
> > + cmd.flags = MMC_RSP_NONE | MMC_CMD_BC;
> > + sd_send_cmd_get_rsp(host, &cmd);
> > +
> > + /* SD v2.0+: CMD8 */
> > + cmd.opcode = SD_SEND_IF_COND;
> > + cmd.arg = 0x1aa;
> > + cmd.flags = MMC_RSP_R7 | MMC_CMD_BCR;
> > + sd_send_cmd_get_rsp(host, &cmd);
> > + if (!cmd.error)
> > + goto out;
> > +
> > + /* SD v1.x: CMD55 */
> > + cmd.opcode = MMC_APP_CMD;
> > + cmd.arg = 0;
> > + cmd.flags = MMC_RSP_R1 | MMC_CMD_AC;
> > + sd_send_cmd_get_rsp(host, &cmd);
> > + if (!cmd.error)
> > + goto out;
> > +
> > + /* MMC: CMD1 */
> > + cmd.opcode = MMC_SEND_OP_COND;
> > + cmd.arg = 0;
> > + cmd.flags = MMC_RSP_R3 | MMC_CMD_BCR;
> > + sd_send_cmd_get_rsp(host, &cmd);
> > +
> > +out:
> > + if (probe_powered)
> > + sd_power_off(host);
> > + return !err && !cmd.error;
> > +}
>
> No, the above code doesn't belong in an MMC host driver.
>
> I will try to guide you toward a better solution, but first I need to
> understand the problem better.
>
> I guess one key piece of information I'm missing is what happens when
> the mmc_recsan() work tries to send commands to initialize a card,
> when there is no card inserted?
>
> > +
> > static int sdmmc_get_cd(struct mmc_host *mmc)
> > {
> > struct rtsx_usb_sdmmc *host = mmc_priv(mmc);
> > struct rtsx_ucr *ucr = host->ucr;
> > int err;
> > u16 val;
> > + u8 pend;
> > + bool sd_int = false;
> > + bool cd_raw = false;
> >
> > if (host->host_removal)
> > return -ENOMEDIUM;
> > @@ -782,28 +877,71 @@ static int sdmmc_get_cd(struct mmc_host *mmc)
> >
> > /* Check SD card detect */
> > err = rtsx_usb_get_card_status(ucr, &val);
> > -
> > - mutex_unlock(&ucr->dev_mutex);
> > -
> > - /* Treat failed detection as non-exist */
> > if (err)
> > - goto no_card;
> > + goto no_card_unlock;
> >
> > /* get OCP status */
> > host->ocp_stat = (val >> 4) & 0x03;
> >
> > - if (val & SD_CD) {
> > - host->card_exist = true;
> > + cd_raw = !!(val & SD_CD);
> > +
> > + /* Use SD_INT as a reliable removal indication on some tray readers. */
> > + err = rtsx_usb_read_register(ucr, CARD_INT_PEND, &pend);
> > + if (!err) {
> > + sd_int = !!(pend & SD_INT);
> > + if (sd_int)
> > + rtsx_usb_write_register(ucr, CARD_INT_PEND,
> > + SD_INT, SD_INT);
> > + }
> > +
> > + if (!cd_raw) {
> > + host->cd_debounce = 0;
> > + host->next_insert_check = 0;
> > + goto no_card_unlock;
> > + }
> > +
> > + /*
> > + * rts5139-style: when a card is already known present, treat SD_INT as
> > + * a removal event even if SD_CD stays high (e.g. tray-based readers).
> > + */
> > + if (host->card_exist) {
> > + if (sd_int) {
> > + host->cd_debounce = 0;
> > + host->next_insert_check = 0;
> > + goto no_card_unlock;
> > + }
> > + mutex_unlock(&ucr->dev_mutex);
> > return 1;
> > }
> >
> > -no_card:
> > + /* Debounce mechanical CD before probing for a response. */
> > + if (host->cd_debounce < RTSX_USB_SD_CD_DEBOUNCE_CNT) {
> > + host->cd_debounce++;
> > + goto no_card_unlock;
> > + }
> > +
> > + /* Avoid pounding the bus with probes if CD is stuck asserted. */
> > + if (time_before(jiffies, host->next_insert_check))
> > + goto no_card_unlock;
> > +
> > + if (!sdmmc_validate_insert_locked(host)) {
> > + host->next_insert_check = jiffies +
> > + msecs_to_jiffies(RTSX_USB_SD_INSERT_RETRY_MS);
> > + goto no_card_unlock;
> > + }
> > +
> > + host->card_exist = true;
> > + mutex_unlock(&ucr->dev_mutex);
> > + return 1;
> > +
> > +no_card_unlock:
> > /* clear OCP status */
> > if (host->ocp_stat & (MS_OCP_NOW | MS_OCP_EVER)) {
> > rtsx_usb_write_register(ucr, OCPCTL, MS_OCP_CLEAR, MS_OCP_CLEAR);
> > host->ocp_stat = 0;
> > }
> > host->card_exist = false;
> > + mutex_unlock(&ucr->dev_mutex);
> > return 0;
> > }
> >
> > @@ -1359,6 +1497,8 @@ static void rtsx_usb_init_host(struct rtsx_usb_sdmmc *host)
> >
> > host->power_mode = MMC_POWER_OFF;
> > host->ocp_stat = 0;
> > + host->cd_debounce = 0;
> > + host->next_insert_check = 0;
> > }
> >
> > static int rtsx_usb_sdmmc_drv_probe(struct platform_device *pdev)
> > --
> > 2.51.0
>
> Kind regards
> Uffe
On Tue, 24 Mar 2026 at 12:59, Sean Rhodes <sean@starlabs.systems> wrote: > > Hi Uffe > > Thanks for the feedback - to answer your questions: > > > Would you mind elaborating on this so I can better understand the problem? > > SD_CD is used for presence, but that's asserted by trays or blanking cards. > > > What prevents the MMC core from handling this correctly? > > > > Don't some of the commands time out, eventually causing mmc_rescan() to bail out? > > It does bail out, but SD_CD is still asserted, so it tries again. The > main thing this breaks is S3 entry - it's either slow (like 100 > seconds), or never gets there. First, please don't top post, but reply in-line below so we can follow the discussion. It's the mmc driver's ->runtime_resume() callback that calls mmc_detect_change(host->mmc, 0). It's sounds to me like the mmc host is being runtime resumed/suspended over and over again, hence we end up calling mmc_detect_change() many times when it's not really needed. Right? > > > I guess one key piece of information I'm missing is what happens when the mmc_recsan() work tries to send commands to initialize a card, when there is no card inserted? > > They fail, and we use that result. > On Tue, 24 Mar 2026 at 11:26, Ulf Hansson <ulf.hansson@linaro.org> wrote: > > > > + Matthew Schwartz > > > > On Thu, 12 Mar 2026 at 13:16, Sean Rhodes <sean@starlabs.systems> wrote: > > > > > > Some Realtek USB SD readers with a tray can assert SD_CD even when no > > > card is present. This can make the MMC core believe a card exists and > > > trigger unnecessary initialization and suspend/shutdown failures. > > > > Would you mind elaborating on this so I can better understand the problem? > > > > > > > > Debounce the CD signal and validate a newly detected card by probing for > > > a response (CMD0 + CMD8/CMD55/CMD1) before reporting it present. Also > > > treat SD_INT as a removal indication even if SD_CD stays asserted. > > > > What prevents the MMC core from handling this correctly? > > > > Don't some of the commands time out, eventually causing mmc_rescan() > > to bail out? > > > > > > > > Tested: Realtek RTS5129 (0bda:0129), tray inserted, no card (2026-02-24) > > > Tested: Realtek RTS5129 (0bda:0129), tray + SDXC card, mmcblk0 (2026-02-24) > > > > > > Signed-off-by: Sean Rhodes <sean@starlabs.systems> > > > > I have looped in Matthew who recently worked on some similar problems > > for the PCI variant [1] of the driver. Perhaps he and Ricky have some > > input on this series too. > > > > [1] > > https://lore.kernel.org/all/20260105060236.400366-1-matthew.schwartz@linux.dev/ > > [...] Kind regards Uffe
On Tue, 24 Mar 2026 at 12:23, Ulf Hansson <ulf.hansson@linaro.org> wrote: > > On Tue, 24 Mar 2026 at 12:59, Sean Rhodes <sean@starlabs.systems> wrote: > > > > Hi Uffe > > > > Thanks for the feedback - to answer your questions: > > > > > Would you mind elaborating on this so I can better understand the problem? > > > > SD_CD is used for presence, but that's asserted by trays or blanking cards. > > > > > What prevents the MMC core from handling this correctly? > > > > > > Don't some of the commands time out, eventually causing mmc_rescan() to bail out? > > > > It does bail out, but SD_CD is still asserted, so it tries again. The > > main thing this breaks is S3 entry - it's either slow (like 100 > > seconds), or never gets there. > > First, please don't top post, but reply in-line below so we can follow > the discussion. > > It's the mmc driver's ->runtime_resume() callback that calls > mmc_detect_change(host->mmc, 0). > > It's sounds to me like the mmc host is being runtime resumed/suspended > over and over again, hence we end up calling mmc_detect_change() many > times when it's not really needed. Right? > Correct > > > > > I guess one key piece of information I'm missing is what happens when the mmc_recsan() work tries to send commands to initialize a card, when there is no card inserted? > > > > They fail, and we use that result. > > On Tue, 24 Mar 2026 at 11:26, Ulf Hansson <ulf.hansson@linaro.org> wrote: > > > > > > + Matthew Schwartz > > > > > > On Thu, 12 Mar 2026 at 13:16, Sean Rhodes <sean@starlabs.systems> wrote: > > > > > > > > Some Realtek USB SD readers with a tray can assert SD_CD even when no > > > > card is present. This can make the MMC core believe a card exists and > > > > trigger unnecessary initialization and suspend/shutdown failures. > > > > > > Would you mind elaborating on this so I can better understand the problem? > > > > > > > > > > > Debounce the CD signal and validate a newly detected card by probing for > > > > a response (CMD0 + CMD8/CMD55/CMD1) before reporting it present. Also > > > > treat SD_INT as a removal indication even if SD_CD stays asserted. > > > > > > What prevents the MMC core from handling this correctly? > > > > > > Don't some of the commands time out, eventually causing mmc_rescan() > > > to bail out? > > > > > > > > > > > Tested: Realtek RTS5129 (0bda:0129), tray inserted, no card (2026-02-24) > > > > Tested: Realtek RTS5129 (0bda:0129), tray + SDXC card, mmcblk0 (2026-02-24) > > > > > > > > Signed-off-by: Sean Rhodes <sean@starlabs.systems> > > > > > > I have looped in Matthew who recently worked on some similar problems > > > for the PCI variant [1] of the driver. Perhaps he and Ricky have some > > > input on this series too. > > > > > > [1] > > > https://lore.kernel.org/all/20260105060236.400366-1-matthew.schwartz@linux.dev/ > > > > > [...] > > Kind regards > Uffe
On Tue, 24 Mar 2026 at 13:30, Sean Rhodes <sean@starlabs.systems> wrote: > > On Tue, 24 Mar 2026 at 12:23, Ulf Hansson <ulf.hansson@linaro.org> wrote: > > > > On Tue, 24 Mar 2026 at 12:59, Sean Rhodes <sean@starlabs.systems> wrote: > > > > > > Hi Uffe > > > > > > Thanks for the feedback - to answer your questions: > > > > > > > Would you mind elaborating on this so I can better understand the problem? > > > > > > SD_CD is used for presence, but that's asserted by trays or blanking cards. > > > > > > > What prevents the MMC core from handling this correctly? > > > > > > > > Don't some of the commands time out, eventually causing mmc_rescan() to bail out? > > > > > > It does bail out, but SD_CD is still asserted, so it tries again. The > > > main thing this breaks is S3 entry - it's either slow (like 100 > > > seconds), or never gets there. > > > > First, please don't top post, but reply in-line below so we can follow > > the discussion. > > > > It's the mmc driver's ->runtime_resume() callback that calls > > mmc_detect_change(host->mmc, 0). > > > > It's sounds to me like the mmc host is being runtime resumed/suspended > > over and over again, hence we end up calling mmc_detect_change() many > > times when it's not really needed. Right? > > > > Correct So why does the rtsx usb card driver do that? Could it keep the mmc host runtime resumed for a while and then we could add a debounce period to the call to mmc_detect_change()? Kind regards Uffe
On Tue, 24 Mar 2026 at 12:58, Ulf Hansson <ulf.hansson@linaro.org> wrote: > > On Tue, 24 Mar 2026 at 13:30, Sean Rhodes <sean@starlabs.systems> wrote: > > > > On Tue, 24 Mar 2026 at 12:23, Ulf Hansson <ulf.hansson@linaro.org> wrote: > > > > > > On Tue, 24 Mar 2026 at 12:59, Sean Rhodes <sean@starlabs.systems> wrote: > > > > > > > > Hi Uffe > > > > > > > > Thanks for the feedback - to answer your questions: > > > > > > > > > Would you mind elaborating on this so I can better understand the problem? > > > > > > > > SD_CD is used for presence, but that's asserted by trays or blanking cards. > > > > > > > > > What prevents the MMC core from handling this correctly? > > > > > > > > > > Don't some of the commands time out, eventually causing mmc_rescan() to bail out? > > > > > > > > It does bail out, but SD_CD is still asserted, so it tries again. The > > > > main thing this breaks is S3 entry - it's either slow (like 100 > > > > seconds), or never gets there. > > > > > > First, please don't top post, but reply in-line below so we can follow > > > the discussion. > > > > > > It's the mmc driver's ->runtime_resume() callback that calls > > > mmc_detect_change(host->mmc, 0). > > > > > > It's sounds to me like the mmc host is being runtime resumed/suspended > > > over and over again, hence we end up calling mmc_detect_change() many > > > times when it's not really needed. Right? > > > > > > > Correct > > So why does the rtsx usb card driver do that? I don't know; I'd assume it was just a shortcut or done by people who didn't have the hardware when moving the driver out of staging (see 00d8521dcd236d1b8f664f54a0309e96bfdcb4f9 which works flawlessly) > > Could it keep the mmc host runtime resumed for a while and then we > could add a debounce period to the call to mmc_detect_change()? I might be missing something, but wouldn't that just make symptoms less obvious? We have bad detection - why not fix that? > > Kind regards > Uffe
On Tue, 24 Mar 2026 at 14:14, Sean Rhodes <sean@starlabs.systems> wrote: > > On Tue, 24 Mar 2026 at 12:58, Ulf Hansson <ulf.hansson@linaro.org> wrote: > > > > On Tue, 24 Mar 2026 at 13:30, Sean Rhodes <sean@starlabs.systems> wrote: > > > > > > On Tue, 24 Mar 2026 at 12:23, Ulf Hansson <ulf.hansson@linaro.org> wrote: > > > > > > > > On Tue, 24 Mar 2026 at 12:59, Sean Rhodes <sean@starlabs.systems> wrote: > > > > > > > > > > Hi Uffe > > > > > > > > > > Thanks for the feedback - to answer your questions: > > > > > > > > > > > Would you mind elaborating on this so I can better understand the problem? > > > > > > > > > > SD_CD is used for presence, but that's asserted by trays or blanking cards. > > > > > > > > > > > What prevents the MMC core from handling this correctly? > > > > > > > > > > > > Don't some of the commands time out, eventually causing mmc_rescan() to bail out? > > > > > > > > > > It does bail out, but SD_CD is still asserted, so it tries again. The > > > > > main thing this breaks is S3 entry - it's either slow (like 100 > > > > > seconds), or never gets there. > > > > > > > > First, please don't top post, but reply in-line below so we can follow > > > > the discussion. > > > > > > > > It's the mmc driver's ->runtime_resume() callback that calls > > > > mmc_detect_change(host->mmc, 0). > > > > > > > > It's sounds to me like the mmc host is being runtime resumed/suspended > > > > over and over again, hence we end up calling mmc_detect_change() many > > > > times when it's not really needed. Right? > > > > > > > > > > Correct > > > > So why does the rtsx usb card driver do that? > > I don't know; I'd assume it was just a shortcut or done by people who > didn't have the hardware when moving the driver out of staging (see > 00d8521dcd236d1b8f664f54a0309e96bfdcb4f9 which works flawlessly) > > > > > Could it keep the mmc host runtime resumed for a while and then we > > could add a debounce period to the call to mmc_detect_change()? > > I might be missing something, but wouldn't that just make symptoms > less obvious? We have bad detection - why not fix that? > I don't think so. Assuming the host gets runtime resumed at some point again, after we failed to detect a card. Not sure if this solves the problem, but should just make things less noisy, I think. Anyway, you may very well be right, but I don't know how to fix this properly on the usb side. That said, we might need input from the usb maintainers on how to proceed. Perhaps if you make a new submission (RFC), by describing the problem in more detail and ask for guidance from the usb folkz? Kind regards Uffe
+ Matthew (again)
On Tue, 24 Mar 2026 at 12:26, Ulf Hansson <ulf.hansson@linaro.org> wrote:
>
> + Matthew Schwartz
>
> On Thu, 12 Mar 2026 at 13:16, Sean Rhodes <sean@starlabs.systems> wrote:
> >
> > Some Realtek USB SD readers with a tray can assert SD_CD even when no
> > card is present. This can make the MMC core believe a card exists and
> > trigger unnecessary initialization and suspend/shutdown failures.
>
> Would you mind elaborating on this so I can better understand the problem?
>
> >
> > Debounce the CD signal and validate a newly detected card by probing for
> > a response (CMD0 + CMD8/CMD55/CMD1) before reporting it present. Also
> > treat SD_INT as a removal indication even if SD_CD stays asserted.
>
> What prevents the MMC core from handling this correctly?
>
> Don't some of the commands time out, eventually causing mmc_rescan()
> to bail out?
>
> >
> > Tested: Realtek RTS5129 (0bda:0129), tray inserted, no card (2026-02-24)
> > Tested: Realtek RTS5129 (0bda:0129), tray + SDXC card, mmcblk0 (2026-02-24)
> >
> > Signed-off-by: Sean Rhodes <sean@starlabs.systems>
>
> I have looped in Matthew who recently worked on some similar problems
> for the PCI variant [1] of the driver. Perhaps he and Ricky have some
> input on this series too.
>
> [1]
> https://lore.kernel.org/all/20260105060236.400366-1-matthew.schwartz@linux.dev/
>
> Please see a few more comments below.
>
> > ---
> > drivers/mmc/host/rtsx_usb_sdmmc.c | 156 ++++++++++++++++++++++++++++--
> > 1 file changed, 148 insertions(+), 8 deletions(-)
> >
> > diff --git a/drivers/mmc/host/rtsx_usb_sdmmc.c b/drivers/mmc/host/rtsx_usb_sdmmc.c
> > index 84674659a84d..ec3eeea78e95 100644
> > --- a/drivers/mmc/host/rtsx_usb_sdmmc.c
> > +++ b/drivers/mmc/host/rtsx_usb_sdmmc.c
> > @@ -19,6 +19,7 @@
> > #include <linux/scatterlist.h>
> > #include <linux/pm.h>
> > #include <linux/pm_runtime.h>
> > +#include <linux/jiffies.h>
> >
> > #include <linux/rtsx_usb.h>
> > #include <linux/unaligned.h>
> > @@ -30,6 +31,9 @@
> > #define RTSX_USB_USE_LEDS_CLASS
> > #endif
> >
> > +#define RTSX_USB_SD_CD_DEBOUNCE_CNT 2
> > +#define RTSX_USB_SD_INSERT_RETRY_MS 1000
> > +
> > struct rtsx_usb_sdmmc {
> > struct platform_device *pdev;
> > struct rtsx_ucr *ucr;
> > @@ -46,6 +50,8 @@ struct rtsx_usb_sdmmc {
> > bool card_exist;
> > bool initial_mode;
> > bool ddr_mode;
> > + u8 cd_debounce;
> > + unsigned long next_insert_check;
> >
> > unsigned char power_mode;
> > u16 ocp_stat;
> > @@ -72,6 +78,13 @@ static inline void sd_clear_error(struct rtsx_usb_sdmmc *host)
> > rtsx_usb_clear_fsm_err(ucr);
> > }
> >
> > +static int sd_set_bus_width(struct rtsx_usb_sdmmc *host,
> > + unsigned char bus_width);
> > +static int sd_set_timing(struct rtsx_usb_sdmmc *host,
> > + unsigned char timing, bool *ddr_mode);
> > +static int sd_power_on(struct rtsx_usb_sdmmc *host);
> > +static int sd_power_off(struct rtsx_usb_sdmmc *host);
> > +
> > #ifdef DEBUG
> > static void sd_print_debug_regs(struct rtsx_usb_sdmmc *host)
> > {
> > @@ -768,12 +781,94 @@ static int sdmmc_get_ro(struct mmc_host *mmc)
> > return 0;
> > }
> >
> > +static bool sdmmc_validate_insert_locked(struct rtsx_usb_sdmmc *host)
> > +{
> > + struct rtsx_ucr *ucr = host->ucr;
> > + struct mmc_command cmd = { };
> > + int err = 0;
> > + bool probe_powered = false;
> > + bool ddr_mode = false;
> > +
> > + /*
> > + * Some readers with a tray assert the mechanical SD_CD pin even when no
> > + * card is present. Only report a card present when it responds to a
> > + * minimal reset/probe sequence, similar to the old rts5139 behavior.
> > + *
> > + * Must be called with ucr->dev_mutex held.
> > + */
> > + if (host->power_mode == MMC_POWER_OFF) {
> > + err = sd_power_on(host);
> > + if (err)
> > + return false;
> > + probe_powered = true;
> > +
> > + /* Issue clock signals to card for at least 74 clocks. */
> > + rtsx_usb_write_register(ucr, SD_BUS_STAT,
> > + SD_CLK_TOGGLE_EN, SD_CLK_TOGGLE_EN);
> > + usleep_range(200, 400);
> > + rtsx_usb_write_register(ucr, SD_BUS_STAT, SD_CLK_TOGGLE_EN, 0);
> > + }
> > +
> > + /*
> > + * Ensure the interface is in a safe, legacy / initial-clock mode before
> > + * probing for a response. The MMC core may not have configured ios yet.
> > + */
> > + err = sd_set_bus_width(host, MMC_BUS_WIDTH_1);
> > + if (err)
> > + goto out;
> > +
> > + err = sd_set_timing(host, MMC_TIMING_LEGACY, &ddr_mode);
> > + if (err)
> > + goto out;
> > +
> > + ucr->cur_clk = 0;
> > + err = rtsx_usb_switch_clock(ucr, 400000, SSC_DEPTH_512K,
> > + true, true, false);
> > + if (err)
> > + goto out;
> > +
> > + cmd.opcode = MMC_GO_IDLE_STATE;
> > + cmd.arg = 0;
> > + cmd.flags = MMC_RSP_NONE | MMC_CMD_BC;
> > + sd_send_cmd_get_rsp(host, &cmd);
> > +
> > + /* SD v2.0+: CMD8 */
> > + cmd.opcode = SD_SEND_IF_COND;
> > + cmd.arg = 0x1aa;
> > + cmd.flags = MMC_RSP_R7 | MMC_CMD_BCR;
> > + sd_send_cmd_get_rsp(host, &cmd);
> > + if (!cmd.error)
> > + goto out;
> > +
> > + /* SD v1.x: CMD55 */
> > + cmd.opcode = MMC_APP_CMD;
> > + cmd.arg = 0;
> > + cmd.flags = MMC_RSP_R1 | MMC_CMD_AC;
> > + sd_send_cmd_get_rsp(host, &cmd);
> > + if (!cmd.error)
> > + goto out;
> > +
> > + /* MMC: CMD1 */
> > + cmd.opcode = MMC_SEND_OP_COND;
> > + cmd.arg = 0;
> > + cmd.flags = MMC_RSP_R3 | MMC_CMD_BCR;
> > + sd_send_cmd_get_rsp(host, &cmd);
> > +
> > +out:
> > + if (probe_powered)
> > + sd_power_off(host);
> > + return !err && !cmd.error;
> > +}
>
> No, the above code doesn't belong in an MMC host driver.
>
> I will try to guide you toward a better solution, but first I need to
> understand the problem better.
>
> I guess one key piece of information I'm missing is what happens when
> the mmc_recsan() work tries to send commands to initialize a card,
> when there is no card inserted?
>
> > +
> > static int sdmmc_get_cd(struct mmc_host *mmc)
> > {
> > struct rtsx_usb_sdmmc *host = mmc_priv(mmc);
> > struct rtsx_ucr *ucr = host->ucr;
> > int err;
> > u16 val;
> > + u8 pend;
> > + bool sd_int = false;
> > + bool cd_raw = false;
> >
> > if (host->host_removal)
> > return -ENOMEDIUM;
> > @@ -782,28 +877,71 @@ static int sdmmc_get_cd(struct mmc_host *mmc)
> >
> > /* Check SD card detect */
> > err = rtsx_usb_get_card_status(ucr, &val);
> > -
> > - mutex_unlock(&ucr->dev_mutex);
> > -
> > - /* Treat failed detection as non-exist */
> > if (err)
> > - goto no_card;
> > + goto no_card_unlock;
> >
> > /* get OCP status */
> > host->ocp_stat = (val >> 4) & 0x03;
> >
> > - if (val & SD_CD) {
> > - host->card_exist = true;
> > + cd_raw = !!(val & SD_CD);
> > +
> > + /* Use SD_INT as a reliable removal indication on some tray readers. */
> > + err = rtsx_usb_read_register(ucr, CARD_INT_PEND, &pend);
> > + if (!err) {
> > + sd_int = !!(pend & SD_INT);
> > + if (sd_int)
> > + rtsx_usb_write_register(ucr, CARD_INT_PEND,
> > + SD_INT, SD_INT);
> > + }
> > +
> > + if (!cd_raw) {
> > + host->cd_debounce = 0;
> > + host->next_insert_check = 0;
> > + goto no_card_unlock;
> > + }
> > +
> > + /*
> > + * rts5139-style: when a card is already known present, treat SD_INT as
> > + * a removal event even if SD_CD stays high (e.g. tray-based readers).
> > + */
> > + if (host->card_exist) {
> > + if (sd_int) {
> > + host->cd_debounce = 0;
> > + host->next_insert_check = 0;
> > + goto no_card_unlock;
> > + }
> > + mutex_unlock(&ucr->dev_mutex);
> > return 1;
> > }
> >
> > -no_card:
> > + /* Debounce mechanical CD before probing for a response. */
> > + if (host->cd_debounce < RTSX_USB_SD_CD_DEBOUNCE_CNT) {
> > + host->cd_debounce++;
> > + goto no_card_unlock;
> > + }
> > +
> > + /* Avoid pounding the bus with probes if CD is stuck asserted. */
> > + if (time_before(jiffies, host->next_insert_check))
> > + goto no_card_unlock;
> > +
> > + if (!sdmmc_validate_insert_locked(host)) {
> > + host->next_insert_check = jiffies +
> > + msecs_to_jiffies(RTSX_USB_SD_INSERT_RETRY_MS);
> > + goto no_card_unlock;
> > + }
> > +
> > + host->card_exist = true;
> > + mutex_unlock(&ucr->dev_mutex);
> > + return 1;
> > +
> > +no_card_unlock:
> > /* clear OCP status */
> > if (host->ocp_stat & (MS_OCP_NOW | MS_OCP_EVER)) {
> > rtsx_usb_write_register(ucr, OCPCTL, MS_OCP_CLEAR, MS_OCP_CLEAR);
> > host->ocp_stat = 0;
> > }
> > host->card_exist = false;
> > + mutex_unlock(&ucr->dev_mutex);
> > return 0;
> > }
> >
> > @@ -1359,6 +1497,8 @@ static void rtsx_usb_init_host(struct rtsx_usb_sdmmc *host)
> >
> > host->power_mode = MMC_POWER_OFF;
> > host->ocp_stat = 0;
> > + host->cd_debounce = 0;
> > + host->next_insert_check = 0;
> > }
> >
> > static int rtsx_usb_sdmmc_drv_probe(struct platform_device *pdev)
> > --
> > 2.51.0
>
> Kind regards
> Uffe
Some tray-based readers keep SD_CD asserted even without a card. The
rtsx_usb_sdmmc driver now validates insertion with a minimal probe
sequence. That probe must start with the SD pads in 3.3V mode.
Like the old rts5139 driver (sd_init_power()), force the SD pads to
3.3V and tune the SD18 regulator to 3.3V before powering up the card.
This avoids spurious probe timeouts when the reader is left in 1.8V
from a previous UHS session.
Tested: Realtek RTS5129 (0bda:0129) + tray + Lexar 2TB SDXC
Tested: cold boot detects mmcblk0 (2026-02-24)
Tested: hotplug insert enumerates mmcblk0 (2026-02-23)
Signed-off-by: Sean Rhodes <sean@starlabs.systems>
---
drivers/mmc/host/rtsx_usb_sdmmc.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/drivers/mmc/host/rtsx_usb_sdmmc.c b/drivers/mmc/host/rtsx_usb_sdmmc.c
index ec3eeea78e95..6be98926387d 100644
--- a/drivers/mmc/host/rtsx_usb_sdmmc.c
+++ b/drivers/mmc/host/rtsx_usb_sdmmc.c
@@ -1108,6 +1108,11 @@ static int sd_power_on(struct rtsx_usb_sdmmc *host)
}
dev_dbg(sdmmc_dev(host), "%s\n", __func__);
rtsx_usb_init_cmd(ucr);
+ /* Start SD init at 3.3V, like the old rts5139 driver. */
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_PAD_CTL,
+ SD_IO_USING_1V8, SD_IO_USING_3V3);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, LDO_POWER_CFG,
+ TUNE_SD18_MASK, TUNE_SD18_3V3);
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_SELECT, 0x07, SD_MOD_SEL);
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_SHARE_MODE,
CARD_SHARE_MASK, CARD_SHARE_SD);
--
2.51.0
On Thu, 12 Mar 2026 at 13:16, Sean Rhodes <sean@starlabs.systems> wrote: > > Some tray-based readers keep SD_CD asserted even without a card. The > rtsx_usb_sdmmc driver now validates insertion with a minimal probe > sequence. That probe must start with the SD pads in 3.3V mode. > > Like the old rts5139 driver (sd_init_power()), force the SD pads to > 3.3V and tune the SD18 regulator to 3.3V before powering up the card. > This avoids spurious probe timeouts when the reader is left in 1.8V > from a previous UHS session. > > Tested: Realtek RTS5129 (0bda:0129) + tray + Lexar 2TB SDXC > Tested: cold boot detects mmcblk0 (2026-02-24) > Tested: hotplug insert enumerates mmcblk0 (2026-02-23) > Signed-off-by: Sean Rhodes <sean@starlabs.systems> > --- > drivers/mmc/host/rtsx_usb_sdmmc.c | 5 +++++ > 1 file changed, 5 insertions(+) > > diff --git a/drivers/mmc/host/rtsx_usb_sdmmc.c b/drivers/mmc/host/rtsx_usb_sdmmc.c > index ec3eeea78e95..6be98926387d 100644 > --- a/drivers/mmc/host/rtsx_usb_sdmmc.c > +++ b/drivers/mmc/host/rtsx_usb_sdmmc.c > @@ -1108,6 +1108,11 @@ static int sd_power_on(struct rtsx_usb_sdmmc *host) > } > dev_dbg(sdmmc_dev(host), "%s\n", __func__); > rtsx_usb_init_cmd(ucr); > + /* Start SD init at 3.3V, like the old rts5139 driver. */ > + rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_PAD_CTL, > + SD_IO_USING_1V8, SD_IO_USING_3V3); > + rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, LDO_POWER_CFG, > + TUNE_SD18_MASK, TUNE_SD18_3V3); Ideally this should be managed by the ->start_signal_voltage_switch() callback that is set to sdmmc_switch_voltage(). However, I understand that you may need this because of patch1, and because mmc_power_up() first calls mmc_set_initial_state() before it calls mmc_set_initial_signal_voltage(). Right? > rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_SELECT, 0x07, SD_MOD_SEL); > rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_SHARE_MODE, > CARD_SHARE_MASK, CARD_SHARE_SD); > -- > 2.51.0 Kind regards Uffe
> Right? Yes, that's right On Tue, 24 Mar 2026 at 11:42, Ulf Hansson <ulf.hansson@linaro.org> wrote: > > On Thu, 12 Mar 2026 at 13:16, Sean Rhodes <sean@starlabs.systems> wrote: > > > > Some tray-based readers keep SD_CD asserted even without a card. The > > rtsx_usb_sdmmc driver now validates insertion with a minimal probe > > sequence. That probe must start with the SD pads in 3.3V mode. > > > > Like the old rts5139 driver (sd_init_power()), force the SD pads to > > 3.3V and tune the SD18 regulator to 3.3V before powering up the card. > > This avoids spurious probe timeouts when the reader is left in 1.8V > > from a previous UHS session. > > > > Tested: Realtek RTS5129 (0bda:0129) + tray + Lexar 2TB SDXC > > Tested: cold boot detects mmcblk0 (2026-02-24) > > Tested: hotplug insert enumerates mmcblk0 (2026-02-23) > > Signed-off-by: Sean Rhodes <sean@starlabs.systems> > > --- > > drivers/mmc/host/rtsx_usb_sdmmc.c | 5 +++++ > > 1 file changed, 5 insertions(+) > > > > diff --git a/drivers/mmc/host/rtsx_usb_sdmmc.c b/drivers/mmc/host/rtsx_usb_sdmmc.c > > index ec3eeea78e95..6be98926387d 100644 > > --- a/drivers/mmc/host/rtsx_usb_sdmmc.c > > +++ b/drivers/mmc/host/rtsx_usb_sdmmc.c > > @@ -1108,6 +1108,11 @@ static int sd_power_on(struct rtsx_usb_sdmmc *host) > > } > > dev_dbg(sdmmc_dev(host), "%s\n", __func__); > > rtsx_usb_init_cmd(ucr); > > + /* Start SD init at 3.3V, like the old rts5139 driver. */ > > + rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_PAD_CTL, > > + SD_IO_USING_1V8, SD_IO_USING_3V3); > > + rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, LDO_POWER_CFG, > > + TUNE_SD18_MASK, TUNE_SD18_3V3); > > Ideally this should be managed by the ->start_signal_voltage_switch() > callback that is set to sdmmc_switch_voltage(). > > However, I understand that you may need this because of patch1, and > because mmc_power_up() first calls mmc_set_initial_state() before it > calls mmc_set_initial_signal_voltage(). > > Right? > > > rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_SELECT, 0x07, SD_MOD_SEL); > > rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_SHARE_MODE, > > CARD_SHARE_MASK, CARD_SHARE_SD); > > -- > > 2.51.0 > > Kind regards > Uffe
The rtsx_usb_sdmmc driver already implements UHS-I voltage switching,
timing setup and tuning, but it only advertises support up to SDR50.
Like the old rts5139 driver, also advertise SDR104 and DDR50 so the MMC
core can pick the best mode supported by the card.
Tested: Realtek RTS5129 (0bda:0129) + tray + Lexar 2TB SDXC
Tested: timing spec SDR104 @ 208 MHz, signal voltage 1.8 V (2026-02-24)
Signed-off-by: Sean Rhodes <sean@starlabs.systems>
---
drivers/mmc/host/rtsx_usb_sdmmc.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/mmc/host/rtsx_usb_sdmmc.c b/drivers/mmc/host/rtsx_usb_sdmmc.c
index 6be98926387d..8d5ebe92018e 100644
--- a/drivers/mmc/host/rtsx_usb_sdmmc.c
+++ b/drivers/mmc/host/rtsx_usb_sdmmc.c
@@ -1487,6 +1487,7 @@ static void rtsx_usb_init_host(struct rtsx_usb_sdmmc *host)
mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_SD_HIGHSPEED |
MMC_CAP_MMC_HIGHSPEED | MMC_CAP_BUS_WIDTH_TEST |
MMC_CAP_UHS_SDR12 | MMC_CAP_UHS_SDR25 | MMC_CAP_UHS_SDR50 |
+ MMC_CAP_UHS_DDR50 | MMC_CAP_UHS_SDR104 |
MMC_CAP_SYNC_RUNTIME_PM;
mmc->caps2 = MMC_CAP2_NO_PRESCAN_POWERUP | MMC_CAP2_FULL_PWR_CYCLE |
MMC_CAP2_NO_SDIO;
--
2.51.0
On Thu, 12 Mar 2026 at 13:16, Sean Rhodes <sean@starlabs.systems> wrote: > > The rtsx_usb_sdmmc driver already implements UHS-I voltage switching, > timing setup and tuning, but it only advertises support up to SDR50. > > Like the old rts5139 driver, also advertise SDR104 and DDR50 so the MMC > core can pick the best mode supported by the card. > > Tested: Realtek RTS5129 (0bda:0129) + tray + Lexar 2TB SDXC > Tested: timing spec SDR104 @ 208 MHz, signal voltage 1.8 V (2026-02-24) > Signed-off-by: Sean Rhodes <sean@starlabs.systems> > --- > drivers/mmc/host/rtsx_usb_sdmmc.c | 1 + > 1 file changed, 1 insertion(+) > > diff --git a/drivers/mmc/host/rtsx_usb_sdmmc.c b/drivers/mmc/host/rtsx_usb_sdmmc.c > index 6be98926387d..8d5ebe92018e 100644 > --- a/drivers/mmc/host/rtsx_usb_sdmmc.c > +++ b/drivers/mmc/host/rtsx_usb_sdmmc.c > @@ -1487,6 +1487,7 @@ static void rtsx_usb_init_host(struct rtsx_usb_sdmmc *host) > mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_SD_HIGHSPEED | > MMC_CAP_MMC_HIGHSPEED | MMC_CAP_BUS_WIDTH_TEST | > MMC_CAP_UHS_SDR12 | MMC_CAP_UHS_SDR25 | MMC_CAP_UHS_SDR50 | > + MMC_CAP_UHS_DDR50 | MMC_CAP_UHS_SDR104 | Please drop this patch from the series for now. My point is that the series addresses enough of complicated problems, so I suggest we start by fixing them first before adding new functionality. > MMC_CAP_SYNC_RUNTIME_PM; > mmc->caps2 = MMC_CAP2_NO_PRESCAN_POWERUP | MMC_CAP2_FULL_PWR_CYCLE | > MMC_CAP2_NO_SDIO; > -- > 2.51.0 Kind regards Uffe
The driver advertises UHS SDR12 and SDR25 support, but sd_set_timing()
only programs SD_30_MODE for SDR50/SDR104. When the core selects SDR12
or SDR25 this leaves the controller in SD 2.0 mode.
Program SD_30_MODE for SDR12 and SDR25 too.
Tested: Realtek RTS5129 (0bda:0129) + tray + Lexar 2TB SDXC
Tested: timing spec SDR104 @ 208 MHz, signal voltage 1.8 V (2026-02-24)
Signed-off-by: Sean Rhodes <sean@starlabs.systems>
---
drivers/mmc/host/rtsx_usb_sdmmc.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/drivers/mmc/host/rtsx_usb_sdmmc.c b/drivers/mmc/host/rtsx_usb_sdmmc.c
index 8d5ebe92018e..62f6b3f76c7b 100644
--- a/drivers/mmc/host/rtsx_usb_sdmmc.c
+++ b/drivers/mmc/host/rtsx_usb_sdmmc.c
@@ -1231,6 +1231,8 @@ static int sd_set_timing(struct rtsx_usb_sdmmc *host,
switch (timing) {
case MMC_TIMING_UHS_SDR104:
+ case MMC_TIMING_UHS_SDR12:
+ case MMC_TIMING_UHS_SDR25:
case MMC_TIMING_UHS_SDR50:
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_CFG1,
0x0C | SD_ASYNC_FIFO_RST,
@@ -1298,13 +1300,14 @@ static void sdmmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
switch (ios->timing) {
case MMC_TIMING_UHS_SDR104:
+ case MMC_TIMING_UHS_SDR12:
+ case MMC_TIMING_UHS_SDR25:
case MMC_TIMING_UHS_SDR50:
host->ssc_depth = SSC_DEPTH_2M;
host->vpclk = true;
host->double_clk = false;
break;
case MMC_TIMING_UHS_DDR50:
- case MMC_TIMING_UHS_SDR25:
host->ssc_depth = SSC_DEPTH_1M;
break;
default:
--
2.51.0
Hold a runtime-PM reference across bulk transfers, and mark the device
busy afterwards.
When runtime PM is already in progress (e.g. from rtsx_usb_suspend()),
avoid forcing a runtime resume from within the PM path by using
usb_autopm_get_interface_no_resume() unless the interface is already
runtime-suspended.
Signed-off-by: Sean Rhodes <sean@starlabs.systems>
---
drivers/misc/cardreader/rtsx_usb.c | 38 ++++++++++++++++++++++++------
1 file changed, 31 insertions(+), 7 deletions(-)
diff --git a/drivers/misc/cardreader/rtsx_usb.c b/drivers/misc/cardreader/rtsx_usb.c
index 1830e9ed2521..5d818b632788 100644
--- a/drivers/misc/cardreader/rtsx_usb.c
+++ b/drivers/misc/cardreader/rtsx_usb.c
@@ -12,6 +12,7 @@
#include <linux/usb.h>
#include <linux/platform_device.h>
#include <linux/mfd/core.h>
+#include <linux/pm_runtime.h>
#include <linux/rtsx_usb.h>
static int polling_pipe = 1;
@@ -65,19 +66,42 @@ static int rtsx_usb_bulk_transfer_sglist(struct rtsx_ucr *ucr,
}
int rtsx_usb_transfer_data(struct rtsx_ucr *ucr, unsigned int pipe,
- void *buf, unsigned int len, int num_sg,
- unsigned int *act_len, int timeout)
+ void *buf, unsigned int len, int num_sg,
+ unsigned int *act_len, int timeout)
{
+ int ret;
+ struct device *dev = &ucr->pusb_intf->dev;
+
if (timeout < 600)
timeout = 600;
+ /*
+ * During runtime suspend/resume callbacks, avoid forcing a runtime resume
+ * from within the PM path. The device is still active when
+ * rtsx_usb_suspend() runs, but usb_autopm_get_interface() can block when
+ * runtime PM is already in progress.
+ */
+ if (pm_runtime_status_suspended(dev)) {
+ ret = usb_autopm_get_interface(ucr->pusb_intf);
+ } else {
+ usb_autopm_get_interface_no_resume(ucr->pusb_intf);
+ ret = 0;
+ }
+ if (ret)
+ return ret;
+
if (num_sg)
- return rtsx_usb_bulk_transfer_sglist(ucr, pipe,
- (struct scatterlist *)buf, num_sg, len, act_len,
- timeout);
+ ret = rtsx_usb_bulk_transfer_sglist(ucr, pipe,
+ (struct scatterlist *)buf,
+ num_sg, len, act_len,
+ timeout);
else
- return usb_bulk_msg(ucr->pusb_dev, pipe, buf, len, act_len,
- timeout);
+ ret = usb_bulk_msg(ucr->pusb_dev, pipe, buf, len, act_len,
+ timeout);
+
+ usb_mark_last_busy(ucr->pusb_dev);
+ usb_autopm_put_interface(ucr->pusb_intf);
+ return ret;
}
EXPORT_SYMBOL_GPL(rtsx_usb_transfer_data);
--
2.51.0
+ Matthew
On Thu, 12 Mar 2026 at 13:16, Sean Rhodes <sean@starlabs.systems> wrote:
>
> Hold a runtime-PM reference across bulk transfers, and mark the device
> busy afterwards.
>
> When runtime PM is already in progress (e.g. from rtsx_usb_suspend()),
> avoid forcing a runtime resume from within the PM path by using
> usb_autopm_get_interface_no_resume() unless the interface is already
> runtime-suspended.
Can you please clarify the problem further? It's unclear to me what
you mean by "runtime PM is already in progress."
>
> Signed-off-by: Sean Rhodes <sean@starlabs.systems>
> ---
> drivers/misc/cardreader/rtsx_usb.c | 38 ++++++++++++++++++++++++------
> 1 file changed, 31 insertions(+), 7 deletions(-)
>
> diff --git a/drivers/misc/cardreader/rtsx_usb.c b/drivers/misc/cardreader/rtsx_usb.c
> index 1830e9ed2521..5d818b632788 100644
> --- a/drivers/misc/cardreader/rtsx_usb.c
> +++ b/drivers/misc/cardreader/rtsx_usb.c
> @@ -12,6 +12,7 @@
> #include <linux/usb.h>
> #include <linux/platform_device.h>
> #include <linux/mfd/core.h>
> +#include <linux/pm_runtime.h>
> #include <linux/rtsx_usb.h>
>
> static int polling_pipe = 1;
> @@ -65,19 +66,42 @@ static int rtsx_usb_bulk_transfer_sglist(struct rtsx_ucr *ucr,
> }
>
> int rtsx_usb_transfer_data(struct rtsx_ucr *ucr, unsigned int pipe,
> - void *buf, unsigned int len, int num_sg,
> - unsigned int *act_len, int timeout)
> + void *buf, unsigned int len, int num_sg,
> + unsigned int *act_len, int timeout)
> {
> + int ret;
> + struct device *dev = &ucr->pusb_intf->dev;
> +
> if (timeout < 600)
> timeout = 600;
>
> + /*
> + * During runtime suspend/resume callbacks, avoid forcing a runtime resume
> + * from within the PM path. The device is still active when
> + * rtsx_usb_suspend() runs, but usb_autopm_get_interface() can block when
> + * runtime PM is already in progress.
> + */
Thanks for adding a comment here! Yet it's unfortunately not clear
enough to me what is happening.
Can you please elaborate a bit more?
> + if (pm_runtime_status_suspended(dev)) {
> + ret = usb_autopm_get_interface(ucr->pusb_intf);
> + } else {
> + usb_autopm_get_interface_no_resume(ucr->pusb_intf);
> + ret = 0;
> + }
> + if (ret)
> + return ret;
> +
> if (num_sg)
> - return rtsx_usb_bulk_transfer_sglist(ucr, pipe,
> - (struct scatterlist *)buf, num_sg, len, act_len,
> - timeout);
> + ret = rtsx_usb_bulk_transfer_sglist(ucr, pipe,
> + (struct scatterlist *)buf,
> + num_sg, len, act_len,
> + timeout);
> else
> - return usb_bulk_msg(ucr->pusb_dev, pipe, buf, len, act_len,
> - timeout);
> + ret = usb_bulk_msg(ucr->pusb_dev, pipe, buf, len, act_len,
> + timeout);
> +
> + usb_mark_last_busy(ucr->pusb_dev);
> + usb_autopm_put_interface(ucr->pusb_intf);
> + return ret;
> }
> EXPORT_SYMBOL_GPL(rtsx_usb_transfer_data);
>
> --
> 2.51.0
Kind regards
Uffe
The runtime autosuspend callback currently queries card status and clears
OCP via USB register accesses.
On some systems this can deadlock runtime PM and leave the USB device
stuck in the "suspending" state.
Avoid issuing USB commands from the runtime autosuspend callback.
Instead, use the last cached card status (updated on successful status
reads) and conservatively assume a card may be present until status is
known.
Signed-off-by: Sean Rhodes <sean@starlabs.systems>
---
drivers/misc/cardreader/rtsx_usb.c | 11 +++++++----
include/linux/rtsx_usb.h | 3 +++
2 files changed, 10 insertions(+), 4 deletions(-)
diff --git a/drivers/misc/cardreader/rtsx_usb.c b/drivers/misc/cardreader/rtsx_usb.c
index 5d818b632788..63751797ee2b 100644
--- a/drivers/misc/cardreader/rtsx_usb.c
+++ b/drivers/misc/cardreader/rtsx_usb.c
@@ -336,6 +336,9 @@ int rtsx_usb_get_card_status(struct rtsx_ucr *ucr, u16 *status)
if (ret < 0)
return ret;
+ ucr->card_status_cache = *status;
+ ucr->card_status_valid = true;
+
return 0;
}
EXPORT_SYMBOL_GPL(rtsx_usb_get_card_status);
@@ -743,16 +746,16 @@ static int rtsx_usb_suspend(struct usb_interface *intf, pm_message_t message)
if (PMSG_IS_AUTO(message)) {
if (mutex_trylock(&ucr->dev_mutex)) {
- rtsx_usb_get_card_status(ucr, &val);
+ if (ucr->card_status_valid)
+ val = ucr->card_status_cache;
+ else
+ val = SD_CD | MS_CD;
mutex_unlock(&ucr->dev_mutex);
/* Defer the autosuspend if card exists */
if (val & (SD_CD | MS_CD)) {
device_for_each_child(&intf->dev, NULL, rtsx_usb_resume_child);
return -EAGAIN;
- } else {
- /* if the card does not exists, clear OCP status */
- rtsx_usb_write_register(ucr, OCPCTL, MS_OCP_CLEAR, MS_OCP_CLEAR);
}
} else {
/* There is an ongoing operation*/
diff --git a/include/linux/rtsx_usb.h b/include/linux/rtsx_usb.h
index 276b509c03e3..3188f6616729 100644
--- a/include/linux/rtsx_usb.h
+++ b/include/linux/rtsx_usb.h
@@ -59,6 +59,9 @@ struct rtsx_ucr {
struct usb_interface *pusb_intf;
struct usb_sg_request current_sg;
+ u16 card_status_cache;
+ bool card_status_valid;
+
struct timer_list sg_timer;
struct mutex dev_mutex;
};
--
2.51.0
Some Realtek USB SD readers with a tray can assert SD_CD even when no
card is present. This can make the MMC core believe a card exists and
trigger unnecessary initialization and suspend/shutdown failures.
Debounce the CD signal and validate a newly detected card by probing for
a response (CMD0 + CMD8/CMD55/CMD1) before reporting it present. Also
treat SD_INT as a removal indication even if SD_CD stays asserted.
Tested: Realtek RTS5129 (0bda:0129), tray inserted, no card (2026-02-24)
Tested: Realtek RTS5129 (0bda:0129), tray + SDXC card, mmcblk0 (2026-02-24)
Signed-off-by: Sean Rhodes <sean@starlabs.systems>
---
drivers/mmc/host/rtsx_usb_sdmmc.c | 156 ++++++++++++++++++++++++++++--
1 file changed, 148 insertions(+), 8 deletions(-)
diff --git a/drivers/mmc/host/rtsx_usb_sdmmc.c b/drivers/mmc/host/rtsx_usb_sdmmc.c
index 84674659a84d..ec3eeea78e95 100644
--- a/drivers/mmc/host/rtsx_usb_sdmmc.c
+++ b/drivers/mmc/host/rtsx_usb_sdmmc.c
@@ -19,6 +19,7 @@
#include <linux/scatterlist.h>
#include <linux/pm.h>
#include <linux/pm_runtime.h>
+#include <linux/jiffies.h>
#include <linux/rtsx_usb.h>
#include <linux/unaligned.h>
@@ -30,6 +31,9 @@
#define RTSX_USB_USE_LEDS_CLASS
#endif
+#define RTSX_USB_SD_CD_DEBOUNCE_CNT 2
+#define RTSX_USB_SD_INSERT_RETRY_MS 1000
+
struct rtsx_usb_sdmmc {
struct platform_device *pdev;
struct rtsx_ucr *ucr;
@@ -46,6 +50,8 @@ struct rtsx_usb_sdmmc {
bool card_exist;
bool initial_mode;
bool ddr_mode;
+ u8 cd_debounce;
+ unsigned long next_insert_check;
unsigned char power_mode;
u16 ocp_stat;
@@ -72,6 +78,13 @@ static inline void sd_clear_error(struct rtsx_usb_sdmmc *host)
rtsx_usb_clear_fsm_err(ucr);
}
+static int sd_set_bus_width(struct rtsx_usb_sdmmc *host,
+ unsigned char bus_width);
+static int sd_set_timing(struct rtsx_usb_sdmmc *host,
+ unsigned char timing, bool *ddr_mode);
+static int sd_power_on(struct rtsx_usb_sdmmc *host);
+static int sd_power_off(struct rtsx_usb_sdmmc *host);
+
#ifdef DEBUG
static void sd_print_debug_regs(struct rtsx_usb_sdmmc *host)
{
@@ -768,12 +781,94 @@ static int sdmmc_get_ro(struct mmc_host *mmc)
return 0;
}
+static bool sdmmc_validate_insert_locked(struct rtsx_usb_sdmmc *host)
+{
+ struct rtsx_ucr *ucr = host->ucr;
+ struct mmc_command cmd = { };
+ int err = 0;
+ bool probe_powered = false;
+ bool ddr_mode = false;
+
+ /*
+ * Some readers with a tray assert the mechanical SD_CD pin even when no
+ * card is present. Only report a card present when it responds to a
+ * minimal reset/probe sequence, similar to the old rts5139 behavior.
+ *
+ * Must be called with ucr->dev_mutex held.
+ */
+ if (host->power_mode == MMC_POWER_OFF) {
+ err = sd_power_on(host);
+ if (err)
+ return false;
+ probe_powered = true;
+
+ /* Issue clock signals to card for at least 74 clocks. */
+ rtsx_usb_write_register(ucr, SD_BUS_STAT,
+ SD_CLK_TOGGLE_EN, SD_CLK_TOGGLE_EN);
+ usleep_range(200, 400);
+ rtsx_usb_write_register(ucr, SD_BUS_STAT, SD_CLK_TOGGLE_EN, 0);
+ }
+
+ /*
+ * Ensure the interface is in a safe, legacy / initial-clock mode before
+ * probing for a response. The MMC core may not have configured ios yet.
+ */
+ err = sd_set_bus_width(host, MMC_BUS_WIDTH_1);
+ if (err)
+ goto out;
+
+ err = sd_set_timing(host, MMC_TIMING_LEGACY, &ddr_mode);
+ if (err)
+ goto out;
+
+ ucr->cur_clk = 0;
+ err = rtsx_usb_switch_clock(ucr, 400000, SSC_DEPTH_512K,
+ true, true, false);
+ if (err)
+ goto out;
+
+ cmd.opcode = MMC_GO_IDLE_STATE;
+ cmd.arg = 0;
+ cmd.flags = MMC_RSP_NONE | MMC_CMD_BC;
+ sd_send_cmd_get_rsp(host, &cmd);
+
+ /* SD v2.0+: CMD8 */
+ cmd.opcode = SD_SEND_IF_COND;
+ cmd.arg = 0x1aa;
+ cmd.flags = MMC_RSP_R7 | MMC_CMD_BCR;
+ sd_send_cmd_get_rsp(host, &cmd);
+ if (!cmd.error)
+ goto out;
+
+ /* SD v1.x: CMD55 */
+ cmd.opcode = MMC_APP_CMD;
+ cmd.arg = 0;
+ cmd.flags = MMC_RSP_R1 | MMC_CMD_AC;
+ sd_send_cmd_get_rsp(host, &cmd);
+ if (!cmd.error)
+ goto out;
+
+ /* MMC: CMD1 */
+ cmd.opcode = MMC_SEND_OP_COND;
+ cmd.arg = 0;
+ cmd.flags = MMC_RSP_R3 | MMC_CMD_BCR;
+ sd_send_cmd_get_rsp(host, &cmd);
+
+out:
+ if (probe_powered)
+ sd_power_off(host);
+ return !err && !cmd.error;
+}
+
static int sdmmc_get_cd(struct mmc_host *mmc)
{
struct rtsx_usb_sdmmc *host = mmc_priv(mmc);
struct rtsx_ucr *ucr = host->ucr;
int err;
u16 val;
+ u8 pend;
+ bool sd_int = false;
+ bool cd_raw = false;
if (host->host_removal)
return -ENOMEDIUM;
@@ -782,28 +877,71 @@ static int sdmmc_get_cd(struct mmc_host *mmc)
/* Check SD card detect */
err = rtsx_usb_get_card_status(ucr, &val);
-
- mutex_unlock(&ucr->dev_mutex);
-
- /* Treat failed detection as non-exist */
if (err)
- goto no_card;
+ goto no_card_unlock;
/* get OCP status */
host->ocp_stat = (val >> 4) & 0x03;
- if (val & SD_CD) {
- host->card_exist = true;
+ cd_raw = !!(val & SD_CD);
+
+ /* Use SD_INT as a reliable removal indication on some tray readers. */
+ err = rtsx_usb_read_register(ucr, CARD_INT_PEND, &pend);
+ if (!err) {
+ sd_int = !!(pend & SD_INT);
+ if (sd_int)
+ rtsx_usb_write_register(ucr, CARD_INT_PEND,
+ SD_INT, SD_INT);
+ }
+
+ if (!cd_raw) {
+ host->cd_debounce = 0;
+ host->next_insert_check = 0;
+ goto no_card_unlock;
+ }
+
+ /*
+ * rts5139-style: when a card is already known present, treat SD_INT as
+ * a removal event even if SD_CD stays high (e.g. tray-based readers).
+ */
+ if (host->card_exist) {
+ if (sd_int) {
+ host->cd_debounce = 0;
+ host->next_insert_check = 0;
+ goto no_card_unlock;
+ }
+ mutex_unlock(&ucr->dev_mutex);
return 1;
}
-no_card:
+ /* Debounce mechanical CD before probing for a response. */
+ if (host->cd_debounce < RTSX_USB_SD_CD_DEBOUNCE_CNT) {
+ host->cd_debounce++;
+ goto no_card_unlock;
+ }
+
+ /* Avoid pounding the bus with probes if CD is stuck asserted. */
+ if (time_before(jiffies, host->next_insert_check))
+ goto no_card_unlock;
+
+ if (!sdmmc_validate_insert_locked(host)) {
+ host->next_insert_check = jiffies +
+ msecs_to_jiffies(RTSX_USB_SD_INSERT_RETRY_MS);
+ goto no_card_unlock;
+ }
+
+ host->card_exist = true;
+ mutex_unlock(&ucr->dev_mutex);
+ return 1;
+
+no_card_unlock:
/* clear OCP status */
if (host->ocp_stat & (MS_OCP_NOW | MS_OCP_EVER)) {
rtsx_usb_write_register(ucr, OCPCTL, MS_OCP_CLEAR, MS_OCP_CLEAR);
host->ocp_stat = 0;
}
host->card_exist = false;
+ mutex_unlock(&ucr->dev_mutex);
return 0;
}
@@ -1359,6 +1497,8 @@ static void rtsx_usb_init_host(struct rtsx_usb_sdmmc *host)
host->power_mode = MMC_POWER_OFF;
host->ocp_stat = 0;
+ host->cd_debounce = 0;
+ host->next_insert_check = 0;
}
static int rtsx_usb_sdmmc_drv_probe(struct platform_device *pdev)
--
2.51.0
Some tray-based readers keep SD_CD asserted even without a card. The
rtsx_usb_sdmmc driver now validates insertion with a minimal probe
sequence. That probe must start with the SD pads in 3.3V mode.
Like the old rts5139 driver (sd_init_power()), force the SD pads to
3.3V and tune the SD18 regulator to 3.3V before powering up the card.
This avoids spurious probe timeouts when the reader is left in 1.8V
from a previous UHS session.
Tested: Realtek RTS5129 (0bda:0129) + tray + Lexar 2TB SDXC
Tested: cold boot detects mmcblk0 (2026-02-24)
Tested: hotplug insert enumerates mmcblk0 (2026-02-23)
Signed-off-by: Sean Rhodes <sean@starlabs.systems>
---
drivers/mmc/host/rtsx_usb_sdmmc.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/drivers/mmc/host/rtsx_usb_sdmmc.c b/drivers/mmc/host/rtsx_usb_sdmmc.c
index ec3eeea78e95..6be98926387d 100644
--- a/drivers/mmc/host/rtsx_usb_sdmmc.c
+++ b/drivers/mmc/host/rtsx_usb_sdmmc.c
@@ -1108,6 +1108,11 @@ static int sd_power_on(struct rtsx_usb_sdmmc *host)
}
dev_dbg(sdmmc_dev(host), "%s\n", __func__);
rtsx_usb_init_cmd(ucr);
+ /* Start SD init at 3.3V, like the old rts5139 driver. */
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_PAD_CTL,
+ SD_IO_USING_1V8, SD_IO_USING_3V3);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, LDO_POWER_CFG,
+ TUNE_SD18_MASK, TUNE_SD18_3V3);
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_SELECT, 0x07, SD_MOD_SEL);
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_SHARE_MODE,
CARD_SHARE_MASK, CARD_SHARE_SD);
--
2.51.0
The rtsx_usb_sdmmc driver already implements UHS-I voltage switching,
timing setup and tuning, but it only advertises support up to SDR50.
Like the old rts5139 driver, also advertise SDR104 and DDR50 so the MMC
core can pick the best mode supported by the card.
Tested: Realtek RTS5129 (0bda:0129) + tray + Lexar 2TB SDXC
Tested: timing spec SDR104 @ 208 MHz, signal voltage 1.8 V (2026-02-24)
Signed-off-by: Sean Rhodes <sean@starlabs.systems>
---
drivers/mmc/host/rtsx_usb_sdmmc.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/mmc/host/rtsx_usb_sdmmc.c b/drivers/mmc/host/rtsx_usb_sdmmc.c
index 6be98926387d..8d5ebe92018e 100644
--- a/drivers/mmc/host/rtsx_usb_sdmmc.c
+++ b/drivers/mmc/host/rtsx_usb_sdmmc.c
@@ -1487,6 +1487,7 @@ static void rtsx_usb_init_host(struct rtsx_usb_sdmmc *host)
mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_SD_HIGHSPEED |
MMC_CAP_MMC_HIGHSPEED | MMC_CAP_BUS_WIDTH_TEST |
MMC_CAP_UHS_SDR12 | MMC_CAP_UHS_SDR25 | MMC_CAP_UHS_SDR50 |
+ MMC_CAP_UHS_DDR50 | MMC_CAP_UHS_SDR104 |
MMC_CAP_SYNC_RUNTIME_PM;
mmc->caps2 = MMC_CAP2_NO_PRESCAN_POWERUP | MMC_CAP2_FULL_PWR_CYCLE |
MMC_CAP2_NO_SDIO;
--
2.51.0
The driver advertises UHS SDR12 and SDR25 support, but sd_set_timing()
only programs SD_30_MODE for SDR50/SDR104. When the core selects SDR12
or SDR25 this leaves the controller in SD 2.0 mode.
Program SD_30_MODE for SDR12 and SDR25 too.
Tested: Realtek RTS5129 (0bda:0129) + tray + Lexar 2TB SDXC
Tested: timing spec SDR104 @ 208 MHz, signal voltage 1.8 V (2026-02-24)
Signed-off-by: Sean Rhodes <sean@starlabs.systems>
---
drivers/mmc/host/rtsx_usb_sdmmc.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/drivers/mmc/host/rtsx_usb_sdmmc.c b/drivers/mmc/host/rtsx_usb_sdmmc.c
index 8d5ebe92018e..62f6b3f76c7b 100644
--- a/drivers/mmc/host/rtsx_usb_sdmmc.c
+++ b/drivers/mmc/host/rtsx_usb_sdmmc.c
@@ -1231,6 +1231,8 @@ static int sd_set_timing(struct rtsx_usb_sdmmc *host,
switch (timing) {
case MMC_TIMING_UHS_SDR104:
+ case MMC_TIMING_UHS_SDR12:
+ case MMC_TIMING_UHS_SDR25:
case MMC_TIMING_UHS_SDR50:
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_CFG1,
0x0C | SD_ASYNC_FIFO_RST,
@@ -1298,13 +1300,14 @@ static void sdmmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
switch (ios->timing) {
case MMC_TIMING_UHS_SDR104:
+ case MMC_TIMING_UHS_SDR12:
+ case MMC_TIMING_UHS_SDR25:
case MMC_TIMING_UHS_SDR50:
host->ssc_depth = SSC_DEPTH_2M;
host->vpclk = true;
host->double_clk = false;
break;
case MMC_TIMING_UHS_DDR50:
- case MMC_TIMING_UHS_SDR25:
host->ssc_depth = SSC_DEPTH_1M;
break;
default:
--
2.51.0
Hold a runtime-PM reference across bulk transfers, and mark the device
busy afterwards.
When runtime PM is already in progress (e.g. from rtsx_usb_suspend()),
avoid forcing a runtime resume from within the PM path by using
usb_autopm_get_interface_no_resume() unless the interface is already
runtime-suspended.
Signed-off-by: Sean Rhodes <sean@starlabs.systems>
---
drivers/misc/cardreader/rtsx_usb.c | 38 ++++++++++++++++++++++++------
1 file changed, 31 insertions(+), 7 deletions(-)
diff --git a/drivers/misc/cardreader/rtsx_usb.c b/drivers/misc/cardreader/rtsx_usb.c
index 1830e9ed2521..5d818b632788 100644
--- a/drivers/misc/cardreader/rtsx_usb.c
+++ b/drivers/misc/cardreader/rtsx_usb.c
@@ -12,6 +12,7 @@
#include <linux/usb.h>
#include <linux/platform_device.h>
#include <linux/mfd/core.h>
+#include <linux/pm_runtime.h>
#include <linux/rtsx_usb.h>
static int polling_pipe = 1;
@@ -65,19 +66,42 @@ static int rtsx_usb_bulk_transfer_sglist(struct rtsx_ucr *ucr,
}
int rtsx_usb_transfer_data(struct rtsx_ucr *ucr, unsigned int pipe,
- void *buf, unsigned int len, int num_sg,
- unsigned int *act_len, int timeout)
+ void *buf, unsigned int len, int num_sg,
+ unsigned int *act_len, int timeout)
{
+ int ret;
+ struct device *dev = &ucr->pusb_intf->dev;
+
if (timeout < 600)
timeout = 600;
+ /*
+ * During runtime suspend/resume callbacks, avoid forcing a runtime resume
+ * from within the PM path. The device is still active when
+ * rtsx_usb_suspend() runs, but usb_autopm_get_interface() can block when
+ * runtime PM is already in progress.
+ */
+ if (pm_runtime_status_suspended(dev)) {
+ ret = usb_autopm_get_interface(ucr->pusb_intf);
+ } else {
+ usb_autopm_get_interface_no_resume(ucr->pusb_intf);
+ ret = 0;
+ }
+ if (ret)
+ return ret;
+
if (num_sg)
- return rtsx_usb_bulk_transfer_sglist(ucr, pipe,
- (struct scatterlist *)buf, num_sg, len, act_len,
- timeout);
+ ret = rtsx_usb_bulk_transfer_sglist(ucr, pipe,
+ (struct scatterlist *)buf,
+ num_sg, len, act_len,
+ timeout);
else
- return usb_bulk_msg(ucr->pusb_dev, pipe, buf, len, act_len,
- timeout);
+ ret = usb_bulk_msg(ucr->pusb_dev, pipe, buf, len, act_len,
+ timeout);
+
+ usb_mark_last_busy(ucr->pusb_dev);
+ usb_autopm_put_interface(ucr->pusb_intf);
+ return ret;
}
EXPORT_SYMBOL_GPL(rtsx_usb_transfer_data);
--
2.51.0
The runtime autosuspend callback currently queries card status and clears
OCP via USB register accesses.
On some systems this can deadlock runtime PM and leave the USB device
stuck in the "suspending" state.
Avoid issuing USB commands from the runtime autosuspend callback.
Instead, use the last cached card status (updated on successful status
reads) and conservatively assume a card may be present until status is
known.
Signed-off-by: Sean Rhodes <sean@starlabs.systems>
---
drivers/misc/cardreader/rtsx_usb.c | 11 +++++++----
include/linux/rtsx_usb.h | 3 +++
2 files changed, 10 insertions(+), 4 deletions(-)
diff --git a/drivers/misc/cardreader/rtsx_usb.c b/drivers/misc/cardreader/rtsx_usb.c
index 5d818b632788..63751797ee2b 100644
--- a/drivers/misc/cardreader/rtsx_usb.c
+++ b/drivers/misc/cardreader/rtsx_usb.c
@@ -336,6 +336,9 @@ int rtsx_usb_get_card_status(struct rtsx_ucr *ucr, u16 *status)
if (ret < 0)
return ret;
+ ucr->card_status_cache = *status;
+ ucr->card_status_valid = true;
+
return 0;
}
EXPORT_SYMBOL_GPL(rtsx_usb_get_card_status);
@@ -743,16 +746,16 @@ static int rtsx_usb_suspend(struct usb_interface *intf, pm_message_t message)
if (PMSG_IS_AUTO(message)) {
if (mutex_trylock(&ucr->dev_mutex)) {
- rtsx_usb_get_card_status(ucr, &val);
+ if (ucr->card_status_valid)
+ val = ucr->card_status_cache;
+ else
+ val = SD_CD | MS_CD;
mutex_unlock(&ucr->dev_mutex);
/* Defer the autosuspend if card exists */
if (val & (SD_CD | MS_CD)) {
device_for_each_child(&intf->dev, NULL, rtsx_usb_resume_child);
return -EAGAIN;
- } else {
- /* if the card does not exists, clear OCP status */
- rtsx_usb_write_register(ucr, OCPCTL, MS_OCP_CLEAR, MS_OCP_CLEAR);
}
} else {
/* There is an ongoing operation*/
diff --git a/include/linux/rtsx_usb.h b/include/linux/rtsx_usb.h
index 276b509c03e3..3188f6616729 100644
--- a/include/linux/rtsx_usb.h
+++ b/include/linux/rtsx_usb.h
@@ -59,6 +59,9 @@ struct rtsx_ucr {
struct usb_interface *pusb_intf;
struct usb_sg_request current_sg;
+ u16 card_status_cache;
+ bool card_status_valid;
+
struct timer_list sg_timer;
struct mutex dev_mutex;
};
--
2.51.0
© 2016 - 2026 Red Hat, Inc.