summaryrefslogtreecommitdiffstats
path: root/drivers/memstick/host
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:49:45 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:49:45 +0000
commit2c3c1048746a4622d8c89a29670120dc8fab93c4 (patch)
tree848558de17fb3008cdf4d861b01ac7781903ce39 /drivers/memstick/host
parentInitial commit. (diff)
downloadlinux-2c3c1048746a4622d8c89a29670120dc8fab93c4.tar.xz
linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.zip
Adding upstream version 6.1.76.upstream/6.1.76upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/memstick/host')
-rw-r--r--drivers/memstick/host/Kconfig65
-rw-r--r--drivers/memstick/host/Makefile10
-rw-r--r--drivers/memstick/host/jmb38x_ms.c1030
-rw-r--r--drivers/memstick/host/r592.c894
-rw-r--r--drivers/memstick/host/r592.h172
-rw-r--r--drivers/memstick/host/rtsx_pci_ms.c643
-rw-r--r--drivers/memstick/host/rtsx_usb_ms.c869
-rw-r--r--drivers/memstick/host/tifm_ms.c683
8 files changed, 4366 insertions, 0 deletions
diff --git a/drivers/memstick/host/Kconfig b/drivers/memstick/host/Kconfig
new file mode 100644
index 000000000..4113343da
--- /dev/null
+++ b/drivers/memstick/host/Kconfig
@@ -0,0 +1,65 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# MemoryStick host controller drivers
+#
+
+comment "MemoryStick Host Controller Drivers"
+
+config MEMSTICK_TIFM_MS
+ tristate "TI Flash Media MemoryStick Interface support "
+ depends on PCI
+ select TIFM_CORE
+ help
+ Say Y here if you want to be able to access MemoryStick cards with
+ the Texas Instruments(R) Flash Media card reader, found in many
+ laptops.
+ This option 'selects' (turns on, enables) 'TIFM_CORE', but you
+ probably also need appropriate card reader host adapter, such as
+ 'Misc devices: TI Flash Media PCI74xx/PCI76xx host adapter support
+ (TIFM_7XX1)'.
+
+ To compile this driver as a module, choose M here: the
+ module will be called tifm_ms.
+
+config MEMSTICK_JMICRON_38X
+ tristate "JMicron JMB38X MemoryStick interface support"
+ depends on PCI
+
+ help
+ Say Y here if you want to be able to access MemoryStick cards with
+ the JMicron(R) JMB38X MemoryStick card reader.
+
+ To compile this driver as a module, choose M here: the
+ module will be called jmb38x_ms.
+
+config MEMSTICK_R592
+ tristate "Ricoh R5C592 MemoryStick interface support"
+ depends on PCI
+
+ help
+ Say Y here if you want to be able to access MemoryStick cards with
+ the Ricoh R5C592 MemoryStick card reader (which is part of 5 in one
+ multifunction reader)
+
+ To compile this driver as a module, choose M here: the module will
+ be called r592.
+
+config MEMSTICK_REALTEK_PCI
+ tristate "Realtek PCI-E Memstick Card Interface Driver"
+ depends on MISC_RTSX_PCI
+ help
+ Say Y here to include driver code to support Memstick card interface
+ of Realtek PCI-E card reader
+
+ To compile this driver as a module, choose M here: the module will
+ be called rtsx_pci_ms.
+
+config MEMSTICK_REALTEK_USB
+ tristate "Realtek USB Memstick Card Interface Driver"
+ depends on MISC_RTSX_USB
+ help
+ Say Y here to include driver code to support Memstick card interface
+ of Realtek RTS5129/39 series USB card reader
+
+ To compile this driver as a module, choose M here: the module will
+ be called rts5139_ms.
diff --git a/drivers/memstick/host/Makefile b/drivers/memstick/host/Makefile
new file mode 100644
index 000000000..1abaa03ee
--- /dev/null
+++ b/drivers/memstick/host/Makefile
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for MemoryStick host controller drivers
+#
+
+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_PCI) += rtsx_pci_ms.o
+obj-$(CONFIG_MEMSTICK_REALTEK_USB) += rtsx_usb_ms.o
diff --git a/drivers/memstick/host/jmb38x_ms.c b/drivers/memstick/host/jmb38x_ms.c
new file mode 100644
index 000000000..21cb2a786
--- /dev/null
+++ b/drivers/memstick/host/jmb38x_ms.c
@@ -0,0 +1,1030 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * jmb38x_ms.c - JMicron jmb38x MemoryStick card reader
+ *
+ * Copyright (C) 2008 Alex Dubov <oakad@yahoo.com>
+ */
+
+#include <linux/spinlock.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/dma-mapping.h>
+#include <linux/delay.h>
+#include <linux/highmem.h>
+#include <linux/memstick.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+
+#define DRIVER_NAME "jmb38x_ms"
+
+static bool no_dma;
+module_param(no_dma, bool, 0644);
+
+enum {
+ DMA_ADDRESS = 0x00,
+ BLOCK = 0x04,
+ DMA_CONTROL = 0x08,
+ TPC_P0 = 0x0c,
+ TPC_P1 = 0x10,
+ TPC = 0x14,
+ HOST_CONTROL = 0x18,
+ DATA = 0x1c,
+ STATUS = 0x20,
+ INT_STATUS = 0x24,
+ INT_STATUS_ENABLE = 0x28,
+ INT_SIGNAL_ENABLE = 0x2c,
+ TIMER = 0x30,
+ TIMER_CONTROL = 0x34,
+ PAD_OUTPUT_ENABLE = 0x38,
+ PAD_PU_PD = 0x3c,
+ CLOCK_DELAY = 0x40,
+ ADMA_ADDRESS = 0x44,
+ CLOCK_CONTROL = 0x48,
+ LED_CONTROL = 0x4c,
+ VERSION = 0x50
+};
+
+struct jmb38x_ms_host {
+ struct jmb38x_ms *chip;
+ void __iomem *addr;
+ spinlock_t lock;
+ struct tasklet_struct notify;
+ int id;
+ char host_id[32];
+ int irq;
+ unsigned int block_pos;
+ unsigned long timeout_jiffies;
+ struct timer_list timer;
+ struct memstick_host *msh;
+ struct memstick_request *req;
+ unsigned char cmd_flags;
+ unsigned char io_pos;
+ unsigned char ifmode;
+ unsigned int io_word[2];
+};
+
+struct jmb38x_ms {
+ struct pci_dev *pdev;
+ int host_cnt;
+ struct memstick_host *hosts[];
+};
+
+#define BLOCK_COUNT_MASK 0xffff0000
+#define BLOCK_SIZE_MASK 0x00000fff
+
+#define DMA_CONTROL_ENABLE 0x00000001
+
+#define TPC_DATA_SEL 0x00008000
+#define TPC_DIR 0x00004000
+#define TPC_WAIT_INT 0x00002000
+#define TPC_GET_INT 0x00000800
+#define TPC_CODE_SZ_MASK 0x00000700
+#define TPC_DATA_SZ_MASK 0x00000007
+
+#define HOST_CONTROL_TDELAY_EN 0x00040000
+#define HOST_CONTROL_HW_OC_P 0x00010000
+#define HOST_CONTROL_RESET_REQ 0x00008000
+#define HOST_CONTROL_REI 0x00004000
+#define HOST_CONTROL_LED 0x00000400
+#define HOST_CONTROL_FAST_CLK 0x00000200
+#define HOST_CONTROL_RESET 0x00000100
+#define HOST_CONTROL_POWER_EN 0x00000080
+#define HOST_CONTROL_CLOCK_EN 0x00000040
+#define HOST_CONTROL_REO 0x00000008
+#define HOST_CONTROL_IF_SHIFT 4
+
+#define HOST_CONTROL_IF_SERIAL 0x0
+#define HOST_CONTROL_IF_PAR4 0x1
+#define HOST_CONTROL_IF_PAR8 0x3
+
+#define STATUS_BUSY 0x00080000
+#define STATUS_MS_DAT7 0x00040000
+#define STATUS_MS_DAT6 0x00020000
+#define STATUS_MS_DAT5 0x00010000
+#define STATUS_MS_DAT4 0x00008000
+#define STATUS_MS_DAT3 0x00004000
+#define STATUS_MS_DAT2 0x00002000
+#define STATUS_MS_DAT1 0x00001000
+#define STATUS_MS_DAT0 0x00000800
+#define STATUS_HAS_MEDIA 0x00000400
+#define STATUS_FIFO_EMPTY 0x00000200
+#define STATUS_FIFO_FULL 0x00000100
+#define STATUS_MS_CED 0x00000080
+#define STATUS_MS_ERR 0x00000040
+#define STATUS_MS_BRQ 0x00000020
+#define STATUS_MS_CNK 0x00000001
+
+#define INT_STATUS_TPC_ERR 0x00080000
+#define INT_STATUS_CRC_ERR 0x00040000
+#define INT_STATUS_TIMER_TO 0x00020000
+#define INT_STATUS_HSK_TO 0x00010000
+#define INT_STATUS_ANY_ERR 0x00008000
+#define INT_STATUS_FIFO_WRDY 0x00000080
+#define INT_STATUS_FIFO_RRDY 0x00000040
+#define INT_STATUS_MEDIA_OUT 0x00000010
+#define INT_STATUS_MEDIA_IN 0x00000008
+#define INT_STATUS_DMA_BOUNDARY 0x00000004
+#define INT_STATUS_EOTRAN 0x00000002
+#define INT_STATUS_EOTPC 0x00000001
+
+#define INT_STATUS_ALL 0x000f801f
+
+#define PAD_OUTPUT_ENABLE_MS 0x0F3F
+
+#define PAD_PU_PD_OFF 0x7FFF0000
+#define PAD_PU_PD_ON_MS_SOCK0 0x5f8f0000
+#define PAD_PU_PD_ON_MS_SOCK1 0x0f0f0000
+
+#define CLOCK_CONTROL_BY_MMIO 0x00000008
+#define CLOCK_CONTROL_40MHZ 0x00000001
+#define CLOCK_CONTROL_50MHZ 0x00000002
+#define CLOCK_CONTROL_60MHZ 0x00000010
+#define CLOCK_CONTROL_62_5MHZ 0x00000004
+#define CLOCK_CONTROL_OFF 0x00000000
+
+#define PCI_CTL_CLOCK_DLY_ADDR 0x000000b0
+
+enum {
+ CMD_READY = 0x01,
+ FIFO_READY = 0x02,
+ REG_DATA = 0x04,
+ DMA_DATA = 0x08
+};
+
+static unsigned int jmb38x_ms_read_data(struct jmb38x_ms_host *host,
+ unsigned char *buf, unsigned int length)
+{
+ unsigned int off = 0;
+
+ while (host->io_pos && length) {
+ buf[off++] = host->io_word[0] & 0xff;
+ host->io_word[0] >>= 8;
+ length--;
+ host->io_pos--;
+ }
+
+ if (!length)
+ return off;
+
+ while (!(STATUS_FIFO_EMPTY & readl(host->addr + STATUS))) {
+ if (length < 4)
+ break;
+ *(unsigned int *)(buf + off) = __raw_readl(host->addr + DATA);
+ length -= 4;
+ off += 4;
+ }
+
+ if (length
+ && !(STATUS_FIFO_EMPTY & readl(host->addr + STATUS))) {
+ host->io_word[0] = readl(host->addr + DATA);
+ for (host->io_pos = 4; host->io_pos; --host->io_pos) {
+ buf[off++] = host->io_word[0] & 0xff;
+ host->io_word[0] >>= 8;
+ length--;
+ if (!length)
+ break;
+ }
+ }
+
+ return off;
+}
+
+static unsigned int jmb38x_ms_read_reg_data(struct jmb38x_ms_host *host,
+ unsigned char *buf,
+ unsigned int length)
+{
+ unsigned int off = 0;
+
+ while (host->io_pos > 4 && length) {
+ buf[off++] = host->io_word[0] & 0xff;
+ host->io_word[0] >>= 8;
+ length--;
+ host->io_pos--;
+ }
+
+ if (!length)
+ return off;
+
+ while (host->io_pos && length) {
+ buf[off++] = host->io_word[1] & 0xff;
+ host->io_word[1] >>= 8;
+ length--;
+ host->io_pos--;
+ }
+
+ return off;
+}
+
+static unsigned int jmb38x_ms_write_data(struct jmb38x_ms_host *host,
+ unsigned char *buf,
+ unsigned int length)
+{
+ unsigned int off = 0;
+
+ if (host->io_pos) {
+ while (host->io_pos < 4 && length) {
+ host->io_word[0] |= buf[off++] << (host->io_pos * 8);
+ host->io_pos++;
+ length--;
+ }
+ }
+
+ if (host->io_pos == 4
+ && !(STATUS_FIFO_FULL & readl(host->addr + STATUS))) {
+ writel(host->io_word[0], host->addr + DATA);
+ host->io_pos = 0;
+ host->io_word[0] = 0;
+ } else if (host->io_pos) {
+ return off;
+ }
+
+ if (!length)
+ return off;
+
+ while (!(STATUS_FIFO_FULL & readl(host->addr + STATUS))) {
+ if (length < 4)
+ break;
+
+ __raw_writel(*(unsigned int *)(buf + off),
+ host->addr + DATA);
+ length -= 4;
+ off += 4;
+ }
+
+ switch (length) {
+ case 3:
+ host->io_word[0] |= buf[off + 2] << 16;
+ host->io_pos++;
+ fallthrough;
+ case 2:
+ host->io_word[0] |= buf[off + 1] << 8;
+ host->io_pos++;
+ fallthrough;
+ case 1:
+ host->io_word[0] |= buf[off];
+ host->io_pos++;
+ }
+
+ off += host->io_pos;
+
+ return off;
+}
+
+static unsigned int jmb38x_ms_write_reg_data(struct jmb38x_ms_host *host,
+ unsigned char *buf,
+ unsigned int length)
+{
+ unsigned int off = 0;
+
+ while (host->io_pos < 4 && length) {
+ host->io_word[0] &= ~(0xff << (host->io_pos * 8));
+ host->io_word[0] |= buf[off++] << (host->io_pos * 8);
+ host->io_pos++;
+ length--;
+ }
+
+ if (!length)
+ return off;
+
+ while (host->io_pos < 8 && length) {
+ host->io_word[1] &= ~(0xff << (host->io_pos * 8));
+ host->io_word[1] |= buf[off++] << (host->io_pos * 8);
+ host->io_pos++;
+ length--;
+ }
+
+ return off;
+}
+
+static int jmb38x_ms_transfer_data(struct jmb38x_ms_host *host)
+{
+ unsigned int length;
+ unsigned int off;
+ unsigned int t_size, p_cnt;
+ unsigned char *buf;
+ struct page *pg;
+ unsigned long flags = 0;
+
+ if (host->req->long_data) {
+ length = host->req->sg.length - host->block_pos;
+ off = host->req->sg.offset + host->block_pos;
+ } else {
+ length = host->req->data_len - host->block_pos;
+ off = 0;
+ }
+
+ while (length) {
+ unsigned int p_off;
+
+ if (host->req->long_data) {
+ pg = nth_page(sg_page(&host->req->sg),
+ off >> PAGE_SHIFT);
+ p_off = offset_in_page(off);
+ p_cnt = PAGE_SIZE - p_off;
+ p_cnt = min(p_cnt, length);
+
+ local_irq_save(flags);
+ buf = kmap_atomic(pg) + p_off;
+ } else {
+ buf = host->req->data + host->block_pos;
+ p_cnt = host->req->data_len - host->block_pos;
+ }
+
+ if (host->req->data_dir == WRITE)
+ t_size = !(host->cmd_flags & REG_DATA)
+ ? jmb38x_ms_write_data(host, buf, p_cnt)
+ : jmb38x_ms_write_reg_data(host, buf, p_cnt);
+ else
+ t_size = !(host->cmd_flags & REG_DATA)
+ ? jmb38x_ms_read_data(host, buf, p_cnt)
+ : jmb38x_ms_read_reg_data(host, buf, p_cnt);
+
+ if (host->req->long_data) {
+ kunmap_atomic(buf - p_off);
+ local_irq_restore(flags);
+ }
+
+ if (!t_size)
+ break;
+ host->block_pos += t_size;
+ length -= t_size;
+ off += t_size;
+ }
+
+ if (!length && host->req->data_dir == WRITE) {
+ if (host->cmd_flags & REG_DATA) {
+ writel(host->io_word[0], host->addr + TPC_P0);
+ writel(host->io_word[1], host->addr + TPC_P1);
+ } else if (host->io_pos) {
+ writel(host->io_word[0], host->addr + DATA);
+ }
+ }
+
+ return length;
+}
+
+static int jmb38x_ms_issue_cmd(struct memstick_host *msh)
+{
+ struct jmb38x_ms_host *host = memstick_priv(msh);
+ unsigned int data_len, cmd, t_val;
+
+ if (!(STATUS_HAS_MEDIA & readl(host->addr + STATUS))) {
+ dev_dbg(&msh->dev, "no media status\n");
+ host->req->error = -ETIME;
+ return host->req->error;
+ }
+
+ dev_dbg(&msh->dev, "control %08x\n", readl(host->addr + HOST_CONTROL));
+ dev_dbg(&msh->dev, "status %08x\n", readl(host->addr + INT_STATUS));
+ dev_dbg(&msh->dev, "hstatus %08x\n", readl(host->addr + STATUS));
+
+ host->cmd_flags = 0;
+ host->block_pos = 0;
+ host->io_pos = 0;
+ host->io_word[0] = 0;
+ host->io_word[1] = 0;
+
+ cmd = host->req->tpc << 16;
+ cmd |= TPC_DATA_SEL;
+
+ if (host->req->data_dir == READ)
+ cmd |= TPC_DIR;
+
+ if (host->req->need_card_int) {
+ if (host->ifmode == MEMSTICK_SERIAL)
+ cmd |= TPC_GET_INT;
+ else
+ cmd |= TPC_WAIT_INT;
+ }
+
+ if (!no_dma)
+ host->cmd_flags |= DMA_DATA;
+
+ if (host->req->long_data) {
+ data_len = host->req->sg.length;
+ } else {
+ data_len = host->req->data_len;
+ host->cmd_flags &= ~DMA_DATA;
+ }
+
+ if (data_len <= 8) {
+ cmd &= ~(TPC_DATA_SEL | 0xf);
+ host->cmd_flags |= REG_DATA;
+ cmd |= data_len & 0xf;
+ host->cmd_flags &= ~DMA_DATA;
+ }
+
+ if (host->cmd_flags & DMA_DATA) {
+ if (1 != dma_map_sg(&host->chip->pdev->dev, &host->req->sg, 1,
+ host->req->data_dir == READ
+ ? DMA_FROM_DEVICE
+ : DMA_TO_DEVICE)) {
+ host->req->error = -ENOMEM;
+ return host->req->error;
+ }
+ data_len = sg_dma_len(&host->req->sg);
+ writel(sg_dma_address(&host->req->sg),
+ host->addr + DMA_ADDRESS);
+ writel(((1 << 16) & BLOCK_COUNT_MASK)
+ | (data_len & BLOCK_SIZE_MASK),
+ host->addr + BLOCK);
+ writel(DMA_CONTROL_ENABLE, host->addr + DMA_CONTROL);
+ } else if (!(host->cmd_flags & REG_DATA)) {
+ writel(((1 << 16) & BLOCK_COUNT_MASK)
+ | (data_len & BLOCK_SIZE_MASK),
+ host->addr + BLOCK);
+ t_val = readl(host->addr + INT_STATUS_ENABLE);
+ t_val |= host->req->data_dir == READ
+ ? INT_STATUS_FIFO_RRDY
+ : INT_STATUS_FIFO_WRDY;
+
+ writel(t_val, host->addr + INT_STATUS_ENABLE);
+ writel(t_val, host->addr + INT_SIGNAL_ENABLE);
+ } else {
+ cmd &= ~(TPC_DATA_SEL | 0xf);
+ host->cmd_flags |= REG_DATA;
+ cmd |= data_len & 0xf;
+
+ if (host->req->data_dir == WRITE) {
+ jmb38x_ms_transfer_data(host);
+ writel(host->io_word[0], host->addr + TPC_P0);
+ writel(host->io_word[1], host->addr + TPC_P1);
+ }
+ }
+
+ mod_timer(&host->timer, jiffies + host->timeout_jiffies);
+ writel(HOST_CONTROL_LED | readl(host->addr + HOST_CONTROL),
+ host->addr + HOST_CONTROL);
+ host->req->error = 0;
+
+ writel(cmd, host->addr + TPC);
+ dev_dbg(&msh->dev, "executing TPC %08x, len %x\n", cmd, data_len);
+
+ return 0;
+}
+
+static void jmb38x_ms_complete_cmd(struct memstick_host *msh, int last)
+{
+ struct jmb38x_ms_host *host = memstick_priv(msh);
+ unsigned int t_val = 0;
+ int rc;
+
+ del_timer(&host->timer);
+
+ dev_dbg(&msh->dev, "c control %08x\n",
+ readl(host->addr + HOST_CONTROL));
+ dev_dbg(&msh->dev, "c status %08x\n",
+ readl(host->addr + INT_STATUS));
+ dev_dbg(&msh->dev, "c hstatus %08x\n", readl(host->addr + STATUS));
+
+ host->req->int_reg = readl(host->addr + STATUS) & 0xff;
+
+ writel(0, host->addr + BLOCK);
+ writel(0, host->addr + DMA_CONTROL);
+
+ if (host->cmd_flags & DMA_DATA) {
+ dma_unmap_sg(&host->chip->pdev->dev, &host->req->sg, 1,
+ host->req->data_dir == READ
+ ? DMA_FROM_DEVICE : DMA_TO_DEVICE);
+ } else {
+ t_val = readl(host->addr + INT_STATUS_ENABLE);
+ if (host->req->data_dir == READ)
+ t_val &= ~INT_STATUS_FIFO_RRDY;
+ else
+ t_val &= ~INT_STATUS_FIFO_WRDY;
+
+ writel(t_val, host->addr + INT_STATUS_ENABLE);
+ writel(t_val, host->addr + INT_SIGNAL_ENABLE);
+ }
+
+ writel((~HOST_CONTROL_LED) & readl(host->addr + HOST_CONTROL),
+ host->addr + HOST_CONTROL);
+
+ if (!last) {
+ do {
+ rc = memstick_next_req(msh, &host->req);
+ } while (!rc && jmb38x_ms_issue_cmd(msh));
+ } else {
+ do {
+ rc = memstick_next_req(msh, &host->req);
+ if (!rc)
+ host->req->error = -ETIME;
+ } while (!rc);
+ }
+}
+
+static irqreturn_t jmb38x_ms_isr(int irq, void *dev_id)
+{
+ struct memstick_host *msh = dev_id;
+ struct jmb38x_ms_host *host = memstick_priv(msh);
+ unsigned int irq_status;
+
+ spin_lock(&host->lock);
+ irq_status = readl(host->addr + INT_STATUS);
+ dev_dbg(&host->chip->pdev->dev, "irq_status = %08x\n", irq_status);
+ if (irq_status == 0 || irq_status == (~0)) {
+ spin_unlock(&host->lock);
+ return IRQ_NONE;
+ }
+
+ if (host->req) {
+ if (irq_status & INT_STATUS_ANY_ERR) {
+ if (irq_status & INT_STATUS_CRC_ERR)
+ host->req->error = -EILSEQ;
+ else if (irq_status & INT_STATUS_TPC_ERR) {
+ dev_dbg(&host->chip->pdev->dev, "TPC_ERR\n");
+ jmb38x_ms_complete_cmd(msh, 0);
+ } else
+ host->req->error = -ETIME;
+ } else {
+ if (host->cmd_flags & DMA_DATA) {
+ if (irq_status & INT_STATUS_EOTRAN)
+ host->cmd_flags |= FIFO_READY;
+ } else {
+ if (irq_status & (INT_STATUS_FIFO_RRDY
+ | INT_STATUS_FIFO_WRDY))
+ jmb38x_ms_transfer_data(host);
+
+ if (irq_status & INT_STATUS_EOTRAN) {
+ jmb38x_ms_transfer_data(host);
+ host->cmd_flags |= FIFO_READY;
+ }
+ }
+
+ if (irq_status & INT_STATUS_EOTPC) {
+ host->cmd_flags |= CMD_READY;
+ if (host->cmd_flags & REG_DATA) {
+ if (host->req->data_dir == READ) {
+ host->io_word[0]
+ = readl(host->addr
+ + TPC_P0);
+ host->io_word[1]
+ = readl(host->addr
+ + TPC_P1);
+ host->io_pos = 8;
+
+ jmb38x_ms_transfer_data(host);
+ }
+ host->cmd_flags |= FIFO_READY;
+ }
+ }
+ }
+ }
+
+ if (irq_status & (INT_STATUS_MEDIA_IN | INT_STATUS_MEDIA_OUT)) {
+ dev_dbg(&host->chip->pdev->dev, "media changed\n");
+ memstick_detect_change(msh);
+ }
+
+ writel(irq_status, host->addr + INT_STATUS);
+
+ if (host->req
+ && (((host->cmd_flags & CMD_READY)
+ && (host->cmd_flags & FIFO_READY))
+ || host->req->error))
+ jmb38x_ms_complete_cmd(msh, 0);
+
+ spin_unlock(&host->lock);
+ return IRQ_HANDLED;
+}
+
+static void jmb38x_ms_abort(struct timer_list *t)
+{
+ struct jmb38x_ms_host *host = from_timer(host, t, timer);
+ struct memstick_host *msh = host->msh;
+ unsigned long flags;
+
+ dev_dbg(&host->chip->pdev->dev, "abort\n");
+ spin_lock_irqsave(&host->lock, flags);
+ if (host->req) {
+ host->req->error = -ETIME;
+ jmb38x_ms_complete_cmd(msh, 0);
+ }
+ spin_unlock_irqrestore(&host->lock, flags);
+}
+
+static void jmb38x_ms_req_tasklet(unsigned long data)
+{
+ struct memstick_host *msh = (struct memstick_host *)data;
+ struct jmb38x_ms_host *host = memstick_priv(msh);
+ unsigned long flags;
+ int rc;
+
+ spin_lock_irqsave(&host->lock, flags);
+ if (!host->req) {
+ do {
+ rc = memstick_next_req(msh, &host->req);
+ dev_dbg(&host->chip->pdev->dev, "tasklet req %d\n", rc);
+ } while (!rc && jmb38x_ms_issue_cmd(msh));
+ }
+ spin_unlock_irqrestore(&host->lock, flags);
+}
+
+static void jmb38x_ms_dummy_submit(struct memstick_host *msh)
+{
+ return;
+}
+
+static void jmb38x_ms_submit_req(struct memstick_host *msh)
+{
+ struct jmb38x_ms_host *host = memstick_priv(msh);
+
+ tasklet_schedule(&host->notify);
+}
+
+static int jmb38x_ms_reset(struct jmb38x_ms_host *host)
+{
+ int cnt;
+
+ writel(HOST_CONTROL_RESET_REQ | HOST_CONTROL_CLOCK_EN
+ | readl(host->addr + HOST_CONTROL),
+ host->addr + HOST_CONTROL);
+
+ for (cnt = 0; cnt < 20; ++cnt) {
+ if (!(HOST_CONTROL_RESET_REQ
+ & readl(host->addr + HOST_CONTROL)))
+ goto reset_next;
+
+ ndelay(20);
+ }
+ dev_dbg(&host->chip->pdev->dev, "reset_req timeout\n");
+
+reset_next:
+ writel(HOST_CONTROL_RESET | HOST_CONTROL_CLOCK_EN
+ | readl(host->addr + HOST_CONTROL),
+ host->addr + HOST_CONTROL);
+
+ for (cnt = 0; cnt < 20; ++cnt) {
+ if (!(HOST_CONTROL_RESET
+ & readl(host->addr + HOST_CONTROL)))
+ goto reset_ok;
+
+ ndelay(20);
+ }
+ dev_dbg(&host->chip->pdev->dev, "reset timeout\n");
+ return -EIO;
+
+reset_ok:
+ writel(INT_STATUS_ALL, host->addr + INT_SIGNAL_ENABLE);
+ writel(INT_STATUS_ALL, host->addr + INT_STATUS_ENABLE);
+ return 0;
+}
+
+static int jmb38x_ms_set_param(struct memstick_host *msh,
+ enum memstick_param param,
+ int value)
+{
+ struct jmb38x_ms_host *host = memstick_priv(msh);
+ unsigned int host_ctl = readl(host->addr + HOST_CONTROL);
+ unsigned int clock_ctl = CLOCK_CONTROL_BY_MMIO, clock_delay = 0;
+ int rc = 0;
+
+ switch (param) {
+ case MEMSTICK_POWER:
+ if (value == MEMSTICK_POWER_ON) {
+ rc = jmb38x_ms_reset(host);
+ if (rc)
+ return rc;
+
+ host_ctl = 7;
+ host_ctl |= HOST_CONTROL_POWER_EN
+ | HOST_CONTROL_CLOCK_EN;
+ writel(host_ctl, host->addr + HOST_CONTROL);
+
+ writel(host->id ? PAD_PU_PD_ON_MS_SOCK1
+ : PAD_PU_PD_ON_MS_SOCK0,
+ host->addr + PAD_PU_PD);
+
+ writel(PAD_OUTPUT_ENABLE_MS,
+ host->addr + PAD_OUTPUT_ENABLE);
+
+ msleep(10);
+ dev_dbg(&host->chip->pdev->dev, "power on\n");
+ } else if (value == MEMSTICK_POWER_OFF) {
+ host_ctl &= ~(HOST_CONTROL_POWER_EN
+ | HOST_CONTROL_CLOCK_EN);
+ writel(host_ctl, host->addr + HOST_CONTROL);
+ writel(0, host->addr + PAD_OUTPUT_ENABLE);
+ writel(PAD_PU_PD_OFF, host->addr + PAD_PU_PD);
+ dev_dbg(&host->chip->pdev->dev, "power off\n");
+ } else
+ return -EINVAL;
+ break;
+ case MEMSTICK_INTERFACE:
+ dev_dbg(&host->chip->pdev->dev,
+ "Set Host Interface Mode to %d\n", value);
+ host_ctl &= ~(HOST_CONTROL_FAST_CLK | HOST_CONTROL_REI |
+ HOST_CONTROL_REO);
+ host_ctl |= HOST_CONTROL_TDELAY_EN | HOST_CONTROL_HW_OC_P;
+ host_ctl &= ~(3 << HOST_CONTROL_IF_SHIFT);
+
+ if (value == MEMSTICK_SERIAL) {
+ host_ctl |= HOST_CONTROL_IF_SERIAL
+ << HOST_CONTROL_IF_SHIFT;
+ host_ctl |= HOST_CONTROL_REI;
+ clock_ctl |= CLOCK_CONTROL_40MHZ;
+ clock_delay = 0;
+ } else if (value == MEMSTICK_PAR4) {
+ host_ctl |= HOST_CONTROL_FAST_CLK;
+ host_ctl |= HOST_CONTROL_IF_PAR4
+ << HOST_CONTROL_IF_SHIFT;
+ host_ctl |= HOST_CONTROL_REO;
+ clock_ctl |= CLOCK_CONTROL_40MHZ;
+ clock_delay = 4;
+ } else if (value == MEMSTICK_PAR8) {
+ host_ctl |= HOST_CONTROL_FAST_CLK;
+ host_ctl |= HOST_CONTROL_IF_PAR8
+ << HOST_CONTROL_IF_SHIFT;
+ clock_ctl |= CLOCK_CONTROL_50MHZ;
+ clock_delay = 0;
+ } else
+ return -EINVAL;
+
+ writel(host_ctl, host->addr + HOST_CONTROL);
+ writel(CLOCK_CONTROL_OFF, host->addr + CLOCK_CONTROL);
+ writel(clock_ctl, host->addr + CLOCK_CONTROL);
+ pci_write_config_byte(host->chip->pdev,
+ PCI_CTL_CLOCK_DLY_ADDR + 1,
+ clock_delay);
+ host->ifmode = value;
+ break;
+ }
+ return 0;
+}
+
+#define PCI_PMOS0_CONTROL 0xae
+#define PMOS0_ENABLE 0x01
+#define PMOS0_OVERCURRENT_LEVEL_2_4V 0x06
+#define PMOS0_EN_OVERCURRENT_DEBOUNCE 0x40
+#define PMOS0_SW_LED_POLARITY_ENABLE 0x80
+#define PMOS0_ACTIVE_BITS (PMOS0_ENABLE | PMOS0_EN_OVERCURRENT_DEBOUNCE | \
+ PMOS0_OVERCURRENT_LEVEL_2_4V)
+#define PCI_PMOS1_CONTROL 0xbd
+#define PMOS1_ACTIVE_BITS 0x4a
+#define PCI_CLOCK_CTL 0xb9
+
+static int jmb38x_ms_pmos(struct pci_dev *pdev, int flag)
+{
+ unsigned char val;
+
+ pci_read_config_byte(pdev, PCI_PMOS0_CONTROL, &val);
+ if (flag)
+ val |= PMOS0_ACTIVE_BITS;
+ else
+ val &= ~PMOS0_ACTIVE_BITS;
+ pci_write_config_byte(pdev, PCI_PMOS0_CONTROL, val);
+ dev_dbg(&pdev->dev, "JMB38x: set PMOS0 val 0x%x\n", val);
+
+ if (pci_resource_flags(pdev, 1)) {
+ pci_read_config_byte(pdev, PCI_PMOS1_CONTROL, &val);
+ if (flag)
+ val |= PMOS1_ACTIVE_BITS;
+ else
+ val &= ~PMOS1_ACTIVE_BITS;
+ pci_write_config_byte(pdev, PCI_PMOS1_CONTROL, val);
+ dev_dbg(&pdev->dev, "JMB38x: set PMOS1 val 0x%x\n", val);
+ }
+
+ pci_read_config_byte(pdev, PCI_CLOCK_CTL, &val);
+ pci_write_config_byte(pdev, PCI_CLOCK_CTL, val & ~0x0f);
+ pci_write_config_byte(pdev, PCI_CLOCK_CTL, val | 0x01);
+ dev_dbg(&pdev->dev, "Clock Control by PCI config is disabled!\n");
+
+ return 0;
+}
+
+static int __maybe_unused jmb38x_ms_suspend(struct device *dev)
+{
+ struct jmb38x_ms *jm = dev_get_drvdata(dev);
+
+ int cnt;
+
+ for (cnt = 0; cnt < jm->host_cnt; ++cnt) {
+ if (!jm->hosts[cnt])
+ break;
+ memstick_suspend_host(jm->hosts[cnt]);
+ }
+
+ device_wakeup_disable(dev);
+
+ return 0;
+}
+
+static int __maybe_unused jmb38x_ms_resume(struct device *dev)
+{
+ struct jmb38x_ms *jm = dev_get_drvdata(dev);
+ int rc;
+
+ jmb38x_ms_pmos(to_pci_dev(dev), 1);
+
+ for (rc = 0; rc < jm->host_cnt; ++rc) {
+ if (!jm->hosts[rc])
+ break;
+ memstick_resume_host(jm->hosts[rc]);
+ memstick_detect_change(jm->hosts[rc]);
+ }
+
+ return 0;
+}
+
+static int jmb38x_ms_count_slots(struct pci_dev *pdev)
+{
+ int cnt, rc = 0;
+
+ for (cnt = 0; cnt < PCI_STD_NUM_BARS; ++cnt) {
+ if (!(IORESOURCE_MEM & pci_resource_flags(pdev, cnt)))
+ break;
+
+ if (256 != pci_resource_len(pdev, cnt))
+ break;
+
+ ++rc;
+ }
+ return rc;
+}
+
+static struct memstick_host *jmb38x_ms_alloc_host(struct jmb38x_ms *jm, int cnt)
+{
+ struct memstick_host *msh;
+ struct jmb38x_ms_host *host;
+
+ msh = memstick_alloc_host(sizeof(struct jmb38x_ms_host),
+ &jm->pdev->dev);
+ if (!msh)
+ return NULL;
+
+ host = memstick_priv(msh);
+ host->msh = msh;
+ host->chip = jm;
+ host->addr = ioremap(pci_resource_start(jm->pdev, cnt),
+ pci_resource_len(jm->pdev, cnt));
+ if (!host->addr)
+ goto err_out_free;
+
+ spin_lock_init(&host->lock);
+ host->id = cnt;
+ snprintf(host->host_id, sizeof(host->host_id), DRIVER_NAME ":slot%d",
+ host->id);
+ host->irq = jm->pdev->irq;
+ host->timeout_jiffies = msecs_to_jiffies(1000);
+
+ tasklet_init(&host->notify, jmb38x_ms_req_tasklet, (unsigned long)msh);
+ msh->request = jmb38x_ms_submit_req;
+ msh->set_param = jmb38x_ms_set_param;
+
+ msh->caps = MEMSTICK_CAP_PAR4 | MEMSTICK_CAP_PAR8;
+
+ timer_setup(&host->timer, jmb38x_ms_abort, 0);
+
+ if (!request_irq(host->irq, jmb38x_ms_isr, IRQF_SHARED, host->host_id,
+ msh))
+ return msh;
+
+ iounmap(host->addr);
+err_out_free:
+ memstick_free_host(msh);
+ return NULL;
+}
+
+static void jmb38x_ms_free_host(struct memstick_host *msh)
+{
+ struct jmb38x_ms_host *host = memstick_priv(msh);
+
+ free_irq(host->irq, msh);
+ iounmap(host->addr);
+ memstick_free_host(msh);
+}
+
+static int jmb38x_ms_probe(struct pci_dev *pdev,
+ const struct pci_device_id *dev_id)
+{
+ struct jmb38x_ms *jm;
+ int pci_dev_busy = 0;
+ int rc, cnt;
+
+ rc = dma_set_mask(&pdev->dev, DMA_BIT_MASK(32));
+ if (rc)
+ return rc;
+
+ rc = pci_enable_device(pdev);
+ if (rc)
+ return rc;
+
+ pci_set_master(pdev);
+
+ rc = pci_request_regions(pdev, DRIVER_NAME);
+ if (rc) {
+ pci_dev_busy = 1;
+ goto err_out;
+ }
+
+ jmb38x_ms_pmos(pdev, 1);
+
+ cnt = jmb38x_ms_count_slots(pdev);
+ if (!cnt) {
+ rc = -ENODEV;
+ pci_dev_busy = 1;
+ goto err_out_int;
+ }
+
+ jm = kzalloc(struct_size(jm, hosts, cnt), GFP_KERNEL);
+ if (!jm) {
+ rc = -ENOMEM;
+ goto err_out_int;
+ }
+
+ jm->pdev = pdev;
+ jm->host_cnt = cnt;
+ pci_set_drvdata(pdev, jm);
+
+ for (cnt = 0; cnt < jm->host_cnt; ++cnt) {
+ jm->hosts[cnt] = jmb38x_ms_alloc_host(jm, cnt);
+ if (!jm->hosts[cnt])
+ break;
+
+ rc = memstick_add_host(jm->hosts[cnt]);
+
+ if (rc) {
+ jmb38x_ms_free_host(jm->hosts[cnt]);
+ jm->hosts[cnt] = NULL;
+ break;
+ }
+ }
+
+ if (cnt)
+ return 0;
+
+ rc = -ENODEV;
+
+ pci_set_drvdata(pdev, NULL);
+ kfree(jm);
+err_out_int:
+ pci_release_regions(pdev);
+err_out:
+ if (!pci_dev_busy)
+ pci_disable_device(pdev);
+ return rc;
+}
+
+static void jmb38x_ms_remove(struct pci_dev *dev)
+{
+ struct jmb38x_ms *jm = pci_get_drvdata(dev);
+ struct jmb38x_ms_host *host;
+ int cnt;
+ unsigned long flags;
+
+ for (cnt = 0; cnt < jm->host_cnt; ++cnt) {
+ if (!jm->hosts[cnt])
+ break;
+
+ host = memstick_priv(jm->hosts[cnt]);
+
+ jm->hosts[cnt]->request = jmb38x_ms_dummy_submit;
+ tasklet_kill(&host->notify);
+ writel(0, host->addr + INT_SIGNAL_ENABLE);
+ writel(0, host->addr + INT_STATUS_ENABLE);
+ dev_dbg(&jm->pdev->dev, "interrupts off\n");
+ spin_lock_irqsave(&host->lock, flags);
+ if (host->req) {
+ host->req->error = -ETIME;
+ jmb38x_ms_complete_cmd(jm->hosts[cnt], 1);
+ }
+ spin_unlock_irqrestore(&host->lock, flags);
+
+ memstick_remove_host(jm->hosts[cnt]);
+ dev_dbg(&jm->pdev->dev, "host removed\n");
+
+ jmb38x_ms_free_host(jm->hosts[cnt]);
+ }
+
+ jmb38x_ms_pmos(dev, 0);
+
+ pci_set_drvdata(dev, NULL);
+ pci_release_regions(dev);
+ pci_disable_device(dev);
+ kfree(jm);
+}
+
+static struct pci_device_id jmb38x_ms_id_tbl [] = {
+ { PCI_VDEVICE(JMICRON, PCI_DEVICE_ID_JMICRON_JMB38X_MS) },
+ { PCI_VDEVICE(JMICRON, PCI_DEVICE_ID_JMICRON_JMB385_MS) },
+ { PCI_VDEVICE(JMICRON, PCI_DEVICE_ID_JMICRON_JMB390_MS) },
+ { }
+};
+
+static SIMPLE_DEV_PM_OPS(jmb38x_ms_pm_ops, jmb38x_ms_suspend, jmb38x_ms_resume);
+
+static struct pci_driver jmb38x_ms_driver = {
+ .name = DRIVER_NAME,
+ .id_table = jmb38x_ms_id_tbl,
+ .probe = jmb38x_ms_probe,
+ .remove = jmb38x_ms_remove,
+ .driver.pm = &jmb38x_ms_pm_ops,
+};
+
+module_pci_driver(jmb38x_ms_driver);
+
+MODULE_AUTHOR("Alex Dubov");
+MODULE_DESCRIPTION("JMicron jmb38x MemoryStick driver");
+MODULE_LICENSE("GPL");
+MODULE_DEVICE_TABLE(pci, jmb38x_ms_id_tbl);
diff --git a/drivers/memstick/host/r592.c b/drivers/memstick/host/r592.c
new file mode 100644
index 000000000..461f5ffd0
--- /dev/null
+++ b/drivers/memstick/host/r592.c
@@ -0,0 +1,894 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2010 - Maxim Levitsky
+ * driver for Ricoh memstick readers
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/freezer.h>
+#include <linux/jiffies.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/pci_ids.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/kthread.h>
+#include <linux/sched.h>
+#include <linux/highmem.h>
+#include <asm/byteorder.h>
+#include <linux/swab.h>
+#include "r592.h"
+
+static bool r592_enable_dma = 1;
+static int debug;
+
+static const char *tpc_names[] = {
+ "MS_TPC_READ_MG_STATUS",
+ "MS_TPC_READ_LONG_DATA",
+ "MS_TPC_READ_SHORT_DATA",
+ "MS_TPC_READ_REG",
+ "MS_TPC_READ_QUAD_DATA",
+ "INVALID",
+ "MS_TPC_GET_INT",
+ "MS_TPC_SET_RW_REG_ADRS",
+ "MS_TPC_EX_SET_CMD",
+ "MS_TPC_WRITE_QUAD_DATA",
+ "MS_TPC_WRITE_REG",
+ "MS_TPC_WRITE_SHORT_DATA",
+ "MS_TPC_WRITE_LONG_DATA",
+ "MS_TPC_SET_CMD",
+};
+
+/**
+ * memstick_debug_get_tpc_name - debug helper that returns string for
+ * a TPC number
+ */
+static __maybe_unused const char *memstick_debug_get_tpc_name(int tpc)
+{
+ return tpc_names[tpc-1];
+}
+
+/* Read a register*/
+static inline u32 r592_read_reg(struct r592_device *dev, int address)
+{
+ u32 value = readl(dev->mmio + address);
+ dbg_reg("reg #%02d == 0x%08x", address, value);
+ return value;
+}
+
+/* Write a register */
+static inline void r592_write_reg(struct r592_device *dev,
+ int address, u32 value)
+{
+ dbg_reg("reg #%02d <- 0x%08x", address, value);
+ writel(value, dev->mmio + address);
+}
+
+/* Reads a big endian DWORD register */
+static inline u32 r592_read_reg_raw_be(struct r592_device *dev, int address)
+{
+ u32 value = __raw_readl(dev->mmio + address);
+ dbg_reg("reg #%02d == 0x%08x", address, value);
+ return be32_to_cpu(value);
+}
+
+/* Writes a big endian DWORD register */
+static inline void r592_write_reg_raw_be(struct r592_device *dev,
+ int address, u32 value)
+{
+ dbg_reg("reg #%02d <- 0x%08x", address, value);
+ __raw_writel(cpu_to_be32(value), dev->mmio + address);
+}
+
+/* Set specific bits in a register (little endian) */
+static inline void r592_set_reg_mask(struct r592_device *dev,
+ int address, u32 mask)
+{
+ u32 reg = readl(dev->mmio + address);
+ dbg_reg("reg #%02d |= 0x%08x (old =0x%08x)", address, mask, reg);
+ writel(reg | mask , dev->mmio + address);
+}
+
+/* Clear specific bits in a register (little endian) */
+static inline void r592_clear_reg_mask(struct r592_device *dev,
+ int address, u32 mask)
+{
+ u32 reg = readl(dev->mmio + address);
+ dbg_reg("reg #%02d &= 0x%08x (old = 0x%08x, mask = 0x%08x)",
+ address, ~mask, reg, mask);
+ writel(reg & ~mask, dev->mmio + address);
+}
+
+
+/* Wait for status bits while checking for errors */
+static int r592_wait_status(struct r592_device *dev, u32 mask, u32 wanted_mask)
+{
+ unsigned long timeout = jiffies + msecs_to_jiffies(1000);
+ u32 reg = r592_read_reg(dev, R592_STATUS);
+
+ if ((reg & mask) == wanted_mask)
+ return 0;
+
+ while (time_before(jiffies, timeout)) {
+
+ reg = r592_read_reg(dev, R592_STATUS);
+
+ if ((reg & mask) == wanted_mask)
+ return 0;
+
+ if (reg & (R592_STATUS_SEND_ERR | R592_STATUS_RECV_ERR))
+ return -EIO;
+
+ cpu_relax();
+ }
+ return -ETIME;
+}
+
+
+/* Enable/disable device */
+static int r592_enable_device(struct r592_device *dev, bool enable)
+{
+ dbg("%sabling the device", enable ? "en" : "dis");
+
+ if (enable) {
+
+ /* Power up the card */
+ r592_write_reg(dev, R592_POWER, R592_POWER_0 | R592_POWER_1);
+
+ /* Perform a reset */
+ r592_set_reg_mask(dev, R592_IO, R592_IO_RESET);
+
+ msleep(100);
+ } else
+ /* Power down the card */
+ r592_write_reg(dev, R592_POWER, 0);
+
+ return 0;
+}
+
+/* Set serial/parallel mode */
+static int r592_set_mode(struct r592_device *dev, bool parallel_mode)
+{
+ if (!parallel_mode) {
+ dbg("switching to serial mode");
+
+ /* Set serial mode */
+ r592_write_reg(dev, R592_IO_MODE, R592_IO_MODE_SERIAL);
+
+ r592_clear_reg_mask(dev, R592_POWER, R592_POWER_20);
+
+ } else {
+ dbg("switching to parallel mode");
+
+ /* This setting should be set _before_ switch TPC */
+ r592_set_reg_mask(dev, R592_POWER, R592_POWER_20);
+
+ r592_clear_reg_mask(dev, R592_IO,
+ R592_IO_SERIAL1 | R592_IO_SERIAL2);
+
+ /* Set the parallel mode now */
+ r592_write_reg(dev, R592_IO_MODE, R592_IO_MODE_PARALLEL);
+ }
+
+ dev->parallel_mode = parallel_mode;
+ return 0;
+}
+
+/* Perform a controller reset without powering down the card */
+static void r592_host_reset(struct r592_device *dev)
+{
+ r592_set_reg_mask(dev, R592_IO, R592_IO_RESET);
+ msleep(100);
+ r592_set_mode(dev, dev->parallel_mode);
+}
+
+#ifdef CONFIG_PM_SLEEP
+/* Disable all hardware interrupts */
+static void r592_clear_interrupts(struct r592_device *dev)
+{
+ /* Disable & ACK all interrupts */
+ r592_clear_reg_mask(dev, R592_REG_MSC, IRQ_ALL_ACK_MASK);
+ r592_clear_reg_mask(dev, R592_REG_MSC, IRQ_ALL_EN_MASK);
+}
+#endif
+
+/* Tests if there is an CRC error */
+static int r592_test_io_error(struct r592_device *dev)
+{
+ if (!(r592_read_reg(dev, R592_STATUS) &
+ (R592_STATUS_SEND_ERR | R592_STATUS_RECV_ERR)))
+ return 0;
+
+ return -EIO;
+}
+
+/* Ensure that FIFO is ready for use */
+static int r592_test_fifo_empty(struct r592_device *dev)
+{
+ if (r592_read_reg(dev, R592_REG_MSC) & R592_REG_MSC_FIFO_EMPTY)
+ return 0;
+
+ dbg("FIFO not ready, trying to reset the device");
+ r592_host_reset(dev);
+
+ if (r592_read_reg(dev, R592_REG_MSC) & R592_REG_MSC_FIFO_EMPTY)
+ return 0;
+
+ message("FIFO still not ready, giving up");
+ return -EIO;
+}
+
+/* Activates the DMA transfer from to FIFO */
+static void r592_start_dma(struct r592_device *dev, bool is_write)
+{
+ unsigned long flags;
+ u32 reg;
+ spin_lock_irqsave(&dev->irq_lock, flags);
+
+ /* Ack interrupts (just in case) + enable them */
+ r592_clear_reg_mask(dev, R592_REG_MSC, DMA_IRQ_ACK_MASK);
+ r592_set_reg_mask(dev, R592_REG_MSC, DMA_IRQ_EN_MASK);
+
+ /* Set DMA address */
+ r592_write_reg(dev, R592_FIFO_DMA, sg_dma_address(&dev->req->sg));
+
+ /* Enable the DMA */
+ reg = r592_read_reg(dev, R592_FIFO_DMA_SETTINGS);
+ reg |= R592_FIFO_DMA_SETTINGS_EN;
+
+ if (!is_write)
+ reg |= R592_FIFO_DMA_SETTINGS_DIR;
+ else
+ reg &= ~R592_FIFO_DMA_SETTINGS_DIR;
+ r592_write_reg(dev, R592_FIFO_DMA_SETTINGS, reg);
+
+ spin_unlock_irqrestore(&dev->irq_lock, flags);
+}
+
+/* Cleanups DMA related settings */
+static void r592_stop_dma(struct r592_device *dev, int error)
+{
+ r592_clear_reg_mask(dev, R592_FIFO_DMA_SETTINGS,
+ R592_FIFO_DMA_SETTINGS_EN);
+
+ /* This is only a precation */
+ r592_write_reg(dev, R592_FIFO_DMA,
+ dev->dummy_dma_page_physical_address);
+
+ r592_clear_reg_mask(dev, R592_REG_MSC, DMA_IRQ_EN_MASK);
+ r592_clear_reg_mask(dev, R592_REG_MSC, DMA_IRQ_ACK_MASK);
+ dev->dma_error = error;
+}
+
+/* Test if hardware supports DMA */
+static void r592_check_dma(struct r592_device *dev)
+{
+ dev->dma_capable = r592_enable_dma &&
+ (r592_read_reg(dev, R592_FIFO_DMA_SETTINGS) &
+ R592_FIFO_DMA_SETTINGS_CAP);
+}
+
+/* Transfers fifo contents in/out using DMA */
+static int r592_transfer_fifo_dma(struct r592_device *dev)
+{
+ int len, sg_count;
+ bool is_write;
+
+ if (!dev->dma_capable || !dev->req->long_data)
+ return -EINVAL;
+
+ len = dev->req->sg.length;
+ is_write = dev->req->data_dir == WRITE;
+
+ if (len != R592_LFIFO_SIZE)
+ return -EINVAL;
+
+ dbg_verbose("doing dma transfer");
+
+ dev->dma_error = 0;
+ reinit_completion(&dev->dma_done);
+
+ /* TODO: hidden assumption about nenth beeing always 1 */
+ sg_count = dma_map_sg(&dev->pci_dev->dev, &dev->req->sg, 1, is_write ?
+ DMA_TO_DEVICE : DMA_FROM_DEVICE);
+
+ if (sg_count != 1 || sg_dma_len(&dev->req->sg) < R592_LFIFO_SIZE) {
+ message("problem in dma_map_sg");
+ return -EIO;
+ }
+
+ r592_start_dma(dev, is_write);
+
+ /* Wait for DMA completion */
+ if (!wait_for_completion_timeout(
+ &dev->dma_done, msecs_to_jiffies(1000))) {
+ message("DMA timeout");
+ r592_stop_dma(dev, -ETIMEDOUT);
+ }
+
+ dma_unmap_sg(&dev->pci_dev->dev, &dev->req->sg, 1, is_write ?
+ DMA_TO_DEVICE : DMA_FROM_DEVICE);
+
+ return dev->dma_error;
+}
+
+/*
+ * Writes the FIFO in 4 byte chunks.
+ * If length isn't 4 byte aligned, rest of the data if put to a fifo
+ * to be written later
+ * Use r592_flush_fifo_write to flush that fifo when writing for the
+ * last time
+ */
+static void r592_write_fifo_pio(struct r592_device *dev,
+ unsigned char *buffer, int len)
+{
+ /* flush spill from former write */
+ if (!kfifo_is_empty(&dev->pio_fifo)) {
+
+ u8 tmp[4] = {0};
+ int copy_len = kfifo_in(&dev->pio_fifo, buffer, len);
+
+ if (!kfifo_is_full(&dev->pio_fifo))
+ return;
+ len -= copy_len;
+ buffer += copy_len;
+
+ copy_len = kfifo_out(&dev->pio_fifo, tmp, 4);
+ WARN_ON(copy_len != 4);
+ r592_write_reg_raw_be(dev, R592_FIFO_PIO, *(u32 *)tmp);
+ }
+
+ WARN_ON(!kfifo_is_empty(&dev->pio_fifo));
+
+ /* write full dwords */
+ while (len >= 4) {
+ r592_write_reg_raw_be(dev, R592_FIFO_PIO, *(u32 *)buffer);
+ buffer += 4;
+ len -= 4;
+ }
+
+ /* put remaining bytes to the spill */
+ if (len)
+ kfifo_in(&dev->pio_fifo, buffer, len);
+}
+
+/* Flushes the temporary FIFO used to make aligned DWORD writes */
+static void r592_flush_fifo_write(struct r592_device *dev)
+{
+ int ret;
+ u8 buffer[4] = { 0 };
+
+ if (kfifo_is_empty(&dev->pio_fifo))
+ return;
+
+ ret = kfifo_out(&dev->pio_fifo, buffer, 4);
+ /* intentionally ignore __must_check return code */
+ (void)ret;
+ r592_write_reg_raw_be(dev, R592_FIFO_PIO, *(u32 *)buffer);
+}
+
+/*
+ * Read a fifo in 4 bytes chunks.
+ * If input doesn't fit the buffer, it places bytes of last dword in spill
+ * buffer, so that they don't get lost on last read, just throw these away.
+ */
+static void r592_read_fifo_pio(struct r592_device *dev,
+ unsigned char *buffer, int len)
+{
+ u8 tmp[4];
+
+ /* Read from last spill */
+ if (!kfifo_is_empty(&dev->pio_fifo)) {
+ int bytes_copied =
+ kfifo_out(&dev->pio_fifo, buffer, min(4, len));
+ buffer += bytes_copied;
+ len -= bytes_copied;
+
+ if (!kfifo_is_empty(&dev->pio_fifo))
+ return;
+ }
+
+ /* Reads dwords from FIFO */
+ while (len >= 4) {
+ *(u32 *)buffer = r592_read_reg_raw_be(dev, R592_FIFO_PIO);
+ buffer += 4;
+ len -= 4;
+ }
+
+ if (len) {
+ *(u32 *)tmp = r592_read_reg_raw_be(dev, R592_FIFO_PIO);
+ kfifo_in(&dev->pio_fifo, tmp, 4);
+ len -= kfifo_out(&dev->pio_fifo, buffer, len);
+ }
+
+ WARN_ON(len);
+ return;
+}
+
+/* Transfers actual data using PIO. */
+static int r592_transfer_fifo_pio(struct r592_device *dev)
+{
+ unsigned long flags;
+
+ bool is_write = dev->req->tpc >= MS_TPC_SET_RW_REG_ADRS;
+ struct sg_mapping_iter miter;
+
+ kfifo_reset(&dev->pio_fifo);
+
+ if (!dev->req->long_data) {
+ if (is_write) {
+ r592_write_fifo_pio(dev, dev->req->data,
+ dev->req->data_len);
+ r592_flush_fifo_write(dev);
+ } else
+ r592_read_fifo_pio(dev, dev->req->data,
+ dev->req->data_len);
+ return 0;
+ }
+
+ local_irq_save(flags);
+ sg_miter_start(&miter, &dev->req->sg, 1, SG_MITER_ATOMIC |
+ (is_write ? SG_MITER_FROM_SG : SG_MITER_TO_SG));
+
+ /* Do the transfer fifo<->memory*/
+ while (sg_miter_next(&miter))
+ if (is_write)
+ r592_write_fifo_pio(dev, miter.addr, miter.length);
+ else
+ r592_read_fifo_pio(dev, miter.addr, miter.length);
+
+
+ /* Write last few non aligned bytes*/
+ if (is_write)
+ r592_flush_fifo_write(dev);
+
+ sg_miter_stop(&miter);
+ local_irq_restore(flags);
+ return 0;
+}
+
+/* Executes one TPC (data is read/written from small or large fifo) */
+static void r592_execute_tpc(struct r592_device *dev)
+{
+ bool is_write;
+ int len, error;
+ u32 status, reg;
+
+ if (!dev->req) {
+ message("BUG: tpc execution without request!");
+ return;
+ }
+
+ is_write = dev->req->tpc >= MS_TPC_SET_RW_REG_ADRS;
+ len = dev->req->long_data ?
+ dev->req->sg.length : dev->req->data_len;
+
+ /* Ensure that FIFO can hold the input data */
+ if (len > R592_LFIFO_SIZE) {
+ message("IO: hardware doesn't support TPCs longer that 512");
+ error = -ENOSYS;
+ goto out;
+ }
+
+ if (!(r592_read_reg(dev, R592_REG_MSC) & R592_REG_MSC_PRSNT)) {
+ dbg("IO: refusing to send TPC because card is absent");
+ error = -ENODEV;
+ goto out;
+ }
+
+ dbg("IO: executing %s LEN=%d",
+ memstick_debug_get_tpc_name(dev->req->tpc), len);
+
+ /* Set IO direction */
+ if (is_write)
+ r592_set_reg_mask(dev, R592_IO, R592_IO_DIRECTION);
+ else
+ r592_clear_reg_mask(dev, R592_IO, R592_IO_DIRECTION);
+
+
+ error = r592_test_fifo_empty(dev);
+ if (error)
+ goto out;
+
+ /* Transfer write data */
+ if (is_write) {
+ error = r592_transfer_fifo_dma(dev);
+ if (error == -EINVAL)
+ error = r592_transfer_fifo_pio(dev);
+ }
+
+ if (error)
+ goto out;
+
+ /* Trigger the TPC */
+ reg = (len << R592_TPC_EXEC_LEN_SHIFT) |
+ (dev->req->tpc << R592_TPC_EXEC_TPC_SHIFT) |
+ R592_TPC_EXEC_BIG_FIFO;
+
+ r592_write_reg(dev, R592_TPC_EXEC, reg);
+
+ /* Wait for TPC completion */
+ status = R592_STATUS_RDY;
+ if (dev->req->need_card_int)
+ status |= R592_STATUS_CED;
+
+ error = r592_wait_status(dev, status, status);
+ if (error) {
+ message("card didn't respond");
+ goto out;
+ }
+
+ /* Test IO errors */
+ error = r592_test_io_error(dev);
+ if (error) {
+ dbg("IO error");
+ goto out;
+ }
+
+ /* Read data from FIFO */
+ if (!is_write) {
+ error = r592_transfer_fifo_dma(dev);
+ if (error == -EINVAL)
+ error = r592_transfer_fifo_pio(dev);
+ }
+
+ /* read INT reg. This can be shortened with shifts, but that way
+ its more readable */
+ if (dev->parallel_mode && dev->req->need_card_int) {
+
+ dev->req->int_reg = 0;
+ status = r592_read_reg(dev, R592_STATUS);
+
+ if (status & R592_STATUS_P_CMDNACK)
+ dev->req->int_reg |= MEMSTICK_INT_CMDNAK;
+ if (status & R592_STATUS_P_BREQ)
+ dev->req->int_reg |= MEMSTICK_INT_BREQ;
+ if (status & R592_STATUS_P_INTERR)
+ dev->req->int_reg |= MEMSTICK_INT_ERR;
+ if (status & R592_STATUS_P_CED)
+ dev->req->int_reg |= MEMSTICK_INT_CED;
+ }
+
+ if (error)
+ dbg("FIFO read error");
+out:
+ dev->req->error = error;
+ r592_clear_reg_mask(dev, R592_REG_MSC, R592_REG_MSC_LED);
+ return;
+}
+
+/* Main request processing thread */
+static int r592_process_thread(void *data)
+{
+ int error;
+ struct r592_device *dev = (struct r592_device *)data;
+ unsigned long flags;
+
+ while (!kthread_should_stop()) {
+ spin_lock_irqsave(&dev->io_thread_lock, flags);
+ set_current_state(TASK_INTERRUPTIBLE);
+ error = memstick_next_req(dev->host, &dev->req);
+ spin_unlock_irqrestore(&dev->io_thread_lock, flags);
+
+ if (error) {
+ if (error == -ENXIO || error == -EAGAIN) {
+ dbg_verbose("IO: done IO, sleeping");
+ } else {
+ dbg("IO: unknown error from "
+ "memstick_next_req %d", error);
+ }
+
+ if (kthread_should_stop())
+ set_current_state(TASK_RUNNING);
+
+ schedule();
+ } else {
+ set_current_state(TASK_RUNNING);
+ r592_execute_tpc(dev);
+ }
+ }
+ return 0;
+}
+
+/* Reprogram chip to detect change in card state */
+/* eg, if card is detected, arm it to detect removal, and vice versa */
+static void r592_update_card_detect(struct r592_device *dev)
+{
+ u32 reg = r592_read_reg(dev, R592_REG_MSC);
+ bool card_detected = reg & R592_REG_MSC_PRSNT;
+
+ dbg("update card detect. card state: %s", card_detected ?
+ "present" : "absent");
+
+ reg &= ~((R592_REG_MSC_IRQ_REMOVE | R592_REG_MSC_IRQ_INSERT) << 16);
+
+ if (card_detected)
+ reg |= (R592_REG_MSC_IRQ_REMOVE << 16);
+ else
+ reg |= (R592_REG_MSC_IRQ_INSERT << 16);
+
+ r592_write_reg(dev, R592_REG_MSC, reg);
+}
+
+/* Timer routine that fires 1 second after last card detection event, */
+static void r592_detect_timer(struct timer_list *t)
+{
+ struct r592_device *dev = from_timer(dev, t, detect_timer);
+ r592_update_card_detect(dev);
+ memstick_detect_change(dev->host);
+}
+
+/* Interrupt handler */
+static irqreturn_t r592_irq(int irq, void *data)
+{
+ struct r592_device *dev = (struct r592_device *)data;
+ irqreturn_t ret = IRQ_NONE;
+ u32 reg;
+ u16 irq_enable, irq_status;
+ unsigned long flags;
+ int error;
+
+ spin_lock_irqsave(&dev->irq_lock, flags);
+
+ reg = r592_read_reg(dev, R592_REG_MSC);
+ irq_enable = reg >> 16;
+ irq_status = reg & 0xFFFF;
+
+ /* Ack the interrupts */
+ reg &= ~irq_status;
+ r592_write_reg(dev, R592_REG_MSC, reg);
+
+ /* Get the IRQ status minus bits that aren't enabled */
+ irq_status &= (irq_enable);
+
+ /* Due to limitation of memstick core, we don't look at bits that
+ indicate that card was removed/inserted and/or present */
+ if (irq_status & (R592_REG_MSC_IRQ_INSERT | R592_REG_MSC_IRQ_REMOVE)) {
+
+ bool card_was_added = irq_status & R592_REG_MSC_IRQ_INSERT;
+ ret = IRQ_HANDLED;
+
+ message("IRQ: card %s", card_was_added ? "added" : "removed");
+
+ mod_timer(&dev->detect_timer,
+ jiffies + msecs_to_jiffies(card_was_added ? 500 : 50));
+ }
+
+ if (irq_status &
+ (R592_REG_MSC_FIFO_DMA_DONE | R592_REG_MSC_FIFO_DMA_ERR)) {
+ ret = IRQ_HANDLED;
+
+ if (irq_status & R592_REG_MSC_FIFO_DMA_ERR) {
+ message("IRQ: DMA error");
+ error = -EIO;
+ } else {
+ dbg_verbose("IRQ: dma done");
+ error = 0;
+ }
+
+ r592_stop_dma(dev, error);
+ complete(&dev->dma_done);
+ }
+
+ spin_unlock_irqrestore(&dev->irq_lock, flags);
+ return ret;
+}
+
+/* External inteface: set settings */
+static int r592_set_param(struct memstick_host *host,
+ enum memstick_param param, int value)
+{
+ struct r592_device *dev = memstick_priv(host);
+
+ switch (param) {
+ case MEMSTICK_POWER:
+ switch (value) {
+ case MEMSTICK_POWER_ON:
+ return r592_enable_device(dev, true);
+ case MEMSTICK_POWER_OFF:
+ return r592_enable_device(dev, false);
+ default:
+ return -EINVAL;
+ }
+ case MEMSTICK_INTERFACE:
+ switch (value) {
+ case MEMSTICK_SERIAL:
+ return r592_set_mode(dev, 0);
+ case MEMSTICK_PAR4:
+ return r592_set_mode(dev, 1);
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+/* External interface: submit requests */
+static void r592_submit_req(struct memstick_host *host)
+{
+ struct r592_device *dev = memstick_priv(host);
+ unsigned long flags;
+
+ if (dev->req)
+ return;
+
+ spin_lock_irqsave(&dev->io_thread_lock, flags);
+ if (wake_up_process(dev->io_thread))
+ dbg_verbose("IO thread woken to process requests");
+ spin_unlock_irqrestore(&dev->io_thread_lock, flags);
+}
+
+static const struct pci_device_id r592_pci_id_tbl[] = {
+
+ { PCI_VDEVICE(RICOH, 0x0592), },
+ { },
+};
+
+/* Main entry */
+static int r592_probe(struct pci_dev *pdev, const struct pci_device_id *id)
+{
+ int error = -ENOMEM;
+ struct memstick_host *host;
+ struct r592_device *dev;
+
+ /* Allocate memory */
+ host = memstick_alloc_host(sizeof(struct r592_device), &pdev->dev);
+ if (!host)
+ goto error1;
+
+ dev = memstick_priv(host);
+ dev->host = host;
+ dev->pci_dev = pdev;
+ pci_set_drvdata(pdev, dev);
+
+ /* pci initialization */
+ error = pci_enable_device(pdev);
+ if (error)
+ goto error2;
+
+ pci_set_master(pdev);
+ error = dma_set_mask(&pdev->dev, DMA_BIT_MASK(32));
+ if (error)
+ goto error3;
+
+ error = pci_request_regions(pdev, DRV_NAME);
+ if (error)
+ goto error3;
+
+ dev->mmio = pci_ioremap_bar(pdev, 0);
+ if (!dev->mmio) {
+ error = -ENOMEM;
+ goto error4;
+ }
+
+ dev->irq = pdev->irq;
+ spin_lock_init(&dev->irq_lock);
+ spin_lock_init(&dev->io_thread_lock);
+ init_completion(&dev->dma_done);
+ INIT_KFIFO(dev->pio_fifo);
+ timer_setup(&dev->detect_timer, r592_detect_timer, 0);
+
+ /* Host initialization */
+ host->caps = MEMSTICK_CAP_PAR4;
+ host->request = r592_submit_req;
+ host->set_param = r592_set_param;
+ r592_check_dma(dev);
+
+ dev->io_thread = kthread_run(r592_process_thread, dev, "r592_io");
+ if (IS_ERR(dev->io_thread)) {
+ error = PTR_ERR(dev->io_thread);
+ goto error5;
+ }
+
+ /* This is just a precation, so don't fail */
+ dev->dummy_dma_page = dma_alloc_coherent(&pdev->dev, PAGE_SIZE,
+ &dev->dummy_dma_page_physical_address, GFP_KERNEL);
+ r592_stop_dma(dev , 0);
+
+ error = request_irq(dev->irq, &r592_irq, IRQF_SHARED,
+ DRV_NAME, dev);
+ if (error)
+ goto error6;
+
+ r592_update_card_detect(dev);
+ error = memstick_add_host(host);
+ if (error)
+ goto error7;
+
+ message("driver successfully loaded");
+ return 0;
+error7:
+ free_irq(dev->irq, dev);
+error6:
+ if (dev->dummy_dma_page)
+ dma_free_coherent(&pdev->dev, PAGE_SIZE, dev->dummy_dma_page,
+ dev->dummy_dma_page_physical_address);
+
+ kthread_stop(dev->io_thread);
+error5:
+ iounmap(dev->mmio);
+error4:
+ pci_release_regions(pdev);
+error3:
+ pci_disable_device(pdev);
+error2:
+ memstick_free_host(host);
+error1:
+ return error;
+}
+
+static void r592_remove(struct pci_dev *pdev)
+{
+ int error = 0;
+ struct r592_device *dev = pci_get_drvdata(pdev);
+
+ /* Stop the processing thread.
+ That ensures that we won't take any more requests */
+ kthread_stop(dev->io_thread);
+ del_timer_sync(&dev->detect_timer);
+ r592_enable_device(dev, false);
+
+ while (!error && dev->req) {
+ dev->req->error = -ETIME;
+ error = memstick_next_req(dev->host, &dev->req);
+ }
+ memstick_remove_host(dev->host);
+
+ if (dev->dummy_dma_page)
+ dma_free_coherent(&pdev->dev, PAGE_SIZE, dev->dummy_dma_page,
+ dev->dummy_dma_page_physical_address);
+
+ free_irq(dev->irq, dev);
+ iounmap(dev->mmio);
+ pci_release_regions(pdev);
+ pci_disable_device(pdev);
+ memstick_free_host(dev->host);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int r592_suspend(struct device *core_dev)
+{
+ struct r592_device *dev = dev_get_drvdata(core_dev);
+
+ r592_clear_interrupts(dev);
+ memstick_suspend_host(dev->host);
+ del_timer_sync(&dev->detect_timer);
+ return 0;
+}
+
+static int r592_resume(struct device *core_dev)
+{
+ struct r592_device *dev = dev_get_drvdata(core_dev);
+
+ r592_clear_interrupts(dev);
+ r592_enable_device(dev, false);
+ memstick_resume_host(dev->host);
+ r592_update_card_detect(dev);
+ return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(r592_pm_ops, r592_suspend, r592_resume);
+
+MODULE_DEVICE_TABLE(pci, r592_pci_id_tbl);
+
+static struct pci_driver r592_pci_driver = {
+ .name = DRV_NAME,
+ .id_table = r592_pci_id_tbl,
+ .probe = r592_probe,
+ .remove = r592_remove,
+ .driver.pm = &r592_pm_ops,
+};
+
+module_pci_driver(r592_pci_driver);
+
+module_param_named(enable_dma, r592_enable_dma, bool, S_IRUGO);
+MODULE_PARM_DESC(enable_dma, "Enable usage of the DMA (default)");
+module_param(debug, int, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(debug, "Debug level (0-3)");
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Maxim Levitsky <maximlevitsky@gmail.com>");
+MODULE_DESCRIPTION("Ricoh R5C592 Memstick/Memstick PRO card reader driver");
diff --git a/drivers/memstick/host/r592.h b/drivers/memstick/host/r592.h
new file mode 100644
index 000000000..c161db70c
--- /dev/null
+++ b/drivers/memstick/host/r592.h
@@ -0,0 +1,172 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2010 - Maxim Levitsky
+ * driver for Ricoh memstick readers
+ */
+
+#ifndef R592_H
+
+#include <linux/memstick.h>
+#include <linux/spinlock.h>
+#include <linux/interrupt.h>
+#include <linux/workqueue.h>
+#include <linux/kfifo.h>
+#include <linux/ctype.h>
+
+/* write to this reg (number,len) triggers TPC execution */
+#define R592_TPC_EXEC 0x00
+#define R592_TPC_EXEC_LEN_SHIFT 16 /* Bits 16..25 are TPC len */
+#define R592_TPC_EXEC_BIG_FIFO (1 << 26) /* If bit 26 is set, large fifo is used (reg 48) */
+#define R592_TPC_EXEC_TPC_SHIFT 28 /* Bits 28..31 are the TPC number */
+
+
+/* Window for small TPC fifo (big endian)*/
+/* reads and writes always are done in 8 byte chunks */
+/* Not used in driver, because large fifo does better job */
+#define R592_SFIFO 0x08
+
+
+/* Status register (ms int, small fifo, IO)*/
+#define R592_STATUS 0x10
+ /* Parallel INT bits */
+#define R592_STATUS_P_CMDNACK (1 << 16) /* INT reg: NACK (parallel mode) */
+#define R592_STATUS_P_BREQ (1 << 17) /* INT reg: card ready (parallel mode)*/
+#define R592_STATUS_P_INTERR (1 << 18) /* INT reg: int error (parallel mode)*/
+#define R592_STATUS_P_CED (1 << 19) /* INT reg: command done (parallel mode) */
+
+ /* Fifo status */
+#define R592_STATUS_SFIFO_FULL (1 << 20) /* Small Fifo almost full (last chunk is written) */
+#define R592_STATUS_SFIFO_EMPTY (1 << 21) /* Small Fifo empty */
+
+ /* Error detection via CRC */
+#define R592_STATUS_SEND_ERR (1 << 24) /* Send failed */
+#define R592_STATUS_RECV_ERR (1 << 25) /* Receive failed */
+
+ /* Card state */
+#define R592_STATUS_RDY (1 << 28) /* RDY signal received */
+#define R592_STATUS_CED (1 << 29) /* INT: Command done (serial mode)*/
+#define R592_STATUS_SFIFO_INPUT (1 << 30) /* Small fifo received data*/
+
+#define R592_SFIFO_SIZE 32 /* total size of small fifo is 32 bytes */
+#define R592_SFIFO_PACKET 8 /* packet size of small fifo */
+
+/* IO control */
+#define R592_IO 0x18
+#define R592_IO_16 (1 << 16) /* Set by default, can be cleared */
+#define R592_IO_18 (1 << 18) /* Set by default, can be cleared */
+#define R592_IO_SERIAL1 (1 << 20) /* Set by default, can be cleared, (cleared on parallel) */
+#define R592_IO_22 (1 << 22) /* Set by default, can be cleared */
+#define R592_IO_DIRECTION (1 << 24) /* TPC direction (1 write 0 read) */
+#define R592_IO_26 (1 << 26) /* Set by default, can be cleared */
+#define R592_IO_SERIAL2 (1 << 30) /* Set by default, can be cleared (cleared on parallel), serial doesn't work if unset */
+#define R592_IO_RESET (1 << 31) /* Reset, sets defaults*/
+
+
+/* Turns hardware on/off */
+#define R592_POWER 0x20 /* bits 0-7 writeable */
+#define R592_POWER_0 (1 << 0) /* set on start, cleared on stop - must be set*/
+#define R592_POWER_1 (1 << 1) /* set on start, cleared on stop - must be set*/
+#define R592_POWER_3 (1 << 3) /* must be clear */
+#define R592_POWER_20 (1 << 5) /* set before switch to parallel */
+
+/* IO mode*/
+#define R592_IO_MODE 0x24
+#define R592_IO_MODE_SERIAL 1
+#define R592_IO_MODE_PARALLEL 3
+
+
+/* IRQ,card detection,large fifo (first word irq status, second enable) */
+/* IRQs are ACKed by clearing the bits */
+#define R592_REG_MSC 0x28
+#define R592_REG_MSC_PRSNT (1 << 1) /* card present (only status)*/
+#define R592_REG_MSC_IRQ_INSERT (1 << 8) /* detect insert / card insered */
+#define R592_REG_MSC_IRQ_REMOVE (1 << 9) /* detect removal / card removed */
+#define R592_REG_MSC_FIFO_EMPTY (1 << 10) /* fifo is empty */
+#define R592_REG_MSC_FIFO_DMA_DONE (1 << 11) /* dma enable / dma done */
+
+#define R592_REG_MSC_FIFO_USER_ORN (1 << 12) /* set if software reads empty fifo (if R592_REG_MSC_FIFO_EMPTY is set) */
+#define R592_REG_MSC_FIFO_MISMATH (1 << 13) /* set if amount of data in fifo doesn't match amount in TPC */
+#define R592_REG_MSC_FIFO_DMA_ERR (1 << 14) /* IO failure */
+#define R592_REG_MSC_LED (1 << 15) /* clear to turn led off (only status)*/
+
+#define DMA_IRQ_ACK_MASK \
+ (R592_REG_MSC_FIFO_DMA_DONE | R592_REG_MSC_FIFO_DMA_ERR)
+
+#define DMA_IRQ_EN_MASK (DMA_IRQ_ACK_MASK << 16)
+
+#define IRQ_ALL_ACK_MASK 0x00007F00
+#define IRQ_ALL_EN_MASK (IRQ_ALL_ACK_MASK << 16)
+
+/* DMA address for large FIFO read/writes*/
+#define R592_FIFO_DMA 0x2C
+
+/* PIO access to large FIFO (512 bytes) (big endian)*/
+#define R592_FIFO_PIO 0x30
+#define R592_LFIFO_SIZE 512 /* large fifo size */
+
+
+/* large FIFO DMA settings */
+#define R592_FIFO_DMA_SETTINGS 0x34
+#define R592_FIFO_DMA_SETTINGS_EN (1 << 0) /* DMA enabled */
+#define R592_FIFO_DMA_SETTINGS_DIR (1 << 1) /* Dma direction (1 read, 0 write) */
+#define R592_FIFO_DMA_SETTINGS_CAP (1 << 24) /* Dma is aviable */
+
+/* Maybe just an delay */
+/* Bits 17..19 are just number */
+/* bit 16 is set, then bit 20 is waited */
+/* time to wait is about 50 spins * 2 ^ (bits 17..19) */
+/* seems to be possible just to ignore */
+/* Probably debug register */
+#define R592_REG38 0x38
+#define R592_REG38_CHANGE (1 << 16) /* Start bit */
+#define R592_REG38_DONE (1 << 20) /* HW set this after the delay */
+#define R592_REG38_SHIFT 17
+
+/* Debug register, written (0xABCDEF00) when error happens - not used*/
+#define R592_REG_3C 0x3C
+
+struct r592_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;
+ spinlock_t irq_lock;
+ spinlock_t io_thread_lock;
+ struct timer_list detect_timer;
+
+ struct task_struct *io_thread;
+ bool parallel_mode;
+
+ DECLARE_KFIFO(pio_fifo, u8, sizeof(u32));
+
+ /* DMA area */
+ int dma_capable;
+ int dma_error;
+ struct completion dma_done;
+ void *dummy_dma_page;
+ dma_addr_t dummy_dma_page_physical_address;
+
+};
+
+#define DRV_NAME "r592"
+
+
+#define message(format, ...) \
+ printk(KERN_INFO DRV_NAME ": " format "\n", ## __VA_ARGS__)
+
+#define __dbg(level, format, ...) \
+ do { \
+ if (debug >= level) \
+ printk(KERN_DEBUG DRV_NAME \
+ ": " format "\n", ## __VA_ARGS__); \
+ } while (0)
+
+
+#define dbg(format, ...) __dbg(1, format, ## __VA_ARGS__)
+#define dbg_verbose(format, ...) __dbg(2, format, ## __VA_ARGS__)
+#define dbg_reg(format, ...) __dbg(3, format, ## __VA_ARGS__)
+
+#endif
diff --git a/drivers/memstick/host/rtsx_pci_ms.c b/drivers/memstick/host/rtsx_pci_ms.c
new file mode 100644
index 000000000..15720a4af
--- /dev/null
+++ b/drivers/memstick/host/rtsx_pci_ms.c
@@ -0,0 +1,643 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* Realtek PCI-Express Memstick Card Interface driver
+ *
+ * Copyright(c) 2009-2013 Realtek Semiconductor Corp. All rights reserved.
+ *
+ * Author:
+ * Wei WANG <wei_wang@realsil.com.cn>
+ */
+
+#include <linux/module.h>
+#include <linux/highmem.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/memstick.h>
+#include <linux/rtsx_pci.h>
+#include <asm/unaligned.h>
+
+struct realtek_pci_ms {
+ struct platform_device *pdev;
+ struct rtsx_pcr *pcr;
+ struct memstick_host *msh;
+ struct memstick_request *req;
+
+ struct mutex host_mutex;
+ struct work_struct handle_req;
+
+ u8 ssc_depth;
+ unsigned int clock;
+ unsigned char ifmode;
+ bool eject;
+};
+
+static inline struct device *ms_dev(struct realtek_pci_ms *host)
+{
+ return &(host->pdev->dev);
+}
+
+static inline void ms_clear_error(struct realtek_pci_ms *host)
+{
+ rtsx_pci_write_register(host->pcr, CARD_STOP,
+ MS_STOP | MS_CLR_ERR, MS_STOP | MS_CLR_ERR);
+}
+
+#ifdef DEBUG
+
+static void ms_print_debug_regs(struct realtek_pci_ms *host)
+{
+ struct rtsx_pcr *pcr = host->pcr;
+ u16 i;
+ u8 *ptr;
+
+ /* Print MS host internal registers */
+ rtsx_pci_init_cmd(pcr);
+ for (i = 0xFD40; i <= 0xFD44; i++)
+ rtsx_pci_add_cmd(pcr, READ_REG_CMD, i, 0, 0);
+ for (i = 0xFD52; i <= 0xFD69; i++)
+ rtsx_pci_add_cmd(pcr, READ_REG_CMD, i, 0, 0);
+ rtsx_pci_send_cmd(pcr, 100);
+
+ ptr = rtsx_pci_get_cmd_data(pcr);
+ for (i = 0xFD40; i <= 0xFD44; i++)
+ dev_dbg(ms_dev(host), "0x%04X: 0x%02x\n", i, *(ptr++));
+ for (i = 0xFD52; i <= 0xFD69; i++)
+ dev_dbg(ms_dev(host), "0x%04X: 0x%02x\n", i, *(ptr++));
+}
+
+#else
+
+#define ms_print_debug_regs(host)
+
+#endif
+
+static int ms_power_on(struct realtek_pci_ms *host)
+{
+ struct rtsx_pcr *pcr = host->pcr;
+ int err;
+
+ rtsx_pci_init_cmd(pcr);
+ rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_SELECT, 0x07, MS_MOD_SEL);
+ rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_SHARE_MODE,
+ CARD_SHARE_MASK, CARD_SHARE_48_MS);
+ rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_CLK_EN,
+ MS_CLK_EN, MS_CLK_EN);
+ err = rtsx_pci_send_cmd(pcr, 100);
+ if (err < 0)
+ return err;
+
+ err = rtsx_pci_card_pull_ctl_enable(pcr, RTSX_MS_CARD);
+ if (err < 0)
+ return err;
+
+ err = rtsx_pci_card_power_on(pcr, RTSX_MS_CARD);
+ if (err < 0)
+ return err;
+
+ /* Wait ms power stable */
+ msleep(150);
+
+ err = rtsx_pci_write_register(pcr, CARD_OE,
+ MS_OUTPUT_EN, MS_OUTPUT_EN);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+
+static int ms_power_off(struct realtek_pci_ms *host)
+{
+ struct rtsx_pcr *pcr = host->pcr;
+ int err;
+
+ rtsx_pci_init_cmd(pcr);
+
+ rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_CLK_EN, MS_CLK_EN, 0);
+ rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_OE, MS_OUTPUT_EN, 0);
+
+ err = rtsx_pci_send_cmd(pcr, 100);
+ if (err < 0)
+ return err;
+
+ err = rtsx_pci_card_power_off(pcr, RTSX_MS_CARD);
+ if (err < 0)
+ return err;
+
+ return rtsx_pci_card_pull_ctl_disable(pcr, RTSX_MS_CARD);
+}
+
+static int ms_transfer_data(struct realtek_pci_ms *host, unsigned char data_dir,
+ u8 tpc, u8 cfg, struct scatterlist *sg)
+{
+ struct rtsx_pcr *pcr = host->pcr;
+ int err;
+ unsigned int length = sg->length;
+ u16 sec_cnt = (u16)(length / 512);
+ u8 val, trans_mode, dma_dir;
+ struct memstick_dev *card = host->msh->card;
+ bool pro_card = card->id.type == MEMSTICK_TYPE_PRO;
+
+ dev_dbg(ms_dev(host), "%s: tpc = 0x%02x, data_dir = %s, length = %d\n",
+ __func__, tpc, (data_dir == READ) ? "READ" : "WRITE",
+ length);
+
+ if (data_dir == READ) {
+ dma_dir = DMA_DIR_FROM_CARD;
+ trans_mode = pro_card ? MS_TM_AUTO_READ : MS_TM_NORMAL_READ;
+ } else {
+ dma_dir = DMA_DIR_TO_CARD;
+ trans_mode = pro_card ? MS_TM_AUTO_WRITE : MS_TM_NORMAL_WRITE;
+ }
+
+ rtsx_pci_init_cmd(pcr);
+
+ rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, MS_TPC, 0xFF, tpc);
+ if (pro_card) {
+ rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, MS_SECTOR_CNT_H,
+ 0xFF, (u8)(sec_cnt >> 8));
+ rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, MS_SECTOR_CNT_L,
+ 0xFF, (u8)sec_cnt);
+ }
+ rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, MS_TRANS_CFG, 0xFF, cfg);
+
+ rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, IRQSTAT0,
+ DMA_DONE_INT, DMA_DONE_INT);
+ rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, DMATC3, 0xFF, (u8)(length >> 24));
+ rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, DMATC2, 0xFF, (u8)(length >> 16));
+ rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, DMATC1, 0xFF, (u8)(length >> 8));
+ rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, DMATC0, 0xFF, (u8)length);
+ rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, DMACTL,
+ 0x03 | DMA_PACK_SIZE_MASK, dma_dir | DMA_EN | DMA_512);
+ rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_DATA_SOURCE,
+ 0x01, RING_BUFFER);
+
+ rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, MS_TRANSFER,
+ 0xFF, MS_TRANSFER_START | trans_mode);
+ rtsx_pci_add_cmd(pcr, CHECK_REG_CMD, MS_TRANSFER,
+ MS_TRANSFER_END, MS_TRANSFER_END);
+
+ rtsx_pci_send_cmd_no_wait(pcr);
+
+ err = rtsx_pci_transfer_data(pcr, sg, 1, data_dir == READ, 10000);
+ if (err < 0) {
+ ms_clear_error(host);
+ return err;
+ }
+
+ rtsx_pci_read_register(pcr, MS_TRANS_CFG, &val);
+ if (pro_card) {
+ if (val & (MS_INT_CMDNK | MS_INT_ERR |
+ MS_CRC16_ERR | MS_RDY_TIMEOUT))
+ return -EIO;
+ } else {
+ if (val & (MS_CRC16_ERR | MS_RDY_TIMEOUT))
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int ms_write_bytes(struct realtek_pci_ms *host, u8 tpc,
+ u8 cfg, u8 cnt, u8 *data, u8 *int_reg)
+{
+ struct rtsx_pcr *pcr = host->pcr;
+ int err, i;
+
+ dev_dbg(ms_dev(host), "%s: tpc = 0x%02x\n", __func__, tpc);
+
+ if (!data)
+ return -EINVAL;
+
+ rtsx_pci_init_cmd(pcr);
+
+ for (i = 0; i < cnt; i++)
+ rtsx_pci_add_cmd(pcr, WRITE_REG_CMD,
+ PPBUF_BASE2 + i, 0xFF, data[i]);
+ if (cnt % 2)
+ rtsx_pci_add_cmd(pcr, WRITE_REG_CMD,
+ PPBUF_BASE2 + i, 0xFF, 0xFF);
+
+ rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, MS_TPC, 0xFF, tpc);
+ rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, MS_BYTE_CNT, 0xFF, cnt);
+ rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, MS_TRANS_CFG, 0xFF, cfg);
+ rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_DATA_SOURCE,
+ 0x01, PINGPONG_BUFFER);
+
+ rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, MS_TRANSFER,
+ 0xFF, MS_TRANSFER_START | MS_TM_WRITE_BYTES);
+ rtsx_pci_add_cmd(pcr, CHECK_REG_CMD, MS_TRANSFER,
+ MS_TRANSFER_END, MS_TRANSFER_END);
+ if (int_reg)
+ rtsx_pci_add_cmd(pcr, READ_REG_CMD, MS_TRANS_CFG, 0, 0);
+
+ err = rtsx_pci_send_cmd(pcr, 5000);
+ if (err < 0) {
+ u8 val;
+
+ rtsx_pci_read_register(pcr, MS_TRANS_CFG, &val);
+ dev_dbg(ms_dev(host), "MS_TRANS_CFG: 0x%02x\n", val);
+
+ if (int_reg)
+ *int_reg = val & 0x0F;
+
+ ms_print_debug_regs(host);
+
+ ms_clear_error(host);
+
+ if (!(tpc & 0x08)) {
+ if (val & MS_CRC16_ERR)
+ return -EIO;
+ } else {
+ if (!(val & 0x80)) {
+ if (val & (MS_INT_ERR | MS_INT_CMDNK))
+ return -EIO;
+ }
+ }
+
+ return -ETIMEDOUT;
+ }
+
+ if (int_reg) {
+ u8 *ptr = rtsx_pci_get_cmd_data(pcr) + 1;
+ *int_reg = *ptr & 0x0F;
+ }
+
+ return 0;
+}
+
+static int ms_read_bytes(struct realtek_pci_ms *host, u8 tpc,
+ u8 cfg, u8 cnt, u8 *data, u8 *int_reg)
+{
+ struct rtsx_pcr *pcr = host->pcr;
+ int err, i;
+ u8 *ptr;
+
+ dev_dbg(ms_dev(host), "%s: tpc = 0x%02x\n", __func__, tpc);
+
+ if (!data)
+ return -EINVAL;
+
+ rtsx_pci_init_cmd(pcr);
+
+ rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, MS_TPC, 0xFF, tpc);
+ rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, MS_BYTE_CNT, 0xFF, cnt);
+ rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, MS_TRANS_CFG, 0xFF, cfg);
+ rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_DATA_SOURCE,
+ 0x01, PINGPONG_BUFFER);
+
+ rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, MS_TRANSFER,
+ 0xFF, MS_TRANSFER_START | MS_TM_READ_BYTES);
+ rtsx_pci_add_cmd(pcr, CHECK_REG_CMD, MS_TRANSFER,
+ MS_TRANSFER_END, MS_TRANSFER_END);
+ for (i = 0; i < cnt - 1; i++)
+ rtsx_pci_add_cmd(pcr, READ_REG_CMD, PPBUF_BASE2 + i, 0, 0);
+ if (cnt % 2)
+ rtsx_pci_add_cmd(pcr, READ_REG_CMD, PPBUF_BASE2 + cnt, 0, 0);
+ else
+ rtsx_pci_add_cmd(pcr, READ_REG_CMD,
+ PPBUF_BASE2 + cnt - 1, 0, 0);
+ if (int_reg)
+ rtsx_pci_add_cmd(pcr, READ_REG_CMD, MS_TRANS_CFG, 0, 0);
+
+ err = rtsx_pci_send_cmd(pcr, 5000);
+ if (err < 0) {
+ u8 val;
+
+ rtsx_pci_read_register(pcr, MS_TRANS_CFG, &val);
+ dev_dbg(ms_dev(host), "MS_TRANS_CFG: 0x%02x\n", val);
+
+ if (int_reg)
+ *int_reg = val & 0x0F;
+
+ ms_print_debug_regs(host);
+
+ ms_clear_error(host);
+
+ if (!(tpc & 0x08)) {
+ if (val & MS_CRC16_ERR)
+ return -EIO;
+ } else {
+ if (!(val & 0x80)) {
+ if (val & (MS_INT_ERR | MS_INT_CMDNK))
+ return -EIO;
+ }
+ }
+
+ return -ETIMEDOUT;
+ }
+
+ ptr = rtsx_pci_get_cmd_data(pcr) + 1;
+ for (i = 0; i < cnt; i++)
+ data[i] = *ptr++;
+
+ if (int_reg)
+ *int_reg = *ptr & 0x0F;
+
+ return 0;
+}
+
+static int rtsx_pci_ms_issue_cmd(struct realtek_pci_ms *host)
+{
+ struct memstick_request *req = host->req;
+ int err = 0;
+ u8 cfg = 0, int_reg;
+
+ dev_dbg(ms_dev(host), "%s\n", __func__);
+
+ if (req->need_card_int) {
+ if (host->ifmode != MEMSTICK_SERIAL)
+ cfg = WAIT_INT;
+ }
+
+ if (req->long_data) {
+ err = ms_transfer_data(host, req->data_dir,
+ req->tpc, cfg, &(req->sg));
+ } else {
+ if (req->data_dir == READ) {
+ err = ms_read_bytes(host, req->tpc, cfg,
+ req->data_len, req->data, &int_reg);
+ } else {
+ err = ms_write_bytes(host, req->tpc, cfg,
+ req->data_len, req->data, &int_reg);
+ }
+ }
+ if (err < 0)
+ return err;
+
+ if (req->need_card_int && (host->ifmode == MEMSTICK_SERIAL)) {
+ err = ms_read_bytes(host, MS_TPC_GET_INT,
+ NO_WAIT_INT, 1, &int_reg, NULL);
+ if (err < 0)
+ return err;
+ }
+
+ if (req->need_card_int) {
+ dev_dbg(ms_dev(host), "int_reg: 0x%02x\n", int_reg);
+
+ if (int_reg & MS_INT_CMDNK)
+ req->int_reg |= MEMSTICK_INT_CMDNAK;
+ if (int_reg & MS_INT_BREQ)
+ req->int_reg |= MEMSTICK_INT_BREQ;
+ if (int_reg & MS_INT_ERR)
+ req->int_reg |= MEMSTICK_INT_ERR;
+ if (int_reg & MS_INT_CED)
+ req->int_reg |= MEMSTICK_INT_CED;
+ }
+
+ return 0;
+}
+
+static void rtsx_pci_ms_handle_req(struct work_struct *work)
+{
+ struct realtek_pci_ms *host = container_of(work,
+ struct realtek_pci_ms, handle_req);
+ struct rtsx_pcr *pcr = host->pcr;
+ struct memstick_host *msh = host->msh;
+ int rc;
+
+ mutex_lock(&pcr->pcr_mutex);
+
+ rtsx_pci_start_run(pcr);
+
+ rtsx_pci_switch_clock(host->pcr, host->clock, host->ssc_depth,
+ false, true, false);
+ rtsx_pci_write_register(pcr, CARD_SELECT, 0x07, MS_MOD_SEL);
+ rtsx_pci_write_register(pcr, CARD_SHARE_MODE,
+ CARD_SHARE_MASK, CARD_SHARE_48_MS);
+
+ if (!host->req) {
+ do {
+ rc = memstick_next_req(msh, &host->req);
+ dev_dbg(ms_dev(host), "next req %d\n", rc);
+
+ if (!rc)
+ host->req->error = rtsx_pci_ms_issue_cmd(host);
+ } while (!rc);
+ }
+
+ mutex_unlock(&pcr->pcr_mutex);
+}
+
+static void rtsx_pci_ms_request(struct memstick_host *msh)
+{
+ struct realtek_pci_ms *host = memstick_priv(msh);
+
+ dev_dbg(ms_dev(host), "--> %s\n", __func__);
+
+ if (rtsx_pci_card_exclusive_check(host->pcr, RTSX_MS_CARD))
+ return;
+
+ schedule_work(&host->handle_req);
+}
+
+static int rtsx_pci_ms_set_param(struct memstick_host *msh,
+ enum memstick_param param, int value)
+{
+ struct realtek_pci_ms *host = memstick_priv(msh);
+ struct rtsx_pcr *pcr = host->pcr;
+ unsigned int clock = 0;
+ u8 ssc_depth = 0;
+ int err;
+
+ dev_dbg(ms_dev(host), "%s: param = %d, value = %d\n",
+ __func__, param, value);
+
+ err = rtsx_pci_card_exclusive_check(host->pcr, RTSX_MS_CARD);
+ if (err)
+ return err;
+
+ switch (param) {
+ case MEMSTICK_POWER:
+ if (value == MEMSTICK_POWER_ON)
+ err = ms_power_on(host);
+ else if (value == MEMSTICK_POWER_OFF)
+ err = ms_power_off(host);
+ else
+ return -EINVAL;
+ break;
+
+ case MEMSTICK_INTERFACE:
+ if (value == MEMSTICK_SERIAL) {
+ clock = 19000000;
+ ssc_depth = RTSX_SSC_DEPTH_500K;
+
+ err = rtsx_pci_write_register(pcr, MS_CFG, 0x58,
+ MS_BUS_WIDTH_1 | PUSH_TIME_DEFAULT);
+ if (err < 0)
+ return err;
+ } else if (value == MEMSTICK_PAR4) {
+ clock = 39000000;
+ ssc_depth = RTSX_SSC_DEPTH_1M;
+
+ err = rtsx_pci_write_register(pcr, MS_CFG,
+ 0x58, MS_BUS_WIDTH_4 | PUSH_TIME_ODD);
+ if (err < 0)
+ return err;
+ } else {
+ return -EINVAL;
+ }
+
+ err = rtsx_pci_switch_clock(pcr, clock,
+ ssc_depth, false, true, false);
+ if (err < 0)
+ return err;
+
+ host->ssc_depth = ssc_depth;
+ host->clock = clock;
+ host->ifmode = value;
+ break;
+ }
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+
+static int rtsx_pci_ms_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct realtek_pci_ms *host = platform_get_drvdata(pdev);
+ struct memstick_host *msh = host->msh;
+
+ dev_dbg(ms_dev(host), "--> %s\n", __func__);
+
+ memstick_suspend_host(msh);
+ return 0;
+}
+
+static int rtsx_pci_ms_resume(struct platform_device *pdev)
+{
+ struct realtek_pci_ms *host = platform_get_drvdata(pdev);
+ struct memstick_host *msh = host->msh;
+
+ dev_dbg(ms_dev(host), "--> %s\n", __func__);
+
+ memstick_resume_host(msh);
+ return 0;
+}
+
+#else /* CONFIG_PM */
+
+#define rtsx_pci_ms_suspend NULL
+#define rtsx_pci_ms_resume NULL
+
+#endif /* CONFIG_PM */
+
+static void rtsx_pci_ms_card_event(struct platform_device *pdev)
+{
+ struct realtek_pci_ms *host = platform_get_drvdata(pdev);
+
+ memstick_detect_change(host->msh);
+}
+
+static int rtsx_pci_ms_drv_probe(struct platform_device *pdev)
+{
+ struct memstick_host *msh;
+ struct realtek_pci_ms *host;
+ struct rtsx_pcr *pcr;
+ struct pcr_handle *handle = pdev->dev.platform_data;
+ int rc;
+
+ if (!handle)
+ return -ENXIO;
+
+ pcr = handle->pcr;
+ if (!pcr)
+ return -ENXIO;
+
+ dev_dbg(&(pdev->dev),
+ ": Realtek PCI-E Memstick controller found\n");
+
+ msh = memstick_alloc_host(sizeof(*host), &pdev->dev);
+ if (!msh)
+ return -ENOMEM;
+
+ host = memstick_priv(msh);
+ host->pcr = pcr;
+ host->msh = msh;
+ host->pdev = pdev;
+ platform_set_drvdata(pdev, host);
+ pcr->slots[RTSX_MS_CARD].p_dev = pdev;
+ pcr->slots[RTSX_MS_CARD].card_event = rtsx_pci_ms_card_event;
+
+ mutex_init(&host->host_mutex);
+
+ INIT_WORK(&host->handle_req, rtsx_pci_ms_handle_req);
+ msh->request = rtsx_pci_ms_request;
+ msh->set_param = rtsx_pci_ms_set_param;
+ msh->caps = MEMSTICK_CAP_PAR4;
+
+ rc = memstick_add_host(msh);
+ if (rc) {
+ memstick_free_host(msh);
+ return rc;
+ }
+
+ return 0;
+}
+
+static int rtsx_pci_ms_drv_remove(struct platform_device *pdev)
+{
+ struct realtek_pci_ms *host = platform_get_drvdata(pdev);
+ struct rtsx_pcr *pcr;
+ struct memstick_host *msh;
+ int rc;
+
+ if (!host)
+ return 0;
+
+ pcr = host->pcr;
+ pcr->slots[RTSX_MS_CARD].p_dev = NULL;
+ pcr->slots[RTSX_MS_CARD].card_event = NULL;
+ msh = host->msh;
+ host->eject = true;
+ cancel_work_sync(&host->handle_req);
+
+ mutex_lock(&host->host_mutex);
+ if (host->req) {
+ dev_dbg(&(pdev->dev),
+ "%s: Controller removed during transfer\n",
+ dev_name(&msh->dev));
+
+ rtsx_pci_complete_unfinished_transfer(pcr);
+
+ host->req->error = -ENOMEDIUM;
+ do {
+ rc = memstick_next_req(msh, &host->req);
+ if (!rc)
+ host->req->error = -ENOMEDIUM;
+ } while (!rc);
+ }
+ mutex_unlock(&host->host_mutex);
+
+ memstick_remove_host(msh);
+ memstick_free_host(msh);
+
+ dev_dbg(&(pdev->dev),
+ ": Realtek PCI-E Memstick controller has been removed\n");
+
+ return 0;
+}
+
+static struct platform_device_id rtsx_pci_ms_ids[] = {
+ {
+ .name = DRV_NAME_RTSX_PCI_MS,
+ }, {
+ /* sentinel */
+ }
+};
+MODULE_DEVICE_TABLE(platform, rtsx_pci_ms_ids);
+
+static struct platform_driver rtsx_pci_ms_driver = {
+ .probe = rtsx_pci_ms_drv_probe,
+ .remove = rtsx_pci_ms_drv_remove,
+ .id_table = rtsx_pci_ms_ids,
+ .suspend = rtsx_pci_ms_suspend,
+ .resume = rtsx_pci_ms_resume,
+ .driver = {
+ .name = DRV_NAME_RTSX_PCI_MS,
+ },
+};
+module_platform_driver(rtsx_pci_ms_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Wei WANG <wei_wang@realsil.com.cn>");
+MODULE_DESCRIPTION("Realtek PCI-E Memstick Card Host Driver");
diff --git a/drivers/memstick/host/rtsx_usb_ms.c b/drivers/memstick/host/rtsx_usb_ms.c
new file mode 100644
index 000000000..29271ad47
--- /dev/null
+++ b/drivers/memstick/host/rtsx_usb_ms.c
@@ -0,0 +1,869 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Realtek USB Memstick Card Interface driver
+ *
+ * Copyright(c) 2009-2013 Realtek Semiconductor Corp. All rights reserved.
+ *
+ * Author:
+ * Roger Tseng <rogerable@realtek.com>
+ */
+
+#include <linux/module.h>
+#include <linux/highmem.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/workqueue.h>
+#include <linux/memstick.h>
+#include <linux/kthread.h>
+#include <linux/rtsx_usb.h>
+#include <linux/pm_runtime.h>
+#include <linux/mutex.h>
+#include <linux/sched.h>
+#include <linux/completion.h>
+#include <asm/unaligned.h>
+
+struct rtsx_usb_ms {
+ struct platform_device *pdev;
+ struct rtsx_ucr *ucr;
+ struct memstick_host *msh;
+ struct memstick_request *req;
+
+ struct mutex host_mutex;
+ struct work_struct handle_req;
+ struct delayed_work poll_card;
+
+ u8 ssc_depth;
+ unsigned int clock;
+ int power_mode;
+ unsigned char ifmode;
+ bool eject;
+ bool system_suspending;
+};
+
+static inline struct device *ms_dev(struct rtsx_usb_ms *host)
+{
+ return &(host->pdev->dev);
+}
+
+static inline void ms_clear_error(struct rtsx_usb_ms *host)
+{
+ struct rtsx_ucr *ucr = host->ucr;
+ rtsx_usb_ep0_write_register(ucr, CARD_STOP,
+ MS_STOP | MS_CLR_ERR,
+ MS_STOP | MS_CLR_ERR);
+
+ rtsx_usb_clear_dma_err(ucr);
+ rtsx_usb_clear_fsm_err(ucr);
+}
+
+#ifdef DEBUG
+
+static void ms_print_debug_regs(struct rtsx_usb_ms *host)
+{
+ struct rtsx_ucr *ucr = host->ucr;
+ u16 i;
+ u8 *ptr;
+
+ /* Print MS host internal registers */
+ rtsx_usb_init_cmd(ucr);
+
+ /* MS_CFG to MS_INT_REG */
+ for (i = 0xFD40; i <= 0xFD44; i++)
+ rtsx_usb_add_cmd(ucr, READ_REG_CMD, i, 0, 0);
+
+ /* CARD_SHARE_MODE to CARD_GPIO */
+ for (i = 0xFD51; i <= 0xFD56; i++)
+ rtsx_usb_add_cmd(ucr, READ_REG_CMD, i, 0, 0);
+
+ /* CARD_PULL_CTLx */
+ for (i = 0xFD60; i <= 0xFD65; i++)
+ rtsx_usb_add_cmd(ucr, READ_REG_CMD, i, 0, 0);
+
+ /* CARD_DATA_SOURCE, CARD_SELECT, CARD_CLK_EN, CARD_PWR_CTL */
+ rtsx_usb_add_cmd(ucr, READ_REG_CMD, CARD_DATA_SOURCE, 0, 0);
+ rtsx_usb_add_cmd(ucr, READ_REG_CMD, CARD_SELECT, 0, 0);
+ rtsx_usb_add_cmd(ucr, READ_REG_CMD, CARD_CLK_EN, 0, 0);
+ rtsx_usb_add_cmd(ucr, READ_REG_CMD, CARD_PWR_CTL, 0, 0);
+
+ rtsx_usb_send_cmd(ucr, MODE_CR, 100);
+ rtsx_usb_get_rsp(ucr, 21, 100);
+
+ ptr = ucr->rsp_buf;
+ for (i = 0xFD40; i <= 0xFD44; i++)
+ dev_dbg(ms_dev(host), "0x%04X: 0x%02x\n", i, *(ptr++));
+ for (i = 0xFD51; i <= 0xFD56; i++)
+ dev_dbg(ms_dev(host), "0x%04X: 0x%02x\n", i, *(ptr++));
+ for (i = 0xFD60; i <= 0xFD65; i++)
+ dev_dbg(ms_dev(host), "0x%04X: 0x%02x\n", i, *(ptr++));
+
+ dev_dbg(ms_dev(host), "0x%04X: 0x%02x\n", CARD_DATA_SOURCE, *(ptr++));
+ dev_dbg(ms_dev(host), "0x%04X: 0x%02x\n", CARD_SELECT, *(ptr++));
+ dev_dbg(ms_dev(host), "0x%04X: 0x%02x\n", CARD_CLK_EN, *(ptr++));
+ dev_dbg(ms_dev(host), "0x%04X: 0x%02x\n", CARD_PWR_CTL, *(ptr++));
+}
+
+#else
+
+static void ms_print_debug_regs(struct rtsx_usb_ms *host)
+{
+}
+
+#endif
+
+static int ms_pull_ctl_disable_lqfp48(struct rtsx_ucr *ucr)
+{
+ rtsx_usb_init_cmd(ucr);
+
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL1, 0xFF, 0x55);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL2, 0xFF, 0x55);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL3, 0xFF, 0x95);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL4, 0xFF, 0x55);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL5, 0xFF, 0x55);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL6, 0xFF, 0xA5);
+
+ return rtsx_usb_send_cmd(ucr, MODE_C, 100);
+}
+
+static int ms_pull_ctl_disable_qfn24(struct rtsx_ucr *ucr)
+{
+ rtsx_usb_init_cmd(ucr);
+
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL1, 0xFF, 0x65);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL2, 0xFF, 0x55);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL3, 0xFF, 0x95);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL4, 0xFF, 0x55);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL5, 0xFF, 0x56);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL6, 0xFF, 0x59);
+
+ return rtsx_usb_send_cmd(ucr, MODE_C, 100);
+}
+
+static int ms_pull_ctl_enable_lqfp48(struct rtsx_ucr *ucr)
+{
+ rtsx_usb_init_cmd(ucr);
+
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL1, 0xFF, 0x55);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL2, 0xFF, 0x55);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL3, 0xFF, 0x95);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL4, 0xFF, 0x55);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL5, 0xFF, 0x55);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL6, 0xFF, 0xA5);
+
+ return rtsx_usb_send_cmd(ucr, MODE_C, 100);
+}
+
+static int ms_pull_ctl_enable_qfn24(struct rtsx_ucr *ucr)
+{
+ rtsx_usb_init_cmd(ucr);
+
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL1, 0xFF, 0x65);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL2, 0xFF, 0x55);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL3, 0xFF, 0x95);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL4, 0xFF, 0x55);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL5, 0xFF, 0x55);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL6, 0xFF, 0x59);
+
+ return rtsx_usb_send_cmd(ucr, MODE_C, 100);
+}
+
+static int ms_power_on(struct rtsx_usb_ms *host)
+{
+ struct rtsx_ucr *ucr = host->ucr;
+ int err;
+
+ dev_dbg(ms_dev(host), "%s\n", __func__);
+
+ rtsx_usb_init_cmd(ucr);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_SELECT, 0x07, MS_MOD_SEL);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_SHARE_MODE,
+ CARD_SHARE_MASK, CARD_SHARE_MS);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_CLK_EN,
+ MS_CLK_EN, MS_CLK_EN);
+ err = rtsx_usb_send_cmd(ucr, MODE_C, 100);
+ if (err < 0)
+ return err;
+
+ if (CHECK_PKG(ucr, LQFP48))
+ err = ms_pull_ctl_enable_lqfp48(ucr);
+ else
+ err = ms_pull_ctl_enable_qfn24(ucr);
+ if (err < 0)
+ return err;
+
+ err = rtsx_usb_write_register(ucr, CARD_PWR_CTL,
+ POWER_MASK, PARTIAL_POWER_ON);
+ if (err)
+ return err;
+
+ usleep_range(800, 1000);
+
+ rtsx_usb_init_cmd(ucr);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PWR_CTL,
+ POWER_MASK, POWER_ON);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_OE,
+ MS_OUTPUT_EN, MS_OUTPUT_EN);
+
+ return rtsx_usb_send_cmd(ucr, MODE_C, 100);
+}
+
+static int ms_power_off(struct rtsx_usb_ms *host)
+{
+ struct rtsx_ucr *ucr = host->ucr;
+ int err;
+
+ dev_dbg(ms_dev(host), "%s\n", __func__);
+
+ rtsx_usb_init_cmd(ucr);
+
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_CLK_EN, MS_CLK_EN, 0);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_OE, MS_OUTPUT_EN, 0);
+
+ err = rtsx_usb_send_cmd(ucr, MODE_C, 100);
+ if (err < 0)
+ return err;
+
+ if (CHECK_PKG(ucr, LQFP48))
+ return ms_pull_ctl_disable_lqfp48(ucr);
+
+ return ms_pull_ctl_disable_qfn24(ucr);
+}
+
+static int ms_transfer_data(struct rtsx_usb_ms *host, unsigned char data_dir,
+ u8 tpc, u8 cfg, struct scatterlist *sg)
+{
+ struct rtsx_ucr *ucr = host->ucr;
+ int err;
+ unsigned int length = sg->length;
+ u16 sec_cnt = (u16)(length / 512);
+ u8 trans_mode, dma_dir, flag;
+ unsigned int pipe;
+ struct memstick_dev *card = host->msh->card;
+
+ dev_dbg(ms_dev(host), "%s: tpc = 0x%02x, data_dir = %s, length = %d\n",
+ __func__, tpc, (data_dir == READ) ? "READ" : "WRITE",
+ length);
+
+ if (data_dir == READ) {
+ flag = MODE_CDIR;
+ dma_dir = DMA_DIR_FROM_CARD;
+ if (card->id.type != MEMSTICK_TYPE_PRO)
+ trans_mode = MS_TM_NORMAL_READ;
+ else
+ trans_mode = MS_TM_AUTO_READ;
+ pipe = usb_rcvbulkpipe(ucr->pusb_dev, EP_BULK_IN);
+ } else {
+ flag = MODE_CDOR;
+ dma_dir = DMA_DIR_TO_CARD;
+ if (card->id.type != MEMSTICK_TYPE_PRO)
+ trans_mode = MS_TM_NORMAL_WRITE;
+ else
+ trans_mode = MS_TM_AUTO_WRITE;
+ pipe = usb_sndbulkpipe(ucr->pusb_dev, EP_BULK_OUT);
+ }
+
+ rtsx_usb_init_cmd(ucr);
+
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, MS_TPC, 0xFF, tpc);
+ if (card->id.type == MEMSTICK_TYPE_PRO) {
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, MS_SECTOR_CNT_H,
+ 0xFF, (u8)(sec_cnt >> 8));
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, MS_SECTOR_CNT_L,
+ 0xFF, (u8)sec_cnt);
+ }
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, MS_TRANS_CFG, 0xFF, cfg);
+
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, MC_DMA_TC3,
+ 0xFF, (u8)(length >> 24));
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, MC_DMA_TC2,
+ 0xFF, (u8)(length >> 16));
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, MC_DMA_TC1,
+ 0xFF, (u8)(length >> 8));
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, MC_DMA_TC0, 0xFF,
+ (u8)length);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, MC_DMA_CTL,
+ 0x03 | DMA_PACK_SIZE_MASK, dma_dir | DMA_EN | DMA_512);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_DATA_SOURCE,
+ 0x01, RING_BUFFER);
+
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, MS_TRANSFER,
+ 0xFF, MS_TRANSFER_START | trans_mode);
+ rtsx_usb_add_cmd(ucr, CHECK_REG_CMD, MS_TRANSFER,
+ MS_TRANSFER_END, MS_TRANSFER_END);
+
+ err = rtsx_usb_send_cmd(ucr, flag | STAGE_MS_STATUS, 100);
+ if (err)
+ return err;
+
+ err = rtsx_usb_transfer_data(ucr, pipe, sg, length,
+ 1, NULL, 10000);
+ if (err)
+ goto err_out;
+
+ err = rtsx_usb_get_rsp(ucr, 3, 15000);
+ if (err)
+ goto err_out;
+
+ if (ucr->rsp_buf[0] & MS_TRANSFER_ERR ||
+ ucr->rsp_buf[1] & (MS_CRC16_ERR | MS_RDY_TIMEOUT)) {
+ err = -EIO;
+ goto err_out;
+ }
+ return 0;
+err_out:
+ ms_clear_error(host);
+ return err;
+}
+
+static int ms_write_bytes(struct rtsx_usb_ms *host, u8 tpc,
+ u8 cfg, u8 cnt, u8 *data, u8 *int_reg)
+{
+ struct rtsx_ucr *ucr = host->ucr;
+ int err, i;
+
+ dev_dbg(ms_dev(host), "%s: tpc = 0x%02x\n", __func__, tpc);
+
+ rtsx_usb_init_cmd(ucr);
+
+ for (i = 0; i < cnt; i++)
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD,
+ PPBUF_BASE2 + i, 0xFF, data[i]);
+
+ if (cnt % 2)
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD,
+ PPBUF_BASE2 + i, 0xFF, 0xFF);
+
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, MS_TPC, 0xFF, tpc);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, MS_BYTE_CNT, 0xFF, cnt);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, MS_TRANS_CFG, 0xFF, cfg);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_DATA_SOURCE,
+ 0x01, PINGPONG_BUFFER);
+
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, MS_TRANSFER,
+ 0xFF, MS_TRANSFER_START | MS_TM_WRITE_BYTES);
+ rtsx_usb_add_cmd(ucr, CHECK_REG_CMD, MS_TRANSFER,
+ MS_TRANSFER_END, MS_TRANSFER_END);
+ rtsx_usb_add_cmd(ucr, READ_REG_CMD, MS_TRANS_CFG, 0, 0);
+
+ err = rtsx_usb_send_cmd(ucr, MODE_CR, 100);
+ if (err)
+ return err;
+
+ err = rtsx_usb_get_rsp(ucr, 2, 5000);
+ if (err || (ucr->rsp_buf[0] & MS_TRANSFER_ERR)) {
+ u8 val;
+
+ rtsx_usb_ep0_read_register(ucr, MS_TRANS_CFG, &val);
+ dev_dbg(ms_dev(host), "MS_TRANS_CFG: 0x%02x\n", val);
+
+ if (int_reg)
+ *int_reg = val & 0x0F;
+
+ ms_print_debug_regs(host);
+
+ ms_clear_error(host);
+
+ if (!(tpc & 0x08)) {
+ if (val & MS_CRC16_ERR)
+ return -EIO;
+ } else {
+ if (!(val & 0x80)) {
+ if (val & (MS_INT_ERR | MS_INT_CMDNK))
+ return -EIO;
+ }
+ }
+
+ return -ETIMEDOUT;
+ }
+
+ if (int_reg)
+ *int_reg = ucr->rsp_buf[1] & 0x0F;
+
+ return 0;
+}
+
+static int ms_read_bytes(struct rtsx_usb_ms *host, u8 tpc,
+ u8 cfg, u8 cnt, u8 *data, u8 *int_reg)
+{
+ struct rtsx_ucr *ucr = host->ucr;
+ int err, i;
+ u8 *ptr;
+
+ dev_dbg(ms_dev(host), "%s: tpc = 0x%02x\n", __func__, tpc);
+
+ rtsx_usb_init_cmd(ucr);
+
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, MS_TPC, 0xFF, tpc);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, MS_BYTE_CNT, 0xFF, cnt);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, MS_TRANS_CFG, 0xFF, cfg);
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_DATA_SOURCE,
+ 0x01, PINGPONG_BUFFER);
+
+ rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, MS_TRANSFER,
+ 0xFF, MS_TRANSFER_START | MS_TM_READ_BYTES);
+ rtsx_usb_add_cmd(ucr, CHECK_REG_CMD, MS_TRANSFER,
+ MS_TRANSFER_END, MS_TRANSFER_END);
+ for (i = 0; i < cnt - 1; i++)
+ rtsx_usb_add_cmd(ucr, READ_REG_CMD, PPBUF_BASE2 + i, 0, 0);
+ if (cnt % 2)
+ rtsx_usb_add_cmd(ucr, READ_REG_CMD, PPBUF_BASE2 + cnt, 0, 0);
+ else
+ rtsx_usb_add_cmd(ucr, READ_REG_CMD,
+ PPBUF_BASE2 + cnt - 1, 0, 0);
+
+ rtsx_usb_add_cmd(ucr, READ_REG_CMD, MS_TRANS_CFG, 0, 0);
+
+ err = rtsx_usb_send_cmd(ucr, MODE_CR, 100);
+ if (err)
+ return err;
+
+ err = rtsx_usb_get_rsp(ucr, cnt + 2, 5000);
+ if (err || (ucr->rsp_buf[0] & MS_TRANSFER_ERR)) {
+ u8 val;
+
+ rtsx_usb_ep0_read_register(ucr, MS_TRANS_CFG, &val);
+ dev_dbg(ms_dev(host), "MS_TRANS_CFG: 0x%02x\n", val);
+
+ if (int_reg && (host->ifmode != MEMSTICK_SERIAL))
+ *int_reg = val & 0x0F;
+
+ ms_print_debug_regs(host);
+
+ ms_clear_error(host);
+
+ if (!(tpc & 0x08)) {
+ if (val & MS_CRC16_ERR)
+ return -EIO;
+ } else {
+ if (!(val & 0x80)) {
+ if (val & (MS_INT_ERR | MS_INT_CMDNK))
+ return -EIO;
+ }
+ }
+
+ return -ETIMEDOUT;
+ }
+
+ ptr = ucr->rsp_buf + 1;
+ for (i = 0; i < cnt; i++)
+ data[i] = *ptr++;
+
+
+ if (int_reg && (host->ifmode != MEMSTICK_SERIAL))
+ *int_reg = *ptr & 0x0F;
+
+ return 0;
+}
+
+static int rtsx_usb_ms_issue_cmd(struct rtsx_usb_ms *host)
+{
+ struct memstick_request *req = host->req;
+ int err = 0;
+ u8 cfg = 0, int_reg;
+
+ dev_dbg(ms_dev(host), "%s\n", __func__);
+
+ if (req->need_card_int) {
+ if (host->ifmode != MEMSTICK_SERIAL)
+ cfg = WAIT_INT;
+ }
+
+ if (req->long_data) {
+ err = ms_transfer_data(host, req->data_dir,
+ req->tpc, cfg, &(req->sg));
+ } else {
+ if (req->data_dir == READ)
+ err = ms_read_bytes(host, req->tpc, cfg,
+ req->data_len, req->data, &int_reg);
+ else
+ err = ms_write_bytes(host, req->tpc, cfg,
+ req->data_len, req->data, &int_reg);
+ }
+ if (err < 0)
+ return err;
+
+ if (req->need_card_int) {
+ if (host->ifmode == MEMSTICK_SERIAL) {
+ err = ms_read_bytes(host, MS_TPC_GET_INT,
+ NO_WAIT_INT, 1, &req->int_reg, NULL);
+ if (err < 0)
+ return err;
+ } else {
+
+ if (int_reg & MS_INT_CMDNK)
+ req->int_reg |= MEMSTICK_INT_CMDNAK;
+ if (int_reg & MS_INT_BREQ)
+ req->int_reg |= MEMSTICK_INT_BREQ;
+ if (int_reg & MS_INT_ERR)
+ req->int_reg |= MEMSTICK_INT_ERR;
+ if (int_reg & MS_INT_CED)
+ req->int_reg |= MEMSTICK_INT_CED;
+ }
+ dev_dbg(ms_dev(host), "int_reg: 0x%02x\n", req->int_reg);
+ }
+
+ return 0;
+}
+
+static void rtsx_usb_ms_handle_req(struct work_struct *work)
+{
+ struct rtsx_usb_ms *host = container_of(work,
+ struct rtsx_usb_ms, handle_req);
+ struct rtsx_ucr *ucr = host->ucr;
+ struct memstick_host *msh = host->msh;
+ int rc;
+
+ if (!host->req) {
+ pm_runtime_get_sync(ms_dev(host));
+ do {
+ rc = memstick_next_req(msh, &host->req);
+ dev_dbg(ms_dev(host), "next req %d\n", rc);
+
+ if (!rc) {
+ mutex_lock(&ucr->dev_mutex);
+
+ if (rtsx_usb_card_exclusive_check(ucr,
+ RTSX_USB_MS_CARD))
+ host->req->error = -EIO;
+ else
+ host->req->error =
+ rtsx_usb_ms_issue_cmd(host);
+
+ mutex_unlock(&ucr->dev_mutex);
+
+ dev_dbg(ms_dev(host), "req result %d\n",
+ host->req->error);
+ }
+ } while (!rc);
+ pm_runtime_put_sync(ms_dev(host));
+ }
+
+}
+
+static void rtsx_usb_ms_request(struct memstick_host *msh)
+{
+ struct rtsx_usb_ms *host = memstick_priv(msh);
+
+ dev_dbg(ms_dev(host), "--> %s\n", __func__);
+
+ if (!host->eject)
+ schedule_work(&host->handle_req);
+}
+
+static int rtsx_usb_ms_set_param(struct memstick_host *msh,
+ enum memstick_param param, int value)
+{
+ struct rtsx_usb_ms *host = memstick_priv(msh);
+ struct rtsx_ucr *ucr = host->ucr;
+ unsigned int clock = 0;
+ u8 ssc_depth = 0;
+ int err;
+
+ dev_dbg(ms_dev(host), "%s: param = %d, value = %d\n",
+ __func__, param, value);
+
+ pm_runtime_get_sync(ms_dev(host));
+ mutex_lock(&ucr->dev_mutex);
+
+ err = rtsx_usb_card_exclusive_check(ucr, RTSX_USB_MS_CARD);
+ if (err)
+ goto out;
+
+ switch (param) {
+ case MEMSTICK_POWER:
+ if (value == host->power_mode)
+ break;
+
+ if (value == MEMSTICK_POWER_ON) {
+ pm_runtime_get_noresume(ms_dev(host));
+ err = ms_power_on(host);
+ if (err)
+ pm_runtime_put_noidle(ms_dev(host));
+ } else if (value == MEMSTICK_POWER_OFF) {
+ err = ms_power_off(host);
+ if (!err)
+ pm_runtime_put_noidle(ms_dev(host));
+ } else
+ err = -EINVAL;
+ if (!err)
+ host->power_mode = value;
+ break;
+
+ case MEMSTICK_INTERFACE:
+ if (value == MEMSTICK_SERIAL) {
+ clock = 19000000;
+ ssc_depth = SSC_DEPTH_512K;
+ err = rtsx_usb_write_register(ucr, MS_CFG, 0x5A,
+ MS_BUS_WIDTH_1 | PUSH_TIME_DEFAULT);
+ if (err < 0)
+ break;
+ } else if (value == MEMSTICK_PAR4) {
+ clock = 39000000;
+ ssc_depth = SSC_DEPTH_1M;
+
+ err = rtsx_usb_write_register(ucr, MS_CFG, 0x5A,
+ MS_BUS_WIDTH_4 | PUSH_TIME_ODD |
+ MS_NO_CHECK_INT);
+ if (err < 0)
+ break;
+ } else {
+ err = -EINVAL;
+ break;
+ }
+
+ err = rtsx_usb_switch_clock(ucr, clock,
+ ssc_depth, false, true, false);
+ if (err < 0) {
+ dev_dbg(ms_dev(host), "switch clock failed\n");
+ break;
+ }
+
+ host->ssc_depth = ssc_depth;
+ host->clock = clock;
+ host->ifmode = value;
+ break;
+ default:
+ err = -EINVAL;
+ break;
+ }
+out:
+ mutex_unlock(&ucr->dev_mutex);
+ pm_runtime_put_sync(ms_dev(host));
+
+ /* power-on delay */
+ if (param == MEMSTICK_POWER && value == MEMSTICK_POWER_ON) {
+ usleep_range(10000, 12000);
+
+ if (!host->eject)
+ schedule_delayed_work(&host->poll_card, 100);
+ }
+
+ dev_dbg(ms_dev(host), "%s: return = %d\n", __func__, err);
+ return err;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int rtsx_usb_ms_suspend(struct device *dev)
+{
+ struct rtsx_usb_ms *host = dev_get_drvdata(dev);
+ struct memstick_host *msh = host->msh;
+
+ /* Since we use rtsx_usb's resume callback to runtime resume its
+ * children to implement remote wakeup signaling, this causes
+ * rtsx_usb_ms' runtime resume callback runs after its suspend
+ * callback:
+ * rtsx_usb_ms_suspend()
+ * rtsx_usb_resume()
+ * -> rtsx_usb_ms_runtime_resume()
+ * -> memstick_detect_change()
+ *
+ * rtsx_usb_suspend()
+ *
+ * To avoid this, skip runtime resume/suspend if system suspend is
+ * underway.
+ */
+
+ host->system_suspending = true;
+ memstick_suspend_host(msh);
+
+ return 0;
+}
+
+static int rtsx_usb_ms_resume(struct device *dev)
+{
+ struct rtsx_usb_ms *host = dev_get_drvdata(dev);
+ struct memstick_host *msh = host->msh;
+
+ memstick_resume_host(msh);
+ host->system_suspending = false;
+
+ return 0;
+}
+#endif /* CONFIG_PM_SLEEP */
+
+#ifdef CONFIG_PM
+static int rtsx_usb_ms_runtime_suspend(struct device *dev)
+{
+ struct rtsx_usb_ms *host = dev_get_drvdata(dev);
+
+ if (host->system_suspending)
+ return 0;
+
+ if (host->msh->card || host->power_mode != MEMSTICK_POWER_OFF)
+ return -EAGAIN;
+
+ return 0;
+}
+
+static int rtsx_usb_ms_runtime_resume(struct device *dev)
+{
+ struct rtsx_usb_ms *host = dev_get_drvdata(dev);
+
+
+ if (host->system_suspending)
+ return 0;
+
+ memstick_detect_change(host->msh);
+
+ return 0;
+}
+#endif /* CONFIG_PM */
+
+static const struct dev_pm_ops rtsx_usb_ms_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(rtsx_usb_ms_suspend, rtsx_usb_ms_resume)
+ SET_RUNTIME_PM_OPS(rtsx_usb_ms_runtime_suspend, rtsx_usb_ms_runtime_resume, NULL)
+};
+
+
+static void rtsx_usb_ms_poll_card(struct work_struct *work)
+{
+ struct rtsx_usb_ms *host = container_of(work, struct rtsx_usb_ms,
+ poll_card.work);
+ struct rtsx_ucr *ucr = host->ucr;
+ int err;
+ u8 val;
+
+ if (host->eject || host->power_mode != MEMSTICK_POWER_ON)
+ return;
+
+ pm_runtime_get_sync(ms_dev(host));
+ mutex_lock(&ucr->dev_mutex);
+
+ /* Check pending MS card changes */
+ err = rtsx_usb_read_register(ucr, CARD_INT_PEND, &val);
+ if (err) {
+ mutex_unlock(&ucr->dev_mutex);
+ goto poll_again;
+ }
+
+ /* Clear the pending */
+ rtsx_usb_write_register(ucr, CARD_INT_PEND,
+ XD_INT | MS_INT | SD_INT,
+ XD_INT | MS_INT | SD_INT);
+
+ mutex_unlock(&ucr->dev_mutex);
+
+ if (val & MS_INT) {
+ dev_dbg(ms_dev(host), "MS slot change detected\n");
+ memstick_detect_change(host->msh);
+ }
+
+poll_again:
+ pm_runtime_put_sync(ms_dev(host));
+
+ if (!host->eject && host->power_mode == MEMSTICK_POWER_ON)
+ schedule_delayed_work(&host->poll_card, 100);
+}
+
+static int rtsx_usb_ms_drv_probe(struct platform_device *pdev)
+{
+ struct memstick_host *msh;
+ struct rtsx_usb_ms *host;
+ struct rtsx_ucr *ucr;
+ int err;
+
+ ucr = usb_get_intfdata(to_usb_interface(pdev->dev.parent));
+ if (!ucr)
+ return -ENXIO;
+
+ dev_dbg(&(pdev->dev),
+ "Realtek USB Memstick controller found\n");
+
+ msh = memstick_alloc_host(sizeof(*host), &pdev->dev);
+ if (!msh)
+ return -ENOMEM;
+
+ host = memstick_priv(msh);
+ host->ucr = ucr;
+ host->msh = msh;
+ host->pdev = pdev;
+ host->power_mode = MEMSTICK_POWER_OFF;
+ platform_set_drvdata(pdev, host);
+
+ mutex_init(&host->host_mutex);
+ INIT_WORK(&host->handle_req, rtsx_usb_ms_handle_req);
+
+ INIT_DELAYED_WORK(&host->poll_card, rtsx_usb_ms_poll_card);
+
+ msh->request = rtsx_usb_ms_request;
+ msh->set_param = rtsx_usb_ms_set_param;
+ msh->caps = MEMSTICK_CAP_PAR4;
+
+ pm_runtime_get_noresume(ms_dev(host));
+ pm_runtime_set_active(ms_dev(host));
+ pm_runtime_enable(ms_dev(host));
+
+ err = memstick_add_host(msh);
+ if (err)
+ goto err_out;
+
+ pm_runtime_put(ms_dev(host));
+
+ return 0;
+err_out:
+ pm_runtime_disable(ms_dev(host));
+ pm_runtime_put_noidle(ms_dev(host));
+ memstick_free_host(msh);
+ return err;
+}
+
+static int rtsx_usb_ms_drv_remove(struct platform_device *pdev)
+{
+ struct rtsx_usb_ms *host = platform_get_drvdata(pdev);
+ struct memstick_host *msh = host->msh;
+ int err;
+
+ host->eject = true;
+ cancel_work_sync(&host->handle_req);
+
+ mutex_lock(&host->host_mutex);
+ if (host->req) {
+ dev_dbg(ms_dev(host),
+ "%s: Controller removed during transfer\n",
+ dev_name(&msh->dev));
+ host->req->error = -ENOMEDIUM;
+ do {
+ err = memstick_next_req(msh, &host->req);
+ if (!err)
+ host->req->error = -ENOMEDIUM;
+ } while (!err);
+ }
+ mutex_unlock(&host->host_mutex);
+
+ /* Balance possible unbalanced usage count
+ * e.g. unconditional module removal
+ */
+ if (pm_runtime_active(ms_dev(host)))
+ pm_runtime_put(ms_dev(host));
+
+ pm_runtime_disable(ms_dev(host));
+ memstick_remove_host(msh);
+ dev_dbg(ms_dev(host),
+ ": Realtek USB Memstick controller has been removed\n");
+ memstick_free_host(msh);
+ platform_set_drvdata(pdev, NULL);
+
+ return 0;
+}
+
+static struct platform_device_id rtsx_usb_ms_ids[] = {
+ {
+ .name = "rtsx_usb_ms",
+ }, {
+ /* sentinel */
+ }
+};
+MODULE_DEVICE_TABLE(platform, rtsx_usb_ms_ids);
+
+static struct platform_driver rtsx_usb_ms_driver = {
+ .probe = rtsx_usb_ms_drv_probe,
+ .remove = rtsx_usb_ms_drv_remove,
+ .id_table = rtsx_usb_ms_ids,
+ .driver = {
+ .name = "rtsx_usb_ms",
+ .pm = &rtsx_usb_ms_pm_ops,
+ },
+};
+module_platform_driver(rtsx_usb_ms_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Roger Tseng <rogerable@realtek.com>");
+MODULE_DESCRIPTION("Realtek USB Memstick Card Host Driver");
diff --git a/drivers/memstick/host/tifm_ms.c b/drivers/memstick/host/tifm_ms.c
new file mode 100644
index 000000000..c27245367
--- /dev/null
+++ b/drivers/memstick/host/tifm_ms.c
@@ -0,0 +1,683 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * TI FlashMedia driver
+ *
+ * Copyright (C) 2007 Alex Dubov <oakad@yahoo.com>
+ *
+ * Special thanks to Carlos Corbacho for providing various MemoryStick cards
+ * that made this driver possible.
+ */
+
+#include <linux/tifm.h>
+#include <linux/memstick.h>
+#include <linux/highmem.h>
+#include <linux/scatterlist.h>
+#include <linux/log2.h>
+#include <linux/module.h>
+#include <asm/io.h>
+
+#define DRIVER_NAME "tifm_ms"
+
+static bool no_dma;
+module_param(no_dma, bool, 0644);
+
+/*
+ * Some control bits of TIFM appear to conform to Sony's reference design,
+ * so I'm just assuming they all are.
+ */
+
+#define TIFM_MS_STAT_DRQ 0x04000
+#define TIFM_MS_STAT_MSINT 0x02000
+#define TIFM_MS_STAT_RDY 0x01000
+#define TIFM_MS_STAT_CRC 0x00200
+#define TIFM_MS_STAT_TOE 0x00100
+#define TIFM_MS_STAT_EMP 0x00020
+#define TIFM_MS_STAT_FUL 0x00010
+#define TIFM_MS_STAT_CED 0x00008
+#define TIFM_MS_STAT_ERR 0x00004
+#define TIFM_MS_STAT_BRQ 0x00002
+#define TIFM_MS_STAT_CNK 0x00001
+
+#define TIFM_MS_SYS_DMA 0x10000
+#define TIFM_MS_SYS_RESET 0x08000
+#define TIFM_MS_SYS_SRAC 0x04000
+#define TIFM_MS_SYS_INTEN 0x02000
+#define TIFM_MS_SYS_NOCRC 0x01000
+#define TIFM_MS_SYS_INTCLR 0x00800
+#define TIFM_MS_SYS_MSIEN 0x00400
+#define TIFM_MS_SYS_FCLR 0x00200
+#define TIFM_MS_SYS_FDIR 0x00100
+#define TIFM_MS_SYS_DAM 0x00080
+#define TIFM_MS_SYS_DRM 0x00040
+#define TIFM_MS_SYS_DRQSL 0x00020
+#define TIFM_MS_SYS_REI 0x00010
+#define TIFM_MS_SYS_REO 0x00008
+#define TIFM_MS_SYS_BSY_MASK 0x00007
+
+#define TIFM_MS_SYS_FIFO (TIFM_MS_SYS_INTEN | TIFM_MS_SYS_MSIEN \
+ | TIFM_MS_SYS_FCLR | TIFM_MS_SYS_BSY_MASK)
+
+/* Hardware flags */
+enum {
+ CMD_READY = 0x01,
+ FIFO_READY = 0x02,
+ CARD_INT = 0x04
+};
+
+struct tifm_ms {
+ struct tifm_dev *dev;
+ struct timer_list timer;
+ struct memstick_request *req;
+ struct tasklet_struct notify;
+ unsigned int mode_mask;
+ unsigned int block_pos;
+ unsigned long timeout_jiffies;
+ unsigned char eject:1,
+ use_dma:1;
+ unsigned char cmd_flags;
+ unsigned char io_pos;
+ unsigned int io_word;
+};
+
+static unsigned int tifm_ms_read_data(struct tifm_ms *host,
+ unsigned char *buf, unsigned int length)
+{
+ struct tifm_dev *sock = host->dev;
+ unsigned int off = 0;
+
+ while (host->io_pos && length) {
+ buf[off++] = host->io_word & 0xff;
+ host->io_word >>= 8;
+ length--;
+ host->io_pos--;
+ }
+
+ if (!length)
+ return off;
+
+ while (!(TIFM_MS_STAT_EMP & readl(sock->addr + SOCK_MS_STATUS))) {
+ if (length < 4)
+ break;
+ *(unsigned int *)(buf + off) = __raw_readl(sock->addr
+ + SOCK_MS_DATA);
+ length -= 4;
+ off += 4;
+ }
+
+ if (length
+ && !(TIFM_MS_STAT_EMP & readl(sock->addr + SOCK_MS_STATUS))) {
+ host->io_word = readl(sock->addr + SOCK_MS_DATA);
+ for (host->io_pos = 4; host->io_pos; --host->io_pos) {
+ buf[off++] = host->io_word & 0xff;
+ host->io_word >>= 8;
+ length--;
+ if (!length)
+ break;
+ }
+ }
+
+ return off;
+}
+
+static unsigned int tifm_ms_write_data(struct tifm_ms *host,
+ unsigned char *buf, unsigned int length)
+{
+ struct tifm_dev *sock = host->dev;
+ unsigned int off = 0;
+
+ if (host->io_pos) {
+ while (host->io_pos < 4 && length) {
+ host->io_word |= buf[off++] << (host->io_pos * 8);
+ host->io_pos++;
+ length--;
+ }
+ }
+
+ if (host->io_pos == 4
+ && !(TIFM_MS_STAT_FUL & readl(sock->addr + SOCK_MS_STATUS))) {
+ writel(TIFM_MS_SYS_FDIR | readl(sock->addr + SOCK_MS_SYSTEM),
+ sock->addr + SOCK_MS_SYSTEM);
+ writel(host->io_word, sock->addr + SOCK_MS_DATA);
+ host->io_pos = 0;
+ host->io_word = 0;
+ } else if (host->io_pos) {
+ return off;
+ }
+
+ if (!length)
+ return off;
+
+ while (!(TIFM_MS_STAT_FUL & readl(sock->addr + SOCK_MS_STATUS))) {
+ if (length < 4)
+ break;
+ writel(TIFM_MS_SYS_FDIR | readl(sock->addr + SOCK_MS_SYSTEM),
+ sock->addr + SOCK_MS_SYSTEM);
+ __raw_writel(*(unsigned int *)(buf + off),
+ sock->addr + SOCK_MS_DATA);
+ length -= 4;
+ off += 4;
+ }
+
+ switch (length) {
+ case 3:
+ host->io_word |= buf[off + 2] << 16;
+ host->io_pos++;
+ fallthrough;
+ case 2:
+ host->io_word |= buf[off + 1] << 8;
+ host->io_pos++;
+ fallthrough;
+ case 1:
+ host->io_word |= buf[off];
+ host->io_pos++;
+ }
+
+ off += host->io_pos;
+
+ return off;
+}
+
+static unsigned int tifm_ms_transfer_data(struct tifm_ms *host)
+{
+ struct tifm_dev *sock = host->dev;
+ unsigned int length;
+ unsigned int off;
+ unsigned int t_size, p_cnt;
+ unsigned char *buf;
+ struct page *pg;
+ unsigned long flags = 0;
+
+ if (host->req->long_data) {
+ length = host->req->sg.length - host->block_pos;
+ off = host->req->sg.offset + host->block_pos;
+ } else {
+ length = host->req->data_len - host->block_pos;
+ off = 0;
+ }
+ dev_dbg(&sock->dev, "fifo data transfer, %d, %d\n", length,
+ host->block_pos);
+
+ while (length) {
+ unsigned int p_off;
+
+ if (host->req->long_data) {
+ pg = nth_page(sg_page(&host->req->sg),
+ off >> PAGE_SHIFT);
+ p_off = offset_in_page(off);
+ p_cnt = PAGE_SIZE - p_off;
+ p_cnt = min(p_cnt, length);
+
+ local_irq_save(flags);
+ buf = kmap_atomic(pg) + p_off;
+ } else {
+ buf = host->req->data + host->block_pos;
+ p_cnt = host->req->data_len - host->block_pos;
+ }
+
+ t_size = host->req->data_dir == WRITE
+ ? tifm_ms_write_data(host, buf, p_cnt)
+ : tifm_ms_read_data(host, buf, p_cnt);
+
+ if (host->req->long_data) {
+ kunmap_atomic(buf - p_off);
+ local_irq_restore(flags);
+ }
+
+ if (!t_size)
+ break;
+ host->block_pos += t_size;
+ length -= t_size;
+ off += t_size;
+ }
+
+ dev_dbg(&sock->dev, "fifo data transfer, %d remaining\n", length);
+ if (!length && (host->req->data_dir == WRITE)) {
+ if (host->io_pos) {
+ writel(TIFM_MS_SYS_FDIR
+ | readl(sock->addr + SOCK_MS_SYSTEM),
+ sock->addr + SOCK_MS_SYSTEM);
+ writel(host->io_word, sock->addr + SOCK_MS_DATA);
+ }
+ writel(TIFM_MS_SYS_FDIR
+ | readl(sock->addr + SOCK_MS_SYSTEM),
+ sock->addr + SOCK_MS_SYSTEM);
+ writel(0, sock->addr + SOCK_MS_DATA);
+ } else {
+ readl(sock->addr + SOCK_MS_DATA);
+ }
+
+ return length;
+}
+
+static int tifm_ms_issue_cmd(struct tifm_ms *host)
+{
+ struct tifm_dev *sock = host->dev;
+ unsigned int data_len, cmd, sys_param;
+
+ host->cmd_flags = 0;
+ host->block_pos = 0;
+ host->io_pos = 0;
+ host->io_word = 0;
+ host->cmd_flags = 0;
+
+ host->use_dma = !no_dma;
+
+ if (host->req->long_data) {
+ data_len = host->req->sg.length;
+ if (!is_power_of_2(data_len))
+ host->use_dma = 0;
+ } else {
+ data_len = host->req->data_len;
+ host->use_dma = 0;
+ }
+
+ writel(TIFM_FIFO_INT_SETALL,
+ sock->addr + SOCK_DMA_FIFO_INT_ENABLE_CLEAR);
+ writel(TIFM_FIFO_ENABLE,
+ sock->addr + SOCK_FIFO_CONTROL);
+
+ if (host->use_dma) {
+ if (1 != tifm_map_sg(sock, &host->req->sg, 1,
+ host->req->data_dir == READ
+ ? DMA_FROM_DEVICE
+ : DMA_TO_DEVICE)) {
+ host->req->error = -ENOMEM;
+ return host->req->error;
+ }
+ data_len = sg_dma_len(&host->req->sg);
+
+ writel(ilog2(data_len) - 2,
+ sock->addr + SOCK_FIFO_PAGE_SIZE);
+ writel(TIFM_FIFO_INTMASK,
+ sock->addr + SOCK_DMA_FIFO_INT_ENABLE_SET);
+ sys_param = TIFM_DMA_EN | (1 << 8);
+ if (host->req->data_dir == WRITE)
+ sys_param |= TIFM_DMA_TX;
+
+ writel(TIFM_FIFO_INTMASK,
+ sock->addr + SOCK_DMA_FIFO_INT_ENABLE_SET);
+
+ writel(sg_dma_address(&host->req->sg),
+ sock->addr + SOCK_DMA_ADDRESS);
+ writel(sys_param, sock->addr + SOCK_DMA_CONTROL);
+ } else {
+ writel(host->mode_mask | TIFM_MS_SYS_FIFO,
+ sock->addr + SOCK_MS_SYSTEM);
+
+ writel(TIFM_FIFO_MORE,
+ sock->addr + SOCK_DMA_FIFO_INT_ENABLE_SET);
+ }
+
+ mod_timer(&host->timer, jiffies + host->timeout_jiffies);
+ writel(TIFM_CTRL_LED | readl(sock->addr + SOCK_CONTROL),
+ sock->addr + SOCK_CONTROL);
+ host->req->error = 0;
+
+ sys_param = readl(sock->addr + SOCK_MS_SYSTEM);
+ sys_param |= TIFM_MS_SYS_INTCLR;
+
+ if (host->use_dma)
+ sys_param |= TIFM_MS_SYS_DMA;
+ else
+ sys_param &= ~TIFM_MS_SYS_DMA;
+
+ writel(sys_param, sock->addr + SOCK_MS_SYSTEM);
+
+ cmd = (host->req->tpc & 0xf) << 12;
+ cmd |= data_len;
+ writel(cmd, sock->addr + SOCK_MS_COMMAND);
+
+ dev_dbg(&sock->dev, "executing TPC %x, %x\n", cmd, sys_param);
+ return 0;
+}
+
+static void tifm_ms_complete_cmd(struct tifm_ms *host)
+{
+ struct tifm_dev *sock = host->dev;
+ struct memstick_host *msh = tifm_get_drvdata(sock);
+ int rc;
+
+ del_timer(&host->timer);
+
+ host->req->int_reg = readl(sock->addr + SOCK_MS_STATUS) & 0xff;
+ host->req->int_reg = (host->req->int_reg & 1)
+ | ((host->req->int_reg << 4) & 0xe0);
+
+ writel(TIFM_FIFO_INT_SETALL,
+ sock->addr + SOCK_DMA_FIFO_INT_ENABLE_CLEAR);
+ writel(TIFM_DMA_RESET, sock->addr + SOCK_DMA_CONTROL);
+
+ if (host->use_dma) {
+ tifm_unmap_sg(sock, &host->req->sg, 1,
+ host->req->data_dir == READ
+ ? DMA_FROM_DEVICE
+ : DMA_TO_DEVICE);
+ }
+
+ writel((~TIFM_CTRL_LED) & readl(sock->addr + SOCK_CONTROL),
+ sock->addr + SOCK_CONTROL);
+
+ dev_dbg(&sock->dev, "TPC complete\n");
+ do {
+ rc = memstick_next_req(msh, &host->req);
+ } while (!rc && tifm_ms_issue_cmd(host));
+}
+
+static int tifm_ms_check_status(struct tifm_ms *host)
+{
+ if (!host->req->error) {
+ if (!(host->cmd_flags & CMD_READY))
+ return 1;
+ if (!(host->cmd_flags & FIFO_READY))
+ return 1;
+ if (host->req->need_card_int
+ && !(host->cmd_flags & CARD_INT))
+ return 1;
+ }
+ return 0;
+}
+
+/* Called from interrupt handler */
+static void tifm_ms_data_event(struct tifm_dev *sock)
+{
+ struct tifm_ms *host;
+ unsigned int fifo_status = 0, host_status = 0;
+ int rc = 1;
+
+ spin_lock(&sock->lock);
+ host = memstick_priv((struct memstick_host *)tifm_get_drvdata(sock));
+ fifo_status = readl(sock->addr + SOCK_DMA_FIFO_STATUS);
+ host_status = readl(sock->addr + SOCK_MS_STATUS);
+ dev_dbg(&sock->dev,
+ "data event: fifo_status %x, host_status %x, flags %x\n",
+ fifo_status, host_status, host->cmd_flags);
+
+ if (host->req) {
+ if (host->use_dma && (fifo_status & 1)) {
+ host->cmd_flags |= FIFO_READY;
+ rc = tifm_ms_check_status(host);
+ }
+ if (!host->use_dma && (fifo_status & TIFM_FIFO_MORE)) {
+ if (!tifm_ms_transfer_data(host)) {
+ host->cmd_flags |= FIFO_READY;
+ rc = tifm_ms_check_status(host);
+ }
+ }
+ }
+
+ writel(fifo_status, sock->addr + SOCK_DMA_FIFO_STATUS);
+ if (!rc)
+ tifm_ms_complete_cmd(host);
+
+ spin_unlock(&sock->lock);
+}
+
+
+/* Called from interrupt handler */
+static void tifm_ms_card_event(struct tifm_dev *sock)
+{
+ struct tifm_ms *host;
+ unsigned int host_status = 0;
+ int rc = 1;
+
+ spin_lock(&sock->lock);
+ host = memstick_priv((struct memstick_host *)tifm_get_drvdata(sock));
+ host_status = readl(sock->addr + SOCK_MS_STATUS);
+ dev_dbg(&sock->dev, "host event: host_status %x, flags %x\n",
+ host_status, host->cmd_flags);
+
+ if (host->req) {
+ if (host_status & TIFM_MS_STAT_TOE)
+ host->req->error = -ETIME;
+ else if (host_status & TIFM_MS_STAT_CRC)
+ host->req->error = -EILSEQ;
+
+ if (host_status & TIFM_MS_STAT_RDY)
+ host->cmd_flags |= CMD_READY;
+
+ if (host_status & TIFM_MS_STAT_MSINT)
+ host->cmd_flags |= CARD_INT;
+
+ rc = tifm_ms_check_status(host);
+
+ }
+
+ writel(TIFM_MS_SYS_INTCLR | readl(sock->addr + SOCK_MS_SYSTEM),
+ sock->addr + SOCK_MS_SYSTEM);
+
+ if (!rc)
+ tifm_ms_complete_cmd(host);
+
+ spin_unlock(&sock->lock);
+ return;
+}
+
+static void tifm_ms_req_tasklet(unsigned long data)
+{
+ struct memstick_host *msh = (struct memstick_host *)data;
+ struct tifm_ms *host = memstick_priv(msh);
+ struct tifm_dev *sock = host->dev;
+ unsigned long flags;
+ int rc;
+
+ spin_lock_irqsave(&sock->lock, flags);
+ if (!host->req) {
+ if (host->eject) {
+ do {
+ rc = memstick_next_req(msh, &host->req);
+ if (!rc)
+ host->req->error = -ETIME;
+ } while (!rc);
+ spin_unlock_irqrestore(&sock->lock, flags);
+ return;
+ }
+
+ do {
+ rc = memstick_next_req(msh, &host->req);
+ } while (!rc && tifm_ms_issue_cmd(host));
+ }
+ spin_unlock_irqrestore(&sock->lock, flags);
+}
+
+static void tifm_ms_dummy_submit(struct memstick_host *msh)
+{
+ return;
+}
+
+static void tifm_ms_submit_req(struct memstick_host *msh)
+{
+ struct tifm_ms *host = memstick_priv(msh);
+
+ tasklet_schedule(&host->notify);
+}
+
+static int tifm_ms_set_param(struct memstick_host *msh,
+ enum memstick_param param,
+ int value)
+{
+ struct tifm_ms *host = memstick_priv(msh);
+ struct tifm_dev *sock = host->dev;
+
+ switch (param) {
+ case MEMSTICK_POWER:
+ /* also affected by media detection mechanism */
+ if (value == MEMSTICK_POWER_ON) {
+ host->mode_mask = TIFM_MS_SYS_SRAC | TIFM_MS_SYS_REI;
+ writel(TIFM_MS_SYS_RESET, sock->addr + SOCK_MS_SYSTEM);
+ writel(TIFM_MS_SYS_FCLR | TIFM_MS_SYS_INTCLR,
+ sock->addr + SOCK_MS_SYSTEM);
+ writel(0xffffffff, sock->addr + SOCK_MS_STATUS);
+ } else if (value == MEMSTICK_POWER_OFF) {
+ writel(TIFM_MS_SYS_FCLR | TIFM_MS_SYS_INTCLR,
+ sock->addr + SOCK_MS_SYSTEM);
+ writel(0xffffffff, sock->addr + SOCK_MS_STATUS);
+ } else
+ return -EINVAL;
+ break;
+ case MEMSTICK_INTERFACE:
+ if (value == MEMSTICK_SERIAL) {
+ host->mode_mask = TIFM_MS_SYS_SRAC | TIFM_MS_SYS_REI;
+ writel((~TIFM_CTRL_FAST_CLK)
+ & readl(sock->addr + SOCK_CONTROL),
+ sock->addr + SOCK_CONTROL);
+ } else if (value == MEMSTICK_PAR4) {
+ host->mode_mask = 0;
+ writel(TIFM_CTRL_FAST_CLK
+ | readl(sock->addr + SOCK_CONTROL),
+ sock->addr + SOCK_CONTROL);
+ } else
+ return -EINVAL;
+ break;
+ }
+
+ return 0;
+}
+
+static void tifm_ms_abort(struct timer_list *t)
+{
+ struct tifm_ms *host = from_timer(host, t, timer);
+
+ dev_dbg(&host->dev->dev, "status %x\n",
+ readl(host->dev->addr + SOCK_MS_STATUS));
+ printk(KERN_ERR
+ "%s : card failed to respond for a long period of time "
+ "(%x, %x)\n",
+ dev_name(&host->dev->dev), host->req ? host->req->tpc : 0,
+ host->cmd_flags);
+
+ tifm_eject(host->dev);
+}
+
+static int tifm_ms_probe(struct tifm_dev *sock)
+{
+ struct memstick_host *msh;
+ struct tifm_ms *host;
+ int rc = -EIO;
+
+ if (!(TIFM_SOCK_STATE_OCCUPIED
+ & readl(sock->addr + SOCK_PRESENT_STATE))) {
+ printk(KERN_WARNING "%s : card gone, unexpectedly\n",
+ dev_name(&sock->dev));
+ return rc;
+ }
+
+ msh = memstick_alloc_host(sizeof(struct tifm_ms), &sock->dev);
+ if (!msh)
+ return -ENOMEM;
+
+ host = memstick_priv(msh);
+ tifm_set_drvdata(sock, msh);
+ host->dev = sock;
+ host->timeout_jiffies = msecs_to_jiffies(1000);
+
+ timer_setup(&host->timer, tifm_ms_abort, 0);
+ tasklet_init(&host->notify, tifm_ms_req_tasklet, (unsigned long)msh);
+
+ msh->request = tifm_ms_submit_req;
+ msh->set_param = tifm_ms_set_param;
+ sock->card_event = tifm_ms_card_event;
+ sock->data_event = tifm_ms_data_event;
+ if (tifm_has_ms_pif(sock))
+ msh->caps |= MEMSTICK_CAP_PAR4;
+
+ rc = memstick_add_host(msh);
+ if (!rc)
+ return 0;
+
+ memstick_free_host(msh);
+ return rc;
+}
+
+static void tifm_ms_remove(struct tifm_dev *sock)
+{
+ struct memstick_host *msh = tifm_get_drvdata(sock);
+ struct tifm_ms *host = memstick_priv(msh);
+ int rc = 0;
+ unsigned long flags;
+
+ msh->request = tifm_ms_dummy_submit;
+ tasklet_kill(&host->notify);
+ spin_lock_irqsave(&sock->lock, flags);
+ host->eject = 1;
+ if (host->req) {
+ del_timer(&host->timer);
+ writel(TIFM_FIFO_INT_SETALL,
+ sock->addr + SOCK_DMA_FIFO_INT_ENABLE_CLEAR);
+ writel(TIFM_DMA_RESET, sock->addr + SOCK_DMA_CONTROL);
+ if (host->use_dma)
+ tifm_unmap_sg(sock, &host->req->sg, 1,
+ host->req->data_dir == READ
+ ? DMA_TO_DEVICE
+ : DMA_FROM_DEVICE);
+ host->req->error = -ETIME;
+
+ do {
+ rc = memstick_next_req(msh, &host->req);
+ if (!rc)
+ host->req->error = -ETIME;
+ } while (!rc);
+ }
+ spin_unlock_irqrestore(&sock->lock, flags);
+
+ memstick_remove_host(msh);
+ memstick_free_host(msh);
+}
+
+#ifdef CONFIG_PM
+
+static int tifm_ms_suspend(struct tifm_dev *sock, pm_message_t state)
+{
+ struct memstick_host *msh = tifm_get_drvdata(sock);
+
+ memstick_suspend_host(msh);
+ return 0;
+}
+
+static int tifm_ms_resume(struct tifm_dev *sock)
+{
+ struct memstick_host *msh = tifm_get_drvdata(sock);
+
+ memstick_resume_host(msh);
+ return 0;
+}
+
+#else
+
+#define tifm_ms_suspend NULL
+#define tifm_ms_resume NULL
+
+#endif /* CONFIG_PM */
+
+static struct tifm_device_id tifm_ms_id_tbl[] = {
+ { TIFM_TYPE_MS }, { 0 }
+};
+
+static struct tifm_driver tifm_ms_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE
+ },
+ .id_table = tifm_ms_id_tbl,
+ .probe = tifm_ms_probe,
+ .remove = tifm_ms_remove,
+ .suspend = tifm_ms_suspend,
+ .resume = tifm_ms_resume
+};
+
+static int __init tifm_ms_init(void)
+{
+ return tifm_register_driver(&tifm_ms_driver);
+}
+
+static void __exit tifm_ms_exit(void)
+{
+ tifm_unregister_driver(&tifm_ms_driver);
+}
+
+MODULE_AUTHOR("Alex Dubov");
+MODULE_DESCRIPTION("TI FlashMedia MemoryStick driver");
+MODULE_LICENSE("GPL");
+MODULE_DEVICE_TABLE(tifm, tifm_ms_id_tbl);
+
+module_init(tifm_ms_init);
+module_exit(tifm_ms_exit);