lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [day] [month] [year] [list]
Message-ID: <CABtds-08TC6brxcSDqz426BZyXVusp96poGS=GrWfSOTFNULMg@mail.gmail.com>
Date: Tue, 4 Nov 2025 12:02:42 -0500
from: Sean Rhodes <sean@...rlabs.systems>
To: linux-kernel@...r.kernel.org
Subject: [PATCH] drivers/mmc: rtsx_usb: add workqueue-based card polling

>From 542dd6baa424b262ef663cfbd2abc60dded6f91b Mon Sep 17 00:00:00 2001
From: Sean Rhodes <sean@...rlabs.systems>
Date: Tue, 4 Nov 2025 16:16:48 +0000
Subject: [PATCH 4/5] drivers/mmc: rtsx_usb: add workqueue-based card polling

Move the SD/MMC host to a delayed work poll that adapts between a
50 ms cadence while a request is active/present and a 1 s cadence once
the controller goes quiet. Track ongoing sequential transfers so we
can stop streaming cleanly, and drop the clock tree into an idle state
when the card is absent or idle to save power.

Cc: Ulf Hansson <ulf.hansson@...aro.org>
Cc: Ricky Wu <ricky_wu@...ltek.com>
Cc: Avri Altman <avri.altman@...disk.com>
Cc: Binbin Zhou <zhoubinbin@...ngson.cn>
Cc: Nathan Chancellor <nathan@...nel.org>
Cc: Jisheng Zhang <jszhang@...nel.org>
Cc: Dan Carpenter <dan.carpenter@...aro.org>
Cc: linux-mmc@...r.kernel.org
Cc: linux-kernel@...r.kernel.org
Signed-off-by: Sean Rhodes <sean@...rlabs.systems>
---
 drivers/mmc/host/rtsx_usb_sdmmc.c | 245 +++++++++++++++++++++++++++++-
 1 file changed, 242 insertions(+), 3 deletions(-)

diff --git a/drivers/mmc/host/rtsx_usb_sdmmc.c
b/drivers/mmc/host/rtsx_usb_sdmmc.c
index e23ef4111dab..5d8c72459b3f 100644
--- a/drivers/mmc/host/rtsx_usb_sdmmc.c
+++ b/drivers/mmc/host/rtsx_usb_sdmmc.c
@@ -19,6 +19,8 @@
 #include <linux/scatterlist.h>
 #include <linux/pm.h>
 #include <linux/pm_runtime.h>
+#include <linux/workqueue.h>
+#include <linux/jiffies.h>

 #include <linux/rtsx_usb.h>
 #include <linux/unaligned.h>
@@ -26,10 +28,14 @@
 #if defined(CONFIG_LEDS_CLASS) || (defined(CONFIG_LEDS_CLASS_MODULE) && \
 		defined(CONFIG_MMC_REALTEK_USB_MODULE))
 #include <linux/leds.h>
-#include <linux/workqueue.h>
 #define RTSX_USB_USE_LEDS_CLASS
 #endif

+#define RTSX_USB_SD_POLL_INTERVAL	msecs_to_jiffies(50)
+#define RTSX_USB_SD_IDLE_POLL_INTERVAL	msecs_to_jiffies(1000)
+#define RTSX_USB_SD_IDLE_WAIT_MAX	10
+#define RTSX_USB_SD_SEQ_WAIT_MAX	2
+
 struct rtsx_usb_sdmmc {
 	struct platform_device	*pdev;
 	struct rtsx_ucr	*ucr;
@@ -37,6 +43,15 @@ struct rtsx_usb_sdmmc {
 	struct mmc_request	*mrq;

 	struct mutex		host_mutex;
+	struct delayed_work	card_poll;
+	unsigned long		poll_interval;
+	unsigned int		idle_counter;
+	unsigned int		idle_wait_max;
+	bool			idle;
+	bool			seq_mode;
+	bool			seq_read;
+	unsigned int		seq_counter;
+	unsigned int		seq_wait_max;

 	u8			ssc_depth;
 	unsigned int		clock;
@@ -72,6 +87,78 @@ static inline void sd_clear_error(struct
rtsx_usb_sdmmc *host)
 	rtsx_usb_clear_fsm_err(ucr);
 }

+static void sdmmc_stop_seq_mode(struct rtsx_usb_sdmmc *host)
+{
+	struct mmc_command stop = { };
+
+	if (!host->seq_mode)
+		return;
+
+	stop.opcode = MMC_STOP_TRANSMISSION;
+	stop.arg = 0;
+	stop.flags = MMC_RSP_R1B | MMC_CMD_AC;
+
+	sd_send_cmd_get_rsp(host, &stop);
+	if (stop.error)
+		dev_dbg(sdmmc_dev(host), "stop transmission err %d\n",
+			stop.error);
+
+	rtsx_usb_write_register(host->ucr, MC_FIFO_CTL,
+				      FIFO_FLUSH, FIFO_FLUSH);
+	host->seq_mode = false;
+	host->seq_read = false;
+	host->seq_counter = 0;
+}
+
+static void sdmmc_enter_idle(struct rtsx_usb_sdmmc *host)
+{
+	struct rtsx_ucr *ucr = host->ucr;
+	int err;
+
+	if (host->idle)
+		return;
+
+	err = rtsx_usb_write_register(ucr, CLK_DIV, CLK_CHANGE, CLK_CHANGE);
+	if (err)
+		goto out_dbg;
+
+	err = rtsx_usb_write_register(ucr, FPDCTL,
+				      SSC_POWER_MASK, SSC_POWER_DOWN);
+	if (!err)
+		host->idle = true;
+
+out_dbg:
+	if (err)
+		dev_dbg(sdmmc_dev(host), "enter idle failed %d\n", err);
+	else
+		host->idle_counter = host->idle_wait_max;
+}
+
+static void sdmmc_leave_idle(struct rtsx_usb_sdmmc *host)
+{
+	struct rtsx_ucr *ucr = host->ucr;
+	int err;
+
+	if (!host->idle)
+		return;
+
+	err = rtsx_usb_write_register(ucr, FPDCTL,
+				      SSC_POWER_MASK, SSC_POWER_ON);
+	if (err)
+		goto out_dbg;
+
+	usleep_range(100, 150);
+	err = rtsx_usb_write_register(ucr, CLK_DIV, CLK_CHANGE, 0);
+	if (!err)
+		host->idle = false;
+
+out_dbg:
+	if (err)
+		dev_dbg(sdmmc_dev(host), "leave idle failed %d\n", err);
+	else
+		host->idle_counter = 0;
+}
+
 #ifdef DEBUG
 static void sd_print_debug_regs(struct rtsx_usb_sdmmc *host)
 {
@@ -507,8 +594,11 @@ static int sd_rw_multi(struct rtsx_usb_sdmmc
*host, struct mmc_request *mrq)
 			SD_TRANSFER_END, SD_TRANSFER_END);

 	err = rtsx_usb_send_cmd(ucr, flag, 100);
-	if (err)
+	if (err) {
+		host->seq_mode = false;
+		host->seq_counter = 0;
 		return err;
+	}

 	if (read)
 		pipe = usb_rcvbulkpipe(ucr->pusb_dev, EP_BULK_IN);
@@ -521,10 +611,23 @@ static int sd_rw_multi(struct rtsx_usb_sdmmc
*host, struct mmc_request *mrq)
 		dev_dbg(sdmmc_dev(host), "rtsx_usb_transfer_data error %d\n"
 				, err);
 		sd_clear_error(host);
+		host->seq_mode = false;
+		host->seq_counter = 0;
 		return err;
 	}

-	return rtsx_usb_get_rsp(ucr, 1, 2000);
+	err = rtsx_usb_get_rsp(ucr, 1, 2000);
+	if (!err) {
+		host->seq_mode = true;
+		host->seq_read = read;
+		host->seq_counter = 0;
+	} else {
+		dev_dbg(sdmmc_dev(host), "sd multi rsp err %d\n", err);
+		host->seq_mode = false;
+		host->seq_counter = 0;
+	}
+
+	return err;
 }

 static inline void sd_enable_initial_mode(struct rtsx_usb_sdmmc *host)
@@ -807,6 +910,119 @@ static int sdmmc_get_cd(struct mmc_host *mmc)
 	return 0;
 }

+static void rtsx_usb_sdmmc_poll_card(struct work_struct *work)
+{
+	struct rtsx_usb_sdmmc *host = container_of(work,
+			struct rtsx_usb_sdmmc, card_poll.work);
+	struct rtsx_ucr *ucr = host->ucr;
+	struct device *dev = sdmmc_dev(host);
+	bool present = READ_ONCE(host->card_exist);
+	bool changed = false;
+	bool prev;
+	int err;
+	u16 status = 0;
+	u8 pend;
+
+	if (host->host_removal)
+		return;
+
+	err = pm_runtime_get_sync(dev);
+	if (err < 0) {
+		pm_runtime_put_noidle(dev);
+		goto requeue;
+	}
+
+	mutex_lock(&ucr->dev_mutex);
+
+	prev = READ_ONCE(host->card_exist);
+
+	err = rtsx_usb_get_card_status(ucr, &status);
+	if (!err) {
+		host->ocp_stat = (status >> 4) & 0x03;
+		present = !!(status & SD_CD);
+		if (host->ocp_stat & (MS_OCP_NOW | MS_OCP_EVER)) {
+			sdmmc_leave_idle(host);
+			rtsx_usb_write_register(ucr, CARD_OE,
+					SD_OUTPUT_EN, 0);
+		}
+	}
+
+	if (!err) {
+		err = rtsx_usb_read_register(ucr, CARD_INT_PEND, &pend);
+		if (!err && (pend & SD_INT)) {
+			rtsx_usb_write_register(ucr, CARD_INT_PEND,
+				SD_INT, SD_INT);
+			if (prev && present)
+				present = false;
+		}
+	}
+
+	if (!err && !present &&
+	    (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;
+	}
+
+	if (!err) {
+		bool busy = READ_ONCE(host->mrq) != NULL;
+
+		changed = present != prev;
+		WRITE_ONCE(host->card_exist, present);
+
+		if (busy) {
+			host->idle_counter = 0;
+			if (host->idle)
+				sdmmc_leave_idle(host);
+		} else if (!present) {
+			if (host->idle_counter < host->idle_wait_max)
+				host->idle_counter++;
+			if (!host->idle &&
+			    host->idle_counter >= host->idle_wait_max)
+				sdmmc_enter_idle(host);
+		} else {
+			host->idle_counter = 0;
+			if (host->idle)
+				sdmmc_leave_idle(host);
+		}
+
+		if (!present) {
+			if (host->seq_mode)
+				sdmmc_stop_seq_mode(host);
+			host->seq_mode = false;
+			host->seq_read = false;
+			host->seq_counter = 0;
+		} else if (host->seq_mode) {
+			if (busy) {
+				host->seq_counter = 0;
+			} else if (++host->seq_counter >= host->seq_wait_max) {
+				sdmmc_stop_seq_mode(host);
+			}
+		} else {
+			host->seq_counter = 0;
+		}
+	}
+
+	mutex_unlock(&ucr->dev_mutex);
+
+	pm_runtime_put_sync_suspend(dev);
+
+	if (changed)
+		mmc_detect_change(host->mmc, 0);
+
+requeue:
+	if (!host->host_removal) {
+		bool request_active = READ_ONCE(host->mrq) != NULL;
+		bool card_present = READ_ONCE(host->card_exist);
+		unsigned long delay = card_present || request_active ?
+			RTSX_USB_SD_POLL_INTERVAL :
+			RTSX_USB_SD_IDLE_POLL_INTERVAL;
+
+		host->poll_interval = delay;
+		queue_delayed_work(system_wq, &host->card_poll, delay);
+	}
+}
+
 static void sdmmc_request(struct mmc_host *mmc, struct mmc_request *mrq)
 {
 	struct rtsx_usb_sdmmc *host = mmc_priv(mmc);
@@ -832,10 +1048,14 @@ static void sdmmc_request(struct mmc_host *mmc,
struct mmc_request *mrq)
 		goto finish_detect_card;
 	}
 	mutex_lock(&ucr->dev_mutex);
+	if (host->seq_mode)
+		sdmmc_stop_seq_mode(host);

 	mutex_lock(&host->host_mutex);
 	host->mrq = mrq;
 	mutex_unlock(&host->host_mutex);
+	sdmmc_leave_idle(host);
+	host->idle_counter = 0;

 	if (mrq->data)
 		data_size = data->blocks * data->blksz;
@@ -1051,6 +1271,8 @@ static void sd_set_power_mode(struct rtsx_usb_sdmmc *host,
 		if (err)
 			dev_dbg(sdmmc_dev(host), "power-off (err = %d)\n", err);
 		pm_runtime_put_noidle(sdmmc_dev(host));
+		sdmmc_enter_idle(host);
+		host->idle_counter = 0;
 		break;

 	case MMC_POWER_UP:
@@ -1060,11 +1282,15 @@ static void sd_set_power_mode(struct
rtsx_usb_sdmmc *host,
 			dev_dbg(sdmmc_dev(host), "power-on (err = %d)\n", err);
 		/* issue the clock signals to card at least 74 clocks */
 		rtsx_usb_write_register(ucr, SD_BUS_STAT, SD_CLK_TOGGLE_EN,
SD_CLK_TOGGLE_EN);
+		sdmmc_leave_idle(host);
+		host->idle_counter = 0;
 		break;

 	case MMC_POWER_ON:
 		/* stop to send the clock signals */
 		rtsx_usb_write_register(ucr, SD_BUS_STAT, SD_CLK_TOGGLE_EN, 0x00);
+		sdmmc_leave_idle(host);
+		host->idle_counter = 0;
 		break;

 	case MMC_POWER_UNDEFINED:
@@ -1367,6 +1593,13 @@ static void rtsx_usb_init_host(struct
rtsx_usb_sdmmc *host)

 	host->power_mode = MMC_POWER_OFF;
 	host->ocp_stat = 0;
+	host->idle_counter = 0;
+	host->idle_wait_max = RTSX_USB_SD_IDLE_WAIT_MAX;
+	host->idle = false;
+	host->seq_mode = false;
+	host->seq_read = false;
+	host->seq_counter = 0;
+	host->seq_wait_max = RTSX_USB_SD_SEQ_WAIT_MAX;
 }

 static int rtsx_usb_sdmmc_drv_probe(struct platform_device *pdev)
@@ -1397,6 +1630,8 @@ static int rtsx_usb_sdmmc_drv_probe(struct
platform_device *pdev)

 	mutex_init(&host->host_mutex);
 	rtsx_usb_init_host(host);
+	host->poll_interval = RTSX_USB_SD_POLL_INTERVAL;
+	INIT_DELAYED_WORK(&host->card_poll, rtsx_usb_sdmmc_poll_card);
 	pm_runtime_enable(&pdev->dev);

 #ifdef RTSX_USB_USE_LEDS_CLASS
@@ -1423,6 +1658,9 @@ static int rtsx_usb_sdmmc_drv_probe(struct
platform_device *pdev)
 		return ret;
 	}

+	queue_delayed_work(system_wq, &host->card_poll,
+			 host->poll_interval);
+
 	return 0;
 }

@@ -1436,6 +1674,7 @@ static void rtsx_usb_sdmmc_drv_remove(struct
platform_device *pdev)

 	mmc = host->mmc;
 	host->host_removal = true;
+	cancel_delayed_work_sync(&host->card_poll);

 	mutex_lock(&host->host_mutex);
 	if (host->mrq) {
-- 
2.51.0

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ