* [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®