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_block.c | 10 | ||||
| -rw-r--r-- | drivers/mmc/mmci.c | 1 | ||||
| -rw-r--r-- | drivers/mmc/pxamci.c | 11 | ||||
| -rw-r--r-- | drivers/mmc/wbsd.c | 125 | 
8 files changed, 1240 insertions, 39 deletions
diff --git a/drivers/mmc/Kconfig b/drivers/mmc/Kconfig index 4991bbd054f..c483a863b11 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 89510c2086c..e351e71146e 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 00000000000..aaf04638054 --- /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 00000000000..341cbdf0bac --- /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_block.c b/drivers/mmc/mmc_block.c index fa83f15fdf1..9b629856c73 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; @@ -97,7 +103,7 @@ static int mmc_blk_open(struct inode *inode, struct file *filp)  		ret = 0;  		if ((filp->f_mode & FMODE_WRITE) && -			mmc_card_readonly(md->queue.card)) +			mmc_blk_readonly(md->queue.card))  			ret = -EROFS;  	} @@ -410,7 +416,7 @@ static int mmc_blk_probe(struct mmc_card *card)  	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, -		mmc_card_readonly(card)?"(ro)":""); +		mmc_blk_readonly(card)?"(ro)":"");  	mmc_set_drvdata(card, md);  	add_disk(md->disk); diff --git a/drivers/mmc/mmci.c b/drivers/mmc/mmci.c index 91c74843dc0..1e6bdba2675 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> diff --git a/drivers/mmc/pxamci.c b/drivers/mmc/pxamci.c index b53af57074e..f31e247b2cb 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> @@ -571,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 3cbca7cbea8..942668e93a7 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> @@ -1033,13 +1033,16 @@ static void wbsd_set_ios(struct mmc_host* mmc, struct mmc_ios* ios)  	}  	else  	{ -		setup &= ~WBSD_DAT3_H; +		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); +			/* +			 * 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); @@ -1461,8 +1464,10 @@ static int __devinit wbsd_scan(struct wbsd_host* host)  		{  			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; @@ -1470,13 +1475,13 @@ static int __devinit wbsd_scan(struct wbsd_host* host)  			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;  				} @@ -1487,13 +1492,14 @@ static int __devinit wbsd_scan(struct wbsd_host* host)  				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;  } @@ -1699,8 +1705,10 @@ 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.  	 */ @@ -1733,16 +1741,20 @@ static void __devinit wbsd_chip_config(struct wbsd_host* host)  	 */  	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.  	 */ @@ -1758,6 +1770,8 @@ static int __devinit wbsd_chip_validate(struct wbsd_host* host)  	dma = wbsd_read_config(host, WBSD_CONF_DRQ); +	wbsd_lock_config(host); +  	/*  	 * Validate against given configuration.  	 */ @@ -1771,6 +1785,20 @@ static int __devinit wbsd_chip_validate(struct wbsd_host* host)  	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                                                * @@ -1844,7 +1872,11 @@ static int __devinit wbsd_init(struct device* dev, int base, int irq, int dma,  	 */  #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. @@ -1885,16 +1917,11 @@ static void __devexit wbsd_shutdown(struct device* dev, int pnp)  	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); @@ -1955,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;  | 
