* [RFC] 1/4 MMC layer
@ 2004-04-29 12:48 Russell King
2004-04-29 12:49 ` [RFC] 2/4 MMC layer: configuration + makefiles Russell King
2004-05-02 14:52 ` [RFC] 1/4 MMC layer Russell King
0 siblings, 2 replies; 8+ messages in thread
From: Russell King @ 2004-04-29 12:48 UTC (permalink / raw)
To: Linux Kernel List
Hi,
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.
The only area of concern is the MMC block device support, which,
since the block layer does not really support hotplug, may be
iffy. I believe Al Viro has some patches to make the block layer
more "hotplug friendly."
Note that MMC is an hotpluggable medium, so we can't just ignore
the issue and hope it goes away.
diff -urpN orig/drivers/mmc/Kconfig linux/drivers/mmc/Kconfig
--- orig/drivers/mmc/Kconfig Thu Jan 1 01:00:00 1970
+++ linux/drivers/mmc/Kconfig Fri Oct 17 17:41:27 2003
@@ -0,0 +1,32 @@
+#
+# 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.
+
+endmenu
diff -urpN orig/drivers/mmc/Makefile linux/drivers/mmc/Makefile
--- orig/drivers/mmc/Makefile Thu Jan 1 01:00:00 1970
+++ linux/drivers/mmc/Makefile Mon Jul 7 21:52:01 2003
@@ -0,0 +1,19 @@
+#
+# 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
+#
+
+mmc_core-y := mmc.o mmc_queue.o mmc_sysfs.o
diff -urpN orig/drivers/mmc/mmc.c linux/drivers/mmc/mmc.c
--- orig/drivers/mmc/mmc.c Thu Jan 1 01:00:00 1970
+++ linux/drivers/mmc/mmc.c Mon Jan 12 13:12:48 2004
@@ -0,0 +1,823 @@
+/*
+ * 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 <linux/config.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/completion.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+
+#include <linux/mmc/card.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/protocol.h>
+
+#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
+ * @cmd: MMC command which completed
+ * @err: MMC error code
+ *
+ * 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 *req)
+{
+ struct mmc_command *cmd = req->cmd;
+ int err = req->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, req);
+ } else if (req->done) {
+ req->done(req);
+ }
+}
+
+EXPORT_SYMBOL(mmc_request_done);
+
+/**
+ * mmc_start_request - start a command on a host
+ * @host: MMC host to start command on
+ * @cmd: MMC command 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 *req)
+{
+ DBG("MMC: starting cmd %02x arg %08x flags %08x\n",
+ req->cmd->opcode, req->cmd->arg, req->cmd->flags);
+
+ WARN_ON(host->card_busy == NULL);
+
+ req->cmd->error = 0;
+ req->cmd->req = req;
+ if (req->data) {
+ req->cmd->data = req->data;
+ req->data->error = 0;
+ req->data->req = req;
+ if (req->stop) {
+ req->data->stop = req->stop;
+ req->stop->error = 0;
+ req->stop->req = req;
+ }
+ }
+ host->ops->request(host, req);
+}
+
+EXPORT_SYMBOL(mmc_start_request);
+
+static void mmc_wait_done(struct mmc_request *req)
+{
+ complete(req->done_data);
+}
+
+int mmc_wait_for_req(struct mmc_host *host, struct mmc_request *req)
+{
+ DECLARE_COMPLETION(complete);
+
+ req->done_data = &complete;
+ req->done = mmc_wait_done;
+
+ mmc_start_request(host, req);
+
+ 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 req;
+
+ BUG_ON(host->card_busy == NULL);
+
+ memset(&req, 0, sizeof(struct mmc_request));
+
+ memset(cmd->resp, 0, sizeof(cmd->resp));
+ cmd->retries = retries;
+
+ req.cmd = cmd;
+ cmd->data = NULL;
+
+ mmc_wait_for_req(host, &req);
+
+ 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 -urpN orig/drivers/mmc/mmc.h linux/drivers/mmc/mmc.h
--- orig/drivers/mmc/mmc.h Thu Jan 1 01:00:00 1970
+++ linux/drivers/mmc/mmc.h Mon Jan 12 13:13:03 2004
@@ -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 -urpN orig/drivers/mmc/mmc_block.c linux/drivers/mmc/mmc_block.c
--- orig/drivers/mmc/mmc_block.c Thu Jan 1 01:00:00 1970
+++ linux/drivers/mmc/mmc_block.c Mon Jan 12 13:13:43 2004
@@ -0,0 +1,498 @@
+/*
+ * 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 <linux/moduleparam.h>
+#include <linux/module.h>
+#include <linux/init.h>
+
+#include <linux/sched.h>
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/errno.h>
+#include <linux/hdreg.h>
+#include <linux/kdev_t.h>
+#include <linux/blkdev.h>
+#include <linux/devfs_fs_kernel.h>
+
+#include <linux/mmc/card.h>
+#include <linux/mmc/protocol.h>
+
+#include <asm/system.h>
+#include <asm/uaccess.h>
+
+#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;
+ unsigned int suspended;
+};
+
+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 req;
+ 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;
+
+ /*
+ * 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);
+ goto kill;
+ }
+
+ if (md->suspended) {
+ blk_plug_device(md->queue.queue);
+ goto defer;
+ }
+
+ /*
+ * Check for excessive requests.
+ */
+ if (req->sector + req->nr_sectors > get_capacity(req->rq_disk)) {
+ printk(KERN_ERR "%s: bad request size\n",
+ req->rq_disk->disk_name);
+ goto kill;
+ }
+
+ return BLKPREP_OK;
+
+ defer:
+ return BLKPREP_DEFER;
+ kill:
+ return BLKPREP_KILL;
+}
+
+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 err, sz = 0;
+
+ err = mmc_card_claim_host(card);
+ if (err)
+ goto cmd_err;
+
+ do {
+ struct mmc_blk_request rq;
+ struct mmc_command cmd;
+
+ memset(&rq, 0, sizeof(struct mmc_blk_request));
+ rq.req.cmd = &rq.cmd;
+ rq.req.data = &rq.data;
+
+ rq.cmd.arg = req->sector << 9;
+ rq.cmd.flags = MMC_RSP_SHORT | MMC_RSP_CRC;
+ rq.data.rq = req;
+ rq.data.timeout_ns = card->csd.tacc_ns * 10;
+ rq.data.timeout_clks = card->csd.tacc_clks * 10;
+ rq.data.blksz_bits = md->block_bits;
+ rq.data.blocks = req->current_nr_sectors >> (md->block_bits - 9);
+ rq.stop.opcode = MMC_STOP_TRANSMISSION;
+ rq.stop.arg = 0;
+ rq.stop.flags = MMC_RSP_SHORT | MMC_RSP_CRC | MMC_RSP_BUSY;
+
+ if (rq_data_dir(req) == READ) {
+ rq.cmd.opcode = rq.data.blocks > 1 ? MMC_READ_MULTIPLE_BLOCK : MMC_READ_SINGLE_BLOCK;
+ rq.data.flags |= MMC_DATA_READ;
+ } else {
+ rq.cmd.opcode = MMC_WRITE_BLOCK;
+ rq.cmd.flags |= MMC_RSP_BUSY;
+ rq.data.flags |= MMC_DATA_WRITE;
+ rq.data.blocks = 1;
+ }
+ rq.req.stop = rq.data.blocks > 1 ? &rq.stop : NULL;
+
+ mmc_wait_for_req(card->host, &rq.req);
+ if (rq.cmd.error) {
+ err = rq.cmd.error;
+ printk(KERN_ERR "%s: error %d sending read/write command\n",
+ req->rq_disk->disk_name, err);
+ goto cmd_err;
+ }
+
+ if (rq_data_dir(req) == READ) {
+ sz = rq.data.bytes_xfered;
+ } else {
+ sz = 0;
+ }
+
+ if (rq.data.error) {
+ err = rq.data.error;
+ printk(KERN_ERR "%s: error %d transferring data\n",
+ req->rq_disk->disk_name, err);
+ goto cmd_err;
+ }
+
+ if (rq.stop.error) {
+ err = rq.stop.error;
+ printk(KERN_ERR "%s: error %d sending stop command\n",
+ req->rq_disk->disk_name, err);
+ goto cmd_err;
+ }
+
+ do {
+ 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]);
+ err = mmc_decode_status(cmd.resp);
+ if (err)
+ goto cmd_err;
+#endif
+
+ sz = rq.data.bytes_xfered;
+ } while (end_that_request_chunk(req, 1, sz));
+
+ mmc_card_release_host(card);
+
+ return 1;
+
+ cmd_err:
+ mmc_card_release_host(card);
+
+ end_that_request_chunk(req, 1, sz);
+ req->errors = err;
+
+ 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 -urpN orig/drivers/mmc/mmc_queue.c linux/drivers/mmc/mmc_queue.c
--- orig/drivers/mmc/mmc_queue.c Thu Jan 1 01:00:00 1970
+++ linux/drivers/mmc/mmc_queue.c Thu Jan 1 15:09:33 2004
@@ -0,0 +1,175 @@
+/*
+ * 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 <linux/module.h>
+#include <linux/blkdev.h>
+
+#include <linux/mmc/card.h>
+#include <linux/mmc/host.h>
+#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);
+ int ret;
+
+ /*
+ * 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);
+ spin_lock_irq(q->queue_lock);
+ do {
+ struct request *req = NULL;
+
+ 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);
+
+ ret = mq->issue_fn(mq, req);
+
+ spin_lock_irq(q->queue_lock);
+ end_request(req, ret);
+ } 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 && !blk_queue_plugged(q))
+ 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)
+ 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 -urpN orig/drivers/mmc/mmc_queue.h linux/drivers/mmc/mmc_queue.h
--- orig/drivers/mmc/mmc_queue.h Thu Jan 1 01:00:00 1970
+++ linux/drivers/mmc/mmc_queue.h Thu Aug 14 18:11:18 2003
@@ -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 -urpN orig/drivers/mmc/mmc_sysfs.c linux/drivers/mmc/mmc_sysfs.c
--- orig/drivers/mmc/mmc_sysfs.c Thu Jan 1 01:00:00 1970
+++ linux/drivers/mmc/mmc_sysfs.c Thu Jan 1 19:50:26 2004
@@ -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 <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+
+#include <linux/mmc/card.h>
+#include <linux/mmc/host.h>
+
+#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 -urpN orig/include/linux/mmc/card.h linux/include/linux/mmc/card.h
--- orig/include/linux/mmc/card.h Thu Jan 1 01:00:00 1970
+++ linux/include/linux/mmc/card.h Fri Aug 29 20:11:30 2003
@@ -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 <linux/mmc/mmc.h>
+
+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 -urpN orig/include/linux/mmc/host.h linux/include/linux/mmc/host.h
--- orig/include/linux/mmc/host.h Thu Jan 1 01:00:00 1970
+++ linux/include/linux/mmc/host.h Thu Jan 1 19:49:23 2004
@@ -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 <linux/mmc/mmc.h>
+
+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 -urpN orig/include/linux/mmc/mmc.h linux/include/linux/mmc/mmc.h
--- orig/include/linux/mmc/mmc.h Thu Jan 1 01:00:00 1970
+++ linux/include/linux/mmc/mmc.h Fri Jul 11 15:19:05 2003
@@ -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 <linux/list.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+
+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 *req; /* 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 *rq; /* 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 *req; /* 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 -urpN orig/include/linux/mmc/protocol.h linux/include/linux/mmc/protocol.h
--- orig/include/linux/mmc/protocol.h Thu Jan 1 01:00:00 1970
+++ linux/include/linux/mmc/protocol.h Mon Jul 7 21:21:21 2003
@@ -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 <tolkien@mizi.com>
+ * 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 <Complex> 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 */
+
--
Russell King
Linux kernel 2.6 ARM Linux - http://www.arm.linux.org.uk/
maintainer of: 2.6 PCMCIA - http://pcmcia.arm.linux.org.uk/
2.6 Serial core
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [RFC] 2/4 MMC layer: configuration + makefiles
2004-04-29 12:48 [RFC] 1/4 MMC layer Russell King
@ 2004-04-29 12:49 ` Russell King
2004-04-29 12:50 ` [RFC] 3/4 MMC layer: ARM MMCI driver Russell King
2004-05-02 14:52 ` [RFC] 1/4 MMC layer Russell King
1 sibling, 1 reply; 8+ messages in thread
From: Russell King @ 2004-04-29 12:49 UTC (permalink / raw)
To: Linux Kernel List
Add changes to Kconfig / Makefiles to support MMC subsystem.
--- orig/arch/arm/Kconfig Wed Apr 28 11:52:31 2004
+++ linux/arch/arm/Kconfig Thu Apr 29 13:32:30 2004
@@ -631,6 +631,8 @@ source "drivers/misc/Kconfig"
source "drivers/usb/Kconfig"
+source "drivers/mmc/Kconfig"
+
menu "Kernel hacking"
--- orig/drivers/Kconfig Thu Mar 4 13:25:22 2004
+++ linux/drivers/Kconfig Thu Apr 29 13:29:42 2004
@@ -52,4 +52,6 @@ source "sound/Kconfig"
source "drivers/usb/Kconfig"
+source "drivers/mmc/Kconfig"
+
endmenu
--- orig/drivers/Makefile Sat Mar 20 09:22:27 2004
+++ linux/drivers/Makefile Thu Apr 29 13:36:36 2004
@@ -49,4 +49,5 @@ obj-$(CONFIG_ISDN) += isdn/
obj-$(CONFIG_MCA) += mca/
obj-$(CONFIG_EISA) += eisa/
obj-$(CONFIG_CPU_FREQ) += cpufreq/
+obj-$(CONFIG_MMC) += mmc/
obj-y += firmware/
--
Russell King
Linux kernel 2.6 ARM Linux - http://www.arm.linux.org.uk/
maintainer of: 2.6 PCMCIA - http://pcmcia.arm.linux.org.uk/
2.6 Serial core
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [RFC] 3/4 MMC layer: ARM MMCI driver
2004-04-29 12:49 ` [RFC] 2/4 MMC layer: configuration + makefiles Russell King
@ 2004-04-29 12:50 ` Russell King
2004-04-29 12:51 ` [RFC] 4/4 MMC layer: PXA MCI driver Russell King
0 siblings, 1 reply; 8+ messages in thread
From: Russell King @ 2004-04-29 12:50 UTC (permalink / raw)
To: Linux Kernel List
This patch adds initial support for the ARM MMCI Primecell - one of
the drivers for a MMC interface.
diff -urpN orig/drivers/mmc/Kconfig linux/drivers/mmc/Kconfig
--- orig/drivers/mmc/Kconfig Fri Oct 17 17:41:27 2003
+++ linux/drivers/mmc/Kconfig Fri Oct 17 17:41:28 2003
@@ -29,4 +29,14 @@
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.
+
endmenu
diff -urpN orig/drivers/mmc/Makefile linux/drivers/mmc/Makefile
--- orig/drivers/mmc/Makefile Mon Jul 7 21:52:01 2003
+++ linux/drivers/mmc/Makefile Mon Jul 7 21:52:02 2003
@@ -15,5 +15,6 @@
#
# Host drivers
#
+obj-$(CONFIG_MMC_ARMMMCI) += mmci.o
mmc_core-y := mmc.o mmc_queue.o mmc_sysfs.o
diff -urpN orig/drivers/mmc/mmci.c linux/drivers/mmc/mmci.c
--- orig/drivers/mmc/mmci.c Thu Jan 1 01:00:00 1970
+++ linux/drivers/mmc/mmci.c Thu Mar 4 23:38:29 2004
@@ -0,0 +1,523 @@
+/*
+ * 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 <linux/config.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/blkdev.h>
+#include <linux/delay.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/protocol.h>
+
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/hardware/amba.h>
+#include <asm/mach/mmc.h>
+
+#include "mmci.h"
+
+#define DRIVER_NAME "mmci-pl18x"
+
+#ifdef CONFIG_MMC_DEBUG
+#define DBG(x...) pr_debug(x)
+#else
+#define DBG(x...) do { } while (0)
+#endif
+
+static unsigned int fmax = 515633;
+
+static void
+mmci_request_end(struct mmci_host *host, struct mmc_request *req)
+{
+ writel(0, host->base + MMCICOMMAND);
+ host->req = NULL;
+ host->cmd = NULL;
+ host->data = NULL;
+ host->buffer = NULL;
+
+ if (req->data)
+ req->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, req);
+ spin_lock(&host->lock);
+}
+
+static void mmci_start_data(struct mmci_host *host, struct mmc_data *data)
+{
+ unsigned int datactrl;
+
+ DBG("%s: data: blksz %04x blks %04x flags %08x\n",
+ host->mmc->host_name, 1 << data->blksz_bits, data->blocks,
+ data->flags);
+
+ datactrl = MCI_DPSM_ENABLE | data->blksz_bits << 4;
+
+ if (data->flags & MMC_DATA_READ)
+ datactrl |= MCI_DPSM_DIRECTION;
+
+ host->data = data;
+ host->buffer = data->rq->buffer;
+ host->size = data->blocks << data->blksz_bits;
+ host->data_xfered = 0;
+
+ writel(0x800000, host->base + MMCIDATATIMER);
+ writel(host->size, host->base + MMCIDATALENGTH);
+ writel(datactrl, host->base + MMCIDATACTRL);
+}
+
+static void
+mmci_start_command(struct mmci_host *host, struct mmc_command *cmd, u32 c)
+{
+ DBG("%s: cmd: op %02x arg %08x flags %08x\n",
+ host->mmc->host_name, cmd->opcode, cmd->arg, cmd->flags);
+
+ if (readl(host->base + MMCICOMMAND) & MCI_CPSM_ENABLE) {
+ writel(0, host->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, host->base + MMCIARGUMENT);
+ writel(c, host->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->req);
+ } else /*if (readl(host->base + MMCIDATACNT) > 6)*/ {
+ mmci_start_command(host, data->stop, 0);
+ }
+ }
+}
+
+static void
+mmci_cmd_irq(struct mmci_host *host, struct mmc_command *cmd,
+ unsigned int status)
+{
+ host->cmd = NULL;
+
+ cmd->resp[0] = readl(host->base + MMCIRESPONSE0);
+ cmd->resp[1] = readl(host->base + MMCIRESPONSE1);
+ cmd->resp[2] = readl(host->base + MMCIRESPONSE2);
+ cmd->resp[3] = readl(host->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->req);
+ } else if (!(cmd->data->flags & MMC_DATA_READ)) {
+ mmci_start_data(host, cmd->data);
+ }
+}
+
+static irqreturn_t mmci_pio_irq(int irq, void *dev_id, struct pt_regs *regs)
+{
+ struct mmci_host *host = dev_id;
+ u32 status;
+ int ret = 0;
+
+ do {
+ status = readl(host->base + MMCISTATUS);
+
+ if (!(status & (MCI_RXDATAAVLBL|MCI_RXFIFOHALFFULL|
+ MCI_TXFIFOEMPTY|MCI_TXFIFOHALFEMPTY)))
+ break;
+
+ DBG("%s: irq1 %08x\n", host->mmc->host_name, status);
+
+ if (status & (MCI_RXDATAAVLBL|MCI_RXFIFOHALFFULL)) {
+ int count = host->size - (readl(host->base + MMCIFIFOCNT) << 2);
+ if (count < 0)
+ count = 0;
+ if (count && host->buffer) {
+ readsl(host->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(host->base + MMCIFIFO);
+ }
+ }
+ if (status & (MCI_TXFIFOEMPTY|MCI_TXFIFOHALFEMPTY)) {
+ int count = host->size;
+ if (count > MCI_FIFOHALFSIZE)
+ count = MCI_FIFOHALFSIZE;
+ if (count && host->buffer) {
+ writesl(host->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: ran out of source data\n");
+ }
+ }
+ }
+ ret = 1;
+ } while (status);
+
+ return IRQ_RETVAL(ret);
+}
+
+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("%s: irq0 %08x\n", host->mmc->host_name, status);
+
+ data = host->data;
+ if (status & (MCI_DATACRCFAIL|MCI_DATATIMEOUT|MCI_TXUNDERRUN|
+ MCI_RXOVERRUN|MCI_DATAEND|MCI_DATABLOCKEND))
+ 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 *req)
+{
+ struct mmci_host *host = mmc_priv(mmc);
+
+ WARN_ON(host->req != NULL);
+
+ spin_lock_irq(&host->lock);
+
+ host->req = req;
+
+ if (req->data && req->data->flags & MMC_DATA_READ)
+ mmci_start_data(host, req->data);
+
+ mmci_start_command(host, req->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("%s: set_ios: clock %dHz busmode %d powermode %d Vdd %d.%02d\n",
+ mmc->host_name, ios->clock, ios->bus_mode, ios->power_mode,
+ ios->vdd / 100, ios->vdd % 100);
+
+ if (ios->clock) {
+ clk = host->mclk / (2 * ios->clock) - 1;
+ if (clk > 256)
+ clk = 255;
+ 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 = NULL;
+ struct mmc_host *mmc;
+ int ret;
+
+ /* must have platform data */
+ if (!plat)
+ return -EINVAL;
+
+ ret = amba_request_regions(dev, DRIVER_NAME);
+ if (ret)
+ return ret;
+
+ mmc = mmc_alloc_host(sizeof(struct mmci_host), &dev->dev);
+ if (!mmc) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ mmc->ops = &mmci_ops;
+ mmc->f_min = (plat->mclk + 511) / 512;
+ mmc->f_max = max(plat->mclk / 2, fmax);
+ mmc->ocr_avail = plat->ocr_mask;
+
+ host = mmc_priv(mmc);
+ host->plat = plat;
+ host->mclk = plat->mclk;
+ host->mmc = mmc;
+ host->base = ioremap(dev->res.start, SZ_4K);
+ if (!host->base) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ 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 out;
+
+ ret = request_irq(dev->irq[1], mmci_pio_irq, SA_SHIRQ, DRIVER_NAME " (pio)", host);
+ if (ret) {
+ free_irq(dev->irq[0], host);
+ goto out;
+ }
+
+ writel(MCI_IRQENABLE, host->base + MMCIMASK0);
+ writel(MCI_TXFIFOHALFEMPTYMASK|MCI_RXFIFOHALFFULLMASK, host->base + MMCIMASK1);
+
+ 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;
+
+ out:
+ if (host && host->base)
+ iounmap(host->base);
+ if (mmc)
+ mmc_free_host(mmc);
+ amba_release_regions(dev);
+ 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);
+
+ 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 -urpN orig/drivers/mmc/mmci.h linux/drivers/mmc/mmci.h
--- orig/drivers/mmc/mmci.h Thu Jan 1 01:00:00 1970
+++ linux/drivers/mmc/mmci.h Wed Dec 31 22:58:09 2003
@@ -0,0 +1,149 @@
+/*
+ * 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_PWRSAVE (1 << 9)
+#define MCI_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 mmci_host {
+ void *base;
+ struct mmc_request *req;
+ struct mmc_command *cmd;
+ struct mmc_data *data;
+ struct mmc_host *mmc;
+
+ unsigned int data_xfered;
+
+ spinlock_t lock;
+
+ unsigned int mclk;
+ u32 pwr;
+ struct mmc_platform_data *plat;
+
+ struct timer_list timer;
+ unsigned int oldstat;
+
+ /* pio stuff */
+ void *buffer;
+ unsigned int size;
+
+ /* dma stuff */
+// struct scatterlist *sg_list;
+// int sg_len;
+// int sg_dir;
+};
+
+#define to_mmci_host(mmc) container_of(mmc, struct mmci_host, mmc)
--- orig/include/asm-arm/mach/mmc.h Sat Apr 26 08:56:46 1997
+++ linux/include/asm-arm/mach/mmc.h Mon Dec 29 17:19:00 2003
@@ -0,0 +1,16 @@
+/*
+ * linux/include/asm-arm/mach/mmc.h
+ */
+#ifndef ASMARM_MACH_MMC_H
+#define ASMARM_MACH_MMC_H
+
+#include <linux/mmc/protocol.h>
+
+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
--
Russell King
Linux kernel 2.6 ARM Linux - http://www.arm.linux.org.uk/
maintainer of: 2.6 PCMCIA - http://pcmcia.arm.linux.org.uk/
2.6 Serial core
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [RFC] 4/4 MMC layer: PXA MCI driver
2004-04-29 12:50 ` [RFC] 3/4 MMC layer: ARM MMCI driver Russell King
@ 2004-04-29 12:51 ` Russell King
0 siblings, 0 replies; 8+ messages in thread
From: Russell King @ 2004-04-29 12:51 UTC (permalink / raw)
To: Linux Kernel List
This patch adds support for the Intel PXA MCI interface.
diff -urpN orig/drivers/mmc/Kconfig linux/drivers/mmc/Kconfig
--- orig/drivers/mmc/Kconfig Fri Oct 17 17:41:28 2003
+++ linux/drivers/mmc/Kconfig Fri Oct 17 17:41:29 2003
@@ -39,4 +39,14 @@
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 -urpN orig/drivers/mmc/Makefile linux/drivers/mmc/Makefile
--- orig/drivers/mmc/Makefile Mon Jul 7 21:52:02 2003
+++ linux/drivers/mmc/Makefile Mon Jul 7 21:52:03 2003
@@ -16,5 +16,6 @@
# 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 -urpN orig/drivers/mmc/pxamci.c linux/drivers/mmc/pxamci.c
--- orig/drivers/mmc/pxamci.c Thu Jan 1 01:00:00 1970
+++ linux/drivers/mmc/pxamci.c Mon Dec 29 20:50:28 2003
@@ -0,0 +1,590 @@
+/*
+ * 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. Yuck!
+ *
+ * 1 and 3 byte data transfers not supported
+ * max block length up to 1023
+ */
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/blkdev.h>
+#include <linux/dma-mapping.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/protocol.h>
+
+#include <asm/dma.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/sizes.h>
+
+#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 *req;
+ 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 flags;
+ unsigned int v;
+
+ writel(STOP_CLOCK, host->base + MMC_STRPCL);
+
+ /*
+ * Wait for the "clock has stopped" interrupt.
+ * We need to unmask the interrupt to receive
+ * the notification. Sigh.
+ */
+ spin_lock_irqsave(&host->lock, flags);
+ writel(host->imask & ~CLK_IS_OFF, host->base + MMC_I_MASK);
+ do {
+ v = readl(host->base + MMC_I_REG);
+ } while (!(v & CLK_IS_OFF));
+ writel(host->imask, host->base + MMC_I_MASK);
+ spin_unlock_irqrestore(&host->lock, flags);
+ }
+}
+
+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->rq->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 *req)
+{
+ DBG("PXAMCI: request done\n");
+ host->req = NULL;
+ host->cmd = NULL;
+ host->data = NULL;
+ mmc_request_done(host->mmc, req);
+}
+
+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->req);
+ }
+
+ 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;
+
+ data->bytes_xfered = (data->blocks - readl(host->base + MMC_NOB))
+ << data->blksz_bits;
+
+ pxamci_disable_irq(host, DATA_TRAN_DONE);
+
+ host->data = NULL;
+ if (host->req->stop && data->error == MMC_ERR_NONE) {
+ pxamci_stop_clock(host);
+ pxamci_start_cmd(host, host->req->stop, 0);
+ } else {
+ pxamci_finish_request(host, host->req);
+ }
+
+ 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 *req)
+{
+ struct pxamci_host *host = mmc_priv(mmc);
+ unsigned int cmdat;
+
+ WARN_ON(host->req != NULL);
+
+ host->req = req;
+
+ pxamci_stop_clock(host);
+
+ cmdat = host->cmdat;
+ host->cmdat &= ~CMDAT_INIT;
+
+ if (req->data) {
+ pxamci_setup_data(host, req->data);
+
+ cmdat &= ~CMDAT_BUSY;
+ cmdat |= CMDAT_DATAEN | CMDAT_DMAEN;
+ if (req->data->flags & MMC_DATA_WRITE)
+ cmdat |= CMDAT_WRITE;
+
+ if (req->data->flags & MMC_DATA_STREAM)
+ cmdat |= CMDAT_STREAM;
+ }
+
+ pxamci_start_cmd(host, req->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 struct resource *platform_device_resource(struct platform_device *dev, unsigned int mask, int nr)
+{
+ int i;
+
+ for (i = 0; i < dev->num_resources; i++)
+ if (dev->resource[i].flags == mask && nr-- == 0)
+ return &dev->resource[i];
+ return NULL;
+}
+
+static int platform_device_irq(struct platform_device *dev, int nr)
+{
+ int i;
+
+ for (i = 0; i < dev->num_resources; i++)
+ if (dev->resource[i].flags == IORESOURCE_IRQ && nr-- == 0)
+ return dev->resource[i].start;
+ return NO_IRQ;
+}
+
+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_device_resource(pdev, IORESOURCE_MEM, 0);
+ irq = platform_device_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);
+
+#ifdef CONFIG_PREEMPT
+#error Not Preempt-safe
+#endif
+ pxa_gpio_mode(GPIO6_MMCCLK_MD);
+ pxa_gpio_mode(GPIO8_MMCCS0_MD);
+ CKEN |= CKEN12_MMC;
+
+ 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, host);
+
+ 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);
+
+ 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 = "pxamci",
+ .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 -urpN orig/drivers/mmc/pxamci.h linux/drivers/mmc/pxamci.h
--- orig/drivers/mmc/pxamci.h Thu Jan 1 01:00:00 1970
+++ linux/drivers/mmc/pxamci.h Tue Jul 8 18:49:00 2003
@@ -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 */
--
Russell King
Linux kernel 2.6 ARM Linux - http://www.arm.linux.org.uk/
maintainer of: 2.6 PCMCIA - http://pcmcia.arm.linux.org.uk/
2.6 Serial core
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [RFC] 1/4 MMC layer
2004-04-29 12:48 [RFC] 1/4 MMC layer Russell King
2004-04-29 12:49 ` [RFC] 2/4 MMC layer: configuration + makefiles Russell King
@ 2004-05-02 14:52 ` Russell King
2004-05-03 13:47 ` Pavel Machek
1 sibling, 1 reply; 8+ messages in thread
From: Russell King @ 2004-05-02 14:52 UTC (permalink / raw)
To: Linux Kernel List
On Thu, Apr 29, 2004 at 01:48:24PM +0100, Russell King wrote:
> 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.
As there haven't been any comments, can I assume that either people
don't care, or people are happy for this to appear in Linus' tree?
(I actually suspect its out of peoples minds having been buried by
about 4 days of lkml.)
Thanks. 8)
--
Russell King
Linux kernel 2.6 ARM Linux - http://www.arm.linux.org.uk/
maintainer of: 2.6 PCMCIA - http://pcmcia.arm.linux.org.uk/
2.6 Serial core
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [RFC] 1/4 MMC layer
2004-05-02 14:52 ` [RFC] 1/4 MMC layer Russell King
@ 2004-05-03 13:47 ` Pavel Machek
2004-05-03 22:47 ` Russell King
0 siblings, 1 reply; 8+ messages in thread
From: Pavel Machek @ 2004-05-03 13:47 UTC (permalink / raw)
To: Linux Kernel List
Hi!
> As there haven't been any comments, can I assume that either people
> don't care, or people are happy for this to appear in Linus' tree?
Happy. i386 nx5k notebook contains mmc slot, and I assume
this will help me with a driver.
--
64 bytes from 195.113.31.123: icmp_seq=28 ttl=51 time=448769.1 ms
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [RFC] 1/4 MMC layer
2004-05-03 13:47 ` Pavel Machek
@ 2004-05-03 22:47 ` Russell King
2004-05-03 23:00 ` Pavel Machek
0 siblings, 1 reply; 8+ messages in thread
From: Russell King @ 2004-05-03 22:47 UTC (permalink / raw)
To: Pavel Machek; +Cc: Linux Kernel List
On Mon, May 03, 2004 at 03:47:45PM +0200, Pavel Machek wrote:
> > As there haven't been any comments, can I assume that either people
> > don't care, or people are happy for this to appear in Linus' tree?
>
> Happy. i386 nx5k notebook contains mmc slot, and I assume
> this will help me with a driver.
That depends on the kind of hardware interface you have - whether it
gives you low level access to the MMC command/data streams, or whether
it looks like a USB device / CF disk / whatever else. (I've heard
rumours that such games are played on laptops, but I've no idea how
true this is.)
--
Russell King
Linux kernel 2.6 ARM Linux - http://www.arm.linux.org.uk/
maintainer of: 2.6 PCMCIA - http://pcmcia.arm.linux.org.uk/
2.6 Serial core
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [RFC] 1/4 MMC layer
2004-05-03 22:47 ` Russell King
@ 2004-05-03 23:00 ` Pavel Machek
0 siblings, 0 replies; 8+ messages in thread
From: Pavel Machek @ 2004-05-03 23:00 UTC (permalink / raw)
To: Linux Kernel List
Hi!
> > > As there haven't been any comments, can I assume that either people
> > > don't care, or people are happy for this to appear in Linus' tree?
> >
> > Happy. i386 nx5k notebook contains mmc slot, and I assume
> > this will help me with a driver.
>
> That depends on the kind of hardware interface you have - whether it
> gives you low level access to the MMC command/data streams, or whether
> it looks like a USB device / CF disk / whatever else. (I've heard
> rumours that such games are played on laptops, but I've no idea how
> true this is.)
Its not USB disk, and it does not look like any device I recognize, so
I'm afraid your layer may be usefull. Interface is "TI PCI7420,
PCI7620 flash media controller", and there's datasheet on the web, but
I was not able to figure out how to control it.
Pavel
--
934a471f20d6580d5aad759bf0d97ddc
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2004-05-03 23:00 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2004-04-29 12:48 [RFC] 1/4 MMC layer Russell King
2004-04-29 12:49 ` [RFC] 2/4 MMC layer: configuration + makefiles Russell King
2004-04-29 12:50 ` [RFC] 3/4 MMC layer: ARM MMCI driver Russell King
2004-04-29 12:51 ` [RFC] 4/4 MMC layer: PXA MCI driver Russell King
2004-05-02 14:52 ` [RFC] 1/4 MMC layer Russell King
2004-05-03 13:47 ` Pavel Machek
2004-05-03 22:47 ` Russell King
2004-05-03 23:00 ` Pavel Machek
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox