lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Date:	Sun, 17 Oct 2010 01:12:51 +0200
From:	Maxim Levitsky <maximlevitsky@...il.com>
To:	Alex Dubov <oakad@...oo.com>
Cc:	Andrew Morton <akpm@...ux-foundation.org>,
	LKML <linux-kernel@...r.kernel.org>,
	Maxim Levitsky <maximlevitsky@...il.com>
Subject: [PATCH 3/6] memstick: jmb38x: Driver rework.

Many of driver parts were rewritten, code simplified,
a lot of debbuging code added.

Tested with ms standard and ms pro cards.

DMA is now used for all transfers by default.
(except < 8 TPC that are written through TPC_P0/1)
That is used to work around hardware bug.

Clock setup fixed as suggested, serial IO works now.

No performance regressions (even a small improvement).

Signed-off-by: Maxim Levitsky <maximlevitsky@...il.com>
---
 drivers/memstick/host/jmb38x_ms.c | 1198 ++++++++++++++++++-------------------
 drivers/memstick/host/jmb38x_ms.h |  200 ++++++
 2 files changed, 775 insertions(+), 623 deletions(-)
 create mode 100644 drivers/memstick/host/jmb38x_ms.h

diff --git a/drivers/memstick/host/jmb38x_ms.c b/drivers/memstick/host/jmb38x_ms.c
index f2b894c..4ee68b2 100644
--- a/drivers/memstick/host/jmb38x_ms.c
+++ b/drivers/memstick/host/jmb38x_ms.c
@@ -2,6 +2,7 @@
  *  jmb38x_ms.c - JMicron jmb38x MemoryStick card reader
  *
  *  Copyright (C) 2008 Alex Dubov <oakad@...oo.com>
+ *  Copyright (C) 2010 Maxim Levitsky <maximlevitsky@...il.com>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2 as
@@ -17,746 +18,672 @@
 #include <linux/highmem.h>
 #include <linux/memstick.h>
 #include <linux/slab.h>
-
-#define DRIVER_NAME "jmb38x_ms"
+#include "jmb38x_ms.h"
 
 static int 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
-};
+static int debug;
 
-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_request *req;
-	unsigned char           cmd_flags;
-	unsigned char           io_pos;
-	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_40MHZ   0x00000001
-#define CLOCK_CONTROL_50MHZ   0x0000000a
-#define CLOCK_CONTROL_60MHZ   0x00000008
-#define CLOCK_CONTROL_62_5MHZ 0x0000000c
-#define CLOCK_CONTROL_OFF     0x00000000
-
-#define PCI_CTL_CLOCK_DLY_ADDR   0x000000b0
-#define PCI_CTL_CLOCK_DLY_MASK_A 0x00000f00
-#define PCI_CTL_CLOCK_DLY_MASK_B 0x0000f000
-
-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)
+/* Read a register*/
+static inline u32 j38ms_read_reg(struct j38ms_host *host, int address)
 {
-	unsigned int off = 0;
+	u32 value =  readl(host->addr + address);
+	dbg_reg(host, "reg 0x%02x == 0x%08x", address, value);
+	return value;
+}
 
-	while (host->io_pos && length) {
-		buf[off++] = host->io_word[0] & 0xff;
-		host->io_word[0] >>= 8;
-		length--;
-		host->io_pos--;
-	}
+/* Write a register */
+static inline void j38ms_write_reg(struct j38ms_host *host,
+						int address, u32 value)
+{
+	dbg_reg(host, "reg 0x%02x <- 0x%08x", address, cpu_to_le32(value));
+	writel(value, host->addr + address);
+}
 
-	if (!length)
-		return off;
+/* Read a register without endiannes conversion*/
+static inline u32 j38ms_read_reg_raw(struct j38ms_host *host, int address)
+{
+	u32 value =  __raw_readl(host->addr + address);
+	dbg_reg(host, "reg 0x%02x == 0x%08x", address, cpu_to_le32(value));
+	return value;
+}
 
-	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;
-		}
-	}
+/* Write a register without endiannes conversion */
+static inline void j38ms_write_reg_raw(struct j38ms_host *host,
+						int address, u32 value)
+{
+	dbg_reg(host, "reg 0x%02x <- 0x%08x", address, value);
+	__raw_writel(value, host->addr + address);
+}
 
-	return off;
+/* Set specific bits in a register*/
+static inline void j38ms_set_reg_mask(struct j38ms_host *host,
+						int address, u32 mask)
+{
+	u32 reg = readl(host->addr + address);
+	dbg_reg(host, "reg 0x%02x |= 0x%08x (old =0x%08x)", address, mask, reg);
+	writel(reg | mask , host->addr + address);
 }
 
-static unsigned int jmb38x_ms_read_reg_data(struct jmb38x_ms_host *host,
-					    unsigned char *buf,
-					    unsigned int length)
+/* Clear specific bits in a register*/
+static inline void j38ms_clear_reg_mask(struct j38ms_host *host,
+						int address, u32 mask)
 {
-	unsigned int off = 0;
+	u32 reg = readl(host->addr + address);
+	dbg_reg(host, "reg 0x%02x &= 0x%08x (old = 0x%08x, mask = 0x%08x)",
+						address, ~mask, reg, mask);
+	writel(reg & ~mask, host->addr + address);
+}
 
-	while (host->io_pos > 4 && length) {
-		buf[off++] = host->io_word[0] & 0xff;
-		host->io_word[0] >>= 8;
-		length--;
-		host->io_pos--;
+/* Reads one DWORD via PIO. returns -EAGAIN if fifo is empty */
+static inline int j38ms_read_fifo_dword_pio(struct j38ms_host *host, u32 *dword)
+{
+	if (unlikely(j38ms_read_reg(host, STATUS) & STATUS_FIFO_EMPTY)) {
+		dbg(host, "PIO: FIFO empty");
+		return -EAGAIN;
 	}
 
-	if (!length)
-		return off;
+	*dword = j38ms_read_reg_raw(host, DATA);
+	dbg(host, "PIO: read: %08x", *dword);
+	return 0;
+}
 
-	while (host->io_pos && length) {
-		buf[off++] = host->io_word[1] & 0xff;
-		host->io_word[1] >>= 8;
-		length--;
-		host->io_pos--;
+/* Writes one DWORD via PIO. returns -EAGAIN if fifo is full */
+static inline int j38ms_write_fifo_dword_pio(struct j38ms_host *host, u32 dword)
+{
+	if (unlikely(j38ms_read_reg(host, STATUS) & STATUS_FIFO_FULL)) {
+		dbg(host, "PIO: FIFO full");
+		return -EAGAIN;
 	}
 
-	return off;
+	dbg(host, "PIO: writing: %08x", dword);
+	j38ms_write_reg_raw(host, DATA, dword);
+	return 0;
 }
 
-static unsigned int jmb38x_ms_write_data(struct jmb38x_ms_host *host,
-					 unsigned char *buf,
-					 unsigned int length)
+/* Read TPC data using PIO */
+static unsigned int j38ms_read_fifo_pio(struct j38ms_host *host,
+				unsigned char *buf, unsigned int length)
 {
-	unsigned int off = 0;
+	unsigned int orig_len = length;
+	unsigned char tmp_buf[4];
 
-	if (host->io_pos) {
-		while (host->io_pos < 4 && length) {
-			host->io_word[0] |=  buf[off++] << (host->io_pos * 8);
-			host->io_pos++;
-			length--;
-		}
+	/* Read bytes from last saved part */
+	if (host->pio_tmp_buf_len) {
+		int count = min(host->pio_tmp_buf_len, length);
+		memcpy(buf, host->pio_tmp_buf, count);
+		buf += count;
+		length -= count;
 	}
 
-	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;
-	}
+	/* Read aligned data*/
+	for (; length >= 4 ; buf += 4, length -= 4)
+		if (j38ms_read_fifo_dword_pio(host, (u32 *)buf))
+			return orig_len - length;
 
-	if (!length)
-		return off;
+	/* Read last 4 bytes, and
+			save unconsumed part of it to the pio_tmp_buf */
+	if (length && !j38ms_read_fifo_dword_pio(host, (u32 *)tmp_buf)) {
 
-	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++;
-	case 2:
-		host->io_word[0] |= buf[off + 1] << 8;
-		host->io_pos++;
-	case 1:
-		host->io_word[0] |= buf[off];
-		host->io_pos++;
+		host->pio_tmp_buf_len = 4 - length;
+		memcpy(buf, tmp_buf, length);
+		memcpy(host->pio_tmp_buf,
+				tmp_buf + length, host->pio_tmp_buf_len);
+		length = 0;
 	}
 
-	off += host->io_pos;
-
-	return off;
+	return orig_len - length;
 }
 
-static unsigned int jmb38x_ms_write_reg_data(struct jmb38x_ms_host *host,
-					     unsigned char *buf,
-					     unsigned int length)
+/* Write TPC data through normal PIO fifo */
+static unsigned int j38ms_write_fifo_pio(struct j38ms_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--;
+	unsigned int orig_len = length;
+
+	/* Complete the last saved bytes*/
+	if (host->pio_tmp_buf_len) {
+		int count = min(4 - host->pio_tmp_buf_len, length);
+		memcpy(host->pio_tmp_buf + host->pio_tmp_buf_len, buf, count);
+		buf += count;
+		length -= count;
+
+		if (host->pio_tmp_buf_len == 4) {
+			if (j38ms_write_fifo_dword_pio(
+					host, *(u32 *)host->pio_tmp_buf))
+				return orig_len - length;
+			else
+				host->pio_tmp_buf_len = 0;
+		}
 	}
 
-	if (!length)
-		return off;
+	/* Write aligned data to hardware */
+	for (; length >= 4 ; length -= 4, buf += 4)
+		if (j38ms_write_fifo_dword_pio(host, *(u32 *)buf))
+			return orig_len - length;
 
-	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--;
+	/* Save last 3-1 bytes to buffer, because we can't send them now*/
+	if (length) {
+		memset(host->pio_tmp_buf, 0, 4);
+		memcpy(host->pio_tmp_buf, buf, length);
+		host->pio_tmp_buf_len = length;
 	}
 
-	return off;
+	return orig_len - length;
 }
 
-static int jmb38x_ms_transfer_data(struct jmb38x_ms_host *host)
+
+/* Transfer data between current request and FIFO */
+static void j38ms_transfer_pio(struct j38ms_host *host)
 {
-	unsigned int length;
-	unsigned int off;
-	unsigned int t_size, p_cnt;
 	unsigned char *buf;
-	struct page *pg;
+	unsigned int len;
 	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;
-	}
+	if (host->req->long_data)
+		local_irq_save(flags);
 
-	while (length) {
-		unsigned int uninitialized_var(p_off);
+	dbg_v(host, "PIO: new transfer");
 
+	while (1) {
 		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, KM_BIO_SRC_IRQ) + p_off;
+			if (!sg_miter_next(&host->pio_sg_iter))
+				break;
+
+			buf = host->pio_sg_iter.addr;
+			len = host->pio_sg_iter.length;
 		} else {
-			buf = host->req->data + host->block_pos;
-			p_cnt = host->req->data_len - host->block_pos;
+			buf = host->req->data + host->pio_offset;
+			len = host->req->data_len - host->pio_offset;
+
+			if (!len)
+				break;
 		}
 
-		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);
+		len = host->req->data_dir == WRITE ?
+			j38ms_write_fifo_pio(host, buf, len) :
+			j38ms_read_fifo_pio(host, buf, len);
 
-		if (host->req->long_data) {
-			kunmap_atomic(buf - p_off, KM_BIO_SRC_IRQ);
-			local_irq_restore(flags);
-		}
+		if (host->req->long_data)
+			host->pio_sg_iter.consumed = len;
+		else
+			host->pio_offset += len;
 
-		if (!t_size)
-			break;
-		host->block_pos += t_size;
-		length -= t_size;
-		off += t_size;
+		dbg(host, "PIO: transfered %d bytes", len);
+		if (!len)
+			goto exit;
 	}
 
-	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);
-		}
+	/* Write last non-complete dword of the data */
+	if (host->req->data_dir == WRITE && host->pio_tmp_buf_len) {
+		if (!j38ms_write_fifo_dword_pio(host,
+						*(u32 *)host->pio_tmp_buf))
+			host->pio_tmp_buf_len = 0;
 	}
+exit:
+	if (host->req->long_data) {
+		sg_miter_stop(&host->pio_sg_iter);
+		local_irq_restore(flags);
+	}
+}
+
+/* Read short TPC data (up to 8 bytes) via 2 special registers*/
+static void j38ms_read_tpc_inline(struct j38ms_host *host)
+{
+       *(u32 *)(host->req->data + 0) = j38ms_read_reg_raw(host, TPC_P0);
+       *(u32 *)(host->req->data + 4) = j38ms_read_reg_raw(host, TPC_P1);
+}
 
-	return length;
+/* Write short TPC data (up to 8 bytes) through 2 special registers */
+static void j38ms_write_tpc_inline(struct j38ms_host *host)
+{
+	j38ms_write_reg_raw(host, TPC_P0, *(u32 *)(host->req->data + 0));
+	j38ms_write_reg_raw(host, TPC_P1, *(u32 *)(host->req->data + 4));
 }
 
-static int jmb38x_ms_issue_cmd(struct memstick_host *msh)
+/*
+ * Start execution of a TPC:
+ * NOTES:
+ *
+ * PIO writes trigger wierd hardware bug that causes DMA writes
+ *  to fail.
+ *
+ * length alignmemt:
+ *	Short (<=8) TPC don't have any alignment problems.
+ *	DMA read/writes must be 4 aligned
+ *	PIO _reads_ must be aligned. Writes can be not aligned
+ *
+ */
+
+static int j38ms_execute_tpc(struct j38ms_host *host)
 {
-	struct jmb38x_ms_host *host = memstick_priv(msh);
-	unsigned char *data;
-	unsigned int data_len, cmd, t_val;
+	u32 cmd = 0, t_val;
+	unsigned int data_len = host->req->long_data ?
+		host->req->sg.length : host->req->data_len;
+	bool is_read = host->req->data_dir == READ;
+	dma_addr_t dma_address;
 
-	if (!(STATUS_HAS_MEDIA & readl(host->addr + STATUS))) {
-		dev_dbg(&msh->dev, "no media status\n");
+	if (host->dead) {
 		host->req->error = -ETIME;
+		return 0;
+	}
+
+	if (!(j38ms_read_reg(host, STATUS) & STATUS_HAS_MEDIA)) {
+		dbg(host, "IO: card removed, refusing to send TPC");
+		host->req->error = -ENODEV;
 		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));
+	if (data_len > BLOCK_SIZE_MASK) {
+		dbg(host, "IO: too long TPC (len: %d)", data_len);
+		host->req->error = -ENOSYS;
+		return host->req->error;
+	}
 
-	host->cmd_flags = 0;
-	host->block_pos = 0;
-	host->io_pos = 0;
-	host->io_word[0] = 0;
-	host->io_word[1] = 0;
+	dbg(host, "IO: Start execution of %s",
+		memstick_debug_get_tpc_name(host->req->tpc));
 
-	cmd = host->req->tpc << 16;
-	cmd |= TPC_DATA_SEL;
 
-	if (host->req->data_dir == READ)
-		cmd |= TPC_DIR;
-	if (host->req->need_card_int)
-		cmd |= TPC_WAIT_INT;
+	dbg_v(host, "host control: %08x", j38ms_read_reg(host, HOST_CONTROL));
+	dbg_v(host, "int status: %08x", j38ms_read_reg(host, INT_STATUS));
+	dbg_v(host, "card status: %08x", j38ms_read_reg(host, STATUS));
 
-	data = host->req->data;
+	host->req->error = 0;
+	host->cmd_flags = 0;
 
-	if (!no_dma)
-		host->cmd_flags |= DMA_DATA;
+	cmd = host->req->tpc << 16;
+	if (is_read)
+		cmd |= TPC_DIR;
 
-	if (host->req->long_data) {
-		data_len = host->req->sg.length;
-	} else {
-		data_len = host->req->data_len;
-		host->cmd_flags &= ~DMA_DATA;
+	if (host->req->need_card_int) {
+		dbg_v(host, "IO: Will wait for card interrupt");
+		cmd |= TPC_WAIT_INT;
+		/* No, the TPC_GET_INT doesn't work.... */
 	}
 
-	if (data_len <= 8) {
-		cmd &= ~(TPC_DATA_SEL | 0xf);
+	/* Special case for short TPCs */
+	if (!host->req->long_data && data_len <= 8) {
+		dbg(host, "IO: Using 8 byte register window");
 		host->cmd_flags |= REG_DATA;
 		cmd |= data_len & 0xf;
-		host->cmd_flags &= ~DMA_DATA;
+
+		if (!is_read)
+			j38ms_write_tpc_inline(host);
+		goto exec;
 	}
 
-	if (host->cmd_flags & DMA_DATA) {
-		if (1 != pci_map_sg(host->chip->pdev, &host->req->sg, 1,
-				    host->req->data_dir == READ
-				    ? PCI_DMA_FROMDEVICE
-				    : PCI_DMA_TODEVICE)) {
-			host->req->error = -ENOMEM;
-			return host->req->error;
+	/* Otherwise use internal fifo*/
+	cmd |= TPC_DATA_SEL;
+
+	if (data_len & 0x03) {
+		dbg(host, "Hardware doesn't support not-aligned len TPCs!");
+		host->req->error = -ENOSYS;
+		return host->req->error;
+	}
+
+	/* DMA */
+	if (!no_dma) {
+
+		dbg(host, "IO: Using DMA");
+		host->cmd_flags |= DMA_DATA;
+
+		if (host->req->long_data) {
+
+			if (pci_map_sg(host->chip->pdev,
+				&host->req->sg, 1, is_read
+				? PCI_DMA_FROMDEVICE : PCI_DMA_TODEVICE) != 1) {
+
+				dbg(host, "IO: DMA map failed");
+				host->req->error = -ENOMEM;
+				return host->req->error;
+			}
+
+			/* We really shouldn't pretend we support that case */
+			if (sg_dma_len(&host->req->sg) != data_len) {
+				dbg(host, "IO: DMA len mismatch");
+				host->req->error = -EFAULT;
+				return host->req->error;
+			}
+
+			dma_address = sg_dma_address(&host->req->sg);
+
+		} else {
+			if (!is_read)
+				memcpy(host->dma_bounce_page,
+					host->req->data, data_len);
+			dma_address = host->dma_bus_address;
 		}
-		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);
+
+		j38ms_write_reg(host, BLOCK, data_len | BLOCK_COUNT_1BLOCK);
+		j38ms_write_reg(host, DMA_ADDRESS, dma_address);
+		j38ms_write_reg(host, DMA_CONTROL, DMA_CONTROL_ENABLE);
+	/* PIO */
 	} 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);
-		}
-	}
+		dbg(host, "IO: Using PIO");
+		host->cmd_flags |= PIO_DATA;
 
-	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;
+		host->pio_offset = 0;
+		host->pio_tmp_buf_len = 0;
+		memset(host->pio_tmp_buf, 0, 4);
+
+		if (host->req->long_data)
+			sg_miter_start(&host->pio_sg_iter,
+				&host->req->sg, 1, SG_MITER_ATOMIC |
+				(is_read ? SG_MITER_FROM_SG : SG_MITER_TO_SG));
 
-	writel(cmd, host->addr + TPC);
-	dev_dbg(&msh->dev, "executing TPC %08x, len %x\n", cmd, data_len);
+		/* Enable FIFO empty interrupts */
+		t_val = is_read ? INT_STATUS_FIFO_RRDY : INT_STATUS_FIFO_WRDY;
 
+		j38ms_write_reg(host, BLOCK, data_len | BLOCK_COUNT_1BLOCK);
+		j38ms_set_reg_mask(host, INT_SIGNAL_ENABLE, t_val);
+		j38ms_set_reg_mask(host, INT_STATUS_ENABLE, t_val);
+	}
+exec:
+	mod_timer(&host->timer, jiffies + host->timeout_jiffies);
+
+	/* Let the TPC fly... */
+	j38ms_write_reg(host, TPC, cmd);
+	j38ms_set_reg_mask(host, HOST_CONTROL, HOST_CONTROL_LED);
 	return 0;
 }
 
-static void jmb38x_ms_complete_cmd(struct memstick_host *msh, int last)
+/* Cleanups execution of current TPC */
+static void j38ms_complete_tpc(struct memstick_host *msh)
 {
-	struct jmb38x_ms_host *host = memstick_priv(msh);
-	unsigned int t_val = 0;
-	int rc;
-
+	struct j38ms_host *host = memstick_priv(msh);
 	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));
+	dbg(host, "IO: TPC complete (error : %d)", host->req->error);
 
-	host->req->int_reg = readl(host->addr + STATUS) & 0xff;
+	j38ms_write_reg(host, BLOCK, 0);
+	j38ms_write_reg(host, DMA_CONTROL, 0);
 
-	writel(0, host->addr + BLOCK);
-	writel(0, host->addr + DMA_CONTROL);
+	dbg_v(host, "host control: %08x", j38ms_read_reg(host, HOST_CONTROL));
+	dbg_v(host, "int status: %08x", j38ms_read_reg(host, INT_STATUS));
+	dbg_v(host, "card status: %08x", j38ms_read_reg(host, STATUS));
+
+	if (host->req->need_card_int)
+		host->req->int_reg = j38ms_read_reg(host, STATUS) & 0xFF;
 
 	if (host->cmd_flags & DMA_DATA) {
-		pci_unmap_sg(host->chip->pdev, &host->req->sg, 1,
-			     host->req->data_dir == READ
-			     ? PCI_DMA_FROMDEVICE : PCI_DMA_TODEVICE);
-	} 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;
+		if (host->req->long_data)
+			pci_unmap_sg(host->chip->pdev, &host->req->sg, 1,
+				host->req->data_dir == READ
+				? PCI_DMA_FROMDEVICE : PCI_DMA_TODEVICE);
+		else if (host->req->data_dir == READ)
+			memcpy(host->req->data,
+				host->dma_bounce_page, host->req->data_len);
 
-		writel(t_val, host->addr + INT_STATUS_ENABLE);
-		writel(t_val, host->addr + INT_SIGNAL_ENABLE);
-	}
+	} else if (host->cmd_flags & PIO_DATA) {
+		u32 t_val = INT_STATUS_FIFO_RRDY | INT_STATUS_FIFO_WRDY;
 
-	writel((~HOST_CONTROL_LED) & readl(host->addr + HOST_CONTROL),
-	       host->addr + HOST_CONTROL);
+		/* This is not known well */
+		j38ms_clear_reg_mask(host, INT_STATUS_ENABLE, t_val);
 
-	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);
+		/* This disables the IRQ to host, really */
+		j38ms_clear_reg_mask(host, INT_SIGNAL_ENABLE, t_val);
 	}
+
+	j38ms_clear_reg_mask(host, HOST_CONTROL, HOST_CONTROL_LED);
 }
 
-static irqreturn_t jmb38x_ms_isr(int irq, void *dev_id)
+/* Interrupt handler */
+static irqreturn_t j38ms_isr(int irq, void *dev_id)
 {
 	struct memstick_host *msh = dev_id;
-	struct jmb38x_ms_host *host = memstick_priv(msh);
-	unsigned int irq_status;
+	struct j38ms_host *host = memstick_priv(msh);
+	u32 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);
+	irq_status = j38ms_read_reg(host, INT_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
-				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;
-				}
-			}
+	dbg(host, "IRQ: status: %08x", irq_status);
 
-			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_ANY_ERR)
+		dbg(host, "IRQ: error");
+
+	if (irq_status & INT_STATUS_FIFO_RRDY)
+		dbg(host, "IRQ: FIFO is now ready for reading");
+
+	if (irq_status & INT_STATUS_FIFO_WRDY)
+		dbg(host, "IRQ: FIFO is now ready for writing");
+
+	if (irq_status & INT_STATUS_EOTRAN)
+		dbg(host, "IRQ: FIFO IO tranfer done");
+
+	if (irq_status & INT_STATUS_EOTPC)
+		dbg(host, "IRQ: TPC complete");
+
+	if (!host->req)
+		goto out;
+
+	/* Errors */
+	if (irq_status & INT_STATUS_ANY_ERR) {
+		host->req->error =
+			(irq_status & INT_STATUS_CRC_ERR) ? -EILSEQ : -EIO;
+		goto out;
+	}
+
+	/* End of TPC interrupt */
+	if (irq_status & INT_STATUS_EOTPC) {
+		host->cmd_flags |= CMD_READY;
+
+		if (host->cmd_flags & REG_DATA) {
+			if (host->req->data_dir == READ)
+				j38ms_read_tpc_inline(host);
+			host->cmd_flags |= FIFO_READY;
 		}
 	}
 
+	/* Fifo handling */
+	if (irq_status & INT_STATUS_EOTRAN)
+		host->cmd_flags |= FIFO_READY;
+	else if (irq_status & (INT_STATUS_FIFO_RRDY | INT_STATUS_FIFO_WRDY))
+		if (host->cmd_flags & PIO_DATA)
+			j38ms_transfer_pio(host);
+
+out:
 	if (irq_status & (INT_STATUS_MEDIA_IN | INT_STATUS_MEDIA_OUT)) {
-		dev_dbg(&host->chip->pdev->dev, "media changed\n");
+		dbg(host, "IRQ: media changed");
 		memstick_detect_change(msh);
 	}
 
-	writel(irq_status, host->addr + INT_STATUS);
+	/* Yes, interrupt status is cleared by setting the bits as usual */
+	j38ms_write_reg(host, INT_STATUS, irq_status);
 
-	if (host->req
-	    && (((host->cmd_flags & CMD_READY)
-		 && (host->cmd_flags & FIFO_READY))
-		|| host->req->error))
-		jmb38x_ms_complete_cmd(msh, 0);
+	if (host->req && (((host->cmd_flags & CMD_READY)
+		&& (host->cmd_flags & FIFO_READY)) || host->req->error)) {
+		j38ms_complete_tpc(msh);
+		tasklet_schedule(&host->tasklet);
+	}
 
 	spin_unlock(&host->lock);
 	return IRQ_HANDLED;
 }
 
-static void jmb38x_ms_abort(unsigned long data)
+/* Timer that is executed in absense of the interrupt */
+static void j38ms_irq_timeout(unsigned long data)
 {
-	struct memstick_host *msh = (struct memstick_host *)data;
-	struct jmb38x_ms_host *host = memstick_priv(msh);
+	struct j38ms_host *host = (struct j38ms_host *)data;
 	unsigned long flags;
 
-	dev_dbg(&host->chip->pdev->dev, "abort\n");
+	dbg(host, "interrupt timeout");
+
 	spin_lock_irqsave(&host->lock, flags);
 	if (host->req) {
 		host->req->error = -ETIME;
-		jmb38x_ms_complete_cmd(msh, 0);
+		j38ms_complete_tpc(host->msh);
+		tasklet_schedule(&host->tasklet);
 	}
+
 	spin_unlock_irqrestore(&host->lock, flags);
 }
 
-static void jmb38x_ms_req_tasklet(unsigned long data)
+static void j38ms_submit_tasklet(unsigned long data)
 {
-	struct memstick_host *msh = (struct memstick_host *)data;
-	struct jmb38x_ms_host *host = memstick_priv(msh);
+	struct j38ms_host *host = (struct j38ms_host *)data;
 	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));
-	}
+	j38ms_next_request(host);
 	spin_unlock_irqrestore(&host->lock, flags);
 }
 
-static void jmb38x_ms_dummy_submit(struct memstick_host *msh)
+/* Asks for next request from upper layer and starts its execution */
+static void j38ms_next_request(struct j38ms_host *host)
 {
-	return;
+	while (!memstick_next_req(host->msh, &host->req))
+		if (!j38ms_execute_tpc(host))
+			return;
 }
 
-static void jmb38x_ms_submit_req(struct memstick_host *msh)
+/* hardware reset */
+static int j38ms_reset(struct j38ms_host *host, u32 reset_bit)
 {
-	struct jmb38x_ms_host *host = memstick_priv(msh);
+	int i;
+	j38ms_set_reg_mask(host,
+			HOST_CONTROL, reset_bit | HOST_CONTROL_CLOCK_EN);
 
-	tasklet_schedule(&host->notify);
+	for (i = 0; i < 20; ++i) {
+		if (!(j38ms_read_reg(host, HOST_CONTROL) & reset_bit))
+			break;
+		ndelay(20);
+	}
+	return (j38ms_read_reg(host, HOST_CONTROL) & reset_bit) ? EIO : 0;
 }
 
-static int jmb38x_ms_reset(struct jmb38x_ms_host *host)
+/* Enable/disable the device */
+static int j38ms_power_device(struct j38ms_host *host, bool enable)
 {
-	int cnt;
+	int error;
 
-	writel(HOST_CONTROL_RESET_REQ | HOST_CONTROL_CLOCK_EN
-	       | readl(host->addr + HOST_CONTROL),
-	       host->addr + HOST_CONTROL);
-	mmiowb();
+	if (enable) {
 
-	for (cnt = 0; cnt < 20; ++cnt) {
-		if (!(HOST_CONTROL_RESET_REQ
-		      & readl(host->addr + HOST_CONTROL)))
-			goto reset_next;
+		error = j38ms_reset(host, HOST_CONTROL_RESET_REQ);
+		error = j38ms_reset(host, HOST_CONTROL_RESET);
 
-		ndelay(20);
-	}
-	dev_dbg(&host->chip->pdev->dev, "reset_req timeout\n");
-	/* return -EIO; */
+		if (error)
+			return error;
 
-reset_next:
-	writel(HOST_CONTROL_RESET | HOST_CONTROL_CLOCK_EN
-	       | readl(host->addr + HOST_CONTROL),
-	       host->addr + HOST_CONTROL);
-	mmiowb();
+		j38ms_write_reg(host, INT_SIGNAL_ENABLE, INT_STATUS_ALL);
+		j38ms_write_reg(host, INT_STATUS_ENABLE, INT_STATUS_ALL);
+		j38ms_write_reg(host, CLOCK_CONTROL, CLOCK_CONTROL_RESET);
 
-	for (cnt = 0; cnt < 20; ++cnt) {
-		if (!(HOST_CONTROL_RESET
-		      & readl(host->addr + HOST_CONTROL)))
-			goto reset_ok;
+		j38ms_write_reg(host, HOST_CONTROL,
+			HOST_CONTROL_POWER_EN |
+			HOST_CONTROL_CLOCK_EN |
+			HOST_CONTROL_HW_OC_P |
+			HOST_CONTROL_TDELAY_EN |
+			HOST_CONTROL_BSY_TIME);
+
+		j38ms_write_reg(host, PAD_PU_PD, host->id ?
+			PAD_PU_PD_ON_MS_SOCK1 : PAD_PU_PD_ON_MS_SOCK0);
+
+		j38ms_write_reg(host, PAD_OUTPUT_ENABLE, PAD_OUTPUT_ENABLE_MS);
+
+	} else {
+		j38ms_clear_reg_mask(host, HOST_CONTROL,
+			HOST_CONTROL_POWER_EN | HOST_CONTROL_CLOCK_EN);
+		j38ms_write_reg(host, PAD_OUTPUT_ENABLE, 0);
+		j38ms_write_reg(host, PAD_PU_PD, PAD_PU_PD_OFF);
+		j38ms_write_reg(host, CLOCK_CONTROL, CLOCK_CONTROL_OFF);
 
-		ndelay(20);
 	}
-	dev_dbg(&host->chip->pdev->dev, "reset timeout\n");
-	return -EIO;
+	msleep(100);
+	return 0;
+}
+
+/* Switch interface */
+static int j38ms_set_interface(struct j38ms_host *host, int interface)
+{
+	u32 host_ctl = j38ms_read_reg(host, HOST_CONTROL);
+	u32 clock_ctl = CLOCK_CONTROL_40MHZ;
+	u32 clock_delay;
+
+	host_ctl &= ~HOST_CONTROL_IF_MASK;
+	pci_read_config_dword(host->chip->pdev, PCI_CTL_CLOCK_DLY_ADDR,
+				&clock_delay);
+
+	clock_delay &= host->id ? ~PCI_CTL_CLOCK_DLY_MASK_B
+				: ~PCI_CTL_CLOCK_DLY_MASK_A;
+
+	if (interface == MEMSTICK_SERIAL) {
+
+		host_ctl |= HOST_CONTROL_IF_SERIAL;
+		clock_ctl = CLOCK_CONTROL_40MHZ;
+
+		host_ctl &= ~HOST_CONTROL_FAST_CLK;
+		host_ctl &= ~HOST_CONTROL_REO;
+		host_ctl |= HOST_CONTROL_REI;
+
+	} else if (interface == MEMSTICK_PAR4) {
+
+		host_ctl |= HOST_CONTROL_IF_PAR4;
+		clock_ctl = CLOCK_CONTROL_40MHZ;
+
+		host_ctl |= HOST_CONTROL_FAST_CLK;
+		host_ctl |= HOST_CONTROL_REO;
+		host_ctl &= ~HOST_CONTROL_REI;
+
+		clock_delay |= host->id ? (4 << 12) : (4 << 8);
+
+	} else if (interface == MEMSTICK_PAR8) {
+
+		host_ctl |= HOST_CONTROL_IF_PAR8;
+		clock_ctl = CLOCK_CONTROL_50MHZ;
+
+		host_ctl |= HOST_CONTROL_FAST_CLK;
+		host_ctl &= ~HOST_CONTROL_REO;
+		host_ctl &= ~HOST_CONTROL_REI;
+	} else
+		return -EINVAL;
 
-reset_ok:
-	mmiowb();
-	writel(INT_STATUS_ALL, host->addr + INT_SIGNAL_ENABLE);
-	writel(INT_STATUS_ALL, host->addr + INT_STATUS_ENABLE);
+	j38ms_write_reg(host, HOST_CONTROL, host_ctl);
+	j38ms_write_reg(host, CLOCK_CONTROL, clock_ctl);
+
+	pci_write_config_dword(host->chip->pdev,
+		PCI_CTL_CLOCK_DLY_ADDR, clock_delay);
+
+	host->interface = interface;
 	return 0;
 }
 
-static int jmb38x_ms_set_param(struct memstick_host *msh,
+/* external interface: control hardware settings */
+static int j38ms_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_40MHZ, clock_delay = 0;
-	int rc = 0;
+	struct j38ms_host *host = memstick_priv(msh);
 
 	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
-				    | HOST_CONTROL_HW_OC_P
-				    | HOST_CONTROL_TDELAY_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;
+		return j38ms_power_device(host, (value == MEMSTICK_POWER_ON));
 	case MEMSTICK_INTERFACE:
-		host_ctl &= ~(3 << HOST_CONTROL_IF_SHIFT);
-		pci_read_config_dword(host->chip->pdev,
-				      PCI_CTL_CLOCK_DLY_ADDR,
-				      &clock_delay);
-		clock_delay &= host->id ? ~PCI_CTL_CLOCK_DLY_MASK_B
-					: ~PCI_CTL_CLOCK_DLY_MASK_A;
-
-		if (value == MEMSTICK_SERIAL) {
-			host_ctl &= ~HOST_CONTROL_FAST_CLK;
-			host_ctl &= ~HOST_CONTROL_REO;
-			host_ctl |= HOST_CONTROL_IF_SERIAL
-				    << HOST_CONTROL_IF_SHIFT;
-			host_ctl |= HOST_CONTROL_REI;
-			clock_ctl = CLOCK_CONTROL_40MHZ;
-		} else if (value == MEMSTICK_PAR4) {
-			host_ctl |= HOST_CONTROL_FAST_CLK | HOST_CONTROL_REO;
-			host_ctl |= HOST_CONTROL_IF_PAR4
-				    << HOST_CONTROL_IF_SHIFT;
-			host_ctl &= ~HOST_CONTROL_REI;
-			clock_ctl = CLOCK_CONTROL_40MHZ;
-			clock_delay |= host->id ? (4 << 12) : (4 << 8);
-		} else if (value == MEMSTICK_PAR8) {
-			host_ctl |= HOST_CONTROL_FAST_CLK;
-			host_ctl |= HOST_CONTROL_IF_PAR8
-				    << HOST_CONTROL_IF_SHIFT;
-			host_ctl &= ~(HOST_CONTROL_REI | HOST_CONTROL_REO);
-			clock_ctl = CLOCK_CONTROL_50MHZ;
-		} else
-			return -EINVAL;
-
-		writel(host_ctl, host->addr + HOST_CONTROL);
-		writel(clock_ctl, host->addr + CLOCK_CONTROL);
-		pci_write_config_dword(host->chip->pdev,
-				       PCI_CTL_CLOCK_DLY_ADDR,
-				       clock_delay);
-		break;
+		return j38ms_set_interface(host, value);
+	default:
+		return -EINVAL;
 	};
-	return 0;
+}
+
+/* external interface: Submit new request from upper layer */
+static void j38ms_submit_req(struct memstick_host *msh)
+{
+	struct j38ms_host *host = memstick_priv(msh);
+	if (!host->req)
+		tasklet_schedule(&host->tasklet);
 }
 
 #ifdef CONFIG_PM
 
-static int jmb38x_ms_suspend(struct pci_dev *dev, pm_message_t state)
+static int j38ms_suspend(struct pci_dev *dev, pm_message_t state)
 {
-	struct jmb38x_ms *jm = pci_get_drvdata(dev);
+	struct j38ms *jm = pci_get_drvdata(dev);
 	int cnt;
 
 	for (cnt = 0; cnt < jm->host_cnt; ++cnt) {
@@ -772,9 +699,9 @@ static int jmb38x_ms_suspend(struct pci_dev *dev, pm_message_t state)
 	return 0;
 }
 
-static int jmb38x_ms_resume(struct pci_dev *dev)
+static int j38ms_resume(struct pci_dev *dev)
 {
-	struct jmb38x_ms *jm = pci_get_drvdata(dev);
+	struct j38ms *jm = pci_get_drvdata(dev);
 	int rc;
 
 	pci_set_power_state(dev, PCI_D0);
@@ -799,12 +726,12 @@ static int jmb38x_ms_resume(struct pci_dev *dev)
 
 #else
 
-#define jmb38x_ms_suspend NULL
-#define jmb38x_ms_resume NULL
+#define j38ms_suspend NULL
+#define j38ms_resume NULL
 
 #endif /* CONFIG_PM */
 
-static int jmb38x_ms_count_slots(struct pci_dev *pdev)
+static int j38ms_count_slots(struct pci_dev *pdev)
 {
 	int cnt, rc = 0;
 
@@ -814,23 +741,23 @@ static int jmb38x_ms_count_slots(struct pci_dev *pdev)
 
 		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)
+static struct memstick_host *j38ms_alloc_host(struct j38ms *jm, int cnt)
 {
 	struct memstick_host *msh;
-	struct jmb38x_ms_host *host;
+	struct j38ms_host *host;
 
-	msh = memstick_alloc_host(sizeof(struct jmb38x_ms_host),
+	msh = memstick_alloc_host(sizeof(struct j38ms_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));
@@ -842,39 +769,53 @@ static struct memstick_host *jmb38x_ms_alloc_host(struct jmb38x_ms *jm, int 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->request = j38ms_submit_req;
+	msh->set_param = j38ms_set_param;
 
 	msh->caps = MEMSTICK_CAP_PAR4 | MEMSTICK_CAP_PAR8;
 
-	setup_timer(&host->timer, jmb38x_ms_abort, (unsigned long)msh);
+	host->timeout_jiffies = msecs_to_jiffies(1000);
+	setup_timer(&host->timer, j38ms_irq_timeout, (unsigned long)host);
+	tasklet_init(&host->tasklet, j38ms_submit_tasklet, (unsigned long)host);
+
+	host->dma_bounce_page = pci_alloc_consistent(
+				jm->pdev, PAGE_SIZE, &host->dma_bus_address);
 
-	if (!request_irq(host->irq, jmb38x_ms_isr, IRQF_SHARED, host->host_id,
+	if (!host->dma_bounce_page)
+		goto err_out_free;
+
+	if (!request_irq(host->irq, j38ms_isr, IRQF_SHARED, host->host_id,
 			 msh))
 		return msh;
 
 	iounmap(host->addr);
+
 err_out_free:
+	if (host->dma_bounce_page)
+		pci_free_consistent(jm->pdev, PAGE_SIZE,
+			host->dma_bounce_page, host->dma_bus_address);
 	kfree(msh);
 	return NULL;
 }
 
-static void jmb38x_ms_free_host(struct memstick_host *msh)
+static void j38ms_free_host(struct memstick_host *msh)
 {
-	struct jmb38x_ms_host *host = memstick_priv(msh);
+	struct j38ms_host *host = memstick_priv(msh);
 
 	free_irq(host->irq, msh);
 	iounmap(host->addr);
+
+	pci_free_consistent(host->chip->pdev, PAGE_SIZE,
+		host->dma_bounce_page, host->dma_bus_address);
+
 	memstick_free_host(msh);
 }
 
-static int jmb38x_ms_probe(struct pci_dev *pdev,
+static int j38ms_probe(struct pci_dev *pdev,
 			   const struct pci_device_id *dev_id)
 {
-	struct jmb38x_ms *jm;
+	struct j38ms *jm;
 	int pci_dev_busy = 0;
 	int rc, cnt;
 
@@ -897,14 +838,14 @@ static int jmb38x_ms_probe(struct pci_dev *pdev,
 	pci_read_config_dword(pdev, 0xac, &rc);
 	pci_write_config_dword(pdev, 0xac, rc | 0x00470000);
 
-	cnt = jmb38x_ms_count_slots(pdev);
+	cnt = j38ms_count_slots(pdev);
 	if (!cnt) {
 		rc = -ENODEV;
 		pci_dev_busy = 1;
 		goto err_out;
 	}
 
-	jm = kzalloc(sizeof(struct jmb38x_ms)
+	jm = kzalloc(sizeof(struct j38ms)
 		     + cnt * sizeof(struct memstick_host *), GFP_KERNEL);
 	if (!jm) {
 		rc = -ENOMEM;
@@ -916,14 +857,14 @@ static int jmb38x_ms_probe(struct pci_dev *pdev,
 	pci_set_drvdata(pdev, jm);
 
 	for (cnt = 0; cnt < jm->host_cnt; ++cnt) {
-		jm->hosts[cnt] = jmb38x_ms_alloc_host(jm, cnt);
+		jm->hosts[cnt] = j38ms_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]);
+			j38ms_free_host(jm->hosts[cnt]);
 			jm->hosts[cnt] = NULL;
 			break;
 		}
@@ -944,10 +885,10 @@ err_out:
 	return rc;
 }
 
-static void jmb38x_ms_remove(struct pci_dev *dev)
+static void j38ms_remove(struct pci_dev *dev)
 {
-	struct jmb38x_ms *jm = pci_get_drvdata(dev);
-	struct jmb38x_ms_host *host;
+	struct j38ms *jm = pci_get_drvdata(dev);
+	struct j38ms_host *host;
 	int cnt;
 	unsigned long flags;
 
@@ -957,23 +898,28 @@ static void jmb38x_ms_remove(struct pci_dev *dev)
 
 		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);
-		mmiowb();
-		dev_dbg(&jm->pdev->dev, "interrupts off\n");
+		tasklet_kill(&host->tasklet);
+
 		spin_lock_irqsave(&host->lock, flags);
+		host->dead = true;
+
+		j38ms_write_reg(host, INT_SIGNAL_ENABLE, 0);
+		j38ms_write_reg(host, INT_STATUS_ENABLE, 0);
+
+		mmiowb();
+		dbg(host, "interrupts off");
+
 		if (host->req) {
 			host->req->error = -ETIME;
-			jmb38x_ms_complete_cmd(jm->hosts[cnt], 1);
+			j38ms_complete_tpc(jm->hosts[cnt]);
 		}
+
 		spin_unlock_irqrestore(&host->lock, flags);
 
 		memstick_remove_host(jm->hosts[cnt]);
-		dev_dbg(&jm->pdev->dev, "host removed\n");
+		dbg(host, "host removed");
 
-		jmb38x_ms_free_host(jm->hosts[cnt]);
+		j38ms_free_host(jm->hosts[cnt]);
 	}
 
 	pci_set_drvdata(dev, NULL);
@@ -982,35 +928,41 @@ static void jmb38x_ms_remove(struct pci_dev *dev)
 	kfree(jm);
 }
 
-static struct pci_device_id jmb38x_ms_id_tbl [] = {
+static struct pci_device_id j38ms_id_tbl[] = {
 	{ PCI_VENDOR_ID_JMICRON, PCI_DEVICE_ID_JMICRON_JMB38X_MS, PCI_ANY_ID,
 	  PCI_ANY_ID, 0, 0, 0 },
 	{ }
 };
 
-static struct pci_driver jmb38x_ms_driver = {
+static struct pci_driver j38ms_driver = {
 	.name = DRIVER_NAME,
-	.id_table = jmb38x_ms_id_tbl,
-	.probe = jmb38x_ms_probe,
-	.remove = jmb38x_ms_remove,
-	.suspend = jmb38x_ms_suspend,
-	.resume = jmb38x_ms_resume
+	.id_table = j38ms_id_tbl,
+	.probe = j38ms_probe,
+	.remove = j38ms_remove,
+	.suspend = j38ms_suspend,
+	.resume = j38ms_resume
 };
 
-static int __init jmb38x_ms_init(void)
+static int __init j38ms_init(void)
 {
-	return pci_register_driver(&jmb38x_ms_driver);
+	return pci_register_driver(&j38ms_driver);
 }
 
-static void __exit jmb38x_ms_exit(void)
+static void __exit j38ms_exit(void)
 {
-	pci_unregister_driver(&jmb38x_ms_driver);
+	pci_unregister_driver(&j38ms_driver);
 }
 
 MODULE_AUTHOR("Alex Dubov");
-MODULE_DESCRIPTION("JMicron jmb38x MemoryStick driver");
+MODULE_DESCRIPTION("JMicron jm38x_ms MemoryStick driver");
 MODULE_LICENSE("GPL");
-MODULE_DEVICE_TABLE(pci, jmb38x_ms_id_tbl);
+MODULE_DEVICE_TABLE(pci, j38ms_id_tbl);
+
+module_param(debug, int, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(debug, "Debug level (0-3)");
+
+module_param(no_dma, bool, 0644);
+MODULE_PARM_DESC(no_dma, "Disable the dma");
 
-module_init(jmb38x_ms_init);
-module_exit(jmb38x_ms_exit);
+module_init(j38ms_init);
+module_exit(j38ms_exit);
diff --git a/drivers/memstick/host/jmb38x_ms.h b/drivers/memstick/host/jmb38x_ms.h
new file mode 100644
index 0000000..0d83365
--- /dev/null
+++ b/drivers/memstick/host/jmb38x_ms.h
@@ -0,0 +1,200 @@
+/*
+ *  jmb38x_ms.c - JMicron jmb38x MemoryStick card reader
+ *
+ *  Copyright (C) 2008 Alex Dubov <oakad@...oo.com>
+ *  Copyright (C) 2010 Maxim Levitsky <maximlevitsky@...il.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#define DRIVER_NAME "jmb38x_ms"
+
+/* physical address of a page for DMA */
+#define	DMA_ADDRESS            0x00
+
+/* controls the size of the TPC */
+#define	BLOCK                  0x04
+#define BLOCK_COUNT_MASK        0xffff0000
+#define BLOCK_COUNT_1BLOCK      0x00010000
+#define BLOCK_SIZE_MASK         0x00000fff
+
+/* Enables the DMA */
+#define	DMA_CONTROL            0x08
+#define DMA_CONTROL_ENABLE      0x00000001
+
+/* A window for small TPCs to send the contents inline */
+#define	TPC_P0                 0x0c
+#define	TPC_P1                 0x10
+
+/* TPC execution register */
+#define	TPC                    0x14
+#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
+
+/* General device settings */
+#define	HOST_CONTROL           0x18
+#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_IF_PAR4    0x00000010
+#define HOST_CONTROL_IF_PAR8    0x00000030
+#define HOST_CONTROL_IF_MASK    0x00000030
+#define HOST_CONTROL_CLOCK_EN   0x00000040
+#define HOST_CONTROL_REO        0x00000008
+#define HOST_CONTROL_BSY_TIME	0x00000007
+#define HOST_CONTROL_IF_SERIAL  0x00000000
+
+/* IO window for PIO access to internal FIFO*/
+#define	DATA                   0x1c
+
+/* MS status */
+#define	STATUS                 0x20
+#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
+
+/* Device status */
+#define	INT_STATUS             0x24
+#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
+
+/* Interrupt enable - ???*/
+#define	INT_STATUS_ENABLE      0x28
+
+/* Interrupt enable*/
+#define	INT_SIGNAL_ENABLE      0x2c
+
+/* Current timer value */
+#define	TIMER                  0x30
+
+/* Enable/disable the timer */
+#define	TIMER_CONTROL          0x34
+
+/* Output pad enable */
+#define	PAD_OUTPUT_ENABLE      0x38
+#define PAD_OUTPUT_ENABLE_MS    0x0F3F
+
+/* Ouput pad pull-up, pull-down control */
+#define	PAD_PU_PD              0x3c
+#define PAD_PU_PD_OFF           0x7fff0000
+#define PAD_PU_PD_ON_MS_SOCK0   0x5f8f0000
+#define PAD_PU_PD_ON_MS_SOCK1   0x0f0f0000
+
+/* Internal clock delay */
+#define	CLOCK_DELAY            0x40
+
+/* ??? */
+#define	ADMA_ADDRESS           0x44
+
+/* Set the device clock */
+#define	CLOCK_CONTROL          0x48
+#define CLOCK_CONTROL_RESET     0x00000008
+#define CLOCK_CONTROL_40MHZ     0x00000009
+#define CLOCK_CONTROL_50MHZ     0x0000000a
+#define CLOCK_CONTROL_62_5MHZ   0x0000000c
+#define CLOCK_CONTROL_OFF       0x00000000
+
+/* Led blink period */
+#define	LED_CONTROL            0x4c
+
+/* Hardware version */
+#define	VERSION                0x50
+
+#define PCI_CTL_CLOCK_DLY_ADDR   0x000000b0
+#define PCI_CTL_CLOCK_DLY_MASK_A 0x00000f00
+#define PCI_CTL_CLOCK_DLY_MASK_B 0x0000f000
+
+struct j38ms_host {
+	struct j38ms            *chip;
+	struct memstick_host    *msh;
+	void __iomem            *addr;
+	bool                    dead;
+	spinlock_t              lock;
+	int                     id;
+	char                    host_id[32];
+	int                     irq;
+	struct memstick_request *req;
+	unsigned char           cmd_flags;
+	struct timer_list       timer;
+	struct tasklet_struct	tasklet;
+	unsigned long		timeout_jiffies;
+	int                     interface;
+
+	/* PIO state */
+	struct sg_mapping_iter  pio_sg_iter;
+	unsigned char		pio_offset;
+	unsigned char           pio_tmp_buf[4];
+	unsigned int            pio_tmp_buf_len;
+
+	/* DMA bounce buffer */
+	void			*dma_bounce_page;
+	dma_addr_t		dma_bus_address;
+
+};
+
+struct j38ms {
+	struct pci_dev        *pdev;
+	int                   host_cnt;
+	struct memstick_host  *hosts[];
+};
+
+enum {
+	CMD_READY    = 0x01,
+	FIFO_READY   = 0x02,
+
+	REG_DATA     = 0x04,
+	DMA_DATA     = 0x08,
+	PIO_DATA     = 0x10
+};
+
+#define __dbg(host, level, format, ...) \
+	do { \
+		if (debug >= level) \
+			printk(KERN_DEBUG \
+				"%s: " format "\n", dev_name(&host->msh->dev) \
+					 , ## __VA_ARGS__); \
+		else \
+			dev_dbg(&host->msh->dev, format, ## __VA_ARGS__); \
+	} while (0)
+
+#define dbg(host, format, ...)		__dbg(host, 1, format, ## __VA_ARGS__)
+#define dbg_v(host, format, ...)	__dbg(host, 2, format, ## __VA_ARGS__)
+#define dbg_reg(host, format, ...)	__dbg(host, 3, format, ## __VA_ARGS__)
+
+static void j38ms_next_request(struct j38ms_host *host);
-- 
1.7.1

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ