* [RFC PATCH] memstick: Add Broadcom BCM57765/BCM57785 host driver
@ 2026-09-12 16:38 Ho Jie Feng
0 siblings, 0 replies; only message 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 related [flat|nested] only message in thread
only message in thread, other threads:[~2026-09-12 16:38 UTC | newest]
Thread overview: (only message) (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
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox