bk://bk.arm.linux.org.uk/linux-2.6-mmc rmk@flint.arm.linux.org.uk|ChangeSet|20040819211027|28392 rmk # This is a BitKeeper generated diff -Nru style patch. # # ChangeSet # 2004/08/19 22:10:27+01:00 rmk@flint.arm.linux.org.uk # [MMC] MMCI optimisations. # # Optimise register accesses; dereferencing "host" each time introduces # extra loads. # # No need to check MCI_TXFIFOEMPTY when checking if we have a data IRQ. # # Since we support clock bypass mode, the maximum clock rate is the # MCLK rate itself. # # drivers/mmc/mmci.c # 2004/08/19 22:08:32+01:00 rmk@flint.arm.linux.org.uk +33 -20 # Optimise register accesses; dereferencing "host" each time introduces # extra loads. # # No need to check MCI_TXFIFOEMPTY when checking if we have a data IRQ. # # Maximum clock rate is the MCLK rate. # # ChangeSet # 2004/08/19 21:58:33+01:00 rmk@flint.arm.linux.org.uk # [MMC] Cleanup: Make MMCI debug macro take host, format and arguments. # # drivers/mmc/mmci.c # 2004/08/19 21:56:52+01:00 rmk@flint.arm.linux.org.uk +11 -12 # DBG() takes host, format and arguments. # # ChangeSet # 2004/08/19 21:51:52+01:00 rmk@flint.arm.linux.org.uk # [MMC] Avoid potential oops in MMCI. # # Avoid calling mmci_data_irq if we do not have a data phase. # # drivers/mmc/mmci.c # 2004/08/19 21:49:50+01:00 rmk@flint.arm.linux.org.uk +1 -1 # Don't call mmci_data_irq if we have no data phase. # # ChangeSet # 2004/08/19 21:44:28+01:00 rmk@flint.arm.linux.org.uk # [MMC] Fix race condition in MMCI write-path data channel. # # There seems to be a hardware race condition in the data channel where # we could end up receiving IRQs for more data than we have available. # Fix this by disabling IRQs when we run out of data; if the device # really does want more data, we'll underrun and flag an error # upstream. # # drivers/mmc/mmci.c # 2004/08/19 21:42:41+01:00 rmk@flint.arm.linux.org.uk +40 -24 # Fix race condition in write-path data channel. # # ChangeSet # 2004/08/19 21:28:08+01:00 rmk@flint.arm.linux.org.uk # [MMC] Update PXAMCI for later kernels. # # Remove obsolete platform_device_resource/irq functions. The driver # model now has equivalents, so these can be removed. # # Use pxa_set_cken() to enable/disable the clock to the MMC interface. # # drivers/mmc/pxamci.c # 2004/08/19 21:26:22+01:00 rmk@flint.arm.linux.org.uk +5 -26 # Remove obsolete platform_device_resource/irq functions. The driver # model now has equivalents, so these can be removed. # # Use pxa_set_cken() to enable/disable the clock to the MMC interface. # # ChangeSet # 2004/08/19 02:03:19-07:00 akpm@bix.(none) # Merge bk://bk.arm.linux.org.uk/linux-2.6-mmc # into bix.(none):/usr/src/bk-mmc # # drivers/Makefile # 2004/08/19 02:03:15-07:00 akpm@bix.(none) +0 -0 # Auto merged # # arch/arm/Kconfig # 2004/08/19 02:03:15-07:00 akpm@bix.(none) +0 -0 # Auto merged # # ChangeSet # 2004/07/08 10:34:43+01:00 rmk@flint.arm.linux.org.uk # [MMC] Fix end of request handling. # # We were mixing end_request with end_that_request_chunk, which # is apparantly bad news. Also, the handycapped pxamci driver # was telling us that it had transferred all data successfully # on error, which is obviously wrong. # # drivers/mmc/pxamci.c # 2004/07/08 10:28:32+01:00 rmk@flint.arm.linux.org.uk +4 -1 # Indicate no data transferred on error. # # drivers/mmc/mmc_queue.c # 2004/07/08 10:28:11+01:00 rmk@flint.arm.linux.org.uk +2 -6 # Fix end of request handling - mixing end_request with # end_that_request_chunk is apparantly bad. # # drivers/mmc/mmc_block.c # 2004/07/08 10:27:31+01:00 rmk@flint.arm.linux.org.uk +39 -21 # Fix end of request handling - mixing end_request with # end_that_request_chunk is apparantly bad. # # ChangeSet # 2004/07/08 10:18:22+01:00 rmk@flint.arm.linux.org.uk # [MMC] Use a consistent naming to refer to mmc_request, # mmc_blk_request and request structures to avoid confusion. # # include/linux/mmc/mmc.h # 2004/07/08 10:10:04+01:00 rmk@flint.arm.linux.org.uk +3 -3 # Use a consistent naming to refer to mmc_request, mmc_blk_request # and request structures. # # drivers/mmc/pxamci.c # 2004/07/08 10:10:04+01:00 rmk@flint.arm.linux.org.uk +18 -17 # Use a consistent naming to refer to mmc_request, mmc_blk_request # and request structures. # # drivers/mmc/mmci.h # 2004/07/08 10:10:03+01:00 rmk@flint.arm.linux.org.uk +1 -1 # Use a consistent naming to refer to mmc_request, mmc_blk_request # and request structures. # # drivers/mmc/mmci.c # 2004/07/08 10:10:02+01:00 rmk@flint.arm.linux.org.uk +14 -14 # Use a consistent naming to refer to mmc_request, mmc_blk_request # and request structures. # # drivers/mmc/mmc.c # 2004/07/08 10:10:01+01:00 rmk@flint.arm.linux.org.uk +31 -32 # Use a consistent naming to refer to mmc_request, mmc_blk_request # and request structures. # # drivers/mmc/mmc_block.c # 2004/07/08 10:09:54+01:00 rmk@flint.arm.linux.org.uk +32 -32 # Use a consistent naming to refer to mmc_request, mmc_blk_request # and request structures. # # ChangeSet # 2004/07/06 18:34:38+01:00 rmk@flint.arm.linux.org.uk # [MMC] Fix PXA MCI driver name. # # drivers/mmc/pxamci.c # 2004/07/06 18:31:33+01:00 rmk@flint.arm.linux.org.uk +1 -1 # PXA MCI driver name is now pxa2xx-mci. # # ChangeSet # 2004/06/22 21:06:23+01:00 rmk@flint.arm.linux.org.uk # [MMC] Fix some review points from Jens Axboe # # Remove "suspended" flag, queue plugging/unplugging and associated # checks, remove check for oversized requests. # # drivers/mmc/mmc_queue.c # 2004/06/22 21:03:47+01:00 rmk@flint.arm.linux.org.uk +2 -2 # No need to check if queue is plugged. # Check whether dma_mask is zero. # # drivers/mmc/mmc_block.c # 2004/06/22 21:03:47+01:00 rmk@flint.arm.linux.org.uk +3 -22 # "suspended" flag is useless; removed. # Remove queue plugging/unplugging - not required. # Remove check for excessive requests - this check is done by the # higher layers. # # ChangeSet # 2004/06/22 20:34:12+01:00 rmk@flint.arm.linux.org.uk # [MMC] MMCI updates. # # Calculate data timeout correctly. # Obtain MCLK rate from clock subsystem. # Limit max clock rate properly. # Use clock bypass mode for fast data rates. # # drivers/mmc/mmci.h # 2004/06/22 20:32:03+01:00 rmk@flint.arm.linux.org.uk +6 -7 # MMCI updates. # - Calculate data timeout correctly. # - Obtain MCLK rate from clock subsystem. # - Limit max clock rate properly. # - Use clock bypass mode for fast data rates. # # drivers/mmc/mmci.c # 2004/06/22 20:32:02+01:00 rmk@flint.arm.linux.org.uk +68 -29 # MMCI updates. # - Calculate data timeout correctly. # - Obtain MCLK rate from clock subsystem. # - Limit max clock rate properly. # - Use clock bypass mode for fast data rates. # # ChangeSet # 2004/06/01 21:47:40+01:00 rmk@flint.arm.linux.org.uk # [MMC] Fix PXA MMC interface issues. # # - Wait for STAT_CLK_EN to clear rather than waiting for the CLK_IS_OFF # interrupt when stopping the MMC clock. # - Always return the number of data blocks transferred no matter what. # - Set the device driver data correctly. # # drivers/mmc/pxamci.c # 2004/06/01 21:45:43+01:00 rmk@flint.arm.linux.org.uk +22 -17 # Fix PXA interface issues: # - Wait for STAT_CLK_EN to clear rather than waiting for the CLK_IS_OFF # interrupt when stopping the MMC clock. # - Always return the number of data blocks transferred no matter what. # - Set the device driver data correctly. # # ChangeSet # 2004/05/06 15:07:13+01:00 rmk@flint.arm.linux.org.uk # [MMC] Add PXA MMC interface support. # # This patch adds support for the Intel PXA MCI interface. # # drivers/mmc/pxamci.h # 2004/05/06 15:04:47+01:00 rmk@flint.arm.linux.org.uk +94 -0 # # drivers/mmc/pxamci.h # 2004/05/06 15:04:47+01:00 rmk@flint.arm.linux.org.uk +0 -0 # BitKeeper file /usr/src/bk/linux-2.6-mmc/drivers/mmc/pxamci.h # # drivers/mmc/pxamci.c # 2004/05/06 15:04:40+01:00 rmk@flint.arm.linux.org.uk +590 -0 # # drivers/mmc/pxamci.c # 2004/05/06 15:04:40+01:00 rmk@flint.arm.linux.org.uk +0 -0 # BitKeeper file /usr/src/bk/linux-2.6-mmc/drivers/mmc/pxamci.c # # drivers/mmc/Makefile # 2004/05/06 15:04:40+01:00 rmk@flint.arm.linux.org.uk +1 -0 # Add PXA MMC Makefile entry. # # drivers/mmc/Kconfig # 2004/05/06 15:04:39+01:00 rmk@flint.arm.linux.org.uk +10 -0 # Add PXA MMC Kconfig entry. # # ChangeSet # 2004/05/06 15:00:42+01:00 rmk@flint.arm.linux.org.uk # [MMC] Add ARM MMCI Primecell driver. # # This patch adds initial support for the ARM MMCI Primecell - one of # the drivers for a MMC interface. # # drivers/mmc/mmci.h # 2004/05/06 14:58:22+01:00 rmk@flint.arm.linux.org.uk +149 -0 # # drivers/mmc/mmci.h # 2004/05/06 14:58:22+01:00 rmk@flint.arm.linux.org.uk +0 -0 # BitKeeper file /usr/src/bk/linux-2.6-mmc/drivers/mmc/mmci.h # # drivers/mmc/mmci.c # 2004/05/06 14:58:15+01:00 rmk@flint.arm.linux.org.uk +523 -0 # # include/asm-arm/mach/mmc.h # 2004/05/06 14:58:15+01:00 rmk@flint.arm.linux.org.uk +16 -0 # Add MMC platform data. # # drivers/mmc/mmci.c # 2004/05/06 14:58:15+01:00 rmk@flint.arm.linux.org.uk +0 -0 # BitKeeper file /usr/src/bk/linux-2.6-mmc/drivers/mmc/mmci.c # # drivers/mmc/Makefile # 2004/05/06 14:58:15+01:00 rmk@flint.arm.linux.org.uk +1 -0 # Add ARM MMCI makefile entry. # # drivers/mmc/Kconfig # 2004/05/06 14:58:15+01:00 rmk@flint.arm.linux.org.uk +10 -0 # Add ARM MMCI PrimeCell configuration. # # ChangeSet # 2004/05/06 14:53:21+01:00 rmk@flint.arm.linux.org.uk # [MMC] Add Kconfig/Makefile changes for MMC support. # # Since this is currently ARM-only, I've dropped the drivers/Kconfig # change for the time being. # # drivers/Makefile # 2004/05/06 14:51:21+01:00 rmk@flint.arm.linux.org.uk +1 -0 # Add mmc/ directory to Makefile # # arch/arm/Kconfig # 2004/05/06 14:51:21+01:00 rmk@flint.arm.linux.org.uk +2 -0 # Add sourcing of drivers/mmc/Kconfig # # ChangeSet # 2004/05/06 14:48:15+01:00 rmk@flint.arm.linux.org.uk # [MMC] Add MMC core support # # This patch adds core support to the Linux kernel for driving MMC # interfaces found on embedded devices, such as found in the Intel # PXA and ARM MMCI primecell. This patch does _not_ add support # for SD or SDIO cards. # # It is vaguely based upon the handhelds.org MMC code, but the bulk # of the core has been rewritten from scratch. # # include/linux/mmc/protocol.h # 2004/05/06 14:46:03+01:00 rmk@flint.arm.linux.org.uk +203 -0 # # include/linux/mmc/protocol.h # 2004/05/06 14:46:03+01:00 rmk@flint.arm.linux.org.uk +0 -0 # BitKeeper file /usr/src/bk/linux-2.6-mmc/include/linux/mmc/protocol.h # # include/linux/mmc/mmc.h # 2004/05/06 14:45:56+01:00 rmk@flint.arm.linux.org.uk +88 -0 # # include/linux/mmc/mmc.h # 2004/05/06 14:45:56+01:00 rmk@flint.arm.linux.org.uk +0 -0 # BitKeeper file /usr/src/bk/linux-2.6-mmc/include/linux/mmc/mmc.h # # include/linux/mmc/host.h # 2004/05/06 14:45:49+01:00 rmk@flint.arm.linux.org.uk +102 -0 # # include/linux/mmc/host.h # 2004/05/06 14:45:49+01:00 rmk@flint.arm.linux.org.uk +0 -0 # BitKeeper file /usr/src/bk/linux-2.6-mmc/include/linux/mmc/host.h # # include/linux/mmc/card.h # 2004/05/06 14:45:36+01:00 rmk@flint.arm.linux.org.uk +83 -0 # # include/linux/mmc/card.h # 2004/05/06 14:45:36+01:00 rmk@flint.arm.linux.org.uk +0 -0 # BitKeeper file /usr/src/bk/linux-2.6-mmc/include/linux/mmc/card.h # # include/asm-arm/mach/mmc.h # 2004/05/06 14:45:28+01:00 rmk@flint.arm.linux.org.uk +0 -0 # # include/asm-arm/mach/mmc.h # 2004/05/06 14:45:28+01:00 rmk@flint.arm.linux.org.uk +0 -0 # BitKeeper file /usr/src/bk/linux-2.6-mmc/include/asm-arm/mach/mmc.h # # drivers/mmc/mmc_sysfs.c # 2004/05/06 14:45:14+01:00 rmk@flint.arm.linux.org.uk +231 -0 # # drivers/mmc/mmc_sysfs.c # 2004/05/06 14:45:14+01:00 rmk@flint.arm.linux.org.uk +0 -0 # BitKeeper file /usr/src/bk/linux-2.6-mmc/drivers/mmc/mmc_sysfs.c # # drivers/mmc/mmc_queue.h # 2004/05/06 14:44:59+01:00 rmk@flint.arm.linux.org.uk +29 -0 # # drivers/mmc/mmc_queue.h # 2004/05/06 14:44:59+01:00 rmk@flint.arm.linux.org.uk +0 -0 # BitKeeper file /usr/src/bk/linux-2.6-mmc/drivers/mmc/mmc_queue.h # # drivers/mmc/mmc_queue.c # 2004/05/06 14:44:52+01:00 rmk@flint.arm.linux.org.uk +175 -0 # # drivers/mmc/mmc_queue.c # 2004/05/06 14:44:52+01:00 rmk@flint.arm.linux.org.uk +0 -0 # BitKeeper file /usr/src/bk/linux-2.6-mmc/drivers/mmc/mmc_queue.c # # drivers/mmc/mmc_block.c # 2004/05/06 14:44:45+01:00 rmk@flint.arm.linux.org.uk +498 -0 # # drivers/mmc/mmc_block.c # 2004/05/06 14:44:45+01:00 rmk@flint.arm.linux.org.uk +0 -0 # BitKeeper file /usr/src/bk/linux-2.6-mmc/drivers/mmc/mmc_block.c # # drivers/mmc/mmc.h # 2004/05/06 14:44:38+01:00 rmk@flint.arm.linux.org.uk +16 -0 # # drivers/mmc/mmc.h # 2004/05/06 14:44:38+01:00 rmk@flint.arm.linux.org.uk +0 -0 # BitKeeper file /usr/src/bk/linux-2.6-mmc/drivers/mmc/mmc.h # # drivers/mmc/mmc.c # 2004/05/06 14:44:31+01:00 rmk@flint.arm.linux.org.uk +823 -0 # # drivers/mmc/mmc.c # 2004/05/06 14:44:31+01:00 rmk@flint.arm.linux.org.uk +0 -0 # BitKeeper file /usr/src/bk/linux-2.6-mmc/drivers/mmc/mmc.c # # drivers/mmc/Makefile # 2004/05/06 14:44:24+01:00 rmk@flint.arm.linux.org.uk +19 -0 # # drivers/mmc/Makefile # 2004/05/06 14:44:24+01:00 rmk@flint.arm.linux.org.uk +0 -0 # BitKeeper file /usr/src/bk/linux-2.6-mmc/drivers/mmc/Makefile # # drivers/mmc/Kconfig # 2004/05/06 14:44:16+01:00 rmk@flint.arm.linux.org.uk +32 -0 # # drivers/mmc/Kconfig # 2004/05/06 14:44:16+01:00 rmk@flint.arm.linux.org.uk +0 -0 # BitKeeper file /usr/src/bk/linux-2.6-mmc/drivers/mmc/Kconfig # diff -Nru a/arch/arm/Kconfig b/arch/arm/Kconfig --- a/arch/arm/Kconfig 2004-08-21 23:49:20 -07:00 +++ b/arch/arm/Kconfig 2004-08-21 23:49:20 -07:00 @@ -661,6 +661,8 @@ source "drivers/usb/Kconfig" +source "drivers/mmc/Kconfig" + menu "Kernel hacking" diff -Nru a/drivers/Makefile b/drivers/Makefile --- a/drivers/Makefile 2004-08-21 23:49:20 -07:00 +++ b/drivers/Makefile 2004-08-21 23:49:20 -07:00 @@ -50,4 +50,5 @@ obj-$(CONFIG_MCA) += mca/ obj-$(CONFIG_EISA) += eisa/ obj-$(CONFIG_CPU_FREQ) += cpufreq/ +obj-$(CONFIG_MMC) += mmc/ obj-y += firmware/ diff -Nru a/drivers/mmc/Kconfig b/drivers/mmc/Kconfig --- /dev/null Wed Dec 31 16:00:00 196900 +++ b/drivers/mmc/Kconfig 2004-08-21 23:49:20 -07:00 @@ -0,0 +1,52 @@ +# +# MMC subsystem configuration +# + +menu "MMC/SD Card support" + +config MMC + tristate "MMC support" + help + MMC is the "multi-media card" bus protocol. + + If you want MMC support, you should say Y here and also + to the specific driver for your MMC interface. + +config MMC_DEBUG + bool "MMC debugging" + depends on MMC != n + help + This is an option for use by developers; most people should + say N here. This enables MMC core and driver debugging. + +config MMC_BLOCK + tristate "MMC block device driver" + depends on MMC + default y + help + Say Y here to enable the MMC block device driver support. + This provides a block device driver, which you can use to + mount the filesystem. Almost everyone wishing MMC support + should say Y or M here. + +config MMC_ARMMMCI + tristate "ARM AMBA Multimedia Card Interface support" + depends on ARM_AMBA && MMC + help + This selects the ARM(R) AMBA(R) PrimeCell Multimedia Card + Interface (PL180 and PL181) support. If you have an ARM(R) + platform with a Multimedia Card slot, say Y or M here. + + If unsure, say N. + +config MMC_PXA + tristate "Intel PXA255 Multimedia Card Interface support" + depends on ARCH_PXA && MMC + help + This selects the Intel(R) PXA(R) Multimedia card Interface. + If you have a PXA(R) platform with a Multimedia Card slot, + say Y or M here. + + If unsure, say N. + +endmenu diff -Nru a/drivers/mmc/Makefile b/drivers/mmc/Makefile --- /dev/null Wed Dec 31 16:00:00 196900 +++ b/drivers/mmc/Makefile 2004-08-21 23:49:20 -07:00 @@ -0,0 +1,21 @@ +# +# Makefile for the kernel mmc device drivers. +# + +# +# Core +# +obj-$(CONFIG_MMC) += mmc_core.o + +# +# Media drivers +# +obj-$(CONFIG_MMC_BLOCK) += mmc_block.o + +# +# Host drivers +# +obj-$(CONFIG_MMC_ARMMMCI) += mmci.o +obj-$(CONFIG_MMC_PXA) += pxamci.o + +mmc_core-y := mmc.o mmc_queue.o mmc_sysfs.o diff -Nru a/drivers/mmc/mmc.c b/drivers/mmc/mmc.c --- /dev/null Wed Dec 31 16:00:00 196900 +++ b/drivers/mmc/mmc.c 2004-08-21 23:49:20 -07:00 @@ -0,0 +1,822 @@ +/* + * linux/drivers/mmc/mmc.c + * + * Copyright (C) 2003 Russell King, All Rights Reserved. + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "mmc.h" + +#ifdef CONFIG_MMC_DEBUG +#define DBG(x...) printk(KERN_DEBUG x) +#else +#define DBG(x...) do { } while (0) +#endif + +#define CMD_RETRIES 3 + +/* + * OCR Bit positions to 10s of Vdd mV. + */ +static const unsigned short mmc_ocr_bit_to_vdd[] = { + 150, 155, 160, 165, 170, 180, 190, 200, + 210, 220, 230, 240, 250, 260, 270, 280, + 290, 300, 310, 320, 330, 340, 350, 360 +}; + +static const unsigned int tran_exp[] = { + 10000, 100000, 1000000, 10000000, + 0, 0, 0, 0 +}; + +static const unsigned char tran_mant[] = { + 0, 10, 12, 13, 15, 20, 25, 30, + 35, 40, 45, 50, 55, 60, 70, 80, +}; + +static const unsigned int tacc_exp[] = { + 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, +}; + +static const unsigned int tacc_mant[] = { + 0, 10, 12, 13, 15, 20, 25, 30, + 35, 40, 45, 50, 55, 60, 70, 80, +}; + + +/** + * mmc_request_done - finish processing an MMC command + * @host: MMC host which completed command + * @mrq: MMC request which completed + * + * MMC drivers should call this function when they have completed + * their processing of a command. This should be called before the + * data part of the command has completed. + */ +void mmc_request_done(struct mmc_host *host, struct mmc_request *mrq) +{ + struct mmc_command *cmd = mrq->cmd; + int err = mrq->cmd->error; + DBG("MMC: req done (%02x): %d: %08x %08x %08x %08x\n", cmd->opcode, + err, cmd->resp[0], cmd->resp[1], cmd->resp[2], cmd->resp[3]); + + if (err && cmd->retries) { + cmd->retries--; + cmd->error = 0; + host->ops->request(host, mrq); + } else if (mrq->done) { + mrq->done(mrq); + } +} + +EXPORT_SYMBOL(mmc_request_done); + +/** + * mmc_start_request - start a command on a host + * @host: MMC host to start command on + * @mrq: MMC request to start + * + * Queue a command on the specified host. We expect the + * caller to be holding the host lock with interrupts disabled. + */ +void +mmc_start_request(struct mmc_host *host, struct mmc_request *mrq) +{ + DBG("MMC: starting cmd %02x arg %08x flags %08x\n", + mrq->cmd->opcode, mrq->cmd->arg, mrq->cmd->flags); + + WARN_ON(host->card_busy == NULL); + + mrq->cmd->error = 0; + mrq->cmd->mrq = mrq; + if (mrq->data) { + mrq->cmd->data = mrq->data; + mrq->data->error = 0; + mrq->data->mrq = mrq; + if (mrq->stop) { + mrq->data->stop = mrq->stop; + mrq->stop->error = 0; + mrq->stop->mrq = mrq; + } + } + host->ops->request(host, mrq); +} + +EXPORT_SYMBOL(mmc_start_request); + +static void mmc_wait_done(struct mmc_request *mrq) +{ + complete(mrq->done_data); +} + +int mmc_wait_for_req(struct mmc_host *host, struct mmc_request *mrq) +{ + DECLARE_COMPLETION(complete); + + mrq->done_data = &complete; + mrq->done = mmc_wait_done; + + mmc_start_request(host, mrq); + + wait_for_completion(&complete); + + return 0; +} + +EXPORT_SYMBOL(mmc_wait_for_req); + +/** + * mmc_wait_for_cmd - start a command and wait for completion + * @host: MMC host to start command + * @cmd: MMC command to start + * @retries: maximum number of retries + * + * Start a new MMC command for a host, and wait for the command + * to complete. Return any error that occurred while the command + * was executing. Do not attempt to parse the response. + */ +int mmc_wait_for_cmd(struct mmc_host *host, struct mmc_command *cmd, int retries) +{ + struct mmc_request mrq; + + BUG_ON(host->card_busy == NULL); + + memset(&mrq, 0, sizeof(struct mmc_request)); + + memset(cmd->resp, 0, sizeof(cmd->resp)); + cmd->retries = retries; + + mrq.cmd = cmd; + cmd->data = NULL; + + mmc_wait_for_req(host, &mrq); + + return cmd->error; +} + +EXPORT_SYMBOL(mmc_wait_for_cmd); + + + +/** + * __mmc_claim_host - exclusively claim a host + * @host: mmc host to claim + * @card: mmc card to claim host for + * + * Claim a host for a set of operations. If a valid card + * is passed and this wasn't the last card selected, select + * the card before returning. + * + * Note: you should use mmc_card_claim_host or mmc_claim_host. + */ +int __mmc_claim_host(struct mmc_host *host, struct mmc_card *card) +{ + DECLARE_WAITQUEUE(wait, current); + unsigned long flags; + int err = 0; + + add_wait_queue(&host->wq, &wait); + spin_lock_irqsave(&host->lock, flags); + while (1) { + set_current_state(TASK_UNINTERRUPTIBLE); + if (host->card_busy == NULL) + break; + spin_unlock_irqrestore(&host->lock, flags); + schedule(); + spin_lock_irqsave(&host->lock, flags); + } + set_current_state(TASK_RUNNING); + host->card_busy = card; + spin_unlock_irqrestore(&host->lock, flags); + remove_wait_queue(&host->wq, &wait); + + if (card != (void *)-1 && host->card_selected != card) { + struct mmc_command cmd; + + host->card_selected = card; + + cmd.opcode = MMC_SELECT_CARD; + cmd.arg = card->rca << 16; + cmd.flags = MMC_RSP_SHORT | MMC_RSP_CRC; + + err = mmc_wait_for_cmd(host, &cmd, CMD_RETRIES); + } + + return err; +} + +EXPORT_SYMBOL(__mmc_claim_host); + +/** + * mmc_release_host - release a host + * @host: mmc host to release + * + * Release a MMC host, allowing others to claim the host + * for their operations. + */ +void mmc_release_host(struct mmc_host *host) +{ + unsigned long flags; + + BUG_ON(host->card_busy == NULL); + + spin_lock_irqsave(&host->lock, flags); + host->card_busy = NULL; + spin_unlock_irqrestore(&host->lock, flags); + + wake_up(&host->wq); +} + +EXPORT_SYMBOL(mmc_release_host); + +/* + * Ensure that no card is selected. + */ +static void mmc_deselect_cards(struct mmc_host *host) +{ + struct mmc_command cmd; + + if (host->card_selected) { + host->card_selected = NULL; + + cmd.opcode = MMC_SELECT_CARD; + cmd.arg = 0; + cmd.flags = MMC_RSP_NONE; + + mmc_wait_for_cmd(host, &cmd, 0); + } +} + + +static inline void mmc_delay(unsigned int ms) +{ + if (ms < HZ / 1000) { + yield(); + mdelay(ms); + } else { + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(ms * HZ / 1000); + } +} + +/* + * Mask off any voltages we don't support and select + * the lowest voltage + */ +static u32 mmc_select_voltage(struct mmc_host *host, u32 ocr) +{ + int bit; + + ocr &= host->ocr_avail; + + bit = ffs(ocr); + if (bit) { + bit -= 1; + + ocr = 3 << bit; + + host->ios.vdd = bit; + host->ops->set_ios(host, &host->ios); + } else { + ocr = 0; + } + + return ocr; +} + +static void mmc_decode_cid(struct mmc_cid *cid, u32 *resp) +{ + memset(cid, 0, sizeof(struct mmc_cid)); + + cid->manfid = resp[0] >> 8; + cid->prod_name[0] = resp[0]; + cid->prod_name[1] = resp[1] >> 24; + cid->prod_name[2] = resp[1] >> 16; + cid->prod_name[3] = resp[1] >> 8; + cid->prod_name[4] = resp[1]; + cid->prod_name[5] = resp[2] >> 24; + cid->prod_name[6] = resp[2] >> 16; + cid->prod_name[7] = '\0'; + cid->hwrev = (resp[2] >> 12) & 15; + cid->fwrev = (resp[2] >> 8) & 15; + cid->serial = (resp[2] & 255) << 16 | (resp[3] >> 16); + cid->month = (resp[3] >> 12) & 15; + cid->year = (resp[3] >> 8) & 15; +} + +static void mmc_decode_csd(struct mmc_csd *csd, u32 *resp) +{ + unsigned int e, m; + + csd->mmc_prot = (resp[0] >> 26) & 15; + m = (resp[0] >> 19) & 15; + e = (resp[0] >> 16) & 7; + csd->tacc_ns = (tacc_exp[e] * tacc_mant[m] + 9) / 10; + csd->tacc_clks = ((resp[0] >> 8) & 255) * 100; + + m = (resp[0] >> 3) & 15; + e = resp[0] & 7; + csd->max_dtr = tran_exp[e] * tran_mant[m]; + csd->cmdclass = (resp[1] >> 20) & 0xfff; + + e = (resp[2] >> 15) & 7; + m = (resp[1] << 2 | resp[2] >> 30) & 0x3fff; + csd->capacity = (1 + m) << (e + 2); + + csd->read_blkbits = (resp[1] >> 16) & 15; +} + +/* + * Locate a MMC card on this MMC host given a CID. + */ +static struct mmc_card * +mmc_find_card(struct mmc_host *host, struct mmc_cid *cid) +{ + struct mmc_card *card; + + list_for_each_entry(card, &host->cards, node) { + if (memcmp(&card->cid, cid, sizeof(struct mmc_cid)) == 0) + return card; + } + return NULL; +} + +/* + * Allocate a new MMC card, and assign a unique RCA. + */ +static struct mmc_card * +mmc_alloc_card(struct mmc_host *host, struct mmc_cid *cid, unsigned int *frca) +{ + struct mmc_card *card, *c; + unsigned int rca = *frca; + + card = kmalloc(sizeof(struct mmc_card), GFP_KERNEL); + if (!card) + return ERR_PTR(-ENOMEM); + + mmc_init_card(card, host); + memcpy(&card->cid, cid, sizeof(struct mmc_cid)); + + again: + list_for_each_entry(c, &host->cards, node) + if (c->rca == rca) { + rca++; + goto again; + } + + card->rca = rca; + + *frca = rca; + + return card; +} + +/* + * Apply power to the MMC stack. + */ +static void mmc_power_up(struct mmc_host *host) +{ + struct mmc_command cmd; + int bit = fls(host->ocr_avail) - 1; + + host->ios.vdd = bit; + host->ios.bus_mode = MMC_BUSMODE_OPENDRAIN; + host->ios.power_mode = MMC_POWER_UP; + host->ops->set_ios(host, &host->ios); + + mmc_delay(1); + + host->ios.clock = host->f_min; + host->ios.power_mode = MMC_POWER_ON; + host->ops->set_ios(host, &host->ios); + + mmc_delay(2); + + cmd.opcode = MMC_GO_IDLE_STATE; + cmd.arg = 0; + cmd.flags = MMC_RSP_NONE; + + mmc_wait_for_cmd(host, &cmd, 0); +} + +static void mmc_power_off(struct mmc_host *host) +{ + host->ios.clock = 0; + host->ios.vdd = 0; + host->ios.bus_mode = MMC_BUSMODE_OPENDRAIN; + host->ios.power_mode = MMC_POWER_OFF; + host->ops->set_ios(host, &host->ios); +} + +static int mmc_send_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr) +{ + struct mmc_command cmd; + int i, err = 0; + + cmd.opcode = MMC_SEND_OP_COND; + cmd.arg = ocr; + cmd.flags = MMC_RSP_SHORT; + + for (i = 100; i; i--) { + err = mmc_wait_for_cmd(host, &cmd, 0); + if (err != MMC_ERR_NONE) + break; + + if (cmd.resp[0] & MMC_CARD_BUSY || ocr == 0) + break; + + err = MMC_ERR_TIMEOUT; + } + + if (rocr) + *rocr = cmd.resp[0]; + + return err; +} + +/* + * Discover cards by requesting their CID. If this command + * times out, it is not an error; there are no further cards + * to be discovered. Add new cards to the list. + * + * Create a mmc_card entry for each discovered card, assigning + * it an RCA, and save the CID. + */ +static void mmc_discover_cards(struct mmc_host *host) +{ + struct mmc_card *card; + unsigned int first_rca = 1, err; + + while (1) { + struct mmc_command cmd; + struct mmc_cid cid; + + cmd.opcode = MMC_ALL_SEND_CID; + cmd.arg = 0; + cmd.flags = MMC_RSP_LONG | MMC_RSP_CRC; + + err = mmc_wait_for_cmd(host, &cmd, CMD_RETRIES); + if (err == MMC_ERR_TIMEOUT) { + err = MMC_ERR_NONE; + break; + } + if (err != MMC_ERR_NONE) { + printk(KERN_ERR "%s: error requesting CID: %d\n", + host->host_name, err); + break; + } + + mmc_decode_cid(&cid, cmd.resp); + + card = mmc_find_card(host, &cid); + if (!card) { + card = mmc_alloc_card(host, &cid, &first_rca); + if (IS_ERR(card)) { + err = PTR_ERR(card); + break; + } + list_add(&card->node, &host->cards); + } + + card->state &= ~MMC_STATE_DEAD; + + cmd.opcode = MMC_SET_RELATIVE_ADDR; + cmd.arg = card->rca << 16; + cmd.flags = MMC_RSP_SHORT | MMC_RSP_CRC; + + err = mmc_wait_for_cmd(host, &cmd, CMD_RETRIES); + if (err != MMC_ERR_NONE) + card->state |= MMC_STATE_DEAD; + } +} + +static void mmc_read_csds(struct mmc_host *host) +{ + struct mmc_card *card; + + list_for_each_entry(card, &host->cards, node) { + struct mmc_command cmd; + int err; + + if (card->state & (MMC_STATE_DEAD|MMC_STATE_PRESENT)) + continue; + + cmd.opcode = MMC_SEND_CSD; + cmd.arg = card->rca << 16; + cmd.flags = MMC_RSP_LONG | MMC_RSP_CRC; + + err = mmc_wait_for_cmd(host, &cmd, CMD_RETRIES); + if (err != MMC_ERR_NONE) { + card->state |= MMC_STATE_DEAD; + continue; + } + + mmc_decode_csd(&card->csd, cmd.resp); + } +} + +static unsigned int mmc_calculate_clock(struct mmc_host *host) +{ + struct mmc_card *card; + unsigned int max_dtr = host->f_max; + + list_for_each_entry(card, &host->cards, node) + if (!mmc_card_dead(card) && max_dtr > card->csd.max_dtr) + max_dtr = card->csd.max_dtr; + + DBG("MMC: selected %d.%03dMHz transfer rate\n", + max_dtr / 1000000, (max_dtr / 1000) % 1000); + + return max_dtr; +} + +/* + * Check whether cards we already know about are still present. + * We do this by requesting status, and checking whether a card + * responds. + * + * A request for status does not cause a state change in data + * transfer mode. + */ +static void mmc_check_cards(struct mmc_host *host) +{ + struct list_head *l, *n; + + mmc_deselect_cards(host); + + list_for_each_safe(l, n, &host->cards) { + struct mmc_card *card = mmc_list_to_card(l); + struct mmc_command cmd; + int err; + + cmd.opcode = MMC_SEND_STATUS; + cmd.arg = card->rca << 16; + cmd.flags = MMC_RSP_SHORT | MMC_RSP_CRC; + + err = mmc_wait_for_cmd(host, &cmd, CMD_RETRIES); + if (err == MMC_ERR_NONE) + continue; + + card->state |= MMC_STATE_DEAD; + } +} + +static void mmc_setup(struct mmc_host *host) +{ + if (host->ios.power_mode != MMC_POWER_ON) { + int err; + u32 ocr; + + mmc_power_up(host); + + err = mmc_send_op_cond(host, 0, &ocr); + if (err != MMC_ERR_NONE) + return; + + host->ocr = mmc_select_voltage(host, ocr); + } else { + host->ios.bus_mode = MMC_BUSMODE_OPENDRAIN; + host->ios.clock = host->f_min; + host->ops->set_ios(host, &host->ios); + + /* + * We should remember the OCR mask from the existing + * cards, and detect the new cards OCR mask, combine + * the two and re-select the VDD. However, if we do + * change VDD, we should do an idle, and then do a + * full re-initialisation. We would need to notify + * drivers so that they can re-setup the cards as + * well, while keeping their queues at bay. + * + * For the moment, we take the easy way out - if the + * new cards don't like our currently selected VDD, + * they drop off the bus. + */ + } + + if (host->ocr == 0) + return; + + /* + * Send the selected OCR multiple times... until the cards + * all get the idea that they should be ready for CMD2. + * (My SanDisk card seems to need this.) + */ + mmc_send_op_cond(host, host->ocr, NULL); + + mmc_discover_cards(host); + + /* + * Ok, now switch to push-pull mode. + */ + host->ios.bus_mode = MMC_BUSMODE_PUSHPULL; + host->ops->set_ios(host, &host->ios); + + mmc_read_csds(host); +} + + +/** + * mmc_detect_change - process change of state on a MMC socket + * @host: host which changed state. + * + * All we know is that card(s) have been inserted or removed + * from the socket(s). We don't know which socket or cards. + */ +void mmc_detect_change(struct mmc_host *host) +{ + schedule_work(&host->detect); +} + +EXPORT_SYMBOL(mmc_detect_change); + + +static void mmc_rescan(void *data) +{ + struct mmc_host *host = data; + struct list_head *l, *n; + + mmc_claim_host(host); + + if (host->ios.power_mode == MMC_POWER_ON) + mmc_check_cards(host); + + mmc_setup(host); + + if (!list_empty(&host->cards)) { + /* + * (Re-)calculate the fastest clock rate which the + * attached cards and the host support. + */ + host->ios.clock = mmc_calculate_clock(host); + host->ops->set_ios(host, &host->ios); + } + + mmc_release_host(host); + + list_for_each_safe(l, n, &host->cards) { + struct mmc_card *card = mmc_list_to_card(l); + + /* + * If this is a new and good card, register it. + */ + if (!mmc_card_present(card) && !mmc_card_dead(card)) { + if (mmc_register_card(card)) + card->state |= MMC_STATE_DEAD; + else + card->state |= MMC_STATE_PRESENT; + } + + /* + * If this card is dead, destroy it. + */ + if (mmc_card_dead(card)) { + list_del(&card->node); + mmc_remove_card(card); + } + } + + /* + * If we discover that there are no cards on the + * bus, turn off the clock and power down. + */ + if (list_empty(&host->cards)) + mmc_power_off(host); +} + + +/** + * mmc_alloc_host - initialise the per-host structure. + * @extra: sizeof private data structure + * @dev: pointer to host device model structure + * + * Initialise the per-host structure. + */ +struct mmc_host *mmc_alloc_host(int extra, struct device *dev) +{ + struct mmc_host *host; + + host = kmalloc(sizeof(struct mmc_host) + extra, GFP_KERNEL); + if (host) { + memset(host, 0, sizeof(struct mmc_host) + extra); + + host->priv = host + 1; + + spin_lock_init(&host->lock); + init_waitqueue_head(&host->wq); + INIT_LIST_HEAD(&host->cards); + INIT_WORK(&host->detect, mmc_rescan, host); + + host->dev = dev; + } + + return host; +} + +EXPORT_SYMBOL(mmc_alloc_host); + +/** + * mmc_add_host - initialise host hardware + * @host: mmc host + */ +int mmc_add_host(struct mmc_host *host) +{ + static unsigned int host_num; + + snprintf(host->host_name, sizeof(host->host_name), + "mmc%d", host_num++); + + mmc_power_off(host); + mmc_detect_change(host); + + return 0; +} + +EXPORT_SYMBOL(mmc_add_host); + +/** + * mmc_remove_host - remove host hardware + * @host: mmc host + * + * Unregister and remove all cards associated with this host, + * and power down the MMC bus. + */ +void mmc_remove_host(struct mmc_host *host) +{ + struct list_head *l, *n; + + list_for_each_safe(l, n, &host->cards) { + struct mmc_card *card = mmc_list_to_card(l); + + mmc_remove_card(card); + } + + mmc_power_off(host); +} + +EXPORT_SYMBOL(mmc_remove_host); + +/** + * mmc_free_host - free the host structure + * @host: mmc host + * + * Free the host once all references to it have been dropped. + */ +void mmc_free_host(struct mmc_host *host) +{ + flush_scheduled_work(); + kfree(host); +} + +EXPORT_SYMBOL(mmc_free_host); + +#ifdef CONFIG_PM + +/** + * mmc_suspend_host - suspend a host + * @host: mmc host + * @state: suspend mode (PM_SUSPEND_xxx) + */ +int mmc_suspend_host(struct mmc_host *host, u32 state) +{ + mmc_claim_host(host); + mmc_deselect_cards(host); + mmc_power_off(host); + mmc_release_host(host); + + return 0; +} + +EXPORT_SYMBOL(mmc_suspend_host); + +/** + * mmc_resume_host - resume a previously suspended host + * @host: mmc host + */ +int mmc_resume_host(struct mmc_host *host) +{ + mmc_detect_change(host); + + return 0; +} + +EXPORT_SYMBOL(mmc_resume_host); + +#endif + +MODULE_LICENSE("GPL"); diff -Nru a/drivers/mmc/mmc.h b/drivers/mmc/mmc.h --- /dev/null Wed Dec 31 16:00:00 196900 +++ b/drivers/mmc/mmc.h 2004-08-21 23:49:20 -07:00 @@ -0,0 +1,16 @@ +/* + * linux/drivers/mmc/mmc.h + * + * Copyright (C) 2003 Russell King, All Rights Reserved. + * + * 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 _MMC_H +#define _MMC_H +/* core-internal functions */ +void mmc_init_card(struct mmc_card *card, struct mmc_host *host); +int mmc_register_card(struct mmc_card *card); +void mmc_remove_card(struct mmc_card *card); +#endif diff -Nru a/drivers/mmc/mmc_block.c b/drivers/mmc/mmc_block.c --- /dev/null Wed Dec 31 16:00:00 196900 +++ b/drivers/mmc/mmc_block.c 2004-08-21 23:49:20 -07:00 @@ -0,0 +1,497 @@ +/* + * Block driver for media (i.e., flash cards) + * + * Copyright 2002 Hewlett-Packard Company + * + * Use consistent with the GNU GPL is permitted, + * provided that this copyright notice is + * preserved in its entirety in all copies and derived works. + * + * HEWLETT-PACKARD COMPANY MAKES NO WARRANTIES, EXPRESSED OR IMPLIED, + * AS TO THE USEFULNESS OR CORRECTNESS OF THIS CODE OR ITS + * FITNESS FOR ANY PARTICULAR PURPOSE. + * + * Many thanks to Alessandro Rubini and Jonathan Corbet! + * + * Author: Andrew Christian + * 28 May 2002 + */ +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "mmc_queue.h" + +/* + * max 8 partitions per card + */ +#define MMC_SHIFT 3 + +static int mmc_major; +static int maxsectors = 8; + +/* + * There is one mmc_blk_data per slot. + */ +struct mmc_blk_data { + spinlock_t lock; + struct gendisk *disk; + struct mmc_queue queue; + + unsigned int usage; + unsigned int block_bits; +}; + +static DECLARE_MUTEX(open_lock); + +static struct mmc_blk_data *mmc_blk_get(struct gendisk *disk) +{ + struct mmc_blk_data *md; + + down(&open_lock); + md = disk->private_data; + if (md && md->usage == 0) + md = NULL; + if (md) + md->usage++; + up(&open_lock); + + return md; +} + +static void mmc_blk_put(struct mmc_blk_data *md) +{ + down(&open_lock); + md->usage--; + if (md->usage == 0) { + put_disk(md->disk); + mmc_cleanup_queue(&md->queue); + kfree(md); + } + up(&open_lock); +} + +static int mmc_blk_open(struct inode *inode, struct file *filp) +{ + struct mmc_blk_data *md; + int ret = -ENXIO; + + md = mmc_blk_get(inode->i_bdev->bd_disk); + if (md) { + if (md->usage == 2) + check_disk_change(inode->i_bdev); + ret = 0; + } + + return ret; +} + +static int mmc_blk_release(struct inode *inode, struct file *filp) +{ + struct mmc_blk_data *md = inode->i_bdev->bd_disk->private_data; + + mmc_blk_put(md); + return 0; +} + +static int +mmc_blk_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) +{ + struct block_device *bdev = inode->i_bdev; + + if (cmd == HDIO_GETGEO) { + struct hd_geometry geo; + + memset(&geo, 0, sizeof(struct hd_geometry)); + + geo.cylinders = get_capacity(bdev->bd_disk) / (4 * 16); + geo.heads = 4; + geo.sectors = 16; + geo.start = get_start_sect(bdev); + + return copy_to_user((void *)arg, &geo, sizeof(geo)) + ? -EFAULT : 0; + } + + return -ENOTTY; +} + +static struct block_device_operations mmc_bdops = { + .open = mmc_blk_open, + .release = mmc_blk_release, + .ioctl = mmc_blk_ioctl, + .owner = THIS_MODULE, +}; + +struct mmc_blk_request { + struct mmc_request mrq; + struct mmc_command cmd; + struct mmc_command stop; + struct mmc_data data; +}; + +static int mmc_blk_prep_rq(struct mmc_queue *mq, struct request *req) +{ + struct mmc_blk_data *md = mq->data; + int stat = BLKPREP_OK; + + /* + * If we have no device, we haven't finished initialising. + */ + if (!md || !mq->card) { + printk(KERN_ERR "%s: killing request - no device/host\n", + req->rq_disk->disk_name); + stat = BLKPREP_KILL; + } + + return stat; +} + +static int mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req) +{ + struct mmc_blk_data *md = mq->data; + struct mmc_card *card = md->queue.card; + int ret; + + if (mmc_card_claim_host(card)) + goto cmd_err; + + do { + struct mmc_blk_request brq; + struct mmc_command cmd; + + memset(&brq, 0, sizeof(struct mmc_blk_request)); + brq.mrq.cmd = &brq.cmd; + brq.mrq.data = &brq.data; + + brq.cmd.arg = req->sector << 9; + brq.cmd.flags = MMC_RSP_SHORT | MMC_RSP_CRC; + brq.data.req = req; + brq.data.timeout_ns = card->csd.tacc_ns * 10; + brq.data.timeout_clks = card->csd.tacc_clks * 10; + brq.data.blksz_bits = md->block_bits; + brq.data.blocks = req->current_nr_sectors >> (md->block_bits - 9); + brq.stop.opcode = MMC_STOP_TRANSMISSION; + brq.stop.arg = 0; + brq.stop.flags = MMC_RSP_SHORT | MMC_RSP_CRC | MMC_RSP_BUSY; + + if (rq_data_dir(req) == READ) { + brq.cmd.opcode = brq.data.blocks > 1 ? MMC_READ_MULTIPLE_BLOCK : MMC_READ_SINGLE_BLOCK; + brq.data.flags |= MMC_DATA_READ; + } else { + brq.cmd.opcode = MMC_WRITE_BLOCK; + brq.cmd.flags |= MMC_RSP_BUSY; + brq.data.flags |= MMC_DATA_WRITE; + brq.data.blocks = 1; + } + brq.mrq.stop = brq.data.blocks > 1 ? &brq.stop : NULL; + + mmc_wait_for_req(card->host, &brq.mrq); + if (brq.cmd.error) { + printk(KERN_ERR "%s: error %d sending read/write command\n", + req->rq_disk->disk_name, brq.cmd.error); + goto cmd_err; + } + + if (brq.data.error) { + printk(KERN_ERR "%s: error %d transferring data\n", + req->rq_disk->disk_name, brq.data.error); + goto cmd_err; + } + + if (brq.stop.error) { + printk(KERN_ERR "%s: error %d sending stop command\n", + req->rq_disk->disk_name, brq.stop.error); + goto cmd_err; + } + + do { + int err; + + cmd.opcode = MMC_SEND_STATUS; + cmd.arg = card->rca << 16; + cmd.flags = MMC_RSP_SHORT | MMC_RSP_CRC; + err = mmc_wait_for_cmd(card->host, &cmd, 5); + if (err) { + printk(KERN_ERR "%s: error %d requesting status\n", + req->rq_disk->disk_name, err); + goto cmd_err; + } + } while (!(cmd.resp[0] & R1_READY_FOR_DATA)); + +#if 0 + if (cmd.resp[0] & ~0x00000900) + printk(KERN_ERR "%s: status = %08x\n", + req->rq_disk->disk_name, cmd.resp[0]); + if (mmc_decode_status(cmd.resp)) + goto cmd_err; +#endif + + /* + * A block was successfully transferred. + */ + spin_lock_irq(&md->lock); + ret = end_that_request_chunk(req, 1, brq.data.bytes_xfered); + if (!ret) { + /* + * The whole request completed successfully. + */ + add_disk_randomness(req->rq_disk); + blkdev_dequeue_request(req); + end_that_request_last(req); + } + spin_unlock_irq(&md->lock); + } while (ret); + + mmc_card_release_host(card); + + return 1; + + cmd_err: + mmc_card_release_host(card); + + /* + * This is a little draconian, but until we get proper + * error handling sorted out here, its the best we can + * do - especially as some hosts have no idea how much + * data was transferred before the error occurred. + */ + spin_lock_irq(&md->lock); + do { + ret = end_that_request_chunk(req, 0, + req->current_nr_sectors << 9); + } while (ret); + + add_disk_randomness(req->rq_disk); + blkdev_dequeue_request(req); + end_that_request_last(req); + spin_unlock_irq(&md->lock); + + return 0; +} + +#define MMC_NUM_MINORS (256 >> MMC_SHIFT) + +static unsigned long dev_use[MMC_NUM_MINORS/(8*sizeof(unsigned long))]; + +static struct mmc_blk_data *mmc_blk_alloc(struct mmc_card *card) +{ + struct mmc_blk_data *md; + int devidx, ret; + + devidx = find_first_zero_bit(dev_use, MMC_NUM_MINORS); + if (devidx >= MMC_NUM_MINORS) + return ERR_PTR(-ENOSPC); + __set_bit(devidx, dev_use); + + md = kmalloc(sizeof(struct mmc_blk_data), GFP_KERNEL); + if (md) { + memset(md, 0, sizeof(struct mmc_blk_data)); + + md->disk = alloc_disk(1 << MMC_SHIFT); + if (md->disk == NULL) { + kfree(md); + md = ERR_PTR(-ENOMEM); + goto out; + } + + spin_lock_init(&md->lock); + md->usage = 1; + + ret = mmc_init_queue(&md->queue, card, &md->lock); + if (ret) { + put_disk(md->disk); + kfree(md); + md = ERR_PTR(ret); + goto out; + } + md->queue.prep_fn = mmc_blk_prep_rq; + md->queue.issue_fn = mmc_blk_issue_rq; + md->queue.data = md; + + md->disk->major = mmc_major; + md->disk->first_minor = devidx << MMC_SHIFT; + md->disk->fops = &mmc_bdops; + md->disk->private_data = md; + md->disk->queue = md->queue.queue; + md->disk->driverfs_dev = &card->dev; + + sprintf(md->disk->disk_name, "mmcblk%d", devidx); + sprintf(md->disk->devfs_name, "mmc/blk%d", devidx); + + md->block_bits = md->queue.card->csd.read_blkbits; + + blk_queue_max_sectors(md->queue.queue, maxsectors); + blk_queue_hardsect_size(md->queue.queue, 1 << md->block_bits); + set_capacity(md->disk, md->queue.card->csd.capacity); + } + out: + return md; +} + +static int +mmc_blk_set_blksize(struct mmc_blk_data *md, struct mmc_card *card) +{ + struct mmc_command cmd; + int err; + + mmc_card_claim_host(card); + cmd.opcode = MMC_SET_BLOCKLEN; + cmd.arg = 1 << card->csd.read_blkbits; + cmd.flags = MMC_RSP_SHORT | MMC_RSP_CRC; + err = mmc_wait_for_cmd(card->host, &cmd, 5); + mmc_card_release_host(card); + + if (err) { + printk(KERN_ERR "%s: unable to set block size to %d: %d\n", + md->disk->disk_name, cmd.arg, err); + return -EINVAL; + } + + return 0; +} + +static int mmc_blk_probe(struct mmc_card *card) +{ + struct mmc_blk_data *md; + int err; + + if (card->csd.cmdclass & ~0x1ff) + return -ENODEV; + + if (card->csd.read_blkbits < 9) { + printk(KERN_WARNING "%s: read blocksize too small (%u)\n", + mmc_card_id(card), 1 << card->csd.read_blkbits); + return -ENODEV; + } + + md = mmc_blk_alloc(card); + if (IS_ERR(md)) + return PTR_ERR(md); + + err = mmc_blk_set_blksize(md, card); + if (err) + goto out; + + printk(KERN_INFO "%s: %s %s %dKiB\n", + md->disk->disk_name, mmc_card_id(card), mmc_card_name(card), + (card->csd.capacity << card->csd.read_blkbits) / 1024); + + mmc_set_drvdata(card, md); + add_disk(md->disk); + return 0; + + out: + mmc_blk_put(md); + + return err; +} + +static void mmc_blk_remove(struct mmc_card *card) +{ + struct mmc_blk_data *md = mmc_get_drvdata(card); + + if (md) { + int devidx; + + del_gendisk(md->disk); + + /* + * I think this is needed. + */ + md->disk->queue = NULL; + + devidx = md->disk->first_minor >> MMC_SHIFT; + __clear_bit(devidx, dev_use); + + mmc_blk_put(md); + } + mmc_set_drvdata(card, NULL); +} + +#ifdef CONFIG_PM +static int mmc_blk_suspend(struct mmc_card *card, u32 state) +{ + struct mmc_blk_data *md = mmc_get_drvdata(card); + + if (md) { + blk_stop_queue(md->queue.queue); + } + return 0; +} + +static int mmc_blk_resume(struct mmc_card *card) +{ + struct mmc_blk_data *md = mmc_get_drvdata(card); + + if (md) { + mmc_blk_set_blksize(md, md->queue.card); + blk_start_queue(md->queue.queue); + } + return 0; +} +#else +#define mmc_blk_suspend NULL +#define mmc_blk_resume NULL +#endif + +static struct mmc_driver mmc_driver = { + .drv = { + .name = "mmcblk", + }, + .probe = mmc_blk_probe, + .remove = mmc_blk_remove, + .suspend = mmc_blk_suspend, + .resume = mmc_blk_resume, +}; + +static int __init mmc_blk_init(void) +{ + int res = -ENOMEM; + + res = register_blkdev(mmc_major, "mmc"); + if (res < 0) { + printk(KERN_WARNING "Unable to get major %d for MMC media: %d\n", + mmc_major, res); + goto out; + } + if (mmc_major == 0) + mmc_major = res; + + devfs_mk_dir("mmc"); + return mmc_register_driver(&mmc_driver); + + out: + return res; +} + +static void __exit mmc_blk_exit(void) +{ + mmc_unregister_driver(&mmc_driver); + devfs_remove("mmc"); + unregister_blkdev(mmc_major, "mmc"); +} + +module_init(mmc_blk_init); +module_exit(mmc_blk_exit); +module_param(maxsectors, int, 0444); + +MODULE_PARM_DESC(maxsectors, "Maximum number of sectors for a single request"); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Multimedia Card (MMC) block device driver"); diff -Nru a/drivers/mmc/mmc_queue.c b/drivers/mmc/mmc_queue.c --- /dev/null Wed Dec 31 16:00:00 196900 +++ b/drivers/mmc/mmc_queue.c 2004-08-21 23:49:20 -07:00 @@ -0,0 +1,171 @@ +/* + * linux/drivers/mmc/mmc_queue.c + * + * Copyright (C) 2003 Russell King, All Rights Reserved. + * + * 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 +#include + +#include +#include +#include "mmc_queue.h" + +/* + * Prepare a MMC request. Essentially, this means passing the + * preparation off to the media driver. The media driver will + * create a mmc_io_request in req->special. + */ +static int mmc_prep_request(struct request_queue *q, struct request *req) +{ + struct mmc_queue *mq = q->queuedata; + int ret = BLKPREP_KILL; + + if (req->flags & REQ_SPECIAL) { + /* + * Special commands already have the command + * blocks already setup in req->special. + */ + BUG_ON(!req->special); + + ret = BLKPREP_OK; + } else if (req->flags & (REQ_CMD | REQ_BLOCK_PC)) { + /* + * Block I/O requests need translating according + * to the protocol. + */ + ret = mq->prep_fn(mq, req); + } else { + /* + * Everything else is invalid. + */ + blk_dump_rq_flags(req, "MMC bad request"); + } + + if (ret == BLKPREP_OK) + req->flags |= REQ_DONTPREP; + + return ret; +} + +static int mmc_queue_thread(void *d) +{ + struct mmc_queue *mq = d; + struct request_queue *q = mq->queue; + DECLARE_WAITQUEUE(wait, current); + + /* + * Set iothread to ensure that we aren't put to sleep by + * the process freezing. We handle suspension ourselves. + */ + current->flags |= PF_MEMALLOC|PF_NOFREEZE; + + daemonize("mmcqd"); + + spin_lock_irq(¤t->sighand->siglock); + sigfillset(¤t->blocked); + recalc_sigpending(); + spin_unlock_irq(¤t->sighand->siglock); + + mq->thread = current; + complete(&mq->thread_complete); + + add_wait_queue(&mq->thread_wq, &wait); + do { + struct request *req = NULL; + + spin_lock_irq(q->queue_lock); + set_current_state(TASK_INTERRUPTIBLE); + if (!blk_queue_plugged(q)) + mq->req = req = elv_next_request(q); + spin_unlock(q->queue_lock); + + if (!req) { + if (!mq->thread) + break; + schedule(); + continue; + } + set_current_state(TASK_RUNNING); + + mq->issue_fn(mq, req); + } while (1); + remove_wait_queue(&mq->thread_wq, &wait); + + complete_and_exit(&mq->thread_complete, 0); + return 0; +} + +/* + * Generic MMC request handler. This is called for any queue on a + * particular host. When the host is not busy, we look for a request + * on any queue on this host, and attempt to issue it. This may + * not be the queue we were asked to process. + */ +static void mmc_request(request_queue_t *q) +{ + struct mmc_queue *mq = q->queuedata; + + if (!mq->req) + wake_up(&mq->thread_wq); +} + +/** + * mmc_init_queue - initialise a queue structure. + * @mq: mmc queue + * @card: mmc card to attach this queue + * @lock: queue lock + * + * Initialise a MMC card request queue. + */ +int mmc_init_queue(struct mmc_queue *mq, struct mmc_card *card, spinlock_t *lock) +{ + u64 limit = BLK_BOUNCE_HIGH; + int ret; + + if (card->host->dev->dma_mask && *card->host->dev->dma_mask) + limit = *card->host->dev->dma_mask; + + mq->card = card; + mq->queue = blk_init_queue(mmc_request, lock); + if (!mq->queue) + return -ENOMEM; + + blk_queue_prep_rq(mq->queue, mmc_prep_request); + blk_queue_bounce_limit(mq->queue, limit); + + mq->queue->queuedata = mq; + mq->req = NULL; + + init_completion(&mq->thread_complete); + init_waitqueue_head(&mq->thread_wq); + + ret = kernel_thread(mmc_queue_thread, mq, CLONE_KERNEL); + if (ret < 0) { + blk_cleanup_queue(mq->queue); + } else { + wait_for_completion(&mq->thread_complete); + init_completion(&mq->thread_complete); + ret = 0; + } + + return ret; +} + +EXPORT_SYMBOL(mmc_init_queue); + +void mmc_cleanup_queue(struct mmc_queue *mq) +{ + mq->thread = NULL; + wake_up(&mq->thread_wq); + wait_for_completion(&mq->thread_complete); + blk_cleanup_queue(mq->queue); + + mq->card = NULL; +} + +EXPORT_SYMBOL(mmc_cleanup_queue); diff -Nru a/drivers/mmc/mmc_queue.h b/drivers/mmc/mmc_queue.h --- /dev/null Wed Dec 31 16:00:00 196900 +++ b/drivers/mmc/mmc_queue.h 2004-08-21 23:49:20 -07:00 @@ -0,0 +1,29 @@ +#ifndef MMC_QUEUE_H +#define MMC_QUEUE_H + +struct request; +struct task_struct; + +struct mmc_queue { + struct mmc_card *card; + struct completion thread_complete; + wait_queue_head_t thread_wq; + struct task_struct *thread; + struct request *req; + int (*prep_fn)(struct mmc_queue *, struct request *); + int (*issue_fn)(struct mmc_queue *, struct request *); + void *data; + struct request_queue *queue; +}; + +struct mmc_io_request { + struct request *rq; + int num; + struct mmc_command selcmd; /* mmc_queue private */ + struct mmc_command cmd[4]; /* max 4 commands */ +}; + +extern int mmc_init_queue(struct mmc_queue *, struct mmc_card *, spinlock_t *); +extern void mmc_cleanup_queue(struct mmc_queue *); + +#endif diff -Nru a/drivers/mmc/mmc_sysfs.c b/drivers/mmc/mmc_sysfs.c --- /dev/null Wed Dec 31 16:00:00 196900 +++ b/drivers/mmc/mmc_sysfs.c 2004-08-21 23:49:20 -07:00 @@ -0,0 +1,231 @@ +/* + * linux/drivers/mmc/mmc_sysfs.c + * + * Copyright (C) 2003 Russell King, All Rights Reserved. + * + * 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. + * + * MMC sysfs/driver model support. + */ +#include +#include +#include + +#include +#include + +#include "mmc.h" + +#define dev_to_mmc_card(d) container_of(d, struct mmc_card, dev) +#define to_mmc_driver(d) container_of(d, struct mmc_driver, drv) + +static void mmc_release_card(struct device *dev) +{ + struct mmc_card *card = dev_to_mmc_card(dev); + + kfree(card); +} + +/* + * This currently matches any MMC driver to any MMC card - drivers + * themselves make the decision whether to drive this card in their + * probe method. + */ +static int mmc_bus_match(struct device *dev, struct device_driver *drv) +{ + return 1; +} + +static int +mmc_bus_hotplug(struct device *dev, char **envp, int num_envp, char *buf, + int buf_size) +{ + struct mmc_card *card = dev_to_mmc_card(dev); + char ccc[13]; + int i = 0; + +#define add_env(fmt,val) \ + ({ \ + int len, ret = -ENOMEM; \ + if (i < num_envp) { \ + envp[i++] = buf; \ + len = snprintf(buf, buf_size, fmt, val) + 1; \ + buf_size -= len; \ + buf += len; \ + if (buf_size >= 0) \ + ret = 0; \ + } \ + ret; \ + }) + + for (i = 0; i < 12; i++) + ccc[i] = card->csd.cmdclass & (1 << i) ? '1' : '0'; + ccc[12] = '\0'; + + i = 0; + add_env("MMC_CCC=%s", ccc); + add_env("MMC_MANFID=%03x", card->cid.manfid); + add_env("MMC_SLOT_NAME=%s", card->dev.bus_id); + + return 0; +} + +static int mmc_bus_suspend(struct device *dev, u32 state) +{ + struct mmc_driver *drv = to_mmc_driver(dev->driver); + struct mmc_card *card = dev_to_mmc_card(dev); + int ret = 0; + + if (dev->driver && drv->suspend) + ret = drv->suspend(card, state); + return ret; +} + +static int mmc_bus_resume(struct device *dev) +{ + struct mmc_driver *drv = to_mmc_driver(dev->driver); + struct mmc_card *card = dev_to_mmc_card(dev); + int ret = 0; + + if (dev->driver && drv->resume) + ret = drv->resume(card); + return ret; +} + +static struct bus_type mmc_bus_type = { + .name = "mmc", + .match = mmc_bus_match, + .hotplug = mmc_bus_hotplug, + .suspend = mmc_bus_suspend, + .resume = mmc_bus_resume, +}; + + +static int mmc_drv_probe(struct device *dev) +{ + struct mmc_driver *drv = to_mmc_driver(dev->driver); + struct mmc_card *card = dev_to_mmc_card(dev); + + return drv->probe(card); +} + +static int mmc_drv_remove(struct device *dev) +{ + struct mmc_driver *drv = to_mmc_driver(dev->driver); + struct mmc_card *card = dev_to_mmc_card(dev); + + drv->remove(card); + + return 0; +} + + +/** + * mmc_register_driver - register a media driver + * @drv: MMC media driver + */ +int mmc_register_driver(struct mmc_driver *drv) +{ + drv->drv.bus = &mmc_bus_type; + drv->drv.probe = mmc_drv_probe; + drv->drv.remove = mmc_drv_remove; + return driver_register(&drv->drv); +} + +EXPORT_SYMBOL(mmc_register_driver); + +/** + * mmc_unregister_driver - unregister a media driver + * @drv: MMC media driver + */ +void mmc_unregister_driver(struct mmc_driver *drv) +{ + drv->drv.bus = &mmc_bus_type; + driver_unregister(&drv->drv); +} + +EXPORT_SYMBOL(mmc_unregister_driver); + + +#define MMC_ATTR(name, fmt, args...) \ +static ssize_t mmc_dev_show_##name (struct device *dev, char *buf) \ +{ \ + struct mmc_card *card = dev_to_mmc_card(dev); \ + return sprintf(buf, fmt, args); \ +} \ +static DEVICE_ATTR(name, S_IRUGO, mmc_dev_show_##name, NULL) + +MMC_ATTR(date, "%02d/%04d\n", card->cid.month, 1997 + card->cid.year); +MMC_ATTR(fwrev, "0x%x\n", card->cid.fwrev); +MMC_ATTR(hwrev, "0x%x\n", card->cid.hwrev); +MMC_ATTR(manfid, "0x%03x\n", card->cid.manfid); +MMC_ATTR(serial, "0x%06x\n", card->cid.serial); +MMC_ATTR(name, "%s\n", card->cid.prod_name); + +static struct device_attribute *mmc_dev_attributes[] = { + &dev_attr_date, + &dev_attr_fwrev, + &dev_attr_hwrev, + &dev_attr_manfid, + &dev_attr_serial, + &dev_attr_name, +}; + +/* + * Internal function. Initialise a MMC card structure. + */ +void mmc_init_card(struct mmc_card *card, struct mmc_host *host) +{ + memset(card, 0, sizeof(struct mmc_card)); + card->host = host; + device_initialize(&card->dev); + card->dev.parent = card->host->dev; + card->dev.bus = &mmc_bus_type; + card->dev.release = mmc_release_card; +} + +/* + * Internal function. Register a new MMC card with the driver model. + */ +int mmc_register_card(struct mmc_card *card) +{ + int ret, i; + + snprintf(card->dev.bus_id, sizeof(card->dev.bus_id), + "%s:%04x", card->host->host_name, card->rca); + + ret = device_add(&card->dev); + if (ret == 0) + for (i = 0; i < ARRAY_SIZE(mmc_dev_attributes); i++) + device_create_file(&card->dev, mmc_dev_attributes[i]); + + return ret; +} + +/* + * Internal function. Unregister a new MMC card with the + * driver model, and (eventually) free it. + */ +void mmc_remove_card(struct mmc_card *card) +{ + if (mmc_card_present(card)) + device_del(&card->dev); + + put_device(&card->dev); +} + + +static int __init mmc_init(void) +{ + return bus_register(&mmc_bus_type); +} + +static void __exit mmc_exit(void) +{ + bus_unregister(&mmc_bus_type); +} + +module_init(mmc_init); +module_exit(mmc_exit); diff -Nru a/drivers/mmc/mmci.c b/drivers/mmc/mmci.c --- /dev/null Wed Dec 31 16:00:00 196900 +++ b/drivers/mmc/mmci.c 2004-08-21 23:49:20 -07:00 @@ -0,0 +1,590 @@ +/* + * linux/drivers/mmc/mmci.c - ARM PrimeCell MMCI PL180/1 driver + * + * Copyright (C) 2003 Deep Blue Solutions, Ltd, All Rights Reserved. + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "mmci.h" + +#define DRIVER_NAME "mmci-pl18x" + +#ifdef CONFIG_MMC_DEBUG +#define DBG(host,fmt,args...) \ + pr_debug("%s: %s: " fmt, host->mmc->host_name, __func__ , args) +#else +#define DBG(host,fmt,args...) do { } while (0) +#endif + +static unsigned int fmax = 515633; + +static void +mmci_request_end(struct mmci_host *host, struct mmc_request *mrq) +{ + writel(0, host->base + MMCICOMMAND); + host->mrq = NULL; + host->cmd = NULL; + host->data = NULL; + host->buffer = NULL; + + if (mrq->data) + mrq->data->bytes_xfered = host->data_xfered; + + /* + * Need to drop the host lock here; mmc_request_done may call + * back into the driver... + */ + spin_unlock(&host->lock); + mmc_request_done(host->mmc, mrq); + spin_lock(&host->lock); +} + +static void mmci_start_data(struct mmci_host *host, struct mmc_data *data) +{ + unsigned int datactrl, timeout, irqmask; + void *base; + + DBG(host, "blksz %04x blks %04x flags %08x\n", + 1 << data->blksz_bits, data->blocks, data->flags); + + host->data = data; + host->buffer = data->req->buffer; + host->size = data->blocks << data->blksz_bits; + host->data_xfered = 0; + + timeout = data->timeout_clks + + ((unsigned long long)data->timeout_ns * host->cclk) / + 1000000000ULL; + + base = host->base; + writel(timeout, base + MMCIDATATIMER); + writel(host->size, base + MMCIDATALENGTH); + + datactrl = MCI_DPSM_ENABLE | data->blksz_bits << 4; + if (data->flags & MMC_DATA_READ) { + datactrl |= MCI_DPSM_DIRECTION; + irqmask = MCI_RXFIFOHALFFULLMASK; + } else { + /* + * We don't actually need to include "FIFO empty" here + * since its implicit in "FIFO half empty". + */ + irqmask = MCI_TXFIFOHALFEMPTYMASK; + } + + writel(datactrl, base + MMCIDATACTRL); + writel(irqmask, base + MMCIMASK1); +} + +static void +mmci_start_command(struct mmci_host *host, struct mmc_command *cmd, u32 c) +{ + void *base = host->base; + + DBG(host, "op %02x arg %08x flags %08x\n", + cmd->opcode, cmd->arg, cmd->flags); + + if (readl(base + MMCICOMMAND) & MCI_CPSM_ENABLE) { + writel(0, base + MMCICOMMAND); + udelay(1); + } + + c |= cmd->opcode | MCI_CPSM_ENABLE; + switch (cmd->flags & MMC_RSP_MASK) { + case MMC_RSP_NONE: + default: + break; + case MMC_RSP_LONG: + c |= MCI_CPSM_LONGRSP; + case MMC_RSP_SHORT: + c |= MCI_CPSM_RESPONSE; + break; + } + if (/*interrupt*/0) + c |= MCI_CPSM_INTERRUPT; + + host->cmd = cmd; + + writel(cmd->arg, base + MMCIARGUMENT); + writel(c, base + MMCICOMMAND); +} + +static void +mmci_data_irq(struct mmci_host *host, struct mmc_data *data, + unsigned int status) +{ + if (status & MCI_DATABLOCKEND) { + host->data_xfered += 1 << data->blksz_bits; + } + if (status & (MCI_DATACRCFAIL|MCI_DATATIMEOUT|MCI_TXUNDERRUN|MCI_RXOVERRUN)) { + if (status & MCI_DATACRCFAIL) + data->error = MMC_ERR_BADCRC; + else if (status & MCI_DATATIMEOUT) + data->error = MMC_ERR_TIMEOUT; + else if (status & (MCI_TXUNDERRUN|MCI_RXOVERRUN)) + data->error = MMC_ERR_FIFO; + status |= MCI_DATAEND; + } + if (status & MCI_DATAEND) { + host->data = NULL; + if (!data->stop) { + mmci_request_end(host, data->mrq); + } else { + mmci_start_command(host, data->stop, 0); + } + } +} + +static void +mmci_cmd_irq(struct mmci_host *host, struct mmc_command *cmd, + unsigned int status) +{ + void *base = host->base; + + host->cmd = NULL; + + cmd->resp[0] = readl(base + MMCIRESPONSE0); + cmd->resp[1] = readl(base + MMCIRESPONSE1); + cmd->resp[2] = readl(base + MMCIRESPONSE2); + cmd->resp[3] = readl(base + MMCIRESPONSE3); + + if (status & MCI_CMDTIMEOUT) { + cmd->error = MMC_ERR_TIMEOUT; + } else if (status & MCI_CMDCRCFAIL && cmd->flags & MMC_RSP_CRC) { + cmd->error = MMC_ERR_BADCRC; + } + + if (!cmd->data || cmd->error != MMC_ERR_NONE) { + mmci_request_end(host, cmd->mrq); + } else if (!(cmd->data->flags & MMC_DATA_READ)) { + mmci_start_data(host, cmd->data); + } +} + +/* + * PIO data transfer IRQ handler. + */ +static irqreturn_t mmci_pio_irq(int irq, void *dev_id, struct pt_regs *regs) +{ + struct mmci_host *host = dev_id; + void *base = host->base; + u32 status; + int ret = 0; + + do { + status = readl(base + MMCISTATUS); + + if (!(status & (MCI_RXDATAAVLBL|MCI_RXFIFOHALFFULL| + MCI_TXFIFOHALFEMPTY))) + break; + + DBG(host, "irq1 %08x\n", status); + + if (status & (MCI_RXDATAAVLBL|MCI_RXFIFOHALFFULL)) { + unsigned int count = host->size - (readl(base + MMCIFIFOCNT) << 2); + if (count < 0) + count = 0; + if (count && host->buffer) { + readsl(base + MMCIFIFO, host->buffer, count >> 2); + host->buffer += count; + host->size -= count; + if (host->size == 0) + host->buffer = NULL; + } else { + static int first = 1; + if (first) { + first = 0; + printk(KERN_ERR "MMCI: sinking excessive data\n"); + } + readl(base + MMCIFIFO); + } + } + + /* + * We only need to test the half-empty flag here - if + * the FIFO is completely empty, then by definition + * it is more than half empty. + */ + if (status & MCI_TXFIFOHALFEMPTY) { + unsigned int maxcnt = status & MCI_TXFIFOEMPTY ? + MCI_FIFOSIZE : MCI_FIFOHALFSIZE; + unsigned int count = min(host->size, maxcnt); + + writesl(base + MMCIFIFO, host->buffer, count >> 2); + + host->buffer += count; + host->size -= count; + + /* + * If we run out of data, disable the data IRQs; + * this prevents a race where the FIFO becomes + * empty before the chip itself has disabled the + * data path. + */ + if (host->size == 0) + writel(0, base + MMCIMASK1); + } + + ret = 1; + } while (status); + + return IRQ_RETVAL(ret); +} + +/* + * Handle completion of command and data transfers. + */ +static irqreturn_t mmci_irq(int irq, void *dev_id, struct pt_regs *regs) +{ + struct mmci_host *host = dev_id; + u32 status; + int ret = 0; + + spin_lock(&host->lock); + + do { + struct mmc_command *cmd; + struct mmc_data *data; + + status = readl(host->base + MMCISTATUS); + writel(status, host->base + MMCICLEAR); + + if (!(status & MCI_IRQMASK)) + break; + + DBG(host, "irq0 %08x\n", status); + + data = host->data; + if (status & (MCI_DATACRCFAIL|MCI_DATATIMEOUT|MCI_TXUNDERRUN| + MCI_RXOVERRUN|MCI_DATAEND|MCI_DATABLOCKEND) && data) + mmci_data_irq(host, data, status); + + cmd = host->cmd; + if (status & (MCI_CMDCRCFAIL|MCI_CMDTIMEOUT|MCI_CMDSENT|MCI_CMDRESPEND) && cmd) + mmci_cmd_irq(host, cmd, status); + + ret = 1; + } while (status); + + spin_unlock(&host->lock); + + return IRQ_RETVAL(ret); +} + +static void mmci_request(struct mmc_host *mmc, struct mmc_request *mrq) +{ + struct mmci_host *host = mmc_priv(mmc); + + WARN_ON(host->mrq != NULL); + + spin_lock_irq(&host->lock); + + host->mrq = mrq; + + if (mrq->data && mrq->data->flags & MMC_DATA_READ) + mmci_start_data(host, mrq->data); + + mmci_start_command(host, mrq->cmd, 0); + + spin_unlock_irq(&host->lock); +} + +static void mmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +{ + struct mmci_host *host = mmc_priv(mmc); + u32 clk = 0, pwr = 0; + + DBG(host, "clock %uHz busmode %u powermode %u Vdd %u\n", + ios->clock, ios->bus_mode, ios->power_mode, ios->vdd); + + if (ios->clock) { + if (ios->clock >= host->mclk) { + clk = MCI_CLK_BYPASS; + host->cclk = host->mclk; + } else { + clk = host->mclk / (2 * ios->clock) - 1; + if (clk > 256) + clk = 255; + host->cclk = host->mclk / (2 * (clk + 1)); + } + clk |= MCI_CLK_ENABLE; + } + + if (host->plat->translate_vdd) + pwr |= host->plat->translate_vdd(mmc_dev(mmc), ios->vdd); + + switch (ios->power_mode) { + case MMC_POWER_OFF: + break; + case MMC_POWER_UP: + pwr |= MCI_PWR_UP; + break; + case MMC_POWER_ON: + pwr |= MCI_PWR_ON; + break; + } + + if (ios->bus_mode == MMC_BUSMODE_OPENDRAIN) + pwr |= MCI_ROD; + + writel(clk, host->base + MMCICLOCK); + + if (host->pwr != pwr) { + host->pwr = pwr; + writel(pwr, host->base + MMCIPOWER); + } +} + +static struct mmc_host_ops mmci_ops = { + .request = mmci_request, + .set_ios = mmci_set_ios, +}; + +static void mmci_check_status(unsigned long data) +{ + struct mmci_host *host = (struct mmci_host *)data; + unsigned int status; + + status = host->plat->status(mmc_dev(host->mmc)); + if (status ^ host->oldstat) + mmc_detect_change(host->mmc); + + host->oldstat = status; + mod_timer(&host->timer, jiffies + HZ); +} + +static int mmci_probe(struct amba_device *dev, void *id) +{ + struct mmc_platform_data *plat = dev->dev.platform_data; + struct mmci_host *host; + struct mmc_host *mmc; + int ret; + + /* must have platform data */ + if (!plat) { + ret = -EINVAL; + goto out; + } + + ret = amba_request_regions(dev, DRIVER_NAME); + if (ret) + goto out; + + mmc = mmc_alloc_host(sizeof(struct mmci_host), &dev->dev); + if (!mmc) { + ret = -ENOMEM; + goto rel_regions; + } + + host = mmc_priv(mmc); + host->clk = clk_get(&dev->dev, "MCLK"); + if (IS_ERR(host->clk)) { + ret = PTR_ERR(host->clk); + host->clk = NULL; + goto host_free; + } + + ret = clk_use(host->clk); + if (ret) + goto clk_free; + + ret = clk_enable(host->clk); + if (ret) + goto clk_unuse; + + host->plat = plat; + host->mclk = clk_get_rate(host->clk); + host->mmc = mmc; + host->base = ioremap(dev->res.start, SZ_4K); + if (!host->base) { + ret = -ENOMEM; + goto clk_disable; + } + + mmc->ops = &mmci_ops; + mmc->f_min = (host->mclk + 511) / 512; + mmc->f_max = min(host->mclk, fmax); + mmc->ocr_avail = plat->ocr_mask; + + spin_lock_init(&host->lock); + + writel(0, host->base + MMCIMASK0); + writel(0, host->base + MMCIMASK1); + writel(0xfff, host->base + MMCICLEAR); + + ret = request_irq(dev->irq[0], mmci_irq, SA_SHIRQ, DRIVER_NAME " (cmd)", host); + if (ret) + goto unmap; + + ret = request_irq(dev->irq[1], mmci_pio_irq, SA_SHIRQ, DRIVER_NAME " (pio)", host); + if (ret) + goto irq0_free; + + writel(MCI_IRQENABLE, host->base + MMCIMASK0); + + amba_set_drvdata(dev, mmc); + + mmc_add_host(mmc); + + printk(KERN_INFO "%s: MMCI rev %x cfg %02x at 0x%08lx irq %d,%d\n", + mmc->host_name, amba_rev(dev), amba_config(dev), + dev->res.start, dev->irq[0], dev->irq[1]); + + init_timer(&host->timer); + host->timer.data = (unsigned long)host; + host->timer.function = mmci_check_status; + host->timer.expires = jiffies + HZ; + add_timer(&host->timer); + + return 0; + + irq0_free: + free_irq(dev->irq[0], host); + unmap: + iounmap(host->base); + clk_disable: + clk_disable(host->clk); + clk_unuse: + clk_unuse(host->clk); + clk_free: + clk_put(host->clk); + host_free: + mmc_free_host(mmc); + rel_regions: + amba_release_regions(dev); + out: + return ret; +} + +static int mmci_remove(struct amba_device *dev) +{ + struct mmc_host *mmc = amba_get_drvdata(dev); + + amba_set_drvdata(dev, NULL); + + if (mmc) { + struct mmci_host *host = mmc_priv(mmc); + + del_timer_sync(&host->timer); + + mmc_remove_host(mmc); + + writel(0, host->base + MMCIMASK0); + writel(0, host->base + MMCIMASK1); + + writel(0, host->base + MMCICOMMAND); + writel(0, host->base + MMCIDATACTRL); + + free_irq(dev->irq[0], host); + free_irq(dev->irq[1], host); + + iounmap(host->base); + clk_disable(host->clk); + clk_unuse(host->clk); + clk_put(host->clk); + + mmc_free_host(mmc); + + amba_release_regions(dev); + } + + return 0; +} + +#ifdef CONFIG_PM +static int mmci_suspend(struct amba_device *dev, u32 state) +{ + struct mmc_host *mmc = amba_get_drvdata(dev); + int ret = 0; + + if (mmc) { + struct mmci_host *host = mmc_priv(mmc); + + ret = mmc_suspend_host(mmc, state); + if (ret == 0) + writel(0, host->base + MMCIMASK0); + } + + return ret; +} + +static int mmci_resume(struct amba_device *dev) +{ + struct mmc_host *mmc = amba_get_drvdata(dev); + int ret = 0; + + if (mmc) { + struct mmci_host *host = mmc_priv(mmc); + + writel(MCI_IRQENABLE, host->base + MMCIMASK0); + + ret = mmc_resume_host(mmc); + } + + return ret; +} +#else +#define mmci_suspend NULL +#define mmci_resume NULL +#endif + +static struct amba_id mmci_ids[] = { + { + .id = 0x00041180, + .mask = 0x000fffff, + }, + { + .id = 0x00041181, + .mask = 0x000fffff, + }, + { 0, 0 }, +}; + +static struct amba_driver mmci_driver = { + .drv = { + .name = DRIVER_NAME, + }, + .probe = mmci_probe, + .remove = mmci_remove, + .suspend = mmci_suspend, + .resume = mmci_resume, + .id_table = mmci_ids, +}; + +static int __init mmci_init(void) +{ + return amba_driver_register(&mmci_driver); +} + +static void __exit mmci_exit(void) +{ + amba_driver_unregister(&mmci_driver); +} + +module_init(mmci_init); +module_exit(mmci_exit); +module_param(fmax, uint, 0444); + +MODULE_DESCRIPTION("ARM PrimeCell PL180/181 Multimedia Card Interface driver"); +MODULE_LICENSE("GPL"); diff -Nru a/drivers/mmc/mmci.h b/drivers/mmc/mmci.h --- /dev/null Wed Dec 31 16:00:00 196900 +++ b/drivers/mmc/mmci.h 2004-08-21 23:49:20 -07:00 @@ -0,0 +1,148 @@ +/* + * linux/drivers/mmc/mmci.h - ARM PrimeCell MMCI PL180/1 driver + * + * Copyright (C) 2003 Deep Blue Solutions, Ltd, All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#define MMCIPOWER 0x000 +#define MCI_PWR_OFF 0x00 +#define MCI_PWR_UP 0x02 +#define MCI_PWR_ON 0x03 +#define MCI_OD (1 << 6) +#define MCI_ROD (1 << 7) + +#define MMCICLOCK 0x004 +#define MCI_CLK_ENABLE (1 << 8) +#define MCI_CLK_PWRSAVE (1 << 9) +#define MCI_CLK_BYPASS (1 << 10) + +#define MMCIARGUMENT 0x008 +#define MMCICOMMAND 0x00c +#define MCI_CPSM_RESPONSE (1 << 6) +#define MCI_CPSM_LONGRSP (1 << 7) +#define MCI_CPSM_INTERRUPT (1 << 8) +#define MCI_CPSM_PENDING (1 << 9) +#define MCI_CPSM_ENABLE (1 << 10) + +#define MMCIRESPCMD 0x010 +#define MMCIRESPONSE0 0x014 +#define MMCIRESPONSE1 0x018 +#define MMCIRESPONSE2 0x01c +#define MMCIRESPONSE3 0x020 +#define MMCIDATATIMER 0x024 +#define MMCIDATALENGTH 0x028 +#define MMCIDATACTRL 0x02c +#define MCI_DPSM_ENABLE (1 << 0) +#define MCI_DPSM_DIRECTION (1 << 1) +#define MCI_DPSM_MODE (1 << 2) +#define MCI_DPSM_DMAENABLE (1 << 3) + +#define MMCIDATACNT 0x030 +#define MMCISTATUS 0x034 +#define MCI_CMDCRCFAIL (1 << 0) +#define MCI_DATACRCFAIL (1 << 1) +#define MCI_CMDTIMEOUT (1 << 2) +#define MCI_DATATIMEOUT (1 << 3) +#define MCI_TXUNDERRUN (1 << 4) +#define MCI_RXOVERRUN (1 << 5) +#define MCI_CMDRESPEND (1 << 6) +#define MCI_CMDSENT (1 << 7) +#define MCI_DATAEND (1 << 8) +#define MCI_DATABLOCKEND (1 << 10) +#define MCI_CMDACTIVE (1 << 11) +#define MCI_TXACTIVE (1 << 12) +#define MCI_RXACTIVE (1 << 13) +#define MCI_TXFIFOHALFEMPTY (1 << 14) +#define MCI_RXFIFOHALFFULL (1 << 15) +#define MCI_TXFIFOFULL (1 << 16) +#define MCI_RXFIFOFULL (1 << 17) +#define MCI_TXFIFOEMPTY (1 << 18) +#define MCI_RXFIFOEMPTY (1 << 19) +#define MCI_TXDATAAVLBL (1 << 20) +#define MCI_RXDATAAVLBL (1 << 21) + +#define MMCICLEAR 0x038 +#define MCI_CMDCRCFAILCLR (1 << 0) +#define MCI_DATACRCFAILCLR (1 << 1) +#define MCI_CMDTIMEOUTCLR (1 << 2) +#define MCI_DATATIMEOUTCLR (1 << 3) +#define MCI_TXUNDERRUNCLR (1 << 4) +#define MCI_RXOVERRUNCLR (1 << 5) +#define MCI_CMDRESPENDCLR (1 << 6) +#define MCI_CMDSENTCLR (1 << 7) +#define MCI_DATAENDCLR (1 << 8) +#define MCI_DATABLOCKENDCLR (1 << 10) + +#define MMCIMASK0 0x03c +#define MCI_CMDCRCFAILMASK (1 << 0) +#define MCI_DATACRCFAILMASK (1 << 1) +#define MCI_CMDTIMEOUTMASK (1 << 2) +#define MCI_DATATIMEOUTMASK (1 << 3) +#define MCI_TXUNDERRUNMASK (1 << 4) +#define MCI_RXOVERRUNMASK (1 << 5) +#define MCI_CMDRESPENDMASK (1 << 6) +#define MCI_CMDSENTMASK (1 << 7) +#define MCI_DATAENDMASK (1 << 8) +#define MCI_DATABLOCKENDMASK (1 << 10) +#define MCI_CMDACTIVEMASK (1 << 11) +#define MCI_TXACTIVEMASK (1 << 12) +#define MCI_RXACTIVEMASK (1 << 13) +#define MCI_TXFIFOHALFEMPTYMASK (1 << 14) +#define MCI_RXFIFOHALFFULLMASK (1 << 15) +#define MCI_TXFIFOFULLMASK (1 << 16) +#define MCI_RXFIFOFULLMASK (1 << 17) +#define MCI_TXFIFOEMPTYMASK (1 << 18) +#define MCI_RXFIFOEMPTYMASK (1 << 19) +#define MCI_TXDATAAVLBLMASK (1 << 20) +#define MCI_RXDATAAVLBLMASK (1 << 21) + +#define MMCIMASK1 0x040 +#define MMCIFIFOCNT 0x048 +#define MMCIFIFO 0x080 /* to 0x0bc */ + +#define MCI_IRQMASK \ + (MCI_CMDCRCFAIL|MCI_DATACRCFAIL|MCI_CMDTIMEOUT|MCI_DATATIMEOUT| \ + MCI_TXUNDERRUN|MCI_RXOVERRUN|MCI_CMDRESPEND|MCI_CMDSENT| \ + MCI_DATAEND|MCI_DATABLOCKEND) + +#define MCI_IRQENABLE \ + (MCI_CMDCRCFAILMASK|MCI_DATACRCFAILMASK|MCI_CMDTIMEOUTMASK| \ + MCI_DATATIMEOUTMASK|MCI_TXUNDERRUNMASK|MCI_RXOVERRUNMASK| \ + MCI_CMDRESPENDMASK|MCI_CMDSENTMASK|MCI_DATAENDMASK| \ + MCI_DATABLOCKENDMASK) + +#define MCI_FIFOSIZE 16 + +#define MCI_FIFOHALFSIZE (MCI_FIFOSIZE / 2) + +struct clk; + +struct mmci_host { + void *base; + struct mmc_request *mrq; + struct mmc_command *cmd; + struct mmc_data *data; + struct mmc_host *mmc; + struct clk *clk; + + unsigned int data_xfered; + + spinlock_t lock; + + unsigned int mclk; + unsigned int cclk; + u32 pwr; + struct mmc_platform_data *plat; + + struct timer_list timer; + unsigned int oldstat; + + /* pio stuff */ + void *buffer; + unsigned int size; +}; + +#define to_mmci_host(mmc) container_of(mmc, struct mmci_host, mmc) diff -Nru a/drivers/mmc/pxamci.c b/drivers/mmc/pxamci.c --- /dev/null Wed Dec 31 16:00:00 196900 +++ b/drivers/mmc/pxamci.c 2004-08-21 23:49:20 -07:00 @@ -0,0 +1,578 @@ +/* + * linux/drivers/mmc/pxa.c - PXA MMCI driver + * + * Copyright (C) 2003 Russell King, All Rights Reserved. + * + * 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. + * + * This hardware is really sick: + * - No way to clear interrupts. + * - Have to turn off the clock whenever we touch the device. + * - Doesn't tell you how many data blocks were transferred. + * Yuck! + * + * 1 and 3 byte data transfers not supported + * max block length up to 1023 + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "pxamci.h" + +#ifdef CONFIG_MMC_DEBUG +#define DBG(x...) printk(KERN_DEBUG x) +#else +#define DBG(x...) do { } while (0) +#endif + +struct pxamci_host { + struct mmc_host *mmc; + spinlock_t lock; + struct resource *res; + void *base; + int irq; + int dma; + unsigned int clkrt; + unsigned int cmdat; + unsigned int imask; + unsigned int power_mode; + + struct mmc_request *mrq; + struct mmc_command *cmd; + struct mmc_data *data; + + dma_addr_t sg_dma; + struct pxa_dma_desc *sg_cpu; + + dma_addr_t dma_buf; + unsigned int dma_size; + unsigned int dma_dir; +}; + +/* + * The base MMC clock rate + */ +#define CLOCKRATE 20000000 + +static inline unsigned int ns_to_clocks(unsigned int ns) +{ + return (ns * (CLOCKRATE / 1000000) + 999) / 1000; +} + +static void pxamci_stop_clock(struct pxamci_host *host) +{ + if (readl(host->base + MMC_STAT) & STAT_CLK_EN) { + unsigned long timeout = 10000; + unsigned int v; + + writel(STOP_CLOCK, host->base + MMC_STRPCL); + + do { + v = readl(host->base + MMC_STAT); + if (!(v & STAT_CLK_EN)) + break; + udelay(1); + } while (timeout--); + + if (v & STAT_CLK_EN) + dev_err(mmc_dev(host->mmc), "unable to stop clock\n"); + } +} + +static void pxamci_enable_irq(struct pxamci_host *host, unsigned int mask) +{ + unsigned long flags; + + spin_lock_irqsave(&host->lock, flags); + host->imask &= ~mask; + writel(host->imask, host->base + MMC_I_MASK); + spin_unlock_irqrestore(&host->lock, flags); +} + +static void pxamci_disable_irq(struct pxamci_host *host, unsigned int mask) +{ + unsigned long flags; + + spin_lock_irqsave(&host->lock, flags); + host->imask |= mask; + writel(host->imask, host->base + MMC_I_MASK); + spin_unlock_irqrestore(&host->lock, flags); +} + +static void pxamci_setup_data(struct pxamci_host *host, struct mmc_data *data) +{ + unsigned int nob = data->blocks; + unsigned int timeout, size; + dma_addr_t dma; + u32 dcmd; + int i; + + host->data = data; + + if (data->flags & MMC_DATA_STREAM) + nob = 0xffff; + + writel(nob, host->base + MMC_NOB); + writel(1 << data->blksz_bits, host->base + MMC_BLKLEN); + + timeout = ns_to_clocks(data->timeout_ns) + data->timeout_clks; + writel((timeout + 255) / 256, host->base + MMC_RDTO); + + if (data->flags & MMC_DATA_READ) { + host->dma_dir = DMA_FROM_DEVICE; + dcmd = DCMD_INCTRGADDR | DCMD_FLOWTRG; + DRCMRTXMMC = 0; + DRCMRRXMMC = host->dma | DRCMR_MAPVLD; + } else { + host->dma_dir = DMA_TO_DEVICE; + dcmd = DCMD_INCSRCADDR | DCMD_FLOWSRC; + DRCMRRXMMC = 0; + DRCMRTXMMC = host->dma | DRCMR_MAPVLD; + } + + dcmd |= DCMD_BURST32 | DCMD_WIDTH1; + + host->dma_size = data->blocks << data->blksz_bits; + host->dma_buf = dma_map_single(mmc_dev(host->mmc), data->req->buffer, + host->dma_size, host->dma_dir); + + for (i = 0, size = host->dma_size, dma = host->dma_buf; size; i++) { + u32 len = size; + + if (len > DCMD_LENGTH) + len = 0x1000; + + if (data->flags & MMC_DATA_READ) { + host->sg_cpu[i].dsadr = host->res->start + MMC_RXFIFO; + host->sg_cpu[i].dtadr = dma; + } else { + host->sg_cpu[i].dsadr = dma; + host->sg_cpu[i].dtadr = host->res->start + MMC_TXFIFO; + } + host->sg_cpu[i].dcmd = dcmd | len; + + dma += len; + size -= len; + + if (size) { + host->sg_cpu[i].ddadr = host->sg_dma + (i + 1) * + sizeof(struct pxa_dma_desc); + } else { + host->sg_cpu[i].ddadr = DDADR_STOP; + } + } + wmb(); + + DDADR(host->dma) = host->sg_dma; + DCSR(host->dma) = DCSR_RUN; +} + +static void pxamci_start_cmd(struct pxamci_host *host, struct mmc_command *cmd, unsigned int cmdat) +{ + WARN_ON(host->cmd != NULL); + host->cmd = cmd; + + if (cmd->flags & MMC_RSP_BUSY) + cmdat |= CMDAT_BUSY; + + switch (cmd->flags & (MMC_RSP_MASK | MMC_RSP_CRC)) { + case MMC_RSP_SHORT | MMC_RSP_CRC: + cmdat |= CMDAT_RESP_SHORT; + break; + case MMC_RSP_SHORT: + cmdat |= CMDAT_RESP_R3; + break; + case MMC_RSP_LONG | MMC_RSP_CRC: + cmdat |= CMDAT_RESP_R2; + break; + default: + break; + } + + writel(cmd->opcode, host->base + MMC_CMD); + writel(cmd->arg >> 16, host->base + MMC_ARGH); + writel(cmd->arg & 0xffff, host->base + MMC_ARGL); + writel(cmdat, host->base + MMC_CMDAT); + writel(host->clkrt, host->base + MMC_CLKRT); + + writel(START_CLOCK, host->base + MMC_STRPCL); + + pxamci_enable_irq(host, END_CMD_RES); +} + +static void pxamci_finish_request(struct pxamci_host *host, struct mmc_request *mrq) +{ + DBG("PXAMCI: request done\n"); + host->mrq = NULL; + host->cmd = NULL; + host->data = NULL; + mmc_request_done(host->mmc, mrq); +} + +static int pxamci_cmd_done(struct pxamci_host *host, unsigned int stat) +{ + struct mmc_command *cmd = host->cmd; + int i; + u32 v; + + if (!cmd) + return 0; + + host->cmd = NULL; + + /* + * Did I mention this is Sick. We always need to + * discard the upper 8 bits of the first 16-bit word. + */ + v = readl(host->base + MMC_RES) & 0xffff; + for (i = 0; i < 4; i++) { + u32 w1 = readl(host->base + MMC_RES) & 0xffff; + u32 w2 = readl(host->base + MMC_RES) & 0xffff; + cmd->resp[i] = v << 24 | w1 << 8 | w2 >> 8; + v = w2; + } + + if (stat & STAT_TIME_OUT_RESPONSE) { + cmd->error = MMC_ERR_TIMEOUT; + } else if (stat & STAT_RES_CRC_ERR && cmd->flags & MMC_RSP_CRC) { + cmd->error = MMC_ERR_BADCRC; + } + + pxamci_disable_irq(host, END_CMD_RES); + if (host->data && cmd->error == MMC_ERR_NONE) { + pxamci_enable_irq(host, DATA_TRAN_DONE); + } else { + pxamci_finish_request(host, host->mrq); + } + + return 1; +} + +static int pxamci_data_done(struct pxamci_host *host, unsigned int stat) +{ + struct mmc_data *data = host->data; + + if (!data) + return 0; + + DCSR(host->dma) = 0; + dma_unmap_single(mmc_dev(host->mmc), host->dma_buf, host->dma_size, + host->dma_dir); + + if (stat & STAT_READ_TIME_OUT) + data->error = MMC_ERR_TIMEOUT; + else if (stat & (STAT_CRC_READ_ERROR|STAT_CRC_WRITE_ERROR)) + data->error = MMC_ERR_BADCRC; + + /* + * There appears to be a hardware design bug here. There seems to + * be no way to find out how much data was transferred to the card. + * This means that if there was an error on any block, we mark all + * data blocks as being in error. + */ + if (data->error == MMC_ERR_NONE) + data->bytes_xfered = data->blocks << data->blksz_bits; + else + data->bytes_xfered = 0; + + pxamci_disable_irq(host, DATA_TRAN_DONE); + + host->data = NULL; + if (host->mrq->stop && data->error == MMC_ERR_NONE) { + pxamci_stop_clock(host); + pxamci_start_cmd(host, host->mrq->stop, 0); + } else { + pxamci_finish_request(host, host->mrq); + } + + return 1; +} + +static irqreturn_t pxamci_irq(int irq, void *devid, struct pt_regs *regs) +{ + struct pxamci_host *host = devid; + unsigned int ireg; + int handled = 0; + + ireg = readl(host->base + MMC_I_REG); + + DBG("PXAMCI: irq %08x\n", ireg); + + if (ireg) { + unsigned stat = readl(host->base + MMC_STAT); + + DBG("PXAMCI: stat %08x\n", stat); + + if (ireg & END_CMD_RES) + handled |= pxamci_cmd_done(host, stat); + if (ireg & DATA_TRAN_DONE) + handled |= pxamci_data_done(host, stat); + } + + return IRQ_RETVAL(handled); +} + +static void pxamci_request(struct mmc_host *mmc, struct mmc_request *mrq) +{ + struct pxamci_host *host = mmc_priv(mmc); + unsigned int cmdat; + + WARN_ON(host->mrq != NULL); + + host->mrq = mrq; + + pxamci_stop_clock(host); + + cmdat = host->cmdat; + host->cmdat &= ~CMDAT_INIT; + + if (mrq->data) { + pxamci_setup_data(host, mrq->data); + + cmdat &= ~CMDAT_BUSY; + cmdat |= CMDAT_DATAEN | CMDAT_DMAEN; + if (mrq->data->flags & MMC_DATA_WRITE) + cmdat |= CMDAT_WRITE; + + if (mrq->data->flags & MMC_DATA_STREAM) + cmdat |= CMDAT_STREAM; + } + + pxamci_start_cmd(host, mrq->cmd, cmdat); +} + +static void pxamci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +{ + struct pxamci_host *host = mmc_priv(mmc); + + DBG("pxamci_set_ios: clock %u power %u vdd %u.%02u\n", + ios->clock, ios->power_mode, ios->vdd / 100, + ios->vdd % 100); + + if (ios->clock) { + unsigned int clk = CLOCKRATE / ios->clock; + if (CLOCKRATE / clk > ios->clock) + clk <<= 1; + host->clkrt = fls(clk) - 1; + + /* + * we write clkrt on the next command + */ + } else if (readl(host->base + MMC_STAT) & STAT_CLK_EN) { + /* + * Ensure that the clock is off. + */ + writel(STOP_CLOCK, host->base + MMC_STRPCL); + } + + if (host->power_mode != ios->power_mode) { + host->power_mode = ios->power_mode; + + /* + * power control? none on the lubbock. + */ + + if (ios->power_mode == MMC_POWER_ON) + host->cmdat |= CMDAT_INIT; + } + + DBG("pxamci_set_ios: clkrt = %x cmdat = %x\n", + host->clkrt, host->cmdat); +} + +static struct mmc_host_ops pxamci_ops = { + .request = pxamci_request, + .set_ios = pxamci_set_ios, +}; + +static void pxamci_dma_irq(int dma, void *devid, struct pt_regs *regs) +{ + printk(KERN_ERR "DMA%d: IRQ???\n", dma); + DCSR(dma) = DCSR_STARTINTR|DCSR_ENDINTR|DCSR_BUSERR; +} + +static int pxamci_probe(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct mmc_host *mmc; + struct pxamci_host *host = NULL; + struct resource *r; + int ret, irq; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + irq = platform_get_irq(pdev, 0); + if (!r || irq == NO_IRQ) + return -ENXIO; + + r = request_mem_region(r->start, SZ_4K, "PXAMCI"); + if (!r) + return -EBUSY; + + mmc = mmc_alloc_host(sizeof(struct pxamci_host), dev); + if (!mmc) { + ret = -ENOMEM; + goto out; + } + + mmc->ops = &pxamci_ops; + mmc->f_min = 312500; + mmc->f_max = 20000000; + mmc->ocr_avail = MMC_VDD_32_33; + + host = mmc_priv(mmc); + host->mmc = mmc; + host->dma = -1; + + host->sg_cpu = dma_alloc_coherent(dev, PAGE_SIZE, &host->sg_dma, GFP_KERNEL); + if (!host->sg_cpu) { + ret = -ENOMEM; + goto out; + } + + spin_lock_init(&host->lock); + host->res = r; + host->irq = irq; + host->imask = TXFIFO_WR_REQ|RXFIFO_RD_REQ|CLK_IS_OFF|STOP_CMD| + END_CMD_RES|PRG_DONE|DATA_TRAN_DONE; + + host->base = ioremap(r->start, SZ_4K); + if (!host->base) { + ret = -ENOMEM; + goto out; + } + + /* + * Ensure that the host controller is shut down, and setup + * with our defaults. + */ + pxamci_stop_clock(host); + writel(0, host->base + MMC_SPI); + writel(64, host->base + MMC_RESTO); + + pxa_gpio_mode(GPIO6_MMCCLK_MD); + pxa_gpio_mode(GPIO8_MMCCS0_MD); + pxa_set_cken(CKEN12_MMC, 1); + + host->dma = pxa_request_dma("PXAMCI", DMA_PRIO_LOW, pxamci_dma_irq, host); + if (host->dma < 0) { + ret = -EBUSY; + goto out; + } + + ret = request_irq(host->irq, pxamci_irq, 0, "PXAMCI", host); + if (ret) + goto out; + + dev_set_drvdata(dev, mmc); + + mmc_add_host(mmc); + + return 0; + + out: + if (host) { + if (host->dma >= 0) + pxa_free_dma(host->dma); + if (host->base) + iounmap(host->base); + if (host->sg_cpu) + dma_free_coherent(dev, PAGE_SIZE, host->sg_cpu, host->sg_dma); + } + if (mmc) + mmc_free_host(mmc); + release_resource(r); + return ret; +} + +static int pxamci_remove(struct device *dev) +{ + struct mmc_host *mmc = dev_get_drvdata(dev); + + dev_set_drvdata(dev, NULL); + + if (mmc) { + struct pxamci_host *host = mmc_priv(mmc); + + mmc_remove_host(mmc); + + pxamci_stop_clock(host); + writel(TXFIFO_WR_REQ|RXFIFO_RD_REQ|CLK_IS_OFF|STOP_CMD| + END_CMD_RES|PRG_DONE|DATA_TRAN_DONE, + host->base + MMC_I_MASK); + + free_irq(host->irq, host); + pxa_free_dma(host->dma); + iounmap(host->base); + dma_free_coherent(dev, PAGE_SIZE, host->sg_cpu, host->sg_dma); + + pxa_set_cken(CKEN12_MMC, 0); + + release_resource(host->res); + + mmc_free_host(mmc); + } + return 0; +} + +static int pxamci_suspend(struct device *dev, u32 state, u32 level) +{ + struct mmc_host *mmc = dev_get_drvdata(dev); + int ret = 0; + + if (mmc && level == SUSPEND_DISABLE) + ret = mmc_suspend_host(mmc, state); + + return ret; +} + +static int pxamci_resume(struct device *dev, u32 level) +{ + struct mmc_host *mmc = dev_get_drvdata(dev); + int ret = 0; + + if (mmc && level == RESUME_ENABLE) + ret = mmc_resume_host(mmc); + + return ret; +} + +static struct device_driver pxamci_driver = { + .name = "pxa2xx-mci", + .bus = &platform_bus_type, + .probe = pxamci_probe, + .remove = pxamci_remove, + .suspend = pxamci_suspend, + .resume = pxamci_resume, +}; + +static int __init pxamci_init(void) +{ + return driver_register(&pxamci_driver); +} + +static void __exit pxamci_exit(void) +{ + driver_unregister(&pxamci_driver); +} + +module_init(pxamci_init); +module_exit(pxamci_exit); + +MODULE_DESCRIPTION("PXA Multimedia Card Interface Driver"); +MODULE_LICENSE("GPL"); diff -Nru a/drivers/mmc/pxamci.h b/drivers/mmc/pxamci.h --- /dev/null Wed Dec 31 16:00:00 196900 +++ b/drivers/mmc/pxamci.h 2004-08-21 23:49:20 -07:00 @@ -0,0 +1,94 @@ +#undef MMC_STRPCL +#undef MMC_STAT +#undef MMC_CLKRT +#undef MMC_SPI +#undef MMC_CMDAT +#undef MMC_RESTO +#undef MMC_RDTO +#undef MMC_BLKLEN +#undef MMC_NOB +#undef MMC_PRTBUF +#undef MMC_I_MASK +#undef END_CMD_RES +#undef PRG_DONE +#undef DATA_TRAN_DONE +#undef MMC_I_REG +#undef MMC_CMD +#undef MMC_ARGH +#undef MMC_ARGL +#undef MMC_RES +#undef MMC_RXFIFO +#undef MMC_TXFIFO + +#define MMC_STRPCL 0x0000 +#define STOP_CLOCK (1 << 0) +#define START_CLOCK (2 << 0) + +#define MMC_STAT 0x0004 +#define STAT_END_CMD_RES (1 << 13) +#define STAT_PRG_DONE (1 << 12) +#define STAT_DATA_TRAN_DONE (1 << 11) +#define STAT_CLK_EN (1 << 8) +#define STAT_RECV_FIFO_FULL (1 << 7) +#define STAT_XMIT_FIFO_EMPTY (1 << 6) +#define STAT_RES_CRC_ERR (1 << 5) +#define STAT_SPI_READ_ERROR_TOKEN (1 << 4) +#define STAT_CRC_READ_ERROR (1 << 3) +#define STAT_CRC_WRITE_ERROR (1 << 2) +#define STAT_TIME_OUT_RESPONSE (1 << 1) +#define STAT_READ_TIME_OUT (1 << 0) + +#define MMC_CLKRT 0x0008 /* 3 bit */ + +#define MMC_SPI 0x000c +#define SPI_CS_ADDRESS (1 << 3) +#define SPI_CS_EN (1 << 2) +#define CRC_ON (1 << 1) +#define SPI_EN (1 << 0) + +#define MMC_CMDAT 0x0010 +#define CMDAT_DMAEN (1 << 7) +#define CMDAT_INIT (1 << 6) +#define CMDAT_BUSY (1 << 5) +#define CMDAT_STREAM (1 << 4) /* 1 = stream */ +#define CMDAT_WRITE (1 << 3) /* 1 = write */ +#define CMDAT_DATAEN (1 << 2) +#define CMDAT_RESP_NONE (0 << 0) +#define CMDAT_RESP_SHORT (1 << 0) +#define CMDAT_RESP_R2 (2 << 0) +#define CMDAT_RESP_R3 (3 << 0) + +#define MMC_RESTO 0x0014 /* 7 bit */ + +#define MMC_RDTO 0x0018 /* 16 bit */ + +#define MMC_BLKLEN 0x001c /* 10 bit */ + +#define MMC_NOB 0x0020 /* 16 bit */ + +#define MMC_PRTBUF 0x0024 +#define BUF_PART_FULL (1 << 0) + +#define MMC_I_MASK 0x0028 +#define TXFIFO_WR_REQ (1 << 6) +#define RXFIFO_RD_REQ (1 << 5) +#define CLK_IS_OFF (1 << 4) +#define STOP_CMD (1 << 3) +#define END_CMD_RES (1 << 2) +#define PRG_DONE (1 << 1) +#define DATA_TRAN_DONE (1 << 0) + +#define MMC_I_REG 0x002c +/* same as MMC_I_MASK */ + +#define MMC_CMD 0x0030 + +#define MMC_ARGH 0x0034 /* 16 bit */ + +#define MMC_ARGL 0x0038 /* 16 bit */ + +#define MMC_RES 0x003c /* 16 bit */ + +#define MMC_RXFIFO 0x0040 /* 8 bit */ + +#define MMC_TXFIFO 0x0044 /* 8 bit */ diff -Nru a/include/asm-arm/mach/mmc.h b/include/asm-arm/mach/mmc.h --- /dev/null Wed Dec 31 16:00:00 196900 +++ b/include/asm-arm/mach/mmc.h 2004-08-21 23:49:20 -07:00 @@ -0,0 +1,16 @@ +/* + * linux/include/asm-arm/mach/mmc.h + */ +#ifndef ASMARM_MACH_MMC_H +#define ASMARM_MACH_MMC_H + +#include + +struct mmc_platform_data { + unsigned int mclk; /* mmc base clock rate */ + unsigned int ocr_mask; /* available voltages */ + u32 (*translate_vdd)(struct device *, unsigned int); + unsigned int (*status)(struct device *); +}; + +#endif diff -Nru a/include/linux/mmc/card.h b/include/linux/mmc/card.h --- /dev/null Wed Dec 31 16:00:00 196900 +++ b/include/linux/mmc/card.h 2004-08-21 23:49:20 -07:00 @@ -0,0 +1,83 @@ +/* + * linux/include/linux/mmc/card.h + * + * 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. + * + * Card driver specific definitions. + */ +#ifndef LINUX_MMC_CARD_H +#define LINUX_MMC_CARD_H + +#include + +struct mmc_cid { + unsigned int manfid; + unsigned int serial; + char prod_name[8]; + unsigned char hwrev; + unsigned char fwrev; + unsigned char month; + unsigned char year; +}; + +struct mmc_csd { + unsigned char mmc_prot; + unsigned short cmdclass; + unsigned short tacc_clks; + unsigned int tacc_ns; + unsigned int max_dtr; + unsigned int read_blkbits; + unsigned int capacity; +}; + +struct mmc_host; + +/* + * MMC device + */ +struct mmc_card { + struct list_head node; /* node in hosts devices list */ + struct mmc_host *host; /* the host this device belongs to */ + struct device dev; /* the device */ + unsigned int rca; /* relative card address of device */ + unsigned int state; /* (our) card state */ +#define MMC_STATE_PRESENT (1<<0) +#define MMC_STATE_DEAD (1<<1) + struct mmc_cid cid; /* card identification */ + struct mmc_csd csd; /* card specific */ +}; + +#define mmc_card_dead(c) ((c)->state & MMC_STATE_DEAD) +#define mmc_card_present(c) ((c)->state & MMC_STATE_PRESENT) + +#define mmc_card_name(c) ((c)->cid.prod_name) +#define mmc_card_id(c) ((c)->dev.bus_id) + +#define mmc_list_to_card(l) container_of(l, struct mmc_card, node) +#define mmc_get_drvdata(c) dev_get_drvdata(&(c)->dev) +#define mmc_set_drvdata(c,d) dev_set_drvdata(&(c)->dev, d) + +/* + * MMC device driver (e.g., Flash card, I/O card...) + */ +struct mmc_driver { + struct device_driver drv; + int (*probe)(struct mmc_card *); + void (*remove)(struct mmc_card *); + int (*suspend)(struct mmc_card *, u32); + int (*resume)(struct mmc_card *); +}; + +extern int mmc_register_driver(struct mmc_driver *); +extern void mmc_unregister_driver(struct mmc_driver *); + +static inline int mmc_card_claim_host(struct mmc_card *card) +{ + return __mmc_claim_host(card->host, card); +} + +#define mmc_card_release_host(c) mmc_release_host((c)->host) + +#endif diff -Nru a/include/linux/mmc/host.h b/include/linux/mmc/host.h --- /dev/null Wed Dec 31 16:00:00 196900 +++ b/include/linux/mmc/host.h 2004-08-21 23:49:20 -07:00 @@ -0,0 +1,102 @@ +/* + * linux/include/linux/mmc/host.h + * + * 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. + * + * Host driver specific definitions. + */ +#ifndef LINUX_MMC_HOST_H +#define LINUX_MMC_HOST_H + +#include + +struct mmc_ios { + unsigned int clock; /* clock rate */ + unsigned short vdd; + +#define MMC_VDD_150 0 +#define MMC_VDD_155 1 +#define MMC_VDD_160 2 +#define MMC_VDD_165 3 +#define MMC_VDD_170 4 +#define MMC_VDD_180 5 +#define MMC_VDD_190 6 +#define MMC_VDD_200 7 +#define MMC_VDD_210 8 +#define MMC_VDD_220 9 +#define MMC_VDD_230 10 +#define MMC_VDD_240 11 +#define MMC_VDD_250 12 +#define MMC_VDD_260 13 +#define MMC_VDD_270 14 +#define MMC_VDD_280 15 +#define MMC_VDD_290 16 +#define MMC_VDD_300 17 +#define MMC_VDD_310 18 +#define MMC_VDD_320 19 +#define MMC_VDD_330 20 +#define MMC_VDD_340 21 +#define MMC_VDD_350 22 +#define MMC_VDD_360 23 + + unsigned char bus_mode; /* command output mode */ + +#define MMC_BUSMODE_OPENDRAIN 1 +#define MMC_BUSMODE_PUSHPULL 2 + + unsigned char power_mode; /* power supply mode */ + +#define MMC_POWER_OFF 0 +#define MMC_POWER_UP 1 +#define MMC_POWER_ON 2 +}; + +struct mmc_host_ops { + void (*request)(struct mmc_host *host, struct mmc_request *req); + void (*set_ios)(struct mmc_host *host, struct mmc_ios *ios); +}; + +struct mmc_card; +struct device; + +struct mmc_host { + struct device *dev; + struct mmc_host_ops *ops; + void *priv; + unsigned int f_min; + unsigned int f_max; + u32 ocr_avail; + char host_name[8]; + + /* private data */ + struct mmc_ios ios; /* current io bus settings */ + u32 ocr; /* the current OCR setting */ + + struct list_head cards; /* devices attached to this host */ + + wait_queue_head_t wq; + spinlock_t lock; /* card_busy lock */ + struct mmc_card *card_busy; /* the MMC card claiming host */ + struct mmc_card *card_selected; /* the selected MMC card */ + + struct work_struct detect; +}; + +extern struct mmc_host *mmc_alloc_host(int extra, struct device *); +extern int mmc_add_host(struct mmc_host *); +extern void mmc_remove_host(struct mmc_host *); +extern void mmc_free_host(struct mmc_host *); + +#define mmc_priv(x) ((void *)((x) + 1)) +#define mmc_dev(x) ((x)->dev) + +extern int mmc_suspend_host(struct mmc_host *, u32); +extern int mmc_resume_host(struct mmc_host *); + +extern void mmc_detect_change(struct mmc_host *); +extern void mmc_request_done(struct mmc_host *, struct mmc_request *); + +#endif + diff -Nru a/include/linux/mmc/mmc.h b/include/linux/mmc/mmc.h --- /dev/null Wed Dec 31 16:00:00 196900 +++ b/include/linux/mmc/mmc.h 2004-08-21 23:49:20 -07:00 @@ -0,0 +1,88 @@ +/* + * linux/include/linux/mmc/mmc.h + * + * 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 MMC_H +#define MMC_H + +#include +#include +#include + +struct request; +struct mmc_data; +struct mmc_request; + +struct mmc_command { + u32 opcode; + u32 arg; + u32 resp[4]; + unsigned int flags; /* expected response type */ +#define MMC_RSP_NONE (0 << 0) +#define MMC_RSP_SHORT (1 << 0) +#define MMC_RSP_LONG (2 << 0) +#define MMC_RSP_MASK (3 << 0) +#define MMC_RSP_CRC (1 << 3) /* expect valid crc */ +#define MMC_RSP_BUSY (1 << 4) /* card may send busy */ + + unsigned int retries; /* max number of retries */ + unsigned int error; /* command error */ + +#define MMC_ERR_NONE 0 +#define MMC_ERR_TIMEOUT 1 +#define MMC_ERR_BADCRC 2 +#define MMC_ERR_FIFO 3 +#define MMC_ERR_FAILED 4 +#define MMC_ERR_INVALID 5 + + struct mmc_data *data; /* data segment associated with cmd */ + struct mmc_request *mrq; /* assoicated request */ +}; + +struct mmc_data { + unsigned int timeout_ns; /* data timeout (in ns, max 80ms) */ + unsigned int timeout_clks; /* data timeout (in clocks) */ + unsigned int blksz_bits; /* data block size */ + unsigned int blocks; /* number of blocks */ + struct request *req; /* request structure */ + unsigned int error; /* data error */ + unsigned int flags; + +#define MMC_DATA_WRITE (1 << 8) +#define MMC_DATA_READ (1 << 9) +#define MMC_DATA_STREAM (1 << 10) + + unsigned int bytes_xfered; + + struct mmc_command *stop; /* stop command */ + struct mmc_request *mrq; /* assoicated request */ +}; + +struct mmc_request { + struct mmc_command *cmd; + struct mmc_data *data; + struct mmc_command *stop; + + void *done_data; /* completion data */ + void (*done)(struct mmc_request *);/* completion function */ +}; + +struct mmc_host; +struct mmc_card; + +extern int mmc_wait_for_req(struct mmc_host *, struct mmc_request *); +extern int mmc_wait_for_cmd(struct mmc_host *, struct mmc_command *, int); + +extern int __mmc_claim_host(struct mmc_host *host, struct mmc_card *card); + +static inline void mmc_claim_host(struct mmc_host *host) +{ + __mmc_claim_host(host, (struct mmc_card *)-1); +} + +extern void mmc_release_host(struct mmc_host *host); + +#endif diff -Nru a/include/linux/mmc/protocol.h b/include/linux/mmc/protocol.h --- /dev/null Wed Dec 31 16:00:00 196900 +++ b/include/linux/mmc/protocol.h 2004-08-21 23:49:20 -07:00 @@ -0,0 +1,203 @@ +/* + * Header for MultiMediaCard (MMC) + * + * Copyright 2002 Hewlett-Packard Company + * + * Use consistent with the GNU GPL is permitted, + * provided that this copyright notice is + * preserved in its entirety in all copies and derived works. + * + * HEWLETT-PACKARD COMPANY MAKES NO WARRANTIES, EXPRESSED OR IMPLIED, + * AS TO THE USEFULNESS OR CORRECTNESS OF THIS CODE OR ITS + * FITNESS FOR ANY PARTICULAR PURPOSE. + * + * Many thanks to Alessandro Rubini and Jonathan Corbet! + * + * Based strongly on code by: + * + * Author: Yong-iL Joh + * Date : $Date: 2002/06/18 12:37:30 $ + * + * Author: Andrew Christian + * 15 May 2002 + */ + +#ifndef MMC_MMC_PROTOCOL_H +#define MMC_MMC_PROTOCOL_H + +/* Standard MMC commands (3.1) type argument response */ + /* class 1 */ +#define MMC_GO_IDLE_STATE 0 /* bc */ +#define MMC_SEND_OP_COND 1 /* bcr [31:0] OCR R3 */ +#define MMC_ALL_SEND_CID 2 /* bcr R2 */ +#define MMC_SET_RELATIVE_ADDR 3 /* ac [31:16] RCA R1 */ +#define MMC_SET_DSR 4 /* bc [31:16] RCA */ +#define MMC_SELECT_CARD 7 /* ac [31:16] RCA R1 */ +#define MMC_SEND_CSD 9 /* ac [31:16] RCA R2 */ +#define MMC_SEND_CID 10 /* ac [31:16] RCA R2 */ +#define MMC_READ_DAT_UNTIL_STOP 11 /* adtc [31:0] dadr R1 */ +#define MMC_STOP_TRANSMISSION 12 /* ac R1b */ +#define MMC_SEND_STATUS 13 /* ac [31:16] RCA R1 */ +#define MMC_GO_INACTIVE_STATE 15 /* ac [31:16] RCA */ + + /* class 2 */ +#define MMC_SET_BLOCKLEN 16 /* ac [31:0] block len R1 */ +#define MMC_READ_SINGLE_BLOCK 17 /* adtc [31:0] data addr R1 */ +#define MMC_READ_MULTIPLE_BLOCK 18 /* adtc [31:0] data addr R1 */ + + /* class 3 */ +#define MMC_WRITE_DAT_UNTIL_STOP 20 /* adtc [31:0] data addr R1 */ + + /* class 4 */ +#define MMC_SET_BLOCK_COUNT 23 /* adtc [31:0] data addr R1 */ +#define MMC_WRITE_BLOCK 24 /* adtc [31:0] data addr R1 */ +#define MMC_WRITE_MULTIPLE_BLOCK 25 /* adtc R1 */ +#define MMC_PROGRAM_CID 26 /* adtc R1 */ +#define MMC_PROGRAM_CSD 27 /* adtc R1 */ + + /* class 6 */ +#define MMC_SET_WRITE_PROT 28 /* ac [31:0] data addr R1b */ +#define MMC_CLR_WRITE_PROT 29 /* ac [31:0] data addr R1b */ +#define MMC_SEND_WRITE_PROT 30 /* adtc [31:0] wpdata addr R1 */ + + /* class 5 */ +#define MMC_ERASE_GROUP_START 35 /* ac [31:0] data addr R1 */ +#define MMC_ERASE_GROUP_END 36 /* ac [31:0] data addr R1 */ +#define MMC_ERASE 37 /* ac R1b */ + + /* class 9 */ +#define MMC_FAST_IO 39 /* ac R4 */ +#define MMC_GO_IRQ_STATE 40 /* bcr R5 */ + + /* class 7 */ +#define MMC_LOCK_UNLOCK 42 /* adtc R1b */ + + /* class 8 */ +#define MMC_APP_CMD 55 /* ac [31:16] RCA R1 */ +#define MMC_GEN_CMD 56 /* adtc [0] RD/WR R1b */ + +/* + MMC status in R1 + Type + e : error bit + s : status bit + r : detected and set for the actual command response + x : detected and set during command execution. the host must poll + the card by sending status command in order to read these bits. + Clear condition + a : according to the card state + b : always related to the previous command. Reception of + a valid command will clear it (with a delay of one command) + c : clear by read + */ + +#define R1_OUT_OF_RANGE (1 << 31) /* er, c */ +#define R1_ADDRESS_ERROR (1 << 30) /* erx, c */ +#define R1_BLOCK_LEN_ERROR (1 << 29) /* er, c */ +#define R1_ERASE_SEQ_ERROR (1 << 28) /* er, c */ +#define R1_ERASE_PARAM (1 << 27) /* ex, c */ +#define R1_WP_VIOLATION (1 << 26) /* erx, c */ +#define R1_CARD_IS_LOCKED (1 << 25) /* sx, a */ +#define R1_LOCK_UNLOCK_FAILED (1 << 24) /* erx, c */ +#define R1_COM_CRC_ERROR (1 << 23) /* er, b */ +#define R1_ILLEGAL_COMMAND (1 << 22) /* er, b */ +#define R1_CARD_ECC_FAILED (1 << 21) /* ex, c */ +#define R1_CC_ERROR (1 << 20) /* erx, c */ +#define R1_ERROR (1 << 19) /* erx, c */ +#define R1_UNDERRUN (1 << 18) /* ex, c */ +#define R1_OVERRUN (1 << 17) /* ex, c */ +#define R1_CID_CSD_OVERWRITE (1 << 16) /* erx, c, CID/CSD overwrite */ +#define R1_WP_ERASE_SKIP (1 << 15) /* sx, c */ +#define R1_CARD_ECC_DISABLED (1 << 14) /* sx, a */ +#define R1_ERASE_RESET (1 << 13) /* sr, c */ +#define R1_STATUS(x) (x & 0xFFFFE000) +#define R1_CURRENT_STATE(x) ((x & 0x00001E00) >> 9) /* sx, b (4 bits) */ +#define R1_READY_FOR_DATA (1 << 8) /* sx, a */ +#define R1_APP_CMD (1 << 7) /* sr, c */ + +/* These are unpacked versions of the actual responses */ + +struct _mmc_csd { + u8 csd_structure; + u8 spec_vers; + u8 taac; + u8 nsac; + u8 tran_speed; + u16 ccc; + u8 read_bl_len; + u8 read_bl_partial; + u8 write_blk_misalign; + u8 read_blk_misalign; + u8 dsr_imp; + u16 c_size; + u8 vdd_r_curr_min; + u8 vdd_r_curr_max; + u8 vdd_w_curr_min; + u8 vdd_w_curr_max; + u8 c_size_mult; + union { + struct { /* MMC system specification version 3.1 */ + u8 erase_grp_size; + u8 erase_grp_mult; + } v31; + struct { /* MMC system specification version 2.2 */ + u8 sector_size; + u8 erase_grp_size; + } v22; + } erase; + u8 wp_grp_size; + u8 wp_grp_enable; + u8 default_ecc; + u8 r2w_factor; + u8 write_bl_len; + u8 write_bl_partial; + u8 file_format_grp; + u8 copy; + u8 perm_write_protect; + u8 tmp_write_protect; + u8 file_format; + u8 ecc; +}; + +#define MMC_VDD_145_150 0x00000001 /* VDD voltage 1.45 - 1.50 */ +#define MMC_VDD_150_155 0x00000002 /* VDD voltage 1.50 - 1.55 */ +#define MMC_VDD_155_160 0x00000004 /* VDD voltage 1.55 - 1.60 */ +#define MMC_VDD_160_165 0x00000008 /* VDD voltage 1.60 - 1.65 */ +#define MMC_VDD_165_170 0x00000010 /* VDD voltage 1.65 - 1.70 */ +#define MMC_VDD_17_18 0x00000020 /* VDD voltage 1.7 - 1.8 */ +#define MMC_VDD_18_19 0x00000040 /* VDD voltage 1.8 - 1.9 */ +#define MMC_VDD_19_20 0x00000080 /* VDD voltage 1.9 - 2.0 */ +#define MMC_VDD_20_21 0x00000100 /* VDD voltage 2.0 ~ 2.1 */ +#define MMC_VDD_21_22 0x00000200 /* VDD voltage 2.1 ~ 2.2 */ +#define MMC_VDD_22_23 0x00000400 /* VDD voltage 2.2 ~ 2.3 */ +#define MMC_VDD_23_24 0x00000800 /* VDD voltage 2.3 ~ 2.4 */ +#define MMC_VDD_24_25 0x00001000 /* VDD voltage 2.4 ~ 2.5 */ +#define MMC_VDD_25_26 0x00002000 /* VDD voltage 2.5 ~ 2.6 */ +#define MMC_VDD_26_27 0x00004000 /* VDD voltage 2.6 ~ 2.7 */ +#define MMC_VDD_27_28 0x00008000 /* VDD voltage 2.7 ~ 2.8 */ +#define MMC_VDD_28_29 0x00010000 /* VDD voltage 2.8 ~ 2.9 */ +#define MMC_VDD_29_30 0x00020000 /* VDD voltage 2.9 ~ 3.0 */ +#define MMC_VDD_30_31 0x00040000 /* VDD voltage 3.0 ~ 3.1 */ +#define MMC_VDD_31_32 0x00080000 /* VDD voltage 3.1 ~ 3.2 */ +#define MMC_VDD_32_33 0x00100000 /* VDD voltage 3.2 ~ 3.3 */ +#define MMC_VDD_33_34 0x00200000 /* VDD voltage 3.3 ~ 3.4 */ +#define MMC_VDD_34_35 0x00400000 /* VDD voltage 3.4 ~ 3.5 */ +#define MMC_VDD_35_36 0x00800000 /* VDD voltage 3.5 ~ 3.6 */ +#define MMC_CARD_BUSY 0x80000000 /* Card Power up status bit */ + + +/* + * CSD field definitions + */ + +#define CSD_STRUCT_VER_1_0 0 /* Valid for system specification 1.0 - 1.2 */ +#define CSD_STRUCT_VER_1_1 1 /* Valid for system specification 1.4 - 2.2 */ +#define CSD_STRUCT_VER_1_2 2 /* Valid for system specification 3.1 */ + +#define CSD_SPEC_VER_0 0 /* Implements system specification 1.0 - 1.2 */ +#define CSD_SPEC_VER_1 1 /* Implements system specification 1.4 */ +#define CSD_SPEC_VER_2 2 /* Implements system specification 2.0 - 2.2 */ +#define CSD_SPEC_VER_3 3 /* Implements system specification 3.1 */ + +#endif /* MMC_MMC_PROTOCOL_H */ +