diff options
Diffstat (limited to 'drivers/mmc')
-rw-r--r-- | drivers/mmc/Kconfig | 9 | ||||
-rw-r--r-- | drivers/mmc/Makefile | 1 | ||||
-rw-r--r-- | drivers/mmc/au1xmmc.c | 1026 | ||||
-rw-r--r-- | drivers/mmc/au1xmmc.h | 96 | ||||
-rw-r--r-- | drivers/mmc/mmc.c | 565 | ||||
-rw-r--r-- | drivers/mmc/mmc.h | 5 | ||||
-rw-r--r-- | drivers/mmc/mmc_block.c | 15 | ||||
-rw-r--r-- | drivers/mmc/mmc_sysfs.c | 113 | ||||
-rw-r--r-- | drivers/mmc/mmci.c | 7 | ||||
-rw-r--r-- | drivers/mmc/pxamci.c | 26 | ||||
-rw-r--r-- | drivers/mmc/wbsd.c | 665 | ||||
-rw-r--r-- | drivers/mmc/wbsd.h | 27 |
12 files changed, 2154 insertions, 401 deletions
diff --git a/drivers/mmc/Kconfig b/drivers/mmc/Kconfig index 4991bbd054f3..c483a863b116 100644 --- a/drivers/mmc/Kconfig +++ b/drivers/mmc/Kconfig @@ -60,4 +60,13 @@ config MMC_WBSD If unsure, say N. +config MMC_AU1X + tristate "Alchemy AU1XX0 MMC Card Interface support" + depends on SOC_AU1X00 && MMC + help + This selects the AMD Alchemy(R) Multimedia card interface. + iIf you have a Alchemy platform with a MMC slot, say Y or M here. + + If unsure, say N. + endmenu diff --git a/drivers/mmc/Makefile b/drivers/mmc/Makefile index 89510c2086c7..e351e71146e9 100644 --- a/drivers/mmc/Makefile +++ b/drivers/mmc/Makefile @@ -18,5 +18,6 @@ obj-$(CONFIG_MMC_BLOCK) += mmc_block.o obj-$(CONFIG_MMC_ARMMMCI) += mmci.o obj-$(CONFIG_MMC_PXA) += pxamci.o obj-$(CONFIG_MMC_WBSD) += wbsd.o +obj-$(CONFIG_MMC_AU1X) += au1xmmc.o mmc_core-y := mmc.o mmc_queue.o mmc_sysfs.o diff --git a/drivers/mmc/au1xmmc.c b/drivers/mmc/au1xmmc.c new file mode 100644 index 000000000000..aaf04638054e --- /dev/null +++ b/drivers/mmc/au1xmmc.c @@ -0,0 +1,1026 @@ +/* + * linux/drivers/mmc/au1xmmc.c - AU1XX0 MMC driver + * + * Copyright (c) 2005, Advanced Micro Devices, Inc. + * + * Developed with help from the 2.4.30 MMC AU1XXX controller including + * the following copyright notices: + * Copyright (c) 2003-2004 Embedded Edge, LLC. + * Portions Copyright (C) 2002 Embedix, Inc + * Copyright 2002 Hewlett-Packard Company + + * 2.6 version of this driver inspired by: + * (drivers/mmc/wbsd.c) Copyright (C) 2004-2005 Pierre Ossman, + * All Rights Reserved. + * (drivers/mmc/pxa.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. + */ + +/* Why is a timer used to detect insert events? + * + * From the AU1100 MMC application guide: + * If the Au1100-based design is intended to support both MultiMediaCards + * and 1- or 4-data bit SecureDigital cards, then the solution is to + * connect a weak (560KOhm) pull-up resistor to connector pin 1. + * In doing so, a MMC card never enters SPI-mode communications, + * but now the SecureDigital card-detect feature of CD/DAT3 is ineffective + * (the low to high transition will not occur). + * + * So we use the timer to check the status manually. + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/mm.h> +#include <linux/interrupt.h> +#include <linux/dma-mapping.h> + +#include <linux/mmc/host.h> +#include <linux/mmc/protocol.h> +#include <asm/io.h> +#include <asm/mach-au1x00/au1000.h> +#include <asm/mach-au1x00/au1xxx_dbdma.h> +#include <asm/mach-au1x00/au1100_mmc.h> +#include <asm/scatterlist.h> + +#include <au1xxx.h> +#include "au1xmmc.h" + +#define DRIVER_NAME "au1xxx-mmc" + +/* Set this to enable special debugging macros */ +/* #define MMC_DEBUG */ + +#ifdef MMC_DEBUG +#define DEBUG(fmt, idx, args...) printk("au1xx(%d): DEBUG: " fmt, idx, ##args) +#else +#define DEBUG(fmt, idx, args...) +#endif + +const struct { + u32 iobase; + u32 tx_devid, rx_devid; + u16 bcsrpwr; + u16 bcsrstatus; + u16 wpstatus; +} au1xmmc_card_table[] = { + { SD0_BASE, DSCR_CMD0_SDMS_TX0, DSCR_CMD0_SDMS_RX0, + BCSR_BOARD_SD0PWR, BCSR_INT_SD0INSERT, BCSR_STATUS_SD0WP }, +#ifndef CONFIG_MIPS_DB1200 + { SD1_BASE, DSCR_CMD0_SDMS_TX1, DSCR_CMD0_SDMS_RX1, + BCSR_BOARD_DS1PWR, BCSR_INT_SD1INSERT, BCSR_STATUS_SD1WP } +#endif +}; + +#define AU1XMMC_CONTROLLER_COUNT \ + (sizeof(au1xmmc_card_table) / sizeof(au1xmmc_card_table[0])) + +/* This array stores pointers for the hosts (used by the IRQ handler) */ +struct au1xmmc_host *au1xmmc_hosts[AU1XMMC_CONTROLLER_COUNT]; +static int dma = 1; + +#ifdef MODULE +MODULE_PARM(dma, "i"); +MODULE_PARM_DESC(dma, "Use DMA engine for data transfers (0 = disabled)"); +#endif + +static inline void IRQ_ON(struct au1xmmc_host *host, u32 mask) +{ + u32 val = au_readl(HOST_CONFIG(host)); + val |= mask; + au_writel(val, HOST_CONFIG(host)); + au_sync(); +} + +static inline void FLUSH_FIFO(struct au1xmmc_host *host) +{ + u32 val = au_readl(HOST_CONFIG2(host)); + + au_writel(val | SD_CONFIG2_FF, HOST_CONFIG2(host)); + au_sync_delay(1); + + /* SEND_STOP will turn off clock control - this re-enables it */ + val &= ~SD_CONFIG2_DF; + + au_writel(val, HOST_CONFIG2(host)); + au_sync(); +} + +static inline void IRQ_OFF(struct au1xmmc_host *host, u32 mask) +{ + u32 val = au_readl(HOST_CONFIG(host)); + val &= ~mask; + au_writel(val, HOST_CONFIG(host)); + au_sync(); +} + +static inline void SEND_STOP(struct au1xmmc_host *host) +{ + + /* We know the value of CONFIG2, so avoid a read we don't need */ + u32 mask = SD_CONFIG2_EN; + + WARN_ON(host->status != HOST_S_DATA); + host->status = HOST_S_STOP; + + au_writel(mask | SD_CONFIG2_DF, HOST_CONFIG2(host)); + au_sync(); + + /* Send the stop commmand */ + au_writel(STOP_CMD, HOST_CMD(host)); +} + +static void au1xmmc_set_power(struct au1xmmc_host *host, int state) +{ + + u32 val = au1xmmc_card_table[host->id].bcsrpwr; + + bcsr->board &= ~val; + if (state) bcsr->board |= val; + + au_sync_delay(1); +} + +static inline int au1xmmc_card_inserted(struct au1xmmc_host *host) +{ + return (bcsr->sig_status & au1xmmc_card_table[host->id].bcsrstatus) + ? 1 : 0; +} + +static inline int au1xmmc_card_readonly(struct au1xmmc_host *host) +{ + return (bcsr->status & au1xmmc_card_table[host->id].wpstatus) + ? 1 : 0; +} + +static void au1xmmc_finish_request(struct au1xmmc_host *host) +{ + + struct mmc_request *mrq = host->mrq; + + host->mrq = NULL; + host->flags &= HOST_F_ACTIVE; + + host->dma.len = 0; + host->dma.dir = 0; + + host->pio.index = 0; + host->pio.offset = 0; + host->pio.len = 0; + + host->status = HOST_S_IDLE; + + bcsr->disk_leds |= (1 << 8); + + mmc_request_done(host->mmc, mrq); +} + +static void au1xmmc_tasklet_finish(unsigned long param) +{ + struct au1xmmc_host *host = (struct au1xmmc_host *) param; + au1xmmc_finish_request(host); +} + +static int au1xmmc_send_command(struct au1xmmc_host *host, int wait, + struct mmc_command *cmd) +{ + + u32 mmccmd = (cmd->opcode << SD_CMD_CI_SHIFT); + + switch(cmd->flags) { + case MMC_RSP_R1: + mmccmd |= SD_CMD_RT_1; + break; + case MMC_RSP_R1B: + mmccmd |= SD_CMD_RT_1B; + break; + case MMC_RSP_R2: + mmccmd |= SD_CMD_RT_2; + break; + case MMC_RSP_R3: + mmccmd |= SD_CMD_RT_3; + break; + } + + switch(cmd->opcode) { + case MMC_READ_SINGLE_BLOCK: + case SD_APP_SEND_SCR: + mmccmd |= SD_CMD_CT_2; + break; + case MMC_READ_MULTIPLE_BLOCK: + mmccmd |= SD_CMD_CT_4; + break; + case MMC_WRITE_BLOCK: + mmccmd |= SD_CMD_CT_1; + break; + + case MMC_WRITE_MULTIPLE_BLOCK: + mmccmd |= SD_CMD_CT_3; + break; + case MMC_STOP_TRANSMISSION: + mmccmd |= SD_CMD_CT_7; + break; + } + + au_writel(cmd->arg, HOST_CMDARG(host)); + au_sync(); + + if (wait) + IRQ_OFF(host, SD_CONFIG_CR); + + au_writel((mmccmd | SD_CMD_GO), HOST_CMD(host)); + au_sync(); + + /* Wait for the command to go on the line */ + + while(1) { + if (!(au_readl(HOST_CMD(host)) & SD_CMD_GO)) + break; + } + + /* Wait for the command to come back */ + + if (wait) { + u32 status = au_readl(HOST_STATUS(host)); + + while(!(status & SD_STATUS_CR)) + status = au_readl(HOST_STATUS(host)); + + /* Clear the CR status */ + au_writel(SD_STATUS_CR, HOST_STATUS(host)); + + IRQ_ON(host, SD_CONFIG_CR); + } + + return MMC_ERR_NONE; +} + +static void au1xmmc_data_complete(struct au1xmmc_host *host, u32 status) +{ + + struct mmc_request *mrq = host->mrq; + struct mmc_data *data; + u32 crc; + + WARN_ON(host->status != HOST_S_DATA && host->status != HOST_S_STOP); + + if (host->mrq == NULL) + return; + + data = mrq->cmd->data; + + if (status == 0) + status = au_readl(HOST_STATUS(host)); + + /* The transaction is really over when the SD_STATUS_DB bit is clear */ + + while((host->flags & HOST_F_XMIT) && (status & SD_STATUS_DB)) + status = au_readl(HOST_STATUS(host)); + + data->error = MMC_ERR_NONE; + dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len, host->dma.dir); + + /* Process any errors */ + + crc = (status & (SD_STATUS_WC | SD_STATUS_RC)); + if (host->flags & HOST_F_XMIT) + crc |= ((status & 0x07) == 0x02) ? 0 : 1; + + if (crc) + data->error = MMC_ERR_BADCRC; + + /* Clear the CRC bits */ + au_writel(SD_STATUS_WC | SD_STATUS_RC, HOST_STATUS(host)); + + data->bytes_xfered = 0; + + if (data->error == MMC_ERR_NONE) { + if (host->flags & HOST_F_DMA) { + u32 chan = DMA_CHANNEL(host); + + chan_tab_t *c = *((chan_tab_t **) chan); + au1x_dma_chan_t *cp = c->chan_ptr; + data->bytes_xfered = cp->ddma_bytecnt; + } + else + data->bytes_xfered = + (data->blocks * (1 << data->blksz_bits)) - + host->pio.len; + } + + au1xmmc_finish_request(host); +} + +static void au1xmmc_tasklet_data(unsigned long param) +{ + struct au1xmmc_host *host = (struct au1xmmc_host *) param; + + u32 status = au_readl(HOST_STATUS(host)); + au1xmmc_data_complete(host, status); +} + +#define AU1XMMC_MAX_TRANSFER 8 + +static void au1xmmc_send_pio(struct au1xmmc_host *host) +{ + + struct mmc_data *data = 0; + int sg_len, max, count = 0; + unsigned char *sg_ptr; + u32 status = 0; + struct scatterlist *sg; + + data = host->mrq->data; + + if (!(host->flags & HOST_F_XMIT)) + return; + + /* This is the pointer to the data buffer */ + sg = &data->sg[host->pio.index]; + sg_ptr = page_address(sg->page) + sg->offset + host->pio.offset; + + /* This is the space left inside the buffer */ + sg_len = data->sg[host->pio.index].length - host->pio.offset; + + /* Check to if we need less then the size of the sg_buffer */ + + max = (sg_len > host->pio.len) ? host->pio.len : sg_len; + if (max > AU1XMMC_MAX_TRANSFER) max = AU1XMMC_MAX_TRANSFER; + + for(count = 0; count < max; count++ ) { + unsigned char val; + + status = au_readl(HOST_STATUS(host)); + + if (!(status & SD_STATUS_TH)) + break; + + val = *sg_ptr++; + + au_writel((unsigned long) val, HOST_TXPORT(host)); + au_sync(); + } + + host->pio.len -= count; + host->pio.offset += count; + + if (count == sg_len) { + host->pio.index++; + host->pio.offset = 0; + } + + if (host->pio.len == 0) { + IRQ_OFF(host, SD_CONFIG_TH); + + if (host->flags & HOST_F_STOP) + SEND_STOP(host); + + tasklet_schedule(&host->data_task); + } +} + +static void au1xmmc_receive_pio(struct au1xmmc_host *host) +{ + + struct mmc_data *data = 0; + int sg_len = 0, max = 0, count = 0; + unsigned char *sg_ptr = 0; + u32 status = 0; + struct scatterlist *sg; + + data = host->mrq->data; + + if (!(host->flags & HOST_F_RECV)) + return; + + max = host->pio.len; + + if (host->pio.index < host->dma.len) { + sg = &data->sg[host->pio.index]; + sg_ptr = page_address(sg->page) + sg->offset + host->pio.offset; + + /* This is the space left inside the buffer */ + sg_len = sg_dma_len(&data->sg[host->pio.index]) - host->pio.offset; + + /* Check to if we need less then the size of the sg_buffer */ + if (sg_len < max) max = sg_len; + } + + if (max > AU1XMMC_MAX_TRANSFER) + max = AU1XMMC_MAX_TRANSFER; + + for(count = 0; count < max; count++ ) { + u32 val; + status = au_readl(HOST_STATUS(host)); + + if (!(status & SD_STATUS_NE)) + break; + + if (status & SD_STATUS_RC) { + DEBUG("RX CRC Error [%d + %d].\n", host->id, + host->pio.len, count); + break; + } + + if (status & SD_STATUS_RO) { + DEBUG("RX Overrun [%d + %d]\n", host->id, + host->pio.len, count); + break; + } + else if (status & SD_STATUS_RU) { + DEBUG("RX Underrun [%d + %d]\n", host->id, + host->pio.len, count); + break; + } + + val = au_readl(HOST_RXPORT(host)); + + if (sg_ptr) + *sg_ptr++ = (unsigned char) (val & 0xFF); + } + + host->pio.len -= count; + host->pio.offset += count; + + if (sg_len && count == sg_len) { + host->pio.index++; + host->pio.offset = 0; + } + + if (host->pio.len == 0) { + //IRQ_OFF(host, SD_CONFIG_RA | SD_CONFIG_RF); + IRQ_OFF(host, SD_CONFIG_NE); + + if (host->flags & HOST_F_STOP) + SEND_STOP(host); + + tasklet_schedule(&host->data_task); + } +} + +/* static void au1xmmc_cmd_complete + This is called when a command has been completed - grab the response + and check for errors. Then start the data transfer if it is indicated. +*/ + +static void au1xmmc_cmd_complete(struct au1xmmc_host *host, u32 status) +{ + + struct mmc_request *mrq = host->mrq; + struct mmc_command *cmd; + int trans; + + if (!host->mrq) + return; + + cmd = mrq->cmd; + cmd->error = MMC_ERR_NONE; + + if ((cmd->flags & MMC_RSP_MASK) == MMC_RSP_SHORT) { + + /* Techincally, we should be getting all 48 bits of the response + * (SD_RESP1 + SD_RESP2), but because our response omits the CRC, + * our data ends up being shifted 8 bits to the right. In this case, + * that means that the OSR data starts at bit 31, so we can just + * read RESP0 and return that + */ + + cmd->resp[0] = au_readl(host->iobase + SD_RESP0); + } + else if ((cmd->flags & MMC_RSP_MASK) == MMC_RSP_LONG) { + u32 r[4]; + int i; + + r[0] = au_readl(host->iobase + SD_RESP3); + r[1] = au_readl(host->iobase + SD_RESP2); + r[2] = au_readl(host->iobase + SD_RESP1); + r[3] = au_readl(host->iobase + SD_RESP0); + + /* The CRC is omitted from the response, so really we only got + * 120 bytes, but the engine expects 128 bits, so we have to shift + * things up + */ + + for(i = 0; i < 4; i++) { + cmd->resp[i] = (r[i] & 0x00FFFFFF) << 8; + if (i != 3) cmd->resp[i] |= (r[i + 1] & 0xFF000000) >> 24; + } + } + + /* Figure out errors */ + + if (status & (SD_STATUS_SC | SD_STATUS_WC | SD_STATUS_RC)) + cmd->error = MMC_ERR_BADCRC; + + trans = host->flags & (HOST_F_XMIT | HOST_F_RECV); + + if (!trans || cmd->error != MMC_ERR_NONE) { + + IRQ_OFF(host, SD_CONFIG_TH | SD_CONFIG_RA|SD_CONFIG_RF); + tasklet_schedule(&host->finish_task); + return; + } + + host->status = HOST_S_DATA; + + if (host->flags & HOST_F_DMA) { + u32 channel = DMA_CHANNEL(host); + + /* Start the DMA as soon as the buffer gets something in it */ + + if (host->flags & HOST_F_RECV) { + u32 mask = SD_STATUS_DB | SD_STATUS_NE; + + while((status & mask) != mask) + status = au_readl(HOST_STATUS(host)); + } + + au1xxx_dbdma_start(channel); + } +} + +static void au1xmmc_set_clock(struct au1xmmc_host *host, int rate) +{ + + unsigned int pbus = get_au1x00_speed(); + unsigned int divisor; + u32 config; + + /* From databook: + divisor = ((((cpuclock / sbus_divisor) / 2) / mmcclock) / 2) - 1 + */ + + pbus /= ((au_readl(SYS_POWERCTRL) & 0x3) + 2); + pbus /= 2; + + divisor = ((pbus / rate) / 2) - 1; + + config = au_readl(HOST_CONFIG(host)); + + config &= ~(SD_CONFIG_DIV); + config |= (divisor & SD_CONFIG_DIV) | SD_CONFIG_DE; + + au_writel(config, HOST_CONFIG(host)); + au_sync(); +} + +static int +au1xmmc_prepare_data(struct au1xmmc_host *host, struct mmc_data *data) +{ + + int datalen = data->blocks * (1 << data->blksz_bits); + + if (dma != 0) + host->flags |= HOST_F_DMA; + + if (data->flags & MMC_DATA_READ) + host->flags |= HOST_F_RECV; + else + host->flags |= HOST_F_XMIT; + + if (host->mrq->stop) + host->flags |= HOST_F_STOP; + + host->dma.dir = DMA_BIDIRECTIONAL; + + host->dma.len = dma_map_sg(mmc_dev(host->mmc), data->sg, + data->sg_len, host->dma.dir); + + if (host->dma.len == 0) + return MMC_ERR_TIMEOUT; + + au_writel((1 << data->blksz_bits) - 1, HOST_BLKSIZE(host)); + + if (host->flags & HOST_F_DMA) { + int i; + u32 channel = DMA_CHANNEL(host); + + au1xxx_dbdma_stop(channel); + + for(i = 0; i < host->dma.len; i++) { + u32 ret = 0, flags = DDMA_FLAGS_NOIE; + struct scatterlist *sg = &data->sg[i]; + int sg_len = sg->length; + + int len = (datalen > sg_len) ? sg_len : datalen; + + if (i == host->dma.len - 1) + flags = DDMA_FLAGS_IE; + + if (host->flags & HOST_F_XMIT){ + ret = au1xxx_dbdma_put_source_flags(channel, + (void *) (page_address(sg->page) + + sg->offset), + len, flags); + } + else { + ret = au1xxx_dbdma_put_dest_flags(channel, + (void *) (page_address(sg->page) + + sg->offset), + len, flags); + } + + if (!ret) + goto dataerr; + + datalen -= len; + } + } + else { + host->pio.index = 0; + host->pio.offset = 0; + host->pio.len = datalen; + + if (host->flags & HOST_F_XMIT) + IRQ_ON(host, SD_CONFIG_TH); + else + IRQ_ON(host, SD_CONFIG_NE); + //IRQ_ON(host, SD_CONFIG_RA|SD_CONFIG_RF); + } + + return MMC_ERR_NONE; + + dataerr: + dma_unmap_sg(mmc_dev(host->mmc),data->sg,data->sg_len,host->dma.dir); + return MMC_ERR_TIMEOUT; +} + +/* static void au1xmmc_request + This actually starts a command or data transaction +*/ + +static void au1xmmc_request(struct mmc_host* mmc, struct mmc_request* mrq) +{ + + struct au1xmmc_host *host = mmc_priv(mmc); + int ret = MMC_ERR_NONE; + + WARN_ON(irqs_disabled()); + WARN_ON(host->status != HOST_S_IDLE); + + host->mrq = mrq; + host->status = HOST_S_CMD; + + bcsr->disk_leds &= ~(1 << 8); + + if (mrq->data) { + FLUSH_FIFO(host); + ret = au1xmmc_prepare_data(host, mrq->data); + } + + if (ret == MMC_ERR_NONE) + ret = au1xmmc_send_command(host, 0, mrq->cmd); + + if (ret != MMC_ERR_NONE) { + mrq->cmd->error = ret; + au1xmmc_finish_request(host); + } +} + +static void au1xmmc_reset_controller(struct au1xmmc_host *host) +{ + + /* Apply the clock */ + au_writel(SD_ENABLE_CE, HOST_ENABLE(host)); + au_sync_delay(1); + + au_writel(SD_ENABLE_R | SD_ENABLE_CE, HOST_ENABLE(host)); + au_sync_delay(5); + + au_writel(~0, HOST_STATUS(host)); + au_sync(); + + au_writel(0, HOST_BLKSIZE(host)); + au_writel(0x001fffff, HOST_TIMEOUT(host)); + au_sync(); + + au_writel(SD_CONFIG2_EN, HOST_CONFIG2(host)); + au_sync(); + + au_writel(SD_CONFIG2_EN | SD_CONFIG2_FF, HOST_CONFIG2(host)); + au_sync_delay(1); + + au_writel(SD_CONFIG2_EN, HOST_CONFIG2(host)); + au_sync(); + + /* Configure interrupts */ + au_writel(AU1XMMC_INTERRUPTS, HOST_CONFIG(host)); + au_sync(); +} + + +static void au1xmmc_set_ios(struct mmc_host* mmc, struct mmc_ios* ios) +{ + struct au1xmmc_host *host = mmc_priv(mmc); + + DEBUG("set_ios (power=%u, clock=%uHz, vdd=%u, mode=%u)\n", + host->id, ios->power_mode, ios->clock, ios->vdd, + ios->bus_mode); + + if (ios->power_mode == MMC_POWER_OFF) + au1xmmc_set_power(host, 0); + else if (ios->power_mode == MMC_POWER_ON) { + au1xmmc_set_power(host, 1); + } + + if (ios->clock && ios->clock != host->clock) { + au1xmmc_set_clock(host, ios->clock); + host->clock = ios->clock; + } +} + +static void au1xmmc_dma_callback(int irq, void *dev_id, struct pt_regs *regs) +{ + struct au1xmmc_host *host = (struct au1xmmc_host *) dev_id; + u32 status; + + /* Avoid spurious interrupts */ + + if (!host->mrq) + return; + + if (host->flags & HOST_F_STOP) + SEND_STOP(host); + + tasklet_schedule(&host->data_task); +} + +#define STATUS_TIMEOUT (SD_STATUS_RAT | SD_STATUS_DT) +#define STATUS_DATA_IN (SD_STATUS_NE) +#define STATUS_DATA_OUT (SD_STATUS_TH) + +static irqreturn_t au1xmmc_irq(int irq, void *dev_id, struct pt_regs *regs) +{ + + u32 status; + int i, ret = 0; + + disable_irq(AU1100_SD_IRQ); + + for(i = 0; i < AU1XMMC_CONTROLLER_COUNT; i++) { + struct au1xmmc_host * host = au1xmmc_hosts[i]; + u32 handled = 1; + + status = au_readl(HOST_STATUS(host)); + + if (host->mrq && (status & STATUS_TIMEOUT)) { + if (status & SD_STATUS_RAT) + host->mrq->cmd->error = MMC_ERR_TIMEOUT; + + else if (status & SD_STATUS_DT) + host->mrq->data->error = MMC_ERR_TIMEOUT; + + /* In PIO mode, interrupts might still be enabled */ + IRQ_OFF(host, SD_CONFIG_NE | SD_CONFIG_TH); + + //IRQ_OFF(host, SD_CONFIG_TH|SD_CONFIG_RA|SD_CONFIG_RF); + tasklet_schedule(&host->finish_task); + } +#if 0 + else if (status & SD_STATUS_DD) { + + /* Sometimes we get a DD before a NE in PIO mode */ + + if (!(host->flags & HOST_F_DMA) && + (status & SD_STATUS_NE)) + au1xmmc_receive_pio(host); + else { + au1xmmc_data_complete(host, status); + //tasklet_schedule(&host->data_task); + } + } +#endif + else if (status & (SD_STATUS_CR)) { + if (host->status == HOST_S_CMD) + au1xmmc_cmd_complete(host,status); + } + else if (!(host->flags & HOST_F_DMA)) { + if ((host->flags & HOST_F_XMIT) && + (status & STATUS_DATA_OUT)) + au1xmmc_send_pio(host); + else if ((host->flags & HOST_F_RECV) && + (status & STATUS_DATA_IN)) + au1xmmc_receive_pio(host); + } + else if (status & 0x203FBC70) { + DEBUG("Unhandled status %8.8x\n", host->id, status); + handled = 0; + } + + au_writel(status, HOST_STATUS(host)); + au_sync(); + + ret |= handled; + } + + enable_irq(AU1100_SD_IRQ); + return ret; +} + +static void au1xmmc_poll_event(unsigned long arg) +{ + struct au1xmmc_host *host = (struct au1xmmc_host *) arg; + + int card = au1xmmc_card_inserted(host); + int controller = (host->flags & HOST_F_ACTIVE) ? 1 : 0; + + if (card != controller) { + host->flags &= ~HOST_F_ACTIVE; + if (card) host->flags |= HOST_F_ACTIVE; + mmc_detect_change(host->mmc, 0); + } + + if (host->mrq != NULL) { + u32 status = au_readl(HOST_STATUS(host)); + DEBUG("PENDING - %8.8x\n", host->id, status); + } + + mod_timer(&host->timer, jiffies + AU1XMMC_DETECT_TIMEOUT); +} + +static dbdev_tab_t au1xmmc_mem_dbdev = +{ + DSCR_CMD0_ALWAYS, DEV_FLAGS_ANYUSE, 0, 8, 0x00000000, 0, 0 +}; + +static void au1xmmc_init_dma(struct au1xmmc_host *host) +{ + + u32 rxchan, txchan; + + int txid = au1xmmc_card_table[host->id].tx_devid; + int rxid = au1xmmc_card_table[host->id].rx_devid; + + /* DSCR_CMD0_ALWAYS has a stride of 32 bits, we need a stride + of 8 bits. And since devices are shared, we need to create + our own to avoid freaking out other devices + */ + + int memid = au1xxx_ddma_add_device(&au1xmmc_mem_dbdev); + + txchan = au1xxx_dbdma_chan_alloc(memid, txid, + au1xmmc_dma_callback, (void *) host); + + rxchan = au1xxx_dbdma_chan_alloc(rxid, memid, + au1xmmc_dma_callback, (void *) host); + + au1xxx_dbdma_set_devwidth(txchan, 8); + au1xxx_dbdma_set_devwidth(rxchan, 8); + + au1xxx_dbdma_ring_alloc(txchan, AU1XMMC_DESCRIPTOR_COUNT); + au1xxx_dbdma_ring_alloc(rxchan, AU1XMMC_DESCRIPTOR_COUNT); + + host->tx_chan = txchan; + host->rx_chan = rxchan; +} + +struct mmc_host_ops au1xmmc_ops = { + .request = au1xmmc_request, + .set_ios = au1xmmc_set_ios, +}; + +static int au1xmmc_probe(struct device *dev) +{ + + int i, ret = 0; + + /* THe interrupt is shared among all controllers */ + ret = request_irq(AU1100_SD_IRQ, au1xmmc_irq, SA_INTERRUPT, "MMC", 0); + + if (ret) { + printk(DRIVER_NAME "ERROR: Couldn't get int %d: %d\n", + AU1100_SD_IRQ, ret); + return -ENXIO; + } + + disable_irq(AU1100_SD_IRQ); + + for(i = 0; i < AU1XMMC_CONTROLLER_COUNT; i++) { + struct mmc_host *mmc = mmc_alloc_host(sizeof(struct au1xmmc_host), dev); + struct au1xmmc_host *host = 0; + + if (!mmc) { + printk(DRIVER_NAME "ERROR: no mem for host %d\n", i); + au1xmmc_hosts[i] = 0; + continue; + } + + mmc->ops = &au1xmmc_ops; + + mmc->f_min = 450000; + mmc->f_max = 24000000; + + mmc->max_seg_size = AU1XMMC_DESCRIPTOR_SIZE; + mmc->max_phys_segs = AU1XMMC_DESCRIPTOR_COUNT; + + mmc->ocr_avail = AU1XMMC_OCR; + + host = mmc_priv(mmc); + host->mmc = mmc; + + host->id = i; + host->iobase = au1xmmc_card_table[host->id].iobase; + host->clock = 0; + host->power_mode = MMC_POWER_OFF; + + host->flags = au1xmmc_card_inserted(host) ? HOST_F_ACTIVE : 0; + host->status = HOST_S_IDLE; + + init_timer(&host->timer); + + host->timer.function = au1xmmc_poll_event; + host->timer.data = (unsigned long) host; + host->timer.expires = jiffies + AU1XMMC_DETECT_TIMEOUT; + + tasklet_init(&host->data_task, au1xmmc_tasklet_data, + (unsigned long) host); + + tasklet_init(&host->finish_task, au1xmmc_tasklet_finish, + (unsigned long) host); + + spin_lock_init(&host->lock); + + if (dma != 0) + au1xmmc_init_dma(host); + + au1xmmc_reset_controller(host); + + mmc_add_host(mmc); + au1xmmc_hosts[i] = host; + + add_timer(&host->timer); + + printk(KERN_INFO DRIVER_NAME ": MMC Controller %d set up at %8.8X (mode=%s)\n", + host->id, host->iobase, dma ? "dma" : "pio"); + } + + enable_irq(AU1100_SD_IRQ); + + return 0; +} + +static int au1xmmc_remove(struct device *dev) +{ + + int i; + + disable_irq(AU1100_SD_IRQ); + + for(i = 0; i < AU1XMMC_CONTROLLER_COUNT; i++) { + struct au1xmmc_host *host = au1xmmc_hosts[i]; + if (!host) continue; + + tasklet_kill(&host->data_task); + tasklet_kill(&host->finish_task); + + del_timer_sync(&host->timer); + au1xmmc_set_power(host, 0); + + mmc_remove_host(host->mmc); + + au1xxx_dbdma_chan_free(host->tx_chan); + au1xxx_dbdma_chan_free(host->rx_chan); + + au_writel(0x0, HOST_ENABLE(host)); + au_sync(); + } + + free_irq(AU1100_SD_IRQ, 0); + return 0; +} + +static struct device_driver au1xmmc_driver = { + .name = DRIVER_NAME, + .bus = &platform_bus_type, + .probe = au1xmmc_probe, + .remove = au1xmmc_remove, + .suspend = NULL, + .resume = NULL +}; + +static int __init au1xmmc_init(void) +{ + return driver_register(&au1xmmc_driver); +} + +static void __exit au1xmmc_exit(void) +{ + driver_unregister(&au1xmmc_driver); +} + +module_init(au1xmmc_init); +module_exit(au1xmmc_exit); + +#ifdef MODULE +MODULE_AUTHOR("Advanced Micro Devices, Inc"); +MODULE_DESCRIPTION("MMC/SD driver for the Alchemy Au1XXX"); +MODULE_LICENSE("GPL"); +#endif + diff --git a/drivers/mmc/au1xmmc.h b/drivers/mmc/au1xmmc.h new file mode 100644 index 000000000000..341cbdf0baca --- /dev/null +++ b/drivers/mmc/au1xmmc.h @@ -0,0 +1,96 @@ +#ifndef _AU1XMMC_H_ +#define _AU1XMMC_H_ + +/* Hardware definitions */ + +#define AU1XMMC_DESCRIPTOR_COUNT 1 +#define AU1XMMC_DESCRIPTOR_SIZE 2048 + +#define AU1XMMC_OCR ( MMC_VDD_27_28 | MMC_VDD_28_29 | MMC_VDD_29_30 | \ + MMC_VDD_30_31 | MMC_VDD_31_32 | MMC_VDD_32_33 | \ + MMC_VDD_33_34 | MMC_VDD_34_35 | MMC_VDD_35_36) + +/* Easy access macros */ + +#define HOST_STATUS(h) ((h)->iobase + SD_STATUS) +#define HOST_CONFIG(h) ((h)->iobase + SD_CONFIG) +#define HOST_ENABLE(h) ((h)->iobase + SD_ENABLE) +#define HOST_TXPORT(h) ((h)->iobase + SD_TXPORT) +#define HOST_RXPORT(h) ((h)->iobase + SD_RXPORT) +#define HOST_CMDARG(h) ((h)->iobase + SD_CMDARG) +#define HOST_BLKSIZE(h) ((h)->iobase + SD_BLKSIZE) +#define HOST_CMD(h) ((h)->iobase + SD_CMD) +#define HOST_CONFIG2(h) ((h)->iobase + SD_CONFIG2) +#define HOST_TIMEOUT(h) ((h)->iobase + SD_TIMEOUT) +#define HOST_DEBUG(h) ((h)->iobase + SD_DEBUG) + +#define DMA_CHANNEL(h) \ + ( ((h)->flags & HOST_F_XMIT) ? (h)->tx_chan : (h)->rx_chan) + +/* This gives us a hard value for the stop command that we can write directly + * to the command register + */ + +#define STOP_CMD (SD_CMD_RT_1B|SD_CMD_CT_7|(0xC << SD_CMD_CI_SHIFT)|SD_CMD_GO) + +/* This is the set of interrupts that we configure by default */ + +#if 0 +#define AU1XMMC_INTERRUPTS (SD_CONFIG_SC | SD_CONFIG_DT | SD_CONFIG_DD | \ + SD_CONFIG_RAT | SD_CONFIG_CR | SD_CONFIG_I) +#endif + +#define AU1XMMC_INTERRUPTS (SD_CONFIG_SC | SD_CONFIG_DT | \ + SD_CONFIG_RAT | SD_CONFIG_CR | SD_CONFIG_I) +/* The poll event (looking for insert/remove events runs twice a second */ +#define AU1XMMC_DETECT_TIMEOUT (HZ/2) + +struct au1xmmc_host { + struct mmc_host *mmc; + struct mmc_request *mrq; + + u32 id; + + u32 flags; + u32 iobase; + u32 clock; + u32 bus_width; + u32 power_mode; + + int status; + + struct { + int len; + int dir; + } dma; + + struct { + int index; + int offset; + int len; + } pio; + + u32 tx_chan; + u32 rx_chan; + + struct timer_list timer; + struct tasklet_struct finish_task; + struct tasklet_struct data_task; + + spinlock_t lock; +}; + +/* Status flags used by the host structure */ + +#define HOST_F_XMIT 0x0001 +#define HOST_F_RECV 0x0002 +#define HOST_F_DMA 0x0010 +#define HOST_F_ACTIVE 0x0100 +#define HOST_F_STOP 0x1000 + +#define HOST_S_IDLE 0x0001 +#define HOST_S_CMD 0x0002 +#define HOST_S_DATA 0x0003 +#define HOST_S_STOP 0x0004 + +#endif diff --git a/drivers/mmc/mmc.c b/drivers/mmc/mmc.c index eeb9f6668e69..ceae379a4d4c 100644 --- a/drivers/mmc/mmc.c +++ b/drivers/mmc/mmc.c @@ -2,6 +2,8 @@ * linux/drivers/mmc/mmc.c * * Copyright (C) 2003-2004 Russell King, All Rights Reserved. + * SD support Copyright (C) 2004 Ian Molton, All Rights Reserved. + * SD support Copyright (C) 2005 Pierre Ossman, 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 @@ -16,6 +18,8 @@ #include <linux/delay.h> #include <linux/pagemap.h> #include <linux/err.h> +#include <asm/scatterlist.h> +#include <linux/scatterlist.h> #include <linux/mmc/card.h> #include <linux/mmc/host.h> @@ -172,7 +176,81 @@ int mmc_wait_for_cmd(struct mmc_host *host, struct mmc_command *cmd, int retries EXPORT_SYMBOL(mmc_wait_for_cmd); +/** + * mmc_wait_for_app_cmd - start an application command and wait for + completion + * @host: MMC host to start command + * @rca: RCA to send MMC_APP_CMD to + * @cmd: MMC command to start + * @retries: maximum number of retries + * + * Sends a MMC_APP_CMD, checks the card response, sends the command + * in the parameter and waits for it to complete. Return any error + * that occurred while the command was executing. Do not attempt to + * parse the response. + */ +int mmc_wait_for_app_cmd(struct mmc_host *host, unsigned int rca, + struct mmc_command *cmd, int retries) +{ + struct mmc_request mrq; + struct mmc_command appcmd; + + int i, err; + + BUG_ON(host->card_busy == NULL); + BUG_ON(retries < 0); + + err = MMC_ERR_INVALID; + + /* + * We have to resend MMC_APP_CMD for each attempt so + * we cannot use the retries field in mmc_command. + */ + for (i = 0;i <= retries;i++) { + memset(&mrq, 0, sizeof(struct mmc_request)); + + appcmd.opcode = MMC_APP_CMD; + appcmd.arg = rca << 16; + appcmd.flags = MMC_RSP_R1; + appcmd.retries = 0; + memset(appcmd.resp, 0, sizeof(appcmd.resp)); + appcmd.data = NULL; + + mrq.cmd = &appcmd; + appcmd.data = NULL; + + mmc_wait_for_req(host, &mrq); + + if (appcmd.error) { + err = appcmd.error; + continue; + } + + /* Check that card supported application commands */ + if (!(appcmd.resp[0] & R1_APP_CMD)) + return MMC_ERR_FAILED; + + memset(&mrq, 0, sizeof(struct mmc_request)); + + memset(cmd->resp, 0, sizeof(cmd->resp)); + cmd->retries = 0; + + mrq.cmd = cmd; + cmd->data = NULL; + + mmc_wait_for_req(host, &mrq); + + err = cmd->error; + if (cmd->error == MMC_ERR_NONE) + break; + } + return err; +} + +EXPORT_SYMBOL(mmc_wait_for_app_cmd); + +static int mmc_select_card(struct mmc_host *host, struct mmc_card *card); /** * __mmc_claim_host - exclusively claim a host @@ -206,16 +284,10 @@ int __mmc_claim_host(struct mmc_host *host, struct mmc_card *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_R1; - - err = mmc_wait_for_cmd(host, &cmd, CMD_RETRIES); + if (card != (void *)-1) { + err = mmc_select_card(host, card); + if (err != MMC_ERR_NONE) + return err; } return err; @@ -245,6 +317,63 @@ void mmc_release_host(struct mmc_host *host) EXPORT_SYMBOL(mmc_release_host); +static int mmc_select_card(struct mmc_host *host, struct mmc_card *card) +{ + int err; + struct mmc_command cmd; + + BUG_ON(host->card_busy == NULL); + + if (host->card_selected == card) + return MMC_ERR_NONE; + + host->card_selected = card; + + cmd.opcode = MMC_SELECT_CARD; + cmd.arg = card->rca << 16; + cmd.flags = MMC_RSP_R1; + + err = mmc_wait_for_cmd(host, &cmd, CMD_RETRIES); + if (err != MMC_ERR_NONE) + return err; + + /* + * Default bus width is 1 bit. + */ + host->ios.bus_width = MMC_BUS_WIDTH_1; + + /* + * We can only change the bus width of the selected + * card so therefore we have to put the handling + * here. + */ + if (host->caps & MMC_CAP_4_BIT_DATA) { + /* + * The card is in 1 bit mode by default so + * we only need to change if it supports the + * wider version. + */ + if (mmc_card_sd(card) && + (card->scr.bus_widths & SD_SCR_BUS_WIDTH_4)) { + struct mmc_command cmd; + cmd.opcode = SD_APP_SET_BUS_WIDTH; + cmd.arg = SD_BUS_WIDTH_4; + cmd.flags = MMC_RSP_R1; + + err = mmc_wait_for_app_cmd(host, card->rca, &cmd, + CMD_RETRIES); + if (err != MMC_ERR_NONE) + return err; + + host->ios.bus_width = MMC_BUS_WIDTH_4; + } + } + + host->ops->set_ios(host, &host->ios); + + return MMC_ERR_NONE; +} + /* * Ensure that no card is selected. */ @@ -322,48 +451,69 @@ static void mmc_decode_cid(struct mmc_card *card) memset(&card->cid, 0, sizeof(struct mmc_cid)); - /* - * The selection of the format here is guesswork based upon - * information people have sent to date. - */ - switch (card->csd.mmca_vsn) { - case 0: /* MMC v1.? */ - case 1: /* MMC v1.4 */ - card->cid.manfid = UNSTUFF_BITS(resp, 104, 24); - card->cid.prod_name[0] = UNSTUFF_BITS(resp, 96, 8); - card->cid.prod_name[1] = UNSTUFF_BITS(resp, 88, 8); - card->cid.prod_name[2] = UNSTUFF_BITS(resp, 80, 8); - card->cid.prod_name[3] = UNSTUFF_BITS(resp, 72, 8); - card->cid.prod_name[4] = UNSTUFF_BITS(resp, 64, 8); - card->cid.prod_name[5] = UNSTUFF_BITS(resp, 56, 8); - card->cid.prod_name[6] = UNSTUFF_BITS(resp, 48, 8); - card->cid.hwrev = UNSTUFF_BITS(resp, 44, 4); - card->cid.fwrev = UNSTUFF_BITS(resp, 40, 4); - card->cid.serial = UNSTUFF_BITS(resp, 16, 24); - card->cid.month = UNSTUFF_BITS(resp, 12, 4); - card->cid.year = UNSTUFF_BITS(resp, 8, 4) + 1997; - break; - - case 2: /* MMC v2.x ? */ - case 3: /* MMC v3.x ? */ - card->cid.manfid = UNSTUFF_BITS(resp, 120, 8); - card->cid.oemid = UNSTUFF_BITS(resp, 104, 16); - card->cid.prod_name[0] = UNSTUFF_BITS(resp, 96, 8); - card->cid.prod_name[1] = UNSTUFF_BITS(resp, 88, 8); - card->cid.prod_name[2] = UNSTUFF_BITS(resp, 80, 8); - card->cid.prod_name[3] = UNSTUFF_BITS(resp, 72, 8); - card->cid.prod_name[4] = UNSTUFF_BITS(resp, 64, 8); - card->cid.prod_name[5] = UNSTUFF_BITS(resp, 56, 8); - card->cid.serial = UNSTUFF_BITS(resp, 16, 32); - card->cid.month = UNSTUFF_BITS(resp, 12, 4); - card->cid.year = UNSTUFF_BITS(resp, 8, 4) + 1997; - break; - - default: - printk("%s: card has unknown MMCA version %d\n", - card->host->host_name, card->csd.mmca_vsn); - mmc_card_set_bad(card); - break; + if (mmc_card_sd(card)) { + /* + * SD doesn't currently have a version field so we will + * have to assume we can parse this. + */ + card->cid.manfid = UNSTUFF_BITS(resp, 120, 8); + card->cid.oemid = UNSTUFF_BITS(resp, 104, 16); + card->cid.prod_name[0] = UNSTUFF_BITS(resp, 96, 8); + card->cid.prod_name[1] = UNSTUFF_BITS(resp, 88, 8); + card->cid.prod_name[2] = UNSTUFF_BITS(resp, 80, 8); + card->cid.prod_name[3] = UNSTUFF_BITS(resp, 72, 8); + card->cid.prod_name[4] = UNSTUFF_BITS(resp, 64, 8); + card->cid.hwrev = UNSTUFF_BITS(resp, 60, 4); + card->cid.fwrev = UNSTUFF_BITS(resp, 56, 4); + card->cid.serial = UNSTUFF_BITS(resp, 24, 32); + card->cid.year = UNSTUFF_BITS(resp, 12, 8); + card->cid.month = UNSTUFF_BITS(resp, 8, 4); + + card->cid.year += 2000; /* SD cards year offset */ + } else { + /* + * The selection of the format here is based upon published + * specs from sandisk and from what people have reported. + */ + switch (card->csd.mmca_vsn) { + case 0: /* MMC v1.0 - v1.2 */ + case 1: /* MMC v1.4 */ + card->cid.manfid = UNSTUFF_BITS(resp, 104, 24); + card->cid.prod_name[0] = UNSTUFF_BITS(resp, 96, 8); + card->cid.prod_name[1] = UNSTUFF_BITS(resp, 88, 8); + card->cid.prod_name[2] = UNSTUFF_BITS(resp, 80, 8); + card->cid.prod_name[3] = UNSTUFF_BITS(resp, 72, 8); + card->cid.prod_name[4] = UNSTUFF_BITS(resp, 64, 8); + card->cid.prod_name[5] = UNSTUFF_BITS(resp, 56, 8); + card->cid.prod_name[6] = UNSTUFF_BITS(resp, 48, 8); + card->cid.hwrev = UNSTUFF_BITS(resp, 44, 4); + card->cid.fwrev = UNSTUFF_BITS(resp, 40, 4); + card->cid.serial = UNSTUFF_BITS(resp, 16, 24); + card->cid.month = UNSTUFF_BITS(resp, 12, 4); + card->cid.year = UNSTUFF_BITS(resp, 8, 4) + 1997; + break; + + case 2: /* MMC v2.0 - v2.2 */ + case 3: /* MMC v3.1 - v3.3 */ + card->cid.manfid = UNSTUFF_BITS(resp, 120, 8); + card->cid.oemid = UNSTUFF_BITS(resp, 104, 16); + card->cid.prod_name[0] = UNSTUFF_BITS(resp, 96, 8); + card->cid.prod_name[1] = UNSTUFF_BITS(resp, 88, 8); + card->cid.prod_name[2] = UNSTUFF_BITS(resp, 80, 8); + card->cid.prod_name[3] = UNSTUFF_BITS(resp, 72, 8); + card->cid.prod_name[4] = UNSTUFF_BITS(resp, 64, 8); + card->cid.prod_name[5] = UNSTUFF_BITS(resp, 56, 8); + card->cid.serial = UNSTUFF_BITS(resp, 16, 32); + card->cid.month = UNSTUFF_BITS(resp, 12, 4); + card->cid.year = UNSTUFF_BITS(resp, 8, 4) + 1997; + break; + + default: + printk("%s: card has unknown MMCA version %d\n", + mmc_hostname(card->host), card->csd.mmca_vsn); + mmc_card_set_bad(card); + break; + } } } @@ -376,34 +526,86 @@ static void mmc_decode_csd(struct mmc_card *card) unsigned int e, m, csd_struct; u32 *resp = card->raw_csd; - /* - * We only understand CSD structure v1.1 and v2. - * v2 has extra information in bits 15, 11 and 10. - */ - csd_struct = UNSTUFF_BITS(resp, 126, 2); - if (csd_struct != 1 && csd_struct != 2) { - printk("%s: unrecognised CSD structure version %d\n", - card->host->host_name, csd_struct); - mmc_card_set_bad(card); - return; + if (mmc_card_sd(card)) { + csd_struct = UNSTUFF_BITS(resp, 126, 2); + if (csd_struct != 0) { + printk("%s: unrecognised CSD structure version %d\n", + mmc_hostname(card->host), csd_struct); + mmc_card_set_bad(card); + return; + } + + m = UNSTUFF_BITS(resp, 115, 4); + e = UNSTUFF_BITS(resp, 112, 3); + csd->tacc_ns = (tacc_exp[e] * tacc_mant[m] + 9) / 10; + csd->tacc_clks = UNSTUFF_BITS(resp, 104, 8) * 100; + + m = UNSTUFF_BITS(resp, 99, 4); + e = UNSTUFF_BITS(resp, 96, 3); + csd->max_dtr = tran_exp[e] * tran_mant[m]; + csd->cmdclass = UNSTUFF_BITS(resp, 84, 12); + + e = UNSTUFF_BITS(resp, 47, 3); + m = UNSTUFF_BITS(resp, 62, 12); + csd->capacity = (1 + m) << (e + 2); + + csd->read_blkbits = UNSTUFF_BITS(resp, 80, 4); + } else { + /* + * We only understand CSD structure v1.1 and v1.2. + * v1.2 has extra information in bits 15, 11 and 10. + */ + csd_struct = UNSTUFF_BITS(resp, 126, 2); + if (csd_struct != 1 && csd_struct != 2) { + printk("%s: unrecognised CSD structure version %d\n", + mmc_hostname(card->host), csd_struct); + mmc_card_set_bad(card); + return; + } + + csd->mmca_vsn = UNSTUFF_BITS(resp, 122, 4); + m = UNSTUFF_BITS(resp, 115, 4); + e = UNSTUFF_BITS(resp, 112, 3); + csd->tacc_ns = (tacc_exp[e] * tacc_mant[m] + 9) / 10; + csd->tacc_clks = UNSTUFF_BITS(resp, 104, 8) * 100; + + m = UNSTUFF_BITS(resp, 99, 4); + e = UNSTUFF_BITS(resp, 96, 3); + csd->max_dtr = tran_exp[e] * tran_mant[m]; + csd->cmdclass = UNSTUFF_BITS(resp, 84, 12); + + e = UNSTUFF_BITS(resp, 47, 3); + m = UNSTUFF_BITS(resp, 62, 12); + csd->capacity = (1 + m) << (e + 2); + + csd->read_blkbits = UNSTUFF_BITS(resp, 80, 4); } +} - csd->mmca_vsn = UNSTUFF_BITS(resp, 122, 4); - m = UNSTUFF_BITS(resp, 115, 4); - e = UNSTUFF_BITS(resp, 112, 3); - csd->tacc_ns = (tacc_exp[e] * tacc_mant[m] + 9) / 10; - csd->tacc_clks = UNSTUFF_BITS(resp, 104, 8) * 100; +/* + * Given a 64-bit response, decode to our card SCR structure. + */ +static void mmc_decode_scr(struct mmc_card *card) +{ + struct sd_scr *scr = &card->scr; + unsigned int scr_struct; + u32 resp[4]; + + BUG_ON(!mmc_card_sd(card)); - m = UNSTUFF_BITS(resp, 99, 4); - e = UNSTUFF_BITS(resp, 96, 3); - csd->max_dtr = tran_exp[e] * tran_mant[m]; - csd->cmdclass = UNSTUFF_BITS(resp, 84, 12); + resp[3] = card->raw_scr[1]; + resp[2] = card->raw_scr[0]; - e = UNSTUFF_BITS(resp, 47, 3); - m = UNSTUFF_BITS(resp, 62, 12); - csd->capacity = (1 + m) << (e + 2); + scr_struct = UNSTUFF_BITS(resp, 60, 4); + if (scr_struct != 0) { + printk("%s: unrecognised SCR structure version %d\n", + mmc_hostname(card->host), scr_struct); + mmc_card_set_bad(card); + return; + } - csd->read_blkbits = UNSTUFF_BITS(resp, 80, 4); + scr->sda_vsn = UNSTUFF_BITS(resp, 56, 4); + scr->bus_widths = UNSTUFF_BITS(resp, 48, 4); } /* @@ -457,6 +659,11 @@ static void mmc_idle_cards(struct mmc_host *host) { struct mmc_command cmd; + host->ios.chip_select = MMC_CS_HIGH; + host->ops->set_ios(host, &host->ios); + + mmc_delay(1); + cmd.opcode = MMC_GO_IDLE_STATE; cmd.arg = 0; cmd.flags = MMC_RSP_NONE; @@ -464,6 +671,11 @@ static void mmc_idle_cards(struct mmc_host *host) mmc_wait_for_cmd(host, &cmd, 0); mmc_delay(1); + + host->ios.chip_select = MMC_CS_DONTCARE; + host->ops->set_ios(host, &host->ios); + + mmc_delay(1); } /* @@ -475,7 +687,9 @@ static void mmc_power_up(struct mmc_host *host) host->ios.vdd = bit; host->ios.bus_mode = MMC_BUSMODE_OPENDRAIN; + host->ios.chip_select = MMC_CS_DONTCARE; host->ios.power_mode = MMC_POWER_UP; + host->ios.bus_width = MMC_BUS_WIDTH_1; host->ops->set_ios(host, &host->ios); mmc_delay(1); @@ -492,7 +706,9 @@ 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.chip_select = MMC_CS_DONTCARE; host->ios.power_mode = MMC_POWER_OFF; + host->ios.bus_width = MMC_BUS_WIDTH_1; host->ops->set_ios(host, &host->ios); } @@ -524,6 +740,34 @@ static int mmc_send_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr) return err; } +static int mmc_send_app_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr) +{ + struct mmc_command cmd; + int i, err = 0; + + cmd.opcode = SD_APP_OP_COND; + cmd.arg = ocr; + cmd.flags = MMC_RSP_R3; + + for (i = 100; i; i--) { + err = mmc_wait_for_app_cmd(host, 0, &cmd, CMD_RETRIES); + if (err != MMC_ERR_NONE) + break; + + if (cmd.resp[0] & MMC_CARD_BUSY || ocr == 0) + break; + + err = MMC_ERR_TIMEOUT; + + mmc_delay(10); + } + + 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 @@ -551,7 +795,7 @@ static void mmc_discover_cards(struct mmc_host *host) } if (err != MMC_ERR_NONE) { printk(KERN_ERR "%s: error requesting CID: %d\n", - host->host_name, err); + mmc_hostname(host), err); break; } @@ -567,13 +811,38 @@ static void mmc_discover_cards(struct mmc_host *host) card->state &= ~MMC_STATE_DEAD; - cmd.opcode = MMC_SET_RELATIVE_ADDR; - cmd.arg = card->rca << 16; - cmd.flags = MMC_RSP_R1; + if (host->mode == MMC_MODE_SD) { + mmc_card_set_sd(card); - err = mmc_wait_for_cmd(host, &cmd, CMD_RETRIES); - if (err != MMC_ERR_NONE) - mmc_card_set_dead(card); + cmd.opcode = SD_SEND_RELATIVE_ADDR; + cmd.arg = 0; + cmd.flags = MMC_RSP_R1; + + err = mmc_wait_for_cmd(host, &cmd, CMD_RETRIES); + if (err != MMC_ERR_NONE) + mmc_card_set_dead(card); + else { + card->rca = cmd.resp[0] >> 16; + + if (!host->ops->get_ro) { + printk(KERN_WARNING "%s: host does not " + "support reading read-only " + "switch. assuming write-enable.\n", + mmc_hostname(host)); + } else { + if (host->ops->get_ro(host)) + mmc_card_set_readonly(card); + } + } + } else { + cmd.opcode = MMC_SET_RELATIVE_ADDR; + cmd.arg = card->rca << 16; + cmd.flags = MMC_RSP_R1; + + err = mmc_wait_for_cmd(host, &cmd, CMD_RETRIES); + if (err != MMC_ERR_NONE) + mmc_card_set_dead(card); + } } } @@ -605,6 +874,79 @@ static void mmc_read_csds(struct mmc_host *host) } } +static void mmc_read_scrs(struct mmc_host *host) +{ + int err; + struct mmc_card *card; + + struct mmc_request mrq; + struct mmc_command cmd; + struct mmc_data data; + + struct scatterlist sg; + + list_for_each_entry(card, &host->cards, node) { + if (card->state & (MMC_STATE_DEAD|MMC_STATE_PRESENT)) + continue; + if (!mmc_card_sd(card)) + continue; + + err = mmc_select_card(host, card); + if (err != MMC_ERR_NONE) { + mmc_card_set_dead(card); + continue; + } + + memset(&cmd, 0, sizeof(struct mmc_command)); + + cmd.opcode = MMC_APP_CMD; + cmd.arg = card->rca << 16; + cmd.flags = MMC_RSP_R1; + + err = mmc_wait_for_cmd(host, &cmd, 0); + if ((err != MMC_ERR_NONE) || !(cmd.resp[0] & R1_APP_CMD)) { + mmc_card_set_dead(card); + continue; + } + + memset(&cmd, 0, sizeof(struct mmc_command)); + + cmd.opcode = SD_APP_SEND_SCR; + cmd.arg = 0; + cmd.flags = MMC_RSP_R1; + + memset(&data, 0, sizeof(struct mmc_data)); + + data.timeout_ns = card->csd.tacc_ns * 10; + data.timeout_clks = card->csd.tacc_clks * 10; + data.blksz_bits = 3; + data.blocks = 1; + data.flags = MMC_DATA_READ; + data.sg = &sg; + data.sg_len = 1; + + memset(&mrq, 0, sizeof(struct mmc_request)); + + mrq.cmd = &cmd; + mrq.data = &data; + + sg_init_one(&sg, (u8*)card->raw_scr, 8); + + err = mmc_wait_for_req(host, &mrq); + if (err != MMC_ERR_NONE) { + mmc_card_set_dead(card); + continue; + } + + card->raw_scr[0] = ntohl(card->raw_scr[0]); + card->raw_scr[1] = ntohl(card->raw_scr[1]); + + mmc_decode_scr(card); + } + + mmc_deselect_cards(host); +} + static unsigned int mmc_calculate_clock(struct mmc_host *host) { struct mmc_card *card; @@ -657,12 +999,24 @@ static void mmc_setup(struct mmc_host *host) int err; u32 ocr; + host->mode = MMC_MODE_SD; + mmc_power_up(host); mmc_idle_cards(host); - err = mmc_send_op_cond(host, 0, &ocr); - if (err != MMC_ERR_NONE) - return; + err = mmc_send_app_op_cond(host, 0, &ocr); + + /* + * If we fail to detect any SD cards then try + * searching for MMC cards. + */ + if (err != MMC_ERR_NONE) { + host->mode = MMC_MODE_MMC; + + err = mmc_send_op_cond(host, 0, &ocr); + if (err != MMC_ERR_NONE) + return; + } host->ocr = mmc_select_voltage(host, ocr); @@ -702,7 +1056,10 @@ static void mmc_setup(struct mmc_host *host) * 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); + if (host->mode == MMC_MODE_SD) + mmc_send_app_op_cond(host, host->ocr, NULL); + else + mmc_send_op_cond(host, host->ocr, NULL); mmc_discover_cards(host); @@ -713,19 +1070,26 @@ static void mmc_setup(struct mmc_host *host) host->ops->set_ios(host, &host->ios); mmc_read_csds(host); + + if (host->mode == MMC_MODE_SD) + mmc_read_scrs(host); } /** * mmc_detect_change - process change of state on a MMC socket * @host: host which changed state. + * @delay: optional delay to wait before detection (jiffies) * * 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) +void mmc_detect_change(struct mmc_host *host, unsigned long delay) { - schedule_work(&host->detect); + if (delay) + schedule_delayed_work(&host->detect, delay); + else + schedule_work(&host->detect); } EXPORT_SYMBOL(mmc_detect_change); @@ -796,17 +1160,13 @@ struct mmc_host *mmc_alloc_host(int extra, struct device *dev) { struct mmc_host *host; - host = kmalloc(sizeof(struct mmc_host) + extra, GFP_KERNEL); + host = mmc_alloc_host_sysfs(extra, dev); if (host) { - memset(host, 0, sizeof(struct mmc_host) + extra); - 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; - /* * By default, hosts do not support SGIO or large requests. * They have to set these according to their abilities. @@ -828,15 +1188,15 @@ EXPORT_SYMBOL(mmc_alloc_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++); + int ret; - mmc_power_off(host); - mmc_detect_change(host); + ret = mmc_add_host_sysfs(host); + if (ret == 0) { + mmc_power_off(host); + mmc_detect_change(host, 0); + } - return 0; + return ret; } EXPORT_SYMBOL(mmc_add_host); @@ -859,6 +1219,7 @@ void mmc_remove_host(struct mmc_host *host) } mmc_power_off(host); + mmc_remove_host_sysfs(host); } EXPORT_SYMBOL(mmc_remove_host); @@ -872,7 +1233,7 @@ EXPORT_SYMBOL(mmc_remove_host); void mmc_free_host(struct mmc_host *host) { flush_scheduled_work(); - kfree(host); + mmc_free_host_sysfs(host); } EXPORT_SYMBOL(mmc_free_host); @@ -902,7 +1263,7 @@ EXPORT_SYMBOL(mmc_suspend_host); */ int mmc_resume_host(struct mmc_host *host) { - mmc_detect_change(host); + mmc_detect_change(host, 0); return 0; } diff --git a/drivers/mmc/mmc.h b/drivers/mmc/mmc.h index b498dffe0b11..97bae00292fa 100644 --- a/drivers/mmc/mmc.h +++ b/drivers/mmc/mmc.h @@ -13,4 +13,9 @@ 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); + +struct mmc_host *mmc_alloc_host_sysfs(int extra, struct device *dev); +int mmc_add_host_sysfs(struct mmc_host *host); +void mmc_remove_host_sysfs(struct mmc_host *host); +void mmc_free_host_sysfs(struct mmc_host *host); #endif diff --git a/drivers/mmc/mmc_block.c b/drivers/mmc/mmc_block.c index d4eee99c2bf6..9b629856c735 100644 --- a/drivers/mmc/mmc_block.c +++ b/drivers/mmc/mmc_block.c @@ -85,6 +85,12 @@ static void mmc_blk_put(struct mmc_blk_data *md) up(&open_lock); } +static inline int mmc_blk_readonly(struct mmc_card *card) +{ + return mmc_card_readonly(card) || + !(card->csd.cmdclass & CCC_BLOCK_WRITE); +} + static int mmc_blk_open(struct inode *inode, struct file *filp) { struct mmc_blk_data *md; @@ -95,6 +101,10 @@ static int mmc_blk_open(struct inode *inode, struct file *filp) if (md->usage == 2) check_disk_change(inode->i_bdev); ret = 0; + + if ((filp->f_mode & FMODE_WRITE) && + mmc_blk_readonly(md->queue.card)) + ret = -EROFS; } return ret; @@ -403,9 +413,10 @@ static int mmc_blk_probe(struct mmc_card *card) if (err) goto out; - printk(KERN_INFO "%s: %s %s %dKiB\n", + printk(KERN_INFO "%s: %s %s %dKiB %s\n", md->disk->disk_name, mmc_card_id(card), mmc_card_name(card), - (card->csd.capacity << card->csd.read_blkbits) / 1024); + (card->csd.capacity << card->csd.read_blkbits) / 1024, + mmc_blk_readonly(card)?"(ro)":""); mmc_set_drvdata(card, md); add_disk(md->disk); diff --git a/drivers/mmc/mmc_sysfs.c b/drivers/mmc/mmc_sysfs.c index 5556cd3b5559..3f4a66ca9555 100644 --- a/drivers/mmc/mmc_sysfs.c +++ b/drivers/mmc/mmc_sysfs.c @@ -12,6 +12,7 @@ #include <linux/module.h> #include <linux/init.h> #include <linux/device.h> +#include <linux/idr.h> #include <linux/mmc/card.h> #include <linux/mmc/host.h> @@ -20,6 +21,7 @@ #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) +#define cls_dev_to_mmc_host(d) container_of(d, struct mmc_host, class_dev) #define MMC_ATTR(name, fmt, args...) \ static ssize_t mmc_##name##_show (struct device *dev, struct device_attribute *attr, char *buf) \ @@ -32,6 +34,7 @@ MMC_ATTR(cid, "%08x%08x%08x%08x\n", card->raw_cid[0], card->raw_cid[1], card->raw_cid[2], card->raw_cid[3]); MMC_ATTR(csd, "%08x%08x%08x%08x\n", card->raw_csd[0], card->raw_csd[1], card->raw_csd[2], card->raw_csd[3]); +MMC_ATTR(scr, "%08x%08x\n", card->raw_scr[0], card->raw_scr[1]); MMC_ATTR(date, "%02d/%04d\n", card->cid.month, card->cid.year); MMC_ATTR(fwrev, "0x%x\n", card->cid.fwrev); MMC_ATTR(hwrev, "0x%x\n", card->cid.hwrev); @@ -55,6 +58,8 @@ static struct device_attribute mmc_dev_attrs[] = { __ATTR_NULL }; +static struct device_attribute mmc_dev_attr_scr = MMC_ATTR_RO(scr); + static void mmc_release_card(struct device *dev) { @@ -205,10 +210,20 @@ void mmc_init_card(struct mmc_card *card, struct mmc_host *host) */ int mmc_register_card(struct mmc_card *card) { - snprintf(card->dev.bus_id, sizeof(card->dev.bus_id), - "%s:%04x", card->host->host_name, card->rca); + int ret; - return device_add(&card->dev); + snprintf(card->dev.bus_id, sizeof(card->dev.bus_id), + "%s:%04x", mmc_hostname(card->host), card->rca); + + ret = device_add(&card->dev); + if (ret == 0) { + if (mmc_card_sd(card)) { + ret = device_create_file(&card->dev, &mmc_dev_attr_scr); + if (ret) + device_del(&card->dev); + } + } + return ret; } /* @@ -217,20 +232,108 @@ int mmc_register_card(struct mmc_card *card) */ void mmc_remove_card(struct mmc_card *card) { - if (mmc_card_present(card)) + if (mmc_card_present(card)) { + if (mmc_card_sd(card)) + device_remove_file(&card->dev, &mmc_dev_attr_scr); + device_del(&card->dev); + } put_device(&card->dev); } +static void mmc_host_classdev_release(struct class_device *dev) +{ + struct mmc_host *host = cls_dev_to_mmc_host(dev); + kfree(host); +} + +static struct class mmc_host_class = { + .name = "mmc_host", + .release = mmc_host_classdev_release, +}; + +static DEFINE_IDR(mmc_host_idr); +static DEFINE_SPINLOCK(mmc_host_lock); + +/* + * Internal function. Allocate a new MMC host. + */ +struct mmc_host *mmc_alloc_host_sysfs(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->dev = dev; + host->class_dev.dev = host->dev; + host->class_dev.class = &mmc_host_class; + class_device_initialize(&host->class_dev); + } + + return host; +} + +/* + * Internal function. Register a new MMC host with the MMC class. + */ +int mmc_add_host_sysfs(struct mmc_host *host) +{ + int err; + + if (!idr_pre_get(&mmc_host_idr, GFP_KERNEL)) + return -ENOMEM; + + spin_lock(&mmc_host_lock); + err = idr_get_new(&mmc_host_idr, host, &host->index); + spin_unlock(&mmc_host_lock); + if (err) + return err; + + snprintf(host->class_dev.class_id, BUS_ID_SIZE, + "mmc%d", host->index); + + return class_device_add(&host->class_dev); +} + +/* + * Internal function. Unregister a MMC host with the MMC class. + */ +void mmc_remove_host_sysfs(struct mmc_host *host) +{ + class_device_del(&host->class_dev); + + spin_lock(&mmc_host_lock); + idr_remove(&mmc_host_idr, host->index); + spin_unlock(&mmc_host_lock); +} + +/* + * Internal function. Free a MMC host. + */ +void mmc_free_host_sysfs(struct mmc_host *host) +{ + class_device_put(&host->class_dev); +} + + static int __init mmc_init(void) { - return bus_register(&mmc_bus_type); + int ret = bus_register(&mmc_bus_type); + if (ret == 0) { + ret = class_register(&mmc_host_class); + if (ret) + bus_unregister(&mmc_bus_type); + } + return ret; } static void __exit mmc_exit(void) { + class_unregister(&mmc_host_class); bus_unregister(&mmc_bus_type); } diff --git a/drivers/mmc/mmci.c b/drivers/mmc/mmci.c index 7a42966d755b..1e6bdba26756 100644 --- a/drivers/mmc/mmci.c +++ b/drivers/mmc/mmci.c @@ -24,6 +24,7 @@ #include <asm/io.h> #include <asm/irq.h> #include <asm/scatterlist.h> +#include <asm/sizes.h> #include <asm/hardware/amba.h> #include <asm/hardware/clock.h> #include <asm/mach/mmc.h> @@ -34,7 +35,7 @@ #ifdef CONFIG_MMC_DEBUG #define DBG(host,fmt,args...) \ - pr_debug("%s: %s: " fmt, host->mmc->host_name, __func__ , args) + pr_debug("%s: %s: " fmt, mmc_hostname(host->mmc), __func__ , args) #else #define DBG(host,fmt,args...) do { } while (0) #endif @@ -442,7 +443,7 @@ static void mmci_check_status(unsigned long data) status = host->plat->status(mmc_dev(host->mmc)); if (status ^ host->oldstat) - mmc_detect_change(host->mmc); + mmc_detect_change(host->mmc, 0); host->oldstat = status; mod_timer(&host->timer, jiffies + HZ); @@ -541,7 +542,7 @@ static int mmci_probe(struct amba_device *dev, void *id) 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), + mmc_hostname(mmc), amba_rev(dev), amba_config(dev), dev->res.start, dev->irq[0], dev->irq[1]); init_timer(&host->timer); diff --git a/drivers/mmc/pxamci.c b/drivers/mmc/pxamci.c index b78beb1b0159..f31e247b2cbe 100644 --- a/drivers/mmc/pxamci.c +++ b/drivers/mmc/pxamci.c @@ -20,7 +20,7 @@ #include <linux/module.h> #include <linux/init.h> #include <linux/ioport.h> -#include <linux/device.h> +#include <linux/platform_device.h> #include <linux/delay.h> #include <linux/interrupt.h> #include <linux/dma-mapping.h> @@ -29,7 +29,6 @@ #include <asm/dma.h> #include <asm/io.h> -#include <asm/irq.h> #include <asm/scatterlist.h> #include <asm/sizes.h> @@ -362,6 +361,16 @@ static void pxamci_request(struct mmc_host *mmc, struct mmc_request *mrq) pxamci_start_cmd(host, mrq->cmd, cmdat); } +static int pxamci_get_ro(struct mmc_host *mmc) +{ + struct pxamci_host *host = mmc_priv(mmc); + + if (host->pdata && host->pdata->get_ro) + return host->pdata->get_ro(mmc->dev); + /* Host doesn't support read only detection so assume writeable */ + return 0; +} + static void pxamci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) { struct pxamci_host *host = mmc_priv(mmc); @@ -401,6 +410,7 @@ static void pxamci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) static struct mmc_host_ops pxamci_ops = { .request = pxamci_request, + .get_ro = pxamci_get_ro, .set_ios = pxamci_set_ios, }; @@ -412,7 +422,9 @@ static void pxamci_dma_irq(int dma, void *devid, struct pt_regs *regs) static irqreturn_t pxamci_detect_irq(int irq, void *devid, struct pt_regs *regs) { - mmc_detect_change(devid); + struct pxamci_host *host = mmc_priv(devid); + + mmc_detect_change(devid, host->pdata->detect_delay); return IRQ_HANDLED; } @@ -558,23 +570,23 @@ static int pxamci_remove(struct device *dev) } #ifdef CONFIG_PM -static int pxamci_suspend(struct device *dev, pm_message_t state, u32 level) +static int pxamci_suspend(struct device *dev, pm_message_t state) { struct mmc_host *mmc = dev_get_drvdata(dev); int ret = 0; - if (mmc && level == SUSPEND_DISABLE) + if (mmc) ret = mmc_suspend_host(mmc, state); return ret; } -static int pxamci_resume(struct device *dev, u32 level) +static int pxamci_resume(struct device *dev) { struct mmc_host *mmc = dev_get_drvdata(dev); int ret = 0; - if (mmc && level == RESUME_ENABLE) + if (mmc) ret = mmc_resume_host(mmc); return ret; diff --git a/drivers/mmc/wbsd.c b/drivers/mmc/wbsd.c index 8b487ed1069c..942668e93a74 100644 --- a/drivers/mmc/wbsd.c +++ b/drivers/mmc/wbsd.c @@ -26,7 +26,7 @@ #include <linux/moduleparam.h> #include <linux/init.h> #include <linux/ioport.h> -#include <linux/device.h> +#include <linux/platform_device.h> #include <linux/interrupt.h> #include <linux/dma-mapping.h> #include <linux/delay.h> @@ -42,7 +42,7 @@ #include "wbsd.h" #define DRIVER_NAME "wbsd" -#define DRIVER_VERSION "1.2" +#define DRIVER_VERSION "1.4" #ifdef CONFIG_MMC_DEBUG #define DBG(x...) \ @@ -93,7 +93,7 @@ static int dma = 2; static inline void wbsd_unlock_config(struct wbsd_host* host) { BUG_ON(host->config == 0); - + outb(host->unlock_code, host->config); outb(host->unlock_code, host->config); } @@ -101,14 +101,14 @@ static inline void wbsd_unlock_config(struct wbsd_host* host) static inline void wbsd_lock_config(struct wbsd_host* host) { BUG_ON(host->config == 0); - + outb(LOCK_CODE, host->config); } static inline void wbsd_write_config(struct wbsd_host* host, u8 reg, u8 value) { BUG_ON(host->config == 0); - + outb(reg, host->config); outb(value, host->config + 1); } @@ -116,7 +116,7 @@ static inline void wbsd_write_config(struct wbsd_host* host, u8 reg, u8 value) static inline u8 wbsd_read_config(struct wbsd_host* host, u8 reg) { BUG_ON(host->config == 0); - + outb(reg, host->config); return inb(host->config + 1); } @@ -140,21 +140,21 @@ static inline u8 wbsd_read_index(struct wbsd_host* host, u8 index) static void wbsd_init_device(struct wbsd_host* host) { u8 setup, ier; - + /* * Reset chip (SD/MMC part) and fifo. */ setup = wbsd_read_index(host, WBSD_IDX_SETUP); setup |= WBSD_FIFO_RESET | WBSD_SOFT_RESET; wbsd_write_index(host, WBSD_IDX_SETUP, setup); - + /* * Set DAT3 to input */ setup &= ~WBSD_DAT3_H; wbsd_write_index(host, WBSD_IDX_SETUP, setup); host->flags &= ~WBSD_FIGNORE_DETECT; - + /* * Read back default clock. */ @@ -164,12 +164,12 @@ static void wbsd_init_device(struct wbsd_host* host) * Power down port. */ outb(WBSD_POWER_N, host->base + WBSD_CSR); - + /* * Set maximum timeout. */ wbsd_write_index(host, WBSD_IDX_TAAC, 0x7F); - + /* * Test for card presence */ @@ -177,7 +177,7 @@ static void wbsd_init_device(struct wbsd_host* host) host->flags |= WBSD_FCARD_PRESENT; else host->flags &= ~WBSD_FCARD_PRESENT; - + /* * Enable interesting interrupts. */ @@ -200,9 +200,9 @@ static void wbsd_init_device(struct wbsd_host* host) static void wbsd_reset(struct wbsd_host* host) { u8 setup; - + printk(KERN_ERR DRIVER_NAME ": Resetting chip\n"); - + /* * Soft reset of chip (SD/MMC part). */ @@ -214,9 +214,9 @@ static void wbsd_reset(struct wbsd_host* host) static void wbsd_request_end(struct wbsd_host* host, struct mmc_request* mrq) { unsigned long dmaflags; - + DBGF("Ending request, cmd (%x)\n", mrq->cmd->opcode); - + if (host->dma >= 0) { /* @@ -232,7 +232,7 @@ static void wbsd_request_end(struct wbsd_host* host, struct mmc_request* mrq) */ wbsd_write_index(host, WBSD_IDX_DMA, 0); } - + host->mrq = NULL; /* @@ -275,7 +275,7 @@ static inline int wbsd_next_sg(struct wbsd_host* host) host->offset = 0; host->remain = host->cur_sg->length; } - + return host->num_sg; } @@ -297,12 +297,12 @@ static inline void wbsd_sg_to_dma(struct wbsd_host* host, struct mmc_data* data) struct scatterlist* sg; char* dmabuf = host->dma_buffer; char* sgbuf; - + size = host->size; - + sg = data->sg; len = data->sg_len; - + /* * Just loop through all entries. Size might not * be the entire list though so make sure that @@ -317,23 +317,23 @@ static inline void wbsd_sg_to_dma(struct wbsd_host* host, struct mmc_data* data) memcpy(dmabuf, sgbuf, sg[i].length); kunmap_atomic(sgbuf, KM_BIO_SRC_IRQ); dmabuf += sg[i].length; - + if (size < sg[i].length) size = 0; else size -= sg[i].length; - + if (size == 0) break; } - + /* * Check that we didn't get a request to transfer * more data than can fit into the SG list. */ - + BUG_ON(size != 0); - + host->size -= size; } @@ -343,12 +343,12 @@ static inline void wbsd_dma_to_sg(struct wbsd_host* host, struct mmc_data* data) struct scatterlist* sg; char* dmabuf = host->dma_buffer; char* sgbuf; - + size = host->size; - + sg = data->sg; len = data->sg_len; - + /* * Just loop through all entries. Size might not * be the entire list though so make sure that @@ -363,30 +363,30 @@ static inline void wbsd_dma_to_sg(struct wbsd_host* host, struct mmc_data* data) memcpy(sgbuf, dmabuf, sg[i].length); kunmap_atomic(sgbuf, KM_BIO_SRC_IRQ); dmabuf += sg[i].length; - + if (size < sg[i].length) size = 0; else size -= sg[i].length; - + if (size == 0) break; } - + /* * Check that we didn't get a request to transfer * more data than can fit into the SG list. */ - + BUG_ON(size != 0); - + host->size -= size; } /* * Command handling */ - + static inline void wbsd_get_short_reply(struct wbsd_host* host, struct mmc_command* cmd) { @@ -398,7 +398,7 @@ static inline void wbsd_get_short_reply(struct wbsd_host* host, cmd->error = MMC_ERR_INVALID; return; } - + cmd->resp[0] = wbsd_read_index(host, WBSD_IDX_RESP12) << 24; cmd->resp[0] |= @@ -415,7 +415,7 @@ static inline void wbsd_get_long_reply(struct wbsd_host* host, struct mmc_command* cmd) { int i; - + /* * Correct response type? */ @@ -424,7 +424,7 @@ static inline void wbsd_get_long_reply(struct wbsd_host* host, cmd->error = MMC_ERR_INVALID; return; } - + for (i = 0;i < 4;i++) { cmd->resp[i] = @@ -442,7 +442,7 @@ static void wbsd_send_command(struct wbsd_host* host, struct mmc_command* cmd) { int i; u8 status, isr; - + DBGF("Sending cmd (%x)\n", cmd->opcode); /* @@ -451,16 +451,16 @@ static void wbsd_send_command(struct wbsd_host* host, struct mmc_command* cmd) * transfer. */ host->isr = 0; - + /* * Send the command (CRC calculated by host). */ outb(cmd->opcode, host->base + WBSD_CMDR); for (i = 3;i >= 0;i--) outb((cmd->arg >> (i * 8)) & 0xff, host->base + WBSD_CMDR); - + cmd->error = MMC_ERR_NONE; - + /* * Wait for the request to complete. */ @@ -477,7 +477,7 @@ static void wbsd_send_command(struct wbsd_host* host, struct mmc_command* cmd) * Read back status. */ isr = host->isr; - + /* Card removed? */ if (isr & WBSD_INT_CARD) cmd->error = MMC_ERR_TIMEOUT; @@ -509,13 +509,13 @@ static void wbsd_empty_fifo(struct wbsd_host* host) struct mmc_data* data = host->mrq->cmd->data; char* buffer; int i, fsr, fifo; - + /* * Handle excessive data. */ if (data->bytes_xfered == host->size) return; - + buffer = wbsd_kmap_sg(host) + host->offset; /* @@ -527,14 +527,14 @@ static void wbsd_empty_fifo(struct wbsd_host* host) /* * The size field in the FSR is broken so we have to * do some guessing. - */ + */ if (fsr & WBSD_FIFO_FULL) fifo = 16; else if (fsr & WBSD_FIFO_FUTHRE) fifo = 8; else fifo = 1; - + for (i = 0;i < fifo;i++) { *buffer = inb(host->base + WBSD_DFR); @@ -543,23 +543,23 @@ static void wbsd_empty_fifo(struct wbsd_host* host) host->remain--; data->bytes_xfered++; - + /* * Transfer done? */ if (data->bytes_xfered == host->size) { - wbsd_kunmap_sg(host); + wbsd_kunmap_sg(host); return; } - + /* * End of scatter list entry? */ if (host->remain == 0) { wbsd_kunmap_sg(host); - + /* * Get next entry. Check if last. */ @@ -572,17 +572,17 @@ static void wbsd_empty_fifo(struct wbsd_host* host) * into the scatter list. */ BUG_ON(1); - + host->size = data->bytes_xfered; - + return; } - + buffer = wbsd_kmap_sg(host); } } } - + wbsd_kunmap_sg(host); /* @@ -599,7 +599,7 @@ static void wbsd_fill_fifo(struct wbsd_host* host) struct mmc_data* data = host->mrq->cmd->data; char* buffer; int i, fsr, fifo; - + /* * Check that we aren't being called after the * entire buffer has been transfered. @@ -618,7 +618,7 @@ static void wbsd_fill_fifo(struct wbsd_host* host) /* * The size field in the FSR is broken so we have to * do some guessing. - */ + */ if (fsr & WBSD_FIFO_EMPTY) fifo = 0; else if (fsr & WBSD_FIFO_EMTHRE) @@ -632,9 +632,9 @@ static void wbsd_fill_fifo(struct wbsd_host* host) buffer++; host->offset++; host->remain--; - + data->bytes_xfered++; - + /* * Transfer done? */ @@ -650,7 +650,7 @@ static void wbsd_fill_fifo(struct wbsd_host* host) if (host->remain == 0) { wbsd_kunmap_sg(host); - + /* * Get next entry. Check if last. */ @@ -663,19 +663,19 @@ static void wbsd_fill_fifo(struct wbsd_host* host) * into the scatter list. */ BUG_ON(1); - + host->size = data->bytes_xfered; - + return; } - + buffer = wbsd_kmap_sg(host); } } } - + wbsd_kunmap_sg(host); - + /* * The controller stops sending interrupts for * 'FIFO empty' under certain conditions. So we @@ -694,7 +694,7 @@ static void wbsd_prepare_data(struct wbsd_host* host, struct mmc_data* data) 1 << data->blksz_bits, data->blocks, data->flags); DBGF("tsac %d ms nsac %d clk\n", data->timeout_ns / 1000000, data->timeout_clks); - + /* * Calculate size. */ @@ -708,23 +708,40 @@ static void wbsd_prepare_data(struct wbsd_host* host, struct mmc_data* data) wbsd_write_index(host, WBSD_IDX_TAAC, 127); else wbsd_write_index(host, WBSD_IDX_TAAC, data->timeout_ns/1000000); - + if (data->timeout_clks > 255) wbsd_write_index(host, WBSD_IDX_NSAC, 255); else wbsd_write_index(host, WBSD_IDX_NSAC, data->timeout_clks); - + /* * Inform the chip of how large blocks will be * sent. It needs this to determine when to * calculate CRC. * * Space for CRC must be included in the size. + * Two bytes are needed for each data line. */ - blksize = (1 << data->blksz_bits) + 2; - - wbsd_write_index(host, WBSD_IDX_PBSMSB, (blksize >> 4) & 0xF0); - wbsd_write_index(host, WBSD_IDX_PBSLSB, blksize & 0xFF); + if (host->bus_width == MMC_BUS_WIDTH_1) + { + blksize = (1 << data->blksz_bits) + 2; + + wbsd_write_index(host, WBSD_IDX_PBSMSB, (blksize >> 4) & 0xF0); + wbsd_write_index(host, WBSD_IDX_PBSLSB, blksize & 0xFF); + } + else if (host->bus_width == MMC_BUS_WIDTH_4) + { + blksize = (1 << data->blksz_bits) + 2 * 4; + + wbsd_write_index(host, WBSD_IDX_PBSMSB, ((blksize >> 4) & 0xF0) + | WBSD_DATA_WIDTH); + wbsd_write_index(host, WBSD_IDX_PBSLSB, blksize & 0xFF); + } + else + { + data->error = MMC_ERR_INVALID; + return; + } /* * Clear the FIFO. This is needed even for DMA @@ -734,12 +751,12 @@ static void wbsd_prepare_data(struct wbsd_host* host, struct mmc_data* data) setup = wbsd_read_index(host, WBSD_IDX_SETUP); setup |= WBSD_FIFO_RESET; wbsd_write_index(host, WBSD_IDX_SETUP, setup); - + /* * DMA transfer? */ if (host->dma >= 0) - { + { /* * The buffer for DMA is only 64 kB. */ @@ -749,17 +766,17 @@ static void wbsd_prepare_data(struct wbsd_host* host, struct mmc_data* data) data->error = MMC_ERR_INVALID; return; } - + /* * Transfer data from the SG list to * the DMA buffer. */ if (data->flags & MMC_DATA_WRITE) wbsd_sg_to_dma(host, data); - + /* * Initialise the ISA DMA controller. - */ + */ dmaflags = claim_dma_lock(); disable_dma(host->dma); clear_dma_ff(host->dma); @@ -785,17 +802,17 @@ static void wbsd_prepare_data(struct wbsd_host* host, struct mmc_data* data) * output to a minimum. */ host->firsterr = 1; - + /* * Initialise the SG list. */ wbsd_init_sg(host, data); - + /* * Turn off DMA. */ wbsd_write_index(host, WBSD_IDX_DMA, 0); - + /* * Set up FIFO threshold levels (and fill * buffer if doing a write). @@ -811,8 +828,8 @@ static void wbsd_prepare_data(struct wbsd_host* host, struct mmc_data* data) WBSD_FIFOEN_EMPTY | 8); wbsd_fill_fifo(host); } - } - + } + data->error = MMC_ERR_NONE; } @@ -821,7 +838,7 @@ static void wbsd_finish_data(struct wbsd_host* host, struct mmc_data* data) unsigned long dmaflags; int count; u8 status; - + WARN_ON(host->mrq == NULL); /* @@ -838,7 +855,7 @@ static void wbsd_finish_data(struct wbsd_host* host, struct mmc_data* data) { status = wbsd_read_index(host, WBSD_IDX_STATUS); } while (status & (WBSD_BLOCK_READ | WBSD_BLOCK_WRITE)); - + /* * DMA transfer? */ @@ -848,7 +865,7 @@ static void wbsd_finish_data(struct wbsd_host* host, struct mmc_data* data) * Disable DMA on the host. */ wbsd_write_index(host, WBSD_IDX_DMA, 0); - + /* * Turn of ISA DMA controller. */ @@ -857,7 +874,7 @@ static void wbsd_finish_data(struct wbsd_host* host, struct mmc_data* data) clear_dma_ff(host->dma); count = get_dma_residue(host->dma); release_dma_lock(dmaflags); - + /* * Any leftover data? */ @@ -865,7 +882,7 @@ static void wbsd_finish_data(struct wbsd_host* host, struct mmc_data* data) { printk(KERN_ERR DRIVER_NAME ": Incomplete DMA " "transfer. %d bytes left.\n", count); - + data->error = MMC_ERR_FAILED; } else @@ -876,13 +893,13 @@ static void wbsd_finish_data(struct wbsd_host* host, struct mmc_data* data) */ if (data->flags & MMC_DATA_READ) wbsd_dma_to_sg(host, data); - + data->bytes_xfered = host->size; } } - + DBGF("Ending data transfer (%d bytes)\n", data->bytes_xfered); - + wbsd_request_end(host, host->mrq); } @@ -907,7 +924,7 @@ static void wbsd_request(struct mmc_host* mmc, struct mmc_request* mrq) cmd = mrq->cmd; host->mrq = mrq; - + /* * If there is no card in the slot then * timeout immediatly. @@ -924,18 +941,18 @@ static void wbsd_request(struct mmc_host* mmc, struct mmc_request* mrq) if (cmd->data) { wbsd_prepare_data(host, cmd->data); - + if (cmd->data->error != MMC_ERR_NONE) goto done; } - + wbsd_send_command(host, cmd); /* * If this is a data transfer the request * will be finished after the data has * transfered. - */ + */ if (cmd->data && (cmd->error == MMC_ERR_NONE)) { /* @@ -948,7 +965,7 @@ static void wbsd_request(struct mmc_host* mmc, struct mmc_request* mrq) return; } - + done: wbsd_request_end(host, mrq); @@ -959,9 +976,10 @@ static void wbsd_set_ios(struct mmc_host* mmc, struct mmc_ios* ios) { struct wbsd_host* host = mmc_priv(mmc); u8 clk, setup, pwr; - - DBGF("clock %uHz busmode %u powermode %u Vdd %u\n", - ios->clock, ios->bus_mode, ios->power_mode, ios->vdd); + + DBGF("clock %uHz busmode %u powermode %u cs %u Vdd %u width %u\n", + ios->clock, ios->bus_mode, ios->power_mode, ios->chip_select, + ios->vdd, ios->bus_width); spin_lock_bh(&host->lock); @@ -971,7 +989,7 @@ static void wbsd_set_ios(struct mmc_host* mmc, struct mmc_ios* ios) */ if (ios->power_mode == MMC_POWER_OFF) wbsd_init_device(host); - + if (ios->clock >= 24000000) clk = WBSD_CLK_24M; else if (ios->clock >= 16000000) @@ -1003,30 +1021,66 @@ static void wbsd_set_ios(struct mmc_host* mmc, struct mmc_ios* ios) /* * MMC cards need to have pin 1 high during init. - * Init time corresponds rather nicely with the bus mode. * It wreaks havoc with the card detection though so - * that needs to be disabed. + * that needs to be disabled. */ setup = wbsd_read_index(host, WBSD_IDX_SETUP); - if ((ios->power_mode == MMC_POWER_ON) && - (ios->bus_mode == MMC_BUSMODE_OPENDRAIN)) + if (ios->chip_select == MMC_CS_HIGH) { + BUG_ON(ios->bus_width != MMC_BUS_WIDTH_1); setup |= WBSD_DAT3_H; host->flags |= WBSD_FIGNORE_DETECT; } else { - setup &= ~WBSD_DAT3_H; - host->flags &= ~WBSD_FIGNORE_DETECT; + if (setup & WBSD_DAT3_H) + { + setup &= ~WBSD_DAT3_H; + + /* + * We cannot resume card detection immediatly + * because of capacitance and delays in the chip. + */ + mod_timer(&host->ignore_timer, jiffies + HZ/100); + } } wbsd_write_index(host, WBSD_IDX_SETUP, setup); - + + /* + * Store bus width for later. Will be used when + * setting up the data transfer. + */ + host->bus_width = ios->bus_width; + + spin_unlock_bh(&host->lock); +} + +static int wbsd_get_ro(struct mmc_host* mmc) +{ + struct wbsd_host* host = mmc_priv(mmc); + u8 csr; + + spin_lock_bh(&host->lock); + + csr = inb(host->base + WBSD_CSR); + csr |= WBSD_MSLED; + outb(csr, host->base + WBSD_CSR); + + mdelay(1); + + csr = inb(host->base + WBSD_CSR); + csr &= ~WBSD_MSLED; + outb(csr, host->base + WBSD_CSR); + spin_unlock_bh(&host->lock); + + return csr & WBSD_WRPT; } static struct mmc_host_ops wbsd_ops = { .request = wbsd_request, .set_ios = wbsd_set_ios, + .get_ro = wbsd_get_ro, }; /*****************************************************************************\ @@ -1036,17 +1090,28 @@ static struct mmc_host_ops wbsd_ops = { \*****************************************************************************/ /* - * Helper function for card detection + * Helper function to reset detection ignore */ -static void wbsd_detect_card(unsigned long data) + +static void wbsd_reset_ignore(unsigned long data) { struct wbsd_host *host = (struct wbsd_host*)data; - + BUG_ON(host == NULL); - - DBG("Executing card detection\n"); - - mmc_detect_change(host->mmc); + + DBG("Resetting card detection ignore\n"); + + spin_lock_bh(&host->lock); + + host->flags &= ~WBSD_FIGNORE_DETECT; + + /* + * Card status might have changed during the + * blackout. + */ + tasklet_schedule(&host->card_tasklet); + + spin_unlock_bh(&host->lock); } /* @@ -1066,7 +1131,7 @@ static inline struct mmc_data* wbsd_get_data(struct wbsd_host* host) WARN_ON(!host->mrq->cmd->data); if (!host->mrq->cmd->data) return NULL; - + return host->mrq->cmd->data; } @@ -1074,68 +1139,67 @@ static void wbsd_tasklet_card(unsigned long param) { struct wbsd_host* host = (struct wbsd_host*)param; u8 csr; - + int delay = -1; + spin_lock(&host->lock); - + if (host->flags & WBSD_FIGNORE_DETECT) { spin_unlock(&host->lock); return; } - + csr = inb(host->base + WBSD_CSR); WARN_ON(csr == 0xff); - + if (csr & WBSD_CARDPRESENT) { if (!(host->flags & WBSD_FCARD_PRESENT)) { DBG("Card inserted\n"); host->flags |= WBSD_FCARD_PRESENT; - - /* - * Delay card detection to allow electrical connections - * to stabilise. - */ - mod_timer(&host->timer, jiffies + HZ/2); + + delay = 500; } - - spin_unlock(&host->lock); } else if (host->flags & WBSD_FCARD_PRESENT) { DBG("Card removed\n"); host->flags &= ~WBSD_FCARD_PRESENT; - + if (host->mrq) { printk(KERN_ERR DRIVER_NAME ": Card removed during transfer!\n"); wbsd_reset(host); - + host->mrq->cmd->error = MMC_ERR_FAILED; tasklet_schedule(&host->finish_tasklet); } - - /* - * Unlock first since we might get a call back. - */ - spin_unlock(&host->lock); - mmc_detect_change(host->mmc); + delay = 0; } + + /* + * Unlock first since we might get a call back. + */ + + spin_unlock(&host->lock); + + if (delay != -1) + mmc_detect_change(host->mmc, msecs_to_jiffies(delay)); } static void wbsd_tasklet_fifo(unsigned long param) { struct wbsd_host* host = (struct wbsd_host*)param; struct mmc_data* data; - + spin_lock(&host->lock); - + if (!host->mrq) goto end; - + data = wbsd_get_data(host); if (!data) goto end; @@ -1154,7 +1218,7 @@ static void wbsd_tasklet_fifo(unsigned long param) tasklet_schedule(&host->finish_tasklet); } -end: +end: spin_unlock(&host->lock); } @@ -1162,23 +1226,23 @@ static void wbsd_tasklet_crc(unsigned long param) { struct wbsd_host* host = (struct wbsd_host*)param; struct mmc_data* data; - + spin_lock(&host->lock); - + if (!host->mrq) goto end; - + data = wbsd_get_data(host); if (!data) goto end; - + DBGF("CRC error\n"); data->error = MMC_ERR_BADCRC; - + tasklet_schedule(&host->finish_tasklet); -end: +end: spin_unlock(&host->lock); } @@ -1186,23 +1250,23 @@ static void wbsd_tasklet_timeout(unsigned long param) { struct wbsd_host* host = (struct wbsd_host*)param; struct mmc_data* data; - + spin_lock(&host->lock); - + if (!host->mrq) goto end; - + data = wbsd_get_data(host); if (!data) goto end; - + DBGF("Timeout\n"); data->error = MMC_ERR_TIMEOUT; - + tasklet_schedule(&host->finish_tasklet); -end: +end: spin_unlock(&host->lock); } @@ -1210,20 +1274,20 @@ static void wbsd_tasklet_finish(unsigned long param) { struct wbsd_host* host = (struct wbsd_host*)param; struct mmc_data* data; - + spin_lock(&host->lock); - + WARN_ON(!host->mrq); if (!host->mrq) goto end; - + data = wbsd_get_data(host); if (!data) goto end; wbsd_finish_data(host, data); - -end: + +end: spin_unlock(&host->lock); } @@ -1231,7 +1295,7 @@ static void wbsd_tasklet_block(unsigned long param) { struct wbsd_host* host = (struct wbsd_host*)param; struct mmc_data* data; - + spin_lock(&host->lock); if ((wbsd_read_index(host, WBSD_IDX_CRCSTATUS) & WBSD_CRC_MASK) != @@ -1240,15 +1304,15 @@ static void wbsd_tasklet_block(unsigned long param) data = wbsd_get_data(host); if (!data) goto end; - + DBGF("CRC error\n"); data->error = MMC_ERR_BADCRC; - + tasklet_schedule(&host->finish_tasklet); } -end: +end: spin_unlock(&host->lock); } @@ -1260,7 +1324,7 @@ static irqreturn_t wbsd_irq(int irq, void *dev_id, struct pt_regs *regs) { struct wbsd_host* host = dev_id; int isr; - + isr = inb(host->base + WBSD_ISR); /* @@ -1268,7 +1332,7 @@ static irqreturn_t wbsd_irq(int irq, void *dev_id, struct pt_regs *regs) */ if (isr == 0xff || isr == 0x00) return IRQ_NONE; - + host->isr |= isr; /* @@ -1286,7 +1350,7 @@ static irqreturn_t wbsd_irq(int irq, void *dev_id, struct pt_regs *regs) tasklet_hi_schedule(&host->block_tasklet); if (isr & WBSD_INT_TC) tasklet_schedule(&host->finish_tasklet); - + return IRQ_HANDLED; } @@ -1304,14 +1368,14 @@ static int __devinit wbsd_alloc_mmc(struct device* dev) { struct mmc_host* mmc; struct wbsd_host* host; - + /* * Allocate MMC structure. */ mmc = mmc_alloc_host(sizeof(struct wbsd_host), dev); if (!mmc) return -ENOMEM; - + host = mmc_priv(mmc); host->mmc = mmc; @@ -1324,37 +1388,38 @@ static int __devinit wbsd_alloc_mmc(struct device* dev) mmc->f_min = 375000; mmc->f_max = 24000000; mmc->ocr_avail = MMC_VDD_32_33|MMC_VDD_33_34; - + mmc->caps = MMC_CAP_4_BIT_DATA; + spin_lock_init(&host->lock); - + /* - * Set up detection timer + * Set up timers */ - init_timer(&host->timer); - host->timer.data = (unsigned long)host; - host->timer.function = wbsd_detect_card; - + init_timer(&host->ignore_timer); + host->ignore_timer.data = (unsigned long)host; + host->ignore_timer.function = wbsd_reset_ignore; + /* * Maximum number of segments. Worst case is one sector per segment * so this will be 64kB/512. */ mmc->max_hw_segs = 128; mmc->max_phys_segs = 128; - + /* * Maximum number of sectors in one transfer. Also limited by 64kB * buffer. */ mmc->max_sectors = 128; - + /* * Maximum segment size. Could be one segment with the maximum number * of segments. */ mmc->max_seg_size = mmc->max_sectors * 512; - + dev_set_drvdata(dev, mmc); - + return 0; } @@ -1362,18 +1427,18 @@ static void __devexit wbsd_free_mmc(struct device* dev) { struct mmc_host* mmc; struct wbsd_host* host; - + mmc = dev_get_drvdata(dev); if (!mmc) return; - + host = mmc_priv(mmc); BUG_ON(host == NULL); - - del_timer_sync(&host->timer); - + + del_timer_sync(&host->ignore_timer); + mmc_free_host(mmc); - + dev_set_drvdata(dev, NULL); } @@ -1385,7 +1450,7 @@ static int __devinit wbsd_scan(struct wbsd_host* host) { int i, j, k; int id; - + /* * Iterate through all ports, all codes to * find hardware that is in our known list. @@ -1394,44 +1459,47 @@ static int __devinit wbsd_scan(struct wbsd_host* host) { if (!request_region(config_ports[i], 2, DRIVER_NAME)) continue; - + for (j = 0;j < sizeof(unlock_codes)/sizeof(int);j++) { id = 0xFFFF; - - outb(unlock_codes[j], config_ports[i]); - outb(unlock_codes[j], config_ports[i]); - + + host->config = config_ports[i]; + host->unlock_code = unlock_codes[j]; + + wbsd_unlock_config(host); + outb(WBSD_CONF_ID_HI, config_ports[i]); id = inb(config_ports[i] + 1) << 8; outb(WBSD_CONF_ID_LO, config_ports[i]); id |= inb(config_ports[i] + 1); - + + wbsd_lock_config(host); + for (k = 0;k < sizeof(valid_ids)/sizeof(int);k++) { if (id == valid_ids[k]) - { + { host->chip_id = id; - host->config = config_ports[i]; - host->unlock_code = unlock_codes[i]; - + return 0; } } - + if (id != 0xFFFF) { DBG("Unknown hardware (id %x) found at %x\n", id, config_ports[i]); } - - outb(LOCK_CODE, config_ports[i]); } - + release_region(config_ports[i], 2); } - + + host->config = 0; + host->unlock_code = 0; + return -ENODEV; } @@ -1443,12 +1511,12 @@ static int __devinit wbsd_request_region(struct wbsd_host* host, int base) { if (io & 0x7) return -EINVAL; - + if (!request_region(base, 8, DRIVER_NAME)) return -EIO; - + host->base = io; - + return 0; } @@ -1456,12 +1524,12 @@ static void __devexit wbsd_release_regions(struct wbsd_host* host) { if (host->base) release_region(host->base, 8); - + host->base = 0; if (host->config) release_region(host->config, 2); - + host->config = 0; } @@ -1473,10 +1541,10 @@ static void __devinit wbsd_request_dma(struct wbsd_host* host, int dma) { if (dma < 0) return; - + if (request_dma(dma, DRIVER_NAME)) goto err; - + /* * We need to allocate a special buffer in * order for ISA to be able to DMA to it. @@ -1491,7 +1559,7 @@ static void __devinit wbsd_request_dma(struct wbsd_host* host, int dma) */ host->dma_addr = dma_map_single(host->mmc->dev, host->dma_buffer, WBSD_DMA_SIZE, DMA_BIDIRECTIONAL); - + /* * ISA DMA must be aligned on a 64k basis. */ @@ -1504,19 +1572,19 @@ static void __devinit wbsd_request_dma(struct wbsd_host* host, int dma) goto kfree; host->dma = dma; - + return; - + kfree: /* * If we've gotten here then there is some kind of alignment bug */ BUG_ON(1); - + dma_unmap_single(host->mmc->dev, host->dma_addr, WBSD_DMA_SIZE, DMA_BIDIRECTIONAL); host->dma_addr = (dma_addr_t)NULL; - + kfree(host->dma_buffer); host->dma_buffer = NULL; @@ -1537,7 +1605,7 @@ static void __devexit wbsd_release_dma(struct wbsd_host* host) kfree(host->dma_buffer); if (host->dma >= 0) free_dma(host->dma); - + host->dma = -1; host->dma_buffer = NULL; host->dma_addr = (dma_addr_t)NULL; @@ -1550,7 +1618,7 @@ static void __devexit wbsd_release_dma(struct wbsd_host* host) static int __devinit wbsd_request_irq(struct wbsd_host* host, int irq) { int ret; - + /* * Allocate interrupt. */ @@ -1558,7 +1626,7 @@ static int __devinit wbsd_request_irq(struct wbsd_host* host, int irq) ret = request_irq(irq, wbsd_irq, SA_SHIRQ, DRIVER_NAME, host); if (ret) return ret; - + host->irq = irq; /* @@ -1570,7 +1638,7 @@ static int __devinit wbsd_request_irq(struct wbsd_host* host, int irq) tasklet_init(&host->timeout_tasklet, wbsd_tasklet_timeout, (unsigned long)host); tasklet_init(&host->finish_tasklet, wbsd_tasklet_finish, (unsigned long)host); tasklet_init(&host->block_tasklet, wbsd_tasklet_block, (unsigned long)host); - + return 0; } @@ -1580,9 +1648,9 @@ static void __devexit wbsd_release_irq(struct wbsd_host* host) return; free_irq(host->irq, host); - + host->irq = 0; - + tasklet_kill(&host->card_tasklet); tasklet_kill(&host->fifo_tasklet); tasklet_kill(&host->crc_tasklet); @@ -1599,7 +1667,7 @@ static int __devinit wbsd_request_resources(struct wbsd_host* host, int base, int irq, int dma) { int ret; - + /* * Allocate I/O ports. */ @@ -1618,7 +1686,7 @@ static int __devinit wbsd_request_resources(struct wbsd_host* host, * Allocate DMA. */ wbsd_request_dma(host, dma); - + return 0; } @@ -1637,11 +1705,13 @@ static void __devexit wbsd_release_resources(struct wbsd_host* host) * Configure the resources the chip should use. */ -static void __devinit wbsd_chip_config(struct wbsd_host* host) +static void wbsd_chip_config(struct wbsd_host* host) { + wbsd_unlock_config(host); + /* * Reset the chip. - */ + */ wbsd_write_config(host, WBSD_CONF_SWRST, 1); wbsd_write_config(host, WBSD_CONF_SWRST, 0); @@ -1649,53 +1719,59 @@ static void __devinit wbsd_chip_config(struct wbsd_host* host) * Select SD/MMC function. */ wbsd_write_config(host, WBSD_CONF_DEVICE, DEVICE_SD); - + /* * Set up card detection. */ wbsd_write_config(host, WBSD_CONF_PINS, WBSD_PINS_DETECT_GP11); - + /* * Configure chip */ wbsd_write_config(host, WBSD_CONF_PORT_HI, host->base >> 8); wbsd_write_config(host, WBSD_CONF_PORT_LO, host->base & 0xff); - + wbsd_write_config(host, WBSD_CONF_IRQ, host->irq); - + if (host->dma >= 0) wbsd_write_config(host, WBSD_CONF_DRQ, host->dma); - + /* * Enable and power up chip. */ wbsd_write_config(host, WBSD_CONF_ENABLE, 1); wbsd_write_config(host, WBSD_CONF_POWER, 0x20); + + wbsd_lock_config(host); } /* * Check that configured resources are correct. */ - -static int __devinit wbsd_chip_validate(struct wbsd_host* host) + +static int wbsd_chip_validate(struct wbsd_host* host) { int base, irq, dma; - + + wbsd_unlock_config(host); + /* * Select SD/MMC function. */ wbsd_write_config(host, WBSD_CONF_DEVICE, DEVICE_SD); - + /* * Read configuration. */ base = wbsd_read_config(host, WBSD_CONF_PORT_HI) << 8; base |= wbsd_read_config(host, WBSD_CONF_PORT_LO); - + irq = wbsd_read_config(host, WBSD_CONF_IRQ); - + dma = wbsd_read_config(host, WBSD_CONF_DRQ); - + + wbsd_lock_config(host); + /* * Validate against given configuration. */ @@ -1705,10 +1781,24 @@ static int __devinit wbsd_chip_validate(struct wbsd_host* host) return 0; if ((dma != host->dma) && (host->dma != -1)) return 0; - + return 1; } +/* + * Powers down the SD function + */ + +static void wbsd_chip_poweroff(struct wbsd_host* host) +{ + wbsd_unlock_config(host); + + wbsd_write_config(host, WBSD_CONF_DEVICE, DEVICE_SD); + wbsd_write_config(host, WBSD_CONF_ENABLE, 0); + + wbsd_lock_config(host); +} + /*****************************************************************************\ * * * Devices setup and shutdown * @@ -1721,14 +1811,14 @@ static int __devinit wbsd_init(struct device* dev, int base, int irq, int dma, struct wbsd_host* host = NULL; struct mmc_host* mmc = NULL; int ret; - + ret = wbsd_alloc_mmc(dev); if (ret) return ret; - + mmc = dev_get_drvdata(dev); host = mmc_priv(mmc); - + /* * Scan for hardware. */ @@ -1747,7 +1837,7 @@ static int __devinit wbsd_init(struct device* dev, int base, int irq, int dma, return ret; } } - + /* * Request resources. */ @@ -1758,7 +1848,7 @@ static int __devinit wbsd_init(struct device* dev, int base, int irq, int dma, wbsd_free_mmc(dev); return ret; } - + /* * See if chip needs to be configured. */ @@ -1775,14 +1865,18 @@ static int __devinit wbsd_init(struct device* dev, int base, int irq, int dma, } else wbsd_chip_config(host); - + /* * Power Management stuff. No idea how this works. * Not tested. */ #ifdef CONFIG_PM if (host->config) + { + wbsd_unlock_config(host); wbsd_write_config(host, WBSD_CONF_PME, 0xA0); + wbsd_lock_config(host); + } #endif /* * Allow device to initialise itself properly. @@ -1793,10 +1887,10 @@ static int __devinit wbsd_init(struct device* dev, int base, int irq, int dma, * Reset the chip into a known state. */ wbsd_init_device(host); - + mmc_add_host(mmc); - printk(KERN_INFO "%s: W83L51xD", mmc->host_name); + printk(KERN_INFO "%s: W83L51xD", mmc_hostname(mmc)); if (host->chip_id != 0) printk(" id %x", (int)host->chip_id); printk(" at 0x%x irq %d", (int)host->base, (int)host->irq); @@ -1815,27 +1909,22 @@ static void __devexit wbsd_shutdown(struct device* dev, int pnp) { struct mmc_host* mmc = dev_get_drvdata(dev); struct wbsd_host* host; - + if (!mmc) return; host = mmc_priv(mmc); - + mmc_remove_host(mmc); + /* + * Power down the SD/MMC function. + */ if (!pnp) - { - /* - * Power down the SD/MMC function. - */ - wbsd_unlock_config(host); - wbsd_write_config(host, WBSD_CONF_DEVICE, DEVICE_SD); - wbsd_write_config(host, WBSD_CONF_ENABLE, 0); - wbsd_lock_config(host); - } - + wbsd_chip_poweroff(host); + wbsd_release_resources(host); - + wbsd_free_mmc(dev); } @@ -1865,7 +1954,7 @@ static int __devinit wbsd_pnp_probe(struct pnp_dev * pnpdev, const struct pnp_device_id *dev_id) { int io, irq, dma; - + /* * Get resources from PnP layer. */ @@ -1875,9 +1964,9 @@ wbsd_pnp_probe(struct pnp_dev * pnpdev, const struct pnp_device_id *dev_id) dma = pnp_dma(pnpdev, 0); else dma = -1; - + DBGF("PnP resources: port %3x irq %d dma %d\n", io, irq, dma); - + return wbsd_init(&pnpdev->dev, io, irq, dma, 1); } @@ -1893,23 +1982,59 @@ static void __devexit wbsd_pnp_remove(struct pnp_dev * dev) */ #ifdef CONFIG_PM -static int wbsd_suspend(struct device *dev, pm_message_t state, u32 level) + +static int wbsd_suspend(struct device *dev, pm_message_t state) { - DBGF("Not yet supported\n"); + struct mmc_host *mmc = dev_get_drvdata(dev); + struct wbsd_host *host; + int ret; + + if (!mmc) + return 0; + + DBG("Suspending...\n"); + + ret = mmc_suspend_host(mmc, state); + if (!ret) + return ret; + + host = mmc_priv(mmc); + + wbsd_chip_poweroff(host); return 0; } -static int wbsd_resume(struct device *dev, u32 level) +static int wbsd_resume(struct device *dev) { - DBGF("Not yet supported\n"); + struct mmc_host *mmc = dev_get_drvdata(dev); + struct wbsd_host *host; - return 0; + if (!mmc) + return 0; + + DBG("Resuming...\n"); + + host = mmc_priv(mmc); + + wbsd_chip_config(host); + + /* + * Allow device to initialise itself properly. + */ + mdelay(5); + + wbsd_init_device(host); + + return mmc_resume_host(mmc); } -#else + +#else /* CONFIG_PM */ + #define wbsd_suspend NULL #define wbsd_resume NULL -#endif + +#endif /* CONFIG_PM */ static struct platform_device *wbsd_device; @@ -1918,7 +2043,7 @@ static struct device_driver wbsd_driver = { .bus = &platform_bus_type, .probe = wbsd_probe, .remove = wbsd_remove, - + .suspend = wbsd_suspend, .resume = wbsd_resume, }; @@ -1941,7 +2066,7 @@ static struct pnp_driver wbsd_pnp_driver = { static int __init wbsd_drv_init(void) { int result; - + printk(KERN_INFO DRIVER_NAME ": Winbond W83L51xD SD/MMC card interface driver, " DRIVER_VERSION "\n"); @@ -1956,8 +2081,8 @@ static int __init wbsd_drv_init(void) return result; } -#endif /* CONFIG_PNP */ - +#endif /* CONFIG_PNP */ + if (nopnp) { result = driver_register(&wbsd_driver); @@ -1979,13 +2104,13 @@ static void __exit wbsd_drv_exit(void) if (!nopnp) pnp_unregister_driver(&wbsd_pnp_driver); - -#endif /* CONFIG_PNP */ + +#endif /* CONFIG_PNP */ if (nopnp) { platform_device_unregister(wbsd_device); - + driver_unregister(&wbsd_driver); } diff --git a/drivers/mmc/wbsd.h b/drivers/mmc/wbsd.h index 661a9f6a6e6f..249baa701cb0 100644 --- a/drivers/mmc/wbsd.h +++ b/drivers/mmc/wbsd.h @@ -106,6 +106,8 @@ #define WBSD_CLK_16M 0x02 #define WBSD_CLK_24M 0x03 +#define WBSD_DATA_WIDTH 0x01 + #define WBSD_DAT3_H 0x08 #define WBSD_FIFO_RESET 0x04 #define WBSD_SOFT_RESET 0x02 @@ -137,49 +139,50 @@ struct wbsd_host { struct mmc_host* mmc; /* MMC structure */ - + spinlock_t lock; /* Mutex */ int flags; /* Driver states */ #define WBSD_FCARD_PRESENT (1<<0) /* Card is present */ #define WBSD_FIGNORE_DETECT (1<<1) /* Ignore card detection */ - + struct mmc_request* mrq; /* Current request */ - + u8 isr; /* Accumulated ISR */ - + struct scatterlist* cur_sg; /* Current SG entry */ unsigned int num_sg; /* Number of entries left */ void* mapped_sg; /* vaddr of mapped sg */ - + unsigned int offset; /* Offset into current entry */ unsigned int remain; /* Data left in curren entry */ int size; /* Total size of transfer */ - + char* dma_buffer; /* ISA DMA buffer */ dma_addr_t dma_addr; /* Physical address for same */ int firsterr; /* See fifo functions */ - + u8 clk; /* Current clock speed */ - + unsigned char bus_width; /* Current bus width */ + int config; /* Config port */ u8 unlock_code; /* Code to unlock config */ int chip_id; /* ID of controller */ - + int base; /* I/O port base */ int irq; /* Interrupt */ int dma; /* DMA channel */ - + struct tasklet_struct card_tasklet; /* Tasklet structures */ struct tasklet_struct fifo_tasklet; struct tasklet_struct crc_tasklet; struct tasklet_struct timeout_tasklet; struct tasklet_struct finish_tasklet; struct tasklet_struct block_tasklet; - - struct timer_list timer; /* Card detection timer */ + + struct timer_list ignore_timer; /* Ignore detection timer */ }; |