mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [RFC PATCH] memstick: Add Broadcom BCM57765/BCM57785 host driver
@ 2026-09-12 16:38 Ho Jie Feng
  2026-09-14 10:31 ` Ulf Hansson
  0 siblings, 1 reply; 3+ messages in thread
From: Ho Jie Feng @ 2026-09-12 16:38 UTC (permalink / raw)
  To: linux-mmc
  Cc: Ho Jie Feng, Maxim Levitsky, Alex Dubov, Ulf Hansson, linux-kernel

Add support for the MemoryStick function of Broadcom BCM57765 and BCM57785
PCI card readers (14e4:16be).

The driver is written by inspecting MMIO traces from the windows driver
and experimentally probing the device registers.

It was found that the controller exposes an SDHCI-like interface with
TPC command handling. DMA mode was then inferred using the positions
of the bits in sdhci.h.

Signed-off-by: Ho Jie Feng <hjf3108@gmail.com>
---
 MAINTAINERS                         |   6 +
 drivers/memstick/host/Kconfig       |  10 +
 drivers/memstick/host/Makefile      |   1 +
 drivers/memstick/host/bcm577x5_ms.c | 754 ++++++++++++++++++++++++++++
 drivers/memstick/host/bcm577x5_ms.h | 138 +++++
 5 files changed, 909 insertions(+)
 create mode 100644 drivers/memstick/host/bcm577x5_ms.c
 create mode 100644 drivers/memstick/host/bcm577x5_ms.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 978fe6999d3b3..20faa8c2b1bcc 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5234,6 +5234,12 @@ S:	Maintained
 F:	arch/arm/boot/dts/broadcom/bcm47189*
 F:	arch/arm/boot/dts/broadcom/bcm53573*
 
+BROADCOM BCM57765/BCM57785 MEMORYSTICK DRIVER
+M:	Ho Jie Feng <hjf3108@gmail.com>
+L:	linux-mmc@vger.kernel.org
+S:	Maintained
+F:	drivers/memstick/host/bcm577x5_ms.*
+
 BROADCOM BCM63XX/BCM33XX UDC DRIVER
 M:	Kevin Cernekee <cernekee@gmail.com>
 L:	linux-usb@vger.kernel.org
diff --git a/drivers/memstick/host/Kconfig b/drivers/memstick/host/Kconfig
index fcd2c2cc3cb47..f9b18f0d85513 100644
--- a/drivers/memstick/host/Kconfig
+++ b/drivers/memstick/host/Kconfig
@@ -53,3 +53,13 @@ config MEMSTICK_REALTEK_USB
 
 	  To compile this driver as a module, choose M here: the module will
 	  be called rts5139_ms.
+
+config MEMSTICK_BCM577X5
+	tristate "Broadcom BCM57765/BCM57785 MemoryStick interface support"
+	depends on PCI
+	help
+	  Say Y here if you want to access MemoryStick cards with the
+	  Broadcom BCM57765/BCM57785 PCI card reader (14e4:16be).
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called bcm577x5_ms.
diff --git a/drivers/memstick/host/Makefile b/drivers/memstick/host/Makefile
index 0c90df33165de..8eafb70b26706 100644
--- a/drivers/memstick/host/Makefile
+++ b/drivers/memstick/host/Makefile
@@ -7,3 +7,4 @@ obj-$(CONFIG_MEMSTICK_TIFM_MS)		+= tifm_ms.o
 obj-$(CONFIG_MEMSTICK_JMICRON_38X)	+= jmb38x_ms.o
 obj-$(CONFIG_MEMSTICK_R592)		+= r592.o
 obj-$(CONFIG_MEMSTICK_REALTEK_USB)	+= rtsx_usb_ms.o
+obj-$(CONFIG_MEMSTICK_BCM577X5)		+= bcm577x5_ms.o
diff --git a/drivers/memstick/host/bcm577x5_ms.c b/drivers/memstick/host/bcm577x5_ms.c
new file mode 100644
index 0000000000000..81824fb4e9a82
--- /dev/null
+++ b/drivers/memstick/host/bcm577x5_ms.c
@@ -0,0 +1,754 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <linux/align.h>
+#include <linux/bits.h>
+#include <linux/completion.h>
+#include <linux/container_of.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/jiffies.h>
+#include <linux/memstick.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/pci.h>
+#include <linux/pci_ids.h>
+#include <linux/pm.h>
+#include <linux/scatterlist.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/workqueue.h>
+
+#include "bcm577x5_ms.h"
+
+#define DRV_NAME "bcm577x5_ms"
+
+static int enable_dma = 2;
+module_param(enable_dma, int, 0444);
+MODULE_PARM_DESC(enable_dma,
+		 "Enable usage of the DMA (0 = no, 1 = yes, 2 = auto,default)");
+
+static const struct pci_device_id bcm577x5_pci_id_tbl[] = {
+	{
+		PCI_VDEVICE(BROADCOM, 0x16be),
+	},
+	{},
+};
+
+static inline u32 bcm577x5_reg_readl(struct bcm577x5_device *dev, int address)
+{
+	u32 value = readl(dev->mmio + address);
+	return value;
+}
+
+static inline u16 bcm577x5_reg_readw(struct bcm577x5_device *dev, int address)
+{
+	u32 value = readw(dev->mmio + address);
+	return value;
+}
+
+static inline u8 bcm577x5_reg_readb(struct bcm577x5_device *dev, int address)
+{
+	u32 value = readb(dev->mmio + address);
+	return value;
+}
+
+static inline void bcm577x5_reg_writel(struct bcm577x5_device *dev, int address,
+				       u32 value)
+{
+	writel(value, dev->mmio + address);
+}
+
+static inline void bcm577x5_reg_writew(struct bcm577x5_device *dev, int address,
+				       u16 value)
+{
+	writew(value, dev->mmio + address);
+}
+
+static inline void bcm577x5_reg_writeb(struct bcm577x5_device *dev, int address,
+				       u8 value)
+{
+	writeb(value, dev->mmio + address);
+}
+
+static int bcm577x5_reg_waitb(struct bcm577x5_device *dev, int address, u8 mask,
+			      u8 value, int timeout)
+{
+	unsigned long wait_time = jiffies + msecs_to_jiffies(timeout);
+	u8 reg;
+
+	do {
+		reg = bcm577x5_reg_readb(dev, address);
+		if ((reg & mask) == value)
+			return 0;
+
+		cpu_relax();
+
+	} while (time_before(jiffies, wait_time));
+
+	return -ETIME;
+}
+
+static int bcm577x5_reg_waitl_different(struct bcm577x5_device *dev,
+					int address, u32 value, int timeout)
+{
+	unsigned long wait_time = jiffies + msecs_to_jiffies(timeout);
+	u32 reg;
+
+	do {
+		reg = bcm577x5_reg_readl(dev, address);
+		if (reg != value)
+			return 0;
+
+		cpu_relax();
+
+	} while (time_before(jiffies, wait_time));
+
+	return -ETIMEDOUT;
+}
+
+static int bcm577x5_ms_reset(struct bcm577x5_device *dev, u8 reset)
+{
+	bcm577x5_reg_writeb(dev, BCM577x5_MS_SOFTWARE_RESET, reset);
+
+	return bcm577x5_reg_waitb(dev, BCM577x5_MS_SOFTWARE_RESET, reset, 0,
+				  1000);
+}
+
+static int bcm577x5_ms_power(struct bcm577x5_device *dev, int power)
+{
+	switch (power) {
+	case MEMSTICK_POWER_ON:
+		bcm577x5_reg_writeb(dev, BCM577x5_MS_POWER_CONTROL,
+				    BCM577x5_MS_POWER_330);
+		bcm577x5_reg_writeb(dev, BCM577x5_MS_POWER_CONTROL,
+				    BCM577x5_MS_POWER_330 |
+					    BCM577x5_MS_POWER_ON);
+		return bcm577x5_reg_waitb(dev, BCM577x5_MS_POWER_CONTROL,
+					  BCM577x5_MS_POWER_ON,
+					  BCM577x5_MS_POWER_ON, 1000);
+	case MEMSTICK_POWER_OFF:
+		bcm577x5_reg_writeb(dev, BCM577x5_MS_POWER_CONTROL, 0);
+		return 0;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int bcm577x5_ms_clock(struct bcm577x5_device *dev, int clock_hz)
+{
+	int err;
+	u16 divisor, divisor_lo, divisor_hi, clock_reg;
+
+	divisor = dev->base_clock / clock_hz / 2;
+	divisor_lo = divisor & BCM577x5_MS_DIV_MASK;
+	divisor_hi = (divisor & BCM577x5_MS_DIV_HI_MASK) >> 8;
+	clock_reg = (divisor_lo << BCM577x5_MS_DIVIDER_SHIFT) |
+		    (divisor_hi << BCM577x5_MS_DIVIDER_HI_SHIFT);
+
+	bcm577x5_reg_writew(dev, BCM577x5_MS_CLOCK_CONTROL, clock_reg);
+	bcm577x5_reg_writew(dev, BCM577x5_MS_CLOCK_CONTROL,
+			    clock_reg | BCM577x5_MS_CLOCK_INT_EN);
+	err = bcm577x5_reg_waitb(dev, BCM577x5_MS_CLOCK_CONTROL,
+				 BCM577x5_MS_CLOCK_INT_STABLE,
+				 BCM577x5_MS_CLOCK_INT_STABLE, 1000);
+	if (err)
+		return err;
+	bcm577x5_reg_writew(dev, BCM577x5_MS_CLOCK_CONTROL,
+			    clock_reg | BCM577x5_MS_CLOCK_INT_EN |
+				    BCM577x5_MS_CLOCK_CARD_EN);
+	return 0;
+}
+
+static int bcm577x5_ms_init(struct bcm577x5_device *dev)
+{
+	int error;
+
+	/* Reset the controller */
+	error = bcm577x5_ms_reset(dev, BCM577x5_MS_RESET_ALL);
+	if (error)
+		return error;
+
+	/* Power cycle the controller */
+	error = bcm577x5_ms_power(dev, MEMSTICK_POWER_OFF);
+	if (error)
+		return error;
+	error = bcm577x5_ms_power(dev, MEMSTICK_POWER_ON);
+	if (error)
+		return error;
+
+	/* Set clock divider to 2 */
+	error = bcm577x5_ms_clock(dev, 24000000);
+	if (error)
+		return error;
+
+	/* Set timeout */
+	bcm577x5_reg_writeb(dev, BCM577x5_MS_TIMEOUT_CONTROL, 0xE);
+
+	error = bcm577x5_ms_reset(dev, BCM577x5_MS_RESET_CMD);
+	if (error)
+		return error;
+	error = bcm577x5_ms_reset(dev, BCM577x5_MS_RESET_DATA);
+	if (error)
+		return error;
+
+	/* Re-enable interrupts */
+	bcm577x5_reg_writel(dev, BCM577x5_MS_INT_ENABLE,
+			    BCM577x5_MS_INT_DEFAULT);
+	bcm577x5_reg_writel(dev, BCM577x5_MS_SIGNAL_ENABLE,
+			    BCM577x5_MS_INT_DEFAULT);
+
+	return 0;
+}
+
+static irqreturn_t bcm577x5_irq(int irq, void *data)
+{
+	struct bcm577x5_device *dev = (struct bcm577x5_device *)data;
+	irqreturn_t ret = IRQ_NONE;
+	u32 val, status;
+	int i;
+
+	val = bcm577x5_reg_readl(dev, BCM577x5_MS_STATUS);
+	if (val & BCM577x5_MS_STATUS_INT_ASSERTED) {
+		status = bcm577x5_reg_readl(dev, BCM577x5_MS_INT_STATUS);
+
+		if (status & 0x100) {
+			bcm577x5_reg_writel(dev, BCM577x5_MS_INT_ENABLE,
+					    bcm577x5_reg_readl(dev,
+							       BCM577x5_MS_INT_ENABLE) &
+					~0x100);
+		}
+
+		/* Each individual bit needs to be acknowledged separately */
+		for (i = 0; i < 32; i++) {
+			if (status & BIT(i)) {
+				bcm577x5_reg_writel(dev, BCM577x5_MS_INT_STATUS,
+						    BIT(i));
+			}
+		}
+
+		/* Signal the command or TPC is done */
+		if (status & BCM577x5_MS_INT_RESPONSE)
+			complete(&dev->cmd_done);
+
+		/* Signal the FIFO is ready */
+		if (status & BCM577x5_MS_INT_DATA_AVAIL)
+			complete(&dev->fifo_done);
+
+		if (status & BCM577x5_MS_INT_DATA_TIMEOUT)
+			dev_warn(&dev->pci_dev->dev, "Timeout!");
+
+		if (status &
+		    (BCM577x5_MS_INT_DATA_END | BCM577x5_MS_INT_DMA_END))
+			complete(&dev->dma_done);
+
+		if (status & (BCM577x5_MS_INT_CARD_INSERT |
+			      BCM577x5_MS_INT_CARD_REMOVE)) {
+			if (status & BCM577x5_MS_INT_CARD_INSERT)
+				dev_info(&dev->pci_dev->dev, "Card inserted\n");
+			else
+				dev_info(&dev->pci_dev->dev, "Card removed\n");
+			memstick_detect_change(dev->host);
+		}
+
+		ret = IRQ_HANDLED;
+	}
+	return ret;
+}
+
+static int bcm577x5_ms_mode(struct bcm577x5_device *dev, int mode)
+{
+	u8 host_control = bcm577x5_reg_readb(dev, BCM577x5_MS_HOST_CONTROL);
+
+	switch (mode) {
+	case MEMSTICK_SERIAL:
+		bcm577x5_reg_writeb(dev, BCM577x5_MS_HOST_CONTROL,
+				    host_control & ~BCM577x5_MS_CTRL_4BITBUS);
+		return 0;
+	case MEMSTICK_PAR4:
+		bcm577x5_reg_writeb(dev, BCM577x5_MS_HOST_CONTROL,
+				    host_control | BCM577x5_MS_CTRL_4BITBUS);
+		return 0;
+	default:
+		return -EINVAL;
+	}
+}
+
+/* External interface: set settings */
+static int bcm577x5_ms_set_param(struct memstick_host *host,
+				 enum memstick_param param, int value)
+{
+	struct bcm577x5_device *dev = memstick_priv(host);
+
+	switch (param) {
+	case MEMSTICK_POWER:
+		return bcm577x5_ms_power(dev, value);
+	case MEMSTICK_INTERFACE:
+		return bcm577x5_ms_mode(dev, value);
+	default:
+		return -EINVAL;
+	}
+}
+
+static int bcm577x5_ms_prepare_dma(struct bcm577x5_device *dev)
+{
+	int len = dev->req->sg.length;
+	void *dma_copy_buffer;
+	dma_addr_t dma_address;
+
+	if (!dev->enable_dma)
+		return -EOPNOTSUPP;
+
+	if (len > 4096)
+		return -EOPNOTSUPP;
+
+	reinit_completion(&dev->dma_done);
+
+	if (dev->req->data_dir == WRITE) {
+		dma_copy_buffer = dev->dma_buffer_aligned + 4096 - len;
+		sg_copy_to_buffer(&dev->req->sg,
+				  sg_nents_for_len(&dev->req->sg, len),
+				  dma_copy_buffer, len);
+		dma_sync_single_for_device(&dev->pci_dev->dev, dev->dma_handle,
+					   4096, DMA_TO_DEVICE);
+	}
+	/* The last byte should cause an overflow interrupt on the 4096 byte boundary */
+	dma_address = dev->dma_handle + 4096 - len;
+
+	bcm577x5_reg_writel(dev, BCM577x5_MS_DMA_ADDRESS, dma_address);
+	return 0;
+}
+
+static int bcm577x5_ms_finish_dma(struct bcm577x5_device *dev)
+{
+	int len = dev->req->sg.length;
+	void *dma_copy_buffer;
+
+	if (!dev->enable_dma)
+		return -EINVAL;
+
+	if (!wait_for_completion_timeout(&dev->dma_done,
+					 msecs_to_jiffies(1000)))
+		return -ETIMEDOUT;
+
+	if (dev->req->data_dir == READ) {
+		dma_copy_buffer = dev->dma_buffer_aligned + 4096 - len;
+		dma_sync_single_for_cpu(&dev->pci_dev->dev, dev->dma_handle,
+					4096, DMA_FROM_DEVICE);
+		sg_copy_from_buffer(&dev->req->sg,
+				    sg_nents_for_len(&dev->req->sg, len),
+				    dma_copy_buffer, len);
+	}
+
+	return 0;
+}
+
+static int bcm577x5_ms_prepare_fifo(struct bcm577x5_device *dev)
+{
+	struct sg_mapping_iter miter;
+	int i;
+
+	reinit_completion(&dev->fifo_done);
+
+	if (dev->req->data_dir == WRITE) {
+		sg_miter_start(&miter, &dev->req->sg, 1,
+			       SG_MITER_ATOMIC | SG_MITER_FROM_SG);
+		while (sg_miter_next(&miter)) {
+			u32 *buf = miter.addr;
+
+			for (i = 0; i < miter.length / 4; i++) {
+				bcm577x5_reg_writel(dev, BCM577x5_MS_BUFFER,
+						    buf[i]);
+			}
+		}
+		sg_miter_stop(&miter);
+	}
+
+	return 0;
+}
+
+static int bcm577x5_ms_finish_fifo(struct bcm577x5_device *dev)
+{
+	struct sg_mapping_iter miter;
+	int i;
+
+	if (!wait_for_completion_timeout(&dev->fifo_done, msecs_to_jiffies(10)))
+		return -ETIMEDOUT;
+
+	if (dev->req->data_dir == READ) {
+		sg_miter_start(&miter, &dev->req->sg, 1,
+			       SG_MITER_ATOMIC | SG_MITER_TO_SG);
+		while (sg_miter_next(&miter)) {
+			u32 *buf = miter.addr;
+
+			for (i = 0; i < miter.length / 4; i++) {
+				buf[i] = bcm577x5_reg_readl(dev,
+							    BCM577x5_MS_BUFFER);
+			}
+		}
+		sg_miter_stop(&miter);
+	}
+
+	return 0;
+}
+
+static int bcm577x5_ms_execute_cmd_short(struct bcm577x5_device *dev, u32 cmd)
+{
+	int len = dev->req->data_len;
+	u32 *data = (u32 *)dev->req->data;
+	u32 response;
+
+	/* Write the data into the controller */
+	if (dev->req->data_dir == WRITE) {
+		bcm577x5_reg_writel(dev, BCM577x5_MS_ARGUMENT, data[0]);
+		if (len > 4)
+			bcm577x5_reg_writel(dev, BCM577x5_MS_ARGUMENT2,
+					    data[1]);
+	}
+
+	reinit_completion(&dev->cmd_done);
+
+	response = bcm577x5_reg_readl(dev, BCM577x5_MS_RESPONSE);
+	bcm577x5_reg_writel(dev, BCM577x5_MS_TPC_REG, cmd);
+
+	if (!wait_for_completion_timeout(&dev->cmd_done,
+					 msecs_to_jiffies(100))) {
+		return -ETIME;
+	}
+
+	if (dev->req->tpc == MS_TPC_SET_CMD) {
+		/*
+		 * The controller will execute a GET_INT command, wait for it.
+		 * Sometimes, the second interrupt with coalesce with the first,
+		 * so detecting when it is completed is a bit tricky.
+		 *
+		 * The GET_INT command changes the response register,
+		 * so we can detect when it is completed by checking whether
+		 * the response register has changed.
+		 *
+		 * This may not be completely reliable if the data is the same,
+		 * just assume the command completed if it takes too long.
+		 */
+		bcm577x5_reg_waitl_different(dev, BCM577x5_MS_RESPONSE,
+					     response, 10);
+
+		/* The int reg is placed in the response register */
+		dev->req->int_reg =
+			bcm577x5_reg_readl(dev, BCM577x5_MS_RESPONSE) & 0xFF;
+
+		/*
+		 * Reset is needed because the controller will expect certain commands
+		 * to follow the SET_CMD command, which the block driver may not
+		 * necessarily issue.
+		 */
+		if (bcm577x5_ms_reset(dev, BCM577x5_MS_RESET_DATA))
+			return -ETIME;
+	}
+
+	/* Read the data back */
+	if (dev->req->data_dir == READ) {
+		data[0] = bcm577x5_reg_readl(dev, BCM577x5_MS_RESPONSE);
+		if (len > 4)
+			data[1] =
+				bcm577x5_reg_readl(dev, BCM577x5_MS_RESPONSE2);
+	}
+
+	return 0;
+}
+
+static int bcm577x5_ms_execute_cmd_long(struct bcm577x5_device *dev, u32 cmd)
+{
+	int err;
+	bool use_dma = dev->enable_dma;
+
+	/* Debug: readonly */
+	if (dev->req->tpc != MS_TPC_READ_LONG_DATA)
+		return 0;
+
+	err = bcm577x5_ms_prepare_dma(dev);
+	if (err) {
+		use_dma = false;
+		err = bcm577x5_ms_prepare_fifo(dev);
+	}
+
+	if (use_dma)
+		cmd |= BCM577x5_MS_TRNS_DMA;
+
+	/* Execute the TPC */
+	bcm577x5_reg_writel(dev, BCM577x5_MS_TPC_REG, cmd);
+
+	if (use_dma)
+		err = bcm577x5_ms_finish_dma(dev);
+	else
+		err = bcm577x5_ms_finish_fifo(dev);
+
+	/* A reset is needed or else the next command will not execute */
+	if (bcm577x5_ms_reset(dev, BCM577x5_MS_RESET_DATA))
+		return -ETIME;
+	return err;
+}
+
+static int bcm577x5_ms_execute_cmd(struct bcm577x5_device *dev)
+{
+	int len =
+		dev->req->long_data ? dev->req->sg.length : dev->req->data_len;
+	int err;
+	u32 cmd;
+
+	/* Immediately return if there is no card inserted */
+	if (!(bcm577x5_reg_readl(dev, BCM577x5_MS_STATUS) &
+	      BCM577x5_MS_STATUS_MS_DETECT))
+		return -ENODEV;
+
+	/* Write the length of the transfer */
+	bcm577x5_reg_writel(dev, BCM577x5_MS_BLOCK_SIZE, len);
+
+	/* Create the execution command */
+	cmd = BCM577x5_MS_MAKE_TPC(dev->req->tpc);
+	if (dev->req->data_dir == READ)
+		cmd |= BCM577x5_MS_TRNS_READ;
+
+	/* Two types of transfers have very different flow,
+	 * so split them into functions
+	 */
+	if (dev->req->long_data)
+		err = bcm577x5_ms_execute_cmd_long(dev, cmd);
+	else
+		err = bcm577x5_ms_execute_cmd_short(dev, cmd);
+
+	return err;
+}
+
+static void bcm577x5_ms_req_process(struct work_struct *data)
+{
+	struct bcm577x5_device *dev =
+		container_of(data, struct bcm577x5_device, req_work);
+	int err, cmderr = 0;
+
+	/* Already processing requests */
+	if (dev->req)
+		return;
+
+	mutex_lock(&dev->req_lock);
+	do {
+		err = memstick_next_req(dev->host, &dev->req);
+		if (!err) {
+			cmderr = bcm577x5_ms_execute_cmd(dev);
+			dev->req->error = cmderr;
+		}
+	} while (!err && !cmderr);
+
+	if (cmderr) {
+		/* Controller will not respond to any more commands anyway */
+		while (!err && dev->req) {
+			dev->req->error = -ETIME;
+			err = memstick_next_req(dev->host, &dev->req);
+		}
+
+		/* Reset the controller so it accepts new commands */
+		bcm577x5_ms_init(dev);
+	}
+	mutex_unlock(&dev->req_lock);
+}
+
+static void bcm577x5_ms_submit_req(struct memstick_host *host)
+{
+	struct bcm577x5_device *dev = memstick_priv(host);
+
+	schedule_work(&dev->req_work);
+}
+
+static int bcm577x5_probe(struct pci_dev *pdev, const struct pci_device_id *id)
+{
+	int error = -ENOMEM;
+	struct memstick_host *host;
+	struct bcm577x5_device *dev;
+	u32 val;
+
+	host = memstick_alloc_host(sizeof(struct bcm577x5_device), &pdev->dev);
+	if (!host)
+		goto error1;
+
+	dev = memstick_priv(host);
+	dev->host = host;
+	dev->pci_dev = pdev;
+	pci_set_drvdata(pdev, dev);
+
+	error = pci_enable_device(pdev);
+	if (error)
+		goto error2;
+
+	pci_set_master(pdev);
+
+	error = pci_request_regions(pdev, DRV_NAME);
+	if (error)
+		goto error3;
+
+	dev->mmio = pci_ioremap_bar(pdev, 0);
+	if (!dev->mmio) {
+		error = -EIO;
+		goto error4;
+	}
+
+	INIT_WORK(&dev->req_work, bcm577x5_ms_req_process);
+
+	host->caps = MEMSTICK_CAP_PAR4;
+	host->request = bcm577x5_ms_submit_req;
+	host->set_param = bcm577x5_ms_set_param;
+
+	val = bcm577x5_reg_readl(dev, BCM577x5_MS_CAPABILITY_SLOT2);
+
+	dev->base_clock = ((val & BCM577x5_MS_CAPABILITY_BASE_CLOCK_MASK) >>
+			   BCM577x5_MS_CAPABILITY_BASE_CLOCK_SHIFT) *
+			  1000000;
+
+	if (enable_dma < 2) {
+		dev_info(&pdev->dev, "DMA set by module parameter\n");
+		dev->enable_dma = enable_dma;
+	} else if (val & BCM577x5_MS_CAPABILITY_SDMA) {
+		dev->enable_dma = 1;
+		if (dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32))) {
+			dev_warn(&pdev->dev, "No suitable DMA available\n");
+			dev->enable_dma = 0;
+		}
+	} else {
+		dev->enable_dma = 0;
+	}
+
+	if (dev->enable_dma) {
+		dev->dma_buffer = kzalloc(8192, GFP_DMA32 | GFP_KERNEL);
+		if (dev->dma_buffer) {
+			/* This needs to be 4096 aligned since the hardware does not seem to
+			 * issue a dma complete interrupt, so we have to rely on
+			 * address line overflows to detect completion.
+			 */
+			dev->dma_buffer_aligned = PTR_ALIGN(dev->dma_buffer, 4096);
+
+			dev->dma_handle =
+				dma_map_single(&pdev->dev, dev->dma_buffer_aligned,
+					       4096, DMA_BIDIRECTIONAL);
+			if (dma_mapping_error(&pdev->dev, dev->dma_handle)) {
+				dev_warn(&pdev->dev,
+					 "bcm577x5_ms: DMA Mapping Failed, disabling\n");
+				dev->enable_dma = 0;
+			}
+		} else {
+			dev_warn(&pdev->dev,
+				 "bcm577x5_ms: DMA memory alloc Failed, disabling\n");
+			dev->enable_dma = 0;
+		}
+	}
+
+	/* Disable interrupts */
+	bcm577x5_reg_writel(dev, BCM577x5_MS_INT_ENABLE, 0);
+	bcm577x5_reg_writel(dev, BCM577x5_MS_SIGNAL_ENABLE, 0);
+
+	pci_read_config_dword(pdev, 0x8, &val);
+	dev->irq = pdev->irq;
+	dev_info(&pdev->dev, "Rev %c%c dma=%d clock=%dHz irq=%d\n",
+		 ((val & 0xFF) >> 3) + 'A', ((val & 0xFF) & 0b111) + '0',
+		 dev->enable_dma, dev->base_clock, dev->irq);
+
+	mutex_init(&dev->req_lock);
+	init_completion(&dev->cmd_done);
+	init_completion(&dev->fifo_done);
+	init_completion(&dev->dma_done);
+	error = request_irq(dev->irq, &bcm577x5_irq, IRQF_SHARED, DRV_NAME, dev);
+	if (error)
+		goto error5;
+
+	error = bcm577x5_ms_init(dev);
+	if (error)
+		goto error6;
+
+	error = memstick_add_host(host);
+	if (error)
+		goto error6;
+
+	return 0;
+error6:
+	free_irq(dev->irq, dev);
+error5:
+	iounmap(dev->mmio);
+	if (dev->enable_dma) {
+		dma_unmap_single(&dev->pci_dev->dev, dev->dma_handle, 4096,
+				 DMA_BIDIRECTIONAL);
+	}
+	kfree(dev->dma_buffer);
+error4:
+	pci_release_regions(pdev);
+error3:
+	pci_disable_device(pdev);
+error2:
+	memstick_free_host(host);
+error1:
+	return error;
+}
+
+static void bcm577x5_remove(struct pci_dev *pdev)
+{
+	int error = 0;
+	struct bcm577x5_device *dev = pci_get_drvdata(pdev);
+
+	/* Cancel the work before interrupts are disabled */
+	cancel_work_sync(&dev->req_work);
+
+	/* Clear interrupts */
+	bcm577x5_reg_writel(dev, BCM577x5_MS_INT_ENABLE, 0);
+	bcm577x5_reg_writel(dev, BCM577x5_MS_SIGNAL_ENABLE, 0);
+
+	/* Invalidate all outstanding requests */
+	while (!error && dev->req) {
+		dev->req->error = -ENODEV;
+		error = memstick_next_req(dev->host, &dev->req);
+	}
+
+	memstick_remove_host(dev->host);
+
+	if (dev->enable_dma) {
+		dma_unmap_single(&dev->pci_dev->dev, dev->dma_handle, 4096,
+				 DMA_BIDIRECTIONAL);
+	}
+	kfree(dev->dma_buffer);
+
+	free_irq(dev->irq, dev);
+	iounmap(dev->mmio);
+	pci_release_regions(pdev);
+	pci_disable_device(pdev);
+	memstick_free_host(dev->host);
+}
+
+static int __maybe_unused bcm577x5_ms_suspend(struct device *pdev)
+{
+	struct bcm577x5_device *dev = dev_get_drvdata(pdev);
+
+	memstick_suspend_host(dev->host);
+	return 0;
+}
+
+static int __maybe_unused bcm577x5_ms_resume(struct device *pdev)
+{
+	struct bcm577x5_device *dev = dev_get_drvdata(pdev);
+
+	memstick_resume_host(dev->host);
+	return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(bcm577x5_ms_pm_ops, bcm577x5_ms_suspend,
+			 bcm577x5_ms_resume);
+
+MODULE_DEVICE_TABLE(pci, bcm577x5_pci_id_tbl);
+
+static struct pci_driver bcm577x5_pci_driver = {
+	.name = DRV_NAME,
+	.id_table = bcm577x5_pci_id_tbl,
+	.probe = bcm577x5_probe,
+	.remove = bcm577x5_remove,
+	.driver.pm = &bcm577x5_ms_pm_ops,
+};
+
+module_pci_driver(bcm577x5_pci_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("BCM577x5 MS/MSPro card reader driver");
diff --git a/drivers/memstick/host/bcm577x5_ms.h b/drivers/memstick/host/bcm577x5_ms.h
new file mode 100644
index 0000000000000..02e69582d05fb
--- /dev/null
+++ b/drivers/memstick/host/bcm577x5_ms.h
@@ -0,0 +1,138 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef BCM577X5_MS_H
+#define BCM577X5_MS_H
+
+#include <linux/compiler.h>
+#include <linux/completion.h>
+#include <linux/mutex.h>
+#include <linux/types.h>
+#include <linux/workqueue.h>
+
+struct memstick_host;
+struct memstick_request;
+struct pci_dev;
+
+struct bcm577x5_device {
+	struct pci_dev *pci_dev;
+	struct memstick_host	*host;		/* host backpointer */
+	struct memstick_request *req;		/* current request */
+
+	/* Registers, IRQ */
+	void __iomem *mmio;
+	int irq;
+
+	/* Command execution */
+	struct work_struct req_work;
+	struct mutex req_lock;		/* prevent concurrent requests */
+	struct completion cmd_done;
+	struct completion fifo_done;
+
+	/* DMA execution */
+	int enable_dma;
+	struct completion dma_done;
+	void *dma_buffer;
+	void *dma_buffer_aligned;
+	dma_addr_t dma_handle;
+
+	/* Device properties */
+	int base_clock;
+};
+
+/*
+ * Controller registers (from sdhci.h)
+ * Registers are almost exactly the same as sdhci registers, reuse them
+ */
+
+#define BCM577x5_MS_DMA_ADDRESS		0x00
+#define BCM577x5_MS_BLOCK_SIZE		0x04
+#define BCM577x5_MS_ARGUMENT		0x08
+
+#define BCM577x5_MS_TPC_REG		0x0C
+#define BCM577x5_MS_MAKE_TPC(c) ((((c) << 4) | ((c) ^ 0xF)) << 24)
+#define  BCM577x5_MS_TRNS_DMA		0x01
+#define  BCM577x5_MS_TRNS_READ		0x10
+
+#define BCM577x5_MS_RESPONSE		0x10
+#define BCM577x5_MS_RESPONSE2		0x14
+#define BCM577x5_MS_BUFFER		0x20
+
+/* This can be used as a logic analyzer */
+#define BCM577x5_MS_PRESENT_STATE	0x24
+#define  BCM577x5_MS_DATA_LVL_MASK	0x00F00000
+#define  BCM577x5_MS_CMD_LVL		0x01000000
+
+#define BCM577x5_MS_HOST_CONTROL	0x28
+#define  BCM577x5_MS_CTRL_4BITBUS	0x02
+#define  BCM577x5_MS_CTRL_8BITBUS	0x20
+
+#define BCM577x5_MS_POWER_CONTROL	0x29
+#define  BCM577x5_MS_POWER_ON		0x01
+#define  BCM577x5_MS_POWER_330		0x0E
+
+#define BCM577x5_MS_CLOCK_CONTROL	0x2C
+#define  BCM577x5_MS_DIVIDER_SHIFT	8
+#define  BCM577x5_MS_DIVIDER_HI_SHIFT	6
+#define  BCM577x5_MS_DIV_MASK		0xFF
+#define  BCM577x5_MS_DIV_HI_MASK	0x300
+#define  BCM577x5_MS_CLOCK_CARD_EN	0x0004
+#define  BCM577x5_MS_CLOCK_INT_STABLE	0x0002
+#define  BCM577x5_MS_CLOCK_INT_EN	0x0001
+
+#define BCM577x5_MS_TIMEOUT_CONTROL	0x2E
+
+#define BCM577x5_MS_SOFTWARE_RESET	0x2F
+#define  BCM577x5_MS_RESET_ALL		0x01
+#define  BCM577x5_MS_RESET_CMD		0x02
+#define  BCM577x5_MS_RESET_DATA		0x04
+
+#define BCM577x5_MS_INT_STATUS		0x30
+#define BCM577x5_MS_INT_ENABLE		0x34
+#define BCM577x5_MS_SIGNAL_ENABLE	0x38
+#define  BCM577x5_MS_INT_RESPONSE	0x00000001
+#define  BCM577x5_MS_INT_DATA_END	0x00000002
+#define  BCM577x5_MS_INT_BLK_GAP	0x00000004
+#define  BCM577x5_MS_INT_DMA_END	0x00000008
+#define  BCM577x5_MS_INT_SPACE_AVAIL	0x00000010
+#define  BCM577x5_MS_INT_DATA_AVAIL	0x00000020
+#define  BCM577x5_MS_INT_CARD_INSERT_2	0x00000040
+#define  BCM577x5_MS_INT_CARD_REMOVE	0x00000080
+#define  BCM577x5_MS_INT_CARD_INT	0x00000100
+#define  BCM577x5_MS_INT_RETUNE		0x00001000
+#define  BCM577x5_MS_INT_CQE		0x00004000
+#define  BCM577x5_MS_INT_ERROR		0x00008000
+#define  BCM577x5_MS_INT_TIMEOUT	0x00010000
+#define  BCM577x5_MS_INT_CRC		0x00020000
+#define  BCM577x5_MS_INT_END_BIT	0x00040000
+#define  BCM577x5_MS_INT_INDEX		0x00080000
+#define  BCM577x5_MS_INT_DATA_TIMEOUT	0x00100000
+#define  BCM577x5_MS_INT_DATA_CRC	0x00200000
+#define  BCM577x5_MS_INT_DATA_END_BIT	0x00400000
+#define  BCM577x5_MS_INT_BUS_POWER	0x00800000
+#define  BCM577x5_MS_INT_AUTO_CMD_ERR	0x01000000
+#define  BCM577x5_MS_INT_ADMA_ERROR	0x02000000
+#define  BCM577x5_MS_INT_CARD_INSERT	0x40000000
+
+#define BCM577x5_MS_INT_DEFAULT                                                \
+	(BCM577x5_MS_INT_RESPONSE | BCM577x5_MS_INT_DATA_END |                 \
+	 BCM577x5_MS_INT_DMA_END | BCM577x5_MS_INT_SPACE_AVAIL |               \
+	 BCM577x5_MS_INT_DATA_AVAIL | BCM577x5_MS_INT_CARD_INSERT_2 |          \
+	 BCM577x5_MS_INT_CARD_REMOVE | BCM577x5_MS_INT_TIMEOUT |               \
+	 BCM577x5_MS_INT_CRC | BCM577x5_MS_INT_END_BIT |                       \
+	 BCM577x5_MS_INT_INDEX | BCM577x5_MS_INT_DATA_TIMEOUT |                \
+	 BCM577x5_MS_INT_DATA_CRC | BCM577x5_MS_INT_DATA_END_BIT |             \
+	 BCM577x5_MS_INT_BUS_POWER | BCM577x5_MS_INT_ADMA_ERROR |              \
+	 BCM577x5_MS_INT_CARD_INSERT)
+
+#define BCM577x5_MS_ARGUMENT2	0xF4
+
+#define BCM577x5_MS_STATUS				0x190
+#define  BCM577x5_MS_STATUS_INT_ASSERTED		0x00000002
+#define  BCM577x5_MS_STATUS_MS_DETECT			0x00004000
+
+#define BCM577x5_MS_CAPABILITY_SLOT2			0x1A8
+#define  BCM577x5_MS_CAPABILITY_SDMA			0x00100000
+#define BCM577x5_MS_CAPABILITY_BASE_CLOCK_MASK		0x00007F80
+#define  BCM577x5_MS_CAPABILITY_BASE_CLOCK_SHIFT	7
+#define BCM577x5_MS_CAPABILITY_TIMEOUT_CLOCK_MASK	0x0000003F
+
+#endif /* BCM577X5_MS_H */

base-commit: 155f6759c2529d273b6b204c92bd51e3e3989232
-- 
2.55.0


^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [RFC PATCH] memstick: Add Broadcom BCM57765/BCM57785 host driver
  2026-09-12 16:38 [RFC PATCH] memstick: Add Broadcom BCM57765/BCM57785 host driver Ho Jie Feng
@ 2026-09-14 10:31 ` Ulf Hansson
  2026-09-14 13:56   ` Ho Jie Feng
  0 siblings, 1 reply; 3+ messages in thread
From: Ulf Hansson @ 2026-09-14 10:31 UTC (permalink / raw)
  To: Ho Jie Feng
  Cc: linux-mmc, Maxim Levitsky, Alex Dubov, Ulf Hansson, linux-kernel

On Sat, Sep 12, 2026 at 6:38 PM Ho Jie Feng <hjf3108@gmail.com> wrote:
>
> Add support for the MemoryStick function of Broadcom BCM57765 and BCM57785
> PCI card readers (14e4:16be).
>
> The driver is written by inspecting MMIO traces from the windows driver
> and experimentally probing the device registers.
>
> It was found that the controller exposes an SDHCI-like interface with
> TPC command handling. DMA mode was then inferred using the positions
> of the bits in sdhci.h.
>
> Signed-off-by: Ho Jie Feng <hjf3108@gmail.com>

Wow! It's been a while since we received new drivers from memstick controllers!

Overall this looks good to me, but I have few minor comments, see below.

> ---
>  MAINTAINERS                         |   6 +
>  drivers/memstick/host/Kconfig       |  10 +
>  drivers/memstick/host/Makefile      |   1 +
>  drivers/memstick/host/bcm577x5_ms.c | 754 ++++++++++++++++++++++++++++
>  drivers/memstick/host/bcm577x5_ms.h | 138 +++++
>  5 files changed, 909 insertions(+)
>  create mode 100644 drivers/memstick/host/bcm577x5_ms.c
>  create mode 100644 drivers/memstick/host/bcm577x5_ms.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 978fe6999d3b3..20faa8c2b1bcc 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -5234,6 +5234,12 @@ S:       Maintained
>  F:     arch/arm/boot/dts/broadcom/bcm47189*
>  F:     arch/arm/boot/dts/broadcom/bcm53573*
>
> +BROADCOM BCM57765/BCM57785 MEMORYSTICK DRIVER
> +M:     Ho Jie Feng <hjf3108@gmail.com>
> +L:     linux-mmc@vger.kernel.org
> +S:     Maintained
> +F:     drivers/memstick/host/bcm577x5_ms.*
> +
>  BROADCOM BCM63XX/BCM33XX UDC DRIVER
>  M:     Kevin Cernekee <cernekee@gmail.com>
>  L:     linux-usb@vger.kernel.org
> diff --git a/drivers/memstick/host/Kconfig b/drivers/memstick/host/Kconfig
> index fcd2c2cc3cb47..f9b18f0d85513 100644
> --- a/drivers/memstick/host/Kconfig
> +++ b/drivers/memstick/host/Kconfig
> @@ -53,3 +53,13 @@ config MEMSTICK_REALTEK_USB
>
>           To compile this driver as a module, choose M here: the module will
>           be called rts5139_ms.
> +
> +config MEMSTICK_BCM577X5
> +       tristate "Broadcom BCM57765/BCM57785 MemoryStick interface support"
> +       depends on PCI
> +       help
> +         Say Y here if you want to access MemoryStick cards with the
> +         Broadcom BCM57765/BCM57785 PCI card reader (14e4:16be).
> +
> +         To compile this driver as a module, choose M here: the module
> +         will be called bcm577x5_ms.
> diff --git a/drivers/memstick/host/Makefile b/drivers/memstick/host/Makefile
> index 0c90df33165de..8eafb70b26706 100644
> --- a/drivers/memstick/host/Makefile
> +++ b/drivers/memstick/host/Makefile
> @@ -7,3 +7,4 @@ obj-$(CONFIG_MEMSTICK_TIFM_MS)          += tifm_ms.o
>  obj-$(CONFIG_MEMSTICK_JMICRON_38X)     += jmb38x_ms.o
>  obj-$(CONFIG_MEMSTICK_R592)            += r592.o
>  obj-$(CONFIG_MEMSTICK_REALTEK_USB)     += rtsx_usb_ms.o
> +obj-$(CONFIG_MEMSTICK_BCM577X5)                += bcm577x5_ms.o
> diff --git a/drivers/memstick/host/bcm577x5_ms.c b/drivers/memstick/host/bcm577x5_ms.c
> new file mode 100644
> index 0000000000000..81824fb4e9a82
> --- /dev/null
> +++ b/drivers/memstick/host/bcm577x5_ms.c
> @@ -0,0 +1,754 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +#include <linux/align.h>
> +#include <linux/bits.h>
> +#include <linux/completion.h>
> +#include <linux/container_of.h>
> +#include <linux/device.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/errno.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/jiffies.h>
> +#include <linux/memstick.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/pci.h>
> +#include <linux/pci_ids.h>
> +#include <linux/pm.h>
> +#include <linux/scatterlist.h>
> +#include <linux/slab.h>
> +#include <linux/types.h>
> +#include <linux/workqueue.h>
> +
> +#include "bcm577x5_ms.h"
> +
> +#define DRV_NAME "bcm577x5_ms"
> +
> +static int enable_dma = 2;
> +module_param(enable_dma, int, 0444);
> +MODULE_PARM_DESC(enable_dma,
> +                "Enable usage of the DMA (0 = no, 1 = yes, 2 = auto,default)");

I assume this is useful because the DMA functionality is a bit flaky, no?

In any case, I would rather not use a module parameter for this, can
you please drop this. If needed at all, can we perhaps use a debugfs
file instead to switch dynamically?

> +
> +static const struct pci_device_id bcm577x5_pci_id_tbl[] = {
> +       {
> +               PCI_VDEVICE(BROADCOM, 0x16be),
> +       },
> +       {},
> +};

[...]

> +
> +static int bcm577x5_reg_waitb(struct bcm577x5_device *dev, int address, u8 mask,
> +                             u8 value, int timeout)
> +{
> +       unsigned long wait_time = jiffies + msecs_to_jiffies(timeout);
> +       u8 reg;
> +
> +       do {
> +               reg = bcm577x5_reg_readb(dev, address);
> +               if ((reg & mask) == value)
> +                       return 0;
> +
> +               cpu_relax();
> +
> +       } while (time_before(jiffies, wait_time));

Please avoid the open coding and convert to the io polling helpers
instead (iopoll.h).

> +
> +       return -ETIME;
> +}
> +
> +static int bcm577x5_reg_waitl_different(struct bcm577x5_device *dev,
> +                                       int address, u32 value, int timeout)
> +{
> +       unsigned long wait_time = jiffies + msecs_to_jiffies(timeout);
> +       u32 reg;
> +
> +       do {
> +               reg = bcm577x5_reg_readl(dev, address);
> +               if (reg != value)
> +                       return 0;
> +
> +               cpu_relax();
> +
> +       } while (time_before(jiffies, wait_time));

Ditto.

> +
> +       return -ETIMEDOUT;
> +}
> +

[...]

Kind regards
Uffe

^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [RFC PATCH] memstick: Add Broadcom BCM57765/BCM57785 host driver
  2026-09-14 10:31 ` Ulf Hansson
@ 2026-09-14 13:56   ` Ho Jie Feng
  0 siblings, 0 replies; 3+ messages in thread
From: Ho Jie Feng @ 2026-09-14 13:56 UTC (permalink / raw)
  To: Ulf Hansson, Ho Jie Feng
  Cc: linux-mmc, Maxim Levitsky, Alex Dubov, Ulf Hansson, linux-kernel

On Mon Sep 14, 2026 at 6:31 PM +08, Ulf Hansson wrote:
> On Sat, Sep 12, 2026 at 6:38 PM Ho Jie Feng <hjf3108@gmail.com> wrote:
>>
>> Add support for the MemoryStick function of Broadcom BCM57765 and BCM57785
>> PCI card readers (14e4:16be).
>>
>> The driver is written by inspecting MMIO traces from the windows driver
>> and experimentally probing the device registers.
>>
>> It was found that the controller exposes an SDHCI-like interface with
>> TPC command handling. DMA mode was then inferred using the positions
>> of the bits in sdhci.h.
>>
>> Signed-off-by: Ho Jie Feng <hjf3108@gmail.com>
>
> Wow! It's been a while since we received new drivers from memstick controllers!
Yep I wrote this quite a while back and only go around to upstreaming it recently.

>> +module_param(enable_dma, int, 0444);
>> +MODULE_PARM_DESC(enable_dma,
>> +                "Enable usage of the DMA (0 = no, 1 = yes, 2 = auto,default)");
>
> I assume this is useful because the DMA functionality is a bit flaky, no?
>
> In any case, I would rather not use a module parameter for this, can
> you please drop this. If needed at all, can we perhaps use a debugfs
> file instead to switch dynamically?
>

The traces from the Windows drivers doesn't use DMA at all, at least only the nice
ones which call the kernel APIs to do MMIO operations, unlike some newer drivers which
do the MMIO within the driver. The machine doesn't have VT-d so that is not usable.
Due to this, I inferred DMA operation from the bits from sdhci.h and doing some
experimentation on the device. The flag is only there because I may have missed
something and the DMA path isn't reliable.

I will look into the debugfs stuff, thanks.

>> +
>> +static const struct pci_device_id bcm577x5_pci_id_tbl[] = {
>> +       {
>> +               PCI_VDEVICE(BROADCOM, 0x16be),
>> +       },
>> +       {},
>> +};
>
> [...]
>
>> +
>> +static int bcm577x5_reg_waitb(struct bcm577x5_device *dev, int address, u8 mask,
>> +                             u8 value, int timeout)
>> +{
>> +       unsigned long wait_time = jiffies + msecs_to_jiffies(timeout);
>> +       u8 reg;
>> +
>> +       do {
>> +               reg = bcm577x5_reg_readb(dev, address);
>> +               if ((reg & mask) == value)
>> +                       return 0;
>> +
>> +               cpu_relax();
>> +
>> +       } while (time_before(jiffies, wait_time));
>
> Please avoid the open coding and convert to the io polling helpers
> instead (iopoll.h).
Will fix in v2.

^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-09-14 13:56 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-12 16:38 [RFC PATCH] memstick: Add Broadcom BCM57765/BCM57785 host driver Ho Jie Feng
2026-09-14 10:31 ` Ulf Hansson
2026-09-14 13:56   ` Ho Jie Feng

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®