linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* MMC host driver requirements
@ 2008-09-07 21:52 Michał Mirosław
  2008-09-09  7:18 ` Pierre Ossman
  0 siblings, 1 reply; 21+ messages in thread
From: Michał Mirosław @ 2008-09-07 21:52 UTC (permalink / raw)
  To: linux-kernel; +Cc: drzeus-mmc, oakad

Hello,

I'm writing a driver for ENE CB710/720 memory card readers found in some
laptops (ie. some versions of HP Compaq nx9500). This chip has three
memory card interfaces: MMC/SD, SmartMedia, MemoryStick. I started with
the MMC interface as I have a card to test it with, but I could not find
any documentation about MMC stack besides the source code.

So here are some questions:

1. Can I call mmc_detect_change() after mmc_alloc_host() but before or
   during mmc_add_host() (ie. from interrupt handler)? If yes, what
   if mmc_alloc_host() fails then?
2. Can I call mmc_request_done() from ->request() handler?
3. Does MMC stack serialize calls to ->request() or any other host
   driver ops?
4. What is the difference (if any) between mrq->data and mrq->cmd->data
   as seen from ->request()?
5. Are there any constraints to scatterlist passed to ->request()
   - number of elements, data alignment, element data size?
   (At first I assumed that there are none and have writted a simple
   wrapper to guarantee multiple-of-16-byte data blocks - but maybe
   its just not needed?)

If/when I finish the driver there is the question what are the
requirements for it to be accepted to mainline? It's based on
reverse-engineering work on a Windows driver so it will have
some magic register-access sequences, as the original driver used
completely different MMC/SD stack and probably not fully used chip's
capabilities.

BTW, Google said nothing interesting about the hardware: most hits are
about not existing drivers for it and manufacturer ignoring inquiries
for datasheets.

Best Regards,
Michał Mirosław

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

* Re: MMC host driver requirements
  2008-09-07 21:52 MMC host driver requirements Michał Mirosław
@ 2008-09-09  7:18 ` Pierre Ossman
  2008-09-09  9:06   ` Michał Mirosław
  0 siblings, 1 reply; 21+ messages in thread
From: Pierre Ossman @ 2008-09-09  7:18 UTC (permalink / raw)
  To: Michał Mirosław; +Cc: linux-kernel, oakad

On Sun, 7 Sep 2008 23:52:28 +0200
Michał Mirosław <mirq-linux@rere.qmqm.pl> wrote:

> Hello,
> 
> I'm writing a driver for ENE CB710/720 memory card readers found in some
> laptops (ie. some versions of HP Compaq nx9500). This chip has three
> memory card interfaces: MMC/SD, SmartMedia, MemoryStick. I started with
> the MMC interface as I have a card to test it with, but I could not find
> any documentation about MMC stack besides the source code.
> 

Hmm... I thought the CB710/720 only had an SDHCI interface for the
MMC/SD portion.

> So here are some questions:
> 
> 1. Can I call mmc_detect_change() after mmc_alloc_host() but before or
>    during mmc_add_host() (ie. from interrupt handler)? If yes, what
>    if mmc_alloc_host() fails then?

No you can't.

> 2. Can I call mmc_request_done() from ->request() handler?

Yes, although I'm not sure how well tested it is.

> 3. Does MMC stack serialize calls to ->request() or any other host
>    driver ops?

Requests are serialised, yes. But you could in theory get set_ios()
calls during an ongoing request. Doing so would be very undefined
though so it should be sufficient to just avoid crashing the entire
system if that happens.

> 4. What is the difference (if any) between mrq->data and mrq->cmd->data
>    as seen from ->request()?

Not much really. The cmd->data pointer is for convenience as it allows
common code paths for handling mrq->cmd and mrq->stop.

> 5. Are there any constraints to scatterlist passed to ->request()
>    - number of elements, data alignment, element data size?
>    (At first I assumed that there are none and have writted a simple
>    wrapper to guarantee multiple-of-16-byte data blocks - but maybe
>    its just not needed?)

None at all. You have to specify your restrictions in the mmc_host
structure fields (note that you cannot restrict alignment in any way).
The mmc_test driver is useful for testing some of the corner cases.

> If/when I finish the driver there is the question what are the
> requirements for it to be accepted to mainline? It's based on
> reverse-engineering work on a Windows driver so it will have
> some magic register-access sequences, as the original driver used
> completely different MMC/SD stack and probably not fully used chip's
> capabilities.

The requirements are generally low for merging. Mostly you just need to
promise to stick around and fix problems. You should be aware that the
style requirements and API adherence are very important. Run you stuff
through checkpatch.pl before submitting. Sparse can usually also find
quite a few problems.

> BTW, Google said nothing interesting about the hardware: most hits are
> about not existing drivers for it and manufacturer ignoring inquiries
> for datasheets.

As I said, to my knowledge the sdhci driver covers this hardware, but
the support is rather crappy as the controller is buggy as hell. And
ENE does indeed ignore all attempts at contact.

Rgds
-- 
     -- Pierre Ossman

  Linux kernel, MMC maintainer        http://www.kernel.org
  rdesktop, core developer          http://www.rdesktop.org

  WARNING: This correspondence is being monitored by the
  Swedish government. Make sure your server uses encryption
  for SMTP traffic and consider using PGP for end-to-end
  encryption.

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

* Re: MMC host driver requirements
  2008-09-09  7:18 ` Pierre Ossman
@ 2008-09-09  9:06   ` Michał Mirosław
  2008-09-11 20:13     ` Driver for CB710/720 memory card reader (MMC part) Michał Mirosław
  0 siblings, 1 reply; 21+ messages in thread
From: Michał Mirosław @ 2008-09-09  9:06 UTC (permalink / raw)
  To: Pierre Ossman; +Cc: linux-kernel, oakad

On Tue, Sep 09, 2008 at 09:18:00AM +0200, Pierre Ossman wrote:
> On Sun, 7 Sep 2008 23:52:28 +0200
> Michał Mirosław <mirq-linux@rere.qmqm.pl> wrote:
> > I'm writing a driver for ENE CB710/720 memory card readers found in some
> > laptops (ie. some versions of HP Compaq nx9500). This chip has three
> > memory card interfaces: MMC/SD, SmartMedia, MemoryStick. I started with
> > the MMC interface as I have a card to test it with, but I could not find
> > any documentation about MMC stack besides the source code.
> Hmm... I thought the CB710/720 only had an SDHCI interface for the
> MMC/SD portion.

SDHCI supports CB712/714 parts. I already investigated that CB710 is not
SDHCI-compatible (register layout is a bit different, for a start).

> > So here are some questions:
> > 
> > 1. Can I call mmc_detect_change() after mmc_alloc_host() but before or
> >    during mmc_add_host() (ie. from interrupt handler)? If yes, what
> >    if mmc_alloc_host() fails then?
> No you can't.

> > 2. Can I call mmc_request_done() from ->request() handler?
> Yes, although I'm not sure how well tested it is.

I checked yesterday that indeed nothing bad happens, but looking at
the code I can see that mmc_request_done can call ->request() with
a retried request so, unless gcc optimizes tail-calls, recursion
there might eat up all stack space someday.

> > 3. Does MMC stack serialize calls to ->request() or any other host
> >    driver ops?
> Requests are serialised, yes. But you could in theory get set_ios()
> calls during an ongoing request. Doing so would be very undefined
> though so it should be sufficient to just avoid crashing the entire
> system if that happens.

This should be easy.

> > 4. What is the difference (if any) between mrq->data and mrq->cmd->data
> >    as seen from ->request()?
> Not much really. The cmd->data pointer is for convenience as it allows
> common code paths for handling mrq->cmd and mrq->stop.

Is it safe to assume that cmd->data is NULL when command type is other
than ADTC? I've looked briefly through drivers/mmc/core/*_ops.c, but
maybe I missed something?

> > 5. Are there any constraints to scatterlist passed to ->request()
> >    - number of elements, data alignment, element data size?
> >    (At first I assumed that there are none and have writted a simple
> >    wrapper to guarantee multiple-of-16-byte data blocks - but maybe
> >    its just not needed?)
> None at all. You have to specify your restrictions in the mmc_host
> structure fields (note that you cannot restrict alignment in any way).
> The mmc_test driver is useful for testing some of the corner cases.

I'll give it a try.

Thanks for your reply. I'll post the code for review when it does more
than just blinking LEDs. ;)

Best Regards,
Michał Mirosław


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

* Driver for CB710/720 memory card reader (MMC part)
  2008-09-09  9:06   ` Michał Mirosław
@ 2008-09-11 20:13     ` Michał Mirosław
  2008-09-12 23:43       ` RFC: " Michał Mirosław
  0 siblings, 1 reply; 21+ messages in thread
From: Michał Mirosław @ 2008-09-11 20:13 UTC (permalink / raw)
  To: linux-kernel; +Cc: Pierre Ossman, Alex Dubov

Hello,

Here's a rough version of a driver for CB710/720 memory card reader.
It was tested with Canon SDC-16M card and actually works... until
first command error as there's no good error recovery. Driver spits
out a lot of debugging output as it goes and currently when it starts
failing commands it's necessary to reload the module to get it
working again.

For the MMC part, TODO list includes at least:
 - split the driver to a bus-like common device driver and reader
   specific parts like tifm driver has
 - maybe get rid of sgbuf.c if there already is some similar API
   or more assumptions on scatterlist elements can be made

If you're brave enough to test this please do so, but I wouldn't
bet my important data on it. ;)

Best Regards,
Michał Mirosław

This, obviously, is not ready to include anywhere.

Signed-off-by: Michał Mirosław <mirq-linux@rere.qmqm.pl>

diff -urN empty/cb710.h cb710-pre-20080911/cb710.h
--- empty/cb710.h	1970-01-01 01:00:00.000000000 +0100
+++ cb710-pre-20080911/cb710.h	2008-09-11 21:06:36.000000000 +0200
@@ -0,0 +1,158 @@
+/*
+ *  cb710/cb710.h
+ *
+ *  Copyleft by Michał Mirosław, 2008
+ *
+ * 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.
+ */
+#ifndef LINUX_CB710_DRIVER_H
+#define LINUX_CB710_DRIVER_H
+
+#include <linux/spinlock.h>
+#include <linux/mmc/host.h>
+#include <linux/pci.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+
+#define CB710_DRIVER_NAME "cb710"
+
+/* per-device structure */
+struct cb710_chip {
+	struct pci_dev *pdev;
+	void __iomem *iobase;
+
+	spinlock_t irq_lock;
+	unsigned irq_enabled;
+
+	struct mmc_host *mmc;
+};
+
+#define RDPORT(t, p) \
+	ioread##t(chip->iobase + (p))
+#define WRPORT(t, p, v) \
+	do { iowrite##t((v), chip->iobase + (p)); \
+		(void)ioread8(chip->iobase + 0x13); } while (0)
+#define UPDPORT(t, p, v, m) \
+	do { \
+		iowrite##t( \
+			(ioread##t(chip->iobase + (p)) & ~(m)) | (v), \
+			chip->iobase + (p)); \
+		(void)ioread8(chip->iobase + 0x13); \
+	} while (0)
+#define S_RDPORT(t, p, b, c) \
+	ioread##t##_rep(chip->iobase + (p), (b), (c))
+#define S_WRPORT(t, p, b, c) \
+	do { \
+		iowrite##t##_rep(chip->iobase + (p), (b), (c)); \
+		(void)ioread8(chip->iobase + 0x13); \
+	} while (0)
+
+
+void __cb710_pci_update_config_reg(struct pci_dev *pdev,
+	int reg, uint32_t mask, uint32_t xor);
+#define cb710_pci_update_config_reg(d, r, m, x) \
+	__cb710_pci_update_config_reg((d), (r), ~(m), (x))
+
+/* sg-to-PIO buffer */
+#define CB710_SG_BUFFER_BLOCK	16	/* power of two */
+struct cb710_sg_chain {
+	uint8_t bounce_buffer[CB710_SG_BUFFER_BLOCK];
+	struct scatterlist *sg;
+	unsigned int sg_num;
+	struct page *page;
+	void *mapped_page;
+	size_t cur_offset;
+	size_t need_advance;
+	unsigned page_no;
+	unsigned page_offset;
+	unsigned page_left;
+	unsigned need_bounce:1;
+	unsigned use_bounce:1;
+};
+
+void cb710_sg_init(struct cb710_sg_chain *buf,
+	struct scatterlist *sg, size_t nelem);
+int cb710_sg_next_buf(struct cb710_sg_chain *buf,
+	void **dataptr, size_t *len, int to_sg);
+void cb710_sg_abort(struct cb710_sg_chain *buf, int to_sg);
+
+#define cb710_sg_read_next(b, d, l) \
+	cb710_sg_next_buf((b), (d), (l), 0)
+#define cb710_sg_write_next(b, d, l) \
+	cb710_sg_next_buf((b), (d), (l), 1)
+#define cb710_sg_abort_read(b) \
+	cb710_sg_abort((b), 0)
+#define cb710_sg_abort_write(b) \
+	cb710_sg_abort((b), 1)
+
+/* per-MMC-reader structure */
+struct cb710_mmc_reader {
+	struct tasklet_struct finish_req_tasklet;
+	struct mmc_request *mrq;
+	unsigned char last_power_mode;
+	unsigned char app_cmd;
+	spinlock_t serialization_lock;
+	unsigned char active_req, active_ios;
+};
+
+int __devinit cb710_mmc_init(struct cb710_chip *chip);
+void __devexit cb710_mmc_exit(struct cb710_chip *chip);
+int cb710_mmc_irq_handler(struct cb710_chip *chip);
+
+/* registers */
+
+#define CB710_MMC_DATA_PORT		0x00
+#define CB710_MMC_CONFIG_PORT		0x04
+#define CB710_MMC_CONFIG0_PORT		0x04
+#define CB710_MMC_CONFIG1_PORT		0x05
+#define   CB710_MMC_C1_4BIT_DATA_BUS		0x40
+#define CB710_MMC_CONFIG2_PORT	0x06
+#define   CB710_MMC_C2_READ_BLOCK_SIZE_MASK	0x0F	/* N-1 */
+#define CB710_MMC_CONFIG3_PORT	0x07
+#define CB710_MMC_INTERRUPT_ENABLE_PORT	0x0D
+#define   CB710_MMC_IE_IRQ_ENABLE		0x80
+#define   CB710_MMC_IE_CARD_INSERTION_STATUS	0x10
+#define CB710_MMC_STATUS_PORT		0x10
+#define CB710_MMC_STATUS0_PORT		0x10
+#define CB710_MMC_STATUS1_PORT		0x11
+#define   CB710_MMC_S1_CARD_CHANGED		0x10
+#define   CB710_MMC_S1_RESET			0x20	/* XXX really? */
+#define CB710_MMC_STATUS2_PORT		0x12
+#define CB710_MMC_STATUS3_PORT		0x13
+#define   CB710_MMC_S3_CARD_DETECTED		0x02
+#define   CB710_MMC_S3_WRITE_PROTECTED		0x04
+#define CB710_MMC_CMD_TYPE_PORT		0x14
+#define   CB710_MMC_RSP_TYPE_MASK		0x0007
+#define     CB710_MMC_RSP_R1			(0)
+#define     CB710_MMC_RSP_136			(5)
+#define     CB710_MMC_RSP_NO_CRC		(2)
+#define   CB710_MMC_RSP_PRESENT_MASK		0x0018
+#define     CB710_MMC_RSP_NONE			(0 << 3)
+#define     CB710_MMC_RSP_PRESENT		(1 << 3)
+#define     CB710_MMC_RSP_PRESENT_X		(2 << 3)
+#define   CB710_MMC_CMD_TYPE_MASK		0x0060
+#define     CB710_MMC_CMD_BC			(0 << 5)
+#define     CB710_MMC_CMD_BCR			(1 << 5)
+#define     CB710_MMC_CMD_AC			(2 << 5)
+#define     CB710_MMC_CMD_ADTC			(3 << 5)
+#define   CB710_MMC_DATA_READ			0x0080
+#define   CB710_MMC_CMD_CODE_MASK		0x3F00
+#define   CB710_MMC_CMD_CODE_SHIFT		8
+#define   CB710_MMC_IS_APP_CMD			0x4000
+#define   CB710_MMC_RSP_BUSY			0x8000
+#define CB710_MMC_CMD_PARAM_PORT	0x18
+#define CB710_MMC_TRANSFER_SIZE_PORT	0x1C
+#define CB710_MMC_RESPONSE0_PORT	0x20
+#define CB710_MMC_RESPONSE1_PORT	0x24
+#define CB710_MMC_RESPONSE2_PORT	0x28
+#define CB710_MMC_RESPONSE3_PORT	0x2C
+
+void cb710_dump_regs(struct cb710_chip *chip, unsigned dump);
+#define CB710_DUMP_REGS_MMC	0x0F
+#define CB710_DUMP_REGS_MS	0x30
+#define CB710_DUMP_REGS_SM	0xC0
+#define CB710_DUMP_REGS_ALL	0xFF
+
+#endif /* LINUX_CB710_DRIVER_H */
diff -urN empty/main.c cb710-pre-20080911/main.c
--- empty/main.c	1970-01-01 01:00:00.000000000 +0100
+++ cb710-pre-20080911/main.c	2008-09-11 20:31:10.000000000 +0200
@@ -0,0 +1,238 @@
+/*
+ *  cb710/main.c
+ *
+ *  Copyleft by Michał Mirosław, 2008
+ *
+ * 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.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include "cb710.h"
+
+void __cb710_pci_update_config_reg(struct pci_dev *pdev,
+	int reg, uint32_t mask, uint32_t xor)
+{
+	u32 rval;
+
+	pci_read_config_dword(pdev, reg, &rval);
+	rval = (rval & mask) ^ xor;
+	pci_write_config_dword(pdev, reg, rval);
+}
+
+void cb710_dump_regs(struct cb710_chip *chip, unsigned select)
+{
+	const unsigned allow[8] = {
+		0xFFF0, 0xFFFF, 0xFFFF, 0xFFFF,
+		0xFFF0, 0xFFFF, 0xFFFF, 0xFFFF,
+	};
+	const char *const prefix[sizeof(allow)/sizeof(*allow)] = {
+		"MMC", "MMC", "MMC", "MMC",
+		"MS?", "MS?", "SM?", "SM?"
+	};
+	u32 regs[sizeof(allow)/sizeof(*allow) << 2];
+
+	int i, j;
+	char msg[100], *p;
+
+	if (!select)
+		select = 0xFF;
+	if (!(select & 0x700))
+		select |= 0x100;
+
+#define reg(b, i) \
+	(((u##b*)regs)[(i) / (b/8)])
+#define allowed(b, i, j) \
+	(((allow[i >> 4] >> j) & ((1 << b/8)-1)) == ((1 << b/8)-1))
+#define dumpregs(b, f, x) { \
+	for (i = 0; i < (sizeof(allow)/sizeof(*allow) << 4); i += 0x10) { \
+		if (!(select & (1 << (i >> 4)))) \
+			continue; \
+		for (j = 0; j < 0x10; j += b/8) { \
+			if (allowed(b, i, j)) \
+				reg(b, i + j) = RDPORT(b, i + j); \
+		}; \
+	} \
+	for (i = 0; i < (sizeof(allow)/sizeof(*allow) << 4); i += 0x10) { \
+		if (!(select & (1 << (i >> 4)))) \
+			continue; \
+		p = msg; \
+		for (j = 0; j < 0x10; j += b/8) { \
+			if (allowed(b, i, j)) \
+				p += sprintf(p, " %s" f, (j == 8 ? " " : ""), \
+					reg(b, i + j)); \
+			else \
+				p += sprintf(p, " %s" x, (j == 8 ? " " : "")); \
+			udelay(1); \
+		} \
+		\
+		printk(KERN_INFO CB710_DRIVER_NAME ": %s 0x%02X %s\n", \
+			prefix[i >> 4], i, msg); \
+	} \
+	}
+
+	if (select & 0x400)
+		dumpregs(32, "%08X", "xxxxxxxx");
+	if (select & 0x200)
+		dumpregs(16, "%04X", "xxxx");
+	if (select & 0x100)
+		dumpregs( 8, "%02X", "xx");
+
+#undef dumpregs
+#undef allowed
+#undef reg
+}
+
+/* Some magic writes based on Windows driver init code */
+static int __devinit cb710_pci_configure(struct pci_dev *pdev)
+{
+	unsigned int devfn = PCI_DEVFN(PCI_SLOT(pdev->devfn), 0);
+	struct pci_dev *pdev0 = pci_get_slot(pdev->bus, devfn);
+	u32 val;
+
+	cb710_pci_update_config_reg(pdev, 0x48, 0xC0, 0x3F);
+
+	pci_read_config_dword(pdev, 0x48, &val);
+	if (val & 0x80000000)
+		return 0;
+
+	if (!pdev0)
+		return -ENODEV;
+
+	if (pdev0->vendor == PCI_VENDOR_ID_ENE
+	    && pdev0->device == PCI_DEVICE_ID_ENE_720) {
+		cb710_pci_update_config_reg(pdev0, 0x8C, 0xE00000, 0x100000);
+		cb710_pci_update_config_reg(pdev0, 0xB0, 0, 0x08000000);
+	}
+
+	cb710_pci_update_config_reg(pdev0, 0x8C, 0x0D00, 0x0200);
+	cb710_pci_update_config_reg(pdev0, 0x90, 0x020000, 0x040000);
+
+	pci_dev_put(pdev0);
+
+	return 0;
+}
+
+static irqreturn_t cb710_irq_handler(int irq, void *data)
+{
+	struct cb710_chip *chip = data;
+	unsigned flags;
+	int handled = 0;
+
+	spin_lock_irqsave(&chip->irq_lock, flags);
+
+	if (chip->irq_enabled & 0x01)
+		handled = cb710_mmc_irq_handler(chip) || handled;
+
+	spin_unlock_irqrestore(&chip->irq_lock, flags);
+
+	return handled ? IRQ_HANDLED : IRQ_NONE;
+}
+
+static int __devinit cb710_init_one(struct pci_dev *pdev,
+	const struct pci_device_id *ent)
+{
+	struct cb710_chip *chip;
+	u32 val;
+	int ok = 0;
+	int err;
+
+	chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
+	if (!chip)
+		return -ENOMEM;
+
+	err = pcim_enable_device(pdev);
+	if (err)
+		return err;
+
+	err = pcim_iomap_regions(pdev, 0x0001, CB710_DRIVER_NAME);
+	if (err)
+		return err;
+
+	spin_lock_init(&chip->irq_lock);
+	chip->pdev = pdev;
+	chip->iobase = pcim_iomap_table(pdev)[0];
+
+	pci_set_drvdata(pdev, chip);
+
+	pci_read_config_dword(pdev, 0x48, &val);
+	if (!(val & 0x80000000)) {
+		pci_write_config_dword(pdev, 0x48, val|0x71000000);
+		pci_read_config_dword(pdev, 0x48, &val);
+	}
+
+	err = cb710_pci_configure(pdev);
+	if (err)
+		return err;
+
+	err = devm_request_irq(&pdev->dev, pdev->irq,
+		cb710_irq_handler, IRQF_SHARED, "cb710", chip);
+	if (err)
+		return err;
+
+	dev_printk(KERN_INFO CB710_DRIVER_NAME ": ", &pdev->dev,
+		"IO 0x%p, IRQ %d\n", chip->iobase, pdev->irq);
+
+	printk(KERN_INFO CB710_DRIVER_NAME
+		": PCI config[0x48] = 0x%08X (%d %d %d %d %d %d)\n",
+		val,
+		!(val & 0x01000000),
+		(val >> 8) & 7,
+		!!(val & 0x10000000),
+		!!(val & 0x20000000),
+		!!(val & 0x40000000),
+		!(val & 0x02000000)
+	);
+
+	if (val & 0x10000000) {
+		err = cb710_mmc_init(chip);
+		if (!err)
+			++ok;
+	}
+
+	if (!ok && !err)
+		return -ENODEV;
+	/* XXX: will disappear after conversion to bus-type driver */
+	return ok ? 0 : err;
+}
+
+static void __devexit cb710_remove_one(struct pci_dev *pdev)
+{
+	struct cb710_chip *chip = pci_get_drvdata(pdev);
+
+	if (chip->mmc)
+		cb710_mmc_exit(chip);
+}
+
+static const struct pci_device_id cb710_pci_tbl[] = {
+	{ PCI_VENDOR_ID_ENE, 0x510, PCI_ANY_ID, PCI_ANY_ID, },
+	{ 0, }
+};
+
+static struct pci_driver cb710_driver = {
+	.name = "cb710",
+	.id_table = cb710_pci_tbl,
+	.probe = cb710_init_one,
+	.remove = __devexit_p(cb710_remove_one),
+};
+
+static int __init cb710_init_module(void)
+{
+	return pci_register_driver(&cb710_driver);
+}
+
+static void __exit cb710_cleanup_module(void)
+{
+	pci_unregister_driver(&cb710_driver);
+}
+
+module_init(cb710_init_module);
+module_exit(cb710_cleanup_module);
+
+MODULE_AUTHOR("Michał Mirosław <mirq-linux@rere.qmqm.pl>");
+MODULE_DESCRIPTION("ENE CB710 memory card reader driver");
+MODULE_LICENSE("GPL");
+MODULE_DEVICE_TABLE(pci, cb710_pci_tbl);
diff -urN empty/Makefile cb710-pre-20080911/Makefile
--- empty/Makefile	1970-01-01 01:00:00.000000000 +0100
+++ cb710-pre-20080911/Makefile	2008-09-11 20:03:07.000000000 +0200
@@ -0,0 +1,22 @@
+ifeq ($(KERNELRELEASE),)
+
+#KDIR	:= /usr/src/linux
+KDIR	:= /usr/src/jaja/build/rechot
+PWD     := $(shell pwd)
+
+.PHONY: module install clean
+module:
+	$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules
+
+install:
+	$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules_install
+
+clean:
+	$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) clean
+
+else	# kbuild part
+
+obj-m       := cb710.o
+cb710-y     := main.o mmc.o sgbuf.o
+
+endif
diff -urN empty/mmc.c cb710-pre-20080911/mmc.c
--- empty/mmc.c	1970-01-01 01:00:00.000000000 +0100
+++ cb710-pre-20080911/mmc.c	2008-09-11 21:08:02.000000000 +0200
@@ -0,0 +1,724 @@
+/*
+ *  cb710/mmc.c
+ *
+ *  Copyleft by Michał Mirosław, 2008
+ *
+ * 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.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/mmc/mmc.h>
+#include "cb710.h"
+
+static const u8 cb710_clock_divider_log2[8] = {
+/*	1, 2, 4, 8, 16, 32, 128, 512 */
+	0, 1, 2, 3,  4,  5,   7,   9
+};
+#define CB710_MMC_MAX_DIVIDER_LOG2 9
+
+static const u8 cb710_src_freq_mhz[16] = {
+	33, 10, 20, 25, 30, 35, 40, 45,
+	50, 55, 60, 65, 70, 75, 80, 85
+};
+
+static void verify_serialization(struct cb710_mmc_reader *reader, unsigned char *counter, int inc)
+{
+	unsigned long flags;
+	int req, ios, cur;
+
+	spin_lock_irqsave(&reader->serialization_lock, flags);
+
+	if (inc)
+		cur = ++*counter;
+	else
+		cur = --*counter;
+	req = reader->active_req;
+	ios = reader->active_ios;
+
+	spin_unlock_irqrestore(&reader->serialization_lock, flags);
+
+	printk(KERN_INFO CB710_DRIVER_NAME ": %s driver;"
+		"counters now: ios=%d req=%d\n",
+		inc ? "entering" : "leaving", ios, req);
+	WARN_ON(cur > 1);
+}
+
+static void cb710_mmc_set_clock(struct cb710_chip *chip, int hz)
+{
+	struct pci_dev *pdev = chip->pdev;
+	u32 src_freq_idx;
+	u32 divider_idx;
+	int src_hz;
+
+	/* this is magic, unverifiable for me, unless I get
+	 * MMC card with cables connected to bus signals */
+	pci_read_config_dword(pdev, 0x48, &src_freq_idx);
+	src_freq_idx = (src_freq_idx >> 16) & 0xF;
+	src_hz = cb710_src_freq_mhz[src_freq_idx] * 1000000;
+
+	for (divider_idx = 0; divider_idx < 8; ++divider_idx) {
+		if (hz >= src_hz >> cb710_clock_divider_log2[divider_idx])
+			break;
+	}
+	if (divider_idx == 8)
+		--divider_idx;
+
+	if (src_freq_idx)
+		divider_idx |= 0x8;
+
+	cb710_pci_update_config_reg(pdev, 0x40, 0xF0000000, divider_idx << 28);
+
+	printk(KERN_INFO CB710_DRIVER_NAME
+		": %s: clock set to %d Hz, wanted %d Hz; flag = %d\n",
+		mmc_hostname(chip->mmc),
+		src_hz >> cb710_clock_divider_log2[divider_idx & 7],
+		hz, (divider_idx & 8) != 0);
+}
+
+static void __cb710_mmc_enable_irq(struct cb710_chip *chip, int enable)
+{
+	if (enable) {
+		chip->irq_enabled |= 0x01;
+		/* look like interrupt is fired whenever
+		 * WORD[0x0C] & WORD[0x10] != 0;
+		 * let's verify it...
+		 *** bit 7 port 0x0D seems to be global interrupt enable
+		 */
+		WRPORT(8, 0x0C, ~0x40);
+		WRPORT(8, 0x0D, 0x90);
+	} else {
+		chip->irq_enabled &= ~0x01;
+		WRPORT(8, 0x0C, 0);
+		WRPORT(8, 0x0D, 0);
+	}
+}
+
+static void cb710_mmc_enable_irq(struct cb710_chip *chip, int enable)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->irq_lock, flags);
+	__cb710_mmc_enable_irq(chip, enable);
+	spin_unlock_irqrestore(&chip->irq_lock, flags);
+}
+
+static int cb710_mmc_is_card_inserted(struct cb710_chip *chip)
+{
+	return RDPORT(8, CB710_MMC_STATUS3_PORT)
+		& CB710_MMC_S3_CARD_DETECTED;
+}
+
+static void cb710_mmc_enable_4bit_data(struct cb710_chip *chip, int enable)
+{
+	printk(KERN_INFO CB710_DRIVER_NAME
+		": configuring %d-data-line%s mode\n",
+		enable ? 4 : 1, enable ? "s" : "");
+	UPDPORT(8, CB710_MMC_CONFIG1_PORT,
+		enable ? CB710_MMC_C1_4BIT_DATA_BUS : 0,
+		CB710_MMC_C1_4BIT_DATA_BUS);
+}
+
+static int cb710_check(struct cb710_chip *chip, int what)
+{
+	uint8_t status1, status2;
+
+	/* all this is magic */
+	BUG_ON(what < 2 || what > 4);
+
+	status1 = RDPORT(8, CB710_MMC_STATUS0_PORT);
+	status2 = RDPORT(8, CB710_MMC_STATUS1_PORT);
+
+	if (status1 & 0x40) {
+		printk(KERN_INFO CB710_DRIVER_NAME
+			": CHECK : ignoring bit S0=%02X & 0x40\n",
+			status1);
+		WRPORT(8, CB710_MMC_STATUS0_PORT, 0x40);
+		status1 &= ~0x40;
+	}
+
+	if (status1 || (status2 & 0x60)) {
+		printk(KERN_INFO CB710_DRIVER_NAME
+			": CHECK : returning EIO on status S0=%02X S1=%02X\n",
+			status1, status2);
+		WRPORT(8, CB710_MMC_STATUS0_PORT, status1);
+		WRPORT(8, CB710_MMC_STATUS1_PORT, 0x20);
+		return -EIO;
+	}
+
+	switch (what) {
+	case 2:		/* block transfer done */
+		if (!(status2 & 0x04))
+			break;
+		WRPORT(8, CB710_MMC_STATUS1_PORT, 0x04);
+		return 1;
+	case 3:		/* command sent */
+		if (!(status2 & 0x01))
+			break;
+		WRPORT(8, CB710_MMC_STATUS1_PORT, 0x01);
+		return 1;
+	case 4:		/* data transfer done */
+		if (!(status2 & 0x02))
+			break;
+		WRPORT(8, CB710_MMC_STATUS1_PORT, 0x02);
+		return 1;
+	}
+	return 0;
+}
+
+static int cb710_wait(struct cb710_chip *chip, int what)
+{
+	int err = 0;
+	unsigned limit = 2000000;	/* FIXME: real timeout */
+	u32 e = 0, x = 0;
+
+	e = RDPORT(32, CB710_MMC_STATUS_PORT);
+	while (!(err = cb710_check(chip, what))) {
+		if (!--limit) {
+			err = -ETIMEDOUT;
+			break;
+		}
+		udelay(1);
+	}
+	x = RDPORT(32, CB710_MMC_STATUS_PORT);
+#if 0
+	printk(KERN_INFO CB710_DRIVER_NAME ": waited %d loops, "
+		"WAIT10: what %d, entry val %08X, exit val %08X\n",
+		2000000 - limit, what, e, x);
+#endif
+	return err < 0 ? err : 0;
+}
+
+
+static int cb710_wait12(struct cb710_chip *chip, uint8_t mask)
+{
+	unsigned limit = 500000;	/* FIXME: real timeout */
+	u32 e, x;
+	int err = 0;
+
+	e = RDPORT(32, CB710_MMC_STATUS_PORT);
+	while (RDPORT(8, CB710_MMC_STATUS2_PORT) & mask) {
+		if (!--limit) {
+			err = -ETIMEDOUT;
+			break;
+		}
+		udelay(1);
+	}
+	x = RDPORT(32, CB710_MMC_STATUS_PORT);
+#if 0
+	printk(KERN_INFO CB710_DRIVER_NAME ": waited %d loops, "
+		"WAIT12: mask %02X, entry val %08X, exit val %08X\n",
+		500000 - limit, mask, e, x);
+#endif
+	return 0;
+}
+
+static void cb710_mmc_set_transfer_size(struct cb710_chip *chip,
+	size_t count, size_t blocksize)
+{
+	cb710_wait12(chip, 0x20);
+	WRPORT(32, CB710_MMC_TRANSFER_SIZE_PORT,
+		((count - 1) << 16)|(blocksize - 1));
+
+	printk(KERN_INFO CB710_DRIVER_NAME
+		": set up for %d block%s of %d bytes\n",
+		count, count == 1 ? "" : "s", blocksize);
+}
+
+static void cb710_mmc_fifo_hack(struct cb710_chip *chip)
+{
+	/* without this, received data is prepended with 8-bytes of zeroes */
+	u32 r1, r2;
+	int ok = 0;
+
+	r1 = RDPORT(32, CB710_MMC_DATA_PORT);
+	r2 = RDPORT(32, CB710_MMC_DATA_PORT);
+	if (RDPORT(8, CB710_MMC_STATUS0_PORT) & 0x40) {
+		WRPORT(8, CB710_MMC_STATUS0_PORT, 0x40);
+		ok = 1;
+	}
+
+	printk(KERN_INFO CB710_DRIVER_NAME ": FIFO-read-hack: "
+		"expected STATUS0 bit was %s dwords ignored: %08X %08X\n",
+		ok ? "set." : "NOT SET!", r1, r2);
+}
+
+static int cb710_mmc_receive(struct cb710_chip *chip, struct mmc_data *data)
+{
+	struct cb710_sg_chain sgc;
+	uint32_t *databuf;
+	size_t len;
+
+	cb710_sg_init(&sgc, data->sg, data->sg_len);
+
+	if (!cb710_sg_write_next(&sgc, (void **)&databuf, &len))
+		return 0;
+
+	UPDPORT(8, CB710_MMC_CONFIG2_PORT,
+		15, CB710_MMC_C2_READ_BLOCK_SIZE_MASK);
+
+	cb710_mmc_fifo_hack(chip);
+
+	while (len >= 16) {
+		if (!(RDPORT(8, CB710_MMC_STATUS2_PORT) & 0x01)) {
+			int err = cb710_wait(chip, 2);
+			if (err) {
+				cb710_sg_abort_write(&sgc);
+				return err;
+			}
+		}
+		S_RDPORT(32, CB710_MMC_DATA_PORT, databuf, 4);
+
+		len -= 16;
+		databuf += 4;
+
+		if (!len && !cb710_sg_write_next(&sgc,
+				(void **)&databuf, &len))
+			return 0;
+	}
+
+	UPDPORT(8, CB710_MMC_CONFIG2_PORT,
+		len - 1, CB710_MMC_C2_READ_BLOCK_SIZE_MASK);
+
+	if (RDPORT(8, CB710_MMC_STATUS2_PORT) & 0x01) {
+		int err = cb710_wait(chip, 2);
+		if (err) {
+			cb710_sg_abort_write(&sgc);
+			return err;
+		}
+	}
+
+	len = (len + 3) >> 2;
+	S_RDPORT(32, CB710_MMC_DATA_PORT, databuf, len);
+
+	if (cb710_sg_write_next(&sgc, (void **)&databuf, &len))
+		BUG();
+
+	return 0;
+}
+
+static int cb710_mmc_send(struct cb710_chip *chip, struct mmc_data *data)
+{
+	struct cb710_sg_chain sgc;
+	const uint32_t *databuf;
+	size_t datalen;
+
+	cb710_sg_init(&sgc, data->sg, data->sg_len);
+
+	UPDPORT(8, CB710_MMC_CONFIG2_PORT,
+		0, CB710_MMC_C2_READ_BLOCK_SIZE_MASK);
+
+	while (cb710_sg_read_next(&sgc, (void **)&databuf, &datalen)) {
+		datalen = (datalen + 15) >> 4;
+		do {
+			if (!(RDPORT(8, CB710_MMC_STATUS2_PORT) & 0x02)) {
+				int err = cb710_wait(chip, 2);
+				if (err) {
+					cb710_sg_abort_read(&sgc);
+					return err;
+				}
+			}
+			S_WRPORT(32, CB710_MMC_DATA_PORT, databuf, 4);
+			databuf += 4;
+		} while (--datalen);
+	}
+
+	return 0;
+}
+
+static u16 cb710_encode_cmd_flags(struct cb710_mmc_reader *reader,
+	struct mmc_command *cmd)
+{
+	unsigned int flags = cmd->flags;
+	u16 cb_flags = 0;
+
+	/* Windows driver returned 0 for commands for which no response
+	 * was expected. It happened that there were only two such commands
+	 * used: MMC_GO_IDLE_STATE and MMC_GO_INACTIVE_STATE so it might
+	 * as well be a bug in that driver.
+	 */
+
+	switch (flags & MMC_CMD_MASK) {
+	case MMC_CMD_AC:	cb_flags = CB710_MMC_CMD_AC;	break;
+	case MMC_CMD_ADTC:	cb_flags = CB710_MMC_CMD_ADTC;	break;
+	case MMC_CMD_BC:	cb_flags = CB710_MMC_CMD_BC;	break;
+	case MMC_CMD_BCR:	cb_flags = CB710_MMC_CMD_BCR;	break;
+	}
+
+	if (flags & MMC_RSP_BUSY)
+		cb_flags |= CB710_MMC_RSP_BUSY;
+
+	cb_flags |= cmd->opcode << CB710_MMC_CMD_CODE_SHIFT;
+
+/*	if (flags & MMC_CMD_IS_APP) */
+	if (reader->app_cmd) {
+		/* original driver set this bit for MMC/SD application
+		 * commands. It apparently works without it, but... oh well.
+		 */
+		cb_flags |= 0x4000;
+		reader->app_cmd = 0;
+	}
+
+	if (cmd->data && (cmd->data->flags & MMC_DATA_READ))
+		cb_flags |= CB710_MMC_DATA_READ;
+
+	if (flags & MMC_RSP_PRESENT) {
+		/* Windows driver set 01 at bits 4,3 except for
+		 * MMC_SET_BLOCKLEN. I assume that 00 here means no
+		 * response is expected.
+		 */
+		if (cmd->opcode != MMC_SET_BLOCKLEN)
+			cb_flags |= CB710_MMC_RSP_PRESENT;
+		else
+			cb_flags |= CB710_MMC_RSP_PRESENT_X;
+
+		if (flags & MMC_RSP_136)		/* R2 */
+			cb_flags |= CB710_MMC_RSP_136;
+		else if (!(flags & MMC_RSP_CRC))	/* R3 */
+			cb_flags |= CB710_MMC_RSP_NO_CRC;
+	}
+
+	return cb_flags;
+}
+
+static void cb710_mmc_reset_events(struct cb710_chip *chip)
+{
+	WRPORT(8, CB710_MMC_STATUS0_PORT, 0xFF);
+	WRPORT(8, CB710_MMC_STATUS1_PORT, 0xFF);
+	WRPORT(8, CB710_MMC_STATUS2_PORT, 0xFF);
+}
+
+static void cb710_receive_response(struct cb710_chip *chip,
+	struct mmc_command *cmd)
+{
+	unsigned rsp_opcode;
+
+	/* Looks like final byte with CRC is always stripped (like SDHCI) */
+	if (cmd->flags & MMC_RSP_136) {
+		u32 resp[4];
+
+		resp[0] = RDPORT(32, CB710_MMC_RESPONSE3_PORT);
+		resp[1] = RDPORT(32, CB710_MMC_RESPONSE2_PORT);
+		resp[2] = RDPORT(32, CB710_MMC_RESPONSE1_PORT);
+		resp[3] = RDPORT(32, CB710_MMC_RESPONSE0_PORT);
+		rsp_opcode = resp[0] >> 24;
+
+		cmd->resp[0] = (resp[0] << 8)|(resp[1] >> 24);
+		cmd->resp[1] = (resp[1] << 8)|(resp[2] >> 24);
+		cmd->resp[2] = (resp[2] << 8)|(resp[3] >> 24);
+		cmd->resp[3] = (resp[3] << 8);
+	} else {
+		rsp_opcode = RDPORT(32, CB710_MMC_RESPONSE1_PORT) & 0x3F;
+		cmd->resp[0] = RDPORT(32, CB710_MMC_RESPONSE0_PORT);
+	}
+
+	if (rsp_opcode != ((cmd->flags & MMC_RSP_OPCODE) ? cmd->opcode : 0x3F))
+		cmd->error = -EILSEQ;
+}
+
+static int cb710_mmc_transfer_data(struct cb710_chip *chip,
+	struct mmc_data *data)
+{
+	int error, to;
+
+	if (data->flags & MMC_DATA_READ)
+		error = cb710_mmc_receive(chip, data);
+	else
+		error = cb710_mmc_send(chip, data);
+
+	to = cb710_wait(chip, 4);
+	if (!error)
+		error = to;
+
+	if (!error)	/* TODO: proper counting */
+		data->bytes_xfered = data->blksz * data->blocks;
+	return error;
+}
+
+static int cb710_mmc_command(struct cb710_chip *chip, struct mmc_command *cmd)
+{
+	struct mmc_data *data = cmd->data;
+	struct cb710_mmc_reader *reader = mmc_priv(chip->mmc);
+
+	u16 cb_cmd = cb710_encode_cmd_flags(reader, cmd);
+	printk(KERN_INFO CB710_DRIVER_NAME ": %s: cmd request: 0x%04X\n",
+		mmc_hostname(chip->mmc), cb_cmd);
+
+	if (data)
+		cb710_mmc_set_transfer_size(chip, data->blocks, data->blksz);
+
+	cb710_wait12(chip, 0x30);
+	WRPORT(16, CB710_MMC_CMD_TYPE_PORT, cb_cmd);
+	cb710_wait12(chip, 0x20);
+	WRPORT(32, CB710_MMC_CMD_PARAM_PORT, cmd->arg);
+	cb710_mmc_reset_events(chip);
+	cb710_wait12(chip, 0x20);
+	UPDPORT(8, 0x04, 0x01, 0x01);
+
+	cmd->error = cb710_wait(chip, 3);
+	if (cmd->error)
+		return -1;
+
+	if (cmd->flags & MMC_RSP_PRESENT) {
+		cb710_receive_response(chip, cmd);
+		if (cmd->error)
+			return -1;
+	}
+
+	reader->app_cmd = (!reader->app_cmd && cmd->opcode == 55);
+
+	if (data)
+		data->error = cb710_mmc_transfer_data(chip, data);
+	return 0;
+}
+
+static void cb710_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+	struct cb710_chip *chip = dev_get_drvdata(mmc_dev(mmc));
+	struct cb710_mmc_reader *reader = mmc_priv(mmc);
+
+	verify_serialization(reader, &reader->active_req, 1);
+
+	WARN_ON(reader->mrq != NULL);
+
+	reader->mrq = mrq;
+	cb710_mmc_enable_irq(chip, 1);
+
+	if (cb710_mmc_is_card_inserted(chip)) {
+		if (!cb710_mmc_command(chip, mrq->cmd) && mrq->stop)
+			cb710_mmc_command(chip, mrq->stop);
+		mdelay(1);
+	} else {
+		mrq->cmd->error = -ENOMEDIUM;
+	}
+
+	tasklet_schedule(&reader->finish_req_tasklet);
+
+	verify_serialization(reader, &reader->active_req, 0);
+}
+
+static void cb710_mmc_powerup(struct cb710_chip *chip)
+{
+	/* a lot of magic; see comment in cb710_mmc_set_clock() */
+	struct cb710_mmc_reader *reader = mmc_priv(chip->mmc);
+
+	printk(KERN_INFO CB710_DRIVER_NAME ": powerup\n");
+	cb710_dump_regs(chip, 0x303);
+	cb710_wait12(chip, 0x20);
+	UPDPORT(8, 0x05, 0x80, 0);
+	UPDPORT(8, 0x07, 0x80, 0);
+	cb710_dump_regs(chip, 0x303);
+	mdelay(1);
+	printk(KERN_INFO CB710_DRIVER_NAME ": after delay 1\n");
+	cb710_dump_regs(chip, 0x303);
+	cb710_wait12(chip, 0x20);
+	UPDPORT(8, 0x05, 0x09, 0);
+	cb710_dump_regs(chip, 0x303);
+	mdelay(1);
+	printk(KERN_INFO CB710_DRIVER_NAME ": after delay 2\n");
+	cb710_dump_regs(chip, 0x303);
+	cb710_wait12(chip, 0x20);
+	UPDPORT(8, 0x05, 0, 0x08);
+	cb710_dump_regs(chip, 0x303);
+	mdelay(2);
+	printk(KERN_INFO CB710_DRIVER_NAME ": after delay 3\n");
+	cb710_dump_regs(chip, 0x303);
+	UPDPORT(8, 0x04, 0x06, 0);
+	UPDPORT(8, 0x05, 0x70, 0);
+	UPDPORT(8, 0x06, 0x80, 0);
+	UPDPORT(8, 0x07, 0x03, 0);
+	cb710_dump_regs(chip, 0x303);
+	cb710_wait12(chip, 0x20);
+	WRPORT(16, 0x08, 0xFFFF);
+	WRPORT(8, 0x09, 0xFF);
+	UPDPORT(8, 0x04, 0x06, 0);
+	cb710_dump_regs(chip, 0x303);
+	printk(KERN_INFO CB710_DRIVER_NAME ": finished\n");
+
+	reader->app_cmd = 0;
+}
+
+static void cb710_mmc_powerdown(struct cb710_chip *chip)
+{
+	struct cb710_mmc_reader *reader = mmc_priv(chip->mmc);
+
+	UPDPORT(8, 0x05, 0, 0x81);
+	UPDPORT(8, 0x07, 0, 0x80);
+	reader->app_cmd = 0;
+}
+
+static void cb710_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+	struct cb710_chip *chip = dev_get_drvdata(mmc_dev(mmc));
+	struct cb710_mmc_reader *reader = mmc_priv(mmc);
+
+	verify_serialization(reader, &reader->active_ios, 1);
+
+	cb710_mmc_set_clock(chip, ios->clock);
+
+	if (!cb710_mmc_is_card_inserted(chip)) {
+		printk(KERN_INFO CB710_DRIVER_NAME
+			": no card inserted - ignoring bus powerup request\n");
+		ios->power_mode = MMC_POWER_OFF;
+	}
+
+	if (ios->power_mode != reader->last_power_mode)
+	switch (ios->power_mode) {
+	case MMC_POWER_ON:
+		cb710_mmc_powerup(chip);
+		reader->last_power_mode = MMC_POWER_ON;
+		break;
+	case MMC_POWER_OFF:
+		cb710_mmc_powerdown(chip);
+		reader->last_power_mode = MMC_POWER_OFF;
+		break;
+	case MMC_POWER_UP:
+	default:
+		/* ignore */;
+	}
+
+	cb710_mmc_enable_4bit_data(chip, ios->bus_width != MMC_BUS_WIDTH_1);
+
+	cb710_mmc_enable_irq(chip, 1);
+
+	verify_serialization(reader, &reader->active_ios, 0);
+}
+
+static int cb710_mmc_get_ro(struct mmc_host *mmc)
+{
+	struct cb710_chip *chip = dev_get_drvdata(mmc_dev(mmc));
+
+	return RDPORT(8, CB710_MMC_STATUS3_PORT)
+		& CB710_MMC_S3_WRITE_PROTECTED;
+}
+
+int cb710_mmc_irq_handler(struct cb710_chip *chip)
+{
+	u32 status, config1, config2, irqen;
+#if 0
+	if (!(RDPORT(8, CB710_MMC_STATUS1_PORT) & CB710_MMC_S1_INTERRUPT))
+		return 0;
+#endif
+	status = RDPORT(32, 0x10);
+	irqen = RDPORT(32, 0x0C);
+	config2 = RDPORT(32, 0x08);
+	config1 = RDPORT(32, 0x04);
+	printk(KERN_INFO CB710_DRIVER_NAME ": interrupt; status: %08X, "
+		"ie: %08X, c2: %08X, c3: %08X\n",
+		status, irqen, config2, config1);
+
+	if (status & (CB710_MMC_S1_CARD_CHANGED << 8)) {
+		WRPORT(8, CB710_MMC_STATUS1_PORT, CB710_MMC_S1_CARD_CHANGED);
+		mmc_detect_change(chip->mmc, HZ/2);
+	} else {
+		printk(KERN_INFO CB710_DRIVER_NAME ": unknown interrupt\n");
+		WRPORT(8, 0x0C, 0x00);
+		WRPORT(8, 0x0D, 0x90);
+	}
+
+	return 1;
+}
+
+static void cb710_mmc_finish_request_tasklet(unsigned long data)
+{
+	struct mmc_host *mmc = (void *)data;
+	struct cb710_mmc_reader *reader = mmc_priv(mmc);
+	struct mmc_request *mrq = reader->mrq;
+
+	reader->mrq = NULL;
+	mmc_request_done(mmc, mrq);
+}
+
+static const struct mmc_host_ops cb710_mmc_host = {
+	.request = cb710_mmc_request,
+	.set_ios = cb710_mmc_set_ios,
+	.get_ro = cb710_mmc_get_ro,
+	.enable_sdio_irq = NULL,
+};
+
+int __devinit cb710_mmc_init(struct cb710_chip *chip)
+{
+	struct mmc_host *mmc;
+	struct cb710_mmc_reader *reader;
+	int err;
+	u32 val;
+
+	mmc = mmc_alloc_host(sizeof(*reader), &chip->pdev->dev);
+	if (!mmc)
+		return -ENOMEM;
+
+	chip->mmc = mmc;
+
+	/* harmless (maybe) magic */
+	pci_read_config_dword(chip->pdev, 0x48, &val);
+	val = cb710_src_freq_mhz[(val >> 16) & 0xF];
+	printk(KERN_INFO CB710_DRIVER_NAME ": %s: source frequency: %dMHz\n",
+		mmc_hostname(mmc), val
+	);
+	val *= 1000000;
+
+	mmc->ops = &cb710_mmc_host;
+	mmc->f_max = val;
+	mmc->f_min = val >> CB710_MMC_MAX_DIVIDER_LOG2;
+	mmc->ocr_avail = MMC_VDD_32_33|MMC_VDD_33_34|MMC_VDD_34_35;
+	mmc->caps = MMC_CAP_4_BIT_DATA;
+
+	reader = mmc_priv(mmc);
+
+	tasklet_init(&reader->finish_req_tasklet,
+		cb710_mmc_finish_request_tasklet, (unsigned long)mmc);
+	spin_lock_init(&reader->serialization_lock);
+
+	cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+#if 0
+	WRPORT(32, 0, 0);
+	WRPORT(8, CB710_MMC_CONFIG0_PORT, 0);
+	WRPORT(8, CB710_MMC_CONFIG1_PORT, 0);
+	WRPORT(8, CB710_MMC_CONFIG2_PORT, 0);
+	WRPORT(8, CB710_MMC_CONFIG3_PORT, 0);
+	WRPORT(16, 8, 0);
+	WRPORT(16, CB710_MMC_CMD_TYPE_PORT, 0);
+	WRPORT(8, CB710_MMC_STATUS0_PORT, ~0);
+	WRPORT(8, CB710_MMC_STATUS1_PORT, ~0);
+	WRPORT(8, CB710_MMC_STATUS2_PORT, ~0);
+	WRPORT(8, CB710_MMC_STATUS3_PORT, ~0);
+	msleep(3);
+	cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+#endif
+
+	err = mmc_add_host(mmc);
+	if (!err)
+		return 0;
+
+	printk(KERN_INFO CB710_DRIVER_NAME ": %s: add_host failed: %d\n",
+		mmc_hostname(mmc), err);
+
+	chip->mmc = NULL;
+	mmc_free_host(mmc);
+
+	return err;
+}
+
+void __devexit cb710_mmc_exit(struct cb710_chip *chip)
+{
+	struct mmc_host *mmc = chip->mmc;
+	struct cb710_mmc_reader *reader = mmc_priv(mmc);
+
+	mmc_remove_host(mmc);
+
+	cb710_mmc_enable_irq(chip, 0);
+
+	/* clear config ports - just in case */
+	WRPORT(32, 0x04, 0);
+	WRPORT(16, 0x08, 0);
+
+	tasklet_kill(&reader->finish_req_tasklet);
+
+	chip->mmc = NULL;
+	mmc_free_host(mmc);
+}
+
diff -urN empty/sgbuf.c cb710-pre-20080911/sgbuf.c
--- empty/sgbuf.c	1970-01-01 01:00:00.000000000 +0100
+++ cb710-pre-20080911/sgbuf.c	2008-09-11 21:05:39.000000000 +0200
@@ -0,0 +1,196 @@
+/*
+ *  cb710/sgbuf.c
+ *
+ *  Copyleft by Michał Mirosław, 2008
+ *
+ * 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.
+ */
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/scatterlist.h>
+#include <linux/highmem.h>
+#include "cb710.h"
+
+#define CB710_SG_BUFFER_MASK  (~(CB710_SG_BUFFER_BLOCK - 1))
+
+static void cb710_sg_init_element(struct cb710_sg_chain *buf)
+{
+	buf->cur_offset = 0;
+	buf->page_offset = buf->sg->offset & ~PAGE_MASK;
+	buf->page_no = buf->sg->offset >> PAGE_SHIFT;
+	buf->page = nth_page(sg_page(buf->sg), buf->page_no);
+
+	printk(KERN_INFO "sg: moved to new scatterlist entry: "
+		"first page +%d, poffs=%d, len=%d\n",
+		buf->page_no, buf->page_offset, buf->sg->length);
+}
+
+static void cb710_sg_unmap_page(struct cb710_sg_chain *buf, int to_sg)
+{
+	printk(KERN_INFO "sg: unmapping %s page\n",
+		to_sg ? "written" : "read");
+
+	if (to_sg)
+		flush_kernel_dcache_page(buf->page);
+	kunmap_atomic(buf->mapped_page, KM_BIO_SRC_IRQ);
+	buf->mapped_page = NULL;
+}
+
+static int cb710_sg_advance(struct cb710_sg_chain *buf, int advance, int to_sg)
+{
+	size_t rlen;
+	unsigned page_end;
+
+	buf->cur_offset += advance;
+	buf->page_offset += advance;
+	rlen = buf->sg->length - buf->cur_offset;
+
+	printk(KERN_INFO "sg: advanced %d bytes; "
+		"cur_offset=%d, page_offset=%d, rlen=%d\n",
+		advance, buf->cur_offset, buf->page_offset, rlen);
+
+	if (!rlen || buf->page_offset == PAGE_SIZE) {
+		if (buf->mapped_page)
+			cb710_sg_unmap_page(buf, to_sg);
+
+		if (!rlen) {
+			if (!--buf->sg_num) {
+				buf->need_bounce = 1;
+				return 0;
+			}
+			buf->sg = sg_next(buf->sg);
+			cb710_sg_init_element(buf);
+			rlen = buf->sg->length;
+		} else {
+			buf->page_offset = 0;
+			buf->page = nth_page(sg_page(buf->sg), ++buf->page_no);
+		}
+	}
+
+	page_end = (buf->page_offset + PAGE_SIZE) & PAGE_MASK;
+	buf->page_left = page_end - buf->page_offset;
+	if (buf->page_left > rlen)
+		buf->page_left = rlen;
+	if (buf->page_left < CB710_SG_BUFFER_BLOCK)
+		buf->need_bounce = 1;
+
+	if (!buf->mapped_page) {
+		buf->mapped_page = kmap_atomic(buf->page, KM_BIO_SRC_IRQ);
+		printk(KERN_INFO "sg: mapped new page: +%d @0x%p\n",
+			buf->page_no, buf->mapped_page);
+	}
+
+	return 1;
+}
+
+void cb710_sg_init(struct cb710_sg_chain *buf,
+	struct scatterlist *sg, size_t nelem)
+{
+	printk(KERN_INFO "sg: init: %d elements\n", nelem);
+	BUG_ON(!nelem);
+
+	memset(buf, 0, sizeof(*buf));
+	buf->sg = sg;
+	buf->sg_num = nelem;
+	cb710_sg_init_element(buf);
+	cb710_sg_advance(buf, 0, 0);
+}
+
+static int cb710_sg_use_bounce(struct cb710_sg_chain *buf, int to_sg)
+{
+	size_t len = 0;
+	size_t bounce_offset = 0;
+
+	do {
+		len = CB710_SG_BUFFER_BLOCK - bounce_offset;
+		if (len > buf->sg->length - buf->cur_offset)
+			len = buf->sg->length - buf->cur_offset;
+
+		if (to_sg)
+			memcpy(buf->mapped_page + buf->page_offset,
+				buf->bounce_buffer + bounce_offset, len);
+		else
+			memcpy(buf->bounce_buffer + bounce_offset,
+				buf->mapped_page + buf->page_offset, len);
+
+		bounce_offset += len;
+	} while (cb710_sg_advance(buf, len, to_sg)
+		&& bounce_offset < CB710_SG_BUFFER_BLOCK);
+
+	printk(KERN_INFO "sg: %d bytes %s sg via bounce_buffer\n",
+		bounce_offset, to_sg ? "written to" : "to read from");
+	return bounce_offset;
+}
+
+static size_t cb710_sg_bounce_space(struct cb710_sg_chain *buf)
+{
+	struct scatterlist *sg;
+	unsigned i;
+	size_t len = buf->sg->length - buf->cur_offset;
+
+	if (len >= CB710_SG_BUFFER_BLOCK)
+		return CB710_SG_BUFFER_BLOCK;
+	if (buf->sg_num == 1)
+		return len;
+
+	for_each_sg(sg_next(buf->sg), sg, buf->sg_num - 1, i) {
+		len += sg->length;
+		if (len >= CB710_SG_BUFFER_BLOCK)
+			return CB710_SG_BUFFER_BLOCK;
+	}
+
+	return len;
+}
+
+int cb710_sg_next_buf(struct cb710_sg_chain *buf,
+	void **dataptr, size_t *len, int to_sg)
+{
+	printk(KERN_INFO "sg: next buffer to %s\n", to_sg ? "write" : "read");
+
+	if (buf->use_bounce) {
+		buf->use_bounce = 0;
+		cb710_sg_use_bounce(buf, 1);
+	}
+
+	if (buf->need_advance) {
+		cb710_sg_advance(buf, buf->need_advance, to_sg);
+		buf->need_advance = 0;
+	}
+
+	if (!buf->need_bounce) {
+		BUG_ON(!buf->mapped_page);
+
+		*dataptr = buf->mapped_page + buf->page_offset;
+		buf->need_advance = *len =
+			buf->page_left & CB710_SG_BUFFER_MASK;
+
+		printk(KERN_INFO "sg: %d bytes mapped directly\n", *len);
+		return 1;
+	}
+
+	if (!buf->sg_num)
+		return 0;
+
+	buf->need_bounce = 0;
+	*dataptr = &buf->bounce_buffer;
+
+	if (to_sg) {
+		*len = cb710_sg_bounce_space(buf);
+		printk(KERN_INFO "sg: "
+			"using bounce_buffer for writing %d bytes\n", *len);
+		buf->use_bounce = 1;
+	} else
+		*len = cb710_sg_use_bounce(buf, 0);
+
+	return *len != 0;
+}
+
+void cb710_sg_abort(struct cb710_sg_chain *buf, int to_sg)
+{
+	printk(KERN_INFO "sg: aborting\n");
+
+	if (buf->mapped_page)
+		cb710_sg_unmap_page(buf, to_sg);
+}

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

* RFC: Driver for CB710/720 memory card reader (MMC part)
  2008-09-11 20:13     ` Driver for CB710/720 memory card reader (MMC part) Michał Mirosław
@ 2008-09-12 23:43       ` Michał Mirosław
  2008-09-20 11:00         ` Pierre Ossman
  0 siblings, 1 reply; 21+ messages in thread
From: Michał Mirosław @ 2008-09-12 23:43 UTC (permalink / raw)
  To: linux-kernel; +Cc: Pierre Ossman, Alex Dubov

Hello again,

Here is next version of CB710 MMC-host driver. Since I don't like
copying code I used platform device "bus" instead of duplicating
tifm_core and modifying it to cb710 specifics. Debugging printk()s
were retained for now.

Please comment,
Michał Mirosław

diff -urN empty/cb710.h cb710-pre-20080913/cb710.h
--- empty/cb710.h	1970-01-01 01:00:00.000000000 +0100
+++ cb710-pre-20080913/cb710.h	2008-09-13 00:30:51.000000000 +0200
@@ -0,0 +1,186 @@
+/*
+ *  cb710/cb710.h
+ *
+ *  Copyleft by Michał Mirosław, 2008
+ *
+ * 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.
+ */
+#ifndef LINUX_CB710_DRIVER_H
+#define LINUX_CB710_DRIVER_H
+
+#include <linux/spinlock.h>
+#include <linux/mmc/host.h>
+#include <linux/pci.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+
+#define CB710_DRIVER_NAME "cb710"
+
+struct cb710_slot {
+	struct platform_device pdev;
+	int (*irq_handler)(struct cb710_slot *);
+};
+
+/* per-device structure */
+struct cb710_chip {
+	struct pci_dev *pdev;
+	void __iomem *iobase;
+
+	spinlock_t irq_lock;
+	unsigned irq_enabled;
+	unsigned platform_id;
+
+	unsigned slots;
+	struct cb710_slot slot[0];
+};
+
+#define CB710_SLOT_MASK		7
+#define CB710_SLOT_MMC		1
+#define CB710_SLOT_MS		2
+#define CB710_SLOT_SM		4
+#define CB710_SLOT_NR_SHIFT	8
+
+#define RDPORT(t, p) \
+	ioread##t(chip->iobase + (p))
+#define WRPORT(t, p, v) \
+	do { iowrite##t((v), chip->iobase + (p)); \
+		(void)ioread8(chip->iobase + 0x13); } while (0)
+#define UPDPORT(t, p, v, m) \
+	do { \
+		iowrite##t( \
+			(ioread##t(chip->iobase + (p)) & ~(m)) | (v), \
+			chip->iobase + (p)); \
+		(void)ioread8(chip->iobase + 0x13); \
+	} while (0)
+#define S_RDPORT(t, p, b, c) \
+	ioread##t##_rep(chip->iobase + (p), (b), (c))
+#define S_WRPORT(t, p, b, c) \
+	do { \
+		iowrite##t##_rep(chip->iobase + (p), (b), (c)); \
+		(void)ioread8(chip->iobase + 0x13); \
+	} while (0)
+
+
+void __cb710_pci_update_config_reg(struct pci_dev *pdev,
+	int reg, uint32_t mask, uint32_t xor);
+#define cb710_pci_update_config_reg(d, r, m, x) \
+	__cb710_pci_update_config_reg((d), (r), ~(m), (x))
+
+/* sg-to-PIO buffer */
+#define CB710_SG_BUFFER_BLOCK	16	/* power of two */
+struct cb710_sg_chain {
+	uint8_t bounce_buffer[CB710_SG_BUFFER_BLOCK];
+	struct scatterlist *sg;
+	unsigned int sg_num;
+	struct page *page;
+	void *mapped_page;
+	size_t cur_offset;
+	size_t need_advance;
+	unsigned page_no;
+	unsigned page_offset;
+	unsigned page_left;
+	unsigned need_bounce:1;
+	unsigned use_bounce:1;
+};
+
+void cb710_sg_init(struct cb710_sg_chain *buf,
+	struct scatterlist *sg, size_t nelem);
+int cb710_sg_next_buf(struct cb710_sg_chain *buf,
+	void **dataptr, size_t *len, int to_sg);
+void cb710_sg_abort(struct cb710_sg_chain *buf, int to_sg);
+
+#define cb710_sg_read_next(b, d, l) \
+	cb710_sg_next_buf((b), (d), (l), 0)
+#define cb710_sg_write_next(b, d, l) \
+	cb710_sg_next_buf((b), (d), (l), 1)
+#define cb710_sg_abort_read(b) \
+	cb710_sg_abort((b), 0)
+#define cb710_sg_abort_write(b) \
+	cb710_sg_abort((b), 1)
+
+/* per-MMC-reader structure */
+struct cb710_mmc_reader {
+	struct tasklet_struct finish_req_tasklet;
+	struct mmc_request *mrq;
+	unsigned char last_power_mode;
+	unsigned char app_cmd;
+	spinlock_t serialization_lock;
+	unsigned char active_req, active_ios;
+};
+
+/* some device struct walking */
+
+static inline struct cb710_chip *cb710_slot_to_chip(struct cb710_slot *slot)
+{
+	return dev_get_drvdata(slot->pdev.dev.parent);
+}
+
+static inline struct cb710_chip *cb710_mmc_to_chip(struct mmc_host *mmc)
+{
+	return dev_get_drvdata(mmc_dev(mmc));
+}
+
+static inline struct mmc_host *cb710_slot_to_mmc(struct cb710_slot *slot)
+{
+	return dev_get_drvdata(&slot->pdev.dev);
+}
+
+
+/* registers */
+
+#define CB710_MMC_DATA_PORT		0x00
+#define CB710_MMC_CONFIG_PORT		0x04
+#define CB710_MMC_CONFIG0_PORT		0x04
+#define CB710_MMC_CONFIG1_PORT		0x05
+#define   CB710_MMC_C1_4BIT_DATA_BUS		0x40
+#define CB710_MMC_CONFIG2_PORT	0x06
+#define   CB710_MMC_C2_READ_BLOCK_SIZE_MASK	0x0F	/* N-1 */
+#define CB710_MMC_CONFIG3_PORT	0x07
+#define CB710_MMC_INTERRUPT_ENABLE_PORT	0x0D
+#define   CB710_MMC_IE_IRQ_ENABLE		0x80
+#define   CB710_MMC_IE_CARD_INSERTION_STATUS	0x10
+#define CB710_MMC_STATUS_PORT		0x10
+#define CB710_MMC_STATUS0_PORT		0x10
+#define CB710_MMC_STATUS1_PORT		0x11
+#define   CB710_MMC_S1_CARD_CHANGED		0x10
+#define   CB710_MMC_S1_RESET			0x20	/* XXX really? */
+#define CB710_MMC_STATUS2_PORT		0x12
+#define CB710_MMC_STATUS3_PORT		0x13
+#define   CB710_MMC_S3_CARD_DETECTED		0x02
+#define   CB710_MMC_S3_WRITE_PROTECTED		0x04
+#define CB710_MMC_CMD_TYPE_PORT		0x14
+#define   CB710_MMC_RSP_TYPE_MASK		0x0007
+#define     CB710_MMC_RSP_R1			(0)
+#define     CB710_MMC_RSP_136			(5)
+#define     CB710_MMC_RSP_NO_CRC		(2)
+#define   CB710_MMC_RSP_PRESENT_MASK		0x0018
+#define     CB710_MMC_RSP_NONE			(0 << 3)
+#define     CB710_MMC_RSP_PRESENT		(1 << 3)
+#define     CB710_MMC_RSP_PRESENT_X		(2 << 3)
+#define   CB710_MMC_CMD_TYPE_MASK		0x0060
+#define     CB710_MMC_CMD_BC			(0 << 5)
+#define     CB710_MMC_CMD_BCR			(1 << 5)
+#define     CB710_MMC_CMD_AC			(2 << 5)
+#define     CB710_MMC_CMD_ADTC			(3 << 5)
+#define   CB710_MMC_DATA_READ			0x0080
+#define   CB710_MMC_CMD_CODE_MASK		0x3F00
+#define   CB710_MMC_CMD_CODE_SHIFT		8
+#define   CB710_MMC_IS_APP_CMD			0x4000
+#define   CB710_MMC_RSP_BUSY			0x8000
+#define CB710_MMC_CMD_PARAM_PORT	0x18
+#define CB710_MMC_TRANSFER_SIZE_PORT	0x1C
+#define CB710_MMC_RESPONSE0_PORT	0x20
+#define CB710_MMC_RESPONSE1_PORT	0x24
+#define CB710_MMC_RESPONSE2_PORT	0x28
+#define CB710_MMC_RESPONSE3_PORT	0x2C
+
+void cb710_dump_regs(struct cb710_chip *chip, unsigned dump);
+#define CB710_DUMP_REGS_MMC	0x0F
+#define CB710_DUMP_REGS_MS	0x30
+#define CB710_DUMP_REGS_SM	0xC0
+#define CB710_DUMP_REGS_ALL	0xFF
+
+#endif /* LINUX_CB710_DRIVER_H */
diff -urN empty/core.c cb710-pre-20080913/core.c
--- empty/core.c	1970-01-01 01:00:00.000000000 +0100
+++ cb710-pre-20080913/core.c	2008-09-13 01:34:03.000000000 +0200
@@ -0,0 +1,342 @@
+/*
+ *  cb710/core.c
+ *
+ *  Copyleft by Michał Mirosław, 2008
+ *
+ * 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.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <linux/idr.h>
+#include "cb710.h"
+
+static DEFINE_IDR(cb710_idr);
+static DEFINE_SPINLOCK(cb710_idr_lock);
+
+void __cb710_pci_update_config_reg(struct pci_dev *pdev,
+	int reg, uint32_t mask, uint32_t xor)
+{
+	u32 rval;
+
+	pci_read_config_dword(pdev, reg, &rval);
+	rval = (rval & mask) ^ xor;
+	pci_write_config_dword(pdev, reg, rval);
+}
+EXPORT_SYMBOL(__cb710_pci_update_config_reg);
+
+void cb710_dump_regs(struct cb710_chip *chip, unsigned select)
+{
+	const unsigned allow[8] = {
+		0xFFF0, 0xFFFF, 0xFFFF, 0xFFFF,
+		0xFFF0, 0xFFFF, 0xFFFF, 0xFFFF,
+	};
+	const char *const prefix[sizeof(allow)/sizeof(*allow)] = {
+		"MMC", "MMC", "MMC", "MMC",
+		"MS?", "MS?", "SM?", "SM?"
+	};
+	u32 regs[sizeof(allow)/sizeof(*allow) << 2];
+
+	int i, j;
+	char msg[100], *p;
+
+	if (!select)
+		select = 0xFF;
+	if (!(select & 0x700))
+		select |= 0x100;
+
+#define reg(b, i) \
+	(((u##b*)regs)[(i) / (b/8)])
+#define allowed(b, i, j) \
+	(((allow[i >> 4] >> j) & ((1 << b/8)-1)) == ((1 << b/8)-1))
+#define dumpregs(b, f, x) { \
+	for (i = 0; i < (sizeof(allow)/sizeof(*allow) << 4); i += 0x10) { \
+		if (!(select & (1 << (i >> 4)))) \
+			continue; \
+		for (j = 0; j < 0x10; j += b/8) { \
+			if (allowed(b, i, j)) \
+				reg(b, i + j) = RDPORT(b, i + j); \
+		}; \
+	} \
+	for (i = 0; i < (sizeof(allow)/sizeof(*allow) << 4); i += 0x10) { \
+		if (!(select & (1 << (i >> 4)))) \
+			continue; \
+		p = msg; \
+		for (j = 0; j < 0x10; j += b/8) { \
+			if (allowed(b, i, j)) \
+				p += sprintf(p, " %s" f, (j == 8 ? " " : ""), \
+					reg(b, i + j)); \
+			else \
+				p += sprintf(p, " %s" x, (j == 8 ? " " : "")); \
+			udelay(1); \
+		} \
+		\
+		printk(KERN_INFO CB710_DRIVER_NAME ": %s 0x%02X %s\n", \
+			prefix[i >> 4], i, msg); \
+	} \
+	}
+
+	if (select & 0x400)
+		dumpregs(32, "%08X", "xxxxxxxx");
+	if (select & 0x200)
+		dumpregs(16, "%04X", "xxxx");
+	if (select & 0x100)
+		dumpregs( 8, "%02X", "xx");
+
+#undef dumpregs
+#undef allowed
+#undef reg
+}
+EXPORT_SYMBOL(cb710_dump_regs);
+
+/* Some magic writes based on Windows driver init code */
+static int __devinit cb710_pci_configure(struct pci_dev *pdev)
+{
+	unsigned int devfn = PCI_DEVFN(PCI_SLOT(pdev->devfn), 0);
+	struct pci_dev *pdev0 = pci_get_slot(pdev->bus, devfn);
+	u32 val;
+
+	cb710_pci_update_config_reg(pdev, 0x48, 0xC0, 0x3F);
+
+	pci_read_config_dword(pdev, 0x48, &val);
+	if (val & 0x80000000)
+		return 0;
+
+	if (!pdev0)
+		return -ENODEV;
+
+	if (pdev0->vendor == PCI_VENDOR_ID_ENE
+	    && pdev0->device == PCI_DEVICE_ID_ENE_720) {
+		cb710_pci_update_config_reg(pdev0, 0x8C, 0xE00000, 0x100000);
+		cb710_pci_update_config_reg(pdev0, 0xB0, 0, 0x08000000);
+	}
+
+	cb710_pci_update_config_reg(pdev0, 0x8C, 0x0D00, 0x0200);
+	cb710_pci_update_config_reg(pdev0, 0x90, 0x020000, 0x040000);
+
+	pci_dev_put(pdev0);
+
+	return 0;
+}
+
+static irqreturn_t cb710_irq_handler(int irq, void *data)
+{
+	struct cb710_chip *chip = data;
+	irqreturn_t handled = IRQ_NONE;
+	unsigned flags;
+	int nr;
+
+	spin_lock_irqsave(&chip->irq_lock, flags);
+
+	nr = chip->slots >> CB710_SLOT_NR_SHIFT;
+	while (nr--) {
+		struct cb710_slot *slot = &chip->slot[nr];
+		if (slot->irq_handler && slot->irq_handler(slot))
+			handled = IRQ_HANDLED;
+	}
+
+	spin_unlock_irqrestore(&chip->irq_lock, flags);
+
+	return handled;
+}
+
+static int __devinit cb710_register_slot(struct cb710_chip *chip,
+	unsigned slot_mask, unsigned io_offset, const char *name)
+{
+	int nr = chip->slots >> CB710_SLOT_NR_SHIFT;
+	struct cb710_slot *slot = &chip->slot[nr];
+	unsigned long flags;
+	int err;
+
+	slot->pdev.dev.parent = &chip->pdev->dev;
+	slot->pdev.name = name;
+	slot->pdev.id = chip->platform_id;
+	err = platform_device_register(&slot->pdev);
+
+	printk(KERN_INFO CB710_DRIVER_NAME ": register: %s.%d; slot %d\n",
+		name, chip->platform_id, slot_mask);
+	if (err)
+		return err;
+
+	spin_lock_irqsave(&chip->irq_lock, flags);
+
+	chip->slots = ((nr + 1) << CB710_SLOT_NR_SHIFT)
+		|(chip->slots & CB710_SLOT_MASK)|slot_mask;
+
+	spin_unlock_irqrestore(&chip->irq_lock, flags);
+
+	return 0;
+}
+
+static void cb710_unregister_slot(struct cb710_chip *chip,
+	unsigned slot)
+{
+	unsigned long flags;
+	int nr;
+
+	if (!(chip->slots & slot))
+		return;
+
+	spin_lock_irqsave(&chip->irq_lock, flags);
+
+	nr = (chip->slots >> CB710_SLOT_NR_SHIFT) - 1;
+	chip->slots = (nr << CB710_SLOT_NR_SHIFT)
+		|(chip->slots & CB710_SLOT_MASK & ~slot);
+
+	spin_unlock_irqrestore(&chip->irq_lock, flags);
+
+	platform_device_unregister(&chip->slot[nr].pdev);
+}
+
+static int __devinit cb710_probe(struct pci_dev *pdev,
+	const struct pci_device_id *ent)
+{
+	struct cb710_chip *chip;
+	unsigned long flags;
+	u32 val;
+	int err;
+	int n = 0;
+
+	err = cb710_pci_configure(pdev);
+	if (err)
+		return err;
+
+	pci_read_config_dword(pdev, 0x48, &val);
+	if (!(val & 0x80000000)) {
+		pci_write_config_dword(pdev, 0x48, val|0x71000000);
+		pci_read_config_dword(pdev, 0x48, &val);
+	}
+
+	printk(KERN_INFO CB710_DRIVER_NAME
+		": PCI config[0x48] = 0x%08X (%d %d %d %d %d %d)\n",
+		val,
+		!(val & 0x01000000),
+		(val >> 8) & 7,
+		!!(val & 0x10000000),
+		!!(val & 0x20000000),
+		!!(val & 0x40000000),
+		!(val & 0x02000000)
+	);
+
+	if (!(val & 0x70000000))
+		return -ENODEV;
+	val = (val >> 28) & 7;
+	if (val & CB710_SLOT_MMC)
+		++n;
+	if (val & CB710_SLOT_MS)
+		++n;
+	if (val & CB710_SLOT_SM)
+		++n;
+
+	chip = devm_kzalloc(&pdev->dev,
+		sizeof(*chip) + n * sizeof(*chip->slot), GFP_KERNEL);
+	if (!chip)
+		return -ENOMEM;
+
+	err = pcim_enable_device(pdev);
+	if (err)
+		return err;
+
+	err = pcim_iomap_regions(pdev, 0x0001, CB710_DRIVER_NAME);
+	if (err)
+		return err;
+
+	spin_lock_init(&chip->irq_lock);
+	chip->pdev = pdev;
+	chip->iobase = pcim_iomap_table(pdev)[0];
+
+	pci_set_drvdata(pdev, chip);
+
+	err = devm_request_irq(&pdev->dev, pdev->irq,
+		cb710_irq_handler, IRQF_SHARED, "cb710", chip);
+	if (err)
+		return err;
+
+	if (!idr_pre_get(&cb710_idr, GFP_KERNEL))
+		return -ENOMEM;
+
+	spin_lock_irqsave(&cb710_idr_lock, flags);
+	err = idr_get_new(&cb710_idr, chip, &chip->platform_id);
+	spin_unlock_irqrestore(&cb710_idr_lock, flags);
+	if (err)
+		return err;
+
+	dev_printk(KERN_INFO CB710_DRIVER_NAME ": ", &pdev->dev,
+		"id %d, IO 0x%p, IRQ %d\n",
+		chip->platform_id, chip->iobase, pdev->irq);
+
+	if (val & CB710_SLOT_MMC) {	/* MMC/SD slot */
+		err = cb710_register_slot(chip,
+			CB710_SLOT_MMC, 0x00, "cb710-mmc");
+		if (err)
+			return err;
+	}
+
+	if (val & CB710_SLOT_MS) {	/* MemoryStick slot */
+		err = cb710_register_slot(chip,
+			CB710_SLOT_MS, 0x40, "cb710-ms");
+		if (err)
+			goto unreg_mmc;
+	}
+
+	if (val & CB710_SLOT_SM) {	/* SmartMedia slot */
+		err = cb710_register_slot(chip,
+			CB710_SLOT_SM, 0x60, "cb710-sm");
+		if (err)
+			goto unreg_ms;
+	}
+
+	return 0;
+unreg_ms:
+	cb710_unregister_slot(chip, CB710_SLOT_MS);
+unreg_mmc:
+	cb710_unregister_slot(chip, CB710_SLOT_MMC);
+	return err;
+}
+
+static void __devexit cb710_remove_one(struct pci_dev *pdev)
+{
+	struct cb710_chip *chip = pci_get_drvdata(pdev);
+	unsigned long flags;
+
+	cb710_unregister_slot(chip, CB710_SLOT_SM);
+	cb710_unregister_slot(chip, CB710_SLOT_MS);
+	cb710_unregister_slot(chip, CB710_SLOT_MMC);
+
+	spin_lock_irqsave(&cb710_idr_lock, flags);
+	idr_remove(&cb710_idr, chip->platform_id);
+	spin_unlock_irqrestore(&cb710_idr_lock, flags);
+}
+
+static const struct pci_device_id cb710_pci_tbl[] = {
+	{ PCI_VENDOR_ID_ENE, 0x510, PCI_ANY_ID, PCI_ANY_ID, },
+	{ 0, }
+};
+
+static struct pci_driver cb710_driver = {
+	.name = "cb710",
+	.id_table = cb710_pci_tbl,
+	.probe = cb710_probe,
+	.remove = __devexit_p(cb710_remove_one),
+};
+
+static int __init cb710_init_module(void)
+{
+	return pci_register_driver(&cb710_driver);
+}
+
+static void __exit cb710_cleanup_module(void)
+{
+	pci_unregister_driver(&cb710_driver);
+}
+
+module_init(cb710_init_module);
+module_exit(cb710_cleanup_module);
+
+MODULE_AUTHOR("Michał Mirosław <mirq-linux@rere.qmqm.pl>");
+MODULE_DESCRIPTION("ENE CB710 memory card reader driver");
+MODULE_LICENSE("GPL");
+MODULE_DEVICE_TABLE(pci, cb710_pci_tbl);
diff -urN empty/Makefile cb710-pre-20080913/Makefile
--- empty/Makefile	1970-01-01 01:00:00.000000000 +0100
+++ cb710-pre-20080913/Makefile	2008-09-12 20:56:39.000000000 +0200
@@ -0,0 +1,24 @@
+ifeq ($(KERNELRELEASE),)
+
+#KDIR	:= /usr/src/linux
+KDIR	:= /usr/src/jaja/build/rechot
+PWD     := $(shell pwd)
+
+.PHONY: module install clean
+module:
+	$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules
+
+install:
+	$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules_install
+
+clean:
+	$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) clean
+
+else	# kbuild part
+
+obj-m		:= cb710-core.o cb710-mmc.o
+
+cb710-core-y	:= core.o sgbuf.o
+cb710-mmc-y	:= mmc.o
+
+endif
diff -urN empty/mmc.c cb710-pre-20080913/mmc.c
--- empty/mmc.c	1970-01-01 01:00:00.000000000 +0100
+++ cb710-pre-20080913/mmc.c	2008-09-13 01:28:10.000000000 +0200
@@ -0,0 +1,746 @@
+/*
+ *  cb710/mmc.c
+ *
+ *  Copyleft by Michał Mirosław, 2008
+ *
+ * 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.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/mmc/mmc.h>
+#include "cb710.h"
+
+static const u8 cb710_clock_divider_log2[8] = {
+/*	1, 2, 4, 8, 16, 32, 128, 512 */
+	0, 1, 2, 3,  4,  5,   7,   9
+};
+#define CB710_MMC_MAX_DIVIDER_LOG2 9
+
+static const u8 cb710_src_freq_mhz[16] = {
+	33, 10, 20, 25, 30, 35, 40, 45,
+	50, 55, 60, 65, 70, 75, 80, 85
+};
+
+static void verify_serialization(struct cb710_mmc_reader *reader,
+	unsigned char *counter, int inc)
+{
+	unsigned long flags;
+	int req, ios, cur;
+
+	spin_lock_irqsave(&reader->serialization_lock, flags);
+
+	if (inc)
+		cur = ++*counter;
+	else
+		cur = --*counter;
+	req = reader->active_req;
+	ios = reader->active_ios;
+
+	spin_unlock_irqrestore(&reader->serialization_lock, flags);
+
+	printk(KERN_INFO CB710_DRIVER_NAME ": %s driver;"
+		"counters now: ios=%d req=%d\n",
+		inc ? "entering" : "leaving", ios, req);
+	WARN_ON(cur > 1);
+}
+
+static void cb710_mmc_set_clock(struct mmc_host *mmc, int hz)
+{
+	struct cb710_chip *chip = cb710_mmc_to_chip(mmc);
+	struct pci_dev *pdev = chip->pdev;
+	u32 src_freq_idx;
+	u32 divider_idx;
+	int src_hz;
+
+	/* this is magic, unverifiable for me, unless I get
+	 * MMC card with cables connected to bus signals */
+	pci_read_config_dword(pdev, 0x48, &src_freq_idx);
+	src_freq_idx = (src_freq_idx >> 16) & 0xF;
+	src_hz = cb710_src_freq_mhz[src_freq_idx] * 1000000;
+
+	for (divider_idx = 0; divider_idx < 8; ++divider_idx) {
+		if (hz >= src_hz >> cb710_clock_divider_log2[divider_idx])
+			break;
+	}
+	if (divider_idx == 8)
+		--divider_idx;
+
+	if (src_freq_idx)
+		divider_idx |= 0x8;
+
+	cb710_pci_update_config_reg(pdev, 0x40, 0xF0000000, divider_idx << 28);
+
+	printk(KERN_INFO CB710_DRIVER_NAME
+		": %s: clock set to %d Hz, wanted %d Hz; flag = %d\n",
+		mmc_hostname(mmc),
+		src_hz >> cb710_clock_divider_log2[divider_idx & 7],
+		hz, (divider_idx & 8) != 0);
+}
+
+static void __cb710_mmc_enable_irq(struct cb710_chip *chip, int enable)
+{
+	if (enable) {
+		chip->irq_enabled |= 0x01;
+		/* look like interrupt is fired whenever
+		 * WORD[0x0C] & WORD[0x10] != 0;
+		 * let's verify it...
+		 *** bit 7 port 0x0D seems to be global interrupt enable
+		 */
+		WRPORT(8, 0x0C, ~0x40);
+		WRPORT(8, 0x0D, 0x90);
+	} else {
+		chip->irq_enabled &= ~0x01;
+		WRPORT(8, 0x0C, 0);
+		WRPORT(8, 0x0D, 0);
+	}
+}
+
+static void cb710_mmc_enable_irq(struct cb710_chip *chip, int enable)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->irq_lock, flags);
+	__cb710_mmc_enable_irq(chip, enable);
+	spin_unlock_irqrestore(&chip->irq_lock, flags);
+}
+
+static int cb710_mmc_is_card_inserted(struct cb710_chip *chip)
+{
+	return RDPORT(8, CB710_MMC_STATUS3_PORT)
+		& CB710_MMC_S3_CARD_DETECTED;
+}
+
+static void cb710_mmc_enable_4bit_data(struct cb710_chip *chip, int enable)
+{
+	printk(KERN_INFO CB710_DRIVER_NAME
+		": configuring %d-data-line%s mode\n",
+		enable ? 4 : 1, enable ? "s" : "");
+	UPDPORT(8, CB710_MMC_CONFIG1_PORT,
+		enable ? CB710_MMC_C1_4BIT_DATA_BUS : 0,
+		CB710_MMC_C1_4BIT_DATA_BUS);
+}
+
+static int cb710_check(struct cb710_chip *chip, int what)
+{
+	uint8_t status1, status2;
+
+	/* all this is magic */
+	BUG_ON(what < 2 || what > 4);
+
+	status1 = RDPORT(8, CB710_MMC_STATUS0_PORT);
+	status2 = RDPORT(8, CB710_MMC_STATUS1_PORT);
+
+	if (status1 & 0x40) {
+		printk(KERN_INFO CB710_DRIVER_NAME
+			": CHECK : ignoring bit S0=%02X & 0x40\n",
+			status1);
+		WRPORT(8, CB710_MMC_STATUS0_PORT, 0x40);
+		status1 &= ~0x40;
+	}
+
+	if (status1 || (status2 & 0x60)) {
+		printk(KERN_INFO CB710_DRIVER_NAME
+			": CHECK : returning EIO on status S0=%02X S1=%02X\n",
+			status1, status2);
+		WRPORT(8, CB710_MMC_STATUS0_PORT, status1);
+		WRPORT(8, CB710_MMC_STATUS1_PORT, 0x20);
+		return -EIO;
+	}
+
+	switch (what) {
+	case 2:		/* block transfer done */
+		if (!(status2 & 0x04))
+			break;
+		WRPORT(8, CB710_MMC_STATUS1_PORT, 0x04);
+		return 1;
+	case 3:		/* command sent */
+		if (!(status2 & 0x01))
+			break;
+		WRPORT(8, CB710_MMC_STATUS1_PORT, 0x01);
+		return 1;
+	case 4:		/* data transfer done */
+		if (!(status2 & 0x02))
+			break;
+		WRPORT(8, CB710_MMC_STATUS1_PORT, 0x02);
+		return 1;
+	}
+	return 0;
+}
+
+static int cb710_wait(struct cb710_chip *chip, int what)
+{
+	int err = 0;
+	unsigned limit = 2000000;	/* FIXME: real timeout */
+	u32 e = 0, x = 0;
+
+	e = RDPORT(32, CB710_MMC_STATUS_PORT);
+	while (!(err = cb710_check(chip, what))) {
+		if (!--limit) {
+			err = -ETIMEDOUT;
+			break;
+		}
+		udelay(1);
+	}
+	x = RDPORT(32, CB710_MMC_STATUS_PORT);
+#if 0
+	printk(KERN_INFO CB710_DRIVER_NAME ": waited %d loops, "
+		"WAIT10: what %d, entry val %08X, exit val %08X\n",
+		2000000 - limit, what, e, x);
+#endif
+	return err < 0 ? err : 0;
+}
+
+
+static int cb710_wait12(struct cb710_chip *chip, uint8_t mask)
+{
+	unsigned limit = 500000;	/* FIXME: real timeout */
+	u32 e, x;
+	int err = 0;
+
+	e = RDPORT(32, CB710_MMC_STATUS_PORT);
+	while (RDPORT(8, CB710_MMC_STATUS2_PORT) & mask) {
+		if (!--limit) {
+			err = -ETIMEDOUT;
+			break;
+		}
+		udelay(1);
+	}
+	x = RDPORT(32, CB710_MMC_STATUS_PORT);
+#if 0
+	printk(KERN_INFO CB710_DRIVER_NAME ": waited %d loops, "
+		"WAIT12: mask %02X, entry val %08X, exit val %08X\n",
+		500000 - limit, mask, e, x);
+#endif
+	return 0;
+}
+
+static void cb710_mmc_set_transfer_size(struct cb710_chip *chip,
+	size_t count, size_t blocksize)
+{
+	cb710_wait12(chip, 0x20);
+	WRPORT(32, CB710_MMC_TRANSFER_SIZE_PORT,
+		((count - 1) << 16)|(blocksize - 1));
+
+	printk(KERN_INFO CB710_DRIVER_NAME
+		": set up for %d block%s of %d bytes\n",
+		count, count == 1 ? "" : "s", blocksize);
+}
+
+static void cb710_mmc_fifo_hack(struct cb710_chip *chip)
+{
+	/* without this, received data is prepended with 8-bytes of zeroes */
+	u32 r1, r2;
+	int ok = 0;
+
+	r1 = RDPORT(32, CB710_MMC_DATA_PORT);
+	r2 = RDPORT(32, CB710_MMC_DATA_PORT);
+	if (RDPORT(8, CB710_MMC_STATUS0_PORT) & 0x40) {
+		WRPORT(8, CB710_MMC_STATUS0_PORT, 0x40);
+		ok = 1;
+	}
+
+	printk(KERN_INFO CB710_DRIVER_NAME ": FIFO-read-hack: "
+		"expected STATUS0 bit was %s dwords ignored: %08X %08X\n",
+		ok ? "set." : "NOT SET!", r1, r2);
+}
+
+static int cb710_mmc_receive(struct cb710_chip *chip, struct mmc_data *data)
+{
+	struct cb710_sg_chain sgc;
+	uint32_t *databuf;
+	size_t len;
+
+	cb710_sg_init(&sgc, data->sg, data->sg_len);
+
+	if (!cb710_sg_write_next(&sgc, (void **)&databuf, &len))
+		return 0;
+
+	UPDPORT(8, CB710_MMC_CONFIG2_PORT,
+		15, CB710_MMC_C2_READ_BLOCK_SIZE_MASK);
+
+	cb710_mmc_fifo_hack(chip);
+
+	while (len >= 16) {
+		if (!(RDPORT(8, CB710_MMC_STATUS2_PORT) & 0x01)) {
+			int err = cb710_wait(chip, 2);
+			if (err) {
+				cb710_sg_abort_write(&sgc);
+				return err;
+			}
+		}
+		S_RDPORT(32, CB710_MMC_DATA_PORT, databuf, 4);
+
+		len -= 16;
+		databuf += 4;
+
+		if (!len && !cb710_sg_write_next(&sgc,
+				(void **)&databuf, &len))
+			return 0;
+	}
+
+	UPDPORT(8, CB710_MMC_CONFIG2_PORT,
+		len - 1, CB710_MMC_C2_READ_BLOCK_SIZE_MASK);
+
+	if (RDPORT(8, CB710_MMC_STATUS2_PORT) & 0x01) {
+		int err = cb710_wait(chip, 2);
+		if (err) {
+			cb710_sg_abort_write(&sgc);
+			return err;
+		}
+	}
+
+	len = (len + 3) >> 2;
+	S_RDPORT(32, CB710_MMC_DATA_PORT, databuf, len);
+
+	if (cb710_sg_write_next(&sgc, (void **)&databuf, &len))
+		BUG();
+
+	return 0;
+}
+
+static int cb710_mmc_send(struct cb710_chip *chip, struct mmc_data *data)
+{
+	struct cb710_sg_chain sgc;
+	const uint32_t *databuf;
+	size_t datalen;
+
+	cb710_sg_init(&sgc, data->sg, data->sg_len);
+
+	UPDPORT(8, CB710_MMC_CONFIG2_PORT,
+		0, CB710_MMC_C2_READ_BLOCK_SIZE_MASK);
+
+	while (cb710_sg_read_next(&sgc, (void **)&databuf, &datalen)) {
+		datalen = (datalen + 15) >> 4;
+		do {
+			if (!(RDPORT(8, CB710_MMC_STATUS2_PORT) & 0x02)) {
+				int err = cb710_wait(chip, 2);
+				if (err) {
+					cb710_sg_abort_read(&sgc);
+					return err;
+				}
+			}
+			S_WRPORT(32, CB710_MMC_DATA_PORT, databuf, 4);
+			databuf += 4;
+		} while (--datalen);
+	}
+
+	return 0;
+}
+
+static u16 cb710_encode_cmd_flags(struct cb710_mmc_reader *reader,
+	struct mmc_command *cmd)
+{
+	unsigned int flags = cmd->flags;
+	u16 cb_flags = 0;
+
+	/* Windows driver returned 0 for commands for which no response
+	 * was expected. It happened that there were only two such commands
+	 * used: MMC_GO_IDLE_STATE and MMC_GO_INACTIVE_STATE so it might
+	 * as well be a bug in that driver.
+	 */
+
+	switch (flags & MMC_CMD_MASK) {
+	case MMC_CMD_AC:	cb_flags = CB710_MMC_CMD_AC;	break;
+	case MMC_CMD_ADTC:	cb_flags = CB710_MMC_CMD_ADTC;	break;
+	case MMC_CMD_BC:	cb_flags = CB710_MMC_CMD_BC;	break;
+	case MMC_CMD_BCR:	cb_flags = CB710_MMC_CMD_BCR;	break;
+	}
+
+	if (flags & MMC_RSP_BUSY)
+		cb_flags |= CB710_MMC_RSP_BUSY;
+
+	cb_flags |= cmd->opcode << CB710_MMC_CMD_CODE_SHIFT;
+
+/*	if (flags & MMC_CMD_IS_APP) */
+	if (reader->app_cmd) {
+		/* original driver set this bit for MMC/SD application
+		 * commands. It apparently works without it, but... oh well.
+		 */
+		cb_flags |= 0x4000;
+		reader->app_cmd = 0;
+	}
+
+	if (cmd->data && (cmd->data->flags & MMC_DATA_READ))
+		cb_flags |= CB710_MMC_DATA_READ;
+
+	if (flags & MMC_RSP_PRESENT) {
+		/* Windows driver set 01 at bits 4,3 except for
+		 * MMC_SET_BLOCKLEN. I assume that 00 here means no
+		 * response is expected.
+		 */
+		if (cmd->opcode != MMC_SET_BLOCKLEN)
+			cb_flags |= CB710_MMC_RSP_PRESENT;
+		else
+			cb_flags |= CB710_MMC_RSP_PRESENT_X;
+
+		if (flags & MMC_RSP_136)		/* R2 */
+			cb_flags |= CB710_MMC_RSP_136;
+		else if (!(flags & MMC_RSP_CRC))	/* R3 */
+			cb_flags |= CB710_MMC_RSP_NO_CRC;
+	}
+
+	return cb_flags;
+}
+
+static void cb710_mmc_reset_events(struct cb710_chip *chip)
+{
+	WRPORT(8, CB710_MMC_STATUS0_PORT, 0xFF);
+	WRPORT(8, CB710_MMC_STATUS1_PORT, 0xFF);
+	WRPORT(8, CB710_MMC_STATUS2_PORT, 0xFF);
+}
+
+static void cb710_receive_response(struct cb710_chip *chip,
+	struct mmc_command *cmd)
+{
+	unsigned rsp_opcode;
+
+	/* Looks like final byte with CRC is always stripped (like SDHCI) */
+	if (cmd->flags & MMC_RSP_136) {
+		u32 resp[4];
+
+		resp[0] = RDPORT(32, CB710_MMC_RESPONSE3_PORT);
+		resp[1] = RDPORT(32, CB710_MMC_RESPONSE2_PORT);
+		resp[2] = RDPORT(32, CB710_MMC_RESPONSE1_PORT);
+		resp[3] = RDPORT(32, CB710_MMC_RESPONSE0_PORT);
+		rsp_opcode = resp[0] >> 24;
+
+		cmd->resp[0] = (resp[0] << 8)|(resp[1] >> 24);
+		cmd->resp[1] = (resp[1] << 8)|(resp[2] >> 24);
+		cmd->resp[2] = (resp[2] << 8)|(resp[3] >> 24);
+		cmd->resp[3] = (resp[3] << 8);
+	} else {
+		rsp_opcode = RDPORT(32, CB710_MMC_RESPONSE1_PORT) & 0x3F;
+		cmd->resp[0] = RDPORT(32, CB710_MMC_RESPONSE0_PORT);
+	}
+
+	if (rsp_opcode != ((cmd->flags & MMC_RSP_OPCODE) ? cmd->opcode : 0x3F))
+		cmd->error = -EILSEQ;
+}
+
+static int cb710_mmc_transfer_data(struct cb710_chip *chip,
+	struct mmc_data *data)
+{
+	int error, to;
+
+	if (data->flags & MMC_DATA_READ)
+		error = cb710_mmc_receive(chip, data);
+	else
+		error = cb710_mmc_send(chip, data);
+
+	to = cb710_wait(chip, 4);
+	if (!error)
+		error = to;
+
+	if (!error)	/* TODO: proper counting */
+		data->bytes_xfered = data->blksz * data->blocks;
+	return error;
+}
+
+static int cb710_mmc_command(struct mmc_host *mmc, struct mmc_command *cmd)
+{
+	struct cb710_chip *chip = cb710_mmc_to_chip(mmc);
+	struct cb710_mmc_reader *reader = mmc_priv(mmc);
+	struct mmc_data *data = cmd->data;
+
+	u16 cb_cmd = cb710_encode_cmd_flags(reader, cmd);
+	printk(KERN_INFO CB710_DRIVER_NAME ": %s: cmd request: 0x%04X\n",
+		mmc_hostname(mmc), cb_cmd);
+
+	if (data)
+		cb710_mmc_set_transfer_size(chip, data->blocks, data->blksz);
+
+	cb710_wait12(chip, 0x30);
+	WRPORT(16, CB710_MMC_CMD_TYPE_PORT, cb_cmd);
+	cb710_wait12(chip, 0x20);
+	WRPORT(32, CB710_MMC_CMD_PARAM_PORT, cmd->arg);
+	cb710_mmc_reset_events(chip);
+	cb710_wait12(chip, 0x20);
+	UPDPORT(8, 0x04, 0x01, 0x01);
+
+	cmd->error = cb710_wait(chip, 3);
+	if (cmd->error)
+		return -1;
+
+	if (cmd->flags & MMC_RSP_PRESENT) {
+		cb710_receive_response(chip, cmd);
+		if (cmd->error)
+			return -1;
+	}
+
+	reader->app_cmd = (!reader->app_cmd && cmd->opcode == 55);
+
+	if (data)
+		data->error = cb710_mmc_transfer_data(chip, data);
+	return 0;
+}
+
+static void cb710_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+	struct cb710_chip *chip = cb710_mmc_to_chip(mmc);
+	struct cb710_mmc_reader *reader = mmc_priv(mmc);
+
+	verify_serialization(reader, &reader->active_req, 1);
+
+	WARN_ON(reader->mrq != NULL);
+
+	reader->mrq = mrq;
+	cb710_mmc_enable_irq(chip, 1);
+
+	if (cb710_mmc_is_card_inserted(chip)) {
+		if (!cb710_mmc_command(mmc, mrq->cmd) && mrq->stop)
+			cb710_mmc_command(mmc, mrq->stop);
+		mdelay(1);
+	} else {
+		mrq->cmd->error = -ENOMEDIUM;
+	}
+
+	tasklet_schedule(&reader->finish_req_tasklet);
+
+	verify_serialization(reader, &reader->active_req, 0);
+}
+
+static void cb710_mmc_powerup(struct cb710_chip *chip,
+	struct cb710_mmc_reader *reader)
+{
+	/* a lot of magic; see comment in cb710_mmc_set_clock() */
+	printk(KERN_INFO CB710_DRIVER_NAME ": powerup\n");
+	cb710_dump_regs(chip, 0x303);
+	cb710_wait12(chip, 0x20);
+	UPDPORT(8, 0x05, 0x80, 0);
+	UPDPORT(8, 0x07, 0x80, 0);
+	cb710_dump_regs(chip, 0x303);
+	mdelay(1);
+	printk(KERN_INFO CB710_DRIVER_NAME ": after delay 1\n");
+	cb710_dump_regs(chip, 0x303);
+	cb710_wait12(chip, 0x20);
+	UPDPORT(8, 0x05, 0x09, 0);
+	cb710_dump_regs(chip, 0x303);
+	mdelay(1);
+	printk(KERN_INFO CB710_DRIVER_NAME ": after delay 2\n");
+	cb710_dump_regs(chip, 0x303);
+	cb710_wait12(chip, 0x20);
+	UPDPORT(8, 0x05, 0, 0x08);
+	cb710_dump_regs(chip, 0x303);
+	mdelay(2);
+	printk(KERN_INFO CB710_DRIVER_NAME ": after delay 3\n");
+	cb710_dump_regs(chip, 0x303);
+	UPDPORT(8, 0x04, 0x06, 0);
+	UPDPORT(8, 0x05, 0x70, 0);
+	UPDPORT(8, 0x06, 0x80, 0);
+	UPDPORT(8, 0x07, 0x03, 0);
+	cb710_dump_regs(chip, 0x303);
+	cb710_wait12(chip, 0x20);
+	WRPORT(16, 0x08, 0xFFFF);
+	WRPORT(8, 0x09, 0xFF);
+	UPDPORT(8, 0x04, 0x06, 0);
+	cb710_dump_regs(chip, 0x303);
+	printk(KERN_INFO CB710_DRIVER_NAME ": finished\n");
+
+	reader->app_cmd = 0;
+}
+
+static void cb710_mmc_powerdown(struct cb710_chip *chip,
+	struct cb710_mmc_reader *reader)
+{
+	UPDPORT(8, 0x05, 0, 0x81);
+	UPDPORT(8, 0x07, 0, 0x80);
+	reader->app_cmd = 0;
+}
+
+static void cb710_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+	struct cb710_chip *chip = cb710_mmc_to_chip(mmc);
+	struct cb710_mmc_reader *reader = mmc_priv(mmc);
+
+	verify_serialization(reader, &reader->active_ios, 1);
+
+	cb710_mmc_set_clock(mmc, ios->clock);
+
+	if (!cb710_mmc_is_card_inserted(chip)) {
+		printk(KERN_INFO CB710_DRIVER_NAME
+			": no card inserted - ignoring bus powerup request\n");
+		ios->power_mode = MMC_POWER_OFF;
+	}
+
+	if (ios->power_mode != reader->last_power_mode)
+	switch (ios->power_mode) {
+	case MMC_POWER_ON:
+		cb710_mmc_powerup(chip, reader);
+		reader->last_power_mode = MMC_POWER_ON;
+		break;
+	case MMC_POWER_OFF:
+		cb710_mmc_powerdown(chip, reader);
+		reader->last_power_mode = MMC_POWER_OFF;
+		break;
+	case MMC_POWER_UP:
+	default:
+		/* ignore */;
+	}
+
+	cb710_mmc_enable_4bit_data(chip, ios->bus_width != MMC_BUS_WIDTH_1);
+
+	cb710_mmc_enable_irq(chip, 1);
+
+	verify_serialization(reader, &reader->active_ios, 0);
+}
+
+static int cb710_mmc_get_ro(struct mmc_host *mmc)
+{
+	struct cb710_chip *chip = cb710_mmc_to_chip(mmc);
+
+	return RDPORT(8, CB710_MMC_STATUS3_PORT)
+		& CB710_MMC_S3_WRITE_PROTECTED;
+}
+
+static int cb710_mmc_irq_handler(struct cb710_slot *slot)
+{
+	u32 status, config1, config2, irqen;
+	struct cb710_chip *chip = cb710_slot_to_chip(slot);
+#if 0
+	if (!(RDPORT(8, CB710_MMC_STATUS1_PORT) & CB710_MMC_S1_INTERRUPT))
+		return 0;
+#endif
+	status = RDPORT(32, 0x10);
+	irqen = RDPORT(32, 0x0C);
+	config2 = RDPORT(32, 0x08);
+	config1 = RDPORT(32, 0x04);
+	printk(KERN_INFO CB710_DRIVER_NAME ": interrupt; status: %08X, "
+		"ie: %08X, c2: %08X, c3: %08X\n",
+		status, irqen, config2, config1);
+
+	if (status & (CB710_MMC_S1_CARD_CHANGED << 8)) {
+		WRPORT(8, CB710_MMC_STATUS1_PORT, CB710_MMC_S1_CARD_CHANGED);
+		mmc_detect_change(cb710_slot_to_mmc(slot), HZ/2);
+	} else {
+		printk(KERN_INFO CB710_DRIVER_NAME ": unknown interrupt\n");
+		WRPORT(8, 0x0C, 0x00);
+		WRPORT(8, 0x0D, 0x90);
+	}
+
+	return 1;
+}
+
+static void cb710_mmc_finish_request_tasklet(unsigned long data)
+{
+	struct mmc_host *mmc = (void *)data;
+	struct cb710_mmc_reader *reader = mmc_priv(mmc);
+	struct mmc_request *mrq = reader->mrq;
+
+	reader->mrq = NULL;
+	mmc_request_done(mmc, mrq);
+}
+
+static const struct mmc_host_ops cb710_mmc_host = {
+	.request = cb710_mmc_request,
+	.set_ios = cb710_mmc_set_ios,
+	.get_ro = cb710_mmc_get_ro,
+	.enable_sdio_irq = NULL,
+};
+
+static int __devinit cb710_mmc_init(struct platform_device *pdev)
+{
+	struct cb710_slot *slot = container_of(pdev, struct cb710_slot, pdev);
+	struct cb710_chip *chip = dev_get_drvdata(pdev->dev.parent);
+	struct mmc_host *mmc;
+	struct cb710_mmc_reader *reader;
+	unsigned long flags;
+	int err;
+	u32 val;
+
+	mmc = mmc_alloc_host(sizeof(*reader), &chip->pdev->dev);
+	if (!mmc)
+		return -ENOMEM;
+
+	dev_set_drvdata(&pdev->dev, mmc);
+
+	/* harmless (maybe) magic */
+	pci_read_config_dword(chip->pdev, 0x48, &val);
+	val = cb710_src_freq_mhz[(val >> 16) & 0xF];
+	printk(KERN_INFO CB710_DRIVER_NAME ": %s: source frequency: %dMHz\n",
+		mmc_hostname(mmc), val
+	);
+	val *= 1000000;
+
+	mmc->ops = &cb710_mmc_host;
+	mmc->f_max = val;
+	mmc->f_min = val >> CB710_MMC_MAX_DIVIDER_LOG2;
+	mmc->ocr_avail = MMC_VDD_32_33|MMC_VDD_33_34|MMC_VDD_34_35;
+	mmc->caps = MMC_CAP_4_BIT_DATA;
+
+	reader = mmc_priv(mmc);
+
+	tasklet_init(&reader->finish_req_tasklet,
+		cb710_mmc_finish_request_tasklet, (unsigned long)mmc);
+	spin_lock_init(&reader->serialization_lock);
+
+	cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+
+	spin_lock_irqsave(&chip->irq_lock, flags);
+	slot->irq_handler = cb710_mmc_irq_handler;
+	spin_unlock_irqrestore(&chip->irq_lock, flags);
+
+	err = mmc_add_host(mmc);
+	if (!err)
+		return 0;
+
+	printk(KERN_INFO CB710_DRIVER_NAME ": %s: add_host failed: %d\n",
+		mmc_hostname(mmc), err);
+
+	mmc_free_host(mmc);
+	return err;
+}
+
+static int __devexit cb710_mmc_exit(struct platform_device *pdev)
+{
+	struct cb710_slot *slot = container_of(pdev, struct cb710_slot, pdev);
+	struct cb710_chip *chip = dev_get_drvdata(pdev->dev.parent);
+	struct mmc_host *mmc = dev_get_drvdata(&pdev->dev);
+	struct cb710_mmc_reader *reader = mmc_priv(mmc);
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->irq_lock, flags);
+	slot->irq_handler = NULL;
+	spin_unlock_irqrestore(&chip->irq_lock, flags);
+
+	mmc_remove_host(mmc);
+
+	cb710_mmc_enable_irq(chip, 0);
+
+	/* clear config ports - just in case */
+	WRPORT(32, 0x04, 0);
+	WRPORT(16, 0x08, 0);
+
+	tasklet_kill(&reader->finish_req_tasklet);
+
+	mmc_free_host(mmc);
+	return 0;
+}
+
+static struct platform_driver cb710_mmc_driver = {
+	.driver.name = "cb710-mmc",
+	.probe = cb710_mmc_init,
+	.remove = __devexit_p(cb710_mmc_exit)
+};
+
+static int __init cb710_mmc_init_module(void)
+{
+	return platform_driver_register(&cb710_mmc_driver);
+}
+
+static void __exit cb710_mmc_cleanup_module(void)
+{
+	platform_driver_unregister(&cb710_mmc_driver);
+}
+
+module_init(cb710_mmc_init_module);
+module_exit(cb710_mmc_cleanup_module);
+
+MODULE_AUTHOR("Michał Mirosław <mirq-linux@rere.qmqm.pl>");
+MODULE_DESCRIPTION("ENE CB710 memory card reader driver - MMC/SD part");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:cb710-mmc");
diff -urN empty/sgbuf.c cb710-pre-20080913/sgbuf.c
--- empty/sgbuf.c	1970-01-01 01:00:00.000000000 +0100
+++ cb710-pre-20080913/sgbuf.c	2008-09-12 19:24:21.000000000 +0200
@@ -0,0 +1,200 @@
+/*
+ *  cb710/sgbuf.c
+ *
+ *  Copyleft by Michał Mirosław, 2008
+ *
+ * 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.
+ */
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/scatterlist.h>
+#include <linux/highmem.h>
+#include "cb710.h"
+
+#define CB710_SG_BUFFER_MASK  (~(CB710_SG_BUFFER_BLOCK - 1))
+
+static void cb710_sg_init_element(struct cb710_sg_chain *buf)
+{
+	buf->cur_offset = 0;
+	buf->page_offset = buf->sg->offset & ~PAGE_MASK;
+	buf->page_no = buf->sg->offset >> PAGE_SHIFT;
+	buf->page = nth_page(sg_page(buf->sg), buf->page_no);
+
+	printk(KERN_INFO "sg: moved to new scatterlist entry: "
+		"first page +%d, poffs=%d, len=%d\n",
+		buf->page_no, buf->page_offset, buf->sg->length);
+}
+
+static void cb710_sg_unmap_page(struct cb710_sg_chain *buf, int to_sg)
+{
+	printk(KERN_INFO "sg: unmapping %s page\n",
+		to_sg ? "written" : "read");
+
+	if (to_sg)
+		flush_kernel_dcache_page(buf->page);
+	kunmap_atomic(buf->mapped_page, KM_BIO_SRC_IRQ);
+	buf->mapped_page = NULL;
+}
+
+static int cb710_sg_advance(struct cb710_sg_chain *buf, int advance, int to_sg)
+{
+	size_t rlen;
+	unsigned page_end;
+
+	buf->cur_offset += advance;
+	buf->page_offset += advance;
+	rlen = buf->sg->length - buf->cur_offset;
+
+	printk(KERN_INFO "sg: advanced %d bytes; "
+		"cur_offset=%d, page_offset=%d, rlen=%d\n",
+		advance, buf->cur_offset, buf->page_offset, rlen);
+
+	if (!rlen || buf->page_offset == PAGE_SIZE) {
+		if (buf->mapped_page)
+			cb710_sg_unmap_page(buf, to_sg);
+
+		if (!rlen) {
+			if (!--buf->sg_num) {
+				buf->need_bounce = 1;
+				return 0;
+			}
+			buf->sg = sg_next(buf->sg);
+			cb710_sg_init_element(buf);
+			rlen = buf->sg->length;
+		} else {
+			buf->page_offset = 0;
+			buf->page = nth_page(sg_page(buf->sg), ++buf->page_no);
+		}
+	}
+
+	page_end = (buf->page_offset + PAGE_SIZE) & PAGE_MASK;
+	buf->page_left = page_end - buf->page_offset;
+	if (buf->page_left > rlen)
+		buf->page_left = rlen;
+	if (buf->page_left < CB710_SG_BUFFER_BLOCK)
+		buf->need_bounce = 1;
+
+	if (!buf->mapped_page) {
+		buf->mapped_page = kmap_atomic(buf->page, KM_BIO_SRC_IRQ);
+		printk(KERN_INFO "sg: mapped new page: +%d @0x%p\n",
+			buf->page_no, buf->mapped_page);
+	}
+
+	return 1;
+}
+
+void cb710_sg_init(struct cb710_sg_chain *buf,
+	struct scatterlist *sg, size_t nelem)
+{
+	printk(KERN_INFO "sg: init: %d elements\n", nelem);
+	BUG_ON(!nelem);
+
+	memset(buf, 0, sizeof(*buf));
+	buf->sg = sg;
+	buf->sg_num = nelem;
+	cb710_sg_init_element(buf);
+	cb710_sg_advance(buf, 0, 0);
+}
+EXPORT_SYMBOL(cb710_sg_init);
+
+static int cb710_sg_use_bounce(struct cb710_sg_chain *buf, int to_sg)
+{
+	size_t len = 0;
+	size_t bounce_offset = 0;
+
+	do {
+		len = CB710_SG_BUFFER_BLOCK - bounce_offset;
+		if (len > buf->sg->length - buf->cur_offset)
+			len = buf->sg->length - buf->cur_offset;
+
+		if (to_sg)
+			memcpy(buf->mapped_page + buf->page_offset,
+				buf->bounce_buffer + bounce_offset, len);
+		else
+			memcpy(buf->bounce_buffer + bounce_offset,
+				buf->mapped_page + buf->page_offset, len);
+
+		bounce_offset += len;
+	} while (cb710_sg_advance(buf, len, to_sg)
+		&& bounce_offset < CB710_SG_BUFFER_BLOCK);
+
+	printk(KERN_INFO "sg: %d bytes %s sg via bounce_buffer\n",
+		bounce_offset, to_sg ? "written to" : "to read from");
+	return bounce_offset;
+}
+
+static size_t cb710_sg_bounce_space(struct cb710_sg_chain *buf)
+{
+	struct scatterlist *sg;
+	unsigned i;
+	size_t len = buf->sg->length - buf->cur_offset;
+
+	if (len >= CB710_SG_BUFFER_BLOCK)
+		return CB710_SG_BUFFER_BLOCK;
+	if (buf->sg_num == 1)
+		return len;
+
+	for_each_sg(sg_next(buf->sg), sg, buf->sg_num - 1, i) {
+		len += sg->length;
+		if (len >= CB710_SG_BUFFER_BLOCK)
+			return CB710_SG_BUFFER_BLOCK;
+	}
+
+	return len;
+}
+
+int cb710_sg_next_buf(struct cb710_sg_chain *buf,
+	void **dataptr, size_t *len, int to_sg)
+{
+	printk(KERN_INFO "sg: next buffer to %s\n", to_sg ? "write" : "read");
+
+	if (buf->use_bounce) {
+		buf->use_bounce = 0;
+		cb710_sg_use_bounce(buf, 1);
+	}
+
+	if (buf->need_advance) {
+		cb710_sg_advance(buf, buf->need_advance, to_sg);
+		buf->need_advance = 0;
+	}
+
+	if (!buf->need_bounce) {
+		BUG_ON(!buf->mapped_page);
+
+		*dataptr = buf->mapped_page + buf->page_offset;
+		buf->need_advance = *len =
+			buf->page_left & CB710_SG_BUFFER_MASK;
+
+		printk(KERN_INFO "sg: %d bytes mapped directly\n", *len);
+		return 1;
+	}
+
+	if (!buf->sg_num)
+		return 0;
+
+	buf->need_bounce = 0;
+	*dataptr = &buf->bounce_buffer;
+
+	if (to_sg) {
+		*len = cb710_sg_bounce_space(buf);
+		printk(KERN_INFO "sg: "
+			"using bounce_buffer for writing %d bytes\n", *len);
+		buf->use_bounce = 1;
+	} else
+		*len = cb710_sg_use_bounce(buf, 0);
+
+	return *len != 0;
+}
+EXPORT_SYMBOL(cb710_sg_next_buf);
+
+void cb710_sg_abort(struct cb710_sg_chain *buf, int to_sg)
+{
+	printk(KERN_INFO "sg: aborting\n");
+
+	if (buf->mapped_page)
+		cb710_sg_unmap_page(buf, to_sg);
+}
+EXPORT_SYMBOL(cb710_sg_abort);
+

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

* Re: RFC: Driver for CB710/720 memory card reader (MMC part)
  2008-09-12 23:43       ` RFC: " Michał Mirosław
@ 2008-09-20 11:00         ` Pierre Ossman
  2008-09-25  6:29           ` RFC: Driver for CB710/720 memory card reader (MMC part) - v2 Michał Mirosław
  0 siblings, 1 reply; 21+ messages in thread
From: Pierre Ossman @ 2008-09-20 11:00 UTC (permalink / raw)
  To: Michał Mirosław; +Cc: linux-kernel, Alex Dubov

On Sat, 13 Sep 2008 01:43:02 +0200
Michał Mirosław <mirq-linux@rere.qmqm.pl> wrote:

> Hello again,
> 
> Here is next version of CB710 MMC-host driver. Since I don't like
> copying code I used platform device "bus" instead of duplicating
> tifm_core and modifying it to cb710 specifics. Debugging printk()s
> were retained for now.
> 
> Please comment,
> Michał Mirosław
> 
> diff -urN empty/cb710.h cb710-pre-20080913/cb710.h
> --- empty/cb710.h	1970-01-01 01:00:00.000000000 +0100
> +++ cb710-pre-20080913/cb710.h	2008-09-13 00:30:51.000000000 +0200
> @@ -0,0 +1,186 @@
> +/*
> + *  cb710/cb710.h
> + *
> + *  Copyleft by Michał Mirosław, 2008
> + *

You should probably stick with "Copyright" even though it is a free
software license.

> +
> +#define RDPORT(t, p) \
> +	ioread##t(chip->iobase + (p))
> +#define WRPORT(t, p, v) \
> +	do { iowrite##t((v), chip->iobase + (p)); \
> +		(void)ioread8(chip->iobase + 0x13); } while (0)
> +#define UPDPORT(t, p, v, m) \
> +	do { \
> +		iowrite##t( \
> +			(ioread##t(chip->iobase + (p)) & ~(m)) | (v), \
> +			chip->iobase + (p)); \
> +		(void)ioread8(chip->iobase + 0x13); \
> +	} while (0)
> +#define S_RDPORT(t, p, b, c) \
> +	ioread##t##_rep(chip->iobase + (p), (b), (c))
> +#define S_WRPORT(t, p, b, c) \
> +	do { \
> +		iowrite##t##_rep(chip->iobase + (p), (b), (c)); \
> +		(void)ioread8(chip->iobase + 0x13); \
> +	} while (0)
> +

This is a pretty bad case of obfuscation. Don't construct macros that
reference local variables (chip in this case). Also,
iowrite16_rep(chip->iobase + CB700_FOO, buffer, 12); is quite readable
as it is. Using standard kernel functions allows your code to be more
accessible to other kernel hackers.

> +
> +void __cb710_pci_update_config_reg(struct pci_dev *pdev,
> +	int reg, uint32_t mask, uint32_t xor);
> +#define cb710_pci_update_config_reg(d, r, m, x) \
> +	__cb710_pci_update_config_reg((d), (r), ~(m), (x))

Again, this just obfuscates. If you keep the ~ in calling code it
becomes more obvious that it is a mask.

> +/* sg-to-PIO buffer */
> +#define CB710_SG_BUFFER_BLOCK	16	/* power of two */
> +struct cb710_sg_chain {
> +	uint8_t bounce_buffer[CB710_SG_BUFFER_BLOCK];
> +	struct scatterlist *sg;
> +	unsigned int sg_num;
> +	struct page *page;
> +	void *mapped_page;
> +	size_t cur_offset;
> +	size_t need_advance;
> +	unsigned page_no;
> +	unsigned page_offset;
> +	unsigned page_left;
> +	unsigned need_bounce:1;
> +	unsigned use_bounce:1;
> +};
> +
> +void cb710_sg_init(struct cb710_sg_chain *buf,
> +	struct scatterlist *sg, size_t nelem);
> +int cb710_sg_next_buf(struct cb710_sg_chain *buf,
> +	void **dataptr, size_t *len, int to_sg);
> +void cb710_sg_abort(struct cb710_sg_chain *buf, int to_sg);
> +
> +#define cb710_sg_read_next(b, d, l) \
> +	cb710_sg_next_buf((b), (d), (l), 0)
> +#define cb710_sg_write_next(b, d, l) \
> +	cb710_sg_next_buf((b), (d), (l), 1)
> +#define cb710_sg_abort_read(b) \
> +	cb710_sg_abort((b), 0)
> +#define cb710_sg_abort_write(b) \
> +	cb710_sg_abort((b), 1)
> +

Why this complex system? Can't you use the handlers the kernel already
provides? You also get a lot of special handling with those, e.g.
highmem.

> +void cb710_dump_regs(struct cb710_chip *chip, unsigned select)
> +{
> +	const unsigned allow[8] = {
> +		0xFFF0, 0xFFFF, 0xFFFF, 0xFFFF,
> +		0xFFF0, 0xFFFF, 0xFFFF, 0xFFFF,
> +	};
> +	const char *const prefix[sizeof(allow)/sizeof(*allow)] = {
> +		"MMC", "MMC", "MMC", "MMC",
> +		"MS?", "MS?", "SM?", "SM?"
> +	};
> +	u32 regs[sizeof(allow)/sizeof(*allow) << 2];
> +
> +	int i, j;
> +	char msg[100], *p;
> +
> +	if (!select)
> +		select = 0xFF;
> +	if (!(select & 0x700))
> +		select |= 0x100;
> +
> +#define reg(b, i) \
> +	(((u##b*)regs)[(i) / (b/8)])
> +#define allowed(b, i, j) \
> +	(((allow[i >> 4] >> j) & ((1 << b/8)-1)) == ((1 << b/8)-1))
> +#define dumpregs(b, f, x) { \

This thing is a bit big. It'll be more readable if you turn it into a
function.

> +
> +static irqreturn_t cb710_irq_handler(int irq, void *data)
> +{
> +	struct cb710_chip *chip = data;
> +	irqreturn_t handled = IRQ_NONE;
> +	unsigned flags;
> +	int nr;
> +
> +	spin_lock_irqsave(&chip->irq_lock, flags);

You shouldn't be using the interrupt versions inside the interrupt
handler. Just do a normal spin_lock().


> +
> +	pci_read_config_dword(pdev, 0x48, &val);
> +	if (!(val & 0x80000000)) {
> +		pci_write_config_dword(pdev, 0x48, val|0x71000000);
> +		pci_read_config_dword(pdev, 0x48, &val);
> +	}
> +
> +	printk(KERN_INFO CB710_DRIVER_NAME
> +		": PCI config[0x48] = 0x%08X (%d %d %d %d %d %d)\n",
> +		val,
> +		!(val & 0x01000000),
> +		(val >> 8) & 7,
> +		!!(val & 0x10000000),
> +		!!(val & 0x20000000),
> +		!!(val & 0x40000000),
> +		!(val & 0x02000000)
> +	);
> +

If this still is unknown voodoo, then please add a comment explaining
that. Otherwise I expect to see more defines. :)

> +
> +static const struct pci_device_id cb710_pci_tbl[] = {
> +	{ PCI_VENDOR_ID_ENE, 0x510, PCI_ANY_ID, PCI_ANY_ID, },
> +	{ 0, }
> +};

Allocate a define for 0x510, either in this file or pci_ids.h.

> diff -urN empty/Makefile cb710-pre-20080913/Makefile
> --- empty/Makefile	1970-01-01 01:00:00.000000000 +0100
> +++ cb710-pre-20080913/Makefile	2008-09-12 20:56:39.000000000 +0200

It's easier if you develop against the real kernel tree and send diffs
for that.

> diff -urN empty/mmc.c cb710-pre-20080913/mmc.c
> --- empty/mmc.c	1970-01-01 01:00:00.000000000 +0100
> +++ cb710-pre-20080913/mmc.c	2008-09-13 01:28:10.000000000 +0200

> +#include <linux/mmc/mmc.h>

You should never have to include this in a host driver.

> +	printk(KERN_INFO CB710_DRIVER_NAME
> +		": %s: clock set to %d Hz, wanted %d Hz; flag = %d\n",
> +		mmc_hostname(mmc),
> +		src_hz >> cb710_clock_divider_log2[divider_idx & 7],
> +		hz, (divider_idx & 8) != 0);

Please use pr_debug() for things like this.

> +static void cb710_mmc_enable_irq(struct cb710_chip *chip, int enable)
> +{
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&chip->irq_lock, flags);
> +	__cb710_mmc_enable_irq(chip, enable);
> +	spin_unlock_irqrestore(&chip->irq_lock, flags);
> +}

This is a fairly useless wrapper. Look over how it is called instead.

> +static int cb710_check(struct cb710_chip *chip, int what)
> +{
> +	uint8_t status1, status2;
> +
> +	/* all this is magic */
> +	BUG_ON(what < 2 || what > 4);
> +

You seem to know what some of the "what" values are. Put some defines
for those.

> +/*	if (flags & MMC_CMD_IS_APP) */
> +	if (reader->app_cmd) {
> +		/* original driver set this bit for MMC/SD application
> +		 * commands. It apparently works without it, but... oh well.
> +		 */
> +		cb_flags |= 0x4000;
> +		reader->app_cmd = 0;
> +	}

ACMD:s are no different from "normal" commands. As you've discovered,
you do not need this code.

> +	if (flags & MMC_RSP_PRESENT) {
> +		/* Windows driver set 01 at bits 4,3 except for
> +		 * MMC_SET_BLOCKLEN. I assume that 00 here means no
> +		 * response is expected.
> +		 */
> +		if (cmd->opcode != MMC_SET_BLOCKLEN)
> +			cb_flags |= CB710_MMC_RSP_PRESENT;
> +		else
> +			cb_flags |= CB710_MMC_RSP_PRESENT_X;

Looking at the opcode is not acceptable, so you need to figure out
what's really going on here. MMC_SET_BLOCKLEN has a common R1 response,
so I don't know why the Windows driver special treated it.

> +		if (flags & MMC_RSP_136)		/* R2 */
> +			cb_flags |= CB710_MMC_RSP_136;
> +		else if (!(flags & MMC_RSP_CRC))	/* R3 */
> +			cb_flags |= CB710_MMC_RSP_NO_CRC;

I don't see a need for the "else".

> +
> +	if (rsp_opcode != ((cmd->flags & MMC_RSP_OPCODE) ? cmd->opcode : 0x3F))
> +		cmd->error = -EILSEQ;

This isn't terribly readable. Something like this would be better:

	if (cmd->flags & MMC_RSP_OPCODE) {
		if (rsp_opcode != cmd->opcode)
			cmd->error = -EILSEQ;
	} else {
		if (rsp_opcode != 0x3F)
			cmd->error = -EILSEQ;
	}

The code generated by the compiler should be the same.

> +	if (!error)	/* TODO: proper counting */
> +		data->bytes_xfered = data->blksz * data->blocks;

You can probably never do proper counting when you don't know how the
hardware behaves.

> +static const struct mmc_host_ops cb710_mmc_host = {
> +	.request = cb710_mmc_request,
> +	.set_ios = cb710_mmc_set_ios,
> +	.get_ro = cb710_mmc_get_ro,
> +	.enable_sdio_irq = NULL,
> +};

NULL is the default so you don't need to specify that.

Rgds
-- 
     -- Pierre Ossman

  Linux kernel, MMC maintainer        http://www.kernel.org
  rdesktop, core developer          http://www.rdesktop.org

  WARNING: This correspondence is being monitored by the
  Swedish government. Make sure your server uses encryption
  for SMTP traffic and consider using PGP for end-to-end
  encryption.

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

* RFC: Driver for CB710/720 memory card reader (MMC part) - v2
  2008-09-20 11:00         ` Pierre Ossman
@ 2008-09-25  6:29           ` Michał Mirosław
  2008-10-04 19:51             ` Pierre Ossman
  0 siblings, 1 reply; 21+ messages in thread
From: Michał Mirosław @ 2008-09-25  6:29 UTC (permalink / raw)
  To: Pierre Ossman; +Cc: linux-kernel, Alex Dubov

Hello,

Thanks for your comments, Pierre - I have applied most of your
suggestions and replied to some below.

Changes in this version:
 - power management functions;
 - IRQ handler modifications: core driver uses RCU, MMC-specific handler
   uses spinlock only in the uncommon case of 'test' interrupt;
 - debugging printks converted to dev_dbg() and friends;
 - code split to files such that MMC specific parts are self-contained

This should be almost ready. mmc-test passes up to a point where it
tries non-sector-size writes to the card - I didn't have time to look
into this further, yet.

On Sat, Sep 20, 2008 at 01:00:06PM +0200, Pierre Ossman wrote:
> On Sat, 13 Sep 2008 01:43:02 +0200
> Michał Mirosław <mirq-linux@rere.qmqm.pl> wrote:
> > +#define RDPORT(t, p) \
> > +	ioread##t(chip->iobase + (p))
[and other such functions]
> This is a pretty bad case of obfuscation. Don't construct macros that
> reference local variables (chip in this case). Also,
> iowrite16_rep(chip->iobase + CB700_FOO, buffer, 12); is quite readable
> as it is. Using standard kernel functions allows your code to be more
> accessible to other kernel hackers.

Sure. This was an attempt at imitating Perl-ish dynamically scoped
package-local variable. ;-)

> > +/* sg-to-PIO buffer */
[and its API]
> Why this complex system? Can't you use the handlers the kernel already
> provides? You also get a lot of special handling with those, e.g.
> highmem.

I looked at linux/lib/scatterlist.c and found nothing really useful. If
there were some preconditions always met for scatterlists - like that block
does not span multiple pages, or at least 16-byte block don't, then most
of this code can go away. It looks complicated, because it implements
a special iterator interface that always returns multiple-of-16-byte blocks
for reading or writing. This makes the PIO loops very simple and fast
(the bounce_buffer case was never triggered by mmc-block in my tests).

> > diff -urN empty/Makefile cb710-pre-20080913/Makefile
> > --- empty/Makefile	1970-01-01 01:00:00.000000000 +0100
> > +++ cb710-pre-20080913/Makefile	2008-09-12 20:56:39.000000000 +0200
> It's easier if you develop against the real kernel tree and send diffs
> for that.

Hmm. For me it was easier to have the driver out-of-tree, as it's
rebuilding went faster and all files are at hand. I will of course
make a proper diff against vanilla kernel when it's ready to go in.
I've put a .tgz at my server to make it easier for those that wan't
to test the driver with other kernel versions.

> > +static void cb710_mmc_enable_irq(struct cb710_chip *chip, int enable)
> > +{
> > +	unsigned long flags;
> > +
> > +	spin_lock_irqsave(&chip->irq_lock, flags);
> > +	__cb710_mmc_enable_irq(chip, enable);
> > +	spin_unlock_irqrestore(&chip->irq_lock, flags);
> > +}
> This is a fairly useless wrapper. Look over how it is called instead.

Can you expand on this? Right now irq handler doesn't call
__cb710_mmc_enable_irq() but modifies registers itself. This will
change eventually.

> > +	if (flags & MMC_RSP_PRESENT) {
> > +		/* Windows driver set 01 at bits 4,3 except for
> > +		 * MMC_SET_BLOCKLEN. I assume that 00 here means no
> > +		 * response is expected.
> > +		 */
> > +		if (cmd->opcode != MMC_SET_BLOCKLEN)
> > +			cb_flags |= CB710_MMC_RSP_PRESENT;
> > +		else
> > +			cb_flags |= CB710_MMC_RSP_PRESENT_X;
> Looking at the opcode is not acceptable, so you need to figure out
> what's really going on here. MMC_SET_BLOCKLEN has a common R1 response,
> so I don't know why the Windows driver special treated it.

I removed this special-case for now and it still works.

> > +	if (rsp_opcode != ((cmd->flags & MMC_RSP_OPCODE) ? cmd->opcode : 0x3F))
> > +		cmd->error = -EILSEQ;
> This isn't terribly readable. Something like this would be better:
> 	if (cmd->flags & MMC_RSP_OPCODE) {
> 		if (rsp_opcode != cmd->opcode)
> 			cmd->error = -EILSEQ;
> 	} else {
> 		if (rsp_opcode != 0x3F)
> 			cmd->error = -EILSEQ;
> 	}

That's a lot of lines. I used another variable to make it clearer.

Thanks again for your comments,
Michał Mirosław


diff -urN empty/cb710.h cb710/cb710.h
--- empty/cb710.h	1970-01-01 01:00:00.000000000 +0100
+++ cb710/cb710.h	2008-09-25 07:07:40.000000000 +0200
@@ -0,0 +1,131 @@
+/*
+ *  cb710/cb710.h
+ *
+ *  Copyright by Michał Mirosław, 2008
+ *
+ * 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.
+ */
+#ifndef LINUX_CB710_DRIVER_H
+#define LINUX_CB710_DRIVER_H
+
+#include <linux/spinlock.h>
+#include <linux/mmc/host.h>
+#include <linux/pci.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+
+#define CB710_DRIVER_NAME "cb710"
+
+struct cb710_slot;
+
+typedef int (*cb710_irq_handler_t)(struct cb710_slot *);
+
+/* per-virtual-slot structure */
+struct cb710_slot {
+	struct platform_device	pdev;
+	void __iomem		*iobase;
+	cb710_irq_handler_t	irq_handler;	/* RCU */
+};
+
+/* per-device structure */
+struct cb710_chip {
+	struct pci_dev		*pdev;
+	void __iomem		*iobase;
+	unsigned		platform_id;
+	atomic_t		slots;		/* RCU */
+	unsigned		slot_mask;
+	struct cb710_slot	slot[0];
+};
+
+/* NOTE: cb710_chip.slots is modified only during device init/exit */
+
+/* cb710_chip.slot_mask values */
+#define CB710_SLOT_MMC		1
+#define CB710_SLOT_MS		2
+#define CB710_SLOT_SM		4
+
+/* slot port accessors - in case it turns out inX() is all that is needed */
+#define CB710_PORT_ACCESSORS(t) \
+static inline void cb710_write_port_##t(struct cb710_slot *slot,	\
+	unsigned port, u##t value)					\
+{									\
+	iowrite##t(value, slot->iobase + port);				\
+}									\
+									\
+static inline u##t cb710_read_port_##t(struct cb710_slot *slot,		\
+	unsigned port)							\
+{									\
+	return ioread##t(slot->iobase + port);				\
+}									\
+									\
+static inline void cb710_modify_port_##t(struct cb710_slot *slot,	\
+	unsigned port, u##t set, u##t clear)				\
+{									\
+	iowrite##t(							\
+		(ioread##t(slot->iobase + port) & ~clear)|set,		\
+		slot->iobase + port);					\
+}
+
+CB710_PORT_ACCESSORS(8)
+CB710_PORT_ACCESSORS(16)
+CB710_PORT_ACCESSORS(32)
+
+void cb710_pci_update_config_reg(struct pci_dev *pdev,
+	int reg, uint32_t and, uint32_t xor);
+
+/* some device struct walking */
+
+static inline struct cb710_slot *cb710_pdev_to_slot(
+	struct platform_device *pdev)
+{
+	return container_of(pdev, struct cb710_slot, pdev);
+}
+
+static inline struct cb710_chip *cb710_slot_to_chip(struct cb710_slot *slot)
+{
+	return dev_get_drvdata(slot->pdev.dev.parent);
+}
+
+static inline struct device *cb710_slot_dev(struct cb710_slot *slot)
+{
+	return &slot->pdev.dev;
+}
+
+static inline struct device *cb710_chip_dev(struct cb710_chip *chip)
+{
+	return &chip->pdev->dev;
+}
+
+/* helper functions */
+
+static inline void cb710_set_irq_handler(struct cb710_slot *slot,
+	cb710_irq_handler_t handler)
+{
+	rcu_assign_pointer(slot->irq_handler, handler);
+	synchronize_rcu();	/* sync to IRQ handler */
+}
+
+/* debugging aids */
+
+#ifdef DEBUG
+void cb710_dump_regs(struct cb710_chip *chip, unsigned dump);
+#else
+#define cb710_dump_regs(c, d) do {} while (0)
+#endif
+
+#define CB710_DUMP_REGS_MMC	0x0F
+#define CB710_DUMP_REGS_MS	0x30
+#define CB710_DUMP_REGS_SM	0xC0
+#define CB710_DUMP_REGS_ALL	0xFF
+#define CB710_DUMP_REGS_MASK	0xFF
+
+#define CB710_DUMP_ACCESS_8	0x100
+#define CB710_DUMP_ACCESS_16	0x200
+#define CB710_DUMP_ACCESS_32	0x400
+#define CB710_DUMP_ACCESS_ALL	0x700
+#define CB710_DUMP_ACCESS_MASK	0x700
+
+#endif /* LINUX_CB710_DRIVER_H */
diff -urN empty/cb710-mmc.h cb710/cb710-mmc.h
--- empty/cb710-mmc.h	1970-01-01 01:00:00.000000000 +0100
+++ cb710/cb710-mmc.h	2008-09-25 05:37:51.000000000 +0200
@@ -0,0 +1,114 @@
+/*
+ *  cb710/cb710-mmc.h
+ *
+ *  Copyright by Michał Mirosław, 2008
+ *
+ * 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.
+ */
+#ifndef LINUX_CB710_MMC_H
+#define LINUX_CB710_MMC_H
+
+#include <linux/spinlock.h>
+#include <linux/mmc/host.h>
+#include <linux/pci.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include "cb710.h"
+
+/* per-MMC-reader structure */
+struct cb710_mmc_reader {
+	struct tasklet_struct finish_req_tasklet;
+	struct mmc_request *mrq;
+	spinlock_t irq_lock;
+	unsigned char last_power_mode;
+#ifdef VERBOSE_DEBUG
+	spinlock_t serialization_lock;
+	unsigned char active_req, active_ios;
+#endif
+};
+
+/* some device struct walking */
+
+static inline struct mmc_host *cb710_slot_to_mmc(struct cb710_slot *slot)
+{
+	return dev_get_drvdata(&slot->pdev.dev);
+}
+
+static inline struct cb710_slot *cb710_mmc_to_slot(struct mmc_host *mmc)
+{
+	struct platform_device *pdev = container_of(mmc_dev(mmc),
+		struct platform_device, dev);
+	return cb710_pdev_to_slot(pdev);
+}
+
+/* registers (this might be all wrong ;) */
+
+#define CB710_MMC_DATA_PORT		0x00
+
+#define CB710_MMC_CONFIG_PORT		0x04
+#define CB710_MMC_CONFIG0_PORT		0x04
+#define CB710_MMC_CONFIG1_PORT		0x05
+#define   CB710_MMC_C1_4BIT_DATA_BUS		0x40
+#define CB710_MMC_CONFIG2_PORT		0x06
+#define   CB710_MMC_C2_READ_PIO_SIZE_MASK	0x0F	/* N-1 */
+#define CB710_MMC_CONFIG3_PORT		0x07
+
+#define CB710_MMC_CONFIGB_PORT		0x08
+
+#define CB710_MMC_IRQ_ENABLE_PORT	0x0C
+#define   CB710_MMC_IE_CISTATUS_MASK		0x00009000
+#define CB710_MMC_IRQ_ENABLE0_PORT	0x0C
+#define CB710_MMC_IRQ_ENABLE1_PORT	0x0D
+#define   CB710_MMC_IE_IRQ_ENABLE		0x80
+#define   CB710_MMC_IE_CARD_INSERTION_STATUS	0x10
+
+#define CB710_MMC_STATUS_PORT		0x10
+#define   CB710_MMC_STATUS_ERROR_EVENTS		0x60FF
+#define CB710_MMC_STATUS0_PORT		0x10
+#define   CB710_MMC_S0_FIFO_UNDERFLOW		0x40
+#define CB710_MMC_STATUS1_PORT		0x11
+#define   CB710_MMC_S1_COMMAND_SENT		0x01
+#define   CB710_MMC_S1_DATA_TRANSFER_DONE	0x02
+#define   CB710_MMC_S1_PIO_TRANSFER_DONE	0x04
+#define   CB710_MMC_S1_CARD_CHANGED		0x10
+#define   CB710_MMC_S1_RESET			0x20
+#define CB710_MMC_STATUS2_PORT		0x12
+#define   CB710_MMC_S2_FIFO_READY		0x01
+#define   CB710_MMC_S2_FIFO_EMPTY		0x02
+#define   CB710_MMC_S2_BUSY_10			0x10
+#define   CB710_MMC_S2_BUSY_20			0x20
+#define CB710_MMC_STATUS3_PORT		0x13
+#define   CB710_MMC_S3_CARD_DETECTED		0x02
+#define   CB710_MMC_S3_WRITE_PROTECTED		0x04
+
+#define CB710_MMC_CMD_TYPE_PORT		0x14
+#define   CB710_MMC_RSP_TYPE_MASK		0x0007
+#define     CB710_MMC_RSP_R1			(0)
+#define     CB710_MMC_RSP_136			(5)
+#define     CB710_MMC_RSP_NO_CRC		(2)
+#define   CB710_MMC_RSP_PRESENT_MASK		0x0018
+#define     CB710_MMC_RSP_NONE			(0 << 3)
+#define     CB710_MMC_RSP_PRESENT		(1 << 3)
+#define     CB710_MMC_RSP_PRESENT_X		(2 << 3)
+#define   CB710_MMC_CMD_TYPE_MASK		0x0060
+#define     CB710_MMC_CMD_BC			(0 << 5)
+#define     CB710_MMC_CMD_BCR			(1 << 5)
+#define     CB710_MMC_CMD_AC			(2 << 5)
+#define     CB710_MMC_CMD_ADTC			(3 << 5)
+#define   CB710_MMC_DATA_READ			0x0080
+#define   CB710_MMC_CMD_CODE_MASK		0x3F00
+#define   CB710_MMC_CMD_CODE_SHIFT		8
+#define   CB710_MMC_IS_APP_CMD			0x4000
+#define   CB710_MMC_RSP_BUSY			0x8000
+
+#define CB710_MMC_CMD_PARAM_PORT	0x18
+#define CB710_MMC_TRANSFER_SIZE_PORT	0x1C
+#define CB710_MMC_RESPONSE0_PORT	0x20
+#define CB710_MMC_RESPONSE1_PORT	0x24
+#define CB710_MMC_RESPONSE2_PORT	0x28
+#define CB710_MMC_RESPONSE3_PORT	0x2C
+
+#endif /* LINUX_CB710_MMC_H */
diff -urN empty/cb710-sg.h cb710/cb710-sg.h
--- empty/cb710-sg.h	1970-01-01 01:00:00.000000000 +0100
+++ cb710/cb710-sg.h	2008-09-21 16:47:47.000000000 +0200
@@ -0,0 +1,49 @@
+/*
+ *  cb710/cb710-sg.h
+ *
+ *  Copyright by Michał Mirosław, 2008
+ *
+ * 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.
+ */
+#ifndef LINUX_CB710_SG_H
+#define LINUX_CB710_SG_H
+
+#include <linux/scatterlist.h>		/* struct scatterlist */
+#include <linux/mm_types.h>		/* struct page */
+
+#define CB710_SG_BUFFER_BLOCK	16	/* power of two */
+#define CB710_SG_BUFFER_MASK	(~(CB710_SG_BUFFER_BLOCK - 1))
+
+struct cb710_sg_chain {
+	uint8_t bounce_buffer[CB710_SG_BUFFER_BLOCK];
+	struct scatterlist *sg;
+	unsigned int sg_num;
+	struct page *page;
+	void *mapped_page;
+	size_t cur_offset;
+	size_t need_advance;
+	unsigned page_no;
+	unsigned page_offset;
+	unsigned page_left;
+	unsigned need_bounce:1;
+	unsigned use_bounce:1;
+};
+
+void cb710_sg_init(struct cb710_sg_chain *buf,
+	struct scatterlist *sg, size_t nelem);
+int cb710_sg_next_buf(struct cb710_sg_chain *buf,
+	void **dataptr, size_t *len, int to_sg);
+void cb710_sg_abort(struct cb710_sg_chain *buf, int to_sg);
+
+#define cb710_sg_read_next(b, d, l) \
+	cb710_sg_next_buf((b), (d), (l), 0)
+#define cb710_sg_write_next(b, d, l) \
+	cb710_sg_next_buf((b), (d), (l), 1)
+#define cb710_sg_abort_read(b) \
+	cb710_sg_abort((b), 0)
+#define cb710_sg_abort_write(b) \
+	cb710_sg_abort((b), 1)
+
+#endif /* LINUX_CB710_SG_H */
diff -urN empty/core.c cb710/core.c
--- empty/core.c	1970-01-01 01:00:00.000000000 +0100
+++ cb710/core.c	2008-09-25 07:23:51.000000000 +0200
@@ -0,0 +1,300 @@
+/*
+ *  cb710/core.c
+ *
+ *  Copyright by Michał Mirosław, 2008
+ *
+ * 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.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <linux/idr.h>
+#include <linux/rcupdate.h>
+#include "cb710.h"
+
+static DEFINE_IDR(cb710_idr);
+static DEFINE_SPINLOCK(cb710_idr_lock);
+
+void cb710_pci_update_config_reg(struct pci_dev *pdev,
+	int reg, uint32_t mask, uint32_t xor)
+{
+	u32 rval;
+
+	pci_read_config_dword(pdev, reg, &rval);
+	rval = (rval & mask) ^ xor;
+	pci_write_config_dword(pdev, reg, rval);
+}
+EXPORT_SYMBOL_GPL(cb710_pci_update_config_reg);
+
+/* Some magic writes based on Windows driver init code */
+static int __devinit cb710_pci_configure(struct pci_dev *pdev)
+{
+	unsigned int devfn = PCI_DEVFN(PCI_SLOT(pdev->devfn), 0);
+	struct pci_dev *pdev0 = pci_get_slot(pdev->bus, devfn);
+	u32 val;
+
+	cb710_pci_update_config_reg(pdev, 0x48,
+		~0x000000FF, 0x0000003F);
+
+	pci_read_config_dword(pdev, 0x48, &val);
+	if (val & 0x80000000)
+		return 0;
+
+	if (!pdev0)
+		return -ENODEV;
+
+	if (pdev0->vendor == PCI_VENDOR_ID_ENE
+	    && pdev0->device == PCI_DEVICE_ID_ENE_720) {
+		cb710_pci_update_config_reg(pdev0, 0x8C,
+			~0x00F00000, 0x00100000);
+		cb710_pci_update_config_reg(pdev0, 0xB0,
+			~0x08000000, 0x08000000);
+	}
+
+	cb710_pci_update_config_reg(pdev0, 0x8C,
+		~0x00000F00, 0x00000200);
+	cb710_pci_update_config_reg(pdev0, 0x90,
+		~0x00060000, 0x00040000);
+
+	pci_dev_put(pdev0);
+
+	return 0;
+}
+
+static irqreturn_t cb710_irq_handler(int irq, void *data)
+{
+	struct cb710_chip *chip = data;
+	struct cb710_slot *slot = &chip->slot[0];
+	cb710_irq_handler_t handler_func;
+	irqreturn_t handled = IRQ_NONE;
+	int nr;
+
+	rcu_read_lock();
+	nr = atomic_read(&chip->slots);
+
+	while (nr--) {
+		handler_func = rcu_dereference(slot->irq_handler);
+		if (handler_func && handler_func(slot))
+			handled = IRQ_HANDLED;
+		++slot;
+	}
+
+	rcu_read_unlock();
+
+	return handled;
+}
+
+static int __devinit cb710_register_slot(struct cb710_chip *chip,
+	unsigned slot_mask, unsigned io_offset, const char *name)
+{
+	int nr = atomic_read(&chip->slots);
+	struct cb710_slot *slot = &chip->slot[nr];
+	int err;
+
+	dev_dbg(cb710_chip_dev(chip),
+		"register: %s.%d; slot %d; mask %d; IO offset: 0x%02X\n",
+		name, chip->platform_id, nr, slot_mask, io_offset);
+
+	slot->iobase = chip->iobase + io_offset;
+	slot->pdev.name = name;
+	slot->pdev.id = chip->platform_id;
+	slot->pdev.dev.parent = &chip->pdev->dev;
+	err = platform_device_register(&slot->pdev);
+
+	if (err)
+		return err;
+
+	chip->slot_mask |= slot_mask;
+	atomic_inc(&chip->slots);
+
+	/* XXX: can this be just left out/replaced by a memory barrier? */
+	synchronize_rcu();
+
+	return 0;
+}
+
+static void cb710_unregister_slot(struct cb710_chip *chip,
+	unsigned slot_mask)
+{
+	int nr;
+
+	if (!(chip->slot_mask & slot_mask))
+		return;
+
+	nr = atomic_sub_return(1, &chip->slots);
+	chip->slot_mask &= ~slot_mask;
+
+	synchronize_rcu();
+
+	platform_device_unregister(&chip->slot[nr].pdev);
+}
+
+#ifdef CONFIG_PM
+
+static int cb710_suspend(struct pci_dev *pdev, pm_message_t state)
+{
+	pci_save_state(pdev);
+	pci_disable_device(pdev);
+	if (state.event & PM_EVENT_SLEEP)
+		pci_set_power_state(pdev, PCI_D3cold);
+	return 0;
+}
+
+static int cb710_resume(struct pci_dev *pdev)
+{
+	pci_set_power_state(pdev, PCI_D0);
+	pci_restore_state(pdev);
+	return pcim_enable_device(pdev);
+}
+
+#endif /* CONFIG_PM */
+
+static int __devinit cb710_probe(struct pci_dev *pdev,
+	const struct pci_device_id *ent)
+{
+	struct cb710_chip *chip;
+	unsigned long flags;
+	u32 val;
+	int err;
+	int n = 0;
+
+	err = cb710_pci_configure(pdev);
+	if (err)
+		return err;
+
+	/* this is actually magic... */
+	pci_read_config_dword(pdev, 0x48, &val);
+	if (!(val & 0x80000000)) {
+		pci_write_config_dword(pdev, 0x48, val|0x71000000);
+		pci_read_config_dword(pdev, 0x48, &val);
+	}
+
+	dev_dbg(&pdev->dev, "PCI config[0x48] = 0x%08X\n", val);
+	if (!(val & 0x70000000))
+		return -ENODEV;
+	val = (val >> 28) & 7;
+	if (val & CB710_SLOT_MMC)
+		++n;
+	if (val & CB710_SLOT_MS)
+		++n;
+	if (val & CB710_SLOT_SM)
+		++n;
+
+	chip = devm_kzalloc(&pdev->dev,
+		sizeof(*chip) + n * sizeof(*chip->slot), GFP_KERNEL);
+	if (!chip)
+		return -ENOMEM;
+
+	err = pcim_enable_device(pdev);
+	if (err)
+		return err;
+
+	err = pcim_iomap_regions(pdev, 0x0001, CB710_DRIVER_NAME);
+	if (err)
+		return err;
+
+	chip->pdev = pdev;
+	chip->iobase = pcim_iomap_table(pdev)[0];
+
+	pci_set_drvdata(pdev, chip);
+
+	err = devm_request_irq(&pdev->dev, pdev->irq,
+		cb710_irq_handler, IRQF_SHARED, CB710_DRIVER_NAME, chip);
+	if (err)
+		return err;
+
+	if (!idr_pre_get(&cb710_idr, GFP_KERNEL))
+		return -ENOMEM;
+
+	spin_lock_irqsave(&cb710_idr_lock, flags);
+	err = idr_get_new(&cb710_idr, chip, &chip->platform_id);
+	spin_unlock_irqrestore(&cb710_idr_lock, flags);
+	if (err)
+		return err;
+
+	dev_info(&pdev->dev, "id %d, IO 0x%p, IRQ %d\n",
+		chip->platform_id, chip->iobase, pdev->irq);
+
+	if (val & CB710_SLOT_MMC) {	/* MMC/SD slot */
+		err = cb710_register_slot(chip,
+			CB710_SLOT_MMC, 0x00, "cb710-mmc");
+		if (err)
+			return err;
+	}
+
+	if (val & CB710_SLOT_MS) {	/* MemoryStick slot */
+		err = cb710_register_slot(chip,
+			CB710_SLOT_MS, 0x40, "cb710-ms");
+		if (err)
+			goto unreg_mmc;
+	}
+
+	if (val & CB710_SLOT_SM) {	/* SmartMedia slot */
+		err = cb710_register_slot(chip,
+			CB710_SLOT_SM, 0x60, "cb710-sm");
+		if (err)
+			goto unreg_ms;
+	}
+
+	return 0;
+unreg_ms:
+	cb710_unregister_slot(chip, CB710_SLOT_MS);
+unreg_mmc:
+	cb710_unregister_slot(chip, CB710_SLOT_MMC);
+	return err;
+}
+
+static void __devexit cb710_remove_one(struct pci_dev *pdev)
+{
+	struct cb710_chip *chip = pci_get_drvdata(pdev);
+	unsigned long flags;
+
+	cb710_unregister_slot(chip, CB710_SLOT_SM);
+	cb710_unregister_slot(chip, CB710_SLOT_MS);
+	cb710_unregister_slot(chip, CB710_SLOT_MMC);
+
+	spin_lock_irqsave(&cb710_idr_lock, flags);
+	idr_remove(&cb710_idr, chip->platform_id);
+	spin_unlock_irqrestore(&cb710_idr_lock, flags);
+}
+
+/* TODO: move to pci_ids.h before merging upstream */
+#define PCI_DEVICE_ID_ENE_710_FLASH	0x0510
+
+static const struct pci_device_id cb710_pci_tbl[] = {
+	{ PCI_VENDOR_ID_ENE, PCI_DEVICE_ID_ENE_710_FLASH,
+		PCI_ANY_ID, PCI_ANY_ID, },
+	{ 0, }
+};
+
+static struct pci_driver cb710_driver = {
+	.name = CB710_DRIVER_NAME,
+	.id_table = cb710_pci_tbl,
+	.probe = cb710_probe,
+	.remove = __devexit_p(cb710_remove_one),
+#ifdef CONFIG_PM
+	.suspend = cb710_suspend,
+	.resume = cb710_resume,
+#endif
+};
+
+static int __init cb710_init_module(void)
+{
+	return pci_register_driver(&cb710_driver);
+}
+
+static void __exit cb710_cleanup_module(void)
+{
+	pci_unregister_driver(&cb710_driver);
+}
+
+module_init(cb710_init_module);
+module_exit(cb710_cleanup_module);
+
+MODULE_AUTHOR("Michał Mirosław <mirq-linux@rere.qmqm.pl>");
+MODULE_DESCRIPTION("ENE CB710 memory card reader driver");
+MODULE_LICENSE("GPL");
+MODULE_DEVICE_TABLE(pci, cb710_pci_tbl);
diff -urN empty/debug.c cb710/debug.c
--- empty/debug.c	1970-01-01 01:00:00.000000000 +0100
+++ cb710/debug.c	2008-09-25 05:51:31.000000000 +0200
@@ -0,0 +1,120 @@
+/*
+ *  cb710/debug.c
+ *
+ *  Copyright by Michał Mirosław, 2008
+ *
+ * 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.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include "cb710.h"
+
+#define CB710_REG_COUNT		0x80
+
+static const u16 allow[CB710_REG_COUNT/16] = {
+	0xFFF0, 0xFFFF, 0xFFFF, 0xFFFF,
+	0xFFF0, 0xFFFF, 0xFFFF, 0xFFFF,
+};
+static const char *const prefix[ARRAY_SIZE(allow)] = {
+	"MMC", "MMC", "MMC", "MMC",
+	"MS?", "MS?", "SM?", "SM?"
+};
+
+static inline int allow_reg_read(unsigned block, unsigned offset, unsigned bits)
+{
+	unsigned mask = (1 << bits/8) - 1;
+	offset *= bits/8;
+	return ((allow[block] >> offset) & mask) == mask;
+}
+
+#define CB710_READ_REGS_TEMPLATE(t)					\
+static void cb710_read_regs_##t(void __iomem *iobase,			\
+	u##t *reg, unsigned select)					\
+{									\
+	unsigned i, j;							\
+									\
+	for (i = 0; i < ARRAY_SIZE(allow); ++i, reg += 16/(t/8)) {	\
+		if (!select & (1 << i))					\
+			continue;					\
+									\
+		for (j = 0; j < 0x10/(t/8); ++j) {			\
+			if (!allow_reg_read(i, j, t))			\
+				continue;				\
+			reg[j] = ioread##t(iobase			\
+				+ (i << 4) + (j * (t/8)));		\
+		}							\
+	}								\
+}
+
+static const char cb710_regf_8[] = "%02X";
+static const char cb710_regf_16[] = "%04X";
+static const char cb710_regf_32[] = "%08X";
+static const char cb710_xes[] = "xxxxxxxx";
+
+#define CB710_DUMP_REGS_TEMPLATE(t)					\
+static void cb710_dump_regs_##t(struct device *dev,			\
+	const u##t *reg, unsigned select)				\
+{									\
+	const char *const xp = &cb710_xes[8 - t/4];			\
+	const char *const format = cb710_regf_##t;			\
+									\
+	char msg[100], *p;						\
+	unsigned i, j;							\
+									\
+	for (i = 0; i < ARRAY_SIZE(allow); ++i, reg += 16/(t/8)) {	\
+		if (!(select & (1 << i)))				\
+			continue;					\
+		p = msg;						\
+		for (j = 0; j < 0x10/(t/8); ++j) {			\
+			*p++ = ' ';					\
+			if (j == 8/(t/8))				\
+				*p++ = ' ';				\
+			if (allow_reg_read(i, j, t))			\
+				p += sprintf(p, format, reg[j]);	\
+			else						\
+				p += sprintf(p, "%s", xp);		\
+		}							\
+		dev_dbg(dev, "%s 0x%02X %s\n", prefix[i], i << 4, msg);	\
+	}								\
+}
+
+#define CB710_READ_AND_DUMP_REGS_TEMPLATE(t)				\
+static void cb710_read_and_dump_regs_##t(struct cb710_chip *chip,	\
+	unsigned select)						\
+{									\
+	u##t regs[CB710_REG_COUNT/sizeof(u##t)];			\
+									\
+	memset(&regs, 0, sizeof(regs));					\
+	cb710_read_regs_##t(chip->iobase, regs, select);		\
+	cb710_dump_regs_##t(cb710_chip_dev(chip), regs, select);	\
+}
+
+#define CB710_REG_ACCESS_TEMPLATES(t)		\
+  CB710_READ_REGS_TEMPLATE(t)			\
+  CB710_DUMP_REGS_TEMPLATE(t)			\
+  CB710_READ_AND_DUMP_REGS_TEMPLATE(t)
+
+CB710_REG_ACCESS_TEMPLATES(8)
+CB710_REG_ACCESS_TEMPLATES(16)
+CB710_REG_ACCESS_TEMPLATES(32)
+
+void cb710_dump_regs(struct cb710_chip *chip, unsigned select)
+{
+	if (!(select & CB710_DUMP_REGS_MASK))
+		select = CB710_DUMP_REGS_ALL;
+	if (!(select & CB710_DUMP_ACCESS_MASK))
+		select |= CB710_DUMP_ACCESS_8;
+
+	if (select & CB710_DUMP_ACCESS_32)
+		cb710_read_and_dump_regs_32(chip, select);
+	if (select & CB710_DUMP_ACCESS_16)
+		cb710_read_and_dump_regs_16(chip, select);
+	if (select & CB710_DUMP_ACCESS_8)
+		cb710_read_and_dump_regs_8(chip, select);
+}
+EXPORT_SYMBOL_GPL(cb710_dump_regs);
+
diff -urN empty/Makefile cb710/Makefile
--- empty/Makefile	1970-01-01 01:00:00.000000000 +0100
+++ cb710/Makefile	2008-09-25 07:02:28.000000000 +0200
@@ -0,0 +1,29 @@
+ifeq ($(KERNELRELEASE),)
+
+#KDIR	:= /usr/src/linux
+KDIR	:= /usr/src/jaja/build/rechot
+PWD     := $(shell pwd)
+
+.PHONY: module install clean
+module:
+	$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules
+
+install:
+	$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules_install
+
+clean:
+	$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) clean
+	rm -f modules.order
+
+else	# kbuild part
+
+obj-m		:= cb710.o cb710-mmc.o
+
+cb710-y		:= core.o sgbuf.o
+cb710-mmc-y	:= mmc.o
+
+# debugging
+EXTRA_CFLAGS	:= -DDEBUG
+cb710-y		+= debug.o
+
+endif
diff -urN empty/mmc.c cb710/mmc.c
--- empty/mmc.c	1970-01-01 01:00:00.000000000 +0100
+++ cb710/mmc.c	2008-09-25 07:12:36.000000000 +0200
@@ -0,0 +1,822 @@
+/*
+ *  cb710/mmc.c
+ *
+ *  Copyright by Michał Mirosław, 2008
+ *
+ * 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.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include "cb710-mmc.h"
+#include "cb710-sg.h"
+
+static const u8 cb710_clock_divider_log2[8] = {
+/*	1, 2, 4, 8, 16, 32, 128, 512 */
+	0, 1, 2, 3,  4,  5,   7,   9
+};
+#define CB710_MAX_DIVIDER_IDX	\
+	(ARRAY_SIZE(cb710_clock_divider_log2) - 1)
+
+static const u8 cb710_src_freq_mhz[16] = {
+	33, 10, 20, 25, 30, 35, 40, 45,
+	50, 55, 60, 65, 70, 75, 80, 85
+};
+
+#ifdef VERBOSE_DEBUG
+static void verify_serialization(struct mmc_host *mmc,
+	unsigned char *counter, int inc)
+{
+	struct cb710_slot *slot = cb710_mmc_to_slot(mmc);
+	struct cb710_mmc_reader *reader = mmc_priv(mmc);
+	unsigned long flags;
+	int req, ios, cur;
+
+	spin_lock_irqsave(&reader->serialization_lock, flags);
+
+	if (inc)
+		cur = ++*counter;
+	else
+		cur = --*counter;
+	req = reader->active_req;
+	ios = reader->active_ios;
+
+	spin_unlock_irqrestore(&reader->serialization_lock, flags);
+
+	dev_info(cb710_slot_dev(slot),
+		"%s driver; counters now: ios=%d req=%d\n",
+		inc ? "entering" : "leaving", ios, req);
+	WARN_ON(cur > 1);
+}
+#else
+#define verify_serialization(h, c, i)
+#endif
+
+static void cb710_mmc_set_clock(struct mmc_host *mmc, int hz)
+{
+	struct cb710_slot *slot = cb710_mmc_to_slot(mmc);
+	struct pci_dev *pdev = cb710_slot_to_chip(slot)->pdev;
+	u32 src_freq_idx;
+	u32 divider_idx;
+	int src_hz;
+
+	/* this is magic, unverifiable for me, unless I get
+	 * MMC card with cables connected to bus signals */
+	pci_read_config_dword(pdev, 0x48, &src_freq_idx);
+	src_freq_idx = (src_freq_idx >> 16) & 0xF;
+	src_hz = cb710_src_freq_mhz[src_freq_idx] * 1000000;
+
+	for (divider_idx = 0; divider_idx < CB710_MAX_DIVIDER_IDX; ++divider_idx) {
+		if (hz >= src_hz >> cb710_clock_divider_log2[divider_idx])
+			break;
+	}
+
+	if (src_freq_idx)
+		divider_idx |= 0x8;
+
+	cb710_pci_update_config_reg(pdev, 0x40, ~0xF0000000, divider_idx << 28);
+
+	dev_dbg(cb710_slot_dev(slot),
+		"clock set to %d Hz, wanted %d Hz; flag = %d\n",
+		src_hz >> cb710_clock_divider_log2[divider_idx & 7],
+		hz, (divider_idx & 8) != 0);
+}
+
+static void __cb710_mmc_enable_irq(struct cb710_slot *slot, int enable)
+{
+	if (enable) {
+		/* look like interrupt is fired whenever
+		 * WORD[0x0C] & WORD[0x10] != 0;
+		 * let's verify it...
+		 *** bit 7 port 0x0D seems to be global interrupt enable
+		 */
+		cb710_write_port_8(slot, CB710_MMC_IRQ_ENABLE0_PORT, ~0x40);
+		cb710_write_port_8(slot, CB710_MMC_IRQ_ENABLE1_PORT,
+			CB710_MMC_IE_IRQ_ENABLE|CB710_MMC_IE_CARD_INSERTION_STATUS);
+	} else {
+		cb710_write_port_8(slot, CB710_MMC_IRQ_ENABLE0_PORT, 0);
+		cb710_write_port_8(slot, CB710_MMC_IRQ_ENABLE1_PORT, 0);
+	}
+}
+
+static void cb710_mmc_enable_irq(struct cb710_slot *slot, int enable)
+{
+	struct cb710_mmc_reader *reader = mmc_priv(cb710_slot_to_mmc(slot));
+	unsigned long flags;
+
+	spin_lock_irqsave(&reader->irq_lock, flags);
+	__cb710_mmc_enable_irq(slot, enable);
+	spin_unlock_irqrestore(&reader->irq_lock, flags);
+}
+
+static void cb710_mmc_reset_events(struct cb710_slot *slot)
+{
+	cb710_write_port_8(slot, CB710_MMC_STATUS0_PORT, 0xFF);
+	cb710_write_port_8(slot, CB710_MMC_STATUS1_PORT, 0xFF);
+	cb710_write_port_8(slot, CB710_MMC_STATUS2_PORT, 0xFF);
+}
+
+static int cb710_mmc_is_card_inserted(struct cb710_slot *slot)
+{
+	return cb710_read_port_8(slot, CB710_MMC_STATUS3_PORT)
+		& CB710_MMC_S3_CARD_DETECTED;
+}
+
+static void cb710_mmc_enable_4bit_data(struct cb710_slot *slot, int enable)
+{
+	dev_dbg(cb710_slot_dev(slot), "configuring %d-data-line%s mode\n",
+		enable ? 4 : 1, enable ? "s" : "");
+	if (enable)
+		cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT,
+			CB710_MMC_C1_4BIT_DATA_BUS, 0);
+	else
+		cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT,
+			0, CB710_MMC_C1_4BIT_DATA_BUS);
+}
+
+static int cb710_check_event(struct cb710_slot *slot, u8 what)
+{
+	u16 status;
+
+	status = cb710_read_port_16(slot, CB710_MMC_STATUS_PORT);
+
+	if (status & CB710_MMC_S0_FIFO_UNDERFLOW) {
+		/* it is just a guess, so log it */
+		dev_dbg(cb710_slot_dev(slot),
+			"CHECK : ignoring bit 6 in status %04X\n", status);
+		cb710_write_port_8(slot, CB710_MMC_STATUS0_PORT,
+			CB710_MMC_S0_FIFO_UNDERFLOW);
+		status &= ~CB710_MMC_S0_FIFO_UNDERFLOW;
+	}
+
+	if (status & CB710_MMC_STATUS_ERROR_EVENTS) {
+		dev_dbg(cb710_slot_dev(slot),
+			"CHECK : returning EIO on status %04X\n", status);
+		cb710_write_port_8(slot, CB710_MMC_STATUS0_PORT, status & 0xFF);
+		cb710_write_port_8(slot, CB710_MMC_STATUS1_PORT,
+			CB710_MMC_S1_RESET);
+		return -EIO;
+	}
+
+	/* 'what' is a bit in MMC_STATUS1 */
+	if ((status >> 8) & what) {
+		cb710_write_port_8(slot, CB710_MMC_STATUS1_PORT, what);
+		return 1;
+	}
+
+	return 0;
+}
+
+static int cb710_wait_for_event(struct cb710_slot *slot, u8 what)
+{
+	int err = 0;
+	unsigned limit = 2000000;	/* FIXME: real timeout */
+
+#ifdef DEBUG
+	u32 e, x;
+	e = cb710_read_port_32(slot, CB710_MMC_STATUS_PORT);
+#endif
+
+	while (!(err = cb710_check_event(slot, what))) {
+		if (!--limit) {
+			struct cb710_chip *chip = cb710_slot_to_chip(slot);
+			cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+			err = -ETIMEDOUT;
+			break;
+		}
+		udelay(1);
+	}
+
+#ifdef DEBUG
+	x = cb710_read_port_32(slot, CB710_MMC_STATUS_PORT);
+
+	limit = 2000000 - limit;
+	if (limit > 100)
+		dev_dbg(cb710_slot_dev(slot),
+			"WAIT10: waited %d loops, what %d, entry val %08X, exit val %08X\n",
+			limit, what, e, x);
+#endif
+	return err < 0 ? err : 0;
+}
+
+
+static int cb710_wait_while_busy(struct cb710_slot *slot, uint8_t mask)
+{
+	unsigned limit = 500000;	/* FIXME: real timeout */
+	int err = 0;
+
+#ifdef DEBUG
+	u32 e, x;
+	e = cb710_read_port_32(slot, CB710_MMC_STATUS_PORT);
+#endif
+
+	while (cb710_read_port_8(slot, CB710_MMC_STATUS2_PORT) & mask) {
+		if (!--limit) {
+			struct cb710_chip *chip = cb710_slot_to_chip(slot);
+			cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+			err = -ETIMEDOUT;
+			break;
+		}
+		udelay(1);
+	}
+
+#ifdef DEBUG
+	x = cb710_read_port_32(slot, CB710_MMC_STATUS_PORT);
+
+	limit = 500000 - limit;
+	if (limit > 100)
+		dev_dbg(cb710_slot_dev(slot),
+			"WAIT12: waited %d loops, mask %02X, entry val %08X, exit val %08X\n",
+			limit, mask, e, x);
+#endif
+	return 0;
+}
+
+static void cb710_mmc_set_transfer_size(struct cb710_slot *slot,
+	size_t count, size_t blocksize)
+{
+	cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
+	cb710_write_port_32(slot, CB710_MMC_TRANSFER_SIZE_PORT,
+		((count - 1) << 16)|(blocksize - 1));
+
+	dev_vdbg(cb710_slot_dev(slot), "set up for %d block%s of %d bytes\n",
+		count, count == 1 ? "" : "s", blocksize);
+}
+
+static void cb710_mmc_fifo_hack(struct cb710_slot *slot)
+{
+	/* without this, received data is prepended with 8-bytes of zeroes */
+	u32 r1, r2;
+	int ok = 0;
+
+	r1 = cb710_read_port_32(slot, CB710_MMC_DATA_PORT);
+	r2 = cb710_read_port_32(slot, CB710_MMC_DATA_PORT);
+	if (cb710_read_port_8(slot, CB710_MMC_STATUS0_PORT)
+	    & CB710_MMC_S0_FIFO_UNDERFLOW) {
+		cb710_write_port_8(slot, CB710_MMC_STATUS0_PORT,
+			CB710_MMC_S0_FIFO_UNDERFLOW);
+		ok = 1;
+	}
+
+	dev_dbg(cb710_slot_dev(slot),
+		"FIFO-read-hack: expected STATUS0 bit was %s\n",
+		ok ? "set." : "NOT SET!");
+	dev_dbg(cb710_slot_dev(slot),
+		"FIFO-read-hack: dwords ignored: %08X %08X - %s\n",
+		r1, r2, (r1|r2) ? "BAD (NOT ZERO)!" : "ok");
+}
+
+static int cb710_mmc_receive(struct cb710_slot *slot, struct mmc_data *data)
+{
+	struct cb710_sg_chain sgc;
+	uint32_t *databuf;
+	size_t len;
+
+	cb710_sg_init(&sgc, data->sg, data->sg_len);
+
+	if (!cb710_sg_write_next(&sgc, (void **)&databuf, &len))
+		return 0;
+
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG2_PORT,
+		15, CB710_MMC_C2_READ_PIO_SIZE_MASK);
+
+	cb710_mmc_fifo_hack(slot);
+
+	while (len >= 16) {
+		if (!(cb710_read_port_8(slot, CB710_MMC_STATUS2_PORT)
+		    & CB710_MMC_S2_FIFO_READY)) {
+			int err = cb710_wait_for_event(slot,
+				CB710_MMC_S1_PIO_TRANSFER_DONE);
+			if (err) {
+				cb710_sg_abort_write(&sgc);
+				return err;
+			}
+		}
+		ioread32_rep(slot->iobase + CB710_MMC_DATA_PORT, databuf, 4);
+
+		len -= 16;
+		databuf += 4;
+
+		if (!len && !cb710_sg_write_next(&sgc,
+				(void **)&databuf, &len))
+			return 0;
+	}
+
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG2_PORT,
+		len - 1, CB710_MMC_C2_READ_PIO_SIZE_MASK);
+
+	if (!(cb710_read_port_8(slot, CB710_MMC_STATUS2_PORT)
+	    & CB710_MMC_S2_FIFO_READY)) {
+		int err = cb710_wait_for_event(slot,
+			CB710_MMC_S1_PIO_TRANSFER_DONE);
+		if (err) {
+			cb710_sg_abort_write(&sgc);
+			return err;
+		}
+	}
+
+	len = (len >= 8) ? 4 : 2;
+	ioread32_rep(slot->iobase + CB710_MMC_DATA_PORT, databuf, len);
+
+	if (cb710_sg_write_next(&sgc, (void **)&databuf, &len))
+		BUG();
+
+	return 0;
+}
+
+static int cb710_mmc_send(struct cb710_slot *slot, struct mmc_data *data)
+{
+	struct cb710_sg_chain sgc;
+	const uint32_t *databuf;
+	size_t datalen;
+
+	cb710_sg_init(&sgc, data->sg, data->sg_len);
+
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG2_PORT,
+		0, CB710_MMC_C2_READ_PIO_SIZE_MASK);
+
+	while (cb710_sg_read_next(&sgc, (void **)&databuf, &datalen)) {
+		datalen = (datalen + 15) >> 4;
+		do {
+			if (!(cb710_read_port_8(slot, CB710_MMC_STATUS2_PORT)
+			    & CB710_MMC_S2_FIFO_EMPTY)) {
+				int err = cb710_wait_for_event(slot,
+					CB710_MMC_S1_PIO_TRANSFER_DONE);
+				if (err) {
+					cb710_sg_abort_read(&sgc);
+					return err;
+				}
+			}
+			iowrite32_rep(slot->iobase + CB710_MMC_DATA_PORT,
+				databuf, 4);
+			databuf += 4;
+		} while (--datalen);
+	}
+
+	return 0;
+}
+
+static u16 cb710_encode_cmd_flags(struct cb710_mmc_reader *reader,
+	struct mmc_command *cmd)
+{
+	unsigned int flags = cmd->flags;
+	u16 cb_flags = 0;
+
+	/* Windows driver returned 0 for commands for which no response
+	 * is expected. It happened that there were only two such commands
+	 * used: MMC_GO_IDLE_STATE and MMC_GO_INACTIVE_STATE so it might
+	 * as well be a bug in that driver.
+	 */
+
+	switch (flags & MMC_CMD_MASK) {
+	case MMC_CMD_AC:	cb_flags = CB710_MMC_CMD_AC;	break;
+	case MMC_CMD_ADTC:	cb_flags = CB710_MMC_CMD_ADTC;	break;
+	case MMC_CMD_BC:	cb_flags = CB710_MMC_CMD_BC;	break;
+	case MMC_CMD_BCR:	cb_flags = CB710_MMC_CMD_BCR;	break;
+	}
+
+	if (flags & MMC_RSP_BUSY)
+		cb_flags |= CB710_MMC_RSP_BUSY;
+
+	cb_flags |= cmd->opcode << CB710_MMC_CMD_CODE_SHIFT;
+
+#if 0
+	/* original driver set bit 14 for MMC/SD application
+	 * commands. There's no difference 'on the wire' and
+	 * it apparently works without it anyway.
+	 */
+	if (flags & MMC_CMD_IS_APPCMD)
+		cb_flags |= 0x4000;
+#endif
+
+	if (cmd->data && (cmd->data->flags & MMC_DATA_READ))
+		cb_flags |= CB710_MMC_DATA_READ;
+
+	if (flags & MMC_RSP_PRESENT) {
+		/* Windows driver set 01 at bits 4,3 except for
+		 * MMC_SET_BLOCKLEN where it set 10. Maybe the
+		 * hardware can do something special about this
+		 * command? The original driver looks buggy/incomplete
+		 * anyway so we ignore this for now.
+		 *
+		 * I assume that 00 here means no response is expected.
+		 */
+		cb_flags |= CB710_MMC_RSP_PRESENT;
+
+		if (flags & MMC_RSP_136)
+			cb_flags |= CB710_MMC_RSP_136;
+		if (!(flags & MMC_RSP_CRC))
+			cb_flags |= CB710_MMC_RSP_NO_CRC;
+	}
+
+	return cb_flags;
+}
+
+static void cb710_receive_response(struct cb710_slot *slot,
+	struct mmc_command *cmd)
+{
+	unsigned rsp_opcode, wanted_opcode;
+
+	/* Looks like final byte with CRC is always stripped (like SDHCI) */
+	if (cmd->flags & MMC_RSP_136) {
+		u32 resp[4];
+
+		resp[0] = cb710_read_port_32(slot, CB710_MMC_RESPONSE3_PORT);
+		resp[1] = cb710_read_port_32(slot, CB710_MMC_RESPONSE2_PORT);
+		resp[2] = cb710_read_port_32(slot, CB710_MMC_RESPONSE1_PORT);
+		resp[3] = cb710_read_port_32(slot, CB710_MMC_RESPONSE0_PORT);
+		rsp_opcode = resp[0] >> 24;
+
+		cmd->resp[0] = (resp[0] << 8)|(resp[1] >> 24);
+		cmd->resp[1] = (resp[1] << 8)|(resp[2] >> 24);
+		cmd->resp[2] = (resp[2] << 8)|(resp[3] >> 24);
+		cmd->resp[3] = (resp[3] << 8);
+	} else {
+		rsp_opcode = cb710_read_port_32(slot, CB710_MMC_RESPONSE1_PORT) & 0x3F;
+		cmd->resp[0] = cb710_read_port_32(slot, CB710_MMC_RESPONSE0_PORT);
+	}
+
+	wanted_opcode = (cmd->flags & MMC_RSP_OPCODE) ? cmd->opcode : 0x3F;
+	if (rsp_opcode != wanted_opcode)
+		cmd->error = -EILSEQ;
+}
+
+static int cb710_mmc_transfer_data(struct cb710_slot *slot,
+	struct mmc_data *data)
+{
+	int error, to;
+
+	if (data->flags & MMC_DATA_READ)
+		error = cb710_mmc_receive(slot, data);
+	else
+		error = cb710_mmc_send(slot, data);
+
+	to = cb710_wait_for_event(slot, CB710_MMC_S1_DATA_TRANSFER_DONE);
+	if (!error)
+		error = to;
+
+	if (!error)	/* TODO: proper counting */
+		data->bytes_xfered = data->blksz * data->blocks;
+	return error;
+}
+
+static int cb710_mmc_command(struct mmc_host *mmc, struct mmc_command *cmd)
+{
+	struct cb710_slot *slot = cb710_mmc_to_slot(mmc);
+	struct cb710_mmc_reader *reader = mmc_priv(mmc);
+	struct mmc_data *data = cmd->data;
+
+	u16 cb_cmd = cb710_encode_cmd_flags(reader, cmd);
+	dev_dbg(cb710_slot_dev(slot), "cmd request: 0x%04X\n", cb_cmd);
+
+	if (data)
+		cb710_mmc_set_transfer_size(slot, data->blocks, data->blksz);
+
+	cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20|CB710_MMC_S2_BUSY_10);
+	cb710_write_port_16(slot, CB710_MMC_CMD_TYPE_PORT, cb_cmd);
+	cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
+	cb710_write_port_32(slot, CB710_MMC_CMD_PARAM_PORT, cmd->arg);
+	cb710_mmc_reset_events(slot);
+	cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG0_PORT, 0x01, 0);
+
+	cmd->error = cb710_wait_for_event(slot, CB710_MMC_S1_COMMAND_SENT);
+	if (cmd->error)
+		return -1;
+
+	if (cmd->flags & MMC_RSP_PRESENT) {
+		cb710_receive_response(slot, cmd);
+		if (cmd->error)
+			return -1;
+	}
+
+	if (data)
+		data->error = cb710_mmc_transfer_data(slot, data);
+	return 0;
+}
+
+static void cb710_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+	struct cb710_slot *slot = cb710_mmc_to_slot(mmc);
+	struct cb710_mmc_reader *reader = mmc_priv(mmc);
+
+	verify_serialization(mmc, &reader->active_req, 1);
+
+	WARN_ON(reader->mrq != NULL);
+
+	reader->mrq = mrq;
+	cb710_mmc_enable_irq(slot, 1);
+
+	if (cb710_mmc_is_card_inserted(slot)) {
+		if (!cb710_mmc_command(mmc, mrq->cmd) && mrq->stop)
+			cb710_mmc_command(mmc, mrq->stop);
+		mdelay(1);
+	} else {
+		mrq->cmd->error = -ENOMEDIUM;
+	}
+
+	tasklet_schedule(&reader->finish_req_tasklet);
+
+	verify_serialization(mmc, &reader->active_req, 0);
+}
+
+static int cb710_mmc_powerup(struct cb710_slot *slot)
+{
+	struct cb710_chip *chip = cb710_slot_to_chip(slot);
+	int err;
+
+	/* a lot of magic; see comment in cb710_mmc_set_clock() */
+	dev_dbg(cb710_slot_dev(slot), "bus powerup\n");
+	cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+	err = cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
+	if (unlikely(err))
+		return err;
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT, 0x80, 0);
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG3_PORT, 0x80, 0);
+	cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+	mdelay(1);
+	dev_dbg(cb710_slot_dev(slot), "after delay 1\n");
+	cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+	err = cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
+	if (unlikely(err))
+		return err;
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT, 0x09, 0);
+	cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+	mdelay(1);
+	dev_dbg(cb710_slot_dev(slot), "after delay 2\n");
+	cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+	err = cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
+	if (unlikely(err))
+		return err;
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT, 0, 0x08);
+	cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+	mdelay(2);
+	dev_dbg(cb710_slot_dev(slot), "after delay 3\n");
+	cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG0_PORT, 0x06, 0);
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT, 0x70, 0);
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG2_PORT, 0x80, 0);
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG3_PORT, 0x03, 0);
+	cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+	err = cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
+	if (unlikely(err))
+		return err;
+	/* This port behaves weird: quick byte reads of 0x08,0x09 return
+	 * 0xFF,0x00 after writing 0xFFFF to 0x08; it works correctly when
+	 * read/written from userspace...  What am I missing here?
+	 * (it doesn't depend on write-to-read delay)  --mq */
+	cb710_write_port_16(slot, CB710_MMC_CONFIGB_PORT, 0xFFFF);
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG0_PORT, 0x06, 0);
+	cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+	dev_dbg(cb710_slot_dev(slot), "bus powerup finished\n");
+
+	return cb710_check_event(slot, 0);
+}
+
+static void cb710_mmc_powerdown(struct cb710_slot *slot)
+{
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT, 0, 0x81);
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG3_PORT, 0, 0x80);
+}
+
+static void cb710_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+	struct cb710_slot *slot = cb710_mmc_to_slot(mmc);
+	struct cb710_mmc_reader *reader = mmc_priv(mmc);
+	int err;
+
+	verify_serialization(mmc, &reader->active_ios, 1);
+
+	cb710_mmc_set_clock(mmc, ios->clock);
+
+	if (!cb710_mmc_is_card_inserted(slot)) {
+		dev_dbg(cb710_slot_dev(slot),
+			"no card inserted - ignoring bus powerup request\n");
+		ios->power_mode = MMC_POWER_OFF;
+	}
+
+	if (ios->power_mode != reader->last_power_mode)
+	switch (ios->power_mode) {
+	case MMC_POWER_ON:
+		err = cb710_mmc_powerup(slot);
+		if (err) {
+			dev_warn(cb710_slot_dev(slot),
+				"powerup failed (%d)- retrying\n", err);
+			cb710_mmc_powerdown(slot);
+			udelay(1);
+			err = cb710_mmc_powerup(slot);
+			if (err)
+				dev_warn(cb710_slot_dev(slot),
+					"powerup retry failed (%d) - expect errors\n",
+					err);
+		}
+		reader->last_power_mode = MMC_POWER_ON;
+		break;
+	case MMC_POWER_OFF:
+		cb710_mmc_powerdown(slot);
+		reader->last_power_mode = MMC_POWER_OFF;
+		break;
+	case MMC_POWER_UP:
+	default:
+		/* ignore */;
+	}
+
+	cb710_mmc_enable_4bit_data(slot, ios->bus_width != MMC_BUS_WIDTH_1);
+
+	cb710_mmc_enable_irq(slot, 1);
+
+	verify_serialization(mmc, &reader->active_ios, 0);
+}
+
+static int cb710_mmc_get_ro(struct mmc_host *mmc)
+{
+	struct cb710_slot *slot = cb710_mmc_to_slot(mmc);
+
+	return cb710_read_port_8(slot, CB710_MMC_STATUS3_PORT)
+		& CB710_MMC_S3_WRITE_PROTECTED;
+}
+
+static int cb710_mmc_irq_handler(struct cb710_slot *slot)
+{
+	struct mmc_host *mmc = cb710_slot_to_mmc(slot);
+	struct cb710_mmc_reader *reader = mmc_priv(mmc);
+	u32 status, config1, config2, irqen;
+
+	status = cb710_read_port_32(slot, CB710_MMC_STATUS_PORT);
+	irqen = cb710_read_port_32(slot, CB710_MMC_IRQ_ENABLE_PORT);
+	config2 = cb710_read_port_32(slot, CB710_MMC_CONFIGB_PORT);
+	config1 = cb710_read_port_32(slot, CB710_MMC_CONFIG_PORT);
+
+	dev_dbg(cb710_slot_dev(slot), "interrupt; status: %08X, "
+		"ie: %08X, c2: %08X, c1: %08X\n",
+		status, irqen, config2, config1);
+
+	if (status & (CB710_MMC_S1_CARD_CHANGED << 8)) {
+		cb710_write_port_8(slot, CB710_MMC_STATUS1_PORT,
+			CB710_MMC_S1_CARD_CHANGED);
+		if ((irqen & CB710_MMC_IE_CISTATUS_MASK)
+		    == CB710_MMC_IE_CISTATUS_MASK)
+			mmc_detect_change(mmc, HZ/2);
+	} else {
+		dev_dbg(cb710_slot_dev(slot), "unknown interrupt\n");
+		spin_lock(&reader->irq_lock);
+		cb710_write_port_8(slot, CB710_MMC_IRQ_ENABLE0_PORT, 0x00);
+		cb710_write_port_8(slot, CB710_MMC_IRQ_ENABLE1_PORT,
+			CB710_MMC_IE_IRQ_ENABLE|CB710_MMC_IE_CARD_INSERTION_STATUS);
+		spin_unlock(&reader->irq_lock);
+	}
+
+	return 1;
+}
+
+static void cb710_mmc_finish_request_tasklet(unsigned long data)
+{
+	struct mmc_host *mmc = (void *)data;
+	struct cb710_mmc_reader *reader = mmc_priv(mmc);
+	struct mmc_request *mrq = reader->mrq;
+
+	reader->mrq = NULL;
+	mmc_request_done(mmc, mrq);
+}
+
+static const struct mmc_host_ops cb710_mmc_host = {
+	.request = cb710_mmc_request,
+	.set_ios = cb710_mmc_set_ios,
+	.get_ro = cb710_mmc_get_ro
+};
+
+#ifdef CONFIG_PM
+
+static int cb710_mmc_suspend(struct platform_device *pdev, pm_message_t state)
+{
+	struct cb710_slot *slot = cb710_pdev_to_slot(pdev);
+	struct mmc_host *mmc = cb710_slot_to_mmc(slot);
+	int err;
+
+	err = mmc_suspend_host(mmc, state);
+	if (err)
+		return err;
+
+	cb710_mmc_enable_irq(slot, 0);
+	return 0;
+}
+
+static int cb710_mmc_resume(struct platform_device *pdev)
+{
+	struct cb710_slot *slot = cb710_pdev_to_slot(pdev);
+	struct mmc_host *mmc = cb710_slot_to_mmc(slot);
+
+	cb710_mmc_enable_irq(slot, 0);
+
+	return mmc_resume_host(mmc);
+}
+
+#endif /* CONFIG_PM */
+
+static int __devinit cb710_mmc_init(struct platform_device *pdev)
+{
+	struct cb710_slot *slot = cb710_pdev_to_slot(pdev);
+	struct cb710_chip *chip = cb710_slot_to_chip(slot);
+	struct mmc_host *mmc;
+	struct cb710_mmc_reader *reader;
+	int err;
+	u32 val;
+
+	mmc = mmc_alloc_host(sizeof(*reader), cb710_slot_dev(slot));
+	if (!mmc)
+		return -ENOMEM;
+
+	dev_set_drvdata(&pdev->dev, mmc);
+
+	/* harmless (maybe) magic */
+	pci_read_config_dword(chip->pdev, 0x48, &val);
+	val = cb710_src_freq_mhz[(val >> 16) & 0xF];
+	dev_dbg(cb710_slot_dev(slot), "source frequency: %dMHz\n", val);
+	val *= 1000000;
+
+	mmc->ops = &cb710_mmc_host;
+	mmc->f_max = val;
+	mmc->f_min = val >> cb710_clock_divider_log2[CB710_MAX_DIVIDER_IDX];
+	mmc->ocr_avail = MMC_VDD_32_33|MMC_VDD_33_34|MMC_VDD_34_35;
+	mmc->caps = MMC_CAP_4_BIT_DATA;
+
+	reader = mmc_priv(mmc);
+
+	tasklet_init(&reader->finish_req_tasklet,
+		cb710_mmc_finish_request_tasklet, (unsigned long)mmc);
+	spin_lock_init(&reader->irq_lock);
+#ifdef VERBOSE_DEBUG
+	spin_lock_init(&reader->serialization_lock);
+#endif
+	cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+
+	cb710_mmc_enable_irq(slot, 0);
+	cb710_set_irq_handler(slot, cb710_mmc_irq_handler);
+
+	err = mmc_add_host(mmc);
+	if (likely(!err)) {
+		dev_dbg(cb710_slot_dev(slot), "mmc_hostname is %s\n",
+			mmc_hostname(mmc));
+		return 0;
+	}
+
+	dev_dbg(cb710_slot_dev(slot), "mmc_add_host() failed: %d\n", err);
+
+	mmc_free_host(mmc);
+	return err;
+}
+
+static int __devexit cb710_mmc_exit(struct platform_device *pdev)
+{
+	struct cb710_slot *slot = cb710_pdev_to_slot(pdev);
+	struct mmc_host *mmc = cb710_slot_to_mmc(slot);
+	struct cb710_mmc_reader *reader = mmc_priv(mmc);
+
+	mmc_remove_host(mmc);
+
+	/* XXX what if IRQ arrives now? */
+
+	cb710_mmc_enable_irq(slot, 0);
+	cb710_set_irq_handler(slot, NULL);
+
+	/* clear config ports - just in case */
+	cb710_write_port_32(slot, CB710_MMC_CONFIG_PORT, 0);
+	cb710_write_port_16(slot, CB710_MMC_CONFIGB_PORT, 0);
+
+	tasklet_kill(&reader->finish_req_tasklet);
+
+	mmc_free_host(mmc);
+	return 0;
+}
+
+static struct platform_driver cb710_mmc_driver = {
+	.driver.name = "cb710-mmc",
+	.probe = cb710_mmc_init,
+	.remove = __devexit_p(cb710_mmc_exit),
+#ifdef CONFIG_PM
+	.suspend = cb710_mmc_suspend,
+	.resume = cb710_mmc_resume,
+#endif
+};
+
+static int __init cb710_mmc_init_module(void)
+{
+	return platform_driver_register(&cb710_mmc_driver);
+}
+
+static void __exit cb710_mmc_cleanup_module(void)
+{
+	platform_driver_unregister(&cb710_mmc_driver);
+}
+
+module_init(cb710_mmc_init_module);
+module_exit(cb710_mmc_cleanup_module);
+
+MODULE_AUTHOR("Michał Mirosław <mirq-linux@rere.qmqm.pl>");
+MODULE_DESCRIPTION("ENE CB710 memory card reader driver - MMC/SD part");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:cb710-mmc");
diff -urN empty/sgbuf.c cb710/sgbuf.c
--- empty/sgbuf.c	1970-01-01 01:00:00.000000000 +0100
+++ cb710/sgbuf.c	2008-09-21 16:48:04.000000000 +0200
@@ -0,0 +1,206 @@
+/*
+ *  cb710/sgbuf.c
+ *
+ *  Copyright by Michał Mirosław, 2008
+ *
+ * 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.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/highmem.h>
+#include <linux/scatterlist.h>
+#include "cb710-sg.h"
+
+#ifdef VERBOSE_DEBUG
+#define sg_debug(fmt, arg...) \
+	pr_debug("sg[0x%p]: " fmt, buf, ##arg)
+#else
+#define sg_debug(fmt, arg...) \
+	do { if (0) pr_debug("sg[0x%p]: " fmt, buf, ##arg); } while (0)
+#endif
+
+static void cb710_sg_init_element(struct cb710_sg_chain *buf)
+{
+	buf->cur_offset = 0;
+	buf->page_offset = buf->sg->offset & ~PAGE_MASK;
+	buf->page_no = buf->sg->offset >> PAGE_SHIFT;
+	buf->page = nth_page(sg_page(buf->sg), buf->page_no);
+
+	sg_debug("moved to new scatterlist entry: first page +%d, poffs=%d, len=%d\n",
+		buf->page_no, buf->page_offset, buf->sg->length);
+}
+
+static void cb710_sg_unmap_page(struct cb710_sg_chain *buf, int to_sg)
+{
+	sg_debug("unmapping %s page\n", to_sg ? "written" : "read");
+
+	if (to_sg)
+		flush_kernel_dcache_page(buf->page);
+	kunmap_atomic(buf->mapped_page, KM_BIO_SRC_IRQ);
+	buf->mapped_page = NULL;
+}
+
+static int cb710_sg_advance(struct cb710_sg_chain *buf, int advance, int to_sg)
+{
+	size_t rlen;
+	unsigned page_end;
+
+	buf->cur_offset += advance;
+	buf->page_offset += advance;
+	rlen = buf->sg->length - buf->cur_offset;
+
+	sg_debug("advanced %d bytes; cur_offset=%d, page_offset=%d, rlen=%d\n",
+		advance, buf->cur_offset, buf->page_offset, rlen);
+
+	if (!rlen || buf->page_offset == PAGE_SIZE) {
+		if (buf->mapped_page)
+			cb710_sg_unmap_page(buf, to_sg);
+
+		if (!rlen) {
+			if (!--buf->sg_num) {
+				buf->need_bounce = 1;
+				return 0;
+			}
+			buf->sg = sg_next(buf->sg);
+			cb710_sg_init_element(buf);
+			rlen = buf->sg->length;
+		} else {
+			buf->page_offset = 0;
+			buf->page = nth_page(sg_page(buf->sg), ++buf->page_no);
+		}
+	}
+
+	page_end = (buf->page_offset + PAGE_SIZE) & PAGE_MASK;
+	buf->page_left = page_end - buf->page_offset;
+	if (buf->page_left > rlen)
+		buf->page_left = rlen;
+	if (buf->page_left < CB710_SG_BUFFER_BLOCK)
+		buf->need_bounce = 1;
+
+	if (!buf->mapped_page) {
+		buf->mapped_page = kmap_atomic(buf->page, KM_BIO_SRC_IRQ);
+		sg_debug("mapped new page: +%d @0x%p\n",
+			buf->page_no, buf->mapped_page);
+	}
+
+	return 1;
+}
+
+void cb710_sg_init(struct cb710_sg_chain *buf,
+	struct scatterlist *sg, size_t nelem)
+{
+	sg_debug("init: %d elements\n", nelem);
+	BUG_ON(!nelem);
+	BUG_ON(sg_is_chain(sg));
+
+	memset(buf, 0, sizeof(*buf));
+	buf->sg = sg;
+	buf->sg_num = nelem;
+	cb710_sg_init_element(buf);
+	cb710_sg_advance(buf, 0, 0);
+}
+EXPORT_SYMBOL_GPL(cb710_sg_init);
+
+static int cb710_sg_use_bounce(struct cb710_sg_chain *buf, int to_sg)
+{
+	size_t len = 0;
+	size_t bounce_offset = 0;
+
+	do {
+		len = CB710_SG_BUFFER_BLOCK - bounce_offset;
+		if (len > buf->sg->length - buf->cur_offset)
+			len = buf->sg->length - buf->cur_offset;
+
+		if (to_sg)
+			memcpy(buf->mapped_page + buf->page_offset,
+				buf->bounce_buffer + bounce_offset, len);
+		else
+			memcpy(buf->bounce_buffer + bounce_offset,
+				buf->mapped_page + buf->page_offset, len);
+
+		bounce_offset += len;
+	} while (cb710_sg_advance(buf, len, to_sg)
+		&& bounce_offset < CB710_SG_BUFFER_BLOCK);
+
+	sg_debug("%d bytes %s sg via bounce_buffer\n",
+		bounce_offset, to_sg ? "written to" : "to read from");
+	return bounce_offset;
+}
+
+static size_t cb710_sg_bounce_space(struct cb710_sg_chain *buf)
+{
+	struct scatterlist *sg;
+	unsigned i;
+	size_t len = buf->sg->length - buf->cur_offset;
+
+	if (len >= CB710_SG_BUFFER_BLOCK)
+		return CB710_SG_BUFFER_BLOCK;
+	if (buf->sg_num == 1)
+		return len;
+
+	for_each_sg(sg_next(buf->sg), sg, buf->sg_num - 1, i) {
+		len += sg->length;
+		if (len >= CB710_SG_BUFFER_BLOCK)
+			return CB710_SG_BUFFER_BLOCK;
+	}
+
+	return len;
+}
+
+int cb710_sg_next_buf(struct cb710_sg_chain *buf,
+	void **dataptr, size_t *len, int to_sg)
+{
+	sg_debug("next buffer to %s\n", to_sg ? "write" : "read");
+
+	if (buf->use_bounce) {
+		buf->use_bounce = 0;
+		cb710_sg_use_bounce(buf, 1);
+	}
+
+	if (buf->need_advance) {
+		cb710_sg_advance(buf, buf->need_advance, to_sg);
+		buf->need_advance = 0;
+	}
+
+	if (!buf->need_bounce) {
+		BUG_ON(!buf->mapped_page);
+
+		*dataptr = buf->mapped_page + buf->page_offset;
+		buf->need_advance = *len =
+			buf->page_left & CB710_SG_BUFFER_MASK;
+
+		sg_debug("%d bytes mapped directly\n", *len);
+		return 1;
+	}
+
+	if (!buf->sg_num)
+		return 0;
+
+	buf->need_bounce = 0;
+	*dataptr = &buf->bounce_buffer;
+
+	if (to_sg) {
+		*len = cb710_sg_bounce_space(buf);
+		buf->use_bounce = 1;
+
+		sg_debug("using bounce_buffer for writing %d bytes\n",
+			*len);
+	} else
+		*len = cb710_sg_use_bounce(buf, 0);
+
+	return *len != 0;
+}
+EXPORT_SYMBOL_GPL(cb710_sg_next_buf);
+
+void cb710_sg_abort(struct cb710_sg_chain *buf, int to_sg)
+{
+	sg_debug("aborting %s\n", to_sg ? "write" : "read");
+
+	if (buf->mapped_page)
+		cb710_sg_unmap_page(buf, to_sg);
+}
+EXPORT_SYMBOL_GPL(cb710_sg_abort);
+

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

* Re: RFC: Driver for CB710/720 memory card reader (MMC part) - v2
  2008-09-25  6:29           ` RFC: Driver for CB710/720 memory card reader (MMC part) - v2 Michał Mirosław
@ 2008-10-04 19:51             ` Pierre Ossman
  2008-10-29 14:11               ` RFC: Driver for CB710/720 memory card reader (MMC part) - v3 Michał Mirosław
  0 siblings, 1 reply; 21+ messages in thread
From: Pierre Ossman @ 2008-10-04 19:51 UTC (permalink / raw)
  To: Michał Mirosław; +Cc: linux-kernel, Alex Dubov

On Thu, 25 Sep 2008 08:29:24 +0200
Michał Mirosław <mirq-linux@rere.qmqm.pl> wrote:

> 
> This should be almost ready. mmc-test passes up to a point where it
> tries non-sector-size writes to the card - I didn't have time to look
> into this further, yet.
> 

If you can't figure it out, just make sure the driver fails the
requests with -EINVAL.

> > > +/* sg-to-PIO buffer */
> [and its API]
> > Why this complex system? Can't you use the handlers the kernel already
> > provides? You also get a lot of special handling with those, e.g.
> > highmem.
> 
> I looked at linux/lib/scatterlist.c and found nothing really useful. If
> there were some preconditions always met for scatterlists - like that block
> does not span multiple pages, or at least 16-byte block don't, then most
> of this code can go away. It looks complicated, because it implements
> a special iterator interface that always returns multiple-of-16-byte blocks
> for reading or writing. This makes the PIO loops very simple and fast
> (the bounce_buffer case was never triggered by mmc-block in my tests).
> 

The buffers leave no alignment guarantees unfortunately. 

Have a look at the current sdhci.c PIO routines though. It uses the sg
iterator helpers and keeps track of four byte chunks (as opposed to the
16 byte ones you need).

> > > +static void cb710_mmc_enable_irq(struct cb710_chip *chip, int enable)
> > > +{
> > > +	unsigned long flags;
> > > +
> > > +	spin_lock_irqsave(&chip->irq_lock, flags);
> > > +	__cb710_mmc_enable_irq(chip, enable);
> > > +	spin_unlock_irqrestore(&chip->irq_lock, flags);
> > > +}
> > This is a fairly useless wrapper. Look over how it is called instead.
> 
> Can you expand on this? Right now irq handler doesn't call
> __cb710_mmc_enable_irq() but modifies registers itself. This will
> change eventually.
> 

Just require that the lock must be held by the caller when invoking
cb710_mmc_enable_irq(). It's usually easier to keep track of locks by
keeping the lock handling at entry points into the driver.

Rgds
-- 
     -- Pierre Ossman

  Linux kernel, MMC maintainer        http://www.kernel.org
  rdesktop, core developer          http://www.rdesktop.org

  WARNING: This correspondence is being monitored by the
  Swedish government. Make sure your server uses encryption
  for SMTP traffic and consider using PGP for end-to-end
  encryption.

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

* RFC: Driver for CB710/720 memory card reader (MMC part) - v3
  2008-10-04 19:51             ` Pierre Ossman
@ 2008-10-29 14:11               ` Michał Mirosław
  2008-11-14 21:06                 ` Pierre Ossman
  0 siblings, 1 reply; 21+ messages in thread
From: Michał Mirosław @ 2008-10-29 14:11 UTC (permalink / raw)
  To: Pierre Ossman; +Cc: linux-kernel, Alex Dubov

Here is the driver for CB710 SD/MMC reader updated to recently released
kernel version 2.6.27. The code is divided into PCI device driver
(wrapped that registers three platform devices) and MMC reader driver.
This idea was taken from TIFM driver.

Changes from v2:
 - return EINVAL for unsupported data transfer sizes
 - use sg_mapping_iter for PIO from/to scatterlist
 - add slot release function and prevent kobject leak on device registration's
   error path

If this code looks reasonably good, then I'll post patches against the
real kernel tree. The file distribution plan is:

   cb710-mmc.h	-> drivers/mmc/host/cb710-mmc.h
   mmc.c	-> drivers/mmc/host/cb710-mmc.c
   sgbuf2.h	-> merge to include/linux/scatterlist.h
   sgbuf2.c	-> merge to lib/scatterlist.c
   cb710.h	-> include/linux/cb710.h
   core.c	-> drivers/misc/cb710-core.c
   debug.c	-> drivers/misc/cb710-debug.c

On Sat, Oct 04, 2008 at 09:51:22PM +0200, Pierre Ossman wrote:
> On Thu, 25 Sep 2008 08:29:24 +0200
> Michał Mirosław <mirq-linux@rere.qmqm.pl> wrote:
> > This should be almost ready. mmc-test passes up to a point where it
> > tries non-sector-size writes to the card - I didn't have time to look
> > into this further, yet.
> If you can't figure it out, just make sure the driver fails the
> requests with -EINVAL.

Done.

> > > > +/* sg-to-PIO buffer */
> > [and its API]
> > > Why this complex system? Can't you use the handlers the kernel already
> > > provides? You also get a lot of special handling with those, e.g.
> > > highmem.
> > I looked at linux/lib/scatterlist.c and found nothing really useful. If
> > there were some preconditions always met for scatterlists - like that block
> > does not span multiple pages, or at least 16-byte block don't, then most
> > of this code can go away. It looks complicated, because it implements
> > a special iterator interface that always returns multiple-of-16-byte blocks
> > for reading or writing. This makes the PIO loops very simple and fast
> > (the bounce_buffer case was never triggered by mmc-block in my tests).
> The buffers leave no alignment guarantees unfortunately. 
> 
> Have a look at the current sdhci.c PIO routines though. It uses the sg
> iterator helpers and keeps track of four byte chunks (as opposed to the
> 16 byte ones you need).

I got around it by introducing a small wrapper around sg_mapping_iter
functions that uses 32-bit blocks. This reduces to what is in SDHCI
driver's sdhci_*_block_pio() functions, but with optimized common case
where the buffers are properly aligned. That leaves a question what should
be inlined and what not for best effect. See: sgbuf2.{c,h}.

> > > > +static void cb710_mmc_enable_irq(struct cb710_chip *chip, int enable)
> > > > +{
> > > > +	unsigned long flags;
> > > > +
> > > > +	spin_lock_irqsave(&chip->irq_lock, flags);
> > > > +	__cb710_mmc_enable_irq(chip, enable);
> > > > +	spin_unlock_irqrestore(&chip->irq_lock, flags);
> > > > +}
> > > This is a fairly useless wrapper. Look over how it is called instead.
> > Can you expand on this? Right now irq handler doesn't call
> > __cb710_mmc_enable_irq() but modifies registers itself. This will
> > change eventually.
> Just require that the lock must be held by the caller when invoking
> cb710_mmc_enable_irq(). It's usually easier to keep track of locks by
> keeping the lock handling at entry points into the driver.

The chip->irq_lock (now in reader->irq_lock) protects only hardware
IRQ-enabling registers. I think there is no point in moving the lock
outside cb710_mmc_enable_irq().

Best Regards,
Michał Mirosław

diff -urN -x '.git*' empty/cb710.h cb710/cb710.h
--- empty/cb710.h	1970-01-01 01:00:00.000000000 +0100
+++ cb710/cb710.h	2008-10-29 12:49:55.000000000 +0100
@@ -0,0 +1,132 @@
+/*
+ *  cb710/cb710.h
+ *
+ *  Copyright by Michał Mirosław, 2008
+ *
+ * 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.
+ */
+#ifndef LINUX_CB710_DRIVER_H
+#define LINUX_CB710_DRIVER_H
+
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+#include <linux/pci.h>
+#include <linux/platform_device.h>
+#include <linux/mmc/host.h>
+
+#define CB710_DRIVER_NAME "cb710"
+
+struct cb710_slot;
+
+typedef int (*cb710_irq_handler_t)(struct cb710_slot *);
+
+/* per-virtual-slot structure */
+struct cb710_slot {
+	struct platform_device	pdev;
+	void __iomem		*iobase;
+	cb710_irq_handler_t	irq_handler;	/* RCU */
+};
+
+/* per-device structure */
+struct cb710_chip {
+	struct pci_dev		*pdev;
+	void __iomem		*iobase;
+	unsigned		platform_id;
+	atomic_t		slots;		/* RCU */
+	unsigned		slot_mask;
+	struct cb710_slot	slot[0];
+	atomic_t		free_test;
+};
+
+/* NOTE: cb710_chip.slots is modified only during device init/exit */
+
+/* cb710_chip.slot_mask values */
+#define CB710_SLOT_MMC		1
+#define CB710_SLOT_MS		2
+#define CB710_SLOT_SM		4
+
+/* slot port accessors - in case it turns out inX() is all that is needed */
+#define CB710_PORT_ACCESSORS(t) \
+static inline void cb710_write_port_##t(struct cb710_slot *slot,	\
+	unsigned port, u##t value)					\
+{									\
+	iowrite##t(value, slot->iobase + port);				\
+}									\
+									\
+static inline u##t cb710_read_port_##t(struct cb710_slot *slot,		\
+	unsigned port)							\
+{									\
+	return ioread##t(slot->iobase + port);				\
+}									\
+									\
+static inline void cb710_modify_port_##t(struct cb710_slot *slot,	\
+	unsigned port, u##t set, u##t clear)				\
+{									\
+	iowrite##t(							\
+		(ioread##t(slot->iobase + port) & ~clear)|set,		\
+		slot->iobase + port);					\
+}
+
+CB710_PORT_ACCESSORS(8)
+CB710_PORT_ACCESSORS(16)
+CB710_PORT_ACCESSORS(32)
+
+void cb710_pci_update_config_reg(struct pci_dev *pdev,
+	int reg, uint32_t and, uint32_t xor);
+
+/* some device struct walking */
+
+static inline struct cb710_slot *cb710_pdev_to_slot(
+	struct platform_device *pdev)
+{
+	return container_of(pdev, struct cb710_slot, pdev);
+}
+
+static inline struct cb710_chip *cb710_slot_to_chip(struct cb710_slot *slot)
+{
+	return dev_get_drvdata(slot->pdev.dev.parent);
+}
+
+static inline struct device *cb710_slot_dev(struct cb710_slot *slot)
+{
+	return &slot->pdev.dev;
+}
+
+static inline struct device *cb710_chip_dev(struct cb710_chip *chip)
+{
+	return &chip->pdev->dev;
+}
+
+/* helper functions */
+
+static inline void cb710_set_irq_handler(struct cb710_slot *slot,
+	cb710_irq_handler_t handler)
+{
+	rcu_assign_pointer(slot->irq_handler, handler);
+	synchronize_rcu();	/* sync to IRQ handler */
+}
+
+/* debugging aids */
+
+#ifdef DEBUG
+void cb710_dump_regs(struct cb710_chip *chip, unsigned dump);
+#else
+#define cb710_dump_regs(c, d) do {} while (0)
+#endif
+
+#define CB710_DUMP_REGS_MMC	0x0F
+#define CB710_DUMP_REGS_MS	0x30
+#define CB710_DUMP_REGS_SM	0xC0
+#define CB710_DUMP_REGS_ALL	0xFF
+#define CB710_DUMP_REGS_MASK	0xFF
+
+#define CB710_DUMP_ACCESS_8	0x100
+#define CB710_DUMP_ACCESS_16	0x200
+#define CB710_DUMP_ACCESS_32	0x400
+#define CB710_DUMP_ACCESS_ALL	0x700
+#define CB710_DUMP_ACCESS_MASK	0x700
+
+#endif /* LINUX_CB710_DRIVER_H */
diff -urN -x '.git*' empty/cb710-mmc.h cb710/cb710-mmc.h
--- empty/cb710-mmc.h	1970-01-01 01:00:00.000000000 +0100
+++ cb710/cb710-mmc.h	2008-09-25 05:37:51.000000000 +0200
@@ -0,0 +1,114 @@
+/*
+ *  cb710/cb710-mmc.h
+ *
+ *  Copyright by Michał Mirosław, 2008
+ *
+ * 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.
+ */
+#ifndef LINUX_CB710_MMC_H
+#define LINUX_CB710_MMC_H
+
+#include <linux/spinlock.h>
+#include <linux/mmc/host.h>
+#include <linux/pci.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include "cb710.h"
+
+/* per-MMC-reader structure */
+struct cb710_mmc_reader {
+	struct tasklet_struct finish_req_tasklet;
+	struct mmc_request *mrq;
+	spinlock_t irq_lock;
+	unsigned char last_power_mode;
+#ifdef VERBOSE_DEBUG
+	spinlock_t serialization_lock;
+	unsigned char active_req, active_ios;
+#endif
+};
+
+/* some device struct walking */
+
+static inline struct mmc_host *cb710_slot_to_mmc(struct cb710_slot *slot)
+{
+	return dev_get_drvdata(&slot->pdev.dev);
+}
+
+static inline struct cb710_slot *cb710_mmc_to_slot(struct mmc_host *mmc)
+{
+	struct platform_device *pdev = container_of(mmc_dev(mmc),
+		struct platform_device, dev);
+	return cb710_pdev_to_slot(pdev);
+}
+
+/* registers (this might be all wrong ;) */
+
+#define CB710_MMC_DATA_PORT		0x00
+
+#define CB710_MMC_CONFIG_PORT		0x04
+#define CB710_MMC_CONFIG0_PORT		0x04
+#define CB710_MMC_CONFIG1_PORT		0x05
+#define   CB710_MMC_C1_4BIT_DATA_BUS		0x40
+#define CB710_MMC_CONFIG2_PORT		0x06
+#define   CB710_MMC_C2_READ_PIO_SIZE_MASK	0x0F	/* N-1 */
+#define CB710_MMC_CONFIG3_PORT		0x07
+
+#define CB710_MMC_CONFIGB_PORT		0x08
+
+#define CB710_MMC_IRQ_ENABLE_PORT	0x0C
+#define   CB710_MMC_IE_CISTATUS_MASK		0x00009000
+#define CB710_MMC_IRQ_ENABLE0_PORT	0x0C
+#define CB710_MMC_IRQ_ENABLE1_PORT	0x0D
+#define   CB710_MMC_IE_IRQ_ENABLE		0x80
+#define   CB710_MMC_IE_CARD_INSERTION_STATUS	0x10
+
+#define CB710_MMC_STATUS_PORT		0x10
+#define   CB710_MMC_STATUS_ERROR_EVENTS		0x60FF
+#define CB710_MMC_STATUS0_PORT		0x10
+#define   CB710_MMC_S0_FIFO_UNDERFLOW		0x40
+#define CB710_MMC_STATUS1_PORT		0x11
+#define   CB710_MMC_S1_COMMAND_SENT		0x01
+#define   CB710_MMC_S1_DATA_TRANSFER_DONE	0x02
+#define   CB710_MMC_S1_PIO_TRANSFER_DONE	0x04
+#define   CB710_MMC_S1_CARD_CHANGED		0x10
+#define   CB710_MMC_S1_RESET			0x20
+#define CB710_MMC_STATUS2_PORT		0x12
+#define   CB710_MMC_S2_FIFO_READY		0x01
+#define   CB710_MMC_S2_FIFO_EMPTY		0x02
+#define   CB710_MMC_S2_BUSY_10			0x10
+#define   CB710_MMC_S2_BUSY_20			0x20
+#define CB710_MMC_STATUS3_PORT		0x13
+#define   CB710_MMC_S3_CARD_DETECTED		0x02
+#define   CB710_MMC_S3_WRITE_PROTECTED		0x04
+
+#define CB710_MMC_CMD_TYPE_PORT		0x14
+#define   CB710_MMC_RSP_TYPE_MASK		0x0007
+#define     CB710_MMC_RSP_R1			(0)
+#define     CB710_MMC_RSP_136			(5)
+#define     CB710_MMC_RSP_NO_CRC		(2)
+#define   CB710_MMC_RSP_PRESENT_MASK		0x0018
+#define     CB710_MMC_RSP_NONE			(0 << 3)
+#define     CB710_MMC_RSP_PRESENT		(1 << 3)
+#define     CB710_MMC_RSP_PRESENT_X		(2 << 3)
+#define   CB710_MMC_CMD_TYPE_MASK		0x0060
+#define     CB710_MMC_CMD_BC			(0 << 5)
+#define     CB710_MMC_CMD_BCR			(1 << 5)
+#define     CB710_MMC_CMD_AC			(2 << 5)
+#define     CB710_MMC_CMD_ADTC			(3 << 5)
+#define   CB710_MMC_DATA_READ			0x0080
+#define   CB710_MMC_CMD_CODE_MASK		0x3F00
+#define   CB710_MMC_CMD_CODE_SHIFT		8
+#define   CB710_MMC_IS_APP_CMD			0x4000
+#define   CB710_MMC_RSP_BUSY			0x8000
+
+#define CB710_MMC_CMD_PARAM_PORT	0x18
+#define CB710_MMC_TRANSFER_SIZE_PORT	0x1C
+#define CB710_MMC_RESPONSE0_PORT	0x20
+#define CB710_MMC_RESPONSE1_PORT	0x24
+#define CB710_MMC_RESPONSE2_PORT	0x28
+#define CB710_MMC_RESPONSE3_PORT	0x2C
+
+#endif /* LINUX_CB710_MMC_H */
diff -urN -x '.git*' empty/core.c cb710/core.c
--- empty/core.c	1970-01-01 01:00:00.000000000 +0100
+++ cb710/core.c	2008-10-29 14:40:37.000000000 +0100
@@ -0,0 +1,321 @@
+/*
+ *  cb710/core.c
+ *
+ *  Copyright by Michał Mirosław, 2008
+ *
+ * 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.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <linux/idr.h>
+#include <linux/rcupdate.h>
+#include "cb710.h"
+
+static DEFINE_IDR(cb710_idr);
+static DEFINE_SPINLOCK(cb710_idr_lock);
+
+void cb710_pci_update_config_reg(struct pci_dev *pdev,
+	int reg, uint32_t mask, uint32_t xor)
+{
+	u32 rval;
+
+	pci_read_config_dword(pdev, reg, &rval);
+	rval = (rval & mask) ^ xor;
+	pci_write_config_dword(pdev, reg, rval);
+}
+EXPORT_SYMBOL_GPL(cb710_pci_update_config_reg);
+
+/* Some magic writes based on Windows driver init code */
+static int __devinit cb710_pci_configure(struct pci_dev *pdev)
+{
+	unsigned int devfn = PCI_DEVFN(PCI_SLOT(pdev->devfn), 0);
+	struct pci_dev *pdev0 = pci_get_slot(pdev->bus, devfn);
+	u32 val;
+
+	cb710_pci_update_config_reg(pdev, 0x48,
+		~0x000000FF, 0x0000003F);
+
+	pci_read_config_dword(pdev, 0x48, &val);
+	if (val & 0x80000000)
+		return 0;
+
+	if (!pdev0)
+		return -ENODEV;
+
+	if (pdev0->vendor == PCI_VENDOR_ID_ENE
+	    && pdev0->device == PCI_DEVICE_ID_ENE_720) {
+		cb710_pci_update_config_reg(pdev0, 0x8C,
+			~0x00F00000, 0x00100000);
+		cb710_pci_update_config_reg(pdev0, 0xB0,
+			~0x08000000, 0x08000000);
+	}
+
+	cb710_pci_update_config_reg(pdev0, 0x8C,
+		~0x00000F00, 0x00000200);
+	cb710_pci_update_config_reg(pdev0, 0x90,
+		~0x00060000, 0x00040000);
+
+	pci_dev_put(pdev0);
+
+	return 0;
+}
+
+static irqreturn_t cb710_irq_handler(int irq, void *data)
+{
+	struct cb710_chip *chip = data;
+	struct cb710_slot *slot = &chip->slot[0];
+	cb710_irq_handler_t handler_func;
+	irqreturn_t handled = IRQ_NONE;
+	int nr;
+
+	rcu_read_lock();
+	nr = atomic_read(&chip->slots);
+
+	while (nr--) {
+		handler_func = rcu_dereference(slot->irq_handler);
+		if (handler_func && handler_func(slot))
+			handled = IRQ_HANDLED;
+		++slot;
+	}
+
+	rcu_read_unlock();
+
+	return handled;
+}
+
+static void cb710_release_slot(struct device *dev)
+{
+	struct cb710_slot *slot = cb710_pdev_to_slot(to_platform_device(dev));
+	struct cb710_chip *chip = cb710_slot_to_chip(slot);
+
+	/* slot struct can be freed now */
+	atomic_inc(&chip->free_test);
+}
+
+static int __devinit cb710_register_slot(struct cb710_chip *chip,
+	unsigned slot_mask, unsigned io_offset, const char *name)
+{
+	int nr = atomic_read(&chip->slots);
+	struct cb710_slot *slot = &chip->slot[nr];
+	int err;
+
+	dev_dbg(cb710_chip_dev(chip),
+		"register: %s.%d; slot %d; mask %d; IO offset: 0x%02X\n",
+		name, chip->platform_id, nr, slot_mask, io_offset);
+
+	slot->iobase = chip->iobase + io_offset;
+	slot->pdev.name = name;
+	slot->pdev.id = chip->platform_id;
+	slot->pdev.dev.parent = &chip->pdev->dev;
+	slot->pdev.dev.release = cb710_release_slot;
+	err = platform_device_register(&slot->pdev);
+
+	if (err) {
+		int a, b;
+		a = atomic_read(&chip->free_test);
+		/* device_initialize() wants this on error returns */
+		platform_device_put(&slot->pdev);
+		b = atomic_read(&chip->free_test);
+		BUG_ON(b == a);	/* XXX: is it really possible? */
+		return err;
+	}
+
+	chip->slot_mask |= slot_mask;
+	atomic_inc(&chip->slots);
+
+	/* XXX: can this be just left out/replaced by a memory barrier? */
+	synchronize_rcu();
+
+	return 0;
+}
+
+static void cb710_unregister_slot(struct cb710_chip *chip,
+	unsigned slot_mask)
+{
+	int nr;
+	int a, b;
+
+	if (!(chip->slot_mask & slot_mask))
+		return;
+
+	nr = atomic_sub_return(1, &chip->slots);
+	chip->slot_mask &= ~slot_mask;
+
+	synchronize_rcu();
+
+	a = atomic_read(&chip->free_test);
+	platform_device_unregister(&chip->slot[nr].pdev);
+	b = atomic_read(&chip->free_test);
+	BUG_ON(b == a);	/* XXX: is it really possible? */
+}
+
+#ifdef CONFIG_PM
+
+static int cb710_suspend(struct pci_dev *pdev, pm_message_t state)
+{
+	pci_save_state(pdev);
+	pci_disable_device(pdev);
+	if (state.event & PM_EVENT_SLEEP)
+		pci_set_power_state(pdev, PCI_D3cold);
+	return 0;
+}
+
+static int cb710_resume(struct pci_dev *pdev)
+{
+	pci_set_power_state(pdev, PCI_D0);
+	pci_restore_state(pdev);
+	return pcim_enable_device(pdev);
+}
+
+#endif /* CONFIG_PM */
+
+static int __devinit cb710_probe(struct pci_dev *pdev,
+	const struct pci_device_id *ent)
+{
+	struct cb710_chip *chip;
+	unsigned long flags;
+	u32 val;
+	int err;
+	int n = 0;
+
+	err = cb710_pci_configure(pdev);
+	if (err)
+		return err;
+
+	/* this is actually magic... */
+	pci_read_config_dword(pdev, 0x48, &val);
+	if (!(val & 0x80000000)) {
+		pci_write_config_dword(pdev, 0x48, val|0x71000000);
+		pci_read_config_dword(pdev, 0x48, &val);
+	}
+
+	dev_dbg(&pdev->dev, "PCI config[0x48] = 0x%08X\n", val);
+	if (!(val & 0x70000000))
+		return -ENODEV;
+	val = (val >> 28) & 7;
+	if (val & CB710_SLOT_MMC)
+		++n;
+	if (val & CB710_SLOT_MS)
+		++n;
+	if (val & CB710_SLOT_SM)
+		++n;
+
+	chip = devm_kzalloc(&pdev->dev,
+		sizeof(*chip) + n * sizeof(*chip->slot), GFP_KERNEL);
+	if (!chip)
+		return -ENOMEM;
+
+	err = pcim_enable_device(pdev);
+	if (err)
+		return err;
+
+	err = pcim_iomap_regions(pdev, 0x0001, CB710_DRIVER_NAME);
+	if (err)
+		return err;
+
+	chip->pdev = pdev;
+	chip->iobase = pcim_iomap_table(pdev)[0];
+
+	pci_set_drvdata(pdev, chip);
+
+	err = devm_request_irq(&pdev->dev, pdev->irq,
+		cb710_irq_handler, IRQF_SHARED, CB710_DRIVER_NAME, chip);
+	if (err)
+		return err;
+
+	if (!idr_pre_get(&cb710_idr, GFP_KERNEL))
+		return -ENOMEM;
+
+	spin_lock_irqsave(&cb710_idr_lock, flags);
+	err = idr_get_new(&cb710_idr, chip, &chip->platform_id);
+	spin_unlock_irqrestore(&cb710_idr_lock, flags);
+	if (err)
+		return err;
+
+	dev_info(&pdev->dev, "id %d, IO 0x%p, IRQ %d\n",
+		chip->platform_id, chip->iobase, pdev->irq);
+
+	if (val & CB710_SLOT_MMC) {	/* MMC/SD slot */
+		err = cb710_register_slot(chip,
+			CB710_SLOT_MMC, 0x00, "cb710-mmc");
+		if (err)
+			return err;
+	}
+
+	if (val & CB710_SLOT_MS) {	/* MemoryStick slot */
+		err = cb710_register_slot(chip,
+			CB710_SLOT_MS, 0x40, "cb710-ms");
+		if (err)
+			goto unreg_mmc;
+	}
+
+	if (val & CB710_SLOT_SM) {	/* SmartMedia slot */
+		err = cb710_register_slot(chip,
+			CB710_SLOT_SM, 0x60, "cb710-sm");
+		if (err)
+			goto unreg_ms;
+	}
+
+	return 0;
+unreg_ms:
+	cb710_unregister_slot(chip, CB710_SLOT_MS);
+unreg_mmc:
+	cb710_unregister_slot(chip, CB710_SLOT_MMC);
+	return err;
+}
+
+static void __devexit cb710_remove_one(struct pci_dev *pdev)
+{
+	struct cb710_chip *chip = pci_get_drvdata(pdev);
+	unsigned long flags;
+
+	cb710_unregister_slot(chip, CB710_SLOT_SM);
+	cb710_unregister_slot(chip, CB710_SLOT_MS);
+	cb710_unregister_slot(chip, CB710_SLOT_MMC);
+
+	spin_lock_irqsave(&cb710_idr_lock, flags);
+	idr_remove(&cb710_idr, chip->platform_id);
+	spin_unlock_irqrestore(&cb710_idr_lock, flags);
+}
+
+/* TODO: move to pci_ids.h before merging upstream */
+#define PCI_DEVICE_ID_ENE_710_FLASH	0x0510
+
+static const struct pci_device_id cb710_pci_tbl[] = {
+	{ PCI_VENDOR_ID_ENE, PCI_DEVICE_ID_ENE_710_FLASH,
+		PCI_ANY_ID, PCI_ANY_ID, },
+	{ 0, }
+};
+
+static struct pci_driver cb710_driver = {
+	.name = CB710_DRIVER_NAME,
+	.id_table = cb710_pci_tbl,
+	.probe = cb710_probe,
+	.remove = __devexit_p(cb710_remove_one),
+#ifdef CONFIG_PM
+	.suspend = cb710_suspend,
+	.resume = cb710_resume,
+#endif
+};
+
+static int __init cb710_init_module(void)
+{
+	return pci_register_driver(&cb710_driver);
+}
+
+static void __exit cb710_cleanup_module(void)
+{
+	pci_unregister_driver(&cb710_driver);
+}
+
+module_init(cb710_init_module);
+module_exit(cb710_cleanup_module);
+
+MODULE_AUTHOR("Michał Mirosław <mirq-linux@rere.qmqm.pl>");
+MODULE_DESCRIPTION("ENE CB710 memory card reader driver");
+MODULE_LICENSE("GPL");
+MODULE_DEVICE_TABLE(pci, cb710_pci_tbl);
diff -urN -x '.git*' empty/debug.c cb710/debug.c
--- empty/debug.c	1970-01-01 01:00:00.000000000 +0100
+++ cb710/debug.c	2008-09-25 05:51:31.000000000 +0200
@@ -0,0 +1,120 @@
+/*
+ *  cb710/debug.c
+ *
+ *  Copyright by Michał Mirosław, 2008
+ *
+ * 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.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include "cb710.h"
+
+#define CB710_REG_COUNT		0x80
+
+static const u16 allow[CB710_REG_COUNT/16] = {
+	0xFFF0, 0xFFFF, 0xFFFF, 0xFFFF,
+	0xFFF0, 0xFFFF, 0xFFFF, 0xFFFF,
+};
+static const char *const prefix[ARRAY_SIZE(allow)] = {
+	"MMC", "MMC", "MMC", "MMC",
+	"MS?", "MS?", "SM?", "SM?"
+};
+
+static inline int allow_reg_read(unsigned block, unsigned offset, unsigned bits)
+{
+	unsigned mask = (1 << bits/8) - 1;
+	offset *= bits/8;
+	return ((allow[block] >> offset) & mask) == mask;
+}
+
+#define CB710_READ_REGS_TEMPLATE(t)					\
+static void cb710_read_regs_##t(void __iomem *iobase,			\
+	u##t *reg, unsigned select)					\
+{									\
+	unsigned i, j;							\
+									\
+	for (i = 0; i < ARRAY_SIZE(allow); ++i, reg += 16/(t/8)) {	\
+		if (!select & (1 << i))					\
+			continue;					\
+									\
+		for (j = 0; j < 0x10/(t/8); ++j) {			\
+			if (!allow_reg_read(i, j, t))			\
+				continue;				\
+			reg[j] = ioread##t(iobase			\
+				+ (i << 4) + (j * (t/8)));		\
+		}							\
+	}								\
+}
+
+static const char cb710_regf_8[] = "%02X";
+static const char cb710_regf_16[] = "%04X";
+static const char cb710_regf_32[] = "%08X";
+static const char cb710_xes[] = "xxxxxxxx";
+
+#define CB710_DUMP_REGS_TEMPLATE(t)					\
+static void cb710_dump_regs_##t(struct device *dev,			\
+	const u##t *reg, unsigned select)				\
+{									\
+	const char *const xp = &cb710_xes[8 - t/4];			\
+	const char *const format = cb710_regf_##t;			\
+									\
+	char msg[100], *p;						\
+	unsigned i, j;							\
+									\
+	for (i = 0; i < ARRAY_SIZE(allow); ++i, reg += 16/(t/8)) {	\
+		if (!(select & (1 << i)))				\
+			continue;					\
+		p = msg;						\
+		for (j = 0; j < 0x10/(t/8); ++j) {			\
+			*p++ = ' ';					\
+			if (j == 8/(t/8))				\
+				*p++ = ' ';				\
+			if (allow_reg_read(i, j, t))			\
+				p += sprintf(p, format, reg[j]);	\
+			else						\
+				p += sprintf(p, "%s", xp);		\
+		}							\
+		dev_dbg(dev, "%s 0x%02X %s\n", prefix[i], i << 4, msg);	\
+	}								\
+}
+
+#define CB710_READ_AND_DUMP_REGS_TEMPLATE(t)				\
+static void cb710_read_and_dump_regs_##t(struct cb710_chip *chip,	\
+	unsigned select)						\
+{									\
+	u##t regs[CB710_REG_COUNT/sizeof(u##t)];			\
+									\
+	memset(&regs, 0, sizeof(regs));					\
+	cb710_read_regs_##t(chip->iobase, regs, select);		\
+	cb710_dump_regs_##t(cb710_chip_dev(chip), regs, select);	\
+}
+
+#define CB710_REG_ACCESS_TEMPLATES(t)		\
+  CB710_READ_REGS_TEMPLATE(t)			\
+  CB710_DUMP_REGS_TEMPLATE(t)			\
+  CB710_READ_AND_DUMP_REGS_TEMPLATE(t)
+
+CB710_REG_ACCESS_TEMPLATES(8)
+CB710_REG_ACCESS_TEMPLATES(16)
+CB710_REG_ACCESS_TEMPLATES(32)
+
+void cb710_dump_regs(struct cb710_chip *chip, unsigned select)
+{
+	if (!(select & CB710_DUMP_REGS_MASK))
+		select = CB710_DUMP_REGS_ALL;
+	if (!(select & CB710_DUMP_ACCESS_MASK))
+		select |= CB710_DUMP_ACCESS_8;
+
+	if (select & CB710_DUMP_ACCESS_32)
+		cb710_read_and_dump_regs_32(chip, select);
+	if (select & CB710_DUMP_ACCESS_16)
+		cb710_read_and_dump_regs_16(chip, select);
+	if (select & CB710_DUMP_ACCESS_8)
+		cb710_read_and_dump_regs_8(chip, select);
+}
+EXPORT_SYMBOL_GPL(cb710_dump_regs);
+
diff -urN -x '.git*' empty/Makefile cb710/Makefile
--- empty/Makefile	1970-01-01 01:00:00.000000000 +0100
+++ cb710/Makefile	2008-10-27 11:42:31.000000000 +0100
@@ -0,0 +1,29 @@
+ifeq ($(KERNELRELEASE),)
+
+#KDIR	:= /usr/src/linux
+KDIR	:= /usr/src/jaja/build/rechot
+PWD     := $(shell pwd)
+
+.PHONY: module install clean
+module:
+	$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules
+
+install:
+	$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules_install
+
+clean:
+	$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) clean
+	rm -f modules.order Module.markers
+
+else	# kbuild part
+
+obj-m		:= cb710.o cb710-mmc.o
+
+cb710-y		:= core.o sgbuf2.o
+cb710-mmc-y	:= mmc.o
+
+# debugging
+EXTRA_CFLAGS	:= -DDEBUG
+cb710-y		+= debug.o
+
+endif
diff -urN -x '.git*' empty/mmc.c cb710/mmc.c
--- empty/mmc.c	1970-01-01 01:00:00.000000000 +0100
+++ cb710/mmc.c	2008-10-29 14:07:52.000000000 +0100
@@ -0,0 +1,845 @@
+/*
+ *  cb710/mmc.c
+ *
+ *  Copyright by Michał Mirosław, 2008
+ *
+ * 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.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include "cb710-mmc.h"
+#include "sgbuf2.h"
+
+static const u8 cb710_clock_divider_log2[8] = {
+/*	1, 2, 4, 8, 16, 32, 128, 512 */
+	0, 1, 2, 3,  4,  5,   7,   9
+};
+#define CB710_MAX_DIVIDER_IDX	\
+	(ARRAY_SIZE(cb710_clock_divider_log2) - 1)
+
+static const u8 cb710_src_freq_mhz[16] = {
+	33, 10, 20, 25, 30, 35, 40, 45,
+	50, 55, 60, 65, 70, 75, 80, 85
+};
+
+#ifdef VERBOSE_DEBUG
+static void verify_serialization(struct mmc_host *mmc,
+	unsigned char *counter, int inc)
+{
+	struct cb710_slot *slot = cb710_mmc_to_slot(mmc);
+	struct cb710_mmc_reader *reader = mmc_priv(mmc);
+	unsigned long flags;
+	int req, ios, cur;
+
+	spin_lock_irqsave(&reader->serialization_lock, flags);
+
+	if (inc)
+		cur = ++*counter;
+	else
+		cur = --*counter;
+	req = reader->active_req;
+	ios = reader->active_ios;
+
+	spin_unlock_irqrestore(&reader->serialization_lock, flags);
+
+	dev_info(cb710_slot_dev(slot),
+		"%s driver; counters now: ios=%d req=%d\n",
+		inc ? "entering" : "leaving", ios, req);
+	WARN_ON(cur > 1);
+}
+#else
+#define verify_serialization(h, c, i)
+#endif
+
+static void cb710_mmc_set_clock(struct mmc_host *mmc, int hz)
+{
+	struct cb710_slot *slot = cb710_mmc_to_slot(mmc);
+	struct pci_dev *pdev = cb710_slot_to_chip(slot)->pdev;
+	u32 src_freq_idx;
+	u32 divider_idx;
+	int src_hz;
+
+	/* this is magic, unverifiable for me, unless I get
+	 * MMC card with cables connected to bus signals */
+	pci_read_config_dword(pdev, 0x48, &src_freq_idx);
+	src_freq_idx = (src_freq_idx >> 16) & 0xF;
+	src_hz = cb710_src_freq_mhz[src_freq_idx] * 1000000;
+
+	for (divider_idx = 0; divider_idx < CB710_MAX_DIVIDER_IDX; ++divider_idx) {
+		if (hz >= src_hz >> cb710_clock_divider_log2[divider_idx])
+			break;
+	}
+
+	if (src_freq_idx)
+		divider_idx |= 0x8;
+
+	cb710_pci_update_config_reg(pdev, 0x40, ~0xF0000000, divider_idx << 28);
+
+	dev_dbg(cb710_slot_dev(slot),
+		"clock set to %d Hz, wanted %d Hz; flag = %d\n",
+		src_hz >> cb710_clock_divider_log2[divider_idx & 7],
+		hz, (divider_idx & 8) != 0);
+}
+
+static void __cb710_mmc_enable_irq(struct cb710_slot *slot, int enable)
+{
+	switch (enable) {
+	case 1:	/* normal enable + test bits */
+		/* look like interrupt is fired whenever
+		 * WORD[0x0C] & WORD[0x10] != 0;
+		 * let's verify it...
+		 *** bit 7 port 0x0D seems to be global interrupt enable
+		 */
+		cb710_write_port_8(slot, CB710_MMC_IRQ_ENABLE0_PORT, ~0x40);
+		cb710_write_port_8(slot, CB710_MMC_IRQ_ENABLE1_PORT,
+			CB710_MMC_IE_IRQ_ENABLE|CB710_MMC_IE_CARD_INSERTION_STATUS);
+		break;
+	case 2:	/* normal enable w/o test bits (to limit test IRQs) */
+		cb710_write_port_8(slot, CB710_MMC_IRQ_ENABLE0_PORT, 0x00);
+		cb710_write_port_8(slot, CB710_MMC_IRQ_ENABLE1_PORT,
+			CB710_MMC_IE_IRQ_ENABLE|CB710_MMC_IE_CARD_INSERTION_STATUS);
+		break;
+	case 0:
+		cb710_write_port_8(slot, CB710_MMC_IRQ_ENABLE0_PORT, 0);
+		cb710_write_port_8(slot, CB710_MMC_IRQ_ENABLE1_PORT, 0);
+		break;
+	}
+}
+
+static void cb710_mmc_enable_irq(struct cb710_slot *slot, int enable)
+{
+	struct cb710_mmc_reader *reader = mmc_priv(cb710_slot_to_mmc(slot));
+	unsigned long flags;
+
+	spin_lock_irqsave(&reader->irq_lock, flags);
+	/* this is the only thing this lock protects */
+	__cb710_mmc_enable_irq(slot, enable);
+	spin_unlock_irqrestore(&reader->irq_lock, flags);
+}
+
+static void cb710_mmc_reset_events(struct cb710_slot *slot)
+{
+	cb710_write_port_8(slot, CB710_MMC_STATUS0_PORT, 0xFF);
+	cb710_write_port_8(slot, CB710_MMC_STATUS1_PORT, 0xFF);
+	cb710_write_port_8(slot, CB710_MMC_STATUS2_PORT, 0xFF);
+}
+
+static int cb710_mmc_is_card_inserted(struct cb710_slot *slot)
+{
+	return cb710_read_port_8(slot, CB710_MMC_STATUS3_PORT)
+		& CB710_MMC_S3_CARD_DETECTED;
+}
+
+static void cb710_mmc_enable_4bit_data(struct cb710_slot *slot, int enable)
+{
+	dev_dbg(cb710_slot_dev(slot), "configuring %d-data-line%s mode\n",
+		enable ? 4 : 1, enable ? "s" : "");
+	if (enable)
+		cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT,
+			CB710_MMC_C1_4BIT_DATA_BUS, 0);
+	else
+		cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT,
+			0, CB710_MMC_C1_4BIT_DATA_BUS);
+}
+
+static int cb710_check_event(struct cb710_slot *slot, u8 what)
+{
+	u16 status;
+
+	status = cb710_read_port_16(slot, CB710_MMC_STATUS_PORT);
+
+	if (status & CB710_MMC_S0_FIFO_UNDERFLOW) {
+		/* it is just a guess, so log it */
+		dev_dbg(cb710_slot_dev(slot),
+			"CHECK : ignoring bit 6 in status %04X\n", status);
+		cb710_write_port_8(slot, CB710_MMC_STATUS0_PORT,
+			CB710_MMC_S0_FIFO_UNDERFLOW);
+		status &= ~CB710_MMC_S0_FIFO_UNDERFLOW;
+	}
+
+	if (status & CB710_MMC_STATUS_ERROR_EVENTS) {
+		dev_dbg(cb710_slot_dev(slot),
+			"CHECK : returning EIO on status %04X\n", status);
+		cb710_write_port_8(slot, CB710_MMC_STATUS0_PORT, status & 0xFF);
+		cb710_write_port_8(slot, CB710_MMC_STATUS1_PORT,
+			CB710_MMC_S1_RESET);
+		return -EIO;
+	}
+
+	/* 'what' is a bit in MMC_STATUS1 */
+	if ((status >> 8) & what) {
+		cb710_write_port_8(slot, CB710_MMC_STATUS1_PORT, what);
+		return 1;
+	}
+
+	return 0;
+}
+
+static int cb710_wait_for_event(struct cb710_slot *slot, u8 what)
+{
+	int err = 0;
+	unsigned limit = 2000000;	/* FIXME: real timeout */
+
+#ifdef DEBUG
+	u32 e, x;
+	e = cb710_read_port_32(slot, CB710_MMC_STATUS_PORT);
+#endif
+
+	while (!(err = cb710_check_event(slot, what))) {
+		if (!--limit) {
+			struct cb710_chip *chip = cb710_slot_to_chip(slot);
+			cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+			err = -ETIMEDOUT;
+			break;
+		}
+		udelay(1);
+	}
+
+#ifdef DEBUG
+	x = cb710_read_port_32(slot, CB710_MMC_STATUS_PORT);
+
+	limit = 2000000 - limit;
+	if (limit > 100)
+		dev_dbg(cb710_slot_dev(slot),
+			"WAIT10: waited %d loops, what %d, entry val %08X, exit val %08X\n",
+			limit, what, e, x);
+#endif
+	return err < 0 ? err : 0;
+}
+
+
+static int cb710_wait_while_busy(struct cb710_slot *slot, uint8_t mask)
+{
+	unsigned limit = 500000;	/* FIXME: real timeout */
+	int err = 0;
+
+#ifdef DEBUG
+	u32 e, x;
+	e = cb710_read_port_32(slot, CB710_MMC_STATUS_PORT);
+#endif
+
+	while (cb710_read_port_8(slot, CB710_MMC_STATUS2_PORT) & mask) {
+		if (!--limit) {
+			struct cb710_chip *chip = cb710_slot_to_chip(slot);
+			cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+			err = -ETIMEDOUT;
+			break;
+		}
+		udelay(1);
+	}
+
+#ifdef DEBUG
+	x = cb710_read_port_32(slot, CB710_MMC_STATUS_PORT);
+
+	limit = 500000 - limit;
+	if (limit > 100)
+		dev_dbg(cb710_slot_dev(slot),
+			"WAIT12: waited %d loops, mask %02X, entry val %08X, exit val %08X\n",
+			limit, mask, e, x);
+#endif
+	return 0;
+}
+
+static void cb710_mmc_set_transfer_size(struct cb710_slot *slot,
+	size_t count, size_t blocksize)
+{
+	cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
+	cb710_write_port_32(slot, CB710_MMC_TRANSFER_SIZE_PORT,
+		((count - 1) << 16)|(blocksize - 1));
+
+	dev_vdbg(cb710_slot_dev(slot), "set up for %d block%s of %d bytes\n",
+		count, count == 1 ? "" : "s", blocksize);
+}
+
+static void cb710_mmc_fifo_hack(struct cb710_slot *slot)
+{
+	/* without this, received data is prepended with 8-bytes of zeroes */
+	u32 r1, r2;
+	int ok = 0;
+
+	r1 = cb710_read_port_32(slot, CB710_MMC_DATA_PORT);
+	r2 = cb710_read_port_32(slot, CB710_MMC_DATA_PORT);
+	if (cb710_read_port_8(slot, CB710_MMC_STATUS0_PORT)
+	    & CB710_MMC_S0_FIFO_UNDERFLOW) {
+		cb710_write_port_8(slot, CB710_MMC_STATUS0_PORT,
+			CB710_MMC_S0_FIFO_UNDERFLOW);
+		ok = 1;
+	}
+
+	dev_dbg(cb710_slot_dev(slot),
+		"FIFO-read-hack: expected STATUS0 bit was %s\n",
+		ok ? "set." : "NOT SET!");
+	dev_dbg(cb710_slot_dev(slot),
+		"FIFO-read-hack: dwords ignored: %08X %08X - %s\n",
+		r1, r2, (r1|r2) ? "BAD (NOT ZERO)!" : "ok");
+}
+
+static int cb710_mmc_receive_pio(struct cb710_slot *slot,
+	struct sg_mapping_iter *miter, size_t dw_count)
+{
+	if (!(cb710_read_port_8(slot, CB710_MMC_STATUS2_PORT) & CB710_MMC_S2_FIFO_READY)) {
+		int err = cb710_wait_for_event(slot,
+			CB710_MMC_S1_PIO_TRANSFER_DONE);
+		if (err)
+			return err;
+	}
+
+	sg_dwiter_write_from_io_le(miter,
+		slot->iobase + CB710_MMC_DATA_PORT, dw_count);
+
+	return 0;
+}
+
+static bool cb710_is_transfer_size_supported(struct mmc_data *data)
+{
+	return !(data->blksz & 15 && (data->blocks != 1 || data->blksz != 8));
+}
+
+static int cb710_mmc_receive(struct cb710_slot *slot, struct mmc_data *data)
+{
+	struct sg_mapping_iter miter;
+	size_t len, blocks = data->blocks;
+	int err = 0;
+
+	/* TODO: I don't know how/if the hardware handles non-16B-boundary blocks
+	 * except single 8B block */
+	if (unlikely(data->blksz & 15 && (data->blocks != 1 || data->blksz != 8)))
+		return -EINVAL;
+
+	sg_miter_start(&miter, data->sg, data->sg_len, 0);
+
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG2_PORT,
+		15, CB710_MMC_C2_READ_PIO_SIZE_MASK);
+
+	cb710_mmc_fifo_hack(slot);
+
+	while (blocks-- > 0) {
+		len = data->blksz;
+
+		while (len >= 16) {
+			err = cb710_mmc_receive_pio(slot, &miter, 4);
+			if (err)
+				goto out;
+			len -= 16;
+		}
+
+		if (!len)
+			continue;
+
+		cb710_modify_port_8(slot, CB710_MMC_CONFIG2_PORT,
+			len - 1, CB710_MMC_C2_READ_PIO_SIZE_MASK);
+
+		len = (len >= 8) ? 4 : 2;
+		err = cb710_mmc_receive_pio(slot, &miter, len);
+		if (err)
+			goto out;
+	}
+out:
+	sg_miter_stop_writing(&miter);
+	return err;
+}
+
+static int cb710_mmc_send(struct cb710_slot *slot, struct mmc_data *data)
+{
+	struct sg_mapping_iter miter;
+	size_t len, blocks = data->blocks;
+	int err = 0;
+
+	/* TODO: I don't know how/if the hardware handles multiple
+	 * non-16B-boundary blocks */
+	if (unlikely(data->blocks > 1 && data->blksz & 15))
+		return -EINVAL;
+
+	sg_miter_start(&miter, data->sg, data->sg_len, 0);
+
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG2_PORT,
+		0, CB710_MMC_C2_READ_PIO_SIZE_MASK);
+
+	while (blocks-- > 0) {
+		len = (data->blksz + 15) >> 4;
+		do {
+			if (!(cb710_read_port_8(slot, CB710_MMC_STATUS2_PORT)
+			    & CB710_MMC_S2_FIFO_EMPTY)) {
+				err = cb710_wait_for_event(slot,
+					CB710_MMC_S1_PIO_TRANSFER_DONE);
+				if (err)
+					goto out;
+			}
+			sg_dwiter_read_to_io_le(&miter,
+				slot->iobase + CB710_MMC_DATA_PORT, 4);
+		} while (--len);
+	}
+out:
+	sg_miter_stop(&miter);
+	return err;
+}
+
+static u16 cb710_encode_cmd_flags(struct cb710_mmc_reader *reader,
+	struct mmc_command *cmd)
+{
+	unsigned int flags = cmd->flags;
+	u16 cb_flags = 0;
+
+	/* Windows driver returned 0 for commands for which no response
+	 * is expected. It happened that there were only two such commands
+	 * used: MMC_GO_IDLE_STATE and MMC_GO_INACTIVE_STATE so it might
+	 * as well be a bug in that driver.
+	 */
+
+	switch (flags & MMC_CMD_MASK) {
+	case MMC_CMD_AC:	cb_flags = CB710_MMC_CMD_AC;	break;
+	case MMC_CMD_ADTC:	cb_flags = CB710_MMC_CMD_ADTC;	break;
+	case MMC_CMD_BC:	cb_flags = CB710_MMC_CMD_BC;	break;
+	case MMC_CMD_BCR:	cb_flags = CB710_MMC_CMD_BCR;	break;
+	}
+
+	if (flags & MMC_RSP_BUSY)
+		cb_flags |= CB710_MMC_RSP_BUSY;
+
+	cb_flags |= cmd->opcode << CB710_MMC_CMD_CODE_SHIFT;
+
+#if 0
+	/* original driver set bit 14 for MMC/SD application
+	 * commands. There's no difference 'on the wire' and
+	 * it apparently works without it anyway.
+	 */
+	if (flags & MMC_CMD_IS_APPCMD)
+		cb_flags |= 0x4000;
+#endif
+
+	if (cmd->data && (cmd->data->flags & MMC_DATA_READ))
+		cb_flags |= CB710_MMC_DATA_READ;
+
+	if (flags & MMC_RSP_PRESENT) {
+		/* Windows driver set 01 at bits 4,3 except for
+		 * MMC_SET_BLOCKLEN where it set 10. Maybe the
+		 * hardware can do something special about this
+		 * command? The original driver looks buggy/incomplete
+		 * anyway so we ignore this for now.
+		 *
+		 * I assume that 00 here means no response is expected.
+		 */
+		cb_flags |= CB710_MMC_RSP_PRESENT;
+
+		if (flags & MMC_RSP_136)
+			cb_flags |= CB710_MMC_RSP_136;
+		if (!(flags & MMC_RSP_CRC))
+			cb_flags |= CB710_MMC_RSP_NO_CRC;
+	}
+
+	return cb_flags;
+}
+
+static void cb710_receive_response(struct cb710_slot *slot,
+	struct mmc_command *cmd)
+{
+	unsigned rsp_opcode, wanted_opcode;
+
+	/* Looks like final byte with CRC is always stripped (like SDHCI) */
+	if (cmd->flags & MMC_RSP_136) {
+		u32 resp[4];
+
+		resp[0] = cb710_read_port_32(slot, CB710_MMC_RESPONSE3_PORT);
+		resp[1] = cb710_read_port_32(slot, CB710_MMC_RESPONSE2_PORT);
+		resp[2] = cb710_read_port_32(slot, CB710_MMC_RESPONSE1_PORT);
+		resp[3] = cb710_read_port_32(slot, CB710_MMC_RESPONSE0_PORT);
+		rsp_opcode = resp[0] >> 24;
+
+		cmd->resp[0] = (resp[0] << 8)|(resp[1] >> 24);
+		cmd->resp[1] = (resp[1] << 8)|(resp[2] >> 24);
+		cmd->resp[2] = (resp[2] << 8)|(resp[3] >> 24);
+		cmd->resp[3] = (resp[3] << 8);
+	} else {
+		rsp_opcode = cb710_read_port_32(slot, CB710_MMC_RESPONSE1_PORT) & 0x3F;
+		cmd->resp[0] = cb710_read_port_32(slot, CB710_MMC_RESPONSE0_PORT);
+	}
+
+	wanted_opcode = (cmd->flags & MMC_RSP_OPCODE) ? cmd->opcode : 0x3F;
+	if (rsp_opcode != wanted_opcode)
+		cmd->error = -EILSEQ;
+}
+
+static int cb710_mmc_transfer_data(struct cb710_slot *slot,
+	struct mmc_data *data)
+{
+	int error, to;
+
+	if (data->flags & MMC_DATA_READ)
+		error = cb710_mmc_receive(slot, data);
+	else
+		error = cb710_mmc_send(slot, data);
+
+	to = cb710_wait_for_event(slot, CB710_MMC_S1_DATA_TRANSFER_DONE);
+	if (!error)
+		error = to;
+
+	if (!error)
+		data->bytes_xfered = data->blksz * data->blocks;
+	return error;
+}
+
+static int cb710_mmc_command(struct mmc_host *mmc, struct mmc_command *cmd)
+{
+	struct cb710_slot *slot = cb710_mmc_to_slot(mmc);
+	struct cb710_mmc_reader *reader = mmc_priv(mmc);
+	struct mmc_data *data = cmd->data;
+
+	u16 cb_cmd = cb710_encode_cmd_flags(reader, cmd);
+	dev_dbg(cb710_slot_dev(slot), "cmd request: 0x%04X\n", cb_cmd);
+
+	if (data) {
+		if (!cb710_is_transfer_size_supported(data)) {
+			data->error = -EINVAL;
+			return -1;
+		}
+		cb710_mmc_set_transfer_size(slot, data->blocks, data->blksz);
+	}
+
+	cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20|CB710_MMC_S2_BUSY_10);
+	cb710_write_port_16(slot, CB710_MMC_CMD_TYPE_PORT, cb_cmd);
+	cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
+	cb710_write_port_32(slot, CB710_MMC_CMD_PARAM_PORT, cmd->arg);
+	cb710_mmc_reset_events(slot);
+	cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG0_PORT, 0x01, 0);
+
+	cmd->error = cb710_wait_for_event(slot, CB710_MMC_S1_COMMAND_SENT);
+	if (cmd->error)
+		return -1;
+
+	if (cmd->flags & MMC_RSP_PRESENT) {
+		cb710_receive_response(slot, cmd);
+		if (cmd->error)
+			return -1;
+	}
+
+	if (data)
+		data->error = cb710_mmc_transfer_data(slot, data);
+	return 0;
+}
+
+static void cb710_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+	struct cb710_slot *slot = cb710_mmc_to_slot(mmc);
+	struct cb710_mmc_reader *reader = mmc_priv(mmc);
+
+	verify_serialization(mmc, &reader->active_req, 1);
+
+	WARN_ON(reader->mrq != NULL);
+
+	reader->mrq = mrq;
+	cb710_mmc_enable_irq(slot, 1);
+
+	if (cb710_mmc_is_card_inserted(slot)) {
+		if (!cb710_mmc_command(mmc, mrq->cmd) && mrq->stop)
+			cb710_mmc_command(mmc, mrq->stop);
+		mdelay(1);
+	} else {
+		mrq->cmd->error = -ENOMEDIUM;
+	}
+
+	tasklet_schedule(&reader->finish_req_tasklet);
+
+	verify_serialization(mmc, &reader->active_req, 0);
+}
+
+static int cb710_mmc_powerup(struct cb710_slot *slot)
+{
+	struct cb710_chip *chip = cb710_slot_to_chip(slot);
+	int err;
+
+	/* a lot of magic; see comment in cb710_mmc_set_clock() */
+	dev_dbg(cb710_slot_dev(slot), "bus powerup\n");
+	cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+	err = cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
+	if (unlikely(err))
+		return err;
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT, 0x80, 0);
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG3_PORT, 0x80, 0);
+	cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+	mdelay(1);
+	dev_dbg(cb710_slot_dev(slot), "after delay 1\n");
+	cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+	err = cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
+	if (unlikely(err))
+		return err;
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT, 0x09, 0);
+	cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+	mdelay(1);
+	dev_dbg(cb710_slot_dev(slot), "after delay 2\n");
+	cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+	err = cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
+	if (unlikely(err))
+		return err;
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT, 0, 0x08);
+	cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+	mdelay(2);
+	dev_dbg(cb710_slot_dev(slot), "after delay 3\n");
+	cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG0_PORT, 0x06, 0);
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT, 0x70, 0);
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG2_PORT, 0x80, 0);
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG3_PORT, 0x03, 0);
+	cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+	err = cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
+	if (unlikely(err))
+		return err;
+	/* This port behaves weird: quick byte reads of 0x08,0x09 return
+	 * 0xFF,0x00 after writing 0xFFFF to 0x08; it works correctly when
+	 * read/written from userspace...  What am I missing here?
+	 * (it doesn't depend on write-to-read delay)  --mq */
+	cb710_write_port_16(slot, CB710_MMC_CONFIGB_PORT, 0xFFFF);
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG0_PORT, 0x06, 0);
+	cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+	dev_dbg(cb710_slot_dev(slot), "bus powerup finished\n");
+
+	return cb710_check_event(slot, 0);
+}
+
+static void cb710_mmc_powerdown(struct cb710_slot *slot)
+{
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT, 0, 0x81);
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG3_PORT, 0, 0x80);
+}
+
+static void cb710_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+	struct cb710_slot *slot = cb710_mmc_to_slot(mmc);
+	struct cb710_mmc_reader *reader = mmc_priv(mmc);
+	int err;
+
+	verify_serialization(mmc, &reader->active_ios, 1);
+
+	cb710_mmc_set_clock(mmc, ios->clock);
+
+	if (!cb710_mmc_is_card_inserted(slot)) {
+		dev_dbg(cb710_slot_dev(slot),
+			"no card inserted - ignoring bus powerup request\n");
+		ios->power_mode = MMC_POWER_OFF;
+	}
+
+	if (ios->power_mode != reader->last_power_mode)
+	switch (ios->power_mode) {
+	case MMC_POWER_ON:
+		err = cb710_mmc_powerup(slot);
+		if (err) {
+			dev_warn(cb710_slot_dev(slot),
+				"powerup failed (%d)- retrying\n", err);
+			cb710_mmc_powerdown(slot);
+			udelay(1);
+			err = cb710_mmc_powerup(slot);
+			if (err)
+				dev_warn(cb710_slot_dev(slot),
+					"powerup retry failed (%d) - expect errors\n",
+					err);
+		}
+		reader->last_power_mode = MMC_POWER_ON;
+		break;
+	case MMC_POWER_OFF:
+		cb710_mmc_powerdown(slot);
+		reader->last_power_mode = MMC_POWER_OFF;
+		break;
+	case MMC_POWER_UP:
+	default:
+		/* ignore */;
+	}
+
+	cb710_mmc_enable_4bit_data(slot, ios->bus_width != MMC_BUS_WIDTH_1);
+
+	cb710_mmc_enable_irq(slot, 1);
+
+	verify_serialization(mmc, &reader->active_ios, 0);
+}
+
+static int cb710_mmc_get_ro(struct mmc_host *mmc)
+{
+	struct cb710_slot *slot = cb710_mmc_to_slot(mmc);
+
+	return cb710_read_port_8(slot, CB710_MMC_STATUS3_PORT)
+		& CB710_MMC_S3_WRITE_PROTECTED;
+}
+
+static int cb710_mmc_irq_handler(struct cb710_slot *slot)
+{
+	struct mmc_host *mmc = cb710_slot_to_mmc(slot);
+	struct cb710_mmc_reader *reader = mmc_priv(mmc);
+	u32 status, config1, config2, irqen;
+
+	status = cb710_read_port_32(slot, CB710_MMC_STATUS_PORT);
+	irqen = cb710_read_port_32(slot, CB710_MMC_IRQ_ENABLE_PORT);
+	config2 = cb710_read_port_32(slot, CB710_MMC_CONFIGB_PORT);
+	config1 = cb710_read_port_32(slot, CB710_MMC_CONFIG_PORT);
+
+	dev_dbg(cb710_slot_dev(slot), "interrupt; status: %08X, "
+		"ie: %08X, c2: %08X, c1: %08X\n",
+		status, irqen, config2, config1);
+
+	if (status & (CB710_MMC_S1_CARD_CHANGED << 8)) {
+		/* ack the event */
+		cb710_write_port_8(slot, CB710_MMC_STATUS1_PORT,
+			CB710_MMC_S1_CARD_CHANGED);
+		if ((irqen & CB710_MMC_IE_CISTATUS_MASK)
+		    == CB710_MMC_IE_CISTATUS_MASK)
+			mmc_detect_change(mmc, HZ/5);
+	} else {
+		dev_dbg(cb710_slot_dev(slot), "unknown interrupt (test)\n");
+		spin_lock(&reader->irq_lock);
+		__cb710_mmc_enable_irq(slot, 2);
+		spin_unlock(&reader->irq_lock);
+	}
+
+	return 1;
+}
+
+static void cb710_mmc_finish_request_tasklet(unsigned long data)
+{
+	struct mmc_host *mmc = (void *)data;
+	struct cb710_mmc_reader *reader = mmc_priv(mmc);
+	struct mmc_request *mrq = reader->mrq;
+
+	reader->mrq = NULL;
+	mmc_request_done(mmc, mrq);
+}
+
+static const struct mmc_host_ops cb710_mmc_host = {
+	.request = cb710_mmc_request,
+	.set_ios = cb710_mmc_set_ios,
+	.get_ro = cb710_mmc_get_ro
+};
+
+#ifdef CONFIG_PM
+
+static int cb710_mmc_suspend(struct platform_device *pdev, pm_message_t state)
+{
+	struct cb710_slot *slot = cb710_pdev_to_slot(pdev);
+	struct mmc_host *mmc = cb710_slot_to_mmc(slot);
+	int err;
+
+	err = mmc_suspend_host(mmc, state);
+	if (err)
+		return err;
+
+	cb710_mmc_enable_irq(slot, 0);
+	return 0;
+}
+
+static int cb710_mmc_resume(struct platform_device *pdev)
+{
+	struct cb710_slot *slot = cb710_pdev_to_slot(pdev);
+	struct mmc_host *mmc = cb710_slot_to_mmc(slot);
+
+	cb710_mmc_enable_irq(slot, 0);
+
+	return mmc_resume_host(mmc);
+}
+
+#endif /* CONFIG_PM */
+
+static int __devinit cb710_mmc_init(struct platform_device *pdev)
+{
+	struct cb710_slot *slot = cb710_pdev_to_slot(pdev);
+	struct cb710_chip *chip = cb710_slot_to_chip(slot);
+	struct mmc_host *mmc;
+	struct cb710_mmc_reader *reader;
+	int err;
+	u32 val;
+
+	mmc = mmc_alloc_host(sizeof(*reader), cb710_slot_dev(slot));
+	if (!mmc)
+		return -ENOMEM;
+
+	dev_set_drvdata(&pdev->dev, mmc);
+
+	/* harmless (maybe) magic */
+	pci_read_config_dword(chip->pdev, 0x48, &val);
+	val = cb710_src_freq_mhz[(val >> 16) & 0xF];
+	dev_dbg(cb710_slot_dev(slot), "source frequency: %dMHz\n", val);
+	val *= 1000000;
+
+	mmc->ops = &cb710_mmc_host;
+	mmc->f_max = val;
+	mmc->f_min = val >> cb710_clock_divider_log2[CB710_MAX_DIVIDER_IDX];
+	mmc->ocr_avail = MMC_VDD_32_33|MMC_VDD_33_34|MMC_VDD_34_35;
+	mmc->caps = MMC_CAP_4_BIT_DATA;
+
+	reader = mmc_priv(mmc);
+
+	tasklet_init(&reader->finish_req_tasklet,
+		cb710_mmc_finish_request_tasklet, (unsigned long)mmc);
+	spin_lock_init(&reader->irq_lock);
+#ifdef VERBOSE_DEBUG
+	spin_lock_init(&reader->serialization_lock);
+#endif
+	cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+
+	cb710_mmc_enable_irq(slot, 0);
+	cb710_set_irq_handler(slot, cb710_mmc_irq_handler);
+
+	err = mmc_add_host(mmc);
+	if (likely(!err)) {
+		dev_dbg(cb710_slot_dev(slot), "mmc_hostname is %s\n",
+			mmc_hostname(mmc));
+		return 0;
+	}
+
+	dev_dbg(cb710_slot_dev(slot), "mmc_add_host() failed: %d\n", err);
+
+	mmc_free_host(mmc);
+	return err;
+}
+
+static int __devexit cb710_mmc_exit(struct platform_device *pdev)
+{
+	struct cb710_slot *slot = cb710_pdev_to_slot(pdev);
+	struct mmc_host *mmc = cb710_slot_to_mmc(slot);
+	struct cb710_mmc_reader *reader = mmc_priv(mmc);
+
+	mmc_remove_host(mmc);
+
+	/* XXX what if IRQ arrives now? */
+
+	cb710_mmc_enable_irq(slot, 0);
+	cb710_set_irq_handler(slot, NULL);
+
+	/* clear config ports - just in case */
+	cb710_write_port_32(slot, CB710_MMC_CONFIG_PORT, 0);
+	cb710_write_port_16(slot, CB710_MMC_CONFIGB_PORT, 0);
+
+	tasklet_kill(&reader->finish_req_tasklet);
+
+	mmc_free_host(mmc);
+	return 0;
+}
+
+static struct platform_driver cb710_mmc_driver = {
+	.driver.name = "cb710-mmc",
+	.probe = cb710_mmc_init,
+	.remove = __devexit_p(cb710_mmc_exit),
+#ifdef CONFIG_PM
+	.suspend = cb710_mmc_suspend,
+	.resume = cb710_mmc_resume,
+#endif
+};
+
+static int __init cb710_mmc_init_module(void)
+{
+	return platform_driver_register(&cb710_mmc_driver);
+}
+
+static void __exit cb710_mmc_cleanup_module(void)
+{
+	platform_driver_unregister(&cb710_mmc_driver);
+}
+
+module_init(cb710_mmc_init_module);
+module_exit(cb710_mmc_cleanup_module);
+
+MODULE_AUTHOR("Michał Mirosław <mirq-linux@rere.qmqm.pl>");
+MODULE_DESCRIPTION("ENE CB710 memory card reader driver - MMC/SD part");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:cb710-mmc");
diff -urN -x '.git*' empty/sgbuf2.c cb710/sgbuf2.c
--- empty/sgbuf2.c	1970-01-01 01:00:00.000000000 +0100
+++ cb710/sgbuf2.c	2008-10-29 09:51:07.000000000 +0100
@@ -0,0 +1,113 @@
+/*
+ *  cb710/sgbuf2.c
+ *
+ *  Copyright by Michał Mirosław, 2008
+ *
+ * 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.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include "sgbuf2.h"
+
+static bool sg_dwiter_next(struct sg_mapping_iter *miter)
+{
+	if (sg_miter_next(miter)) {
+		miter->consumed = 0;
+		return true;
+	} else
+		return false;
+}
+
+static bool sg_dwiter_is_at_end(struct sg_mapping_iter *miter)
+{
+	return miter->length == miter->consumed && !sg_dwiter_next(miter);
+}
+
+static uint32_t sg_dwiter_read_buffer(struct sg_mapping_iter *miter)
+{
+	size_t len, left = 4;
+	uint32_t data;
+	void *addr = &data;
+
+	do {
+		len = min(miter->length - miter->consumed, left);
+		memcpy(addr, miter->addr + miter->consumed, len);
+		miter->consumed += len;
+		left -= len;
+		if (!left)
+			return data;
+		addr += len;
+	} while (sg_dwiter_next(miter));
+
+	memset(addr, 0, left);
+	return data;
+}
+
+static bool sg_dwiter_get_next_block(struct sg_mapping_iter *miter, uint32_t **ptr)
+{
+	size_t len;
+
+	if (sg_dwiter_is_at_end(miter))
+		return true;
+
+	len = miter->length - miter->consumed;
+
+	/* optimize for common case when data is of enough length and
+	 * properly aligned (TODO: this can be optimized for archs that
+	 * can do unaligned accesses) */
+	if (likely(len >= 4 && !((unsigned)(miter->addr + miter->consumed) & 3))) {
+		*ptr = miter->addr + miter->consumed;
+		miter->consumed += 4;
+		return true;
+	}
+
+	return false;
+}
+
+uint32_t sg_dwiter_read_next_block(struct sg_mapping_iter *miter)
+{
+	uint32_t *ptr = NULL;
+
+	if (likely(sg_dwiter_get_next_block(miter, &ptr)))
+		return ptr ? *ptr : 0;
+
+	return sg_dwiter_read_buffer(miter);
+}
+EXPORT_SYMBOL_GPL(sg_dwiter_read_next_block);
+
+static void sg_dwiter_write_slow(struct sg_mapping_iter *miter, uint32_t data)
+{
+	size_t len, left = 4;
+	void *addr = &data;
+
+	do {
+		len = min(miter->length - miter->consumed, left);
+		memcpy(miter->addr, addr, len);
+		miter->consumed += len;
+		left -= len;
+		if (!left)
+			return;
+		addr += len;
+		flush_kernel_dcache_page(miter->page);
+	} while (sg_dwiter_next(miter));
+}
+
+void sg_dwiter_write_next_block(struct sg_mapping_iter *miter, uint32_t data)
+{
+	uint32_t *ptr = NULL;
+
+	if (likely(sg_dwiter_get_next_block(miter, &ptr))) {
+		if (ptr)
+			*ptr = data;
+		else
+			return;
+	} else
+		sg_dwiter_write_slow(miter, data);
+
+	if (miter->length == miter->consumed)
+		flush_kernel_dcache_page(miter->page);
+}
+EXPORT_SYMBOL_GPL(sg_dwiter_write_next_block);
+
diff -urN -x '.git*' empty/sgbuf2.h cb710/sgbuf2.h
--- empty/sgbuf2.h	1970-01-01 01:00:00.000000000 +0100
+++ cb710/sgbuf2.h	2008-10-29 14:07:42.000000000 +0100
@@ -0,0 +1,49 @@
+/*
+ *  cb710/sgbuf2.h
+ *
+ *  Copyright by Michał Mirosław, 2008
+ *
+ * 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.
+ */
+#ifndef LINUX_CB710_SG_H
+#define LINUX_CB710_SG_H
+
+#include <linux/highmem.h>
+#include <linux/scatterlist.h>
+#include <asm/byteorder.h>
+
+#if defined(VERBOSE_DEBUG)
+#define sg_debug(fmt, arg...) \
+	pr_debug("sg[0x%p]: " fmt, miter, ##arg)
+#else
+#define sg_debug(fmt, arg...) \
+	do { if (0) pr_debug("sg[0x%p]: " fmt, miter, ##arg); } while (0)
+#endif
+
+static inline void sg_miter_stop_writing(struct sg_mapping_iter *miter)
+{
+	if (miter->page)
+		flush_kernel_dcache_page(miter->page);
+	sg_miter_stop(miter);
+}
+
+uint32_t sg_dwiter_read_next_block(struct sg_mapping_iter *miter);
+void sg_dwiter_write_next_block(struct sg_mapping_iter *miter, uint32_t data);
+
+static inline void sg_dwiter_write_from_io_le(struct sg_mapping_iter *miter,
+	void __iomem *port, size_t count)
+{
+	while (count-- > 0)
+		sg_dwiter_write_next_block(miter, cpu_to_le32(ioread32(port)));
+}
+
+static inline void sg_dwiter_read_to_io_le(struct sg_mapping_iter *miter,
+	void __iomem *port, size_t count)
+{
+	while (count-- > 0)
+		iowrite32(le32_to_cpu(sg_dwiter_read_next_block(miter)), port);
+}
+
+#endif /* LINUX_CB710_SG_H */

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

* Re: RFC: Driver for CB710/720 memory card reader (MMC part) - v3
  2008-10-29 14:11               ` RFC: Driver for CB710/720 memory card reader (MMC part) - v3 Michał Mirosław
@ 2008-11-14 21:06                 ` Pierre Ossman
  2009-02-01 18:54                   ` Michał Mirosław
  0 siblings, 1 reply; 21+ messages in thread
From: Pierre Ossman @ 2008-11-14 21:06 UTC (permalink / raw)
  To: Michał Mirosław; +Cc: linux-kernel, Alex Dubov

On Wed, 29 Oct 2008 15:11:47 +0100
Michał Mirosław <mirq-linux@rere.qmqm.pl> wrote:

> Here is the driver for CB710 SD/MMC reader updated to recently released
> kernel version 2.6.27. The code is divided into PCI device driver
> (wrapped that registers three platform devices) and MMC reader driver.
> This idea was taken from TIFM driver.
> 
> Changes from v2:
>  - return EINVAL for unsupported data transfer sizes
>  - use sg_mapping_iter for PIO from/to scatterlist
>  - add slot release function and prevent kobject leak on device registration's
>    error path
> 
> If this code looks reasonably good, then I'll post patches against the
> real kernel tree. The file distribution plan is:
> 

It looks more or less good to go. I have some small notes, and you
probably need to poke some scatterlist folks to okay those changes
(should probably be a separate patch).

> +/* slot port accessors - in case it turns out inX() is all that is needed */
> +#define CB710_PORT_ACCESSORS(t) \
> +static inline void cb710_write_port_##t(struct cb710_slot *slot,	\
> +	unsigned port, u##t value)					\
> +{									\
> +	iowrite##t(value, slot->iobase + port);				\
> +}									\
> +									\
> +static inline u##t cb710_read_port_##t(struct cb710_slot *slot,		\
> +	unsigned port)							\
> +{									\
> +	return ioread##t(slot->iobase + port);				\
> +}									\
> +									\
> +static inline void cb710_modify_port_##t(struct cb710_slot *slot,	\
> +	unsigned port, u##t set, u##t clear)				\
> +{									\
> +	iowrite##t(							\
> +		(ioread##t(slot->iobase + port) & ~clear)|set,		\
> +		slot->iobase + port);					\
> +}
> +
> +CB710_PORT_ACCESSORS(8)
> +CB710_PORT_ACCESSORS(16)
> +CB710_PORT_ACCESSORS(32)

This is clearer than what you had before, but I still think you should
consider using the kernel functions directly.

> +/* helper functions */
> +
> +static inline void cb710_set_irq_handler(struct cb710_slot *slot,
> +	cb710_irq_handler_t handler)
> +{
> +	rcu_assign_pointer(slot->irq_handler, handler);
> +	synchronize_rcu();	/* sync to IRQ handler */
> +}
> +

Why RCU:s? This smells of premature optimisation.

> +/* per-MMC-reader structure */
> +struct cb710_mmc_reader {
> +	struct tasklet_struct finish_req_tasklet;
> +	struct mmc_request *mrq;
> +	spinlock_t irq_lock;
> +	unsigned char last_power_mode;
> +#ifdef VERBOSE_DEBUG
> +	spinlock_t serialization_lock;
> +	unsigned char active_req, active_ios;
> +#endif
> +};

I couldn't find VERBOSE_DEBUG defined somewhere. For ease of use with
future testers, you should connect it to some Kconfig option.

> +static int cb710_suspend(struct pci_dev *pdev, pm_message_t state)
> +{
> +	pci_save_state(pdev);
> +	pci_disable_device(pdev);
> +	if (state.event & PM_EVENT_SLEEP)
> +		pci_set_power_state(pdev, PCI_D3cold);
> +	return 0;
> +}
> +
> +static int cb710_resume(struct pci_dev *pdev)
> +{
> +	pci_set_power_state(pdev, PCI_D0);
> +	pci_restore_state(pdev);
> +	return pcim_enable_device(pdev);
> +}

Free/restore interrupt?

> +/* TODO: move to pci_ids.h before merging upstream */
> +#define PCI_DEVICE_ID_ENE_710_FLASH	0x0510

It's a matter of taste really. Many drivers have their defines in the
driver. Personally I think it is nice to have everything in pci_ids.h,
but it's up to you really.

> +#if 0
> +	/* original driver set bit 14 for MMC/SD application
> +	 * commands. There's no difference 'on the wire' and
> +	 * it apparently works without it anyway.
> +	 */
> +	if (flags & MMC_CMD_IS_APPCMD)
> +		cb_flags |= 0x4000;
> +#endif

As this code is just useless, please clean it out before you submit the
driver.

> +	mmc->ops = &cb710_mmc_host;
> +	mmc->f_max = val;
> +	mmc->f_min = val >> cb710_clock_divider_log2[CB710_MAX_DIVIDER_IDX];
> +	mmc->ocr_avail = MMC_VDD_32_33|MMC_VDD_33_34|MMC_VDD_34_35;

Since you can't change the voltage, this seems wrong. My guess is that
the MMC_VDD_34_35 define should go.

> +static int __devexit cb710_mmc_exit(struct platform_device *pdev)
> +{
> +	struct cb710_slot *slot = cb710_pdev_to_slot(pdev);
> +	struct mmc_host *mmc = cb710_slot_to_mmc(slot);
> +	struct cb710_mmc_reader *reader = mmc_priv(mmc);
> +
> +	mmc_remove_host(mmc);
> +
> +	/* XXX what if IRQ arrives now? */

Most drivers handle this by sticking their head in the sand, but the
proper way is probably to disable the card insertions interrupt before
mmc_remove_host().

(and there should be no other unsolicited interrupts)

Rgds
-- 
     -- Pierre Ossman

  Linux kernel, MMC maintainer        http://www.kernel.org
  rdesktop, core developer          http://www.rdesktop.org

  WARNING: This correspondence is being monitored by the
  Swedish government. Make sure your server uses encryption
  for SMTP traffic and consider using PGP for end-to-end
  encryption.

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

* Re: RFC: Driver for CB710/720 memory card reader (MMC part) - v3
  2008-11-14 21:06                 ` Pierre Ossman
@ 2009-02-01 18:54                   ` Michał Mirosław
  2009-02-21 12:46                     ` Pierre Ossman
  0 siblings, 1 reply; 21+ messages in thread
From: Michał Mirosław @ 2009-02-01 18:54 UTC (permalink / raw)
  To: Pierre Ossman, linux-kernel, Alex Dubov

Hello again,

Sorry for a long delay with my reply.

On Fri, Nov 14, 2008 at 10:06:58PM +0100, Pierre Ossman wrote:
> On Wed, 29 Oct 2008 15:11:47 +0100
> Michał Mirosław <mirq-linux@rere.qmqm.pl> wrote:
> > Here is the driver for CB710 SD/MMC reader updated to recently released
> > kernel version 2.6.27. The code is divided into PCI device driver
> > (wrapped that registers three platform devices) and MMC reader driver.
> > This idea was taken from TIFM driver.
> > 
> > Changes from v2:
> >  - return EINVAL for unsupported data transfer sizes
> >  - use sg_mapping_iter for PIO from/to scatterlist
> >  - add slot release function and prevent kobject leak on device registration's
> >    error path
> > 
> > If this code looks reasonably good, then I'll post patches against the
> > real kernel tree. The file distribution plan is:
> It looks more or less good to go. I have some small notes, and you
> probably need to poke some scatterlist folks to okay those changes
> (should probably be a separate patch).

There's no entry in MAINTAINERS list, but I assume it's Jens Axboe as he's
in the copyright section in scatterlist.c.

> > +/* slot port accessors - in case it turns out inX() is all that is needed */
> > +#define CB710_PORT_ACCESSORS(t) \
> > +static inline void cb710_write_port_##t(struct cb710_slot *slot,	\
> > +	unsigned port, u##t value)					\
> > +{									\
> > +	iowrite##t(value, slot->iobase + port);				\
> > +}									\
> > +									\
> > +static inline u##t cb710_read_port_##t(struct cb710_slot *slot,		\
> > +	unsigned port)							\
> > +{									\
> > +	return ioread##t(slot->iobase + port);				\
> > +}									\
> > +									\
> > +static inline void cb710_modify_port_##t(struct cb710_slot *slot,	\
> > +	unsigned port, u##t set, u##t clear)				\
> > +{									\
> > +	iowrite##t(							\
> > +		(ioread##t(slot->iobase + port) & ~clear)|set,		\
> > +		slot->iobase + port);					\
> > +}
> > +
> > +CB710_PORT_ACCESSORS(8)
> > +CB710_PORT_ACCESSORS(16)
> > +CB710_PORT_ACCESSORS(32)
> This is clearer than what you had before, but I still think you should
> consider using the kernel functions directly.

The really useful one here is cb710_modify_port_*(), read/write are for
consistency. I'd prefer to keep those wrappers because they help to eliminate
some noise from register access logic in the driver.

> > +/* helper functions */
> > +
> > +static inline void cb710_set_irq_handler(struct cb710_slot *slot,
> > +	cb710_irq_handler_t handler)
> > +{
> > +	rcu_assign_pointer(slot->irq_handler, handler);
> > +	synchronize_rcu();	/* sync to IRQ handler */
> > +}
> Why RCU:s? This smells of premature optimisation.

I thought this as the easiest way to assure that on return from
cb710_set_irq_handler() there's no interrupt handler running using the
old slot->irq_handler value.

> > +/* per-MMC-reader structure */
> > +struct cb710_mmc_reader {
> > +	struct tasklet_struct finish_req_tasklet;
> > +	struct mmc_request *mrq;
> > +	spinlock_t irq_lock;
> > +	unsigned char last_power_mode;
> > +#ifdef VERBOSE_DEBUG
> > +	spinlock_t serialization_lock;
> > +	unsigned char active_req, active_ios;
> > +#endif
> > +};
> I couldn't find VERBOSE_DEBUG defined somewhere. For ease of use with
> future testers, you should connect it to some Kconfig option.

This is mostly related to veryfying some assumptions on the MMC core
infrastructure. I can remove this altogether if the calls to driver
->request() and ->set_ios() are really guaranteed to be serialized.

> > +static int cb710_suspend(struct pci_dev *pdev, pm_message_t state)
> > +{
> > +	pci_save_state(pdev);
> > +	pci_disable_device(pdev);
> > +	if (state.event & PM_EVENT_SLEEP)
> > +		pci_set_power_state(pdev, PCI_D3cold);
> > +	return 0;
> > +}
> > +
> > +static int cb710_resume(struct pci_dev *pdev)
> > +{
> > +	pci_set_power_state(pdev, PCI_D0);
> > +	pci_restore_state(pdev);
> > +	return pcim_enable_device(pdev);
> > +}
> Free/restore interrupt?

Hmm. I checked couple random drivers in kernel tree and none (including
MMC host drivers) are touching interrupt allocation in suspend/resume.

> > +#if 0
> > +	/* original driver set bit 14 for MMC/SD application
> > +	 * commands. There's no difference 'on the wire' and
> > +	 * it apparently works without it anyway.
> > +	 */
> > +	if (flags & MMC_CMD_IS_APPCMD)
> > +		cb_flags |= 0x4000;
> > +#endif
> As this code is just useless, please clean it out before you submit the
> driver.

Will turn this into pure comment - just to remember.

> > +	mmc->ops = &cb710_mmc_host;
> > +	mmc->f_max = val;
> > +	mmc->f_min = val >> cb710_clock_divider_log2[CB710_MAX_DIVIDER_IDX];
> > +	mmc->ocr_avail = MMC_VDD_32_33|MMC_VDD_33_34|MMC_VDD_34_35;
> Since you can't change the voltage, this seems wrong. My guess is that
> the MMC_VDD_34_35 define should go.

That's what the original driver advertises. I can't verify the exact
voltage that is supplied to the card, so I'll assume that's 3.3V and
remove MMC_VDD_34_35 as you suggest.

> > +static int __devexit cb710_mmc_exit(struct platform_device *pdev)
> > +{
> > +	struct cb710_slot *slot = cb710_pdev_to_slot(pdev);
> > +	struct mmc_host *mmc = cb710_slot_to_mmc(slot);
> > +	struct cb710_mmc_reader *reader = mmc_priv(mmc);
> > +
> > +	mmc_remove_host(mmc);
> > +
> > +	/* XXX what if IRQ arrives now? */
> 
> Most drivers handle this by sticking their head in the sand, but the
> proper way is probably to disable the card insertions interrupt before
> mmc_remove_host().
> (and there should be no other unsolicited interrupts)

Will try.

Best Regards,
Michał Mirosław


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

* Re: RFC: Driver for CB710/720 memory card reader (MMC part) - v3
  2009-02-01 18:54                   ` Michał Mirosław
@ 2009-02-21 12:46                     ` Pierre Ossman
  2009-05-08 15:16                       ` [PATCH/RFC 2.6.29.2] Driver for CB710/720 memory card reader (MMC part) - v4 Michał Mirosław
  0 siblings, 1 reply; 21+ messages in thread
From: Pierre Ossman @ 2009-02-21 12:46 UTC (permalink / raw)
  To: Michał Mirosław; +Cc: linux-kernel, Alex Dubov

On Sun, 1 Feb 2009 19:54:35 +0100
Michał Mirosław <mirq-linux@rere.qmqm.pl> wrote:

> > > +/* helper functions */
> > > +
> > > +static inline void cb710_set_irq_handler(struct cb710_slot *slot,
> > > +	cb710_irq_handler_t handler)
> > > +{
> > > +	rcu_assign_pointer(slot->irq_handler, handler);
> > > +	synchronize_rcu();	/* sync to IRQ handler */
> > > +}
> > Why RCU:s? This smells of premature optimisation.
> 
> I thought this as the easiest way to assure that on return from
> cb710_set_irq_handler() there's no interrupt handler running using the
> old slot->irq_handler value.
> 

A spinlock will suffice fine for that.

> > > +/* per-MMC-reader structure */
> > > +struct cb710_mmc_reader {
> > > +	struct tasklet_struct finish_req_tasklet;
> > > +	struct mmc_request *mrq;
> > > +	spinlock_t irq_lock;
> > > +	unsigned char last_power_mode;
> > > +#ifdef VERBOSE_DEBUG
> > > +	spinlock_t serialization_lock;
> > > +	unsigned char active_req, active_ios;
> > > +#endif
> > > +};
> > I couldn't find VERBOSE_DEBUG defined somewhere. For ease of use with
> > future testers, you should connect it to some Kconfig option.
> 
> This is mostly related to veryfying some assumptions on the MMC core
> infrastructure. I can remove this altogether if the calls to driver
> ->request() and ->set_ios() are really guaranteed to be serialized.
> 

They are. Changing device settings during an ongoing request is very
undefined.

> > > +static int cb710_resume(struct pci_dev *pdev)
> > > +{
> > > +	pci_set_power_state(pdev, PCI_D0);
> > > +	pci_restore_state(pdev);
> > > +	return pcim_enable_device(pdev);
> > > +}
> > Free/restore interrupt?
> 
> Hmm. I checked couple random drivers in kernel tree and none (including
> MMC host drivers) are touching interrupt allocation in suspend/resume.
> 

Check sdhci.c, it frees the irq and re-requests it during suspend. This
is generally needed when you share interrupts as you need to avoid
trying to handle interrupts before your device has been resumed.

Rgds
-- 
     -- Pierre Ossman

  Linux kernel, MMC maintainer        http://www.kernel.org
  rdesktop, core developer          http://www.rdesktop.org

  WARNING: This correspondence is being monitored by the
  Swedish government. Make sure your server uses encryption
  for SMTP traffic and consider using PGP for end-to-end
  encryption.

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

* [PATCH/RFC 2.6.29.2] Driver for CB710/720 memory card reader (MMC part) - v4
  2009-02-21 12:46                     ` Pierre Ossman
@ 2009-05-08 15:16                       ` Michał Mirosław
  2009-05-22 11:27                         ` Pierre Ossman
  0 siblings, 1 reply; 21+ messages in thread
From: Michał Mirosław @ 2009-05-08 15:16 UTC (permalink / raw)
  To: Pierre Ossman; +Cc: linux-kernel

Here is next version of the CB710 SD/MMC reader driver. As in previous
versions the code is divided in two parts - virtual 'bus' driver that
handles PCI device and registers three new devices one per card reader
type. The other device handles SD/MMC part of the reader.

Key changes from v3:
 - kerneldoc for sg_mapping_iterator extension
 - free/restore IRQ during suspend/resume
 - protect slot->irq_handler by spinlock instead of RCU
 - remove verify_serialization()
 - Kconfig
 - complete patch for linux kernel 2.6.29.2

My patches for sg_mapping_iterator extension went to /dev/null, so I have
incorporated them to the cb710 core driver module for now. Probably someone
with more karma points should look at them (sgbuf2.{c,h}) and push further
if they are of any value to other driver writers.

Signed-off-by: Michał Mirosław <mirq-linux@rere.qmqm.pl>

diff -urN linux-2.6.29.2/drivers/misc/cb710/core.c cb710-tree/drivers/misc/cb710/core.c
--- linux-2.6.29.2/drivers/misc/cb710/core.c	1970-01-01 01:00:00.000000000 +0100
+++ cb710-tree/drivers/misc/cb710/core.c	2009-05-04 20:31:59.000000000 +0200
@@ -0,0 +1,356 @@
+/*
+ *  cb710/core.c
+ *
+ *  Copyright by Michał Mirosław, 2008-2009
+ *
+ * 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.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <linux/spinlock.h>
+#include <linux/idr.h>
+#include <linux/cb710.h>
+
+static DEFINE_IDR(cb710_idr);
+static DEFINE_SPINLOCK(cb710_idr_lock);
+
+void cb710_pci_update_config_reg(struct pci_dev *pdev,
+	int reg, uint32_t mask, uint32_t xor)
+{
+	u32 rval;
+
+	pci_read_config_dword(pdev, reg, &rval);
+	rval = (rval & mask) ^ xor;
+	pci_write_config_dword(pdev, reg, rval);
+}
+EXPORT_SYMBOL_GPL(cb710_pci_update_config_reg);
+
+/* Some magic writes based on Windows driver init code */
+static int __devinit cb710_pci_configure(struct pci_dev *pdev)
+{
+	unsigned int devfn = PCI_DEVFN(PCI_SLOT(pdev->devfn), 0);
+	struct pci_dev *pdev0 = pci_get_slot(pdev->bus, devfn);
+	u32 val;
+
+	cb710_pci_update_config_reg(pdev, 0x48,
+		~0x000000FF, 0x0000003F);
+
+	pci_read_config_dword(pdev, 0x48, &val);
+	if (val & 0x80000000)
+		return 0;
+
+	if (!pdev0)
+		return -ENODEV;
+
+	if (pdev0->vendor == PCI_VENDOR_ID_ENE
+	    && pdev0->device == PCI_DEVICE_ID_ENE_720) {
+		cb710_pci_update_config_reg(pdev0, 0x8C,
+			~0x00F00000, 0x00100000);
+		cb710_pci_update_config_reg(pdev0, 0xB0,
+			~0x08000000, 0x08000000);
+	}
+
+	cb710_pci_update_config_reg(pdev0, 0x8C,
+		~0x00000F00, 0x00000200);
+	cb710_pci_update_config_reg(pdev0, 0x90,
+		~0x00060000, 0x00040000);
+
+	pci_dev_put(pdev0);
+
+	return 0;
+}
+
+static irqreturn_t cb710_irq_handler(int irq, void *data)
+{
+	struct cb710_chip *chip = data;
+	struct cb710_slot *slot = &chip->slot[0];
+	irqreturn_t handled = IRQ_NONE;
+	unsigned nr;
+
+	spin_lock(&chip->irq_lock); /* incl. smp_rmb() */
+
+	for (nr = chip->slots; nr; ++slot, --nr) {
+		cb710_irq_handler_t handler_func = slot->irq_handler;
+		if (handler_func && handler_func(slot))
+			handled = IRQ_HANDLED;
+	}
+
+	spin_unlock(&chip->irq_lock);
+
+	return handled;
+}
+
+static void cb710_release_slot(struct device *dev)
+{
+#ifdef CONFIG_CB710_DEBUG_ASSUMPTIONS
+	struct cb710_slot *slot = cb710_pdev_to_slot(to_platform_device(dev));
+	struct cb710_chip *chip = cb710_slot_to_chip(slot);
+
+	/* slot struct can be freed now */
+	atomic_dec(&chip->slot_refs_count);
+#endif
+}
+
+static int __devinit cb710_register_slot(struct cb710_chip *chip,
+	unsigned slot_mask, unsigned io_offset, const char *name)
+{
+	int nr = chip->slots;
+	struct cb710_slot *slot = &chip->slot[nr];
+	int err;
+
+	dev_dbg(cb710_chip_dev(chip),
+		"register: %s.%d; slot %d; mask %d; IO offset: 0x%02X\n",
+		name, chip->platform_id, nr, slot_mask, io_offset);
+
+	/* slot->irq_handler == NULL here; this needs to be
+	 * seen before platform_device_register() */
+	++chip->slots;
+	smp_wmb();
+
+	slot->iobase = chip->iobase + io_offset;
+	slot->pdev.name = name;
+	slot->pdev.id = chip->platform_id;
+	slot->pdev.dev.parent = &chip->pdev->dev;
+	slot->pdev.dev.release = cb710_release_slot;
+
+	err = platform_device_register(&slot->pdev);
+
+#ifdef CONFIG_CB710_DEBUG_ASSUMPTIONS
+	atomic_inc(&chip->slot_refs_count);
+#endif
+
+	if (err) {
+		/* device_initialize() called from platform_device_register()
+		 * wants this on error path */
+		platform_device_put(&slot->pdev);
+
+		/* slot->irq_handler == NULL here anyway, so no lock needed */
+		--chip->slots;
+		return err;
+	}
+
+	chip->slot_mask |= slot_mask;
+
+	return 0;
+}
+
+static void cb710_unregister_slot(struct cb710_chip *chip,
+	unsigned slot_mask)
+{
+	int nr = chip->slots - 1;
+
+	if (!(chip->slot_mask & slot_mask))
+		return;
+
+	platform_device_unregister(&chip->slot[nr].pdev);
+
+	/* complementary to spin_unlock() in cb710_set_irq_handler() */
+	smp_rmb();
+	BUG_ON(chip->slot[nr].irq_handler != NULL);
+
+	/* slot->irq_handler == NULL here, so no lock needed */
+	--chip->slots;
+	chip->slot_mask &= ~slot_mask;
+}
+
+void cb710_set_irq_handler(struct cb710_slot *slot,
+	cb710_irq_handler_t handler)
+{
+	struct cb710_chip *chip = cb710_slot_to_chip(slot);
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->irq_lock, flags);
+	slot->irq_handler = handler;
+	spin_unlock_irqrestore(&chip->irq_lock, flags);
+}
+EXPORT_SYMBOL_GPL(cb710_set_irq_handler);
+
+#ifdef CONFIG_PM
+
+static int cb710_suspend(struct pci_dev *pdev, pm_message_t state)
+{
+	struct cb710_chip *chip = pci_get_drvdata(pdev);
+
+	free_irq(pdev->irq, chip);
+	pci_save_state(pdev);
+	pci_disable_device(pdev);
+	if (state.event & PM_EVENT_SLEEP)
+		pci_set_power_state(pdev, PCI_D3cold);
+	return 0;
+}
+
+static int cb710_resume(struct pci_dev *pdev)
+{
+	struct cb710_chip *chip = pci_get_drvdata(pdev);
+	int err;
+
+	pci_set_power_state(pdev, PCI_D0);
+	pci_restore_state(pdev);
+	err = pcim_enable_device(pdev);
+	if (err)
+		return err;
+
+	return devm_request_irq(&pdev->dev, pdev->irq,
+		cb710_irq_handler, IRQF_SHARED, KBUILD_MODNAME, chip);
+}
+
+#endif /* CONFIG_PM */
+
+static int __devinit cb710_probe(struct pci_dev *pdev,
+	const struct pci_device_id *ent)
+{
+	struct cb710_chip *chip;
+	unsigned long flags;
+	u32 val;
+	int err;
+	int n = 0;
+
+	err = cb710_pci_configure(pdev);
+	if (err)
+		return err;
+
+	/* this is actually magic... */
+	pci_read_config_dword(pdev, 0x48, &val);
+	if (!(val & 0x80000000)) {
+		pci_write_config_dword(pdev, 0x48, val|0x71000000);
+		pci_read_config_dword(pdev, 0x48, &val);
+	}
+
+	dev_dbg(&pdev->dev, "PCI config[0x48] = 0x%08X\n", val);
+	if (!(val & 0x70000000))
+		return -ENODEV;
+	val = (val >> 28) & 7;
+	if (val & CB710_SLOT_MMC)
+		++n;
+	if (val & CB710_SLOT_MS)
+		++n;
+	if (val & CB710_SLOT_SM)
+		++n;
+
+	chip = devm_kzalloc(&pdev->dev,
+		sizeof(*chip) + n * sizeof(*chip->slot), GFP_KERNEL);
+	if (!chip)
+		return -ENOMEM;
+
+	err = pcim_enable_device(pdev);
+	if (err)
+		return err;
+
+	err = pcim_iomap_regions(pdev, 0x0001, KBUILD_MODNAME);
+	if (err)
+		return err;
+
+	chip->pdev = pdev;
+	chip->iobase = pcim_iomap_table(pdev)[0];
+
+	pci_set_drvdata(pdev, chip);
+
+	err = devm_request_irq(&pdev->dev, pdev->irq,
+		cb710_irq_handler, IRQF_SHARED, KBUILD_MODNAME, chip);
+	if (err)
+		return err;
+
+	do {
+		if (!idr_pre_get(&cb710_idr, GFP_KERNEL))
+			return -ENOMEM;
+
+		spin_lock_irqsave(&cb710_idr_lock, flags);
+		err = idr_get_new(&cb710_idr, chip, &chip->platform_id);
+		spin_unlock_irqrestore(&cb710_idr_lock, flags);
+
+		if (err && err != -EAGAIN)
+			return err;
+	} while (err);
+
+
+	dev_info(&pdev->dev, "id %d, IO 0x%p, IRQ %d\n",
+		chip->platform_id, chip->iobase, pdev->irq);
+
+	if (val & CB710_SLOT_MMC) {	/* MMC/SD slot */
+		err = cb710_register_slot(chip,
+			CB710_SLOT_MMC, 0x00, "cb710-mmc");
+		if (err)
+			return err;
+	}
+
+	if (val & CB710_SLOT_MS) {	/* MemoryStick slot */
+		err = cb710_register_slot(chip,
+			CB710_SLOT_MS, 0x40, "cb710-ms");
+		if (err)
+			goto unreg_mmc;
+	}
+
+	if (val & CB710_SLOT_SM) {	/* SmartMedia slot */
+		err = cb710_register_slot(chip,
+			CB710_SLOT_SM, 0x60, "cb710-sm");
+		if (err)
+			goto unreg_ms;
+	}
+
+	return 0;
+unreg_ms:
+	cb710_unregister_slot(chip, CB710_SLOT_MS);
+unreg_mmc:
+	cb710_unregister_slot(chip, CB710_SLOT_MMC);
+
+#ifdef CONFIG_CB710_DEBUG_ASSUMPTIONS
+	BUG_ON(atomic_read(&chip->slot_refs_count) != 0);
+#endif
+	return err;
+}
+
+static void __devexit cb710_remove_one(struct pci_dev *pdev)
+{
+	struct cb710_chip *chip = pci_get_drvdata(pdev);
+	unsigned long flags;
+
+	cb710_unregister_slot(chip, CB710_SLOT_SM);
+	cb710_unregister_slot(chip, CB710_SLOT_MS);
+	cb710_unregister_slot(chip, CB710_SLOT_MMC);
+#ifdef CONFIG_CB710_DEBUG_ASSUMPTIONS
+	BUG_ON(atomic_read(&chip->slot_refs_count) != 0);
+#endif
+
+	spin_lock_irqsave(&cb710_idr_lock, flags);
+	idr_remove(&cb710_idr, chip->platform_id);
+	spin_unlock_irqrestore(&cb710_idr_lock, flags);
+}
+
+static const struct pci_device_id cb710_pci_tbl[] = {
+	{ PCI_VENDOR_ID_ENE, PCI_DEVICE_ID_ENE_CB710_FLASH,
+		PCI_ANY_ID, PCI_ANY_ID, },
+	{ 0, }
+};
+
+static struct pci_driver cb710_driver = {
+	.name = KBUILD_MODNAME,
+	.id_table = cb710_pci_tbl,
+	.probe = cb710_probe,
+	.remove = __devexit_p(cb710_remove_one),
+#ifdef CONFIG_PM
+	.suspend = cb710_suspend,
+	.resume = cb710_resume,
+#endif
+};
+
+static int __init cb710_init_module(void)
+{
+	return pci_register_driver(&cb710_driver);
+}
+
+static void __exit cb710_cleanup_module(void)
+{
+	pci_unregister_driver(&cb710_driver);
+}
+
+module_init(cb710_init_module);
+module_exit(cb710_cleanup_module);
+
+MODULE_AUTHOR("Michał Mirosław <mirq-linux@rere.qmqm.pl>");
+MODULE_DESCRIPTION("ENE CB710 memory card reader driver");
+MODULE_LICENSE("GPL");
+MODULE_DEVICE_TABLE(pci, cb710_pci_tbl);
diff -urN linux-2.6.29.2/drivers/misc/cb710/debug.c cb710-tree/drivers/misc/cb710/debug.c
--- linux-2.6.29.2/drivers/misc/cb710/debug.c	1970-01-01 01:00:00.000000000 +0100
+++ cb710-tree/drivers/misc/cb710/debug.c	2009-05-04 20:29:38.000000000 +0200
@@ -0,0 +1,119 @@
+/*
+ *  cb710/debug.c
+ *
+ *  Copyright by Michał Mirosław, 2008-2009
+ *
+ * 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.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/cb710.h>
+
+#define CB710_REG_COUNT		0x80
+
+static const u16 allow[CB710_REG_COUNT/16] = {
+	0xFFF0, 0xFFFF, 0xFFFF, 0xFFFF,
+	0xFFF0, 0xFFFF, 0xFFFF, 0xFFFF,
+};
+static const char *const prefix[ARRAY_SIZE(allow)] = {
+	"MMC", "MMC", "MMC", "MMC",
+	"MS?", "MS?", "SM?", "SM?"
+};
+
+static inline int allow_reg_read(unsigned block, unsigned offset, unsigned bits)
+{
+	unsigned mask = (1 << bits/8) - 1;
+	offset *= bits/8;
+	return ((allow[block] >> offset) & mask) == mask;
+}
+
+#define CB710_READ_REGS_TEMPLATE(t)					\
+static void cb710_read_regs_##t(void __iomem *iobase,			\
+	u##t *reg, unsigned select)					\
+{									\
+	unsigned i, j;							\
+									\
+	for (i = 0; i < ARRAY_SIZE(allow); ++i, reg += 16/(t/8)) {	\
+		if (!select & (1 << i))					\
+			continue;					\
+									\
+		for (j = 0; j < 0x10/(t/8); ++j) {			\
+			if (!allow_reg_read(i, j, t))			\
+				continue;				\
+			reg[j] = ioread##t(iobase			\
+				+ (i << 4) + (j * (t/8)));		\
+		}							\
+	}								\
+}
+
+static const char cb710_regf_8[] = "%02X";
+static const char cb710_regf_16[] = "%04X";
+static const char cb710_regf_32[] = "%08X";
+static const char cb710_xes[] = "xxxxxxxx";
+
+#define CB710_DUMP_REGS_TEMPLATE(t)					\
+static void cb710_dump_regs_##t(struct device *dev,			\
+	const u##t *reg, unsigned select)				\
+{									\
+	const char *const xp = &cb710_xes[8 - t/4];			\
+	const char *const format = cb710_regf_##t;			\
+									\
+	char msg[100], *p;						\
+	unsigned i, j;							\
+									\
+	for (i = 0; i < ARRAY_SIZE(allow); ++i, reg += 16/(t/8)) {	\
+		if (!(select & (1 << i)))				\
+			continue;					\
+		p = msg;						\
+		for (j = 0; j < 0x10/(t/8); ++j) {			\
+			*p++ = ' ';					\
+			if (j == 8/(t/8))				\
+				*p++ = ' ';				\
+			if (allow_reg_read(i, j, t))			\
+				p += sprintf(p, format, reg[j]);	\
+			else						\
+				p += sprintf(p, "%s", xp);		\
+		}							\
+		dev_dbg(dev, "%s 0x%02X %s\n", prefix[i], i << 4, msg);	\
+	}								\
+}
+
+#define CB710_READ_AND_DUMP_REGS_TEMPLATE(t)				\
+static void cb710_read_and_dump_regs_##t(struct cb710_chip *chip,	\
+	unsigned select)						\
+{									\
+	u##t regs[CB710_REG_COUNT/sizeof(u##t)];			\
+									\
+	memset(&regs, 0, sizeof(regs));					\
+	cb710_read_regs_##t(chip->iobase, regs, select);		\
+	cb710_dump_regs_##t(cb710_chip_dev(chip), regs, select);	\
+}
+
+#define CB710_REG_ACCESS_TEMPLATES(t)		\
+  CB710_READ_REGS_TEMPLATE(t)			\
+  CB710_DUMP_REGS_TEMPLATE(t)			\
+  CB710_READ_AND_DUMP_REGS_TEMPLATE(t)
+
+CB710_REG_ACCESS_TEMPLATES(8)
+CB710_REG_ACCESS_TEMPLATES(16)
+CB710_REG_ACCESS_TEMPLATES(32)
+
+void cb710_dump_regs(struct cb710_chip *chip, unsigned select)
+{
+	if (!(select & CB710_DUMP_REGS_MASK))
+		select = CB710_DUMP_REGS_ALL;
+	if (!(select & CB710_DUMP_ACCESS_MASK))
+		select |= CB710_DUMP_ACCESS_8;
+
+	if (select & CB710_DUMP_ACCESS_32)
+		cb710_read_and_dump_regs_32(chip, select);
+	if (select & CB710_DUMP_ACCESS_16)
+		cb710_read_and_dump_regs_16(chip, select);
+	if (select & CB710_DUMP_ACCESS_8)
+		cb710_read_and_dump_regs_8(chip, select);
+}
+EXPORT_SYMBOL_GPL(cb710_dump_regs);
+
diff -urN linux-2.6.29.2/drivers/misc/cb710/Kconfig cb710-tree/drivers/misc/cb710/Kconfig
--- linux-2.6.29.2/drivers/misc/cb710/Kconfig	1970-01-01 01:00:00.000000000 +0100
+++ cb710-tree/drivers/misc/cb710/Kconfig	2009-05-04 20:29:38.000000000 +0200
@@ -0,0 +1,21 @@
+config CB710_CORE
+	tristate "ENE CB710/720 Flash memory card reader support"
+	depends on PCI
+	help
+	  This option enables support for PCI ENE CB710/720 Flash memory card
+	  reader found in some laptops (ie. some versions of HP Compaq nx9500).
+
+	  You will also have to select some flash card format drivers (MMC/SD,
+	  MemoryStick).
+
+	  This driver can also be built as a module. If so, the module
+	  will be called cb710.
+
+config CB710_DEBUG
+	bool "Enable driver debugging"
+	depends on CB710_CORE != n
+	default n
+	help
+	  This is an option for use by developers; most people should
+	  say N here.  This adds a lot of debugging output to dmesg.
+
diff -urN linux-2.6.29.2/drivers/misc/cb710/Makefile cb710-tree/drivers/misc/cb710/Makefile
--- linux-2.6.29.2/drivers/misc/cb710/Makefile	1970-01-01 01:00:00.000000000 +0100
+++ cb710-tree/drivers/misc/cb710/Makefile	2009-05-04 20:29:38.000000000 +0200
@@ -0,0 +1,4 @@
+obj-$(CONFIG_CB710_CORE)	+= cb710.o
+
+cb710-y				:= core.o sgbuf2.o
+cb710-$(CONFIG_CB710_DEBUG)	+= debug.o
diff -urN linux-2.6.29.2/drivers/misc/cb710/sgbuf2.c cb710-tree/drivers/misc/cb710/sgbuf2.c
--- linux-2.6.29.2/drivers/misc/cb710/sgbuf2.c	1970-01-01 01:00:00.000000000 +0100
+++ cb710-tree/drivers/misc/cb710/sgbuf2.c	2009-05-04 20:29:38.000000000 +0200
@@ -0,0 +1,150 @@
+/*
+ *  cb710/sgbuf2.c
+ *
+ *  Copyright by Michał Mirosław, 2008-2009
+ *
+ * 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.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/sgbuf2.h>
+
+static bool sg_dwiter_next(struct sg_mapping_iter *miter)
+{
+	if (sg_miter_next(miter)) {
+		miter->consumed = 0;
+		return true;
+	} else
+		return false;
+}
+
+static bool sg_dwiter_is_at_end(struct sg_mapping_iter *miter)
+{
+	return miter->length == miter->consumed && !sg_dwiter_next(miter);
+}
+
+static uint32_t sg_dwiter_read_buffer(struct sg_mapping_iter *miter)
+{
+	size_t len, left = 4;
+	uint32_t data;
+	void *addr = &data;
+
+	do {
+		len = min(miter->length - miter->consumed, left);
+		memcpy(addr, miter->addr + miter->consumed, len);
+		miter->consumed += len;
+		left -= len;
+		if (!left)
+			return data;
+		addr += len;
+	} while (sg_dwiter_next(miter));
+
+	memset(addr, 0, left);
+	return data;
+}
+
+static inline bool needs_unaligned_copy(const void *ptr)
+{
+#ifdef HAVE_EFFICIENT_UNALIGNED_ACCESS
+	return false;
+#else
+	return ((ptr - NULL) & 3) != 0;
+#endif
+}
+
+static bool sg_dwiter_get_next_block(struct sg_mapping_iter *miter, uint32_t **ptr)
+{
+	size_t len;
+
+	if (sg_dwiter_is_at_end(miter))
+		return true;
+
+	len = miter->length - miter->consumed;
+
+	if (likely(len >= 4 && !needs_unaligned_copy(
+			miter->addr + miter->consumed))) {
+		*ptr = miter->addr + miter->consumed;
+		miter->consumed += 4;
+		return true;
+	}
+
+	return false;
+}
+
+/**
+ * sg_dwiter_read_next_block() - get next 32-bit word from sg buffer
+ * @miter: sg mapping iterator used for reading
+ *
+ * Description:
+ *   Returns 32-bit word starting at byte pointed to by @miter@
+ *   handling any alignment issues.  Bytes past the buffer's end
+ *   are not accessed (read) but are returned as zeroes.  @miter@
+ *   is advanced by 4 bytes or to the end of buffer whichever is
+ *   closer.
+ *
+ * Context:
+ *   Same requirements as in sg_miter_next().
+ *
+ * Returns:
+ *   32-bit word just read.
+ */
+uint32_t sg_dwiter_read_next_block(struct sg_mapping_iter *miter)
+{
+	uint32_t *ptr = NULL;
+
+	if (likely(sg_dwiter_get_next_block(miter, &ptr)))
+		return ptr ? *ptr : 0;
+
+	return sg_dwiter_read_buffer(miter);
+}
+EXPORT_SYMBOL_GPL(sg_dwiter_read_next_block);
+
+static void sg_dwiter_write_slow(struct sg_mapping_iter *miter, uint32_t data)
+{
+	size_t len, left = 4;
+	void *addr = &data;
+
+	do {
+		len = min(miter->length - miter->consumed, left);
+		memcpy(miter->addr, addr, len);
+		miter->consumed += len;
+		left -= len;
+		if (!left)
+			return;
+		addr += len;
+		flush_kernel_dcache_page(miter->page);
+	} while (sg_dwiter_next(miter));
+}
+
+/**
+ * sg_dwiter_write_next_block() - write next 32-bit word to sg buffer
+ * @miter: sg mapping iterator used for writing
+ *
+ * Description:
+ *   Writes 32-bit word starting at byte pointed to by @miter@
+ *   handling any alignment issues.  Bytes which would be written
+ *   past the buffer's end are silently discarded. @miter@ is
+ *   advanced by 4 bytes or to the end of buffer whichever is closer.
+ *
+ * Context:
+ *   Same requirements as in sg_miter_next().
+ */
+void sg_dwiter_write_next_block(struct sg_mapping_iter *miter, uint32_t data)
+{
+	uint32_t *ptr = NULL;
+
+	if (likely(sg_dwiter_get_next_block(miter, &ptr))) {
+		if (ptr)
+			*ptr = data;
+		else
+			return;
+	} else
+		sg_dwiter_write_slow(miter, data);
+
+	if (miter->length == miter->consumed)
+		flush_kernel_dcache_page(miter->page);
+}
+EXPORT_SYMBOL_GPL(sg_dwiter_write_next_block);
+
diff -urN linux-2.6.29.2/drivers/misc/Kconfig cb710-tree/drivers/misc/Kconfig
--- linux-2.6.29.2/drivers/misc/Kconfig	2009-05-04 20:39:00.000000000 +0200
+++ cb710-tree/drivers/misc/Kconfig	2009-05-04 20:38:42.000000000 +0200
@@ -225,5 +225,6 @@
 
 source "drivers/misc/c2port/Kconfig"
 source "drivers/misc/eeprom/Kconfig"
+source "drivers/misc/cb710/Kconfig"
 
 endif # MISC_DEVICES
diff -urN linux-2.6.29.2/drivers/misc/Makefile cb710-tree/drivers/misc/Makefile
--- linux-2.6.29.2/drivers/misc/Makefile	2009-05-04 20:39:04.000000000 +0200
+++ cb710-tree/drivers/misc/Makefile	2009-05-04 20:38:44.000000000 +0200
@@ -20,3 +20,4 @@
 obj-$(CONFIG_HP_ILO)		+= hpilo.o
 obj-$(CONFIG_C2PORT)		+= c2port/
 obj-y				+= eeprom/
+obj-y				+= cb710/
diff -urN linux-2.6.29.2/drivers/mmc/host/cb710-mmc.c cb710-tree/drivers/mmc/host/cb710-mmc.c
--- linux-2.6.29.2/drivers/mmc/host/cb710-mmc.c	1970-01-01 01:00:00.000000000 +0100
+++ cb710-tree/drivers/mmc/host/cb710-mmc.c	2009-05-04 20:34:25.000000000 +0200
@@ -0,0 +1,805 @@
+/*
+ *  cb710/mmc.c
+ *
+ *  Copyright by Michał Mirosław, 2008-2009
+ *
+ * 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.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include "cb710-mmc.h"
+#include <linux/sgbuf2.h>
+
+static const u8 cb710_clock_divider_log2[8] = {
+/*	1, 2, 4, 8, 16, 32, 128, 512 */
+	0, 1, 2, 3,  4,  5,   7,   9
+};
+#define CB710_MAX_DIVIDER_IDX	\
+	(ARRAY_SIZE(cb710_clock_divider_log2) - 1)
+
+static const u8 cb710_src_freq_mhz[16] = {
+	33, 10, 20, 25, 30, 35, 40, 45,
+	50, 55, 60, 65, 70, 75, 80, 85
+};
+
+static void cb710_mmc_set_clock(struct mmc_host *mmc, int hz)
+{
+	struct cb710_slot *slot = cb710_mmc_to_slot(mmc);
+	struct pci_dev *pdev = cb710_slot_to_chip(slot)->pdev;
+	u32 src_freq_idx;
+	u32 divider_idx;
+	int src_hz;
+
+	/* this is magic, unverifiable for me, unless I get
+	 * MMC card with cables connected to bus signals */
+	pci_read_config_dword(pdev, 0x48, &src_freq_idx);
+	src_freq_idx = (src_freq_idx >> 16) & 0xF;
+	src_hz = cb710_src_freq_mhz[src_freq_idx] * 1000000;
+
+	for (divider_idx = 0; divider_idx < CB710_MAX_DIVIDER_IDX; ++divider_idx) {
+		if (hz >= src_hz >> cb710_clock_divider_log2[divider_idx])
+			break;
+	}
+
+	if (src_freq_idx)
+		divider_idx |= 0x8;
+
+	cb710_pci_update_config_reg(pdev, 0x40, ~0xF0000000, divider_idx << 28);
+
+	dev_dbg(cb710_slot_dev(slot),
+		"clock set to %d Hz, wanted %d Hz; flag = %d\n",
+		src_hz >> cb710_clock_divider_log2[divider_idx & 7],
+		hz, (divider_idx & 8) != 0);
+}
+
+static void __cb710_mmc_enable_irq(struct cb710_slot *slot,
+	unsigned short enable, unsigned short mask)
+{
+	/* clear global IE
+	 * - it gets set later if any interrupt sources are enabled */
+	mask |= CB710_MMC_IE_IRQ_ENABLE;
+
+	/* look like interrupt is fired whenever
+	 * WORD[0x0C] & WORD[0x10] != 0;
+	 * -> bit 15 port 0x0C seems to be global interrupt enable
+	 */
+
+	enable = (cb710_read_port_16(slot, CB710_MMC_IRQ_ENABLE_PORT)
+		& ~mask) | enable;
+
+	if (enable)
+		enable |= CB710_MMC_IE_IRQ_ENABLE;
+
+	cb710_write_port_16(slot, CB710_MMC_IRQ_ENABLE_PORT, enable);
+}
+
+static void cb710_mmc_enable_irq(struct cb710_slot *slot,
+	unsigned short enable, unsigned short mask)
+{
+	struct cb710_mmc_reader *reader = mmc_priv(cb710_slot_to_mmc(slot));
+	unsigned long flags;
+
+	spin_lock_irqsave(&reader->irq_lock, flags);
+	/* this is the only thing irq_lock protects */
+	__cb710_mmc_enable_irq(slot, enable, mask);
+	spin_unlock_irqrestore(&reader->irq_lock, flags);
+}
+
+static void cb710_mmc_reset_events(struct cb710_slot *slot)
+{
+	cb710_write_port_8(slot, CB710_MMC_STATUS0_PORT, 0xFF);
+	cb710_write_port_8(slot, CB710_MMC_STATUS1_PORT, 0xFF);
+	cb710_write_port_8(slot, CB710_MMC_STATUS2_PORT, 0xFF);
+}
+
+static int cb710_mmc_is_card_inserted(struct cb710_slot *slot)
+{
+	return cb710_read_port_8(slot, CB710_MMC_STATUS3_PORT)
+		& CB710_MMC_S3_CARD_DETECTED;
+}
+
+static void cb710_mmc_enable_4bit_data(struct cb710_slot *slot, int enable)
+{
+	dev_dbg(cb710_slot_dev(slot), "configuring %d-data-line%s mode\n",
+		enable ? 4 : 1, enable ? "s" : "");
+	if (enable)
+		cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT,
+			CB710_MMC_C1_4BIT_DATA_BUS, 0);
+	else
+		cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT,
+			0, CB710_MMC_C1_4BIT_DATA_BUS);
+}
+
+static int cb710_check_event(struct cb710_slot *slot, u8 what)
+{
+	u16 status;
+
+	status = cb710_read_port_16(slot, CB710_MMC_STATUS_PORT);
+
+	if (status & CB710_MMC_S0_FIFO_UNDERFLOW) {
+		/* it is just a guess, so log it */
+		dev_dbg(cb710_slot_dev(slot),
+			"CHECK : ignoring bit 6 in status %04X\n", status);
+		cb710_write_port_8(slot, CB710_MMC_STATUS0_PORT,
+			CB710_MMC_S0_FIFO_UNDERFLOW);
+		status &= ~CB710_MMC_S0_FIFO_UNDERFLOW;
+	}
+
+	if (status & CB710_MMC_STATUS_ERROR_EVENTS) {
+		dev_dbg(cb710_slot_dev(slot),
+			"CHECK : returning EIO on status %04X\n", status);
+		cb710_write_port_8(slot, CB710_MMC_STATUS0_PORT, status & 0xFF);
+		cb710_write_port_8(slot, CB710_MMC_STATUS1_PORT,
+			CB710_MMC_S1_RESET);
+		return -EIO;
+	}
+
+	/* 'what' is a bit in MMC_STATUS1 */
+	if ((status >> 8) & what) {
+		cb710_write_port_8(slot, CB710_MMC_STATUS1_PORT, what);
+		return 1;
+	}
+
+	return 0;
+}
+
+static int cb710_wait_for_event(struct cb710_slot *slot, u8 what)
+{
+	int err = 0;
+	unsigned limit = 2000000;	/* FIXME: real timeout */
+
+#ifdef CONFIG_CB710_DEBUG
+	u32 e, x;
+	e = cb710_read_port_32(slot, CB710_MMC_STATUS_PORT);
+#endif
+
+	while (!(err = cb710_check_event(slot, what))) {
+		if (!--limit) {
+			cb710_dump_regs(cb710_slot_to_chip(slot),
+				CB710_DUMP_REGS_MMC);
+			err = -ETIMEDOUT;
+			break;
+		}
+		udelay(1);
+	}
+
+#ifdef CONFIG_CB710_DEBUG
+	x = cb710_read_port_32(slot, CB710_MMC_STATUS_PORT);
+
+	limit = 2000000 - limit;
+	if (limit > 100)
+		dev_dbg(cb710_slot_dev(slot),
+			"WAIT10: waited %d loops, what %d, entry val %08X, exit val %08X\n",
+			limit, what, e, x);
+#endif
+	return err < 0 ? err : 0;
+}
+
+
+static int cb710_wait_while_busy(struct cb710_slot *slot, uint8_t mask)
+{
+	unsigned limit = 500000;	/* FIXME: real timeout */
+	int err = 0;
+
+#ifdef CONFIG_CB710_DEBUG
+	u32 e, x;
+	e = cb710_read_port_32(slot, CB710_MMC_STATUS_PORT);
+#endif
+
+	while (cb710_read_port_8(slot, CB710_MMC_STATUS2_PORT) & mask) {
+		if (!--limit) {
+			cb710_dump_regs(cb710_slot_to_chip(slot),
+				CB710_DUMP_REGS_MMC);
+			err = -ETIMEDOUT;
+			break;
+		}
+		udelay(1);
+	}
+
+#ifdef CONFIG_CB710_DEBUG
+	x = cb710_read_port_32(slot, CB710_MMC_STATUS_PORT);
+
+	limit = 500000 - limit;
+	if (limit > 100)
+		dev_dbg(cb710_slot_dev(slot),
+			"WAIT12: waited %d loops, mask %02X, entry val %08X, exit val %08X\n",
+			limit, mask, e, x);
+#endif
+	return 0;
+}
+
+static void cb710_mmc_set_transfer_size(struct cb710_slot *slot,
+	size_t count, size_t blocksize)
+{
+	cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
+	cb710_write_port_32(slot, CB710_MMC_TRANSFER_SIZE_PORT,
+		((count - 1) << 16)|(blocksize - 1));
+
+	dev_vdbg(cb710_slot_dev(slot), "set up for %d block%s of %d bytes\n",
+		count, count == 1 ? "" : "s", blocksize);
+}
+
+static void cb710_mmc_fifo_hack(struct cb710_slot *slot)
+{
+	/* without this, received data is prepended with 8-bytes of zeroes */
+	u32 r1, r2;
+	int ok = 0;
+
+	r1 = cb710_read_port_32(slot, CB710_MMC_DATA_PORT);
+	r2 = cb710_read_port_32(slot, CB710_MMC_DATA_PORT);
+	if (cb710_read_port_8(slot, CB710_MMC_STATUS0_PORT)
+	    & CB710_MMC_S0_FIFO_UNDERFLOW) {
+		cb710_write_port_8(slot, CB710_MMC_STATUS0_PORT,
+			CB710_MMC_S0_FIFO_UNDERFLOW);
+		ok = 1;
+	}
+
+	dev_dbg(cb710_slot_dev(slot),
+		"FIFO-read-hack: expected STATUS0 bit was %s\n",
+		ok ? "set." : "NOT SET!");
+	dev_dbg(cb710_slot_dev(slot),
+		"FIFO-read-hack: dwords ignored: %08X %08X - %s\n",
+		r1, r2, (r1|r2) ? "BAD (NOT ZERO)!" : "ok");
+}
+
+static int cb710_mmc_receive_pio(struct cb710_slot *slot,
+	struct sg_mapping_iter *miter, size_t dw_count)
+{
+	if (!(cb710_read_port_8(slot, CB710_MMC_STATUS2_PORT) & CB710_MMC_S2_FIFO_READY)) {
+		int err = cb710_wait_for_event(slot,
+			CB710_MMC_S1_PIO_TRANSFER_DONE);
+		if (err)
+			return err;
+	}
+
+	sg_dwiter_write_from_io(miter,
+		slot->iobase + CB710_MMC_DATA_PORT, dw_count);
+
+	return 0;
+}
+
+static bool cb710_is_transfer_size_supported(struct mmc_data *data)
+{
+	return !(data->blksz & 15 && (data->blocks != 1 || data->blksz != 8));
+}
+
+static int cb710_mmc_receive(struct cb710_slot *slot, struct mmc_data *data)
+{
+	struct sg_mapping_iter miter;
+	size_t len, blocks = data->blocks;
+	int err = 0;
+
+	/* TODO: I don't know how/if the hardware handles non-16B-boundary blocks
+	 * except single 8B block */
+	if (unlikely(data->blksz & 15 && (data->blocks != 1 || data->blksz != 8)))
+		return -EINVAL;
+
+	sg_miter_start(&miter, data->sg, data->sg_len, 0);
+
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG2_PORT,
+		15, CB710_MMC_C2_READ_PIO_SIZE_MASK);
+
+	cb710_mmc_fifo_hack(slot);
+
+	while (blocks-- > 0) {
+		len = data->blksz;
+
+		while (len >= 16) {
+			err = cb710_mmc_receive_pio(slot, &miter, 4);
+			if (err)
+				goto out;
+			len -= 16;
+		}
+
+		if (!len)
+			continue;
+
+		cb710_modify_port_8(slot, CB710_MMC_CONFIG2_PORT,
+			len - 1, CB710_MMC_C2_READ_PIO_SIZE_MASK);
+
+		len = (len >= 8) ? 4 : 2;
+		err = cb710_mmc_receive_pio(slot, &miter, len);
+		if (err)
+			goto out;
+	}
+out:
+	sg_miter_stop_writing(&miter);
+	return err;
+}
+
+static int cb710_mmc_send(struct cb710_slot *slot, struct mmc_data *data)
+{
+	struct sg_mapping_iter miter;
+	size_t len, blocks = data->blocks;
+	int err = 0;
+
+	/* TODO: I don't know how/if the hardware handles multiple
+	 * non-16B-boundary blocks */
+	if (unlikely(data->blocks > 1 && data->blksz & 15))
+		return -EINVAL;
+
+	sg_miter_start(&miter, data->sg, data->sg_len, 0);
+
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG2_PORT,
+		0, CB710_MMC_C2_READ_PIO_SIZE_MASK);
+
+	while (blocks-- > 0) {
+		len = (data->blksz + 15) >> 4;
+		do {
+			if (!(cb710_read_port_8(slot, CB710_MMC_STATUS2_PORT)
+			    & CB710_MMC_S2_FIFO_EMPTY)) {
+				err = cb710_wait_for_event(slot,
+					CB710_MMC_S1_PIO_TRANSFER_DONE);
+				if (err)
+					goto out;
+			}
+			sg_dwiter_read_to_io(&miter,
+				slot->iobase + CB710_MMC_DATA_PORT, 4);
+		} while (--len);
+	}
+out:
+	sg_miter_stop(&miter);
+	return err;
+}
+
+static u16 cb710_encode_cmd_flags(struct cb710_mmc_reader *reader,
+	struct mmc_command *cmd)
+{
+	unsigned int flags = cmd->flags;
+	u16 cb_flags = 0;
+
+	/* Windows driver returned 0 for commands for which no response
+	 * is expected. It happened that there were only two such commands
+	 * used: MMC_GO_IDLE_STATE and MMC_GO_INACTIVE_STATE so it might
+	 * as well be a bug in that driver.
+	 *
+	 * Original driver set bit 14 for MMC/SD application
+	 * commands. There's no difference 'on the wire' and
+	 * it apparently works without it anyway.
+	 */
+
+	switch (flags & MMC_CMD_MASK) {
+	case MMC_CMD_AC:	cb_flags = CB710_MMC_CMD_AC;	break;
+	case MMC_CMD_ADTC:	cb_flags = CB710_MMC_CMD_ADTC;	break;
+	case MMC_CMD_BC:	cb_flags = CB710_MMC_CMD_BC;	break;
+	case MMC_CMD_BCR:	cb_flags = CB710_MMC_CMD_BCR;	break;
+	}
+
+	if (flags & MMC_RSP_BUSY)
+		cb_flags |= CB710_MMC_RSP_BUSY;
+
+	cb_flags |= cmd->opcode << CB710_MMC_CMD_CODE_SHIFT;
+
+	if (cmd->data && (cmd->data->flags & MMC_DATA_READ))
+		cb_flags |= CB710_MMC_DATA_READ;
+
+	if (flags & MMC_RSP_PRESENT) {
+		/* Windows driver set 01 at bits 4,3 except for
+		 * MMC_SET_BLOCKLEN where it set 10. Maybe the
+		 * hardware can do something special about this
+		 * command? The original driver looks buggy/incomplete
+		 * anyway so we ignore this for now.
+		 *
+		 * I assume that 00 here means no response is expected.
+		 */
+		cb_flags |= CB710_MMC_RSP_PRESENT;
+
+		if (flags & MMC_RSP_136)
+			cb_flags |= CB710_MMC_RSP_136;
+		if (!(flags & MMC_RSP_CRC))
+			cb_flags |= CB710_MMC_RSP_NO_CRC;
+	}
+
+	return cb_flags;
+}
+
+static void cb710_receive_response(struct cb710_slot *slot,
+	struct mmc_command *cmd)
+{
+	unsigned rsp_opcode, wanted_opcode;
+
+	/* Looks like final byte with CRC is always stripped (same as SDHCI) */
+	if (cmd->flags & MMC_RSP_136) {
+		u32 resp[4];
+
+		resp[0] = cb710_read_port_32(slot, CB710_MMC_RESPONSE3_PORT);
+		resp[1] = cb710_read_port_32(slot, CB710_MMC_RESPONSE2_PORT);
+		resp[2] = cb710_read_port_32(slot, CB710_MMC_RESPONSE1_PORT);
+		resp[3] = cb710_read_port_32(slot, CB710_MMC_RESPONSE0_PORT);
+		rsp_opcode = resp[0] >> 24;
+
+		cmd->resp[0] = (resp[0] << 8)|(resp[1] >> 24);
+		cmd->resp[1] = (resp[1] << 8)|(resp[2] >> 24);
+		cmd->resp[2] = (resp[2] << 8)|(resp[3] >> 24);
+		cmd->resp[3] = (resp[3] << 8);
+	} else {
+		rsp_opcode = cb710_read_port_32(slot, CB710_MMC_RESPONSE1_PORT) & 0x3F;
+		cmd->resp[0] = cb710_read_port_32(slot, CB710_MMC_RESPONSE0_PORT);
+	}
+
+	wanted_opcode = (cmd->flags & MMC_RSP_OPCODE) ? cmd->opcode : 0x3F;
+	if (rsp_opcode != wanted_opcode)
+		cmd->error = -EILSEQ;
+}
+
+static int cb710_mmc_transfer_data(struct cb710_slot *slot,
+	struct mmc_data *data)
+{
+	int error, to;
+
+	if (data->flags & MMC_DATA_READ)
+		error = cb710_mmc_receive(slot, data);
+	else
+		error = cb710_mmc_send(slot, data);
+
+	to = cb710_wait_for_event(slot, CB710_MMC_S1_DATA_TRANSFER_DONE);
+	if (!error)
+		error = to;
+
+	if (!error)
+		data->bytes_xfered = data->blksz * data->blocks;
+	return error;
+}
+
+static int cb710_mmc_command(struct mmc_host *mmc, struct mmc_command *cmd)
+{
+	struct cb710_slot *slot = cb710_mmc_to_slot(mmc);
+	struct cb710_mmc_reader *reader = mmc_priv(mmc);
+	struct mmc_data *data = cmd->data;
+
+	u16 cb_cmd = cb710_encode_cmd_flags(reader, cmd);
+	dev_dbg(cb710_slot_dev(slot), "cmd request: 0x%04X\n", cb_cmd);
+
+	if (data) {
+		if (!cb710_is_transfer_size_supported(data)) {
+			data->error = -EINVAL;
+			return -1;
+		}
+		cb710_mmc_set_transfer_size(slot, data->blocks, data->blksz);
+	}
+
+	cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20|CB710_MMC_S2_BUSY_10);
+	cb710_write_port_16(slot, CB710_MMC_CMD_TYPE_PORT, cb_cmd);
+	cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
+	cb710_write_port_32(slot, CB710_MMC_CMD_PARAM_PORT, cmd->arg);
+	cb710_mmc_reset_events(slot);
+	cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG0_PORT, 0x01, 0);
+
+	cmd->error = cb710_wait_for_event(slot, CB710_MMC_S1_COMMAND_SENT);
+	if (cmd->error)
+		return -1;
+
+	if (cmd->flags & MMC_RSP_PRESENT) {
+		cb710_receive_response(slot, cmd);
+		if (cmd->error)
+			return -1;
+	}
+
+	if (data)
+		data->error = cb710_mmc_transfer_data(slot, data);
+	return 0;
+}
+
+static void cb710_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+	struct cb710_slot *slot = cb710_mmc_to_slot(mmc);
+	struct cb710_mmc_reader *reader = mmc_priv(mmc);
+
+	WARN_ON(reader->mrq != NULL);
+
+	reader->mrq = mrq;
+	cb710_mmc_enable_irq(slot, CB710_MMC_IE_TEST_MASK, 0);
+
+	if (cb710_mmc_is_card_inserted(slot)) {
+		if (!cb710_mmc_command(mmc, mrq->cmd) && mrq->stop)
+			cb710_mmc_command(mmc, mrq->stop);
+		mdelay(1);
+	} else {
+		mrq->cmd->error = -ENOMEDIUM;
+	}
+
+	tasklet_schedule(&reader->finish_req_tasklet);
+}
+
+static int cb710_mmc_powerup(struct cb710_slot *slot)
+{
+#ifdef CONFIG_CB710_DEBUG
+	struct cb710_chip *chip = cb710_slot_to_chip(slot);
+#endif
+	int err;
+
+	/* a lot of magic; see comment in cb710_mmc_set_clock() */
+	dev_dbg(cb710_slot_dev(slot), "bus powerup\n");
+	cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+	err = cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
+	if (unlikely(err))
+		return err;
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT, 0x80, 0);
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG3_PORT, 0x80, 0);
+	cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+	mdelay(1);
+	dev_dbg(cb710_slot_dev(slot), "after delay 1\n");
+	cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+	err = cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
+	if (unlikely(err))
+		return err;
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT, 0x09, 0);
+	cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+	mdelay(1);
+	dev_dbg(cb710_slot_dev(slot), "after delay 2\n");
+	cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+	err = cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
+	if (unlikely(err))
+		return err;
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT, 0, 0x08);
+	cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+	mdelay(2);
+	dev_dbg(cb710_slot_dev(slot), "after delay 3\n");
+	cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG0_PORT, 0x06, 0);
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT, 0x70, 0);
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG2_PORT, 0x80, 0);
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG3_PORT, 0x03, 0);
+	cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+	err = cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
+	if (unlikely(err))
+		return err;
+	/* This port behaves weird: quick byte reads of 0x08,0x09 return
+	 * 0xFF,0x00 after writing 0xFFFF to 0x08; it works correctly when
+	 * read/written from userspace...  What am I missing here?
+	 * (it doesn't depend on write-to-read delay) */
+	cb710_write_port_16(slot, CB710_MMC_CONFIGB_PORT, 0xFFFF);
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG0_PORT, 0x06, 0);
+	cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+	dev_dbg(cb710_slot_dev(slot), "bus powerup finished\n");
+
+	return cb710_check_event(slot, 0);
+}
+
+static void cb710_mmc_powerdown(struct cb710_slot *slot)
+{
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT, 0, 0x81);
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG3_PORT, 0, 0x80);
+}
+
+static void cb710_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+	struct cb710_slot *slot = cb710_mmc_to_slot(mmc);
+	struct cb710_mmc_reader *reader = mmc_priv(mmc);
+	int err;
+
+	cb710_mmc_set_clock(mmc, ios->clock);
+
+	if (!cb710_mmc_is_card_inserted(slot)) {
+		dev_dbg(cb710_slot_dev(slot),
+			"no card inserted - ignoring bus powerup request\n");
+		ios->power_mode = MMC_POWER_OFF;
+	}
+
+	if (ios->power_mode != reader->last_power_mode)
+	switch (ios->power_mode) {
+	case MMC_POWER_ON:
+		err = cb710_mmc_powerup(slot);
+		if (err) {
+			dev_warn(cb710_slot_dev(slot),
+				"powerup failed (%d)- retrying\n", err);
+			cb710_mmc_powerdown(slot);
+			udelay(1);
+			err = cb710_mmc_powerup(slot);
+			if (err)
+				dev_warn(cb710_slot_dev(slot),
+					"powerup retry failed (%d) - expect errors\n",
+					err);
+		}
+		reader->last_power_mode = MMC_POWER_ON;
+		break;
+	case MMC_POWER_OFF:
+		cb710_mmc_powerdown(slot);
+		reader->last_power_mode = MMC_POWER_OFF;
+		break;
+	case MMC_POWER_UP:
+	default:
+		/* ignore */;
+	}
+
+	cb710_mmc_enable_4bit_data(slot, ios->bus_width != MMC_BUS_WIDTH_1);
+
+	cb710_mmc_enable_irq(slot, CB710_MMC_IE_TEST_MASK, 0);
+}
+
+static int cb710_mmc_get_ro(struct mmc_host *mmc)
+{
+	struct cb710_slot *slot = cb710_mmc_to_slot(mmc);
+
+	return cb710_read_port_8(slot, CB710_MMC_STATUS3_PORT)
+		& CB710_MMC_S3_WRITE_PROTECTED;
+}
+
+static int cb710_mmc_irq_handler(struct cb710_slot *slot)
+{
+	struct mmc_host *mmc = cb710_slot_to_mmc(slot);
+	struct cb710_mmc_reader *reader = mmc_priv(mmc);
+	u32 status, config1, config2, irqen;
+
+	status = cb710_read_port_32(slot, CB710_MMC_STATUS_PORT);
+	irqen = cb710_read_port_32(slot, CB710_MMC_IRQ_ENABLE_PORT);
+	config2 = cb710_read_port_32(slot, CB710_MMC_CONFIGB_PORT);
+	config1 = cb710_read_port_32(slot, CB710_MMC_CONFIG_PORT);
+
+	dev_dbg(cb710_slot_dev(slot), "interrupt; status: %08X, "
+		"ie: %08X, c2: %08X, c1: %08X\n",
+		status, irqen, config2, config1);
+
+	if (status & (CB710_MMC_S1_CARD_CHANGED << 8)) {
+		/* ack the event */
+		cb710_write_port_8(slot, CB710_MMC_STATUS1_PORT,
+			CB710_MMC_S1_CARD_CHANGED);
+		if ((irqen & CB710_MMC_IE_CISTATUS_MASK)
+		    == CB710_MMC_IE_CISTATUS_MASK)
+			mmc_detect_change(mmc, HZ/5);
+	} else {
+		dev_dbg(cb710_slot_dev(slot), "unknown interrupt (test)\n");
+		spin_lock(&reader->irq_lock);
+		__cb710_mmc_enable_irq(slot, 0, CB710_MMC_IE_TEST_MASK);
+		spin_unlock(&reader->irq_lock);
+	}
+
+	return 1;
+}
+
+static void cb710_mmc_finish_request_tasklet(unsigned long data)
+{
+	struct mmc_host *mmc = (void *)data;
+	struct cb710_mmc_reader *reader = mmc_priv(mmc);
+	struct mmc_request *mrq = reader->mrq;
+
+	reader->mrq = NULL;
+	mmc_request_done(mmc, mrq);
+}
+
+static const struct mmc_host_ops cb710_mmc_host = {
+	.request = cb710_mmc_request,
+	.set_ios = cb710_mmc_set_ios,
+	.get_ro = cb710_mmc_get_ro
+};
+
+#ifdef CONFIG_PM
+
+static int cb710_mmc_suspend(struct platform_device *pdev, pm_message_t state)
+{
+	struct cb710_slot *slot = cb710_pdev_to_slot(pdev);
+	struct mmc_host *mmc = cb710_slot_to_mmc(slot);
+	int err;
+
+	err = mmc_suspend_host(mmc, state);
+	if (err)
+		return err;
+
+	cb710_mmc_enable_irq(slot, 0, ~0);
+	return 0;
+}
+
+static int cb710_mmc_resume(struct platform_device *pdev)
+{
+	struct cb710_slot *slot = cb710_pdev_to_slot(pdev);
+	struct mmc_host *mmc = cb710_slot_to_mmc(slot);
+
+	cb710_mmc_enable_irq(slot, 0, ~0);
+
+	return mmc_resume_host(mmc);
+}
+
+#endif /* CONFIG_PM */
+
+static int __devinit cb710_mmc_init(struct platform_device *pdev)
+{
+	struct cb710_slot *slot = cb710_pdev_to_slot(pdev);
+	struct cb710_chip *chip = cb710_slot_to_chip(slot);
+	struct mmc_host *mmc;
+	struct cb710_mmc_reader *reader;
+	int err;
+	u32 val;
+
+	mmc = mmc_alloc_host(sizeof(*reader), cb710_slot_dev(slot));
+	if (!mmc)
+		return -ENOMEM;
+
+	dev_set_drvdata(&pdev->dev, mmc);
+
+	/* harmless (maybe) magic */
+	pci_read_config_dword(chip->pdev, 0x48, &val);
+	val = cb710_src_freq_mhz[(val >> 16) & 0xF];
+	dev_dbg(cb710_slot_dev(slot), "source frequency: %dMHz\n", val);
+	val *= 1000000;
+
+	mmc->ops = &cb710_mmc_host;
+	mmc->f_max = val;
+	mmc->f_min = val >> cb710_clock_divider_log2[CB710_MAX_DIVIDER_IDX];
+	mmc->ocr_avail = MMC_VDD_32_33|MMC_VDD_33_34;
+	mmc->caps = MMC_CAP_4_BIT_DATA;
+
+	reader = mmc_priv(mmc);
+
+	tasklet_init(&reader->finish_req_tasklet,
+		cb710_mmc_finish_request_tasklet, (unsigned long)mmc);
+	spin_lock_init(&reader->irq_lock);
+	cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+
+	cb710_mmc_enable_irq(slot, 0, ~0);
+	cb710_set_irq_handler(slot, cb710_mmc_irq_handler);
+
+	err = mmc_add_host(mmc);
+	if (unlikely(err))
+		goto err_free_mmc;
+
+	dev_dbg(cb710_slot_dev(slot), "mmc_hostname is %s\n",
+		mmc_hostname(mmc));
+
+	cb710_mmc_enable_irq(slot, CB710_MMC_IE_CARD_INSERTION_STATUS, 0);
+
+	return 0;
+
+err_free_mmc:
+	dev_dbg(cb710_slot_dev(slot), "mmc_add_host() failed: %d\n", err);
+
+	mmc_free_host(mmc);
+	return err;
+}
+
+static int __devexit cb710_mmc_exit(struct platform_device *pdev)
+{
+	struct cb710_slot *slot = cb710_pdev_to_slot(pdev);
+	struct mmc_host *mmc = cb710_slot_to_mmc(slot);
+	struct cb710_mmc_reader *reader = mmc_priv(mmc);
+
+	cb710_mmc_enable_irq(slot, 0, CB710_MMC_IE_CARD_INSERTION_STATUS);
+
+	mmc_remove_host(mmc);
+
+	/* IRQs should be disabled now, but let's stay on the safe side */
+	cb710_mmc_enable_irq(slot, 0, ~0);
+	cb710_set_irq_handler(slot, NULL);
+
+	/* clear config ports - just in case */
+	cb710_write_port_32(slot, CB710_MMC_CONFIG_PORT, 0);
+	cb710_write_port_16(slot, CB710_MMC_CONFIGB_PORT, 0);
+
+	tasklet_kill(&reader->finish_req_tasklet);
+
+	mmc_free_host(mmc);
+	return 0;
+}
+
+static struct platform_driver cb710_mmc_driver = {
+	.driver.name = "cb710-mmc",
+	.probe = cb710_mmc_init,
+	.remove = __devexit_p(cb710_mmc_exit),
+#ifdef CONFIG_PM
+	.suspend = cb710_mmc_suspend,
+	.resume = cb710_mmc_resume,
+#endif
+};
+
+static int __init cb710_mmc_init_module(void)
+{
+	return platform_driver_register(&cb710_mmc_driver);
+}
+
+static void __exit cb710_mmc_cleanup_module(void)
+{
+	platform_driver_unregister(&cb710_mmc_driver);
+}
+
+module_init(cb710_mmc_init_module);
+module_exit(cb710_mmc_cleanup_module);
+
+MODULE_AUTHOR("Michał Mirosław <mirq-linux@rere.qmqm.pl>");
+MODULE_DESCRIPTION("ENE CB710 memory card reader driver - MMC/SD part");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:cb710-mmc");
diff -urN linux-2.6.29.2/drivers/mmc/host/cb710-mmc.h cb710-tree/drivers/mmc/host/cb710-mmc.h
--- linux-2.6.29.2/drivers/mmc/host/cb710-mmc.h	1970-01-01 01:00:00.000000000 +0100
+++ cb710-tree/drivers/mmc/host/cb710-mmc.h	2009-05-04 20:34:15.000000000 +0200
@@ -0,0 +1,104 @@
+/*
+ *  cb710/cb710-mmc.h
+ *
+ *  Copyright by Michał Mirosław, 2008-2009
+ *
+ * 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.
+ */
+#ifndef LINUX_CB710_MMC_H
+#define LINUX_CB710_MMC_H
+
+#include <linux/cb710.h>
+
+/* per-MMC-reader structure */
+struct cb710_mmc_reader {
+	struct tasklet_struct finish_req_tasklet;
+	struct mmc_request *mrq;
+	spinlock_t irq_lock;
+	unsigned char last_power_mode;
+};
+
+/* some device struct walking */
+
+static inline struct mmc_host *cb710_slot_to_mmc(struct cb710_slot *slot)
+{
+	return dev_get_drvdata(&slot->pdev.dev);
+}
+
+static inline struct cb710_slot *cb710_mmc_to_slot(struct mmc_host *mmc)
+{
+	struct platform_device *pdev = container_of(mmc_dev(mmc),
+		struct platform_device, dev);
+	return cb710_pdev_to_slot(pdev);
+}
+
+/* registers (this might be all wrong ;) */
+
+#define CB710_MMC_DATA_PORT		0x00
+
+#define CB710_MMC_CONFIG_PORT		0x04
+#define CB710_MMC_CONFIG0_PORT		0x04
+#define CB710_MMC_CONFIG1_PORT		0x05
+#define   CB710_MMC_C1_4BIT_DATA_BUS		0x40
+#define CB710_MMC_CONFIG2_PORT		0x06
+#define   CB710_MMC_C2_READ_PIO_SIZE_MASK	0x0F	/* N-1 */
+#define CB710_MMC_CONFIG3_PORT		0x07
+
+#define CB710_MMC_CONFIGB_PORT		0x08
+
+#define CB710_MMC_IRQ_ENABLE_PORT	0x0C
+#define   CB710_MMC_IE_TEST_MASK		0x00BF
+#define   CB710_MMC_IE_CARD_INSERTION_STATUS	0x1000
+#define   CB710_MMC_IE_IRQ_ENABLE		0x8000
+#define   CB710_MMC_IE_CISTATUS_MASK		\
+		(CB710_MMC_IE_CARD_INSERTION_STATUS|CB710_MMC_IE_IRQ_ENABLE)
+
+#define CB710_MMC_STATUS_PORT		0x10
+#define   CB710_MMC_STATUS_ERROR_EVENTS		0x60FF
+#define CB710_MMC_STATUS0_PORT		0x10
+#define   CB710_MMC_S0_FIFO_UNDERFLOW		0x40
+#define CB710_MMC_STATUS1_PORT		0x11
+#define   CB710_MMC_S1_COMMAND_SENT		0x01
+#define   CB710_MMC_S1_DATA_TRANSFER_DONE	0x02
+#define   CB710_MMC_S1_PIO_TRANSFER_DONE	0x04
+#define   CB710_MMC_S1_CARD_CHANGED		0x10
+#define   CB710_MMC_S1_RESET			0x20
+#define CB710_MMC_STATUS2_PORT		0x12
+#define   CB710_MMC_S2_FIFO_READY		0x01
+#define   CB710_MMC_S2_FIFO_EMPTY		0x02
+#define   CB710_MMC_S2_BUSY_10			0x10
+#define   CB710_MMC_S2_BUSY_20			0x20
+#define CB710_MMC_STATUS3_PORT		0x13
+#define   CB710_MMC_S3_CARD_DETECTED		0x02
+#define   CB710_MMC_S3_WRITE_PROTECTED		0x04
+
+#define CB710_MMC_CMD_TYPE_PORT		0x14
+#define   CB710_MMC_RSP_TYPE_MASK		0x0007
+#define     CB710_MMC_RSP_R1			(0)
+#define     CB710_MMC_RSP_136			(5)
+#define     CB710_MMC_RSP_NO_CRC		(2)
+#define   CB710_MMC_RSP_PRESENT_MASK		0x0018
+#define     CB710_MMC_RSP_NONE			(0 << 3)
+#define     CB710_MMC_RSP_PRESENT		(1 << 3)
+#define     CB710_MMC_RSP_PRESENT_X		(2 << 3)
+#define   CB710_MMC_CMD_TYPE_MASK		0x0060
+#define     CB710_MMC_CMD_BC			(0 << 5)
+#define     CB710_MMC_CMD_BCR			(1 << 5)
+#define     CB710_MMC_CMD_AC			(2 << 5)
+#define     CB710_MMC_CMD_ADTC			(3 << 5)
+#define   CB710_MMC_DATA_READ			0x0080
+#define   CB710_MMC_CMD_CODE_MASK		0x3F00
+#define   CB710_MMC_CMD_CODE_SHIFT		8
+#define   CB710_MMC_IS_APP_CMD			0x4000
+#define   CB710_MMC_RSP_BUSY			0x8000
+
+#define CB710_MMC_CMD_PARAM_PORT	0x18
+#define CB710_MMC_TRANSFER_SIZE_PORT	0x1C
+#define CB710_MMC_RESPONSE0_PORT	0x20
+#define CB710_MMC_RESPONSE1_PORT	0x24
+#define CB710_MMC_RESPONSE2_PORT	0x28
+#define CB710_MMC_RESPONSE3_PORT	0x2C
+
+#endif /* LINUX_CB710_MMC_H */
diff -urN linux-2.6.29.2/drivers/mmc/host/Kconfig cb710-tree/drivers/mmc/host/Kconfig
--- linux-2.6.29.2/drivers/mmc/host/Kconfig	2009-05-04 20:39:37.000000000 +0200
+++ cb710-tree/drivers/mmc/host/Kconfig	2009-05-04 20:38:22.000000000 +0200
@@ -213,3 +213,15 @@
 	  This provides support for the SD/MMC cell found in TC6393XB,
 	  T7L66XB and also ipaq ASIC3
 
+config MMC_CB710
+	tristate "ENE CB710 MMC/SD Interface support"
+	depends on PCI
+	select CB710_CORE
+	help
+	  This option enables support for MMC/SD part of ENE CB710/720 Flash
+	  memory card reader found in some laptops (ie. some versions of
+	  HP Compaq nx9500).
+
+	  This driver can also be built as a module. If so, the module
+	  will be called cb710-mmc.
+
diff -urN linux-2.6.29.2/drivers/mmc/host/Makefile cb710-tree/drivers/mmc/host/Makefile
--- linux-2.6.29.2/drivers/mmc/host/Makefile	2009-05-04 20:39:18.000000000 +0200
+++ cb710-tree/drivers/mmc/host/Makefile	2009-05-04 20:38:26.000000000 +0200
@@ -27,4 +27,5 @@
 obj-$(CONFIG_MMC_S3C)   	+= s3cmci.o
 obj-$(CONFIG_MMC_SDRICOH_CS)	+= sdricoh_cs.o
 obj-$(CONFIG_MMC_TMIO)		+= tmio_mmc.o
+obj-$(CONFIG_MMC_CB710)	+= cb710-mmc.o
 
diff -urN linux-2.6.29.2/include/linux/cb710.h cb710-tree/include/linux/cb710.h
--- linux-2.6.29.2/include/linux/cb710.h	1970-01-01 01:00:00.000000000 +0100
+++ cb710-tree/include/linux/cb710.h	2009-05-04 20:29:59.000000000 +0200
@@ -0,0 +1,134 @@
+/*
+ *  cb710/cb710.h
+ *
+ *  Copyright by Michał Mirosław, 2008-2009
+ *
+ * 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.
+ */
+#ifndef LINUX_CB710_DRIVER_H
+#define LINUX_CB710_DRIVER_H
+
+#ifdef CONFIG_CB710_DEBUG
+#define DEBUG
+#endif
+
+/* verify assumptions on platform_device framework */
+#define CONFIG_CB710_DEBUG_ASSUMPTIONS
+
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+#include <linux/pci.h>
+#include <linux/platform_device.h>
+#include <linux/mmc/host.h>
+
+struct cb710_slot;
+
+typedef int (*cb710_irq_handler_t)(struct cb710_slot *);
+
+/* per-virtual-slot structure */
+struct cb710_slot {
+	struct platform_device	pdev;
+	void __iomem		*iobase;
+	cb710_irq_handler_t	irq_handler;
+};
+
+/* per-device structure */
+struct cb710_chip {
+	struct pci_dev		*pdev;
+	void __iomem		*iobase;
+	unsigned		platform_id;
+#ifdef CONFIG_CB710_DEBUG_ASSUMPTIONS
+	atomic_t		slot_refs_count;
+#endif
+	unsigned		slot_mask;
+	unsigned		slots;
+	spinlock_t		irq_lock;
+	struct cb710_slot	slot[0];
+};
+
+/* NOTE: cb710_chip.slots is modified only during device init/exit and
+ * they are all serialized wrt themselves */
+
+/* cb710_chip.slot_mask values */
+#define CB710_SLOT_MMC		1
+#define CB710_SLOT_MS		2
+#define CB710_SLOT_SM		4
+
+/* slot port accessors - in case it turns out inX() is all that is needed */
+#define CB710_PORT_ACCESSORS(t) \
+static inline void cb710_write_port_##t(struct cb710_slot *slot,	\
+	unsigned port, u##t value)					\
+{									\
+	iowrite##t(value, slot->iobase + port);				\
+}									\
+									\
+static inline u##t cb710_read_port_##t(struct cb710_slot *slot,		\
+	unsigned port)							\
+{									\
+	return ioread##t(slot->iobase + port);				\
+}									\
+									\
+static inline void cb710_modify_port_##t(struct cb710_slot *slot,	\
+	unsigned port, u##t set, u##t clear)				\
+{									\
+	iowrite##t(							\
+		(ioread##t(slot->iobase + port) & ~clear)|set,		\
+		slot->iobase + port);					\
+}
+
+CB710_PORT_ACCESSORS(8)
+CB710_PORT_ACCESSORS(16)
+CB710_PORT_ACCESSORS(32)
+
+void cb710_pci_update_config_reg(struct pci_dev *pdev,
+	int reg, uint32_t and, uint32_t xor);
+void cb710_set_irq_handler(struct cb710_slot *slot,
+	cb710_irq_handler_t handler);
+
+/* some device struct walking */
+
+static inline struct cb710_slot *cb710_pdev_to_slot(
+	struct platform_device *pdev)
+{
+	return container_of(pdev, struct cb710_slot, pdev);
+}
+
+static inline struct cb710_chip *cb710_slot_to_chip(struct cb710_slot *slot)
+{
+	return dev_get_drvdata(slot->pdev.dev.parent);
+}
+
+static inline struct device *cb710_slot_dev(struct cb710_slot *slot)
+{
+	return &slot->pdev.dev;
+}
+
+static inline struct device *cb710_chip_dev(struct cb710_chip *chip)
+{
+	return &chip->pdev->dev;
+}
+
+/* debugging aids */
+
+#ifdef CONFIG_CB710_DEBUG
+void cb710_dump_regs(struct cb710_chip *chip, unsigned dump);
+#else
+#define cb710_dump_regs(c, d) do {} while (0)
+#endif
+
+#define CB710_DUMP_REGS_MMC	0x0F
+#define CB710_DUMP_REGS_MS	0x30
+#define CB710_DUMP_REGS_SM	0xC0
+#define CB710_DUMP_REGS_ALL	0xFF
+#define CB710_DUMP_REGS_MASK	0xFF
+
+#define CB710_DUMP_ACCESS_8	0x100
+#define CB710_DUMP_ACCESS_16	0x200
+#define CB710_DUMP_ACCESS_32	0x400
+#define CB710_DUMP_ACCESS_ALL	0x700
+#define CB710_DUMP_ACCESS_MASK	0x700
+
+#endif /* LINUX_CB710_DRIVER_H */
diff -urN linux-2.6.29.2/include/linux/pci_ids.h cb710-tree/include/linux/pci_ids.h
--- linux-2.6.29.2/include/linux/pci_ids.h	2009-03-24 00:12:14.000000000 +0100
+++ cb710-tree/include/linux/pci_ids.h	2009-05-04 20:31:45.000000000 +0200
@@ -2077,6 +2077,7 @@
 #define PCI_VENDOR_ID_MAINPINE		0x1522
 #define PCI_DEVICE_ID_MAINPINE_PBRIDGE	0x0100
 #define PCI_VENDOR_ID_ENE		0x1524
+#define PCI_DEVICE_ID_ENE_CB710_FLASH	0x0510
 #define PCI_DEVICE_ID_ENE_CB712_SD	0x0550
 #define PCI_DEVICE_ID_ENE_CB712_SD_2	0x0551
 #define PCI_DEVICE_ID_ENE_CB714_SD	0x0750
diff -urN linux-2.6.29.2/include/linux/sgbuf2.h cb710-tree/include/linux/sgbuf2.h
--- linux-2.6.29.2/include/linux/sgbuf2.h	1970-01-01 01:00:00.000000000 +0100
+++ cb710-tree/include/linux/sgbuf2.h	2009-05-04 20:29:59.000000000 +0200
@@ -0,0 +1,104 @@
+/*
+ *  cb710/sgbuf2.h
+ *
+ *  Copyright by Michał Mirosław, 2008-2009
+ *
+ * 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.
+ */
+#ifndef LINUX_CB710_SG_H
+#define LINUX_CB710_SG_H
+
+#include <linux/highmem.h>
+#include <linux/scatterlist.h>
+
+/**
+ * sg_miter_stop_writing - stop mapping iteration after writing
+ * @miter: sg mapping iter to be stopped
+ *
+ * Description:
+ *   Stops mapping iterator @miter.  @miter should have been started
+ *   started using sg_miter_start().  A stopped iteration can be
+ *   resumed by calling sg_miter_next() on it.  This is useful when
+ *   resources (kmap) need to be released during iteration.
+ *
+ *   This is a convenience wrapper that will be optimized out for arches
+ *   that don't need flush_kernel_dcache_page().
+ *
+ * Context:
+ *   IRQ disabled if the SG_MITER_ATOMIC is set.  Don't care otherwise.
+ */
+static inline void sg_miter_stop_writing(struct sg_mapping_iter *miter)
+{
+	if (miter->page)
+		flush_kernel_dcache_page(miter->page);
+	sg_miter_stop(miter);
+}
+
+/*
+ * 32-bit PIO mapping sg iterator
+ *
+ * Hides scatterlist access issues - fragment boundaries, alignment, page
+ * mapping - for drivers using 32-bit-word-at-a-time-PIO (ie. PCI devices
+ * without DMA support).
+ *
+ * Best-case reading (transfer from device):
+ *   sg_miter_start();
+ *   sg_dwiter_write_from_io();
+ *   sg_miter_stop_writing();
+ *
+ * Best-case writing (transfer to device):
+ *   sg_miter_start();
+ *   sg_dwiter_read_to_io();
+ *   sg_miter_stop();
+ */
+
+uint32_t sg_dwiter_read_next_block(struct sg_mapping_iter *miter);
+void sg_dwiter_write_next_block(struct sg_mapping_iter *miter, uint32_t data);
+
+/**
+ * sg_dwiter_write_from_io - transfer data to mapped buffer from 32-bit IO port
+ * @miter: sg mapping iter
+ * @port: PIO port - IO or MMIO address
+ * @count: number of 32-bit words to transfer
+ *
+ * Description:
+ *   Reads @count 32-bit words from register @port and stores it in
+ *   buffer iterated by @miter.  Data that would overflow the buffer
+ *   is silently ignored.  Iterator is advanced by 4*@count bytes
+ *   or to the buffer's end whichever is closer.
+ *
+ * Context:
+ *   IRQ disabled if the SG_MITER_ATOMIC is set.  Don't care otherwise.
+ */
+static inline void sg_dwiter_write_from_io(struct sg_mapping_iter *miter,
+	void __iomem *port, size_t count)
+{
+	while (count-- > 0)
+		sg_dwiter_write_next_block(miter, ioread32(port));
+}
+
+/**
+ * sg_dwiter_read_to_io - transfer data to 32-bit IO port from mapped buffer
+ * @miter: sg mapping iter
+ * @port: PIO port - IO or MMIO address
+ * @count: number of 32-bit words to transfer
+ *
+ * Description:
+ *   Writes @count 32-bit words to register @port from buffer iterated
+ *   through @miter.  If buffer ends before @count words are written
+ *   missing data is replaced by zeroes. @miter is advanced by 4*@count
+ *   bytes or to the buffer's end whichever is closer.
+ *
+ * Context:
+ *   IRQ disabled if the SG_MITER_ATOMIC is set.  Don't care otherwise.
+ */
+static inline void sg_dwiter_read_to_io(struct sg_mapping_iter *miter,
+	void __iomem *port, size_t count)
+{
+	while (count-- > 0)
+		iowrite32(sg_dwiter_read_next_block(miter), port);
+}
+
+#endif /* LINUX_CB710_SG_H */

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

* Re: [PATCH/RFC 2.6.29.2] Driver for CB710/720 memory card reader (MMC part) - v4
  2009-05-08 15:16                       ` [PATCH/RFC 2.6.29.2] Driver for CB710/720 memory card reader (MMC part) - v4 Michał Mirosław
@ 2009-05-22 11:27                         ` Pierre Ossman
  2009-05-22 17:55                           ` [PATCH 2.6.29.3] Driver for CB710/720 memory card reader (MMC part) - v5 Michał Mirosław
  2009-05-22 19:04                           ` [PATCH/RFC 2.6.29.2] Driver for CB710/720 memory card reader (MMC part) - v4 Bartlomiej Zolnierkiewicz
  0 siblings, 2 replies; 21+ messages in thread
From: Pierre Ossman @ 2009-05-22 11:27 UTC (permalink / raw)
  To: Michał Mirosław; +Cc: linux-kernel

[-- Attachment #1: Type: text/plain, Size: 1409 bytes --]

On Fri, 8 May 2009 17:16:22 +0200
Michał Mirosław <mirq@rere.qmqm.pl> wrote:

> Here is next version of the CB710 SD/MMC reader driver. As in previous
> versions the code is divided in two parts - virtual 'bus' driver that
> handles PCI device and registers three new devices one per card reader
> type. The other device handles SD/MMC part of the reader.
> 
> Key changes from v3:
>  - kerneldoc for sg_mapping_iterator extension
>  - free/restore IRQ during suspend/resume
>  - protect slot->irq_handler by spinlock instead of RCU
>  - remove verify_serialization()
>  - Kconfig
>  - complete patch for linux kernel 2.6.29.2
> 

Looks good to me. I just want you to add a MAINTAINERS entry so that
people know who to contact if they have problems with this driver.

> My patches for sg_mapping_iterator extension went to /dev/null, so I have
> incorporated them to the cb710 core driver module for now. Probably someone
> with more karma points should look at them (sgbuf2.{c,h}) and push further
> if they are of any value to other driver writers.

Give them to Andrew Morton. He's good at finding the right people and
making sure they notice. :)

Rgds
-- 
     -- Pierre Ossman

  WARNING: This correspondence is being monitored by the
  Swedish government. Make sure your server uses encryption
  for SMTP traffic and consider using PGP for end-to-end
  encryption.

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 198 bytes --]

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

* [PATCH 2.6.29.3] Driver for CB710/720 memory card reader (MMC part) - v5
  2009-05-22 11:27                         ` Pierre Ossman
@ 2009-05-22 17:55                           ` Michał Mirosław
  2009-05-22 18:33                             ` [PATCH 2.6.29.4] Driver for CB710/720 memory card reader (MMC part) - v5 fixed Michał Mirosław
  2009-05-22 19:04                           ` [PATCH/RFC 2.6.29.2] Driver for CB710/720 memory card reader (MMC part) - v4 Bartlomiej Zolnierkiewicz
  1 sibling, 1 reply; 21+ messages in thread
From: Michał Mirosław @ 2009-05-22 17:55 UTC (permalink / raw)
  To: Pierre Ossman; +Cc: linux-kernel

Here is a version of the CB710 SD/MMC reader driver ready for inclusion
upstream. The code is divided in two parts. There is a virtual 'bus' driver
that handles PCI device and registers three new devices one per card reader
type. The other driver handles SD/MMC part of the reader.

Changes from v4:
 - sg_mapping_iterator extension renamed to avoid namespace clashes
   when/if the extension would be merged upstream
 - appended sgbuf2.h to cb710.h
 - move from IDR to IDA for device ID generation
 - cleanup after IDA on module unload
 - added MAINTAINERS entry

Signed-off-by: Michał Mirosław <mirq-linux@rere.qmqm.pl>

diff -urpN linux-2.6.29.3/drivers/misc/cb710/core.c test2/drivers/misc/cb710/core.c
--- linux-2.6.29.3/drivers/misc/cb710/core.c	1970-01-01 01:00:00.000000000 +0100
+++ test2/drivers/misc/cb710/core.c	2009-05-22 19:47:19.000000000 +0200
@@ -0,0 +1,357 @@
+/*
+ *  cb710/core.c
+ *
+ *  Copyright by Michał Mirosław, 2008-2009
+ *
+ * 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.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <linux/spinlock.h>
+#include <linux/idr.h>
+#include <linux/cb710.h>
+
+static DEFINE_IDA(cb710_ida);
+static DEFINE_SPINLOCK(cb710_ida_lock);
+
+void cb710_pci_update_config_reg(struct pci_dev *pdev,
+	int reg, uint32_t mask, uint32_t xor)
+{
+	u32 rval;
+
+	pci_read_config_dword(pdev, reg, &rval);
+	rval = (rval & mask) ^ xor;
+	pci_write_config_dword(pdev, reg, rval);
+}
+EXPORT_SYMBOL_GPL(cb710_pci_update_config_reg);
+
+/* Some magic writes based on Windows driver init code */
+static int __devinit cb710_pci_configure(struct pci_dev *pdev)
+{
+	unsigned int devfn = PCI_DEVFN(PCI_SLOT(pdev->devfn), 0);
+	struct pci_dev *pdev0 = pci_get_slot(pdev->bus, devfn);
+	u32 val;
+
+	cb710_pci_update_config_reg(pdev, 0x48,
+		~0x000000FF, 0x0000003F);
+
+	pci_read_config_dword(pdev, 0x48, &val);
+	if (val & 0x80000000)
+		return 0;
+
+	if (!pdev0)
+		return -ENODEV;
+
+	if (pdev0->vendor == PCI_VENDOR_ID_ENE
+	    && pdev0->device == PCI_DEVICE_ID_ENE_720) {
+		cb710_pci_update_config_reg(pdev0, 0x8C,
+			~0x00F00000, 0x00100000);
+		cb710_pci_update_config_reg(pdev0, 0xB0,
+			~0x08000000, 0x08000000);
+	}
+
+	cb710_pci_update_config_reg(pdev0, 0x8C,
+		~0x00000F00, 0x00000200);
+	cb710_pci_update_config_reg(pdev0, 0x90,
+		~0x00060000, 0x00040000);
+
+	pci_dev_put(pdev0);
+
+	return 0;
+}
+
+static irqreturn_t cb710_irq_handler(int irq, void *data)
+{
+	struct cb710_chip *chip = data;
+	struct cb710_slot *slot = &chip->slot[0];
+	irqreturn_t handled = IRQ_NONE;
+	unsigned nr;
+
+	spin_lock(&chip->irq_lock); /* incl. smp_rmb() */
+
+	for (nr = chip->slots; nr; ++slot, --nr) {
+		cb710_irq_handler_t handler_func = slot->irq_handler;
+		if (handler_func && handler_func(slot))
+			handled = IRQ_HANDLED;
+	}
+
+	spin_unlock(&chip->irq_lock);
+
+	return handled;
+}
+
+static void cb710_release_slot(struct device *dev)
+{
+#ifdef CONFIG_CB710_DEBUG_ASSUMPTIONS
+	struct cb710_slot *slot = cb710_pdev_to_slot(to_platform_device(dev));
+	struct cb710_chip *chip = cb710_slot_to_chip(slot);
+
+	/* slot struct can be freed now */
+	atomic_dec(&chip->slot_refs_count);
+#endif
+}
+
+static int __devinit cb710_register_slot(struct cb710_chip *chip,
+	unsigned slot_mask, unsigned io_offset, const char *name)
+{
+	int nr = chip->slots;
+	struct cb710_slot *slot = &chip->slot[nr];
+	int err;
+
+	dev_dbg(cb710_chip_dev(chip),
+		"register: %s.%d; slot %d; mask %d; IO offset: 0x%02X\n",
+		name, chip->platform_id, nr, slot_mask, io_offset);
+
+	/* slot->irq_handler == NULL here; this needs to be
+	 * seen before platform_device_register() */
+	++chip->slots;
+	smp_wmb();
+
+	slot->iobase = chip->iobase + io_offset;
+	slot->pdev.name = name;
+	slot->pdev.id = chip->platform_id;
+	slot->pdev.dev.parent = &chip->pdev->dev;
+	slot->pdev.dev.release = cb710_release_slot;
+
+	err = platform_device_register(&slot->pdev);
+
+#ifdef CONFIG_CB710_DEBUG_ASSUMPTIONS
+	atomic_inc(&chip->slot_refs_count);
+#endif
+
+	if (err) {
+		/* device_initialize() called from platform_device_register()
+		 * wants this on error path */
+		platform_device_put(&slot->pdev);
+
+		/* slot->irq_handler == NULL here anyway, so no lock needed */
+		--chip->slots;
+		return err;
+	}
+
+	chip->slot_mask |= slot_mask;
+
+	return 0;
+}
+
+static void cb710_unregister_slot(struct cb710_chip *chip,
+	unsigned slot_mask)
+{
+	int nr = chip->slots - 1;
+
+	if (!(chip->slot_mask & slot_mask))
+		return;
+
+	platform_device_unregister(&chip->slot[nr].pdev);
+
+	/* complementary to spin_unlock() in cb710_set_irq_handler() */
+	smp_rmb();
+	BUG_ON(chip->slot[nr].irq_handler != NULL);
+
+	/* slot->irq_handler == NULL here, so no lock needed */
+	--chip->slots;
+	chip->slot_mask &= ~slot_mask;
+}
+
+void cb710_set_irq_handler(struct cb710_slot *slot,
+	cb710_irq_handler_t handler)
+{
+	struct cb710_chip *chip = cb710_slot_to_chip(slot);
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->irq_lock, flags);
+	slot->irq_handler = handler;
+	spin_unlock_irqrestore(&chip->irq_lock, flags);
+}
+EXPORT_SYMBOL_GPL(cb710_set_irq_handler);
+
+#ifdef CONFIG_PM
+
+static int cb710_suspend(struct pci_dev *pdev, pm_message_t state)
+{
+	struct cb710_chip *chip = pci_get_drvdata(pdev);
+
+	free_irq(pdev->irq, chip);
+	pci_save_state(pdev);
+	pci_disable_device(pdev);
+	if (state.event & PM_EVENT_SLEEP)
+		pci_set_power_state(pdev, PCI_D3cold);
+	return 0;
+}
+
+static int cb710_resume(struct pci_dev *pdev)
+{
+	struct cb710_chip *chip = pci_get_drvdata(pdev);
+	int err;
+
+	pci_set_power_state(pdev, PCI_D0);
+	pci_restore_state(pdev);
+	err = pcim_enable_device(pdev);
+	if (err)
+		return err;
+
+	return devm_request_irq(&pdev->dev, pdev->irq,
+		cb710_irq_handler, IRQF_SHARED, KBUILD_MODNAME, chip);
+}
+
+#endif /* CONFIG_PM */
+
+static int __devinit cb710_probe(struct pci_dev *pdev,
+	const struct pci_device_id *ent)
+{
+	struct cb710_chip *chip;
+	unsigned long flags;
+	u32 val;
+	int err;
+	int n = 0;
+
+	err = cb710_pci_configure(pdev);
+	if (err)
+		return err;
+
+	/* this is actually magic... */
+	pci_read_config_dword(pdev, 0x48, &val);
+	if (!(val & 0x80000000)) {
+		pci_write_config_dword(pdev, 0x48, val|0x71000000);
+		pci_read_config_dword(pdev, 0x48, &val);
+	}
+
+	dev_dbg(&pdev->dev, "PCI config[0x48] = 0x%08X\n", val);
+	if (!(val & 0x70000000))
+		return -ENODEV;
+	val = (val >> 28) & 7;
+	if (val & CB710_SLOT_MMC)
+		++n;
+	if (val & CB710_SLOT_MS)
+		++n;
+	if (val & CB710_SLOT_SM)
+		++n;
+
+	chip = devm_kzalloc(&pdev->dev,
+		sizeof(*chip) + n * sizeof(*chip->slot), GFP_KERNEL);
+	if (!chip)
+		return -ENOMEM;
+
+	err = pcim_enable_device(pdev);
+	if (err)
+		return err;
+
+	err = pcim_iomap_regions(pdev, 0x0001, KBUILD_MODNAME);
+	if (err)
+		return err;
+
+	chip->pdev = pdev;
+	chip->iobase = pcim_iomap_table(pdev)[0];
+
+	pci_set_drvdata(pdev, chip);
+
+	err = devm_request_irq(&pdev->dev, pdev->irq,
+		cb710_irq_handler, IRQF_SHARED, KBUILD_MODNAME, chip);
+	if (err)
+		return err;
+
+	do {
+		if (!ida_pre_get(&cb710_ida, GFP_KERNEL))
+			return -ENOMEM;
+
+		spin_lock_irqsave(&cb710_ida_lock, flags);
+		err = ida_get_new(&cb710_ida, &chip->platform_id);
+		spin_unlock_irqrestore(&cb710_ida_lock, flags);
+
+		if (err && err != -EAGAIN)
+			return err;
+	} while (err);
+
+
+	dev_info(&pdev->dev, "id %d, IO 0x%p, IRQ %d\n",
+		chip->platform_id, chip->iobase, pdev->irq);
+
+	if (val & CB710_SLOT_MMC) {	/* MMC/SD slot */
+		err = cb710_register_slot(chip,
+			CB710_SLOT_MMC, 0x00, "cb710-mmc");
+		if (err)
+			return err;
+	}
+
+	if (val & CB710_SLOT_MS) {	/* MemoryStick slot */
+		err = cb710_register_slot(chip,
+			CB710_SLOT_MS, 0x40, "cb710-ms");
+		if (err)
+			goto unreg_mmc;
+	}
+
+	if (val & CB710_SLOT_SM) {	/* SmartMedia slot */
+		err = cb710_register_slot(chip,
+			CB710_SLOT_SM, 0x60, "cb710-sm");
+		if (err)
+			goto unreg_ms;
+	}
+
+	return 0;
+unreg_ms:
+	cb710_unregister_slot(chip, CB710_SLOT_MS);
+unreg_mmc:
+	cb710_unregister_slot(chip, CB710_SLOT_MMC);
+
+#ifdef CONFIG_CB710_DEBUG_ASSUMPTIONS
+	BUG_ON(atomic_read(&chip->slot_refs_count) != 0);
+#endif
+	return err;
+}
+
+static void __devexit cb710_remove_one(struct pci_dev *pdev)
+{
+	struct cb710_chip *chip = pci_get_drvdata(pdev);
+	unsigned long flags;
+
+	cb710_unregister_slot(chip, CB710_SLOT_SM);
+	cb710_unregister_slot(chip, CB710_SLOT_MS);
+	cb710_unregister_slot(chip, CB710_SLOT_MMC);
+#ifdef CONFIG_CB710_DEBUG_ASSUMPTIONS
+	BUG_ON(atomic_read(&chip->slot_refs_count) != 0);
+#endif
+
+	spin_lock_irqsave(&cb710_ida_lock, flags);
+	ida_remove(&cb710_ida, chip->platform_id);
+	spin_unlock_irqrestore(&cb710_ida_lock, flags);
+}
+
+static const struct pci_device_id cb710_pci_tbl[] = {
+	{ PCI_VENDOR_ID_ENE, PCI_DEVICE_ID_ENE_CB710_FLASH,
+		PCI_ANY_ID, PCI_ANY_ID, },
+	{ 0, }
+};
+
+static struct pci_driver cb710_driver = {
+	.name = KBUILD_MODNAME,
+	.id_table = cb710_pci_tbl,
+	.probe = cb710_probe,
+	.remove = __devexit_p(cb710_remove_one),
+#ifdef CONFIG_PM
+	.suspend = cb710_suspend,
+	.resume = cb710_resume,
+#endif
+};
+
+static int __init cb710_init_module(void)
+{
+	return pci_register_driver(&cb710_driver);
+}
+
+static void __exit cb710_cleanup_module(void)
+{
+	pci_unregister_driver(&cb710_driver);
+	ida_destroy(&cb710_ida);
+}
+
+module_init(cb710_init_module);
+module_exit(cb710_cleanup_module);
+
+MODULE_AUTHOR("Michał Mirosław <mirq-linux@rere.qmqm.pl>");
+MODULE_DESCRIPTION("ENE CB710 memory card reader driver");
+MODULE_LICENSE("GPL");
+MODULE_DEVICE_TABLE(pci, cb710_pci_tbl);
diff -urpN linux-2.6.29.3/drivers/misc/cb710/debug.c test2/drivers/misc/cb710/debug.c
--- linux-2.6.29.3/drivers/misc/cb710/debug.c	1970-01-01 01:00:00.000000000 +0100
+++ test2/drivers/misc/cb710/debug.c	2009-05-22 19:47:19.000000000 +0200
@@ -0,0 +1,119 @@
+/*
+ *  cb710/debug.c
+ *
+ *  Copyright by Michał Mirosław, 2008-2009
+ *
+ * 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.
+ */
+#include <linux/cb710.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+#define CB710_REG_COUNT		0x80
+
+static const u16 allow[CB710_REG_COUNT/16] = {
+	0xFFF0, 0xFFFF, 0xFFFF, 0xFFFF,
+	0xFFF0, 0xFFFF, 0xFFFF, 0xFFFF,
+};
+static const char *const prefix[ARRAY_SIZE(allow)] = {
+	"MMC", "MMC", "MMC", "MMC",
+	"MS?", "MS?", "SM?", "SM?"
+};
+
+static inline int allow_reg_read(unsigned block, unsigned offset, unsigned bits)
+{
+	unsigned mask = (1 << bits/8) - 1;
+	offset *= bits/8;
+	return ((allow[block] >> offset) & mask) == mask;
+}
+
+#define CB710_READ_REGS_TEMPLATE(t)					\
+static void cb710_read_regs_##t(void __iomem *iobase,			\
+	u##t *reg, unsigned select)					\
+{									\
+	unsigned i, j;							\
+									\
+	for (i = 0; i < ARRAY_SIZE(allow); ++i, reg += 16/(t/8)) {	\
+		if (!select & (1 << i))					\
+			continue;					\
+									\
+		for (j = 0; j < 0x10/(t/8); ++j) {			\
+			if (!allow_reg_read(i, j, t))			\
+				continue;				\
+			reg[j] = ioread##t(iobase			\
+				+ (i << 4) + (j * (t/8)));		\
+		}							\
+	}								\
+}
+
+static const char cb710_regf_8[] = "%02X";
+static const char cb710_regf_16[] = "%04X";
+static const char cb710_regf_32[] = "%08X";
+static const char cb710_xes[] = "xxxxxxxx";
+
+#define CB710_DUMP_REGS_TEMPLATE(t)					\
+static void cb710_dump_regs_##t(struct device *dev,			\
+	const u##t *reg, unsigned select)				\
+{									\
+	const char *const xp = &cb710_xes[8 - t/4];			\
+	const char *const format = cb710_regf_##t;			\
+									\
+	char msg[100], *p;						\
+	unsigned i, j;							\
+									\
+	for (i = 0; i < ARRAY_SIZE(allow); ++i, reg += 16/(t/8)) {	\
+		if (!(select & (1 << i)))				\
+			continue;					\
+		p = msg;						\
+		for (j = 0; j < 0x10/(t/8); ++j) {			\
+			*p++ = ' ';					\
+			if (j == 8/(t/8))				\
+				*p++ = ' ';				\
+			if (allow_reg_read(i, j, t))			\
+				p += sprintf(p, format, reg[j]);	\
+			else						\
+				p += sprintf(p, "%s", xp);		\
+		}							\
+		dev_dbg(dev, "%s 0x%02X %s\n", prefix[i], i << 4, msg);	\
+	}								\
+}
+
+#define CB710_READ_AND_DUMP_REGS_TEMPLATE(t)				\
+static void cb710_read_and_dump_regs_##t(struct cb710_chip *chip,	\
+	unsigned select)						\
+{									\
+	u##t regs[CB710_REG_COUNT/sizeof(u##t)];			\
+									\
+	memset(&regs, 0, sizeof(regs));					\
+	cb710_read_regs_##t(chip->iobase, regs, select);		\
+	cb710_dump_regs_##t(cb710_chip_dev(chip), regs, select);	\
+}
+
+#define CB710_REG_ACCESS_TEMPLATES(t)		\
+  CB710_READ_REGS_TEMPLATE(t)			\
+  CB710_DUMP_REGS_TEMPLATE(t)			\
+  CB710_READ_AND_DUMP_REGS_TEMPLATE(t)
+
+CB710_REG_ACCESS_TEMPLATES(8)
+CB710_REG_ACCESS_TEMPLATES(16)
+CB710_REG_ACCESS_TEMPLATES(32)
+
+void cb710_dump_regs(struct cb710_chip *chip, unsigned select)
+{
+	if (!(select & CB710_DUMP_REGS_MASK))
+		select = CB710_DUMP_REGS_ALL;
+	if (!(select & CB710_DUMP_ACCESS_MASK))
+		select |= CB710_DUMP_ACCESS_8;
+
+	if (select & CB710_DUMP_ACCESS_32)
+		cb710_read_and_dump_regs_32(chip, select);
+	if (select & CB710_DUMP_ACCESS_16)
+		cb710_read_and_dump_regs_16(chip, select);
+	if (select & CB710_DUMP_ACCESS_8)
+		cb710_read_and_dump_regs_8(chip, select);
+}
+EXPORT_SYMBOL_GPL(cb710_dump_regs);
+
diff -urpN linux-2.6.29.3/drivers/misc/cb710/Kconfig test2/drivers/misc/cb710/Kconfig
--- linux-2.6.29.3/drivers/misc/cb710/Kconfig	1970-01-01 01:00:00.000000000 +0100
+++ test2/drivers/misc/cb710/Kconfig	2009-05-22 19:47:19.000000000 +0200
@@ -0,0 +1,21 @@
+config CB710_CORE
+	tristate "ENE CB710/720 Flash memory card reader support"
+	depends on PCI
+	help
+	  This option enables support for PCI ENE CB710/720 Flash memory card
+	  reader found in some laptops (ie. some versions of HP Compaq nx9500).
+
+	  You will also have to select some flash card format drivers (MMC/SD,
+	  MemoryStick).
+
+	  This driver can also be built as a module. If so, the module
+	  will be called cb710.
+
+config CB710_DEBUG
+	bool "Enable driver debugging"
+	depends on CB710_CORE != n
+	default n
+	help
+	  This is an option for use by developers; most people should
+	  say N here.  This adds a lot of debugging output to dmesg.
+
diff -urpN linux-2.6.29.3/drivers/misc/cb710/Makefile test2/drivers/misc/cb710/Makefile
--- linux-2.6.29.3/drivers/misc/cb710/Makefile	1970-01-01 01:00:00.000000000 +0100
+++ test2/drivers/misc/cb710/Makefile	2009-05-22 19:47:19.000000000 +0200
@@ -0,0 +1,4 @@
+obj-$(CONFIG_CB710_CORE)	+= cb710.o
+
+cb710-y				:= core.o sgbuf2.o
+cb710-$(CONFIG_CB710_DEBUG)	+= debug.o
diff -urpN linux-2.6.29.3/drivers/misc/cb710/sgbuf2.c test2/drivers/misc/cb710/sgbuf2.c
--- linux-2.6.29.3/drivers/misc/cb710/sgbuf2.c	1970-01-01 01:00:00.000000000 +0100
+++ test2/drivers/misc/cb710/sgbuf2.c	2009-05-22 19:47:19.000000000 +0200
@@ -0,0 +1,150 @@
+/*
+ *  cb710/sgbuf2.c
+ *
+ *  Copyright by Michał Mirosław, 2008-2009
+ *
+ * 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.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/cb710.h>
+
+static bool sg_dwiter_next(struct sg_mapping_iter *miter)
+{
+	if (sg_miter_next(miter)) {
+		miter->consumed = 0;
+		return true;
+	} else
+		return false;
+}
+
+static bool sg_dwiter_is_at_end(struct sg_mapping_iter *miter)
+{
+	return miter->length == miter->consumed && !sg_dwiter_next(miter);
+}
+
+static uint32_t sg_dwiter_read_buffer(struct sg_mapping_iter *miter)
+{
+	size_t len, left = 4;
+	uint32_t data;
+	void *addr = &data;
+
+	do {
+		len = min(miter->length - miter->consumed, left);
+		memcpy(addr, miter->addr + miter->consumed, len);
+		miter->consumed += len;
+		left -= len;
+		if (!left)
+			return data;
+		addr += len;
+	} while (sg_dwiter_next(miter));
+
+	memset(addr, 0, left);
+	return data;
+}
+
+static inline bool needs_unaligned_copy(const void *ptr)
+{
+#ifdef HAVE_EFFICIENT_UNALIGNED_ACCESS
+	return false;
+#else
+	return ((ptr - NULL) & 3) != 0;
+#endif
+}
+
+static bool sg_dwiter_get_next_block(struct sg_mapping_iter *miter, uint32_t **ptr)
+{
+	size_t len;
+
+	if (sg_dwiter_is_at_end(miter))
+		return true;
+
+	len = miter->length - miter->consumed;
+
+	if (likely(len >= 4 && !needs_unaligned_copy(
+			miter->addr + miter->consumed))) {
+		*ptr = miter->addr + miter->consumed;
+		miter->consumed += 4;
+		return true;
+	}
+
+	return false;
+}
+
+/**
+ * cb710_sg_dwiter_read_next_block() - get next 32-bit word from sg buffer
+ * @miter: sg mapping iterator used for reading
+ *
+ * Description:
+ *   Returns 32-bit word starting at byte pointed to by @miter@
+ *   handling any alignment issues.  Bytes past the buffer's end
+ *   are not accessed (read) but are returned as zeroes.  @miter@
+ *   is advanced by 4 bytes or to the end of buffer whichever is
+ *   closer.
+ *
+ * Context:
+ *   Same requirements as in sg_miter_next().
+ *
+ * Returns:
+ *   32-bit word just read.
+ */
+uint32_t cb710_sg_dwiter_read_next_block(struct sg_mapping_iter *miter)
+{
+	uint32_t *ptr = NULL;
+
+	if (likely(sg_dwiter_get_next_block(miter, &ptr)))
+		return ptr ? *ptr : 0;
+
+	return sg_dwiter_read_buffer(miter);
+}
+EXPORT_SYMBOL_GPL(cb710_sg_dwiter_read_next_block);
+
+static void sg_dwiter_write_slow(struct sg_mapping_iter *miter, uint32_t data)
+{
+	size_t len, left = 4;
+	void *addr = &data;
+
+	do {
+		len = min(miter->length - miter->consumed, left);
+		memcpy(miter->addr, addr, len);
+		miter->consumed += len;
+		left -= len;
+		if (!left)
+			return;
+		addr += len;
+		flush_kernel_dcache_page(miter->page);
+	} while (sg_dwiter_next(miter));
+}
+
+/**
+ * cb710_sg_dwiter_write_next_block() - write next 32-bit word to sg buffer
+ * @miter: sg mapping iterator used for writing
+ *
+ * Description:
+ *   Writes 32-bit word starting at byte pointed to by @miter@
+ *   handling any alignment issues.  Bytes which would be written
+ *   past the buffer's end are silently discarded. @miter@ is
+ *   advanced by 4 bytes or to the end of buffer whichever is closer.
+ *
+ * Context:
+ *   Same requirements as in sg_miter_next().
+ */
+void cb710_sg_dwiter_write_next_block(struct sg_mapping_iter *miter, uint32_t data)
+{
+	uint32_t *ptr = NULL;
+
+	if (likely(sg_dwiter_get_next_block(miter, &ptr))) {
+		if (ptr)
+			*ptr = data;
+		else
+			return;
+	} else
+		sg_dwiter_write_slow(miter, data);
+
+	if (miter->length == miter->consumed)
+		flush_kernel_dcache_page(miter->page);
+}
+EXPORT_SYMBOL_GPL(cb710_sg_dwiter_write_next_block);
+
diff -urpN linux-2.6.29.3/drivers/misc/Kconfig test2/drivers/misc/Kconfig
--- linux-2.6.29.3/drivers/misc/Kconfig	2009-03-24 00:12:14.000000000 +0100
+++ test2/drivers/misc/Kconfig	2009-05-22 19:47:19.000000000 +0200
@@ -225,5 +225,6 @@ config DELL_LAPTOP
 
 source "drivers/misc/c2port/Kconfig"
 source "drivers/misc/eeprom/Kconfig"
+source "drivers/misc/cb710/Kconfig"
 
 endif # MISC_DEVICES
diff -urpN linux-2.6.29.3/drivers/misc/Makefile test2/drivers/misc/Makefile
--- linux-2.6.29.3/drivers/misc/Makefile	2009-03-24 00:12:14.000000000 +0100
+++ test2/drivers/misc/Makefile	2009-05-22 19:47:19.000000000 +0200
@@ -20,3 +20,4 @@ obj-$(CONFIG_SGI_GRU)		+= sgi-gru/
 obj-$(CONFIG_HP_ILO)		+= hpilo.o
 obj-$(CONFIG_C2PORT)		+= c2port/
 obj-y				+= eeprom/
+obj-y				+= cb710/
diff -urpN linux-2.6.29.3/drivers/mmc/host/cb710-mmc.c test2/drivers/mmc/host/cb710-mmc.c
--- linux-2.6.29.3/drivers/mmc/host/cb710-mmc.c	1970-01-01 01:00:00.000000000 +0100
+++ test2/drivers/mmc/host/cb710-mmc.c	2009-05-22 19:47:19.000000000 +0200
@@ -0,0 +1,804 @@
+/*
+ *  cb710/mmc.c
+ *
+ *  Copyright by Michał Mirosław, 2008-2009
+ *
+ * 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.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include "cb710-mmc.h"
+
+static const u8 cb710_clock_divider_log2[8] = {
+/*	1, 2, 4, 8, 16, 32, 128, 512 */
+	0, 1, 2, 3,  4,  5,   7,   9
+};
+#define CB710_MAX_DIVIDER_IDX	\
+	(ARRAY_SIZE(cb710_clock_divider_log2) - 1)
+
+static const u8 cb710_src_freq_mhz[16] = {
+	33, 10, 20, 25, 30, 35, 40, 45,
+	50, 55, 60, 65, 70, 75, 80, 85
+};
+
+static void cb710_mmc_set_clock(struct mmc_host *mmc, int hz)
+{
+	struct cb710_slot *slot = cb710_mmc_to_slot(mmc);
+	struct pci_dev *pdev = cb710_slot_to_chip(slot)->pdev;
+	u32 src_freq_idx;
+	u32 divider_idx;
+	int src_hz;
+
+	/* this is magic, unverifiable for me, unless I get
+	 * MMC card with cables connected to bus signals */
+	pci_read_config_dword(pdev, 0x48, &src_freq_idx);
+	src_freq_idx = (src_freq_idx >> 16) & 0xF;
+	src_hz = cb710_src_freq_mhz[src_freq_idx] * 1000000;
+
+	for (divider_idx = 0; divider_idx < CB710_MAX_DIVIDER_IDX; ++divider_idx) {
+		if (hz >= src_hz >> cb710_clock_divider_log2[divider_idx])
+			break;
+	}
+
+	if (src_freq_idx)
+		divider_idx |= 0x8;
+
+	cb710_pci_update_config_reg(pdev, 0x40, ~0xF0000000, divider_idx << 28);
+
+	dev_dbg(cb710_slot_dev(slot),
+		"clock set to %d Hz, wanted %d Hz; flag = %d\n",
+		src_hz >> cb710_clock_divider_log2[divider_idx & 7],
+		hz, (divider_idx & 8) != 0);
+}
+
+static void __cb710_mmc_enable_irq(struct cb710_slot *slot,
+	unsigned short enable, unsigned short mask)
+{
+	/* clear global IE
+	 * - it gets set later if any interrupt sources are enabled */
+	mask |= CB710_MMC_IE_IRQ_ENABLE;
+
+	/* look like interrupt is fired whenever
+	 * WORD[0x0C] & WORD[0x10] != 0;
+	 * -> bit 15 port 0x0C seems to be global interrupt enable
+	 */
+
+	enable = (cb710_read_port_16(slot, CB710_MMC_IRQ_ENABLE_PORT)
+		& ~mask) | enable;
+
+	if (enable)
+		enable |= CB710_MMC_IE_IRQ_ENABLE;
+
+	cb710_write_port_16(slot, CB710_MMC_IRQ_ENABLE_PORT, enable);
+}
+
+static void cb710_mmc_enable_irq(struct cb710_slot *slot,
+	unsigned short enable, unsigned short mask)
+{
+	struct cb710_mmc_reader *reader = mmc_priv(cb710_slot_to_mmc(slot));
+	unsigned long flags;
+
+	spin_lock_irqsave(&reader->irq_lock, flags);
+	/* this is the only thing irq_lock protects */
+	__cb710_mmc_enable_irq(slot, enable, mask);
+	spin_unlock_irqrestore(&reader->irq_lock, flags);
+}
+
+static void cb710_mmc_reset_events(struct cb710_slot *slot)
+{
+	cb710_write_port_8(slot, CB710_MMC_STATUS0_PORT, 0xFF);
+	cb710_write_port_8(slot, CB710_MMC_STATUS1_PORT, 0xFF);
+	cb710_write_port_8(slot, CB710_MMC_STATUS2_PORT, 0xFF);
+}
+
+static int cb710_mmc_is_card_inserted(struct cb710_slot *slot)
+{
+	return cb710_read_port_8(slot, CB710_MMC_STATUS3_PORT)
+		& CB710_MMC_S3_CARD_DETECTED;
+}
+
+static void cb710_mmc_enable_4bit_data(struct cb710_slot *slot, int enable)
+{
+	dev_dbg(cb710_slot_dev(slot), "configuring %d-data-line%s mode\n",
+		enable ? 4 : 1, enable ? "s" : "");
+	if (enable)
+		cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT,
+			CB710_MMC_C1_4BIT_DATA_BUS, 0);
+	else
+		cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT,
+			0, CB710_MMC_C1_4BIT_DATA_BUS);
+}
+
+static int cb710_check_event(struct cb710_slot *slot, u8 what)
+{
+	u16 status;
+
+	status = cb710_read_port_16(slot, CB710_MMC_STATUS_PORT);
+
+	if (status & CB710_MMC_S0_FIFO_UNDERFLOW) {
+		/* it is just a guess, so log it */
+		dev_dbg(cb710_slot_dev(slot),
+			"CHECK : ignoring bit 6 in status %04X\n", status);
+		cb710_write_port_8(slot, CB710_MMC_STATUS0_PORT,
+			CB710_MMC_S0_FIFO_UNDERFLOW);
+		status &= ~CB710_MMC_S0_FIFO_UNDERFLOW;
+	}
+
+	if (status & CB710_MMC_STATUS_ERROR_EVENTS) {
+		dev_dbg(cb710_slot_dev(slot),
+			"CHECK : returning EIO on status %04X\n", status);
+		cb710_write_port_8(slot, CB710_MMC_STATUS0_PORT, status & 0xFF);
+		cb710_write_port_8(slot, CB710_MMC_STATUS1_PORT,
+			CB710_MMC_S1_RESET);
+		return -EIO;
+	}
+
+	/* 'what' is a bit in MMC_STATUS1 */
+	if ((status >> 8) & what) {
+		cb710_write_port_8(slot, CB710_MMC_STATUS1_PORT, what);
+		return 1;
+	}
+
+	return 0;
+}
+
+static int cb710_wait_for_event(struct cb710_slot *slot, u8 what)
+{
+	int err = 0;
+	unsigned limit = 2000000;	/* FIXME: real timeout */
+
+#ifdef CONFIG_CB710_DEBUG
+	u32 e, x;
+	e = cb710_read_port_32(slot, CB710_MMC_STATUS_PORT);
+#endif
+
+	while (!(err = cb710_check_event(slot, what))) {
+		if (!--limit) {
+			cb710_dump_regs(cb710_slot_to_chip(slot),
+				CB710_DUMP_REGS_MMC);
+			err = -ETIMEDOUT;
+			break;
+		}
+		udelay(1);
+	}
+
+#ifdef CONFIG_CB710_DEBUG
+	x = cb710_read_port_32(slot, CB710_MMC_STATUS_PORT);
+
+	limit = 2000000 - limit;
+	if (limit > 100)
+		dev_dbg(cb710_slot_dev(slot),
+			"WAIT10: waited %d loops, what %d, entry val %08X, exit val %08X\n",
+			limit, what, e, x);
+#endif
+	return err < 0 ? err : 0;
+}
+
+
+static int cb710_wait_while_busy(struct cb710_slot *slot, uint8_t mask)
+{
+	unsigned limit = 500000;	/* FIXME: real timeout */
+	int err = 0;
+
+#ifdef CONFIG_CB710_DEBUG
+	u32 e, x;
+	e = cb710_read_port_32(slot, CB710_MMC_STATUS_PORT);
+#endif
+
+	while (cb710_read_port_8(slot, CB710_MMC_STATUS2_PORT) & mask) {
+		if (!--limit) {
+			cb710_dump_regs(cb710_slot_to_chip(slot),
+				CB710_DUMP_REGS_MMC);
+			err = -ETIMEDOUT;
+			break;
+		}
+		udelay(1);
+	}
+
+#ifdef CONFIG_CB710_DEBUG
+	x = cb710_read_port_32(slot, CB710_MMC_STATUS_PORT);
+
+	limit = 500000 - limit;
+	if (limit > 100)
+		dev_dbg(cb710_slot_dev(slot),
+			"WAIT12: waited %d loops, mask %02X, entry val %08X, exit val %08X\n",
+			limit, mask, e, x);
+#endif
+	return 0;
+}
+
+static void cb710_mmc_set_transfer_size(struct cb710_slot *slot,
+	size_t count, size_t blocksize)
+{
+	cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
+	cb710_write_port_32(slot, CB710_MMC_TRANSFER_SIZE_PORT,
+		((count - 1) << 16)|(blocksize - 1));
+
+	dev_vdbg(cb710_slot_dev(slot), "set up for %d block%s of %d bytes\n",
+		count, count == 1 ? "" : "s", blocksize);
+}
+
+static void cb710_mmc_fifo_hack(struct cb710_slot *slot)
+{
+	/* without this, received data is prepended with 8-bytes of zeroes */
+	u32 r1, r2;
+	int ok = 0;
+
+	r1 = cb710_read_port_32(slot, CB710_MMC_DATA_PORT);
+	r2 = cb710_read_port_32(slot, CB710_MMC_DATA_PORT);
+	if (cb710_read_port_8(slot, CB710_MMC_STATUS0_PORT)
+	    & CB710_MMC_S0_FIFO_UNDERFLOW) {
+		cb710_write_port_8(slot, CB710_MMC_STATUS0_PORT,
+			CB710_MMC_S0_FIFO_UNDERFLOW);
+		ok = 1;
+	}
+
+	dev_dbg(cb710_slot_dev(slot),
+		"FIFO-read-hack: expected STATUS0 bit was %s\n",
+		ok ? "set." : "NOT SET!");
+	dev_dbg(cb710_slot_dev(slot),
+		"FIFO-read-hack: dwords ignored: %08X %08X - %s\n",
+		r1, r2, (r1|r2) ? "BAD (NOT ZERO)!" : "ok");
+}
+
+static int cb710_mmc_receive_pio(struct cb710_slot *slot,
+	struct sg_mapping_iter *miter, size_t dw_count)
+{
+	if (!(cb710_read_port_8(slot, CB710_MMC_STATUS2_PORT) & CB710_MMC_S2_FIFO_READY)) {
+		int err = cb710_wait_for_event(slot,
+			CB710_MMC_S1_PIO_TRANSFER_DONE);
+		if (err)
+			return err;
+	}
+
+	cb710_sg_dwiter_write_from_io(miter,
+		slot->iobase + CB710_MMC_DATA_PORT, dw_count);
+
+	return 0;
+}
+
+static bool cb710_is_transfer_size_supported(struct mmc_data *data)
+{
+	return !(data->blksz & 15 && (data->blocks != 1 || data->blksz != 8));
+}
+
+static int cb710_mmc_receive(struct cb710_slot *slot, struct mmc_data *data)
+{
+	struct sg_mapping_iter miter;
+	size_t len, blocks = data->blocks;
+	int err = 0;
+
+	/* TODO: I don't know how/if the hardware handles non-16B-boundary blocks
+	 * except single 8B block */
+	if (unlikely(data->blksz & 15 && (data->blocks != 1 || data->blksz != 8)))
+		return -EINVAL;
+
+	sg_miter_start(&miter, data->sg, data->sg_len, 0);
+
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG2_PORT,
+		15, CB710_MMC_C2_READ_PIO_SIZE_MASK);
+
+	cb710_mmc_fifo_hack(slot);
+
+	while (blocks-- > 0) {
+		len = data->blksz;
+
+		while (len >= 16) {
+			err = cb710_mmc_receive_pio(slot, &miter, 4);
+			if (err)
+				goto out;
+			len -= 16;
+		}
+
+		if (!len)
+			continue;
+
+		cb710_modify_port_8(slot, CB710_MMC_CONFIG2_PORT,
+			len - 1, CB710_MMC_C2_READ_PIO_SIZE_MASK);
+
+		len = (len >= 8) ? 4 : 2;
+		err = cb710_mmc_receive_pio(slot, &miter, len);
+		if (err)
+			goto out;
+	}
+out:
+	cb710_sg_miter_stop_writing(&miter);
+	return err;
+}
+
+static int cb710_mmc_send(struct cb710_slot *slot, struct mmc_data *data)
+{
+	struct sg_mapping_iter miter;
+	size_t len, blocks = data->blocks;
+	int err = 0;
+
+	/* TODO: I don't know how/if the hardware handles multiple
+	 * non-16B-boundary blocks */
+	if (unlikely(data->blocks > 1 && data->blksz & 15))
+		return -EINVAL;
+
+	sg_miter_start(&miter, data->sg, data->sg_len, 0);
+
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG2_PORT,
+		0, CB710_MMC_C2_READ_PIO_SIZE_MASK);
+
+	while (blocks-- > 0) {
+		len = (data->blksz + 15) >> 4;
+		do {
+			if (!(cb710_read_port_8(slot, CB710_MMC_STATUS2_PORT)
+			    & CB710_MMC_S2_FIFO_EMPTY)) {
+				err = cb710_wait_for_event(slot,
+					CB710_MMC_S1_PIO_TRANSFER_DONE);
+				if (err)
+					goto out;
+			}
+			cb710_sg_dwiter_read_to_io(&miter,
+				slot->iobase + CB710_MMC_DATA_PORT, 4);
+		} while (--len);
+	}
+out:
+	sg_miter_stop(&miter);
+	return err;
+}
+
+static u16 cb710_encode_cmd_flags(struct cb710_mmc_reader *reader,
+	struct mmc_command *cmd)
+{
+	unsigned int flags = cmd->flags;
+	u16 cb_flags = 0;
+
+	/* Windows driver returned 0 for commands for which no response
+	 * is expected. It happened that there were only two such commands
+	 * used: MMC_GO_IDLE_STATE and MMC_GO_INACTIVE_STATE so it might
+	 * as well be a bug in that driver.
+	 *
+	 * Original driver set bit 14 for MMC/SD application
+	 * commands. There's no difference 'on the wire' and
+	 * it apparently works without it anyway.
+	 */
+
+	switch (flags & MMC_CMD_MASK) {
+	case MMC_CMD_AC:	cb_flags = CB710_MMC_CMD_AC;	break;
+	case MMC_CMD_ADTC:	cb_flags = CB710_MMC_CMD_ADTC;	break;
+	case MMC_CMD_BC:	cb_flags = CB710_MMC_CMD_BC;	break;
+	case MMC_CMD_BCR:	cb_flags = CB710_MMC_CMD_BCR;	break;
+	}
+
+	if (flags & MMC_RSP_BUSY)
+		cb_flags |= CB710_MMC_RSP_BUSY;
+
+	cb_flags |= cmd->opcode << CB710_MMC_CMD_CODE_SHIFT;
+
+	if (cmd->data && (cmd->data->flags & MMC_DATA_READ))
+		cb_flags |= CB710_MMC_DATA_READ;
+
+	if (flags & MMC_RSP_PRESENT) {
+		/* Windows driver set 01 at bits 4,3 except for
+		 * MMC_SET_BLOCKLEN where it set 10. Maybe the
+		 * hardware can do something special about this
+		 * command? The original driver looks buggy/incomplete
+		 * anyway so we ignore this for now.
+		 *
+		 * I assume that 00 here means no response is expected.
+		 */
+		cb_flags |= CB710_MMC_RSP_PRESENT;
+
+		if (flags & MMC_RSP_136)
+			cb_flags |= CB710_MMC_RSP_136;
+		if (!(flags & MMC_RSP_CRC))
+			cb_flags |= CB710_MMC_RSP_NO_CRC;
+	}
+
+	return cb_flags;
+}
+
+static void cb710_receive_response(struct cb710_slot *slot,
+	struct mmc_command *cmd)
+{
+	unsigned rsp_opcode, wanted_opcode;
+
+	/* Looks like final byte with CRC is always stripped (same as SDHCI) */
+	if (cmd->flags & MMC_RSP_136) {
+		u32 resp[4];
+
+		resp[0] = cb710_read_port_32(slot, CB710_MMC_RESPONSE3_PORT);
+		resp[1] = cb710_read_port_32(slot, CB710_MMC_RESPONSE2_PORT);
+		resp[2] = cb710_read_port_32(slot, CB710_MMC_RESPONSE1_PORT);
+		resp[3] = cb710_read_port_32(slot, CB710_MMC_RESPONSE0_PORT);
+		rsp_opcode = resp[0] >> 24;
+
+		cmd->resp[0] = (resp[0] << 8)|(resp[1] >> 24);
+		cmd->resp[1] = (resp[1] << 8)|(resp[2] >> 24);
+		cmd->resp[2] = (resp[2] << 8)|(resp[3] >> 24);
+		cmd->resp[3] = (resp[3] << 8);
+	} else {
+		rsp_opcode = cb710_read_port_32(slot, CB710_MMC_RESPONSE1_PORT) & 0x3F;
+		cmd->resp[0] = cb710_read_port_32(slot, CB710_MMC_RESPONSE0_PORT);
+	}
+
+	wanted_opcode = (cmd->flags & MMC_RSP_OPCODE) ? cmd->opcode : 0x3F;
+	if (rsp_opcode != wanted_opcode)
+		cmd->error = -EILSEQ;
+}
+
+static int cb710_mmc_transfer_data(struct cb710_slot *slot,
+	struct mmc_data *data)
+{
+	int error, to;
+
+	if (data->flags & MMC_DATA_READ)
+		error = cb710_mmc_receive(slot, data);
+	else
+		error = cb710_mmc_send(slot, data);
+
+	to = cb710_wait_for_event(slot, CB710_MMC_S1_DATA_TRANSFER_DONE);
+	if (!error)
+		error = to;
+
+	if (!error)
+		data->bytes_xfered = data->blksz * data->blocks;
+	return error;
+}
+
+static int cb710_mmc_command(struct mmc_host *mmc, struct mmc_command *cmd)
+{
+	struct cb710_slot *slot = cb710_mmc_to_slot(mmc);
+	struct cb710_mmc_reader *reader = mmc_priv(mmc);
+	struct mmc_data *data = cmd->data;
+
+	u16 cb_cmd = cb710_encode_cmd_flags(reader, cmd);
+	dev_dbg(cb710_slot_dev(slot), "cmd request: 0x%04X\n", cb_cmd);
+
+	if (data) {
+		if (!cb710_is_transfer_size_supported(data)) {
+			data->error = -EINVAL;
+			return -1;
+		}
+		cb710_mmc_set_transfer_size(slot, data->blocks, data->blksz);
+	}
+
+	cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20|CB710_MMC_S2_BUSY_10);
+	cb710_write_port_16(slot, CB710_MMC_CMD_TYPE_PORT, cb_cmd);
+	cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
+	cb710_write_port_32(slot, CB710_MMC_CMD_PARAM_PORT, cmd->arg);
+	cb710_mmc_reset_events(slot);
+	cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG0_PORT, 0x01, 0);
+
+	cmd->error = cb710_wait_for_event(slot, CB710_MMC_S1_COMMAND_SENT);
+	if (cmd->error)
+		return -1;
+
+	if (cmd->flags & MMC_RSP_PRESENT) {
+		cb710_receive_response(slot, cmd);
+		if (cmd->error)
+			return -1;
+	}
+
+	if (data)
+		data->error = cb710_mmc_transfer_data(slot, data);
+	return 0;
+}
+
+static void cb710_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+	struct cb710_slot *slot = cb710_mmc_to_slot(mmc);
+	struct cb710_mmc_reader *reader = mmc_priv(mmc);
+
+	WARN_ON(reader->mrq != NULL);
+
+	reader->mrq = mrq;
+	cb710_mmc_enable_irq(slot, CB710_MMC_IE_TEST_MASK, 0);
+
+	if (cb710_mmc_is_card_inserted(slot)) {
+		if (!cb710_mmc_command(mmc, mrq->cmd) && mrq->stop)
+			cb710_mmc_command(mmc, mrq->stop);
+		mdelay(1);
+	} else {
+		mrq->cmd->error = -ENOMEDIUM;
+	}
+
+	tasklet_schedule(&reader->finish_req_tasklet);
+}
+
+static int cb710_mmc_powerup(struct cb710_slot *slot)
+{
+#ifdef CONFIG_CB710_DEBUG
+	struct cb710_chip *chip = cb710_slot_to_chip(slot);
+#endif
+	int err;
+
+	/* a lot of magic; see comment in cb710_mmc_set_clock() */
+	dev_dbg(cb710_slot_dev(slot), "bus powerup\n");
+	cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+	err = cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
+	if (unlikely(err))
+		return err;
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT, 0x80, 0);
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG3_PORT, 0x80, 0);
+	cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+	mdelay(1);
+	dev_dbg(cb710_slot_dev(slot), "after delay 1\n");
+	cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+	err = cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
+	if (unlikely(err))
+		return err;
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT, 0x09, 0);
+	cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+	mdelay(1);
+	dev_dbg(cb710_slot_dev(slot), "after delay 2\n");
+	cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+	err = cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
+	if (unlikely(err))
+		return err;
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT, 0, 0x08);
+	cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+	mdelay(2);
+	dev_dbg(cb710_slot_dev(slot), "after delay 3\n");
+	cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG0_PORT, 0x06, 0);
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT, 0x70, 0);
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG2_PORT, 0x80, 0);
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG3_PORT, 0x03, 0);
+	cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+	err = cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
+	if (unlikely(err))
+		return err;
+	/* This port behaves weird: quick byte reads of 0x08,0x09 return
+	 * 0xFF,0x00 after writing 0xFFFF to 0x08; it works correctly when
+	 * read/written from userspace...  What am I missing here?
+	 * (it doesn't depend on write-to-read delay) */
+	cb710_write_port_16(slot, CB710_MMC_CONFIGB_PORT, 0xFFFF);
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG0_PORT, 0x06, 0);
+	cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+	dev_dbg(cb710_slot_dev(slot), "bus powerup finished\n");
+
+	return cb710_check_event(slot, 0);
+}
+
+static void cb710_mmc_powerdown(struct cb710_slot *slot)
+{
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT, 0, 0x81);
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG3_PORT, 0, 0x80);
+}
+
+static void cb710_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+	struct cb710_slot *slot = cb710_mmc_to_slot(mmc);
+	struct cb710_mmc_reader *reader = mmc_priv(mmc);
+	int err;
+
+	cb710_mmc_set_clock(mmc, ios->clock);
+
+	if (!cb710_mmc_is_card_inserted(slot)) {
+		dev_dbg(cb710_slot_dev(slot),
+			"no card inserted - ignoring bus powerup request\n");
+		ios->power_mode = MMC_POWER_OFF;
+	}
+
+	if (ios->power_mode != reader->last_power_mode)
+	switch (ios->power_mode) {
+	case MMC_POWER_ON:
+		err = cb710_mmc_powerup(slot);
+		if (err) {
+			dev_warn(cb710_slot_dev(slot),
+				"powerup failed (%d)- retrying\n", err);
+			cb710_mmc_powerdown(slot);
+			udelay(1);
+			err = cb710_mmc_powerup(slot);
+			if (err)
+				dev_warn(cb710_slot_dev(slot),
+					"powerup retry failed (%d) - expect errors\n",
+					err);
+		}
+		reader->last_power_mode = MMC_POWER_ON;
+		break;
+	case MMC_POWER_OFF:
+		cb710_mmc_powerdown(slot);
+		reader->last_power_mode = MMC_POWER_OFF;
+		break;
+	case MMC_POWER_UP:
+	default:
+		/* ignore */;
+	}
+
+	cb710_mmc_enable_4bit_data(slot, ios->bus_width != MMC_BUS_WIDTH_1);
+
+	cb710_mmc_enable_irq(slot, CB710_MMC_IE_TEST_MASK, 0);
+}
+
+static int cb710_mmc_get_ro(struct mmc_host *mmc)
+{
+	struct cb710_slot *slot = cb710_mmc_to_slot(mmc);
+
+	return cb710_read_port_8(slot, CB710_MMC_STATUS3_PORT)
+		& CB710_MMC_S3_WRITE_PROTECTED;
+}
+
+static int cb710_mmc_irq_handler(struct cb710_slot *slot)
+{
+	struct mmc_host *mmc = cb710_slot_to_mmc(slot);
+	struct cb710_mmc_reader *reader = mmc_priv(mmc);
+	u32 status, config1, config2, irqen;
+
+	status = cb710_read_port_32(slot, CB710_MMC_STATUS_PORT);
+	irqen = cb710_read_port_32(slot, CB710_MMC_IRQ_ENABLE_PORT);
+	config2 = cb710_read_port_32(slot, CB710_MMC_CONFIGB_PORT);
+	config1 = cb710_read_port_32(slot, CB710_MMC_CONFIG_PORT);
+
+	dev_dbg(cb710_slot_dev(slot), "interrupt; status: %08X, "
+		"ie: %08X, c2: %08X, c1: %08X\n",
+		status, irqen, config2, config1);
+
+	if (status & (CB710_MMC_S1_CARD_CHANGED << 8)) {
+		/* ack the event */
+		cb710_write_port_8(slot, CB710_MMC_STATUS1_PORT,
+			CB710_MMC_S1_CARD_CHANGED);
+		if ((irqen & CB710_MMC_IE_CISTATUS_MASK)
+		    == CB710_MMC_IE_CISTATUS_MASK)
+			mmc_detect_change(mmc, HZ/5);
+	} else {
+		dev_dbg(cb710_slot_dev(slot), "unknown interrupt (test)\n");
+		spin_lock(&reader->irq_lock);
+		__cb710_mmc_enable_irq(slot, 0, CB710_MMC_IE_TEST_MASK);
+		spin_unlock(&reader->irq_lock);
+	}
+
+	return 1;
+}
+
+static void cb710_mmc_finish_request_tasklet(unsigned long data)
+{
+	struct mmc_host *mmc = (void *)data;
+	struct cb710_mmc_reader *reader = mmc_priv(mmc);
+	struct mmc_request *mrq = reader->mrq;
+
+	reader->mrq = NULL;
+	mmc_request_done(mmc, mrq);
+}
+
+static const struct mmc_host_ops cb710_mmc_host = {
+	.request = cb710_mmc_request,
+	.set_ios = cb710_mmc_set_ios,
+	.get_ro = cb710_mmc_get_ro
+};
+
+#ifdef CONFIG_PM
+
+static int cb710_mmc_suspend(struct platform_device *pdev, pm_message_t state)
+{
+	struct cb710_slot *slot = cb710_pdev_to_slot(pdev);
+	struct mmc_host *mmc = cb710_slot_to_mmc(slot);
+	int err;
+
+	err = mmc_suspend_host(mmc, state);
+	if (err)
+		return err;
+
+	cb710_mmc_enable_irq(slot, 0, ~0);
+	return 0;
+}
+
+static int cb710_mmc_resume(struct platform_device *pdev)
+{
+	struct cb710_slot *slot = cb710_pdev_to_slot(pdev);
+	struct mmc_host *mmc = cb710_slot_to_mmc(slot);
+
+	cb710_mmc_enable_irq(slot, 0, ~0);
+
+	return mmc_resume_host(mmc);
+}
+
+#endif /* CONFIG_PM */
+
+static int __devinit cb710_mmc_init(struct platform_device *pdev)
+{
+	struct cb710_slot *slot = cb710_pdev_to_slot(pdev);
+	struct cb710_chip *chip = cb710_slot_to_chip(slot);
+	struct mmc_host *mmc;
+	struct cb710_mmc_reader *reader;
+	int err;
+	u32 val;
+
+	mmc = mmc_alloc_host(sizeof(*reader), cb710_slot_dev(slot));
+	if (!mmc)
+		return -ENOMEM;
+
+	dev_set_drvdata(&pdev->dev, mmc);
+
+	/* harmless (maybe) magic */
+	pci_read_config_dword(chip->pdev, 0x48, &val);
+	val = cb710_src_freq_mhz[(val >> 16) & 0xF];
+	dev_dbg(cb710_slot_dev(slot), "source frequency: %dMHz\n", val);
+	val *= 1000000;
+
+	mmc->ops = &cb710_mmc_host;
+	mmc->f_max = val;
+	mmc->f_min = val >> cb710_clock_divider_log2[CB710_MAX_DIVIDER_IDX];
+	mmc->ocr_avail = MMC_VDD_32_33|MMC_VDD_33_34;
+	mmc->caps = MMC_CAP_4_BIT_DATA;
+
+	reader = mmc_priv(mmc);
+
+	tasklet_init(&reader->finish_req_tasklet,
+		cb710_mmc_finish_request_tasklet, (unsigned long)mmc);
+	spin_lock_init(&reader->irq_lock);
+	cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+
+	cb710_mmc_enable_irq(slot, 0, ~0);
+	cb710_set_irq_handler(slot, cb710_mmc_irq_handler);
+
+	err = mmc_add_host(mmc);
+	if (unlikely(err))
+		goto err_free_mmc;
+
+	dev_dbg(cb710_slot_dev(slot), "mmc_hostname is %s\n",
+		mmc_hostname(mmc));
+
+	cb710_mmc_enable_irq(slot, CB710_MMC_IE_CARD_INSERTION_STATUS, 0);
+
+	return 0;
+
+err_free_mmc:
+	dev_dbg(cb710_slot_dev(slot), "mmc_add_host() failed: %d\n", err);
+
+	mmc_free_host(mmc);
+	return err;
+}
+
+static int __devexit cb710_mmc_exit(struct platform_device *pdev)
+{
+	struct cb710_slot *slot = cb710_pdev_to_slot(pdev);
+	struct mmc_host *mmc = cb710_slot_to_mmc(slot);
+	struct cb710_mmc_reader *reader = mmc_priv(mmc);
+
+	cb710_mmc_enable_irq(slot, 0, CB710_MMC_IE_CARD_INSERTION_STATUS);
+
+	mmc_remove_host(mmc);
+
+	/* IRQs should be disabled now, but let's stay on the safe side */
+	cb710_mmc_enable_irq(slot, 0, ~0);
+	cb710_set_irq_handler(slot, NULL);
+
+	/* clear config ports - just in case */
+	cb710_write_port_32(slot, CB710_MMC_CONFIG_PORT, 0);
+	cb710_write_port_16(slot, CB710_MMC_CONFIGB_PORT, 0);
+
+	tasklet_kill(&reader->finish_req_tasklet);
+
+	mmc_free_host(mmc);
+	return 0;
+}
+
+static struct platform_driver cb710_mmc_driver = {
+	.driver.name = "cb710-mmc",
+	.probe = cb710_mmc_init,
+	.remove = __devexit_p(cb710_mmc_exit),
+#ifdef CONFIG_PM
+	.suspend = cb710_mmc_suspend,
+	.resume = cb710_mmc_resume,
+#endif
+};
+
+static int __init cb710_mmc_init_module(void)
+{
+	return platform_driver_register(&cb710_mmc_driver);
+}
+
+static void __exit cb710_mmc_cleanup_module(void)
+{
+	platform_driver_unregister(&cb710_mmc_driver);
+}
+
+module_init(cb710_mmc_init_module);
+module_exit(cb710_mmc_cleanup_module);
+
+MODULE_AUTHOR("Michał Mirosław <mirq-linux@rere.qmqm.pl>");
+MODULE_DESCRIPTION("ENE CB710 memory card reader driver - MMC/SD part");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:cb710-mmc");
diff -urpN linux-2.6.29.3/drivers/mmc/host/cb710-mmc.h test2/drivers/mmc/host/cb710-mmc.h
--- linux-2.6.29.3/drivers/mmc/host/cb710-mmc.h	1970-01-01 01:00:00.000000000 +0100
+++ test2/drivers/mmc/host/cb710-mmc.h	2009-05-22 19:47:19.000000000 +0200
@@ -0,0 +1,104 @@
+/*
+ *  cb710/cb710-mmc.h
+ *
+ *  Copyright by Michał Mirosław, 2008-2009
+ *
+ * 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.
+ */
+#ifndef LINUX_CB710_MMC_H
+#define LINUX_CB710_MMC_H
+
+#include <linux/cb710.h>
+
+/* per-MMC-reader structure */
+struct cb710_mmc_reader {
+	struct tasklet_struct finish_req_tasklet;
+	struct mmc_request *mrq;
+	spinlock_t irq_lock;
+	unsigned char last_power_mode;
+};
+
+/* some device struct walking */
+
+static inline struct mmc_host *cb710_slot_to_mmc(struct cb710_slot *slot)
+{
+	return dev_get_drvdata(&slot->pdev.dev);
+}
+
+static inline struct cb710_slot *cb710_mmc_to_slot(struct mmc_host *mmc)
+{
+	struct platform_device *pdev = container_of(mmc_dev(mmc),
+		struct platform_device, dev);
+	return cb710_pdev_to_slot(pdev);
+}
+
+/* registers (this might be all wrong ;) */
+
+#define CB710_MMC_DATA_PORT		0x00
+
+#define CB710_MMC_CONFIG_PORT		0x04
+#define CB710_MMC_CONFIG0_PORT		0x04
+#define CB710_MMC_CONFIG1_PORT		0x05
+#define   CB710_MMC_C1_4BIT_DATA_BUS		0x40
+#define CB710_MMC_CONFIG2_PORT		0x06
+#define   CB710_MMC_C2_READ_PIO_SIZE_MASK	0x0F	/* N-1 */
+#define CB710_MMC_CONFIG3_PORT		0x07
+
+#define CB710_MMC_CONFIGB_PORT		0x08
+
+#define CB710_MMC_IRQ_ENABLE_PORT	0x0C
+#define   CB710_MMC_IE_TEST_MASK		0x00BF
+#define   CB710_MMC_IE_CARD_INSERTION_STATUS	0x1000
+#define   CB710_MMC_IE_IRQ_ENABLE		0x8000
+#define   CB710_MMC_IE_CISTATUS_MASK		\
+		(CB710_MMC_IE_CARD_INSERTION_STATUS|CB710_MMC_IE_IRQ_ENABLE)
+
+#define CB710_MMC_STATUS_PORT		0x10
+#define   CB710_MMC_STATUS_ERROR_EVENTS		0x60FF
+#define CB710_MMC_STATUS0_PORT		0x10
+#define   CB710_MMC_S0_FIFO_UNDERFLOW		0x40
+#define CB710_MMC_STATUS1_PORT		0x11
+#define   CB710_MMC_S1_COMMAND_SENT		0x01
+#define   CB710_MMC_S1_DATA_TRANSFER_DONE	0x02
+#define   CB710_MMC_S1_PIO_TRANSFER_DONE	0x04
+#define   CB710_MMC_S1_CARD_CHANGED		0x10
+#define   CB710_MMC_S1_RESET			0x20
+#define CB710_MMC_STATUS2_PORT		0x12
+#define   CB710_MMC_S2_FIFO_READY		0x01
+#define   CB710_MMC_S2_FIFO_EMPTY		0x02
+#define   CB710_MMC_S2_BUSY_10			0x10
+#define   CB710_MMC_S2_BUSY_20			0x20
+#define CB710_MMC_STATUS3_PORT		0x13
+#define   CB710_MMC_S3_CARD_DETECTED		0x02
+#define   CB710_MMC_S3_WRITE_PROTECTED		0x04
+
+#define CB710_MMC_CMD_TYPE_PORT		0x14
+#define   CB710_MMC_RSP_TYPE_MASK		0x0007
+#define     CB710_MMC_RSP_R1			(0)
+#define     CB710_MMC_RSP_136			(5)
+#define     CB710_MMC_RSP_NO_CRC		(2)
+#define   CB710_MMC_RSP_PRESENT_MASK		0x0018
+#define     CB710_MMC_RSP_NONE			(0 << 3)
+#define     CB710_MMC_RSP_PRESENT		(1 << 3)
+#define     CB710_MMC_RSP_PRESENT_X		(2 << 3)
+#define   CB710_MMC_CMD_TYPE_MASK		0x0060
+#define     CB710_MMC_CMD_BC			(0 << 5)
+#define     CB710_MMC_CMD_BCR			(1 << 5)
+#define     CB710_MMC_CMD_AC			(2 << 5)
+#define     CB710_MMC_CMD_ADTC			(3 << 5)
+#define   CB710_MMC_DATA_READ			0x0080
+#define   CB710_MMC_CMD_CODE_MASK		0x3F00
+#define   CB710_MMC_CMD_CODE_SHIFT		8
+#define   CB710_MMC_IS_APP_CMD			0x4000
+#define   CB710_MMC_RSP_BUSY			0x8000
+
+#define CB710_MMC_CMD_PARAM_PORT	0x18
+#define CB710_MMC_TRANSFER_SIZE_PORT	0x1C
+#define CB710_MMC_RESPONSE0_PORT	0x20
+#define CB710_MMC_RESPONSE1_PORT	0x24
+#define CB710_MMC_RESPONSE2_PORT	0x28
+#define CB710_MMC_RESPONSE3_PORT	0x2C
+
+#endif /* LINUX_CB710_MMC_H */
diff -urpN linux-2.6.29.3/drivers/mmc/host/Kconfig test2/drivers/mmc/host/Kconfig
--- linux-2.6.29.3/drivers/mmc/host/Kconfig	2009-05-22 19:20:40.000000000 +0200
+++ test2/drivers/mmc/host/Kconfig	2009-05-22 19:47:19.000000000 +0200
@@ -212,3 +212,16 @@ config MMC_TMIO
 	help
 	  This provides support for the SD/MMC cell found in TC6393XB,
 	  T7L66XB and also ipaq ASIC3
+
+config MMC_CB710
+	tristate "ENE CB710 MMC/SD Interface support"
+	depends on PCI
+	select CB710_CORE
+	help
+	  This option enables support for MMC/SD part of ENE CB710/720 Flash
+	  memory card reader found in some laptops (ie. some versions of
+	  HP Compaq nx9500).
+
+	  This driver can also be built as a module. If so, the module
+	  will be called cb710-mmc.
+
diff -urpN linux-2.6.29.3/drivers/mmc/host/Makefile test2/drivers/mmc/host/Makefile
--- linux-2.6.29.3/drivers/mmc/host/Makefile	2009-03-24 00:12:14.000000000 +0100
+++ test2/drivers/mmc/host/Makefile	2009-05-22 19:47:19.000000000 +0200
@@ -27,4 +27,5 @@ endif
 obj-$(CONFIG_MMC_S3C)   	+= s3cmci.o
 obj-$(CONFIG_MMC_SDRICOH_CS)	+= sdricoh_cs.o
 obj-$(CONFIG_MMC_TMIO)		+= tmio_mmc.o
+obj-$(CONFIG_MMC_CB710)	+= cb710-mmc.o
 
diff -urpN linux-2.6.29.3/include/linux/cb710.h test2/include/linux/cb710.h
--- linux-2.6.29.3/include/linux/cb710.h	1970-01-01 01:00:00.000000000 +0100
+++ test2/include/linux/cb710.h	2009-05-22 19:47:18.000000000 +0200
@@ -0,0 +1,238 @@
+/*
+ *  cb710/cb710.h
+ *
+ *  Copyright by Michał Mirosław, 2008-2009
+ *
+ * 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.
+ */
+#ifndef LINUX_CB710_DRIVER_H
+#define LINUX_CB710_DRIVER_H
+
+#ifdef CONFIG_CB710_DEBUG
+#define DEBUG
+#endif
+
+/* verify assumptions on platform_device framework */
+#define CONFIG_CB710_DEBUG_ASSUMPTIONS
+
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+#include <linux/pci.h>
+#include <linux/platform_device.h>
+#include <linux/mmc/host.h>
+
+struct cb710_slot;
+
+typedef int (*cb710_irq_handler_t)(struct cb710_slot *);
+
+/* per-virtual-slot structure */
+struct cb710_slot {
+	struct platform_device	pdev;
+	void __iomem		*iobase;
+	cb710_irq_handler_t	irq_handler;
+};
+
+/* per-device structure */
+struct cb710_chip {
+	struct pci_dev		*pdev;
+	void __iomem		*iobase;
+	unsigned		platform_id;
+#ifdef CONFIG_CB710_DEBUG_ASSUMPTIONS
+	atomic_t		slot_refs_count;
+#endif
+	unsigned		slot_mask;
+	unsigned		slots;
+	spinlock_t		irq_lock;
+	struct cb710_slot	slot[0];
+};
+
+/* NOTE: cb710_chip.slots is modified only during device init/exit and
+ * they are all serialized wrt themselves */
+
+/* cb710_chip.slot_mask values */
+#define CB710_SLOT_MMC		1
+#define CB710_SLOT_MS		2
+#define CB710_SLOT_SM		4
+
+/* slot port accessors - so the logic is more clear in the code */
+#define CB710_PORT_ACCESSORS(t) \
+static inline void cb710_write_port_##t(struct cb710_slot *slot,	\
+	unsigned port, u##t value)					\
+{									\
+	iowrite##t(value, slot->iobase + port);				\
+}									\
+									\
+static inline u##t cb710_read_port_##t(struct cb710_slot *slot,		\
+	unsigned port)							\
+{									\
+	return ioread##t(slot->iobase + port);				\
+}									\
+									\
+static inline void cb710_modify_port_##t(struct cb710_slot *slot,	\
+	unsigned port, u##t set, u##t clear)				\
+{									\
+	iowrite##t(							\
+		(ioread##t(slot->iobase + port) & ~clear)|set,		\
+		slot->iobase + port);					\
+}
+
+CB710_PORT_ACCESSORS(8)
+CB710_PORT_ACCESSORS(16)
+CB710_PORT_ACCESSORS(32)
+
+void cb710_pci_update_config_reg(struct pci_dev *pdev,
+	int reg, uint32_t and, uint32_t xor);
+void cb710_set_irq_handler(struct cb710_slot *slot,
+	cb710_irq_handler_t handler);
+
+/* some device struct walking */
+
+static inline struct cb710_slot *cb710_pdev_to_slot(
+	struct platform_device *pdev)
+{
+	return container_of(pdev, struct cb710_slot, pdev);
+}
+
+static inline struct cb710_chip *cb710_slot_to_chip(struct cb710_slot *slot)
+{
+	return dev_get_drvdata(slot->pdev.dev.parent);
+}
+
+static inline struct device *cb710_slot_dev(struct cb710_slot *slot)
+{
+	return &slot->pdev.dev;
+}
+
+static inline struct device *cb710_chip_dev(struct cb710_chip *chip)
+{
+	return &chip->pdev->dev;
+}
+
+/* debugging aids */
+
+#ifdef CONFIG_CB710_DEBUG
+void cb710_dump_regs(struct cb710_chip *chip, unsigned dump);
+#else
+#define cb710_dump_regs(c, d) do {} while (0)
+#endif
+
+#define CB710_DUMP_REGS_MMC	0x0F
+#define CB710_DUMP_REGS_MS	0x30
+#define CB710_DUMP_REGS_SM	0xC0
+#define CB710_DUMP_REGS_ALL	0xFF
+#define CB710_DUMP_REGS_MASK	0xFF
+
+#define CB710_DUMP_ACCESS_8	0x100
+#define CB710_DUMP_ACCESS_16	0x200
+#define CB710_DUMP_ACCESS_32	0x400
+#define CB710_DUMP_ACCESS_ALL	0x700
+#define CB710_DUMP_ACCESS_MASK	0x700
+
+#endif /* LINUX_CB710_DRIVER_H */
+/*
+ *  cb710/sgbuf2.h
+ *
+ *  Copyright by Michał Mirosław, 2008-2009
+ *
+ * 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.
+ */
+#ifndef LINUX_CB710_SG_H
+#define LINUX_CB710_SG_H
+
+#include <linux/highmem.h>
+#include <linux/scatterlist.h>
+
+/**
+ * sg_miter_stop_writing - stop mapping iteration after writing
+ * @miter: sg mapping iter to be stopped
+ *
+ * Description:
+ *   Stops mapping iterator @miter.  @miter should have been started
+ *   started using sg_miter_start().  A stopped iteration can be
+ *   resumed by calling sg_miter_next() on it.  This is useful when
+ *   resources (kmap) need to be released during iteration.
+ *
+ *   This is a convenience wrapper that will be optimized out for arches
+ *   that don't need flush_kernel_dcache_page().
+ *
+ * Context:
+ *   IRQ disabled if the SG_MITER_ATOMIC is set.  Don't care otherwise.
+ */
+static inline void sg_miter_stop_writing(struct sg_mapping_iter *miter)
+{
+	if (miter->page)
+		flush_kernel_dcache_page(miter->page);
+	sg_miter_stop(miter);
+}
+
+/*
+ * 32-bit PIO mapping sg iterator
+ *
+ * Hides scatterlist access issues - fragment boundaries, alignment, page
+ * mapping - for drivers using 32-bit-word-at-a-time-PIO (ie. PCI devices
+ * without DMA support).
+ *
+ * Best-case reading (transfer from device):
+ *   sg_miter_start();
+ *   sg_dwiter_write_from_io();
+ *   sg_miter_stop_writing();
+ *
+ * Best-case writing (transfer to device):
+ *   sg_miter_start();
+ *   sg_dwiter_read_to_io();
+ *   sg_miter_stop();
+ */
+
+uint32_t sg_dwiter_read_next_block(struct sg_mapping_iter *miter);
+void sg_dwiter_write_next_block(struct sg_mapping_iter *miter, uint32_t data);
+
+/**
+ * sg_dwiter_write_from_io - transfer data to mapped buffer from 32-bit IO port
+ * @miter: sg mapping iter
+ * @port: PIO port - IO or MMIO address
+ * @count: number of 32-bit words to transfer
+ *
+ * Description:
+ *   Reads @count 32-bit words from register @port and stores it in
+ *   buffer iterated by @miter.  Data that would overflow the buffer
+ *   is silently ignored.  Iterator is advanced by 4*@count bytes
+ *   or to the buffer's end whichever is closer.
+ *
+ * Context:
+ *   IRQ disabled if the SG_MITER_ATOMIC is set.  Don't care otherwise.
+ */
+static inline void sg_dwiter_write_from_io(struct sg_mapping_iter *miter,
+	void __iomem *port, size_t count)
+{
+	while (count-- > 0)
+		sg_dwiter_write_next_block(miter, ioread32(port));
+}
+
+/**
+ * sg_dwiter_read_to_io - transfer data to 32-bit IO port from mapped buffer
+ * @miter: sg mapping iter
+ * @port: PIO port - IO or MMIO address
+ * @count: number of 32-bit words to transfer
+ *
+ * Description:
+ *   Writes @count 32-bit words to register @port from buffer iterated
+ *   through @miter.  If buffer ends before @count words are written
+ *   missing data is replaced by zeroes. @miter is advanced by 4*@count
+ *   bytes or to the buffer's end whichever is closer.
+ *
+ * Context:
+ *   IRQ disabled if the SG_MITER_ATOMIC is set.  Don't care otherwise.
+ */
+static inline void sg_dwiter_read_to_io(struct sg_mapping_iter *miter,
+	void __iomem *port, size_t count)
+{
+	while (count-- > 0)
+		iowrite32(sg_dwiter_read_next_block(miter), port);
+}
+
+#endif /* LINUX_CB710_SG_H */
diff -urpN linux-2.6.29.3/include/linux/pci_ids.h test2/include/linux/pci_ids.h
--- linux-2.6.29.3/include/linux/pci_ids.h	2009-03-24 00:12:14.000000000 +0100
+++ test2/include/linux/pci_ids.h	2009-05-22 19:47:19.000000000 +0200
@@ -2077,6 +2077,7 @@
 #define PCI_VENDOR_ID_MAINPINE		0x1522
 #define PCI_DEVICE_ID_MAINPINE_PBRIDGE	0x0100
 #define PCI_VENDOR_ID_ENE		0x1524
+#define PCI_DEVICE_ID_ENE_CB710_FLASH	0x0510
 #define PCI_DEVICE_ID_ENE_CB712_SD	0x0550
 #define PCI_DEVICE_ID_ENE_CB712_SD_2	0x0551
 #define PCI_DEVICE_ID_ENE_CB714_SD	0x0750
diff -urpN linux-2.6.29.3/MAINTAINERS test2/MAINTAINERS
--- linux-2.6.29.3/MAINTAINERS	2009-03-24 00:12:14.000000000 +0100
+++ test2/MAINTAINERS	2009-05-22 19:47:19.000000000 +0200
@@ -1660,6 +1660,15 @@ L:	linux-scsi@vger.kernel.org
 W:	http://sourceforge.net/projects/lpfcxxxx
 S:	Supported
 
+ENE CB710 FLASH CARD READER DRIVER
+P:	Michał Mirosław
+M:	mirq-linux@rere.qmqm.pl
+L:	linux-kernel@vger.kernel.org
+S:	Maintained
+F:	drivers/misc/cb710/
+F:	drivers/mmc/host/cb710-mmc.*
+F:	include/linux/cb710.h
+
 EPSON 1355 FRAMEBUFFER DRIVER
 P:	Christopher Hoover
 M:	ch@murgatroid.com, ch@hpl.hp.com

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

* [PATCH 2.6.29.4] Driver for CB710/720 memory card reader (MMC part) - v5 fixed
  2009-05-22 17:55                           ` [PATCH 2.6.29.3] Driver for CB710/720 memory card reader (MMC part) - v5 Michał Mirosław
@ 2009-05-22 18:33                             ` Michał Mirosław
  2009-05-27 20:13                               ` Pierre Ossman
  0 siblings, 1 reply; 21+ messages in thread
From: Michał Mirosław @ 2009-05-22 18:33 UTC (permalink / raw)
  To: Pierre Ossman; +Cc: linux-kernel

[I messed up source trees for previous diff, please use this one instead.]

Here is a version of the CB710 SD/MMC reader driver ready for inclusion
upstream. The code is divided in two parts. There is a virtual 'bus' driver
that handles PCI device and registers three new devices one per card reader
type. The other driver handles SD/MMC part of the reader.

Changes from v4:
  - sg_mapping_iterator extension renamed to avoid namespace clashes
    when/if the extension would be merged upstream
  - appended sgbuf2.h to cb710.h
  - move from IDR to IDA for device ID generation
  - cleanup after IDA on module unload
  - added MAINTAINERS entry
 
Signed-off-by: Michał Mirosław <mirq-linux@rere.qmqm.pl>

diff -urpN linux-2.6.29.4/drivers/misc/cb710/core.c test2/drivers/misc/cb710/core.c
--- linux-2.6.29.4/drivers/misc/cb710/core.c	1970-01-01 01:00:00.000000000 +0100
+++ test2/drivers/misc/cb710/core.c	2009-05-22 20:28:02.000000000 +0200
@@ -0,0 +1,357 @@
+/*
+ *  cb710/core.c
+ *
+ *  Copyright by Michał Mirosław, 2008-2009
+ *
+ * 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.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <linux/spinlock.h>
+#include <linux/idr.h>
+#include <linux/cb710.h>
+
+static DEFINE_IDA(cb710_ida);
+static DEFINE_SPINLOCK(cb710_ida_lock);
+
+void cb710_pci_update_config_reg(struct pci_dev *pdev,
+	int reg, uint32_t mask, uint32_t xor)
+{
+	u32 rval;
+
+	pci_read_config_dword(pdev, reg, &rval);
+	rval = (rval & mask) ^ xor;
+	pci_write_config_dword(pdev, reg, rval);
+}
+EXPORT_SYMBOL_GPL(cb710_pci_update_config_reg);
+
+/* Some magic writes based on Windows driver init code */
+static int __devinit cb710_pci_configure(struct pci_dev *pdev)
+{
+	unsigned int devfn = PCI_DEVFN(PCI_SLOT(pdev->devfn), 0);
+	struct pci_dev *pdev0 = pci_get_slot(pdev->bus, devfn);
+	u32 val;
+
+	cb710_pci_update_config_reg(pdev, 0x48,
+		~0x000000FF, 0x0000003F);
+
+	pci_read_config_dword(pdev, 0x48, &val);
+	if (val & 0x80000000)
+		return 0;
+
+	if (!pdev0)
+		return -ENODEV;
+
+	if (pdev0->vendor == PCI_VENDOR_ID_ENE
+	    && pdev0->device == PCI_DEVICE_ID_ENE_720) {
+		cb710_pci_update_config_reg(pdev0, 0x8C,
+			~0x00F00000, 0x00100000);
+		cb710_pci_update_config_reg(pdev0, 0xB0,
+			~0x08000000, 0x08000000);
+	}
+
+	cb710_pci_update_config_reg(pdev0, 0x8C,
+		~0x00000F00, 0x00000200);
+	cb710_pci_update_config_reg(pdev0, 0x90,
+		~0x00060000, 0x00040000);
+
+	pci_dev_put(pdev0);
+
+	return 0;
+}
+
+static irqreturn_t cb710_irq_handler(int irq, void *data)
+{
+	struct cb710_chip *chip = data;
+	struct cb710_slot *slot = &chip->slot[0];
+	irqreturn_t handled = IRQ_NONE;
+	unsigned nr;
+
+	spin_lock(&chip->irq_lock); /* incl. smp_rmb() */
+
+	for (nr = chip->slots; nr; ++slot, --nr) {
+		cb710_irq_handler_t handler_func = slot->irq_handler;
+		if (handler_func && handler_func(slot))
+			handled = IRQ_HANDLED;
+	}
+
+	spin_unlock(&chip->irq_lock);
+
+	return handled;
+}
+
+static void cb710_release_slot(struct device *dev)
+{
+#ifdef CONFIG_CB710_DEBUG_ASSUMPTIONS
+	struct cb710_slot *slot = cb710_pdev_to_slot(to_platform_device(dev));
+	struct cb710_chip *chip = cb710_slot_to_chip(slot);
+
+	/* slot struct can be freed now */
+	atomic_dec(&chip->slot_refs_count);
+#endif
+}
+
+static int __devinit cb710_register_slot(struct cb710_chip *chip,
+	unsigned slot_mask, unsigned io_offset, const char *name)
+{
+	int nr = chip->slots;
+	struct cb710_slot *slot = &chip->slot[nr];
+	int err;
+
+	dev_dbg(cb710_chip_dev(chip),
+		"register: %s.%d; slot %d; mask %d; IO offset: 0x%02X\n",
+		name, chip->platform_id, nr, slot_mask, io_offset);
+
+	/* slot->irq_handler == NULL here; this needs to be
+	 * seen before platform_device_register() */
+	++chip->slots;
+	smp_wmb();
+
+	slot->iobase = chip->iobase + io_offset;
+	slot->pdev.name = name;
+	slot->pdev.id = chip->platform_id;
+	slot->pdev.dev.parent = &chip->pdev->dev;
+	slot->pdev.dev.release = cb710_release_slot;
+
+	err = platform_device_register(&slot->pdev);
+
+#ifdef CONFIG_CB710_DEBUG_ASSUMPTIONS
+	atomic_inc(&chip->slot_refs_count);
+#endif
+
+	if (err) {
+		/* device_initialize() called from platform_device_register()
+		 * wants this on error path */
+		platform_device_put(&slot->pdev);
+
+		/* slot->irq_handler == NULL here anyway, so no lock needed */
+		--chip->slots;
+		return err;
+	}
+
+	chip->slot_mask |= slot_mask;
+
+	return 0;
+}
+
+static void cb710_unregister_slot(struct cb710_chip *chip,
+	unsigned slot_mask)
+{
+	int nr = chip->slots - 1;
+
+	if (!(chip->slot_mask & slot_mask))
+		return;
+
+	platform_device_unregister(&chip->slot[nr].pdev);
+
+	/* complementary to spin_unlock() in cb710_set_irq_handler() */
+	smp_rmb();
+	BUG_ON(chip->slot[nr].irq_handler != NULL);
+
+	/* slot->irq_handler == NULL here, so no lock needed */
+	--chip->slots;
+	chip->slot_mask &= ~slot_mask;
+}
+
+void cb710_set_irq_handler(struct cb710_slot *slot,
+	cb710_irq_handler_t handler)
+{
+	struct cb710_chip *chip = cb710_slot_to_chip(slot);
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->irq_lock, flags);
+	slot->irq_handler = handler;
+	spin_unlock_irqrestore(&chip->irq_lock, flags);
+}
+EXPORT_SYMBOL_GPL(cb710_set_irq_handler);
+
+#ifdef CONFIG_PM
+
+static int cb710_suspend(struct pci_dev *pdev, pm_message_t state)
+{
+	struct cb710_chip *chip = pci_get_drvdata(pdev);
+
+	free_irq(pdev->irq, chip);
+	pci_save_state(pdev);
+	pci_disable_device(pdev);
+	if (state.event & PM_EVENT_SLEEP)
+		pci_set_power_state(pdev, PCI_D3cold);
+	return 0;
+}
+
+static int cb710_resume(struct pci_dev *pdev)
+{
+	struct cb710_chip *chip = pci_get_drvdata(pdev);
+	int err;
+
+	pci_set_power_state(pdev, PCI_D0);
+	pci_restore_state(pdev);
+	err = pcim_enable_device(pdev);
+	if (err)
+		return err;
+
+	return devm_request_irq(&pdev->dev, pdev->irq,
+		cb710_irq_handler, IRQF_SHARED, KBUILD_MODNAME, chip);
+}
+
+#endif /* CONFIG_PM */
+
+static int __devinit cb710_probe(struct pci_dev *pdev,
+	const struct pci_device_id *ent)
+{
+	struct cb710_chip *chip;
+	unsigned long flags;
+	u32 val;
+	int err;
+	int n = 0;
+
+	err = cb710_pci_configure(pdev);
+	if (err)
+		return err;
+
+	/* this is actually magic... */
+	pci_read_config_dword(pdev, 0x48, &val);
+	if (!(val & 0x80000000)) {
+		pci_write_config_dword(pdev, 0x48, val|0x71000000);
+		pci_read_config_dword(pdev, 0x48, &val);
+	}
+
+	dev_dbg(&pdev->dev, "PCI config[0x48] = 0x%08X\n", val);
+	if (!(val & 0x70000000))
+		return -ENODEV;
+	val = (val >> 28) & 7;
+	if (val & CB710_SLOT_MMC)
+		++n;
+	if (val & CB710_SLOT_MS)
+		++n;
+	if (val & CB710_SLOT_SM)
+		++n;
+
+	chip = devm_kzalloc(&pdev->dev,
+		sizeof(*chip) + n * sizeof(*chip->slot), GFP_KERNEL);
+	if (!chip)
+		return -ENOMEM;
+
+	err = pcim_enable_device(pdev);
+	if (err)
+		return err;
+
+	err = pcim_iomap_regions(pdev, 0x0001, KBUILD_MODNAME);
+	if (err)
+		return err;
+
+	chip->pdev = pdev;
+	chip->iobase = pcim_iomap_table(pdev)[0];
+
+	pci_set_drvdata(pdev, chip);
+
+	err = devm_request_irq(&pdev->dev, pdev->irq,
+		cb710_irq_handler, IRQF_SHARED, KBUILD_MODNAME, chip);
+	if (err)
+		return err;
+
+	do {
+		if (!ida_pre_get(&cb710_ida, GFP_KERNEL))
+			return -ENOMEM;
+
+		spin_lock_irqsave(&cb710_ida_lock, flags);
+		err = ida_get_new(&cb710_ida, &chip->platform_id);
+		spin_unlock_irqrestore(&cb710_ida_lock, flags);
+
+		if (err && err != -EAGAIN)
+			return err;
+	} while (err);
+
+
+	dev_info(&pdev->dev, "id %d, IO 0x%p, IRQ %d\n",
+		chip->platform_id, chip->iobase, pdev->irq);
+
+	if (val & CB710_SLOT_MMC) {	/* MMC/SD slot */
+		err = cb710_register_slot(chip,
+			CB710_SLOT_MMC, 0x00, "cb710-mmc");
+		if (err)
+			return err;
+	}
+
+	if (val & CB710_SLOT_MS) {	/* MemoryStick slot */
+		err = cb710_register_slot(chip,
+			CB710_SLOT_MS, 0x40, "cb710-ms");
+		if (err)
+			goto unreg_mmc;
+	}
+
+	if (val & CB710_SLOT_SM) {	/* SmartMedia slot */
+		err = cb710_register_slot(chip,
+			CB710_SLOT_SM, 0x60, "cb710-sm");
+		if (err)
+			goto unreg_ms;
+	}
+
+	return 0;
+unreg_ms:
+	cb710_unregister_slot(chip, CB710_SLOT_MS);
+unreg_mmc:
+	cb710_unregister_slot(chip, CB710_SLOT_MMC);
+
+#ifdef CONFIG_CB710_DEBUG_ASSUMPTIONS
+	BUG_ON(atomic_read(&chip->slot_refs_count) != 0);
+#endif
+	return err;
+}
+
+static void __devexit cb710_remove_one(struct pci_dev *pdev)
+{
+	struct cb710_chip *chip = pci_get_drvdata(pdev);
+	unsigned long flags;
+
+	cb710_unregister_slot(chip, CB710_SLOT_SM);
+	cb710_unregister_slot(chip, CB710_SLOT_MS);
+	cb710_unregister_slot(chip, CB710_SLOT_MMC);
+#ifdef CONFIG_CB710_DEBUG_ASSUMPTIONS
+	BUG_ON(atomic_read(&chip->slot_refs_count) != 0);
+#endif
+
+	spin_lock_irqsave(&cb710_ida_lock, flags);
+	ida_remove(&cb710_ida, chip->platform_id);
+	spin_unlock_irqrestore(&cb710_ida_lock, flags);
+}
+
+static const struct pci_device_id cb710_pci_tbl[] = {
+	{ PCI_VENDOR_ID_ENE, PCI_DEVICE_ID_ENE_CB710_FLASH,
+		PCI_ANY_ID, PCI_ANY_ID, },
+	{ 0, }
+};
+
+static struct pci_driver cb710_driver = {
+	.name = KBUILD_MODNAME,
+	.id_table = cb710_pci_tbl,
+	.probe = cb710_probe,
+	.remove = __devexit_p(cb710_remove_one),
+#ifdef CONFIG_PM
+	.suspend = cb710_suspend,
+	.resume = cb710_resume,
+#endif
+};
+
+static int __init cb710_init_module(void)
+{
+	return pci_register_driver(&cb710_driver);
+}
+
+static void __exit cb710_cleanup_module(void)
+{
+	pci_unregister_driver(&cb710_driver);
+	ida_destroy(&cb710_ida);
+}
+
+module_init(cb710_init_module);
+module_exit(cb710_cleanup_module);
+
+MODULE_AUTHOR("Michał Mirosław <mirq-linux@rere.qmqm.pl>");
+MODULE_DESCRIPTION("ENE CB710 memory card reader driver");
+MODULE_LICENSE("GPL");
+MODULE_DEVICE_TABLE(pci, cb710_pci_tbl);
diff -urpN linux-2.6.29.4/drivers/misc/cb710/debug.c test2/drivers/misc/cb710/debug.c
--- linux-2.6.29.4/drivers/misc/cb710/debug.c	1970-01-01 01:00:00.000000000 +0100
+++ test2/drivers/misc/cb710/debug.c	2009-05-22 20:28:02.000000000 +0200
@@ -0,0 +1,119 @@
+/*
+ *  cb710/debug.c
+ *
+ *  Copyright by Michał Mirosław, 2008-2009
+ *
+ * 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.
+ */
+#include <linux/cb710.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+#define CB710_REG_COUNT		0x80
+
+static const u16 allow[CB710_REG_COUNT/16] = {
+	0xFFF0, 0xFFFF, 0xFFFF, 0xFFFF,
+	0xFFF0, 0xFFFF, 0xFFFF, 0xFFFF,
+};
+static const char *const prefix[ARRAY_SIZE(allow)] = {
+	"MMC", "MMC", "MMC", "MMC",
+	"MS?", "MS?", "SM?", "SM?"
+};
+
+static inline int allow_reg_read(unsigned block, unsigned offset, unsigned bits)
+{
+	unsigned mask = (1 << bits/8) - 1;
+	offset *= bits/8;
+	return ((allow[block] >> offset) & mask) == mask;
+}
+
+#define CB710_READ_REGS_TEMPLATE(t)					\
+static void cb710_read_regs_##t(void __iomem *iobase,			\
+	u##t *reg, unsigned select)					\
+{									\
+	unsigned i, j;							\
+									\
+	for (i = 0; i < ARRAY_SIZE(allow); ++i, reg += 16/(t/8)) {	\
+		if (!select & (1 << i))					\
+			continue;					\
+									\
+		for (j = 0; j < 0x10/(t/8); ++j) {			\
+			if (!allow_reg_read(i, j, t))			\
+				continue;				\
+			reg[j] = ioread##t(iobase			\
+				+ (i << 4) + (j * (t/8)));		\
+		}							\
+	}								\
+}
+
+static const char cb710_regf_8[] = "%02X";
+static const char cb710_regf_16[] = "%04X";
+static const char cb710_regf_32[] = "%08X";
+static const char cb710_xes[] = "xxxxxxxx";
+
+#define CB710_DUMP_REGS_TEMPLATE(t)					\
+static void cb710_dump_regs_##t(struct device *dev,			\
+	const u##t *reg, unsigned select)				\
+{									\
+	const char *const xp = &cb710_xes[8 - t/4];			\
+	const char *const format = cb710_regf_##t;			\
+									\
+	char msg[100], *p;						\
+	unsigned i, j;							\
+									\
+	for (i = 0; i < ARRAY_SIZE(allow); ++i, reg += 16/(t/8)) {	\
+		if (!(select & (1 << i)))				\
+			continue;					\
+		p = msg;						\
+		for (j = 0; j < 0x10/(t/8); ++j) {			\
+			*p++ = ' ';					\
+			if (j == 8/(t/8))				\
+				*p++ = ' ';				\
+			if (allow_reg_read(i, j, t))			\
+				p += sprintf(p, format, reg[j]);	\
+			else						\
+				p += sprintf(p, "%s", xp);		\
+		}							\
+		dev_dbg(dev, "%s 0x%02X %s\n", prefix[i], i << 4, msg);	\
+	}								\
+}
+
+#define CB710_READ_AND_DUMP_REGS_TEMPLATE(t)				\
+static void cb710_read_and_dump_regs_##t(struct cb710_chip *chip,	\
+	unsigned select)						\
+{									\
+	u##t regs[CB710_REG_COUNT/sizeof(u##t)];			\
+									\
+	memset(&regs, 0, sizeof(regs));					\
+	cb710_read_regs_##t(chip->iobase, regs, select);		\
+	cb710_dump_regs_##t(cb710_chip_dev(chip), regs, select);	\
+}
+
+#define CB710_REG_ACCESS_TEMPLATES(t)		\
+  CB710_READ_REGS_TEMPLATE(t)			\
+  CB710_DUMP_REGS_TEMPLATE(t)			\
+  CB710_READ_AND_DUMP_REGS_TEMPLATE(t)
+
+CB710_REG_ACCESS_TEMPLATES(8)
+CB710_REG_ACCESS_TEMPLATES(16)
+CB710_REG_ACCESS_TEMPLATES(32)
+
+void cb710_dump_regs(struct cb710_chip *chip, unsigned select)
+{
+	if (!(select & CB710_DUMP_REGS_MASK))
+		select = CB710_DUMP_REGS_ALL;
+	if (!(select & CB710_DUMP_ACCESS_MASK))
+		select |= CB710_DUMP_ACCESS_8;
+
+	if (select & CB710_DUMP_ACCESS_32)
+		cb710_read_and_dump_regs_32(chip, select);
+	if (select & CB710_DUMP_ACCESS_16)
+		cb710_read_and_dump_regs_16(chip, select);
+	if (select & CB710_DUMP_ACCESS_8)
+		cb710_read_and_dump_regs_8(chip, select);
+}
+EXPORT_SYMBOL_GPL(cb710_dump_regs);
+
diff -urpN linux-2.6.29.4/drivers/misc/cb710/Kconfig test2/drivers/misc/cb710/Kconfig
--- linux-2.6.29.4/drivers/misc/cb710/Kconfig	1970-01-01 01:00:00.000000000 +0100
+++ test2/drivers/misc/cb710/Kconfig	2009-05-22 20:28:02.000000000 +0200
@@ -0,0 +1,21 @@
+config CB710_CORE
+	tristate "ENE CB710/720 Flash memory card reader support"
+	depends on PCI
+	help
+	  This option enables support for PCI ENE CB710/720 Flash memory card
+	  reader found in some laptops (ie. some versions of HP Compaq nx9500).
+
+	  You will also have to select some flash card format drivers (MMC/SD,
+	  MemoryStick).
+
+	  This driver can also be built as a module. If so, the module
+	  will be called cb710.
+
+config CB710_DEBUG
+	bool "Enable driver debugging"
+	depends on CB710_CORE != n
+	default n
+	help
+	  This is an option for use by developers; most people should
+	  say N here.  This adds a lot of debugging output to dmesg.
+
diff -urpN linux-2.6.29.4/drivers/misc/cb710/Makefile test2/drivers/misc/cb710/Makefile
--- linux-2.6.29.4/drivers/misc/cb710/Makefile	1970-01-01 01:00:00.000000000 +0100
+++ test2/drivers/misc/cb710/Makefile	2009-05-22 20:28:02.000000000 +0200
@@ -0,0 +1,4 @@
+obj-$(CONFIG_CB710_CORE)	+= cb710.o
+
+cb710-y				:= core.o sgbuf2.o
+cb710-$(CONFIG_CB710_DEBUG)	+= debug.o
diff -urpN linux-2.6.29.4/drivers/misc/cb710/sgbuf2.c test2/drivers/misc/cb710/sgbuf2.c
--- linux-2.6.29.4/drivers/misc/cb710/sgbuf2.c	1970-01-01 01:00:00.000000000 +0100
+++ test2/drivers/misc/cb710/sgbuf2.c	2009-05-22 20:28:06.000000000 +0200
@@ -0,0 +1,150 @@
+/*
+ *  cb710/sgbuf2.c
+ *
+ *  Copyright by Michał Mirosław, 2008-2009
+ *
+ * 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.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/cb710.h>
+
+static bool sg_dwiter_next(struct sg_mapping_iter *miter)
+{
+	if (sg_miter_next(miter)) {
+		miter->consumed = 0;
+		return true;
+	} else
+		return false;
+}
+
+static bool sg_dwiter_is_at_end(struct sg_mapping_iter *miter)
+{
+	return miter->length == miter->consumed && !sg_dwiter_next(miter);
+}
+
+static uint32_t sg_dwiter_read_buffer(struct sg_mapping_iter *miter)
+{
+	size_t len, left = 4;
+	uint32_t data;
+	void *addr = &data;
+
+	do {
+		len = min(miter->length - miter->consumed, left);
+		memcpy(addr, miter->addr + miter->consumed, len);
+		miter->consumed += len;
+		left -= len;
+		if (!left)
+			return data;
+		addr += len;
+	} while (sg_dwiter_next(miter));
+
+	memset(addr, 0, left);
+	return data;
+}
+
+static inline bool needs_unaligned_copy(const void *ptr)
+{
+#ifdef HAVE_EFFICIENT_UNALIGNED_ACCESS
+	return false;
+#else
+	return ((ptr - NULL) & 3) != 0;
+#endif
+}
+
+static bool sg_dwiter_get_next_block(struct sg_mapping_iter *miter, uint32_t **ptr)
+{
+	size_t len;
+
+	if (sg_dwiter_is_at_end(miter))
+		return true;
+
+	len = miter->length - miter->consumed;
+
+	if (likely(len >= 4 && !needs_unaligned_copy(
+			miter->addr + miter->consumed))) {
+		*ptr = miter->addr + miter->consumed;
+		miter->consumed += 4;
+		return true;
+	}
+
+	return false;
+}
+
+/**
+ * cb710_sg_dwiter_read_next_block() - get next 32-bit word from sg buffer
+ * @miter: sg mapping iterator used for reading
+ *
+ * Description:
+ *   Returns 32-bit word starting at byte pointed to by @miter@
+ *   handling any alignment issues.  Bytes past the buffer's end
+ *   are not accessed (read) but are returned as zeroes.  @miter@
+ *   is advanced by 4 bytes or to the end of buffer whichever is
+ *   closer.
+ *
+ * Context:
+ *   Same requirements as in sg_miter_next().
+ *
+ * Returns:
+ *   32-bit word just read.
+ */
+uint32_t cb710_sg_dwiter_read_next_block(struct sg_mapping_iter *miter)
+{
+	uint32_t *ptr = NULL;
+
+	if (likely(sg_dwiter_get_next_block(miter, &ptr)))
+		return ptr ? *ptr : 0;
+
+	return sg_dwiter_read_buffer(miter);
+}
+EXPORT_SYMBOL_GPL(cb710_sg_dwiter_read_next_block);
+
+static void sg_dwiter_write_slow(struct sg_mapping_iter *miter, uint32_t data)
+{
+	size_t len, left = 4;
+	void *addr = &data;
+
+	do {
+		len = min(miter->length - miter->consumed, left);
+		memcpy(miter->addr, addr, len);
+		miter->consumed += len;
+		left -= len;
+		if (!left)
+			return;
+		addr += len;
+		flush_kernel_dcache_page(miter->page);
+	} while (sg_dwiter_next(miter));
+}
+
+/**
+ * cb710_sg_dwiter_write_next_block() - write next 32-bit word to sg buffer
+ * @miter: sg mapping iterator used for writing
+ *
+ * Description:
+ *   Writes 32-bit word starting at byte pointed to by @miter@
+ *   handling any alignment issues.  Bytes which would be written
+ *   past the buffer's end are silently discarded. @miter@ is
+ *   advanced by 4 bytes or to the end of buffer whichever is closer.
+ *
+ * Context:
+ *   Same requirements as in sg_miter_next().
+ */
+void cb710_sg_dwiter_write_next_block(struct sg_mapping_iter *miter, uint32_t data)
+{
+	uint32_t *ptr = NULL;
+
+	if (likely(sg_dwiter_get_next_block(miter, &ptr))) {
+		if (ptr)
+			*ptr = data;
+		else
+			return;
+	} else
+		sg_dwiter_write_slow(miter, data);
+
+	if (miter->length == miter->consumed)
+		flush_kernel_dcache_page(miter->page);
+}
+EXPORT_SYMBOL_GPL(cb710_sg_dwiter_write_next_block);
+
diff -urpN linux-2.6.29.4/drivers/misc/Kconfig test2/drivers/misc/Kconfig
--- linux-2.6.29.4/drivers/misc/Kconfig	2009-03-24 00:12:14.000000000 +0100
+++ test2/drivers/misc/Kconfig	2009-05-22 20:28:02.000000000 +0200
@@ -225,5 +225,6 @@ config DELL_LAPTOP
 
 source "drivers/misc/c2port/Kconfig"
 source "drivers/misc/eeprom/Kconfig"
+source "drivers/misc/cb710/Kconfig"
 
 endif # MISC_DEVICES
diff -urpN linux-2.6.29.4/drivers/misc/Makefile test2/drivers/misc/Makefile
--- linux-2.6.29.4/drivers/misc/Makefile	2009-03-24 00:12:14.000000000 +0100
+++ test2/drivers/misc/Makefile	2009-05-22 20:28:02.000000000 +0200
@@ -20,3 +20,4 @@ obj-$(CONFIG_SGI_GRU)		+= sgi-gru/
 obj-$(CONFIG_HP_ILO)		+= hpilo.o
 obj-$(CONFIG_C2PORT)		+= c2port/
 obj-y				+= eeprom/
+obj-y				+= cb710/
diff -urpN linux-2.6.29.4/drivers/mmc/host/cb710-mmc.c test2/drivers/mmc/host/cb710-mmc.c
--- linux-2.6.29.4/drivers/mmc/host/cb710-mmc.c	1970-01-01 01:00:00.000000000 +0100
+++ test2/drivers/mmc/host/cb710-mmc.c	2009-05-22 20:28:06.000000000 +0200
@@ -0,0 +1,804 @@
+/*
+ *  cb710/mmc.c
+ *
+ *  Copyright by Michał Mirosław, 2008-2009
+ *
+ * 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.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include "cb710-mmc.h"
+
+static const u8 cb710_clock_divider_log2[8] = {
+/*	1, 2, 4, 8, 16, 32, 128, 512 */
+	0, 1, 2, 3,  4,  5,   7,   9
+};
+#define CB710_MAX_DIVIDER_IDX	\
+	(ARRAY_SIZE(cb710_clock_divider_log2) - 1)
+
+static const u8 cb710_src_freq_mhz[16] = {
+	33, 10, 20, 25, 30, 35, 40, 45,
+	50, 55, 60, 65, 70, 75, 80, 85
+};
+
+static void cb710_mmc_set_clock(struct mmc_host *mmc, int hz)
+{
+	struct cb710_slot *slot = cb710_mmc_to_slot(mmc);
+	struct pci_dev *pdev = cb710_slot_to_chip(slot)->pdev;
+	u32 src_freq_idx;
+	u32 divider_idx;
+	int src_hz;
+
+	/* this is magic, unverifiable for me, unless I get
+	 * MMC card with cables connected to bus signals */
+	pci_read_config_dword(pdev, 0x48, &src_freq_idx);
+	src_freq_idx = (src_freq_idx >> 16) & 0xF;
+	src_hz = cb710_src_freq_mhz[src_freq_idx] * 1000000;
+
+	for (divider_idx = 0; divider_idx < CB710_MAX_DIVIDER_IDX; ++divider_idx) {
+		if (hz >= src_hz >> cb710_clock_divider_log2[divider_idx])
+			break;
+	}
+
+	if (src_freq_idx)
+		divider_idx |= 0x8;
+
+	cb710_pci_update_config_reg(pdev, 0x40, ~0xF0000000, divider_idx << 28);
+
+	dev_dbg(cb710_slot_dev(slot),
+		"clock set to %d Hz, wanted %d Hz; flag = %d\n",
+		src_hz >> cb710_clock_divider_log2[divider_idx & 7],
+		hz, (divider_idx & 8) != 0);
+}
+
+static void __cb710_mmc_enable_irq(struct cb710_slot *slot,
+	unsigned short enable, unsigned short mask)
+{
+	/* clear global IE
+	 * - it gets set later if any interrupt sources are enabled */
+	mask |= CB710_MMC_IE_IRQ_ENABLE;
+
+	/* look like interrupt is fired whenever
+	 * WORD[0x0C] & WORD[0x10] != 0;
+	 * -> bit 15 port 0x0C seems to be global interrupt enable
+	 */
+
+	enable = (cb710_read_port_16(slot, CB710_MMC_IRQ_ENABLE_PORT)
+		& ~mask) | enable;
+
+	if (enable)
+		enable |= CB710_MMC_IE_IRQ_ENABLE;
+
+	cb710_write_port_16(slot, CB710_MMC_IRQ_ENABLE_PORT, enable);
+}
+
+static void cb710_mmc_enable_irq(struct cb710_slot *slot,
+	unsigned short enable, unsigned short mask)
+{
+	struct cb710_mmc_reader *reader = mmc_priv(cb710_slot_to_mmc(slot));
+	unsigned long flags;
+
+	spin_lock_irqsave(&reader->irq_lock, flags);
+	/* this is the only thing irq_lock protects */
+	__cb710_mmc_enable_irq(slot, enable, mask);
+	spin_unlock_irqrestore(&reader->irq_lock, flags);
+}
+
+static void cb710_mmc_reset_events(struct cb710_slot *slot)
+{
+	cb710_write_port_8(slot, CB710_MMC_STATUS0_PORT, 0xFF);
+	cb710_write_port_8(slot, CB710_MMC_STATUS1_PORT, 0xFF);
+	cb710_write_port_8(slot, CB710_MMC_STATUS2_PORT, 0xFF);
+}
+
+static int cb710_mmc_is_card_inserted(struct cb710_slot *slot)
+{
+	return cb710_read_port_8(slot, CB710_MMC_STATUS3_PORT)
+		& CB710_MMC_S3_CARD_DETECTED;
+}
+
+static void cb710_mmc_enable_4bit_data(struct cb710_slot *slot, int enable)
+{
+	dev_dbg(cb710_slot_dev(slot), "configuring %d-data-line%s mode\n",
+		enable ? 4 : 1, enable ? "s" : "");
+	if (enable)
+		cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT,
+			CB710_MMC_C1_4BIT_DATA_BUS, 0);
+	else
+		cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT,
+			0, CB710_MMC_C1_4BIT_DATA_BUS);
+}
+
+static int cb710_check_event(struct cb710_slot *slot, u8 what)
+{
+	u16 status;
+
+	status = cb710_read_port_16(slot, CB710_MMC_STATUS_PORT);
+
+	if (status & CB710_MMC_S0_FIFO_UNDERFLOW) {
+		/* it is just a guess, so log it */
+		dev_dbg(cb710_slot_dev(slot),
+			"CHECK : ignoring bit 6 in status %04X\n", status);
+		cb710_write_port_8(slot, CB710_MMC_STATUS0_PORT,
+			CB710_MMC_S0_FIFO_UNDERFLOW);
+		status &= ~CB710_MMC_S0_FIFO_UNDERFLOW;
+	}
+
+	if (status & CB710_MMC_STATUS_ERROR_EVENTS) {
+		dev_dbg(cb710_slot_dev(slot),
+			"CHECK : returning EIO on status %04X\n", status);
+		cb710_write_port_8(slot, CB710_MMC_STATUS0_PORT, status & 0xFF);
+		cb710_write_port_8(slot, CB710_MMC_STATUS1_PORT,
+			CB710_MMC_S1_RESET);
+		return -EIO;
+	}
+
+	/* 'what' is a bit in MMC_STATUS1 */
+	if ((status >> 8) & what) {
+		cb710_write_port_8(slot, CB710_MMC_STATUS1_PORT, what);
+		return 1;
+	}
+
+	return 0;
+}
+
+static int cb710_wait_for_event(struct cb710_slot *slot, u8 what)
+{
+	int err = 0;
+	unsigned limit = 2000000;	/* FIXME: real timeout */
+
+#ifdef CONFIG_CB710_DEBUG
+	u32 e, x;
+	e = cb710_read_port_32(slot, CB710_MMC_STATUS_PORT);
+#endif
+
+	while (!(err = cb710_check_event(slot, what))) {
+		if (!--limit) {
+			cb710_dump_regs(cb710_slot_to_chip(slot),
+				CB710_DUMP_REGS_MMC);
+			err = -ETIMEDOUT;
+			break;
+		}
+		udelay(1);
+	}
+
+#ifdef CONFIG_CB710_DEBUG
+	x = cb710_read_port_32(slot, CB710_MMC_STATUS_PORT);
+
+	limit = 2000000 - limit;
+	if (limit > 100)
+		dev_dbg(cb710_slot_dev(slot),
+			"WAIT10: waited %d loops, what %d, entry val %08X, exit val %08X\n",
+			limit, what, e, x);
+#endif
+	return err < 0 ? err : 0;
+}
+
+
+static int cb710_wait_while_busy(struct cb710_slot *slot, uint8_t mask)
+{
+	unsigned limit = 500000;	/* FIXME: real timeout */
+	int err = 0;
+
+#ifdef CONFIG_CB710_DEBUG
+	u32 e, x;
+	e = cb710_read_port_32(slot, CB710_MMC_STATUS_PORT);
+#endif
+
+	while (cb710_read_port_8(slot, CB710_MMC_STATUS2_PORT) & mask) {
+		if (!--limit) {
+			cb710_dump_regs(cb710_slot_to_chip(slot),
+				CB710_DUMP_REGS_MMC);
+			err = -ETIMEDOUT;
+			break;
+		}
+		udelay(1);
+	}
+
+#ifdef CONFIG_CB710_DEBUG
+	x = cb710_read_port_32(slot, CB710_MMC_STATUS_PORT);
+
+	limit = 500000 - limit;
+	if (limit > 100)
+		dev_dbg(cb710_slot_dev(slot),
+			"WAIT12: waited %d loops, mask %02X, entry val %08X, exit val %08X\n",
+			limit, mask, e, x);
+#endif
+	return 0;
+}
+
+static void cb710_mmc_set_transfer_size(struct cb710_slot *slot,
+	size_t count, size_t blocksize)
+{
+	cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
+	cb710_write_port_32(slot, CB710_MMC_TRANSFER_SIZE_PORT,
+		((count - 1) << 16)|(blocksize - 1));
+
+	dev_vdbg(cb710_slot_dev(slot), "set up for %d block%s of %d bytes\n",
+		count, count == 1 ? "" : "s", blocksize);
+}
+
+static void cb710_mmc_fifo_hack(struct cb710_slot *slot)
+{
+	/* without this, received data is prepended with 8-bytes of zeroes */
+	u32 r1, r2;
+	int ok = 0;
+
+	r1 = cb710_read_port_32(slot, CB710_MMC_DATA_PORT);
+	r2 = cb710_read_port_32(slot, CB710_MMC_DATA_PORT);
+	if (cb710_read_port_8(slot, CB710_MMC_STATUS0_PORT)
+	    & CB710_MMC_S0_FIFO_UNDERFLOW) {
+		cb710_write_port_8(slot, CB710_MMC_STATUS0_PORT,
+			CB710_MMC_S0_FIFO_UNDERFLOW);
+		ok = 1;
+	}
+
+	dev_dbg(cb710_slot_dev(slot),
+		"FIFO-read-hack: expected STATUS0 bit was %s\n",
+		ok ? "set." : "NOT SET!");
+	dev_dbg(cb710_slot_dev(slot),
+		"FIFO-read-hack: dwords ignored: %08X %08X - %s\n",
+		r1, r2, (r1|r2) ? "BAD (NOT ZERO)!" : "ok");
+}
+
+static int cb710_mmc_receive_pio(struct cb710_slot *slot,
+	struct sg_mapping_iter *miter, size_t dw_count)
+{
+	if (!(cb710_read_port_8(slot, CB710_MMC_STATUS2_PORT) & CB710_MMC_S2_FIFO_READY)) {
+		int err = cb710_wait_for_event(slot,
+			CB710_MMC_S1_PIO_TRANSFER_DONE);
+		if (err)
+			return err;
+	}
+
+	cb710_sg_dwiter_write_from_io(miter,
+		slot->iobase + CB710_MMC_DATA_PORT, dw_count);
+
+	return 0;
+}
+
+static bool cb710_is_transfer_size_supported(struct mmc_data *data)
+{
+	return !(data->blksz & 15 && (data->blocks != 1 || data->blksz != 8));
+}
+
+static int cb710_mmc_receive(struct cb710_slot *slot, struct mmc_data *data)
+{
+	struct sg_mapping_iter miter;
+	size_t len, blocks = data->blocks;
+	int err = 0;
+
+	/* TODO: I don't know how/if the hardware handles non-16B-boundary blocks
+	 * except single 8B block */
+	if (unlikely(data->blksz & 15 && (data->blocks != 1 || data->blksz != 8)))
+		return -EINVAL;
+
+	sg_miter_start(&miter, data->sg, data->sg_len, 0);
+
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG2_PORT,
+		15, CB710_MMC_C2_READ_PIO_SIZE_MASK);
+
+	cb710_mmc_fifo_hack(slot);
+
+	while (blocks-- > 0) {
+		len = data->blksz;
+
+		while (len >= 16) {
+			err = cb710_mmc_receive_pio(slot, &miter, 4);
+			if (err)
+				goto out;
+			len -= 16;
+		}
+
+		if (!len)
+			continue;
+
+		cb710_modify_port_8(slot, CB710_MMC_CONFIG2_PORT,
+			len - 1, CB710_MMC_C2_READ_PIO_SIZE_MASK);
+
+		len = (len >= 8) ? 4 : 2;
+		err = cb710_mmc_receive_pio(slot, &miter, len);
+		if (err)
+			goto out;
+	}
+out:
+	cb710_sg_miter_stop_writing(&miter);
+	return err;
+}
+
+static int cb710_mmc_send(struct cb710_slot *slot, struct mmc_data *data)
+{
+	struct sg_mapping_iter miter;
+	size_t len, blocks = data->blocks;
+	int err = 0;
+
+	/* TODO: I don't know how/if the hardware handles multiple
+	 * non-16B-boundary blocks */
+	if (unlikely(data->blocks > 1 && data->blksz & 15))
+		return -EINVAL;
+
+	sg_miter_start(&miter, data->sg, data->sg_len, 0);
+
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG2_PORT,
+		0, CB710_MMC_C2_READ_PIO_SIZE_MASK);
+
+	while (blocks-- > 0) {
+		len = (data->blksz + 15) >> 4;
+		do {
+			if (!(cb710_read_port_8(slot, CB710_MMC_STATUS2_PORT)
+			    & CB710_MMC_S2_FIFO_EMPTY)) {
+				err = cb710_wait_for_event(slot,
+					CB710_MMC_S1_PIO_TRANSFER_DONE);
+				if (err)
+					goto out;
+			}
+			cb710_sg_dwiter_read_to_io(&miter,
+				slot->iobase + CB710_MMC_DATA_PORT, 4);
+		} while (--len);
+	}
+out:
+	sg_miter_stop(&miter);
+	return err;
+}
+
+static u16 cb710_encode_cmd_flags(struct cb710_mmc_reader *reader,
+	struct mmc_command *cmd)
+{
+	unsigned int flags = cmd->flags;
+	u16 cb_flags = 0;
+
+	/* Windows driver returned 0 for commands for which no response
+	 * is expected. It happened that there were only two such commands
+	 * used: MMC_GO_IDLE_STATE and MMC_GO_INACTIVE_STATE so it might
+	 * as well be a bug in that driver.
+	 *
+	 * Original driver set bit 14 for MMC/SD application
+	 * commands. There's no difference 'on the wire' and
+	 * it apparently works without it anyway.
+	 */
+
+	switch (flags & MMC_CMD_MASK) {
+	case MMC_CMD_AC:	cb_flags = CB710_MMC_CMD_AC;	break;
+	case MMC_CMD_ADTC:	cb_flags = CB710_MMC_CMD_ADTC;	break;
+	case MMC_CMD_BC:	cb_flags = CB710_MMC_CMD_BC;	break;
+	case MMC_CMD_BCR:	cb_flags = CB710_MMC_CMD_BCR;	break;
+	}
+
+	if (flags & MMC_RSP_BUSY)
+		cb_flags |= CB710_MMC_RSP_BUSY;
+
+	cb_flags |= cmd->opcode << CB710_MMC_CMD_CODE_SHIFT;
+
+	if (cmd->data && (cmd->data->flags & MMC_DATA_READ))
+		cb_flags |= CB710_MMC_DATA_READ;
+
+	if (flags & MMC_RSP_PRESENT) {
+		/* Windows driver set 01 at bits 4,3 except for
+		 * MMC_SET_BLOCKLEN where it set 10. Maybe the
+		 * hardware can do something special about this
+		 * command? The original driver looks buggy/incomplete
+		 * anyway so we ignore this for now.
+		 *
+		 * I assume that 00 here means no response is expected.
+		 */
+		cb_flags |= CB710_MMC_RSP_PRESENT;
+
+		if (flags & MMC_RSP_136)
+			cb_flags |= CB710_MMC_RSP_136;
+		if (!(flags & MMC_RSP_CRC))
+			cb_flags |= CB710_MMC_RSP_NO_CRC;
+	}
+
+	return cb_flags;
+}
+
+static void cb710_receive_response(struct cb710_slot *slot,
+	struct mmc_command *cmd)
+{
+	unsigned rsp_opcode, wanted_opcode;
+
+	/* Looks like final byte with CRC is always stripped (same as SDHCI) */
+	if (cmd->flags & MMC_RSP_136) {
+		u32 resp[4];
+
+		resp[0] = cb710_read_port_32(slot, CB710_MMC_RESPONSE3_PORT);
+		resp[1] = cb710_read_port_32(slot, CB710_MMC_RESPONSE2_PORT);
+		resp[2] = cb710_read_port_32(slot, CB710_MMC_RESPONSE1_PORT);
+		resp[3] = cb710_read_port_32(slot, CB710_MMC_RESPONSE0_PORT);
+		rsp_opcode = resp[0] >> 24;
+
+		cmd->resp[0] = (resp[0] << 8)|(resp[1] >> 24);
+		cmd->resp[1] = (resp[1] << 8)|(resp[2] >> 24);
+		cmd->resp[2] = (resp[2] << 8)|(resp[3] >> 24);
+		cmd->resp[3] = (resp[3] << 8);
+	} else {
+		rsp_opcode = cb710_read_port_32(slot, CB710_MMC_RESPONSE1_PORT) & 0x3F;
+		cmd->resp[0] = cb710_read_port_32(slot, CB710_MMC_RESPONSE0_PORT);
+	}
+
+	wanted_opcode = (cmd->flags & MMC_RSP_OPCODE) ? cmd->opcode : 0x3F;
+	if (rsp_opcode != wanted_opcode)
+		cmd->error = -EILSEQ;
+}
+
+static int cb710_mmc_transfer_data(struct cb710_slot *slot,
+	struct mmc_data *data)
+{
+	int error, to;
+
+	if (data->flags & MMC_DATA_READ)
+		error = cb710_mmc_receive(slot, data);
+	else
+		error = cb710_mmc_send(slot, data);
+
+	to = cb710_wait_for_event(slot, CB710_MMC_S1_DATA_TRANSFER_DONE);
+	if (!error)
+		error = to;
+
+	if (!error)
+		data->bytes_xfered = data->blksz * data->blocks;
+	return error;
+}
+
+static int cb710_mmc_command(struct mmc_host *mmc, struct mmc_command *cmd)
+{
+	struct cb710_slot *slot = cb710_mmc_to_slot(mmc);
+	struct cb710_mmc_reader *reader = mmc_priv(mmc);
+	struct mmc_data *data = cmd->data;
+
+	u16 cb_cmd = cb710_encode_cmd_flags(reader, cmd);
+	dev_dbg(cb710_slot_dev(slot), "cmd request: 0x%04X\n", cb_cmd);
+
+	if (data) {
+		if (!cb710_is_transfer_size_supported(data)) {
+			data->error = -EINVAL;
+			return -1;
+		}
+		cb710_mmc_set_transfer_size(slot, data->blocks, data->blksz);
+	}
+
+	cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20|CB710_MMC_S2_BUSY_10);
+	cb710_write_port_16(slot, CB710_MMC_CMD_TYPE_PORT, cb_cmd);
+	cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
+	cb710_write_port_32(slot, CB710_MMC_CMD_PARAM_PORT, cmd->arg);
+	cb710_mmc_reset_events(slot);
+	cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG0_PORT, 0x01, 0);
+
+	cmd->error = cb710_wait_for_event(slot, CB710_MMC_S1_COMMAND_SENT);
+	if (cmd->error)
+		return -1;
+
+	if (cmd->flags & MMC_RSP_PRESENT) {
+		cb710_receive_response(slot, cmd);
+		if (cmd->error)
+			return -1;
+	}
+
+	if (data)
+		data->error = cb710_mmc_transfer_data(slot, data);
+	return 0;
+}
+
+static void cb710_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+	struct cb710_slot *slot = cb710_mmc_to_slot(mmc);
+	struct cb710_mmc_reader *reader = mmc_priv(mmc);
+
+	WARN_ON(reader->mrq != NULL);
+
+	reader->mrq = mrq;
+	cb710_mmc_enable_irq(slot, CB710_MMC_IE_TEST_MASK, 0);
+
+	if (cb710_mmc_is_card_inserted(slot)) {
+		if (!cb710_mmc_command(mmc, mrq->cmd) && mrq->stop)
+			cb710_mmc_command(mmc, mrq->stop);
+		mdelay(1);
+	} else {
+		mrq->cmd->error = -ENOMEDIUM;
+	}
+
+	tasklet_schedule(&reader->finish_req_tasklet);
+}
+
+static int cb710_mmc_powerup(struct cb710_slot *slot)
+{
+#ifdef CONFIG_CB710_DEBUG
+	struct cb710_chip *chip = cb710_slot_to_chip(slot);
+#endif
+	int err;
+
+	/* a lot of magic; see comment in cb710_mmc_set_clock() */
+	dev_dbg(cb710_slot_dev(slot), "bus powerup\n");
+	cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+	err = cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
+	if (unlikely(err))
+		return err;
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT, 0x80, 0);
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG3_PORT, 0x80, 0);
+	cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+	mdelay(1);
+	dev_dbg(cb710_slot_dev(slot), "after delay 1\n");
+	cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+	err = cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
+	if (unlikely(err))
+		return err;
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT, 0x09, 0);
+	cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+	mdelay(1);
+	dev_dbg(cb710_slot_dev(slot), "after delay 2\n");
+	cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+	err = cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
+	if (unlikely(err))
+		return err;
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT, 0, 0x08);
+	cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+	mdelay(2);
+	dev_dbg(cb710_slot_dev(slot), "after delay 3\n");
+	cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG0_PORT, 0x06, 0);
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT, 0x70, 0);
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG2_PORT, 0x80, 0);
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG3_PORT, 0x03, 0);
+	cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+	err = cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
+	if (unlikely(err))
+		return err;
+	/* This port behaves weird: quick byte reads of 0x08,0x09 return
+	 * 0xFF,0x00 after writing 0xFFFF to 0x08; it works correctly when
+	 * read/written from userspace...  What am I missing here?
+	 * (it doesn't depend on write-to-read delay) */
+	cb710_write_port_16(slot, CB710_MMC_CONFIGB_PORT, 0xFFFF);
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG0_PORT, 0x06, 0);
+	cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+	dev_dbg(cb710_slot_dev(slot), "bus powerup finished\n");
+
+	return cb710_check_event(slot, 0);
+}
+
+static void cb710_mmc_powerdown(struct cb710_slot *slot)
+{
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT, 0, 0x81);
+	cb710_modify_port_8(slot, CB710_MMC_CONFIG3_PORT, 0, 0x80);
+}
+
+static void cb710_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+	struct cb710_slot *slot = cb710_mmc_to_slot(mmc);
+	struct cb710_mmc_reader *reader = mmc_priv(mmc);
+	int err;
+
+	cb710_mmc_set_clock(mmc, ios->clock);
+
+	if (!cb710_mmc_is_card_inserted(slot)) {
+		dev_dbg(cb710_slot_dev(slot),
+			"no card inserted - ignoring bus powerup request\n");
+		ios->power_mode = MMC_POWER_OFF;
+	}
+
+	if (ios->power_mode != reader->last_power_mode)
+	switch (ios->power_mode) {
+	case MMC_POWER_ON:
+		err = cb710_mmc_powerup(slot);
+		if (err) {
+			dev_warn(cb710_slot_dev(slot),
+				"powerup failed (%d)- retrying\n", err);
+			cb710_mmc_powerdown(slot);
+			udelay(1);
+			err = cb710_mmc_powerup(slot);
+			if (err)
+				dev_warn(cb710_slot_dev(slot),
+					"powerup retry failed (%d) - expect errors\n",
+					err);
+		}
+		reader->last_power_mode = MMC_POWER_ON;
+		break;
+	case MMC_POWER_OFF:
+		cb710_mmc_powerdown(slot);
+		reader->last_power_mode = MMC_POWER_OFF;
+		break;
+	case MMC_POWER_UP:
+	default:
+		/* ignore */;
+	}
+
+	cb710_mmc_enable_4bit_data(slot, ios->bus_width != MMC_BUS_WIDTH_1);
+
+	cb710_mmc_enable_irq(slot, CB710_MMC_IE_TEST_MASK, 0);
+}
+
+static int cb710_mmc_get_ro(struct mmc_host *mmc)
+{
+	struct cb710_slot *slot = cb710_mmc_to_slot(mmc);
+
+	return cb710_read_port_8(slot, CB710_MMC_STATUS3_PORT)
+		& CB710_MMC_S3_WRITE_PROTECTED;
+}
+
+static int cb710_mmc_irq_handler(struct cb710_slot *slot)
+{
+	struct mmc_host *mmc = cb710_slot_to_mmc(slot);
+	struct cb710_mmc_reader *reader = mmc_priv(mmc);
+	u32 status, config1, config2, irqen;
+
+	status = cb710_read_port_32(slot, CB710_MMC_STATUS_PORT);
+	irqen = cb710_read_port_32(slot, CB710_MMC_IRQ_ENABLE_PORT);
+	config2 = cb710_read_port_32(slot, CB710_MMC_CONFIGB_PORT);
+	config1 = cb710_read_port_32(slot, CB710_MMC_CONFIG_PORT);
+
+	dev_dbg(cb710_slot_dev(slot), "interrupt; status: %08X, "
+		"ie: %08X, c2: %08X, c1: %08X\n",
+		status, irqen, config2, config1);
+
+	if (status & (CB710_MMC_S1_CARD_CHANGED << 8)) {
+		/* ack the event */
+		cb710_write_port_8(slot, CB710_MMC_STATUS1_PORT,
+			CB710_MMC_S1_CARD_CHANGED);
+		if ((irqen & CB710_MMC_IE_CISTATUS_MASK)
+		    == CB710_MMC_IE_CISTATUS_MASK)
+			mmc_detect_change(mmc, HZ/5);
+	} else {
+		dev_dbg(cb710_slot_dev(slot), "unknown interrupt (test)\n");
+		spin_lock(&reader->irq_lock);
+		__cb710_mmc_enable_irq(slot, 0, CB710_MMC_IE_TEST_MASK);
+		spin_unlock(&reader->irq_lock);
+	}
+
+	return 1;
+}
+
+static void cb710_mmc_finish_request_tasklet(unsigned long data)
+{
+	struct mmc_host *mmc = (void *)data;
+	struct cb710_mmc_reader *reader = mmc_priv(mmc);
+	struct mmc_request *mrq = reader->mrq;
+
+	reader->mrq = NULL;
+	mmc_request_done(mmc, mrq);
+}
+
+static const struct mmc_host_ops cb710_mmc_host = {
+	.request = cb710_mmc_request,
+	.set_ios = cb710_mmc_set_ios,
+	.get_ro = cb710_mmc_get_ro
+};
+
+#ifdef CONFIG_PM
+
+static int cb710_mmc_suspend(struct platform_device *pdev, pm_message_t state)
+{
+	struct cb710_slot *slot = cb710_pdev_to_slot(pdev);
+	struct mmc_host *mmc = cb710_slot_to_mmc(slot);
+	int err;
+
+	err = mmc_suspend_host(mmc, state);
+	if (err)
+		return err;
+
+	cb710_mmc_enable_irq(slot, 0, ~0);
+	return 0;
+}
+
+static int cb710_mmc_resume(struct platform_device *pdev)
+{
+	struct cb710_slot *slot = cb710_pdev_to_slot(pdev);
+	struct mmc_host *mmc = cb710_slot_to_mmc(slot);
+
+	cb710_mmc_enable_irq(slot, 0, ~0);
+
+	return mmc_resume_host(mmc);
+}
+
+#endif /* CONFIG_PM */
+
+static int __devinit cb710_mmc_init(struct platform_device *pdev)
+{
+	struct cb710_slot *slot = cb710_pdev_to_slot(pdev);
+	struct cb710_chip *chip = cb710_slot_to_chip(slot);
+	struct mmc_host *mmc;
+	struct cb710_mmc_reader *reader;
+	int err;
+	u32 val;
+
+	mmc = mmc_alloc_host(sizeof(*reader), cb710_slot_dev(slot));
+	if (!mmc)
+		return -ENOMEM;
+
+	dev_set_drvdata(&pdev->dev, mmc);
+
+	/* harmless (maybe) magic */
+	pci_read_config_dword(chip->pdev, 0x48, &val);
+	val = cb710_src_freq_mhz[(val >> 16) & 0xF];
+	dev_dbg(cb710_slot_dev(slot), "source frequency: %dMHz\n", val);
+	val *= 1000000;
+
+	mmc->ops = &cb710_mmc_host;
+	mmc->f_max = val;
+	mmc->f_min = val >> cb710_clock_divider_log2[CB710_MAX_DIVIDER_IDX];
+	mmc->ocr_avail = MMC_VDD_32_33|MMC_VDD_33_34;
+	mmc->caps = MMC_CAP_4_BIT_DATA;
+
+	reader = mmc_priv(mmc);
+
+	tasklet_init(&reader->finish_req_tasklet,
+		cb710_mmc_finish_request_tasklet, (unsigned long)mmc);
+	spin_lock_init(&reader->irq_lock);
+	cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+
+	cb710_mmc_enable_irq(slot, 0, ~0);
+	cb710_set_irq_handler(slot, cb710_mmc_irq_handler);
+
+	err = mmc_add_host(mmc);
+	if (unlikely(err))
+		goto err_free_mmc;
+
+	dev_dbg(cb710_slot_dev(slot), "mmc_hostname is %s\n",
+		mmc_hostname(mmc));
+
+	cb710_mmc_enable_irq(slot, CB710_MMC_IE_CARD_INSERTION_STATUS, 0);
+
+	return 0;
+
+err_free_mmc:
+	dev_dbg(cb710_slot_dev(slot), "mmc_add_host() failed: %d\n", err);
+
+	mmc_free_host(mmc);
+	return err;
+}
+
+static int __devexit cb710_mmc_exit(struct platform_device *pdev)
+{
+	struct cb710_slot *slot = cb710_pdev_to_slot(pdev);
+	struct mmc_host *mmc = cb710_slot_to_mmc(slot);
+	struct cb710_mmc_reader *reader = mmc_priv(mmc);
+
+	cb710_mmc_enable_irq(slot, 0, CB710_MMC_IE_CARD_INSERTION_STATUS);
+
+	mmc_remove_host(mmc);
+
+	/* IRQs should be disabled now, but let's stay on the safe side */
+	cb710_mmc_enable_irq(slot, 0, ~0);
+	cb710_set_irq_handler(slot, NULL);
+
+	/* clear config ports - just in case */
+	cb710_write_port_32(slot, CB710_MMC_CONFIG_PORT, 0);
+	cb710_write_port_16(slot, CB710_MMC_CONFIGB_PORT, 0);
+
+	tasklet_kill(&reader->finish_req_tasklet);
+
+	mmc_free_host(mmc);
+	return 0;
+}
+
+static struct platform_driver cb710_mmc_driver = {
+	.driver.name = "cb710-mmc",
+	.probe = cb710_mmc_init,
+	.remove = __devexit_p(cb710_mmc_exit),
+#ifdef CONFIG_PM
+	.suspend = cb710_mmc_suspend,
+	.resume = cb710_mmc_resume,
+#endif
+};
+
+static int __init cb710_mmc_init_module(void)
+{
+	return platform_driver_register(&cb710_mmc_driver);
+}
+
+static void __exit cb710_mmc_cleanup_module(void)
+{
+	platform_driver_unregister(&cb710_mmc_driver);
+}
+
+module_init(cb710_mmc_init_module);
+module_exit(cb710_mmc_cleanup_module);
+
+MODULE_AUTHOR("Michał Mirosław <mirq-linux@rere.qmqm.pl>");
+MODULE_DESCRIPTION("ENE CB710 memory card reader driver - MMC/SD part");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:cb710-mmc");
diff -urpN linux-2.6.29.4/drivers/mmc/host/cb710-mmc.h test2/drivers/mmc/host/cb710-mmc.h
--- linux-2.6.29.4/drivers/mmc/host/cb710-mmc.h	1970-01-01 01:00:00.000000000 +0100
+++ test2/drivers/mmc/host/cb710-mmc.h	2009-05-22 20:28:06.000000000 +0200
@@ -0,0 +1,104 @@
+/*
+ *  cb710/cb710-mmc.h
+ *
+ *  Copyright by Michał Mirosław, 2008-2009
+ *
+ * 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.
+ */
+#ifndef LINUX_CB710_MMC_H
+#define LINUX_CB710_MMC_H
+
+#include <linux/cb710.h>
+
+/* per-MMC-reader structure */
+struct cb710_mmc_reader {
+	struct tasklet_struct finish_req_tasklet;
+	struct mmc_request *mrq;
+	spinlock_t irq_lock;
+	unsigned char last_power_mode;
+};
+
+/* some device struct walking */
+
+static inline struct mmc_host *cb710_slot_to_mmc(struct cb710_slot *slot)
+{
+	return dev_get_drvdata(&slot->pdev.dev);
+}
+
+static inline struct cb710_slot *cb710_mmc_to_slot(struct mmc_host *mmc)
+{
+	struct platform_device *pdev = container_of(mmc_dev(mmc),
+		struct platform_device, dev);
+	return cb710_pdev_to_slot(pdev);
+}
+
+/* registers (this might be all wrong ;) */
+
+#define CB710_MMC_DATA_PORT		0x00
+
+#define CB710_MMC_CONFIG_PORT		0x04
+#define CB710_MMC_CONFIG0_PORT		0x04
+#define CB710_MMC_CONFIG1_PORT		0x05
+#define   CB710_MMC_C1_4BIT_DATA_BUS		0x40
+#define CB710_MMC_CONFIG2_PORT		0x06
+#define   CB710_MMC_C2_READ_PIO_SIZE_MASK	0x0F	/* N-1 */
+#define CB710_MMC_CONFIG3_PORT		0x07
+
+#define CB710_MMC_CONFIGB_PORT		0x08
+
+#define CB710_MMC_IRQ_ENABLE_PORT	0x0C
+#define   CB710_MMC_IE_TEST_MASK		0x00BF
+#define   CB710_MMC_IE_CARD_INSERTION_STATUS	0x1000
+#define   CB710_MMC_IE_IRQ_ENABLE		0x8000
+#define   CB710_MMC_IE_CISTATUS_MASK		\
+		(CB710_MMC_IE_CARD_INSERTION_STATUS|CB710_MMC_IE_IRQ_ENABLE)
+
+#define CB710_MMC_STATUS_PORT		0x10
+#define   CB710_MMC_STATUS_ERROR_EVENTS		0x60FF
+#define CB710_MMC_STATUS0_PORT		0x10
+#define   CB710_MMC_S0_FIFO_UNDERFLOW		0x40
+#define CB710_MMC_STATUS1_PORT		0x11
+#define   CB710_MMC_S1_COMMAND_SENT		0x01
+#define   CB710_MMC_S1_DATA_TRANSFER_DONE	0x02
+#define   CB710_MMC_S1_PIO_TRANSFER_DONE	0x04
+#define   CB710_MMC_S1_CARD_CHANGED		0x10
+#define   CB710_MMC_S1_RESET			0x20
+#define CB710_MMC_STATUS2_PORT		0x12
+#define   CB710_MMC_S2_FIFO_READY		0x01
+#define   CB710_MMC_S2_FIFO_EMPTY		0x02
+#define   CB710_MMC_S2_BUSY_10			0x10
+#define   CB710_MMC_S2_BUSY_20			0x20
+#define CB710_MMC_STATUS3_PORT		0x13
+#define   CB710_MMC_S3_CARD_DETECTED		0x02
+#define   CB710_MMC_S3_WRITE_PROTECTED		0x04
+
+#define CB710_MMC_CMD_TYPE_PORT		0x14
+#define   CB710_MMC_RSP_TYPE_MASK		0x0007
+#define     CB710_MMC_RSP_R1			(0)
+#define     CB710_MMC_RSP_136			(5)
+#define     CB710_MMC_RSP_NO_CRC		(2)
+#define   CB710_MMC_RSP_PRESENT_MASK		0x0018
+#define     CB710_MMC_RSP_NONE			(0 << 3)
+#define     CB710_MMC_RSP_PRESENT		(1 << 3)
+#define     CB710_MMC_RSP_PRESENT_X		(2 << 3)
+#define   CB710_MMC_CMD_TYPE_MASK		0x0060
+#define     CB710_MMC_CMD_BC			(0 << 5)
+#define     CB710_MMC_CMD_BCR			(1 << 5)
+#define     CB710_MMC_CMD_AC			(2 << 5)
+#define     CB710_MMC_CMD_ADTC			(3 << 5)
+#define   CB710_MMC_DATA_READ			0x0080
+#define   CB710_MMC_CMD_CODE_MASK		0x3F00
+#define   CB710_MMC_CMD_CODE_SHIFT		8
+#define   CB710_MMC_IS_APP_CMD			0x4000
+#define   CB710_MMC_RSP_BUSY			0x8000
+
+#define CB710_MMC_CMD_PARAM_PORT	0x18
+#define CB710_MMC_TRANSFER_SIZE_PORT	0x1C
+#define CB710_MMC_RESPONSE0_PORT	0x20
+#define CB710_MMC_RESPONSE1_PORT	0x24
+#define CB710_MMC_RESPONSE2_PORT	0x28
+#define CB710_MMC_RESPONSE3_PORT	0x2C
+
+#endif /* LINUX_CB710_MMC_H */
diff -urpN linux-2.6.29.4/drivers/mmc/host/Kconfig test2/drivers/mmc/host/Kconfig
--- linux-2.6.29.4/drivers/mmc/host/Kconfig	2009-03-24 00:12:14.000000000 +0100
+++ test2/drivers/mmc/host/Kconfig	2009-05-22 20:28:02.000000000 +0200
@@ -212,3 +212,16 @@ config MMC_TMIO
 	help
 	  This provides support for the SD/MMC cell found in TC6393XB,
 	  T7L66XB and also ipaq ASIC3
+
+config MMC_CB710
+	tristate "ENE CB710 MMC/SD Interface support"
+	depends on PCI
+	select CB710_CORE
+	help
+	  This option enables support for MMC/SD part of ENE CB710/720 Flash
+	  memory card reader found in some laptops (ie. some versions of
+	  HP Compaq nx9500).
+
+	  This driver can also be built as a module. If so, the module
+	  will be called cb710-mmc.
+
diff -urpN linux-2.6.29.4/drivers/mmc/host/Makefile test2/drivers/mmc/host/Makefile
--- linux-2.6.29.4/drivers/mmc/host/Makefile	2009-03-24 00:12:14.000000000 +0100
+++ test2/drivers/mmc/host/Makefile	2009-05-22 20:28:02.000000000 +0200
@@ -27,4 +27,5 @@ endif
 obj-$(CONFIG_MMC_S3C)   	+= s3cmci.o
 obj-$(CONFIG_MMC_SDRICOH_CS)	+= sdricoh_cs.o
 obj-$(CONFIG_MMC_TMIO)		+= tmio_mmc.o
+obj-$(CONFIG_MMC_CB710)	+= cb710-mmc.o
 
diff -urpN linux-2.6.29.4/include/linux/cb710.h test2/include/linux/cb710.h
--- linux-2.6.29.4/include/linux/cb710.h	1970-01-01 01:00:00.000000000 +0100
+++ test2/include/linux/cb710.h	2009-05-22 20:28:06.000000000 +0200
@@ -0,0 +1,238 @@
+/*
+ *  cb710/cb710.h
+ *
+ *  Copyright by Michał Mirosław, 2008-2009
+ *
+ * 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.
+ */
+#ifndef LINUX_CB710_DRIVER_H
+#define LINUX_CB710_DRIVER_H
+
+#ifdef CONFIG_CB710_DEBUG
+#define DEBUG
+#endif
+
+/* verify assumptions on platform_device framework */
+#define CONFIG_CB710_DEBUG_ASSUMPTIONS
+
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+#include <linux/pci.h>
+#include <linux/platform_device.h>
+#include <linux/mmc/host.h>
+
+struct cb710_slot;
+
+typedef int (*cb710_irq_handler_t)(struct cb710_slot *);
+
+/* per-virtual-slot structure */
+struct cb710_slot {
+	struct platform_device	pdev;
+	void __iomem		*iobase;
+	cb710_irq_handler_t	irq_handler;
+};
+
+/* per-device structure */
+struct cb710_chip {
+	struct pci_dev		*pdev;
+	void __iomem		*iobase;
+	unsigned		platform_id;
+#ifdef CONFIG_CB710_DEBUG_ASSUMPTIONS
+	atomic_t		slot_refs_count;
+#endif
+	unsigned		slot_mask;
+	unsigned		slots;
+	spinlock_t		irq_lock;
+	struct cb710_slot	slot[0];
+};
+
+/* NOTE: cb710_chip.slots is modified only during device init/exit and
+ * they are all serialized wrt themselves */
+
+/* cb710_chip.slot_mask values */
+#define CB710_SLOT_MMC		1
+#define CB710_SLOT_MS		2
+#define CB710_SLOT_SM		4
+
+/* slot port accessors - so the logic is more clear in the code */
+#define CB710_PORT_ACCESSORS(t) \
+static inline void cb710_write_port_##t(struct cb710_slot *slot,	\
+	unsigned port, u##t value)					\
+{									\
+	iowrite##t(value, slot->iobase + port);				\
+}									\
+									\
+static inline u##t cb710_read_port_##t(struct cb710_slot *slot,		\
+	unsigned port)							\
+{									\
+	return ioread##t(slot->iobase + port);				\
+}									\
+									\
+static inline void cb710_modify_port_##t(struct cb710_slot *slot,	\
+	unsigned port, u##t set, u##t clear)				\
+{									\
+	iowrite##t(							\
+		(ioread##t(slot->iobase + port) & ~clear)|set,		\
+		slot->iobase + port);					\
+}
+
+CB710_PORT_ACCESSORS(8)
+CB710_PORT_ACCESSORS(16)
+CB710_PORT_ACCESSORS(32)
+
+void cb710_pci_update_config_reg(struct pci_dev *pdev,
+	int reg, uint32_t and, uint32_t xor);
+void cb710_set_irq_handler(struct cb710_slot *slot,
+	cb710_irq_handler_t handler);
+
+/* some device struct walking */
+
+static inline struct cb710_slot *cb710_pdev_to_slot(
+	struct platform_device *pdev)
+{
+	return container_of(pdev, struct cb710_slot, pdev);
+}
+
+static inline struct cb710_chip *cb710_slot_to_chip(struct cb710_slot *slot)
+{
+	return dev_get_drvdata(slot->pdev.dev.parent);
+}
+
+static inline struct device *cb710_slot_dev(struct cb710_slot *slot)
+{
+	return &slot->pdev.dev;
+}
+
+static inline struct device *cb710_chip_dev(struct cb710_chip *chip)
+{
+	return &chip->pdev->dev;
+}
+
+/* debugging aids */
+
+#ifdef CONFIG_CB710_DEBUG
+void cb710_dump_regs(struct cb710_chip *chip, unsigned dump);
+#else
+#define cb710_dump_regs(c, d) do {} while (0)
+#endif
+
+#define CB710_DUMP_REGS_MMC	0x0F
+#define CB710_DUMP_REGS_MS	0x30
+#define CB710_DUMP_REGS_SM	0xC0
+#define CB710_DUMP_REGS_ALL	0xFF
+#define CB710_DUMP_REGS_MASK	0xFF
+
+#define CB710_DUMP_ACCESS_8	0x100
+#define CB710_DUMP_ACCESS_16	0x200
+#define CB710_DUMP_ACCESS_32	0x400
+#define CB710_DUMP_ACCESS_ALL	0x700
+#define CB710_DUMP_ACCESS_MASK	0x700
+
+#endif /* LINUX_CB710_DRIVER_H */
+/*
+ *  cb710/sgbuf2.h
+ *
+ *  Copyright by Michał Mirosław, 2008-2009
+ *
+ * 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.
+ */
+#ifndef LINUX_CB710_SG_H
+#define LINUX_CB710_SG_H
+
+#include <linux/highmem.h>
+#include <linux/scatterlist.h>
+
+/**
+ * cb710_sg_miter_stop_writing - stop mapping iteration after writing
+ * @miter: sg mapping iter to be stopped
+ *
+ * Description:
+ *   Stops mapping iterator @miter.  @miter should have been started
+ *   started using sg_miter_start().  A stopped iteration can be
+ *   resumed by calling sg_miter_next() on it.  This is useful when
+ *   resources (kmap) need to be released during iteration.
+ *
+ *   This is a convenience wrapper that will be optimized out for arches
+ *   that don't need flush_kernel_dcache_page().
+ *
+ * Context:
+ *   IRQ disabled if the SG_MITER_ATOMIC is set.  Don't care otherwise.
+ */
+static inline void cb710_sg_miter_stop_writing(struct sg_mapping_iter *miter)
+{
+	if (miter->page)
+		flush_kernel_dcache_page(miter->page);
+	sg_miter_stop(miter);
+}
+
+/*
+ * 32-bit PIO mapping sg iterator
+ *
+ * Hides scatterlist access issues - fragment boundaries, alignment, page
+ * mapping - for drivers using 32-bit-word-at-a-time-PIO (ie. PCI devices
+ * without DMA support).
+ *
+ * Best-case reading (transfer from device):
+ *   sg_miter_start();
+ *   cb710_sg_dwiter_write_from_io();
+ *   cb710_sg_miter_stop_writing();
+ *
+ * Best-case writing (transfer to device):
+ *   sg_miter_start();
+ *   cb710_sg_dwiter_read_to_io();
+ *   sg_miter_stop();
+ */
+
+uint32_t cb710_sg_dwiter_read_next_block(struct sg_mapping_iter *miter);
+void cb710_sg_dwiter_write_next_block(struct sg_mapping_iter *miter, uint32_t data);
+
+/**
+ * cb710_sg_dwiter_write_from_io - transfer data to mapped buffer from 32-bit IO port
+ * @miter: sg mapping iter
+ * @port: PIO port - IO or MMIO address
+ * @count: number of 32-bit words to transfer
+ *
+ * Description:
+ *   Reads @count 32-bit words from register @port and stores it in
+ *   buffer iterated by @miter.  Data that would overflow the buffer
+ *   is silently ignored.  Iterator is advanced by 4*@count bytes
+ *   or to the buffer's end whichever is closer.
+ *
+ * Context:
+ *   IRQ disabled if the SG_MITER_ATOMIC is set.  Don't care otherwise.
+ */
+static inline void cb710_sg_dwiter_write_from_io(struct sg_mapping_iter *miter,
+	void __iomem *port, size_t count)
+{
+	while (count-- > 0)
+		cb710_sg_dwiter_write_next_block(miter, ioread32(port));
+}
+
+/**
+ * cb710_sg_dwiter_read_to_io - transfer data to 32-bit IO port from mapped buffer
+ * @miter: sg mapping iter
+ * @port: PIO port - IO or MMIO address
+ * @count: number of 32-bit words to transfer
+ *
+ * Description:
+ *   Writes @count 32-bit words to register @port from buffer iterated
+ *   through @miter.  If buffer ends before @count words are written
+ *   missing data is replaced by zeroes. @miter is advanced by 4*@count
+ *   bytes or to the buffer's end whichever is closer.
+ *
+ * Context:
+ *   IRQ disabled if the SG_MITER_ATOMIC is set.  Don't care otherwise.
+ */
+static inline void cb710_sg_dwiter_read_to_io(struct sg_mapping_iter *miter,
+	void __iomem *port, size_t count)
+{
+	while (count-- > 0)
+		iowrite32(cb710_sg_dwiter_read_next_block(miter), port);
+}
+
+#endif /* LINUX_CB710_SG_H */
diff -urpN linux-2.6.29.4/include/linux/pci_ids.h test2/include/linux/pci_ids.h
--- linux-2.6.29.4/include/linux/pci_ids.h	2009-03-24 00:12:14.000000000 +0100
+++ test2/include/linux/pci_ids.h	2009-05-22 20:28:02.000000000 +0200
@@ -2077,6 +2077,7 @@
 #define PCI_VENDOR_ID_MAINPINE		0x1522
 #define PCI_DEVICE_ID_MAINPINE_PBRIDGE	0x0100
 #define PCI_VENDOR_ID_ENE		0x1524
+#define PCI_DEVICE_ID_ENE_CB710_FLASH	0x0510
 #define PCI_DEVICE_ID_ENE_CB712_SD	0x0550
 #define PCI_DEVICE_ID_ENE_CB712_SD_2	0x0551
 #define PCI_DEVICE_ID_ENE_CB714_SD	0x0750
diff -urpN linux-2.6.29.4/MAINTAINERS test2/MAINTAINERS
--- linux-2.6.29.4/MAINTAINERS	2009-03-24 00:12:14.000000000 +0100
+++ test2/MAINTAINERS	2009-05-22 20:28:06.000000000 +0200
@@ -1660,6 +1660,15 @@ L:	linux-scsi@vger.kernel.org
 W:	http://sourceforge.net/projects/lpfcxxxx
 S:	Supported
 
+ENE CB710 FLASH CARD READER DRIVER
+P:	Michał Mirosław
+M:	mirq-linux@rere.qmqm.pl
+L:	linux-kernel@vger.kernel.org
+S:	Maintained
+F:	drivers/misc/cb710/
+F:	drivers/mmc/host/cb710-mmc.*
+F:	include/linux/cb710.h
+
 EPSON 1355 FRAMEBUFFER DRIVER
 P:	Christopher Hoover
 M:	ch@murgatroid.com, ch@hpl.hp.com

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

* Re: [PATCH/RFC 2.6.29.2] Driver for CB710/720 memory card reader (MMC part) - v4
  2009-05-22 11:27                         ` Pierre Ossman
  2009-05-22 17:55                           ` [PATCH 2.6.29.3] Driver for CB710/720 memory card reader (MMC part) - v5 Michał Mirosław
@ 2009-05-22 19:04                           ` Bartlomiej Zolnierkiewicz
  1 sibling, 0 replies; 21+ messages in thread
From: Bartlomiej Zolnierkiewicz @ 2009-05-22 19:04 UTC (permalink / raw)
  To: Pierre Ossman; +Cc: Michał Mirosław, linux-kernel

On Friday 22 May 2009 13:27:55 Pierre Ossman wrote:
> On Fri, 8 May 2009 17:16:22 +0200
> Michał Mirosław <mirq@rere.qmqm.pl> wrote:
> 
> > Here is next version of the CB710 SD/MMC reader driver. As in previous
> > versions the code is divided in two parts - virtual 'bus' driver that
> > handles PCI device and registers three new devices one per card reader
> > type. The other device handles SD/MMC part of the reader.
> > 
> > Key changes from v3:
> >  - kerneldoc for sg_mapping_iterator extension
> >  - free/restore IRQ during suspend/resume
> >  - protect slot->irq_handler by spinlock instead of RCU
> >  - remove verify_serialization()
> >  - Kconfig
> >  - complete patch for linux kernel 2.6.29.2
> > 
> 
> Looks good to me. I just want you to add a MAINTAINERS entry so that
> people know who to contact if they have problems with this driver.
> 
> > My patches for sg_mapping_iterator extension went to /dev/null, so I have
> > incorporated them to the cb710 core driver module for now. Probably someone
> > with more karma points should look at them (sgbuf2.{c,h}) and push further
> > if they are of any value to other driver writers.

I like the general idea and I think that this could serve as a good starting
point into making generic PIO handling library (which has been on my personal
TODO of things to look into for a long time).

Michał, I'll try ping you back about it once I'm done with 2.6.31 IDE stuff..

> Give them to Andrew Morton. He's good at finding the right people and
> making sure they notice. :)

Hehe, please keep me on cc:.

Thanks.
Bart

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

* Re: [PATCH 2.6.29.4] Driver for CB710/720 memory card reader (MMC part) - v5 fixed
  2009-05-22 18:33                             ` [PATCH 2.6.29.4] Driver for CB710/720 memory card reader (MMC part) - v5 fixed Michał Mirosław
@ 2009-05-27 20:13                               ` Pierre Ossman
  2009-06-04 10:24                                 ` Michał Mirosław
  0 siblings, 1 reply; 21+ messages in thread
From: Pierre Ossman @ 2009-05-27 20:13 UTC (permalink / raw)
  To: Michał Mirosław; +Cc: linux-kernel

[-- Attachment #1: Type: text/plain, Size: 1101 bytes --]

On Fri, 22 May 2009 20:33:59 +0200
Michał Mirosław <mirq-linux@rere.qmqm.pl> wrote:

> [I messed up source trees for previous diff, please use this one instead.]
> 
> Here is a version of the CB710 SD/MMC reader driver ready for inclusion
> upstream. The code is divided in two parts. There is a virtual 'bus' driver
> that handles PCI device and registers three new devices one per card reader
> type. The other driver handles SD/MMC part of the reader.
> 
> Changes from v4:
>   - sg_mapping_iterator extension renamed to avoid namespace clashes
>     when/if the extension would be merged upstream
>   - appended sgbuf2.h to cb710.h
>   - move from IDR to IDA for device ID generation
>   - cleanup after IDA on module unload
>   - added MAINTAINERS entry
>  
> Signed-off-by: Michał Mirosław <mirq-linux@rere.qmqm.pl>
> 

Queued.

Nice work. :)

-- 
     -- Pierre Ossman

  WARNING: This correspondence is being monitored by the
  Swedish government. Make sure your server uses encryption
  for SMTP traffic and consider using PGP for end-to-end
  encryption.

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 198 bytes --]

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

* Re: [PATCH 2.6.29.4] Driver for CB710/720 memory card reader (MMC part) - v5 fixed
  2009-05-27 20:13                               ` Pierre Ossman
@ 2009-06-04 10:24                                 ` Michał Mirosław
  2009-06-05  9:26                                   ` Michał Mirosław
  2009-06-13 10:39                                   ` Pierre Ossman
  0 siblings, 2 replies; 21+ messages in thread
From: Michał Mirosław @ 2009-06-04 10:24 UTC (permalink / raw)
  To: Pierre Ossman; +Cc: linux-kernel

On Wed, May 27, 2009 at 10:13:42PM +0200, Pierre Ossman wrote:
> On Fri, 22 May 2009 20:33:59 +0200
> Michał Mirosław <mirq-linux@rere.qmqm.pl> wrote:
[cut: CB710 driver patch description]
> Queued.

One more fixup for the driver following.

Best Regards,
Michał Mirosław

---here---

cb710: Fix compilation warning in DEBUG case.

Signed-off-by: Michał Mirosław <mirq-linux@rere.qmqm.pl>

diff -urpN test1/drivers/misc/cb710/Kconfig test2/drivers/misc/cb710/Kconfig
--- test1/drivers/misc/cb710/Kconfig	2009-06-04 12:20:29.000000000 +0200
+++ test2/drivers/misc/cb710/Kconfig	2009-06-04 10:53:34.000000000 +0200
@@ -19,3 +19,8 @@ config CB710_DEBUG
 	  This is an option for use by developers; most people should
 	  say N here.  This adds a lot of debugging output to dmesg.
 
+config CB710_DEBUG_ASSUMPTIONS
+	bool
+	depends on CB710_CORE != n
+	default y
+
diff -urpN test1/drivers/misc/cb710/Makefile test2/drivers/misc/cb710/Makefile
--- test1/drivers/misc/cb710/Makefile	2009-06-04 12:20:29.000000000 +0200
+++ test2/drivers/misc/cb710/Makefile	2009-06-04 10:53:34.000000000 +0200
@@ -2,3 +2,5 @@ obj-$(CONFIG_CB710_CORE)	+= cb710.o
 
 cb710-y				:= core.o sgbuf2.o
 cb710-$(CONFIG_CB710_DEBUG)	+= debug.o
+
+ccflags-$(CONFIG_CB710_DEBUG)	+= -DDEBUG
diff -urpN test1/drivers/mmc/host/Makefile test2/drivers/mmc/host/Makefile
--- test1/drivers/mmc/host/Makefile	2009-06-04 12:20:29.000000000 +0200
+++ test2/drivers/mmc/host/Makefile	2009-06-04 10:53:34.000000000 +0200
@@ -29,3 +29,7 @@ obj-$(CONFIG_MMC_SDRICOH_CS)	+= sdricoh_
 obj-$(CONFIG_MMC_TMIO)		+= tmio_mmc.o
 obj-$(CONFIG_MMC_CB710)	+= cb710-mmc.o
 
+ifeq ($(CONFIG_CB710_DEBUG),y)
+CFLAGS-cb710-mmc		+= -DDEBUG
+endif
+
diff -urpN test1/include/linux/cb710.h test2/include/linux/cb710.h
--- test1/include/linux/cb710.h	2009-06-04 12:20:29.000000000 +0200
+++ test2/include/linux/cb710.h	2009-06-04 10:53:34.000000000 +0200
@@ -10,13 +10,6 @@
 #ifndef LINUX_CB710_DRIVER_H
 #define LINUX_CB710_DRIVER_H
 
-#ifdef CONFIG_CB710_DEBUG
-#define DEBUG
-#endif
-
-/* verify assumptions on platform_device framework */
-#define CONFIG_CB710_DEBUG_ASSUMPTIONS
-
 #include <linux/io.h>
 #include <linux/interrupt.h>
 #include <linux/spinlock.h>

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

* Re: [PATCH 2.6.29.4] Driver for CB710/720 memory card reader (MMC part) - v5 fixed
  2009-06-04 10:24                                 ` Michał Mirosław
@ 2009-06-05  9:26                                   ` Michał Mirosław
  2009-06-13 10:39                                   ` Pierre Ossman
  1 sibling, 0 replies; 21+ messages in thread
From: Michał Mirosław @ 2009-06-05  9:26 UTC (permalink / raw)
  To: Pierre Ossman; +Cc: linux-kernel

cb710: Fix compilation warnings

Fix compilation warnings reported by Stephen Rothwell and change
the only dev_vdbg() to dev_dbg().

drivers/misc/cb710/debug.c: In function 'cb710_read_regs_8':                                                           
drivers/misc/cb710/debug.c:100: warning: suggest parentheses around operand of '!' or change '&' to '&&' or '!' to '~' 
drivers/misc/cb710/debug.c: In function 'cb710_read_regs_16':                                                          
drivers/misc/cb710/debug.c:101: warning: suggest parentheses around operand of '!' or change '&' to '&&' or '!' to '~' 
drivers/misc/cb710/debug.c: In function 'cb710_read_regs_32':                                                          
drivers/misc/cb710/debug.c:102: warning: suggest parentheses around operand of '!' or change '&' to '&&' or '!' to '~' 
drivers/mmc/host/cb710-mmc.c: In function 'cb710_mmc_set_transfer_size':                                               
drivers/mmc/host/cb710-mmc.c:222: warning: format '%d' expects type 'int', but argument 4 has type 'size_t'            
drivers/mmc/host/cb710-mmc.c:222: warning: format '%d' expects type 'int', but argument 6 has type 'size_t'            

Signed-off-by: Michał Mirosław <mirq-linux@rere.qmqm.pl>

diff -urpN test2/drivers/misc/cb710/debug.c test3/drivers/misc/cb710/debug.c
--- test2/drivers/misc/cb710/debug.c	2009-06-04 10:53:34.000000000 +0200
+++ test3/drivers/misc/cb710/debug.c	2009-06-05 11:22:38.000000000 +0200
@@ -37,7 +37,7 @@ static void cb710_read_regs_##t(void __i
 	unsigned i, j;							\
 									\
 	for (i = 0; i < ARRAY_SIZE(allow); ++i, reg += 16/(t/8)) {	\
-		if (!select & (1 << i))					\
+		if (!(select & (1 << i)))				\
 			continue;					\
 									\
 		for (j = 0; j < 0x10/(t/8); ++j) {			\
diff -urpN test2/drivers/mmc/host/cb710-mmc.c test3/drivers/mmc/host/cb710-mmc.c
--- test2/drivers/mmc/host/cb710-mmc.c	2009-06-04 10:53:34.000000000 +0200
+++ test3/drivers/mmc/host/cb710-mmc.c	2009-06-05 11:22:39.000000000 +0200
@@ -219,7 +219,7 @@ static void cb710_mmc_set_transfer_size(
 	cb710_write_port_32(slot, CB710_MMC_TRANSFER_SIZE_PORT,
 		((count - 1) << 16)|(blocksize - 1));
 
-	dev_vdbg(cb710_slot_dev(slot), "set up for %d block%s of %d bytes\n",
+	dev_dbg(cb710_slot_dev(slot), "set up for %Zu block%s of %Zu bytes\n",
 		count, count == 1 ? "" : "s", blocksize);
 }
 

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

* Re: [PATCH 2.6.29.4] Driver for CB710/720 memory card reader (MMC part) - v5 fixed
  2009-06-04 10:24                                 ` Michał Mirosław
  2009-06-05  9:26                                   ` Michał Mirosław
@ 2009-06-13 10:39                                   ` Pierre Ossman
  1 sibling, 0 replies; 21+ messages in thread
From: Pierre Ossman @ 2009-06-13 10:39 UTC (permalink / raw)
  To: Michał Mirosław; +Cc: linux-kernel

[-- Attachment #1: Type: text/plain, Size: 678 bytes --]

On Thu, 4 Jun 2009 12:24:55 +0200
Michał Mirosław <mirq-linux@rere.qmqm.pl> wrote:

> On Wed, May 27, 2009 at 10:13:42PM +0200, Pierre Ossman wrote:
> > On Fri, 22 May 2009 20:33:59 +0200
> > Michał Mirosław <mirq-linux@rere.qmqm.pl> wrote:
> [cut: CB710 driver patch description]
> > Queued.
> 
> One more fixup for the driver following.
> 

I had already done some of this, but I've merged the rest of the
changes you had here.

Rgds
-- 
     -- Pierre Ossman

  WARNING: This correspondence is being monitored by the
  Swedish government. Make sure your server uses encryption
  for SMTP traffic and consider using PGP for end-to-end
  encryption.

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 198 bytes --]

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

end of thread, other threads:[~2009-06-13 10:39 UTC | newest]

Thread overview: 21+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2008-09-07 21:52 MMC host driver requirements Michał Mirosław
2008-09-09  7:18 ` Pierre Ossman
2008-09-09  9:06   ` Michał Mirosław
2008-09-11 20:13     ` Driver for CB710/720 memory card reader (MMC part) Michał Mirosław
2008-09-12 23:43       ` RFC: " Michał Mirosław
2008-09-20 11:00         ` Pierre Ossman
2008-09-25  6:29           ` RFC: Driver for CB710/720 memory card reader (MMC part) - v2 Michał Mirosław
2008-10-04 19:51             ` Pierre Ossman
2008-10-29 14:11               ` RFC: Driver for CB710/720 memory card reader (MMC part) - v3 Michał Mirosław
2008-11-14 21:06                 ` Pierre Ossman
2009-02-01 18:54                   ` Michał Mirosław
2009-02-21 12:46                     ` Pierre Ossman
2009-05-08 15:16                       ` [PATCH/RFC 2.6.29.2] Driver for CB710/720 memory card reader (MMC part) - v4 Michał Mirosław
2009-05-22 11:27                         ` Pierre Ossman
2009-05-22 17:55                           ` [PATCH 2.6.29.3] Driver for CB710/720 memory card reader (MMC part) - v5 Michał Mirosław
2009-05-22 18:33                             ` [PATCH 2.6.29.4] Driver for CB710/720 memory card reader (MMC part) - v5 fixed Michał Mirosław
2009-05-27 20:13                               ` Pierre Ossman
2009-06-04 10:24                                 ` Michał Mirosław
2009-06-05  9:26                                   ` Michał Mirosław
2009-06-13 10:39                                   ` Pierre Ossman
2009-05-22 19:04                           ` [PATCH/RFC 2.6.29.2] Driver for CB710/720 memory card reader (MMC part) - v4 Bartlomiej Zolnierkiewicz

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).