diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2009-06-14 13:46:57 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2009-06-14 13:46:57 -0700 |
commit | 2625b10d8c37656cf410a464ed95942b3abbd1f6 (patch) | |
tree | f02fc44aaed07dceed2566b3fdf4dc64b786cb89 /drivers/mmc | |
parent | 489f7ab6c18cdd64a2d444e056d60a0e722f4ad7 (diff) | |
parent | 7f72134c32eb64c77d1fb35123ba8bf815bf797c (diff) |
Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/drzeus/mmc
* 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/drzeus/mmc: (25 commits)
atmel-mci: add MCI2 register definitions
atmel-mci: Integrate AT91 specific definition in header file
tmio_mmc: allow compilation for ASIC3
mmc_block: do not DMA to stack
sdhci: Print ADMA status and pointer on debug
tmio_mmc: fix clock setup
tmio_mmc: map SD control registers after enabling the MFD cell
tmio_mmc: correct probe return value for num_resources != 3
tmio_mmc: don't use set_irq_type
tmio_mmc: add bus_shift support
MFD,mmc: tmio_mmc: make HCLK configurable
mmc_spi: don't use EINVAL for possible transmission errors
cb710: more cleanup for the DEBUG case.
sdhci: platform driver for SDHCI
mxcmmc: remove frequency workaround
cb710: handle DEBUG define in Makefile
cb710: add missing parenthesis
cb710: fix printk format string
mmc: Driver for CB710/720 memory card reader (MMC part)
pxamci: add regulator support.
...
Diffstat (limited to 'drivers/mmc')
-rw-r--r-- | drivers/mmc/card/block.c | 16 | ||||
-rw-r--r-- | drivers/mmc/core/core.c | 107 | ||||
-rw-r--r-- | drivers/mmc/host/Kconfig | 28 | ||||
-rw-r--r-- | drivers/mmc/host/Makefile | 5 | ||||
-rw-r--r-- | drivers/mmc/host/atmel-mci-regs.h | 33 | ||||
-rw-r--r-- | drivers/mmc/host/cb710-mmc.c | 804 | ||||
-rw-r--r-- | drivers/mmc/host/cb710-mmc.h | 104 | ||||
-rw-r--r-- | drivers/mmc/host/mmc_spi.c | 23 | ||||
-rw-r--r-- | drivers/mmc/host/mxcmmc.c | 2 | ||||
-rw-r--r-- | drivers/mmc/host/omap.c | 3 | ||||
-rw-r--r-- | drivers/mmc/host/pxamci.c | 46 | ||||
-rw-r--r-- | drivers/mmc/host/sdhci-pltfm.c | 168 | ||||
-rw-r--r-- | drivers/mmc/host/sdhci.c | 58 | ||||
-rw-r--r-- | drivers/mmc/host/sdhci.h | 2 | ||||
-rw-r--r-- | drivers/mmc/host/tmio_mmc.c | 180 | ||||
-rw-r--r-- | drivers/mmc/host/tmio_mmc.h | 77 |
16 files changed, 1453 insertions, 203 deletions
diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 98ffc41eaf2..adc205c49fb 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -147,7 +147,8 @@ struct mmc_blk_request { static u32 mmc_sd_num_wr_blocks(struct mmc_card *card) { int err; - __be32 blocks; + u32 result; + __be32 *blocks; struct mmc_request mrq; struct mmc_command cmd; @@ -199,14 +200,21 @@ static u32 mmc_sd_num_wr_blocks(struct mmc_card *card) mrq.cmd = &cmd; mrq.data = &data; - sg_init_one(&sg, &blocks, 4); + blocks = kmalloc(4, GFP_KERNEL); + if (!blocks) + return (u32)-1; + + sg_init_one(&sg, blocks, 4); mmc_wait_for_req(card->host, &mrq); + result = ntohl(*blocks); + kfree(blocks); + if (cmd.error || data.error) - return (u32)-1; + result = (u32)-1; - return ntohl(blocks); + return result; } static u32 get_card_status(struct mmc_card *card, struct request *req) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 26491173275..d84c880fac8 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -708,7 +708,13 @@ static void mmc_power_up(struct mmc_host *host) */ mmc_delay(10); - host->ios.clock = host->f_min; + if (host->f_min > 400000) { + pr_warning("%s: Minimum clock frequency too high for " + "identification mode\n", mmc_hostname(host)); + host->ios.clock = host->f_min; + } else + host->ios.clock = 400000; + host->ios.power_mode = MMC_POWER_ON; mmc_set_ios(host); @@ -855,61 +861,72 @@ void mmc_rescan(struct work_struct *work) mmc_bus_get(host); - if (host->bus_ops == NULL) { - /* - * Only we can add a new handler, so it's safe to - * release the lock here. - */ + /* if there is a card registered, check whether it is still present */ + if ((host->bus_ops != NULL) && host->bus_ops->detect && !host->bus_dead) + host->bus_ops->detect(host); + + mmc_bus_put(host); + + + mmc_bus_get(host); + + /* if there still is a card present, stop here */ + if (host->bus_ops != NULL) { mmc_bus_put(host); + goto out; + } - if (host->ops->get_cd && host->ops->get_cd(host) == 0) - goto out; + /* detect a newly inserted card */ - mmc_claim_host(host); + /* + * Only we can add a new handler, so it's safe to + * release the lock here. + */ + mmc_bus_put(host); - mmc_power_up(host); - mmc_go_idle(host); + if (host->ops->get_cd && host->ops->get_cd(host) == 0) + goto out; - mmc_send_if_cond(host, host->ocr_avail); + mmc_claim_host(host); - /* - * First we search for SDIO... - */ - err = mmc_send_io_op_cond(host, 0, &ocr); - if (!err) { - if (mmc_attach_sdio(host, ocr)) - mmc_power_off(host); - goto out; - } + mmc_power_up(host); + mmc_go_idle(host); - /* - * ...then normal SD... - */ - err = mmc_send_app_op_cond(host, 0, &ocr); - if (!err) { - if (mmc_attach_sd(host, ocr)) - mmc_power_off(host); - goto out; - } + mmc_send_if_cond(host, host->ocr_avail); - /* - * ...and finally MMC. - */ - err = mmc_send_op_cond(host, 0, &ocr); - if (!err) { - if (mmc_attach_mmc(host, ocr)) - mmc_power_off(host); - goto out; - } + /* + * First we search for SDIO... + */ + err = mmc_send_io_op_cond(host, 0, &ocr); + if (!err) { + if (mmc_attach_sdio(host, ocr)) + mmc_power_off(host); + goto out; + } - mmc_release_host(host); - mmc_power_off(host); - } else { - if (host->bus_ops->detect && !host->bus_dead) - host->bus_ops->detect(host); + /* + * ...then normal SD... + */ + err = mmc_send_app_op_cond(host, 0, &ocr); + if (!err) { + if (mmc_attach_sd(host, ocr)) + mmc_power_off(host); + goto out; + } - mmc_bus_put(host); + /* + * ...and finally MMC. + */ + err = mmc_send_op_cond(host, 0, &ocr); + if (!err) { + if (mmc_attach_mmc(host, ocr)) + mmc_power_off(host); + goto out; } + + mmc_release_host(host); + mmc_power_off(host); + out: if (host->caps & MMC_CAP_NEEDS_POLL) mmc_schedule_delayed_work(&host->detect, HZ); diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index 3eb87bda14f..40111a6d8d5 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -83,6 +83,17 @@ config MMC_SDHCI_OF If unsure, say N. +config MMC_SDHCI_PLTFM + tristate "SDHCI support on the platform specific bus" + depends on MMC_SDHCI + help + This selects the platform specific bus support for Secure Digital Host + Controller Interface. + + If you have a controller with this interface, say Y or M here. + + If unsure, say N. + config MMC_OMAP tristate "TI OMAP Multimedia Card Interface support" depends on ARCH_OMAP @@ -237,7 +248,20 @@ config MMC_SDRICOH_CS config MMC_TMIO tristate "Toshiba Mobile IO Controller (TMIO) MMC/SD function support" - depends on MFD_TMIO + depends on MFD_TMIO || MFD_ASIC3 help This provides support for the SD/MMC cell found in TC6393XB, - T7L66XB and also ipaq ASIC3 + T7L66XB and also HTC 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 --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index 970a997620e..79da397c5fe 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -14,6 +14,7 @@ obj-$(CONFIG_MMC_SDHCI) += sdhci.o obj-$(CONFIG_MMC_SDHCI_PCI) += sdhci-pci.o obj-$(CONFIG_MMC_RICOH_MMC) += ricoh_mmc.o obj-$(CONFIG_MMC_SDHCI_OF) += sdhci-of.o +obj-$(CONFIG_MMC_SDHCI_PLTFM) += sdhci-pltfm.o obj-$(CONFIG_MMC_WBSD) += wbsd.o obj-$(CONFIG_MMC_AU1X) += au1xmmc.o obj-$(CONFIG_MMC_OMAP) += omap.o @@ -29,4 +30,8 @@ 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 +ifeq ($(CONFIG_CB710_DEBUG),y) + CFLAGS-cb710-mmc += -DDEBUG +endif diff --git a/drivers/mmc/host/atmel-mci-regs.h b/drivers/mmc/host/atmel-mci-regs.h index b58364ed6bb..fc8a0fe7c5c 100644 --- a/drivers/mmc/host/atmel-mci-regs.h +++ b/drivers/mmc/host/atmel-mci-regs.h @@ -7,6 +7,12 @@ * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ + +/* + * Superset of MCI IP registers integrated in Atmel AVR32 and AT91 Processors + * Registers and bitfields marked with [2] are only available in MCI2 + */ + #ifndef __DRIVERS_MMC_ATMEL_MCI_H__ #define __DRIVERS_MMC_ATMEL_MCI_H__ @@ -14,11 +20,17 @@ #define MCI_CR 0x0000 /* Control */ # define MCI_CR_MCIEN ( 1 << 0) /* MCI Enable */ # define MCI_CR_MCIDIS ( 1 << 1) /* MCI Disable */ +# define MCI_CR_PWSEN ( 1 << 2) /* Power Save Enable */ +# define MCI_CR_PWSDIS ( 1 << 3) /* Power Save Disable */ # define MCI_CR_SWRST ( 1 << 7) /* Software Reset */ #define MCI_MR 0x0004 /* Mode */ # define MCI_MR_CLKDIV(x) ((x) << 0) /* Clock Divider */ +# define MCI_MR_PWSDIV(x) ((x) << 8) /* Power Saving Divider */ # define MCI_MR_RDPROOF ( 1 << 11) /* Read Proof */ # define MCI_MR_WRPROOF ( 1 << 12) /* Write Proof */ +# define MCI_MR_PDCFBYTE ( 1 << 13) /* Force Byte Transfer */ +# define MCI_MR_PDCPADV ( 1 << 14) /* Padding Value */ +# define MCI_MR_PDCMODE ( 1 << 15) /* PDC-oriented Mode */ #define MCI_DTOR 0x0008 /* Data Timeout */ # define MCI_DTOCYC(x) ((x) << 0) /* Data Timeout Cycles */ # define MCI_DTOMUL(x) ((x) << 4) /* Data Timeout Multiplier */ @@ -28,6 +40,7 @@ # define MCI_SDCSEL_MASK ( 3 << 0) # define MCI_SDCBUS_1BIT ( 0 << 6) /* 1-bit data bus */ # define MCI_SDCBUS_4BIT ( 2 << 6) /* 4-bit data bus */ +# define MCI_SDCBUS_8BIT ( 3 << 6) /* 8-bit data bus[2] */ # define MCI_SDCBUS_MASK ( 3 << 6) #define MCI_ARGR 0x0010 /* Command Argument */ #define MCI_CMDR 0x0014 /* Command */ @@ -56,6 +69,9 @@ #define MCI_BLKR 0x0018 /* Block */ # define MCI_BCNT(x) ((x) << 0) /* Data Block Count */ # define MCI_BLKLEN(x) ((x) << 16) /* Data Block Length */ +#define MCI_CSTOR 0x001c /* Completion Signal Timeout[2] */ +# define MCI_CSTOCYC(x) ((x) << 0) /* CST cycles */ +# define MCI_CSTOMUL(x) ((x) << 4) /* CST multiplier */ #define MCI_RSPR 0x0020 /* Response 0 */ #define MCI_RSPR1 0x0024 /* Response 1 */ #define MCI_RSPR2 0x0028 /* Response 2 */ @@ -83,7 +99,24 @@ # define MCI_DTOE ( 1 << 22) /* Data Time-Out Error */ # define MCI_OVRE ( 1 << 30) /* RX Overrun Error */ # define MCI_UNRE ( 1 << 31) /* TX Underrun Error */ +#define MCI_DMA 0x0050 /* DMA Configuration[2] */ +# define MCI_DMA_OFFSET(x) ((x) << 0) /* DMA Write Buffer Offset */ +# define MCI_DMA_CHKSIZE(x) ((x) << 4) /* DMA Channel Read and Write Chunk Size */ +# define MCI_DMAEN ( 1 << 8) /* DMA Hardware Handshaking Enable */ +#define MCI_CFG 0x0054 /* Configuration[2] */ +# define MCI_CFG_FIFOMODE_1DATA ( 1 << 0) /* MCI Internal FIFO control mode */ +# define MCI_CFG_FERRCTRL_COR ( 1 << 4) /* Flow Error flag reset control mode */ +# define MCI_CFG_HSMODE ( 1 << 8) /* High Speed Mode */ +# define MCI_CFG_LSYNC ( 1 << 12) /* Synchronize on the last block */ +#define MCI_WPMR 0x00e4 /* Write Protection Mode[2] */ +# define MCI_WP_EN ( 1 << 0) /* WP Enable */ +# define MCI_WP_KEY (0x4d4349 << 8) /* WP Key */ +#define MCI_WPSR 0x00e8 /* Write Protection Status[2] */ +# define MCI_GET_WP_VS(x) ((x) & 0x0f) +# define MCI_GET_WP_VSRC(x) (((x) >> 8) & 0xffff) +#define MCI_FIFO_APERTURE 0x0200 /* FIFO Aperture[2] */ +/* This is not including the FIFO Aperture on MCI2 */ #define MCI_REGS_SIZE 0x100 /* Register access macros */ diff --git a/drivers/mmc/host/cb710-mmc.c b/drivers/mmc/host/cb710-mmc.c new file mode 100644 index 00000000000..11efefb1af5 --- /dev/null +++ b/drivers/mmc/host/cb710-mmc.c @@ -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 %zu block%s of %zu 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); |