Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1750894AbWAaOCp (ORCPT ); Tue, 31 Jan 2006 09:02:45 -0500 Received: (majordomo@vger.kernel.org) by vger.kernel.org id S1750896AbWAaOCp (ORCPT ); Tue, 31 Jan 2006 09:02:45 -0500 Received: from mgw-ext03.nokia.com ([131.228.20.95]:3070 "EHLO mgw-ext03.nokia.com") by vger.kernel.org with ESMTP id S1750889AbWAaOCo (ORCPT ); Tue, 31 Jan 2006 09:02:44 -0500 Message-ID: <43DF6750.1060505@indt.org.br> Date: Tue, 31 Jan 2006 09:34:08 -0400 From: Anderson Briglia User-Agent: Debian Thunderbird 1.0.6 (X11/20050802) X-Accept-Language: en-us, en MIME-Version: 1.0 To: linux-kernel@vger.kernel.org CC: Russell King - ARM Linux , Tony Lindgren Subject: [patch 1/5] MMC OMAP driver X-Enigmail-Version: 0.90.0.0 X-Enigmail-Supports: pgp-inline, pgp-mime Content-Type: text/plain; charset=ISO-8859-1 Content-Transfer-Encoding: 8bit X-OriginalArrivalTime: 31 Jan 2006 13:32:25.0411 (UTC) FILETIME=[C5D53130:01C6266A] Sender: linux-kernel-owner@vger.kernel.org X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 39584 Lines: 1478 Adds OMAP MMC driver. Signed-off-by: Juha Yrj?l? Signed-off-by: Tony Lindgren Index: linux-2.6.15-mmc_omap/drivers/mmc/Kconfig =================================================================== --- linux-2.6.15-mmc_omap.orig/drivers/mmc/Kconfig 2006-01-25 16:11:46.000000000 -0400 +++ linux-2.6.15-mmc_omap/drivers/mmc/Kconfig 2006-01-25 16:22:22.000000000 -0400 @@ -49,6 +49,17 @@ config MMC_PXA If unsure, say N. +config MMC_OMAP + tristate "TI OMAP Multimedia Card Interface support" + depends on ARCH_OMAP && MMC + select TPS65010 if MACH_OMAP_H2 + help + This selects the TI OMAP Multimedia card Interface. + If you have an OMAP board with a Multimedia Card slot, + say Y or M here. + + If unsure, say N. + config MMC_WBSD tristate "Winbond W83L51xD SD/MMC Card Interface support" depends on MMC && ISA_DMA_API Index: linux-2.6.15-mmc_omap/drivers/mmc/Makefile =================================================================== --- linux-2.6.15-mmc_omap.orig/drivers/mmc/Makefile 2006-01-25 16:11:46.000000000 -0400 +++ linux-2.6.15-mmc_omap/drivers/mmc/Makefile 2006-01-25 16:15:43.000000000 -0400 @@ -19,5 +19,6 @@ obj-$(CONFIG_MMC_ARMMMCI) += mmci.o obj-$(CONFIG_MMC_PXA) += pxamci.o obj-$(CONFIG_MMC_WBSD) += wbsd.o obj-$(CONFIG_MMC_AU1X) += au1xmmc.o +obj-$(CONFIG_MMC_OMAP) += omap.o mmc_core-y := mmc.o mmc_queue.o mmc_sysfs.o Index: linux-2.6.15-mmc_omap/drivers/mmc/omap.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-2.6.15-mmc_omap/drivers/mmc/omap.c 2006-01-25 16:22:24.000000000 -0400 @@ -0,0 +1,1358 @@ +/* + * linux/drivers/media/mmc/omap.c + * + * Copyright (C) 2004 Nokia Corporation + * Written by Tuukka Tikkanen and Juha Yrj?l? + * Misc hacks here and there by Tony Lindgren + * Other hacks (DMA, SD, etc) by David Brownell + * + * 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 + +#ifdef CONFIG_MMC_DEBUG +#define DEBUG /* for dev_dbg(), pr_debug(), etc */ +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "omap.h" + +#define DRIVER_NAME "mmci-omap" + +/* Specifies how often in millisecs to poll for card status changes + * when the cover switch is open */ +#define OMAP_MMC_SWITCH_POLL_DELAY 500 + +static int mmc_omap_enable_poll = 1; + +struct mmc_omap_host { + int initialized; + int suspended; + struct mmc_request * mrq; + struct mmc_command * cmd; + struct mmc_data * data; + struct mmc_host * mmc; + struct device * dev; + unsigned char id; /* 16xx chips have 2 MMC blocks */ + struct clk * iclk; + struct clk * fclk; + void __iomem *base; + int irq; + unsigned char bus_mode; + unsigned char hw_bus_mode; + + unsigned int sg_len; + int sg_idx; + u16 * buffer; + u32 buffer_bytes_left; + u32 total_bytes_left; + + unsigned use_dma:1; + unsigned brs_received:1, dma_done:1; + unsigned dma_is_read:1; + unsigned dma_in_use:1; + int dma_ch; + spinlock_t dma_lock; + struct timer_list dma_timer; + unsigned dma_len; + + short power_pin; + short wp_pin; + + int switch_pin; + struct work_struct switch_work; + struct timer_list switch_timer; + int switch_last_state; +}; + +static inline int +mmc_omap_cover_is_open(struct mmc_omap_host *host) +{ + if (host->switch_pin < 0) + return 0; + return omap_get_gpio_datain(host->switch_pin); +} + +static ssize_t +mmc_omap_show_cover_switch(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mmc_omap_host *host = dev_get_drvdata(dev); + + return sprintf(buf, "%s\n", mmc_omap_cover_is_open(host) ? "open" : "closed"); +} + +static DEVICE_ATTR(cover_switch, S_IRUGO, mmc_omap_show_cover_switch, NULL); + +static ssize_t +mmc_omap_show_enable_poll(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", mmc_omap_enable_poll); +} + +static ssize_t +mmc_omap_store_enable_poll(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t size) +{ + int enable_poll; + + if (sscanf(buf, "%10d", &enable_poll) != 1) + return -EINVAL; + + if (enable_poll != mmc_omap_enable_poll) { + struct mmc_omap_host *host = dev_get_drvdata(dev); + + mmc_omap_enable_poll = enable_poll; + if (enable_poll && host->switch_pin >= 0) + schedule_work(&host->switch_work); + } + return size; +} + +static DEVICE_ATTR(enable_poll, 0664, + mmc_omap_show_enable_poll, mmc_omap_store_enable_poll); + +static void +mmc_omap_start_command(struct mmc_omap_host *host, struct mmc_command *cmd) +{ + u32 cmdreg; + u32 resptype; + u32 cmdtype; + + host->cmd = cmd; + + resptype = 0; + cmdtype = 0; + + /* + * On 24xx we may have external MMC transceiver on Menelaus. + * In that case we need to manually toggle between open-drain + * and push-pull states. + */ + if (omap_has_menelaus() && (host->bus_mode != host->hw_bus_mode)) { + if (host->bus_mode == MMC_BUSMODE_OPENDRAIN) + menelaus_mmc_opendrain(1); + else + menelaus_mmc_opendrain(0); + host->hw_bus_mode = host->bus_mode; + } + + /* Our hardware needs to know exact type */ + switch (cmd->flags & MMC_RSP_MASK) { + case MMC_RSP_NONE: + /* resp 0 */ + break; + case MMC_RSP_SHORT: + /* resp 1, resp 1b */ + /* OR resp 3!! (assume this if bus is set opendrain) */ + if (host->bus_mode == MMC_BUSMODE_OPENDRAIN) + resptype = 3; + else + resptype = 1; + break; + case MMC_RSP_LONG: + /* resp 2 */ + resptype = 2; + break; + } + + /* Any data transfer means adtc type (but that information is not + * in command structure, so we flagged it into host struct.) + * However, telling bc, bcr and ac apart based on response is + * not foolproof: + * CMD0 = bc = resp0 CMD15 = ac = resp0 + * CMD2 = bcr = resp2 CMD10 = ac = resp2 + * + * Resolve to best guess with some exception testing: + * resp0 -> bc, except CMD15 = ac + * rest are ac, except if opendrain + */ + if (host->data) { + cmdtype = OMAP_MMC_CMDTYPE_ADTC; + } else if (resptype == 0 && cmd->opcode != 15) { + cmdtype = OMAP_MMC_CMDTYPE_BC; + } else if (host->bus_mode == MMC_BUSMODE_OPENDRAIN) { + cmdtype = OMAP_MMC_CMDTYPE_BCR; + } else { + cmdtype = OMAP_MMC_CMDTYPE_AC; + } + + cmdreg = cmd->opcode | (resptype << 8) | (cmdtype << 12); + + if (host->bus_mode == MMC_BUSMODE_OPENDRAIN) + cmdreg |= 1 << 6; + + if (cmd->flags & MMC_RSP_BUSY) + cmdreg |= 1 << 11; + + if (host->data && !(host->data->flags & MMC_DATA_WRITE)) + cmdreg |= 1 << 15; + + clk_enable(host->fclk); + + OMAP_MMC_WRITE(host->base, CTO, 200); + OMAP_MMC_WRITE(host->base, ARGL, cmd->arg & 0xffff); + OMAP_MMC_WRITE(host->base, ARGH, cmd->arg >> 16); + OMAP_MMC_WRITE(host->base, IE, + OMAP_MMC_STAT_A_EMPTY | OMAP_MMC_STAT_A_FULL | + OMAP_MMC_STAT_CMD_CRC | OMAP_MMC_STAT_CMD_TOUT | + OMAP_MMC_STAT_DATA_CRC | OMAP_MMC_STAT_DATA_TOUT | + OMAP_MMC_STAT_END_OF_CMD | OMAP_MMC_STAT_CARD_ERR | + OMAP_MMC_STAT_END_OF_DATA); + OMAP_MMC_WRITE(host->base, CMD, cmdreg); +} + +static void +mmc_omap_xfer_done(struct mmc_omap_host *host, struct mmc_data *data) +{ + if (host->dma_in_use) { + enum dma_data_direction dma_data_dir; + + BUG_ON(host->dma_ch < 0); + if (data->error != MMC_ERR_NONE) + omap_stop_dma(host->dma_ch); + /* Release DMA channel lazily */ + mod_timer(&host->dma_timer, jiffies + HZ); + if (data->flags & MMC_DATA_WRITE) + dma_data_dir = DMA_TO_DEVICE; + else + dma_data_dir = DMA_FROM_DEVICE; + dma_unmap_sg(mmc_dev(host->mmc), data->sg, host->sg_len, + dma_data_dir); + } + host->data = NULL; + host->sg_len = 0; + clk_disable(host->fclk); + + /* NOTE: MMC layer will sometimes poll-wait CMD13 next, issuing + * dozens of requests until the card finishes writing data. + * It'd be cheaper to just wait till an EOFB interrupt arrives... + */ + + if (!data->stop) { + host->mrq = NULL; + mmc_request_done(host->mmc, data->mrq); + return; + } + + mmc_omap_start_command(host, data->stop); +} + +static void +mmc_omap_end_of_data(struct mmc_omap_host *host, struct mmc_data *data) +{ + unsigned long flags; + int done; + + if (!host->dma_in_use) { + mmc_omap_xfer_done(host, data); + return; + } + done = 0; + spin_lock_irqsave(&host->dma_lock, flags); + if (host->dma_done) + done = 1; + else + host->brs_received = 1; + spin_unlock_irqrestore(&host->dma_lock, flags); + if (done) + mmc_omap_xfer_done(host, data); +} + +static void +mmc_omap_dma_timer(unsigned long data) +{ + struct mmc_omap_host *host = (struct mmc_omap_host *) data; + + BUG_ON(host->dma_ch < 0); + omap_free_dma(host->dma_ch); + host->dma_ch = -1; +} + +static void +mmc_omap_dma_done(struct mmc_omap_host *host, struct mmc_data *data) +{ + unsigned long flags; + int done; + + done = 0; + spin_lock_irqsave(&host->dma_lock, flags); + if (host->brs_received) + done = 1; + else + host->dma_done = 1; + spin_unlock_irqrestore(&host->dma_lock, flags); + if (done) + mmc_omap_xfer_done(host, data); +} + +static void +mmc_omap_cmd_done(struct mmc_omap_host *host, struct mmc_command *cmd) +{ + host->cmd = NULL; + + switch (cmd->flags & MMC_RSP_MASK) { + case MMC_RSP_NONE: + /* resp 0 */ + break; + case MMC_RSP_SHORT: + /* response types 1, 1b, 3, 4, 5, 6 */ + cmd->resp[0] = + OMAP_MMC_READ(host->base, RSP6) | + (OMAP_MMC_READ(host->base, RSP7) << 16); + break; + case MMC_RSP_LONG: + /* response type 2 */ + cmd->resp[3] = + OMAP_MMC_READ(host->base, RSP0) | + (OMAP_MMC_READ(host->base, RSP1) << 16); + cmd->resp[2] = + OMAP_MMC_READ(host->base, RSP2) | + (OMAP_MMC_READ(host->base, RSP3) << 16); + cmd->resp[1] = + OMAP_MMC_READ(host->base, RSP4) | + (OMAP_MMC_READ(host->base, RSP5) << 16); + cmd->resp[0] = + OMAP_MMC_READ(host->base, RSP6) | + (OMAP_MMC_READ(host->base, RSP7) << 16); + break; + } + + if (host->data == NULL || cmd->error != MMC_ERR_NONE) { + host->mrq = NULL; + clk_disable(host->fclk); + mmc_request_done(host->mmc, cmd->mrq); + } +} + +/* PIO only */ +static void +mmc_omap_sg_to_buf(struct mmc_omap_host *host) +{ + struct scatterlist *sg; + + sg = host->data->sg + host->sg_idx; + host->buffer_bytes_left = sg->length; + host->buffer = page_address(sg->page) + sg->offset; + if (host->buffer_bytes_left > host->total_bytes_left) + host->buffer_bytes_left = host->total_bytes_left; +} + +/* PIO only */ +static void +mmc_omap_xfer_data(struct mmc_omap_host *host, int write) +{ + int n; + void __iomem *reg; + u16 *p; + + if (host->buffer_bytes_left == 0) { + host->sg_idx++; + BUG_ON(host->sg_idx == host->sg_len); + mmc_omap_sg_to_buf(host); + } + n = 64; + if (n > host->buffer_bytes_left) + n = host->buffer_bytes_left; + host->buffer_bytes_left -= n; + host->total_bytes_left -= n; + host->data->bytes_xfered += n; + + /* Optimize the loop a bit by calculating the register only + * once */ + reg = host->base + OMAP_MMC_REG_DATA; + p = host->buffer; + n /= 2; + if (write) { + while (n--) + __raw_writew(*p++, reg); + } else { + while (n-- > 0) + *p++ = __raw_readw(reg); + } + host->buffer = p; +} + +static inline void mmc_omap_report_irq(u16 status) +{ + static const char *mmc_omap_status_bits[] = { + "EOC", "CD", "CB", "BRS", "EOFB", "DTO", "DCRC", "CTO", + "CCRC", "CRW", "AF", "AE", "OCRB", "CIRQ", "CERR" + }; + int i, c = 0; + + for (i = 0; i < ARRAY_SIZE(mmc_omap_status_bits); i++) + if (status & (1 << i)) { + if (c) + printk(" "); + printk("%s", mmc_omap_status_bits[i]); + c++; + } +} + +static irqreturn_t mmc_omap_irq(int irq, void *dev_id, struct pt_regs *regs) +{ + struct mmc_omap_host * host = (struct mmc_omap_host *)dev_id; + u16 status; + int end_command; + int end_transfer; + int transfer_error; + + if (host->cmd == NULL && host->data == NULL) { + status = OMAP_MMC_READ(host->base, STAT); + printk(KERN_INFO "MMC%d: Spurious interrupt 0x%04x\n", host->id, status); + if (status != 0) { + OMAP_MMC_WRITE(host->base, STAT, status); + OMAP_MMC_WRITE(host->base, IE, 0); + } + return IRQ_HANDLED; + } + + end_command = 0; + end_transfer = 0; + transfer_error = 0; + + while ((status = OMAP_MMC_READ(host->base, STAT)) != 0) { + OMAP_MMC_WRITE(host->base, STAT, status); +#ifdef CONFIG_MMC_DEBUG + printk(KERN_DEBUG "\tMMC IRQ %04x (CMD %d): ", status, + host->cmd != NULL ? host->cmd->opcode : -1); + mmc_omap_report_irq(status); + printk("\n"); +#endif + if (host->total_bytes_left) { + if ((status & OMAP_MMC_STAT_A_FULL) || + (status & OMAP_MMC_STAT_END_OF_DATA)) + mmc_omap_xfer_data(host, 0); + if (status & OMAP_MMC_STAT_A_EMPTY) + mmc_omap_xfer_data(host, 1); + } + + if (status & OMAP_MMC_STAT_END_OF_DATA) { + end_transfer = 1; + } + + if (status & OMAP_MMC_STAT_DATA_TOUT) { + printk(KERN_DEBUG "MMC%d: Data timeout\n", host->id); + if (host->data) { + host->data->error |= MMC_ERR_TIMEOUT; + transfer_error = 1; + } + } + + if (status & OMAP_MMC_STAT_DATA_CRC) { + if (host->data) { + host->data->error |= MMC_ERR_BADCRC; + printk(KERN_DEBUG "MMC%d: Data CRC error, bytes left %d\n", + host->id, host->total_bytes_left); + transfer_error = 1; + } else { + printk(KERN_DEBUG "MMC%d: Data CRC error\n", + host->id); + } + } + + if (status & OMAP_MMC_STAT_CMD_TOUT) { + /* Timeouts are routine with some commands */ + if (host->cmd) { + if (host->cmd->opcode != MMC_ALL_SEND_CID && + host->cmd->opcode != MMC_SEND_OP_COND && + host->cmd->opcode != MMC_APP_CMD && + !mmc_omap_cover_is_open(host)) + printk(KERN_ERR "MMC%d: Command timeout, CMD%d\n", + host->id, host->cmd->opcode); + host->cmd->error |= MMC_ERR_TIMEOUT; + end_command = 1; + } + } + + if (status & OMAP_MMC_STAT_CMD_CRC) { + if (host->cmd) { + printk(KERN_ERR "MMC%d: Command CRC error (CMD%d, arg 0x%08x)\n", + host->id, host->cmd->opcode, + host->cmd->arg); + host->cmd->error |= MMC_ERR_BADCRC; + end_command = 1; + } else + printk(KERN_ERR "MMC%d: Command CRC error without cmd?\n", host->id); + } + + if (status & OMAP_MMC_STAT_CARD_ERR) { + if (host->cmd && host->cmd->opcode == MMC_STOP_TRANSMISSION) { + u32 response = OMAP_MMC_READ(host->base, RSP6) + | (OMAP_MMC_READ(host->base, RSP7) << 16); + /* STOP sometimes sets must-ignore bits */ + if (!(response & (R1_CC_ERROR + | R1_ILLEGAL_COMMAND + | R1_COM_CRC_ERROR))) { + end_command = 1; + continue; + } + } + + printk(KERN_DEBUG "MMC%d: Card status error (CMD%d)\n", + host->id, host->cmd->opcode); + if (host->cmd) { + host->cmd->error |= MMC_ERR_FAILED; + end_command = 1; + } + if (host->data) { + host->data->error |= MMC_ERR_FAILED; + transfer_error = 1; + } + } + + /* + * NOTE: On 1610 the END_OF_CMD may come too early when + * starting a write + */ + if ((status & OMAP_MMC_STAT_END_OF_CMD) && + (!(status & OMAP_MMC_STAT_A_EMPTY))) { + end_command = 1; + } + } + + if (end_command) { + mmc_omap_cmd_done(host, host->cmd); + } + if (transfer_error) + mmc_omap_xfer_done(host, host->data); + else if (end_transfer) + mmc_omap_end_of_data(host, host->data); + + return IRQ_HANDLED; +} + +static irqreturn_t mmc_omap_switch_irq(int irq, void *dev_id, struct pt_regs *regs) +{ + struct mmc_omap_host *host = (struct mmc_omap_host *) dev_id; + + schedule_work(&host->switch_work); + + return IRQ_HANDLED; +} + +static void mmc_omap_switch_timer(unsigned long arg) +{ + struct mmc_omap_host *host = (struct mmc_omap_host *) arg; + + schedule_work(&host->switch_work); +} + +/* FIXME: Handle card insertion and removal properly. Maybe use a mask + * for MMC state? */ +static void mmc_omap_switch_callback(unsigned long data, u8 mmc_mask) +{ +} + +static void mmc_omap_switch_handler(void *data) +{ + struct mmc_omap_host *host = (struct mmc_omap_host *) data; + struct mmc_card *card; + static int complained = 0; + int cards = 0, cover_open; + + if (host->switch_pin == -1) + return; + cover_open = mmc_omap_cover_is_open(host); + if (cover_open != host->switch_last_state) { + kobject_uevent(&host->dev->kobj, KOBJ_CHANGE); + host->switch_last_state = cover_open; + } + mmc_detect_change(host->mmc, 0); + list_for_each_entry(card, &host->mmc->cards, node) { + if (mmc_card_present(card)) + cards++; + } + if (mmc_omap_cover_is_open(host)) { + if (!complained) { + printk(KERN_INFO "MMC%d: cover is open\n", host->id); + complained = 1; + } + if (mmc_omap_enable_poll) + mod_timer(&host->switch_timer, jiffies + + msecs_to_jiffies(OMAP_MMC_SWITCH_POLL_DELAY)); + } else { + complained = 0; + } +} + +/* Prepare to transfer the next segment of a scatterlist */ +static void +mmc_omap_prepare_dma(struct mmc_omap_host *host, struct mmc_data *data) +{ + int dma_ch = host->dma_ch; + unsigned long data_addr; + u16 buf, frame; + u32 count; + struct scatterlist *sg = &data->sg[host->sg_idx]; + int src_port = 0; + int dst_port = 0; + int sync_dev = 0; + + data_addr = io_v2p((void __force *) host->base) + OMAP_MMC_REG_DATA; + frame = 1 << data->blksz_bits; + count = (u32)sg_dma_len(sg); + + if ((data->blocks == 1) && (count > (1 << data->blksz_bits))) + count = frame; + + host->dma_len = count; + + /* FIFO is 16x2 bytes on 15xx, and 32x2 bytes on 16xx and 24xx. + * Use 16 or 32 word frames when the blocksize is at least that large. + * Blocksize is usually 512 bytes; but not for some SD reads. + */ + if (cpu_is_omap15xx() && frame > 32) + frame = 32; + else if (frame > 64) + frame = 64; + count /= frame; + frame >>= 1; + + if (!(data->flags & MMC_DATA_WRITE)) { + buf = 0x800f | ((frame - 1) << 8); + + if (cpu_class_is_omap1()) { + src_port = OMAP_DMA_PORT_TIPB; + dst_port = OMAP_DMA_PORT_EMIFF; + } + if (cpu_is_omap24xx()) + sync_dev = OMAP24XX_DMA_MMC1_RX; + + omap_set_dma_src_params(dma_ch, src_port, + OMAP_DMA_AMODE_CONSTANT, + data_addr, 0, 0); + omap_set_dma_dest_params(dma_ch, dst_port, + OMAP_DMA_AMODE_POST_INC, + sg_dma_address(sg), 0, 0); + omap_set_dma_dest_data_pack(dma_ch, 1); + omap_set_dma_dest_burst_mode(dma_ch, OMAP_DMA_DATA_BURST_4); + } else { + buf = 0x0f80 | ((frame - 1) << 0); + + if (cpu_class_is_omap1()) { + src_port = OMAP_DMA_PORT_EMIFF; + dst_port = OMAP_DMA_PORT_TIPB; + } + if (cpu_is_omap24xx()) + sync_dev = OMAP24XX_DMA_MMC1_TX; + + omap_set_dma_dest_params(dma_ch, dst_port, + OMAP_DMA_AMODE_CONSTANT, + data_addr, 0, 0); + omap_set_dma_src_params(dma_ch, src_port, + OMAP_DMA_AMODE_POST_INC, + sg_dma_address(sg), 0, 0); + omap_set_dma_src_data_pack(dma_ch, 1); + omap_set_dma_src_burst_mode(dma_ch, OMAP_DMA_DATA_BURST_4); + } + + /* Max limit for DMA frame count is 0xffff */ + if (unlikely(count > 0xffff)) + BUG(); + + OMAP_MMC_WRITE(host->base, BUF, buf); + omap_set_dma_transfer_params(dma_ch, OMAP_DMA_DATA_TYPE_S16, + frame, count, OMAP_DMA_SYNC_FRAME, + sync_dev, 0); +} + +/* A scatterlist segment completed */ +static void mmc_omap_dma_cb(int lch, u16 ch_status, void *data) +{ + struct mmc_omap_host *host = (struct mmc_omap_host *) data; + struct mmc_data *mmcdat = host->data; + + if (unlikely(host->dma_ch < 0)) { + printk(KERN_ERR "MMC%d: DMA callback while DMA not enabled\n", + host->id); + return; + } + /* FIXME: We really should do something to _handle_ the errors */ + if (ch_status & OMAP_DMA_TOUT_IRQ) { + printk(KERN_ERR "MMC%d: DMA timeout\n", host->id); + return; + } + if (ch_status & OMAP_DMA_DROP_IRQ) { + printk(KERN_ERR "MMC%d: DMA sync error\n", host->id); + return; + } + if (!(ch_status & OMAP_DMA_BLOCK_IRQ)) { + return; + } + mmcdat->bytes_xfered += host->dma_len; + host->sg_idx++; + if (host->sg_idx < host->sg_len) { + mmc_omap_prepare_dma(host, host->data); + omap_start_dma(host->dma_ch); + } else + mmc_omap_dma_done(host, host->data); +} + +static int mmc_omap_get_dma_channel(struct mmc_omap_host *host, struct mmc_data *data) +{ + const char *dev_name; + int sync_dev, dma_ch, is_read, r; + + is_read = !(data->flags & MMC_DATA_WRITE); + del_timer_sync(&host->dma_timer); + if (host->dma_ch >= 0) { + if (is_read == host->dma_is_read) + return 0; + omap_free_dma(host->dma_ch); + host->dma_ch = -1; + } + + if (is_read) { + if (host->id == 1) { + sync_dev = OMAP_DMA_MMC_RX; + dev_name = "MMC1 read"; + } else { + sync_dev = OMAP_DMA_MMC2_RX; + dev_name = "MMC2 read"; + } + } else { + if (host->id == 1) { + sync_dev = OMAP_DMA_MMC_TX; + dev_name = "MMC1 write"; + } else { + sync_dev = OMAP_DMA_MMC2_TX; + dev_name = "MMC2 write"; + } + } + r = omap_request_dma(sync_dev, dev_name, mmc_omap_dma_cb, + host, &dma_ch); + if (r != 0) { + printk("MMC%d: omap_request_dma() failed with %d\n", + host->id, r); + return r; + } + host->dma_ch = dma_ch; + host->dma_is_read = is_read; + + return 0; +} + +static inline void set_cmd_timeout(struct mmc_omap_host *host, struct mmc_request *req) +{ + u16 reg; + + reg = OMAP_MMC_READ(host->base, SDIO); + reg &= ~(1 << 5); + OMAP_MMC_WRITE(host->base, SDIO, reg); + /* Set maximum timeout */ + OMAP_MMC_WRITE(host->base, CTO, 0xff); +} + +static inline void set_data_timeout(struct mmc_omap_host *host, struct mmc_request *req) +{ + int timeout; + u16 reg; + + /* Convert ns to clock cycles by assuming 20MHz frequency + * 1 cycle at 20MHz = 500 ns + */ + timeout = req->data->timeout_clks + req->data->timeout_ns / 500; + + /* Some cards require more time to do at least the first read operation */ + timeout = timeout << 4; + + /* Check if we need to use timeout multiplier register */ + reg = OMAP_MMC_READ(host->base, SDIO); + if (timeout > 0xffff) { + reg |= (1 << 5); + timeout /= 1024; + } else + reg &= ~(1 << 5); + OMAP_MMC_WRITE(host->base, SDIO, reg); + OMAP_MMC_WRITE(host->base, DTO, timeout); +} + +static void +mmc_omap_prepare_data(struct mmc_omap_host *host, struct mmc_request *req) +{ + struct mmc_data *data = req->data; + int i, use_dma, block_size; + unsigned sg_len; + + host->data = data; + if (data == NULL) { + OMAP_MMC_WRITE(host->base, BLEN, 0); + OMAP_MMC_WRITE(host->base, NBLK, 0); + OMAP_MMC_WRITE(host->base, BUF, 0); + host->dma_in_use = 0; + set_cmd_timeout(host, req); + return; + } + + + block_size = 1 << data->blksz_bits; + + OMAP_MMC_WRITE(host->base, NBLK, data->blocks - 1); + OMAP_MMC_WRITE(host->base, BLEN, block_size - 1); + set_data_timeout(host, req); + + /* cope with calling layer confusion; it issues "single + * block" writes using multi-block scatterlists. + */ + sg_len = (data->blocks == 1) ? 1 : data->sg_len; + + /* Only do DMA for entire blocks */ + use_dma = host->use_dma; + if (use_dma) { + for (i = 0; i < sg_len; i++) { + if ((data->sg[i].length % block_size) != 0) { + use_dma = 0; + break; + } + } + } + + host->sg_idx = 0; + if (use_dma) { + if (mmc_omap_get_dma_channel(host, data) == 0) { + enum dma_data_direction dma_data_dir; + + if (data->flags & MMC_DATA_WRITE) + dma_data_dir = DMA_TO_DEVICE; + else + dma_data_dir = DMA_FROM_DEVICE; + + host->sg_len = dma_map_sg(mmc_dev(host->mmc), data->sg, + sg_len, dma_data_dir); + host->total_bytes_left = 0; + mmc_omap_prepare_dma(host, req->data); + host->brs_received = 0; + host->dma_done = 0; + host->dma_in_use = 1; + } else + use_dma = 0; + } + + /* Revert to PIO? */ + if (!use_dma) { + OMAP_MMC_WRITE(host->base, BUF, 0x1f1f); + host->total_bytes_left = data->blocks * block_size; + host->sg_len = sg_len; + mmc_omap_sg_to_buf(host); + host->dma_in_use = 0; + } +} + +static inline int is_broken_card(struct mmc_card *card) +{ + int i; + struct mmc_cid *c = &card->cid; + static const struct broken_card_cid { + unsigned int manfid; + char prod_name[8]; + unsigned char hwrev; + unsigned char fwrev; + } broken_cards[] = { + { 0x00150000, "\x30\x30\x30\x30\x30\x30\x15\x00", 0x06, 0x03 }, + }; + + for (i = 0; i < sizeof(broken_cards)/sizeof(broken_cards[0]); i++) { + const struct broken_card_cid *b = broken_cards + i; + + if (b->manfid != c->manfid) + continue; + if (memcmp(b->prod_name, c->prod_name, sizeof(b->prod_name)) != 0) + continue; + if (b->hwrev != c->hwrev || b->fwrev != c->fwrev) + continue; + return 1; + } + return 0; +} + +static void mmc_omap_request(struct mmc_host *mmc, struct mmc_request *req) +{ + struct mmc_omap_host *host = mmc_priv(mmc); + + WARN_ON(host->mrq != NULL); + + host->mrq = req; + + /* Some cards (vendor left unnamed to protect the guilty) seem to + * require this delay after power-up. Otherwise we'll get mysterious + * data timeouts. + */ + if (req->cmd->opcode == MMC_SEND_CSD) { + struct mmc_card *card; + int broken_present = 0; + + list_for_each_entry(card, &mmc->cards, node) { + if (is_broken_card(card)) { + broken_present = 1; + break; + } + } + if (broken_present) { + static int complained = 0; + + if (!complained) { + printk(KERN_WARNING "MMC%d: Broken card workaround enabled\n", + host->id); + complained = 1; + } + if (in_interrupt()) { + /* This is nasty */ + printk(KERN_ERR "Sleeping in IRQ handler, FIXME please!\n"); + dump_stack(); + mdelay(100); + } else { + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(100 * HZ / 1000); + } + } + } + + /* only touch fifo AFTER the controller readies it */ + mmc_omap_prepare_data(host, req); + mmc_omap_start_command(host, req->cmd); + if (host->dma_in_use) + omap_start_dma(host->dma_ch); +} + +static void innovator_fpga_socket_power(int on) +{ +#if defined(CONFIG_MACH_OMAP_INNOVATOR) && defined(CONFIG_ARCH_OMAP15XX) + + if (on) { + fpga_write(fpga_read(OMAP1510_FPGA_POWER) | (1 << 3), + OMAP1510_FPGA_POWER); + } else { + fpga_write(fpga_read(OMAP1510_FPGA_POWER) & ~(1 << 3), + OMAP1510_FPGA_POWER); + } +#endif +} + +/* + * Turn the socket power on/off. Innovator uses FPGA, most boards + * probably use GPIO. + */ +static void mmc_omap_power(struct mmc_omap_host *host, int on) +{ +#ifdef CONFIG_I2C + if (on) { + if (machine_is_omap_innovator()) + innovator_fpga_socket_power(1); + else if (machine_is_omap_h2()) + tps65010_set_gpio_out_value(GPIO3, HIGH); + else if (machine_is_omap_h3()) + /* GPIO 4 of TPS65010 sends SD_EN signal */ + tps65010_set_gpio_out_value(GPIO4, HIGH); + else if (cpu_is_omap24xx()) { + u16 reg = OMAP_MMC_READ(host->base, CON); + OMAP_MMC_WRITE(host->base, CON, reg | (1 << 11)); + } else + if (host->power_pin >= 0) + omap_set_gpio_dataout(host->power_pin, 1); + } else { + if (machine_is_omap_innovator()) + innovator_fpga_socket_power(0); + else if (machine_is_omap_h2()) + tps65010_set_gpio_out_value(GPIO3, LOW); + else if (machine_is_omap_h3()) + tps65010_set_gpio_out_value(GPIO4, LOW); + else if (cpu_is_omap24xx()) { + u16 reg = OMAP_MMC_READ(host->base, CON); + OMAP_MMC_WRITE(host->base, CON, reg & ~(1 << 11)); + } else + if (host->power_pin >= 0) + omap_set_gpio_dataout(host->power_pin, 0); + } +#endif +} + +static void mmc_omap_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +{ + struct mmc_omap_host *host = mmc_priv(mmc); + int dsor; + int realclock, i; + + if (ios->power_mode == MMC_POWER_UP && ios->clock < 400000) + realclock = 400000; + else + realclock = ios->clock; + + if (ios->clock == 0) + dsor = 0; + else { + int func_clk_rate = clk_get_rate(host->fclk); + + dsor = func_clk_rate / realclock; + if (dsor < 1) + dsor = 1; + + if (func_clk_rate / dsor > realclock) + dsor++; + + if (dsor > 250) + dsor = 250; + dsor++; + + if (ios->bus_width == MMC_BUS_WIDTH_4) + dsor |= 1 << 15; + } + + switch (ios->power_mode) { + case MMC_POWER_OFF: +#ifdef CONFIG_I2C + mmc_omap_power(host, 0); +#endif + break; + case MMC_POWER_UP: + case MMC_POWER_ON: +#ifdef CONFIG_I2C + mmc_omap_power(host, 1); +#endif + dsor |= 1<<11; + break; + } + + host->bus_mode = ios->bus_mode; + if (omap_has_menelaus()) { + if (host->bus_mode == MMC_BUSMODE_OPENDRAIN) + menelaus_mmc_opendrain(1); + else + menelaus_mmc_opendrain(0); + } + host->hw_bus_mode = host->bus_mode; + + clk_enable(host->fclk); + + /* On insanely high arm_per frequencies something sometimes + * goes somehow out of sync, and the POW bit is not being set, + * which results in the while loop below getting stuck. + * Writing to the CON register twice seems to do the trick. */ + for (i = 0; i < 2; i++) + OMAP_MMC_WRITE(host->base, CON, dsor); + if (ios->power_mode == MMC_POWER_UP) { + /* Send clock cycles, poll completion */ + OMAP_MMC_WRITE(host->base, IE, 0); + OMAP_MMC_WRITE(host->base, STAT, 0xffff); + OMAP_MMC_WRITE(host->base, CMD, 1<<7); + while (0 == (OMAP_MMC_READ(host->base, STAT) & 1)); + OMAP_MMC_WRITE(host->base, STAT, 1); + } + clk_disable(host->fclk); +} + +static int mmc_omap_get_ro(struct mmc_host *mmc) +{ + struct mmc_omap_host *host = mmc_priv(mmc); + + return host->wp_pin && omap_get_gpio_datain(host->wp_pin); +} + +static struct mmc_host_ops mmc_omap_ops = { + .request = mmc_omap_request, + .set_ios = mmc_omap_set_ios, + .get_ro = mmc_omap_get_ro, +}; + +static int __init mmc_omap_probe(struct platform_device *pdev) +{ + struct omap_mmc_conf *minfo = pdev->dev.platform_data; + struct mmc_host *mmc; + struct mmc_omap_host *host = NULL; + int ret = 0; + + if (pdev->resource[0].flags != IORESOURCE_MEM + || pdev->resource[1].flags != IORESOURCE_IRQ) { + printk(KERN_ERR "mmc_omap_probe: invalid resource type\n"); + return -ENODEV; + } + + if (!request_mem_region(pdev->resource[0].start, + pdev->resource[0].end - pdev->resource[0].start + 1, + pdev->name)) { + dev_dbg(&pdev->dev, "request_mem_region failed\n"); + return -EBUSY; + } + + mmc = mmc_alloc_host(sizeof(struct mmc_omap_host), &pdev->dev); + if (!mmc) { + ret = -ENOMEM; + goto out; + } + + host = mmc_priv(mmc); + host->mmc = mmc; + + spin_lock_init(&host->dma_lock); + init_timer(&host->dma_timer); + host->dma_timer.function = mmc_omap_dma_timer; + host->dma_timer.data = (unsigned long) host; + + host->id = pdev->id; + + if (cpu_is_omap24xx()) { + host->iclk = clk_get(&pdev->dev, "mmc_ick"); + if (IS_ERR(host->iclk)) + goto out; + clk_enable(host->iclk); + } + + if (!cpu_is_omap24xx()) + host->fclk = clk_get(&pdev->dev, + (host->id == 1) ? "mmc1_ck" : "mmc2_ck"); + else + host->fclk = clk_get(&pdev->dev, "mmc_fck"); + + if (IS_ERR(host->fclk)) { + ret = PTR_ERR(host->fclk); + goto out; + } + + /* REVISIT: + * Also, use minfo->cover to decide how to manage + * the card detect sensing. + */ + host->power_pin = minfo->power_pin; + host->switch_pin = minfo->switch_pin; + host->wp_pin = minfo->wp_pin; + host->use_dma = 1; + host->dma_ch = -1; + + host->irq = pdev->resource[1].start; + host->base = (void __iomem *)pdev->resource[0].start; + + if (minfo->wire4) + mmc->caps |= MMC_CAP_4_BIT_DATA; + + mmc->ops = &mmc_omap_ops; + mmc->f_min = 400000; + mmc->f_max = 24000000; + mmc->ocr_avail = MMC_VDD_33_34; + + /* Use scatterlist DMA to reduce per-transfer costs. + * NOTE max_seg_size assumption that small blocks aren't + * normally used (except e.g. for reading SD registers). + */ + mmc->max_phys_segs = 32; + mmc->max_hw_segs = 32; + mmc->max_sectors = 256; /* NBLK max 11-bits, OMAP also limited by DMA */ + mmc->max_seg_size = mmc->max_sectors * 512; + + if (host->power_pin >= 0) { + if ((ret = omap_request_gpio(host->power_pin)) != 0) { + printk(KERN_ERR "MMC%d: Unable to get GPIO pin for MMC power\n", + host->id); + goto out; + } + omap_set_gpio_direction(host->power_pin, 0); + } + + ret = request_irq(host->irq, mmc_omap_irq, 0, DRIVER_NAME, host); + if (ret) + goto out; + + host->dev = &pdev->dev; + platform_set_drvdata(pdev, host); + + mmc_add_host(mmc); + + if (host->switch_pin >= 0) { + INIT_WORK(&host->switch_work, mmc_omap_switch_handler, host); + init_timer(&host->switch_timer); + host->switch_timer.function = mmc_omap_switch_timer; + host->switch_timer.data = (unsigned long) host; + if (omap_request_gpio(host->switch_pin) != 0) { + printk(KERN_WARNING "MMC%d: Unable to get GPIO pin for MMC cover switch\n", + host->id); + host->switch_pin = -1; + goto no_switch; + } + + omap_set_gpio_direction(host->switch_pin, 1); + set_irq_type(OMAP_GPIO_IRQ(host->switch_pin), IRQT_RISING); + ret = request_irq(OMAP_GPIO_IRQ(host->switch_pin), + mmc_omap_switch_irq, 0, DRIVER_NAME, host); + if (ret) { + printk(KERN_WARNING "MMC%d: Unable to get IRQ for MMC cover switch\n", + host->id); + omap_free_gpio(host->switch_pin); + host->switch_pin = -1; + goto no_switch; + } + ret = device_create_file(&pdev->dev, &dev_attr_cover_switch); + if (ret == 0) { + ret = device_create_file(&pdev->dev, &dev_attr_enable_poll); + if (ret != 0) + device_remove_file(&pdev->dev, &dev_attr_cover_switch); + } + if (ret) { + printk(KERN_WARNING "MMC%d: Unable to create sysfs attributes\n", + host->id); + free_irq(OMAP_GPIO_IRQ(host->switch_pin), host); + omap_free_gpio(host->switch_pin); + host->switch_pin = -1; + goto no_switch; + } + if (mmc_omap_enable_poll && mmc_omap_cover_is_open(host)) + schedule_work(&host->switch_work); + } + + if (omap_has_menelaus()) + menelaus_mmc_register(mmc_omap_switch_callback, &host); + +no_switch: + return 0; + +out: + /* FIXME: Free other resources too. */ + if (host) { + if (host->iclk && !IS_ERR(host->iclk)) + clk_put(host->iclk); + if (host->fclk && !IS_ERR(host->fclk)) + clk_put(host->fclk); + mmc_free_host(host->mmc); + } + return ret; +} + +static int mmc_omap_remove(struct platform_device *pdev) +{ + struct mmc_omap_host *host = platform_get_drvdata(pdev); + + platform_set_drvdata(pdev, NULL); + + if (host) { + mmc_remove_host(host->mmc); + free_irq(host->irq, host); +#ifdef CONFIG_I2C + mmc_omap_power(host, 0); +#endif + + if (host->power_pin >= 0) + omap_free_gpio(host->power_pin); + if (host->switch_pin >= 0) { + device_remove_file(&pdev->dev, &dev_attr_enable_poll); + device_remove_file(&pdev->dev, &dev_attr_cover_switch); + free_irq(OMAP_GPIO_IRQ(host->switch_pin), host); + omap_free_gpio(host->switch_pin); + host->switch_pin = -1; + del_timer_sync(&host->switch_timer); + flush_scheduled_work(); + } + if (host->iclk && !IS_ERR(host->iclk)) + clk_put(host->iclk); + if (host->fclk && !IS_ERR(host->fclk)) + clk_put(host->fclk); + mmc_free_host(host->mmc); + } + + if (omap_has_menelaus()) + menelaus_mmc_remove(); + + release_mem_region(pdev->resource[0].start, + pdev->resource[0].end - pdev->resource[0].start + 1); + + return 0; +} + +#ifdef CONFIG_PM +static int mmc_omap_suspend(struct platform_device *pdev, pm_message_t mesg) +{ + int ret = 0; + struct mmc_omap_host *host = platform_get_drvdata(pdev); + + if (host && host->suspended) + return 0; + + if (!irqs_disabled()) + return -EAGAIN; + + if (host) { + ret = mmc_suspend_host(host->mmc, mesg); + if (ret == 0) + host->suspended = 1; + } + return ret; +} + +static int mmc_omap_resume(struct platform_device *pdev) +{ + int ret = 0; + struct mmc_omap_host *host = platform_get_drvdata(pdev); + + if (host && !host->suspended) + return 0; + + if (host) { + ret = mmc_resume_host(host->mmc); + if (ret == 0) + host->suspended = 0; + } + + return ret; +} +#else +#define mmc_omap_suspend NULL +#define mmc_omap_resume NULL +#endif + +static struct platform_driver mmc_omap_driver = { + .probe = mmc_omap_probe, + .remove = mmc_omap_remove, + .suspend = mmc_omap_suspend, + .resume = mmc_omap_resume, + .driver = { + .name = DRIVER_NAME, + }, +}; + +static int __init mmc_omap_init(void) +{ + return platform_driver_register(&mmc_omap_driver); +} + +static void __exit mmc_omap_exit(void) +{ + platform_driver_unregister(&mmc_omap_driver); +} + +module_init(mmc_omap_init); +module_exit(mmc_omap_exit); + +MODULE_DESCRIPTION("OMAP Multimedia Card driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS(DRIVER_NAME); +MODULE_AUTHOR("Juha Yrj?l?"); Index: linux-2.6.15-mmc_omap/drivers/mmc/omap.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-2.6.15-mmc_omap/drivers/mmc/omap.h 2006-01-25 16:15:43.000000000 -0400 @@ -0,0 +1,55 @@ +#ifndef DRIVERS_MEDIA_MMC_OMAP_H +#define DRIVERS_MEDIA_MMC_OMAP_H + +#define OMAP_MMC_REG_CMD 0x00 +#define OMAP_MMC_REG_ARGL 0x04 +#define OMAP_MMC_REG_ARGH 0x08 +#define OMAP_MMC_REG_CON 0x0c +#define OMAP_MMC_REG_STAT 0x10 +#define OMAP_MMC_REG_IE 0x14 +#define OMAP_MMC_REG_CTO 0x18 +#define OMAP_MMC_REG_DTO 0x1c +#define OMAP_MMC_REG_DATA 0x20 +#define OMAP_MMC_REG_BLEN 0x24 +#define OMAP_MMC_REG_NBLK 0x28 +#define OMAP_MMC_REG_BUF 0x2c +#define OMAP_MMC_REG_SDIO 0x34 +#define OMAP_MMC_REG_REV 0x3c +#define OMAP_MMC_REG_RSP0 0x40 +#define OMAP_MMC_REG_RSP1 0x44 +#define OMAP_MMC_REG_RSP2 0x48 +#define OMAP_MMC_REG_RSP3 0x4c +#define OMAP_MMC_REG_RSP4 0x50 +#define OMAP_MMC_REG_RSP5 0x54 +#define OMAP_MMC_REG_RSP6 0x58 +#define OMAP_MMC_REG_RSP7 0x5c +#define OMAP_MMC_REG_IOSR 0x60 +#define OMAP_MMC_REG_SYSC 0x64 +#define OMAP_MMC_REG_SYSS 0x68 + +#define OMAP_MMC_STAT_CARD_ERR (1 << 14) +#define OMAP_MMC_STAT_CARD_IRQ (1 << 13) +#define OMAP_MMC_STAT_OCR_BUSY (1 << 12) +#define OMAP_MMC_STAT_A_EMPTY (1 << 11) +#define OMAP_MMC_STAT_A_FULL (1 << 10) +#define OMAP_MMC_STAT_CMD_CRC (1 << 8) +#define OMAP_MMC_STAT_CMD_TOUT (1 << 7) +#define OMAP_MMC_STAT_DATA_CRC (1 << 6) +#define OMAP_MMC_STAT_DATA_TOUT (1 << 5) +#define OMAP_MMC_STAT_END_BUSY (1 << 4) +#define OMAP_MMC_STAT_END_OF_DATA (1 << 3) +#define OMAP_MMC_STAT_CARD_BUSY (1 << 2) +#define OMAP_MMC_STAT_END_OF_CMD (1 << 0) + +#define OMAP_MMC_READ(base, reg) __raw_readw((base) + OMAP_MMC_REG_##reg) +#define OMAP_MMC_WRITE(base, reg, val) __raw_writew((val), (base) + OMAP_MMC_REG_##reg) + +/* + * Command types + */ +#define OMAP_MMC_CMDTYPE_BC 0 +#define OMAP_MMC_CMDTYPE_BCR 1 +#define OMAP_MMC_CMDTYPE_AC 2 +#define OMAP_MMC_CMDTYPE_ADTC 3 + +#endif Index: linux-2.6.15-mmc_omap/drivers/mmc/mmc.c =================================================================== --- linux-2.6.15-mmc_omap.orig/drivers/mmc/mmc.c 2006-01-25 16:11:46.000000000 -0400 +++ linux-2.6.15-mmc_omap/drivers/mmc/mmc.c 2006-01-25 16:25:45.000000000 -0400 @@ -392,7 +392,6 @@ static void mmc_deselect_cards(struct mm } } - static inline void mmc_delay(unsigned int ms) { if (ms < HZ / 1000) { - To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/